@getpaseo/server 0.1.75 → 0.1.77
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/dist/server/client/daemon-client.d.ts +14 -2
- package/dist/server/client/daemon-client.d.ts.map +1 -1
- package/dist/server/client/daemon-client.js +37 -0
- package/dist/server/client/daemon-client.js.map +1 -1
- package/dist/server/server/agent/agent-manager.d.ts +2 -1
- package/dist/server/server/agent/agent-manager.d.ts.map +1 -1
- package/dist/server/server/agent/agent-manager.js +21 -10
- package/dist/server/server/agent/agent-manager.js.map +1 -1
- package/dist/server/server/agent/agent-sdk-types.d.ts +5 -0
- package/dist/server/server/agent/agent-sdk-types.d.ts.map +1 -1
- package/dist/server/server/agent/agent-sdk-types.js +7 -0
- package/dist/server/server/agent/agent-sdk-types.js.map +1 -1
- package/dist/server/server/agent/import-sessions.d.ts.map +1 -1
- package/dist/server/server/agent/import-sessions.js +6 -2
- package/dist/server/server/agent/import-sessions.js.map +1 -1
- package/dist/server/server/agent/mcp-server.d.ts.map +1 -1
- package/dist/server/server/agent/mcp-server.js +227 -4
- package/dist/server/server/agent/mcp-server.js.map +1 -1
- package/dist/server/server/agent/provider-history-timestamps.d.ts +2 -0
- package/dist/server/server/agent/provider-history-timestamps.d.ts.map +1 -0
- package/dist/server/server/agent/provider-history-timestamps.js +16 -0
- package/dist/server/server/agent/provider-history-timestamps.js.map +1 -0
- package/dist/server/server/agent/provider-manifest.d.ts +1 -1
- package/dist/server/server/agent/provider-manifest.d.ts.map +1 -1
- package/dist/server/server/agent/provider-manifest.js +14 -0
- package/dist/server/server/agent/provider-manifest.js.map +1 -1
- package/dist/server/server/agent/provider-registry.d.ts.map +1 -1
- package/dist/server/server/agent/provider-registry.js +53 -27
- package/dist/server/server/agent/provider-registry.js.map +1 -1
- package/dist/server/server/agent/provider-snapshot-manager.d.ts +2 -6
- package/dist/server/server/agent/provider-snapshot-manager.d.ts.map +1 -1
- package/dist/server/server/agent/provider-snapshot-manager.js +43 -32
- package/dist/server/server/agent/provider-snapshot-manager.js.map +1 -1
- package/dist/server/server/agent/providers/acp-agent.d.ts +3 -1
- package/dist/server/server/agent/providers/acp-agent.d.ts.map +1 -1
- package/dist/server/server/agent/providers/acp-agent.js +61 -14
- package/dist/server/server/agent/providers/acp-agent.js.map +1 -1
- package/dist/server/server/agent/providers/claude/agent.d.ts.map +1 -1
- package/dist/server/server/agent/providers/claude/agent.js +111 -38
- package/dist/server/server/agent/providers/claude/agent.js.map +1 -1
- package/dist/server/server/agent/providers/claude/models.d.ts +2 -0
- package/dist/server/server/agent/providers/claude/models.d.ts.map +1 -1
- package/dist/server/server/agent/providers/claude/models.js +78 -0
- package/dist/server/server/agent/providers/claude/models.js.map +1 -1
- package/dist/server/server/agent/providers/codex/app-server-transport.d.ts +1 -0
- package/dist/server/server/agent/providers/codex/app-server-transport.d.ts.map +1 -1
- package/dist/server/server/agent/providers/codex/app-server-transport.js +14 -10
- package/dist/server/server/agent/providers/codex/app-server-transport.js.map +1 -1
- package/dist/server/server/agent/providers/codex-app-server-agent.d.ts +15 -1
- package/dist/server/server/agent/providers/codex-app-server-agent.d.ts.map +1 -1
- package/dist/server/server/agent/providers/codex-app-server-agent.js +372 -69
- package/dist/server/server/agent/providers/codex-app-server-agent.js.map +1 -1
- package/dist/server/server/agent/providers/cursor-acp-agent.d.ts +21 -0
- package/dist/server/server/agent/providers/cursor-acp-agent.d.ts.map +1 -0
- package/dist/server/server/agent/providers/cursor-acp-agent.js +85 -0
- package/dist/server/server/agent/providers/cursor-acp-agent.js.map +1 -0
- package/dist/server/server/agent/providers/generic-acp-agent.d.ts +13 -0
- package/dist/server/server/agent/providers/generic-acp-agent.d.ts.map +1 -1
- package/dist/server/server/agent/providers/generic-acp-agent.js +209 -2
- package/dist/server/server/agent/providers/generic-acp-agent.js.map +1 -1
- package/dist/server/server/agent/providers/opencode/server-manager.d.ts.map +1 -1
- package/dist/server/server/agent/providers/opencode/server-manager.js +2 -1
- package/dist/server/server/agent/providers/opencode/server-manager.js.map +1 -1
- package/dist/server/server/agent/providers/opencode/test-utils/test-opencode-runtime.d.ts +7 -0
- package/dist/server/server/agent/providers/opencode/test-utils/test-opencode-runtime.d.ts.map +1 -1
- package/dist/server/server/agent/providers/opencode/test-utils/test-opencode-runtime.js +50 -1
- package/dist/server/server/agent/providers/opencode/test-utils/test-opencode-runtime.js.map +1 -1
- package/dist/server/server/agent/providers/opencode-agent.d.ts +11 -7
- package/dist/server/server/agent/providers/opencode-agent.d.ts.map +1 -1
- package/dist/server/server/agent/providers/opencode-agent.js +307 -346
- package/dist/server/server/agent/providers/opencode-agent.js.map +1 -1
- package/dist/server/server/agent/providers/pi-direct-agent.d.ts +1 -1
- package/dist/server/server/agent/providers/pi-direct-agent.d.ts.map +1 -1
- package/dist/server/server/agent/providers/pi-direct-agent.js +14 -0
- package/dist/server/server/agent/providers/pi-direct-agent.js.map +1 -1
- package/dist/server/server/agent/providers/pi-session-recovery-policy.d.ts +22 -0
- package/dist/server/server/agent/providers/pi-session-recovery-policy.d.ts.map +1 -0
- package/dist/server/server/agent/providers/pi-session-recovery-policy.js +51 -0
- package/dist/server/server/agent/providers/pi-session-recovery-policy.js.map +1 -0
- package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts.map +1 -1
- package/dist/server/server/agent/timeline-projection.d.ts.map +1 -1
- package/dist/server/server/agent/timeline-projection.js +9 -0
- package/dist/server/server/agent/timeline-projection.js.map +1 -1
- package/dist/server/server/auto-archive-on-merge/archive-if-safe.d.ts +40 -0
- package/dist/server/server/auto-archive-on-merge/archive-if-safe.d.ts.map +1 -0
- package/dist/server/server/auto-archive-on-merge/archive-if-safe.js +80 -0
- package/dist/server/server/auto-archive-on-merge/archive-if-safe.js.map +1 -0
- package/dist/server/server/auto-archive-on-merge/index.d.ts +8 -0
- package/dist/server/server/auto-archive-on-merge/index.d.ts.map +1 -0
- package/dist/server/server/auto-archive-on-merge/index.js +15 -0
- package/dist/server/server/auto-archive-on-merge/index.js.map +1 -0
- package/dist/server/server/bootstrap.d.ts +2 -0
- package/dist/server/server/bootstrap.d.ts.map +1 -1
- package/dist/server/server/bootstrap.js +82 -40
- package/dist/server/server/bootstrap.js.map +1 -1
- package/dist/server/server/checkout/status-projection.d.ts.map +1 -1
- package/dist/server/server/checkout/status-projection.js +5 -1
- package/dist/server/server/checkout/status-projection.js.map +1 -1
- package/dist/server/server/config.d.ts.map +1 -1
- package/dist/server/server/config.js +13 -5
- package/dist/server/server/config.js.map +1 -1
- package/dist/server/server/daemon-config-store.js +1 -0
- package/dist/server/server/daemon-config-store.js.map +1 -1
- package/dist/server/server/loop/rpc-schemas.d.ts +96 -96
- package/dist/server/server/loop-service.d.ts +18 -18
- package/dist/server/server/pairing-offer.d.ts +1 -0
- package/dist/server/server/pairing-offer.d.ts.map +1 -1
- package/dist/server/server/pairing-offer.js +2 -1
- package/dist/server/server/pairing-offer.js.map +1 -1
- package/dist/server/server/paseo-worktree-service.d.ts +3 -1
- package/dist/server/server/paseo-worktree-service.d.ts.map +1 -1
- package/dist/server/server/paseo-worktree-service.js +55 -18
- package/dist/server/server/paseo-worktree-service.js.map +1 -1
- package/dist/server/server/persisted-config.d.ts +16 -0
- package/dist/server/server/persisted-config.d.ts.map +1 -1
- package/dist/server/server/persisted-config.js +11 -3
- package/dist/server/server/persisted-config.js.map +1 -1
- package/dist/server/server/relay-transport.d.ts +2 -1
- package/dist/server/server/relay-transport.d.ts.map +1 -1
- package/dist/server/server/relay-transport.js +26 -4
- package/dist/server/server/relay-transport.js.map +1 -1
- package/dist/server/server/session.d.ts +6 -0
- package/dist/server/server/session.d.ts.map +1 -1
- package/dist/server/server/session.js +195 -34
- package/dist/server/server/session.js.map +1 -1
- package/dist/server/server/utils/diff-highlighter.d.ts.map +1 -1
- package/dist/server/server/utils/diff-highlighter.js +30 -9
- package/dist/server/server/utils/diff-highlighter.js.map +1 -1
- package/dist/server/server/websocket-server.d.ts.map +1 -1
- package/dist/server/server/websocket-server.js +5 -0
- package/dist/server/server/websocket-server.js.map +1 -1
- package/dist/server/server/workspace-git-service.d.ts +6 -1
- package/dist/server/server/workspace-git-service.d.ts.map +1 -1
- package/dist/server/server/workspace-git-service.js +27 -4
- package/dist/server/server/workspace-git-service.js.map +1 -1
- package/dist/server/server/workspace-reconciliation-service.d.ts +4 -2
- package/dist/server/server/workspace-reconciliation-service.d.ts.map +1 -1
- package/dist/server/server/workspace-reconciliation-service.js +112 -14
- package/dist/server/server/workspace-reconciliation-service.js.map +1 -1
- package/dist/server/server/workspace-registry.d.ts +6 -1
- package/dist/server/server/workspace-registry.d.ts.map +1 -1
- package/dist/server/server/workspace-registry.js +11 -0
- package/dist/server/server/workspace-registry.js.map +1 -1
- package/dist/server/server/worktree-session.d.ts.map +1 -1
- package/dist/server/server/worktree-session.js +1 -0
- package/dist/server/server/worktree-session.js.map +1 -1
- package/dist/server/services/github-service.d.ts +46 -0
- package/dist/server/services/github-service.d.ts.map +1 -1
- package/dist/server/services/github-service.js +274 -5
- package/dist/server/services/github-service.js.map +1 -1
- package/dist/server/shared/messages.d.ts +3443 -290
- package/dist/server/shared/messages.d.ts.map +1 -1
- package/dist/server/shared/messages.js +94 -3
- package/dist/server/shared/messages.js.map +1 -1
- package/dist/server/shared/terminal-input-mode.d.ts +26 -0
- package/dist/server/shared/terminal-input-mode.d.ts.map +1 -0
- package/dist/server/shared/terminal-input-mode.js +151 -0
- package/dist/server/shared/terminal-input-mode.js.map +1 -0
- package/dist/server/terminal/terminal-session-controller.d.ts.map +1 -1
- package/dist/server/terminal/terminal-session-controller.js +12 -2
- package/dist/server/terminal/terminal-session-controller.js.map +1 -1
- package/dist/server/terminal/terminal.d.ts +1 -0
- package/dist/server/terminal/terminal.d.ts.map +1 -1
- package/dist/server/terminal/terminal.js +16 -3
- package/dist/server/terminal/terminal.js.map +1 -1
- package/dist/server/terminal/worker-terminal-manager.d.ts.map +1 -1
- package/dist/server/terminal/worker-terminal-manager.js +8 -0
- package/dist/server/terminal/worker-terminal-manager.js.map +1 -1
- package/dist/server/utils/checkout-git.d.ts +4 -1
- package/dist/server/utils/checkout-git.d.ts.map +1 -1
- package/dist/server/utils/checkout-git.js +85 -29
- package/dist/server/utils/checkout-git.js.map +1 -1
- package/dist/server/utils/directory-suggestions.d.ts +2 -0
- package/dist/server/utils/directory-suggestions.d.ts.map +1 -1
- package/dist/server/utils/directory-suggestions.js +87 -14
- package/dist/server/utils/directory-suggestions.js.map +1 -1
- package/dist/server/utils/executable.d.ts.map +1 -1
- package/dist/server/utils/executable.js +6 -3
- package/dist/server/utils/executable.js.map +1 -1
- package/dist/server/utils/path.d.ts +10 -0
- package/dist/server/utils/path.d.ts.map +1 -1
- package/dist/server/utils/path.js +65 -1
- package/dist/server/utils/path.js.map +1 -1
- package/dist/server/utils/run-git-command.d.ts +2 -0
- package/dist/server/utils/run-git-command.d.ts.map +1 -1
- package/dist/server/utils/run-git-command.js +41 -1
- package/dist/server/utils/run-git-command.js.map +1 -1
- package/dist/server/utils/worktree.js +1 -1
- package/dist/server/utils/worktree.js.map +1 -1
- package/dist/src/server/agent/agent-sdk-types.js +7 -0
- package/dist/src/server/agent/agent-sdk-types.js.map +1 -1
- package/dist/src/server/agent/provider-manifest.js +14 -0
- package/dist/src/server/agent/provider-manifest.js.map +1 -1
- package/dist/src/server/persisted-config.js +11 -3
- package/dist/src/server/persisted-config.js.map +1 -1
- package/dist/src/shared/messages.js +94 -3
- package/dist/src/shared/messages.js.map +1 -1
- package/dist/src/utils/executable.js +6 -3
- package/dist/src/utils/executable.js.map +1 -1
- package/package.json +3 -3
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { readdir, readFile } from "node:fs/promises";
|
|
2
1
|
import { homedir } from "node:os";
|
|
3
|
-
import path from "node:path";
|
|
4
2
|
import { findExecutable, isCommandAvailable } from "../../../utils/executable.js";
|
|
3
|
+
import { createPathEquivalenceMatcher } from "../../../utils/path.js";
|
|
5
4
|
import { z } from "zod";
|
|
6
5
|
import { getAgentStreamEventTurnId, } from "../agent-sdk-types.js";
|
|
7
6
|
import { createProviderEnvSpec } from "../provider-launch-config.js";
|
|
@@ -14,6 +13,7 @@ import { formatDiagnosticStatus, formatProviderDiagnostic, formatProviderDiagnos
|
|
|
14
13
|
import { runProviderTurn } from "./provider-runner.js";
|
|
15
14
|
import { renderPromptAttachmentAsText } from "../prompt-attachments.js";
|
|
16
15
|
import { createSdkOpenCodeClient, } from "./opencode/runtime.js";
|
|
16
|
+
import { normalizeProviderReplayTimestamp } from "../provider-history-timestamps.js";
|
|
17
17
|
const OPENCODE_CAPABILITIES = {
|
|
18
18
|
supportsStreaming: true,
|
|
19
19
|
supportsSessionPersistence: true,
|
|
@@ -24,9 +24,8 @@ const OPENCODE_CAPABILITIES = {
|
|
|
24
24
|
};
|
|
25
25
|
const OPENCODE_BUILD_MODE_ID = "build";
|
|
26
26
|
const OPENCODE_FULL_ACCESS_MODE_ID = "full-access";
|
|
27
|
-
const
|
|
27
|
+
const OPENCODE_PERSISTED_SESSION_LIMIT = 200;
|
|
28
28
|
const OPENCODE_PENDING_ABORT_START_TIMEOUT_MS = 10000;
|
|
29
|
-
const OPENCODE_RETRY_STATUS_FAILURE_MS = 10000;
|
|
30
29
|
const DEFAULT_MODES = [
|
|
31
30
|
{
|
|
32
31
|
id: OPENCODE_BUILD_MODE_ID,
|
|
@@ -44,44 +43,6 @@ const DEFAULT_MODES = [
|
|
|
44
43
|
description: "Automatically approves all tool permission prompts for the session",
|
|
45
44
|
},
|
|
46
45
|
];
|
|
47
|
-
const OpenCodeStoredSessionSchema = z
|
|
48
|
-
.object({
|
|
49
|
-
id: z.string().min(1),
|
|
50
|
-
directory: z.string().min(1),
|
|
51
|
-
title: z.string().nullable().optional(),
|
|
52
|
-
time: z
|
|
53
|
-
.object({
|
|
54
|
-
created: z.number().optional(),
|
|
55
|
-
updated: z.number().optional(),
|
|
56
|
-
})
|
|
57
|
-
.optional(),
|
|
58
|
-
})
|
|
59
|
-
.passthrough();
|
|
60
|
-
const OpenCodeStoredMessageSchema = z
|
|
61
|
-
.object({
|
|
62
|
-
id: z.string().min(1),
|
|
63
|
-
sessionID: z.string().min(1),
|
|
64
|
-
role: z.string().optional(),
|
|
65
|
-
time: z
|
|
66
|
-
.object({
|
|
67
|
-
created: z.number().optional(),
|
|
68
|
-
completed: z.number().optional(),
|
|
69
|
-
})
|
|
70
|
-
.optional(),
|
|
71
|
-
})
|
|
72
|
-
.passthrough();
|
|
73
|
-
const OpenCodeStoredPartSchema = z
|
|
74
|
-
.object({
|
|
75
|
-
type: z.string().optional(),
|
|
76
|
-
text: z.string().optional(),
|
|
77
|
-
time: z
|
|
78
|
-
.object({
|
|
79
|
-
start: z.number().optional(),
|
|
80
|
-
end: z.number().optional(),
|
|
81
|
-
})
|
|
82
|
-
.optional(),
|
|
83
|
-
})
|
|
84
|
-
.passthrough();
|
|
85
46
|
const MCP_ALREADY_PRESENT_ERROR_TOKENS = ["already", "exists", "connected"];
|
|
86
47
|
const OPENCODE_PROVIDER_LIST_TIMEOUT_MS = 30000;
|
|
87
48
|
const OPENCODE_HANDLED_BUILTIN_SLASH_COMMANDS = [
|
|
@@ -389,7 +350,9 @@ function buildOpenCodeModelContextWindowLookup(providers) {
|
|
|
389
350
|
}
|
|
390
351
|
const connectedProviderIds = new Set(providers.connected ?? []);
|
|
391
352
|
for (const provider of providers.all ?? []) {
|
|
392
|
-
|
|
353
|
+
// Providers with source "api" are managed by the OpenCode console/subscription and are
|
|
354
|
+
// usable even though they don't appear in `connected` (which only lists env/config providers).
|
|
355
|
+
if (!connectedProviderIds.has(provider.id) && provider.source !== "api") {
|
|
393
356
|
continue;
|
|
394
357
|
}
|
|
395
358
|
for (const [modelId, modelDefinition] of Object.entries(provider.models ?? {})) {
|
|
@@ -503,35 +466,30 @@ function buildOpenCodePromptParts(prompt) {
|
|
|
503
466
|
}
|
|
504
467
|
return output;
|
|
505
468
|
}
|
|
506
|
-
function
|
|
507
|
-
const
|
|
508
|
-
const
|
|
509
|
-
|
|
510
|
-
:
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
469
|
+
async function collectOpenCodePersistedAgentsFromSdk(client, options) {
|
|
470
|
+
const limit = options?.limit ?? OPENCODE_PERSISTED_SESSION_LIMIT;
|
|
471
|
+
const sessionListLimit = options?.cwd ? Math.max(limit, OPENCODE_PERSISTED_SESSION_LIMIT) : limit;
|
|
472
|
+
const response = await client.experimental.session.list({
|
|
473
|
+
archived: true,
|
|
474
|
+
roots: true,
|
|
475
|
+
limit: sessionListLimit,
|
|
476
|
+
});
|
|
477
|
+
if (response.error) {
|
|
478
|
+
throw new Error(`Failed to list OpenCode sessions: ${JSON.stringify(response.error)}`);
|
|
479
|
+
}
|
|
480
|
+
const sessions = response.data ?? [];
|
|
481
|
+
const matchesCwd = options?.cwd ? createPathEquivalenceMatcher(options.cwd) : null;
|
|
516
482
|
const candidates = sessions
|
|
517
|
-
.filter((session) => !
|
|
483
|
+
.filter((session) => !matchesCwd || matchesCwd(session.directory))
|
|
518
484
|
.sort((left, right) => getOpenCodeSessionTimestamp(right) - getOpenCodeSessionTimestamp(left))
|
|
519
485
|
.slice(0, limit);
|
|
520
|
-
return await Promise.all(candidates.map((session) => buildOpenCodePersistedAgentDescriptor(
|
|
486
|
+
return await Promise.all(candidates.map((session) => buildOpenCodePersistedAgentDescriptor(client, session)));
|
|
521
487
|
}
|
|
522
|
-
async function
|
|
523
|
-
const
|
|
524
|
-
const
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
if (parsed) {
|
|
528
|
-
sessions.push(parsed);
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
return sessions;
|
|
532
|
-
}
|
|
533
|
-
async function buildOpenCodePersistedAgentDescriptor(storageRoot, session) {
|
|
534
|
-
const timeline = await readOpenCodeSessionTimeline(storageRoot, session.id);
|
|
488
|
+
async function buildOpenCodePersistedAgentDescriptor(client, session) {
|
|
489
|
+
const messages = await readOpenCodeSessionMessagesFromSdk(client, session);
|
|
490
|
+
const timeline = buildOpenCodeSessionTimeline(messages);
|
|
491
|
+
const modeId = resolveOpenCodePersistedSessionModeId(session, messages);
|
|
492
|
+
const model = resolveOpenCodePersistedSessionModel(session, messages);
|
|
535
493
|
return {
|
|
536
494
|
provider: "opencode",
|
|
537
495
|
sessionId: session.id,
|
|
@@ -546,96 +504,144 @@ async function buildOpenCodePersistedAgentDescriptor(storageRoot, session) {
|
|
|
546
504
|
provider: "opencode",
|
|
547
505
|
cwd: session.directory,
|
|
548
506
|
title: normalizeOpenCodeSessionTitle(session.title),
|
|
507
|
+
...(modeId ? { modeId } : {}),
|
|
508
|
+
...(model ? { model } : {}),
|
|
549
509
|
},
|
|
550
510
|
},
|
|
551
511
|
timeline,
|
|
552
512
|
};
|
|
553
513
|
}
|
|
554
|
-
|
|
555
|
-
const
|
|
556
|
-
|
|
557
|
-
const messages = [];
|
|
558
|
-
for (const file of messageFiles) {
|
|
559
|
-
const parsed = await readJsonFile(file, OpenCodeStoredMessageSchema);
|
|
560
|
-
if (parsed?.sessionID === sessionId) {
|
|
561
|
-
messages.push(parsed);
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
const timeline = [];
|
|
565
|
-
for (const message of messages.sort((left, right) => getOpenCodeMessageTimestamp(left) - getOpenCodeMessageTimestamp(right))) {
|
|
566
|
-
const text = await readOpenCodeMessageText(storageRoot, message.id);
|
|
567
|
-
if (!text) {
|
|
568
|
-
continue;
|
|
569
|
-
}
|
|
570
|
-
if (message.role === "user") {
|
|
571
|
-
timeline.push({ type: "user_message", text, messageId: message.id });
|
|
572
|
-
}
|
|
573
|
-
else if (message.role === "assistant") {
|
|
574
|
-
timeline.push({ type: "assistant_message", text });
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
return timeline;
|
|
578
|
-
}
|
|
579
|
-
async function readOpenCodeMessageText(storageRoot, messageId) {
|
|
580
|
-
const parts = await readOpenCodeStoredParts(storageRoot, messageId);
|
|
581
|
-
return readOpenCodeTextFromParts(parts);
|
|
514
|
+
function normalizeOpenCodeSessionTitle(title) {
|
|
515
|
+
const normalized = title?.trim();
|
|
516
|
+
return normalized ? normalized : null;
|
|
582
517
|
}
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
const partFiles = await findJsonFiles(partRoot);
|
|
586
|
-
const parts = [];
|
|
587
|
-
for (const file of partFiles) {
|
|
588
|
-
const parsed = await readJsonFile(file, OpenCodeStoredPartSchema);
|
|
589
|
-
if (parsed) {
|
|
590
|
-
parts.push(parsed);
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
return parts.sort((left, right) => getOpenCodePartTimestamp(left) - getOpenCodePartTimestamp(right));
|
|
518
|
+
function getOpenCodeSessionTimestamp(session) {
|
|
519
|
+
return session.time?.updated ?? session.time?.created ?? 0;
|
|
594
520
|
}
|
|
595
|
-
function
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
.
|
|
600
|
-
.
|
|
521
|
+
function resolveOpenCodeReplayTimestamp(params) {
|
|
522
|
+
const timedPart = params.part;
|
|
523
|
+
const partTimestamp = timedPart?.time?.start ??
|
|
524
|
+
timedPart?.time?.end ??
|
|
525
|
+
params.message.time?.created ??
|
|
526
|
+
params.message.time?.completed;
|
|
527
|
+
return normalizeProviderReplayTimestamp(partTimestamp);
|
|
528
|
+
}
|
|
529
|
+
function buildOpenCodeReplayTimelineEvent(params) {
|
|
530
|
+
const timestamp = resolveOpenCodeReplayTimestamp({
|
|
531
|
+
message: params.message,
|
|
532
|
+
part: params.part,
|
|
533
|
+
});
|
|
534
|
+
return {
|
|
535
|
+
type: "timeline",
|
|
536
|
+
provider: "opencode",
|
|
537
|
+
item: params.item,
|
|
538
|
+
...(timestamp ? { timestamp } : {}),
|
|
539
|
+
};
|
|
601
540
|
}
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
541
|
+
function buildOpenCodeReplayPartTimelineEvent(params) {
|
|
542
|
+
const { part, message } = params;
|
|
543
|
+
if (part.type === "text" && part.text) {
|
|
544
|
+
return buildOpenCodeReplayTimelineEvent({
|
|
545
|
+
item: { type: "assistant_message", text: part.text },
|
|
546
|
+
message,
|
|
547
|
+
part,
|
|
548
|
+
});
|
|
606
549
|
}
|
|
607
|
-
|
|
608
|
-
return
|
|
550
|
+
if (part.type === "reasoning" && part.text) {
|
|
551
|
+
return buildOpenCodeReplayTimelineEvent({
|
|
552
|
+
item: { type: "reasoning", text: part.text },
|
|
553
|
+
message,
|
|
554
|
+
part,
|
|
555
|
+
});
|
|
609
556
|
}
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
if (entry.isDirectory()) {
|
|
613
|
-
return findJsonFiles(entryPath);
|
|
614
|
-
}
|
|
615
|
-
return entry.isFile() && entry.name.endsWith(".json") ? [entryPath] : [];
|
|
616
|
-
}));
|
|
617
|
-
return files.flat();
|
|
618
|
-
}
|
|
619
|
-
async function readJsonFile(file, schema) {
|
|
620
|
-
try {
|
|
621
|
-
return schema.parse(JSON.parse(await readFile(file, "utf8")));
|
|
557
|
+
if (part.type !== "tool") {
|
|
558
|
+
return null;
|
|
622
559
|
}
|
|
623
|
-
|
|
560
|
+
const parsedToolPart = OpencodeToolPartToTimelineItemSchema.safeParse(part);
|
|
561
|
+
if (!parsedToolPart.success || !parsedToolPart.data) {
|
|
624
562
|
return null;
|
|
625
563
|
}
|
|
564
|
+
return buildOpenCodeReplayTimelineEvent({
|
|
565
|
+
item: parsedToolPart.data,
|
|
566
|
+
message,
|
|
567
|
+
part,
|
|
568
|
+
});
|
|
626
569
|
}
|
|
627
|
-
function
|
|
628
|
-
const
|
|
629
|
-
|
|
570
|
+
async function readOpenCodeSessionMessagesFromSdk(client, session) {
|
|
571
|
+
const response = await client.session.messages({
|
|
572
|
+
sessionID: session.id,
|
|
573
|
+
directory: session.directory,
|
|
574
|
+
});
|
|
575
|
+
if (response.error || !response.data) {
|
|
576
|
+
return [];
|
|
577
|
+
}
|
|
578
|
+
return response.data;
|
|
630
579
|
}
|
|
631
|
-
function
|
|
632
|
-
return
|
|
580
|
+
function buildOpenCodeSessionTimeline(messages) {
|
|
581
|
+
return messages.flatMap((message) => buildOpenCodeReplayTimelineEvents(message).map((event) => event.item));
|
|
582
|
+
}
|
|
583
|
+
function resolveOpenCodePersistedSessionModeId(session, messages) {
|
|
584
|
+
const agent = session.agent ?? messages.map(readOpenCodeMessageAgent).find(Boolean);
|
|
585
|
+
return agent ? normalizeOpenCodeModeId(agent) : undefined;
|
|
633
586
|
}
|
|
634
|
-
function
|
|
635
|
-
|
|
587
|
+
function readOpenCodeMessageAgent(message) {
|
|
588
|
+
const agent = message.info.agent;
|
|
589
|
+
return typeof agent === "string" && agent.trim() ? agent : undefined;
|
|
636
590
|
}
|
|
637
|
-
function
|
|
638
|
-
|
|
591
|
+
function resolveOpenCodePersistedSessionModel(session, messages) {
|
|
592
|
+
if (session.model) {
|
|
593
|
+
return buildOpenCodeModelLookupKey(session.model.providerID, session.model.id);
|
|
594
|
+
}
|
|
595
|
+
const model = messages.map(readOpenCodeMessageModel).find(Boolean);
|
|
596
|
+
return model ? buildOpenCodeModelLookupKey(model.providerID, model.modelID) : undefined;
|
|
597
|
+
}
|
|
598
|
+
function readOpenCodeMessageModel(message) {
|
|
599
|
+
const { info } = message;
|
|
600
|
+
if (info.role === "user") {
|
|
601
|
+
return info.model;
|
|
602
|
+
}
|
|
603
|
+
return {
|
|
604
|
+
providerID: info.providerID,
|
|
605
|
+
modelID: info.modelID,
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
function buildOpenCodeReplayTimelineEvents(message) {
|
|
609
|
+
const { info, parts } = message;
|
|
610
|
+
if (info.role === "user") {
|
|
611
|
+
const text = parts
|
|
612
|
+
.filter((part) => part.type === "text")
|
|
613
|
+
.map((part) => part.text)
|
|
614
|
+
.join("");
|
|
615
|
+
return text
|
|
616
|
+
? [
|
|
617
|
+
buildOpenCodeReplayTimelineEvent({
|
|
618
|
+
item: { type: "user_message", text, messageId: info.id },
|
|
619
|
+
message: info,
|
|
620
|
+
}),
|
|
621
|
+
]
|
|
622
|
+
: [];
|
|
623
|
+
}
|
|
624
|
+
const events = [];
|
|
625
|
+
let emittedAssistantText = false;
|
|
626
|
+
for (const part of parts) {
|
|
627
|
+
if (part.type === "text" && part.text) {
|
|
628
|
+
emittedAssistantText = true;
|
|
629
|
+
}
|
|
630
|
+
const event = buildOpenCodeReplayPartTimelineEvent({ part, message: info });
|
|
631
|
+
if (event) {
|
|
632
|
+
events.push(event);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
if (!emittedAssistantText) {
|
|
636
|
+
const text = stringifyStructuredAssistantMessage(info.structured);
|
|
637
|
+
if (text) {
|
|
638
|
+
events.push(buildOpenCodeReplayTimelineEvent({
|
|
639
|
+
item: { type: "assistant_message", text },
|
|
640
|
+
message: info,
|
|
641
|
+
}));
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
return events;
|
|
639
645
|
}
|
|
640
646
|
export const __openCodeInternals = {
|
|
641
647
|
buildOpenCodePromptParts,
|
|
@@ -672,13 +678,12 @@ class ProductionOpenCodeRuntime {
|
|
|
672
678
|
}
|
|
673
679
|
}
|
|
674
680
|
export class OpenCodeAgentClient {
|
|
675
|
-
constructor(logger, runtimeSettings,
|
|
681
|
+
constructor(logger, runtimeSettings, deps = {}) {
|
|
676
682
|
this.provider = "opencode";
|
|
677
683
|
this.capabilities = OPENCODE_CAPABILITIES;
|
|
678
684
|
this.modelContextWindows = new Map();
|
|
679
685
|
this.logger = logger.child({ module: "agent", provider: "opencode" });
|
|
680
686
|
this.runtimeSettings = runtimeSettings;
|
|
681
|
-
this.storageRoot = storageRoot ?? resolveOpenCodeStorageRoot();
|
|
682
687
|
this.runtime =
|
|
683
688
|
deps.runtime ??
|
|
684
689
|
new ProductionOpenCodeRuntime(OpenCodeServerManager.getInstance(this.logger, runtimeSettings));
|
|
@@ -701,7 +706,7 @@ export class OpenCodeAgentClient {
|
|
|
701
706
|
throw new Error("OpenCode session creation returned no data");
|
|
702
707
|
}
|
|
703
708
|
await this.populateModelContextWindowCache(client, openCodeConfig.cwd);
|
|
704
|
-
return new OpenCodeAgentSession(openCodeConfig, client, session.id, this.logger,
|
|
709
|
+
return new OpenCodeAgentSession(openCodeConfig, client, session.id, this.logger, new Map(this.modelContextWindows), acquisition.release, options?.persistSession, launchContext?.agentId);
|
|
705
710
|
}
|
|
706
711
|
catch (error) {
|
|
707
712
|
acquisition.release();
|
|
@@ -709,14 +714,16 @@ export class OpenCodeAgentClient {
|
|
|
709
714
|
}
|
|
710
715
|
}
|
|
711
716
|
async resumeSession(handle, overrides, launchContext) {
|
|
712
|
-
const
|
|
717
|
+
const metadata = (handle.metadata ?? {});
|
|
718
|
+
const cwd = overrides?.cwd ?? metadata.cwd;
|
|
713
719
|
if (!cwd) {
|
|
714
720
|
throw new Error("OpenCode resume requires the original working directory");
|
|
715
721
|
}
|
|
716
722
|
const config = {
|
|
723
|
+
...metadata,
|
|
724
|
+
...overrides,
|
|
717
725
|
provider: "opencode",
|
|
718
726
|
cwd,
|
|
719
|
-
...overrides,
|
|
720
727
|
};
|
|
721
728
|
const openCodeConfig = this.assertConfig(config);
|
|
722
729
|
const acquisition = await this.runtime.acquireServer({ force: false });
|
|
@@ -727,7 +734,7 @@ export class OpenCodeAgentClient {
|
|
|
727
734
|
});
|
|
728
735
|
try {
|
|
729
736
|
await this.populateModelContextWindowCache(client, openCodeConfig.cwd);
|
|
730
|
-
return new OpenCodeAgentSession(openCodeConfig, client, handle.sessionId, this.logger,
|
|
737
|
+
return new OpenCodeAgentSession(openCodeConfig, client, handle.sessionId, this.logger, new Map(this.modelContextWindows), acquisition.release, undefined, launchContext?.agentId);
|
|
731
738
|
}
|
|
732
739
|
catch (error) {
|
|
733
740
|
acquisition.release();
|
|
@@ -752,17 +759,21 @@ export class OpenCodeAgentClient {
|
|
|
752
759
|
if (!providers) {
|
|
753
760
|
return [];
|
|
754
761
|
}
|
|
755
|
-
// Only include models from connected providers (ones that are actually available)
|
|
756
762
|
const connectedProviderIds = new Set(providers.connected);
|
|
757
|
-
//
|
|
758
|
-
|
|
759
|
-
|
|
763
|
+
// Providers with source "api" are managed by the OpenCode console/subscription (e.g. Pi
|
|
764
|
+
// coding agent). They do not appear in `connected` (which only lists env/config providers)
|
|
765
|
+
// but are fully usable — OpenCode authenticates them internally via the console session.
|
|
766
|
+
const isAccessible = (provider) => connectedProviderIds.has(provider.id) || provider.source === "api";
|
|
767
|
+
// Fail fast if no providers are accessible at all
|
|
768
|
+
if (!providers.all.some(isAccessible)) {
|
|
769
|
+
throw new Error("OpenCode has no connected providers. Please authenticate with at least one provider " +
|
|
770
|
+
"(e.g., openai, anthropic), set appropriate environment variables (e.g., OPENAI_API_KEY), " +
|
|
771
|
+
"or log in to OpenCode Go via the console.");
|
|
760
772
|
}
|
|
761
773
|
const models = [];
|
|
762
774
|
this.modelContextWindows.clear();
|
|
763
775
|
for (const provider of providers.all) {
|
|
764
|
-
|
|
765
|
-
if (!connectedProviderIds.has(provider.id)) {
|
|
776
|
+
if (!isAccessible(provider)) {
|
|
766
777
|
continue;
|
|
767
778
|
}
|
|
768
779
|
for (const [modelId, model] of Object.entries(provider.models)) {
|
|
@@ -803,8 +814,37 @@ export class OpenCodeAgentClient {
|
|
|
803
814
|
acquisition.release();
|
|
804
815
|
}
|
|
805
816
|
}
|
|
817
|
+
async listCommands(config) {
|
|
818
|
+
const openCodeConfig = this.assertConfig(config);
|
|
819
|
+
const acquisition = await this.runtime.acquireServer({ force: false });
|
|
820
|
+
const { url } = acquisition.server;
|
|
821
|
+
const client = this.runtime.createClient({
|
|
822
|
+
baseUrl: url,
|
|
823
|
+
directory: openCodeConfig.cwd,
|
|
824
|
+
});
|
|
825
|
+
try {
|
|
826
|
+
return await listOpenCodeCommandsFromSdk(client, openCodeConfig.cwd);
|
|
827
|
+
}
|
|
828
|
+
finally {
|
|
829
|
+
acquisition.release();
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
async listFeatures(_config) {
|
|
833
|
+
return [];
|
|
834
|
+
}
|
|
806
835
|
async listPersistedAgents(options) {
|
|
807
|
-
|
|
836
|
+
const acquisition = await this.runtime.acquireServer({ force: false });
|
|
837
|
+
const { url } = acquisition.server;
|
|
838
|
+
const client = this.runtime.createClient({
|
|
839
|
+
baseUrl: url,
|
|
840
|
+
directory: options?.cwd ?? "",
|
|
841
|
+
});
|
|
842
|
+
try {
|
|
843
|
+
return await collectOpenCodePersistedAgentsFromSdk(client, options);
|
|
844
|
+
}
|
|
845
|
+
finally {
|
|
846
|
+
acquisition.release();
|
|
847
|
+
}
|
|
808
848
|
}
|
|
809
849
|
async isAvailable() {
|
|
810
850
|
const command = this.runtimeSettings?.command;
|
|
@@ -923,6 +963,21 @@ function stringifyStructuredAssistantMessage(value) {
|
|
|
923
963
|
return null;
|
|
924
964
|
}
|
|
925
965
|
}
|
|
966
|
+
async function listOpenCodeCommandsFromSdk(client, directory) {
|
|
967
|
+
const result = await client.command.list({ directory });
|
|
968
|
+
const commandsByName = new Map(OPENCODE_HANDLED_BUILTIN_SLASH_COMMANDS.map((command) => [command.name, command]));
|
|
969
|
+
if (result.error || !result.data) {
|
|
970
|
+
return Array.from(commandsByName.values());
|
|
971
|
+
}
|
|
972
|
+
for (const cmd of result.data) {
|
|
973
|
+
commandsByName.set(cmd.name, {
|
|
974
|
+
name: cmd.name,
|
|
975
|
+
description: cmd.description ?? "",
|
|
976
|
+
argumentHint: cmd.hints?.length ? cmd.hints.join(" ") : "",
|
|
977
|
+
});
|
|
978
|
+
}
|
|
979
|
+
return Array.from(commandsByName.values());
|
|
980
|
+
}
|
|
926
981
|
function readOpenCodeRecord(value) {
|
|
927
982
|
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
928
983
|
? value
|
|
@@ -1579,7 +1634,7 @@ function unwrapOpenCodeGlobalEvent(event) {
|
|
|
1579
1634
|
return null;
|
|
1580
1635
|
}
|
|
1581
1636
|
class OpenCodeAgentSession {
|
|
1582
|
-
constructor(config, client, sessionId, logger,
|
|
1637
|
+
constructor(config, client, sessionId, logger, modelContextWindowsByModelKey = new Map(), releaseServer, persistSession = true, agentId) {
|
|
1583
1638
|
this.agentId = agentId;
|
|
1584
1639
|
this.provider = "opencode";
|
|
1585
1640
|
this.capabilities = OPENCODE_CAPABILITIES;
|
|
@@ -1606,8 +1661,10 @@ class OpenCodeAgentSession {
|
|
|
1606
1661
|
this.subAgentsByCallId = new Map();
|
|
1607
1662
|
this.subAgentCallIdByChildSessionId = new Map();
|
|
1608
1663
|
this.pendingChildToolPartsBySessionId = new Map();
|
|
1664
|
+
this.eventStreamAbortController = null;
|
|
1665
|
+
this.eventStreamReady = null;
|
|
1666
|
+
this.closed = false;
|
|
1609
1667
|
this.deletedFromProvider = false;
|
|
1610
|
-
this.retryFailureTimer = null;
|
|
1611
1668
|
this.config = config;
|
|
1612
1669
|
this.client = client;
|
|
1613
1670
|
this.sessionId = sessionId;
|
|
@@ -1617,6 +1674,7 @@ class OpenCodeAgentSession {
|
|
|
1617
1674
|
this.releaseServer = releaseServer ?? null;
|
|
1618
1675
|
this.persistSession = persistSession;
|
|
1619
1676
|
this.selectedModelContextWindowMaxTokens = this.resolveConfiguredModelContextWindowMaxTokens(config.model);
|
|
1677
|
+
this.startEventStream();
|
|
1620
1678
|
}
|
|
1621
1679
|
get id() {
|
|
1622
1680
|
return this.sessionId;
|
|
@@ -1703,7 +1761,6 @@ class OpenCodeAgentSession {
|
|
|
1703
1761
|
this.subAgentsByCallId.clear();
|
|
1704
1762
|
this.subAgentCallIdByChildSessionId.clear();
|
|
1705
1763
|
this.pendingChildToolPartsBySessionId.clear();
|
|
1706
|
-
this.clearRetryFailureTimer();
|
|
1707
1764
|
const turnAbortController = new AbortController();
|
|
1708
1765
|
this.abortController = turnAbortController;
|
|
1709
1766
|
await this.ensureMcpServersConfigured();
|
|
@@ -1714,22 +1771,18 @@ class OpenCodeAgentSession {
|
|
|
1714
1771
|
const thinkingOptionId = this.config.thinkingOptionId;
|
|
1715
1772
|
const effectiveVariant = thinkingOptionId ?? undefined;
|
|
1716
1773
|
const effectiveMode = resolveOpenCodeRuntimeAgentId(this.currentMode);
|
|
1717
|
-
const turnId = this.createTurnId();
|
|
1718
|
-
this.activeForegroundTurnId = turnId;
|
|
1719
|
-
// OpenCode's /event SSE endpoint does NOT replay past events. If we send
|
|
1720
|
-
// the prompt before our reader is connected, terminal events fired early
|
|
1721
|
-
// by the server (e.g. session.error / session.idle for invalid model or
|
|
1722
|
-
// mode) are missed and the turn hangs forever. Wait for the subscription
|
|
1723
|
-
// to be established before sending anything.
|
|
1724
|
-
const subscriptionReady = createDeferred();
|
|
1725
|
-
void this.consumeEventStream(turnId, turnAbortController, subscriptionReady);
|
|
1726
1774
|
try {
|
|
1727
|
-
await
|
|
1775
|
+
await this.ensureEventStreamReady();
|
|
1728
1776
|
}
|
|
1729
|
-
catch {
|
|
1730
|
-
|
|
1731
|
-
|
|
1777
|
+
catch (error) {
|
|
1778
|
+
if (this.abortController === turnAbortController) {
|
|
1779
|
+
this.abortController = null;
|
|
1780
|
+
}
|
|
1781
|
+
throw error;
|
|
1732
1782
|
}
|
|
1783
|
+
const turnId = this.createTurnId();
|
|
1784
|
+
this.activeForegroundTurnId = turnId;
|
|
1785
|
+
this.notifySubscribers({ type: "turn_started", provider: "opencode" }, turnId);
|
|
1733
1786
|
const slashCommand = await this.resolveSlashCommandInvocation(prompt);
|
|
1734
1787
|
if (slashCommand) {
|
|
1735
1788
|
if (slashCommand.commandName === "compact" || slashCommand.commandName === "summarize") {
|
|
@@ -1761,12 +1814,8 @@ class OpenCodeAgentSession {
|
|
|
1761
1814
|
});
|
|
1762
1815
|
return { turnId };
|
|
1763
1816
|
}
|
|
1764
|
-
// command()
|
|
1765
|
-
//
|
|
1766
|
-
// our SSE reader connects, we miss `session.idle` and the turn hangs.
|
|
1767
|
-
// Handle both success and error in the response handler as a fallback —
|
|
1768
|
-
// finishForegroundTurn's guard prevents duplicate terminal events if the
|
|
1769
|
-
// SSE stream already delivered the event.
|
|
1817
|
+
// command() is only dispatch acknowledgement. OpenCode session events are
|
|
1818
|
+
// the source of truth for when the command turn becomes idle or fails.
|
|
1770
1819
|
void this.client.session
|
|
1771
1820
|
.command({
|
|
1772
1821
|
sessionID: this.sessionId,
|
|
@@ -1790,9 +1839,6 @@ class OpenCodeAgentSession {
|
|
|
1790
1839
|
const errorMsg = toDiagnosticErrorMessage(response.error);
|
|
1791
1840
|
this.finishForegroundTurn({ type: "turn_failed", provider: "opencode", error: errorMsg }, turnId);
|
|
1792
1841
|
}
|
|
1793
|
-
else {
|
|
1794
|
-
this.finishForegroundTurn({ type: "turn_completed", provider: "opencode", usage: undefined }, turnId);
|
|
1795
|
-
}
|
|
1796
1842
|
return;
|
|
1797
1843
|
})
|
|
1798
1844
|
.catch((err) => {
|
|
@@ -1875,89 +1921,95 @@ class OpenCodeAgentSession {
|
|
|
1875
1921
|
this.subscribers.delete(callback);
|
|
1876
1922
|
};
|
|
1877
1923
|
}
|
|
1878
|
-
|
|
1924
|
+
startEventStream() {
|
|
1925
|
+
void this.ensureEventStreamReady().catch((error) => {
|
|
1926
|
+
this.logger.warn({ err: error, sessionId: this.sessionId }, "OpenCode event stream failed");
|
|
1927
|
+
});
|
|
1928
|
+
}
|
|
1929
|
+
ensureEventStreamReady() {
|
|
1930
|
+
if (this.eventStreamReady) {
|
|
1931
|
+
return this.eventStreamReady.promise;
|
|
1932
|
+
}
|
|
1933
|
+
const eventStreamAbortController = new AbortController();
|
|
1934
|
+
const eventStreamReady = createDeferred();
|
|
1935
|
+
this.eventStreamAbortController = eventStreamAbortController;
|
|
1936
|
+
this.eventStreamReady = eventStreamReady;
|
|
1937
|
+
void this.consumeEventStream(eventStreamAbortController, eventStreamReady).finally(() => {
|
|
1938
|
+
if (this.eventStreamAbortController === eventStreamAbortController) {
|
|
1939
|
+
this.eventStreamAbortController = null;
|
|
1940
|
+
this.eventStreamReady = null;
|
|
1941
|
+
}
|
|
1942
|
+
});
|
|
1943
|
+
return eventStreamReady.promise;
|
|
1944
|
+
}
|
|
1945
|
+
async consumeEventStream(eventStreamAbortController, eventStreamReady) {
|
|
1879
1946
|
this.traceOpenCode("provider.opencode.subscribe.start", {
|
|
1880
|
-
turnId,
|
|
1881
1947
|
sessionId: this.sessionId,
|
|
1882
1948
|
cwd: this.config.cwd,
|
|
1883
1949
|
});
|
|
1950
|
+
let eventStreamReadyResolved = false;
|
|
1884
1951
|
try {
|
|
1885
1952
|
const result = await this.client.global.event({
|
|
1886
|
-
signal:
|
|
1953
|
+
signal: eventStreamAbortController.signal,
|
|
1887
1954
|
sseMaxRetryAttempts: 0,
|
|
1888
1955
|
});
|
|
1956
|
+
eventStreamReadyResolved = true;
|
|
1957
|
+
this.traceOpenCode("provider.opencode.subscribe.ready", {
|
|
1958
|
+
sessionId: this.sessionId,
|
|
1959
|
+
});
|
|
1960
|
+
eventStreamReady.resolve();
|
|
1889
1961
|
let eventCount = 0;
|
|
1890
|
-
let subscriptionReadyResolved = false;
|
|
1891
1962
|
for await (const rawEvent of result.stream) {
|
|
1892
1963
|
eventCount += 1;
|
|
1893
|
-
|
|
1894
|
-
subscriptionReadyResolved = true;
|
|
1895
|
-
this.traceOpenCode("provider.opencode.subscribe.ready", {
|
|
1896
|
-
turnId,
|
|
1897
|
-
sessionId: this.sessionId,
|
|
1898
|
-
});
|
|
1899
|
-
subscriptionReady.resolve();
|
|
1900
|
-
}
|
|
1901
|
-
const shouldContinue = await this.consumeOpenCodeStreamEvent({
|
|
1902
|
-
rawEvent,
|
|
1903
|
-
eventCount,
|
|
1904
|
-
turnId,
|
|
1905
|
-
turnAbortController,
|
|
1906
|
-
});
|
|
1907
|
-
if (!shouldContinue) {
|
|
1908
|
-
return;
|
|
1909
|
-
}
|
|
1964
|
+
await this.consumeOpenCodeStreamEvent({ rawEvent, eventCount });
|
|
1910
1965
|
}
|
|
1911
1966
|
this.traceOpenCode("provider.opencode.stream.eof", {
|
|
1912
|
-
turnId,
|
|
1913
1967
|
eventCount,
|
|
1914
|
-
aborted:
|
|
1915
|
-
|
|
1968
|
+
aborted: eventStreamAbortController.signal.aborted,
|
|
1969
|
+
activeTurnId: this.activeForegroundTurnId,
|
|
1916
1970
|
});
|
|
1917
|
-
if (!
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1971
|
+
if (!eventStreamAbortController.signal.aborted) {
|
|
1972
|
+
if (!eventStreamReadyResolved) {
|
|
1973
|
+
eventStreamReady.reject(new Error("OpenCode event stream ended before it became ready"));
|
|
1974
|
+
}
|
|
1975
|
+
const activeTurnId = this.activeForegroundTurnId;
|
|
1976
|
+
if (activeTurnId) {
|
|
1977
|
+
this.traceOpenCode("provider.opencode.turn.fail_eof", {
|
|
1978
|
+
turnId: activeTurnId,
|
|
1979
|
+
eventCount,
|
|
1980
|
+
});
|
|
1981
|
+
this.finishForegroundTurn({
|
|
1982
|
+
type: "turn_failed",
|
|
1983
|
+
provider: "opencode",
|
|
1984
|
+
error: "OpenCode event stream ended before the turn reached a terminal state",
|
|
1985
|
+
}, activeTurnId);
|
|
1921
1986
|
}
|
|
1922
|
-
this.finishForegroundTurn({
|
|
1923
|
-
type: "turn_failed",
|
|
1924
|
-
provider: "opencode",
|
|
1925
|
-
error: "OpenCode event stream ended before the turn reached a terminal state",
|
|
1926
|
-
}, turnId);
|
|
1927
1987
|
}
|
|
1928
1988
|
}
|
|
1929
1989
|
catch (error) {
|
|
1930
1990
|
this.traceOpenCode("provider.opencode.subscribe.error", {
|
|
1931
|
-
turnId,
|
|
1991
|
+
turnId: this.activeForegroundTurnId ?? undefined,
|
|
1932
1992
|
error: error instanceof Error ? { name: error.name, message: error.message } : String(error),
|
|
1933
1993
|
});
|
|
1934
|
-
|
|
1935
|
-
|
|
1994
|
+
if (!eventStreamReadyResolved) {
|
|
1995
|
+
eventStreamReady.reject(error);
|
|
1996
|
+
}
|
|
1997
|
+
const activeTurnId = this.activeForegroundTurnId;
|
|
1998
|
+
if (!eventStreamAbortController.signal.aborted && activeTurnId) {
|
|
1936
1999
|
this.finishForegroundTurn({
|
|
1937
2000
|
type: "turn_failed",
|
|
1938
2001
|
provider: "opencode",
|
|
1939
2002
|
error: toDiagnosticErrorMessage(error),
|
|
1940
|
-
},
|
|
1941
|
-
}
|
|
1942
|
-
}
|
|
1943
|
-
finally {
|
|
1944
|
-
if (turnAbortController.signal.aborted) {
|
|
1945
|
-
this.finishForegroundTurn({
|
|
1946
|
-
type: "turn_canceled",
|
|
1947
|
-
provider: "opencode",
|
|
1948
|
-
reason: "interrupted",
|
|
1949
|
-
}, turnId);
|
|
1950
|
-
}
|
|
1951
|
-
if (this.abortController === turnAbortController && this.activeForegroundTurnId !== turnId) {
|
|
1952
|
-
this.abortController = null;
|
|
2003
|
+
}, activeTurnId);
|
|
1953
2004
|
}
|
|
1954
2005
|
}
|
|
1955
2006
|
}
|
|
1956
2007
|
async consumeOpenCodeStreamEvent(params) {
|
|
1957
|
-
const { rawEvent, eventCount
|
|
2008
|
+
const { rawEvent, eventCount } = params;
|
|
2009
|
+
const turnId = this.activeForegroundTurnId;
|
|
1958
2010
|
const event = unwrapOpenCodeGlobalEvent(rawEvent);
|
|
1959
2011
|
this.traceOpenCode("provider.opencode.raw_event", {
|
|
1960
|
-
turnId,
|
|
2012
|
+
turnId: turnId ?? undefined,
|
|
1961
2013
|
n: eventCount,
|
|
1962
2014
|
type: event?.type,
|
|
1963
2015
|
rawType: readOpenCodeRecord(rawEvent)?.type,
|
|
@@ -1966,18 +2018,16 @@ class OpenCodeAgentSession {
|
|
|
1966
2018
|
properties: event?.properties,
|
|
1967
2019
|
});
|
|
1968
2020
|
if (!event) {
|
|
1969
|
-
return
|
|
2021
|
+
return;
|
|
1970
2022
|
}
|
|
1971
|
-
if (
|
|
2023
|
+
if (!turnId) {
|
|
1972
2024
|
this.traceOpenCode("provider.opencode.event.skip", {
|
|
1973
|
-
turnId,
|
|
1974
2025
|
n: eventCount,
|
|
1975
|
-
|
|
1976
|
-
|
|
2026
|
+
reason: "no_active_turn",
|
|
2027
|
+
type: event.type,
|
|
1977
2028
|
});
|
|
1978
|
-
return
|
|
2029
|
+
return;
|
|
1979
2030
|
}
|
|
1980
|
-
this.armRetryFailureTimerForStatus(event, turnId);
|
|
1981
2031
|
const translated = await this.translateEvent(event);
|
|
1982
2032
|
this.traceOpenCode("provider.opencode.parsed_event", {
|
|
1983
2033
|
turnId,
|
|
@@ -1989,7 +2039,7 @@ class OpenCodeAgentSession {
|
|
|
1989
2039
|
for (const e of translated) {
|
|
1990
2040
|
if (this.activeForegroundTurnId !== turnId) {
|
|
1991
2041
|
this.traceOpenCode("provider.opencode.parsed_event.skip_active", { turnId, type: e.type });
|
|
1992
|
-
return
|
|
2042
|
+
return;
|
|
1993
2043
|
}
|
|
1994
2044
|
if (e.type === "timeline" && e.item.type === "tool_call") {
|
|
1995
2045
|
this.trackToolCall(e.item);
|
|
@@ -2001,11 +2051,10 @@ class OpenCodeAgentSession {
|
|
|
2001
2051
|
type: terminalEvent.type,
|
|
2002
2052
|
});
|
|
2003
2053
|
this.finishForegroundTurn(terminalEvent, turnId);
|
|
2004
|
-
return
|
|
2054
|
+
return;
|
|
2005
2055
|
}
|
|
2006
2056
|
this.notifySubscribers(e, turnId);
|
|
2007
2057
|
}
|
|
2008
|
-
return true;
|
|
2009
2058
|
}
|
|
2010
2059
|
finishForegroundTurn(event, turnId) {
|
|
2011
2060
|
this.traceOpenCode("provider.opencode.finish_foreground_turn", {
|
|
@@ -2024,10 +2073,7 @@ class OpenCodeAgentSession {
|
|
|
2024
2073
|
else {
|
|
2025
2074
|
this.runningToolCalls.clear();
|
|
2026
2075
|
}
|
|
2027
|
-
this.clearRetryFailureTimer();
|
|
2028
2076
|
this.activeForegroundTurnId = null;
|
|
2029
|
-
// Abort the SSE connection so the SDK tears down the underlying fetch.
|
|
2030
|
-
this.abortController?.abort();
|
|
2031
2077
|
this.abortController = null;
|
|
2032
2078
|
this.notifySubscribers(event, turnId);
|
|
2033
2079
|
}
|
|
@@ -2038,37 +2084,6 @@ class OpenCodeAgentSession {
|
|
|
2038
2084
|
}
|
|
2039
2085
|
this.runningToolCalls.delete(item.callId);
|
|
2040
2086
|
}
|
|
2041
|
-
armRetryFailureTimerForStatus(event, turnId) {
|
|
2042
|
-
if (this.retryFailureTimer || event.type !== "session.status") {
|
|
2043
|
-
return;
|
|
2044
|
-
}
|
|
2045
|
-
if (event.properties.sessionID !== this.sessionId || event.properties.status.type !== "retry") {
|
|
2046
|
-
return;
|
|
2047
|
-
}
|
|
2048
|
-
const retry = event.properties.status;
|
|
2049
|
-
const message = typeof retry.message === "string" ? retry.message.trim() : "";
|
|
2050
|
-
const error = message
|
|
2051
|
-
? `OpenCode provider retry did not recover: ${message}`
|
|
2052
|
-
: "OpenCode provider retry did not recover";
|
|
2053
|
-
this.retryFailureTimer = setTimeout(() => {
|
|
2054
|
-
this.retryFailureTimer = null;
|
|
2055
|
-
if (this.activeForegroundTurnId !== turnId) {
|
|
2056
|
-
return;
|
|
2057
|
-
}
|
|
2058
|
-
this.finishForegroundTurn({
|
|
2059
|
-
type: "turn_failed",
|
|
2060
|
-
provider: "opencode",
|
|
2061
|
-
error,
|
|
2062
|
-
}, turnId);
|
|
2063
|
-
}, OPENCODE_RETRY_STATUS_FAILURE_MS);
|
|
2064
|
-
}
|
|
2065
|
-
clearRetryFailureTimer() {
|
|
2066
|
-
if (!this.retryFailureTimer) {
|
|
2067
|
-
return;
|
|
2068
|
-
}
|
|
2069
|
-
clearTimeout(this.retryFailureTimer);
|
|
2070
|
-
this.retryFailureTimer = null;
|
|
2071
|
-
}
|
|
2072
2087
|
synthesizeInterruptedToolCalls(turnId) {
|
|
2073
2088
|
for (const item of this.runningToolCalls.values()) {
|
|
2074
2089
|
const error = { message: "Tool execution aborted" };
|
|
@@ -2093,6 +2108,9 @@ class OpenCodeAgentSession {
|
|
|
2093
2108
|
this.runningToolCalls.clear();
|
|
2094
2109
|
}
|
|
2095
2110
|
notifySubscribers(event, turnIdOverride) {
|
|
2111
|
+
if (this.closed) {
|
|
2112
|
+
return;
|
|
2113
|
+
}
|
|
2096
2114
|
const turnId = turnIdOverride ?? this.activeForegroundTurnId;
|
|
2097
2115
|
const tagged = turnId ? { ...event, turnId } : event;
|
|
2098
2116
|
this.traceOpenCode("provider.opencode.event_emit", {
|
|
@@ -2128,62 +2146,9 @@ class OpenCodeAgentSession {
|
|
|
2128
2146
|
if (response.error || !response.data) {
|
|
2129
2147
|
return;
|
|
2130
2148
|
}
|
|
2131
|
-
for (const
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
.filter((p) => p.type === "text")
|
|
2135
|
-
.map((p) => p.text)
|
|
2136
|
-
.join("");
|
|
2137
|
-
if (text) {
|
|
2138
|
-
yield {
|
|
2139
|
-
type: "timeline",
|
|
2140
|
-
provider: "opencode",
|
|
2141
|
-
item: { type: "user_message", text },
|
|
2142
|
-
};
|
|
2143
|
-
}
|
|
2144
|
-
}
|
|
2145
|
-
else {
|
|
2146
|
-
let emittedAssistantText = false;
|
|
2147
|
-
for (const part of parts) {
|
|
2148
|
-
if (part.type === "text" && part.text) {
|
|
2149
|
-
emittedAssistantText = true;
|
|
2150
|
-
yield {
|
|
2151
|
-
type: "timeline",
|
|
2152
|
-
provider: "opencode",
|
|
2153
|
-
item: { type: "assistant_message", text: part.text },
|
|
2154
|
-
};
|
|
2155
|
-
continue;
|
|
2156
|
-
}
|
|
2157
|
-
if (part.type === "reasoning" && part.text) {
|
|
2158
|
-
yield {
|
|
2159
|
-
type: "timeline",
|
|
2160
|
-
provider: "opencode",
|
|
2161
|
-
item: { type: "reasoning", text: part.text },
|
|
2162
|
-
};
|
|
2163
|
-
continue;
|
|
2164
|
-
}
|
|
2165
|
-
if (part.type !== "tool") {
|
|
2166
|
-
continue;
|
|
2167
|
-
}
|
|
2168
|
-
const parsedToolPart = OpencodeToolPartToTimelineItemSchema.safeParse(part);
|
|
2169
|
-
if (parsedToolPart.success && parsedToolPart.data) {
|
|
2170
|
-
yield {
|
|
2171
|
-
type: "timeline",
|
|
2172
|
-
provider: "opencode",
|
|
2173
|
-
item: parsedToolPart.data,
|
|
2174
|
-
};
|
|
2175
|
-
}
|
|
2176
|
-
}
|
|
2177
|
-
if (!emittedAssistantText) {
|
|
2178
|
-
const text = stringifyStructuredAssistantMessage(info.structured);
|
|
2179
|
-
if (text) {
|
|
2180
|
-
yield {
|
|
2181
|
-
type: "timeline",
|
|
2182
|
-
provider: "opencode",
|
|
2183
|
-
item: { type: "assistant_message", text },
|
|
2184
|
-
};
|
|
2185
|
-
}
|
|
2186
|
-
}
|
|
2149
|
+
for (const message of response.data) {
|
|
2150
|
+
for (const event of buildOpenCodeReplayTimelineEvents(message)) {
|
|
2151
|
+
yield event;
|
|
2187
2152
|
}
|
|
2188
2153
|
}
|
|
2189
2154
|
}
|
|
@@ -2209,21 +2174,7 @@ class OpenCodeAgentSession {
|
|
|
2209
2174
|
return this.currentMode;
|
|
2210
2175
|
}
|
|
2211
2176
|
async listCommands() {
|
|
2212
|
-
|
|
2213
|
-
directory: this.config.cwd,
|
|
2214
|
-
});
|
|
2215
|
-
const commandsByName = new Map(OPENCODE_HANDLED_BUILTIN_SLASH_COMMANDS.map((command) => [command.name, command]));
|
|
2216
|
-
if (result.error || !result.data) {
|
|
2217
|
-
return Array.from(commandsByName.values());
|
|
2218
|
-
}
|
|
2219
|
-
for (const cmd of result.data) {
|
|
2220
|
-
commandsByName.set(cmd.name, {
|
|
2221
|
-
name: cmd.name,
|
|
2222
|
-
description: cmd.description ?? "",
|
|
2223
|
-
argumentHint: cmd.hints?.length ? cmd.hints.join(" ") : "",
|
|
2224
|
-
});
|
|
2225
|
-
}
|
|
2226
|
-
return Array.from(commandsByName.values());
|
|
2177
|
+
return await listOpenCodeCommandsFromSdk(this.client, this.config.cwd);
|
|
2227
2178
|
}
|
|
2228
2179
|
async setMode(modeId) {
|
|
2229
2180
|
this.currentMode = normalizeOpenCodeModeId(modeId);
|
|
@@ -2282,12 +2233,23 @@ class OpenCodeAgentSession {
|
|
|
2282
2233
|
nativeHandle: this.sessionId,
|
|
2283
2234
|
metadata: {
|
|
2284
2235
|
cwd: this.config.cwd,
|
|
2236
|
+
...(this.config.modeId ? { modeId: this.config.modeId } : {}),
|
|
2237
|
+
...(this.config.model ? { model: this.config.model } : {}),
|
|
2285
2238
|
},
|
|
2286
2239
|
};
|
|
2287
2240
|
}
|
|
2288
2241
|
async close() {
|
|
2289
2242
|
try {
|
|
2243
|
+
// Flip closed before clearing subscribers so any event the SDK delivers
|
|
2244
|
+
// after the abort (between here and subscribers.clear) is swallowed by
|
|
2245
|
+
// notifySubscribers instead of bubbling through provider-runner as an
|
|
2246
|
+
// unhandled rejection in whichever test the daemon hops to next.
|
|
2247
|
+
this.closed = true;
|
|
2290
2248
|
this.abortController?.abort();
|
|
2249
|
+
this.eventStreamAbortController?.abort();
|
|
2250
|
+
this.eventStreamAbortController = null;
|
|
2251
|
+
this.eventStreamReady = null;
|
|
2252
|
+
this.subscribers.clear();
|
|
2291
2253
|
await reconcileOpenCodeSessionClose({
|
|
2292
2254
|
client: this.client,
|
|
2293
2255
|
sessionId: this.sessionId,
|
|
@@ -2295,7 +2257,6 @@ class OpenCodeAgentSession {
|
|
|
2295
2257
|
logger: this.logger,
|
|
2296
2258
|
});
|
|
2297
2259
|
await this.deleteProviderSessionIfEphemeral();
|
|
2298
|
-
this.subscribers.clear();
|
|
2299
2260
|
this.activeForegroundTurnId = null;
|
|
2300
2261
|
}
|
|
2301
2262
|
finally {
|