@oh-my-pi/pi-ai 8.4.5 → 8.6.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oh-my-pi/pi-ai",
3
- "version": "8.4.5",
3
+ "version": "8.6.0",
4
4
  "description": "Unified LLM API with automatic model discovery and provider configuration",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -56,7 +56,6 @@
56
56
  "test": "bun test"
57
57
  },
58
58
  "dependencies": {
59
- "@oh-my-pi/pi-utils": "8.4.5",
60
59
  "@anthropic-ai/sdk": "^0.71.2",
61
60
  "@aws-sdk/client-bedrock-runtime": "^3.975.0",
62
61
  "@bufbuild/protobuf": "^2.10.2",
@@ -64,13 +63,16 @@
64
63
  "@connectrpc/connect-node": "^2.1.1",
65
64
  "@google/genai": "^1.38.0",
66
65
  "@mistralai/mistralai": "^1.13.0",
66
+ "@oh-my-pi/pi-utils": "8.6.0",
67
67
  "@sinclair/typebox": "^0.34.41",
68
+ "@smithy/node-http-handler": "4.4.8",
68
69
  "ajv": "^8.17.1",
69
70
  "ajv-formats": "^3.0.1",
70
71
  "chalk": "^5.6.2",
71
- "json5": "^2.2.3",
72
72
  "openai": "^6.16.0",
73
73
  "partial-json": "^0.1.7",
74
+ "proxy-agent": "^6.5.0",
75
+ "undici": "^7.19.1",
74
76
  "zod-to-json-schema": "^3.24.6"
75
77
  },
76
78
  "keywords": [
@@ -4279,23 +4279,6 @@ export const MODELS = {
4279
4279
  contextWindow: 204800,
4280
4280
  maxTokens: 131072,
4281
4281
  } satisfies Model<"openai-completions">,
4282
- "glm-4.7-free": {
4283
- id: "glm-4.7-free",
4284
- name: "GLM-4.7",
4285
- api: "openai-completions",
4286
- provider: "opencode",
4287
- baseUrl: "https://opencode.ai/zen/v1",
4288
- reasoning: true,
4289
- input: ["text"],
4290
- cost: {
4291
- input: 0,
4292
- output: 0,
4293
- cacheRead: 0,
4294
- cacheWrite: 0,
4295
- },
4296
- contextWindow: 204800,
4297
- maxTokens: 131072,
4298
- } satisfies Model<"openai-completions">,
4299
4282
  "gpt-5": {
4300
4283
  id: "gpt-5",
4301
4284
  name: "GPT-5",
@@ -4449,23 +4432,6 @@ export const MODELS = {
4449
4432
  contextWindow: 400000,
4450
4433
  maxTokens: 128000,
4451
4434
  } satisfies Model<"openai-responses">,
4452
- "grok-code": {
4453
- id: "grok-code",
4454
- name: "Grok Code Fast 1",
4455
- api: "openai-completions",
4456
- provider: "opencode",
4457
- baseUrl: "https://opencode.ai/zen/v1",
4458
- reasoning: true,
4459
- input: ["text"],
4460
- cost: {
4461
- input: 0,
4462
- output: 0,
4463
- cacheRead: 0,
4464
- cacheWrite: 0,
4465
- },
4466
- contextWindow: 256000,
4467
- maxTokens: 256000,
4468
- } satisfies Model<"openai-completions">,
4469
4435
  "kimi-k2": {
4470
4436
  id: "kimi-k2",
4471
4437
  name: "Kimi K2",
@@ -4500,23 +4466,40 @@ export const MODELS = {
4500
4466
  contextWindow: 262144,
4501
4467
  maxTokens: 262144,
4502
4468
  } satisfies Model<"openai-completions">,
4503
- "minimax-m2.1-free": {
4504
- id: "minimax-m2.1-free",
4469
+ "kimi-k2.5": {
4470
+ id: "kimi-k2.5",
4471
+ name: "Kimi K2.5",
4472
+ api: "openai-completions",
4473
+ provider: "opencode",
4474
+ baseUrl: "https://opencode.ai/zen/v1",
4475
+ reasoning: true,
4476
+ input: ["text", "image"],
4477
+ cost: {
4478
+ input: 1.2,
4479
+ output: 1.2,
4480
+ cacheRead: 0.6,
4481
+ cacheWrite: 0,
4482
+ },
4483
+ contextWindow: 262144,
4484
+ maxTokens: 262144,
4485
+ } satisfies Model<"openai-completions">,
4486
+ "minimax-m2.1": {
4487
+ id: "minimax-m2.1",
4505
4488
  name: "MiniMax M2.1",
4506
- api: "anthropic-messages",
4489
+ api: "openai-completions",
4507
4490
  provider: "opencode",
4508
- baseUrl: "https://opencode.ai/zen",
4491
+ baseUrl: "https://opencode.ai/zen/v1",
4509
4492
  reasoning: true,
4510
4493
  input: ["text"],
4511
4494
  cost: {
4512
- input: 0,
4513
- output: 0,
4514
- cacheRead: 0,
4495
+ input: 0.3,
4496
+ output: 1.2,
4497
+ cacheRead: 0.1,
4515
4498
  cacheWrite: 0,
4516
4499
  },
4517
4500
  contextWindow: 204800,
4518
4501
  maxTokens: 131072,
4519
- } satisfies Model<"anthropic-messages">,
4502
+ } satisfies Model<"openai-completions">,
4520
4503
  "qwen3-coder": {
4521
4504
  id: "qwen3-coder",
4522
4505
  name: "Qwen3 Coder",
@@ -5367,7 +5350,7 @@ export const MODELS = {
5367
5350
  cacheWrite: 0.08333333333333334,
5368
5351
  },
5369
5352
  contextWindow: 1048576,
5370
- maxTokens: 65535,
5353
+ maxTokens: 65536,
5371
5354
  } satisfies Model<"openai-completions">,
5372
5355
  "google/gemini-2.5-pro": {
5373
5356
  id: "google/gemini-2.5-pro",
@@ -5760,23 +5743,6 @@ export const MODELS = {
5760
5743
  contextWindow: 262144,
5761
5744
  maxTokens: 65536,
5762
5745
  } satisfies Model<"openai-completions">,
5763
- "mistralai/devstral-2512:free": {
5764
- id: "mistralai/devstral-2512:free",
5765
- name: "Mistral: Devstral 2 2512 (free)",
5766
- api: "openai-completions",
5767
- provider: "openrouter",
5768
- baseUrl: "https://openrouter.ai/api/v1",
5769
- reasoning: false,
5770
- input: ["text"],
5771
- cost: {
5772
- input: 0,
5773
- output: 0,
5774
- cacheRead: 0,
5775
- cacheWrite: 0,
5776
- },
5777
- contextWindow: 262144,
5778
- maxTokens: 4096,
5779
- } satisfies Model<"openai-completions">,
5780
5746
  "mistralai/devstral-medium": {
5781
5747
  id: "mistralai/devstral-medium",
5782
5748
  name: "Mistral: Devstral Medium",
@@ -6287,6 +6253,23 @@ export const MODELS = {
6287
6253
  contextWindow: 262144,
6288
6254
  maxTokens: 65535,
6289
6255
  } satisfies Model<"openai-completions">,
6256
+ "moonshotai/kimi-k2.5": {
6257
+ id: "moonshotai/kimi-k2.5",
6258
+ name: "MoonshotAI: Kimi K2.5",
6259
+ api: "openai-completions",
6260
+ provider: "openrouter",
6261
+ baseUrl: "https://openrouter.ai/api/v1",
6262
+ reasoning: true,
6263
+ input: ["text", "image"],
6264
+ cost: {
6265
+ input: 0.6,
6266
+ output: 3,
6267
+ cacheRead: 0.09999999999999999,
6268
+ cacheWrite: 0,
6269
+ },
6270
+ contextWindow: 262144,
6271
+ maxTokens: 4096,
6272
+ } satisfies Model<"openai-completions">,
6290
6273
  "nex-agi/deepseek-v3.1-nex-n1": {
6291
6274
  id: "nex-agi/deepseek-v3.1-nex-n1",
6292
6275
  name: "Nex AGI: DeepSeek V3.1 Nex N1",
@@ -7726,7 +7709,7 @@ export const MODELS = {
7726
7709
  cost: {
7727
7710
  input: 0.22,
7728
7711
  output: 1.7999999999999998,
7729
- cacheRead: 0,
7712
+ cacheRead: 0.022,
7730
7713
  cacheWrite: 0,
7731
7714
  },
7732
7715
  contextWindow: 262144,
@@ -7862,7 +7845,7 @@ export const MODELS = {
7862
7845
  cost: {
7863
7846
  input: 0.15,
7864
7847
  output: 0.6,
7865
- cacheRead: 0,
7848
+ cacheRead: 0.075,
7866
7849
  cacheWrite: 0,
7867
7850
  },
7868
7851
  contextWindow: 262144,
@@ -8089,6 +8072,23 @@ export const MODELS = {
8089
8072
  contextWindow: 163840,
8090
8073
  maxTokens: 65536,
8091
8074
  } satisfies Model<"openai-completions">,
8075
+ "upstage/solar-pro-3:free": {
8076
+ id: "upstage/solar-pro-3:free",
8077
+ name: "Upstage: Solar Pro 3 (free)",
8078
+ api: "openai-completions",
8079
+ provider: "openrouter",
8080
+ baseUrl: "https://openrouter.ai/api/v1",
8081
+ reasoning: true,
8082
+ input: ["text"],
8083
+ cost: {
8084
+ input: 0,
8085
+ output: 0,
8086
+ cacheRead: 0,
8087
+ cacheWrite: 0,
8088
+ },
8089
+ contextWindow: 128000,
8090
+ maxTokens: 4096,
8091
+ } satisfies Model<"openai-completions">,
8092
8092
  "x-ai/grok-3": {
8093
8093
  id: "x-ai/grok-3",
8094
8094
  name: "xAI: Grok 3",
@@ -8242,23 +8242,6 @@ export const MODELS = {
8242
8242
  contextWindow: 262144,
8243
8243
  maxTokens: 4096,
8244
8244
  } satisfies Model<"openai-completions">,
8245
- "xiaomi/mimo-v2-flash:free": {
8246
- id: "xiaomi/mimo-v2-flash:free",
8247
- name: "Xiaomi: MiMo-V2-Flash (free)",
8248
- api: "openai-completions",
8249
- provider: "openrouter",
8250
- baseUrl: "https://openrouter.ai/api/v1",
8251
- reasoning: true,
8252
- input: ["text"],
8253
- cost: {
8254
- input: 0,
8255
- output: 0,
8256
- cacheRead: 0,
8257
- cacheWrite: 0,
8258
- },
8259
- contextWindow: 262144,
8260
- maxTokens: 65536,
8261
- } satisfies Model<"openai-completions">,
8262
8245
  "z-ai/glm-4-32b": {
8263
8246
  id: "z-ai/glm-4-32b",
8264
8247
  name: "Z.AI: GLM 4 32B ",
@@ -8372,7 +8355,7 @@ export const MODELS = {
8372
8355
  cost: {
8373
8356
  input: 0.44,
8374
8357
  output: 1.76,
8375
- cacheRead: 0,
8358
+ cacheRead: 0.11,
8376
8359
  cacheWrite: 0,
8377
8360
  },
8378
8361
  contextWindow: 204800,
@@ -8601,6 +8584,23 @@ export const MODELS = {
8601
8584
  contextWindow: 262144,
8602
8585
  maxTokens: 32768,
8603
8586
  } satisfies Model<"anthropic-messages">,
8587
+ "alibaba/qwen3-max-thinking": {
8588
+ id: "alibaba/qwen3-max-thinking",
8589
+ name: "Qwen 3 Max Thinking",
8590
+ api: "anthropic-messages",
8591
+ provider: "vercel-ai-gateway",
8592
+ baseUrl: "https://ai-gateway.vercel.sh",
8593
+ reasoning: true,
8594
+ input: ["text"],
8595
+ cost: {
8596
+ input: 1.2,
8597
+ output: 6,
8598
+ cacheRead: 0.24,
8599
+ cacheWrite: 0,
8600
+ },
8601
+ contextWindow: 256000,
8602
+ maxTokens: 256000,
8603
+ } satisfies Model<"anthropic-messages">,
8604
8604
  "anthropic/claude-3-haiku": {
8605
8605
  id: "anthropic/claude-3-haiku",
8606
8606
  name: "Claude 3 Haiku",
@@ -9485,6 +9485,23 @@ export const MODELS = {
9485
9485
  contextWindow: 256000,
9486
9486
  maxTokens: 16384,
9487
9487
  } satisfies Model<"anthropic-messages">,
9488
+ "moonshotai/kimi-k2.5": {
9489
+ id: "moonshotai/kimi-k2.5",
9490
+ name: "Kimi K2.5",
9491
+ api: "anthropic-messages",
9492
+ provider: "vercel-ai-gateway",
9493
+ baseUrl: "https://ai-gateway.vercel.sh",
9494
+ reasoning: true,
9495
+ input: ["text", "image"],
9496
+ cost: {
9497
+ input: 1.2,
9498
+ output: 1.2,
9499
+ cacheRead: 0.6,
9500
+ cacheWrite: 0,
9501
+ },
9502
+ contextWindow: 256000,
9503
+ maxTokens: 256000,
9504
+ } satisfies Model<"anthropic-messages">,
9488
9505
  "nvidia/nemotron-nano-12b-v2-vl": {
9489
9506
  id: "nvidia/nemotron-nano-12b-v2-vl",
9490
9507
  name: "Nvidia Nemotron Nano 12B V2 VL",
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  BedrockRuntimeClient,
3
+ type BedrockRuntimeClientConfig,
3
4
  StopReason as BedrockStopReason,
4
5
  type Tool as BedrockTool,
5
6
  CachePointType,
@@ -89,11 +90,42 @@ export const streamBedrock: StreamFunction<"bedrock-converse-stream"> = (
89
90
 
90
91
  const blocks = output.content as Block[];
91
92
 
93
+ const config: BedrockRuntimeClientConfig = {
94
+ region: options.region,
95
+ profile: options.profile,
96
+ };
97
+
98
+ // in Node.js/Bun environment only
99
+ if (typeof process !== "undefined" && (process.versions?.node || process.versions?.bun)) {
100
+ config.region = config.region || process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION;
101
+
102
+ if (
103
+ process.env.HTTP_PROXY ||
104
+ process.env.HTTPS_PROXY ||
105
+ process.env.NO_PROXY ||
106
+ process.env.http_proxy ||
107
+ process.env.https_proxy ||
108
+ process.env.no_proxy
109
+ ) {
110
+ const nodeHttpHandler = await import("@smithy/node-http-handler");
111
+ const proxyAgent = await import("proxy-agent");
112
+
113
+ const agent = new proxyAgent.ProxyAgent();
114
+
115
+ // Bedrock runtime uses NodeHttp2Handler by default since v3.798.0, which is based
116
+ // on `http2` module and has no support for http agent.
117
+ // Use NodeHttpHandler to support http agent.
118
+ config.requestHandler = new nodeHttpHandler.NodeHttpHandler({
119
+ httpAgent: agent,
120
+ httpsAgent: agent,
121
+ });
122
+ }
123
+ }
124
+
125
+ config.region = config.region || "us-east-1";
126
+
92
127
  try {
93
- const client = new BedrockRuntimeClient({
94
- region: options.region || process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION || "us-east-1",
95
- profile: options.profile,
96
- });
128
+ const client = new BedrockRuntimeClient(config);
97
129
 
98
130
  const commandInput = {
99
131
  modelId: model.id,
@@ -321,7 +321,7 @@ export const streamAnthropic: StreamFunction<"anthropic-messages"> = (
321
321
  }
322
322
 
323
323
  if (output.stopReason === "aborted" || output.stopReason === "error") {
324
- throw new Error("An unkown error ocurred");
324
+ throw new Error("An unknown error occurred");
325
325
  }
326
326
 
327
327
  output.duration = Date.now() - startTime;
@@ -337,7 +337,7 @@ export const streamAzureOpenAIResponses: StreamFunction<"azure-openai-responses"
337
337
  }
338
338
 
339
339
  if (output.stopReason === "aborted" || output.stopReason === "error") {
340
- throw new Error("An unkown error ocurred");
340
+ throw new Error("An unknown error occurred");
341
341
  }
342
342
 
343
343
  output.duration = Date.now() - startTime;
@@ -3,7 +3,6 @@ import * as fs from "node:fs/promises";
3
3
  import http2 from "node:http2";
4
4
  import { create, fromBinary, fromJson, type JsonValue, toBinary, toJson } from "@bufbuild/protobuf";
5
5
  import { ValueSchema } from "@bufbuild/protobuf/wkt";
6
- import JSON5 from "json5";
7
6
  import { calculateCost } from "../models";
8
7
  import type {
9
8
  Api,
@@ -1509,7 +1508,7 @@ function parseToolArgsJson(text: string): unknown {
1509
1508
  .replace(/\bNone\b/g, "null")
1510
1509
  .replace(/\bTrue\b/g, "true")
1511
1510
  .replace(/\bFalse\b/g, "false");
1512
- return JSON5.parse(normalized);
1511
+ return Bun.JSON5.parse(normalized);
1513
1512
  } catch {}
1514
1513
  return text;
1515
1514
  }
@@ -527,6 +527,7 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
527
527
  const reader = activeResponse.body.getReader();
528
528
  const decoder = new TextDecoder();
529
529
  let buffer = "";
530
+ let jsonlBuffer = "";
530
531
 
531
532
  // Set up abort handler to cancel reader when signal fires
532
533
  const abortHandler = () => {
@@ -553,14 +554,17 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
553
554
 
554
555
  const jsonStr = line.slice(5).trim();
555
556
  if (!jsonStr) continue;
556
-
557
- let chunk: CloudCodeAssistResponseChunk;
558
- try {
559
- chunk = JSON.parse(jsonStr);
560
- } catch {
557
+ jsonlBuffer += `${jsonStr}\n`;
558
+ const parsed = Bun.JSONL.parseChunk(jsonlBuffer);
559
+ jsonlBuffer = jsonlBuffer.slice(parsed.read);
560
+ if (parsed.error) {
561
+ jsonlBuffer = "";
561
562
  continue;
562
563
  }
563
564
 
565
+ const chunk = parsed.values[0] as CloudCodeAssistResponseChunk | undefined;
566
+ if (!chunk) continue;
567
+
564
568
  // Unwrap the response
565
569
  const responseData = chunk.response;
566
570
  if (!responseData) continue;
@@ -145,10 +145,19 @@ export function convertMessages<T extends GoogleApiType>(model: Model<T>, contex
145
145
  } else if (block.type === "toolCall") {
146
146
  const thoughtSignature = resolveThoughtSignature(isSameProviderAndModel, block.thoughtSignature);
147
147
  if (isGemini3Model(model.id) && !thoughtSignature) {
148
- const argsStr = JSON.stringify(block.arguments, null, 2);
148
+ const params = Object.entries(block.arguments ?? {})
149
+ .map(([key, value]) => {
150
+ const valueStr = typeof value === "string" ? value : JSON.stringify(value, null, 2);
151
+ return `<parameter name="${key}">${valueStr}</parameter>`;
152
+ })
153
+ .join("\n");
154
+
149
155
  parts.push({
150
156
  text: sanitizeSurrogates(
151
- `[Historical context: a different model called tool "${block.name}" with arguments: ${argsStr}. Do not mimic this format - use proper function calling.]`,
157
+ `<call_record tool="${block.name}">
158
+ <critical>Historical context only. You cannot invoke tools this way—use proper function calling.</critical>
159
+ ${params}
160
+ </call_record>`,
152
161
  ),
153
162
  });
154
163
  continue;
@@ -246,7 +246,7 @@ export const streamGoogle: StreamFunction<"google-generative-ai"> = (
246
246
  }
247
247
 
248
248
  if (output.stopReason === "aborted" || output.stopReason === "error") {
249
- throw new Error("An unkown error ocurred");
249
+ throw new Error("An unknown error occurred");
250
250
  }
251
251
 
252
252
  output.duration = Date.now() - startTime;
@@ -309,7 +309,7 @@ export const streamOpenAICompletions: StreamFunction<"openai-completions"> = (
309
309
  }
310
310
 
311
311
  if (output.stopReason === "aborted" || output.stopReason === "error") {
312
- throw new Error("An unkown error ocurred");
312
+ throw new Error("An unknown error occurred");
313
313
  }
314
314
 
315
315
  output.duration = Date.now() - startTime;
@@ -504,26 +504,31 @@ export function convertMessages(
504
504
 
505
505
  if (msg.role === "user") {
506
506
  if (typeof msg.content === "string") {
507
+ const text = sanitizeSurrogates(msg.content);
508
+ if (text.trim().length === 0) continue;
507
509
  params.push({
508
510
  role: "user",
509
- content: sanitizeSurrogates(msg.content),
511
+ content: text,
510
512
  });
511
513
  } else {
512
- const content: ChatCompletionContentPart[] = msg.content.map((item): ChatCompletionContentPart => {
514
+ const content: ChatCompletionContentPart[] = [];
515
+ for (const item of msg.content) {
513
516
  if (item.type === "text") {
514
- return {
517
+ const text = sanitizeSurrogates(item.text);
518
+ if (text.trim().length === 0) continue;
519
+ content.push({
515
520
  type: "text",
516
- text: sanitizeSurrogates(item.text),
517
- } satisfies ChatCompletionContentPartText;
521
+ text,
522
+ } satisfies ChatCompletionContentPartText);
518
523
  } else {
519
- return {
524
+ content.push({
520
525
  type: "image_url",
521
526
  image_url: {
522
527
  url: `data:${item.mimeType};base64,${item.data}`,
523
528
  },
524
- } satisfies ChatCompletionContentPartImage;
529
+ } satisfies ChatCompletionContentPartImage);
525
530
  }
526
- });
531
+ }
527
532
  const filteredContent = !model.input.includes("image")
528
533
  ? content.filter(c => c.type !== "image_url")
529
534
  : content;
@@ -578,7 +583,36 @@ export function convertMessages(
578
583
  }
579
584
  }
580
585
 
586
+ if (compat.thinkingFormat === "openai") {
587
+ const reasoningField = compat.reasoningContentField ?? "reasoning_content";
588
+ const reasoningContent = (assistantMsg as any)[reasoningField];
589
+ if (!reasoningContent) {
590
+ const reasoning = (assistantMsg as any).reasoning;
591
+ const reasoningText = (assistantMsg as any).reasoning_text;
592
+ if (reasoning && reasoningField !== "reasoning") {
593
+ (assistantMsg as any)[reasoningField] = reasoning;
594
+ } else if (reasoningText && reasoningField !== "reasoning_text") {
595
+ (assistantMsg as any)[reasoningField] = reasoningText;
596
+ } else if (nonEmptyThinkingBlocks.length > 0) {
597
+ (assistantMsg as any)[reasoningField] = nonEmptyThinkingBlocks.map(b => b.thinking).join("\n");
598
+ }
599
+ }
600
+ }
601
+
581
602
  const toolCalls = msg.content.filter(b => b.type === "toolCall") as ToolCall[];
603
+ const hasReasoningField =
604
+ (assistantMsg as any).reasoning_content !== undefined ||
605
+ (assistantMsg as any).reasoning !== undefined ||
606
+ (assistantMsg as any).reasoning_text !== undefined;
607
+ if (
608
+ toolCalls.length > 0 &&
609
+ compat.requiresReasoningContentForToolCalls &&
610
+ compat.thinkingFormat === "openai" &&
611
+ !hasReasoningField
612
+ ) {
613
+ const reasoningField = compat.reasoningContentField ?? "reasoning_content";
614
+ (assistantMsg as any)[reasoningField] = ".";
615
+ }
582
616
  if (toolCalls.length > 0) {
583
617
  assistantMsg.tool_calls = toolCalls.map(tc => ({
584
618
  id: normalizeMistralToolId(tc.id, compat.requiresMistralToolIds),
@@ -611,6 +645,9 @@ export function convertMessages(
611
645
  content !== null &&
612
646
  content !== undefined &&
613
647
  (typeof content === "string" ? content.length > 0 : content.length > 0);
648
+ if (!hasContent && assistantMsg.tool_calls && compat.requiresAssistantContentForToolCalls) {
649
+ assistantMsg.content = ".";
650
+ }
614
651
  if (!hasContent && !assistantMsg.tool_calls) {
615
652
  continue;
616
653
  }
@@ -732,6 +769,7 @@ function detectCompat(model: Model<"openai-completions">): ResolvedOpenAICompat
732
769
  const baseUrl = model.baseUrl;
733
770
 
734
771
  const isZai = provider === "zai" || baseUrl.includes("api.z.ai");
772
+ const isOpenRouterKimi = provider === "openrouter" && model.id.includes("moonshotai/kimi");
735
773
 
736
774
  const isNonStandard =
737
775
  provider === "cerebras" ||
@@ -762,6 +800,9 @@ function detectCompat(model: Model<"openai-completions">): ResolvedOpenAICompat
762
800
  requiresThinkingAsText: isMistral,
763
801
  requiresMistralToolIds: isMistral,
764
802
  thinkingFormat: isZai ? "zai" : "openai",
803
+ reasoningContentField: "reasoning_content",
804
+ requiresReasoningContentForToolCalls: isOpenRouterKimi,
805
+ requiresAssistantContentForToolCalls: isOpenRouterKimi,
765
806
  openRouterRouting: undefined,
766
807
  };
767
808
  }
@@ -786,6 +827,11 @@ function getCompat(model: Model<"openai-completions">): ResolvedOpenAICompat {
786
827
  requiresThinkingAsText: model.compat.requiresThinkingAsText ?? detected.requiresThinkingAsText,
787
828
  requiresMistralToolIds: model.compat.requiresMistralToolIds ?? detected.requiresMistralToolIds,
788
829
  thinkingFormat: model.compat.thinkingFormat ?? detected.thinkingFormat,
830
+ reasoningContentField: model.compat.reasoningContentField ?? detected.reasoningContentField,
831
+ requiresReasoningContentForToolCalls:
832
+ model.compat.requiresReasoningContentForToolCalls ?? detected.requiresReasoningContentForToolCalls,
833
+ requiresAssistantContentForToolCalls:
834
+ model.compat.requiresAssistantContentForToolCalls ?? detected.requiresAssistantContentForToolCalls,
789
835
  openRouterRouting: model.compat.openRouterRouting ?? detected.openRouterRouting,
790
836
  };
791
837
  }
@@ -313,7 +313,7 @@ export const streamOpenAIResponses: StreamFunction<"openai-responses"> = (
313
313
  }
314
314
 
315
315
  if (output.stopReason === "aborted" || output.stopReason === "error") {
316
- throw new Error("An unkown error ocurred");
316
+ throw new Error("An unknown error occurred");
317
317
  }
318
318
 
319
319
  output.duration = Date.now() - startTime;
package/src/stream.ts CHANGED
@@ -29,6 +29,15 @@ import type {
29
29
  ThinkingLevel,
30
30
  } from "./types";
31
31
 
32
+ // Set up http proxy according to env variables for `fetch` based SDKs in Node.js.
33
+ // Bun has builtin support for this.
34
+ if (typeof process !== "undefined" && process.versions?.node) {
35
+ import("undici").then(m => {
36
+ const { EnvHttpProxyAgent, setGlobalDispatcher } = m;
37
+ setGlobalDispatcher(new EnvHttpProxyAgent());
38
+ });
39
+ }
40
+
32
41
  let cachedVertexAdcCredentialsExists: boolean | null = null;
33
42
 
34
43
  function hasVertexAdcCredentials(): boolean {
package/src/types.ts CHANGED
@@ -292,6 +292,12 @@ export interface OpenAICompat {
292
292
  requiresMistralToolIds?: boolean;
293
293
  /** Format for reasoning/thinking parameter. "openai" uses reasoning_effort, "zai" uses thinking: { type: "enabled" }. Default: "openai". */
294
294
  thinkingFormat?: "openai" | "zai";
295
+ /** Which reasoning content field to emit on assistant messages. Default: auto-detected. */
296
+ reasoningContentField?: "reasoning_content" | "reasoning" | "reasoning_text";
297
+ /** Whether assistant tool-call messages must include reasoning content. Default: false. */
298
+ requiresReasoningContentForToolCalls?: boolean;
299
+ /** Whether assistant tool-call messages must include non-empty content. Default: false. */
300
+ requiresAssistantContentForToolCalls?: boolean;
295
301
  /** OpenRouter-specific routing preferences. Only used when baseUrl points to OpenRouter. */
296
302
  openRouterRouting?: OpenRouterRouting;
297
303
  }