codex-relay 1.0.3 → 1.0.4
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/api-schema.js +2 -2
- package/dist/api-schema2.js +178 -2
- package/dist/cli.js +1 -1
- package/dist/src.js +525 -34
- package/package.json +1 -1
- package/src/api-schema.ts +174 -0
package/dist/src.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { A as PairResponseSchema,
|
|
1
|
+
import { A as PairResponseSchema, C as ListQueuedThreadInputsResponseSchema, D as ListWorkspaceFilesResponseSchema, Dt as apiPaths, E as ListWorkspaceDirectoriesResponseSchema, G as ResolveApprovalRequestSchema, K as ResolveApprovalResponseSchema, Lt as stripPromptSkillMentions, Mt as promptMarkdownWithSkills, Ot as chatMessageDetailsFromPromptContext, Q as RuntimePreferencesSchema, S as ListModelsResponseSchema, St as WorkspaceGitActionResponseSchema, T as ListThreadsResponseSchema, U as RateLimitsResponseSchema, X as RuntimePreferencesByWorkspacePathSchema, Z as RuntimePreferencesResponseSchema, _ as EncryptedPayloadSchema, a as ArchiveThreadResponseSchema, at as ThreadContextWindowResponseSchema, b as InterruptThreadRunResponseSchema, bt as WorkspaceFileContentResponseSchema, ct as ThreadMessageDetailResponseSchema, d as CheckoutWorkspaceBranchRequestSchema, dt as ThreadSummarySchema, et as StatusResponseSchema, ft as UpdateRuntimePreferencesRequestSchema, h as CreateThreadRequestSchema, jt as normalizePromptContext, k as PairRequestSchema, kt as createOpenApiDocument, l as ChatMessageSchema, m as ContextWindowUsageSchema, mt as VersionResponseSchema, nt as StreamThreadRunRequestSchema, ot as ThreadDetailResponseSchema, p as CommitPushWorkspaceRequestSchema, pt as UpdateWorkspaceFileContentRequestSchema, q as RunThreadRequestSchema, rt as SubmitThreadInputResponseSchema, st as ThreadMessageDetailFieldSchema, tt as StreamThreadRunEventSchema, vt as WorkspaceChangesResponseSchema, w as ListSkillsResponseSchema, y as ImageAttachmentUploadResponseSchema, z as QueuedThreadInputActionResponseSchema } from "./api-schema2.js";
|
|
2
2
|
import { r as legacyCodexRelayDataPath, t as codexRelayDataPath } from "./paths.js";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import qrcode from "qrcode-terminal";
|
|
5
5
|
import { execFile, execFileSync, spawn } from "node:child_process";
|
|
6
6
|
import fs, { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
7
|
-
import { access, appendFile, copyFile, mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
|
|
8
|
-
import path, { basename, dirname, extname, join, relative, resolve } from "node:path";
|
|
7
|
+
import { access, appendFile, copyFile, mkdir, open, readFile, readdir, stat, writeFile } from "node:fs/promises";
|
|
8
|
+
import path, { basename, dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
|
10
10
|
import { z } from "zod";
|
|
11
11
|
import os, { homedir, hostname, networkInterfaces } from "node:os";
|
|
@@ -539,14 +539,16 @@ function updatePreferencesFile(current, patch, workspacePath) {
|
|
|
539
539
|
});
|
|
540
540
|
}
|
|
541
541
|
function mergeRuntimePreferences(current, patch) {
|
|
542
|
-
const { model, reasoningEffort, threadId: _threadId, workspacePath: _workspacePath, ...rest } = UpdateRuntimePreferencesRequestSchema.parse(patch);
|
|
542
|
+
const { model, serviceTier, reasoningEffort, threadId: _threadId, workspacePath: _workspacePath, ...rest } = UpdateRuntimePreferencesRequestSchema.parse(patch);
|
|
543
543
|
const next = {
|
|
544
544
|
...current,
|
|
545
545
|
...rest,
|
|
546
546
|
...model === null ? {} : { model },
|
|
547
|
+
...serviceTier === null ? {} : { serviceTier },
|
|
547
548
|
...reasoningEffort === null ? {} : { reasoningEffort }
|
|
548
549
|
};
|
|
549
550
|
if (model === null) delete next.model;
|
|
551
|
+
if (serviceTier === null) delete next.serviceTier;
|
|
550
552
|
if (reasoningEffort === null) delete next.reasoningEffort;
|
|
551
553
|
return RuntimePreferencesSchema.parse({ ...next });
|
|
552
554
|
}
|
|
@@ -798,6 +800,9 @@ const defaultWorkspacePath = process.cwd();
|
|
|
798
800
|
const defaultCodexModel = "gpt-5.5";
|
|
799
801
|
const execFileAsync = promisify(execFile);
|
|
800
802
|
const IMAGE_ATTACHMENT_MAX_BYTES = 8 * 1024 * 1024;
|
|
803
|
+
const WORKSPACE_FILE_PREVIEW_MAX_BYTES = 256 * 1024;
|
|
804
|
+
const LOCAL_MARKDOWN_IMAGE_PATTERN = /!\[([^\]]*)\]\(([^)]*)\)/g;
|
|
805
|
+
const LOCAL_IMAGE_REFERENCE_PATTERN = /\.(gif|heic|heif|jpe?g|png|webp)$/i;
|
|
801
806
|
const imageAttachmentDirectory = codexRelayDataPath("attachments/images");
|
|
802
807
|
const relayPackage = createRequire(import.meta.url)("../package.json");
|
|
803
808
|
const collaborationModeTemplateNames = [
|
|
@@ -1157,8 +1162,12 @@ function createApp(options = {}) {
|
|
|
1157
1162
|
const selectedWorkspacePath = await validateThreadWorkspacePath(workspacePath, c.req.query("workspacePath"));
|
|
1158
1163
|
if (!selectedWorkspacePath.success) return secureJson(c, options.pairing, secureSessionsByTokenHash, apiError("invalid_workspace_path", selectedWorkspacePath.error), 400);
|
|
1159
1164
|
const query = c.req.query("query")?.trim() ?? "";
|
|
1165
|
+
const directoryPath = normalizeWorkspaceDirectoryPath(c.req.query("directory") ?? "");
|
|
1166
|
+
if (!directoryPath.success) return secureJson(c, options.pairing, secureSessionsByTokenHash, apiError("invalid_workspace_file_path", directoryPath.error), 400);
|
|
1160
1167
|
const response = ListWorkspaceFilesResponseSchema.parse({
|
|
1161
|
-
|
|
1168
|
+
directory: directoryPath.path,
|
|
1169
|
+
files: await listWorkspaceFiles(selectedWorkspacePath.path, query, directoryPath.path),
|
|
1170
|
+
parentDirectory: parentWorkspaceDirectory(directoryPath.path),
|
|
1162
1171
|
query,
|
|
1163
1172
|
workspacePath: selectedWorkspacePath.path
|
|
1164
1173
|
});
|
|
@@ -1167,6 +1176,40 @@ function createApp(options = {}) {
|
|
|
1167
1176
|
return secureJson(c, options.pairing, secureSessionsByTokenHash, apiError("workspace_files_unavailable", errorMessage(error)), 502);
|
|
1168
1177
|
}
|
|
1169
1178
|
});
|
|
1179
|
+
app.get(apiPaths.workspaceFileContent, async (c) => {
|
|
1180
|
+
try {
|
|
1181
|
+
const selectedWorkspacePath = await validateThreadWorkspacePath(workspacePath, c.req.query("workspacePath"));
|
|
1182
|
+
if (!selectedWorkspacePath.success) return secureJson(c, options.pairing, secureSessionsByTokenHash, apiError("invalid_workspace_path", selectedWorkspacePath.error), 400);
|
|
1183
|
+
const requestedPath = c.req.query("path")?.trim();
|
|
1184
|
+
if (!requestedPath) return secureJson(c, options.pairing, secureSessionsByTokenHash, apiError("missing_workspace_file_path", "Workspace file path is required."), 400);
|
|
1185
|
+
const file = await readWorkspaceFileContent(selectedWorkspacePath.path, requestedPath);
|
|
1186
|
+
if (!file.success) return secureJson(c, options.pairing, secureSessionsByTokenHash, apiError(file.code, file.error), file.status);
|
|
1187
|
+
const response = WorkspaceFileContentResponseSchema.parse({
|
|
1188
|
+
workspacePath: selectedWorkspacePath.path,
|
|
1189
|
+
...file.content
|
|
1190
|
+
});
|
|
1191
|
+
return secureJson(c, options.pairing, secureSessionsByTokenHash, response);
|
|
1192
|
+
} catch (error) {
|
|
1193
|
+
return secureJson(c, options.pairing, secureSessionsByTokenHash, apiError("workspace_file_unavailable", errorMessage(error)), 502);
|
|
1194
|
+
}
|
|
1195
|
+
});
|
|
1196
|
+
app.put(apiPaths.workspaceFileContent, async (c) => {
|
|
1197
|
+
const parsed = await parseRequestJson(c, options.pairing, secureSessionsByTokenHash, UpdateWorkspaceFileContentRequestSchema);
|
|
1198
|
+
if (!parsed.success) return secureJson(c, options.pairing, secureSessionsByTokenHash, validationError(parsed.error), 400);
|
|
1199
|
+
try {
|
|
1200
|
+
const selectedWorkspacePath = await validateThreadWorkspacePath(workspacePath, parsed.data.workspacePath);
|
|
1201
|
+
if (!selectedWorkspacePath.success) return secureJson(c, options.pairing, secureSessionsByTokenHash, apiError("invalid_workspace_path", selectedWorkspacePath.error), 400);
|
|
1202
|
+
const file = await updateWorkspaceFileContent(selectedWorkspacePath.path, parsed.data);
|
|
1203
|
+
if (!file.success) return secureJson(c, options.pairing, secureSessionsByTokenHash, apiError(file.code, file.error), file.status);
|
|
1204
|
+
const response = WorkspaceFileContentResponseSchema.parse({
|
|
1205
|
+
workspacePath: selectedWorkspacePath.path,
|
|
1206
|
+
...file.content
|
|
1207
|
+
});
|
|
1208
|
+
return secureJson(c, options.pairing, secureSessionsByTokenHash, response);
|
|
1209
|
+
} catch (error) {
|
|
1210
|
+
return secureJson(c, options.pairing, secureSessionsByTokenHash, apiError("workspace_file_update_unavailable", errorMessage(error)), 502);
|
|
1211
|
+
}
|
|
1212
|
+
});
|
|
1170
1213
|
app.get(apiPaths.rateLimits, async (c) => {
|
|
1171
1214
|
if (!appServer) return secureJson(c, options.pairing, secureSessionsByTokenHash, RateLimitsResponseSchema.parse({ buckets: [] }));
|
|
1172
1215
|
try {
|
|
@@ -1299,7 +1342,7 @@ function createApp(options = {}) {
|
|
|
1299
1342
|
let loadedMessages = false;
|
|
1300
1343
|
let messages = cachedMessages;
|
|
1301
1344
|
let responseThread = preserveKnownRunningThreadState(mappedThread, wasKnownRunning);
|
|
1302
|
-
const rolloutHistory = readRolloutThreadMessages(threadId);
|
|
1345
|
+
const rolloutHistory = readRolloutThreadMessages(threadId, workspacePath);
|
|
1303
1346
|
if (rolloutHistory.messages.length > 0) {
|
|
1304
1347
|
messages = mergeThreadMessagePages(rolloutHistory.messages, cachedMessages);
|
|
1305
1348
|
responseThread = rememberRolloutThreadMessages(threads, responseThread, messages, rolloutHistory.messageCountLowerBound);
|
|
@@ -1338,7 +1381,7 @@ function createApp(options = {}) {
|
|
|
1338
1381
|
});
|
|
1339
1382
|
}
|
|
1340
1383
|
const cachedMessages = messagesByThreadId.get(threadId) ?? [];
|
|
1341
|
-
const rolloutHistory = readRolloutThreadMessages(threadId);
|
|
1384
|
+
const rolloutHistory = readRolloutThreadMessages(threadId, workspacePath);
|
|
1342
1385
|
const baseThread = threads.get(threadId) ?? knownThread ?? (rolloutHistory.rolloutPath ? rolloutThreadMetadata(threadId, workspacePath, rolloutHistory.rolloutPath, [...rolloutHistory.messages, ...cachedMessages]) : void 0);
|
|
1343
1386
|
if (baseThread && (cachedMessages.length > 0 || rolloutHistory.messages.length > 0)) {
|
|
1344
1387
|
const messages = rolloutHistory.messages.length > 0 ? mergeThreadMessagePages(rolloutHistory.messages, cachedMessages) : dedupeThreadMessages(cachedMessages);
|
|
@@ -1880,7 +1923,8 @@ async function createAppServerThreadRecord(input) {
|
|
|
1880
1923
|
experimentalRawEvents: false,
|
|
1881
1924
|
model: input.options.model ?? null,
|
|
1882
1925
|
persistExtendedHistory: true,
|
|
1883
|
-
sandbox: runtime.sandbox
|
|
1926
|
+
sandbox: runtime.sandbox,
|
|
1927
|
+
serviceTier: input.options.serviceTier ?? null
|
|
1884
1928
|
});
|
|
1885
1929
|
const metadata = ThreadSummarySchema.parse({
|
|
1886
1930
|
...mapAppServerThread({
|
|
@@ -2133,6 +2177,7 @@ async function startAppServerTurn(appServer, threadId, input) {
|
|
|
2133
2177
|
input: appServerTurnInput(input.prompt, input.attachments, input.skills),
|
|
2134
2178
|
model: input.runOptions.model ?? null,
|
|
2135
2179
|
sandboxPolicy: runtime.sandboxPolicy,
|
|
2180
|
+
serviceTier: input.runOptions.serviceTier ?? null,
|
|
2136
2181
|
threadId
|
|
2137
2182
|
};
|
|
2138
2183
|
await resumeAppServerThreadIfNeeded(appServer, threadId, input, runtime);
|
|
@@ -2164,6 +2209,7 @@ async function resumeAppServerThread(appServer, threadId, input, runtime) {
|
|
|
2164
2209
|
model: input.runOptions.model ?? null,
|
|
2165
2210
|
persistExtendedHistory: true,
|
|
2166
2211
|
sandbox: runtime.sandbox,
|
|
2212
|
+
serviceTier: input.runOptions.serviceTier ?? null,
|
|
2167
2213
|
threadId
|
|
2168
2214
|
});
|
|
2169
2215
|
}
|
|
@@ -2918,7 +2964,8 @@ async function recoverMissingAppServerThread(input) {
|
|
|
2918
2964
|
experimentalRawEvents: false,
|
|
2919
2965
|
model: input.runOptions.model ?? null,
|
|
2920
2966
|
persistExtendedHistory: true,
|
|
2921
|
-
sandbox: runtime.sandbox
|
|
2967
|
+
sandbox: runtime.sandbox,
|
|
2968
|
+
serviceTier: input.runOptions.serviceTier ?? null
|
|
2922
2969
|
});
|
|
2923
2970
|
const recoveredThread = mapAppServerThread({
|
|
2924
2971
|
...thread,
|
|
@@ -3109,6 +3156,9 @@ function appServerUserMessageText(item) {
|
|
|
3109
3156
|
case "text": return content.text;
|
|
3110
3157
|
case "image":
|
|
3111
3158
|
case "localImage":
|
|
3159
|
+
case "document":
|
|
3160
|
+
case "file":
|
|
3161
|
+
case "localFile":
|
|
3112
3162
|
case "mention":
|
|
3113
3163
|
case "skill": return "";
|
|
3114
3164
|
}
|
|
@@ -3127,11 +3177,86 @@ function appServerUserMessageDetails(item) {
|
|
|
3127
3177
|
type: "image"
|
|
3128
3178
|
}];
|
|
3129
3179
|
case "localImage": return localImageAttachmentDetails(content.path);
|
|
3180
|
+
case "document":
|
|
3181
|
+
case "file":
|
|
3182
|
+
case "localFile": return appServerDocumentAttachmentDetails(content);
|
|
3130
3183
|
default: return [];
|
|
3131
3184
|
}
|
|
3132
3185
|
});
|
|
3133
3186
|
return attachments.length > 0 ? { attachments } : void 0;
|
|
3134
3187
|
}
|
|
3188
|
+
function appServerAgentMessageParts(item) {
|
|
3189
|
+
return agentMessageParts(item.text);
|
|
3190
|
+
}
|
|
3191
|
+
function agentMessageParts(text) {
|
|
3192
|
+
const imageReferences = localMarkdownImageReferences(text);
|
|
3193
|
+
if (imageReferences.length === 0) return {
|
|
3194
|
+
content: text,
|
|
3195
|
+
details: void 0
|
|
3196
|
+
};
|
|
3197
|
+
const materializedReferences = /* @__PURE__ */ new Set();
|
|
3198
|
+
const attachments = imageReferences.flatMap((reference) => {
|
|
3199
|
+
const name = reference.alt || basename(localImageFilePath(reference.destination) ?? reference.destination);
|
|
3200
|
+
const details = localImageAttachmentDetails(reference.destination, name);
|
|
3201
|
+
if (details.length > 0) materializedReferences.add(reference.destination);
|
|
3202
|
+
return details;
|
|
3203
|
+
});
|
|
3204
|
+
return {
|
|
3205
|
+
content: attachments.length > 0 ? stripMaterializedMarkdownImages(text, materializedReferences) : text,
|
|
3206
|
+
details: attachments.length > 0 ? { attachments } : void 0
|
|
3207
|
+
};
|
|
3208
|
+
}
|
|
3209
|
+
function localMarkdownImageReferences(markdown) {
|
|
3210
|
+
const references = [];
|
|
3211
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3212
|
+
for (const match of markdown.matchAll(LOCAL_MARKDOWN_IMAGE_PATTERN)) {
|
|
3213
|
+
const destination = markdownImageDestination(match[2] ?? "");
|
|
3214
|
+
if (!destination || seen.has(destination) || !isLocalMarkdownImageReference(destination)) continue;
|
|
3215
|
+
seen.add(destination);
|
|
3216
|
+
references.push({
|
|
3217
|
+
alt: (match[1] ?? "").trim(),
|
|
3218
|
+
destination
|
|
3219
|
+
});
|
|
3220
|
+
}
|
|
3221
|
+
return references;
|
|
3222
|
+
}
|
|
3223
|
+
function stripMaterializedMarkdownImages(markdown, destinations) {
|
|
3224
|
+
return markdown.replace(LOCAL_MARKDOWN_IMAGE_PATTERN, (match, _alt, rawDestination) => {
|
|
3225
|
+
const destination = markdownImageDestination(rawDestination);
|
|
3226
|
+
return destination && destinations.has(destination) ? "" : match;
|
|
3227
|
+
}).replace(/[ \t]+\n/g, "\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
3228
|
+
}
|
|
3229
|
+
function markdownImageDestination(value) {
|
|
3230
|
+
let destination = value.trim();
|
|
3231
|
+
if (destination.startsWith("<")) {
|
|
3232
|
+
const endIndex = destination.indexOf(">");
|
|
3233
|
+
if (endIndex > 0) destination = destination.slice(1, endIndex);
|
|
3234
|
+
} else {
|
|
3235
|
+
const titleIndex = destination.search(/\s+["']/);
|
|
3236
|
+
if (titleIndex > 0) destination = destination.slice(0, titleIndex);
|
|
3237
|
+
}
|
|
3238
|
+
try {
|
|
3239
|
+
return decodeURI(destination);
|
|
3240
|
+
} catch {
|
|
3241
|
+
return destination;
|
|
3242
|
+
}
|
|
3243
|
+
}
|
|
3244
|
+
function isLocalMarkdownImageReference(reference) {
|
|
3245
|
+
if (/^[A-Za-z][A-Za-z0-9+.-]*:/.test(reference) && !reference.startsWith("file://")) return false;
|
|
3246
|
+
const filePath = localImageFilePath(reference);
|
|
3247
|
+
return Boolean(filePath && LOCAL_IMAGE_REFERENCE_PATTERN.test(filePath));
|
|
3248
|
+
}
|
|
3249
|
+
function appServerDocumentAttachmentDetails(input) {
|
|
3250
|
+
const reference = input.path ?? input.url;
|
|
3251
|
+
if (!reference) return [];
|
|
3252
|
+
return [{
|
|
3253
|
+
mimeType: input.mimeType,
|
|
3254
|
+
name: input.name ?? basename(reference),
|
|
3255
|
+
path: input.path,
|
|
3256
|
+
type: "document",
|
|
3257
|
+
url: input.url
|
|
3258
|
+
}];
|
|
3259
|
+
}
|
|
3135
3260
|
async function saveUploadedImageAttachment(file) {
|
|
3136
3261
|
if (!file.type.startsWith("image/")) throw new Error(`Unsupported attachment type: ${file.type || "unknown"}`);
|
|
3137
3262
|
if (file.size > IMAGE_ATTACHMENT_MAX_BYTES) throw new Error(`Image ${file.name || "attachment"} is too large.`);
|
|
@@ -3556,6 +3681,7 @@ function runtimeMetadataFromOptions(options) {
|
|
|
3556
3681
|
const sandboxMode = options.sandboxMode ?? runtime.sandboxMode;
|
|
3557
3682
|
return {
|
|
3558
3683
|
...options.model ? { model: options.model } : {},
|
|
3684
|
+
...options.serviceTier ? { serviceTier: options.serviceTier } : {},
|
|
3559
3685
|
...options.runtimeMode ? { runtimeMode: options.runtimeMode } : {},
|
|
3560
3686
|
...options.collaborationMode ? { collaborationMode: options.collaborationMode } : {},
|
|
3561
3687
|
...approvalPolicy ? { approvalPolicy } : {},
|
|
@@ -3568,6 +3694,7 @@ function withRuntimePreferences(preferences, options) {
|
|
|
3568
3694
|
...options,
|
|
3569
3695
|
approvalPolicy: options.approvalPolicy,
|
|
3570
3696
|
model: options.model ?? preferences.model,
|
|
3697
|
+
serviceTier: options.serviceTier ?? preferences.serviceTier,
|
|
3571
3698
|
reasoningEffort: options.reasoningEffort ?? preferences.reasoningEffort,
|
|
3572
3699
|
runtimeMode: options.runtimeMode ?? preferences.runtimeMode,
|
|
3573
3700
|
sandboxMode: options.sandboxMode
|
|
@@ -3615,7 +3742,7 @@ function sandboxPolicyForMode(sandboxMode, workspacePath) {
|
|
|
3615
3742
|
};
|
|
3616
3743
|
}
|
|
3617
3744
|
function hasExplicitRunOptions(options) {
|
|
3618
|
-
return Boolean(options.model || options.reasoningEffort || options.approvalPolicy || options.sandboxMode || options.collaborationMode === "plan" || options.runtimeMode);
|
|
3745
|
+
return Boolean(options.model || options.serviceTier || options.reasoningEffort || options.approvalPolicy || options.sandboxMode || options.collaborationMode === "plan" || options.runtimeMode);
|
|
3619
3746
|
}
|
|
3620
3747
|
function mapAppServerThread(thread, fallbackMessageCount) {
|
|
3621
3748
|
const createdAt = fromUnixSeconds(thread.createdAt);
|
|
@@ -3791,7 +3918,7 @@ function readSessionIndexThreadTitle(threadId) {
|
|
|
3791
3918
|
} catch {}
|
|
3792
3919
|
}
|
|
3793
3920
|
}
|
|
3794
|
-
function readRolloutThreadMessages(threadId) {
|
|
3921
|
+
function readRolloutThreadMessages(threadId, workspacePath = defaultWorkspacePath) {
|
|
3795
3922
|
const rolloutPath = findRolloutFileForThread(threadId);
|
|
3796
3923
|
if (!rolloutPath) return {
|
|
3797
3924
|
messageCountLowerBound: 0,
|
|
@@ -3799,6 +3926,8 @@ function readRolloutThreadMessages(threadId) {
|
|
|
3799
3926
|
rolloutPath
|
|
3800
3927
|
};
|
|
3801
3928
|
const collected = [];
|
|
3929
|
+
const applyPatchInputs = /* @__PURE__ */ new Map();
|
|
3930
|
+
const pendingApplyPatchChanges = [];
|
|
3802
3931
|
const lines = readFileSync(rolloutPath, "utf8").split("\n");
|
|
3803
3932
|
for (let index = 0; index < lines.length; index += 1) {
|
|
3804
3933
|
const line = lines[index];
|
|
@@ -3806,7 +3935,15 @@ function readRolloutThreadMessages(threadId) {
|
|
|
3806
3935
|
if (!line.trim()) continue;
|
|
3807
3936
|
if (!isRolloutMessageLine(line)) continue;
|
|
3808
3937
|
try {
|
|
3809
|
-
const
|
|
3938
|
+
const record = JSON.parse(line);
|
|
3939
|
+
rememberRolloutApplyPatchInput(record, applyPatchInputs);
|
|
3940
|
+
collectRolloutApplyPatchOutput(record, workspacePath, applyPatchInputs, pendingApplyPatchChanges);
|
|
3941
|
+
if (isRolloutTaskComplete(record) && pendingApplyPatchChanges.length > 0) {
|
|
3942
|
+
collected.push(rolloutApplyPatchSummaryMessage(threadId, record, `rollout:${lineNumber}:apply_patch`, pendingApplyPatchChanges));
|
|
3943
|
+
pendingApplyPatchChanges.length = 0;
|
|
3944
|
+
continue;
|
|
3945
|
+
}
|
|
3946
|
+
const message = rolloutRecordMessage(threadId, record, `rollout:${lineNumber}`, workspacePath);
|
|
3810
3947
|
if (!message) continue;
|
|
3811
3948
|
collected.push(message);
|
|
3812
3949
|
} catch {}
|
|
@@ -3833,7 +3970,10 @@ function findRolloutFileForThread(threadId) {
|
|
|
3833
3970
|
}
|
|
3834
3971
|
}
|
|
3835
3972
|
}
|
|
3836
|
-
function
|
|
3973
|
+
function isRolloutTaskComplete(record) {
|
|
3974
|
+
return record.type === "event_msg" && record.payload?.type === "task_complete";
|
|
3975
|
+
}
|
|
3976
|
+
function rolloutRecordMessage(threadId, record, messageKey, workspacePath = defaultWorkspacePath) {
|
|
3837
3977
|
const timestamp = typeof record.timestamp === "string" ? record.timestamp : (/* @__PURE__ */ new Date()).toISOString();
|
|
3838
3978
|
const payload = record.payload;
|
|
3839
3979
|
if (!payload) return;
|
|
@@ -3853,15 +3993,37 @@ function rolloutRecordMessage(threadId, record, messageKey) {
|
|
|
3853
3993
|
if (record.type === "event_msg" && payload.type === "agent_message") {
|
|
3854
3994
|
const content = firstString(payload, ["message"]);
|
|
3855
3995
|
if (!content) return;
|
|
3996
|
+
const messageParts = agentMessageParts(content);
|
|
3856
3997
|
return ChatMessageSchema.parse({
|
|
3857
3998
|
id: `${messageKey}:assistant`,
|
|
3858
3999
|
threadId,
|
|
3859
4000
|
role: "assistant",
|
|
3860
|
-
content,
|
|
4001
|
+
content: messageParts.content,
|
|
4002
|
+
details: messageParts.details,
|
|
3861
4003
|
createdAt: timestamp,
|
|
3862
4004
|
state: "completed"
|
|
3863
4005
|
});
|
|
3864
4006
|
}
|
|
4007
|
+
if (record.type === "event_msg" && payload.type === "patch_apply_end") {
|
|
4008
|
+
const changes = rolloutPatchApplyChanges(payload.changes, workspacePath);
|
|
4009
|
+
if (changes.length === 0) return;
|
|
4010
|
+
const patchPreview = largeTextPreview(changes.map((change) => change.patch).filter((patch) => Boolean(patch)).join("\n"));
|
|
4011
|
+
return ChatMessageSchema.parse({
|
|
4012
|
+
id: `${messageKey}:patch:${firstString(payload, ["call_id"]) ?? ""}`,
|
|
4013
|
+
threadId,
|
|
4014
|
+
role: "tool",
|
|
4015
|
+
kind: "fileChange",
|
|
4016
|
+
content: summarizeFileChanges(changes),
|
|
4017
|
+
createdAt: timestamp,
|
|
4018
|
+
state: "completed",
|
|
4019
|
+
details: {
|
|
4020
|
+
changes: changes.map(({ patch: _patch, ...change }) => change),
|
|
4021
|
+
patch: patchPreview?.text,
|
|
4022
|
+
patchOriginalLength: patchPreview?.originalLength,
|
|
4023
|
+
patchTruncated: patchPreview?.truncated
|
|
4024
|
+
}
|
|
4025
|
+
});
|
|
4026
|
+
}
|
|
3865
4027
|
if (record.type === "event_msg" && payload.type === "exec_command_end") {
|
|
3866
4028
|
const command = Array.isArray(payload.command) ? payload.command.map((part) => String(part)).join(" ") : firstString(payload, ["command"]) || "Command";
|
|
3867
4029
|
const outputPreview = largeTextPreview(firstString(payload, ["aggregated_output"]));
|
|
@@ -3916,8 +4078,114 @@ function rolloutImageAttachments(value, source) {
|
|
|
3916
4078
|
return localImageAttachmentDetails(image, name);
|
|
3917
4079
|
});
|
|
3918
4080
|
}
|
|
4081
|
+
function rememberRolloutApplyPatchInput(record, applyPatchInputs) {
|
|
4082
|
+
const payload = record.payload;
|
|
4083
|
+
if (record.type !== "response_item" || payload?.type !== "custom_tool_call" || firstString(payload, ["name"]) !== "apply_patch") return;
|
|
4084
|
+
const callId = firstString(payload, ["call_id"]);
|
|
4085
|
+
const input = firstString(payload, ["input"]);
|
|
4086
|
+
if (callId && input) applyPatchInputs.set(callId, input);
|
|
4087
|
+
}
|
|
4088
|
+
function collectRolloutApplyPatchOutput(record, workspacePath, applyPatchInputs, pendingApplyPatchChanges) {
|
|
4089
|
+
const payload = record.payload;
|
|
4090
|
+
if (record.type !== "response_item" || payload?.type !== "custom_tool_call_output" || !firstString(payload, ["call_id"])) return;
|
|
4091
|
+
const changes = rolloutApplyPatchOutputChanges(rolloutCustomToolOutputText(payload), workspacePath);
|
|
4092
|
+
if (changes.length === 0) return;
|
|
4093
|
+
const callId = firstString(payload, ["call_id"]);
|
|
4094
|
+
const patch = callId ? applyPatchInputs.get(callId) : void 0;
|
|
4095
|
+
for (const change of changes) pendingApplyPatchChanges.push({
|
|
4096
|
+
...change,
|
|
4097
|
+
patch
|
|
4098
|
+
});
|
|
4099
|
+
}
|
|
4100
|
+
function rolloutApplyPatchSummaryMessage(threadId, record, messageKey, pendingApplyPatchChanges) {
|
|
4101
|
+
const timestamp = typeof record.timestamp === "string" ? record.timestamp : (/* @__PURE__ */ new Date()).toISOString();
|
|
4102
|
+
const patchPreview = largeTextPreview([...new Set(pendingApplyPatchChanges.map((change) => change.patch))].filter((patch) => Boolean(patch)).join("\n"));
|
|
4103
|
+
return ChatMessageSchema.parse({
|
|
4104
|
+
id: `${messageKey}:summary`,
|
|
4105
|
+
threadId,
|
|
4106
|
+
role: "tool",
|
|
4107
|
+
kind: "fileChange",
|
|
4108
|
+
content: summarizeFileChanges(pendingApplyPatchChanges),
|
|
4109
|
+
createdAt: timestamp,
|
|
4110
|
+
state: "completed",
|
|
4111
|
+
details: {
|
|
4112
|
+
changes: pendingApplyPatchChanges.map(({ patch: _patch, ...change }) => change),
|
|
4113
|
+
patch: patchPreview?.text,
|
|
4114
|
+
patchOriginalLength: patchPreview?.originalLength,
|
|
4115
|
+
patchTruncated: patchPreview?.truncated
|
|
4116
|
+
}
|
|
4117
|
+
});
|
|
4118
|
+
}
|
|
4119
|
+
function rolloutCustomToolOutputText(payload) {
|
|
4120
|
+
const rawOutput = firstString(payload, ["output"]);
|
|
4121
|
+
if (!rawOutput) return;
|
|
4122
|
+
try {
|
|
4123
|
+
const parsed = JSON.parse(rawOutput);
|
|
4124
|
+
return typeof parsed.output === "string" ? parsed.output : rawOutput;
|
|
4125
|
+
} catch {
|
|
4126
|
+
return rawOutput;
|
|
4127
|
+
}
|
|
4128
|
+
}
|
|
4129
|
+
function rolloutApplyPatchOutputChanges(value, workspacePath) {
|
|
4130
|
+
if (!value || !value.includes("Updated the following files")) return [];
|
|
4131
|
+
return value.split("\n").flatMap((line) => {
|
|
4132
|
+
const match = line.match(/^\s*([ADM])\s+(.+?)\s*$/);
|
|
4133
|
+
if (!match) return [];
|
|
4134
|
+
return [{
|
|
4135
|
+
kind: rolloutPatchChangeKind(match[1] === "A" ? "added" : match[1] === "D" ? "deleted" : "modified"),
|
|
4136
|
+
path: rolloutPatchDisplayPath(match[2] ?? "", workspacePath)
|
|
4137
|
+
}];
|
|
4138
|
+
});
|
|
4139
|
+
}
|
|
4140
|
+
function rolloutPatchApplyChanges(value, workspacePath = defaultWorkspacePath) {
|
|
4141
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return [];
|
|
4142
|
+
return Object.entries(value).flatMap(([rawPath, rawChange]) => {
|
|
4143
|
+
if (!rawChange || typeof rawChange !== "object") return [];
|
|
4144
|
+
const record = rawChange;
|
|
4145
|
+
return [{
|
|
4146
|
+
kind: rolloutPatchChangeKind(firstString(record, ["type"]) ?? "modified"),
|
|
4147
|
+
patch: firstString(record, ["unified_diff", "patch"]),
|
|
4148
|
+
path: rolloutPatchDisplayPath(rawPath, workspacePath)
|
|
4149
|
+
}];
|
|
4150
|
+
});
|
|
4151
|
+
}
|
|
4152
|
+
function rolloutPatchChangeKind(type) {
|
|
4153
|
+
const normalized = type.toLowerCase();
|
|
4154
|
+
if ([
|
|
4155
|
+
"add",
|
|
4156
|
+
"added",
|
|
4157
|
+
"create",
|
|
4158
|
+
"created"
|
|
4159
|
+
].includes(normalized)) return "added";
|
|
4160
|
+
if ([
|
|
4161
|
+
"delete",
|
|
4162
|
+
"deleted",
|
|
4163
|
+
"remove",
|
|
4164
|
+
"removed"
|
|
4165
|
+
].includes(normalized)) return "deleted";
|
|
4166
|
+
if ([
|
|
4167
|
+
"move",
|
|
4168
|
+
"moved",
|
|
4169
|
+
"rename",
|
|
4170
|
+
"renamed"
|
|
4171
|
+
].includes(normalized)) return "renamed";
|
|
4172
|
+
return "modified";
|
|
4173
|
+
}
|
|
4174
|
+
function rolloutPatchDisplayPath(value, workspacePath = defaultWorkspacePath) {
|
|
4175
|
+
const filePath = value.startsWith("file://") ? fileUrlPath(value) : value;
|
|
4176
|
+
if (!isAbsolute(filePath)) return filePath;
|
|
4177
|
+
const relativePath = relative(resolve(workspacePath), filePath);
|
|
4178
|
+
return relativePath && !relativePath.startsWith("..") && !isAbsolute(relativePath) ? relativePath.split("\\").join("/") : filePath;
|
|
4179
|
+
}
|
|
4180
|
+
function fileUrlPath(value) {
|
|
4181
|
+
try {
|
|
4182
|
+
return fileURLToPath(value);
|
|
4183
|
+
} catch {
|
|
4184
|
+
return value;
|
|
4185
|
+
}
|
|
4186
|
+
}
|
|
3919
4187
|
function isRolloutMessageLine(line) {
|
|
3920
|
-
return line.includes("\"type\":\"event_msg\"") && (line.includes("\"type\":\"user_message\"") || line.includes("\"type\":\"agent_message\"") || line.includes("\"type\":\"exec_command_end\"") || line.includes("\"type\":\"mcp_tool_call_end\""));
|
|
4188
|
+
return line.includes("\"type\":\"event_msg\"") && (line.includes("\"type\":\"user_message\"") || line.includes("\"type\":\"agent_message\"") || line.includes("\"type\":\"patch_apply_end\"") || line.includes("\"type\":\"task_complete\"") || line.includes("\"type\":\"exec_command_end\"") || line.includes("\"type\":\"mcp_tool_call_end\"")) || line.includes("\"type\":\"response_item\"") && (line.includes("\"type\":\"custom_tool_call\"") || line.includes("\"type\":\"custom_tool_call_output\""));
|
|
3921
4189
|
}
|
|
3922
4190
|
function threadDetailResponse(input) {
|
|
3923
4191
|
return ThreadDetailResponseSchema.parse({
|
|
@@ -3996,12 +4264,13 @@ function mapAppServerItem(threadId, turn, item) {
|
|
|
3996
4264
|
case "agentMessage": {
|
|
3997
4265
|
const agentItem = item;
|
|
3998
4266
|
const planContent = proposedPlanContent(agentItem.text);
|
|
4267
|
+
const messageParts = appServerAgentMessageParts(agentItem);
|
|
3999
4268
|
return ChatMessageSchema.parse({
|
|
4000
4269
|
...base,
|
|
4001
4270
|
role: "assistant",
|
|
4002
4271
|
kind: planContent ? "plan" : void 0,
|
|
4003
|
-
content: planContent ??
|
|
4004
|
-
details: planContent ? { raw: agentItem.text } :
|
|
4272
|
+
content: planContent ?? messageParts.content,
|
|
4273
|
+
details: planContent ? { raw: agentItem.text } : messageParts.details
|
|
4005
4274
|
});
|
|
4006
4275
|
}
|
|
4007
4276
|
case "reasoning": {
|
|
@@ -4115,6 +4384,11 @@ function appServerTurnShell(turnId) {
|
|
|
4115
4384
|
};
|
|
4116
4385
|
}
|
|
4117
4386
|
function mapAppServerModel(model) {
|
|
4387
|
+
const serviceTiers = model.serviceTiers ?? model.additionalSpeedTiers?.map((tier) => ({
|
|
4388
|
+
id: tier,
|
|
4389
|
+
name: tier === "fast" ? "Fast" : tier,
|
|
4390
|
+
description: void 0
|
|
4391
|
+
})) ?? [];
|
|
4118
4392
|
return {
|
|
4119
4393
|
id: model.id,
|
|
4120
4394
|
model: model.model,
|
|
@@ -4122,7 +4396,8 @@ function mapAppServerModel(model) {
|
|
|
4122
4396
|
description: model.description,
|
|
4123
4397
|
isDefault: Boolean(model.isDefault),
|
|
4124
4398
|
defaultReasoningEffort: model.defaultReasoningEffort,
|
|
4125
|
-
supportedReasoningEfforts: model.supportedReasoningEfforts?.map((effort) => effort.reasoningEffort) ?? []
|
|
4399
|
+
supportedReasoningEfforts: model.supportedReasoningEfforts?.map((effort) => effort.reasoningEffort) ?? [],
|
|
4400
|
+
serviceTiers
|
|
4126
4401
|
};
|
|
4127
4402
|
}
|
|
4128
4403
|
function normalizeRateLimitBuckets(rateLimits) {
|
|
@@ -4183,7 +4458,13 @@ function fallbackModels() {
|
|
|
4183
4458
|
{ reasoningEffort: "medium" },
|
|
4184
4459
|
{ reasoningEffort: "high" },
|
|
4185
4460
|
{ reasoningEffort: "xhigh" }
|
|
4186
|
-
]
|
|
4461
|
+
],
|
|
4462
|
+
additionalSpeedTiers: ["fast"],
|
|
4463
|
+
serviceTiers: [{
|
|
4464
|
+
id: "priority",
|
|
4465
|
+
name: "Fast",
|
|
4466
|
+
description: "1.5x speed, increased usage"
|
|
4467
|
+
}]
|
|
4187
4468
|
}];
|
|
4188
4469
|
}
|
|
4189
4470
|
function mapAppServerThreadState(status, turns) {
|
|
@@ -4887,53 +5168,235 @@ async function git(cwd, args) {
|
|
|
4887
5168
|
});
|
|
4888
5169
|
return stdout.trimEnd();
|
|
4889
5170
|
}
|
|
4890
|
-
async function listWorkspaceFiles(workspacePath, query) {
|
|
5171
|
+
async function listWorkspaceFiles(workspacePath, query, directory) {
|
|
4891
5172
|
const normalizedQuery = query.toLowerCase();
|
|
4892
5173
|
const filePaths = await workspaceFilePaths(workspacePath);
|
|
4893
5174
|
const entriesByPath = /* @__PURE__ */ new Map();
|
|
4894
5175
|
for (const path of filePaths) {
|
|
4895
5176
|
if (!path || path.startsWith("../") || path.includes("/.git/")) continue;
|
|
4896
|
-
|
|
4897
|
-
|
|
5177
|
+
if (!directory && normalizedQuery) {
|
|
5178
|
+
entriesByPath.set(path, {
|
|
5179
|
+
directory: dirname(path) === "." ? "" : dirname(path),
|
|
5180
|
+
kind: "file",
|
|
5181
|
+
name: path.split("/").pop() ?? path,
|
|
5182
|
+
path
|
|
5183
|
+
});
|
|
5184
|
+
const parts = path.split("/");
|
|
5185
|
+
for (let index = 1; index < parts.length; index += 1) {
|
|
5186
|
+
const directoryPath = parts.slice(0, index).join("/");
|
|
5187
|
+
entriesByPath.set(directoryPath, {
|
|
5188
|
+
directory: dirname(directoryPath) === "." ? "" : dirname(directoryPath),
|
|
5189
|
+
kind: "directory",
|
|
5190
|
+
name: parts[index - 1],
|
|
5191
|
+
path: directoryPath
|
|
5192
|
+
});
|
|
5193
|
+
}
|
|
5194
|
+
continue;
|
|
5195
|
+
}
|
|
5196
|
+
const prefix = directory ? `${directory}/` : "";
|
|
5197
|
+
if (!path.startsWith(prefix)) continue;
|
|
5198
|
+
const childParts = path.slice(prefix.length).split("/").filter(Boolean);
|
|
5199
|
+
if (childParts.length === 0) continue;
|
|
5200
|
+
if (childParts.length === 1) entriesByPath.set(path, {
|
|
5201
|
+
directory,
|
|
4898
5202
|
kind: "file",
|
|
4899
|
-
name:
|
|
5203
|
+
name: childParts[0],
|
|
4900
5204
|
path
|
|
4901
5205
|
});
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
const directoryPath = parts.slice(0, index).join("/");
|
|
5206
|
+
else {
|
|
5207
|
+
const directoryPath = [...directory ? directory.split("/") : [], childParts[0]].join("/");
|
|
4905
5208
|
entriesByPath.set(directoryPath, {
|
|
4906
|
-
directory
|
|
5209
|
+
directory,
|
|
4907
5210
|
kind: "directory",
|
|
4908
|
-
name:
|
|
5211
|
+
name: childParts[0],
|
|
4909
5212
|
path: directoryPath
|
|
4910
5213
|
});
|
|
4911
5214
|
}
|
|
4912
5215
|
}
|
|
4913
5216
|
return [...entriesByPath.values()].filter((entry) => {
|
|
4914
5217
|
if (!normalizedQuery) return true;
|
|
4915
|
-
return entry.path.toLowerCase().includes(normalizedQuery);
|
|
5218
|
+
return entry.name.toLowerCase().includes(normalizedQuery) || entry.path.toLowerCase().includes(normalizedQuery);
|
|
4916
5219
|
}).sort((left, right) => {
|
|
4917
5220
|
const leftScore = workspaceFileScore(left.path, normalizedQuery);
|
|
4918
5221
|
const rightScore = workspaceFileScore(right.path, normalizedQuery);
|
|
4919
5222
|
if (leftScore !== rightScore) return leftScore - rightScore;
|
|
4920
5223
|
if (left.kind !== right.kind) return left.kind === "directory" ? -1 : 1;
|
|
4921
5224
|
return left.path.localeCompare(right.path);
|
|
4922
|
-
}).slice(0, 80);
|
|
5225
|
+
}).slice(0, directory || !normalizedQuery ? 160 : 80);
|
|
5226
|
+
}
|
|
5227
|
+
async function readWorkspaceFileContent(workspacePath, requestedPath) {
|
|
5228
|
+
const relativePath = normalizeWorkspaceRelativePath(requestedPath);
|
|
5229
|
+
if (!relativePath.success) return {
|
|
5230
|
+
code: "invalid_workspace_file_path",
|
|
5231
|
+
error: relativePath.error,
|
|
5232
|
+
status: 400,
|
|
5233
|
+
success: false
|
|
5234
|
+
};
|
|
5235
|
+
const rootPath = resolve(workspacePath);
|
|
5236
|
+
const absolutePath = resolve(rootPath, relativePath.path);
|
|
5237
|
+
if (!isPathInside(rootPath, absolutePath)) return {
|
|
5238
|
+
code: "invalid_workspace_file_path",
|
|
5239
|
+
error: "Workspace file path must stay inside the workspace.",
|
|
5240
|
+
status: 400,
|
|
5241
|
+
success: false
|
|
5242
|
+
};
|
|
5243
|
+
const fileStat = await stat(absolutePath).catch(() => null);
|
|
5244
|
+
if (!fileStat?.isFile()) return {
|
|
5245
|
+
code: "workspace_file_not_found",
|
|
5246
|
+
error: "Workspace file was not found.",
|
|
5247
|
+
status: 404,
|
|
5248
|
+
success: false
|
|
5249
|
+
};
|
|
5250
|
+
const bytesToRead = Math.min(fileStat.size, WORKSPACE_FILE_PREVIEW_MAX_BYTES);
|
|
5251
|
+
const buffer = Buffer.alloc(bytesToRead);
|
|
5252
|
+
if (bytesToRead > 0) {
|
|
5253
|
+
const handle = await open(absolutePath, "r");
|
|
5254
|
+
try {
|
|
5255
|
+
await handle.read(buffer, 0, bytesToRead, 0);
|
|
5256
|
+
} finally {
|
|
5257
|
+
await handle.close();
|
|
5258
|
+
}
|
|
5259
|
+
}
|
|
5260
|
+
const binary = buffer.includes(0);
|
|
5261
|
+
return {
|
|
5262
|
+
content: {
|
|
5263
|
+
binary,
|
|
5264
|
+
content: binary ? "" : buffer.toString("utf8"),
|
|
5265
|
+
directory: dirname(relativePath.path) === "." ? "" : dirname(relativePath.path),
|
|
5266
|
+
language: languageFromWorkspaceFile(relativePath.path),
|
|
5267
|
+
name: basename(relativePath.path),
|
|
5268
|
+
path: relativePath.path,
|
|
5269
|
+
size: fileStat.size,
|
|
5270
|
+
truncated: fileStat.size > WORKSPACE_FILE_PREVIEW_MAX_BYTES
|
|
5271
|
+
},
|
|
5272
|
+
success: true
|
|
5273
|
+
};
|
|
5274
|
+
}
|
|
5275
|
+
async function updateWorkspaceFileContent(workspacePath, input) {
|
|
5276
|
+
const relativePath = normalizeWorkspaceRelativePath(input.path);
|
|
5277
|
+
if (!relativePath.success) return {
|
|
5278
|
+
code: "invalid_workspace_file_path",
|
|
5279
|
+
error: relativePath.error,
|
|
5280
|
+
status: 400,
|
|
5281
|
+
success: false
|
|
5282
|
+
};
|
|
5283
|
+
const rootPath = resolve(workspacePath);
|
|
5284
|
+
const absolutePath = resolve(rootPath, relativePath.path);
|
|
5285
|
+
if (!isPathInside(rootPath, absolutePath)) return {
|
|
5286
|
+
code: "invalid_workspace_file_path",
|
|
5287
|
+
error: "Workspace file path must stay inside the workspace.",
|
|
5288
|
+
status: 400,
|
|
5289
|
+
success: false
|
|
5290
|
+
};
|
|
5291
|
+
if (!(await stat(absolutePath).catch(() => null))?.isFile()) return {
|
|
5292
|
+
code: "workspace_file_not_found",
|
|
5293
|
+
error: "Workspace file was not found.",
|
|
5294
|
+
status: 404,
|
|
5295
|
+
success: false
|
|
5296
|
+
};
|
|
5297
|
+
await writeFile(absolutePath, input.content, "utf8");
|
|
5298
|
+
return readWorkspaceFileContent(workspacePath, relativePath.path);
|
|
5299
|
+
}
|
|
5300
|
+
function normalizeWorkspaceRelativePath(requestedPath) {
|
|
5301
|
+
const normalized = requestedPath.trim().replaceAll("\\", "/").replace(/^\.\/+/, "");
|
|
5302
|
+
if (!normalized) return {
|
|
5303
|
+
error: "Workspace file path is required.",
|
|
5304
|
+
success: false
|
|
5305
|
+
};
|
|
5306
|
+
if (isAbsolute(normalized)) return {
|
|
5307
|
+
error: "Workspace file path must be relative.",
|
|
5308
|
+
success: false
|
|
5309
|
+
};
|
|
5310
|
+
const segments = normalized.split("/").filter(Boolean);
|
|
5311
|
+
if (segments.some((segment) => segment === "." || segment === ".." || segment === ".git")) return {
|
|
5312
|
+
error: "Workspace file path contains an unsupported segment.",
|
|
5313
|
+
success: false
|
|
5314
|
+
};
|
|
5315
|
+
return {
|
|
5316
|
+
path: segments.join("/"),
|
|
5317
|
+
success: true
|
|
5318
|
+
};
|
|
5319
|
+
}
|
|
5320
|
+
function normalizeWorkspaceDirectoryPath(requestedPath) {
|
|
5321
|
+
const normalized = requestedPath.trim().replaceAll("\\", "/").replace(/^\.\/+/, "").replace(/\/+$/, "");
|
|
5322
|
+
if (!normalized) return {
|
|
5323
|
+
path: "",
|
|
5324
|
+
success: true
|
|
5325
|
+
};
|
|
5326
|
+
if (isAbsolute(normalized)) return {
|
|
5327
|
+
error: "Workspace directory path must be relative.",
|
|
5328
|
+
success: false
|
|
5329
|
+
};
|
|
5330
|
+
const segments = normalized.split("/").filter(Boolean);
|
|
5331
|
+
if (segments.some((segment) => segment === "." || segment === ".." || segment === ".git")) return {
|
|
5332
|
+
error: "Workspace directory path contains an unsupported segment.",
|
|
5333
|
+
success: false
|
|
5334
|
+
};
|
|
5335
|
+
return {
|
|
5336
|
+
path: segments.join("/"),
|
|
5337
|
+
success: true
|
|
5338
|
+
};
|
|
5339
|
+
}
|
|
5340
|
+
function parentWorkspaceDirectory(directory) {
|
|
5341
|
+
if (!directory) return null;
|
|
5342
|
+
const parent = dirname(directory);
|
|
5343
|
+
return parent === "." ? "" : parent;
|
|
5344
|
+
}
|
|
5345
|
+
function isPathInside(rootPath, targetPath) {
|
|
5346
|
+
const pathFromRoot = relative(rootPath, targetPath);
|
|
5347
|
+
return Boolean(pathFromRoot) && !pathFromRoot.startsWith("..") && !isAbsolute(pathFromRoot);
|
|
5348
|
+
}
|
|
5349
|
+
function languageFromWorkspaceFile(path) {
|
|
5350
|
+
const name = basename(path).toLowerCase();
|
|
5351
|
+
const extension = extname(name).replace(/^\./, "");
|
|
5352
|
+
if (name === "dockerfile") return "dockerfile";
|
|
5353
|
+
if (name === "makefile") return "make";
|
|
5354
|
+
if (name === "package.json") return "json";
|
|
5355
|
+
return {
|
|
5356
|
+
cjs: "javascript",
|
|
5357
|
+
css: "css",
|
|
5358
|
+
diff: "diff",
|
|
5359
|
+
go: "go",
|
|
5360
|
+
htm: "html",
|
|
5361
|
+
html: "html",
|
|
5362
|
+
java: "java",
|
|
5363
|
+
js: "javascript",
|
|
5364
|
+
json: "json",
|
|
5365
|
+
jsx: "jsx",
|
|
5366
|
+
kt: "kotlin",
|
|
5367
|
+
lock: "yaml",
|
|
5368
|
+
log: "text",
|
|
5369
|
+
md: "markdown",
|
|
5370
|
+
mdx: "markdown",
|
|
5371
|
+
mjs: "javascript",
|
|
5372
|
+
plist: "xml",
|
|
5373
|
+
py: "python",
|
|
5374
|
+
rb: "ruby",
|
|
5375
|
+
rs: "rust",
|
|
5376
|
+
sh: "bash",
|
|
5377
|
+
swift: "swift",
|
|
5378
|
+
ts: "typescript",
|
|
5379
|
+
tsx: "tsx",
|
|
5380
|
+
txt: "text",
|
|
5381
|
+
xml: "xml",
|
|
5382
|
+
yaml: "yaml",
|
|
5383
|
+
yml: "yaml"
|
|
5384
|
+
}[extension] ?? extension;
|
|
4923
5385
|
}
|
|
4924
5386
|
async function workspaceFilePaths(workspacePath) {
|
|
5387
|
+
const isIgnored = await workspaceIgnoreMatcher(workspacePath);
|
|
4925
5388
|
try {
|
|
4926
5389
|
return (await git(workspacePath, [
|
|
4927
5390
|
"ls-files",
|
|
4928
5391
|
"--cached",
|
|
4929
5392
|
"--others",
|
|
4930
5393
|
"--exclude-standard"
|
|
4931
|
-
])).split("\n").filter(Boolean);
|
|
5394
|
+
])).split("\n").filter(Boolean).filter((path) => !isIgnored(path));
|
|
4932
5395
|
} catch {
|
|
4933
|
-
return recursiveWorkspaceFilePaths(workspacePath);
|
|
5396
|
+
return recursiveWorkspaceFilePaths(workspacePath, isIgnored);
|
|
4934
5397
|
}
|
|
4935
5398
|
}
|
|
4936
|
-
async function recursiveWorkspaceFilePaths(rootPath) {
|
|
5399
|
+
async function recursiveWorkspaceFilePaths(rootPath, isIgnored) {
|
|
4937
5400
|
const ignoredDirectories = new Set([
|
|
4938
5401
|
".git",
|
|
4939
5402
|
".expo",
|
|
@@ -4947,17 +5410,45 @@ async function recursiveWorkspaceFilePaths(rootPath) {
|
|
|
4947
5410
|
for (const entry of entries) {
|
|
4948
5411
|
if (entry.name.startsWith(".") && entry.name !== ".github") continue;
|
|
4949
5412
|
const absolutePath = resolve(directory, entry.name);
|
|
5413
|
+
const relativePath = relative(rootPath, absolutePath).split("\\").join("/");
|
|
5414
|
+
if (isIgnored(relativePath)) continue;
|
|
4950
5415
|
if (entry.isDirectory()) {
|
|
4951
5416
|
if (ignoredDirectories.has(entry.name)) continue;
|
|
4952
5417
|
await visit(absolutePath);
|
|
4953
5418
|
continue;
|
|
4954
5419
|
}
|
|
4955
|
-
if (entry.isFile()) results.push(
|
|
5420
|
+
if (entry.isFile()) results.push(relativePath);
|
|
4956
5421
|
}
|
|
4957
5422
|
}
|
|
4958
5423
|
await visit(rootPath);
|
|
4959
5424
|
return results;
|
|
4960
5425
|
}
|
|
5426
|
+
async function workspaceIgnoreMatcher(workspacePath) {
|
|
5427
|
+
const matchers = (await readFile(join(workspacePath, ".gitignore"), "utf8").catch(() => "")).split(/\r?\n/).map((line) => gitignorePatternMatcher(line)).filter((matcher) => Boolean(matcher));
|
|
5428
|
+
return (path) => matchers.some((matcher) => matcher(path.split("\\").join("/")));
|
|
5429
|
+
}
|
|
5430
|
+
function gitignorePatternMatcher(line) {
|
|
5431
|
+
const trimmed = line.trim();
|
|
5432
|
+
if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith("!")) return;
|
|
5433
|
+
const directoryOnly = trimmed.endsWith("/");
|
|
5434
|
+
const anchored = trimmed.startsWith("/");
|
|
5435
|
+
const pattern = trimmed.replace(/^\/+/, "").replace(/\/+$/, "");
|
|
5436
|
+
if (!pattern) return;
|
|
5437
|
+
const matcher = gitignoreGlobMatcher(pattern);
|
|
5438
|
+
const hasSlash = pattern.includes("/");
|
|
5439
|
+
return (path) => {
|
|
5440
|
+
const normalizedPath = path.replace(/^\/+/, "");
|
|
5441
|
+
return (anchored || hasSlash ? [normalizedPath] : normalizedPath.split("/")).some((candidate) => {
|
|
5442
|
+
if (directoryOnly) return matcher(candidate) || normalizedPath.startsWith(`${candidate}/`);
|
|
5443
|
+
return matcher(candidate);
|
|
5444
|
+
});
|
|
5445
|
+
};
|
|
5446
|
+
}
|
|
5447
|
+
function gitignoreGlobMatcher(pattern) {
|
|
5448
|
+
const expression = pattern.split("**").map((part) => part.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, "[^/]*")).join(".*").replace(/\?/g, "[^/]");
|
|
5449
|
+
const regex = new RegExp(`^${expression}(?:/.*)?$`);
|
|
5450
|
+
return (path) => regex.test(path);
|
|
5451
|
+
}
|
|
4961
5452
|
function workspaceFileScore(path, query) {
|
|
4962
5453
|
if (!query) return path.split("/").length;
|
|
4963
5454
|
const lowerPath = path.toLowerCase();
|