@iam-brain/opencode-codex-auth 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/README.md +74 -32
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +3 -18
  4. package/dist/index.js.map +1 -1
  5. package/dist/lib/codex-native/request-transform.d.ts +10 -0
  6. package/dist/lib/codex-native/request-transform.d.ts.map +1 -1
  7. package/dist/lib/codex-native/request-transform.js +132 -0
  8. package/dist/lib/codex-native/request-transform.js.map +1 -1
  9. package/dist/lib/codex-native.d.ts +2 -4
  10. package/dist/lib/codex-native.d.ts.map +1 -1
  11. package/dist/lib/codex-native.js +102 -106
  12. package/dist/lib/codex-native.js.map +1 -1
  13. package/dist/lib/codex-status-tool.d.ts.map +1 -1
  14. package/dist/lib/codex-status-tool.js.map +1 -1
  15. package/dist/lib/codex-status-ui.d.ts.map +1 -1
  16. package/dist/lib/codex-status-ui.js +6 -2
  17. package/dist/lib/codex-status-ui.js.map +1 -1
  18. package/dist/lib/config.d.ts +6 -1
  19. package/dist/lib/config.d.ts.map +1 -1
  20. package/dist/lib/config.js +34 -5
  21. package/dist/lib/config.js.map +1 -1
  22. package/dist/lib/installer-cli.d.ts.map +1 -1
  23. package/dist/lib/installer-cli.js +22 -58
  24. package/dist/lib/installer-cli.js.map +1 -1
  25. package/dist/lib/model-catalog.d.ts.map +1 -1
  26. package/dist/lib/model-catalog.js +48 -1
  27. package/dist/lib/model-catalog.js.map +1 -1
  28. package/package.json +1 -1
  29. package/schemas/codex-config.schema.json +7 -1
  30. package/dist/lib/codex-native/collaboration.d.ts +0 -19
  31. package/dist/lib/codex-native/collaboration.d.ts.map +0 -1
  32. package/dist/lib/codex-native/collaboration.js +0 -126
  33. package/dist/lib/codex-native/collaboration.js.map +0 -1
  34. package/dist/lib/orchestrator-agents.d.ts +0 -29
  35. package/dist/lib/orchestrator-agents.d.ts.map +0 -1
  36. package/dist/lib/orchestrator-agents.js +0 -212
  37. package/dist/lib/orchestrator-agents.js.map +0 -1
@@ -12,12 +12,10 @@ import { formatToastMessage } from "./toast";
12
12
  import { runAuthMenuOnce } from "./ui/auth-menu-runner";
13
13
  import { shouldUseColor } from "./ui/tty/ansi";
14
14
  import { applyCodexCatalogToProviderModels, getCodexModelCatalog, getRuntimeDefaultsForModel, resolveInstructionsForModel } from "./model-catalog";
15
- import { CODEX_RS_COMPACT_PROMPT } from "./orchestrator-agents";
16
15
  import { fetchQuotaSnapshotFromBackend } from "./codex-quota-fetch";
17
16
  import { createRequestSnapshots } from "./request-snapshots";
18
17
  import { CODEX_OAUTH_SUCCESS_HTML } from "./oauth-pages";
19
- import { mergeInstructions, resolveCollaborationInstructions, resolveCollaborationModeKind, resolveCollaborationProfile, resolveHookAgentName, resolveSubagentHeaderValue } from "./codex-native/collaboration";
20
- import { applyCatalogInstructionOverrideToRequest, applyCodexRuntimeDefaultsToParams, findCatalogModelForCandidates, getModelLookupCandidates, getModelThinkingSummariesOverride, getVariantLookupCandidates, resolvePersonalityForModel, sanitizeOutboundRequestIfNeeded } from "./codex-native/request-transform";
18
+ import { applyCatalogInstructionOverrideToRequest, applyCodexRuntimeDefaultsToParams, findCatalogModelForCandidates, getModelLookupCandidates, getModelThinkingSummariesOverride, getVariantLookupCandidates, remapDeveloperMessagesToUserOnRequest, resolvePersonalityForModel, sanitizeOutboundRequestIfNeeded } from "./codex-native/request-transform";
21
19
  import { createSessionExistsFn, loadSessionAffinity, pruneSessionAffinitySnapshot, readSessionAffinitySnapshot, saveSessionAffinity, writeSessionAffinitySnapshot } from "./session-affinity";
22
20
  import { resolveCodexOriginator } from "./codex-native/originator";
23
21
  import { tryOpenUrlInBrowser as openUrlInBrowser } from "./codex-native/browser";
@@ -63,27 +61,6 @@ const AUTH_MENU_QUOTA_FAILURE_COOLDOWN_MS = 30_000;
63
61
  const AUTH_MENU_QUOTA_FETCH_TIMEOUT_MS = 5000;
64
62
  const INTERNAL_COLLABORATION_MODE_HEADER = "x-opencode-collaboration-mode-kind";
65
63
  const SESSION_AFFINITY_MISSING_GRACE_MS = 15 * 60 * 1000;
66
- const CODEX_PLAN_MODE_INSTRUCTIONS = [
67
- "# Plan Mode",
68
- "",
69
- "You are in Plan Mode. Focus on producing a decision-complete implementation plan before making code changes.",
70
- "Use non-mutating exploration first, ask focused questions only when needed, and make assumptions explicit.",
71
- "If asked to execute while still in plan mode, continue planning until the active agent switches out of plan mode."
72
- ].join("\n");
73
- const CODEX_CODE_MODE_INSTRUCTIONS = "you are now in code mode.";
74
- const CODEX_EXECUTE_MODE_INSTRUCTIONS = [
75
- "# Collaboration Style: Execute",
76
- "You execute on a well-specified task independently and report progress.",
77
- "",
78
- "You do not collaborate on decisions in this mode. You execute end-to-end.",
79
- "You make reasonable assumptions when the user hasn't specified something, and you proceed without asking questions."
80
- ].join("\n");
81
- const CODEX_PAIR_PROGRAMMING_MODE_INSTRUCTIONS = [
82
- "# Collaboration Style: Pair Programming",
83
- "",
84
- "## Build together as you go",
85
- "You treat collaboration as pairing by default. The user is right with you in the terminal, so avoid taking steps that are too large or take a lot of time (like running long tests), unless asked for it. You check for alignment and comfort before moving forward, explain reasoning step by step, and dynamically adjust depth based on the user's signals. There is no need to ask multiple rounds of questions—build as you go. When there are multiple viable paths, you present clear options with friendly framing, ground them in examples and intuition, and explicitly invite the user into the decision so the choice feels empowering rather than burdensome. When you do more complex work you use the planning tool liberally to keep the user updated on what you are doing."
86
- ].join("\n");
87
64
  const STATIC_FALLBACK_MODELS = [
88
65
  "gpt-5.1-codex-max",
89
66
  "gpt-5.1-codex-mini",
@@ -92,6 +69,17 @@ const STATIC_FALLBACK_MODELS = [
92
69
  "gpt-5.3-codex",
93
70
  "gpt-5.1-codex"
94
71
  ];
72
+ const CODEX_RS_COMPACT_PROMPT = `You are performing a CONTEXT CHECKPOINT COMPACTION. Create a handoff summary for another LLM that will resume the task.
73
+
74
+ Include:
75
+ - Current progress and key decisions made
76
+ - Important context, constraints, or user preferences
77
+ - What remains to be done (clear next steps)
78
+ - Any critical data, examples, or references needed to continue
79
+
80
+ Be concise, structured, and focused on helping the next LLM seamlessly continue the work.
81
+ `;
82
+ const CODEX_RS_COMPACT_SUMMARY_PREFIX = "Another language model started to solve this problem and produced a summary of its thinking process. You also have access to the state of the tools that were used by that language model. Use this to build on the work that has already been done and avoid duplicating work. Here is the summary produced by the other language model, use the information in this summary to assist with your own analysis:";
95
83
  function sleep(ms) {
96
84
  return new Promise((resolve) => setTimeout(resolve, ms));
97
85
  }
@@ -169,9 +157,6 @@ export const __testOnly = {
169
157
  resolveRequestUserAgent,
170
158
  resolveCodexClientVersion,
171
159
  refreshCodexClientVersionFromGitHub,
172
- resolveHookAgentName,
173
- resolveCollaborationModeKind,
174
- resolveSubagentHeaderValue,
175
160
  isOAuthDebugEnabled,
176
161
  stopOAuthServer
177
162
  };
@@ -496,30 +481,49 @@ function assertAllowedOutboundUrl(url) {
496
481
  });
497
482
  }
498
483
  async function sessionUsesOpenAIProvider(client, sessionID) {
484
+ const rows = await readSessionMessageRows(client, sessionID);
485
+ for (let index = rows.length - 1; index >= 0; index -= 1) {
486
+ const row = rows[index];
487
+ if (!isRecord(row) || !isRecord(row.info))
488
+ continue;
489
+ const info = row.info;
490
+ if (asString(info.role) !== "user")
491
+ continue;
492
+ const providerID = getMessageProviderID(info);
493
+ if (!providerID)
494
+ continue;
495
+ return providerID === "openai";
496
+ }
497
+ return false;
498
+ }
499
+ function getMessageProviderID(info) {
500
+ const model = isRecord(info.model) ? info.model : undefined;
501
+ return model ? asString(model.providerID) : asString(info.providerID);
502
+ }
503
+ async function readSessionMessageRows(client, sessionID) {
499
504
  const sessionApi = client?.session;
500
505
  if (!sessionApi || typeof sessionApi.messages !== "function")
501
- return false;
506
+ return [];
502
507
  try {
503
508
  const response = await sessionApi.messages({ sessionID, limit: 100 });
504
- const rows = isRecord(response) && Array.isArray(response.data) ? response.data : [];
505
- for (let index = rows.length - 1; index >= 0; index -= 1) {
506
- const row = rows[index];
507
- if (!isRecord(row) || !isRecord(row.info))
508
- continue;
509
- const info = row.info;
510
- if (asString(info.role) !== "user")
511
- continue;
512
- const model = isRecord(info.model) ? info.model : undefined;
513
- const providerID = model ? asString(model.providerID) : asString(info.providerID);
514
- if (!providerID)
515
- continue;
516
- return providerID === "openai";
517
- }
509
+ return isRecord(response) && Array.isArray(response.data) ? response.data : [];
518
510
  }
519
511
  catch {
520
- return false;
512
+ return [];
521
513
  }
522
- return false;
514
+ }
515
+ async function readSessionMessageInfo(client, sessionID, messageID) {
516
+ const rows = await readSessionMessageRows(client, sessionID);
517
+ for (let index = rows.length - 1; index >= 0; index -= 1) {
518
+ const row = rows[index];
519
+ if (!isRecord(row) || !isRecord(row.info))
520
+ continue;
521
+ const info = row.info;
522
+ if (asString(info.id) !== messageID)
523
+ continue;
524
+ return info;
525
+ }
526
+ return undefined;
523
527
  }
524
528
  function formatAccountLabel(account, index) {
525
529
  const email = account?.email?.trim();
@@ -652,16 +656,14 @@ function asString(value) {
652
656
  }
653
657
  export async function CodexAuthPlugin(input, opts = {}) {
654
658
  opts.log?.debug("codex-native init");
659
+ const codexCompactionSummaryPrefixSessions = new Set();
655
660
  const spoofMode = opts.spoofMode === "codex" || opts.spoofMode === "strict"
656
661
  ? "codex"
657
662
  : "native";
658
- const runtimeMode = opts.mode === "collab" || opts.mode === "codex" || opts.mode === "native"
659
- ? opts.mode
660
- : spoofMode === "codex"
661
- ? "codex"
662
- : "native";
663
- const collabModeEnabled = runtimeMode === "collab";
663
+ const runtimeMode = opts.mode === "codex" || opts.mode === "native" ? opts.mode : spoofMode === "codex" ? "codex" : "native";
664
664
  const authMode = modeForRuntimeMode(runtimeMode);
665
+ const remapDeveloperMessagesToUserEnabled = spoofMode === "codex" && opts.remapDeveloperMessagesToUser !== false;
666
+ const codexCompactionOverrideEnabled = opts.codexCompactionOverride !== undefined ? opts.codexCompactionOverride : runtimeMode === "codex";
665
667
  void refreshCodexClientVersionFromGitHub(opts.log).catch(() => { });
666
668
  const resolveCatalogHeaders = () => {
667
669
  const originator = resolveCodexOriginator(spoofMode);
@@ -1065,11 +1067,9 @@ export async function CodexAuthPlugin(input, opts = {}) {
1065
1067
  apiKey: OAUTH_DUMMY_KEY,
1066
1068
  async fetch(requestInput, init) {
1067
1069
  const baseRequest = new Request(requestInput, init);
1068
- const inboundCollaborationModeKind = baseRequest.headers.get(INTERNAL_COLLABORATION_MODE_HEADER) ?? undefined;
1069
1070
  if (opts.headerTransformDebug === true) {
1070
1071
  await requestSnapshots.captureRequest("before-header-transform", baseRequest, {
1071
- spoofMode,
1072
- ...(inboundCollaborationModeKind ? { collaborationModeKind: inboundCollaborationModeKind } : {})
1072
+ spoofMode
1073
1073
  });
1074
1074
  }
1075
1075
  let outbound = new Request(rewriteUrl(baseRequest), baseRequest);
@@ -1087,8 +1087,7 @@ export async function CodexAuthPlugin(input, opts = {}) {
1087
1087
  else {
1088
1088
  outbound.headers.set("user-agent", resolveRequestUserAgent(spoofMode, outboundOriginator));
1089
1089
  }
1090
- const collaborationModeKind = outbound.headers.get(INTERNAL_COLLABORATION_MODE_HEADER);
1091
- if (collaborationModeKind) {
1090
+ if (outbound.headers.has(INTERNAL_COLLABORATION_MODE_HEADER)) {
1092
1091
  outbound.headers.delete(INTERNAL_COLLABORATION_MODE_HEADER);
1093
1092
  }
1094
1093
  const instructionOverride = await applyCatalogInstructionOverrideToRequest({
@@ -1098,7 +1097,11 @@ export async function CodexAuthPlugin(input, opts = {}) {
1098
1097
  customSettings: opts.customSettings,
1099
1098
  fallbackPersonality: opts.personality
1100
1099
  });
1101
- outbound = instructionOverride.request;
1100
+ const developerRoleRemap = await remapDeveloperMessagesToUserOnRequest({
1101
+ request: instructionOverride.request,
1102
+ enabled: remapDeveloperMessagesToUserEnabled
1103
+ });
1104
+ outbound = developerRoleRemap.request;
1102
1105
  const subagentHeader = outbound.headers.get("x-openai-subagent")?.trim();
1103
1106
  const isSubagentRequest = Boolean(subagentHeader);
1104
1107
  if (opts.headerTransformDebug === true) {
@@ -1106,15 +1109,15 @@ export async function CodexAuthPlugin(input, opts = {}) {
1106
1109
  spoofMode,
1107
1110
  instructionsOverridden: instructionOverride.changed,
1108
1111
  instructionOverrideReason: instructionOverride.reason,
1109
- ...(collaborationModeKind ? { collaborationModeKind } : {}),
1112
+ developerMessagesRemapped: developerRoleRemap.changed,
1113
+ developerMessageRemapReason: developerRoleRemap.reason,
1114
+ developerMessageRemapCount: developerRoleRemap.remappedCount,
1115
+ developerMessagePreservedCount: developerRoleRemap.preservedCount,
1110
1116
  ...(isSubagentRequest ? { subagent: subagentHeader } : {})
1111
1117
  });
1112
1118
  }
1113
1119
  let selectedIdentityKey;
1114
- await requestSnapshots.captureRequest("before-auth", outbound, {
1115
- spoofMode,
1116
- ...(collaborationModeKind ? { collaborationModeKind } : {})
1117
- });
1120
+ await requestSnapshots.captureRequest("before-auth", outbound, { spoofMode });
1118
1121
  const orchestrator = new FetchOrchestrator({
1119
1122
  acquireAuth: async (context) => {
1120
1123
  let access;
@@ -1397,7 +1400,11 @@ export async function CodexAuthPlugin(input, opts = {}) {
1397
1400
  customSettings: opts.customSettings,
1398
1401
  fallbackPersonality: opts.personality
1399
1402
  });
1400
- await requestSnapshots.captureRequest("outbound-attempt", instructionOverride.request, {
1403
+ const developerRoleRemap = await remapDeveloperMessagesToUserOnRequest({
1404
+ request: instructionOverride.request,
1405
+ enabled: remapDeveloperMessagesToUserEnabled
1406
+ });
1407
+ await requestSnapshots.captureRequest("outbound-attempt", developerRoleRemap.request, {
1401
1408
  attempt: attempt + 1,
1402
1409
  maxAttempts,
1403
1410
  sessionKey,
@@ -1405,9 +1412,12 @@ export async function CodexAuthPlugin(input, opts = {}) {
1405
1412
  accountLabel: auth.accountLabel,
1406
1413
  instructionsOverridden: instructionOverride.changed,
1407
1414
  instructionOverrideReason: instructionOverride.reason,
1408
- ...(collaborationModeKind ? { collaborationModeKind } : {})
1415
+ developerMessagesRemapped: developerRoleRemap.changed,
1416
+ developerMessageRemapReason: developerRoleRemap.reason,
1417
+ developerMessageRemapCount: developerRoleRemap.remappedCount,
1418
+ developerMessagePreservedCount: developerRoleRemap.preservedCount
1409
1419
  });
1410
- return instructionOverride.request;
1420
+ return developerRoleRemap.request;
1411
1421
  },
1412
1422
  onAttemptResponse: async ({ attempt, maxAttempts, response, auth, sessionKey }) => {
1413
1423
  await requestSnapshots.captureResponse("outbound-response", response, {
@@ -1415,8 +1425,7 @@ export async function CodexAuthPlugin(input, opts = {}) {
1415
1425
  maxAttempts,
1416
1426
  sessionKey,
1417
1427
  identityKey: auth.identityKey,
1418
- accountLabel: auth.accountLabel,
1419
- ...(collaborationModeKind ? { collaborationModeKind } : {})
1428
+ accountLabel: auth.accountLabel
1420
1429
  });
1421
1430
  }
1422
1431
  });
@@ -1426,8 +1435,7 @@ export async function CodexAuthPlugin(input, opts = {}) {
1426
1435
  }
1427
1436
  await requestSnapshots.captureRequest("after-sanitize", sanitizedOutbound.request, {
1428
1437
  spoofMode,
1429
- sanitized: sanitizedOutbound.changed,
1430
- ...(collaborationModeKind ? { collaborationModeKind } : {})
1438
+ sanitized: sanitizedOutbound.changed
1431
1439
  });
1432
1440
  try {
1433
1441
  assertAllowedOutboundUrl(new URL(sanitizedOutbound.request.url));
@@ -1683,8 +1691,6 @@ export async function CodexAuthPlugin(input, opts = {}) {
1683
1691
  "chat.params": async (hookInput, output) => {
1684
1692
  if (hookInput.model.providerID !== "openai")
1685
1693
  return;
1686
- const initialReasoningEffort = asString(output.options.reasoningEffort);
1687
- const collaborationProfile = collabModeEnabled ? resolveCollaborationProfile(hookInput.agent) : { enabled: false };
1688
1694
  const modelOptions = isRecord(hookInput.model.options) ? hookInput.model.options : {};
1689
1695
  const modelCandidates = getModelLookupCandidates({
1690
1696
  id: hookInput.model.id,
@@ -1738,33 +1744,10 @@ export async function CodexAuthPlugin(input, opts = {}) {
1738
1744
  preferCodexInstructions: spoofMode === "codex",
1739
1745
  output
1740
1746
  });
1741
- if (collabModeEnabled && collaborationProfile.enabled && collaborationProfile.kind) {
1742
- const collaborationModeKind = collaborationProfile.kind;
1743
- const collaborationInstructions = resolveCollaborationInstructions(collaborationModeKind, {
1744
- plan: CODEX_PLAN_MODE_INSTRUCTIONS,
1745
- code: CODEX_CODE_MODE_INSTRUCTIONS,
1746
- execute: CODEX_EXECUTE_MODE_INSTRUCTIONS,
1747
- pairProgramming: CODEX_PAIR_PROGRAMMING_MODE_INSTRUCTIONS
1748
- });
1749
- const mergedInstructions = mergeInstructions(asString(output.options.instructions), collaborationInstructions);
1750
- if (mergedInstructions) {
1751
- output.options.instructions = mergedInstructions;
1752
- }
1753
- if (initialReasoningEffort === undefined) {
1754
- if (collaborationModeKind === "plan" || collaborationModeKind === "pair_programming") {
1755
- output.options.reasoningEffort = "medium";
1756
- }
1757
- else if (collaborationModeKind === "execute") {
1758
- output.options.reasoningEffort = "high";
1759
- }
1760
- }
1761
- }
1762
1747
  },
1763
1748
  "chat.headers": async (hookInput, output) => {
1764
1749
  if (hookInput.model.providerID !== "openai")
1765
1750
  return;
1766
- const collaborationProfile = collabModeEnabled ? resolveCollaborationProfile(hookInput.agent) : { enabled: false };
1767
- const collaborationModeKind = collaborationProfile.enabled ? collaborationProfile.kind : undefined;
1768
1751
  const originator = resolveCodexOriginator(spoofMode);
1769
1752
  output.headers.originator = originator;
1770
1753
  output.headers["User-Agent"] = resolveRequestUserAgent(spoofMode, originator);
@@ -1777,25 +1760,38 @@ export async function CodexAuthPlugin(input, opts = {}) {
1777
1760
  output.headers.session_id = hookInput.sessionID;
1778
1761
  delete output.headers["OpenAI-Beta"];
1779
1762
  delete output.headers.conversation_id;
1780
- const subagentHeader = collaborationProfile.enabled ? resolveSubagentHeaderValue(hookInput.agent) : undefined;
1781
- if (subagentHeader) {
1782
- output.headers["x-openai-subagent"] = subagentHeader;
1783
- }
1784
- else {
1785
- delete output.headers["x-openai-subagent"];
1786
- }
1787
- if (collaborationModeKind) {
1788
- output.headers[INTERNAL_COLLABORATION_MODE_HEADER] = collaborationModeKind;
1789
- }
1790
- else {
1791
- delete output.headers[INTERNAL_COLLABORATION_MODE_HEADER];
1792
- }
1763
+ delete output.headers["x-openai-subagent"];
1764
+ delete output.headers[INTERNAL_COLLABORATION_MODE_HEADER];
1793
1765
  }
1794
1766
  },
1795
1767
  "experimental.session.compacting": async (hookInput, output) => {
1768
+ if (!codexCompactionOverrideEnabled)
1769
+ return;
1796
1770
  if (await sessionUsesOpenAIProvider(input.client, hookInput.sessionID)) {
1797
1771
  output.prompt = CODEX_RS_COMPACT_PROMPT;
1772
+ codexCompactionSummaryPrefixSessions.add(hookInput.sessionID);
1798
1773
  }
1774
+ },
1775
+ "experimental.text.complete": async (hookInput, output) => {
1776
+ if (!codexCompactionOverrideEnabled)
1777
+ return;
1778
+ if (!codexCompactionSummaryPrefixSessions.has(hookInput.sessionID))
1779
+ return;
1780
+ const info = await readSessionMessageInfo(input.client, hookInput.sessionID, hookInput.messageID);
1781
+ codexCompactionSummaryPrefixSessions.delete(hookInput.sessionID);
1782
+ if (!info)
1783
+ return;
1784
+ if (asString(info.role) !== "assistant")
1785
+ return;
1786
+ if (asString(info.agent) !== "compaction")
1787
+ return;
1788
+ if (info.summary !== true)
1789
+ return;
1790
+ if (getMessageProviderID(info) !== "openai")
1791
+ return;
1792
+ if (output.text.startsWith(CODEX_RS_COMPACT_SUMMARY_PREFIX))
1793
+ return;
1794
+ output.text = `${CODEX_RS_COMPACT_SUMMARY_PREFIX}\n${output.text.trimStart()}`;
1799
1795
  }
1800
1796
  };
1801
1797
  async function persistOAuthTokens(tokens) {