@glwhappen/web-code 1.32.7 → 1.32.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.de.md +1 -1
- package/README.ko.md +1 -1
- package/README.md +1 -1
- package/README.ru.md +1 -1
- package/README.tr.md +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/api-docs.html +6 -7
- package/dist/assets/{index-CCGk0QgG.js → index-BLLsK3sG.js} +277 -262
- package/dist/assets/index-Dl5QP21C.css +32 -0
- package/dist/index.html +2 -2
- package/dist/modelConstants.js +841 -0
- package/dist-server/server/claude-sdk.js +57 -34
- package/dist-server/server/claude-sdk.js.map +1 -1
- package/dist-server/server/cursor-cli.js +6 -3
- package/dist-server/server/cursor-cli.js.map +1 -1
- package/dist-server/server/gemini-cli.js +3 -1
- package/dist-server/server/gemini-cli.js.map +1 -1
- package/dist-server/server/gemini-response-handler.js +34 -0
- package/dist-server/server/gemini-response-handler.js.map +1 -1
- package/dist-server/server/index.js +131 -19
- package/dist-server/server/index.js.map +1 -1
- package/dist-server/server/modules/database/index.js +1 -0
- package/dist-server/server/modules/database/index.js.map +1 -1
- package/dist-server/server/modules/projects/services/project-management.service.js +1 -0
- package/dist-server/server/modules/projects/services/project-management.service.js.map +1 -1
- package/dist-server/server/modules/projects/services/projects-with-sessions-fetch.service.js +4 -0
- package/dist-server/server/modules/projects/services/projects-with-sessions-fetch.service.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-models.provider.js +143 -0
- package/dist-server/server/modules/providers/list/claude/claude-models.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/claude/claude.provider.js +2 -0
- package/dist-server/server/modules/providers/list/claude/claude.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-models.provider.js +84 -0
- package/dist-server/server/modules/providers/list/codex/codex-models.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/codex/codex-skills.provider.js +7 -39
- package/dist-server/server/modules/providers/list/codex/codex-skills.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex.provider.js +2 -0
- package/dist-server/server/modules/providers/list/codex/codex.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/cursor/cursor-models.provider.js +754 -0
- package/dist-server/server/modules/providers/list/cursor/cursor-models.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/cursor/cursor-sessions.provider.js +2 -15
- package/dist-server/server/modules/providers/list/cursor/cursor-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/cursor/cursor.provider.js +2 -0
- package/dist-server/server/modules/providers/list/cursor/cursor.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/gemini/gemini-models.provider.js +27 -0
- package/dist-server/server/modules/providers/list/gemini/gemini-models.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/gemini/gemini-sessions.provider.js +3 -9
- package/dist-server/server/modules/providers/list/gemini/gemini-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/gemini/gemini.provider.js +2 -0
- package/dist-server/server/modules/providers/list/gemini/gemini.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/opencode/opencode-auth.provider.js +92 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-auth.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-mcp.provider.js +181 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-mcp.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js +267 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js +115 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-sessions.provider.js +410 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-sessions.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-skills.provider.js +62 -0
- package/dist-server/server/modules/providers/list/opencode/opencode-skills.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/opencode/opencode.provider.js +19 -0
- package/dist-server/server/modules/providers/list/opencode/opencode.provider.js.map +1 -0
- package/dist-server/server/modules/providers/provider.registry.js +2 -0
- package/dist-server/server/modules/providers/provider.registry.js.map +1 -1
- package/dist-server/server/modules/providers/provider.routes.js +42 -1
- package/dist-server/server/modules/providers/provider.routes.js.map +1 -1
- package/dist-server/server/modules/providers/services/mcp.service.js +1 -9
- package/dist-server/server/modules/providers/services/mcp.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/provider-models.service.js +199 -0
- package/dist-server/server/modules/providers/services/provider-models.service.js.map +1 -0
- package/dist-server/server/modules/providers/services/session-synchronizer.service.js +1 -0
- package/dist-server/server/modules/providers/services/session-synchronizer.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/sessions-watcher.service.js +7 -0
- package/dist-server/server/modules/providers/services/sessions-watcher.service.js.map +1 -1
- package/dist-server/server/modules/providers/shared/base/abstract.provider.js.map +1 -1
- package/dist-server/server/modules/providers/tests/mcp.test.js +73 -6
- package/dist-server/server/modules/providers/tests/mcp.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/opencode-models.test.js +66 -0
- package/dist-server/server/modules/providers/tests/opencode-models.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/opencode-sessions.test.js +264 -0
- package/dist-server/server/modules/providers/tests/opencode-sessions.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/provider-models.service.test.js +270 -0
- package/dist-server/server/modules/providers/tests/provider-models.service.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/skills.test.js +33 -0
- package/dist-server/server/modules/providers/tests/skills.test.js.map +1 -1
- package/dist-server/server/modules/websocket/services/chat-websocket.service.js +18 -1
- package/dist-server/server/modules/websocket/services/chat-websocket.service.js.map +1 -1
- package/dist-server/server/modules/websocket/services/shell-websocket.service.js +9 -1
- package/dist-server/server/modules/websocket/services/shell-websocket.service.js.map +1 -1
- package/dist-server/server/modules/websocket/services/websocket-writer.service.js +6 -0
- package/dist-server/server/modules/websocket/services/websocket-writer.service.js.map +1 -1
- package/dist-server/server/openai-codex.js +32 -4
- package/dist-server/server/openai-codex.js.map +1 -1
- package/dist-server/server/opencode-cli.js +287 -0
- package/dist-server/server/opencode-cli.js.map +1 -0
- package/dist-server/server/opencode-cli.test.js +84 -0
- package/dist-server/server/opencode-cli.test.js.map +1 -0
- package/dist-server/server/routes/agent.js +21 -8
- package/dist-server/server/routes/agent.js.map +1 -1
- package/dist-server/server/routes/commands.js +202 -209
- package/dist-server/server/routes/commands.js.map +1 -1
- package/dist-server/server/routes/cursor.js +2 -2
- package/dist-server/server/routes/cursor.js.map +1 -1
- package/dist-server/server/routes/settings.js +0 -10
- package/dist-server/server/routes/settings.js.map +1 -1
- package/dist-server/server/routes/tests/commands.test.js +76 -0
- package/dist-server/server/routes/tests/commands.test.js.map +1 -0
- package/dist-server/server/shared/utils.js +286 -0
- package/dist-server/server/shared/utils.js.map +1 -1
- package/package.json +3 -1
- package/public/api-docs.html +878 -0
- package/public/modelConstants.js +841 -0
- package/server/claude-sdk.js +64 -35
- package/server/cursor-cli.js +6 -3
- package/server/gemini-cli.js +7 -1
- package/server/gemini-response-handler.js +38 -0
- package/server/index.js +150 -19
- package/server/modules/database/index.ts +1 -0
- package/server/modules/projects/services/project-management.service.ts +2 -0
- package/server/modules/projects/services/projects-with-sessions-fetch.service.ts +7 -1
- package/server/modules/providers/README.md +11 -3
- package/server/modules/providers/list/claude/claude-models.provider.ts +193 -0
- package/server/modules/providers/list/claude/claude.provider.ts +3 -0
- package/server/modules/providers/list/codex/codex-models.provider.ts +125 -0
- package/server/modules/providers/list/codex/codex-skills.provider.ts +10 -50
- package/server/modules/providers/list/codex/codex.provider.ts +3 -0
- package/server/modules/providers/list/cursor/cursor-models.provider.ts +820 -0
- package/server/modules/providers/list/cursor/cursor-sessions.provider.ts +7 -20
- package/server/modules/providers/list/cursor/cursor.provider.ts +3 -0
- package/server/modules/providers/list/gemini/gemini-models.provider.ts +42 -0
- package/server/modules/providers/list/gemini/gemini-sessions.provider.ts +3 -10
- package/server/modules/providers/list/gemini/gemini.provider.ts +3 -0
- package/server/modules/providers/list/opencode/opencode-auth.provider.ts +111 -0
- package/server/modules/providers/list/opencode/opencode-mcp.provider.ts +228 -0
- package/server/modules/providers/list/opencode/opencode-models.provider.ts +339 -0
- package/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.ts +158 -0
- package/server/modules/providers/list/opencode/opencode-sessions.provider.ts +506 -0
- package/server/modules/providers/list/opencode/opencode-skills.provider.ts +78 -0
- package/server/modules/providers/list/opencode/opencode.provider.ts +27 -0
- package/server/modules/providers/provider.registry.ts +2 -0
- package/server/modules/providers/provider.routes.ts +62 -2
- package/server/modules/providers/services/mcp.service.ts +1 -12
- package/server/modules/providers/services/provider-models.service.ts +325 -0
- package/server/modules/providers/services/session-synchronizer.service.ts +1 -0
- package/server/modules/providers/services/sessions-watcher.service.ts +8 -0
- package/server/modules/providers/shared/base/abstract.provider.ts +2 -0
- package/server/modules/providers/tests/mcp.test.ts +93 -6
- package/server/modules/providers/tests/opencode-models.test.ts +73 -0
- package/server/modules/providers/tests/opencode-sessions.test.ts +336 -0
- package/server/modules/providers/tests/provider-models.service.test.ts +318 -0
- package/server/modules/providers/tests/skills.test.ts +66 -0
- package/server/modules/websocket/services/chat-websocket.service.ts +21 -1
- package/server/modules/websocket/services/shell-websocket.service.ts +9 -0
- package/server/modules/websocket/services/websocket-writer.service.ts +7 -0
- package/server/openai-codex.js +40 -4
- package/server/opencode-cli.js +336 -0
- package/server/opencode-cli.test.js +95 -0
- package/server/routes/agent.js +22 -8
- package/server/routes/commands.js +254 -233
- package/server/routes/cursor.js +2 -2
- package/server/routes/settings.js +1 -10
- package/server/routes/tests/commands.test.js +82 -0
- package/server/shared/interfaces.ts +45 -0
- package/server/shared/types.ts +88 -1
- package/server/shared/utils.ts +384 -0
- package/dist/assets/index-DdxLnCfK.css +0 -32
- package/dist-server/shared/modelConstants.js +0 -99
- package/dist-server/shared/modelConstants.js.map +0 -1
- package/shared/modelConstants.js +0 -107
|
@@ -37,6 +37,7 @@ Current provider ids in this repo are:
|
|
|
37
37
|
- `codex`
|
|
38
38
|
- `cursor`
|
|
39
39
|
- `gemini`
|
|
40
|
+
- `opencode`
|
|
40
41
|
|
|
41
42
|
Those ids are mirrored in backend unions and frontend provider constants. If
|
|
42
43
|
adding a new provider, update every place that hardcodes this list.
|
|
@@ -55,7 +56,8 @@ server/modules/providers/list/<provider>/
|
|
|
55
56
|
<provider>-session-synchronizer.provider.ts
|
|
56
57
|
```
|
|
57
58
|
|
|
58
|
-
The existing provider folders are `claude`, `codex`, `cursor`,
|
|
59
|
+
The existing provider folders are `claude`, `codex`, `cursor`, `gemini`, and
|
|
60
|
+
`opencode`.
|
|
59
61
|
|
|
60
62
|
## What Each Facet Does
|
|
61
63
|
|
|
@@ -81,7 +83,7 @@ The existing provider folders are `claude`, `codex`, `cursor`, and `gemini`.
|
|
|
81
83
|
- Update `server/modules/providers/provider.routes.ts`.
|
|
82
84
|
- Update `server/routes/agent.js` if the provider is launchable from the agent runtime.
|
|
83
85
|
- Update `server/index.js` if the provider needs runtime boot or shutdown wiring.
|
|
84
|
-
- Update `
|
|
86
|
+
- Update `public/modelConstants.js` if the provider appears in README or public API docs.
|
|
85
87
|
- Update `src/components/chat/hooks/useChatProviderState.ts` and
|
|
86
88
|
`src/components/chat/view/subcomponents/ProviderSelectionEmptyState.tsx` if
|
|
87
89
|
the provider should be selectable in chat.
|
|
@@ -122,6 +124,7 @@ Current MCP formats in this repo are:
|
|
|
122
124
|
| Codex | `.codex/config.toml` | `user`, `project` | `stdio`, `http` |
|
|
123
125
|
| Cursor | `.cursor/mcp.json` | `user`, `project` | `stdio`, `http` |
|
|
124
126
|
| Gemini | `.gemini/settings.json` | `user`, `project` | `stdio`, `http` |
|
|
127
|
+
| OpenCode | `~/.config/opencode/opencode.json` or `<workspace>/opencode.json` (`.jsonc` is read when present) | `user`, `project` | `stdio`, `http` |
|
|
125
128
|
|
|
126
129
|
5. Implement skills.
|
|
127
130
|
|
|
@@ -142,6 +145,7 @@ Current skill discovery roots are:
|
|
|
142
145
|
| Codex | `~/.agents/skills`, `~/.codex/skills/.system`, `/etc/codex/skills` | `<workspace>/.agents/skills`, `path.dirname(workspacePath)/.agents/skills`, topmost git root `.agents/skills` | `$` | Overlapping roots are deduplicated before scanning. |
|
|
143
146
|
| Cursor | `~/.cursor/skills` | `<workspace>/.cursor/skills`, `<workspace>/.agents/skills` | `/` | Uses slash-style commands. |
|
|
144
147
|
| Gemini | `~/.gemini/skills`, `~/.agents/skills` | `<workspace>/.gemini/skills`, `<workspace>/.agents/skills` | `/` | Uses slash-style commands. |
|
|
148
|
+
| OpenCode | `~/.config/opencode/skills`, `~/.claude/skills`, `~/.agents/skills` | Cwd-to-topmost-git-root `.opencode/skills`, `.claude/skills`, and `.agents/skills` | `/` | Reuses OpenCode, Claude, and Agents skill locations. Overlapping roots are deduplicated before scanning. |
|
|
145
149
|
|
|
146
150
|
Command forms currently used by the providers are:
|
|
147
151
|
|
|
@@ -150,6 +154,7 @@ Command forms currently used by the providers are:
|
|
|
150
154
|
- Codex skills: `$skill-name`
|
|
151
155
|
- Cursor skills: `/skill-name`
|
|
152
156
|
- Gemini skills: `/skill-name`
|
|
157
|
+
- OpenCode skills: `/skill-name`
|
|
153
158
|
|
|
154
159
|
6. Implement sessions.
|
|
155
160
|
|
|
@@ -187,6 +192,7 @@ Current session sync roots are:
|
|
|
187
192
|
| Codex | `~/.codex/sessions/**/*.jsonl` | Uses `~/.codex/session_index.jsonl` for title lookup and the last `task_complete` message for a fallback title. |
|
|
188
193
|
| Cursor | `~/.cursor/projects/**/*.jsonl` | Uses sibling `worker.log` to recover `workspacePath`, then derives the session title from the first user prompt. |
|
|
189
194
|
| Gemini | `~/.gemini/tmp/**/*.jsonl` | Current full scans only index temp JSONL chat artifacts. Single-file sync also accepts legacy `.json` files. |
|
|
195
|
+
| OpenCode | `~/.local/share/opencode/opencode.db` | Reads active sessions/messages/parts from OpenCode's shared SQLite database and stores `jsonl_path` as `null` so deleting one app session cannot remove the shared DB. |
|
|
190
196
|
|
|
191
197
|
8. Register the provider.
|
|
192
198
|
|
|
@@ -203,10 +209,11 @@ If the provider can run live chat sessions, update the runtime entrypoints too:
|
|
|
203
209
|
|
|
204
210
|
If the provider is visible in the UI, update:
|
|
205
211
|
|
|
206
|
-
- `
|
|
212
|
+
- provider model fallback files under `server/modules/providers/list/<provider>/`
|
|
207
213
|
- `src/components/chat/hooks/useChatProviderState.ts`
|
|
208
214
|
- `src/components/chat/view/subcomponents/ProviderSelectionEmptyState.tsx`
|
|
209
215
|
- `src/components/provider-auth/view/ProviderLoginModal.tsx`
|
|
216
|
+
- `src/components/mcp/constants.ts`
|
|
210
217
|
|
|
211
218
|
## Minimal Wrapper Template
|
|
212
219
|
|
|
@@ -324,6 +331,7 @@ Useful tests in this repo:
|
|
|
324
331
|
|
|
325
332
|
- `server/modules/providers/tests/mcp.test.ts`
|
|
326
333
|
- `server/modules/providers/tests/skills.test.ts`
|
|
334
|
+
- `server/modules/providers/tests/opencode-sessions.test.ts`
|
|
327
335
|
|
|
328
336
|
If you touch sessions or session synchronization, add or update focused tests
|
|
329
337
|
alongside the implementation.
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
|
|
3
|
+
import { sessionsDb } from '@/modules/database/index.js';
|
|
4
|
+
import { getDefaultOwnerUserId } from '@/shared/default-user.js';
|
|
5
|
+
import type { IProviderModels } from '@/shared/interfaces.js';
|
|
6
|
+
import type {
|
|
7
|
+
ProviderChangeActiveModelInput,
|
|
8
|
+
ProviderCurrentActiveModel,
|
|
9
|
+
ProviderModelsDefinition,
|
|
10
|
+
ProviderSessionActiveModelChange,
|
|
11
|
+
} from '@/shared/types.js';
|
|
12
|
+
import {
|
|
13
|
+
buildDefaultProviderCurrentActiveModel,
|
|
14
|
+
writeProviderSessionActiveModelChange,
|
|
15
|
+
} from '@/shared/utils.js';
|
|
16
|
+
|
|
17
|
+
export const CLAUDE_FALLBACK_MODELS: ProviderModelsDefinition = {
|
|
18
|
+
OPTIONS: [
|
|
19
|
+
{
|
|
20
|
+
value: 'default',
|
|
21
|
+
label: 'Default (recommended)',
|
|
22
|
+
description: 'Use the default model (currently Opus 4.7 (1M context)) · $5/$25 per Mtok',
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
value: 'sonnet',
|
|
26
|
+
label: 'Sonnet',
|
|
27
|
+
description: 'Sonnet 4.6 · Best for everyday tasks · $3/$15 per Mtok',
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
value: 'sonnet[1m]',
|
|
31
|
+
label: 'Sonnet (1M context)',
|
|
32
|
+
description: 'Sonnet 4.6 for long sessions · $3/$15 per Mtok',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
value: 'haiku',
|
|
36
|
+
label: 'Haiku',
|
|
37
|
+
description: 'Haiku 4.5 · Fastest for quick answers · $1/$5 per Mtok',
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
DEFAULT: 'default',
|
|
41
|
+
};
|
|
42
|
+
type ClaudeInitEvent = {
|
|
43
|
+
sessionId?: string;
|
|
44
|
+
session_id?: string;
|
|
45
|
+
type?: string;
|
|
46
|
+
subtype?: string;
|
|
47
|
+
model?: string;
|
|
48
|
+
message?: {
|
|
49
|
+
content?: unknown;
|
|
50
|
+
model?: string;
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const ANSI_PATTERN = new RegExp(
|
|
55
|
+
'[\\u001B\\u009B][[\\]()#;?]*(?:'
|
|
56
|
+
+ '(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]'
|
|
57
|
+
+ '|(?:[\\dA-PR-TZcf-ntqry=><~]))',
|
|
58
|
+
'g',
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
const extractClaudeEventModel = (event: ClaudeInitEvent, sessionId: string): string | null => {
|
|
62
|
+
const eventSessionId = event.sessionId ?? event.session_id;
|
|
63
|
+
if (eventSessionId && eventSessionId !== sessionId) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const contentModel = extractClaudeModelFromMessageContent(event.message?.content);
|
|
68
|
+
if (contentModel) {
|
|
69
|
+
return contentModel;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const directModel = event.model?.trim();
|
|
73
|
+
if (directModel) {
|
|
74
|
+
return directModel;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const messageModel = event.message?.model?.trim();
|
|
78
|
+
return messageModel || null;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const stripAnsi = (value: string): string => value.replace(ANSI_PATTERN, '');
|
|
82
|
+
|
|
83
|
+
const extractTaggedContent = (content: string, tagName: string): string | null => {
|
|
84
|
+
const escapedTagName = tagName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
85
|
+
const match = new RegExp(`<${escapedTagName}>([\\s\\S]*?)<\\/${escapedTagName}>`).exec(content);
|
|
86
|
+
return match ? match[1] : null;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const extractClaudeModelFromTextContent = (content: string): string | null => {
|
|
90
|
+
const localCommandStdout = extractTaggedContent(content, 'local-command-stdout');
|
|
91
|
+
if (localCommandStdout !== null) {
|
|
92
|
+
const cleanedStdout = stripAnsi(localCommandStdout).replace(/\s+/g, ' ').trim();
|
|
93
|
+
const changedModel = /(?:set|changed|switched)\s+model\s+to\s+(.+?)\.?$/i.exec(cleanedStdout);
|
|
94
|
+
if (changedModel?.[1]?.trim()) {
|
|
95
|
+
return changedModel[1].trim();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const modelTag = extractTaggedContent(content, 'model')?.trim();
|
|
100
|
+
return modelTag || null;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const extractClaudeModelFromMessageContent = (content: unknown): string | null => {
|
|
104
|
+
if (typeof content === 'string') {
|
|
105
|
+
return extractClaudeModelFromTextContent(content);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (!Array.isArray(content)) {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
for (const part of content) {
|
|
113
|
+
if (!part || typeof part !== 'object' || !('text' in part) || typeof part.text !== 'string') {
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const model = extractClaudeModelFromTextContent(part.text);
|
|
118
|
+
if (model) {
|
|
119
|
+
return model;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return null;
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const readClaudeSessionModelFromJsonl = async (
|
|
127
|
+
sessionId: string,
|
|
128
|
+
jsonlPath: string,
|
|
129
|
+
): Promise<ProviderCurrentActiveModel | null> => {
|
|
130
|
+
const content = await readFile(jsonlPath, 'utf8');
|
|
131
|
+
const lines = content
|
|
132
|
+
.split(/\r?\n/)
|
|
133
|
+
.map((line) => line.trim())
|
|
134
|
+
.filter(Boolean);
|
|
135
|
+
|
|
136
|
+
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
137
|
+
try {
|
|
138
|
+
const event = JSON.parse(lines[index]) as ClaudeInitEvent;
|
|
139
|
+
const model = extractClaudeEventModel(event, sessionId);
|
|
140
|
+
if (model) {
|
|
141
|
+
return { model };
|
|
142
|
+
}
|
|
143
|
+
} catch {
|
|
144
|
+
// Skip malformed JSONL lines that can happen during concurrent writes.
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return null;
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
export class ClaudeProviderModels implements IProviderModels {
|
|
152
|
+
async getSupportedModels(): Promise<ProviderModelsDefinition> {
|
|
153
|
+
// claude creates a new jsonl file as a separate session for this request.
|
|
154
|
+
// As a result, it lists the workspace where this is invoked when it shouldn't.
|
|
155
|
+
//
|
|
156
|
+
// Disabled for now:
|
|
157
|
+
// const queryInstance = query({
|
|
158
|
+
// prompt: 'Get supported models',
|
|
159
|
+
// options: buildClaudeQueryOptions(),
|
|
160
|
+
// });
|
|
161
|
+
// const supportedModels = await queryInstance.supportedModels();
|
|
162
|
+
// queryInstance.close();
|
|
163
|
+
// return buildClaudeModelsDefinition(supportedModels);
|
|
164
|
+
return CLAUDE_FALLBACK_MODELS;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async getCurrentActiveModel(sessionId?: string): Promise<ProviderCurrentActiveModel> {
|
|
168
|
+
if (!sessionId?.trim()) {
|
|
169
|
+
return buildDefaultProviderCurrentActiveModel(await this.getSupportedModels());
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
const ownerUserId = getDefaultOwnerUserId();
|
|
174
|
+
const jsonlPath = sessionsDb.getSessionById(ownerUserId, sessionId)?.jsonl_path;
|
|
175
|
+
const activeModel = jsonlPath
|
|
176
|
+
? await readClaudeSessionModelFromJsonl(sessionId, jsonlPath)
|
|
177
|
+
: null;
|
|
178
|
+
if (activeModel?.model) {
|
|
179
|
+
return activeModel;
|
|
180
|
+
}
|
|
181
|
+
} catch {
|
|
182
|
+
// Fall through to the provider default when the session-backed lookup fails.
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return buildDefaultProviderCurrentActiveModel(await this.getSupportedModels());
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async changeActiveModel(
|
|
189
|
+
input: ProviderChangeActiveModelInput,
|
|
190
|
+
): Promise<ProviderSessionActiveModelChange> {
|
|
191
|
+
return writeProviderSessionActiveModelChange('claude', input);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
@@ -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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
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
|
-
|
|
30
|
+
addUniqueProviderSkillSource(sources, seenRootDirs, {
|
|
71
31
|
scope: 'repo',
|
|
72
32
|
rootDir: path.join(path.dirname(workspacePath), '.agents', 'skills'),
|
|
73
33
|
commandPrefix: '$',
|
|
74
34
|
});
|
|
75
|
-
|
|
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
|
-
|
|
42
|
+
addUniqueProviderSkillSource(sources, seenRootDirs, {
|
|
83
43
|
scope: 'user',
|
|
84
44
|
rootDir: path.join(os.homedir(), '.agents', 'skills'),
|
|
85
45
|
commandPrefix: '$',
|
|
86
46
|
});
|
|
87
|
-
|
|
47
|
+
addUniqueProviderSkillSource(sources, seenRootDirs, {
|
|
88
48
|
scope: 'admin',
|
|
89
49
|
rootDir: path.join('/etc', 'codex', 'skills'),
|
|
90
50
|
commandPrefix: '$',
|
|
91
51
|
});
|
|
92
|
-
|
|
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();
|