@cloudcli-ai/cloudcli 1.31.5 → 1.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.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-MOIvpy53.css +32 -0
- package/dist/assets/{index-DqMVUeZS.js → index-skXSjBje.js} +290 -270
- package/dist/index.html +2 -2
- package/dist/modelConstants.js +841 -0
- package/dist-server/server/claude-sdk.js +67 -39
- package/dist-server/server/claude-sdk.js.map +1 -1
- package/dist-server/server/cursor-cli.js +6 -9
- package/dist-server/server/cursor-cli.js.map +1 -1
- package/dist-server/server/gemini-cli.js +163 -35
- 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/database/migrations.js +11 -0
- package/dist-server/server/modules/database/migrations.js.map +1 -1
- package/dist-server/server/modules/database/repositories/projects.db.js +12 -0
- package/dist-server/server/modules/database/repositories/projects.db.js.map +1 -1
- package/dist-server/server/modules/database/repositories/sessions.db.integration.test.js +61 -0
- package/dist-server/server/modules/database/repositories/sessions.db.integration.test.js.map +1 -0
- package/dist-server/server/modules/database/repositories/sessions.db.js +49 -8
- package/dist-server/server/modules/database/repositories/sessions.db.js.map +1 -1
- package/dist-server/server/modules/database/schema.js +1 -0
- package/dist-server/server/modules/database/schema.js.map +1 -1
- package/dist-server/server/modules/projects/projects.routes.js +12 -3
- package/dist-server/server/modules/projects/projects.routes.js.map +1 -1
- package/dist-server/server/modules/projects/services/project-delete.service.js +14 -1
- package/dist-server/server/modules/projects/services/project-delete.service.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 +47 -0
- package/dist-server/server/modules/projects/services/projects-with-sessions-fetch.service.js.map +1 -1
- package/dist-server/server/modules/providers/index.js +1 -0
- package/dist-server/server/modules/providers/index.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js +27 -7
- package/dist-server/server/modules/providers/list/claude/claude-auth.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-models.provider.js +141 -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-session-synchronizer.provider.js +55 -2
- package/dist-server/server/modules/providers/list/claude/claude-session-synchronizer.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js +153 -14
- package/dist-server/server/modules/providers/list/claude/claude-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-skills.provider.js +198 -0
- package/dist-server/server/modules/providers/list/claude/claude-skills.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/claude/claude.provider.js +4 -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-session-synchronizer.provider.js +54 -2
- package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js +21 -6
- package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-skills.provider.js +50 -0
- package/dist-server/server/modules/providers/list/codex/codex-skills.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/codex/codex.provider.js +4 -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-session-synchronizer.provider.js +8 -30
- package/dist-server/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/cursor/cursor-sessions.provider.js +187 -24
- package/dist-server/server/modules/providers/list/cursor/cursor-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/cursor/cursor-skills.provider.js +28 -0
- package/dist-server/server/modules/providers/list/cursor/cursor-skills.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/cursor/cursor.provider.js +4 -0
- package/dist-server/server/modules/providers/list/cursor/cursor.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/gemini/gemini-auth.provider.js +125 -2
- package/dist-server/server/modules/providers/list/gemini/gemini-auth.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-session-synchronizer.provider.js +24 -8
- package/dist-server/server/modules/providers/list/gemini/gemini-session-synchronizer.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/gemini/gemini-sessions.provider.js +10 -10
- package/dist-server/server/modules/providers/list/gemini/gemini-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/gemini/gemini-skills.provider.js +33 -0
- package/dist-server/server/modules/providers/list/gemini/gemini-skills.provider.js.map +1 -0
- package/dist-server/server/modules/providers/list/gemini/gemini.provider.js +4 -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 +65 -3
- 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-conversations-search.service.js +129 -13
- package/dist-server/server/modules/providers/services/session-conversations-search.service.js.map +1 -1
- 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 +13 -4
- package/dist-server/server/modules/providers/services/sessions-watcher.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/sessions.service.js +81 -7
- package/dist-server/server/modules/providers/services/sessions.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/skills.service.js +11 -0
- package/dist-server/server/modules/providers/services/skills.service.js.map +1 -0
- package/dist-server/server/modules/providers/shared/base/abstract.provider.js.map +1 -1
- package/dist-server/server/modules/providers/shared/skills/skills.provider.js +45 -0
- package/dist-server/server/modules/providers/shared/skills/skills.provider.js.map +1 -0
- 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 +262 -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 +259 -0
- package/dist-server/server/modules/providers/tests/skills.test.js.map +1 -0
- package/dist-server/server/modules/websocket/services/chat-websocket.service.js +12 -1
- package/dist-server/server/modules/websocket/services/chat-websocket.service.js.map +1 -1
- package/dist-server/server/modules/websocket/services/plugin-websocket-proxy.service.js +4 -4
- package/dist-server/server/modules/websocket/services/plugin-websocket-proxy.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/openai-codex.js +87 -29
- 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 +204 -211
- 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/claude-cli-path.js +103 -0
- package/dist-server/server/shared/claude-cli-path.js.map +1 -0
- package/dist-server/server/shared/claude-cli-path.test.js +45 -0
- package/dist-server/server/shared/claude-cli-path.test.js.map +1 -0
- package/dist-server/server/{utils → shared}/frontmatter.js +3 -3
- package/dist-server/server/shared/frontmatter.js.map +1 -0
- package/dist-server/server/shared/utils.js +368 -0
- package/dist-server/server/shared/utils.js.map +1 -1
- package/dist-server/server/utils/commandParser.js +3 -3
- package/dist-server/server/utils/commandParser.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 +74 -40
- package/server/cursor-cli.js +6 -11
- package/server/gemini-cli.js +197 -43
- package/server/gemini-response-handler.js +38 -0
- package/server/index.js +144 -19
- package/server/modules/database/index.ts +1 -0
- package/server/modules/database/migrations.ts +12 -0
- package/server/modules/database/repositories/projects.db.ts +13 -0
- package/server/modules/database/repositories/sessions.db.integration.test.ts +72 -0
- package/server/modules/database/repositories/sessions.db.ts +60 -9
- package/server/modules/database/schema.ts +1 -0
- package/server/modules/projects/projects.routes.ts +20 -3
- package/server/modules/projects/services/project-delete.service.ts +16 -1
- package/server/modules/projects/services/project-management.service.ts +2 -0
- package/server/modules/projects/services/projects-with-sessions-fetch.service.ts +71 -1
- package/server/modules/providers/README.md +354 -0
- package/server/modules/providers/index.ts +2 -1
- package/server/modules/providers/list/claude/claude-auth.provider.ts +38 -13
- package/server/modules/providers/list/claude/claude-models.provider.ts +191 -0
- package/server/modules/providers/list/claude/claude-session-synchronizer.provider.ts +68 -2
- package/server/modules/providers/list/claude/claude-sessions.provider.ts +175 -14
- package/server/modules/providers/list/claude/claude-skills.provider.ts +257 -0
- package/server/modules/providers/list/claude/claude.provider.ts +11 -1
- package/server/modules/providers/list/codex/codex-models.provider.ts +125 -0
- package/server/modules/providers/list/codex/codex-session-synchronizer.provider.ts +62 -2
- package/server/modules/providers/list/codex/codex-sessions.provider.ts +25 -6
- package/server/modules/providers/list/codex/codex-skills.provider.ts +60 -0
- package/server/modules/providers/list/codex/codex.provider.ts +11 -1
- package/server/modules/providers/list/cursor/cursor-models.provider.ts +820 -0
- package/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.ts +17 -40
- package/server/modules/providers/list/cursor/cursor-sessions.provider.ts +216 -26
- package/server/modules/providers/list/cursor/cursor-skills.provider.ts +31 -0
- package/server/modules/providers/list/cursor/cursor.provider.ts +11 -1
- package/server/modules/providers/list/gemini/gemini-auth.provider.ts +158 -2
- package/server/modules/providers/list/gemini/gemini-models.provider.ts +42 -0
- package/server/modules/providers/list/gemini/gemini-session-synchronizer.provider.ts +24 -20
- package/server/modules/providers/list/gemini/gemini-sessions.provider.ts +10 -11
- package/server/modules/providers/list/gemini/gemini-skills.provider.ts +36 -0
- package/server/modules/providers/list/gemini/gemini.provider.ts +11 -1
- 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 +157 -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 +97 -4
- 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-conversations-search.service.ts +162 -13
- package/server/modules/providers/services/session-synchronizer.service.ts +1 -0
- package/server/modules/providers/services/sessions-watcher.service.ts +14 -4
- package/server/modules/providers/services/sessions.service.ts +112 -9
- package/server/modules/providers/services/skills.service.ts +15 -0
- package/server/modules/providers/shared/base/abstract.provider.ts +4 -0
- package/server/modules/providers/shared/skills/skills.provider.ts +64 -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 +334 -0
- package/server/modules/providers/tests/provider-models.service.test.ts +318 -0
- package/server/modules/providers/tests/skills.test.ts +512 -0
- package/server/modules/websocket/services/chat-websocket.service.ts +15 -1
- package/server/modules/websocket/services/plugin-websocket-proxy.service.ts +4 -4
- package/server/modules/websocket/services/shell-websocket.service.ts +9 -0
- package/server/openai-codex.js +99 -30
- 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 +256 -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/claude-cli-path.test.ts +61 -0
- package/server/shared/claude-cli-path.ts +139 -0
- package/server/{utils/frontmatter.js → shared/frontmatter.ts} +3 -3
- package/server/shared/interfaces.ts +64 -0
- package/server/shared/types.ts +166 -1
- package/server/shared/utils.ts +478 -0
- package/server/utils/commandParser.js +5 -3
- package/dist/assets/index-6rYG_mRo.css +0 -32
- package/dist-server/server/utils/frontmatter.js.map +0 -1
- package/dist-server/shared/modelConstants.js +0 -98
- package/dist-server/shared/modelConstants.js.map +0 -1
- package/shared/modelConstants.js +0 -106
|
@@ -0,0 +1,191 @@
|
|
|
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.7 (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: 'haiku',
|
|
35
|
+
label: 'Haiku',
|
|
36
|
+
description: 'Haiku 4.5 · Fastest for quick answers · $1/$5 per Mtok',
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
DEFAULT: 'default',
|
|
40
|
+
};
|
|
41
|
+
type ClaudeInitEvent = {
|
|
42
|
+
sessionId?: string;
|
|
43
|
+
session_id?: string;
|
|
44
|
+
type?: string;
|
|
45
|
+
subtype?: string;
|
|
46
|
+
model?: string;
|
|
47
|
+
message?: {
|
|
48
|
+
content?: unknown;
|
|
49
|
+
model?: string;
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const ANSI_PATTERN = new RegExp(
|
|
54
|
+
'[\\u001B\\u009B][[\\]()#;?]*(?:'
|
|
55
|
+
+ '(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]'
|
|
56
|
+
+ '|(?:[\\dA-PR-TZcf-ntqry=><~]))',
|
|
57
|
+
'g',
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
const extractClaudeEventModel = (event: ClaudeInitEvent, sessionId: string): string | null => {
|
|
61
|
+
const eventSessionId = event.sessionId ?? event.session_id;
|
|
62
|
+
if (eventSessionId && eventSessionId !== sessionId) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const contentModel = extractClaudeModelFromMessageContent(event.message?.content);
|
|
67
|
+
if (contentModel) {
|
|
68
|
+
return contentModel;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const directModel = event.model?.trim();
|
|
72
|
+
if (directModel) {
|
|
73
|
+
return directModel;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const messageModel = event.message?.model?.trim();
|
|
77
|
+
return messageModel || null;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const stripAnsi = (value: string): string => value.replace(ANSI_PATTERN, '');
|
|
81
|
+
|
|
82
|
+
const extractTaggedContent = (content: string, tagName: string): string | null => {
|
|
83
|
+
const escapedTagName = tagName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
84
|
+
const match = new RegExp(`<${escapedTagName}>([\\s\\S]*?)<\\/${escapedTagName}>`).exec(content);
|
|
85
|
+
return match ? match[1] : null;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const extractClaudeModelFromTextContent = (content: string): string | null => {
|
|
89
|
+
const localCommandStdout = extractTaggedContent(content, 'local-command-stdout');
|
|
90
|
+
if (localCommandStdout !== null) {
|
|
91
|
+
const cleanedStdout = stripAnsi(localCommandStdout).replace(/\s+/g, ' ').trim();
|
|
92
|
+
const changedModel = /(?:set|changed|switched)\s+model\s+to\s+(.+?)\.?$/i.exec(cleanedStdout);
|
|
93
|
+
if (changedModel?.[1]?.trim()) {
|
|
94
|
+
return changedModel[1].trim();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const modelTag = extractTaggedContent(content, 'model')?.trim();
|
|
99
|
+
return modelTag || null;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const extractClaudeModelFromMessageContent = (content: unknown): string | null => {
|
|
103
|
+
if (typeof content === 'string') {
|
|
104
|
+
return extractClaudeModelFromTextContent(content);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (!Array.isArray(content)) {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
for (const part of content) {
|
|
112
|
+
if (!part || typeof part !== 'object' || !('text' in part) || typeof part.text !== 'string') {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const model = extractClaudeModelFromTextContent(part.text);
|
|
117
|
+
if (model) {
|
|
118
|
+
return model;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return null;
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const readClaudeSessionModelFromJsonl = async (
|
|
126
|
+
sessionId: string,
|
|
127
|
+
jsonlPath: string,
|
|
128
|
+
): Promise<ProviderCurrentActiveModel | null> => {
|
|
129
|
+
const content = await readFile(jsonlPath, 'utf8');
|
|
130
|
+
const lines = content
|
|
131
|
+
.split(/\r?\n/)
|
|
132
|
+
.map((line) => line.trim())
|
|
133
|
+
.filter(Boolean);
|
|
134
|
+
|
|
135
|
+
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
136
|
+
try {
|
|
137
|
+
const event = JSON.parse(lines[index]) as ClaudeInitEvent;
|
|
138
|
+
const model = extractClaudeEventModel(event, sessionId);
|
|
139
|
+
if (model) {
|
|
140
|
+
return { model };
|
|
141
|
+
}
|
|
142
|
+
} catch {
|
|
143
|
+
// Skip malformed JSONL lines that can happen during concurrent writes.
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return null;
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
export class ClaudeProviderModels implements IProviderModels {
|
|
151
|
+
async getSupportedModels(): Promise<ProviderModelsDefinition> {
|
|
152
|
+
// claude creates a new jsonl file as a separate session for this request.
|
|
153
|
+
// As a result, it lists the workspace where this is invoked when it shouldn't.
|
|
154
|
+
//
|
|
155
|
+
// Disabled for now:
|
|
156
|
+
// const queryInstance = query({
|
|
157
|
+
// prompt: 'Get supported models',
|
|
158
|
+
// options: buildClaudeQueryOptions(),
|
|
159
|
+
// });
|
|
160
|
+
// const supportedModels = await queryInstance.supportedModels();
|
|
161
|
+
// queryInstance.close();
|
|
162
|
+
// return buildClaudeModelsDefinition(supportedModels);
|
|
163
|
+
return CLAUDE_FALLBACK_MODELS;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async getCurrentActiveModel(sessionId?: string): Promise<ProviderCurrentActiveModel> {
|
|
167
|
+
if (!sessionId?.trim()) {
|
|
168
|
+
return buildDefaultProviderCurrentActiveModel(await this.getSupportedModels());
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
const jsonlPath = sessionsDb.getSessionById(sessionId)?.jsonl_path;
|
|
173
|
+
const activeModel = jsonlPath
|
|
174
|
+
? await readClaudeSessionModelFromJsonl(sessionId, jsonlPath)
|
|
175
|
+
: null;
|
|
176
|
+
if (activeModel?.model) {
|
|
177
|
+
return activeModel;
|
|
178
|
+
}
|
|
179
|
+
} catch {
|
|
180
|
+
// Fall through to the provider default when the session-backed lookup fails.
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return buildDefaultProviderCurrentActiveModel(await this.getSupportedModels());
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async changeActiveModel(
|
|
187
|
+
input: ProviderChangeActiveModelInput,
|
|
188
|
+
): Promise<ProviderSessionActiveModelChange> {
|
|
189
|
+
return writeProviderSessionActiveModelChange('claude', input);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import os from 'node:os';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { readFile } from 'node:fs/promises';
|
|
3
4
|
|
|
4
5
|
import { sessionsDb } from '@/modules/database/index.js';
|
|
5
6
|
import {
|
|
@@ -91,7 +92,7 @@ export class ClaudeSessionSynchronizer implements IProviderSessionSynchronizer {
|
|
|
91
92
|
filePath: string,
|
|
92
93
|
nameMap: Map<string, string>
|
|
93
94
|
): Promise<ParsedSession | null> {
|
|
94
|
-
|
|
95
|
+
const parsed = await extractFirstValidJsonlData(filePath, (rawData) => {
|
|
95
96
|
const data = rawData as Record<string, unknown>;
|
|
96
97
|
const sessionId = typeof data.sessionId === 'string' ? data.sessionId : undefined;
|
|
97
98
|
const projectPath = typeof data.cwd === 'string' ? data.cwd : undefined;
|
|
@@ -103,8 +104,73 @@ export class ClaudeSessionSynchronizer implements IProviderSessionSynchronizer {
|
|
|
103
104
|
return {
|
|
104
105
|
sessionId,
|
|
105
106
|
projectPath,
|
|
106
|
-
sessionName: normalizeSessionName(nameMap.get(sessionId), 'Untitled Claude Session'),
|
|
107
107
|
};
|
|
108
108
|
});
|
|
109
|
+
|
|
110
|
+
if (!parsed) {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const existingSession = sessionsDb.getSessionById(parsed.sessionId);
|
|
115
|
+
const existingSessionName = existingSession?.custom_name;
|
|
116
|
+
if (existingSessionName && existingSessionName !== 'Untitled Claude Session') {
|
|
117
|
+
return {
|
|
118
|
+
...parsed,
|
|
119
|
+
sessionName: normalizeSessionName(existingSessionName, 'Untitled Claude Session'),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
let sessionName = nameMap.get(parsed.sessionId);
|
|
124
|
+
if (!sessionName) {
|
|
125
|
+
sessionName = await this.extractSessionAiTitleFromEnd(filePath, parsed.sessionId);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
...parsed,
|
|
130
|
+
sessionName: normalizeSessionName(sessionName, 'Untitled Claude Session'),
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private async extractSessionAiTitleFromEnd(
|
|
135
|
+
filePath: string,
|
|
136
|
+
sessionId: string
|
|
137
|
+
): Promise<string | undefined> {
|
|
138
|
+
try {
|
|
139
|
+
const content = await readFile(filePath, 'utf8');
|
|
140
|
+
const lines = content.split(/\r?\n/);
|
|
141
|
+
|
|
142
|
+
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
143
|
+
const line = lines[index]?.trim();
|
|
144
|
+
if (!line) {
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
let parsed: unknown;
|
|
149
|
+
try {
|
|
150
|
+
parsed = JSON.parse(line);
|
|
151
|
+
} catch {
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const data = parsed as Record<string, unknown>;
|
|
156
|
+
const eventType = typeof data.type === 'string' ? data.type : undefined;
|
|
157
|
+
const eventSessionId = typeof data.sessionId === 'string' ? data.sessionId : undefined;
|
|
158
|
+
const aiTitle = typeof data.aiTitle === 'string' ? data.aiTitle : undefined;
|
|
159
|
+
const lastPrompt = typeof data.lastPrompt === 'string' ? data.lastPrompt : undefined;
|
|
160
|
+
const claudeRenamedTitle = typeof data.customTitle === 'string' ? data.customTitle : undefined;
|
|
161
|
+
|
|
162
|
+
if (
|
|
163
|
+
(eventType === 'ai-title' && eventSessionId === sessionId && aiTitle?.trim()) ||
|
|
164
|
+
(eventType === 'last-prompt' && eventSessionId === sessionId && lastPrompt?.trim()) ||
|
|
165
|
+
(eventType === "custom-title" && eventSessionId === sessionId && claudeRenamedTitle?.trim())
|
|
166
|
+
) {
|
|
167
|
+
return aiTitle || lastPrompt || claudeRenamedTitle;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
} catch {
|
|
171
|
+
// Ignore missing/unreadable files so sync can continue.
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return undefined;
|
|
109
175
|
}
|
|
110
176
|
}
|
|
@@ -200,17 +200,18 @@ async function getSessionMessages(
|
|
|
200
200
|
}
|
|
201
201
|
|
|
202
202
|
/**
|
|
203
|
-
* Claude writes
|
|
204
|
-
*
|
|
203
|
+
* Claude writes a mix of truly internal transcript rows and "UI-hidden" local
|
|
204
|
+
* command artifacts into the same JSONL stream.
|
|
205
|
+
*
|
|
206
|
+
* Important distinction:
|
|
207
|
+
* - system reminders / caveats / interruption banners should stay hidden
|
|
208
|
+
* - local command payloads (`<command-name>...`) and stdout wrappers
|
|
209
|
+
* (`<local-command-stdout>...`) should be remapped into normal chat messages
|
|
210
|
+
* instead of being discarded as internal content
|
|
205
211
|
*/
|
|
206
212
|
const INTERNAL_CONTENT_PREFIXES = [
|
|
207
|
-
'<command-name>',
|
|
208
|
-
'<command-message>',
|
|
209
|
-
'<command-args>',
|
|
210
|
-
'<local-command-stdout>',
|
|
211
213
|
'<system-reminder>',
|
|
212
214
|
'Caveat:',
|
|
213
|
-
'This session is being continued from a previous',
|
|
214
215
|
'[Request interrupted',
|
|
215
216
|
] as const;
|
|
216
217
|
|
|
@@ -218,6 +219,73 @@ function isInternalContent(content: string): boolean {
|
|
|
218
219
|
return INTERNAL_CONTENT_PREFIXES.some((prefix) => content.startsWith(prefix));
|
|
219
220
|
}
|
|
220
221
|
|
|
222
|
+
/**
|
|
223
|
+
* Claude wraps local slash-command metadata in lightweight XML-like tags inside
|
|
224
|
+
* a plain string payload. We intentionally parse only the small tag surface we
|
|
225
|
+
* care about instead of introducing a generic XML parser for untrusted history.
|
|
226
|
+
*/
|
|
227
|
+
function extractTaggedContent(content: string, tagName: string): string | null {
|
|
228
|
+
const escapedTagName = tagName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
229
|
+
const match = new RegExp(`<${escapedTagName}>([\\s\\S]*?)<\\/${escapedTagName}>`).exec(content);
|
|
230
|
+
return match ? match[1] : null;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
type ClaudeLocalCommandPayload = {
|
|
234
|
+
commandName: string;
|
|
235
|
+
commandMessage: string;
|
|
236
|
+
commandArgs: string;
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Converts Claude's hidden local command wrapper into structured metadata.
|
|
241
|
+
*
|
|
242
|
+
* The three tags often coexist in one string payload. Returning `null` lets the
|
|
243
|
+
* normal text path continue untouched for unrelated messages.
|
|
244
|
+
*/
|
|
245
|
+
function parseLocalCommandPayload(content: string): ClaudeLocalCommandPayload | null {
|
|
246
|
+
const commandName = extractTaggedContent(content, 'command-name');
|
|
247
|
+
const commandMessage = extractTaggedContent(content, 'command-message');
|
|
248
|
+
const commandArgs = extractTaggedContent(content, 'command-args');
|
|
249
|
+
|
|
250
|
+
if (commandName === null && commandMessage === null && commandArgs === null) {
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return {
|
|
255
|
+
commandName: commandName ?? '',
|
|
256
|
+
commandMessage: commandMessage ?? '',
|
|
257
|
+
commandArgs: commandArgs ?? '',
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Produces the short user-visible command string that should appear in chat.
|
|
263
|
+
*
|
|
264
|
+
* We prefer the slash-prefixed command name because that most closely matches
|
|
265
|
+
* what the user actually typed, and only fall back to the message body when the
|
|
266
|
+
* command name is unavailable in older transcript variants.
|
|
267
|
+
*/
|
|
268
|
+
function buildLocalCommandDisplayText(payload: ClaudeLocalCommandPayload): string {
|
|
269
|
+
const commandName = payload.commandName.trim();
|
|
270
|
+
const commandMessage = payload.commandMessage.trim();
|
|
271
|
+
const commandArgs = payload.commandArgs.trim();
|
|
272
|
+
const baseCommand = commandName || commandMessage;
|
|
273
|
+
|
|
274
|
+
if (!baseCommand) {
|
|
275
|
+
return '';
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return commandArgs ? `${baseCommand} ${commandArgs}` : baseCommand;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Claude local-command stdout may contain ANSI styling codes because it was
|
|
283
|
+
* captured from the terminal. The web chat should receive readable plain text.
|
|
284
|
+
*/
|
|
285
|
+
function stripAnsiFormatting(text: string): string {
|
|
286
|
+
return text.replace(/\u001B\[[0-9;?]*[ -/]*[@-~]/g, '');
|
|
287
|
+
}
|
|
288
|
+
|
|
221
289
|
export class ClaudeSessionsProvider implements IProviderSessions {
|
|
222
290
|
/**
|
|
223
291
|
* Normalizes one Claude JSONL entry or live SDK stream event into the shared
|
|
@@ -240,7 +308,7 @@ export class ClaudeSessionsProvider implements IProviderSessions {
|
|
|
240
308
|
const ts = raw.timestamp || new Date().toISOString();
|
|
241
309
|
const baseId = raw.uuid || generateMessageId('claude');
|
|
242
310
|
|
|
243
|
-
if (raw.message?.role === 'user' && raw.message?.content) {
|
|
311
|
+
if (raw.message?.role === 'user' && raw.message?.content && raw.isMeta !== true) {
|
|
244
312
|
if (Array.isArray(raw.message.content)) {
|
|
245
313
|
for (let partIndex = 0; partIndex < raw.message.content.length; partIndex++) {
|
|
246
314
|
const part = raw.message.content[partIndex];
|
|
@@ -293,6 +361,80 @@ export class ClaudeSessionsProvider implements IProviderSessions {
|
|
|
293
361
|
}
|
|
294
362
|
} else if (typeof raw.message.content === 'string') {
|
|
295
363
|
const text = raw.message.content;
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Claude stores compact summaries as synthetic "user" rows so the CLI
|
|
367
|
+
* can resume the next session turn with the summary in-context.
|
|
368
|
+
*
|
|
369
|
+
* For the web UI this is much more useful as assistant-authored summary
|
|
370
|
+
* text; otherwise it is both filtered by the generic internal-prefix
|
|
371
|
+
* check and visually mislabeled as a user message.
|
|
372
|
+
*/
|
|
373
|
+
if (raw.isCompactSummary === true && text.trim()) {
|
|
374
|
+
messages.push(createNormalizedMessage({
|
|
375
|
+
id: baseId,
|
|
376
|
+
sessionId,
|
|
377
|
+
timestamp: ts,
|
|
378
|
+
provider: PROVIDER,
|
|
379
|
+
kind: 'text',
|
|
380
|
+
role: 'assistant',
|
|
381
|
+
content: text,
|
|
382
|
+
isCompactSummary: true,
|
|
383
|
+
}));
|
|
384
|
+
return messages;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Local slash commands are serialized as tagged text even though they
|
|
389
|
+
* are semantically a user action. Expose the parsed fields to the
|
|
390
|
+
* frontend and emit a plain user-visible command string so the command
|
|
391
|
+
* no longer disappears from history.
|
|
392
|
+
*/
|
|
393
|
+
const localCommandPayload = parseLocalCommandPayload(text);
|
|
394
|
+
if (localCommandPayload) {
|
|
395
|
+
const displayText = buildLocalCommandDisplayText(localCommandPayload);
|
|
396
|
+
if (displayText) {
|
|
397
|
+
messages.push(createNormalizedMessage({
|
|
398
|
+
id: baseId,
|
|
399
|
+
sessionId,
|
|
400
|
+
timestamp: ts,
|
|
401
|
+
provider: PROVIDER,
|
|
402
|
+
kind: 'text',
|
|
403
|
+
role: 'user',
|
|
404
|
+
content: displayText,
|
|
405
|
+
commandName: localCommandPayload.commandName,
|
|
406
|
+
commandMessage: localCommandPayload.commandMessage,
|
|
407
|
+
commandArgs: localCommandPayload.commandArgs,
|
|
408
|
+
isLocalCommand: true,
|
|
409
|
+
}));
|
|
410
|
+
}
|
|
411
|
+
return messages;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Local command stdout is also written as a "user" row in Claude's
|
|
416
|
+
* transcript, but it is terminal output produced in response to the
|
|
417
|
+
* command. Re-label it as assistant text so the chat transcript matches
|
|
418
|
+
* the actual conversational flow seen by the user.
|
|
419
|
+
*/
|
|
420
|
+
const localCommandStdout = extractTaggedContent(text, 'local-command-stdout');
|
|
421
|
+
if (localCommandStdout !== null) {
|
|
422
|
+
const stdoutText = stripAnsiFormatting(localCommandStdout).trim();
|
|
423
|
+
if (stdoutText) {
|
|
424
|
+
messages.push(createNormalizedMessage({
|
|
425
|
+
id: baseId,
|
|
426
|
+
sessionId,
|
|
427
|
+
timestamp: ts,
|
|
428
|
+
provider: PROVIDER,
|
|
429
|
+
kind: 'text',
|
|
430
|
+
role: 'assistant',
|
|
431
|
+
content: stdoutText,
|
|
432
|
+
isLocalCommandStdout: true,
|
|
433
|
+
}));
|
|
434
|
+
}
|
|
435
|
+
return messages;
|
|
436
|
+
}
|
|
437
|
+
|
|
296
438
|
if (text && !isInternalContent(text)) {
|
|
297
439
|
messages.push(createNormalizedMessage({
|
|
298
440
|
id: baseId,
|
|
@@ -414,7 +556,9 @@ export class ClaudeSessionsProvider implements IProviderSessions {
|
|
|
414
556
|
|
|
415
557
|
let result: ClaudeHistoryResult;
|
|
416
558
|
try {
|
|
417
|
-
|
|
559
|
+
// Load full history first so `total` reflects frontend-normalized messages,
|
|
560
|
+
// not raw JSONL records.
|
|
561
|
+
result = await getSessionMessages(sessionId, null, 0);
|
|
418
562
|
} catch (error) {
|
|
419
563
|
const message = error instanceof Error ? error.message : String(error);
|
|
420
564
|
console.warn(`[ClaudeProvider] Failed to load session ${sessionId}:`, message);
|
|
@@ -422,8 +566,6 @@ export class ClaudeSessionsProvider implements IProviderSessions {
|
|
|
422
566
|
}
|
|
423
567
|
|
|
424
568
|
const rawMessages = Array.isArray(result) ? result : (result.messages || []);
|
|
425
|
-
const total = Array.isArray(result) ? rawMessages.length : (result.total || 0);
|
|
426
|
-
const hasMore = Array.isArray(result) ? false : Boolean(result.hasMore);
|
|
427
569
|
|
|
428
570
|
const toolResultMap = new Map<string, ClaudeToolResult>();
|
|
429
571
|
for (const raw of rawMessages) {
|
|
@@ -464,12 +606,31 @@ export class ClaudeSessionsProvider implements IProviderSessions {
|
|
|
464
606
|
}
|
|
465
607
|
}
|
|
466
608
|
|
|
609
|
+
const totalNormalized = normalized.length;
|
|
610
|
+
let total = 0;
|
|
611
|
+
for (const msg of normalized) {
|
|
612
|
+
if (msg.kind !== 'tool_result') {
|
|
613
|
+
total += 1;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
const normalizedOffset = Math.max(0, offset);
|
|
617
|
+
const normalizedLimit = limit === null ? null : Math.max(0, limit);
|
|
618
|
+
const messages = normalizedLimit === null
|
|
619
|
+
? normalized
|
|
620
|
+
: normalized.slice(
|
|
621
|
+
Math.max(0, totalNormalized - normalizedOffset - normalizedLimit),
|
|
622
|
+
Math.max(0, totalNormalized - normalizedOffset),
|
|
623
|
+
);
|
|
624
|
+
const hasMore = normalizedLimit === null
|
|
625
|
+
? false
|
|
626
|
+
: Math.max(0, totalNormalized - normalizedOffset - normalizedLimit) > 0;
|
|
627
|
+
|
|
467
628
|
return {
|
|
468
|
-
messages
|
|
629
|
+
messages,
|
|
469
630
|
total,
|
|
470
631
|
hasMore,
|
|
471
|
-
offset,
|
|
472
|
-
limit,
|
|
632
|
+
offset: normalizedOffset,
|
|
633
|
+
limit: normalizedLimit,
|
|
473
634
|
};
|
|
474
635
|
}
|
|
475
636
|
}
|