@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.
@@ -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 { Flag } from '../flag/flag';
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 (Flag.OPENCODE_VERBOSE) {
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: Flag.STREAM_CHUNK_TIMEOUT_MS(),
770
- stepMs: Flag.STREAM_STEP_TIMEOUT_MS(),
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 (!Flag.GENERATE_TITLE) {
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',
@@ -1,5 +1,5 @@
1
1
  import { MessageV2 } from './message-v2';
2
- import { Flag } from '../flag/flag';
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 Flag.MAX_RETRY_DELAY();
16
+ return config.maxRetryDelay * 1000;
17
17
  }
18
18
 
19
19
  // Get retry timeout in milliseconds
20
20
  export function getRetryTimeout(): number {
21
- return Flag.RETRY_TIMEOUT() * 1000;
21
+ return config.retryTimeout * 1000;
22
22
  }
23
23
 
24
24
  /**
25
- * Error thrown when retry-after exceeds AGENT_RETRY_TIMEOUT
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 AGENT_RETRY_TIMEOUT env var to increase the limit.`
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 = Flag.RETRY_TIMEOUT() * 1000; // Convert to ms
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 <= AGENT_RETRY_TIMEOUT: use it directly (exact time)
202
- * - If retry-after > AGENT_RETRY_TIMEOUT: throw RetryTimeoutExceededError (fail immediately)
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 AGENT_RETRY_TIMEOUT
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();
@@ -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 { Flag } from '../flag/flag';
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 (!Flag.SUMMARIZE_SESSION) {
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 (Flag.OPENCODE_VERBOSE) {
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 (Flag.OPENCODE_VERBOSE) {
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 (Flag.OPENCODE_VERBOSE) {
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 (Flag.OPENCODE_VERBOSE) {
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 (Flag.OPENCODE_VERBOSE) {
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 (Flag.OPENCODE_VERBOSE) {
279
+ if (isVerbose()) {
280
280
  log.info(() => ({
281
281
  message: 'body summary API response received',
282
282
  providerID: model.providerID,
@@ -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';
@@ -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 environment variable)
74
- const verifyImages = process.env.VERIFY_IMAGES_AT_READ_TOOL !== 'false';
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: VERIFY_IMAGES_AT_READ_TOOL=false`
94
+ `To disable image validation, set environment variable: LINK_ASSISTANT_AGENT_VERIFY_IMAGES_AT_READ_TOOL=false`
94
95
  );
95
96
  }
96
97
  }
@@ -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 () => {
@@ -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' });
@@ -1,5 +1,5 @@
1
1
  import makeLog, { levels, LogLevel } from 'log-lazy';
2
- import { Flag } from '../flag/flag';
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
- import { Flag } from '../flag/flag';
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 Flag
103
- const useCompact = compactJsonMode || Flag.COMPACT_JSON();
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 = Flag.OPENCODE_VERBOSE ? LEVEL_VERBOSE : LEVEL_DISABLED;
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: () => Flag.OPENCODE_VERBOSE,
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 Flag.setVerbose() to sync the logger state
297
+ * Call this after setVerbose() to sync the logger state
300
298
  */
301
299
  export function syncLoggerWithVerboseFlag(): void {
302
- if (Flag.OPENCODE_VERBOSE) {
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 { Flag } from '../flag/flag';
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 = Flag.COMPACT_JSON(); // Whether to use compact JSON (single line)
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
- if (Flag.OPENCODE_VERBOSE || options.print) {
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 Flag
212
- const useCompact = compactJsonOutput || Flag.COMPACT_JSON();
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 Flag.setVerbose() to update lazy logging state
375
+ * Call after setVerbose() to update lazy logging state
375
376
  */
376
377
  export function syncWithVerboseFlag(): void {
377
- if (Flag.OPENCODE_VERBOSE) {
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 { Flag } from '../flag/flag';
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 Flag.OPENCODE_VERBOSE at call time
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 Flag.OPENCODE_VERBOSE is true, logs all HTTP requests and responses
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
- if (!Flag.OPENCODE_VERBOSE) {
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
- }