@ordewell/core 0.4.17 → 0.4.18
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.d.mts +23 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +92 -12
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +92 -12
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -9695,6 +9695,8 @@ ${item.text}` : item.text });
|
|
|
9695
9695
|
var SERVER_READY_TIMEOUT_MS = 3e4;
|
|
9696
9696
|
var STDERR_TAIL_CHARS2 = 4e3;
|
|
9697
9697
|
var STREAM_CONNECT_TIMEOUT_MS = 5e3;
|
|
9698
|
+
var RECOVERY_POLL_INTERVAL_MS = 2e3;
|
|
9699
|
+
var RECOVERY_TIMEOUT_MS = 9e5;
|
|
9698
9700
|
var DISABLED_TOOLS = {
|
|
9699
9701
|
question: false,
|
|
9700
9702
|
edit: false,
|
|
@@ -9707,6 +9709,33 @@ function splitModelId(id) {
|
|
|
9707
9709
|
if (slash <= 0 || slash === id.length - 1) return null;
|
|
9708
9710
|
return { providerID: id.slice(0, slash), modelID: id.slice(slash + 1) };
|
|
9709
9711
|
}
|
|
9712
|
+
function describeError(err) {
|
|
9713
|
+
const visited = /* @__PURE__ */ new Set();
|
|
9714
|
+
const lines = [];
|
|
9715
|
+
let current = err;
|
|
9716
|
+
while (current && !visited.has(current)) {
|
|
9717
|
+
visited.add(current);
|
|
9718
|
+
if (!(current instanceof Error)) {
|
|
9719
|
+
lines.push(String(current));
|
|
9720
|
+
break;
|
|
9721
|
+
}
|
|
9722
|
+
const code = current.code;
|
|
9723
|
+
const suffix = typeof code === "string" && !current.message.includes(code) ? ` (${code})` : "";
|
|
9724
|
+
lines.push(`${current.message}${suffix}`);
|
|
9725
|
+
current = current.cause;
|
|
9726
|
+
}
|
|
9727
|
+
return lines.join(": ");
|
|
9728
|
+
}
|
|
9729
|
+
function delay(ms, signal) {
|
|
9730
|
+
return new Promise((resolve4) => {
|
|
9731
|
+
const timer = setTimeout(resolve4, ms);
|
|
9732
|
+
timer.unref?.();
|
|
9733
|
+
signal?.addEventListener("abort", () => {
|
|
9734
|
+
clearTimeout(timer);
|
|
9735
|
+
resolve4();
|
|
9736
|
+
}, { once: true });
|
|
9737
|
+
});
|
|
9738
|
+
}
|
|
9710
9739
|
var OpenCodeAdapter = class {
|
|
9711
9740
|
constructor(deps) {
|
|
9712
9741
|
this.deps = deps;
|
|
@@ -9722,6 +9751,8 @@ var OpenCodeAdapter = class {
|
|
|
9722
9751
|
opts = null;
|
|
9723
9752
|
/** Whether this turn has already emitted reply text — see {@link emitPart}. */
|
|
9724
9753
|
turnHasText = false;
|
|
9754
|
+
/** The last assistant message already settled — the baseline {@link recoverReply} measures a new reply against. */
|
|
9755
|
+
lastAssistantId = null;
|
|
9725
9756
|
async start(opts) {
|
|
9726
9757
|
this.opts = opts;
|
|
9727
9758
|
assertWorkspaceExists(opts.cwd, { isDirectory: this.deps.isDirectory });
|
|
@@ -9775,6 +9806,8 @@ ${this.stderrTail.trim()}` : ""}`);
|
|
|
9775
9806
|
const existing = await this.json("GET", `/session/${opts.resumeSessionId}`).catch(() => null);
|
|
9776
9807
|
if (existing?.id) {
|
|
9777
9808
|
this.sessionId = existing.id;
|
|
9809
|
+
const history = await this.json("GET", `/session/${existing.id}/message`).catch(() => null);
|
|
9810
|
+
this.lastAssistantId = this.newestAssistantId(history);
|
|
9778
9811
|
return;
|
|
9779
9812
|
}
|
|
9780
9813
|
}
|
|
@@ -9825,29 +9858,76 @@ ${this.stderrTail.trim()}` : ""}`);
|
|
|
9825
9858
|
this.dispose();
|
|
9826
9859
|
return;
|
|
9827
9860
|
}
|
|
9828
|
-
|
|
9829
|
-
if (failure) {
|
|
9830
|
-
onEvent({ type: "error", message: failure });
|
|
9831
|
-
return;
|
|
9832
|
-
}
|
|
9833
|
-
const assistantId = reply?.info?.id;
|
|
9834
|
-
for (const part of reply?.parts ?? []) {
|
|
9835
|
-
if (part.type !== "tool" && assistantId && part.messageID !== assistantId) continue;
|
|
9836
|
-
this.emitPart(part, seen, onEvent);
|
|
9837
|
-
}
|
|
9838
|
-
onEvent({ type: "turn_end" });
|
|
9861
|
+
this.settle(reply, seen, onEvent);
|
|
9839
9862
|
} catch (err) {
|
|
9840
9863
|
if (signal?.aborted) {
|
|
9841
9864
|
this.dispose();
|
|
9842
9865
|
return;
|
|
9843
9866
|
}
|
|
9844
|
-
|
|
9867
|
+
const recovered = this.exited ? null : await this.recoverReply(signal, onActivity);
|
|
9868
|
+
if (recovered) {
|
|
9869
|
+
this.settle(recovered, seen, onEvent);
|
|
9870
|
+
return;
|
|
9871
|
+
}
|
|
9872
|
+
onEvent({ type: "error", message: `The OpenCode planner turn failed: ${describeError(err)}` });
|
|
9845
9873
|
} finally {
|
|
9846
9874
|
streamAbort.abort();
|
|
9847
9875
|
await live.catch(() => {
|
|
9848
9876
|
});
|
|
9849
9877
|
}
|
|
9850
9878
|
}
|
|
9879
|
+
/**
|
|
9880
|
+
* Turn one settled assistant message into events. The settled response is
|
|
9881
|
+
* authoritative: it names the assistant message, so its parts are the ones
|
|
9882
|
+
* that make up the reply. Tool parts already seen live are deduplicated by
|
|
9883
|
+
* call id; anything the stream missed (including a stream that never
|
|
9884
|
+
* connected) arrives here.
|
|
9885
|
+
*/
|
|
9886
|
+
settle(reply, seen, onEvent) {
|
|
9887
|
+
const failure = typeof reply?.error === "string" ? reply.error : reply?.error?.message ?? reply?.info?.error?.data?.message;
|
|
9888
|
+
if (failure) {
|
|
9889
|
+
onEvent({ type: "error", message: failure });
|
|
9890
|
+
return;
|
|
9891
|
+
}
|
|
9892
|
+
const assistantId = reply?.info?.id;
|
|
9893
|
+
for (const part of reply?.parts ?? []) {
|
|
9894
|
+
if (part.type !== "tool" && assistantId && part.messageID !== assistantId) continue;
|
|
9895
|
+
this.emitPart(part, seen, onEvent);
|
|
9896
|
+
}
|
|
9897
|
+
if (assistantId) this.lastAssistantId = assistantId;
|
|
9898
|
+
onEvent({ type: "turn_end" });
|
|
9899
|
+
}
|
|
9900
|
+
/**
|
|
9901
|
+
* Poll the session for this turn's assistant message after the POST's socket
|
|
9902
|
+
* died under it. Node's global `fetch` is undici, which aborts a request
|
|
9903
|
+
* whose response headers have not arrived within 300s — and OpenCode sends
|
|
9904
|
+
* none until the turn is done, so any turn past five minutes fails as
|
|
9905
|
+
* `TypeError: fetch failed` while the server is still working. The message
|
|
9906
|
+
* exists server-side either way, so it is waited for and read back.
|
|
9907
|
+
*
|
|
9908
|
+
* A message that exists but has not completed is progress, not an answer:
|
|
9909
|
+
* it refreshes the watchdog and the poll continues.
|
|
9910
|
+
*/
|
|
9911
|
+
async recoverReply(signal, onActivity) {
|
|
9912
|
+
const deadline = Date.now() + RECOVERY_TIMEOUT_MS;
|
|
9913
|
+
for (; ; ) {
|
|
9914
|
+
if (signal?.aborted || this.exited || this.disposed || !this.sessionId) return null;
|
|
9915
|
+
const messages = await this.json("GET", `/session/${this.sessionId}/message`).catch(() => null);
|
|
9916
|
+
const pending = Array.isArray(messages) ? [...messages].reverse().find((m) => m.info?.role === "assistant" && m.info.id && m.info.id !== this.lastAssistantId) : void 0;
|
|
9917
|
+
if (pending?.info?.time?.completed || pending?.info?.error) return pending;
|
|
9918
|
+
if (pending) onActivity?.();
|
|
9919
|
+
if (Date.now() >= deadline) return null;
|
|
9920
|
+
await delay(RECOVERY_POLL_INTERVAL_MS, signal);
|
|
9921
|
+
}
|
|
9922
|
+
}
|
|
9923
|
+
newestAssistantId(messages) {
|
|
9924
|
+
if (!Array.isArray(messages)) return null;
|
|
9925
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
9926
|
+
const info = messages[i]?.info;
|
|
9927
|
+
if (info?.role === "assistant" && info.id) return info.id;
|
|
9928
|
+
}
|
|
9929
|
+
return null;
|
|
9930
|
+
}
|
|
9851
9931
|
/**
|
|
9852
9932
|
* Emit one message part, once. OpenCode reports a tool part repeatedly as it
|
|
9853
9933
|
* moves through pending → running → completed, so parts are keyed by id and
|