@link-assistant/agent 0.26.2 → 0.26.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.
- package/package.json +1 -1
- package/src/auth/claude-oauth.ts +11 -2
- package/src/cli/model-config.js +100 -15
- package/src/cli/run-options.js +4 -3
- package/src/config/defaults.ts +32 -0
- package/src/config/file-config.ts +16 -1
- package/src/provider/models.ts +26 -0
- package/src/provider/provider.ts +15 -5
- package/src/session/compaction.ts +4 -1
- package/src/session/summary.ts +21 -4
package/package.json
CHANGED
package/src/auth/claude-oauth.ts
CHANGED
|
@@ -57,8 +57,17 @@ export namespace ClaudeOAuth {
|
|
|
57
57
|
refreshToken: z.string(),
|
|
58
58
|
expiresAt: z.number(),
|
|
59
59
|
scopes: z.array(z.string()).optional(),
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
// Claude Code writes null when subscription metadata is unavailable.
|
|
61
|
+
// Normalize that JSON representation to the same value as an omitted
|
|
62
|
+
// optional field while keeping every other non-string value invalid.
|
|
63
|
+
subscriptionType: z
|
|
64
|
+
.string()
|
|
65
|
+
.nullish()
|
|
66
|
+
.transform((value) => value ?? undefined),
|
|
67
|
+
rateLimitTier: z
|
|
68
|
+
.string()
|
|
69
|
+
.nullish()
|
|
70
|
+
.transform((value) => value ?? undefined),
|
|
62
71
|
})
|
|
63
72
|
.optional(),
|
|
64
73
|
});
|
package/src/cli/model-config.js
CHANGED
|
@@ -12,7 +12,9 @@ import {
|
|
|
12
12
|
getDefaultModelParts,
|
|
13
13
|
getDefaultModelSource,
|
|
14
14
|
getDefaultCompactionModel,
|
|
15
|
+
getDefaultCompactionModelSource,
|
|
15
16
|
getDefaultCompactionModels,
|
|
17
|
+
getDefaultCompactionModelsSource,
|
|
16
18
|
getDefaultCompactionSafetyMarginPercent,
|
|
17
19
|
} from './defaults.ts';
|
|
18
20
|
import { outputModelResolved } from './model-resolution.ts';
|
|
@@ -356,6 +358,64 @@ async function resolveCompactionModelEntry(
|
|
|
356
358
|
};
|
|
357
359
|
}
|
|
358
360
|
|
|
361
|
+
/**
|
|
362
|
+
* Narrow a built-in compaction default to the provider the run was pointed at.
|
|
363
|
+
*
|
|
364
|
+
* The shipped cascade names OpenCode models, because the shipped base model is
|
|
365
|
+
* an OpenCode one. A run launched with `--model formalai/formal-ai` inherited
|
|
366
|
+
* that cascade unchanged, so compaction — and the session summary, which reuses
|
|
367
|
+
* the compaction model — called a provider the operator may hold no credentials
|
|
368
|
+
* for. Every one of those calls fails, and the failures read as the run having
|
|
369
|
+
* failed.
|
|
370
|
+
*
|
|
371
|
+
* Only the *built-in* defaults are narrowed, and only for a run pointed away
|
|
372
|
+
* from the built-in default provider. A cascade the operator named
|
|
373
|
+
* (`--compaction-model`, `--compaction-models`, or the
|
|
374
|
+
* `LINK_ASSISTANT_AGENT_DEFAULT_COMPACTION_MODEL*` env vars) is an explicit
|
|
375
|
+
* choice and is left alone, cross-provider or not; and a run on the shipped
|
|
376
|
+
* provider keeps the shipped cascade, whose extra entries are the rate-limit
|
|
377
|
+
* fallbacks it was written for.
|
|
378
|
+
*
|
|
379
|
+
* @param {{providerID: string, modelID: string, useSameModel: boolean}[]} entries
|
|
380
|
+
* @param {string} baseProviderID Provider of the model that runs the turns
|
|
381
|
+
* @param {string} baseModelID Model that runs the turns
|
|
382
|
+
* @returns {{providerID: string, modelID: string, useSameModel: boolean}[]}
|
|
383
|
+
* @see https://github.com/link-assistant/agent/issues/307
|
|
384
|
+
*/
|
|
385
|
+
function inheritBaseProvider(entries, baseProviderID, baseModelID) {
|
|
386
|
+
const kept = entries.filter(
|
|
387
|
+
(entry) => entry.useSameModel || entry.providerID === baseProviderID
|
|
388
|
+
);
|
|
389
|
+
const dropped = entries.filter((entry) => !kept.includes(entry));
|
|
390
|
+
if (dropped.length === 0) {
|
|
391
|
+
return entries;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const inherited =
|
|
395
|
+
kept.length > 0
|
|
396
|
+
? kept
|
|
397
|
+
: [
|
|
398
|
+
{
|
|
399
|
+
providerID: baseProviderID,
|
|
400
|
+
modelID: baseModelID,
|
|
401
|
+
useSameModel: true,
|
|
402
|
+
},
|
|
403
|
+
];
|
|
404
|
+
|
|
405
|
+
Log.Default.info(() => ({
|
|
406
|
+
message: 'default compaction models narrowed to the configured provider',
|
|
407
|
+
hint: 'Secondary calls (compaction, session summary) inherit --model unless a compaction model is named explicitly',
|
|
408
|
+
baseProviderID,
|
|
409
|
+
baseModelID,
|
|
410
|
+
dropped: dropped.map((entry) => `${entry.providerID}/${entry.modelID}`),
|
|
411
|
+
kept: inherited.map((entry) =>
|
|
412
|
+
entry.useSameModel ? 'same' : `${entry.providerID}/${entry.modelID}`
|
|
413
|
+
),
|
|
414
|
+
}));
|
|
415
|
+
|
|
416
|
+
return inherited;
|
|
417
|
+
}
|
|
418
|
+
|
|
359
419
|
/**
|
|
360
420
|
* Parse compaction model config from argv.
|
|
361
421
|
* Supports both --compaction-model (single) and --compaction-models (cascade).
|
|
@@ -363,6 +423,7 @@ async function resolveCompactionModelEntry(
|
|
|
363
423
|
* The special value "same" means use the base model for compaction.
|
|
364
424
|
* @see https://github.com/link-assistant/agent/issues/219
|
|
365
425
|
* @see https://github.com/link-assistant/agent/issues/232
|
|
426
|
+
* @see https://github.com/link-assistant/agent/issues/307
|
|
366
427
|
*/
|
|
367
428
|
async function parseCompactionModelConfig(
|
|
368
429
|
argv,
|
|
@@ -425,9 +486,18 @@ async function parseCompactionModelConfig(
|
|
|
425
486
|
}
|
|
426
487
|
}
|
|
427
488
|
|
|
489
|
+
// A cascade nobody asked for must not reach across providers (#307).
|
|
490
|
+
const usingBuiltInCascade =
|
|
491
|
+
compactionModelsSource === 'default' &&
|
|
492
|
+
getDefaultCompactionModelsSource(defaultOptions) === 'default' &&
|
|
493
|
+
baseProviderID !== DEFAULT_PROVIDER_ID;
|
|
494
|
+
const cascade = usingBuiltInCascade
|
|
495
|
+
? inheritBaseProvider(compactionModels, baseProviderID, baseModelID)
|
|
496
|
+
: compactionModels;
|
|
497
|
+
|
|
428
498
|
Log.Default.info(() => ({
|
|
429
499
|
message: 'compaction models cascade configured',
|
|
430
|
-
models:
|
|
500
|
+
models: cascade.map((m) =>
|
|
431
501
|
m.useSameModel ? 'same' : `${m.providerID}/${m.modelID}`
|
|
432
502
|
),
|
|
433
503
|
source: compactionModelsSource,
|
|
@@ -435,7 +505,7 @@ async function parseCompactionModelConfig(
|
|
|
435
505
|
|
|
436
506
|
// Use the first model as the primary compaction model (for backward compatibility)
|
|
437
507
|
// The full cascade is stored in compactionModels array
|
|
438
|
-
const primary =
|
|
508
|
+
const primary = cascade[0] || {
|
|
439
509
|
providerID: baseProviderID,
|
|
440
510
|
modelID: baseModelID,
|
|
441
511
|
useSameModel: true,
|
|
@@ -446,16 +516,21 @@ async function parseCompactionModelConfig(
|
|
|
446
516
|
modelID: primary.modelID,
|
|
447
517
|
useSameModel: primary.useSameModel,
|
|
448
518
|
compactionSafetyMarginPercent,
|
|
449
|
-
compactionModels,
|
|
519
|
+
compactionModels: cascade,
|
|
450
520
|
};
|
|
451
521
|
}
|
|
452
522
|
|
|
453
523
|
// Fallback to single --compaction-model
|
|
454
524
|
const cliCompactionModelArg = getCompactionModelFromProcessArgv();
|
|
525
|
+
const defaultCompactionModel = getDefaultCompactionModel(defaultOptions);
|
|
526
|
+
const compactionModelSource =
|
|
527
|
+
cliCompactionModelArg ||
|
|
528
|
+
(argv['compaction-model'] &&
|
|
529
|
+
argv['compaction-model'] !== defaultCompactionModel)
|
|
530
|
+
? 'cli'
|
|
531
|
+
: 'default';
|
|
455
532
|
const compactionModelArg =
|
|
456
|
-
cliCompactionModelArg ??
|
|
457
|
-
argv['compaction-model'] ??
|
|
458
|
-
getDefaultCompactionModel(defaultOptions);
|
|
533
|
+
cliCompactionModelArg ?? argv['compaction-model'] ?? defaultCompactionModel;
|
|
459
534
|
|
|
460
535
|
const resolved = await resolveCompactionModelEntry(
|
|
461
536
|
compactionModelArg,
|
|
@@ -463,23 +538,33 @@ async function parseCompactionModelConfig(
|
|
|
463
538
|
baseModelID
|
|
464
539
|
);
|
|
465
540
|
|
|
541
|
+
// Same rule as the cascade: an unrequested default follows --model (#307).
|
|
542
|
+
const usingBuiltInCompactionModel =
|
|
543
|
+
compactionModelSource === 'default' &&
|
|
544
|
+
getDefaultCompactionModelSource(defaultOptions) === 'default' &&
|
|
545
|
+
baseProviderID !== DEFAULT_PROVIDER_ID;
|
|
546
|
+
const [entry] = usingBuiltInCompactionModel
|
|
547
|
+
? inheritBaseProvider([resolved], baseProviderID, baseModelID)
|
|
548
|
+
: [resolved];
|
|
549
|
+
|
|
466
550
|
Log.Default.info(() => ({
|
|
467
551
|
message: 'using single compaction model',
|
|
468
|
-
compactionProviderID:
|
|
469
|
-
compactionModelID:
|
|
470
|
-
useSameModel:
|
|
552
|
+
compactionProviderID: entry.providerID,
|
|
553
|
+
compactionModelID: entry.modelID,
|
|
554
|
+
useSameModel: entry.useSameModel,
|
|
555
|
+
source: compactionModelSource,
|
|
471
556
|
}));
|
|
472
557
|
|
|
473
558
|
return {
|
|
474
|
-
providerID:
|
|
475
|
-
modelID:
|
|
476
|
-
useSameModel:
|
|
559
|
+
providerID: entry.providerID,
|
|
560
|
+
modelID: entry.modelID,
|
|
561
|
+
useSameModel: entry.useSameModel,
|
|
477
562
|
compactionSafetyMarginPercent,
|
|
478
563
|
compactionModels: [
|
|
479
564
|
{
|
|
480
|
-
providerID:
|
|
481
|
-
modelID:
|
|
482
|
-
useSameModel:
|
|
565
|
+
providerID: entry.providerID,
|
|
566
|
+
modelID: entry.modelID,
|
|
567
|
+
useSameModel: entry.useSameModel,
|
|
483
568
|
},
|
|
484
569
|
],
|
|
485
570
|
};
|
package/src/cli/run-options.js
CHANGED
|
@@ -184,13 +184,13 @@ export function buildRunOptions(yargs, defaultOptions = {}) {
|
|
|
184
184
|
.option('summarize-session', {
|
|
185
185
|
type: 'boolean',
|
|
186
186
|
description:
|
|
187
|
-
'Generate AI session summaries (default: true). Use --no-summarize-session to disable.',
|
|
187
|
+
'Generate AI session summaries (default: true). Use --no-summarize-session to disable. Automatically off for a model declared as having no context limit (`unlimited: true` or `limit.context: null` in the provider config).',
|
|
188
188
|
default: true,
|
|
189
189
|
})
|
|
190
190
|
.option('compaction-model', {
|
|
191
191
|
type: 'string',
|
|
192
192
|
description:
|
|
193
|
-
'Model to use for context compaction in format providerID/modelID. Use "same" to use the base model. Default: opencode/gpt-5-nano (free, 400K context). Overridden by --compaction-models if both are specified.',
|
|
193
|
+
'Model to use for context compaction and session summaries, in format providerID/modelID. Use "same" to use the base model. Default: opencode/gpt-5-nano (free, 400K context), which falls back to the --model model when --model points at another provider. Overridden by --compaction-models if both are specified.',
|
|
194
194
|
default: defaultCompactionModel,
|
|
195
195
|
})
|
|
196
196
|
.option('compaction-models', {
|
|
@@ -198,7 +198,8 @@ export function buildRunOptions(yargs, defaultOptions = {}) {
|
|
|
198
198
|
description:
|
|
199
199
|
'Ordered cascade of compaction models in links notation sequence format: "(model1 model2 ... same)". ' +
|
|
200
200
|
"Models are tried from smallest/cheapest context to largest. If used context exceeds a model's limit or its rate limit is reached, the next model is tried. " +
|
|
201
|
-
'The special value "same" uses the base model. Overrides --compaction-model when specified.'
|
|
201
|
+
'The special value "same" uses the base model. Overrides --compaction-model when specified. ' +
|
|
202
|
+
'The default cascade is OpenCode-specific: when --model points at another provider, its entries from other providers are dropped so secondary calls inherit --model. A cascade given explicitly is used as written.',
|
|
202
203
|
default: defaultCompactionModels,
|
|
203
204
|
})
|
|
204
205
|
.option('compaction-safety-margin', {
|
package/src/config/defaults.ts
CHANGED
|
@@ -142,6 +142,24 @@ export function getDefaultCompactionModel(
|
|
|
142
142
|
);
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
+
/**
|
|
146
|
+
* Report where the effective default compaction model came from.
|
|
147
|
+
*
|
|
148
|
+
* Same contract as `getDefaultModelSource`: `'config'` when an operator set
|
|
149
|
+
* `defaultOptions.defaultCompactionModel` or
|
|
150
|
+
* `LINK_ASSISTANT_AGENT_DEFAULT_COMPACTION_MODEL`, `'default'` when the
|
|
151
|
+
* built-in `DEFAULT_COMPACTION_MODEL` applies. Only a built-in default may be
|
|
152
|
+
* narrowed to the configured model's provider (#307).
|
|
153
|
+
*/
|
|
154
|
+
export function getDefaultCompactionModelSource(
|
|
155
|
+
options: DefaultConfigOptions = {}
|
|
156
|
+
): 'config' | 'default' {
|
|
157
|
+
const env = options.env ?? process.env;
|
|
158
|
+
if (optionString(options.defaultCompactionModel)) return 'config';
|
|
159
|
+
if (envString(env, DEFAULT_COMPACTION_MODEL_ENV)) return 'config';
|
|
160
|
+
return 'default';
|
|
161
|
+
}
|
|
162
|
+
|
|
145
163
|
export function getDefaultCompactionModels(
|
|
146
164
|
options: DefaultConfigOptions = {}
|
|
147
165
|
): string {
|
|
@@ -153,6 +171,20 @@ export function getDefaultCompactionModels(
|
|
|
153
171
|
);
|
|
154
172
|
}
|
|
155
173
|
|
|
174
|
+
/**
|
|
175
|
+
* Report where the effective default compaction cascade came from.
|
|
176
|
+
*
|
|
177
|
+
* @see getDefaultCompactionModelSource
|
|
178
|
+
*/
|
|
179
|
+
export function getDefaultCompactionModelsSource(
|
|
180
|
+
options: DefaultConfigOptions = {}
|
|
181
|
+
): 'config' | 'default' {
|
|
182
|
+
const env = options.env ?? process.env;
|
|
183
|
+
if (optionString(options.defaultCompactionModels)) return 'config';
|
|
184
|
+
if (envString(env, DEFAULT_COMPACTION_MODELS_ENV)) return 'config';
|
|
185
|
+
return 'default';
|
|
186
|
+
}
|
|
187
|
+
|
|
156
188
|
export function getDefaultCompactionSafetyMarginPercent(
|
|
157
189
|
options: DefaultConfigOptions = {}
|
|
158
190
|
): number {
|
|
@@ -834,7 +834,22 @@ export namespace Config {
|
|
|
834
834
|
ModelsDev.Provider.partial()
|
|
835
835
|
.extend({
|
|
836
836
|
models: z
|
|
837
|
-
.record(
|
|
837
|
+
.record(
|
|
838
|
+
z.string(),
|
|
839
|
+
ModelsDev.Model.partial().extend({
|
|
840
|
+
// `context: null` states that the model has no context
|
|
841
|
+
// window to run out of, which turns off compaction and
|
|
842
|
+
// session summarization for it (#307). `output` is
|
|
843
|
+
// optional here so `{ "context": null }` alone is a valid
|
|
844
|
+
// override.
|
|
845
|
+
limit: z
|
|
846
|
+
.object({
|
|
847
|
+
context: z.number().nullable().optional(),
|
|
848
|
+
output: z.number().optional(),
|
|
849
|
+
})
|
|
850
|
+
.optional(),
|
|
851
|
+
})
|
|
852
|
+
)
|
|
838
853
|
.optional(),
|
|
839
854
|
options: z
|
|
840
855
|
.object({
|
package/src/provider/models.ts
CHANGED
|
@@ -42,6 +42,12 @@ export namespace ModelsDev {
|
|
|
42
42
|
context: z.number(),
|
|
43
43
|
output: z.number(),
|
|
44
44
|
}),
|
|
45
|
+
unlimited: z
|
|
46
|
+
.boolean()
|
|
47
|
+
.optional()
|
|
48
|
+
.describe(
|
|
49
|
+
'Model has no context limit. Compaction and session summarization are skipped for it. Also set by writing `limit.context: null` in a provider model override.'
|
|
50
|
+
),
|
|
45
51
|
modalities: z
|
|
46
52
|
.object({
|
|
47
53
|
input: z.array(z.enum(['text', 'audio', 'image', 'video', 'pdf'])),
|
|
@@ -59,6 +65,26 @@ export namespace ModelsDev {
|
|
|
59
65
|
});
|
|
60
66
|
export type Model = z.infer<typeof Model>;
|
|
61
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Whether a model declares that it has no context window to run out of.
|
|
70
|
+
*
|
|
71
|
+
* An operator states this per model in the config, either as
|
|
72
|
+
* `"unlimited": true` or as `"limit": { "context": null }` — the two are
|
|
73
|
+
* normalized to the same flag when the config is merged into the model
|
|
74
|
+
* database.
|
|
75
|
+
*
|
|
76
|
+
* Compaction and session summarization both exist to keep a conversation
|
|
77
|
+
* inside a context window. For a model without one they have no work to do,
|
|
78
|
+
* so they are skipped instead of spending a secondary API call per turn.
|
|
79
|
+
*
|
|
80
|
+
* @see https://github.com/link-assistant/agent/issues/307
|
|
81
|
+
*/
|
|
82
|
+
export function hasUnlimitedContext(
|
|
83
|
+
model: { unlimited?: boolean } | undefined | null
|
|
84
|
+
): boolean {
|
|
85
|
+
return model?.unlimited === true;
|
|
86
|
+
}
|
|
87
|
+
|
|
62
88
|
export const Provider = z
|
|
63
89
|
.object({
|
|
64
90
|
api: z.string().optional(),
|
package/src/provider/provider.ts
CHANGED
|
@@ -1138,11 +1138,21 @@ export namespace Provider {
|
|
|
1138
1138
|
...existing?.options,
|
|
1139
1139
|
...model.options,
|
|
1140
1140
|
},
|
|
1141
|
-
limit:
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1141
|
+
limit: iife(() => {
|
|
1142
|
+
const limit = model.limit ?? existing?.limit;
|
|
1143
|
+
if (!limit) return { context: 0, output: 0 };
|
|
1144
|
+
// `context: null` is the config spelling of "no context limit";
|
|
1145
|
+
// the arithmetic downstream keeps reading 0 as "no window to
|
|
1146
|
+
// check", and the capability itself travels in `unlimited` (#307).
|
|
1147
|
+
return {
|
|
1148
|
+
context: limit.context ?? 0,
|
|
1149
|
+
output: limit.output ?? existing?.limit?.output ?? 0,
|
|
1150
|
+
};
|
|
1151
|
+
}),
|
|
1152
|
+
unlimited:
|
|
1153
|
+
model.unlimited ??
|
|
1154
|
+
(model.limit && model.limit.context === null ? true : undefined) ??
|
|
1155
|
+
existing?.unlimited,
|
|
1146
1156
|
modalities: model.modalities ??
|
|
1147
1157
|
existing?.modalities ?? {
|
|
1148
1158
|
input: ['text'],
|
|
@@ -7,7 +7,7 @@ import { MessageV2 } from './message-v2';
|
|
|
7
7
|
import { SystemPrompt } from './system';
|
|
8
8
|
import { Bus } from '../bus';
|
|
9
9
|
import z from 'zod';
|
|
10
|
-
import
|
|
10
|
+
import { ModelsDev } from '../provider/models';
|
|
11
11
|
import { SessionPrompt } from './prompt';
|
|
12
12
|
import { config, isVerbose } from '../config/config';
|
|
13
13
|
import { Token } from '../util/token';
|
|
@@ -135,6 +135,8 @@ export namespace SessionCompaction {
|
|
|
135
135
|
estimatedInputTokens?: number;
|
|
136
136
|
}) {
|
|
137
137
|
if (config.disableAutocompact) return false;
|
|
138
|
+
// A model without a context window can never overflow it (#307).
|
|
139
|
+
if (ModelsDev.hasUnlimitedContext(input.model)) return false;
|
|
138
140
|
const baseModelContextLimit = input.model.limit.context;
|
|
139
141
|
if (baseModelContextLimit === 0) return false;
|
|
140
142
|
const providerCount =
|
|
@@ -193,6 +195,7 @@ export namespace SessionCompaction {
|
|
|
193
195
|
compactionModel?: CompactionModelConfig;
|
|
194
196
|
compactionModelContextLimit?: number;
|
|
195
197
|
}): MessageV2.ContextDiagnostics | undefined {
|
|
198
|
+
if (ModelsDev.hasUnlimitedContext(input.model)) return undefined;
|
|
196
199
|
const contextLimit = input.model.limit.context;
|
|
197
200
|
if (contextLimit === 0) return undefined;
|
|
198
201
|
const outputLimit =
|
package/src/session/summary.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { Instance } from '../project/instance';
|
|
|
14
14
|
import { Storage } from '../storage/storage';
|
|
15
15
|
import { Bus } from '../bus';
|
|
16
16
|
import { config, isVerbose } from '../config/config';
|
|
17
|
+
import { ModelsDev } from '../provider/models';
|
|
17
18
|
import { Token } from '../util/token';
|
|
18
19
|
|
|
19
20
|
export namespace SessionSummary {
|
|
@@ -112,6 +113,25 @@ export namespace SessionSummary {
|
|
|
112
113
|
const assistantMsg = messages.find((m) => m.info.role === 'assistant')!
|
|
113
114
|
.info as MessageV2.Assistant;
|
|
114
115
|
|
|
116
|
+
// Summarization keeps a conversation inside a context window. A model that
|
|
117
|
+
// declares it has none has nothing to summarize for, so the secondary call
|
|
118
|
+
// is skipped rather than spent — an integrator running such a model no
|
|
119
|
+
// longer has to pass --no-summarize-session.
|
|
120
|
+
// See: https://github.com/link-assistant/agent/issues/307
|
|
121
|
+
const baseModel = await Provider.getModel(
|
|
122
|
+
assistantMsg.providerID,
|
|
123
|
+
assistantMsg.modelID
|
|
124
|
+
).catch(() => null);
|
|
125
|
+
if (baseModel && ModelsDev.hasUnlimitedContext(baseModel.info)) {
|
|
126
|
+
log.info(() => ({
|
|
127
|
+
message: 'session summarization skipped for an unlimited-context model',
|
|
128
|
+
hint: 'The model declares `unlimited: true` (or `limit.context: null`); the diff stat is still recorded',
|
|
129
|
+
providerID: assistantMsg.providerID,
|
|
130
|
+
modelID: assistantMsg.modelID,
|
|
131
|
+
}));
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
115
135
|
// Use the compaction model (--compaction-model, e.g. gpt-5-nano) for summarization
|
|
116
136
|
// to avoid doubling rate-limit pressure on the main model.
|
|
117
137
|
// If the compaction model is unavailable, fall back to the main model.
|
|
@@ -145,10 +165,7 @@ export namespace SessionSummary {
|
|
|
145
165
|
? 'Compaction model unavailable, falling back to main model'
|
|
146
166
|
: 'Using same model as --model (no compaction model configured)',
|
|
147
167
|
}));
|
|
148
|
-
model =
|
|
149
|
-
assistantMsg.providerID,
|
|
150
|
-
assistantMsg.modelID
|
|
151
|
-
).catch(() => null);
|
|
168
|
+
model = baseModel;
|
|
152
169
|
}
|
|
153
170
|
if (!model) {
|
|
154
171
|
log.info(() => ({
|