mixdog 1.3.3 → 1.3.4

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.
@@ -5,7 +5,6 @@ import { getModelMetadataSync } from '../../../../runtime/shared/llm/model-catal
5
5
  import { providerCachedModelsSync } from '../../../../runtime/shared/llm/provider-catalog-cache.mjs';
6
6
 
7
7
  export const CLAUDE_CURRENT_MODE = 'claude-current';
8
- export const CLAUDE_CURRENT_CHOICE_ID = 'claude-current';
9
8
 
10
9
  const DEFAULT_FAMILY_MODEL = Object.freeze({
11
10
  opus: 'claude-opus-4-8',
@@ -20,7 +19,6 @@ const EFFORT_BUDGET = Object.freeze({
20
19
  xhigh: 32768,
21
20
  max: 32768,
22
21
  });
23
- const SNAPSHOT_REFRESH_MS = 30_000;
24
22
 
25
23
  function cleanString(v) {
26
24
  return typeof v === 'string' && v.trim() ? v.trim() : '';
@@ -33,15 +31,6 @@ export function isMixdogModelSelection(value) {
33
31
  return cleanString(raw).toLowerCase().startsWith('mixdog/');
34
32
  }
35
33
 
36
- export function isClaudeNativeModelSelection(route) {
37
- return !!(
38
- route &&
39
- route.provider === 'anthropic-oauth' &&
40
- cleanString(route.model) &&
41
- !isMixdogModelSelection(route)
42
- );
43
- }
44
-
45
34
  function num(v, fallback = null) {
46
35
  if (v === null || v === undefined || v === '') return fallback;
47
36
  const n = Number(v);
@@ -80,23 +69,6 @@ function readFreshJsonFile(file, maxAgeMs) {
80
69
  }
81
70
  }
82
71
 
83
- function snapshotEquivalent(a, b) {
84
- if (!a || !b) return false;
85
- const keys = [
86
- 'mode',
87
- 'source',
88
- 'provider',
89
- 'model',
90
- 'requestedModel',
91
- 'modelDisplay',
92
- 'effort',
93
- 'fast',
94
- 'thinkingBudgetTokens',
95
- 'contextWindow',
96
- ];
97
- return keys.every(k => (a[k] ?? null) === (b[k] ?? null));
98
- }
99
-
100
72
  // The newest roster model of a family, by the roster's own release stamps.
101
73
  function newestRosterModel(family) {
102
74
  const stamp = (row) => Date.parse(row.releaseDate) || (Number(row.created) > 0 ? Number(row.created) : 0);
@@ -298,33 +270,3 @@ export function readClaudeCodeCurrentRoute({ request = null, maxAgeMs = 10 * 60
298
270
  if (route.fast === null || route.fast === undefined) route.fast = false;
299
271
  return route;
300
272
  }
301
-
302
- export function writeClaudeCodeCurrentSnapshot(ccJsonInput) {
303
- if (!ccJsonInput) return null;
304
- let payload = null;
305
- try { payload = JSON.parse(String(ccJsonInput)); } catch { return null; }
306
- const route = routeFromPayload(payload, 'statusline');
307
- if (!route || (!route.model && !route.requestedModel)) return null;
308
- const file = path.join(pluginDataDir(), 'claude-current-model.json');
309
- try {
310
- fs.mkdirSync(path.dirname(file), { recursive: true });
311
- const st = fs.existsSync(file) ? fs.statSync(file) : null;
312
- const current = st ? readJsonFile(file) : null;
313
- if (
314
- st &&
315
- Date.now() - st.mtimeMs < SNAPSHOT_REFRESH_MS &&
316
- snapshotEquivalent(current, route)
317
- ) {
318
- return route;
319
- }
320
- fs.writeFileSync(file, JSON.stringify(route, null, 2) + '\n');
321
- } catch { /* best-effort statusline cache */ }
322
- return route;
323
- }
324
-
325
- export function formatClaudeCurrentChoiceLabel(route = readClaudeCodeCurrentRoute()) {
326
- const parts = [`Current selection: ${route.modelDisplay || route.model || 'current model'}`];
327
- if (route.effort) parts.push(String(route.effort).toUpperCase());
328
- if (route.fast === true) parts.push('fast');
329
- return parts.join(' · ');
330
- }
@@ -9,12 +9,6 @@ import { resolvePluginData } from '../../../../runtime/shared/plugin-paths.mjs';
9
9
  import { readSection } from '../../../../runtime/shared/config.mjs';
10
10
  import { withFileLock, withFileLockSync } from '../../../../runtime/shared/file-lock.mjs';
11
11
  import { computeCostUsd, isInclusiveProvider } from '../../../../runtime/shared/llm/cost.mjs';
12
- import { getModelMetadataSync } from '../../../../runtime/agent/orchestrator/providers/model-catalog.mjs';
13
- import {
14
- estimateMessagesTokens,
15
- estimateRequestReserveTokens,
16
- } from '../../../../runtime/agent/orchestrator/session/context-utils.mjs';
17
- import { CLAUDE_CURRENT_MODE } from './claude-current.mjs';
18
12
 
19
13
  // Legacy store: one JSON document rewritten on every flush. Current store: an
20
14
  // append-only JSONL (one event per line) compacted off the hot path.
@@ -49,10 +43,6 @@ function round(value, digits = 6) {
49
43
  return Math.round(n * scale) / scale;
50
44
  }
51
45
 
52
- function hasOwn(obj, key) {
53
- return !!obj && Object.prototype.hasOwnProperty.call(obj, key);
54
- }
55
-
56
46
  function readJsonFile(file) {
57
47
  try {
58
48
  if (!existsSync(file)) return null;
@@ -78,27 +68,11 @@ function providerSection(provider) {
78
68
  : {};
79
69
  }
80
70
 
81
- function gatewayPresets() {
82
- const agent = agentConfigSection();
83
- return Array.isArray(agent?.presets) ? agent.presets : [];
84
- }
85
-
86
71
  function cleanString(value) {
87
72
  const s = typeof value === 'string' ? value.trim() : '';
88
73
  return s || null;
89
74
  }
90
75
 
91
- function cleanBool(value) {
92
- if (value === true) return true;
93
- if (value === false) return false;
94
- if (typeof value === 'string') {
95
- const s = value.trim().toLowerCase();
96
- if (['1', 'true', 'yes', 'on', 'fast', 'priority', 'ultrafast'].includes(s)) return true;
97
- if (['0', 'false', 'no', 'off', 'none'].includes(s)) return false;
98
- }
99
- return null;
100
- }
101
-
102
76
  function providerKind(provider) {
103
77
  const p = String(provider || '').toLowerCase();
104
78
  if (!p) return 'unknown';
@@ -107,12 +81,6 @@ function providerKind(provider) {
107
81
  return 'api';
108
82
  }
109
83
 
110
- function boundedPercent(value, fallback = null) {
111
- const n = Number(value);
112
- if (Number.isFinite(n) && n > 0 && n <= 100) return n;
113
- return fallback;
114
- }
115
-
116
84
  export function defaultEffectiveContextWindowPercent(_provider) {
117
85
  // Gateway/statusline reports the full model boundary. Trigger headroom is a
118
86
  // Compact policy concern and must not shrink the displayed denominator.
@@ -120,60 +88,6 @@ export function defaultEffectiveContextWindowPercent(_provider) {
120
88
  return 100;
121
89
  }
122
90
 
123
- function effectiveContextWindowPercent(provider, info = {}, seed = {}) {
124
- return boundedPercent(
125
- seed.effectiveContextWindowPercent
126
- ?? seed.effective_context_window_percent
127
- ?? info.effectiveContextWindowPercent
128
- ?? info.effective_context_window_percent,
129
- defaultEffectiveContextWindowPercent(provider),
130
- );
131
- }
132
-
133
- function effectiveContextWindow(rawContextWindow, percent) {
134
- const raw = num(rawContextWindow, 0);
135
- if (!(raw > 0)) return null;
136
- const pct = boundedPercent(percent, 100);
137
- return Math.max(1, Math.floor(raw * pct / 100));
138
- }
139
-
140
- function autoCompactTokenLimit(provider, rawContextWindow, contextWindow, info = {}, seed = {}) {
141
- // routeInfo.autoCompactTokenLimit is an EXPLICIT auto-compaction limit only.
142
- // It must NOT be derived from the effective/raw context window: the runtime
143
- // (manager.mjs resolveSessionContextMeta / loop.mjs resolveWorkerCompactPolicy)
144
- // treats autoCompactTokenLimit as the compaction trigger. Deriving it from the
145
- // full boundary makes autoTriggerTokens == boundary, collapses the compaction
146
- // buffer to 0, and auto-compaction only fires once the window is already full
147
- // (where semantic compact overflows and the turn is lost). The boundary/window
148
- // fallback that status + gateway env still need lives in compactBoundaryForRoute
149
- // / autoCompactWindowForRoute, which read contextWindow/rawContextWindow
150
- // directly. Return null when there is no explicit provider/catalog/seed limit.
151
- const explicit = num(
152
- seed.autoCompactTokenLimit
153
- ?? seed.auto_compact_token_limit
154
- ?? info.autoCompactTokenLimit
155
- ?? info.auto_compact_token_limit,
156
- 0,
157
- );
158
- if (!(explicit > 0)) return null;
159
- // An explicit limit is only a real auto-compaction trigger when it sits
160
- // STRICTLY BELOW the boundary/window. A legacy seed/info value equal to or
161
- // above the boundary is a derived full-window artifact — returning
162
- // Math.min(explicit, derived) would still surface the boundary, which the
163
- // runtime then treats as the trigger and collapses the compaction buffer.
164
- // Drop those to null so display + downstream fall back to the default
165
- // boundary trigger.
166
- const derived = num(contextWindow, 0) || num(rawContextWindow, 0);
167
- if (derived > 0) return explicit < derived ? explicit : null;
168
- // No boundary known: keep the positive explicit value (cannot be proven to
169
- // be a full-window artifact), but never derive one.
170
- return explicit;
171
- }
172
-
173
- // Test-only export of the explicit-vs-derived auto-compact-limit resolver
174
- // (see scripts/compact-trigger-migration-smoke.mjs).
175
- export const _autoCompactTokenLimit = autoCompactTokenLimit;
176
-
177
91
  function routeIdentityKey(routeInfo = {}) {
178
92
  return [
179
93
  routeInfo.provider || '',
@@ -198,196 +112,7 @@ function shouldTrackDollarSpend(routeInfo = {}) {
198
112
  return kind === 'api' || kind === 'quota-api';
199
113
  }
200
114
 
201
- function presetLabel(p) {
202
- return cleanString(p?.name) || cleanString(p?.id) || null;
203
- }
204
-
205
- function findPresetForRoute(provider, model, gateway) {
206
- const presets = gatewayPresets().filter(p => p?.provider === provider && p?.model === model);
207
- if (!presets.length) return null;
208
- if (cleanString(gateway?.mode) === CLAUDE_CURRENT_MODE) {
209
- const effort = cleanString(gateway?.effort ?? gateway?.displayEffort);
210
- const fast = cleanBool(gateway?.fast);
211
- if (effort || fast !== null) {
212
- const exact = presets.find(p =>
213
- (!effort || cleanString(p.effort) === effort) &&
214
- (fast === null || cleanBool(p.fast) === fast)
215
- );
216
- if (exact) return exact;
217
- }
218
- return presets.length === 1 ? presets[0] : null;
219
- }
220
- const presetId = cleanString(gateway?.presetId);
221
- const presetName = cleanString(gateway?.presetName);
222
- if (presetId || presetName) {
223
- const exact = presets.find(p =>
224
- (presetId && p.id === presetId) ||
225
- (presetName && (p.name === presetName || p.id === presetName))
226
- );
227
- if (exact) return exact;
228
- }
229
- const effort = cleanString(gateway?.effort ?? gateway?.displayEffort);
230
- const fast = cleanBool(gateway?.fast);
231
- if (effort || fast !== null) {
232
- const exact = presets.find(p =>
233
- (!effort || cleanString(p.effort) === effort) &&
234
- (fast === null || cleanBool(p.fast) === fast)
235
- );
236
- if (exact) return exact;
237
- }
238
- return presets.length === 1 ? presets[0] : null;
239
- }
240
-
241
- function loadCachedModel(provider, model) {
242
- const files = [
243
- `${provider}-models.json`,
244
- provider === 'openai-oauth' ? 'openai-oauth-models.json' : null,
245
- provider === 'anthropic-oauth' ? 'anthropic-oauth-models.json' : null,
246
- provider === 'grok-oauth' ? 'grok-oauth-models.json' : null,
247
- ].filter(Boolean);
248
- const dataDir = resolvePluginData();
249
- for (const name of files) {
250
- const raw = readJsonFile(join(dataDir, name));
251
- const models = Array.isArray(raw?.models) ? raw.models : Array.isArray(raw) ? raw : [];
252
- const found = models.find(m => (m?.id || m?.name || m?.slug) === model);
253
- if (found) return found;
254
- }
255
- return null;
256
- }
257
-
258
- function modelInfoFromProvider(providerObj, model) {
259
- try {
260
- const info = providerObj?.getCachedModelInfo?.(model);
261
- return info && typeof info === 'object' ? info : null;
262
- } catch {
263
- return null;
264
- }
265
- }
266
-
267
- function displayForModel(provider, model, info) {
268
- const display = cleanString(info?.display) || cleanString(info?.displayName) || cleanString(info?.name);
269
- if (display) return display;
270
- if (provider === 'anthropic-oauth') {
271
- const m = String(model || '').match(/^claude-(opus|sonnet|haiku|fable)-(\d+)(?:-(\d+))?/i);
272
- if (m) return `${m[1][0].toUpperCase()}${m[1].slice(1).toLowerCase()} ${m[2]}${m[3] ? `.${m[3]}` : ''}`;
273
- }
274
- return model || '';
275
- }
276
-
277
- function mergeModelInfo(provider, model, providerObj) {
278
- const fromProvider = modelInfoFromProvider(providerObj, model);
279
- const fromCache = loadCachedModel(provider, model);
280
- const fromCatalog = getModelMetadataSync(model, provider);
281
- const merged = {
282
- ...(fromCatalog || {}),
283
- ...(fromCache || {}),
284
- ...(fromProvider || {}),
285
- };
286
- // Catalog/known metadata is authoritative for the context window. Spreading
287
- // fromCache after fromCatalog otherwise lets a STALE cached window (e.g. Opus
288
- // 4.8 cached at 272k after its window grew to the catalog's 1M) override the
289
- // catalog. Only a live provider API window (context_window / max_context_window)
290
- // outranks the catalog; when the provider info supplies no such window and the
291
- // catalog disagrees with the merged (cached) window, prefer the catalog.
292
- const providerApiWindow = num(
293
- fromProvider?.context_window ?? fromProvider?.max_context_window,
294
- 0,
295
- );
296
- const catalogWindow = num(
297
- fromCatalog?.contextWindow
298
- ?? fromCatalog?.maxContextWindow
299
- ?? fromCatalog?.context_window
300
- ?? fromCatalog?.max_context_window,
301
- 0,
302
- );
303
- const mergedWindow = num(merged?.contextWindow ?? merged?.maxContextWindow, 0);
304
- if (!providerApiWindow && catalogWindow > 0 && mergedWindow !== catalogWindow) {
305
- merged.contextWindow = catalogWindow;
306
- merged.maxContextWindow = catalogWindow;
307
- }
308
- return merged;
309
- }
310
-
311
- export function readGatewayRouteInfo(seed = {}, providerObj = null) {
312
- let gateway = {};
313
- try { gateway = gatewaySection(); } catch { gateway = {}; }
314
- const mode = cleanString(seed.mode) || cleanString(gateway.mode);
315
- const inheritClaudeCurrent = mode === CLAUDE_CURRENT_MODE;
316
- const seedProvider = cleanString(seed.provider ?? seed.defaultProvider);
317
- const seedModel = cleanString(seed.model ?? seed.defaultModel);
318
- const provider = cleanString(process.env.MIXDOG_GATEWAY_PROVIDER)
319
- || seedProvider
320
- || cleanString(gateway.defaultProvider);
321
- const model = cleanString(process.env.MIXDOG_GATEWAY_MODEL)
322
- || seedModel
323
- || cleanString(gateway.defaultModel);
324
- if (!provider || !model) return { provider, model, mode };
325
-
326
- const routeGateway = {
327
- ...gateway,
328
- ...(seed?.presetId ? { presetId: seed.presetId } : {}),
329
- ...(seed?.presetName ? { presetName: seed.presetName } : {}),
330
- ...(seed?.effort || seed?.displayEffort ? { effort: seed.effort ?? seed.displayEffort } : {}),
331
- ...(hasOwn(seed, 'fast') ? { fast: seed.fast } : {}),
332
- };
333
- const seedGateway = inheritClaudeCurrent
334
- ? {
335
- mode,
336
- effort: seed.effort ?? seed.displayEffort ?? gateway.effort ?? gateway.displayEffort,
337
- fast: seed.fast ?? gateway.fast,
338
- }
339
- : routeGateway;
340
- const preset = findPresetForRoute(provider, model, seedGateway);
341
- const effort = cleanString(process.env.MIXDOG_GATEWAY_EFFORT)
342
- || cleanString(seed.effort ?? seed.displayEffort)
343
- || cleanString(gateway.effort ?? gateway.displayEffort)
344
- || cleanString(preset?.effort);
345
- const fast = cleanBool(process.env.MIXDOG_GATEWAY_FAST)
346
- ?? cleanBool(seed.fast)
347
- ?? cleanBool(gateway.fast)
348
- ?? cleanBool(preset?.fast)
349
- ?? false;
350
- const info = mergeModelInfo(provider, model, providerObj);
351
- const rawContextWindow = num(
352
- info?.context_window
353
- ?? info?.max_context_window
354
- ?? info?.contextWindow
355
- ?? info?.maxContextWindow
356
- ?? info?.max_input_tokens,
357
- 0,
358
- )
359
- || num(seed.contextWindow, 0);
360
- const effectivePercent = effectiveContextWindowPercent(provider, info, seed);
361
- const contextWindow = effectiveContextWindow(rawContextWindow, effectivePercent);
362
- const compactLimit = autoCompactTokenLimit(provider, rawContextWindow, contextWindow, info, seed);
363
- const outputTokens = num(info?.outputTokens ?? info?.maxOutputTokens ?? info?.max_output_tokens, 0);
364
-
365
- return {
366
- mode,
367
- provider,
368
- model,
369
- requestedModel: cleanString(seed.requestedModel) || null,
370
- providerKind: providerKind(provider),
371
- modelDisplay: cleanString(seed.modelDisplay) || displayForModel(provider, model, info),
372
- presetId: inheritClaudeCurrent ? cleanString(preset?.id) : cleanString(seed.presetId) || cleanString(gateway.presetId) || cleanString(preset?.id),
373
- presetName: inheritClaudeCurrent ? presetLabel(preset) : cleanString(seed.presetName) || cleanString(gateway.presetName) || presetLabel(preset),
374
- effort,
375
- fast,
376
- thinkingBudgetTokens: num(seed.thinkingBudgetTokens, null),
377
- contextWindow: contextWindow > 0 ? contextWindow : null,
378
- rawContextWindow: rawContextWindow > 0 ? rawContextWindow : null,
379
- effectiveContextWindowPercent: effectivePercent,
380
- autoCompactTokenLimit: compactLimit,
381
- outputTokens: outputTokens > 0 ? outputTokens : null,
382
- inputCostPerM: info?.inputCostPerM ?? null,
383
- outputCostPerM: info?.outputCostPerM ?? null,
384
- cacheReadCostPerM: info?.cacheReadCostPerM ?? null,
385
- cacheWriteCostPerM: info?.cacheWriteCostPerM ?? null,
386
- serviceTier: cleanString(info?.defaultServiceTier) || null,
387
- };
388
- }
389
-
390
- // Boundary denominator for status %, overflow checks, and gateway env window.
115
+ // Boundary denominator for status percentages and usage telemetry.
391
116
  // Order: explicit boundary (routeInfo or compact) → contextWindow → budgetWindow
392
117
  // → rawContextWindow. autoCompactTokenLimit / compactLimitTokens are triggers
393
118
  // only and are never read here.
@@ -414,62 +139,6 @@ export function compactBoundaryDenominator(routeInfo = {}, compact = null) {
414
139
  return rawContextWindow > 0 ? rawContextWindow : 0;
415
140
  }
416
141
 
417
- function compactBoundaryForRoute(routeInfo = {}, compact = null) {
418
- return compactBoundaryDenominator(routeInfo, compact);
419
- }
420
-
421
- // Single source of truth for the value synced to host env
422
- // CLAUDE_CODE_AUTO_COMPACT_WINDOW. BOTH the --enable-time write
423
- // (scripts/gateway-model.mjs) and the runtime sync (src/gateway/server.mjs)
424
- // must derive the window the SAME way, or they disagree on the value written to
425
- // settings.json. Denominator order: compactBoundaryTokens / boundaryTokens (route
426
- // or compact) → contextWindow → budgetWindow → rawContextWindow. Never
427
- // autoCompactTokenLimit or compactLimitTokens (trigger-only). Returns a positive
428
- // integer, or null when unknown.
429
- export function autoCompactWindowForRoute(routeInfo) {
430
- const n = compactBoundaryForRoute(routeInfo);
431
- return Number.isFinite(n) && n > 0 ? Math.floor(n) : null;
432
- }
433
-
434
- export function gatewaySendOptions(routeInfo, extra = {}) {
435
- const opts = {};
436
- if (routeInfo?.thinkingBudgetTokens) opts.thinkingBudgetTokens = routeInfo.thinkingBudgetTokens;
437
- if (routeInfo?.effort) opts.effort = routeInfo.effort;
438
- if (routeInfo?.fast === true) opts.fast = true;
439
- if (routeInfo?.outputTokens) opts.maxOutputTokens = routeInfo.outputTokens;
440
- if (extra?.sessionId) opts.sessionId = extra.sessionId;
441
- return opts;
442
- }
443
-
444
- export function prepareGatewayMessages(messages, tools, routeInfo, _log = () => {}, opts = {}) {
445
- const working = messages;
446
- const contextWindow = num(routeInfo?.contextWindow, 0);
447
- const compactLimit = num(routeInfo?.autoCompactTokenLimit, 0);
448
- const budgetWindow = compactBoundaryForRoute(routeInfo);
449
- const reserveTokens = estimateRequestReserveTokens(tools);
450
- const beforeTokens = estimateMessagesTokens(working) + reserveTokens;
451
- const lastPromptTokens = num(opts?.lastPromptTokens, 0);
452
- const cacheCold = opts?.cacheCold === true;
453
- const afterTokens = estimateMessagesTokens(working) + reserveTokens;
454
- const compact = {
455
- contextWindow: contextWindow > 0 ? contextWindow : null,
456
- compactLimitTokens: compactLimit > 0 ? compactLimit : null,
457
- budgetWindow: budgetWindow > 0 ? budgetWindow : null,
458
- beforeTokens,
459
- afterTokens,
460
- reserveTokens,
461
- lastPromptTokens: lastPromptTokens || null,
462
- cacheCold,
463
- method: 'none',
464
- compacted: false,
465
- };
466
- if (budgetWindow > 0 && afterTokens > budgetWindow) compact.overflow = true;
467
- return {
468
- messages: working,
469
- compact,
470
- };
471
- }
472
-
473
142
  function promptFootprintTokens(provider, usage) {
474
143
  const input = num(usage?.inputTokens, 0);
475
144
  const cacheRead = num(usage?.cachedTokens ?? usage?.cacheReadTokens, 0);
@@ -997,32 +666,3 @@ export function buildGatewayLimits(routeInfo, providerOut = null, usageSnapshot
997
666
  routeSpend: localRouteSpend(routeInfo),
998
667
  };
999
668
  }
1000
-
1001
- export function gatewayAdvertFields(routeInfo, usageSummary = null, limits = null) {
1002
- ensureRouteSection(routeInfo);
1003
- const fields = {
1004
- gateway_route_mode: routeInfo?.mode || null,
1005
- gateway_provider: routeInfo?.provider || null,
1006
- gateway_provider_kind: routeInfo?.providerKind || providerKind(routeInfo?.provider),
1007
- gateway_model: routeInfo?.model || null,
1008
- gateway_model_display: routeInfo?.modelDisplay || routeInfo?.model || null,
1009
- gateway_context_window: routeInfo?.contextWindow || null,
1010
- gateway_raw_context_window: routeInfo?.rawContextWindow || routeInfo?.contextWindow || null,
1011
- gateway_effective_context_window_percent: routeInfo?.effectiveContextWindowPercent || null,
1012
- gateway_auto_compact_token_limit: routeInfo?.autoCompactTokenLimit || null,
1013
- gateway_output_tokens: routeInfo?.outputTokens || null,
1014
- gateway_preset_id: routeInfo?.presetId || null,
1015
- gateway_preset_name: routeInfo?.presetName || null,
1016
- gateway_effort: routeInfo?.effort || null,
1017
- gateway_fast: routeInfo?.fast === true,
1018
- gateway_updated_at: Date.now(),
1019
- };
1020
- if (usageSummary) {
1021
- fields.gateway_context_used_pct = usageSummary.contextUsedPct;
1022
- fields.gateway_last_usage = usageSummary;
1023
- }
1024
- if (limits?.quotaWindows?.length) fields.gateway_quota_windows = limits.quotaWindows;
1025
- if (limits?.balance) fields.gateway_balance = limits.balance;
1026
- if (limits?.routeSpend) fields.gateway_route_spend = limits.routeSpend;
1027
- return fields;
1028
- }
@@ -221,58 +221,3 @@ export function clearGatewaySessionRoute(sessionId, options = {}) {
221
221
  return false;
222
222
  }
223
223
  }
224
-
225
- export function priorGatewaySessionIdForClear(active, newSessionId) {
226
- const sid = normalizeGatewaySessionId(newSessionId);
227
- if (!sid || !active || typeof active !== 'object') return null;
228
- const transcriptPath = cleanString(active.transcriptPath);
229
- const currentBase = transcriptPath ? cleanString(transcriptPath.split(/[\\/]/).pop()?.replace(/\.jsonl$/i, '')) : null;
230
- if (currentBase !== sid) return null;
231
- const priorTranscriptPath = cleanString(active.priorTranscriptPath);
232
- const priorBase = priorTranscriptPath ? cleanString(priorTranscriptPath.split(/[\\/]/).pop()?.replace(/\.jsonl$/i, '')) : null;
233
- const priorSid = normalizeGatewaySessionId(priorBase);
234
- return priorSid && priorSid !== sid ? priorSid : null;
235
- }
236
-
237
- export function gatewayConfigForSession(globalGateway = {}, sessionId = null, options = {}) {
238
- const sessionRoute = readGatewaySessionRoute(sessionId, options);
239
- return sessionRoute ? { ...(globalGateway || {}), ...sessionRoute } : (globalGateway || {});
240
- }
241
-
242
- export function gatewayConfigForSessionBoot(globalGateway = {}, sessionId = null, inheritFromSessionId = null, options = {}) {
243
- const sid = normalizeGatewaySessionId(sessionId);
244
- if (!sid) return globalGateway || {};
245
- const pid = normalizeClientHostPid(options?.clientHostPid);
246
- const routeOptions = pid ? { clientHostPid: pid } : {};
247
- // 1. Session already has a stored route — use it (idempotent).
248
- const sessionRoute = readGatewaySessionRoute(sid, routeOptions);
249
- if (sessionRoute) return { ...(globalGateway || {}), ...sessionRoute };
250
- // 2. No route yet — try to inherit from a prior session (e.g. after /clear)
251
- // before falling back to the global default. This preserves a manually
252
- // pinned route across session boundaries.
253
- if (inheritFromSessionId) {
254
- const priorSid = normalizeGatewaySessionId(inheritFromSessionId);
255
- if (priorSid && priorSid !== sid) {
256
- const priorRoute = readGatewaySessionRoute(priorSid, routeOptions);
257
- if (priorRoute) {
258
- writeGatewaySessionRoute(sid, priorRoute, routeOptions);
259
- return { ...(globalGateway || {}), ...priorRoute };
260
- }
261
- }
262
- }
263
- // 3. If the host session did not provide a reliable prior transcript on /new,
264
- // carry the last explicit route for this terminal only. This keeps multiple
265
- // terminals isolated without falling back to the process-global default.
266
- if (pid) {
267
- const hostRoute = readLatestGatewayHostRoute(pid, { excludeSessionId: sid });
268
- if (hostRoute) {
269
- writeGatewaySessionRoute(sid, hostRoute, routeOptions);
270
- return { ...(globalGateway || {}), ...hostRoute };
271
- }
272
- }
273
- // 4. No prior route to inherit — pin the current global config.
274
- const normalized = normalizeGatewayRouteSection(globalGateway || {});
275
- if (!normalized) return globalGateway || {};
276
- writeGatewaySessionRoute(sid, normalized, routeOptions);
277
- return { ...(globalGateway || {}), ...normalized };
278
- }