@offworld/sdk 0.2.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -20
- package/dist/ai/index.d.mts +134 -0
- package/dist/ai/index.d.mts.map +1 -0
- package/dist/ai/index.mjs +924 -0
- package/dist/ai/index.mjs.map +1 -0
- package/dist/clone-DyLvmbJZ.mjs +364 -0
- package/dist/clone-DyLvmbJZ.mjs.map +1 -0
- package/dist/config-DW8J4gl5.mjs +174 -0
- package/dist/config-DW8J4gl5.mjs.map +1 -0
- package/dist/convex/_generated/api.d.ts +67 -0
- package/dist/convex/_generated/api.js +23 -0
- package/dist/convex/_generated/dataModel.d.ts +60 -0
- package/dist/convex/_generated/server.d.ts +143 -0
- package/dist/convex/_generated/server.js +93 -0
- package/dist/index.d.mts +2 -953
- package/dist/index.mjs +4 -3909
- package/dist/internal.d.mts +69 -0
- package/dist/internal.d.mts.map +1 -0
- package/dist/internal.mjs +326 -0
- package/dist/internal.mjs.map +1 -0
- package/dist/public-DbZeh2Mr.mjs +1823 -0
- package/dist/public-DbZeh2Mr.mjs.map +1 -0
- package/dist/public-MYVLaKUi.d.mts +655 -0
- package/dist/public-MYVLaKUi.d.mts.map +1 -0
- package/dist/sync/index.d.mts +175 -0
- package/dist/sync/index.d.mts.map +1 -0
- package/dist/sync/index.mjs +4 -0
- package/dist/sync-DuLJ5wla.mjs +4 -0
- package/dist/sync-wcy5fJRb.mjs +372 -0
- package/dist/sync-wcy5fJRb.mjs.map +1 -0
- package/package.json +35 -6
- package/dist/index.d.mts.map +0 -1
- package/dist/index.mjs.map +0 -1
|
@@ -0,0 +1,924 @@
|
|
|
1
|
+
import { d as toReferenceName, s as loadConfig } from "../config-DW8J4gl5.mjs";
|
|
2
|
+
import { c as getCommitSha } from "../clone-DyLvmbJZ.mjs";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
|
|
5
|
+
//#region src/ai/errors.ts
|
|
6
|
+
/**
|
|
7
|
+
* Base class for OpenCode reference errors
|
|
8
|
+
*/
|
|
9
|
+
var OpenCodeReferenceError = class extends Error {
|
|
10
|
+
_tag = "OpenCodeReferenceError";
|
|
11
|
+
constructor(message, details) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.details = details;
|
|
14
|
+
this.name = "OpenCodeReferenceError";
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Error when the @opencode-ai/sdk package is not installed or invalid
|
|
19
|
+
*/
|
|
20
|
+
var OpenCodeSDKError = class extends OpenCodeReferenceError {
|
|
21
|
+
_tag = "OpenCodeSDKError";
|
|
22
|
+
constructor(message) {
|
|
23
|
+
super(message ?? "Failed to import @opencode-ai/sdk. Install it with: bun add @opencode-ai/sdk");
|
|
24
|
+
this.name = "OpenCodeSDKError";
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Error when the requested provider is not found
|
|
29
|
+
*/
|
|
30
|
+
var InvalidProviderError = class extends OpenCodeReferenceError {
|
|
31
|
+
_tag = "InvalidProviderError";
|
|
32
|
+
hint;
|
|
33
|
+
constructor(providerID, availableProviders) {
|
|
34
|
+
const hint = availableProviders.length > 0 ? `Available providers: ${availableProviders.join(", ")}` : "No providers available. Check your OpenCode configuration.";
|
|
35
|
+
super(`Provider "${providerID}" not found. ${hint}`);
|
|
36
|
+
this.providerID = providerID;
|
|
37
|
+
this.availableProviders = availableProviders;
|
|
38
|
+
this.name = "InvalidProviderError";
|
|
39
|
+
this.hint = hint;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Error when the provider exists but is not connected/authenticated
|
|
44
|
+
*/
|
|
45
|
+
var ProviderNotConnectedError = class extends OpenCodeReferenceError {
|
|
46
|
+
_tag = "ProviderNotConnectedError";
|
|
47
|
+
hint;
|
|
48
|
+
constructor(providerID, connectedProviders) {
|
|
49
|
+
const hint = connectedProviders.length > 0 ? `Connected providers: ${connectedProviders.join(", ")}. Run 'opencode auth ${providerID}' to connect.` : `No providers connected. Run 'opencode auth ${providerID}' to authenticate.`;
|
|
50
|
+
super(`Provider "${providerID}" is not connected. ${hint}`);
|
|
51
|
+
this.providerID = providerID;
|
|
52
|
+
this.connectedProviders = connectedProviders;
|
|
53
|
+
this.name = "ProviderNotConnectedError";
|
|
54
|
+
this.hint = hint;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Error when the requested model is not found for a provider
|
|
59
|
+
*/
|
|
60
|
+
var InvalidModelError = class extends OpenCodeReferenceError {
|
|
61
|
+
_tag = "InvalidModelError";
|
|
62
|
+
hint;
|
|
63
|
+
constructor(modelID, providerID, availableModels) {
|
|
64
|
+
const hint = availableModels.length > 0 ? `Available models for ${providerID}: ${availableModels.slice(0, 10).join(", ")}${availableModels.length > 10 ? ` (and ${availableModels.length - 10} more)` : ""}` : `No models available for provider "${providerID}".`;
|
|
65
|
+
super(`Model "${modelID}" not found for provider "${providerID}". ${hint}`);
|
|
66
|
+
this.modelID = modelID;
|
|
67
|
+
this.providerID = providerID;
|
|
68
|
+
this.availableModels = availableModels;
|
|
69
|
+
this.name = "InvalidModelError";
|
|
70
|
+
this.hint = hint;
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Error when the OpenCode server fails to start
|
|
75
|
+
*/
|
|
76
|
+
var ServerStartError = class extends OpenCodeReferenceError {
|
|
77
|
+
_tag = "ServerStartError";
|
|
78
|
+
hint;
|
|
79
|
+
constructor(message, port, details) {
|
|
80
|
+
const hint = port ? `Failed to start server on port ${port}. Ensure no other process is using this port.` : "Failed to start OpenCode server. Check your OpenCode installation and configuration.";
|
|
81
|
+
super(`${message}. ${hint}`, details);
|
|
82
|
+
this.port = port;
|
|
83
|
+
this.name = "ServerStartError";
|
|
84
|
+
this.hint = hint;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
/**
|
|
88
|
+
* Error when a session operation fails
|
|
89
|
+
*/
|
|
90
|
+
var SessionError = class extends OpenCodeReferenceError {
|
|
91
|
+
_tag = "SessionError";
|
|
92
|
+
hint;
|
|
93
|
+
constructor(message, sessionId, sessionState, details) {
|
|
94
|
+
const hint = `Session operation failed${sessionId ? ` (session: ${sessionId})` : ""}.${sessionState ? ` State: ${sessionState}.` : ""} Try creating a new session.`;
|
|
95
|
+
super(`${message}. ${hint}`, details);
|
|
96
|
+
this.sessionId = sessionId;
|
|
97
|
+
this.sessionState = sessionState;
|
|
98
|
+
this.name = "SessionError";
|
|
99
|
+
this.hint = hint;
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
/**
|
|
103
|
+
* Error when a request times out
|
|
104
|
+
*/
|
|
105
|
+
var TimeoutError = class extends OpenCodeReferenceError {
|
|
106
|
+
_tag = "TimeoutError";
|
|
107
|
+
hint;
|
|
108
|
+
constructor(timeoutMs, operation = "operation") {
|
|
109
|
+
const hint = `The ${operation} did not complete within ${timeoutMs}ms. Consider increasing the timeout or checking if the model is responding.`;
|
|
110
|
+
super(`Timeout: ${operation} did not complete within ${timeoutMs}ms. ${hint}`);
|
|
111
|
+
this.timeoutMs = timeoutMs;
|
|
112
|
+
this.operation = operation;
|
|
113
|
+
this.name = "TimeoutError";
|
|
114
|
+
this.hint = hint;
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
//#endregion
|
|
119
|
+
//#region src/ai/stream/types.ts
|
|
120
|
+
/**
|
|
121
|
+
* Zod schemas for OpenCode stream events
|
|
122
|
+
* Replaces inline `as` casts with runtime-validated types
|
|
123
|
+
*/
|
|
124
|
+
const PartBaseSchema = z.object({
|
|
125
|
+
id: z.string().optional(),
|
|
126
|
+
sessionID: z.string().optional(),
|
|
127
|
+
messageID: z.string().optional()
|
|
128
|
+
});
|
|
129
|
+
const TextPartSchema = PartBaseSchema.extend({
|
|
130
|
+
type: z.literal("text"),
|
|
131
|
+
text: z.string().optional()
|
|
132
|
+
});
|
|
133
|
+
const ToolStatePendingSchema = z.object({ status: z.literal("pending") });
|
|
134
|
+
const ToolStateRunningSchema = z.object({
|
|
135
|
+
status: z.literal("running"),
|
|
136
|
+
title: z.string().optional(),
|
|
137
|
+
input: z.unknown().optional(),
|
|
138
|
+
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
139
|
+
time: z.object({ start: z.number() }).optional()
|
|
140
|
+
});
|
|
141
|
+
const ToolStateCompletedSchema = z.object({
|
|
142
|
+
status: z.literal("completed"),
|
|
143
|
+
title: z.string().optional(),
|
|
144
|
+
input: z.record(z.string(), z.unknown()).optional(),
|
|
145
|
+
output: z.string().optional(),
|
|
146
|
+
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
147
|
+
time: z.object({
|
|
148
|
+
start: z.number(),
|
|
149
|
+
end: z.number()
|
|
150
|
+
}).optional()
|
|
151
|
+
});
|
|
152
|
+
const ToolStateErrorSchema = z.object({
|
|
153
|
+
status: z.literal("error"),
|
|
154
|
+
error: z.string().optional(),
|
|
155
|
+
input: z.record(z.string(), z.unknown()).optional(),
|
|
156
|
+
time: z.object({
|
|
157
|
+
start: z.number(),
|
|
158
|
+
end: z.number()
|
|
159
|
+
}).optional()
|
|
160
|
+
});
|
|
161
|
+
const ToolStateSchema = z.discriminatedUnion("status", [
|
|
162
|
+
ToolStatePendingSchema,
|
|
163
|
+
ToolStateRunningSchema,
|
|
164
|
+
ToolStateCompletedSchema,
|
|
165
|
+
ToolStateErrorSchema
|
|
166
|
+
]);
|
|
167
|
+
const ToolPartSchema = PartBaseSchema.extend({
|
|
168
|
+
type: z.literal("tool"),
|
|
169
|
+
callID: z.string().optional(),
|
|
170
|
+
tool: z.string().optional(),
|
|
171
|
+
state: ToolStateSchema.optional()
|
|
172
|
+
});
|
|
173
|
+
const StepStartPartSchema = PartBaseSchema.extend({ type: z.literal("step-start") });
|
|
174
|
+
const StepFinishPartSchema = PartBaseSchema.extend({
|
|
175
|
+
type: z.literal("step-finish"),
|
|
176
|
+
reason: z.string().optional()
|
|
177
|
+
});
|
|
178
|
+
const ToolUsePartSchema = PartBaseSchema.extend({
|
|
179
|
+
type: z.literal("tool-use"),
|
|
180
|
+
toolUseId: z.string().optional(),
|
|
181
|
+
name: z.string().optional()
|
|
182
|
+
});
|
|
183
|
+
const ToolResultPartSchema = PartBaseSchema.extend({
|
|
184
|
+
type: z.literal("tool-result"),
|
|
185
|
+
toolUseId: z.string().optional()
|
|
186
|
+
});
|
|
187
|
+
const MessagePartSchema = z.discriminatedUnion("type", [
|
|
188
|
+
TextPartSchema,
|
|
189
|
+
ToolPartSchema,
|
|
190
|
+
StepStartPartSchema,
|
|
191
|
+
StepFinishPartSchema,
|
|
192
|
+
ToolUsePartSchema,
|
|
193
|
+
ToolResultPartSchema
|
|
194
|
+
]);
|
|
195
|
+
/**
|
|
196
|
+
* Session error payload
|
|
197
|
+
*/
|
|
198
|
+
const SessionErrorSchema = z.object({
|
|
199
|
+
name: z.string().optional(),
|
|
200
|
+
message: z.string().optional(),
|
|
201
|
+
code: z.string().optional()
|
|
202
|
+
});
|
|
203
|
+
const MessagePartUpdatedPropsSchema = z.object({ part: MessagePartSchema });
|
|
204
|
+
/**
|
|
205
|
+
* Properties for session.idle event
|
|
206
|
+
*/
|
|
207
|
+
const SessionIdlePropsSchema = z.object({ sessionID: z.string() });
|
|
208
|
+
/**
|
|
209
|
+
* Properties for session.error event
|
|
210
|
+
*/
|
|
211
|
+
const SessionErrorPropsSchema = z.object({
|
|
212
|
+
sessionID: z.string(),
|
|
213
|
+
error: SessionErrorSchema.optional()
|
|
214
|
+
});
|
|
215
|
+
/**
|
|
216
|
+
* Properties for session.updated event
|
|
217
|
+
*/
|
|
218
|
+
const SessionUpdatedPropsSchema = z.object({
|
|
219
|
+
sessionID: z.string(),
|
|
220
|
+
status: z.string().optional()
|
|
221
|
+
});
|
|
222
|
+
/**
|
|
223
|
+
* message.part.updated event
|
|
224
|
+
*/
|
|
225
|
+
const MessagePartUpdatedEventSchema = z.object({
|
|
226
|
+
type: z.literal("message.part.updated"),
|
|
227
|
+
properties: MessagePartUpdatedPropsSchema
|
|
228
|
+
});
|
|
229
|
+
/**
|
|
230
|
+
* session.idle event
|
|
231
|
+
*/
|
|
232
|
+
const SessionIdleEventSchema = z.object({
|
|
233
|
+
type: z.literal("session.idle"),
|
|
234
|
+
properties: SessionIdlePropsSchema
|
|
235
|
+
});
|
|
236
|
+
/**
|
|
237
|
+
* session.error event
|
|
238
|
+
*/
|
|
239
|
+
const SessionErrorEventSchema = z.object({
|
|
240
|
+
type: z.literal("session.error"),
|
|
241
|
+
properties: SessionErrorPropsSchema
|
|
242
|
+
});
|
|
243
|
+
/**
|
|
244
|
+
* session.updated event
|
|
245
|
+
*/
|
|
246
|
+
const SessionUpdatedEventSchema = z.object({
|
|
247
|
+
type: z.literal("session.updated"),
|
|
248
|
+
properties: SessionUpdatedPropsSchema
|
|
249
|
+
});
|
|
250
|
+
/**
|
|
251
|
+
* Generic event for unknown types (passthrough)
|
|
252
|
+
*/
|
|
253
|
+
const GenericEventSchema = z.object({
|
|
254
|
+
type: z.string(),
|
|
255
|
+
properties: z.record(z.string(), z.unknown())
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
//#endregion
|
|
259
|
+
//#region src/ai/stream/accumulator.ts
|
|
260
|
+
/**
|
|
261
|
+
* Accumulates text from streaming message parts.
|
|
262
|
+
* Tracks multiple parts by ID and provides delta text between updates.
|
|
263
|
+
*/
|
|
264
|
+
var TextAccumulator = class {
|
|
265
|
+
parts = /* @__PURE__ */ new Map();
|
|
266
|
+
_firstTextReceived = false;
|
|
267
|
+
/**
|
|
268
|
+
* Whether any text has been received
|
|
269
|
+
*/
|
|
270
|
+
get hasReceivedText() {
|
|
271
|
+
return this._firstTextReceived;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Accumulate text from a message part and return the delta (new text only).
|
|
275
|
+
* Returns null if the part should be skipped (non-text, no ID, no text).
|
|
276
|
+
*/
|
|
277
|
+
accumulatePart(part) {
|
|
278
|
+
if (part.type !== "text" || !part.text || !part.id) return null;
|
|
279
|
+
const partId = part.id;
|
|
280
|
+
const prevText = this.parts.get(partId) ?? "";
|
|
281
|
+
this.parts.set(partId, part.text);
|
|
282
|
+
if (!this._firstTextReceived) this._firstTextReceived = true;
|
|
283
|
+
if (part.text.length > prevText.length) return part.text.slice(prevText.length);
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Get the full accumulated text from all parts
|
|
288
|
+
*/
|
|
289
|
+
getFullText() {
|
|
290
|
+
return Array.from(this.parts.values()).join("");
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Get the number of distinct parts accumulated
|
|
294
|
+
*/
|
|
295
|
+
getPartCount() {
|
|
296
|
+
return this.parts.size;
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Get info about each part for debugging
|
|
300
|
+
*/
|
|
301
|
+
getPartInfo() {
|
|
302
|
+
return Array.from(this.parts.entries()).map(([id, text]) => ({
|
|
303
|
+
id,
|
|
304
|
+
length: text.length
|
|
305
|
+
}));
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Clear accumulated text
|
|
309
|
+
*/
|
|
310
|
+
clear() {
|
|
311
|
+
this.parts.clear();
|
|
312
|
+
this._firstTextReceived = false;
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
//#endregion
|
|
317
|
+
//#region src/ai/stream/transformer.ts
|
|
318
|
+
/**
|
|
319
|
+
* Stream event transformer and parser
|
|
320
|
+
* Safely parses and validates stream events using Zod schemas
|
|
321
|
+
*/
|
|
322
|
+
/**
|
|
323
|
+
* Parse a raw stream event into a typed result.
|
|
324
|
+
* Uses safe parsing - returns type: "unknown" for unrecognized or invalid events.
|
|
325
|
+
*/
|
|
326
|
+
function parseStreamEvent(event) {
|
|
327
|
+
switch (event.type) {
|
|
328
|
+
case "message.part.updated": {
|
|
329
|
+
const propsResult = MessagePartUpdatedPropsSchema.safeParse(event.properties);
|
|
330
|
+
if (!propsResult.success) return {
|
|
331
|
+
type: "unknown",
|
|
332
|
+
rawType: event.type
|
|
333
|
+
};
|
|
334
|
+
const props = propsResult.data;
|
|
335
|
+
return {
|
|
336
|
+
type: "message.part.updated",
|
|
337
|
+
props,
|
|
338
|
+
textPart: props.part.type === "text" ? props.part : null,
|
|
339
|
+
toolPart: props.part.type === "tool" ? props.part : null
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
case "session.idle": {
|
|
343
|
+
const propsResult = SessionIdlePropsSchema.safeParse(event.properties);
|
|
344
|
+
if (!propsResult.success) return {
|
|
345
|
+
type: "unknown",
|
|
346
|
+
rawType: event.type
|
|
347
|
+
};
|
|
348
|
+
return {
|
|
349
|
+
type: "session.idle",
|
|
350
|
+
props: propsResult.data
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
case "session.error": {
|
|
354
|
+
const propsResult = SessionErrorPropsSchema.safeParse(event.properties);
|
|
355
|
+
if (!propsResult.success) return {
|
|
356
|
+
type: "unknown",
|
|
357
|
+
rawType: event.type
|
|
358
|
+
};
|
|
359
|
+
const props = propsResult.data;
|
|
360
|
+
let error = null;
|
|
361
|
+
if (props.error) {
|
|
362
|
+
const errorResult = SessionErrorSchema.safeParse(props.error);
|
|
363
|
+
if (errorResult.success) error = errorResult.data;
|
|
364
|
+
}
|
|
365
|
+
return {
|
|
366
|
+
type: "session.error",
|
|
367
|
+
props,
|
|
368
|
+
error
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
default: return {
|
|
372
|
+
type: "unknown",
|
|
373
|
+
rawType: event.type
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
function isEventForSession(event, sessionId) {
|
|
378
|
+
const props = event.properties;
|
|
379
|
+
if ("sessionID" in props && typeof props.sessionID === "string") return props.sessionID === sessionId;
|
|
380
|
+
if ("part" in props && typeof props.part === "object" && props.part !== null && "sessionID" in props.part && typeof props.part.sessionID === "string") return props.part.sessionID === sessionId;
|
|
381
|
+
return true;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
//#endregion
|
|
385
|
+
//#region src/ai/opencode.ts
|
|
386
|
+
const DEFAULT_AI_PROVIDER = "opencode";
|
|
387
|
+
const DEFAULT_AI_MODEL = "claude-opus-4-5";
|
|
388
|
+
let cachedCreateOpencode = null;
|
|
389
|
+
let cachedCreateOpencodeClient = null;
|
|
390
|
+
async function getOpenCodeSDK() {
|
|
391
|
+
if (cachedCreateOpencode && cachedCreateOpencodeClient) return {
|
|
392
|
+
createOpencode: cachedCreateOpencode,
|
|
393
|
+
createOpencodeClient: cachedCreateOpencodeClient
|
|
394
|
+
};
|
|
395
|
+
try {
|
|
396
|
+
const sdk = await import("@opencode-ai/sdk");
|
|
397
|
+
if (typeof sdk.createOpencode !== "function" || typeof sdk.createOpencodeClient !== "function") throw new OpenCodeSDKError("SDK missing required exports");
|
|
398
|
+
cachedCreateOpencode = sdk.createOpencode;
|
|
399
|
+
cachedCreateOpencodeClient = sdk.createOpencodeClient;
|
|
400
|
+
return {
|
|
401
|
+
createOpencode: cachedCreateOpencode,
|
|
402
|
+
createOpencodeClient: cachedCreateOpencodeClient
|
|
403
|
+
};
|
|
404
|
+
} catch (error) {
|
|
405
|
+
if (error instanceof OpenCodeSDKError) throw error;
|
|
406
|
+
throw new OpenCodeSDKError();
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
function formatToolMessage(tool, state) {
|
|
410
|
+
if (state.title) return state.title;
|
|
411
|
+
if (!tool) return null;
|
|
412
|
+
const input = state.input && typeof state.input === "object" && !Array.isArray(state.input) ? state.input : void 0;
|
|
413
|
+
if (!input) return `Running ${tool}...`;
|
|
414
|
+
switch (tool) {
|
|
415
|
+
case "read": {
|
|
416
|
+
const path = input.filePath ?? input.path;
|
|
417
|
+
if (typeof path === "string") return `Reading ${path.split("/").pop()}...`;
|
|
418
|
+
return "Reading file...";
|
|
419
|
+
}
|
|
420
|
+
case "glob": {
|
|
421
|
+
const pattern = input.pattern;
|
|
422
|
+
if (typeof pattern === "string") return `Globbing ${pattern}...`;
|
|
423
|
+
return "Searching files...";
|
|
424
|
+
}
|
|
425
|
+
case "grep": {
|
|
426
|
+
const pattern = input.pattern;
|
|
427
|
+
if (typeof pattern === "string") return `Searching for "${pattern.length > 30 ? `${pattern.slice(0, 30)}...` : pattern}"...`;
|
|
428
|
+
return "Searching content...";
|
|
429
|
+
}
|
|
430
|
+
case "list": {
|
|
431
|
+
const path = input.path;
|
|
432
|
+
if (typeof path === "string") return `Listing ${path}...`;
|
|
433
|
+
return "Listing directory...";
|
|
434
|
+
}
|
|
435
|
+
default: return `Running ${tool}...`;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
async function streamPrompt(options) {
|
|
439
|
+
const { prompt, cwd, systemPrompt, provider: optProvider, model: optModel, timeoutMs, onDebug, onStream } = options;
|
|
440
|
+
const debug = onDebug ?? (() => {});
|
|
441
|
+
const stream = onStream ?? (() => {});
|
|
442
|
+
const startTime = Date.now();
|
|
443
|
+
debug("Loading OpenCode SDK...");
|
|
444
|
+
const { createOpencode, createOpencodeClient } = await getOpenCodeSDK();
|
|
445
|
+
const maxAttempts = 10;
|
|
446
|
+
let server = null;
|
|
447
|
+
let client = null;
|
|
448
|
+
let port = 0;
|
|
449
|
+
const config = {
|
|
450
|
+
plugin: [],
|
|
451
|
+
mcp: {},
|
|
452
|
+
instructions: [],
|
|
453
|
+
agent: {
|
|
454
|
+
build: { disable: true },
|
|
455
|
+
general: { disable: true },
|
|
456
|
+
plan: { disable: true },
|
|
457
|
+
explore: { disable: true },
|
|
458
|
+
analyze: {
|
|
459
|
+
prompt: [
|
|
460
|
+
"You are an expert at analyzing open source codebases and producing documentation.",
|
|
461
|
+
"",
|
|
462
|
+
"Your job is to read the codebase and produce structured output based on the user's request.",
|
|
463
|
+
"Use glob to discover files, grep to search for patterns, and read to examine file contents.",
|
|
464
|
+
"",
|
|
465
|
+
"Guidelines:",
|
|
466
|
+
"- Explore the codebase thoroughly before producing output",
|
|
467
|
+
"- Focus on understanding architecture, key abstractions, and usage patterns",
|
|
468
|
+
"- When asked for JSON output, respond with ONLY valid JSON - no markdown, no code blocks",
|
|
469
|
+
"- When asked for prose, write clear and concise documentation",
|
|
470
|
+
"- Always base your analysis on actual code you've read, never speculate"
|
|
471
|
+
].join("\n"),
|
|
472
|
+
mode: "primary",
|
|
473
|
+
description: "Analyze open source codebases and produce summaries and reference files",
|
|
474
|
+
tools: {
|
|
475
|
+
read: true,
|
|
476
|
+
grep: true,
|
|
477
|
+
glob: true,
|
|
478
|
+
list: true,
|
|
479
|
+
write: false,
|
|
480
|
+
bash: false,
|
|
481
|
+
delete: false,
|
|
482
|
+
edit: false,
|
|
483
|
+
patch: false,
|
|
484
|
+
path: false,
|
|
485
|
+
todowrite: false,
|
|
486
|
+
todoread: false,
|
|
487
|
+
websearch: false,
|
|
488
|
+
webfetch: false,
|
|
489
|
+
codesearch: false,
|
|
490
|
+
skill: false,
|
|
491
|
+
task: false,
|
|
492
|
+
mcp: false,
|
|
493
|
+
question: false,
|
|
494
|
+
plan_enter: false,
|
|
495
|
+
plan_exit: false
|
|
496
|
+
},
|
|
497
|
+
permission: {
|
|
498
|
+
edit: "deny",
|
|
499
|
+
bash: "deny",
|
|
500
|
+
webfetch: "deny",
|
|
501
|
+
external_directory: "deny"
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
};
|
|
506
|
+
debug("Starting embedded OpenCode server...");
|
|
507
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
508
|
+
port = Math.floor(Math.random() * 3e3) + 3e3;
|
|
509
|
+
try {
|
|
510
|
+
server = (await createOpencode({
|
|
511
|
+
port,
|
|
512
|
+
cwd,
|
|
513
|
+
config
|
|
514
|
+
})).server;
|
|
515
|
+
client = createOpencodeClient({
|
|
516
|
+
baseUrl: `http://localhost:${port}`,
|
|
517
|
+
directory: cwd
|
|
518
|
+
});
|
|
519
|
+
debug(`Server started on port ${port}`);
|
|
520
|
+
break;
|
|
521
|
+
} catch (err) {
|
|
522
|
+
if (err instanceof Error && err.message?.includes("port")) continue;
|
|
523
|
+
throw new ServerStartError("Failed to start OpenCode server", port, err);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
if (!server || !client) throw new ServerStartError("Failed to start OpenCode server after all attempts");
|
|
527
|
+
const providerID = optProvider ?? DEFAULT_AI_PROVIDER;
|
|
528
|
+
const modelID = optModel ?? DEFAULT_AI_MODEL;
|
|
529
|
+
try {
|
|
530
|
+
debug("Creating session...");
|
|
531
|
+
const sessionResult = await client.session.create();
|
|
532
|
+
if (sessionResult.error) throw new SessionError("Failed to create session", void 0, void 0, sessionResult.error);
|
|
533
|
+
const sessionId = sessionResult.data.id;
|
|
534
|
+
debug(`Session created: ${sessionId}`);
|
|
535
|
+
debug("Validating provider and model...");
|
|
536
|
+
const providerResult = await client.provider.list();
|
|
537
|
+
if (providerResult.error) throw new OpenCodeReferenceError("Failed to fetch provider list", providerResult.error);
|
|
538
|
+
const { all: allProviders, connected: connectedProviders } = providerResult.data;
|
|
539
|
+
const allProviderIds = allProviders.map((p) => p.id);
|
|
540
|
+
const provider = allProviders.find((p) => p.id === providerID);
|
|
541
|
+
if (!provider) throw new InvalidProviderError(providerID, allProviderIds);
|
|
542
|
+
if (!connectedProviders.includes(providerID)) throw new ProviderNotConnectedError(providerID, connectedProviders);
|
|
543
|
+
const availableModelIds = Object.keys(provider.models);
|
|
544
|
+
if (!provider.models[modelID]) throw new InvalidModelError(modelID, providerID, availableModelIds);
|
|
545
|
+
debug(`Provider "${providerID}" and model "${modelID}" validated`);
|
|
546
|
+
debug("Subscribing to events...");
|
|
547
|
+
const { stream: eventStream } = await client.event.subscribe();
|
|
548
|
+
const fullPrompt = systemPrompt ? `${systemPrompt}\n\nAnalyzing codebase at: ${cwd}\n\n${prompt}` : `Analyzing codebase at: ${cwd}\n\n${prompt}`;
|
|
549
|
+
debug("Sending prompt...");
|
|
550
|
+
const promptPromise = client.session.prompt({
|
|
551
|
+
path: { id: sessionId },
|
|
552
|
+
body: {
|
|
553
|
+
agent: "analyze",
|
|
554
|
+
parts: [{
|
|
555
|
+
type: "text",
|
|
556
|
+
text: fullPrompt
|
|
557
|
+
}],
|
|
558
|
+
model: {
|
|
559
|
+
providerID,
|
|
560
|
+
modelID
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
});
|
|
564
|
+
const textAccumulator = new TextAccumulator();
|
|
565
|
+
debug("Waiting for response...");
|
|
566
|
+
let timeoutId = null;
|
|
567
|
+
const processEvents = async () => {
|
|
568
|
+
for await (const event of eventStream) {
|
|
569
|
+
if (!isEventForSession(event, sessionId)) continue;
|
|
570
|
+
const parsed = parseStreamEvent(event);
|
|
571
|
+
switch (parsed.type) {
|
|
572
|
+
case "message.part.updated":
|
|
573
|
+
if (parsed.toolPart?.state) {
|
|
574
|
+
const { state, tool } = parsed.toolPart;
|
|
575
|
+
if (state.status === "running") {
|
|
576
|
+
const message = formatToolMessage(tool, state);
|
|
577
|
+
if (message) debug(message);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
if (parsed.textPart) {
|
|
581
|
+
const delta = textAccumulator.accumulatePart(parsed.textPart);
|
|
582
|
+
if (!textAccumulator.hasReceivedText) debug("Writing reference...");
|
|
583
|
+
if (delta) stream(delta);
|
|
584
|
+
}
|
|
585
|
+
break;
|
|
586
|
+
case "session.idle":
|
|
587
|
+
if (parsed.props.sessionID === sessionId) {
|
|
588
|
+
debug("Response complete");
|
|
589
|
+
return textAccumulator.getFullText();
|
|
590
|
+
}
|
|
591
|
+
break;
|
|
592
|
+
case "session.error":
|
|
593
|
+
if (parsed.props.sessionID === sessionId) {
|
|
594
|
+
const errorName = parsed.error?.name ?? "Unknown session error";
|
|
595
|
+
debug(`Session error: ${JSON.stringify(parsed.props.error)}`);
|
|
596
|
+
throw new SessionError(errorName, sessionId, "error", parsed.props.error);
|
|
597
|
+
}
|
|
598
|
+
break;
|
|
599
|
+
case "unknown": break;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
return textAccumulator.getFullText();
|
|
603
|
+
};
|
|
604
|
+
let responseText;
|
|
605
|
+
if (timeoutMs && timeoutMs > 0) {
|
|
606
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
607
|
+
timeoutId = setTimeout(() => {
|
|
608
|
+
reject(new TimeoutError(timeoutMs, "session response"));
|
|
609
|
+
}, timeoutMs);
|
|
610
|
+
});
|
|
611
|
+
responseText = await Promise.race([processEvents(), timeoutPromise]);
|
|
612
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
613
|
+
} else responseText = await processEvents();
|
|
614
|
+
await promptPromise;
|
|
615
|
+
if (!responseText) throw new OpenCodeReferenceError("No response received from OpenCode");
|
|
616
|
+
debug(`Response received (${responseText.length} chars)`);
|
|
617
|
+
const durationMs = Date.now() - startTime;
|
|
618
|
+
debug(`Complete in ${durationMs}ms`);
|
|
619
|
+
return {
|
|
620
|
+
text: responseText,
|
|
621
|
+
sessionId,
|
|
622
|
+
durationMs
|
|
623
|
+
};
|
|
624
|
+
} finally {
|
|
625
|
+
debug("Closing server...");
|
|
626
|
+
server.close();
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
//#endregion
|
|
631
|
+
//#region src/generate.ts
|
|
632
|
+
/**
|
|
633
|
+
* This module provides a streamlined approach to generating reference files
|
|
634
|
+
* by delegating all codebase exploration to the AI agent via OpenCode.
|
|
635
|
+
*/
|
|
636
|
+
function createReferenceGenerationPrompt(referenceName) {
|
|
637
|
+
return `You are an expert at analyzing open source libraries and producing reference documentation for AI coding agents.
|
|
638
|
+
|
|
639
|
+
## PRIMARY GOAL
|
|
640
|
+
|
|
641
|
+
Generate a reference markdown file that helps developers USE this library effectively. This is NOT a contribution guide - it's a usage reference for developers consuming this library in their own projects.
|
|
642
|
+
|
|
643
|
+
## CRITICAL RULES
|
|
644
|
+
|
|
645
|
+
1. **USER PERSPECTIVE ONLY**: Write for developers who will npm/pip/cargo install this library and use it in THEIR code.
|
|
646
|
+
- DO NOT include: how to contribute, internal test commands, repo-specific policies
|
|
647
|
+
- DO NOT include: "never mock in tests" or similar internal dev guidelines
|
|
648
|
+
- DO NOT include: commands like "npx hereby", "just ready", "bun test" that run the library's own tests
|
|
649
|
+
- DO include: how to install, import, configure, and use the public API
|
|
650
|
+
|
|
651
|
+
2. **NO FRONTMATTER**: Output pure markdown with NO YAML frontmatter. Start directly with the library name heading.
|
|
652
|
+
|
|
653
|
+
3. **QUICK REFERENCES**: Include a "Quick References" section with paths to key entry points in the repo:
|
|
654
|
+
- Paths must be relative from repo root (e.g., \`src/index.ts\`, \`docs/api.md\`)
|
|
655
|
+
- Include: main entry point, type definitions, README, key docs
|
|
656
|
+
- DO NOT include absolute paths or user-specific paths
|
|
657
|
+
- Keep to 3-5 most important files that help users understand the library
|
|
658
|
+
|
|
659
|
+
4. **PUBLIC API FOCUS**: Document what users import and call, not internal implementation details.
|
|
660
|
+
- Entry points: what to import from the package
|
|
661
|
+
- Configuration: how to set up/initialize
|
|
662
|
+
- Core methods/functions: the main API surface
|
|
663
|
+
- Types: key TypeScript interfaces users need
|
|
664
|
+
|
|
665
|
+
5. **MONOREPO AWARENESS**: Many libraries are monorepos with multiple packages:
|
|
666
|
+
- Check for \`packages/\`, \`apps/\`, \`crates/\`, or \`libs/\` directories
|
|
667
|
+
- Check root package.json for \`workspaces\` field
|
|
668
|
+
- If monorepo: document the package structure and key packages users would install
|
|
669
|
+
- Use full paths from repo root (e.g., \`packages/core/src/index.ts\`)
|
|
670
|
+
- Identify which packages are publishable vs internal
|
|
671
|
+
|
|
672
|
+
## EXPLORATION STEPS
|
|
673
|
+
|
|
674
|
+
Use Read, Grep, Glob tools to explore:
|
|
675
|
+
1. Root package.json / Cargo.toml - check for workspaces/monorepo config
|
|
676
|
+
2. Check for \`packages/\`, \`apps/\`, \`crates/\` directories
|
|
677
|
+
3. README.md - official usage documentation
|
|
678
|
+
4. For monorepos: explore each publishable package's entry point
|
|
679
|
+
5. docs/ or website/ - find documentation
|
|
680
|
+
6. examples/ - real usage patterns
|
|
681
|
+
7. TypeScript definitions (.d.ts) - public API surface
|
|
682
|
+
|
|
683
|
+
## OUTPUT FORMAT
|
|
684
|
+
|
|
685
|
+
IMPORTANT: Reference name is "${referenceName}" (for internal tracking only - do NOT include in output).
|
|
686
|
+
|
|
687
|
+
\`\`\`markdown
|
|
688
|
+
# {Library Name}
|
|
689
|
+
|
|
690
|
+
{2-3 sentence overview of what this library does and its key value proposition}
|
|
691
|
+
|
|
692
|
+
## Quick References
|
|
693
|
+
|
|
694
|
+
| File | Purpose |
|
|
695
|
+
|------|---------|
|
|
696
|
+
| \`packages/{pkg}/src/index.ts\` | Main entry point (monorepo example) |
|
|
697
|
+
| \`src/index.ts\` | Main entry point (single-package example) |
|
|
698
|
+
| \`README.md\` | Documentation |
|
|
699
|
+
|
|
700
|
+
(For monorepos, include paths to key publishable packages)
|
|
701
|
+
|
|
702
|
+
## Packages (for monorepos only)
|
|
703
|
+
|
|
704
|
+
| Package | npm name | Description |
|
|
705
|
+
|---------|----------|-------------|
|
|
706
|
+
| \`packages/core\` | \`@scope/core\` | Core functionality |
|
|
707
|
+
| \`packages/react\` | \`@scope/react\` | React bindings |
|
|
708
|
+
|
|
709
|
+
(OMIT this section for single-package repos)
|
|
710
|
+
|
|
711
|
+
## When to Use
|
|
712
|
+
|
|
713
|
+
- {Practical scenario where a developer would reach for this library}
|
|
714
|
+
- {Another real-world use case}
|
|
715
|
+
- {Problem this library solves}
|
|
716
|
+
|
|
717
|
+
## Installation
|
|
718
|
+
|
|
719
|
+
\`\`\`bash
|
|
720
|
+
# Single package
|
|
721
|
+
npm install {package-name}
|
|
722
|
+
|
|
723
|
+
# Monorepo (show key packages)
|
|
724
|
+
npm install @scope/core @scope/react
|
|
725
|
+
\`\`\`
|
|
726
|
+
|
|
727
|
+
## Best Practices
|
|
728
|
+
|
|
729
|
+
1. {Actionable best practice for USERS of this library}
|
|
730
|
+
2. {Common mistake to avoid when using this library}
|
|
731
|
+
3. {Performance or correctness tip}
|
|
732
|
+
|
|
733
|
+
## Common Patterns
|
|
734
|
+
|
|
735
|
+
**{Pattern Name}:**
|
|
736
|
+
\`\`\`{language}
|
|
737
|
+
{Minimal working code example}
|
|
738
|
+
\`\`\`
|
|
739
|
+
|
|
740
|
+
**{Another Pattern}:**
|
|
741
|
+
\`\`\`{language}
|
|
742
|
+
{Another code example}
|
|
743
|
+
\`\`\`
|
|
744
|
+
|
|
745
|
+
## API Quick Reference
|
|
746
|
+
|
|
747
|
+
| Export | Type | Description |
|
|
748
|
+
|--------|------|-------------|
|
|
749
|
+
| \`{main export}\` | {type} | {what it does} |
|
|
750
|
+
| \`{another export}\` | {type} | {what it does} |
|
|
751
|
+
|
|
752
|
+
{Add more sections as appropriate for the library: Configuration, Types, CLI Commands (if user-facing), etc.}
|
|
753
|
+
\`\`\`
|
|
754
|
+
|
|
755
|
+
## QUALITY CHECKLIST
|
|
756
|
+
|
|
757
|
+
Before outputting, verify:
|
|
758
|
+
- [ ] NO YAML frontmatter - start directly with # heading
|
|
759
|
+
- [ ] Every code example is something a USER would write, not a contributor
|
|
760
|
+
- [ ] No internal test commands or contribution workflows
|
|
761
|
+
- [ ] Quick References paths are relative from repo root (no absolute/user paths)
|
|
762
|
+
- [ ] Best practices are for using the library, not developing it
|
|
763
|
+
- [ ] If monorepo: Packages section lists publishable packages with npm names
|
|
764
|
+
- [ ] If monorepo: paths include package directory (e.g., \`packages/core/src/index.ts\`)
|
|
765
|
+
|
|
766
|
+
Now explore the codebase and generate the reference content.
|
|
767
|
+
|
|
768
|
+
## OUTPUT INSTRUCTIONS
|
|
769
|
+
|
|
770
|
+
After exploring, output your complete reference wrapped in XML tags like this:
|
|
771
|
+
|
|
772
|
+
\`\`\`
|
|
773
|
+
<reference_output>
|
|
774
|
+
(your complete markdown reference here)
|
|
775
|
+
</reference_output>
|
|
776
|
+
\`\`\`
|
|
777
|
+
|
|
778
|
+
REQUIREMENTS:
|
|
779
|
+
- Start with a level-1 heading with the actual library name (e.g., "# TanStack Query")
|
|
780
|
+
- Include sections: Quick References (table), When to Use (bullets), Installation, Best Practices, Common Patterns (with code), API Quick Reference (table)
|
|
781
|
+
- Minimum 2000 characters of actual content - short or placeholder content will be rejected
|
|
782
|
+
- Fill in real information from your exploration - do not use placeholder text like "{Library Name}"
|
|
783
|
+
- No YAML frontmatter - start directly with the markdown heading
|
|
784
|
+
- Output ONLY the reference inside the tags, no other text
|
|
785
|
+
|
|
786
|
+
Begin exploring now.`;
|
|
787
|
+
}
|
|
788
|
+
/**
|
|
789
|
+
* Extract the actual reference markdown content from AI response.
|
|
790
|
+
* The response may include echoed prompt/system context before the actual reference.
|
|
791
|
+
* Handles multiple edge cases:
|
|
792
|
+
* - Model echoes the prompt template (skip template content)
|
|
793
|
+
* - Model forgets to close the tag (extract to end of response)
|
|
794
|
+
* - Multiple tag pairs (find the one with real content)
|
|
795
|
+
*/
|
|
796
|
+
function extractReferenceContent(rawResponse, onDebug) {
|
|
797
|
+
const openTag = "<reference_output>";
|
|
798
|
+
const closeTag = "</reference_output>";
|
|
799
|
+
onDebug?.(`[extract] Raw response length: ${rawResponse.length} chars`);
|
|
800
|
+
const openIndices = [];
|
|
801
|
+
const closeIndices = [];
|
|
802
|
+
let pos = 0;
|
|
803
|
+
while ((pos = rawResponse.indexOf(openTag, pos)) !== -1) {
|
|
804
|
+
openIndices.push(pos);
|
|
805
|
+
pos += 18;
|
|
806
|
+
}
|
|
807
|
+
pos = 0;
|
|
808
|
+
while ((pos = rawResponse.indexOf(closeTag, pos)) !== -1) {
|
|
809
|
+
closeIndices.push(pos);
|
|
810
|
+
pos += 19;
|
|
811
|
+
}
|
|
812
|
+
onDebug?.(`[extract] Found ${openIndices.length} open tag(s), ${closeIndices.length} close tag(s)`);
|
|
813
|
+
const cleanContent = (raw) => {
|
|
814
|
+
let content = raw.trim();
|
|
815
|
+
if (content.startsWith("```")) {
|
|
816
|
+
content = content.replace(/^```(?:markdown)?\s*\n?/, "");
|
|
817
|
+
content = content.replace(/\n?```\s*$/, "");
|
|
818
|
+
}
|
|
819
|
+
return content.trim();
|
|
820
|
+
};
|
|
821
|
+
const isTemplateContent = (content) => {
|
|
822
|
+
return content.includes("{Library Name}") || content.includes("(your complete markdown reference here)");
|
|
823
|
+
};
|
|
824
|
+
for (let i = openIndices.length - 1; i >= 0; i--) {
|
|
825
|
+
const openIdx = openIndices[i];
|
|
826
|
+
if (openIdx === void 0) continue;
|
|
827
|
+
const closeIdx = closeIndices.find((c) => c > openIdx);
|
|
828
|
+
if (closeIdx !== void 0) {
|
|
829
|
+
const content = cleanContent(rawResponse.slice(openIdx + 18, closeIdx));
|
|
830
|
+
onDebug?.(`[extract] Pair ${i}: open=${openIdx}, close=${closeIdx}, len=${content.length}`);
|
|
831
|
+
onDebug?.(`[extract] Preview: "${content.slice(0, 200)}${content.length > 200 ? "..." : ""}"`);
|
|
832
|
+
if (isTemplateContent(content)) {
|
|
833
|
+
onDebug?.(`[extract] Skipping pair ${i} - template placeholder content`);
|
|
834
|
+
continue;
|
|
835
|
+
}
|
|
836
|
+
if (content.length >= 500) {
|
|
837
|
+
onDebug?.(`[extract] Using pair ${i} - valid content`);
|
|
838
|
+
validateReferenceContent(content);
|
|
839
|
+
return content;
|
|
840
|
+
}
|
|
841
|
+
onDebug?.(`[extract] Pair ${i} too short (${content.length} chars)`);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
const lastOpenIdx = openIndices[openIndices.length - 1];
|
|
845
|
+
if (lastOpenIdx !== void 0) {
|
|
846
|
+
if (!closeIndices.some((c) => c > lastOpenIdx)) {
|
|
847
|
+
onDebug?.(`[extract] Last open tag at ${lastOpenIdx} is unclosed - extracting to end`);
|
|
848
|
+
const content = cleanContent(rawResponse.slice(lastOpenIdx + 18));
|
|
849
|
+
onDebug?.(`[extract] Unclosed content: ${content.length} chars`);
|
|
850
|
+
onDebug?.(`[extract] Preview: "${content.slice(0, 200)}${content.length > 200 ? "..." : ""}"`);
|
|
851
|
+
if (!isTemplateContent(content) && content.length >= 500) {
|
|
852
|
+
onDebug?.(`[extract] Using unclosed content - valid`);
|
|
853
|
+
validateReferenceContent(content);
|
|
854
|
+
return content;
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
onDebug?.(`[extract] No valid content found`);
|
|
859
|
+
onDebug?.(`[extract] Response tail: "${rawResponse.slice(-300)}"`);
|
|
860
|
+
throw new Error("Failed to extract reference content: no valid <reference_output> tags found. The AI may have failed to follow the output format or produced placeholder content.");
|
|
861
|
+
}
|
|
862
|
+
/**
|
|
863
|
+
* Validate extracted reference content has minimum required structure.
|
|
864
|
+
* Throws if content is invalid.
|
|
865
|
+
*/
|
|
866
|
+
function validateReferenceContent(content) {
|
|
867
|
+
const foundPlaceholders = [
|
|
868
|
+
"{Library Name}",
|
|
869
|
+
"{Full overview paragraph}",
|
|
870
|
+
"{Table with 3-5 key files}",
|
|
871
|
+
"{3+ bullet points}",
|
|
872
|
+
"{Install commands}",
|
|
873
|
+
"{3+ numbered items}",
|
|
874
|
+
"{2+ code examples",
|
|
875
|
+
"{Table of key exports}",
|
|
876
|
+
"{Additional sections"
|
|
877
|
+
].filter((p) => content.includes(p));
|
|
878
|
+
if (foundPlaceholders.length > 0) throw new Error(`Invalid reference content: contains template placeholders (${foundPlaceholders.slice(0, 3).join(", ")}). The AI echoed the format instead of generating actual content.`);
|
|
879
|
+
if (content.length < 500) throw new Error(`Invalid reference content: too short (${content.length} chars, minimum 500). The AI may have produced placeholder or incomplete content.`);
|
|
880
|
+
if (!content.startsWith("#")) throw new Error("Invalid reference content: must start with markdown heading. Content must begin with '# Library Name' (no YAML frontmatter).");
|
|
881
|
+
}
|
|
882
|
+
/**
|
|
883
|
+
* Generate a reference markdown file for a repository using AI.
|
|
884
|
+
*
|
|
885
|
+
* Opens an OpenCode session and instructs the AI agent to explore the codebase
|
|
886
|
+
* using Read, Grep, and Glob tools, then produce a comprehensive reference.
|
|
887
|
+
*
|
|
888
|
+
* @param repoPath - Path to the repository to analyze
|
|
889
|
+
* @param repoName - Qualified name of the repo (e.g., "tanstack/query" or "my-local-repo")
|
|
890
|
+
* @param options - Generation options (provider, model, callbacks)
|
|
891
|
+
* @returns The generated reference content and commit SHA
|
|
892
|
+
*/
|
|
893
|
+
async function generateReferenceWithAI(repoPath, repoName, options = {}) {
|
|
894
|
+
const { provider, model, onDebug, onStream } = options;
|
|
895
|
+
const [configProvider, configModel] = loadConfig().defaultModel?.split("/") ?? [];
|
|
896
|
+
const aiProvider = provider ?? configProvider;
|
|
897
|
+
const aiModel = model ?? configModel;
|
|
898
|
+
onDebug?.(`Starting AI reference generation for ${repoName}`);
|
|
899
|
+
onDebug?.(`Repo path: ${repoPath}`);
|
|
900
|
+
onDebug?.(`Provider: ${aiProvider ?? "default"}, Model: ${aiModel ?? "default"}`);
|
|
901
|
+
const commitSha = getCommitSha(repoPath);
|
|
902
|
+
onDebug?.(`Commit SHA: ${commitSha}`);
|
|
903
|
+
const referenceName = toReferenceName(repoName);
|
|
904
|
+
onDebug?.(`Reference name: ${referenceName}`);
|
|
905
|
+
const result = await streamPrompt({
|
|
906
|
+
prompt: createReferenceGenerationPrompt(referenceName),
|
|
907
|
+
cwd: repoPath,
|
|
908
|
+
provider: aiProvider,
|
|
909
|
+
model: aiModel,
|
|
910
|
+
onDebug,
|
|
911
|
+
onStream
|
|
912
|
+
});
|
|
913
|
+
onDebug?.(`Generation complete (${result.durationMs}ms, ${result.text.length} chars)`);
|
|
914
|
+
const referenceContent = extractReferenceContent(result.text, onDebug);
|
|
915
|
+
onDebug?.(`Extracted reference content (${referenceContent.length} chars)`);
|
|
916
|
+
return {
|
|
917
|
+
referenceContent,
|
|
918
|
+
commitSha
|
|
919
|
+
};
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
//#endregion
|
|
923
|
+
export { DEFAULT_AI_MODEL, DEFAULT_AI_PROVIDER, InvalidModelError, InvalidProviderError, OpenCodeReferenceError, OpenCodeSDKError, ProviderNotConnectedError, ServerStartError, SessionError, TimeoutError, generateReferenceWithAI, streamPrompt };
|
|
924
|
+
//# sourceMappingURL=index.mjs.map
|