@golba98/codexa 1.0.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.
- package/README.md +320 -0
- package/bin/codexa.js +445 -0
- package/package.json +45 -0
- package/scripts/audit-codexa-capabilities.mjs +466 -0
- package/scripts/smoke-terminal-bench.mjs +35 -0
- package/src/app.tsx +4561 -0
- package/src/appRenderStability.test.ts +131 -0
- package/src/commands/handler.test.ts +643 -0
- package/src/commands/handler.ts +875 -0
- package/src/config/launchArgs.test.ts +158 -0
- package/src/config/launchArgs.ts +186 -0
- package/src/config/layeredConfig.test.ts +143 -0
- package/src/config/layeredConfig.ts +836 -0
- package/src/config/persistence.test.ts +110 -0
- package/src/config/persistence.ts +311 -0
- package/src/config/runtimeConfig.test.ts +218 -0
- package/src/config/runtimeConfig.ts +554 -0
- package/src/config/settings.test.ts +155 -0
- package/src/config/settings.ts +401 -0
- package/src/config/toml-serialize.ts +98 -0
- package/src/config/trustStore.test.ts +29 -0
- package/src/config/trustStore.ts +68 -0
- package/src/core/attachments.test.ts +155 -0
- package/src/core/attachments.ts +71 -0
- package/src/core/auth/codexAuth.test.ts +68 -0
- package/src/core/auth/codexAuth.ts +359 -0
- package/src/core/cleanupFastFail.test.ts +76 -0
- package/src/core/cleanupFastFail.ts +67 -0
- package/src/core/clipboard.ts +24 -0
- package/src/core/codex.ts +124 -0
- package/src/core/codexExecArgs.test.ts +195 -0
- package/src/core/codexExecArgs.ts +152 -0
- package/src/core/codexLaunch.test.ts +205 -0
- package/src/core/codexLaunch.ts +162 -0
- package/src/core/codexPrompt.test.ts +252 -0
- package/src/core/codexPrompt.ts +428 -0
- package/src/core/executables/claudeExecutable.ts +63 -0
- package/src/core/executables/codexExecutable.test.ts +212 -0
- package/src/core/executables/codexExecutable.ts +159 -0
- package/src/core/executables/executableResolver.test.ts +129 -0
- package/src/core/executables/executableResolver.ts +138 -0
- package/src/core/executables/geminiExecutable.test.ts +116 -0
- package/src/core/executables/geminiExecutable.ts +78 -0
- package/src/core/executables/pathSanityScan.test.ts +47 -0
- package/src/core/githubDiagnostics.test.ts +92 -0
- package/src/core/githubDiagnostics.ts +222 -0
- package/src/core/hollowResponseFormat.test.ts +58 -0
- package/src/core/hollowResponseFormat.ts +39 -0
- package/src/core/inputDebug.ts +51 -0
- package/src/core/launchContext.test.ts +157 -0
- package/src/core/launchContext.ts +266 -0
- package/src/core/models/codexCapabilities.test.ts +45 -0
- package/src/core/models/codexCapabilities.ts +95 -0
- package/src/core/models/codexModelCapabilities.test.ts +246 -0
- package/src/core/models/codexModelCapabilities.ts +571 -0
- package/src/core/models/modelSpecs.test.ts +283 -0
- package/src/core/models/modelSpecs.ts +300 -0
- package/src/core/perf/profiler.ts +125 -0
- package/src/core/perf/renderDebug.test.ts +230 -0
- package/src/core/perf/renderDebug.ts +373 -0
- package/src/core/planStorage.test.ts +143 -0
- package/src/core/planStorage.ts +141 -0
- package/src/core/process/CommandRunner.test.ts +105 -0
- package/src/core/process/CommandRunner.ts +269 -0
- package/src/core/process/processValidation.ts +101 -0
- package/src/core/projectInstructions.test.ts +50 -0
- package/src/core/projectInstructions.ts +54 -0
- package/src/core/providerLauncher/launcher.test.ts +238 -0
- package/src/core/providerLauncher/launcher.ts +203 -0
- package/src/core/providerLauncher/registry.test.ts +324 -0
- package/src/core/providerLauncher/registry.ts +253 -0
- package/src/core/providerLauncher/types.ts +84 -0
- package/src/core/providerLauncher/workspaceConfig.test.ts +638 -0
- package/src/core/providerLauncher/workspaceConfig.ts +407 -0
- package/src/core/providerRuntime/anthropic.test.ts +1120 -0
- package/src/core/providerRuntime/anthropic.ts +576 -0
- package/src/core/providerRuntime/capabilityProfile.test.ts +311 -0
- package/src/core/providerRuntime/capabilityProfile.ts +288 -0
- package/src/core/providerRuntime/claudeCodeDiscovery.ts +446 -0
- package/src/core/providerRuntime/contextMetadata.test.ts +468 -0
- package/src/core/providerRuntime/contextMetadata.ts +409 -0
- package/src/core/providerRuntime/gemini.test.ts +437 -0
- package/src/core/providerRuntime/gemini.ts +784 -0
- package/src/core/providerRuntime/lmstudio.test.ts +168 -0
- package/src/core/providerRuntime/lmstudio.ts +118 -0
- package/src/core/providerRuntime/local.test.ts +787 -0
- package/src/core/providerRuntime/local.ts +754 -0
- package/src/core/providerRuntime/models.ts +150 -0
- package/src/core/providerRuntime/reasoning.ts +17 -0
- package/src/core/providerRuntime/registry.test.ts +233 -0
- package/src/core/providerRuntime/registry.ts +203 -0
- package/src/core/providerRuntime/types.ts +103 -0
- package/src/core/providers/codexJsonStream.test.ts +148 -0
- package/src/core/providers/codexJsonStream.ts +305 -0
- package/src/core/providers/codexSubprocess.test.ts +68 -0
- package/src/core/providers/codexSubprocess.ts +372 -0
- package/src/core/providers/codexTranscript.test.ts +284 -0
- package/src/core/providers/codexTranscript.ts +695 -0
- package/src/core/providers/openaiNative.ts +13 -0
- package/src/core/providers/registry.ts +21 -0
- package/src/core/providers/types.ts +59 -0
- package/src/core/terminal/terminalCapabilities.test.ts +93 -0
- package/src/core/terminal/terminalCapabilities.ts +100 -0
- package/src/core/terminal/terminalControl.test.ts +75 -0
- package/src/core/terminal/terminalControl.ts +147 -0
- package/src/core/terminal/terminalSanitize.test.ts +22 -0
- package/src/core/terminal/terminalSanitize.ts +147 -0
- package/src/core/terminal/terminalSelection.test.ts +42 -0
- package/src/core/terminal/terminalSelection.ts +66 -0
- package/src/core/terminal/terminalTitle.test.ts +328 -0
- package/src/core/terminal/terminalTitle.ts +483 -0
- package/src/core/workspaceActivity.test.ts +163 -0
- package/src/core/workspaceActivity.ts +380 -0
- package/src/core/workspaceGuard.test.ts +151 -0
- package/src/core/workspaceGuard.ts +288 -0
- package/src/core/workspaceRoot.test.ts +23 -0
- package/src/core/workspaceRoot.ts +47 -0
- package/src/exec.test.ts +13 -0
- package/src/exec.ts +72 -0
- package/src/headless/execArgs.test.ts +147 -0
- package/src/headless/execArgs.ts +294 -0
- package/src/headless/execRunner.test.ts +434 -0
- package/src/headless/execRunner.ts +304 -0
- package/src/index.test.tsx +618 -0
- package/src/index.tsx +296 -0
- package/src/session/appSession.test.ts +897 -0
- package/src/session/appSession.ts +761 -0
- package/src/session/chatLifecycle.test.ts +64 -0
- package/src/session/chatLifecycle.ts +951 -0
- package/src/session/liveRenderScheduler.test.ts +201 -0
- package/src/session/liveRenderScheduler.ts +214 -0
- package/src/session/planFlow.test.ts +103 -0
- package/src/session/planFlow.ts +149 -0
- package/src/session/planTranscript.test.ts +65 -0
- package/src/session/planTranscript.ts +15 -0
- package/src/session/promptRunSchedule.test.ts +36 -0
- package/src/session/promptRunSchedule.ts +26 -0
- package/src/session/types.ts +228 -0
- package/src/test/runtimeTestUtils.ts +14 -0
- package/src/types/react-dom.d.ts +3 -0
- package/src/ui/ActionRequiredBlock.tsx +38 -0
- package/src/ui/ActivityBars.tsx +68 -0
- package/src/ui/ActivityIndicator.test.tsx +58 -0
- package/src/ui/ActivityIndicator.tsx +58 -0
- package/src/ui/AgentBlock.test.ts +6 -0
- package/src/ui/AgentBlock.tsx +130 -0
- package/src/ui/AnimatedStatusText.test.ts +16 -0
- package/src/ui/AnimatedStatusText.tsx +69 -0
- package/src/ui/AppShell.test.tsx +1739 -0
- package/src/ui/AppShell.tsx +698 -0
- package/src/ui/AttachmentImportPanel.test.tsx +204 -0
- package/src/ui/AttachmentImportPanel.tsx +98 -0
- package/src/ui/AuthPanel.tsx +113 -0
- package/src/ui/BackendPicker.tsx +28 -0
- package/src/ui/BottomComposer.test.ts +674 -0
- package/src/ui/BottomComposer.tsx +1028 -0
- package/src/ui/CodexLogo.tsx +55 -0
- package/src/ui/DashCard.tsx +82 -0
- package/src/ui/Markdown.test.ts +157 -0
- package/src/ui/Markdown.tsx +310 -0
- package/src/ui/ModePicker.tsx +27 -0
- package/src/ui/ModelPicker.tsx +31 -0
- package/src/ui/ModelPickerProviderScope.test.tsx +411 -0
- package/src/ui/ModelPickerScreen.test.tsx +99 -0
- package/src/ui/ModelPickerScreen.tsx +416 -0
- package/src/ui/ModelPickerState.test.tsx +151 -0
- package/src/ui/ModelReasoningPicker.test.tsx +447 -0
- package/src/ui/ModelReasoningPicker.tsx +458 -0
- package/src/ui/Panel.tsx +51 -0
- package/src/ui/PermissionsPanel.tsx +78 -0
- package/src/ui/PlanActionPicker.tsx +119 -0
- package/src/ui/PlanReviewPanel.test.tsx +267 -0
- package/src/ui/PlanReviewPanel.tsx +212 -0
- package/src/ui/PromptCardBorder.test.tsx +161 -0
- package/src/ui/ProviderPicker.test.tsx +289 -0
- package/src/ui/ProviderPicker.tsx +321 -0
- package/src/ui/ProviderShortcut.test.tsx +143 -0
- package/src/ui/ReasoningPicker.tsx +46 -0
- package/src/ui/RunFooter.tsx +65 -0
- package/src/ui/SelectionPanel.tsx +67 -0
- package/src/ui/SettingsPanel.test.tsx +233 -0
- package/src/ui/SettingsPanel.tsx +156 -0
- package/src/ui/Spinner.tsx +25 -0
- package/src/ui/StaticIntroItem.tsx +54 -0
- package/src/ui/StaticTranscriptItem.tsx +56 -0
- package/src/ui/TextEntryPanel.tsx +139 -0
- package/src/ui/ThemePicker.tsx +31 -0
- package/src/ui/ThinkingBlock.tsx +100 -0
- package/src/ui/Timeline.test.ts +2067 -0
- package/src/ui/Timeline.tsx +1472 -0
- package/src/ui/TimelineNavigation.test.tsx +201 -0
- package/src/ui/TopHeader.test.tsx +239 -0
- package/src/ui/TopHeader.tsx +257 -0
- package/src/ui/TurnGroup.test.tsx +365 -0
- package/src/ui/TurnGroup.tsx +657 -0
- package/src/ui/busyStatusAnimation.test.ts +30 -0
- package/src/ui/busyStatusAnimation.ts +11 -0
- package/src/ui/commandNormalize.test.ts +142 -0
- package/src/ui/commandNormalize.ts +66 -0
- package/src/ui/diffRenderer.test.ts +102 -0
- package/src/ui/diffRenderer.ts +116 -0
- package/src/ui/focus.ts +61 -0
- package/src/ui/focusFlow.test.tsx +1098 -0
- package/src/ui/inputBuffer.test.ts +151 -0
- package/src/ui/inputBuffer.ts +203 -0
- package/src/ui/layout.test.ts +145 -0
- package/src/ui/layout.ts +287 -0
- package/src/ui/modeDisplay.test.ts +42 -0
- package/src/ui/modeDisplay.ts +52 -0
- package/src/ui/outputPipeline.ts +64 -0
- package/src/ui/progressEntries.ts +156 -0
- package/src/ui/runActivityView.test.ts +89 -0
- package/src/ui/runActivityView.ts +37 -0
- package/src/ui/runLifecycleView.test.tsx +237 -0
- package/src/ui/slashCommands.ts +41 -0
- package/src/ui/statusRenderIsolation.test.tsx +654 -0
- package/src/ui/terminalAnswerFormat.test.ts +19 -0
- package/src/ui/terminalAnswerFormat.ts +128 -0
- package/src/ui/textLayout.test.ts +18 -0
- package/src/ui/textLayout.ts +338 -0
- package/src/ui/theme.tsx +395 -0
- package/src/ui/themeFlow.test.ts +53 -0
- package/src/ui/themeFlow.ts +41 -0
- package/src/ui/timelineMeasure.ts +3088 -0
- package/src/ui/timelineMeasureCache.test.ts +986 -0
- package/src/ui/useThrottledValue.ts +31 -0
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
import type { ModelSpec } from "../models/modelSpecs.js";
|
|
2
|
+
import type { ProviderId, ProviderWorkspaceOverride } from "../providerLauncher/types.js";
|
|
3
|
+
|
|
4
|
+
export type ContextLengthSource = "api" | "cli" | "config" | "known-registry" | "lmstudio-api" | "unknown";
|
|
5
|
+
export type ContextConfidence = "verified" | "configured" | "known" | "estimated" | "unknown";
|
|
6
|
+
|
|
7
|
+
export interface ModelContextMetadata {
|
|
8
|
+
providerId: ProviderId;
|
|
9
|
+
modelId: string;
|
|
10
|
+
contextLength: number | null;
|
|
11
|
+
source: ContextLengthSource;
|
|
12
|
+
confidence: ContextConfidence;
|
|
13
|
+
raw?: unknown;
|
|
14
|
+
rawField?: string;
|
|
15
|
+
error?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ResolveModelContextLengthOptions {
|
|
19
|
+
providerId: ProviderId;
|
|
20
|
+
modelId: string;
|
|
21
|
+
providerConfig?: ProviderWorkspaceOverride | null;
|
|
22
|
+
rawMetadata?: unknown;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface KnownContextRegistryEntry {
|
|
26
|
+
contextLength: number;
|
|
27
|
+
sourceUrl: string;
|
|
28
|
+
note: string;
|
|
29
|
+
estimated?: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const KNOWN_CONTEXT_REGISTRY: Record<string, KnownContextRegistryEntry> = {
|
|
33
|
+
// Exact documented Gemini API model IDs only.
|
|
34
|
+
// Source: https://ai.google.dev/gemini-api/docs/models
|
|
35
|
+
"google:gemini-2.5-pro": {
|
|
36
|
+
contextLength: 1_048_576,
|
|
37
|
+
sourceUrl: "https://ai.google.dev/gemini-api/docs/models",
|
|
38
|
+
note: "Gemini API documented input token limit for this exact model ID.",
|
|
39
|
+
},
|
|
40
|
+
"google:gemini-2.5-flash": {
|
|
41
|
+
contextLength: 1_048_576,
|
|
42
|
+
sourceUrl: "https://ai.google.dev/gemini-api/docs/models",
|
|
43
|
+
note: "Gemini API documented input token limit for this exact model ID.",
|
|
44
|
+
},
|
|
45
|
+
"google:gemini-2.5-flash-lite": {
|
|
46
|
+
contextLength: 1_048_576,
|
|
47
|
+
sourceUrl: "https://ai.google.dev/gemini-api/docs/models",
|
|
48
|
+
note: "Gemini API documented input token limit for this exact model ID.",
|
|
49
|
+
},
|
|
50
|
+
// Gemini 3 preview model IDs — verified route IDs from GEMINI_VERIFIED_MODEL_IDS.
|
|
51
|
+
// Source: https://ai.google.dev/gemini-api/docs/models
|
|
52
|
+
"google:gemini-3-flash-preview": {
|
|
53
|
+
contextLength: 1_048_576,
|
|
54
|
+
sourceUrl: "https://ai.google.dev/gemini-api/docs/models",
|
|
55
|
+
note: "Gemini 3 Flash Preview — 1M token input limit.",
|
|
56
|
+
},
|
|
57
|
+
"google:gemini-3.1-pro-preview": {
|
|
58
|
+
contextLength: 1_048_576,
|
|
59
|
+
sourceUrl: "https://ai.google.dev/gemini-api/docs/models",
|
|
60
|
+
note: "Gemini 3.1 Pro Preview — 1M token input limit.",
|
|
61
|
+
},
|
|
62
|
+
"google:gemini-3.1-flash-lite-preview": {
|
|
63
|
+
contextLength: 1_048_576,
|
|
64
|
+
sourceUrl: "https://ai.google.dev/gemini-api/docs/models",
|
|
65
|
+
note: "Gemini 3.1 Flash Lite Preview — 1M token input limit.",
|
|
66
|
+
},
|
|
67
|
+
// Exact documented Anthropic model IDs only. Provider aliases such as
|
|
68
|
+
// "haiku" remain unknown unless configured or discovered by CLI metadata.
|
|
69
|
+
// Source: https://docs.anthropic.com/en/docs/about-claude/models
|
|
70
|
+
"anthropic:claude-opus-4-7": {
|
|
71
|
+
contextLength: 200_000,
|
|
72
|
+
sourceUrl: "https://docs.anthropic.com/en/docs/about-claude/models",
|
|
73
|
+
note: "Anthropic documented context window for this exact model ID.",
|
|
74
|
+
},
|
|
75
|
+
"anthropic:claude-sonnet-4-6": {
|
|
76
|
+
contextLength: 200_000,
|
|
77
|
+
sourceUrl: "https://docs.anthropic.com/en/docs/about-claude/models",
|
|
78
|
+
note: "Anthropic documented context window for this exact model ID.",
|
|
79
|
+
},
|
|
80
|
+
"anthropic:claude-haiku-4-5": {
|
|
81
|
+
contextLength: 200_000,
|
|
82
|
+
sourceUrl: "https://docs.anthropic.com/en/docs/about-claude/models",
|
|
83
|
+
note: "Anthropic documented context window for this exact model ID.",
|
|
84
|
+
},
|
|
85
|
+
// OpenAI/Codex model IDs as used by the Codex CLI ("openai" provider).
|
|
86
|
+
// All values are estimated — Codex CLI effective context may differ from the
|
|
87
|
+
// OpenAI API. Keys are lowercase-normalised (resolveFromKnownRegistry normalises
|
|
88
|
+
// before lookup so "GPT-5 Codex" → "gpt-5-codex" matches here).
|
|
89
|
+
// Source: https://platform.openai.com/docs/models
|
|
90
|
+
"openai:gpt-5.5": {
|
|
91
|
+
contextLength: 1_048_576,
|
|
92
|
+
estimated: true,
|
|
93
|
+
sourceUrl: "https://platform.openai.com/docs/models",
|
|
94
|
+
note: "Estimated — Codex CLI effective context unverified.",
|
|
95
|
+
},
|
|
96
|
+
"openai:gpt-5.5-mini": {
|
|
97
|
+
contextLength: 200_000,
|
|
98
|
+
estimated: true,
|
|
99
|
+
sourceUrl: "https://platform.openai.com/docs/models",
|
|
100
|
+
note: "Estimated.",
|
|
101
|
+
},
|
|
102
|
+
"openai:gpt-5.4": {
|
|
103
|
+
contextLength: 400_000,
|
|
104
|
+
estimated: true,
|
|
105
|
+
sourceUrl: "https://platform.openai.com/docs/models",
|
|
106
|
+
note: "Estimated.",
|
|
107
|
+
},
|
|
108
|
+
"openai:gpt-5.4-mini": {
|
|
109
|
+
contextLength: 200_000,
|
|
110
|
+
estimated: true,
|
|
111
|
+
sourceUrl: "https://platform.openai.com/docs/models",
|
|
112
|
+
note: "Estimated.",
|
|
113
|
+
},
|
|
114
|
+
"openai:gpt-5.3-codex": {
|
|
115
|
+
contextLength: 400_000,
|
|
116
|
+
estimated: true,
|
|
117
|
+
sourceUrl: "https://platform.openai.com/docs/models",
|
|
118
|
+
note: "Estimated — Codex variant.",
|
|
119
|
+
},
|
|
120
|
+
"openai:gpt-5.2": {
|
|
121
|
+
contextLength: 200_000,
|
|
122
|
+
estimated: true,
|
|
123
|
+
sourceUrl: "https://platform.openai.com/docs/models",
|
|
124
|
+
note: "Estimated.",
|
|
125
|
+
},
|
|
126
|
+
"openai:gpt-5.1-codex": {
|
|
127
|
+
contextLength: 400_000,
|
|
128
|
+
estimated: true,
|
|
129
|
+
sourceUrl: "https://platform.openai.com/docs/models",
|
|
130
|
+
note: "Estimated — Codex variant.",
|
|
131
|
+
},
|
|
132
|
+
"openai:gpt-5.1-codex-mini": {
|
|
133
|
+
contextLength: 200_000,
|
|
134
|
+
estimated: true,
|
|
135
|
+
sourceUrl: "https://platform.openai.com/docs/models",
|
|
136
|
+
note: "Estimated — Codex mini variant.",
|
|
137
|
+
},
|
|
138
|
+
"openai:gpt-5-codex": {
|
|
139
|
+
contextLength: 400_000,
|
|
140
|
+
estimated: true,
|
|
141
|
+
sourceUrl: "https://platform.openai.com/docs/models",
|
|
142
|
+
note: "Estimated — Codex variant.",
|
|
143
|
+
},
|
|
144
|
+
"openai:gpt-5-codex-mini": {
|
|
145
|
+
contextLength: 200_000,
|
|
146
|
+
estimated: true,
|
|
147
|
+
sourceUrl: "https://platform.openai.com/docs/models",
|
|
148
|
+
note: "Estimated — Codex mini variant.",
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
// Short alias IDs used by ANTHROPIC_FALLBACK_MODELS → canonical versioned IDs in the registry.
|
|
153
|
+
const ANTHROPIC_MODEL_ALIAS_MAP: Record<string, string> = {
|
|
154
|
+
opus: "claude-opus-4-7",
|
|
155
|
+
sonnet: "claude-sonnet-4-6",
|
|
156
|
+
haiku: "claude-haiku-4-5",
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
// Normalise OpenAI/Codex model IDs to lowercase-dashed form before registry lookup.
|
|
160
|
+
// Handles display-label variants like "GPT-5 Codex" → "gpt-5-codex".
|
|
161
|
+
function normalizeOpenAIModelId(modelId: string): string {
|
|
162
|
+
return modelId.toLowerCase().replace(/\s+/g, "-");
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const CONTEXT_FIELD_CANDIDATES = [
|
|
166
|
+
"loaded_context_length",
|
|
167
|
+
"context_length",
|
|
168
|
+
"contextLength",
|
|
169
|
+
"max_context_length",
|
|
170
|
+
"maxContextLength",
|
|
171
|
+
"n_ctx",
|
|
172
|
+
"ctx_len",
|
|
173
|
+
"max_tokens",
|
|
174
|
+
"max_input_tokens",
|
|
175
|
+
] as const;
|
|
176
|
+
|
|
177
|
+
const NESTED_METADATA_KEYS = [
|
|
178
|
+
"model_info",
|
|
179
|
+
"modelInfo",
|
|
180
|
+
"metadata",
|
|
181
|
+
"details",
|
|
182
|
+
"config",
|
|
183
|
+
"raw",
|
|
184
|
+
] as const;
|
|
185
|
+
|
|
186
|
+
const contextCache = new Map<string, ModelContextMetadata>();
|
|
187
|
+
|
|
188
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
189
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function validContextLength(value: unknown): number | null {
|
|
193
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
return value;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function cacheKey(options: ResolveModelContextLengthOptions): string {
|
|
200
|
+
return JSON.stringify({
|
|
201
|
+
providerId: options.providerId,
|
|
202
|
+
modelId: options.modelId,
|
|
203
|
+
models: options.providerConfig?.models ?? null,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export function clearModelContextMetadataCache(): void {
|
|
208
|
+
contextCache.clear();
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function formatContextLength(value: number | null): string {
|
|
212
|
+
return value === null ? "Unknown" : value.toLocaleString("en-US");
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export function formatContextCompact(n: number): string {
|
|
216
|
+
if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;
|
|
217
|
+
if (n >= 100_000) return `${Math.round(n / 1_000)}K`;
|
|
218
|
+
if (n >= 1_000) return `${(n / 1_000).toFixed(1)}k`;
|
|
219
|
+
return `${n}`;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export function formatContextMeter(
|
|
223
|
+
usedTokens: number | null | undefined,
|
|
224
|
+
contextLimit: number | null,
|
|
225
|
+
): string {
|
|
226
|
+
if (contextLimit === null || contextLimit === undefined) return "Unknown";
|
|
227
|
+
const used = usedTokens ?? 0;
|
|
228
|
+
const pct = contextLimit > 0
|
|
229
|
+
? Math.min(100, Math.floor((used / contextLimit) * 100))
|
|
230
|
+
: 0;
|
|
231
|
+
return `${used.toLocaleString("en-US")} / ${contextLimit.toLocaleString("en-US")} · ${pct}%`;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export function contextMetadataToModelSpec(metadata: ModelContextMetadata): ModelSpec {
|
|
235
|
+
if (metadata.contextLength === null) {
|
|
236
|
+
return {
|
|
237
|
+
status: "unknown",
|
|
238
|
+
contextWindow: null,
|
|
239
|
+
maxOutputTokens: null,
|
|
240
|
+
sourceUrl: "",
|
|
241
|
+
verifiedAt: null,
|
|
242
|
+
error: metadata.error ?? null,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return {
|
|
247
|
+
status: "verified",
|
|
248
|
+
contextWindow: metadata.contextLength,
|
|
249
|
+
maxOutputTokens: metadata.contextLength,
|
|
250
|
+
sourceUrl: metadata.source,
|
|
251
|
+
verifiedAt: Date.now(),
|
|
252
|
+
isEstimated: metadata.confidence === "estimated",
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function unknownMetadata(
|
|
257
|
+
providerId: ProviderId,
|
|
258
|
+
modelId: string,
|
|
259
|
+
error = "Context length metadata is unavailable for this provider/model.",
|
|
260
|
+
): ModelContextMetadata {
|
|
261
|
+
return {
|
|
262
|
+
providerId,
|
|
263
|
+
modelId,
|
|
264
|
+
contextLength: null,
|
|
265
|
+
source: "unknown",
|
|
266
|
+
confidence: "unknown",
|
|
267
|
+
error,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function findContextField(raw: unknown): { value: number; field: string } | null {
|
|
272
|
+
if (!isRecord(raw)) return null;
|
|
273
|
+
|
|
274
|
+
for (const field of CONTEXT_FIELD_CANDIDATES) {
|
|
275
|
+
const value = validContextLength(raw[field]);
|
|
276
|
+
if (value !== null) return { value, field };
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
for (const nestedKey of NESTED_METADATA_KEYS) {
|
|
280
|
+
const nested = raw[nestedKey];
|
|
281
|
+
if (!isRecord(nested)) continue;
|
|
282
|
+
for (const field of CONTEXT_FIELD_CANDIDATES) {
|
|
283
|
+
const value = validContextLength(nested[field]);
|
|
284
|
+
if (value !== null) return { value, field: `${nestedKey}.${field}` };
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function resolveFromRawMetadata(providerId: ProviderId, modelId: string, raw: unknown): ModelContextMetadata | null {
|
|
292
|
+
const found = findContextField(raw);
|
|
293
|
+
if (!found) return null;
|
|
294
|
+
const source: ContextLengthSource =
|
|
295
|
+
found.field === "loaded_context_length" || found.field.endsWith(".loaded_context_length")
|
|
296
|
+
? "lmstudio-api"
|
|
297
|
+
: providerId === "local" ? "api" : "cli";
|
|
298
|
+
return {
|
|
299
|
+
providerId,
|
|
300
|
+
modelId,
|
|
301
|
+
contextLength: found.value,
|
|
302
|
+
source,
|
|
303
|
+
confidence: "verified",
|
|
304
|
+
raw,
|
|
305
|
+
rawField: found.field,
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function resolveFromConfig(
|
|
310
|
+
providerId: ProviderId,
|
|
311
|
+
modelId: string,
|
|
312
|
+
providerConfig?: ProviderWorkspaceOverride | null,
|
|
313
|
+
): ModelContextMetadata | null {
|
|
314
|
+
const value = providerConfig?.models?.[modelId]?.contextLength;
|
|
315
|
+
if (value === undefined) return null;
|
|
316
|
+
const contextLength = validContextLength(value);
|
|
317
|
+
if (contextLength === null) {
|
|
318
|
+
return {
|
|
319
|
+
providerId,
|
|
320
|
+
modelId,
|
|
321
|
+
contextLength: null,
|
|
322
|
+
source: "unknown",
|
|
323
|
+
confidence: "unknown",
|
|
324
|
+
error: `Invalid configured contextLength for ${modelId}. Expected a positive integer.`,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
return {
|
|
328
|
+
providerId,
|
|
329
|
+
modelId,
|
|
330
|
+
contextLength,
|
|
331
|
+
source: "config",
|
|
332
|
+
confidence: "configured",
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function resolveFromKnownRegistry(providerId: ProviderId, modelId: string): ModelContextMetadata | null {
|
|
337
|
+
const lookupId = providerId === "anthropic"
|
|
338
|
+
? (ANTHROPIC_MODEL_ALIAS_MAP[modelId] ?? modelId)
|
|
339
|
+
: providerId === "openai"
|
|
340
|
+
? normalizeOpenAIModelId(modelId)
|
|
341
|
+
: modelId;
|
|
342
|
+
const entry = KNOWN_CONTEXT_REGISTRY[`${providerId}:${lookupId}`];
|
|
343
|
+
if (!entry) return null;
|
|
344
|
+
return {
|
|
345
|
+
providerId,
|
|
346
|
+
modelId,
|
|
347
|
+
contextLength: entry.contextLength,
|
|
348
|
+
source: "known-registry",
|
|
349
|
+
confidence: entry.estimated ? "estimated" : "known",
|
|
350
|
+
raw: {
|
|
351
|
+
sourceUrl: entry.sourceUrl,
|
|
352
|
+
note: entry.note,
|
|
353
|
+
},
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
export async function resolveModelContextLength(
|
|
358
|
+
options: ResolveModelContextLengthOptions,
|
|
359
|
+
): Promise<ModelContextMetadata> {
|
|
360
|
+
const key = cacheKey(options);
|
|
361
|
+
const cached = contextCache.get(key);
|
|
362
|
+
if (cached && !(options.rawMetadata !== undefined && cached.source === "unknown")) {
|
|
363
|
+
return cached;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const rawMetadata = options.rawMetadata ?? null;
|
|
367
|
+
const resolved =
|
|
368
|
+
resolveFromRawMetadata(options.providerId, options.modelId, rawMetadata)
|
|
369
|
+
?? resolveFromConfig(options.providerId, options.modelId, options.providerConfig)
|
|
370
|
+
?? resolveFromKnownRegistry(options.providerId, options.modelId)
|
|
371
|
+
?? unknownMetadata(
|
|
372
|
+
options.providerId,
|
|
373
|
+
options.modelId,
|
|
374
|
+
options.providerId === "local"
|
|
375
|
+
? "/v1/models did not include context length metadata."
|
|
376
|
+
: "Provider metadata did not include context length.",
|
|
377
|
+
);
|
|
378
|
+
|
|
379
|
+
contextCache.set(key, resolved);
|
|
380
|
+
return resolved;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
export function resolveModelContextLengthCached(
|
|
384
|
+
options: ResolveModelContextLengthOptions,
|
|
385
|
+
): ModelContextMetadata {
|
|
386
|
+
const key = cacheKey(options);
|
|
387
|
+
const cached = contextCache.get(key);
|
|
388
|
+
if (cached && !(options.rawMetadata !== undefined && cached.source === "unknown")) return cached;
|
|
389
|
+
|
|
390
|
+
const raw = resolveFromRawMetadata(options.providerId, options.modelId, options.rawMetadata);
|
|
391
|
+
if (raw) {
|
|
392
|
+
contextCache.set(key, raw);
|
|
393
|
+
return raw;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const config = resolveFromConfig(options.providerId, options.modelId, options.providerConfig);
|
|
397
|
+
if (config) {
|
|
398
|
+
contextCache.set(key, config);
|
|
399
|
+
return config;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const registry = resolveFromKnownRegistry(options.providerId, options.modelId);
|
|
403
|
+
if (registry) {
|
|
404
|
+
contextCache.set(key, registry);
|
|
405
|
+
return registry;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
return unknownMetadata(options.providerId, options.modelId);
|
|
409
|
+
}
|