@corbat-tech/coco 2.23.1 → 2.24.1

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