@0xmaxma/claude-gateway 1.1.0 → 1.1.2
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 +113 -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,51 @@ 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
|
+
// Normalize: treat agent-default model as "no override" so default sessions
|
|
566
|
+
// don't restart unnecessarily when the frontend explicitly sends the default model.
|
|
567
|
+
const agentDefaultModel = this.agentConfig.claude.model;
|
|
568
|
+
const effectiveOverride = (modelOverride && modelOverride !== agentDefaultModel)
|
|
569
|
+
? modelOverride
|
|
570
|
+
: undefined;
|
|
571
|
+
// If a spawn is already in progress for this key, wait for it instead of spawning a second one
|
|
572
|
+
const pending = this.sessionSpawnLocks.get(mapKey);
|
|
573
|
+
if (pending)
|
|
574
|
+
return pending;
|
|
564
575
|
const existing = this.sessions.get(mapKey);
|
|
565
|
-
if (existing)
|
|
566
|
-
|
|
576
|
+
if (existing) {
|
|
577
|
+
// Restart if model changed (including switching back to the agent default)
|
|
578
|
+
if (existing.modelOverride !== effectiveOverride) {
|
|
579
|
+
this.logger.info('Model changed, restarting session', { mapKey, oldModel: existing.modelOverride, newModel: effectiveOverride ?? agentDefaultModel });
|
|
580
|
+
const respawn = (async () => {
|
|
581
|
+
await existing.stop();
|
|
582
|
+
this.sessions.delete(mapKey);
|
|
583
|
+
return this.spawnSession(mapKey, source, sessionId, effectiveOverride);
|
|
584
|
+
})();
|
|
585
|
+
this.sessionSpawnLocks.set(mapKey, respawn);
|
|
586
|
+
try {
|
|
587
|
+
return await respawn;
|
|
588
|
+
}
|
|
589
|
+
finally {
|
|
590
|
+
this.sessionSpawnLocks.delete(mapKey);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
else {
|
|
594
|
+
return existing;
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
// No existing session — acquire lock for the spawn path
|
|
598
|
+
const spawnPromise = this.spawnSession(mapKey, source, sessionId, effectiveOverride);
|
|
599
|
+
this.sessionSpawnLocks.set(mapKey, spawnPromise);
|
|
600
|
+
try {
|
|
601
|
+
return await spawnPromise;
|
|
602
|
+
}
|
|
603
|
+
finally {
|
|
604
|
+
this.sessionSpawnLocks.delete(mapKey);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
async spawnSession(mapKey, source, sessionId, modelOverride) {
|
|
567
608
|
// Evict oldest idle session if at capacity
|
|
568
609
|
if (this.sessions.size >= this.maxConcurrent) {
|
|
569
610
|
const sorted = [...this.sessions.entries()].sort(([, a], [, b]) => a.lastActivityAt - b.lastActivityAt);
|
|
@@ -580,6 +621,9 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
580
621
|
const actualSessionId = sessionId ?? mapKey;
|
|
581
622
|
const chatId = source !== 'api' ? mapKey : undefined;
|
|
582
623
|
const proc = new process_1.SessionProcess(actualSessionId, source, this.agentConfig, this.gatewayConfig, this.sessionStore, chatId);
|
|
624
|
+
// Apply per-session model override (caller already normalized to undefined when == agent default)
|
|
625
|
+
if (modelOverride)
|
|
626
|
+
proc.modelOverride = modelOverride;
|
|
583
627
|
await proc.start();
|
|
584
628
|
// Forward all session output lines so listeners on AgentRunner (GatewayRouter,
|
|
585
629
|
// CronScheduler, tests) receive them without needing individual session references.
|
|
@@ -822,8 +866,9 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
822
866
|
this.writeAutoForward(chatId, 'No active session found.');
|
|
823
867
|
return;
|
|
824
868
|
}
|
|
825
|
-
const
|
|
826
|
-
const
|
|
869
|
+
const effectiveModel = this.agentConfig.claude.model;
|
|
870
|
+
const availableModels = this.gatewayConfig.gateway.models ?? exports.DEFAULT_MODELS;
|
|
871
|
+
const modelConfig = availableModels.find(m => m.id === effectiveModel);
|
|
827
872
|
const contextWindow = modelConfig?.contextWindow ?? 200000;
|
|
828
873
|
const contextTokens = meta.lastInputTokens ?? 0;
|
|
829
874
|
const usedPct = Math.round((contextTokens / contextWindow) * 100);
|
|
@@ -916,13 +961,14 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
916
961
|
const index = await this.sessionStore.listSessions(agentId, chatId, ch);
|
|
917
962
|
const meta = index.sessions.find(s => s.id === sessionId);
|
|
918
963
|
const name = meta?.name ?? 'Session';
|
|
919
|
-
const
|
|
920
|
-
const
|
|
964
|
+
const compactModel = this.agentConfig.claude.model;
|
|
965
|
+
const availableModels = this.gatewayConfig.gateway.models ?? exports.DEFAULT_MODELS;
|
|
966
|
+
const modelConfig = availableModels.find(m => m.id === compactModel);
|
|
921
967
|
const contextWindow = modelConfig?.contextWindow ?? 200000;
|
|
922
968
|
this.writeAutoForward(chatId, `⏳ Compacting session "${name}"...`);
|
|
923
969
|
try {
|
|
924
970
|
const compactor = new compactor_1.SessionCompactor(this.sessionStore);
|
|
925
|
-
const result = await compactor.compact(agentId, chatId, sessionId,
|
|
971
|
+
const result = await compactor.compact(agentId, chatId, sessionId, compactModel, contextWindow, ch);
|
|
926
972
|
await this.sessionStore.updateSessionMeta(agentId, chatId, sessionId, {
|
|
927
973
|
loadedAtSpawn: undefined,
|
|
928
974
|
archivedCount: undefined,
|
|
@@ -1172,12 +1218,13 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
1172
1218
|
const err = Object.assign(new Error(`Session ${sessionId} already has a pending request`), { code: 'CONFLICT' });
|
|
1173
1219
|
throw err;
|
|
1174
1220
|
}
|
|
1175
|
-
const session = await this.getOrSpawnSession(sessionId, 'api');
|
|
1176
1221
|
// Register session in api-{chatId} index.json on first use
|
|
1177
1222
|
const internalChatIdForSession = `api-${chatId}`;
|
|
1178
1223
|
await this.sessionStore.ensureApiSession(this.agentConfig.id, internalChatIdForSession, sessionId).catch((err) => {
|
|
1179
1224
|
this.logger.warn('Failed to register API session in index', { agentId: this.agentConfig.id, chatId, sessionId, error: err.message });
|
|
1180
1225
|
});
|
|
1226
|
+
// Use model from request body if provided, otherwise use agent default
|
|
1227
|
+
const session = await this.getOrSpawnSession(sessionId, 'api', undefined, opts.model);
|
|
1181
1228
|
// Promote UI-uploaded files from staging to permanent per-session storage
|
|
1182
1229
|
const finalMediaFiles = opts.mediaFiles?.length
|
|
1183
1230
|
? await promoteUiUploads(this.agentsBaseDir, this.agentConfig.id, sessionId, opts.mediaFiles, this.logger)
|
|
@@ -1333,12 +1380,13 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
1333
1380
|
const err = Object.assign(new Error(`Session ${sessionId} already has a pending request`), { code: 'CONFLICT' });
|
|
1334
1381
|
throw err;
|
|
1335
1382
|
}
|
|
1336
|
-
const session = await this.getOrSpawnSession(sessionId, 'api');
|
|
1337
1383
|
// Register session in api-{chatId} index.json on first use
|
|
1338
1384
|
const internalChatIdStream = `api-${chatId}`;
|
|
1339
1385
|
await this.sessionStore.ensureApiSession(this.agentConfig.id, internalChatIdStream, sessionId).catch((err) => {
|
|
1340
1386
|
this.logger.warn('Failed to register API session in index', { agentId: this.agentConfig.id, chatId, sessionId, error: err.message });
|
|
1341
1387
|
});
|
|
1388
|
+
// Use model from request body if provided, otherwise use agent default
|
|
1389
|
+
const session = await this.getOrSpawnSession(sessionId, 'api', undefined, opts.model);
|
|
1342
1390
|
// Promote UI-uploaded files from staging to permanent per-session storage
|
|
1343
1391
|
const finalMediaFilesStream = opts.mediaFiles?.length
|
|
1344
1392
|
? await promoteUiUploads(this.agentsBaseDir, this.agentConfig.id, sessionId, opts.mediaFiles, this.logger)
|
|
@@ -1538,8 +1586,8 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
1538
1586
|
getHistoryDb() {
|
|
1539
1587
|
return this.historyDb;
|
|
1540
1588
|
}
|
|
1541
|
-
|
|
1542
|
-
return this.sessionStore.
|
|
1589
|
+
getAllSessionMeta() {
|
|
1590
|
+
return this.sessionStore.getAllSessionMeta(this.agentConfig.id);
|
|
1543
1591
|
}
|
|
1544
1592
|
async listSessionsForChat(chatId, channel) {
|
|
1545
1593
|
return this.sessionStore.listSessions(this.agentConfig.id, chatId, channel);
|
|
@@ -1562,10 +1610,11 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
1562
1610
|
if (command === '/session') {
|
|
1563
1611
|
const index = await this.sessionStore.listSessions(agentId, internalChatId, 'api').catch(() => null);
|
|
1564
1612
|
const meta = index?.sessions.find((s) => s.id === sessionId);
|
|
1613
|
+
const effectiveModel = this.agentConfig.claude.model;
|
|
1565
1614
|
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 ===
|
|
1615
|
+
return { sessionId, sessionName: null, messageCount: 0, archivedCount: 0, contextUsedPct: 0, model: effectiveModel };
|
|
1616
|
+
const availableModels = this.gatewayConfig.gateway.models ?? exports.DEFAULT_MODELS;
|
|
1617
|
+
const modelConfig = availableModels.find((m) => m.id === effectiveModel);
|
|
1569
1618
|
const contextWindow = modelConfig?.contextWindow ?? 200000;
|
|
1570
1619
|
const contextUsedPct = Math.round(((meta.lastInputTokens ?? 0) / contextWindow) * 100);
|
|
1571
1620
|
return {
|
|
@@ -1574,7 +1623,7 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
1574
1623
|
messageCount: meta.messageCount,
|
|
1575
1624
|
archivedCount: meta.archivedCount ?? 0,
|
|
1576
1625
|
contextUsedPct,
|
|
1577
|
-
model:
|
|
1626
|
+
model: effectiveModel,
|
|
1578
1627
|
};
|
|
1579
1628
|
}
|
|
1580
1629
|
if (command === '/clear') {
|
|
@@ -1594,11 +1643,12 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
1594
1643
|
}
|
|
1595
1644
|
if (command === '/compact') {
|
|
1596
1645
|
const ch = 'api';
|
|
1597
|
-
const
|
|
1598
|
-
const
|
|
1646
|
+
const compactEffectiveModel = this.agentConfig.claude.model;
|
|
1647
|
+
const availableModels = this.gatewayConfig.gateway.models ?? exports.DEFAULT_MODELS;
|
|
1648
|
+
const modelConfig = availableModels.find((m) => m.id === compactEffectiveModel);
|
|
1599
1649
|
const contextWindow = modelConfig?.contextWindow ?? 200000;
|
|
1600
1650
|
const compactor = new compactor_1.SessionCompactor(this.sessionStore);
|
|
1601
|
-
const result = await compactor.compact(agentId, internalChatId, sessionId,
|
|
1651
|
+
const result = await compactor.compact(agentId, internalChatId, sessionId, compactEffectiveModel, contextWindow, ch);
|
|
1602
1652
|
await this.sessionStore.updateSessionMeta(agentId, internalChatId, sessionId, {
|
|
1603
1653
|
loadedAtSpawn: undefined,
|
|
1604
1654
|
archivedCount: undefined,
|
|
@@ -1610,7 +1660,7 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
1610
1660
|
throw new Error(`Unknown command: ${command}`);
|
|
1611
1661
|
}
|
|
1612
1662
|
async setModel(newModel) {
|
|
1613
|
-
const availableModels = this.gatewayConfig.gateway.models ?? DEFAULT_MODELS;
|
|
1663
|
+
const availableModels = this.gatewayConfig.gateway.models ?? exports.DEFAULT_MODELS;
|
|
1614
1664
|
if (!availableModels.find((m) => m.id === newModel)) {
|
|
1615
1665
|
throw Object.assign(new Error(`Unknown model: ${newModel}`), { code: 'UNKNOWN_MODEL' });
|
|
1616
1666
|
}
|
|
@@ -1647,8 +1697,9 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
1647
1697
|
const meta = index?.sessions.find((s) => s.id === sessionId);
|
|
1648
1698
|
if (!meta)
|
|
1649
1699
|
return null;
|
|
1650
|
-
const
|
|
1651
|
-
const
|
|
1700
|
+
const effectiveModel = this.agentConfig.claude.model;
|
|
1701
|
+
const availableModels = this.gatewayConfig.gateway.models ?? exports.DEFAULT_MODELS;
|
|
1702
|
+
const modelConfig = availableModels.find((m) => m.id === effectiveModel);
|
|
1652
1703
|
const contextWindow = modelConfig?.contextWindow ?? 200000;
|
|
1653
1704
|
const contextUsedPct = Math.round(((meta.lastInputTokens ?? 0) / contextWindow) * 100);
|
|
1654
1705
|
return {
|
|
@@ -1657,11 +1708,14 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
1657
1708
|
messageCount: meta.messageCount,
|
|
1658
1709
|
archivedCount: meta.archivedCount ?? 0,
|
|
1659
1710
|
contextUsedPct,
|
|
1660
|
-
model:
|
|
1711
|
+
model: effectiveModel,
|
|
1661
1712
|
};
|
|
1662
1713
|
}
|
|
1663
|
-
async
|
|
1664
|
-
|
|
1714
|
+
async updateApiSession(chatId, sessionId, updates) {
|
|
1715
|
+
if (updates.sessionName) {
|
|
1716
|
+
await this.sessionStore.updateSessionMeta(this.agentConfig.id, `api-${chatId}`, sessionId, { name: updates.sessionName }, 'api');
|
|
1717
|
+
}
|
|
1718
|
+
return { sessionId, ...(updates.sessionName ? { sessionName: updates.sessionName } : {}) };
|
|
1665
1719
|
}
|
|
1666
1720
|
async deleteApiSession(chatId, sessionId) {
|
|
1667
1721
|
await this.sessionStore.deleteTelegramSession(this.agentConfig.id, `api-${chatId}`, sessionId, 'api');
|
|
@@ -1674,6 +1728,7 @@ class AgentRunner extends events_1.EventEmitter {
|
|
|
1674
1728
|
async sendMessageToSession(rawChatId, channel, sessionId, message, senderName, callbacks, opts) {
|
|
1675
1729
|
// Ensure the session process uses the correct channel source
|
|
1676
1730
|
this.channelSourceMap.set(rawChatId, channel);
|
|
1731
|
+
// Channel sessions use agent-level model (not per-session)
|
|
1677
1732
|
const session = await this.getOrSpawnSession(rawChatId, channel, sessionId);
|
|
1678
1733
|
// Persist user message (Layer 1 session JSON + Layer 2 history DB)
|
|
1679
1734
|
const uiUserTs = Date.now();
|