ubume 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/README.md +88 -0
- package/bin/codexa-local-harness-bridge.js +3 -0
- package/bin/codexa.js +28 -0
- package/bin/ubume-local-harness-bridge.js +325 -0
- package/bin/ubume.js +398 -0
- package/package.json +66 -0
- package/src/app.tsx +5759 -0
- package/src/commands/handler.ts +889 -0
- package/src/config/appVersion.ts +69 -0
- package/src/config/buildInfo.ts +3 -0
- package/src/config/launchArgs.ts +196 -0
- package/src/config/layeredConfig.ts +853 -0
- package/src/config/legacyEnv.ts +16 -0
- package/src/config/persistence.ts +377 -0
- package/src/config/runtimeConfig.ts +558 -0
- package/src/config/settings.ts +405 -0
- package/src/config/toml-serialize.ts +109 -0
- package/src/config/trustStore.ts +84 -0
- package/src/config/updateCheckCache.ts +85 -0
- package/src/core/README.md +55 -0
- package/src/core/agent/loop.ts +464 -0
- package/src/core/agent/protocol.ts +345 -0
- package/src/core/agent/tools.ts +423 -0
- package/src/core/auth/codexAuth.ts +359 -0
- package/src/core/codex/codexExecArgs.ts +166 -0
- package/src/core/codex/codexLaunch.ts +163 -0
- package/src/core/codex/codexPrompt.ts +429 -0
- package/src/core/debug/inputDebug.ts +51 -0
- package/src/core/debug/localStreamDebug.ts +50 -0
- package/src/core/debug/modelStateDebug.ts +35 -0
- package/src/core/executables/antigravityExecutable.ts +48 -0
- package/src/core/executables/claudeExecutable.ts +63 -0
- package/src/core/executables/codexExecutable.ts +160 -0
- package/src/core/executables/executableResolver.ts +164 -0
- package/src/core/executables/geminiExecutable.ts +78 -0
- package/src/core/models/codexCapabilities.ts +97 -0
- package/src/core/models/codexModelCapabilities.ts +624 -0
- package/src/core/models/codexModelsCacheSeed.ts +153 -0
- package/src/core/models/modelSpecs.ts +303 -0
- package/src/core/models/providerModelCache.ts +94 -0
- package/src/core/perf/profiler.ts +125 -0
- package/src/core/perf/renderDebug.ts +398 -0
- package/src/core/process/CommandRunner.ts +280 -0
- package/src/core/process/processValidation.ts +111 -0
- package/src/core/providerLauncher/launcher.ts +220 -0
- package/src/core/providerLauncher/registry.ts +354 -0
- package/src/core/providerLauncher/types.ts +95 -0
- package/src/core/providerLauncher/workspaceConfig.ts +487 -0
- package/src/core/providerRuntime/anthropic.ts +580 -0
- package/src/core/providerRuntime/antigravity.ts +500 -0
- package/src/core/providerRuntime/capabilityProfile.ts +383 -0
- package/src/core/providerRuntime/claudeCodeDiscovery.ts +724 -0
- package/src/core/providerRuntime/claudeCodeDiscoveryDebug.ts +55 -0
- package/src/core/providerRuntime/codexaCupy.ts +97 -0
- package/src/core/providerRuntime/codexaNative.ts +425 -0
- package/src/core/providerRuntime/contextMetadata.ts +397 -0
- package/src/core/providerRuntime/gemini.ts +789 -0
- package/src/core/providerRuntime/lmstudio.ts +118 -0
- package/src/core/providerRuntime/local.ts +770 -0
- package/src/core/providerRuntime/localHarness/runtime.ts +1090 -0
- package/src/core/providerRuntime/localOutputBudget.ts +17 -0
- package/src/core/providerRuntime/mistralVibe.ts +667 -0
- package/src/core/providerRuntime/models.ts +175 -0
- package/src/core/providerRuntime/reasoning.ts +20 -0
- package/src/core/providerRuntime/registry.ts +286 -0
- package/src/core/providerRuntime/types.ts +142 -0
- package/src/core/providerRuntime/unsloth.ts +216 -0
- package/src/core/providers/codexJsonStream.ts +305 -0
- package/src/core/providers/codexSubprocess.ts +378 -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 +91 -0
- package/src/core/shared/attachments.ts +101 -0
- package/src/core/shared/cleanupFastFail.ts +67 -0
- package/src/core/shared/clipboard.ts +24 -0
- package/src/core/shared/clipboardImage.ts +111 -0
- package/src/core/shared/githubDiagnostics.ts +222 -0
- package/src/core/shared/hollowResponseFormat.ts +39 -0
- package/src/core/terminal/clearFrameBoundary.ts +852 -0
- package/src/core/terminal/frameLock.ts +110 -0
- package/src/core/terminal/inkRenderReset.ts +123 -0
- package/src/core/terminal/startupClear.ts +20 -0
- package/src/core/terminal/terminalCapabilities.ts +100 -0
- package/src/core/terminal/terminalControl.ts +169 -0
- package/src/core/terminal/terminalSanitize.ts +147 -0
- package/src/core/terminal/terminalTitle.ts +400 -0
- package/src/core/version/channel.ts +27 -0
- package/src/core/version/packageManager.ts +119 -0
- package/src/core/version/updateCheck.ts +203 -0
- package/src/core/workspace/appData.ts +107 -0
- package/src/core/workspace/conversationStore.ts +335 -0
- package/src/core/workspace/launchContext.ts +259 -0
- package/src/core/workspace/planStorage.ts +135 -0
- package/src/core/workspace/projectInstructions.ts +54 -0
- package/src/core/workspace/scratchDir.ts +64 -0
- package/src/core/workspace/workspaceActivity.ts +384 -0
- package/src/core/workspace/workspaceGuard.ts +377 -0
- package/src/core/workspace/workspaceRoot.ts +47 -0
- package/src/exec.ts +73 -0
- package/src/headless/execArgs.ts +296 -0
- package/src/headless/execRunner.ts +304 -0
- package/src/index.tsx +270 -0
- package/src/legacyEnvBootstrap.ts +5 -0
- package/src/session/appSession.ts +771 -0
- package/src/session/chatLifecycle.ts +994 -0
- package/src/session/conversation.ts +107 -0
- package/src/session/liveRenderScheduler.ts +214 -0
- package/src/session/persistedResponse.ts +93 -0
- package/src/session/planFlow.ts +159 -0
- package/src/session/planTranscript.ts +19 -0
- package/src/session/promptRunSchedule.ts +26 -0
- package/src/session/types.ts +234 -0
- package/src/test/runtimeTestUtils.ts +14 -0
- package/src/types/react-dom.d.ts +3 -0
- package/src/ui/chrome/ActivityBars.tsx +68 -0
- package/src/ui/chrome/ActivityIndicator.tsx +58 -0
- package/src/ui/chrome/AnimatedStatusText.tsx +69 -0
- package/src/ui/chrome/AppShell.tsx +474 -0
- package/src/ui/chrome/BottomComposer.tsx +1135 -0
- package/src/ui/chrome/DashCard.tsx +82 -0
- package/src/ui/chrome/RunFooter.tsx +65 -0
- package/src/ui/chrome/RuntimeStatusBar.tsx +108 -0
- package/src/ui/chrome/Spinner.tsx +25 -0
- package/src/ui/chrome/TopHeader.tsx +439 -0
- package/src/ui/chrome/UpdateAvailableCard.tsx +42 -0
- package/src/ui/chrome/busyStatusAnimation.ts +11 -0
- package/src/ui/input/commandNormalize.ts +66 -0
- package/src/ui/input/focus.ts +73 -0
- package/src/ui/input/imageAttachments.ts +18 -0
- package/src/ui/input/inputBuffer.ts +203 -0
- package/src/ui/input/pastedContent.ts +75 -0
- package/src/ui/input/rawArrowKeys.ts +28 -0
- package/src/ui/input/slashCommands.ts +43 -0
- package/src/ui/input/useStdinRawModeLease.ts +23 -0
- package/src/ui/layout.ts +560 -0
- package/src/ui/panels/AttachmentImportPanel.tsx +131 -0
- package/src/ui/panels/AuthPanel.tsx +149 -0
- package/src/ui/panels/BackendPicker.tsx +28 -0
- package/src/ui/panels/ModePicker.tsx +31 -0
- package/src/ui/panels/ModelPicker.tsx +31 -0
- package/src/ui/panels/ModelPickerScreen.tsx +761 -0
- package/src/ui/panels/ModelReasoningPicker.tsx +458 -0
- package/src/ui/panels/Panel.tsx +51 -0
- package/src/ui/panels/PermissionsPanel.tsx +78 -0
- package/src/ui/panels/PlanActionPicker.tsx +187 -0
- package/src/ui/panels/ProviderPicker.tsx +753 -0
- package/src/ui/panels/ProviderSetupPrompt.tsx +52 -0
- package/src/ui/panels/ReasoningPicker.tsx +46 -0
- package/src/ui/panels/ResumePicker.tsx +90 -0
- package/src/ui/panels/SelectionPanel.tsx +138 -0
- package/src/ui/panels/SettingsPanel.tsx +156 -0
- package/src/ui/panels/TextEntryPanel.tsx +139 -0
- package/src/ui/panels/ThemePicker.tsx +32 -0
- package/src/ui/panels/ToolApprovalPanel.tsx +46 -0
- package/src/ui/panels/UpdatePromptPanel.tsx +236 -0
- package/src/ui/panels/responsivePickerViewport.ts +64 -0
- package/src/ui/render/Markdown.tsx +331 -0
- package/src/ui/render/diffRenderer.ts +116 -0
- package/src/ui/render/logoVariants.ts +113 -0
- package/src/ui/render/modeDisplay.ts +52 -0
- package/src/ui/render/outputPipeline.ts +64 -0
- package/src/ui/render/runtimeDisplay.ts +128 -0
- package/src/ui/render/terminalAnswerFormat.ts +128 -0
- package/src/ui/render/textLayout.ts +392 -0
- package/src/ui/theme.tsx +274 -0
- package/src/ui/themeFlow.ts +41 -0
- package/src/ui/timeline/ActionRequiredBlock.tsx +38 -0
- package/src/ui/timeline/AgentBlock.tsx +130 -0
- package/src/ui/timeline/StaticIntroItem.tsx +54 -0
- package/src/ui/timeline/ThinkingBlock.tsx +100 -0
- package/src/ui/timeline/Timeline.tsx +1410 -0
- package/src/ui/timeline/TranscriptShell.tsx +302 -0
- package/src/ui/timeline/TurnGroup.tsx +673 -0
- package/src/ui/timeline/layoutListWindow.ts +145 -0
- package/src/ui/timeline/liveViewportWindow.ts +68 -0
- package/src/ui/timeline/progressEntries.ts +156 -0
- package/src/ui/timeline/runActivityView.ts +37 -0
- package/src/ui/timeline/staticTranscriptCache.ts +174 -0
- package/src/ui/timeline/streamCoalesce.ts +53 -0
- package/src/ui/timeline/timelineMeasure.ts +3273 -0
- package/src/ui/useThrottledValue.ts +31 -0
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
import type { ProviderId, ProviderWorkspaceOverride } from "../providerLauncher/types.js";
|
|
2
|
+
|
|
3
|
+
export type CapabilitySource = "api" | "cli" | "config" | "detected-family" | "known-registry" | "unknown";
|
|
4
|
+
export type CapabilityConfidence = "verified" | "configured" | "detected" | "known" | "unknown";
|
|
5
|
+
export type LocalModelFamily = "deepseek";
|
|
6
|
+
|
|
7
|
+
export interface ModelCapabilityProfile {
|
|
8
|
+
providerId: ProviderId;
|
|
9
|
+
modelId: string;
|
|
10
|
+
family: LocalModelFamily | null;
|
|
11
|
+
maxOutputTokens: number | null;
|
|
12
|
+
supportsStreaming: boolean | null;
|
|
13
|
+
supportsToolCalls: boolean | null;
|
|
14
|
+
supportsSystemPrompt: boolean | null;
|
|
15
|
+
supportsVision: boolean | null;
|
|
16
|
+
source: CapabilitySource;
|
|
17
|
+
confidence: CapabilityConfidence;
|
|
18
|
+
raw?: unknown;
|
|
19
|
+
error?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ResolveModelCapabilityProfileOptions {
|
|
23
|
+
providerId: ProviderId;
|
|
24
|
+
modelId: string;
|
|
25
|
+
providerConfig?: ProviderWorkspaceOverride | null;
|
|
26
|
+
rawMetadata?: unknown;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface KnownCapabilityRegistryEntry {
|
|
30
|
+
maxOutputTokens?: number;
|
|
31
|
+
supportsStreaming?: boolean;
|
|
32
|
+
supportsToolCalls?: boolean;
|
|
33
|
+
supportsSystemPrompt?: boolean;
|
|
34
|
+
supportsVision?: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Empty by default — only exact "providerId:modelId" matches are allowed.
|
|
38
|
+
// Do NOT add wildcard or family-based entries.
|
|
39
|
+
const KNOWN_CAPABILITY_REGISTRY: Record<string, KnownCapabilityRegistryEntry> = {};
|
|
40
|
+
|
|
41
|
+
const SYSTEM_PROMPT_CANDIDATES = [
|
|
42
|
+
"supports_system_prompt",
|
|
43
|
+
"supportsSystemPrompt",
|
|
44
|
+
"system_prompt",
|
|
45
|
+
"has_system_prompt",
|
|
46
|
+
] as const;
|
|
47
|
+
|
|
48
|
+
const STREAMING_CANDIDATES = [
|
|
49
|
+
"supports_streaming",
|
|
50
|
+
"supportsStreaming",
|
|
51
|
+
"streaming",
|
|
52
|
+
"stream_supported",
|
|
53
|
+
] as const;
|
|
54
|
+
|
|
55
|
+
const TOOL_CALLS_CANDIDATES = [
|
|
56
|
+
"supports_tool_calls",
|
|
57
|
+
"supportsToolCalls",
|
|
58
|
+
"tool_calls",
|
|
59
|
+
"supports_tools",
|
|
60
|
+
"function_calling",
|
|
61
|
+
"has_tools",
|
|
62
|
+
] as const;
|
|
63
|
+
|
|
64
|
+
const MAX_OUTPUT_TOKENS_CANDIDATES = [
|
|
65
|
+
"max_output_tokens",
|
|
66
|
+
"maxOutputTokens",
|
|
67
|
+
"max_new_tokens",
|
|
68
|
+
"max_tokens_output",
|
|
69
|
+
] as const;
|
|
70
|
+
|
|
71
|
+
const NESTED_METADATA_KEYS = [
|
|
72
|
+
"model_info",
|
|
73
|
+
"modelInfo",
|
|
74
|
+
"metadata",
|
|
75
|
+
"details",
|
|
76
|
+
"config",
|
|
77
|
+
] as const;
|
|
78
|
+
|
|
79
|
+
const MODEL_IDENTITY_FIELDS = [
|
|
80
|
+
"id",
|
|
81
|
+
"name",
|
|
82
|
+
"model",
|
|
83
|
+
"model_id",
|
|
84
|
+
"modelId",
|
|
85
|
+
"display_name",
|
|
86
|
+
"displayName",
|
|
87
|
+
"repository",
|
|
88
|
+
"repo",
|
|
89
|
+
"path",
|
|
90
|
+
"publisher",
|
|
91
|
+
] as const;
|
|
92
|
+
|
|
93
|
+
const capabilityCache = new Map<string, ModelCapabilityProfile>();
|
|
94
|
+
|
|
95
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
96
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function validBoolean(value: unknown): boolean | null {
|
|
100
|
+
if (value === true || value === false) return value;
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function validPositiveInteger(value: unknown): number | null {
|
|
105
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) return null;
|
|
106
|
+
return value;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function cacheKey(options: ResolveModelCapabilityProfileOptions): string {
|
|
110
|
+
return JSON.stringify({
|
|
111
|
+
providerId: options.providerId,
|
|
112
|
+
modelId: options.modelId,
|
|
113
|
+
models: options.providerConfig?.models ?? null,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function deepSeekIdentity(value: string): boolean {
|
|
118
|
+
return /(^|[^a-z0-9])deep[\s_\-./:]*seek(?=$|[^a-z0-9])/i.test(value);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function metadataIdentityValues(raw: unknown): string[] {
|
|
122
|
+
if (!isRecord(raw)) return [];
|
|
123
|
+
const values: string[] = [];
|
|
124
|
+
for (const field of MODEL_IDENTITY_FIELDS) {
|
|
125
|
+
if (typeof raw[field] === "string") values.push(raw[field]);
|
|
126
|
+
}
|
|
127
|
+
for (const nestedKey of NESTED_METADATA_KEYS) {
|
|
128
|
+
const nested = raw[nestedKey];
|
|
129
|
+
if (!isRecord(nested)) continue;
|
|
130
|
+
for (const field of MODEL_IDENTITY_FIELDS) {
|
|
131
|
+
if (typeof nested[field] === "string") values.push(nested[field]);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return values;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function detectLocalModelFamily(modelId: string, rawMetadata?: unknown): LocalModelFamily | null {
|
|
138
|
+
return [modelId, ...metadataIdentityValues(rawMetadata)].some(deepSeekIdentity)
|
|
139
|
+
? "deepseek"
|
|
140
|
+
: null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function clearModelCapabilityProfileCache(): void {
|
|
144
|
+
capabilityCache.clear();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function unknownProfile(
|
|
148
|
+
providerId: ProviderId,
|
|
149
|
+
modelId: string,
|
|
150
|
+
error?: string,
|
|
151
|
+
): ModelCapabilityProfile {
|
|
152
|
+
return {
|
|
153
|
+
providerId,
|
|
154
|
+
modelId,
|
|
155
|
+
family: null,
|
|
156
|
+
maxOutputTokens: null,
|
|
157
|
+
supportsStreaming: null,
|
|
158
|
+
supportsToolCalls: null,
|
|
159
|
+
supportsSystemPrompt: null,
|
|
160
|
+
supportsVision: null,
|
|
161
|
+
source: "unknown",
|
|
162
|
+
confidence: "unknown",
|
|
163
|
+
...(error ? { error } : {}),
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function findBooleanField(
|
|
168
|
+
raw: Record<string, unknown>,
|
|
169
|
+
candidates: readonly string[],
|
|
170
|
+
): boolean | null {
|
|
171
|
+
for (const field of candidates) {
|
|
172
|
+
const value = validBoolean(raw[field]);
|
|
173
|
+
if (value !== null) return value;
|
|
174
|
+
}
|
|
175
|
+
for (const nestedKey of NESTED_METADATA_KEYS) {
|
|
176
|
+
const nested = raw[nestedKey];
|
|
177
|
+
if (!isRecord(nested)) continue;
|
|
178
|
+
for (const field of candidates) {
|
|
179
|
+
const value = validBoolean(nested[field]);
|
|
180
|
+
if (value !== null) return value;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function findIntegerField(
|
|
187
|
+
raw: Record<string, unknown>,
|
|
188
|
+
candidates: readonly string[],
|
|
189
|
+
): number | null {
|
|
190
|
+
for (const field of candidates) {
|
|
191
|
+
const value = validPositiveInteger(raw[field]);
|
|
192
|
+
if (value !== null) return value;
|
|
193
|
+
}
|
|
194
|
+
for (const nestedKey of NESTED_METADATA_KEYS) {
|
|
195
|
+
const nested = raw[nestedKey];
|
|
196
|
+
if (!isRecord(nested)) continue;
|
|
197
|
+
for (const field of candidates) {
|
|
198
|
+
const value = validPositiveInteger(nested[field]);
|
|
199
|
+
if (value !== null) return value;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function resolveFromRawMetadata(
|
|
206
|
+
providerId: ProviderId,
|
|
207
|
+
modelId: string,
|
|
208
|
+
raw: unknown,
|
|
209
|
+
): ModelCapabilityProfile | null {
|
|
210
|
+
if (providerId !== "local") return null;
|
|
211
|
+
if (!isRecord(raw)) return null;
|
|
212
|
+
|
|
213
|
+
const supportsSystemPrompt = findBooleanField(raw, SYSTEM_PROMPT_CANDIDATES);
|
|
214
|
+
const supportsStreaming = findBooleanField(raw, STREAMING_CANDIDATES);
|
|
215
|
+
let supportsToolCalls = findBooleanField(raw, TOOL_CALLS_CANDIDATES);
|
|
216
|
+
const maxOutputTokens = findIntegerField(raw, MAX_OUTPUT_TOKENS_CANDIDATES);
|
|
217
|
+
|
|
218
|
+
if (supportsToolCalls === null && Array.isArray(raw.capabilities)) {
|
|
219
|
+
if ((raw.capabilities as unknown[]).includes("tool_use")) {
|
|
220
|
+
supportsToolCalls = true;
|
|
221
|
+
}
|
|
222
|
+
// absence of "tool_use" in capabilities ≠ not supported — leave null
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (
|
|
226
|
+
supportsSystemPrompt === null
|
|
227
|
+
&& supportsStreaming === null
|
|
228
|
+
&& supportsToolCalls === null
|
|
229
|
+
&& maxOutputTokens === null
|
|
230
|
+
) {
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return {
|
|
235
|
+
providerId,
|
|
236
|
+
modelId,
|
|
237
|
+
family: null,
|
|
238
|
+
maxOutputTokens,
|
|
239
|
+
supportsStreaming,
|
|
240
|
+
supportsToolCalls,
|
|
241
|
+
supportsSystemPrompt,
|
|
242
|
+
supportsVision: null,
|
|
243
|
+
source: "api",
|
|
244
|
+
confidence: "verified",
|
|
245
|
+
raw,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function resolveFromConfig(
|
|
250
|
+
providerId: ProviderId,
|
|
251
|
+
modelId: string,
|
|
252
|
+
providerConfig?: ProviderWorkspaceOverride | null,
|
|
253
|
+
): ModelCapabilityProfile | null {
|
|
254
|
+
const modelOverride = providerConfig?.models?.[modelId];
|
|
255
|
+
if (!modelOverride) return null;
|
|
256
|
+
|
|
257
|
+
const supportsStreaming = validBoolean(modelOverride.supportsStreaming);
|
|
258
|
+
const supportsToolCalls = validBoolean(modelOverride.supportsToolCalls);
|
|
259
|
+
const supportsSystemPrompt = validBoolean(modelOverride.supportsSystemPrompt);
|
|
260
|
+
const maxOutputTokens = validPositiveInteger(modelOverride.maxOutputTokens);
|
|
261
|
+
|
|
262
|
+
const hasAny =
|
|
263
|
+
supportsSystemPrompt !== null
|
|
264
|
+
|| supportsStreaming !== null
|
|
265
|
+
|| supportsToolCalls !== null
|
|
266
|
+
|| maxOutputTokens !== null;
|
|
267
|
+
|
|
268
|
+
if (!hasAny) return null;
|
|
269
|
+
|
|
270
|
+
return {
|
|
271
|
+
providerId,
|
|
272
|
+
modelId,
|
|
273
|
+
family: null,
|
|
274
|
+
maxOutputTokens,
|
|
275
|
+
supportsStreaming,
|
|
276
|
+
supportsToolCalls,
|
|
277
|
+
supportsSystemPrompt,
|
|
278
|
+
supportsVision: validBoolean(modelOverride.supportsVision),
|
|
279
|
+
source: "config",
|
|
280
|
+
confidence: "configured",
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function resolveFromKnownRegistry(
|
|
285
|
+
providerId: ProviderId,
|
|
286
|
+
modelId: string,
|
|
287
|
+
): ModelCapabilityProfile | null {
|
|
288
|
+
const entry = KNOWN_CAPABILITY_REGISTRY[`${providerId}:${modelId}`];
|
|
289
|
+
if (!entry) return null;
|
|
290
|
+
return {
|
|
291
|
+
providerId,
|
|
292
|
+
modelId,
|
|
293
|
+
family: null,
|
|
294
|
+
maxOutputTokens: entry.maxOutputTokens ?? null,
|
|
295
|
+
supportsStreaming: entry.supportsStreaming ?? null,
|
|
296
|
+
supportsToolCalls: entry.supportsToolCalls ?? null,
|
|
297
|
+
supportsSystemPrompt: entry.supportsSystemPrompt ?? null,
|
|
298
|
+
supportsVision: entry.supportsVision ?? null,
|
|
299
|
+
source: "known-registry",
|
|
300
|
+
confidence: "known",
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function resolveFromDetectedFamily(
|
|
305
|
+
providerId: ProviderId,
|
|
306
|
+
modelId: string,
|
|
307
|
+
rawMetadata?: unknown,
|
|
308
|
+
): ModelCapabilityProfile | null {
|
|
309
|
+
if (providerId !== "local") return null;
|
|
310
|
+
const family = detectLocalModelFamily(modelId, rawMetadata);
|
|
311
|
+
if (family !== "deepseek") return null;
|
|
312
|
+
return {
|
|
313
|
+
providerId,
|
|
314
|
+
modelId,
|
|
315
|
+
family,
|
|
316
|
+
maxOutputTokens: null,
|
|
317
|
+
supportsStreaming: true,
|
|
318
|
+
supportsToolCalls: true,
|
|
319
|
+
supportsSystemPrompt: null,
|
|
320
|
+
supportsVision: null,
|
|
321
|
+
source: "detected-family",
|
|
322
|
+
confidence: "detected",
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function mergeProfiles(options: {
|
|
327
|
+
providerId: ProviderId;
|
|
328
|
+
modelId: string;
|
|
329
|
+
rawMetadata?: unknown;
|
|
330
|
+
profiles: readonly (ModelCapabilityProfile | null)[];
|
|
331
|
+
}): ModelCapabilityProfile {
|
|
332
|
+
const profiles = options.profiles.filter((profile): profile is ModelCapabilityProfile => profile !== null);
|
|
333
|
+
const first = profiles[0];
|
|
334
|
+
const family = detectLocalModelFamily(options.modelId, options.rawMetadata)
|
|
335
|
+
?? profiles.find((profile) => profile.family)?.family
|
|
336
|
+
?? null;
|
|
337
|
+
const value = <K extends keyof Pick<ModelCapabilityProfile,
|
|
338
|
+
"maxOutputTokens" | "supportsStreaming" | "supportsToolCalls" | "supportsSystemPrompt" | "supportsVision"
|
|
339
|
+
>>(field: K): ModelCapabilityProfile[K] => {
|
|
340
|
+
for (const profile of profiles) {
|
|
341
|
+
if (profile[field] !== null) return profile[field];
|
|
342
|
+
}
|
|
343
|
+
return null as ModelCapabilityProfile[K];
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
if (!first && !family) return unknownProfile(options.providerId, options.modelId);
|
|
347
|
+
return {
|
|
348
|
+
providerId: options.providerId,
|
|
349
|
+
modelId: options.modelId,
|
|
350
|
+
family,
|
|
351
|
+
maxOutputTokens: value("maxOutputTokens"),
|
|
352
|
+
supportsStreaming: value("supportsStreaming"),
|
|
353
|
+
supportsToolCalls: value("supportsToolCalls"),
|
|
354
|
+
supportsSystemPrompt: value("supportsSystemPrompt"),
|
|
355
|
+
supportsVision: value("supportsVision"),
|
|
356
|
+
source: first?.source ?? "unknown",
|
|
357
|
+
confidence: first?.confidence ?? "unknown",
|
|
358
|
+
...(options.rawMetadata !== undefined ? { raw: options.rawMetadata } : {}),
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
export function resolveModelCapabilityProfileCached(
|
|
363
|
+
options: ResolveModelCapabilityProfileOptions,
|
|
364
|
+
): ModelCapabilityProfile {
|
|
365
|
+
const key = cacheKey(options);
|
|
366
|
+
const cached = capabilityCache.get(key);
|
|
367
|
+
if (cached && options.rawMetadata === undefined) {
|
|
368
|
+
return cached;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const raw = resolveFromRawMetadata(options.providerId, options.modelId, options.rawMetadata);
|
|
372
|
+
const config = resolveFromConfig(options.providerId, options.modelId, options.providerConfig);
|
|
373
|
+
const detected = resolveFromDetectedFamily(options.providerId, options.modelId, options.rawMetadata);
|
|
374
|
+
const registry = resolveFromKnownRegistry(options.providerId, options.modelId);
|
|
375
|
+
const result = mergeProfiles({
|
|
376
|
+
providerId: options.providerId,
|
|
377
|
+
modelId: options.modelId,
|
|
378
|
+
rawMetadata: options.rawMetadata,
|
|
379
|
+
profiles: [raw, config, detected, registry],
|
|
380
|
+
});
|
|
381
|
+
capabilityCache.set(key, result);
|
|
382
|
+
return result;
|
|
383
|
+
}
|