opencode-gateway 0.2.9 → 0.2.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -4
- package/dist/cli/args.d.ts +1 -1
- package/dist/cli/opencode-server.d.ts +19 -0
- package/dist/cli/serve.d.ts +6 -0
- package/dist/cli/warm.d.ts +6 -0
- package/dist/cli.js +195 -46
- package/dist/index.js +42 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -28,10 +28,22 @@ Check what it resolved:
|
|
|
28
28
|
npx opencode-gateway doctor
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
Recommended:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
opencode-gateway serve
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
This wraps `opencode serve` and warms the gateway plugin worker immediately, so
|
|
38
|
+
Telegram polling and scheduled jobs do not stay idle until the first
|
|
39
|
+
project-scoped request.
|
|
40
|
+
|
|
41
|
+
If you still prefer the raw OpenCode command, warm the gateway explicitly after
|
|
42
|
+
startup:
|
|
32
43
|
|
|
33
44
|
```bash
|
|
34
45
|
opencode serve
|
|
46
|
+
opencode-gateway warm
|
|
35
47
|
```
|
|
36
48
|
|
|
37
49
|
If you want a separate managed config tree instead of editing your existing
|
|
@@ -39,9 +51,7 @@ OpenCode config:
|
|
|
39
51
|
|
|
40
52
|
```bash
|
|
41
53
|
npx opencode-gateway init --managed
|
|
42
|
-
|
|
43
|
-
export OPENCODE_CONFIG_DIR="$HOME/.config/opencode-gateway/opencode"
|
|
44
|
-
opencode serve
|
|
54
|
+
opencode-gateway serve --managed
|
|
45
55
|
```
|
|
46
56
|
|
|
47
57
|
## Example gateway config
|
package/dist/cli/args.d.ts
CHANGED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
type CliOptions = {
|
|
2
|
+
managed: boolean;
|
|
3
|
+
configDir: string | null;
|
|
4
|
+
};
|
|
5
|
+
export type ResolvedServeTarget = {
|
|
6
|
+
configDir: string;
|
|
7
|
+
gatewayConfigPath: string;
|
|
8
|
+
workspaceDirPath: string;
|
|
9
|
+
opencodeConfigPath: string;
|
|
10
|
+
serverOrigin: string;
|
|
11
|
+
env: Record<string, string>;
|
|
12
|
+
};
|
|
13
|
+
export declare function resolveServeTarget(options: CliOptions, env: Record<string, string | undefined>): Promise<ResolvedServeTarget>;
|
|
14
|
+
export declare function warmGatewayProject(target: ResolvedServeTarget, options?: {
|
|
15
|
+
deadlineMs?: number;
|
|
16
|
+
intervalMs?: number;
|
|
17
|
+
}): Promise<void>;
|
|
18
|
+
export declare function resolveServerOriginFromDocument(document: Record<string, unknown>): string;
|
|
19
|
+
export {};
|
package/dist/cli.js
CHANGED
|
@@ -7,7 +7,7 @@ function parseCliCommand(argv) {
|
|
|
7
7
|
if (!command || command === "help" || command === "--help" || command === "-h") {
|
|
8
8
|
return { kind: "help" };
|
|
9
9
|
}
|
|
10
|
-
if (command !== "init" && command !== "doctor") {
|
|
10
|
+
if (command !== "init" && command !== "doctor" && command !== "serve" && command !== "warm") {
|
|
11
11
|
throw new Error(`unknown command: ${command}`);
|
|
12
12
|
}
|
|
13
13
|
let managed = false;
|
|
@@ -48,6 +48,8 @@ function formatCliHelp() {
|
|
|
48
48
|
"Commands:",
|
|
49
49
|
" opencode-gateway init [--managed] [--config-dir <path>]",
|
|
50
50
|
" opencode-gateway doctor [--managed] [--config-dir <path>]",
|
|
51
|
+
" opencode-gateway warm [--managed] [--config-dir <path>]",
|
|
52
|
+
" opencode-gateway serve [--managed] [--config-dir <path>]",
|
|
51
53
|
"",
|
|
52
54
|
"Defaults:",
|
|
53
55
|
" init/doctor use OPENCODE_CONFIG_DIR when set, otherwise ~/.config/opencode",
|
|
@@ -57,42 +59,7 @@ function formatCliHelp() {
|
|
|
57
59
|
}
|
|
58
60
|
|
|
59
61
|
// src/cli/doctor.ts
|
|
60
|
-
import { readFile } from "node:fs/promises";
|
|
61
|
-
import { join as join3 } from "node:path";
|
|
62
|
-
|
|
63
|
-
// src/config/paths.ts
|
|
64
|
-
import { homedir } from "node:os";
|
|
65
|
-
import { dirname, join, resolve as resolve2 } from "node:path";
|
|
66
|
-
var GATEWAY_CONFIG_FILE = "opencode-gateway.toml";
|
|
67
|
-
var OPENCODE_CONFIG_FILE = "opencode.json";
|
|
68
|
-
var OPENCODE_CONFIG_FILE_JSONC = "opencode.jsonc";
|
|
69
|
-
var OPENCODE_CONFIG_FILE_CANDIDATES = [OPENCODE_CONFIG_FILE_JSONC, OPENCODE_CONFIG_FILE];
|
|
70
|
-
var GATEWAY_WORKSPACE_DIR = "opencode-gateway-workspace";
|
|
71
|
-
function resolveOpencodeConfigDir(env) {
|
|
72
|
-
const explicit = env.OPENCODE_CONFIG_DIR;
|
|
73
|
-
if (explicit && explicit.trim().length > 0) {
|
|
74
|
-
return resolve2(explicit);
|
|
75
|
-
}
|
|
76
|
-
return defaultOpencodeConfigDir(env);
|
|
77
|
-
}
|
|
78
|
-
function resolveManagedOpencodeConfigDir(env) {
|
|
79
|
-
return join(resolveConfigHome(env), "opencode-gateway", "opencode");
|
|
80
|
-
}
|
|
81
|
-
function resolveGatewayWorkspacePath(configPath) {
|
|
82
|
-
return join(dirname(configPath), GATEWAY_WORKSPACE_DIR);
|
|
83
|
-
}
|
|
84
|
-
function defaultGatewayStateDbPath(env) {
|
|
85
|
-
return join(resolveDataHome(env), "opencode-gateway", "state.db");
|
|
86
|
-
}
|
|
87
|
-
function resolveConfigHome(env) {
|
|
88
|
-
return env.XDG_CONFIG_HOME ?? join(homedir(), ".config");
|
|
89
|
-
}
|
|
90
|
-
function resolveDataHome(env) {
|
|
91
|
-
return env.XDG_DATA_HOME ?? join(homedir(), ".local", "share");
|
|
92
|
-
}
|
|
93
|
-
function defaultOpencodeConfigDir(env) {
|
|
94
|
-
return join(resolveConfigHome(env), "opencode");
|
|
95
|
-
}
|
|
62
|
+
import { readFile as readFile2 } from "node:fs/promises";
|
|
96
63
|
|
|
97
64
|
// src/cli/opencode-config.ts
|
|
98
65
|
var OPENCODE_SCHEMA_URL = "https://opencode.ai/config.json";
|
|
@@ -290,6 +257,47 @@ function isGatewayPluginReference(entry) {
|
|
|
290
257
|
// src/cli/opencode-config-file.ts
|
|
291
258
|
import { join as join2 } from "node:path";
|
|
292
259
|
|
|
260
|
+
// src/config/paths.ts
|
|
261
|
+
import { homedir } from "node:os";
|
|
262
|
+
import { dirname, join, resolve as resolve2 } from "node:path";
|
|
263
|
+
var GATEWAY_CONFIG_FILE = "opencode-gateway.toml";
|
|
264
|
+
var OPENCODE_CONFIG_FILE = "opencode.json";
|
|
265
|
+
var OPENCODE_CONFIG_FILE_JSONC = "opencode.jsonc";
|
|
266
|
+
var OPENCODE_CONFIG_FILE_CANDIDATES = [OPENCODE_CONFIG_FILE_JSONC, OPENCODE_CONFIG_FILE];
|
|
267
|
+
var GATEWAY_WORKSPACE_DIR = "opencode-gateway-workspace";
|
|
268
|
+
function resolveGatewayConfigPath(env) {
|
|
269
|
+
const explicit = env.OPENCODE_GATEWAY_CONFIG;
|
|
270
|
+
if (explicit && explicit.trim().length > 0) {
|
|
271
|
+
return resolve2(explicit);
|
|
272
|
+
}
|
|
273
|
+
return join(resolveOpencodeConfigDir(env), GATEWAY_CONFIG_FILE);
|
|
274
|
+
}
|
|
275
|
+
function resolveOpencodeConfigDir(env) {
|
|
276
|
+
const explicit = env.OPENCODE_CONFIG_DIR;
|
|
277
|
+
if (explicit && explicit.trim().length > 0) {
|
|
278
|
+
return resolve2(explicit);
|
|
279
|
+
}
|
|
280
|
+
return defaultOpencodeConfigDir(env);
|
|
281
|
+
}
|
|
282
|
+
function resolveManagedOpencodeConfigDir(env) {
|
|
283
|
+
return join(resolveConfigHome(env), "opencode-gateway", "opencode");
|
|
284
|
+
}
|
|
285
|
+
function resolveGatewayWorkspacePath(configPath) {
|
|
286
|
+
return join(dirname(configPath), GATEWAY_WORKSPACE_DIR);
|
|
287
|
+
}
|
|
288
|
+
function defaultGatewayStateDbPath(env) {
|
|
289
|
+
return join(resolveDataHome(env), "opencode-gateway", "state.db");
|
|
290
|
+
}
|
|
291
|
+
function resolveConfigHome(env) {
|
|
292
|
+
return env.XDG_CONFIG_HOME ?? join(homedir(), ".config");
|
|
293
|
+
}
|
|
294
|
+
function resolveDataHome(env) {
|
|
295
|
+
return env.XDG_DATA_HOME ?? join(homedir(), ".local", "share");
|
|
296
|
+
}
|
|
297
|
+
function defaultOpencodeConfigDir(env) {
|
|
298
|
+
return join(resolveConfigHome(env), "opencode");
|
|
299
|
+
}
|
|
300
|
+
|
|
293
301
|
// src/cli/paths.ts
|
|
294
302
|
import { constants } from "node:fs";
|
|
295
303
|
import { access } from "node:fs/promises";
|
|
@@ -329,19 +337,106 @@ async function resolveOpencodeConfigFile(configDir) {
|
|
|
329
337
|
};
|
|
330
338
|
}
|
|
331
339
|
|
|
340
|
+
// src/cli/opencode-server.ts
|
|
341
|
+
import { readFile } from "node:fs/promises";
|
|
342
|
+
var DEFAULT_SERVER_ORIGIN = "http://127.0.0.1:4096";
|
|
343
|
+
var WARM_REQUEST_TIMEOUT_MS = 2000;
|
|
344
|
+
async function resolveServeTarget(options, env) {
|
|
345
|
+
const configDir = resolveCliConfigDir(options, env);
|
|
346
|
+
const gatewayConfigPath = resolveGatewayConfigPath({
|
|
347
|
+
...env,
|
|
348
|
+
OPENCODE_CONFIG_DIR: configDir
|
|
349
|
+
});
|
|
350
|
+
const workspaceDirPath = resolveGatewayWorkspacePath(gatewayConfigPath);
|
|
351
|
+
const opencodeConfig = await resolveOpencodeConfigFile(configDir);
|
|
352
|
+
const serverOrigin = opencodeConfig.exists ? await resolveServerOrigin(opencodeConfig.path) : DEFAULT_SERVER_ORIGIN;
|
|
353
|
+
return {
|
|
354
|
+
configDir,
|
|
355
|
+
gatewayConfigPath,
|
|
356
|
+
workspaceDirPath,
|
|
357
|
+
opencodeConfigPath: opencodeConfig.path,
|
|
358
|
+
serverOrigin,
|
|
359
|
+
env: {
|
|
360
|
+
OPENCODE_CONFIG_DIR: configDir,
|
|
361
|
+
OPENCODE_CONFIG: opencodeConfig.path
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
async function warmGatewayProject(target, options) {
|
|
366
|
+
const deadlineMs = options?.deadlineMs ?? 30000;
|
|
367
|
+
const intervalMs = options?.intervalMs ?? 250;
|
|
368
|
+
const warmUrl = new URL("/experimental/tool/ids", target.serverOrigin);
|
|
369
|
+
warmUrl.searchParams.set("directory", target.workspaceDirPath);
|
|
370
|
+
const deadline = Date.now() + deadlineMs;
|
|
371
|
+
while (Date.now() < deadline) {
|
|
372
|
+
try {
|
|
373
|
+
const response = await fetch(warmUrl, {
|
|
374
|
+
signal: AbortSignal.timeout(WARM_REQUEST_TIMEOUT_MS)
|
|
375
|
+
});
|
|
376
|
+
if (response.ok) {
|
|
377
|
+
const payload = await response.json();
|
|
378
|
+
if (isGatewayToolList(payload)) {
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
} catch {}
|
|
383
|
+
await delay(intervalMs);
|
|
384
|
+
}
|
|
385
|
+
throw new Error(`failed to warm the gateway plugin at ${target.serverOrigin} for ${target.workspaceDirPath}`);
|
|
386
|
+
}
|
|
387
|
+
function resolveServerOriginFromDocument(document) {
|
|
388
|
+
const server = document.server;
|
|
389
|
+
if (server === null || typeof server !== "object" || Array.isArray(server)) {
|
|
390
|
+
return DEFAULT_SERVER_ORIGIN;
|
|
391
|
+
}
|
|
392
|
+
const hostname = readNonEmptyString(server.hostname);
|
|
393
|
+
const port = readPort(server.port);
|
|
394
|
+
if (hostname === null || port === null) {
|
|
395
|
+
return DEFAULT_SERVER_ORIGIN;
|
|
396
|
+
}
|
|
397
|
+
return `http://${hostname}:${port}`;
|
|
398
|
+
}
|
|
399
|
+
async function resolveServerOrigin(configPath) {
|
|
400
|
+
const source = await readFile(configPath, "utf8");
|
|
401
|
+
const document = parseOpencodeConfig(source, configPath);
|
|
402
|
+
return resolveServerOriginFromDocument(document);
|
|
403
|
+
}
|
|
404
|
+
function readNonEmptyString(value) {
|
|
405
|
+
if (typeof value !== "string") {
|
|
406
|
+
return null;
|
|
407
|
+
}
|
|
408
|
+
const normalized = value.trim();
|
|
409
|
+
return normalized.length > 0 ? normalized : null;
|
|
410
|
+
}
|
|
411
|
+
function readPort(value) {
|
|
412
|
+
if (typeof value !== "number" || !Number.isInteger(value)) {
|
|
413
|
+
return null;
|
|
414
|
+
}
|
|
415
|
+
return value >= 1 && value <= 65535 ? value : null;
|
|
416
|
+
}
|
|
417
|
+
function isGatewayToolList(value) {
|
|
418
|
+
return Array.isArray(value) && value.includes("gateway_status");
|
|
419
|
+
}
|
|
420
|
+
function delay(durationMs) {
|
|
421
|
+
return new Promise((resolve4) => {
|
|
422
|
+
setTimeout(resolve4, durationMs);
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
|
|
332
426
|
// src/cli/doctor.ts
|
|
333
427
|
async function runDoctor(options, env) {
|
|
334
428
|
const configDir = resolveCliConfigDir(options, env);
|
|
335
|
-
const gatewayConfigPath = join3(configDir, GATEWAY_CONFIG_FILE);
|
|
336
|
-
const workspaceDirPath = resolveGatewayWorkspacePath(gatewayConfigPath);
|
|
337
429
|
const opencodeConfig = await resolveOpencodeConfigFile(configDir);
|
|
338
430
|
const opencodeStatus = await inspectOpencodeConfig(opencodeConfig.path);
|
|
339
431
|
const gatewayOverride = env.OPENCODE_GATEWAY_CONFIG?.trim() || null;
|
|
432
|
+
const serveTarget = await resolveServeTarget(options, env);
|
|
340
433
|
console.log("doctor report");
|
|
341
434
|
console.log(` config dir: ${configDir}`);
|
|
342
435
|
console.log(` opencode config: ${await describePath(opencodeConfig.path)}`);
|
|
343
|
-
console.log(` gateway config: ${await describePath(gatewayConfigPath)}`);
|
|
344
|
-
console.log(` gateway workspace: ${await describePath(workspaceDirPath)}`);
|
|
436
|
+
console.log(` gateway config: ${await describePath(serveTarget.gatewayConfigPath)}`);
|
|
437
|
+
console.log(` gateway workspace: ${await describePath(serveTarget.workspaceDirPath)}`);
|
|
438
|
+
console.log(` warm server: ${serveTarget.serverOrigin}`);
|
|
439
|
+
console.log(` warm directory: ${serveTarget.workspaceDirPath}`);
|
|
345
440
|
console.log(` gateway config override: ${gatewayOverride ?? "not set"}`);
|
|
346
441
|
console.log(` plugin configured: ${opencodeStatus.pluginConfigured}`);
|
|
347
442
|
console.log(` TELEGRAM_BOT_TOKEN: ${env.TELEGRAM_BOT_TOKEN?.trim() ? "set" : "missing"}`);
|
|
@@ -360,7 +455,7 @@ async function inspectOpencodeConfig(path) {
|
|
|
360
455
|
};
|
|
361
456
|
}
|
|
362
457
|
try {
|
|
363
|
-
const parsed = parseOpencodeConfig(await
|
|
458
|
+
const parsed = parseOpencodeConfig(await readFile2(path, "utf8"), path);
|
|
364
459
|
return {
|
|
365
460
|
pluginConfigured: inspectGatewayPlugin(parsed),
|
|
366
461
|
error: null
|
|
@@ -374,8 +469,8 @@ async function inspectOpencodeConfig(path) {
|
|
|
374
469
|
}
|
|
375
470
|
|
|
376
471
|
// src/cli/init.ts
|
|
377
|
-
import { mkdir, readFile as
|
|
378
|
-
import { dirname as dirname2, join as
|
|
472
|
+
import { mkdir, readFile as readFile3, writeFile } from "node:fs/promises";
|
|
473
|
+
import { dirname as dirname2, join as join3 } from "node:path";
|
|
379
474
|
|
|
380
475
|
// src/cli/templates.ts
|
|
381
476
|
function buildGatewayConfigTemplate(stateDbPath) {
|
|
@@ -438,7 +533,7 @@ function escapeTomlString(value) {
|
|
|
438
533
|
// src/cli/init.ts
|
|
439
534
|
async function runInit(options, env) {
|
|
440
535
|
const configDir = resolveCliConfigDir(options, env);
|
|
441
|
-
const gatewayConfigPath =
|
|
536
|
+
const gatewayConfigPath = join3(configDir, GATEWAY_CONFIG_FILE);
|
|
442
537
|
const workspaceDirPath = resolveGatewayWorkspacePath(gatewayConfigPath);
|
|
443
538
|
const opencodeConfig = await resolveOpencodeConfigFile(configDir);
|
|
444
539
|
const opencodeConfigPath = opencodeConfig.path;
|
|
@@ -449,7 +544,7 @@ async function runInit(options, env) {
|
|
|
449
544
|
await writeFile(opencodeConfigPath, stringifyOpencodeConfig(createDefaultOpencodeConfig(options.managed)));
|
|
450
545
|
opencodeStatus = "created";
|
|
451
546
|
} else {
|
|
452
|
-
const source = await
|
|
547
|
+
const source = await readFile3(opencodeConfigPath, "utf8");
|
|
453
548
|
const parsed = parseOpencodeConfig(source, opencodeConfigPath);
|
|
454
549
|
const next = ensureGatewayPlugin(parsed);
|
|
455
550
|
if (next.changed) {
|
|
@@ -467,6 +562,54 @@ async function runInit(options, env) {
|
|
|
467
562
|
console.log(`opencode config: ${opencodeConfigPath} (${opencodeStatus})`);
|
|
468
563
|
console.log(`gateway config: ${gatewayConfigPath} (${gatewayStatus})`);
|
|
469
564
|
console.log(`gateway workspace: ${workspaceDirPath} (ready)`);
|
|
565
|
+
console.log("next step: start OpenCode with `opencode-gateway serve`");
|
|
566
|
+
console.log("fallback: if you still run `opencode serve`, run `opencode-gateway warm` after startup");
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// src/cli/serve.ts
|
|
570
|
+
import { spawn } from "node:child_process";
|
|
571
|
+
import { mkdir as mkdir2 } from "node:fs/promises";
|
|
572
|
+
async function runServe(options, env) {
|
|
573
|
+
const target = await resolveServeTarget(options, env);
|
|
574
|
+
await mkdir2(target.workspaceDirPath, { recursive: true });
|
|
575
|
+
const child = spawn("opencode", ["serve"], {
|
|
576
|
+
stdio: "inherit",
|
|
577
|
+
env: {
|
|
578
|
+
...process.env,
|
|
579
|
+
...target.env
|
|
580
|
+
}
|
|
581
|
+
});
|
|
582
|
+
warmGatewayProject(target).catch((error) => {
|
|
583
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
584
|
+
console.warn(`warning: ${message}`);
|
|
585
|
+
console.warn("warning: the gateway plugin may stay idle until the first project-scoped request");
|
|
586
|
+
});
|
|
587
|
+
const exitCode = await waitForChild(child);
|
|
588
|
+
if (exitCode !== 0) {
|
|
589
|
+
process.exitCode = exitCode;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
function waitForChild(child) {
|
|
593
|
+
return new Promise((resolve4, reject) => {
|
|
594
|
+
child.once("error", reject);
|
|
595
|
+
child.once("exit", (code, signal) => {
|
|
596
|
+
if (signal !== null) {
|
|
597
|
+
resolve4(1);
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
resolve4(code ?? 0);
|
|
601
|
+
});
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// src/cli/warm.ts
|
|
606
|
+
import { mkdir as mkdir3 } from "node:fs/promises";
|
|
607
|
+
async function runWarm(options, env) {
|
|
608
|
+
const target = await resolveServeTarget(options, env);
|
|
609
|
+
await mkdir3(target.workspaceDirPath, { recursive: true });
|
|
610
|
+
await warmGatewayProject(target);
|
|
611
|
+
console.log(`gateway plugin warmed: ${target.serverOrigin}`);
|
|
612
|
+
console.log(`warm directory: ${target.workspaceDirPath}`);
|
|
470
613
|
}
|
|
471
614
|
|
|
472
615
|
// src/cli.ts
|
|
@@ -482,6 +625,12 @@ async function main() {
|
|
|
482
625
|
case "init":
|
|
483
626
|
await runInit(command, process.env);
|
|
484
627
|
return;
|
|
628
|
+
case "serve":
|
|
629
|
+
await runServe(command, process.env);
|
|
630
|
+
return;
|
|
631
|
+
case "warm":
|
|
632
|
+
await runWarm(command, process.env);
|
|
633
|
+
return;
|
|
485
634
|
}
|
|
486
635
|
}
|
|
487
636
|
main().catch((error) => {
|
package/dist/index.js
CHANGED
|
@@ -17754,7 +17754,9 @@ function delay(durationMs) {
|
|
|
17754
17754
|
|
|
17755
17755
|
// src/opencode/adapter.ts
|
|
17756
17756
|
var SESSION_IDLE_POLL_MS = 250;
|
|
17757
|
-
var
|
|
17757
|
+
var PROMPT_RESPONSE_PROGRESS_TIMEOUT_MS = 90000;
|
|
17758
|
+
var PROMPT_RESPONSE_MAX_TIMEOUT_MS = 10 * 60000;
|
|
17759
|
+
var PROMPT_RESPONSE_SETTLE_MS = 1000;
|
|
17758
17760
|
|
|
17759
17761
|
class OpencodeSdkAdapter {
|
|
17760
17762
|
client;
|
|
@@ -17900,8 +17902,12 @@ class OpencodeSdkAdapter {
|
|
|
17900
17902
|
};
|
|
17901
17903
|
}
|
|
17902
17904
|
async awaitPromptResponse(command) {
|
|
17903
|
-
const
|
|
17905
|
+
const startedAtMs = Date.now();
|
|
17906
|
+
const maxDeadline = startedAtMs + PROMPT_RESPONSE_MAX_TIMEOUT_MS;
|
|
17907
|
+
let progressDeadline = startedAtMs + PROMPT_RESPONSE_PROGRESS_TIMEOUT_MS;
|
|
17904
17908
|
let stableCandidateKey = null;
|
|
17909
|
+
let stableCandidateSinceMs = null;
|
|
17910
|
+
let progressKey = null;
|
|
17905
17911
|
for (;; ) {
|
|
17906
17912
|
const messages = await this.client.session.messages({
|
|
17907
17913
|
path: { id: command.sessionId },
|
|
@@ -17912,22 +17918,32 @@ class OpencodeSdkAdapter {
|
|
|
17912
17918
|
responseStyle: "data",
|
|
17913
17919
|
throwOnError: true
|
|
17914
17920
|
});
|
|
17915
|
-
const
|
|
17921
|
+
const assistantChildren = listAssistantResponses(unwrapData(messages), command.messageId);
|
|
17922
|
+
const nextProgressKey = createAssistantProgressKey(assistantChildren);
|
|
17923
|
+
const now = Date.now();
|
|
17924
|
+
if (progressKey !== nextProgressKey) {
|
|
17925
|
+
progressKey = nextProgressKey;
|
|
17926
|
+
progressDeadline = now + PROMPT_RESPONSE_PROGRESS_TIMEOUT_MS;
|
|
17927
|
+
}
|
|
17928
|
+
const response = selectAssistantResponse(assistantChildren);
|
|
17916
17929
|
if (response !== null) {
|
|
17917
17930
|
const candidateKey = createAssistantCandidateKey(response);
|
|
17918
17931
|
if (stableCandidateKey === candidateKey) {
|
|
17919
|
-
|
|
17920
|
-
|
|
17921
|
-
|
|
17922
|
-
|
|
17923
|
-
|
|
17924
|
-
|
|
17932
|
+
if (stableCandidateSinceMs !== null && now - stableCandidateSinceMs >= PROMPT_RESPONSE_SETTLE_MS) {
|
|
17933
|
+
return toAwaitPromptResponseResult(command.sessionId, response);
|
|
17934
|
+
}
|
|
17935
|
+
} else {
|
|
17936
|
+
stableCandidateKey = candidateKey;
|
|
17937
|
+
stableCandidateSinceMs = now;
|
|
17925
17938
|
}
|
|
17926
|
-
stableCandidateKey = candidateKey;
|
|
17927
17939
|
} else {
|
|
17928
17940
|
stableCandidateKey = null;
|
|
17941
|
+
stableCandidateSinceMs = null;
|
|
17929
17942
|
}
|
|
17930
|
-
if (
|
|
17943
|
+
if (now >= progressDeadline || now >= maxDeadline) {
|
|
17944
|
+
if (response !== null) {
|
|
17945
|
+
return toAwaitPromptResponseResult(command.sessionId, response);
|
|
17946
|
+
}
|
|
17931
17947
|
throw new Error(`assistant message for prompt ${command.messageId} is unavailable after prompt completion`);
|
|
17932
17948
|
}
|
|
17933
17949
|
await delay(SESSION_IDLE_POLL_MS);
|
|
@@ -17988,8 +18004,10 @@ function toSessionPromptPart(part) {
|
|
|
17988
18004
|
};
|
|
17989
18005
|
}
|
|
17990
18006
|
}
|
|
17991
|
-
function
|
|
17992
|
-
|
|
18007
|
+
function listAssistantResponses(messages, userMessageId) {
|
|
18008
|
+
return messages.filter(isAssistantChildMessage(userMessageId));
|
|
18009
|
+
}
|
|
18010
|
+
function selectAssistantResponse(assistantChildren) {
|
|
17993
18011
|
for (let index = assistantChildren.length - 1;index >= 0; index -= 1) {
|
|
17994
18012
|
const candidate = assistantChildren[index];
|
|
17995
18013
|
if (hasVisibleText(candidate)) {
|
|
@@ -18004,6 +18022,9 @@ function selectAssistantResponse(messages, userMessageId) {
|
|
|
18004
18022
|
}
|
|
18005
18023
|
return null;
|
|
18006
18024
|
}
|
|
18025
|
+
function createAssistantProgressKey(messages) {
|
|
18026
|
+
return JSON.stringify(messages.map(createAssistantCandidateKey));
|
|
18027
|
+
}
|
|
18007
18028
|
function createAssistantCandidateKey(message) {
|
|
18008
18029
|
return JSON.stringify({
|
|
18009
18030
|
messageId: message.info.id,
|
|
@@ -18020,6 +18041,14 @@ function createAssistantCandidateKey(message) {
|
|
|
18020
18041
|
function isAssistantChildMessage(userMessageId) {
|
|
18021
18042
|
return (message) => message.info?.role === "assistant" && message.info.parentID === userMessageId;
|
|
18022
18043
|
}
|
|
18044
|
+
function toAwaitPromptResponseResult(sessionId, message) {
|
|
18045
|
+
return {
|
|
18046
|
+
kind: "awaitPromptResponse",
|
|
18047
|
+
sessionId,
|
|
18048
|
+
messageId: message.info.id,
|
|
18049
|
+
parts: message.parts.flatMap(toBindingMessagePart)
|
|
18050
|
+
};
|
|
18051
|
+
}
|
|
18023
18052
|
function toBindingMessagePart(part) {
|
|
18024
18053
|
if (typeof part.id !== "string" || typeof part.messageID !== "string" || part.type.length === 0) {
|
|
18025
18054
|
return [];
|