agentgui 1.0.219 → 1.0.221
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/database.js +7 -5
- package/lib/claude-runner.js +14 -6
- package/package.json +1 -1
- package/server.js +74 -24
- package/static/index.html +24 -0
- package/static/js/client.js +107 -17
- package/static/js/conversations.js +6 -2
package/database.js
CHANGED
|
@@ -228,7 +228,8 @@ try {
|
|
|
228
228
|
lastSyncedAt: 'INTEGER',
|
|
229
229
|
workingDirectory: 'TEXT',
|
|
230
230
|
claudeSessionId: 'TEXT',
|
|
231
|
-
isStreaming: 'INTEGER DEFAULT 0'
|
|
231
|
+
isStreaming: 'INTEGER DEFAULT 0',
|
|
232
|
+
model: 'TEXT'
|
|
232
233
|
};
|
|
233
234
|
|
|
234
235
|
let addedColumns = false;
|
|
@@ -270,19 +271,20 @@ function generateId(prefix) {
|
|
|
270
271
|
|
|
271
272
|
export const queries = {
|
|
272
273
|
_db: db,
|
|
273
|
-
createConversation(agentType, title = null, workingDirectory = null) {
|
|
274
|
+
createConversation(agentType, title = null, workingDirectory = null, model = null) {
|
|
274
275
|
const id = generateId('conv');
|
|
275
276
|
const now = Date.now();
|
|
276
277
|
const stmt = prep(
|
|
277
|
-
`INSERT INTO conversations (id, agentId, agentType, title, created_at, updated_at, status, workingDirectory) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
|
|
278
|
+
`INSERT INTO conversations (id, agentId, agentType, title, created_at, updated_at, status, workingDirectory, model) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
278
279
|
);
|
|
279
|
-
stmt.run(id, agentType, agentType, title, now, now, 'active', workingDirectory);
|
|
280
|
+
stmt.run(id, agentType, agentType, title, now, now, 'active', workingDirectory, model);
|
|
280
281
|
|
|
281
282
|
return {
|
|
282
283
|
id,
|
|
283
284
|
agentType,
|
|
284
285
|
title,
|
|
285
286
|
workingDirectory,
|
|
287
|
+
model,
|
|
286
288
|
created_at: now,
|
|
287
289
|
updated_at: now,
|
|
288
290
|
status: 'active'
|
|
@@ -301,7 +303,7 @@ export const queries = {
|
|
|
301
303
|
|
|
302
304
|
getConversationsList() {
|
|
303
305
|
const stmt = prep(
|
|
304
|
-
'SELECT id, title, agentType, created_at, updated_at, messageCount, workingDirectory, isStreaming FROM conversations WHERE status != ? ORDER BY updated_at DESC'
|
|
306
|
+
'SELECT id, title, agentType, created_at, updated_at, messageCount, workingDirectory, isStreaming, model FROM conversations WHERE status != ? ORDER BY updated_at DESC'
|
|
305
307
|
);
|
|
306
308
|
return stmt.all('deleted');
|
|
307
309
|
},
|
package/lib/claude-runner.js
CHANGED
|
@@ -306,14 +306,16 @@ class AgentRunner {
|
|
|
306
306
|
if (initialized && !sessionCreated) {
|
|
307
307
|
sessionCreated = true;
|
|
308
308
|
|
|
309
|
+
const sessionParams = {
|
|
310
|
+
cwd: cwd,
|
|
311
|
+
mcpServers: []
|
|
312
|
+
};
|
|
313
|
+
if (config.model) sessionParams.model = config.model;
|
|
309
314
|
const sessionRequest = {
|
|
310
315
|
jsonrpc: '2.0',
|
|
311
316
|
id: ++requestId,
|
|
312
317
|
method: 'session/new',
|
|
313
|
-
params:
|
|
314
|
-
cwd: cwd,
|
|
315
|
-
mcpServers: []
|
|
316
|
-
}
|
|
318
|
+
params: sessionParams
|
|
317
319
|
};
|
|
318
320
|
proc.stdin.write(JSON.stringify(sessionRequest) + '\n');
|
|
319
321
|
} else if (!initialized) {
|
|
@@ -482,7 +484,8 @@ registry.register({
|
|
|
482
484
|
outputFormat = 'stream-json',
|
|
483
485
|
print = true,
|
|
484
486
|
resumeSessionId = null,
|
|
485
|
-
systemPrompt = null
|
|
487
|
+
systemPrompt = null,
|
|
488
|
+
model = null
|
|
486
489
|
} = config;
|
|
487
490
|
|
|
488
491
|
const flags = [];
|
|
@@ -490,6 +493,7 @@ registry.register({
|
|
|
490
493
|
if (verbose) flags.push('--verbose');
|
|
491
494
|
flags.push(`--output-format=${outputFormat}`);
|
|
492
495
|
flags.push('--dangerously-skip-permissions');
|
|
496
|
+
if (model) flags.push('--model', model);
|
|
493
497
|
if (resumeSessionId) flags.push('--resume', resumeSessionId);
|
|
494
498
|
if (systemPrompt) flags.push('--append-system-prompt', systemPrompt);
|
|
495
499
|
|
|
@@ -735,7 +739,11 @@ registry.register({
|
|
|
735
739
|
protocol: 'acp',
|
|
736
740
|
supportsStdin: false,
|
|
737
741
|
supportedFeatures: ['streaming', 'resume', 'acp-protocol'],
|
|
738
|
-
buildArgs
|
|
742
|
+
buildArgs(prompt, config) {
|
|
743
|
+
const args = ['--experimental-acp', '--yolo'];
|
|
744
|
+
if (config?.model) args.push('--model', config.model);
|
|
745
|
+
return args;
|
|
746
|
+
},
|
|
739
747
|
protocolHandler: acpProtocolHandler
|
|
740
748
|
});
|
|
741
749
|
|
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -202,6 +202,44 @@ function discoverAgents() {
|
|
|
202
202
|
|
|
203
203
|
const discoveredAgents = discoverAgents();
|
|
204
204
|
|
|
205
|
+
const modelCache = new Map();
|
|
206
|
+
|
|
207
|
+
async function getModelsForAgent(agentId) {
|
|
208
|
+
if (agentId === 'claude-code') {
|
|
209
|
+
return [
|
|
210
|
+
{ id: '', label: 'Default' },
|
|
211
|
+
{ id: 'sonnet', label: 'Sonnet' },
|
|
212
|
+
{ id: 'opus', label: 'Opus' },
|
|
213
|
+
{ id: 'haiku', label: 'Haiku' },
|
|
214
|
+
{ id: 'claude-sonnet-4-5-20250929', label: 'Sonnet 4.5' },
|
|
215
|
+
{ id: 'claude-opus-4-6', label: 'Opus 4.6' },
|
|
216
|
+
{ id: 'claude-haiku-4-5-20251001', label: 'Haiku 4.5' }
|
|
217
|
+
];
|
|
218
|
+
}
|
|
219
|
+
if (agentId === 'opencode') {
|
|
220
|
+
try {
|
|
221
|
+
const result = execSync('opencode models 2>/dev/null', { encoding: 'utf-8', timeout: 10000 });
|
|
222
|
+
const lines = result.split('\n').map(l => l.trim()).filter(Boolean);
|
|
223
|
+
const models = [{ id: '', label: 'Default' }];
|
|
224
|
+
for (const line of lines) {
|
|
225
|
+
models.push({ id: line, label: line });
|
|
226
|
+
}
|
|
227
|
+
return models;
|
|
228
|
+
} catch (_) {
|
|
229
|
+
return [{ id: '', label: 'Default' }];
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
if (agentId === 'gemini') {
|
|
233
|
+
return [
|
|
234
|
+
{ id: '', label: 'Default' },
|
|
235
|
+
{ id: 'gemini-2.5-pro', label: 'Gemini 2.5 Pro' },
|
|
236
|
+
{ id: 'gemini-2.5-flash', label: 'Gemini 2.5 Flash' },
|
|
237
|
+
{ id: 'gemini-2.0-flash', label: 'Gemini 2.0 Flash' }
|
|
238
|
+
];
|
|
239
|
+
}
|
|
240
|
+
return [];
|
|
241
|
+
}
|
|
242
|
+
|
|
205
243
|
const GEMINI_SCOPES = [
|
|
206
244
|
'https://www.googleapis.com/auth/cloud-platform',
|
|
207
245
|
'https://www.googleapis.com/auth/userinfo.email',
|
|
@@ -722,8 +760,8 @@ const server = http.createServer(async (req, res) => {
|
|
|
722
760
|
|
|
723
761
|
if (pathOnly === '/api/conversations' && req.method === 'POST') {
|
|
724
762
|
const body = await parseBody(req);
|
|
725
|
-
const conversation = queries.createConversation(body.agentId, body.title, body.workingDirectory || null);
|
|
726
|
-
queries.createEvent('conversation.created', { agentId: body.agentId, workingDirectory: conversation.workingDirectory }, conversation.id);
|
|
763
|
+
const conversation = queries.createConversation(body.agentId, body.title, body.workingDirectory || null, body.model || null);
|
|
764
|
+
queries.createEvent('conversation.created', { agentId: body.agentId, workingDirectory: conversation.workingDirectory, model: conversation.model }, conversation.id);
|
|
727
765
|
broadcastSync({ type: 'conversation_created', conversation });
|
|
728
766
|
sendJSON(req, res, 201, { conversation });
|
|
729
767
|
return;
|
|
@@ -782,6 +820,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
782
820
|
if (!conv) { sendJSON(req, res, 404, { error: 'Conversation not found' }); return; }
|
|
783
821
|
const body = await parseBody(req);
|
|
784
822
|
const agentId = body.agentId || conv.agentType || conv.agentId || 'claude-code';
|
|
823
|
+
const model = body.model || conv.model || null;
|
|
785
824
|
const idempotencyKey = body.idempotencyKey || null;
|
|
786
825
|
const message = queries.createMessage(conversationId, 'user', body.content, idempotencyKey);
|
|
787
826
|
queries.createEvent('message.created', { role: 'user', messageId: message.id }, conversationId);
|
|
@@ -789,7 +828,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
789
828
|
|
|
790
829
|
if (activeExecutions.has(conversationId)) {
|
|
791
830
|
if (!messageQueues.has(conversationId)) messageQueues.set(conversationId, []);
|
|
792
|
-
messageQueues.get(conversationId).push({ content: body.content, agentId, messageId: message.id });
|
|
831
|
+
messageQueues.get(conversationId).push({ content: body.content, agentId, model, messageId: message.id });
|
|
793
832
|
const queueLength = messageQueues.get(conversationId).length;
|
|
794
833
|
broadcastSync({ type: 'queue_status', conversationId, queueLength, messageId: message.id, timestamp: Date.now() });
|
|
795
834
|
sendJSON(req, res, 200, { message, queued: true, queuePosition: queueLength, idempotencyKey });
|
|
@@ -813,7 +852,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
813
852
|
|
|
814
853
|
sendJSON(req, res, 201, { message, session, idempotencyKey });
|
|
815
854
|
|
|
816
|
-
processMessageWithStreaming(conversationId, message.id, session.id, body.content, agentId)
|
|
855
|
+
processMessageWithStreaming(conversationId, message.id, session.id, body.content, agentId, model)
|
|
817
856
|
.catch(err => {
|
|
818
857
|
console.error(`[messages] Uncaught error for conv ${conversationId}:`, err.message);
|
|
819
858
|
debugLog(`[messages] Uncaught error: ${err.message}`);
|
|
@@ -831,6 +870,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
831
870
|
|
|
832
871
|
const prompt = body.content || body.message || '';
|
|
833
872
|
const agentId = body.agentId || conv.agentType || conv.agentId || 'claude-code';
|
|
873
|
+
const model = body.model || conv.model || null;
|
|
834
874
|
|
|
835
875
|
const userMessage = queries.createMessage(conversationId, 'user', prompt);
|
|
836
876
|
queries.createEvent('message.created', { role: 'user', messageId: userMessage.id }, conversationId);
|
|
@@ -840,7 +880,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
840
880
|
if (activeExecutions.has(conversationId)) {
|
|
841
881
|
debugLog(`[stream] Conversation ${conversationId} is busy, queuing message`);
|
|
842
882
|
if (!messageQueues.has(conversationId)) messageQueues.set(conversationId, []);
|
|
843
|
-
messageQueues.get(conversationId).push({ content: prompt, agentId, messageId: userMessage.id });
|
|
883
|
+
messageQueues.get(conversationId).push({ content: prompt, agentId, model, messageId: userMessage.id });
|
|
844
884
|
|
|
845
885
|
const queueLength = messageQueues.get(conversationId).length;
|
|
846
886
|
broadcastSync({ type: 'queue_status', conversationId, queueLength, messageId: userMessage.id, timestamp: Date.now() });
|
|
@@ -866,7 +906,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
866
906
|
|
|
867
907
|
sendJSON(req, res, 200, { message: userMessage, session, streamId: session.id });
|
|
868
908
|
|
|
869
|
-
processMessageWithStreaming(conversationId, userMessage.id, session.id, prompt, agentId)
|
|
909
|
+
processMessageWithStreaming(conversationId, userMessage.id, session.id, prompt, agentId, model)
|
|
870
910
|
.catch(err => debugLog(`[stream] Uncaught error: ${err.message}`));
|
|
871
911
|
return;
|
|
872
912
|
}
|
|
@@ -1127,6 +1167,24 @@ const server = http.createServer(async (req, res) => {
|
|
|
1127
1167
|
return;
|
|
1128
1168
|
}
|
|
1129
1169
|
|
|
1170
|
+
const modelsMatch = pathOnly.match(/^\/api\/agents\/([^/]+)\/models$/);
|
|
1171
|
+
if (modelsMatch && req.method === 'GET') {
|
|
1172
|
+
const agentId = modelsMatch[1];
|
|
1173
|
+
const cached = modelCache.get(agentId);
|
|
1174
|
+
if (cached && (Date.now() - cached.ts) < 300000) {
|
|
1175
|
+
sendJSON(req, res, 200, { models: cached.models });
|
|
1176
|
+
return;
|
|
1177
|
+
}
|
|
1178
|
+
try {
|
|
1179
|
+
const models = await getModelsForAgent(agentId);
|
|
1180
|
+
modelCache.set(agentId, { models, ts: Date.now() });
|
|
1181
|
+
sendJSON(req, res, 200, { models });
|
|
1182
|
+
} catch (err) {
|
|
1183
|
+
sendJSON(req, res, 200, { models: [] });
|
|
1184
|
+
}
|
|
1185
|
+
return;
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1130
1188
|
if (pathOnly === '/api/agents/auth-status' && req.method === 'GET') {
|
|
1131
1189
|
const statuses = discoveredAgents.map(agent => {
|
|
1132
1190
|
const status = { id: agent.id, name: agent.name, authenticated: false, detail: '' };
|
|
@@ -1438,11 +1496,6 @@ const server = http.createServer(async (req, res) => {
|
|
|
1438
1496
|
}
|
|
1439
1497
|
|
|
1440
1498
|
const speech = await getSpeech();
|
|
1441
|
-
const status = speech.getStatus();
|
|
1442
|
-
if (status.ttsError) {
|
|
1443
|
-
sendJSON(req, res, 503, { error: status.ttsError, retryable: false });
|
|
1444
|
-
return;
|
|
1445
|
-
}
|
|
1446
1499
|
const wavBuffer = await speech.synthesize(text, voiceId);
|
|
1447
1500
|
res.writeHead(200, { 'Content-Type': 'audio/wav', 'Content-Length': wavBuffer.length });
|
|
1448
1501
|
res.end(wavBuffer);
|
|
@@ -1465,11 +1518,6 @@ const server = http.createServer(async (req, res) => {
|
|
|
1465
1518
|
return;
|
|
1466
1519
|
}
|
|
1467
1520
|
const speech = await getSpeech();
|
|
1468
|
-
const status = speech.getStatus();
|
|
1469
|
-
if (status.ttsError) {
|
|
1470
|
-
sendJSON(req, res, 503, { error: status.ttsError, retryable: false });
|
|
1471
|
-
return;
|
|
1472
|
-
}
|
|
1473
1521
|
res.writeHead(200, {
|
|
1474
1522
|
'Content-Type': 'application/octet-stream',
|
|
1475
1523
|
'Transfer-Encoding': 'chunked',
|
|
@@ -1717,7 +1765,7 @@ function createChunkBatcher() {
|
|
|
1717
1765
|
return { add, drain };
|
|
1718
1766
|
}
|
|
1719
1767
|
|
|
1720
|
-
async function processMessageWithStreaming(conversationId, messageId, sessionId, content, agentId) {
|
|
1768
|
+
async function processMessageWithStreaming(conversationId, messageId, sessionId, content, agentId, model) {
|
|
1721
1769
|
const startTime = Date.now();
|
|
1722
1770
|
|
|
1723
1771
|
const conv = queries.getConversation(conversationId);
|
|
@@ -1854,6 +1902,7 @@ async function processMessageWithStreaming(conversationId, messageId, sessionId,
|
|
|
1854
1902
|
}
|
|
1855
1903
|
};
|
|
1856
1904
|
|
|
1905
|
+
const resolvedModel = model || conv?.model || null;
|
|
1857
1906
|
const config = {
|
|
1858
1907
|
verbose: true,
|
|
1859
1908
|
outputFormat: 'stream-json',
|
|
@@ -1861,6 +1910,7 @@ async function processMessageWithStreaming(conversationId, messageId, sessionId,
|
|
|
1861
1910
|
print: true,
|
|
1862
1911
|
resumeSessionId,
|
|
1863
1912
|
systemPrompt: SYSTEM_PROMPT,
|
|
1913
|
+
model: resolvedModel || undefined,
|
|
1864
1914
|
onEvent,
|
|
1865
1915
|
onPid: (pid) => {
|
|
1866
1916
|
const entry = activeExecutions.get(conversationId);
|
|
@@ -1962,7 +2012,7 @@ async function processMessageWithStreaming(conversationId, messageId, sessionId,
|
|
|
1962
2012
|
conversationId,
|
|
1963
2013
|
timestamp: Date.now()
|
|
1964
2014
|
});
|
|
1965
|
-
scheduleRetry(conversationId, messageId, content, agentId);
|
|
2015
|
+
scheduleRetry(conversationId, messageId, content, agentId, model);
|
|
1966
2016
|
}, cooldownMs);
|
|
1967
2017
|
return;
|
|
1968
2018
|
}
|
|
@@ -1993,16 +2043,16 @@ async function processMessageWithStreaming(conversationId, messageId, sessionId,
|
|
|
1993
2043
|
}
|
|
1994
2044
|
}
|
|
1995
2045
|
|
|
1996
|
-
function scheduleRetry(conversationId, messageId, content, agentId) {
|
|
2046
|
+
function scheduleRetry(conversationId, messageId, content, agentId, model) {
|
|
1997
2047
|
debugLog(`[rate-limit] scheduleRetry called for conv ${conversationId}, messageId=${messageId}`);
|
|
1998
|
-
|
|
2048
|
+
|
|
1999
2049
|
if (!content) {
|
|
2000
2050
|
const conv = queries.getConversation(conversationId);
|
|
2001
2051
|
const lastMsg = queries.getLastUserMessage(conversationId);
|
|
2002
2052
|
content = lastMsg?.content || 'continue';
|
|
2003
2053
|
debugLog(`[rate-limit] Recovered content from last message: ${content?.substring?.(0, 50)}...`);
|
|
2004
2054
|
}
|
|
2005
|
-
|
|
2055
|
+
|
|
2006
2056
|
const newSession = queries.createSession(conversationId);
|
|
2007
2057
|
queries.createEvent('session.created', { messageId, sessionId: newSession.id, retryReason: 'rate_limit' }, conversationId, newSession.id);
|
|
2008
2058
|
|
|
@@ -2017,7 +2067,7 @@ function scheduleRetry(conversationId, messageId, content, agentId) {
|
|
|
2017
2067
|
});
|
|
2018
2068
|
|
|
2019
2069
|
debugLog(`[rate-limit] Calling processMessageWithStreaming for retry`);
|
|
2020
|
-
processMessageWithStreaming(conversationId, messageId, newSession.id, content, agentId)
|
|
2070
|
+
processMessageWithStreaming(conversationId, messageId, newSession.id, content, agentId, model)
|
|
2021
2071
|
.catch(err => {
|
|
2022
2072
|
debugLog(`[rate-limit] Retry failed: ${err.message}`);
|
|
2023
2073
|
console.error(`[rate-limit] Retry error for conv ${conversationId}:`, err);
|
|
@@ -2052,7 +2102,7 @@ function drainMessageQueue(conversationId) {
|
|
|
2052
2102
|
timestamp: Date.now()
|
|
2053
2103
|
});
|
|
2054
2104
|
|
|
2055
|
-
processMessageWithStreaming(conversationId, next.messageId, session.id, next.content, next.agentId)
|
|
2105
|
+
processMessageWithStreaming(conversationId, next.messageId, session.id, next.content, next.agentId, next.model)
|
|
2056
2106
|
.catch(err => debugLog(`[queue] Error processing queued message: ${err.message}`));
|
|
2057
2107
|
}
|
|
2058
2108
|
|
|
@@ -2366,7 +2416,7 @@ async function resumeInterruptedStreams() {
|
|
|
2366
2416
|
const messageId = lastMsg?.id || null;
|
|
2367
2417
|
console.log(`[RESUME] Resuming conv ${conv.id} (claude session: ${conv.claudeSessionId})`);
|
|
2368
2418
|
|
|
2369
|
-
processMessageWithStreaming(conv.id, messageId, session.id, promptText, conv.agentType)
|
|
2419
|
+
processMessageWithStreaming(conv.id, messageId, session.id, promptText, conv.agentType, conv.model)
|
|
2370
2420
|
.catch(err => debugLog(`[RESUME] Error resuming conv ${conv.id}: ${err.message}`));
|
|
2371
2421
|
|
|
2372
2422
|
if (i < toResume.length - 1) {
|
package/static/index.html
CHANGED
|
@@ -816,6 +816,28 @@
|
|
|
816
816
|
flex-shrink: 0;
|
|
817
817
|
}
|
|
818
818
|
|
|
819
|
+
.agent-selector:disabled, .model-selector:disabled {
|
|
820
|
+
opacity: 0.5;
|
|
821
|
+
cursor: not-allowed;
|
|
822
|
+
background-color: var(--color-bg-secondary);
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
.model-selector {
|
|
826
|
+
padding: 0.5rem;
|
|
827
|
+
border: none;
|
|
828
|
+
border-radius: 0.375rem;
|
|
829
|
+
background-color: var(--color-bg-secondary);
|
|
830
|
+
color: var(--color-text-primary);
|
|
831
|
+
font-size: 0.8rem;
|
|
832
|
+
cursor: pointer;
|
|
833
|
+
flex-shrink: 0;
|
|
834
|
+
max-width: 160px;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
.model-selector:empty, .model-selector[data-empty="true"] {
|
|
838
|
+
display: none;
|
|
839
|
+
}
|
|
840
|
+
|
|
819
841
|
.message-textarea {
|
|
820
842
|
flex: 1;
|
|
821
843
|
padding: 0.625rem 0.875rem;
|
|
@@ -1005,6 +1027,7 @@
|
|
|
1005
1027
|
.messages-wrapper { padding: 0.375rem 0.5rem; }
|
|
1006
1028
|
.input-section { padding: 0.5rem; padding-bottom: calc(0.5rem + env(safe-area-inset-bottom)); }
|
|
1007
1029
|
.agent-selector { display: none; }
|
|
1030
|
+
.model-selector { display: none; }
|
|
1008
1031
|
}
|
|
1009
1032
|
|
|
1010
1033
|
/* ===== SCROLLBAR STYLING ===== */
|
|
@@ -2456,6 +2479,7 @@
|
|
|
2456
2479
|
<div class="input-section">
|
|
2457
2480
|
<div class="input-wrapper">
|
|
2458
2481
|
<select class="agent-selector" data-agent-selector title="Select agent"></select>
|
|
2482
|
+
<select class="model-selector" data-model-selector title="Select model" data-empty="true"></select>
|
|
2459
2483
|
<textarea
|
|
2460
2484
|
class="message-textarea"
|
|
2461
2485
|
data-message-input
|
package/static/js/client.js
CHANGED
|
@@ -42,9 +42,13 @@ class AgentGUIClient {
|
|
|
42
42
|
statusIndicator: null,
|
|
43
43
|
messageInput: null,
|
|
44
44
|
sendButton: null,
|
|
45
|
-
agentSelector: null
|
|
45
|
+
agentSelector: null,
|
|
46
|
+
modelSelector: null
|
|
46
47
|
};
|
|
47
48
|
|
|
49
|
+
this._agentLocked = false;
|
|
50
|
+
this._modelCache = new Map();
|
|
51
|
+
|
|
48
52
|
this.chunkPollState = {
|
|
49
53
|
isPolling: false,
|
|
50
54
|
lastFetchTimestamp: 0,
|
|
@@ -322,6 +326,15 @@ class AgentGUIClient {
|
|
|
322
326
|
this.ui.messageInput = document.querySelector('[data-message-input]');
|
|
323
327
|
this.ui.sendButton = document.querySelector('[data-send-button]');
|
|
324
328
|
this.ui.agentSelector = document.querySelector('[data-agent-selector]');
|
|
329
|
+
this.ui.modelSelector = document.querySelector('[data-model-selector]');
|
|
330
|
+
|
|
331
|
+
if (this.ui.agentSelector) {
|
|
332
|
+
this.ui.agentSelector.addEventListener('change', () => {
|
|
333
|
+
if (!this._agentLocked) {
|
|
334
|
+
this.loadModelsForAgent(this.ui.agentSelector.value);
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
}
|
|
325
338
|
|
|
326
339
|
// Setup event listeners
|
|
327
340
|
if (this.ui.sendButton) {
|
|
@@ -352,10 +365,15 @@ class AgentGUIClient {
|
|
|
352
365
|
this.setupScrollTracking();
|
|
353
366
|
|
|
354
367
|
window.addEventListener('create-new-conversation', (event) => {
|
|
368
|
+
this.unlockAgentAndModel();
|
|
355
369
|
const detail = event.detail || {};
|
|
356
370
|
this.createNewConversation(detail.workingDirectory, detail.title);
|
|
357
371
|
});
|
|
358
372
|
|
|
373
|
+
window.addEventListener('preparing-new-conversation', () => {
|
|
374
|
+
this.unlockAgentAndModel();
|
|
375
|
+
});
|
|
376
|
+
|
|
359
377
|
// Listen for conversation selection
|
|
360
378
|
window.addEventListener('conversation-selected', (event) => {
|
|
361
379
|
const conversationId = event.detail.conversationId;
|
|
@@ -479,7 +497,7 @@ class AgentGUIClient {
|
|
|
479
497
|
outputEl.innerHTML = `
|
|
480
498
|
<div class="conversation-header">
|
|
481
499
|
<h2>${this.escapeHtml(conv?.title || 'Conversation')}</h2>
|
|
482
|
-
<p class="text-secondary">${conv?.agentType || 'unknown'} - ${new Date(conv?.created_at || Date.now()).toLocaleDateString()}${wdInfo}</p>
|
|
500
|
+
<p class="text-secondary">${conv?.agentType || 'unknown'}${conv?.model ? ' (' + this.escapeHtml(conv.model) + ')' : ''} - ${new Date(conv?.created_at || Date.now()).toLocaleDateString()}${wdInfo}</p>
|
|
483
501
|
</div>
|
|
484
502
|
<div class="conversation-messages"></div>
|
|
485
503
|
`;
|
|
@@ -1098,7 +1116,9 @@ class AgentGUIClient {
|
|
|
1098
1116
|
|
|
1099
1117
|
async startExecution() {
|
|
1100
1118
|
const prompt = this.ui.messageInput?.value || '';
|
|
1101
|
-
const
|
|
1119
|
+
const conv = this.state.currentConversation;
|
|
1120
|
+
const agentId = conv?.agentType || this.ui.agentSelector?.value || 'claude-code';
|
|
1121
|
+
const model = this.ui.modelSelector?.value || null;
|
|
1102
1122
|
|
|
1103
1123
|
if (!prompt.trim()) {
|
|
1104
1124
|
this.showError('Please enter a prompt');
|
|
@@ -1116,24 +1136,27 @@ class AgentGUIClient {
|
|
|
1116
1136
|
this.disableControls();
|
|
1117
1137
|
|
|
1118
1138
|
try {
|
|
1119
|
-
if (
|
|
1120
|
-
await this.streamToConversation(
|
|
1139
|
+
if (conv?.id) {
|
|
1140
|
+
await this.streamToConversation(conv.id, savedPrompt, agentId, model);
|
|
1121
1141
|
this._confirmOptimisticMessage(pendingId);
|
|
1122
1142
|
} else {
|
|
1143
|
+
const body = { agentId, title: savedPrompt.substring(0, 50) };
|
|
1144
|
+
if (model) body.model = model;
|
|
1123
1145
|
const response = await fetch(window.__BASE_URL + '/api/conversations', {
|
|
1124
1146
|
method: 'POST',
|
|
1125
1147
|
headers: { 'Content-Type': 'application/json' },
|
|
1126
|
-
body: JSON.stringify(
|
|
1148
|
+
body: JSON.stringify(body)
|
|
1127
1149
|
});
|
|
1128
1150
|
const { conversation } = await response.json();
|
|
1129
1151
|
this.state.currentConversation = conversation;
|
|
1152
|
+
this.lockAgentAndModel(agentId, model);
|
|
1130
1153
|
|
|
1131
1154
|
if (window.conversationManager) {
|
|
1132
1155
|
window.conversationManager.loadConversations();
|
|
1133
1156
|
window.conversationManager.select(conversation.id);
|
|
1134
1157
|
}
|
|
1135
1158
|
|
|
1136
|
-
await this.streamToConversation(conversation.id, savedPrompt, agentId);
|
|
1159
|
+
await this.streamToConversation(conversation.id, savedPrompt, agentId, model);
|
|
1137
1160
|
this._confirmOptimisticMessage(pendingId);
|
|
1138
1161
|
}
|
|
1139
1162
|
} catch (error) {
|
|
@@ -1361,7 +1384,7 @@ class AgentGUIClient {
|
|
|
1361
1384
|
outputEl.innerHTML = `
|
|
1362
1385
|
<div class="conversation-header">
|
|
1363
1386
|
<h2>${this.escapeHtml(title)}</h2>
|
|
1364
|
-
<p class="text-secondary">${conv?.agentType || 'unknown'} - ${conv ? new Date(conv.created_at).toLocaleDateString() : ''}${wdInfo}</p>
|
|
1387
|
+
<p class="text-secondary">${conv?.agentType || 'unknown'}${conv?.model ? ' (' + this.escapeHtml(conv.model) + ')' : ''} - ${conv ? new Date(conv.created_at).toLocaleDateString() : ''}${wdInfo}</p>
|
|
1365
1388
|
</div>
|
|
1366
1389
|
<div class="conversation-messages">
|
|
1367
1390
|
<div class="skeleton-loading">
|
|
@@ -1374,29 +1397,33 @@ class AgentGUIClient {
|
|
|
1374
1397
|
`;
|
|
1375
1398
|
}
|
|
1376
1399
|
|
|
1377
|
-
async streamToConversation(conversationId, prompt, agentId) {
|
|
1400
|
+
async streamToConversation(conversationId, prompt, agentId, model) {
|
|
1378
1401
|
try {
|
|
1379
1402
|
if (this.wsManager.isConnected) {
|
|
1380
1403
|
this.wsManager.sendMessage({ type: 'subscribe', conversationId });
|
|
1381
1404
|
}
|
|
1382
1405
|
|
|
1406
|
+
const streamBody = { content: prompt, agentId };
|
|
1407
|
+
if (model) streamBody.model = model;
|
|
1383
1408
|
const response = await fetch(`${window.__BASE_URL}/api/conversations/${conversationId}/stream`, {
|
|
1384
1409
|
method: 'POST',
|
|
1385
1410
|
headers: { 'Content-Type': 'application/json' },
|
|
1386
|
-
body: JSON.stringify(
|
|
1411
|
+
body: JSON.stringify(streamBody)
|
|
1387
1412
|
});
|
|
1388
1413
|
|
|
1389
1414
|
if (response.status === 404) {
|
|
1390
1415
|
console.warn('Conversation not found, recreating:', conversationId);
|
|
1391
1416
|
const conv = this.state.currentConversation;
|
|
1417
|
+
const createBody = {
|
|
1418
|
+
agentId,
|
|
1419
|
+
title: conv?.title || prompt.substring(0, 50),
|
|
1420
|
+
workingDirectory: conv?.workingDirectory || null
|
|
1421
|
+
};
|
|
1422
|
+
if (model) createBody.model = model;
|
|
1392
1423
|
const createResp = await fetch(window.__BASE_URL + '/api/conversations', {
|
|
1393
1424
|
method: 'POST',
|
|
1394
1425
|
headers: { 'Content-Type': 'application/json' },
|
|
1395
|
-
body: JSON.stringify(
|
|
1396
|
-
agentId,
|
|
1397
|
-
title: conv?.title || prompt.substring(0, 50),
|
|
1398
|
-
workingDirectory: conv?.workingDirectory || null
|
|
1399
|
-
})
|
|
1426
|
+
body: JSON.stringify(createBody)
|
|
1400
1427
|
});
|
|
1401
1428
|
if (!createResp.ok) throw new Error(`Failed to recreate conversation: HTTP ${createResp.status}`);
|
|
1402
1429
|
const { conversation: newConv } = await createResp.json();
|
|
@@ -1406,7 +1433,7 @@ class AgentGUIClient {
|
|
|
1406
1433
|
window.conversationManager.select(newConv.id);
|
|
1407
1434
|
}
|
|
1408
1435
|
this.updateUrlForConversation(newConv.id);
|
|
1409
|
-
return this.streamToConversation(newConv.id, prompt, agentId);
|
|
1436
|
+
return this.streamToConversation(newConv.id, prompt, agentId, model);
|
|
1410
1437
|
}
|
|
1411
1438
|
|
|
1412
1439
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
@@ -1698,6 +1725,9 @@ class AgentGUIClient {
|
|
|
1698
1725
|
}
|
|
1699
1726
|
|
|
1700
1727
|
window.dispatchEvent(new CustomEvent('agents-loaded', { detail: { agents } }));
|
|
1728
|
+
if (agents.length > 0 && !this._agentLocked) {
|
|
1729
|
+
this.loadModelsForAgent(agents[0].id);
|
|
1730
|
+
}
|
|
1701
1731
|
return agents;
|
|
1702
1732
|
} catch (error) {
|
|
1703
1733
|
console.error('Failed to load agents:', error);
|
|
@@ -1706,6 +1736,61 @@ class AgentGUIClient {
|
|
|
1706
1736
|
});
|
|
1707
1737
|
}
|
|
1708
1738
|
|
|
1739
|
+
async loadModelsForAgent(agentId) {
|
|
1740
|
+
if (!agentId || !this.ui.modelSelector) return;
|
|
1741
|
+
const cached = this._modelCache.get(agentId);
|
|
1742
|
+
if (cached) {
|
|
1743
|
+
this._populateModelSelector(cached);
|
|
1744
|
+
return;
|
|
1745
|
+
}
|
|
1746
|
+
try {
|
|
1747
|
+
const response = await fetch(window.__BASE_URL + `/api/agents/${agentId}/models`);
|
|
1748
|
+
const { models } = await response.json();
|
|
1749
|
+
this._modelCache.set(agentId, models || []);
|
|
1750
|
+
this._populateModelSelector(models || []);
|
|
1751
|
+
} catch (error) {
|
|
1752
|
+
console.error('Failed to load models:', error);
|
|
1753
|
+
this._populateModelSelector([]);
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
_populateModelSelector(models) {
|
|
1758
|
+
if (!this.ui.modelSelector) return;
|
|
1759
|
+
if (!models || models.length === 0) {
|
|
1760
|
+
this.ui.modelSelector.innerHTML = '';
|
|
1761
|
+
this.ui.modelSelector.setAttribute('data-empty', 'true');
|
|
1762
|
+
return;
|
|
1763
|
+
}
|
|
1764
|
+
this.ui.modelSelector.removeAttribute('data-empty');
|
|
1765
|
+
this.ui.modelSelector.innerHTML = models
|
|
1766
|
+
.map(m => `<option value="${m.id}">${this.escapeHtml(m.label)}</option>`)
|
|
1767
|
+
.join('');
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1770
|
+
lockAgentAndModel(agentId, model) {
|
|
1771
|
+
this._agentLocked = true;
|
|
1772
|
+
if (this.ui.agentSelector) {
|
|
1773
|
+
this.ui.agentSelector.value = agentId;
|
|
1774
|
+
this.ui.agentSelector.disabled = true;
|
|
1775
|
+
}
|
|
1776
|
+
this.loadModelsForAgent(agentId).then(() => {
|
|
1777
|
+
if (this.ui.modelSelector) {
|
|
1778
|
+
if (model) this.ui.modelSelector.value = model;
|
|
1779
|
+
this.ui.modelSelector.disabled = true;
|
|
1780
|
+
}
|
|
1781
|
+
});
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
unlockAgentAndModel() {
|
|
1785
|
+
this._agentLocked = false;
|
|
1786
|
+
if (this.ui.agentSelector) {
|
|
1787
|
+
this.ui.agentSelector.disabled = false;
|
|
1788
|
+
}
|
|
1789
|
+
if (this.ui.modelSelector) {
|
|
1790
|
+
this.ui.modelSelector.disabled = false;
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1709
1794
|
/**
|
|
1710
1795
|
* Load conversations
|
|
1711
1796
|
*/
|
|
@@ -1840,9 +1925,11 @@ class AgentGUIClient {
|
|
|
1840
1925
|
async createNewConversation(workingDirectory, title) {
|
|
1841
1926
|
try {
|
|
1842
1927
|
const agentId = this.ui.agentSelector?.value || 'claude-code';
|
|
1928
|
+
const model = this.ui.modelSelector?.value || null;
|
|
1843
1929
|
const convTitle = title || 'New Conversation';
|
|
1844
1930
|
const body = { agentId, title: convTitle };
|
|
1845
1931
|
if (workingDirectory) body.workingDirectory = workingDirectory;
|
|
1932
|
+
if (model) body.model = model;
|
|
1846
1933
|
|
|
1847
1934
|
const response = await fetch(window.__BASE_URL + '/api/conversations', {
|
|
1848
1935
|
method: 'POST',
|
|
@@ -1855,6 +1942,7 @@ class AgentGUIClient {
|
|
|
1855
1942
|
}
|
|
1856
1943
|
|
|
1857
1944
|
const { conversation } = await response.json();
|
|
1945
|
+
this.lockAgentAndModel(agentId, model);
|
|
1858
1946
|
|
|
1859
1947
|
await this.loadConversations();
|
|
1860
1948
|
|
|
@@ -1930,6 +2018,7 @@ class AgentGUIClient {
|
|
|
1930
2018
|
outputEl.appendChild(cached.dom.firstChild);
|
|
1931
2019
|
}
|
|
1932
2020
|
this.state.currentConversation = cached.conversation;
|
|
2021
|
+
this.lockAgentAndModel(cached.conversation.agentType || 'claude-code', cached.conversation.model || null);
|
|
1933
2022
|
this.conversationCache.delete(conversationId);
|
|
1934
2023
|
this.restoreScrollPosition(conversationId);
|
|
1935
2024
|
this.enableControls();
|
|
@@ -1957,6 +2046,7 @@ class AgentGUIClient {
|
|
|
1957
2046
|
const { conversation, isActivelyStreaming, latestSession, chunks: rawChunks, totalChunks, messages: allMessages } = await resp.json();
|
|
1958
2047
|
|
|
1959
2048
|
this.state.currentConversation = conversation;
|
|
2049
|
+
this.lockAgentAndModel(conversation.agentType || 'claude-code', conversation.model || null);
|
|
1960
2050
|
|
|
1961
2051
|
const chunks = (rawChunks || []).map(chunk => ({
|
|
1962
2052
|
...chunk,
|
|
@@ -1975,7 +2065,7 @@ class AgentGUIClient {
|
|
|
1975
2065
|
outputEl.innerHTML = `
|
|
1976
2066
|
<div class="conversation-header">
|
|
1977
2067
|
<h2>${this.escapeHtml(conversation.title || 'Conversation')}</h2>
|
|
1978
|
-
<p class="text-secondary">${conversation.agentType || 'unknown'} - ${new Date(conversation.created_at).toLocaleDateString()}${wdInfo}</p>
|
|
2068
|
+
<p class="text-secondary">${conversation.agentType || 'unknown'}${conversation.model ? ' (' + this.escapeHtml(conversation.model) + ')' : ''} - ${new Date(conversation.created_at).toLocaleDateString()}${wdInfo}</p>
|
|
1979
2069
|
</div>
|
|
1980
2070
|
<div class="conversation-messages"></div>
|
|
1981
2071
|
`;
|
|
@@ -93,6 +93,7 @@ class ConversationManager {
|
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
async openFolderBrowser() {
|
|
96
|
+
window.dispatchEvent(new CustomEvent('preparing-new-conversation'));
|
|
96
97
|
if (!this.folderBrowser.modal) {
|
|
97
98
|
this.createNew();
|
|
98
99
|
return;
|
|
@@ -375,8 +376,9 @@ class ConversationManager {
|
|
|
375
376
|
const title = conv.title || `Conversation ${conv.id.slice(0, 8)}`;
|
|
376
377
|
const timestamp = conv.created_at ? new Date(conv.created_at).toLocaleDateString() : 'Unknown';
|
|
377
378
|
const agent = conv.agentType || 'unknown';
|
|
379
|
+
const modelLabel = conv.model ? ` (${conv.model})` : '';
|
|
378
380
|
const wd = conv.workingDirectory ? conv.workingDirectory.split('/').pop() : '';
|
|
379
|
-
const metaParts = [agent, timestamp];
|
|
381
|
+
const metaParts = [agent + modelLabel, timestamp];
|
|
380
382
|
if (wd) metaParts.push(wd);
|
|
381
383
|
|
|
382
384
|
const titleEl = el.querySelector('.conversation-item-title');
|
|
@@ -402,8 +404,9 @@ class ConversationManager {
|
|
|
402
404
|
const title = conv.title || `Conversation ${conv.id.slice(0, 8)}`;
|
|
403
405
|
const timestamp = conv.created_at ? new Date(conv.created_at).toLocaleDateString() : 'Unknown';
|
|
404
406
|
const agent = conv.agentType || 'unknown';
|
|
407
|
+
const modelLabel = conv.model ? ` (${conv.model})` : '';
|
|
405
408
|
const wd = conv.workingDirectory ? conv.workingDirectory.split('/').pop() : '';
|
|
406
|
-
const metaParts = [agent, timestamp];
|
|
409
|
+
const metaParts = [agent + modelLabel, timestamp];
|
|
407
410
|
if (wd) metaParts.push(wd);
|
|
408
411
|
|
|
409
412
|
const streamingBadge = isStreaming
|
|
@@ -466,6 +469,7 @@ class ConversationManager {
|
|
|
466
469
|
}
|
|
467
470
|
|
|
468
471
|
createNew() {
|
|
472
|
+
window.dispatchEvent(new CustomEvent('preparing-new-conversation'));
|
|
469
473
|
window.dispatchEvent(new CustomEvent('create-new-conversation'));
|
|
470
474
|
}
|
|
471
475
|
|