@kenkaiiii/gg-ai 5.90.0 → 5.91.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/NOTICE ADDED
@@ -0,0 +1,52 @@
1
+ GG Coder
2
+ Copyright (C) 2025-2026 KenKai
3
+
4
+ This program is free software: you can redistribute it and/or modify it under
5
+ the terms of the GNU Affero General Public License as published by the Free
6
+ Software Foundation, version 3 only (AGPL-3.0-only). See LICENSE.
7
+
8
+ This program is distributed in the hope that it will be useful, but WITHOUT
9
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
10
+ FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
11
+ details.
12
+
13
+ Releases before 4 November 2026 (up to and including v0.102.0 of the desktop
14
+ app and 5.91.0 of the npm packages) were published under the MIT License, and
15
+ copies already received under those terms keep them.
16
+
17
+ --------------------------------------------------------------------------------
18
+ Contributions under the MIT License
19
+
20
+ The WSL-to-Windows-host audio playback in packages/ggcoder/src/core/radio.ts
21
+ (isWsl and tryPlayOnWindowsHost) is derived from contributions by
22
+ creativeprofit22 (pull requests #15 and #16, May 2026), made under these terms:
23
+
24
+ Copyright (c) 2026 creativeprofit22
25
+
26
+ Permission is hereby granted, free of charge, to any person obtaining a copy
27
+ of this software and associated documentation files (the "Software"), to deal
28
+ in the Software without restriction, including without limitation the rights
29
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
30
+ copies of the Software, and to permit persons to whom the Software is
31
+ furnished to do so, subject to the following conditions:
32
+
33
+ The above copyright notice and this permission notice shall be included in
34
+ all copies or substantial portions of the Software.
35
+
36
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
37
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
38
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
39
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
40
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
41
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
42
+ SOFTWARE.
43
+
44
+ --------------------------------------------------------------------------------
45
+ Third-party components
46
+
47
+ Bundled third-party code, fonts and media keep their own licenses, listed in:
48
+ gg-app/src/assets/fonts/THIRD-PARTY.md
49
+ packages/ggcoder/assets/motion/THIRD-PARTY.md
50
+ packages/gg-world/CREDITS.md
51
+ and in the LICENSE / OFL.txt files beside each component. npm and Cargo
52
+ dependencies are installed under their own licenses.
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  <p align="center">
8
8
  <a href="https://www.npmjs.com/package/@kenkaiiii/gg-ai"><img src="https://img.shields.io/npm/v/@kenkaiiii/gg-ai?style=for-the-badge" alt="npm version"></a>
9
- <a href="../../LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue.svg?style=for-the-badge" alt="MIT License"></a>
9
+ <a href="../../LICENSE"><img src="https://img.shields.io/badge/License-AGPL--3.0-blue.svg?style=for-the-badge" alt="AGPL-3.0 License"></a>
10
10
  </p>
11
11
 
12
12
  One function. Flat options. Switch providers by changing a string. No adapters, no plugins, no wrapper classes.
@@ -38,21 +38,21 @@ Tool parameters are Zod schemas. Converted to JSON Schema at the provider bounda
38
38
 
39
39
  ## Providers
40
40
 
41
- | Provider | Models | Notes |
42
- |---|---|---|
43
- | `anthropic` | Claude Fable 5.1, Opus 5.5, Sonnet 5.5, Haiku 5.5 | Extended thinking, prompt caching, server-side compaction |
44
- | `openai` | GPT-6 Astra, GPT-6.1 Sol, GPT-6 Luna | OAuth (Codex endpoint) or API key |
45
- | `gemini` | Gemini 3.1 Pro (Preview), 3.8 / 3.7 / 3.5 Flash, 3.5 / 3.1 Flash Lite | OAuth (Code Assist); native video input |
46
- | `xai` | Grok 4.7 | OpenAI-compatible, `https://api.x.ai/v1` |
47
- | `moonshot` | Kimi K3, K2.8 Preview, K2.7 Code, K2.7 Code HighSpeed | OpenAI-compatible; native video input |
48
- | `glm` | GLM-5.3, GLM-5.3-Flash | Z.AI coding endpoint, OpenAI-compatible |
49
- | `minimax` | MiniMax M3 | Anthropic-compatible endpoint |
50
- | `xiaomi` | MiMo-V2.6-Pro, MiMo-V2.6-Flash, MiMo-V2.6-Pro-UltraSpeed | OpenAI-compatible |
51
- | `deepseek` | DeepSeek V4 Pro, V4.1 Flash | OpenAI-compatible |
52
- | `sakana` | Fugu, Fugu Max, Fugu Ultra | OpenAI-compatible |
53
- | `openrouter` | Qwen3.8 Max, or any OpenRouter model id | OpenAI-compatible gateway |
54
- | `huggingface` | Kimi K2.7 Code, DeepSeek V4.1 Flash, GPT-OSS 120B | Inference Providers router, OpenAI-compatible |
55
- | `local` | Any model your server exposes | Ollama, LM Studio, llama.cpp, vLLM; `baseUrl` is required |
41
+ | Provider | Models | Notes |
42
+ | ------------- | --------------------------------------------------------------------- | --------------------------------------------------------- |
43
+ | `anthropic` | Claude Fable 5.1, Opus 5.5, Sonnet 5.5, Haiku 5.5 | Extended thinking, prompt caching, server-side compaction |
44
+ | `openai` | GPT-6 Astra, GPT-6.1 Sol, GPT-6 Luna | OAuth (Codex endpoint) or API key |
45
+ | `gemini` | Gemini 3.1 Pro (Preview), 3.8 / 3.7 / 3.5 Flash, 3.5 / 3.1 Flash Lite | OAuth (Code Assist); native video input |
46
+ | `xai` | Grok 4.7 | OpenAI-compatible, `https://api.x.ai/v1` |
47
+ | `moonshot` | Kimi K3, K2.8 Preview, K2.7 Code, K2.7 Code HighSpeed | OpenAI-compatible; native video input |
48
+ | `glm` | GLM-5.3, GLM-5.3-Flash | Z.AI coding endpoint, OpenAI-compatible |
49
+ | `minimax` | MiniMax M3 | Anthropic-compatible endpoint |
50
+ | `xiaomi` | MiMo-V2.6-Pro, MiMo-V2.6-Flash, MiMo-V2.6-Pro-UltraSpeed | OpenAI-compatible |
51
+ | `deepseek` | DeepSeek V4 Pro, V4.1 Flash | OpenAI-compatible |
52
+ | `sakana` | Fugu, Fugu Max, Fugu Ultra | OpenAI-compatible |
53
+ | `openrouter` | Qwen3.8 Max, or any OpenRouter model id | OpenAI-compatible gateway |
54
+ | `huggingface` | Kimi K2.7 Code, DeepSeek V4.1 Flash, GPT-OSS 120B | Inference Providers router, OpenAI-compatible |
55
+ | `local` | Any model your server exposes | Ollama, LM Studio, llama.cpp, vLLM; `baseUrl` is required |
56
56
 
57
57
  The model lists are the ones GG Coder ships in its registry (`@kenkaiiii/gg-core`). `stream()` passes `model` straight through, so any id the provider accepts works. Every provider's default endpoint can be overridden with `baseUrl`, and `providerRegistry.register()` adds your own.
58
58
 
@@ -60,45 +60,45 @@ The model lists are the ones GG Coder ships in its registry (`@kenkaiiii/gg-core
60
60
 
61
61
  ## Stream events
62
62
 
63
- | Event | Description |
64
- |---|---|
65
- | `text_delta` | Incremental text output |
66
- | `thinking_delta` | Reasoning output, from any provider that streams it |
67
- | `toolcall_delta` | Streaming tool call arguments |
68
- | `toolcall_done` | Completed tool call with parsed args |
69
- | `server_toolcall` | Server-side tool invocation |
70
- | `server_toolresult` | Server-side tool result |
71
- | `keepalive` | Provider heartbeat: the stream is alive but has no new content yet |
72
- | `done` | Stream finished, includes stop reason |
73
- | `error` | Error occurred |
63
+ | Event | Description |
64
+ | ------------------- | ------------------------------------------------------------------ |
65
+ | `text_delta` | Incremental text output |
66
+ | `thinking_delta` | Reasoning output, from any provider that streams it |
67
+ | `toolcall_delta` | Streaming tool call arguments |
68
+ | `toolcall_done` | Completed tool call with parsed args |
69
+ | `server_toolcall` | Server-side tool invocation |
70
+ | `server_toolresult` | Server-side tool result |
71
+ | `keepalive` | Provider heartbeat: the stream is alive but has no new content yet |
72
+ | `done` | Stream finished, includes stop reason |
73
+ | `error` | Error occurred |
74
74
 
75
75
  ---
76
76
 
77
77
  ## Options
78
78
 
79
- | Option | Type | Description |
80
- |---|---|---|
81
- | `provider` | `Provider` (any id in the table above) | Required |
82
- | `model` | `string` | Required |
83
- | `messages` | `Message[]` | Required |
84
- | `tools` | `Tool[]` | Tool definitions with Zod schemas |
85
- | `toolChoice` | `"auto" \| "none" \| "required" \| { name }` | Tool selection strategy |
86
- | `serverTools` | `ServerToolDefinition[]` | Server-side tool definitions |
87
- | `maxTokens` | `number` | Max output tokens |
88
- | `temperature` | `number` | Sampling temperature |
89
- | `topP` | `number` | Nucleus sampling |
90
- | `stop` | `string[]` | Stop sequences |
91
- | `thinking` | `"low" \| "medium" \| "high" \| "xhigh" \| "max" \| "ultra"` | Reasoning effort; each provider maps it to what the model supports |
92
- | `apiKey` | `string` | Provider API key |
93
- | `baseUrl` | `string` | Custom endpoint |
94
- | `signal` | `AbortSignal` | Cancellation |
95
- | `cacheRetention` | `"none" \| "short" \| "long"` | Prompt cache preference |
96
- | `promptCacheKey` | `string` | Stable cache routing key (OpenAI, Moonshot, Gemini) |
97
- | `webSearch` | `boolean` | Provider-native web search where supported |
98
- | `compaction` | `boolean` | Server-side compaction (Anthropic only) |
99
- | `clearToolUses` | `boolean` | Server-side clearing of old tool results (Anthropic only) |
100
- | `fetch` | `typeof fetch` | Custom fetch, e.g. for React Native |
101
- | `providerOptions` | `Record<string, unknown>` | Provider-specific body fields; see below |
79
+ | Option | Type | Description |
80
+ | ----------------- | ------------------------------------------------------------ | ------------------------------------------------------------------ |
81
+ | `provider` | `Provider` (any id in the table above) | Required |
82
+ | `model` | `string` | Required |
83
+ | `messages` | `Message[]` | Required |
84
+ | `tools` | `Tool[]` | Tool definitions with Zod schemas |
85
+ | `toolChoice` | `"auto" \| "none" \| "required" \| { name }` | Tool selection strategy |
86
+ | `serverTools` | `ServerToolDefinition[]` | Server-side tool definitions |
87
+ | `maxTokens` | `number` | Max output tokens |
88
+ | `temperature` | `number` | Sampling temperature |
89
+ | `topP` | `number` | Nucleus sampling |
90
+ | `stop` | `string[]` | Stop sequences |
91
+ | `thinking` | `"low" \| "medium" \| "high" \| "xhigh" \| "max" \| "ultra"` | Reasoning effort; each provider maps it to what the model supports |
92
+ | `apiKey` | `string` | Provider API key |
93
+ | `baseUrl` | `string` | Custom endpoint |
94
+ | `signal` | `AbortSignal` | Cancellation |
95
+ | `cacheRetention` | `"none" \| "short" \| "long"` | Prompt cache preference |
96
+ | `promptCacheKey` | `string` | Stable cache routing key (OpenAI, Moonshot, Gemini) |
97
+ | `webSearch` | `boolean` | Provider-native web search where supported |
98
+ | `compaction` | `boolean` | Server-side compaction (Anthropic only) |
99
+ | `clearToolUses` | `boolean` | Server-side clearing of old tool results (Anthropic only) |
100
+ | `fetch` | `typeof fetch` | Custom fetch, e.g. for React Native |
101
+ | `providerOptions` | `Record<string, unknown>` | Provider-specific body fields; see below |
102
102
 
103
103
  ### Provider-specific body fields
104
104
 
@@ -152,6 +152,7 @@ const { results } = await rerank({
152
152
  ```
153
153
 
154
154
  **What `embed()` guarantees**
155
+
155
156
  - Vectors come back in input order, even when the provider returns them out of order (gg-ai sorts by `index`).
156
157
  - Long inputs are split at the provider's per-request limit (table below; override with `batchSize`). Batches run one at a time, in order.
157
158
  - The response is checked: exactly one vector per input, all numbers, all the same length, and `dimensions` long when you asked for that. If any check fails it throws `ProviderError`. Nothing is padded or truncated.
@@ -163,18 +164,19 @@ const { results } = await rerank({
163
164
 
164
165
  ### Provider support
165
166
 
166
- | Provider | `embed` | Max inputs per request | `inputType` | `rerank` |
167
- |---|---|---|---|---|
168
- | `openai` | ✓ `/v1/embeddings` | 2048 | ignored | — |
169
- | `openrouter` | ✓ `/api/v1/embeddings` | 64 (gg-ai default; OpenRouter documents no limit) | `input_type` | ✓ `/api/v1/rerank` |
170
- | `gemini` | ✓ native `batchEmbedContents` | 100 | `taskType` | — |
171
- | `local` | ✓ `{baseUrl}/embeddings` (Ollama, LM Studio, llama.cpp, vLLM) | 64 (gg-ai default) | ignored | ✓ `{baseUrl}/rerank` (llama.cpp `--rerank`, vLLM; Ollama and LM Studio have none and return 404) |
172
- | `palsu` | ✓ deterministic fake | all | ignored | ✓ deterministic fake |
173
- | `anthropic`, `xiaomi`, `glm`, `moonshot`, `minimax`, `deepseek`, `sakana`, `xai`, `huggingface` | — | | | — |
167
+ | Provider | `embed` | Max inputs per request | `inputType` | `rerank` |
168
+ | ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------- | ------------------------------------------------- | ------------ | ------------------------------------------------------------------------------------------------ |
169
+ | `openai` | ✓ `/v1/embeddings` | 2048 | ignored | — |
170
+ | `openrouter` | ✓ `/api/v1/embeddings` | 64 (gg-ai default; OpenRouter documents no limit) | `input_type` | ✓ `/api/v1/rerank` |
171
+ | `gemini` | ✓ native `batchEmbedContents` | 100 | `taskType` | — |
172
+ | `local` | ✓ `{baseUrl}/embeddings` (Ollama, LM Studio, llama.cpp, vLLM) | 64 (gg-ai default) | ignored | ✓ `{baseUrl}/rerank` (llama.cpp `--rerank`, vLLM; Ollama and LM Studio have none and return 404) |
173
+ | `palsu` | ✓ deterministic fake | all | ignored | ✓ deterministic fake |
174
+ | `anthropic`, `xiaomi`, `glm`, `moonshot`, `minimax`, `deepseek`, `sakana`, `xai`, `huggingface` | — | | | — |
174
175
 
175
176
  Calling `embed()` or `rerank()` on a provider without that capability throws `GGAIError` with `source: "capability"`. Check first with `providerRegistry.supports(name, "embed")`.
176
177
 
177
178
  Notes:
179
+
178
180
  - **Gemini** embeddings use the Gemini API with an AI Studio key, sent as `x-goog-api-key`. The Code Assist sign-in that `stream()` uses for Gemini chat has no embeddings endpoint.
179
181
  - **OpenAI** embeddings need a platform API key. ChatGPT-subscription (Codex) tokens can't call `/embeddings`.
180
182
  - Anthropic has no embeddings API (it points to Voyage AI). The other providers in the last row don't document an embeddings or rerank endpoint on the API gg-ai talks to, so none is registered for them.
@@ -184,14 +186,14 @@ Notes:
184
186
 
185
187
  Failures use the same error model as `stream()`:
186
188
 
187
- | Failure | Error |
188
- |---|---|
189
- | HTTP 401 / 403 | `ProviderError`, `source: "auth"` |
190
- | HTTP 402, or billing text such as `insufficient_quota` | `ProviderError` with "usage limit reached" (`isUsageLimitError()` is true) |
191
- | HTTP 429 | `ProviderError`, `statusCode: 429`, `resetsAt` from `Retry-After` when sent |
192
- | Other HTTP errors | `ProviderError` with `statusCode` and `requestId` |
193
- | Connection failure | `GGAIError`, `source: "network"` |
194
- | `signal` aborted | `AbortError` (`DOMException`); remaining batches are skipped |
189
+ | Failure | Error |
190
+ | ------------------------------------------------------ | --------------------------------------------------------------------------- |
191
+ | HTTP 401 / 403 | `ProviderError`, `source: "auth"` |
192
+ | HTTP 402, or billing text such as `insufficient_quota` | `ProviderError` with "usage limit reached" (`isUsageLimitError()` is true) |
193
+ | HTTP 429 | `ProviderError`, `statusCode: 429`, `resetsAt` from `Retry-After` when sent |
194
+ | Other HTTP errors | `ProviderError` with `statusCode` and `requestId` |
195
+ | Connection failure | `GGAIError`, `source: "network"` |
196
+ | `signal` aborted | `AbortError` (`DOMException`); remaining batches are skipped |
195
197
 
196
198
  Provider error bodies are trimmed to their message, then redacted and truncated, so API keys never end up in an error message.
197
199
 
@@ -205,7 +207,9 @@ A provider registered at runtime can offer embeddings and reranking too. `stream
205
207
  import { providerRegistry, embed, type Provider, type ProviderEntry } from "@kenkaiiii/gg-ai";
206
208
 
207
209
  providerRegistry.register("my-search", {
208
- stream: () => { throw new Error("chat not supported"); },
210
+ stream: () => {
211
+ throw new Error("chat not supported");
212
+ },
209
213
  embedBatchSize: 128,
210
214
  embed: async ({ model, input, dimensions, apiKey, signal, fetch = globalThis.fetch }) => {
211
215
  const res = await fetch("https://example.com/embed", {
@@ -234,4 +238,4 @@ await embed({ provider: "my-search" as Provider, model: "v1", input: ["hello"] }
234
238
 
235
239
  ## License
236
240
 
237
- MIT
241
+ AGPL-3.0-only. See [LICENSE](../../LICENSE).
package/dist/index.cjs CHANGED
@@ -335,6 +335,8 @@ function formatError(err) {
335
335
  guidance: fastRejection.guidance
336
336
  };
337
337
  }
338
+ const miniMax = err.provider === "minimax" ? formatMiniMaxError(err, cleanMessage) : void 0;
339
+ if (miniMax) return miniMax;
338
340
  if (isUsageLimitError(err)) {
339
341
  const resetClause = err.resetsAt ? ` It resets at ${formatResetTime(err.resetsAt)}.` : "";
340
342
  return {
@@ -441,6 +443,38 @@ function inferSource(err) {
441
443
  }
442
444
  return "ggcoder";
443
445
  }
446
+ function formatMiniMaxError(err, cleanMessage) {
447
+ const code = /\((\d{4,5})\)/.exec(err.message)?.[1];
448
+ const base = {
449
+ source: "provider",
450
+ message: cleanMessage,
451
+ provider: err.provider,
452
+ statusCode: err.statusCode,
453
+ ...err.requestId ? { requestId: err.requestId } : {}
454
+ };
455
+ if (code === "2049" || code === "1004") {
456
+ return {
457
+ ...base,
458
+ headline: "MiniMax didn't accept this API key.",
459
+ guidance: "A MiniMax key only works in the region that issued it (Global: platform.minimax.io, Mainland China: platform.minimaxi.com). Connect MiniMax again with the key so GG Coder can detect its region, and check the key is active."
460
+ };
461
+ }
462
+ if (code === "2061") {
463
+ return {
464
+ ...base,
465
+ headline: "Your MiniMax plan doesn't include this model.",
466
+ guidance: "Switch to MiniMax M3.1 Flash in the model selector \u2014 it's the model M Plan and Token Plan cover. MiniMax M3 needs a pay-as-you-go key."
467
+ };
468
+ }
469
+ if (code === "1008") {
470
+ return {
471
+ ...base,
472
+ headline: "MiniMax has no balance for this request.",
473
+ guidance: "Check this key's plan is active, or its pay-as-you-go balance, at platform.minimax.io (platform.minimaxi.com for Mainland China keys). Plan keys only cover MiniMax M3.1 Flash. Not a GG Coder issue."
474
+ };
475
+ }
476
+ return void 0;
477
+ }
444
478
  function providerGuidance(provider, message, statusCode) {
445
479
  const name = provider ? providerDisplayName(provider) : "the provider";
446
480
  const status = provider ? PROVIDER_STATUS_URL[provider] : void 0;
@@ -1818,6 +1852,13 @@ function isAdaptiveThinkingModel(model) {
1818
1852
  return /opus-5|opus-4[-.]8|opus-4[-.]7|opus-4[-.]6|sonnet-5|haiku-5|fable-5|mythos-5/.test(model);
1819
1853
  }
1820
1854
  function toAnthropicThinking(level, maxTokens, model) {
1855
+ if (model.startsWith("MiniMax-M3.1")) {
1856
+ return {
1857
+ thinking: { type: "adaptive" },
1858
+ maxTokens,
1859
+ outputConfig: { effort: level === "ultra" ? "max" : level }
1860
+ };
1861
+ }
1821
1862
  if (isAdaptiveThinkingModel(model)) {
1822
1863
  let effort = level;
1823
1864
  if (effort === "xhigh" && !/fable-5|mythos-5|opus-5|opus-4-8|opus-4-7|sonnet-5[-.]5|haiku-5[-.]5/.test(model)) {
@@ -2027,6 +2068,14 @@ function toCodexReasoningEffort(level, model) {
2027
2068
  if (level !== "ultra") return level;
2028
2069
  return model === "gpt-6-astra" || model === "gpt-6.1-sol" ? "xhigh" : "max";
2029
2070
  }
2071
+ var DECLARED_EFFORT_ORDER = ["low", "medium", "high", "xhigh", "max"];
2072
+ function effortForRequest(level, model, declared) {
2073
+ const rungs = DECLARED_EFFORT_ORDER.filter((rung) => declared?.includes(rung));
2074
+ if (rungs.length === 0) return toOpenAIReasoningEffort(level, model);
2075
+ const wanted = level === "ultra" ? DECLARED_EFFORT_ORDER.length - 1 : DECLARED_EFFORT_ORDER.indexOf(level);
2076
+ const atOrBelow = rungs.filter((rung) => DECLARED_EFFORT_ORDER.indexOf(rung) <= wanted);
2077
+ return atOrBelow[atOrBelow.length - 1] ?? rungs[0];
2078
+ }
2030
2079
  function toOpenAIReasoningEffort(level, model) {
2031
2080
  const effort = level === "max" || level === "ultra" ? "xhigh" : level;
2032
2081
  if (model.startsWith("fugu") && (effort === "low" || effort === "medium")) {
@@ -2339,8 +2388,7 @@ async function* runStream(options) {
2339
2388
  ...system ? { system } : {},
2340
2389
  ...thinking ? { thinking } : {},
2341
2390
  ...outputConfig ? { output_config: outputConfig } : {},
2342
- ...options.temperature != null && !thinking ? { temperature: options.temperature } : {},
2343
- ...options.topP != null ? { top_p: options.topP } : {},
2391
+ // API version 2026-10-21 (SDK 1.x) rejects temperature/top_p/top_k with a 400.
2344
2392
  ...options.stop ? { stop_sequences: options.stop } : {},
2345
2393
  ...(deferredPlan?.live ?? options.tools)?.length || options.serverTools?.length || options.webSearch ? (() => {
2346
2394
  const reservedServerNames = /* @__PURE__ */ new Set();
@@ -2441,7 +2489,7 @@ async function* runStream(options) {
2441
2489
  yield* synthesizeEventsFromMessage(message);
2442
2490
  return messageToResponse(message);
2443
2491
  } catch (err) {
2444
- throw toError(err, { fastMode: useFastMode });
2492
+ throw toError(err, { fastMode: useFastMode, provider: options.provider });
2445
2493
  }
2446
2494
  }
2447
2495
  const contentParts = [];
@@ -2557,7 +2605,7 @@ async function* runStream(options) {
2557
2605
  const rawPartial = accum.argsJson;
2558
2606
  const snippet = rawPartial.length > 200 ? `${rawPartial.slice(0, 200)}\u2026` : rawPartial;
2559
2607
  throw new ProviderError(
2560
- "anthropic",
2608
+ options.provider,
2561
2609
  `Tool "${accum.toolName}" input JSON was truncated in the stream (${rawPartial.length} bytes): ${snippet}; ${parseErr.message}`,
2562
2610
  { cause: parseErr }
2563
2611
  );
@@ -2660,15 +2708,15 @@ async function* runStream(options) {
2660
2708
  }
2661
2709
  }
2662
2710
  } catch (err) {
2663
- throw toError(err, { fastMode: useFastMode });
2711
+ throw toError(err, { fastMode: useFastMode, provider: options.provider });
2664
2712
  }
2665
2713
  if (!receivedAnyEvent) {
2666
- throw new ProviderError("anthropic", "Stream ended without producing any events.", {
2714
+ throw new ProviderError(options.provider, "Stream ended without producing any events.", {
2667
2715
  statusCode: 504
2668
2716
  });
2669
2717
  }
2670
2718
  if (stopReason === null) {
2671
- throw new ProviderError("anthropic", "Stream ended before completion (no stop_reason).", {
2719
+ throw new ProviderError(options.provider, "Stream ended before completion (no stop_reason).", {
2672
2720
  statusCode: 504,
2673
2721
  cause: { partialContent: contentParts, outputTokens }
2674
2722
  });
@@ -2810,6 +2858,7 @@ function readUnifiedRateLimit(headers) {
2810
2858
  return { rejected: status === "rejected", ...resetsAt ? { resetsAt } : {} };
2811
2859
  }
2812
2860
  function toError(err, context) {
2861
+ const provider = context?.provider ?? "anthropic";
2813
2862
  if (err instanceof ProviderError) return err;
2814
2863
  if (err instanceof import_sdk.default.APIError) {
2815
2864
  const errorBody = err.error;
@@ -2843,22 +2892,22 @@ function toError(err, context) {
2843
2892
  }
2844
2893
  if (isHardBillingMessage(message)) {
2845
2894
  const usageMessage = /usage limit reached/i.test(message) ? message : `usage limit reached: ${message}`;
2846
- return new ProviderError("anthropic", usageMessage, {
2895
+ return new ProviderError(provider, usageMessage, {
2847
2896
  statusCode: err.status,
2848
2897
  ...requestId ? { requestId } : {},
2849
2898
  cause: err
2850
2899
  });
2851
2900
  }
2852
- return new ProviderError("anthropic", message, {
2901
+ return new ProviderError(provider, message, {
2853
2902
  statusCode: err.status,
2854
2903
  ...requestId ? { requestId } : {},
2855
2904
  cause: err
2856
2905
  });
2857
2906
  }
2858
2907
  if (err instanceof Error) {
2859
- return new ProviderError("anthropic", err.message, { cause: err });
2908
+ return new ProviderError(provider, err.message, { cause: err });
2860
2909
  }
2861
- return new ProviderError("anthropic", String(err));
2910
+ return new ProviderError(provider, String(err));
2862
2911
  }
2863
2912
 
2864
2913
  // src/providers/openai.ts
@@ -3062,7 +3111,13 @@ async function* runStream2(options) {
3062
3111
  ...effectiveTemp != null && !options.thinking && !hasFixedKimiSampling ? { temperature: effectiveTemp } : {},
3063
3112
  ...options.topP != null && !hasFixedKimiSampling ? { top_p: options.topP } : {},
3064
3113
  ...options.stop ? { stop: options.stop } : {},
3065
- ...options.thinking && !usesThinkingParam && !isKimiK3 && !isKimiK27 && !isLocal ? { reasoning_effort: toOpenAIReasoningEffort(options.thinking, options.model) } : {},
3114
+ ...options.thinking && !usesThinkingParam && !isKimiK3 && !isKimiK27 && !isLocal ? {
3115
+ reasoning_effort: effortForRequest(
3116
+ options.thinking,
3117
+ options.model,
3118
+ options.thinkingLevels
3119
+ )
3120
+ } : {},
3066
3121
  ...options.tools?.length ? {
3067
3122
  tools: toOpenAITools(options.tools, {
3068
3123
  strict: supportsStrictToolSampling(options.provider) && (options.strictTools ?? true)