remote-codex 0.11.35 → 0.11.37
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 +195 -18
- package/apps/supervisor-api/dist/index.js +183 -45
- package/apps/supervisor-web/dist/assets/index-P0J5du_C.css +1 -0
- package/apps/supervisor-web/dist/assets/index-pkf-_mec.js +21 -0
- package/apps/supervisor-web/dist/assets/{thread-ui-CK1xSLOW.js → thread-ui-xWvswa2v.js} +57 -24
- package/apps/supervisor-web/dist/index.html +3 -3
- package/package.json +1 -1
- package/packages/codex/src/runtimeAdapter.test.ts +100 -1
- package/packages/codex/src/runtimeAdapter.ts +95 -28
- package/packages/codex/src/types.ts +8 -2
- package/packages/db/src/repositories.ts +16 -0
- package/apps/supervisor-web/dist/assets/index-C14vD5Hp.js +0 -21
- package/apps/supervisor-web/dist/assets/index-DYIjFImB.css +0 -1
|
@@ -10,16 +10,16 @@
|
|
|
10
10
|
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
|
11
11
|
<link rel="manifest" href="/site.webmanifest" />
|
|
12
12
|
<title>Remote Codex</title>
|
|
13
|
-
<script type="module" crossorigin src="/assets/index-
|
|
13
|
+
<script type="module" crossorigin src="/assets/index-pkf-_mec.js"></script>
|
|
14
14
|
<link rel="modulepreload" crossorigin href="/assets/react-vendor-Dfg_6BLf.js">
|
|
15
15
|
<link rel="modulepreload" crossorigin href="/assets/ui-vendor-CuR8GHb0.js">
|
|
16
16
|
<link rel="modulepreload" crossorigin href="/assets/graph-vendor-DVQUpZ8C.js">
|
|
17
17
|
<link rel="modulepreload" crossorigin href="/assets/terminal-vendor-C5bTa-Ka.js">
|
|
18
18
|
<link rel="modulepreload" crossorigin href="/assets/markdown-vendor-RZk8L7-L.js">
|
|
19
|
-
<link rel="modulepreload" crossorigin href="/assets/thread-ui-
|
|
19
|
+
<link rel="modulepreload" crossorigin href="/assets/thread-ui-xWvswa2v.js">
|
|
20
20
|
<link rel="stylesheet" crossorigin href="/assets/graph-vendor-C5ap-Sga.css">
|
|
21
21
|
<link rel="stylesheet" crossorigin href="/assets/terminal-vendor-Beg8tuEN.css">
|
|
22
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
22
|
+
<link rel="stylesheet" crossorigin href="/assets/index-P0J5du_C.css">
|
|
23
23
|
</head>
|
|
24
24
|
<body class="bg-stone-950">
|
|
25
25
|
<div id="root"></div>
|
package/package.json
CHANGED
|
@@ -32,7 +32,7 @@ class FakeCodexManager extends EventEmitter {
|
|
|
32
32
|
return { account: { type: 'chatgpt' }, requiresOpenaiAuth: true };
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
async readAccountRateLimits() {
|
|
35
|
+
async readAccountRateLimits(): Promise<any> {
|
|
36
36
|
return {
|
|
37
37
|
rateLimits: {},
|
|
38
38
|
rateLimitsByLimitId: {
|
|
@@ -46,6 +46,63 @@ class FakeCodexManager extends EventEmitter {
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
describe('CodexRuntimeAdapter', () => {
|
|
49
|
+
it('does not terminate a turn while Codex is retrying an upstream request', () => {
|
|
50
|
+
const manager = new FakeCodexManager();
|
|
51
|
+
const adapter = new CodexRuntimeAdapter(manager as never);
|
|
52
|
+
const events: unknown[] = [];
|
|
53
|
+
adapter.on('event', (event) => events.push(event));
|
|
54
|
+
|
|
55
|
+
manager.emit('notification', {
|
|
56
|
+
method: 'error',
|
|
57
|
+
params: {
|
|
58
|
+
threadId: 'thread-1',
|
|
59
|
+
turnId: 'turn-1',
|
|
60
|
+
willRetry: true,
|
|
61
|
+
error: {
|
|
62
|
+
message: 'Reconnecting... 1/5',
|
|
63
|
+
additionalDetails: 'unexpected status 404 Not Found',
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
expect(events).toEqual([]);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('preserves detailed upstream errors when the final retry fails', () => {
|
|
72
|
+
const manager = new FakeCodexManager();
|
|
73
|
+
const adapter = new CodexRuntimeAdapter(manager as never);
|
|
74
|
+
const events: unknown[] = [];
|
|
75
|
+
adapter.on('event', (event) => events.push(event));
|
|
76
|
+
|
|
77
|
+
manager.emit('notification', {
|
|
78
|
+
method: 'error',
|
|
79
|
+
params: {
|
|
80
|
+
threadId: 'thread-1',
|
|
81
|
+
turnId: 'turn-1',
|
|
82
|
+
willRetry: false,
|
|
83
|
+
error: {
|
|
84
|
+
message: 'Upstream request failed.',
|
|
85
|
+
additionalDetails: JSON.stringify({
|
|
86
|
+
error: {
|
|
87
|
+
message: 'Model "gpt-5.6-sol" is not supported by any configured account in this group',
|
|
88
|
+
type: 'model_not_found',
|
|
89
|
+
},
|
|
90
|
+
}),
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
expect(events).toEqual([
|
|
96
|
+
expect.objectContaining({
|
|
97
|
+
type: 'turn.failed',
|
|
98
|
+
providerSessionId: 'thread-1',
|
|
99
|
+
providerTurnId: 'turn-1',
|
|
100
|
+
error: 'Model "gpt-5.6-sol" is not supported by any configured account in this group',
|
|
101
|
+
willRetry: false,
|
|
102
|
+
}),
|
|
103
|
+
]);
|
|
104
|
+
});
|
|
105
|
+
|
|
49
106
|
it('converts prompt photo tokens into structured local image input', async () => {
|
|
50
107
|
const manager = new FakeCodexManager();
|
|
51
108
|
const adapter = new CodexRuntimeAdapter(manager as never);
|
|
@@ -104,6 +161,48 @@ describe('CodexRuntimeAdapter', () => {
|
|
|
104
161
|
});
|
|
105
162
|
});
|
|
106
163
|
|
|
164
|
+
it('uses the populated backward-compatible snapshot when a named bucket is empty', async () => {
|
|
165
|
+
const manager = new FakeCodexManager();
|
|
166
|
+
manager.readAccountRateLimits = async () => ({
|
|
167
|
+
rateLimits: {
|
|
168
|
+
primary: {
|
|
169
|
+
usedPercent: 63,
|
|
170
|
+
windowDurationMins: 10_080,
|
|
171
|
+
resetsAt: 1_800_604_800,
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
rateLimitsByLimitId: { codex: { primary: null, secondary: null } },
|
|
175
|
+
});
|
|
176
|
+
const adapter = new CodexRuntimeAdapter(manager as never);
|
|
177
|
+
|
|
178
|
+
await expect(adapter.getSubscriptionUsage()).resolves.toMatchObject({
|
|
179
|
+
provider: 'codex',
|
|
180
|
+
authKind: 'subscription',
|
|
181
|
+
windows: [
|
|
182
|
+
{ id: 'primary', label: '7d', durationMinutes: 10_080, usedPercent: 63 },
|
|
183
|
+
],
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('normalizes legacy snake-case rate-limit window fields', async () => {
|
|
188
|
+
const manager = new FakeCodexManager();
|
|
189
|
+
manager.readAccountRateLimits = async () => ({
|
|
190
|
+
rateLimits: {
|
|
191
|
+
primary: {
|
|
192
|
+
used_percent: 12,
|
|
193
|
+
window_minutes: 300,
|
|
194
|
+
resets_at: 1_800_000_000,
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
rateLimitsByLimitId: null,
|
|
198
|
+
});
|
|
199
|
+
const adapter = new CodexRuntimeAdapter(manager as never);
|
|
200
|
+
|
|
201
|
+
await expect(adapter.getSubscriptionUsage()).resolves.toMatchObject({
|
|
202
|
+
windows: [{ label: '5h', durationMinutes: 300, usedPercent: 12 }],
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
|
|
107
206
|
it('hides subscription windows for API-key authentication', async () => {
|
|
108
207
|
const manager = new FakeCodexManager();
|
|
109
208
|
manager.readAccount = async () => ({
|
|
@@ -45,6 +45,7 @@ import {
|
|
|
45
45
|
CodexThreadRecord,
|
|
46
46
|
CodexThreadStatus,
|
|
47
47
|
CodexThreadGoalRecord,
|
|
48
|
+
CodexTurnError,
|
|
48
49
|
CodexTurnRecord,
|
|
49
50
|
CodexTurnItem,
|
|
50
51
|
CodexServerEvent,
|
|
@@ -208,7 +209,52 @@ function mapModel(model: Awaited<ReturnType<CodexAppServerManager['listModels']>
|
|
|
208
209
|
}
|
|
209
210
|
|
|
210
211
|
function mapTurn(turn: CodexTurnRecord): AgentTurn {
|
|
211
|
-
|
|
212
|
+
const mapped = codexTurnToAgentTurn(turn);
|
|
213
|
+
return turn.error
|
|
214
|
+
? {
|
|
215
|
+
...mapped,
|
|
216
|
+
error: { message: codexTurnErrorMessage(turn.error) },
|
|
217
|
+
}
|
|
218
|
+
: mapped;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function nestedUpstreamErrorMessage(value: string | null | undefined) {
|
|
222
|
+
if (!value) {
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
try {
|
|
227
|
+
const parsed = JSON.parse(value) as unknown;
|
|
228
|
+
if (typeof parsed !== 'object' || parsed === null) {
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
const error = 'error' in parsed ? parsed.error : parsed;
|
|
232
|
+
if (typeof error !== 'object' || error === null || !('message' in error)) {
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
return typeof error.message === 'string' && error.message.trim()
|
|
236
|
+
? error.message.trim()
|
|
237
|
+
: null;
|
|
238
|
+
} catch {
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function codexTurnErrorMessage(error: CodexTurnError) {
|
|
244
|
+
const message = error.message?.trim() || 'Turn failed unexpectedly.';
|
|
245
|
+
const details = error.additionalDetails?.trim();
|
|
246
|
+
if (!details) {
|
|
247
|
+
return message;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const upstreamMessage = nestedUpstreamErrorMessage(details);
|
|
251
|
+
if (upstreamMessage && !message.includes(upstreamMessage)) {
|
|
252
|
+
return upstreamMessage;
|
|
253
|
+
}
|
|
254
|
+
if (message.includes(details)) {
|
|
255
|
+
return message;
|
|
256
|
+
}
|
|
257
|
+
return `${message}\n\n${details}`;
|
|
212
258
|
}
|
|
213
259
|
|
|
214
260
|
function mapGoal(goal: CodexThreadGoalRecord): AgentGoal {
|
|
@@ -467,15 +513,18 @@ function mapCodexNotification(event: CodexServerEvent): AgentRuntimeEvent | null
|
|
|
467
513
|
const params = event.params as {
|
|
468
514
|
threadId: string;
|
|
469
515
|
turnId: string;
|
|
470
|
-
error:
|
|
516
|
+
error: CodexTurnError;
|
|
471
517
|
willRetry: boolean;
|
|
472
518
|
};
|
|
519
|
+
if (params.willRetry) {
|
|
520
|
+
return null;
|
|
521
|
+
}
|
|
473
522
|
return {
|
|
474
523
|
type: 'turn.failed',
|
|
475
524
|
provider: 'codex',
|
|
476
525
|
providerSessionId: params.threadId,
|
|
477
526
|
providerTurnId: params.turnId,
|
|
478
|
-
error: params.error
|
|
527
|
+
error: codexTurnErrorMessage(params.error),
|
|
479
528
|
willRetry: params.willRetry,
|
|
480
529
|
};
|
|
481
530
|
}
|
|
@@ -498,6 +547,41 @@ function formatRateLimitWindowLabel(
|
|
|
498
547
|
return `${durationMinutes}m`;
|
|
499
548
|
}
|
|
500
549
|
|
|
550
|
+
function recordOrNull(value: unknown): Record<string, unknown> | null {
|
|
551
|
+
return value && typeof value === 'object' && !Array.isArray(value)
|
|
552
|
+
? (value as Record<string, unknown>)
|
|
553
|
+
: null;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
function rateLimitWindowsFromSnapshot(snapshot: unknown) {
|
|
557
|
+
const record = recordOrNull(snapshot);
|
|
558
|
+
if (!record) return [];
|
|
559
|
+
|
|
560
|
+
return ['primary', 'secondary'].flatMap((id) => {
|
|
561
|
+
const window = recordOrNull(record[id]);
|
|
562
|
+
if (!window) return [];
|
|
563
|
+
const usedPercent = Number(window.usedPercent ?? window.used_percent);
|
|
564
|
+
if (!Number.isFinite(usedPercent)) return [];
|
|
565
|
+
const duration = Number(
|
|
566
|
+
window.windowDurationMins ??
|
|
567
|
+
window.window_duration_mins ??
|
|
568
|
+
window.windowMinutes ??
|
|
569
|
+
window.window_minutes,
|
|
570
|
+
);
|
|
571
|
+
const durationMinutes = Number.isFinite(duration) ? duration : null;
|
|
572
|
+
const resetsAt = Number(window.resetsAt ?? window.resets_at);
|
|
573
|
+
return [{
|
|
574
|
+
id,
|
|
575
|
+
durationMinutes,
|
|
576
|
+
label: formatRateLimitWindowLabel(durationMinutes, id),
|
|
577
|
+
usedPercent: Math.max(0, Math.min(100, usedPercent)),
|
|
578
|
+
resetsAt: Number.isFinite(resetsAt)
|
|
579
|
+
? new Date(resetsAt * 1000).toISOString()
|
|
580
|
+
: null,
|
|
581
|
+
}];
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
|
|
501
585
|
function mapCodexRuntimeError(error: unknown): never {
|
|
502
586
|
if (error instanceof AgentRuntimeError) {
|
|
503
587
|
throw error;
|
|
@@ -649,31 +733,14 @@ export class CodexRuntimeAdapter extends EventEmitter implements AgentRuntime {
|
|
|
649
733
|
this.manager.readAccountRateLimits(),
|
|
650
734
|
);
|
|
651
735
|
const buckets = response.rateLimitsByLimitId;
|
|
652
|
-
const
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
if (!value || typeof value !== 'object') return [];
|
|
661
|
-
const window = value as Record<string, unknown>;
|
|
662
|
-
const usedPercent = Number(window.usedPercent);
|
|
663
|
-
if (!Number.isFinite(usedPercent)) return [];
|
|
664
|
-
const duration = Number(window.windowDurationMins);
|
|
665
|
-
const durationMinutes = Number.isFinite(duration) ? duration : null;
|
|
666
|
-
const resetsAt = Number(window.resetsAt);
|
|
667
|
-
return [{
|
|
668
|
-
id,
|
|
669
|
-
durationMinutes,
|
|
670
|
-
label: formatRateLimitWindowLabel(durationMinutes, id),
|
|
671
|
-
usedPercent: Math.max(0, Math.min(100, usedPercent)),
|
|
672
|
-
resetsAt: Number.isFinite(resetsAt)
|
|
673
|
-
? new Date(resetsAt * 1000).toISOString()
|
|
674
|
-
: null,
|
|
675
|
-
}];
|
|
676
|
-
});
|
|
736
|
+
const candidates = [
|
|
737
|
+
response.rateLimits,
|
|
738
|
+
buckets?.codex,
|
|
739
|
+
...Object.values(buckets ?? {}),
|
|
740
|
+
];
|
|
741
|
+
const windows = candidates
|
|
742
|
+
.map(rateLimitWindowsFromSnapshot)
|
|
743
|
+
.sort((left, right) => right.length - left.length)[0] ?? [];
|
|
677
744
|
return {
|
|
678
745
|
provider: 'codex' as const,
|
|
679
746
|
authKind: 'subscription' as const,
|
|
@@ -135,7 +135,7 @@ export interface CodexTurnItem {
|
|
|
135
135
|
export interface CodexTurnRecord {
|
|
136
136
|
id: string;
|
|
137
137
|
status: CodexTurnStatus;
|
|
138
|
-
error:
|
|
138
|
+
error: CodexTurnError | null;
|
|
139
139
|
items: CodexTurnItem[];
|
|
140
140
|
}
|
|
141
141
|
|
|
@@ -401,6 +401,12 @@ export interface CodexErrorEvent {
|
|
|
401
401
|
willRetry: boolean;
|
|
402
402
|
}
|
|
403
403
|
|
|
404
|
+
export interface CodexTurnError {
|
|
405
|
+
message: string;
|
|
406
|
+
additionalDetails?: string | null;
|
|
407
|
+
codexErrorInfo?: unknown;
|
|
408
|
+
}
|
|
409
|
+
|
|
404
410
|
export type CodexServerEvent =
|
|
405
411
|
| { method: 'thread/started'; params: { thread: CodexThreadRecord } }
|
|
406
412
|
| { method: 'thread/status/changed'; params: { threadId: string; status: CodexThreadStatus } }
|
|
@@ -416,5 +422,5 @@ export type CodexServerEvent =
|
|
|
416
422
|
| { method: 'turn/plan/updated'; params: { threadId: string; turnId: string; explanation: string | null; plan: Array<{ step: string; status: string }> } }
|
|
417
423
|
| { method: 'turn/completed'; params: { threadId: string; turn: CodexTurnRecord } }
|
|
418
424
|
| { method: 'item/agentMessage/delta'; params: CodexOutputDeltaEvent }
|
|
419
|
-
| { method: 'error'; params: { error:
|
|
425
|
+
| { method: 'error'; params: { error: CodexTurnError; willRetry: boolean; threadId: string; turnId: string } }
|
|
420
426
|
| { method: string; params: Record<string, unknown> };
|
|
@@ -558,6 +558,22 @@ export function deleteThreadPendingSteerRecordById(db: DatabaseClient, id: strin
|
|
|
558
558
|
db.delete(threadPendingSteers).where(eq(threadPendingSteers.id, id)).run();
|
|
559
559
|
}
|
|
560
560
|
|
|
561
|
+
export function updateThreadPendingSteerRecordDelivery(
|
|
562
|
+
db: DatabaseClient,
|
|
563
|
+
id: string,
|
|
564
|
+
delivery: 'steer' | 'continuation',
|
|
565
|
+
turnId: string,
|
|
566
|
+
) {
|
|
567
|
+
db.update(threadPendingSteers)
|
|
568
|
+
.set({
|
|
569
|
+
delivery,
|
|
570
|
+
turnId,
|
|
571
|
+
updatedAt: new Date().toISOString(),
|
|
572
|
+
})
|
|
573
|
+
.where(eq(threadPendingSteers.id, id))
|
|
574
|
+
.run();
|
|
575
|
+
}
|
|
576
|
+
|
|
561
577
|
export function deleteThreadPendingSteerRecordsByThreadId(
|
|
562
578
|
db: DatabaseClient,
|
|
563
579
|
threadId: string,
|