@corbat-tech/coco 2.23.1 → 2.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -1425,6 +1425,150 @@ var init_anthropic = __esm({
1425
1425
  };
1426
1426
  }
1427
1427
  });
1428
+ function getSingleBuilderKey(builders) {
1429
+ return builders.size === 1 ? Array.from(builders.keys())[0] ?? null : null;
1430
+ }
1431
+ function parseToolCallArguments(args, providerName) {
1432
+ try {
1433
+ return args ? JSON.parse(args) : {};
1434
+ } catch {
1435
+ try {
1436
+ if (args) {
1437
+ const repaired = jsonrepair(args);
1438
+ return JSON.parse(repaired);
1439
+ }
1440
+ } catch {
1441
+ console.error(`[${providerName}] Cannot parse tool arguments: ${args.slice(0, 200)}`);
1442
+ }
1443
+ return {};
1444
+ }
1445
+ }
1446
+ var ChatToolCallAssembler, ResponsesToolCallAssembler;
1447
+ var init_tool_call_normalizer = __esm({
1448
+ "src/providers/tool-call-normalizer.ts"() {
1449
+ ChatToolCallAssembler = class {
1450
+ builders = /* @__PURE__ */ new Map();
1451
+ lastBuilderKey = null;
1452
+ consume(delta) {
1453
+ const key = typeof delta.index === "number" ? `index:${delta.index}` : typeof delta.id === "string" && delta.id.length > 0 ? `id:${delta.id}` : getSingleBuilderKey(this.builders) ?? this.lastBuilderKey ?? `fallback:${this.builders.size}`;
1454
+ let started;
1455
+ if (!this.builders.has(key)) {
1456
+ const initialId = delta.id ?? "";
1457
+ const initialName = delta.function?.name ?? "";
1458
+ this.builders.set(key, { id: initialId, name: initialName, arguments: "" });
1459
+ started = {
1460
+ id: initialId || void 0,
1461
+ name: initialName || void 0
1462
+ };
1463
+ }
1464
+ const builder = this.builders.get(key);
1465
+ this.lastBuilderKey = key;
1466
+ if (delta.id) {
1467
+ builder.id = delta.id;
1468
+ }
1469
+ if (delta.function?.name) {
1470
+ builder.name = delta.function.name;
1471
+ }
1472
+ const text13 = delta.function?.arguments ?? "";
1473
+ if (!text13) return { started };
1474
+ builder.arguments += text13;
1475
+ return {
1476
+ started,
1477
+ argumentDelta: {
1478
+ id: builder.id,
1479
+ name: builder.name,
1480
+ text: text13
1481
+ }
1482
+ };
1483
+ }
1484
+ finalizeAll(providerName) {
1485
+ const result = [];
1486
+ for (const builder of this.builders.values()) {
1487
+ result.push({
1488
+ id: builder.id,
1489
+ name: builder.name,
1490
+ input: parseToolCallArguments(builder.arguments, providerName)
1491
+ });
1492
+ }
1493
+ this.builders.clear();
1494
+ this.lastBuilderKey = null;
1495
+ return result;
1496
+ }
1497
+ };
1498
+ ResponsesToolCallAssembler = class {
1499
+ builders = /* @__PURE__ */ new Map();
1500
+ outputIndexToBuilderKey = /* @__PURE__ */ new Map();
1501
+ onOutputItemAdded(event) {
1502
+ const item = event.item;
1503
+ if (!item || item.type !== "function_call") return null;
1504
+ const callId = item.call_id ?? "";
1505
+ const itemKey = item.id ?? callId;
1506
+ this.builders.set(itemKey, {
1507
+ callId,
1508
+ name: item.name ?? "",
1509
+ arguments: item.arguments ?? ""
1510
+ });
1511
+ if (typeof event.output_index === "number") {
1512
+ this.outputIndexToBuilderKey.set(event.output_index, itemKey);
1513
+ }
1514
+ return {
1515
+ id: callId,
1516
+ name: item.name ?? ""
1517
+ };
1518
+ }
1519
+ onArgumentsDelta(event) {
1520
+ const builderKey = this.resolveBuilderKey(event.item_id, event.output_index);
1521
+ if (!builderKey) return;
1522
+ const builder = this.builders.get(builderKey);
1523
+ if (!builder) return;
1524
+ builder.arguments += event.delta ?? "";
1525
+ }
1526
+ onArgumentsDone(event, providerName) {
1527
+ const builderKey = this.resolveBuilderKey(event.item_id, event.output_index);
1528
+ if (!builderKey) return null;
1529
+ const builder = this.builders.get(builderKey);
1530
+ if (!builder) return null;
1531
+ const toolCall = {
1532
+ id: builder.callId,
1533
+ name: builder.name,
1534
+ input: parseToolCallArguments(event.arguments ?? builder.arguments, providerName)
1535
+ };
1536
+ this.deleteBuilder(builderKey);
1537
+ return toolCall;
1538
+ }
1539
+ finalizeAll(providerName) {
1540
+ const calls = [];
1541
+ for (const builder of this.builders.values()) {
1542
+ calls.push({
1543
+ id: builder.callId,
1544
+ name: builder.name,
1545
+ input: parseToolCallArguments(builder.arguments, providerName)
1546
+ });
1547
+ }
1548
+ this.builders.clear();
1549
+ this.outputIndexToBuilderKey.clear();
1550
+ return calls;
1551
+ }
1552
+ resolveBuilderKey(itemId, outputIndex) {
1553
+ if (itemId && this.builders.has(itemId)) {
1554
+ return itemId;
1555
+ }
1556
+ if (typeof outputIndex === "number") {
1557
+ return this.outputIndexToBuilderKey.get(outputIndex) ?? null;
1558
+ }
1559
+ return getSingleBuilderKey(this.builders);
1560
+ }
1561
+ deleteBuilder(builderKey) {
1562
+ this.builders.delete(builderKey);
1563
+ for (const [idx, key] of this.outputIndexToBuilderKey.entries()) {
1564
+ if (key === builderKey) {
1565
+ this.outputIndexToBuilderKey.delete(idx);
1566
+ }
1567
+ }
1568
+ }
1569
+ };
1570
+ }
1571
+ });
1428
1572
  function needsResponsesApi(model) {
1429
1573
  return model.includes("codex") || model.startsWith("gpt-5") || model.startsWith("o4-") || model === "o3";
1430
1574
  }
@@ -1464,6 +1608,7 @@ var init_openai = __esm({
1464
1608
  "src/providers/openai.ts"() {
1465
1609
  init_errors();
1466
1610
  init_retry();
1611
+ init_tool_call_normalizer();
1467
1612
  DEFAULT_MODEL2 = "gpt-5.4-codex";
1468
1613
  CONTEXT_WINDOWS2 = {
1469
1614
  // OpenAI models
@@ -1791,8 +1936,7 @@ var init_openai = __esm({
1791
1936
  const stream = await this.client.chat.completions.create(
1792
1937
  requestParams
1793
1938
  );
1794
- const toolCallBuilders = /* @__PURE__ */ new Map();
1795
- let lastToolCallKey = null;
1939
+ const toolCallAssembler = new ChatToolCallAssembler();
1796
1940
  const streamTimeout = this.config.timeout ?? 12e4;
1797
1941
  let lastActivityTime = Date.now();
1798
1942
  const timeoutController = new AbortController();
@@ -1806,30 +1950,6 @@ var init_openai = __esm({
1806
1950
  timeoutController.signal.addEventListener("abort", () => stream.controller.abort(), {
1807
1951
  once: true
1808
1952
  });
1809
- const providerName = this.name;
1810
- const parseArguments2 = (builder) => {
1811
- let input = {};
1812
- try {
1813
- input = builder.arguments ? JSON.parse(builder.arguments) : {};
1814
- } catch (error) {
1815
- console.warn(
1816
- `[${providerName}] Failed to parse tool call arguments for ${builder.name}: ${builder.arguments?.slice(0, 300)}`
1817
- );
1818
- try {
1819
- if (builder.arguments) {
1820
- const repaired = jsonrepair(builder.arguments);
1821
- input = JSON.parse(repaired);
1822
- console.log(`[${providerName}] \u2713 Successfully repaired JSON for ${builder.name}`);
1823
- }
1824
- } catch {
1825
- console.error(
1826
- `[${providerName}] Cannot repair JSON for ${builder.name}, using empty object`
1827
- );
1828
- console.error(`[${providerName}] Original error:`, error);
1829
- }
1830
- }
1831
- return input;
1832
- };
1833
1953
  try {
1834
1954
  let streamStopReason;
1835
1955
  for await (const chunk of stream) {
@@ -1842,38 +1962,31 @@ var init_openai = __esm({
1842
1962
  }
1843
1963
  if (delta?.tool_calls) {
1844
1964
  for (const toolCallDelta of delta.tool_calls) {
1845
- const key = typeof toolCallDelta.index === "number" ? `index:${toolCallDelta.index}` : typeof toolCallDelta.id === "string" && toolCallDelta.id.length > 0 ? `id:${toolCallDelta.id}` : toolCallBuilders.size === 1 ? Array.from(toolCallBuilders.keys())[0] ?? `fallback:${toolCallBuilders.size}` : lastToolCallKey ?? `fallback:${toolCallBuilders.size}`;
1846
- if (!toolCallBuilders.has(key)) {
1847
- toolCallBuilders.set(key, {
1848
- id: toolCallDelta.id ?? "",
1849
- name: toolCallDelta.function?.name ?? "",
1850
- arguments: ""
1851
- });
1965
+ const consumed = toolCallAssembler.consume({
1966
+ index: toolCallDelta.index,
1967
+ id: toolCallDelta.id ?? void 0,
1968
+ function: {
1969
+ name: toolCallDelta.function?.name ?? void 0,
1970
+ arguments: toolCallDelta.function?.arguments ?? void 0
1971
+ }
1972
+ });
1973
+ if (consumed.started) {
1852
1974
  yield {
1853
1975
  type: "tool_use_start",
1854
1976
  toolCall: {
1855
- id: toolCallDelta.id,
1856
- name: toolCallDelta.function?.name
1977
+ id: consumed.started.id,
1978
+ name: consumed.started.name
1857
1979
  }
1858
1980
  };
1859
1981
  }
1860
- const builder = toolCallBuilders.get(key);
1861
- lastToolCallKey = key;
1862
- if (toolCallDelta.id) {
1863
- builder.id = toolCallDelta.id;
1864
- }
1865
- if (toolCallDelta.function?.name) {
1866
- builder.name = toolCallDelta.function.name;
1867
- }
1868
- if (toolCallDelta.function?.arguments) {
1869
- builder.arguments += toolCallDelta.function.arguments;
1982
+ if (consumed.argumentDelta) {
1870
1983
  yield {
1871
1984
  type: "tool_use_delta",
1872
1985
  toolCall: {
1873
- id: builder.id,
1874
- name: builder.name
1986
+ id: consumed.argumentDelta.id,
1987
+ name: consumed.argumentDelta.name
1875
1988
  },
1876
- text: toolCallDelta.function.arguments
1989
+ text: consumed.argumentDelta.text
1877
1990
  };
1878
1991
  }
1879
1992
  }
@@ -1882,27 +1995,26 @@ var init_openai = __esm({
1882
1995
  if (finishReason) {
1883
1996
  streamStopReason = this.mapFinishReason(finishReason);
1884
1997
  }
1885
- if (finishReason && toolCallBuilders.size > 0) {
1886
- for (const [, builder] of toolCallBuilders) {
1998
+ if (finishReason) {
1999
+ for (const toolCall of toolCallAssembler.finalizeAll(this.name)) {
1887
2000
  yield {
1888
2001
  type: "tool_use_end",
1889
2002
  toolCall: {
1890
- id: builder.id,
1891
- name: builder.name,
1892
- input: parseArguments2(builder)
2003
+ id: toolCall.id,
2004
+ name: toolCall.name,
2005
+ input: toolCall.input
1893
2006
  }
1894
2007
  };
1895
2008
  }
1896
- toolCallBuilders.clear();
1897
2009
  }
1898
2010
  }
1899
- for (const [, builder] of toolCallBuilders) {
2011
+ for (const toolCall of toolCallAssembler.finalizeAll(this.name)) {
1900
2012
  yield {
1901
2013
  type: "tool_use_end",
1902
2014
  toolCall: {
1903
- id: builder.id,
1904
- name: builder.name,
1905
- input: parseArguments2(builder)
2015
+ id: toolCall.id,
2016
+ name: toolCall.name,
2017
+ input: toolCall.input
1906
2018
  }
1907
2019
  };
1908
2020
  }
@@ -2339,7 +2451,7 @@ var init_openai = __esm({
2339
2451
  toolCalls.push({
2340
2452
  id: item.call_id,
2341
2453
  name: item.name,
2342
- input: this.parseResponsesArguments(item.arguments)
2454
+ input: parseToolCallArguments(item.arguments, this.name)
2343
2455
  });
2344
2456
  }
2345
2457
  }
@@ -2445,8 +2557,7 @@ var init_openai = __esm({
2445
2557
  const stream = await this.client.responses.create(
2446
2558
  requestParams
2447
2559
  );
2448
- const fnCallBuilders = /* @__PURE__ */ new Map();
2449
- const outputIndexToBuilderKey = /* @__PURE__ */ new Map();
2560
+ const toolCallAssembler = new ResponsesToolCallAssembler();
2450
2561
  const streamTimeout = this.config.timeout ?? 12e4;
2451
2562
  let lastActivityTime = Date.now();
2452
2563
  const timeoutController = new AbortController();
@@ -2470,67 +2581,66 @@ var init_openai = __esm({
2470
2581
  yield { type: "text", text: event.delta };
2471
2582
  break;
2472
2583
  case "response.output_item.added":
2473
- if (event.item.type === "function_call") {
2474
- const fc = event.item;
2475
- const itemKey = fc.id ?? fc.call_id;
2476
- fnCallBuilders.set(itemKey, {
2477
- callId: fc.call_id,
2478
- name: fc.name,
2479
- arguments: fc.arguments ?? ""
2584
+ {
2585
+ const item = event.item;
2586
+ const start = toolCallAssembler.onOutputItemAdded({
2587
+ output_index: event.output_index,
2588
+ item: {
2589
+ type: item.type,
2590
+ id: item.id,
2591
+ call_id: item.call_id,
2592
+ name: item.name,
2593
+ arguments: item.arguments
2594
+ }
2480
2595
  });
2481
- if (typeof event.output_index === "number") {
2482
- outputIndexToBuilderKey.set(event.output_index, itemKey);
2483
- }
2596
+ if (!start) break;
2484
2597
  yield {
2485
2598
  type: "tool_use_start",
2486
- toolCall: { id: fc.call_id, name: fc.name }
2599
+ toolCall: { id: start.id, name: start.name }
2487
2600
  };
2488
2601
  }
2489
2602
  break;
2490
2603
  case "response.function_call_arguments.delta":
2491
- {
2492
- const builderKey = (event.item_id && fnCallBuilders.has(event.item_id) ? event.item_id : null) ?? (typeof event.output_index === "number" ? outputIndexToBuilderKey.get(event.output_index) ?? null : null) ?? (fnCallBuilders.size === 1 ? Array.from(fnCallBuilders.keys())[0] : null);
2493
- if (!builderKey) break;
2494
- const builder = fnCallBuilders.get(builderKey);
2495
- if (builder) {
2496
- builder.arguments += event.delta;
2497
- }
2498
- }
2604
+ toolCallAssembler.onArgumentsDelta({
2605
+ item_id: event.item_id,
2606
+ output_index: event.output_index,
2607
+ delta: event.delta
2608
+ });
2499
2609
  break;
2500
2610
  case "response.function_call_arguments.done":
2501
2611
  {
2502
- const builderKey = (event.item_id && fnCallBuilders.has(event.item_id) ? event.item_id : null) ?? (typeof event.output_index === "number" ? outputIndexToBuilderKey.get(event.output_index) ?? null : null) ?? (fnCallBuilders.size === 1 ? Array.from(fnCallBuilders.keys())[0] : null);
2503
- if (!builderKey) break;
2504
- const builder = fnCallBuilders.get(builderKey);
2505
- if (builder) {
2612
+ const toolCall = toolCallAssembler.onArgumentsDone(
2613
+ {
2614
+ item_id: event.item_id,
2615
+ output_index: event.output_index,
2616
+ arguments: event.arguments
2617
+ },
2618
+ this.name
2619
+ );
2620
+ if (toolCall) {
2506
2621
  yield {
2507
2622
  type: "tool_use_end",
2508
2623
  toolCall: {
2509
- id: builder.callId,
2510
- name: builder.name,
2511
- input: this.parseResponsesArguments(event.arguments ?? builder.arguments)
2624
+ id: toolCall.id,
2625
+ name: toolCall.name,
2626
+ input: toolCall.input
2512
2627
  }
2513
2628
  };
2514
- fnCallBuilders.delete(builderKey);
2515
- for (const [idx, key] of outputIndexToBuilderKey.entries()) {
2516
- if (key === builderKey) outputIndexToBuilderKey.delete(idx);
2517
- }
2518
2629
  }
2519
2630
  }
2520
2631
  break;
2521
2632
  case "response.completed":
2522
2633
  {
2523
- for (const [, builder] of fnCallBuilders) {
2634
+ for (const toolCall of toolCallAssembler.finalizeAll(this.name)) {
2524
2635
  yield {
2525
2636
  type: "tool_use_end",
2526
2637
  toolCall: {
2527
- id: builder.callId,
2528
- name: builder.name,
2529
- input: this.parseResponsesArguments(builder.arguments)
2638
+ id: toolCall.id,
2639
+ name: toolCall.name,
2640
+ input: toolCall.input
2530
2641
  }
2531
2642
  };
2532
2643
  }
2533
- fnCallBuilders.clear();
2534
2644
  const hasToolCalls = event.response.output.some(
2535
2645
  (i) => i.type === "function_call"
2536
2646
  );
@@ -2650,24 +2760,6 @@ var init_openai = __esm({
2650
2760
  strict: false
2651
2761
  }));
2652
2762
  }
2653
- /**
2654
- * Parse tool call arguments with jsonrepair fallback (Responses API)
2655
- */
2656
- parseResponsesArguments(args) {
2657
- try {
2658
- return args ? JSON.parse(args) : {};
2659
- } catch {
2660
- try {
2661
- if (args) {
2662
- const repaired = jsonrepair(args);
2663
- return JSON.parse(repaired);
2664
- }
2665
- } catch {
2666
- console.error(`[${this.name}] Cannot parse tool arguments: ${args.slice(0, 200)}`);
2667
- }
2668
- return {};
2669
- }
2670
- }
2671
2763
  };
2672
2764
  }
2673
2765
  });
@@ -4296,6 +4388,8 @@ var init_auth = __esm({
4296
4388
  init_gcloud();
4297
4389
  }
4298
4390
  });
4391
+
4392
+ // src/providers/codex.ts
4299
4393
  function parseJwtClaims(token) {
4300
4394
  const parts = token.split(".");
4301
4395
  if (parts.length !== 3 || !parts[1]) return void 0;
@@ -4311,21 +4405,6 @@ function extractAccountId(accessToken) {
4311
4405
  const auth = claims["https://api.openai.com/auth"];
4312
4406
  return claims["chatgpt_account_id"] || auth?.["chatgpt_account_id"] || claims["organizations"]?.[0]?.id;
4313
4407
  }
4314
- function parseArguments(args) {
4315
- try {
4316
- return args ? JSON.parse(args) : {};
4317
- } catch {
4318
- try {
4319
- if (args) {
4320
- const repaired = jsonrepair(args);
4321
- return JSON.parse(repaired);
4322
- }
4323
- } catch {
4324
- console.error(`[Codex] Cannot parse tool arguments: ${args.slice(0, 200)}`);
4325
- }
4326
- return {};
4327
- }
4328
- }
4329
4408
  function createCodexProvider(config) {
4330
4409
  const provider = new CodexProvider();
4331
4410
  if (config) {
@@ -4340,6 +4419,7 @@ var init_codex = __esm({
4340
4419
  init_errors();
4341
4420
  init_auth();
4342
4421
  init_retry();
4422
+ init_tool_call_normalizer();
4343
4423
  CODEX_API_ENDPOINT = "https://chatgpt.com/backend-api/codex/responses";
4344
4424
  DEFAULT_MODEL3 = "gpt-5.4-codex";
4345
4425
  CONTEXT_WINDOWS3 = {
@@ -4652,8 +4732,7 @@ var init_codex = __esm({
4652
4732
  let inputTokens = 0;
4653
4733
  let outputTokens = 0;
4654
4734
  const toolCalls = [];
4655
- const fnCallBuilders = /* @__PURE__ */ new Map();
4656
- const outputIndexToBuilderKey = /* @__PURE__ */ new Map();
4735
+ const toolCallAssembler = new ResponsesToolCallAssembler();
4657
4736
  await this.readSSEStream(response, (event) => {
4658
4737
  if (event.id) responseId = event.id;
4659
4738
  switch (event.type) {
@@ -4664,41 +4743,35 @@ var init_codex = __esm({
4664
4743
  content = event.text ?? content;
4665
4744
  break;
4666
4745
  case "response.output_item.added": {
4667
- const item = event.item;
4668
- if (item.type === "function_call") {
4669
- const itemKey = item.id ?? item.call_id;
4670
- fnCallBuilders.set(itemKey, {
4671
- callId: item.call_id,
4672
- name: item.name,
4673
- arguments: item.arguments ?? ""
4674
- });
4675
- if (typeof event.output_index === "number") {
4676
- outputIndexToBuilderKey.set(event.output_index, itemKey);
4677
- }
4678
- }
4746
+ toolCallAssembler.onOutputItemAdded({
4747
+ output_index: event.output_index,
4748
+ item: event.item
4749
+ });
4679
4750
  break;
4680
4751
  }
4681
4752
  case "response.function_call_arguments.delta": {
4682
- const builderKey = (event.item_id && fnCallBuilders.has(event.item_id) ? event.item_id : null) ?? (typeof event.output_index === "number" ? outputIndexToBuilderKey.get(event.output_index) ?? null : null) ?? (fnCallBuilders.size === 1 ? Array.from(fnCallBuilders.keys())[0] : null);
4683
- if (!builderKey) break;
4684
- const builder = fnCallBuilders.get(builderKey);
4685
- if (builder) builder.arguments += event.delta ?? "";
4753
+ toolCallAssembler.onArgumentsDelta({
4754
+ item_id: event.item_id,
4755
+ output_index: event.output_index,
4756
+ delta: event.delta
4757
+ });
4686
4758
  break;
4687
4759
  }
4688
4760
  case "response.function_call_arguments.done": {
4689
- const builderKey = (event.item_id && fnCallBuilders.has(event.item_id) ? event.item_id : null) ?? (typeof event.output_index === "number" ? outputIndexToBuilderKey.get(event.output_index) ?? null : null) ?? (fnCallBuilders.size === 1 ? Array.from(fnCallBuilders.keys())[0] : null);
4690
- if (!builderKey) break;
4691
- const builder = fnCallBuilders.get(builderKey);
4692
- if (builder) {
4761
+ const toolCall = toolCallAssembler.onArgumentsDone(
4762
+ {
4763
+ item_id: event.item_id,
4764
+ output_index: event.output_index,
4765
+ arguments: event.arguments
4766
+ },
4767
+ this.name
4768
+ );
4769
+ if (toolCall) {
4693
4770
  toolCalls.push({
4694
- id: builder.callId,
4695
- name: builder.name,
4696
- input: parseArguments(event.arguments ?? builder.arguments)
4771
+ id: toolCall.id,
4772
+ name: toolCall.name,
4773
+ input: toolCall.input
4697
4774
  });
4698
- fnCallBuilders.delete(builderKey);
4699
- for (const [idx, key] of outputIndexToBuilderKey.entries()) {
4700
- if (key === builderKey) outputIndexToBuilderKey.delete(idx);
4701
- }
4702
4775
  }
4703
4776
  break;
4704
4777
  }
@@ -4709,14 +4782,13 @@ var init_codex = __esm({
4709
4782
  inputTokens = usage.input_tokens ?? 0;
4710
4783
  outputTokens = usage.output_tokens ?? 0;
4711
4784
  }
4712
- for (const [, builder] of fnCallBuilders) {
4785
+ for (const toolCall of toolCallAssembler.finalizeAll(this.name)) {
4713
4786
  toolCalls.push({
4714
- id: builder.callId,
4715
- name: builder.name,
4716
- input: parseArguments(builder.arguments)
4787
+ id: toolCall.id,
4788
+ name: toolCall.name,
4789
+ input: toolCall.input
4717
4790
  });
4718
4791
  }
4719
- fnCallBuilders.clear();
4720
4792
  break;
4721
4793
  }
4722
4794
  }
@@ -4810,8 +4882,7 @@ var init_codex = __esm({
4810
4882
  const reader = response.body.getReader();
4811
4883
  const decoder = new TextDecoder();
4812
4884
  let buffer = "";
4813
- const fnCallBuilders = /* @__PURE__ */ new Map();
4814
- const outputIndexToBuilderKey = /* @__PURE__ */ new Map();
4885
+ const toolCallAssembler = new ResponsesToolCallAssembler();
4815
4886
  let lastActivityTime = Date.now();
4816
4887
  const timeoutController = new AbortController();
4817
4888
  const timeoutInterval = setInterval(() => {
@@ -4844,65 +4915,58 @@ var init_codex = __esm({
4844
4915
  yield { type: "text", text: event.delta ?? "" };
4845
4916
  break;
4846
4917
  case "response.output_item.added": {
4847
- const item = event.item;
4848
- if (item.type === "function_call") {
4849
- const itemKey = item.id ?? item.call_id;
4850
- fnCallBuilders.set(itemKey, {
4851
- callId: item.call_id,
4852
- name: item.name,
4853
- arguments: item.arguments ?? ""
4854
- });
4855
- if (typeof event.output_index === "number") {
4856
- outputIndexToBuilderKey.set(event.output_index, itemKey);
4857
- }
4918
+ const start = toolCallAssembler.onOutputItemAdded({
4919
+ output_index: event.output_index,
4920
+ item: event.item
4921
+ });
4922
+ if (start) {
4858
4923
  yield {
4859
4924
  type: "tool_use_start",
4860
- toolCall: { id: item.call_id, name: item.name }
4925
+ toolCall: { id: start.id, name: start.name }
4861
4926
  };
4862
4927
  }
4863
4928
  break;
4864
4929
  }
4865
4930
  case "response.function_call_arguments.delta": {
4866
- const builderKey = (event.item_id && fnCallBuilders.has(event.item_id) ? event.item_id : null) ?? (typeof event.output_index === "number" ? outputIndexToBuilderKey.get(event.output_index) ?? null : null) ?? (fnCallBuilders.size === 1 ? Array.from(fnCallBuilders.keys())[0] : null);
4867
- if (!builderKey) break;
4868
- const builder = fnCallBuilders.get(builderKey);
4869
- if (builder) {
4870
- builder.arguments += event.delta ?? "";
4871
- }
4931
+ toolCallAssembler.onArgumentsDelta({
4932
+ item_id: event.item_id,
4933
+ output_index: event.output_index,
4934
+ delta: event.delta
4935
+ });
4872
4936
  break;
4873
4937
  }
4874
4938
  case "response.function_call_arguments.done": {
4875
- const builderKey = (event.item_id && fnCallBuilders.has(event.item_id) ? event.item_id : null) ?? (typeof event.output_index === "number" ? outputIndexToBuilderKey.get(event.output_index) ?? null : null) ?? (fnCallBuilders.size === 1 ? Array.from(fnCallBuilders.keys())[0] : null);
4876
- if (!builderKey) break;
4877
- const builder = fnCallBuilders.get(builderKey);
4878
- if (builder) {
4939
+ const toolCall = toolCallAssembler.onArgumentsDone(
4940
+ {
4941
+ item_id: event.item_id,
4942
+ output_index: event.output_index,
4943
+ arguments: event.arguments
4944
+ },
4945
+ this.name
4946
+ );
4947
+ if (toolCall) {
4879
4948
  yield {
4880
4949
  type: "tool_use_end",
4881
4950
  toolCall: {
4882
- id: builder.callId,
4883
- name: builder.name,
4884
- input: parseArguments(event.arguments ?? builder.arguments)
4951
+ id: toolCall.id,
4952
+ name: toolCall.name,
4953
+ input: toolCall.input
4885
4954
  }
4886
4955
  };
4887
- fnCallBuilders.delete(builderKey);
4888
- for (const [idx, key] of outputIndexToBuilderKey.entries()) {
4889
- if (key === builderKey) outputIndexToBuilderKey.delete(idx);
4890
- }
4891
4956
  }
4892
4957
  break;
4893
4958
  }
4894
4959
  case "response.completed": {
4895
- for (const [, builder] of fnCallBuilders) {
4960
+ for (const toolCall of toolCallAssembler.finalizeAll(this.name)) {
4896
4961
  yield {
4897
4962
  type: "tool_use_end",
4898
4963
  toolCall: {
4899
- id: builder.callId,
4900
- name: builder.name,
4901
- input: parseArguments(builder.arguments)
4964
+ id: toolCall.id,
4965
+ name: toolCall.name,
4966
+ input: toolCall.input
4902
4967
  }
4903
4968
  };
4904
4969
  }
4905
- fnCallBuilders.clear();
4906
4970
  const resp = event.response;
4907
4971
  const output = resp?.output ?? [];
4908
4972
  const hasToolCalls = output.some((i) => i.type === "function_call");
@@ -6123,6 +6187,154 @@ var init_fallback = __esm({
6123
6187
  }
6124
6188
  });
6125
6189
 
6190
+ // src/providers/resilient.ts
6191
+ function sleep2(ms) {
6192
+ return new Promise((resolve4) => setTimeout(resolve4, ms));
6193
+ }
6194
+ function computeRetryDelay(attempt, config) {
6195
+ const exp = config.initialDelayMs * Math.pow(config.backoffMultiplier, attempt);
6196
+ const capped = Math.min(exp, config.maxDelayMs);
6197
+ const jitter = capped * config.jitterFactor * (Math.random() * 2 - 1);
6198
+ return Math.max(0, Math.min(capped + jitter, config.maxDelayMs));
6199
+ }
6200
+ function getDefaultResilienceConfig(providerId) {
6201
+ if (providerId === "ollama" || providerId === "lmstudio") {
6202
+ return {
6203
+ retry: {
6204
+ maxRetries: 1,
6205
+ initialDelayMs: 300,
6206
+ maxDelayMs: 1500
6207
+ },
6208
+ streamRetry: {
6209
+ maxRetries: 0
6210
+ },
6211
+ circuitBreaker: {
6212
+ failureThreshold: 3,
6213
+ resetTimeout: 1e4
6214
+ }
6215
+ };
6216
+ }
6217
+ return {
6218
+ retry: {
6219
+ maxRetries: 3,
6220
+ initialDelayMs: 1e3,
6221
+ maxDelayMs: 3e4
6222
+ },
6223
+ streamRetry: {
6224
+ maxRetries: 1,
6225
+ initialDelayMs: 500,
6226
+ maxDelayMs: 5e3
6227
+ },
6228
+ circuitBreaker: {
6229
+ failureThreshold: 5,
6230
+ resetTimeout: 3e4
6231
+ }
6232
+ };
6233
+ }
6234
+ function createResilientProvider(provider, config) {
6235
+ return new ResilientProvider(provider, config ?? getDefaultResilienceConfig(provider.id));
6236
+ }
6237
+ var DEFAULT_STREAM_RETRY, ResilientProvider;
6238
+ var init_resilient = __esm({
6239
+ "src/providers/resilient.ts"() {
6240
+ init_retry();
6241
+ init_circuit_breaker();
6242
+ DEFAULT_STREAM_RETRY = {
6243
+ maxRetries: 1,
6244
+ initialDelayMs: 500,
6245
+ maxDelayMs: 5e3,
6246
+ backoffMultiplier: 2,
6247
+ jitterFactor: 0.1
6248
+ };
6249
+ ResilientProvider = class {
6250
+ id;
6251
+ name;
6252
+ provider;
6253
+ breaker;
6254
+ retryConfig;
6255
+ streamRetryConfig;
6256
+ constructor(provider, config = {}) {
6257
+ this.provider = provider;
6258
+ this.id = provider.id;
6259
+ this.name = provider.name;
6260
+ this.retryConfig = { ...DEFAULT_RETRY_CONFIG, ...config.retry };
6261
+ this.streamRetryConfig = { ...DEFAULT_STREAM_RETRY, ...config.streamRetry };
6262
+ this.breaker = new CircuitBreaker(
6263
+ { ...DEFAULT_CIRCUIT_BREAKER_CONFIG, ...config.circuitBreaker },
6264
+ provider.id
6265
+ );
6266
+ }
6267
+ async initialize(config) {
6268
+ await this.provider.initialize(config);
6269
+ }
6270
+ async chat(messages, options) {
6271
+ return this.breaker.execute(
6272
+ () => withRetry(() => this.provider.chat(messages, options), this.retryConfig)
6273
+ );
6274
+ }
6275
+ async chatWithTools(messages, options) {
6276
+ return this.breaker.execute(
6277
+ () => withRetry(() => this.provider.chatWithTools(messages, options), this.retryConfig)
6278
+ );
6279
+ }
6280
+ async *stream(messages, options) {
6281
+ yield* this.streamWithPolicy(() => this.provider.stream(messages, options));
6282
+ }
6283
+ async *streamWithTools(messages, options) {
6284
+ yield* this.streamWithPolicy(() => this.provider.streamWithTools(messages, options));
6285
+ }
6286
+ countTokens(text13) {
6287
+ return this.provider.countTokens(text13);
6288
+ }
6289
+ getContextWindow() {
6290
+ return this.provider.getContextWindow();
6291
+ }
6292
+ async isAvailable() {
6293
+ try {
6294
+ return await this.breaker.execute(() => this.provider.isAvailable());
6295
+ } catch (error) {
6296
+ if (error instanceof CircuitOpenError) {
6297
+ return false;
6298
+ }
6299
+ return false;
6300
+ }
6301
+ }
6302
+ getCircuitState() {
6303
+ return this.breaker.getState();
6304
+ }
6305
+ resetCircuit() {
6306
+ this.breaker.reset();
6307
+ }
6308
+ async *streamWithPolicy(createStream) {
6309
+ let attempt = 0;
6310
+ while (attempt <= this.streamRetryConfig.maxRetries) {
6311
+ if (this.breaker.isOpen()) {
6312
+ throw new CircuitOpenError(this.id, 0);
6313
+ }
6314
+ let emittedChunk = false;
6315
+ try {
6316
+ for await (const chunk of createStream()) {
6317
+ emittedChunk = true;
6318
+ yield chunk;
6319
+ }
6320
+ this.breaker.recordSuccess();
6321
+ return;
6322
+ } catch (error) {
6323
+ this.breaker.recordFailure();
6324
+ const shouldRetry = !emittedChunk && attempt < this.streamRetryConfig.maxRetries && isRetryableError(error);
6325
+ if (!shouldRetry) {
6326
+ throw error;
6327
+ }
6328
+ const delay = computeRetryDelay(attempt, this.streamRetryConfig);
6329
+ await sleep2(delay);
6330
+ attempt++;
6331
+ }
6332
+ }
6333
+ }
6334
+ };
6335
+ }
6336
+ });
6337
+
6126
6338
  // src/config/env.ts
6127
6339
  var env_exports = {};
6128
6340
  __export(env_exports, {
@@ -6526,6 +6738,7 @@ __export(providers_exports, {
6526
6738
  MODEL_PRICING: () => MODEL_PRICING,
6527
6739
  OpenAIProvider: () => OpenAIProvider,
6528
6740
  ProviderFallback: () => ProviderFallback,
6741
+ ResilientProvider: () => ResilientProvider,
6529
6742
  createAnthropicProvider: () => createAnthropicProvider,
6530
6743
  createCircuitBreaker: () => createCircuitBreaker,
6531
6744
  createCodexProvider: () => createCodexProvider,
@@ -6536,10 +6749,12 @@ __export(providers_exports, {
6536
6749
  createOpenAIProvider: () => createOpenAIProvider,
6537
6750
  createProvider: () => createProvider,
6538
6751
  createProviderFallback: () => createProviderFallback,
6752
+ createResilientProvider: () => createResilientProvider,
6539
6753
  createRetryableMethod: () => createRetryableMethod,
6540
6754
  estimateCost: () => estimateCost,
6541
6755
  formatCost: () => formatCost,
6542
6756
  getDefaultProvider: () => getDefaultProvider2,
6757
+ getDefaultResilienceConfig: () => getDefaultResilienceConfig,
6543
6758
  getModelPricing: () => getModelPricing,
6544
6759
  hasKnownPricing: () => hasKnownPricing,
6545
6760
  isRetryableError: () => isRetryableError,
@@ -6575,12 +6790,10 @@ async function createProvider(type, config = {}) {
6575
6790
  break;
6576
6791
  case "kimi":
6577
6792
  provider = createKimiProvider(mergedConfig);
6578
- await provider.initialize(mergedConfig);
6579
- return provider;
6793
+ break;
6580
6794
  case "kimi-code":
6581
6795
  provider = createKimiCodeProvider(mergedConfig);
6582
- await provider.initialize(mergedConfig);
6583
- return provider;
6796
+ break;
6584
6797
  case "lmstudio":
6585
6798
  provider = new OpenAIProvider("lmstudio", "LM Studio");
6586
6799
  mergedConfig.baseUrl = mergedConfig.baseUrl ?? "http://localhost:1234/v1";
@@ -6625,7 +6838,10 @@ async function createProvider(type, config = {}) {
6625
6838
  });
6626
6839
  }
6627
6840
  await provider.initialize(mergedConfig);
6628
- return provider;
6841
+ const resilienceEnabled = !["0", "false", "off"].includes(
6842
+ (process.env["COCO_PROVIDER_RESILIENCE"] ?? "1").toLowerCase()
6843
+ );
6844
+ return resilienceEnabled ? createResilientProvider(provider) : provider;
6629
6845
  }
6630
6846
  async function getDefaultProvider2(config = {}) {
6631
6847
  const { getDefaultProvider: getEnvProvider } = await Promise.resolve().then(() => (init_env(), env_exports));
@@ -6679,6 +6895,7 @@ var init_providers = __esm({
6679
6895
  init_pricing();
6680
6896
  init_circuit_breaker();
6681
6897
  init_fallback();
6898
+ init_resilient();
6682
6899
  init_copilot();
6683
6900
  init_anthropic();
6684
6901
  init_openai();
@@ -6687,6 +6904,7 @@ var init_providers = __esm({
6687
6904
  init_copilot2();
6688
6905
  init_errors();
6689
6906
  init_env();
6907
+ init_resilient();
6690
6908
  }
6691
6909
  });
6692
6910
 
@@ -9449,7 +9667,7 @@ async function checkAndCompactContext(session, provider, signal, toolRegistry) {
9449
9667
  session.contextManager.setUsedTokens(result.compactedTokens);
9450
9668
  }
9451
9669
  return result;
9452
- } catch (error) {
9670
+ } catch {
9453
9671
  return null;
9454
9672
  }
9455
9673
  }
@@ -10553,6 +10771,34 @@ function isAbortError(error, signal) {
10553
10771
  if (error.message.endsWith("Request was aborted.")) return true;
10554
10772
  return false;
10555
10773
  }
10774
+ function classifyAgentLoopError(error, signal) {
10775
+ if (isAbortError(error, signal)) {
10776
+ return {
10777
+ kind: "abort",
10778
+ message: "Request was aborted.",
10779
+ original: error
10780
+ };
10781
+ }
10782
+ if (isNonRetryableProviderError(error)) {
10783
+ return {
10784
+ kind: "provider_non_retryable",
10785
+ message: error instanceof Error ? error.message : String(error),
10786
+ original: error
10787
+ };
10788
+ }
10789
+ if (error instanceof ProviderError) {
10790
+ return {
10791
+ kind: "provider_retryable",
10792
+ message: error.message,
10793
+ original: error
10794
+ };
10795
+ }
10796
+ return {
10797
+ kind: "unexpected",
10798
+ message: error instanceof Error ? error.message : String(error),
10799
+ original: error
10800
+ };
10801
+ }
10556
10802
  function isNonRetryableProviderError(error) {
10557
10803
  if (error instanceof ProviderError) {
10558
10804
  const code = error.statusCode;
@@ -19103,7 +19349,7 @@ async function runCIChecks(ctx) {
19103
19349
  cwd: ctx.cwd
19104
19350
  });
19105
19351
  if (result.checks.length === 0) {
19106
- await sleep2(pollMs);
19352
+ await sleep3(pollMs);
19107
19353
  continue;
19108
19354
  }
19109
19355
  if (result.allPassed) {
@@ -19153,7 +19399,7 @@ async function runCIChecks(ctx) {
19153
19399
  spinner18.message(`CI: ${passed}/${result.checks.length} passed, ${pending} pending...`);
19154
19400
  } catch {
19155
19401
  }
19156
- await sleep2(pollMs);
19402
+ await sleep3(pollMs);
19157
19403
  }
19158
19404
  spinner18.stop("CI check timeout");
19159
19405
  const action = await p26.select({
@@ -19178,7 +19424,7 @@ async function runCIChecks(ctx) {
19178
19424
  durationMs: performance.now() - start
19179
19425
  };
19180
19426
  }
19181
- function sleep2(ms) {
19427
+ function sleep3(ms) {
19182
19428
  return new Promise((resolve4) => setTimeout(resolve4, ms));
19183
19429
  }
19184
19430
  var init_ci_checks = __esm({
@@ -50116,6 +50362,60 @@ function extractDeniedPath(error) {
50116
50362
  // src/cli/repl/agent-loop.ts
50117
50363
  init_allow_path_prompt();
50118
50364
  init_error_resilience();
50365
+
50366
+ // src/cli/repl/turn-quality.ts
50367
+ function clamp(value, min, max) {
50368
+ return Math.max(min, Math.min(max, value));
50369
+ }
50370
+ function computeTurnQualityMetrics(input) {
50371
+ const executedToolCalls = input.executedTools.length;
50372
+ const successfulToolCalls = input.executedTools.filter((t) => t.result.success).length;
50373
+ const failedToolCalls = executedToolCalls - successfulToolCalls;
50374
+ let score = 100;
50375
+ if (input.hadError) score -= 25;
50376
+ if (executedToolCalls > 0) {
50377
+ const failureRatio = failedToolCalls / executedToolCalls;
50378
+ score -= Math.round(failureRatio * 35);
50379
+ }
50380
+ if (input.maxIterations > 0) {
50381
+ const iterationRatio = input.iterationsUsed / input.maxIterations;
50382
+ if (iterationRatio > 0.8) score -= 10;
50383
+ if (iterationRatio >= 1) score -= 10;
50384
+ }
50385
+ score += Math.min(input.repeatedOutputsSuppressed * 2, 8);
50386
+ return {
50387
+ score: clamp(score, 0, 100),
50388
+ iterationsUsed: input.iterationsUsed,
50389
+ maxIterations: input.maxIterations,
50390
+ executedToolCalls,
50391
+ successfulToolCalls,
50392
+ failedToolCalls,
50393
+ hadError: input.hadError,
50394
+ repeatedOutputsSuppressed: input.repeatedOutputsSuppressed
50395
+ };
50396
+ }
50397
+ var RepeatedOutputSuppressor = class {
50398
+ seen = /* @__PURE__ */ new Map();
50399
+ transform(toolName, content) {
50400
+ const fingerprint = this.fingerprint(toolName, content);
50401
+ const count = this.seen.get(fingerprint) ?? 0;
50402
+ this.seen.set(fingerprint, count + 1);
50403
+ if (count === 0) {
50404
+ return { content, suppressed: false };
50405
+ }
50406
+ return {
50407
+ content: `[Repeated tool output suppressed: '${toolName}' produced the same output as before (occurrence ${count + 1}). If needed, re-run with different inputs.]`,
50408
+ suppressed: true
50409
+ };
50410
+ }
50411
+ fingerprint(toolName, content) {
50412
+ const head = content.slice(0, 200);
50413
+ const tail = content.slice(-200);
50414
+ return `${toolName}|${content.length}|${head}|${tail}`;
50415
+ }
50416
+ };
50417
+
50418
+ // src/cli/repl/agent-loop.ts
50119
50419
  async function executeAgentTurn(session, userMessage, provider, toolRegistry, options = {}) {
50120
50420
  resetLineBuffer();
50121
50421
  const messageSnapshot = session.messages.length;
@@ -50124,12 +50424,23 @@ async function executeAgentTurn(session, userMessage, provider, toolRegistry, op
50124
50424
  let totalInputTokens = 0;
50125
50425
  let totalOutputTokens = 0;
50126
50426
  let finalContent = "";
50427
+ let hadTurnError = false;
50428
+ let repeatedOutputsSuppressed = 0;
50429
+ const repeatedOutputSuppressor = new RepeatedOutputSuppressor();
50430
+ const buildQualityMetrics = () => computeTurnQualityMetrics({
50431
+ iterationsUsed: iteration,
50432
+ maxIterations,
50433
+ executedTools,
50434
+ hadError: hadTurnError,
50435
+ repeatedOutputsSuppressed
50436
+ });
50127
50437
  const abortReturn = () => {
50128
50438
  session.messages.length = messageSnapshot;
50129
50439
  return {
50130
50440
  content: finalContent,
50131
50441
  toolCalls: executedTools,
50132
50442
  usage: { inputTokens: totalInputTokens, outputTokens: totalOutputTokens },
50443
+ quality: buildQualityMetrics(),
50133
50444
  aborted: true,
50134
50445
  partialContent: finalContent || void 0,
50135
50446
  abortReason: "user_cancel"
@@ -50243,13 +50554,15 @@ ${tail}`;
50243
50554
  options.onThinkingEnd?.();
50244
50555
  thinkingEnded = true;
50245
50556
  }
50246
- if (isAbortError(streamError, options.signal)) {
50557
+ const classification = classifyAgentLoopError(streamError, options.signal);
50558
+ if (classification.kind === "abort") {
50247
50559
  return abortReturn();
50248
50560
  }
50249
- if (isNonRetryableProviderError(streamError)) {
50250
- throw streamError;
50561
+ if (classification.kind === "provider_non_retryable") {
50562
+ throw classification.original;
50251
50563
  }
50252
- const errorMsg = streamError instanceof Error ? streamError.message : String(streamError);
50564
+ hadTurnError = true;
50565
+ const errorMsg = classification.message;
50253
50566
  addMessage(session, {
50254
50567
  role: "assistant",
50255
50568
  content: `[Error during streaming: ${errorMsg}]`
@@ -50258,6 +50571,7 @@ ${tail}`;
50258
50571
  content: finalContent || `[Error: ${errorMsg}]`,
50259
50572
  toolCalls: executedTools,
50260
50573
  usage: { inputTokens: totalInputTokens, outputTokens: totalOutputTokens },
50574
+ quality: buildQualityMetrics(),
50261
50575
  aborted: false,
50262
50576
  partialContent: finalContent || void 0,
50263
50577
  error: errorMsg
@@ -50463,10 +50777,15 @@ ${tail}`;
50463
50777
  }
50464
50778
  const executedCall = executedTools.find((e) => e.id === toolCall.id);
50465
50779
  if (executedCall) {
50780
+ const truncatedOutput = truncateInlineResult(executedCall.result.output, toolCall.name);
50781
+ const transformedOutput = repeatedOutputSuppressor.transform(toolCall.name, truncatedOutput);
50782
+ if (transformedOutput.suppressed) {
50783
+ repeatedOutputsSuppressed++;
50784
+ }
50466
50785
  toolResults.push({
50467
50786
  type: "tool_result",
50468
50787
  tool_use_id: toolCall.id,
50469
- content: truncateInlineResult(executedCall.result.output, toolCall.name),
50788
+ content: transformedOutput.content,
50470
50789
  is_error: !executedCall.result.success
50471
50790
  });
50472
50791
  } else {
@@ -50614,6 +50933,7 @@ I have reached the maximum iteration limit (${maxIterations}). The task may be i
50614
50933
  content: finalContent,
50615
50934
  toolCalls: executedTools,
50616
50935
  usage: { inputTokens: totalInputTokens, outputTokens: totalOutputTokens },
50936
+ quality: buildQualityMetrics(),
50617
50937
  aborted: false
50618
50938
  };
50619
50939
  }