@iaforged/context-code 1.2.9 → 1.2.10
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 +119 -119
- package/context-bootstrap.js +26 -26
- package/dist/src/QueryEngine.js +394 -327
- package/dist/src/bridge/bridgeUI.js +1 -1
- package/dist/src/buddy/prompt.js +4 -4
- package/dist/src/cli/handlers/auth.js +126 -9
- package/dist/src/cli/print.js +35 -1
- package/dist/src/commands/agent/agent.js +28 -2
- package/dist/src/commands/agent/agentStore.js +8 -1
- package/dist/src/commands/agent/index.js +1 -1
- package/dist/src/commands/bridge-kick.js +9 -9
- package/dist/src/commands/commit.js +34 -34
- package/dist/src/commands/init-verifiers.js +3 -3
- package/dist/src/commands/init.js +88 -88
- package/dist/src/commands/insights.js +787 -787
- package/dist/src/commands/install.js +19 -19
- package/dist/src/commands/login/login.js +21 -12
- package/dist/src/commands/logout/logout.js +9 -0
- package/dist/src/commands/model/model.js +9 -4
- package/dist/src/commands/orchestrate/SwarmUI.js +50 -0
- package/dist/src/commands/orchestrate/index.js +2 -2
- package/dist/src/commands/orchestrate/orchestrate.js +708 -12
- package/dist/src/commands/pr_comments/index.js +33 -33
- package/dist/src/commands/profile/index.js +1 -1
- package/dist/src/commands/profile/profile.js +52 -3
- package/dist/src/commands/provider/index.js +1 -1
- package/dist/src/commands/provider/provider.js +117 -45
- package/dist/src/commands/resumen/index.js +9 -0
- package/dist/src/commands/resumen/resumen.js +29 -0
- package/dist/src/commands/security-review.js +190 -190
- package/dist/src/commands/swarm-auto/index.js +9 -0
- package/dist/src/commands/swarm-auto/swarmAuto.js +111 -0
- package/dist/src/commands/swarm-init/index.js +9 -0
- package/dist/src/commands/swarm-init/swarmInit.js +72 -0
- package/dist/src/commands/team/team.js +39 -6
- package/dist/src/commands.js +14 -0
- package/dist/src/components/LogoV2/CondensedLogo.js +2 -2
- package/dist/src/components/PromptInput/PromptInputQueuedCommands.js +3 -3
- package/dist/src/components/agents/agentFileUtils.js +6 -6
- package/dist/src/components/permissions/hooks.js +5 -5
- package/dist/src/constants/outputStyles.js +83 -83
- package/dist/src/core/agents/blueprints.js +58 -0
- package/dist/src/core/agents/cliAdapter.js +61 -0
- package/dist/src/core/agents/registry.js +93 -0
- package/dist/src/core/agents/runtime.js +4 -0
- package/dist/src/core/agents/runtime.smoke.js +42 -0
- package/dist/src/core/agents/swarm.smoke.js +48 -0
- package/dist/src/core/agents/swarmTools.js +38 -0
- package/dist/src/core/auth/index.js +2 -0
- package/dist/src/core/auth/loginCliAdapter.js +24 -0
- package/dist/src/core/auth/loginCore.js +67 -0
- package/dist/src/core/auth/logoutCliAdapter.js +34 -0
- package/dist/src/core/auth/logoutCore.js +52 -0
- package/dist/src/core/auth/preflight.smoke.js +151 -0
- package/dist/src/core/index.js +21 -0
- package/dist/src/core/mcp/blueprints.js +27 -0
- package/dist/src/core/mcp/common.js +14 -0
- package/dist/src/core/mcp/runtime.js +67 -0
- package/dist/src/core/mcp/runtime.smoke.js +50 -0
- package/dist/src/core/mcp/swarmClient.js +40 -0
- package/dist/src/core/mcp/swarmSetup.js +43 -0
- package/dist/src/core/providers/cliAdapter.js +39 -0
- package/dist/src/core/providers/contracts.js +1 -0
- package/dist/src/core/providers/index.js +3 -0
- package/dist/src/core/providers/llmCore.js +123 -0
- package/dist/src/core/providers/providerCore.js +141 -0
- package/dist/src/core/providers/providerModelCompatibility.js +98 -0
- package/dist/src/core/providers/providerParitySmoke.js +83 -0
- package/dist/src/core/providers/providerProfileModelSmoke.js +80 -0
- package/dist/src/core/query/contracts.js +1 -0
- package/dist/src/core/query/runtime.js +117 -0
- package/dist/src/core/query/runtime.smoke.js +39 -0
- package/dist/src/core/query/timelineThinking.smoke.js +25 -0
- package/dist/src/core/query/wiring.smoke.js +76 -0
- package/dist/src/core/skills/cliAdapter.js +38 -0
- package/dist/src/core/skills/index.js +52 -0
- package/dist/src/core/skills/runtime.smoke.js +53 -0
- package/dist/src/core/tasks/runtime.js +205 -0
- package/dist/src/core/tasks/runtime.smoke.js +63 -0
- package/dist/src/core/tasks/sdkAdapter.js +4 -0
- package/dist/src/core/tools/contracts.js +3 -0
- package/dist/src/core/tools/fileResolution.js +112 -0
- package/dist/src/core/tools/fileResolution.smoke.js +33 -0
- package/dist/src/core/tools/filesCore.js +51 -0
- package/dist/src/core/tools/filesCore.smoke.js +108 -0
- package/dist/src/core/tools/gitCore.js +20 -0
- package/dist/src/core/tools/imageParity.smoke.js +36 -0
- package/dist/src/core/tools/notebookParity.smoke.js +68 -0
- package/dist/src/core/tools/registry.js +22 -0
- package/dist/src/core/tools/runtime.smoke.js +32 -0
- package/dist/src/core/tools/shellCore.js +60 -0
- package/dist/src/core/types/agentContext.js +9 -0
- package/dist/src/core/types/auth.js +3 -0
- package/dist/src/core/types/command.js +13 -0
- package/dist/src/core/types/provider.js +3 -0
- package/dist/src/core/types/sdkEvent.js +10 -0
- package/dist/src/core/types/swarm.js +1 -0
- package/dist/src/cost-tracker.js +3 -3
- package/dist/src/hooks/useAwaySummary.js +22 -9
- package/dist/src/main.js +32 -2
- package/dist/src/screens/REPL.js +9 -0
- package/dist/src/services/AgentSummary/agentSummary.js +10 -10
- package/dist/src/services/autoDream/autoDream.js +5 -5
- package/dist/src/services/autoDream/consolidationPrompt.js +49 -49
- package/dist/src/services/compact/prompt.js +238 -238
- package/dist/src/services/limits/sessionCounter.js +17 -17
- package/dist/src/services/mcp/client.js +27 -1
- package/dist/src/services/orchestration/execution/AgentTaskExecutor.js +39 -20
- package/dist/src/services/orchestration/execution/OrchestrationExecutionRuntime.js +65 -58
- package/dist/src/skills/bundled/loop.js +57 -57
- package/dist/src/skills/bundled/remember.js +53 -53
- package/dist/src/skills/bundled/simplify.js +49 -49
- package/dist/src/skills/bundled/skillify.js +2 -2
- package/dist/src/state/onChangeAppState.js +6 -0
- package/dist/src/tasks/LocalAgentTask/LocalAgentTask.js +5 -5
- package/dist/src/tasks/LocalMainSessionTask.js +5 -5
- package/dist/src/tasks/LocalShellTask/LocalShellTask.js +13 -13
- package/dist/src/tools/AgentTool/forkSubagent.js +25 -25
- package/dist/src/tools/AskUserQuestionTool/prompt.js +29 -29
- package/dist/src/tools/BashTool/BashTool.js +27 -2
- package/dist/src/tools/BriefTool/prompt.js +14 -14
- package/dist/src/tools/EnterPlanModeTool/EnterPlanModeTool.js +12 -12
- package/dist/src/tools/EnterPlanModeTool/prompt.js +140 -140
- package/dist/src/tools/ExitPlanModeTool/ExitPlanModeV2Tool.js +18 -18
- package/dist/src/tools/ExitPlanModeTool/prompt.js +23 -23
- package/dist/src/tools/ExitWorktreeTool/prompt.js +29 -29
- package/dist/src/tools/FileEditTool/prompt.js +7 -7
- package/dist/src/tools/FileReadTool/FileReadTool.js +18 -1
- package/dist/src/tools/FileWriteTool/prompt.js +6 -6
- package/dist/src/tools/GlobTool/prompt.js +4 -4
- package/dist/src/tools/GrepTool/prompt.js +10 -10
- package/dist/src/tools/LSPTool/prompt.js +18 -18
- package/dist/src/tools/ListMcpResourcesTool/prompt.js +15 -15
- package/dist/src/tools/PowerShellTool/PowerShellTool.js +25 -2
- package/dist/src/tools/ReadMcpResourceTool/prompt.js +13 -13
- package/dist/src/tools/SendMessageTool/prompt.js +36 -36
- package/dist/src/tools/SkillTool/prompt.js +21 -21
- package/dist/src/tools/SleepTool/prompt.js +10 -10
- package/dist/src/tools/TaskCreateTool/prompt.js +41 -41
- package/dist/src/tools/TaskGetTool/prompt.js +21 -21
- package/dist/src/tools/TaskListTool/prompt.js +30 -30
- package/dist/src/tools/TaskOutputTool/TaskOutputTool.js +8 -8
- package/dist/src/tools/TaskStopTool/prompt.js +5 -5
- package/dist/src/tools/TaskUpdateTool/prompt.js +74 -74
- package/dist/src/tools/TodoWriteTool/prompt.js +178 -178
- package/dist/src/tools/ToolSearchTool/prompt.js +9 -9
- package/dist/src/tools/WebFetchTool/WebFetchTool.js +9 -9
- package/dist/src/tools/WebFetchTool/prompt.js +31 -31
- package/dist/src/tools/WebSearchTool/prompt.js +26 -26
- package/dist/src/utils/agentContext.js +2 -0
- package/dist/src/utils/agenticSessionSearch.js +38 -38
- package/dist/src/utils/config.js +2 -0
- package/dist/src/utils/genericProcessUtils.js +21 -21
- package/dist/src/utils/heapDumpService.js +4 -4
- package/dist/src/utils/mcpValidation.js +2 -2
- package/dist/src/utils/model/modelStrings.js +1 -1
- package/dist/src/utils/model/providers.js +5 -0
- package/dist/src/utils/orchestration/store/providerAgentStore.js +22 -22
- package/dist/src/utils/orchestration/store/providerWorkspaceStore.js +10 -10
- package/dist/src/utils/orchestration/store/runStore.js +68 -68
- package/dist/src/utils/orchestration/store/teamStore.js +28 -28
- package/dist/src/utils/permissions/permissionExplainer.js +6 -6
- package/dist/src/utils/permissions/permissionsDb.js +43 -43
- package/dist/src/utils/sdkEventQueue.js +2 -0
- package/dist/src/utils/secureStorage/sqliteStorage.js +12 -12
- package/dist/src/utils/standardMcp/common.js +15 -0
- package/dist/src/utils/standardMcp/setup.js +52 -0
- package/dist/src/utils/swarm/teammatePromptAddendum.js +10 -10
- package/dist/src/utils/task/framework.js +6 -6
- package/package.json +1 -1
- package/dist/src/commands/usage/index.js +0 -7
- package/dist/src/commands/usage/usage.js +0 -5
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { getSecureStorage } from '../../utils/secureStorage/index.js';
|
|
2
|
+
import { getProviderProfile } from '../../utils/model/providerProfiles.js';
|
|
3
|
+
export const cliCredentialProvider = async (provider, profileId) => {
|
|
4
|
+
const storage = getSecureStorage().read() ?? {};
|
|
5
|
+
const profile = getProviderProfile(profileId);
|
|
6
|
+
let result = { kind: 'none', token: null, baseUrl: profile?.baseUrl };
|
|
7
|
+
// Buscar en API keys de perfil
|
|
8
|
+
const profileApiKey = storage.providerProfileApiKeys?.[profileId];
|
|
9
|
+
if (profileApiKey?.trim()) {
|
|
10
|
+
result = { kind: 'api-key', token: profileApiKey.trim() };
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
// Buscar en OAuth de perfil
|
|
14
|
+
const profileOauth = storage.providerProfileOauth?.[profileId]?.accessToken;
|
|
15
|
+
if (profileOauth?.trim()) {
|
|
16
|
+
result = { kind: 'oauth', token: profileOauth.trim() };
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
// Buscar en API keys de proveedor (fallback)
|
|
20
|
+
const providerApiKey = storage.providerApiKeys?.[provider];
|
|
21
|
+
if (providerApiKey?.trim()) {
|
|
22
|
+
result = { kind: 'api-key', token: providerApiKey.trim() };
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
// Fallback final a variables de entorno
|
|
26
|
+
const envKey = process.env[`${String(provider).toUpperCase().replace(/-/g, '_')}_API_KEY`];
|
|
27
|
+
if (envKey?.trim()) {
|
|
28
|
+
result = { kind: 'api-key', token: envKey.trim() };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
// Debug log (omit token for security)
|
|
34
|
+
try {
|
|
35
|
+
fs.appendFileSync('d:/Documents/GitHub/Claude/claude-code_V1/Context_Code_V1/CLI/scratch_auth_log.txt', `[${new Date().toISOString()}] provider=${provider} profileId=${profileId} found=${result.kind !== 'none'}\n`);
|
|
36
|
+
}
|
|
37
|
+
catch (e) { }
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
export class CoreLlmClient {
|
|
2
|
+
credentialProvider;
|
|
3
|
+
constructor(credentialProvider) {
|
|
4
|
+
this.credentialProvider = credentialProvider;
|
|
5
|
+
}
|
|
6
|
+
async chat(request) {
|
|
7
|
+
const credential = await this.credentialProvider(request.provider, request.profileId);
|
|
8
|
+
const baseUrl = credential.baseUrl || this.resolveBaseUrl(request.provider, request.profileId);
|
|
9
|
+
// Heuristica: si el provider es Claude O el baseUrl contiene 'anthropic', usar protocolo Anthropic
|
|
10
|
+
const isAnthropicProtocol = request.provider === 'claude' ||
|
|
11
|
+
baseUrl.toLowerCase().includes('anthropic');
|
|
12
|
+
if (isAnthropicProtocol) {
|
|
13
|
+
return this.callAnthropic(request, credential, baseUrl);
|
|
14
|
+
}
|
|
15
|
+
return this.callOpenAICompatible(request, credential, baseUrl);
|
|
16
|
+
}
|
|
17
|
+
async callOpenAICompatible(request, credential) {
|
|
18
|
+
const baseUrl = this.resolveBaseUrl(request.provider);
|
|
19
|
+
const headers = {
|
|
20
|
+
'Content-Type': 'application/json'
|
|
21
|
+
};
|
|
22
|
+
if (credential.token) {
|
|
23
|
+
headers['Authorization'] = `Bearer ${credential.token}`;
|
|
24
|
+
}
|
|
25
|
+
const response = await fetch(`${baseUrl}/chat/completions`, {
|
|
26
|
+
method: 'POST',
|
|
27
|
+
headers,
|
|
28
|
+
body: JSON.stringify({
|
|
29
|
+
model: request.model,
|
|
30
|
+
messages: request.messages,
|
|
31
|
+
tools: request.tools,
|
|
32
|
+
temperature: request.temperature ?? 0.2
|
|
33
|
+
})
|
|
34
|
+
});
|
|
35
|
+
if (!response.ok) {
|
|
36
|
+
const err = await response.text();
|
|
37
|
+
throw new Error(`LLM Error (${response.status}): ${err}`);
|
|
38
|
+
}
|
|
39
|
+
const data = await response.json();
|
|
40
|
+
return {
|
|
41
|
+
content: data.choices?.[0]?.message?.content ?? null,
|
|
42
|
+
toolCalls: data.choices?.[0]?.message?.tool_calls?.map((tc) => ({
|
|
43
|
+
id: tc.id,
|
|
44
|
+
name: tc.function.name,
|
|
45
|
+
arguments: tc.function.arguments
|
|
46
|
+
})),
|
|
47
|
+
usage: {
|
|
48
|
+
promptTokens: data.usage?.prompt_tokens ?? 0,
|
|
49
|
+
completionTokens: data.usage?.completion_tokens ?? 0
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
async callAnthropic(request, credential, baseUrl) {
|
|
54
|
+
const headers = {
|
|
55
|
+
'Content-Type': 'application/json',
|
|
56
|
+
'x-api-key': credential.token || '',
|
|
57
|
+
};
|
|
58
|
+
if (request.provider === 'claude') {
|
|
59
|
+
headers['anthropic-version'] = '2023-06-01';
|
|
60
|
+
}
|
|
61
|
+
const systemMessage = request.messages.find(m => m.role === 'system')?.content;
|
|
62
|
+
const messages = request.messages.filter(m => m.role !== 'system').map(m => ({
|
|
63
|
+
role: m.role === 'assistant' ? 'assistant' : 'user',
|
|
64
|
+
content: m.content
|
|
65
|
+
}));
|
|
66
|
+
const body = {
|
|
67
|
+
model: request.model,
|
|
68
|
+
max_tokens: request.maxTokens ?? 4096,
|
|
69
|
+
messages,
|
|
70
|
+
system: systemMessage,
|
|
71
|
+
tools: request.tools?.map(t => ({
|
|
72
|
+
name: t.name,
|
|
73
|
+
description: t.description,
|
|
74
|
+
input_schema: t.parameters
|
|
75
|
+
})),
|
|
76
|
+
temperature: request.temperature ?? 0.2
|
|
77
|
+
};
|
|
78
|
+
// Si el baseUrl ya termina en /messages o similar, lo usamos.
|
|
79
|
+
// Si no, construimos el endpoint de Anthropic estandar.
|
|
80
|
+
const endpoint = baseUrl.endsWith('/messages') ? baseUrl : `${baseUrl.replace(/\/+$/, '')}/v1/messages`;
|
|
81
|
+
const response = await fetch(endpoint, {
|
|
82
|
+
method: 'POST',
|
|
83
|
+
headers,
|
|
84
|
+
body: JSON.stringify(body)
|
|
85
|
+
});
|
|
86
|
+
if (!response.ok) {
|
|
87
|
+
const err = await response.text();
|
|
88
|
+
throw new Error(`Anthropic Error (${response.status}): ${err}`);
|
|
89
|
+
}
|
|
90
|
+
const data = await response.json();
|
|
91
|
+
const content = data.content.find((c) => c.type === 'text')?.text ?? null;
|
|
92
|
+
const toolCalls = data.content
|
|
93
|
+
.filter((c) => c.type === 'tool_use')
|
|
94
|
+
.map((tc) => ({
|
|
95
|
+
id: tc.id,
|
|
96
|
+
name: tc.name,
|
|
97
|
+
arguments: JSON.stringify(tc.input)
|
|
98
|
+
}));
|
|
99
|
+
return {
|
|
100
|
+
content,
|
|
101
|
+
toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
|
|
102
|
+
usage: {
|
|
103
|
+
promptTokens: data.usage?.input_tokens ?? 0,
|
|
104
|
+
completionTokens: data.usage?.output_tokens ?? 0
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
resolveBaseUrl(provider, profileId) {
|
|
109
|
+
const storage = this.credentialProvider ? {} : {}; // Placeholder, logic below
|
|
110
|
+
// Note: We need a way to get the baseUrl from storage without duplicating logic.
|
|
111
|
+
// For now, we'll assume the credentialProvider can also provide the baseUrl if we extend it.
|
|
112
|
+
const defaults = {
|
|
113
|
+
claude: 'https://api.anthropic.com',
|
|
114
|
+
openai: 'https://api.openai.com/v1',
|
|
115
|
+
openrouter: 'https://openrouter.ai/api/v1',
|
|
116
|
+
ollama: 'http://localhost:11434/v1',
|
|
117
|
+
'gemini-api': 'https://generativelanguage.googleapis.com/v1beta/openai',
|
|
118
|
+
zai: 'https://api.z.ai/api/coding/paas/v4',
|
|
119
|
+
minimax: 'https://api.minimax.chat/v1'
|
|
120
|
+
};
|
|
121
|
+
return defaults[provider] || defaults.openai;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { getVisibleProvider, VISIBLE_PROVIDERS, } from '../../utils/model/providerCatalog.js';
|
|
2
|
+
import { getActiveProviderProfile, getLastUsedProviderProfile, isProfiledProvider, listProviderProfiles, setActiveProfileForProvider, } from '../../utils/model/providerProfiles.js';
|
|
3
|
+
import { switchProviderPreference } from '../../utils/model/providerSwitch.js';
|
|
4
|
+
import { hasStoredProviderApiKey, hasStoredProviderOAuthTokens, } from '../../utils/auth.js';
|
|
5
|
+
export function isCoreProviderEnabled() {
|
|
6
|
+
return process.env.CORE_PROVIDER_ENABLED === '1';
|
|
7
|
+
}
|
|
8
|
+
export function listCoreProviders() {
|
|
9
|
+
return VISIBLE_PROVIDERS.map(item => ({
|
|
10
|
+
id: item.id,
|
|
11
|
+
label: item.label,
|
|
12
|
+
implemented: item.implemented,
|
|
13
|
+
}));
|
|
14
|
+
}
|
|
15
|
+
export function normalizeCoreProviderInput(rawProvider) {
|
|
16
|
+
if (!rawProvider) {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
switch (rawProvider.trim().toLowerCase()) {
|
|
20
|
+
case 'z.ai':
|
|
21
|
+
case 'z-ai':
|
|
22
|
+
return 'zai';
|
|
23
|
+
case 'open-router':
|
|
24
|
+
return 'openrouter';
|
|
25
|
+
case 'ollama-cloud':
|
|
26
|
+
case 'ollama_cloud':
|
|
27
|
+
case 'ollamacloud':
|
|
28
|
+
return 'ollama-cloud';
|
|
29
|
+
case 'gemini':
|
|
30
|
+
case 'gemini-api-key':
|
|
31
|
+
case 'google-ai':
|
|
32
|
+
case 'google-ai-studio':
|
|
33
|
+
return 'gemini-api';
|
|
34
|
+
case 'gemini-google':
|
|
35
|
+
case 'google':
|
|
36
|
+
case 'google-oauth':
|
|
37
|
+
return 'gemini-google';
|
|
38
|
+
default:
|
|
39
|
+
return rawProvider.trim().toLowerCase();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export function getCoreProviderLabel(provider) {
|
|
43
|
+
return getVisibleProvider(provider).label;
|
|
44
|
+
}
|
|
45
|
+
export function listCoreProviderProfiles() {
|
|
46
|
+
const allProfiles = listProviderProfiles();
|
|
47
|
+
const activeProfile = getActiveProviderProfile();
|
|
48
|
+
const grouped = allProfiles.reduce((acc, profile) => {
|
|
49
|
+
const provider = profile.provider;
|
|
50
|
+
if (!acc[provider]) {
|
|
51
|
+
acc[provider] = [];
|
|
52
|
+
}
|
|
53
|
+
acc[provider].push({
|
|
54
|
+
id: profile.id,
|
|
55
|
+
name: profile.name,
|
|
56
|
+
provider,
|
|
57
|
+
baseUrl: profile.baseUrl,
|
|
58
|
+
isActive: profile.id === activeProfile?.id,
|
|
59
|
+
isAuthenticated: isProviderProfileAuthenticated(provider, profile.id),
|
|
60
|
+
});
|
|
61
|
+
return acc;
|
|
62
|
+
}, {});
|
|
63
|
+
return {
|
|
64
|
+
activeProfile: activeProfile
|
|
65
|
+
? {
|
|
66
|
+
id: activeProfile.id,
|
|
67
|
+
name: activeProfile.name,
|
|
68
|
+
provider: activeProfile.provider,
|
|
69
|
+
baseUrl: activeProfile.baseUrl,
|
|
70
|
+
}
|
|
71
|
+
: null,
|
|
72
|
+
providers: grouped,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
export function getCoreCurrentProviderProfile() {
|
|
76
|
+
const activeProfile = getActiveProviderProfile();
|
|
77
|
+
if (!activeProfile) {
|
|
78
|
+
return { activeProfile: null };
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
activeProfile: {
|
|
82
|
+
id: activeProfile.id,
|
|
83
|
+
name: activeProfile.name,
|
|
84
|
+
provider: activeProfile.provider,
|
|
85
|
+
baseUrl: activeProfile.baseUrl,
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
export function setCoreActiveProfile(provider, profileName) {
|
|
90
|
+
if (!isProfiledProvider(provider)) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
setActiveProfileForProvider(provider, profileName);
|
|
94
|
+
}
|
|
95
|
+
export function isCoreSelectedProfileReady(provider, profileName) {
|
|
96
|
+
if (!profileName || !isProfiledProvider(provider)) {
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
const profile = getLastUsedProviderProfile(provider);
|
|
100
|
+
if (!profile) {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
return isProviderProfileAuthenticated(provider, profile.id);
|
|
104
|
+
}
|
|
105
|
+
export function switchCoreProvider(input) {
|
|
106
|
+
const restoredModel = switchProviderPreference({
|
|
107
|
+
currentModel: input.currentModel,
|
|
108
|
+
currentProvider: input.currentProvider,
|
|
109
|
+
targetProvider: input.targetProvider,
|
|
110
|
+
targetProfileName: input.targetProfileName,
|
|
111
|
+
});
|
|
112
|
+
const profile = isProfiledProvider(input.targetProvider)
|
|
113
|
+
? getLastUsedProviderProfile(input.targetProvider)
|
|
114
|
+
: null;
|
|
115
|
+
return {
|
|
116
|
+
restoredModel,
|
|
117
|
+
activeProfileName: profile?.name,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function isProviderProfileAuthenticated(provider, profileId) {
|
|
121
|
+
if (provider === 'claude') {
|
|
122
|
+
return hasStoredProviderOAuthTokens('claude', profileId);
|
|
123
|
+
}
|
|
124
|
+
if (provider === 'openai') {
|
|
125
|
+
return hasStoredProviderOAuthTokens('openai', profileId);
|
|
126
|
+
}
|
|
127
|
+
if (provider === 'openrouter' ||
|
|
128
|
+
provider === 'gemini-api' ||
|
|
129
|
+
provider === 'zai' ||
|
|
130
|
+
provider === 'minimax' ||
|
|
131
|
+
provider === 'nvidia') {
|
|
132
|
+
return hasStoredProviderApiKey(provider, profileId);
|
|
133
|
+
}
|
|
134
|
+
if (provider === 'ollama' || provider === 'ollama-cloud') {
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
if (provider === 'gemini-google') {
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single-source compatibility samples used by smoke/parity checks.
|
|
3
|
+
* Keep this in sync with validateProviderModelCompatibility rules.
|
|
4
|
+
*/
|
|
5
|
+
export const PROVIDER_COMPATIBILITY_SAMPLES = {
|
|
6
|
+
claude: {
|
|
7
|
+
accepts: ['claude-sonnet-4-5'],
|
|
8
|
+
rejects: ['gpt-4o'],
|
|
9
|
+
},
|
|
10
|
+
openai: {
|
|
11
|
+
accepts: ['gpt-4.1', 'o3', 'codex-mini-latest'],
|
|
12
|
+
rejects: ['claude-sonnet-4-5'],
|
|
13
|
+
},
|
|
14
|
+
'gemini-api': {
|
|
15
|
+
accepts: ['gemini-2.5-pro'],
|
|
16
|
+
rejects: ['gpt-4o'],
|
|
17
|
+
},
|
|
18
|
+
'gemini-google': {
|
|
19
|
+
accepts: ['gemini-2.5-flash'],
|
|
20
|
+
rejects: ['claude-sonnet-4-5'],
|
|
21
|
+
},
|
|
22
|
+
openrouter: {
|
|
23
|
+
accepts: ['anthropic/claude-sonnet-4'],
|
|
24
|
+
rejects: [],
|
|
25
|
+
},
|
|
26
|
+
ollama: {
|
|
27
|
+
accepts: ['llama3.1:8b'],
|
|
28
|
+
rejects: [],
|
|
29
|
+
},
|
|
30
|
+
'ollama-cloud': {
|
|
31
|
+
accepts: ['llama3.3:70b-instruct-q8_0'],
|
|
32
|
+
rejects: [],
|
|
33
|
+
},
|
|
34
|
+
zai: {
|
|
35
|
+
accepts: ['glm-4.5'],
|
|
36
|
+
rejects: [],
|
|
37
|
+
},
|
|
38
|
+
minimax: {
|
|
39
|
+
accepts: ['MiniMax-M2.7'],
|
|
40
|
+
rejects: [],
|
|
41
|
+
},
|
|
42
|
+
nvidia: {
|
|
43
|
+
accepts: ['meta/llama-3.1-70b-instruct'],
|
|
44
|
+
rejects: [],
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Canonical, no-network compatibility pre-check shared by CLI/Desktop adapters.
|
|
49
|
+
* Dynamic catalogs (OpenRouter/Ollama/ZAI/etc.) are treated as compatible when
|
|
50
|
+
* the model string is non-empty; strict validation is delegated to runtime APIs.
|
|
51
|
+
*/
|
|
52
|
+
export function validateProviderModelCompatibility(provider, model) {
|
|
53
|
+
const normalizedModel = (model ?? '').trim();
|
|
54
|
+
if (!normalizedModel) {
|
|
55
|
+
return {
|
|
56
|
+
provider,
|
|
57
|
+
model: '',
|
|
58
|
+
compatible: false,
|
|
59
|
+
reason: 'empty-model',
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
const m = normalizedModel.toLowerCase();
|
|
63
|
+
if (provider === 'claude') {
|
|
64
|
+
return {
|
|
65
|
+
provider,
|
|
66
|
+
model: normalizedModel,
|
|
67
|
+
compatible: m.startsWith('claude-'),
|
|
68
|
+
reason: 'claude-prefix',
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
if (provider === 'openai') {
|
|
72
|
+
const compatible = m.startsWith('gpt-') ||
|
|
73
|
+
m.startsWith('o1') ||
|
|
74
|
+
m.startsWith('o3') ||
|
|
75
|
+
m.startsWith('o4') ||
|
|
76
|
+
m.startsWith('codex-');
|
|
77
|
+
return {
|
|
78
|
+
provider,
|
|
79
|
+
model: normalizedModel,
|
|
80
|
+
compatible,
|
|
81
|
+
reason: 'openai-family',
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
if (provider === 'gemini-api' || provider === 'gemini-google') {
|
|
85
|
+
return {
|
|
86
|
+
provider,
|
|
87
|
+
model: normalizedModel,
|
|
88
|
+
compatible: m.startsWith('gemini-'),
|
|
89
|
+
reason: 'gemini-prefix',
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
provider,
|
|
94
|
+
model: normalizedModel,
|
|
95
|
+
compatible: true,
|
|
96
|
+
reason: 'dynamic-catalog',
|
|
97
|
+
};
|
|
98
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { VISIBLE_PROVIDERS } from '../../utils/model/providerCatalog.js';
|
|
3
|
+
import { listCoreProviders, normalizeCoreProviderInput, } from './providerCore.js';
|
|
4
|
+
import { PROVIDER_COMPATIBILITY_SAMPLES, validateProviderModelCompatibility, } from './providerModelCompatibility.js';
|
|
5
|
+
function normalizeLegacyProviderInput(rawProvider) {
|
|
6
|
+
if (!rawProvider) {
|
|
7
|
+
return undefined;
|
|
8
|
+
}
|
|
9
|
+
switch (rawProvider.trim().toLowerCase()) {
|
|
10
|
+
case 'z.ai':
|
|
11
|
+
case 'z-ai':
|
|
12
|
+
return 'zai';
|
|
13
|
+
case 'open-router':
|
|
14
|
+
return 'openrouter';
|
|
15
|
+
case 'ollama-cloud':
|
|
16
|
+
case 'ollama_cloud':
|
|
17
|
+
case 'ollamacloud':
|
|
18
|
+
return 'ollama-cloud';
|
|
19
|
+
case 'gemini':
|
|
20
|
+
case 'gemini-api-key':
|
|
21
|
+
case 'google-ai':
|
|
22
|
+
case 'google-ai-studio':
|
|
23
|
+
return 'gemini-api';
|
|
24
|
+
case 'gemini-google':
|
|
25
|
+
case 'google':
|
|
26
|
+
case 'google-oauth':
|
|
27
|
+
return 'gemini-google';
|
|
28
|
+
default:
|
|
29
|
+
return rawProvider.trim().toLowerCase();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function runProviderNormalizationParity() {
|
|
33
|
+
const cases = [
|
|
34
|
+
{ input: 'z.ai', expected: 'zai' },
|
|
35
|
+
{ input: 'open-router', expected: 'openrouter' },
|
|
36
|
+
{ input: 'ollama_cloud', expected: 'ollama-cloud' },
|
|
37
|
+
{ input: 'gemini', expected: 'gemini-api' },
|
|
38
|
+
{ input: 'google', expected: 'gemini-google' },
|
|
39
|
+
{ input: 'CLAUDE', expected: 'claude' },
|
|
40
|
+
{ input: 'OpenAI', expected: 'openai' },
|
|
41
|
+
];
|
|
42
|
+
for (const c of cases) {
|
|
43
|
+
const core = normalizeCoreProviderInput(c.input);
|
|
44
|
+
const legacy = normalizeLegacyProviderInput(c.input);
|
|
45
|
+
assert.equal(core, c.expected);
|
|
46
|
+
assert.equal(legacy, c.expected);
|
|
47
|
+
assert.equal(core, legacy);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function runProviderCatalogParity() {
|
|
51
|
+
const coreIds = listCoreProviders().map(p => p.id).sort();
|
|
52
|
+
const legacyIds = VISIBLE_PROVIDERS.map(p => p.id).sort();
|
|
53
|
+
assert.deepEqual(coreIds, legacyIds);
|
|
54
|
+
}
|
|
55
|
+
function runCompatibilityChecks() {
|
|
56
|
+
const coreIds = listCoreProviders().map(p => p.id).sort();
|
|
57
|
+
const sampleIds = Object.keys(PROVIDER_COMPATIBILITY_SAMPLES).sort();
|
|
58
|
+
assert.deepEqual(sampleIds, coreIds);
|
|
59
|
+
for (const provider of coreIds) {
|
|
60
|
+
const sample = PROVIDER_COMPATIBILITY_SAMPLES[provider];
|
|
61
|
+
for (const model of sample.accepts) {
|
|
62
|
+
const result = validateProviderModelCompatibility(provider, model);
|
|
63
|
+
assert.equal(result.compatible, true);
|
|
64
|
+
}
|
|
65
|
+
for (const model of sample.rejects) {
|
|
66
|
+
const result = validateProviderModelCompatibility(provider, model);
|
|
67
|
+
assert.equal(result.compatible, false);
|
|
68
|
+
}
|
|
69
|
+
const empty = validateProviderModelCompatibility(provider, '');
|
|
70
|
+
assert.equal(empty.compatible, false);
|
|
71
|
+
assert.equal(empty.reason, 'empty-model');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
export function runProviderParitySmoke() {
|
|
75
|
+
runProviderNormalizationParity();
|
|
76
|
+
runProviderCatalogParity();
|
|
77
|
+
runCompatibilityChecks();
|
|
78
|
+
}
|
|
79
|
+
const isDirectRun = process.argv[1]?.endsWith('providerParitySmoke.js');
|
|
80
|
+
if (isDirectRun) {
|
|
81
|
+
runProviderParitySmoke();
|
|
82
|
+
console.log('provider parity smoke: ok');
|
|
83
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { mkdtempSync, rmSync } from 'node:fs';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { enableConfigs } from '../../utils/config.js';
|
|
6
|
+
async function runProviderProfileModelSmoke() {
|
|
7
|
+
const tempConfigDir = mkdtempSync(join(tmpdir(), 'context-core-provider-smoke-'));
|
|
8
|
+
process.env.CONTEXT_CONFIG_DIR = tempConfigDir;
|
|
9
|
+
process.env.CORE_PROVIDER_ENABLED = '1';
|
|
10
|
+
enableConfigs();
|
|
11
|
+
try {
|
|
12
|
+
const config = await import('../../utils/config.js');
|
|
13
|
+
config.enableConfigs();
|
|
14
|
+
const providerProfiles = await import('../../utils/model/providerProfiles.js');
|
|
15
|
+
const providerSwitch = await import('../../utils/model/providerSwitch.js');
|
|
16
|
+
const providerDb = await import('../../utils/model/providerProfilesDb.js');
|
|
17
|
+
const providerCore = await import('./providerCore.js');
|
|
18
|
+
providerProfiles.clearAllProviderProfiles();
|
|
19
|
+
providerProfiles.createProviderProfile({
|
|
20
|
+
provider: 'claude',
|
|
21
|
+
name: 'main',
|
|
22
|
+
agentName: 'claude-main',
|
|
23
|
+
});
|
|
24
|
+
providerProfiles.createProviderProfile({
|
|
25
|
+
provider: 'openai',
|
|
26
|
+
name: 'work',
|
|
27
|
+
agentName: 'openai-work',
|
|
28
|
+
});
|
|
29
|
+
providerProfiles.setActiveProfileForProvider('openai', 'work');
|
|
30
|
+
providerProfiles.setProviderProfileLastModelByName('openai', 'work', 'gpt-4.1');
|
|
31
|
+
const switched = providerCore.switchCoreProvider({
|
|
32
|
+
currentModel: 'sonnet',
|
|
33
|
+
currentProvider: 'claude',
|
|
34
|
+
targetProvider: 'openai',
|
|
35
|
+
targetProfileName: 'work',
|
|
36
|
+
});
|
|
37
|
+
assert.equal(switched.activeProfileName, 'work');
|
|
38
|
+
assert.equal(switched.restoredModel, 'gpt-4.1');
|
|
39
|
+
const activeProvider = providerDb.getStoredActiveProviderPreference();
|
|
40
|
+
assert.equal(activeProvider, 'openai');
|
|
41
|
+
const normalized = providerCore.normalizeCoreProviderInput('google-oauth');
|
|
42
|
+
assert.equal(normalized, 'gemini-google');
|
|
43
|
+
const current = providerCore.getCoreCurrentProviderProfile();
|
|
44
|
+
assert.equal(current.activeProfile?.provider, 'openai');
|
|
45
|
+
assert.equal(current.activeProfile?.name, 'work');
|
|
46
|
+
const list = providerCore.listCoreProviderProfiles();
|
|
47
|
+
const openaiProfiles = list.providers.openai ?? [];
|
|
48
|
+
assert.ok(openaiProfiles.some((p) => p.name === 'work'));
|
|
49
|
+
// Validate compatibility helper remains aligned with provider IDs.
|
|
50
|
+
const compatibility = await import('./providerModelCompatibility.js');
|
|
51
|
+
const providerIds = providerCore.listCoreProviders().map((p) => p.id);
|
|
52
|
+
for (const providerId of providerIds) {
|
|
53
|
+
const sample = compatibility.PROVIDER_COMPATIBILITY_SAMPLES[providerId];
|
|
54
|
+
assert.ok(sample);
|
|
55
|
+
assert.equal(compatibility.validateProviderModelCompatibility(providerId, sample.accepts[0]).compatible, true);
|
|
56
|
+
}
|
|
57
|
+
// Legacy route should also restore model for active profiled provider.
|
|
58
|
+
const restoredLegacy = providerSwitch.switchProviderPreference({
|
|
59
|
+
currentModel: 'claude-legacy',
|
|
60
|
+
currentProvider: 'claude',
|
|
61
|
+
targetProvider: 'openai',
|
|
62
|
+
targetProfileName: 'work',
|
|
63
|
+
});
|
|
64
|
+
assert.equal(restoredLegacy, 'gpt-4.1');
|
|
65
|
+
}
|
|
66
|
+
finally {
|
|
67
|
+
try {
|
|
68
|
+
rmSync(tempConfigDir, { recursive: true, force: true });
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// Windows can keep sqlite handles alive briefly; cleanup is best-effort.
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const isDirectRun = process.argv[1]?.endsWith('providerProfileModelSmoke.js');
|
|
76
|
+
if (isDirectRun) {
|
|
77
|
+
await runProviderProfileModelSmoke();
|
|
78
|
+
console.log('provider/profile/model smoke: ok');
|
|
79
|
+
}
|
|
80
|
+
export { runProviderProfileModelSmoke };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|