@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/index.js CHANGED
@@ -13072,6 +13072,147 @@ function createKimiCodeProvider(config) {
13072
13072
 
13073
13073
  // src/providers/openai.ts
13074
13074
  init_errors();
13075
+ function getSingleBuilderKey(builders) {
13076
+ return builders.size === 1 ? Array.from(builders.keys())[0] ?? null : null;
13077
+ }
13078
+ function parseToolCallArguments(args, providerName) {
13079
+ try {
13080
+ return args ? JSON.parse(args) : {};
13081
+ } catch {
13082
+ try {
13083
+ if (args) {
13084
+ const repaired = jsonrepair(args);
13085
+ return JSON.parse(repaired);
13086
+ }
13087
+ } catch {
13088
+ console.error(`[${providerName}] Cannot parse tool arguments: ${args.slice(0, 200)}`);
13089
+ }
13090
+ return {};
13091
+ }
13092
+ }
13093
+ var ChatToolCallAssembler = class {
13094
+ builders = /* @__PURE__ */ new Map();
13095
+ lastBuilderKey = null;
13096
+ consume(delta) {
13097
+ 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}`;
13098
+ let started;
13099
+ if (!this.builders.has(key)) {
13100
+ const initialId = delta.id ?? "";
13101
+ const initialName = delta.function?.name ?? "";
13102
+ this.builders.set(key, { id: initialId, name: initialName, arguments: "" });
13103
+ started = {
13104
+ id: initialId || void 0,
13105
+ name: initialName || void 0
13106
+ };
13107
+ }
13108
+ const builder = this.builders.get(key);
13109
+ this.lastBuilderKey = key;
13110
+ if (delta.id) {
13111
+ builder.id = delta.id;
13112
+ }
13113
+ if (delta.function?.name) {
13114
+ builder.name = delta.function.name;
13115
+ }
13116
+ const text = delta.function?.arguments ?? "";
13117
+ if (!text) return { started };
13118
+ builder.arguments += text;
13119
+ return {
13120
+ started,
13121
+ argumentDelta: {
13122
+ id: builder.id,
13123
+ name: builder.name,
13124
+ text
13125
+ }
13126
+ };
13127
+ }
13128
+ finalizeAll(providerName) {
13129
+ const result = [];
13130
+ for (const builder of this.builders.values()) {
13131
+ result.push({
13132
+ id: builder.id,
13133
+ name: builder.name,
13134
+ input: parseToolCallArguments(builder.arguments, providerName)
13135
+ });
13136
+ }
13137
+ this.builders.clear();
13138
+ this.lastBuilderKey = null;
13139
+ return result;
13140
+ }
13141
+ };
13142
+ var ResponsesToolCallAssembler = class {
13143
+ builders = /* @__PURE__ */ new Map();
13144
+ outputIndexToBuilderKey = /* @__PURE__ */ new Map();
13145
+ onOutputItemAdded(event) {
13146
+ const item = event.item;
13147
+ if (!item || item.type !== "function_call") return null;
13148
+ const callId = item.call_id ?? "";
13149
+ const itemKey = item.id ?? callId;
13150
+ this.builders.set(itemKey, {
13151
+ callId,
13152
+ name: item.name ?? "",
13153
+ arguments: item.arguments ?? ""
13154
+ });
13155
+ if (typeof event.output_index === "number") {
13156
+ this.outputIndexToBuilderKey.set(event.output_index, itemKey);
13157
+ }
13158
+ return {
13159
+ id: callId,
13160
+ name: item.name ?? ""
13161
+ };
13162
+ }
13163
+ onArgumentsDelta(event) {
13164
+ const builderKey = this.resolveBuilderKey(event.item_id, event.output_index);
13165
+ if (!builderKey) return;
13166
+ const builder = this.builders.get(builderKey);
13167
+ if (!builder) return;
13168
+ builder.arguments += event.delta ?? "";
13169
+ }
13170
+ onArgumentsDone(event, providerName) {
13171
+ const builderKey = this.resolveBuilderKey(event.item_id, event.output_index);
13172
+ if (!builderKey) return null;
13173
+ const builder = this.builders.get(builderKey);
13174
+ if (!builder) return null;
13175
+ const toolCall = {
13176
+ id: builder.callId,
13177
+ name: builder.name,
13178
+ input: parseToolCallArguments(event.arguments ?? builder.arguments, providerName)
13179
+ };
13180
+ this.deleteBuilder(builderKey);
13181
+ return toolCall;
13182
+ }
13183
+ finalizeAll(providerName) {
13184
+ const calls = [];
13185
+ for (const builder of this.builders.values()) {
13186
+ calls.push({
13187
+ id: builder.callId,
13188
+ name: builder.name,
13189
+ input: parseToolCallArguments(builder.arguments, providerName)
13190
+ });
13191
+ }
13192
+ this.builders.clear();
13193
+ this.outputIndexToBuilderKey.clear();
13194
+ return calls;
13195
+ }
13196
+ resolveBuilderKey(itemId, outputIndex) {
13197
+ if (itemId && this.builders.has(itemId)) {
13198
+ return itemId;
13199
+ }
13200
+ if (typeof outputIndex === "number") {
13201
+ return this.outputIndexToBuilderKey.get(outputIndex) ?? null;
13202
+ }
13203
+ return getSingleBuilderKey(this.builders);
13204
+ }
13205
+ deleteBuilder(builderKey) {
13206
+ this.builders.delete(builderKey);
13207
+ for (const [idx, key] of this.outputIndexToBuilderKey.entries()) {
13208
+ if (key === builderKey) {
13209
+ this.outputIndexToBuilderKey.delete(idx);
13210
+ }
13211
+ }
13212
+ }
13213
+ };
13214
+
13215
+ // src/providers/openai.ts
13075
13216
  var DEFAULT_MODEL2 = "gpt-5.4-codex";
13076
13217
  var CONTEXT_WINDOWS2 = {
13077
13218
  // OpenAI models
@@ -13411,8 +13552,7 @@ var OpenAIProvider = class {
13411
13552
  const stream = await this.client.chat.completions.create(
13412
13553
  requestParams
13413
13554
  );
13414
- const toolCallBuilders = /* @__PURE__ */ new Map();
13415
- let lastToolCallKey = null;
13555
+ const toolCallAssembler = new ChatToolCallAssembler();
13416
13556
  const streamTimeout = this.config.timeout ?? 12e4;
13417
13557
  let lastActivityTime = Date.now();
13418
13558
  const timeoutController = new AbortController();
@@ -13426,30 +13566,6 @@ var OpenAIProvider = class {
13426
13566
  timeoutController.signal.addEventListener("abort", () => stream.controller.abort(), {
13427
13567
  once: true
13428
13568
  });
13429
- const providerName = this.name;
13430
- const parseArguments2 = (builder) => {
13431
- let input = {};
13432
- try {
13433
- input = builder.arguments ? JSON.parse(builder.arguments) : {};
13434
- } catch (error) {
13435
- console.warn(
13436
- `[${providerName}] Failed to parse tool call arguments for ${builder.name}: ${builder.arguments?.slice(0, 300)}`
13437
- );
13438
- try {
13439
- if (builder.arguments) {
13440
- const repaired = jsonrepair(builder.arguments);
13441
- input = JSON.parse(repaired);
13442
- console.log(`[${providerName}] \u2713 Successfully repaired JSON for ${builder.name}`);
13443
- }
13444
- } catch {
13445
- console.error(
13446
- `[${providerName}] Cannot repair JSON for ${builder.name}, using empty object`
13447
- );
13448
- console.error(`[${providerName}] Original error:`, error);
13449
- }
13450
- }
13451
- return input;
13452
- };
13453
13569
  try {
13454
13570
  let streamStopReason;
13455
13571
  for await (const chunk of stream) {
@@ -13462,38 +13578,31 @@ var OpenAIProvider = class {
13462
13578
  }
13463
13579
  if (delta?.tool_calls) {
13464
13580
  for (const toolCallDelta of delta.tool_calls) {
13465
- 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}`;
13466
- if (!toolCallBuilders.has(key)) {
13467
- toolCallBuilders.set(key, {
13468
- id: toolCallDelta.id ?? "",
13469
- name: toolCallDelta.function?.name ?? "",
13470
- arguments: ""
13471
- });
13581
+ const consumed = toolCallAssembler.consume({
13582
+ index: toolCallDelta.index,
13583
+ id: toolCallDelta.id ?? void 0,
13584
+ function: {
13585
+ name: toolCallDelta.function?.name ?? void 0,
13586
+ arguments: toolCallDelta.function?.arguments ?? void 0
13587
+ }
13588
+ });
13589
+ if (consumed.started) {
13472
13590
  yield {
13473
13591
  type: "tool_use_start",
13474
13592
  toolCall: {
13475
- id: toolCallDelta.id,
13476
- name: toolCallDelta.function?.name
13593
+ id: consumed.started.id,
13594
+ name: consumed.started.name
13477
13595
  }
13478
13596
  };
13479
13597
  }
13480
- const builder = toolCallBuilders.get(key);
13481
- lastToolCallKey = key;
13482
- if (toolCallDelta.id) {
13483
- builder.id = toolCallDelta.id;
13484
- }
13485
- if (toolCallDelta.function?.name) {
13486
- builder.name = toolCallDelta.function.name;
13487
- }
13488
- if (toolCallDelta.function?.arguments) {
13489
- builder.arguments += toolCallDelta.function.arguments;
13598
+ if (consumed.argumentDelta) {
13490
13599
  yield {
13491
13600
  type: "tool_use_delta",
13492
13601
  toolCall: {
13493
- id: builder.id,
13494
- name: builder.name
13602
+ id: consumed.argumentDelta.id,
13603
+ name: consumed.argumentDelta.name
13495
13604
  },
13496
- text: toolCallDelta.function.arguments
13605
+ text: consumed.argumentDelta.text
13497
13606
  };
13498
13607
  }
13499
13608
  }
@@ -13502,27 +13611,26 @@ var OpenAIProvider = class {
13502
13611
  if (finishReason) {
13503
13612
  streamStopReason = this.mapFinishReason(finishReason);
13504
13613
  }
13505
- if (finishReason && toolCallBuilders.size > 0) {
13506
- for (const [, builder] of toolCallBuilders) {
13614
+ if (finishReason) {
13615
+ for (const toolCall of toolCallAssembler.finalizeAll(this.name)) {
13507
13616
  yield {
13508
13617
  type: "tool_use_end",
13509
13618
  toolCall: {
13510
- id: builder.id,
13511
- name: builder.name,
13512
- input: parseArguments2(builder)
13619
+ id: toolCall.id,
13620
+ name: toolCall.name,
13621
+ input: toolCall.input
13513
13622
  }
13514
13623
  };
13515
13624
  }
13516
- toolCallBuilders.clear();
13517
13625
  }
13518
13626
  }
13519
- for (const [, builder] of toolCallBuilders) {
13627
+ for (const toolCall of toolCallAssembler.finalizeAll(this.name)) {
13520
13628
  yield {
13521
13629
  type: "tool_use_end",
13522
13630
  toolCall: {
13523
- id: builder.id,
13524
- name: builder.name,
13525
- input: parseArguments2(builder)
13631
+ id: toolCall.id,
13632
+ name: toolCall.name,
13633
+ input: toolCall.input
13526
13634
  }
13527
13635
  };
13528
13636
  }
@@ -13959,7 +14067,7 @@ var OpenAIProvider = class {
13959
14067
  toolCalls.push({
13960
14068
  id: item.call_id,
13961
14069
  name: item.name,
13962
- input: this.parseResponsesArguments(item.arguments)
14070
+ input: parseToolCallArguments(item.arguments, this.name)
13963
14071
  });
13964
14072
  }
13965
14073
  }
@@ -14065,8 +14173,7 @@ var OpenAIProvider = class {
14065
14173
  const stream = await this.client.responses.create(
14066
14174
  requestParams
14067
14175
  );
14068
- const fnCallBuilders = /* @__PURE__ */ new Map();
14069
- const outputIndexToBuilderKey = /* @__PURE__ */ new Map();
14176
+ const toolCallAssembler = new ResponsesToolCallAssembler();
14070
14177
  const streamTimeout = this.config.timeout ?? 12e4;
14071
14178
  let lastActivityTime = Date.now();
14072
14179
  const timeoutController = new AbortController();
@@ -14090,67 +14197,66 @@ var OpenAIProvider = class {
14090
14197
  yield { type: "text", text: event.delta };
14091
14198
  break;
14092
14199
  case "response.output_item.added":
14093
- if (event.item.type === "function_call") {
14094
- const fc = event.item;
14095
- const itemKey = fc.id ?? fc.call_id;
14096
- fnCallBuilders.set(itemKey, {
14097
- callId: fc.call_id,
14098
- name: fc.name,
14099
- arguments: fc.arguments ?? ""
14200
+ {
14201
+ const item = event.item;
14202
+ const start = toolCallAssembler.onOutputItemAdded({
14203
+ output_index: event.output_index,
14204
+ item: {
14205
+ type: item.type,
14206
+ id: item.id,
14207
+ call_id: item.call_id,
14208
+ name: item.name,
14209
+ arguments: item.arguments
14210
+ }
14100
14211
  });
14101
- if (typeof event.output_index === "number") {
14102
- outputIndexToBuilderKey.set(event.output_index, itemKey);
14103
- }
14212
+ if (!start) break;
14104
14213
  yield {
14105
14214
  type: "tool_use_start",
14106
- toolCall: { id: fc.call_id, name: fc.name }
14215
+ toolCall: { id: start.id, name: start.name }
14107
14216
  };
14108
14217
  }
14109
14218
  break;
14110
14219
  case "response.function_call_arguments.delta":
14111
- {
14112
- 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);
14113
- if (!builderKey) break;
14114
- const builder = fnCallBuilders.get(builderKey);
14115
- if (builder) {
14116
- builder.arguments += event.delta;
14117
- }
14118
- }
14220
+ toolCallAssembler.onArgumentsDelta({
14221
+ item_id: event.item_id,
14222
+ output_index: event.output_index,
14223
+ delta: event.delta
14224
+ });
14119
14225
  break;
14120
14226
  case "response.function_call_arguments.done":
14121
14227
  {
14122
- 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);
14123
- if (!builderKey) break;
14124
- const builder = fnCallBuilders.get(builderKey);
14125
- if (builder) {
14228
+ const toolCall = toolCallAssembler.onArgumentsDone(
14229
+ {
14230
+ item_id: event.item_id,
14231
+ output_index: event.output_index,
14232
+ arguments: event.arguments
14233
+ },
14234
+ this.name
14235
+ );
14236
+ if (toolCall) {
14126
14237
  yield {
14127
14238
  type: "tool_use_end",
14128
14239
  toolCall: {
14129
- id: builder.callId,
14130
- name: builder.name,
14131
- input: this.parseResponsesArguments(event.arguments ?? builder.arguments)
14240
+ id: toolCall.id,
14241
+ name: toolCall.name,
14242
+ input: toolCall.input
14132
14243
  }
14133
14244
  };
14134
- fnCallBuilders.delete(builderKey);
14135
- for (const [idx, key] of outputIndexToBuilderKey.entries()) {
14136
- if (key === builderKey) outputIndexToBuilderKey.delete(idx);
14137
- }
14138
14245
  }
14139
14246
  }
14140
14247
  break;
14141
14248
  case "response.completed":
14142
14249
  {
14143
- for (const [, builder] of fnCallBuilders) {
14250
+ for (const toolCall of toolCallAssembler.finalizeAll(this.name)) {
14144
14251
  yield {
14145
14252
  type: "tool_use_end",
14146
14253
  toolCall: {
14147
- id: builder.callId,
14148
- name: builder.name,
14149
- input: this.parseResponsesArguments(builder.arguments)
14254
+ id: toolCall.id,
14255
+ name: toolCall.name,
14256
+ input: toolCall.input
14150
14257
  }
14151
14258
  };
14152
14259
  }
14153
- fnCallBuilders.clear();
14154
14260
  const hasToolCalls = event.response.output.some(
14155
14261
  (i) => i.type === "function_call"
14156
14262
  );
@@ -14270,24 +14376,6 @@ var OpenAIProvider = class {
14270
14376
  strict: false
14271
14377
  }));
14272
14378
  }
14273
- /**
14274
- * Parse tool call arguments with jsonrepair fallback (Responses API)
14275
- */
14276
- parseResponsesArguments(args) {
14277
- try {
14278
- return args ? JSON.parse(args) : {};
14279
- } catch {
14280
- try {
14281
- if (args) {
14282
- const repaired = jsonrepair(args);
14283
- return JSON.parse(repaired);
14284
- }
14285
- } catch {
14286
- console.error(`[${this.name}] Cannot parse tool arguments: ${args.slice(0, 200)}`);
14287
- }
14288
- return {};
14289
- }
14290
- }
14291
14379
  };
14292
14380
  function createKimiProvider(config) {
14293
14381
  const provider = new OpenAIProvider("kimi", "Kimi (Moonshot)");
@@ -14335,21 +14423,6 @@ function extractAccountId(accessToken) {
14335
14423
  const auth = claims["https://api.openai.com/auth"];
14336
14424
  return claims["chatgpt_account_id"] || auth?.["chatgpt_account_id"] || claims["organizations"]?.[0]?.id;
14337
14425
  }
14338
- function parseArguments(args) {
14339
- try {
14340
- return args ? JSON.parse(args) : {};
14341
- } catch {
14342
- try {
14343
- if (args) {
14344
- const repaired = jsonrepair(args);
14345
- return JSON.parse(repaired);
14346
- }
14347
- } catch {
14348
- console.error(`[Codex] Cannot parse tool arguments: ${args.slice(0, 200)}`);
14349
- }
14350
- return {};
14351
- }
14352
- }
14353
14426
  var CodexProvider = class {
14354
14427
  id = "codex";
14355
14428
  name = "OpenAI Codex (ChatGPT Plus/Pro)";
@@ -14649,8 +14722,7 @@ var CodexProvider = class {
14649
14722
  let inputTokens = 0;
14650
14723
  let outputTokens = 0;
14651
14724
  const toolCalls = [];
14652
- const fnCallBuilders = /* @__PURE__ */ new Map();
14653
- const outputIndexToBuilderKey = /* @__PURE__ */ new Map();
14725
+ const toolCallAssembler = new ResponsesToolCallAssembler();
14654
14726
  await this.readSSEStream(response, (event) => {
14655
14727
  if (event.id) responseId = event.id;
14656
14728
  switch (event.type) {
@@ -14661,41 +14733,35 @@ var CodexProvider = class {
14661
14733
  content = event.text ?? content;
14662
14734
  break;
14663
14735
  case "response.output_item.added": {
14664
- const item = event.item;
14665
- if (item.type === "function_call") {
14666
- const itemKey = item.id ?? item.call_id;
14667
- fnCallBuilders.set(itemKey, {
14668
- callId: item.call_id,
14669
- name: item.name,
14670
- arguments: item.arguments ?? ""
14671
- });
14672
- if (typeof event.output_index === "number") {
14673
- outputIndexToBuilderKey.set(event.output_index, itemKey);
14674
- }
14675
- }
14736
+ toolCallAssembler.onOutputItemAdded({
14737
+ output_index: event.output_index,
14738
+ item: event.item
14739
+ });
14676
14740
  break;
14677
14741
  }
14678
14742
  case "response.function_call_arguments.delta": {
14679
- 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);
14680
- if (!builderKey) break;
14681
- const builder = fnCallBuilders.get(builderKey);
14682
- if (builder) builder.arguments += event.delta ?? "";
14743
+ toolCallAssembler.onArgumentsDelta({
14744
+ item_id: event.item_id,
14745
+ output_index: event.output_index,
14746
+ delta: event.delta
14747
+ });
14683
14748
  break;
14684
14749
  }
14685
14750
  case "response.function_call_arguments.done": {
14686
- 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);
14687
- if (!builderKey) break;
14688
- const builder = fnCallBuilders.get(builderKey);
14689
- if (builder) {
14751
+ const toolCall = toolCallAssembler.onArgumentsDone(
14752
+ {
14753
+ item_id: event.item_id,
14754
+ output_index: event.output_index,
14755
+ arguments: event.arguments
14756
+ },
14757
+ this.name
14758
+ );
14759
+ if (toolCall) {
14690
14760
  toolCalls.push({
14691
- id: builder.callId,
14692
- name: builder.name,
14693
- input: parseArguments(event.arguments ?? builder.arguments)
14761
+ id: toolCall.id,
14762
+ name: toolCall.name,
14763
+ input: toolCall.input
14694
14764
  });
14695
- fnCallBuilders.delete(builderKey);
14696
- for (const [idx, key] of outputIndexToBuilderKey.entries()) {
14697
- if (key === builderKey) outputIndexToBuilderKey.delete(idx);
14698
- }
14699
14765
  }
14700
14766
  break;
14701
14767
  }
@@ -14706,14 +14772,13 @@ var CodexProvider = class {
14706
14772
  inputTokens = usage.input_tokens ?? 0;
14707
14773
  outputTokens = usage.output_tokens ?? 0;
14708
14774
  }
14709
- for (const [, builder] of fnCallBuilders) {
14775
+ for (const toolCall of toolCallAssembler.finalizeAll(this.name)) {
14710
14776
  toolCalls.push({
14711
- id: builder.callId,
14712
- name: builder.name,
14713
- input: parseArguments(builder.arguments)
14777
+ id: toolCall.id,
14778
+ name: toolCall.name,
14779
+ input: toolCall.input
14714
14780
  });
14715
14781
  }
14716
- fnCallBuilders.clear();
14717
14782
  break;
14718
14783
  }
14719
14784
  }
@@ -14807,8 +14872,7 @@ var CodexProvider = class {
14807
14872
  const reader = response.body.getReader();
14808
14873
  const decoder = new TextDecoder();
14809
14874
  let buffer = "";
14810
- const fnCallBuilders = /* @__PURE__ */ new Map();
14811
- const outputIndexToBuilderKey = /* @__PURE__ */ new Map();
14875
+ const toolCallAssembler = new ResponsesToolCallAssembler();
14812
14876
  let lastActivityTime = Date.now();
14813
14877
  const timeoutController = new AbortController();
14814
14878
  const timeoutInterval = setInterval(() => {
@@ -14841,65 +14905,58 @@ var CodexProvider = class {
14841
14905
  yield { type: "text", text: event.delta ?? "" };
14842
14906
  break;
14843
14907
  case "response.output_item.added": {
14844
- const item = event.item;
14845
- if (item.type === "function_call") {
14846
- const itemKey = item.id ?? item.call_id;
14847
- fnCallBuilders.set(itemKey, {
14848
- callId: item.call_id,
14849
- name: item.name,
14850
- arguments: item.arguments ?? ""
14851
- });
14852
- if (typeof event.output_index === "number") {
14853
- outputIndexToBuilderKey.set(event.output_index, itemKey);
14854
- }
14908
+ const start = toolCallAssembler.onOutputItemAdded({
14909
+ output_index: event.output_index,
14910
+ item: event.item
14911
+ });
14912
+ if (start) {
14855
14913
  yield {
14856
14914
  type: "tool_use_start",
14857
- toolCall: { id: item.call_id, name: item.name }
14915
+ toolCall: { id: start.id, name: start.name }
14858
14916
  };
14859
14917
  }
14860
14918
  break;
14861
14919
  }
14862
14920
  case "response.function_call_arguments.delta": {
14863
- 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);
14864
- if (!builderKey) break;
14865
- const builder = fnCallBuilders.get(builderKey);
14866
- if (builder) {
14867
- builder.arguments += event.delta ?? "";
14868
- }
14921
+ toolCallAssembler.onArgumentsDelta({
14922
+ item_id: event.item_id,
14923
+ output_index: event.output_index,
14924
+ delta: event.delta
14925
+ });
14869
14926
  break;
14870
14927
  }
14871
14928
  case "response.function_call_arguments.done": {
14872
- 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);
14873
- if (!builderKey) break;
14874
- const builder = fnCallBuilders.get(builderKey);
14875
- if (builder) {
14929
+ const toolCall = toolCallAssembler.onArgumentsDone(
14930
+ {
14931
+ item_id: event.item_id,
14932
+ output_index: event.output_index,
14933
+ arguments: event.arguments
14934
+ },
14935
+ this.name
14936
+ );
14937
+ if (toolCall) {
14876
14938
  yield {
14877
14939
  type: "tool_use_end",
14878
14940
  toolCall: {
14879
- id: builder.callId,
14880
- name: builder.name,
14881
- input: parseArguments(event.arguments ?? builder.arguments)
14941
+ id: toolCall.id,
14942
+ name: toolCall.name,
14943
+ input: toolCall.input
14882
14944
  }
14883
14945
  };
14884
- fnCallBuilders.delete(builderKey);
14885
- for (const [idx, key] of outputIndexToBuilderKey.entries()) {
14886
- if (key === builderKey) outputIndexToBuilderKey.delete(idx);
14887
- }
14888
14946
  }
14889
14947
  break;
14890
14948
  }
14891
14949
  case "response.completed": {
14892
- for (const [, builder] of fnCallBuilders) {
14950
+ for (const toolCall of toolCallAssembler.finalizeAll(this.name)) {
14893
14951
  yield {
14894
14952
  type: "tool_use_end",
14895
14953
  toolCall: {
14896
- id: builder.callId,
14897
- name: builder.name,
14898
- input: parseArguments(builder.arguments)
14954
+ id: toolCall.id,
14955
+ name: toolCall.name,
14956
+ input: toolCall.input
14899
14957
  }
14900
14958
  };
14901
14959
  }
14902
- fnCallBuilders.clear();
14903
14960
  const resp = event.response;
14904
14961
  const output = resp?.output ?? [];
14905
14962
  const hasToolCalls = output.some((i) => i.type === "function_call");
@@ -15565,10 +15622,296 @@ var GeminiProvider = class {
15565
15622
 
15566
15623
  // src/providers/circuit-breaker.ts
15567
15624
  init_errors();
15625
+ var DEFAULT_CIRCUIT_BREAKER_CONFIG = {
15626
+ failureThreshold: 5,
15627
+ resetTimeout: 3e4,
15628
+ halfOpenRequests: 1
15629
+ };
15630
+ var CircuitOpenError = class extends ProviderError {
15631
+ remainingTime;
15632
+ constructor(provider, remainingTime) {
15633
+ super(`Circuit breaker is open for provider: ${provider}`, {
15634
+ provider,
15635
+ retryable: true
15636
+ });
15637
+ this.name = "CircuitOpenError";
15638
+ this.remainingTime = remainingTime;
15639
+ }
15640
+ };
15641
+ var CircuitBreaker = class {
15642
+ config;
15643
+ state = "closed";
15644
+ failureCount = 0;
15645
+ lastFailureTime = null;
15646
+ halfOpenSuccesses = 0;
15647
+ providerId;
15648
+ /**
15649
+ * Create a new circuit breaker
15650
+ *
15651
+ * @param config - Circuit breaker configuration
15652
+ * @param providerId - Provider identifier for error messages
15653
+ */
15654
+ constructor(config, providerId = "unknown") {
15655
+ this.config = { ...DEFAULT_CIRCUIT_BREAKER_CONFIG, ...config };
15656
+ this.providerId = providerId;
15657
+ }
15658
+ /**
15659
+ * Get the current circuit state
15660
+ */
15661
+ getState() {
15662
+ this.checkStateTransition();
15663
+ return this.state;
15664
+ }
15665
+ /**
15666
+ * Check if the circuit is currently open (blocking requests)
15667
+ */
15668
+ isOpen() {
15669
+ this.checkStateTransition();
15670
+ return this.state === "open";
15671
+ }
15672
+ /**
15673
+ * Get the current failure count
15674
+ */
15675
+ getFailureCount() {
15676
+ return this.failureCount;
15677
+ }
15678
+ /**
15679
+ * Record a successful request
15680
+ * Resets failure count in closed state, or counts toward closing in half-open
15681
+ */
15682
+ recordSuccess() {
15683
+ if (this.state === "half-open") {
15684
+ this.halfOpenSuccesses++;
15685
+ if (this.halfOpenSuccesses >= this.config.halfOpenRequests) {
15686
+ this.close();
15687
+ }
15688
+ } else if (this.state === "closed") {
15689
+ this.failureCount = 0;
15690
+ }
15691
+ }
15692
+ /**
15693
+ * Record a failed request
15694
+ * Increments failure count and may open the circuit
15695
+ */
15696
+ recordFailure() {
15697
+ this.lastFailureTime = Date.now();
15698
+ this.failureCount++;
15699
+ if (this.state === "half-open") {
15700
+ this.open();
15701
+ } else if (this.state === "closed" && this.failureCount >= this.config.failureThreshold) {
15702
+ this.open();
15703
+ }
15704
+ }
15705
+ /**
15706
+ * Execute a function with circuit breaker protection
15707
+ *
15708
+ * @param fn - Async function to execute
15709
+ * @returns Result of the function
15710
+ * @throws CircuitOpenError if circuit is open
15711
+ * @throws Original error if function fails
15712
+ */
15713
+ async execute(fn) {
15714
+ this.checkStateTransition();
15715
+ if (this.state === "open") {
15716
+ const elapsed = Date.now() - (this.lastFailureTime ?? Date.now());
15717
+ const remaining = this.config.resetTimeout - elapsed;
15718
+ throw new CircuitOpenError(this.providerId, remaining);
15719
+ }
15720
+ try {
15721
+ const result = await fn();
15722
+ this.recordSuccess();
15723
+ return result;
15724
+ } catch (error) {
15725
+ this.recordFailure();
15726
+ throw error;
15727
+ }
15728
+ }
15729
+ /**
15730
+ * Manually reset the circuit breaker to closed state
15731
+ */
15732
+ reset() {
15733
+ this.close();
15734
+ }
15735
+ /**
15736
+ * Check and perform state transitions based on time
15737
+ */
15738
+ checkStateTransition() {
15739
+ if (this.state === "open" && this.lastFailureTime !== null) {
15740
+ const elapsed = Date.now() - this.lastFailureTime;
15741
+ if (elapsed >= this.config.resetTimeout) {
15742
+ this.halfOpen();
15743
+ }
15744
+ }
15745
+ }
15746
+ /**
15747
+ * Transition to closed state
15748
+ */
15749
+ close() {
15750
+ this.state = "closed";
15751
+ this.failureCount = 0;
15752
+ this.halfOpenSuccesses = 0;
15753
+ this.lastFailureTime = null;
15754
+ }
15755
+ /**
15756
+ * Transition to open state
15757
+ */
15758
+ open() {
15759
+ this.state = "open";
15760
+ this.halfOpenSuccesses = 0;
15761
+ }
15762
+ /**
15763
+ * Transition to half-open state
15764
+ */
15765
+ halfOpen() {
15766
+ this.state = "half-open";
15767
+ this.halfOpenSuccesses = 0;
15768
+ }
15769
+ };
15568
15770
 
15569
15771
  // src/providers/fallback.ts
15570
15772
  init_errors();
15571
15773
 
15774
+ // src/providers/resilient.ts
15775
+ var DEFAULT_STREAM_RETRY = {
15776
+ maxRetries: 1,
15777
+ initialDelayMs: 500,
15778
+ maxDelayMs: 5e3,
15779
+ backoffMultiplier: 2,
15780
+ jitterFactor: 0.1
15781
+ };
15782
+ function sleep2(ms) {
15783
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
15784
+ }
15785
+ function computeRetryDelay(attempt, config) {
15786
+ const exp = config.initialDelayMs * Math.pow(config.backoffMultiplier, attempt);
15787
+ const capped = Math.min(exp, config.maxDelayMs);
15788
+ const jitter = capped * config.jitterFactor * (Math.random() * 2 - 1);
15789
+ return Math.max(0, Math.min(capped + jitter, config.maxDelayMs));
15790
+ }
15791
+ var ResilientProvider = class {
15792
+ id;
15793
+ name;
15794
+ provider;
15795
+ breaker;
15796
+ retryConfig;
15797
+ streamRetryConfig;
15798
+ constructor(provider, config = {}) {
15799
+ this.provider = provider;
15800
+ this.id = provider.id;
15801
+ this.name = provider.name;
15802
+ this.retryConfig = { ...DEFAULT_RETRY_CONFIG, ...config.retry };
15803
+ this.streamRetryConfig = { ...DEFAULT_STREAM_RETRY, ...config.streamRetry };
15804
+ this.breaker = new CircuitBreaker(
15805
+ { ...DEFAULT_CIRCUIT_BREAKER_CONFIG, ...config.circuitBreaker },
15806
+ provider.id
15807
+ );
15808
+ }
15809
+ async initialize(config) {
15810
+ await this.provider.initialize(config);
15811
+ }
15812
+ async chat(messages, options) {
15813
+ return this.breaker.execute(
15814
+ () => withRetry(() => this.provider.chat(messages, options), this.retryConfig)
15815
+ );
15816
+ }
15817
+ async chatWithTools(messages, options) {
15818
+ return this.breaker.execute(
15819
+ () => withRetry(() => this.provider.chatWithTools(messages, options), this.retryConfig)
15820
+ );
15821
+ }
15822
+ async *stream(messages, options) {
15823
+ yield* this.streamWithPolicy(() => this.provider.stream(messages, options));
15824
+ }
15825
+ async *streamWithTools(messages, options) {
15826
+ yield* this.streamWithPolicy(() => this.provider.streamWithTools(messages, options));
15827
+ }
15828
+ countTokens(text) {
15829
+ return this.provider.countTokens(text);
15830
+ }
15831
+ getContextWindow() {
15832
+ return this.provider.getContextWindow();
15833
+ }
15834
+ async isAvailable() {
15835
+ try {
15836
+ return await this.breaker.execute(() => this.provider.isAvailable());
15837
+ } catch (error) {
15838
+ if (error instanceof CircuitOpenError) {
15839
+ return false;
15840
+ }
15841
+ return false;
15842
+ }
15843
+ }
15844
+ getCircuitState() {
15845
+ return this.breaker.getState();
15846
+ }
15847
+ resetCircuit() {
15848
+ this.breaker.reset();
15849
+ }
15850
+ async *streamWithPolicy(createStream) {
15851
+ let attempt = 0;
15852
+ while (attempt <= this.streamRetryConfig.maxRetries) {
15853
+ if (this.breaker.isOpen()) {
15854
+ throw new CircuitOpenError(this.id, 0);
15855
+ }
15856
+ let emittedChunk = false;
15857
+ try {
15858
+ for await (const chunk of createStream()) {
15859
+ emittedChunk = true;
15860
+ yield chunk;
15861
+ }
15862
+ this.breaker.recordSuccess();
15863
+ return;
15864
+ } catch (error) {
15865
+ this.breaker.recordFailure();
15866
+ const shouldRetry = !emittedChunk && attempt < this.streamRetryConfig.maxRetries && isRetryableError(error);
15867
+ if (!shouldRetry) {
15868
+ throw error;
15869
+ }
15870
+ const delay = computeRetryDelay(attempt, this.streamRetryConfig);
15871
+ await sleep2(delay);
15872
+ attempt++;
15873
+ }
15874
+ }
15875
+ }
15876
+ };
15877
+ function getDefaultResilienceConfig(providerId) {
15878
+ if (providerId === "ollama" || providerId === "lmstudio") {
15879
+ return {
15880
+ retry: {
15881
+ maxRetries: 1,
15882
+ initialDelayMs: 300,
15883
+ maxDelayMs: 1500
15884
+ },
15885
+ streamRetry: {
15886
+ maxRetries: 0
15887
+ },
15888
+ circuitBreaker: {
15889
+ failureThreshold: 3,
15890
+ resetTimeout: 1e4
15891
+ }
15892
+ };
15893
+ }
15894
+ return {
15895
+ retry: {
15896
+ maxRetries: 3,
15897
+ initialDelayMs: 1e3,
15898
+ maxDelayMs: 3e4
15899
+ },
15900
+ streamRetry: {
15901
+ maxRetries: 1,
15902
+ initialDelayMs: 500,
15903
+ maxDelayMs: 5e3
15904
+ },
15905
+ circuitBreaker: {
15906
+ failureThreshold: 5,
15907
+ resetTimeout: 3e4
15908
+ }
15909
+ };
15910
+ }
15911
+ function createResilientProvider(provider, config) {
15912
+ return new ResilientProvider(provider, getDefaultResilienceConfig(provider.id));
15913
+ }
15914
+
15572
15915
  // src/providers/index.ts
15573
15916
  init_copilot();
15574
15917
  init_errors();
@@ -15601,12 +15944,10 @@ async function createProvider(type, config = {}) {
15601
15944
  break;
15602
15945
  case "kimi":
15603
15946
  provider = createKimiProvider(mergedConfig);
15604
- await provider.initialize(mergedConfig);
15605
- return provider;
15947
+ break;
15606
15948
  case "kimi-code":
15607
15949
  provider = createKimiCodeProvider(mergedConfig);
15608
- await provider.initialize(mergedConfig);
15609
- return provider;
15950
+ break;
15610
15951
  case "lmstudio":
15611
15952
  provider = new OpenAIProvider("lmstudio", "LM Studio");
15612
15953
  mergedConfig.baseUrl = mergedConfig.baseUrl ?? "http://localhost:1234/v1";
@@ -15651,7 +15992,10 @@ async function createProvider(type, config = {}) {
15651
15992
  });
15652
15993
  }
15653
15994
  await provider.initialize(mergedConfig);
15654
- return provider;
15995
+ const resilienceEnabled = !["0", "false", "off"].includes(
15996
+ (process.env["COCO_PROVIDER_RESILIENCE"] ?? "1").toLowerCase()
15997
+ );
15998
+ return resilienceEnabled ? createResilientProvider(provider) : provider;
15655
15999
  }
15656
16000
 
15657
16001
  // src/orchestrator/orchestrator.ts