@dudousxd/nestjs-agent-opencode 0.3.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -3
- package/dist/durable/index.cjs +283 -14
- package/dist/durable/index.cjs.map +1 -1
- package/dist/durable/index.d.cts +3 -2
- package/dist/durable/index.d.ts +3 -2
- package/dist/durable/index.js +284 -15
- package/dist/durable/index.js.map +1 -1
- package/dist/{engine-Cp5uysaN.d.cts → engine-BX6MLzxs.d.cts} +109 -10
- package/dist/{engine-Cp5uysaN.d.ts → engine-BX6MLzxs.d.ts} +109 -10
- package/dist/index.cjs +279 -12
- 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 +279 -13
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/durable/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AgentEngine } from '@dudousxd/nestjs-agent';
|
|
2
|
-
import { O as OpenCodeTurns, a as OpenCodeEngineSettings, b as OpenCodeHost, c as OpenCodeEngineOptions } from '../engine-
|
|
2
|
+
import { O as OpenCodeTurns, a as OpenCodeEngineSettings, b as OpenCodeHost, c as OpenCodeEngineOptions } from '../engine-BX6MLzxs.cjs';
|
|
3
3
|
import { AgentRunner, AgentStore, AgentRunInput, AgentRunStartOptions, HumanReply } from '@dudousxd/nestjs-agent-core';
|
|
4
4
|
import { WorkflowService } from '@dudousxd/nestjs-durable';
|
|
5
5
|
import { RunGateway, WorkflowCtx } from '@dudousxd/nestjs-durable-core';
|
|
@@ -53,7 +53,8 @@ declare function decisionToken(runId: string, toolCallId: string): string;
|
|
|
53
53
|
* the steps from their checkpoints and replying to OpenCode from there (`OpenCodeTurns` rebuilds the
|
|
54
54
|
* live turn, and re-prompts a fresh session when OpenCode itself restarted meanwhile). A process
|
|
55
55
|
* that dies while OpenCode works re-runs that `observe` step, which catches up on what OpenCode
|
|
56
|
-
* asked while nobody listened
|
|
56
|
+
* asked while nobody listened, and — when the execution ends — on what it said: the text, tool
|
|
57
|
+
* calls and usage it streamed meanwhile, read back from the session's messages.
|
|
57
58
|
*/
|
|
58
59
|
declare class OpenCodeRunWorkflow {
|
|
59
60
|
private readonly turns;
|
package/dist/durable/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AgentEngine } from '@dudousxd/nestjs-agent';
|
|
2
|
-
import { O as OpenCodeTurns, a as OpenCodeEngineSettings, b as OpenCodeHost, c as OpenCodeEngineOptions } from '../engine-
|
|
2
|
+
import { O as OpenCodeTurns, a as OpenCodeEngineSettings, b as OpenCodeHost, c as OpenCodeEngineOptions } from '../engine-BX6MLzxs.js';
|
|
3
3
|
import { AgentRunner, AgentStore, AgentRunInput, AgentRunStartOptions, HumanReply } from '@dudousxd/nestjs-agent-core';
|
|
4
4
|
import { WorkflowService } from '@dudousxd/nestjs-durable';
|
|
5
5
|
import { RunGateway, WorkflowCtx } from '@dudousxd/nestjs-durable-core';
|
|
@@ -53,7 +53,8 @@ declare function decisionToken(runId: string, toolCallId: string): string;
|
|
|
53
53
|
* the steps from their checkpoints and replying to OpenCode from there (`OpenCodeTurns` rebuilds the
|
|
54
54
|
* live turn, and re-prompts a fresh session when OpenCode itself restarted meanwhile). A process
|
|
55
55
|
* that dies while OpenCode works re-runs that `observe` step, which catches up on what OpenCode
|
|
56
|
-
* asked while nobody listened
|
|
56
|
+
* asked while nobody listened, and — when the execution ends — on what it said: the text, tool
|
|
57
|
+
* calls and usage it streamed meanwhile, read back from the session's messages.
|
|
57
58
|
*/
|
|
58
59
|
declare class OpenCodeRunWorkflow {
|
|
59
60
|
private readonly turns;
|
package/dist/durable/index.js
CHANGED
|
@@ -125,7 +125,8 @@ OpenCodeMcpController = _ts_decorate([
|
|
|
125
125
|
|
|
126
126
|
// src/mcp.ts
|
|
127
127
|
import { createHash, createHmac, randomBytes, timingSafeEqual } from "crypto";
|
|
128
|
-
import { REMEMBER_TOOL_DESCRIPTION, REMEMBER_TOOL_NAME, ToolNotFoundError, rememberInputSchema, schemaExtensionOf } from "@dudousxd/nestjs-agent-core";
|
|
128
|
+
import { GenuiRunRecorder, REMEMBER_TOOL_DESCRIPTION, REMEMBER_TOOL_NAME, ToolNotFoundError, rememberInputSchema, schemaExtensionOf } from "@dudousxd/nestjs-agent-core";
|
|
129
|
+
import { turnChannel } from "@dudousxd/nestjs-agent-core/genui";
|
|
129
130
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
130
131
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
131
132
|
import { toJsonSchemaCompat } from "@modelcontextprotocol/sdk/server/zod-json-schema-compat.js";
|
|
@@ -256,6 +257,9 @@ function subUsage(a, b) {
|
|
|
256
257
|
};
|
|
257
258
|
}
|
|
258
259
|
__name(subUsage, "subUsage");
|
|
260
|
+
var LOST_ANSWER_ERROR = "The answer was lost while the agent restarted; ask again.";
|
|
261
|
+
var RECOVER_PAGES = 10;
|
|
262
|
+
var RECOVER_PAGE_SIZE = 100;
|
|
259
263
|
var ASK_TOOL_NAME = "ask";
|
|
260
264
|
var FAILED_STATUSES = /* @__PURE__ */ new Set([
|
|
261
265
|
"error",
|
|
@@ -344,6 +348,14 @@ var OpenCodeTurn = class {
|
|
|
344
348
|
wroteMessage = false;
|
|
345
349
|
ended = false;
|
|
346
350
|
stepError;
|
|
351
|
+
/** What this turn object took from the session's assistant messages (see {@link TurnProgress}). */
|
|
352
|
+
took = {
|
|
353
|
+
text: /* @__PURE__ */ new Map(),
|
|
354
|
+
reasoning: /* @__PURE__ */ new Map(),
|
|
355
|
+
called: /* @__PURE__ */ new Set(),
|
|
356
|
+
settled: /* @__PURE__ */ new Set(),
|
|
357
|
+
steps: /* @__PURE__ */ new Set()
|
|
358
|
+
};
|
|
347
359
|
constructor(a) {
|
|
348
360
|
this.a = a;
|
|
349
361
|
this.model = modelOfLabel(a.input.model);
|
|
@@ -361,6 +373,30 @@ var OpenCodeTurn = class {
|
|
|
361
373
|
tokens: info.tokens
|
|
362
374
|
};
|
|
363
375
|
}
|
|
376
|
+
/** What the turn took so far: carried from before this turn object, plus what it took. */
|
|
377
|
+
progress() {
|
|
378
|
+
const before = this.a.progress;
|
|
379
|
+
const merged = /* @__PURE__ */ __name((carried, own) => {
|
|
380
|
+
const out = {
|
|
381
|
+
...carried ?? {}
|
|
382
|
+
};
|
|
383
|
+
for (const [id, n] of own) out[id] = (out[id] ?? 0) + n;
|
|
384
|
+
return out;
|
|
385
|
+
}, "merged");
|
|
386
|
+
const union = /* @__PURE__ */ __name((carried, own) => [
|
|
387
|
+
.../* @__PURE__ */ new Set([
|
|
388
|
+
...carried ?? [],
|
|
389
|
+
...own
|
|
390
|
+
])
|
|
391
|
+
], "union");
|
|
392
|
+
return {
|
|
393
|
+
text: merged(before?.text, this.took.text),
|
|
394
|
+
reasoning: merged(before?.reasoning, this.took.reasoning),
|
|
395
|
+
called: union(before?.called, this.took.called),
|
|
396
|
+
settled: union(before?.settled, this.took.settled),
|
|
397
|
+
steps: union(before?.steps, this.took.steps)
|
|
398
|
+
};
|
|
399
|
+
}
|
|
364
400
|
/** What the run spent so far: carried from before this turn object, plus what it saw. */
|
|
365
401
|
totalUsage() {
|
|
366
402
|
return addUsage(this.a.carried, this.spent);
|
|
@@ -451,7 +487,11 @@ var OpenCodeTurn = class {
|
|
|
451
487
|
this.reported = {
|
|
452
488
|
...this.spent
|
|
453
489
|
};
|
|
454
|
-
const milestone = {
|
|
490
|
+
const milestone = reached.kind === "ask" ? {
|
|
491
|
+
...reached,
|
|
492
|
+
usage,
|
|
493
|
+
progress: this.progress()
|
|
494
|
+
} : {
|
|
455
495
|
...reached,
|
|
456
496
|
usage
|
|
457
497
|
};
|
|
@@ -506,6 +546,7 @@ var OpenCodeTurn = class {
|
|
|
506
546
|
innerSettled: /* @__PURE__ */ new Set()
|
|
507
547
|
};
|
|
508
548
|
this.tools.set(id, state);
|
|
549
|
+
this.took.called.add(id);
|
|
509
550
|
this.segment.calls.push({
|
|
510
551
|
id,
|
|
511
552
|
name,
|
|
@@ -633,6 +674,7 @@ var OpenCodeTurn = class {
|
|
|
633
674
|
case "session.text.delta": {
|
|
634
675
|
const delta = typeof d.delta === "string" ? d.delta : "";
|
|
635
676
|
if (!delta) return;
|
|
677
|
+
this.count(this.took.text, d.assistantMessageID, delta.length);
|
|
636
678
|
this.endReasoning();
|
|
637
679
|
if (!this.sawText && !delta.trim()) return;
|
|
638
680
|
await this.step();
|
|
@@ -654,6 +696,7 @@ ${delta}` : delta;
|
|
|
654
696
|
case "session.reasoning.delta": {
|
|
655
697
|
const delta = typeof d.delta === "string" ? d.delta : "";
|
|
656
698
|
if (!delta) return;
|
|
699
|
+
this.count(this.took.reasoning, d.assistantMessageID, delta.length);
|
|
657
700
|
this.reasoningSince ??= Date.now();
|
|
658
701
|
await this.step();
|
|
659
702
|
this.segment.reasoning += delta;
|
|
@@ -708,6 +751,7 @@ ${delta}` : delta;
|
|
|
708
751
|
}
|
|
709
752
|
case "session.step.ended":
|
|
710
753
|
this.endReasoning();
|
|
754
|
+
if (typeof d.assistantMessageID === "string") this.took.steps.add(d.assistantMessageID);
|
|
711
755
|
await this.stepEnded(d);
|
|
712
756
|
return;
|
|
713
757
|
case "session.step.failed":
|
|
@@ -748,10 +792,16 @@ ${delta}` : delta;
|
|
|
748
792
|
return;
|
|
749
793
|
}
|
|
750
794
|
}
|
|
795
|
+
/** Characters taken from an assistant message's text or reasoning. */
|
|
796
|
+
count(into, messageId, n) {
|
|
797
|
+
if (typeof messageId !== "string") return;
|
|
798
|
+
into.set(messageId, (into.get(messageId) ?? 0) + n);
|
|
799
|
+
}
|
|
751
800
|
async settleTool(id, ok, error = "failed") {
|
|
752
801
|
const state = this.tool(id);
|
|
753
802
|
if (state.settled) return;
|
|
754
803
|
state.settled = true;
|
|
804
|
+
this.took.settled.add(id);
|
|
755
805
|
if (ok) {
|
|
756
806
|
this.segment.results.push({
|
|
757
807
|
id,
|
|
@@ -1301,9 +1351,171 @@ ${delta}` : delta;
|
|
|
1301
1351
|
cost: cost < 1e-9 ? 0 : cost
|
|
1302
1352
|
}, this.compacted ? "history_summary" : "title");
|
|
1303
1353
|
}
|
|
1304
|
-
|
|
1354
|
+
/**
|
|
1355
|
+
* The session's messages since this turn's prompt, oldest first: everything after its newest user
|
|
1356
|
+
* message. `undefined` when they cannot be read (no `message.list`, no prompt within reach); empty
|
|
1357
|
+
* when the newest prompt is another run's (this turn's never reached the session).
|
|
1358
|
+
*/
|
|
1359
|
+
async messagesSincePrompt() {
|
|
1360
|
+
const messages = this.a.client.message;
|
|
1361
|
+
if (messages === void 0) return void 0;
|
|
1362
|
+
const since = [];
|
|
1363
|
+
let cursor;
|
|
1364
|
+
for (let page = 0; page < RECOVER_PAGES; page += 1) {
|
|
1365
|
+
const listed = await messages.list({
|
|
1366
|
+
sessionID: this.a.sessionId,
|
|
1367
|
+
order: "desc",
|
|
1368
|
+
limit: RECOVER_PAGE_SIZE,
|
|
1369
|
+
...cursor !== void 0 ? {
|
|
1370
|
+
cursor
|
|
1371
|
+
} : {}
|
|
1372
|
+
});
|
|
1373
|
+
for (const message of listed.data) {
|
|
1374
|
+
if (message.type === "user") {
|
|
1375
|
+
const runId = message.metadata?.runId;
|
|
1376
|
+
if (typeof runId === "string" && runId !== this.a.runId) return [];
|
|
1377
|
+
return since.reverse();
|
|
1378
|
+
}
|
|
1379
|
+
since.push(message);
|
|
1380
|
+
}
|
|
1381
|
+
const next = listed.cursor?.next;
|
|
1382
|
+
if (typeof next !== "string" || next.length === 0 || listed.data.length === 0) break;
|
|
1383
|
+
cursor = next;
|
|
1384
|
+
}
|
|
1385
|
+
return void 0;
|
|
1386
|
+
}
|
|
1387
|
+
/**
|
|
1388
|
+
* A turn rebuilt after a restart heard only what the session said once it re-attached: what it
|
|
1389
|
+
* said in between — read back from the session's messages since the prompt, less what an earlier
|
|
1390
|
+
* process already took ({@link TurnProgress}) and what this one heard — is taken now, as its
|
|
1391
|
+
* events would have been: text, reasoning, tool calls and results, each step's usage. A session
|
|
1392
|
+
* that left nothing for the turn fails it ({@link LOST_ANSWER_ERROR}) rather than reading as an
|
|
1393
|
+
* empty answer.
|
|
1394
|
+
*/
|
|
1395
|
+
async recover(outcome) {
|
|
1396
|
+
const messages = await this.messagesSincePrompt().catch((error) => {
|
|
1397
|
+
this.a.logger.warn(`run ${this.a.runId}: ${errorText(error, "could not read the session messages")}`);
|
|
1398
|
+
return void 0;
|
|
1399
|
+
});
|
|
1400
|
+
const before = this.a.progress;
|
|
1401
|
+
const text = [];
|
|
1402
|
+
let reasoning = "";
|
|
1403
|
+
let lastError;
|
|
1404
|
+
for (const message of messages ?? []) {
|
|
1405
|
+
if (message.type !== "assistant") continue;
|
|
1406
|
+
const id = message.id;
|
|
1407
|
+
const parts = message.content ?? [];
|
|
1408
|
+
text.push(...missed(parts, "text", before?.text[id] ?? 0, this.took.text.get(id) ?? 0));
|
|
1409
|
+
reasoning += missed(parts, "reasoning", before?.reasoning[id] ?? 0, this.took.reasoning.get(id) ?? 0).map((piece) => piece.text).join("");
|
|
1410
|
+
for (const part of parts) if (part.type === "tool") await this.missedTool(part);
|
|
1411
|
+
if (message.tokens !== void 0 && !before?.steps.includes(id) && !this.took.steps.has(id)) {
|
|
1412
|
+
this.took.steps.add(id);
|
|
1413
|
+
const model = message.model;
|
|
1414
|
+
if (typeof model?.id === "string" && typeof model.providerID === "string") this.model = {
|
|
1415
|
+
providerID: model.providerID,
|
|
1416
|
+
id: model.id
|
|
1417
|
+
};
|
|
1418
|
+
await this.spend({
|
|
1419
|
+
tokens: message.tokens,
|
|
1420
|
+
cost: message.cost
|
|
1421
|
+
}, "chat");
|
|
1422
|
+
}
|
|
1423
|
+
if (message.error?.message) lastError = message.error.message;
|
|
1424
|
+
}
|
|
1425
|
+
const recovered = text.map((piece) => piece.text).join("\n\n").trimStart();
|
|
1426
|
+
if (recovered.trim()) {
|
|
1427
|
+
await this.step();
|
|
1428
|
+
const joint = this.segment.text && !text.at(-1)?.continues ? "\n\n" : "";
|
|
1429
|
+
this.segment.text = `${recovered}${joint}${this.segment.text}`;
|
|
1430
|
+
this.sawText = true;
|
|
1431
|
+
await this.write({
|
|
1432
|
+
kind: "text",
|
|
1433
|
+
text: recovered
|
|
1434
|
+
});
|
|
1435
|
+
}
|
|
1436
|
+
if (reasoning) {
|
|
1437
|
+
await this.step();
|
|
1438
|
+
this.segment.reasoning = `${reasoning}${this.segment.reasoning}`;
|
|
1439
|
+
await this.write({
|
|
1440
|
+
kind: "reasoning",
|
|
1441
|
+
text: reasoning
|
|
1442
|
+
});
|
|
1443
|
+
}
|
|
1444
|
+
if (outcome.status !== "succeeded") return outcome;
|
|
1445
|
+
const idle = (messages ?? []).filter((m) => m.type === "idle").at(-1);
|
|
1446
|
+
if (idle?.outcome === "failed") return {
|
|
1447
|
+
status: "failed",
|
|
1448
|
+
error: lastError ?? this.stepError ?? "the execution failed"
|
|
1449
|
+
};
|
|
1450
|
+
if (idle?.outcome === "interrupted") return {
|
|
1451
|
+
status: "interrupted"
|
|
1452
|
+
};
|
|
1453
|
+
const s = this.segment;
|
|
1454
|
+
const said = s.text || s.reasoning || s.calls.length > 0 || s.ui.length > 0;
|
|
1455
|
+
const progress = this.progress();
|
|
1456
|
+
const tookAny = Object.keys(progress.text).length > 0 || progress.called.length > 0 || progress.steps.length > 0;
|
|
1457
|
+
if (!said && !this.wroteMessage && !tookAny) return {
|
|
1458
|
+
status: "failed",
|
|
1459
|
+
error: LOST_ANSWER_ERROR
|
|
1460
|
+
};
|
|
1461
|
+
return outcome;
|
|
1462
|
+
}
|
|
1463
|
+
/** A tool part of a message the turn did not hear: its call, its result, whichever it lacks. */
|
|
1464
|
+
async missedTool(part) {
|
|
1465
|
+
const id = part.id;
|
|
1466
|
+
const name = part.name ?? "tool";
|
|
1467
|
+
if (id === void 0 || HIDDEN_TOOLS.has(name)) return;
|
|
1468
|
+
const state = part.state ?? {};
|
|
1469
|
+
const before = this.a.progress;
|
|
1470
|
+
if (!before?.called.includes(id) && !this.tools.has(id)) {
|
|
1471
|
+
await this.step();
|
|
1472
|
+
this.setInput(id, state.input, name);
|
|
1473
|
+
await this.makeAvailable(id);
|
|
1474
|
+
}
|
|
1475
|
+
const ok = state.status === "completed";
|
|
1476
|
+
if (!ok && state.status !== "error" || before?.settled.includes(id)) return;
|
|
1477
|
+
const error = errorText(state.error, "tool failed");
|
|
1478
|
+
if (this.tools.has(id)) {
|
|
1479
|
+
await this.settleTool(id, ok, error);
|
|
1480
|
+
return;
|
|
1481
|
+
}
|
|
1482
|
+
this.took.settled.add(id);
|
|
1483
|
+
const result = ok ? {
|
|
1484
|
+
id,
|
|
1485
|
+
name,
|
|
1486
|
+
output: {
|
|
1487
|
+
ok: true
|
|
1488
|
+
}
|
|
1489
|
+
} : {
|
|
1490
|
+
id,
|
|
1491
|
+
name,
|
|
1492
|
+
output: {
|
|
1493
|
+
ok: false
|
|
1494
|
+
},
|
|
1495
|
+
error
|
|
1496
|
+
};
|
|
1497
|
+
const messageId = await this.messageOf(id);
|
|
1498
|
+
if (messageId !== void 0) await this.addResult(messageId, result);
|
|
1499
|
+
else this.segment.results.push(result);
|
|
1500
|
+
await this.write(ok ? {
|
|
1501
|
+
kind: "tool-output",
|
|
1502
|
+
id,
|
|
1503
|
+
output: {
|
|
1504
|
+
ok: true
|
|
1505
|
+
}
|
|
1506
|
+
} : {
|
|
1507
|
+
kind: "tool-output-error",
|
|
1508
|
+
id,
|
|
1509
|
+
error
|
|
1510
|
+
});
|
|
1511
|
+
}
|
|
1512
|
+
async end(ended) {
|
|
1305
1513
|
if (this.ended) return;
|
|
1306
1514
|
this.ended = true;
|
|
1515
|
+
const outcome = this.a.resumed === true ? await this.recover(ended).catch((error) => {
|
|
1516
|
+
this.a.logger.warn(`run ${this.a.runId}: ${errorText(error, "could not recover the turn")}`);
|
|
1517
|
+
return ended;
|
|
1518
|
+
}) : ended;
|
|
1307
1519
|
await this.unreported().catch((error) => this.a.logger.warn(`run ${this.a.runId}: ${errorText(error, "could not read the session usage")}`));
|
|
1308
1520
|
if (outcome.status !== "failed" || this.segment.text || this.segment.calls.length > 0 || this.segment.ui.length > 0) {
|
|
1309
1521
|
await this.flush(true);
|
|
@@ -1320,6 +1532,26 @@ ${delta}` : delta;
|
|
|
1320
1532
|
});
|
|
1321
1533
|
}
|
|
1322
1534
|
};
|
|
1535
|
+
function missed(parts, type, head, tail) {
|
|
1536
|
+
const texts = parts.flatMap((p) => p.type === type && typeof p.text === "string" ? [
|
|
1537
|
+
p.text
|
|
1538
|
+
] : []);
|
|
1539
|
+
const total = texts.reduce((n, t) => n + t.length, 0);
|
|
1540
|
+
const end = total - tail;
|
|
1541
|
+
const out = [];
|
|
1542
|
+
let at = 0;
|
|
1543
|
+
for (const t of texts) {
|
|
1544
|
+
const from = Math.max(head, at);
|
|
1545
|
+
const to = Math.min(end, at + t.length);
|
|
1546
|
+
if (to > from) out.push({
|
|
1547
|
+
text: t.slice(from - at, to - at),
|
|
1548
|
+
continues: to < at + t.length
|
|
1549
|
+
});
|
|
1550
|
+
at += t.length;
|
|
1551
|
+
}
|
|
1552
|
+
return out;
|
|
1553
|
+
}
|
|
1554
|
+
__name(missed, "missed");
|
|
1323
1555
|
function modelOfLabel(label) {
|
|
1324
1556
|
const slash = label?.indexOf("/") ?? -1;
|
|
1325
1557
|
if (label === void 0 || slash <= 0 || slash === label.length - 1) return void 0;
|
|
@@ -1446,6 +1678,8 @@ var OpenCodeMcpEndpoint2 = class {
|
|
|
1446
1678
|
}
|
|
1447
1679
|
turns;
|
|
1448
1680
|
tokens;
|
|
1681
|
+
/** Genui telemetry of the UI calls OpenCode makes (its code mode calls tools here, not in the loop). */
|
|
1682
|
+
genui = new GenuiRunRecorder();
|
|
1449
1683
|
constructor(turns, tokens) {
|
|
1450
1684
|
this.turns = turns;
|
|
1451
1685
|
this.tokens = tokens;
|
|
@@ -1577,7 +1811,7 @@ var OpenCodeMcpEndpoint2 = class {
|
|
|
1577
1811
|
if (spec.kind === "action" && !await this.turns.spendApproval(turn.runId, name, turn.input)) {
|
|
1578
1812
|
throw new OpenCodeToolRefusedError(`"${name}" is an action and nobody approved this call; ask for the permission first.`);
|
|
1579
1813
|
}
|
|
1580
|
-
|
|
1814
|
+
const invoke = /* @__PURE__ */ __name(() => registry.invoke(name, args ?? {}, {
|
|
1581
1815
|
actor,
|
|
1582
1816
|
threadId: turn.ctx.threadId,
|
|
1583
1817
|
runId: turn.runId,
|
|
@@ -1594,7 +1828,24 @@ var OpenCodeMcpEndpoint2 = class {
|
|
|
1594
1828
|
} : {}
|
|
1595
1829
|
}, this.turns.rolesPolicyFor(turn.input), allowed !== void 0 ? {
|
|
1596
1830
|
allowedTools: allowed
|
|
1597
|
-
} : {});
|
|
1831
|
+
} : {}), "invoke");
|
|
1832
|
+
return this.genui.track({
|
|
1833
|
+
store: this.turns.store,
|
|
1834
|
+
marker: spec.genui,
|
|
1835
|
+
runId: turn.runId,
|
|
1836
|
+
threadId: turn.ctx.threadId,
|
|
1837
|
+
actorRef: actor.id,
|
|
1838
|
+
...turn.input.agentName !== void 0 ? {
|
|
1839
|
+
agentName: turn.input.agentName
|
|
1840
|
+
} : {},
|
|
1841
|
+
channel: turnChannel(turn.input.pageContext),
|
|
1842
|
+
modelId: turn.input.model ?? "opencode",
|
|
1843
|
+
call: {
|
|
1844
|
+
id: `mcp:${turn.runId}:${requestId}`,
|
|
1845
|
+
name,
|
|
1846
|
+
input: args ?? {}
|
|
1847
|
+
}
|
|
1848
|
+
}, invoke);
|
|
1598
1849
|
}
|
|
1599
1850
|
/** `remember`: one fact, at the actor's own scope only — as the loop serves it. */
|
|
1600
1851
|
async remember(turn, args) {
|
|
@@ -2446,7 +2697,11 @@ ${body}
|
|
|
2446
2697
|
text: prompt.text,
|
|
2447
2698
|
...prompt.files?.length ? {
|
|
2448
2699
|
files: prompt.files
|
|
2449
|
-
} : {}
|
|
2700
|
+
} : {},
|
|
2701
|
+
// Marks the prompt as this run's: a turn resumed after a restart reads what came after it.
|
|
2702
|
+
metadata: {
|
|
2703
|
+
runId
|
|
2704
|
+
}
|
|
2450
2705
|
});
|
|
2451
2706
|
} catch (error) {
|
|
2452
2707
|
live.turn.fail(`OpenCode refused the message: ${error.message}`);
|
|
@@ -2459,9 +2714,11 @@ ${body}
|
|
|
2459
2714
|
*
|
|
2460
2715
|
* `spent` is what the run spent before (the sum of the `usage` of its earlier milestones): a turn
|
|
2461
2716
|
* rebuilt here starts from it, so the answer it persists carries the whole run's usage.
|
|
2717
|
+
* `progress` is what the turn took before (its last `ask` milestone's): a turn rebuilt here reads
|
|
2718
|
+
* back from the session's messages, when the execution ends, what nobody heard after that.
|
|
2462
2719
|
*/
|
|
2463
|
-
async observe(runId, input, handle, spent) {
|
|
2464
|
-
const live = await this.ensureLive(runId, input, handle, true, spent);
|
|
2720
|
+
async observe(runId, input, handle, spent, progress) {
|
|
2721
|
+
const live = await this.ensureLive(runId, input, handle, true, spent, progress);
|
|
2465
2722
|
const { turn, client } = live;
|
|
2466
2723
|
if (turn.hasMilestone()) return this.reached(runId, input, handle, await turn.next());
|
|
2467
2724
|
let done = false;
|
|
@@ -2500,12 +2757,12 @@ ${body}
|
|
|
2500
2757
|
* request is gone with it: a new session is opened, told what the person decided, and prompted
|
|
2501
2758
|
* to go on.
|
|
2502
2759
|
*/
|
|
2503
|
-
async reply(runId, input, handle, ask, reply, spent) {
|
|
2760
|
+
async reply(runId, input, handle, ask, reply, spent, progress) {
|
|
2504
2761
|
const server = await this.host.server(input.actor);
|
|
2505
2762
|
if (server.key !== handle.serverKey || server.bootId !== handle.bootId) {
|
|
2506
2763
|
return this.continueAfterRestart(runId, input, server, ask, reply, spent);
|
|
2507
2764
|
}
|
|
2508
|
-
const live = await this.ensureLive(runId, input, handle, false, spent);
|
|
2765
|
+
const live = await this.ensureLive(runId, input, handle, false, spent, progress);
|
|
2509
2766
|
await live.turn.decide(ask, reply);
|
|
2510
2767
|
return handle;
|
|
2511
2768
|
}
|
|
@@ -2523,7 +2780,10 @@ ${body}
|
|
|
2523
2780
|
live.turn.setBaseline(await this.sessionUsage(live.client, handle.sessionId));
|
|
2524
2781
|
await live.client.session.prompt({
|
|
2525
2782
|
sessionID: handle.sessionId,
|
|
2526
|
-
text: note
|
|
2783
|
+
text: note,
|
|
2784
|
+
metadata: {
|
|
2785
|
+
runId
|
|
2786
|
+
}
|
|
2527
2787
|
});
|
|
2528
2788
|
return handle;
|
|
2529
2789
|
}
|
|
@@ -2547,9 +2807,10 @@ ${body}
|
|
|
2547
2807
|
sessionID: known.sessionId
|
|
2548
2808
|
});
|
|
2549
2809
|
}
|
|
2550
|
-
async ensureLive(runId, input, handle, catchUp, spent) {
|
|
2810
|
+
async ensureLive(runId, input, handle, catchUp, spent, progress) {
|
|
2551
2811
|
const current = this.live.get(runId);
|
|
2552
2812
|
if (current !== void 0 && current.handle.sessionId === handle.sessionId) return current;
|
|
2813
|
+
const resumed = catchUp && current === void 0;
|
|
2553
2814
|
current?.stop();
|
|
2554
2815
|
const server = await this.host.server(input.actor);
|
|
2555
2816
|
const deps = this.deps.forAgent(input.agentName);
|
|
@@ -2580,6 +2841,10 @@ ${body}
|
|
|
2580
2841
|
...spent !== void 0 ? {
|
|
2581
2842
|
carried: spent
|
|
2582
2843
|
} : {},
|
|
2844
|
+
...progress !== void 0 ? {
|
|
2845
|
+
progress
|
|
2846
|
+
} : {},
|
|
2847
|
+
resumed,
|
|
2583
2848
|
logger: this.logger,
|
|
2584
2849
|
onGranted: /* @__PURE__ */ __name((action, id) => {
|
|
2585
2850
|
const grants = this.grants.get(runId) ?? [];
|
|
@@ -2599,7 +2864,7 @@ ${body}
|
|
|
2599
2864
|
stop
|
|
2600
2865
|
};
|
|
2601
2866
|
this.live.set(runId, live);
|
|
2602
|
-
if (
|
|
2867
|
+
if (resumed) await turn.catchUp().catch(() => void 0);
|
|
2603
2868
|
return live;
|
|
2604
2869
|
}
|
|
2605
2870
|
/** Stop following a run in this process. */
|
|
@@ -3177,6 +3442,7 @@ var OpenCodeRunWorkflow = class {
|
|
|
3177
3442
|
}
|
|
3178
3443
|
async run(ctx, input) {
|
|
3179
3444
|
let spent = emptyUsage();
|
|
3445
|
+
let progress;
|
|
3180
3446
|
try {
|
|
3181
3447
|
const begun = await ctx.localStep("begin", async () => ({
|
|
3182
3448
|
handle: await this.turns.begin(ctx.runId, input),
|
|
@@ -3189,7 +3455,8 @@ var OpenCodeRunWorkflow = class {
|
|
|
3189
3455
|
});
|
|
3190
3456
|
for (let n = 0; ; n += 1) {
|
|
3191
3457
|
const before = spent;
|
|
3192
|
-
const
|
|
3458
|
+
const taken = progress;
|
|
3459
|
+
const milestone = await ctx.localStep(`observe:${n}`, () => this.turns.observe(ctx.runId, input, handle, before, taken));
|
|
3193
3460
|
spent = addUsage(spent, milestone.usage);
|
|
3194
3461
|
const total = spent;
|
|
3195
3462
|
if (milestone.kind === "finished") {
|
|
@@ -3215,7 +3482,9 @@ var OpenCodeRunWorkflow = class {
|
|
|
3215
3482
|
};
|
|
3216
3483
|
}
|
|
3217
3484
|
const ask = milestone.ask;
|
|
3218
|
-
|
|
3485
|
+
progress = milestone.progress ?? progress;
|
|
3486
|
+
const tookSoFar = progress;
|
|
3487
|
+
handle = await ctx.localStep(`reply:${n}`, () => this.turns.reply(ctx.runId, input, handle, ask, reply, total, tookSoFar));
|
|
3219
3488
|
}
|
|
3220
3489
|
} catch (error) {
|
|
3221
3490
|
if (isWorkflowControlFlowSignal(error)) throw error;
|