@link-assistant/agent 0.26.15 → 0.27.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/README.md +49 -7
- package/package.json +51 -42
- package/src/agent/agent.ts +12 -7
- package/src/auth/azure-login.ts +241 -0
- package/src/auth/plugins.ts +9 -0
- package/src/auth/provider-logins.ts +804 -0
- package/src/cli/argv.ts +1 -1
- package/src/cli/cmd/auth.ts +4 -0
- package/src/cli/cmd/models.ts +68 -13
- package/src/cli/cmd/run.ts +7 -1
- package/src/cli/continuous-mode.js +38 -12
- package/src/cli/event-handler.js +3 -1
- package/src/cli/model-config.js +27 -8
- package/src/cli/run-options.js +144 -78
- package/src/config/config.ts +306 -105
- package/src/config/defaults.ts +13 -5
- package/src/config/file-config.ts +16 -0
- package/src/file/ripgrep.ts +38 -5
- package/src/file/time.ts +86 -9
- package/src/index.js +42 -33
- package/src/mcp/index.ts +370 -106
- package/src/permission/index.ts +22 -0
- package/src/project/instance.ts +5 -1
- package/src/project/project.ts +35 -2
- package/src/provider/env-template.ts +115 -0
- package/src/provider/extra-providers.ts +347 -0
- package/src/provider/free-tier-error.ts +1 -1
- package/src/provider/kilo-deprecation.ts +30 -0
- package/src/provider/kilo.ts +44 -7
- package/src/provider/model-discovery.ts +690 -0
- package/src/provider/models.ts +11 -1
- package/src/provider/parity-loaders.ts +595 -0
- package/src/provider/provider.ts +309 -203
- package/src/provider/retry-fetch.ts +5 -0
- package/src/provider/transform.ts +168 -17
- package/src/session/compaction.ts +18 -2
- package/src/session/index.ts +30 -3
- package/src/session/message-v2.ts +318 -32
- package/src/session/processor.ts +274 -140
- package/src/session/prompt.ts +154 -128
- package/src/session/retry.ts +26 -13
- package/src/session/summary.ts +4 -1
- package/src/tool/bash.ts +232 -45
- package/src/tool/bash.txt +1 -1
- package/src/tool/codesearch.ts +15 -78
- package/src/tool/edit.ts +111 -30
- package/src/tool/glob.ts +2 -1
- package/src/tool/grep.ts +175 -57
- package/src/tool/ls.ts +2 -1
- package/src/tool/multiedit.ts +3 -1
- package/src/tool/read.ts +169 -41
- package/src/tool/task.ts +26 -18
- package/src/tool/tool.ts +4 -1
- package/src/tool/webfetch.ts +68 -56
- package/src/tool/webfetch.txt +4 -4
- package/src/tool/websearch.ts +20 -75
- package/src/tool/websearch.txt +2 -0
- package/src/tool/write.ts +23 -6
- package/src/util/bom.ts +32 -0
- package/src/util/exa-mcp.ts +82 -0
- package/src/util/legacy-error-names.ts +39 -0
- package/src/util/read-bounded.ts +32 -0
- package/src/util/sse-usage-extractor.ts +67 -13
- package/src/util/timeout.ts +3 -4
- package/src/util/verbose-fetch.ts +40 -1
package/src/cli/argv.ts
CHANGED
|
@@ -105,7 +105,7 @@ export function getCompactionSafetyMarginFromProcessArgv(): string | null {
|
|
|
105
105
|
/**
|
|
106
106
|
* Extract --compaction-models argument directly from process.argv
|
|
107
107
|
* The value is a links notation references sequence, e.g.:
|
|
108
|
-
* "(kilo/nemotron-3-
|
|
108
|
+
* "(kilo/nemotron-3-ultra-free kilo/nemotron-3.5-lightning-free same)"
|
|
109
109
|
* @returns The compaction models argument from CLI or null if not found
|
|
110
110
|
* @see https://github.com/link-assistant/agent/issues/232
|
|
111
111
|
*/
|
package/src/cli/cmd/auth.ts
CHANGED
|
@@ -359,6 +359,10 @@ export const AuthLoginCommand = cmd({
|
|
|
359
359
|
togetherai: 'https://api.together.xyz/settings/api-keys',
|
|
360
360
|
deepseek: 'https://platform.deepseek.com/api_keys',
|
|
361
361
|
xai: 'https://console.x.ai',
|
|
362
|
+
poe: 'https://poe.com/api_key',
|
|
363
|
+
digitalocean: 'https://cloud.digitalocean.com/gen-ai/model-access-keys',
|
|
364
|
+
gitlab: 'https://gitlab.com/-/user_settings/personal_access_tokens',
|
|
365
|
+
azure: 'https://ai.azure.com/',
|
|
362
366
|
};
|
|
363
367
|
|
|
364
368
|
if (apiKeyUrls[provider]) {
|
package/src/cli/cmd/models.ts
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import type { Argv } from 'yargs';
|
|
2
2
|
import { Instance } from '../../project/instance';
|
|
3
3
|
import { Provider } from '../../provider/provider';
|
|
4
|
+
import { ModelsDev } from '../../provider/models';
|
|
5
|
+
import { ModelDiscovery } from '../../provider/model-discovery';
|
|
6
|
+
import { ExtraProviders } from '../../provider/extra-providers';
|
|
4
7
|
import { cmd } from './cmd';
|
|
5
8
|
import { UI } from '../ui';
|
|
6
9
|
import { EOL } from 'os';
|
|
7
10
|
|
|
11
|
+
function isFree(model: ModelsDev.Model) {
|
|
12
|
+
return model.cost?.input === 0 && model.cost?.output === 0;
|
|
13
|
+
}
|
|
14
|
+
|
|
8
15
|
export const ModelsCommand = cmd({
|
|
9
16
|
command: 'models [provider]',
|
|
10
17
|
describe: 'list all available models',
|
|
@@ -19,39 +26,87 @@ export const ModelsCommand = cmd({
|
|
|
19
26
|
describe:
|
|
20
27
|
'use more verbose model output (includes metadata like costs)',
|
|
21
28
|
type: 'boolean',
|
|
29
|
+
})
|
|
30
|
+
.option('discover', {
|
|
31
|
+
describe:
|
|
32
|
+
"also list models from each provider's own /models endpoint (cached for an hour)",
|
|
33
|
+
type: 'boolean',
|
|
34
|
+
})
|
|
35
|
+
.option('refresh', {
|
|
36
|
+
describe:
|
|
37
|
+
'refetch the models.dev catalogue and provider model listings, ignoring caches (implies --discover)',
|
|
38
|
+
type: 'boolean',
|
|
39
|
+
})
|
|
40
|
+
.option('free', {
|
|
41
|
+
describe: 'only list models that cost nothing to use',
|
|
42
|
+
type: 'boolean',
|
|
22
43
|
});
|
|
23
44
|
},
|
|
24
45
|
handler: async (args) => {
|
|
46
|
+
if (args.refresh) await ModelsDev.refresh();
|
|
47
|
+
const discover = Boolean(args.discover || args.refresh);
|
|
48
|
+
|
|
25
49
|
await Instance.provide({
|
|
26
50
|
directory: process.cwd(),
|
|
27
51
|
async fn() {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
52
|
+
async function printModels(providerID: string) {
|
|
53
|
+
const provider = await Provider.getProvider(providerID);
|
|
54
|
+
if (!provider) return false;
|
|
55
|
+
const models: Record<string, ModelsDev.Model> = {
|
|
56
|
+
...provider.info.models,
|
|
57
|
+
};
|
|
58
|
+
const unpriced = new Set<string>();
|
|
59
|
+
const keyless = ExtraProviders.isKeyless(provider.info.id);
|
|
60
|
+
if (discover) {
|
|
61
|
+
const result = await Provider.discoverModels(providerID, {
|
|
62
|
+
force: Boolean(args.refresh),
|
|
63
|
+
});
|
|
64
|
+
if (result.status === 'error') {
|
|
65
|
+
UI.error(
|
|
66
|
+
`${providerID}: model discovery failed: ${result.error}`
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
const s = await Provider.state();
|
|
70
|
+
for (const found of result.models) {
|
|
71
|
+
if (models[found.id]) continue;
|
|
72
|
+
const metadata = ModelDiscovery.catalogueMetadata(
|
|
73
|
+
s.database,
|
|
74
|
+
found.model ?? found.id,
|
|
75
|
+
provider.info.id
|
|
76
|
+
);
|
|
77
|
+
// A listing without prices says nothing about the cost, except
|
|
78
|
+
// on keyless providers (gpt4free, local servers), which are free.
|
|
79
|
+
if (!keyless && !found.free && !found.cost && !metadata?.cost) {
|
|
80
|
+
unpriced.add(found.id);
|
|
81
|
+
}
|
|
82
|
+
models[found.id] = ModelDiscovery.toModelInfo(found, {
|
|
83
|
+
metadata,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
for (const [modelID, model] of Object.entries(models)) {
|
|
88
|
+
if (args.free && (unpriced.has(modelID) || !isFree(model)))
|
|
89
|
+
continue;
|
|
90
|
+
process.stdout.write(`${provider.info.id}/${modelID}`);
|
|
34
91
|
process.stdout.write(EOL);
|
|
35
|
-
if (verbose) {
|
|
92
|
+
if (args.verbose) {
|
|
36
93
|
process.stdout.write(JSON.stringify(model, null, 2));
|
|
37
94
|
process.stdout.write(EOL);
|
|
38
95
|
}
|
|
39
96
|
}
|
|
97
|
+
return true;
|
|
40
98
|
}
|
|
41
99
|
|
|
42
100
|
if (args.provider) {
|
|
43
|
-
|
|
44
|
-
if (!provider) {
|
|
101
|
+
if (!(await printModels(args.provider))) {
|
|
45
102
|
UI.error(`Provider not found: ${args.provider}`);
|
|
46
|
-
return;
|
|
47
103
|
}
|
|
48
|
-
|
|
49
|
-
printModels(args.provider, args.verbose);
|
|
50
104
|
return;
|
|
51
105
|
}
|
|
52
106
|
|
|
107
|
+
const providers = await Provider.list();
|
|
53
108
|
for (const providerID of Object.keys(providers)) {
|
|
54
|
-
printModels(providerID
|
|
109
|
+
await printModels(providerID);
|
|
55
110
|
}
|
|
56
111
|
},
|
|
57
112
|
});
|
package/src/cli/cmd/run.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { EOL } from 'os';
|
|
|
9
9
|
import { select } from '@clack/prompts';
|
|
10
10
|
import { createOpencodeClient, type OpencodeClient } from '@opencode-ai/sdk';
|
|
11
11
|
import { Provider } from '../../provider/provider';
|
|
12
|
+
import { legacyErrorObject } from '../../util/legacy-error-names';
|
|
12
13
|
|
|
13
14
|
const TOOL: Record<string, [string, string]> = {
|
|
14
15
|
todowrite: ['Todo', UI.Style.TEXT_WARNING_BOLD],
|
|
@@ -267,7 +268,12 @@ export const RunCommand = cmd({
|
|
|
267
268
|
// Always include the human-readable string alongside the
|
|
268
269
|
// machine-readable object so consumers never print
|
|
269
270
|
// "[object Object]" (#289).
|
|
270
|
-
if (
|
|
271
|
+
if (
|
|
272
|
+
outputJsonEvent('error', {
|
|
273
|
+
message: err,
|
|
274
|
+
error: legacyErrorObject(props.error),
|
|
275
|
+
})
|
|
276
|
+
)
|
|
271
277
|
continue;
|
|
272
278
|
UI.error(err);
|
|
273
279
|
}
|
|
@@ -356,6 +356,10 @@ export async function runContinuousServerMode(
|
|
|
356
356
|
const parts = [{ type: 'text', text: messageText }];
|
|
357
357
|
|
|
358
358
|
// Create a promise to wait for this message to complete
|
|
359
|
+
// `stopIdleWait` ends the wait when the prompt settles without ever
|
|
360
|
+
// going idle (it can throw before the loop starts); otherwise the
|
|
361
|
+
// subscription leaked and the input queue hung (issue #335).
|
|
362
|
+
let stopIdleWait = () => {};
|
|
359
363
|
const messagePromise = new Promise((resolve) => {
|
|
360
364
|
const checkIdle = Bus.subscribeAll((event) => {
|
|
361
365
|
if (
|
|
@@ -366,6 +370,10 @@ export async function runContinuousServerMode(
|
|
|
366
370
|
resolve();
|
|
367
371
|
}
|
|
368
372
|
});
|
|
373
|
+
stopIdleWait = () => {
|
|
374
|
+
checkIdle();
|
|
375
|
+
resolve();
|
|
376
|
+
};
|
|
369
377
|
});
|
|
370
378
|
|
|
371
379
|
// Send message
|
|
@@ -383,21 +391,28 @@ export async function runContinuousServerMode(
|
|
|
383
391
|
temperature,
|
|
384
392
|
}),
|
|
385
393
|
}
|
|
386
|
-
)
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
+
)
|
|
395
|
+
// Read the reply to the end: it completes only after the prompt has
|
|
396
|
+
// returned (and released the connection), successful or not.
|
|
397
|
+
.then((response) => response.text())
|
|
398
|
+
.catch((error) => {
|
|
399
|
+
hasError = true;
|
|
400
|
+
eventHandler.output({
|
|
401
|
+
type: 'error',
|
|
402
|
+
timestamp: Date.now(),
|
|
403
|
+
sessionID,
|
|
404
|
+
message: errorText(error),
|
|
405
|
+
error: error instanceof Error ? error.message : String(error),
|
|
406
|
+
});
|
|
394
407
|
});
|
|
395
|
-
});
|
|
396
408
|
|
|
397
409
|
// See the direct-mode comment below: the request rejection must be
|
|
398
|
-
// observed before the process can exit (#290).
|
|
399
|
-
|
|
410
|
+
// observed before the process can exit (#290). `session.idle` is
|
|
411
|
+
// published before the reply is written, so once the reply is complete
|
|
412
|
+
// the idle wait can stop.
|
|
400
413
|
await requestPromise;
|
|
414
|
+
stopIdleWait();
|
|
415
|
+
await messagePromise;
|
|
401
416
|
isProcessing = false;
|
|
402
417
|
|
|
403
418
|
// Process next pending message if any
|
|
@@ -582,6 +597,10 @@ export async function runContinuousDirectMode(
|
|
|
582
597
|
const parts = [{ type: 'text', text: messageText }];
|
|
583
598
|
|
|
584
599
|
// Create a promise to wait for this message to complete
|
|
600
|
+
// `stopIdleWait` ends the wait when the prompt settles without ever
|
|
601
|
+
// going idle (it can throw before the loop starts); otherwise the
|
|
602
|
+
// subscription leaked and the input queue hung (issue #335).
|
|
603
|
+
let stopIdleWait = () => {};
|
|
585
604
|
const messagePromise = new Promise((resolve) => {
|
|
586
605
|
const checkIdle = Bus.subscribeAll((event) => {
|
|
587
606
|
if (
|
|
@@ -592,6 +611,10 @@ export async function runContinuousDirectMode(
|
|
|
592
611
|
resolve();
|
|
593
612
|
}
|
|
594
613
|
});
|
|
614
|
+
stopIdleWait = () => {
|
|
615
|
+
checkIdle();
|
|
616
|
+
resolve();
|
|
617
|
+
};
|
|
595
618
|
});
|
|
596
619
|
|
|
597
620
|
// Send message directly
|
|
@@ -618,8 +641,11 @@ export async function runContinuousDirectMode(
|
|
|
618
641
|
// publishes `session.idle` while unwinding, so waiting only on the idle
|
|
619
642
|
// event let the process exit before the rejection handler above ran —
|
|
620
643
|
// no error record, exit code 0 (#290).
|
|
621
|
-
|
|
644
|
+
// `session.idle` is published synchronously before `prompt` settles,
|
|
645
|
+
// so after it settles the idle wait can stop.
|
|
622
646
|
await promptPromise;
|
|
647
|
+
stopIdleWait();
|
|
648
|
+
await messagePromise;
|
|
623
649
|
isProcessing = false;
|
|
624
650
|
|
|
625
651
|
// Process next pending message if any
|
package/src/cli/event-handler.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import { Bus } from '../bus/index.ts';
|
|
7
7
|
import { errorText } from '../util/error-text.ts';
|
|
8
|
+
import { legacyErrorObject } from '../util/legacy-error-names.ts';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Create a subscription for session bus events that outputs events in the selected JSON format.
|
|
@@ -142,7 +143,8 @@ export function outputBusEvent({
|
|
|
142
143
|
timestamp: Date.now(),
|
|
143
144
|
sessionID,
|
|
144
145
|
message: errorText(props.error),
|
|
145
|
-
error
|
|
146
|
+
// --legacy-error-names maps new error names to the previous ones.
|
|
147
|
+
error: legacyErrorObject(props.error),
|
|
146
148
|
});
|
|
147
149
|
}
|
|
148
150
|
}
|
package/src/cli/model-config.js
CHANGED
|
@@ -5,6 +5,8 @@ import {
|
|
|
5
5
|
getCompactionSafetyMarginFromProcessArgv,
|
|
6
6
|
} from './argv.ts';
|
|
7
7
|
import { Log } from '../util/log.ts';
|
|
8
|
+
import { ExtraProviders } from '../provider/extra-providers.ts';
|
|
9
|
+
import { resolveKiloDeprecatedAlias } from '../provider/kilo-deprecation.ts';
|
|
8
10
|
import {
|
|
9
11
|
DEFAULT_PROVIDER_ID,
|
|
10
12
|
DEFAULT_MODEL_ID,
|
|
@@ -30,6 +32,17 @@ export class ModelResolutionError extends Error {
|
|
|
30
32
|
}
|
|
31
33
|
}
|
|
32
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Map a withdrawn built-in Kilo alias to its replacement, with a warning (#335).
|
|
37
|
+
* Other providers and model IDs are returned unchanged.
|
|
38
|
+
* @param {string} providerID
|
|
39
|
+
* @param {string} modelID
|
|
40
|
+
* @returns {string}
|
|
41
|
+
*/
|
|
42
|
+
function replaceDeprecatedModel(providerID, modelID) {
|
|
43
|
+
return providerID === 'kilo' ? resolveKiloDeprecatedAlias(modelID) : modelID;
|
|
44
|
+
}
|
|
45
|
+
|
|
33
46
|
/**
|
|
34
47
|
* Parse model config from argv. Supports "provider/model" or short "model" format.
|
|
35
48
|
* @param {object} argv - Parsed command line arguments
|
|
@@ -124,8 +137,9 @@ export async function parseModelConfig(
|
|
|
124
137
|
if (modelArg.includes('/')) {
|
|
125
138
|
// Explicit provider/model format - respect user's choice
|
|
126
139
|
const modelParts = modelArg.split('/');
|
|
127
|
-
|
|
128
|
-
|
|
140
|
+
// Accept alternative spellings such as `g4f/...` for `gpt4free/...` (#334)
|
|
141
|
+
providerID = ExtraProviders.canonicalID(modelParts[0]);
|
|
142
|
+
modelID = replaceDeprecatedModel(providerID, modelParts.slice(1).join('/'));
|
|
129
143
|
|
|
130
144
|
// Validate that providerID and modelID are not empty
|
|
131
145
|
// Do NOT fall back to defaults - if the user provided an invalid format, fail clearly (#196)
|
|
@@ -152,9 +166,10 @@ export async function parseModelConfig(
|
|
|
152
166
|
const isDefaultModel = !cliModelArg && modelArg === defaultModel;
|
|
153
167
|
try {
|
|
154
168
|
const { Provider } = await import('../provider/provider.ts');
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
169
|
+
// getProvider also enables keyless providers (gpt4free, local servers)
|
|
170
|
+
// on explicit request; routers among them accept unlisted IDs (#334).
|
|
171
|
+
const provider = await Provider.getProvider(providerID);
|
|
172
|
+
if (provider && !(await Provider.hasModel(providerID, modelID))) {
|
|
158
173
|
const liveInfo = await Provider.refreshLiveModelInfo(
|
|
159
174
|
providerID,
|
|
160
175
|
modelID
|
|
@@ -344,7 +359,7 @@ async function resolveCompactionModelEntry(
|
|
|
344
359
|
const parts = modelArg.split('/');
|
|
345
360
|
return {
|
|
346
361
|
providerID: parts[0],
|
|
347
|
-
modelID: parts.slice(1).join('/'),
|
|
362
|
+
modelID: replaceDeprecatedModel(parts[0], parts.slice(1).join('/')),
|
|
348
363
|
useSameModel: false,
|
|
349
364
|
};
|
|
350
365
|
}
|
|
@@ -396,7 +411,7 @@ async function resolveEntryWithinProvider(
|
|
|
396
411
|
}
|
|
397
412
|
return {
|
|
398
413
|
providerID: parts[0],
|
|
399
|
-
modelID: parts.slice(1).join('/'),
|
|
414
|
+
modelID: replaceDeprecatedModel(parts[0], parts.slice(1).join('/')),
|
|
400
415
|
useSameModel: false,
|
|
401
416
|
};
|
|
402
417
|
}
|
|
@@ -405,7 +420,11 @@ async function resolveEntryWithinProvider(
|
|
|
405
420
|
if (!(await Provider.hasModel(baseProviderID, modelArg))) {
|
|
406
421
|
return null;
|
|
407
422
|
}
|
|
408
|
-
return {
|
|
423
|
+
return {
|
|
424
|
+
providerID: baseProviderID,
|
|
425
|
+
modelID: replaceDeprecatedModel(baseProviderID, modelArg),
|
|
426
|
+
useSameModel: false,
|
|
427
|
+
};
|
|
409
428
|
}
|
|
410
429
|
|
|
411
430
|
/**
|
package/src/cli/run-options.js
CHANGED
|
@@ -158,82 +158,148 @@ export function buildRunOptions(yargs, defaultOptions = {}) {
|
|
|
158
158
|
}, true)
|
|
159
159
|
: parser;
|
|
160
160
|
|
|
161
|
-
return
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
161
|
+
return (
|
|
162
|
+
normalizedParser
|
|
163
|
+
.option('generate-title', {
|
|
164
|
+
type: 'boolean',
|
|
165
|
+
description:
|
|
166
|
+
'Generate session titles using AI (default: false). Disabling saves tokens and prevents rate limit issues.',
|
|
167
|
+
default: false,
|
|
168
|
+
})
|
|
169
|
+
.option('retry-timeout', {
|
|
170
|
+
type: 'number',
|
|
171
|
+
description:
|
|
172
|
+
'Maximum total retry time in seconds for rate limit errors (default: 604800 = 7 days)',
|
|
173
|
+
})
|
|
174
|
+
.option('retry-on-rate-limits', {
|
|
175
|
+
type: 'boolean',
|
|
176
|
+
description:
|
|
177
|
+
'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.',
|
|
178
|
+
default: true,
|
|
179
|
+
})
|
|
180
|
+
.option('output-response-model', {
|
|
181
|
+
type: 'boolean',
|
|
182
|
+
description: 'Include model info in step_finish output',
|
|
183
|
+
default: true,
|
|
184
|
+
})
|
|
185
|
+
.option('summarize-session', {
|
|
186
|
+
type: 'boolean',
|
|
187
|
+
description:
|
|
188
|
+
'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).',
|
|
189
|
+
default: true,
|
|
190
|
+
})
|
|
191
|
+
.option('compaction-model', {
|
|
192
|
+
type: 'string',
|
|
193
|
+
description:
|
|
194
|
+
'Model to use for context compaction and session summaries, in format providerID/modelID. Use "same" to use the base model. Default: kilo/nemotron-3-ultra-free (free, Kilo authentication recommended, 1M context), which falls back to the --model model when --model points at another provider. Overridden by --compaction-models if both are specified.',
|
|
195
|
+
default: defaultCompactionModel,
|
|
196
|
+
})
|
|
197
|
+
.option('compaction-models', {
|
|
198
|
+
type: 'string',
|
|
199
|
+
description:
|
|
200
|
+
'Ordered cascade of compaction models in links notation sequence format: "(model1 model2 ... same)". ' +
|
|
201
|
+
"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. " +
|
|
202
|
+
'The special value "same" uses the base model. Overrides --compaction-model when specified. ' +
|
|
203
|
+
'The default cascade is Kilo-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.',
|
|
204
|
+
default: defaultCompactionModels,
|
|
205
|
+
})
|
|
206
|
+
.option('compaction-safety-margin', {
|
|
207
|
+
type: 'number',
|
|
208
|
+
description:
|
|
209
|
+
'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: 25.',
|
|
210
|
+
default: defaultCompactionSafetyMarginPercent,
|
|
211
|
+
})
|
|
212
|
+
.option('temperature', {
|
|
213
|
+
type: 'number',
|
|
214
|
+
description:
|
|
215
|
+
'Override the temperature for model completions. When not set, the default per-model temperature is used.',
|
|
216
|
+
})
|
|
217
|
+
.option('read-only', {
|
|
218
|
+
type: 'boolean',
|
|
219
|
+
description:
|
|
220
|
+
'Enforceable read-only / planning mode. Disables all filesystem-mutating and shell tools (bash, edit, write, multiedit, patch) so the agent can only read, search and plan. Maps to agent-commander --read-only.',
|
|
221
|
+
default: false,
|
|
222
|
+
})
|
|
223
|
+
.option('disable-tools', {
|
|
224
|
+
type: 'string',
|
|
225
|
+
description:
|
|
226
|
+
'Comma-separated list of tool ids to disable (e.g. "bash,write,edit"). Disabled tools are never exposed to the model and are rejected if invoked via batch.',
|
|
227
|
+
})
|
|
228
|
+
.option('permission-mode', {
|
|
229
|
+
type: 'string',
|
|
230
|
+
choices: ['auto', 'plan', 'readonly', 'ask'],
|
|
231
|
+
description:
|
|
232
|
+
'Permission policy for mutating tools. "auto" (default) allows everything; "plan" allows read-only shell and asks before mutations; "readonly" denies all mutations; "ask" asks before every mutating tool. Approvals are driven over JSON (permission_request / permission_response).',
|
|
233
|
+
default: 'auto',
|
|
234
|
+
})
|
|
235
|
+
.option('permission', {
|
|
236
|
+
type: 'string',
|
|
237
|
+
description:
|
|
238
|
+
'OpenCode-compatible permission override as JSON, merged on top of --permission-mode. Example: \'{"edit":"ask","bash":{"git push*":"ask","*":"allow"},"webfetch":"allow"}\'.',
|
|
239
|
+
})
|
|
240
|
+
// Compatibility switches (issue #335). They are resolved (together with
|
|
241
|
+
// their LINK_ASSISTANT_AGENT_* env vars) by initConfig() in
|
|
242
|
+
// src/config/config.ts; they are declared here so they show up in --help.
|
|
243
|
+
// No defaults here, so an env var is not shadowed by a yargs default.
|
|
244
|
+
.option('bash-shell', {
|
|
245
|
+
type: 'string',
|
|
246
|
+
description:
|
|
247
|
+
'Shell for the bash tool: "auto" (default, $SHELL if POSIX, else bash, else /bin/sh), "legacy" (/bin/sh as before) or a shell path. Env: LINK_ASSISTANT_AGENT_BASH_SHELL',
|
|
248
|
+
})
|
|
249
|
+
.option('legacy-tool-output', {
|
|
250
|
+
type: 'boolean',
|
|
251
|
+
description:
|
|
252
|
+
'Restore previous tool output formats (empty edit/write output, head-truncated bash output, empty read past EOF). Env: LINK_ASSISTANT_AGENT_LEGACY_TOOL_OUTPUT',
|
|
253
|
+
})
|
|
254
|
+
.option('legacy-edit-matching', {
|
|
255
|
+
type: 'boolean',
|
|
256
|
+
description:
|
|
257
|
+
'Restore previous edit matching (empty oldString overwrites a file, loose fuzzy thresholds). Env: LINK_ASSISTANT_AGENT_LEGACY_EDIT_MATCHING',
|
|
258
|
+
})
|
|
259
|
+
.option('glob-follow-symlinks', {
|
|
260
|
+
type: 'boolean',
|
|
261
|
+
description:
|
|
262
|
+
'Follow symlinked directories in glob and ls (previous behaviour). Env: LINK_ASSISTANT_AGENT_GLOB_FOLLOW_SYMLINKS',
|
|
263
|
+
})
|
|
264
|
+
.option('grep-hidden', {
|
|
265
|
+
type: 'boolean',
|
|
266
|
+
description:
|
|
267
|
+
'Search hidden files in grep (default true; --no-grep-hidden restores previous behaviour). Env: LINK_ASSISTANT_AGENT_GREP_HIDDEN',
|
|
268
|
+
})
|
|
269
|
+
.option('grep-timeout-ms', {
|
|
270
|
+
type: 'number',
|
|
271
|
+
description:
|
|
272
|
+
'Timeout of one grep search in ms (default 30000, 0 = none). Env: LINK_ASSISTANT_AGENT_GREP_TIMEOUT_MS',
|
|
273
|
+
})
|
|
274
|
+
.option('read-max-lines', {
|
|
275
|
+
type: 'number',
|
|
276
|
+
description:
|
|
277
|
+
'Max lines one read call returns (default 2000, 0 = no cap). Env: LINK_ASSISTANT_AGENT_READ_MAX_LINES',
|
|
278
|
+
})
|
|
279
|
+
.option('read-max-bytes', {
|
|
280
|
+
type: 'number',
|
|
281
|
+
description:
|
|
282
|
+
'Max bytes one read call returns (default 51200, 0 = no cap). Env: LINK_ASSISTANT_AGENT_READ_MAX_BYTES',
|
|
283
|
+
})
|
|
284
|
+
.option('legacy-error-names', {
|
|
285
|
+
type: 'boolean',
|
|
286
|
+
description:
|
|
287
|
+
'Report ContextOverflowError as APIError in JSON output (previous name). Env: LINK_ASSISTANT_AGENT_LEGACY_ERROR_NAMES',
|
|
288
|
+
})
|
|
289
|
+
.option('legacy-token-usage', {
|
|
290
|
+
type: 'boolean',
|
|
291
|
+
description:
|
|
292
|
+
'Report tokens.output including reasoning tokens (previous accounting). Env: LINK_ASSISTANT_AGENT_LEGACY_TOKEN_USAGE',
|
|
293
|
+
})
|
|
294
|
+
.option('legacy-loop-exit', {
|
|
295
|
+
type: 'boolean',
|
|
296
|
+
description:
|
|
297
|
+
'End the turn on a "stop" finish even if tools ran in that step (previous behaviour). Env: LINK_ASSISTANT_AGENT_LEGACY_LOOP_EXIT',
|
|
298
|
+
})
|
|
299
|
+
.option('mcp-connect-timeout', {
|
|
300
|
+
type: 'number',
|
|
301
|
+
description:
|
|
302
|
+
'Default MCP connect/initialize timeout in ms (default 30000, 0 = none). Env: LINK_ASSISTANT_AGENT_MCP_CONNECT_TIMEOUT',
|
|
303
|
+
})
|
|
304
|
+
);
|
|
239
305
|
}
|