@corbat-tech/coco 2.23.0 → 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,7 +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();
13555
+ const toolCallAssembler = new ChatToolCallAssembler();
13415
13556
  const streamTimeout = this.config.timeout ?? 12e4;
13416
13557
  let lastActivityTime = Date.now();
13417
13558
  const timeoutController = new AbortController();
@@ -13425,30 +13566,6 @@ var OpenAIProvider = class {
13425
13566
  timeoutController.signal.addEventListener("abort", () => stream.controller.abort(), {
13426
13567
  once: true
13427
13568
  });
13428
- const providerName = this.name;
13429
- const parseArguments2 = (builder) => {
13430
- let input = {};
13431
- try {
13432
- input = builder.arguments ? JSON.parse(builder.arguments) : {};
13433
- } catch (error) {
13434
- console.warn(
13435
- `[${providerName}] Failed to parse tool call arguments for ${builder.name}: ${builder.arguments?.slice(0, 300)}`
13436
- );
13437
- try {
13438
- if (builder.arguments) {
13439
- const repaired = jsonrepair(builder.arguments);
13440
- input = JSON.parse(repaired);
13441
- console.log(`[${providerName}] \u2713 Successfully repaired JSON for ${builder.name}`);
13442
- }
13443
- } catch {
13444
- console.error(
13445
- `[${providerName}] Cannot repair JSON for ${builder.name}, using empty object`
13446
- );
13447
- console.error(`[${providerName}] Original error:`, error);
13448
- }
13449
- }
13450
- return input;
13451
- };
13452
13569
  try {
13453
13570
  let streamStopReason;
13454
13571
  for await (const chunk of stream) {
@@ -13461,37 +13578,31 @@ var OpenAIProvider = class {
13461
13578
  }
13462
13579
  if (delta?.tool_calls) {
13463
13580
  for (const toolCallDelta of delta.tool_calls) {
13464
- const index = toolCallDelta.index ?? toolCallBuilders.size;
13465
- if (!toolCallBuilders.has(index)) {
13466
- toolCallBuilders.set(index, {
13467
- id: toolCallDelta.id ?? "",
13468
- name: toolCallDelta.function?.name ?? "",
13469
- arguments: ""
13470
- });
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) {
13471
13590
  yield {
13472
13591
  type: "tool_use_start",
13473
13592
  toolCall: {
13474
- id: toolCallDelta.id,
13475
- name: toolCallDelta.function?.name
13593
+ id: consumed.started.id,
13594
+ name: consumed.started.name
13476
13595
  }
13477
13596
  };
13478
13597
  }
13479
- const builder = toolCallBuilders.get(index);
13480
- if (toolCallDelta.id) {
13481
- builder.id = toolCallDelta.id;
13482
- }
13483
- if (toolCallDelta.function?.name) {
13484
- builder.name = toolCallDelta.function.name;
13485
- }
13486
- if (toolCallDelta.function?.arguments) {
13487
- builder.arguments += toolCallDelta.function.arguments;
13598
+ if (consumed.argumentDelta) {
13488
13599
  yield {
13489
13600
  type: "tool_use_delta",
13490
13601
  toolCall: {
13491
- id: builder.id,
13492
- name: builder.name
13602
+ id: consumed.argumentDelta.id,
13603
+ name: consumed.argumentDelta.name
13493
13604
  },
13494
- text: toolCallDelta.function.arguments
13605
+ text: consumed.argumentDelta.text
13495
13606
  };
13496
13607
  }
13497
13608
  }
@@ -13500,27 +13611,26 @@ var OpenAIProvider = class {
13500
13611
  if (finishReason) {
13501
13612
  streamStopReason = this.mapFinishReason(finishReason);
13502
13613
  }
13503
- if (finishReason && toolCallBuilders.size > 0) {
13504
- for (const [, builder] of toolCallBuilders) {
13614
+ if (finishReason) {
13615
+ for (const toolCall of toolCallAssembler.finalizeAll(this.name)) {
13505
13616
  yield {
13506
13617
  type: "tool_use_end",
13507
13618
  toolCall: {
13508
- id: builder.id,
13509
- name: builder.name,
13510
- input: parseArguments2(builder)
13619
+ id: toolCall.id,
13620
+ name: toolCall.name,
13621
+ input: toolCall.input
13511
13622
  }
13512
13623
  };
13513
13624
  }
13514
- toolCallBuilders.clear();
13515
13625
  }
13516
13626
  }
13517
- for (const [, builder] of toolCallBuilders) {
13627
+ for (const toolCall of toolCallAssembler.finalizeAll(this.name)) {
13518
13628
  yield {
13519
13629
  type: "tool_use_end",
13520
13630
  toolCall: {
13521
- id: builder.id,
13522
- name: builder.name,
13523
- input: parseArguments2(builder)
13631
+ id: toolCall.id,
13632
+ name: toolCall.name,
13633
+ input: toolCall.input
13524
13634
  }
13525
13635
  };
13526
13636
  }
@@ -13957,7 +14067,7 @@ var OpenAIProvider = class {
13957
14067
  toolCalls.push({
13958
14068
  id: item.call_id,
13959
14069
  name: item.name,
13960
- input: this.parseResponsesArguments(item.arguments)
14070
+ input: parseToolCallArguments(item.arguments, this.name)
13961
14071
  });
13962
14072
  }
13963
14073
  }
@@ -14063,7 +14173,7 @@ var OpenAIProvider = class {
14063
14173
  const stream = await this.client.responses.create(
14064
14174
  requestParams
14065
14175
  );
14066
- const fnCallBuilders = /* @__PURE__ */ new Map();
14176
+ const toolCallAssembler = new ResponsesToolCallAssembler();
14067
14177
  const streamTimeout = this.config.timeout ?? 12e4;
14068
14178
  let lastActivityTime = Date.now();
14069
14179
  const timeoutController = new AbortController();
@@ -14087,57 +14197,66 @@ var OpenAIProvider = class {
14087
14197
  yield { type: "text", text: event.delta };
14088
14198
  break;
14089
14199
  case "response.output_item.added":
14090
- if (event.item.type === "function_call") {
14091
- const fc = event.item;
14092
- const itemKey = fc.id ?? fc.call_id;
14093
- fnCallBuilders.set(itemKey, {
14094
- callId: fc.call_id,
14095
- name: fc.name,
14096
- 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
+ }
14097
14211
  });
14212
+ if (!start) break;
14098
14213
  yield {
14099
14214
  type: "tool_use_start",
14100
- toolCall: { id: fc.call_id, name: fc.name }
14215
+ toolCall: { id: start.id, name: start.name }
14101
14216
  };
14102
14217
  }
14103
14218
  break;
14104
14219
  case "response.function_call_arguments.delta":
14105
- {
14106
- const builder = fnCallBuilders.get(event.item_id);
14107
- if (builder) {
14108
- builder.arguments += event.delta;
14109
- }
14110
- }
14220
+ toolCallAssembler.onArgumentsDelta({
14221
+ item_id: event.item_id,
14222
+ output_index: event.output_index,
14223
+ delta: event.delta
14224
+ });
14111
14225
  break;
14112
14226
  case "response.function_call_arguments.done":
14113
14227
  {
14114
- const builder = fnCallBuilders.get(event.item_id);
14115
- 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) {
14116
14237
  yield {
14117
14238
  type: "tool_use_end",
14118
14239
  toolCall: {
14119
- id: builder.callId,
14120
- name: builder.name,
14121
- input: this.parseResponsesArguments(event.arguments)
14240
+ id: toolCall.id,
14241
+ name: toolCall.name,
14242
+ input: toolCall.input
14122
14243
  }
14123
14244
  };
14124
- fnCallBuilders.delete(event.item_id);
14125
14245
  }
14126
14246
  }
14127
14247
  break;
14128
14248
  case "response.completed":
14129
14249
  {
14130
- for (const [, builder] of fnCallBuilders) {
14250
+ for (const toolCall of toolCallAssembler.finalizeAll(this.name)) {
14131
14251
  yield {
14132
14252
  type: "tool_use_end",
14133
14253
  toolCall: {
14134
- id: builder.callId,
14135
- name: builder.name,
14136
- input: this.parseResponsesArguments(builder.arguments)
14254
+ id: toolCall.id,
14255
+ name: toolCall.name,
14256
+ input: toolCall.input
14137
14257
  }
14138
14258
  };
14139
14259
  }
14140
- fnCallBuilders.clear();
14141
14260
  const hasToolCalls = event.response.output.some(
14142
14261
  (i) => i.type === "function_call"
14143
14262
  );
@@ -14257,24 +14376,6 @@ var OpenAIProvider = class {
14257
14376
  strict: false
14258
14377
  }));
14259
14378
  }
14260
- /**
14261
- * Parse tool call arguments with jsonrepair fallback (Responses API)
14262
- */
14263
- parseResponsesArguments(args) {
14264
- try {
14265
- return args ? JSON.parse(args) : {};
14266
- } catch {
14267
- try {
14268
- if (args) {
14269
- const repaired = jsonrepair(args);
14270
- return JSON.parse(repaired);
14271
- }
14272
- } catch {
14273
- console.error(`[${this.name}] Cannot parse tool arguments: ${args.slice(0, 200)}`);
14274
- }
14275
- return {};
14276
- }
14277
- }
14278
14379
  };
14279
14380
  function createKimiProvider(config) {
14280
14381
  const provider = new OpenAIProvider("kimi", "Kimi (Moonshot)");
@@ -14322,21 +14423,6 @@ function extractAccountId(accessToken) {
14322
14423
  const auth = claims["https://api.openai.com/auth"];
14323
14424
  return claims["chatgpt_account_id"] || auth?.["chatgpt_account_id"] || claims["organizations"]?.[0]?.id;
14324
14425
  }
14325
- function parseArguments(args) {
14326
- try {
14327
- return args ? JSON.parse(args) : {};
14328
- } catch {
14329
- try {
14330
- if (args) {
14331
- const repaired = jsonrepair(args);
14332
- return JSON.parse(repaired);
14333
- }
14334
- } catch {
14335
- console.error(`[Codex] Cannot parse tool arguments: ${args.slice(0, 200)}`);
14336
- }
14337
- return {};
14338
- }
14339
- }
14340
14426
  var CodexProvider = class {
14341
14427
  id = "codex";
14342
14428
  name = "OpenAI Codex (ChatGPT Plus/Pro)";
@@ -14636,7 +14722,7 @@ var CodexProvider = class {
14636
14722
  let inputTokens = 0;
14637
14723
  let outputTokens = 0;
14638
14724
  const toolCalls = [];
14639
- const fnCallBuilders = /* @__PURE__ */ new Map();
14725
+ const toolCallAssembler = new ResponsesToolCallAssembler();
14640
14726
  await this.readSSEStream(response, (event) => {
14641
14727
  if (event.id) responseId = event.id;
14642
14728
  switch (event.type) {
@@ -14647,31 +14733,35 @@ var CodexProvider = class {
14647
14733
  content = event.text ?? content;
14648
14734
  break;
14649
14735
  case "response.output_item.added": {
14650
- const item = event.item;
14651
- if (item.type === "function_call") {
14652
- const itemKey = item.id ?? item.call_id;
14653
- fnCallBuilders.set(itemKey, {
14654
- callId: item.call_id,
14655
- name: item.name,
14656
- arguments: ""
14657
- });
14658
- }
14736
+ toolCallAssembler.onOutputItemAdded({
14737
+ output_index: event.output_index,
14738
+ item: event.item
14739
+ });
14659
14740
  break;
14660
14741
  }
14661
14742
  case "response.function_call_arguments.delta": {
14662
- const builder = fnCallBuilders.get(event.item_id);
14663
- 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
+ });
14664
14748
  break;
14665
14749
  }
14666
14750
  case "response.function_call_arguments.done": {
14667
- const builder = fnCallBuilders.get(event.item_id);
14668
- 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) {
14669
14760
  toolCalls.push({
14670
- id: builder.callId,
14671
- name: builder.name,
14672
- input: parseArguments(event.arguments)
14761
+ id: toolCall.id,
14762
+ name: toolCall.name,
14763
+ input: toolCall.input
14673
14764
  });
14674
- fnCallBuilders.delete(event.item_id);
14675
14765
  }
14676
14766
  break;
14677
14767
  }
@@ -14682,14 +14772,13 @@ var CodexProvider = class {
14682
14772
  inputTokens = usage.input_tokens ?? 0;
14683
14773
  outputTokens = usage.output_tokens ?? 0;
14684
14774
  }
14685
- for (const [, builder] of fnCallBuilders) {
14775
+ for (const toolCall of toolCallAssembler.finalizeAll(this.name)) {
14686
14776
  toolCalls.push({
14687
- id: builder.callId,
14688
- name: builder.name,
14689
- input: parseArguments(builder.arguments)
14777
+ id: toolCall.id,
14778
+ name: toolCall.name,
14779
+ input: toolCall.input
14690
14780
  });
14691
14781
  }
14692
- fnCallBuilders.clear();
14693
14782
  break;
14694
14783
  }
14695
14784
  }
@@ -14783,7 +14872,7 @@ var CodexProvider = class {
14783
14872
  const reader = response.body.getReader();
14784
14873
  const decoder = new TextDecoder();
14785
14874
  let buffer = "";
14786
- const fnCallBuilders = /* @__PURE__ */ new Map();
14875
+ const toolCallAssembler = new ResponsesToolCallAssembler();
14787
14876
  let lastActivityTime = Date.now();
14788
14877
  const timeoutController = new AbortController();
14789
14878
  const timeoutInterval = setInterval(() => {
@@ -14816,55 +14905,58 @@ var CodexProvider = class {
14816
14905
  yield { type: "text", text: event.delta ?? "" };
14817
14906
  break;
14818
14907
  case "response.output_item.added": {
14819
- const item = event.item;
14820
- if (item.type === "function_call") {
14821
- const itemKey = item.id ?? item.call_id;
14822
- fnCallBuilders.set(itemKey, {
14823
- callId: item.call_id,
14824
- name: item.name,
14825
- arguments: ""
14826
- });
14908
+ const start = toolCallAssembler.onOutputItemAdded({
14909
+ output_index: event.output_index,
14910
+ item: event.item
14911
+ });
14912
+ if (start) {
14827
14913
  yield {
14828
14914
  type: "tool_use_start",
14829
- toolCall: { id: item.call_id, name: item.name }
14915
+ toolCall: { id: start.id, name: start.name }
14830
14916
  };
14831
14917
  }
14832
14918
  break;
14833
14919
  }
14834
14920
  case "response.function_call_arguments.delta": {
14835
- const builder = fnCallBuilders.get(event.item_id);
14836
- if (builder) {
14837
- builder.arguments += event.delta ?? "";
14838
- }
14921
+ toolCallAssembler.onArgumentsDelta({
14922
+ item_id: event.item_id,
14923
+ output_index: event.output_index,
14924
+ delta: event.delta
14925
+ });
14839
14926
  break;
14840
14927
  }
14841
14928
  case "response.function_call_arguments.done": {
14842
- const builder = fnCallBuilders.get(event.item_id);
14843
- 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) {
14844
14938
  yield {
14845
14939
  type: "tool_use_end",
14846
14940
  toolCall: {
14847
- id: builder.callId,
14848
- name: builder.name,
14849
- input: parseArguments(event.arguments ?? builder.arguments)
14941
+ id: toolCall.id,
14942
+ name: toolCall.name,
14943
+ input: toolCall.input
14850
14944
  }
14851
14945
  };
14852
- fnCallBuilders.delete(event.item_id);
14853
14946
  }
14854
14947
  break;
14855
14948
  }
14856
14949
  case "response.completed": {
14857
- for (const [, builder] of fnCallBuilders) {
14950
+ for (const toolCall of toolCallAssembler.finalizeAll(this.name)) {
14858
14951
  yield {
14859
14952
  type: "tool_use_end",
14860
14953
  toolCall: {
14861
- id: builder.callId,
14862
- name: builder.name,
14863
- input: parseArguments(builder.arguments)
14954
+ id: toolCall.id,
14955
+ name: toolCall.name,
14956
+ input: toolCall.input
14864
14957
  }
14865
14958
  };
14866
14959
  }
14867
- fnCallBuilders.clear();
14868
14960
  const resp = event.response;
14869
14961
  const output = resp?.output ?? [];
14870
14962
  const hasToolCalls = output.some((i) => i.type === "function_call");
@@ -15222,8 +15314,8 @@ var GeminiProvider = class {
15222
15314
  const { history, lastMessage } = this.convertMessages(messages);
15223
15315
  const chat = model.startChat({ history });
15224
15316
  const result = await chat.sendMessageStream(lastMessage);
15225
- const emittedToolCalls = /* @__PURE__ */ new Set();
15226
15317
  let streamStopReason;
15318
+ let streamToolCallCounter = 0;
15227
15319
  for await (const chunk of result.stream) {
15228
15320
  const text = chunk.text();
15229
15321
  if (text) {
@@ -15238,30 +15330,23 @@ var GeminiProvider = class {
15238
15330
  for (const part of candidate.content.parts) {
15239
15331
  if ("functionCall" in part && part.functionCall) {
15240
15332
  const funcCall = part.functionCall;
15241
- const sortedArgs = funcCall.args ? Object.keys(funcCall.args).sort().map(
15242
- (k) => `${k}:${JSON.stringify(funcCall.args[k])}`
15243
- ).join(",") : "";
15244
- const callKey = `${funcCall.name}-${sortedArgs}`;
15245
- if (!emittedToolCalls.has(callKey)) {
15246
- emittedToolCalls.add(callKey);
15247
- const toolCall = {
15248
- id: funcCall.name,
15249
- // Gemini uses name as ID
15250
- name: funcCall.name,
15251
- input: funcCall.args ?? {}
15252
- };
15253
- yield {
15254
- type: "tool_use_start",
15255
- toolCall: {
15256
- id: toolCall.id,
15257
- name: toolCall.name
15258
- }
15259
- };
15260
- yield {
15261
- type: "tool_use_end",
15262
- toolCall
15263
- };
15264
- }
15333
+ streamToolCallCounter++;
15334
+ const toolCall = {
15335
+ id: `gemini_call_${streamToolCallCounter}`,
15336
+ name: funcCall.name,
15337
+ input: funcCall.args ?? {}
15338
+ };
15339
+ yield {
15340
+ type: "tool_use_start",
15341
+ toolCall: {
15342
+ id: toolCall.id,
15343
+ name: toolCall.name
15344
+ }
15345
+ };
15346
+ yield {
15347
+ type: "tool_use_end",
15348
+ toolCall
15349
+ };
15265
15350
  }
15266
15351
  }
15267
15352
  }
@@ -15336,13 +15421,13 @@ var GeminiProvider = class {
15336
15421
  * Convert messages to Gemini format
15337
15422
  */
15338
15423
  convertMessages(messages) {
15424
+ const toolNameByUseId = this.buildToolUseNameMap(messages);
15425
+ const conversation = messages.filter((m) => m.role !== "system");
15339
15426
  const history = [];
15340
15427
  let lastUserMessage = "";
15341
- for (const msg of messages) {
15342
- if (msg.role === "system") {
15343
- continue;
15344
- }
15345
- const parts = this.convertContent(msg.content);
15428
+ for (let i = 0; i < conversation.length; i++) {
15429
+ const msg = conversation[i];
15430
+ const isLastMessage = i === conversation.length - 1;
15346
15431
  if (msg.role === "user") {
15347
15432
  if (Array.isArray(msg.content) && msg.content[0]?.type === "tool_result") {
15348
15433
  const functionResponses = [];
@@ -15351,23 +15436,49 @@ var GeminiProvider = class {
15351
15436
  const toolResult = block;
15352
15437
  functionResponses.push({
15353
15438
  functionResponse: {
15354
- name: toolResult.tool_use_id,
15355
- // Gemini uses name, we store it in tool_use_id
15439
+ // Gemini expects the function name in functionResponse.name.
15440
+ // Recover it from prior assistant tool_use blocks when possible.
15441
+ name: toolNameByUseId.get(toolResult.tool_use_id) ?? toolResult.tool_use_id,
15356
15442
  response: { result: toolResult.content }
15357
15443
  }
15358
15444
  });
15359
15445
  }
15360
15446
  }
15361
- history.push({ role: "user", parts: functionResponses });
15447
+ if (isLastMessage) {
15448
+ lastUserMessage = functionResponses;
15449
+ } else {
15450
+ history.push({ role: "user", parts: functionResponses });
15451
+ }
15362
15452
  } else {
15363
- lastUserMessage = parts;
15453
+ const parts = this.convertContent(msg.content);
15454
+ if (isLastMessage) {
15455
+ lastUserMessage = parts;
15456
+ } else {
15457
+ history.push({ role: "user", parts });
15458
+ }
15364
15459
  }
15365
15460
  } else if (msg.role === "assistant") {
15461
+ const parts = this.convertContent(msg.content);
15366
15462
  history.push({ role: "model", parts });
15367
15463
  }
15368
15464
  }
15369
15465
  return { history, lastMessage: lastUserMessage };
15370
15466
  }
15467
+ /**
15468
+ * Build a map from tool_use IDs to function names from assistant history.
15469
+ */
15470
+ buildToolUseNameMap(messages) {
15471
+ const map = /* @__PURE__ */ new Map();
15472
+ for (const msg of messages) {
15473
+ if (msg.role !== "assistant" || !Array.isArray(msg.content)) continue;
15474
+ for (const block of msg.content) {
15475
+ if (block.type === "tool_use") {
15476
+ map.set(block.id, block.name);
15477
+ }
15478
+ }
15479
+ }
15480
+ return map;
15481
+ }
15371
15482
  /**
15372
15483
  * Convert content to Gemini parts
15373
15484
  */
@@ -15444,14 +15555,15 @@ var GeminiProvider = class {
15444
15555
  let textContent = "";
15445
15556
  const toolCalls = [];
15446
15557
  if (candidate?.content?.parts) {
15558
+ let toolIndex = 0;
15447
15559
  for (const part of candidate.content.parts) {
15448
15560
  if ("text" in part && part.text) {
15449
15561
  textContent += part.text;
15450
15562
  }
15451
15563
  if ("functionCall" in part && part.functionCall) {
15564
+ toolIndex++;
15452
15565
  toolCalls.push({
15453
- id: part.functionCall.name,
15454
- // Use name as ID for Gemini
15566
+ id: `gemini_call_${toolIndex}`,
15455
15567
  name: part.functionCall.name,
15456
15568
  input: part.functionCall.args ?? {}
15457
15569
  });
@@ -15510,10 +15622,296 @@ var GeminiProvider = class {
15510
15622
 
15511
15623
  // src/providers/circuit-breaker.ts
15512
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
+ };
15513
15770
 
15514
15771
  // src/providers/fallback.ts
15515
15772
  init_errors();
15516
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
+
15517
15915
  // src/providers/index.ts
15518
15916
  init_copilot();
15519
15917
  init_errors();
@@ -15546,12 +15944,10 @@ async function createProvider(type, config = {}) {
15546
15944
  break;
15547
15945
  case "kimi":
15548
15946
  provider = createKimiProvider(mergedConfig);
15549
- await provider.initialize(mergedConfig);
15550
- return provider;
15947
+ break;
15551
15948
  case "kimi-code":
15552
15949
  provider = createKimiCodeProvider(mergedConfig);
15553
- await provider.initialize(mergedConfig);
15554
- return provider;
15950
+ break;
15555
15951
  case "lmstudio":
15556
15952
  provider = new OpenAIProvider("lmstudio", "LM Studio");
15557
15953
  mergedConfig.baseUrl = mergedConfig.baseUrl ?? "http://localhost:1234/v1";
@@ -15596,7 +15992,10 @@ async function createProvider(type, config = {}) {
15596
15992
  });
15597
15993
  }
15598
15994
  await provider.initialize(mergedConfig);
15599
- return provider;
15995
+ const resilienceEnabled = !["0", "false", "off"].includes(
15996
+ (process.env["COCO_PROVIDER_RESILIENCE"] ?? "1").toLowerCase()
15997
+ );
15998
+ return resilienceEnabled ? createResilientProvider(provider) : provider;
15600
15999
  }
15601
16000
 
15602
16001
  // src/orchestrator/orchestrator.ts