@jskit-ai/assistant-core 0.1.208 → 0.1.211

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.
@@ -2444,3 +2444,349 @@ test("held lifecycle catalogue rejection preserves a newer same-runtime tail and
2444
2444
  await Promise.allSettled([failed, next, last]);
2445
2445
  }
2446
2446
  });
2447
+
2448
+ // Bound-host contract additions retain the original native app-server fixture,
2449
+ // schemas, tool-call framing, transcript store and run owner. Public Main's
2450
+ // identity/authority/readiness composition is a separate adoption prerequisite.
2451
+ import { createCodexAppServerRunOwner, codexAppServerTurnState } from "../src/server/conversation/codexTurn.js";
2452
+ import { codexApplicationToolConfiguration, ensureCodexAppServerThread } from "../src/server/conversation/codexProvider.js";
2453
+ import { createServiceToolCatalog } from "../src/server/lib/serviceToolCatalog.js";
2454
+
2455
+ async function boundCodexToolsFixture(t, options = {}) {
2456
+ let sharedProvider;
2457
+ const startThread = CodexAppServerAgentProvider.prototype.startThread;
2458
+ t.mock.method(CodexAppServerAgentProvider.prototype, "startThread", async function (...args) {
2459
+ sharedProvider ||= this;
2460
+ return startThread.apply(this, args);
2461
+ });
2462
+ const cleanups = [];
2463
+ let boundRuntime, owner;
2464
+ t.after(async () => {
2465
+ try { await boundRuntime?.close(); }
2466
+ finally {
2467
+ if (owner) {
2468
+ for (const id of ["bound-one", "bound-two"]) owner.clearSessionRecoveryTimers(id);
2469
+ for (const key of [...owner.eventSubscriptions.keys()]) owner.unsubscribeEventSubscription(key);
2470
+ }
2471
+ for (const cleanup of cleanups) await cleanup();
2472
+ }
2473
+ });
2474
+ const f = await fixture({ after: callback => cleanups.push(callback) });
2475
+ await f.conversation.send({ messageId: "original-process-seed", text: "Hello" });
2476
+ await f.conversation.wait();
2477
+ assert.ok(sharedProvider);
2478
+ const initialParams = (await f.trace()).find(row => row.method === "thread/start").params;
2479
+ const storage = createReentrantConversationStorage(f.storage);
2480
+ const transcript = createConversationTranscript({ storage });
2481
+ const streams = createConversationStreams();
2482
+ const runtimes = new Map(), bindings = new Map();
2483
+ const effects = [], mappings = [];
2484
+ const actor = { actor: { id: "bound-owner" }, surface: "app", permissions: ["numbers.read"] };
2485
+ const actions = applicationActions(async (_input, actual) => {
2486
+ effects.push(actual.boundAdmission);
2487
+ return options.execute ? options.execute(actual) : { value: 7 };
2488
+ });
2489
+ const catalog = createServiceToolCatalog(actions);
2490
+ const hostRuntime = {
2491
+ getSession: id => runtimes.get(id).getSession(id),
2492
+ store: new Proxy({}, { get(_target, key) {
2493
+ const originalStore = runtimes.values().next().value?.store;
2494
+ if (typeof originalStore?.[key] !== "function") return originalStore?.[key];
2495
+ return (id, ...args) => runtimes.get(id).store[key](id, ...args);
2496
+ } })
2497
+ };
2498
+ owner = createCodexAppServerRunOwner({
2499
+ createRuntime: async () => hostRuntime, createStore: async id => runtimes.get(id).store,
2500
+ acquireProvider: async () => sharedProvider,
2501
+ publish: (id, event) => boundRuntime?.publishNative({ namespace: `main-${id}`, sessionId: id, event }),
2502
+ hasRuntime: () => true
2503
+ });
2504
+ // The fixture's host owns shared-provider retention and scoped cleanup. It
2505
+ // supplies the same direct control context used by the original standalone
2506
+ // owner, and never closes the process held by the original fixture/peer.
2507
+ t.mock.method(owner, "controlContext", (_id, prepared) => prepared);
2508
+ t.mock.method(owner, "closeSession", async id => {
2509
+ for (const [key, subscription] of owner.eventSubscriptions) {
2510
+ if (subscription.sessionId === id) owner.unsubscribeEventSubscription(key);
2511
+ }
2512
+ owner.clearSessionRecoveryTimers(id);
2513
+ return { ok: true };
2514
+ });
2515
+ for (const id of ["bound-one", "bound-two"]) {
2516
+ const segmentId = `original-host-${id}`;
2517
+ await storage.write(id, async transaction => transaction.writeMetadata({ runtime: {
2518
+ engine: "codex", segmentId, configuration, binding: { threadId: "" }
2519
+ } }));
2520
+ const store = createCodexConversationStore({ storage, scope: id, segmentId, isCurrent: () => true, transcript, streams });
2521
+ const runtime = { store, getSession: store.getSession };
2522
+ runtimes.set(id, runtime);
2523
+ const readBinding = () => storage.read(id, async transaction => (await transaction.readMetadata()).runtime.binding);
2524
+ const identity = { read: async () => (await readBinding()).threadId,
2525
+ readToolSchemaIdentity: async () => (await readBinding()).toolSchemaIdentity };
2526
+ const messagePreparation = {
2527
+ async readContext() {
2528
+ const binding = await readBinding();
2529
+ return { runtime, session: await runtime.getSession(id), workdir: f.directory, binding,
2530
+ selection: { runtime, session: await runtime.getSession(id), threadId: () => binding.threadId,
2531
+ acquireProvider: async () => ({ provider: sharedProvider, reused: true }) } };
2532
+ },
2533
+ threadPreparation(_input, prepared) {
2534
+ return { provider: async () => ({ provider: sharedProvider, workdir: f.directory, observerOptions: { providerKey: "bound-shared-original-provider" },
2535
+ preparation: { settings: () => ({ threadSettings: initialParams, threadStartSettings: initialParams }),
2536
+ identity: { read: () => prepared.binding.threadId,
2537
+ readToolSchemaIdentity: () => prepared.binding.toolSchemaIdentity,
2538
+ async write({ threadId, toolSchemaIdentity }) {
2539
+ await storage.write(id, async transaction => {
2540
+ const metadata = await transaction.readMetadata();
2541
+ metadata.runtime.binding = { ...metadata.runtime.binding, threadId, toolSchemaIdentity };
2542
+ await transaction.writeMetadata(metadata);
2543
+ });
2544
+ } } } }) };
2545
+ },
2546
+ async prepareMessage(input, _prepared, { starting }) {
2547
+ if (options.prepareMessage) await options.prepareMessage(input, { starting });
2548
+ return starting ? { renderedPrompt: input.message, turnSettings: { cwd: f.directory, model: "test-model" } } : null;
2549
+ },
2550
+ finishMessage(_input, _prepared, outcome) { if (outcome.error) throw outcome.error; }
2551
+ };
2552
+ bindings.set(id, { sessionId: id, namespace: `main-${id}`, engine: "codex", runtime, identity,
2553
+ admission() {}, prepareInput: async input => input,
2554
+ applicationTools: { storage, prepareContext(context, admission) {
2555
+ mappings.push({ context, admission });
2556
+ return { ...context, boundAdmission: admission };
2557
+ } },
2558
+ read: async () => { const binding = await readBinding(); return { threadId: binding.threadId, configuration,
2559
+ run: binding.codexAppServerRun }; },
2560
+ readStream: () => Promise.resolve(streams.read(id)),
2561
+ state: { read: () => storage.read(id, transaction => transaction.readMetadata()).then(metadata => metadata.delivery),
2562
+ write: delivery => storage.write(id, async transaction => {
2563
+ const metadata = await transaction.readMetadata(); metadata.delivery = delivery; await transaction.writeMetadata(metadata);
2564
+ }) },
2565
+ transcript: { history: async () => [], readConversationLog: () => transcript.readConversationLog(id),
2566
+ hasMessage: messageId => transcript.conversationMessageIdExists(id, messageId),
2567
+ writeUserMessage: input => store.writeConversationUserMessage(id, input) },
2568
+ native: { runOwner: owner, providerOwner: {}, messagePreparation,
2569
+ admission: () => ({ ok: true, release() {} }),
2570
+ controlPreparation: async () => {
2571
+ const binding = await readBinding();
2572
+ return { runtime, session: await runtime.getSession(id), admissionError: () => null,
2573
+ threadId: () => binding.threadId, acquireProvider: async () => sharedProvider };
2574
+ },
2575
+ preparation: { cleanup: () => ({}) } }
2576
+ });
2577
+ }
2578
+ const runtime = createConversationRuntime({ engine: "codex", toolCatalog: catalog, authorize: () => true,
2579
+ host: { ...f.driverOptions.host, conversation: ({ id }) => bindings.get(id) } });
2580
+ boundRuntime = runtime;
2581
+ const one = await runtime.open({ id: "bound-one", context: actor });
2582
+ const two = await runtime.open({ id: "bound-two", context: actor });
2583
+ return { ...f, one, two, boundRuntime: runtime, sharedProvider, owner, effects, mappings, bindings, storage, catalog };
2584
+ }
2585
+
2586
+ test("two bound Codex tool threads share the original provider and retain exact receipts through independent Stop", async t => {
2587
+ const entered = Promise.withResolvers(), effect = Promise.withResolvers();
2588
+ let hold = true;
2589
+ const f = await boundCodexToolsFixture(t, { execute: async () => {
2590
+ if (hold) { entered.resolve(); return effect.promise; }
2591
+ return { value: 7 };
2592
+ } });
2593
+ await f.one.send({ messageId: "bound-operation-one", text: "tools" });
2594
+ await entered.promise;
2595
+ assert.equal(f.sharedProvider.threadRequestHandlers.size, 1);
2596
+ await f.two.send({ messageId: "bound-peer-wait", text: "wait" });
2597
+ assert.equal(f.sharedProvider.threadRequestHandlers.size, 2);
2598
+ let stopped = false;
2599
+ const stopping = f.one.cancel().then(() => { stopped = true; });
2600
+ await new Promise(resolve => setImmediate(resolve));
2601
+ assert.equal(stopped, false);
2602
+ effect.resolve({ value: 7 });
2603
+ await stopping;
2604
+ assert.equal((await f.two.read()).status, "working", "Stopping one exact thread retains its peer");
2605
+ assert.equal(f.sharedProvider.threadRequestHandlers.size, 1);
2606
+ await f.two.send({ messageId: "bound-peer-steer", text: "Continue", steer: true });
2607
+ await f.two.wait();
2608
+ assert.equal(f.sharedProvider.threadRequestHandlers.size, 0);
2609
+ hold = false;
2610
+ await f.two.send({ messageId: "bound-operation-two", text: "tools" });
2611
+ const second = await f.two.wait();
2612
+ const first = await f.one.wait();
2613
+ assert.equal(f.effects.length, 2);
2614
+ assert.deepEqual(f.effects.map(admission => admission.conversationId), ["bound-one", "bound-two"]);
2615
+ assert.deepEqual(f.effects.map(admission => admission.messageId), ["bound-operation-one", "bound-operation-two"]);
2616
+ assert.equal(first.conversationLog[0].metadata.applicationTools.length, 3);
2617
+ assert.equal(second.conversationLog.at(-1).metadata.applicationTools.length, 3);
2618
+ assert.equal(second.conversationLog.at(-1).metadata.applicationTools.at(-1).result.result.result.value, 7);
2619
+ const starts = (await f.trace()).filter(row => row.method === "thread/start");
2620
+ assert.equal(starts.length, 3, "Only the original seed and two actual native bound threads were created");
2621
+ assert.deepEqual(starts[1].params.dynamicTools, codexApplicationToolConfiguration({
2622
+ schemas: f.catalog.resolveToolSet({ actor: { id: "bound-owner" }, surface: "app", permissions: ["numbers.read"] }, { discoveryOnly: true }).tools.map(f.catalog.toOpenAiToolSchema)
2623
+ }).dynamicTools);
2624
+ assert.deepEqual(starts[2].params.dynamicTools, starts[1].params.dynamicTools);
2625
+ assert.equal((await f.trace()).filter(row => row.args).length, 1, "All threads retain the original account-shared app-server process");
2626
+ });
2627
+
2628
+ test("bound Codex refuses stale turns, foreign threads and retired handlers without executing their calls", async t => {
2629
+ const f = await boundCodexToolsFixture(t);
2630
+ await f.one.send({ messageId: "bound-old", text: "Hello" });
2631
+ await f.one.wait();
2632
+ const oldTurn = codexAppServerTurnState(await f.bindings.get("bound-one").runtime.getSession("bound-one"));
2633
+ await f.one.send({ messageId: "bound-current", text: "wait" });
2634
+ await f.two.send({ messageId: "bound-peer", text: "wait" });
2635
+ const threadId = await f.bindings.get("bound-one").identity.read();
2636
+ const handler = f.sharedProvider.threadRequestHandlers.get(threadId).handler;
2637
+ const request = { method: "item/tool/call", params: { threadId, turnId: oldTurn.turnId, callId: "stale-call",
2638
+ tool: "assistant_action_search", arguments: { query: "numbers" } } };
2639
+ f.sharedProvider.observationFailure = { cause: new Error("Unrelated retained observation failure") };
2640
+ try { await assert.rejects(handler(request), /another turn/); }
2641
+ finally { f.sharedProvider.observationFailure = null; }
2642
+ const peerTurn = codexAppServerTurnState(await f.bindings.get("bound-two").runtime.getSession("bound-two"));
2643
+ await assert.rejects(handler({ ...request, params: { ...request.params, turnId: peerTurn.turnId } }), /another turn/);
2644
+ await assert.rejects(handler({ ...request, params: { ...request.params, namespace: "unsupported" } }), /does not authorize/);
2645
+ const currentTurn = codexAppServerTurnState(await f.bindings.get("bound-one").runtime.getSession("bound-one"));
2646
+ const originalClient = f.sharedProvider.client;
2647
+ f.sharedProvider.client = {};
2648
+ try {
2649
+ await assert.rejects(handler({ ...request, params: { ...request.params, turnId: currentTurn.turnId } }), /does not authorize/);
2650
+ } finally { f.sharedProvider.client = originalClient; }
2651
+ assert.equal((await f.one.read()).status, "working");
2652
+ assert.equal((await f.two.read()).status, "working");
2653
+ assert.equal((await f.trace()).filter(row => row.method === "turn/interrupt").length, 0);
2654
+ await f.one.send({ messageId: "healthy-current", text: "Continue", steer: true });
2655
+ await f.two.send({ messageId: "healthy-peer", text: "Continue", steer: true });
2656
+ await f.one.wait();
2657
+ await f.two.wait();
2658
+ assert.equal(f.effects.length, 0);
2659
+ assert.equal(f.sharedProvider.threadRequestHandlers.size, 0);
2660
+ await assert.rejects(handler(request), /does not authorize/);
2661
+ await assert.rejects(f.sharedProvider.handleServerRequest({ ...request, params: { ...request.params, threadId: "foreign-thread" } }), /not supported/);
2662
+ assert.equal(f.effects.length, 0);
2663
+ });
2664
+
2665
+ test("shared Codex thread registrations retain ownership tokens and Helper request precedence", async t => {
2666
+ const provider = new CodexAppServerAgentProvider();
2667
+ const calls = [];
2668
+ let originalClientRequest;
2669
+ provider.client = { setRequestHandler(callback) { originalClientRequest = callback; }, close() {} };
2670
+ provider.setServerRequestHandler(async request => { calls.push("fallback"); return request.method; });
2671
+ const first = provider.registerThreadRequestHandler("first-thread", async () => "first");
2672
+ const peer = provider.registerThreadRequestHandler("peer-thread", async () => "peer");
2673
+ const request = threadId => ({ method: "item/tool/call", params: { threadId } });
2674
+ assert.equal(await provider.handleServerRequest(request("first-thread")), "first");
2675
+ assert.equal(await provider.handleServerRequest(request("peer-thread")), "peer");
2676
+ assert.equal(await provider.handleServerRequest({ method: "other", params: { threadId: "peer-thread" } }), "other");
2677
+ assert.throws(() => provider.registerThreadRequestHandler("peer-thread", () => {}), /already has/);
2678
+ first.release();
2679
+ const oldClientRequest = originalClientRequest;
2680
+ let replacementClientRequest;
2681
+ provider.client = { setRequestHandler(callback) { replacementClientRequest = callback; }, close() {} };
2682
+ const replacement = provider.registerThreadRequestHandler("first-thread", async () => "replacement");
2683
+ await assert.rejects(oldClientRequest(request("first-thread")), /retired Codex connection/);
2684
+ assert.equal(await replacementClientRequest(request("first-thread")), "replacement");
2685
+ assert.equal(await replacementClientRequest(request("peer-thread")), "peer");
2686
+ first.release();
2687
+ assert.equal(replacement.isCurrent(), true);
2688
+ assert.equal(peer.isCurrent(), true);
2689
+ t.mock.method(provider, "isHelperProvider", () => true);
2690
+ t.mock.method(provider, "refreshHelperChatgptAuth", async params => ({ refreshed: params }));
2691
+ await assert.rejects(provider.handleServerRequest(request("peer-thread")), /isolated helper/);
2692
+ assert.deepEqual(await provider.handleServerRequest({ method: "account/chatgptAuthTokens/refresh", params: { original: true } }), { refreshed: { original: true } });
2693
+ assert.deepEqual(await oldClientRequest({ method: "account/chatgptAuthTokens/refresh", params: { oldClient: true } }), { refreshed: { oldClient: true } });
2694
+ await assert.rejects(oldClientRequest(request("first-thread")), /isolated helper/);
2695
+ assert.deepEqual(calls, ["fallback"]);
2696
+ provider.close();
2697
+ assert.equal(replacement.isCurrent(), false);
2698
+ assert.equal(peer.isCurrent(), false);
2699
+ const newer = provider.registerThreadRequestHandler("first-thread", () => "newer");
2700
+ replacement.release();
2701
+ assert.equal(newer.isCurrent(), true);
2702
+ newer.release();
2703
+ });
2704
+
2705
+ test("bound Codex readiness publishes exact schemas before start/resume and rejects retained mismatches", async () => {
2706
+ const tools = { schemas: [{ type: "function", function: { name: "stable-tool", description: "Stable", parameters: { type: "object", properties: {} } } }] };
2707
+ const configuration = codexApplicationToolConfiguration(tools);
2708
+ let threadId = "", schemaIdentity, resumed = 0, started = 0;
2709
+ const events = [];
2710
+ const provider = { ensureAvailable: async () => ({ runtime: {} }),
2711
+ startThread: async params => { started++; assert.deepEqual(params.dynamicTools, configuration.dynamicTools); return { id: "exact-thread" }; },
2712
+ resumeThread: async (id, params) => { resumed++; assert.equal(id, threadId); assert.deepEqual(params.dynamicTools, configuration.dynamicTools); return { id }; } };
2713
+ const preparation = { provider, workdir: "/original", applicationTools: tools,
2714
+ settings: () => ({ threadSettings: { model: "original-model" }, threadStartSettings: { model: "original-model" } }),
2715
+ observeThread: id => events.push(["observe", id]), providerReady: value => events.push(["provider", value.threadId]),
2716
+ identity: { read: () => threadId, readToolSchemaIdentity: () => schemaIdentity,
2717
+ write: value => { threadId = value.threadId; schemaIdentity = value.toolSchemaIdentity; events.push(["write", threadId]); } } };
2718
+ await ensureCodexAppServerThread(preparation);
2719
+ assert.equal(schemaIdentity, configuration.toolSchemaIdentity);
2720
+ assert.deepEqual(events, [["provider", ""], ["write", "exact-thread"], ["provider", "exact-thread"]]);
2721
+ events.length = 0;
2722
+ await ensureCodexAppServerThread(preparation);
2723
+ assert.deepEqual(events, [["provider", "exact-thread"], ["observe", "exact-thread"], ["write", "exact-thread"], ["provider", "exact-thread"]]);
2724
+ schemaIdentity = "different-saved-schema";
2725
+ await assert.rejects(ensureCodexAppServerThread(preparation), /different application tool entry points/);
2726
+ assert.equal(resumed, 1);
2727
+ assert.equal(started, 1);
2728
+ assert.equal(threadId, "exact-thread");
2729
+ assert.equal(schemaIdentity, "different-saved-schema");
2730
+ delete preparation.identity.readToolSchemaIdentity;
2731
+ await assert.rejects(ensureCodexAppServerThread(preparation), /original schema identity/);
2732
+ assert.equal(resumed, 1);
2733
+ });
2734
+
2735
+ test("an owned bound tool failure interrupts only its native thread and preserves the peer", async t => {
2736
+ const f = await boundCodexToolsFixture(t, { execute: async () => { throw new Error("Owned action result unavailable"); } });
2737
+ await f.two.send({ messageId: "owned-failure-peer", text: "wait" });
2738
+ const peerThread = await f.bindings.get("bound-two").identity.read();
2739
+ await f.one.send({ messageId: "owned-failure", text: "tools" });
2740
+ const failed = await f.one.wait();
2741
+ const native = codexAppServerTurnState(await f.bindings.get("bound-one").runtime.getSession("bound-one"));
2742
+ assert.equal(native.active, false);
2743
+ assert.equal(f.effects.length, 1);
2744
+ assert.equal(failed.conversationLog[0].metadata.applicationTools.at(-1).status, "unknown");
2745
+ assert.equal((await f.two.read()).status, "working");
2746
+ assert.equal(f.sharedProvider.threadRequestHandlers.has(peerThread), true);
2747
+ const interrupts = (await f.trace()).filter(row => row.method === "turn/interrupt");
2748
+ assert.equal(interrupts.length, 1, "An owned executor failure must stop its admitted native turn");
2749
+ assert.equal(interrupts[0].params.threadId, await f.bindings.get("bound-one").identity.read());
2750
+ assert.notEqual(interrupts[0].params.threadId, peerThread);
2751
+ await f.two.send({ messageId: "owned-failure-peer-finish", text: "Continue", steer: true });
2752
+ await f.two.wait();
2753
+ });
2754
+
2755
+
2756
+ test("closing a bound native owner refuses tool callbacks before and after input readiness and drains cleanup", async t => {
2757
+ const entered = Promise.withResolvers(), release = Promise.withResolvers();
2758
+ const f = await boundCodexToolsFixture(t, { prepareMessage: async (_input, { starting }) => {
2759
+ if (!starting) { entered.resolve(); await release.promise; throw new Error("Held steering refused"); }
2760
+ } });
2761
+ await f.one.send({ messageId: "closing-owned", text: "wait" });
2762
+ await f.two.send({ messageId: "closing-peer", text: "wait" });
2763
+ // This direct-provider host has no providerSessions lifecycle. Supply only
2764
+ // its controlled shutdown flag, read by the ORIGINAL notification queue.
2765
+ let closing = false;
2766
+ f.owner.runtimeLifecycle = { get closing() { return closing; } };
2767
+ const native = codexAppServerTurnState(await f.bindings.get("bound-one").runtime.getSession("bound-one"));
2768
+ const handler = f.sharedProvider.threadRequestHandlers.get(native.threadId).handler;
2769
+ const request = { method: "item/tool/call", params: { threadId: native.threadId, turnId: native.turnId,
2770
+ callId: "closing-call", tool: "assistant_action_search", arguments: { query: "numbers" } } };
2771
+ closing = true;
2772
+ try { await assert.rejects(handler(request), /does not authorize/); }
2773
+ finally { closing = false; }
2774
+ const steering = assert.rejects(f.one.send({ messageId: "closing-held-steer", text: "Continue", steer: true }), /Held steering refused/);
2775
+ await entered.promise;
2776
+ const heldTool = assert.rejects(handler(request), /does not authorize/);
2777
+ await new Promise(resolve => setImmediate(resolve));
2778
+ closing = true;
2779
+ try {
2780
+ release.resolve();
2781
+ await steering;
2782
+ await heldTool;
2783
+ } finally { closing = false; release.resolve(); }
2784
+ assert.equal(f.effects.length, 0);
2785
+ assert.equal((await f.one.read()).status, "working");
2786
+ assert.equal((await f.two.read()).status, "working");
2787
+ await f.one.cancel();
2788
+ await f.two.cancel();
2789
+ await f.owner.notificationQueue.drain("bound-one");
2790
+ await f.owner.notificationQueue.drain("bound-two");
2791
+ assert.equal(f.sharedProvider.threadRequestHandlers.size, 0);
2792
+ });
@@ -645,3 +645,106 @@ test("the real adapter forwards selected GPT compaction and response requests wi
645
645
  assert.equal(calls.length, accepted, "Unsupported paired history must fail before provider admission.");
646
646
  assert.deepEqual(body, saved);
647
647
  });
648
+
649
+ test("recovery excludes only exact native retained user copies with occurrence multiplicity", async t => {
650
+ const fixture = await compactedFixture(t);
651
+ const message = (text, id) => ({ type: "message", role: "user", id, content: [{ type: "input_text", text }] });
652
+ const repeated = message("Exact repeated words.\n Keep spacing.", "old-one");
653
+ const second = { ...repeated, id: "old-two", encrypted_content: "native-only" };
654
+ const third = { ...repeated, id: "old-three" };
655
+ const changed = message("Exact repeated words.\n Keep spacing.", "changed");
656
+ const tool = { type: "function_call_output", call_id: "fact", output: "PRESERVE_TOOL_FACT" };
657
+ await fixture.save([fixture.records[0], ...[repeated, tool, second, third, changed].map(payload => ({ type: "response_item", payload })),
658
+ { type: "compacted", payload: { replacement_history: [repeated, second, fixture.compacted] } }]);
659
+ const native = await readFile(fixture.historyPath);
660
+ const retained = { content: repeated.content, role: "user", type: "message", id: "wire-copy" };
661
+ const after = { ...repeated, id: "new-turn" };
662
+ for (const copies of [1, 2]) {
663
+ const body = { model: "deepseek-flash", input: [...Array.from({ length: copies }, () => retained), fixture.compacted, after] };
664
+ const saved = structuredClone(body);
665
+ assert.equal(await (await fixture.send(body)).text(), "accepted");
666
+ const input = fixture.calls.at(-1).body.input;
667
+ const content = input[copies + 1].content[0].text;
668
+ const archived = JSON.parse(content.slice(content.indexOf("\n") + 1));
669
+ const normalized = ({ id, encrypted_content, ...record }) => record;
670
+ const expected = [repeated, tool, second, third, changed].filter((record, index) => index !== 0 && !(copies === 2 && index === 2)).map(normalized);
671
+ assert.deepEqual(archived, expected);
672
+ assert.deepEqual(input.slice(0, copies + 1), body.input.slice(0, copies + 1));
673
+ assert.deepEqual(input.at(-1), after);
674
+ assert.deepEqual(body, saved);
675
+ }
676
+ assert.deepEqual(await readFile(fixture.historyPath), native);
677
+ assert.equal(fixture.calls.length, 2);
678
+ });
679
+
680
+ test("recovery retains altered user images and unmatched content without duplicating retained screenshots", async t => {
681
+ const fixture = await compactedFixture(t);
682
+ const image = { type: "input_image", image_url: "data:image/png;base64,original", detail: "original" };
683
+ const user = { type: "message", role: "user", content: [{ type: "input_text", text: "Inspect this." }, image] };
684
+ const altered = { ...user, content: [user.content[0], { ...image, detail: "high" }] };
685
+ const assistant = { type: "message", role: "assistant", content: [{ type: "output_text", text: "Inspect this." }] };
686
+ await fixture.save([fixture.records[0], ...[user, altered, assistant].map(payload => ({ type: "response_item", payload })),
687
+ { type: "compacted", payload: { replacement_history: [user, fixture.compacted] } }]);
688
+ const native = await readFile(fixture.historyPath);
689
+ assert.equal(await (await fixture.send({ model: "deepseek-flash", input: [user, fixture.compacted] })).text(), "accepted");
690
+ const content = fixture.calls[0].body.input[2].content;
691
+ const archived = JSON.parse(content[0].text.slice(content[0].text.indexOf("\n") + 1));
692
+ assert.deepEqual(archived, [{ ...altered, content: [user.content[0], { type: "input_image", archived_image: 1 }] }, assistant]);
693
+ assert.deepEqual(content.filter(part => part.type === "input_image"), [altered.content[1]]);
694
+ assert.deepEqual(fixture.calls[0].body.input[0], user);
695
+ assert.deepEqual(await readFile(fixture.historyPath), native);
696
+ });
697
+
698
+ test("fully retained readable history omits the supplement while later matching words remain a new turn", async t => {
699
+ const fixture = await compactedFixture(t);
700
+ const user = { type: "message", role: "user", content: [{ type: "input_text", text: "Do the approved work." }] };
701
+ await fixture.save([fixture.records[0], { type: "response_item", payload: user },
702
+ { type: "compacted", payload: { replacement_history: [user, fixture.compacted] } }]);
703
+ const native = await readFile(fixture.historyPath);
704
+ const body = { model: "deepseek-flash", input: [user, fixture.compacted] };
705
+ assert.equal(await (await fixture.send(body)).text(), "accepted");
706
+ assert.deepEqual(fixture.calls[0].body, body);
707
+ assert.equal(await (await fixture.send({ ...body, input: [fixture.compacted, user] })).text(), "accepted");
708
+ assert.match(fixture.calls[1].body.input[1].content[0].text, /Do the approved work/);
709
+ assert.deepEqual(fixture.calls[1].body.input.at(-1), user);
710
+ assert.deepEqual(await readFile(fixture.historyPath), native);
711
+ });
712
+
713
+ test("DeepSeek exact numeric context rejection exposes native recovery code without retry or other error changes", async t => {
714
+ const message = "This model's maximum context length is 1048576 tokens. However, you requested 1084989 tokens (1084989 in the messages, 0 in the completion). Please reduce the length of the messages or completion. (request_id: a7d102f7-6c5f-4125-b76c-de721745dff7)";
715
+ const error = { message, type: "invalid_request_error", param: null, code: "invalid_request_error" };
716
+ let upstreamBody;
717
+ let upstreamStatus = 400;
718
+ let attempts = 0;
719
+ const adapter = await startCodexHistoryAdapter({ token: randomUUID(), fetchImpl: async () => {
720
+ attempts += 1;
721
+ return new Response(upstreamBody, { status: upstreamStatus, headers: { "content-type": "application/json", "x-request-id": "original-id" } });
722
+ } });
723
+ t.after(() => adapter.close());
724
+ for (const route of ["responses", "responses/compact"]) {
725
+ upstreamBody = JSON.stringify({ error, detail: "preserved" });
726
+ const response = await fetch(`${adapter.baseUrl}/deepseek/${route}`, { method: "POST", body: '{"input":[]}' });
727
+ assert.equal(response.status, 400);
728
+ assert.equal(response.headers.get("x-request-id"), "original-id");
729
+ assert.deepEqual(await response.json(), { error: { ...error, code: "context_length_exceeded" }, detail: "preserved" });
730
+ }
731
+ const cases = [
732
+ ["chatgpt", error, 400], ["zai", error, 400], ["deepseek", error, 429],
733
+ ["deepseek", { ...error, code: "other_error" }, 400],
734
+ ["deepseek", { ...error, type: "other_error" }, 400],
735
+ ["deepseek", { ...error, message: message.replace("1084989 in", "1084988 in") }, 400],
736
+ ["deepseek", { ...error, message: message.replace("1048576 tokens", "2084989 tokens") }, 400],
737
+ ["deepseek", { ...error, message: message + " additional detail" }, 400],
738
+ ["deepseek", { ...error, message: "Context invalid for a different reason." }, 400]
739
+ ];
740
+ for (const [destination, detail, status] of cases) {
741
+ upstreamStatus = status;
742
+ upstreamBody = ` { "error": ${JSON.stringify(detail)} }\n`;
743
+ const before = attempts;
744
+ const response = await fetch(`${adapter.baseUrl}/${destination}/responses`, { method: "POST", body: '{"input":[]}' });
745
+ assert.equal(response.status, status);
746
+ assert.equal(await response.text(), upstreamBody);
747
+ assert.equal(attempts, before + 1);
748
+ }
749
+ assert.equal(attempts, cases.length + 2, "exactly one upstream admission per request");
750
+ });