codex-relay 1.0.7 → 1.1.1
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/README.md +10 -0
- package/dist/src.js +65 -23
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Codex Relay runs a local bridge server for the Codex Relay mobile app. Keep Codex on your computer, then use your phone to pair with that local session, send prompts, watch streamed output, and respond to approval requests.
|
|
4
4
|
|
|
5
|
+
Codex Relay is an independent project. It is not affiliated with, endorsed by, or sponsored by OpenAI or the OpenAI Codex team.
|
|
6
|
+
|
|
5
7
|
## Requirements
|
|
6
8
|
|
|
7
9
|
- Node.js 22.14 or newer
|
|
@@ -145,3 +147,11 @@ kill -TERM <pid>
|
|
|
145
147
|
```
|
|
146
148
|
|
|
147
149
|
If the mobile app cannot connect, confirm that the phone can reach the printed `Mobile:` URL and that the chosen port is not blocked by a firewall.
|
|
150
|
+
|
|
151
|
+
Connection checklist:
|
|
152
|
+
|
|
153
|
+
- Are the phone and computer on the same Wi-Fi or LAN?
|
|
154
|
+
- If keeping the same network is difficult, are both devices connected through Tailscale or another reachable private network?
|
|
155
|
+
- Can the phone open the exact `Mobile:` URL printed by the relay?
|
|
156
|
+
- Does the computer firewall allow inbound traffic on the relay port, usually `8787`?
|
|
157
|
+
- If the printed URL is not reachable, did you set `CODEX_RELAY_PUBLIC_URL` to a reachable LAN, Tailscale, or tunnel URL?
|
package/dist/src.js
CHANGED
|
@@ -2229,16 +2229,17 @@ async function runPromptStreamed(input) {
|
|
|
2229
2229
|
if (kind === "error") throw new Error(text ?? "Codex run failed.");
|
|
2230
2230
|
if (!text) continue;
|
|
2231
2231
|
if (kind === "assistant") {
|
|
2232
|
-
|
|
2232
|
+
const assistantPatch = appendMessageDelta(input.messagesByThreadId, activeThreadId, assistantMessage.id, text);
|
|
2233
|
+
assistantMessage = assistantPatch.message;
|
|
2233
2234
|
updateThread(input.threads, input.messagesByThreadId, activeThreadId, {
|
|
2234
2235
|
state: "running",
|
|
2235
2236
|
lastResult: assistantMessage.content
|
|
2236
2237
|
});
|
|
2237
|
-
sendSse(input.controller, input.encoder, input.secureSession, {
|
|
2238
|
+
if (assistantPatch.delta) sendSse(input.controller, input.encoder, input.secureSession, {
|
|
2238
2239
|
type: "thread.message.delta",
|
|
2239
2240
|
threadId: activeThreadId,
|
|
2240
2241
|
messageId: assistantMessage.id,
|
|
2241
|
-
delta:
|
|
2242
|
+
delta: assistantPatch.delta
|
|
2242
2243
|
});
|
|
2243
2244
|
} else {
|
|
2244
2245
|
const structured = structuredStreamMessage(kind, event, text);
|
|
@@ -2494,16 +2495,17 @@ async function streamRunningAppServerThread(input) {
|
|
|
2494
2495
|
turnId: firstString(params, ["turnId"]) ?? activeTurnId
|
|
2495
2496
|
});
|
|
2496
2497
|
assistantMessageId = itemId;
|
|
2497
|
-
const
|
|
2498
|
+
const patch = appendMessageDelta(input.messagesByThreadId, input.threadId, itemId, delta);
|
|
2499
|
+
const message = patch.message;
|
|
2498
2500
|
updateThread(input.threads, input.messagesByThreadId, input.threadId, {
|
|
2499
2501
|
state: "running",
|
|
2500
2502
|
lastResult: message.content
|
|
2501
2503
|
});
|
|
2502
|
-
sendSse(input.controller, input.encoder, input.secureSession, {
|
|
2504
|
+
if (patch.delta) sendSse(input.controller, input.encoder, input.secureSession, {
|
|
2503
2505
|
type: "thread.message.delta",
|
|
2504
2506
|
threadId: input.threadId,
|
|
2505
2507
|
messageId: itemId,
|
|
2506
|
-
delta
|
|
2508
|
+
delta: patch.delta
|
|
2507
2509
|
});
|
|
2508
2510
|
return;
|
|
2509
2511
|
}
|
|
@@ -2705,7 +2707,7 @@ async function runAppServerPromptStreamed(input) {
|
|
|
2705
2707
|
case "item/completed": {
|
|
2706
2708
|
const item = params?.item;
|
|
2707
2709
|
if (!item || typeof item !== "object") return;
|
|
2708
|
-
const canonicalUserMessage = replaceDuplicateInitialUserMessage(input.messagesByThreadId, activeThreadId, firstString(params, ["turnId"]) ?? activeTurnId, item, userMessage.id,
|
|
2710
|
+
const canonicalUserMessage = replaceDuplicateInitialUserMessage(input.messagesByThreadId, activeThreadId, firstString(params, ["turnId"]) ?? activeTurnId, item, userMessage.id, displayPrompt);
|
|
2709
2711
|
if (canonicalUserMessage) {
|
|
2710
2712
|
userMessage = canonicalUserMessage;
|
|
2711
2713
|
return;
|
|
@@ -2737,17 +2739,18 @@ async function runAppServerPromptStreamed(input) {
|
|
|
2737
2739
|
turnId: firstString(params, ["turnId"]) ?? activeTurnId
|
|
2738
2740
|
});
|
|
2739
2741
|
assistantMessageId = itemId;
|
|
2740
|
-
const
|
|
2742
|
+
const patch = appendMessageDelta(input.messagesByThreadId, activeThreadId, itemId, delta);
|
|
2743
|
+
const message = patch.message;
|
|
2741
2744
|
producedTurnOutput = true;
|
|
2742
2745
|
updateThread(input.threads, input.messagesByThreadId, activeThreadId, {
|
|
2743
2746
|
state: "running",
|
|
2744
2747
|
lastResult: message.content
|
|
2745
2748
|
});
|
|
2746
|
-
sendSse(input.controller, input.encoder, input.secureSession, {
|
|
2749
|
+
if (patch.delta) sendSse(input.controller, input.encoder, input.secureSession, {
|
|
2747
2750
|
type: "thread.message.delta",
|
|
2748
2751
|
threadId: activeThreadId,
|
|
2749
2752
|
messageId: itemId,
|
|
2750
|
-
delta
|
|
2753
|
+
delta: patch.delta
|
|
2751
2754
|
});
|
|
2752
2755
|
return;
|
|
2753
2756
|
}
|
|
@@ -3269,8 +3272,9 @@ function isDuplicateInitialUserMessage(messagesByThreadId, threadId, item, local
|
|
|
3269
3272
|
if (item.type !== "userMessage" || !("content" in item) || !Array.isArray(item.content)) return false;
|
|
3270
3273
|
const localMessage = messagesByThreadId.get(threadId)?.find((message) => message.id === localMessageId);
|
|
3271
3274
|
const skills = appServerUserMessageSkills(item);
|
|
3272
|
-
const
|
|
3273
|
-
|
|
3275
|
+
const normalizeContent = (content) => stripPromptSkillMentions(normalizeImageMessageContent(content), skills);
|
|
3276
|
+
const normalizedPrompt = normalizeContent(prompt);
|
|
3277
|
+
return normalizeContent(localMessage?.content ?? "") === normalizedPrompt && normalizeContent(appServerUserMessageText(item)) === normalizedPrompt;
|
|
3274
3278
|
}
|
|
3275
3279
|
function messageWithReplacementDetail(message, replacesMessageId) {
|
|
3276
3280
|
return ChatMessageSchema.parse({
|
|
@@ -3526,10 +3530,18 @@ function isAppServerSkillInput(input) {
|
|
|
3526
3530
|
}
|
|
3527
3531
|
function appendMessageDelta(messagesByThreadId, threadId, messageId, delta) {
|
|
3528
3532
|
const existing = messagesByThreadId.get(threadId)?.find((message) => message.id === messageId);
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
+
const normalizedDelta = normalizeStreamDelta(existing?.content ?? "", delta);
|
|
3534
|
+
return {
|
|
3535
|
+
delta: normalizedDelta,
|
|
3536
|
+
message: updateMessage(messagesByThreadId, threadId, messageId, {
|
|
3537
|
+
content: `${existing?.content ?? ""}${normalizedDelta}`,
|
|
3538
|
+
state: "streaming"
|
|
3539
|
+
})
|
|
3540
|
+
};
|
|
3541
|
+
}
|
|
3542
|
+
function normalizeStreamDelta(existingContent, incomingDelta) {
|
|
3543
|
+
if (!existingContent || !incomingDelta.startsWith(existingContent)) return incomingDelta;
|
|
3544
|
+
return incomingDelta.slice(existingContent.length);
|
|
3533
3545
|
}
|
|
3534
3546
|
function markApprovalMessageResolved(messagesByThreadId, threadId, messageId, decision) {
|
|
3535
3547
|
const message = (messagesByThreadId.get(threadId) ?? []).find((candidate) => candidate.id === messageId);
|
|
@@ -4092,6 +4104,7 @@ function readRolloutThreadMessages(threadId, workspacePath = defaultWorkspacePat
|
|
|
4092
4104
|
};
|
|
4093
4105
|
const collected = [];
|
|
4094
4106
|
const applyPatchInputs = /* @__PURE__ */ new Map();
|
|
4107
|
+
const handledApplyPatchCallIds = /* @__PURE__ */ new Set();
|
|
4095
4108
|
const pendingApplyPatchChanges = [];
|
|
4096
4109
|
const lines = readFileSync(rolloutPath, "utf8").split("\n");
|
|
4097
4110
|
for (let index = 0; index < lines.length; index += 1) {
|
|
@@ -4102,7 +4115,7 @@ function readRolloutThreadMessages(threadId, workspacePath = defaultWorkspacePat
|
|
|
4102
4115
|
try {
|
|
4103
4116
|
const record = JSON.parse(line);
|
|
4104
4117
|
rememberRolloutApplyPatchInput(record, applyPatchInputs);
|
|
4105
|
-
collectRolloutApplyPatchOutput(record, workspacePath, applyPatchInputs, pendingApplyPatchChanges);
|
|
4118
|
+
collectRolloutApplyPatchOutput(record, workspacePath, applyPatchInputs, handledApplyPatchCallIds, pendingApplyPatchChanges);
|
|
4106
4119
|
if (isRolloutTaskComplete(record) && pendingApplyPatchChanges.length > 0) {
|
|
4107
4120
|
collected.push(rolloutApplyPatchSummaryMessage(threadId, record, `rollout:${lineNumber}:apply_patch`, pendingApplyPatchChanges));
|
|
4108
4121
|
pendingApplyPatchChanges.length = 0;
|
|
@@ -4111,6 +4124,11 @@ function readRolloutThreadMessages(threadId, workspacePath = defaultWorkspacePat
|
|
|
4111
4124
|
const message = rolloutRecordMessage(threadId, record, `rollout:${lineNumber}`, workspacePath);
|
|
4112
4125
|
if (!message) continue;
|
|
4113
4126
|
collected.push(message);
|
|
4127
|
+
const patchApplyEndCallId = rolloutPatchApplyEndCallId(record);
|
|
4128
|
+
if (patchApplyEndCallId) {
|
|
4129
|
+
handledApplyPatchCallIds.add(patchApplyEndCallId);
|
|
4130
|
+
pendingApplyPatchChanges.splice(0, pendingApplyPatchChanges.length, ...pendingApplyPatchChanges.filter((change) => change.callId !== patchApplyEndCallId));
|
|
4131
|
+
}
|
|
4114
4132
|
} catch {}
|
|
4115
4133
|
}
|
|
4116
4134
|
return {
|
|
@@ -4172,7 +4190,7 @@ function rolloutRecordMessage(threadId, record, messageKey, workspacePath = defa
|
|
|
4172
4190
|
if (record.type === "event_msg" && payload.type === "patch_apply_end") {
|
|
4173
4191
|
const changes = rolloutPatchApplyChanges(payload.changes, workspacePath);
|
|
4174
4192
|
if (changes.length === 0) return;
|
|
4175
|
-
const patchPreview = largeTextPreview(changes
|
|
4193
|
+
const patchPreview = largeTextPreview(rolloutPatchPreview(changes));
|
|
4176
4194
|
return ChatMessageSchema.parse({
|
|
4177
4195
|
id: `${messageKey}:patch:${firstString(payload, ["call_id"]) ?? ""}`,
|
|
4178
4196
|
threadId,
|
|
@@ -4182,7 +4200,7 @@ function rolloutRecordMessage(threadId, record, messageKey, workspacePath = defa
|
|
|
4182
4200
|
createdAt: timestamp,
|
|
4183
4201
|
state: "completed",
|
|
4184
4202
|
details: {
|
|
4185
|
-
changes: changes.map(
|
|
4203
|
+
changes: changes.map(publicRolloutPatchChange),
|
|
4186
4204
|
patch: patchPreview?.text,
|
|
4187
4205
|
patchOriginalLength: patchPreview?.originalLength,
|
|
4188
4206
|
patchTruncated: patchPreview?.truncated
|
|
@@ -4250,15 +4268,16 @@ function rememberRolloutApplyPatchInput(record, applyPatchInputs) {
|
|
|
4250
4268
|
const input = firstString(payload, ["input"]);
|
|
4251
4269
|
if (callId && input) applyPatchInputs.set(callId, input);
|
|
4252
4270
|
}
|
|
4253
|
-
function collectRolloutApplyPatchOutput(record, workspacePath, applyPatchInputs, pendingApplyPatchChanges) {
|
|
4271
|
+
function collectRolloutApplyPatchOutput(record, workspacePath, applyPatchInputs, handledApplyPatchCallIds, pendingApplyPatchChanges) {
|
|
4254
4272
|
const payload = record.payload;
|
|
4255
|
-
|
|
4273
|
+
const callId = firstString(payload, ["call_id"]);
|
|
4274
|
+
if (record.type !== "response_item" || payload?.type !== "custom_tool_call_output" || !callId || handledApplyPatchCallIds.has(callId)) return;
|
|
4256
4275
|
const changes = rolloutApplyPatchOutputChanges(rolloutCustomToolOutputText(payload), workspacePath);
|
|
4257
4276
|
if (changes.length === 0) return;
|
|
4258
|
-
const callId = firstString(payload, ["call_id"]);
|
|
4259
4277
|
const patch = callId ? applyPatchInputs.get(callId) : void 0;
|
|
4260
4278
|
for (const change of changes) pendingApplyPatchChanges.push({
|
|
4261
4279
|
...change,
|
|
4280
|
+
callId,
|
|
4262
4281
|
patch
|
|
4263
4282
|
});
|
|
4264
4283
|
}
|
|
@@ -4274,13 +4293,36 @@ function rolloutApplyPatchSummaryMessage(threadId, record, messageKey, pendingAp
|
|
|
4274
4293
|
createdAt: timestamp,
|
|
4275
4294
|
state: "completed",
|
|
4276
4295
|
details: {
|
|
4277
|
-
changes: pendingApplyPatchChanges.map(
|
|
4296
|
+
changes: pendingApplyPatchChanges.map(publicRolloutPatchChange),
|
|
4278
4297
|
patch: patchPreview?.text,
|
|
4279
4298
|
patchOriginalLength: patchPreview?.originalLength,
|
|
4280
4299
|
patchTruncated: patchPreview?.truncated
|
|
4281
4300
|
}
|
|
4282
4301
|
});
|
|
4283
4302
|
}
|
|
4303
|
+
function rolloutPatchApplyEndCallId(record) {
|
|
4304
|
+
if (record.type !== "event_msg" || record.payload?.type !== "patch_apply_end") return;
|
|
4305
|
+
return firstString(record.payload, ["call_id"]);
|
|
4306
|
+
}
|
|
4307
|
+
function publicRolloutPatchChange(change) {
|
|
4308
|
+
const { callId: _callId, patch: _patch, ...publicChange } = change;
|
|
4309
|
+
return publicChange;
|
|
4310
|
+
}
|
|
4311
|
+
function rolloutPatchPreview(changes) {
|
|
4312
|
+
return changes.flatMap((change) => {
|
|
4313
|
+
if (!change.patch) return [];
|
|
4314
|
+
if (hasPatchFileHeader(change.patch)) return [change.patch];
|
|
4315
|
+
return [`*** ${patchHeaderChangeKind(change.kind)} File: ${change.path}\n${change.patch}`];
|
|
4316
|
+
}).join("\n");
|
|
4317
|
+
}
|
|
4318
|
+
function hasPatchFileHeader(patch) {
|
|
4319
|
+
return /^(?:diff --git |\*\*\* (?:Add|Update|Delete) File: )/m.test(patch);
|
|
4320
|
+
}
|
|
4321
|
+
function patchHeaderChangeKind(kind) {
|
|
4322
|
+
if (kind === "added") return "Add";
|
|
4323
|
+
if (kind === "deleted") return "Delete";
|
|
4324
|
+
return "Update";
|
|
4325
|
+
}
|
|
4284
4326
|
function rolloutCustomToolOutputText(payload) {
|
|
4285
4327
|
const rawOutput = firstString(payload, ["output"]);
|
|
4286
4328
|
if (!rawOutput) return;
|