@cloudcli-ai/cloudcli 1.32.0 → 1.33.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.
Files changed (180) hide show
  1. package/README.de.md +1 -1
  2. package/README.ko.md +1 -1
  3. package/README.md +1 -1
  4. package/README.ru.md +1 -1
  5. package/README.tr.md +1 -1
  6. package/README.zh-CN.md +1 -1
  7. package/dist/api-docs.html +6 -7
  8. package/dist/assets/index-BsyL_xmI.js +1348 -0
  9. package/dist/assets/index-FSP9Hz7V.css +32 -0
  10. package/dist/index.html +2 -2
  11. package/dist/modelConstants.js +848 -0
  12. package/dist-server/server/claude-sdk.js +57 -34
  13. package/dist-server/server/claude-sdk.js.map +1 -1
  14. package/dist-server/server/cursor-cli.js +6 -3
  15. package/dist-server/server/cursor-cli.js.map +1 -1
  16. package/dist-server/server/gemini-cli.js +3 -1
  17. package/dist-server/server/gemini-cli.js.map +1 -1
  18. package/dist-server/server/gemini-response-handler.js +34 -0
  19. package/dist-server/server/gemini-response-handler.js.map +1 -1
  20. package/dist-server/server/index.js +234 -67
  21. package/dist-server/server/index.js.map +1 -1
  22. package/dist-server/server/modules/database/index.js +1 -0
  23. package/dist-server/server/modules/database/index.js.map +1 -1
  24. package/dist-server/server/modules/projects/services/project-management.service.js +1 -0
  25. package/dist-server/server/modules/projects/services/project-management.service.js.map +1 -1
  26. package/dist-server/server/modules/projects/services/projects-with-sessions-fetch.service.js +4 -0
  27. package/dist-server/server/modules/projects/services/projects-with-sessions-fetch.service.js.map +1 -1
  28. package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js +28 -6
  29. package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js.map +1 -1
  30. package/dist-server/server/modules/providers/list/claude/claude-models.provider.js +146 -0
  31. package/dist-server/server/modules/providers/list/claude/claude-models.provider.js.map +1 -0
  32. package/dist-server/server/modules/providers/list/claude/claude.provider.js +2 -0
  33. package/dist-server/server/modules/providers/list/claude/claude.provider.js.map +1 -1
  34. package/dist-server/server/modules/providers/list/codex/codex-models.provider.js +84 -0
  35. package/dist-server/server/modules/providers/list/codex/codex-models.provider.js.map +1 -0
  36. package/dist-server/server/modules/providers/list/codex/codex-skills.provider.js +7 -39
  37. package/dist-server/server/modules/providers/list/codex/codex-skills.provider.js.map +1 -1
  38. package/dist-server/server/modules/providers/list/codex/codex.provider.js +2 -0
  39. package/dist-server/server/modules/providers/list/codex/codex.provider.js.map +1 -1
  40. package/dist-server/server/modules/providers/list/cursor/cursor-models.provider.js +754 -0
  41. package/dist-server/server/modules/providers/list/cursor/cursor-models.provider.js.map +1 -0
  42. package/dist-server/server/modules/providers/list/cursor/cursor-sessions.provider.js +2 -15
  43. package/dist-server/server/modules/providers/list/cursor/cursor-sessions.provider.js.map +1 -1
  44. package/dist-server/server/modules/providers/list/cursor/cursor.provider.js +2 -0
  45. package/dist-server/server/modules/providers/list/cursor/cursor.provider.js.map +1 -1
  46. package/dist-server/server/modules/providers/list/gemini/gemini-models.provider.js +27 -0
  47. package/dist-server/server/modules/providers/list/gemini/gemini-models.provider.js.map +1 -0
  48. package/dist-server/server/modules/providers/list/gemini/gemini-sessions.provider.js +3 -9
  49. package/dist-server/server/modules/providers/list/gemini/gemini-sessions.provider.js.map +1 -1
  50. package/dist-server/server/modules/providers/list/gemini/gemini.provider.js +2 -0
  51. package/dist-server/server/modules/providers/list/gemini/gemini.provider.js.map +1 -1
  52. package/dist-server/server/modules/providers/list/opencode/opencode-auth.provider.js +92 -0
  53. package/dist-server/server/modules/providers/list/opencode/opencode-auth.provider.js.map +1 -0
  54. package/dist-server/server/modules/providers/list/opencode/opencode-mcp.provider.js +181 -0
  55. package/dist-server/server/modules/providers/list/opencode/opencode-mcp.provider.js.map +1 -0
  56. package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js +267 -0
  57. package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js.map +1 -0
  58. package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js +115 -0
  59. package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js.map +1 -0
  60. package/dist-server/server/modules/providers/list/opencode/opencode-sessions.provider.js +410 -0
  61. package/dist-server/server/modules/providers/list/opencode/opencode-sessions.provider.js.map +1 -0
  62. package/dist-server/server/modules/providers/list/opencode/opencode-skills.provider.js +62 -0
  63. package/dist-server/server/modules/providers/list/opencode/opencode-skills.provider.js.map +1 -0
  64. package/dist-server/server/modules/providers/list/opencode/opencode.provider.js +19 -0
  65. package/dist-server/server/modules/providers/list/opencode/opencode.provider.js.map +1 -0
  66. package/dist-server/server/modules/providers/provider.registry.js +2 -0
  67. package/dist-server/server/modules/providers/provider.registry.js.map +1 -1
  68. package/dist-server/server/modules/providers/provider.routes.js +42 -1
  69. package/dist-server/server/modules/providers/provider.routes.js.map +1 -1
  70. package/dist-server/server/modules/providers/services/mcp.service.js +1 -9
  71. package/dist-server/server/modules/providers/services/mcp.service.js.map +1 -1
  72. package/dist-server/server/modules/providers/services/provider-models.service.js +226 -0
  73. package/dist-server/server/modules/providers/services/provider-models.service.js.map +1 -0
  74. package/dist-server/server/modules/providers/services/session-synchronizer.service.js +1 -0
  75. package/dist-server/server/modules/providers/services/session-synchronizer.service.js.map +1 -1
  76. package/dist-server/server/modules/providers/services/sessions-watcher.service.js +7 -0
  77. package/dist-server/server/modules/providers/services/sessions-watcher.service.js.map +1 -1
  78. package/dist-server/server/modules/providers/shared/base/abstract.provider.js.map +1 -1
  79. package/dist-server/server/modules/providers/tests/mcp.test.js +73 -6
  80. package/dist-server/server/modules/providers/tests/mcp.test.js.map +1 -1
  81. package/dist-server/server/modules/providers/tests/opencode-models.test.js +66 -0
  82. package/dist-server/server/modules/providers/tests/opencode-models.test.js.map +1 -0
  83. package/dist-server/server/modules/providers/tests/opencode-sessions.test.js +262 -0
  84. package/dist-server/server/modules/providers/tests/opencode-sessions.test.js.map +1 -0
  85. package/dist-server/server/modules/providers/tests/provider-models.service.test.js +298 -0
  86. package/dist-server/server/modules/providers/tests/provider-models.service.test.js.map +1 -0
  87. package/dist-server/server/modules/providers/tests/skills.test.js +33 -0
  88. package/dist-server/server/modules/providers/tests/skills.test.js.map +1 -1
  89. package/dist-server/server/modules/websocket/services/chat-websocket.service.js +12 -1
  90. package/dist-server/server/modules/websocket/services/chat-websocket.service.js.map +1 -1
  91. package/dist-server/server/modules/websocket/services/plugin-websocket-proxy.service.js +4 -4
  92. package/dist-server/server/modules/websocket/services/plugin-websocket-proxy.service.js.map +1 -1
  93. package/dist-server/server/modules/websocket/services/shell-websocket.service.js +18 -3
  94. package/dist-server/server/modules/websocket/services/shell-websocket.service.js.map +1 -1
  95. package/dist-server/server/modules/websocket/services/websocket-auth.service.js +6 -2
  96. package/dist-server/server/modules/websocket/services/websocket-auth.service.js.map +1 -1
  97. package/dist-server/server/modules/websocket/services/websocket-server.service.js +18 -0
  98. package/dist-server/server/modules/websocket/services/websocket-server.service.js.map +1 -1
  99. package/dist-server/server/openai-codex.js +32 -4
  100. package/dist-server/server/openai-codex.js.map +1 -1
  101. package/dist-server/server/opencode-cli.js +287 -0
  102. package/dist-server/server/opencode-cli.js.map +1 -0
  103. package/dist-server/server/opencode-cli.test.js +84 -0
  104. package/dist-server/server/opencode-cli.test.js.map +1 -0
  105. package/dist-server/server/routes/agent.js +21 -8
  106. package/dist-server/server/routes/agent.js.map +1 -1
  107. package/dist-server/server/routes/commands.js +202 -209
  108. package/dist-server/server/routes/commands.js.map +1 -1
  109. package/dist-server/server/routes/cursor.js +2 -2
  110. package/dist-server/server/routes/cursor.js.map +1 -1
  111. package/dist-server/server/routes/settings.js +0 -10
  112. package/dist-server/server/routes/settings.js.map +1 -1
  113. package/dist-server/server/routes/tests/commands.test.js +76 -0
  114. package/dist-server/server/routes/tests/commands.test.js.map +1 -0
  115. package/dist-server/server/shared/utils.js +286 -0
  116. package/dist-server/server/shared/utils.js.map +1 -1
  117. package/package.json +5 -2
  118. package/public/api-docs.html +878 -0
  119. package/public/modelConstants.js +848 -0
  120. package/server/claude-sdk.js +64 -35
  121. package/server/cursor-cli.js +6 -3
  122. package/server/gemini-cli.js +7 -1
  123. package/server/gemini-response-handler.js +38 -0
  124. package/server/index.js +259 -75
  125. package/server/modules/database/index.ts +1 -0
  126. package/server/modules/projects/services/project-management.service.ts +2 -0
  127. package/server/modules/projects/services/projects-with-sessions-fetch.service.ts +7 -1
  128. package/server/modules/providers/README.md +11 -3
  129. package/server/modules/providers/list/claude/claude-auth.provider.ts +34 -6
  130. package/server/modules/providers/list/claude/claude-models.provider.ts +196 -0
  131. package/server/modules/providers/list/claude/claude.provider.ts +3 -0
  132. package/server/modules/providers/list/codex/codex-models.provider.ts +125 -0
  133. package/server/modules/providers/list/codex/codex-skills.provider.ts +10 -50
  134. package/server/modules/providers/list/codex/codex.provider.ts +3 -0
  135. package/server/modules/providers/list/cursor/cursor-models.provider.ts +820 -0
  136. package/server/modules/providers/list/cursor/cursor-sessions.provider.ts +7 -20
  137. package/server/modules/providers/list/cursor/cursor.provider.ts +3 -0
  138. package/server/modules/providers/list/gemini/gemini-models.provider.ts +42 -0
  139. package/server/modules/providers/list/gemini/gemini-sessions.provider.ts +3 -10
  140. package/server/modules/providers/list/gemini/gemini.provider.ts +3 -0
  141. package/server/modules/providers/list/opencode/opencode-auth.provider.ts +111 -0
  142. package/server/modules/providers/list/opencode/opencode-mcp.provider.ts +228 -0
  143. package/server/modules/providers/list/opencode/opencode-models.provider.ts +339 -0
  144. package/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.ts +157 -0
  145. package/server/modules/providers/list/opencode/opencode-sessions.provider.ts +506 -0
  146. package/server/modules/providers/list/opencode/opencode-skills.provider.ts +78 -0
  147. package/server/modules/providers/list/opencode/opencode.provider.ts +27 -0
  148. package/server/modules/providers/provider.registry.ts +2 -0
  149. package/server/modules/providers/provider.routes.ts +62 -2
  150. package/server/modules/providers/services/mcp.service.ts +1 -12
  151. package/server/modules/providers/services/provider-models.service.ts +358 -0
  152. package/server/modules/providers/services/session-synchronizer.service.ts +1 -0
  153. package/server/modules/providers/services/sessions-watcher.service.ts +8 -0
  154. package/server/modules/providers/shared/base/abstract.provider.ts +2 -0
  155. package/server/modules/providers/tests/mcp.test.ts +93 -6
  156. package/server/modules/providers/tests/opencode-models.test.ts +73 -0
  157. package/server/modules/providers/tests/opencode-sessions.test.ts +334 -0
  158. package/server/modules/providers/tests/provider-models.service.test.ts +349 -0
  159. package/server/modules/providers/tests/skills.test.ts +66 -0
  160. package/server/modules/websocket/services/chat-websocket.service.ts +15 -1
  161. package/server/modules/websocket/services/plugin-websocket-proxy.service.ts +4 -4
  162. package/server/modules/websocket/services/shell-websocket.service.ts +22 -2
  163. package/server/modules/websocket/services/websocket-auth.service.ts +7 -2
  164. package/server/modules/websocket/services/websocket-server.service.ts +18 -0
  165. package/server/openai-codex.js +41 -4
  166. package/server/opencode-cli.js +336 -0
  167. package/server/opencode-cli.test.js +95 -0
  168. package/server/routes/agent.js +22 -8
  169. package/server/routes/commands.js +254 -233
  170. package/server/routes/cursor.js +2 -2
  171. package/server/routes/settings.js +1 -10
  172. package/server/routes/tests/commands.test.js +82 -0
  173. package/server/shared/interfaces.ts +45 -0
  174. package/server/shared/types.ts +88 -1
  175. package/server/shared/utils.ts +384 -0
  176. package/dist/assets/index-AaSE7-3t.js +0 -1336
  177. package/dist/assets/index-CKaku72N.css +0 -32
  178. package/dist-server/shared/modelConstants.js +0 -99
  179. package/dist-server/shared/modelConstants.js.map +0 -1
  180. package/shared/modelConstants.js +0 -107
@@ -16,6 +16,10 @@ type ClaudeCredentialsStatus = {
16
16
  error?: string;
17
17
  };
18
18
 
19
+ const hasErrorCode = (error: unknown, code: string): boolean => (
20
+ error instanceof Error && 'code' in error && error.code === code
21
+ );
22
+
19
23
  export class ClaudeProviderAuth implements IProviderAuth {
20
24
  /**
21
25
  * Checks whether the Claude Code CLI is available on this host.
@@ -77,6 +81,12 @@ export class ClaudeProviderAuth implements IProviderAuth {
77
81
  * Checks Claude credentials in the same priority order used by Claude Code.
78
82
  */
79
83
  private async checkCredentials(): Promise<ClaudeCredentialsStatus> {
84
+ const missingCredentialsError = 'Claude CLI is not authenticated. Run claude /login or configure ANTHROPIC_API_KEY.';
85
+
86
+ if (process.env.ANTHROPIC_AUTH_TOKEN?.trim()) {
87
+ return { authenticated: true, email: 'Auth Token', method: 'api_key' };
88
+ }
89
+
80
90
  if (process.env.ANTHROPIC_API_KEY?.trim()) {
81
91
  return { authenticated: true, email: 'API Key Auth', method: 'api_key' };
82
92
  }
@@ -110,15 +120,33 @@ export class ClaudeProviderAuth implements IProviderAuth {
110
120
 
111
121
  return {
112
122
  authenticated: false,
113
- email,
114
- method: 'credentials_file',
115
- error: 'OAuth token has expired. Please re-authenticate with claude login',
123
+ email: null,
124
+ method: null,
125
+ error: 'Claude login has expired. Run claude /login again.',
116
126
  };
117
127
  }
118
128
 
119
- return { authenticated: false, email: null, method: null };
120
- } catch {
121
- return { authenticated: false, email: null, method: null };
129
+ return {
130
+ authenticated: false,
131
+ email: null,
132
+ method: null,
133
+ error: missingCredentialsError,
134
+ };
135
+ } catch (error) {
136
+ let errorMessage = 'Unable to read Claude credentials. Run claude /login again.';
137
+
138
+ if (hasErrorCode(error, 'ENOENT')) {
139
+ errorMessage = missingCredentialsError;
140
+ } else if (error instanceof SyntaxError) {
141
+ errorMessage = 'Claude credentials are unreadable. Run claude /login again.';
142
+ }
143
+
144
+ return {
145
+ authenticated: false,
146
+ email: null,
147
+ method: null,
148
+ error: errorMessage,
149
+ };
122
150
  }
123
151
  }
124
152
  }
@@ -0,0 +1,196 @@
1
+ import { readFile } from 'node:fs/promises';
2
+
3
+ import { sessionsDb } from '@/modules/database/index.js';
4
+ import type { IProviderModels } from '@/shared/interfaces.js';
5
+ import type {
6
+ ProviderChangeActiveModelInput,
7
+ ProviderCurrentActiveModel,
8
+ ProviderModelsDefinition,
9
+ ProviderSessionActiveModelChange,
10
+ } from '@/shared/types.js';
11
+ import {
12
+ buildDefaultProviderCurrentActiveModel,
13
+ writeProviderSessionActiveModelChange,
14
+ } from '@/shared/utils.js';
15
+
16
+ export const CLAUDE_FALLBACK_MODELS: ProviderModelsDefinition = {
17
+ OPTIONS: [
18
+ {
19
+ value: 'default',
20
+ label: 'Default (recommended)',
21
+ description: 'Use the default model (currently Opus 4.8 (1M context)) · $5/$25 per Mtok',
22
+ },
23
+ {
24
+ value: "sonnet",
25
+ label: "Sonnet",
26
+ description: "Sonnet 4.6 · Best for everyday tasks · $3/$15 per Mtok",
27
+ },
28
+ {
29
+ value: 'sonnet[1m]',
30
+ label: 'Sonnet (1M context)',
31
+ description: 'Sonnet 4.6 for long sessions · $3/$15 per Mtok',
32
+ },
33
+ {
34
+ value: 'opus[1m]',
35
+ label: 'Opus 4.8 (1M context)',
36
+ description: 'Opus 4.8 with 1M context · Most capable for complex work · $5/$25 per Mtok',
37
+ },
38
+ {
39
+ value: 'haiku',
40
+ label: 'Haiku',
41
+ description: 'Haiku 4.5 · Fastest for quick answers · $1/$5 per Mtok',
42
+ },
43
+ ],
44
+ DEFAULT: 'default',
45
+ };
46
+ type ClaudeInitEvent = {
47
+ sessionId?: string;
48
+ session_id?: string;
49
+ type?: string;
50
+ subtype?: string;
51
+ model?: string;
52
+ message?: {
53
+ content?: unknown;
54
+ model?: string;
55
+ };
56
+ };
57
+
58
+ const ANSI_PATTERN = new RegExp(
59
+ '[\\u001B\\u009B][[\\]()#;?]*(?:'
60
+ + '(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]'
61
+ + '|(?:[\\dA-PR-TZcf-ntqry=><~]))',
62
+ 'g',
63
+ );
64
+
65
+ const extractClaudeEventModel = (event: ClaudeInitEvent, sessionId: string): string | null => {
66
+ const eventSessionId = event.sessionId ?? event.session_id;
67
+ if (eventSessionId && eventSessionId !== sessionId) {
68
+ return null;
69
+ }
70
+
71
+ const contentModel = extractClaudeModelFromMessageContent(event.message?.content);
72
+ if (contentModel) {
73
+ return contentModel;
74
+ }
75
+
76
+ const directModel = event.model?.trim();
77
+ if (directModel) {
78
+ return directModel;
79
+ }
80
+
81
+ const messageModel = event.message?.model?.trim();
82
+ return messageModel || null;
83
+ };
84
+
85
+ const stripAnsi = (value: string): string => value.replace(ANSI_PATTERN, '');
86
+
87
+ const extractTaggedContent = (content: string, tagName: string): string | null => {
88
+ const escapedTagName = tagName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
89
+ const match = new RegExp(`<${escapedTagName}>([\\s\\S]*?)<\\/${escapedTagName}>`).exec(content);
90
+ return match ? match[1] : null;
91
+ };
92
+
93
+ const extractClaudeModelFromTextContent = (content: string): string | null => {
94
+ const localCommandStdout = extractTaggedContent(content, 'local-command-stdout');
95
+ if (localCommandStdout !== null) {
96
+ const cleanedStdout = stripAnsi(localCommandStdout).replace(/\s+/g, ' ').trim();
97
+ const changedModel = /(?:set|changed|switched)\s+model\s+to\s+(.+?)\.?$/i.exec(cleanedStdout);
98
+ if (changedModel?.[1]?.trim()) {
99
+ return changedModel[1].trim();
100
+ }
101
+ }
102
+
103
+ const modelTag = extractTaggedContent(content, 'model')?.trim();
104
+ return modelTag || null;
105
+ };
106
+
107
+ const extractClaudeModelFromMessageContent = (content: unknown): string | null => {
108
+ if (typeof content === 'string') {
109
+ return extractClaudeModelFromTextContent(content);
110
+ }
111
+
112
+ if (!Array.isArray(content)) {
113
+ return null;
114
+ }
115
+
116
+ for (const part of content) {
117
+ if (!part || typeof part !== 'object' || !('text' in part) || typeof part.text !== 'string') {
118
+ continue;
119
+ }
120
+
121
+ const model = extractClaudeModelFromTextContent(part.text);
122
+ if (model) {
123
+ return model;
124
+ }
125
+ }
126
+
127
+ return null;
128
+ };
129
+
130
+ const readClaudeSessionModelFromJsonl = async (
131
+ sessionId: string,
132
+ jsonlPath: string,
133
+ ): Promise<ProviderCurrentActiveModel | null> => {
134
+ const content = await readFile(jsonlPath, 'utf8');
135
+ const lines = content
136
+ .split(/\r?\n/)
137
+ .map((line) => line.trim())
138
+ .filter(Boolean);
139
+
140
+ for (let index = lines.length - 1; index >= 0; index -= 1) {
141
+ try {
142
+ const event = JSON.parse(lines[index]) as ClaudeInitEvent;
143
+ const model = extractClaudeEventModel(event, sessionId);
144
+ if (model) {
145
+ return { model };
146
+ }
147
+ } catch {
148
+ // Skip malformed JSONL lines that can happen during concurrent writes.
149
+ }
150
+ }
151
+
152
+ return null;
153
+ };
154
+
155
+ export class ClaudeProviderModels implements IProviderModels {
156
+ async getSupportedModels(): Promise<ProviderModelsDefinition> {
157
+ // claude creates a new jsonl file as a separate session for this request.
158
+ // As a result, it lists the workspace where this is invoked when it shouldn't.
159
+ //
160
+ // Disabled for now:
161
+ // const queryInstance = query({
162
+ // prompt: 'Get supported models',
163
+ // options: buildClaudeQueryOptions(),
164
+ // });
165
+ // const supportedModels = await queryInstance.supportedModels();
166
+ // queryInstance.close();
167
+ // return buildClaudeModelsDefinition(supportedModels);
168
+ return CLAUDE_FALLBACK_MODELS;
169
+ }
170
+
171
+ async getCurrentActiveModel(sessionId?: string): Promise<ProviderCurrentActiveModel> {
172
+ if (!sessionId?.trim()) {
173
+ return buildDefaultProviderCurrentActiveModel(await this.getSupportedModels());
174
+ }
175
+
176
+ try {
177
+ const jsonlPath = sessionsDb.getSessionById(sessionId)?.jsonl_path;
178
+ const activeModel = jsonlPath
179
+ ? await readClaudeSessionModelFromJsonl(sessionId, jsonlPath)
180
+ : null;
181
+ if (activeModel?.model) {
182
+ return activeModel;
183
+ }
184
+ } catch {
185
+ // Fall through to the provider default when the session-backed lookup fails.
186
+ }
187
+
188
+ return buildDefaultProviderCurrentActiveModel(await this.getSupportedModels());
189
+ }
190
+
191
+ async changeActiveModel(
192
+ input: ProviderChangeActiveModelInput,
193
+ ): Promise<ProviderSessionActiveModelChange> {
194
+ return writeProviderSessionActiveModelChange('claude', input);
195
+ }
196
+ }
@@ -1,17 +1,20 @@
1
1
  import { AbstractProvider } from '@/modules/providers/shared/base/abstract.provider.js';
2
2
  import { ClaudeProviderAuth } from '@/modules/providers/list/claude/claude-auth.provider.js';
3
+ import { ClaudeProviderModels } from '@/modules/providers/list/claude/claude-models.provider.js';
3
4
  import { ClaudeMcpProvider } from '@/modules/providers/list/claude/claude-mcp.provider.js';
4
5
  import { ClaudeSessionSynchronizer } from '@/modules/providers/list/claude/claude-session-synchronizer.provider.js';
5
6
  import { ClaudeSessionsProvider } from '@/modules/providers/list/claude/claude-sessions.provider.js';
6
7
  import { ClaudeSkillsProvider } from '@/modules/providers/list/claude/claude-skills.provider.js';
7
8
  import type {
8
9
  IProviderAuth,
10
+ IProviderModels,
9
11
  IProviderSessionSynchronizer,
10
12
  IProviderSkills,
11
13
  IProviderSessions,
12
14
  } from '@/shared/interfaces.js';
13
15
 
14
16
  export class ClaudeProvider extends AbstractProvider {
17
+ readonly models: IProviderModels = new ClaudeProviderModels();
15
18
  readonly mcp = new ClaudeMcpProvider();
16
19
  readonly auth: IProviderAuth = new ClaudeProviderAuth();
17
20
  readonly skills: IProviderSkills = new ClaudeSkillsProvider();
@@ -0,0 +1,125 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+
5
+ import TOML from '@iarna/toml';
6
+
7
+ import type { IProviderModels } from '@/shared/interfaces.js';
8
+ import type {
9
+ ProviderChangeActiveModelInput,
10
+ ProviderCurrentActiveModel,
11
+ ProviderModelOption,
12
+ ProviderModelsDefinition,
13
+ ProviderSessionActiveModelChange,
14
+ } from '@/shared/types.js';
15
+ import {
16
+ buildDefaultProviderCurrentActiveModel,
17
+ readObjectRecord,
18
+ readOptionalString,
19
+ writeProviderSessionActiveModelChange,
20
+ } from '@/shared/utils.js';
21
+
22
+ export const CODEX_FALLBACK_MODELS: ProviderModelsDefinition = {
23
+ OPTIONS: [
24
+ { value: 'gpt-5.5', label: 'gpt-5.5' },
25
+ { value: 'gpt-5.4', label: 'gpt-5.4' },
26
+ { value: 'gpt-5.4-mini', label: 'gpt-5.4-mini' },
27
+ { value: 'gpt-5.3-codex', label: 'gpt-5.3-codex' },
28
+ { value: 'gpt-5.2', label: 'gpt-5.2' },
29
+ ],
30
+ DEFAULT: 'gpt-5.4',
31
+ };
32
+
33
+ type CodexCachedModel = {
34
+ slug?: string;
35
+ display_name?: string;
36
+ description?: string;
37
+ priority?: number;
38
+ visibility?: string;
39
+ supported_in_api?: boolean;
40
+ };
41
+
42
+ const CODEX_MODELS_CACHE_PATH = path.join(os.homedir(), '.codex', 'models_cache.json');
43
+ const CODEX_CONFIG_PATH = path.join(os.homedir(), '.codex', 'config.toml');
44
+
45
+ const isCodexCachedModel = (value: unknown): value is CodexCachedModel => {
46
+ const record = readObjectRecord(value);
47
+ return Boolean(record && readOptionalString(record.slug));
48
+ };
49
+
50
+ const readCodexPriority = (value: unknown): number => (
51
+ typeof value === 'number' && Number.isFinite(value) ? value : Number.MAX_SAFE_INTEGER
52
+ );
53
+
54
+ const mapCodexModel = (model: CodexCachedModel): ProviderModelOption => ({
55
+ value: model.slug as string,
56
+ label: readOptionalString(model.display_name) ?? (model.slug as string),
57
+ description: readOptionalString(model.description),
58
+ });
59
+
60
+ const buildCodexModelsDefinition = (models: CodexCachedModel[]): ProviderModelsDefinition => {
61
+ const sortedModels = [...models]
62
+ .filter((model) => model.visibility !== 'hidden' && model.supported_in_api !== false)
63
+ .sort((left, right) => readCodexPriority(left.priority) - readCodexPriority(right.priority));
64
+
65
+ const options: ProviderModelOption[] = [];
66
+ const seenValues = new Set<string>();
67
+
68
+ for (const model of sortedModels) {
69
+ const mappedModel = mapCodexModel(model);
70
+ if (seenValues.has(mappedModel.value)) {
71
+ continue;
72
+ }
73
+
74
+ seenValues.add(mappedModel.value);
75
+ options.push(mappedModel);
76
+ }
77
+
78
+ if (options.length === 0) {
79
+ return CODEX_FALLBACK_MODELS;
80
+ }
81
+
82
+ return {
83
+ OPTIONS: options,
84
+ DEFAULT: options[0]?.value ?? CODEX_FALLBACK_MODELS.DEFAULT,
85
+ };
86
+ };
87
+
88
+ export class CodexProviderModels implements IProviderModels {
89
+ async getSupportedModels(): Promise<ProviderModelsDefinition> {
90
+ try {
91
+ const raw = await readFile(CODEX_MODELS_CACHE_PATH, 'utf8');
92
+ const parsed = readObjectRecord(JSON.parse(raw));
93
+ const models = Array.isArray(parsed?.models)
94
+ ? parsed.models.filter(isCodexCachedModel)
95
+ : [];
96
+
97
+ return buildCodexModelsDefinition(models);
98
+ } catch {
99
+ return CODEX_FALLBACK_MODELS;
100
+ }
101
+ }
102
+
103
+ async getCurrentActiveModel(): Promise<ProviderCurrentActiveModel> {
104
+ try {
105
+ const raw = await readFile(CODEX_CONFIG_PATH, 'utf8');
106
+ const parsed = readObjectRecord(TOML.parse(raw));
107
+ const model = readOptionalString(parsed?.model);
108
+ if (!model) {
109
+ return buildDefaultProviderCurrentActiveModel(await this.getSupportedModels());
110
+ }
111
+
112
+ return {
113
+ model,
114
+ };
115
+ } catch {
116
+ return buildDefaultProviderCurrentActiveModel(await this.getSupportedModels());
117
+ }
118
+ }
119
+
120
+ async changeActiveModel(
121
+ input: ProviderChangeActiveModelInput,
122
+ ): Promise<ProviderSessionActiveModelChange> {
123
+ return writeProviderSessionActiveModelChange('codex', input);
124
+ }
125
+ }
@@ -1,52 +1,12 @@
1
- import fs from 'node:fs/promises';
2
1
  import os from 'node:os';
3
2
  import path from 'node:path';
4
3
 
5
4
  import { SkillsProvider } from '@/modules/providers/shared/skills/skills.provider.js';
6
5
  import type { ProviderSkillSource } from '@/shared/types.js';
7
-
8
- const hasGitMarker = async (dirPath: string): Promise<boolean> => {
9
- try {
10
- const gitMarkerStats = await fs.stat(path.join(dirPath, '.git'));
11
- return gitMarkerStats.isDirectory() || gitMarkerStats.isFile();
12
- } catch {
13
- return false;
14
- }
15
- };
16
-
17
- const findTopmostGitRoot = async (startPath: string): Promise<string | null> => {
18
- let currentPath = path.resolve(startPath);
19
- let topmostGitRoot: string | null = null;
20
-
21
- while (true) {
22
- if (await hasGitMarker(currentPath)) {
23
- topmostGitRoot = currentPath;
24
- }
25
-
26
- const parentPath = path.dirname(currentPath);
27
- if (parentPath === currentPath) {
28
- break;
29
- }
30
-
31
- currentPath = parentPath;
32
- }
33
-
34
- return topmostGitRoot;
35
- };
36
-
37
- const addUniqueSource = (
38
- sources: ProviderSkillSource[],
39
- seenRootDirs: Set<string>,
40
- source: ProviderSkillSource,
41
- ): void => {
42
- const normalizedRootDir = path.resolve(source.rootDir);
43
- if (seenRootDirs.has(normalizedRootDir)) {
44
- return;
45
- }
46
-
47
- seenRootDirs.add(normalizedRootDir);
48
- sources.push({ ...source, rootDir: normalizedRootDir });
49
- };
6
+ import {
7
+ addUniqueProviderSkillSource,
8
+ findTopmostGitRoot,
9
+ } from '@/shared/utils.js';
50
10
 
51
11
  export class CodexSkillsProvider extends SkillsProvider {
52
12
  constructor() {
@@ -58,7 +18,7 @@ export class CodexSkillsProvider extends SkillsProvider {
58
18
  const seenRootDirs = new Set<string>();
59
19
  const repoRoot = await findTopmostGitRoot(workspacePath);
60
20
 
61
- addUniqueSource(sources, seenRootDirs, {
21
+ addUniqueProviderSkillSource(sources, seenRootDirs, {
62
22
  scope: 'repo',
63
23
  rootDir: path.join(workspacePath, '.agents', 'skills'),
64
24
  commandPrefix: '$',
@@ -67,29 +27,29 @@ export class CodexSkillsProvider extends SkillsProvider {
67
27
  if (repoRoot) {
68
28
  // Codex checks repository skills at the launch folder, one folder above it,
69
29
  // and the topmost git root; these can collapse to the same directory.
70
- addUniqueSource(sources, seenRootDirs, {
30
+ addUniqueProviderSkillSource(sources, seenRootDirs, {
71
31
  scope: 'repo',
72
32
  rootDir: path.join(path.dirname(workspacePath), '.agents', 'skills'),
73
33
  commandPrefix: '$',
74
34
  });
75
- addUniqueSource(sources, seenRootDirs, {
35
+ addUniqueProviderSkillSource(sources, seenRootDirs, {
76
36
  scope: 'repo',
77
37
  rootDir: path.join(repoRoot, '.agents', 'skills'),
78
38
  commandPrefix: '$',
79
39
  });
80
40
  }
81
41
 
82
- addUniqueSource(sources, seenRootDirs, {
42
+ addUniqueProviderSkillSource(sources, seenRootDirs, {
83
43
  scope: 'user',
84
44
  rootDir: path.join(os.homedir(), '.agents', 'skills'),
85
45
  commandPrefix: '$',
86
46
  });
87
- addUniqueSource(sources, seenRootDirs, {
47
+ addUniqueProviderSkillSource(sources, seenRootDirs, {
88
48
  scope: 'admin',
89
49
  rootDir: path.join('/etc', 'codex', 'skills'),
90
50
  commandPrefix: '$',
91
51
  });
92
- addUniqueSource(sources, seenRootDirs, {
52
+ addUniqueProviderSkillSource(sources, seenRootDirs, {
93
53
  scope: 'system',
94
54
  rootDir: path.join(os.homedir(), '.codex', 'skills', '.system'),
95
55
  commandPrefix: '$',
@@ -1,17 +1,20 @@
1
1
  import { AbstractProvider } from '@/modules/providers/shared/base/abstract.provider.js';
2
2
  import { CodexProviderAuth } from '@/modules/providers/list/codex/codex-auth.provider.js';
3
+ import { CodexProviderModels } from '@/modules/providers/list/codex/codex-models.provider.js';
3
4
  import { CodexMcpProvider } from '@/modules/providers/list/codex/codex-mcp.provider.js';
4
5
  import { CodexSessionSynchronizer } from '@/modules/providers/list/codex/codex-session-synchronizer.provider.js';
5
6
  import { CodexSessionsProvider } from '@/modules/providers/list/codex/codex-sessions.provider.js';
6
7
  import { CodexSkillsProvider } from '@/modules/providers/list/codex/codex-skills.provider.js';
7
8
  import type {
8
9
  IProviderAuth,
10
+ IProviderModels,
9
11
  IProviderSessionSynchronizer,
10
12
  IProviderSkills,
11
13
  IProviderSessions,
12
14
  } from '@/shared/interfaces.js';
13
15
 
14
16
  export class CodexProvider extends AbstractProvider {
17
+ readonly models: IProviderModels = new CodexProviderModels();
15
18
  readonly mcp = new CodexMcpProvider();
16
19
  readonly auth: IProviderAuth = new CodexProviderAuth();
17
20
  readonly skills: IProviderSkills = new CodexSkillsProvider();