@link-assistant/agent 0.18.2 → 0.19.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/README.md +1 -1
- package/package.json +3 -2
- package/src/agent/agent.ts +1 -1
- package/src/bun/index.ts +2 -2
- package/src/cli/cmd/mcp.ts +1 -1
- package/src/cli/cmd/run.ts +1 -2
- package/src/cli/continuous-mode.js +3 -3
- package/src/cli/error.ts +1 -1
- package/src/cli/output.ts +5 -5
- package/src/command/index.ts +1 -1
- package/src/config/config.ts +345 -1116
- package/src/config/file-config.ts +1146 -0
- package/src/file/watcher.ts +3 -3
- package/src/format/index.ts +1 -1
- package/src/index.js +44 -37
- package/src/json-standard/index.ts +5 -5
- package/src/mcp/index.ts +6 -13
- package/src/project/bootstrap.ts +0 -1
- package/src/project/project.ts +0 -1
- package/src/provider/provider.ts +10 -8
- package/src/provider/retry-fetch.ts +11 -11
- package/src/session/agent.js +4 -2
- package/src/session/compaction.ts +5 -5
- package/src/session/index.ts +19 -19
- package/src/session/processor.ts +4 -4
- package/src/session/prompt.ts +5 -5
- package/src/session/retry.ts +9 -9
- package/src/session/summary.ts +8 -8
- package/src/session/system.ts +1 -1
- package/src/snapshot/index.ts +1 -1
- package/src/tool/read.ts +4 -3
- package/src/tool/registry.ts +1 -2
- package/src/tool/websearch.ts +1 -1
- package/src/util/log-lazy.ts +9 -11
- package/src/util/log.ts +9 -8
- package/src/util/verbose-fetch.ts +8 -5
- package/src/flag/flag.ts +0 -212
package/src/session/prompt.ts
CHANGED
|
@@ -24,7 +24,7 @@ import { Instance } from '../project/instance';
|
|
|
24
24
|
import { Bus } from '../bus';
|
|
25
25
|
import { ProviderTransform } from '../provider/transform';
|
|
26
26
|
import { SystemPrompt } from './system';
|
|
27
|
-
import {
|
|
27
|
+
import { config, isVerbose } from '../config/config';
|
|
28
28
|
import { Token } from '../util/token';
|
|
29
29
|
|
|
30
30
|
import PROMPT_PLAN from '../session/prompt/plan.txt';
|
|
@@ -684,7 +684,7 @@ export namespace SessionPrompt {
|
|
|
684
684
|
: [];
|
|
685
685
|
|
|
686
686
|
// Verbose logging: output request details for debugging
|
|
687
|
-
if (
|
|
687
|
+
if (isVerbose()) {
|
|
688
688
|
const systemTokens = system.reduce(
|
|
689
689
|
(acc, s) => acc + Token.estimate(s),
|
|
690
690
|
0
|
|
@@ -766,8 +766,8 @@ export namespace SessionPrompt {
|
|
|
766
766
|
const result = await processor.process(() =>
|
|
767
767
|
streamText({
|
|
768
768
|
timeout: {
|
|
769
|
-
chunkMs:
|
|
770
|
-
stepMs:
|
|
769
|
+
chunkMs: config.streamChunkTimeoutMs,
|
|
770
|
+
stepMs: config.streamStepTimeoutMs,
|
|
771
771
|
},
|
|
772
772
|
onError(error) {
|
|
773
773
|
log.error(() => ({ message: 'stream error', error }));
|
|
@@ -1671,7 +1671,7 @@ export namespace SessionPrompt {
|
|
|
1671
1671
|
modelID: string;
|
|
1672
1672
|
}) {
|
|
1673
1673
|
// Skip title generation if disabled (default)
|
|
1674
|
-
if (!
|
|
1674
|
+
if (!config.generateTitle) {
|
|
1675
1675
|
log.info(() => ({
|
|
1676
1676
|
message: 'title generation disabled',
|
|
1677
1677
|
hint: 'Enable with --generate-title flag or AGENT_GENERATE_TITLE=true',
|
package/src/session/retry.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { MessageV2 } from './message-v2';
|
|
2
|
-
import {
|
|
2
|
+
import { config } from '../config/config';
|
|
3
3
|
import { Log } from '../util/log';
|
|
4
4
|
|
|
5
5
|
export namespace SessionRetry {
|
|
@@ -13,16 +13,16 @@ export namespace SessionRetry {
|
|
|
13
13
|
// This caps exponential backoff when headers are not available
|
|
14
14
|
// Can be configured via AGENT_MAX_RETRY_DELAY env var
|
|
15
15
|
export function getMaxRetryDelay(): number {
|
|
16
|
-
return
|
|
16
|
+
return config.maxRetryDelay * 1000;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
// Get retry timeout in milliseconds
|
|
20
20
|
export function getRetryTimeout(): number {
|
|
21
|
-
return
|
|
21
|
+
return config.retryTimeout * 1000;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
|
-
* Error thrown when retry-after exceeds
|
|
25
|
+
* Error thrown when retry-after exceeds LINK_ASSISTANT_AGENT_RETRY_TIMEOUT
|
|
26
26
|
* This indicates the wait time is too long and we should fail immediately
|
|
27
27
|
*/
|
|
28
28
|
export class RetryTimeoutExceededError extends Error {
|
|
@@ -34,7 +34,7 @@ export namespace SessionRetry {
|
|
|
34
34
|
const maxTimeoutHours = (maxTimeoutMs / 1000 / 3600).toFixed(2);
|
|
35
35
|
super(
|
|
36
36
|
`API returned retry-after of ${retryAfterHours} hours, which exceeds the maximum retry timeout of ${maxTimeoutHours} hours. ` +
|
|
37
|
-
`Failing immediately instead of waiting. You can adjust
|
|
37
|
+
`Failing immediately instead of waiting. You can adjust LINK_ASSISTANT_AGENT_RETRY_TIMEOUT env var to increase the limit.`
|
|
38
38
|
);
|
|
39
39
|
this.name = 'RetryTimeoutExceededError';
|
|
40
40
|
this.retryAfterMs = retryAfterMs;
|
|
@@ -75,7 +75,7 @@ export namespace SessionRetry {
|
|
|
75
75
|
sessionID: string,
|
|
76
76
|
errorType: string
|
|
77
77
|
): { shouldRetry: boolean; elapsedTime: number; maxTime: number } {
|
|
78
|
-
const maxTime =
|
|
78
|
+
const maxTime = config.retryTimeout * 1000; // Convert to ms
|
|
79
79
|
const state = retryStates.get(sessionID);
|
|
80
80
|
|
|
81
81
|
if (!state || state.errorType !== errorType) {
|
|
@@ -198,15 +198,15 @@ export namespace SessionRetry {
|
|
|
198
198
|
*
|
|
199
199
|
* RETRY LOGIC (per issue #157 requirements):
|
|
200
200
|
* 1. If retry-after header is available:
|
|
201
|
-
* - If retry-after <=
|
|
202
|
-
* - If retry-after >
|
|
201
|
+
* - If retry-after <= LINK_ASSISTANT_AGENT_RETRY_TIMEOUT: use it directly (exact time)
|
|
202
|
+
* - If retry-after > LINK_ASSISTANT_AGENT_RETRY_TIMEOUT: throw RetryTimeoutExceededError (fail immediately)
|
|
203
203
|
* 2. If no retry-after header:
|
|
204
204
|
* - Use exponential backoff up to AGENT_MAX_RETRY_DELAY
|
|
205
205
|
*
|
|
206
206
|
* Adds jitter to prevent thundering herd when multiple requests retry.
|
|
207
207
|
* See: https://github.com/link-assistant/agent/issues/157
|
|
208
208
|
*
|
|
209
|
-
* @throws {RetryTimeoutExceededError} When retry-after exceeds
|
|
209
|
+
* @throws {RetryTimeoutExceededError} When retry-after exceeds LINK_ASSISTANT_AGENT_RETRY_TIMEOUT
|
|
210
210
|
*/
|
|
211
211
|
export function delay(error: MessageV2.APIError, attempt: number): number {
|
|
212
212
|
const maxRetryTimeout = getRetryTimeout();
|
package/src/session/summary.ts
CHANGED
|
@@ -13,7 +13,7 @@ import path from 'path';
|
|
|
13
13
|
import { Instance } from '../project/instance';
|
|
14
14
|
import { Storage } from '../storage/storage';
|
|
15
15
|
import { Bus } from '../bus';
|
|
16
|
-
import {
|
|
16
|
+
import { config, isVerbose } from '../config/config';
|
|
17
17
|
import { Token } from '../util/token';
|
|
18
18
|
|
|
19
19
|
export namespace SessionSummary {
|
|
@@ -83,7 +83,7 @@ export namespace SessionSummary {
|
|
|
83
83
|
|
|
84
84
|
// Skip AI-powered summarization if disabled
|
|
85
85
|
// See: https://github.com/link-assistant/agent/issues/217
|
|
86
|
-
if (!
|
|
86
|
+
if (!config.summarizeSession) {
|
|
87
87
|
log.info(() => ({
|
|
88
88
|
message: 'session summarization disabled',
|
|
89
89
|
hint: 'Enable with --summarize-session flag (enabled by default) or AGENT_SUMMARIZE_SESSION=true',
|
|
@@ -141,7 +141,7 @@ export namespace SessionSummary {
|
|
|
141
141
|
return;
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
if (
|
|
144
|
+
if (isVerbose()) {
|
|
145
145
|
log.info(() => ({
|
|
146
146
|
message: 'summarization model loaded',
|
|
147
147
|
providerID: model.providerID,
|
|
@@ -167,7 +167,7 @@ export namespace SessionSummary {
|
|
|
167
167
|
</text>
|
|
168
168
|
`;
|
|
169
169
|
|
|
170
|
-
if (
|
|
170
|
+
if (isVerbose()) {
|
|
171
171
|
log.info(() => ({
|
|
172
172
|
message: 'generating title via API',
|
|
173
173
|
providerID: model.providerID,
|
|
@@ -203,7 +203,7 @@ export namespace SessionSummary {
|
|
|
203
203
|
model: model.language,
|
|
204
204
|
});
|
|
205
205
|
|
|
206
|
-
if (
|
|
206
|
+
if (isVerbose()) {
|
|
207
207
|
log.info(() => ({
|
|
208
208
|
message: 'title API response received',
|
|
209
209
|
providerID: model.providerID,
|
|
@@ -234,7 +234,7 @@ export namespace SessionSummary {
|
|
|
234
234
|
const modelMessages = await MessageV2.toModelMessage(messages);
|
|
235
235
|
const conversationContent = JSON.stringify(modelMessages);
|
|
236
236
|
|
|
237
|
-
if (
|
|
237
|
+
if (isVerbose()) {
|
|
238
238
|
log.info(() => ({
|
|
239
239
|
message: 'generating body summary via API',
|
|
240
240
|
providerID: model.providerID,
|
|
@@ -264,7 +264,7 @@ export namespace SessionSummary {
|
|
|
264
264
|
],
|
|
265
265
|
headers: model.info.headers,
|
|
266
266
|
}).catch((err) => {
|
|
267
|
-
if (
|
|
267
|
+
if (isVerbose()) {
|
|
268
268
|
log.warn(() => ({
|
|
269
269
|
message: 'body summary API call failed',
|
|
270
270
|
providerID: model.providerID,
|
|
@@ -276,7 +276,7 @@ export namespace SessionSummary {
|
|
|
276
276
|
return undefined;
|
|
277
277
|
});
|
|
278
278
|
if (result) {
|
|
279
|
-
if (
|
|
279
|
+
if (isVerbose()) {
|
|
280
280
|
log.info(() => ({
|
|
281
281
|
message: 'body summary API response received',
|
|
282
282
|
providerID: model.providerID,
|
package/src/session/system.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Ripgrep } from '../file/ripgrep';
|
|
2
2
|
import { Global } from '../global';
|
|
3
3
|
import { Filesystem } from '../util/filesystem';
|
|
4
|
-
import { Config } from '../config/config';
|
|
4
|
+
import { Config } from '../config/file-config';
|
|
5
5
|
|
|
6
6
|
import { Instance } from '../project/instance';
|
|
7
7
|
import path from 'path';
|
package/src/snapshot/index.ts
CHANGED
|
@@ -4,7 +4,7 @@ import fs from 'fs/promises';
|
|
|
4
4
|
import { Log } from '../util/log';
|
|
5
5
|
import { Global } from '../global';
|
|
6
6
|
import z from 'zod';
|
|
7
|
-
import { Config } from '../config/config';
|
|
7
|
+
import { Config } from '../config/file-config';
|
|
8
8
|
import { Instance } from '../project/instance';
|
|
9
9
|
|
|
10
10
|
export namespace Snapshot {
|
package/src/tool/read.ts
CHANGED
|
@@ -4,6 +4,7 @@ import * as path from 'path';
|
|
|
4
4
|
import { Tool } from './tool';
|
|
5
5
|
import { FileTime } from '../file/time';
|
|
6
6
|
import DESCRIPTION from './read.txt';
|
|
7
|
+
import { config } from '../config/config';
|
|
7
8
|
import { Filesystem } from '../util/filesystem';
|
|
8
9
|
import { Instance } from '../project/instance';
|
|
9
10
|
import { Provider } from '../provider/provider';
|
|
@@ -70,8 +71,8 @@ export const ReadTool = Tool.define('read', {
|
|
|
70
71
|
return model.info.modalities?.input?.includes('image') ?? false;
|
|
71
72
|
})();
|
|
72
73
|
if (isImage) {
|
|
73
|
-
// Image format validation (can be disabled via
|
|
74
|
-
const verifyImages =
|
|
74
|
+
// Image format validation (can be disabled via LINK_ASSISTANT_AGENT_VERIFY_IMAGES_AT_READ_TOOL)
|
|
75
|
+
const verifyImages = config.verifyImagesAtReadTool;
|
|
75
76
|
if (verifyImages && !supportsImages) {
|
|
76
77
|
throw new Error(
|
|
77
78
|
`Failed to read image: ${filepath}, model may not be able to read images`
|
|
@@ -90,7 +91,7 @@ export const ReadTool = Tool.define('read', {
|
|
|
90
91
|
)
|
|
91
92
|
.map((b) => b.toString(16).padStart(2, '0'))
|
|
92
93
|
.join(' ')}\n` +
|
|
93
|
-
`To disable image validation, set environment variable:
|
|
94
|
+
`To disable image validation, set environment variable: LINK_ASSISTANT_AGENT_VERIFY_IMAGES_AT_READ_TOOL=false`
|
|
94
95
|
);
|
|
95
96
|
}
|
|
96
97
|
}
|
package/src/tool/registry.ts
CHANGED
|
@@ -13,10 +13,9 @@ import { InvalidTool } from './invalid';
|
|
|
13
13
|
import type { Agent } from '../agent/agent';
|
|
14
14
|
import { Tool } from './tool';
|
|
15
15
|
import { Instance } from '../project/instance';
|
|
16
|
-
import { Config } from '../config/config';
|
|
16
|
+
import { Config } from '../config/file-config';
|
|
17
17
|
import { WebSearchTool } from './websearch';
|
|
18
18
|
import { CodeSearchTool } from './codesearch';
|
|
19
|
-
import { Flag } from '../flag/flag';
|
|
20
19
|
|
|
21
20
|
export namespace ToolRegistry {
|
|
22
21
|
export const state = Instance.state(async () => {
|
package/src/tool/websearch.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import z from 'zod';
|
|
2
2
|
import { Tool } from './tool';
|
|
3
3
|
import DESCRIPTION from './websearch.txt';
|
|
4
|
-
import { Config } from '../config/config';
|
|
4
|
+
import { Config } from '../config/file-config';
|
|
5
5
|
import { createVerboseFetch } from '../util/verbose-fetch';
|
|
6
6
|
|
|
7
7
|
const verboseFetch = createVerboseFetch(fetch, { caller: 'websearch' });
|
package/src/util/log-lazy.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import makeLog, { levels, LogLevel } from 'log-lazy';
|
|
2
|
-
import {
|
|
2
|
+
import { config, isVerbose } from '../config/config';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* JSON Lazy Logger
|
|
@@ -53,10 +53,8 @@ const LEVEL_PRESETS = {
|
|
|
53
53
|
|
|
54
54
|
type LevelPreset = keyof typeof LEVEL_PRESETS;
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
// Compact JSON mode (can be set at runtime, initialized from Flag)
|
|
59
|
-
let compactJsonMode = Flag.COMPACT_JSON();
|
|
56
|
+
// Compact JSON mode (can be set at runtime, initialized from config)
|
|
57
|
+
let compactJsonMode = false; // Deferred to avoid circular init; config.compactJson checked at runtime
|
|
60
58
|
|
|
61
59
|
/**
|
|
62
60
|
* Set compact JSON output mode
|
|
@@ -99,8 +97,8 @@ function formatLogEntry(
|
|
|
99
97
|
logEntry.message = String(data);
|
|
100
98
|
}
|
|
101
99
|
|
|
102
|
-
// Check both local setting and global
|
|
103
|
-
const useCompact = compactJsonMode ||
|
|
100
|
+
// Check both local setting and global config
|
|
101
|
+
const useCompact = compactJsonMode || config.compactJson;
|
|
104
102
|
return useCompact
|
|
105
103
|
? JSON.stringify(logEntry)
|
|
106
104
|
: JSON.stringify(logEntry, null, 2);
|
|
@@ -158,7 +156,7 @@ export function createLazyLogger(
|
|
|
158
156
|
initialTags?: Record<string, unknown>
|
|
159
157
|
): LazyLogger {
|
|
160
158
|
// Determine initial log level based on verbose flag
|
|
161
|
-
const initialLevel =
|
|
159
|
+
const initialLevel = isVerbose() ? LEVEL_VERBOSE : LEVEL_DISABLED;
|
|
162
160
|
|
|
163
161
|
// Create base log-lazy instance
|
|
164
162
|
const baseLog = makeLog({ level: initialLevel });
|
|
@@ -281,7 +279,7 @@ export function createLazyLogger(
|
|
|
281
279
|
|
|
282
280
|
// Configuration
|
|
283
281
|
Object.defineProperty(wrappedLog, 'enabled', {
|
|
284
|
-
get: () =>
|
|
282
|
+
get: () => isVerbose(),
|
|
285
283
|
enumerable: true,
|
|
286
284
|
});
|
|
287
285
|
|
|
@@ -296,10 +294,10 @@ export const lazyLog = createLazyLogger({ service: 'agent' });
|
|
|
296
294
|
|
|
297
295
|
/**
|
|
298
296
|
* Utility to update the global logger level at runtime
|
|
299
|
-
* Call this after
|
|
297
|
+
* Call this after setVerbose() to sync the logger state
|
|
300
298
|
*/
|
|
301
299
|
export function syncLoggerWithVerboseFlag(): void {
|
|
302
|
-
if (
|
|
300
|
+
if (isVerbose()) {
|
|
303
301
|
lazyLog.setLevel('verbose');
|
|
304
302
|
} else {
|
|
305
303
|
lazyLog.setLevel('disabled');
|
package/src/util/log.ts
CHANGED
|
@@ -3,7 +3,7 @@ import fs from 'fs/promises';
|
|
|
3
3
|
import { Global } from '../global';
|
|
4
4
|
import z from 'zod';
|
|
5
5
|
import makeLog, { levels } from 'log-lazy';
|
|
6
|
-
import {
|
|
6
|
+
import { config, isVerbose } from '../config/config';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Logging module with JSON output and lazy evaluation support.
|
|
@@ -32,7 +32,7 @@ export namespace Log {
|
|
|
32
32
|
|
|
33
33
|
let level: Level = 'INFO';
|
|
34
34
|
let jsonOutput = false; // Whether to output JSON format (enabled in verbose mode)
|
|
35
|
-
let compactJsonOutput =
|
|
35
|
+
let compactJsonOutput = false; // Whether to use compact JSON (single line); deferred to avoid circular init
|
|
36
36
|
|
|
37
37
|
function shouldLog(input: Level): boolean {
|
|
38
38
|
return levelPriority[input] >= levelPriority[level];
|
|
@@ -109,8 +109,9 @@ export namespace Log {
|
|
|
109
109
|
// Always use JSON output format for logs
|
|
110
110
|
jsonOutput = true;
|
|
111
111
|
|
|
112
|
-
// Configure lazy logging level based on verbose flag
|
|
113
|
-
|
|
112
|
+
// Configure lazy logging level based on verbose flag (with env var fallback)
|
|
113
|
+
// See: https://github.com/link-assistant/agent/issues/227
|
|
114
|
+
if (isVerbose() || options.print) {
|
|
114
115
|
// Enable all levels for lazy logging when verbose
|
|
115
116
|
lazyLogInstance = makeLog({
|
|
116
117
|
level: levels.debug | levels.info | levels.warn | levels.error,
|
|
@@ -208,8 +209,8 @@ export namespace Log {
|
|
|
208
209
|
}
|
|
209
210
|
|
|
210
211
|
// Use compact or pretty format based on configuration
|
|
211
|
-
// Check both local setting and global
|
|
212
|
-
const useCompact = compactJsonOutput ||
|
|
212
|
+
// Check both local setting and global config
|
|
213
|
+
const useCompact = compactJsonOutput || config.compactJson;
|
|
213
214
|
return useCompact
|
|
214
215
|
? JSON.stringify(logEntry)
|
|
215
216
|
: JSON.stringify(logEntry, null, 2);
|
|
@@ -371,10 +372,10 @@ export namespace Log {
|
|
|
371
372
|
|
|
372
373
|
/**
|
|
373
374
|
* Sync lazy logging with verbose flag at runtime
|
|
374
|
-
* Call after
|
|
375
|
+
* Call after setVerbose() to update lazy logging state
|
|
375
376
|
*/
|
|
376
377
|
export function syncWithVerboseFlag(): void {
|
|
377
|
-
if (
|
|
378
|
+
if (isVerbose()) {
|
|
378
379
|
jsonOutput = true;
|
|
379
380
|
// Use stdout for verbose output (following Unix conventions)
|
|
380
381
|
write = (msg: any) => process.stdout.write(msg);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Log } from './log';
|
|
2
|
-
import {
|
|
2
|
+
import { isVerbose } from '../config/config';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Shared verbose HTTP fetch wrapper.
|
|
@@ -14,7 +14,7 @@ import { Flag } from '../flag/flag';
|
|
|
14
14
|
* - Logs HTTP errors: stack trace, error cause chain
|
|
15
15
|
* - Sequential call numbering for correlation
|
|
16
16
|
* - Error-resilient: logging failures never break the actual HTTP request
|
|
17
|
-
* - Runtime verbose check: respects
|
|
17
|
+
* - Runtime verbose check: respects isVerbose() at call time (with env var fallback)
|
|
18
18
|
*
|
|
19
19
|
* @see https://github.com/link-assistant/agent/issues/215
|
|
20
20
|
*/
|
|
@@ -98,7 +98,7 @@ export interface VerboseFetchOptions {
|
|
|
98
98
|
/**
|
|
99
99
|
* Wrap a fetch function with verbose HTTP logging.
|
|
100
100
|
*
|
|
101
|
-
* When
|
|
101
|
+
* When isVerbose() returns true, logs all HTTP requests and responses
|
|
102
102
|
* as JSON objects. When verbose is false, returns a no-op passthrough.
|
|
103
103
|
*
|
|
104
104
|
* All logging is wrapped in try/catch so it never breaks the actual HTTP request.
|
|
@@ -121,8 +121,11 @@ export function createVerboseFetch(
|
|
|
121
121
|
input: RequestInfo | URL,
|
|
122
122
|
init?: RequestInit
|
|
123
123
|
): Promise<Response> => {
|
|
124
|
-
// Check verbose flag at call time
|
|
125
|
-
|
|
124
|
+
// Check verbose flag at call time, with env var fallback for resilience.
|
|
125
|
+
// Uses isVerbose() which checks both the in-memory flag and environment
|
|
126
|
+
// variables, preventing silent logging loss when the flag state is disrupted.
|
|
127
|
+
// See: https://github.com/link-assistant/agent/issues/227
|
|
128
|
+
if (!isVerbose()) {
|
|
126
129
|
return innerFetch(input, init);
|
|
127
130
|
}
|
|
128
131
|
|
package/src/flag/flag.ts
DELETED
|
@@ -1,212 +0,0 @@
|
|
|
1
|
-
export namespace Flag {
|
|
2
|
-
// Helper to check env vars with new prefix first, then fall back to old prefix for backwards compatibility
|
|
3
|
-
function getEnv(newKey: string, oldKey: string): string | undefined {
|
|
4
|
-
return process.env[newKey] ?? process.env[oldKey];
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
function truthy(key: string) {
|
|
8
|
-
const value = process.env[key]?.toLowerCase();
|
|
9
|
-
return value === 'true' || value === '1';
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function truthyCompat(newKey: string, oldKey: string): boolean {
|
|
13
|
-
const value = (getEnv(newKey, oldKey) ?? '').toLowerCase();
|
|
14
|
-
return value === 'true' || value === '1';
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
// LINK_ASSISTANT_AGENT_AUTO_SHARE removed - no sharing support
|
|
18
|
-
export const OPENCODE_CONFIG = getEnv(
|
|
19
|
-
'LINK_ASSISTANT_AGENT_CONFIG',
|
|
20
|
-
'OPENCODE_CONFIG'
|
|
21
|
-
);
|
|
22
|
-
export const OPENCODE_CONFIG_DIR = getEnv(
|
|
23
|
-
'LINK_ASSISTANT_AGENT_CONFIG_DIR',
|
|
24
|
-
'OPENCODE_CONFIG_DIR'
|
|
25
|
-
);
|
|
26
|
-
export const OPENCODE_CONFIG_CONTENT = getEnv(
|
|
27
|
-
'LINK_ASSISTANT_AGENT_CONFIG_CONTENT',
|
|
28
|
-
'OPENCODE_CONFIG_CONTENT'
|
|
29
|
-
);
|
|
30
|
-
export const OPENCODE_DISABLE_AUTOUPDATE = truthyCompat(
|
|
31
|
-
'LINK_ASSISTANT_AGENT_DISABLE_AUTOUPDATE',
|
|
32
|
-
'OPENCODE_DISABLE_AUTOUPDATE'
|
|
33
|
-
);
|
|
34
|
-
export const OPENCODE_DISABLE_PRUNE = truthyCompat(
|
|
35
|
-
'LINK_ASSISTANT_AGENT_DISABLE_PRUNE',
|
|
36
|
-
'OPENCODE_DISABLE_PRUNE'
|
|
37
|
-
);
|
|
38
|
-
export const OPENCODE_ENABLE_EXPERIMENTAL_MODELS = truthyCompat(
|
|
39
|
-
'LINK_ASSISTANT_AGENT_ENABLE_EXPERIMENTAL_MODELS',
|
|
40
|
-
'OPENCODE_ENABLE_EXPERIMENTAL_MODELS'
|
|
41
|
-
);
|
|
42
|
-
export const OPENCODE_DISABLE_AUTOCOMPACT = truthyCompat(
|
|
43
|
-
'LINK_ASSISTANT_AGENT_DISABLE_AUTOCOMPACT',
|
|
44
|
-
'OPENCODE_DISABLE_AUTOCOMPACT'
|
|
45
|
-
);
|
|
46
|
-
|
|
47
|
-
// Experimental
|
|
48
|
-
export const OPENCODE_EXPERIMENTAL = truthyCompat(
|
|
49
|
-
'LINK_ASSISTANT_AGENT_EXPERIMENTAL',
|
|
50
|
-
'OPENCODE_EXPERIMENTAL'
|
|
51
|
-
);
|
|
52
|
-
export const OPENCODE_EXPERIMENTAL_WATCHER =
|
|
53
|
-
OPENCODE_EXPERIMENTAL ||
|
|
54
|
-
truthyCompat(
|
|
55
|
-
'LINK_ASSISTANT_AGENT_EXPERIMENTAL_WATCHER',
|
|
56
|
-
'OPENCODE_EXPERIMENTAL_WATCHER'
|
|
57
|
-
);
|
|
58
|
-
|
|
59
|
-
// Verbose mode - enables detailed logging of API requests
|
|
60
|
-
export let OPENCODE_VERBOSE = truthyCompat(
|
|
61
|
-
'LINK_ASSISTANT_AGENT_VERBOSE',
|
|
62
|
-
'OPENCODE_VERBOSE'
|
|
63
|
-
);
|
|
64
|
-
|
|
65
|
-
// Dry run mode - simulate operations without making actual API calls or changes
|
|
66
|
-
export let OPENCODE_DRY_RUN = truthyCompat(
|
|
67
|
-
'LINK_ASSISTANT_AGENT_DRY_RUN',
|
|
68
|
-
'OPENCODE_DRY_RUN'
|
|
69
|
-
);
|
|
70
|
-
|
|
71
|
-
// Title generation configuration
|
|
72
|
-
// When disabled, sessions will use default "New session - {timestamp}" titles
|
|
73
|
-
// This saves tokens and prevents rate limit issues with free tier models
|
|
74
|
-
// See: https://github.com/link-assistant/agent/issues/157
|
|
75
|
-
export let GENERATE_TITLE = truthyCompat(
|
|
76
|
-
'LINK_ASSISTANT_AGENT_GENERATE_TITLE',
|
|
77
|
-
'AGENT_GENERATE_TITLE'
|
|
78
|
-
);
|
|
79
|
-
|
|
80
|
-
// Allow setting title generation mode programmatically (e.g., from CLI --generate-title flag)
|
|
81
|
-
export function setGenerateTitle(value: boolean) {
|
|
82
|
-
GENERATE_TITLE = value;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Output response model information in step-finish parts
|
|
86
|
-
// Enabled by default - includes model info (providerID, requestedModelID, respondedModelID) in output
|
|
87
|
-
// Can be disabled with AGENT_OUTPUT_RESPONSE_MODEL=false
|
|
88
|
-
// See: https://github.com/link-assistant/agent/issues/179
|
|
89
|
-
export let OUTPUT_RESPONSE_MODEL = (() => {
|
|
90
|
-
const value = (
|
|
91
|
-
getEnv(
|
|
92
|
-
'LINK_ASSISTANT_AGENT_OUTPUT_RESPONSE_MODEL',
|
|
93
|
-
'AGENT_OUTPUT_RESPONSE_MODEL'
|
|
94
|
-
) ?? ''
|
|
95
|
-
).toLowerCase();
|
|
96
|
-
if (value === 'false' || value === '0') return false;
|
|
97
|
-
return true; // Default to true
|
|
98
|
-
})();
|
|
99
|
-
|
|
100
|
-
// Allow setting output-response-model mode programmatically (e.g., from CLI --output-response-model flag)
|
|
101
|
-
export function setOutputResponseModel(value: boolean) {
|
|
102
|
-
OUTPUT_RESPONSE_MODEL = value;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Session summarization configuration
|
|
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
|
-
})();
|
|
119
|
-
|
|
120
|
-
// Allow setting summarize-session mode programmatically (e.g., from CLI --summarize-session flag)
|
|
121
|
-
export function setSummarizeSession(value: boolean) {
|
|
122
|
-
SUMMARIZE_SESSION = value;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// Retry timeout configuration
|
|
126
|
-
// Maximum total time to keep retrying for the same error type (default: 7 days in seconds)
|
|
127
|
-
// For different error types, the timer resets
|
|
128
|
-
// See: https://github.com/link-assistant/agent/issues/157
|
|
129
|
-
export function RETRY_TIMEOUT(): number {
|
|
130
|
-
const val = getEnv(
|
|
131
|
-
'LINK_ASSISTANT_AGENT_RETRY_TIMEOUT',
|
|
132
|
-
'AGENT_RETRY_TIMEOUT'
|
|
133
|
-
);
|
|
134
|
-
return val ? parseInt(val, 10) : 604800; // 7 days in seconds
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// Maximum delay for a single retry attempt (default: 20 minutes in milliseconds)
|
|
138
|
-
export function MAX_RETRY_DELAY(): number {
|
|
139
|
-
const val = getEnv(
|
|
140
|
-
'LINK_ASSISTANT_AGENT_MAX_RETRY_DELAY',
|
|
141
|
-
'AGENT_MAX_RETRY_DELAY'
|
|
142
|
-
);
|
|
143
|
-
return val ? parseInt(val, 10) * 1000 : 1200000; // 20 minutes in ms
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// Minimum retry interval to prevent rapid retries (default: 30 seconds)
|
|
147
|
-
// This ensures we don't hammer the API with rapid retry attempts
|
|
148
|
-
// See: https://github.com/link-assistant/agent/issues/167
|
|
149
|
-
export function MIN_RETRY_INTERVAL(): number {
|
|
150
|
-
const val = getEnv(
|
|
151
|
-
'LINK_ASSISTANT_AGENT_MIN_RETRY_INTERVAL',
|
|
152
|
-
'AGENT_MIN_RETRY_INTERVAL'
|
|
153
|
-
);
|
|
154
|
-
return val ? parseInt(val, 10) * 1000 : 30000; // 30 seconds in ms
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Stream timeout configuration
|
|
158
|
-
// chunkMs: timeout between stream chunks - detects stalled streams (default: 2 minutes)
|
|
159
|
-
// stepMs: timeout for each individual LLM step (default: 10 minutes)
|
|
160
|
-
export function STREAM_CHUNK_TIMEOUT_MS(): number {
|
|
161
|
-
const val = getEnv(
|
|
162
|
-
'LINK_ASSISTANT_AGENT_STREAM_CHUNK_TIMEOUT_MS',
|
|
163
|
-
'AGENT_STREAM_CHUNK_TIMEOUT_MS'
|
|
164
|
-
);
|
|
165
|
-
return val ? parseInt(val, 10) : 120_000;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
export function STREAM_STEP_TIMEOUT_MS(): number {
|
|
169
|
-
const val = getEnv(
|
|
170
|
-
'LINK_ASSISTANT_AGENT_STREAM_STEP_TIMEOUT_MS',
|
|
171
|
-
'AGENT_STREAM_STEP_TIMEOUT_MS'
|
|
172
|
-
);
|
|
173
|
-
return val ? parseInt(val, 10) : 600_000;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// Compact JSON mode - output JSON on single lines (NDJSON format)
|
|
177
|
-
// Enabled by AGENT_CLI_COMPACT env var or --compact-json flag
|
|
178
|
-
// Uses getter to check env var at runtime for tests
|
|
179
|
-
let _compactJson: boolean | null = null;
|
|
180
|
-
|
|
181
|
-
export function COMPACT_JSON(): boolean {
|
|
182
|
-
if (_compactJson !== null) return _compactJson;
|
|
183
|
-
return (
|
|
184
|
-
truthy('AGENT_CLI_COMPACT') ||
|
|
185
|
-
truthyCompat('LINK_ASSISTANT_AGENT_COMPACT_JSON', 'OPENCODE_COMPACT_JSON')
|
|
186
|
-
);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// Allow setting verbose mode programmatically (e.g., from CLI --verbose flag)
|
|
190
|
-
export function setVerbose(value: boolean) {
|
|
191
|
-
OPENCODE_VERBOSE = value;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// Allow setting dry run mode programmatically (e.g., from CLI --dry-run flag)
|
|
195
|
-
export function setDryRun(value: boolean) {
|
|
196
|
-
OPENCODE_DRY_RUN = value;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// Allow setting compact JSON mode programmatically (e.g., from CLI --compact-json flag)
|
|
200
|
-
export function setCompactJson(value: boolean) {
|
|
201
|
-
_compactJson = value;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// Retry on rate limits - when disabled, 429 responses are returned immediately without retrying
|
|
205
|
-
// Enabled by default. Use --no-retry-on-rate-limits in integration tests to avoid waiting for rate limits.
|
|
206
|
-
export let RETRY_ON_RATE_LIMITS = true;
|
|
207
|
-
|
|
208
|
-
// Allow setting retry-on-rate-limits mode programmatically (e.g., from CLI --retry-on-rate-limits flag)
|
|
209
|
-
export function setRetryOnRateLimits(value: boolean) {
|
|
210
|
-
RETRY_ON_RATE_LIMITS = value;
|
|
211
|
-
}
|
|
212
|
-
}
|