codex-relay 1.0.2 → 1.0.3
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/src.js +86 -7
- package/package.json +1 -1
package/dist/src.js
CHANGED
|
@@ -1217,14 +1217,24 @@ function createApp(options = {}) {
|
|
|
1217
1217
|
return c.json(apiError("not_found", "Image attachment not found."), 404);
|
|
1218
1218
|
}
|
|
1219
1219
|
const fileStat = statSync(filePath);
|
|
1220
|
+
if (!fileStat.isFile() || fileStat.size === 0) {
|
|
1221
|
+
relayDebugLog("image_attachment.rejected", {
|
|
1222
|
+
attachmentId,
|
|
1223
|
+
reason: fileStat.isFile() ? "empty_file" : "not_file",
|
|
1224
|
+
userAgent: c.req.header("user-agent")
|
|
1225
|
+
});
|
|
1226
|
+
return c.json(apiError("not_found", "Image attachment not found."), 404);
|
|
1227
|
+
}
|
|
1228
|
+
const imageBytes = await readFile(filePath);
|
|
1220
1229
|
relayDebugLog("image_attachment.served", {
|
|
1221
1230
|
attachmentId,
|
|
1222
1231
|
mimeType: imageMimeType(filePath),
|
|
1223
|
-
size:
|
|
1232
|
+
size: imageBytes.length,
|
|
1224
1233
|
userAgent: c.req.header("user-agent")
|
|
1225
1234
|
});
|
|
1226
|
-
return new Response(
|
|
1235
|
+
return new Response(imageBytes, { headers: {
|
|
1227
1236
|
"cache-control": "private, max-age=31536000, immutable",
|
|
1237
|
+
"content-length": String(imageBytes.length),
|
|
1228
1238
|
"content-type": imageMimeType(filePath)
|
|
1229
1239
|
} });
|
|
1230
1240
|
});
|
|
@@ -2517,7 +2527,11 @@ async function runAppServerPromptStreamed(input) {
|
|
|
2517
2527
|
case "item/completed": {
|
|
2518
2528
|
const item = params?.item;
|
|
2519
2529
|
if (!item || typeof item !== "object") return;
|
|
2520
|
-
|
|
2530
|
+
const canonicalUserMessage = replaceDuplicateInitialUserMessage(input.messagesByThreadId, activeThreadId, firstString(params, ["turnId"]) ?? activeTurnId, item, userMessage.id, prompt);
|
|
2531
|
+
if (canonicalUserMessage) {
|
|
2532
|
+
userMessage = canonicalUserMessage;
|
|
2533
|
+
return;
|
|
2534
|
+
}
|
|
2521
2535
|
const turnId = firstString(params, ["turnId"]) ?? activeTurnId;
|
|
2522
2536
|
const message = upsertAppServerItemMessage(input.messagesByThreadId, activeThreadId, turnId, item);
|
|
2523
2537
|
if (!message) return;
|
|
@@ -2750,7 +2764,11 @@ async function runAppServerPromptStreamed(input) {
|
|
|
2750
2764
|
debugStream("start turn complete", activeThreadId, activeTurnId);
|
|
2751
2765
|
let streamedReturnedItem = false;
|
|
2752
2766
|
for (const item of turn.items) {
|
|
2753
|
-
|
|
2767
|
+
const canonicalUserMessage = replaceDuplicateInitialUserMessage(input.messagesByThreadId, activeThreadId, activeTurnId, item, userMessage.id, displayPrompt);
|
|
2768
|
+
if (canonicalUserMessage) {
|
|
2769
|
+
userMessage = canonicalUserMessage;
|
|
2770
|
+
continue;
|
|
2771
|
+
}
|
|
2754
2772
|
const message = upsertAppServerItemMessage(input.messagesByThreadId, activeThreadId, activeTurnId, item);
|
|
2755
2773
|
if (!message) continue;
|
|
2756
2774
|
streamedReturnedItem = true;
|
|
@@ -3062,6 +3080,12 @@ function upsertAppServerItemMessage(messagesByThreadId, threadId, turnId, item)
|
|
|
3062
3080
|
turnId: message.turnId
|
|
3063
3081
|
});
|
|
3064
3082
|
}
|
|
3083
|
+
function replaceDuplicateInitialUserMessage(messagesByThreadId, threadId, turnId, item, localMessageId, prompt) {
|
|
3084
|
+
if (!isDuplicateInitialUserMessage(messagesByThreadId, threadId, item, localMessageId, prompt)) return;
|
|
3085
|
+
const message = mapAppServerItem(threadId, appServerTurnShell(turnId), item);
|
|
3086
|
+
if (!message) return;
|
|
3087
|
+
return replaceMessage(messagesByThreadId, threadId, localMessageId, messageWithReplacementDetail(message, localMessageId));
|
|
3088
|
+
}
|
|
3065
3089
|
function isDuplicateInitialUserMessage(messagesByThreadId, threadId, item, localMessageId, prompt) {
|
|
3066
3090
|
if (item.type !== "userMessage" || !("content" in item) || !Array.isArray(item.content)) return false;
|
|
3067
3091
|
const localMessage = messagesByThreadId.get(threadId)?.find((message) => message.id === localMessageId);
|
|
@@ -3069,6 +3093,15 @@ function isDuplicateInitialUserMessage(messagesByThreadId, threadId, item, local
|
|
|
3069
3093
|
const normalizedPrompt = stripPromptSkillMentions(prompt, skills);
|
|
3070
3094
|
return stripPromptSkillMentions(localMessage?.content ?? "", skills) === normalizedPrompt && stripPromptSkillMentions(appServerUserMessageText(item), skills) === normalizedPrompt;
|
|
3071
3095
|
}
|
|
3096
|
+
function messageWithReplacementDetail(message, replacesMessageId) {
|
|
3097
|
+
return ChatMessageSchema.parse({
|
|
3098
|
+
...message,
|
|
3099
|
+
details: {
|
|
3100
|
+
...message.details,
|
|
3101
|
+
replacesMessageId
|
|
3102
|
+
}
|
|
3103
|
+
});
|
|
3104
|
+
}
|
|
3072
3105
|
function appServerUserMessageText(item) {
|
|
3073
3106
|
const skills = appServerUserMessageSkills(item);
|
|
3074
3107
|
return promptMarkdownWithSkills(promptWithAppServerImageReferences(item.content.map((content) => {
|
|
@@ -3102,6 +3135,7 @@ function appServerUserMessageDetails(item) {
|
|
|
3102
3135
|
async function saveUploadedImageAttachment(file) {
|
|
3103
3136
|
if (!file.type.startsWith("image/")) throw new Error(`Unsupported attachment type: ${file.type || "unknown"}`);
|
|
3104
3137
|
if (file.size > IMAGE_ATTACHMENT_MAX_BYTES) throw new Error(`Image ${file.name || "attachment"} is too large.`);
|
|
3138
|
+
if (file.size === 0) throw new Error(`Image ${file.name || "attachment"} is empty.`);
|
|
3105
3139
|
const attachmentId = `${Date.now()}-${randomUUID()}${imageExtension(file.name, file.type)}`;
|
|
3106
3140
|
const filePath = resolve(imageAttachmentDirectory, attachmentId);
|
|
3107
3141
|
await mkdir(imageAttachmentDirectory, { recursive: true });
|
|
@@ -3152,7 +3186,7 @@ function materializeLocalImageFile(path, name = basename(path)) {
|
|
|
3152
3186
|
const filePath = localImageFilePath(path);
|
|
3153
3187
|
if (!filePath || !existsSync(filePath)) return;
|
|
3154
3188
|
const fileStat = statSync(filePath);
|
|
3155
|
-
if (!fileStat.isFile() || fileStat.size > IMAGE_ATTACHMENT_MAX_BYTES) return;
|
|
3189
|
+
if (!fileStat.isFile() || fileStat.size === 0 || fileStat.size > IMAGE_ATTACHMENT_MAX_BYTES) return;
|
|
3156
3190
|
const buffer = readFileSync(filePath);
|
|
3157
3191
|
const mimeType = imageMimeType(filePath);
|
|
3158
3192
|
const attachmentId = `${createHash("sha256").update(buffer).digest("hex").slice(0, 24)}${imageExtension(name, mimeType)}`;
|
|
@@ -3268,6 +3302,17 @@ function updateMessage(messagesByThreadId, threadId, messageId, update) {
|
|
|
3268
3302
|
messages[index] = next;
|
|
3269
3303
|
return next;
|
|
3270
3304
|
}
|
|
3305
|
+
function replaceMessage(messagesByThreadId, threadId, messageId, replacement) {
|
|
3306
|
+
const messages = messagesByThreadId.get(threadId) ?? [];
|
|
3307
|
+
const index = messages.findIndex((message) => message.id === messageId);
|
|
3308
|
+
if (index === -1) throw new Error(`Unknown message: ${messageId}`);
|
|
3309
|
+
const next = ChatMessageSchema.parse({
|
|
3310
|
+
...replacement,
|
|
3311
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3312
|
+
});
|
|
3313
|
+
messages[index] = next;
|
|
3314
|
+
return next;
|
|
3315
|
+
}
|
|
3271
3316
|
function replaceLocalThreadId(threads, messagesByThreadId, liveThreads, currentThreadId, sdkThreadId) {
|
|
3272
3317
|
if (!sdkThreadId || sdkThreadId === currentThreadId) return currentThreadId;
|
|
3273
3318
|
const metadata = threads.get(currentThreadId);
|
|
@@ -3628,26 +3673,58 @@ function mergeThreadMessagePages(incomingMessages, cachedMessages) {
|
|
|
3628
3673
|
return dedupeThreadMessages(Array.from(byId.values()));
|
|
3629
3674
|
}
|
|
3630
3675
|
function dedupeThreadMessages(messages) {
|
|
3676
|
+
const byCrossSourceKey = /* @__PURE__ */ new Map();
|
|
3631
3677
|
const byImageKey = /* @__PURE__ */ new Map();
|
|
3632
3678
|
const deduped = [];
|
|
3633
3679
|
const sortedMessages = [...messages].sort((left, right) => left.createdAt.localeCompare(right.createdAt));
|
|
3634
3680
|
for (const message of sortedMessages) {
|
|
3681
|
+
const crossSourceKey = crossSourceMessageKey(message);
|
|
3682
|
+
const crossSourceIndex = crossSourceKey ? byCrossSourceKey.get(crossSourceKey) : void 0;
|
|
3683
|
+
if (crossSourceIndex !== void 0) {
|
|
3684
|
+
const existingMessage = deduped[crossSourceIndex];
|
|
3685
|
+
if (existingMessage && shouldPreferDuplicateThreadMessage(message, existingMessage)) deduped[crossSourceIndex] = message;
|
|
3686
|
+
continue;
|
|
3687
|
+
}
|
|
3688
|
+
const previous = deduped[deduped.length - 1];
|
|
3689
|
+
if (previous && isDuplicateCrossSourceMessage(previous, message)) {
|
|
3690
|
+
if (shouldPreferDuplicateThreadMessage(message, previous)) deduped[deduped.length - 1] = message;
|
|
3691
|
+
continue;
|
|
3692
|
+
}
|
|
3635
3693
|
const imageKey = userImageMessageKey(message);
|
|
3636
3694
|
if (!imageKey) {
|
|
3695
|
+
if (crossSourceKey) byCrossSourceKey.set(crossSourceKey, deduped.length);
|
|
3637
3696
|
deduped.push(message);
|
|
3638
3697
|
continue;
|
|
3639
3698
|
}
|
|
3640
3699
|
const existingIndex = byImageKey.get(imageKey);
|
|
3641
3700
|
if (existingIndex === void 0) {
|
|
3642
3701
|
byImageKey.set(imageKey, deduped.length);
|
|
3702
|
+
if (crossSourceKey) byCrossSourceKey.set(crossSourceKey, deduped.length);
|
|
3643
3703
|
deduped.push(message);
|
|
3644
3704
|
continue;
|
|
3645
3705
|
}
|
|
3646
3706
|
const existingMessage = deduped[existingIndex];
|
|
3647
|
-
if (existingMessage &&
|
|
3707
|
+
if (existingMessage && shouldPreferDuplicateThreadMessage(message, existingMessage)) deduped[existingIndex] = message;
|
|
3648
3708
|
}
|
|
3649
3709
|
return deduped;
|
|
3650
3710
|
}
|
|
3711
|
+
function crossSourceMessageKey(message) {
|
|
3712
|
+
if (!isSyntheticHistoryMessageId(message.id)) return;
|
|
3713
|
+
if (message.role !== "user" && message.role !== "assistant") return;
|
|
3714
|
+
return [
|
|
3715
|
+
message.threadId,
|
|
3716
|
+
message.createdAt.slice(0, 19),
|
|
3717
|
+
message.role,
|
|
3718
|
+
message.kind,
|
|
3719
|
+
message.content
|
|
3720
|
+
].join("\n");
|
|
3721
|
+
}
|
|
3722
|
+
function isDuplicateCrossSourceMessage(previous, next) {
|
|
3723
|
+
return previous.id !== next.id && (isSyntheticHistoryMessageId(previous.id) || isSyntheticHistoryMessageId(next.id)) && previous.threadId === next.threadId && previous.role === next.role && previous.kind === next.kind && previous.content === next.content;
|
|
3724
|
+
}
|
|
3725
|
+
function isSyntheticHistoryMessageId(id) {
|
|
3726
|
+
return id.startsWith("msg-") || id.startsWith("rollout:");
|
|
3727
|
+
}
|
|
3651
3728
|
function userImageMessageKey(message) {
|
|
3652
3729
|
if (message.role !== "user") return;
|
|
3653
3730
|
const imageUris = imageAttachmentUris(message);
|
|
@@ -3665,9 +3742,11 @@ function imageAttachmentUris(message) {
|
|
|
3665
3742
|
return ["url" in attachment ? attachment.url : void 0, "path" in attachment ? attachment.path : void 0].filter((value) => typeof value === "string" && value.length > 0);
|
|
3666
3743
|
});
|
|
3667
3744
|
}
|
|
3668
|
-
function
|
|
3745
|
+
function shouldPreferDuplicateThreadMessage(candidate, existing) {
|
|
3669
3746
|
if (existing.id.startsWith("rollout") && !candidate.id.startsWith("rollout")) return true;
|
|
3670
3747
|
if (!existing.id.startsWith("rollout") && candidate.id.startsWith("rollout")) return false;
|
|
3748
|
+
if (!existing.turnId && candidate.turnId) return true;
|
|
3749
|
+
if (existing.turnId && !candidate.turnId) return false;
|
|
3671
3750
|
return candidate.content.length < existing.content.length;
|
|
3672
3751
|
}
|
|
3673
3752
|
function rememberRolloutThreadMessages(threads, thread, messages, messageCountLowerBound = messages.length) {
|