@0xmaxma/claude-gateway 1.1.0 → 1.1.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/agent/runner.d.ts +15 -3
- package/dist/agent/runner.d.ts.map +1 -1
- package/dist/agent/runner.js +107 -58
- package/dist/agent/runner.js.map +1 -1
- package/dist/api/router.d.ts.map +1 -1
- package/dist/api/router.js +51 -8
- package/dist/api/router.js.map +1 -1
- package/dist/session/process.d.ts +3 -2
- package/dist/session/process.d.ts.map +1 -1
- package/dist/session/process.js +5 -2
- package/dist/session/process.js.map +1 -1
- package/dist/session/store.d.ts +4 -1
- package/dist/session/store.d.ts.map +1 -1
- package/dist/session/store.js +8 -7
- package/dist/session/store.js.map +1 -1
- package/package.json +1 -1
package/dist/agent/runner.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { EventEmitter } from 'events';
|
|
2
|
-
import { AgentConfig, GatewayConfig, Logger, StreamEvent } from '../types';
|
|
2
|
+
import { AgentConfig, GatewayConfig, Logger, ModelConfig, StreamEvent } from '../types';
|
|
3
3
|
import { type SkillRegistry } from '../skills';
|
|
4
4
|
import { HistoryDB } from '../history/db';
|
|
5
5
|
export declare const MAX_IMAGE_SIZE_BYTES: number;
|
|
6
|
+
export declare const DEFAULT_MODELS: ModelConfig[];
|
|
6
7
|
export declare class AgentRunner extends EventEmitter {
|
|
7
8
|
private readonly agentConfig;
|
|
8
9
|
private readonly gatewayConfig;
|
|
@@ -25,6 +26,7 @@ export declare class AgentRunner extends EventEmitter {
|
|
|
25
26
|
private readonly maxConcurrent;
|
|
26
27
|
private idleCleanerTimer;
|
|
27
28
|
private readonly pendingApiSessions;
|
|
29
|
+
private readonly sessionSpawnLocks;
|
|
28
30
|
private readonly pendingImagePaths;
|
|
29
31
|
private skillRegistry;
|
|
30
32
|
private readonly configPath;
|
|
@@ -56,6 +58,7 @@ export declare class AgentRunner extends EventEmitter {
|
|
|
56
58
|
private resolveMediaPaths;
|
|
57
59
|
private static buildChannelXml;
|
|
58
60
|
private getOrSpawnSession;
|
|
61
|
+
private spawnSession;
|
|
59
62
|
/**
|
|
60
63
|
* Returns true if the message content is a session management command.
|
|
61
64
|
*/
|
|
@@ -150,6 +153,7 @@ export declare class AgentRunner extends EventEmitter {
|
|
|
150
153
|
timeoutMs: number;
|
|
151
154
|
allowTools?: boolean;
|
|
152
155
|
mediaFiles?: string[];
|
|
156
|
+
model?: string;
|
|
153
157
|
}): Promise<string>;
|
|
154
158
|
/**
|
|
155
159
|
* Send a message to an API session and stream back events via callbacks.
|
|
@@ -165,6 +169,7 @@ export declare class AgentRunner extends EventEmitter {
|
|
|
165
169
|
timeoutMs: number;
|
|
166
170
|
allowTools?: boolean;
|
|
167
171
|
mediaFiles?: string[];
|
|
172
|
+
model?: string;
|
|
168
173
|
}): Promise<() => void>;
|
|
169
174
|
/**
|
|
170
175
|
* Check if a session has a pending API request (for preflight conflict check).
|
|
@@ -173,14 +178,21 @@ export declare class AgentRunner extends EventEmitter {
|
|
|
173
178
|
getAgentsBaseDir(): string;
|
|
174
179
|
getAgentDir(): string;
|
|
175
180
|
getHistoryDb(): HistoryDB;
|
|
176
|
-
|
|
181
|
+
getAllSessionMeta(): Promise<Map<string, {
|
|
182
|
+
name: string;
|
|
183
|
+
}>>;
|
|
177
184
|
listSessionsForChat(chatId: string, channel: 'telegram' | 'discord'): Promise<import('../types').SessionIndex>;
|
|
178
185
|
executeApiCommand(sessionId: string, chatId: string, command: string): Promise<Record<string, unknown>>;
|
|
179
186
|
setModel(newModel: string): Promise<void>;
|
|
180
187
|
listApiSessions(chatId: string): Promise<import('../types').SessionIndex>;
|
|
181
188
|
createApiSession(chatId: string, prompt?: string, name?: string): Promise<import('../types').SessionMeta>;
|
|
182
189
|
getApiSessionInfo(chatId: string, sessionId: string): Promise<Record<string, unknown> | null>;
|
|
183
|
-
|
|
190
|
+
updateApiSession(chatId: string, sessionId: string, updates: {
|
|
191
|
+
sessionName?: string;
|
|
192
|
+
}): Promise<{
|
|
193
|
+
sessionId: string;
|
|
194
|
+
sessionName?: string;
|
|
195
|
+
}>;
|
|
184
196
|
deleteApiSession(chatId: string, sessionId: string): Promise<void>;
|
|
185
197
|
/**
|
|
186
198
|
* Send a message into an existing channel session (cross-channel continuation from UI).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/agent/runner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAMtC,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/agent/runner.ts"],"names":[],"mappings":"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"}
|
package/dist/agent/runner.js
CHANGED
|
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.AgentRunner = exports.MAX_IMAGE_SIZE_BYTES = void 0;
|
|
36
|
+
exports.AgentRunner = exports.DEFAULT_MODELS = exports.MAX_IMAGE_SIZE_BYTES = void 0;
|
|
37
37
|
const events_1 = require("events");
|
|
38
38
|
const fs = __importStar(require("fs"));
|
|
39
39
|
const fsPromises = __importStar(require("fs/promises"));
|
|
@@ -54,7 +54,7 @@ const cleanup_1 = require("../history/cleanup");
|
|
|
54
54
|
const DEFAULT_IDLE_TIMEOUT_MINUTES = 30;
|
|
55
55
|
const DEFAULT_MAX_CONCURRENT = 20;
|
|
56
56
|
exports.MAX_IMAGE_SIZE_BYTES = 10 * 1024 * 1024;
|
|
57
|
-
|
|
57
|
+
exports.DEFAULT_MODELS = [
|
|
58
58
|
{ id: 'claude-opus-4-7', label: 'Opus 4.7', alias: 'opus', contextWindow: 1000000 },
|
|
59
59
|
{ id: 'claude-opus-4-6', label: 'Opus 4.6', alias: 'opus46', contextWindow: 1000000 },
|
|
60
60
|
{ id: 'claude-sonnet-4-6', label: 'Sonnet 4.6', alias: 'sonnet', contextWindow: 1000000 },
|
|
@@ -121,6 +121,8 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
121
121
|
this.idleCleanerTimer = null;
|
|
122
122
|
// Tracks session IDs with an in-flight API request (prevents concurrent turns)
|
|
123
123
|
this.pendingApiSessions = new Set();
|
|
124
|
+
// Serialises concurrent getOrSpawnSession calls for the same key to prevent double-spawn
|
|
125
|
+
this.sessionSpawnLocks = new Map();
|
|
124
126
|
// Tracks pending Telegram image paths per chatId (queue) for size accumulation after each turn.
|
|
125
127
|
this.pendingImagePaths = new Map();
|
|
126
128
|
// Skill registry for detecting /skill-name commands in user messages
|
|
@@ -249,6 +251,7 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
249
251
|
this.imageSizePerChat.delete(chatId);
|
|
250
252
|
}
|
|
251
253
|
// Route to session process (map key = chatId, actual sessionId passed separately)
|
|
254
|
+
// Channel sessions use agent-level model (not per-session)
|
|
252
255
|
const session = await this.getOrSpawnSession(chatId, channelSource, sessionId);
|
|
253
256
|
let channelXml = AgentRunner.buildChannelXml(params);
|
|
254
257
|
// Detect skill commands and inject skill content
|
|
@@ -319,42 +322,38 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
319
322
|
}
|
|
320
323
|
if (command === 'set_model') {
|
|
321
324
|
const newModel = typeof body.payload?.model === 'string' ? body.payload.model : '';
|
|
322
|
-
const availableModels = this.gatewayConfig.gateway.models ?? DEFAULT_MODELS;
|
|
323
|
-
const valid = availableModels.find(m => m.id === newModel);
|
|
324
|
-
if (!valid) {
|
|
325
|
-
respond({ success: false, error: 'Unknown model' });
|
|
326
|
-
return;
|
|
327
|
-
}
|
|
328
|
-
// Update in-memory config
|
|
329
|
-
this.agentConfig.claude.model = newModel;
|
|
330
|
-
// Persist to config.json (atomic write)
|
|
331
325
|
try {
|
|
332
|
-
this.
|
|
326
|
+
await this.setModel(newModel);
|
|
333
327
|
}
|
|
334
328
|
catch (err) {
|
|
335
|
-
|
|
329
|
+
respond({ success: false, error: err.message });
|
|
330
|
+
return;
|
|
336
331
|
}
|
|
337
|
-
// Graceful restart
|
|
332
|
+
// Graceful restart ALL channel sessions via signal files so they pick up the new agent-level model.
|
|
333
|
+
// Signal file approach: chokidar watcher in SessionProcess detects the file, reads the notify
|
|
334
|
+
// payload, finishes in-flight work, then restarts — ensuring no mid-response interruption.
|
|
338
335
|
const chatId = body.chat_id ?? '';
|
|
339
336
|
let restarted = false;
|
|
340
|
-
for (const [
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
const signalPath = path.join(this.agentConfig.workspace, '.telegram-state', `restart-${sessionId}`);
|
|
349
|
-
try {
|
|
350
|
-
fs.mkdirSync(path.dirname(signalPath), { recursive: true });
|
|
351
|
-
fs.writeFileSync(signalPath, notifyPayload);
|
|
352
|
-
}
|
|
353
|
-
catch (err) {
|
|
354
|
-
this.logger.error('Failed to write restart signal', {
|
|
355
|
-
sessionId,
|
|
356
|
-
error: err.message,
|
|
337
|
+
for (const [key, session] of this.sessions) {
|
|
338
|
+
// Only restart channel sessions (not API sessions which use per-session model)
|
|
339
|
+
if (session.source !== 'api') {
|
|
340
|
+
restarted = true;
|
|
341
|
+
const channel = this.channelSourceMap.get(key) ?? 'telegram';
|
|
342
|
+
const stateDir = channel === 'discord' ? '.discord-state' : '.telegram-state';
|
|
343
|
+
const signalPayload = JSON.stringify({
|
|
344
|
+
notify: { chat_id: chatId, text: `✅ Model changed to ${newModel}` },
|
|
357
345
|
});
|
|
346
|
+
const signalPath = path.join(this.agentConfig.workspace, stateDir, `restart-${key}`);
|
|
347
|
+
try {
|
|
348
|
+
fs.mkdirSync(path.dirname(signalPath), { recursive: true });
|
|
349
|
+
fs.writeFileSync(signalPath, signalPayload);
|
|
350
|
+
}
|
|
351
|
+
catch (err) {
|
|
352
|
+
this.logger.error('Failed to write restart signal for set_model', {
|
|
353
|
+
sessionKey: key,
|
|
354
|
+
error: err.message,
|
|
355
|
+
});
|
|
356
|
+
}
|
|
358
357
|
}
|
|
359
358
|
}
|
|
360
359
|
respond({ success: true, model: newModel, restarted });
|
|
@@ -414,8 +413,9 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
414
413
|
if (!meta) {
|
|
415
414
|
return respond({ success: true, text: 'No active session found.' });
|
|
416
415
|
}
|
|
417
|
-
const
|
|
418
|
-
const
|
|
416
|
+
const effectiveModel = this.agentConfig.claude.model;
|
|
417
|
+
const availableModels = this.gatewayConfig.gateway.models ?? exports.DEFAULT_MODELS;
|
|
418
|
+
const modelConfig = availableModels.find(m => m.id === effectiveModel);
|
|
419
419
|
const contextWindow = modelConfig?.contextWindow ?? 200000;
|
|
420
420
|
const contextTokens = meta.lastInputTokens ?? 0;
|
|
421
421
|
const usedPct = Math.round((contextTokens / contextWindow) * 100);
|
|
@@ -560,10 +560,45 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
560
560
|
`ts="${meta['ts'] ?? new Date().toISOString()}"${optionalAttrs}>${repliedBlock}${params.content ?? ''}</channel>`);
|
|
561
561
|
}
|
|
562
562
|
async getOrSpawnSession(mapKey, // Map lookup key (chatId for telegram/discord, sessionId for API)
|
|
563
|
-
source, sessionId)
|
|
563
|
+
source, sessionId, // actual session UUID (only for channel sessions; equals mapKey for API)
|
|
564
|
+
modelOverride) {
|
|
565
|
+
// If a spawn is already in progress for this key, wait for it instead of spawning a second one
|
|
566
|
+
const pending = this.sessionSpawnLocks.get(mapKey);
|
|
567
|
+
if (pending)
|
|
568
|
+
return pending;
|
|
564
569
|
const existing = this.sessions.get(mapKey);
|
|
565
|
-
if (existing)
|
|
566
|
-
|
|
570
|
+
if (existing) {
|
|
571
|
+
// If model changed, restart the session with the new model
|
|
572
|
+
if (modelOverride && existing.modelOverride !== modelOverride) {
|
|
573
|
+
this.logger.info('Model changed, restarting session', { mapKey, oldModel: existing.modelOverride, newModel: modelOverride });
|
|
574
|
+
const respawn = (async () => {
|
|
575
|
+
await existing.stop();
|
|
576
|
+
this.sessions.delete(mapKey);
|
|
577
|
+
return this.spawnSession(mapKey, source, sessionId, modelOverride);
|
|
578
|
+
})();
|
|
579
|
+
this.sessionSpawnLocks.set(mapKey, respawn);
|
|
580
|
+
try {
|
|
581
|
+
return await respawn;
|
|
582
|
+
}
|
|
583
|
+
finally {
|
|
584
|
+
this.sessionSpawnLocks.delete(mapKey);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
else {
|
|
588
|
+
return existing;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
// No existing session — acquire lock for the spawn path
|
|
592
|
+
const spawnPromise = this.spawnSession(mapKey, source, sessionId, modelOverride);
|
|
593
|
+
this.sessionSpawnLocks.set(mapKey, spawnPromise);
|
|
594
|
+
try {
|
|
595
|
+
return await spawnPromise;
|
|
596
|
+
}
|
|
597
|
+
finally {
|
|
598
|
+
this.sessionSpawnLocks.delete(mapKey);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
async spawnSession(mapKey, source, sessionId, modelOverride) {
|
|
567
602
|
// Evict oldest idle session if at capacity
|
|
568
603
|
if (this.sessions.size >= this.maxConcurrent) {
|
|
569
604
|
const sorted = [...this.sessions.entries()].sort(([, a], [, b]) => a.lastActivityAt - b.lastActivityAt);
|
|
@@ -580,6 +615,9 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
580
615
|
const actualSessionId = sessionId ?? mapKey;
|
|
581
616
|
const chatId = source !== 'api' ? mapKey : undefined;
|
|
582
617
|
const proc = new process_1.SessionProcess(actualSessionId, source, this.agentConfig, this.gatewayConfig, this.sessionStore, chatId);
|
|
618
|
+
// Apply per-session model override if provided
|
|
619
|
+
if (modelOverride)
|
|
620
|
+
proc.modelOverride = modelOverride;
|
|
583
621
|
await proc.start();
|
|
584
622
|
// Forward all session output lines so listeners on AgentRunner (GatewayRouter,
|
|
585
623
|
// CronScheduler, tests) receive them without needing individual session references.
|
|
@@ -822,8 +860,9 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
822
860
|
this.writeAutoForward(chatId, 'No active session found.');
|
|
823
861
|
return;
|
|
824
862
|
}
|
|
825
|
-
const
|
|
826
|
-
const
|
|
863
|
+
const effectiveModel = this.agentConfig.claude.model;
|
|
864
|
+
const availableModels = this.gatewayConfig.gateway.models ?? exports.DEFAULT_MODELS;
|
|
865
|
+
const modelConfig = availableModels.find(m => m.id === effectiveModel);
|
|
827
866
|
const contextWindow = modelConfig?.contextWindow ?? 200000;
|
|
828
867
|
const contextTokens = meta.lastInputTokens ?? 0;
|
|
829
868
|
const usedPct = Math.round((contextTokens / contextWindow) * 100);
|
|
@@ -916,13 +955,14 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
916
955
|
const index = await this.sessionStore.listSessions(agentId, chatId, ch);
|
|
917
956
|
const meta = index.sessions.find(s => s.id === sessionId);
|
|
918
957
|
const name = meta?.name ?? 'Session';
|
|
919
|
-
const
|
|
920
|
-
const
|
|
958
|
+
const compactModel = this.agentConfig.claude.model;
|
|
959
|
+
const availableModels = this.gatewayConfig.gateway.models ?? exports.DEFAULT_MODELS;
|
|
960
|
+
const modelConfig = availableModels.find(m => m.id === compactModel);
|
|
921
961
|
const contextWindow = modelConfig?.contextWindow ?? 200000;
|
|
922
962
|
this.writeAutoForward(chatId, `⏳ Compacting session "${name}"...`);
|
|
923
963
|
try {
|
|
924
964
|
const compactor = new compactor_1.SessionCompactor(this.sessionStore);
|
|
925
|
-
const result = await compactor.compact(agentId, chatId, sessionId,
|
|
965
|
+
const result = await compactor.compact(agentId, chatId, sessionId, compactModel, contextWindow, ch);
|
|
926
966
|
await this.sessionStore.updateSessionMeta(agentId, chatId, sessionId, {
|
|
927
967
|
loadedAtSpawn: undefined,
|
|
928
968
|
archivedCount: undefined,
|
|
@@ -1172,12 +1212,13 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
1172
1212
|
const err = Object.assign(new Error(`Session ${sessionId} already has a pending request`), { code: 'CONFLICT' });
|
|
1173
1213
|
throw err;
|
|
1174
1214
|
}
|
|
1175
|
-
const session = await this.getOrSpawnSession(sessionId, 'api');
|
|
1176
1215
|
// Register session in api-{chatId} index.json on first use
|
|
1177
1216
|
const internalChatIdForSession = `api-${chatId}`;
|
|
1178
1217
|
await this.sessionStore.ensureApiSession(this.agentConfig.id, internalChatIdForSession, sessionId).catch((err) => {
|
|
1179
1218
|
this.logger.warn('Failed to register API session in index', { agentId: this.agentConfig.id, chatId, sessionId, error: err.message });
|
|
1180
1219
|
});
|
|
1220
|
+
// Use model from request body if provided, otherwise use agent default
|
|
1221
|
+
const session = await this.getOrSpawnSession(sessionId, 'api', undefined, opts.model);
|
|
1181
1222
|
// Promote UI-uploaded files from staging to permanent per-session storage
|
|
1182
1223
|
const finalMediaFiles = opts.mediaFiles?.length
|
|
1183
1224
|
? await promoteUiUploads(this.agentsBaseDir, this.agentConfig.id, sessionId, opts.mediaFiles, this.logger)
|
|
@@ -1333,12 +1374,13 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
1333
1374
|
const err = Object.assign(new Error(`Session ${sessionId} already has a pending request`), { code: 'CONFLICT' });
|
|
1334
1375
|
throw err;
|
|
1335
1376
|
}
|
|
1336
|
-
const session = await this.getOrSpawnSession(sessionId, 'api');
|
|
1337
1377
|
// Register session in api-{chatId} index.json on first use
|
|
1338
1378
|
const internalChatIdStream = `api-${chatId}`;
|
|
1339
1379
|
await this.sessionStore.ensureApiSession(this.agentConfig.id, internalChatIdStream, sessionId).catch((err) => {
|
|
1340
1380
|
this.logger.warn('Failed to register API session in index', { agentId: this.agentConfig.id, chatId, sessionId, error: err.message });
|
|
1341
1381
|
});
|
|
1382
|
+
// Use model from request body if provided, otherwise use agent default
|
|
1383
|
+
const session = await this.getOrSpawnSession(sessionId, 'api', undefined, opts.model);
|
|
1342
1384
|
// Promote UI-uploaded files from staging to permanent per-session storage
|
|
1343
1385
|
const finalMediaFilesStream = opts.mediaFiles?.length
|
|
1344
1386
|
? await promoteUiUploads(this.agentsBaseDir, this.agentConfig.id, sessionId, opts.mediaFiles, this.logger)
|
|
@@ -1538,8 +1580,8 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
1538
1580
|
getHistoryDb() {
|
|
1539
1581
|
return this.historyDb;
|
|
1540
1582
|
}
|
|
1541
|
-
|
|
1542
|
-
return this.sessionStore.
|
|
1583
|
+
getAllSessionMeta() {
|
|
1584
|
+
return this.sessionStore.getAllSessionMeta(this.agentConfig.id);
|
|
1543
1585
|
}
|
|
1544
1586
|
async listSessionsForChat(chatId, channel) {
|
|
1545
1587
|
return this.sessionStore.listSessions(this.agentConfig.id, chatId, channel);
|
|
@@ -1562,10 +1604,11 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
1562
1604
|
if (command === '/session') {
|
|
1563
1605
|
const index = await this.sessionStore.listSessions(agentId, internalChatId, 'api').catch(() => null);
|
|
1564
1606
|
const meta = index?.sessions.find((s) => s.id === sessionId);
|
|
1607
|
+
const effectiveModel = this.agentConfig.claude.model;
|
|
1565
1608
|
if (!meta)
|
|
1566
|
-
return { sessionId, sessionName: null, messageCount: 0, archivedCount: 0, contextUsedPct: 0, model:
|
|
1567
|
-
const availableModels = this.gatewayConfig.gateway.models ?? DEFAULT_MODELS;
|
|
1568
|
-
const modelConfig = availableModels.find((m) => m.id ===
|
|
1609
|
+
return { sessionId, sessionName: null, messageCount: 0, archivedCount: 0, contextUsedPct: 0, model: effectiveModel };
|
|
1610
|
+
const availableModels = this.gatewayConfig.gateway.models ?? exports.DEFAULT_MODELS;
|
|
1611
|
+
const modelConfig = availableModels.find((m) => m.id === effectiveModel);
|
|
1569
1612
|
const contextWindow = modelConfig?.contextWindow ?? 200000;
|
|
1570
1613
|
const contextUsedPct = Math.round(((meta.lastInputTokens ?? 0) / contextWindow) * 100);
|
|
1571
1614
|
return {
|
|
@@ -1574,7 +1617,7 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
1574
1617
|
messageCount: meta.messageCount,
|
|
1575
1618
|
archivedCount: meta.archivedCount ?? 0,
|
|
1576
1619
|
contextUsedPct,
|
|
1577
|
-
model:
|
|
1620
|
+
model: effectiveModel,
|
|
1578
1621
|
};
|
|
1579
1622
|
}
|
|
1580
1623
|
if (command === '/clear') {
|
|
@@ -1594,11 +1637,12 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
1594
1637
|
}
|
|
1595
1638
|
if (command === '/compact') {
|
|
1596
1639
|
const ch = 'api';
|
|
1597
|
-
const
|
|
1598
|
-
const
|
|
1640
|
+
const compactEffectiveModel = this.agentConfig.claude.model;
|
|
1641
|
+
const availableModels = this.gatewayConfig.gateway.models ?? exports.DEFAULT_MODELS;
|
|
1642
|
+
const modelConfig = availableModels.find((m) => m.id === compactEffectiveModel);
|
|
1599
1643
|
const contextWindow = modelConfig?.contextWindow ?? 200000;
|
|
1600
1644
|
const compactor = new compactor_1.SessionCompactor(this.sessionStore);
|
|
1601
|
-
const result = await compactor.compact(agentId, internalChatId, sessionId,
|
|
1645
|
+
const result = await compactor.compact(agentId, internalChatId, sessionId, compactEffectiveModel, contextWindow, ch);
|
|
1602
1646
|
await this.sessionStore.updateSessionMeta(agentId, internalChatId, sessionId, {
|
|
1603
1647
|
loadedAtSpawn: undefined,
|
|
1604
1648
|
archivedCount: undefined,
|
|
@@ -1610,7 +1654,7 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
1610
1654
|
throw new Error(`Unknown command: ${command}`);
|
|
1611
1655
|
}
|
|
1612
1656
|
async setModel(newModel) {
|
|
1613
|
-
const availableModels = this.gatewayConfig.gateway.models ?? DEFAULT_MODELS;
|
|
1657
|
+
const availableModels = this.gatewayConfig.gateway.models ?? exports.DEFAULT_MODELS;
|
|
1614
1658
|
if (!availableModels.find((m) => m.id === newModel)) {
|
|
1615
1659
|
throw Object.assign(new Error(`Unknown model: ${newModel}`), { code: 'UNKNOWN_MODEL' });
|
|
1616
1660
|
}
|
|
@@ -1647,8 +1691,9 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
1647
1691
|
const meta = index?.sessions.find((s) => s.id === sessionId);
|
|
1648
1692
|
if (!meta)
|
|
1649
1693
|
return null;
|
|
1650
|
-
const
|
|
1651
|
-
const
|
|
1694
|
+
const effectiveModel = this.agentConfig.claude.model;
|
|
1695
|
+
const availableModels = this.gatewayConfig.gateway.models ?? exports.DEFAULT_MODELS;
|
|
1696
|
+
const modelConfig = availableModels.find((m) => m.id === effectiveModel);
|
|
1652
1697
|
const contextWindow = modelConfig?.contextWindow ?? 200000;
|
|
1653
1698
|
const contextUsedPct = Math.round(((meta.lastInputTokens ?? 0) / contextWindow) * 100);
|
|
1654
1699
|
return {
|
|
@@ -1657,11 +1702,14 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
1657
1702
|
messageCount: meta.messageCount,
|
|
1658
1703
|
archivedCount: meta.archivedCount ?? 0,
|
|
1659
1704
|
contextUsedPct,
|
|
1660
|
-
model:
|
|
1705
|
+
model: effectiveModel,
|
|
1661
1706
|
};
|
|
1662
1707
|
}
|
|
1663
|
-
async
|
|
1664
|
-
|
|
1708
|
+
async updateApiSession(chatId, sessionId, updates) {
|
|
1709
|
+
if (updates.sessionName) {
|
|
1710
|
+
await this.sessionStore.updateSessionMeta(this.agentConfig.id, `api-${chatId}`, sessionId, { name: updates.sessionName }, 'api');
|
|
1711
|
+
}
|
|
1712
|
+
return { sessionId, ...(updates.sessionName ? { sessionName: updates.sessionName } : {}) };
|
|
1665
1713
|
}
|
|
1666
1714
|
async deleteApiSession(chatId, sessionId) {
|
|
1667
1715
|
await this.sessionStore.deleteTelegramSession(this.agentConfig.id, `api-${chatId}`, sessionId, 'api');
|
|
@@ -1674,6 +1722,7 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
1674
1722
|
async sendMessageToSession(rawChatId, channel, sessionId, message, senderName, callbacks, opts) {
|
|
1675
1723
|
// Ensure the session process uses the correct channel source
|
|
1676
1724
|
this.channelSourceMap.set(rawChatId, channel);
|
|
1725
|
+
// Channel sessions use agent-level model (not per-session)
|
|
1677
1726
|
const session = await this.getOrSpawnSession(rawChatId, channel, sessionId);
|
|
1678
1727
|
// Persist user message (Layer 1 session JSON + Layer 2 history DB)
|
|
1679
1728
|
const uiUserTs = Date.now();
|