@mclean-capital/neura 3.6.0 → 3.6.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.
@@ -80568,16 +80568,16 @@ ${context}
80568
80568
  this.turnAudioChunks = 0;
80569
80569
  this.turnAudioBytes = 0;
80570
80570
  this.cb.onTurnComplete();
80571
- const outputItems = msg.response?.output;
80571
+ const res = msg.response;
80572
+ const outputItems = Array.isArray(res?.output) ? res.output : [];
80572
80573
  const fullParts = [];
80573
- if (Array.isArray(outputItems)) {
80574
- for (const item of outputItems) {
80575
- if (item.type === "message" && Array.isArray(item.content)) {
80576
- for (const part of item.content) {
80577
- if (part.transcript) {
80578
- this.pushTranscript("assistant", part.transcript);
80579
- fullParts.push(part.transcript);
80580
- }
80574
+ for (const item of outputItems) {
80575
+ const it = item;
80576
+ if (it.type === "message" && Array.isArray(it.content)) {
80577
+ for (const part of it.content) {
80578
+ if (part.transcript) {
80579
+ this.pushTranscript("assistant", part.transcript);
80580
+ fullParts.push(part.transcript);
80581
80581
  }
80582
80582
  }
80583
80583
  }
@@ -80585,53 +80585,85 @@ ${context}
80585
80585
  if (fullParts.length > 0) {
80586
80586
  this.cb.onOutputTranscriptComplete(fullParts.join(""));
80587
80587
  }
80588
+ if (res?.status === "completed") {
80589
+ void this.dispatchFunctionCalls(outputItems);
80590
+ }
80588
80591
  break;
80589
80592
  }
80590
- case "response.function_call_arguments.done":
80591
- void this.handleFunctionCallDone(msg);
80592
- break;
80593
80593
  case "error":
80594
80594
  log32.error("api error", { error: msg.error });
80595
80595
  this.cb.onError(msg.error?.message || "Unknown error");
80596
80596
  break;
80597
80597
  }
80598
80598
  }
80599
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
80600
- async handleFunctionCallDone(msg) {
80601
- const name = msg.name ?? "";
80602
- let args = {};
80603
- try {
80604
- args = JSON.parse(msg.arguments ?? "{}");
80605
- } catch {
80606
- }
80607
- const currentWs = this.ws;
80608
- const callId = msg.call_id;
80609
- this.cb.onToolCall(name, args);
80610
- const result = await handleToolCall(name, args, {
80611
- queryWatcher: this.cb.queryWatcher,
80612
- memoryTools: this.config.memoryTools,
80613
- enterMode: this.config.enterMode,
80614
- taskTools: this.config.taskTools,
80615
- skillTools: this.config.skillTools,
80616
- workerControl: this.config.workerControl,
80617
- workerDispatch: this.config.workerDispatch,
80618
- systemState: this.config.systemState,
80619
- workerLogs: this.config.workerLogs
80599
+ /**
80600
+ * Execute every `function_call` item from a completed `response.done`
80601
+ * payload in parallel, post their outputs in original `output_index`
80602
+ * order, then fire exactly one `response.create` so the model sees the
80603
+ * full batch as a single follow-up turn.
80604
+ *
80605
+ * Authoritative source: `response.output`. We intentionally do NOT
80606
+ * buffer `response.function_call_arguments.done` deltas; `response.done`
80607
+ * carries the finalized list and is the only shape we trust — see the
80608
+ * Codex review cited in the corresponding commit.
80609
+ */
80610
+ async dispatchFunctionCalls(outputItems) {
80611
+ const calls = [];
80612
+ outputItems.forEach((item, idx) => {
80613
+ const it = item;
80614
+ if (it.type !== "function_call" || !it.call_id || typeof it.name !== "string") return;
80615
+ let parsed = {};
80616
+ try {
80617
+ parsed = JSON.parse(it.arguments ?? "{}");
80618
+ } catch {
80619
+ }
80620
+ calls.push({ callId: it.call_id, name: it.name, args: parsed, outputIndex: idx });
80621
+ });
80622
+ const seen = /* @__PURE__ */ new Set();
80623
+ const unique = calls.filter((c) => {
80624
+ if (seen.has(c.callId)) return false;
80625
+ seen.add(c.callId);
80626
+ return true;
80620
80627
  });
80621
- this.cb.onToolResult(name, result);
80622
- if (currentWs && currentWs === this.ws && currentWs.readyState === wrapper_default.OPEN) {
80628
+ if (unique.length === 0) return;
80629
+ const currentWs = this.ws;
80630
+ const outcomes = [];
80631
+ for (const c of unique) {
80632
+ this.cb.onToolCall(c.name, c.args);
80633
+ try {
80634
+ const result = await handleToolCall(c.name, c.args, {
80635
+ queryWatcher: this.cb.queryWatcher,
80636
+ memoryTools: this.config.memoryTools,
80637
+ enterMode: this.config.enterMode,
80638
+ taskTools: this.config.taskTools,
80639
+ skillTools: this.config.skillTools,
80640
+ workerControl: this.config.workerControl,
80641
+ workerDispatch: this.config.workerDispatch,
80642
+ systemState: this.config.systemState,
80643
+ workerLogs: this.config.workerLogs
80644
+ });
80645
+ this.cb.onToolResult(c.name, result);
80646
+ outcomes.push({ callId: c.callId, result, outputIndex: c.outputIndex });
80647
+ } catch (err) {
80648
+ const errorResult = { error: `Tool ${c.name} failed: ${String(err)}` };
80649
+ this.cb.onToolResult(c.name, errorResult);
80650
+ outcomes.push({ callId: c.callId, result: errorResult, outputIndex: c.outputIndex });
80651
+ }
80652
+ }
80653
+ if (!currentWs || currentWs !== this.ws || currentWs.readyState !== wrapper_default.OPEN) return;
80654
+ outcomes.sort((a, b) => a.outputIndex - b.outputIndex).forEach((o) => {
80623
80655
  currentWs.send(
80624
80656
  JSON.stringify({
80625
80657
  type: "conversation.item.create",
80626
80658
  item: {
80627
80659
  type: "function_call_output",
80628
- call_id: callId,
80629
- output: JSON.stringify(result)
80660
+ call_id: o.callId,
80661
+ output: JSON.stringify(o.result)
80630
80662
  }
80631
80663
  })
80632
80664
  );
80633
- currentWs.send(JSON.stringify({ type: "response.create" }));
80634
- }
80665
+ });
80666
+ currentWs.send(JSON.stringify({ type: "response.create" }));
80635
80667
  }
80636
80668
  sendAudio(base643) {
80637
80669
  if (this.ws?.readyState === wrapper_default.OPEN) {