@agentex/agent 0.0.30 → 0.0.31
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/CHANGELOG.md +44 -0
- package/README.md +71 -2
- package/dist/derived.d.ts.map +1 -1
- package/dist/derived.js +35 -0
- package/dist/derived.js.map +1 -1
- package/dist/history/index.d.ts +1 -1
- package/dist/history/index.d.ts.map +1 -1
- package/dist/history/types.d.ts +74 -1
- package/dist/history/types.d.ts.map +1 -1
- package/dist/history/types.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/providers/opencode/history.d.ts +22 -8
- package/dist/providers/opencode/history.d.ts.map +1 -1
- package/dist/providers/opencode/history.js +119 -18
- package/dist/providers/opencode/history.js.map +1 -1
- package/dist/providers/opencode/index.d.ts.map +1 -1
- package/dist/providers/opencode/index.js +17 -0
- package/dist/providers/opencode/index.js.map +1 -1
- package/dist/providers/opencode/probe.d.ts.map +1 -1
- package/dist/providers/opencode/probe.js +2 -0
- package/dist/providers/opencode/probe.js.map +1 -1
- package/dist/providers/opencode/saved-history.d.ts +20 -0
- package/dist/providers/opencode/saved-history.d.ts.map +1 -0
- package/dist/providers/opencode/saved-history.js +361 -0
- package/dist/providers/opencode/saved-history.js.map +1 -0
- package/dist/types.d.ts +4 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
- package/src/derived.ts +39 -0
- package/src/history/index.ts +10 -0
- package/src/history/types.ts +90 -1
- package/src/index.ts +11 -1
- package/src/providers/opencode/history.ts +156 -23
- package/src/providers/opencode/index.ts +17 -0
- package/src/providers/opencode/probe.ts +4 -0
- package/src/providers/opencode/saved-history.ts +467 -0
- package/src/types.ts +4 -0
package/src/history/types.ts
CHANGED
|
@@ -1,4 +1,93 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
BaseStreamEventFields,
|
|
3
|
+
HistoryCheckpoint,
|
|
4
|
+
ProviderRuntimeContext,
|
|
5
|
+
StreamEvent,
|
|
6
|
+
} from "../types.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Provider-neutral discovery options for persisted sessions. Unlike
|
|
10
|
+
* `LocalHistoryDiscoverOptions`, these options do not assume a filesystem
|
|
11
|
+
* source. Providers may satisfy them through a local authenticated service.
|
|
12
|
+
*/
|
|
13
|
+
export interface SavedHistoryDiscoverOptions extends ProviderRuntimeContext {
|
|
14
|
+
/** Optional provider-session directory filter. `cwd` is runtime context only. */
|
|
15
|
+
directory?: string;
|
|
16
|
+
includeArchived?: boolean;
|
|
17
|
+
mainSessionsOnly?: boolean;
|
|
18
|
+
requireUserMessage?: boolean;
|
|
19
|
+
limit?: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface SavedHistoryProbeOptions extends ProviderRuntimeContext {
|
|
23
|
+
limit?: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface SavedHistoryProbeResult {
|
|
27
|
+
providerType: string;
|
|
28
|
+
/** The provider-owned history source could be reached. */
|
|
29
|
+
sourceAvailable: boolean;
|
|
30
|
+
historyAvailable: boolean;
|
|
31
|
+
/** A bounded source count, not necessarily an eligible-session count. */
|
|
32
|
+
approximateCount?: number;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type SavedHistoryArchiveState = "active" | "archived" | "unknown";
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Serializable metadata for a provider-owned persisted session. Storage
|
|
39
|
+
* details such as transcript paths, byte offsets, and database layouts are
|
|
40
|
+
* deliberately absent.
|
|
41
|
+
*/
|
|
42
|
+
export interface SavedHistorySession {
|
|
43
|
+
version: 1;
|
|
44
|
+
providerType: string;
|
|
45
|
+
externalSessionId: string;
|
|
46
|
+
cwd: string | null;
|
|
47
|
+
title: string | null;
|
|
48
|
+
startedAt: string | null;
|
|
49
|
+
updatedAt: string;
|
|
50
|
+
branch: string | null;
|
|
51
|
+
gitOriginUrl: string | null;
|
|
52
|
+
archiveState: SavedHistoryArchiveState;
|
|
53
|
+
hasUserMessage: boolean;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export type SavedHistoryUserEvent = {
|
|
57
|
+
type: "user";
|
|
58
|
+
text: string;
|
|
59
|
+
} & BaseStreamEventFields;
|
|
60
|
+
|
|
61
|
+
export type SavedHistoryEvent = StreamEvent | SavedHistoryUserEvent;
|
|
62
|
+
|
|
63
|
+
export interface SavedHistoryYield {
|
|
64
|
+
event: SavedHistoryEvent & { eventId: string };
|
|
65
|
+
/** Opaque provider-owned bookmark. Persist only after the event commits. */
|
|
66
|
+
checkpoint: HistoryCheckpoint;
|
|
67
|
+
eventId: string;
|
|
68
|
+
/** Disambiguates normalized events that share one provider source part. */
|
|
69
|
+
partIndex: number;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface SavedHistoryReadOptions extends ProviderRuntimeContext {
|
|
73
|
+
after?: HistoryCheckpoint;
|
|
74
|
+
mode?: "incremental" | "bounded_full_resync";
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Discover and read provider-owned saved sessions when their ids are not yet
|
|
79
|
+
* known. This is the provider-neutral import/synchronization surface. It is
|
|
80
|
+
* separate from `attachHistory`, which starts from a known `SessionRecord`,
|
|
81
|
+
* and from the file-specific `LocalHistoryOps` compatibility API.
|
|
82
|
+
*/
|
|
83
|
+
export interface SavedHistoryOps {
|
|
84
|
+
probe(options?: SavedHistoryProbeOptions): Promise<SavedHistoryProbeResult>;
|
|
85
|
+
discover(options?: SavedHistoryDiscoverOptions): AsyncIterable<SavedHistorySession>;
|
|
86
|
+
read(
|
|
87
|
+
session: SavedHistorySession,
|
|
88
|
+
options?: SavedHistoryReadOptions,
|
|
89
|
+
): AsyncIterable<SavedHistoryYield>;
|
|
90
|
+
}
|
|
2
91
|
|
|
3
92
|
export interface LocalHistoryDiscoverOptions {
|
|
4
93
|
includeArchived?: boolean;
|
package/src/index.ts
CHANGED
|
@@ -206,9 +206,19 @@ export type {
|
|
|
206
206
|
GoalSentinelContext,
|
|
207
207
|
} from "./types.js";
|
|
208
208
|
|
|
209
|
-
//
|
|
209
|
+
// Persisted session discovery. Runtime errors are exported from the
|
|
210
210
|
// `@agentex/agent/history` subpath to keep the root barrel lazy.
|
|
211
211
|
export type {
|
|
212
|
+
SavedHistoryArchiveState,
|
|
213
|
+
SavedHistoryDiscoverOptions,
|
|
214
|
+
SavedHistoryEvent,
|
|
215
|
+
SavedHistoryOps,
|
|
216
|
+
SavedHistoryProbeOptions,
|
|
217
|
+
SavedHistoryProbeResult,
|
|
218
|
+
SavedHistoryReadOptions,
|
|
219
|
+
SavedHistorySession,
|
|
220
|
+
SavedHistoryUserEvent,
|
|
221
|
+
SavedHistoryYield,
|
|
212
222
|
LocalHistoryArchiveState,
|
|
213
223
|
LocalHistoryDiscoverOptions,
|
|
214
224
|
LocalHistoryErrorCode,
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
1
3
|
import type {
|
|
2
4
|
AttachOptions,
|
|
3
5
|
HistoryAttachment,
|
|
@@ -6,8 +8,9 @@ import type {
|
|
|
6
8
|
HistoryCheckpoint,
|
|
7
9
|
LastTurnStatus,
|
|
8
10
|
SessionRecord,
|
|
9
|
-
StreamEvent,
|
|
10
11
|
} from "../../types.js";
|
|
12
|
+
import type { SavedHistoryEvent, SavedHistoryUserEvent } from "../../history/types.js";
|
|
13
|
+
import { meaningfulHumanText } from "../../history/fs.js";
|
|
11
14
|
import { assertSessionRecord, createSessionRecord, MalformedSessionRecordError } from "../../sessions/record.js";
|
|
12
15
|
import { acquireOpenCodeRuntime } from "./runtime.js";
|
|
13
16
|
import { opencodeSessionCodec } from "./codec.js";
|
|
@@ -18,7 +21,7 @@ const PAGE_SIZE = 100;
|
|
|
18
21
|
const MAX_PAGES = 100;
|
|
19
22
|
const MAX_MESSAGES = 10_000;
|
|
20
23
|
const MAX_BYTES = 25 * 1024 * 1024;
|
|
21
|
-
const CHECKPOINT_KIND = "opencode:message-part:
|
|
24
|
+
const CHECKPOINT_KIND = "opencode:message-part:v2";
|
|
22
25
|
|
|
23
26
|
export class OpenCodeHistoryCheckpointNotFoundError extends Error {
|
|
24
27
|
readonly code = "history_checkpoint_not_found";
|
|
@@ -36,17 +39,39 @@ export class OpenCodeHistoryResyncLimitError extends Error {
|
|
|
36
39
|
}
|
|
37
40
|
}
|
|
38
41
|
|
|
42
|
+
export class OpenCodeHistorySourceMissingError extends Error {
|
|
43
|
+
readonly code = "source_missing";
|
|
44
|
+
|
|
45
|
+
constructor(sessionId: string) {
|
|
46
|
+
super(`OpenCode saved session ${JSON.stringify(sessionId)} no longer exists`);
|
|
47
|
+
this.name = "OpenCodeHistorySourceMissingError";
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
39
51
|
interface DecodedCheckpoint {
|
|
40
52
|
messageId: string;
|
|
41
53
|
partId: string;
|
|
42
54
|
ordinal: number | null;
|
|
55
|
+
messageRevision: string;
|
|
43
56
|
}
|
|
44
57
|
|
|
45
|
-
export interface MessageEnvelope {
|
|
58
|
+
export interface MessageEnvelope extends Record<string, unknown> {
|
|
46
59
|
info?: Record<string, unknown>;
|
|
47
60
|
parts?: unknown[];
|
|
48
61
|
}
|
|
49
62
|
|
|
63
|
+
interface HistoricalEvent {
|
|
64
|
+
event: SavedHistoryEvent;
|
|
65
|
+
partId: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface CollectedHistoryEvent {
|
|
69
|
+
event: SavedHistoryEvent;
|
|
70
|
+
checkpoint: HistoryCheckpoint;
|
|
71
|
+
eventId: string | null;
|
|
72
|
+
partIndex: number;
|
|
73
|
+
}
|
|
74
|
+
|
|
50
75
|
function rec(value: unknown): Record<string, unknown> | null {
|
|
51
76
|
return value && typeof value === "object" && !Array.isArray(value)
|
|
52
77
|
? value as Record<string, unknown>
|
|
@@ -57,35 +82,118 @@ function string(value: unknown): string | null {
|
|
|
57
82
|
return typeof value === "string" && value.length > 0 ? value : null;
|
|
58
83
|
}
|
|
59
84
|
|
|
85
|
+
function historicalTimestamp(info: Record<string, unknown>): string {
|
|
86
|
+
const created = rec(info["time"])?.["created"];
|
|
87
|
+
if (typeof created === "number" && Number.isFinite(created)) {
|
|
88
|
+
const timestamp = new Date(created);
|
|
89
|
+
if (!Number.isNaN(timestamp.getTime())) return timestamp.toISOString();
|
|
90
|
+
}
|
|
91
|
+
return new Date(0).toISOString();
|
|
92
|
+
}
|
|
93
|
+
|
|
60
94
|
function decodeCheckpoint(checkpoint: HistoryCheckpoint | undefined): DecodedCheckpoint | null {
|
|
61
95
|
if (!checkpoint) return null;
|
|
62
96
|
if (checkpoint.kind !== CHECKPOINT_KIND) throw new OpenCodeHistoryCheckpointNotFoundError();
|
|
63
97
|
const value = rec(checkpoint.value);
|
|
64
98
|
const messageId = string(value?.["messageId"]);
|
|
65
99
|
const partId = string(value?.["partId"]);
|
|
66
|
-
|
|
100
|
+
const messageRevision = string(value?.["messageRevision"]);
|
|
101
|
+
if (!messageId || !partId || !messageRevision) throw new OpenCodeHistoryCheckpointNotFoundError();
|
|
67
102
|
const ordinal = typeof value?.["ordinal"] === "number" && Number.isInteger(value["ordinal"])
|
|
68
103
|
? value["ordinal"] as number
|
|
69
104
|
: null;
|
|
70
|
-
return { messageId, partId, ordinal };
|
|
105
|
+
return { messageId, partId, ordinal, messageRevision };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function checkpoint(
|
|
109
|
+
messageId: string,
|
|
110
|
+
partId: string,
|
|
111
|
+
ordinal: number,
|
|
112
|
+
messageRevision: string,
|
|
113
|
+
): HistoryCheckpoint {
|
|
114
|
+
return {
|
|
115
|
+
kind: CHECKPOINT_KIND,
|
|
116
|
+
value: { messageId, partId, ordinal, messageRevision },
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function canonicalJson(value: unknown): unknown {
|
|
121
|
+
if (Array.isArray(value)) return value.map(canonicalJson);
|
|
122
|
+
const record = rec(value);
|
|
123
|
+
if (record) {
|
|
124
|
+
return Object.fromEntries(
|
|
125
|
+
Object.keys(record).sort().map((key) => [key, canonicalJson(record[key])]),
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
return value;
|
|
71
129
|
}
|
|
72
130
|
|
|
73
|
-
function
|
|
74
|
-
return
|
|
131
|
+
function messageRevision(message: MessageEnvelope): string {
|
|
132
|
+
return createHash("sha256")
|
|
133
|
+
.update(JSON.stringify(canonicalJson(message)))
|
|
134
|
+
.digest("hex");
|
|
75
135
|
}
|
|
76
136
|
|
|
77
|
-
|
|
137
|
+
function userTextPart(part: Record<string, unknown>): string | null {
|
|
138
|
+
if (part["synthetic"] === true || part["ignored"] === true) return null;
|
|
139
|
+
if (part["type"] === "text") return string(part["text"]);
|
|
140
|
+
if (part["type"] === "subtask") return string(part["prompt"]);
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function historicalUserEvent(
|
|
145
|
+
message: MessageEnvelope,
|
|
146
|
+
sessionId: string,
|
|
147
|
+
timestamp: string,
|
|
148
|
+
): HistoricalEvent | null {
|
|
78
149
|
const info = message.info ?? {};
|
|
79
|
-
if (info["role"] !== "assistant") return [];
|
|
80
150
|
const messageId = string(info["id"]);
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
151
|
+
const texts: string[] = [];
|
|
152
|
+
let partId: string | null = null;
|
|
153
|
+
for (const rawPart of Array.isArray(message.parts) ? message.parts : []) {
|
|
154
|
+
const part = rec(rawPart);
|
|
155
|
+
const text = part ? userTextPart(part) : null;
|
|
156
|
+
if (!part || !text) continue;
|
|
157
|
+
const candidatePartId = string(part["id"]);
|
|
158
|
+
if (!candidatePartId) continue;
|
|
159
|
+
texts.push(text);
|
|
160
|
+
partId = candidatePartId;
|
|
161
|
+
}
|
|
162
|
+
const text = meaningfulHumanText(texts.join("\n\n"));
|
|
163
|
+
if (!text || !partId) return null;
|
|
164
|
+
const eventId = messageId ?? partId;
|
|
165
|
+
const event: SavedHistoryUserEvent = {
|
|
166
|
+
type: "user",
|
|
167
|
+
text,
|
|
168
|
+
timestamp,
|
|
169
|
+
providerType: "opencode",
|
|
170
|
+
sessionId,
|
|
171
|
+
messageId,
|
|
172
|
+
eventId,
|
|
173
|
+
turnId: null,
|
|
174
|
+
parentToolCallId: null,
|
|
175
|
+
raw: message,
|
|
176
|
+
};
|
|
177
|
+
return { event, partId };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function historicalEvents(
|
|
181
|
+
message: MessageEnvelope,
|
|
182
|
+
sessionId: string,
|
|
183
|
+
options: { includeUserMessages?: boolean } = {},
|
|
184
|
+
): HistoricalEvent[] {
|
|
185
|
+
const info = rec(message.info) ?? {};
|
|
186
|
+
const messageId = string(info["id"]);
|
|
187
|
+
const timestamp = historicalTimestamp(info);
|
|
188
|
+
if (info["role"] === "user") {
|
|
189
|
+
if (!options.includeUserMessages) return [];
|
|
190
|
+
const user = historicalUserEvent(message, sessionId, timestamp);
|
|
191
|
+
return user ? [user] : [];
|
|
192
|
+
}
|
|
193
|
+
if (info["role"] !== "assistant") return [];
|
|
86
194
|
const base: OcBaseInfo = { provider: "opencode", sessionId, timestamp };
|
|
87
|
-
const events:
|
|
88
|
-
for (const rawPart of message.parts
|
|
195
|
+
const events: HistoricalEvent[] = [];
|
|
196
|
+
for (const rawPart of Array.isArray(message.parts) ? message.parts : []) {
|
|
89
197
|
const part = rec(rawPart);
|
|
90
198
|
const partId = string(part?.["id"]);
|
|
91
199
|
if (!part || !partId) continue;
|
|
@@ -99,7 +207,8 @@ export function historicalEvents(message: MessageEnvelope, sessionId: string): A
|
|
|
99
207
|
if (event) events.push({ event, partId });
|
|
100
208
|
}
|
|
101
209
|
const lastPartId = events.at(-1)?.partId;
|
|
102
|
-
|
|
210
|
+
const finished = string(info["finish"]) !== null || info["error"] != null;
|
|
211
|
+
if (messageId && lastPartId && finished) {
|
|
103
212
|
const error = info["error"] != null;
|
|
104
213
|
events.push({
|
|
105
214
|
partId: lastPartId,
|
|
@@ -130,7 +239,11 @@ export async function collectHistory(
|
|
|
130
239
|
client: import("./client.js").OpenCodeClient,
|
|
131
240
|
sessionId: string,
|
|
132
241
|
options: HistoryCatchUpOptions | undefined,
|
|
133
|
-
|
|
242
|
+
mapping: {
|
|
243
|
+
includeUserMessages?: boolean;
|
|
244
|
+
missingSession?: "empty" | "error";
|
|
245
|
+
} = {},
|
|
246
|
+
): Promise<CollectedHistoryEvent[]> {
|
|
134
247
|
const after = options?.mode === "bounded_full_resync" ? null : decodeCheckpoint(options?.after);
|
|
135
248
|
const pages: MessageEnvelope[][] = [];
|
|
136
249
|
let before: string | null = null;
|
|
@@ -142,7 +255,12 @@ export async function collectHistory(
|
|
|
142
255
|
const query = new URLSearchParams({ limit: String(PAGE_SIZE) });
|
|
143
256
|
if (before) query.set("before", before);
|
|
144
257
|
const response = await client.request(`/session/${encodeURIComponent(sessionId)}/message?${query}`);
|
|
145
|
-
if (response.status === 404)
|
|
258
|
+
if (response.status === 404) {
|
|
259
|
+
if (mapping.missingSession === "error") {
|
|
260
|
+
throw new OpenCodeHistorySourceMissingError(sessionId);
|
|
261
|
+
}
|
|
262
|
+
return [];
|
|
263
|
+
}
|
|
146
264
|
if (!response.ok) throw new Error(`OpenCode history request failed (${response.status})`);
|
|
147
265
|
const text = await response.text();
|
|
148
266
|
collectedBytes += Buffer.byteLength(text);
|
|
@@ -171,13 +289,21 @@ export async function collectHistory(
|
|
|
171
289
|
}
|
|
172
290
|
|
|
173
291
|
const messages = pages.reverse().flat();
|
|
174
|
-
const output:
|
|
292
|
+
const output: CollectedHistoryEvent[] = [];
|
|
175
293
|
let passedCheckpoint = after === null;
|
|
176
294
|
let foundPart = after === null;
|
|
177
295
|
for (const message of messages) {
|
|
178
296
|
const messageId = string(message.info?.["id"]);
|
|
179
297
|
if (!messageId) continue;
|
|
180
|
-
const
|
|
298
|
+
const revision = messageRevision(message);
|
|
299
|
+
if (
|
|
300
|
+
after
|
|
301
|
+
&& messageId === after.messageId
|
|
302
|
+
&& revision !== after.messageRevision
|
|
303
|
+
) {
|
|
304
|
+
throw new OpenCodeHistoryCheckpointNotFoundError();
|
|
305
|
+
}
|
|
306
|
+
const events = historicalEvents(message, sessionId, mapping);
|
|
181
307
|
const partOrdinals = new Map<string, number>();
|
|
182
308
|
for (const item of events) {
|
|
183
309
|
const ordinal = partOrdinals.get(item.partId) ?? 0;
|
|
@@ -204,8 +330,9 @@ export async function collectHistory(
|
|
|
204
330
|
}
|
|
205
331
|
output.push({
|
|
206
332
|
event: item.event,
|
|
207
|
-
checkpoint: checkpoint(messageId, item.partId, ordinal),
|
|
333
|
+
checkpoint: checkpoint(messageId, item.partId, ordinal, revision),
|
|
208
334
|
eventId: item.event.eventId,
|
|
335
|
+
partIndex: ordinal,
|
|
209
336
|
});
|
|
210
337
|
}
|
|
211
338
|
if (foundPart && messageId === after?.messageId) passedCheckpoint = true;
|
|
@@ -256,7 +383,13 @@ export async function attachOpenCodeHistory(
|
|
|
256
383
|
catchUp(options) {
|
|
257
384
|
return {
|
|
258
385
|
async *[Symbol.asyncIterator](): AsyncIterator<HistoryCatchUpYield> {
|
|
259
|
-
for (const yielded of await collectHistory(runtime.server.client, sessionId, options))
|
|
386
|
+
for (const yielded of await collectHistory(runtime.server.client, sessionId, options)) {
|
|
387
|
+
// Known-session catch-up deliberately excludes user prompts. The
|
|
388
|
+
// host already owns them. `savedHistory.read()` opts in to users
|
|
389
|
+
// when importing a conversation the host did not create.
|
|
390
|
+
const event = yielded.event;
|
|
391
|
+
if (event.type !== "user") yield { ...yielded, event };
|
|
392
|
+
}
|
|
260
393
|
},
|
|
261
394
|
};
|
|
262
395
|
},
|
|
@@ -20,6 +20,7 @@ export const opencodeProvider: ProviderModule = {
|
|
|
20
20
|
modes: true,
|
|
21
21
|
goals: EMULATED_GOAL_CAPABILITY,
|
|
22
22
|
durableHistory: true,
|
|
23
|
+
savedHistory: true,
|
|
23
24
|
resume: true,
|
|
24
25
|
modelVariants: true,
|
|
25
26
|
permissionRequests: true,
|
|
@@ -50,4 +51,20 @@ export const opencodeProvider: ProviderModule = {
|
|
|
50
51
|
disconnect: (providerId, ctx) => import("./manager.js").then((m) => m.openCodeUpstreamProviders.disconnect(providerId, ctx)),
|
|
51
52
|
},
|
|
52
53
|
attachHistory: (record, options) => import("./history.js").then((m) => m.attachOpenCodeHistory(record, options)),
|
|
54
|
+
savedHistory: {
|
|
55
|
+
probe: (options) => import("./saved-history.js")
|
|
56
|
+
.then((module) => module.openCodeSavedHistory.probe(options)),
|
|
57
|
+
discover: (options) => ({
|
|
58
|
+
async *[Symbol.asyncIterator]() {
|
|
59
|
+
const module = await import("./saved-history.js");
|
|
60
|
+
yield* module.openCodeSavedHistory.discover(options);
|
|
61
|
+
},
|
|
62
|
+
}),
|
|
63
|
+
read: (session, options) => ({
|
|
64
|
+
async *[Symbol.asyncIterator]() {
|
|
65
|
+
const module = await import("./saved-history.js");
|
|
66
|
+
yield* module.openCodeSavedHistory.read(session, options);
|
|
67
|
+
},
|
|
68
|
+
}),
|
|
69
|
+
},
|
|
53
70
|
};
|
|
@@ -74,6 +74,10 @@ export async function probeOpenCodeCapabilities(
|
|
|
74
74
|
questionRequests: capability(has("/question", "get") && has("/question/{requestID}/reply", "post")),
|
|
75
75
|
upstreamProviderDisconnect: capability(supportsDisconnect),
|
|
76
76
|
durableHistory: capability(has("/session/{sessionID}/message", "get")),
|
|
77
|
+
savedHistory: capability(
|
|
78
|
+
(has("/experimental/session", "get") || has("/session", "get"))
|
|
79
|
+
&& has("/session/{sessionID}/message", "get"),
|
|
80
|
+
),
|
|
77
81
|
planMode: capability(agents.some((value) => {
|
|
78
82
|
const agent = rec(value);
|
|
79
83
|
return agent["name"] === "plan" || agent["id"] === "plan";
|