@wrongstack/providers 0.3.3 → 0.3.7

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/README.md CHANGED
@@ -10,7 +10,7 @@ Most providers ride a single declarative `WireFormatConfig` adapter; only the th
10
10
  pnpm add @wrongstack/providers @wrongstack/core
11
11
  ```
12
12
 
13
- `@wrongstack/core` is a peer of every provider — providers depend on the core `Provider` interface, message types, and tool format.
13
+ `@wrongstack/core` provides the shared `Provider` interface, message types, and tool format.
14
14
 
15
15
  ## What's in here
16
16
 
@@ -37,13 +37,16 @@ import { AnthropicProvider } from '@wrongstack/providers';
37
37
 
38
38
  const provider = new AnthropicProvider({
39
39
  apiKey: process.env.ANTHROPIC_API_KEY!,
40
- modelId: 'claude-sonnet-4-6',
41
40
  });
42
41
 
43
- const stream = provider.stream({
44
- messages: [{ role: 'user', content: 'hello' }],
45
- tools: [],
46
- });
42
+ const stream = provider.stream(
43
+ {
44
+ model: 'claude-sonnet-4-6',
45
+ messages: [{ role: 'user', content: 'hello' }],
46
+ maxTokens: 512,
47
+ },
48
+ { signal: new AbortController().signal },
49
+ );
47
50
 
48
51
  for await (const event of stream) {
49
52
  if (event.type === 'text_delta') process.stdout.write(event.text);
@@ -58,39 +61,48 @@ import { OpenAICompatibleProvider } from '@wrongstack/providers';
58
61
  const groq = new OpenAICompatibleProvider({
59
62
  id: 'groq',
60
63
  apiKey: process.env.GROQ_API_KEY!,
61
- baseURL: 'https://api.groq.com/openai/v1',
62
- modelId: 'llama-3.3-70b-versatile',
64
+ baseUrl: 'https://api.groq.com/openai/v1',
63
65
  capabilities: { tools: true, vision: false, maxContext: 128_000 },
64
66
  });
67
+
68
+ const result = await groq.complete(
69
+ {
70
+ model: 'llama-3.3-70b-versatile',
71
+ messages: [{ role: 'user', content: 'hello' }],
72
+ maxTokens: 512,
73
+ },
74
+ { signal: new AbortController().signal },
75
+ );
65
76
  ```
66
77
 
67
78
  ## Wire-format adapter (declarative)
68
79
 
69
- For a new provider that doesn't fit one of the existing presets, write a `WireFormatConfig` and plug it into `WireAdapter`. See [docs/provider-author-guide.md](../../docs/provider-author-guide.md) for the full spec.
80
+ For a new provider that doesn't fit one of the existing presets, write a `WireFormatConfig` and plug it into `WireFormatProvider`. See [docs/provider-author-guide.md](../../docs/provider-author-guide.md) for the full spec.
70
81
 
71
82
  ```ts
72
- import { WireAdapter } from '@wrongstack/providers';
73
- import type { WireFormatConfig } from '@wrongstack/core';
83
+ import { WireFormatProvider, type WireFormatConfig } from '@wrongstack/providers';
74
84
 
75
85
  const myWire: WireFormatConfig = {
76
- family: 'openai',
77
- endpoint: 'https://api.myprovider.com/v1/chat/completions',
78
- authHeader: (key) => ({ 'Authorization': `Bearer ${key}` }),
79
- // tool format, message shape, stream parsing — see WireFormatConfig type
86
+ id: 'myprovider',
87
+ family: 'openai-compatible',
88
+ capabilities: { tools: true, parallelTools: true, vision: false, streaming: true, promptCache: false, systemPrompt: true, jsonMode: false, maxContext: 32_000, cacheControl: 'none' },
89
+ defaultBaseUrl: 'https://api.myprovider.com/v1',
90
+ buildUrl: (baseUrl) => `${baseUrl.replace(/\/+$/, '')}/chat/completions`,
91
+ buildHeaders: (apiKey) => ({ authorization: `Bearer ${apiKey}` }),
92
+ buildBody: (req) => ({ model: req.model, messages: req.messages, max_tokens: req.maxTokens, stream: true }),
93
+ createStreamState: (fallbackModel) => ({ model: fallbackModel, started: false }),
94
+ parseStreamEvent: () => [],
95
+ finalizeStream: () => [{ type: 'message_stop', stopReason: 'end_turn', usage: { input: 0, output: 0 } }],
80
96
  };
81
97
 
82
- const provider = new WireAdapter({
83
- id: 'myprovider',
98
+ const provider = new WireFormatProvider(myWire, {
84
99
  apiKey: '…',
85
- modelId: 'my-model-1',
86
- wire: myWire,
87
- capabilities: { tools: true, maxContext: 32_000 },
88
100
  });
89
101
  ```
90
102
 
91
103
  ## Tool input parsing (`parseToolInput`)
92
104
 
93
- All four stream parsers (anthropic / openai / aggregate + the three OpenAI-compatible presets) run tool-call JSON through one canonical helper: [`_tool-input.ts`](src/_tool-input.ts). It guarantees the agent always receives a `Record<string, unknown>` for `tool_use.input`, never a parse-error or `null`. Invalid or non-object inputs are wrapped under `{ __raw: ... }` instead of crashing the provider runner.
105
+ Anthropic/OpenAI-style stream parsers and the aggregate path run tool-call JSON through one canonical helper: [`_tool-input.ts`](src/_tool-input.ts). It guarantees the agent always receives a `Record<string, unknown>` for `tool_use.input`, never a parse-error or `null`. Invalid or non-object inputs are wrapped under `{ __raw: ... }` instead of crashing the provider runner.
94
106
 
95
107
  ## Capabilities
96
108
 
package/dist/index.d.ts CHANGED
@@ -320,16 +320,18 @@ interface MistralStreamState {
320
320
  name?: string;
321
321
  partial: string;
322
322
  emittedStart: boolean;
323
+ emittedArgLength: number;
323
324
  }>;
324
325
  }
325
326
  declare const mistralWireFormat: WireFormatConfig<MistralStreamState>;
326
327
 
327
- type BlockKind = 'text' | 'tool_use' | 'unknown';
328
+ type BlockKind = 'text' | 'tool_use' | 'thinking' | 'unknown';
328
329
  interface AnthropicStreamState {
329
330
  model: string;
330
331
  usage: Usage;
331
332
  stopReason: StopReason;
332
333
  started: boolean;
334
+ stopped: boolean;
333
335
  blocks: Map<number, {
334
336
  kind: BlockKind;
335
337
  id?: string;
@@ -347,9 +349,11 @@ interface OpenAIStreamState {
347
349
  textOpen: boolean;
348
350
  thinkingOpen: boolean;
349
351
  toolByIndex: Map<number, {
350
- id: string;
351
- name: string;
352
+ id?: string;
353
+ name?: string;
352
354
  argBuf: string;
355
+ emittedStart: boolean;
356
+ emittedArgLength: number;
353
357
  }>;
354
358
  finalEmitted: boolean;
355
359
  }
package/dist/index.js CHANGED
@@ -433,6 +433,18 @@ function toolsToAnthropic(tools) {
433
433
  }
434
434
  }));
435
435
  }
436
+ function validateResponse(res) {
437
+ const r = res;
438
+ if (r === void 0 || typeof r.ok !== "boolean" || typeof r.status !== "number") {
439
+ throw new Error("fetchImpl returned invalid response shape \u2014 expected { ok, status, text, body }");
440
+ }
441
+ if (!("body" in r) || r.body === void 0) {
442
+ const proto = Object.getPrototypeOf(r);
443
+ if (proto === Object.prototype || proto === null) {
444
+ r.body = null;
445
+ }
446
+ }
447
+ }
436
448
  async function safeText(res) {
437
449
  try {
438
450
  return await res.text();
@@ -460,12 +472,14 @@ var WireAdapter = class {
460
472
  const body = this.buildBody(req);
461
473
  let httpRes;
462
474
  try {
463
- httpRes = await this.fetchImpl(url, {
475
+ const raw = await this.fetchImpl(url, {
464
476
  method: "POST",
465
477
  headers,
466
478
  body: JSON.stringify(body),
467
479
  signal: opts.signal
468
480
  });
481
+ validateResponse(raw);
482
+ httpRes = raw;
469
483
  } catch (err) {
470
484
  if (opts.signal.aborted) throw err;
471
485
  throw new ProviderError(err instanceof Error ? err.message : String(err), 0, true, this.id, {
@@ -554,6 +568,7 @@ async function* parseAnthropicStream(body, fallbackModel) {
554
568
  let usage = { input: 0, output: 0 };
555
569
  let stopReason = "end_turn";
556
570
  let started = false;
571
+ let stopped = false;
557
572
  for await (const msg of parseSSE(body)) {
558
573
  if (!msg.data || msg.data === "[DONE]") continue;
559
574
  const parsed = safeParse(msg.data);
@@ -634,6 +649,7 @@ async function* parseAnthropicStream(body, fallbackModel) {
634
649
  break;
635
650
  }
636
651
  case "message_stop":
652
+ stopped = true;
637
653
  yield { type: "message_stop", stopReason, usage };
638
654
  break;
639
655
  case "error": {
@@ -644,7 +660,7 @@ async function* parseAnthropicStream(body, fallbackModel) {
644
660
  }
645
661
  }
646
662
  }
647
- if (started) {
663
+ if (started && !stopped) {
648
664
  yield { type: "message_stop", stopReason, usage };
649
665
  }
650
666
  }
@@ -1086,17 +1102,33 @@ async function* parseOpenAIStream(body, fallbackModel) {
1086
1102
  for (const tc of choice.delta.tool_calls) {
1087
1103
  const idx = tc.index ?? 0;
1088
1104
  let entry = toolByIndex.get(idx);
1089
- if (!entry && tc.id && tc.function?.name) {
1090
- entry = { id: tc.id, name: tc.function.name, argBuf: "" };
1105
+ if (!entry) {
1106
+ entry = {
1107
+ id: tc.id,
1108
+ name: tc.function?.name,
1109
+ argBuf: "",
1110
+ emittedStart: false,
1111
+ emittedArgLength: 0
1112
+ };
1091
1113
  toolByIndex.set(idx, entry);
1092
- yield { type: "tool_use_start", id: entry.id, name: entry.name };
1114
+ } else {
1115
+ if (tc.id && !entry.id) entry.id = tc.id;
1116
+ if (tc.function?.name && !entry.name) entry.name = tc.function.name;
1093
1117
  }
1094
- if (entry && tc.function?.arguments) {
1118
+ if (tc.function?.arguments) {
1095
1119
  entry.argBuf += tc.function.arguments;
1120
+ }
1121
+ if (!entry.emittedStart && entry.id && entry.name) {
1122
+ entry.emittedStart = true;
1123
+ yield { type: "tool_use_start", id: entry.id, name: entry.name };
1124
+ }
1125
+ if (entry.emittedStart && entry.id && entry.emittedArgLength < entry.argBuf.length) {
1126
+ const partial = entry.argBuf.slice(entry.emittedArgLength);
1127
+ entry.emittedArgLength = entry.argBuf.length;
1096
1128
  yield {
1097
1129
  type: "tool_use_input_delta",
1098
1130
  id: entry.id,
1099
- partial: tc.function.arguments
1131
+ partial
1100
1132
  };
1101
1133
  }
1102
1134
  }
@@ -1106,10 +1138,11 @@ async function* parseOpenAIStream(body, fallbackModel) {
1106
1138
  }
1107
1139
  const u = obj["usage"];
1108
1140
  if (u) {
1109
- const cached = u.prompt_tokens_details?.cached_tokens ?? 0;
1110
- const promptTotal = u.prompt_tokens ?? usage.input + cached;
1141
+ const hasDeepSeekCacheFields = u.prompt_cache_hit_tokens !== void 0 || u.prompt_cache_miss_tokens !== void 0;
1142
+ const cached = u.prompt_tokens_details?.cached_tokens ?? u.prompt_cache_hit_tokens ?? 0;
1143
+ const promptTotal = u.prompt_tokens ?? (hasDeepSeekCacheFields ? (u.prompt_cache_hit_tokens ?? 0) + (u.prompt_cache_miss_tokens ?? 0) : usage.input + cached);
1111
1144
  usage = {
1112
- input: Math.max(0, promptTotal - cached),
1145
+ input: u.prompt_cache_miss_tokens ?? Math.max(0, promptTotal - cached),
1113
1146
  output: u.completion_tokens ?? usage.output,
1114
1147
  cacheRead: cached || usage.cacheRead
1115
1148
  };
@@ -1119,6 +1152,10 @@ async function* parseOpenAIStream(body, fallbackModel) {
1119
1152
  yield { type: "thinking_stop" };
1120
1153
  }
1121
1154
  for (const entry of toolByIndex.values()) {
1155
+ if (!entry.id || !entry.name) continue;
1156
+ if (!entry.emittedStart) {
1157
+ yield { type: "tool_use_start", id: entry.id, name: entry.name };
1158
+ }
1122
1159
  const input = parseToolInput(entry.argBuf);
1123
1160
  yield { type: "tool_use_stop", id: entry.id, input };
1124
1161
  }
@@ -1237,16 +1274,31 @@ var mistralWireFormat = defineWireFormat({
1237
1274
  defaultBaseUrl: "https://api.mistral.ai/v1",
1238
1275
  buildUrl: (base) => `${base.replace(/\/+$/, "")}/chat/completions`,
1239
1276
  buildHeaders: (apiKey) => ({ authorization: `Bearer ${apiKey}` }),
1240
- buildBody: (req) => ({
1241
- model: req.model,
1242
- messages: req.messages,
1243
- max_tokens: req.maxTokens,
1244
- temperature: req.temperature,
1245
- top_p: req.topP,
1246
- stop: req.stopSequences,
1247
- stream: true,
1248
- tools: req.tools
1249
- }),
1277
+ buildBody: (req) => {
1278
+ const body = {
1279
+ model: req.model,
1280
+ messages: messagesToOpenAI(stripCacheControl(req.system), req.messages, {}),
1281
+ max_tokens: req.maxTokens,
1282
+ stream: true
1283
+ };
1284
+ if (req.tools && req.tools.length > 0) {
1285
+ body["tools"] = toolsToOpenAI(req.tools);
1286
+ if (req.toolChoice) {
1287
+ if (typeof req.toolChoice === "string") {
1288
+ body["tool_choice"] = req.toolChoice === "required" ? "required" : req.toolChoice;
1289
+ } else {
1290
+ body["tool_choice"] = {
1291
+ type: "function",
1292
+ function: { name: req.toolChoice.name }
1293
+ };
1294
+ }
1295
+ }
1296
+ }
1297
+ if (req.temperature !== void 0) body["temperature"] = req.temperature;
1298
+ if (req.topP !== void 0) body["top_p"] = req.topP;
1299
+ if (req.stopSequences) body["stop"] = req.stopSequences;
1300
+ return body;
1301
+ },
1250
1302
  createStreamState: (fallbackModel) => ({
1251
1303
  model: fallbackModel,
1252
1304
  started: false,
@@ -1270,25 +1322,38 @@ var mistralWireFormat = defineWireFormat({
1270
1322
  for (const tc of choice?.delta?.tool_calls ?? []) {
1271
1323
  let block = state.toolCalls.get(tc.index);
1272
1324
  if (!block) {
1273
- block = { id: tc.id, name: tc.function?.name, partial: "", emittedStart: false };
1325
+ block = {
1326
+ id: tc.id,
1327
+ name: tc.function?.name,
1328
+ partial: "",
1329
+ emittedStart: false,
1330
+ emittedArgLength: 0
1331
+ };
1274
1332
  state.toolCalls.set(tc.index, block);
1275
1333
  } else {
1276
1334
  if (tc.id && !block.id) block.id = tc.id;
1277
1335
  if (tc.function?.name && !block.name) block.name = tc.function.name;
1278
1336
  }
1337
+ const arg = tc.function?.arguments;
1338
+ if (arg) {
1339
+ block.partial += arg;
1340
+ }
1279
1341
  if (!block.emittedStart && block.id && block.name) {
1280
1342
  block.emittedStart = true;
1281
1343
  out.push({ type: "tool_use_start", id: block.id, name: block.name });
1282
1344
  }
1283
- const arg = tc.function?.arguments;
1284
- if (arg && block.id) {
1285
- block.partial += arg;
1286
- out.push({ type: "tool_use_input_delta", id: block.id, partial: arg });
1345
+ if (block.emittedStart && block.id && block.emittedArgLength < block.partial.length) {
1346
+ const partial = block.partial.slice(block.emittedArgLength);
1347
+ block.emittedArgLength = block.partial.length;
1348
+ out.push({ type: "tool_use_input_delta", id: block.id, partial });
1287
1349
  }
1288
1350
  }
1289
1351
  if (choice?.finish_reason) {
1290
1352
  for (const block of state.toolCalls.values()) {
1291
- if (block.id) {
1353
+ if (block.id && block.name) {
1354
+ if (!block.emittedStart) {
1355
+ out.push({ type: "tool_use_start", id: block.id, name: block.name });
1356
+ }
1292
1357
  out.push({
1293
1358
  type: "tool_use_stop",
1294
1359
  id: block.id,
@@ -1313,13 +1378,21 @@ function mapStopReason(reason) {
1313
1378
  case "tool_calls":
1314
1379
  return "tool_use";
1315
1380
  case "length":
1381
+ case "model_length":
1316
1382
  return "max_tokens";
1317
1383
  case "stop":
1318
- return "stop_sequence";
1384
+ return "end_turn";
1319
1385
  default:
1320
1386
  return "end_turn";
1321
1387
  }
1322
1388
  }
1389
+ function stripCacheControl(system) {
1390
+ if (!system) return void 0;
1391
+ return system.map((b) => {
1392
+ const { cache_control: _cc, ...rest } = b;
1393
+ return rest;
1394
+ });
1395
+ }
1323
1396
 
1324
1397
  // src/presets/anthropic.ts
1325
1398
  init_tool_input();
@@ -1362,6 +1435,7 @@ var anthropicWireFormat = defineWireFormat({
1362
1435
  usage: { input: 0, output: 0 },
1363
1436
  stopReason: "end_turn",
1364
1437
  started: false,
1438
+ stopped: false,
1365
1439
  blocks: /* @__PURE__ */ new Map()
1366
1440
  }),
1367
1441
  parseStreamEvent: (msg, state) => {
@@ -1397,6 +1471,9 @@ var anthropicWireFormat = defineWireFormat({
1397
1471
  }
1398
1472
  } else if (cb?.type === "text") {
1399
1473
  state.blocks.set(index, { kind: "text", partial: "" });
1474
+ } else if (cb?.type === "thinking" || cb?.type === "redacted_thinking") {
1475
+ state.blocks.set(index, { kind: "thinking", partial: "" });
1476
+ out.push({ type: "thinking_start" });
1400
1477
  } else {
1401
1478
  state.blocks.set(index, { kind: "unknown", partial: "" });
1402
1479
  }
@@ -1414,6 +1491,10 @@ var anthropicWireFormat = defineWireFormat({
1414
1491
  block.partial += delta.partial_json;
1415
1492
  out.push({ type: "tool_use_input_delta", id: block.id, partial: delta.partial_json });
1416
1493
  }
1494
+ } else if (delta.type === "thinking_delta" && typeof delta.thinking === "string") {
1495
+ out.push({ type: "thinking_delta", text: delta.thinking });
1496
+ } else if (delta.type === "signature_delta" && typeof delta.signature === "string") {
1497
+ out.push({ type: "thinking_signature", signature: delta.signature });
1417
1498
  }
1418
1499
  break;
1419
1500
  }
@@ -1423,6 +1504,8 @@ var anthropicWireFormat = defineWireFormat({
1423
1504
  if (block?.kind === "tool_use" && block.id) {
1424
1505
  const input = parseToolInput(block.partial);
1425
1506
  out.push({ type: "tool_use_stop", id: block.id, input });
1507
+ } else if (block?.kind === "thinking") {
1508
+ out.push({ type: "thinking_stop" });
1426
1509
  }
1427
1510
  break;
1428
1511
  }
@@ -1438,6 +1521,7 @@ var anthropicWireFormat = defineWireFormat({
1438
1521
  break;
1439
1522
  }
1440
1523
  case "message_stop":
1524
+ state.stopped = true;
1441
1525
  out.push({ type: "message_stop", stopReason: state.stopReason, usage: state.usage });
1442
1526
  break;
1443
1527
  case "error": {
@@ -1450,7 +1534,7 @@ var anthropicWireFormat = defineWireFormat({
1450
1534
  return out;
1451
1535
  },
1452
1536
  finalizeStream: (state) => {
1453
- if (state.started) {
1537
+ if (state.started && !state.stopped) {
1454
1538
  return [{ type: "message_stop", stopReason: state.stopReason, usage: state.usage }];
1455
1539
  }
1456
1540
  return [];
@@ -1474,7 +1558,7 @@ var openaiWireFormat = defineWireFormat({
1474
1558
  buildBody: (req) => {
1475
1559
  const body = {
1476
1560
  model: req.model,
1477
- messages: messagesToOpenAI(stripCacheControl(req.system), req.messages, {}),
1561
+ messages: messagesToOpenAI(stripCacheControl2(req.system), req.messages, {}),
1478
1562
  max_tokens: req.maxTokens,
1479
1563
  stream: true,
1480
1564
  stream_options: { include_usage: true }
@@ -1544,18 +1628,34 @@ var openaiWireFormat = defineWireFormat({
1544
1628
  for (const tc of choice.delta.tool_calls) {
1545
1629
  const idx = tc.index ?? 0;
1546
1630
  let entry = state.toolByIndex.get(idx);
1547
- if (!entry && tc.id && tc.function?.name) {
1548
- entry = { id: tc.id, name: tc.function.name, argBuf: "" };
1631
+ if (!entry) {
1632
+ entry = {
1633
+ id: tc.id,
1634
+ name: tc.function?.name,
1635
+ argBuf: "",
1636
+ emittedStart: false,
1637
+ emittedArgLength: 0
1638
+ };
1549
1639
  state.toolByIndex.set(idx, entry);
1640
+ } else {
1641
+ if (tc.id && !entry.id) entry.id = tc.id;
1642
+ if (tc.function?.name && !entry.name) entry.name = tc.function.name;
1643
+ }
1644
+ if (tc.function?.arguments) {
1645
+ entry.argBuf += tc.function.arguments;
1646
+ }
1647
+ if (!entry.emittedStart && entry.id && entry.name) {
1648
+ entry.emittedStart = true;
1550
1649
  state.textOpen = false;
1551
1650
  out.push({ type: "tool_use_start", id: entry.id, name: entry.name });
1552
1651
  }
1553
- if (entry && tc.function?.arguments) {
1554
- entry.argBuf += tc.function.arguments;
1652
+ if (entry.emittedStart && entry.id && entry.emittedArgLength < entry.argBuf.length) {
1653
+ const partial = entry.argBuf.slice(entry.emittedArgLength);
1654
+ entry.emittedArgLength = entry.argBuf.length;
1555
1655
  out.push({
1556
1656
  type: "tool_use_input_delta",
1557
1657
  id: entry.id,
1558
- partial: tc.function.arguments
1658
+ partial
1559
1659
  });
1560
1660
  }
1561
1661
  }
@@ -1565,10 +1665,11 @@ var openaiWireFormat = defineWireFormat({
1565
1665
  }
1566
1666
  const u = obj["usage"];
1567
1667
  if (u) {
1568
- const cached = u.prompt_tokens_details?.cached_tokens ?? 0;
1569
- const promptTotal = u.prompt_tokens ?? state.usage.input + cached;
1668
+ const hasDeepSeekCacheFields = u.prompt_cache_hit_tokens !== void 0 || u.prompt_cache_miss_tokens !== void 0;
1669
+ const cached = u.prompt_tokens_details?.cached_tokens ?? u.prompt_cache_hit_tokens ?? 0;
1670
+ const promptTotal = u.prompt_tokens ?? (hasDeepSeekCacheFields ? (u.prompt_cache_hit_tokens ?? 0) + (u.prompt_cache_miss_tokens ?? 0) : state.usage.input + cached);
1570
1671
  state.usage = {
1571
- input: Math.max(0, promptTotal - cached),
1672
+ input: u.prompt_cache_miss_tokens ?? Math.max(0, promptTotal - cached),
1572
1673
  output: u.completion_tokens ?? state.usage.output,
1573
1674
  cacheRead: cached || state.usage.cacheRead
1574
1675
  };
@@ -1584,6 +1685,10 @@ var openaiWireFormat = defineWireFormat({
1584
1685
  out.push({ type: "thinking_stop" });
1585
1686
  }
1586
1687
  for (const entry of state.toolByIndex.values()) {
1688
+ if (!entry.id || !entry.name) continue;
1689
+ if (!entry.emittedStart) {
1690
+ out.push({ type: "tool_use_start", id: entry.id, name: entry.name });
1691
+ }
1587
1692
  const input = parseToolInput(entry.argBuf);
1588
1693
  out.push({ type: "tool_use_stop", id: entry.id, input });
1589
1694
  }
@@ -1593,7 +1698,7 @@ var openaiWireFormat = defineWireFormat({
1593
1698
  return out;
1594
1699
  }
1595
1700
  });
1596
- function stripCacheControl(system) {
1701
+ function stripCacheControl2(system) {
1597
1702
  if (!system) return void 0;
1598
1703
  return system.map((b) => {
1599
1704
  const { cache_control: _cc, ...rest } = b;