@inetafrica/open-claudia 3.4.11 → 3.4.13
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/CHANGELOG.md +13 -0
- package/channels/kazee/call/access.js +36 -3
- package/channels/kazee/call/manager.js +14 -12
- package/channels/kazee/call/session.js +59 -9
- package/core/handlers.js +10 -0
- package/core/providers/codex-events.js +3 -1
- package/core/providers/codex-rollout.js +312 -0
- package/core/providers/codex.js +12 -0
- package/core/runner.js +90 -26
- package/package.json +2 -2
- package/test-kazee-call-manager.js +33 -4
- package/test-kazee-call-session.js +61 -4
- package/test-provider-codex.js +12 -0
- package/test-provider-events.js +1 -1
- package/test-provider-language.js +4 -2
- package/test-usage-accounting.js +45 -12
|
@@ -94,7 +94,7 @@ function fixture() {
|
|
|
94
94
|
if (["call:allocate", "call:room-access"].includes(name)) {
|
|
95
95
|
setImmediate(() => originalEmit(`${name}-result`, { requestId: payload.requestId, ok: true, protocolVersion: 3,
|
|
96
96
|
callAttemptId: "managed-attempt-123456789", organizationId: "tenant-a", roomId: 100, chatId: "managed-chat",
|
|
97
|
-
deviceId: payload.deviceId, generation: 0, ...replyPlacement, janusToken: "root-token", roomToken: "room-token", roomTokenExpiresAt: Date.now() + 300000, leaseExpiresAt: Date.now() + 120000 }));
|
|
97
|
+
deviceId: payload.deviceId, generation: 0, roomAdmissionRequired: true, iceServers: [{ urls: ["turn:secondary:3478", "turns:secondary:5349"], username: "assigned", credential: "assigned" }], iceCredentialsExpiresAt: Date.now() + 3600000, ...replyPlacement, janusToken: "root-token", roomToken: "room-token", roomTokenExpiresAt: Date.now() + 300000, leaseExpiresAt: Date.now() + 120000 }));
|
|
98
98
|
}
|
|
99
99
|
return originalEmit(name, payload);
|
|
100
100
|
};
|
|
@@ -105,7 +105,8 @@ function fixture() {
|
|
|
105
105
|
assert.strictEqual(f.manager.session.options.janusToken, "root-token");
|
|
106
106
|
assert.strictEqual(f.manager.session.options.roomToken, "room-token");
|
|
107
107
|
assert.strictEqual(f.manager.session.options.janusUrl, "wss://assigned/janus");
|
|
108
|
-
assert.
|
|
108
|
+
assert.strictEqual(f.manager.session.options.iceServers[0].urls, "turn:secondary:3478");
|
|
109
|
+
assert.ok(f.manager.session.options.iceCredentialsExpiresAt > Date.now());
|
|
109
110
|
const refreshed = await f.manager.session.options.refreshRoomAccess();
|
|
110
111
|
assert.strictEqual(refreshed.server, "wss://assigned/janus");
|
|
111
112
|
replyPlacement = {nodeId: "secondary", server: "wss://other/janus"};
|
|
@@ -139,8 +140,12 @@ function fixture() {
|
|
|
139
140
|
assert.strictEqual(f.manager.session, null, "authoritative end also ends an unanswered modern call");
|
|
140
141
|
assert.strictEqual(socket.listenerCount("call:allocate-result"), 0);
|
|
141
142
|
assert.strictEqual(socket.listenerCount("call:heartbeat-result"), 0, "ended manager session releases lease listener");
|
|
142
|
-
|
|
143
|
+
f.manager.adapter._request = async () => ({ config: { roomAdmissionEnabled: false, janusServer: "wss://legacy/janus", iceServers: [{ urls: "turn:legacy:3478", username: "legacy", credential: "legacy" }] } });
|
|
144
|
+
await f.manager.incoming({ fromUserId: "owner-1", chatId: "managed-chat", roomId: 100, callType: "audio", callAttemptId: managedInvite.callAttemptId, organizationId: "tenant-a", roomAdmissionRequired: true });
|
|
143
145
|
const incomingManaged = f.manager.session;
|
|
146
|
+
assert.strictEqual(incomingManaged.options.managedRoom, true);
|
|
147
|
+
assert.strictEqual(incomingManaged.options.janusUrl, "wss://assigned/janus");
|
|
148
|
+
assert.strictEqual(incomingManaged.options.iceServers[0].urls, "turn:secondary:3478");
|
|
144
149
|
const managedAccept = emitted.filter(event => event.name === "call:accept").at(-1).payload;
|
|
145
150
|
assert.strictEqual(managedAccept.deviceId, f.manager.deviceId);
|
|
146
151
|
assert.strictEqual(managedAccept.generation, 0);
|
|
@@ -156,6 +161,19 @@ function fixture() {
|
|
|
156
161
|
assert.strictEqual(socket.listenerCount("call:heartbeat-result"), 0);
|
|
157
162
|
|
|
158
163
|
|
|
164
|
+
// Legacy attempts, including those with modern attempt IDs, keep baseline
|
|
165
|
+
// transport even when the organization now enables new managed allocations.
|
|
166
|
+
for (const roomAdmissionRequired of [undefined, false]) {
|
|
167
|
+
f = fixture();
|
|
168
|
+
f.manager.adapter._request = async () => ({ config: { roomAdmissionEnabled: true, janusServer: "wss://legacy/janus", iceServers: [{ urls: "turn:legacy:3478", username: "legacy", credential: "legacy" }] } });
|
|
169
|
+
await f.manager.incoming({ fromUserId: "owner-1", chatId: "legacy", roomId: 99, callType: "audio", callAttemptId: "legacy-attempt-123456789", roomAdmissionRequired });
|
|
170
|
+
assert.strictEqual(f.manager.session.options.managedRoom, false);
|
|
171
|
+
assert.strictEqual(f.manager.session.options.janusUrl, "wss://legacy/janus");
|
|
172
|
+
assert.strictEqual(f.manager.session.options.refreshRoomAccess, undefined);
|
|
173
|
+
assert.ok(!f.events.some(event => event.name === "call:room-access"));
|
|
174
|
+
await f.manager.stop();
|
|
175
|
+
}
|
|
176
|
+
|
|
159
177
|
f = fixture();
|
|
160
178
|
await f.manager.incoming({ fromUserId: "owner-1", chatId: "managed-chat", roomId: 100, callType: "video", callAttemptId: "managed-attempt-123456789", organizationId: "tenant-a", generation: 7 });
|
|
161
179
|
assert.strictEqual(f.events[0].payload.generation, 7, "pre-access decline preserves recipient reinvite generation");
|
|
@@ -200,12 +218,23 @@ function fixture() {
|
|
|
200
218
|
accessSocket.emit("call:allocate-result", { requestId: requested.requestId, ok: true, protocolVersion: 3, callAttemptId: "attempt-1234567890", organizationId: "tenant-a", roomId: 20, chatId: "chat", deviceId: "different-device", generation: 0, janusToken: "t", roomToken: "t", roomTokenExpiresAt: Date.now() + 300000 });
|
|
201
219
|
await assert.rejects(accessPromise, /Invalid call room/);
|
|
202
220
|
assert.strictEqual(accessSocket.listenerCount("call:allocate-result"), 0);
|
|
203
|
-
for (const server of ["http://assigned/janus", "wss://user:password@assigned/janus", "wss://assigned/janus#fragment", 123]) {
|
|
221
|
+
for (const server of ["http://assigned/janus", "wss://user:password@assigned/janus", "wss://assigned/janus#fragment", "wss://assigned/janus?token=bad", 123]) {
|
|
204
222
|
const invalid = requestRoomAccess(accessSocket, "allocate", { deviceId: "device-1234567890", chatId: "chat" });
|
|
205
223
|
accessSocket.emit("call:allocate-result", { requestId: requested.requestId, ok: true, protocolVersion: 3, callAttemptId: "attempt-1234567890", organizationId: "tenant-a", roomId: 20, chatId: "chat", deviceId: "device-1234567890", generation: 0, server, janusToken: "t", roomToken: "t", roomTokenExpiresAt: Date.now() + 300000 });
|
|
206
224
|
await assert.rejects(invalid, /Invalid call room/);
|
|
207
225
|
assert.strictEqual(accessSocket.listenerCount("call:allocate-result"), 0);
|
|
208
226
|
}
|
|
227
|
+
for (const malformed of [
|
|
228
|
+
{ roomAdmissionRequired: undefined }, { roomAdmissionRequired: false },
|
|
229
|
+
{ iceServers: undefined }, { iceServers: [] }, { iceCredentialsExpiresAt: Date.now() - 1 },
|
|
230
|
+
{ iceServers: [{ urls: 'turn:user:secret@relay:3478', username: 'u', credential: 'p' }] },
|
|
231
|
+
{ iceServers: [{ urls: 'turn:relay:3478' }] },
|
|
232
|
+
]) {
|
|
233
|
+
const invalid = requestRoomAccess(accessSocket, "allocate", { deviceId: "device-1234567890", chatId: "chat" });
|
|
234
|
+
accessSocket.emit("call:allocate-result", { requestId: requested.requestId, ok: true, protocolVersion: 3, callAttemptId: "attempt-1234567890", organizationId: "tenant-a", roomId: 20, chatId: "chat", deviceId: "device-1234567890", generation: 0, server: "wss://assigned/janus", nodeId: "secondary", roomAdmissionRequired: true, iceServers: [{ urls: 'turn:relay:3478', username: 'u', credential: 'p' }], iceCredentialsExpiresAt: Date.now() + 3600000, janusToken: "t", roomToken: "t", roomTokenExpiresAt: Date.now() + 300000, ...malformed });
|
|
235
|
+
await assert.rejects(invalid, /Invalid call room/);
|
|
236
|
+
assert.strictEqual(accessSocket.listenerCount("call:allocate-result"), 0);
|
|
237
|
+
}
|
|
209
238
|
const controller = new AbortController();
|
|
210
239
|
const cancelledAccess = requestRoomAccess(accessSocket, "allocate", { deviceId: "device-1234567890", chatId: "chat" }, { signal: controller.signal });
|
|
211
240
|
controller.abort();
|
|
@@ -19,7 +19,7 @@ class FakeJanus {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
class FakePC {
|
|
22
|
-
constructor() { this.onTrack = { subscribe: () => {} }; }
|
|
22
|
+
constructor(options) { this.options = options; this.onTrack = { subscribe: () => {} }; }
|
|
23
23
|
addTrack() { return { codec: { payloadType: 111 } }; }
|
|
24
24
|
async createOffer() { return {}; }
|
|
25
25
|
async setLocalDescription() {}
|
|
@@ -448,19 +448,22 @@ async function testManagedRoomCredentials() {
|
|
|
448
448
|
constructor(options) { super(); this.options = options; this.calls = []; }
|
|
449
449
|
async message(handle, body) {
|
|
450
450
|
this.calls.push(body);
|
|
451
|
-
if (body.request === "join" && body.ptype === "publisher") return { plugindata: { data: { videoroom: "joined", private_id: 999, publishers: [] } } };
|
|
451
|
+
if (body.request === "join" && body.ptype === "publisher") return { plugindata: { data: { videoroom: "joined", id: 41, private_id: 999, publishers: [] } } };
|
|
452
452
|
return {};
|
|
453
453
|
}
|
|
454
454
|
}
|
|
455
455
|
let refreshes = 0;
|
|
456
456
|
const session = new KazeeCallSession({ roomId: 7, chatId: "chat", callerId: "owner", botUserId: "bot", janusUrl: "ws://test",
|
|
457
|
-
janusToken: "root-token", roomToken: "publisher-token", roomTokenExpiresAt: Date.now() + 300000, managedRoom: true, asCaller: true,
|
|
458
|
-
refreshRoomAccess: async () => { refreshes += 1; return { janusToken: "fresh-root", janusTokenExpiresAt: Date.now() + 86400000, roomToken: "subscriber-token", roomTokenExpiresAt: Date.now() + 300000 }; },
|
|
457
|
+
janusToken: "root-token", roomToken: "publisher-token", roomTokenExpiresAt: Date.now() + 300000, managedRoom: true, asCaller: true, iceServers: [{ urls: "turn:relay:3478", username: "fixture", credential: "fixture" }], iceCredentialsExpiresAt: Date.now() + 3600000,
|
|
458
|
+
refreshRoomAccess: async () => { refreshes += 1; return { iceServers: [{ urls: "turn:relay:3478", username: "fresh", credential: "fresh" }], iceCredentialsExpiresAt: Date.now() + 3600000, janusToken: "fresh-root", janusTokenExpiresAt: Date.now() + 86400000, roomToken: "subscriber-token", roomTokenExpiresAt: Date.now() + 300000 }; },
|
|
459
459
|
JanusClientImpl: SignedJanus, PeerConnectionImpl: FakePC, SpeechStreamImpl: FakeSpeechStream });
|
|
460
460
|
session._maybePublish = async () => {};
|
|
461
461
|
await session.start();
|
|
462
462
|
assert.strictEqual(session.janus.options.token, "root-token");
|
|
463
463
|
assert.strictEqual(session.janus.calls[0].token, "publisher-token");
|
|
464
|
+
await session._consumePublisherFrame({ plugindata: { data: { publishers: [{ id: 41, display: 'canonical-bot@calling.invalid' }, { id: 42, display: 'open-claudia:legacy-bot' }] } } });
|
|
465
|
+
assert.strictEqual(session.subscribers.size, 0, 'own joined publisher ID is excluded independently of canonical display; legacy bot exclusion remains');
|
|
466
|
+
|
|
464
467
|
session.roomTokenExpiresAt = Date.now() - 1;
|
|
465
468
|
await Promise.all([session._subscribe(17, "owner"), session._subscribe(18, "other")]);
|
|
466
469
|
const subJoin = session.janus.calls.find(body => body.ptype === "subscriber");
|
|
@@ -481,7 +484,61 @@ async function testManagedRoomCredentials() {
|
|
|
481
484
|
assert.ok(!session.janus.calls.some(body => ["create", "destroy"].includes(body.request)), "server allocated room ownership never transfers to the bot");
|
|
482
485
|
}
|
|
483
486
|
|
|
487
|
+
async function testAssignedRelayTransportLifetime() {
|
|
488
|
+
const { RTCPeerConnection } = require('werift');
|
|
489
|
+
const originalExpiry = Date.now() + 3600000;
|
|
490
|
+
const assigned = (username, expiresAt) => ({ iceServers: [{ urls: 'turn:127.0.0.1:3478', username, credential: username + '-password' }], iceCredentialsExpiresAt: expiresAt });
|
|
491
|
+
const session = new KazeeCallSession({ roomId: 7, chatId: 'chat', callerId: 'owner', botUserId: 'bot', managedRoom: true,
|
|
492
|
+
...assigned('original-fixture', originalExpiry), roomToken: 'room', roomTokenExpiresAt: Date.now() + 300000,
|
|
493
|
+
refreshRoomAccess: async () => ({ ...assigned('renewed-fixture', originalExpiry + 3600000), roomToken: 'renewed-room', roomTokenExpiresAt: Date.now() + 300000, janusToken: 'renewed-root', janusTokenExpiresAt: Date.now() + 300000 }),
|
|
494
|
+
JanusClientImpl: FakeJanus, PeerConnectionImpl: RTCPeerConnection, SpeechStreamImpl: FakeSpeechStream });
|
|
495
|
+
try {
|
|
496
|
+
const old = session._newPeerConnection(); old.addTransceiver('audio');
|
|
497
|
+
assert.strictEqual(old.iceTransports[0].connection.options.turnUsername, 'original-fixture');
|
|
498
|
+
// Calling Werift's public setter cannot refresh an already-created ICE
|
|
499
|
+
// transport. Verify the installed implementation, without network gathering.
|
|
500
|
+
old.setConfiguration({ iceServers: assigned('setter-fixture', originalExpiry).iceServers });
|
|
501
|
+
assert.strictEqual(old.getConfiguration().iceServers[0].username, 'setter-fixture');
|
|
502
|
+
assert.strictEqual(old.iceTransports[0].connection.options.turnUsername, 'original-fixture');
|
|
503
|
+
await session._roomAuth(true);
|
|
504
|
+
assert.strictEqual(session._transportIceExpiry.get(old), originalExpiry, 'new access never extends an existing allocation deadline');
|
|
505
|
+
assert.strictEqual(old.iceTransports[0].connection.options.turnUsername, 'original-fixture');
|
|
506
|
+
const fresh = session._newPeerConnection(); fresh.addTransceiver('audio');
|
|
507
|
+
assert.strictEqual(fresh.iceTransports[0].connection.options.turnUsername, 'renewed-fixture');
|
|
508
|
+
assert.strictEqual(session._transportIceExpiry.get(fresh), originalExpiry + 3600000);
|
|
509
|
+
} finally { await session.close(); }
|
|
510
|
+
assert.strictEqual(session._transportExpiryTimer, null);
|
|
511
|
+
assert.strictEqual(session._transportIceExpiry.size, 0);
|
|
512
|
+
|
|
513
|
+
let ended;
|
|
514
|
+
const bounded = new KazeeCallSession({ roomId: 7, chatId: 'chat', callerId: 'owner', botUserId: 'bot', managedRoom: true,
|
|
515
|
+
...assigned('short-fixture', Date.now() + 1500), roomToken: 'room', roomTokenExpiresAt: Date.now() + 300000,
|
|
516
|
+
refreshRoomAccess: async () => ({ ...assigned('fresh-fixture', Date.now() + 3600000), roomToken: 'fresh', roomTokenExpiresAt: Date.now() + 300000 }),
|
|
517
|
+
onEnded: reason => { ended = reason; }, JanusClientImpl: FakeJanus, PeerConnectionImpl: FakePC, SpeechStreamImpl: FakeSpeechStream });
|
|
518
|
+
try {
|
|
519
|
+
const old = bounded._newPeerConnection();
|
|
520
|
+
const deadline = bounded._transportIceExpiry.get(old);
|
|
521
|
+
await bounded._roomAuth(true);
|
|
522
|
+
assert.strictEqual(bounded._transportIceExpiry.get(old), deadline);
|
|
523
|
+
const until = Date.now() + 2000;
|
|
524
|
+
while (!bounded.closed && Date.now() < until) await wait(25);
|
|
525
|
+
assert.strictEqual(ended, 'relay-credentials-expired', 'existing transport closes on its original expiry despite successful refresh');
|
|
526
|
+
} finally { await bounded.close(); }
|
|
527
|
+
|
|
528
|
+
let publications = 0;
|
|
529
|
+
class PublishingPC extends FakePC { constructor(options) { super(options); publications += 1; } }
|
|
530
|
+
class PublishingJanus extends FakeJanus { async message() { return { jsep: { type: 'answer', sdp: 'fixture' } }; } }
|
|
531
|
+
const concurrent = new KazeeCallSession({ roomId: 7, chatId: 'chat', callerId: 'owner', botUserId: 'bot', managedRoom: true,
|
|
532
|
+
...assigned('original-fixture', Date.now() + 3600000), roomToken: 'room', roomTokenExpiresAt: Date.now() + 300000,
|
|
533
|
+
JanusClientImpl: PublishingJanus, PeerConnectionImpl: PublishingPC });
|
|
534
|
+
try { await Promise.all([concurrent._publish(), concurrent._publish()]); assert.strictEqual(publications, 1, 'concurrent publisher events cannot race across async room authorization'); } finally { await concurrent.close(); }
|
|
535
|
+
|
|
536
|
+
const missing = new KazeeCallSession({ roomId: 7, chatId: 'chat', callerId: 'owner', botUserId: 'bot', managedRoom: true, JanusClientImpl: FakeJanus, PeerConnectionImpl: FakePC });
|
|
537
|
+
try { assert.throws(() => missing._newPeerConnection(), /relay credentials unavailable/); } finally { await missing.close(); }
|
|
538
|
+
}
|
|
539
|
+
|
|
484
540
|
(async () => {
|
|
541
|
+
await testAssignedRelayTransportLifetime();
|
|
485
542
|
await testManagedRoomCredentials();
|
|
486
543
|
await testSpeakSerialization();
|
|
487
544
|
await testSpeakCancellation();
|
package/test-provider-codex.js
CHANGED
|
@@ -134,6 +134,18 @@ const directResume = provider.buildMainInvocation(simpleContext({ sessionId: "th
|
|
|
134
134
|
assert.ok(directResume.args.includes("--dangerously-bypass-approvals-and-sandbox"));
|
|
135
135
|
assert.ok(directResume.args.indexOf("--dangerously-bypass-approvals-and-sandbox") < directResume.args.indexOf("thread-direct"));
|
|
136
136
|
|
|
137
|
+
{
|
|
138
|
+
const before = process.env.CODEX_MULTI_AGENT;
|
|
139
|
+
delete process.env.CODEX_MULTI_AGENT;
|
|
140
|
+
assert.ok(!provider.buildMainInvocation(simpleContext({})).args.includes("features.multi_agent=false"), "multi_agent stays as Codex ships it by default");
|
|
141
|
+
process.env.CODEX_MULTI_AGENT = "off";
|
|
142
|
+
const noAgents = provider.buildMainInvocation(simpleContext({}));
|
|
143
|
+
const at = noAgents.args.indexOf("features.multi_agent=false");
|
|
144
|
+
assert.ok(at > 0 && noAgents.args[at - 1] === "-c", "CODEX_MULTI_AGENT=off passes -c features.multi_agent=false");
|
|
145
|
+
assert.ok(at < noAgents.args.indexOf("--json"), "the override sits with the other -c overrides, before the exec flags");
|
|
146
|
+
if (before === undefined) delete process.env.CODEX_MULTI_AGENT; else process.env.CODEX_MULTI_AGENT = before;
|
|
147
|
+
}
|
|
148
|
+
|
|
137
149
|
const continued = provider.buildMainInvocation(simpleContext({ continueSession: true, sessionId: "thread-continue" }));
|
|
138
150
|
assert.strictEqual(continued.args[1], "resume");
|
|
139
151
|
assert.ok(continued.args.includes("thread-continue"));
|
package/test-provider-events.js
CHANGED
|
@@ -203,7 +203,7 @@ assert.deepStrictEqual(normalizeCodexEvent(samples.codex.assistant, codexState),
|
|
|
203
203
|
{ type: "text_final", text: "Hello from Codex" },
|
|
204
204
|
]);
|
|
205
205
|
assert.deepStrictEqual(normalizeCodexEvent(samples.codex.result, codexState), [
|
|
206
|
-
{ type: "usage", usage: { input_tokens: 20, cached_input_tokens: 5, output_tokens: 4 }, scope: "
|
|
206
|
+
{ type: "usage", usage: { input_tokens: 20, cached_input_tokens: 5, output_tokens: 4 }, scope: "turn" },
|
|
207
207
|
{ type: "result", text: "", sessionId: "codex-thread-1" },
|
|
208
208
|
]);
|
|
209
209
|
assert.deepStrictEqual(normalizeCodexEvent(samples.codex.result, codexState), [], "duplicate Codex terminal and usage settle once");
|
|
@@ -463,8 +463,10 @@ assert.match(pkg.description, /provider-agnostic coding-agent harness/i);
|
|
|
463
463
|
// Codex turn. Live probe: unified-exec shell calls still reach PreToolUse as
|
|
464
464
|
// tool_name "Bash" and the deny gate caught the probe, so the gate no longer
|
|
465
465
|
// requires unified_exec off. Push approved by Sumeet before release.
|
|
466
|
-
// 3.4.
|
|
467
|
-
|
|
466
|
+
// 3.4.12: Sumeet approved the full calling rollout on 2026-09-05; preserve assigned media configuration across canary changes.
|
|
467
|
+
// 3.4.13: Codex usage accounting fix — Sumeet approved the three-bug fix on 2026-09-05 ("Yes");
|
|
468
|
+
// prepared 2026-09-06 for his release go. Rollout-based Codex context, per-turn ledger, sub-agent + quota accounting.
|
|
469
|
+
assert.strictEqual(pkg.version, "3.4.13", "release version must remain unchanged without explicit approval");
|
|
468
470
|
for (const keyword of ["claude", "codex", "coding-agent", "provider-agnostic"]) {
|
|
469
471
|
assert.ok(pkg.keywords.includes(keyword), `package keyword missing: ${keyword}`);
|
|
470
472
|
}
|
package/test-usage-accounting.js
CHANGED
|
@@ -9,6 +9,7 @@ const {
|
|
|
9
9
|
normalizeCodexUsage,
|
|
10
10
|
applyUsageToState,
|
|
11
11
|
shouldAutoCompact,
|
|
12
|
+
effectiveCompactThreshold,
|
|
12
13
|
peakContextTokens,
|
|
13
14
|
usageSessionColdStart,
|
|
14
15
|
} = require("./core/runner");
|
|
@@ -48,7 +49,9 @@ function state(overrides = {}) {
|
|
|
48
49
|
|
|
49
50
|
assert.deepStrictEqual(
|
|
50
51
|
usageParts({ input_tokens: 100, cached_input_tokens: 60, output_tokens: 10 }, "codex"),
|
|
51
|
-
|
|
52
|
+
// OpenAI counts cached tokens inside input_tokens: `input` is the uncached
|
|
53
|
+
// part so it compares like-for-like with Claude; `context` is the full prefix.
|
|
54
|
+
{ input: 40, cacheRead: 60, cacheCreation: 0, output: 10, context: 100 },
|
|
52
55
|
);
|
|
53
56
|
|
|
54
57
|
assert.deepStrictEqual(
|
|
@@ -62,7 +65,7 @@ const first = normalizeCodexUsage(s, "codex-session-1", {
|
|
|
62
65
|
cached_input_tokens: 700,
|
|
63
66
|
output_tokens: 50,
|
|
64
67
|
}, { project: "alpha" });
|
|
65
|
-
assert.strictEqual(first.usageScope, "
|
|
68
|
+
assert.strictEqual(first.usageScope, "turn", "Codex turn.completed usage is per turn, never a session total");
|
|
66
69
|
assert.strictEqual(usageParts(first.usage, "codex").context, 1000);
|
|
67
70
|
|
|
68
71
|
const second = normalizeCodexUsage(s, "codex-session-1", {
|
|
@@ -70,13 +73,13 @@ const second = normalizeCodexUsage(s, "codex-session-1", {
|
|
|
70
73
|
cached_input_tokens: 800,
|
|
71
74
|
output_tokens: 80,
|
|
72
75
|
}, { project: "alpha" });
|
|
73
|
-
assert.strictEqual(second.usageScope, "
|
|
76
|
+
assert.strictEqual(second.usageScope, "turn");
|
|
77
|
+
// No delta against the previous turn: a smaller turn after a bigger one used
|
|
78
|
+
// to be recorded as 0 (9 of 36 Codex turns on 2026-09-05).
|
|
74
79
|
assert.deepStrictEqual(second.usage, {
|
|
75
|
-
input_tokens:
|
|
76
|
-
cached_input_tokens:
|
|
77
|
-
|
|
78
|
-
cache_creation_input_tokens: 0,
|
|
79
|
-
output_tokens: 30,
|
|
80
|
+
input_tokens: 1250,
|
|
81
|
+
cached_input_tokens: 800,
|
|
82
|
+
output_tokens: 80,
|
|
80
83
|
});
|
|
81
84
|
|
|
82
85
|
applyUsageToState(s, second.usage, 0, {
|
|
@@ -87,9 +90,9 @@ applyUsageToState(s, second.usage, 0, {
|
|
|
87
90
|
liveContextTokens: usageParts(second.rawUsage, "codex").context,
|
|
88
91
|
});
|
|
89
92
|
assert.strictEqual(s.sessionUsage.turns, 1);
|
|
90
|
-
assert.strictEqual(s.sessionUsage.inputTokens,
|
|
91
|
-
assert.strictEqual(s.sessionUsage.cacheReadTokens,
|
|
92
|
-
assert.strictEqual(s.sessionUsage.lastInputTokens,
|
|
93
|
+
assert.strictEqual(s.sessionUsage.inputTokens, 450, "uncached input only");
|
|
94
|
+
assert.strictEqual(s.sessionUsage.cacheReadTokens, 800);
|
|
95
|
+
assert.strictEqual(s.sessionUsage.lastInputTokens, 1250);
|
|
93
96
|
assert.strictEqual(s.sessionUsage.liveContextTokens, 1250);
|
|
94
97
|
|
|
95
98
|
// The same native-looking ID in another project has an independent cumulative
|
|
@@ -99,7 +102,7 @@ const otherProject = normalizeCodexUsage(s, "codex-session-1", {
|
|
|
99
102
|
cached_input_tokens: 1200,
|
|
100
103
|
output_tokens: 100,
|
|
101
104
|
}, { project: "beta" });
|
|
102
|
-
assert.strictEqual(otherProject.usageScope, "
|
|
105
|
+
assert.strictEqual(otherProject.usageScope, "turn");
|
|
103
106
|
s.currentSession = { name: "beta", dir: "/workspace/beta" };
|
|
104
107
|
s.settings.backend = "claude";
|
|
105
108
|
s.activeSessions.beta = { claude: "claude-beta", codex: "codex-session-1" };
|
|
@@ -154,6 +157,36 @@ assert.strictEqual(shouldAutoCompact(state({
|
|
|
154
157
|
sessionUsage: { liveContextTokens: 250, lastInputTokens: 250, lastCompactedAt: Date.now() },
|
|
155
158
|
})), true);
|
|
156
159
|
|
|
160
|
+
// Codex is compacted at 80% of the window it reports, because its window
|
|
161
|
+
// (258k on gpt-6-astra) is below the default threshold — measured honestly the
|
|
162
|
+
// bot would otherwise never compact a Codex session.
|
|
163
|
+
{
|
|
164
|
+
const windowed = state({ sessionUsage: { liveContextTokens: 210000, lastInputTokens: 210000, contextWindowTokens: 258400 } });
|
|
165
|
+
delete windowed.settings.compactWindow;
|
|
166
|
+
assert.strictEqual(effectiveCompactThreshold(windowed), 206720, "80% of the reported Codex window");
|
|
167
|
+
assert.strictEqual(shouldAutoCompact(windowed), true, "210k of a 258k window compacts");
|
|
168
|
+
windowed.sessionUsage.liveContextTokens = 200000;
|
|
169
|
+
assert.strictEqual(shouldAutoCompact(windowed), false, "200k of a 258k window does not");
|
|
170
|
+
windowed.settings.backend = "claude";
|
|
171
|
+
assert.strictEqual(effectiveCompactThreshold(windowed), 380000, "the Codex window cap never applies to Claude");
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Rollout-derived facts ride along into the session bucket.
|
|
175
|
+
{
|
|
176
|
+
const enriched = state();
|
|
177
|
+
applyUsageToState(enriched, { input_tokens: 1000, cached_input_tokens: 900, output_tokens: 10 }, 0, {
|
|
178
|
+
backend: "codex", project: "alpha", sessionId: "codex-session-1", usageScope: "turn", liveContextTokens: 1000,
|
|
179
|
+
contextWindowTokens: 258400, quota: { usedPercent: 31, resetsAt: "2026-09-12T13:57:00.000Z" },
|
|
180
|
+
subagents: { threads: 2, requests: 5, totals: { input: 70000, cached: 50000, output: 300 } },
|
|
181
|
+
});
|
|
182
|
+
assert.strictEqual(enriched.sessionUsage.contextWindowTokens, 258400);
|
|
183
|
+
assert.strictEqual(enriched.sessionUsage.quotaUsedPercent, 31);
|
|
184
|
+
assert.strictEqual(enriched.sessionUsage.subagentThreads, 2);
|
|
185
|
+
assert.strictEqual(enriched.sessionUsage.subagentInputTokens, 20000, "sub-agent input is stored uncached");
|
|
186
|
+
assert.strictEqual(enriched.sessionUsage.subagentCacheReadTokens, 50000);
|
|
187
|
+
assert.strictEqual(enriched.sessionUsage.subagentOutputTokens, 300);
|
|
188
|
+
}
|
|
189
|
+
|
|
157
190
|
// peakContextTokens: the live context number must be the LARGEST single API
|
|
158
191
|
// call, not the turn-cumulative sum (the v3.0.0 347k-vs-160k regression).
|
|
159
192
|
assert.strictEqual(peakContextTokens([
|