codex-multi-auth 0.1.0
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/LICENSE +1 -0
- package/README.md +162 -0
- package/assets/opencode-logo-ornate-dark.svg +18 -0
- package/assets/readme-hero.svg +31 -0
- package/config/README.md +87 -0
- package/config/minimal-opencode.json +13 -0
- package/config/opencode-legacy.json +571 -0
- package/config/opencode-modern.json +239 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3160 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/accounts/rate-limits.d.ts +22 -0
- package/dist/lib/accounts/rate-limits.d.ts.map +1 -0
- package/dist/lib/accounts/rate-limits.js +63 -0
- package/dist/lib/accounts/rate-limits.js.map +1 -0
- package/dist/lib/accounts.d.ts +95 -0
- package/dist/lib/accounts.d.ts.map +1 -0
- package/dist/lib/accounts.js +668 -0
- package/dist/lib/accounts.js.map +1 -0
- package/dist/lib/audit.d.ts +45 -0
- package/dist/lib/audit.d.ts.map +1 -0
- package/dist/lib/audit.js +131 -0
- package/dist/lib/audit.js.map +1 -0
- package/dist/lib/auth/auth.d.ts +56 -0
- package/dist/lib/auth/auth.d.ts.map +1 -0
- package/dist/lib/auth/auth.js +214 -0
- package/dist/lib/auth/auth.js.map +1 -0
- package/dist/lib/auth/browser.d.ts +34 -0
- package/dist/lib/auth/browser.d.ts.map +1 -0
- package/dist/lib/auth/browser.js +185 -0
- package/dist/lib/auth/browser.js.map +1 -0
- package/dist/lib/auth/server.d.ts +24 -0
- package/dist/lib/auth/server.d.ts.map +1 -0
- package/dist/lib/auth/server.js +116 -0
- package/dist/lib/auth/server.js.map +1 -0
- package/dist/lib/auth/token-utils.d.ts +59 -0
- package/dist/lib/auth/token-utils.d.ts.map +1 -0
- package/dist/lib/auth/token-utils.js +331 -0
- package/dist/lib/auth/token-utils.js.map +1 -0
- package/dist/lib/auth-rate-limit.d.ts +20 -0
- package/dist/lib/auth-rate-limit.d.ts.map +1 -0
- package/dist/lib/auth-rate-limit.js +91 -0
- package/dist/lib/auth-rate-limit.js.map +1 -0
- package/dist/lib/auto-update-checker.d.ts +10 -0
- package/dist/lib/auto-update-checker.d.ts.map +1 -0
- package/dist/lib/auto-update-checker.js +216 -0
- package/dist/lib/auto-update-checker.js.map +1 -0
- package/dist/lib/capability-policy.d.ts +18 -0
- package/dist/lib/capability-policy.d.ts.map +1 -0
- package/dist/lib/capability-policy.js +150 -0
- package/dist/lib/capability-policy.js.map +1 -0
- package/dist/lib/circuit-breaker.d.ts +34 -0
- package/dist/lib/circuit-breaker.d.ts.map +1 -0
- package/dist/lib/circuit-breaker.js +124 -0
- package/dist/lib/circuit-breaker.js.map +1 -0
- package/dist/lib/cli.d.ts +64 -0
- package/dist/lib/cli.d.ts.map +1 -0
- package/dist/lib/cli.js +274 -0
- package/dist/lib/cli.js.map +1 -0
- package/dist/lib/codex-cli/observability.d.ts +22 -0
- package/dist/lib/codex-cli/observability.d.ts.map +1 -0
- package/dist/lib/codex-cli/observability.js +36 -0
- package/dist/lib/codex-cli/observability.js.map +1 -0
- package/dist/lib/codex-cli/state.d.ts +86 -0
- package/dist/lib/codex-cli/state.d.ts.map +1 -0
- package/dist/lib/codex-cli/state.js +470 -0
- package/dist/lib/codex-cli/state.js.map +1 -0
- package/dist/lib/codex-cli/sync.d.ts +27 -0
- package/dist/lib/codex-cli/sync.d.ts.map +1 -0
- package/dist/lib/codex-cli/sync.js +325 -0
- package/dist/lib/codex-cli/sync.js.map +1 -0
- package/dist/lib/codex-cli/writer.d.ts +12 -0
- package/dist/lib/codex-cli/writer.d.ts.map +1 -0
- package/dist/lib/codex-cli/writer.js +388 -0
- package/dist/lib/codex-cli/writer.js.map +1 -0
- package/dist/lib/codex-manager.d.ts +2 -0
- package/dist/lib/codex-manager.d.ts.map +1 -0
- package/dist/lib/codex-manager.js +4841 -0
- package/dist/lib/codex-manager.js.map +1 -0
- package/dist/lib/config.d.ts +269 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +789 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/constants.d.ts +78 -0
- package/dist/lib/constants.d.ts.map +1 -0
- package/dist/lib/constants.js +78 -0
- package/dist/lib/constants.js.map +1 -0
- package/dist/lib/context-overflow.d.ts +27 -0
- package/dist/lib/context-overflow.d.ts.map +1 -0
- package/dist/lib/context-overflow.js +124 -0
- package/dist/lib/context-overflow.js.map +1 -0
- package/dist/lib/dashboard-settings.d.ts +90 -0
- package/dist/lib/dashboard-settings.d.ts.map +1 -0
- package/dist/lib/dashboard-settings.js +327 -0
- package/dist/lib/dashboard-settings.js.map +1 -0
- package/dist/lib/entitlement-cache.d.ts +41 -0
- package/dist/lib/entitlement-cache.d.ts.map +1 -0
- package/dist/lib/entitlement-cache.js +137 -0
- package/dist/lib/entitlement-cache.js.map +1 -0
- package/dist/lib/errors.d.ts +113 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/errors.js +103 -0
- package/dist/lib/errors.js.map +1 -0
- package/dist/lib/forecast.d.ts +42 -0
- package/dist/lib/forecast.d.ts.map +1 -0
- package/dist/lib/forecast.js +256 -0
- package/dist/lib/forecast.js.map +1 -0
- package/dist/lib/health.d.ts +33 -0
- package/dist/lib/health.d.ts.map +1 -0
- package/dist/lib/health.js +70 -0
- package/dist/lib/health.js.map +1 -0
- package/dist/lib/index.d.ts +32 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/index.js +32 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/live-account-sync.d.ts +39 -0
- package/dist/lib/live-account-sync.d.ts.map +1 -0
- package/dist/lib/live-account-sync.js +196 -0
- package/dist/lib/live-account-sync.js.map +1 -0
- package/dist/lib/logger.d.ts +40 -0
- package/dist/lib/logger.d.ts.map +1 -0
- package/dist/lib/logger.js +364 -0
- package/dist/lib/logger.js.map +1 -0
- package/dist/lib/oauth-success.html +338 -0
- package/dist/lib/parallel-probe.d.ts +28 -0
- package/dist/lib/parallel-probe.d.ts.map +1 -0
- package/dist/lib/parallel-probe.js +97 -0
- package/dist/lib/parallel-probe.js.map +1 -0
- package/dist/lib/preemptive-quota-scheduler.d.ts +53 -0
- package/dist/lib/preemptive-quota-scheduler.d.ts.map +1 -0
- package/dist/lib/preemptive-quota-scheduler.js +220 -0
- package/dist/lib/preemptive-quota-scheduler.js.map +1 -0
- package/dist/lib/proactive-refresh.d.ts +66 -0
- package/dist/lib/proactive-refresh.d.ts.map +1 -0
- package/dist/lib/proactive-refresh.js +143 -0
- package/dist/lib/proactive-refresh.js.map +1 -0
- package/dist/lib/prompts/codex-opencode-bridge.d.ts +19 -0
- package/dist/lib/prompts/codex-opencode-bridge.d.ts.map +1 -0
- package/dist/lib/prompts/codex-opencode-bridge.js +169 -0
- package/dist/lib/prompts/codex-opencode-bridge.js.map +1 -0
- package/dist/lib/prompts/codex.d.ts +41 -0
- package/dist/lib/prompts/codex.d.ts.map +1 -0
- package/dist/lib/prompts/codex.js +383 -0
- package/dist/lib/prompts/codex.js.map +1 -0
- package/dist/lib/prompts/opencode-codex.d.ts +25 -0
- package/dist/lib/prompts/opencode-codex.d.ts.map +1 -0
- package/dist/lib/prompts/opencode-codex.js +270 -0
- package/dist/lib/prompts/opencode-codex.js.map +1 -0
- package/dist/lib/quota-cache.d.ts +68 -0
- package/dist/lib/quota-cache.d.ts.map +1 -0
- package/dist/lib/quota-cache.js +224 -0
- package/dist/lib/quota-cache.js.map +1 -0
- package/dist/lib/quota-probe.d.ts +49 -0
- package/dist/lib/quota-probe.d.ts.map +1 -0
- package/dist/lib/quota-probe.js +368 -0
- package/dist/lib/quota-probe.js.map +1 -0
- package/dist/lib/recovery/constants.d.ts +12 -0
- package/dist/lib/recovery/constants.d.ts.map +1 -0
- package/dist/lib/recovery/constants.js +31 -0
- package/dist/lib/recovery/constants.js.map +1 -0
- package/dist/lib/recovery/index.d.ts +12 -0
- package/dist/lib/recovery/index.d.ts.map +1 -0
- package/dist/lib/recovery/index.js +12 -0
- package/dist/lib/recovery/index.js.map +1 -0
- package/dist/lib/recovery/storage.d.ts +24 -0
- package/dist/lib/recovery/storage.d.ts.map +1 -0
- package/dist/lib/recovery/storage.js +362 -0
- package/dist/lib/recovery/storage.js.map +1 -0
- package/dist/lib/recovery/types.d.ts +116 -0
- package/dist/lib/recovery/types.d.ts.map +1 -0
- package/dist/lib/recovery/types.js +7 -0
- package/dist/lib/recovery/types.js.map +1 -0
- package/dist/lib/recovery.d.ts +31 -0
- package/dist/lib/recovery.d.ts.map +1 -0
- package/dist/lib/recovery.js +313 -0
- package/dist/lib/recovery.js.map +1 -0
- package/dist/lib/refresh-guardian.d.ts +31 -0
- package/dist/lib/refresh-guardian.d.ts.map +1 -0
- package/dist/lib/refresh-guardian.js +151 -0
- package/dist/lib/refresh-guardian.js.map +1 -0
- package/dist/lib/refresh-lease.d.ts +37 -0
- package/dist/lib/refresh-lease.d.ts.map +1 -0
- package/dist/lib/refresh-lease.js +335 -0
- package/dist/lib/refresh-lease.js.map +1 -0
- package/dist/lib/refresh-queue.d.ts +117 -0
- package/dist/lib/refresh-queue.d.ts.map +1 -0
- package/dist/lib/refresh-queue.js +297 -0
- package/dist/lib/refresh-queue.js.map +1 -0
- package/dist/lib/request/failure-policy.d.ts +42 -0
- package/dist/lib/request/failure-policy.d.ts.map +1 -0
- package/dist/lib/request/failure-policy.js +133 -0
- package/dist/lib/request/failure-policy.js.map +1 -0
- package/dist/lib/request/fetch-helpers.d.ts +152 -0
- package/dist/lib/request/fetch-helpers.d.ts.map +1 -0
- package/dist/lib/request/fetch-helpers.js +704 -0
- package/dist/lib/request/fetch-helpers.js.map +1 -0
- package/dist/lib/request/helpers/input-utils.d.ts +7 -0
- package/dist/lib/request/helpers/input-utils.d.ts.map +1 -0
- package/dist/lib/request/helpers/input-utils.js +214 -0
- package/dist/lib/request/helpers/input-utils.js.map +1 -0
- package/dist/lib/request/helpers/model-map.d.ts +28 -0
- package/dist/lib/request/helpers/model-map.d.ts.map +1 -0
- package/dist/lib/request/helpers/model-map.js +133 -0
- package/dist/lib/request/helpers/model-map.js.map +1 -0
- package/dist/lib/request/helpers/tool-utils.d.ts +29 -0
- package/dist/lib/request/helpers/tool-utils.d.ts.map +1 -0
- package/dist/lib/request/helpers/tool-utils.js +117 -0
- package/dist/lib/request/helpers/tool-utils.js.map +1 -0
- package/dist/lib/request/rate-limit-backoff.d.ts +17 -0
- package/dist/lib/request/rate-limit-backoff.d.ts.map +1 -0
- package/dist/lib/request/rate-limit-backoff.js +83 -0
- package/dist/lib/request/rate-limit-backoff.js.map +1 -0
- package/dist/lib/request/request-transformer.d.ts +107 -0
- package/dist/lib/request/request-transformer.d.ts.map +1 -0
- package/dist/lib/request/request-transformer.js +814 -0
- package/dist/lib/request/request-transformer.js.map +1 -0
- package/dist/lib/request/response-handler.d.ts +23 -0
- package/dist/lib/request/response-handler.d.ts.map +1 -0
- package/dist/lib/request/response-handler.js +155 -0
- package/dist/lib/request/response-handler.js.map +1 -0
- package/dist/lib/request/stream-failover.d.ts +21 -0
- package/dist/lib/request/stream-failover.d.ts.map +1 -0
- package/dist/lib/request/stream-failover.js +204 -0
- package/dist/lib/request/stream-failover.js.map +1 -0
- package/dist/lib/rotation.d.ts +146 -0
- package/dist/lib/rotation.d.ts.map +1 -0
- package/dist/lib/rotation.js +321 -0
- package/dist/lib/rotation.js.map +1 -0
- package/dist/lib/runtime-paths.d.ts +58 -0
- package/dist/lib/runtime-paths.d.ts.map +1 -0
- package/dist/lib/runtime-paths.js +164 -0
- package/dist/lib/runtime-paths.js.map +1 -0
- package/dist/lib/schemas.d.ts +435 -0
- package/dist/lib/schemas.d.ts.map +1 -0
- package/dist/lib/schemas.js +268 -0
- package/dist/lib/schemas.js.map +1 -0
- package/dist/lib/session-affinity.d.ts +23 -0
- package/dist/lib/session-affinity.d.ts.map +1 -0
- package/dist/lib/session-affinity.js +127 -0
- package/dist/lib/session-affinity.js.map +1 -0
- package/dist/lib/shutdown.d.ts +7 -0
- package/dist/lib/shutdown.d.ts.map +1 -0
- package/dist/lib/shutdown.js +43 -0
- package/dist/lib/shutdown.js.map +1 -0
- package/dist/lib/storage/migrations.d.ts +59 -0
- package/dist/lib/storage/migrations.d.ts.map +1 -0
- package/dist/lib/storage/migrations.js +41 -0
- package/dist/lib/storage/migrations.js.map +1 -0
- package/dist/lib/storage/paths.d.ts +51 -0
- package/dist/lib/storage/paths.d.ts.map +1 -0
- package/dist/lib/storage/paths.js +152 -0
- package/dist/lib/storage/paths.js.map +1 -0
- package/dist/lib/storage.d.ts +106 -0
- package/dist/lib/storage.d.ts.map +1 -0
- package/dist/lib/storage.js +896 -0
- package/dist/lib/storage.js.map +1 -0
- package/dist/lib/table-formatter.d.ts +32 -0
- package/dist/lib/table-formatter.d.ts.map +1 -0
- package/dist/lib/table-formatter.js +44 -0
- package/dist/lib/table-formatter.js.map +1 -0
- package/dist/lib/tools/hashline-tools.d.ts +51 -0
- package/dist/lib/tools/hashline-tools.d.ts.map +1 -0
- package/dist/lib/tools/hashline-tools.js +456 -0
- package/dist/lib/tools/hashline-tools.js.map +1 -0
- package/dist/lib/types.d.ts +130 -0
- package/dist/lib/types.d.ts.map +1 -0
- package/dist/lib/types.js +2 -0
- package/dist/lib/types.js.map +1 -0
- package/dist/lib/ui/ansi.d.ts +40 -0
- package/dist/lib/ui/ansi.d.ts.map +1 -0
- package/dist/lib/ui/ansi.js +68 -0
- package/dist/lib/ui/ansi.js.map +1 -0
- package/dist/lib/ui/auth-menu.d.ts +76 -0
- package/dist/lib/ui/auth-menu.d.ts.map +1 -0
- package/dist/lib/ui/auth-menu.js +590 -0
- package/dist/lib/ui/auth-menu.js.map +1 -0
- package/dist/lib/ui/confirm.d.ts +11 -0
- package/dist/lib/ui/confirm.d.ts.map +1 -0
- package/dist/lib/ui/confirm.js +29 -0
- package/dist/lib/ui/confirm.js.map +1 -0
- package/dist/lib/ui/copy.d.ts +123 -0
- package/dist/lib/ui/copy.d.ts.map +1 -0
- package/dist/lib/ui/copy.js +127 -0
- package/dist/lib/ui/copy.js.map +1 -0
- package/dist/lib/ui/format.d.ts +62 -0
- package/dist/lib/ui/format.d.ts.map +1 -0
- package/dist/lib/ui/format.js +205 -0
- package/dist/lib/ui/format.js.map +1 -0
- package/dist/lib/ui/runtime.d.ts +43 -0
- package/dist/lib/ui/runtime.d.ts.map +1 -0
- package/dist/lib/ui/runtime.js +69 -0
- package/dist/lib/ui/runtime.js.map +1 -0
- package/dist/lib/ui/select.d.ts +60 -0
- package/dist/lib/ui/select.d.ts.map +1 -0
- package/dist/lib/ui/select.js +467 -0
- package/dist/lib/ui/select.js.map +1 -0
- package/dist/lib/ui/theme.d.ts +56 -0
- package/dist/lib/ui/theme.d.ts.map +1 -0
- package/dist/lib/ui/theme.js +186 -0
- package/dist/lib/ui/theme.js.map +1 -0
- package/dist/lib/unified-settings.d.ts +71 -0
- package/dist/lib/unified-settings.d.ts.map +1 -0
- package/dist/lib/unified-settings.js +299 -0
- package/dist/lib/unified-settings.js.map +1 -0
- package/dist/lib/utils.d.ts +29 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +54 -0
- package/dist/lib/utils.js.map +1 -0
- package/package.json +115 -0
- package/scripts/audit-dev-allowlist.js +128 -0
- package/scripts/bench-format/hashline-v2.mjs +642 -0
- package/scripts/bench-format/models.mjs +105 -0
- package/scripts/bench-format/opencode.mjs +205 -0
- package/scripts/bench-format/render.mjs +496 -0
- package/scripts/bench-format/stats.mjs +54 -0
- package/scripts/bench-format/tasks.mjs +151 -0
- package/scripts/benchmark-edit-formats.mjs +1161 -0
- package/scripts/benchmark-render-dashboard.mjs +49 -0
- package/scripts/codex-multi-auth.js +6 -0
- package/scripts/codex-routing.js +34 -0
- package/scripts/codex.js +122 -0
- package/scripts/copy-oauth-success.js +37 -0
- package/scripts/install-opencode-codex-auth.js +193 -0
- package/scripts/test-all-models.sh +7 -0
- package/scripts/test-model-matrix.js +424 -0
- package/scripts/validate-model-map.sh +7 -0
|
@@ -0,0 +1,814 @@
|
|
|
1
|
+
import { logDebug, logWarn } from "../logger.js";
|
|
2
|
+
import { TOOL_REMAP_MESSAGE } from "../prompts/codex.js";
|
|
3
|
+
import { CODEX_OPENCODE_BRIDGE } from "../prompts/codex-opencode-bridge.js";
|
|
4
|
+
import { getOpenCodeCodexPrompt } from "../prompts/opencode-codex.js";
|
|
5
|
+
import { getNormalizedModel } from "./helpers/model-map.js";
|
|
6
|
+
import { filterOpenCodeSystemPromptsWithCachedPrompt, normalizeOrphanedToolOutputs, injectMissingToolOutputs, } from "./helpers/input-utils.js";
|
|
7
|
+
import { cleanupToolDefinitions } from "./helpers/tool-utils.js";
|
|
8
|
+
const PLAN_MODE_ONLY_TOOLS = new Set(["request_user_input"]);
|
|
9
|
+
export { isOpenCodeSystemPrompt, filterOpenCodeSystemPromptsWithCachedPrompt, } from "./helpers/input-utils.js";
|
|
10
|
+
/**
|
|
11
|
+
* Normalize model name to Codex-supported variants
|
|
12
|
+
*
|
|
13
|
+
* Uses explicit model map for known models, with fallback pattern matching
|
|
14
|
+
* for unknown/custom model names.
|
|
15
|
+
*
|
|
16
|
+
* @param model - Original model name (e.g., "gpt-5-codex-low", "openai/gpt-5-codex")
|
|
17
|
+
* @returns Normalized model name (e.g., "gpt-5-codex", "gpt-5.1-codex-max")
|
|
18
|
+
*/
|
|
19
|
+
export function normalizeModel(model) {
|
|
20
|
+
if (!model)
|
|
21
|
+
return "gpt-5.1";
|
|
22
|
+
// Strip provider prefix if present (e.g., "openai/gpt-5-codex" → "gpt-5-codex")
|
|
23
|
+
const modelId = model.includes("/") ? model.split("/").pop() ?? model : model;
|
|
24
|
+
// Try explicit model map first (handles all known model variants)
|
|
25
|
+
const mappedModel = getNormalizedModel(modelId);
|
|
26
|
+
if (mappedModel) {
|
|
27
|
+
return mappedModel;
|
|
28
|
+
}
|
|
29
|
+
// Fallback: Pattern-based matching for unknown/custom model names
|
|
30
|
+
// This preserves backwards compatibility with old verbose names
|
|
31
|
+
// like "GPT 5 Codex Low (ChatGPT Subscription)"
|
|
32
|
+
const normalized = modelId.toLowerCase();
|
|
33
|
+
// Priority order for pattern matching (most specific first):
|
|
34
|
+
// 1. GPT-5.3 Codex Spark (legacy alias -> canonical gpt-5-codex)
|
|
35
|
+
if (normalized.includes("gpt-5.3-codex-spark") ||
|
|
36
|
+
normalized.includes("gpt 5.3 codex spark")) {
|
|
37
|
+
return "gpt-5-codex";
|
|
38
|
+
}
|
|
39
|
+
// 2. GPT-5.3 Codex (legacy alias -> canonical gpt-5-codex)
|
|
40
|
+
if (normalized.includes("gpt-5.3-codex") ||
|
|
41
|
+
normalized.includes("gpt 5.3 codex")) {
|
|
42
|
+
return "gpt-5-codex";
|
|
43
|
+
}
|
|
44
|
+
// 3. GPT-5.2 Codex (legacy alias -> canonical gpt-5-codex)
|
|
45
|
+
if (normalized.includes("gpt-5.2-codex") ||
|
|
46
|
+
normalized.includes("gpt 5.2 codex")) {
|
|
47
|
+
return "gpt-5-codex";
|
|
48
|
+
}
|
|
49
|
+
// 4. GPT-5.2 (general purpose)
|
|
50
|
+
if (normalized.includes("gpt-5.2") || normalized.includes("gpt 5.2")) {
|
|
51
|
+
return "gpt-5.2";
|
|
52
|
+
}
|
|
53
|
+
// 5. GPT-5.1 Codex Max
|
|
54
|
+
if (normalized.includes("gpt-5.1-codex-max") ||
|
|
55
|
+
normalized.includes("gpt 5.1 codex max")) {
|
|
56
|
+
return "gpt-5.1-codex-max";
|
|
57
|
+
}
|
|
58
|
+
// 6. GPT-5.1 Codex Mini
|
|
59
|
+
if (normalized.includes("gpt-5.1-codex-mini") ||
|
|
60
|
+
normalized.includes("gpt 5.1 codex mini")) {
|
|
61
|
+
return "gpt-5.1-codex-mini";
|
|
62
|
+
}
|
|
63
|
+
// 7. Legacy Codex Mini
|
|
64
|
+
if (normalized.includes("codex-mini-latest") ||
|
|
65
|
+
normalized.includes("gpt-5-codex-mini") ||
|
|
66
|
+
normalized.includes("gpt 5 codex mini")) {
|
|
67
|
+
return "gpt-5.1-codex-mini";
|
|
68
|
+
}
|
|
69
|
+
// 8. GPT-5 Codex canonical + GPT-5.1 Codex legacy alias
|
|
70
|
+
if (normalized.includes("gpt-5-codex") ||
|
|
71
|
+
normalized.includes("gpt 5 codex")) {
|
|
72
|
+
return "gpt-5-codex";
|
|
73
|
+
}
|
|
74
|
+
// 9. GPT-5.1 Codex (legacy alias)
|
|
75
|
+
if (normalized.includes("gpt-5.1-codex") ||
|
|
76
|
+
normalized.includes("gpt 5.1 codex")) {
|
|
77
|
+
return "gpt-5-codex";
|
|
78
|
+
}
|
|
79
|
+
// 10. GPT-5.1 (general-purpose)
|
|
80
|
+
if (normalized.includes("gpt-5.1") || normalized.includes("gpt 5.1")) {
|
|
81
|
+
return "gpt-5.1";
|
|
82
|
+
}
|
|
83
|
+
// 11. GPT-5 Codex family (any other variant with "codex")
|
|
84
|
+
if (normalized.includes("codex")) {
|
|
85
|
+
return "gpt-5-codex";
|
|
86
|
+
}
|
|
87
|
+
// 12. GPT-5 family (any variant) - default to 5.1
|
|
88
|
+
if (normalized.includes("gpt-5") || normalized.includes("gpt 5")) {
|
|
89
|
+
return "gpt-5.1";
|
|
90
|
+
}
|
|
91
|
+
// Default fallback
|
|
92
|
+
return "gpt-5.1";
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Extract configuration for a specific model
|
|
96
|
+
* Merges global options with model-specific options (model-specific takes precedence)
|
|
97
|
+
* @param modelName - Model name (e.g., "gpt-5-codex")
|
|
98
|
+
* @param userConfig - Full user configuration object
|
|
99
|
+
* @returns Merged configuration for this model
|
|
100
|
+
*/
|
|
101
|
+
export function getModelConfig(modelName, userConfig = { global: {}, models: {} }) {
|
|
102
|
+
const globalOptions = userConfig.global ?? {};
|
|
103
|
+
const modelMap = userConfig.models ?? {};
|
|
104
|
+
const stripProviderPrefix = (name) => name.includes("/") ? (name.split("/").pop() ?? name) : name;
|
|
105
|
+
const getVariantFromModelName = (name) => {
|
|
106
|
+
const stripped = stripProviderPrefix(name).toLowerCase();
|
|
107
|
+
const match = stripped.match(/-(none|minimal|low|medium|high|xhigh)$/);
|
|
108
|
+
if (!match)
|
|
109
|
+
return undefined;
|
|
110
|
+
const variant = match[1];
|
|
111
|
+
if (variant === "none" ||
|
|
112
|
+
variant === "minimal" ||
|
|
113
|
+
variant === "low" ||
|
|
114
|
+
variant === "medium" ||
|
|
115
|
+
variant === "high" ||
|
|
116
|
+
variant === "xhigh") {
|
|
117
|
+
return variant;
|
|
118
|
+
}
|
|
119
|
+
return undefined;
|
|
120
|
+
};
|
|
121
|
+
const removeVariantSuffix = (name) => stripProviderPrefix(name).replace(/-(none|minimal|low|medium|high|xhigh)$/i, "");
|
|
122
|
+
const findModelEntry = (candidates) => {
|
|
123
|
+
for (const key of candidates) {
|
|
124
|
+
const entry = modelMap[key];
|
|
125
|
+
if (entry)
|
|
126
|
+
return { key, entry };
|
|
127
|
+
}
|
|
128
|
+
return undefined;
|
|
129
|
+
};
|
|
130
|
+
const strippedModelName = stripProviderPrefix(modelName);
|
|
131
|
+
const normalizedModelName = normalizeModel(strippedModelName);
|
|
132
|
+
const normalizedBaseModelName = normalizeModel(removeVariantSuffix(strippedModelName));
|
|
133
|
+
const baseModelName = removeVariantSuffix(strippedModelName);
|
|
134
|
+
const requestedVariant = getVariantFromModelName(strippedModelName);
|
|
135
|
+
// 1) Honor exact per-model keys first (including variant-specific keys)
|
|
136
|
+
const directMatch = findModelEntry([modelName, strippedModelName]);
|
|
137
|
+
if (directMatch?.entry?.options) {
|
|
138
|
+
return { ...globalOptions, ...directMatch.entry.options };
|
|
139
|
+
}
|
|
140
|
+
// 2) Resolve to base model config (supports provider-prefixed names + aliases)
|
|
141
|
+
const baseMatch = findModelEntry([
|
|
142
|
+
baseModelName,
|
|
143
|
+
normalizedBaseModelName,
|
|
144
|
+
normalizedModelName,
|
|
145
|
+
]);
|
|
146
|
+
const baseOptions = baseMatch?.entry?.options ?? {};
|
|
147
|
+
// 3) If model requested a variant, merge variant options from base model config
|
|
148
|
+
const variantConfig = requestedVariant && baseMatch?.entry?.variants
|
|
149
|
+
? baseMatch.entry.variants[requestedVariant]
|
|
150
|
+
: undefined;
|
|
151
|
+
let variantOptions = {};
|
|
152
|
+
if (variantConfig) {
|
|
153
|
+
const { disabled: _disabled, ...rest } = variantConfig;
|
|
154
|
+
void _disabled;
|
|
155
|
+
variantOptions = rest;
|
|
156
|
+
}
|
|
157
|
+
// Model-specific options override global options
|
|
158
|
+
return { ...globalOptions, ...baseOptions, ...variantOptions };
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Apply fast-session defaults to reduce latency/cost for interactive sessions.
|
|
162
|
+
* Explicit user/model overrides still take precedence.
|
|
163
|
+
*/
|
|
164
|
+
export function applyFastSessionDefaults(userConfig = { global: {}, models: {} }) {
|
|
165
|
+
const global = userConfig.global ?? {};
|
|
166
|
+
return {
|
|
167
|
+
...userConfig,
|
|
168
|
+
global: {
|
|
169
|
+
...global,
|
|
170
|
+
reasoningEffort: global.reasoningEffort ?? "low",
|
|
171
|
+
textVerbosity: global.textVerbosity ?? "low",
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
function resolveReasoningConfig(modelName, modelConfig, body) {
|
|
176
|
+
const providerOpenAI = body.providerOptions?.openai;
|
|
177
|
+
const existingEffort = body.reasoning?.effort ?? providerOpenAI?.reasoningEffort;
|
|
178
|
+
const existingSummary = body.reasoning?.summary ?? providerOpenAI?.reasoningSummary;
|
|
179
|
+
const mergedConfig = {
|
|
180
|
+
...modelConfig,
|
|
181
|
+
...(existingEffort ? { reasoningEffort: existingEffort } : {}),
|
|
182
|
+
...(existingSummary ? { reasoningSummary: existingSummary } : {}),
|
|
183
|
+
};
|
|
184
|
+
return getReasoningConfig(modelName, mergedConfig);
|
|
185
|
+
}
|
|
186
|
+
function resolveTextVerbosity(modelConfig, body) {
|
|
187
|
+
const providerOpenAI = body.providerOptions?.openai;
|
|
188
|
+
return (body.text?.verbosity ??
|
|
189
|
+
providerOpenAI?.textVerbosity ??
|
|
190
|
+
modelConfig.textVerbosity ??
|
|
191
|
+
"medium");
|
|
192
|
+
}
|
|
193
|
+
function resolveInclude(modelConfig, body) {
|
|
194
|
+
const providerOpenAI = body.providerOptions?.openai;
|
|
195
|
+
const base = body.include ??
|
|
196
|
+
providerOpenAI?.include ??
|
|
197
|
+
modelConfig.include ??
|
|
198
|
+
["reasoning.encrypted_content"];
|
|
199
|
+
const include = Array.from(new Set(base.filter(Boolean)));
|
|
200
|
+
if (!include.includes("reasoning.encrypted_content")) {
|
|
201
|
+
include.push("reasoning.encrypted_content");
|
|
202
|
+
}
|
|
203
|
+
return include;
|
|
204
|
+
}
|
|
205
|
+
function parseCollaborationMode(value) {
|
|
206
|
+
if (!value)
|
|
207
|
+
return undefined;
|
|
208
|
+
const normalized = value.trim().toLowerCase();
|
|
209
|
+
if (normalized === "plan")
|
|
210
|
+
return "plan";
|
|
211
|
+
if (normalized === "default")
|
|
212
|
+
return "default";
|
|
213
|
+
return undefined;
|
|
214
|
+
}
|
|
215
|
+
function extractMessageText(content) {
|
|
216
|
+
if (typeof content === "string")
|
|
217
|
+
return content;
|
|
218
|
+
if (!Array.isArray(content))
|
|
219
|
+
return "";
|
|
220
|
+
return content
|
|
221
|
+
.map((item) => {
|
|
222
|
+
if (typeof item === "string")
|
|
223
|
+
return item;
|
|
224
|
+
if (!item || typeof item !== "object")
|
|
225
|
+
return "";
|
|
226
|
+
const typedItem = item;
|
|
227
|
+
return typeof typedItem.text === "string" ? typedItem.text : "";
|
|
228
|
+
})
|
|
229
|
+
.filter(Boolean)
|
|
230
|
+
.join("\n");
|
|
231
|
+
}
|
|
232
|
+
function detectCollaborationMode(body) {
|
|
233
|
+
const envMode = parseCollaborationMode(process.env.CODEX_COLLABORATION_MODE) ??
|
|
234
|
+
parseCollaborationMode(process.env.OPENCODE_COLLABORATION_MODE);
|
|
235
|
+
if (envMode)
|
|
236
|
+
return envMode;
|
|
237
|
+
if (!Array.isArray(body.input))
|
|
238
|
+
return "unknown";
|
|
239
|
+
let sawPlan = false;
|
|
240
|
+
let sawDefault = false;
|
|
241
|
+
for (const item of body.input) {
|
|
242
|
+
if (!item || typeof item !== "object")
|
|
243
|
+
continue;
|
|
244
|
+
const role = typeof item.role === "string" ? item.role.toLowerCase() : "";
|
|
245
|
+
if (role !== "developer" && role !== "system")
|
|
246
|
+
continue;
|
|
247
|
+
const text = extractMessageText(item.content);
|
|
248
|
+
if (!text)
|
|
249
|
+
continue;
|
|
250
|
+
if (/collaboration mode:\s*plan/i.test(text) || /in Plan mode/i.test(text)) {
|
|
251
|
+
sawPlan = true;
|
|
252
|
+
}
|
|
253
|
+
if (/collaboration mode:\s*default/i.test(text) || /in Default mode/i.test(text)) {
|
|
254
|
+
sawDefault = true;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
if (sawPlan && !sawDefault)
|
|
258
|
+
return "plan";
|
|
259
|
+
if (sawDefault)
|
|
260
|
+
return "default";
|
|
261
|
+
return "unknown";
|
|
262
|
+
}
|
|
263
|
+
function sanitizePlanOnlyTools(tools, mode) {
|
|
264
|
+
if (!Array.isArray(tools) || mode === "plan")
|
|
265
|
+
return tools;
|
|
266
|
+
let removed = 0;
|
|
267
|
+
const filtered = tools.filter((entry) => {
|
|
268
|
+
if (!entry || typeof entry !== "object")
|
|
269
|
+
return true;
|
|
270
|
+
const functionDef = entry.function;
|
|
271
|
+
if (!functionDef || typeof functionDef !== "object")
|
|
272
|
+
return true;
|
|
273
|
+
const name = functionDef.name;
|
|
274
|
+
if (typeof name !== "string")
|
|
275
|
+
return true;
|
|
276
|
+
if (!PLAN_MODE_ONLY_TOOLS.has(name))
|
|
277
|
+
return true;
|
|
278
|
+
removed++;
|
|
279
|
+
return false;
|
|
280
|
+
});
|
|
281
|
+
if (removed > 0) {
|
|
282
|
+
logWarn(`Removed ${removed} plan-mode-only tool definition(s) because collaboration mode is ${mode}`);
|
|
283
|
+
}
|
|
284
|
+
return filtered;
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Configure reasoning parameters based on model variant and user config
|
|
288
|
+
*
|
|
289
|
+
* NOTE: This plugin follows Codex CLI defaults instead of opencode defaults because:
|
|
290
|
+
* - We're accessing the ChatGPT backend API (not OpenAI Platform API)
|
|
291
|
+
* - opencode explicitly excludes gpt-5-codex from automatic reasoning configuration
|
|
292
|
+
* - Codex CLI has been thoroughly tested against this backend
|
|
293
|
+
*
|
|
294
|
+
* @param originalModel - Original model name before normalization
|
|
295
|
+
* @param userConfig - User configuration object
|
|
296
|
+
* @returns Reasoning configuration
|
|
297
|
+
*/
|
|
298
|
+
export function getReasoningConfig(modelName, userConfig = {}) {
|
|
299
|
+
const normalizedName = modelName?.toLowerCase() ?? "";
|
|
300
|
+
// Canonical GPT-5 Codex (stable) defaults to high and does not support "none".
|
|
301
|
+
const isGpt5Codex = normalizedName.includes("gpt-5-codex") ||
|
|
302
|
+
normalizedName.includes("gpt 5 codex");
|
|
303
|
+
// Legacy GPT-5.3 Codex alias behavior (supports xhigh, but not "none")
|
|
304
|
+
const isGpt53Codex = normalizedName.includes("gpt-5.3-codex") ||
|
|
305
|
+
normalizedName.includes("gpt 5.3 codex");
|
|
306
|
+
// Legacy GPT-5.2 Codex alias behavior (supports xhigh, but not "none")
|
|
307
|
+
const isGpt52Codex = normalizedName.includes("gpt-5.2-codex") ||
|
|
308
|
+
normalizedName.includes("gpt 5.2 codex");
|
|
309
|
+
// GPT-5.2 general purpose (not codex variant)
|
|
310
|
+
const isGpt52General = (normalizedName.includes("gpt-5.2") || normalizedName.includes("gpt 5.2")) &&
|
|
311
|
+
!isGpt52Codex;
|
|
312
|
+
const isCodexMax = normalizedName.includes("codex-max") ||
|
|
313
|
+
normalizedName.includes("codex max");
|
|
314
|
+
const isCodexMini = normalizedName.includes("codex-mini") ||
|
|
315
|
+
normalizedName.includes("codex mini") ||
|
|
316
|
+
normalizedName.includes("codex_mini") ||
|
|
317
|
+
normalizedName.includes("codex-mini-latest");
|
|
318
|
+
const isCodex = normalizedName.includes("codex") && !isCodexMini;
|
|
319
|
+
const isLightweight = !isCodexMini &&
|
|
320
|
+
(normalizedName.includes("nano") ||
|
|
321
|
+
normalizedName.includes("mini"));
|
|
322
|
+
// GPT-5.1 general purpose (not codex variants) - supports "none" per OpenAI API docs
|
|
323
|
+
const isGpt51General = (normalizedName.includes("gpt-5.1") ||
|
|
324
|
+
normalizedName.includes("gpt 5.1") ||
|
|
325
|
+
normalizedName === "gpt-5" ||
|
|
326
|
+
normalizedName.startsWith("gpt-5-")) &&
|
|
327
|
+
!isCodex &&
|
|
328
|
+
!isGpt52General &&
|
|
329
|
+
!isCodexMax &&
|
|
330
|
+
!isCodexMini;
|
|
331
|
+
// GPT-5.2 general, legacy GPT-5.2/5.3 Codex aliases, and Codex Max support xhigh reasoning
|
|
332
|
+
const supportsXhigh = isGpt52General || isGpt53Codex || isGpt52Codex || isCodexMax;
|
|
333
|
+
// GPT 5.1 general and GPT 5.2 general support "none" reasoning per:
|
|
334
|
+
// - OpenAI API docs: "gpt-5.1 defaults to none, supports: none, low, medium, high"
|
|
335
|
+
// - Codex CLI: ReasoningEffort enum includes None variant (codex-rs/protocol/src/openai_models.rs)
|
|
336
|
+
// - Codex CLI: docs/config.md lists "none" as valid for model_reasoning_effort
|
|
337
|
+
// - gpt-5.2 (being newer) also supports: none, low, medium, high, xhigh
|
|
338
|
+
// - Codex models (including GPT-5 Codex and legacy GPT-5.3/5.2 Codex aliases) do NOT support "none"
|
|
339
|
+
const supportsNone = isGpt52General || isGpt51General;
|
|
340
|
+
// Default based on model type (Codex CLI defaults + plugin opinionated tuning)
|
|
341
|
+
// Note: OpenAI docs say gpt-5.1 defaults to "none", but we default to "medium"
|
|
342
|
+
// for better coding assistance unless user explicitly requests "none".
|
|
343
|
+
// - Canonical GPT-5 Codex defaults to high in stable Codex.
|
|
344
|
+
// - Legacy GPT-5.3/5.2 Codex aliases default to xhigh for backward compatibility.
|
|
345
|
+
const defaultEffort = isCodexMini
|
|
346
|
+
? "medium"
|
|
347
|
+
: isGpt5Codex
|
|
348
|
+
? "high"
|
|
349
|
+
: isGpt53Codex || isGpt52Codex
|
|
350
|
+
? "xhigh"
|
|
351
|
+
: supportsXhigh
|
|
352
|
+
? "high"
|
|
353
|
+
: isLightweight
|
|
354
|
+
? "minimal"
|
|
355
|
+
: "medium";
|
|
356
|
+
// Get user-requested effort
|
|
357
|
+
let effort = userConfig.reasoningEffort || defaultEffort;
|
|
358
|
+
if (isCodexMini) {
|
|
359
|
+
if (effort === "minimal" || effort === "low" || effort === "none") {
|
|
360
|
+
effort = "medium";
|
|
361
|
+
}
|
|
362
|
+
if (effort === "xhigh") {
|
|
363
|
+
effort = "high";
|
|
364
|
+
}
|
|
365
|
+
if (effort !== "high" && effort !== "medium") {
|
|
366
|
+
effort = "medium";
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
// For models that don't support xhigh, downgrade to high
|
|
370
|
+
if (!supportsXhigh && effort === "xhigh") {
|
|
371
|
+
effort = "high";
|
|
372
|
+
}
|
|
373
|
+
// For models that don't support "none", upgrade to "low"
|
|
374
|
+
// (Codex models don't support "none" - only GPT-5.1 and GPT-5.2 general purpose do)
|
|
375
|
+
if (!supportsNone && effort === "none") {
|
|
376
|
+
effort = "low";
|
|
377
|
+
}
|
|
378
|
+
// Normalize "minimal" to "low" for Codex families
|
|
379
|
+
// Codex CLI presets are low/medium/high (or xhigh for Codex Max / GPT-5.3/5.2 Codex)
|
|
380
|
+
if (isCodex && effort === "minimal") {
|
|
381
|
+
effort = "low";
|
|
382
|
+
}
|
|
383
|
+
const summary = sanitizeReasoningSummary(userConfig.reasoningSummary);
|
|
384
|
+
return {
|
|
385
|
+
effort,
|
|
386
|
+
summary,
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
function sanitizeReasoningSummary(summary) {
|
|
390
|
+
if (!summary)
|
|
391
|
+
return "auto";
|
|
392
|
+
const normalized = summary.toLowerCase();
|
|
393
|
+
if (normalized === "concise" || normalized === "detailed" || normalized === "auto") {
|
|
394
|
+
return normalized;
|
|
395
|
+
}
|
|
396
|
+
return "auto";
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Filter input array for stateless Codex API (store: false)
|
|
400
|
+
*
|
|
401
|
+
* Two transformations needed:
|
|
402
|
+
* 1. Remove AI SDK-specific items (not supported by Codex API)
|
|
403
|
+
* 2. Strip IDs from all remaining items (stateless mode)
|
|
404
|
+
*
|
|
405
|
+
* AI SDK constructs to REMOVE (not in OpenAI Responses API spec):
|
|
406
|
+
* - type: "item_reference" - AI SDK uses this for server-side state lookup
|
|
407
|
+
*
|
|
408
|
+
* Items to KEEP (strip IDs):
|
|
409
|
+
* - type: "message" - Conversation messages (provides context to LLM)
|
|
410
|
+
* - type: "function_call" - Tool calls from conversation
|
|
411
|
+
* - type: "function_call_output" - Tool results from conversation
|
|
412
|
+
*
|
|
413
|
+
* Context is maintained through:
|
|
414
|
+
* - Full message history (without IDs)
|
|
415
|
+
* - reasoning.encrypted_content (for reasoning continuity)
|
|
416
|
+
*
|
|
417
|
+
* @param input - Original input array from OpenCode/AI SDK
|
|
418
|
+
* @returns Filtered input array compatible with Codex API
|
|
419
|
+
*/
|
|
420
|
+
export function filterInput(input) {
|
|
421
|
+
if (!Array.isArray(input))
|
|
422
|
+
return input;
|
|
423
|
+
return input
|
|
424
|
+
.filter((item) => {
|
|
425
|
+
// Remove AI SDK constructs not supported by Codex API
|
|
426
|
+
if (item.type === "item_reference") {
|
|
427
|
+
return false; // AI SDK only - references server state
|
|
428
|
+
}
|
|
429
|
+
return true; // Keep all other items
|
|
430
|
+
})
|
|
431
|
+
.map((item) => {
|
|
432
|
+
// Strip IDs from all items (Codex API stateless mode)
|
|
433
|
+
if (item.id) {
|
|
434
|
+
const { id: _omit, ...itemWithoutId } = item;
|
|
435
|
+
void _omit;
|
|
436
|
+
return itemWithoutId;
|
|
437
|
+
}
|
|
438
|
+
return item;
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* Trim long stateless histories for low-latency sessions.
|
|
443
|
+
* Keeps a small leading developer/system context plus the most recent items.
|
|
444
|
+
*/
|
|
445
|
+
export function trimInputForFastSession(input, maxItems, options) {
|
|
446
|
+
if (!Array.isArray(input))
|
|
447
|
+
return input;
|
|
448
|
+
const MAX_HEAD_INSTRUCTION_CHARS = 1200;
|
|
449
|
+
const MAX_HEAD_INSTRUCTION_CHARS_TRIVIAL = 400;
|
|
450
|
+
if (options?.preferLatestUserOnly) {
|
|
451
|
+
const keepIndexes = new Set();
|
|
452
|
+
for (let i = 0; i < input.length; i++) {
|
|
453
|
+
const item = input[i];
|
|
454
|
+
if (!item || typeof item !== "object")
|
|
455
|
+
continue;
|
|
456
|
+
const role = typeof item?.role === "string" ? item.role : "";
|
|
457
|
+
if (role === "developer" || role === "system") {
|
|
458
|
+
const headText = extractMessageText(item.content);
|
|
459
|
+
if (headText.length <= MAX_HEAD_INSTRUCTION_CHARS_TRIVIAL) {
|
|
460
|
+
keepIndexes.add(i);
|
|
461
|
+
}
|
|
462
|
+
break;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
for (let i = input.length - 1; i >= 0; i--) {
|
|
466
|
+
const item = input[i];
|
|
467
|
+
const role = typeof item?.role === "string" ? item.role.toLowerCase() : "";
|
|
468
|
+
if (role === "user") {
|
|
469
|
+
keepIndexes.add(i);
|
|
470
|
+
break;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
const compacted = input.filter((_item, index) => keepIndexes.has(index));
|
|
474
|
+
if (compacted.length > 0)
|
|
475
|
+
return compacted;
|
|
476
|
+
}
|
|
477
|
+
const safeMax = Math.max(8, Math.floor(maxItems));
|
|
478
|
+
const keepIndexes = new Set();
|
|
479
|
+
const excludedHeadIndexes = new Set();
|
|
480
|
+
let keptHead = 0;
|
|
481
|
+
for (let i = 0; i < input.length && keptHead < 2; i++) {
|
|
482
|
+
const item = input[i];
|
|
483
|
+
if (!item || typeof item !== "object")
|
|
484
|
+
break;
|
|
485
|
+
const role = typeof item?.role === "string" ? item.role : "";
|
|
486
|
+
if (role === "developer" || role === "system") {
|
|
487
|
+
const headText = extractMessageText(item.content);
|
|
488
|
+
if (headText.length <= MAX_HEAD_INSTRUCTION_CHARS) {
|
|
489
|
+
keepIndexes.add(i);
|
|
490
|
+
keptHead++;
|
|
491
|
+
}
|
|
492
|
+
else {
|
|
493
|
+
excludedHeadIndexes.add(i);
|
|
494
|
+
}
|
|
495
|
+
continue;
|
|
496
|
+
}
|
|
497
|
+
break;
|
|
498
|
+
}
|
|
499
|
+
for (let i = Math.max(0, input.length - safeMax); i < input.length; i++) {
|
|
500
|
+
if (excludedHeadIndexes.has(i))
|
|
501
|
+
continue;
|
|
502
|
+
keepIndexes.add(i);
|
|
503
|
+
}
|
|
504
|
+
const trimmed = input.filter((_item, index) => keepIndexes.has(index));
|
|
505
|
+
if (trimmed.length === 0)
|
|
506
|
+
return input;
|
|
507
|
+
if (input.length <= maxItems && excludedHeadIndexes.size === 0)
|
|
508
|
+
return input;
|
|
509
|
+
if (trimmed.length <= safeMax)
|
|
510
|
+
return trimmed;
|
|
511
|
+
return trimmed.slice(trimmed.length - safeMax);
|
|
512
|
+
}
|
|
513
|
+
function isTrivialLatestPrompt(text) {
|
|
514
|
+
const normalized = text.trim();
|
|
515
|
+
if (!normalized)
|
|
516
|
+
return false;
|
|
517
|
+
if (normalized.length > 220)
|
|
518
|
+
return false;
|
|
519
|
+
if (normalized.includes("\n"))
|
|
520
|
+
return false;
|
|
521
|
+
if (normalized.includes("```"))
|
|
522
|
+
return false;
|
|
523
|
+
if (/(^|\n)\s*(?:[-*]|\d+\.)\s+\S/m.test(normalized))
|
|
524
|
+
return false;
|
|
525
|
+
if (/https?:\/\//i.test(normalized))
|
|
526
|
+
return false;
|
|
527
|
+
if (/\|.+\|/.test(normalized))
|
|
528
|
+
return false;
|
|
529
|
+
return true;
|
|
530
|
+
}
|
|
531
|
+
function isStructurallyComplexPrompt(text) {
|
|
532
|
+
const normalized = text.trim();
|
|
533
|
+
if (!normalized)
|
|
534
|
+
return false;
|
|
535
|
+
if (normalized.includes("```"))
|
|
536
|
+
return true;
|
|
537
|
+
const lineCount = normalized.split(/\r?\n/).filter(Boolean).length;
|
|
538
|
+
if (lineCount >= 3)
|
|
539
|
+
return true;
|
|
540
|
+
if (/(^|\n)\s*(?:[-*]|\d+\.)\s+\S/m.test(normalized))
|
|
541
|
+
return true;
|
|
542
|
+
if (/\|.+\|/.test(normalized))
|
|
543
|
+
return true;
|
|
544
|
+
return false;
|
|
545
|
+
}
|
|
546
|
+
function isComplexFastSessionRequest(body, maxItems) {
|
|
547
|
+
const input = Array.isArray(body.input) ? body.input : [];
|
|
548
|
+
const lookbackWindow = Math.max(12, Math.floor(maxItems / 2));
|
|
549
|
+
const recentItems = input.slice(-lookbackWindow);
|
|
550
|
+
const userTexts = [];
|
|
551
|
+
for (const item of recentItems) {
|
|
552
|
+
if (!item || typeof item !== "object")
|
|
553
|
+
continue;
|
|
554
|
+
if (item.type === "function_call" || item.type === "function_call_output") {
|
|
555
|
+
return true;
|
|
556
|
+
}
|
|
557
|
+
const role = typeof item.role === "string" ? item.role.toLowerCase() : "";
|
|
558
|
+
if (role !== "user")
|
|
559
|
+
continue;
|
|
560
|
+
const text = extractMessageText(item.content);
|
|
561
|
+
if (!text)
|
|
562
|
+
continue;
|
|
563
|
+
userTexts.push(text);
|
|
564
|
+
}
|
|
565
|
+
if (userTexts.length === 0)
|
|
566
|
+
return false;
|
|
567
|
+
const latestUserText = userTexts[userTexts.length - 1];
|
|
568
|
+
if (latestUserText && isTrivialLatestPrompt(latestUserText)) {
|
|
569
|
+
return false;
|
|
570
|
+
}
|
|
571
|
+
const recentUserTexts = userTexts.slice(-3);
|
|
572
|
+
if (recentUserTexts.some(isStructurallyComplexPrompt))
|
|
573
|
+
return true;
|
|
574
|
+
return false;
|
|
575
|
+
}
|
|
576
|
+
function getLatestUserText(input) {
|
|
577
|
+
if (!Array.isArray(input))
|
|
578
|
+
return undefined;
|
|
579
|
+
for (let i = input.length - 1; i >= 0; i--) {
|
|
580
|
+
const item = input[i];
|
|
581
|
+
if (!item || typeof item !== "object")
|
|
582
|
+
continue;
|
|
583
|
+
const role = typeof item.role === "string" ? item.role.toLowerCase() : "";
|
|
584
|
+
if (role !== "user")
|
|
585
|
+
continue;
|
|
586
|
+
const text = extractMessageText(item.content);
|
|
587
|
+
if (text)
|
|
588
|
+
return text;
|
|
589
|
+
}
|
|
590
|
+
return undefined;
|
|
591
|
+
}
|
|
592
|
+
function compactInstructionsForFastSession(instructions, isTrivialTurn = false) {
|
|
593
|
+
const normalized = instructions.trim();
|
|
594
|
+
if (!normalized)
|
|
595
|
+
return instructions;
|
|
596
|
+
const MAX_FAST_INSTRUCTION_CHARS = isTrivialTurn ? 320 : 900;
|
|
597
|
+
if (normalized.length <= MAX_FAST_INSTRUCTION_CHARS) {
|
|
598
|
+
return instructions;
|
|
599
|
+
}
|
|
600
|
+
const splitIndex = normalized.lastIndexOf("\n", MAX_FAST_INSTRUCTION_CHARS);
|
|
601
|
+
const safeCutoff = splitIndex >= 180 ? splitIndex : MAX_FAST_INSTRUCTION_CHARS;
|
|
602
|
+
const compacted = normalized.slice(0, safeCutoff).trimEnd();
|
|
603
|
+
return `${compacted}\n\n[Fast session mode: keep answers concise, direct, and action-oriented. Do not output internal planning labels such as "Thinking:".]`;
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* Filter out OpenCode system prompts from input
|
|
607
|
+
* Used in CODEX_MODE to replace OpenCode prompts with Codex-OpenCode bridge
|
|
608
|
+
* @param input - Input array
|
|
609
|
+
* @returns Input array without OpenCode system prompts
|
|
610
|
+
*/
|
|
611
|
+
export async function filterOpenCodeSystemPrompts(input) {
|
|
612
|
+
if (!Array.isArray(input))
|
|
613
|
+
return input;
|
|
614
|
+
// Fetch cached OpenCode prompt for verification
|
|
615
|
+
let cachedPrompt = null;
|
|
616
|
+
try {
|
|
617
|
+
cachedPrompt = await getOpenCodeCodexPrompt();
|
|
618
|
+
}
|
|
619
|
+
catch {
|
|
620
|
+
// If fetch fails, fallback to text-based detection only
|
|
621
|
+
// This is safe because we still have the "starts with" check
|
|
622
|
+
}
|
|
623
|
+
return filterOpenCodeSystemPromptsWithCachedPrompt(input, cachedPrompt);
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* Add Codex-OpenCode bridge message to input if tools are present
|
|
627
|
+
* @param input - Input array
|
|
628
|
+
* @param hasTools - Whether tools are present in request
|
|
629
|
+
* @returns Input array with bridge message prepended if needed
|
|
630
|
+
*/
|
|
631
|
+
export function addCodexBridgeMessage(input, hasTools) {
|
|
632
|
+
if (!hasTools || !Array.isArray(input))
|
|
633
|
+
return input;
|
|
634
|
+
const bridgeMessage = {
|
|
635
|
+
type: "message",
|
|
636
|
+
role: "developer",
|
|
637
|
+
content: [
|
|
638
|
+
{
|
|
639
|
+
type: "input_text",
|
|
640
|
+
text: CODEX_OPENCODE_BRIDGE,
|
|
641
|
+
},
|
|
642
|
+
],
|
|
643
|
+
};
|
|
644
|
+
return [bridgeMessage, ...input];
|
|
645
|
+
}
|
|
646
|
+
/**
|
|
647
|
+
* Add tool remapping message to input if tools are present
|
|
648
|
+
* @param input - Input array
|
|
649
|
+
* @param hasTools - Whether tools are present in request
|
|
650
|
+
* @returns Input array with tool remap message prepended if needed
|
|
651
|
+
*/
|
|
652
|
+
export function addToolRemapMessage(input, hasTools) {
|
|
653
|
+
if (!hasTools || !Array.isArray(input))
|
|
654
|
+
return input;
|
|
655
|
+
const toolRemapMessage = {
|
|
656
|
+
type: "message",
|
|
657
|
+
role: "developer",
|
|
658
|
+
content: [
|
|
659
|
+
{
|
|
660
|
+
type: "input_text",
|
|
661
|
+
text: TOOL_REMAP_MESSAGE,
|
|
662
|
+
},
|
|
663
|
+
],
|
|
664
|
+
};
|
|
665
|
+
return [toolRemapMessage, ...input];
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* Transform request body for Codex API
|
|
669
|
+
*
|
|
670
|
+
* NOTE: Configuration follows Codex CLI patterns instead of opencode defaults:
|
|
671
|
+
* - opencode sets textVerbosity="low" for gpt-5, but Codex CLI uses "medium"
|
|
672
|
+
* - opencode excludes gpt-5-codex from reasoning configuration
|
|
673
|
+
* - This plugin uses store=false (stateless), requiring encrypted reasoning content
|
|
674
|
+
*
|
|
675
|
+
* @param body - Original request body
|
|
676
|
+
* @param codexInstructions - Codex system instructions
|
|
677
|
+
* @param userConfig - User configuration from loader
|
|
678
|
+
* @param codexMode - Enable CODEX_MODE (bridge prompt instead of tool remap) - defaults to true
|
|
679
|
+
* @param fastSession - Force low-latency output settings for faster responses
|
|
680
|
+
* @returns Transformed request body
|
|
681
|
+
*/
|
|
682
|
+
export async function transformRequestBody(body, codexInstructions, userConfig = { global: {}, models: {} }, codexMode = true, fastSession = false, fastSessionStrategy = "hybrid", fastSessionMaxInputItems = 30) {
|
|
683
|
+
const originalModel = body.model;
|
|
684
|
+
const normalizedModel = normalizeModel(body.model);
|
|
685
|
+
// Get model-specific configuration using ORIGINAL model name (config key)
|
|
686
|
+
// This allows per-model options like "gpt-5-codex-low" to work correctly
|
|
687
|
+
const lookupModel = originalModel || normalizedModel;
|
|
688
|
+
const modelConfig = getModelConfig(lookupModel, userConfig);
|
|
689
|
+
// Debug: Log which config was resolved
|
|
690
|
+
logDebug(`Model config lookup: "${lookupModel}" → normalized to "${normalizedModel}" for API`, {
|
|
691
|
+
hasModelSpecificConfig: !!userConfig.models?.[lookupModel],
|
|
692
|
+
resolvedConfig: modelConfig,
|
|
693
|
+
});
|
|
694
|
+
// Normalize model name for API call
|
|
695
|
+
body.model = normalizedModel;
|
|
696
|
+
const shouldUseNormalizedReasoningModel = normalizedModel === "gpt-5-codex" &&
|
|
697
|
+
lookupModel.toLowerCase().includes("codex");
|
|
698
|
+
const reasoningModel = shouldUseNormalizedReasoningModel
|
|
699
|
+
? normalizedModel
|
|
700
|
+
: lookupModel;
|
|
701
|
+
const shouldApplyFastSessionTuning = fastSession &&
|
|
702
|
+
(fastSessionStrategy === "always" ||
|
|
703
|
+
!isComplexFastSessionRequest(body, fastSessionMaxInputItems));
|
|
704
|
+
const latestUserText = getLatestUserText(body.input);
|
|
705
|
+
const isTrivialTurn = isTrivialLatestPrompt(latestUserText ?? "");
|
|
706
|
+
const shouldDisableToolsForTrivialTurn = shouldApplyFastSessionTuning &&
|
|
707
|
+
isTrivialTurn;
|
|
708
|
+
const shouldPreferLatestUserOnly = shouldApplyFastSessionTuning && isTrivialTurn;
|
|
709
|
+
// Codex required fields
|
|
710
|
+
// ChatGPT backend REQUIRES store=false (confirmed via testing)
|
|
711
|
+
body.store = false;
|
|
712
|
+
// Always set stream=true for API - response handling detects original intent
|
|
713
|
+
body.stream = true;
|
|
714
|
+
// Clean up tool definitions (implement strict "require" logic)
|
|
715
|
+
// Filters invalid required fields and ensures empty objects have placeholders
|
|
716
|
+
const collaborationMode = detectCollaborationMode(body);
|
|
717
|
+
if (body.tools) {
|
|
718
|
+
if (shouldDisableToolsForTrivialTurn) {
|
|
719
|
+
body.tools = undefined;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
if (body.tools) {
|
|
723
|
+
body.tools = cleanupToolDefinitions(body.tools);
|
|
724
|
+
body.tools = sanitizePlanOnlyTools(body.tools, collaborationMode);
|
|
725
|
+
}
|
|
726
|
+
body.instructions = shouldApplyFastSessionTuning
|
|
727
|
+
? compactInstructionsForFastSession(codexInstructions, isTrivialTurn)
|
|
728
|
+
: codexInstructions;
|
|
729
|
+
// Prompt caching relies on the host providing a stable prompt_cache_key
|
|
730
|
+
// (OpenCode passes its session identifier). We no longer synthesize one here.
|
|
731
|
+
// Filter and transform input
|
|
732
|
+
if (body.input && Array.isArray(body.input)) {
|
|
733
|
+
let inputItems = body.input;
|
|
734
|
+
if (shouldApplyFastSessionTuning) {
|
|
735
|
+
inputItems =
|
|
736
|
+
trimInputForFastSession(inputItems, fastSessionMaxInputItems, {
|
|
737
|
+
preferLatestUserOnly: shouldPreferLatestUserOnly,
|
|
738
|
+
}) ?? inputItems;
|
|
739
|
+
}
|
|
740
|
+
// Debug: Log original input message IDs before filtering
|
|
741
|
+
const originalIds = inputItems
|
|
742
|
+
.filter((item) => item.id)
|
|
743
|
+
.map((item) => item.id);
|
|
744
|
+
if (originalIds.length > 0) {
|
|
745
|
+
logDebug(`Filtering ${originalIds.length} message IDs from input:`, originalIds);
|
|
746
|
+
}
|
|
747
|
+
inputItems = filterInput(inputItems) ?? inputItems;
|
|
748
|
+
body.input = inputItems;
|
|
749
|
+
// istanbul ignore next -- filterInput always removes IDs; this is defensive debug code
|
|
750
|
+
const remainingIds = (body.input || [])
|
|
751
|
+
.filter((item) => item.id)
|
|
752
|
+
.map((item) => item.id);
|
|
753
|
+
// istanbul ignore if -- filterInput always removes IDs; defensive debug warning
|
|
754
|
+
if (remainingIds.length > 0) {
|
|
755
|
+
logWarn(`WARNING: ${remainingIds.length} IDs still present after filtering:`, remainingIds);
|
|
756
|
+
}
|
|
757
|
+
else if (originalIds.length > 0) {
|
|
758
|
+
logDebug(`Successfully removed all ${originalIds.length} message IDs`);
|
|
759
|
+
}
|
|
760
|
+
if (codexMode) {
|
|
761
|
+
// CODEX_MODE: Remove OpenCode system prompt, add bridge prompt
|
|
762
|
+
body.input = await filterOpenCodeSystemPrompts(body.input);
|
|
763
|
+
body.input = addCodexBridgeMessage(body.input, !!body.tools);
|
|
764
|
+
}
|
|
765
|
+
else {
|
|
766
|
+
// DEFAULT MODE: Keep original behavior with tool remap message
|
|
767
|
+
body.input = addToolRemapMessage(body.input, !!body.tools);
|
|
768
|
+
}
|
|
769
|
+
// Handle orphaned function_call_output items (where function_call was an item_reference that got filtered)
|
|
770
|
+
// Instead of removing orphans (which causes infinite loops as LLM loses tool results),
|
|
771
|
+
// convert them to messages to preserve context while avoiding API errors
|
|
772
|
+
if (body.input) {
|
|
773
|
+
body.input = normalizeOrphanedToolOutputs(body.input);
|
|
774
|
+
body.input = injectMissingToolOutputs(body.input);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
// Configure reasoning (prefer existing body/provider options, then config defaults)
|
|
778
|
+
const reasoningConfig = resolveReasoningConfig(reasoningModel, modelConfig, body);
|
|
779
|
+
body.reasoning = {
|
|
780
|
+
...body.reasoning,
|
|
781
|
+
...reasoningConfig,
|
|
782
|
+
};
|
|
783
|
+
// Configure text verbosity (support user config)
|
|
784
|
+
// Default: "medium" (matches Codex CLI default for all GPT-5 models)
|
|
785
|
+
body.text = {
|
|
786
|
+
...body.text,
|
|
787
|
+
verbosity: resolveTextVerbosity(modelConfig, body),
|
|
788
|
+
};
|
|
789
|
+
if (shouldApplyFastSessionTuning) {
|
|
790
|
+
// In fast-session mode, prioritize speed by clamping to minimum reasoning + verbosity.
|
|
791
|
+
// getReasoningConfig normalizes unsupported values per model family.
|
|
792
|
+
const fastReasoning = getReasoningConfig(reasoningModel, {
|
|
793
|
+
reasoningEffort: "none",
|
|
794
|
+
reasoningSummary: "auto",
|
|
795
|
+
});
|
|
796
|
+
body.reasoning = {
|
|
797
|
+
...body.reasoning,
|
|
798
|
+
...fastReasoning,
|
|
799
|
+
};
|
|
800
|
+
body.text = {
|
|
801
|
+
...body.text,
|
|
802
|
+
verbosity: "low",
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
// Add include for encrypted reasoning content
|
|
806
|
+
// Default: ["reasoning.encrypted_content"] (required for stateless operation with store=false)
|
|
807
|
+
// This allows reasoning context to persist across turns without server-side storage
|
|
808
|
+
body.include = resolveInclude(modelConfig, body);
|
|
809
|
+
// Remove unsupported parameters
|
|
810
|
+
body.max_output_tokens = undefined;
|
|
811
|
+
body.max_completion_tokens = undefined;
|
|
812
|
+
return body;
|
|
813
|
+
}
|
|
814
|
+
//# sourceMappingURL=request-transformer.js.map
|