@agentex/agent 0.0.27 → 0.0.28

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.
Files changed (168) hide show
  1. package/CHANGELOG.md +108 -0
  2. package/README.md +58 -7
  3. package/dist/derived.d.ts.map +1 -1
  4. package/dist/derived.js +99 -2
  5. package/dist/derived.js.map +1 -1
  6. package/dist/goals/controller.d.ts.map +1 -1
  7. package/dist/goals/controller.js +21 -0
  8. package/dist/goals/controller.js.map +1 -1
  9. package/dist/goals/normalize.d.ts +1 -0
  10. package/dist/goals/normalize.d.ts.map +1 -1
  11. package/dist/goals/normalize.js +5 -0
  12. package/dist/goals/normalize.js.map +1 -1
  13. package/dist/index.d.ts +2 -2
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js.map +1 -1
  16. package/dist/providers/claude/attach.d.ts.map +1 -1
  17. package/dist/providers/claude/attach.js +50 -9
  18. package/dist/providers/claude/attach.js.map +1 -1
  19. package/dist/providers/claude/index.d.ts.map +1 -1
  20. package/dist/providers/claude/index.js +8 -0
  21. package/dist/providers/claude/index.js.map +1 -1
  22. package/dist/providers/claude/session.d.ts +2 -0
  23. package/dist/providers/claude/session.d.ts.map +1 -1
  24. package/dist/providers/claude/session.js +17 -10
  25. package/dist/providers/claude/session.js.map +1 -1
  26. package/dist/providers/codex/attach.d.ts.map +1 -1
  27. package/dist/providers/codex/attach.js +40 -3
  28. package/dist/providers/codex/attach.js.map +1 -1
  29. package/dist/providers/codex/index.d.ts.map +1 -1
  30. package/dist/providers/codex/index.js +8 -0
  31. package/dist/providers/codex/index.js.map +1 -1
  32. package/dist/providers/codex/transcript.d.ts +3 -1
  33. package/dist/providers/codex/transcript.d.ts.map +1 -1
  34. package/dist/providers/codex/transcript.js +3 -1
  35. package/dist/providers/codex/transcript.js.map +1 -1
  36. package/dist/providers/cursor/discovery.d.ts +6 -0
  37. package/dist/providers/cursor/discovery.d.ts.map +1 -0
  38. package/dist/providers/cursor/discovery.js +118 -0
  39. package/dist/providers/cursor/discovery.js.map +1 -0
  40. package/dist/providers/cursor/execute.d.ts.map +1 -1
  41. package/dist/providers/cursor/execute.js +83 -45
  42. package/dist/providers/cursor/execute.js.map +1 -1
  43. package/dist/providers/cursor/index.d.ts.map +1 -1
  44. package/dist/providers/cursor/index.js +17 -4
  45. package/dist/providers/cursor/index.js.map +1 -1
  46. package/dist/providers/cursor/parse.js +1 -1
  47. package/dist/providers/cursor/parse.js.map +1 -1
  48. package/dist/providers/cursor/probe.d.ts +3 -0
  49. package/dist/providers/cursor/probe.d.ts.map +1 -0
  50. package/dist/providers/cursor/probe.js +89 -0
  51. package/dist/providers/cursor/probe.js.map +1 -0
  52. package/dist/providers/cursor/runtime.d.ts +4 -0
  53. package/dist/providers/cursor/runtime.d.ts.map +1 -0
  54. package/dist/providers/cursor/runtime.js +12 -0
  55. package/dist/providers/cursor/runtime.js.map +1 -0
  56. package/dist/providers/cursor/session.d.ts +3 -0
  57. package/dist/providers/cursor/session.d.ts.map +1 -0
  58. package/dist/providers/cursor/session.js +14 -0
  59. package/dist/providers/cursor/session.js.map +1 -0
  60. package/dist/providers/opencode/client.d.ts +14 -0
  61. package/dist/providers/opencode/client.d.ts.map +1 -0
  62. package/dist/providers/opencode/client.js +40 -0
  63. package/dist/providers/opencode/client.js.map +1 -0
  64. package/dist/providers/opencode/discovery.d.ts +4 -0
  65. package/dist/providers/opencode/discovery.d.ts.map +1 -0
  66. package/dist/providers/opencode/discovery.js +68 -0
  67. package/dist/providers/opencode/discovery.js.map +1 -0
  68. package/dist/providers/opencode/execute.d.ts.map +1 -1
  69. package/dist/providers/opencode/execute.js +219 -164
  70. package/dist/providers/opencode/execute.js.map +1 -1
  71. package/dist/providers/opencode/history.d.ts +24 -0
  72. package/dist/providers/opencode/history.d.ts.map +1 -0
  73. package/dist/providers/opencode/history.js +242 -0
  74. package/dist/providers/opencode/history.js.map +1 -0
  75. package/dist/providers/opencode/http-session.d.ts.map +1 -1
  76. package/dist/providers/opencode/http-session.js +333 -49
  77. package/dist/providers/opencode/http-session.js.map +1 -1
  78. package/dist/providers/opencode/index.d.ts.map +1 -1
  79. package/dist/providers/opencode/index.js +26 -3
  80. package/dist/providers/opencode/index.js.map +1 -1
  81. package/dist/providers/opencode/manager.d.ts +11 -0
  82. package/dist/providers/opencode/manager.d.ts.map +1 -0
  83. package/dist/providers/opencode/manager.js +240 -0
  84. package/dist/providers/opencode/manager.js.map +1 -0
  85. package/dist/providers/opencode/modes.d.ts +3 -0
  86. package/dist/providers/opencode/modes.d.ts.map +1 -0
  87. package/dist/providers/opencode/modes.js +30 -0
  88. package/dist/providers/opencode/modes.js.map +1 -0
  89. package/dist/providers/opencode/parse.d.ts.map +1 -1
  90. package/dist/providers/opencode/parse.js +2 -22
  91. package/dist/providers/opencode/parse.js.map +1 -1
  92. package/dist/providers/opencode/probe.d.ts +3 -0
  93. package/dist/providers/opencode/probe.d.ts.map +1 -0
  94. package/dist/providers/opencode/probe.js +107 -0
  95. package/dist/providers/opencode/probe.js.map +1 -0
  96. package/dist/providers/opencode/runtime.d.ts +8 -0
  97. package/dist/providers/opencode/runtime.d.ts.map +1 -0
  98. package/dist/providers/opencode/runtime.js +12 -0
  99. package/dist/providers/opencode/runtime.js.map +1 -0
  100. package/dist/providers/opencode/server.d.ts +18 -3
  101. package/dist/providers/opencode/server.d.ts.map +1 -1
  102. package/dist/providers/opencode/server.js +0 -0
  103. package/dist/providers/opencode/server.js.map +1 -1
  104. package/dist/providers/opencode/skill-config.d.ts +11 -0
  105. package/dist/providers/opencode/skill-config.d.ts.map +1 -0
  106. package/dist/providers/opencode/skill-config.js +57 -0
  107. package/dist/providers/opencode/skill-config.js.map +1 -0
  108. package/dist/sessions/exec-backed.d.ts +10 -0
  109. package/dist/sessions/exec-backed.d.ts.map +1 -0
  110. package/dist/sessions/exec-backed.js +141 -0
  111. package/dist/sessions/exec-backed.js.map +1 -0
  112. package/dist/sessions/history.d.ts +4 -0
  113. package/dist/sessions/history.d.ts.map +1 -0
  114. package/dist/sessions/history.js +59 -0
  115. package/dist/sessions/history.js.map +1 -0
  116. package/dist/sessions/index.d.ts +3 -0
  117. package/dist/sessions/index.d.ts.map +1 -1
  118. package/dist/sessions/index.js +2 -0
  119. package/dist/sessions/index.js.map +1 -1
  120. package/dist/types.d.ts +147 -5
  121. package/dist/types.d.ts.map +1 -1
  122. package/dist/types.js.map +1 -1
  123. package/dist/utils/auth.d.ts.map +1 -1
  124. package/dist/utils/auth.js +36 -6
  125. package/dist/utils/auth.js.map +1 -1
  126. package/dist/utils/binary.d.ts.map +1 -1
  127. package/dist/utils/binary.js +3 -0
  128. package/dist/utils/binary.js.map +1 -1
  129. package/dist/utils/endpoint.js +3 -3
  130. package/dist/utils/endpoint.js.map +1 -1
  131. package/package.json +4 -2
  132. package/src/derived.ts +126 -3
  133. package/src/goals/controller.ts +17 -0
  134. package/src/goals/normalize.ts +5 -0
  135. package/src/index.ts +13 -0
  136. package/src/providers/claude/attach.ts +48 -6
  137. package/src/providers/claude/index.ts +8 -0
  138. package/src/providers/claude/session.ts +15 -10
  139. package/src/providers/codex/attach.ts +48 -3
  140. package/src/providers/codex/index.ts +8 -0
  141. package/src/providers/codex/transcript.ts +5 -1
  142. package/src/providers/cursor/discovery.ts +114 -0
  143. package/src/providers/cursor/execute.ts +67 -30
  144. package/src/providers/cursor/index.ts +19 -5
  145. package/src/providers/cursor/parse.ts +1 -1
  146. package/src/providers/cursor/probe.ts +90 -0
  147. package/src/providers/cursor/runtime.ts +11 -0
  148. package/src/providers/cursor/session.ts +15 -0
  149. package/src/providers/opencode/client.ts +44 -0
  150. package/src/providers/opencode/discovery.ts +69 -0
  151. package/src/providers/opencode/execute.ts +64 -12
  152. package/src/providers/opencode/history.ts +270 -0
  153. package/src/providers/opencode/http-session.ts +319 -50
  154. package/src/providers/opencode/index.ts +26 -3
  155. package/src/providers/opencode/manager.ts +269 -0
  156. package/src/providers/opencode/modes.ts +31 -0
  157. package/src/providers/opencode/parse.ts +2 -22
  158. package/src/providers/opencode/probe.ts +113 -0
  159. package/src/providers/opencode/runtime.ts +13 -0
  160. package/src/providers/opencode/server.ts +0 -0
  161. package/src/providers/opencode/skill-config.ts +64 -0
  162. package/src/sessions/exec-backed.ts +169 -0
  163. package/src/sessions/history.ts +76 -0
  164. package/src/sessions/index.ts +3 -0
  165. package/src/types.ts +158 -3
  166. package/src/utils/auth.ts +35 -6
  167. package/src/utils/binary.ts +3 -0
  168. package/src/utils/endpoint.ts +3 -3
@@ -3,7 +3,6 @@ import type { ExecutionContext, ExecutionResult } from "../../types.js";
3
3
  import { findBinary } from "../../utils/binary.js";
4
4
  import { buildEnv, ensurePathInEnv } from "../../utils/env.js";
5
5
  import { runChildProcess, deriveErrorCode } from "../../utils/process.js";
6
- import { injectHomeSkills } from "../../utils/skills.js";
7
6
  import { resolveInstructions } from "../../utils/instructions.js";
8
7
  import { prepareWorkspace } from "../../utils/workspace.js";
9
8
  import type { PreparedWorkspace } from "../../utils/workspace.js";
@@ -14,6 +13,7 @@ import {
14
13
  isOpenCodeUnknownSessionError,
15
14
  isOpenCodeAuthRequired,
16
15
  } from "./parse.js";
16
+ import { prepareOpenCodeSkillConfig } from "./skill-config.js";
17
17
 
18
18
  export async function executeOpenCodeProvider(ctx: ExecutionContext): Promise<ExecutionResult> {
19
19
  const runId = ctx.runId ?? uuidv7();
@@ -29,6 +29,31 @@ export async function executeOpenCodeProvider(ctx: ExecutionContext): Promise<Ex
29
29
  resolvedBinary = await findBinary("opencode", config.command);
30
30
  } catch (err) {
31
31
  const errorMessage = err instanceof Error ? err.message : "Binary not found";
32
+ const completedAt = new Date().toISOString();
33
+ if (ctx.onEvent) {
34
+ try {
35
+ await ctx.onEvent({
36
+ type: "result",
37
+ text: errorMessage,
38
+ costUsd: null,
39
+ isError: true,
40
+ stopReason: null,
41
+ terminalReason: "failed",
42
+ numTurns: null,
43
+ durationMs: new Date(completedAt).getTime() - new Date(startedAt).getTime(),
44
+ timestamp: completedAt,
45
+ providerType: "opencode",
46
+ sessionId: null,
47
+ messageId: null,
48
+ eventId: null,
49
+ turnId: null,
50
+ parentToolCallId: null,
51
+ raw: {},
52
+ });
53
+ } catch {
54
+ // A host event handler cannot fail the execution.
55
+ }
56
+ }
32
57
  ctx.onLifecycle?.({ phase: "error", message: errorMessage });
33
58
  return {
34
59
  runId,
@@ -36,7 +61,7 @@ export async function executeOpenCodeProvider(ctx: ExecutionContext): Promise<Ex
36
61
  signal: null,
37
62
  status: "failed" as const,
38
63
  startedAt,
39
- completedAt: new Date().toISOString(),
64
+ completedAt,
40
65
  durationMs: Date.now() - new Date(startedAt).getTime(),
41
66
  errorMessage,
42
67
  errorCode: "binary_not_found",
@@ -59,24 +84,20 @@ export async function executeOpenCodeProvider(ctx: ExecutionContext): Promise<Ex
59
84
  }
60
85
 
61
86
  // 3. Build env
62
- const env = buildEnv(ctx.env);
63
- ensurePathInEnv(env);
87
+ const baseEnv = buildEnv(ctx.env);
88
+ ensurePathInEnv(baseEnv);
64
89
 
65
90
  // 3. Resolve instructions
66
91
  ctx.onLifecycle?.({ phase: "preparing", step: "instructions" });
67
92
  const instructions = await resolveInstructions(config.instructionsFile);
68
93
  const fullPrompt = instructions ? `${instructions}\n\n${ctx.prompt}` : ctx.prompt;
69
94
 
70
- // 4. Inject skills into ~/.claude/skills/ (OpenCode shares Claude's skills dir)
95
+ // 4. Inject skills through an isolated OpenCode config directory.
71
96
  ctx.onLifecycle?.({ phase: "preparing", step: "skills" });
72
- if (config.skillDirs && config.skillDirs.length > 0) {
73
- try {
74
- await injectHomeSkills(config.skillDirs, "opencode");
75
- } catch {
76
- // Non-fatal
77
- }
78
- }
97
+ const skillConfig = await prepareOpenCodeSkillConfig(baseEnv, config.skillDirs);
98
+ const env = skillConfig.env;
79
99
 
100
+ try {
80
101
  // 4. Determine session resume
81
102
  const sessionParams = ctx.sessionParams ?? null;
82
103
  const sessionId = (() => {
@@ -96,6 +117,9 @@ export async function executeOpenCodeProvider(ctx: ExecutionContext): Promise<Ex
96
117
  const args = [...resolvedBinary.prefixArgs, "run", "--format", "json"];
97
118
  if (resumeSessionId) args.push("--session", resumeSessionId);
98
119
  if (model) args.push("--model", model);
120
+ if (config.modelVariant) args.push("--variant", config.modelVariant);
121
+ const agent = config.planMode ? "plan" : (config.modeId ?? config.mode);
122
+ if (agent) args.push("--agent", agent);
99
123
  if (config.extraArgs) args.push(...config.extraArgs);
100
124
  return args;
101
125
  };
@@ -217,6 +241,31 @@ export async function executeOpenCodeProvider(ctx: ExecutionContext): Promise<Ex
217
241
  : (errorCode || errorMessage) ? "failed" as const
218
242
  : "completed" as const;
219
243
 
244
+ if (ctx.onEvent) {
245
+ try {
246
+ await ctx.onEvent({
247
+ type: "result",
248
+ text: parsed.summary ?? errorMessage ?? "",
249
+ costUsd: parsed.costUsd,
250
+ isError: status !== "completed",
251
+ stopReason: null,
252
+ terminalReason: status,
253
+ numTurns: null,
254
+ durationMs: new Date(completedAt).getTime() - new Date(startedAt).getTime(),
255
+ timestamp: completedAt,
256
+ providerType: "opencode",
257
+ sessionId: resolvedSessionId,
258
+ messageId: null,
259
+ eventId: null,
260
+ turnId: null,
261
+ parentToolCallId: null,
262
+ raw: {},
263
+ });
264
+ } catch {
265
+ // A host event handler cannot fail the execution.
266
+ }
267
+ }
268
+
220
269
  if (status === "completed") {
221
270
  ctx.onLifecycle?.({ phase: "completed" });
222
271
  } else if (status === "aborted") {
@@ -248,4 +297,7 @@ export async function executeOpenCodeProvider(ctx: ExecutionContext): Promise<Ex
248
297
  raw: null,
249
298
  workspace,
250
299
  };
300
+ } finally {
301
+ await skillConfig.cleanup();
302
+ }
251
303
  }
@@ -0,0 +1,270 @@
1
+ import type {
2
+ AttachOptions,
3
+ HistoryAttachment,
4
+ HistoryCatchUpOptions,
5
+ HistoryCatchUpYield,
6
+ HistoryCheckpoint,
7
+ LastTurnStatus,
8
+ SessionRecord,
9
+ StreamEvent,
10
+ } from "../../types.js";
11
+ import { assertSessionRecord, createSessionRecord, MalformedSessionRecordError } from "../../sessions/record.js";
12
+ import { acquireOpenCodeRuntime } from "./runtime.js";
13
+ import { opencodeSessionCodec } from "./codec.js";
14
+ import { createOpenCodeSession } from "./http-session.js";
15
+ import { mapOpenCodePart, mapOpenCodeToolCall, type OcBaseInfo } from "./event-parse.js";
16
+
17
+ const PAGE_SIZE = 100;
18
+ const MAX_PAGES = 100;
19
+ const MAX_MESSAGES = 10_000;
20
+ const MAX_BYTES = 25 * 1024 * 1024;
21
+ const CHECKPOINT_KIND = "opencode:message-part:v1";
22
+
23
+ export class OpenCodeHistoryCheckpointNotFoundError extends Error {
24
+ readonly code = "history_checkpoint_not_found";
25
+ constructor() {
26
+ super("The OpenCode history checkpoint is no longer present");
27
+ this.name = "OpenCodeHistoryCheckpointNotFoundError";
28
+ }
29
+ }
30
+
31
+ export class OpenCodeHistoryResyncLimitError extends Error {
32
+ readonly code = "history_resync_limit";
33
+ constructor() {
34
+ super("OpenCode history exceeded the bounded resync limit");
35
+ this.name = "OpenCodeHistoryResyncLimitError";
36
+ }
37
+ }
38
+
39
+ interface DecodedCheckpoint {
40
+ messageId: string;
41
+ partId: string;
42
+ ordinal: number | null;
43
+ }
44
+
45
+ export interface MessageEnvelope {
46
+ info?: Record<string, unknown>;
47
+ parts?: unknown[];
48
+ }
49
+
50
+ function rec(value: unknown): Record<string, unknown> | null {
51
+ return value && typeof value === "object" && !Array.isArray(value)
52
+ ? value as Record<string, unknown>
53
+ : null;
54
+ }
55
+
56
+ function string(value: unknown): string | null {
57
+ return typeof value === "string" && value.length > 0 ? value : null;
58
+ }
59
+
60
+ function decodeCheckpoint(checkpoint: HistoryCheckpoint | undefined): DecodedCheckpoint | null {
61
+ if (!checkpoint) return null;
62
+ if (checkpoint.kind !== CHECKPOINT_KIND) throw new OpenCodeHistoryCheckpointNotFoundError();
63
+ const value = rec(checkpoint.value);
64
+ const messageId = string(value?.["messageId"]);
65
+ const partId = string(value?.["partId"]);
66
+ if (!messageId || !partId) throw new OpenCodeHistoryCheckpointNotFoundError();
67
+ const ordinal = typeof value?.["ordinal"] === "number" && Number.isInteger(value["ordinal"])
68
+ ? value["ordinal"] as number
69
+ : null;
70
+ return { messageId, partId, ordinal };
71
+ }
72
+
73
+ function checkpoint(messageId: string, partId: string, ordinal: number): HistoryCheckpoint {
74
+ return { kind: CHECKPOINT_KIND, value: { messageId, partId, ordinal } };
75
+ }
76
+
77
+ export function historicalEvents(message: MessageEnvelope, sessionId: string): Array<{ event: StreamEvent; partId: string }> {
78
+ const info = message.info ?? {};
79
+ if (info["role"] !== "assistant") return [];
80
+ const messageId = string(info["id"]);
81
+ const timestamp = new Date(
82
+ typeof rec(info["time"])?.["created"] === "number"
83
+ ? rec(info["time"])!["created"] as number
84
+ : Date.now(),
85
+ ).toISOString();
86
+ const base: OcBaseInfo = { provider: "opencode", sessionId, timestamp };
87
+ const events: Array<{ event: StreamEvent; partId: string }> = [];
88
+ for (const rawPart of message.parts ?? []) {
89
+ const part = rec(rawPart);
90
+ const partId = string(part?.["id"]);
91
+ if (!part || !partId) continue;
92
+ if (part["type"] === "tool") {
93
+ events.push({ event: mapOpenCodeToolCall(part, base), partId });
94
+ const result = mapOpenCodePart(part, base);
95
+ if (result?.type === "tool_result") events.push({ event: result, partId });
96
+ continue;
97
+ }
98
+ const event = mapOpenCodePart(part, base);
99
+ if (event) events.push({ event, partId });
100
+ }
101
+ const lastPartId = events.at(-1)?.partId;
102
+ if (messageId && lastPartId) {
103
+ const error = info["error"] != null;
104
+ events.push({
105
+ partId: lastPartId,
106
+ event: {
107
+ type: "result",
108
+ text: "",
109
+ costUsd: typeof info["cost"] === "number" ? info["cost"] : null,
110
+ isError: error,
111
+ stopReason: null,
112
+ terminalReason: error ? "failed" : "completed",
113
+ numTurns: 1,
114
+ durationMs: null,
115
+ timestamp,
116
+ providerType: "opencode",
117
+ sessionId,
118
+ messageId,
119
+ eventId: `${messageId}:result`,
120
+ turnId: null,
121
+ parentToolCallId: null,
122
+ raw: info,
123
+ },
124
+ });
125
+ }
126
+ return events;
127
+ }
128
+
129
+ export async function collectHistory(
130
+ client: import("./client.js").OpenCodeClient,
131
+ sessionId: string,
132
+ options: HistoryCatchUpOptions | undefined,
133
+ ): Promise<Array<{ event: StreamEvent; checkpoint: HistoryCheckpoint; eventId: string | null }>> {
134
+ const after = options?.mode === "bounded_full_resync" ? null : decodeCheckpoint(options?.after);
135
+ const pages: MessageEnvelope[][] = [];
136
+ let before: string | null = null;
137
+ let foundMessage = after === null;
138
+ let collectedMessages = 0;
139
+ let collectedBytes = 0;
140
+
141
+ for (let pageIndex = 0; pageIndex < MAX_PAGES; pageIndex += 1) {
142
+ const query = new URLSearchParams({ limit: String(PAGE_SIZE) });
143
+ if (before) query.set("before", before);
144
+ const response = await client.request(`/session/${encodeURIComponent(sessionId)}/message?${query}`);
145
+ if (response.status === 404) return [];
146
+ if (!response.ok) throw new Error(`OpenCode history request failed (${response.status})`);
147
+ const text = await response.text();
148
+ collectedBytes += Buffer.byteLength(text);
149
+ const parsed = JSON.parse(text) as unknown;
150
+ const page = Array.isArray(parsed)
151
+ ? parsed.map((value) => rec(value) as MessageEnvelope).filter(Boolean)
152
+ : [];
153
+ collectedMessages += page.length;
154
+ if (collectedBytes > MAX_BYTES || collectedMessages > MAX_MESSAGES) {
155
+ throw new OpenCodeHistoryResyncLimitError();
156
+ }
157
+ pages.push(page);
158
+ if (after && page.some((message) => string(message.info?.["id"]) === after.messageId)) {
159
+ foundMessage = true;
160
+ break;
161
+ }
162
+ if (page.length < PAGE_SIZE) break;
163
+ const next = response.headers.get("x-next-cursor");
164
+ if (!next || next === before) break;
165
+ before = next;
166
+ }
167
+
168
+ if (!foundMessage) throw new OpenCodeHistoryCheckpointNotFoundError();
169
+ if (pages.length === MAX_PAGES && pages.at(-1)?.length === PAGE_SIZE) {
170
+ throw new OpenCodeHistoryResyncLimitError();
171
+ }
172
+
173
+ const messages = pages.reverse().flat();
174
+ const output: Array<{ event: StreamEvent; checkpoint: HistoryCheckpoint; eventId: string | null }> = [];
175
+ let passedCheckpoint = after === null;
176
+ let foundPart = after === null;
177
+ for (const message of messages) {
178
+ const messageId = string(message.info?.["id"]);
179
+ if (!messageId) continue;
180
+ const events = historicalEvents(message, sessionId);
181
+ const partOrdinals = new Map<string, number>();
182
+ for (const item of events) {
183
+ const ordinal = partOrdinals.get(item.partId) ?? 0;
184
+ partOrdinals.set(item.partId, ordinal + 1);
185
+ if (!passedCheckpoint) {
186
+ if (
187
+ foundPart
188
+ && after!.ordinal === null
189
+ && messageId === after!.messageId
190
+ && item.partId === after!.partId
191
+ ) {
192
+ continue;
193
+ }
194
+ if (
195
+ messageId === after!.messageId
196
+ && item.partId === after!.partId
197
+ && (after!.ordinal === null || ordinal === after!.ordinal)
198
+ ) {
199
+ foundPart = true;
200
+ continue;
201
+ }
202
+ if (foundPart) passedCheckpoint = true;
203
+ else continue;
204
+ }
205
+ output.push({
206
+ event: item.event,
207
+ checkpoint: checkpoint(messageId, item.partId, ordinal),
208
+ eventId: item.event.eventId,
209
+ });
210
+ }
211
+ if (foundPart && messageId === after?.messageId) passedCheckpoint = true;
212
+ }
213
+ if (!foundPart) throw new OpenCodeHistoryCheckpointNotFoundError();
214
+ return output;
215
+ }
216
+
217
+ export async function attachOpenCodeHistory(
218
+ record: SessionRecord,
219
+ opts?: AttachOptions,
220
+ ): Promise<HistoryAttachment> {
221
+ assertSessionRecord(record);
222
+ if (record.providerType !== "opencode") {
223
+ throw new MalformedSessionRecordError(
224
+ `opencode history attach requires providerType "opencode"; got ${JSON.stringify(record.providerType)}`,
225
+ "providerType",
226
+ );
227
+ }
228
+ const params = opencodeSessionCodec.deserialize(record.params);
229
+ if (!params) throw new MalformedSessionRecordError("opencode history record has no session id", "params");
230
+ const sessionId = params["sessionId"] as string;
231
+ const cwd = typeof params["cwd"] === "string" ? params["cwd"] : record.cwd ?? process.cwd();
232
+ const runtime = await acquireOpenCodeRuntime({ cwd, env: opts?.env, config: opts?.config });
233
+ let lastTurn: LastTurnStatus = "unknown";
234
+ try {
235
+ const response = await runtime.server.client.request(`/session/${encodeURIComponent(sessionId)}/message?limit=1`);
236
+ if (response.ok) {
237
+ const messages = await response.json() as MessageEnvelope[];
238
+ const info = messages.at(-1)?.info;
239
+ lastTurn = info
240
+ ? (info["finish"] && !info["error"] ? "completed" : "interrupted")
241
+ : "unknown";
242
+ }
243
+ } catch {
244
+ lastTurn = "unknown";
245
+ }
246
+ const normalized = createSessionRecord({
247
+ providerType: "opencode",
248
+ params: { sessionId, cwd },
249
+ cwd,
250
+ displayId: sessionId,
251
+ });
252
+ return {
253
+ record: normalized,
254
+ historySource: { kind: "service", description: "OpenCode authenticated session message history" },
255
+ lastTurn,
256
+ catchUp(options) {
257
+ return {
258
+ async *[Symbol.asyncIterator](): AsyncIterator<HistoryCatchUpYield> {
259
+ for (const yielded of await collectHistory(runtime.server.client, sessionId, options)) yield yielded;
260
+ },
261
+ };
262
+ },
263
+ resume(ctx) {
264
+ return createOpenCodeSession({ ...ctx, cwd: ctx?.cwd ?? cwd, sessionParams: params });
265
+ },
266
+ async close() {
267
+ runtime.server.release();
268
+ },
269
+ };
270
+ }