@link-assistant/agent 0.16.18 → 0.18.0
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/flag/flag.ts +13 -7
- package/src/index.js +31 -150
- package/src/provider/provider.ts +21 -16
- package/src/session/compaction.ts +164 -5
- package/src/session/message-v2.ts +32 -0
- package/src/session/processor.ts +18 -0
- package/src/session/prompt.ts +45 -2
- package/src/session/summary.ts +121 -22
- package/src/util/verbose-fetch.ts +5 -5
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/flag/flag.ts
CHANGED
|
@@ -103,13 +103,19 @@ export namespace Flag {
|
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
// Session summarization configuration
|
|
106
|
-
//
|
|
107
|
-
//
|
|
108
|
-
// See: https://github.com/link-assistant/agent/issues/
|
|
109
|
-
export let SUMMARIZE_SESSION =
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
106
|
+
// Enabled by default - generates AI-powered session summaries using the same model
|
|
107
|
+
// Can be disabled with --no-summarize-session or AGENT_SUMMARIZE_SESSION=false
|
|
108
|
+
// See: https://github.com/link-assistant/agent/issues/217
|
|
109
|
+
export let SUMMARIZE_SESSION = (() => {
|
|
110
|
+
const value = (
|
|
111
|
+
getEnv(
|
|
112
|
+
'LINK_ASSISTANT_AGENT_SUMMARIZE_SESSION',
|
|
113
|
+
'AGENT_SUMMARIZE_SESSION'
|
|
114
|
+
) ?? ''
|
|
115
|
+
).toLowerCase();
|
|
116
|
+
if (value === 'false' || value === '0') return false;
|
|
117
|
+
return true; // Default to true
|
|
118
|
+
})();
|
|
113
119
|
|
|
114
120
|
// Allow setting summarize-session mode programmatically (e.g., from CLI --summarize-session flag)
|
|
115
121
|
export function setSummarizeSession(value: boolean) {
|