@cloudcli-ai/cloudcli 1.37.0 → 1.37.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.
- package/dist/assets/index-D3jfzuKN.css +32 -0
- package/dist/assets/{index-eGqPeIEu.js → index-DTMWjJj8.js} +324 -305
- package/dist/index.html +2 -2
- package/dist-server/server/modules/commands/commands.routes.js +3 -3
- package/dist-server/server/modules/commands/commands.routes.js.map +1 -1
- package/dist-server/server/modules/commands/tests/commands.test.js +2 -7
- package/dist-server/server/modules/commands/tests/commands.test.js.map +1 -1
- package/dist-server/server/modules/database/index.js +2 -0
- package/dist-server/server/modules/database/index.js.map +1 -1
- package/dist-server/server/modules/database/migrations.js +18 -1
- package/dist-server/server/modules/database/migrations.js.map +1 -1
- package/dist-server/server/modules/database/repositories/provider-models.js +112 -0
- package/dist-server/server/modules/database/repositories/provider-models.js.map +1 -0
- package/dist-server/server/modules/database/repositories/sessions.db.js +63 -7
- package/dist-server/server/modules/database/repositories/sessions.db.js.map +1 -1
- package/dist-server/server/modules/database/schema.js +28 -3
- package/dist-server/server/modules/database/schema.js.map +1 -1
- package/dist-server/server/modules/database/tests/provider-models.db.integration.test.js +102 -0
- package/dist-server/server/modules/database/tests/provider-models.db.integration.test.js.map +1 -0
- package/dist-server/server/modules/database/tests/sessions-provider-mapping.test.js +2 -1
- package/dist-server/server/modules/database/tests/sessions-provider-mapping.test.js.map +1 -1
- package/dist-server/server/modules/database/tests/sessions.db.integration.test.js +22 -0
- package/dist-server/server/modules/database/tests/sessions.db.integration.test.js.map +1 -1
- package/dist-server/server/modules/file-tree/file-tree.module.js +5 -1
- package/dist-server/server/modules/file-tree/file-tree.module.js.map +1 -1
- package/dist-server/server/modules/file-tree/file-tree.service.js +50 -18
- package/dist-server/server/modules/file-tree/file-tree.service.js.map +1 -1
- package/dist-server/server/modules/file-tree/tests/file-tree.service.test.js +83 -7
- package/dist-server/server/modules/file-tree/tests/file-tree.service.test.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-models.provider.js +46 -14
- package/dist-server/server/modules/providers/list/claude/claude-models.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/claude/claude-runtime.provider.js +8 -15
- package/dist-server/server/modules/providers/list/claude/claude-runtime.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-models.provider.js +58 -71
- package/dist-server/server/modules/providers/list/codex/codex-models.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/codex/codex-session-synchronizer.provider.js +1 -54
- 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 +7 -7
- package/dist-server/server/modules/providers/list/codex/codex-sessions.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/cursor/cursor-models.provider.js +32 -669
- package/dist-server/server/modules/providers/list/cursor/cursor-models.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js +105 -328
- package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js.map +1 -1
- package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js +0 -12
- package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js.map +1 -1
- package/dist-server/server/modules/providers/provider.routes.js +139 -31
- package/dist-server/server/modules/providers/provider.routes.js.map +1 -1
- package/dist-server/server/modules/providers/services/provider-models.service.js +167 -199
- package/dist-server/server/modules/providers/services/provider-models.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/provider-runtime.service.js +2 -2
- package/dist-server/server/modules/providers/services/provider-runtime.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/session-conversations-search.service.js +12 -4
- package/dist-server/server/modules/providers/services/session-conversations-search.service.js.map +1 -1
- package/dist-server/server/modules/providers/services/sessions.service.js +62 -3
- package/dist-server/server/modules/providers/services/sessions.service.js.map +1 -1
- package/dist-server/server/modules/providers/tests/codex-sessions.test.js +53 -17
- package/dist-server/server/modules/providers/tests/codex-sessions.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/opencode-models.test.js +15 -126
- package/dist-server/server/modules/providers/tests/opencode-models.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/opencode-sessions.test.js +5 -5
- package/dist-server/server/modules/providers/tests/opencode-sessions.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/provider-models.service.test.js +175 -292
- package/dist-server/server/modules/providers/tests/provider-models.service.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/provider-runtime.service.test.js +2 -6
- package/dist-server/server/modules/providers/tests/provider-runtime.service.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/provider-token-usage.service.test.js +1 -0
- package/dist-server/server/modules/providers/tests/provider-token-usage.service.test.js.map +1 -1
- package/dist-server/server/modules/providers/tests/provider.routes.test.js +126 -0
- package/dist-server/server/modules/providers/tests/provider.routes.test.js.map +1 -0
- package/dist-server/server/modules/providers/tests/sessions.service.test.js +87 -0
- package/dist-server/server/modules/providers/tests/sessions.service.test.js.map +1 -0
- package/dist-server/server/modules/websocket/services/chat-websocket.service.js +5 -1
- package/dist-server/server/modules/websocket/services/chat-websocket.service.js.map +1 -1
- package/package.json +3 -3
- package/server/modules/commands/commands.routes.ts +3 -3
- package/server/modules/commands/tests/commands.test.ts +2 -7
- package/server/modules/database/index.ts +2 -0
- package/server/modules/database/migrations.ts +20 -0
- package/server/modules/database/repositories/provider-models.ts +168 -0
- package/server/modules/database/repositories/sessions.db.ts +85 -8
- package/server/modules/database/schema.ts +29 -3
- package/server/modules/database/tests/provider-models.db.integration.test.ts +125 -0
- package/server/modules/database/tests/sessions-provider-mapping.test.ts +2 -1
- package/server/modules/database/tests/sessions.db.integration.test.ts +32 -0
- package/server/modules/file-tree/file-tree.module.ts +5 -1
- package/server/modules/file-tree/file-tree.service.ts +68 -20
- package/server/modules/file-tree/tests/file-tree.service.test.ts +104 -7
- package/server/modules/providers/list/claude/claude-models.provider.ts +46 -14
- package/server/modules/providers/list/claude/claude-runtime.provider.js +8 -16
- package/server/modules/providers/list/codex/codex-models.provider.ts +58 -99
- package/server/modules/providers/list/codex/codex-session-synchronizer.provider.ts +1 -60
- package/server/modules/providers/list/codex/codex-sessions.provider.ts +7 -7
- package/server/modules/providers/list/cursor/cursor-models.provider.ts +32 -710
- package/server/modules/providers/list/opencode/opencode-models.provider.ts +105 -413
- package/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.ts +0 -13
- package/server/modules/providers/provider.routes.ts +185 -33
- package/server/modules/providers/services/provider-models.service.ts +223 -288
- package/server/modules/providers/services/provider-runtime.service.ts +2 -3
- package/server/modules/providers/services/session-conversations-search.service.ts +12 -4
- package/server/modules/providers/services/sessions.service.ts +87 -3
- package/server/modules/providers/tests/codex-sessions.test.ts +57 -18
- package/server/modules/providers/tests/opencode-models.test.ts +34 -135
- package/server/modules/providers/tests/opencode-sessions.test.ts +5 -5
- package/server/modules/providers/tests/provider-models.service.test.ts +353 -450
- package/server/modules/providers/tests/provider-runtime.service.test.ts +2 -6
- package/server/modules/providers/tests/provider-token-usage.service.test.ts +1 -0
- package/server/modules/providers/tests/provider.routes.test.ts +183 -0
- package/server/modules/providers/tests/sessions.service.test.ts +127 -0
- package/server/modules/websocket/services/chat-websocket.service.ts +5 -1
- package/server/shared/interfaces.ts +4 -6
- package/server/shared/types.ts +29 -17
- package/dist/assets/index-DSlds633.css +0 -32
|
@@ -61,12 +61,8 @@ function createService(providers: IProvider[]) {
|
|
|
61
61
|
},
|
|
62
62
|
async getProviderModels() {
|
|
63
63
|
return {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
updatedAt: new Date(0).toISOString(),
|
|
67
|
-
expiresAt: new Date(0).toISOString(),
|
|
68
|
-
source: 'fresh',
|
|
69
|
-
},
|
|
64
|
+
OPTIONS: [],
|
|
65
|
+
DEFAULT: 'default-model',
|
|
70
66
|
};
|
|
71
67
|
},
|
|
72
68
|
});
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { once } from 'node:events';
|
|
3
|
+
import { mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
4
|
+
import type { AddressInfo } from 'node:net';
|
|
5
|
+
import os from 'node:os';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import test from 'node:test';
|
|
8
|
+
|
|
9
|
+
import express, { type NextFunction, type Request, type Response } from 'express';
|
|
10
|
+
|
|
11
|
+
import { closeConnection, initializeDatabase, sessionsDb } from '@/modules/database/index.js';
|
|
12
|
+
import providerRouter from '@/modules/providers/provider.routes.js';
|
|
13
|
+
import { AppError } from '@/shared/utils.js';
|
|
14
|
+
|
|
15
|
+
async function withProviderServer(
|
|
16
|
+
run: (baseUrl: string, workspacePath: string) => Promise<void>,
|
|
17
|
+
): Promise<void> {
|
|
18
|
+
const previousDatabasePath = process.env.DATABASE_PATH;
|
|
19
|
+
const tempDirectory = await mkdtemp(path.join(os.tmpdir(), 'provider-routes-'));
|
|
20
|
+
|
|
21
|
+
closeConnection();
|
|
22
|
+
process.env.DATABASE_PATH = path.join(tempDirectory, 'auth.db');
|
|
23
|
+
await writeFile(process.env.DATABASE_PATH, '');
|
|
24
|
+
await initializeDatabase();
|
|
25
|
+
|
|
26
|
+
const app = express().use(express.json()).use('/api/providers', providerRouter);
|
|
27
|
+
app.use((error: unknown, _req: Request, res: Response, _next: NextFunction) => {
|
|
28
|
+
if (error instanceof AppError) {
|
|
29
|
+
res.status(error.statusCode).json({
|
|
30
|
+
success: false,
|
|
31
|
+
error: { code: error.code, message: error.message },
|
|
32
|
+
});
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
res.status(500).json({ success: false, error: { code: 'INTERNAL_ERROR' } });
|
|
36
|
+
});
|
|
37
|
+
const server = app.listen(0, '127.0.0.1');
|
|
38
|
+
await once(server, 'listening');
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
const address = server.address() as AddressInfo;
|
|
42
|
+
await run(`http://127.0.0.1:${address.port}`, path.join(tempDirectory, 'workspace'));
|
|
43
|
+
} finally {
|
|
44
|
+
await new Promise<void>((resolve, reject) => {
|
|
45
|
+
server.close((error) => error ? reject(error) : resolve());
|
|
46
|
+
});
|
|
47
|
+
closeConnection();
|
|
48
|
+
if (previousDatabasePath === undefined) {
|
|
49
|
+
delete process.env.DATABASE_PATH;
|
|
50
|
+
} else {
|
|
51
|
+
process.env.DATABASE_PATH = previousDatabasePath;
|
|
52
|
+
}
|
|
53
|
+
await rm(tempDirectory, { recursive: true, force: true });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
test('session creation route names a CloudCLI session from the initial message', async () => {
|
|
58
|
+
await withProviderServer(async (baseUrl, workspacePath) => {
|
|
59
|
+
const response = await fetch(`${baseUrl}/api/providers/sessions`, {
|
|
60
|
+
method: 'POST',
|
|
61
|
+
headers: { 'content-type': 'application/json' },
|
|
62
|
+
body: JSON.stringify({
|
|
63
|
+
provider: 'codex',
|
|
64
|
+
projectPath: workspacePath,
|
|
65
|
+
initialMessage: 'abcd efg\nhij klm nop',
|
|
66
|
+
}),
|
|
67
|
+
});
|
|
68
|
+
const payload = await response.json() as {
|
|
69
|
+
data: { sessionId: string; sessionName: string };
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
assert.equal(response.status, 201);
|
|
73
|
+
assert.equal(payload.data.sessionName, 'abcd efg hij klm');
|
|
74
|
+
assert.equal(
|
|
75
|
+
sessionsDb.getSessionById(payload.data.sessionId)?.custom_name,
|
|
76
|
+
'abcd efg hij klm',
|
|
77
|
+
);
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('reasoning effort is persisted and returned with the active session model', async () => {
|
|
82
|
+
await withProviderServer(async (baseUrl, workspacePath) => {
|
|
83
|
+
sessionsDb.createAppSession('effort-session', 'codex', workspacePath);
|
|
84
|
+
|
|
85
|
+
const updateResponse = await fetch(
|
|
86
|
+
`${baseUrl}/api/providers/codex/sessions/effort-session/active-effort`,
|
|
87
|
+
{
|
|
88
|
+
method: 'POST',
|
|
89
|
+
headers: { 'content-type': 'application/json' },
|
|
90
|
+
body: JSON.stringify({ effort: 'ultra' }),
|
|
91
|
+
},
|
|
92
|
+
);
|
|
93
|
+
const updatePayload = await updateResponse.json() as {
|
|
94
|
+
data: { effort: string; sessionId: string };
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
assert.equal(updateResponse.status, 200);
|
|
98
|
+
assert.equal(updatePayload.data.effort, 'ultra');
|
|
99
|
+
assert.equal(sessionsDb.getSessionById('effort-session')?.effort, 'ultra');
|
|
100
|
+
|
|
101
|
+
const readResponse = await fetch(
|
|
102
|
+
`${baseUrl}/api/providers/codex/sessions/effort-session/active-model`,
|
|
103
|
+
);
|
|
104
|
+
const readPayload = await readResponse.json() as {
|
|
105
|
+
data: { effort: string | null; sessionId: string };
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
assert.equal(readResponse.status, 200);
|
|
109
|
+
assert.equal(readPayload.data.sessionId, 'effort-session');
|
|
110
|
+
assert.equal(readPayload.data.effort, 'ultra');
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test('model routes expose immutable defaults and full custom model CRUD', async () => {
|
|
115
|
+
await withProviderServer(async (baseUrl) => {
|
|
116
|
+
const initialResponse = await fetch(`${baseUrl}/api/providers/codex/models`);
|
|
117
|
+
const initialPayload = await initialResponse.json() as {
|
|
118
|
+
data: {
|
|
119
|
+
cache?: unknown;
|
|
120
|
+
models: {
|
|
121
|
+
OPTIONS: Array<{ recordId?: number; value: string; isCustom: boolean }>;
|
|
122
|
+
};
|
|
123
|
+
};
|
|
124
|
+
};
|
|
125
|
+
assert.equal(initialResponse.status, 200);
|
|
126
|
+
assert.equal('cache' in initialPayload.data, false);
|
|
127
|
+
const predefined = initialPayload.data.models.OPTIONS[0];
|
|
128
|
+
assert.equal(predefined.isCustom, false);
|
|
129
|
+
assert.equal(predefined.recordId, undefined);
|
|
130
|
+
|
|
131
|
+
const createResponse = await fetch(`${baseUrl}/api/providers/codex/models`, {
|
|
132
|
+
method: 'POST',
|
|
133
|
+
headers: { 'content-type': 'application/json' },
|
|
134
|
+
body: JSON.stringify({ model: 'Gateway GPT', id: 'gateway/gpt' }),
|
|
135
|
+
});
|
|
136
|
+
const createPayload = await createResponse.json() as {
|
|
137
|
+
data: { model: { recordId: number; value: string; label: string; isCustom: boolean } };
|
|
138
|
+
};
|
|
139
|
+
assert.equal(createResponse.status, 201);
|
|
140
|
+
assert.equal(createPayload.data.model.isCustom, true);
|
|
141
|
+
const customRecordId = createPayload.data.model.recordId;
|
|
142
|
+
|
|
143
|
+
const updateResponse = await fetch(
|
|
144
|
+
`${baseUrl}/api/providers/codex/models/${customRecordId}`,
|
|
145
|
+
{
|
|
146
|
+
method: 'PATCH',
|
|
147
|
+
headers: { 'content-type': 'application/json' },
|
|
148
|
+
body: JSON.stringify({ model: 'Gateway GPT Updated', id: 'gateway/gpt-v2' }),
|
|
149
|
+
},
|
|
150
|
+
);
|
|
151
|
+
const updatePayload = await updateResponse.json() as {
|
|
152
|
+
data: { model: { value: string; label: string } };
|
|
153
|
+
};
|
|
154
|
+
assert.equal(updateResponse.status, 200);
|
|
155
|
+
assert.equal(updatePayload.data.model.value, 'gateway/gpt-v2');
|
|
156
|
+
assert.equal(updatePayload.data.model.label, 'Gateway GPT Updated');
|
|
157
|
+
|
|
158
|
+
const immutableResponse = await fetch(
|
|
159
|
+
`${baseUrl}/api/providers/codex/models/999999`,
|
|
160
|
+
{
|
|
161
|
+
method: 'PATCH',
|
|
162
|
+
headers: { 'content-type': 'application/json' },
|
|
163
|
+
body: JSON.stringify({ model: 'Changed', id: 'changed' }),
|
|
164
|
+
},
|
|
165
|
+
);
|
|
166
|
+
const immutablePayload = await immutableResponse.json() as { error: { code: string } };
|
|
167
|
+
assert.equal(immutableResponse.status, 404);
|
|
168
|
+
assert.equal(immutablePayload.error.code, 'MODEL_NOT_FOUND');
|
|
169
|
+
|
|
170
|
+
const deleteResponse = await fetch(
|
|
171
|
+
`${baseUrl}/api/providers/codex/models/${customRecordId}`,
|
|
172
|
+
{ method: 'DELETE' },
|
|
173
|
+
);
|
|
174
|
+
const deletePayload = await deleteResponse.json() as {
|
|
175
|
+
data: { models: { OPTIONS: Array<{ recordId: number }> } };
|
|
176
|
+
};
|
|
177
|
+
assert.equal(deleteResponse.status, 200);
|
|
178
|
+
assert.equal(
|
|
179
|
+
deletePayload.data.models.OPTIONS.some((option) => option.recordId === customRecordId),
|
|
180
|
+
false,
|
|
181
|
+
);
|
|
182
|
+
});
|
|
183
|
+
});
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { mkdtemp, rm } from 'node:fs/promises';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import test from 'node:test';
|
|
6
|
+
|
|
7
|
+
import { closeConnection, initializeDatabase, projectsDb, sessionsDb } from '@/modules/database/index.js';
|
|
8
|
+
import { sessionsService } from '@/modules/providers/services/sessions.service.js';
|
|
9
|
+
|
|
10
|
+
async function withIsolatedDatabase(runTest: () => void | Promise<void>): Promise<void> {
|
|
11
|
+
const previousDatabasePath = process.env.DATABASE_PATH;
|
|
12
|
+
const tempDirectory = await mkdtemp(path.join(os.tmpdir(), 'sessions-service-db-'));
|
|
13
|
+
|
|
14
|
+
closeConnection();
|
|
15
|
+
process.env.DATABASE_PATH = path.join(tempDirectory, 'auth.db');
|
|
16
|
+
await initializeDatabase();
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
await runTest();
|
|
20
|
+
} finally {
|
|
21
|
+
closeConnection();
|
|
22
|
+
if (previousDatabasePath === undefined) {
|
|
23
|
+
delete process.env.DATABASE_PATH;
|
|
24
|
+
} else {
|
|
25
|
+
process.env.DATABASE_PATH = previousDatabasePath;
|
|
26
|
+
}
|
|
27
|
+
await rm(tempDirectory, { recursive: true, force: true });
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
test('provider session id returns the mapped native id', { concurrency: false }, async () => {
|
|
32
|
+
await withIsolatedDatabase(() => {
|
|
33
|
+
sessionsDb.createAppSession('app-session-id', 'codex', '/tmp/session-id-copy-project');
|
|
34
|
+
sessionsDb.assignProviderSessionId('app-session-id', 'codex-native-session-id');
|
|
35
|
+
|
|
36
|
+
assert.equal(sessionsService.getProviderSessionId('app-session-id'), 'codex-native-session-id');
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('app session names use at most four whole words from the initial message', { concurrency: false }, async () => {
|
|
41
|
+
await withIsolatedDatabase(() => {
|
|
42
|
+
const result = sessionsService.createAppSession(
|
|
43
|
+
'codex',
|
|
44
|
+
'/tmp/session-name-project',
|
|
45
|
+
' supercalifragilisticexpialidocious\nsecond third fourth fifth ',
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
assert.equal(result.sessionName, 'supercalifragilisticexpialidocious second third fourth');
|
|
49
|
+
assert.equal(
|
|
50
|
+
sessionsDb.getSessionById(result.sessionId)?.custom_name,
|
|
51
|
+
'supercalifragilisticexpialidocious second third fourth',
|
|
52
|
+
);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('app sessions without message text receive a stable fallback name', { concurrency: false }, async () => {
|
|
57
|
+
await withIsolatedDatabase(() => {
|
|
58
|
+
const result = sessionsService.createAppSession('claude', '/tmp/attachment-only-project', ' \n ');
|
|
59
|
+
|
|
60
|
+
assert.equal(result.sessionName, 'Untitled Session');
|
|
61
|
+
assert.equal(sessionsDb.getSessionById(result.sessionId)?.custom_name, 'Untitled Session');
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('provider session id is unavailable until the provider assigns one', { concurrency: false }, async () => {
|
|
66
|
+
await withIsolatedDatabase(() => {
|
|
67
|
+
sessionsDb.createAppSession('pending-app-session', 'claude', '/tmp/session-id-copy-project');
|
|
68
|
+
|
|
69
|
+
assert.throws(
|
|
70
|
+
() => sessionsService.getProviderSessionId('pending-app-session'),
|
|
71
|
+
(error: unknown) => {
|
|
72
|
+
const typedError = error as { code?: string; statusCode?: number };
|
|
73
|
+
return typedError.code === 'PROVIDER_SESSION_ID_NOT_AVAILABLE' && typedError.statusCode === 409;
|
|
74
|
+
},
|
|
75
|
+
);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('provider session id reports a missing app session', { concurrency: false }, async () => {
|
|
80
|
+
await withIsolatedDatabase(() => {
|
|
81
|
+
assert.throws(
|
|
82
|
+
() => sessionsService.getProviderSessionId('missing-session'),
|
|
83
|
+
(error: unknown) => {
|
|
84
|
+
const typedError = error as { code?: string; statusCode?: number };
|
|
85
|
+
return typedError.code === 'SESSION_NOT_FOUND' && typedError.statusCode === 404;
|
|
86
|
+
},
|
|
87
|
+
);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test('recent sessions map project metadata and preserve database pagination', { concurrency: false }, async () => {
|
|
92
|
+
await withIsolatedDatabase(() => {
|
|
93
|
+
sessionsDb.createSession(
|
|
94
|
+
'older-session',
|
|
95
|
+
'claude',
|
|
96
|
+
'/tmp/recent-project',
|
|
97
|
+
'Older conversation',
|
|
98
|
+
'2026-08-01T08:00:00.000Z',
|
|
99
|
+
'2026-08-01T09:00:00.000Z',
|
|
100
|
+
);
|
|
101
|
+
sessionsDb.createSession(
|
|
102
|
+
'newer-session',
|
|
103
|
+
'codex',
|
|
104
|
+
'/tmp/recent-project',
|
|
105
|
+
'Newer conversation',
|
|
106
|
+
'2026-08-01T10:00:00.000Z',
|
|
107
|
+
'2026-08-01T11:00:00.000Z',
|
|
108
|
+
);
|
|
109
|
+
projectsDb.updateCustomProjectName('/tmp/recent-project', 'Recent Project');
|
|
110
|
+
|
|
111
|
+
const project = projectsDb.getProjectPath('/tmp/recent-project');
|
|
112
|
+
const page = sessionsService.listRecentSessions(1, 0);
|
|
113
|
+
|
|
114
|
+
assert.deepEqual(page, {
|
|
115
|
+
conversations: [{
|
|
116
|
+
sessionId: 'newer-session',
|
|
117
|
+
provider: 'codex',
|
|
118
|
+
projectId: project?.project_id ?? null,
|
|
119
|
+
projectDisplayName: 'Recent Project',
|
|
120
|
+
sessionTitle: 'Newer conversation',
|
|
121
|
+
lastActivity: '2026-08-01T11:00:00.000Z',
|
|
122
|
+
}],
|
|
123
|
+
total: 2,
|
|
124
|
+
hasMore: true,
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
});
|
|
@@ -194,10 +194,14 @@ async function handleChatSend(
|
|
|
194
194
|
const command = typeof data.content === 'string' ? data.content : '';
|
|
195
195
|
|
|
196
196
|
// Record what this turn runs with so reopening the session later restores the
|
|
197
|
-
// same model, and so the resume path has a
|
|
197
|
+
// same model and reasoning effort, and so the resume path has a
|
|
198
|
+
// session-scoped model answer to use.
|
|
198
199
|
if (typeof clientOptions.model === 'string' && clientOptions.model.trim()) {
|
|
199
200
|
providerModelsService.setSessionModel(provider, sessionId, clientOptions.model);
|
|
200
201
|
}
|
|
202
|
+
if (typeof clientOptions.effort === 'string' && clientOptions.effort.trim()) {
|
|
203
|
+
providerModelsService.setSessionEffort(provider, sessionId, clientOptions.effort);
|
|
204
|
+
}
|
|
201
205
|
|
|
202
206
|
const attachmentCandidates = [
|
|
203
207
|
...normalizeAttachmentDescriptors(clientOptions.images),
|
|
@@ -60,15 +60,13 @@ export interface IProvider {
|
|
|
60
60
|
/**
|
|
61
61
|
* Model catalog contract for one provider.
|
|
62
62
|
*
|
|
63
|
-
* Implementations
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
* pickers. The `DEFAULT` field should be the most appropriate default selection
|
|
67
|
-
* for that provider at the time the catalog is read.
|
|
63
|
+
* Implementations supply CloudCLI's curated predefined models and can inspect
|
|
64
|
+
* provider-native session state. The Providers service merges these immutable
|
|
65
|
+
* source-controlled definitions with user-created SQLite rows at read time.
|
|
68
66
|
*/
|
|
69
67
|
export interface IProviderModels {
|
|
70
68
|
/**
|
|
71
|
-
* Returns the
|
|
69
|
+
* Returns the curated predefined catalog owned by this provider adapter.
|
|
72
70
|
*/
|
|
73
71
|
getSupportedModels(): Promise<ProviderModelsDefinition>;
|
|
74
72
|
|
package/server/shared/types.ts
CHANGED
|
@@ -75,6 +75,10 @@ export type ProviderModelOption = {
|
|
|
75
75
|
value: string;
|
|
76
76
|
label: string;
|
|
77
77
|
description?: string;
|
|
78
|
+
/** Stable SQLite row id used only by model-management actions. */
|
|
79
|
+
recordId?: number;
|
|
80
|
+
/** True for user-created rows; false for immutable CloudCLI defaults. */
|
|
81
|
+
isCustom?: boolean;
|
|
78
82
|
effort?: {
|
|
79
83
|
default?: string;
|
|
80
84
|
values: {
|
|
@@ -93,28 +97,31 @@ export type ProviderModelsDefinition = {
|
|
|
93
97
|
};
|
|
94
98
|
|
|
95
99
|
/**
|
|
96
|
-
*
|
|
100
|
+
* One persisted custom-model row in the provider model library.
|
|
97
101
|
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
* `
|
|
101
|
-
*
|
|
102
|
+
* Provider modules use this shape at the database boundary. Predefined models
|
|
103
|
+
* never use this type because they remain source-controlled in provider
|
|
104
|
+
* adapters. `modelId` is sent to the provider runtime, while `model` is the
|
|
105
|
+
* user-supplied display name shown in pickers.
|
|
102
106
|
*/
|
|
103
|
-
export type
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
+
export type CustomProviderModelRecord = {
|
|
108
|
+
recordId: number;
|
|
109
|
+
provider: LLMProvider;
|
|
110
|
+
modelId: string;
|
|
111
|
+
model: string;
|
|
112
|
+
sortOrder: number;
|
|
107
113
|
};
|
|
108
114
|
|
|
109
115
|
/**
|
|
110
|
-
*
|
|
116
|
+
* User-editable values accepted when creating or changing a custom model.
|
|
111
117
|
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
118
|
+
* `id` must be the exact provider-facing model identifier and cannot contain
|
|
119
|
+
* whitespace. `model` is a concise display name. The provider is supplied by
|
|
120
|
+
* the route path so a row can never be moved across providers accidentally.
|
|
114
121
|
*/
|
|
115
|
-
export type
|
|
116
|
-
|
|
117
|
-
|
|
122
|
+
export type CustomProviderModelInput = {
|
|
123
|
+
id: string;
|
|
124
|
+
model: string;
|
|
118
125
|
};
|
|
119
126
|
|
|
120
127
|
// ---------------------------
|
|
@@ -147,7 +154,8 @@ export type ProviderCurrentActiveModel = {
|
|
|
147
154
|
export type ProviderSessionModelSource = 'session' | 'provider' | 'default';
|
|
148
155
|
|
|
149
156
|
/**
|
|
150
|
-
* The model one session runs with,
|
|
157
|
+
* The model one session runs with, its persisted reasoning effort when one has
|
|
158
|
+
* been recorded, and where the model answer came from.
|
|
151
159
|
*
|
|
152
160
|
* Returned by `providerModelsService.resolveSessionModel` and used by the
|
|
153
161
|
* `/models`, `/cost` and `/status` commands, the active-model route, and the
|
|
@@ -157,6 +165,8 @@ export type ProviderSessionModel = {
|
|
|
157
165
|
provider: LLMProvider;
|
|
158
166
|
sessionId: string | null;
|
|
159
167
|
model: string;
|
|
168
|
+
/** NULL means this session has not recorded an effort choice yet. */
|
|
169
|
+
effort: string | null;
|
|
160
170
|
source: ProviderSessionModelSource;
|
|
161
171
|
};
|
|
162
172
|
|
|
@@ -944,7 +954,9 @@ export type FileTreeFileSystem = {
|
|
|
944
954
|
access(candidatePath: string): Promise<void>;
|
|
945
955
|
stat(candidatePath: string): Promise<FileTreeStats>;
|
|
946
956
|
lstat(candidatePath: string): Promise<FileTreeStats>;
|
|
947
|
-
|
|
957
|
+
// Streamed rather than returned as an array so a directory with millions of
|
|
958
|
+
// children is abandoned at the entry limit instead of being materialized.
|
|
959
|
+
openDirectory(directoryPath: string): AsyncIterable<FileTreeDirectoryEntry>;
|
|
948
960
|
realpath(candidatePath: string): Promise<string>;
|
|
949
961
|
readTextFile(filePath: string): Promise<string>;
|
|
950
962
|
writeTextFile(filePath: string, content: string): Promise<void>;
|