@dudousxd/nestjs-agent-opencode 0.0.0-stage → 0.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.
@@ -0,0 +1,3138 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/durable/index.ts
22
+ var durable_exports = {};
23
+ __export(durable_exports, {
24
+ DurableOpenCodeAgentRunner: () => DurableOpenCodeAgentRunner,
25
+ OPENCODE_RUN_WORKFLOW: () => OPENCODE_RUN_WORKFLOW,
26
+ OpenCodeRunWorkflow: () => OpenCodeRunWorkflow,
27
+ decisionToken: () => decisionToken,
28
+ openCodeDurable: () => openCodeDurable
29
+ });
30
+ module.exports = __toCommonJS(durable_exports);
31
+
32
+ // src/engine.ts
33
+ var import_common6 = require("@nestjs/common");
34
+
35
+ // src/host.ts
36
+ var InMemoryOpenCodeSessionStore = class {
37
+ static {
38
+ __name(this, "InMemoryOpenCodeSessionStore");
39
+ }
40
+ refs = /* @__PURE__ */ new Map();
41
+ async get(threadId) {
42
+ return this.refs.get(threadId) ?? null;
43
+ }
44
+ async set(threadId, ref) {
45
+ this.refs.set(threadId, ref);
46
+ }
47
+ };
48
+
49
+ // src/mcp.controller.ts
50
+ var import_common = require("@nestjs/common");
51
+
52
+ // src/tokens.ts
53
+ var OPENCODE_HOST = /* @__PURE__ */ Symbol.for("@dudousxd/nestjs-agent-opencode:host");
54
+ var OPENCODE_SESSIONS = /* @__PURE__ */ Symbol.for("@dudousxd/nestjs-agent-opencode:sessions");
55
+ var OPENCODE_OPTIONS = /* @__PURE__ */ Symbol.for("@dudousxd/nestjs-agent-opencode:options");
56
+ var OPENCODE_TURNS = /* @__PURE__ */ Symbol.for("@dudousxd/nestjs-agent-opencode:turns");
57
+ var OPENCODE_MCP_ENDPOINT = /* @__PURE__ */ Symbol.for("@dudousxd/nestjs-agent-opencode:mcp-endpoint");
58
+ var OPENCODE_TOOLS_TOKENS = /* @__PURE__ */ Symbol.for("@dudousxd/nestjs-agent-opencode:tools-tokens");
59
+
60
+ // src/mcp.controller.ts
61
+ function _ts_decorate(decorators, target, key, desc) {
62
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
63
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
64
+ r = Reflect.decorate(decorators, target, key, desc);
65
+ } else {
66
+ for (var i = decorators.length - 1; i >= 0; i--) {
67
+ if (d = decorators[i]) {
68
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
69
+ }
70
+ }
71
+ }
72
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
73
+ }
74
+ __name(_ts_decorate, "_ts_decorate");
75
+ function _ts_metadata(metadataKey, metadataValue) {
76
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") {
77
+ return Reflect.metadata(metadataKey, metadataValue);
78
+ }
79
+ }
80
+ __name(_ts_metadata, "_ts_metadata");
81
+ function _ts_param(paramIndex, decorator) {
82
+ return function(target, key) {
83
+ decorator(target, key, paramIndex);
84
+ };
85
+ }
86
+ __name(_ts_param, "_ts_param");
87
+ function notAllowed(res) {
88
+ res.statusCode = 405;
89
+ res.setHeader("allow", "POST");
90
+ res.setHeader("content-type", "application/json");
91
+ res.end(JSON.stringify({
92
+ error: "method not allowed"
93
+ }));
94
+ }
95
+ __name(notAllowed, "notAllowed");
96
+ var OpenCodeMcpController = class {
97
+ static {
98
+ __name(this, "OpenCodeMcpController");
99
+ }
100
+ endpoint;
101
+ constructor(endpoint) {
102
+ this.endpoint = endpoint;
103
+ }
104
+ async post(req, res, body) {
105
+ await this.endpoint.handle(req, res, body);
106
+ }
107
+ get(res) {
108
+ notAllowed(res);
109
+ }
110
+ delete(res) {
111
+ notAllowed(res);
112
+ }
113
+ };
114
+ _ts_decorate([
115
+ (0, import_common.Post)(),
116
+ _ts_param(0, (0, import_common.Req)()),
117
+ _ts_param(1, (0, import_common.Res)()),
118
+ _ts_param(2, (0, import_common.Body)()),
119
+ _ts_metadata("design:type", Function),
120
+ _ts_metadata("design:paramtypes", [
121
+ typeof IncomingMessage === "undefined" ? Object : IncomingMessage,
122
+ typeof ServerResponse === "undefined" ? Object : ServerResponse,
123
+ Object
124
+ ]),
125
+ _ts_metadata("design:returntype", Promise)
126
+ ], OpenCodeMcpController.prototype, "post", null);
127
+ _ts_decorate([
128
+ (0, import_common.Get)(),
129
+ _ts_param(0, (0, import_common.Res)()),
130
+ _ts_metadata("design:type", Function),
131
+ _ts_metadata("design:paramtypes", [
132
+ typeof ServerResponse === "undefined" ? Object : ServerResponse
133
+ ]),
134
+ _ts_metadata("design:returntype", void 0)
135
+ ], OpenCodeMcpController.prototype, "get", null);
136
+ _ts_decorate([
137
+ (0, import_common.Delete)(),
138
+ _ts_param(0, (0, import_common.Res)()),
139
+ _ts_metadata("design:type", Function),
140
+ _ts_metadata("design:paramtypes", [
141
+ typeof ServerResponse === "undefined" ? Object : ServerResponse
142
+ ]),
143
+ _ts_metadata("design:returntype", void 0)
144
+ ], OpenCodeMcpController.prototype, "delete", null);
145
+ OpenCodeMcpController = _ts_decorate([
146
+ (0, import_common.Controller)("opencode/mcp"),
147
+ _ts_param(0, (0, import_common.Inject)(OPENCODE_MCP_ENDPOINT)),
148
+ _ts_metadata("design:type", Function),
149
+ _ts_metadata("design:paramtypes", [
150
+ typeof OpenCodeMcpEndpoint === "undefined" ? Object : OpenCodeMcpEndpoint
151
+ ])
152
+ ], OpenCodeMcpController);
153
+
154
+ // src/mcp.ts
155
+ var import_node_crypto = require("crypto");
156
+ var import_nestjs_agent_core3 = require("@dudousxd/nestjs-agent-core");
157
+ var import_server = require("@modelcontextprotocol/sdk/server/index.js");
158
+ var import_streamableHttp = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
159
+ var import_zod_json_schema_compat = require("@modelcontextprotocol/sdk/server/zod-json-schema-compat.js");
160
+ var import_types = require("@modelcontextprotocol/sdk/types.js");
161
+
162
+ // src/turn.ts
163
+ var import_nestjs_agent_core2 = require("@dudousxd/nestjs-agent-core");
164
+ var import_common2 = require("@nestjs/common");
165
+
166
+ // src/forms.ts
167
+ var import_nestjs_agent_core = require("@dudousxd/nestjs-agent-core");
168
+ function toQuestion(field) {
169
+ const required = field.required === true ? {
170
+ required: true
171
+ } : {};
172
+ const base = {
173
+ id: field.key,
174
+ prompt: field.title || field.key,
175
+ ...field.description && field.description !== field.title ? {
176
+ description: field.description
177
+ } : {}
178
+ };
179
+ if (field.options !== void 0 && field.options.length > 0) {
180
+ return {
181
+ ...base,
182
+ options: field.options.map((o) => ({
183
+ value: String(o.value),
184
+ label: String(o.label ?? o.value)
185
+ })),
186
+ input: {
187
+ type: "select",
188
+ ...required
189
+ },
190
+ ...field.type === "array" ? {
191
+ multiple: true
192
+ } : {},
193
+ ...field.custom === true ? {
194
+ allowFreeText: true
195
+ } : {}
196
+ };
197
+ }
198
+ const type = field.type === "number" || field.type === "integer" ? "number" : field.type === "boolean" ? "boolean" : "text";
199
+ return {
200
+ ...base,
201
+ input: {
202
+ type,
203
+ ...required
204
+ }
205
+ };
206
+ }
207
+ __name(toQuestion, "toQuestion");
208
+ function formFields(form) {
209
+ return (form.fields ?? []).filter((f) => typeof f === "object" && f !== null && typeof f.key === "string");
210
+ }
211
+ __name(formFields, "formFields");
212
+ function toElicitation(id, form) {
213
+ const questions = formFields(form).map(toQuestion);
214
+ const first = questions[0];
215
+ const title = form.title;
216
+ const preamble = title && title !== "Questions" && title !== first?.prompt && title !== first?.description ? title : void 0;
217
+ return {
218
+ id,
219
+ source: "ask",
220
+ ...preamble ? {
221
+ preamble
222
+ } : {},
223
+ questions
224
+ };
225
+ }
226
+ __name(toElicitation, "toElicitation");
227
+ var FormAnswerError = class extends Error {
228
+ static {
229
+ __name(this, "FormAnswerError");
230
+ }
231
+ };
232
+ function toFormAnswer(form, answers = {}) {
233
+ const out = {};
234
+ for (const field of formFields(form)) {
235
+ const question = toQuestion(field);
236
+ const values = answers[field.key] ?? question.defaults ?? [];
237
+ const problem = (0, import_nestjs_agent_core.validateElicitationAnswer)(question, values);
238
+ if (problem) throw new FormAnswerError(`answers["${field.key}"] ${problem}`);
239
+ const present = values.filter((v) => v !== "");
240
+ const [value] = present;
241
+ if (value === void 0) continue;
242
+ if (question.multiple) out[field.key] = present;
243
+ else if (question.input?.type === "number") out[field.key] = Number(value);
244
+ else if (question.input?.type === "boolean") out[field.key] = value === "true";
245
+ else out[field.key] = value;
246
+ }
247
+ return out;
248
+ }
249
+ __name(toFormAnswer, "toFormAnswer");
250
+
251
+ // src/turn.ts
252
+ var ASK_TOOL_NAME = "ask";
253
+ var FAILED_STATUSES = /* @__PURE__ */ new Set([
254
+ "error",
255
+ "failed",
256
+ "denied",
257
+ "cancelled"
258
+ ]);
259
+ function emptySegment() {
260
+ return {
261
+ text: "",
262
+ reasoning: "",
263
+ reasoningMs: 0,
264
+ calls: [],
265
+ results: [],
266
+ ui: []
267
+ };
268
+ }
269
+ __name(emptySegment, "emptySegment");
270
+ function isDecision(reply) {
271
+ return typeof reply.approved === "boolean";
272
+ }
273
+ __name(isDecision, "isDecision");
274
+ var OpenCodeReplyMismatchError = class extends import_common2.ConflictException {
275
+ static {
276
+ __name(this, "OpenCodeReplyMismatchError");
277
+ }
278
+ runId;
279
+ toolCallId;
280
+ constructor(runId, toolCallId) {
281
+ super(`Tool call "${toolCallId}" on run "${runId}" is waiting for an approve/reject, not for answers`), this.runId = runId, this.toolCallId = toolCallId;
282
+ this.name = "OpenCodeReplyMismatchError";
283
+ }
284
+ };
285
+ function permissionArgs(req) {
286
+ const metadata = req.metadata ?? {};
287
+ const nested = metadata.input ?? metadata.args;
288
+ if (nested !== null && typeof nested === "object") return nested;
289
+ return metadata;
290
+ }
291
+ __name(permissionArgs, "permissionArgs");
292
+ var HIDDEN_TOOLS = /* @__PURE__ */ new Set([
293
+ "question"
294
+ ]);
295
+ function errorText(error, fallback) {
296
+ if (typeof error === "string" && error.length > 0) return error;
297
+ const message = error?.message;
298
+ return typeof message === "string" && message.length > 0 ? message : fallback;
299
+ }
300
+ __name(errorText, "errorText");
301
+ var OpenCodeTurn = class {
302
+ static {
303
+ __name(this, "OpenCodeTurn");
304
+ }
305
+ a;
306
+ /** Serializes the handling of events, so frames and rows land in the order OpenCode sent them. */
307
+ chain = Promise.resolve();
308
+ segment = emptySegment();
309
+ tools = /* @__PURE__ */ new Map();
310
+ /** Results already persisted per message, so a late settlement adds to them rather than replacing. */
311
+ messageResults = /* @__PURE__ */ new Map();
312
+ asked = /* @__PURE__ */ new Set();
313
+ /** Calls of {@link HIDDEN_TOOLS}, by id. */
314
+ hidden = /* @__PURE__ */ new Set();
315
+ milestones = [];
316
+ waiting;
317
+ usage = {
318
+ inputTokens: 0,
319
+ outputTokens: 0
320
+ };
321
+ costUsd;
322
+ stepOpen = false;
323
+ sawText = false;
324
+ separator = false;
325
+ reasoningSince;
326
+ wroteMessage = false;
327
+ ended = false;
328
+ stepError;
329
+ constructor(a) {
330
+ this.a = a;
331
+ }
332
+ get sessionId() {
333
+ return this.a.sessionId;
334
+ }
335
+ get finished() {
336
+ return this.ended;
337
+ }
338
+ /** Feed one of the session's events. */
339
+ handle(event) {
340
+ this.enqueue(() => this.onEvent(event));
341
+ }
342
+ /** End the turn as failed without an event (the prompt was refused, the stream is gone). */
343
+ fail(error) {
344
+ this.enqueue(() => this.end({
345
+ status: "failed",
346
+ error
347
+ }));
348
+ }
349
+ /**
350
+ * A component a tool pushed (`ctx.emitUi`, through the MCP surface): streamed as a `ui` frame and
351
+ * persisted on the message being written, in the order it arrived. Pushing the same id again
352
+ * replaces it.
353
+ */
354
+ pushUi(component) {
355
+ return new Promise((resolve, reject) => {
356
+ this.chain = this.chain.then(async () => {
357
+ await this.step();
358
+ const ui = this.segment.ui;
359
+ const at = ui.findIndex((c) => c.id === component.id);
360
+ if (at >= 0) ui[at] = component;
361
+ else ui.push(component);
362
+ await this.write({
363
+ kind: "ui",
364
+ ...component
365
+ });
366
+ }).then(resolve, reject);
367
+ });
368
+ }
369
+ /** The next milestone: one already reached, or the next one to come. */
370
+ next() {
371
+ const ready = this.milestones.shift();
372
+ if (ready !== void 0) return Promise.resolve(ready);
373
+ return new Promise((resolve) => {
374
+ this.waiting = resolve;
375
+ });
376
+ }
377
+ /** Whether a milestone is waiting to be taken. */
378
+ hasMilestone() {
379
+ return this.milestones.length > 0;
380
+ }
381
+ /**
382
+ * Requests OpenCode raised while nobody listened (this process started after them, or the event
383
+ * stream dropped): its open permissions and forms, handled as if their events had just arrived.
384
+ * One already recorded in the store is reported without being recorded or streamed again.
385
+ */
386
+ async catchUp() {
387
+ const { client, sessionId } = this.a;
388
+ const permissions = await client.permission.list?.({
389
+ sessionID: sessionId
390
+ }) ?? [];
391
+ const forms = await client.session.form.list?.({
392
+ sessionID: sessionId
393
+ }) ?? [];
394
+ await new Promise((resolve) => {
395
+ this.enqueue(async () => {
396
+ for (const p of permissions) await this.onPermission(p, true);
397
+ for (const f of forms) await this.onForm(f, true);
398
+ resolve();
399
+ });
400
+ });
401
+ }
402
+ /**
403
+ * Hand a person's answer to what the turn asked, and let OpenCode go on. `tellOpenCode: false`
404
+ * only settles the call (stream and store): the request died with a restarted server, and the
405
+ * new session is told the answer in its prompt instead.
406
+ */
407
+ decide(ask, reply, opts = {}) {
408
+ const tell = opts.tellOpenCode ?? true;
409
+ return new Promise((resolve, reject) => {
410
+ this.chain = this.chain.then(() => ask.kind === "approval" ? this.decided(ask, reply, tell) : this.answered(ask, reply, tell)).then(resolve, reject);
411
+ });
412
+ }
413
+ reach(milestone) {
414
+ const waiting = this.waiting;
415
+ if (waiting !== void 0) {
416
+ this.waiting = void 0;
417
+ waiting(milestone);
418
+ } else {
419
+ this.milestones.push(milestone);
420
+ }
421
+ }
422
+ enqueue(work) {
423
+ this.chain = this.chain.then(work).catch((error) => {
424
+ this.a.logger.error(`run ${this.a.runId}: ${errorText(error, "event handling failed")}`);
425
+ if (!this.ended) {
426
+ this.ended = true;
427
+ this.reach({
428
+ kind: "finished",
429
+ outcome: {
430
+ status: "failed",
431
+ error: errorText(error, "event handling failed")
432
+ }
433
+ });
434
+ }
435
+ });
436
+ }
437
+ async write(event) {
438
+ await this.a.writer.write((0, import_nestjs_agent_core2.encodeStreamEvent)(event));
439
+ }
440
+ async step() {
441
+ if (this.stepOpen) return;
442
+ this.stepOpen = true;
443
+ await this.write({
444
+ kind: "step-start"
445
+ });
446
+ }
447
+ endReasoning() {
448
+ if (this.reasoningSince === void 0) return;
449
+ this.segment.reasoningMs += Date.now() - this.reasoningSince;
450
+ this.reasoningSince = void 0;
451
+ }
452
+ tool(id, name = "tool") {
453
+ let state = this.tools.get(id);
454
+ if (state === void 0) {
455
+ state = {
456
+ name,
457
+ announced: false,
458
+ available: false,
459
+ settled: false,
460
+ input: {},
461
+ innerAnnounced: /* @__PURE__ */ new Set(),
462
+ innerSettled: /* @__PURE__ */ new Set()
463
+ };
464
+ this.tools.set(id, state);
465
+ this.segment.calls.push({
466
+ id,
467
+ name,
468
+ input: state.input,
469
+ kind: "read"
470
+ });
471
+ }
472
+ return state;
473
+ }
474
+ async announce(id, name) {
475
+ const state = this.tool(id, name);
476
+ if (!state.announced) {
477
+ state.announced = true;
478
+ await this.write({
479
+ kind: "tool-input-start",
480
+ id,
481
+ name: state.name,
482
+ toolKind: "read"
483
+ });
484
+ }
485
+ return state;
486
+ }
487
+ async makeAvailable(id) {
488
+ const state = await this.announce(id, this.tools.get(id)?.name ?? "tool");
489
+ if (!state.available) {
490
+ state.available = true;
491
+ await this.write({
492
+ kind: "tool-input-available",
493
+ id,
494
+ name: state.name,
495
+ input: state.input,
496
+ toolKind: "read"
497
+ });
498
+ }
499
+ return state;
500
+ }
501
+ setInput(id, input, name) {
502
+ const state = this.tool(id, name);
503
+ if (input === null || typeof input !== "object") return;
504
+ Object.assign(state.input, input);
505
+ }
506
+ async innerCalls(parentId, raw) {
507
+ if (!Array.isArray(raw)) return;
508
+ const parent = this.tool(parentId);
509
+ for (const [index, call] of raw.entries()) {
510
+ const id = `${parentId}.${index}`;
511
+ const name = String(call?.tool ?? "tool");
512
+ const input = call?.input !== null && typeof call?.input === "object" ? call.input : {};
513
+ if (!parent.innerAnnounced.has(index)) {
514
+ parent.innerAnnounced.add(index);
515
+ this.segment.calls.push({
516
+ id,
517
+ name,
518
+ input,
519
+ kind: "read",
520
+ parentId
521
+ });
522
+ await this.write({
523
+ kind: "tool-input-available",
524
+ id,
525
+ name,
526
+ input,
527
+ toolKind: "read",
528
+ parentId
529
+ });
530
+ }
531
+ const status = String(call?.status ?? "").toLowerCase();
532
+ if (parent.innerSettled.has(index) || status === "running" || status === "pending" || !status) {
533
+ continue;
534
+ }
535
+ parent.innerSettled.add(index);
536
+ if (FAILED_STATUSES.has(status)) {
537
+ this.segment.results.push({
538
+ id,
539
+ name,
540
+ output: {
541
+ status
542
+ },
543
+ error: status
544
+ });
545
+ await this.write({
546
+ kind: "tool-output-error",
547
+ id,
548
+ error: status
549
+ });
550
+ } else {
551
+ this.segment.results.push({
552
+ id,
553
+ name,
554
+ output: {
555
+ status
556
+ }
557
+ });
558
+ await this.write({
559
+ kind: "tool-output",
560
+ id,
561
+ output: {
562
+ status
563
+ }
564
+ });
565
+ }
566
+ }
567
+ }
568
+ async onEvent(e) {
569
+ if (this.ended) return;
570
+ const d = e.data ?? {};
571
+ switch (e.type) {
572
+ case "session.text.delta": {
573
+ const delta = typeof d.delta === "string" ? d.delta : "";
574
+ if (!delta) return;
575
+ this.endReasoning();
576
+ if (!this.sawText && !delta.trim()) return;
577
+ await this.step();
578
+ const text = this.separator && this.sawText ? `
579
+
580
+ ${delta}` : delta;
581
+ this.separator = false;
582
+ this.sawText = true;
583
+ this.segment.text += text;
584
+ await this.write({
585
+ kind: "text",
586
+ text
587
+ });
588
+ return;
589
+ }
590
+ case "session.text.ended":
591
+ this.separator = true;
592
+ return;
593
+ case "session.reasoning.delta": {
594
+ const delta = typeof d.delta === "string" ? d.delta : "";
595
+ if (!delta) return;
596
+ this.reasoningSince ??= Date.now();
597
+ await this.step();
598
+ this.segment.reasoning += delta;
599
+ await this.write({
600
+ kind: "reasoning",
601
+ text: delta
602
+ });
603
+ return;
604
+ }
605
+ case "session.tool.input.started":
606
+ this.endReasoning();
607
+ if (HIDDEN_TOOLS.has(String(d.name))) {
608
+ this.hidden.add(String(d.id));
609
+ return;
610
+ }
611
+ await this.step();
612
+ await this.announce(String(d.id), String(d.name ?? "tool"));
613
+ return;
614
+ case "session.tool.called":
615
+ if (this.hidden.has(String(d.id))) return;
616
+ await this.step();
617
+ this.setInput(String(d.id), d.input, typeof d.name === "string" ? d.name : void 0);
618
+ await this.makeAvailable(String(d.id));
619
+ return;
620
+ case "session.skill.activated": {
621
+ const name = String(d.id ?? d.name ?? "skill");
622
+ const id = `skill:${name}:${this.tools.size}`;
623
+ await this.step();
624
+ await this.announce(id, "skill");
625
+ this.setInput(id, {
626
+ name
627
+ });
628
+ await this.makeAvailable(id);
629
+ await this.settleTool(id, true);
630
+ return;
631
+ }
632
+ case "session.tool.progress":
633
+ if (this.hidden.has(String(d.id))) return;
634
+ await this.step();
635
+ await this.makeAvailable(String(d.id));
636
+ await this.innerCalls(String(d.id), d.metadata?.toolCalls);
637
+ return;
638
+ case "session.tool.success":
639
+ case "session.tool.failed": {
640
+ const id = String(d.id);
641
+ if (this.hidden.has(id)) return;
642
+ await this.step();
643
+ await this.makeAvailable(id);
644
+ await this.innerCalls(id, d.metadata?.toolCalls);
645
+ await this.settleTool(id, e.type === "session.tool.success", errorText(d.error, "tool failed"));
646
+ return;
647
+ }
648
+ case "session.step.ended":
649
+ this.endReasoning();
650
+ await this.stepEnded(d);
651
+ return;
652
+ case "session.step.failed":
653
+ this.stepError = errorText(d.error, "the model call failed");
654
+ return;
655
+ case "session.renamed":
656
+ if (typeof d.title === "string" && d.title.trim()) {
657
+ await this.a.store.setTitle(this.a.input.threadId, d.title.trim());
658
+ await this.write({
659
+ kind: "title",
660
+ title: d.title.trim()
661
+ });
662
+ }
663
+ return;
664
+ case "permission.asked":
665
+ await this.onPermission(d, false);
666
+ return;
667
+ case "form.created":
668
+ await this.onForm(d.form ?? d, false);
669
+ return;
670
+ case "session.execution.succeeded":
671
+ await this.end({
672
+ status: "succeeded"
673
+ });
674
+ return;
675
+ case "session.execution.failed":
676
+ await this.end({
677
+ status: "failed",
678
+ error: errorText(d.error, this.stepError ?? "the execution failed")
679
+ });
680
+ return;
681
+ case "session.execution.interrupted":
682
+ await this.end({
683
+ status: "interrupted"
684
+ });
685
+ return;
686
+ default:
687
+ return;
688
+ }
689
+ }
690
+ async settleTool(id, ok, error = "failed") {
691
+ const state = this.tool(id);
692
+ if (state.settled) return;
693
+ state.settled = true;
694
+ if (ok) {
695
+ this.segment.results.push({
696
+ id,
697
+ name: state.name,
698
+ output: {
699
+ ok: true
700
+ }
701
+ });
702
+ await this.write({
703
+ kind: "tool-output",
704
+ id,
705
+ output: {
706
+ ok: true
707
+ }
708
+ });
709
+ } else {
710
+ this.segment.results.push({
711
+ id,
712
+ name: state.name,
713
+ output: {
714
+ ok: false
715
+ },
716
+ error
717
+ });
718
+ await this.write({
719
+ kind: "tool-output-error",
720
+ id,
721
+ error
722
+ });
723
+ }
724
+ }
725
+ async stepEnded(d) {
726
+ const usage = {
727
+ inputTokens: Number(d.tokens?.input) || 0,
728
+ outputTokens: Number(d.tokens?.output) || 0,
729
+ ...Number(d.tokens?.cache?.read) ? {
730
+ cacheReadTokens: Number(d.tokens.cache.read)
731
+ } : {},
732
+ ...Number(d.tokens?.cache?.write) ? {
733
+ cacheWriteTokens: Number(d.tokens.cache.write)
734
+ } : {}
735
+ };
736
+ const costUsd = typeof d.cost === "number" ? d.cost : void 0;
737
+ this.usage.inputTokens += usage.inputTokens;
738
+ this.usage.outputTokens += usage.outputTokens;
739
+ if (costUsd !== void 0) this.costUsd = (this.costUsd ?? 0) + costUsd;
740
+ await this.a.store.recordUsage({
741
+ threadId: this.a.input.threadId,
742
+ actorRef: this.a.input.actor.id,
743
+ modelId: this.a.modelLabel,
744
+ purpose: "chat",
745
+ usage,
746
+ ...costUsd !== void 0 ? {
747
+ costUsd
748
+ } : {}
749
+ });
750
+ if (!this.stepOpen) return;
751
+ this.stepOpen = false;
752
+ await this.write({
753
+ kind: "step-finish",
754
+ usage,
755
+ costUsd: costUsd ?? null,
756
+ ...this.segment.reasoningMs > 0 ? {
757
+ reasoningMs: this.segment.reasoningMs
758
+ } : {},
759
+ model: this.a.modelLabel
760
+ });
761
+ }
762
+ /**
763
+ * Persist what the turn produced since the last flush as one assistant message — the message a
764
+ * call put to a person hangs on, or the turn's last one. Returns its id.
765
+ */
766
+ async flush(final) {
767
+ this.endReasoning();
768
+ const s = this.segment;
769
+ const empty = !s.text && !s.reasoning && s.calls.length === 0 && s.ui.length === 0;
770
+ if (empty && !(final && !this.wroteMessage)) return void 0;
771
+ this.segment = emptySegment();
772
+ const message = await this.a.store.appendMessage({
773
+ threadId: this.a.input.threadId,
774
+ role: "assistant",
775
+ content: s.text,
776
+ runId: this.a.runId,
777
+ ...final ? {
778
+ usage: {
779
+ ...this.usage,
780
+ ...this.costUsd !== void 0 ? {
781
+ costUsd: this.costUsd
782
+ } : {}
783
+ }
784
+ } : {},
785
+ ...this.a.input.agentName !== void 0 ? {
786
+ agentName: this.a.input.agentName
787
+ } : {},
788
+ ...this.a.input.persona !== void 0 ? {
789
+ persona: this.a.input.persona
790
+ } : {},
791
+ ...s.reasoning ? {
792
+ reasoning: s.reasoning,
793
+ reasoningMs: s.reasoningMs
794
+ } : {},
795
+ ...s.calls.length > 0 ? {
796
+ toolCalls: s.calls
797
+ } : {},
798
+ ...s.results.length > 0 ? {
799
+ toolResults: s.results
800
+ } : {},
801
+ ...s.ui.length > 0 ? {
802
+ ui: s.ui
803
+ } : {}
804
+ });
805
+ this.wroteMessage = true;
806
+ this.messageResults.set(message.id, [
807
+ ...s.results
808
+ ]);
809
+ return message.id;
810
+ }
811
+ /** A request recorded by an earlier process (a turn resumed after a restart). */
812
+ async alreadyRecorded(id) {
813
+ return (await this.a.store.toolCallApproval?.(id) ?? null) !== null;
814
+ }
815
+ /** The persisted assistant message carrying call `id`, so its result lands on the same message. */
816
+ async messageOf(id) {
817
+ const thread = await this.a.store.getThread(this.a.input.threadId);
818
+ return thread?.messages.find((m) => m.toolCalls?.some((call) => call.id === id))?.id;
819
+ }
820
+ async onPermission(req, recovering) {
821
+ if (this.asked.has(req.id)) return;
822
+ this.asked.add(req.id);
823
+ const { client, sessionId, input, store } = this.a;
824
+ const action = String(req.action);
825
+ const args = permissionArgs(req);
826
+ if (recovering && await this.alreadyRecorded(req.id)) {
827
+ const state = await store.toolCallApproval?.(req.id);
828
+ const messageId2 = await this.messageOf(req.id);
829
+ this.reach({
830
+ kind: "ask",
831
+ ask: {
832
+ kind: "approval",
833
+ id: req.id,
834
+ action,
835
+ approver: state?.approver ?? "requester",
836
+ ...state?.expiresAt ? {
837
+ expiresAt: state.expiresAt
838
+ } : {},
839
+ ...messageId2 !== void 0 ? {
840
+ messageId: messageId2
841
+ } : {}
842
+ },
843
+ ...state?.expiresAt ? {
844
+ timeoutMs: Math.max(0, Date.parse(state.expiresAt) - Date.now())
845
+ } : {}
846
+ });
847
+ return;
848
+ }
849
+ await this.step();
850
+ const requirement = await this.a.approvalFor(action);
851
+ const remembered = requirement.required ? (await store.rememberedApprovals?.(input.threadId) ?? []).includes(action) : false;
852
+ if (!requirement.required || remembered) {
853
+ this.a.onGranted?.(action, req.id);
854
+ await client.permission.reply({
855
+ sessionID: sessionId,
856
+ requestID: req.id,
857
+ decision: "once"
858
+ });
859
+ this.segment.calls.push({
860
+ id: req.id,
861
+ name: action,
862
+ input: args,
863
+ kind: "action"
864
+ });
865
+ this.segment.results.push({
866
+ id: req.id,
867
+ name: action,
868
+ output: {
869
+ approved: true
870
+ }
871
+ });
872
+ await this.write({
873
+ kind: "tool-input-available",
874
+ id: req.id,
875
+ name: action,
876
+ input: args,
877
+ toolKind: "action"
878
+ });
879
+ await this.write({
880
+ kind: "approval-settled",
881
+ id: req.id,
882
+ status: "approved",
883
+ approver: requirement.approver,
884
+ decidedVia: remembered ? "remembered" : "policy",
885
+ ...remembered ? {
886
+ remember: true
887
+ } : {}
888
+ });
889
+ await this.write({
890
+ kind: "tool-output",
891
+ id: req.id,
892
+ output: {
893
+ approved: true
894
+ }
895
+ });
896
+ return;
897
+ }
898
+ const ttlMs = requirement.ttlMs !== void 0 && Number.isFinite(requirement.ttlMs) && requirement.ttlMs > 0 ? Math.floor(requirement.ttlMs) : void 0;
899
+ const expiresAt = ttlMs !== void 0 ? new Date(Date.now() + ttlMs).toISOString() : void 0;
900
+ this.segment.calls.push({
901
+ id: req.id,
902
+ name: action,
903
+ input: args,
904
+ kind: "action"
905
+ });
906
+ const messageId = await this.flush(false);
907
+ await store.recordToolCall({
908
+ toolCallId: req.id,
909
+ messageId,
910
+ toolName: action,
911
+ toolType: "action",
912
+ input: args,
913
+ status: "pending_approval",
914
+ runId: this.a.runId,
915
+ approver: requirement.approver,
916
+ ...expiresAt !== void 0 ? {
917
+ expiresAt
918
+ } : {}
919
+ });
920
+ await this.write({
921
+ kind: "tool-input-available",
922
+ id: req.id,
923
+ name: action,
924
+ input: args,
925
+ toolKind: "action"
926
+ });
927
+ await this.write({
928
+ kind: "approval-requested",
929
+ id: req.id,
930
+ approver: requirement.approver,
931
+ ...expiresAt !== void 0 ? {
932
+ expiresAt
933
+ } : {}
934
+ });
935
+ this.reach({
936
+ kind: "ask",
937
+ ask: {
938
+ kind: "approval",
939
+ id: req.id,
940
+ action,
941
+ approver: requirement.approver,
942
+ messageId,
943
+ ...expiresAt !== void 0 ? {
944
+ expiresAt
945
+ } : {}
946
+ },
947
+ ...ttlMs !== void 0 ? {
948
+ timeoutMs: ttlMs
949
+ } : {}
950
+ });
951
+ }
952
+ async decided(ask, reply, tell) {
953
+ if (!isDecision(reply)) throw new OpenCodeReplyMismatchError(this.a.runId, ask.id);
954
+ const decision = reply;
955
+ const { client, sessionId, store } = this.a;
956
+ const { id, action } = ask;
957
+ const status = decision.approved ? "approved" : decision.expired ? "expired" : "rejected";
958
+ if (decision.approved) this.a.onGranted?.(action, id);
959
+ if (tell) await client.permission.reply({
960
+ sessionID: sessionId,
961
+ requestID: id,
962
+ decision: decision.approved ? "once" : "reject",
963
+ ...decision.approved ? {} : {
964
+ message: decision.expired ? "Nobody approved this action in time, so it was not run." : `The user denied this action${decision.reason ? `: ${decision.reason}` : ""}. Do not retry it; tell the user what you would have done.`
965
+ }
966
+ });
967
+ const result = decision.approved ? {
968
+ id,
969
+ name: action,
970
+ output: {
971
+ approved: true
972
+ }
973
+ } : {
974
+ id,
975
+ name: action,
976
+ output: {
977
+ rejected: true,
978
+ ...decision.reason ? {
979
+ reason: decision.reason
980
+ } : {}
981
+ },
982
+ error: decision.expired ? "approval expired" : "rejected by the user",
983
+ denied: true,
984
+ ...decision.expired ? {
985
+ expired: true
986
+ } : {}
987
+ };
988
+ await store.updateToolCall({
989
+ toolCallId: id,
990
+ status: decision.approved ? "executed" : status === "expired" ? "expired" : "rejected",
991
+ output: result.output,
992
+ ...result.error !== void 0 ? {
993
+ error: result.error
994
+ } : {},
995
+ ...decision.executedByRef !== void 0 ? {
996
+ executedByRef: decision.executedByRef
997
+ } : {},
998
+ ...decision.remember === true ? {
999
+ remember: true
1000
+ } : {},
1001
+ ...decision.decidedVia !== void 0 ? {
1002
+ decidedVia: decision.decidedVia
1003
+ } : {}
1004
+ });
1005
+ await this.addResult(ask.messageId, result);
1006
+ await this.write({
1007
+ kind: "approval-settled",
1008
+ id,
1009
+ status,
1010
+ approver: ask.approver,
1011
+ ...decision.executedByRef !== void 0 ? {
1012
+ decidedBy: decision.executedByRef
1013
+ } : {},
1014
+ ...decision.decidedVia !== void 0 ? {
1015
+ decidedVia: decision.decidedVia
1016
+ } : {},
1017
+ ...decision.remember === true ? {
1018
+ remember: true
1019
+ } : {},
1020
+ ...decision.reason !== void 0 ? {
1021
+ reason: decision.reason
1022
+ } : {}
1023
+ });
1024
+ if (decision.approved) await this.write({
1025
+ kind: "tool-output",
1026
+ id,
1027
+ output: {
1028
+ approved: true
1029
+ }
1030
+ });
1031
+ else await this.write({
1032
+ kind: "tool-output-denied",
1033
+ id,
1034
+ ...decision.expired ? {
1035
+ reason: "approval expired"
1036
+ } : decision.reason ? {
1037
+ reason: decision.reason
1038
+ } : {}
1039
+ });
1040
+ }
1041
+ async onForm(form, recovering) {
1042
+ if (this.asked.has(form.id)) return;
1043
+ this.asked.add(form.id);
1044
+ const request = toElicitation(form.id, form);
1045
+ if (recovering && await this.alreadyRecorded(form.id)) {
1046
+ const messageId2 = await this.messageOf(form.id);
1047
+ this.reach({
1048
+ kind: "ask",
1049
+ ask: {
1050
+ kind: "form",
1051
+ id: form.id,
1052
+ form,
1053
+ request,
1054
+ ...messageId2 !== void 0 ? {
1055
+ messageId: messageId2
1056
+ } : {}
1057
+ }
1058
+ });
1059
+ return;
1060
+ }
1061
+ const input = {
1062
+ ...request.preamble !== void 0 ? {
1063
+ preamble: request.preamble
1064
+ } : {},
1065
+ questions: request.questions
1066
+ };
1067
+ await this.step();
1068
+ this.segment.calls.push({
1069
+ id: form.id,
1070
+ name: ASK_TOOL_NAME,
1071
+ input,
1072
+ kind: "ask"
1073
+ });
1074
+ const messageId = await this.flush(false);
1075
+ await this.a.store.recordToolCall({
1076
+ toolCallId: form.id,
1077
+ messageId,
1078
+ toolName: ASK_TOOL_NAME,
1079
+ toolType: "action",
1080
+ input,
1081
+ status: "pending_approval",
1082
+ runId: this.a.runId
1083
+ });
1084
+ await this.write({
1085
+ kind: "elicitation",
1086
+ id: form.id,
1087
+ request
1088
+ });
1089
+ this.reach({
1090
+ kind: "ask",
1091
+ ask: {
1092
+ kind: "form",
1093
+ id: form.id,
1094
+ form,
1095
+ request,
1096
+ messageId
1097
+ }
1098
+ });
1099
+ }
1100
+ async answered(ask, reply, tell) {
1101
+ const { client, sessionId } = this.a;
1102
+ const { form, request } = ask;
1103
+ const answer = isDecision(reply) ? {
1104
+ answers: {},
1105
+ skipped: true
1106
+ } : reply;
1107
+ if (!tell) {
1108
+ } else if (answer.skipped === true) {
1109
+ await client.session.form.cancel({
1110
+ sessionID: sessionId,
1111
+ formID: form.id
1112
+ });
1113
+ } else {
1114
+ await client.session.form.reply({
1115
+ sessionID: sessionId,
1116
+ formID: form.id,
1117
+ answer: toFormAnswer(form, answer.answers)
1118
+ });
1119
+ }
1120
+ const result = (0, import_nestjs_agent_core2.settleElicitation)(request, answer);
1121
+ await this.a.store.updateToolCall({
1122
+ toolCallId: form.id,
1123
+ status: result.skipped ? "rejected" : "executed",
1124
+ output: result,
1125
+ ...result.skipped ? {
1126
+ error: "skipped by the user"
1127
+ } : {},
1128
+ ...answer.answeredByRef !== void 0 ? {
1129
+ executedByRef: answer.answeredByRef
1130
+ } : {},
1131
+ ...answer.answeredVia !== void 0 ? {
1132
+ decidedVia: answer.answeredVia
1133
+ } : {}
1134
+ });
1135
+ await this.addResult(ask.messageId, {
1136
+ id: form.id,
1137
+ name: ASK_TOOL_NAME,
1138
+ output: result
1139
+ });
1140
+ await this.write({
1141
+ kind: "tool-output",
1142
+ id: form.id,
1143
+ output: result
1144
+ });
1145
+ }
1146
+ async addResult(messageId, result) {
1147
+ if (messageId === void 0) return;
1148
+ let known = this.messageResults.get(messageId);
1149
+ if (known === void 0) {
1150
+ const thread = await this.a.store.getThread(this.a.input.threadId);
1151
+ known = [
1152
+ ...thread?.messages.find((m) => m.id === messageId)?.toolResults ?? []
1153
+ ];
1154
+ }
1155
+ const results = [
1156
+ ...known.filter((r) => r.id !== result.id),
1157
+ result
1158
+ ];
1159
+ this.messageResults.set(messageId, results);
1160
+ await this.a.store.setMessageToolResults(messageId, results);
1161
+ }
1162
+ async end(outcome) {
1163
+ if (this.ended) return;
1164
+ this.ended = true;
1165
+ if (outcome.status !== "failed" || this.segment.text || this.segment.calls.length > 0 || this.segment.ui.length > 0) {
1166
+ await this.flush(true);
1167
+ }
1168
+ if (this.stepOpen) {
1169
+ this.stepOpen = false;
1170
+ await this.write({
1171
+ kind: "step-finish"
1172
+ });
1173
+ }
1174
+ this.reach({
1175
+ kind: "finished",
1176
+ outcome
1177
+ });
1178
+ }
1179
+ };
1180
+
1181
+ // src/mcp.ts
1182
+ var LOOP_SERVED_KINDS = /* @__PURE__ */ new Set([
1183
+ "agent",
1184
+ "ask",
1185
+ "skill",
1186
+ "memory"
1187
+ ]);
1188
+ var b64url = /* @__PURE__ */ __name((data) => Buffer.from(data).toString("base64url"), "b64url");
1189
+ var OpenCodeToolsTokens = class {
1190
+ static {
1191
+ __name(this, "OpenCodeToolsTokens");
1192
+ }
1193
+ ttlMs;
1194
+ key;
1195
+ constructor(secret, ttlMs) {
1196
+ this.ttlMs = ttlMs;
1197
+ this.key = (0, import_node_crypto.createHash)("sha256").update("aviary:opencode:tools\0").update(secret ?? (0, import_node_crypto.randomBytes)(32).toString("hex")).digest();
1198
+ }
1199
+ mint(actor, server, now = Date.now()) {
1200
+ const claims = {
1201
+ v: 1,
1202
+ actor,
1203
+ server,
1204
+ exp: now + this.ttlMs
1205
+ };
1206
+ const body = b64url(JSON.stringify(claims));
1207
+ return `${body}.${this.sign(body)}`;
1208
+ }
1209
+ verify(token, now = Date.now()) {
1210
+ const [body, signature, extra] = token.split(".");
1211
+ if (body === void 0 || signature === void 0 || extra !== void 0) return null;
1212
+ const expected = Buffer.from(this.sign(body));
1213
+ const given = Buffer.from(signature);
1214
+ if (expected.length !== given.length || !(0, import_node_crypto.timingSafeEqual)(expected, given)) return null;
1215
+ try {
1216
+ const claims = JSON.parse(Buffer.from(body, "base64url").toString("utf8"));
1217
+ if (claims?.v !== 1 || typeof claims.server !== "string" || typeof claims.exp !== "number" || typeof claims.actor?.id !== "string" || claims.exp <= now) {
1218
+ return null;
1219
+ }
1220
+ return claims;
1221
+ } catch {
1222
+ return null;
1223
+ }
1224
+ }
1225
+ sign(body) {
1226
+ return (0, import_node_crypto.createHmac)("sha256", this.key).update(body).digest("base64url");
1227
+ }
1228
+ };
1229
+ function isZodSchema(schema) {
1230
+ return schema["~standard"].vendor === "zod";
1231
+ }
1232
+ __name(isZodSchema, "isZodSchema");
1233
+ function toInputSchema(schema) {
1234
+ const extension = (0, import_nestjs_agent_core3.schemaExtensionOf)(schema);
1235
+ if (extension !== void 0) {
1236
+ const base = toInputSchema(extension.base);
1237
+ const properties = {
1238
+ ...base.properties ?? {},
1239
+ ...extension.properties
1240
+ };
1241
+ return {
1242
+ ...base,
1243
+ properties
1244
+ };
1245
+ }
1246
+ let converted;
1247
+ if (isZodSchema(schema)) converted = (0, import_zod_json_schema_compat.toJsonSchemaCompat)(schema);
1248
+ else {
1249
+ const converter = schema["~standard"].jsonSchema;
1250
+ if (typeof converter?.input === "function") converted = converter.input();
1251
+ }
1252
+ if (converted === void 0) return {
1253
+ type: "object",
1254
+ properties: {},
1255
+ additionalProperties: true
1256
+ };
1257
+ const { type: _type, ...rest } = converted;
1258
+ return {
1259
+ ...rest,
1260
+ type: "object"
1261
+ };
1262
+ }
1263
+ __name(toInputSchema, "toInputSchema");
1264
+ var asText = /* @__PURE__ */ __name((output) => typeof output === "string" ? output : JSON.stringify(output), "asText");
1265
+ var OpenCodeToolRefusedError = class extends Error {
1266
+ static {
1267
+ __name(this, "OpenCodeToolRefusedError");
1268
+ }
1269
+ name = "OpenCodeToolRefusedError";
1270
+ };
1271
+ var OpenCodeMcpEndpoint2 = class {
1272
+ static {
1273
+ __name(this, "OpenCodeMcpEndpoint");
1274
+ }
1275
+ turns;
1276
+ tokens;
1277
+ constructor(turns, tokens) {
1278
+ this.turns = turns;
1279
+ this.tokens = tokens;
1280
+ }
1281
+ /** The claims of a request's bearer token, or `null`. */
1282
+ authenticate(authorization) {
1283
+ const header = Array.isArray(authorization) ? authorization[0] : authorization;
1284
+ const match = /^Bearer\s+(.+)$/i.exec(header ?? "");
1285
+ const token = match?.[1]?.trim();
1286
+ return token ? this.tokens.verify(token) : null;
1287
+ }
1288
+ async handle(req, res, body) {
1289
+ const claims = this.authenticate(req.headers.authorization);
1290
+ if (claims === null) {
1291
+ res.statusCode = 401;
1292
+ res.setHeader("WWW-Authenticate", 'Bearer error="invalid_token"');
1293
+ res.setHeader("content-type", "application/json");
1294
+ res.end(JSON.stringify({
1295
+ error: "unauthorized"
1296
+ }));
1297
+ return;
1298
+ }
1299
+ const server = this.server(claims);
1300
+ const transport = new import_streamableHttp.StreamableHTTPServerTransport({
1301
+ enableJsonResponse: true
1302
+ });
1303
+ res.on("close", () => {
1304
+ void transport.close();
1305
+ void server.close();
1306
+ });
1307
+ try {
1308
+ await server.connect(transport);
1309
+ await transport.handleRequest(req, res, body);
1310
+ } catch (error) {
1311
+ if (!res.headersSent) {
1312
+ res.statusCode = 500;
1313
+ res.setHeader("content-type", "application/json");
1314
+ res.end(JSON.stringify({
1315
+ jsonrpc: "2.0",
1316
+ error: {
1317
+ code: -32603,
1318
+ message: errorText(error, "Internal error")
1319
+ },
1320
+ id: null
1321
+ }));
1322
+ }
1323
+ }
1324
+ }
1325
+ /** One MCP server for one request, answering as the token's actor. */
1326
+ server(claims) {
1327
+ const server = new import_server.Server({
1328
+ name: "aviary-opencode",
1329
+ version: "1.0.0"
1330
+ }, {
1331
+ capabilities: {
1332
+ tools: {}
1333
+ }
1334
+ });
1335
+ const { actor } = claims;
1336
+ const call = /* @__PURE__ */ __name((meta, requestId) => this.turns.callContext({
1337
+ actor,
1338
+ serverKey: claims.server,
1339
+ requestId: String(requestId ?? ""),
1340
+ meta
1341
+ }), "call");
1342
+ server.setRequestHandler(import_types.ListToolsRequestSchema, async (request, extra) => {
1343
+ const turn = await call(request.params?._meta, extra.requestId);
1344
+ const input = turn?.input ?? {};
1345
+ const allowed = turn !== void 0 ? this.turns.allowedTools(turn.input) : void 0;
1346
+ const definitions = (await this.turns.registry.definitionsFor(actor, this.turns.rolesPolicyFor(input), allowed)).filter((definition) => !LOOP_SERVED_KINDS.has(definition.kind));
1347
+ const tools = definitions.map((definition) => ({
1348
+ name: definition.name,
1349
+ description: definition.description,
1350
+ inputSchema: toInputSchema(definition.inputSchema),
1351
+ annotations: {
1352
+ readOnlyHint: definition.kind === "read"
1353
+ }
1354
+ }));
1355
+ if (this.turns.memoryWritable()) {
1356
+ tools.push({
1357
+ name: import_nestjs_agent_core3.REMEMBER_TOOL_NAME,
1358
+ description: import_nestjs_agent_core3.REMEMBER_TOOL_DESCRIPTION,
1359
+ inputSchema: toInputSchema(import_nestjs_agent_core3.rememberInputSchema),
1360
+ annotations: {
1361
+ readOnlyHint: false
1362
+ }
1363
+ });
1364
+ }
1365
+ return {
1366
+ tools
1367
+ };
1368
+ });
1369
+ server.setRequestHandler(import_types.CallToolRequestSchema, async (request, extra) => {
1370
+ const { name, arguments: args } = request.params;
1371
+ try {
1372
+ const turn = await call(request.params._meta, extra.requestId);
1373
+ if (turn === void 0) {
1374
+ throw new OpenCodeToolRefusedError("This endpoint serves OpenCode turns: no turn of yours is running on this session.");
1375
+ }
1376
+ const output = name === import_nestjs_agent_core3.REMEMBER_TOOL_NAME && this.turns.memoryWritable() ? await this.remember(turn, args) : await this.invoke(turn, actor, name, args, String(extra.requestId ?? ""));
1377
+ return {
1378
+ content: [
1379
+ {
1380
+ type: "text",
1381
+ text: asText(output)
1382
+ }
1383
+ ]
1384
+ };
1385
+ } catch (error) {
1386
+ return {
1387
+ content: [
1388
+ {
1389
+ type: "text",
1390
+ text: errorText(error, "the tool failed")
1391
+ }
1392
+ ],
1393
+ isError: true
1394
+ };
1395
+ }
1396
+ });
1397
+ return server;
1398
+ }
1399
+ async invoke(turn, actor, name, args, requestId) {
1400
+ const { registry } = this.turns;
1401
+ const spec = registry.spec(name);
1402
+ if (spec === void 0 || LOOP_SERVED_KINDS.has(spec.kind)) throw new import_nestjs_agent_core3.ToolNotFoundError(name);
1403
+ const allowed = this.turns.allowedTools(turn.input);
1404
+ if (allowed !== void 0 && !allowed.includes(name)) throw new import_nestjs_agent_core3.ToolNotFoundError(name);
1405
+ if (spec.kind === "action" && !await this.turns.spendApproval(turn.runId, name, turn.input)) {
1406
+ throw new OpenCodeToolRefusedError(`"${name}" is an action and nobody approved this call; ask for the permission first.`);
1407
+ }
1408
+ return registry.invoke(name, args ?? {}, {
1409
+ actor,
1410
+ threadId: turn.ctx.threadId,
1411
+ runId: turn.runId,
1412
+ requestId: `mcp:${turn.runId}:${requestId}`,
1413
+ emitUi: turn.ctx.emitUi,
1414
+ ...turn.input.agentName !== void 0 ? {
1415
+ agentName: turn.input.agentName
1416
+ } : {},
1417
+ ...turn.input.persona !== void 0 ? {
1418
+ persona: turn.input.persona
1419
+ } : {},
1420
+ ...turn.input.pageContext !== void 0 ? {
1421
+ pageContext: turn.input.pageContext
1422
+ } : {}
1423
+ }, this.turns.rolesPolicyFor(turn.input), allowed !== void 0 ? {
1424
+ allowedTools: allowed
1425
+ } : {});
1426
+ }
1427
+ /** `remember`: one fact, at the actor's own scope only — as the loop serves it. */
1428
+ async remember(turn, args) {
1429
+ const parsed = await import_nestjs_agent_core3.rememberInputSchema["~standard"].validate(args ?? {});
1430
+ if (parsed.issues !== void 0) {
1431
+ throw new OpenCodeToolRefusedError(`remember: ${parsed.issues.map((issue) => issue.message).join("; ")}`);
1432
+ }
1433
+ return this.turns.remember(parsed.value, turn);
1434
+ }
1435
+ };
1436
+
1437
+ // src/runner.ts
1438
+ var import_nestjs_agent_core5 = require("@dudousxd/nestjs-agent-core");
1439
+ var import_common5 = require("@nestjs/common");
1440
+
1441
+ // src/turns.ts
1442
+ var import_node_crypto2 = require("crypto");
1443
+ var import_nestjs_agent = require("@dudousxd/nestjs-agent");
1444
+ var import_nestjs_agent_core4 = require("@dudousxd/nestjs-agent-core");
1445
+ var import_common4 = require("@nestjs/common");
1446
+
1447
+ // src/event-hub.ts
1448
+ var import_common3 = require("@nestjs/common");
1449
+ function sessionOf(event) {
1450
+ const id = event.data?.sessionID ?? event.data?.form?.sessionID;
1451
+ return typeof id === "string" ? id : void 0;
1452
+ }
1453
+ __name(sessionOf, "sessionOf");
1454
+ var DEFAULT_CONNECT_TIMEOUT_MS = 1e4;
1455
+ var OpenCodeEventHub = class _OpenCodeEventHub {
1456
+ static {
1457
+ __name(this, "OpenCodeEventHub");
1458
+ }
1459
+ options;
1460
+ logger = new import_common3.Logger(_OpenCodeEventHub.name);
1461
+ streams = /* @__PURE__ */ new Map();
1462
+ constructor(options = {}) {
1463
+ this.options = options;
1464
+ }
1465
+ /**
1466
+ * Listen to one session's events on a server. Resolves once the server's stream is open (its first
1467
+ * event arrived), or after the connect timeout — the turn's safety nets cover a stream that is
1468
+ * slow to open.
1469
+ */
1470
+ async listen(serverKey, client, sessionId, listener) {
1471
+ const stream = this.streamFor(serverKey, client);
1472
+ let set = stream.listeners.get(sessionId);
1473
+ if (set === void 0) {
1474
+ set = /* @__PURE__ */ new Set();
1475
+ stream.listeners.set(sessionId, set);
1476
+ }
1477
+ set.add(listener);
1478
+ await stream.ready;
1479
+ return () => {
1480
+ const live = this.streams.get(serverKey) ?? stream;
1481
+ const current = live.listeners.get(sessionId);
1482
+ current?.delete(listener);
1483
+ if (current?.size === 0) live.listeners.delete(sessionId);
1484
+ if (live.listeners.size === 0 && this.streams.get(serverKey) === live) {
1485
+ live.controller.abort();
1486
+ this.streams.delete(serverKey);
1487
+ }
1488
+ };
1489
+ }
1490
+ close() {
1491
+ for (const stream of this.streams.values()) stream.controller.abort();
1492
+ this.streams.clear();
1493
+ }
1494
+ /**
1495
+ * The server's stream, opened on `client`. A different client for a known key (the host
1496
+ * replaced the connection, e.g. a new sandbox under the same key) moves every listener to a stream
1497
+ * on the new one: the old connection may be dead, and its sessions would never hear back.
1498
+ */
1499
+ streamFor(serverKey, client) {
1500
+ const existing = this.streams.get(serverKey);
1501
+ if (existing !== void 0 && existing.client === client) return existing;
1502
+ existing?.controller.abort();
1503
+ const controller = new AbortController();
1504
+ let opened;
1505
+ const ready = new Promise((resolve) => {
1506
+ opened = resolve;
1507
+ });
1508
+ const timeoutMs = this.options.connectTimeoutMs ?? DEFAULT_CONNECT_TIMEOUT_MS;
1509
+ const timer = setTimeout(() => {
1510
+ this.logger.warn(`event stream of ${serverKey} not open after ${timeoutMs}ms; going on`);
1511
+ opened();
1512
+ }, timeoutMs);
1513
+ timer.unref?.();
1514
+ void ready.then(() => clearTimeout(timer));
1515
+ controller.signal.addEventListener("abort", () => opened(), {
1516
+ once: true
1517
+ });
1518
+ const stream = {
1519
+ client,
1520
+ controller,
1521
+ listeners: existing?.listeners ?? /* @__PURE__ */ new Map(),
1522
+ ready
1523
+ };
1524
+ this.streams.set(serverKey, stream);
1525
+ void this.pump(serverKey, stream, client, opened);
1526
+ return stream;
1527
+ }
1528
+ async pump(serverKey, stream, client, opened) {
1529
+ let backoff = 500;
1530
+ while (!stream.controller.signal.aborted) {
1531
+ try {
1532
+ const events = client.event.subscribe({
1533
+ signal: stream.controller.signal
1534
+ });
1535
+ for await (const event of events) {
1536
+ opened();
1537
+ backoff = 500;
1538
+ const sessionId = sessionOf(event);
1539
+ if (sessionId === void 0) continue;
1540
+ for (const listener of stream.listeners.get(sessionId) ?? []) {
1541
+ try {
1542
+ listener(event);
1543
+ } catch (error) {
1544
+ this.logger.error(`listener failed: ${error.message}`);
1545
+ }
1546
+ }
1547
+ }
1548
+ } catch (error) {
1549
+ if (stream.controller.signal.aborted) return;
1550
+ this.logger.warn(`event stream of ${serverKey} dropped: ${error.message}`);
1551
+ }
1552
+ if (stream.controller.signal.aborted) return;
1553
+ await new Promise((resolve) => setTimeout(resolve, backoff).unref?.());
1554
+ backoff = Math.min(backoff * 2, 1e4);
1555
+ }
1556
+ }
1557
+ };
1558
+
1559
+ // src/turns.ts
1560
+ function _ts_decorate2(decorators, target, key, desc) {
1561
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1562
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
1563
+ r = Reflect.decorate(decorators, target, key, desc);
1564
+ } else {
1565
+ for (var i = decorators.length - 1; i >= 0; i--) {
1566
+ if (d = decorators[i]) {
1567
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
1568
+ }
1569
+ }
1570
+ }
1571
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
1572
+ }
1573
+ __name(_ts_decorate2, "_ts_decorate");
1574
+ function _ts_metadata2(metadataKey, metadataValue) {
1575
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") {
1576
+ return Reflect.metadata(metadataKey, metadataValue);
1577
+ }
1578
+ }
1579
+ __name(_ts_metadata2, "_ts_metadata");
1580
+ function _ts_param2(paramIndex, decorator) {
1581
+ return function(target, key) {
1582
+ decorator(target, key, paramIndex);
1583
+ };
1584
+ }
1585
+ __name(_ts_param2, "_ts_param");
1586
+ var DEFAULT_TURN_TIMEOUT_MS = 30 * 6e4;
1587
+ var DEFAULT_TOOLS_TTL_MS = 7 * 24 * 60 * 6e4;
1588
+ var OpenCodeTurns = class {
1589
+ static {
1590
+ __name(this, "OpenCodeTurns");
1591
+ }
1592
+ host;
1593
+ sessions;
1594
+ settings;
1595
+ store;
1596
+ sink;
1597
+ deps;
1598
+ registry;
1599
+ skills;
1600
+ memory;
1601
+ queue;
1602
+ toolsTokens;
1603
+ logger = new import_common4.Logger("OpenCodeTurns");
1604
+ events = new OpenCodeEventHub();
1605
+ live = /* @__PURE__ */ new Map();
1606
+ /** Actions approved per run, and the approvals the MCP endpoint already spent. */
1607
+ grants = /* @__PURE__ */ new Map();
1608
+ spent = /* @__PURE__ */ new Set();
1609
+ /** When the tools endpoint was last registered for a location (`server|boot|directory|actor`). */
1610
+ toolsIssued = /* @__PURE__ */ new Map();
1611
+ constructor(host, sessions, settings, store, sink, deps, registry, skills, memory, queue, toolsTokens2) {
1612
+ this.host = host;
1613
+ this.sessions = sessions;
1614
+ this.settings = settings;
1615
+ this.store = store;
1616
+ this.sink = sink;
1617
+ this.deps = deps;
1618
+ this.registry = registry;
1619
+ this.skills = skills;
1620
+ this.memory = memory;
1621
+ this.queue = queue;
1622
+ this.toolsTokens = toolsTokens2;
1623
+ }
1624
+ /** The tools server's name in OpenCode. */
1625
+ get toolsServer() {
1626
+ return this.settings.tools?.server ?? "aviary";
1627
+ }
1628
+ /** The roles policy a turn's tools are gated by (the agent's own). */
1629
+ rolesPolicyFor(input) {
1630
+ return this.deps.forAgent(input.agentName).rolesPolicy;
1631
+ }
1632
+ /** The tools a turn may reach: the agent's allow-list, narrowed by its persona's. */
1633
+ allowedTools(input) {
1634
+ const deps = this.deps.forAgent(input.agentName);
1635
+ const agent = deps.toolAllowList;
1636
+ const persona = input.persona !== void 0 ? deps.personas?.find((p) => p.id === input.persona)?.allowedTools : void 0;
1637
+ if (agent === void 0) return persona === void 0 ? void 0 : [
1638
+ ...persona
1639
+ ];
1640
+ if (persona === void 0) return [
1641
+ ...agent
1642
+ ];
1643
+ return agent.filter((name) => persona.includes(name));
1644
+ }
1645
+ /**
1646
+ * Whether the tools endpoint serves `remember`: the module's tools are served and the memory
1647
+ * provider writes. Served by the engine's own endpoint only — never put in the module's shared
1648
+ * registry, where every MCP client and the `/tools` catalog would see it.
1649
+ */
1650
+ memoryWritable() {
1651
+ return this.settings.tools !== void 0 && this.memory?.provider.write !== void 0;
1652
+ }
1653
+ /** `remember` for a turn: one fact, at the actor's own scope only — as the loop serves it. */
1654
+ async remember(input, turn) {
1655
+ const memory = this.memory;
1656
+ const write = memory?.provider.write?.bind(memory.provider);
1657
+ if (memory === void 0 || write === void 0) return "Memory is read-only here.";
1658
+ const max = memory.maxFactChars ?? import_nestjs_agent_core4.DEFAULT_MAX_FACT_CHARS;
1659
+ if (input.fact.length > max) return `Not recorded: a fact is at most ${max} characters.`;
1660
+ const actor = turn.input.actor;
1661
+ const scopeCtx = {
1662
+ actor,
1663
+ threadId: turn.ctx.threadId,
1664
+ ...turn.input.agentName !== void 0 ? {
1665
+ agentName: turn.input.agentName
1666
+ } : {}
1667
+ };
1668
+ const scopes = await (memory.scopes ?? import_nestjs_agent_core4.defaultScopeResolver).resolve(scopeCtx);
1669
+ const scope = (0, import_nestjs_agent_core4.actorScope)(actor);
1670
+ const verdict = (0, import_nestjs_agent_core4.memoryWriteVerdict)({
1671
+ scope,
1672
+ scopes,
1673
+ actor,
1674
+ author: {
1675
+ kind: "agent"
1676
+ }
1677
+ });
1678
+ if (!verdict.allowed) return `Not recorded: ${verdict.reason}`;
1679
+ await write({
1680
+ key: input.key,
1681
+ text: input.fact,
1682
+ scope,
1683
+ origin: {
1684
+ author: "agent",
1685
+ actorRef: actor.id,
1686
+ threadId: turn.ctx.threadId,
1687
+ runId: turn.runId
1688
+ },
1689
+ ctx: scopeCtx
1690
+ });
1691
+ return `Recorded "${input.key}".`;
1692
+ }
1693
+ /**
1694
+ * The turn a tool call over the engine's MCP endpoint belongs to, or `undefined` when it belongs
1695
+ * to none — which the endpoint refuses: it exists only to serve turns. OpenCode names its session in
1696
+ * every call's `_meta` (`ai.opencode/sessionID`); the call is that session's running turn's, and
1697
+ * only when the endpoint's caller is the person that turn runs for, on the server the token was
1698
+ * issued for. A session this process is not following is found through OpenCode (`session.get` →
1699
+ * the thread it was created for → the thread's running turn); its components still reach the
1700
+ * stream, as a message of their own.
1701
+ */
1702
+ async callContext(call) {
1703
+ const sessionId = sessionOfMeta(call.meta);
1704
+ if (sessionId === void 0) return void 0;
1705
+ const scope = /* @__PURE__ */ __name((runId2) => `${runId2}:${call.requestId || (0, import_node_crypto2.randomUUID)()}`, "scope");
1706
+ for (const [runId2, live] of this.live) {
1707
+ if (live.handle.sessionId !== sessionId) continue;
1708
+ if (live.input.actor.id !== call.actor.id || live.handle.serverKey !== call.serverKey) {
1709
+ return void 0;
1710
+ }
1711
+ return {
1712
+ input: live.input,
1713
+ runId: runId2,
1714
+ ctx: {
1715
+ threadId: live.input.threadId,
1716
+ runId: runId2,
1717
+ emitUi: uiEmitter(scope(runId2), async (component) => {
1718
+ await live.turn.pushUi(component);
1719
+ await this.uiPushed(live.input, runId2, component);
1720
+ })
1721
+ }
1722
+ };
1723
+ }
1724
+ const threadId = await this.threadOfSession(call.actor, sessionId);
1725
+ if (threadId === void 0) return void 0;
1726
+ if (await this.store.ownerOfThread(threadId) !== call.actor.id) return void 0;
1727
+ const known = await this.sessions.get(threadId);
1728
+ if (known === null || known.sessionId !== sessionId || known.serverKey !== call.serverKey) {
1729
+ return void 0;
1730
+ }
1731
+ const runId = await this.activeRun(threadId);
1732
+ if (runId === null) return void 0;
1733
+ const input = {
1734
+ threadId,
1735
+ actor: call.actor,
1736
+ userText: "",
1737
+ ...known.agentName !== void 0 ? {
1738
+ agentName: known.agentName
1739
+ } : {},
1740
+ ...known.persona !== void 0 ? {
1741
+ persona: known.persona
1742
+ } : {}
1743
+ };
1744
+ return {
1745
+ input,
1746
+ runId,
1747
+ ctx: {
1748
+ threadId,
1749
+ runId,
1750
+ emitUi: uiEmitter(scope(runId), async (component) => {
1751
+ const writer = await this.sink.open(runId);
1752
+ await writer.write((0, import_nestjs_agent_core4.encodeStreamEvent)({
1753
+ kind: "ui",
1754
+ ...component
1755
+ }));
1756
+ await this.store.appendMessage({
1757
+ threadId,
1758
+ role: "assistant",
1759
+ content: "",
1760
+ runId,
1761
+ ui: [
1762
+ component
1763
+ ]
1764
+ });
1765
+ await this.uiPushed(input, runId, component);
1766
+ })
1767
+ }
1768
+ };
1769
+ }
1770
+ /**
1771
+ * Spend one approval of `tool` in `runId` — what the MCP endpoint asks before it runs an `action`.
1772
+ * OpenCode asks the person (or the policy answers) before it calls an action tool; the endpoint
1773
+ * runs it only against an approval this engine saw granted, so a caller that skipped OpenCode's
1774
+ * permission rules (a model in code mode that read the endpoint's headers, anything else that got
1775
+ * hold of the token) cannot run an action unapproved. One approval, one call. The approval is the
1776
+ * run's own: live in this process, else read off the run's persisted calls (spent marks are kept
1777
+ * per process).
1778
+ */
1779
+ async spendApproval(runId, tool, input) {
1780
+ const names = /* @__PURE__ */ new Set([
1781
+ tool,
1782
+ `${this.toolsServer}.${tool}`,
1783
+ `${this.toolsServer}_${tool}`
1784
+ ]);
1785
+ const local = (this.grants.get(runId) ?? []).find((grant) => names.has(grant.action) && !this.spent.has(grant.id));
1786
+ if (local !== void 0) {
1787
+ this.spent.add(local.id);
1788
+ return true;
1789
+ }
1790
+ const thread = await this.store.getThread(input.threadId);
1791
+ for (const message of thread?.messages ?? []) {
1792
+ if (message.runId !== runId) continue;
1793
+ for (const call of message.toolCalls ?? []) {
1794
+ if (!names.has(call.name) || this.spent.has(call.id)) continue;
1795
+ const result = message.toolResults?.find((r) => r.id === call.id);
1796
+ const approved = result?.output?.approved === true;
1797
+ if (approved) {
1798
+ this.spent.add(call.id);
1799
+ return true;
1800
+ }
1801
+ }
1802
+ }
1803
+ return false;
1804
+ }
1805
+ async activeRun(threadId) {
1806
+ if (this.store.activeRunForThread !== void 0) return this.store.activeRunForThread(threadId);
1807
+ return (await this.store.getThread(threadId))?.activeRunId ?? null;
1808
+ }
1809
+ async uiPushed(input, runId, component) {
1810
+ await this.host.onUi?.({
1811
+ input,
1812
+ runId,
1813
+ component
1814
+ }).catch((error) => {
1815
+ this.logger.warn(`onUi failed: ${errorText(error, "error")}`);
1816
+ });
1817
+ }
1818
+ /**
1819
+ * Push a component into the run an OpenCode session is serving, for the host's own trusted
1820
+ * callers (no caller check — {@link callContext} is the one for MCP requests). `false` when no run
1821
+ * is live on that session.
1822
+ */
1823
+ async pushToSession(sessionId, component) {
1824
+ for (const [runId, live] of this.live) {
1825
+ if (live.handle.sessionId !== sessionId) continue;
1826
+ await live.turn.pushUi(component);
1827
+ await this.uiPushed(live.input, runId, component);
1828
+ return true;
1829
+ }
1830
+ return false;
1831
+ }
1832
+ /** The runs this process is following now (an updater drains on it). */
1833
+ liveRuns() {
1834
+ return [
1835
+ ...this.live.keys()
1836
+ ];
1837
+ }
1838
+ async threadOfSession(actor, sessionId) {
1839
+ try {
1840
+ const server = await this.host.server(actor);
1841
+ const info = await server.client.session.get?.({
1842
+ sessionID: sessionId
1843
+ });
1844
+ const threadId = info?.metadata?.threadId;
1845
+ return typeof threadId === "string" ? threadId : void 0;
1846
+ } catch {
1847
+ return void 0;
1848
+ }
1849
+ }
1850
+ onApplicationShutdown() {
1851
+ for (const live of this.live.values()) live.stop();
1852
+ this.live.clear();
1853
+ this.events.close();
1854
+ }
1855
+ // ---- begin ---------------------------------------------------------------------------------
1856
+ /**
1857
+ * Persist the user message (or rewind the thread for a regenerate), find or create the thread's
1858
+ * session, and refresh what the session is told this turn. Safe to run again for the same run (a
1859
+ * durable `begin` re-run after a crash): the run's user message is written once.
1860
+ */
1861
+ async begin(runId, input) {
1862
+ const earlier = await this.prepareThread(runId, input);
1863
+ const server = await this.host.server(input.actor);
1864
+ const known = await this.sessions.get(input.threadId);
1865
+ const reusable = known !== null && known.serverKey === server.key && known.bootId === server.bootId && (await this.host.reuse?.({
1866
+ input,
1867
+ runId,
1868
+ session: known
1869
+ }) ?? true);
1870
+ let handle;
1871
+ let created = false;
1872
+ if (known !== null && reusable) {
1873
+ handle = {
1874
+ sessionId: known.sessionId,
1875
+ serverKey: known.serverKey,
1876
+ ...known.bootId !== void 0 ? {
1877
+ bootId: known.bootId
1878
+ } : {},
1879
+ ...known.directory !== void 0 ? {
1880
+ directory: known.directory
1881
+ } : {}
1882
+ };
1883
+ if (input.regenerate === true) await this.revertLastExchange(server.client, handle.sessionId);
1884
+ await this.addTools(input, server, handle.directory, false);
1885
+ } else {
1886
+ handle = await this.createSession(runId, input, server, earlier);
1887
+ created = true;
1888
+ }
1889
+ if (created || known?.agentName !== input.agentName || known?.persona !== input.persona) {
1890
+ await this.sessions.set(input.threadId, {
1891
+ ...handle,
1892
+ ...input.agentName !== void 0 ? {
1893
+ agentName: input.agentName
1894
+ } : {},
1895
+ ...input.persona !== void 0 ? {
1896
+ persona: input.persona
1897
+ } : {}
1898
+ });
1899
+ }
1900
+ await this.host.beforePrompt?.({
1901
+ input,
1902
+ runId,
1903
+ sessionId: handle.sessionId,
1904
+ client: server.client,
1905
+ created
1906
+ });
1907
+ await this.refreshInstructions(runId, input, server.client, handle.sessionId);
1908
+ return handle;
1909
+ }
1910
+ async createSession(runId, input, server, earlier, note) {
1911
+ const { client } = server;
1912
+ const context = {
1913
+ input,
1914
+ runId
1915
+ };
1916
+ const spec = await this.host.session(context);
1917
+ const directory = spec.location?.directory;
1918
+ const skills = await this.writeSkills(input, client, directory);
1919
+ const sessionId = (await client.session.create({
1920
+ ...spec,
1921
+ permissions: [
1922
+ ...spec.permissions ?? [],
1923
+ ...this.toolRules(input),
1924
+ ...skills.map((name) => ({
1925
+ action: "skill",
1926
+ resource: name,
1927
+ effect: "allow"
1928
+ }))
1929
+ ],
1930
+ metadata: {
1931
+ ...spec.metadata ?? {},
1932
+ threadId: input.threadId
1933
+ }
1934
+ })).id;
1935
+ const handle = {
1936
+ sessionId,
1937
+ serverKey: server.key,
1938
+ ...server.bootId !== void 0 ? {
1939
+ bootId: server.bootId
1940
+ } : {},
1941
+ ...directory !== void 0 ? {
1942
+ directory
1943
+ } : {}
1944
+ };
1945
+ await this.sessions.set(input.threadId, handle);
1946
+ await this.addTools(input, server, directory, true);
1947
+ await this.host.prepare?.({
1948
+ ...context,
1949
+ sessionId,
1950
+ client
1951
+ });
1952
+ const transcript = transcriptOf(earlier, this.settings.historyMessages ?? 20);
1953
+ if (transcript || note) {
1954
+ await client.session.instructions.entry.put({
1955
+ sessionID: sessionId,
1956
+ key: "aviary.history",
1957
+ value: [
1958
+ transcript ? `The conversation so far (continue it; do not repeat it):
1959
+ ${transcript}` : "",
1960
+ note ?? ""
1961
+ ].filter(Boolean).join("\n\n")
1962
+ });
1963
+ }
1964
+ return handle;
1965
+ }
1966
+ /** The user message, as the loop persists it (or, on a regenerate, the thread rewound to it). */
1967
+ async prepareThread(runId, input) {
1968
+ const thread = await this.store.getThread(input.threadId);
1969
+ const messages = thread?.messages ?? [];
1970
+ if (input.regenerate === true) {
1971
+ let lastUser = -1;
1972
+ for (let i = messages.length - 1; i >= 0; i -= 1) if (messages[i]?.role === "user") {
1973
+ lastUser = i;
1974
+ break;
1975
+ }
1976
+ const firstDropped = messages[lastUser + 1];
1977
+ if (firstDropped !== void 0) await this.store.truncateFrom(input.threadId, firstDropped.id);
1978
+ return messages.slice(0, Math.max(lastUser, 0));
1979
+ }
1980
+ const mine = messages.findIndex((m) => m.role === "user" && m.runId === runId);
1981
+ if (mine >= 0) return messages.slice(0, mine);
1982
+ await this.store.appendMessage({
1983
+ threadId: input.threadId,
1984
+ role: "user",
1985
+ content: input.userText,
1986
+ runId,
1987
+ ...input.persona !== void 0 ? {
1988
+ persona: input.persona
1989
+ } : {},
1990
+ ...input.attachments !== void 0 ? {
1991
+ attachments: input.attachments
1992
+ } : {}
1993
+ });
1994
+ if (thread !== null && (thread.title === "" || thread.title === "New chat")) {
1995
+ await this.store.setTitle(input.threadId, titleOf(input.userText));
1996
+ }
1997
+ return messages;
1998
+ }
1999
+ /** Rewind the session to before its last user message, so the regenerated answer replaces it. */
2000
+ async revertLastExchange(client, sessionId) {
2001
+ if (client.message === void 0 || client.session.revert === void 0) return;
2002
+ const page = await client.message.list({
2003
+ sessionID: sessionId,
2004
+ order: "desc",
2005
+ limit: 50
2006
+ });
2007
+ const last = page.data.find((m) => m.type === "user");
2008
+ if (last === void 0) return;
2009
+ await client.session.revert.stage({
2010
+ sessionID: sessionId,
2011
+ messageID: last.id,
2012
+ files: false
2013
+ });
2014
+ await client.session.revert.commit({
2015
+ sessionID: sessionId
2016
+ });
2017
+ }
2018
+ /** The agent's prompt, the memory block and the host's entries — refreshed every turn. */
2019
+ async refreshInstructions(runId, input, client, sessionId) {
2020
+ const memory = await this.memoryBlock(input);
2021
+ const entries = {
2022
+ [this.settings.systemKey ?? "aviary.system"]: await this.systemPrompt(input),
2023
+ ...memory !== void 0 ? {
2024
+ "aviary.memory": memory
2025
+ } : {},
2026
+ ...await this.host.instructions?.({
2027
+ input,
2028
+ runId,
2029
+ sessionId
2030
+ }) ?? {}
2031
+ };
2032
+ for (const [key, value] of Object.entries(entries)) {
2033
+ await client.session.instructions.entry.put({
2034
+ sessionID: sessionId,
2035
+ key,
2036
+ value
2037
+ });
2038
+ }
2039
+ }
2040
+ /** The agent's base prompt (`@Agent({ systemPrompt })` / `@SystemPrompt()`) and the contributors. */
2041
+ async systemPrompt(input) {
2042
+ const deps = this.deps.forAgent(input.agentName);
2043
+ const ctx = {
2044
+ actor: input.actor,
2045
+ agentName: input.agentName ?? "default",
2046
+ ...input.pageContext !== void 0 ? {
2047
+ pageContext: input.pageContext
2048
+ } : {},
2049
+ ...input.uiCapabilities !== void 0 ? {
2050
+ uiCapabilities: input.uiCapabilities
2051
+ } : {}
2052
+ };
2053
+ const base = typeof deps.systemPrompt === "function" ? await deps.systemPrompt(ctx) : deps.systemPrompt;
2054
+ const sections = [
2055
+ base
2056
+ ];
2057
+ for (const contribute of deps.promptContributors) {
2058
+ const section = await contribute(ctx);
2059
+ if (section) sections.push(section);
2060
+ }
2061
+ return sections.filter(Boolean).join("\n\n");
2062
+ }
2063
+ scopeContext(input) {
2064
+ return {
2065
+ actor: input.actor,
2066
+ threadId: input.threadId,
2067
+ ...input.agentName !== void 0 ? {
2068
+ agentName: input.agentName
2069
+ } : {},
2070
+ ...input.pageContext !== void 0 ? {
2071
+ pageContext: input.pageContext
2072
+ } : {}
2073
+ };
2074
+ }
2075
+ /** What is on file about the actor (`forRoot({ memory })`); writable when `remember` is served. */
2076
+ async memoryBlock(input) {
2077
+ const memory = this.memory;
2078
+ if (memory === void 0) return void 0;
2079
+ const ctx = this.scopeContext(input);
2080
+ const scopes = await (memory.scopes ?? import_nestjs_agent_core4.defaultScopeResolver).resolve(ctx);
2081
+ const records = await memory.provider.list({
2082
+ scopes,
2083
+ ctx
2084
+ });
2085
+ const digest = (0, import_nestjs_agent_core4.resolveMemoryDigest)({
2086
+ records,
2087
+ scopes,
2088
+ ...memory.maxMemories !== void 0 ? {
2089
+ maxMemories: memory.maxMemories
2090
+ } : {}
2091
+ });
2092
+ if (digest.entries.length === 0) return void 0;
2093
+ return (0, import_nestjs_agent_core4.buildMemoryBlock)({
2094
+ entries: digest.entries,
2095
+ writable: this.memoryWritable(),
2096
+ partial: digest.omitted > 0
2097
+ });
2098
+ }
2099
+ /**
2100
+ * The module's skills (`forRoot({ skills })` / `@Skill`) as `SKILL.md` files under the session's
2101
+ * directory, where OpenCode's own `skill` tool finds them. Returns the names written.
2102
+ */
2103
+ async writeSkills(input, client, directory) {
2104
+ const skills = this.skills;
2105
+ if (skills === void 0 || client.file === void 0 || directory === void 0) return [];
2106
+ const ctx = this.scopeContext(input);
2107
+ const scopes = await (skills.scopes ?? import_nestjs_agent_core4.defaultScopeResolver).resolve(ctx);
2108
+ const summaries = await skills.provider.list({
2109
+ scopes,
2110
+ ctx
2111
+ });
2112
+ const { entries } = (0, import_nestjs_agent_core4.resolveSkillCatalog)(summaries, scopes, skills.maxSkills);
2113
+ const dir = this.settings.skillsDir ?? ".opencode/skills";
2114
+ const written = [];
2115
+ for (const entry of entries) {
2116
+ const body = await skills.provider.load({
2117
+ name: entry.name,
2118
+ scope: entry.scope,
2119
+ ctx
2120
+ });
2121
+ if (body === null) continue;
2122
+ const file = `---
2123
+ name: ${entry.name}
2124
+ description: ${JSON.stringify(entry.description)}
2125
+ ---
2126
+
2127
+ ${body}
2128
+ `;
2129
+ await client.file.write({
2130
+ location: {
2131
+ directory
2132
+ },
2133
+ path: `${dir}/${entry.name}/SKILL.md`,
2134
+ payload: new TextEncoder().encode(file)
2135
+ });
2136
+ written.push(entry.name);
2137
+ }
2138
+ return written;
2139
+ }
2140
+ /**
2141
+ * Permission rules for the module's tools: the MCP server allowed as a whole (OpenCode offers a
2142
+ * server's tools only when the server itself is allowed), then each `action` tool asked — the last
2143
+ * matching rule wins, so an action still lands on an approval card. Tools outside the agent's
2144
+ * (and persona's) allow-list, and the kinds only the loop serves, are denied. The endpoint enforces
2145
+ * all of it again on every call: these rules are what OpenCode offers, not what keeps a call out.
2146
+ */
2147
+ toolRules(input) {
2148
+ if (this.settings.tools === void 0) return [];
2149
+ const server = this.toolsServer;
2150
+ const allow = this.allowedTools(input);
2151
+ const both = /* @__PURE__ */ __name((name) => [
2152
+ `${server}.${name}`,
2153
+ `${server}_${name}`
2154
+ ], "both");
2155
+ const rules = [
2156
+ {
2157
+ action: `${server}*`,
2158
+ resource: "*",
2159
+ effect: "allow"
2160
+ }
2161
+ ];
2162
+ for (const spec of this.registry.allSpecs()) {
2163
+ const offered = (allow === void 0 || allow.includes(spec.name)) && !LOOP_SERVED_KINDS.has(spec.kind);
2164
+ if (offered && spec.kind === "read") continue;
2165
+ const effect = offered && spec.kind === "action" ? "ask" : "deny";
2166
+ for (const action of both(spec.name)) rules.push({
2167
+ action,
2168
+ resource: "*",
2169
+ effect
2170
+ });
2171
+ }
2172
+ return rules;
2173
+ }
2174
+ /**
2175
+ * Register the tools endpoint at the session's location, with a bearer token for the turn's actor
2176
+ * on this server. On a new session always; on a kept one when this process has not registered it
2177
+ * yet or its token is half-way through its life.
2178
+ */
2179
+ async addTools(input, server, directory, fresh) {
2180
+ const tools = this.settings.tools;
2181
+ const tokens = this.toolsTokens;
2182
+ if (tools === void 0 || tokens == null) return;
2183
+ const { client } = server;
2184
+ if (client.mcp === void 0) {
2185
+ this.logger.warn("`tools` is set but the OpenCode client cannot add MCP servers (`mcp.add`)");
2186
+ return;
2187
+ }
2188
+ const key = `${server.key}|${server.bootId ?? ""}|${directory ?? ""}|${input.actor.id}`;
2189
+ const issued = this.toolsIssued.get(key);
2190
+ const ttl = tools.ttlMs ?? DEFAULT_TOOLS_TTL_MS;
2191
+ if (!fresh && issued !== void 0 && Date.now() - issued < ttl / 2) return;
2192
+ await client.mcp.add({
2193
+ server: this.toolsServer,
2194
+ ...directory !== void 0 ? {
2195
+ location: {
2196
+ directory
2197
+ }
2198
+ } : {},
2199
+ config: {
2200
+ type: "remote",
2201
+ url: tools.url,
2202
+ headers: {
2203
+ Authorization: `Bearer ${tokens.mint(input.actor, server.key)}`
2204
+ },
2205
+ oauth: false
2206
+ }
2207
+ });
2208
+ this.toolsIssued.set(key, Date.now());
2209
+ }
2210
+ // ---- prompt / observe / reply --------------------------------------------------------------
2211
+ /** Start listening to the session (so nothing it says is missed), then send the user message. */
2212
+ async prompt(runId, input, handle) {
2213
+ const live = await this.ensureLive(runId, input, handle, false);
2214
+ try {
2215
+ const prompt = await this.host.promptFor?.({
2216
+ input,
2217
+ runId,
2218
+ sessionId: handle.sessionId
2219
+ }) ?? {
2220
+ text: input.userText,
2221
+ ...input.attachments?.length && this.host.files ? {
2222
+ files: await this.host.files({
2223
+ input,
2224
+ runId
2225
+ })
2226
+ } : {}
2227
+ };
2228
+ await live.client.session.prompt({
2229
+ sessionID: handle.sessionId,
2230
+ text: prompt.text,
2231
+ ...prompt.files?.length ? {
2232
+ files: prompt.files
2233
+ } : {}
2234
+ });
2235
+ } catch (error) {
2236
+ live.turn.fail(`OpenCode refused the message: ${error.message}`);
2237
+ }
2238
+ }
2239
+ /**
2240
+ * Wait for the turn's next milestone. In a process that was not following the turn (a resumed
2241
+ * durable run), the turn is rebuilt and first catches up on requests OpenCode raised meanwhile;
2242
+ * `session.wait` is the safety net for a terminal event that was missed.
2243
+ */
2244
+ async observe(runId, input, handle) {
2245
+ const live = await this.ensureLive(runId, input, handle, true);
2246
+ const { turn, client } = live;
2247
+ if (turn.hasMilestone()) return this.reached(runId, input, handle, await turn.next());
2248
+ let done = false;
2249
+ const next = turn.next().then((milestone) => {
2250
+ done = true;
2251
+ return milestone;
2252
+ });
2253
+ const timeoutMs = this.settings.turnTimeoutMs ?? DEFAULT_TURN_TIMEOUT_MS;
2254
+ const timer = setTimeout(() => {
2255
+ if (!done) turn.fail("Timed out waiting for OpenCode.");
2256
+ }, timeoutMs);
2257
+ timer.unref?.();
2258
+ if (client.session.wait !== void 0) {
2259
+ void client.session.wait({
2260
+ sessionID: handle.sessionId
2261
+ }).then(() => new Promise((resolve) => setTimeout(resolve, 2e3))).then(async () => {
2262
+ if (done || turn.finished) return;
2263
+ await turn.catchUp().catch(() => void 0);
2264
+ await new Promise((resolve) => setImmediate(resolve));
2265
+ if (!done && !turn.finished) turn.handle({
2266
+ type: "session.execution.succeeded",
2267
+ data: {
2268
+ sessionID: handle.sessionId
2269
+ }
2270
+ });
2271
+ }).catch(() => void 0);
2272
+ }
2273
+ try {
2274
+ return await this.reached(runId, input, handle, await next);
2275
+ } finally {
2276
+ clearTimeout(timer);
2277
+ }
2278
+ }
2279
+ /**
2280
+ * Hand a person's answer back to OpenCode. When the server restarted while the turn waited, the
2281
+ * request is gone with it: a new session is opened, told what the person decided, and prompted
2282
+ * to go on.
2283
+ */
2284
+ async reply(runId, input, handle, ask, reply) {
2285
+ const server = await this.host.server(input.actor);
2286
+ if (server.key !== handle.serverKey || server.bootId !== handle.bootId) {
2287
+ return this.continueAfterRestart(runId, input, server, ask, reply);
2288
+ }
2289
+ const live = await this.ensureLive(runId, input, handle, false);
2290
+ await live.turn.decide(ask, reply);
2291
+ return handle;
2292
+ }
2293
+ async continueAfterRestart(runId, input, server, ask, reply) {
2294
+ const approved = "approved" in reply && reply.approved === true;
2295
+ 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.`;
2296
+ const thread = await this.store.getThread(input.threadId);
2297
+ const handle = await this.createSession(runId, input, server, thread?.messages ?? [], note);
2298
+ await this.refreshInstructions(runId, input, server.client, handle.sessionId);
2299
+ this.drop(runId);
2300
+ const live = await this.ensureLive(runId, input, handle, false);
2301
+ await live.turn.decide(ask, reply, {
2302
+ tellOpenCode: false
2303
+ });
2304
+ await live.client.session.prompt({
2305
+ sessionID: handle.sessionId,
2306
+ text: note
2307
+ });
2308
+ return handle;
2309
+ }
2310
+ /** Interrupt the thread's session (cancel). */
2311
+ async interrupt(input) {
2312
+ const known = await this.sessions.get(input.threadId);
2313
+ if (known === null) return;
2314
+ const server = await this.host.server(input.actor);
2315
+ if (server.key !== known.serverKey || server.bootId !== known.bootId) return;
2316
+ await server.client.session.interrupt({
2317
+ sessionID: known.sessionId
2318
+ });
2319
+ }
2320
+ async ensureLive(runId, input, handle, catchUp) {
2321
+ const current = this.live.get(runId);
2322
+ if (current !== void 0 && current.handle.sessionId === handle.sessionId) return current;
2323
+ current?.stop();
2324
+ const server = await this.host.server(input.actor);
2325
+ const deps = this.deps.forAgent(input.agentName);
2326
+ const policy = deps.approvalPolicy ?? new import_nestjs_agent_core4.DefaultApprovalPolicy();
2327
+ const writer = await this.sink.open(input.sinkRunId ?? runId);
2328
+ const turn = new OpenCodeTurn({
2329
+ runId,
2330
+ input,
2331
+ client: server.client,
2332
+ sessionId: handle.sessionId,
2333
+ writer,
2334
+ store: this.store,
2335
+ approvalFor: /* @__PURE__ */ __name(async (action) => policy.requirementFor({
2336
+ name: action,
2337
+ kind: "action"
2338
+ }, input.actor, {
2339
+ threadId: input.threadId,
2340
+ runId,
2341
+ ...input.agentName !== void 0 ? {
2342
+ agentName: input.agentName
2343
+ } : {}
2344
+ }), "approvalFor"),
2345
+ modelLabel: input.model ?? "opencode",
2346
+ logger: this.logger,
2347
+ onGranted: /* @__PURE__ */ __name((action, id) => {
2348
+ const grants = this.grants.get(runId) ?? [];
2349
+ grants.push({
2350
+ action,
2351
+ id
2352
+ });
2353
+ this.grants.set(runId, grants);
2354
+ }, "onGranted")
2355
+ });
2356
+ const stop = await this.events.listen(server.key, server.client, handle.sessionId, (event) => turn.handle(event));
2357
+ const live = {
2358
+ turn,
2359
+ input,
2360
+ handle,
2361
+ client: server.client,
2362
+ stop
2363
+ };
2364
+ this.live.set(runId, live);
2365
+ if (catchUp && current === void 0) await turn.catchUp().catch(() => void 0);
2366
+ return live;
2367
+ }
2368
+ /** Stop following a run in this process. */
2369
+ drop(runId) {
2370
+ this.live.get(runId)?.stop();
2371
+ this.live.delete(runId);
2372
+ }
2373
+ /** Stop following a run and drop the approvals it was granted: it is over. */
2374
+ forget(runId) {
2375
+ this.drop(runId);
2376
+ for (const grant of this.grants.get(runId) ?? []) this.spent.delete(grant.id);
2377
+ this.grants.delete(runId);
2378
+ }
2379
+ // ---- settle --------------------------------------------------------------------------------
2380
+ /** A person was asked something: tell the host (cards in other channels, wake-ups). */
2381
+ async reached(runId, input, handle, milestone) {
2382
+ if (milestone.kind === "ask" && this.host.onAsk !== void 0) {
2383
+ await this.host.onAsk({
2384
+ input,
2385
+ runId,
2386
+ sessionId: handle.sessionId,
2387
+ ask: milestone.ask
2388
+ }).catch((error) => this.logger.warn(`onAsk failed: ${errorText(error, "error")}`));
2389
+ }
2390
+ return milestone;
2391
+ }
2392
+ /** What the run produced, as the host's settle hooks see it. */
2393
+ async runResult(runId, input, outcome, durationMs) {
2394
+ const thread = await this.store.getThread(input.threadId);
2395
+ const messages = (thread?.messages ?? []).filter((m) => m.role === "assistant" && m.runId === runId);
2396
+ const usage = {
2397
+ inputTokens: 0,
2398
+ outputTokens: 0,
2399
+ costUsd: 0
2400
+ };
2401
+ for (const m of messages) {
2402
+ usage.inputTokens += m.usage?.inputTokens ?? 0;
2403
+ usage.outputTokens += m.usage?.outputTokens ?? 0;
2404
+ usage.costUsd += m.usage?.costUsd ?? 0;
2405
+ }
2406
+ return {
2407
+ runId,
2408
+ input,
2409
+ outcome,
2410
+ text: messages.map((m) => m.content).filter(Boolean).join("\n\n"),
2411
+ messages,
2412
+ usage,
2413
+ durationMs
2414
+ };
2415
+ }
2416
+ /**
2417
+ * The host's last word on the answer before the stream ends: components to append (a guardrail
2418
+ * notice), or, for a failed run, the error the person reads instead of OpenCode's.
2419
+ */
2420
+ async amend(writer, result) {
2421
+ if (this.host.beforeSettle === void 0) return {};
2422
+ let amendment = {};
2423
+ try {
2424
+ amendment = await this.host.beforeSettle(result) ?? {};
2425
+ } catch (error) {
2426
+ this.logger.warn(`beforeSettle failed: ${errorText(error, "error")}`);
2427
+ }
2428
+ const ui = amendment.ui ?? [];
2429
+ const last = result.messages.at(-1);
2430
+ if (ui.length > 0) {
2431
+ for (const component of ui) await writer.write((0, import_nestjs_agent_core4.encodeStreamEvent)({
2432
+ kind: "ui",
2433
+ ...component
2434
+ }));
2435
+ if (last !== void 0) await this.store.setMessageUi?.(last.id, [
2436
+ ...last.ui ?? [],
2437
+ ...ui
2438
+ ]);
2439
+ }
2440
+ return amendment;
2441
+ }
2442
+ /** Delivery, telemetry, spend: after the run settled. Errors are the host's, never the run's. */
2443
+ async settled(result) {
2444
+ try {
2445
+ await this.host.onSettled?.(result);
2446
+ } catch (error) {
2447
+ this.logger.warn(`onSettled failed: ${errorText(error, "error")}`);
2448
+ }
2449
+ }
2450
+ /** End the run's stream and bookkeeping for how its execution ended. */
2451
+ async settle(runId, input, outcome, durationMs) {
2452
+ if (outcome.status === "failed") {
2453
+ await this.settleFailed(runId, input, outcome.error, durationMs);
2454
+ return;
2455
+ }
2456
+ this.forget(runId);
2457
+ const writer = await this.sink.open(runId);
2458
+ const result = await this.runResult(runId, input, outcome, durationMs);
2459
+ await this.amend(writer, result);
2460
+ if (outcome.status === "interrupted") {
2461
+ this.logger.log(`agent run ${runId} cancelled`);
2462
+ await Promise.resolve(this.store.failUnsettledToolCalls?.(runId, import_nestjs_agent_core4.RUN_ENDED_BEFORE_TOOL_CALL)).catch(() => 0);
2463
+ await this.handoff(writer, input, runId, "cancelled");
2464
+ await (0, import_nestjs_agent_core4.releaseThreadRun)(this.store, input.threadId, runId);
2465
+ await writer.write((0, import_nestjs_agent_core4.encodeStreamEvent)({
2466
+ kind: "cancelled"
2467
+ }));
2468
+ await writer.end();
2469
+ await this.store.recordRunEnd?.({
2470
+ runId,
2471
+ status: "cancelled",
2472
+ durationMs
2473
+ });
2474
+ } else {
2475
+ await this.handoff(writer, input, runId, "completed");
2476
+ await (0, import_nestjs_agent_core4.releaseThreadRun)(this.store, input.threadId, runId);
2477
+ await writer.end();
2478
+ await this.store.recordRunEnd?.({
2479
+ runId,
2480
+ status: "completed",
2481
+ durationMs
2482
+ });
2483
+ }
2484
+ await this.settled(result);
2485
+ }
2486
+ async settleFailed(runId, input, error, durationMs = 0) {
2487
+ this.forget(runId);
2488
+ const writer = await this.sink.open(runId);
2489
+ const result = await this.runResult(runId, input, {
2490
+ status: "failed",
2491
+ error
2492
+ }, durationMs);
2493
+ const worded = (await this.amend(writer, result)).error;
2494
+ const message = worded ?? (0, import_nestjs_agent_core4.streamFailure)(new Error(error)).message;
2495
+ this.logger.error(`agent run ${runId} failed: ${error}`);
2496
+ (0, import_nestjs_agent_core4.publishAgentRunFailed)({
2497
+ runId,
2498
+ code: "opencode_failed",
2499
+ message: error
2500
+ });
2501
+ await this.store.recordRunEnd?.({
2502
+ runId,
2503
+ status: "failed",
2504
+ errorCode: "opencode_failed",
2505
+ errorMessage: error
2506
+ });
2507
+ await Promise.resolve(this.store.failUnsettledToolCalls?.(runId, import_nestjs_agent_core4.RUN_ENDED_BEFORE_TOOL_CALL)).catch(() => 0);
2508
+ await this.handoff(writer, input, runId, "failed", error);
2509
+ await (0, import_nestjs_agent_core4.releaseThreadRun)(this.store, input.threadId, runId);
2510
+ await writer.fail({
2511
+ code: "opencode_failed",
2512
+ message
2513
+ });
2514
+ await this.settled({
2515
+ ...result,
2516
+ outcome: {
2517
+ status: "failed",
2518
+ error: worded ?? error
2519
+ }
2520
+ });
2521
+ }
2522
+ /** Starts the next queued message of the thread; set by the runner (it owns `start`). */
2523
+ startNext;
2524
+ /** As `InlineAgentRunner`: hand the thread to its next queued message, and say so on the stream. */
2525
+ async handoff(writer, input, runId, outcome, error) {
2526
+ const queue = this.queue;
2527
+ const startNext = this.startNext;
2528
+ if (queue === void 0 || !queue.supported || startNext === void 0) return;
2529
+ if (input.sinkRunId !== void 0 || input.deliverTo !== void 0) return;
2530
+ try {
2531
+ const frame = await queue.handoff({
2532
+ threadId: input.threadId,
2533
+ runId,
2534
+ outcome,
2535
+ ...error !== void 0 ? {
2536
+ error
2537
+ } : {}
2538
+ }, (next, nextRunId) => startNext(next, nextRunId));
2539
+ if (frame !== void 0) await writer.write((0, import_nestjs_agent_core4.encodeStreamEvent)(frame));
2540
+ } catch (failure) {
2541
+ this.logger.error(`could not advance the queue of thread ${input.threadId} after run ${runId}: ${failure instanceof Error ? failure.message : String(failure)}`);
2542
+ }
2543
+ }
2544
+ };
2545
+ OpenCodeTurns = _ts_decorate2([
2546
+ (0, import_common4.Injectable)(),
2547
+ _ts_param2(0, (0, import_common4.Inject)(OPENCODE_HOST)),
2548
+ _ts_param2(1, (0, import_common4.Inject)(OPENCODE_SESSIONS)),
2549
+ _ts_param2(2, (0, import_common4.Inject)(OPENCODE_OPTIONS)),
2550
+ _ts_param2(3, (0, import_common4.Inject)(import_nestjs_agent_core4.AGENT_STORE)),
2551
+ _ts_param2(4, (0, import_common4.Inject)(import_nestjs_agent_core4.AGENT_SINK)),
2552
+ _ts_param2(5, (0, import_common4.Inject)(import_nestjs_agent_core4.AGENT_DEPS_FACTORY)),
2553
+ _ts_param2(6, (0, import_common4.Inject)(import_nestjs_agent_core4.AGENT_TOOL_REGISTRY)),
2554
+ _ts_param2(7, (0, import_common4.Optional)()),
2555
+ _ts_param2(7, (0, import_common4.Inject)(import_nestjs_agent_core4.AGENT_SKILLS)),
2556
+ _ts_param2(8, (0, import_common4.Optional)()),
2557
+ _ts_param2(8, (0, import_common4.Inject)(import_nestjs_agent_core4.AGENT_MEMORY)),
2558
+ _ts_param2(9, (0, import_common4.Optional)()),
2559
+ _ts_param2(9, (0, import_common4.Inject)(import_nestjs_agent.AGENT_CHAT_QUEUE)),
2560
+ _ts_param2(10, (0, import_common4.Optional)()),
2561
+ _ts_param2(10, (0, import_common4.Inject)(OPENCODE_TOOLS_TOKENS)),
2562
+ _ts_metadata2("design:type", Function),
2563
+ _ts_metadata2("design:paramtypes", [
2564
+ typeof OpenCodeHost === "undefined" ? Object : OpenCodeHost,
2565
+ typeof OpenCodeSessionStore === "undefined" ? Object : OpenCodeSessionStore,
2566
+ typeof OpenCodeEngineSettings === "undefined" ? Object : OpenCodeEngineSettings,
2567
+ typeof AgentStore === "undefined" ? Object : AgentStore,
2568
+ typeof TokenStreamSink === "undefined" ? Object : TokenStreamSink,
2569
+ typeof AgentDepsFactory === "undefined" ? Object : AgentDepsFactory,
2570
+ typeof ToolRegistry === "undefined" ? Object : ToolRegistry,
2571
+ typeof SkillsConfig === "undefined" ? Object : SkillsConfig,
2572
+ typeof MemoryConfig === "undefined" ? Object : MemoryConfig,
2573
+ typeof ChatQueueService === "undefined" ? Object : ChatQueueService,
2574
+ Object
2575
+ ])
2576
+ ], OpenCodeTurns);
2577
+ function titleOf(text) {
2578
+ const trimmed = text.trim().replace(/\s+/g, " ");
2579
+ return trimmed.length > 60 ? `${trimmed.slice(0, 57)}...` : trimmed || "New chat";
2580
+ }
2581
+ __name(titleOf, "titleOf");
2582
+ function transcriptOf(messages, limit) {
2583
+ return messages.filter((m) => (m.role === "user" || m.role === "assistant") && m.content.trim()).slice(-limit).map((m) => `${m.role === "user" ? "User" : "Assistant"}: ${m.content.slice(0, 2e3)}`).join("\n");
2584
+ }
2585
+ __name(transcriptOf, "transcriptOf");
2586
+ var SESSION_META_KEY = "ai.opencode/sessionID";
2587
+ function sessionOfMeta(meta) {
2588
+ const value = meta?.[SESSION_META_KEY];
2589
+ return typeof value === "string" && value.length > 0 ? value : void 0;
2590
+ }
2591
+ __name(sessionOfMeta, "sessionOfMeta");
2592
+ function uiEmitter(scope, push) {
2593
+ let next = 0;
2594
+ return async (component, props, options = {}) => {
2595
+ if (typeof component !== "string" || component.length === 0) {
2596
+ throw new Error("emitUi: component must be a non-empty string");
2597
+ }
2598
+ if (typeof props !== "object" || props === null || Array.isArray(props)) {
2599
+ throw new Error("emitUi: props must be a JSON object");
2600
+ }
2601
+ const id = options.id ?? `${scope}:ui:${next++}`;
2602
+ await push({
2603
+ id,
2604
+ component,
2605
+ props: JSON.parse(JSON.stringify(props)),
2606
+ ...options.version !== void 0 ? {
2607
+ version: options.version
2608
+ } : {}
2609
+ });
2610
+ return {
2611
+ id
2612
+ };
2613
+ };
2614
+ }
2615
+ __name(uiEmitter, "uiEmitter");
2616
+
2617
+ // src/runner.ts
2618
+ function _ts_decorate3(decorators, target, key, desc) {
2619
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
2620
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
2621
+ r = Reflect.decorate(decorators, target, key, desc);
2622
+ } else {
2623
+ for (var i = decorators.length - 1; i >= 0; i--) {
2624
+ if (d = decorators[i]) {
2625
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
2626
+ }
2627
+ }
2628
+ }
2629
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
2630
+ }
2631
+ __name(_ts_decorate3, "_ts_decorate");
2632
+ function _ts_metadata3(metadataKey, metadataValue) {
2633
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") {
2634
+ return Reflect.metadata(metadataKey, metadataValue);
2635
+ }
2636
+ }
2637
+ __name(_ts_metadata3, "_ts_metadata");
2638
+ function _ts_param3(paramIndex, decorator) {
2639
+ return function(target, key) {
2640
+ decorator(target, key, paramIndex);
2641
+ };
2642
+ }
2643
+ __name(_ts_param3, "_ts_param");
2644
+ var OpenCodeAgentRunner = class {
2645
+ static {
2646
+ __name(this, "OpenCodeAgentRunner");
2647
+ }
2648
+ turns;
2649
+ settings;
2650
+ live = /* @__PURE__ */ new Set();
2651
+ cancelled = /* @__PURE__ */ new Set();
2652
+ pending = /* @__PURE__ */ new Map();
2653
+ constructor(turns, settings) {
2654
+ this.turns = turns;
2655
+ this.settings = settings;
2656
+ turns.startNext = (next, runId) => this.start(next, {
2657
+ runId
2658
+ });
2659
+ }
2660
+ runIdFor(input) {
2661
+ return this.settings.runId?.(input) ?? crypto.randomUUID();
2662
+ }
2663
+ async isRunActive(runId) {
2664
+ if (this.live.has(runId)) return true;
2665
+ for (const key of this.pending.keys()) if (key.startsWith(`${runId}:`)) return true;
2666
+ return false;
2667
+ }
2668
+ async start(input, options = {}) {
2669
+ const runId = options.runId ?? this.runIdFor(input);
2670
+ this.live.add(runId);
2671
+ void this.run(runId, input).finally(() => {
2672
+ this.live.delete(runId);
2673
+ this.cancelled.delete(runId);
2674
+ for (const key of [
2675
+ ...this.pending.keys()
2676
+ ]) if (key.startsWith(`${runId}:`)) this.pending.delete(key);
2677
+ });
2678
+ return {
2679
+ runId
2680
+ };
2681
+ }
2682
+ /**
2683
+ * Deliver a person's reply. Answers addressed at an approval are refused
2684
+ * ({@link OpenCodeReplyMismatchError}, a 409) and the approval keeps waiting: they say nothing
2685
+ * about whether the action should run.
2686
+ */
2687
+ async signal(runId, toolCallId, reply) {
2688
+ const key = `${runId}:${toolCallId}`;
2689
+ const waiter = this.pending.get(key);
2690
+ if (waiter === void 0) return;
2691
+ if (waiter.on === "approval" && !isDecision(reply)) {
2692
+ throw new OpenCodeReplyMismatchError(runId, toolCallId);
2693
+ }
2694
+ this.pending.delete(key);
2695
+ waiter.resolve(reply);
2696
+ }
2697
+ /**
2698
+ * Stop the run: what it waits on a person for is dropped, and OpenCode is interrupted — it answers
2699
+ * `session.execution.interrupted`, which settles the run as cancelled.
2700
+ */
2701
+ async cancel(runId) {
2702
+ this.cancelled.add(runId);
2703
+ for (const [key, waiter] of [
2704
+ ...this.pending
2705
+ ]) {
2706
+ if (!key.startsWith(`${runId}:`)) continue;
2707
+ this.pending.delete(key);
2708
+ waiter.reject(new import_nestjs_agent_core5.RunCancelledError());
2709
+ }
2710
+ const input = this.inputs.get(runId);
2711
+ if (input !== void 0) await this.turns.interrupt(input).catch(() => void 0);
2712
+ }
2713
+ inputs = /* @__PURE__ */ new Map();
2714
+ async run(runId, input) {
2715
+ const started = Date.now();
2716
+ this.inputs.set(runId, input);
2717
+ try {
2718
+ let handle = await this.turns.begin(runId, input);
2719
+ if (this.cancelled.has(runId)) throw new import_nestjs_agent_core5.RunCancelledError();
2720
+ await this.turns.prompt(runId, input, handle);
2721
+ for (; ; ) {
2722
+ const milestone = await this.turns.observe(runId, input, handle);
2723
+ if (milestone.kind === "finished") {
2724
+ await this.turns.settle(runId, input, milestone.outcome, Date.now() - started);
2725
+ return;
2726
+ }
2727
+ const reply = await this.park(runId, milestone.ask.id, milestone.ask.kind === "approval" ? "approval" : "answers", milestone.timeoutMs);
2728
+ handle = await this.turns.reply(runId, input, handle, milestone.ask, reply);
2729
+ }
2730
+ } catch (error) {
2731
+ if (error instanceof import_nestjs_agent_core5.RunCancelledError) {
2732
+ await this.turns.interrupt(input).catch(() => void 0);
2733
+ await this.turns.settle(runId, input, {
2734
+ status: "interrupted"
2735
+ }, Date.now() - started);
2736
+ return;
2737
+ }
2738
+ await this.turns.settleFailed(runId, input, errorText(error, "the turn failed"));
2739
+ } finally {
2740
+ this.inputs.delete(runId);
2741
+ }
2742
+ }
2743
+ /** Wait for a person's answer; a lapsed approval answers itself as expired. */
2744
+ park(runId, toolCallId, on, timeoutMs) {
2745
+ const key = `${runId}:${toolCallId}`;
2746
+ const waiting = new Promise((resolve, reject) => {
2747
+ this.pending.set(key, {
2748
+ on,
2749
+ resolve,
2750
+ reject
2751
+ });
2752
+ });
2753
+ if (timeoutMs === void 0) return waiting;
2754
+ let timer;
2755
+ const lapse = new Promise((resolve) => {
2756
+ timer = setTimeout(() => {
2757
+ if (!this.pending.has(key)) return;
2758
+ this.pending.delete(key);
2759
+ resolve({
2760
+ approved: false,
2761
+ expired: true
2762
+ });
2763
+ }, timeoutMs);
2764
+ timer.unref?.();
2765
+ });
2766
+ return Promise.race([
2767
+ waiting,
2768
+ lapse
2769
+ ]).finally(() => clearTimeout(timer));
2770
+ }
2771
+ };
2772
+ OpenCodeAgentRunner = _ts_decorate3([
2773
+ (0, import_common5.Injectable)(),
2774
+ _ts_param3(1, (0, import_common5.Inject)(OPENCODE_OPTIONS)),
2775
+ _ts_metadata3("design:type", Function),
2776
+ _ts_metadata3("design:paramtypes", [
2777
+ typeof OpenCodeTurns === "undefined" ? Object : OpenCodeTurns,
2778
+ typeof OpenCodeEngineSettings === "undefined" ? Object : OpenCodeEngineSettings
2779
+ ])
2780
+ ], OpenCodeAgentRunner);
2781
+
2782
+ // src/engine.ts
2783
+ function isInstance(value, method) {
2784
+ return typeof value === "object" && value !== null && typeof value[method] === "function";
2785
+ }
2786
+ __name(isInstance, "isInstance");
2787
+ function bind(token, value, method) {
2788
+ if (isInstance(value, method)) return [
2789
+ {
2790
+ provide: token,
2791
+ useValue: value
2792
+ }
2793
+ ];
2794
+ if (typeof value === "function") {
2795
+ return [
2796
+ value,
2797
+ {
2798
+ provide: token,
2799
+ useExisting: value
2800
+ }
2801
+ ];
2802
+ }
2803
+ return [
2804
+ {
2805
+ provide: token,
2806
+ useExisting: value
2807
+ }
2808
+ ];
2809
+ }
2810
+ __name(bind, "bind");
2811
+ function openCodeControllers(options) {
2812
+ return options.tools !== void 0 ? [
2813
+ OpenCodeMcpController
2814
+ ] : [];
2815
+ }
2816
+ __name(openCodeControllers, "openCodeControllers");
2817
+ var DEFAULT_TOOLS_TTL_MS2 = 7 * 24 * 60 * 6e4;
2818
+ function toolsTokens(settings) {
2819
+ const tools = settings.tools;
2820
+ if (tools === void 0) return null;
2821
+ if (tools.secret === void 0) {
2822
+ new import_common6.Logger("OpenCodeEngine").warn("`tools` has no `secret`: the tools endpoint signs its tokens with a per-process secret, so it only works with one process (and sessions lose their tools on a restart until their next turn).");
2823
+ }
2824
+ return new OpenCodeToolsTokens(tools.secret, tools.ttlMs ?? DEFAULT_TOOLS_TTL_MS2);
2825
+ }
2826
+ __name(toolsTokens, "toolsTokens");
2827
+ function openCodeProviders(options) {
2828
+ const { host, sessions, ...settings } = options;
2829
+ return [
2830
+ ...bind(OPENCODE_HOST, host, "server"),
2831
+ ...bind(OPENCODE_SESSIONS, sessions ?? new InMemoryOpenCodeSessionStore(), "get"),
2832
+ {
2833
+ provide: OPENCODE_OPTIONS,
2834
+ useValue: settings
2835
+ },
2836
+ {
2837
+ provide: OPENCODE_TOOLS_TOKENS,
2838
+ useFactory: /* @__PURE__ */ __name(() => toolsTokens(settings), "useFactory")
2839
+ },
2840
+ OpenCodeTurns,
2841
+ {
2842
+ provide: OPENCODE_TURNS,
2843
+ useExisting: OpenCodeTurns
2844
+ },
2845
+ {
2846
+ provide: OPENCODE_MCP_ENDPOINT,
2847
+ useFactory: /* @__PURE__ */ __name((turns, tokens) => tokens === null ? null : new OpenCodeMcpEndpoint2(turns, tokens), "useFactory"),
2848
+ inject: [
2849
+ OpenCodeTurns,
2850
+ OPENCODE_TOOLS_TOKENS
2851
+ ]
2852
+ }
2853
+ ];
2854
+ }
2855
+ __name(openCodeProviders, "openCodeProviders");
2856
+
2857
+ // src/durable/runner.ts
2858
+ var import_node_crypto3 = require("crypto");
2859
+ var import_nestjs_agent_core6 = require("@dudousxd/nestjs-agent-core");
2860
+ var import_nestjs_durable2 = require("@dudousxd/nestjs-durable");
2861
+ var import_nestjs_durable_core2 = require("@dudousxd/nestjs-durable-core");
2862
+ var import_common8 = require("@nestjs/common");
2863
+
2864
+ // src/durable/workflow.ts
2865
+ var import_nestjs_durable = require("@dudousxd/nestjs-durable");
2866
+ var import_nestjs_durable_core = require("@dudousxd/nestjs-durable-core");
2867
+ var import_common7 = require("@nestjs/common");
2868
+ function _ts_decorate4(decorators, target, key, desc) {
2869
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
2870
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
2871
+ r = Reflect.decorate(decorators, target, key, desc);
2872
+ } else {
2873
+ for (var i = decorators.length - 1; i >= 0; i--) {
2874
+ if (d = decorators[i]) {
2875
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
2876
+ }
2877
+ }
2878
+ }
2879
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
2880
+ }
2881
+ __name(_ts_decorate4, "_ts_decorate");
2882
+ function _ts_metadata4(metadataKey, metadataValue) {
2883
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") {
2884
+ return Reflect.metadata(metadataKey, metadataValue);
2885
+ }
2886
+ }
2887
+ __name(_ts_metadata4, "_ts_metadata");
2888
+ function _ts_param4(paramIndex, decorator) {
2889
+ return function(target, key) {
2890
+ decorator(target, key, paramIndex);
2891
+ };
2892
+ }
2893
+ __name(_ts_param4, "_ts_param");
2894
+ var OPENCODE_RUN_WORKFLOW = "agent.opencode.run";
2895
+ function decisionToken(runId, toolCallId) {
2896
+ return `tool:${runId}:${toolCallId}`;
2897
+ }
2898
+ __name(decisionToken, "decisionToken");
2899
+ var OpenCodeRunWorkflow = class {
2900
+ static {
2901
+ __name(this, "OpenCodeRunWorkflow");
2902
+ }
2903
+ turns;
2904
+ runs;
2905
+ constructor(turns, runs) {
2906
+ this.turns = turns;
2907
+ this.runs = runs;
2908
+ }
2909
+ async run(ctx, input) {
2910
+ try {
2911
+ const begun = await ctx.localStep("begin", async () => ({
2912
+ handle: await this.turns.begin(ctx.runId, input),
2913
+ startedAt: Date.now()
2914
+ }));
2915
+ let handle = begun.handle;
2916
+ await ctx.localStep("prompt", async () => {
2917
+ await this.turns.prompt(ctx.runId, input, handle);
2918
+ return true;
2919
+ });
2920
+ for (let n = 0; ; n += 1) {
2921
+ const milestone = await ctx.localStep(`observe:${n}`, () => this.turns.observe(ctx.runId, input, handle));
2922
+ if (milestone.kind === "finished") {
2923
+ await ctx.localStep("finish", async () => {
2924
+ if (await this.cancelled(ctx.runId)) this.turns.drop(ctx.runId);
2925
+ else await this.turns.settle(ctx.runId, input, milestone.outcome, Date.now() - begun.startedAt);
2926
+ return true;
2927
+ });
2928
+ return {
2929
+ outcome: milestone.outcome.status
2930
+ };
2931
+ }
2932
+ let reply;
2933
+ try {
2934
+ reply = await ctx.waitForSignal(decisionToken(ctx.runId, milestone.ask.id), milestone.timeoutMs !== void 0 ? {
2935
+ timeoutMs: milestone.timeoutMs
2936
+ } : void 0);
2937
+ } catch (error) {
2938
+ if (!(error instanceof import_nestjs_durable_core.SignalTimeoutError)) throw error;
2939
+ reply = {
2940
+ approved: false,
2941
+ expired: true
2942
+ };
2943
+ }
2944
+ const ask = milestone.ask;
2945
+ handle = await ctx.localStep(`reply:${n}`, () => this.turns.reply(ctx.runId, input, handle, ask, reply));
2946
+ }
2947
+ } catch (error) {
2948
+ if ((0, import_nestjs_durable_core.isWorkflowControlFlowSignal)(error)) throw error;
2949
+ await ctx.localStep("fail", async () => {
2950
+ await this.turns.settleFailed(ctx.runId, input, errorText(error, "the turn failed"));
2951
+ return true;
2952
+ });
2953
+ return {
2954
+ outcome: "failed"
2955
+ };
2956
+ }
2957
+ }
2958
+ async cancelled(runId) {
2959
+ try {
2960
+ const status = (await this.runs?.getRunDetail(runId))?.run.status;
2961
+ return status === "cancelling" || status === "cancelled";
2962
+ } catch {
2963
+ return false;
2964
+ }
2965
+ }
2966
+ };
2967
+ OpenCodeRunWorkflow = _ts_decorate4([
2968
+ (0, import_common7.Injectable)(),
2969
+ (0, import_nestjs_durable.Workflow)({
2970
+ name: OPENCODE_RUN_WORKFLOW,
2971
+ version: "1"
2972
+ }),
2973
+ _ts_param4(1, (0, import_common7.Optional)()),
2974
+ _ts_param4(1, (0, import_common7.Inject)(import_nestjs_durable.RUN_GATEWAY)),
2975
+ _ts_metadata4("design:type", Function),
2976
+ _ts_metadata4("design:paramtypes", [
2977
+ typeof OpenCodeTurns === "undefined" ? Object : OpenCodeTurns,
2978
+ typeof RunGateway === "undefined" ? Object : RunGateway
2979
+ ])
2980
+ ], OpenCodeRunWorkflow);
2981
+
2982
+ // src/durable/runner.ts
2983
+ function _ts_decorate5(decorators, target, key, desc) {
2984
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
2985
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") {
2986
+ r = Reflect.decorate(decorators, target, key, desc);
2987
+ } else {
2988
+ for (var i = decorators.length - 1; i >= 0; i--) {
2989
+ if (d = decorators[i]) {
2990
+ r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
2991
+ }
2992
+ }
2993
+ }
2994
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
2995
+ }
2996
+ __name(_ts_decorate5, "_ts_decorate");
2997
+ function _ts_metadata5(metadataKey, metadataValue) {
2998
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") {
2999
+ return Reflect.metadata(metadataKey, metadataValue);
3000
+ }
3001
+ }
3002
+ __name(_ts_metadata5, "_ts_metadata");
3003
+ function _ts_param5(paramIndex, decorator) {
3004
+ return function(target, key) {
3005
+ decorator(target, key, paramIndex);
3006
+ };
3007
+ }
3008
+ __name(_ts_param5, "_ts_param");
3009
+ function isRunInput(value) {
3010
+ return typeof value === "object" && value !== null && typeof value.threadId === "string" && typeof value.actor === "object";
3011
+ }
3012
+ __name(isRunInput, "isRunInput");
3013
+ var DurableOpenCodeAgentRunner = class _DurableOpenCodeAgentRunner {
3014
+ static {
3015
+ __name(this, "DurableOpenCodeAgentRunner");
3016
+ }
3017
+ workflows;
3018
+ runs;
3019
+ turns;
3020
+ settings;
3021
+ host;
3022
+ store;
3023
+ logger = new import_common8.Logger(_DurableOpenCodeAgentRunner.name);
3024
+ constructor(workflows, runs, turns, settings, host, store) {
3025
+ this.workflows = workflows;
3026
+ this.runs = runs;
3027
+ this.turns = turns;
3028
+ this.settings = settings;
3029
+ this.host = host;
3030
+ this.store = store;
3031
+ turns.startNext = (next, runId) => this.start(next, {
3032
+ runId
3033
+ });
3034
+ }
3035
+ runIdFor(input) {
3036
+ return this.settings.runId?.(input) ?? (0, import_node_crypto3.randomUUID)();
3037
+ }
3038
+ async start(input, options = {}) {
3039
+ const runId = options.runId ?? this.runIdFor(input);
3040
+ const durable = this.settings.durable;
3041
+ const startOptions = {
3042
+ ...await durable?.start?.(input, runId) ?? {},
3043
+ ...await this.host.startOptions?.(input, runId) ?? {}
3044
+ };
3045
+ try {
3046
+ await this.workflows.start(OpenCodeRunWorkflow, input, runId, startOptions);
3047
+ } catch (error) {
3048
+ if ((0, import_nestjs_durable_core2.isWorkflowControlFlowSignal)(error)) return {
3049
+ runId
3050
+ };
3051
+ throw this.host.startError?.(error, input) ?? durable?.startError?.(error, input) ?? error;
3052
+ }
3053
+ return {
3054
+ runId
3055
+ };
3056
+ }
3057
+ async isRunActive(runId) {
3058
+ try {
3059
+ const status = (await this.runs.getRunDetail(runId))?.run.status;
3060
+ return !(status === "completed" || status === "failed" || status === "cancelled" || status === "dead");
3061
+ } catch {
3062
+ return true;
3063
+ }
3064
+ }
3065
+ /**
3066
+ * Signal a person's reply into the run. Answers addressed at an approval (a call recorded with an
3067
+ * approver; questions have none) are refused ({@link OpenCodeReplyMismatchError}, a 409) before
3068
+ * they reach the workflow, which would otherwise spend its wait on them.
3069
+ */
3070
+ async signal(runId, toolCallId, reply) {
3071
+ if (!isDecision(reply)) {
3072
+ const call = await this.store.toolCallApproval?.(toolCallId);
3073
+ if (call != null && call.approver !== null) {
3074
+ throw new OpenCodeReplyMismatchError(runId, toolCallId);
3075
+ }
3076
+ }
3077
+ await this.workflows.signal(decisionToken(runId, toolCallId), reply);
3078
+ }
3079
+ /**
3080
+ * Interrupt OpenCode, cancel the run in the runtime (a run parked on a person never runs its body
3081
+ * again), and settle it here: the queue, the thread, the `cancelled` frame and the run row.
3082
+ */
3083
+ async cancel(runId) {
3084
+ this.logger.log(`cancelling agent run ${runId}`);
3085
+ const input = await this.runs.getRunDetail(runId).then((detail) => detail?.run.input).catch(() => void 0);
3086
+ if (isRunInput(input)) await this.turns.interrupt(input).catch(() => void 0);
3087
+ await this.runs.cancel(runId, {
3088
+ compensate: true
3089
+ });
3090
+ if (isRunInput(input)) await this.turns.settle(runId, input, {
3091
+ status: "interrupted"
3092
+ }, 0);
3093
+ }
3094
+ };
3095
+ DurableOpenCodeAgentRunner = _ts_decorate5([
3096
+ (0, import_common8.Injectable)(),
3097
+ _ts_param5(1, (0, import_common8.Inject)(import_nestjs_durable2.RUN_GATEWAY)),
3098
+ _ts_param5(3, (0, import_common8.Inject)(OPENCODE_OPTIONS)),
3099
+ _ts_param5(4, (0, import_common8.Inject)(OPENCODE_HOST)),
3100
+ _ts_param5(5, (0, import_common8.Inject)(import_nestjs_agent_core6.AGENT_STORE)),
3101
+ _ts_metadata5("design:type", Function),
3102
+ _ts_metadata5("design:paramtypes", [
3103
+ typeof import_nestjs_durable2.WorkflowService === "undefined" ? Object : import_nestjs_durable2.WorkflowService,
3104
+ typeof RunGateway === "undefined" ? Object : RunGateway,
3105
+ typeof OpenCodeTurns === "undefined" ? Object : OpenCodeTurns,
3106
+ typeof OpenCodeEngineSettings === "undefined" ? Object : OpenCodeEngineSettings,
3107
+ typeof OpenCodeHost === "undefined" ? Object : OpenCodeHost,
3108
+ typeof AgentStore === "undefined" ? Object : AgentStore
3109
+ ])
3110
+ ], DurableOpenCodeAgentRunner);
3111
+
3112
+ // src/durable/index.ts
3113
+ function openCodeDurable(options) {
3114
+ return {
3115
+ name: "opencode-durable",
3116
+ providers: [
3117
+ ...openCodeProviders(options),
3118
+ OpenCodeRunWorkflow,
3119
+ DurableOpenCodeAgentRunner
3120
+ ],
3121
+ runner: DurableOpenCodeAgentRunner,
3122
+ exports: [
3123
+ OpenCodeTurns,
3124
+ OPENCODE_TURNS
3125
+ ],
3126
+ controllers: openCodeControllers(options)
3127
+ };
3128
+ }
3129
+ __name(openCodeDurable, "openCodeDurable");
3130
+ // Annotate the CommonJS export names for ESM import in node:
3131
+ 0 && (module.exports = {
3132
+ DurableOpenCodeAgentRunner,
3133
+ OPENCODE_RUN_WORKFLOW,
3134
+ OpenCodeRunWorkflow,
3135
+ decisionToken,
3136
+ openCodeDurable
3137
+ });
3138
+ //# sourceMappingURL=index.cjs.map