@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.
Files changed (172) hide show
  1. package/README.md +119 -119
  2. package/context-bootstrap.js +26 -26
  3. package/dist/src/QueryEngine.js +394 -327
  4. package/dist/src/bridge/bridgeUI.js +1 -1
  5. package/dist/src/buddy/prompt.js +4 -4
  6. package/dist/src/cli/handlers/auth.js +126 -9
  7. package/dist/src/cli/print.js +35 -1
  8. package/dist/src/commands/agent/agent.js +28 -2
  9. package/dist/src/commands/agent/agentStore.js +8 -1
  10. package/dist/src/commands/agent/index.js +1 -1
  11. package/dist/src/commands/bridge-kick.js +9 -9
  12. package/dist/src/commands/commit.js +34 -34
  13. package/dist/src/commands/init-verifiers.js +3 -3
  14. package/dist/src/commands/init.js +88 -88
  15. package/dist/src/commands/insights.js +787 -787
  16. package/dist/src/commands/install.js +19 -19
  17. package/dist/src/commands/login/login.js +21 -12
  18. package/dist/src/commands/logout/logout.js +9 -0
  19. package/dist/src/commands/model/model.js +9 -4
  20. package/dist/src/commands/orchestrate/SwarmUI.js +50 -0
  21. package/dist/src/commands/orchestrate/index.js +2 -2
  22. package/dist/src/commands/orchestrate/orchestrate.js +708 -12
  23. package/dist/src/commands/pr_comments/index.js +33 -33
  24. package/dist/src/commands/profile/index.js +1 -1
  25. package/dist/src/commands/profile/profile.js +52 -3
  26. package/dist/src/commands/provider/index.js +1 -1
  27. package/dist/src/commands/provider/provider.js +117 -45
  28. package/dist/src/commands/resumen/index.js +9 -0
  29. package/dist/src/commands/resumen/resumen.js +29 -0
  30. package/dist/src/commands/security-review.js +190 -190
  31. package/dist/src/commands/swarm-auto/index.js +9 -0
  32. package/dist/src/commands/swarm-auto/swarmAuto.js +111 -0
  33. package/dist/src/commands/swarm-init/index.js +9 -0
  34. package/dist/src/commands/swarm-init/swarmInit.js +72 -0
  35. package/dist/src/commands/team/team.js +39 -6
  36. package/dist/src/commands.js +14 -0
  37. package/dist/src/components/LogoV2/CondensedLogo.js +2 -2
  38. package/dist/src/components/PromptInput/PromptInputQueuedCommands.js +3 -3
  39. package/dist/src/components/agents/agentFileUtils.js +6 -6
  40. package/dist/src/components/permissions/hooks.js +5 -5
  41. package/dist/src/constants/outputStyles.js +83 -83
  42. package/dist/src/core/agents/blueprints.js +58 -0
  43. package/dist/src/core/agents/cliAdapter.js +61 -0
  44. package/dist/src/core/agents/registry.js +93 -0
  45. package/dist/src/core/agents/runtime.js +4 -0
  46. package/dist/src/core/agents/runtime.smoke.js +42 -0
  47. package/dist/src/core/agents/swarm.smoke.js +48 -0
  48. package/dist/src/core/agents/swarmTools.js +38 -0
  49. package/dist/src/core/auth/index.js +2 -0
  50. package/dist/src/core/auth/loginCliAdapter.js +24 -0
  51. package/dist/src/core/auth/loginCore.js +67 -0
  52. package/dist/src/core/auth/logoutCliAdapter.js +34 -0
  53. package/dist/src/core/auth/logoutCore.js +52 -0
  54. package/dist/src/core/auth/preflight.smoke.js +151 -0
  55. package/dist/src/core/index.js +21 -0
  56. package/dist/src/core/mcp/blueprints.js +27 -0
  57. package/dist/src/core/mcp/common.js +14 -0
  58. package/dist/src/core/mcp/runtime.js +67 -0
  59. package/dist/src/core/mcp/runtime.smoke.js +50 -0
  60. package/dist/src/core/mcp/swarmClient.js +40 -0
  61. package/dist/src/core/mcp/swarmSetup.js +43 -0
  62. package/dist/src/core/providers/cliAdapter.js +39 -0
  63. package/dist/src/core/providers/contracts.js +1 -0
  64. package/dist/src/core/providers/index.js +3 -0
  65. package/dist/src/core/providers/llmCore.js +123 -0
  66. package/dist/src/core/providers/providerCore.js +141 -0
  67. package/dist/src/core/providers/providerModelCompatibility.js +98 -0
  68. package/dist/src/core/providers/providerParitySmoke.js +83 -0
  69. package/dist/src/core/providers/providerProfileModelSmoke.js +80 -0
  70. package/dist/src/core/query/contracts.js +1 -0
  71. package/dist/src/core/query/runtime.js +117 -0
  72. package/dist/src/core/query/runtime.smoke.js +39 -0
  73. package/dist/src/core/query/timelineThinking.smoke.js +25 -0
  74. package/dist/src/core/query/wiring.smoke.js +76 -0
  75. package/dist/src/core/skills/cliAdapter.js +38 -0
  76. package/dist/src/core/skills/index.js +52 -0
  77. package/dist/src/core/skills/runtime.smoke.js +53 -0
  78. package/dist/src/core/tasks/runtime.js +205 -0
  79. package/dist/src/core/tasks/runtime.smoke.js +63 -0
  80. package/dist/src/core/tasks/sdkAdapter.js +4 -0
  81. package/dist/src/core/tools/contracts.js +3 -0
  82. package/dist/src/core/tools/fileResolution.js +112 -0
  83. package/dist/src/core/tools/fileResolution.smoke.js +33 -0
  84. package/dist/src/core/tools/filesCore.js +51 -0
  85. package/dist/src/core/tools/filesCore.smoke.js +108 -0
  86. package/dist/src/core/tools/gitCore.js +20 -0
  87. package/dist/src/core/tools/imageParity.smoke.js +36 -0
  88. package/dist/src/core/tools/notebookParity.smoke.js +68 -0
  89. package/dist/src/core/tools/registry.js +22 -0
  90. package/dist/src/core/tools/runtime.smoke.js +32 -0
  91. package/dist/src/core/tools/shellCore.js +60 -0
  92. package/dist/src/core/types/agentContext.js +9 -0
  93. package/dist/src/core/types/auth.js +3 -0
  94. package/dist/src/core/types/command.js +13 -0
  95. package/dist/src/core/types/provider.js +3 -0
  96. package/dist/src/core/types/sdkEvent.js +10 -0
  97. package/dist/src/core/types/swarm.js +1 -0
  98. package/dist/src/cost-tracker.js +3 -3
  99. package/dist/src/hooks/useAwaySummary.js +22 -9
  100. package/dist/src/main.js +32 -2
  101. package/dist/src/screens/REPL.js +9 -0
  102. package/dist/src/services/AgentSummary/agentSummary.js +10 -10
  103. package/dist/src/services/autoDream/autoDream.js +5 -5
  104. package/dist/src/services/autoDream/consolidationPrompt.js +49 -49
  105. package/dist/src/services/compact/prompt.js +238 -238
  106. package/dist/src/services/limits/sessionCounter.js +17 -17
  107. package/dist/src/services/mcp/client.js +27 -1
  108. package/dist/src/services/orchestration/execution/AgentTaskExecutor.js +39 -20
  109. package/dist/src/services/orchestration/execution/OrchestrationExecutionRuntime.js +65 -58
  110. package/dist/src/skills/bundled/loop.js +57 -57
  111. package/dist/src/skills/bundled/remember.js +53 -53
  112. package/dist/src/skills/bundled/simplify.js +49 -49
  113. package/dist/src/skills/bundled/skillify.js +2 -2
  114. package/dist/src/state/onChangeAppState.js +6 -0
  115. package/dist/src/tasks/LocalAgentTask/LocalAgentTask.js +5 -5
  116. package/dist/src/tasks/LocalMainSessionTask.js +5 -5
  117. package/dist/src/tasks/LocalShellTask/LocalShellTask.js +13 -13
  118. package/dist/src/tools/AgentTool/forkSubagent.js +25 -25
  119. package/dist/src/tools/AskUserQuestionTool/prompt.js +29 -29
  120. package/dist/src/tools/BashTool/BashTool.js +27 -2
  121. package/dist/src/tools/BriefTool/prompt.js +14 -14
  122. package/dist/src/tools/EnterPlanModeTool/EnterPlanModeTool.js +12 -12
  123. package/dist/src/tools/EnterPlanModeTool/prompt.js +140 -140
  124. package/dist/src/tools/ExitPlanModeTool/ExitPlanModeV2Tool.js +18 -18
  125. package/dist/src/tools/ExitPlanModeTool/prompt.js +23 -23
  126. package/dist/src/tools/ExitWorktreeTool/prompt.js +29 -29
  127. package/dist/src/tools/FileEditTool/prompt.js +7 -7
  128. package/dist/src/tools/FileReadTool/FileReadTool.js +18 -1
  129. package/dist/src/tools/FileWriteTool/prompt.js +6 -6
  130. package/dist/src/tools/GlobTool/prompt.js +4 -4
  131. package/dist/src/tools/GrepTool/prompt.js +10 -10
  132. package/dist/src/tools/LSPTool/prompt.js +18 -18
  133. package/dist/src/tools/ListMcpResourcesTool/prompt.js +15 -15
  134. package/dist/src/tools/PowerShellTool/PowerShellTool.js +25 -2
  135. package/dist/src/tools/ReadMcpResourceTool/prompt.js +13 -13
  136. package/dist/src/tools/SendMessageTool/prompt.js +36 -36
  137. package/dist/src/tools/SkillTool/prompt.js +21 -21
  138. package/dist/src/tools/SleepTool/prompt.js +10 -10
  139. package/dist/src/tools/TaskCreateTool/prompt.js +41 -41
  140. package/dist/src/tools/TaskGetTool/prompt.js +21 -21
  141. package/dist/src/tools/TaskListTool/prompt.js +30 -30
  142. package/dist/src/tools/TaskOutputTool/TaskOutputTool.js +8 -8
  143. package/dist/src/tools/TaskStopTool/prompt.js +5 -5
  144. package/dist/src/tools/TaskUpdateTool/prompt.js +74 -74
  145. package/dist/src/tools/TodoWriteTool/prompt.js +178 -178
  146. package/dist/src/tools/ToolSearchTool/prompt.js +9 -9
  147. package/dist/src/tools/WebFetchTool/WebFetchTool.js +9 -9
  148. package/dist/src/tools/WebFetchTool/prompt.js +31 -31
  149. package/dist/src/tools/WebSearchTool/prompt.js +26 -26
  150. package/dist/src/utils/agentContext.js +2 -0
  151. package/dist/src/utils/agenticSessionSearch.js +38 -38
  152. package/dist/src/utils/config.js +2 -0
  153. package/dist/src/utils/genericProcessUtils.js +21 -21
  154. package/dist/src/utils/heapDumpService.js +4 -4
  155. package/dist/src/utils/mcpValidation.js +2 -2
  156. package/dist/src/utils/model/modelStrings.js +1 -1
  157. package/dist/src/utils/model/providers.js +5 -0
  158. package/dist/src/utils/orchestration/store/providerAgentStore.js +22 -22
  159. package/dist/src/utils/orchestration/store/providerWorkspaceStore.js +10 -10
  160. package/dist/src/utils/orchestration/store/runStore.js +68 -68
  161. package/dist/src/utils/orchestration/store/teamStore.js +28 -28
  162. package/dist/src/utils/permissions/permissionExplainer.js +6 -6
  163. package/dist/src/utils/permissions/permissionsDb.js +43 -43
  164. package/dist/src/utils/sdkEventQueue.js +2 -0
  165. package/dist/src/utils/secureStorage/sqliteStorage.js +12 -12
  166. package/dist/src/utils/standardMcp/common.js +15 -0
  167. package/dist/src/utils/standardMcp/setup.js +52 -0
  168. package/dist/src/utils/swarm/teammatePromptAddendum.js +10 -10
  169. package/dist/src/utils/task/framework.js +6 -6
  170. package/package.json +1 -1
  171. package/dist/src/commands/usage/index.js +0 -7
  172. 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,3 @@
1
+ export * from './contracts.js';
2
+ export * from './providerCore.js';
3
+ export * from './providerModelCompatibility.js';
@@ -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 {};