@dudousxd/nestjs-agent-opencode 1.0.0 → 2.0.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/README.md +88 -0
- package/dist/durable/index.cjs +617 -43
- package/dist/durable/index.cjs.map +1 -1
- package/dist/durable/index.d.cts +11 -5
- package/dist/durable/index.d.ts +11 -5
- package/dist/durable/index.js +620 -46
- package/dist/durable/index.js.map +1 -1
- package/dist/{engine-BX6MLzxs.d.cts → engine-yFh3eD8j.d.cts} +241 -7
- package/dist/{engine-BX6MLzxs.d.ts → engine-yFh3eD8j.d.ts} +241 -7
- package/dist/index.cjs +595 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +594 -26
- package/dist/index.js.map +1 -1
- package/package.json +8 -5
package/dist/index.js
CHANGED
|
@@ -1999,6 +1999,37 @@ var OpenCodeEventHub = class _OpenCodeEventHub {
|
|
|
1999
1999
|
}
|
|
2000
2000
|
};
|
|
2001
2001
|
|
|
2002
|
+
// src/turn-limits.ts
|
|
2003
|
+
var STOPPED_ERROR_CODE = "opencode_stopped";
|
|
2004
|
+
function stopErrorText(stop) {
|
|
2005
|
+
switch (stop.reason) {
|
|
2006
|
+
case "steps":
|
|
2007
|
+
return `The turn was stopped after ${stop.counts.steps - 1} steps, its limit.`;
|
|
2008
|
+
case "compactions":
|
|
2009
|
+
return `The turn was stopped after ${stop.counts.compactions - 1} compactions, its limit.`;
|
|
2010
|
+
case "wall-clock":
|
|
2011
|
+
return "The turn was stopped: it ran longer than allowed.";
|
|
2012
|
+
default:
|
|
2013
|
+
return "The turn was stopped.";
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
__name(stopErrorText, "stopErrorText");
|
|
2017
|
+
function cleanLimits(limits) {
|
|
2018
|
+
const ok = /* @__PURE__ */ __name((n) => typeof n === "number" && Number.isFinite(n) && n >= 0, "ok");
|
|
2019
|
+
return {
|
|
2020
|
+
...ok(limits?.steps) ? {
|
|
2021
|
+
steps: limits.steps
|
|
2022
|
+
} : {},
|
|
2023
|
+
...ok(limits?.compactions) ? {
|
|
2024
|
+
compactions: limits.compactions
|
|
2025
|
+
} : {},
|
|
2026
|
+
...ok(limits?.wallClockMs) ? {
|
|
2027
|
+
wallClockMs: limits.wallClockMs
|
|
2028
|
+
} : {}
|
|
2029
|
+
};
|
|
2030
|
+
}
|
|
2031
|
+
__name(cleanLimits, "cleanLimits");
|
|
2032
|
+
|
|
2002
2033
|
// src/turns.ts
|
|
2003
2034
|
function _ts_decorate2(decorators, target, key, desc) {
|
|
2004
2035
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
@@ -2028,6 +2059,16 @@ function _ts_param2(paramIndex, decorator) {
|
|
|
2028
2059
|
__name(_ts_param2, "_ts_param");
|
|
2029
2060
|
var DEFAULT_TURN_TIMEOUT_MS = 30 * 6e4;
|
|
2030
2061
|
var DEFAULT_TOOLS_TTL_MS = 7 * 24 * 60 * 6e4;
|
|
2062
|
+
var DEFAULT_HOOK_TIMEOUT_MS = 5e3;
|
|
2063
|
+
var PARKED_RECHECKS_MS = [
|
|
2064
|
+
250,
|
|
2065
|
+
1e3,
|
|
2066
|
+
3e3,
|
|
2067
|
+
1e4,
|
|
2068
|
+
3e4
|
|
2069
|
+
];
|
|
2070
|
+
var PARKED_READ_GAP_MS = 250;
|
|
2071
|
+
var RELEASED_USAGE_MS = 10 * 6e4;
|
|
2031
2072
|
var OpenCodeTurns = class {
|
|
2032
2073
|
static {
|
|
2033
2074
|
__name(this, "OpenCodeTurns");
|
|
@@ -2051,6 +2092,21 @@ var OpenCodeTurns = class {
|
|
|
2051
2092
|
spent = /* @__PURE__ */ new Set();
|
|
2052
2093
|
/** When the tools endpoint was last registered for a location (`server|boot|directory|actor`). */
|
|
2053
2094
|
toolsIssued = /* @__PURE__ */ new Map();
|
|
2095
|
+
/**
|
|
2096
|
+
* What a turn let go of here had spent, for a settle that follows here (a cancel in this process
|
|
2097
|
+
* that settles after the turn was released). Dropped when the run settles here, or after
|
|
2098
|
+
* {@link RELEASED_USAGE_MS} when it settles elsewhere, so no run is kept for good.
|
|
2099
|
+
*/
|
|
2100
|
+
releasedUsage = /* @__PURE__ */ new Map();
|
|
2101
|
+
keepReleasedUsage(runId, usage) {
|
|
2102
|
+
this.releasedUsage.set(runId, usage);
|
|
2103
|
+
setTimeout(() => {
|
|
2104
|
+
if (this.releasedUsage.get(runId) === usage) this.releasedUsage.delete(runId);
|
|
2105
|
+
}, RELEASED_USAGE_MS).unref?.();
|
|
2106
|
+
}
|
|
2107
|
+
/** The turns this process executes, and which turn owns each session (`server|session`). */
|
|
2108
|
+
watches = /* @__PURE__ */ new Map();
|
|
2109
|
+
owners = /* @__PURE__ */ new Map();
|
|
2054
2110
|
constructor(host, sessions, settings, store, sink, deps, registry, skills, memory, queue, toolsTokens2, pricingStore, agentOptions) {
|
|
2055
2111
|
this.host = host;
|
|
2056
2112
|
this.sessions = sessions;
|
|
@@ -2063,6 +2119,10 @@ var OpenCodeTurns = class {
|
|
|
2063
2119
|
this.memory = memory;
|
|
2064
2120
|
this.queue = queue;
|
|
2065
2121
|
this.toolsTokens = toolsTokens2;
|
|
2122
|
+
const hookTimeoutMs = settings.hookTimeoutMs;
|
|
2123
|
+
if (hookTimeoutMs !== void 0 && !(Number.isFinite(hookTimeoutMs) && hookTimeoutMs > 0)) {
|
|
2124
|
+
throw new Error(`hookTimeoutMs must be a positive number of milliseconds, not ${hookTimeoutMs}`);
|
|
2125
|
+
}
|
|
2066
2126
|
this.prices = new ModelPriceResolver({
|
|
2067
2127
|
pricingStore,
|
|
2068
2128
|
catalog: agentOptions?.priceCatalog,
|
|
@@ -2334,8 +2394,12 @@ var OpenCodeTurns = class {
|
|
|
2334
2394
|
}
|
|
2335
2395
|
}
|
|
2336
2396
|
onApplicationShutdown() {
|
|
2397
|
+
for (const runId of [
|
|
2398
|
+
...this.watches.keys()
|
|
2399
|
+
]) this.release(runId, "released");
|
|
2337
2400
|
for (const live of this.live.values()) live.stop();
|
|
2338
2401
|
this.live.clear();
|
|
2402
|
+
this.owners.clear();
|
|
2339
2403
|
this.events.close();
|
|
2340
2404
|
}
|
|
2341
2405
|
// ---- begin ---------------------------------------------------------------------------------
|
|
@@ -2345,7 +2409,8 @@ var OpenCodeTurns = class {
|
|
|
2345
2409
|
* durable `begin` re-run after a crash): the run's user message is written once.
|
|
2346
2410
|
*/
|
|
2347
2411
|
async begin(runId, input) {
|
|
2348
|
-
const
|
|
2412
|
+
const startedAt = this.watches.get(runId)?.startedAt ?? Date.now();
|
|
2413
|
+
const { earlier, rerun } = await this.prepareThread(runId, input);
|
|
2349
2414
|
const server = await this.host.server(input.actor);
|
|
2350
2415
|
const known = await this.sessions.get(input.threadId);
|
|
2351
2416
|
const reusable = known !== null && known.serverKey === server.key && known.bootId === server.bootId && (await this.host.reuse?.({
|
|
@@ -2391,7 +2456,12 @@ var OpenCodeTurns = class {
|
|
|
2391
2456
|
created
|
|
2392
2457
|
});
|
|
2393
2458
|
await this.refreshInstructions(runId, input, server.client, handle.sessionId);
|
|
2394
|
-
|
|
2459
|
+
const begun = this.limitsFor(input).wallClockMs !== void 0 ? {
|
|
2460
|
+
...handle,
|
|
2461
|
+
startedAt
|
|
2462
|
+
} : handle;
|
|
2463
|
+
await this.adopt(runId, input, begun, rerun, void 0, startedAt);
|
|
2464
|
+
return begun;
|
|
2395
2465
|
}
|
|
2396
2466
|
async createSession(runId, input, server, earlier, note) {
|
|
2397
2467
|
const { client } = server;
|
|
@@ -2461,10 +2531,16 @@ ${transcript}` : "",
|
|
|
2461
2531
|
}
|
|
2462
2532
|
const firstDropped = messages[lastUser + 1];
|
|
2463
2533
|
if (firstDropped !== void 0) await this.store.truncateFrom(input.threadId, firstDropped.id);
|
|
2464
|
-
return
|
|
2534
|
+
return {
|
|
2535
|
+
earlier: messages.slice(0, Math.max(lastUser, 0)),
|
|
2536
|
+
rerun: false
|
|
2537
|
+
};
|
|
2465
2538
|
}
|
|
2466
2539
|
const mine = messages.findIndex((m) => m.role === "user" && m.runId === runId);
|
|
2467
|
-
if (mine >= 0) return
|
|
2540
|
+
if (mine >= 0) return {
|
|
2541
|
+
earlier: messages.slice(0, mine),
|
|
2542
|
+
rerun: true
|
|
2543
|
+
};
|
|
2468
2544
|
await this.store.appendMessage({
|
|
2469
2545
|
threadId: input.threadId,
|
|
2470
2546
|
role: "user",
|
|
@@ -2480,7 +2556,10 @@ ${transcript}` : "",
|
|
|
2480
2556
|
if (thread !== null && (thread.title === "" || thread.title === "New chat")) {
|
|
2481
2557
|
await this.store.setTitle(input.threadId, titleOf(input.userText));
|
|
2482
2558
|
}
|
|
2483
|
-
return
|
|
2559
|
+
return {
|
|
2560
|
+
earlier: messages,
|
|
2561
|
+
rerun: false
|
|
2562
|
+
};
|
|
2484
2563
|
}
|
|
2485
2564
|
/** Rewind the session to before its last user message, so the regenerated answer replaces it. */
|
|
2486
2565
|
async revertLastExchange(client, sessionId) {
|
|
@@ -2696,6 +2775,7 @@ ${body}
|
|
|
2696
2775
|
// ---- prompt / observe / reply --------------------------------------------------------------
|
|
2697
2776
|
/** Start listening to the session (so nothing it says is missed), then send the user message. */
|
|
2698
2777
|
async prompt(runId, input, handle, spent) {
|
|
2778
|
+
await this.adopt(runId, input, handle, true);
|
|
2699
2779
|
const live = await this.ensureLive(runId, input, handle, false, spent);
|
|
2700
2780
|
try {
|
|
2701
2781
|
const prompt = await this.host.promptFor?.({
|
|
@@ -2738,7 +2818,24 @@ ${body}
|
|
|
2738
2818
|
* back from the session's messages, when the execution ends, what nobody heard after that.
|
|
2739
2819
|
*/
|
|
2740
2820
|
async observe(runId, input, handle, spent, progress) {
|
|
2821
|
+
const watch = await this.adopt(runId, input, handle, true, progress?.counts);
|
|
2741
2822
|
const live = await this.ensureLive(runId, input, handle, true, spent, progress);
|
|
2823
|
+
watch.parkedOn = void 0;
|
|
2824
|
+
watch.parkedEnd = void 0;
|
|
2825
|
+
watch.series = false;
|
|
2826
|
+
watch.observing = true;
|
|
2827
|
+
const wall = this.watchClock(watch);
|
|
2828
|
+
try {
|
|
2829
|
+
if (watch.pending !== void 0) await this.requestStop(runId, watch.pending);
|
|
2830
|
+
const milestone = this.marked(watch, await this.observeLive(runId, input, handle, live));
|
|
2831
|
+
if (milestone.kind === "ask") watch.parkedOn = milestone.ask.id;
|
|
2832
|
+
return milestone;
|
|
2833
|
+
} finally {
|
|
2834
|
+
watch.observing = false;
|
|
2835
|
+
if (wall !== void 0) clearTimeout(wall);
|
|
2836
|
+
}
|
|
2837
|
+
}
|
|
2838
|
+
async observeLive(runId, input, handle, live) {
|
|
2742
2839
|
const { turn, client } = live;
|
|
2743
2840
|
if (turn.hasMilestone()) return this.reached(runId, input, handle, await turn.next());
|
|
2744
2841
|
let done = false;
|
|
@@ -2778,19 +2875,64 @@ ${body}
|
|
|
2778
2875
|
* to go on.
|
|
2779
2876
|
*/
|
|
2780
2877
|
async reply(runId, input, handle, ask, reply, spent, progress) {
|
|
2878
|
+
const watch = await this.adopt(runId, input, handle, true, progress?.counts);
|
|
2879
|
+
watch.parkedOn = void 0;
|
|
2880
|
+
watch.parkedEnd = void 0;
|
|
2881
|
+
watch.series = false;
|
|
2882
|
+
const stopping = watch.pending !== void 0 || watch.stop !== void 0;
|
|
2883
|
+
const answer = stopping ? refusalOf(ask) : reply;
|
|
2781
2884
|
const server = await this.host.server(input.actor);
|
|
2782
2885
|
if (server.key !== handle.serverKey || server.bootId !== handle.bootId) {
|
|
2783
|
-
return this.
|
|
2886
|
+
if (stopping) return this.stopAfterRestart(watch, runId, input, handle, ask, answer, spent);
|
|
2887
|
+
return this.continueAfterRestart(runId, input, server, ask, answer, spent, handle.startedAt);
|
|
2784
2888
|
}
|
|
2785
2889
|
const live = await this.ensureLive(runId, input, handle, false, spent, progress);
|
|
2786
|
-
await live.turn.decide(ask,
|
|
2890
|
+
await live.turn.decide(ask, answer);
|
|
2787
2891
|
return handle;
|
|
2788
2892
|
}
|
|
2789
|
-
|
|
2893
|
+
/**
|
|
2894
|
+
* A stopping turn whose OpenCode server restarted while it waited: there is no session left to
|
|
2895
|
+
* abort, and none is opened or prompted. The ask is settled as refused and the turn ends stopped.
|
|
2896
|
+
*/
|
|
2897
|
+
async stopAfterRestart(watch, runId, input, handle, ask, answer, spent) {
|
|
2898
|
+
const live = await this.ensureLive(runId, input, handle, false, spent);
|
|
2899
|
+
await live.turn.decide(ask, answer, {
|
|
2900
|
+
tellOpenCode: false
|
|
2901
|
+
});
|
|
2902
|
+
const reason = watch.pending ?? watch.stop?.reason ?? "stopped";
|
|
2903
|
+
if (watch.stop === void 0) {
|
|
2904
|
+
const stop = {
|
|
2905
|
+
reason,
|
|
2906
|
+
counts: this.counts(watch)
|
|
2907
|
+
};
|
|
2908
|
+
watch.stop = stop;
|
|
2909
|
+
this.hook("onStop", {
|
|
2910
|
+
turnId: runId,
|
|
2911
|
+
reason,
|
|
2912
|
+
counts: stop.counts
|
|
2913
|
+
});
|
|
2914
|
+
}
|
|
2915
|
+
watch.aborted = true;
|
|
2916
|
+
watch.pending = void 0;
|
|
2917
|
+
live.turn.handle({
|
|
2918
|
+
type: "session.execution.interrupted",
|
|
2919
|
+
data: {
|
|
2920
|
+
sessionID: handle.sessionId
|
|
2921
|
+
}
|
|
2922
|
+
});
|
|
2923
|
+
return handle;
|
|
2924
|
+
}
|
|
2925
|
+
async continueAfterRestart(runId, input, server, ask, reply, spent, startedAt) {
|
|
2790
2926
|
const approved = "approved" in reply && reply.approved === true;
|
|
2791
2927
|
const note = ask.kind === "approval" ? approved ? `The user approved the action ${ask.action} you asked permission for. Continue the task.` : `The user did not approve the action ${ask.action}. Continue without it and explain.` : `The user answered your question: ${JSON.stringify("answers" in reply ? reply.answers : {})}. Continue.`;
|
|
2792
2928
|
const thread = await this.store.getThread(input.threadId);
|
|
2793
|
-
const
|
|
2929
|
+
const created = await this.createSession(runId, input, server, thread?.messages ?? [], note);
|
|
2930
|
+
const handle = {
|
|
2931
|
+
...created,
|
|
2932
|
+
...startedAt !== void 0 ? {
|
|
2933
|
+
startedAt
|
|
2934
|
+
} : {}
|
|
2935
|
+
};
|
|
2794
2936
|
await this.refreshInstructions(runId, input, server.client, handle.sessionId);
|
|
2795
2937
|
this.drop(runId);
|
|
2796
2938
|
const live = await this.ensureLive(runId, input, handle, false, spent);
|
|
@@ -2875,7 +3017,11 @@ ${body}
|
|
|
2875
3017
|
this.grants.set(runId, grants);
|
|
2876
3018
|
}, "onGranted")
|
|
2877
3019
|
});
|
|
2878
|
-
const
|
|
3020
|
+
const sessionId = handle.sessionId;
|
|
3021
|
+
const stop = await this.events.listen(server.key, server.client, sessionId, (event) => {
|
|
3022
|
+
turn.handle(event);
|
|
3023
|
+
this.sawEvent(runId, sessionId, event);
|
|
3024
|
+
});
|
|
2879
3025
|
const live = {
|
|
2880
3026
|
turn,
|
|
2881
3027
|
input,
|
|
@@ -2884,6 +3030,15 @@ ${body}
|
|
|
2884
3030
|
stop
|
|
2885
3031
|
};
|
|
2886
3032
|
this.live.set(runId, live);
|
|
3033
|
+
this.owners.set(ownerKey(handle), runId);
|
|
3034
|
+
const watch = this.watches.get(runId);
|
|
3035
|
+
if (watch !== void 0 && watch.handle.sessionId !== sessionId) {
|
|
3036
|
+
watch.handle = {
|
|
3037
|
+
...handle,
|
|
3038
|
+
startedAt: watch.startedAt
|
|
3039
|
+
};
|
|
3040
|
+
watch.ending = false;
|
|
3041
|
+
}
|
|
2887
3042
|
if (resumed) await turn.catchUp().catch(() => void 0);
|
|
2888
3043
|
return live;
|
|
2889
3044
|
}
|
|
@@ -2892,11 +3047,46 @@ ${body}
|
|
|
2892
3047
|
this.live.get(runId)?.stop();
|
|
2893
3048
|
this.live.delete(runId);
|
|
2894
3049
|
}
|
|
2895
|
-
/**
|
|
3050
|
+
/**
|
|
3051
|
+
* Stop following a run and drop the approvals it was granted: it is over. Returns the turn this
|
|
3052
|
+
* process executed, now settled (no stop reaches it any more), for its `onTurnEnd`.
|
|
3053
|
+
*/
|
|
2896
3054
|
forget(runId) {
|
|
2897
3055
|
this.drop(runId);
|
|
3056
|
+
this.releasedUsage.delete(runId);
|
|
2898
3057
|
for (const grant of this.grants.get(runId) ?? []) this.spent.delete(grant.id);
|
|
2899
3058
|
this.grants.delete(runId);
|
|
3059
|
+
const watch = this.watches.get(runId);
|
|
3060
|
+
if (watch === void 0) return void 0;
|
|
3061
|
+
watch.settled = true;
|
|
3062
|
+
this.watches.delete(runId);
|
|
3063
|
+
for (const [key, owner] of this.owners) if (owner === runId) this.owners.delete(key);
|
|
3064
|
+
return watch;
|
|
3065
|
+
}
|
|
3066
|
+
/**
|
|
3067
|
+
* A run that ended without settling here (cancelled while this process executed it; whoever
|
|
3068
|
+
* cancelled settled it): stop following it, and end the turn this process executed.
|
|
3069
|
+
*/
|
|
3070
|
+
async abandon(runId) {
|
|
3071
|
+
const watch = this.forget(runId);
|
|
3072
|
+
if (watch !== void 0) this.ended(watch, "interrupted", void 0);
|
|
3073
|
+
}
|
|
3074
|
+
/**
|
|
3075
|
+
* Let go of a turn this process executed without settling it here: it was cancelled while parked
|
|
3076
|
+
* (its session ended under it, `interrupted`), another worker took it over, or this process shuts
|
|
3077
|
+
* down (`released`). The per-turn state goes, `onTurnEnd` fires once, and a later stop of the turn
|
|
3078
|
+
* here is ignored. Nothing settles the run here afterwards: whoever answered or cancelled it does.
|
|
3079
|
+
*/
|
|
3080
|
+
release(runId, status) {
|
|
3081
|
+
const watch = this.watches.get(runId);
|
|
3082
|
+
if (watch === void 0) return;
|
|
3083
|
+
const seen = this.live.get(runId)?.turn.totalUsage();
|
|
3084
|
+
if (seen !== void 0) this.keepReleasedUsage(runId, seen);
|
|
3085
|
+
this.drop(runId);
|
|
3086
|
+
watch.settled = true;
|
|
3087
|
+
this.watches.delete(runId);
|
|
3088
|
+
for (const [key, owner] of this.owners) if (owner === runId) this.owners.delete(key);
|
|
3089
|
+
this.ended(watch, status, void 0);
|
|
2900
3090
|
}
|
|
2901
3091
|
// ---- settle --------------------------------------------------------------------------------
|
|
2902
3092
|
/** A person was asked something: tell the host (cards in other channels, wake-ups). */
|
|
@@ -2969,7 +3159,7 @@ ${body}
|
|
|
2969
3159
|
* two milestones.
|
|
2970
3160
|
*/
|
|
2971
3161
|
spentBy(runId, spent) {
|
|
2972
|
-
const seen = this.live.get(runId)?.turn.totalUsage();
|
|
3162
|
+
const seen = this.live.get(runId)?.turn.totalUsage() ?? this.releasedUsage.get(runId);
|
|
2973
3163
|
if (spent === void 0) return seen ?? emptyUsage();
|
|
2974
3164
|
if (seen === void 0) return spent;
|
|
2975
3165
|
const tokens = /* @__PURE__ */ __name((u) => u.inputTokens + u.outputTokens, "tokens");
|
|
@@ -2979,13 +3169,13 @@ ${body}
|
|
|
2979
3169
|
* End the run's stream and bookkeeping for how its execution ended. `spent` is what the run spent
|
|
2980
3170
|
* (the sum of its milestones' `usage`); omit → what the turn this process follows saw.
|
|
2981
3171
|
*/
|
|
2982
|
-
async settle(runId, input,
|
|
3172
|
+
async settle(runId, input, ended, durationMs, spent) {
|
|
3173
|
+
const outcome = this.withStop(ended, this.watches.get(runId));
|
|
2983
3174
|
if (outcome.status === "failed") {
|
|
2984
|
-
|
|
2985
|
-
return;
|
|
3175
|
+
return this.settleFailed(runId, input, outcome.error, durationMs, spent, outcome.stop);
|
|
2986
3176
|
}
|
|
2987
3177
|
const usage = this.spentBy(runId, spent);
|
|
2988
|
-
this.forget(runId);
|
|
3178
|
+
const watch = this.forget(runId);
|
|
2989
3179
|
const writer = await this.sink.open(runId);
|
|
2990
3180
|
const result = await this.runResult(runId, input, outcome, durationMs, usage);
|
|
2991
3181
|
await this.amend(writer, result);
|
|
@@ -3020,22 +3210,30 @@ ${body}
|
|
|
3020
3210
|
});
|
|
3021
3211
|
}
|
|
3022
3212
|
await this.settled(result);
|
|
3213
|
+
if (watch !== void 0) this.ended(watch, outcome.status, outcome.status === "succeeded" ? outcome.stop : void 0);
|
|
3214
|
+
return outcome;
|
|
3023
3215
|
}
|
|
3024
|
-
async settleFailed(runId, input, error, durationMs = 0, spent) {
|
|
3216
|
+
async settleFailed(runId, input, error, durationMs = 0, spent, stop) {
|
|
3025
3217
|
const usage = this.spentBy(runId, spent);
|
|
3026
|
-
this.forget(runId);
|
|
3218
|
+
const watch = this.forget(runId);
|
|
3219
|
+
const stopped = stop ?? watch?.stop;
|
|
3027
3220
|
const writer = await this.sink.open(runId);
|
|
3028
3221
|
const result = await this.runResult(runId, input, {
|
|
3029
3222
|
status: "failed",
|
|
3030
|
-
error
|
|
3223
|
+
error,
|
|
3224
|
+
...stopped !== void 0 ? {
|
|
3225
|
+
stop: stopped
|
|
3226
|
+
} : {}
|
|
3031
3227
|
}, durationMs, usage);
|
|
3032
3228
|
const worded = (await this.amend(writer, result)).error;
|
|
3033
|
-
const message = worded ?? streamFailure(new Error(error)).message;
|
|
3034
|
-
|
|
3229
|
+
const message = worded ?? (stopped !== void 0 ? error : streamFailure(new Error(error)).message);
|
|
3230
|
+
const code = stopped !== void 0 ? STOPPED_ERROR_CODE : "opencode_failed";
|
|
3231
|
+
if (stopped !== void 0) this.logger.log(`agent run ${runId} stopped: ${stopped.reason}`);
|
|
3232
|
+
else this.logger.error(`agent run ${runId} failed: ${error}`);
|
|
3035
3233
|
publishAgentRunFailed({
|
|
3036
3234
|
runId,
|
|
3037
3235
|
threadId: input.threadId,
|
|
3038
|
-
code
|
|
3236
|
+
code,
|
|
3039
3237
|
message: error,
|
|
3040
3238
|
durationMs,
|
|
3041
3239
|
context: agentRunContext(input)
|
|
@@ -3044,23 +3242,370 @@ ${body}
|
|
|
3044
3242
|
runId,
|
|
3045
3243
|
status: "failed",
|
|
3046
3244
|
durationMs,
|
|
3047
|
-
errorCode:
|
|
3245
|
+
errorCode: code,
|
|
3048
3246
|
errorMessage: error
|
|
3049
3247
|
});
|
|
3050
3248
|
await Promise.resolve(this.store.failUnsettledToolCalls?.(runId, RUN_ENDED_BEFORE_TOOL_CALL)).catch(() => 0);
|
|
3051
3249
|
await this.handoff(writer, input, runId, "failed", error);
|
|
3052
3250
|
await releaseThreadRun(this.store, input.threadId, runId);
|
|
3053
3251
|
await writer.fail({
|
|
3054
|
-
code
|
|
3252
|
+
code,
|
|
3055
3253
|
message
|
|
3056
3254
|
});
|
|
3057
3255
|
await this.settled({
|
|
3058
3256
|
...result,
|
|
3059
3257
|
outcome: {
|
|
3060
3258
|
status: "failed",
|
|
3061
|
-
error: worded ?? error
|
|
3259
|
+
error: worded ?? error,
|
|
3260
|
+
...stopped !== void 0 ? {
|
|
3261
|
+
stop: stopped
|
|
3262
|
+
} : {}
|
|
3062
3263
|
}
|
|
3063
3264
|
});
|
|
3265
|
+
if (watch !== void 0) this.ended(watch, "failed", stopped);
|
|
3266
|
+
return {
|
|
3267
|
+
status: "failed",
|
|
3268
|
+
error,
|
|
3269
|
+
...stopped !== void 0 ? {
|
|
3270
|
+
stop: stopped
|
|
3271
|
+
} : {}
|
|
3272
|
+
};
|
|
3273
|
+
}
|
|
3274
|
+
// ---- limits, stops and the turn's lifecycle -------------------------------------------------
|
|
3275
|
+
/**
|
|
3276
|
+
* Stop a turn's own work: abort its session so OpenCode starts no new round, only while the turn
|
|
3277
|
+
* still owns the session (a thread's session is reused by its next turn) — checked immediately
|
|
3278
|
+
* before the abort. Acts in the process executing the turn; see {@link OpenCodeStopResult}. The
|
|
3279
|
+
* turn ends failed with `outcome.stop` (code `opencode_stopped`), and `onStop` is called.
|
|
3280
|
+
*/
|
|
3281
|
+
stopTurn(turnId, reason) {
|
|
3282
|
+
return this.requestStop(turnId, reason);
|
|
3283
|
+
}
|
|
3284
|
+
/** The turn this process executes, made known here on its first step in this process. */
|
|
3285
|
+
async adopt(runId, input, handle, resumed, counts, startedAt) {
|
|
3286
|
+
const known = this.watches.get(runId);
|
|
3287
|
+
if (known !== void 0) return known;
|
|
3288
|
+
const watch = {
|
|
3289
|
+
runId,
|
|
3290
|
+
input,
|
|
3291
|
+
handle,
|
|
3292
|
+
limits: this.limitsFor(input),
|
|
3293
|
+
startedAt: handle.startedAt ?? startedAt ?? Date.now(),
|
|
3294
|
+
steps: counts?.steps ?? 0,
|
|
3295
|
+
compactions: counts?.compactions ?? 0,
|
|
3296
|
+
observing: false,
|
|
3297
|
+
ending: false,
|
|
3298
|
+
settled: false,
|
|
3299
|
+
aborted: false,
|
|
3300
|
+
checking: false,
|
|
3301
|
+
lastRead: 0
|
|
3302
|
+
};
|
|
3303
|
+
this.watches.set(runId, watch);
|
|
3304
|
+
await this.hook("onTurnStart", {
|
|
3305
|
+
turnId: runId,
|
|
3306
|
+
threadId: input.threadId,
|
|
3307
|
+
sessionId: handle.sessionId,
|
|
3308
|
+
resumed
|
|
3309
|
+
});
|
|
3310
|
+
return watch;
|
|
3311
|
+
}
|
|
3312
|
+
limitsFor(input) {
|
|
3313
|
+
const limits = this.settings.limits;
|
|
3314
|
+
return cleanLimits(typeof limits === "function" ? limits(input) : limits);
|
|
3315
|
+
}
|
|
3316
|
+
/**
|
|
3317
|
+
* One event of the turn's session: counted for the limits, and told to `onTurnEvent`. While the
|
|
3318
|
+
* turn is parked on a person here, it may also say the turn went on without this process
|
|
3319
|
+
* ({@link wentOnElsewhere}).
|
|
3320
|
+
*/
|
|
3321
|
+
sawEvent(runId, sessionId, event) {
|
|
3322
|
+
if (sessionOf(event) !== sessionId) return;
|
|
3323
|
+
const watch = this.watches.get(runId);
|
|
3324
|
+
if (watch === void 0 || watch.settled || watch.handle.sessionId !== sessionId) return;
|
|
3325
|
+
if (watch.parkedOn !== void 0 && this.wentOnElsewhere(watch, event)) return;
|
|
3326
|
+
let reached;
|
|
3327
|
+
if (event.type.startsWith("session.execution.")) watch.ending = true;
|
|
3328
|
+
else if (event.type === "session.step.started") {
|
|
3329
|
+
watch.steps += 1;
|
|
3330
|
+
if (passed(watch.steps, watch.limits.steps)) reached = "steps";
|
|
3331
|
+
} else if (event.type === "session.compaction.started") {
|
|
3332
|
+
watch.compactions += 1;
|
|
3333
|
+
if (passed(watch.compactions, watch.limits.compactions)) reached = "compactions";
|
|
3334
|
+
}
|
|
3335
|
+
this.hook("onTurnEvent", {
|
|
3336
|
+
turnId: runId,
|
|
3337
|
+
type: event.type,
|
|
3338
|
+
sessionId
|
|
3339
|
+
});
|
|
3340
|
+
if (reached !== void 0) void this.limitStop(runId, reached);
|
|
3341
|
+
}
|
|
3342
|
+
/**
|
|
3343
|
+
* Whether a turn parked here went on without this process, which then lets it go. While a tool of
|
|
3344
|
+
* the step waits on a person, OpenCode keeps running the step's other tools, so their events say
|
|
3345
|
+
* nothing. What does: a new round (`session.step.started`, which cannot begin while the step
|
|
3346
|
+
* waits), or the store showing the parked ask decided by someone else (another worker answered
|
|
3347
|
+
* it, or a cancel elsewhere settled it) — this process clears the park before it answers. When
|
|
3348
|
+
* the session's execution ends while parked, the store is asked again for a while, as a cancel
|
|
3349
|
+
* elsewhere settles the ask just after it interrupted the session; with the ask still open, the
|
|
3350
|
+
* turn stays here for its answer.
|
|
3351
|
+
*/
|
|
3352
|
+
wentOnElsewhere(watch, event) {
|
|
3353
|
+
if (event.type === "session.step.started") {
|
|
3354
|
+
this.release(watch.runId, "released");
|
|
3355
|
+
return true;
|
|
3356
|
+
}
|
|
3357
|
+
const ended = event.type.startsWith("session.execution.");
|
|
3358
|
+
if (ended) watch.parkedEnd = event.type === "session.execution.interrupted" ? "interrupted" : "released";
|
|
3359
|
+
this.checkParked(watch, ended);
|
|
3360
|
+
return false;
|
|
3361
|
+
}
|
|
3362
|
+
/**
|
|
3363
|
+
* Ask the store whether the parked ask was decided elsewhere, and let the turn go when it was —
|
|
3364
|
+
* one read at a time, at most one every {@link PARKED_READ_GAP_MS}. `series`: the session's
|
|
3365
|
+
* execution ended, so the store is asked again over {@link parkedRechecksMs}. An execution that
|
|
3366
|
+
* was interrupted under a parked turn means a cancel elsewhere: when the store cannot say (it has
|
|
3367
|
+
* no `toolCallApproval`, the read fails, the ask is not on file) past the first recheck, or the
|
|
3368
|
+
* rechecks run out with the ask still open, the turn is let go as `interrupted` anyway.
|
|
3369
|
+
*/
|
|
3370
|
+
checkParked(watch, series) {
|
|
3371
|
+
if (series) watch.series = true;
|
|
3372
|
+
if (watch.checking) {
|
|
3373
|
+
watch.again = true;
|
|
3374
|
+
return;
|
|
3375
|
+
}
|
|
3376
|
+
watch.checking = true;
|
|
3377
|
+
void this.runParkedChecks(watch);
|
|
3378
|
+
}
|
|
3379
|
+
/**
|
|
3380
|
+
* The checks of {@link checkParked}. `checking` is cleared in the same step the loop returns in,
|
|
3381
|
+
* so a check asked for afterwards starts a new loop; one asked for while it ran (`again`) but not
|
|
3382
|
+
* read because the loop returned starts one too.
|
|
3383
|
+
*/
|
|
3384
|
+
async runParkedChecks(watch) {
|
|
3385
|
+
try {
|
|
3386
|
+
await this.parkedChecks(watch);
|
|
3387
|
+
} finally {
|
|
3388
|
+
watch.checking = false;
|
|
3389
|
+
if (watch.again && !watch.settled && watch.parkedOn !== void 0) {
|
|
3390
|
+
watch.checking = true;
|
|
3391
|
+
void this.runParkedChecks(watch);
|
|
3392
|
+
}
|
|
3393
|
+
}
|
|
3394
|
+
}
|
|
3395
|
+
async parkedChecks(watch) {
|
|
3396
|
+
const ask = watch.parkedOn;
|
|
3397
|
+
const gone = /* @__PURE__ */ __name(() => watch.settled || ask === void 0 || watch.parkedOn !== ask, "gone");
|
|
3398
|
+
let step = 0;
|
|
3399
|
+
for (; ; ) {
|
|
3400
|
+
watch.again = false;
|
|
3401
|
+
const wait = watch.lastRead + PARKED_READ_GAP_MS - Date.now();
|
|
3402
|
+
if (wait > 0) await sleep(wait);
|
|
3403
|
+
if (gone()) return;
|
|
3404
|
+
const answer = await this.parkedAsk(ask);
|
|
3405
|
+
watch.lastRead = Date.now();
|
|
3406
|
+
if (gone()) return;
|
|
3407
|
+
if (answer === "decided") {
|
|
3408
|
+
this.release(watch.runId, watch.parkedEnd ?? "released");
|
|
3409
|
+
return;
|
|
3410
|
+
}
|
|
3411
|
+
if (watch.series) {
|
|
3412
|
+
const cancelled = watch.parkedEnd === "interrupted";
|
|
3413
|
+
if (cancelled && answer === "unknown" && step > 0) {
|
|
3414
|
+
this.release(watch.runId, "interrupted");
|
|
3415
|
+
return;
|
|
3416
|
+
}
|
|
3417
|
+
const gap = this.parkedRechecksMs[step];
|
|
3418
|
+
step += 1;
|
|
3419
|
+
if (gap === void 0) {
|
|
3420
|
+
if (cancelled) this.release(watch.runId, "interrupted");
|
|
3421
|
+
return;
|
|
3422
|
+
}
|
|
3423
|
+
await sleep(gap);
|
|
3424
|
+
continue;
|
|
3425
|
+
}
|
|
3426
|
+
if (!watch.again) return;
|
|
3427
|
+
}
|
|
3428
|
+
}
|
|
3429
|
+
/** What the store says of an ask: decided, still open, or nothing it can tell. */
|
|
3430
|
+
async parkedAsk(id) {
|
|
3431
|
+
if (this.store.toolCallApproval === void 0) return "unknown";
|
|
3432
|
+
try {
|
|
3433
|
+
const state = await this.store.toolCallApproval(id);
|
|
3434
|
+
if (state == null) return "unknown";
|
|
3435
|
+
return state.status === "pending_approval" ? "open" : "decided";
|
|
3436
|
+
} catch {
|
|
3437
|
+
return "unknown";
|
|
3438
|
+
}
|
|
3439
|
+
}
|
|
3440
|
+
/**
|
|
3441
|
+
* The waits between the store's rereads of a parked ask after its execution ended (cumulative
|
|
3442
|
+
* 0.25, 1.25, 4.25, 14.25 and 44.25 seconds after the first). Overridden by tests only.
|
|
3443
|
+
*
|
|
3444
|
+
* @internal
|
|
3445
|
+
*/
|
|
3446
|
+
parkedRechecksMs = PARKED_RECHECKS_MS;
|
|
3447
|
+
/** A limit's stop: what it did is the engine's to log, there is no caller to tell. */
|
|
3448
|
+
async limitStop(runId, reason) {
|
|
3449
|
+
const result = await this.requestStop(runId, reason);
|
|
3450
|
+
if (result === "unconfirmed") this.logger.warn(`run ${runId}: passed its ${reason} limit, but its owner could not be read`);
|
|
3451
|
+
}
|
|
3452
|
+
/** The wall-clock bound of an observation: a timer for what is left of it, if anything. */
|
|
3453
|
+
watchClock(watch) {
|
|
3454
|
+
const max = watch.limits.wallClockMs;
|
|
3455
|
+
if (max === void 0) return void 0;
|
|
3456
|
+
const left = watch.startedAt + max - Date.now();
|
|
3457
|
+
if (left <= 0) {
|
|
3458
|
+
watch.pending ??= "wall-clock";
|
|
3459
|
+
return void 0;
|
|
3460
|
+
}
|
|
3461
|
+
const timer = setTimeout(() => void this.limitStop(watch.runId, "wall-clock"), left);
|
|
3462
|
+
timer.unref?.();
|
|
3463
|
+
return timer;
|
|
3464
|
+
}
|
|
3465
|
+
counts(watch) {
|
|
3466
|
+
return {
|
|
3467
|
+
steps: watch.steps,
|
|
3468
|
+
compactions: watch.compactions,
|
|
3469
|
+
elapsedMs: Date.now() - watch.startedAt
|
|
3470
|
+
};
|
|
3471
|
+
}
|
|
3472
|
+
async requestStop(runId, reason) {
|
|
3473
|
+
const watch = this.watches.get(runId);
|
|
3474
|
+
if (watch === void 0 || watch.settled || watch.stop !== void 0) return "ignored";
|
|
3475
|
+
const live = this.live.get(runId);
|
|
3476
|
+
if (watch.parkedOn !== void 0) {
|
|
3477
|
+
watch.pending ??= reason;
|
|
3478
|
+
return "pending";
|
|
3479
|
+
}
|
|
3480
|
+
if (watch.ending || live?.turn.finished === true) return this.landed(watch, reason);
|
|
3481
|
+
if (!watch.observing || live === void 0) {
|
|
3482
|
+
watch.pending ??= reason;
|
|
3483
|
+
return "pending";
|
|
3484
|
+
}
|
|
3485
|
+
watch.pending = void 0;
|
|
3486
|
+
let active;
|
|
3487
|
+
try {
|
|
3488
|
+
active = await this.activeRun(watch.input.threadId);
|
|
3489
|
+
} catch (error) {
|
|
3490
|
+
this.logger.warn(`run ${runId}: could not read the thread's run: ${errorText(error, "error")}`);
|
|
3491
|
+
return "unconfirmed";
|
|
3492
|
+
}
|
|
3493
|
+
if (watch.settled || watch.stop !== void 0 || this.live.get(runId) !== live) return "ignored";
|
|
3494
|
+
if (active !== null && active !== runId) return "ignored";
|
|
3495
|
+
if (this.owners.get(ownerKey(watch.handle)) !== runId) return "ignored";
|
|
3496
|
+
if (watch.ending || live.turn.finished) return this.landed(watch, reason);
|
|
3497
|
+
const stop = {
|
|
3498
|
+
reason,
|
|
3499
|
+
counts: this.counts(watch)
|
|
3500
|
+
};
|
|
3501
|
+
watch.stop = stop;
|
|
3502
|
+
watch.aborted = true;
|
|
3503
|
+
try {
|
|
3504
|
+
await live.client.session.interrupt({
|
|
3505
|
+
sessionID: watch.handle.sessionId
|
|
3506
|
+
});
|
|
3507
|
+
} catch (error) {
|
|
3508
|
+
this.logger.warn(`run ${runId}: could not abort the session: ${errorText(error, "error")}`);
|
|
3509
|
+
}
|
|
3510
|
+
this.hook("onStop", {
|
|
3511
|
+
turnId: runId,
|
|
3512
|
+
reason,
|
|
3513
|
+
counts: stop.counts
|
|
3514
|
+
});
|
|
3515
|
+
return "aborted";
|
|
3516
|
+
}
|
|
3517
|
+
/** A stop that arrived while the answer settles: recorded and reported, the answer kept. */
|
|
3518
|
+
landed(watch, reason) {
|
|
3519
|
+
const stop = {
|
|
3520
|
+
reason,
|
|
3521
|
+
counts: this.counts(watch)
|
|
3522
|
+
};
|
|
3523
|
+
watch.stop = stop;
|
|
3524
|
+
watch.pending = void 0;
|
|
3525
|
+
this.hook("onStop", {
|
|
3526
|
+
turnId: watch.runId,
|
|
3527
|
+
reason,
|
|
3528
|
+
counts: stop.counts
|
|
3529
|
+
});
|
|
3530
|
+
return "settling";
|
|
3531
|
+
}
|
|
3532
|
+
/** The milestone as the runner journals it: the limits' counts on an ask, the stop on an end. */
|
|
3533
|
+
marked(watch, milestone) {
|
|
3534
|
+
if (milestone.kind === "finished") return {
|
|
3535
|
+
...milestone,
|
|
3536
|
+
outcome: this.withStop(milestone.outcome, watch)
|
|
3537
|
+
};
|
|
3538
|
+
if (milestone.progress === void 0) return milestone;
|
|
3539
|
+
const counts = {
|
|
3540
|
+
steps: watch.steps,
|
|
3541
|
+
compactions: watch.compactions
|
|
3542
|
+
};
|
|
3543
|
+
return {
|
|
3544
|
+
...milestone,
|
|
3545
|
+
progress: {
|
|
3546
|
+
...milestone.progress,
|
|
3547
|
+
counts
|
|
3548
|
+
}
|
|
3549
|
+
};
|
|
3550
|
+
}
|
|
3551
|
+
/**
|
|
3552
|
+
* How a turn a stop landed on ends. An answer that settled anyway is kept, with the stop on it; an
|
|
3553
|
+
* execution that failed on its own keeps its error, with the stop on it; an execution the stop
|
|
3554
|
+
* aborted ends failed with the stop's own text. One interrupted by a person's cancel stays so.
|
|
3555
|
+
*/
|
|
3556
|
+
withStop(outcome, watch) {
|
|
3557
|
+
const stop = watch?.stop;
|
|
3558
|
+
if (stop === void 0) return outcome;
|
|
3559
|
+
if (outcome.status !== "interrupted") return outcome.stop !== void 0 ? outcome : {
|
|
3560
|
+
...outcome,
|
|
3561
|
+
stop
|
|
3562
|
+
};
|
|
3563
|
+
if (!watch?.aborted) return outcome;
|
|
3564
|
+
return {
|
|
3565
|
+
status: "failed",
|
|
3566
|
+
error: stopErrorText(stop),
|
|
3567
|
+
stop
|
|
3568
|
+
};
|
|
3569
|
+
}
|
|
3570
|
+
/** `onTurnEnd`, once per turn this process executed; not waited for. */
|
|
3571
|
+
ended(watch, status, stop) {
|
|
3572
|
+
this.hook("onTurnEnd", {
|
|
3573
|
+
turnId: watch.runId,
|
|
3574
|
+
status,
|
|
3575
|
+
...stop !== void 0 ? {
|
|
3576
|
+
stop
|
|
3577
|
+
} : {}
|
|
3578
|
+
});
|
|
3579
|
+
}
|
|
3580
|
+
/**
|
|
3581
|
+
* A lifecycle hook, from the settings and the host, called at once. The promise settles when both
|
|
3582
|
+
* have, or after `hookTimeoutMs` (default 5 s) — only `onTurnStart` is waited for. Errors are
|
|
3583
|
+
* logged, never the turn's.
|
|
3584
|
+
*/
|
|
3585
|
+
hook(name, event) {
|
|
3586
|
+
const calls = [];
|
|
3587
|
+
for (const target of [
|
|
3588
|
+
this.settings,
|
|
3589
|
+
this.host
|
|
3590
|
+
]) {
|
|
3591
|
+
const fn = target[name];
|
|
3592
|
+
if (typeof fn !== "function") continue;
|
|
3593
|
+
calls.push(Promise.resolve().then(() => fn.call(target, event)).catch((error) => this.logger.warn(`${name} failed: ${errorText(error, "error")}`)));
|
|
3594
|
+
}
|
|
3595
|
+
if (calls.length === 0) return Promise.resolve();
|
|
3596
|
+
const ms = this.settings.hookTimeoutMs ?? DEFAULT_HOOK_TIMEOUT_MS;
|
|
3597
|
+
let timer;
|
|
3598
|
+
const late = new Promise((resolve) => {
|
|
3599
|
+
timer = setTimeout(() => {
|
|
3600
|
+
this.logger.warn(`${name} took longer than ${ms}ms; the turn went on`);
|
|
3601
|
+
resolve();
|
|
3602
|
+
}, ms);
|
|
3603
|
+
timer.unref?.();
|
|
3604
|
+
});
|
|
3605
|
+
return Promise.race([
|
|
3606
|
+
Promise.all(calls).then(() => void 0),
|
|
3607
|
+
late
|
|
3608
|
+
]).finally(() => clearTimeout(timer));
|
|
3064
3609
|
}
|
|
3065
3610
|
/** Starts the next queued message of the thread; set by the runner (it owns `start`). */
|
|
3066
3611
|
startNext;
|
|
@@ -3123,6 +3668,28 @@ OpenCodeTurns = _ts_decorate2([
|
|
|
3123
3668
|
Object
|
|
3124
3669
|
])
|
|
3125
3670
|
], OpenCodeTurns);
|
|
3671
|
+
function passed(count, limit) {
|
|
3672
|
+
return limit !== void 0 && count > limit;
|
|
3673
|
+
}
|
|
3674
|
+
__name(passed, "passed");
|
|
3675
|
+
function sleep(ms) {
|
|
3676
|
+
return new Promise((resolve) => setTimeout(resolve, ms).unref?.());
|
|
3677
|
+
}
|
|
3678
|
+
__name(sleep, "sleep");
|
|
3679
|
+
function refusalOf(ask) {
|
|
3680
|
+
return ask.kind === "approval" ? {
|
|
3681
|
+
approved: false,
|
|
3682
|
+
reason: "The turn was stopped."
|
|
3683
|
+
} : {
|
|
3684
|
+
answers: {},
|
|
3685
|
+
skipped: true
|
|
3686
|
+
};
|
|
3687
|
+
}
|
|
3688
|
+
__name(refusalOf, "refusalOf");
|
|
3689
|
+
function ownerKey(handle) {
|
|
3690
|
+
return `${handle.serverKey}|${handle.sessionId}`;
|
|
3691
|
+
}
|
|
3692
|
+
__name(ownerKey, "ownerKey");
|
|
3126
3693
|
function titleOf(text) {
|
|
3127
3694
|
const trimmed = text.trim().replace(/\s+/g, " ");
|
|
3128
3695
|
return trimmed.length > 60 ? `${trimmed.slice(0, 57)}...` : trimmed || "New chat";
|
|
@@ -3296,7 +3863,7 @@ var OpenCodeAgentRunner = class {
|
|
|
3296
3863
|
await this.turns.interrupt(input).catch(() => void 0);
|
|
3297
3864
|
await this.turns.settle(runId, input, {
|
|
3298
3865
|
status: "interrupted"
|
|
3299
|
-
}, Date.now() - started);
|
|
3866
|
+
}, Date.now() - started, spent);
|
|
3300
3867
|
return;
|
|
3301
3868
|
}
|
|
3302
3869
|
await this.turns.settleFailed(runId, input, errorText(error, "the turn failed"), Date.now() - started);
|
|
@@ -3452,6 +4019,7 @@ export {
|
|
|
3452
4019
|
OpenCodeToolsTokens,
|
|
3453
4020
|
OpenCodeTurn,
|
|
3454
4021
|
OpenCodeTurns,
|
|
4022
|
+
STOPPED_ERROR_CODE,
|
|
3455
4023
|
addUsage,
|
|
3456
4024
|
emptyUsage,
|
|
3457
4025
|
keyValueOpenCodeSessionStore,
|