cyrus-opencode-runner 0.2.69

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,666 @@
1
+ import { spawn } from "node:child_process";
2
+ import crypto from "node:crypto";
3
+ import { EventEmitter } from "node:events";
4
+ import { cwd } from "node:process";
5
+ import { buildOpenCodeConfig, buildOpenCodeRuntimeEnv, ensureOpenCodeStateDirectories, } from "./config.js";
6
+ import { OpenCodeMessageFormatter } from "./formatter.js";
7
+ const FORCE_KILL_DELAY_MS = 5_000;
8
+ const DEFAULT_OPENCODE_MODEL = "opencode";
9
+ const DEFAULT_OPENCODE_MODEL_DISPLAY = "OpenCode default model";
10
+ function asRecord(value) {
11
+ if (value && typeof value === "object" && !Array.isArray(value)) {
12
+ return value;
13
+ }
14
+ return null;
15
+ }
16
+ function safeStringify(value) {
17
+ try {
18
+ return JSON.stringify(value, null, 2);
19
+ }
20
+ catch {
21
+ return String(value);
22
+ }
23
+ }
24
+ function normalizeError(error) {
25
+ if (error instanceof Error)
26
+ return error.message;
27
+ if (typeof error === "string")
28
+ return error;
29
+ return "OpenCode execution failed";
30
+ }
31
+ function toFiniteNumber(value) {
32
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
33
+ }
34
+ function inferToolNameFromInput(input) {
35
+ const record = asRecord(input);
36
+ if (!record) {
37
+ return null;
38
+ }
39
+ if (typeof (record.command ?? record.cmd) === "string") {
40
+ return "Bash";
41
+ }
42
+ if (typeof (record.filePath ?? record.file_path ?? record.path) === "string") {
43
+ return "Read";
44
+ }
45
+ if (typeof record.url === "string") {
46
+ return "WebFetch";
47
+ }
48
+ if (typeof (record.pattern ?? record.query) === "string") {
49
+ return "Grep";
50
+ }
51
+ return null;
52
+ }
53
+ function normalizeToolName(toolName, input) {
54
+ const trimmed = toolName?.trim() || "";
55
+ if (!trimmed) {
56
+ return "OpenCode tool call";
57
+ }
58
+ const inferredName = inferToolNameFromInput(input);
59
+ if (trimmed.toLowerCase() === "unknown") {
60
+ return inferredName || "OpenCode tool call";
61
+ }
62
+ const normalized = trimmed.toLowerCase().replace(/[\s_-]+/g, "");
63
+ switch (normalized) {
64
+ case "bash":
65
+ case "shell":
66
+ case "terminal":
67
+ return "Bash";
68
+ case "edit":
69
+ case "patch":
70
+ return "Edit";
71
+ case "read":
72
+ return "Read";
73
+ case "write":
74
+ return "Write";
75
+ case "grep":
76
+ return "Grep";
77
+ case "glob":
78
+ return "Glob";
79
+ case "webfetch":
80
+ case "fetch":
81
+ return "WebFetch";
82
+ case "websearch":
83
+ case "search":
84
+ return "WebSearch";
85
+ case "todowrite":
86
+ case "todolist":
87
+ return "TodoWrite";
88
+ default:
89
+ return trimmed;
90
+ }
91
+ }
92
+ function resolveModelDisplay(config) {
93
+ if (config.model) {
94
+ return config.model;
95
+ }
96
+ const runtimeConfig = buildOpenCodeConfig(config).config;
97
+ const model = runtimeConfig.model;
98
+ const provider = runtimeConfig.provider;
99
+ if (typeof model === "string" && model.trim()) {
100
+ if (typeof provider === "string" &&
101
+ provider.trim() &&
102
+ !model.includes("/")) {
103
+ return `${provider}/${model}`;
104
+ }
105
+ return model;
106
+ }
107
+ return DEFAULT_OPENCODE_MODEL_DISPLAY;
108
+ }
109
+ function normalizeToolInput(input) {
110
+ const record = asRecord(input);
111
+ if (!record) {
112
+ return {};
113
+ }
114
+ const normalized = { ...record };
115
+ if (typeof record.filePath === "string" && !normalized.file_path) {
116
+ normalized.file_path = record.filePath;
117
+ }
118
+ return normalized;
119
+ }
120
+ function outputToString(output, metadata) {
121
+ if (typeof output === "string") {
122
+ return output;
123
+ }
124
+ if (output !== undefined) {
125
+ return safeStringify(output);
126
+ }
127
+ if (metadata !== undefined) {
128
+ return safeStringify(metadata);
129
+ }
130
+ return "Tool completed";
131
+ }
132
+ function createAssistantToolUseMessage(toolUseId, toolName, toolInput, messageId = crypto.randomUUID()) {
133
+ return {
134
+ id: messageId,
135
+ type: "message",
136
+ role: "assistant",
137
+ content: [
138
+ { type: "tool_use", id: toolUseId, name: toolName, input: toolInput },
139
+ ],
140
+ model: DEFAULT_OPENCODE_MODEL,
141
+ stop_reason: null,
142
+ stop_sequence: null,
143
+ stop_details: null,
144
+ usage: {
145
+ input_tokens: 0,
146
+ output_tokens: 0,
147
+ cache_creation_input_tokens: 0,
148
+ cache_read_input_tokens: 0,
149
+ cache_creation: null,
150
+ },
151
+ container: null,
152
+ context_management: null,
153
+ diagnostics: null,
154
+ };
155
+ }
156
+ function createAssistantTextMessage(text, messageId = crypto.randomUUID()) {
157
+ return {
158
+ id: messageId,
159
+ type: "message",
160
+ role: "assistant",
161
+ content: [
162
+ { type: "text", text },
163
+ ],
164
+ model: DEFAULT_OPENCODE_MODEL,
165
+ stop_reason: null,
166
+ stop_sequence: null,
167
+ stop_details: null,
168
+ usage: {
169
+ input_tokens: 0,
170
+ output_tokens: 0,
171
+ cache_creation_input_tokens: 0,
172
+ cache_read_input_tokens: 0,
173
+ cache_creation: null,
174
+ },
175
+ container: null,
176
+ context_management: null,
177
+ diagnostics: null,
178
+ };
179
+ }
180
+ function createUserToolResultMessage(toolUseId, result, isError) {
181
+ return {
182
+ role: "user",
183
+ content: [
184
+ {
185
+ type: "tool_result",
186
+ tool_use_id: toolUseId,
187
+ content: result,
188
+ is_error: isError,
189
+ },
190
+ ],
191
+ };
192
+ }
193
+ function createResultUsage(usage) {
194
+ return {
195
+ input_tokens: usage.inputTokens,
196
+ output_tokens: usage.outputTokens,
197
+ cache_creation_input_tokens: usage.cacheWriteTokens,
198
+ cache_read_input_tokens: usage.cacheReadTokens,
199
+ cache_creation: {
200
+ ephemeral_1h_input_tokens: 0,
201
+ ephemeral_5m_input_tokens: 0,
202
+ },
203
+ };
204
+ }
205
+ function parseUsage(event) {
206
+ const usage = event.usage || {};
207
+ return {
208
+ inputTokens: toFiniteNumber(usage.inputTokens ?? usage.input_tokens),
209
+ outputTokens: toFiniteNumber(usage.outputTokens ?? usage.output_tokens),
210
+ cacheReadTokens: toFiniteNumber(usage.cacheReadTokens ?? usage.cache_read_input_tokens),
211
+ cacheWriteTokens: toFiniteNumber(usage.cacheWriteTokens ?? usage.cache_creation_input_tokens),
212
+ };
213
+ }
214
+ function parseCost(event) {
215
+ return toFiniteNumber(event.cost ?? event.totalCostUSD ?? event.total_cost_usd);
216
+ }
217
+ function parseFinalResult(event) {
218
+ const value = event.result ?? event.output ?? event.message;
219
+ if (typeof value === "string") {
220
+ const trimmed = value.trim();
221
+ return trimmed || null;
222
+ }
223
+ if (value !== undefined) {
224
+ return safeStringify(value);
225
+ }
226
+ return null;
227
+ }
228
+ export class OpenCodeRunner extends EventEmitter {
229
+ supportsStreamingInput = false;
230
+ config;
231
+ formatter;
232
+ sessionInfo = null;
233
+ messages = [];
234
+ process = null;
235
+ hasInitMessage = false;
236
+ emittedToolUseIds = new Set();
237
+ pendingResultMessage = null;
238
+ lastAssistantText = null;
239
+ lastUsage = {
240
+ inputTokens: 0,
241
+ outputTokens: 0,
242
+ cacheReadTokens: 0,
243
+ cacheWriteTokens: 0,
244
+ };
245
+ totalCostUsd = 0;
246
+ startTimestampMs = 0;
247
+ wasStopped = false;
248
+ hasFinalized = false;
249
+ stderr = "";
250
+ nonJsonStartupOutput = [];
251
+ constructor(config) {
252
+ super();
253
+ this.config = config;
254
+ this.formatter = new OpenCodeMessageFormatter();
255
+ if (config.onMessage)
256
+ this.on("message", config.onMessage);
257
+ if (config.onError)
258
+ this.on("error", config.onError);
259
+ if (config.onComplete)
260
+ this.on("complete", config.onComplete);
261
+ }
262
+ async start(prompt) {
263
+ if (this.isRunning()) {
264
+ throw new Error("OpenCode session already running");
265
+ }
266
+ this.resetSessionState();
267
+ this.sessionInfo = {
268
+ sessionId: this.config.resumeSessionId || null,
269
+ startedAt: new Date(),
270
+ isRunning: true,
271
+ };
272
+ const selectorError = this.validateModelSelector();
273
+ if (selectorError) {
274
+ this.finalizeSession(selectorError);
275
+ return this.sessionInfo;
276
+ }
277
+ return new Promise((resolve) => {
278
+ let stdoutBuffer = "";
279
+ let inactivityTimer;
280
+ let forceKillTimer;
281
+ const inactivityTimeoutMs = this.config.inactivityTimeoutMs;
282
+ const args = this.buildArgs();
283
+ const inputPrompt = this.buildInputPrompt(prompt);
284
+ const runtimeEnv = this.buildRuntimeEnv();
285
+ ensureOpenCodeStateDirectories(runtimeEnv);
286
+ const child = spawn(this.config.openCodePath || "opencode", args, {
287
+ cwd: this.config.workingDirectory || cwd(),
288
+ env: {
289
+ ...process.env,
290
+ ...this.config.env,
291
+ ...runtimeEnv,
292
+ },
293
+ stdio: ["pipe", "pipe", "pipe"],
294
+ });
295
+ this.process = child;
296
+ const clearInactivityTimers = () => {
297
+ if (inactivityTimer)
298
+ clearTimeout(inactivityTimer);
299
+ if (forceKillTimer)
300
+ clearTimeout(forceKillTimer);
301
+ };
302
+ const refreshInactivityTimer = () => {
303
+ if (!inactivityTimeoutMs || inactivityTimeoutMs <= 0) {
304
+ return;
305
+ }
306
+ if (inactivityTimer)
307
+ clearTimeout(inactivityTimer);
308
+ inactivityTimer = setTimeout(() => {
309
+ const timeoutDescription = inactivityTimeoutMs >= 60_000
310
+ ? `${Math.round(inactivityTimeoutMs / 60_000)} minutes`
311
+ : `${inactivityTimeoutMs}ms`;
312
+ const error = new Error(`OpenCode produced no output for ${timeoutDescription} and was terminated`);
313
+ this.finalizeSession(error);
314
+ resolve(this.sessionInfo);
315
+ child.kill("SIGTERM");
316
+ forceKillTimer = setTimeout(() => {
317
+ if (child.exitCode === null && child.signalCode === null) {
318
+ child.kill("SIGKILL");
319
+ }
320
+ }, FORCE_KILL_DELAY_MS);
321
+ }, inactivityTimeoutMs);
322
+ };
323
+ refreshInactivityTimer();
324
+ child.stdout.on("data", (chunk) => {
325
+ refreshInactivityTimer();
326
+ stdoutBuffer += chunk.toString("utf8");
327
+ const lines = stdoutBuffer.split(/\r?\n/);
328
+ stdoutBuffer = lines.pop() || "";
329
+ for (const line of lines) {
330
+ this.handleLine(line);
331
+ }
332
+ });
333
+ child.stderr.on("data", (chunk) => {
334
+ refreshInactivityTimer();
335
+ this.stderr += chunk.toString("utf8");
336
+ });
337
+ child.on("error", (error) => {
338
+ clearInactivityTimers();
339
+ this.finalizeSession(error);
340
+ resolve(this.sessionInfo);
341
+ });
342
+ child.on("close", (code, signal) => {
343
+ clearInactivityTimers();
344
+ if (stdoutBuffer.trim()) {
345
+ this.handleLine(stdoutBuffer);
346
+ }
347
+ let error;
348
+ if (this.wasStopped) {
349
+ error = new Error("OpenCode session stopped");
350
+ }
351
+ else if (typeof code === "number" && code !== 0) {
352
+ const output = this.stderr.trim() || this.nonJsonStartupOutput.join("\n").trim();
353
+ const suffix = output ? `: ${output}` : "";
354
+ error = new Error(`OpenCode exited with code ${code}${suffix}`);
355
+ }
356
+ else if (signal) {
357
+ error = new Error(`OpenCode exited with signal ${signal}`);
358
+ }
359
+ this.finalizeSession(error);
360
+ resolve(this.sessionInfo);
361
+ });
362
+ child.stdin.end(inputPrompt);
363
+ });
364
+ }
365
+ async startStreaming(initialPrompt) {
366
+ return this.start(initialPrompt || "");
367
+ }
368
+ addStreamMessage(_content) {
369
+ throw new Error("OpenCodeRunner does not support streaming input messages");
370
+ }
371
+ completeStream() {
372
+ // No-op: OpenCodeRunner does not support streaming input.
373
+ }
374
+ stop() {
375
+ if (!this.sessionInfo?.isRunning) {
376
+ return;
377
+ }
378
+ this.wasStopped = true;
379
+ this.process?.kill("SIGTERM");
380
+ }
381
+ isRunning() {
382
+ return this.sessionInfo?.isRunning ?? false;
383
+ }
384
+ getMessages() {
385
+ return [...this.messages];
386
+ }
387
+ getFormatter() {
388
+ return this.formatter;
389
+ }
390
+ resetSessionState() {
391
+ this.messages = [];
392
+ this.process = null;
393
+ this.hasInitMessage = false;
394
+ this.emittedToolUseIds = new Set();
395
+ this.pendingResultMessage = null;
396
+ this.lastAssistantText = null;
397
+ this.lastUsage = {
398
+ inputTokens: 0,
399
+ outputTokens: 0,
400
+ cacheReadTokens: 0,
401
+ cacheWriteTokens: 0,
402
+ };
403
+ this.totalCostUsd = 0;
404
+ this.startTimestampMs = Date.now();
405
+ this.wasStopped = false;
406
+ this.hasFinalized = false;
407
+ this.stderr = "";
408
+ this.nonJsonStartupOutput = [];
409
+ }
410
+ validateModelSelector() {
411
+ const model = this.config.model?.trim();
412
+ if (!model?.toLowerCase().startsWith("opencode/")) {
413
+ return undefined;
414
+ }
415
+ return new Error(`Invalid OpenCode model selector "${model}". Use a provider-qualified OpenCode model such as "openai/gpt-5.5" in runner config or select it with the Cyrus label "opencode/openai/gpt-5.5".`);
416
+ }
417
+ buildRuntimeEnv() {
418
+ const built = buildOpenCodeConfig(this.config);
419
+ for (const entry of built.unsupported) {
420
+ console.warn(`[OpenCodeRunner] Unsupported config entry skipped: ${entry}`);
421
+ }
422
+ return buildOpenCodeRuntimeEnv(this.config);
423
+ }
424
+ buildArgs() {
425
+ const args = [
426
+ "run",
427
+ "--format",
428
+ "json",
429
+ "--print-logs",
430
+ "--log-level",
431
+ "ERROR",
432
+ "--dir",
433
+ this.config.workingDirectory || cwd(),
434
+ "--title",
435
+ this.config.title || "Cyrus OpenCode session",
436
+ ];
437
+ if (this.config.model) {
438
+ args.push("--model", this.config.model);
439
+ }
440
+ if (this.config.agent) {
441
+ args.push("--agent", this.config.agent);
442
+ }
443
+ if (this.config.resumeSessionId) {
444
+ args.push("--session", this.config.resumeSessionId);
445
+ }
446
+ return args;
447
+ }
448
+ buildInputPrompt(prompt) {
449
+ const systemPrompt = this.config.appendSystemPrompt?.trim();
450
+ if (!systemPrompt)
451
+ return prompt;
452
+ return `${systemPrompt}\n\n${prompt}`;
453
+ }
454
+ handleLine(line) {
455
+ const trimmed = line.trim();
456
+ if (!trimmed) {
457
+ return;
458
+ }
459
+ try {
460
+ this.handleEvent(JSON.parse(trimmed));
461
+ }
462
+ catch (error) {
463
+ if (!this.hasInitMessage) {
464
+ this.nonJsonStartupOutput.push(trimmed);
465
+ return;
466
+ }
467
+ this.emitError(new Error(`Failed to parse OpenCode JSON event: ${normalizeError(error)} (${trimmed})`));
468
+ }
469
+ }
470
+ handleEvent(event) {
471
+ this.emit("streamEvent", event);
472
+ switch (event.type) {
473
+ case "step_start": {
474
+ const sessionId = event.sessionID || event.sessionId || event.session_id || "pending";
475
+ if (this.sessionInfo) {
476
+ this.sessionInfo.sessionId = sessionId;
477
+ }
478
+ this.emitSystemInitMessage(sessionId);
479
+ break;
480
+ }
481
+ case "tool_use":
482
+ this.emitToolMessages(event);
483
+ break;
484
+ case "text":
485
+ this.emitAssistantMessage(event.part?.text || "");
486
+ break;
487
+ case "step_finish":
488
+ this.lastUsage = parseUsage(event);
489
+ this.totalCostUsd = parseCost(event);
490
+ this.pendingResultMessage = this.createSuccessResultMessage(parseFinalResult(event) ||
491
+ this.lastAssistantText ||
492
+ "OpenCode session completed successfully", event.reason || event.stopReason || event.stop_reason || null);
493
+ break;
494
+ default:
495
+ break;
496
+ }
497
+ }
498
+ projectToolUse(event) {
499
+ const part = event.part;
500
+ const callId = part?.callID || part?.callId || part?.call_id;
501
+ if (!callId) {
502
+ return null;
503
+ }
504
+ const state = part.state || {};
505
+ const status = (state.status || "").toLowerCase();
506
+ const isError = status === "error" ||
507
+ status === "failed" ||
508
+ status === "aborted" ||
509
+ status === "canceled" ||
510
+ status === "cancelled" ||
511
+ state.error !== undefined;
512
+ const hasResult = state.output !== undefined ||
513
+ state.metadata !== undefined ||
514
+ isError ||
515
+ status === "completed" ||
516
+ status === "success";
517
+ return {
518
+ toolUseId: callId,
519
+ toolName: normalizeToolName(part.tool, state.input),
520
+ toolInput: normalizeToolInput(state.input),
521
+ result: outputToString(state.output ?? state.error, state.metadata),
522
+ isError,
523
+ hasResult,
524
+ };
525
+ }
526
+ emitToolMessages(event) {
527
+ const projection = this.projectToolUse(event);
528
+ if (!projection) {
529
+ return;
530
+ }
531
+ if (!this.emittedToolUseIds.has(projection.toolUseId)) {
532
+ const message = {
533
+ type: "assistant",
534
+ message: createAssistantToolUseMessage(projection.toolUseId, projection.toolName, projection.toolInput),
535
+ parent_tool_use_id: null,
536
+ uuid: crypto.randomUUID(),
537
+ session_id: this.sessionInfo?.sessionId || "pending",
538
+ };
539
+ this.pushMessage(message);
540
+ this.emittedToolUseIds.add(projection.toolUseId);
541
+ }
542
+ if (!projection.hasResult) {
543
+ return;
544
+ }
545
+ const message = {
546
+ type: "user",
547
+ message: createUserToolResultMessage(projection.toolUseId, projection.result, projection.isError),
548
+ parent_tool_use_id: null,
549
+ uuid: crypto.randomUUID(),
550
+ session_id: this.sessionInfo?.sessionId || "pending",
551
+ };
552
+ this.pushMessage(message);
553
+ this.emittedToolUseIds.delete(projection.toolUseId);
554
+ }
555
+ emitAssistantMessage(text) {
556
+ const normalized = text.trim();
557
+ if (!normalized) {
558
+ return;
559
+ }
560
+ this.lastAssistantText = normalized;
561
+ const message = {
562
+ type: "assistant",
563
+ message: createAssistantTextMessage(normalized),
564
+ parent_tool_use_id: null,
565
+ uuid: crypto.randomUUID(),
566
+ session_id: this.sessionInfo?.sessionId || "pending",
567
+ };
568
+ this.pushMessage(message);
569
+ }
570
+ emitSystemInitMessage(sessionId) {
571
+ if (this.hasInitMessage) {
572
+ return;
573
+ }
574
+ this.hasInitMessage = true;
575
+ const message = {
576
+ type: "system",
577
+ subtype: "init",
578
+ agents: undefined,
579
+ apiKeySource: "user",
580
+ claude_code_version: "opencode-cli",
581
+ cwd: this.config.workingDirectory || cwd(),
582
+ tools: this.config.allowedTools || [],
583
+ mcp_servers: Object.keys(buildOpenCodeConfig(this.config).config.mcp ?? {}).map((name) => ({ name, status: "connected" })),
584
+ model: resolveModelDisplay(this.config),
585
+ permissionMode: "default",
586
+ slash_commands: [],
587
+ output_style: "default",
588
+ skills: [],
589
+ plugins: [],
590
+ uuid: crypto.randomUUID(),
591
+ session_id: sessionId,
592
+ };
593
+ this.pushMessage(message);
594
+ }
595
+ createSuccessResultMessage(result, stopReason = null) {
596
+ return {
597
+ type: "result",
598
+ subtype: "success",
599
+ duration_ms: Math.max(Date.now() - this.startTimestampMs, 0),
600
+ duration_api_ms: 0,
601
+ is_error: false,
602
+ num_turns: 1,
603
+ result,
604
+ stop_reason: stopReason,
605
+ total_cost_usd: this.totalCostUsd,
606
+ usage: createResultUsage(this.lastUsage),
607
+ modelUsage: {},
608
+ permission_denials: [],
609
+ uuid: crypto.randomUUID(),
610
+ session_id: this.sessionInfo?.sessionId || "pending",
611
+ };
612
+ }
613
+ createErrorResultMessage(errorMessage) {
614
+ return {
615
+ type: "result",
616
+ subtype: "error_during_execution",
617
+ duration_ms: Math.max(Date.now() - this.startTimestampMs, 0),
618
+ duration_api_ms: 0,
619
+ is_error: true,
620
+ num_turns: 1,
621
+ stop_reason: null,
622
+ errors: [errorMessage],
623
+ total_cost_usd: this.totalCostUsd,
624
+ usage: createResultUsage(this.lastUsage),
625
+ modelUsage: {},
626
+ permission_denials: [],
627
+ uuid: crypto.randomUUID(),
628
+ session_id: this.sessionInfo?.sessionId || "pending",
629
+ };
630
+ }
631
+ finalizeSession(error) {
632
+ if (this.hasFinalized) {
633
+ return;
634
+ }
635
+ this.hasFinalized = true;
636
+ if (!this.sessionInfo) {
637
+ return;
638
+ }
639
+ this.sessionInfo.isRunning = false;
640
+ this.process = null;
641
+ if (!this.hasInitMessage) {
642
+ this.emitSystemInitMessage(this.sessionInfo.sessionId || this.config.resumeSessionId || "pending");
643
+ }
644
+ if (error) {
645
+ const normalized = normalizeError(error);
646
+ this.pendingResultMessage = this.createErrorResultMessage(normalized);
647
+ this.emitError(error instanceof Error ? error : new Error(normalized));
648
+ }
649
+ if (!this.pendingResultMessage) {
650
+ this.pendingResultMessage = this.createSuccessResultMessage(this.lastAssistantText || "OpenCode session completed successfully");
651
+ }
652
+ this.pushMessage(this.pendingResultMessage);
653
+ this.pendingResultMessage = null;
654
+ this.emit("complete", [...this.messages]);
655
+ }
656
+ pushMessage(message) {
657
+ this.messages.push(message);
658
+ this.emit("message", message);
659
+ }
660
+ emitError(error) {
661
+ if (this.listenerCount("error") > 0) {
662
+ this.emit("error", error);
663
+ }
664
+ }
665
+ }
666
+ //# sourceMappingURL=OpenCodeRunner.js.map