@jskit-ai/assistant-core 0.1.240 → 0.1.241

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.
Files changed (101) hide show
  1. package/examples/conversation/package.json +5 -5
  2. package/package.json +5 -5
  3. package/src/client/conversation/AssistantTranscript.vue +26 -29
  4. package/src/client/conversation/LongTextPreviewBlocks.vue +11 -0
  5. package/src/client/conversation/messageDelivery.js +8 -2
  6. package/src/client/conversation/useAssistantAttachments.js +1 -1
  7. package/src/server/conversation/agentRun.js +12 -198
  8. package/src/server/conversation/attachments.js +6 -5
  9. package/src/server/conversation/boundedToolLoop.js +160 -0
  10. package/src/server/conversation/claudeHistory.js +20 -16
  11. package/src/server/conversation/claudeProcess.js +2 -2
  12. package/src/server/conversation/claudeTurn.js +41 -38
  13. package/src/server/conversation/codexAuthPreflight.js +104 -0
  14. package/src/server/conversation/codexClient.js +11 -16
  15. package/src/server/conversation/codexConfiguration.js +112 -98
  16. package/src/server/conversation/codexConversationStore.js +189 -0
  17. package/src/server/conversation/codexConversations.js +47 -44
  18. package/src/server/conversation/codexDetachedTurn.js +12 -11
  19. package/src/server/conversation/codexEvents.js +95 -99
  20. package/src/server/conversation/codexHelperExecution.js +3 -13
  21. package/src/server/conversation/codexHelperThreadLedger.js +7 -13
  22. package/src/server/conversation/codexHelperThreadRecovery.js +2 -16
  23. package/src/server/conversation/codexHelperThreads.js +5 -6
  24. package/src/server/conversation/codexHistoryAdapter.js +32 -26
  25. package/src/server/conversation/codexMessageCommands.js +22 -22
  26. package/src/server/conversation/codexMessageDelivery.js +12 -7
  27. package/src/server/conversation/codexModelCatalog.js +97 -0
  28. package/src/server/conversation/codexNotificationObserver.js +24 -40
  29. package/src/server/conversation/codexNotificationQueue.js +2 -3
  30. package/src/server/conversation/codexOutput.js +20 -27
  31. package/src/server/conversation/codexProcess.js +123 -127
  32. package/src/server/conversation/codexProcessLeader.js +3 -3
  33. package/src/server/conversation/codexProvider.js +31 -614
  34. package/src/server/conversation/codexProviderOwner.js +2 -2
  35. package/src/server/conversation/codexProviderSelection.js +38 -40
  36. package/src/server/conversation/codexRenewalCommands.js +13 -10
  37. package/src/server/conversation/codexRenewalThreads.js +412 -0
  38. package/src/server/conversation/codexRunJournal.js +24 -42
  39. package/src/server/conversation/codexRuntimeLifecycle.js +19 -15
  40. package/src/server/conversation/codexSessionCleanup.js +11 -10
  41. package/src/server/conversation/codexSessionReconciliation.js +6 -6
  42. package/src/server/conversation/codexThreadReadiness.js +19 -33
  43. package/src/server/conversation/codexThreadStatus.js +3 -3
  44. package/src/server/conversation/codexTurn.js +124 -297
  45. package/src/server/conversation/codexTurnRecovery.js +49 -28
  46. package/src/server/conversation/codexTurnState.js +34 -13
  47. package/src/server/conversation/commandWrapper.js +3 -0
  48. package/src/server/conversation/deliveryTiming.js +14 -5
  49. package/src/server/conversation/index.js +1 -0
  50. package/src/server/conversation/localExecution.js +13 -17
  51. package/src/server/conversation/normalize.js +8 -3
  52. package/src/server/conversation/openCodeClient.js +9 -10
  53. package/src/server/conversation/openCodeProcess.js +9 -5
  54. package/src/server/conversation/openCodeRuntime.js +10 -11
  55. package/src/server/conversation/openCodeTurn.js +43 -29
  56. package/src/server/conversation/providers/claudeDriver.js +8 -16
  57. package/src/server/conversation/providers/codexDriver.js +25 -41
  58. package/src/server/conversation/providers/opencodeDriver.js +17 -42
  59. package/src/server/conversation/providers/scoped.js +48 -4
  60. package/src/server/conversation/runtime.js +50 -49
  61. package/src/server/conversation/runtimeStateUpgrade.js +34 -22
  62. package/src/server/conversation/transcript.js +10 -10
  63. package/src/server/hosts/codexConfiguration.js +1 -0
  64. package/src/server/hosts/codexProvider.js +8 -6
  65. package/src/server/index.js +2 -1
  66. package/src/server/lib/assistantToolLoop.js +1 -153
  67. package/src/server/lib/serviceToolCatalog.js +17 -42
  68. package/src/shared/assistantResource.js +4 -2
  69. package/src/shared/conversation/attachments.js +4 -0
  70. package/src/shared/conversation/longTextBlocks.js +54 -19
  71. package/src/shared/conversation/numberedQuestionSugar.js +17 -10
  72. package/src/shared/conversation/pagination.js +4 -6
  73. package/src/shared/conversation/patches.js +1 -4
  74. package/src/shared/conversation/streaming.js +3 -11
  75. package/src/shared/conversation/turns.js +11 -0
  76. package/src/shared/index.js +1 -0
  77. package/test/assistantBoundedToolLoop.test.js +2 -2
  78. package/test/assistantResource.test.js +4 -4
  79. package/test/assistantSettingsResource.test.js +2 -2
  80. package/test/claudeConversationSession.test.js +11 -1
  81. package/test/claudeTurn.test.js +27 -7
  82. package/test/codexClient.test.js +20 -0
  83. package/test/codexConversationSession.test.js +2 -2
  84. package/test/codexConversationsCheckpoint.test.js +51 -0
  85. package/test/codexModelCatalog.test.js +1 -1
  86. package/test/codexProviderHome.test.js +51 -0
  87. package/test/codexProviderSelection.test.js +71 -0
  88. package/test/codexTurn.test.js +222 -6
  89. package/test/conversationAgentRun.test.js +2 -1
  90. package/test/conversationAttachments.test.js +27 -2
  91. package/test/conversationDeliveryTiming.test.js +21 -0
  92. package/test/conversationNormalize.test.js +11 -0
  93. package/test/fixtures/nestedListQuestionMessage.md +37 -0
  94. package/test/frameworkActions.test.js +2 -2
  95. package/test/longTextBlocks.test.js +102 -0
  96. package/test/messageDelivery.test.js +7 -1
  97. package/test/openCodeClient.test.js +20 -0
  98. package/test/openCodeRuntime.test.js +1 -1
  99. package/test/openCodeTurn.test.js +90 -0
  100. package/test/opencodeConversationSession.test.js +1 -1
  101. package/test/serviceToolCatalog.test.js +27 -0
@@ -8,21 +8,24 @@ import {
8
8
  } from "./codexConfiguration.js";
9
9
  import {
10
10
  CODEX_APP_SERVER_PROCESS_STATE, CODEX_APP_SERVER_TRANSPORT,
11
- assertExistingDirectory, codexAppServerCommandBaseEnv, codexAppServerCredentialHome,
12
11
  codexAppServerExecutionMode, codexAppServerHelperWorkspaceDir, codexAppServerIsHelper,
13
- codexAppServerProcessCwd, codexAppServerRuntimeIdentity, codexAppServerTerminalEnvHash,
12
+ codexAppServerRuntimeIdentity, codexAppServerTerminalEnvHash,
14
13
  ensureWritablePrivateDirectory, normalizeCodexAppServerTerminalEnv, tailTextFile
15
14
  } from "./codexProcess.js";
15
+ import { assertCodexAuthPreflightReady } from "./codexAuthPreflight.js";
16
+ import { codexAppServerModelCatalogError } from "./codexModelCatalog.js";
16
17
  import { prepareCodexHistory } from "./codexHistoryAdapter.js";
17
18
  import { createCodexConversationAdapter } from "./providers/codex.js";
18
19
  import { exportCodexNativeHistory } from "./codexNativeHistoryExport.js";
19
20
  import { retireNativeConversation, readStoppedNativeDatabase } from "./nativeHistoryExport.js";
20
- import { isPlainObject, normalizeText as normalizeAgentText } from "./normalize.js";
21
+ import { isRecord, normalizePositiveInteger } from "@jskit-ai/kernel/shared/support/normalize";
22
+ import { normalizeNullableText as normalizeAgentText } from "./normalize.js";
21
23
  import {
22
24
  codexAppServerProviderThreadTurns,
23
25
  codexAppServerProviderTurnItems,
24
26
  codexAppServerProviderTurnClientIds,
25
- codexAppServerThreadRawValue
27
+ codexAppServerThreadRawValue,
28
+ codexAppServerErrorText
26
29
  } from "./codexEvents.js";
27
30
 
28
31
  const CODEX_APP_SERVER_PROVIDER_ID = "codex_app_server";
@@ -30,8 +33,6 @@ const CODEX_APP_SERVER_EXECUTION_MODES = Object.freeze({ HELPER: "helper", INTER
30
33
  const codexAppServerHelperAuthError = createCodexAccountReader().error;
31
34
 
32
35
  const CODEX_APP_SERVER_INVALID_REQUEST_CODE = -32600;
33
- const CODEX_RENEWAL_UNMATERIALIZED_THREAD_SUFFIX =
34
- "is not materialized yet; includeTurns is unavailable before first user message";
35
36
  const CODEX_APP_SERVER_MODEL_CATALOG_PAGE_LIMIT = 100;
36
37
  const CODEX_APP_SERVER_MODEL_CATALOG_MAX_PAGES = 100;
37
38
  const CODEX_APP_SERVER_MODEL_CATALOG_MAX_ENTRIES = 1000;
@@ -40,23 +41,16 @@ const CODEX_APP_SERVER_MODEL_CATALOG_MAX_TOTAL_BYTES = 2 * 1024 * 1024;
40
41
  const CODEX_APP_SERVER_MODEL_CATALOG_MAX_CURSOR_LENGTH = 512;
41
42
  const CODEX_APP_SERVER_THREAD_INVENTORY_PAGE_LIMIT = 100;
42
43
  const CODEX_APP_SERVER_THREAD_INVENTORY_MAX_PAGES = 100;
43
- const CODEX_APP_SERVER_THREAD_INVENTORY_MAX_COUNT = 1000;
44
- const CODEX_APP_SERVER_THREAD_INVENTORY_ID_MAX_LENGTH = 512;
44
+ export const CODEX_APP_SERVER_THREAD_INVENTORY_MAX_COUNT = 1000;
45
+ export const CODEX_APP_SERVER_THREAD_INVENTORY_ID_MAX_LENGTH = 512;
45
46
  const CODEX_APP_SERVER_THREAD_INVENTORY_MAX_ENTRY_BYTES = 64 * 1024;
46
47
  const CODEX_APP_SERVER_THREAD_INVENTORY_MAX_TOTAL_BYTES = 2 * 1024 * 1024;
47
48
  const CODEX_APP_SERVER_HELPER_MAX_MESSAGE_BYTES = 4 * 1024 * 1024;
48
49
  const CODEX_APP_SERVER_SERVER_INFO_USER_AGENT_MAX_LENGTH = 512;
49
- const CODEX_AUTH_PREFLIGHT_TIMEOUT_MS = 15000;
50
- const CODEX_AUTH_PREFLIGHT_OUTPUT_TAIL_BYTES = 4096;
51
50
  const CODEX_APP_SERVER_CHATGPT_REFRESH_METHOD = "account/chatgptAuthTokens/refresh";
52
51
  const CODEX_NATIVE_STORAGE_SOURCE_KINDS = ["cli", "vscode", "exec", "appServer", "subAgent", "subAgentReview",
53
52
  "subAgentCompact", "subAgentThreadSpawn", "subAgentOther", "unknown"];
54
53
 
55
- function normalizePositiveInteger(value, fallback) {
56
- const number = Number(value);
57
- return Number.isSafeInteger(number) && number > 0 ? number : fallback;
58
- }
59
-
60
54
  function codexAppServerRequestIsInvalid(error = null, method = "") {
61
55
  const expectedMethod = normalizeAgentText(method);
62
56
  const actualMethod = normalizeAgentText(error?.method);
@@ -206,7 +200,7 @@ async function inspectCodexAppServerMessageAdmission({ provider, threadId, messa
206
200
  // proof and durable cleanup receipt.
207
201
  async function deleteCodexAppServerThread({ provider, threadId } = {}) {
208
202
  const result = await provider.deleteThread(threadId);
209
- if (!isPlainObject(result)) {
203
+ if (!isRecord(result)) {
210
204
  const error = new Error("Codex app-server returned an invalid thread deletion result.");
211
205
  error.code = "codex_helper_thread_delete_unconfirmed";
212
206
  throw error;
@@ -354,501 +348,6 @@ async function ensureCodexAppServerThread({
354
348
  };
355
349
  }
356
350
 
357
- function defineCodexRenewalThreadIds(value = [], {
358
- errorCode = "codex_renewal_fresh_thread_required"
359
- } = {}) {
360
- if (
361
- !Array.isArray(value) ||
362
- value.length > CODEX_APP_SERVER_THREAD_INVENTORY_MAX_COUNT
363
- ) {
364
- throw codexRenewalThreadError(
365
- errorCode,
366
- "The successor assistant thread inventory is invalid."
367
- );
368
- }
369
- const threadIds = value.map((threadId) => normalizeAgentText(threadId));
370
- if (
371
- threadIds.some((threadId) => (
372
- !threadId ||
373
- threadId.length > CODEX_APP_SERVER_THREAD_INVENTORY_ID_MAX_LENGTH ||
374
- codexAppServerTextHasControlCharacters(threadId)
375
- )) ||
376
- new Set(threadIds).size !== threadIds.length
377
- ) {
378
- throw codexRenewalThreadError(
379
- errorCode,
380
- "The successor assistant thread inventory contains an invalid identity."
381
- );
382
- }
383
- return Object.freeze([...threadIds].sort());
384
- }
385
-
386
- async function listCodexRenewalThreadIds(provider, workdir, errorCode) {
387
- if (typeof provider?.listAppServerThreadsForCwd !== "function") {
388
- throw codexRenewalThreadError(
389
- errorCode,
390
- "The assistant provider cannot inventory the successor's exact session threads."
391
- );
392
- }
393
- const normalizedWorkdir = normalizeAgentText(workdir);
394
- const inventory = await provider.listAppServerThreadsForCwd({
395
- cwd: normalizedWorkdir
396
- });
397
- if (normalizeAgentText(inventory?.cwd) !== normalizedWorkdir) {
398
- throw codexRenewalThreadError(
399
- errorCode,
400
- "The assistant provider returned a thread inventory for a different session source."
401
- );
402
- }
403
- return defineCodexRenewalThreadIds(inventory?.threadIds, { errorCode });
404
- }
405
-
406
- function assertCodexRenewalThreadSnapshot(threadSnapshot = null, {
407
- threadId = "",
408
- workdir = "",
409
- errorCode
410
- } = {}) {
411
- const expectedThreadId = normalizeAgentText(threadId);
412
- const expectedWorkdir = normalizeAgentText(workdir);
413
- const actualThreadId = codexAppServerThreadResponseId(threadSnapshot);
414
- const actualWorkdir = normalizeAgentText(
415
- threadSnapshot?.cwd ||
416
- threadSnapshot?.raw?.cwd ||
417
- threadSnapshot?.response?.thread?.cwd
418
- );
419
- if (actualThreadId !== expectedThreadId || actualWorkdir !== expectedWorkdir) {
420
- throw codexRenewalThreadError(
421
- errorCode,
422
- "The assistant provider could not verify the exact successor thread and session source.",
423
- {
424
- actualThreadId,
425
- actualWorkdir,
426
- expectedThreadId,
427
- expectedWorkdir
428
- }
429
- );
430
- }
431
- return threadSnapshot;
432
- }
433
-
434
- function codexRenewalThreadNeedsStatusRead(error = null, threadId = "") {
435
- const normalizedThreadId = normalizeAgentText(threadId);
436
- return Boolean(
437
- normalizedThreadId &&
438
- (
439
- (
440
- codexAppServerRequestIsInvalid(error, "thread/read") &&
441
- normalizeAgentText(error?.message) ===
442
- `thread ${normalizedThreadId} ${CODEX_RENEWAL_UNMATERIALIZED_THREAD_SUFFIX}`
443
- ) ||
444
- (
445
- Number(error?.code) === -32601 &&
446
- normalizeAgentText(error?.method) === "thread/read"
447
- )
448
- )
449
- );
450
- }
451
-
452
- async function readCodexRenewalSuccessorThreadSnapshot({
453
- provider,
454
- threadId = "",
455
- workdir = "",
456
- errorCode
457
- } = {}) {
458
- try {
459
- return assertCodexRenewalThreadSnapshot(
460
- await provider.readThread(threadId),
461
- { threadId, workdir, errorCode }
462
- );
463
- } catch (error) {
464
- if (
465
- !codexRenewalThreadNeedsStatusRead(error, threadId) ||
466
- typeof provider?.readThreadStatus !== "function"
467
- ) {
468
- throw error;
469
- }
470
- return assertCodexRenewalThreadSnapshot(
471
- await provider.readThreadStatus(threadId),
472
- { threadId, workdir, errorCode }
473
- );
474
- }
475
- }
476
-
477
- function codexRenewalThreadError(code, message, details = {}, {
478
- retryable = false
479
- } = {}) {
480
- const error = new Error(message);
481
- error.code = code;
482
- error.details = {
483
- ...details,
484
- retryable
485
- };
486
- error.retryable = retryable;
487
- return error;
488
- }
489
-
490
- function codexAppServerThreadResponseId(thread = null, fallback = "") {
491
- return normalizeAgentText(
492
- thread?.id ||
493
- thread?.response?.thread?.id ||
494
- fallback
495
- );
496
- }
497
-
498
- /** Resume only the supplied retained thread; native read failures never replace it. */
499
- async function resumeExactCodexAppServerThread({
500
- expectedThreadId = "",
501
- provider,
502
- settings,
503
- projectHooks = false,
504
- workdir = "",
505
- errorCode = "codex_renewal_thread_unreadable"
506
- } = {}) {
507
- const normalizedWorkdir = normalizeAgentText(workdir);
508
- const normalizedExpectedThreadId = normalizeAgentText(expectedThreadId);
509
- if (
510
- typeof provider?.ensureRuntime !== "function" ||
511
- typeof provider?.resumeThread !== "function" ||
512
- typeof provider?.readThread !== "function"
513
- ) {
514
- throw codexRenewalThreadError(
515
- errorCode,
516
- "The old assistant provider cannot read its exact main thread.",
517
- { expectedThreadId: normalizedExpectedThreadId }
518
- );
519
- }
520
- const appServerRuntime = await provider.ensureRuntime();
521
- const config = projectHooks
522
- ? await codexAppServerProjectHookTrustConfig(provider, normalizedWorkdir)
523
- : null;
524
- const { threadSettings } = await settings(normalizedWorkdir, config);
525
- let thread = null;
526
- let threadSnapshot = null;
527
- try {
528
- thread = await provider.resumeThread(normalizedExpectedThreadId, threadSettings);
529
- const resumedThreadId = codexAppServerThreadResponseId(thread, normalizedExpectedThreadId);
530
- if (resumedThreadId !== normalizedExpectedThreadId) {
531
- throw codexRenewalThreadError(
532
- errorCode,
533
- "The old assistant provider resumed a different thread.",
534
- {
535
- expectedThreadId: normalizedExpectedThreadId,
536
- resumedThreadId
537
- }
538
- );
539
- }
540
- threadSnapshot = await provider.readThread(normalizedExpectedThreadId);
541
- } catch (error) {
542
- if (
543
- error?.code === errorCode ||
544
- codexAppServerRequestIsInvalid(error, "thread/resume") ||
545
- codexAppServerRequestIsInvalid(error, "thread/read")
546
- ) {
547
- if (error?.code === errorCode) {
548
- throw error;
549
- }
550
- throw codexRenewalThreadError(
551
- errorCode,
552
- "The old assistant thread cannot be read. Write or edit the handover manually instead.",
553
- {
554
- expectedThreadId: normalizedExpectedThreadId,
555
- providerError: normalizeAgentText(error?.message)
556
- }
557
- );
558
- }
559
- throw error;
560
- }
561
- return Object.freeze({
562
- appServerRuntime,
563
- thread,
564
- threadId: normalizedExpectedThreadId,
565
- threadSnapshot,
566
- threadSettings
567
- });
568
- }
569
-
570
- /** Preserve the original baseline → create/adopt → durable identity → first-read order. */
571
- async function startFreshCodexAppServerThread({
572
- provider,
573
- settings,
574
- projectHooks = false,
575
- identity,
576
- resumableThreadId = "",
577
- forbiddenThreadId = "",
578
- operationId = "",
579
- workdir = "",
580
- errorCode = "codex_renewal_fresh_thread_required",
581
- applicationName = "The application"
582
- } = {}) {
583
- const normalizedWorkdir = normalizeAgentText(workdir);
584
- const normalizedForbiddenThreadId = normalizeAgentText(forbiddenThreadId);
585
- const normalizedOperationId = normalizeAgentText(operationId);
586
- if (resumableThreadId && resumableThreadId === normalizedForbiddenThreadId) {
587
- throw codexRenewalThreadError(
588
- errorCode,
589
- "The renewed session cannot reuse the old session's assistant thread.",
590
- { forbiddenThreadId: normalizedForbiddenThreadId }
591
- );
592
- }
593
- if (
594
- typeof provider?.ensureRuntime !== "function" ||
595
- typeof provider?.resumeThread !== "function" ||
596
- typeof provider?.readThread !== "function" ||
597
- typeof provider?.startThread !== "function"
598
- ) {
599
- throw codexRenewalThreadError(
600
- errorCode,
601
- "The assistant provider cannot prove a genuinely fresh renewal thread."
602
- );
603
- }
604
- const appServerRuntime = await provider.ensureRuntime();
605
- const config = projectHooks
606
- ? await codexAppServerProjectHookTrustConfig(provider, normalizedWorkdir)
607
- : null;
608
- const { threadSettings, threadStartSettings } = await settings(normalizedWorkdir, config);
609
- let fresh = false;
610
- let thread = null;
611
- let threadSnapshot = null;
612
- let baselineThreadIds = Object.freeze([]);
613
- if (resumableThreadId) {
614
- try {
615
- thread = await provider.resumeThread(resumableThreadId, threadSettings);
616
- const resumedThreadId = codexAppServerThreadResponseId(thread, resumableThreadId);
617
- if (resumedThreadId !== resumableThreadId) {
618
- throw codexRenewalThreadError(
619
- errorCode,
620
- "The assistant provider resumed a different renewal thread.",
621
- {
622
- expectedThreadId: resumableThreadId,
623
- resumedThreadId
624
- }
625
- );
626
- }
627
- threadSnapshot = await readCodexRenewalSuccessorThreadSnapshot({
628
- provider,
629
- threadId: resumableThreadId,
630
- workdir: normalizedWorkdir,
631
- errorCode
632
- });
633
- } catch (error) {
634
- if (
635
- error?.code === errorCode ||
636
- codexAppServerRequestIsInvalid(error, "thread/resume") ||
637
- codexAppServerRequestIsInvalid(error, "thread/read")
638
- ) {
639
- if (error?.code === errorCode) {
640
- throw error;
641
- }
642
- throw codexRenewalThreadError(
643
- errorCode,
644
- `The previously started renewal thread is no longer readable; ${applicationName} will not replace it silently.`,
645
- {
646
- expectedThreadId: resumableThreadId,
647
- providerError: normalizeAgentText(error?.message)
648
- }
649
- );
650
- }
651
- throw error;
652
- }
653
- } else {
654
- if (!normalizedOperationId) {
655
- throw codexRenewalThreadError(
656
- errorCode,
657
- "Starting a fresh successor thread requires its exact renewal operation id."
658
- );
659
- }
660
- baselineThreadIds = await identity.readBaseline();
661
- if (!baselineThreadIds) {
662
- baselineThreadIds = await listCodexRenewalThreadIds(
663
- provider,
664
- normalizedWorkdir,
665
- errorCode
666
- );
667
- await identity.writeBaseline(baselineThreadIds);
668
- }
669
- const currentThreadIds = await listCodexRenewalThreadIds(
670
- provider,
671
- normalizedWorkdir,
672
- errorCode
673
- );
674
- const baseline = new Set(baselineThreadIds);
675
- const candidateThreadIds = currentThreadIds.filter((threadId) => !baseline.has(threadId));
676
- if (candidateThreadIds.length > 1) {
677
- throw codexRenewalThreadError(
678
- errorCode,
679
- `More than one unclaimed assistant thread appeared for this renewal; ${applicationName} will not guess which one is authoritative.`,
680
- { candidateThreadIds }
681
- );
682
- }
683
- if (candidateThreadIds.length === 1) {
684
- const [candidateThreadId] = candidateThreadIds;
685
- thread = await provider.resumeThread(candidateThreadId, threadSettings);
686
- const resumedThreadId = codexAppServerThreadResponseId(thread, candidateThreadId);
687
- if (resumedThreadId !== candidateThreadId) {
688
- throw codexRenewalThreadError(
689
- errorCode,
690
- "The assistant provider resumed a different recovered renewal thread.",
691
- {
692
- expectedThreadId: candidateThreadId,
693
- resumedThreadId
694
- }
695
- );
696
- }
697
- threadSnapshot = await readCodexRenewalSuccessorThreadSnapshot({
698
- provider,
699
- threadId: candidateThreadId,
700
- workdir: normalizedWorkdir,
701
- errorCode
702
- });
703
- } else {
704
- thread = await provider.startThread(threadStartSettings);
705
- fresh = true;
706
- }
707
- }
708
- const threadId = codexAppServerThreadResponseId(thread, resumableThreadId);
709
- if (
710
- !threadId ||
711
- threadId === normalizedForbiddenThreadId ||
712
- (fresh && baselineThreadIds.includes(threadId))
713
- ) {
714
- throw codexRenewalThreadError(
715
- errorCode,
716
- threadId
717
- ? "The assistant provider reused the old session's thread instead of starting a fresh one."
718
- : "The assistant provider did not return a fresh renewal thread id.",
719
- {
720
- forbiddenThreadId: normalizedForbiddenThreadId,
721
- threadId
722
- }
723
- );
724
- }
725
- await identity.write({ appServerRuntime, threadId, workdir: normalizedWorkdir });
726
- if (fresh) {
727
- try {
728
- threadSnapshot = await readCodexRenewalSuccessorThreadSnapshot({
729
- provider,
730
- threadId,
731
- workdir: normalizedWorkdir,
732
- errorCode
733
- });
734
- } catch (error) {
735
- throw codexRenewalThreadError(
736
- errorCode,
737
- `The newly started renewal thread cannot be read; ${applicationName} will not replace it silently.`,
738
- {
739
- providerError: normalizeAgentText(error?.message),
740
- threadId
741
- },
742
- { retryable: true }
743
- );
744
- }
745
- }
746
- return Object.freeze({
747
- appServerRuntime,
748
- fresh,
749
- thread,
750
- threadId,
751
- threadSnapshot,
752
- threadStartSettings,
753
- threadSettings
754
- });
755
- }
756
-
757
- function codexAppServerModelCatalogError(message = "", errorPrefix = "") {
758
- const error = new Error(normalizeAgentText(message) || "Codex returned an invalid model catalog.");
759
- error.code = `${errorPrefix}codex_model_catalog_invalid`;
760
- return error;
761
- }
762
-
763
- function codexAppServerProviderConnectionGeneration(provider = null) {
764
- const generation = typeof provider?.currentConnectionGeneration === "function"
765
- ? provider.currentConnectionGeneration()
766
- : typeof provider?.connectionGeneration === "function"
767
- ? provider.connectionGeneration()
768
- : provider?.connectionGeneration;
769
- return normalizeAgentText(generation || "");
770
- }
771
-
772
- function codexAppServerModelCatalogSnapshot(value = null, errorPrefix = "") {
773
- if (!Array.isArray(value?.data)) {
774
- throw codexAppServerModelCatalogError("Codex did not return a usable live model catalog.", errorPrefix);
775
- }
776
- return Object.freeze({
777
- data: Object.freeze(value.data.map((model = {}) => Object.freeze({
778
- hidden: model.hidden === true,
779
- model: normalizeAgentText(model.model || ""),
780
- supportedReasoningEfforts: Object.freeze((Array.isArray(model.supportedReasoningEfforts)
781
- ? model.supportedReasoningEfforts
782
- : []).map((option = {}) => Object.freeze({
783
- reasoningEffort: normalizeAgentText(option.reasoningEffort || "")
784
- })))
785
- })))
786
- });
787
- }
788
-
789
- function createCodexAppServerModelCatalogCache({
790
- cacheMs = 30_000,
791
- errorPrefix = ""
792
- } = {}) {
793
- const codexAppServerModelCatalogs = new WeakMap();
794
- return async function readCodexAppServerModelCatalog(provider, { signal = null } = {}) {
795
- if (typeof provider.listModels !== "function") {
796
- const error = new Error("Codex live model discovery is unavailable.");
797
- error.code = `${errorPrefix}codex_model_catalog_unavailable`;
798
- throw error;
799
- }
800
- const connectionGeneration = codexAppServerProviderConnectionGeneration(provider);
801
- const now = Date.now();
802
- const cached = codexAppServerModelCatalogs.get(provider);
803
- if (
804
- cached?.connectionGeneration === connectionGeneration &&
805
- cached.value &&
806
- cached.expiresAt > now
807
- ) {
808
- return cached.value;
809
- }
810
- if (
811
- cached?.connectionGeneration === connectionGeneration &&
812
- cached.pending
813
- ) {
814
- return cached.pending;
815
- }
816
-
817
- const pending = provider.listModels({
818
- includeHidden: false,
819
- limit: 100
820
- }, { signal }).then((result) => {
821
- if (codexAppServerProviderConnectionGeneration(provider) !== connectionGeneration) {
822
- const error = new Error("Codex reconnected while resolving the helper model catalog.");
823
- error.code = `${errorPrefix}codex_model_catalog_stale`;
824
- throw error;
825
- }
826
- return codexAppServerModelCatalogSnapshot(result, errorPrefix);
827
- });
828
- codexAppServerModelCatalogs.set(provider, {
829
- connectionGeneration,
830
- expiresAt: 0,
831
- pending,
832
- value: null
833
- });
834
- try {
835
- const value = await pending;
836
- codexAppServerModelCatalogs.set(provider, {
837
- connectionGeneration,
838
- expiresAt: Date.now() + cacheMs,
839
- pending: null,
840
- value
841
- });
842
- return value;
843
- } catch (error) {
844
- if (codexAppServerModelCatalogs.get(provider)?.pending === pending) {
845
- codexAppServerModelCatalogs.delete(provider);
846
- }
847
- throw error;
848
- }
849
- };
850
- }
851
-
852
351
  function codexAppServerHelperLoginParams(auth = {}, errorPrefix = "") {
853
352
  if (auth.authMode === "chatgpt") {
854
353
  return {
@@ -901,7 +400,7 @@ function codexAppServerHelperRequestError(error = null, reason = "", errorPrefix
901
400
  }
902
401
 
903
402
  function normalizeCodexAppServerInfo(initializeResult = null) {
904
- if (!isPlainObject(initializeResult)) {
403
+ if (!isRecord(initializeResult)) {
905
404
  return null;
906
405
  }
907
406
  const rawUserAgent = initializeResult.userAgent;
@@ -918,77 +417,6 @@ function normalizeCodexAppServerInfo(initializeResult = null) {
918
417
  });
919
418
  }
920
419
 
921
- function tailAppend(text = "", chunk = "", maxBytes = CODEX_AUTH_PREFLIGHT_OUTPUT_TAIL_BYTES) {
922
- const next = `${String(text || "")}${String(chunk || "")}`;
923
- return next.length > maxBytes ? next.slice(-maxBytes) : next;
924
- }
925
-
926
- function codexAuthPreflightArgs() {
927
- return [
928
- "-c",
929
- "check_for_update_on_startup=false",
930
- "debug",
931
- "models"
932
- ];
933
- }
934
-
935
- async function runCodexAuthPreflight({
936
- codexCommand = "codex",
937
- env = process.env,
938
- executionRoot = "",
939
- runtimeDir = "",
940
- terminalEnv = {},
941
- timeoutMs = CODEX_AUTH_PREFLIGHT_TIMEOUT_MS,
942
- toolHomeSource = "",
943
- workdir = ""
944
- } = {}, execution) {
945
- const normalizedToolHomeSource = normalizeAgentText(toolHomeSource);
946
- if (normalizedToolHomeSource) {
947
- await assertExistingDirectory(normalizedToolHomeSource, "Codex credential home");
948
- }
949
- const normalizedRuntimeDir = normalizeAgentText(runtimeDir);
950
- if (normalizedRuntimeDir) {
951
- await ensureWritablePrivateDirectory(path.resolve(normalizedRuntimeDir));
952
- }
953
- const baseEnv = codexAppServerCommandBaseEnv({
954
- env,
955
- terminalEnv
956
- });
957
- const processCwd = codexAppServerProcessCwd({
958
- executionRoot,
959
- runtimeDir,
960
- workdir
961
- });
962
- try {
963
- const result = await execution.run({
964
- args: codexAuthPreflightArgs(),
965
- baseEnv,
966
- command: codexCommand,
967
- credentialHome: codexAppServerCredentialHome(normalizedToolHomeSource, baseEnv),
968
- cwd: processCwd,
969
- inheritProcessEnv: false,
970
- mode: "capture",
971
- timeout: normalizePositiveInteger(timeoutMs, CODEX_AUTH_PREFLIGHT_TIMEOUT_MS)
972
- });
973
- return {
974
- code: result.exitCode,
975
- ok: result.ok === true,
976
- output: tailAppend("", result.output || [
977
- result.stderr,
978
- result.stdout
979
- ].filter(Boolean).join("\n")),
980
- signal: result.signal,
981
- timedOut: result.timedOut === true
982
- };
983
- } catch (error) {
984
- return {
985
- error,
986
- ok: false,
987
- output: normalizeAgentText(error?.message || error)
988
- };
989
- }
990
- }
991
-
992
420
  function codexAppServerEndpointForTarget(endpoint = "") {
993
421
  const normalizedEndpoint = normalizeAgentText(endpoint);
994
422
  if (!normalizedEndpoint) {
@@ -998,8 +426,8 @@ function codexAppServerEndpointForTarget(endpoint = "") {
998
426
  }
999
427
 
1000
428
  function codexAppServerThreadRequestParams(params = {}, threadEnv = {}) {
1001
- const source = isPlainObject(params) ? params : {};
1002
- const config = isPlainObject(source.config) ? source.config : {};
429
+ const source = isRecord(params) ? params : {};
430
+ const config = isRecord(source.config) ? source.config : {};
1003
431
  if (Object.hasOwn(config, "shell_environment_policy")) {
1004
432
  return source;
1005
433
  }
@@ -1071,13 +499,13 @@ async function sendCodexAppServerPrompt({
1071
499
  function codexTurnInput(input = []) {
1072
500
  const values = Array.isArray(input) ? input : [input];
1073
501
  return values.map((item) => {
1074
- if (isPlainObject(item) && item.type === "localImage" && typeof item.path === "string") {
502
+ if (isRecord(item) && item.type === "localImage" && typeof item.path === "string") {
1075
503
  return { type: "localImage", path: item.path };
1076
504
  }
1077
- if (isPlainObject(item) && item.type === "image" && typeof item.url === "string") {
505
+ if (isRecord(item) && item.type === "image" && typeof item.url === "string") {
1078
506
  return { type: "image", url: item.url };
1079
507
  }
1080
- if (isPlainObject(item) && item.type === "text") {
508
+ if (isRecord(item) && item.type === "text") {
1081
509
  return codexTextInput(item.text);
1082
510
  }
1083
511
  return codexTextInput(item);
@@ -1196,7 +624,7 @@ async function listBoundedCodexAppServerThreadIds({
1196
624
  entryBytes = CODEX_APP_SERVER_THREAD_INVENTORY_MAX_ENTRY_BYTES + 1;
1197
625
  }
1198
626
  if (
1199
- !isPlainObject(entry) ||
627
+ !isRecord(entry) ||
1200
628
  !threadId ||
1201
629
  threadId.length > CODEX_APP_SERVER_THREAD_INVENTORY_ID_MAX_LENGTH ||
1202
630
  codexAppServerTextHasControlCharacters(threadId) ||
@@ -1276,24 +704,15 @@ function normalizeAgentTurn(value = {}) {
1276
704
  };
1277
705
  }
1278
706
 
1279
- async function assertCodexAuthPreflightReady(options = {}, {
1280
- reason = "codex-auth-preflight", execution, credentials
1281
- } = {}) {
1282
- const result = await runCodexAuthPreflight(options, execution);
1283
- if (result.ok && !codexAuthOutputRequiresReconnect(result.output)) {
1284
- return result;
1285
- }
1286
- const observed = normalizeAgentText(result.output || result.error?.message || "Codex auth preflight failed.");
1287
- if (codexAuthOutputRequiresReconnect(observed)) {
1288
- await credentials.markInvalid( {
1289
- reason
1290
- });
1291
- throw credentials.reconnectError({
1292
- modelProviderId: "",
1293
- observed
1294
- });
1295
- }
1296
- throw new Error(observed || "Codex auth preflight failed.");
707
+ // The one reading of a sendTurn delivery. Its turn is already normalized; the
708
+ // provider's own error stays on the raw native turn.
709
+ function codexAppServerDeliveredTurn(delivery = {}) {
710
+ const turn = delivery?.turn || {};
711
+ return {
712
+ turnId: normalizeAgentText(turn.id),
713
+ status: normalizeAgentText(turn.status || turn.raw?.status),
714
+ error: codexAppServerErrorText(turn.raw?.error || turn.response?.turn?.error || turn.error)
715
+ };
1297
716
  }
1298
717
 
1299
718
  export class CodexAppServerAgentProvider {
@@ -2523,7 +1942,7 @@ export class CodexAppServerAgentProvider {
2523
1942
  const includeHidden = params.includeHidden === true;
2524
1943
  const limit = normalizePositiveInteger(
2525
1944
  params.limit,
2526
- CODEX_APP_SERVER_MODEL_CATALOG_PAGE_LIMIT
1945
+ { fallback: CODEX_APP_SERVER_MODEL_CATALOG_PAGE_LIMIT }
2527
1946
  );
2528
1947
  const boundedLimit = Math.min(limit, CODEX_APP_SERVER_MODEL_CATALOG_PAGE_LIMIT);
2529
1948
  let totalBytes = 0;
@@ -2562,7 +1981,7 @@ export class CodexAppServerAgentProvider {
2562
1981
  throw this.modelCatalogError("Codex model catalog exceeded its entry limit.");
2563
1982
  }
2564
1983
  for (const entry of response.data) {
2565
- if (!isPlainObject(entry)) {
1984
+ if (!isRecord(entry)) {
2566
1985
  throw this.modelCatalogError("Codex model catalog contained an invalid entry.");
2567
1986
  }
2568
1987
  let entryBytes = 0;
@@ -2651,7 +2070,7 @@ export class CodexAppServerAgentProvider {
2651
2070
  }
2652
2071
 
2653
2072
  async writeHookTrustState(state = {}) {
2654
- if (!isPlainObject(state) || Object.keys(state).length === 0) {
2073
+ if (!isRecord(state) || Object.keys(state).length === 0) {
2655
2074
  return null;
2656
2075
  }
2657
2076
  const client = await this.activeClient();
@@ -2860,11 +2279,9 @@ export class CodexAppServerAgentProvider {
2860
2279
  }
2861
2280
 
2862
2281
  export {
2863
- CODEX_APP_SERVER_INVALID_REQUEST_CODE, assertCodexAuthPreflightReady,
2864
- createCodexAppServerModelCatalogCache, codexAppServerProviderConnectionGeneration,
2282
+ CODEX_APP_SERVER_INVALID_REQUEST_CODE,
2865
2283
  ensureCodexAppServerThread, codexApplicationToolConfiguration, assertCodexApplicationToolSchemaIdentity, sendCodexAppServerPrompt,
2866
- resumeExactCodexAppServerThread, startFreshCodexAppServerThread,
2867
- defineCodexRenewalThreadIds, codexRenewalThreadError,
2284
+ codexAppServerDeliveredTurn,
2868
2285
  codexAppServerEndpointForTarget, codexAppServerRequestIsInvalid, codexAppServerThreadIsMissing,
2869
2286
  codexAppServerThreadHasReadableHistory, deleteCodexAppServerThread, deleteCodexAppServerHelperThread,
2870
2287
  inspectCodexAppServerMessageAdmission,