@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.
- package/dist/chunks/{Banner-BlktOjfl.js → Banner-BKC6yG6z.js} +2 -2
- package/dist/chunks/{Banner-BlktOjfl.js.map → Banner-BKC6yG6z.js.map} +1 -1
- package/dist/chunks/{cli-auth-ChCnxlFl.js → cli-auth-BgiGSBOt.js} +4 -4
- package/dist/chunks/{cli-auth-ChCnxlFl.js.map → cli-auth-BgiGSBOt.js.map} +1 -1
- package/dist/chunks/{index-oFqGtEeF.js → index-ZNRLfdj5.js} +2 -2
- package/dist/chunks/{index-oFqGtEeF.js.map → index-ZNRLfdj5.js.map} +1 -1
- package/dist/chunks/{init-BsQ3dhwf.js → init-IQRjA1g3.js} +5 -6
- package/dist/chunks/{init-BsQ3dhwf.js.map → init-IQRjA1g3.js.map} +1 -1
- package/dist/chunks/{init-tui-Dvk6Ndvl.js → init-tui-B4jfmo3U.js} +5 -6
- package/dist/chunks/{init-tui-Dvk6Ndvl.js.map → init-tui-B4jfmo3U.js.map} +1 -1
- package/dist/chunks/{login-BhtodVsj.js → login-CrIcDJpS.js} +2 -2
- package/dist/chunks/{login-BhtodVsj.js.map → login-CrIcDJpS.js.map} +1 -1
- package/dist/chunks/{logout-CDDASeuQ.js → logout-BxgiczmY.js} +2 -2
- package/dist/chunks/{logout-CDDASeuQ.js.map → logout-BxgiczmY.js.map} +1 -1
- package/dist/chunks/{main-tui-Cklcr3FX.js → main-tui-DmZeCepg.js} +7 -8
- package/dist/chunks/{main-tui-Cklcr3FX.js.map → main-tui-DmZeCepg.js.map} +1 -1
- package/dist/chunks/{port-allocator-Ct3ioni4.js → port-allocator-DuAZe2_S.js} +3 -4
- package/dist/chunks/{port-allocator-Ct3ioni4.js.map → port-allocator-DuAZe2_S.js.map} +1 -1
- package/dist/chunks/{run-wycadErJ.js → run-BAh7Xc-y.js} +11 -17
- package/dist/chunks/{run-wycadErJ.js.map → run-BAh7Xc-y.js.map} +1 -1
- package/dist/chunks/{start-CQKEEma-.js → start-B-brfyVy.js} +5 -6
- package/dist/chunks/{start-CQKEEma-.js.map → start-B-brfyVy.js.map} +1 -1
- package/dist/chunks/{theme-CktnrDZj.js → theme-DOjeB8BU.js} +13 -8
- package/dist/chunks/{theme-CktnrDZj.js.map → theme-DOjeB8BU.js.map} +1 -1
- package/dist/chunks/{use-app-Cj2bzWaw.js → use-app-DozfqdJj.js} +2 -2
- package/dist/chunks/{use-app-Cj2bzWaw.js.map → use-app-DozfqdJj.js.map} +1 -1
- package/dist/chunks/{useBuildState-pcDGDakI.js → useBuildState-DV6wurQ2.js} +2 -2
- package/dist/chunks/{useBuildState-pcDGDakI.js.map → useBuildState-DV6wurQ2.js.map} +1 -1
- package/dist/cli/index.js +7 -7
- package/dist/index.js +1128 -2809
- package/dist/index.js.map +1 -1
- package/dist/instrument.js +7 -64162
- package/dist/instrument.js.map +1 -1
- package/package.json +5 -13
- package/dist/chunks/_commonjsHelpers-h-Bqc03Z.js +0 -34
- package/dist/chunks/_commonjsHelpers-h-Bqc03Z.js.map +0 -1
- package/dist/chunks/exports-ij9sv4UM.js +0 -7793
- package/dist/chunks/exports-ij9sv4UM.js.map +0 -1
- package/scripts/install-vendor-deps.js +0 -34
- package/scripts/install-vendor.js +0 -167
- package/scripts/prepare-release.js +0 -83
- package/vendor/ai-sdk-provider-claude-code-LOCAL.tgz +0 -0
- package/vendor/sentry-core-LOCAL.tgz +0 -0
- package/vendor/sentry-nextjs-LOCAL.tgz +0 -0
- package/vendor/sentry-node-LOCAL.tgz +0 -0
- 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
|
|
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
|
|
7
|
+
import path$1, { resolve, relative, isAbsolute, dirname, join as join$1 } from 'node:path';
|
|
7
8
|
import { fileURLToPath } from 'node:url';
|
|
8
|
-
import {
|
|
9
|
-
import '
|
|
10
|
-
import '
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import
|
|
15
|
-
import {
|
|
16
|
-
import
|
|
17
|
-
import {
|
|
18
|
-
import
|
|
19
|
-
import {
|
|
20
|
-
import
|
|
21
|
-
import {
|
|
22
|
-
import
|
|
23
|
-
import { randomUUID
|
|
24
|
-
import
|
|
25
|
-
import {
|
|
26
|
-
import
|
|
27
|
-
import {
|
|
28
|
-
import
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
31
|
-
import
|
|
32
|
-
import
|
|
33
|
-
import
|
|
34
|
-
import
|
|
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 [
|
|
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
|
-
|
|
6487
|
-
|
|
6488
|
-
|
|
6489
|
-
|
|
6490
|
-
|
|
6491
|
-
|
|
6492
|
-
|
|
6493
|
-
|
|
6494
|
-
|
|
6495
|
-
|
|
6496
|
-
|
|
6497
|
-
|
|
6498
|
-
|
|
6499
|
-
|
|
6500
|
-
|
|
6501
|
-
|
|
6502
|
-
|
|
6503
|
-
|
|
6504
|
-
|
|
6505
|
-
|
|
6506
|
-
|
|
6507
|
-
|
|
6508
|
-
|
|
6509
|
-
|
|
6510
|
-
|
|
6511
|
-
|
|
6512
|
-
|
|
6513
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
7115
|
-
|
|
7116
|
-
|
|
7117
|
-
|
|
7118
|
-
|
|
7119
|
-
|
|
7120
|
-
|
|
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
|
-
|
|
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
|
-
|
|
7131
|
-
|
|
7132
|
-
//
|
|
7133
|
-
|
|
7134
|
-
|
|
7135
|
-
|
|
7136
|
-
|
|
7137
|
-
|
|
7138
|
-
|
|
7139
|
-
|
|
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
|
-
//
|
|
7145
|
-
|
|
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
|
|
8763
|
-
var
|
|
8764
|
-
"src/lib/agents/
|
|
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
|
-
|
|
8770
|
-
buildSystemPromptSections:
|
|
8771
|
-
buildFullPrompt:
|
|
8772
|
-
shouldDownloadTemplate() {
|
|
8773
|
-
return
|
|
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
|
-
|
|
8785
|
-
|
|
8786
|
-
|
|
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
|
-
|
|
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 [
|
|
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$
|
|
7483
|
+
const debugLog$1 = (message) => {
|
|
8834
7484
|
if (process.env.SILENT_MODE !== '1' && process.env.DEBUG_BUILD === '1') {
|
|
8835
|
-
debugLog$
|
|
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 ||
|
|
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
|
|
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$
|
|
7530
|
+
debugLog$1();
|
|
8881
7531
|
const generator = query(fullPrompt, actualWorkingDir, systemPrompt, agent, options.codexThreadId, messageParts);
|
|
8882
|
-
debugLog$
|
|
7532
|
+
debugLog$1();
|
|
8883
7533
|
// Create a ReadableStream from the AsyncGenerator
|
|
8884
7534
|
const stream = new ReadableStream({
|
|
8885
7535
|
async start(controller) {
|
|
8886
|
-
debugLog$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
9566
|
-
|
|
9567
|
-
|
|
9568
|
-
|
|
9569
|
-
|
|
9570
|
-
|
|
9571
|
-
|
|
9572
|
-
|
|
9573
|
-
|
|
9574
|
-
|
|
9575
|
-
|
|
9576
|
-
|
|
9577
|
-
|
|
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
|
|
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
|
-
|
|
11080
|
-
|
|
11081
|
-
|
|
11082
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
9644
|
+
if (existsSync(viteConfigTs))
|
|
11285
9645
|
configPath = viteConfigTs;
|
|
11286
|
-
else if (existsSync
|
|
9646
|
+
else if (existsSync(viteConfigMts))
|
|
11287
9647
|
configPath = viteConfigMts;
|
|
11288
|
-
else if (existsSync
|
|
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
|
|
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
|
-
|
|
12348
|
-
|
|
12349
|
-
|
|
12350
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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 (
|
|
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
|
-
//
|
|
12869
|
-
if (
|
|
12870
|
-
|
|
12871
|
-
|
|
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
|
|
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-${
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
14445
|
-
|
|
14446
|
-
|
|
14447
|
-
|
|
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 =
|
|
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-
|
|
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
|
-
|
|
14596
|
-
|
|
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
|
-
|
|
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 =
|
|
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:
|
|
14869
|
-
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 () => {
|