@codehz/ai 0.3.0 → 0.4.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/dist/index.mjs CHANGED
@@ -883,12 +883,6 @@ function contentBlocksToText(blocks) {
883
883
  return blocks.map(blockToText).join("\n");
884
884
  }
885
885
  /**
886
- * 将 instructions(string | InstructionBlock[])归一化为纯文本。
887
- */
888
- function instructionsToText(instructions) {
889
- return typeof instructions === "string" ? instructions : contentBlocksToText(instructions);
890
- }
891
- /**
892
886
  * 从 OutputItem 数组中提取所有 message 类型 item 的文本内容。
893
887
  */
894
888
  function extractText(output) {
@@ -1074,14 +1068,6 @@ function emitMalformedStreamWarning(factory, options) {
1074
1068
  if (options.count < 1) return void 0;
1075
1069
  return factory.responseWarning(`Skipped ${options.count} malformed ${options.providerLabel} ${options.transportLabel}`, "STREAM_ERROR");
1076
1070
  }
1077
- function metadataSourceList(...groups) {
1078
- const sources = /* @__PURE__ */ new Set();
1079
- for (const group of groups) {
1080
- if (!group) continue;
1081
- for (const source of group) sources.add(source);
1082
- }
1083
- return sources.size > 0 ? [...sources] : void 0;
1084
- }
1085
1071
  function isEmptyRecord(value) {
1086
1072
  return Object.keys(value).length === 0;
1087
1073
  }
@@ -1134,7 +1120,7 @@ var AdapterBase = class {
1134
1120
  * 子类可在返回前自定义覆盖。
1135
1121
  */
1136
1122
  buildResponse(request, result, _factory) {
1137
- const text = this.extractText(result.output);
1123
+ const text = extractText(result.output);
1138
1124
  const warnings = mergeWarnings(result.warnings, _factory.warnings);
1139
1125
  const auxiliary = mergeAuxiliary(result.auxiliary, result.providerMetadata ? { providerMetadata: result.providerMetadata } : void 0);
1140
1126
  return {
@@ -1158,9 +1144,30 @@ var AdapterBase = class {
1158
1144
  }
1159
1145
  };
1160
1146
  }
1161
- /** 从 output items 中提取文本内容。 */
1162
- extractText(output) {
1163
- return extractText(output);
1147
+ /**
1148
+ * 统一 finalize auxiliary → response.completed。
1149
+ * adapter 在调用前组装 output / replay / stopReason 等业务字段。
1150
+ */
1151
+ async *emitStreamCompleted(factory, request, auxiliary, result) {
1152
+ const auxiliaryResult = await auxiliary.finalize(factory);
1153
+ for (const event of auxiliaryResult.events) yield event;
1154
+ const finalResponse = this.buildResponse(request, {
1155
+ ...result,
1156
+ usage: result.usage ?? auxiliaryResult.usage,
1157
+ billing: result.billing ?? auxiliaryResult.billing,
1158
+ auxiliary: mergeAuxiliary(result.auxiliary, auxiliaryResult.auxiliary),
1159
+ warnings: mergeWarnings(result.warnings, auxiliaryResult.warnings),
1160
+ metadataSources: result.metadataSources ?? auxiliaryResult.metadataSources
1161
+ }, factory);
1162
+ yield factory.responseCompleted({
1163
+ replay: finalResponse.replay,
1164
+ stopReason: finalResponse.stopReason,
1165
+ trace: finalResponse.backend,
1166
+ usage: finalResponse.usage,
1167
+ billing: finalResponse.billing,
1168
+ auxiliary: finalResponse.auxiliary,
1169
+ warnings: finalResponse.warnings
1170
+ });
1164
1171
  }
1165
1172
  createAuxiliaryState(request) {
1166
1173
  return new AdapterAuxiliaryState(request);
@@ -1269,32 +1276,35 @@ function record(obj) {
1269
1276
  for (const [key, value] of Object.entries(obj)) if (value !== void 0) out[key] = value;
1270
1277
  return out;
1271
1278
  }
1272
- /** OpenAI Chat Completions `usage` */
1273
- function usageFromChatCompletions(raw) {
1274
- const inputTokens = num(raw.prompt_tokens);
1275
- const outputTokens = num(raw.completion_tokens);
1276
- const cachedInputTokens = num(raw.prompt_tokens_details?.cached_tokens);
1277
- const reasoningTokens = num(raw.completion_tokens_details?.reasoning_tokens);
1279
+ function withDerivedTotal(usage) {
1280
+ const { inputTokens, outputTokens, totalTokens, cachedInputTokens, reasoningTokens, cacheWriteInputTokens } = usage;
1278
1281
  return record({
1279
1282
  inputTokens,
1280
1283
  outputTokens,
1281
- totalTokens: num(raw.total_tokens) ?? (inputTokens !== void 0 && outputTokens !== void 0 ? inputTokens + outputTokens : void 0),
1284
+ totalTokens: totalTokens ?? (inputTokens !== void 0 && outputTokens !== void 0 ? inputTokens + outputTokens : void 0),
1282
1285
  cachedInputTokens,
1283
- reasoningTokens
1286
+ reasoningTokens,
1287
+ cacheWriteInputTokens
1288
+ });
1289
+ }
1290
+ /** OpenAI Chat Completions `usage` */
1291
+ function usageFromChatCompletions(raw) {
1292
+ return withDerivedTotal({
1293
+ inputTokens: num(raw.prompt_tokens),
1294
+ outputTokens: num(raw.completion_tokens),
1295
+ totalTokens: num(raw.total_tokens),
1296
+ cachedInputTokens: num(raw.prompt_tokens_details?.cached_tokens),
1297
+ reasoningTokens: num(raw.completion_tokens_details?.reasoning_tokens)
1284
1298
  });
1285
1299
  }
1286
1300
  /** OpenAI Responses API `usage` */
1287
1301
  function usageFromOpenAIResponses(raw) {
1288
- const inputTokens = num(raw.input_tokens);
1289
- const outputTokens = num(raw.output_tokens);
1290
- const cachedInputTokens = num(raw.input_tokens_details?.cached_tokens);
1291
- const reasoningTokens = num(raw.output_tokens_details?.reasoning_tokens);
1292
- return record({
1293
- inputTokens,
1294
- outputTokens,
1295
- totalTokens: num(raw.total_tokens) ?? (inputTokens !== void 0 && outputTokens !== void 0 ? inputTokens + outputTokens : void 0),
1296
- cachedInputTokens,
1297
- reasoningTokens
1302
+ return withDerivedTotal({
1303
+ inputTokens: num(raw.input_tokens),
1304
+ outputTokens: num(raw.output_tokens),
1305
+ totalTokens: num(raw.total_tokens),
1306
+ cachedInputTokens: num(raw.input_tokens_details?.cached_tokens),
1307
+ reasoningTokens: num(raw.output_tokens_details?.reasoning_tokens)
1298
1308
  });
1299
1309
  }
1300
1310
  /** Anthropic Messages `usage`(message_start / message_delta) */
@@ -1308,93 +1318,21 @@ function usageFromAnthropicMessages(raw) {
1308
1318
  cacheWriteInputTokens,
1309
1319
  cachedInputTokens
1310
1320
  ].filter((n) => n !== void 0);
1311
- const inputTokens = inputParts.length > 0 ? inputParts.reduce((sum, n) => sum + n, 0) : void 0;
1312
- return record({
1313
- inputTokens,
1321
+ return withDerivedTotal({
1322
+ inputTokens: inputParts.length > 0 ? inputParts.reduce((sum, n) => sum + n, 0) : void 0,
1314
1323
  outputTokens,
1315
- totalTokens: inputTokens !== void 0 && outputTokens !== void 0 ? inputTokens + outputTokens : void 0,
1316
1324
  cachedInputTokens,
1317
1325
  cacheWriteInputTokens
1318
1326
  });
1319
1327
  }
1320
1328
  /** Ollama 流式 chunk */
1321
1329
  function usageFromOllama(raw) {
1322
- const inputTokens = num(raw.prompt_eval_count);
1323
- const outputTokens = num(raw.eval_count);
1324
- return record({
1325
- inputTokens,
1326
- outputTokens,
1327
- totalTokens: inputTokens !== void 0 && outputTokens !== void 0 ? inputTokens + outputTokens : void 0
1330
+ return withDerivedTotal({
1331
+ inputTokens: num(raw.prompt_eval_count),
1332
+ outputTokens: num(raw.eval_count)
1328
1333
  });
1329
1334
  }
1330
1335
  //#endregion
1331
- //#region src/helpers/sse-parser.ts
1332
- /**
1333
- * 将 SSE 文本块解析为事件数组。
1334
- * 累积事件行直到遇到空行,支持 [DONE] 标记。
1335
- * 返回已解析的事件和未处理的剩余 buffer(用于增量解析)。
1336
- *
1337
- * 关键行为:
1338
- * - 只解析完整的 event(以空行结尾)
1339
- * - 未完成的行保留在 rest 中,等待下次 chunk 补全
1340
- * - 支持跨 chunk 的 event 分片
1341
- */
1342
- function parseSSEEvents(chunk, options = {}) {
1343
- const events = [];
1344
- let eventType = "";
1345
- let dataLines = [];
1346
- let consumedUntil = 0;
1347
- let cursor = 0;
1348
- let malformedEvents = 0;
1349
- const emitEvent = (consumedCursor) => {
1350
- const dataStr = dataLines.join("\n");
1351
- if (dataStr === "[DONE]") {
1352
- eventType = "";
1353
- dataLines = [];
1354
- consumedUntil = consumedCursor;
1355
- return;
1356
- }
1357
- try {
1358
- const data = JSON.parse(dataStr);
1359
- events.push({
1360
- type: eventType,
1361
- data
1362
- });
1363
- } catch {
1364
- malformedEvents++;
1365
- }
1366
- eventType = "";
1367
- dataLines = [];
1368
- consumedUntil = consumedCursor;
1369
- };
1370
- const consumeLine = (line, consumedCursor) => {
1371
- if (line.startsWith("event: ")) eventType = line.slice(7).trim();
1372
- else if (line.startsWith("data: ")) dataLines.push(line.slice(6));
1373
- else if (line === "" && eventType && dataLines.length > 0) emitEvent(consumedCursor);
1374
- else if (line === "" && !eventType && dataLines.length === 0) consumedUntil = consumedCursor;
1375
- };
1376
- while (cursor < chunk.length) {
1377
- const lineEnd = chunk.indexOf("\n", cursor);
1378
- if (lineEnd === -1) break;
1379
- let line = chunk.slice(cursor, lineEnd);
1380
- cursor = lineEnd + 1;
1381
- if (line.endsWith("\r")) line = line.slice(0, -1);
1382
- consumeLine(line, cursor);
1383
- }
1384
- if (options.allowEOF && cursor < chunk.length) {
1385
- let line = chunk.slice(cursor);
1386
- if (line.endsWith("\r")) line = line.slice(0, -1);
1387
- consumeLine(line, chunk.length);
1388
- cursor = chunk.length;
1389
- }
1390
- if (options.allowEOF && eventType && dataLines.length > 0) emitEvent(chunk.length);
1391
- return {
1392
- events,
1393
- rest: chunk.slice(consumedUntil),
1394
- malformedEvents
1395
- };
1396
- }
1397
- //#endregion
1398
1336
  //#region src/helpers/synthetic-stream.ts
1399
1337
  /**
1400
1338
  * 模拟流式 (Synthetic Streaming)
@@ -1568,6 +1506,159 @@ function splitSSEFrames(buffer, allowEOF) {
1568
1506
  rest: normalized.slice(cursor)
1569
1507
  };
1570
1508
  }
1509
+ /** 解析标准 SSE frame(event: + data:),用于 Messages / Responses。 */
1510
+ function parseSseJsonFrame(frame) {
1511
+ let eventType = "";
1512
+ let dataStr = "";
1513
+ for (const rawLine of frame.split("\n")) {
1514
+ const line = rawLine.trim();
1515
+ if (line.startsWith("event: ")) eventType = line.slice(7).trim();
1516
+ else if (line.startsWith("data: ")) dataStr += line.slice(6);
1517
+ }
1518
+ if (!eventType) return { status: "ignored" };
1519
+ try {
1520
+ const data = JSON.parse(dataStr);
1521
+ return {
1522
+ status: "parsed",
1523
+ value: {
1524
+ type: eventType,
1525
+ data
1526
+ }
1527
+ };
1528
+ } catch {
1529
+ return { status: "malformed" };
1530
+ }
1531
+ }
1532
+ function createSseJsonParser() {
1533
+ return new IncrementalStreamParser(splitSSEFrames, (frame) => parseSseJsonFrame(frame));
1534
+ }
1535
+ /** OpenAI Chat Completions 简化 SSE:仅 `data: ...` 行,忽略 `[DONE]`。 */
1536
+ function parseChatCompletionsDataLine(item) {
1537
+ const trimmed = item.trim();
1538
+ if (!trimmed.startsWith("data: ")) return { status: "ignored" };
1539
+ const data = trimmed.slice(6).trim();
1540
+ if (data === "[DONE]") return { status: "ignored" };
1541
+ try {
1542
+ return {
1543
+ status: "parsed",
1544
+ value: JSON.parse(data)
1545
+ };
1546
+ } catch {
1547
+ return { status: "malformed" };
1548
+ }
1549
+ }
1550
+ function createChatCompletionsSseParser() {
1551
+ return new IncrementalStreamParser(splitLines, (item) => parseChatCompletionsDataLine(item));
1552
+ }
1553
+ /** NDJSON 行解析(Ollama 等):空行忽略,JSON 失败为 malformed。 */
1554
+ function createNdjsonLineParser(isValid) {
1555
+ return new IncrementalStreamParser(splitLines, (item) => {
1556
+ const trimmed = item.trim();
1557
+ if (!trimmed) return { status: "ignored" };
1558
+ try {
1559
+ const parsed = JSON.parse(trimmed);
1560
+ if (isValid(parsed)) return {
1561
+ status: "parsed",
1562
+ value: parsed
1563
+ };
1564
+ return { status: "malformed" };
1565
+ } catch {
1566
+ return { status: "malformed" };
1567
+ }
1568
+ });
1569
+ }
1570
+ //#endregion
1571
+ //#region src/helpers/provider-stream.ts
1572
+ /**
1573
+ * Provider HTTP 流公共脚手架
1574
+ *
1575
+ * 收敛 adapter 间重复的:
1576
+ * - JSON POST + 错误映射
1577
+ * - ReadableStream reader 生命周期
1578
+ * - IncrementalStreamParser feed/flush + malformed warning
1579
+ * - 不完整尾帧 warning
1580
+ */
1581
+ /** POST JSON 并返回可读 body reader + response headers;统一网络/HTTP/空 body 错误。 */
1582
+ async function openProviderJsonStream(options) {
1583
+ const { fetchFn, url, headers, body, signal } = options;
1584
+ let response;
1585
+ try {
1586
+ response = await fetchFn(url, {
1587
+ method: "POST",
1588
+ headers,
1589
+ body: JSON.stringify(body),
1590
+ signal
1591
+ });
1592
+ } catch (err) {
1593
+ throw new AIProviderError(err instanceof Error ? err.message : String(err), "PROVIDER_ERROR");
1594
+ }
1595
+ if (!response.ok) {
1596
+ const errorBody = await response.text().catch(() => "");
1597
+ throw providerHttpError(response.status, errorBody);
1598
+ }
1599
+ const bodyStream = response.body;
1600
+ if (!bodyStream) throw new AIStreamError("Response body is not readable", "STREAM_ERROR");
1601
+ return {
1602
+ reader: bodyStream.getReader(),
1603
+ headers: response.headers
1604
+ };
1605
+ }
1606
+ /**
1607
+ * 读取并解析 provider 流。
1608
+ * 每个 batch 携带本轮解析出的 items 与(可选)malformed / incomplete warning。
1609
+ * 调用方应 `for await` 消费完毕;reader 在迭代结束时 cancel/release。
1610
+ */
1611
+ async function* iterateProviderStreamBatches(options) {
1612
+ const { reader, parser, factory, providerLabel, transportLabel, incompleteMessage } = options;
1613
+ let streamDone = false;
1614
+ try {
1615
+ while (true) {
1616
+ const { done, value } = await reader.read().catch((err) => {
1617
+ throw new AIStreamError(`Failed to read response stream: ${err instanceof Error ? err.message : String(err)}`, "STREAM_ERROR");
1618
+ });
1619
+ const { items, malformed } = done ? parser.flush() : parser.feed(value);
1620
+ const warnings = [];
1621
+ const malformedWarning = emitMalformedStreamWarning(factory, {
1622
+ count: malformed,
1623
+ providerLabel,
1624
+ transportLabel
1625
+ });
1626
+ if (malformedWarning) warnings.push(malformedWarning);
1627
+ yield {
1628
+ items,
1629
+ warnings
1630
+ };
1631
+ if (done) {
1632
+ streamDone = true;
1633
+ break;
1634
+ }
1635
+ }
1636
+ } finally {
1637
+ try {
1638
+ if (!streamDone) await reader.cancel().catch(() => void 0);
1639
+ } finally {
1640
+ reader.releaseLock();
1641
+ }
1642
+ }
1643
+ if (parser.getRemaining().trim().length > 0) yield {
1644
+ items: [],
1645
+ warnings: [factory.responseWarning(incompleteMessage, "STREAM_ERROR")]
1646
+ };
1647
+ }
1648
+ /** 一次性 complete 守卫:首次成功,后续返回 false。 */
1649
+ function createCompletionGate() {
1650
+ let completed = false;
1651
+ return {
1652
+ get completed() {
1653
+ return completed;
1654
+ },
1655
+ tryComplete() {
1656
+ if (completed) return false;
1657
+ completed = true;
1658
+ return true;
1659
+ }
1660
+ };
1661
+ }
1571
1662
  //#endregion
1572
1663
  //#region src/helpers/request-mapper.ts
1573
1664
  var NormalizedRequestMapper = class {
@@ -1584,6 +1675,10 @@ var NormalizedRequestMapper = class {
1584
1675
  ensureReasoningBlocks(blocks, field) {
1585
1676
  return this.ensureBlocks(blocks, field, ["text"], "reasoning only supports text blocks");
1586
1677
  }
1678
+ /** ensureTextBlocks + contentBlocksToText 的常见组合。 */
1679
+ textFromBlocks(blocks, field) {
1680
+ return contentBlocksToText(this.ensureTextBlocks(blocks, field));
1681
+ }
1587
1682
  parseToolArguments(item) {
1588
1683
  try {
1589
1684
  const parsed = JSON.parse(item.argumentsText);
@@ -1594,6 +1689,20 @@ var NormalizedRequestMapper = class {
1594
1689
  rollbackTrailingAssistantMessages(messages) {
1595
1690
  while (messages.length > 0 && messages[messages.length - 1]?.role === "assistant") messages.pop();
1596
1691
  }
1692
+ mapToolsIfPresent(tools, map) {
1693
+ if (!tools || tools.length === 0) return void 0;
1694
+ return tools.map(map);
1695
+ }
1696
+ /**
1697
+ * 将 canonical toolChoice 映射为 provider 形状。
1698
+ * 返回 undefined 表示调用方无需写入 body 字段。
1699
+ */
1700
+ mapToolChoice(toolChoice, mappers) {
1701
+ if (!toolChoice) return void 0;
1702
+ if (toolChoice === "auto") return mappers.auto;
1703
+ if (toolChoice === "none") return mappers.none;
1704
+ if (toolChoice.type === "tool") return mappers.tool(toolChoice.name);
1705
+ }
1597
1706
  ensureBlocks(blocks, field, supportedTypes, description) {
1598
1707
  for (let i = 0; i < blocks.length; i++) {
1599
1708
  const block = blocks[i];
@@ -1682,7 +1791,7 @@ var ResponsesAdapter = class extends AdapterBase {
1682
1791
  } else input.push({
1683
1792
  type: "message",
1684
1793
  role: item.role,
1685
- content: contentBlocksToText(mapper$3.ensureTextBlocks(item.content, `input message (${item.role}) content`))
1794
+ content: mapper$3.textFromBlocks(item.content, `input message (${item.role}) content`)
1686
1795
  });
1687
1796
  break;
1688
1797
  case "reasoning": {
@@ -1705,7 +1814,7 @@ var ResponsesAdapter = class extends AdapterBase {
1705
1814
  });
1706
1815
  break;
1707
1816
  case "tool_result": {
1708
- const output = mapper$3.ensureTextBlocks(item.content, `tool_result ${item.callId} content`).map(blockToText).join("\n");
1817
+ const output = mapper$3.textFromBlocks(item.content, `tool_result ${item.callId} content`);
1709
1818
  input.push({
1710
1819
  type: "function_call_output",
1711
1820
  call_id: item.callId,
@@ -1733,20 +1842,20 @@ var ResponsesAdapter = class extends AdapterBase {
1733
1842
  stream: true
1734
1843
  };
1735
1844
  if (request.instructions) body.instructions = mapper$3.mapInstructions(request.instructions);
1736
- if (request.tools && request.tools.length > 0) body.tools = request.tools.map((t) => ({
1845
+ body.tools = mapper$3.mapToolsIfPresent(request.tools, (t) => ({
1737
1846
  type: "function",
1738
1847
  name: t.name,
1739
1848
  description: t.description,
1740
1849
  input_schema: t.inputSchema
1741
1850
  }));
1742
- if (request.toolChoice) {
1743
- if (request.toolChoice === "auto") body.tool_choice = "auto";
1744
- else if (request.toolChoice === "none") body.tool_choice = "none";
1745
- else if (request.toolChoice.type === "tool") body.tool_choice = {
1851
+ body.tool_choice = mapper$3.mapToolChoice(request.toolChoice, {
1852
+ auto: "auto",
1853
+ none: "none",
1854
+ tool: (name) => ({
1746
1855
  type: "function",
1747
- name: request.toolChoice.name
1748
- };
1749
- }
1856
+ name
1857
+ })
1858
+ });
1750
1859
  if (request.temperature !== void 0) body.temperature = request.temperature;
1751
1860
  if (request.maxOutputTokens !== void 0) body.max_output_tokens = request.maxOutputTokens;
1752
1861
  if (request.metadata) body.metadata = request.metadata;
@@ -1754,155 +1863,108 @@ var ResponsesAdapter = class extends AdapterBase {
1754
1863
  }
1755
1864
  async *runStream(providerRequest, factory, request) {
1756
1865
  const auxiliary = this.createAuxiliaryState(request);
1757
- let response;
1758
- try {
1759
- response = await this.fetchFn(`${this.baseUrl}/responses`, {
1760
- method: "POST",
1761
- headers: {
1762
- "Content-Type": "application/json",
1763
- Authorization: `Bearer ${this.apiKey}`
1764
- },
1765
- body: JSON.stringify(providerRequest),
1766
- signal: request.signal
1767
- });
1768
- } catch (err) {
1769
- throw new AIProviderError(err instanceof Error ? err.message : String(err), "PROVIDER_ERROR");
1770
- }
1771
- if (!response.ok) {
1772
- const errorBody = await response.text().catch(() => "");
1773
- throw providerHttpError(response.status, errorBody);
1774
- }
1775
- const reader = response.body?.getReader();
1776
- if (!reader) throw new AIStreamError("Response body is not readable", "STREAM_ERROR");
1777
- const parser = new IncrementalStreamParser(splitSSEFrames, (frame) => {
1778
- let eventType = "";
1779
- let dataStr = "";
1780
- for (const rawLine of frame.split("\n")) {
1781
- const line = rawLine.trim();
1782
- if (line.startsWith("event: ")) eventType = line.slice(7).trim();
1783
- else if (line.startsWith("data: ")) dataStr += line.slice(6);
1784
- }
1785
- if (!eventType) return { status: "ignored" };
1786
- try {
1787
- const data = JSON.parse(dataStr);
1788
- return {
1789
- status: "parsed",
1790
- value: {
1791
- type: eventType,
1792
- data
1793
- }
1794
- };
1795
- } catch {
1796
- return { status: "malformed" };
1797
- }
1866
+ const gate = createCompletionGate();
1867
+ const { reader } = await openProviderJsonStream({
1868
+ fetchFn: this.fetchFn,
1869
+ url: `${this.baseUrl}/responses`,
1870
+ headers: {
1871
+ "Content-Type": "application/json",
1872
+ Authorization: `Bearer ${this.apiKey}`
1873
+ },
1874
+ body: providerRequest,
1875
+ signal: request.signal
1798
1876
  });
1877
+ const parser = createSseJsonParser();
1799
1878
  const output = [];
1800
- let streamDone = false;
1801
1879
  let completedResponse;
1802
- let completedEmitted = false;
1803
1880
  let unknownEventsWarned = false;
1804
1881
  const messageItemsWithDelta = /* @__PURE__ */ new Set();
1805
1882
  const toolCallNames = /* @__PURE__ */ new Map();
1806
- try {
1807
- while (true) {
1808
- const { done, value } = await reader.read().catch((err) => {
1809
- throw new AIStreamError(`Failed to read response stream: ${err instanceof Error ? err.message : String(err)}`, "STREAM_ERROR");
1810
- });
1811
- const { items: events, malformed: malformedEvents } = done ? parser.flush() : parser.feed(value);
1812
- const malformedWarning = emitMalformedStreamWarning(factory, {
1813
- count: malformedEvents,
1814
- providerLabel: "Responses",
1815
- transportLabel: "SSE event(s)"
1816
- });
1817
- if (malformedWarning) yield malformedWarning;
1818
- for (const sseEvent of events) {
1819
- if (sseEvent.type === "error") {
1820
- const data = sseEvent.data;
1821
- yield factory.responseWarning(data.message ?? "Provider error event", data.code);
1822
- continue;
1823
- }
1824
- if (sseEvent.type === "response.output_item.added") {
1825
- const item = sseEvent.data.item;
1826
- switch (item.type) {
1827
- case "message":
1828
- yield factory.messageStarted(item.id);
1829
- break;
1830
- case "reasoning":
1831
- yield factory.reasoningStarted(item.id, "full");
1832
- break;
1833
- case "function_call": {
1834
- const name = typeof item.name === "string" ? item.name : "unknown";
1835
- toolCallNames.set(item.id, name);
1836
- yield factory.toolCallStarted(item.id, name);
1837
- break;
1838
- }
1883
+ for await (const batch of iterateProviderStreamBatches({
1884
+ reader,
1885
+ parser,
1886
+ factory,
1887
+ providerLabel: "Responses",
1888
+ transportLabel: "SSE event(s)",
1889
+ incompleteMessage: "Stream ended with an incomplete Responses SSE frame"
1890
+ })) {
1891
+ for (const warning of batch.warnings) yield warning;
1892
+ for (const sseEvent of batch.items) {
1893
+ if (sseEvent.type === "error") {
1894
+ const data = sseEvent.data;
1895
+ yield factory.responseWarning(data.message ?? "Provider error event", data.code);
1896
+ continue;
1897
+ }
1898
+ if (sseEvent.type === "response.output_item.added") {
1899
+ const item = sseEvent.data.item;
1900
+ switch (item.type) {
1901
+ case "message":
1902
+ yield factory.messageStarted(item.id);
1903
+ break;
1904
+ case "reasoning":
1905
+ yield factory.reasoningStarted(item.id, "full");
1906
+ break;
1907
+ case "function_call": {
1908
+ const name = typeof item.name === "string" ? item.name : "unknown";
1909
+ toolCallNames.set(item.id, name);
1910
+ yield factory.toolCallStarted(item.id, name);
1911
+ break;
1839
1912
  }
1840
- continue;
1841
- }
1842
- if (sseEvent.type === "response.output_text.delta") {
1843
- const data = sseEvent.data;
1844
- yield factory.messageDelta(data.item_id, textBlock(data.delta));
1845
- messageItemsWithDelta.add(data.item_id);
1846
- continue;
1847
- }
1848
- if (sseEvent.type === "response.output_text.done") {
1849
- const data = sseEvent.data;
1850
- if (!messageItemsWithDelta.has(data.item_id) && data.text) yield factory.messageDelta(data.item_id, textBlock(data.text));
1851
- yield factory.messageCompleted(data.item_id);
1852
- output.push(messageItem([textBlock(data.text)], { id: data.item_id }));
1853
- continue;
1854
- }
1855
- if (sseEvent.type === "response.reasoning.delta") {
1856
- const data = sseEvent.data;
1857
- yield factory.reasoningDelta(data.item_id, textBlock(data.delta));
1858
- continue;
1859
- }
1860
- if (sseEvent.type === "response.reasoning.done") {
1861
- const data = sseEvent.data;
1862
- yield factory.reasoningCompleted(data.item_id);
1863
- output.push(reasoningItem([textBlock(data.text)], "full", data.item_id));
1864
- continue;
1865
1913
  }
1866
- if (sseEvent.type === "response.function_call_arguments.delta") {
1867
- const data = sseEvent.data;
1868
- if (data.delta) yield factory.toolCallDelta(data.item_id, { argumentsText: data.delta });
1869
- continue;
1870
- }
1871
- if (sseEvent.type === "response.function_call_arguments.done") {
1872
- const data = sseEvent.data;
1873
- const tcItem = toolCallItem(data.item_id, toolCallNames.get(data.item_id) ?? "unknown", data.arguments);
1874
- yield factory.toolCallCompleted(data.item_id);
1875
- output.push(tcItem);
1876
- continue;
1877
- }
1878
- if (sseEvent.type === "response.completed" || sseEvent.type === "response.failed" || sseEvent.type === "response.incomplete") {
1879
- const data = sseEvent.data;
1880
- if (completedResponse) {
1881
- yield factory.responseWarning("Duplicate finish signal ignored", "DUPLICATE_FINISH");
1882
- continue;
1883
- }
1884
- completedResponse = data.response;
1885
- if (sseEvent.type === "response.failed") yield factory.responseWarning(`Response failed: ${extractFailureMessage(data.response)}`, "PROVIDER_FAILURE");
1914
+ continue;
1915
+ }
1916
+ if (sseEvent.type === "response.output_text.delta") {
1917
+ const data = sseEvent.data;
1918
+ yield factory.messageDelta(data.item_id, textBlock(data.delta));
1919
+ messageItemsWithDelta.add(data.item_id);
1920
+ continue;
1921
+ }
1922
+ if (sseEvent.type === "response.output_text.done") {
1923
+ const data = sseEvent.data;
1924
+ if (!messageItemsWithDelta.has(data.item_id) && data.text) yield factory.messageDelta(data.item_id, textBlock(data.text));
1925
+ yield factory.messageCompleted(data.item_id);
1926
+ output.push(messageItem([textBlock(data.text)], { id: data.item_id }));
1927
+ continue;
1928
+ }
1929
+ if (sseEvent.type === "response.reasoning.delta") {
1930
+ const data = sseEvent.data;
1931
+ yield factory.reasoningDelta(data.item_id, textBlock(data.delta));
1932
+ continue;
1933
+ }
1934
+ if (sseEvent.type === "response.reasoning.done") {
1935
+ const data = sseEvent.data;
1936
+ yield factory.reasoningCompleted(data.item_id);
1937
+ output.push(reasoningItem([textBlock(data.text)], "full", data.item_id));
1938
+ continue;
1939
+ }
1940
+ if (sseEvent.type === "response.function_call_arguments.delta") {
1941
+ const data = sseEvent.data;
1942
+ if (data.delta) yield factory.toolCallDelta(data.item_id, { argumentsText: data.delta });
1943
+ continue;
1944
+ }
1945
+ if (sseEvent.type === "response.function_call_arguments.done") {
1946
+ const data = sseEvent.data;
1947
+ const tcItem = toolCallItem(data.item_id, toolCallNames.get(data.item_id) ?? "unknown", data.arguments);
1948
+ yield factory.toolCallCompleted(data.item_id);
1949
+ output.push(tcItem);
1950
+ continue;
1951
+ }
1952
+ if (sseEvent.type === "response.completed" || sseEvent.type === "response.failed" || sseEvent.type === "response.incomplete") {
1953
+ const data = sseEvent.data;
1954
+ if (completedResponse) {
1955
+ yield factory.responseWarning("Duplicate finish signal ignored", "DUPLICATE_FINISH");
1886
1956
  continue;
1887
1957
  }
1888
- if (!KNOWN_RESPONSES_SSE_TYPES.has(sseEvent.type) && !unknownEventsWarned) {
1889
- unknownEventsWarned = true;
1890
- yield factory.responseWarning(`Responses API sent unknown event type "${sseEvent.type}"; this may indicate an incomplete integration`, "UNKNOWN_PROVIDER_EVENT");
1891
- }
1958
+ completedResponse = data.response;
1959
+ if (sseEvent.type === "response.failed") yield factory.responseWarning(`Response failed: ${extractFailureMessage(data.response)}`, "PROVIDER_FAILURE");
1960
+ continue;
1892
1961
  }
1893
- if (done) {
1894
- streamDone = true;
1895
- break;
1962
+ if (!KNOWN_RESPONSES_SSE_TYPES.has(sseEvent.type) && !unknownEventsWarned) {
1963
+ unknownEventsWarned = true;
1964
+ yield factory.responseWarning(`Responses API sent unknown event type "${sseEvent.type}"; this may indicate an incomplete integration`, "UNKNOWN_PROVIDER_EVENT");
1896
1965
  }
1897
1966
  }
1898
- } finally {
1899
- try {
1900
- if (!streamDone) await reader.cancel().catch(() => void 0);
1901
- } finally {
1902
- reader.releaseLock();
1903
- }
1904
1967
  }
1905
- if (parser.getRemaining().trim().length > 0) yield factory.responseWarning("Stream ended with an incomplete Responses SSE frame", "STREAM_ERROR");
1906
1968
  let rawResponseId;
1907
1969
  if (completedResponse) {
1908
1970
  rawResponseId = completedResponse.id;
@@ -1911,31 +1973,12 @@ var ResponsesAdapter = class extends AdapterBase {
1911
1973
  const replay = [...replayFromOutput(output)];
1912
1974
  if (completedResponse?.id) replay.push(opaqueItem("responses", "replay", { id: completedResponse.id }));
1913
1975
  const stopReason = completedResponse ? this.inferStopReason(completedResponse) : void 0;
1914
- const auxiliaryResult = await auxiliary.finalize(factory);
1915
- for (const event of auxiliaryResult.events) yield event;
1916
- if (!completedEmitted) {
1917
- completedEmitted = true;
1918
- const finalResponse = this.buildResponse(request, {
1919
- output,
1920
- replay,
1921
- stopReason,
1922
- usage: auxiliaryResult.usage,
1923
- billing: auxiliaryResult.billing,
1924
- auxiliary: auxiliaryResult.auxiliary,
1925
- warnings: auxiliaryResult.warnings,
1926
- metadataSources: auxiliaryResult.metadataSources,
1927
- rawResponseId
1928
- }, factory);
1929
- yield factory.responseCompleted({
1930
- replay: finalResponse.replay,
1931
- stopReason: finalResponse.stopReason,
1932
- trace: finalResponse.backend,
1933
- usage: finalResponse.usage,
1934
- billing: finalResponse.billing,
1935
- auxiliary: finalResponse.auxiliary,
1936
- warnings: finalResponse.warnings
1937
- });
1938
- }
1976
+ if (gate.tryComplete()) yield* this.emitStreamCompleted(factory, request, auxiliary, {
1977
+ output,
1978
+ replay,
1979
+ stopReason,
1980
+ rawResponseId
1981
+ });
1939
1982
  }
1940
1983
  inferStopReason(response) {
1941
1984
  if (response.status === "failed") return "error";
@@ -2086,7 +2129,7 @@ var MessagesAdapter = class extends AdapterBase {
2086
2129
  break;
2087
2130
  }
2088
2131
  case "tool_result": {
2089
- const content = mapper$2.ensureTextBlocks(item.content, `tool_result ${item.callId} content`).map(blockToText).join("\n");
2132
+ const content = mapper$2.textFromBlocks(item.content, `tool_result ${item.callId} content`);
2090
2133
  const block = {
2091
2134
  type: "tool_result",
2092
2135
  tool_use_id: item.callId,
@@ -2139,71 +2182,39 @@ var MessagesAdapter = class extends AdapterBase {
2139
2182
  stream: true
2140
2183
  };
2141
2184
  if (systemPrompt) body.system = systemPrompt;
2142
- if (request.tools && request.tools.length > 0) body.tools = request.tools.map((t) => ({
2185
+ body.tools = mapper$2.mapToolsIfPresent(request.tools, (t) => ({
2143
2186
  name: t.name,
2144
2187
  description: t.description,
2145
2188
  input_schema: t.inputSchema
2146
2189
  }));
2147
- if (request.toolChoice) {
2148
- if (request.toolChoice === "auto") body.tool_choice = { type: "auto" };
2149
- else if (request.toolChoice === "none") body.tool_choice = { type: "none" };
2150
- else if (request.toolChoice.type === "tool") body.tool_choice = {
2190
+ body.tool_choice = mapper$2.mapToolChoice(request.toolChoice, {
2191
+ auto: { type: "auto" },
2192
+ none: { type: "none" },
2193
+ tool: (name) => ({
2151
2194
  type: "tool",
2152
- name: request.toolChoice.name
2153
- };
2154
- }
2195
+ name
2196
+ })
2197
+ });
2155
2198
  if (request.temperature !== void 0) body.temperature = request.temperature;
2156
2199
  return body;
2157
2200
  }
2158
2201
  async *runStream(providerRequest, factory, request) {
2159
2202
  const auxiliary = this.createAuxiliaryState(request);
2160
- let completedEmitted = false;
2203
+ const gate = createCompletionGate();
2161
2204
  if (request.metadata) yield factory.responseWarning("Request metadata is not supported by the Messages adapter", "UNSUPPORTED_METADATA");
2162
- let response;
2163
- try {
2164
- response = await this.fetchFn(`${this.baseUrl}/messages`, {
2165
- method: "POST",
2166
- headers: {
2167
- "Content-Type": "application/json",
2168
- "x-api-key": this.apiKey,
2169
- "anthropic-version": this.apiVersion
2170
- },
2171
- body: JSON.stringify(providerRequest),
2172
- signal: request.signal
2173
- });
2174
- } catch (err) {
2175
- throw new AIProviderError(err instanceof Error ? err.message : String(err), "PROVIDER_ERROR");
2176
- }
2177
- if (!response.ok) {
2178
- const errorBody = await response.text().catch(() => "");
2179
- throw providerHttpError(response.status, errorBody);
2180
- }
2181
- const reader = response.body?.getReader();
2182
- if (!reader) throw new AIStreamError("Response body is not readable", "STREAM_ERROR");
2183
- const parser = new IncrementalStreamParser(splitSSEFrames, (frame) => {
2184
- let eventType = "";
2185
- let dataStr = "";
2186
- for (const rawLine of frame.split("\n")) {
2187
- const line = rawLine.trim();
2188
- if (line.startsWith("event: ")) eventType = line.slice(7).trim();
2189
- else if (line.startsWith("data: ")) dataStr += line.slice(6);
2190
- }
2191
- if (!eventType) return { status: "ignored" };
2192
- try {
2193
- const data = JSON.parse(dataStr);
2194
- return {
2195
- status: "parsed",
2196
- value: {
2197
- type: eventType,
2198
- data
2199
- }
2200
- };
2201
- } catch {
2202
- return { status: "malformed" };
2203
- }
2205
+ const { reader, headers } = await openProviderJsonStream({
2206
+ fetchFn: this.fetchFn,
2207
+ url: `${this.baseUrl}/messages`,
2208
+ headers: {
2209
+ "Content-Type": "application/json",
2210
+ "x-api-key": this.apiKey,
2211
+ "anthropic-version": this.apiVersion
2212
+ },
2213
+ body: providerRequest,
2214
+ signal: request.signal
2204
2215
  });
2216
+ const parser = createSseJsonParser();
2205
2217
  const output = [];
2206
- let streamDone = false;
2207
2218
  let messageResponse;
2208
2219
  let currentContentBlockIndex = -1;
2209
2220
  let currentItemType = null;
@@ -2211,7 +2222,6 @@ var MessagesAdapter = class extends AdapterBase {
2211
2222
  let currentToolName = "";
2212
2223
  let currentArgsText = "";
2213
2224
  let currentThinkingVisibility = "full";
2214
- let hasStreamedReasoning = false;
2215
2225
  const rawReplayContent = [];
2216
2226
  let textBuffer = "";
2217
2227
  let thinkingBuffer = "";
@@ -2220,160 +2230,142 @@ var MessagesAdapter = class extends AdapterBase {
2220
2230
  let stopSequence;
2221
2231
  let rawResponseId = "";
2222
2232
  if (request.include?.providerMetadata !== "off") {
2223
- const headerMetadata = pickProviderHeaders(response.headers);
2233
+ const headerMetadata = pickProviderHeaders(headers);
2224
2234
  auxiliary.recordProviderMetadata("header", Object.keys(headerMetadata).length > 0 ? { headers: headerMetadata } : void 0);
2225
2235
  }
2226
- try {
2227
- while (true) {
2228
- const { done, value } = await reader.read().catch((err) => {
2229
- throw new AIStreamError(`Failed to read response stream: ${err instanceof Error ? err.message : String(err)}`, "STREAM_ERROR");
2230
- });
2231
- const { items: events, malformed: malformedEvents } = done ? parser.flush() : parser.feed(value);
2232
- const malformedWarning = emitMalformedStreamWarning(factory, {
2233
- count: malformedEvents,
2234
- providerLabel: "Messages",
2235
- transportLabel: "SSE event(s)"
2236
- });
2237
- if (malformedWarning) yield malformedWarning;
2238
- for (const sseEvent of events) switch (sseEvent.type) {
2239
- case "ping": continue;
2240
- case "error": {
2241
- const err = sseEvent.data.error;
2242
- yield factory.responseWarning(err.message, err.type);
2243
- continue;
2244
- }
2245
- case "message_start":
2246
- messageResponse = sseEvent.data.message;
2247
- rawResponseId = messageResponse.id;
2248
- if (messageResponse.content.some((b) => b.type === "thinking" || b.type === "redacted_thinking")) hasStreamedReasoning = true;
2249
- continue;
2250
- case "content_block_start": {
2251
- const block = sseEvent.data.content_block;
2252
- currentContentBlockIndex = sseEvent.data.index;
2253
- switch (block.type) {
2254
- case "text":
2255
- currentItemType = "message";
2256
- currentItemId = synthesizeItemId("msg", currentContentBlockIndex, rawResponseId);
2257
- textBuffer = "";
2258
- yield factory.messageStarted(currentItemId);
2259
- break;
2260
- case "thinking":
2261
- hasStreamedReasoning = true;
2262
- currentItemType = "reasoning";
2263
- currentItemId = synthesizeItemId("reason", currentContentBlockIndex, rawResponseId);
2264
- currentThinkingVisibility = "full";
2265
- thinkingBuffer = "";
2266
- yield factory.reasoningStarted(currentItemId, "full");
2267
- break;
2268
- case "redacted_thinking": {
2269
- hasStreamedReasoning = true;
2270
- currentItemType = "reasoning";
2271
- currentItemId = synthesizeItemId("reason-redacted", currentContentBlockIndex, rawResponseId);
2272
- currentThinkingVisibility = "redacted";
2273
- const data = block.data;
2274
- yield factory.reasoningStarted(currentItemId, "redacted");
2275
- yield factory.reasoningDelta(currentItemId, textBlock(data));
2276
- const redactedItem = reasoningItem([textBlock(data)], "redacted", currentItemId);
2277
- yield factory.reasoningCompleted(currentItemId);
2278
- output.push(redactedItem);
2279
- rawReplayContent.push({
2280
- type: "redacted_thinking",
2281
- data
2282
- });
2283
- currentItemType = null;
2284
- break;
2285
- }
2286
- case "tool_use": {
2287
- const tuBlock = block;
2288
- currentItemType = "tool_call";
2289
- currentItemId = tuBlock.id;
2290
- currentToolName = tuBlock.name;
2291
- currentArgsText = "";
2292
- argsBuffer = "";
2293
- yield factory.toolCallStarted(currentItemId, currentToolName);
2294
- break;
2295
- }
2296
- }
2297
- continue;
2298
- }
2299
- case "content_block_delta": {
2300
- const delta = sseEvent.data.delta;
2301
- switch (delta.type) {
2302
- case "text_delta":
2303
- if (currentItemType === "message" && currentItemId) {
2304
- const txt = delta.text;
2305
- textBuffer += txt;
2306
- yield factory.messageDelta(currentItemId, textBlock(txt));
2307
- }
2308
- break;
2309
- case "thinking_delta":
2310
- if (currentItemType === "reasoning" && currentItemId) {
2311
- const txt = delta.thinking;
2312
- thinkingBuffer += txt;
2313
- yield factory.reasoningDelta(currentItemId, textBlock(txt));
2314
- }
2315
- break;
2316
- case "input_json_delta":
2317
- if (currentItemType === "tool_call" && currentItemId) {
2318
- const partial = delta.partial_json;
2319
- argsBuffer += partial;
2320
- yield factory.toolCallDelta(currentItemId, { argumentsText: partial });
2321
- }
2322
- break;
2323
- }
2324
- continue;
2325
- }
2326
- case "content_block_stop":
2327
- if (currentItemType === "message" && currentItemId) {
2328
- yield factory.messageCompleted(currentItemId);
2329
- output.push(messageItem([textBlock(textBuffer)], { id: currentItemId }));
2330
- rawReplayContent.push({
2331
- type: "text",
2332
- text: textBuffer
2333
- });
2334
- } else if (currentItemType === "reasoning" && currentItemId && currentThinkingVisibility !== "redacted") {
2236
+ for await (const batch of iterateProviderStreamBatches({
2237
+ reader,
2238
+ parser,
2239
+ factory,
2240
+ providerLabel: "Messages",
2241
+ transportLabel: "SSE event(s)",
2242
+ incompleteMessage: "Stream ended with an incomplete Messages SSE frame"
2243
+ })) {
2244
+ for (const warning of batch.warnings) yield warning;
2245
+ for (const sseEvent of batch.items) switch (sseEvent.type) {
2246
+ case "ping": continue;
2247
+ case "error": {
2248
+ const err = sseEvent.data.error;
2249
+ yield factory.responseWarning(err.message, err.type);
2250
+ continue;
2251
+ }
2252
+ case "message_start":
2253
+ messageResponse = sseEvent.data.message;
2254
+ rawResponseId = messageResponse.id;
2255
+ continue;
2256
+ case "content_block_start": {
2257
+ const block = sseEvent.data.content_block;
2258
+ currentContentBlockIndex = sseEvent.data.index;
2259
+ switch (block.type) {
2260
+ case "text":
2261
+ currentItemType = "message";
2262
+ currentItemId = synthesizeItemId("msg", currentContentBlockIndex, rawResponseId);
2263
+ textBuffer = "";
2264
+ yield factory.messageStarted(currentItemId);
2265
+ break;
2266
+ case "thinking":
2267
+ currentItemType = "reasoning";
2268
+ currentItemId = synthesizeItemId("reason", currentContentBlockIndex, rawResponseId);
2269
+ currentThinkingVisibility = "full";
2270
+ thinkingBuffer = "";
2271
+ yield factory.reasoningStarted(currentItemId, "full");
2272
+ break;
2273
+ case "redacted_thinking": {
2274
+ currentItemType = "reasoning";
2275
+ currentItemId = synthesizeItemId("reason-redacted", currentContentBlockIndex, rawResponseId);
2276
+ currentThinkingVisibility = "redacted";
2277
+ const data = block.data;
2278
+ yield factory.reasoningStarted(currentItemId, "redacted");
2279
+ yield factory.reasoningDelta(currentItemId, textBlock(data));
2280
+ const redactedItem = reasoningItem([textBlock(data)], "redacted", currentItemId);
2335
2281
  yield factory.reasoningCompleted(currentItemId);
2336
- output.push(reasoningItem([textBlock(thinkingBuffer)], currentThinkingVisibility, currentItemId));
2337
- rawReplayContent.push({
2338
- type: "thinking",
2339
- thinking: thinkingBuffer
2340
- });
2341
- } else if (currentItemType === "tool_call" && currentItemId) {
2342
- const tcItem = toolCallItem(currentItemId, currentToolName, currentArgsText || argsBuffer);
2343
- yield factory.toolCallCompleted(currentItemId);
2344
- output.push(tcItem);
2282
+ output.push(redactedItem);
2345
2283
  rawReplayContent.push({
2346
- type: "tool_use",
2347
- id: currentItemId,
2348
- name: currentToolName,
2349
- input: parseProviderToolUseInput(currentArgsText || argsBuffer)
2284
+ type: "redacted_thinking",
2285
+ data
2350
2286
  });
2287
+ currentItemType = null;
2288
+ break;
2289
+ }
2290
+ case "tool_use": {
2291
+ const tuBlock = block;
2292
+ currentItemType = "tool_call";
2293
+ currentItemId = tuBlock.id;
2294
+ currentToolName = tuBlock.name;
2295
+ currentArgsText = "";
2296
+ argsBuffer = "";
2297
+ yield factory.toolCallStarted(currentItemId, currentToolName);
2298
+ break;
2351
2299
  }
2352
- currentItemType = null;
2353
- currentItemId = "";
2354
- continue;
2355
- case "message_delta": {
2356
- stopReason = sseEvent.data.delta.stop_reason;
2357
- stopSequence = sseEvent.data.delta.stop_sequence;
2358
- const u = sseEvent.data.usage;
2359
- if (u) auxiliary.recordUsage(usageFromAnthropicMessages(u), "stream", u);
2360
- continue;
2361
2300
  }
2362
- case "message_stop": break;
2301
+ continue;
2363
2302
  }
2364
- if (done) {
2365
- streamDone = true;
2366
- break;
2303
+ case "content_block_delta": {
2304
+ const delta = sseEvent.data.delta;
2305
+ switch (delta.type) {
2306
+ case "text_delta":
2307
+ if (currentItemType === "message" && currentItemId) {
2308
+ const txt = delta.text;
2309
+ textBuffer += txt;
2310
+ yield factory.messageDelta(currentItemId, textBlock(txt));
2311
+ }
2312
+ break;
2313
+ case "thinking_delta":
2314
+ if (currentItemType === "reasoning" && currentItemId) {
2315
+ const txt = delta.thinking;
2316
+ thinkingBuffer += txt;
2317
+ yield factory.reasoningDelta(currentItemId, textBlock(txt));
2318
+ }
2319
+ break;
2320
+ case "input_json_delta":
2321
+ if (currentItemType === "tool_call" && currentItemId) {
2322
+ const partial = delta.partial_json;
2323
+ argsBuffer += partial;
2324
+ yield factory.toolCallDelta(currentItemId, { argumentsText: partial });
2325
+ }
2326
+ break;
2327
+ }
2328
+ continue;
2367
2329
  }
2368
- }
2369
- } finally {
2370
- try {
2371
- if (!streamDone) await reader.cancel().catch(() => void 0);
2372
- } finally {
2373
- reader.releaseLock();
2330
+ case "content_block_stop":
2331
+ if (currentItemType === "message" && currentItemId) {
2332
+ yield factory.messageCompleted(currentItemId);
2333
+ output.push(messageItem([textBlock(textBuffer)], { id: currentItemId }));
2334
+ rawReplayContent.push({
2335
+ type: "text",
2336
+ text: textBuffer
2337
+ });
2338
+ } else if (currentItemType === "reasoning" && currentItemId && currentThinkingVisibility !== "redacted") {
2339
+ yield factory.reasoningCompleted(currentItemId);
2340
+ output.push(reasoningItem([textBlock(thinkingBuffer)], currentThinkingVisibility, currentItemId));
2341
+ rawReplayContent.push({
2342
+ type: "thinking",
2343
+ thinking: thinkingBuffer
2344
+ });
2345
+ } else if (currentItemType === "tool_call" && currentItemId) {
2346
+ const tcItem = toolCallItem(currentItemId, currentToolName, currentArgsText || argsBuffer);
2347
+ yield factory.toolCallCompleted(currentItemId);
2348
+ output.push(tcItem);
2349
+ rawReplayContent.push({
2350
+ type: "tool_use",
2351
+ id: currentItemId,
2352
+ name: currentToolName,
2353
+ input: parseProviderToolUseInput(currentArgsText || argsBuffer)
2354
+ });
2355
+ }
2356
+ currentItemType = null;
2357
+ currentItemId = "";
2358
+ continue;
2359
+ case "message_delta": {
2360
+ stopReason = sseEvent.data.delta.stop_reason;
2361
+ stopSequence = sseEvent.data.delta.stop_sequence;
2362
+ const u = sseEvent.data.usage;
2363
+ if (u) auxiliary.recordUsage(usageFromAnthropicMessages(u), "stream", u);
2364
+ continue;
2365
+ }
2366
+ case "message_stop": break;
2374
2367
  }
2375
2368
  }
2376
- if (parser.getRemaining().trim().length > 0) yield factory.responseWarning("Stream ended with an incomplete Messages SSE frame", "STREAM_ERROR");
2377
2369
  const replay = [...replayFromOutput(output)];
2378
2370
  if (messageResponse) {
2379
2371
  const replayContent = rawReplayContent.length > 0 ? rawReplayContent : messageResponse.content;
@@ -2391,32 +2383,12 @@ var MessagesAdapter = class extends AdapterBase {
2391
2383
  stopReason,
2392
2384
  stopSequence
2393
2385
  }));
2394
- if (!hasStreamedReasoning) {}
2395
- const auxiliaryResult = await auxiliary.finalize(factory);
2396
- for (const event of auxiliaryResult.events) yield event;
2397
- if (!completedEmitted) {
2398
- completedEmitted = true;
2399
- const finalResponse = this.buildResponse(request, {
2400
- output,
2401
- replay,
2402
- stopReason: stopReason ? mapStopReason(stopReason) : void 0,
2403
- usage: auxiliaryResult.usage,
2404
- billing: auxiliaryResult.billing,
2405
- auxiliary: auxiliaryResult.auxiliary,
2406
- warnings: auxiliaryResult.warnings,
2407
- metadataSources: auxiliaryResult.metadataSources,
2408
- rawResponseId
2409
- }, factory);
2410
- yield factory.responseCompleted({
2411
- replay: finalResponse.replay,
2412
- stopReason: finalResponse.stopReason,
2413
- trace: finalResponse.backend,
2414
- usage: finalResponse.usage,
2415
- billing: finalResponse.billing,
2416
- auxiliary: finalResponse.auxiliary,
2417
- warnings: finalResponse.warnings
2418
- });
2419
- }
2386
+ if (gate.tryComplete()) yield* this.emitStreamCompleted(factory, request, auxiliary, {
2387
+ output,
2388
+ replay,
2389
+ stopReason: stopReason ? mapStopReason(stopReason) : void 0,
2390
+ rawResponseId
2391
+ });
2420
2392
  }
2421
2393
  };
2422
2394
  //#endregion
@@ -2527,7 +2499,7 @@ var ChatCompletionsAdapter = class extends AdapterBase {
2527
2499
  for (const item of request.input) switch (item.type) {
2528
2500
  case "message": {
2529
2501
  const role = item.role;
2530
- const text = contentBlocksToText(mapper$1.ensureTextBlocks(item.content, `input message (${item.role}) content`));
2502
+ const text = mapper$1.textFromBlocks(item.content, `input message (${item.role}) content`);
2531
2503
  messages.push({
2532
2504
  role,
2533
2505
  content: text || null
@@ -2557,13 +2529,13 @@ var ChatCompletionsAdapter = class extends AdapterBase {
2557
2529
  role: "tool",
2558
2530
  tool_call_id: item.callId,
2559
2531
  name: item.toolName,
2560
- content: contentBlocksToText(mapper$1.ensureTextBlocks(item.content, `tool_result ${item.callId} content`))
2532
+ content: mapper$1.textFromBlocks(item.content, `tool_result ${item.callId} content`)
2561
2533
  });
2562
2534
  break;
2563
2535
  case "reasoning":
2564
2536
  messages.push({
2565
2537
  role: "assistant",
2566
- content: contentBlocksToText(mapper$1.ensureTextBlocks(item.content, "reasoning content"))
2538
+ content: mapper$1.textFromBlocks(item.content, "reasoning content")
2567
2539
  });
2568
2540
  break;
2569
2541
  case "opaque": {
@@ -2591,7 +2563,7 @@ var ChatCompletionsAdapter = class extends AdapterBase {
2591
2563
  stream: true,
2592
2564
  n: 1
2593
2565
  };
2594
- if (request.tools && request.tools.length > 0) body.tools = request.tools.map((t) => ({
2566
+ body.tools = mapper$1.mapToolsIfPresent(request.tools, (t) => ({
2595
2567
  type: "function",
2596
2568
  function: {
2597
2569
  name: t.name,
@@ -2599,14 +2571,14 @@ var ChatCompletionsAdapter = class extends AdapterBase {
2599
2571
  parameters: t.inputSchema
2600
2572
  }
2601
2573
  }));
2602
- if (request.toolChoice) {
2603
- if (request.toolChoice === "auto") body.tool_choice = "auto";
2604
- else if (request.toolChoice === "none") body.tool_choice = "none";
2605
- else if (request.toolChoice.type === "tool") body.tool_choice = {
2574
+ body.tool_choice = mapper$1.mapToolChoice(request.toolChoice, {
2575
+ auto: "auto",
2576
+ none: "none",
2577
+ tool: (name) => ({
2606
2578
  type: "function",
2607
- function: { name: request.toolChoice.name }
2608
- };
2609
- }
2579
+ function: { name }
2580
+ })
2581
+ });
2610
2582
  if (request.temperature !== void 0) body.temperature = request.temperature;
2611
2583
  if (request.maxOutputTokens !== void 0) body.max_tokens = request.maxOutputTokens;
2612
2584
  if (request.metadata) body.metadata = request.metadata;
@@ -2614,42 +2586,19 @@ var ChatCompletionsAdapter = class extends AdapterBase {
2614
2586
  }
2615
2587
  async *runStream(providerRequest, factory, request) {
2616
2588
  const auxiliary = this.createAuxiliaryState(request);
2617
- let response;
2618
- try {
2619
- response = await this.fetchFn(`${this.baseUrl}/chat/completions`, {
2620
- method: "POST",
2621
- headers: {
2622
- "Content-Type": "application/json",
2623
- Authorization: `Bearer ${this.apiKey}`
2624
- },
2625
- body: JSON.stringify(providerRequest),
2626
- signal: request.signal
2627
- });
2628
- } catch (err) {
2629
- throw new AIProviderError(err instanceof Error ? err.message : String(err), "PROVIDER_ERROR");
2630
- }
2631
- if (!response.ok) {
2632
- const errorBody = await response.text().catch(() => "");
2633
- throw providerHttpError(response.status, errorBody);
2634
- }
2635
- const reader = response.body?.getReader();
2636
- if (!reader) throw new AIStreamError("Response body is not readable", "STREAM_ERROR");
2637
- const parser = new IncrementalStreamParser(splitLines, (item) => {
2638
- const trimmed = item.trim();
2639
- if (!trimmed.startsWith("data: ")) return { status: "ignored" };
2640
- const data = trimmed.slice(6).trim();
2641
- if (data === "[DONE]") return { status: "ignored" };
2642
- try {
2643
- return {
2644
- status: "parsed",
2645
- value: JSON.parse(data)
2646
- };
2647
- } catch {
2648
- return { status: "malformed" };
2649
- }
2589
+ const gate = createCompletionGate();
2590
+ const { reader } = await openProviderJsonStream({
2591
+ fetchFn: this.fetchFn,
2592
+ url: `${this.baseUrl}/chat/completions`,
2593
+ headers: {
2594
+ "Content-Type": "application/json",
2595
+ Authorization: `Bearer ${this.apiKey}`
2596
+ },
2597
+ body: providerRequest,
2598
+ signal: request.signal
2650
2599
  });
2600
+ const parser = createChatCompletionsSseParser();
2651
2601
  const output = [];
2652
- let streamDone = false;
2653
2602
  let responseId;
2654
2603
  let accumulatedContent = "";
2655
2604
  let accumulatedReasoning = "";
@@ -2657,9 +2606,7 @@ var ChatCompletionsAdapter = class extends AdapterBase {
2657
2606
  let currentReasoningId = "";
2658
2607
  let hasMessageStarted = false;
2659
2608
  let hasReasoningStarted = false;
2660
- let completedEmitted = false;
2661
2609
  let warnedNonZeroChoice = false;
2662
- const buildResponse = this.buildResponse.bind(this);
2663
2610
  const pendingToolCalls = /* @__PURE__ */ new Map();
2664
2611
  const reasoningByField = /* @__PURE__ */ new Map();
2665
2612
  const finalizePendingTurn = () => {
@@ -2700,164 +2647,132 @@ var ChatCompletionsAdapter = class extends AdapterBase {
2700
2647
  };
2701
2648
  };
2702
2649
  const emitCompleted = async function* (stopReason, assistantReplayMessage, rawResponseId) {
2703
- if (completedEmitted) {
2650
+ if (!gate.tryComplete()) {
2704
2651
  yield factory.responseWarning("Duplicate finish signal ignored", "DUPLICATE_FINISH");
2705
2652
  return;
2706
2653
  }
2707
- completedEmitted = true;
2708
2654
  const replay = [...replayFromOutput(output)];
2709
2655
  if (assistantReplayMessage) replay.push(opaqueItem("chat.completions", "replay", {
2710
2656
  replaceCanonical: true,
2711
2657
  messages: [assistantReplayMessage]
2712
2658
  }));
2713
- const auxiliaryResult = await auxiliary.finalize(factory);
2714
- for (const event of auxiliaryResult.events) yield event;
2715
- const finalResponse = buildResponse(request, {
2659
+ yield* this.emitStreamCompleted(factory, request, auxiliary, {
2716
2660
  output,
2717
2661
  replay,
2718
2662
  stopReason,
2719
- usage: auxiliaryResult.usage,
2720
- billing: auxiliaryResult.billing,
2721
- auxiliary: auxiliaryResult.auxiliary,
2722
- warnings: auxiliaryResult.warnings,
2723
- metadataSources: auxiliaryResult.metadataSources,
2724
2663
  rawResponseId
2725
- }, factory);
2726
- yield factory.responseCompleted({
2727
- replay: finalResponse.replay,
2728
- stopReason: finalResponse.stopReason,
2729
- trace: finalResponse.backend,
2730
- usage: finalResponse.usage,
2731
- billing: finalResponse.billing,
2732
- auxiliary: finalResponse.auxiliary,
2733
- warnings: finalResponse.warnings
2734
2664
  });
2735
- };
2736
- try {
2737
- while (true) {
2738
- const { done, value } = await reader.read().catch((err) => {
2739
- throw new AIStreamError(`Failed to read response stream: ${err instanceof Error ? err.message : String(err)}`, "STREAM_ERROR");
2740
- });
2741
- const { items: chunks, malformed: malformedEvents } = done ? parser.flush() : parser.feed(value);
2742
- const malformedWarning = emitMalformedStreamWarning(factory, {
2743
- count: malformedEvents,
2744
- providerLabel: "Chat Completions",
2745
- transportLabel: "SSE event(s)"
2746
- });
2747
- if (malformedWarning) yield malformedWarning;
2748
- for (const chunk of chunks) {
2749
- responseId = chunk.id;
2750
- if (chunk.usage) auxiliary.recordUsage(usageFromChatCompletions(chunk.usage), "final", chunk.usage);
2751
- for (const choice of chunk.choices) {
2752
- if (choice.index !== 0) {
2753
- if (!warnedNonZeroChoice) {
2754
- yield factory.responseWarning(`Chat Completions returned choice index ${choice.index}; only the first choice (index 0) is supported. This choice is ignored.`, "MULTIPLE_CHOICES_IGNORED");
2755
- warnedNonZeroChoice = true;
2756
- }
2757
- continue;
2665
+ }.bind(this);
2666
+ for await (const batch of iterateProviderStreamBatches({
2667
+ reader,
2668
+ parser,
2669
+ factory,
2670
+ providerLabel: "Chat Completions",
2671
+ transportLabel: "SSE event(s)",
2672
+ incompleteMessage: "Stream ended with an incomplete Chat Completions SSE frame"
2673
+ })) {
2674
+ for (const warning of batch.warnings) yield warning;
2675
+ for (const chunk of batch.items) {
2676
+ responseId = chunk.id;
2677
+ if (chunk.usage) auxiliary.recordUsage(usageFromChatCompletions(chunk.usage), "final", chunk.usage);
2678
+ for (const choice of chunk.choices) {
2679
+ if (choice.index !== 0) {
2680
+ if (!warnedNonZeroChoice) {
2681
+ yield factory.responseWarning(`Chat Completions returned choice index ${choice.index}; only the first choice (index 0) is supported. This choice is ignored.`, "MULTIPLE_CHOICES_IGNORED");
2682
+ warnedNonZeroChoice = true;
2758
2683
  }
2759
- if (completedEmitted) {
2760
- if (choice.finish_reason) yield factory.responseWarning("Duplicate finish signal ignored", "DUPLICATE_FINISH");
2761
- continue;
2684
+ continue;
2685
+ }
2686
+ if (gate.completed) {
2687
+ if (choice.finish_reason) yield factory.responseWarning("Duplicate finish signal ignored", "DUPLICATE_FINISH");
2688
+ continue;
2689
+ }
2690
+ const delta = choice.delta;
2691
+ const finishReason = choice.finish_reason;
2692
+ const reasoningDeltas = extractReasoningDeltas(delta);
2693
+ const ensureMessageStarted = () => {
2694
+ if (hasMessageStarted) return;
2695
+ currentMessageId = `msg-${chunk.id}`;
2696
+ hasMessageStarted = true;
2697
+ accumulatedContent = "";
2698
+ };
2699
+ if (reasoningDeltas.length > 0) {
2700
+ if (!hasReasoningStarted) {
2701
+ currentReasoningId = `reason-${chunk.id}`;
2702
+ hasReasoningStarted = true;
2703
+ accumulatedReasoning = "";
2704
+ yield factory.reasoningStarted(currentReasoningId, "full");
2762
2705
  }
2763
- const delta = choice.delta;
2764
- const finishReason = choice.finish_reason;
2765
- const reasoningDeltas = extractReasoningDeltas(delta);
2766
- const ensureMessageStarted = () => {
2767
- if (hasMessageStarted) return;
2768
- currentMessageId = `msg-${chunk.id}`;
2769
- hasMessageStarted = true;
2770
- accumulatedContent = "";
2771
- };
2772
- if (reasoningDeltas.length > 0) {
2773
- if (!hasReasoningStarted) {
2774
- currentReasoningId = `reason-${chunk.id}`;
2775
- hasReasoningStarted = true;
2776
- accumulatedReasoning = "";
2777
- yield factory.reasoningStarted(currentReasoningId, "full");
2778
- }
2779
- for (const reasoningDelta of reasoningDeltas) {
2780
- accumulatedReasoning += reasoningDelta.text;
2781
- reasoningByField.set(reasoningDelta.field, (reasoningByField.get(reasoningDelta.field) ?? "") + reasoningDelta.text);
2782
- yield factory.reasoningDelta(currentReasoningId, textBlock(reasoningDelta.text));
2783
- }
2706
+ for (const reasoningDelta of reasoningDeltas) {
2707
+ accumulatedReasoning += reasoningDelta.text;
2708
+ reasoningByField.set(reasoningDelta.field, (reasoningByField.get(reasoningDelta.field) ?? "") + reasoningDelta.text);
2709
+ yield factory.reasoningDelta(currentReasoningId, textBlock(reasoningDelta.text));
2784
2710
  }
2785
- if (delta.content) {
2786
- if (!hasMessageStarted) {
2787
- ensureMessageStarted();
2788
- yield factory.messageStarted(currentMessageId);
2789
- }
2790
- accumulatedContent += delta.content;
2791
- yield factory.messageDelta(currentMessageId, textBlock(delta.content));
2711
+ }
2712
+ if (delta.content) {
2713
+ if (!hasMessageStarted) {
2714
+ ensureMessageStarted();
2715
+ yield factory.messageStarted(currentMessageId);
2792
2716
  }
2793
- if (delta.tool_calls) {
2794
- if (!hasMessageStarted) {
2795
- ensureMessageStarted();
2796
- yield factory.messageStarted(currentMessageId);
2797
- }
2798
- for (const tc of delta.tool_calls) {
2799
- const idx = tc.index;
2800
- if (tc.id) {
2801
- pendingToolCalls.set(idx, {
2802
- id: tc.id,
2803
- name: tc.function?.name ?? "",
2804
- args: ""
2805
- });
2806
- yield factory.toolCallStarted(tc.id, tc.function?.name ?? "");
2807
- }
2808
- if (tc.function?.arguments) {
2809
- const pending = pendingToolCalls.get(idx);
2810
- if (pending) {
2811
- pending.args += tc.function.arguments;
2812
- yield factory.toolCallDelta(pending.id, { argumentsText: tc.function.arguments });
2813
- }
2814
- }
2815
- }
2717
+ accumulatedContent += delta.content;
2718
+ yield factory.messageDelta(currentMessageId, textBlock(delta.content));
2719
+ }
2720
+ if (delta.tool_calls) {
2721
+ if (!hasMessageStarted) {
2722
+ ensureMessageStarted();
2723
+ yield factory.messageStarted(currentMessageId);
2816
2724
  }
2817
- if (delta.function_call) {
2818
- if (!hasMessageStarted) {
2819
- ensureMessageStarted();
2820
- yield factory.messageStarted(currentMessageId);
2821
- }
2822
- if (delta.function_call.name) {
2823
- const fcId = `fc-${chunk.id}-0`;
2824
- pendingToolCalls.set(0, {
2825
- id: fcId,
2826
- name: delta.function_call.name,
2725
+ for (const tc of delta.tool_calls) {
2726
+ const idx = tc.index;
2727
+ if (tc.id) {
2728
+ pendingToolCalls.set(idx, {
2729
+ id: tc.id,
2730
+ name: tc.function?.name ?? "",
2827
2731
  args: ""
2828
2732
  });
2829
- yield factory.toolCallStarted(fcId, delta.function_call.name);
2733
+ yield factory.toolCallStarted(tc.id, tc.function?.name ?? "");
2830
2734
  }
2831
- if (delta.function_call.arguments) {
2832
- const pending = pendingToolCalls.get(0);
2735
+ if (tc.function?.arguments) {
2736
+ const pending = pendingToolCalls.get(idx);
2833
2737
  if (pending) {
2834
- pending.args += delta.function_call.arguments;
2835
- yield factory.toolCallDelta(pending.id, { argumentsText: delta.function_call.arguments });
2738
+ pending.args += tc.function.arguments;
2739
+ yield factory.toolCallDelta(pending.id, { argumentsText: tc.function.arguments });
2836
2740
  }
2837
2741
  }
2838
2742
  }
2839
- if (finishReason && finishReason !== null) {
2840
- const { events, assistantReplayMessage } = finalizePendingTurn();
2841
- for (const event of events) yield event;
2842
- yield* emitCompleted(mapStopReason(finishReason), assistantReplayMessage, chunk.id);
2743
+ }
2744
+ if (delta.function_call) {
2745
+ if (!hasMessageStarted) {
2746
+ ensureMessageStarted();
2747
+ yield factory.messageStarted(currentMessageId);
2748
+ }
2749
+ if (delta.function_call.name) {
2750
+ const fcId = `fc-${chunk.id}-0`;
2751
+ pendingToolCalls.set(0, {
2752
+ id: fcId,
2753
+ name: delta.function_call.name,
2754
+ args: ""
2755
+ });
2756
+ yield factory.toolCallStarted(fcId, delta.function_call.name);
2757
+ }
2758
+ if (delta.function_call.arguments) {
2759
+ const pending = pendingToolCalls.get(0);
2760
+ if (pending) {
2761
+ pending.args += delta.function_call.arguments;
2762
+ yield factory.toolCallDelta(pending.id, { argumentsText: delta.function_call.arguments });
2763
+ }
2843
2764
  }
2844
2765
  }
2766
+ if (finishReason && finishReason !== null) {
2767
+ const { events, assistantReplayMessage } = finalizePendingTurn();
2768
+ for (const event of events) yield event;
2769
+ yield* emitCompleted(mapStopReason(finishReason), assistantReplayMessage, chunk.id);
2770
+ }
2845
2771
  }
2846
- if (done) {
2847
- streamDone = true;
2848
- break;
2849
- }
2850
- }
2851
- } finally {
2852
- try {
2853
- if (!streamDone) await reader.cancel().catch(() => void 0);
2854
- } finally {
2855
- reader.releaseLock();
2856
2772
  }
2857
2773
  }
2858
- if (parser.getRemaining().trim().length > 0) yield factory.responseWarning("Stream ended with an incomplete Chat Completions SSE frame", "STREAM_ERROR");
2859
- if (!completedEmitted && (hasMessageStarted || hasReasoningStarted || pendingToolCalls.size > 0)) {
2860
- yield factory.responseWarning("Stream ended without a finish_reason", "INCOMPLETE_STREAM");
2774
+ if (!gate.completed && (hasMessageStarted || hasReasoningStarted || pendingToolCalls.size > 0)) {
2775
+ yield factory.responseWarning("Stream ended without a finish_reason", WarningCode.STREAM_INCOMPLETE);
2861
2776
  const { events, assistantReplayMessage } = finalizePendingTurn();
2862
2777
  for (const event of events) yield event;
2863
2778
  yield* emitCompleted(void 0, assistantReplayMessage, responseId);
@@ -2923,7 +2838,7 @@ var OllamaAdapter = class extends AdapterBase {
2923
2838
  const role = item.role;
2924
2839
  messages.push({
2925
2840
  role,
2926
- content: contentBlocksToText(mapper.ensureTextBlocks(item.content, `input message (${item.role}) content`))
2841
+ content: mapper.textFromBlocks(item.content, `input message (${item.role}) content`)
2927
2842
  });
2928
2843
  break;
2929
2844
  }
@@ -2949,7 +2864,7 @@ var OllamaAdapter = class extends AdapterBase {
2949
2864
  if (queue && queue.length > 0) queue.shift();
2950
2865
  messages.push({
2951
2866
  role: "tool",
2952
- content: contentBlocksToText(mapper.ensureTextBlocks(item.content, `tool_result ${item.callId} content`))
2867
+ content: mapper.textFromBlocks(item.content, `tool_result ${item.callId} content`)
2953
2868
  });
2954
2869
  break;
2955
2870
  }
@@ -3010,57 +2925,31 @@ var OllamaAdapter = class extends AdapterBase {
3010
2925
  }
3011
2926
  async *runStream(providerRequest, factory, request) {
3012
2927
  const auxiliary = this.createAuxiliaryState(request);
3013
- let completedEmitted = false;
2928
+ const gate = createCompletionGate();
3014
2929
  if (request.toolChoice && request.toolChoice !== "auto") yield factory.responseWarning(request.toolChoice === "none" ? "Ollama toolChoice none was mapped by omitting tools" : `Ollama cannot force tool choice; only tool "${request.toolChoice.name}" was provided as a best-effort constraint`, WarningCode.CAPABILITY_DOWNGRADE);
3015
2930
  if (request.metadata) yield factory.responseWarning("Request metadata is not supported by the Ollama adapter", "UNSUPPORTED_METADATA");
3016
2931
  const headers = { "Content-Type": "application/json" };
3017
2932
  if (this.apiKey) headers.Authorization = `Bearer ${this.apiKey}`;
3018
- let response;
3019
- try {
3020
- response = await this.fetchFn(`${this.baseUrl}/api/chat`, {
3021
- method: "POST",
3022
- headers,
3023
- body: JSON.stringify(providerRequest),
3024
- signal: request.signal
3025
- });
3026
- } catch (err) {
3027
- throw new AIProviderError(err instanceof Error ? err.message : String(err), "PROVIDER_ERROR");
3028
- }
3029
- if (!response.ok) {
3030
- const errorBody = await response.text().catch(() => "");
3031
- throw providerHttpError(response.status, errorBody);
3032
- }
3033
- const reader = response.body?.getReader();
3034
- if (!reader) throw new AIStreamError("Response body is not readable", "STREAM_ERROR");
3035
- const parser = new IncrementalStreamParser(splitLines, (item) => {
3036
- const trimmed = item.trim();
3037
- if (!trimmed) return { status: "ignored" };
3038
- try {
3039
- const parsed = JSON.parse(trimmed);
3040
- if (parsed && typeof parsed === "object" && "message" in parsed) return {
3041
- status: "parsed",
3042
- value: parsed
3043
- };
3044
- return { status: "malformed" };
3045
- } catch {
3046
- return { status: "malformed" };
3047
- }
2933
+ const { reader } = await openProviderJsonStream({
2934
+ fetchFn: this.fetchFn,
2935
+ url: `${this.baseUrl}/api/chat`,
2936
+ headers,
2937
+ body: providerRequest,
2938
+ signal: request.signal
3048
2939
  });
2940
+ const parser = createNdjsonLineParser((value) => !!value && typeof value === "object" && "message" in value);
3049
2941
  const output = [];
3050
- let streamDone = false;
3051
2942
  let responseId;
3052
2943
  let accumulatedContent = "";
3053
2944
  let currentMessageId = "";
3054
2945
  let hasMessageStarted = false;
3055
2946
  let pendingToolCalls = [];
3056
2947
  let toolCallIndex = 0;
3057
- const buildResponse = this.buildResponse.bind(this);
3058
2948
  const emitCompleted = async function* (stopReason, rawResponseId) {
3059
- if (completedEmitted) {
2949
+ if (!gate.tryComplete()) {
3060
2950
  yield factory.responseWarning("Duplicate finish signal ignored", "DUPLICATE_FINISH");
3061
2951
  return;
3062
2952
  }
3063
- completedEmitted = true;
3064
2953
  const replay = replayFromOutput(output);
3065
2954
  if (accumulatedContent || pendingToolCalls.length > 0) replay.push(opaqueItem("ollama", "replay", {
3066
2955
  role: "assistant",
@@ -3073,114 +2962,83 @@ var OllamaAdapter = class extends AdapterBase {
3073
2962
  }
3074
2963
  }))
3075
2964
  }));
3076
- const auxiliaryResult = await auxiliary.finalize(factory);
3077
- for (const event of auxiliaryResult.events) yield event;
3078
- const finalResponse = buildResponse(request, {
2965
+ yield* this.emitStreamCompleted(factory, request, auxiliary, {
3079
2966
  output,
3080
2967
  replay,
3081
2968
  stopReason,
3082
- usage: auxiliaryResult.usage,
3083
- billing: auxiliaryResult.billing,
3084
- auxiliary: auxiliaryResult.auxiliary,
3085
- warnings: auxiliaryResult.warnings,
3086
- metadataSources: auxiliaryResult.metadataSources,
3087
2969
  rawResponseId
3088
- }, factory);
3089
- yield factory.responseCompleted({
3090
- replay: finalResponse.replay,
3091
- stopReason: finalResponse.stopReason,
3092
- trace: finalResponse.backend,
3093
- usage: finalResponse.usage,
3094
- billing: finalResponse.billing,
3095
- auxiliary: finalResponse.auxiliary,
3096
- warnings: finalResponse.warnings
3097
2970
  });
3098
- };
3099
- try {
3100
- while (true) {
3101
- const { done, value } = await reader.read().catch((err) => {
3102
- throw new AIStreamError(`Failed to read response stream: ${err instanceof Error ? err.message : String(err)}`, "STREAM_ERROR");
3103
- });
3104
- const { items: chunks, malformed: malformedLines } = done ? parser.flush() : parser.feed(value);
3105
- const malformedWarning = emitMalformedStreamWarning(factory, {
3106
- count: malformedLines,
3107
- providerLabel: "Ollama",
3108
- transportLabel: "NDJSON line(s)"
3109
- });
3110
- if (malformedWarning) yield malformedWarning;
3111
- for (const chunk of chunks) {
3112
- responseId = chunk.created_at;
3113
- if (completedEmitted) {
3114
- if (chunk.done) yield factory.responseWarning("Duplicate finish signal ignored", "DUPLICATE_FINISH");
3115
- continue;
2971
+ }.bind(this);
2972
+ for await (const batch of iterateProviderStreamBatches({
2973
+ reader,
2974
+ parser,
2975
+ factory,
2976
+ providerLabel: "Ollama",
2977
+ transportLabel: "NDJSON line(s)",
2978
+ incompleteMessage: "Stream ended with an incomplete Ollama NDJSON line"
2979
+ })) {
2980
+ for (const warning of batch.warnings) yield warning;
2981
+ for (const chunk of batch.items) {
2982
+ responseId = chunk.created_at;
2983
+ if (gate.completed) {
2984
+ if (chunk.done) yield factory.responseWarning("Duplicate finish signal ignored", "DUPLICATE_FINISH");
2985
+ continue;
2986
+ }
2987
+ const msg = chunk.message;
2988
+ if (msg.content) {
2989
+ if (!hasMessageStarted) {
2990
+ currentMessageId = `msg-${chunk.created_at}`;
2991
+ hasMessageStarted = true;
2992
+ yield factory.messageStarted(currentMessageId);
3116
2993
  }
3117
- const msg = chunk.message;
3118
- if (msg.content) {
3119
- if (!hasMessageStarted) {
3120
- currentMessageId = `msg-${chunk.created_at}`;
3121
- hasMessageStarted = true;
3122
- yield factory.messageStarted(currentMessageId);
3123
- }
3124
- accumulatedContent += msg.content;
3125
- yield factory.messageDelta(currentMessageId, textBlock(msg.content));
2994
+ accumulatedContent += msg.content;
2995
+ yield factory.messageDelta(currentMessageId, textBlock(msg.content));
2996
+ }
2997
+ if (msg.tool_calls && msg.tool_calls.length > 0) for (const tc of msg.tool_calls) {
2998
+ const tcId = `ollama-tc-${request.requestId}-${toolCallIndex++}`;
2999
+ const argsText = JSON.stringify(tc.function.arguments);
3000
+ pendingToolCalls.push({
3001
+ id: tcId,
3002
+ name: tc.function.name,
3003
+ argumentsText: argsText
3004
+ });
3005
+ }
3006
+ if (chunk.done) {
3007
+ if (accumulatedContent === "" && pendingToolCalls.length > 0 && !hasMessageStarted) {
3008
+ currentMessageId = `msg-${chunk.created_at}`;
3009
+ hasMessageStarted = true;
3010
+ yield factory.messageStarted(currentMessageId);
3126
3011
  }
3127
- if (msg.tool_calls && msg.tool_calls.length > 0) for (const tc of msg.tool_calls) {
3128
- const tcId = `ollama-tc-${request.requestId}-${toolCallIndex++}`;
3129
- const argsText = JSON.stringify(tc.function.arguments);
3130
- pendingToolCalls.push({
3131
- id: tcId,
3132
- name: tc.function.name,
3133
- argumentsText: argsText
3134
- });
3012
+ if (hasMessageStarted) {
3013
+ const message = messageItem([textBlock(accumulatedContent)], { id: currentMessageId });
3014
+ yield factory.messageCompleted(currentMessageId);
3015
+ if (accumulatedContent) output.push(message);
3135
3016
  }
3136
- if (chunk.done) {
3137
- if (accumulatedContent === "" && pendingToolCalls.length > 0 && !hasMessageStarted) {
3138
- currentMessageId = `msg-${chunk.created_at}`;
3139
- hasMessageStarted = true;
3140
- yield factory.messageStarted(currentMessageId);
3141
- }
3142
- if (hasMessageStarted) {
3143
- const message = messageItem([textBlock(accumulatedContent)], { id: currentMessageId });
3144
- yield factory.messageCompleted(currentMessageId);
3145
- if (accumulatedContent) output.push(message);
3146
- }
3147
- if (pendingToolCalls.length > 0) yield factory.responseWarning(`Ollama delivered ${pendingToolCalls.length} tool call(s) as a batch; tool_call streaming is not supported`, WarningCode.TOOL_CALL_BATCHED);
3148
- for (const pending of pendingToolCalls) {
3149
- const toolCall = toolCallItem(pending.id, pending.name, pending.argumentsText);
3150
- yield factory.toolCallStarted(pending.id, pending.name);
3151
- yield factory.toolCallDelta(pending.id, { argumentsText: pending.argumentsText });
3152
- yield factory.toolCallCompleted(pending.id);
3153
- output.push(toolCall);
3154
- }
3155
- if (request.include?.usage !== "off" && (chunk.prompt_eval_count !== void 0 || chunk.eval_count !== void 0)) auxiliary.recordUsage(usageFromOllama({
3156
- prompt_eval_count: chunk.prompt_eval_count,
3157
- eval_count: chunk.eval_count
3158
- }), "final", {
3159
- prompt_eval_count: chunk.prompt_eval_count,
3160
- eval_count: chunk.eval_count
3161
- });
3162
- yield* emitCompleted(chunk.done_reason ? mapStopReason(chunk.done_reason) : void 0, chunk.created_at);
3163
- accumulatedContent = "";
3164
- currentMessageId = "";
3165
- hasMessageStarted = false;
3166
- pendingToolCalls = [];
3017
+ if (pendingToolCalls.length > 0) yield factory.responseWarning(`Ollama delivered ${pendingToolCalls.length} tool call(s) as a batch; tool_call streaming is not supported`, WarningCode.TOOL_CALL_BATCHED);
3018
+ for (const pending of pendingToolCalls) {
3019
+ const toolCall = toolCallItem(pending.id, pending.name, pending.argumentsText);
3020
+ yield factory.toolCallStarted(pending.id, pending.name);
3021
+ yield factory.toolCallDelta(pending.id, { argumentsText: pending.argumentsText });
3022
+ yield factory.toolCallCompleted(pending.id);
3023
+ output.push(toolCall);
3167
3024
  }
3025
+ if (request.include?.usage !== "off" && (chunk.prompt_eval_count !== void 0 || chunk.eval_count !== void 0)) auxiliary.recordUsage(usageFromOllama({
3026
+ prompt_eval_count: chunk.prompt_eval_count,
3027
+ eval_count: chunk.eval_count
3028
+ }), "final", {
3029
+ prompt_eval_count: chunk.prompt_eval_count,
3030
+ eval_count: chunk.eval_count
3031
+ });
3032
+ yield* emitCompleted(chunk.done_reason ? mapStopReason(chunk.done_reason) : void 0, chunk.created_at);
3033
+ accumulatedContent = "";
3034
+ currentMessageId = "";
3035
+ hasMessageStarted = false;
3036
+ pendingToolCalls = [];
3168
3037
  }
3169
- if (done) {
3170
- streamDone = true;
3171
- break;
3172
- }
3173
- }
3174
- } finally {
3175
- try {
3176
- if (!streamDone) await reader.cancel().catch(() => void 0);
3177
- } finally {
3178
- reader.releaseLock();
3179
3038
  }
3180
3039
  }
3181
- if (parser.getRemaining().trim().length > 0) yield factory.responseWarning("Stream ended with an incomplete Ollama NDJSON line", "STREAM_ERROR");
3182
- if (!completedEmitted && (hasMessageStarted || pendingToolCalls.length > 0)) {
3183
- yield factory.responseWarning("Stream ended without a done signal", "INCOMPLETE_STREAM");
3040
+ if (!gate.completed && (hasMessageStarted || pendingToolCalls.length > 0)) {
3041
+ yield factory.responseWarning("Stream ended without a done signal", WarningCode.STREAM_INCOMPLETE);
3184
3042
  if (hasMessageStarted) {
3185
3043
  const message = messageItem([textBlock(accumulatedContent)], { id: currentMessageId });
3186
3044
  yield factory.messageCompleted(currentMessageId);
@@ -3619,6 +3477,6 @@ function cloneItem(item) {
3619
3477
  return structuredClone(item);
3620
3478
  }
3621
3479
  //#endregion
3622
- export { AIError, AIMappingError, AIProviderError, AIRequestError, AIStreamError, AdapterAuxiliaryState, AdapterBase, AuxiliaryCollector, ChatCompletionsAdapter, IncrementalStreamParser, MAX_OPAQUE_JSON_DEPTH, MAX_OPAQUE_PAYLOAD_BYTES, MessagesAdapter, MockAdapter, NormalizedRequestMapper, OllamaAdapter, PROVIDER_ERROR_MESSAGE_MAX_LEN, PROVIDER_ERROR_RAW_BODY_THRESHOLD, ResponsesAdapter, WarningCode, aggregateEvents, assertMockRequest, assertOpaqueReplayEnvelope, assertValidRequest, blockToText, collectStream, contentBlocksToText, createAIClient, createEventFactory, emitMalformedStreamWarning, extractProviderErrorMessage, extractText, imageBlock, instructionsToText, jsonBlock, mapReasoningVisibility, mapStopReason, measureJsonDepth, messageItem, metadataSourceList, normalizeRequest, opaqueBlock, opaqueItem, parseSSEEvents, providerHttpError, reasoningItem, replayFromOutput, splitLines, splitSSEFrames, syntheticStream, textBlock, toolCallItem, toolResultItem, usageFromAnthropicMessages, usageFromChatCompletions, usageFromOllama, usageFromOpenAIResponses, validateOpaqueReplayEnvelope, validateRequest, withMockStreaming };
3480
+ export { AIError, AIMappingError, AIProviderError, AIRequestError, AIStreamError, AdapterAuxiliaryState, AdapterBase, AuxiliaryCollector, ChatCompletionsAdapter, IncrementalStreamParser, MAX_OPAQUE_JSON_DEPTH, MAX_OPAQUE_PAYLOAD_BYTES, MessagesAdapter, MockAdapter, NormalizedRequestMapper, OllamaAdapter, PROVIDER_ERROR_MESSAGE_MAX_LEN, PROVIDER_ERROR_RAW_BODY_THRESHOLD, ResponsesAdapter, WarningCode, aggregateEvents, assertMockRequest, assertOpaqueReplayEnvelope, assertValidRequest, blockToText, collectStream, contentBlocksToText, createAIClient, createChatCompletionsSseParser, createCompletionGate, createEventFactory, createNdjsonLineParser, createSseJsonParser, emitMalformedStreamWarning, extractProviderErrorMessage, extractText, imageBlock, iterateProviderStreamBatches, jsonBlock, mapReasoningVisibility, mapStopReason, measureJsonDepth, messageItem, normalizeRequest, opaqueBlock, opaqueItem, openProviderJsonStream, parseChatCompletionsDataLine, parseSseJsonFrame, providerHttpError, reasoningItem, replayFromOutput, splitLines, splitSSEFrames, syntheticStream, textBlock, toolCallItem, toolResultItem, usageFromAnthropicMessages, usageFromChatCompletions, usageFromOllama, usageFromOpenAIResponses, validateOpaqueReplayEnvelope, validateRequest, withMockStreaming };
3623
3481
 
3624
3482
  //# sourceMappingURL=index.mjs.map