@dudousxd/nestjs-agent-opencode 0.5.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 +89 -1
- package/dist/durable/index.cjs +673 -97
- 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 +615 -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 +653 -82
- 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 +596 -26
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
package/dist/durable/index.cjs
CHANGED
|
@@ -47,6 +47,7 @@ var InMemoryOpenCodeSessionStore = class {
|
|
|
47
47
|
};
|
|
48
48
|
|
|
49
49
|
// src/mcp.controller.ts
|
|
50
|
+
var import_nestjs_agent_core = require("@dudousxd/nestjs-agent-core");
|
|
50
51
|
var import_common = require("@nestjs/common");
|
|
51
52
|
|
|
52
53
|
// src/tokens.ts
|
|
@@ -143,6 +144,7 @@ _ts_decorate([
|
|
|
143
144
|
_ts_metadata("design:returntype", void 0)
|
|
144
145
|
], OpenCodeMcpController.prototype, "delete", null);
|
|
145
146
|
OpenCodeMcpController = _ts_decorate([
|
|
147
|
+
(0, import_nestjs_agent_core.CanAnyone)(),
|
|
146
148
|
(0, import_common.Controller)("opencode/mcp"),
|
|
147
149
|
_ts_param(0, (0, import_common.Inject)(OPENCODE_MCP_ENDPOINT)),
|
|
148
150
|
_ts_metadata("design:type", Function),
|
|
@@ -153,7 +155,7 @@ OpenCodeMcpController = _ts_decorate([
|
|
|
153
155
|
|
|
154
156
|
// src/mcp.ts
|
|
155
157
|
var import_node_crypto = require("crypto");
|
|
156
|
-
var
|
|
158
|
+
var import_nestjs_agent_core4 = require("@dudousxd/nestjs-agent-core");
|
|
157
159
|
var import_genui = require("@dudousxd/nestjs-agent-core/genui");
|
|
158
160
|
var import_server = require("@modelcontextprotocol/sdk/server/index.js");
|
|
159
161
|
var import_streamableHttp = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
@@ -161,11 +163,11 @@ var import_zod_json_schema_compat = require("@modelcontextprotocol/sdk/server/zo
|
|
|
161
163
|
var import_types = require("@modelcontextprotocol/sdk/types.js");
|
|
162
164
|
|
|
163
165
|
// src/turn.ts
|
|
164
|
-
var
|
|
166
|
+
var import_nestjs_agent_core3 = require("@dudousxd/nestjs-agent-core");
|
|
165
167
|
var import_common2 = require("@nestjs/common");
|
|
166
168
|
|
|
167
169
|
// src/forms.ts
|
|
168
|
-
var
|
|
170
|
+
var import_nestjs_agent_core2 = require("@dudousxd/nestjs-agent-core");
|
|
169
171
|
function toQuestion(field) {
|
|
170
172
|
const required = field.required === true ? {
|
|
171
173
|
required: true
|
|
@@ -235,7 +237,7 @@ function toFormAnswer(form, answers = {}) {
|
|
|
235
237
|
for (const field of formFields(form)) {
|
|
236
238
|
const question = toQuestion(field);
|
|
237
239
|
const values = answers[field.key] ?? question.defaults ?? [];
|
|
238
|
-
const problem = (0,
|
|
240
|
+
const problem = (0, import_nestjs_agent_core2.validateElicitationAnswer)(question, values);
|
|
239
241
|
if (problem) throw new FormAnswerError(`answers["${field.key}"] ${problem}`);
|
|
240
242
|
const present = values.filter((v) => v !== "");
|
|
241
243
|
const [value] = present;
|
|
@@ -547,7 +549,7 @@ var OpenCodeTurn = class {
|
|
|
547
549
|
});
|
|
548
550
|
}
|
|
549
551
|
async write(event) {
|
|
550
|
-
await this.a.writer.write((0,
|
|
552
|
+
await this.a.writer.write((0, import_nestjs_agent_core3.encodeStreamEvent)(event));
|
|
551
553
|
}
|
|
552
554
|
async step() {
|
|
553
555
|
if (this.stepOpen) return;
|
|
@@ -1081,7 +1083,7 @@ ${delta}` : delta;
|
|
|
1081
1083
|
const ttlMs = requirement.ttlMs !== void 0 && Number.isFinite(requirement.ttlMs) && requirement.ttlMs > 0 ? Math.floor(requirement.ttlMs) : void 0;
|
|
1082
1084
|
const expiresAt = ttlMs !== void 0 ? new Date(Date.now() + ttlMs).toISOString() : void 0;
|
|
1083
1085
|
const tool = this.a.approvalPresentation?.(action);
|
|
1084
|
-
const confirmation = tool !== void 0 ? (0,
|
|
1086
|
+
const confirmation = tool !== void 0 ? (0, import_nestjs_agent_core3.resolveToolConfirmation)(tool.name, args, tool.presentation) : void 0;
|
|
1085
1087
|
this.segment.calls.push({
|
|
1086
1088
|
id: req.id,
|
|
1087
1089
|
name: action,
|
|
@@ -1308,7 +1310,7 @@ ${delta}` : delta;
|
|
|
1308
1310
|
answer: toFormAnswer(form, answer.answers)
|
|
1309
1311
|
});
|
|
1310
1312
|
}
|
|
1311
|
-
const result = (0,
|
|
1313
|
+
const result = (0, import_nestjs_agent_core3.settleElicitation)(request, answer);
|
|
1312
1314
|
await this.a.store.updateToolCall({
|
|
1313
1315
|
toolCallId: form.id,
|
|
1314
1316
|
status: result.skipped ? "rejected" : "executed",
|
|
@@ -1663,7 +1665,7 @@ function isZodSchema(schema) {
|
|
|
1663
1665
|
}
|
|
1664
1666
|
__name(isZodSchema, "isZodSchema");
|
|
1665
1667
|
function toInputSchema(schema) {
|
|
1666
|
-
const extension = (0,
|
|
1668
|
+
const extension = (0, import_nestjs_agent_core4.schemaExtensionOf)(schema);
|
|
1667
1669
|
if (extension !== void 0) {
|
|
1668
1670
|
const base = toInputSchema(extension.base);
|
|
1669
1671
|
const properties = {
|
|
@@ -1707,7 +1709,7 @@ var OpenCodeMcpEndpoint2 = class {
|
|
|
1707
1709
|
turns;
|
|
1708
1710
|
tokens;
|
|
1709
1711
|
/** Genui telemetry of the UI calls OpenCode makes (its code mode calls tools here, not in the loop). */
|
|
1710
|
-
genui = new
|
|
1712
|
+
genui = new import_nestjs_agent_core4.GenuiRunRecorder();
|
|
1711
1713
|
constructor(turns, tokens) {
|
|
1712
1714
|
this.turns = turns;
|
|
1713
1715
|
this.tokens = tokens;
|
|
@@ -1788,9 +1790,9 @@ var OpenCodeMcpEndpoint2 = class {
|
|
|
1788
1790
|
}));
|
|
1789
1791
|
if (this.turns.memoryWritable()) {
|
|
1790
1792
|
tools.push({
|
|
1791
|
-
name:
|
|
1792
|
-
description:
|
|
1793
|
-
inputSchema: toInputSchema(
|
|
1793
|
+
name: import_nestjs_agent_core4.REMEMBER_TOOL_NAME,
|
|
1794
|
+
description: import_nestjs_agent_core4.REMEMBER_TOOL_DESCRIPTION,
|
|
1795
|
+
inputSchema: toInputSchema(import_nestjs_agent_core4.rememberInputSchema),
|
|
1794
1796
|
annotations: {
|
|
1795
1797
|
readOnlyHint: false
|
|
1796
1798
|
}
|
|
@@ -1807,7 +1809,7 @@ var OpenCodeMcpEndpoint2 = class {
|
|
|
1807
1809
|
if (turn === void 0) {
|
|
1808
1810
|
throw new OpenCodeToolRefusedError("This endpoint serves OpenCode turns: no turn of yours is running on this session.");
|
|
1809
1811
|
}
|
|
1810
|
-
const output = name ===
|
|
1812
|
+
const output = name === import_nestjs_agent_core4.REMEMBER_TOOL_NAME && this.turns.memoryWritable() ? await this.remember(turn, args) : await this.invoke(turn, actor, name, args, String(extra.requestId ?? ""));
|
|
1811
1813
|
return {
|
|
1812
1814
|
content: [
|
|
1813
1815
|
{
|
|
@@ -1833,9 +1835,9 @@ var OpenCodeMcpEndpoint2 = class {
|
|
|
1833
1835
|
async invoke(turn, actor, name, args, requestId) {
|
|
1834
1836
|
const { registry } = this.turns;
|
|
1835
1837
|
const spec = registry.spec(name);
|
|
1836
|
-
if (spec === void 0 || LOOP_SERVED_KINDS.has(spec.kind)) throw new
|
|
1838
|
+
if (spec === void 0 || LOOP_SERVED_KINDS.has(spec.kind)) throw new import_nestjs_agent_core4.ToolNotFoundError(name);
|
|
1837
1839
|
const allowed = this.turns.allowedTools(turn.input);
|
|
1838
|
-
if (allowed !== void 0 && !allowed.includes(name)) throw new
|
|
1840
|
+
if (allowed !== void 0 && !allowed.includes(name)) throw new import_nestjs_agent_core4.ToolNotFoundError(name);
|
|
1839
1841
|
if (spec.kind === "action" && !await this.turns.spendApproval(turn.runId, name, turn.input)) {
|
|
1840
1842
|
throw new OpenCodeToolRefusedError(`"${name}" is an action and nobody approved this call; ask for the permission first.`);
|
|
1841
1843
|
}
|
|
@@ -1877,7 +1879,7 @@ var OpenCodeMcpEndpoint2 = class {
|
|
|
1877
1879
|
}
|
|
1878
1880
|
/** `remember`: one fact, at the actor's own scope only — as the loop serves it. */
|
|
1879
1881
|
async remember(turn, args) {
|
|
1880
|
-
const parsed = await
|
|
1882
|
+
const parsed = await import_nestjs_agent_core4.rememberInputSchema["~standard"].validate(args ?? {});
|
|
1881
1883
|
if (parsed.issues !== void 0) {
|
|
1882
1884
|
throw new OpenCodeToolRefusedError(`remember: ${parsed.issues.map((issue) => issue.message).join("; ")}`);
|
|
1883
1885
|
}
|
|
@@ -1886,13 +1888,13 @@ var OpenCodeMcpEndpoint2 = class {
|
|
|
1886
1888
|
};
|
|
1887
1889
|
|
|
1888
1890
|
// src/runner.ts
|
|
1889
|
-
var
|
|
1891
|
+
var import_nestjs_agent_core6 = require("@dudousxd/nestjs-agent-core");
|
|
1890
1892
|
var import_common5 = require("@nestjs/common");
|
|
1891
1893
|
|
|
1892
1894
|
// src/turns.ts
|
|
1893
1895
|
var import_node_crypto2 = require("crypto");
|
|
1894
1896
|
var import_nestjs_agent = require("@dudousxd/nestjs-agent");
|
|
1895
|
-
var
|
|
1897
|
+
var import_nestjs_agent_core5 = require("@dudousxd/nestjs-agent-core");
|
|
1896
1898
|
var import_common4 = require("@nestjs/common");
|
|
1897
1899
|
|
|
1898
1900
|
// src/event-hub.ts
|
|
@@ -2007,6 +2009,37 @@ var OpenCodeEventHub = class _OpenCodeEventHub {
|
|
|
2007
2009
|
}
|
|
2008
2010
|
};
|
|
2009
2011
|
|
|
2012
|
+
// src/turn-limits.ts
|
|
2013
|
+
var STOPPED_ERROR_CODE = "opencode_stopped";
|
|
2014
|
+
function stopErrorText(stop) {
|
|
2015
|
+
switch (stop.reason) {
|
|
2016
|
+
case "steps":
|
|
2017
|
+
return `The turn was stopped after ${stop.counts.steps - 1} steps, its limit.`;
|
|
2018
|
+
case "compactions":
|
|
2019
|
+
return `The turn was stopped after ${stop.counts.compactions - 1} compactions, its limit.`;
|
|
2020
|
+
case "wall-clock":
|
|
2021
|
+
return "The turn was stopped: it ran longer than allowed.";
|
|
2022
|
+
default:
|
|
2023
|
+
return "The turn was stopped.";
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
__name(stopErrorText, "stopErrorText");
|
|
2027
|
+
function cleanLimits(limits) {
|
|
2028
|
+
const ok = /* @__PURE__ */ __name((n) => typeof n === "number" && Number.isFinite(n) && n >= 0, "ok");
|
|
2029
|
+
return {
|
|
2030
|
+
...ok(limits?.steps) ? {
|
|
2031
|
+
steps: limits.steps
|
|
2032
|
+
} : {},
|
|
2033
|
+
...ok(limits?.compactions) ? {
|
|
2034
|
+
compactions: limits.compactions
|
|
2035
|
+
} : {},
|
|
2036
|
+
...ok(limits?.wallClockMs) ? {
|
|
2037
|
+
wallClockMs: limits.wallClockMs
|
|
2038
|
+
} : {}
|
|
2039
|
+
};
|
|
2040
|
+
}
|
|
2041
|
+
__name(cleanLimits, "cleanLimits");
|
|
2042
|
+
|
|
2010
2043
|
// src/turns.ts
|
|
2011
2044
|
function _ts_decorate2(decorators, target, key, desc) {
|
|
2012
2045
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
@@ -2036,6 +2069,16 @@ function _ts_param2(paramIndex, decorator) {
|
|
|
2036
2069
|
__name(_ts_param2, "_ts_param");
|
|
2037
2070
|
var DEFAULT_TURN_TIMEOUT_MS = 30 * 6e4;
|
|
2038
2071
|
var DEFAULT_TOOLS_TTL_MS = 7 * 24 * 60 * 6e4;
|
|
2072
|
+
var DEFAULT_HOOK_TIMEOUT_MS = 5e3;
|
|
2073
|
+
var PARKED_RECHECKS_MS = [
|
|
2074
|
+
250,
|
|
2075
|
+
1e3,
|
|
2076
|
+
3e3,
|
|
2077
|
+
1e4,
|
|
2078
|
+
3e4
|
|
2079
|
+
];
|
|
2080
|
+
var PARKED_READ_GAP_MS = 250;
|
|
2081
|
+
var RELEASED_USAGE_MS = 10 * 6e4;
|
|
2039
2082
|
var OpenCodeTurns = class {
|
|
2040
2083
|
static {
|
|
2041
2084
|
__name(this, "OpenCodeTurns");
|
|
@@ -2059,6 +2102,21 @@ var OpenCodeTurns = class {
|
|
|
2059
2102
|
spent = /* @__PURE__ */ new Set();
|
|
2060
2103
|
/** When the tools endpoint was last registered for a location (`server|boot|directory|actor`). */
|
|
2061
2104
|
toolsIssued = /* @__PURE__ */ new Map();
|
|
2105
|
+
/**
|
|
2106
|
+
* What a turn let go of here had spent, for a settle that follows here (a cancel in this process
|
|
2107
|
+
* that settles after the turn was released). Dropped when the run settles here, or after
|
|
2108
|
+
* {@link RELEASED_USAGE_MS} when it settles elsewhere, so no run is kept for good.
|
|
2109
|
+
*/
|
|
2110
|
+
releasedUsage = /* @__PURE__ */ new Map();
|
|
2111
|
+
keepReleasedUsage(runId, usage) {
|
|
2112
|
+
this.releasedUsage.set(runId, usage);
|
|
2113
|
+
setTimeout(() => {
|
|
2114
|
+
if (this.releasedUsage.get(runId) === usage) this.releasedUsage.delete(runId);
|
|
2115
|
+
}, RELEASED_USAGE_MS).unref?.();
|
|
2116
|
+
}
|
|
2117
|
+
/** The turns this process executes, and which turn owns each session (`server|session`). */
|
|
2118
|
+
watches = /* @__PURE__ */ new Map();
|
|
2119
|
+
owners = /* @__PURE__ */ new Map();
|
|
2062
2120
|
constructor(host, sessions, settings, store, sink, deps, registry, skills, memory, queue, toolsTokens2, pricingStore, agentOptions) {
|
|
2063
2121
|
this.host = host;
|
|
2064
2122
|
this.sessions = sessions;
|
|
@@ -2071,7 +2129,11 @@ var OpenCodeTurns = class {
|
|
|
2071
2129
|
this.memory = memory;
|
|
2072
2130
|
this.queue = queue;
|
|
2073
2131
|
this.toolsTokens = toolsTokens2;
|
|
2074
|
-
|
|
2132
|
+
const hookTimeoutMs = settings.hookTimeoutMs;
|
|
2133
|
+
if (hookTimeoutMs !== void 0 && !(Number.isFinite(hookTimeoutMs) && hookTimeoutMs > 0)) {
|
|
2134
|
+
throw new Error(`hookTimeoutMs must be a positive number of milliseconds, not ${hookTimeoutMs}`);
|
|
2135
|
+
}
|
|
2136
|
+
this.prices = new import_nestjs_agent_core5.ModelPriceResolver({
|
|
2075
2137
|
pricingStore,
|
|
2076
2138
|
catalog: agentOptions?.priceCatalog,
|
|
2077
2139
|
log: {
|
|
@@ -2091,10 +2153,10 @@ var OpenCodeTurns = class {
|
|
|
2091
2153
|
const tokens = usage.inputTokens + usage.outputTokens;
|
|
2092
2154
|
const reported = reportedCostUsd !== void 0 && (reportedCostUsd > 0 || tokens === 0);
|
|
2093
2155
|
if (reported && this.settings.cost !== "estimate") {
|
|
2094
|
-
return (0,
|
|
2156
|
+
return (0, import_nestjs_agent_core5.resolveUsageCost)(usage, reportedCostUsd, void 0);
|
|
2095
2157
|
}
|
|
2096
2158
|
const price = model !== void 0 && tokens > 0 ? await this.prices.priceFor(priceRef(model)) : void 0;
|
|
2097
|
-
return (0,
|
|
2159
|
+
return (0, import_nestjs_agent_core5.resolveUsageCost)(usage, price === void 0 ? reportedCostUsd : void 0, price);
|
|
2098
2160
|
}
|
|
2099
2161
|
/** The tools server's name in OpenCode. */
|
|
2100
2162
|
get toolsServer() {
|
|
@@ -2149,7 +2211,7 @@ var OpenCodeTurns = class {
|
|
|
2149
2211
|
const memory = this.memory;
|
|
2150
2212
|
const write = memory?.provider.write?.bind(memory.provider);
|
|
2151
2213
|
if (memory === void 0 || write === void 0) return "Memory is read-only here.";
|
|
2152
|
-
const max = memory.maxFactChars ??
|
|
2214
|
+
const max = memory.maxFactChars ?? import_nestjs_agent_core5.DEFAULT_MAX_FACT_CHARS;
|
|
2153
2215
|
if (input.fact.length > max) return `Not recorded: a fact is at most ${max} characters.`;
|
|
2154
2216
|
const actor = turn.input.actor;
|
|
2155
2217
|
const scopeCtx = {
|
|
@@ -2159,9 +2221,9 @@ var OpenCodeTurns = class {
|
|
|
2159
2221
|
agentName: turn.input.agentName
|
|
2160
2222
|
} : {}
|
|
2161
2223
|
};
|
|
2162
|
-
const scopes = await (memory.scopes ??
|
|
2163
|
-
const scope = (0,
|
|
2164
|
-
const verdict = (0,
|
|
2224
|
+
const scopes = await (memory.scopes ?? import_nestjs_agent_core5.defaultScopeResolver).resolve(scopeCtx);
|
|
2225
|
+
const scope = (0, import_nestjs_agent_core5.actorScope)(actor);
|
|
2226
|
+
const verdict = (0, import_nestjs_agent_core5.memoryWriteVerdict)({
|
|
2165
2227
|
scope,
|
|
2166
2228
|
scopes,
|
|
2167
2229
|
actor,
|
|
@@ -2243,7 +2305,7 @@ var OpenCodeTurns = class {
|
|
|
2243
2305
|
runId,
|
|
2244
2306
|
emitUi: uiEmitter(scope(runId), async (component) => {
|
|
2245
2307
|
const writer = await this.sink.open(runId);
|
|
2246
|
-
await writer.write((0,
|
|
2308
|
+
await writer.write((0, import_nestjs_agent_core5.encodeStreamEvent)({
|
|
2247
2309
|
kind: "ui",
|
|
2248
2310
|
...component
|
|
2249
2311
|
}));
|
|
@@ -2342,8 +2404,12 @@ var OpenCodeTurns = class {
|
|
|
2342
2404
|
}
|
|
2343
2405
|
}
|
|
2344
2406
|
onApplicationShutdown() {
|
|
2407
|
+
for (const runId of [
|
|
2408
|
+
...this.watches.keys()
|
|
2409
|
+
]) this.release(runId, "released");
|
|
2345
2410
|
for (const live of this.live.values()) live.stop();
|
|
2346
2411
|
this.live.clear();
|
|
2412
|
+
this.owners.clear();
|
|
2347
2413
|
this.events.close();
|
|
2348
2414
|
}
|
|
2349
2415
|
// ---- begin ---------------------------------------------------------------------------------
|
|
@@ -2353,7 +2419,8 @@ var OpenCodeTurns = class {
|
|
|
2353
2419
|
* durable `begin` re-run after a crash): the run's user message is written once.
|
|
2354
2420
|
*/
|
|
2355
2421
|
async begin(runId, input) {
|
|
2356
|
-
const
|
|
2422
|
+
const startedAt = this.watches.get(runId)?.startedAt ?? Date.now();
|
|
2423
|
+
const { earlier, rerun } = await this.prepareThread(runId, input);
|
|
2357
2424
|
const server = await this.host.server(input.actor);
|
|
2358
2425
|
const known = await this.sessions.get(input.threadId);
|
|
2359
2426
|
const reusable = known !== null && known.serverKey === server.key && known.bootId === server.bootId && (await this.host.reuse?.({
|
|
@@ -2399,7 +2466,12 @@ var OpenCodeTurns = class {
|
|
|
2399
2466
|
created
|
|
2400
2467
|
});
|
|
2401
2468
|
await this.refreshInstructions(runId, input, server.client, handle.sessionId);
|
|
2402
|
-
|
|
2469
|
+
const begun = this.limitsFor(input).wallClockMs !== void 0 ? {
|
|
2470
|
+
...handle,
|
|
2471
|
+
startedAt
|
|
2472
|
+
} : handle;
|
|
2473
|
+
await this.adopt(runId, input, begun, rerun, void 0, startedAt);
|
|
2474
|
+
return begun;
|
|
2403
2475
|
}
|
|
2404
2476
|
async createSession(runId, input, server, earlier, note) {
|
|
2405
2477
|
const { client } = server;
|
|
@@ -2469,10 +2541,16 @@ ${transcript}` : "",
|
|
|
2469
2541
|
}
|
|
2470
2542
|
const firstDropped = messages[lastUser + 1];
|
|
2471
2543
|
if (firstDropped !== void 0) await this.store.truncateFrom(input.threadId, firstDropped.id);
|
|
2472
|
-
return
|
|
2544
|
+
return {
|
|
2545
|
+
earlier: messages.slice(0, Math.max(lastUser, 0)),
|
|
2546
|
+
rerun: false
|
|
2547
|
+
};
|
|
2473
2548
|
}
|
|
2474
2549
|
const mine = messages.findIndex((m) => m.role === "user" && m.runId === runId);
|
|
2475
|
-
if (mine >= 0) return
|
|
2550
|
+
if (mine >= 0) return {
|
|
2551
|
+
earlier: messages.slice(0, mine),
|
|
2552
|
+
rerun: true
|
|
2553
|
+
};
|
|
2476
2554
|
await this.store.appendMessage({
|
|
2477
2555
|
threadId: input.threadId,
|
|
2478
2556
|
role: "user",
|
|
@@ -2488,7 +2566,10 @@ ${transcript}` : "",
|
|
|
2488
2566
|
if (thread !== null && (thread.title === "" || thread.title === "New chat")) {
|
|
2489
2567
|
await this.store.setTitle(input.threadId, titleOf(input.userText));
|
|
2490
2568
|
}
|
|
2491
|
-
return
|
|
2569
|
+
return {
|
|
2570
|
+
earlier: messages,
|
|
2571
|
+
rerun: false
|
|
2572
|
+
};
|
|
2492
2573
|
}
|
|
2493
2574
|
/** Rewind the session to before its last user message, so the regenerated answer replaces it. */
|
|
2494
2575
|
async revertLastExchange(client, sessionId) {
|
|
@@ -2571,12 +2652,12 @@ ${transcript}` : "",
|
|
|
2571
2652
|
const memory = this.memory;
|
|
2572
2653
|
if (memory === void 0) return void 0;
|
|
2573
2654
|
const ctx = this.scopeContext(input);
|
|
2574
|
-
const scopes = await (memory.scopes ??
|
|
2655
|
+
const scopes = await (memory.scopes ?? import_nestjs_agent_core5.defaultScopeResolver).resolve(ctx);
|
|
2575
2656
|
const records = await memory.provider.list({
|
|
2576
2657
|
scopes,
|
|
2577
2658
|
ctx
|
|
2578
2659
|
});
|
|
2579
|
-
const digest = (0,
|
|
2660
|
+
const digest = (0, import_nestjs_agent_core5.resolveMemoryDigest)({
|
|
2580
2661
|
records,
|
|
2581
2662
|
scopes,
|
|
2582
2663
|
...memory.maxMemories !== void 0 ? {
|
|
@@ -2584,7 +2665,7 @@ ${transcript}` : "",
|
|
|
2584
2665
|
} : {}
|
|
2585
2666
|
});
|
|
2586
2667
|
if (digest.entries.length === 0) return void 0;
|
|
2587
|
-
return (0,
|
|
2668
|
+
return (0, import_nestjs_agent_core5.buildMemoryBlock)({
|
|
2588
2669
|
entries: digest.entries,
|
|
2589
2670
|
writable: this.memoryWritable(),
|
|
2590
2671
|
partial: digest.omitted > 0
|
|
@@ -2598,12 +2679,12 @@ ${transcript}` : "",
|
|
|
2598
2679
|
const skills = this.skills;
|
|
2599
2680
|
if (skills === void 0 || client.file === void 0 || directory === void 0) return [];
|
|
2600
2681
|
const ctx = this.scopeContext(input);
|
|
2601
|
-
const scopes = await (skills.scopes ??
|
|
2682
|
+
const scopes = await (skills.scopes ?? import_nestjs_agent_core5.defaultScopeResolver).resolve(ctx);
|
|
2602
2683
|
const summaries = await skills.provider.list({
|
|
2603
2684
|
scopes,
|
|
2604
2685
|
ctx
|
|
2605
2686
|
});
|
|
2606
|
-
const { entries } = (0,
|
|
2687
|
+
const { entries } = (0, import_nestjs_agent_core5.resolveSkillCatalog)(summaries, scopes, skills.maxSkills);
|
|
2607
2688
|
const dir = this.settings.skillsDir ?? ".opencode/skills";
|
|
2608
2689
|
const written = [];
|
|
2609
2690
|
for (const entry of entries) {
|
|
@@ -2704,6 +2785,7 @@ ${body}
|
|
|
2704
2785
|
// ---- prompt / observe / reply --------------------------------------------------------------
|
|
2705
2786
|
/** Start listening to the session (so nothing it says is missed), then send the user message. */
|
|
2706
2787
|
async prompt(runId, input, handle, spent) {
|
|
2788
|
+
await this.adopt(runId, input, handle, true);
|
|
2707
2789
|
const live = await this.ensureLive(runId, input, handle, false, spent);
|
|
2708
2790
|
try {
|
|
2709
2791
|
const prompt = await this.host.promptFor?.({
|
|
@@ -2746,7 +2828,24 @@ ${body}
|
|
|
2746
2828
|
* back from the session's messages, when the execution ends, what nobody heard after that.
|
|
2747
2829
|
*/
|
|
2748
2830
|
async observe(runId, input, handle, spent, progress) {
|
|
2831
|
+
const watch = await this.adopt(runId, input, handle, true, progress?.counts);
|
|
2749
2832
|
const live = await this.ensureLive(runId, input, handle, true, spent, progress);
|
|
2833
|
+
watch.parkedOn = void 0;
|
|
2834
|
+
watch.parkedEnd = void 0;
|
|
2835
|
+
watch.series = false;
|
|
2836
|
+
watch.observing = true;
|
|
2837
|
+
const wall = this.watchClock(watch);
|
|
2838
|
+
try {
|
|
2839
|
+
if (watch.pending !== void 0) await this.requestStop(runId, watch.pending);
|
|
2840
|
+
const milestone = this.marked(watch, await this.observeLive(runId, input, handle, live));
|
|
2841
|
+
if (milestone.kind === "ask") watch.parkedOn = milestone.ask.id;
|
|
2842
|
+
return milestone;
|
|
2843
|
+
} finally {
|
|
2844
|
+
watch.observing = false;
|
|
2845
|
+
if (wall !== void 0) clearTimeout(wall);
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2848
|
+
async observeLive(runId, input, handle, live) {
|
|
2750
2849
|
const { turn, client } = live;
|
|
2751
2850
|
if (turn.hasMilestone()) return this.reached(runId, input, handle, await turn.next());
|
|
2752
2851
|
let done = false;
|
|
@@ -2786,19 +2885,64 @@ ${body}
|
|
|
2786
2885
|
* to go on.
|
|
2787
2886
|
*/
|
|
2788
2887
|
async reply(runId, input, handle, ask, reply, spent, progress) {
|
|
2888
|
+
const watch = await this.adopt(runId, input, handle, true, progress?.counts);
|
|
2889
|
+
watch.parkedOn = void 0;
|
|
2890
|
+
watch.parkedEnd = void 0;
|
|
2891
|
+
watch.series = false;
|
|
2892
|
+
const stopping = watch.pending !== void 0 || watch.stop !== void 0;
|
|
2893
|
+
const answer = stopping ? refusalOf(ask) : reply;
|
|
2789
2894
|
const server = await this.host.server(input.actor);
|
|
2790
2895
|
if (server.key !== handle.serverKey || server.bootId !== handle.bootId) {
|
|
2791
|
-
return this.
|
|
2896
|
+
if (stopping) return this.stopAfterRestart(watch, runId, input, handle, ask, answer, spent);
|
|
2897
|
+
return this.continueAfterRestart(runId, input, server, ask, answer, spent, handle.startedAt);
|
|
2792
2898
|
}
|
|
2793
2899
|
const live = await this.ensureLive(runId, input, handle, false, spent, progress);
|
|
2794
|
-
await live.turn.decide(ask,
|
|
2900
|
+
await live.turn.decide(ask, answer);
|
|
2901
|
+
return handle;
|
|
2902
|
+
}
|
|
2903
|
+
/**
|
|
2904
|
+
* A stopping turn whose OpenCode server restarted while it waited: there is no session left to
|
|
2905
|
+
* abort, and none is opened or prompted. The ask is settled as refused and the turn ends stopped.
|
|
2906
|
+
*/
|
|
2907
|
+
async stopAfterRestart(watch, runId, input, handle, ask, answer, spent) {
|
|
2908
|
+
const live = await this.ensureLive(runId, input, handle, false, spent);
|
|
2909
|
+
await live.turn.decide(ask, answer, {
|
|
2910
|
+
tellOpenCode: false
|
|
2911
|
+
});
|
|
2912
|
+
const reason = watch.pending ?? watch.stop?.reason ?? "stopped";
|
|
2913
|
+
if (watch.stop === void 0) {
|
|
2914
|
+
const stop = {
|
|
2915
|
+
reason,
|
|
2916
|
+
counts: this.counts(watch)
|
|
2917
|
+
};
|
|
2918
|
+
watch.stop = stop;
|
|
2919
|
+
this.hook("onStop", {
|
|
2920
|
+
turnId: runId,
|
|
2921
|
+
reason,
|
|
2922
|
+
counts: stop.counts
|
|
2923
|
+
});
|
|
2924
|
+
}
|
|
2925
|
+
watch.aborted = true;
|
|
2926
|
+
watch.pending = void 0;
|
|
2927
|
+
live.turn.handle({
|
|
2928
|
+
type: "session.execution.interrupted",
|
|
2929
|
+
data: {
|
|
2930
|
+
sessionID: handle.sessionId
|
|
2931
|
+
}
|
|
2932
|
+
});
|
|
2795
2933
|
return handle;
|
|
2796
2934
|
}
|
|
2797
|
-
async continueAfterRestart(runId, input, server, ask, reply, spent) {
|
|
2935
|
+
async continueAfterRestart(runId, input, server, ask, reply, spent, startedAt) {
|
|
2798
2936
|
const approved = "approved" in reply && reply.approved === true;
|
|
2799
2937
|
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.`;
|
|
2800
2938
|
const thread = await this.store.getThread(input.threadId);
|
|
2801
|
-
const
|
|
2939
|
+
const created = await this.createSession(runId, input, server, thread?.messages ?? [], note);
|
|
2940
|
+
const handle = {
|
|
2941
|
+
...created,
|
|
2942
|
+
...startedAt !== void 0 ? {
|
|
2943
|
+
startedAt
|
|
2944
|
+
} : {}
|
|
2945
|
+
};
|
|
2802
2946
|
await this.refreshInstructions(runId, input, server.client, handle.sessionId);
|
|
2803
2947
|
this.drop(runId);
|
|
2804
2948
|
const live = await this.ensureLive(runId, input, handle, false, spent);
|
|
@@ -2842,7 +2986,7 @@ ${body}
|
|
|
2842
2986
|
current?.stop();
|
|
2843
2987
|
const server = await this.host.server(input.actor);
|
|
2844
2988
|
const deps = this.deps.forAgent(input.agentName);
|
|
2845
|
-
const policy = deps.approvalPolicy ?? new
|
|
2989
|
+
const policy = deps.approvalPolicy ?? new import_nestjs_agent_core5.DefaultApprovalPolicy();
|
|
2846
2990
|
const writer = await this.sink.open(input.sinkRunId ?? runId);
|
|
2847
2991
|
const turn = new OpenCodeTurn({
|
|
2848
2992
|
runId,
|
|
@@ -2883,7 +3027,11 @@ ${body}
|
|
|
2883
3027
|
this.grants.set(runId, grants);
|
|
2884
3028
|
}, "onGranted")
|
|
2885
3029
|
});
|
|
2886
|
-
const
|
|
3030
|
+
const sessionId = handle.sessionId;
|
|
3031
|
+
const stop = await this.events.listen(server.key, server.client, sessionId, (event) => {
|
|
3032
|
+
turn.handle(event);
|
|
3033
|
+
this.sawEvent(runId, sessionId, event);
|
|
3034
|
+
});
|
|
2887
3035
|
const live = {
|
|
2888
3036
|
turn,
|
|
2889
3037
|
input,
|
|
@@ -2892,6 +3040,15 @@ ${body}
|
|
|
2892
3040
|
stop
|
|
2893
3041
|
};
|
|
2894
3042
|
this.live.set(runId, live);
|
|
3043
|
+
this.owners.set(ownerKey(handle), runId);
|
|
3044
|
+
const watch = this.watches.get(runId);
|
|
3045
|
+
if (watch !== void 0 && watch.handle.sessionId !== sessionId) {
|
|
3046
|
+
watch.handle = {
|
|
3047
|
+
...handle,
|
|
3048
|
+
startedAt: watch.startedAt
|
|
3049
|
+
};
|
|
3050
|
+
watch.ending = false;
|
|
3051
|
+
}
|
|
2895
3052
|
if (resumed) await turn.catchUp().catch(() => void 0);
|
|
2896
3053
|
return live;
|
|
2897
3054
|
}
|
|
@@ -2900,11 +3057,46 @@ ${body}
|
|
|
2900
3057
|
this.live.get(runId)?.stop();
|
|
2901
3058
|
this.live.delete(runId);
|
|
2902
3059
|
}
|
|
2903
|
-
/**
|
|
3060
|
+
/**
|
|
3061
|
+
* Stop following a run and drop the approvals it was granted: it is over. Returns the turn this
|
|
3062
|
+
* process executed, now settled (no stop reaches it any more), for its `onTurnEnd`.
|
|
3063
|
+
*/
|
|
2904
3064
|
forget(runId) {
|
|
2905
3065
|
this.drop(runId);
|
|
3066
|
+
this.releasedUsage.delete(runId);
|
|
2906
3067
|
for (const grant of this.grants.get(runId) ?? []) this.spent.delete(grant.id);
|
|
2907
3068
|
this.grants.delete(runId);
|
|
3069
|
+
const watch = this.watches.get(runId);
|
|
3070
|
+
if (watch === void 0) return void 0;
|
|
3071
|
+
watch.settled = true;
|
|
3072
|
+
this.watches.delete(runId);
|
|
3073
|
+
for (const [key, owner] of this.owners) if (owner === runId) this.owners.delete(key);
|
|
3074
|
+
return watch;
|
|
3075
|
+
}
|
|
3076
|
+
/**
|
|
3077
|
+
* A run that ended without settling here (cancelled while this process executed it; whoever
|
|
3078
|
+
* cancelled settled it): stop following it, and end the turn this process executed.
|
|
3079
|
+
*/
|
|
3080
|
+
async abandon(runId) {
|
|
3081
|
+
const watch = this.forget(runId);
|
|
3082
|
+
if (watch !== void 0) this.ended(watch, "interrupted", void 0);
|
|
3083
|
+
}
|
|
3084
|
+
/**
|
|
3085
|
+
* Let go of a turn this process executed without settling it here: it was cancelled while parked
|
|
3086
|
+
* (its session ended under it, `interrupted`), another worker took it over, or this process shuts
|
|
3087
|
+
* down (`released`). The per-turn state goes, `onTurnEnd` fires once, and a later stop of the turn
|
|
3088
|
+
* here is ignored. Nothing settles the run here afterwards: whoever answered or cancelled it does.
|
|
3089
|
+
*/
|
|
3090
|
+
release(runId, status) {
|
|
3091
|
+
const watch = this.watches.get(runId);
|
|
3092
|
+
if (watch === void 0) return;
|
|
3093
|
+
const seen = this.live.get(runId)?.turn.totalUsage();
|
|
3094
|
+
if (seen !== void 0) this.keepReleasedUsage(runId, seen);
|
|
3095
|
+
this.drop(runId);
|
|
3096
|
+
watch.settled = true;
|
|
3097
|
+
this.watches.delete(runId);
|
|
3098
|
+
for (const [key, owner] of this.owners) if (owner === runId) this.owners.delete(key);
|
|
3099
|
+
this.ended(watch, status, void 0);
|
|
2908
3100
|
}
|
|
2909
3101
|
// ---- settle --------------------------------------------------------------------------------
|
|
2910
3102
|
/** A person was asked something: tell the host (cards in other channels, wake-ups). */
|
|
@@ -2952,7 +3144,7 @@ ${body}
|
|
|
2952
3144
|
const ui = amendment.ui ?? [];
|
|
2953
3145
|
const last = result.messages.at(-1);
|
|
2954
3146
|
if (ui.length > 0) {
|
|
2955
|
-
for (const component of ui) await writer.write((0,
|
|
3147
|
+
for (const component of ui) await writer.write((0, import_nestjs_agent_core5.encodeStreamEvent)({
|
|
2956
3148
|
kind: "ui",
|
|
2957
3149
|
...component
|
|
2958
3150
|
}));
|
|
@@ -2977,7 +3169,7 @@ ${body}
|
|
|
2977
3169
|
* two milestones.
|
|
2978
3170
|
*/
|
|
2979
3171
|
spentBy(runId, spent) {
|
|
2980
|
-
const seen = this.live.get(runId)?.turn.totalUsage();
|
|
3172
|
+
const seen = this.live.get(runId)?.turn.totalUsage() ?? this.releasedUsage.get(runId);
|
|
2981
3173
|
if (spent === void 0) return seen ?? emptyUsage();
|
|
2982
3174
|
if (seen === void 0) return spent;
|
|
2983
3175
|
const tokens = /* @__PURE__ */ __name((u) => u.inputTokens + u.outputTokens, "tokens");
|
|
@@ -2987,30 +3179,30 @@ ${body}
|
|
|
2987
3179
|
* End the run's stream and bookkeeping for how its execution ended. `spent` is what the run spent
|
|
2988
3180
|
* (the sum of its milestones' `usage`); omit → what the turn this process follows saw.
|
|
2989
3181
|
*/
|
|
2990
|
-
async settle(runId, input,
|
|
3182
|
+
async settle(runId, input, ended, durationMs, spent) {
|
|
3183
|
+
const outcome = this.withStop(ended, this.watches.get(runId));
|
|
2991
3184
|
if (outcome.status === "failed") {
|
|
2992
|
-
|
|
2993
|
-
return;
|
|
3185
|
+
return this.settleFailed(runId, input, outcome.error, durationMs, spent, outcome.stop);
|
|
2994
3186
|
}
|
|
2995
3187
|
const usage = this.spentBy(runId, spent);
|
|
2996
|
-
this.forget(runId);
|
|
3188
|
+
const watch = this.forget(runId);
|
|
2997
3189
|
const writer = await this.sink.open(runId);
|
|
2998
3190
|
const result = await this.runResult(runId, input, outcome, durationMs, usage);
|
|
2999
3191
|
await this.amend(writer, result);
|
|
3000
3192
|
if (outcome.status === "interrupted") {
|
|
3001
3193
|
this.logger.log(`agent run ${runId} cancelled`);
|
|
3002
|
-
await Promise.resolve(this.store.failUnsettledToolCalls?.(runId,
|
|
3194
|
+
await Promise.resolve(this.store.failUnsettledToolCalls?.(runId, import_nestjs_agent_core5.RUN_ENDED_BEFORE_TOOL_CALL)).catch(() => 0);
|
|
3003
3195
|
await this.handoff(writer, input, runId, "cancelled");
|
|
3004
|
-
await (0,
|
|
3005
|
-
await writer.write((0,
|
|
3196
|
+
await (0, import_nestjs_agent_core5.releaseThreadRun)(this.store, input.threadId, runId);
|
|
3197
|
+
await writer.write((0, import_nestjs_agent_core5.encodeStreamEvent)({
|
|
3006
3198
|
kind: "cancelled"
|
|
3007
3199
|
}));
|
|
3008
3200
|
await writer.end();
|
|
3009
|
-
(0,
|
|
3201
|
+
(0, import_nestjs_agent_core5.publishAgentRunCancelled)({
|
|
3010
3202
|
runId,
|
|
3011
3203
|
threadId: input.threadId,
|
|
3012
3204
|
durationMs,
|
|
3013
|
-
context: (0,
|
|
3205
|
+
context: (0, import_nestjs_agent_core5.agentRunContext)(input)
|
|
3014
3206
|
});
|
|
3015
3207
|
await this.store.recordRunEnd?.({
|
|
3016
3208
|
runId,
|
|
@@ -3019,7 +3211,7 @@ ${body}
|
|
|
3019
3211
|
});
|
|
3020
3212
|
} else {
|
|
3021
3213
|
await this.handoff(writer, input, runId, "completed");
|
|
3022
|
-
await (0,
|
|
3214
|
+
await (0, import_nestjs_agent_core5.releaseThreadRun)(this.store, input.threadId, runId);
|
|
3023
3215
|
await writer.end();
|
|
3024
3216
|
await this.store.recordRunEnd?.({
|
|
3025
3217
|
runId,
|
|
@@ -3028,47 +3220,402 @@ ${body}
|
|
|
3028
3220
|
});
|
|
3029
3221
|
}
|
|
3030
3222
|
await this.settled(result);
|
|
3223
|
+
if (watch !== void 0) this.ended(watch, outcome.status, outcome.status === "succeeded" ? outcome.stop : void 0);
|
|
3224
|
+
return outcome;
|
|
3031
3225
|
}
|
|
3032
|
-
async settleFailed(runId, input, error, durationMs = 0, spent) {
|
|
3226
|
+
async settleFailed(runId, input, error, durationMs = 0, spent, stop) {
|
|
3033
3227
|
const usage = this.spentBy(runId, spent);
|
|
3034
|
-
this.forget(runId);
|
|
3228
|
+
const watch = this.forget(runId);
|
|
3229
|
+
const stopped = stop ?? watch?.stop;
|
|
3035
3230
|
const writer = await this.sink.open(runId);
|
|
3036
3231
|
const result = await this.runResult(runId, input, {
|
|
3037
3232
|
status: "failed",
|
|
3038
|
-
error
|
|
3233
|
+
error,
|
|
3234
|
+
...stopped !== void 0 ? {
|
|
3235
|
+
stop: stopped
|
|
3236
|
+
} : {}
|
|
3039
3237
|
}, durationMs, usage);
|
|
3040
3238
|
const worded = (await this.amend(writer, result)).error;
|
|
3041
|
-
const message = worded ?? (0,
|
|
3042
|
-
|
|
3043
|
-
(0
|
|
3239
|
+
const message = worded ?? (stopped !== void 0 ? error : (0, import_nestjs_agent_core5.streamFailure)(new Error(error)).message);
|
|
3240
|
+
const code = stopped !== void 0 ? STOPPED_ERROR_CODE : "opencode_failed";
|
|
3241
|
+
if (stopped !== void 0) this.logger.log(`agent run ${runId} stopped: ${stopped.reason}`);
|
|
3242
|
+
else this.logger.error(`agent run ${runId} failed: ${error}`);
|
|
3243
|
+
(0, import_nestjs_agent_core5.publishAgentRunFailed)({
|
|
3044
3244
|
runId,
|
|
3045
3245
|
threadId: input.threadId,
|
|
3046
|
-
code
|
|
3246
|
+
code,
|
|
3047
3247
|
message: error,
|
|
3048
3248
|
durationMs,
|
|
3049
|
-
context: (0,
|
|
3249
|
+
context: (0, import_nestjs_agent_core5.agentRunContext)(input)
|
|
3050
3250
|
});
|
|
3051
3251
|
await this.store.recordRunEnd?.({
|
|
3052
3252
|
runId,
|
|
3053
3253
|
status: "failed",
|
|
3054
3254
|
durationMs,
|
|
3055
|
-
errorCode:
|
|
3255
|
+
errorCode: code,
|
|
3056
3256
|
errorMessage: error
|
|
3057
3257
|
});
|
|
3058
|
-
await Promise.resolve(this.store.failUnsettledToolCalls?.(runId,
|
|
3258
|
+
await Promise.resolve(this.store.failUnsettledToolCalls?.(runId, import_nestjs_agent_core5.RUN_ENDED_BEFORE_TOOL_CALL)).catch(() => 0);
|
|
3059
3259
|
await this.handoff(writer, input, runId, "failed", error);
|
|
3060
|
-
await (0,
|
|
3260
|
+
await (0, import_nestjs_agent_core5.releaseThreadRun)(this.store, input.threadId, runId);
|
|
3061
3261
|
await writer.fail({
|
|
3062
|
-
code
|
|
3262
|
+
code,
|
|
3063
3263
|
message
|
|
3064
3264
|
});
|
|
3065
3265
|
await this.settled({
|
|
3066
3266
|
...result,
|
|
3067
3267
|
outcome: {
|
|
3068
3268
|
status: "failed",
|
|
3069
|
-
error: worded ?? error
|
|
3269
|
+
error: worded ?? error,
|
|
3270
|
+
...stopped !== void 0 ? {
|
|
3271
|
+
stop: stopped
|
|
3272
|
+
} : {}
|
|
3273
|
+
}
|
|
3274
|
+
});
|
|
3275
|
+
if (watch !== void 0) this.ended(watch, "failed", stopped);
|
|
3276
|
+
return {
|
|
3277
|
+
status: "failed",
|
|
3278
|
+
error,
|
|
3279
|
+
...stopped !== void 0 ? {
|
|
3280
|
+
stop: stopped
|
|
3281
|
+
} : {}
|
|
3282
|
+
};
|
|
3283
|
+
}
|
|
3284
|
+
// ---- limits, stops and the turn's lifecycle -------------------------------------------------
|
|
3285
|
+
/**
|
|
3286
|
+
* Stop a turn's own work: abort its session so OpenCode starts no new round, only while the turn
|
|
3287
|
+
* still owns the session (a thread's session is reused by its next turn) — checked immediately
|
|
3288
|
+
* before the abort. Acts in the process executing the turn; see {@link OpenCodeStopResult}. The
|
|
3289
|
+
* turn ends failed with `outcome.stop` (code `opencode_stopped`), and `onStop` is called.
|
|
3290
|
+
*/
|
|
3291
|
+
stopTurn(turnId, reason) {
|
|
3292
|
+
return this.requestStop(turnId, reason);
|
|
3293
|
+
}
|
|
3294
|
+
/** The turn this process executes, made known here on its first step in this process. */
|
|
3295
|
+
async adopt(runId, input, handle, resumed, counts, startedAt) {
|
|
3296
|
+
const known = this.watches.get(runId);
|
|
3297
|
+
if (known !== void 0) return known;
|
|
3298
|
+
const watch = {
|
|
3299
|
+
runId,
|
|
3300
|
+
input,
|
|
3301
|
+
handle,
|
|
3302
|
+
limits: this.limitsFor(input),
|
|
3303
|
+
startedAt: handle.startedAt ?? startedAt ?? Date.now(),
|
|
3304
|
+
steps: counts?.steps ?? 0,
|
|
3305
|
+
compactions: counts?.compactions ?? 0,
|
|
3306
|
+
observing: false,
|
|
3307
|
+
ending: false,
|
|
3308
|
+
settled: false,
|
|
3309
|
+
aborted: false,
|
|
3310
|
+
checking: false,
|
|
3311
|
+
lastRead: 0
|
|
3312
|
+
};
|
|
3313
|
+
this.watches.set(runId, watch);
|
|
3314
|
+
await this.hook("onTurnStart", {
|
|
3315
|
+
turnId: runId,
|
|
3316
|
+
threadId: input.threadId,
|
|
3317
|
+
sessionId: handle.sessionId,
|
|
3318
|
+
resumed
|
|
3319
|
+
});
|
|
3320
|
+
return watch;
|
|
3321
|
+
}
|
|
3322
|
+
limitsFor(input) {
|
|
3323
|
+
const limits = this.settings.limits;
|
|
3324
|
+
return cleanLimits(typeof limits === "function" ? limits(input) : limits);
|
|
3325
|
+
}
|
|
3326
|
+
/**
|
|
3327
|
+
* One event of the turn's session: counted for the limits, and told to `onTurnEvent`. While the
|
|
3328
|
+
* turn is parked on a person here, it may also say the turn went on without this process
|
|
3329
|
+
* ({@link wentOnElsewhere}).
|
|
3330
|
+
*/
|
|
3331
|
+
sawEvent(runId, sessionId, event) {
|
|
3332
|
+
if (sessionOf(event) !== sessionId) return;
|
|
3333
|
+
const watch = this.watches.get(runId);
|
|
3334
|
+
if (watch === void 0 || watch.settled || watch.handle.sessionId !== sessionId) return;
|
|
3335
|
+
if (watch.parkedOn !== void 0 && this.wentOnElsewhere(watch, event)) return;
|
|
3336
|
+
let reached;
|
|
3337
|
+
if (event.type.startsWith("session.execution.")) watch.ending = true;
|
|
3338
|
+
else if (event.type === "session.step.started") {
|
|
3339
|
+
watch.steps += 1;
|
|
3340
|
+
if (passed(watch.steps, watch.limits.steps)) reached = "steps";
|
|
3341
|
+
} else if (event.type === "session.compaction.started") {
|
|
3342
|
+
watch.compactions += 1;
|
|
3343
|
+
if (passed(watch.compactions, watch.limits.compactions)) reached = "compactions";
|
|
3344
|
+
}
|
|
3345
|
+
this.hook("onTurnEvent", {
|
|
3346
|
+
turnId: runId,
|
|
3347
|
+
type: event.type,
|
|
3348
|
+
sessionId
|
|
3349
|
+
});
|
|
3350
|
+
if (reached !== void 0) void this.limitStop(runId, reached);
|
|
3351
|
+
}
|
|
3352
|
+
/**
|
|
3353
|
+
* Whether a turn parked here went on without this process, which then lets it go. While a tool of
|
|
3354
|
+
* the step waits on a person, OpenCode keeps running the step's other tools, so their events say
|
|
3355
|
+
* nothing. What does: a new round (`session.step.started`, which cannot begin while the step
|
|
3356
|
+
* waits), or the store showing the parked ask decided by someone else (another worker answered
|
|
3357
|
+
* it, or a cancel elsewhere settled it) — this process clears the park before it answers. When
|
|
3358
|
+
* the session's execution ends while parked, the store is asked again for a while, as a cancel
|
|
3359
|
+
* elsewhere settles the ask just after it interrupted the session; with the ask still open, the
|
|
3360
|
+
* turn stays here for its answer.
|
|
3361
|
+
*/
|
|
3362
|
+
wentOnElsewhere(watch, event) {
|
|
3363
|
+
if (event.type === "session.step.started") {
|
|
3364
|
+
this.release(watch.runId, "released");
|
|
3365
|
+
return true;
|
|
3366
|
+
}
|
|
3367
|
+
const ended = event.type.startsWith("session.execution.");
|
|
3368
|
+
if (ended) watch.parkedEnd = event.type === "session.execution.interrupted" ? "interrupted" : "released";
|
|
3369
|
+
this.checkParked(watch, ended);
|
|
3370
|
+
return false;
|
|
3371
|
+
}
|
|
3372
|
+
/**
|
|
3373
|
+
* Ask the store whether the parked ask was decided elsewhere, and let the turn go when it was —
|
|
3374
|
+
* one read at a time, at most one every {@link PARKED_READ_GAP_MS}. `series`: the session's
|
|
3375
|
+
* execution ended, so the store is asked again over {@link parkedRechecksMs}. An execution that
|
|
3376
|
+
* was interrupted under a parked turn means a cancel elsewhere: when the store cannot say (it has
|
|
3377
|
+
* no `toolCallApproval`, the read fails, the ask is not on file) past the first recheck, or the
|
|
3378
|
+
* rechecks run out with the ask still open, the turn is let go as `interrupted` anyway.
|
|
3379
|
+
*/
|
|
3380
|
+
checkParked(watch, series) {
|
|
3381
|
+
if (series) watch.series = true;
|
|
3382
|
+
if (watch.checking) {
|
|
3383
|
+
watch.again = true;
|
|
3384
|
+
return;
|
|
3385
|
+
}
|
|
3386
|
+
watch.checking = true;
|
|
3387
|
+
void this.runParkedChecks(watch);
|
|
3388
|
+
}
|
|
3389
|
+
/**
|
|
3390
|
+
* The checks of {@link checkParked}. `checking` is cleared in the same step the loop returns in,
|
|
3391
|
+
* so a check asked for afterwards starts a new loop; one asked for while it ran (`again`) but not
|
|
3392
|
+
* read because the loop returned starts one too.
|
|
3393
|
+
*/
|
|
3394
|
+
async runParkedChecks(watch) {
|
|
3395
|
+
try {
|
|
3396
|
+
await this.parkedChecks(watch);
|
|
3397
|
+
} finally {
|
|
3398
|
+
watch.checking = false;
|
|
3399
|
+
if (watch.again && !watch.settled && watch.parkedOn !== void 0) {
|
|
3400
|
+
watch.checking = true;
|
|
3401
|
+
void this.runParkedChecks(watch);
|
|
3070
3402
|
}
|
|
3403
|
+
}
|
|
3404
|
+
}
|
|
3405
|
+
async parkedChecks(watch) {
|
|
3406
|
+
const ask = watch.parkedOn;
|
|
3407
|
+
const gone = /* @__PURE__ */ __name(() => watch.settled || ask === void 0 || watch.parkedOn !== ask, "gone");
|
|
3408
|
+
let step = 0;
|
|
3409
|
+
for (; ; ) {
|
|
3410
|
+
watch.again = false;
|
|
3411
|
+
const wait = watch.lastRead + PARKED_READ_GAP_MS - Date.now();
|
|
3412
|
+
if (wait > 0) await sleep(wait);
|
|
3413
|
+
if (gone()) return;
|
|
3414
|
+
const answer = await this.parkedAsk(ask);
|
|
3415
|
+
watch.lastRead = Date.now();
|
|
3416
|
+
if (gone()) return;
|
|
3417
|
+
if (answer === "decided") {
|
|
3418
|
+
this.release(watch.runId, watch.parkedEnd ?? "released");
|
|
3419
|
+
return;
|
|
3420
|
+
}
|
|
3421
|
+
if (watch.series) {
|
|
3422
|
+
const cancelled = watch.parkedEnd === "interrupted";
|
|
3423
|
+
if (cancelled && answer === "unknown" && step > 0) {
|
|
3424
|
+
this.release(watch.runId, "interrupted");
|
|
3425
|
+
return;
|
|
3426
|
+
}
|
|
3427
|
+
const gap = this.parkedRechecksMs[step];
|
|
3428
|
+
step += 1;
|
|
3429
|
+
if (gap === void 0) {
|
|
3430
|
+
if (cancelled) this.release(watch.runId, "interrupted");
|
|
3431
|
+
return;
|
|
3432
|
+
}
|
|
3433
|
+
await sleep(gap);
|
|
3434
|
+
continue;
|
|
3435
|
+
}
|
|
3436
|
+
if (!watch.again) return;
|
|
3437
|
+
}
|
|
3438
|
+
}
|
|
3439
|
+
/** What the store says of an ask: decided, still open, or nothing it can tell. */
|
|
3440
|
+
async parkedAsk(id) {
|
|
3441
|
+
if (this.store.toolCallApproval === void 0) return "unknown";
|
|
3442
|
+
try {
|
|
3443
|
+
const state = await this.store.toolCallApproval(id);
|
|
3444
|
+
if (state == null) return "unknown";
|
|
3445
|
+
return state.status === "pending_approval" ? "open" : "decided";
|
|
3446
|
+
} catch {
|
|
3447
|
+
return "unknown";
|
|
3448
|
+
}
|
|
3449
|
+
}
|
|
3450
|
+
/**
|
|
3451
|
+
* The waits between the store's rereads of a parked ask after its execution ended (cumulative
|
|
3452
|
+
* 0.25, 1.25, 4.25, 14.25 and 44.25 seconds after the first). Overridden by tests only.
|
|
3453
|
+
*
|
|
3454
|
+
* @internal
|
|
3455
|
+
*/
|
|
3456
|
+
parkedRechecksMs = PARKED_RECHECKS_MS;
|
|
3457
|
+
/** A limit's stop: what it did is the engine's to log, there is no caller to tell. */
|
|
3458
|
+
async limitStop(runId, reason) {
|
|
3459
|
+
const result = await this.requestStop(runId, reason);
|
|
3460
|
+
if (result === "unconfirmed") this.logger.warn(`run ${runId}: passed its ${reason} limit, but its owner could not be read`);
|
|
3461
|
+
}
|
|
3462
|
+
/** The wall-clock bound of an observation: a timer for what is left of it, if anything. */
|
|
3463
|
+
watchClock(watch) {
|
|
3464
|
+
const max = watch.limits.wallClockMs;
|
|
3465
|
+
if (max === void 0) return void 0;
|
|
3466
|
+
const left = watch.startedAt + max - Date.now();
|
|
3467
|
+
if (left <= 0) {
|
|
3468
|
+
watch.pending ??= "wall-clock";
|
|
3469
|
+
return void 0;
|
|
3470
|
+
}
|
|
3471
|
+
const timer = setTimeout(() => void this.limitStop(watch.runId, "wall-clock"), left);
|
|
3472
|
+
timer.unref?.();
|
|
3473
|
+
return timer;
|
|
3474
|
+
}
|
|
3475
|
+
counts(watch) {
|
|
3476
|
+
return {
|
|
3477
|
+
steps: watch.steps,
|
|
3478
|
+
compactions: watch.compactions,
|
|
3479
|
+
elapsedMs: Date.now() - watch.startedAt
|
|
3480
|
+
};
|
|
3481
|
+
}
|
|
3482
|
+
async requestStop(runId, reason) {
|
|
3483
|
+
const watch = this.watches.get(runId);
|
|
3484
|
+
if (watch === void 0 || watch.settled || watch.stop !== void 0) return "ignored";
|
|
3485
|
+
const live = this.live.get(runId);
|
|
3486
|
+
if (watch.parkedOn !== void 0) {
|
|
3487
|
+
watch.pending ??= reason;
|
|
3488
|
+
return "pending";
|
|
3489
|
+
}
|
|
3490
|
+
if (watch.ending || live?.turn.finished === true) return this.landed(watch, reason);
|
|
3491
|
+
if (!watch.observing || live === void 0) {
|
|
3492
|
+
watch.pending ??= reason;
|
|
3493
|
+
return "pending";
|
|
3494
|
+
}
|
|
3495
|
+
watch.pending = void 0;
|
|
3496
|
+
let active;
|
|
3497
|
+
try {
|
|
3498
|
+
active = await this.activeRun(watch.input.threadId);
|
|
3499
|
+
} catch (error) {
|
|
3500
|
+
this.logger.warn(`run ${runId}: could not read the thread's run: ${errorText(error, "error")}`);
|
|
3501
|
+
return "unconfirmed";
|
|
3502
|
+
}
|
|
3503
|
+
if (watch.settled || watch.stop !== void 0 || this.live.get(runId) !== live) return "ignored";
|
|
3504
|
+
if (active !== null && active !== runId) return "ignored";
|
|
3505
|
+
if (this.owners.get(ownerKey(watch.handle)) !== runId) return "ignored";
|
|
3506
|
+
if (watch.ending || live.turn.finished) return this.landed(watch, reason);
|
|
3507
|
+
const stop = {
|
|
3508
|
+
reason,
|
|
3509
|
+
counts: this.counts(watch)
|
|
3510
|
+
};
|
|
3511
|
+
watch.stop = stop;
|
|
3512
|
+
watch.aborted = true;
|
|
3513
|
+
try {
|
|
3514
|
+
await live.client.session.interrupt({
|
|
3515
|
+
sessionID: watch.handle.sessionId
|
|
3516
|
+
});
|
|
3517
|
+
} catch (error) {
|
|
3518
|
+
this.logger.warn(`run ${runId}: could not abort the session: ${errorText(error, "error")}`);
|
|
3519
|
+
}
|
|
3520
|
+
this.hook("onStop", {
|
|
3521
|
+
turnId: runId,
|
|
3522
|
+
reason,
|
|
3523
|
+
counts: stop.counts
|
|
3071
3524
|
});
|
|
3525
|
+
return "aborted";
|
|
3526
|
+
}
|
|
3527
|
+
/** A stop that arrived while the answer settles: recorded and reported, the answer kept. */
|
|
3528
|
+
landed(watch, reason) {
|
|
3529
|
+
const stop = {
|
|
3530
|
+
reason,
|
|
3531
|
+
counts: this.counts(watch)
|
|
3532
|
+
};
|
|
3533
|
+
watch.stop = stop;
|
|
3534
|
+
watch.pending = void 0;
|
|
3535
|
+
this.hook("onStop", {
|
|
3536
|
+
turnId: watch.runId,
|
|
3537
|
+
reason,
|
|
3538
|
+
counts: stop.counts
|
|
3539
|
+
});
|
|
3540
|
+
return "settling";
|
|
3541
|
+
}
|
|
3542
|
+
/** The milestone as the runner journals it: the limits' counts on an ask, the stop on an end. */
|
|
3543
|
+
marked(watch, milestone) {
|
|
3544
|
+
if (milestone.kind === "finished") return {
|
|
3545
|
+
...milestone,
|
|
3546
|
+
outcome: this.withStop(milestone.outcome, watch)
|
|
3547
|
+
};
|
|
3548
|
+
if (milestone.progress === void 0) return milestone;
|
|
3549
|
+
const counts = {
|
|
3550
|
+
steps: watch.steps,
|
|
3551
|
+
compactions: watch.compactions
|
|
3552
|
+
};
|
|
3553
|
+
return {
|
|
3554
|
+
...milestone,
|
|
3555
|
+
progress: {
|
|
3556
|
+
...milestone.progress,
|
|
3557
|
+
counts
|
|
3558
|
+
}
|
|
3559
|
+
};
|
|
3560
|
+
}
|
|
3561
|
+
/**
|
|
3562
|
+
* How a turn a stop landed on ends. An answer that settled anyway is kept, with the stop on it; an
|
|
3563
|
+
* execution that failed on its own keeps its error, with the stop on it; an execution the stop
|
|
3564
|
+
* aborted ends failed with the stop's own text. One interrupted by a person's cancel stays so.
|
|
3565
|
+
*/
|
|
3566
|
+
withStop(outcome, watch) {
|
|
3567
|
+
const stop = watch?.stop;
|
|
3568
|
+
if (stop === void 0) return outcome;
|
|
3569
|
+
if (outcome.status !== "interrupted") return outcome.stop !== void 0 ? outcome : {
|
|
3570
|
+
...outcome,
|
|
3571
|
+
stop
|
|
3572
|
+
};
|
|
3573
|
+
if (!watch?.aborted) return outcome;
|
|
3574
|
+
return {
|
|
3575
|
+
status: "failed",
|
|
3576
|
+
error: stopErrorText(stop),
|
|
3577
|
+
stop
|
|
3578
|
+
};
|
|
3579
|
+
}
|
|
3580
|
+
/** `onTurnEnd`, once per turn this process executed; not waited for. */
|
|
3581
|
+
ended(watch, status, stop) {
|
|
3582
|
+
this.hook("onTurnEnd", {
|
|
3583
|
+
turnId: watch.runId,
|
|
3584
|
+
status,
|
|
3585
|
+
...stop !== void 0 ? {
|
|
3586
|
+
stop
|
|
3587
|
+
} : {}
|
|
3588
|
+
});
|
|
3589
|
+
}
|
|
3590
|
+
/**
|
|
3591
|
+
* A lifecycle hook, from the settings and the host, called at once. The promise settles when both
|
|
3592
|
+
* have, or after `hookTimeoutMs` (default 5 s) — only `onTurnStart` is waited for. Errors are
|
|
3593
|
+
* logged, never the turn's.
|
|
3594
|
+
*/
|
|
3595
|
+
hook(name, event) {
|
|
3596
|
+
const calls = [];
|
|
3597
|
+
for (const target of [
|
|
3598
|
+
this.settings,
|
|
3599
|
+
this.host
|
|
3600
|
+
]) {
|
|
3601
|
+
const fn = target[name];
|
|
3602
|
+
if (typeof fn !== "function") continue;
|
|
3603
|
+
calls.push(Promise.resolve().then(() => fn.call(target, event)).catch((error) => this.logger.warn(`${name} failed: ${errorText(error, "error")}`)));
|
|
3604
|
+
}
|
|
3605
|
+
if (calls.length === 0) return Promise.resolve();
|
|
3606
|
+
const ms = this.settings.hookTimeoutMs ?? DEFAULT_HOOK_TIMEOUT_MS;
|
|
3607
|
+
let timer;
|
|
3608
|
+
const late = new Promise((resolve) => {
|
|
3609
|
+
timer = setTimeout(() => {
|
|
3610
|
+
this.logger.warn(`${name} took longer than ${ms}ms; the turn went on`);
|
|
3611
|
+
resolve();
|
|
3612
|
+
}, ms);
|
|
3613
|
+
timer.unref?.();
|
|
3614
|
+
});
|
|
3615
|
+
return Promise.race([
|
|
3616
|
+
Promise.all(calls).then(() => void 0),
|
|
3617
|
+
late
|
|
3618
|
+
]).finally(() => clearTimeout(timer));
|
|
3072
3619
|
}
|
|
3073
3620
|
/** Starts the next queued message of the thread; set by the runner (it owns `start`). */
|
|
3074
3621
|
startNext;
|
|
@@ -3087,7 +3634,7 @@ ${body}
|
|
|
3087
3634
|
error
|
|
3088
3635
|
} : {}
|
|
3089
3636
|
}, (next, nextRunId) => startNext(next, nextRunId));
|
|
3090
|
-
if (frame !== void 0) await writer.write((0,
|
|
3637
|
+
if (frame !== void 0) await writer.write((0, import_nestjs_agent_core5.encodeStreamEvent)(frame));
|
|
3091
3638
|
} catch (failure) {
|
|
3092
3639
|
this.logger.error(`could not advance the queue of thread ${input.threadId} after run ${runId}: ${failure instanceof Error ? failure.message : String(failure)}`);
|
|
3093
3640
|
}
|
|
@@ -3098,22 +3645,22 @@ OpenCodeTurns = _ts_decorate2([
|
|
|
3098
3645
|
_ts_param2(0, (0, import_common4.Inject)(OPENCODE_HOST)),
|
|
3099
3646
|
_ts_param2(1, (0, import_common4.Inject)(OPENCODE_SESSIONS)),
|
|
3100
3647
|
_ts_param2(2, (0, import_common4.Inject)(OPENCODE_OPTIONS)),
|
|
3101
|
-
_ts_param2(3, (0, import_common4.Inject)(
|
|
3102
|
-
_ts_param2(4, (0, import_common4.Inject)(
|
|
3103
|
-
_ts_param2(5, (0, import_common4.Inject)(
|
|
3104
|
-
_ts_param2(6, (0, import_common4.Inject)(
|
|
3648
|
+
_ts_param2(3, (0, import_common4.Inject)(import_nestjs_agent_core5.AGENT_STORE)),
|
|
3649
|
+
_ts_param2(4, (0, import_common4.Inject)(import_nestjs_agent_core5.AGENT_SINK)),
|
|
3650
|
+
_ts_param2(5, (0, import_common4.Inject)(import_nestjs_agent_core5.AGENT_DEPS_FACTORY)),
|
|
3651
|
+
_ts_param2(6, (0, import_common4.Inject)(import_nestjs_agent_core5.AGENT_TOOL_REGISTRY)),
|
|
3105
3652
|
_ts_param2(7, (0, import_common4.Optional)()),
|
|
3106
|
-
_ts_param2(7, (0, import_common4.Inject)(
|
|
3653
|
+
_ts_param2(7, (0, import_common4.Inject)(import_nestjs_agent_core5.AGENT_SKILLS)),
|
|
3107
3654
|
_ts_param2(8, (0, import_common4.Optional)()),
|
|
3108
|
-
_ts_param2(8, (0, import_common4.Inject)(
|
|
3655
|
+
_ts_param2(8, (0, import_common4.Inject)(import_nestjs_agent_core5.AGENT_MEMORY)),
|
|
3109
3656
|
_ts_param2(9, (0, import_common4.Optional)()),
|
|
3110
3657
|
_ts_param2(9, (0, import_common4.Inject)(import_nestjs_agent.AGENT_CHAT_QUEUE)),
|
|
3111
3658
|
_ts_param2(10, (0, import_common4.Optional)()),
|
|
3112
3659
|
_ts_param2(10, (0, import_common4.Inject)(OPENCODE_TOOLS_TOKENS)),
|
|
3113
3660
|
_ts_param2(11, (0, import_common4.Optional)()),
|
|
3114
|
-
_ts_param2(11, (0, import_common4.Inject)(
|
|
3661
|
+
_ts_param2(11, (0, import_common4.Inject)(import_nestjs_agent_core5.AGENT_PRICING_STORE)),
|
|
3115
3662
|
_ts_param2(12, (0, import_common4.Optional)()),
|
|
3116
|
-
_ts_param2(12, (0, import_common4.Inject)(
|
|
3663
|
+
_ts_param2(12, (0, import_common4.Inject)(import_nestjs_agent_core5.AGENT_OPTIONS)),
|
|
3117
3664
|
_ts_metadata2("design:type", Function),
|
|
3118
3665
|
_ts_metadata2("design:paramtypes", [
|
|
3119
3666
|
typeof OpenCodeHost === "undefined" ? Object : OpenCodeHost,
|
|
@@ -3131,6 +3678,28 @@ OpenCodeTurns = _ts_decorate2([
|
|
|
3131
3678
|
Object
|
|
3132
3679
|
])
|
|
3133
3680
|
], OpenCodeTurns);
|
|
3681
|
+
function passed(count, limit) {
|
|
3682
|
+
return limit !== void 0 && count > limit;
|
|
3683
|
+
}
|
|
3684
|
+
__name(passed, "passed");
|
|
3685
|
+
function sleep(ms) {
|
|
3686
|
+
return new Promise((resolve) => setTimeout(resolve, ms).unref?.());
|
|
3687
|
+
}
|
|
3688
|
+
__name(sleep, "sleep");
|
|
3689
|
+
function refusalOf(ask) {
|
|
3690
|
+
return ask.kind === "approval" ? {
|
|
3691
|
+
approved: false,
|
|
3692
|
+
reason: "The turn was stopped."
|
|
3693
|
+
} : {
|
|
3694
|
+
answers: {},
|
|
3695
|
+
skipped: true
|
|
3696
|
+
};
|
|
3697
|
+
}
|
|
3698
|
+
__name(refusalOf, "refusalOf");
|
|
3699
|
+
function ownerKey(handle) {
|
|
3700
|
+
return `${handle.serverKey}|${handle.sessionId}`;
|
|
3701
|
+
}
|
|
3702
|
+
__name(ownerKey, "ownerKey");
|
|
3134
3703
|
function titleOf(text) {
|
|
3135
3704
|
const trimmed = text.trim().replace(/\s+/g, " ");
|
|
3136
3705
|
return trimmed.length > 60 ? `${trimmed.slice(0, 57)}...` : trimmed || "New chat";
|
|
@@ -3175,7 +3744,7 @@ function priceRef(model) {
|
|
|
3175
3744
|
model.id
|
|
3176
3745
|
];
|
|
3177
3746
|
const tail = model.id.slice(model.id.lastIndexOf("/") + 1);
|
|
3178
|
-
if (tail !== model.id && (0,
|
|
3747
|
+
if (tail !== model.id && (0, import_nestjs_agent_core5.parseBedrockModelId)(tail) !== void 0) aliases.push(tail);
|
|
3179
3748
|
return {
|
|
3180
3749
|
modelId: `${model.providerID}/${model.id}`,
|
|
3181
3750
|
provider: model.providerID,
|
|
@@ -3275,7 +3844,7 @@ var OpenCodeAgentRunner = class {
|
|
|
3275
3844
|
]) {
|
|
3276
3845
|
if (!key.startsWith(`${runId}:`)) continue;
|
|
3277
3846
|
this.pending.delete(key);
|
|
3278
|
-
waiter.reject(new
|
|
3847
|
+
waiter.reject(new import_nestjs_agent_core6.RunCancelledError());
|
|
3279
3848
|
}
|
|
3280
3849
|
const input = this.inputs.get(runId);
|
|
3281
3850
|
if (input !== void 0) await this.turns.interrupt(input).catch(() => void 0);
|
|
@@ -3287,7 +3856,7 @@ var OpenCodeAgentRunner = class {
|
|
|
3287
3856
|
let spent = emptyUsage();
|
|
3288
3857
|
try {
|
|
3289
3858
|
let handle = await this.turns.begin(runId, input);
|
|
3290
|
-
if (this.cancelled.has(runId)) throw new
|
|
3859
|
+
if (this.cancelled.has(runId)) throw new import_nestjs_agent_core6.RunCancelledError();
|
|
3291
3860
|
await this.turns.prompt(runId, input, handle, spent);
|
|
3292
3861
|
for (; ; ) {
|
|
3293
3862
|
const milestone = await this.turns.observe(runId, input, handle, spent);
|
|
@@ -3300,11 +3869,11 @@ var OpenCodeAgentRunner = class {
|
|
|
3300
3869
|
handle = await this.turns.reply(runId, input, handle, milestone.ask, reply, spent);
|
|
3301
3870
|
}
|
|
3302
3871
|
} catch (error) {
|
|
3303
|
-
if (error instanceof
|
|
3872
|
+
if (error instanceof import_nestjs_agent_core6.RunCancelledError) {
|
|
3304
3873
|
await this.turns.interrupt(input).catch(() => void 0);
|
|
3305
3874
|
await this.turns.settle(runId, input, {
|
|
3306
3875
|
status: "interrupted"
|
|
3307
|
-
}, Date.now() - started);
|
|
3876
|
+
}, Date.now() - started, spent);
|
|
3308
3877
|
return;
|
|
3309
3878
|
}
|
|
3310
3879
|
await this.turns.settleFailed(runId, input, errorText(error, "the turn failed"), Date.now() - started);
|
|
@@ -3428,7 +3997,7 @@ __name(openCodeProviders, "openCodeProviders");
|
|
|
3428
3997
|
|
|
3429
3998
|
// src/durable/runner.ts
|
|
3430
3999
|
var import_node_crypto3 = require("crypto");
|
|
3431
|
-
var
|
|
4000
|
+
var import_nestjs_agent_core7 = require("@dudousxd/nestjs-agent-core");
|
|
3432
4001
|
var import_nestjs_durable2 = require("@dudousxd/nestjs-durable");
|
|
3433
4002
|
var import_nestjs_durable_core2 = require("@dudousxd/nestjs-durable-core");
|
|
3434
4003
|
var import_common8 = require("@nestjs/common");
|
|
@@ -3498,14 +4067,14 @@ var OpenCodeRunWorkflow = class {
|
|
|
3498
4067
|
spent = addUsage(spent, milestone.usage);
|
|
3499
4068
|
const total = spent;
|
|
3500
4069
|
if (milestone.kind === "finished") {
|
|
3501
|
-
await ctx.localStep("finish", async () => {
|
|
3502
|
-
if (await this.cancelled(ctx.runId))
|
|
3503
|
-
|
|
3504
|
-
|
|
4070
|
+
const settled = await ctx.localStep("finish", async () => {
|
|
4071
|
+
if (await this.cancelled(ctx.runId)) {
|
|
4072
|
+
await this.turns.abandon(ctx.runId);
|
|
4073
|
+
return milestone.outcome;
|
|
4074
|
+
}
|
|
4075
|
+
return this.turns.settle(ctx.runId, input, milestone.outcome, Date.now() - begun.startedAt, total);
|
|
3505
4076
|
});
|
|
3506
|
-
return
|
|
3507
|
-
outcome: milestone.outcome.status
|
|
3508
|
-
};
|
|
4077
|
+
return resultOf(settled === true ? milestone.outcome : settled);
|
|
3509
4078
|
}
|
|
3510
4079
|
let reply;
|
|
3511
4080
|
try {
|
|
@@ -3526,13 +4095,10 @@ var OpenCodeRunWorkflow = class {
|
|
|
3526
4095
|
}
|
|
3527
4096
|
} catch (error) {
|
|
3528
4097
|
if ((0, import_nestjs_durable_core.isWorkflowControlFlowSignal)(error)) throw error;
|
|
3529
|
-
await ctx.localStep("fail", async () =>
|
|
3530
|
-
|
|
3531
|
-
return true;
|
|
3532
|
-
});
|
|
3533
|
-
return {
|
|
4098
|
+
const settled = await ctx.localStep("fail", async () => this.turns.settleFailed(ctx.runId, input, errorText(error, "the turn failed"), 0, spent));
|
|
4099
|
+
return settled === true ? {
|
|
3534
4100
|
outcome: "failed"
|
|
3535
|
-
};
|
|
4101
|
+
} : resultOf(settled);
|
|
3536
4102
|
}
|
|
3537
4103
|
}
|
|
3538
4104
|
async cancelled(runId) {
|
|
@@ -3558,6 +4124,16 @@ OpenCodeRunWorkflow = _ts_decorate4([
|
|
|
3558
4124
|
typeof RunGateway === "undefined" ? Object : RunGateway
|
|
3559
4125
|
])
|
|
3560
4126
|
], OpenCodeRunWorkflow);
|
|
4127
|
+
function resultOf(outcome) {
|
|
4128
|
+
const stop = outcome.status === "interrupted" ? void 0 : outcome.stop;
|
|
4129
|
+
return {
|
|
4130
|
+
outcome: outcome.status,
|
|
4131
|
+
...stop !== void 0 ? {
|
|
4132
|
+
stop: stop.reason
|
|
4133
|
+
} : {}
|
|
4134
|
+
};
|
|
4135
|
+
}
|
|
4136
|
+
__name(resultOf, "resultOf");
|
|
3561
4137
|
|
|
3562
4138
|
// src/durable/runner.ts
|
|
3563
4139
|
function _ts_decorate5(decorators, target, key, desc) {
|
|
@@ -3677,7 +4253,7 @@ DurableOpenCodeAgentRunner = _ts_decorate5([
|
|
|
3677
4253
|
_ts_param5(1, (0, import_common8.Inject)(import_nestjs_durable2.RUN_GATEWAY)),
|
|
3678
4254
|
_ts_param5(3, (0, import_common8.Inject)(OPENCODE_OPTIONS)),
|
|
3679
4255
|
_ts_param5(4, (0, import_common8.Inject)(OPENCODE_HOST)),
|
|
3680
|
-
_ts_param5(5, (0, import_common8.Inject)(
|
|
4256
|
+
_ts_param5(5, (0, import_common8.Inject)(import_nestjs_agent_core7.AGENT_STORE)),
|
|
3681
4257
|
_ts_metadata5("design:type", Function),
|
|
3682
4258
|
_ts_metadata5("design:paramtypes", [
|
|
3683
4259
|
typeof import_nestjs_durable2.WorkflowService === "undefined" ? Object : import_nestjs_durable2.WorkflowService,
|