@openbuilder/cli 0.50.44 → 0.50.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/chunks/{Banner-BlktOjfl.js → Banner-BKC6yG6z.js} +2 -2
  2. package/dist/chunks/{Banner-BlktOjfl.js.map → Banner-BKC6yG6z.js.map} +1 -1
  3. package/dist/chunks/{cli-auth-ChCnxlFl.js → cli-auth-BgiGSBOt.js} +4 -4
  4. package/dist/chunks/{cli-auth-ChCnxlFl.js.map → cli-auth-BgiGSBOt.js.map} +1 -1
  5. package/dist/chunks/{index-oFqGtEeF.js → index-ZNRLfdj5.js} +2 -2
  6. package/dist/chunks/{index-oFqGtEeF.js.map → index-ZNRLfdj5.js.map} +1 -1
  7. package/dist/chunks/{init-BsQ3dhwf.js → init-IQRjA1g3.js} +5 -6
  8. package/dist/chunks/{init-BsQ3dhwf.js.map → init-IQRjA1g3.js.map} +1 -1
  9. package/dist/chunks/{init-tui-Dvk6Ndvl.js → init-tui-B4jfmo3U.js} +5 -6
  10. package/dist/chunks/{init-tui-Dvk6Ndvl.js.map → init-tui-B4jfmo3U.js.map} +1 -1
  11. package/dist/chunks/{login-BhtodVsj.js → login-CrIcDJpS.js} +2 -2
  12. package/dist/chunks/{login-BhtodVsj.js.map → login-CrIcDJpS.js.map} +1 -1
  13. package/dist/chunks/{logout-CDDASeuQ.js → logout-BxgiczmY.js} +2 -2
  14. package/dist/chunks/{logout-CDDASeuQ.js.map → logout-BxgiczmY.js.map} +1 -1
  15. package/dist/chunks/{main-tui-Cklcr3FX.js → main-tui-DmZeCepg.js} +7 -8
  16. package/dist/chunks/{main-tui-Cklcr3FX.js.map → main-tui-DmZeCepg.js.map} +1 -1
  17. package/dist/chunks/{port-allocator-Ct3ioni4.js → port-allocator-DuAZe2_S.js} +3 -4
  18. package/dist/chunks/{port-allocator-Ct3ioni4.js.map → port-allocator-DuAZe2_S.js.map} +1 -1
  19. package/dist/chunks/{run-wycadErJ.js → run-BAh7Xc-y.js} +11 -17
  20. package/dist/chunks/{run-wycadErJ.js.map → run-BAh7Xc-y.js.map} +1 -1
  21. package/dist/chunks/{start-CQKEEma-.js → start-B-brfyVy.js} +5 -6
  22. package/dist/chunks/{start-CQKEEma-.js.map → start-B-brfyVy.js.map} +1 -1
  23. package/dist/chunks/{theme-CktnrDZj.js → theme-DOjeB8BU.js} +13 -8
  24. package/dist/chunks/{theme-CktnrDZj.js.map → theme-DOjeB8BU.js.map} +1 -1
  25. package/dist/chunks/{use-app-Cj2bzWaw.js → use-app-DozfqdJj.js} +2 -2
  26. package/dist/chunks/{use-app-Cj2bzWaw.js.map → use-app-DozfqdJj.js.map} +1 -1
  27. package/dist/chunks/{useBuildState-pcDGDakI.js → useBuildState-DV6wurQ2.js} +2 -2
  28. package/dist/chunks/{useBuildState-pcDGDakI.js.map → useBuildState-DV6wurQ2.js.map} +1 -1
  29. package/dist/cli/index.js +7 -7
  30. package/dist/index.js +1128 -2809
  31. package/dist/index.js.map +1 -1
  32. package/dist/instrument.js +7 -64162
  33. package/dist/instrument.js.map +1 -1
  34. package/package.json +5 -13
  35. package/dist/chunks/_commonjsHelpers-h-Bqc03Z.js +0 -34
  36. package/dist/chunks/_commonjsHelpers-h-Bqc03Z.js.map +0 -1
  37. package/dist/chunks/exports-ij9sv4UM.js +0 -7793
  38. package/dist/chunks/exports-ij9sv4UM.js.map +0 -1
  39. package/scripts/install-vendor-deps.js +0 -34
  40. package/scripts/install-vendor.js +0 -167
  41. package/scripts/prepare-release.js +0 -83
  42. package/vendor/ai-sdk-provider-claude-code-LOCAL.tgz +0 -0
  43. package/vendor/sentry-core-LOCAL.tgz +0 -0
  44. package/vendor/sentry-nextjs-LOCAL.tgz +0 -0
  45. package/vendor/sentry-node-LOCAL.tgz +0 -0
  46. package/vendor/sentry-node-core-LOCAL.tgz +0 -0
package/dist/index.js CHANGED
@@ -1,2004 +1,38 @@
1
1
  // OpenBuilder CLI - Built with Rollup
2
+ import * as Sentry from '@sentry/node';
2
3
  import { query } from '@anthropic-ai/claude-agent-sdk';
3
4
  import { Codex } from '@openai/codex-sdk';
4
- import { a as streamLog, f as fileLog, i as initRunnerLogger, s as setFileLoggerTuiMode, b as getLogger$1 } from './chunks/runner-logger-instance-nDWv2h2T.js';
5
+ import { a as streamLog, f as fileLog, i as initRunnerLogger, s as setFileLoggerTuiMode, b as getLogger } from './chunks/runner-logger-instance-nDWv2h2T.js';
5
6
  import { config as config$1 } from 'dotenv';
6
- import require$$1, { resolve, relative, isAbsolute, dirname, join as join$1 } from 'node:path';
7
+ import path$1, { resolve, relative, isAbsolute, dirname, join as join$1 } from 'node:path';
7
8
  import { fileURLToPath } from 'node:url';
8
- import { generateText, streamText } from 'ai';
9
- import 'zod/v4';
10
- import 'zod/v3';
11
- import { parse } from 'jsonc-parser';
12
- import { z } from 'zod';
13
- import { existsSync, mkdirSync as mkdirSync$1 } from 'fs';
14
- import { existsSync as existsSync$1, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
15
- import { readFile } from 'fs/promises';
16
- import * as path from 'path';
17
- import { join } from 'path';
18
- import WebSocket$1, { WebSocketServer, WebSocket } from 'ws';
19
- import { drizzle } from 'drizzle-orm/node-postgres';
20
- import pg from 'pg';
21
- import { pgTable, timestamp, boolean, text, uuid, index, uniqueIndex, integer, jsonb } from 'drizzle-orm/pg-core';
22
- import { sql, eq, and, desc, isNull } from 'drizzle-orm';
23
- import { randomUUID, createHash } from 'crypto';
24
- import { migrate } from 'drizzle-orm/node-postgres/migrator';
25
- import { ak as addBreadcrumb, aa as captureException, a as getActiveSpan, a0 as getTraceData, ap as startInactiveSpan, bh as flush, bn as continueTrace, af as startSpan, by as setTag, bF as count, bG as distribution, bH as info, bI as fmt } from './chunks/exports-ij9sv4UM.js';
26
- import os__default from 'node:os';
27
- import { randomUUID as randomUUID$1 } from 'node:crypto';
28
- import express from 'express';
29
- import { spawn } from 'node:child_process';
30
- import { EventEmitter } from 'node:events';
31
- import { createServer, createConnection } from 'node:net';
32
- import { readFile as readFile$1, rm, writeFile, readdir } from 'node:fs/promises';
33
- import { simpleGit } from 'simple-git';
34
- import * as os from 'os';
35
- import { tunnelManager } from './chunks/manager-CvGX9qqe.js';
36
- import 'chalk';
37
- import 'node:util';
38
- import 'http';
39
- import 'http-proxy';
40
- import 'zlib';
41
-
42
- // src/errors/ai-sdk-error.ts
43
- var marker = "vercel.ai.error";
44
- var symbol = Symbol.for(marker);
45
- var _a;
46
- var _AISDKError = class _AISDKError extends Error {
47
- /**
48
- * Creates an AI SDK Error.
49
- *
50
- * @param {Object} params - The parameters for creating the error.
51
- * @param {string} params.name - The name of the error.
52
- * @param {string} params.message - The error message.
53
- * @param {unknown} [params.cause] - The underlying cause of the error.
54
- */
55
- constructor({
56
- name: name14,
57
- message,
58
- cause
59
- }) {
60
- super(message);
61
- this[_a] = true;
62
- this.name = name14;
63
- this.cause = cause;
64
- }
65
- /**
66
- * Checks if the given error is an AI SDK Error.
67
- * @param {unknown} error - The error to check.
68
- * @returns {boolean} True if the error is an AI SDK Error, false otherwise.
69
- */
70
- static isInstance(error) {
71
- return _AISDKError.hasMarker(error, marker);
72
- }
73
- static hasMarker(error, marker15) {
74
- const markerSymbol = Symbol.for(marker15);
75
- return error != null && typeof error === "object" && markerSymbol in error && typeof error[markerSymbol] === "boolean" && error[markerSymbol] === true;
76
- }
77
- };
78
- _a = symbol;
79
- var AISDKError = _AISDKError;
80
-
81
- // src/errors/api-call-error.ts
82
- var name = "AI_APICallError";
83
- var marker2 = `vercel.ai.error.${name}`;
84
- var symbol2 = Symbol.for(marker2);
85
- var _a2;
86
- var APICallError = class extends AISDKError {
87
- constructor({
88
- message,
89
- url,
90
- requestBodyValues,
91
- statusCode,
92
- responseHeaders,
93
- responseBody,
94
- cause,
95
- isRetryable = statusCode != null && (statusCode === 408 || // request timeout
96
- statusCode === 409 || // conflict
97
- statusCode === 429 || // too many requests
98
- statusCode >= 500),
99
- // server error
100
- data
101
- }) {
102
- super({ name, message, cause });
103
- this[_a2] = true;
104
- this.url = url;
105
- this.requestBodyValues = requestBodyValues;
106
- this.statusCode = statusCode;
107
- this.responseHeaders = responseHeaders;
108
- this.responseBody = responseBody;
109
- this.isRetryable = isRetryable;
110
- this.data = data;
111
- }
112
- static isInstance(error) {
113
- return AISDKError.hasMarker(error, marker2);
114
- }
115
- };
116
- _a2 = symbol2;
117
-
118
- // src/errors/invalid-argument-error.ts
119
- var name3 = "AI_InvalidArgumentError";
120
- var marker4 = `vercel.ai.error.${name3}`;
121
- var symbol4 = Symbol.for(marker4);
122
- var _a4;
123
- var InvalidArgumentError = class extends AISDKError {
124
- constructor({
125
- message,
126
- cause,
127
- argument
128
- }) {
129
- super({ name: name3, message, cause });
130
- this[_a4] = true;
131
- this.argument = argument;
132
- }
133
- static isInstance(error) {
134
- return AISDKError.hasMarker(error, marker4);
135
- }
136
- };
137
- _a4 = symbol4;
138
-
139
- // src/errors/load-api-key-error.ts
140
- var name7 = "AI_LoadAPIKeyError";
141
- var marker8 = `vercel.ai.error.${name7}`;
142
- var symbol8 = Symbol.for(marker8);
143
- var _a8;
144
- var LoadAPIKeyError = class extends AISDKError {
145
- // used in isInstance
146
- constructor({ message }) {
147
- super({ name: name7, message });
148
- this[_a8] = true;
149
- }
150
- static isInstance(error) {
151
- return AISDKError.hasMarker(error, marker8);
152
- }
153
- };
154
- _a8 = symbol8;
155
-
156
- // src/errors/no-such-model-error.ts
157
- var name10 = "AI_NoSuchModelError";
158
- var marker11 = `vercel.ai.error.${name10}`;
159
- var symbol11 = Symbol.for(marker11);
160
- var _a11;
161
- var NoSuchModelError = class extends AISDKError {
162
- constructor({
163
- errorName = name10,
164
- modelId,
165
- modelType,
166
- message = `No such ${modelType}: ${modelId}`
167
- }) {
168
- super({ name: errorName, message });
169
- this[_a11] = true;
170
- this.modelId = modelId;
171
- this.modelType = modelType;
172
- }
173
- static isInstance(error) {
174
- return AISDKError.hasMarker(error, marker11);
175
- }
176
- };
177
- _a11 = symbol11;
178
-
179
- // src/combine-headers.ts
180
- var createIdGenerator = ({
181
- prefix,
182
- size = 16,
183
- alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz",
184
- separator = "-"
185
- } = {}) => {
186
- const generator = () => {
187
- const alphabetLength = alphabet.length;
188
- const chars = new Array(size);
189
- for (let i = 0; i < size; i++) {
190
- chars[i] = alphabet[Math.random() * alphabetLength | 0];
191
- }
192
- return chars.join("");
193
- };
194
- if (prefix == null) {
195
- return generator;
196
- }
197
- if (alphabet.includes(separator)) {
198
- throw new InvalidArgumentError({
199
- argument: "separator",
200
- message: `The separator "${separator}" must not be part of the alphabet "${alphabet}".`
201
- });
202
- }
203
- return () => `${prefix}${separator}${generator()}`;
204
- };
205
- var generateId = createIdGenerator();
206
- new Set(
207
- "ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvxyz0123456789"
208
- );
209
-
210
- // src/claude-code-provider.ts
211
-
212
- // src/convert-to-claude-code-messages.ts
213
- var IMAGE_URL_WARNING = "Image URLs are not supported by this provider; supply base64/data URLs.";
214
- var IMAGE_CONVERSION_WARNING = "Unable to convert image content; supply base64/data URLs.";
215
- function normalizeBase64(base64) {
216
- return base64.replace(/\s+/g, "");
217
- }
218
- function isImageMimeType(mimeType) {
219
- return typeof mimeType === "string" && mimeType.trim().toLowerCase().startsWith("image/");
220
- }
221
- function createImageContent(mediaType, data) {
222
- const trimmedType = mediaType.trim();
223
- const trimmedData = normalizeBase64(data.trim());
224
- if (!trimmedType || !trimmedData) {
225
- return void 0;
226
- }
227
- return {
228
- type: "image",
229
- source: {
230
- type: "base64",
231
- media_type: trimmedType,
232
- data: trimmedData
233
- }
234
- };
235
- }
236
- function extractMimeType(candidate) {
237
- if (typeof candidate === "string" && candidate.trim()) {
238
- return candidate.trim();
239
- }
240
- return void 0;
241
- }
242
- function parseObjectImage(imageObj, fallbackMimeType) {
243
- const data = typeof imageObj.data === "string" ? imageObj.data : void 0;
244
- const mimeType = extractMimeType(
245
- imageObj.mimeType ?? imageObj.mediaType ?? imageObj.media_type ?? fallbackMimeType
246
- );
247
- if (!data || !mimeType) {
248
- return void 0;
249
- }
250
- return createImageContent(mimeType, data);
251
- }
252
- function parseStringImage(value, fallbackMimeType) {
253
- const trimmed = value.trim();
254
- if (/^https?:\/\//i.test(trimmed)) {
255
- return { warning: IMAGE_URL_WARNING };
256
- }
257
- const dataUrlMatch = trimmed.match(/^data:([^;]+);base64,(.+)$/i);
258
- if (dataUrlMatch) {
259
- const [, mediaType, data] = dataUrlMatch;
260
- const content = createImageContent(mediaType, data);
261
- return content ? { content } : { warning: IMAGE_CONVERSION_WARNING };
262
- }
263
- const base64Match = trimmed.match(/^base64:([^,]+),(.+)$/i);
264
- if (base64Match) {
265
- const [, explicitMimeType, data] = base64Match;
266
- const content = createImageContent(explicitMimeType, data);
267
- return content ? { content } : { warning: IMAGE_CONVERSION_WARNING };
268
- }
269
- if (fallbackMimeType) {
270
- const content = createImageContent(fallbackMimeType, trimmed);
271
- if (content) {
272
- return { content };
273
- }
274
- }
275
- return { warning: IMAGE_CONVERSION_WARNING };
276
- }
277
- function parseImagePart(part) {
278
- if (!part || typeof part !== "object") {
279
- return { warning: IMAGE_CONVERSION_WARNING };
280
- }
281
- const imageValue = part.image;
282
- const mimeType = extractMimeType(part.mimeType);
283
- if (typeof imageValue === "string") {
284
- return parseStringImage(imageValue, mimeType);
285
- }
286
- if (imageValue && typeof imageValue === "object") {
287
- const content = parseObjectImage(imageValue, mimeType);
288
- return content ? { content } : { warning: IMAGE_CONVERSION_WARNING };
289
- }
290
- return { warning: IMAGE_CONVERSION_WARNING };
291
- }
292
- function convertBinaryToBase64(data) {
293
- if (typeof Buffer !== "undefined") {
294
- const buffer = data instanceof Uint8Array ? Buffer.from(data) : Buffer.from(new Uint8Array(data));
295
- return buffer.toString("base64");
296
- }
297
- if (typeof btoa === "function") {
298
- const bytes = data instanceof Uint8Array ? data : new Uint8Array(data);
299
- let binary = "";
300
- const chunkSize = 32768;
301
- for (let i = 0; i < bytes.length; i += chunkSize) {
302
- const chunk = bytes.subarray(i, i + chunkSize);
303
- binary += String.fromCharCode(...chunk);
304
- }
305
- return btoa(binary);
306
- }
307
- return void 0;
308
- }
309
- function parseFilePart(part) {
310
- const mimeType = extractMimeType(part.mediaType ?? part.mimeType);
311
- if (!mimeType || !isImageMimeType(mimeType)) {
312
- return {};
313
- }
314
- const data = part.data;
315
- if (typeof data === "string") {
316
- const content = createImageContent(mimeType, data);
317
- return content ? { content } : { warning: IMAGE_CONVERSION_WARNING };
318
- }
319
- if (data instanceof Uint8Array || typeof ArrayBuffer !== "undefined" && data instanceof ArrayBuffer) {
320
- const base64 = convertBinaryToBase64(data);
321
- if (!base64) {
322
- return { warning: IMAGE_CONVERSION_WARNING };
323
- }
324
- const content = createImageContent(mimeType, base64);
325
- return content ? { content } : { warning: IMAGE_CONVERSION_WARNING };
326
- }
327
- return { warning: IMAGE_CONVERSION_WARNING };
328
- }
329
- function convertToClaudeCodeMessages(prompt, mode = { type: "regular" }, jsonSchema) {
330
- const messages = [];
331
- const warnings = [];
332
- let systemPrompt;
333
- const streamingSegments = [];
334
- const imageMap = /* @__PURE__ */ new Map();
335
- let hasImageParts = false;
336
- const addSegment = (formatted) => {
337
- streamingSegments.push({ formatted });
338
- return streamingSegments.length - 1;
339
- };
340
- const addImageForSegment = (segmentIndex, content) => {
341
- hasImageParts = true;
342
- if (!imageMap.has(segmentIndex)) {
343
- imageMap.set(segmentIndex, []);
344
- }
345
- imageMap.get(segmentIndex)?.push(content);
346
- };
347
- for (const message of prompt) {
348
- switch (message.role) {
349
- case "system":
350
- systemPrompt = message.content;
351
- if (typeof message.content === "string" && message.content.trim().length > 0) {
352
- addSegment(message.content);
353
- } else {
354
- addSegment("");
355
- }
356
- break;
357
- case "user":
358
- if (typeof message.content === "string") {
359
- messages.push(message.content);
360
- addSegment(`Human: ${message.content}`);
361
- } else {
362
- const textParts = message.content.filter((part) => part.type === "text").map((part) => part.text).join("\n");
363
- const segmentIndex = addSegment(textParts ? `Human: ${textParts}` : "");
364
- if (textParts) {
365
- messages.push(textParts);
366
- }
367
- for (const part of message.content) {
368
- if (part.type === "image") {
369
- const { content, warning } = parseImagePart(part);
370
- if (content) {
371
- addImageForSegment(segmentIndex, content);
372
- } else if (warning) {
373
- warnings.push(warning);
374
- }
375
- } else if (part.type === "file") {
376
- const { content, warning } = parseFilePart(part);
377
- if (content) {
378
- addImageForSegment(segmentIndex, content);
379
- } else if (warning) {
380
- warnings.push(warning);
381
- }
382
- }
383
- }
384
- }
385
- break;
386
- case "assistant": {
387
- let assistantContent = "";
388
- if (typeof message.content === "string") {
389
- assistantContent = message.content;
390
- } else {
391
- const textParts = message.content.filter((part) => part.type === "text").map((part) => part.text).join("\n");
392
- if (textParts) {
393
- assistantContent = textParts;
394
- }
395
- const toolCalls = message.content.filter((part) => part.type === "tool-call");
396
- if (toolCalls.length > 0) {
397
- assistantContent += `
398
- [Tool calls made]`;
399
- }
400
- }
401
- const formattedAssistant = `Assistant: ${assistantContent}`;
402
- messages.push(formattedAssistant);
403
- addSegment(formattedAssistant);
404
- break;
405
- }
406
- case "tool":
407
- for (const tool3 of message.content) {
408
- const resultText = tool3.output.type === "text" ? tool3.output.value : JSON.stringify(tool3.output.value);
409
- const formattedToolResult = `Tool Result (${tool3.toolName}): ${resultText}`;
410
- messages.push(formattedToolResult);
411
- addSegment(formattedToolResult);
412
- }
413
- break;
414
- }
415
- }
416
- let finalPrompt = "";
417
- if (systemPrompt) {
418
- finalPrompt = systemPrompt;
419
- }
420
- if (messages.length > 0) {
421
- const formattedMessages = [];
422
- for (let i = 0; i < messages.length; i++) {
423
- const msg = messages[i];
424
- if (msg.startsWith("Assistant:") || msg.startsWith("Tool Result")) {
425
- formattedMessages.push(msg);
426
- } else {
427
- formattedMessages.push(`Human: ${msg}`);
428
- }
429
- }
430
- if (finalPrompt) {
431
- const joinedMessages = formattedMessages.join("\n\n");
432
- finalPrompt = joinedMessages ? `${finalPrompt}
433
-
434
- ${joinedMessages}` : finalPrompt;
435
- } else {
436
- finalPrompt = formattedMessages.join("\n\n");
437
- }
438
- }
439
- let streamingParts = [];
440
- const imagePartsInOrder = [];
441
- const appendImagesForIndex = (index) => {
442
- const images = imageMap.get(index);
443
- if (!images) {
444
- return;
445
- }
446
- images.forEach((image) => {
447
- streamingParts.push(image);
448
- imagePartsInOrder.push(image);
449
- });
450
- };
451
- if (streamingSegments.length > 0) {
452
- let accumulatedText = "";
453
- let emittedText = false;
454
- const flushText = () => {
455
- if (!accumulatedText) {
456
- return;
457
- }
458
- streamingParts.push({ type: "text", text: accumulatedText });
459
- accumulatedText = "";
460
- emittedText = true;
461
- };
462
- streamingSegments.forEach((segment, index) => {
463
- const segmentText = segment.formatted;
464
- if (segmentText) {
465
- if (!accumulatedText) {
466
- accumulatedText = emittedText ? `
467
-
468
- ${segmentText}` : segmentText;
469
- } else {
470
- accumulatedText += `
471
-
472
- ${segmentText}`;
473
- }
474
- }
475
- if (imageMap.has(index)) {
476
- flushText();
477
- appendImagesForIndex(index);
478
- }
479
- });
480
- flushText();
481
- }
482
- if (mode?.type === "object-json" && jsonSchema) {
483
- const schemaStr = JSON.stringify(jsonSchema, null, 2);
484
- finalPrompt = `CRITICAL: You MUST respond with ONLY a JSON object. NO other text, NO explanations, NO questions.
485
-
486
- Your response MUST start with { and end with }
487
-
488
- The JSON MUST match this EXACT schema:
489
- ${schemaStr}
490
-
491
- Now, based on the following conversation, generate ONLY the JSON object with the exact fields specified above:
492
-
493
- ${finalPrompt}
494
-
495
- Remember: Your ENTIRE response must be ONLY the JSON object, starting with { and ending with }`;
496
- streamingParts = [{ type: "text", text: finalPrompt }, ...imagePartsInOrder];
497
- }
498
- return {
499
- messagesPrompt: finalPrompt,
500
- systemPrompt,
501
- ...warnings.length > 0 && { warnings },
502
- streamingContentParts: streamingParts.length > 0 ? streamingParts : [
503
- { type: "text", text: finalPrompt },
504
- ...imagePartsInOrder
505
- ],
506
- hasImageParts
507
- };
508
- }
509
- function extractJson(text) {
510
- let content = text.trim();
511
- const fenceMatch = /```(?:json)?\s*([\s\S]*?)\s*```/i.exec(content);
512
- if (fenceMatch) {
513
- content = fenceMatch[1];
514
- }
515
- const varMatch = /^\s*(?:const|let|var)\s+\w+\s*=\s*([\s\S]*)/i.exec(content);
516
- if (varMatch) {
517
- content = varMatch[1];
518
- if (content.trim().endsWith(";")) {
519
- content = content.trim().slice(0, -1);
520
- }
521
- }
522
- const firstObj = content.indexOf("{");
523
- const firstArr = content.indexOf("[");
524
- if (firstObj === -1 && firstArr === -1) {
525
- return text;
526
- }
527
- const start = firstArr === -1 ? firstObj : firstObj === -1 ? firstArr : Math.min(firstObj, firstArr);
528
- content = content.slice(start);
529
- const tryParse = (value) => {
530
- const errors = [];
531
- try {
532
- const result = parse(value, errors, { allowTrailingComma: true });
533
- if (errors.length === 0) {
534
- return JSON.stringify(result, null, 2);
535
- }
536
- } catch {
537
- }
538
- return void 0;
539
- };
540
- const parsed = tryParse(content);
541
- if (parsed !== void 0) {
542
- return parsed;
543
- }
544
- const openChar = content[0];
545
- const closeChar = openChar === "{" ? "}" : "]";
546
- const closingPositions = [];
547
- let depth = 0;
548
- let inString = false;
549
- let escapeNext = false;
550
- for (let i = 0; i < content.length; i++) {
551
- const char = content[i];
552
- if (escapeNext) {
553
- escapeNext = false;
554
- continue;
555
- }
556
- if (char === "\\") {
557
- escapeNext = true;
558
- continue;
559
- }
560
- if (char === '"' && !inString) {
561
- inString = true;
562
- continue;
563
- }
564
- if (char === '"' && inString) {
565
- inString = false;
566
- continue;
567
- }
568
- if (inString) continue;
569
- if (char === openChar) {
570
- depth++;
571
- } else if (char === closeChar) {
572
- depth--;
573
- if (depth === 0) {
574
- closingPositions.push(i + 1);
575
- }
576
- }
577
- }
578
- for (let i = closingPositions.length - 1; i >= 0; i--) {
579
- const attempt = tryParse(content.slice(0, closingPositions[i]));
580
- if (attempt !== void 0) {
581
- return attempt;
582
- }
583
- }
584
- const searchStart = Math.max(0, content.length - 1e3);
585
- for (let end = content.length - 1; end > searchStart; end--) {
586
- const attempt = tryParse(content.slice(0, end));
587
- if (attempt !== void 0) {
588
- return attempt;
589
- }
590
- }
591
- return text;
592
- }
593
- function createAPICallError({
594
- message,
595
- code,
596
- exitCode,
597
- stderr,
598
- promptExcerpt,
599
- isRetryable = false
600
- }) {
601
- const metadata = {
602
- code,
603
- exitCode,
604
- stderr,
605
- promptExcerpt
606
- };
607
- return new APICallError({
608
- message,
609
- isRetryable,
610
- url: "claude-code-cli://command",
611
- requestBodyValues: promptExcerpt ? { prompt: promptExcerpt } : void 0,
612
- data: metadata
613
- });
614
- }
615
- function createAuthenticationError({ message }) {
616
- return new LoadAPIKeyError({
617
- message: message || "Authentication failed. Please ensure Claude Code SDK is properly authenticated."
618
- });
619
- }
620
- function createTimeoutError({
621
- message,
622
- promptExcerpt,
623
- timeoutMs
624
- }) {
625
- const metadata = {
626
- code: "TIMEOUT",
627
- promptExcerpt
628
- };
629
- return new APICallError({
630
- message,
631
- isRetryable: true,
632
- url: "claude-code-cli://command",
633
- requestBodyValues: promptExcerpt ? { prompt: promptExcerpt } : void 0,
634
- data: timeoutMs !== void 0 ? { ...metadata, timeoutMs } : metadata
635
- });
636
- }
637
-
638
- // src/map-claude-code-finish-reason.ts
639
- function mapClaudeCodeFinishReason(subtype) {
640
- switch (subtype) {
641
- case "success":
642
- return "stop";
643
- case "error_max_turns":
644
- return "length";
645
- case "error_during_execution":
646
- return "error";
647
- default:
648
- return "stop";
649
- }
650
- }
651
- var loggerFunctionSchema = z.object({
652
- debug: z.any().refine((val) => typeof val === "function", {
653
- message: "debug must be a function"
654
- }),
655
- info: z.any().refine((val) => typeof val === "function", {
656
- message: "info must be a function"
657
- }),
658
- warn: z.any().refine((val) => typeof val === "function", {
659
- message: "warn must be a function"
660
- }),
661
- error: z.any().refine((val) => typeof val === "function", {
662
- message: "error must be a function"
663
- })
664
- });
665
- var claudeCodeSettingsSchema = z.object({
666
- pathToClaudeCodeExecutable: z.string().optional(),
667
- customSystemPrompt: z.string().optional(),
668
- appendSystemPrompt: z.string().optional(),
669
- systemPrompt: z.union([
670
- z.string(),
671
- z.object({
672
- type: z.literal("preset"),
673
- preset: z.literal("claude_code"),
674
- append: z.string().optional()
675
- })
676
- ]).optional(),
677
- maxTurns: z.number().int().min(1).max(100).optional(),
678
- maxThinkingTokens: z.number().int().positive().max(1e5).optional(),
679
- cwd: z.string().refine(
680
- (val) => {
681
- if (typeof process === "undefined" || !process.versions?.node) {
682
- return true;
683
- }
684
- return !val || existsSync(val);
685
- },
686
- { message: "Working directory must exist" }
687
- ).optional(),
688
- executable: z.enum(["bun", "deno", "node"]).optional(),
689
- executableArgs: z.array(z.string()).optional(),
690
- permissionMode: z.enum(["default", "acceptEdits", "bypassPermissions", "plan"]).optional(),
691
- permissionPromptToolName: z.string().optional(),
692
- continue: z.boolean().optional(),
693
- resume: z.string().optional(),
694
- allowedTools: z.array(z.string()).optional(),
695
- disallowedTools: z.array(z.string()).optional(),
696
- settingSources: z.array(z.enum(["user", "project", "local"])).optional(),
697
- streamingInput: z.enum(["auto", "always", "off"]).optional(),
698
- // Hooks and tool-permission callback (permissive validation of shapes)
699
- canUseTool: z.any().refine((v) => v === void 0 || typeof v === "function", {
700
- message: "canUseTool must be a function"
701
- }).optional(),
702
- hooks: z.record(
703
- z.string(),
704
- z.array(
705
- z.object({
706
- matcher: z.string().optional(),
707
- hooks: z.array(z.any()).nonempty()
708
- })
709
- )
710
- ).optional(),
711
- mcpServers: z.record(
712
- z.string(),
713
- z.union([
714
- // McpStdioServerConfig
715
- z.object({
716
- type: z.literal("stdio").optional(),
717
- command: z.string(),
718
- args: z.array(z.string()).optional(),
719
- env: z.record(z.string(), z.string()).optional()
720
- }),
721
- // McpSSEServerConfig
722
- z.object({
723
- type: z.literal("sse"),
724
- url: z.string(),
725
- headers: z.record(z.string(), z.string()).optional()
726
- }),
727
- // McpHttpServerConfig
728
- z.object({
729
- type: z.literal("http"),
730
- url: z.string(),
731
- headers: z.record(z.string(), z.string()).optional()
732
- }),
733
- // McpSdkServerConfig (in-process custom tools)
734
- z.object({
735
- type: z.literal("sdk"),
736
- name: z.string(),
737
- instance: z.any()
738
- })
739
- ])
740
- ).optional(),
741
- verbose: z.boolean().optional(),
742
- logger: z.union([z.literal(false), loggerFunctionSchema]).optional(),
743
- env: z.record(z.string(), z.string().optional()).optional(),
744
- additionalDirectories: z.array(z.string()).optional(),
745
- agents: z.record(
746
- z.string(),
747
- z.object({
748
- description: z.string(),
749
- tools: z.array(z.string()).optional(),
750
- prompt: z.string(),
751
- model: z.enum(["sonnet", "opus", "haiku", "inherit"]).optional()
752
- })
753
- ).optional(),
754
- includePartialMessages: z.boolean().optional(),
755
- fallbackModel: z.string().optional(),
756
- forkSession: z.boolean().optional(),
757
- stderr: z.any().refine((val) => val === void 0 || typeof val === "function", {
758
- message: "stderr must be a function"
759
- }).optional(),
760
- strictMcpConfig: z.boolean().optional(),
761
- extraArgs: z.record(z.string(), z.union([z.string(), z.null()])).optional(),
762
- queryFunction: z.any().refine((val) => val === void 0 || typeof val === "function", {
763
- message: "queryFunction must be a function"
764
- }).optional()
765
- }).strict();
766
- function validateModelId(modelId) {
767
- const knownModels = ["opus", "sonnet", "haiku"];
768
- if (!modelId || modelId.trim() === "") {
769
- throw new Error("Model ID cannot be empty");
770
- }
771
- if (!knownModels.includes(modelId)) {
772
- return `Unknown model ID: '${modelId}'. Proceeding with custom model. Known models are: ${knownModels.join(", ")}`;
773
- }
774
- return void 0;
775
- }
776
- function validateSettings(settings) {
777
- const warnings = [];
778
- const errors = [];
779
- try {
780
- const result = claudeCodeSettingsSchema.safeParse(settings);
781
- if (!result.success) {
782
- const errorObject = result.error;
783
- const issues = errorObject.errors || errorObject.issues || [];
784
- issues.forEach((err) => {
785
- const path = err.path.join(".");
786
- errors.push(`${path ? `${path}: ` : ""}${err.message}`);
787
- });
788
- return { valid: false, warnings, errors };
789
- }
790
- const validSettings = result.data;
791
- if (validSettings.maxTurns && validSettings.maxTurns > 20) {
792
- warnings.push(
793
- `High maxTurns value (${validSettings.maxTurns}) may lead to long-running conversations`
794
- );
795
- }
796
- if (validSettings.maxThinkingTokens && validSettings.maxThinkingTokens > 5e4) {
797
- warnings.push(
798
- `Very high maxThinkingTokens (${validSettings.maxThinkingTokens}) may increase response time`
799
- );
800
- }
801
- if (validSettings.allowedTools && validSettings.disallowedTools) {
802
- warnings.push(
803
- "Both allowedTools and disallowedTools are specified. Only allowedTools will be used."
804
- );
805
- }
806
- const validateToolNames = (tools, type) => {
807
- tools.forEach((tool3) => {
808
- if (!/^[a-zA-Z_][a-zA-Z0-9_]*(\([^)]*\))?$/.test(tool3) && !tool3.startsWith("mcp__")) {
809
- warnings.push(`Unusual ${type} tool name format: '${tool3}'`);
810
- }
811
- });
812
- };
813
- if (validSettings.allowedTools) {
814
- validateToolNames(validSettings.allowedTools, "allowed");
815
- }
816
- if (validSettings.disallowedTools) {
817
- validateToolNames(validSettings.disallowedTools, "disallowed");
818
- }
819
- return { valid: true, warnings, errors };
820
- } catch (error) {
821
- errors.push(`Validation error: ${error instanceof Error ? error.message : String(error)}`);
822
- return { valid: false, warnings, errors };
823
- }
824
- }
825
- function validatePrompt(prompt) {
826
- const MAX_PROMPT_LENGTH = 1e5;
827
- if (prompt.length > MAX_PROMPT_LENGTH) {
828
- return `Very long prompt (${prompt.length} characters) may cause performance issues or timeouts`;
829
- }
830
- return void 0;
831
- }
832
- function validateSessionId(sessionId) {
833
- if (sessionId && !/^[a-zA-Z0-9-_]+$/.test(sessionId)) {
834
- return `Unusual session ID format. This may cause issues with session resumption.`;
835
- }
836
- return void 0;
837
- }
838
-
839
- // src/logger.ts
840
- var defaultLogger = {
841
- debug: (message) => console.debug(`[DEBUG] ${message}`),
842
- info: (message) => console.info(`[INFO] ${message}`),
843
- warn: (message) => console.warn(`[WARN] ${message}`),
844
- error: (message) => console.error(`[ERROR] ${message}`)
845
- };
846
- var noopLogger = {
847
- debug: () => {
848
- },
849
- info: () => {
850
- },
851
- warn: () => {
852
- },
853
- error: () => {
854
- }
855
- };
856
- function getLogger(logger) {
857
- if (logger === false) {
858
- return noopLogger;
859
- }
860
- if (logger === void 0) {
861
- return defaultLogger;
862
- }
863
- return logger;
864
- }
865
- function createVerboseLogger(logger, verbose = false) {
866
- if (verbose) {
867
- return logger;
868
- }
869
- return {
870
- debug: () => {
871
- },
872
- // No-op when not verbose
873
- info: () => {
874
- },
875
- // No-op when not verbose
876
- warn: logger.warn.bind(logger),
877
- error: logger.error.bind(logger)
878
- };
879
- }
880
- var CLAUDE_CODE_TRUNCATION_WARNING = "Claude Code SDK output ended unexpectedly; returning truncated response from buffered text. Await upstream fix to avoid data loss.";
881
- var MIN_TRUNCATION_LENGTH = 512;
882
- function isClaudeCodeTruncationError(error, bufferedText) {
883
- const isSyntaxError = error instanceof SyntaxError || // eslint-disable-next-line @typescript-eslint/no-explicit-any
884
- typeof error?.name === "string" && // eslint-disable-next-line @typescript-eslint/no-explicit-any
885
- error.name.toLowerCase() === "syntaxerror";
886
- if (!isSyntaxError) {
887
- return false;
888
- }
889
- if (!bufferedText) {
890
- return false;
891
- }
892
- const rawMessage = typeof error?.message === "string" ? error.message : "";
893
- const message = rawMessage.toLowerCase();
894
- const truncationIndicators = [
895
- "unexpected end of json input",
896
- "unexpected end of input",
897
- "unexpected end of string",
898
- "unexpected eof",
899
- "end of file",
900
- "unterminated string",
901
- "unterminated string constant"
902
- ];
903
- if (!truncationIndicators.some((indicator) => message.includes(indicator))) {
904
- return false;
905
- }
906
- if (bufferedText.length < MIN_TRUNCATION_LENGTH) {
907
- return false;
908
- }
909
- return true;
910
- }
911
- function isAbortError(err) {
912
- if (err && typeof err === "object") {
913
- const e = err;
914
- if (typeof e.name === "string" && e.name === "AbortError") return true;
915
- if (typeof e.code === "string" && e.code.toUpperCase() === "ABORT_ERR") return true;
916
- }
917
- return false;
918
- }
919
- var STREAMING_FEATURE_WARNING = "Claude Agent SDK features (hooks/MCP/images) require streaming input. Set `streamingInput: 'always'` or provide `canUseTool` (auto streams only when canUseTool is set).";
920
- function toAsyncIterablePrompt(messagesPrompt, outputStreamEnded, sessionId, contentParts) {
921
- const content = contentParts && contentParts.length > 0 ? contentParts : [{ type: "text", text: messagesPrompt }];
922
- const msg = {
923
- type: "user",
924
- message: {
925
- role: "user",
926
- content
927
- },
928
- parent_tool_use_id: null,
929
- session_id: sessionId ?? ""
930
- };
931
- return {
932
- async *[Symbol.asyncIterator]() {
933
- yield msg;
934
- await outputStreamEnded;
935
- }
936
- };
937
- }
938
- var modelMap = {
939
- opus: "opus",
940
- sonnet: "sonnet",
941
- haiku: "haiku"
942
- };
943
- var ClaudeCodeLanguageModel = class _ClaudeCodeLanguageModel {
944
- specificationVersion = "v2";
945
- defaultObjectGenerationMode = "json";
946
- supportsImageUrls = false;
947
- supportedUrls = {};
948
- supportsStructuredOutputs = false;
949
- // Fallback/magic string constants
950
- static UNKNOWN_TOOL_NAME = "unknown-tool";
951
- // Tool input safety limits
952
- static MAX_TOOL_INPUT_SIZE = 1048576;
953
- // 1MB hard limit
954
- static MAX_TOOL_INPUT_WARN = 102400;
955
- // 100KB warning threshold
956
- static MAX_DELTA_CALC_SIZE = 1e4;
957
- // 10KB delta computation threshold
958
- modelId;
959
- settings;
960
- sessionId;
961
- modelValidationWarning;
962
- settingsValidationWarnings;
963
- logger;
964
- queryFn;
965
- constructor(options) {
966
- this.modelId = options.id;
967
- this.settings = options.settings ?? {};
968
- this.settingsValidationWarnings = options.settingsValidationWarnings ?? [];
969
- this.queryFn = this.settings.queryFunction ?? query;
970
- const baseLogger = getLogger(this.settings.logger);
971
- this.logger = createVerboseLogger(baseLogger, this.settings.verbose ?? false);
972
- if (!this.modelId || typeof this.modelId !== "string" || this.modelId.trim() === "") {
973
- throw new NoSuchModelError({
974
- modelId: this.modelId,
975
- modelType: "languageModel"
976
- });
977
- }
978
- this.modelValidationWarning = validateModelId(this.modelId);
979
- if (this.modelValidationWarning) {
980
- this.logger.warn(`Claude Code Model: ${this.modelValidationWarning}`);
981
- }
982
- }
983
- get provider() {
984
- return "claude-code";
985
- }
986
- getModel() {
987
- const mapped = modelMap[this.modelId];
988
- return mapped ?? this.modelId;
989
- }
990
- extractToolUses(content) {
991
- if (!Array.isArray(content)) {
992
- return [];
993
- }
994
- return content.filter(
995
- (item) => typeof item === "object" && item !== null && "type" in item && item.type === "tool_use"
996
- ).map((item) => {
997
- const { id, name, input } = item;
998
- return {
999
- id: typeof id === "string" && id.length > 0 ? id : generateId(),
1000
- name: typeof name === "string" && name.length > 0 ? name : _ClaudeCodeLanguageModel.UNKNOWN_TOOL_NAME,
1001
- input
1002
- };
1003
- });
1004
- }
1005
- extractToolResults(content) {
1006
- if (!Array.isArray(content)) {
1007
- return [];
1008
- }
1009
- return content.filter(
1010
- (item) => typeof item === "object" && item !== null && "type" in item && item.type === "tool_result"
1011
- ).map((item) => {
1012
- const { tool_use_id, content: content2, is_error, name } = item;
1013
- return {
1014
- id: typeof tool_use_id === "string" && tool_use_id.length > 0 ? tool_use_id : generateId(),
1015
- name: typeof name === "string" && name.length > 0 ? name : void 0,
1016
- result: content2,
1017
- isError: Boolean(is_error)
1018
- };
1019
- });
1020
- }
1021
- extractToolErrors(content) {
1022
- if (!Array.isArray(content)) {
1023
- return [];
1024
- }
1025
- return content.filter(
1026
- (item) => typeof item === "object" && item !== null && "type" in item && item.type === "tool_error"
1027
- ).map((item) => {
1028
- const { tool_use_id, error, name } = item;
1029
- return {
1030
- id: typeof tool_use_id === "string" && tool_use_id.length > 0 ? tool_use_id : generateId(),
1031
- name: typeof name === "string" && name.length > 0 ? name : void 0,
1032
- error
1033
- };
1034
- });
1035
- }
1036
- serializeToolInput(input) {
1037
- if (typeof input === "string") {
1038
- return this.checkInputSize(input);
1039
- }
1040
- if (input === void 0) {
1041
- return "";
1042
- }
1043
- try {
1044
- const serialized = JSON.stringify(input);
1045
- return this.checkInputSize(serialized);
1046
- } catch {
1047
- const fallback = String(input);
1048
- return this.checkInputSize(fallback);
1049
- }
1050
- }
1051
- checkInputSize(str) {
1052
- const length = str.length;
1053
- if (length > _ClaudeCodeLanguageModel.MAX_TOOL_INPUT_SIZE) {
1054
- throw new Error(
1055
- `Tool input exceeds maximum size of ${_ClaudeCodeLanguageModel.MAX_TOOL_INPUT_SIZE} bytes (got ${length} bytes). This may indicate a malformed request or an attempt to process excessively large data.`
1056
- );
1057
- }
1058
- if (length > _ClaudeCodeLanguageModel.MAX_TOOL_INPUT_WARN) {
1059
- this.logger.warn(
1060
- `[claude-code] Large tool input detected: ${length} bytes. Performance may be impacted. Consider chunking or reducing input size.`
1061
- );
1062
- }
1063
- return str;
1064
- }
1065
- normalizeToolResult(result) {
1066
- if (typeof result === "string") {
1067
- try {
1068
- return JSON.parse(result);
1069
- } catch {
1070
- return result;
1071
- }
1072
- }
1073
- return result;
1074
- }
1075
- generateAllWarnings(options, prompt) {
1076
- const warnings = [];
1077
- const unsupportedParams = [];
1078
- if (options.temperature !== void 0) unsupportedParams.push("temperature");
1079
- if (options.topP !== void 0) unsupportedParams.push("topP");
1080
- if (options.topK !== void 0) unsupportedParams.push("topK");
1081
- if (options.presencePenalty !== void 0) unsupportedParams.push("presencePenalty");
1082
- if (options.frequencyPenalty !== void 0) unsupportedParams.push("frequencyPenalty");
1083
- if (options.stopSequences !== void 0 && options.stopSequences.length > 0)
1084
- unsupportedParams.push("stopSequences");
1085
- if (options.seed !== void 0) unsupportedParams.push("seed");
1086
- if (unsupportedParams.length > 0) {
1087
- for (const param of unsupportedParams) {
1088
- warnings.push({
1089
- type: "unsupported-setting",
1090
- setting: param,
1091
- details: `Claude Code SDK does not support the ${param} parameter. It will be ignored.`
1092
- });
1093
- }
1094
- }
1095
- if (this.modelValidationWarning) {
1096
- warnings.push({
1097
- type: "other",
1098
- message: this.modelValidationWarning
1099
- });
1100
- }
1101
- this.settingsValidationWarnings.forEach((warning) => {
1102
- warnings.push({
1103
- type: "other",
1104
- message: warning
1105
- });
1106
- });
1107
- const promptWarning = validatePrompt(prompt);
1108
- if (promptWarning) {
1109
- warnings.push({
1110
- type: "other",
1111
- message: promptWarning
1112
- });
1113
- }
1114
- return warnings;
1115
- }
1116
- handleJsonExtraction(text, warnings) {
1117
- const extracted = extractJson(text);
1118
- const validation = this.validateJsonExtraction(text, extracted);
1119
- if (!validation.valid && validation.warning) {
1120
- warnings.push(validation.warning);
1121
- }
1122
- return extracted;
1123
- }
1124
- createQueryOptions(abortController) {
1125
- const opts = {
1126
- model: this.getModel(),
1127
- abortController,
1128
- resume: this.settings.resume ?? this.sessionId,
1129
- pathToClaudeCodeExecutable: this.settings.pathToClaudeCodeExecutable,
1130
- maxTurns: this.settings.maxTurns,
1131
- maxThinkingTokens: this.settings.maxThinkingTokens,
1132
- cwd: this.settings.cwd,
1133
- executable: this.settings.executable,
1134
- executableArgs: this.settings.executableArgs,
1135
- permissionMode: this.settings.permissionMode,
1136
- permissionPromptToolName: this.settings.permissionPromptToolName,
1137
- continue: this.settings.continue,
1138
- allowedTools: this.settings.allowedTools,
1139
- disallowedTools: this.settings.disallowedTools,
1140
- mcpServers: this.settings.mcpServers,
1141
- canUseTool: this.settings.canUseTool
1142
- };
1143
- if (this.settings.systemPrompt !== void 0) {
1144
- opts.systemPrompt = this.settings.systemPrompt;
1145
- } else if (this.settings.customSystemPrompt !== void 0) {
1146
- this.logger.warn(
1147
- "[claude-code] 'customSystemPrompt' is deprecated and will be removed in a future major release. Please use 'systemPrompt' instead (string or { type: 'preset', preset: 'claude_code', append? })."
1148
- );
1149
- opts.systemPrompt = this.settings.customSystemPrompt;
1150
- } else if (this.settings.appendSystemPrompt !== void 0) {
1151
- this.logger.warn(
1152
- "[claude-code] 'appendSystemPrompt' is deprecated and will be removed in a future major release. Please use 'systemPrompt: { type: 'preset', preset: 'claude_code', append: <text> }' instead."
1153
- );
1154
- opts.systemPrompt = {
1155
- type: "preset",
1156
- preset: "claude_code",
1157
- append: this.settings.appendSystemPrompt
1158
- };
1159
- }
1160
- if (this.settings.settingSources !== void 0) {
1161
- opts.settingSources = this.settings.settingSources;
1162
- }
1163
- if (this.settings.additionalDirectories !== void 0) {
1164
- opts.additionalDirectories = this.settings.additionalDirectories;
1165
- }
1166
- if (this.settings.agents !== void 0) {
1167
- opts.agents = this.settings.agents;
1168
- }
1169
- if (this.settings.includePartialMessages !== void 0) {
1170
- opts.includePartialMessages = this.settings.includePartialMessages;
1171
- }
1172
- if (this.settings.fallbackModel !== void 0) {
1173
- opts.fallbackModel = this.settings.fallbackModel;
1174
- }
1175
- if (this.settings.forkSession !== void 0) {
1176
- opts.forkSession = this.settings.forkSession;
1177
- }
1178
- if (this.settings.stderr !== void 0) {
1179
- opts.stderr = this.settings.stderr;
1180
- }
1181
- if (this.settings.strictMcpConfig !== void 0) {
1182
- opts.strictMcpConfig = this.settings.strictMcpConfig;
1183
- }
1184
- if (this.settings.extraArgs !== void 0) {
1185
- opts.extraArgs = this.settings.extraArgs;
1186
- }
1187
- if (this.settings.hooks) {
1188
- opts.hooks = this.settings.hooks;
1189
- }
1190
- if (this.settings.env !== void 0) {
1191
- opts.env = { ...process.env, ...this.settings.env };
1192
- }
1193
- return opts;
1194
- }
1195
- handleClaudeCodeError(error, messagesPrompt) {
1196
- if (isAbortError(error)) {
1197
- throw error;
1198
- }
1199
- const isErrorWithMessage = (err) => {
1200
- return typeof err === "object" && err !== null && "message" in err;
1201
- };
1202
- const isErrorWithCode = (err) => {
1203
- return typeof err === "object" && err !== null;
1204
- };
1205
- const authErrorPatterns = [
1206
- "not logged in",
1207
- "authentication",
1208
- "unauthorized",
1209
- "auth failed",
1210
- "please login",
1211
- "claude login"
1212
- ];
1213
- const errorMessage = isErrorWithMessage(error) && error.message ? error.message.toLowerCase() : "";
1214
- const exitCode = isErrorWithCode(error) && typeof error.exitCode === "number" ? error.exitCode : void 0;
1215
- const isAuthError = authErrorPatterns.some((pattern) => errorMessage.includes(pattern)) || exitCode === 401;
1216
- if (isAuthError) {
1217
- return createAuthenticationError({
1218
- message: isErrorWithMessage(error) && error.message ? error.message : "Authentication failed. Please ensure Claude Code SDK is properly authenticated."
1219
- });
1220
- }
1221
- const errorCode = isErrorWithCode(error) && typeof error.code === "string" ? error.code : "";
1222
- if (errorCode === "ETIMEDOUT" || errorMessage.includes("timeout")) {
1223
- return createTimeoutError({
1224
- message: isErrorWithMessage(error) && error.message ? error.message : "Request timed out",
1225
- promptExcerpt: messagesPrompt.substring(0, 200)
1226
- // Don't specify timeoutMs since we don't know the actual timeout value
1227
- // It's controlled by the consumer via AbortSignal
1228
- });
1229
- }
1230
- const isRetryable = errorCode === "ENOENT" || errorCode === "ECONNREFUSED" || errorCode === "ETIMEDOUT" || errorCode === "ECONNRESET";
1231
- return createAPICallError({
1232
- message: isErrorWithMessage(error) && error.message ? error.message : "Claude Code SDK error",
1233
- code: errorCode || void 0,
1234
- exitCode,
1235
- stderr: isErrorWithCode(error) && typeof error.stderr === "string" ? error.stderr : void 0,
1236
- promptExcerpt: messagesPrompt.substring(0, 200),
1237
- isRetryable
1238
- });
1239
- }
1240
- setSessionId(sessionId) {
1241
- this.sessionId = sessionId;
1242
- const warning = validateSessionId(sessionId);
1243
- if (warning) {
1244
- this.logger.warn(`Claude Code Session: ${warning}`);
1245
- }
1246
- }
1247
- validateJsonExtraction(originalText, extractedJson) {
1248
- if (extractedJson === originalText) {
1249
- return {
1250
- valid: false,
1251
- warning: {
1252
- type: "other",
1253
- message: "JSON extraction from model response may be incomplete or modified. The model may not have returned valid JSON."
1254
- }
1255
- };
1256
- }
1257
- try {
1258
- JSON.parse(extractedJson);
1259
- return { valid: true };
1260
- } catch {
1261
- return {
1262
- valid: false,
1263
- warning: {
1264
- type: "other",
1265
- message: "JSON extraction resulted in invalid JSON. The response may be malformed."
1266
- }
1267
- };
1268
- }
1269
- }
1270
- async doGenerate(options) {
1271
- this.logger.debug(`[claude-code] Starting doGenerate request with model: ${this.modelId}`);
1272
- const mode = options.responseFormat?.type === "json" ? { type: "object-json" } : { type: "regular" };
1273
- this.logger.debug(
1274
- `[claude-code] Request mode: ${mode.type}, response format: ${options.responseFormat?.type ?? "none"}`
1275
- );
1276
- const {
1277
- messagesPrompt,
1278
- warnings: messageWarnings,
1279
- streamingContentParts,
1280
- hasImageParts
1281
- } = convertToClaudeCodeMessages(
1282
- options.prompt,
1283
- mode,
1284
- options.responseFormat?.type === "json" ? options.responseFormat.schema : void 0
1285
- );
1286
- this.logger.debug(
1287
- `[claude-code] Converted ${options.prompt.length} messages, hasImageParts: ${hasImageParts}`
1288
- );
1289
- const abortController = new AbortController();
1290
- let abortListener;
1291
- if (options.abortSignal?.aborted) {
1292
- abortController.abort(options.abortSignal.reason);
1293
- } else if (options.abortSignal) {
1294
- abortListener = () => abortController.abort(options.abortSignal?.reason);
1295
- options.abortSignal.addEventListener("abort", abortListener, { once: true });
1296
- }
1297
- const queryOptions = this.createQueryOptions(abortController);
1298
- let text = "";
1299
- let usage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
1300
- let finishReason = "stop";
1301
- let wasTruncated = false;
1302
- let costUsd;
1303
- let durationMs;
1304
- let rawUsage;
1305
- const warnings = this.generateAllWarnings(
1306
- options,
1307
- messagesPrompt
1308
- );
1309
- if (messageWarnings) {
1310
- messageWarnings.forEach((warning) => {
1311
- warnings.push({
1312
- type: "other",
1313
- message: warning
1314
- });
1315
- });
1316
- }
1317
- const modeSetting = this.settings.streamingInput ?? "auto";
1318
- const wantsStreamInput = modeSetting === "always" || modeSetting === "auto" && !!this.settings.canUseTool;
1319
- if (!wantsStreamInput && hasImageParts) {
1320
- warnings.push({
1321
- type: "other",
1322
- message: STREAMING_FEATURE_WARNING
1323
- });
1324
- }
1325
- let done = () => {
1326
- };
1327
- const outputStreamEnded = new Promise((resolve) => {
1328
- done = () => resolve(void 0);
1329
- });
1330
- try {
1331
- if (this.settings.canUseTool && this.settings.permissionPromptToolName) {
1332
- throw new Error(
1333
- "canUseTool requires streamingInput mode ('auto' or 'always') and cannot be used with permissionPromptToolName (SDK constraint). Set streamingInput: 'auto' (or 'always') and remove permissionPromptToolName, or remove canUseTool."
1334
- );
1335
- }
1336
- const sdkPrompt = wantsStreamInput ? toAsyncIterablePrompt(
1337
- messagesPrompt,
1338
- outputStreamEnded,
1339
- this.settings.resume ?? this.sessionId,
1340
- streamingContentParts
1341
- ) : messagesPrompt;
1342
- this.logger.debug(
1343
- `[claude-code] Executing query with streamingInput: ${wantsStreamInput}, session: ${this.settings.resume ?? this.sessionId ?? "new"}`
1344
- );
1345
- const response = this.queryFn({
1346
- prompt: sdkPrompt,
1347
- options: queryOptions
1348
- });
1349
- for await (const message of response) {
1350
- this.logger.debug(`[claude-code] Received message type: ${message.type}`);
1351
- if (message.type === "assistant") {
1352
- text += message.message.content.map((c) => c.type === "text" ? c.text : "").join("");
1353
- } else if (message.type === "result") {
1354
- done();
1355
- this.setSessionId(message.session_id);
1356
- costUsd = message.total_cost_usd;
1357
- durationMs = message.duration_ms;
1358
- this.logger.info(
1359
- `[claude-code] Request completed - Session: ${message.session_id}, Cost: $${costUsd?.toFixed(4) ?? "N/A"}, Duration: ${durationMs ?? "N/A"}ms`
1360
- );
1361
- if ("usage" in message) {
1362
- rawUsage = message.usage;
1363
- usage = {
1364
- inputTokens: (message.usage.cache_creation_input_tokens ?? 0) + (message.usage.cache_read_input_tokens ?? 0) + (message.usage.input_tokens ?? 0),
1365
- outputTokens: message.usage.output_tokens ?? 0,
1366
- totalTokens: (message.usage.cache_creation_input_tokens ?? 0) + (message.usage.cache_read_input_tokens ?? 0) + (message.usage.input_tokens ?? 0) + (message.usage.output_tokens ?? 0)
1367
- };
1368
- this.logger.debug(
1369
- `[claude-code] Token usage - Input: ${usage.inputTokens}, Output: ${usage.outputTokens}, Total: ${usage.totalTokens}`
1370
- );
1371
- }
1372
- finishReason = mapClaudeCodeFinishReason(message.subtype);
1373
- this.logger.debug(`[claude-code] Finish reason: ${finishReason}`);
1374
- } else if (message.type === "system" && message.subtype === "init") {
1375
- this.setSessionId(message.session_id);
1376
- this.logger.info(`[claude-code] Session initialized: ${message.session_id}`);
1377
- }
1378
- }
1379
- } catch (error) {
1380
- done();
1381
- this.logger.debug(
1382
- `[claude-code] Error during doGenerate: ${error instanceof Error ? error.message : String(error)}`
1383
- );
1384
- if (isAbortError(error)) {
1385
- this.logger.debug("[claude-code] Request aborted by user");
1386
- throw options.abortSignal?.aborted ? options.abortSignal.reason : error;
1387
- }
1388
- if (isClaudeCodeTruncationError(error, text)) {
1389
- this.logger.warn(
1390
- `[claude-code] Detected truncated response, returning ${text.length} characters of buffered text`
1391
- );
1392
- wasTruncated = true;
1393
- finishReason = "length";
1394
- warnings.push({
1395
- type: "other",
1396
- message: CLAUDE_CODE_TRUNCATION_WARNING
1397
- });
1398
- } else {
1399
- throw this.handleClaudeCodeError(error, messagesPrompt);
1400
- }
1401
- } finally {
1402
- if (options.abortSignal && abortListener) {
1403
- options.abortSignal.removeEventListener("abort", abortListener);
1404
- }
1405
- }
1406
- if (options.responseFormat?.type === "json" && text) {
1407
- text = this.handleJsonExtraction(text, warnings);
1408
- }
1409
- return {
1410
- content: [{ type: "text", text }],
1411
- usage,
1412
- finishReason,
1413
- warnings,
1414
- response: {
1415
- id: generateId(),
1416
- timestamp: /* @__PURE__ */ new Date(),
1417
- modelId: this.modelId
1418
- },
1419
- request: {
1420
- body: messagesPrompt
1421
- },
1422
- providerMetadata: {
1423
- "claude-code": {
1424
- ...this.sessionId !== void 0 && { sessionId: this.sessionId },
1425
- ...costUsd !== void 0 && { costUsd },
1426
- ...durationMs !== void 0 && { durationMs },
1427
- ...rawUsage !== void 0 && { rawUsage },
1428
- ...wasTruncated && { truncated: true }
1429
- }
1430
- }
1431
- };
1432
- }
1433
- async doStream(options) {
1434
- this.logger.debug(`[claude-code] Starting doStream request with model: ${this.modelId}`);
1435
- const mode = options.responseFormat?.type === "json" ? { type: "object-json" } : { type: "regular" };
1436
- this.logger.debug(
1437
- `[claude-code] Stream mode: ${mode.type}, response format: ${options.responseFormat?.type ?? "none"}`
1438
- );
1439
- const {
1440
- messagesPrompt,
1441
- warnings: messageWarnings,
1442
- streamingContentParts,
1443
- hasImageParts
1444
- } = convertToClaudeCodeMessages(
1445
- options.prompt,
1446
- mode,
1447
- options.responseFormat?.type === "json" ? options.responseFormat.schema : void 0
1448
- );
1449
- this.logger.debug(
1450
- `[claude-code] Converted ${options.prompt.length} messages for streaming, hasImageParts: ${hasImageParts}`
1451
- );
1452
- const abortController = new AbortController();
1453
- let abortListener;
1454
- if (options.abortSignal?.aborted) {
1455
- abortController.abort(options.abortSignal.reason);
1456
- } else if (options.abortSignal) {
1457
- abortListener = () => abortController.abort(options.abortSignal?.reason);
1458
- options.abortSignal.addEventListener("abort", abortListener, { once: true });
1459
- }
1460
- const queryOptions = this.createQueryOptions(abortController);
1461
- const warnings = this.generateAllWarnings(
1462
- options,
1463
- messagesPrompt
1464
- );
1465
- if (messageWarnings) {
1466
- messageWarnings.forEach((warning) => {
1467
- warnings.push({
1468
- type: "other",
1469
- message: warning
1470
- });
1471
- });
1472
- }
1473
- const modeSetting = this.settings.streamingInput ?? "auto";
1474
- const wantsStreamInput = modeSetting === "always" || modeSetting === "auto" && !!this.settings.canUseTool;
1475
- if (!wantsStreamInput && hasImageParts) {
1476
- warnings.push({
1477
- type: "other",
1478
- message: STREAMING_FEATURE_WARNING
1479
- });
1480
- }
1481
- const stream = new ReadableStream({
1482
- start: async (controller) => {
1483
- let done = () => {
1484
- };
1485
- const outputStreamEnded = new Promise((resolve) => {
1486
- done = () => resolve(void 0);
1487
- });
1488
- const toolStates = /* @__PURE__ */ new Map();
1489
- const streamWarnings = [];
1490
- const closeToolInput = (toolId, state) => {
1491
- if (!state.inputClosed && state.inputStarted) {
1492
- controller.enqueue({
1493
- type: "tool-input-end",
1494
- id: toolId
1495
- });
1496
- state.inputClosed = true;
1497
- }
1498
- };
1499
- const emitToolCall = (toolId, state) => {
1500
- if (state.callEmitted) {
1501
- return;
1502
- }
1503
- closeToolInput(toolId, state);
1504
- controller.enqueue({
1505
- type: "tool-call",
1506
- toolCallId: toolId,
1507
- toolName: state.name,
1508
- input: state.lastSerializedInput ?? "",
1509
- providerExecuted: true,
1510
- dynamic: true,
1511
- // V3 field: indicates tool is provider-defined (not in user's tools map)
1512
- providerMetadata: {
1513
- "claude-code": {
1514
- // rawInput preserves the original serialized format before AI SDK normalization.
1515
- // Use this if you need the exact string sent to the Claude CLI, which may differ
1516
- // from the `input` field after AI SDK processing.
1517
- rawInput: state.lastSerializedInput ?? ""
1518
- }
1519
- }
1520
- });
1521
- state.callEmitted = true;
1522
- };
1523
- const finalizeToolCalls = () => {
1524
- for (const [toolId, state] of toolStates) {
1525
- emitToolCall(toolId, state);
1526
- }
1527
- toolStates.clear();
1528
- };
1529
- let usage = { inputTokens: 0, outputTokens: 0, totalTokens: 0 };
1530
- let accumulatedText = "";
1531
- let textPartId;
1532
- try {
1533
- controller.enqueue({ type: "stream-start", warnings });
1534
- if (this.settings.canUseTool && this.settings.permissionPromptToolName) {
1535
- throw new Error(
1536
- "canUseTool requires streamingInput mode ('auto' or 'always') and cannot be used with permissionPromptToolName (SDK constraint). Set streamingInput: 'auto' (or 'always') and remove permissionPromptToolName, or remove canUseTool."
1537
- );
1538
- }
1539
- const sdkPrompt = wantsStreamInput ? toAsyncIterablePrompt(
1540
- messagesPrompt,
1541
- outputStreamEnded,
1542
- this.settings.resume ?? this.sessionId,
1543
- streamingContentParts
1544
- ) : messagesPrompt;
1545
- this.logger.debug(
1546
- `[claude-code] Starting stream query with streamingInput: ${wantsStreamInput}, session: ${this.settings.resume ?? this.sessionId ?? "new"}`
1547
- );
1548
- const response = this.queryFn({
1549
- prompt: sdkPrompt,
1550
- options: queryOptions
1551
- });
1552
- for await (const message of response) {
1553
- this.logger.debug(`[claude-code] Stream received message type: ${message.type}`);
1554
- if (message.type === "assistant") {
1555
- if (!message.message?.content) {
1556
- this.logger.warn(
1557
- `[claude-code] Unexpected assistant message structure: missing content field. Message type: ${message.type}. This may indicate an SDK protocol violation.`
1558
- );
1559
- continue;
1560
- }
1561
- const content = message.message.content;
1562
- for (const tool3 of this.extractToolUses(content)) {
1563
- const toolId = tool3.id;
1564
- let state = toolStates.get(toolId);
1565
- if (!state) {
1566
- state = {
1567
- name: tool3.name,
1568
- inputStarted: false,
1569
- inputClosed: false,
1570
- callEmitted: false
1571
- };
1572
- toolStates.set(toolId, state);
1573
- this.logger.debug(
1574
- `[claude-code] New tool use detected - Tool: ${tool3.name}, ID: ${toolId}`
1575
- );
1576
- }
1577
- state.name = tool3.name;
1578
- if (!state.inputStarted) {
1579
- this.logger.debug(
1580
- `[claude-code] Tool input started - Tool: ${tool3.name}, ID: ${toolId}`
1581
- );
1582
- controller.enqueue({
1583
- type: "tool-input-start",
1584
- id: toolId,
1585
- toolName: tool3.name,
1586
- providerExecuted: true,
1587
- dynamic: true
1588
- // V3 field: indicates tool is provider-defined
1589
- });
1590
- state.inputStarted = true;
1591
- }
1592
- const serializedInput = this.serializeToolInput(tool3.input);
1593
- if (serializedInput) {
1594
- let deltaPayload = "";
1595
- if (state.lastSerializedInput === void 0) {
1596
- if (serializedInput.length <= _ClaudeCodeLanguageModel.MAX_DELTA_CALC_SIZE) {
1597
- deltaPayload = serializedInput;
1598
- }
1599
- } else if (serializedInput.length <= _ClaudeCodeLanguageModel.MAX_DELTA_CALC_SIZE && state.lastSerializedInput.length <= _ClaudeCodeLanguageModel.MAX_DELTA_CALC_SIZE && serializedInput.startsWith(state.lastSerializedInput)) {
1600
- deltaPayload = serializedInput.slice(state.lastSerializedInput.length);
1601
- } else if (serializedInput !== state.lastSerializedInput) {
1602
- deltaPayload = "";
1603
- }
1604
- if (deltaPayload) {
1605
- controller.enqueue({
1606
- type: "tool-input-delta",
1607
- id: toolId,
1608
- delta: deltaPayload
1609
- });
1610
- }
1611
- state.lastSerializedInput = serializedInput;
1612
- }
1613
- }
1614
- const text = content.map((c) => c.type === "text" ? c.text : "").join("");
1615
- if (text) {
1616
- accumulatedText += text;
1617
- if (options.responseFormat?.type !== "json") {
1618
- if (!textPartId) {
1619
- textPartId = generateId();
1620
- controller.enqueue({
1621
- type: "text-start",
1622
- id: textPartId
1623
- });
1624
- }
1625
- controller.enqueue({
1626
- type: "text-delta",
1627
- id: textPartId,
1628
- delta: text
1629
- });
1630
- }
1631
- }
1632
- } else if (message.type === "user") {
1633
- if (!message.message?.content) {
1634
- this.logger.warn(
1635
- `[claude-code] Unexpected user message structure: missing content field. Message type: ${message.type}. This may indicate an SDK protocol violation.`
1636
- );
1637
- continue;
1638
- }
1639
- const content = message.message.content;
1640
- for (const result of this.extractToolResults(content)) {
1641
- let state = toolStates.get(result.id);
1642
- const toolName = result.name ?? state?.name ?? _ClaudeCodeLanguageModel.UNKNOWN_TOOL_NAME;
1643
- this.logger.debug(
1644
- `[claude-code] Tool result received - Tool: ${toolName}, ID: ${result.id}`
1645
- );
1646
- if (!state) {
1647
- this.logger.warn(
1648
- `[claude-code] Received tool result for unknown tool ID: ${result.id}`
1649
- );
1650
- state = {
1651
- name: toolName,
1652
- inputStarted: false,
1653
- inputClosed: false,
1654
- callEmitted: false
1655
- };
1656
- toolStates.set(result.id, state);
1657
- if (!state.inputStarted) {
1658
- controller.enqueue({
1659
- type: "tool-input-start",
1660
- id: result.id,
1661
- toolName,
1662
- providerExecuted: true,
1663
- dynamic: true
1664
- // V3 field: indicates tool is provider-defined
1665
- });
1666
- state.inputStarted = true;
1667
- }
1668
- if (!state.inputClosed) {
1669
- controller.enqueue({
1670
- type: "tool-input-end",
1671
- id: result.id
1672
- });
1673
- state.inputClosed = true;
1674
- }
1675
- }
1676
- state.name = toolName;
1677
- const normalizedResult = this.normalizeToolResult(result.result);
1678
- const rawResult = typeof result.result === "string" ? result.result : (() => {
1679
- try {
1680
- return JSON.stringify(result.result);
1681
- } catch {
1682
- return String(result.result);
1683
- }
1684
- })();
1685
- emitToolCall(result.id, state);
1686
- controller.enqueue({
1687
- type: "tool-result",
1688
- toolCallId: result.id,
1689
- toolName,
1690
- result: normalizedResult,
1691
- isError: result.isError,
1692
- providerExecuted: true,
1693
- dynamic: true,
1694
- // V3 field: indicates tool is provider-defined
1695
- providerMetadata: {
1696
- "claude-code": {
1697
- // rawResult preserves the original CLI output string before JSON parsing.
1698
- // Use this when you need the exact string returned by the tool, especially
1699
- // if the `result` field has been parsed/normalized and you need the original format.
1700
- rawResult
1701
- }
1702
- }
1703
- });
1704
- }
1705
- for (const error of this.extractToolErrors(content)) {
1706
- let state = toolStates.get(error.id);
1707
- const toolName = error.name ?? state?.name ?? _ClaudeCodeLanguageModel.UNKNOWN_TOOL_NAME;
1708
- this.logger.debug(
1709
- `[claude-code] Tool error received - Tool: ${toolName}, ID: ${error.id}`
1710
- );
1711
- if (!state) {
1712
- this.logger.warn(
1713
- `[claude-code] Received tool error for unknown tool ID: ${error.id}`
1714
- );
1715
- state = {
1716
- name: toolName,
1717
- inputStarted: true,
1718
- inputClosed: true,
1719
- callEmitted: false
1720
- };
1721
- toolStates.set(error.id, state);
1722
- }
1723
- emitToolCall(error.id, state);
1724
- const rawError = typeof error.error === "string" ? error.error : typeof error.error === "object" && error.error !== null ? (() => {
1725
- try {
1726
- return JSON.stringify(error.error);
1727
- } catch {
1728
- return String(error.error);
1729
- }
1730
- })() : String(error.error);
1731
- controller.enqueue({
1732
- type: "tool-error",
1733
- toolCallId: error.id,
1734
- toolName,
1735
- error: rawError,
1736
- providerExecuted: true,
1737
- dynamic: true,
1738
- // V3 field: indicates tool is provider-defined
1739
- providerMetadata: {
1740
- "claude-code": {
1741
- rawError
1742
- }
1743
- }
1744
- });
1745
- }
1746
- } else if (message.type === "result") {
1747
- done();
1748
- this.logger.info(
1749
- `[claude-code] Stream completed - Session: ${message.session_id}, Cost: $${message.total_cost_usd?.toFixed(4) ?? "N/A"}, Duration: ${message.duration_ms ?? "N/A"}ms`
1750
- );
1751
- let rawUsage;
1752
- if ("usage" in message) {
1753
- rawUsage = message.usage;
1754
- usage = {
1755
- inputTokens: (message.usage.cache_creation_input_tokens ?? 0) + (message.usage.cache_read_input_tokens ?? 0) + (message.usage.input_tokens ?? 0),
1756
- outputTokens: message.usage.output_tokens ?? 0,
1757
- totalTokens: (message.usage.cache_creation_input_tokens ?? 0) + (message.usage.cache_read_input_tokens ?? 0) + (message.usage.input_tokens ?? 0) + (message.usage.output_tokens ?? 0)
1758
- };
1759
- this.logger.debug(
1760
- `[claude-code] Stream token usage - Input: ${usage.inputTokens}, Output: ${usage.outputTokens}, Total: ${usage.totalTokens}`
1761
- );
1762
- }
1763
- const finishReason = mapClaudeCodeFinishReason(
1764
- message.subtype
1765
- );
1766
- this.logger.debug(`[claude-code] Stream finish reason: ${finishReason}`);
1767
- this.setSessionId(message.session_id);
1768
- if (options.responseFormat?.type === "json" && accumulatedText) {
1769
- const extractedJson = this.handleJsonExtraction(accumulatedText, streamWarnings);
1770
- const jsonTextId = generateId();
1771
- controller.enqueue({
1772
- type: "text-start",
1773
- id: jsonTextId
1774
- });
1775
- controller.enqueue({
1776
- type: "text-delta",
1777
- id: jsonTextId,
1778
- delta: extractedJson
1779
- });
1780
- controller.enqueue({
1781
- type: "text-end",
1782
- id: jsonTextId
1783
- });
1784
- } else if (textPartId) {
1785
- controller.enqueue({
1786
- type: "text-end",
1787
- id: textPartId
1788
- });
1789
- }
1790
- finalizeToolCalls();
1791
- const warningsJson = this.serializeWarningsForMetadata(streamWarnings);
1792
- controller.enqueue({
1793
- type: "finish",
1794
- finishReason,
1795
- usage,
1796
- providerMetadata: {
1797
- "claude-code": {
1798
- sessionId: message.session_id,
1799
- ...message.total_cost_usd !== void 0 && {
1800
- costUsd: message.total_cost_usd
1801
- },
1802
- ...message.duration_ms !== void 0 && { durationMs: message.duration_ms },
1803
- ...rawUsage !== void 0 && { rawUsage },
1804
- // JSON validation warnings are collected during streaming and included
1805
- // in providerMetadata since the AI SDK's finish event doesn't support
1806
- // a top-level warnings field (unlike stream-start which was already emitted)
1807
- ...streamWarnings.length > 0 && {
1808
- warnings: warningsJson
1809
- }
1810
- }
1811
- }
1812
- });
1813
- } else if (message.type === "system" && message.subtype === "init") {
1814
- this.setSessionId(message.session_id);
1815
- this.logger.info(`[claude-code] Stream session initialized: ${message.session_id}`);
1816
- controller.enqueue({
1817
- type: "response-metadata",
1818
- id: message.session_id,
1819
- timestamp: /* @__PURE__ */ new Date(),
1820
- modelId: this.modelId
1821
- });
1822
- }
1823
- }
1824
- finalizeToolCalls();
1825
- this.logger.debug("[claude-code] Stream finalized, closing stream");
1826
- controller.close();
1827
- } catch (error) {
1828
- done();
1829
- this.logger.debug(
1830
- `[claude-code] Error during doStream: ${error instanceof Error ? error.message : String(error)}`
1831
- );
1832
- if (isClaudeCodeTruncationError(error, accumulatedText)) {
1833
- this.logger.warn(
1834
- `[claude-code] Detected truncated stream response, returning ${accumulatedText.length} characters of buffered text`
1835
- );
1836
- const truncationWarning = {
1837
- type: "other",
1838
- message: CLAUDE_CODE_TRUNCATION_WARNING
1839
- };
1840
- streamWarnings.push(truncationWarning);
1841
- const emitJsonText = () => {
1842
- const extractedJson = this.handleJsonExtraction(accumulatedText, streamWarnings);
1843
- const jsonTextId = generateId();
1844
- controller.enqueue({
1845
- type: "text-start",
1846
- id: jsonTextId
1847
- });
1848
- controller.enqueue({
1849
- type: "text-delta",
1850
- id: jsonTextId,
1851
- delta: extractedJson
1852
- });
1853
- controller.enqueue({
1854
- type: "text-end",
1855
- id: jsonTextId
1856
- });
1857
- };
1858
- if (options.responseFormat?.type === "json") {
1859
- emitJsonText();
1860
- } else if (textPartId) {
1861
- controller.enqueue({
1862
- type: "text-end",
1863
- id: textPartId
1864
- });
1865
- } else if (accumulatedText) {
1866
- const fallbackTextId = generateId();
1867
- controller.enqueue({
1868
- type: "text-start",
1869
- id: fallbackTextId
1870
- });
1871
- controller.enqueue({
1872
- type: "text-delta",
1873
- id: fallbackTextId,
1874
- delta: accumulatedText
1875
- });
1876
- controller.enqueue({
1877
- type: "text-end",
1878
- id: fallbackTextId
1879
- });
1880
- }
1881
- finalizeToolCalls();
1882
- const warningsJson = this.serializeWarningsForMetadata(streamWarnings);
1883
- controller.enqueue({
1884
- type: "finish",
1885
- finishReason: "length",
1886
- usage,
1887
- providerMetadata: {
1888
- "claude-code": {
1889
- ...this.sessionId !== void 0 && { sessionId: this.sessionId },
1890
- truncated: true,
1891
- ...streamWarnings.length > 0 && {
1892
- warnings: warningsJson
1893
- }
1894
- }
1895
- }
1896
- });
1897
- controller.close();
1898
- return;
1899
- }
1900
- finalizeToolCalls();
1901
- let errorToEmit;
1902
- if (isAbortError(error)) {
1903
- errorToEmit = options.abortSignal?.aborted ? options.abortSignal.reason : error;
1904
- } else {
1905
- errorToEmit = this.handleClaudeCodeError(error, messagesPrompt);
1906
- }
1907
- controller.enqueue({
1908
- type: "error",
1909
- error: errorToEmit
1910
- });
1911
- controller.close();
1912
- } finally {
1913
- if (options.abortSignal && abortListener) {
1914
- options.abortSignal.removeEventListener("abort", abortListener);
1915
- }
1916
- }
1917
- },
1918
- cancel: () => {
1919
- if (options.abortSignal && abortListener) {
1920
- options.abortSignal.removeEventListener("abort", abortListener);
1921
- }
1922
- }
1923
- });
1924
- return {
1925
- stream,
1926
- request: {
1927
- body: messagesPrompt
1928
- }
1929
- };
1930
- }
1931
- serializeWarningsForMetadata(warnings) {
1932
- const result = warnings.map((w) => {
1933
- const base = { type: w.type };
1934
- if ("message" in w) {
1935
- const m = w.message;
1936
- if (m !== void 0) base.message = String(m);
1937
- }
1938
- if (w.type === "unsupported-setting") {
1939
- const setting = w.setting;
1940
- if (setting !== void 0) base.setting = String(setting);
1941
- if ("details" in w) {
1942
- const d = w.details;
1943
- if (d !== void 0) base.details = String(d);
1944
- }
1945
- }
1946
- return base;
1947
- });
1948
- return result;
1949
- }
1950
- };
1951
-
1952
- // src/claude-code-provider.ts
1953
- function createClaudeCode(options = {}) {
1954
- const logger = getLogger(options.defaultSettings?.logger);
1955
- if (options.defaultSettings) {
1956
- const validation = validateSettings(options.defaultSettings);
1957
- if (!validation.valid) {
1958
- throw new Error(`Invalid default settings: ${validation.errors.join(", ")}`);
1959
- }
1960
- if (validation.warnings.length > 0) {
1961
- validation.warnings.forEach((warning) => logger.warn(`Claude Code Provider: ${warning}`));
1962
- }
1963
- }
1964
- const createModel = (modelId, settings = {}) => {
1965
- const mergedSettings = {
1966
- ...options.defaultSettings,
1967
- ...settings
1968
- };
1969
- const validation = validateSettings(mergedSettings);
1970
- if (!validation.valid) {
1971
- throw new Error(`Invalid settings: ${validation.errors.join(", ")}`);
1972
- }
1973
- return new ClaudeCodeLanguageModel({
1974
- id: modelId,
1975
- settings: mergedSettings,
1976
- settingsValidationWarnings: validation.warnings
1977
- });
1978
- };
1979
- const provider = function(modelId, settings) {
1980
- if (new.target) {
1981
- throw new Error("The Claude Code model function cannot be called with the new keyword.");
1982
- }
1983
- return createModel(modelId, settings);
1984
- };
1985
- provider.languageModel = createModel;
1986
- provider.chat = createModel;
1987
- provider.textEmbeddingModel = (modelId) => {
1988
- throw new NoSuchModelError({
1989
- modelId,
1990
- modelType: "textEmbeddingModel"
1991
- });
1992
- };
1993
- provider.imageModel = (modelId) => {
1994
- throw new NoSuchModelError({
1995
- modelId,
1996
- modelType: "imageModel"
1997
- });
1998
- };
1999
- return provider;
2000
- }
2001
- var claudeCode = createClaudeCode();
9
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
10
+ import { readFile } from 'fs/promises';
11
+ import * as path from 'path';
12
+ import { join } from 'path';
13
+ import WebSocket$1, { WebSocketServer, WebSocket } from 'ws';
14
+ import { drizzle } from 'drizzle-orm/node-postgres';
15
+ import pg from 'pg';
16
+ import { pgTable, timestamp, boolean, text, uuid, index, uniqueIndex, integer, jsonb } from 'drizzle-orm/pg-core';
17
+ import { sql, eq, and, desc, isNull } from 'drizzle-orm';
18
+ import { randomUUID, createHash } from 'crypto';
19
+ import { migrate } from 'drizzle-orm/node-postgres/migrator';
20
+ import { z } from 'zod';
21
+ import { spawn } from 'node:child_process';
22
+ import { EventEmitter } from 'node:events';
23
+ import os$1, { homedir } from 'node:os';
24
+ import { randomUUID as randomUUID$1 } from 'node:crypto';
25
+ import express from 'express';
26
+ import { createServer, createConnection } from 'node:net';
27
+ import { readFile as readFile$1, rm, writeFile, readdir } from 'node:fs/promises';
28
+ import { simpleGit } from 'simple-git';
29
+ import * as os from 'os';
30
+ import { existsSync as existsSync$1, mkdirSync as mkdirSync$1 } from 'fs';
31
+ import { tunnelManager } from './chunks/manager-CvGX9qqe.js';
32
+ import 'chalk';
33
+ import 'http';
34
+ import 'http-proxy';
35
+ import 'zlib';
2002
36
 
2003
37
  /**
2004
38
  * Creates a permission handler that restricts Claude to a specific project directory
@@ -2168,7 +202,8 @@ var init_tags$1 = __esm$1({
2168
202
  description: "Anthropic Claude - Balanced performance and speed",
2169
203
  logo: "/claude.png",
2170
204
  provider: "claude-code",
2171
- model: "claude-sonnet-4-5"
205
+ model: "claude-sonnet-4-5",
206
+ sdk: "agent"
2172
207
  },
2173
208
  {
2174
209
  value: "claude-opus-4-5",
@@ -2176,7 +211,8 @@ var init_tags$1 = __esm$1({
2176
211
  description: "Anthropic Claude - Most capable for complex tasks",
2177
212
  logo: "/claude.png",
2178
213
  provider: "claude-code",
2179
- model: "claude-opus-4-5"
214
+ model: "claude-opus-4-5",
215
+ sdk: "agent"
2180
216
  },
2181
217
  {
2182
218
  value: "claude-haiku-4-5",
@@ -2184,7 +220,8 @@ var init_tags$1 = __esm$1({
2184
220
  description: "Anthropic Claude - Fastest, good for iterations",
2185
221
  logo: "/claude.png",
2186
222
  provider: "claude-code",
2187
- model: "claude-haiku-4-5"
223
+ model: "claude-haiku-4-5",
224
+ sdk: "agent"
2188
225
  },
2189
226
  {
2190
227
  value: "gpt-5.2-codex",
@@ -2192,7 +229,35 @@ var init_tags$1 = __esm$1({
2192
229
  description: "OpenAI Codex - Advanced code generation",
2193
230
  logo: "/openai.png",
2194
231
  provider: "openai-codex",
2195
- model: "openai/gpt-5.2-codex"
232
+ model: "openai/gpt-5.2-codex",
233
+ sdk: "agent"
234
+ },
235
+ {
236
+ value: "factory-droid-opus",
237
+ label: "Factory Droid (Opus)",
238
+ description: "Factory Droid SDK with Claude Opus 4.5",
239
+ logo: "/factory.svg",
240
+ provider: "factory-droid",
241
+ model: "claude-opus-4-5-20251101",
242
+ sdk: "droid"
243
+ },
244
+ {
245
+ value: "factory-droid-codex",
246
+ label: "Factory Droid (Codex)",
247
+ description: "Factory Droid SDK with GPT-5.2 Codex",
248
+ logo: "/factory.svg",
249
+ provider: "factory-droid",
250
+ model: "gpt-5.2-codex",
251
+ sdk: "droid"
252
+ },
253
+ {
254
+ value: "factory-droid-glm",
255
+ label: "Factory Droid (GLM)",
256
+ description: "Factory Droid SDK with GLM 4.7",
257
+ logo: "/factory.svg",
258
+ provider: "factory-droid",
259
+ model: "glm-4.7",
260
+ sdk: "droid"
2196
261
  }
2197
262
  ]
2198
263
  },
@@ -3235,6 +1300,94 @@ var init_codex_strategy$1 = __esm$1({
3235
1300
  }
3236
1301
  });
3237
1302
 
1303
+ // src/lib/agents/droid-strategy.ts
1304
+ var droid_strategy_exports$1 = {};
1305
+ __export$1(droid_strategy_exports$1, {
1306
+ default: () => droid_strategy_default$1
1307
+ });
1308
+ function buildDroidSections$1(context) {
1309
+ const sections = [];
1310
+ if (context.tags && context.tags.length > 0) {
1311
+ const resolved = resolveTags$1(context.tags);
1312
+ const tagPrompt = generatePromptFromTags$1(resolved, context.projectName, context.isNewProject);
1313
+ if (tagPrompt) {
1314
+ sections.push(tagPrompt);
1315
+ }
1316
+ }
1317
+ if (context.isNewProject) {
1318
+ sections.push(`## New Project Setup
1319
+
1320
+ - Project name: ${context.projectName}
1321
+ - Location: ${context.workingDirectory}
1322
+ - Operation type: ${context.operationType}
1323
+
1324
+ The template has already been downloaded. Install dependencies and customize the scaffold to satisfy the request.`);
1325
+ } else {
1326
+ let existingProjectSection = `## Existing Project Context
1327
+
1328
+ - Project location: ${context.workingDirectory}
1329
+ - Operation type: ${context.operationType}
1330
+
1331
+ Review the current codebase and apply the requested changes without re-scaffolding.`;
1332
+ if (context.conversationHistory && context.conversationHistory.length > 0) {
1333
+ existingProjectSection += `
1334
+
1335
+ **Recent Conversation History:**
1336
+ `;
1337
+ context.conversationHistory.forEach((msg, index2) => {
1338
+ const roleLabel = msg.role === "user" ? "User" : "Assistant";
1339
+ const content = msg.content.length > 500 ? msg.content.substring(0, 500) + "...[truncated]" : msg.content;
1340
+ existingProjectSection += `${index2 + 1}. ${roleLabel}:
1341
+ ${content}
1342
+
1343
+ `;
1344
+ });
1345
+ }
1346
+ sections.push(existingProjectSection);
1347
+ }
1348
+ sections.push(`## Workspace Rules
1349
+ - Use relative paths within the project.
1350
+ - Work inside the existing project structure.
1351
+ - Provide complete file contents without placeholders.`);
1352
+ if (context.fileTree) {
1353
+ sections.push(`## Project Structure
1354
+ ${context.fileTree}`);
1355
+ }
1356
+ if (context.templateName) {
1357
+ sections.push(`## Template
1358
+ - Name: ${context.templateName}
1359
+ - Framework: ${context.templateFramework ?? "unknown"}`);
1360
+ }
1361
+ return sections;
1362
+ }
1363
+ function buildFullPrompt3$1(context, basePrompt) {
1364
+ if (!context.isNewProject) {
1365
+ return basePrompt;
1366
+ }
1367
+ return `${basePrompt}
1368
+
1369
+ CRITICAL: The template has already been prepared in ${context.workingDirectory}. Do not scaffold a new project.`;
1370
+ }
1371
+ var droidStrategy$1, droid_strategy_default$1;
1372
+ var init_droid_strategy$1 = __esm$1({
1373
+ "src/lib/agents/droid-strategy.ts"() {
1374
+ init_resolver$1();
1375
+ droidStrategy$1 = {
1376
+ buildSystemPromptSections: buildDroidSections$1,
1377
+ buildFullPrompt: buildFullPrompt3$1,
1378
+ shouldDownloadTemplate(context) {
1379
+ return context.isNewProject && !context.skipTemplates;
1380
+ },
1381
+ postTemplateSelected(context, template) {
1382
+ context.templateName = template.name;
1383
+ context.templateFramework = template.framework;
1384
+ context.fileTree = template.fileTree;
1385
+ }
1386
+ };
1387
+ droid_strategy_default$1 = droidStrategy$1;
1388
+ }
1389
+ });
1390
+
3238
1391
  // src/lib/prompts.ts
3239
1392
  var CLAUDE_SYSTEM_PROMPT = `You are an elite coding assistant specialized in building visually stunning, production-ready JavaScript applications.
3240
1393
 
@@ -4065,12 +2218,18 @@ async function ensureRegistry$1() {
4065
2218
  if (initialized$1) {
4066
2219
  return;
4067
2220
  }
4068
- const [{ default: claudeStrategy2 }, { default: codexStrategy2 }] = await Promise.all([
2221
+ const [
2222
+ { default: claudeStrategy2 },
2223
+ { default: codexStrategy2 },
2224
+ { default: droidStrategy2 }
2225
+ ] = await Promise.all([
4069
2226
  Promise.resolve().then(() => (init_claude_strategy$1(), claude_strategy_exports$1)),
4070
- Promise.resolve().then(() => (init_codex_strategy$1(), codex_strategy_exports$1))
2227
+ Promise.resolve().then(() => (init_codex_strategy$1(), codex_strategy_exports$1)),
2228
+ Promise.resolve().then(() => (init_droid_strategy$1(), droid_strategy_exports$1))
4071
2229
  ]);
4072
2230
  registerAgentStrategy$1("claude-code", claudeStrategy2);
4073
2231
  registerAgentStrategy$1("openai-codex", codexStrategy2);
2232
+ registerAgentStrategy$1("factory-droid", droidStrategy2);
4074
2233
  initialized$1 = true;
4075
2234
  }
4076
2235
  async function resolveAgentStrategy$1(agentId) {
@@ -4133,7 +2292,7 @@ var BuildLogger$1 = class BuildLogger {
4133
2292
  if (typeof process !== "undefined" && process.env.NODE_ENV !== "test") {
4134
2293
  try {
4135
2294
  if (level === "warn" || level === "error") {
4136
- addBreadcrumb({
2295
+ Sentry.addBreadcrumb({
4137
2296
  category: `build-logger.${context}`,
4138
2297
  message,
4139
2298
  level: level === "error" ? "error" : "warning",
@@ -5605,7 +3764,7 @@ var BuildWebSocketServer = class {
5605
3764
  pingInterval,
5606
3765
  userId: runnerUserId
5607
3766
  });
5608
- addBreadcrumb({
3767
+ Sentry.addBreadcrumb({
5609
3768
  category: "websocket",
5610
3769
  message: `Runner connected: ${runnerId}`,
5611
3770
  level: "info"
@@ -5639,7 +3798,7 @@ var BuildWebSocketServer = class {
5639
3798
  }
5640
3799
  } catch (error) {
5641
3800
  this.runnerTotalErrors++;
5642
- captureException(error, {
3801
+ Sentry.captureException(error, {
5643
3802
  tags: { runnerId, source: "websocket_message" },
5644
3803
  level: "error"
5645
3804
  });
@@ -5656,7 +3815,7 @@ var BuildWebSocketServer = class {
5656
3815
  httpProxyManager.cancelRequestsForRunner(runnerId);
5657
3816
  hmrProxyManager$1.disconnectRunner(runnerId);
5658
3817
  this.cleanupRunnerProcesses(runnerId);
5659
- addBreadcrumb({
3818
+ Sentry.addBreadcrumb({
5660
3819
  category: "websocket",
5661
3820
  message: `Runner disconnected: ${runnerId}`,
5662
3821
  level: "info",
@@ -5666,7 +3825,7 @@ var BuildWebSocketServer = class {
5666
3825
  ws.on("error", (error) => {
5667
3826
  buildLogger$2.websocket.error("Runner socket error", error, { runnerId });
5668
3827
  this.runnerTotalErrors++;
5669
- captureException(error, {
3828
+ Sentry.captureException(error, {
5670
3829
  tags: { runnerId, source: "websocket_error" },
5671
3830
  level: "error"
5672
3831
  });
@@ -5707,10 +3866,10 @@ var BuildWebSocketServer = class {
5707
3866
  return false;
5708
3867
  }
5709
3868
  try {
5710
- const activeSpan = getActiveSpan();
3869
+ const activeSpan = Sentry.getActiveSpan();
5711
3870
  const hasTrace = !!activeSpan;
5712
3871
  if (activeSpan) {
5713
- const traceData = getTraceData();
3872
+ const traceData = Sentry.getTraceData();
5714
3873
  if (traceData["sentry-trace"]) {
5715
3874
  command._sentry = {
5716
3875
  trace: traceData["sentry-trace"],
@@ -5727,7 +3886,7 @@ var BuildWebSocketServer = class {
5727
3886
  return true;
5728
3887
  } catch (error) {
5729
3888
  this.runnerTotalErrors++;
5730
- captureException(error, {
3889
+ Sentry.captureException(error, {
5731
3890
  tags: { runnerId, commandType: command.type },
5732
3891
  level: "error"
5733
3892
  });
@@ -5793,7 +3952,7 @@ var BuildWebSocketServer = class {
5793
3952
  for (const [runnerId, conn] of this.runnerConnections.entries()) {
5794
3953
  if (now - conn.lastHeartbeat > this.RUNNER_HEARTBEAT_TIMEOUT) {
5795
3954
  buildLogger$2.websocket.runnerStaleRemoved(runnerId);
5796
- addBreadcrumb({
3955
+ Sentry.addBreadcrumb({
5797
3956
  category: "websocket",
5798
3957
  message: `Stale runner connection removed: ${runnerId}`,
5799
3958
  level: "warning",
@@ -5836,7 +3995,7 @@ var BuildWebSocketServer = class {
5836
3995
  runnerId,
5837
3996
  projectIds
5838
3997
  });
5839
- addBreadcrumb({
3998
+ Sentry.addBreadcrumb({
5840
3999
  category: "websocket",
5841
4000
  message: `Cleaned up processes for disconnected runner`,
5842
4001
  level: "info",
@@ -5851,7 +4010,7 @@ var BuildWebSocketServer = class {
5851
4010
  }
5852
4011
  } catch (error) {
5853
4012
  buildLogger$2.websocket.error("Failed to cleanup runner processes", error, { runnerId });
5854
- captureException(error, {
4013
+ Sentry.captureException(error, {
5855
4014
  tags: { runnerId, source: "runner_cleanup" },
5856
4015
  level: "error"
5857
4016
  });
@@ -6002,10 +4161,10 @@ var BuildWebSocketServer = class {
6002
4161
  updates: []
6003
4162
  });
6004
4163
  }
6005
- const activeSpan = getActiveSpan();
4164
+ const activeSpan = Sentry.getActiveSpan();
6006
4165
  const traceContext = activeSpan ? {
6007
- trace: getTraceData()["sentry-trace"],
6008
- baggage: getTraceData().baggage
4166
+ trace: Sentry.getTraceData()["sentry-trace"],
4167
+ baggage: Sentry.getTraceData().baggage
6009
4168
  } : void 0;
6010
4169
  const batch = this.pendingUpdates.get(key);
6011
4170
  batch.updates.push({
@@ -6037,10 +4196,10 @@ var BuildWebSocketServer = class {
6037
4196
  updates: []
6038
4197
  });
6039
4198
  }
6040
- const activeSpan = getActiveSpan();
4199
+ const activeSpan = Sentry.getActiveSpan();
6041
4200
  const traceContext = activeSpan ? {
6042
- trace: getTraceData()["sentry-trace"],
6043
- baggage: getTraceData().baggage
4201
+ trace: Sentry.getTraceData()["sentry-trace"],
4202
+ baggage: Sentry.getTraceData().baggage
6044
4203
  } : void 0;
6045
4204
  const batch = this.pendingUpdates.get(key);
6046
4205
  batch.updates.push({
@@ -6483,41 +4642,41 @@ var AVAILABLE_ICONS = [
6483
4642
  ];
6484
4643
 
6485
4644
  var AgentCore = /*#__PURE__*/Object.freeze({
6486
- __proto__: null,
6487
- AVAILABLE_ICONS: AVAILABLE_ICONS,
6488
- CLAUDE_SYSTEM_PROMPT: CLAUDE_SYSTEM_PROMPT,
6489
- CODEX_SYSTEM_PROMPT: CODEX_SYSTEM_PROMPT,
6490
- DEFAULT_AGENT_ID: DEFAULT_AGENT_ID,
6491
- DEFAULT_CLAUDE_MODEL_ID: DEFAULT_CLAUDE_MODEL_ID,
6492
- DEFAULT_OPENCODE_MODEL_ID: DEFAULT_OPENCODE_MODEL_ID,
6493
- GITHUB_CHAT_MESSAGES: GITHUB_CHAT_MESSAGES,
6494
- LEGACY_MODEL_MAP: LEGACY_MODEL_MAP,
6495
- MODEL_METADATA: MODEL_METADATA,
6496
- NEONDB_CHAT_MESSAGES: NEONDB_CHAT_MESSAGES,
6497
- ProjectMetadataSchema: ProjectMetadataSchema,
6498
- ProjectNamingSchema: ProjectNamingSchema,
6499
- TemplateAnalysisSchema: TemplateAnalysisSchema,
6500
- buildLogger: buildLogger$2,
6501
- buildWebSocketServer: buildWebSocketServer,
6502
- db: db,
6503
- getDb: getDb,
6504
- getModelLabel: getModelLabel,
6505
- initializeDatabase: initializeDatabase,
6506
- isRunnerCommand: isRunnerCommand,
6507
- isRunnerEvent: isRunnerEvent,
6508
- normalizeModelId: normalizeModelId,
6509
- parseModelId: parseModelId,
6510
- resetDatabase: resetDatabase,
6511
- resolveAgentStrategy: resolveAgentStrategy$1,
6512
- runMigrations: runMigrations,
6513
- setTemplatesPath: setTemplatesPath$1
4645
+ __proto__: null,
4646
+ AVAILABLE_ICONS: AVAILABLE_ICONS,
4647
+ CLAUDE_SYSTEM_PROMPT: CLAUDE_SYSTEM_PROMPT,
4648
+ CODEX_SYSTEM_PROMPT: CODEX_SYSTEM_PROMPT,
4649
+ DEFAULT_AGENT_ID: DEFAULT_AGENT_ID,
4650
+ DEFAULT_CLAUDE_MODEL_ID: DEFAULT_CLAUDE_MODEL_ID,
4651
+ DEFAULT_OPENCODE_MODEL_ID: DEFAULT_OPENCODE_MODEL_ID,
4652
+ GITHUB_CHAT_MESSAGES: GITHUB_CHAT_MESSAGES,
4653
+ LEGACY_MODEL_MAP: LEGACY_MODEL_MAP,
4654
+ MODEL_METADATA: MODEL_METADATA,
4655
+ NEONDB_CHAT_MESSAGES: NEONDB_CHAT_MESSAGES,
4656
+ ProjectMetadataSchema: ProjectMetadataSchema,
4657
+ ProjectNamingSchema: ProjectNamingSchema,
4658
+ TemplateAnalysisSchema: TemplateAnalysisSchema,
4659
+ buildLogger: buildLogger$2,
4660
+ buildWebSocketServer: buildWebSocketServer,
4661
+ db: db,
4662
+ getDb: getDb,
4663
+ getModelLabel: getModelLabel,
4664
+ initializeDatabase: initializeDatabase,
4665
+ isRunnerCommand: isRunnerCommand,
4666
+ isRunnerEvent: isRunnerEvent,
4667
+ normalizeModelId: normalizeModelId,
4668
+ parseModelId: parseModelId,
4669
+ resetDatabase: resetDatabase,
4670
+ resolveAgentStrategy: resolveAgentStrategy$1,
4671
+ runMigrations: runMigrations,
4672
+ setTemplatesPath: setTemplatesPath$1
6514
4673
  });
6515
4674
 
6516
4675
  /**
6517
4676
  * Native Claude Agent SDK Integration
6518
4677
  *
6519
4678
  * This is the default SDK integration for AI-powered builds.
6520
- * For multi-provider support, set USE_OPENCODE_SDK=1 to use opencode-sdk.ts instead.
4679
+ * For multi-provider support, set ENABLE_OPENCODE_SDK=true to use opencode-sdk.ts instead.
6521
4680
  *
6522
4681
  * This module provides direct integration with the official @anthropic-ai/claude-agent-sdk
6523
4682
  * without going through the AI SDK or community provider layers.
@@ -6661,7 +4820,7 @@ function createNativeClaudeQuery(modelId = DEFAULT_CLAUDE_MODEL_ID, abortControl
6661
4820
  }
6662
4821
  const appendedSystemPrompt = systemPromptSegments.join('\n\n');
6663
4822
  // Ensure working directory exists
6664
- if (!existsSync$1(workingDirectory)) {
4823
+ if (!existsSync(workingDirectory)) {
6665
4824
  console.log(`[native-sdk] Creating working directory: ${workingDirectory}`);
6666
4825
  mkdirSync(workingDirectory, { recursive: true });
6667
4826
  }
@@ -6743,18 +4902,11 @@ function createNativeClaudeQuery(modelId = DEFAULT_CLAUDE_MODEL_ID, abortControl
6743
4902
  }
6744
4903
  catch (error) {
6745
4904
  debugLog$4(`[runner] [native-sdk] ❌ Error: ${error instanceof Error ? error.message : String(error)}\n`);
6746
- captureException(error);
4905
+ Sentry.captureException(error);
6747
4906
  throw error;
6748
4907
  }
6749
4908
  };
6750
4909
  }
6751
- /**
6752
- * Feature flag to control which implementation to use
6753
- *
6754
- * Default: Native SDK is enabled (true)
6755
- * Set USE_LEGACY_AI_SDK=1 to use the old AI SDK + community provider path
6756
- */
6757
- const USE_NATIVE_SDK = process.env.USE_LEGACY_AI_SDK !== '1';
6758
4910
 
6759
4911
  /**
6760
4912
  * OpenCode SDK Integration
@@ -6939,7 +5091,7 @@ function createOpenCodeQuery(modelId = DEFAULT_OPENCODE_MODEL_ID) {
6939
5091
  debugLog$3(`[runner] [opencode-sdk] Working dir: ${workingDirectory}`);
6940
5092
  debugLog$3(`[runner] [opencode-sdk] Prompt length: ${prompt.length}`);
6941
5093
  // Ensure working directory exists
6942
- if (!existsSync$1(workingDirectory)) {
5094
+ if (!existsSync(workingDirectory)) {
6943
5095
  console.log(`[opencode-sdk] Creating working directory: ${workingDirectory}`);
6944
5096
  mkdirSync(workingDirectory, { recursive: true });
6945
5097
  }
@@ -6963,7 +5115,7 @@ function createOpenCodeQuery(modelId = DEFAULT_OPENCODE_MODEL_ID) {
6963
5115
  let sessionId = null;
6964
5116
  // Start Sentry AI agent span for the entire OpenCode query
6965
5117
  // This provides visibility into AI operations in Sentry's trace view
6966
- const aiSpan = startInactiveSpan({
5118
+ const aiSpan = Sentry.startInactiveSpan({
6967
5119
  name: 'opencode.query',
6968
5120
  op: 'ai.pipeline',
6969
5121
  attributes: {
@@ -7102,193 +5254,566 @@ function createOpenCodeQuery(modelId = DEFAULT_OPENCODE_MODEL_ID) {
7102
5254
  }
7103
5255
  }
7104
5256
  }
7105
- finally {
7106
- reader.releaseLock();
7107
- }
7108
- // Ensure prompt request completed
7109
- const promptResponse = await promptPromise;
7110
- if (!promptResponse.ok) {
7111
- const error = await promptResponse.text();
7112
- console.error(`[opencode-sdk] Prompt request failed: ${error}`);
5257
+ finally {
5258
+ reader.releaseLock();
5259
+ }
5260
+ // Ensure prompt request completed
5261
+ const promptResponse = await promptPromise;
5262
+ if (!promptResponse.ok) {
5263
+ const error = await promptResponse.text();
5264
+ console.error(`[opencode-sdk] Prompt request failed: ${error}`);
5265
+ }
5266
+ // Yield final result if not already done
5267
+ if (!completed) {
5268
+ yield {
5269
+ type: 'result',
5270
+ result: 'completed',
5271
+ session_id: sessionId ?? undefined,
5272
+ subtype: 'success',
5273
+ };
5274
+ }
5275
+ debugLog$3('[runner] [opencode-sdk] Query complete');
5276
+ // Update span with final metrics
5277
+ aiSpan?.setAttribute('opencode.tool_calls', toolCallCount);
5278
+ aiSpan?.setAttribute('opencode.messages', messageCount);
5279
+ aiSpan?.setStatus({ code: 1 }); // OK status
5280
+ }
5281
+ catch (error) {
5282
+ debugLog$3(`[runner] [opencode-sdk] Error: ${error instanceof Error ? error.message : String(error)}`);
5283
+ Sentry.captureException(error);
5284
+ // Mark span as errored
5285
+ aiSpan?.setStatus({ code: 2, message: error instanceof Error ? error.message : String(error) });
5286
+ // Yield error result
5287
+ yield {
5288
+ type: 'result',
5289
+ result: error instanceof Error ? error.message : String(error),
5290
+ session_id: sessionId || undefined,
5291
+ subtype: 'error',
5292
+ };
5293
+ throw error;
5294
+ }
5295
+ finally {
5296
+ // End the AI span
5297
+ aiSpan?.end();
5298
+ }
5299
+ };
5300
+ }
5301
+ /**
5302
+ * Feature flag to control which implementation to use
5303
+ *
5304
+ * Default: Claude Agent SDK (native)
5305
+ * Set ENABLE_OPENCODE_SDK=true to enable OpenCode multi-provider support
5306
+ * Note: OPENCODE_URL must also be set to the OpenCode service URL
5307
+ */
5308
+ const ENABLE_OPENCODE_SDK = process.env.ENABLE_OPENCODE_SDK === 'true' && !!process.env.OPENCODE_URL;
5309
+
5310
+ class JsonLineParser {
5311
+ buffer = "";
5312
+ parse(chunk) {
5313
+ this.buffer += chunk;
5314
+ const events = [];
5315
+ const lines = this.buffer.split("\n");
5316
+ // Keep the last incomplete line in buffer
5317
+ this.buffer = lines.pop() || "";
5318
+ for (const line of lines) {
5319
+ const trimmed = line.trim();
5320
+ if (!trimmed)
5321
+ continue;
5322
+ try {
5323
+ const event = JSON.parse(trimmed);
5324
+ events.push(event);
5325
+ }
5326
+ catch {
5327
+ // Skip malformed lines
5328
+ console.error("[Parser] Failed to parse line:", trimmed.slice(0, 100));
5329
+ }
5330
+ }
5331
+ return events;
5332
+ }
5333
+ flush() {
5334
+ if (!this.buffer.trim())
5335
+ return [];
5336
+ try {
5337
+ const event = JSON.parse(this.buffer);
5338
+ this.buffer = "";
5339
+ return [event];
5340
+ }
5341
+ catch {
5342
+ this.buffer = "";
5343
+ return [];
5344
+ }
5345
+ }
5346
+ reset() {
5347
+ this.buffer = "";
5348
+ }
5349
+ }
5350
+
5351
+ function findDroidPath() {
5352
+ // Check common installation locations
5353
+ const candidates = [
5354
+ join$1(homedir(), ".local", "bin", "droid"),
5355
+ join$1(homedir(), ".factory", "bin", "droid"),
5356
+ "/usr/local/bin/droid",
5357
+ "/opt/homebrew/bin/droid",
5358
+ "droid", // Fall back to PATH lookup
5359
+ ];
5360
+ for (const candidate of candidates) {
5361
+ if (candidate === "droid" || existsSync(candidate)) {
5362
+ return candidate;
5363
+ }
5364
+ }
5365
+ return "droid"; // Let it fail with a clear error if not found
5366
+ }
5367
+ class DroidSession extends EventEmitter {
5368
+ parser = new JsonLineParser();
5369
+ sessionId = null;
5370
+ startTime = null;
5371
+ options;
5372
+ droidPath;
5373
+ constructor(options = {}) {
5374
+ super();
5375
+ this.options = options;
5376
+ this.droidPath = findDroidPath();
5377
+ this.startTime = Date.now();
5378
+ }
5379
+ buildArgs(prompt, overrides) {
5380
+ const args = ["exec", "--output-format", "stream-json"];
5381
+ // Custom droid (must come before other flags)
5382
+ if (overrides?.droid) {
5383
+ args.push("--droid", overrides.droid);
5384
+ }
5385
+ if (this.options.autonomyLevel) {
5386
+ args.push("--auto", this.options.autonomyLevel);
5387
+ }
5388
+ else {
5389
+ args.push("--auto", "high");
5390
+ }
5391
+ // Model: override > fallback
5392
+ const model = overrides?.model || this.options.model;
5393
+ if (model) {
5394
+ args.push("-m", model);
5395
+ }
5396
+ // Reasoning: override > fallback
5397
+ const reasoning = overrides?.reasoningEffort || this.options.reasoningEffort;
5398
+ if (reasoning) {
5399
+ args.push("-r", reasoning);
5400
+ }
5401
+ // Spec mode
5402
+ if (overrides?.useSpec) {
5403
+ args.push("--use-spec");
5404
+ if (overrides.specModel) {
5405
+ args.push("--spec-model", overrides.specModel);
5406
+ }
5407
+ if (overrides.specReasoningEffort) {
5408
+ args.push("--spec-reasoning-effort", overrides.specReasoningEffort);
5409
+ }
5410
+ }
5411
+ // Tool controls: override > session defaults
5412
+ const enabledTools = overrides?.enabledTools || this.options.enabledTools;
5413
+ if (enabledTools?.length) {
5414
+ args.push("--enabled-tools", enabledTools.join(","));
5415
+ }
5416
+ const disabledTools = overrides?.disabledTools || this.options.disabledTools;
5417
+ if (disabledTools?.length) {
5418
+ args.push("--disabled-tools", disabledTools.join(","));
5419
+ }
5420
+ // Use session-id for multi-turn conversation
5421
+ if (this.sessionId) {
5422
+ args.push("-s", this.sessionId);
5423
+ }
5424
+ // Prepend system prompt to the first message only (when no session exists yet)
5425
+ let finalPrompt = prompt;
5426
+ if (this.options.systemPrompt && !this.sessionId) {
5427
+ finalPrompt = `${this.options.systemPrompt}\n\n---\n\nTask: ${prompt}`;
5428
+ }
5429
+ args.push(finalPrompt);
5430
+ return args;
5431
+ }
5432
+ async *send(prompt, options) {
5433
+ const args = this.buildArgs(prompt, options);
5434
+ const cwd = this.options.cwd || process.cwd();
5435
+ const effectiveModel = options?.model || this.options.model || "default";
5436
+ const effectiveReasoning = options?.reasoningEffort || this.options.reasoningEffort || "default";
5437
+ const extras = [];
5438
+ if (options?.useSpec)
5439
+ extras.push("spec");
5440
+ if (options?.droid)
5441
+ extras.push(`droid=${options.droid}`);
5442
+ const extraInfo = extras.length ? ` [${extras.join(", ")}]` : "";
5443
+ console.log(`[DroidSession] Running (model=${effectiveModel}, reasoning=${effectiveReasoning})${extraInfo}:`, this.droidPath, args.slice(0, 5).join(" "), "...");
5444
+ const child = spawn(this.droidPath, args, {
5445
+ cwd,
5446
+ stdio: ["pipe", "pipe", "pipe"],
5447
+ });
5448
+ this.parser.reset();
5449
+ let finalResult = null;
5450
+ let resolveNext = null;
5451
+ const eventQueue = [];
5452
+ const pushEvent = (event) => {
5453
+ if (resolveNext) {
5454
+ resolveNext(event);
5455
+ resolveNext = null;
5456
+ }
5457
+ else {
5458
+ eventQueue.push(event);
5459
+ }
5460
+ };
5461
+ child.stdout?.on("data", (data) => {
5462
+ const events = this.parser.parse(data.toString());
5463
+ for (const event of events) {
5464
+ // Capture session ID from init event
5465
+ if (event.type === "system" && event.subtype === "init") {
5466
+ this.sessionId = event.session_id;
5467
+ }
5468
+ this.emit("event", event);
5469
+ pushEvent(event);
5470
+ }
5471
+ });
5472
+ child.stderr?.on("data", (data) => {
5473
+ console.error("[DroidSession] stderr:", data.toString());
5474
+ });
5475
+ const processEnded = new Promise((resolve, reject) => {
5476
+ child.on("close", (code) => {
5477
+ // Flush any remaining data
5478
+ const remaining = this.parser.flush();
5479
+ for (const event of remaining) {
5480
+ pushEvent(event);
5481
+ }
5482
+ this.emit("close", code);
5483
+ resolve();
5484
+ });
5485
+ child.on("error", (err) => {
5486
+ console.error("[DroidSession] Process error:", err);
5487
+ this.emit("error", err);
5488
+ reject(err);
5489
+ });
5490
+ });
5491
+ const nextEvent = () => {
5492
+ if (eventQueue.length > 0) {
5493
+ return Promise.resolve(eventQueue.shift());
5494
+ }
5495
+ return new Promise((resolve) => {
5496
+ resolveNext = resolve;
5497
+ });
5498
+ };
5499
+ // Yield events until completion
5500
+ while (true) {
5501
+ const event = await Promise.race([
5502
+ nextEvent(),
5503
+ processEnded.then(() => null),
5504
+ ]);
5505
+ if (!event) {
5506
+ // Process ended without completion event
5507
+ if (!finalResult) {
5508
+ throw new Error("Process ended without completion");
5509
+ }
5510
+ break;
5511
+ }
5512
+ yield event;
5513
+ if (event.type === "completion") {
5514
+ finalResult = {
5515
+ success: true,
5516
+ text: event.finalText,
5517
+ sessionId: event.session_id,
5518
+ numTurns: event.numTurns,
5519
+ durationMs: event.durationMs,
5520
+ };
5521
+ break;
5522
+ }
5523
+ }
5524
+ return finalResult;
5525
+ }
5526
+ async close() {
5527
+ this.sessionId = null;
5528
+ this.startTime = null;
5529
+ this.parser.reset();
5530
+ }
5531
+ isAlive() {
5532
+ return this.sessionId !== null;
5533
+ }
5534
+ getSessionId() {
5535
+ return this.sessionId;
5536
+ }
5537
+ getUptime() {
5538
+ if (!this.startTime)
5539
+ return 0;
5540
+ return Date.now() - this.startTime;
5541
+ }
5542
+ getModel() {
5543
+ return this.options.model || "default";
5544
+ }
5545
+ getCwd() {
5546
+ return this.options.cwd || process.cwd();
5547
+ }
5548
+ updateOptions(options) {
5549
+ this.options = { ...this.options, ...options };
5550
+ }
5551
+ }
5552
+
5553
+ /**
5554
+ * Factory Droid SDK Integration
5555
+ *
5556
+ * This module provides integration with the Factory Droid SDK (droid-sdk)
5557
+ * for AI-powered builds. It wraps the DroidSession class and transforms
5558
+ * DroidEvents into the standard TransformedMessage format used by the runner.
5559
+ *
5560
+ * Enable by setting agent to 'factory-droid' in build requests.
5561
+ */
5562
+ const debugLog$2 = (message) => {
5563
+ if (process.env.SILENT_MODE !== '1' && process.env.DEBUG_BUILD === '1') {
5564
+ console.log(message);
5565
+ }
5566
+ };
5567
+ /**
5568
+ * Transform a DroidEvent to our internal TransformedMessage format
5569
+ */
5570
+ function transformDroidEvent(event) {
5571
+ switch (event.type) {
5572
+ case 'system': {
5573
+ // System init event - emit session info
5574
+ if (event.subtype === 'init') {
5575
+ return {
5576
+ type: 'system',
5577
+ subtype: 'init',
5578
+ session_id: event.session_id,
5579
+ };
5580
+ }
5581
+ return null;
5582
+ }
5583
+ case 'message': {
5584
+ // Message events from assistant
5585
+ if (event.role === 'assistant') {
5586
+ let text = event.text || '';
5587
+ // Filter out TodoWrite JSON that might be embedded in message text
5588
+ // The droid CLI sometimes echoes the todo list as JSON in its message
5589
+ if (text.includes('[{"activeForm"') || text.includes('[{"content"')) {
5590
+ // Try to remove JSON array that looks like todos
5591
+ text = text.replace(/\[(\s*\{[^[\]]*"(activeForm|content|status)"[^[\]]*\}\s*,?\s*)+\]/g, '').trim();
5592
+ }
5593
+ // Skip empty messages after filtering
5594
+ if (!text) {
5595
+ return null;
5596
+ }
5597
+ return {
5598
+ type: 'assistant',
5599
+ message: {
5600
+ id: event.id,
5601
+ content: [{ type: 'text', text }],
5602
+ },
5603
+ };
5604
+ }
5605
+ return null;
5606
+ }
5607
+ case 'tool_call': {
5608
+ // Tool call events - map to tool_use format
5609
+ let input = event.parameters;
5610
+ // Special handling for TodoWrite - droid CLI may send different formats,
5611
+ // but UI expects array format: { todos: [{ content, status, activeForm? }] }
5612
+ if (event.toolName === 'TodoWrite' && input) {
5613
+ const rawInput = input;
5614
+ // Case 1: todos is already an array (pass through with normalization)
5615
+ if (Array.isArray(rawInput.todos)) {
5616
+ const normalizedTodos = rawInput.todos.map((item) => ({
5617
+ content: item.content || item.activeForm || 'Untitled task',
5618
+ status: item.status || 'pending',
5619
+ activeForm: item.activeForm,
5620
+ }));
5621
+ input = { todos: normalizedTodos };
5622
+ process.stderr.write(`[runner] [droid-sdk] TodoWrite: ${normalizedTodos.length} todos (already array)\n`);
5623
+ }
5624
+ // Case 2: todos is a string (needs parsing)
5625
+ else if (typeof rawInput.todos === 'string') {
5626
+ const rawTodos = rawInput.todos;
5627
+ let parsedTodos = [];
5628
+ // Try to parse as JSON first (droid may send serialized array)
5629
+ const trimmed = rawTodos.trim();
5630
+ if (trimmed.startsWith('[')) {
5631
+ try {
5632
+ const jsonParsed = JSON.parse(trimmed);
5633
+ if (Array.isArray(jsonParsed)) {
5634
+ parsedTodos = jsonParsed.map((item) => ({
5635
+ content: item.content || item.activeForm || 'Untitled task',
5636
+ status: item.status || 'pending',
5637
+ activeForm: item.activeForm,
5638
+ }));
5639
+ process.stderr.write(`[runner] [droid-sdk] TodoWrite: ${parsedTodos.length} todos parsed from JSON string\n`);
5640
+ }
5641
+ }
5642
+ catch {
5643
+ // Not valid JSON, fall through to line-by-line parsing
5644
+ }
5645
+ }
5646
+ // If JSON parsing didn't work, try numbered format: "1. [completed] Task"
5647
+ if (parsedTodos.length === 0) {
5648
+ const lines = rawTodos.split('\n').filter((l) => l.trim());
5649
+ parsedTodos = lines.map((line) => {
5650
+ const match = line.match(/^\d+\.\s*\[(\w+)\]\s*(.+)$/);
5651
+ if (match) {
5652
+ return {
5653
+ content: match[2].trim(),
5654
+ status: match[1].toLowerCase(),
5655
+ };
5656
+ }
5657
+ return {
5658
+ content: line.replace(/^\d+\.\s*/, '').trim(),
5659
+ status: 'pending',
5660
+ };
5661
+ });
5662
+ process.stderr.write(`[runner] [droid-sdk] TodoWrite: ${parsedTodos.length} todos parsed from numbered format\n`);
5663
+ }
5664
+ input = { todos: parsedTodos };
5665
+ }
5666
+ }
5667
+ return {
5668
+ type: 'assistant',
5669
+ message: {
5670
+ id: event.messageId,
5671
+ content: [{
5672
+ type: 'tool_use',
5673
+ id: event.toolId,
5674
+ name: event.toolName,
5675
+ input,
5676
+ }],
5677
+ },
5678
+ };
5679
+ }
5680
+ case 'tool_result': {
5681
+ // Tool result events - map to tool_result format
5682
+ return {
5683
+ type: 'user',
5684
+ message: {
5685
+ id: event.messageId,
5686
+ content: [{
5687
+ type: 'tool_result',
5688
+ tool_use_id: event.toolId,
5689
+ content: event.value,
5690
+ is_error: event.isError,
5691
+ }],
5692
+ },
5693
+ };
5694
+ }
5695
+ case 'completion': {
5696
+ // Completion event - final result
5697
+ return {
5698
+ type: 'result',
5699
+ result: event.finalText,
5700
+ session_id: event.session_id,
5701
+ usage: {
5702
+ numTurns: event.numTurns,
5703
+ durationMs: event.durationMs,
5704
+ },
5705
+ };
5706
+ }
5707
+ default:
5708
+ return null;
5709
+ }
5710
+ }
5711
+ /**
5712
+ * Create a Factory Droid query function
5713
+ *
5714
+ * This implements the same BuildQueryFn interface as createNativeClaudeQuery
5715
+ * but uses the Factory Droid SDK instead.
5716
+ */
5717
+ function createDroidQuery(modelId) {
5718
+ return async function* droidQuery(prompt, workingDirectory, systemPrompt, _agent, _codexThreadId, _messageParts) {
5719
+ debugLog$2('[runner] [droid-sdk] Starting Factory Droid query');
5720
+ debugLog$2(`[runner] [droid-sdk] Model: ${modelId || 'default'}`);
5721
+ debugLog$2(`[runner] [droid-sdk] Working dir: ${workingDirectory}`);
5722
+ debugLog$2(`[runner] [droid-sdk] Prompt length: ${prompt.length}`);
5723
+ // Build combined system prompt
5724
+ const systemPromptSegments = [CLAUDE_SYSTEM_PROMPT.trim()];
5725
+ if (systemPrompt && systemPrompt.trim().length > 0) {
5726
+ systemPromptSegments.push(systemPrompt.trim());
5727
+ }
5728
+ const combinedSystemPrompt = systemPromptSegments.join('\n\n');
5729
+ // Ensure working directory exists
5730
+ if (!existsSync(workingDirectory)) {
5731
+ console.log(`[droid-sdk] Creating working directory: ${workingDirectory}`);
5732
+ mkdirSync(workingDirectory, { recursive: true });
5733
+ }
5734
+ // Configure DroidSession options
5735
+ const sessionOptions = {
5736
+ model: modelId,
5737
+ cwd: workingDirectory,
5738
+ autonomyLevel: 'high',
5739
+ systemPrompt: combinedSystemPrompt,
5740
+ };
5741
+ // Force log to stderr so it shows in TUI mode
5742
+ process.stderr.write(`[runner] [droid-sdk] Creating DroidSession with model=${modelId || 'default'}, cwd=${workingDirectory}\n`);
5743
+ const session = new DroidSession(sessionOptions);
5744
+ // Listen for error events from the session (these bubble up from the child process)
5745
+ session.on('error', (err) => {
5746
+ process.stderr.write(`[runner] [droid-sdk] Session error event: ${err.message}\n`);
5747
+ if (err.stack) {
5748
+ process.stderr.write(`[runner] [droid-sdk] Stack: ${err.stack}\n`);
7113
5749
  }
7114
- // Yield final result if not already done
7115
- if (!completed) {
7116
- yield {
7117
- type: 'result',
7118
- result: 'completed',
7119
- session_id: sessionId ?? undefined,
7120
- subtype: 'success',
7121
- };
5750
+ });
5751
+ session.on('close', (code) => {
5752
+ process.stderr.write(`[runner] [droid-sdk] Session closed with code: ${code}\n`);
5753
+ });
5754
+ let messageCount = 0;
5755
+ let toolCallCount = 0;
5756
+ let lastEventType = '';
5757
+ try {
5758
+ process.stderr.write(`[runner] [droid-sdk] Sending prompt to Droid (${prompt.length} chars)\n`);
5759
+ // Stream events from the Droid SDK
5760
+ for await (const event of session.send(prompt)) {
5761
+ messageCount++;
5762
+ lastEventType = event.type;
5763
+ // Log every event type to track progress
5764
+ if (event.type === 'tool_call') {
5765
+ toolCallCount++;
5766
+ process.stderr.write(`[runner] [droid-sdk] Event #${messageCount}: tool_call - ${event.toolName}\n`);
5767
+ }
5768
+ else if (event.type === 'tool_result') {
5769
+ const toolResult = event;
5770
+ const status = toolResult.isError ? 'ERROR' : 'OK';
5771
+ const preview = (toolResult.value || '').substring(0, 50);
5772
+ process.stderr.write(`[runner] [droid-sdk] Event #${messageCount}: tool_result [${status}] ${preview}...\n`);
5773
+ }
5774
+ else {
5775
+ process.stderr.write(`[runner] [droid-sdk] Event #${messageCount}: ${event.type}\n`);
5776
+ }
5777
+ // Transform Droid event to our internal format
5778
+ const transformed = transformDroidEvent(event);
5779
+ if (transformed) {
5780
+ yield transformed;
5781
+ }
5782
+ // Log completion
5783
+ if (event.type === 'completion') {
5784
+ process.stderr.write(`[runner] [droid-sdk] ✅ Query complete - ${event.numTurns} turns, ${event.durationMs}ms\n`);
5785
+ }
7122
5786
  }
7123
- debugLog$3('[runner] [opencode-sdk] Query complete');
7124
- // Update span with final metrics
7125
- aiSpan?.setAttribute('opencode.tool_calls', toolCallCount);
7126
- aiSpan?.setAttribute('opencode.messages', messageCount);
7127
- aiSpan?.setStatus({ code: 1 }); // OK status
5787
+ process.stderr.write(`[runner] [droid-sdk] Stream finished - ${messageCount} events, ${toolCallCount} tool calls, last event: ${lastEventType}\n`);
7128
5788
  }
7129
5789
  catch (error) {
7130
- debugLog$3(`[runner] [opencode-sdk] Error: ${error instanceof Error ? error.message : String(error)}`);
7131
- captureException(error);
7132
- // Mark span as errored
7133
- aiSpan?.setStatus({ code: 2, message: error instanceof Error ? error.message : String(error) });
7134
- // Yield error result
7135
- yield {
7136
- type: 'result',
7137
- result: error instanceof Error ? error.message : String(error),
7138
- session_id: sessionId || undefined,
7139
- subtype: 'error',
7140
- };
5790
+ const errorMessage = error instanceof Error ? error.message : String(error);
5791
+ const errorStack = error instanceof Error ? error.stack : '';
5792
+ // Force write to stderr to bypass TUI silent mode
5793
+ process.stderr.write(`\n[runner] [droid-sdk] Error: ${errorMessage}\n`);
5794
+ process.stderr.write(`[runner] [droid-sdk] Stack: ${errorStack}\n`);
5795
+ process.stderr.write(`[runner] [droid-sdk] Model: ${modelId || 'default'}\n`);
5796
+ process.stderr.write(`[runner] [droid-sdk] Working dir: ${workingDirectory}\n`);
5797
+ process.stderr.write(`[runner] [droid-sdk] Events received: ${messageCount}\n`);
5798
+ process.stderr.write(`[runner] [droid-sdk] Tool calls: ${toolCallCount}\n`);
5799
+ process.stderr.write(`[runner] [droid-sdk] Last event type: ${lastEventType}\n\n`);
5800
+ Sentry.captureException(error, {
5801
+ extra: {
5802
+ modelId,
5803
+ workingDirectory,
5804
+ promptLength: prompt.length,
5805
+ eventsReceived: messageCount,
5806
+ toolCalls: toolCallCount,
5807
+ }
5808
+ });
7141
5809
  throw error;
7142
5810
  }
7143
5811
  finally {
7144
- // End the AI span
7145
- aiSpan?.end();
5812
+ // Cleanup session
5813
+ await session.close();
7146
5814
  }
7147
5815
  };
7148
5816
  }
7149
- /**
7150
- * Feature flag to control which implementation to use
7151
- *
7152
- * Default: Claude Agent SDK (native)
7153
- * Set USE_OPENCODE_SDK=1 to enable OpenCode multi-provider support
7154
- * Note: OPENCODE_URL must also be set to the OpenCode service URL
7155
- */
7156
- const USE_OPENCODE_SDK = process.env.USE_OPENCODE_SDK === '1' && !!process.env.OPENCODE_URL;
7157
-
7158
- // src/lib/claude/tools.ts
7159
- var genericInput = z.any();
7160
- var genericObject = z.record(z.string(), z.any());
7161
- var dynamicMcpTool = {
7162
- description: "Generic MCP tool passthrough",
7163
- type: "dynamic",
7164
- inputSchema: genericObject
7165
- };
7166
- var fallbackTool = {
7167
- description: "Fallback Claude CLI tool",
7168
- inputSchema: genericInput
7169
- };
7170
- var builtinTools = {
7171
- Agent: {
7172
- description: "Launches a nested Claude agent",
7173
- inputSchema: genericObject
7174
- },
7175
- Bash: {
7176
- description: "Execute shell commands via Claude CLI",
7177
- inputSchema: genericObject
7178
- },
7179
- BashOutput: {
7180
- description: "Fetch output from a background bash process",
7181
- inputSchema: genericObject
7182
- },
7183
- ExitPlanMode: {
7184
- description: "Exit plan/approval mode with a summary",
7185
- inputSchema: genericObject
7186
- },
7187
- Read: {
7188
- description: "Read file contents",
7189
- inputSchema: genericObject
7190
- },
7191
- Write: {
7192
- description: "Write entire file contents",
7193
- inputSchema: genericObject
7194
- },
7195
- Edit: {
7196
- description: "Edit part of a file by replacing text",
7197
- inputSchema: genericObject
7198
- },
7199
- Glob: {
7200
- description: "Match files using glob patterns",
7201
- inputSchema: genericObject
7202
- },
7203
- Grep: {
7204
- description: "Search files for a regex pattern",
7205
- inputSchema: genericObject
7206
- },
7207
- KillShell: {
7208
- description: "Stop a running bash session",
7209
- inputSchema: genericObject
7210
- },
7211
- ListMcpResources: {
7212
- description: "Enumerate MCP resources",
7213
- inputSchema: genericObject
7214
- },
7215
- Mcp: {
7216
- description: "Invoke an MCP tool",
7217
- inputSchema: genericObject
7218
- },
7219
- NotebookEdit: {
7220
- description: "Edit Jupyter notebooks",
7221
- inputSchema: genericObject
7222
- },
7223
- ReadMcpResource: {
7224
- description: "Read a particular MCP resource",
7225
- inputSchema: genericObject
7226
- },
7227
- TimeMachine: {
7228
- description: "Rewind conversation with a course correction",
7229
- inputSchema: genericObject
7230
- },
7231
- TodoWrite: {
7232
- description: "Emit TODO status updates",
7233
- inputSchema: genericObject
7234
- },
7235
- WebFetch: {
7236
- description: "Fetch and summarize a URL",
7237
- inputSchema: genericObject
7238
- },
7239
- WebSearch: {
7240
- description: "Search the web with constraints",
7241
- inputSchema: genericObject
7242
- },
7243
- MultipleChoiceQuestion: {
7244
- description: "Ask the user a multiple choice question",
7245
- inputSchema: genericObject
7246
- },
7247
- // Common aliases seen in telemetry/logs
7248
- LS: {
7249
- description: "List files in the working directory",
7250
- inputSchema: genericObject
7251
- },
7252
- TodoRead: {
7253
- description: "Read TODO state emitted from runner",
7254
- inputSchema: genericObject
7255
- }
7256
- };
7257
- var proxyHandler = {
7258
- get(target, prop) {
7259
- if (typeof prop !== "string") {
7260
- return Reflect.get(target, prop);
7261
- }
7262
- if (Reflect.has(target, prop)) {
7263
- return Reflect.get(target, prop);
7264
- }
7265
- if (prop.startsWith("mcp__")) {
7266
- return dynamicMcpTool;
7267
- }
7268
- return fallbackTool;
7269
- },
7270
- has(target, prop) {
7271
- if (typeof prop === "string") {
7272
- if (Reflect.has(target, prop) || prop.startsWith("mcp__")) {
7273
- return true;
7274
- }
7275
- }
7276
- return Reflect.has(target, prop);
7277
- },
7278
- ownKeys(target) {
7279
- return Reflect.ownKeys(target);
7280
- },
7281
- getOwnPropertyDescriptor(target, prop) {
7282
- if (Reflect.has(target, prop)) {
7283
- return Object.getOwnPropertyDescriptor(target, prop);
7284
- }
7285
- return void 0;
7286
- }
7287
- };
7288
- function createClaudeToolRegistry() {
7289
- return new Proxy(builtinTools, proxyHandler);
7290
- }
7291
- var CLAUDE_CLI_TOOL_REGISTRY = createClaudeToolRegistry();
7292
5817
 
7293
5818
  // src/lib/logging/build-logger.ts
7294
5819
  var BuildLogger = class {
@@ -7344,7 +5869,7 @@ var BuildLogger = class {
7344
5869
  if (typeof process !== "undefined" && process.env.NODE_ENV !== "test") {
7345
5870
  try {
7346
5871
  if (level === "warn" || level === "error") {
7347
- addBreadcrumb({
5872
+ Sentry.addBreadcrumb({
7348
5873
  category: `build-logger.${context}`,
7349
5874
  message,
7350
5875
  level: level === "error" ? "error" : "warning",
@@ -7730,7 +6255,8 @@ var init_tags = __esm({
7730
6255
  description: "Anthropic Claude - Balanced performance and speed",
7731
6256
  logo: "/claude.png",
7732
6257
  provider: "claude-code",
7733
- model: "claude-sonnet-4-5"
6258
+ model: "claude-sonnet-4-5",
6259
+ sdk: "agent"
7734
6260
  },
7735
6261
  {
7736
6262
  value: "claude-opus-4-5",
@@ -7738,7 +6264,8 @@ var init_tags = __esm({
7738
6264
  description: "Anthropic Claude - Most capable for complex tasks",
7739
6265
  logo: "/claude.png",
7740
6266
  provider: "claude-code",
7741
- model: "claude-opus-4-5"
6267
+ model: "claude-opus-4-5",
6268
+ sdk: "agent"
7742
6269
  },
7743
6270
  {
7744
6271
  value: "claude-haiku-4-5",
@@ -7746,7 +6273,8 @@ var init_tags = __esm({
7746
6273
  description: "Anthropic Claude - Fastest, good for iterations",
7747
6274
  logo: "/claude.png",
7748
6275
  provider: "claude-code",
7749
- model: "claude-haiku-4-5"
6276
+ model: "claude-haiku-4-5",
6277
+ sdk: "agent"
7750
6278
  },
7751
6279
  {
7752
6280
  value: "gpt-5.2-codex",
@@ -7754,7 +6282,35 @@ var init_tags = __esm({
7754
6282
  description: "OpenAI Codex - Advanced code generation",
7755
6283
  logo: "/openai.png",
7756
6284
  provider: "openai-codex",
7757
- model: "openai/gpt-5.2-codex"
6285
+ model: "openai/gpt-5.2-codex",
6286
+ sdk: "agent"
6287
+ },
6288
+ {
6289
+ value: "factory-droid-opus",
6290
+ label: "Factory Droid (Opus)",
6291
+ description: "Factory Droid SDK with Claude Opus 4.5",
6292
+ logo: "/factory.svg",
6293
+ provider: "factory-droid",
6294
+ model: "claude-opus-4-5-20251101",
6295
+ sdk: "droid"
6296
+ },
6297
+ {
6298
+ value: "factory-droid-codex",
6299
+ label: "Factory Droid (Codex)",
6300
+ description: "Factory Droid SDK with GPT-5.2 Codex",
6301
+ logo: "/factory.svg",
6302
+ provider: "factory-droid",
6303
+ model: "gpt-5.2-codex",
6304
+ sdk: "droid"
6305
+ },
6306
+ {
6307
+ value: "factory-droid-glm",
6308
+ label: "Factory Droid (GLM)",
6309
+ description: "Factory Droid SDK with GLM 4.7",
6310
+ logo: "/factory.svg",
6311
+ provider: "factory-droid",
6312
+ model: "glm-4.7",
6313
+ sdk: "droid"
7758
6314
  }
7759
6315
  ]
7760
6316
  },
@@ -8755,45 +7311,133 @@ STEP 5: VERIFY BUILD
8755
7311
 
8756
7312
  After successful build, include final JSON code block with all tasks "completed"
8757
7313
 
8758
- CRITICAL: Complete ALL 5 steps in this single session.
8759
- Each bash command runs in a fresh shell - prefix with "cd ${context.projectName} &&"
8760
- Only respond "Implementation complete" after ALL steps are verified.`;
7314
+ CRITICAL: Complete ALL 5 steps in this single session.
7315
+ Each bash command runs in a fresh shell - prefix with "cd ${context.projectName} &&"
7316
+ Only respond "Implementation complete" after ALL steps are verified.`;
7317
+ }
7318
+ var codexStrategy, codex_strategy_default;
7319
+ var init_codex_strategy = __esm({
7320
+ "src/lib/agents/codex-strategy.ts"() {
7321
+ init_template_catalog();
7322
+ init_events();
7323
+ init_load_template_context();
7324
+ init_resolver();
7325
+ codexStrategy = {
7326
+ buildSystemPromptSections: buildCodexSections,
7327
+ buildFullPrompt: buildFullPrompt2,
7328
+ shouldDownloadTemplate() {
7329
+ return false;
7330
+ },
7331
+ resolveWorkingDirectory(context) {
7332
+ if (!context.isNewProject) {
7333
+ return context.workingDirectory;
7334
+ }
7335
+ return getParentDirectory(context.workingDirectory);
7336
+ },
7337
+ async getTemplateSelectionContext(context) {
7338
+ return loadTemplateSelectionContext2(context);
7339
+ },
7340
+ processRunnerEvent(state, event) {
7341
+ if (!state || typeof state !== "object") {
7342
+ return state;
7343
+ }
7344
+ const codexLike = state;
7345
+ if (!Array.isArray(codexLike?.phases)) {
7346
+ return state;
7347
+ }
7348
+ const next = processCodexEvent(codexLike, event);
7349
+ return next;
7350
+ }
7351
+ };
7352
+ codex_strategy_default = codexStrategy;
7353
+ }
7354
+ });
7355
+
7356
+ // src/lib/agents/droid-strategy.ts
7357
+ var droid_strategy_exports = {};
7358
+ __export(droid_strategy_exports, {
7359
+ default: () => droid_strategy_default
7360
+ });
7361
+ function buildDroidSections(context) {
7362
+ const sections = [];
7363
+ if (context.tags && context.tags.length > 0) {
7364
+ const resolved = resolveTags(context.tags);
7365
+ const tagPrompt = generatePromptFromTags(resolved, context.projectName, context.isNewProject);
7366
+ if (tagPrompt) {
7367
+ sections.push(tagPrompt);
7368
+ }
7369
+ }
7370
+ if (context.isNewProject) {
7371
+ sections.push(`## New Project Setup
7372
+
7373
+ - Project name: ${context.projectName}
7374
+ - Location: ${context.workingDirectory}
7375
+ - Operation type: ${context.operationType}
7376
+
7377
+ The template has already been downloaded. Install dependencies and customize the scaffold to satisfy the request.`);
7378
+ } else {
7379
+ let existingProjectSection = `## Existing Project Context
7380
+
7381
+ - Project location: ${context.workingDirectory}
7382
+ - Operation type: ${context.operationType}
7383
+
7384
+ Review the current codebase and apply the requested changes without re-scaffolding.`;
7385
+ if (context.conversationHistory && context.conversationHistory.length > 0) {
7386
+ existingProjectSection += `
7387
+
7388
+ **Recent Conversation History:**
7389
+ `;
7390
+ context.conversationHistory.forEach((msg, index) => {
7391
+ const roleLabel = msg.role === "user" ? "User" : "Assistant";
7392
+ const content = msg.content.length > 500 ? msg.content.substring(0, 500) + "...[truncated]" : msg.content;
7393
+ existingProjectSection += `${index + 1}. ${roleLabel}:
7394
+ ${content}
7395
+
7396
+ `;
7397
+ });
7398
+ }
7399
+ sections.push(existingProjectSection);
7400
+ }
7401
+ sections.push(`## Workspace Rules
7402
+ - Use relative paths within the project.
7403
+ - Work inside the existing project structure.
7404
+ - Provide complete file contents without placeholders.`);
7405
+ if (context.fileTree) {
7406
+ sections.push(`## Project Structure
7407
+ ${context.fileTree}`);
7408
+ }
7409
+ if (context.templateName) {
7410
+ sections.push(`## Template
7411
+ - Name: ${context.templateName}
7412
+ - Framework: ${context.templateFramework ?? "unknown"}`);
7413
+ }
7414
+ return sections;
7415
+ }
7416
+ function buildFullPrompt3(context, basePrompt) {
7417
+ if (!context.isNewProject) {
7418
+ return basePrompt;
7419
+ }
7420
+ return `${basePrompt}
7421
+
7422
+ CRITICAL: The template has already been prepared in ${context.workingDirectory}. Do not scaffold a new project.`;
8761
7423
  }
8762
- var codexStrategy, codex_strategy_default;
8763
- var init_codex_strategy = __esm({
8764
- "src/lib/agents/codex-strategy.ts"() {
8765
- init_template_catalog();
8766
- init_events();
8767
- init_load_template_context();
7424
+ var droidStrategy, droid_strategy_default;
7425
+ var init_droid_strategy = __esm({
7426
+ "src/lib/agents/droid-strategy.ts"() {
8768
7427
  init_resolver();
8769
- codexStrategy = {
8770
- buildSystemPromptSections: buildCodexSections,
8771
- buildFullPrompt: buildFullPrompt2,
8772
- shouldDownloadTemplate() {
8773
- return false;
8774
- },
8775
- resolveWorkingDirectory(context) {
8776
- if (!context.isNewProject) {
8777
- return context.workingDirectory;
8778
- }
8779
- return getParentDirectory(context.workingDirectory);
8780
- },
8781
- async getTemplateSelectionContext(context) {
8782
- return loadTemplateSelectionContext2(context);
7428
+ droidStrategy = {
7429
+ buildSystemPromptSections: buildDroidSections,
7430
+ buildFullPrompt: buildFullPrompt3,
7431
+ shouldDownloadTemplate(context) {
7432
+ return context.isNewProject && !context.skipTemplates;
8783
7433
  },
8784
- processRunnerEvent(state, event) {
8785
- if (!state || typeof state !== "object") {
8786
- return state;
8787
- }
8788
- const codexLike = state;
8789
- if (!Array.isArray(codexLike?.phases)) {
8790
- return state;
8791
- }
8792
- const next = processCodexEvent(codexLike, event);
8793
- return next;
7434
+ postTemplateSelected(context, template) {
7435
+ context.templateName = template.name;
7436
+ context.templateFramework = template.framework;
7437
+ context.fileTree = template.fileTree;
8794
7438
  }
8795
7439
  };
8796
- codex_strategy_default = codexStrategy;
7440
+ droid_strategy_default = droidStrategy;
8797
7441
  }
8798
7442
  });
8799
7443
 
@@ -8816,12 +7460,18 @@ async function ensureRegistry() {
8816
7460
  if (initialized) {
8817
7461
  return;
8818
7462
  }
8819
- const [{ default: claudeStrategy2 }, { default: codexStrategy2 }] = await Promise.all([
7463
+ const [
7464
+ { default: claudeStrategy2 },
7465
+ { default: codexStrategy2 },
7466
+ { default: droidStrategy2 }
7467
+ ] = await Promise.all([
8820
7468
  Promise.resolve().then(() => (init_claude_strategy(), claude_strategy_exports)),
8821
- Promise.resolve().then(() => (init_codex_strategy(), codex_strategy_exports))
7469
+ Promise.resolve().then(() => (init_codex_strategy(), codex_strategy_exports)),
7470
+ Promise.resolve().then(() => (init_droid_strategy(), droid_strategy_exports))
8822
7471
  ]);
8823
7472
  registerAgentStrategy("claude-code", claudeStrategy2);
8824
7473
  registerAgentStrategy("openai-codex", codexStrategy2);
7474
+ registerAgentStrategy("factory-droid", droidStrategy2);
8825
7475
  initialized = true;
8826
7476
  }
8827
7477
  async function resolveAgentStrategy(agentId) {
@@ -8830,9 +7480,9 @@ async function resolveAgentStrategy(agentId) {
8830
7480
  }
8831
7481
 
8832
7482
  // Debug logging helper - suppressed in TUI mode (SILENT_MODE=1)
8833
- const debugLog$2 = (message) => {
7483
+ const debugLog$1 = (message) => {
8834
7484
  if (process.env.SILENT_MODE !== '1' && process.env.DEBUG_BUILD === '1') {
8835
- debugLog$2();
7485
+ debugLog$1();
8836
7486
  }
8837
7487
  };
8838
7488
  /**
@@ -8843,7 +7493,7 @@ async function createBuildStream(options) {
8843
7493
  // For Codex on NEW projects, use parent directory as CWD (Codex will create the project dir)
8844
7494
  // For everything else, use the project directory
8845
7495
  const strategy = await resolveAgentStrategy(agent);
8846
- const projectName = options.projectName || require$$1.basename(workingDirectory);
7496
+ const projectName = options.projectName || path$1.basename(workingDirectory);
8847
7497
  const strategyContext = {
8848
7498
  projectId: options.projectId,
8849
7499
  projectName,
@@ -8858,7 +7508,7 @@ async function createBuildStream(options) {
8858
7508
  if (process.env.DEBUG_BUILD === '1')
8859
7509
  console.log(`[engine] Strategy adjusted CWD to: ${actualWorkingDir}`);
8860
7510
  }
8861
- else if (!existsSync$1(workingDirectory)) {
7511
+ else if (!existsSync(workingDirectory)) {
8862
7512
  mkdirSync(workingDirectory, { recursive: true });
8863
7513
  }
8864
7514
  if (!resolvedDir) {
@@ -8877,19 +7527,19 @@ async function createBuildStream(options) {
8877
7527
  // Pass prompt, working directory, and system prompt to the query function
8878
7528
  // The buildQuery wrapper will configure the SDK with all options
8879
7529
  // Use actualWorkingDir so the query function gets the correct CWD
8880
- debugLog$2();
7530
+ debugLog$1();
8881
7531
  const generator = query(fullPrompt, actualWorkingDir, systemPrompt, agent, options.codexThreadId, messageParts);
8882
- debugLog$2();
7532
+ debugLog$1();
8883
7533
  // Create a ReadableStream from the AsyncGenerator
8884
7534
  const stream = new ReadableStream({
8885
7535
  async start(controller) {
8886
- debugLog$2();
7536
+ debugLog$1();
8887
7537
  let chunkCount = 0;
8888
7538
  try {
8889
7539
  for await (const chunk of generator) {
8890
7540
  chunkCount++;
8891
7541
  if (chunkCount % 5 === 0) {
8892
- debugLog$2(`[runner] [build-engine] Processed ${chunkCount} chunks from generator\n`);
7542
+ debugLog$1(`[runner] [build-engine] Processed ${chunkCount} chunks from generator\n`);
8893
7543
  }
8894
7544
  // Convert chunk to appropriate format
8895
7545
  if (typeof chunk === 'string') {
@@ -8902,11 +7552,11 @@ async function createBuildStream(options) {
8902
7552
  controller.enqueue(new TextEncoder().encode(JSON.stringify(chunk)));
8903
7553
  }
8904
7554
  }
8905
- debugLog$2(`[runner] [build-engine] ✅ Generator exhausted after ${chunkCount} chunks, closing stream\n`);
7555
+ debugLog$1(`[runner] [build-engine] ✅ Generator exhausted after ${chunkCount} chunks, closing stream\n`);
8906
7556
  controller.close();
8907
7557
  }
8908
7558
  catch (error) {
8909
- debugLog$2();
7559
+ debugLog$1();
8910
7560
  controller.error(error);
8911
7561
  }
8912
7562
  finally {
@@ -8915,7 +7565,7 @@ async function createBuildStream(options) {
8915
7565
  }
8916
7566
  },
8917
7567
  });
8918
- debugLog$2();
7568
+ debugLog$1();
8919
7569
  return stream;
8920
7570
  }
8921
7571
 
@@ -9134,7 +7784,7 @@ function classifyStartupError(error, processInfo) {
9134
7784
  suggestion: 'Check file permissions and ownership'
9135
7785
  };
9136
7786
  }
9137
- if (processInfo.cwd && !existsSync$1(processInfo.cwd)) {
7787
+ if (processInfo.cwd && !existsSync(processInfo.cwd)) {
9138
7788
  return {
9139
7789
  reason: FailureReason.DIRECTORY_MISSING,
9140
7790
  message: `Working directory does not exist: ${processInfo.cwd}`,
@@ -9198,7 +7848,7 @@ function startDevServer(options) {
9198
7848
  buildLogger$1.processManager.processStarting(projectId, command, cwd);
9199
7849
  const emitter = new EventEmitter();
9200
7850
  // Verify CWD exists
9201
- if (!existsSync$1(cwd)) {
7851
+ if (!existsSync(cwd)) {
9202
7852
  const error = new Error(`Working directory does not exist: ${cwd}`);
9203
7853
  buildLogger$1.processManager.error('Failed to spawn process', error, { projectId });
9204
7854
  emitter.emit('error', error);
@@ -9435,7 +8085,7 @@ async function waitForPortRelease(port, maxWaitMs = 10000, pollIntervalMs = 500)
9435
8085
  */
9436
8086
  function fixPackageJsonPort(cwd, targetPort) {
9437
8087
  const packageJsonPath = join$1(cwd, 'package.json');
9438
- if (!existsSync$1(packageJsonPath)) {
8088
+ if (!existsSync(packageJsonPath)) {
9439
8089
  buildLogger$1.log('debug', 'process-manager', 'No package.json found to fix', { cwd });
9440
8090
  return false;
9441
8091
  }
@@ -9562,19 +8212,19 @@ async function stopAllDevServers(tunnelManager) {
9562
8212
  }
9563
8213
 
9564
8214
  var processManager = /*#__PURE__*/Object.freeze({
9565
- __proto__: null,
9566
- get FailureReason () { return FailureReason; },
9567
- get ProcessState () { return ProcessState; },
9568
- checkPortInUse: checkPortInUse,
9569
- findAvailablePort: findAvailablePort,
9570
- getAllActiveProjectIds: getAllActiveProjectIds,
9571
- runHealthCheck: runHealthCheck,
9572
- setSilentMode: setSilentMode$1,
9573
- startDevServer: startDevServer,
9574
- startDevServerAsync: startDevServerAsync,
9575
- stopAllDevServers: stopAllDevServers,
9576
- stopDevServer: stopDevServer,
9577
- waitForPortRelease: waitForPortRelease
8215
+ __proto__: null,
8216
+ get FailureReason () { return FailureReason; },
8217
+ get ProcessState () { return ProcessState; },
8218
+ checkPortInUse: checkPortInUse,
8219
+ findAvailablePort: findAvailablePort,
8220
+ getAllActiveProjectIds: getAllActiveProjectIds,
8221
+ runHealthCheck: runHealthCheck,
8222
+ setSilentMode: setSilentMode$1,
8223
+ startDevServer: startDevServer,
8224
+ startDevServerAsync: startDevServerAsync,
8225
+ stopAllDevServers: stopAllDevServers,
8226
+ stopDevServer: stopDevServer,
8227
+ waitForPortRelease: waitForPortRelease
9578
8228
  });
9579
8229
 
9580
8230
  /**
@@ -9584,7 +8234,7 @@ var processManager = /*#__PURE__*/Object.freeze({
9584
8234
  function getWorkspaceRoot() {
9585
8235
  // Check environment variable first
9586
8236
  const envWorkspace = process.env.WORKSPACE_ROOT;
9587
- if (envWorkspace && existsSync$1(envWorkspace)) {
8237
+ if (envWorkspace && existsSync(envWorkspace)) {
9588
8238
  return resolve(envWorkspace);
9589
8239
  }
9590
8240
  // Default to a workspace directory in the user's home
@@ -9897,296 +8547,6 @@ function transformAgentMessageToSSE(agentMessage) {
9897
8547
  return events;
9898
8548
  }
9899
8549
 
9900
- // Debug logging helper - suppressed in TUI mode (SILENT_MODE=1)
9901
- const debugLog$1 = (message) => {
9902
- if (process.env.SILENT_MODE !== '1' && process.env.DEBUG_BUILD === '1') {
9903
- debugLog$1();
9904
- }
9905
- };
9906
- /**
9907
- * Truncate strings for logging to prevent excessive output
9908
- */
9909
- function truncate$1(str, maxLength = 200) {
9910
- if (str.length <= maxLength)
9911
- return str;
9912
- return str.substring(0, maxLength) + '...';
9913
- }
9914
- /**
9915
- * Truncate JSON objects for logging
9916
- */
9917
- function truncateJSON$1(obj, maxLength = 200) {
9918
- const json = JSON.stringify(obj, null, 2);
9919
- return truncate$1(json, maxLength);
9920
- }
9921
- /**
9922
- * Transforms AI SDK stream events into messages compatible with our message transformer
9923
- */
9924
- async function* transformAISDKStream(stream) {
9925
- const DEBUG = process.env.DEBUG_BUILD === '1';
9926
- let currentMessageId = `msg-${Date.now()}`; // Always have a default ID
9927
- let currentTextContent = '';
9928
- const toolInputBuffer = new Map();
9929
- const toolResults = new Map();
9930
- let eventCount = 0;
9931
- let yieldCount = 0;
9932
- // Track active tool spans for proper duration measurement
9933
- const activeToolSpans = new Map();
9934
- // ALWAYS log start - write to stderr to bypass TUI console interceptor
9935
- debugLog$1();
9936
- streamLog.info('━━━ STREAM TRANSFORMATION STARTED ━━━');
9937
- for await (const part of stream) {
9938
- eventCount++;
9939
- // Log ALL events to file (first 50)
9940
- if (eventCount <= 50) {
9941
- streamLog.event(eventCount, part.type, part);
9942
- }
9943
- // DEBUG: Log first 20 events with full JSON to see what we're actually getting
9944
- if (DEBUG && eventCount <= 20) {
9945
- debugLog$1(`[runner] [ai-sdk-adapter] Event #${eventCount}: type="${part.type}"\n`);
9946
- debugLog$1(`[runner] [ai-sdk-adapter] Full JSON: ${truncateJSON$1(part, 500)}\n`);
9947
- }
9948
- if (DEBUG)
9949
- console.log('[ai-sdk-adapter] Event:', part.type, part);
9950
- // DEBUG: Log every 10 events to show progress
9951
- if (DEBUG && eventCount % 10 === 0) {
9952
- debugLog$1();
9953
- }
9954
- switch (part.type) {
9955
- case 'start':
9956
- case 'start-step':
9957
- // Update message ID if provided
9958
- if (part.id) {
9959
- currentMessageId = part.id;
9960
- if (DEBUG)
9961
- debugLog$1();
9962
- }
9963
- break;
9964
- case 'text-start':
9965
- // Capture message ID from text-start event
9966
- if (part.id) {
9967
- currentMessageId = part.id;
9968
- if (DEBUG)
9969
- debugLog$1();
9970
- }
9971
- break;
9972
- case 'text-delta':
9973
- // Just accumulate text content - DON'T yield for every token!
9974
- // We'll only yield when there's a tool call or the message finishes
9975
- const textChunk = part.delta ?? part.text ?? part.textDelta;
9976
- if (typeof textChunk === 'string') {
9977
- currentTextContent += textChunk;
9978
- // Update message ID if provided in the event
9979
- if (part.id) {
9980
- currentMessageId = part.id;
9981
- }
9982
- }
9983
- break;
9984
- case 'tool-input-start':
9985
- // Start buffering tool input
9986
- toolInputBuffer.set(part.id, {
9987
- name: part.toolName,
9988
- input: '',
9989
- });
9990
- break;
9991
- case 'tool-input-delta':
9992
- // Accumulate tool input
9993
- const buffer = toolInputBuffer.get(part.id);
9994
- if (buffer) {
9995
- buffer.input += part.delta || '';
9996
- }
9997
- break;
9998
- case 'tool-input-end':
9999
- case 'tool-call':
10000
- // Tool call is complete - emit it
10001
- const toolCallId = part.toolCallId || part.id;
10002
- const toolName = part.toolName;
10003
- let toolInput = part.args || part.input;
10004
- // If we have buffered input, parse it
10005
- if (!toolInput && toolInputBuffer.has(toolCallId) && typeof toolCallId === 'string') {
10006
- const buffered = toolInputBuffer.get(toolCallId);
10007
- if (buffered?.input) {
10008
- if (typeof buffered.input === 'string') {
10009
- try {
10010
- toolInput = JSON.parse(buffered.input);
10011
- }
10012
- catch {
10013
- toolInput = { raw: buffered.input };
10014
- }
10015
- }
10016
- try {
10017
- toolInput = JSON.parse(buffered.input);
10018
- }
10019
- catch {
10020
- toolInput = { raw: buffered.input };
10021
- }
10022
- }
10023
- toolInputBuffer.delete(toolCallId);
10024
- }
10025
- if (toolName) {
10026
- // First, yield any accumulated text as a separate message
10027
- if (currentTextContent.trim().length > 0) {
10028
- const textMessage = {
10029
- type: 'assistant',
10030
- message: {
10031
- id: currentMessageId,
10032
- content: [{ type: 'text', text: currentTextContent }],
10033
- },
10034
- };
10035
- yieldCount++;
10036
- debugLog$1(`[runner] [ai-sdk-adapter] 💬 Yielding text before tool: ${currentTextContent.length} chars\n`);
10037
- yield textMessage;
10038
- // Reset so it's not included in the tool message
10039
- currentTextContent = '';
10040
- }
10041
- // Now yield the tool call as a separate message
10042
- const toolMessage = {
10043
- type: 'assistant',
10044
- message: {
10045
- id: `${currentMessageId}-tool-${toolCallId}`,
10046
- content: [
10047
- {
10048
- type: 'tool_use',
10049
- id: toolCallId,
10050
- name: toolName,
10051
- input: toolInput || {},
10052
- },
10053
- ],
10054
- },
10055
- };
10056
- yieldCount++;
10057
- debugLog$1();
10058
- debugLog$1(`[runner] [ai-sdk-adapter] Tool input: ${truncateJSON$1(toolInput)}\n`);
10059
- // Log to file
10060
- streamLog.yield('tool-call', { toolName, toolCallId, toolInput, message: toolMessage });
10061
- yield toolMessage;
10062
- }
10063
- break;
10064
- case 'tool-result':
10065
- // Store tool result
10066
- const resultId = part.toolCallId;
10067
- const toolResult = part.result ?? part.output;
10068
- toolResults.set(resultId, toolResult);
10069
- // Emit tool result as a user message
10070
- const resultMessage = {
10071
- type: 'user',
10072
- message: {
10073
- id: `result-${resultId}`,
10074
- content: [
10075
- {
10076
- type: 'tool_result',
10077
- tool_use_id: resultId,
10078
- content: JSON.stringify(toolResult),
10079
- },
10080
- ],
10081
- },
10082
- };
10083
- yieldCount++;
10084
- debugLog$1(`[runner] [ai-sdk-adapter] 📥 Tool result for: ${part.toolName || resultId}\n`);
10085
- // Special handling for verbose tool results
10086
- if (part.toolName === 'TodoWrite') {
10087
- debugLog$1();
10088
- }
10089
- else {
10090
- debugLog$1(`[runner] [ai-sdk-adapter] Result: ${truncateJSON$1(toolResult)}\n`);
10091
- }
10092
- yield resultMessage;
10093
- break;
10094
- case 'tool-error':
10095
- // Emit tool error as a user message
10096
- const errorId = part.toolCallId || part.id;
10097
- const errorMessage = {
10098
- type: 'user',
10099
- message: {
10100
- id: `error-${errorId}`,
10101
- content: [
10102
- {
10103
- type: 'tool_result',
10104
- tool_use_id: errorId,
10105
- content: JSON.stringify({ error: part.error }),
10106
- is_error: true,
10107
- },
10108
- ],
10109
- },
10110
- };
10111
- yieldCount++;
10112
- debugLog$1(`[runner] [ai-sdk-adapter] ⚠️ Tool error: ${part.toolName || errorId}\n`);
10113
- debugLog$1(`[runner] [ai-sdk-adapter] Error: ${truncate$1(JSON.stringify(part.error), 150)}\n`);
10114
- yield errorMessage;
10115
- break;
10116
- case 'text-end':
10117
- // Text block is complete - yield the accumulated text
10118
- if (currentTextContent.trim().length > 0) {
10119
- const message = {
10120
- type: 'assistant',
10121
- message: {
10122
- id: currentMessageId,
10123
- content: [{ type: 'text', text: currentTextContent }],
10124
- },
10125
- };
10126
- yieldCount++;
10127
- debugLog$1(`[runner] [ai-sdk-adapter] Yielding complete text block: ${currentTextContent.length} chars\n`);
10128
- yield message;
10129
- // Reset text content for next block
10130
- currentTextContent = '';
10131
- }
10132
- break;
10133
- case 'finish':
10134
- case 'finish-step':
10135
- // Final message - yield any remaining text
10136
- if (currentTextContent.trim().length > 0) {
10137
- yieldCount++;
10138
- if (DEBUG)
10139
- debugLog$1(`[runner] [ai-sdk-adapter] Yielding final text: ${currentTextContent.length} chars\n`);
10140
- yield {
10141
- type: 'assistant',
10142
- message: {
10143
- id: currentMessageId,
10144
- content: [{ type: 'text', text: currentTextContent }],
10145
- },
10146
- };
10147
- }
10148
- break;
10149
- case 'error':
10150
- // Emit error
10151
- const errorPayload = part.error ?? { message: 'Unknown Claude stream error' };
10152
- const errorText = typeof errorPayload === 'object' && errorPayload !== null
10153
- ? JSON.stringify(errorPayload)
10154
- : String(errorPayload);
10155
- const wrappedError = new Error(`Claude stream error: ${errorText}`);
10156
- Object.assign(wrappedError, { cause: errorPayload });
10157
- debugLog$1();
10158
- throw wrappedError;
10159
- // Ignore other event types
10160
- case 'stream-start':
10161
- case 'response-metadata':
10162
- break;
10163
- default:
10164
- // Log unknown event types for debugging
10165
- if (process.env.DEBUG_BUILD === '1') {
10166
- console.log('[ai-sdk-adapter] Unknown event type:', part.type);
10167
- }
10168
- break;
10169
- }
10170
- }
10171
- // Clean up any orphaned tool spans (tool calls that never received results)
10172
- if (activeToolSpans.size > 0) {
10173
- debugLog$1(`[runner] [ai-sdk-adapter] ⚠️ Cleaning up ${activeToolSpans.size} orphaned tool spans\n`);
10174
- for (const [toolId, span] of activeToolSpans) {
10175
- span.setStatus({ code: 2, message: 'Tool execution incomplete - no result received' });
10176
- span.end();
10177
- debugLog$1();
10178
- }
10179
- activeToolSpans.clear();
10180
- }
10181
- // DEBUG: Log completion stats
10182
- if (DEBUG) {
10183
- debugLog$1();
10184
- }
10185
- streamLog.info('━━━ STREAM TRANSFORMATION COMPLETE ━━━');
10186
- streamLog.info(`Total events: ${eventCount}`);
10187
- streamLog.info(`Total yields: ${yieldCount}`);
10188
- }
10189
-
10190
8550
  /**
10191
8551
  * Adapter to transform Codex SDK events into the unified format expected by message transformer
10192
8552
  *
@@ -11076,10 +9436,10 @@ async function selectTemplateFromPrompt(userPrompt) {
11076
9436
  }
11077
9437
 
11078
9438
  var config = /*#__PURE__*/Object.freeze({
11079
- __proto__: null,
11080
- getAllTemplates: getAllTemplates,
11081
- loadTemplateConfig: loadTemplateConfig,
11082
- selectTemplateFromPrompt: selectTemplateFromPrompt
9439
+ __proto__: null,
9440
+ getAllTemplates: getAllTemplates,
9441
+ loadTemplateConfig: loadTemplateConfig,
9442
+ selectTemplateFromPrompt: selectTemplateFromPrompt
11083
9443
  });
11084
9444
 
11085
9445
  /**
@@ -11097,7 +9457,7 @@ async function downloadTemplate(template, targetPath) {
11097
9457
  console.log(` Target: ${targetPath}`);
11098
9458
  // Check if target already exists and add random suffix if needed
11099
9459
  let finalTargetPath = targetPath;
11100
- if (existsSync$1(targetPath)) {
9460
+ if (existsSync(targetPath)) {
11101
9461
  // Generate a unique 4-character suffix
11102
9462
  const randomSuffix = Math.random().toString(36).substring(2, 6);
11103
9463
  finalTargetPath = `${targetPath}-${randomSuffix}`;
@@ -11169,12 +9529,12 @@ async function downloadTemplateWithGit(template, targetPath) {
11169
9529
  // Handle multi-package projects with client/server subdirectories
11170
9530
  const clientPkgPath = join$1(targetPath, 'client', 'package.json');
11171
9531
  const serverPkgPath = join$1(targetPath, 'server', 'package.json');
11172
- if (existsSync$1(clientPkgPath)) {
9532
+ if (existsSync(clientPkgPath)) {
11173
9533
  await updatePackageName(join$1(targetPath, 'client'), `${projectName}-client`);
11174
9534
  await removeHardcodedPorts(join$1(targetPath, 'client'));
11175
9535
  await ensureVitePortConfig(join$1(targetPath, 'client'));
11176
9536
  }
11177
- if (existsSync$1(serverPkgPath)) {
9537
+ if (existsSync(serverPkgPath)) {
11178
9538
  await updatePackageName(join$1(targetPath, 'server'), `${projectName}-server`);
11179
9539
  await removeHardcodedPorts(join$1(targetPath, 'server'));
11180
9540
  await ensureVitePortConfig(join$1(targetPath, 'server'));
@@ -11209,7 +9569,7 @@ shamefully-hoist=false
11209
9569
  */
11210
9570
  async function updatePackageName(projectPath, newName) {
11211
9571
  const pkgPath = join$1(projectPath, 'package.json');
11212
- if (!existsSync$1(pkgPath)) {
9572
+ if (!existsSync(pkgPath)) {
11213
9573
  if (process.env.DEBUG_BUILD === '1')
11214
9574
  console.log(` No package.json found in ${projectPath}, skipping name update`);
11215
9575
  return;
@@ -11232,7 +9592,7 @@ async function updatePackageName(projectPath, newName) {
11232
9592
  */
11233
9593
  async function removeHardcodedPorts(projectPath) {
11234
9594
  const pkgPath = join$1(projectPath, 'package.json');
11235
- if (!existsSync$1(pkgPath)) {
9595
+ if (!existsSync(pkgPath)) {
11236
9596
  return;
11237
9597
  }
11238
9598
  try {
@@ -11281,11 +9641,11 @@ async function ensureVitePortConfig(projectPath) {
11281
9641
  const viteConfigJs = join$1(projectPath, 'vite.config.js');
11282
9642
  const viteConfigMts = join$1(projectPath, 'vite.config.mts');
11283
9643
  let configPath = null;
11284
- if (existsSync$1(viteConfigTs))
9644
+ if (existsSync(viteConfigTs))
11285
9645
  configPath = viteConfigTs;
11286
- else if (existsSync$1(viteConfigMts))
9646
+ else if (existsSync(viteConfigMts))
11287
9647
  configPath = viteConfigMts;
11288
- else if (existsSync$1(viteConfigJs))
9648
+ else if (existsSync(viteConfigJs))
11289
9649
  configPath = viteConfigJs;
11290
9650
  if (!configPath)
11291
9651
  return;
@@ -11423,7 +9783,7 @@ async function orchestrateBuild(context) {
11423
9783
  }
11424
9784
  // Verify directory state for logging
11425
9785
  try {
11426
- if (existsSync$1(workingDirectory)) {
9786
+ if (existsSync(workingDirectory)) {
11427
9787
  const files = await readdir(workingDirectory);
11428
9788
  if (process.env.DEBUG_BUILD === '1')
11429
9789
  console.log(`[orchestrator] Directory status: ${files.length} files found`);
@@ -11662,7 +10022,8 @@ var TAG_DEFINITIONS = [
11662
10022
  description: "Anthropic Claude - Balanced performance and speed",
11663
10023
  logo: "/claude.png",
11664
10024
  provider: "claude-code",
11665
- model: "claude-sonnet-4-5"
10025
+ model: "claude-sonnet-4-5",
10026
+ sdk: "agent"
11666
10027
  },
11667
10028
  {
11668
10029
  value: "claude-opus-4-5",
@@ -11670,7 +10031,8 @@ var TAG_DEFINITIONS = [
11670
10031
  description: "Anthropic Claude - Most capable for complex tasks",
11671
10032
  logo: "/claude.png",
11672
10033
  provider: "claude-code",
11673
- model: "claude-opus-4-5"
10034
+ model: "claude-opus-4-5",
10035
+ sdk: "agent"
11674
10036
  },
11675
10037
  {
11676
10038
  value: "claude-haiku-4-5",
@@ -11678,7 +10040,8 @@ var TAG_DEFINITIONS = [
11678
10040
  description: "Anthropic Claude - Fastest, good for iterations",
11679
10041
  logo: "/claude.png",
11680
10042
  provider: "claude-code",
11681
- model: "claude-haiku-4-5"
10043
+ model: "claude-haiku-4-5",
10044
+ sdk: "agent"
11682
10045
  },
11683
10046
  {
11684
10047
  value: "gpt-5.2-codex",
@@ -11686,7 +10049,35 @@ var TAG_DEFINITIONS = [
11686
10049
  description: "OpenAI Codex - Advanced code generation",
11687
10050
  logo: "/openai.png",
11688
10051
  provider: "openai-codex",
11689
- model: "openai/gpt-5.2-codex"
10052
+ model: "openai/gpt-5.2-codex",
10053
+ sdk: "agent"
10054
+ },
10055
+ {
10056
+ value: "factory-droid-opus",
10057
+ label: "Factory Droid (Opus)",
10058
+ description: "Factory Droid SDK with Claude Opus 4.5",
10059
+ logo: "/factory.svg",
10060
+ provider: "factory-droid",
10061
+ model: "claude-opus-4-5-20251101",
10062
+ sdk: "droid"
10063
+ },
10064
+ {
10065
+ value: "factory-droid-codex",
10066
+ label: "Factory Droid (Codex)",
10067
+ description: "Factory Droid SDK with GPT-5.2 Codex",
10068
+ logo: "/factory.svg",
10069
+ provider: "factory-droid",
10070
+ model: "gpt-5.2-codex",
10071
+ sdk: "droid"
10072
+ },
10073
+ {
10074
+ value: "factory-droid-glm",
10075
+ label: "Factory Droid (GLM)",
10076
+ description: "Factory Droid SDK with GLM 4.7",
10077
+ logo: "/factory.svg",
10078
+ provider: "factory-droid",
10079
+ model: "glm-4.7",
10080
+ sdk: "droid"
11690
10081
  }
11691
10082
  ]
11692
10083
  },
@@ -11910,7 +10301,7 @@ function getCleanEnv() {
11910
10301
  * Ensure a directory exists
11911
10302
  */
11912
10303
  function ensureDir(dir) {
11913
- if (!existsSync(dir)) {
10304
+ if (!existsSync$1(dir)) {
11914
10305
  mkdirSync$1(dir, { recursive: true });
11915
10306
  }
11916
10307
  }
@@ -11954,7 +10345,7 @@ CRITICAL: Your response must START with { and END with }. Output only the JSON o
11954
10345
  }
11955
10346
  catch (error) {
11956
10347
  console.error('[project-analyzer] SDK query failed:', error);
11957
- captureException(error);
10348
+ Sentry.captureException(error);
11958
10349
  throw error;
11959
10350
  }
11960
10351
  if (!responseText) {
@@ -12344,10 +10735,10 @@ async function waitForPort(port, maxRetries = 10, delayMs = 500) {
12344
10735
  }
12345
10736
 
12346
10737
  var portChecker = /*#__PURE__*/Object.freeze({
12347
- __proto__: null,
12348
- isPortReady: isPortReady,
12349
- setSilentMode: setSilentMode,
12350
- waitForPort: waitForPort
10738
+ __proto__: null,
10739
+ isPortReady: isPortReady,
10740
+ setSilentMode: setSilentMode,
10741
+ waitForPort: waitForPort
12351
10742
  });
12352
10743
 
12353
10744
  /**
@@ -12492,7 +10883,7 @@ const hmrProxyManager = new HmrProxyManager();
12492
10883
 
12493
10884
  // Sentry is initialized via --import flag (see package.json scripts)
12494
10885
  // This ensures instrumentation loads before any other modules
12495
- globalThis.AI_SDK_LOG_WARNINGS = false;
10886
+ // AI SDK log warnings disabled (legacy - no longer using AI SDK)
12496
10887
  /**
12497
10888
  * Truncate strings for logging to prevent excessive output
12498
10889
  */
@@ -12524,7 +10915,7 @@ const DEBUG_BUILD = process.env.DEBUG_BUILD === "1" || false;
12524
10915
  const log = (...args) => {
12525
10916
  const message = args.map(a => typeof a === 'string' ? a : JSON.stringify(a)).join(' ');
12526
10917
  try {
12527
- const logger = getLogger$1();
10918
+ const logger = getLogger();
12528
10919
  logger.info('system', message);
12529
10920
  }
12530
10921
  catch {
@@ -12538,7 +10929,7 @@ const log = (...args) => {
12538
10929
  const buildLog = (...args) => {
12539
10930
  const message = args.map(a => typeof a === 'string' ? a : JSON.stringify(a)).join(' ');
12540
10931
  try {
12541
- const logger = getLogger$1();
10932
+ const logger = getLogger();
12542
10933
  logger.info('build', message);
12543
10934
  }
12544
10935
  catch {
@@ -12554,7 +10945,7 @@ const debugLog = (...args) => {
12554
10945
  return;
12555
10946
  const message = args.map(a => typeof a === 'string' ? a : JSON.stringify(a)).join(' ');
12556
10947
  try {
12557
- const logger = getLogger$1();
10948
+ const logger = getLogger();
12558
10949
  logger.debug('system', message);
12559
10950
  }
12560
10951
  catch {
@@ -12563,146 +10954,6 @@ const debugLog = (...args) => {
12563
10954
  };
12564
10955
  const DEFAULT_AGENT = "claude-code";
12565
10956
  const CODEX_MODEL = "gpt-5-codex";
12566
- // Stderr debug logging - suppressed in TUI mode (SILENT_MODE=1)
12567
- const stderrDebug = (message) => {
12568
- if (process.env.SILENT_MODE !== '1' && DEBUG_BUILD) {
12569
- process.stderr.write(message);
12570
- }
12571
- };
12572
- /**
12573
- * Create Claude query function using AI SDK
12574
- *
12575
- * NOTE: This function prepends CLAUDE_SYSTEM_PROMPT to the systemPrompt from orchestrator.
12576
- * The orchestrator provides context-specific sections only (no base prompt).
12577
- */
12578
- function createClaudeQuery(modelId = DEFAULT_CLAUDE_MODEL_ID) {
12579
- return async function* (prompt, workingDirectory, systemPrompt, agent, codexThreadId, messageParts) {
12580
- // Note: query is auto-instrumented by Sentry's claudeCodeAgentSdkIntegration via OpenTelemetry
12581
- stderrDebug("[runner] [createClaudeQuery] 🎯 Query function called\n");
12582
- stderrDebug(`[runner] [createClaudeQuery] Model: ${modelId}\n`);
12583
- stderrDebug(`[runner] [createClaudeQuery] Working dir: ${workingDirectory}\n`);
12584
- stderrDebug(`[runner] [createClaudeQuery] Prompt length: ${prompt.length}\n`);
12585
- // Check if we have image parts
12586
- const hasImages = messageParts?.some(p => p.type === 'image');
12587
- if (hasImages) {
12588
- const imageCount = messageParts?.filter(p => p.type === 'image').length || 0;
12589
- stderrDebug(`[runner] [createClaudeQuery] 🖼️ Multi-modal message with ${imageCount} image(s)\n`);
12590
- }
12591
- // Build combined system prompt
12592
- const systemPromptSegments = [CLAUDE_SYSTEM_PROMPT.trim()];
12593
- if (systemPrompt && systemPrompt.trim().length > 0) {
12594
- systemPromptSegments.push(systemPrompt.trim());
12595
- }
12596
- const appendedSystemPrompt = systemPromptSegments.join("\n\n");
12597
- // Use the full model ID as-is (claude-haiku-4-5, claude-sonnet-4-5, claude-opus-4-5)
12598
- const aiSdkModelId = modelId || "claude-haiku-4-5";
12599
- // Ensure working directory exists before passing to Claude Code
12600
- if (!existsSync$1(workingDirectory)) {
12601
- console.log(`[createClaudeQuery] Creating working directory: ${workingDirectory}`);
12602
- mkdirSync(workingDirectory, { recursive: true });
12603
- }
12604
- // NOTE: The community provider (ai-sdk-provider-claude-code) bundles an older
12605
- // version of the SDK types. We cast to `any` to avoid type conflicts.
12606
- // This legacy path is deprecated in favor of the native SDK implementation.
12607
- const model = claudeCode(aiSdkModelId, {
12608
- queryFunction: query, // Cast to avoid SDK version type conflict
12609
- systemPrompt: {
12610
- type: "preset",
12611
- preset: "claude_code",
12612
- append: appendedSystemPrompt,
12613
- },
12614
- cwd: workingDirectory,
12615
- permissionMode: "bypassPermissions",
12616
- maxTurns: 100,
12617
- additionalDirectories: [workingDirectory],
12618
- env: {
12619
- // Claude Code CLI default output cap is 32k; bumping to 64k per
12620
- // https://docs.claude.com/en/docs/claude-code/settings#environment-variables
12621
- CLAUDE_CODE_MAX_OUTPUT_TOKENS: process.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS ?? "64000",
12622
- },
12623
- // Explicitly allow all tools to prevent "No tools are available" errors
12624
- canUseTool: createProjectScopedPermissionHandler(workingDirectory), // Cast for type compat
12625
- streamingInput: "always", // REQUIRED when using canUseTool - enables tool callbacks
12626
- includePartialMessages: true,
12627
- settingSources: ["user", "project"], // Load skills from user and project directories
12628
- });
12629
- // Build user message - either simple text or multi-part with images
12630
- let userMessage;
12631
- if (messageParts && messageParts.length > 0) {
12632
- // Multi-part message with images
12633
- const contentParts = [];
12634
- // Add image parts first (Claude best practice)
12635
- for (const part of messageParts) {
12636
- if (part.type === 'image' && part.image) {
12637
- // AI SDK expects images as data URLs, which we already have
12638
- contentParts.push({
12639
- type: 'image',
12640
- image: part.image, // Already a base64 data URL
12641
- });
12642
- // Extract media type for logging
12643
- const match = part.image.match(/^data:(.+);base64,/);
12644
- if (match) {
12645
- stderrDebug(`[runner] [createClaudeQuery] ✅ Added image part (${match[1]})\n`);
12646
- }
12647
- }
12648
- else if (part.type === 'text' && part.text) {
12649
- contentParts.push({
12650
- type: 'text',
12651
- text: part.text
12652
- });
12653
- }
12654
- }
12655
- // Add prompt text if not already in parts
12656
- if (!contentParts.some(p => p.type === 'text')) {
12657
- contentParts.push({
12658
- type: 'text',
12659
- text: prompt
12660
- });
12661
- }
12662
- userMessage = contentParts;
12663
- stderrDebug(`[runner] [createClaudeQuery] 📦 Built multi-part message with ${contentParts.length} part(s)\n`);
12664
- }
12665
- else {
12666
- // Simple text message (existing behavior)
12667
- userMessage = prompt;
12668
- }
12669
- // Stream with telemetry enabled for Sentry
12670
- // Use 'messages' param for multi-part content, 'prompt' for simple text
12671
- // IMPORTANT: experimental_telemetry is REQUIRED for the Vercel AI SDK integration to work
12672
- const streamOptions = {
12673
- model,
12674
- tools: CLAUDE_CLI_TOOL_REGISTRY,
12675
- experimental_telemetry: {
12676
- isEnabled: true,
12677
- functionId: 'createClaudeQuery',
12678
- recordInputs: true,
12679
- recordOutputs: true,
12680
- },
12681
- };
12682
- const result = Array.isArray(userMessage)
12683
- ? streamText({
12684
- ...streamOptions,
12685
- messages: [{ role: 'user', content: userMessage }],
12686
- })
12687
- : streamText({
12688
- ...streamOptions,
12689
- prompt: userMessage,
12690
- });
12691
- // Transform AI SDK stream format to our message format
12692
- if (process.env.DEBUG_BUILD === "1") {
12693
- console.log("[createClaudeQuery] Starting stream consumption...");
12694
- }
12695
- for await (const message of transformAISDKStream(result.fullStream)) {
12696
- if (process.env.DEBUG_BUILD === "1") {
12697
- console.log("[createClaudeQuery] Yielding message:", message.type);
12698
- }
12699
- yield message;
12700
- }
12701
- if (process.env.DEBUG_BUILD === "1") {
12702
- console.log("[createClaudeQuery] Stream consumption complete");
12703
- }
12704
- };
12705
- }
12706
10957
  /**
12707
10958
  * Create Codex query function using ORIGINAL Codex SDK (NOT AI SDK)
12708
10959
  *
@@ -12797,7 +11048,7 @@ function createCodexQuery() {
12797
11048
  log(`🚀 [codex-query] Turn ${turnCount}: ${turnCount === 1 ? 'Initial request' : 'Continuing work'}...`);
12798
11049
  // Log full prompt being sent to Codex
12799
11050
  if (turnCount === 1) {
12800
- info(fmt `Full Codex prompt (Turn 1) ${{
11051
+ Sentry.logger.info(Sentry.logger.fmt `Full Codex prompt (Turn 1) ${{
12801
11052
  prompt: turnPrompt,
12802
11053
  promptLength: turnPrompt.length,
12803
11054
  promptPreview: turnPrompt.substring(0, 200),
@@ -12850,9 +11101,18 @@ function createCodexQuery() {
12850
11101
  fileLog.info(`Total turns: ${turnCount}`);
12851
11102
  };
12852
11103
  }
11104
+ /**
11105
+ * SDK Feature Flags (environment variables)
11106
+ *
11107
+ * - ENABLE_OPENCODE_SDK: Imported from opencode-sdk.ts, enables OpenCode multi-provider routing
11108
+ * - ENABLE_FACTORY_SDK: Enables Factory Droid SDK for builds
11109
+ *
11110
+ * By default (no flags set), only Agent SDK (Claude + Codex) is available.
11111
+ */
11112
+ const ENABLE_FACTORY_SDK = process.env.ENABLE_FACTORY_SDK === 'true';
12853
11113
  function createBuildQuery(agent, modelId, abortController) {
12854
11114
  // When OpenCode SDK is enabled, route ALL requests through it (including Codex)
12855
- if (USE_OPENCODE_SDK) {
11115
+ if (ENABLE_OPENCODE_SDK) {
12856
11116
  // Map openai-codex agent to the correct OpenCode model
12857
11117
  const normalizedModel = agent === "openai-codex"
12858
11118
  ? "openai/gpt-5.2-codex"
@@ -12865,13 +11125,18 @@ function createBuildQuery(agent, modelId, abortController) {
12865
11125
  console.log('[runner] 🔄 Using direct Codex SDK (fallback mode)');
12866
11126
  return createCodexQuery();
12867
11127
  }
12868
- // Use legacy AI SDK path when explicitly requested
12869
- if (!USE_NATIVE_SDK) {
12870
- console.log('[runner] 🔄 Using AI SDK with claude-code provider (legacy mode)');
12871
- return createClaudeQuery(modelId ?? DEFAULT_CLAUDE_MODEL_ID);
11128
+ // Factory Droid SDK - uses the droid CLI for builds (only if enabled)
11129
+ if (agent === "factory-droid") {
11130
+ if (!ENABLE_FACTORY_SDK) {
11131
+ console.warn('[runner] ⚠️ Factory Droid requested but ENABLE_FACTORY_SDK is not set');
11132
+ console.warn('[runner] ⚠️ Falling back to native Claude Agent SDK');
11133
+ return createNativeClaudeQuery(DEFAULT_CLAUDE_MODEL_ID, abortController);
11134
+ }
11135
+ console.log('[runner] 🔄 Using Factory Droid SDK');
11136
+ return createDroidQuery(modelId);
12872
11137
  }
12873
11138
  // Default: Use native Claude Agent SDK (direct integration)
12874
- console.log('[runner] 🔄 Using NATIVE Claude Agent SDK v0.1.76');
11139
+ console.log('[runner] 🔄 Using NATIVE Claude Agent SDK');
12875
11140
  return createNativeClaudeQuery(modelId ?? DEFAULT_CLAUDE_MODEL_ID, abortController);
12876
11141
  }
12877
11142
  /**
@@ -13047,7 +11312,7 @@ async function startRunner(options = {}) {
13047
11312
  const RUNNER_ID = options.runnerId ||
13048
11313
  process.env.RUNNER_ID ||
13049
11314
  process.env.RAILWAY_REPLICA_ID ||
13050
- `runner-${os__default.hostname()}`;
11315
+ `runner-${os$1.hostname()}`;
13051
11316
  // WebSocket URL for direct connection to Next.js server
13052
11317
  // Supports both new RUNNER_WS_URL and legacy RUNNER_BROKER_URL for backward compatibility
13053
11318
  const WS_URL = options.wsUrl ||
@@ -13078,7 +11343,7 @@ async function startRunner(options = {}) {
13078
11343
  }
13079
11344
  const runnerSharedSecret = SHARED_SECRET; // Guaranteed to be string after validation check
13080
11345
  // Ensure workspace directory exists
13081
- if (!existsSync$1(WORKSPACE_ROOT)) {
11346
+ if (!existsSync(WORKSPACE_ROOT)) {
13082
11347
  console.log(`📁 Creating workspace directory: ${WORKSPACE_ROOT}`);
13083
11348
  mkdirSync(WORKSPACE_ROOT, { recursive: true });
13084
11349
  }
@@ -13109,14 +11374,14 @@ async function startRunner(options = {}) {
13109
11374
  id: RUNNER_ID,
13110
11375
  connected: socket?.readyState === WebSocket$1.OPEN,
13111
11376
  workspace: WORKSPACE_ROOT,
13112
- workspaceExists: existsSync$1(WORKSPACE_ROOT),
11377
+ workspaceExists: existsSync(WORKSPACE_ROOT),
13113
11378
  },
13114
11379
  uptime: process.uptime(),
13115
11380
  timestamp: new Date().toISOString(),
13116
11381
  });
13117
11382
  });
13118
11383
  healthApp.get('/ready', (req, res) => {
13119
- const isReady = socket?.readyState === WebSocket$1.OPEN && existsSync$1(WORKSPACE_ROOT);
11384
+ const isReady = socket?.readyState === WebSocket$1.OPEN && existsSync(WORKSPACE_ROOT);
13120
11385
  res.status(isReady ? 200 : 503).json({
13121
11386
  ready: isReady,
13122
11387
  runnerId: RUNNER_ID,
@@ -13182,7 +11447,7 @@ async function startRunner(options = {}) {
13182
11447
  throw lastError || new Error('Persistence failed after retries');
13183
11448
  }
13184
11449
  async function persistBuildEventDirect(context, event) {
13185
- return startSpan({
11450
+ return Sentry.startSpan({
13186
11451
  name: `persist.build-event.${event.type}`,
13187
11452
  op: 'http.client',
13188
11453
  attributes: {
@@ -13193,7 +11458,7 @@ async function startRunner(options = {}) {
13193
11458
  },
13194
11459
  }, async () => {
13195
11460
  // Get trace context for propagation
13196
- const traceData = getTraceData();
11461
+ const traceData = Sentry.getTraceData();
13197
11462
  const headers = {
13198
11463
  'Authorization': `Bearer ${runnerSharedSecret}`,
13199
11464
  'Content-Type': 'application/json',
@@ -13301,7 +11566,7 @@ async function startRunner(options = {}) {
13301
11566
  */
13302
11567
  async function persistRunnerEvent(event) {
13303
11568
  // Wrap in Sentry span to create proper trace hierarchy
13304
- return startSpan({
11569
+ return Sentry.startSpan({
13305
11570
  name: `persist.runner-event.${event.type}`,
13306
11571
  op: 'http.client',
13307
11572
  attributes: {
@@ -13355,9 +11620,9 @@ async function startRunner(options = {}) {
13355
11620
  try {
13356
11621
  // Attach trace context to ALL events for distributed tracing
13357
11622
  // This allows events to be linked back to the originating frontend request
13358
- const span = getActiveSpan();
11623
+ const span = Sentry.getActiveSpan();
13359
11624
  if (span) {
13360
- const traceData = getTraceData();
11625
+ const traceData = Sentry.getTraceData();
13361
11626
  event._sentry = {
13362
11627
  trace: traceData['sentry-trace'],
13363
11628
  baggage: traceData.baggage,
@@ -13382,6 +11647,9 @@ async function startRunner(options = {}) {
13382
11647
  }
13383
11648
  else if (event.type === "build-failed") {
13384
11649
  log(`❌ Build failed: ${event.error}`);
11650
+ // Debug: log stack trace to see where this was triggered from
11651
+ process.stderr.write(`[runner] BUILD-FAILED triggered. Error: ${event.error}\n`);
11652
+ process.stderr.write(`[runner] Stack trace at send time:\n${new Error().stack}\n`);
13385
11653
  }
13386
11654
  // Suppress: build-stream, runner-status, ack, etc.
13387
11655
  if (socket && socket.readyState === WebSocket$1.OPEN) {
@@ -13862,7 +12130,7 @@ async function startRunner(options = {}) {
13862
12130
  logger.tunnel({ port, url: tunnelUrl, status: 'created' });
13863
12131
  // Instrument tunnel startup timing
13864
12132
  const tunnelDuration = Date.now() - tunnelStartTime;
13865
- distribution('tunnel_startup_duration', tunnelDuration, {
12133
+ Sentry.metrics.distribution('tunnel_startup_duration', tunnelDuration, {
13866
12134
  unit: 'millisecond',
13867
12135
  attributes: {
13868
12136
  port: port.toString(),
@@ -13880,7 +12148,7 @@ async function startRunner(options = {}) {
13880
12148
  console.error("Failed to create tunnel:", error);
13881
12149
  // Instrument failed tunnel startup timing
13882
12150
  const tunnelDuration = Date.now() - tunnelStartTime;
13883
- distribution('tunnel_startup_duration', tunnelDuration, {
12151
+ Sentry.metrics.distribution('tunnel_startup_duration', tunnelDuration, {
13884
12152
  unit: 'millisecond',
13885
12153
  attributes: {
13886
12154
  port: command.payload.port.toString(),
@@ -14160,7 +12428,7 @@ async function startRunner(options = {}) {
14160
12428
  log("project name:", projectName);
14161
12429
  // Determine agent to use for this build
14162
12430
  const agent = command.payload.agent ?? DEFAULT_AGENT;
14163
- const agentLabel = agent === "openai-codex" ? "Codex" : "Claude";
12431
+ const agentLabel = agent === "openai-codex" ? "Codex" : (agent === "factory-droid" ? "Droid" : "Claude");
14164
12432
  log("selected agent:", agent);
14165
12433
  const claudeModel = agent === "claude-code" &&
14166
12434
  (command.payload.claudeModel === "claude-haiku-4-5" ||
@@ -14168,6 +12436,8 @@ async function startRunner(options = {}) {
14168
12436
  command.payload.claudeModel === "claude-opus-4-5")
14169
12437
  ? command.payload.claudeModel
14170
12438
  : DEFAULT_CLAUDE_MODEL_ID;
12439
+ // For factory-droid, use the droidModel from payload
12440
+ const droidModel = agent === "factory-droid" ? command.payload.droidModel : undefined;
14171
12441
  // Create AbortController for cancellation support
14172
12442
  const buildAbortController = new AbortController();
14173
12443
  // Register build context for HTTP persistence
@@ -14189,7 +12459,12 @@ async function startRunner(options = {}) {
14189
12459
  if (agent === "claude-code") {
14190
12460
  log("claude model:", claudeModel);
14191
12461
  }
14192
- const agentQuery = createBuildQuery(agent, claudeModel, buildAbortController);
12462
+ else if (agent === "factory-droid") {
12463
+ log("droid model:", droidModel || "default");
12464
+ }
12465
+ // Select the appropriate model for the agent
12466
+ const modelId = agent === "factory-droid" ? droidModel : claudeModel;
12467
+ const agentQuery = createBuildQuery(agent, modelId, buildAbortController);
14193
12468
  // Reset transformer state for new build
14194
12469
  resetTransformerState();
14195
12470
  setExpectedCwd(projectDirectory);
@@ -14441,18 +12716,48 @@ async function startRunner(options = {}) {
14441
12716
  }
14442
12717
  // Track completed todos for summary context
14443
12718
  if (toolEvent.toolName === 'TodoWrite' && toolEvent.input) {
14444
- const input = toolEvent.input;
14445
- if (input.todos) {
14446
- // Update the logger's todo list for the build panel
14447
- const todoItems = input.todos.map(t => ({
12719
+ const rawInput = toolEvent.input;
12720
+ // Handle both formats:
12721
+ // 1. Droid CLI format: string with numbered list like "1. [completed] Task"
12722
+ // 2. Claude format: array of objects with content, status, etc.
12723
+ let todoItems = [];
12724
+ if (typeof rawInput.todos === 'string') {
12725
+ // Parse droid CLI string format: "1. [completed] First task\n2. [in_progress] Second task"
12726
+ const lines = rawInput.todos.split('\n').filter(l => l.trim());
12727
+ todoItems = lines.map((line, idx) => {
12728
+ // Match patterns like "1. [completed] Task content" or "1. [in_progress] Task"
12729
+ const match = line.match(/^\d+\.\s*\[(\w+)\]\s*(.+)$/);
12730
+ if (match) {
12731
+ const [, statusStr, content] = match;
12732
+ const status = statusStr;
12733
+ return {
12734
+ id: `todo-${Date.now()}-${idx}`,
12735
+ content: content.trim(),
12736
+ status,
12737
+ };
12738
+ }
12739
+ // Fallback: treat line as pending task
12740
+ return {
12741
+ id: `todo-${Date.now()}-${idx}`,
12742
+ content: line.replace(/^\d+\.\s*/, '').trim(),
12743
+ status: 'pending',
12744
+ };
12745
+ });
12746
+ }
12747
+ else if (Array.isArray(rawInput.todos)) {
12748
+ // Claude format: array of objects
12749
+ todoItems = rawInput.todos.map(t => ({
14448
12750
  id: t.id || `todo-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
14449
12751
  content: t.content,
14450
12752
  status: t.status,
14451
12753
  priority: t.priority,
14452
12754
  }));
12755
+ }
12756
+ if (todoItems.length > 0) {
12757
+ // Update the logger's todo list for the build panel
14453
12758
  logger.updateTodos(todoItems);
14454
12759
  // Get completed todos (excluding any "summarize" todos from old prompts)
14455
- const completed = input.todos
12760
+ const completed = todoItems
14456
12761
  .filter(t => t.status === 'completed' && !t.content.toLowerCase().includes('summarize'))
14457
12762
  .map(t => t.content);
14458
12763
  // Update our list with the latest completed todos
@@ -14553,7 +12858,7 @@ async function startRunner(options = {}) {
14553
12858
  // Detect framework from generated files
14554
12859
  let detectedFramework = null;
14555
12860
  try {
14556
- const { detectFrameworkFromFilesystem } = await import('./chunks/port-allocator-Ct3ioni4.js');
12861
+ const { detectFrameworkFromFilesystem } = await import('./chunks/port-allocator-DuAZe2_S.js');
14557
12862
  const framework = await detectFrameworkFromFilesystem(projectDirectory);
14558
12863
  detectedFramework = framework;
14559
12864
  if (framework) {
@@ -14592,11 +12897,25 @@ ${filesArray.length > 0 ? `Files modified:\n${filesArray.map(f => `- ${f}`).join
14592
12897
  ${completedTodoSnapshot.length > 0 ? `Tasks completed:\n${completedTodoSnapshot.map(t => `- ${t}`).join('\n')}` : ''}
14593
12898
 
14594
12899
  Write a brief, professional summary (1-3 sentences) describing what was accomplished. Focus on the outcome, not the process. Do not use phrases like "I did" or "The assistant". Just describe what was built or changed.`;
14595
- const summaryResult = await generateText({
14596
- model: claudeCode("claude-haiku-4-5"),
12900
+ // Use native Claude Agent SDK for summary generation
12901
+ let summaryText = '';
12902
+ for await (const msg of query({
14597
12903
  prompt: summaryPrompt,
14598
- });
14599
- const summaryText = summaryResult.text?.trim();
12904
+ options: {
12905
+ model: 'claude-haiku-4-5',
12906
+ maxTurns: 1,
12907
+ permissionMode: 'default',
12908
+ },
12909
+ })) {
12910
+ if (msg.type === 'assistant' && msg.message?.content) {
12911
+ for (const block of msg.message.content) {
12912
+ if (block.type === 'text') {
12913
+ summaryText += block.text;
12914
+ }
12915
+ }
12916
+ }
12917
+ }
12918
+ summaryText = summaryText.trim();
14600
12919
  if (summaryText) {
14601
12920
  buildLog(` ✅ AI summary generated (${summaryText.length} chars)`);
14602
12921
  sendEvent({
@@ -14641,7 +12960,7 @@ Write a brief, professional summary (1-3 sentences) describing what was accompli
14641
12960
  catch (error) {
14642
12961
  const errorMessage = error instanceof Error ? error.message : "Failed to run build";
14643
12962
  logger.buildFailed(errorMessage);
14644
- getActiveSpan()?.setStatus({
12963
+ Sentry.getActiveSpan()?.setStatus({
14645
12964
  code: 2, // SPAN_STATUS_ERROR
14646
12965
  message: "Build failed",
14647
12966
  });
@@ -14858,15 +13177,15 @@ Write a brief, professional summary (1-3 sentences) describing what was accompli
14858
13177
  }
14859
13178
  function publishStatus(projectId, commandId) {
14860
13179
  const uptimeSeconds = Math.round(process.uptime());
14861
- const load = os__default.loadavg?.()[0];
13180
+ const load = os$1.loadavg?.()[0];
14862
13181
  sendEvent({
14863
13182
  type: "runner-status",
14864
13183
  ...buildEventBase(projectId, commandId),
14865
13184
  payload: {
14866
13185
  status: "online",
14867
13186
  version: "prototype",
14868
- hostname: os__default.hostname(),
14869
- platform: os__default.platform(),
13187
+ hostname: os$1.hostname(),
13188
+ platform: os$1.platform(),
14870
13189
  uptimeSeconds,
14871
13190
  load,
14872
13191
  },
@@ -14994,12 +13313,12 @@ Write a brief, professional summary (1-3 sentences) describing what was accompli
14994
13313
  // This creates a span within the continued trace for the runner's work
14995
13314
  if (command._sentry?.trace) {
14996
13315
  console.log("[runner] Continuing trace from frontend:", command._sentry.trace.substring(0, 50));
14997
- await continueTrace({
13316
+ await Sentry.continueTrace({
14998
13317
  sentryTrace: command._sentry.trace,
14999
13318
  baggage: command._sentry.baggage,
15000
13319
  }, async () => {
15001
13320
  // Create a span for this command execution within the continued trace
15002
- await startSpan({
13321
+ await Sentry.startSpan({
15003
13322
  name: `runner.${command.type}`,
15004
13323
  op: command.type === 'start-build' ? 'build.runner' : `runner.${command.type}`,
15005
13324
  attributes: {
@@ -15010,9 +13329,9 @@ Write a brief, professional summary (1-3 sentences) describing what was accompli
15010
13329
  },
15011
13330
  }, async (span) => {
15012
13331
  try {
15013
- setTag("command_type", command.type);
15014
- setTag("project_id", projectIdForTelemetry);
15015
- setTag("command_id", command.id);
13332
+ Sentry.setTag("command_type", command.type);
13333
+ Sentry.setTag("project_id", projectIdForTelemetry);
13334
+ Sentry.setTag("command_id", command.id);
15016
13335
  // Capture build metrics for start-build commands
15017
13336
  if (command.type === 'start-build' && command.payload) {
15018
13337
  const agent = command.payload.agent ?? 'claude-code';
@@ -15022,7 +13341,7 @@ Write a brief, professional summary (1-3 sentences) describing what was accompli
15022
13341
  command.payload.claudeModel === 'claude-opus-4-5')
15023
13342
  ? command.payload.claudeModel
15024
13343
  : 'claude-sonnet-4-5';
15025
- count('runner.build.started', 1, {
13344
+ Sentry.metrics.count('runner.build.started', 1, {
15026
13345
  attributes: {
15027
13346
  project_id: command.projectId,
15028
13347
  model: agent === 'claude-code' ? claudeModel : agent,
@@ -15043,7 +13362,7 @@ Write a brief, professional summary (1-3 sentences) describing what was accompli
15043
13362
  else {
15044
13363
  console.log("[runner] No trace context - starting isolated span");
15045
13364
  // Create an isolated span when no trace context is provided
15046
- await startSpan({
13365
+ await Sentry.startSpan({
15047
13366
  name: `runner.${command.type}`,
15048
13367
  op: command.type === 'start-build' ? 'build.runner' : `runner.${command.type}`,
15049
13368
  attributes: {
@@ -15054,9 +13373,9 @@ Write a brief, professional summary (1-3 sentences) describing what was accompli
15054
13373
  },
15055
13374
  }, async (span) => {
15056
13375
  try {
15057
- setTag("command_type", command.type);
15058
- setTag("project_id", projectIdForTelemetry);
15059
- setTag("command_id", command.id);
13376
+ Sentry.setTag("command_type", command.type);
13377
+ Sentry.setTag("project_id", projectIdForTelemetry);
13378
+ Sentry.setTag("command_id", command.id);
15060
13379
  // Capture build metrics for start-build commands
15061
13380
  if (command.type === 'start-build' && command.payload) {
15062
13381
  const agent = command.payload.agent ?? 'claude-code';
@@ -15066,7 +13385,7 @@ Write a brief, professional summary (1-3 sentences) describing what was accompli
15066
13385
  command.payload.claudeModel === 'claude-opus-4-5')
15067
13386
  ? command.payload.claudeModel
15068
13387
  : 'claude-sonnet-4-5';
15069
- count('runner.build.started', 1, {
13388
+ Sentry.metrics.count('runner.build.started', 1, {
15070
13389
  attributes: {
15071
13390
  project_id: command.projectId,
15072
13391
  model: agent === 'claude-code' ? claudeModel : agent,
@@ -15086,7 +13405,7 @@ Write a brief, professional summary (1-3 sentences) describing what was accompli
15086
13405
  }
15087
13406
  catch (error) {
15088
13407
  console.error("Failed to parse command", error);
15089
- captureException(error);
13408
+ Sentry.captureException(error);
15090
13409
  sendEvent({
15091
13410
  type: "error",
15092
13411
  ...buildEventBase(undefined, randomUUID$1()),
@@ -15178,7 +13497,7 @@ Write a brief, professional summary (1-3 sentences) describing what was accompli
15178
13497
  // Final cleanup of any remaining tunnels
15179
13498
  await tunnelManager.closeAll();
15180
13499
  // Flush Sentry events before exiting
15181
- await flush(2000);
13500
+ await Sentry.flush(2000);
15182
13501
  log("shutdown complete");
15183
13502
  };
15184
13503
  process.on("SIGINT", async () => {