@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.
@@ -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