@dudousxd/nestjs-agent-opencode 1.0.0 → 1.1.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 +613 -39
- 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 +613 -39
- 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 +4 -4
package/dist/index.cjs
CHANGED
|
@@ -39,6 +39,7 @@ __export(index_exports, {
|
|
|
39
39
|
OpenCodeToolsTokens: () => OpenCodeToolsTokens,
|
|
40
40
|
OpenCodeTurn: () => OpenCodeTurn,
|
|
41
41
|
OpenCodeTurns: () => OpenCodeTurns,
|
|
42
|
+
STOPPED_ERROR_CODE: () => STOPPED_ERROR_CODE,
|
|
42
43
|
addUsage: () => addUsage,
|
|
43
44
|
emptyUsage: () => emptyUsage,
|
|
44
45
|
keyValueOpenCodeSessionStore: () => keyValueOpenCodeSessionStore,
|
|
@@ -2051,6 +2052,37 @@ var OpenCodeEventHub = class _OpenCodeEventHub {
|
|
|
2051
2052
|
}
|
|
2052
2053
|
};
|
|
2053
2054
|
|
|
2055
|
+
// src/turn-limits.ts
|
|
2056
|
+
var STOPPED_ERROR_CODE = "opencode_stopped";
|
|
2057
|
+
function stopErrorText(stop) {
|
|
2058
|
+
switch (stop.reason) {
|
|
2059
|
+
case "steps":
|
|
2060
|
+
return `The turn was stopped after ${stop.counts.steps - 1} steps, its limit.`;
|
|
2061
|
+
case "compactions":
|
|
2062
|
+
return `The turn was stopped after ${stop.counts.compactions - 1} compactions, its limit.`;
|
|
2063
|
+
case "wall-clock":
|
|
2064
|
+
return "The turn was stopped: it ran longer than allowed.";
|
|
2065
|
+
default:
|
|
2066
|
+
return "The turn was stopped.";
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
2069
|
+
__name(stopErrorText, "stopErrorText");
|
|
2070
|
+
function cleanLimits(limits) {
|
|
2071
|
+
const ok = /* @__PURE__ */ __name((n) => typeof n === "number" && Number.isFinite(n) && n >= 0, "ok");
|
|
2072
|
+
return {
|
|
2073
|
+
...ok(limits?.steps) ? {
|
|
2074
|
+
steps: limits.steps
|
|
2075
|
+
} : {},
|
|
2076
|
+
...ok(limits?.compactions) ? {
|
|
2077
|
+
compactions: limits.compactions
|
|
2078
|
+
} : {},
|
|
2079
|
+
...ok(limits?.wallClockMs) ? {
|
|
2080
|
+
wallClockMs: limits.wallClockMs
|
|
2081
|
+
} : {}
|
|
2082
|
+
};
|
|
2083
|
+
}
|
|
2084
|
+
__name(cleanLimits, "cleanLimits");
|
|
2085
|
+
|
|
2054
2086
|
// src/turns.ts
|
|
2055
2087
|
function _ts_decorate2(decorators, target, key, desc) {
|
|
2056
2088
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
@@ -2080,6 +2112,16 @@ function _ts_param2(paramIndex, decorator) {
|
|
|
2080
2112
|
__name(_ts_param2, "_ts_param");
|
|
2081
2113
|
var DEFAULT_TURN_TIMEOUT_MS = 30 * 6e4;
|
|
2082
2114
|
var DEFAULT_TOOLS_TTL_MS = 7 * 24 * 60 * 6e4;
|
|
2115
|
+
var DEFAULT_HOOK_TIMEOUT_MS = 5e3;
|
|
2116
|
+
var PARKED_RECHECKS_MS = [
|
|
2117
|
+
250,
|
|
2118
|
+
1e3,
|
|
2119
|
+
3e3,
|
|
2120
|
+
1e4,
|
|
2121
|
+
3e4
|
|
2122
|
+
];
|
|
2123
|
+
var PARKED_READ_GAP_MS = 250;
|
|
2124
|
+
var RELEASED_USAGE_MS = 10 * 6e4;
|
|
2083
2125
|
var OpenCodeTurns = class {
|
|
2084
2126
|
static {
|
|
2085
2127
|
__name(this, "OpenCodeTurns");
|
|
@@ -2103,6 +2145,21 @@ var OpenCodeTurns = class {
|
|
|
2103
2145
|
spent = /* @__PURE__ */ new Set();
|
|
2104
2146
|
/** When the tools endpoint was last registered for a location (`server|boot|directory|actor`). */
|
|
2105
2147
|
toolsIssued = /* @__PURE__ */ new Map();
|
|
2148
|
+
/**
|
|
2149
|
+
* What a turn let go of here had spent, for a settle that follows here (a cancel in this process
|
|
2150
|
+
* that settles after the turn was released). Dropped when the run settles here, or after
|
|
2151
|
+
* {@link RELEASED_USAGE_MS} when it settles elsewhere, so no run is kept for good.
|
|
2152
|
+
*/
|
|
2153
|
+
releasedUsage = /* @__PURE__ */ new Map();
|
|
2154
|
+
keepReleasedUsage(runId, usage) {
|
|
2155
|
+
this.releasedUsage.set(runId, usage);
|
|
2156
|
+
setTimeout(() => {
|
|
2157
|
+
if (this.releasedUsage.get(runId) === usage) this.releasedUsage.delete(runId);
|
|
2158
|
+
}, RELEASED_USAGE_MS).unref?.();
|
|
2159
|
+
}
|
|
2160
|
+
/** The turns this process executes, and which turn owns each session (`server|session`). */
|
|
2161
|
+
watches = /* @__PURE__ */ new Map();
|
|
2162
|
+
owners = /* @__PURE__ */ new Map();
|
|
2106
2163
|
constructor(host, sessions, settings, store, sink, deps, registry, skills, memory, queue, toolsTokens2, pricingStore, agentOptions) {
|
|
2107
2164
|
this.host = host;
|
|
2108
2165
|
this.sessions = sessions;
|
|
@@ -2115,6 +2172,10 @@ var OpenCodeTurns = class {
|
|
|
2115
2172
|
this.memory = memory;
|
|
2116
2173
|
this.queue = queue;
|
|
2117
2174
|
this.toolsTokens = toolsTokens2;
|
|
2175
|
+
const hookTimeoutMs = settings.hookTimeoutMs;
|
|
2176
|
+
if (hookTimeoutMs !== void 0 && !(Number.isFinite(hookTimeoutMs) && hookTimeoutMs > 0)) {
|
|
2177
|
+
throw new Error(`hookTimeoutMs must be a positive number of milliseconds, not ${hookTimeoutMs}`);
|
|
2178
|
+
}
|
|
2118
2179
|
this.prices = new import_nestjs_agent_core5.ModelPriceResolver({
|
|
2119
2180
|
pricingStore,
|
|
2120
2181
|
catalog: agentOptions?.priceCatalog,
|
|
@@ -2386,8 +2447,12 @@ var OpenCodeTurns = class {
|
|
|
2386
2447
|
}
|
|
2387
2448
|
}
|
|
2388
2449
|
onApplicationShutdown() {
|
|
2450
|
+
for (const runId of [
|
|
2451
|
+
...this.watches.keys()
|
|
2452
|
+
]) this.release(runId, "released");
|
|
2389
2453
|
for (const live of this.live.values()) live.stop();
|
|
2390
2454
|
this.live.clear();
|
|
2455
|
+
this.owners.clear();
|
|
2391
2456
|
this.events.close();
|
|
2392
2457
|
}
|
|
2393
2458
|
// ---- begin ---------------------------------------------------------------------------------
|
|
@@ -2397,7 +2462,8 @@ var OpenCodeTurns = class {
|
|
|
2397
2462
|
* durable `begin` re-run after a crash): the run's user message is written once.
|
|
2398
2463
|
*/
|
|
2399
2464
|
async begin(runId, input) {
|
|
2400
|
-
const
|
|
2465
|
+
const startedAt = this.watches.get(runId)?.startedAt ?? Date.now();
|
|
2466
|
+
const { earlier, rerun } = await this.prepareThread(runId, input);
|
|
2401
2467
|
const server = await this.host.server(input.actor);
|
|
2402
2468
|
const known = await this.sessions.get(input.threadId);
|
|
2403
2469
|
const reusable = known !== null && known.serverKey === server.key && known.bootId === server.bootId && (await this.host.reuse?.({
|
|
@@ -2443,7 +2509,12 @@ var OpenCodeTurns = class {
|
|
|
2443
2509
|
created
|
|
2444
2510
|
});
|
|
2445
2511
|
await this.refreshInstructions(runId, input, server.client, handle.sessionId);
|
|
2446
|
-
|
|
2512
|
+
const begun = this.limitsFor(input).wallClockMs !== void 0 ? {
|
|
2513
|
+
...handle,
|
|
2514
|
+
startedAt
|
|
2515
|
+
} : handle;
|
|
2516
|
+
await this.adopt(runId, input, begun, rerun, void 0, startedAt);
|
|
2517
|
+
return begun;
|
|
2447
2518
|
}
|
|
2448
2519
|
async createSession(runId, input, server, earlier, note) {
|
|
2449
2520
|
const { client } = server;
|
|
@@ -2513,10 +2584,16 @@ ${transcript}` : "",
|
|
|
2513
2584
|
}
|
|
2514
2585
|
const firstDropped = messages[lastUser + 1];
|
|
2515
2586
|
if (firstDropped !== void 0) await this.store.truncateFrom(input.threadId, firstDropped.id);
|
|
2516
|
-
return
|
|
2587
|
+
return {
|
|
2588
|
+
earlier: messages.slice(0, Math.max(lastUser, 0)),
|
|
2589
|
+
rerun: false
|
|
2590
|
+
};
|
|
2517
2591
|
}
|
|
2518
2592
|
const mine = messages.findIndex((m) => m.role === "user" && m.runId === runId);
|
|
2519
|
-
if (mine >= 0) return
|
|
2593
|
+
if (mine >= 0) return {
|
|
2594
|
+
earlier: messages.slice(0, mine),
|
|
2595
|
+
rerun: true
|
|
2596
|
+
};
|
|
2520
2597
|
await this.store.appendMessage({
|
|
2521
2598
|
threadId: input.threadId,
|
|
2522
2599
|
role: "user",
|
|
@@ -2532,7 +2609,10 @@ ${transcript}` : "",
|
|
|
2532
2609
|
if (thread !== null && (thread.title === "" || thread.title === "New chat")) {
|
|
2533
2610
|
await this.store.setTitle(input.threadId, titleOf(input.userText));
|
|
2534
2611
|
}
|
|
2535
|
-
return
|
|
2612
|
+
return {
|
|
2613
|
+
earlier: messages,
|
|
2614
|
+
rerun: false
|
|
2615
|
+
};
|
|
2536
2616
|
}
|
|
2537
2617
|
/** Rewind the session to before its last user message, so the regenerated answer replaces it. */
|
|
2538
2618
|
async revertLastExchange(client, sessionId) {
|
|
@@ -2748,6 +2828,7 @@ ${body}
|
|
|
2748
2828
|
// ---- prompt / observe / reply --------------------------------------------------------------
|
|
2749
2829
|
/** Start listening to the session (so nothing it says is missed), then send the user message. */
|
|
2750
2830
|
async prompt(runId, input, handle, spent) {
|
|
2831
|
+
await this.adopt(runId, input, handle, true);
|
|
2751
2832
|
const live = await this.ensureLive(runId, input, handle, false, spent);
|
|
2752
2833
|
try {
|
|
2753
2834
|
const prompt = await this.host.promptFor?.({
|
|
@@ -2790,7 +2871,24 @@ ${body}
|
|
|
2790
2871
|
* back from the session's messages, when the execution ends, what nobody heard after that.
|
|
2791
2872
|
*/
|
|
2792
2873
|
async observe(runId, input, handle, spent, progress) {
|
|
2874
|
+
const watch = await this.adopt(runId, input, handle, true, progress?.counts);
|
|
2793
2875
|
const live = await this.ensureLive(runId, input, handle, true, spent, progress);
|
|
2876
|
+
watch.parkedOn = void 0;
|
|
2877
|
+
watch.parkedEnd = void 0;
|
|
2878
|
+
watch.series = false;
|
|
2879
|
+
watch.observing = true;
|
|
2880
|
+
const wall = this.watchClock(watch);
|
|
2881
|
+
try {
|
|
2882
|
+
if (watch.pending !== void 0) await this.requestStop(runId, watch.pending);
|
|
2883
|
+
const milestone = this.marked(watch, await this.observeLive(runId, input, handle, live));
|
|
2884
|
+
if (milestone.kind === "ask") watch.parkedOn = milestone.ask.id;
|
|
2885
|
+
return milestone;
|
|
2886
|
+
} finally {
|
|
2887
|
+
watch.observing = false;
|
|
2888
|
+
if (wall !== void 0) clearTimeout(wall);
|
|
2889
|
+
}
|
|
2890
|
+
}
|
|
2891
|
+
async observeLive(runId, input, handle, live) {
|
|
2794
2892
|
const { turn, client } = live;
|
|
2795
2893
|
if (turn.hasMilestone()) return this.reached(runId, input, handle, await turn.next());
|
|
2796
2894
|
let done = false;
|
|
@@ -2830,19 +2928,64 @@ ${body}
|
|
|
2830
2928
|
* to go on.
|
|
2831
2929
|
*/
|
|
2832
2930
|
async reply(runId, input, handle, ask, reply, spent, progress) {
|
|
2931
|
+
const watch = await this.adopt(runId, input, handle, true, progress?.counts);
|
|
2932
|
+
watch.parkedOn = void 0;
|
|
2933
|
+
watch.parkedEnd = void 0;
|
|
2934
|
+
watch.series = false;
|
|
2935
|
+
const stopping = watch.pending !== void 0 || watch.stop !== void 0;
|
|
2936
|
+
const answer = stopping ? refusalOf(ask) : reply;
|
|
2833
2937
|
const server = await this.host.server(input.actor);
|
|
2834
2938
|
if (server.key !== handle.serverKey || server.bootId !== handle.bootId) {
|
|
2835
|
-
return this.
|
|
2939
|
+
if (stopping) return this.stopAfterRestart(watch, runId, input, handle, ask, answer, spent);
|
|
2940
|
+
return this.continueAfterRestart(runId, input, server, ask, answer, spent, handle.startedAt);
|
|
2836
2941
|
}
|
|
2837
2942
|
const live = await this.ensureLive(runId, input, handle, false, spent, progress);
|
|
2838
|
-
await live.turn.decide(ask,
|
|
2943
|
+
await live.turn.decide(ask, answer);
|
|
2839
2944
|
return handle;
|
|
2840
2945
|
}
|
|
2841
|
-
|
|
2946
|
+
/**
|
|
2947
|
+
* A stopping turn whose OpenCode server restarted while it waited: there is no session left to
|
|
2948
|
+
* abort, and none is opened or prompted. The ask is settled as refused and the turn ends stopped.
|
|
2949
|
+
*/
|
|
2950
|
+
async stopAfterRestart(watch, runId, input, handle, ask, answer, spent) {
|
|
2951
|
+
const live = await this.ensureLive(runId, input, handle, false, spent);
|
|
2952
|
+
await live.turn.decide(ask, answer, {
|
|
2953
|
+
tellOpenCode: false
|
|
2954
|
+
});
|
|
2955
|
+
const reason = watch.pending ?? watch.stop?.reason ?? "stopped";
|
|
2956
|
+
if (watch.stop === void 0) {
|
|
2957
|
+
const stop = {
|
|
2958
|
+
reason,
|
|
2959
|
+
counts: this.counts(watch)
|
|
2960
|
+
};
|
|
2961
|
+
watch.stop = stop;
|
|
2962
|
+
this.hook("onStop", {
|
|
2963
|
+
turnId: runId,
|
|
2964
|
+
reason,
|
|
2965
|
+
counts: stop.counts
|
|
2966
|
+
});
|
|
2967
|
+
}
|
|
2968
|
+
watch.aborted = true;
|
|
2969
|
+
watch.pending = void 0;
|
|
2970
|
+
live.turn.handle({
|
|
2971
|
+
type: "session.execution.interrupted",
|
|
2972
|
+
data: {
|
|
2973
|
+
sessionID: handle.sessionId
|
|
2974
|
+
}
|
|
2975
|
+
});
|
|
2976
|
+
return handle;
|
|
2977
|
+
}
|
|
2978
|
+
async continueAfterRestart(runId, input, server, ask, reply, spent, startedAt) {
|
|
2842
2979
|
const approved = "approved" in reply && reply.approved === true;
|
|
2843
2980
|
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.`;
|
|
2844
2981
|
const thread = await this.store.getThread(input.threadId);
|
|
2845
|
-
const
|
|
2982
|
+
const created = await this.createSession(runId, input, server, thread?.messages ?? [], note);
|
|
2983
|
+
const handle = {
|
|
2984
|
+
...created,
|
|
2985
|
+
...startedAt !== void 0 ? {
|
|
2986
|
+
startedAt
|
|
2987
|
+
} : {}
|
|
2988
|
+
};
|
|
2846
2989
|
await this.refreshInstructions(runId, input, server.client, handle.sessionId);
|
|
2847
2990
|
this.drop(runId);
|
|
2848
2991
|
const live = await this.ensureLive(runId, input, handle, false, spent);
|
|
@@ -2927,7 +3070,11 @@ ${body}
|
|
|
2927
3070
|
this.grants.set(runId, grants);
|
|
2928
3071
|
}, "onGranted")
|
|
2929
3072
|
});
|
|
2930
|
-
const
|
|
3073
|
+
const sessionId = handle.sessionId;
|
|
3074
|
+
const stop = await this.events.listen(server.key, server.client, sessionId, (event) => {
|
|
3075
|
+
turn.handle(event);
|
|
3076
|
+
this.sawEvent(runId, sessionId, event);
|
|
3077
|
+
});
|
|
2931
3078
|
const live = {
|
|
2932
3079
|
turn,
|
|
2933
3080
|
input,
|
|
@@ -2936,6 +3083,15 @@ ${body}
|
|
|
2936
3083
|
stop
|
|
2937
3084
|
};
|
|
2938
3085
|
this.live.set(runId, live);
|
|
3086
|
+
this.owners.set(ownerKey(handle), runId);
|
|
3087
|
+
const watch = this.watches.get(runId);
|
|
3088
|
+
if (watch !== void 0 && watch.handle.sessionId !== sessionId) {
|
|
3089
|
+
watch.handle = {
|
|
3090
|
+
...handle,
|
|
3091
|
+
startedAt: watch.startedAt
|
|
3092
|
+
};
|
|
3093
|
+
watch.ending = false;
|
|
3094
|
+
}
|
|
2939
3095
|
if (resumed) await turn.catchUp().catch(() => void 0);
|
|
2940
3096
|
return live;
|
|
2941
3097
|
}
|
|
@@ -2944,11 +3100,46 @@ ${body}
|
|
|
2944
3100
|
this.live.get(runId)?.stop();
|
|
2945
3101
|
this.live.delete(runId);
|
|
2946
3102
|
}
|
|
2947
|
-
/**
|
|
3103
|
+
/**
|
|
3104
|
+
* Stop following a run and drop the approvals it was granted: it is over. Returns the turn this
|
|
3105
|
+
* process executed, now settled (no stop reaches it any more), for its `onTurnEnd`.
|
|
3106
|
+
*/
|
|
2948
3107
|
forget(runId) {
|
|
2949
3108
|
this.drop(runId);
|
|
3109
|
+
this.releasedUsage.delete(runId);
|
|
2950
3110
|
for (const grant of this.grants.get(runId) ?? []) this.spent.delete(grant.id);
|
|
2951
3111
|
this.grants.delete(runId);
|
|
3112
|
+
const watch = this.watches.get(runId);
|
|
3113
|
+
if (watch === void 0) return void 0;
|
|
3114
|
+
watch.settled = true;
|
|
3115
|
+
this.watches.delete(runId);
|
|
3116
|
+
for (const [key, owner] of this.owners) if (owner === runId) this.owners.delete(key);
|
|
3117
|
+
return watch;
|
|
3118
|
+
}
|
|
3119
|
+
/**
|
|
3120
|
+
* A run that ended without settling here (cancelled while this process executed it; whoever
|
|
3121
|
+
* cancelled settled it): stop following it, and end the turn this process executed.
|
|
3122
|
+
*/
|
|
3123
|
+
async abandon(runId) {
|
|
3124
|
+
const watch = this.forget(runId);
|
|
3125
|
+
if (watch !== void 0) this.ended(watch, "interrupted", void 0);
|
|
3126
|
+
}
|
|
3127
|
+
/**
|
|
3128
|
+
* Let go of a turn this process executed without settling it here: it was cancelled while parked
|
|
3129
|
+
* (its session ended under it, `interrupted`), another worker took it over, or this process shuts
|
|
3130
|
+
* down (`released`). The per-turn state goes, `onTurnEnd` fires once, and a later stop of the turn
|
|
3131
|
+
* here is ignored. Nothing settles the run here afterwards: whoever answered or cancelled it does.
|
|
3132
|
+
*/
|
|
3133
|
+
release(runId, status) {
|
|
3134
|
+
const watch = this.watches.get(runId);
|
|
3135
|
+
if (watch === void 0) return;
|
|
3136
|
+
const seen = this.live.get(runId)?.turn.totalUsage();
|
|
3137
|
+
if (seen !== void 0) this.keepReleasedUsage(runId, seen);
|
|
3138
|
+
this.drop(runId);
|
|
3139
|
+
watch.settled = true;
|
|
3140
|
+
this.watches.delete(runId);
|
|
3141
|
+
for (const [key, owner] of this.owners) if (owner === runId) this.owners.delete(key);
|
|
3142
|
+
this.ended(watch, status, void 0);
|
|
2952
3143
|
}
|
|
2953
3144
|
// ---- settle --------------------------------------------------------------------------------
|
|
2954
3145
|
/** A person was asked something: tell the host (cards in other channels, wake-ups). */
|
|
@@ -3021,7 +3212,7 @@ ${body}
|
|
|
3021
3212
|
* two milestones.
|
|
3022
3213
|
*/
|
|
3023
3214
|
spentBy(runId, spent) {
|
|
3024
|
-
const seen = this.live.get(runId)?.turn.totalUsage();
|
|
3215
|
+
const seen = this.live.get(runId)?.turn.totalUsage() ?? this.releasedUsage.get(runId);
|
|
3025
3216
|
if (spent === void 0) return seen ?? emptyUsage();
|
|
3026
3217
|
if (seen === void 0) return spent;
|
|
3027
3218
|
const tokens = /* @__PURE__ */ __name((u) => u.inputTokens + u.outputTokens, "tokens");
|
|
@@ -3031,13 +3222,13 @@ ${body}
|
|
|
3031
3222
|
* End the run's stream and bookkeeping for how its execution ended. `spent` is what the run spent
|
|
3032
3223
|
* (the sum of its milestones' `usage`); omit → what the turn this process follows saw.
|
|
3033
3224
|
*/
|
|
3034
|
-
async settle(runId, input,
|
|
3225
|
+
async settle(runId, input, ended, durationMs, spent) {
|
|
3226
|
+
const outcome = this.withStop(ended, this.watches.get(runId));
|
|
3035
3227
|
if (outcome.status === "failed") {
|
|
3036
|
-
|
|
3037
|
-
return;
|
|
3228
|
+
return this.settleFailed(runId, input, outcome.error, durationMs, spent, outcome.stop);
|
|
3038
3229
|
}
|
|
3039
3230
|
const usage = this.spentBy(runId, spent);
|
|
3040
|
-
this.forget(runId);
|
|
3231
|
+
const watch = this.forget(runId);
|
|
3041
3232
|
const writer = await this.sink.open(runId);
|
|
3042
3233
|
const result = await this.runResult(runId, input, outcome, durationMs, usage);
|
|
3043
3234
|
await this.amend(writer, result);
|
|
@@ -3072,22 +3263,30 @@ ${body}
|
|
|
3072
3263
|
});
|
|
3073
3264
|
}
|
|
3074
3265
|
await this.settled(result);
|
|
3266
|
+
if (watch !== void 0) this.ended(watch, outcome.status, outcome.status === "succeeded" ? outcome.stop : void 0);
|
|
3267
|
+
return outcome;
|
|
3075
3268
|
}
|
|
3076
|
-
async settleFailed(runId, input, error, durationMs = 0, spent) {
|
|
3269
|
+
async settleFailed(runId, input, error, durationMs = 0, spent, stop) {
|
|
3077
3270
|
const usage = this.spentBy(runId, spent);
|
|
3078
|
-
this.forget(runId);
|
|
3271
|
+
const watch = this.forget(runId);
|
|
3272
|
+
const stopped = stop ?? watch?.stop;
|
|
3079
3273
|
const writer = await this.sink.open(runId);
|
|
3080
3274
|
const result = await this.runResult(runId, input, {
|
|
3081
3275
|
status: "failed",
|
|
3082
|
-
error
|
|
3276
|
+
error,
|
|
3277
|
+
...stopped !== void 0 ? {
|
|
3278
|
+
stop: stopped
|
|
3279
|
+
} : {}
|
|
3083
3280
|
}, durationMs, usage);
|
|
3084
3281
|
const worded = (await this.amend(writer, result)).error;
|
|
3085
|
-
const message = worded ?? (0, import_nestjs_agent_core5.streamFailure)(new Error(error)).message;
|
|
3086
|
-
|
|
3282
|
+
const message = worded ?? (stopped !== void 0 ? error : (0, import_nestjs_agent_core5.streamFailure)(new Error(error)).message);
|
|
3283
|
+
const code = stopped !== void 0 ? STOPPED_ERROR_CODE : "opencode_failed";
|
|
3284
|
+
if (stopped !== void 0) this.logger.log(`agent run ${runId} stopped: ${stopped.reason}`);
|
|
3285
|
+
else this.logger.error(`agent run ${runId} failed: ${error}`);
|
|
3087
3286
|
(0, import_nestjs_agent_core5.publishAgentRunFailed)({
|
|
3088
3287
|
runId,
|
|
3089
3288
|
threadId: input.threadId,
|
|
3090
|
-
code
|
|
3289
|
+
code,
|
|
3091
3290
|
message: error,
|
|
3092
3291
|
durationMs,
|
|
3093
3292
|
context: (0, import_nestjs_agent_core5.agentRunContext)(input)
|
|
@@ -3096,23 +3295,370 @@ ${body}
|
|
|
3096
3295
|
runId,
|
|
3097
3296
|
status: "failed",
|
|
3098
3297
|
durationMs,
|
|
3099
|
-
errorCode:
|
|
3298
|
+
errorCode: code,
|
|
3100
3299
|
errorMessage: error
|
|
3101
3300
|
});
|
|
3102
3301
|
await Promise.resolve(this.store.failUnsettledToolCalls?.(runId, import_nestjs_agent_core5.RUN_ENDED_BEFORE_TOOL_CALL)).catch(() => 0);
|
|
3103
3302
|
await this.handoff(writer, input, runId, "failed", error);
|
|
3104
3303
|
await (0, import_nestjs_agent_core5.releaseThreadRun)(this.store, input.threadId, runId);
|
|
3105
3304
|
await writer.fail({
|
|
3106
|
-
code
|
|
3305
|
+
code,
|
|
3107
3306
|
message
|
|
3108
3307
|
});
|
|
3109
3308
|
await this.settled({
|
|
3110
3309
|
...result,
|
|
3111
3310
|
outcome: {
|
|
3112
3311
|
status: "failed",
|
|
3113
|
-
error: worded ?? error
|
|
3312
|
+
error: worded ?? error,
|
|
3313
|
+
...stopped !== void 0 ? {
|
|
3314
|
+
stop: stopped
|
|
3315
|
+
} : {}
|
|
3114
3316
|
}
|
|
3115
3317
|
});
|
|
3318
|
+
if (watch !== void 0) this.ended(watch, "failed", stopped);
|
|
3319
|
+
return {
|
|
3320
|
+
status: "failed",
|
|
3321
|
+
error,
|
|
3322
|
+
...stopped !== void 0 ? {
|
|
3323
|
+
stop: stopped
|
|
3324
|
+
} : {}
|
|
3325
|
+
};
|
|
3326
|
+
}
|
|
3327
|
+
// ---- limits, stops and the turn's lifecycle -------------------------------------------------
|
|
3328
|
+
/**
|
|
3329
|
+
* Stop a turn's own work: abort its session so OpenCode starts no new round, only while the turn
|
|
3330
|
+
* still owns the session (a thread's session is reused by its next turn) — checked immediately
|
|
3331
|
+
* before the abort. Acts in the process executing the turn; see {@link OpenCodeStopResult}. The
|
|
3332
|
+
* turn ends failed with `outcome.stop` (code `opencode_stopped`), and `onStop` is called.
|
|
3333
|
+
*/
|
|
3334
|
+
stopTurn(turnId, reason) {
|
|
3335
|
+
return this.requestStop(turnId, reason);
|
|
3336
|
+
}
|
|
3337
|
+
/** The turn this process executes, made known here on its first step in this process. */
|
|
3338
|
+
async adopt(runId, input, handle, resumed, counts, startedAt) {
|
|
3339
|
+
const known = this.watches.get(runId);
|
|
3340
|
+
if (known !== void 0) return known;
|
|
3341
|
+
const watch = {
|
|
3342
|
+
runId,
|
|
3343
|
+
input,
|
|
3344
|
+
handle,
|
|
3345
|
+
limits: this.limitsFor(input),
|
|
3346
|
+
startedAt: handle.startedAt ?? startedAt ?? Date.now(),
|
|
3347
|
+
steps: counts?.steps ?? 0,
|
|
3348
|
+
compactions: counts?.compactions ?? 0,
|
|
3349
|
+
observing: false,
|
|
3350
|
+
ending: false,
|
|
3351
|
+
settled: false,
|
|
3352
|
+
aborted: false,
|
|
3353
|
+
checking: false,
|
|
3354
|
+
lastRead: 0
|
|
3355
|
+
};
|
|
3356
|
+
this.watches.set(runId, watch);
|
|
3357
|
+
await this.hook("onTurnStart", {
|
|
3358
|
+
turnId: runId,
|
|
3359
|
+
threadId: input.threadId,
|
|
3360
|
+
sessionId: handle.sessionId,
|
|
3361
|
+
resumed
|
|
3362
|
+
});
|
|
3363
|
+
return watch;
|
|
3364
|
+
}
|
|
3365
|
+
limitsFor(input) {
|
|
3366
|
+
const limits = this.settings.limits;
|
|
3367
|
+
return cleanLimits(typeof limits === "function" ? limits(input) : limits);
|
|
3368
|
+
}
|
|
3369
|
+
/**
|
|
3370
|
+
* One event of the turn's session: counted for the limits, and told to `onTurnEvent`. While the
|
|
3371
|
+
* turn is parked on a person here, it may also say the turn went on without this process
|
|
3372
|
+
* ({@link wentOnElsewhere}).
|
|
3373
|
+
*/
|
|
3374
|
+
sawEvent(runId, sessionId, event) {
|
|
3375
|
+
if (sessionOf(event) !== sessionId) return;
|
|
3376
|
+
const watch = this.watches.get(runId);
|
|
3377
|
+
if (watch === void 0 || watch.settled || watch.handle.sessionId !== sessionId) return;
|
|
3378
|
+
if (watch.parkedOn !== void 0 && this.wentOnElsewhere(watch, event)) return;
|
|
3379
|
+
let reached;
|
|
3380
|
+
if (event.type.startsWith("session.execution.")) watch.ending = true;
|
|
3381
|
+
else if (event.type === "session.step.started") {
|
|
3382
|
+
watch.steps += 1;
|
|
3383
|
+
if (passed(watch.steps, watch.limits.steps)) reached = "steps";
|
|
3384
|
+
} else if (event.type === "session.compaction.started") {
|
|
3385
|
+
watch.compactions += 1;
|
|
3386
|
+
if (passed(watch.compactions, watch.limits.compactions)) reached = "compactions";
|
|
3387
|
+
}
|
|
3388
|
+
this.hook("onTurnEvent", {
|
|
3389
|
+
turnId: runId,
|
|
3390
|
+
type: event.type,
|
|
3391
|
+
sessionId
|
|
3392
|
+
});
|
|
3393
|
+
if (reached !== void 0) void this.limitStop(runId, reached);
|
|
3394
|
+
}
|
|
3395
|
+
/**
|
|
3396
|
+
* Whether a turn parked here went on without this process, which then lets it go. While a tool of
|
|
3397
|
+
* the step waits on a person, OpenCode keeps running the step's other tools, so their events say
|
|
3398
|
+
* nothing. What does: a new round (`session.step.started`, which cannot begin while the step
|
|
3399
|
+
* waits), or the store showing the parked ask decided by someone else (another worker answered
|
|
3400
|
+
* it, or a cancel elsewhere settled it) — this process clears the park before it answers. When
|
|
3401
|
+
* the session's execution ends while parked, the store is asked again for a while, as a cancel
|
|
3402
|
+
* elsewhere settles the ask just after it interrupted the session; with the ask still open, the
|
|
3403
|
+
* turn stays here for its answer.
|
|
3404
|
+
*/
|
|
3405
|
+
wentOnElsewhere(watch, event) {
|
|
3406
|
+
if (event.type === "session.step.started") {
|
|
3407
|
+
this.release(watch.runId, "released");
|
|
3408
|
+
return true;
|
|
3409
|
+
}
|
|
3410
|
+
const ended = event.type.startsWith("session.execution.");
|
|
3411
|
+
if (ended) watch.parkedEnd = event.type === "session.execution.interrupted" ? "interrupted" : "released";
|
|
3412
|
+
this.checkParked(watch, ended);
|
|
3413
|
+
return false;
|
|
3414
|
+
}
|
|
3415
|
+
/**
|
|
3416
|
+
* Ask the store whether the parked ask was decided elsewhere, and let the turn go when it was —
|
|
3417
|
+
* one read at a time, at most one every {@link PARKED_READ_GAP_MS}. `series`: the session's
|
|
3418
|
+
* execution ended, so the store is asked again over {@link parkedRechecksMs}. An execution that
|
|
3419
|
+
* was interrupted under a parked turn means a cancel elsewhere: when the store cannot say (it has
|
|
3420
|
+
* no `toolCallApproval`, the read fails, the ask is not on file) past the first recheck, or the
|
|
3421
|
+
* rechecks run out with the ask still open, the turn is let go as `interrupted` anyway.
|
|
3422
|
+
*/
|
|
3423
|
+
checkParked(watch, series) {
|
|
3424
|
+
if (series) watch.series = true;
|
|
3425
|
+
if (watch.checking) {
|
|
3426
|
+
watch.again = true;
|
|
3427
|
+
return;
|
|
3428
|
+
}
|
|
3429
|
+
watch.checking = true;
|
|
3430
|
+
void this.runParkedChecks(watch);
|
|
3431
|
+
}
|
|
3432
|
+
/**
|
|
3433
|
+
* The checks of {@link checkParked}. `checking` is cleared in the same step the loop returns in,
|
|
3434
|
+
* so a check asked for afterwards starts a new loop; one asked for while it ran (`again`) but not
|
|
3435
|
+
* read because the loop returned starts one too.
|
|
3436
|
+
*/
|
|
3437
|
+
async runParkedChecks(watch) {
|
|
3438
|
+
try {
|
|
3439
|
+
await this.parkedChecks(watch);
|
|
3440
|
+
} finally {
|
|
3441
|
+
watch.checking = false;
|
|
3442
|
+
if (watch.again && !watch.settled && watch.parkedOn !== void 0) {
|
|
3443
|
+
watch.checking = true;
|
|
3444
|
+
void this.runParkedChecks(watch);
|
|
3445
|
+
}
|
|
3446
|
+
}
|
|
3447
|
+
}
|
|
3448
|
+
async parkedChecks(watch) {
|
|
3449
|
+
const ask = watch.parkedOn;
|
|
3450
|
+
const gone = /* @__PURE__ */ __name(() => watch.settled || ask === void 0 || watch.parkedOn !== ask, "gone");
|
|
3451
|
+
let step = 0;
|
|
3452
|
+
for (; ; ) {
|
|
3453
|
+
watch.again = false;
|
|
3454
|
+
const wait = watch.lastRead + PARKED_READ_GAP_MS - Date.now();
|
|
3455
|
+
if (wait > 0) await sleep(wait);
|
|
3456
|
+
if (gone()) return;
|
|
3457
|
+
const answer = await this.parkedAsk(ask);
|
|
3458
|
+
watch.lastRead = Date.now();
|
|
3459
|
+
if (gone()) return;
|
|
3460
|
+
if (answer === "decided") {
|
|
3461
|
+
this.release(watch.runId, watch.parkedEnd ?? "released");
|
|
3462
|
+
return;
|
|
3463
|
+
}
|
|
3464
|
+
if (watch.series) {
|
|
3465
|
+
const cancelled = watch.parkedEnd === "interrupted";
|
|
3466
|
+
if (cancelled && answer === "unknown" && step > 0) {
|
|
3467
|
+
this.release(watch.runId, "interrupted");
|
|
3468
|
+
return;
|
|
3469
|
+
}
|
|
3470
|
+
const gap = this.parkedRechecksMs[step];
|
|
3471
|
+
step += 1;
|
|
3472
|
+
if (gap === void 0) {
|
|
3473
|
+
if (cancelled) this.release(watch.runId, "interrupted");
|
|
3474
|
+
return;
|
|
3475
|
+
}
|
|
3476
|
+
await sleep(gap);
|
|
3477
|
+
continue;
|
|
3478
|
+
}
|
|
3479
|
+
if (!watch.again) return;
|
|
3480
|
+
}
|
|
3481
|
+
}
|
|
3482
|
+
/** What the store says of an ask: decided, still open, or nothing it can tell. */
|
|
3483
|
+
async parkedAsk(id) {
|
|
3484
|
+
if (this.store.toolCallApproval === void 0) return "unknown";
|
|
3485
|
+
try {
|
|
3486
|
+
const state = await this.store.toolCallApproval(id);
|
|
3487
|
+
if (state == null) return "unknown";
|
|
3488
|
+
return state.status === "pending_approval" ? "open" : "decided";
|
|
3489
|
+
} catch {
|
|
3490
|
+
return "unknown";
|
|
3491
|
+
}
|
|
3492
|
+
}
|
|
3493
|
+
/**
|
|
3494
|
+
* The waits between the store's rereads of a parked ask after its execution ended (cumulative
|
|
3495
|
+
* 0.25, 1.25, 4.25, 14.25 and 44.25 seconds after the first). Overridden by tests only.
|
|
3496
|
+
*
|
|
3497
|
+
* @internal
|
|
3498
|
+
*/
|
|
3499
|
+
parkedRechecksMs = PARKED_RECHECKS_MS;
|
|
3500
|
+
/** A limit's stop: what it did is the engine's to log, there is no caller to tell. */
|
|
3501
|
+
async limitStop(runId, reason) {
|
|
3502
|
+
const result = await this.requestStop(runId, reason);
|
|
3503
|
+
if (result === "unconfirmed") this.logger.warn(`run ${runId}: passed its ${reason} limit, but its owner could not be read`);
|
|
3504
|
+
}
|
|
3505
|
+
/** The wall-clock bound of an observation: a timer for what is left of it, if anything. */
|
|
3506
|
+
watchClock(watch) {
|
|
3507
|
+
const max = watch.limits.wallClockMs;
|
|
3508
|
+
if (max === void 0) return void 0;
|
|
3509
|
+
const left = watch.startedAt + max - Date.now();
|
|
3510
|
+
if (left <= 0) {
|
|
3511
|
+
watch.pending ??= "wall-clock";
|
|
3512
|
+
return void 0;
|
|
3513
|
+
}
|
|
3514
|
+
const timer = setTimeout(() => void this.limitStop(watch.runId, "wall-clock"), left);
|
|
3515
|
+
timer.unref?.();
|
|
3516
|
+
return timer;
|
|
3517
|
+
}
|
|
3518
|
+
counts(watch) {
|
|
3519
|
+
return {
|
|
3520
|
+
steps: watch.steps,
|
|
3521
|
+
compactions: watch.compactions,
|
|
3522
|
+
elapsedMs: Date.now() - watch.startedAt
|
|
3523
|
+
};
|
|
3524
|
+
}
|
|
3525
|
+
async requestStop(runId, reason) {
|
|
3526
|
+
const watch = this.watches.get(runId);
|
|
3527
|
+
if (watch === void 0 || watch.settled || watch.stop !== void 0) return "ignored";
|
|
3528
|
+
const live = this.live.get(runId);
|
|
3529
|
+
if (watch.parkedOn !== void 0) {
|
|
3530
|
+
watch.pending ??= reason;
|
|
3531
|
+
return "pending";
|
|
3532
|
+
}
|
|
3533
|
+
if (watch.ending || live?.turn.finished === true) return this.landed(watch, reason);
|
|
3534
|
+
if (!watch.observing || live === void 0) {
|
|
3535
|
+
watch.pending ??= reason;
|
|
3536
|
+
return "pending";
|
|
3537
|
+
}
|
|
3538
|
+
watch.pending = void 0;
|
|
3539
|
+
let active;
|
|
3540
|
+
try {
|
|
3541
|
+
active = await this.activeRun(watch.input.threadId);
|
|
3542
|
+
} catch (error) {
|
|
3543
|
+
this.logger.warn(`run ${runId}: could not read the thread's run: ${errorText(error, "error")}`);
|
|
3544
|
+
return "unconfirmed";
|
|
3545
|
+
}
|
|
3546
|
+
if (watch.settled || watch.stop !== void 0 || this.live.get(runId) !== live) return "ignored";
|
|
3547
|
+
if (active !== null && active !== runId) return "ignored";
|
|
3548
|
+
if (this.owners.get(ownerKey(watch.handle)) !== runId) return "ignored";
|
|
3549
|
+
if (watch.ending || live.turn.finished) return this.landed(watch, reason);
|
|
3550
|
+
const stop = {
|
|
3551
|
+
reason,
|
|
3552
|
+
counts: this.counts(watch)
|
|
3553
|
+
};
|
|
3554
|
+
watch.stop = stop;
|
|
3555
|
+
watch.aborted = true;
|
|
3556
|
+
try {
|
|
3557
|
+
await live.client.session.interrupt({
|
|
3558
|
+
sessionID: watch.handle.sessionId
|
|
3559
|
+
});
|
|
3560
|
+
} catch (error) {
|
|
3561
|
+
this.logger.warn(`run ${runId}: could not abort the session: ${errorText(error, "error")}`);
|
|
3562
|
+
}
|
|
3563
|
+
this.hook("onStop", {
|
|
3564
|
+
turnId: runId,
|
|
3565
|
+
reason,
|
|
3566
|
+
counts: stop.counts
|
|
3567
|
+
});
|
|
3568
|
+
return "aborted";
|
|
3569
|
+
}
|
|
3570
|
+
/** A stop that arrived while the answer settles: recorded and reported, the answer kept. */
|
|
3571
|
+
landed(watch, reason) {
|
|
3572
|
+
const stop = {
|
|
3573
|
+
reason,
|
|
3574
|
+
counts: this.counts(watch)
|
|
3575
|
+
};
|
|
3576
|
+
watch.stop = stop;
|
|
3577
|
+
watch.pending = void 0;
|
|
3578
|
+
this.hook("onStop", {
|
|
3579
|
+
turnId: watch.runId,
|
|
3580
|
+
reason,
|
|
3581
|
+
counts: stop.counts
|
|
3582
|
+
});
|
|
3583
|
+
return "settling";
|
|
3584
|
+
}
|
|
3585
|
+
/** The milestone as the runner journals it: the limits' counts on an ask, the stop on an end. */
|
|
3586
|
+
marked(watch, milestone) {
|
|
3587
|
+
if (milestone.kind === "finished") return {
|
|
3588
|
+
...milestone,
|
|
3589
|
+
outcome: this.withStop(milestone.outcome, watch)
|
|
3590
|
+
};
|
|
3591
|
+
if (milestone.progress === void 0) return milestone;
|
|
3592
|
+
const counts = {
|
|
3593
|
+
steps: watch.steps,
|
|
3594
|
+
compactions: watch.compactions
|
|
3595
|
+
};
|
|
3596
|
+
return {
|
|
3597
|
+
...milestone,
|
|
3598
|
+
progress: {
|
|
3599
|
+
...milestone.progress,
|
|
3600
|
+
counts
|
|
3601
|
+
}
|
|
3602
|
+
};
|
|
3603
|
+
}
|
|
3604
|
+
/**
|
|
3605
|
+
* How a turn a stop landed on ends. An answer that settled anyway is kept, with the stop on it; an
|
|
3606
|
+
* execution that failed on its own keeps its error, with the stop on it; an execution the stop
|
|
3607
|
+
* aborted ends failed with the stop's own text. One interrupted by a person's cancel stays so.
|
|
3608
|
+
*/
|
|
3609
|
+
withStop(outcome, watch) {
|
|
3610
|
+
const stop = watch?.stop;
|
|
3611
|
+
if (stop === void 0) return outcome;
|
|
3612
|
+
if (outcome.status !== "interrupted") return outcome.stop !== void 0 ? outcome : {
|
|
3613
|
+
...outcome,
|
|
3614
|
+
stop
|
|
3615
|
+
};
|
|
3616
|
+
if (!watch?.aborted) return outcome;
|
|
3617
|
+
return {
|
|
3618
|
+
status: "failed",
|
|
3619
|
+
error: stopErrorText(stop),
|
|
3620
|
+
stop
|
|
3621
|
+
};
|
|
3622
|
+
}
|
|
3623
|
+
/** `onTurnEnd`, once per turn this process executed; not waited for. */
|
|
3624
|
+
ended(watch, status, stop) {
|
|
3625
|
+
this.hook("onTurnEnd", {
|
|
3626
|
+
turnId: watch.runId,
|
|
3627
|
+
status,
|
|
3628
|
+
...stop !== void 0 ? {
|
|
3629
|
+
stop
|
|
3630
|
+
} : {}
|
|
3631
|
+
});
|
|
3632
|
+
}
|
|
3633
|
+
/**
|
|
3634
|
+
* A lifecycle hook, from the settings and the host, called at once. The promise settles when both
|
|
3635
|
+
* have, or after `hookTimeoutMs` (default 5 s) — only `onTurnStart` is waited for. Errors are
|
|
3636
|
+
* logged, never the turn's.
|
|
3637
|
+
*/
|
|
3638
|
+
hook(name, event) {
|
|
3639
|
+
const calls = [];
|
|
3640
|
+
for (const target of [
|
|
3641
|
+
this.settings,
|
|
3642
|
+
this.host
|
|
3643
|
+
]) {
|
|
3644
|
+
const fn = target[name];
|
|
3645
|
+
if (typeof fn !== "function") continue;
|
|
3646
|
+
calls.push(Promise.resolve().then(() => fn.call(target, event)).catch((error) => this.logger.warn(`${name} failed: ${errorText(error, "error")}`)));
|
|
3647
|
+
}
|
|
3648
|
+
if (calls.length === 0) return Promise.resolve();
|
|
3649
|
+
const ms = this.settings.hookTimeoutMs ?? DEFAULT_HOOK_TIMEOUT_MS;
|
|
3650
|
+
let timer;
|
|
3651
|
+
const late = new Promise((resolve) => {
|
|
3652
|
+
timer = setTimeout(() => {
|
|
3653
|
+
this.logger.warn(`${name} took longer than ${ms}ms; the turn went on`);
|
|
3654
|
+
resolve();
|
|
3655
|
+
}, ms);
|
|
3656
|
+
timer.unref?.();
|
|
3657
|
+
});
|
|
3658
|
+
return Promise.race([
|
|
3659
|
+
Promise.all(calls).then(() => void 0),
|
|
3660
|
+
late
|
|
3661
|
+
]).finally(() => clearTimeout(timer));
|
|
3116
3662
|
}
|
|
3117
3663
|
/** Starts the next queued message of the thread; set by the runner (it owns `start`). */
|
|
3118
3664
|
startNext;
|
|
@@ -3175,6 +3721,28 @@ OpenCodeTurns = _ts_decorate2([
|
|
|
3175
3721
|
Object
|
|
3176
3722
|
])
|
|
3177
3723
|
], OpenCodeTurns);
|
|
3724
|
+
function passed(count, limit) {
|
|
3725
|
+
return limit !== void 0 && count > limit;
|
|
3726
|
+
}
|
|
3727
|
+
__name(passed, "passed");
|
|
3728
|
+
function sleep(ms) {
|
|
3729
|
+
return new Promise((resolve) => setTimeout(resolve, ms).unref?.());
|
|
3730
|
+
}
|
|
3731
|
+
__name(sleep, "sleep");
|
|
3732
|
+
function refusalOf(ask) {
|
|
3733
|
+
return ask.kind === "approval" ? {
|
|
3734
|
+
approved: false,
|
|
3735
|
+
reason: "The turn was stopped."
|
|
3736
|
+
} : {
|
|
3737
|
+
answers: {},
|
|
3738
|
+
skipped: true
|
|
3739
|
+
};
|
|
3740
|
+
}
|
|
3741
|
+
__name(refusalOf, "refusalOf");
|
|
3742
|
+
function ownerKey(handle) {
|
|
3743
|
+
return `${handle.serverKey}|${handle.sessionId}`;
|
|
3744
|
+
}
|
|
3745
|
+
__name(ownerKey, "ownerKey");
|
|
3178
3746
|
function titleOf(text) {
|
|
3179
3747
|
const trimmed = text.trim().replace(/\s+/g, " ");
|
|
3180
3748
|
return trimmed.length > 60 ? `${trimmed.slice(0, 57)}...` : trimmed || "New chat";
|
|
@@ -3348,7 +3916,7 @@ var OpenCodeAgentRunner = class {
|
|
|
3348
3916
|
await this.turns.interrupt(input).catch(() => void 0);
|
|
3349
3917
|
await this.turns.settle(runId, input, {
|
|
3350
3918
|
status: "interrupted"
|
|
3351
|
-
}, Date.now() - started);
|
|
3919
|
+
}, Date.now() - started, spent);
|
|
3352
3920
|
return;
|
|
3353
3921
|
}
|
|
3354
3922
|
await this.turns.settleFailed(runId, input, errorText(error, "the turn failed"), Date.now() - started);
|
|
@@ -3505,6 +4073,7 @@ __name(openCodeProviders, "openCodeProviders");
|
|
|
3505
4073
|
OpenCodeToolsTokens,
|
|
3506
4074
|
OpenCodeTurn,
|
|
3507
4075
|
OpenCodeTurns,
|
|
4076
|
+
STOPPED_ERROR_CODE,
|
|
3508
4077
|
addUsage,
|
|
3509
4078
|
emptyUsage,
|
|
3510
4079
|
keyValueOpenCodeSessionStore,
|