@link-assistant/agent 0.17.0 → 0.18.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/package.json +1 -1
- package/src/cli/argv.ts +54 -16
- package/src/cli/continuous-mode.js +6 -2
- package/src/cli/defaults.ts +18 -0
- package/src/cli/model-config.js +87 -3
- package/src/cli/run-options.js +163 -0
- package/src/index.js +26 -152
- package/src/provider/provider.ts +10 -13
- package/src/session/compaction.ts +84 -12
- package/src/session/message-v2.ts +8 -0
- package/src/session/prompt.ts +45 -2
package/package.json
CHANGED
package/src/cli/argv.ts
CHANGED
|
@@ -4,30 +4,68 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* Extract
|
|
8
|
-
*
|
|
9
|
-
* @returns The
|
|
7
|
+
* Extract a named argument directly from process.argv.
|
|
8
|
+
* Supports --name=value, --name value, and optional short aliases (-x=value, -x value).
|
|
9
|
+
* @returns The argument value from CLI or null if not found
|
|
10
10
|
*/
|
|
11
|
-
|
|
11
|
+
function getArgFromProcessArgv(
|
|
12
|
+
longFlag: string,
|
|
13
|
+
shortFlag?: string
|
|
14
|
+
): string | null {
|
|
12
15
|
const args = process.argv;
|
|
16
|
+
const longPrefix = `--${longFlag}=`;
|
|
17
|
+
const shortPrefix = shortFlag ? `-${shortFlag}=` : null;
|
|
13
18
|
for (let i = 0; i < args.length; i++) {
|
|
14
19
|
const arg = args[i];
|
|
15
|
-
// Handle --
|
|
16
|
-
if (arg.startsWith(
|
|
17
|
-
return arg.substring(
|
|
20
|
+
// Handle --flag=value format
|
|
21
|
+
if (arg.startsWith(longPrefix)) {
|
|
22
|
+
return arg.substring(longPrefix.length);
|
|
18
23
|
}
|
|
19
|
-
// Handle --
|
|
20
|
-
if (arg ===
|
|
24
|
+
// Handle --flag value format
|
|
25
|
+
if (arg === `--${longFlag}` && i + 1 < args.length) {
|
|
21
26
|
return args[i + 1];
|
|
22
27
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
if (shortPrefix) {
|
|
29
|
+
// Handle -x=value format
|
|
30
|
+
if (arg.startsWith(shortPrefix)) {
|
|
31
|
+
return arg.substring(shortPrefix.length);
|
|
32
|
+
}
|
|
33
|
+
// Handle -x value format (but not if it looks like another flag)
|
|
34
|
+
if (
|
|
35
|
+
arg === `-${shortFlag}` &&
|
|
36
|
+
i + 1 < args.length &&
|
|
37
|
+
!args[i + 1].startsWith('-')
|
|
38
|
+
) {
|
|
39
|
+
return args[i + 1];
|
|
40
|
+
}
|
|
30
41
|
}
|
|
31
42
|
}
|
|
32
43
|
return null;
|
|
33
44
|
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Extract model argument directly from process.argv
|
|
48
|
+
* This is a safeguard against yargs caching issues (#192)
|
|
49
|
+
* @returns The model argument from CLI or null if not found
|
|
50
|
+
*/
|
|
51
|
+
export function getModelFromProcessArgv(): string | null {
|
|
52
|
+
return getArgFromProcessArgv('model', 'm');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Extract --compaction-model argument directly from process.argv
|
|
57
|
+
* @returns The compaction model argument from CLI or null if not found
|
|
58
|
+
* @see https://github.com/link-assistant/agent/issues/219
|
|
59
|
+
*/
|
|
60
|
+
export function getCompactionModelFromProcessArgv(): string | null {
|
|
61
|
+
return getArgFromProcessArgv('compaction-model');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Extract --compaction-safety-margin argument directly from process.argv
|
|
66
|
+
* @returns The compaction safety margin (%) from CLI or null if not found
|
|
67
|
+
* @see https://github.com/link-assistant/agent/issues/219
|
|
68
|
+
*/
|
|
69
|
+
export function getCompactionSafetyMarginFromProcessArgv(): string | null {
|
|
70
|
+
return getArgFromProcessArgv('compaction-safety-margin');
|
|
71
|
+
}
|
|
@@ -193,7 +193,8 @@ export async function runContinuousServerMode(
|
|
|
193
193
|
modelID,
|
|
194
194
|
systemMessage,
|
|
195
195
|
appendSystemMessage,
|
|
196
|
-
jsonStandard
|
|
196
|
+
jsonStandard,
|
|
197
|
+
compactionModel
|
|
197
198
|
) {
|
|
198
199
|
// Check both CLI flag and environment variable for compact JSON mode
|
|
199
200
|
const compactJson = argv['compact-json'] === true || Flag.COMPACT_JSON();
|
|
@@ -286,6 +287,7 @@ export async function runContinuousServerMode(
|
|
|
286
287
|
body: JSON.stringify({
|
|
287
288
|
parts,
|
|
288
289
|
model: { providerID, modelID },
|
|
290
|
+
compactionModel,
|
|
289
291
|
system: systemMessage,
|
|
290
292
|
appendSystem: appendSystemMessage,
|
|
291
293
|
}),
|
|
@@ -443,7 +445,8 @@ export async function runContinuousDirectMode(
|
|
|
443
445
|
modelID,
|
|
444
446
|
systemMessage,
|
|
445
447
|
appendSystemMessage,
|
|
446
|
-
jsonStandard
|
|
448
|
+
jsonStandard,
|
|
449
|
+
compactionModel
|
|
447
450
|
) {
|
|
448
451
|
// Check both CLI flag and environment variable for compact JSON mode
|
|
449
452
|
const compactJson = argv['compact-json'] === true || Flag.COMPACT_JSON();
|
|
@@ -517,6 +520,7 @@ export async function runContinuousDirectMode(
|
|
|
517
520
|
sessionID,
|
|
518
521
|
parts,
|
|
519
522
|
model: { providerID, modelID },
|
|
523
|
+
compactionModel,
|
|
520
524
|
system: systemMessage,
|
|
521
525
|
appendSystem: appendSystemMessage,
|
|
522
526
|
}).catch((error) => {
|
package/src/cli/defaults.ts
CHANGED
|
@@ -13,3 +13,21 @@ export const DEFAULT_PROVIDER_ID = DEFAULT_MODEL.split('/')[0];
|
|
|
13
13
|
|
|
14
14
|
/** Default model ID extracted from DEFAULT_MODEL. */
|
|
15
15
|
export const DEFAULT_MODEL_ID = DEFAULT_MODEL.split('/').slice(1).join('/');
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Default compaction model used when no `--compaction-model` CLI argument is provided.
|
|
19
|
+
* gpt-5-nano has a 400K context window, larger than most free base models (~200K),
|
|
20
|
+
* which allows compacting 100% of the base model's context without a safety margin.
|
|
21
|
+
* The special value "same" means use the same model as `--model`.
|
|
22
|
+
* @see https://github.com/link-assistant/agent/issues/219
|
|
23
|
+
*/
|
|
24
|
+
export const DEFAULT_COMPACTION_MODEL = 'opencode/gpt-5-nano';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Default compaction safety margin as a percentage of usable context window.
|
|
28
|
+
* Applied only when the compaction model has a context window equal to or smaller
|
|
29
|
+
* than the base model. When the compaction model has a larger context, the margin
|
|
30
|
+
* is automatically set to 0 (allowing 100% context usage).
|
|
31
|
+
* @see https://github.com/link-assistant/agent/issues/219
|
|
32
|
+
*/
|
|
33
|
+
export const DEFAULT_COMPACTION_SAFETY_MARGIN_PERCENT = 15;
|
package/src/cli/model-config.js
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
getModelFromProcessArgv,
|
|
3
|
+
getCompactionModelFromProcessArgv,
|
|
4
|
+
getCompactionSafetyMarginFromProcessArgv,
|
|
5
|
+
} from './argv.ts';
|
|
2
6
|
import { Log } from '../util/log.ts';
|
|
3
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
DEFAULT_PROVIDER_ID,
|
|
9
|
+
DEFAULT_MODEL_ID,
|
|
10
|
+
DEFAULT_COMPACTION_MODEL,
|
|
11
|
+
DEFAULT_COMPACTION_SAFETY_MARGIN_PERCENT,
|
|
12
|
+
} from './defaults.ts';
|
|
4
13
|
|
|
5
14
|
/**
|
|
6
15
|
* Parse model config from argv. Supports "provider/model" or short "model" format.
|
|
@@ -101,6 +110,13 @@ export async function parseModelConfig(argv, outputError, outputStatus) {
|
|
|
101
110
|
}));
|
|
102
111
|
}
|
|
103
112
|
|
|
113
|
+
// Parse compaction model (#219)
|
|
114
|
+
const compactionModelResult = await parseCompactionModelConfig(
|
|
115
|
+
argv,
|
|
116
|
+
providerID,
|
|
117
|
+
modelID
|
|
118
|
+
);
|
|
119
|
+
|
|
104
120
|
// Handle --use-existing-claude-oauth option
|
|
105
121
|
// This reads OAuth credentials from ~/.claude/.credentials.json (Claude Code CLI)
|
|
106
122
|
// For new authentication, use: agent auth login (select Anthropic > Claude Pro/Max)
|
|
@@ -144,5 +160,73 @@ export async function parseModelConfig(argv, outputError, outputStatus) {
|
|
|
144
160
|
}
|
|
145
161
|
}
|
|
146
162
|
|
|
147
|
-
return { providerID, modelID };
|
|
163
|
+
return { providerID, modelID, compactionModel: compactionModelResult };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Parse compaction model config from argv.
|
|
168
|
+
* Resolves --compaction-model and --compaction-safety-margin CLI arguments.
|
|
169
|
+
* The special value "same" means use the base model for compaction.
|
|
170
|
+
* @see https://github.com/link-assistant/agent/issues/219
|
|
171
|
+
*/
|
|
172
|
+
async function parseCompactionModelConfig(argv, baseProviderID, baseModelID) {
|
|
173
|
+
// Get compaction model from CLI (safeguard against yargs caching)
|
|
174
|
+
const cliCompactionModelArg = getCompactionModelFromProcessArgv();
|
|
175
|
+
const compactionModelArg =
|
|
176
|
+
cliCompactionModelArg ??
|
|
177
|
+
argv['compaction-model'] ??
|
|
178
|
+
DEFAULT_COMPACTION_MODEL;
|
|
179
|
+
|
|
180
|
+
// Get safety margin from CLI
|
|
181
|
+
const cliSafetyMarginArg = getCompactionSafetyMarginFromProcessArgv();
|
|
182
|
+
const compactionSafetyMarginPercent = cliSafetyMarginArg
|
|
183
|
+
? parseInt(cliSafetyMarginArg, 10)
|
|
184
|
+
: (argv['compaction-safety-margin'] ??
|
|
185
|
+
DEFAULT_COMPACTION_SAFETY_MARGIN_PERCENT);
|
|
186
|
+
|
|
187
|
+
// Special "same" alias — use the base model for compaction
|
|
188
|
+
const useSameModel = compactionModelArg.toLowerCase() === 'same';
|
|
189
|
+
|
|
190
|
+
let compactionProviderID;
|
|
191
|
+
let compactionModelID;
|
|
192
|
+
|
|
193
|
+
if (useSameModel) {
|
|
194
|
+
compactionProviderID = baseProviderID;
|
|
195
|
+
compactionModelID = baseModelID;
|
|
196
|
+
Log.Default.info(() => ({
|
|
197
|
+
message:
|
|
198
|
+
'compaction model set to "same" — using base model for compaction',
|
|
199
|
+
compactionProviderID,
|
|
200
|
+
compactionModelID,
|
|
201
|
+
}));
|
|
202
|
+
} else if (compactionModelArg.includes('/')) {
|
|
203
|
+
const parts = compactionModelArg.split('/');
|
|
204
|
+
compactionProviderID = parts[0];
|
|
205
|
+
compactionModelID = parts.slice(1).join('/');
|
|
206
|
+
Log.Default.info(() => ({
|
|
207
|
+
message: 'using explicit compaction model',
|
|
208
|
+
compactionProviderID,
|
|
209
|
+
compactionModelID,
|
|
210
|
+
}));
|
|
211
|
+
} else {
|
|
212
|
+
// Short name resolution
|
|
213
|
+
const { Provider } = await import('../provider/provider.ts');
|
|
214
|
+
const resolved =
|
|
215
|
+
await Provider.parseModelWithResolution(compactionModelArg);
|
|
216
|
+
compactionProviderID = resolved.providerID;
|
|
217
|
+
compactionModelID = resolved.modelID;
|
|
218
|
+
Log.Default.info(() => ({
|
|
219
|
+
message: 'resolved short compaction model name',
|
|
220
|
+
input: compactionModelArg,
|
|
221
|
+
compactionProviderID,
|
|
222
|
+
compactionModelID,
|
|
223
|
+
}));
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
providerID: compactionProviderID,
|
|
228
|
+
modelID: compactionModelID,
|
|
229
|
+
useSameModel,
|
|
230
|
+
compactionSafetyMarginPercent,
|
|
231
|
+
};
|
|
148
232
|
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_MODEL,
|
|
3
|
+
DEFAULT_COMPACTION_MODEL,
|
|
4
|
+
DEFAULT_COMPACTION_SAFETY_MARGIN_PERCENT,
|
|
5
|
+
} from './defaults.ts';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Yargs builder for the default `run` command options.
|
|
9
|
+
* Extracted from index.js to keep file size under 1000 lines.
|
|
10
|
+
*/
|
|
11
|
+
export function buildRunOptions(yargs) {
|
|
12
|
+
return yargs
|
|
13
|
+
.option('model', {
|
|
14
|
+
type: 'string',
|
|
15
|
+
description: 'Model to use in format providerID/modelID',
|
|
16
|
+
default: DEFAULT_MODEL,
|
|
17
|
+
})
|
|
18
|
+
.option('json-standard', {
|
|
19
|
+
type: 'string',
|
|
20
|
+
description:
|
|
21
|
+
'JSON output format standard: "opencode" (default) or "claude" (experimental)',
|
|
22
|
+
default: 'opencode',
|
|
23
|
+
choices: ['opencode', 'claude'],
|
|
24
|
+
})
|
|
25
|
+
.option('system-message', {
|
|
26
|
+
type: 'string',
|
|
27
|
+
description: 'Full override of the system message',
|
|
28
|
+
})
|
|
29
|
+
.option('system-message-file', {
|
|
30
|
+
type: 'string',
|
|
31
|
+
description: 'Full override of the system message from file',
|
|
32
|
+
})
|
|
33
|
+
.option('append-system-message', {
|
|
34
|
+
type: 'string',
|
|
35
|
+
description: 'Append to the default system message',
|
|
36
|
+
})
|
|
37
|
+
.option('append-system-message-file', {
|
|
38
|
+
type: 'string',
|
|
39
|
+
description: 'Append to the default system message from file',
|
|
40
|
+
})
|
|
41
|
+
.option('server', {
|
|
42
|
+
type: 'boolean',
|
|
43
|
+
description: 'Run in server mode (default)',
|
|
44
|
+
default: true,
|
|
45
|
+
})
|
|
46
|
+
.option('verbose', {
|
|
47
|
+
type: 'boolean',
|
|
48
|
+
description:
|
|
49
|
+
'Enable verbose mode to debug API requests (shows system prompt, token counts, etc.)',
|
|
50
|
+
default: false,
|
|
51
|
+
})
|
|
52
|
+
.option('dry-run', {
|
|
53
|
+
type: 'boolean',
|
|
54
|
+
description:
|
|
55
|
+
'Simulate operations without making actual API calls or package installations (useful for testing)',
|
|
56
|
+
default: false,
|
|
57
|
+
})
|
|
58
|
+
.option('use-existing-claude-oauth', {
|
|
59
|
+
type: 'boolean',
|
|
60
|
+
description:
|
|
61
|
+
'Use existing Claude OAuth credentials from ~/.claude/.credentials.json (from Claude Code CLI)',
|
|
62
|
+
default: false,
|
|
63
|
+
})
|
|
64
|
+
.option('prompt', {
|
|
65
|
+
alias: 'p',
|
|
66
|
+
type: 'string',
|
|
67
|
+
description: 'Prompt message to send directly (bypasses stdin reading)',
|
|
68
|
+
})
|
|
69
|
+
.option('disable-stdin', {
|
|
70
|
+
type: 'boolean',
|
|
71
|
+
description:
|
|
72
|
+
'Disable stdin streaming mode (requires --prompt or shows help)',
|
|
73
|
+
default: false,
|
|
74
|
+
})
|
|
75
|
+
.option('stdin-stream-timeout', {
|
|
76
|
+
type: 'number',
|
|
77
|
+
description:
|
|
78
|
+
'Optional timeout in milliseconds for stdin reading (default: no timeout)',
|
|
79
|
+
})
|
|
80
|
+
.option('auto-merge-queued-messages', {
|
|
81
|
+
type: 'boolean',
|
|
82
|
+
description:
|
|
83
|
+
'Enable auto-merging of rapidly arriving input lines into single messages (default: true)',
|
|
84
|
+
default: true,
|
|
85
|
+
})
|
|
86
|
+
.option('interactive', {
|
|
87
|
+
type: 'boolean',
|
|
88
|
+
description:
|
|
89
|
+
'Enable interactive mode to accept manual input as plain text strings (default: true). Use --no-interactive to only accept JSON input.',
|
|
90
|
+
default: true,
|
|
91
|
+
})
|
|
92
|
+
.option('always-accept-stdin', {
|
|
93
|
+
type: 'boolean',
|
|
94
|
+
description:
|
|
95
|
+
'Keep accepting stdin input even after the agent finishes work (default: true). Use --no-always-accept-stdin for single-message mode.',
|
|
96
|
+
default: true,
|
|
97
|
+
})
|
|
98
|
+
.option('compact-json', {
|
|
99
|
+
type: 'boolean',
|
|
100
|
+
description:
|
|
101
|
+
'Output compact JSON (single line) instead of pretty-printed JSON (default: false). Useful for program-to-program communication.',
|
|
102
|
+
default: false,
|
|
103
|
+
})
|
|
104
|
+
.option('resume', {
|
|
105
|
+
alias: 'r',
|
|
106
|
+
type: 'string',
|
|
107
|
+
description:
|
|
108
|
+
'Resume a specific session by ID. By default, forks the session with a new UUID. Use --no-fork to continue in the same session.',
|
|
109
|
+
})
|
|
110
|
+
.option('continue', {
|
|
111
|
+
alias: 'c',
|
|
112
|
+
type: 'boolean',
|
|
113
|
+
description:
|
|
114
|
+
'Continue the most recent session. By default, forks the session with a new UUID. Use --no-fork to continue in the same session.',
|
|
115
|
+
default: false,
|
|
116
|
+
})
|
|
117
|
+
.option('no-fork', {
|
|
118
|
+
type: 'boolean',
|
|
119
|
+
description:
|
|
120
|
+
'When used with --resume or --continue, continue in the same session without forking to a new UUID.',
|
|
121
|
+
default: false,
|
|
122
|
+
})
|
|
123
|
+
.option('generate-title', {
|
|
124
|
+
type: 'boolean',
|
|
125
|
+
description:
|
|
126
|
+
'Generate session titles using AI (default: false). Disabling saves tokens and prevents rate limit issues.',
|
|
127
|
+
default: false,
|
|
128
|
+
})
|
|
129
|
+
.option('retry-timeout', {
|
|
130
|
+
type: 'number',
|
|
131
|
+
description:
|
|
132
|
+
'Maximum total retry time in seconds for rate limit errors (default: 604800 = 7 days)',
|
|
133
|
+
})
|
|
134
|
+
.option('retry-on-rate-limits', {
|
|
135
|
+
type: 'boolean',
|
|
136
|
+
description:
|
|
137
|
+
'Retry AI completions API requests when rate limited (HTTP 429). Use --no-retry-on-rate-limits in integration tests to fail fast instead of waiting.',
|
|
138
|
+
default: true,
|
|
139
|
+
})
|
|
140
|
+
.option('output-response-model', {
|
|
141
|
+
type: 'boolean',
|
|
142
|
+
description: 'Include model info in step_finish output',
|
|
143
|
+
default: true,
|
|
144
|
+
})
|
|
145
|
+
.option('summarize-session', {
|
|
146
|
+
type: 'boolean',
|
|
147
|
+
description:
|
|
148
|
+
'Generate AI session summaries (default: true). Use --no-summarize-session to disable.',
|
|
149
|
+
default: true,
|
|
150
|
+
})
|
|
151
|
+
.option('compaction-model', {
|
|
152
|
+
type: 'string',
|
|
153
|
+
description:
|
|
154
|
+
'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).',
|
|
155
|
+
default: DEFAULT_COMPACTION_MODEL,
|
|
156
|
+
})
|
|
157
|
+
.option('compaction-safety-margin', {
|
|
158
|
+
type: 'number',
|
|
159
|
+
description:
|
|
160
|
+
'Safety margin (%) of usable context window before triggering compaction. Only applies when the compaction model has equal or smaller context than the base model. Default: 15.',
|
|
161
|
+
default: DEFAULT_COMPACTION_SAFETY_MARGIN_PERCENT,
|
|
162
|
+
});
|
|
163
|
+
}
|
package/src/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import { Server } from './server/server.ts';
|
|
|
6
6
|
import { Instance } from './project/instance.ts';
|
|
7
7
|
import { Log } from './util/log.ts';
|
|
8
8
|
import { parseModelConfig } from './cli/model-config.js';
|
|
9
|
-
import {
|
|
9
|
+
import { buildRunOptions } from './cli/run-options.js';
|
|
10
10
|
// Bus is used via createBusEventSubscription in event-handler.js
|
|
11
11
|
import { Session } from './session/index.ts';
|
|
12
12
|
import { SessionPrompt } from './session/prompt.ts';
|
|
@@ -278,7 +278,7 @@ async function runAgentMode(argv, request) {
|
|
|
278
278
|
fn: async () => {
|
|
279
279
|
// Parse model config inside Instance.provide context
|
|
280
280
|
// This allows parseModelWithResolution to access the provider state
|
|
281
|
-
const { providerID, modelID } = await parseModelConfig(
|
|
281
|
+
const { providerID, modelID, compactionModel } = await parseModelConfig(
|
|
282
282
|
argv,
|
|
283
283
|
outputError,
|
|
284
284
|
outputStatus
|
|
@@ -293,7 +293,8 @@ async function runAgentMode(argv, request) {
|
|
|
293
293
|
modelID,
|
|
294
294
|
systemMessage,
|
|
295
295
|
appendSystemMessage,
|
|
296
|
-
jsonStandard
|
|
296
|
+
jsonStandard,
|
|
297
|
+
compactionModel
|
|
297
298
|
);
|
|
298
299
|
} else {
|
|
299
300
|
// DIRECT MODE: Run everything in single process
|
|
@@ -304,7 +305,8 @@ async function runAgentMode(argv, request) {
|
|
|
304
305
|
modelID,
|
|
305
306
|
systemMessage,
|
|
306
307
|
appendSystemMessage,
|
|
307
|
-
jsonStandard
|
|
308
|
+
jsonStandard,
|
|
309
|
+
compactionModel
|
|
308
310
|
);
|
|
309
311
|
}
|
|
310
312
|
},
|
|
@@ -363,7 +365,7 @@ async function runContinuousAgentMode(argv) {
|
|
|
363
365
|
fn: async () => {
|
|
364
366
|
// Parse model config inside Instance.provide context
|
|
365
367
|
// This allows parseModelWithResolution to access the provider state
|
|
366
|
-
const { providerID, modelID } = await parseModelConfig(
|
|
368
|
+
const { providerID, modelID, compactionModel } = await parseModelConfig(
|
|
367
369
|
argv,
|
|
368
370
|
outputError,
|
|
369
371
|
outputStatus
|
|
@@ -377,7 +379,8 @@ async function runContinuousAgentMode(argv) {
|
|
|
377
379
|
modelID,
|
|
378
380
|
systemMessage,
|
|
379
381
|
appendSystemMessage,
|
|
380
|
-
jsonStandard
|
|
382
|
+
jsonStandard,
|
|
383
|
+
compactionModel
|
|
381
384
|
);
|
|
382
385
|
} else {
|
|
383
386
|
// DIRECT MODE: Run everything in single process
|
|
@@ -387,7 +390,8 @@ async function runContinuousAgentMode(argv) {
|
|
|
387
390
|
modelID,
|
|
388
391
|
systemMessage,
|
|
389
392
|
appendSystemMessage,
|
|
390
|
-
jsonStandard
|
|
393
|
+
jsonStandard,
|
|
394
|
+
compactionModel
|
|
391
395
|
);
|
|
392
396
|
}
|
|
393
397
|
},
|
|
@@ -409,7 +413,8 @@ async function runServerMode(
|
|
|
409
413
|
modelID,
|
|
410
414
|
systemMessage,
|
|
411
415
|
appendSystemMessage,
|
|
412
|
-
jsonStandard
|
|
416
|
+
jsonStandard,
|
|
417
|
+
compactionModel
|
|
413
418
|
) {
|
|
414
419
|
const compactJson = argv['compact-json'] === true;
|
|
415
420
|
|
|
@@ -475,6 +480,7 @@ async function runServerMode(
|
|
|
475
480
|
providerID,
|
|
476
481
|
modelID,
|
|
477
482
|
},
|
|
483
|
+
compactionModel,
|
|
478
484
|
system: systemMessage,
|
|
479
485
|
appendSystem: appendSystemMessage,
|
|
480
486
|
}),
|
|
@@ -508,7 +514,8 @@ async function runDirectMode(
|
|
|
508
514
|
modelID,
|
|
509
515
|
systemMessage,
|
|
510
516
|
appendSystemMessage,
|
|
511
|
-
jsonStandard
|
|
517
|
+
jsonStandard,
|
|
518
|
+
compactionModel
|
|
512
519
|
) {
|
|
513
520
|
const compactJson = argv['compact-json'] === true;
|
|
514
521
|
|
|
@@ -558,6 +565,7 @@ async function runDirectMode(
|
|
|
558
565
|
providerID,
|
|
559
566
|
modelID,
|
|
560
567
|
},
|
|
568
|
+
compactionModel,
|
|
561
569
|
system: systemMessage,
|
|
562
570
|
appendSystem: appendSystemMessage,
|
|
563
571
|
}).catch((error) => {
|
|
@@ -596,147 +604,7 @@ async function main() {
|
|
|
596
604
|
.command({
|
|
597
605
|
command: '$0',
|
|
598
606
|
describe: 'Run agent in interactive or stdin mode (default)',
|
|
599
|
-
builder:
|
|
600
|
-
yargs
|
|
601
|
-
.option('model', {
|
|
602
|
-
type: 'string',
|
|
603
|
-
description: 'Model to use in format providerID/modelID',
|
|
604
|
-
default: DEFAULT_MODEL,
|
|
605
|
-
})
|
|
606
|
-
.option('json-standard', {
|
|
607
|
-
type: 'string',
|
|
608
|
-
description:
|
|
609
|
-
'JSON output format standard: "opencode" (default) or "claude" (experimental)',
|
|
610
|
-
default: 'opencode',
|
|
611
|
-
choices: ['opencode', 'claude'],
|
|
612
|
-
})
|
|
613
|
-
.option('system-message', {
|
|
614
|
-
type: 'string',
|
|
615
|
-
description: 'Full override of the system message',
|
|
616
|
-
})
|
|
617
|
-
.option('system-message-file', {
|
|
618
|
-
type: 'string',
|
|
619
|
-
description: 'Full override of the system message from file',
|
|
620
|
-
})
|
|
621
|
-
.option('append-system-message', {
|
|
622
|
-
type: 'string',
|
|
623
|
-
description: 'Append to the default system message',
|
|
624
|
-
})
|
|
625
|
-
.option('append-system-message-file', {
|
|
626
|
-
type: 'string',
|
|
627
|
-
description: 'Append to the default system message from file',
|
|
628
|
-
})
|
|
629
|
-
.option('server', {
|
|
630
|
-
type: 'boolean',
|
|
631
|
-
description: 'Run in server mode (default)',
|
|
632
|
-
default: true,
|
|
633
|
-
})
|
|
634
|
-
.option('verbose', {
|
|
635
|
-
type: 'boolean',
|
|
636
|
-
description:
|
|
637
|
-
'Enable verbose mode to debug API requests (shows system prompt, token counts, etc.)',
|
|
638
|
-
default: false,
|
|
639
|
-
})
|
|
640
|
-
.option('dry-run', {
|
|
641
|
-
type: 'boolean',
|
|
642
|
-
description:
|
|
643
|
-
'Simulate operations without making actual API calls or package installations (useful for testing)',
|
|
644
|
-
default: false,
|
|
645
|
-
})
|
|
646
|
-
.option('use-existing-claude-oauth', {
|
|
647
|
-
type: 'boolean',
|
|
648
|
-
description:
|
|
649
|
-
'Use existing Claude OAuth credentials from ~/.claude/.credentials.json (from Claude Code CLI)',
|
|
650
|
-
default: false,
|
|
651
|
-
})
|
|
652
|
-
.option('prompt', {
|
|
653
|
-
alias: 'p',
|
|
654
|
-
type: 'string',
|
|
655
|
-
description:
|
|
656
|
-
'Prompt message to send directly (bypasses stdin reading)',
|
|
657
|
-
})
|
|
658
|
-
.option('disable-stdin', {
|
|
659
|
-
type: 'boolean',
|
|
660
|
-
description:
|
|
661
|
-
'Disable stdin streaming mode (requires --prompt or shows help)',
|
|
662
|
-
default: false,
|
|
663
|
-
})
|
|
664
|
-
.option('stdin-stream-timeout', {
|
|
665
|
-
type: 'number',
|
|
666
|
-
description:
|
|
667
|
-
'Optional timeout in milliseconds for stdin reading (default: no timeout)',
|
|
668
|
-
})
|
|
669
|
-
.option('auto-merge-queued-messages', {
|
|
670
|
-
type: 'boolean',
|
|
671
|
-
description:
|
|
672
|
-
'Enable auto-merging of rapidly arriving input lines into single messages (default: true)',
|
|
673
|
-
default: true,
|
|
674
|
-
})
|
|
675
|
-
.option('interactive', {
|
|
676
|
-
type: 'boolean',
|
|
677
|
-
description:
|
|
678
|
-
'Enable interactive mode to accept manual input as plain text strings (default: true). Use --no-interactive to only accept JSON input.',
|
|
679
|
-
default: true,
|
|
680
|
-
})
|
|
681
|
-
.option('always-accept-stdin', {
|
|
682
|
-
type: 'boolean',
|
|
683
|
-
description:
|
|
684
|
-
'Keep accepting stdin input even after the agent finishes work (default: true). Use --no-always-accept-stdin for single-message mode.',
|
|
685
|
-
default: true,
|
|
686
|
-
})
|
|
687
|
-
.option('compact-json', {
|
|
688
|
-
type: 'boolean',
|
|
689
|
-
description:
|
|
690
|
-
'Output compact JSON (single line) instead of pretty-printed JSON (default: false). Useful for program-to-program communication.',
|
|
691
|
-
default: false,
|
|
692
|
-
})
|
|
693
|
-
.option('resume', {
|
|
694
|
-
alias: 'r',
|
|
695
|
-
type: 'string',
|
|
696
|
-
description:
|
|
697
|
-
'Resume a specific session by ID. By default, forks the session with a new UUID. Use --no-fork to continue in the same session.',
|
|
698
|
-
})
|
|
699
|
-
.option('continue', {
|
|
700
|
-
alias: 'c',
|
|
701
|
-
type: 'boolean',
|
|
702
|
-
description:
|
|
703
|
-
'Continue the most recent session. By default, forks the session with a new UUID. Use --no-fork to continue in the same session.',
|
|
704
|
-
default: false,
|
|
705
|
-
})
|
|
706
|
-
.option('no-fork', {
|
|
707
|
-
type: 'boolean',
|
|
708
|
-
description:
|
|
709
|
-
'When used with --resume or --continue, continue in the same session without forking to a new UUID.',
|
|
710
|
-
default: false,
|
|
711
|
-
})
|
|
712
|
-
.option('generate-title', {
|
|
713
|
-
type: 'boolean',
|
|
714
|
-
description:
|
|
715
|
-
'Generate session titles using AI (default: false). Disabling saves tokens and prevents rate limit issues.',
|
|
716
|
-
default: false,
|
|
717
|
-
})
|
|
718
|
-
.option('retry-timeout', {
|
|
719
|
-
type: 'number',
|
|
720
|
-
description:
|
|
721
|
-
'Maximum total retry time in seconds for rate limit errors (default: 604800 = 7 days)',
|
|
722
|
-
})
|
|
723
|
-
.option('retry-on-rate-limits', {
|
|
724
|
-
type: 'boolean',
|
|
725
|
-
description:
|
|
726
|
-
'Retry AI completions API requests when rate limited (HTTP 429). Use --no-retry-on-rate-limits in integration tests to fail fast instead of waiting.',
|
|
727
|
-
default: true,
|
|
728
|
-
})
|
|
729
|
-
.option('output-response-model', {
|
|
730
|
-
type: 'boolean',
|
|
731
|
-
description: 'Include model info in step_finish output',
|
|
732
|
-
default: true,
|
|
733
|
-
})
|
|
734
|
-
.option('summarize-session', {
|
|
735
|
-
type: 'boolean',
|
|
736
|
-
description:
|
|
737
|
-
'Generate AI session summaries (default: true). Use --no-summarize-session to disable.',
|
|
738
|
-
default: true,
|
|
739
|
-
}),
|
|
607
|
+
builder: buildRunOptions,
|
|
740
608
|
handler: async (argv) => {
|
|
741
609
|
// Check both CLI flag and environment variable for compact JSON mode
|
|
742
610
|
const compactJson =
|
|
@@ -934,8 +802,14 @@ async function main() {
|
|
|
934
802
|
compactJson: isCompact,
|
|
935
803
|
});
|
|
936
804
|
|
|
937
|
-
//
|
|
938
|
-
//
|
|
805
|
+
// Global fetch monkey-patch for verbose HTTP logging (#221).
|
|
806
|
+
// This catches any HTTP calls that go through globalThis.fetch directly,
|
|
807
|
+
// including non-provider calls (auth, config, tools) that may not have
|
|
808
|
+
// their own createVerboseFetch wrapper. The provider-level wrapper in
|
|
809
|
+
// provider.ts getSDK() also logs independently — both mechanisms are
|
|
810
|
+
// kept active to maximize HTTP observability in --verbose mode.
|
|
811
|
+
// See: https://github.com/link-assistant/agent/issues/221
|
|
812
|
+
// See: https://github.com/link-assistant/agent/issues/217
|
|
939
813
|
if (!globalThis.__agentVerboseFetchInstalled) {
|
|
940
814
|
globalThis.fetch = createVerboseFetch(globalThis.fetch, {
|
|
941
815
|
caller: 'global',
|
package/src/provider/provider.ts
CHANGED
|
@@ -1201,11 +1201,13 @@ export namespace Provider {
|
|
|
1201
1201
|
sessionID: provider.id,
|
|
1202
1202
|
});
|
|
1203
1203
|
|
|
1204
|
-
// Verbose HTTP logging
|
|
1205
|
-
//
|
|
1206
|
-
//
|
|
1207
|
-
//
|
|
1208
|
-
//
|
|
1204
|
+
// Verbose HTTP logging for provider API calls.
|
|
1205
|
+
// This provider-level wrapper logs HTTP requests/responses independently
|
|
1206
|
+
// of the global fetch monkey-patch. Both mechanisms are kept active to
|
|
1207
|
+
// maximize HTTP observability — the global patch may miss calls if the
|
|
1208
|
+
// AI SDK captures/resolves fetch references before it is installed,
|
|
1209
|
+
// while this wrapper is injected directly into the SDK's fetch option.
|
|
1210
|
+
// See: https://github.com/link-assistant/agent/issues/221
|
|
1209
1211
|
// See: https://github.com/link-assistant/agent/issues/217
|
|
1210
1212
|
// See: https://github.com/link-assistant/agent/issues/215
|
|
1211
1213
|
{
|
|
@@ -1226,14 +1228,9 @@ export namespace Provider {
|
|
|
1226
1228
|
init?: RequestInit
|
|
1227
1229
|
): Promise<Response> => {
|
|
1228
1230
|
// Check verbose flag at call time — not at SDK creation time.
|
|
1229
|
-
//
|
|
1230
|
-
//
|
|
1231
|
-
|
|
1232
|
-
// See: https://github.com/link-assistant/agent/issues/217
|
|
1233
|
-
if (
|
|
1234
|
-
!Flag.OPENCODE_VERBOSE ||
|
|
1235
|
-
globalThis.__agentVerboseFetchInstalled
|
|
1236
|
-
) {
|
|
1231
|
+
// This ensures --verbose works even when the flag is set after SDK creation.
|
|
1232
|
+
// See: https://github.com/link-assistant/agent/issues/206
|
|
1233
|
+
if (!Flag.OPENCODE_VERBOSE) {
|
|
1237
1234
|
return innerFetch(input, init);
|
|
1238
1235
|
}
|
|
1239
1236
|
|
|
@@ -29,36 +29,101 @@ export namespace SessionCompaction {
|
|
|
29
29
|
};
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
|
-
*
|
|
32
|
+
* Default safety margin ratio for compaction trigger.
|
|
33
33
|
* We trigger compaction at 85% of usable context to avoid hitting hard limits.
|
|
34
34
|
* This means we stop 15% before (context - output) tokens.
|
|
35
35
|
* @see https://github.com/link-assistant/agent/issues/217
|
|
36
36
|
*/
|
|
37
37
|
export const OVERFLOW_SAFETY_MARGIN = 0.85;
|
|
38
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Compaction model configuration passed from CLI.
|
|
41
|
+
* @see https://github.com/link-assistant/agent/issues/219
|
|
42
|
+
*/
|
|
43
|
+
export interface CompactionModelConfig {
|
|
44
|
+
providerID: string;
|
|
45
|
+
modelID: string;
|
|
46
|
+
useSameModel: boolean;
|
|
47
|
+
compactionSafetyMarginPercent: number;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Compute the effective safety margin ratio.
|
|
52
|
+
*
|
|
53
|
+
* When the compaction model has a larger context window than the base model,
|
|
54
|
+
* the entire base model context can be used (ratio = 1.0, i.e. 0% margin),
|
|
55
|
+
* because the compaction model can ingest all of it.
|
|
56
|
+
*
|
|
57
|
+
* When the compaction model has equal or smaller context, the configured
|
|
58
|
+
* safety margin applies (default 15% → ratio 0.85).
|
|
59
|
+
*
|
|
60
|
+
* @see https://github.com/link-assistant/agent/issues/219
|
|
61
|
+
*/
|
|
62
|
+
export function computeSafetyMarginRatio(input: {
|
|
63
|
+
baseModelContextLimit: number;
|
|
64
|
+
compactionModel?: CompactionModelConfig;
|
|
65
|
+
compactionModelContextLimit?: number;
|
|
66
|
+
}): number {
|
|
67
|
+
const compactionModelConfig = input.compactionModel;
|
|
68
|
+
if (!compactionModelConfig) return OVERFLOW_SAFETY_MARGIN;
|
|
69
|
+
|
|
70
|
+
const compactionSafetyMarginPercent =
|
|
71
|
+
compactionModelConfig.compactionSafetyMarginPercent;
|
|
72
|
+
const configuredRatio = 1 - compactionSafetyMarginPercent / 100;
|
|
73
|
+
|
|
74
|
+
// When using the same model, always apply the configured safety margin
|
|
75
|
+
if (compactionModelConfig.useSameModel) return configuredRatio;
|
|
76
|
+
|
|
77
|
+
// When compaction model has a larger context, no safety margin needed
|
|
78
|
+
const compactionContextLimit = input.compactionModelContextLimit ?? 0;
|
|
79
|
+
if (
|
|
80
|
+
compactionContextLimit > 0 &&
|
|
81
|
+
compactionContextLimit > input.baseModelContextLimit
|
|
82
|
+
) {
|
|
83
|
+
log.info(() => ({
|
|
84
|
+
message:
|
|
85
|
+
'compaction model has larger context — using full base model context',
|
|
86
|
+
baseModelContextLimit: input.baseModelContextLimit,
|
|
87
|
+
compactionModelContextLimit: compactionContextLimit,
|
|
88
|
+
}));
|
|
89
|
+
return 1.0;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return configuredRatio;
|
|
93
|
+
}
|
|
94
|
+
|
|
39
95
|
export function isOverflow(input: {
|
|
40
96
|
tokens: MessageV2.Assistant['tokens'];
|
|
41
97
|
model: ModelsDev.Model;
|
|
98
|
+
compactionModel?: CompactionModelConfig;
|
|
99
|
+
compactionModelContextLimit?: number;
|
|
42
100
|
}) {
|
|
43
101
|
if (Flag.OPENCODE_DISABLE_AUTOCOMPACT) return false;
|
|
44
|
-
const
|
|
45
|
-
if (
|
|
102
|
+
const baseModelContextLimit = input.model.limit.context;
|
|
103
|
+
if (baseModelContextLimit === 0) return false;
|
|
46
104
|
const count =
|
|
47
105
|
input.tokens.input + input.tokens.cache.read + input.tokens.output;
|
|
48
|
-
const
|
|
106
|
+
const outputTokenLimit =
|
|
49
107
|
Math.min(input.model.limit.output, SessionPrompt.OUTPUT_TOKEN_MAX) ||
|
|
50
108
|
SessionPrompt.OUTPUT_TOKEN_MAX;
|
|
51
|
-
const
|
|
52
|
-
const
|
|
109
|
+
const usableContextWindow = baseModelContextLimit - outputTokenLimit;
|
|
110
|
+
const safetyMarginRatio = computeSafetyMarginRatio({
|
|
111
|
+
baseModelContextLimit,
|
|
112
|
+
compactionModel: input.compactionModel,
|
|
113
|
+
compactionModelContextLimit: input.compactionModelContextLimit,
|
|
114
|
+
});
|
|
115
|
+
const safeLimit = Math.floor(usableContextWindow * safetyMarginRatio);
|
|
53
116
|
const overflow = count > safeLimit;
|
|
54
117
|
log.info(() => ({
|
|
55
118
|
message: 'overflow check',
|
|
56
119
|
modelID: input.model.id,
|
|
57
|
-
contextLimit:
|
|
58
|
-
outputLimit:
|
|
59
|
-
|
|
120
|
+
contextLimit: baseModelContextLimit,
|
|
121
|
+
outputLimit: outputTokenLimit,
|
|
122
|
+
usableContextWindow,
|
|
60
123
|
safeLimit,
|
|
61
|
-
|
|
124
|
+
safetyMarginRatio,
|
|
125
|
+
compactionModelID: input.compactionModel?.modelID,
|
|
126
|
+
compactionModelContextLimit: input.compactionModelContextLimit,
|
|
62
127
|
currentTokens: count,
|
|
63
128
|
tokensBreakdown: {
|
|
64
129
|
input: input.tokens.input,
|
|
@@ -79,6 +144,8 @@ export namespace SessionCompaction {
|
|
|
79
144
|
export function contextDiagnostics(input: {
|
|
80
145
|
tokens: { input: number; output: number; cache: { read: number } };
|
|
81
146
|
model: ModelsDev.Model;
|
|
147
|
+
compactionModel?: CompactionModelConfig;
|
|
148
|
+
compactionModelContextLimit?: number;
|
|
82
149
|
}): MessageV2.ContextDiagnostics | undefined {
|
|
83
150
|
const contextLimit = input.model.limit.context;
|
|
84
151
|
if (contextLimit === 0) return undefined;
|
|
@@ -86,7 +153,12 @@ export namespace SessionCompaction {
|
|
|
86
153
|
Math.min(input.model.limit.output, SessionPrompt.OUTPUT_TOKEN_MAX) ||
|
|
87
154
|
SessionPrompt.OUTPUT_TOKEN_MAX;
|
|
88
155
|
const usableContext = contextLimit - outputLimit;
|
|
89
|
-
const
|
|
156
|
+
const safetyMarginRatio = computeSafetyMarginRatio({
|
|
157
|
+
baseModelContextLimit: contextLimit,
|
|
158
|
+
compactionModel: input.compactionModel,
|
|
159
|
+
compactionModelContextLimit: input.compactionModelContextLimit,
|
|
160
|
+
});
|
|
161
|
+
const safeLimit = Math.floor(usableContext * safetyMarginRatio);
|
|
90
162
|
const currentTokens =
|
|
91
163
|
input.tokens.input + input.tokens.cache.read + input.tokens.output;
|
|
92
164
|
return {
|
|
@@ -94,7 +166,7 @@ export namespace SessionCompaction {
|
|
|
94
166
|
outputLimit,
|
|
95
167
|
usableContext,
|
|
96
168
|
safeLimit,
|
|
97
|
-
safetyMargin:
|
|
169
|
+
safetyMargin: safetyMarginRatio,
|
|
98
170
|
currentTokens,
|
|
99
171
|
headroom: safeLimit - currentTokens,
|
|
100
172
|
overflow: currentTokens > safeLimit,
|
|
@@ -392,6 +392,14 @@ export namespace MessageV2 {
|
|
|
392
392
|
providerID: z.string(),
|
|
393
393
|
modelID: z.string(),
|
|
394
394
|
}),
|
|
395
|
+
compactionModel: z
|
|
396
|
+
.object({
|
|
397
|
+
providerID: z.string(),
|
|
398
|
+
modelID: z.string(),
|
|
399
|
+
useSameModel: z.boolean(),
|
|
400
|
+
compactionSafetyMarginPercent: z.number(),
|
|
401
|
+
})
|
|
402
|
+
.optional(),
|
|
395
403
|
system: z.string().optional(),
|
|
396
404
|
appendSystem: z.string().optional(),
|
|
397
405
|
tools: z.record(z.string(), z.boolean()).optional(),
|
package/src/session/prompt.ts
CHANGED
|
@@ -89,6 +89,14 @@ export namespace SessionPrompt {
|
|
|
89
89
|
modelID: z.string(),
|
|
90
90
|
})
|
|
91
91
|
.optional(),
|
|
92
|
+
compactionModel: z
|
|
93
|
+
.object({
|
|
94
|
+
providerID: z.string(),
|
|
95
|
+
modelID: z.string(),
|
|
96
|
+
useSameModel: z.boolean(),
|
|
97
|
+
compactionSafetyMarginPercent: z.number(),
|
|
98
|
+
})
|
|
99
|
+
.optional(),
|
|
92
100
|
agent: z.string().optional(),
|
|
93
101
|
noReply: z.boolean().optional(),
|
|
94
102
|
system: z.string().optional(),
|
|
@@ -396,6 +404,28 @@ export namespace SessionPrompt {
|
|
|
396
404
|
// Re-throw the error so it can be handled by the caller
|
|
397
405
|
throw error;
|
|
398
406
|
}
|
|
407
|
+
// Resolve compaction model context limit for overflow detection (#219)
|
|
408
|
+
let compactionModelContextLimit: number | undefined;
|
|
409
|
+
const compactionModelConfig = lastUser.compactionModel;
|
|
410
|
+
if (compactionModelConfig && !compactionModelConfig.useSameModel) {
|
|
411
|
+
try {
|
|
412
|
+
const compactionModelResolved = await Provider.getModel(
|
|
413
|
+
compactionModelConfig.providerID,
|
|
414
|
+
compactionModelConfig.modelID
|
|
415
|
+
);
|
|
416
|
+
compactionModelContextLimit =
|
|
417
|
+
compactionModelResolved.info?.limit?.context;
|
|
418
|
+
} catch {
|
|
419
|
+
// If compaction model can't be resolved, fall back to default safety margin
|
|
420
|
+
log.info(() => ({
|
|
421
|
+
message:
|
|
422
|
+
'could not resolve compaction model for context limit — using default safety margin',
|
|
423
|
+
compactionProviderID: compactionModelConfig.providerID,
|
|
424
|
+
compactionModelID: compactionModelConfig.modelID,
|
|
425
|
+
}));
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
399
429
|
const task = tasks.pop();
|
|
400
430
|
|
|
401
431
|
// pending subtask
|
|
@@ -512,13 +542,23 @@ export namespace SessionPrompt {
|
|
|
512
542
|
|
|
513
543
|
// pending compaction
|
|
514
544
|
if (task?.type === 'compaction') {
|
|
545
|
+
// Use compaction model if configured, otherwise fall back to base model
|
|
546
|
+
const compactionModelConfig = lastUser.compactionModel;
|
|
547
|
+
const compactionProviderID =
|
|
548
|
+
compactionModelConfig && !compactionModelConfig.useSameModel
|
|
549
|
+
? compactionModelConfig.providerID
|
|
550
|
+
: model.providerID;
|
|
551
|
+
const compactionModelID =
|
|
552
|
+
compactionModelConfig && !compactionModelConfig.useSameModel
|
|
553
|
+
? compactionModelConfig.modelID
|
|
554
|
+
: model.modelID;
|
|
515
555
|
const result = await SessionCompaction.process({
|
|
516
556
|
messages: msgs,
|
|
517
557
|
parentID: lastUser.id,
|
|
518
558
|
abort,
|
|
519
559
|
model: {
|
|
520
|
-
providerID:
|
|
521
|
-
modelID:
|
|
560
|
+
providerID: compactionProviderID,
|
|
561
|
+
modelID: compactionModelID,
|
|
522
562
|
},
|
|
523
563
|
sessionID,
|
|
524
564
|
});
|
|
@@ -533,6 +573,8 @@ export namespace SessionPrompt {
|
|
|
533
573
|
SessionCompaction.isOverflow({
|
|
534
574
|
tokens: lastFinished.tokens,
|
|
535
575
|
model: model.info ?? { id: model.modelID },
|
|
576
|
+
compactionModel: lastUser.compactionModel,
|
|
577
|
+
compactionModelContextLimit,
|
|
536
578
|
})
|
|
537
579
|
) {
|
|
538
580
|
await SessionCompaction.create({
|
|
@@ -1053,6 +1095,7 @@ export namespace SessionPrompt {
|
|
|
1053
1095
|
model: input.model,
|
|
1054
1096
|
agent,
|
|
1055
1097
|
}),
|
|
1098
|
+
compactionModel: input.compactionModel,
|
|
1056
1099
|
};
|
|
1057
1100
|
|
|
1058
1101
|
const parts = await Promise.all(
|