@link-assistant/agent 0.18.3 → 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.
@@ -1,10 +1,10 @@
1
1
  import z from 'zod';
2
2
  import { Bus } from '../bus';
3
- import { Flag } from '../flag/flag';
3
+ import { config } from '../config/config';
4
4
  import { Instance } from '../project/instance';
5
5
  import { Log } from '../util/log';
6
6
  import { FileIgnore } from './ignore';
7
- import { Config } from '../config/config';
7
+ import { Config } from '../config/file-config';
8
8
  // @ts-ignore
9
9
  import { createWrapper } from '@parcel/watcher/wrapper';
10
10
  import { lazy } from '../util/lazy';
@@ -83,7 +83,7 @@ export namespace FileWatcher {
83
83
  );
84
84
 
85
85
  export function init() {
86
- if (!Flag.OPENCODE_EXPERIMENTAL_WATCHER) return;
86
+ if (!config.experimentalWatcher) return;
87
87
  state();
88
88
  }
89
89
  }
@@ -5,7 +5,7 @@ import path from 'path';
5
5
  import z from 'zod';
6
6
 
7
7
  import * as Formatter from './formatter';
8
- import { Config } from '../config/config';
8
+ import { Config } from '../config/file-config';
9
9
  import { mergeDeep } from 'remeda';
10
10
  import { Instance } from '../project/instance';
11
11
 
package/src/index.js CHANGED
@@ -1,5 +1,11 @@
1
1
  #!/usr/bin/env bun
2
- import { Flag } from './flag/flag.ts';
2
+ import {
3
+ config,
4
+ initConfig,
5
+ isVerbose,
6
+ setVerbose,
7
+ getConfigSnapshot,
8
+ } from './config/config.ts';
3
9
  import { setProcessName } from './cli/process-name.ts';
4
10
  setProcessName('agent');
5
11
  import { Server } from './server/server.ts';
@@ -249,7 +255,7 @@ async function runAgentMode(argv, request) {
249
255
  workingDirectory: process.cwd(),
250
256
  scriptPath: import.meta.path,
251
257
  }));
252
- if (Flag.OPENCODE_DRY_RUN) {
258
+ if (config.dryRun) {
253
259
  Log.Default.info(() => ({
254
260
  message: 'Dry run mode enabled',
255
261
  mode: 'dry-run',
@@ -336,7 +342,7 @@ async function runContinuousAgentMode(argv) {
336
342
  workingDirectory: process.cwd(),
337
343
  scriptPath: import.meta.path,
338
344
  }));
339
- if (Flag.OPENCODE_DRY_RUN) {
345
+ if (config.dryRun) {
340
346
  Log.Default.info(() => ({
341
347
  message: 'Dry run mode enabled',
342
348
  mode: 'dry-run',
@@ -608,7 +614,7 @@ async function main() {
608
614
  handler: async (argv) => {
609
615
  // Check both CLI flag and environment variable for compact JSON mode
610
616
  const compactJson =
611
- argv['compact-json'] === true || Flag.COMPACT_JSON();
617
+ argv['compact-json'] === true || config.compactJson;
612
618
 
613
619
  // Check if --prompt flag was provided
614
620
  if (argv.prompt) {
@@ -767,49 +773,50 @@ async function main() {
767
773
  await runAgentMode(argv, request);
768
774
  },
769
775
  })
770
- // Initialize logging and flags early for all CLI commands
776
+ // Initialize centralized config and flags from CLI args + env vars + .lenv.
777
+ // Uses lino-arguments getenv() for env var resolution (case-insensitive,
778
+ // type-preserving, .lenv support).
779
+ // See: https://github.com/link-foundation/lino-arguments
780
+ // See: https://github.com/link-assistant/agent/issues/227
771
781
  .middleware(async (argv) => {
772
- const isCompact = argv['compact-json'] === true || Flag.COMPACT_JSON();
782
+ // Initialize global config using makeConfig from lino-arguments.
783
+ // Resolves CLI args + env vars + .lenv files in one place.
784
+ // After this call, the global `config` variable is fully resolved.
785
+ // See: https://github.com/link-foundation/lino-arguments
786
+ initConfig();
787
+
788
+ // Override compact-json from argv if explicitly set.
789
+ if (argv['compact-json'] === true) {
790
+ config.compactJson = true;
791
+ }
792
+ const isCompact = config.compactJson;
773
793
  if (isCompact) {
774
794
  setCompactJson(true);
775
795
  }
776
- if (argv.verbose) {
777
- Flag.setVerbose(true);
778
- }
779
- if (argv['dry-run']) {
780
- Flag.setDryRun(true);
781
- }
782
- if (argv['generate-title'] === true) {
783
- Flag.setGenerateTitle(true);
784
- }
785
- // output-response-model is enabled by default, only set if explicitly disabled
786
- if (argv['output-response-model'] === false) {
787
- Flag.setOutputResponseModel(false);
788
- }
789
- // summarize-session is enabled by default, only set if explicitly disabled
790
- if (argv['summarize-session'] === false) {
791
- Flag.setSummarizeSession(false);
792
- } else {
793
- Flag.setSummarizeSession(true);
794
- }
795
- // retry-on-rate-limits is enabled by default, only set if explicitly disabled
796
- if (argv['retry-on-rate-limits'] === false) {
797
- Flag.setRetryOnRateLimits(false);
796
+
797
+ // Sync verbose to env var for subprocess resilience.
798
+ if (config.verbose) {
799
+ setVerbose(true);
798
800
  }
801
+
802
+ // Initialize logging.
799
803
  await Log.init({
800
- print: Flag.OPENCODE_VERBOSE,
801
- level: Flag.OPENCODE_VERBOSE ? 'DEBUG' : 'INFO',
804
+ print: isVerbose(),
805
+ level: isVerbose() ? 'DEBUG' : 'INFO',
802
806
  compactJson: isCompact,
803
807
  });
804
808
 
809
+ // Always log the resolved configuration as JSON.
810
+ // This is critical for debugging — shows exactly what config was resolved
811
+ // from CLI args, env vars, and .lenv files combined.
812
+ Log.Default.info(() => ({
813
+ type: 'config',
814
+ message: 'Agent configuration resolved',
815
+ source: 'lino-arguments (CLI args > env vars > .lenv > defaults)',
816
+ config: getConfigSnapshot(),
817
+ }));
818
+
805
819
  // Global fetch monkey-patch for verbose HTTP logging (#221).
806
- // This catches any HTTP calls that go through globalThis.fetch directly,
807
- // including non-provider calls (auth, config, tools) that may not have
808
- // their own createVerboseFetch wrapper. The provider-level wrapper in
809
- // provider.ts getSDK() also logs independently — both mechanisms are
810
- // kept active to maximize HTTP observability in --verbose mode.
811
- // See: https://github.com/link-assistant/agent/issues/221
812
- // See: https://github.com/link-assistant/agent/issues/217
813
820
  if (!globalThis.__agentVerboseFetchInstalled) {
814
821
  globalThis.fetch = createVerboseFetch(globalThis.fetch, {
815
822
  caller: 'global',
@@ -6,11 +6,11 @@
6
6
  * - claude: Claude CLI stream-json format - NDJSON (newline-delimited JSON)
7
7
  *
8
8
  * Output goes to stdout for normal messages, stderr for errors.
9
- * Use AGENT_CLI_COMPACT env var or --compact-json flag for NDJSON output.
9
+ * Use LINK_ASSISTANT_AGENT_COMPACT_JSON env var or --compact-json flag for NDJSON output.
10
10
  */
11
11
 
12
12
  import { EOL } from 'os';
13
- import { Flag } from '../flag/flag';
13
+ import { config } from '../config/config';
14
14
 
15
15
  export type JsonStandard = 'opencode' | 'claude';
16
16
 
@@ -50,7 +50,7 @@ export interface ClaudeEvent {
50
50
 
51
51
  /**
52
52
  * Serialize JSON output based on the selected standard
53
- * Respects AGENT_CLI_COMPACT env var for OpenCode format
53
+ * Respects LINK_ASSISTANT_AGENT_COMPACT_JSON env var for OpenCode format
54
54
  */
55
55
  export function serializeOutput(
56
56
  event: OpenCodeEvent | ClaudeEvent,
@@ -60,8 +60,8 @@ export function serializeOutput(
60
60
  // NDJSON format - always compact, one line
61
61
  return JSON.stringify(event) + EOL;
62
62
  }
63
- // OpenCode format - compact if AGENT_CLI_COMPACT is set
64
- if (Flag.COMPACT_JSON()) {
63
+ // OpenCode format - compact if LINK_ASSISTANT_AGENT_COMPACT_JSON is set
64
+ if (config.compactJson) {
65
65
  return JSON.stringify(event) + EOL;
66
66
  }
67
67
  return JSON.stringify(event, null, 2) + EOL;
package/src/mcp/index.ts CHANGED
@@ -3,7 +3,8 @@ import { type Tool } from 'ai';
3
3
  import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
4
4
  import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
5
5
  import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
6
- import { Config } from '../config/config';
6
+ import { Config } from '../config/file-config';
7
+ import { config } from '../config/config';
7
8
  import { Log } from '../util/log';
8
9
  import { NamedError } from '../util/error';
9
10
  import z from 'zod/v4';
@@ -89,23 +90,15 @@ export namespace MCP {
89
90
  const status: Record<string, Status> = {};
90
91
  const timeoutConfigs: Record<string, TimeoutConfig> = {};
91
92
 
92
- // Determine global timeout defaults from config and environment variables
93
- const envDefaultTimeout = process.env.MCP_DEFAULT_TOOL_CALL_TIMEOUT
94
- ? parseInt(process.env.MCP_DEFAULT_TOOL_CALL_TIMEOUT, 10)
95
- : undefined;
96
- const envMaxTimeout = process.env.MCP_MAX_TOOL_CALL_TIMEOUT
97
- ? parseInt(process.env.MCP_MAX_TOOL_CALL_TIMEOUT, 10)
98
- : undefined;
99
-
93
+ // Determine global timeout defaults from config and environment variables.
94
+ // Uses config.mcp*() which reads from centralized AgentConfig (lino-arguments).
100
95
  const globalDefaults: GlobalTimeoutDefaults = {
101
96
  defaultTimeout:
102
97
  cfg.mcp_defaults?.tool_call_timeout ??
103
- envDefaultTimeout ??
104
- BUILTIN_DEFAULT_TOOL_CALL_TIMEOUT,
98
+ config.mcpDefaultToolCallTimeout,
105
99
  maxTimeout:
106
100
  cfg.mcp_defaults?.max_tool_call_timeout ??
107
- envMaxTimeout ??
108
- BUILTIN_MAX_TOOL_CALL_TIMEOUT,
101
+ config.mcpMaxToolCallTimeout,
109
102
  };
110
103
 
111
104
  await Promise.all(
@@ -1,7 +1,6 @@
1
1
  import { Format } from '../format';
2
2
  import { FileWatcher } from '../file/watcher';
3
3
  import { File } from '../file';
4
- import { Flag } from '../flag/flag';
5
4
  import { Project } from './project';
6
5
  import { Bus } from '../bus';
7
6
  import { Command } from '../command';
@@ -4,7 +4,6 @@ import path from 'path';
4
4
  import { $ } from 'bun';
5
5
  import { Storage } from '../storage/storage';
6
6
  import { Log } from '../util/log';
7
- import { Flag } from '../flag/flag';
8
7
 
9
8
  export namespace Project {
10
9
  const log = Log.create({ service: 'project' });
@@ -1,6 +1,6 @@
1
1
  import z from 'zod';
2
2
  import path from 'path';
3
- import { Config } from '../config/config';
3
+ import { Config } from '../config/file-config';
4
4
  import { mergeDeep, sortBy } from 'remeda';
5
5
  import { NoSuchModelError, type LanguageModel, type Provider as SDK } from 'ai';
6
6
  import { Log } from '../util/log';
@@ -12,7 +12,7 @@ import { ClaudeOAuth } from '../auth/claude-oauth';
12
12
  import { AuthPlugins } from '../auth/plugins';
13
13
  import { Instance } from '../project/instance';
14
14
  import { Global } from '../global';
15
- import { Flag } from '../flag/flag';
15
+ import { config, isVerbose } from '../config/config';
16
16
  import { iife } from '../util/iife';
17
17
  import { createEchoModel } from './echo';
18
18
  import { createCacheModel } from './cache';
@@ -647,7 +647,7 @@ export namespace Provider {
647
647
  'link-assistant': async () => {
648
648
  // Echo provider is always available - no external dependencies needed
649
649
  return {
650
- autoload: Flag.OPENCODE_DRY_RUN, // Auto-load only in dry-run mode
650
+ autoload: config.dryRun, // Auto-load only in dry-run mode
651
651
  async getModel(_sdk: any, modelID: string) {
652
652
  // Return our custom echo model that implements LanguageModelV1
653
653
  return createEchoModel(modelID);
@@ -1124,7 +1124,7 @@ export namespace Provider {
1124
1124
  .filter(
1125
1125
  ([, model]) =>
1126
1126
  ((!model.experimental && model.status !== 'alpha') ||
1127
- Flag.OPENCODE_ENABLE_EXPERIMENTAL_MODELS) &&
1127
+ config.enableExperimentalModels) &&
1128
1128
  model.status !== 'deprecated'
1129
1129
  )
1130
1130
  );
@@ -1220,7 +1220,7 @@ export namespace Provider {
1220
1220
  pkg,
1221
1221
  globalVerboseFetchInstalled:
1222
1222
  !!globalThis.__agentVerboseFetchInstalled,
1223
- verboseAtCreation: Flag.OPENCODE_VERBOSE,
1223
+ verboseAtCreation: isVerbose(),
1224
1224
  });
1225
1225
 
1226
1226
  options['fetch'] = async (
@@ -1228,9 +1228,11 @@ export namespace Provider {
1228
1228
  init?: RequestInit
1229
1229
  ): Promise<Response> => {
1230
1230
  // Check verbose flag at call time — not at SDK creation time.
1231
- // This ensures --verbose works even when the flag is set after SDK creation.
1231
+ // Uses isVerbose() with env var fallback for resilience against
1232
+ // flag state loss in subprocess/module-reload scenarios.
1232
1233
  // See: https://github.com/link-assistant/agent/issues/206
1233
- if (!Flag.OPENCODE_VERBOSE) {
1234
+ // See: https://github.com/link-assistant/agent/issues/227
1235
+ if (!isVerbose()) {
1234
1236
  return innerFetch(input, init);
1235
1237
  }
1236
1238
 
@@ -1787,7 +1789,7 @@ export namespace Provider {
1787
1789
  // In dry-run mode, use the echo provider by default
1788
1790
  // This allows testing round-trips and multi-turn conversations without API costs
1789
1791
  // @see https://github.com/link-assistant/agent/issues/89
1790
- if (Flag.OPENCODE_DRY_RUN) {
1792
+ if (config.dryRun) {
1791
1793
  log.info('dry-run mode enabled, using echo provider as default');
1792
1794
  return {
1793
1795
  providerID: 'link-assistant',
@@ -1,5 +1,5 @@
1
1
  import { Log } from '../util/log';
2
- import { Flag } from '../flag/flag';
2
+ import { config } from '../config/config';
3
3
 
4
4
  /**
5
5
  * Custom fetch wrapper that handles rate limits (HTTP 429) using time-based retry logic.
@@ -8,8 +8,8 @@ import { Flag } from '../flag/flag';
8
8
  * retry mechanism can interfere. It respects:
9
9
  * - retry-after headers (both seconds and HTTP date formats)
10
10
  * - retry-after-ms header for millisecond precision
11
- * - AGENT_RETRY_TIMEOUT for global time-based retry limit
12
- * - AGENT_MAX_RETRY_DELAY for maximum single retry wait time
11
+ * - LINK_ASSISTANT_AGENT_RETRY_TIMEOUT for global time-based retry limit
12
+ * - LINK_ASSISTANT_AGENT_MAX_RETRY_DELAY for maximum single retry wait time
13
13
  *
14
14
  * Problem solved:
15
15
  * The AI SDK's internal retry uses a fixed count (default 3 attempts) and ignores
@@ -40,7 +40,7 @@ export namespace RetryFetch {
40
40
  // Minimum retry interval to prevent rapid retries (default: 30 seconds)
41
41
  // Can be configured via AGENT_MIN_RETRY_INTERVAL env var
42
42
  function getMinRetryInterval(): number {
43
- return Flag.MIN_RETRY_INTERVAL();
43
+ return config.minRetryInterval * 1000;
44
44
  }
45
45
 
46
46
  /**
@@ -194,7 +194,7 @@ export namespace RetryFetch {
194
194
  *
195
195
  * This controller is NOT connected to the request's AbortSignal, so it won't be
196
196
  * affected by provider timeouts (default 5 minutes) or stream timeouts.
197
- * It only respects the global AGENT_RETRY_TIMEOUT.
197
+ * It only respects the global LINK_ASSISTANT_AGENT_RETRY_TIMEOUT.
198
198
  *
199
199
  * However, it DOES check the user's abort signal periodically (every 10 seconds)
200
200
  * to allow user cancellation during long rate limit waits.
@@ -217,7 +217,7 @@ export namespace RetryFetch {
217
217
  const controller = new AbortController();
218
218
  const timers: NodeJS.Timeout[] = [];
219
219
 
220
- // Set a timeout based on the global AGENT_RETRY_TIMEOUT (not provider timeout)
220
+ // Set a timeout based on the global LINK_ASSISTANT_AGENT_RETRY_TIMEOUT (not provider timeout)
221
221
  const globalTimeoutId = setTimeout(() => {
222
222
  controller.abort(
223
223
  new DOMException(
@@ -306,7 +306,7 @@ export namespace RetryFetch {
306
306
  * 3. Waits for the specified duration (respecting global timeout)
307
307
  * 4. Retries the request
308
308
  *
309
- * If retry-after exceeds AGENT_RETRY_TIMEOUT, the original 429 response is returned
309
+ * If retry-after exceeds LINK_ASSISTANT_AGENT_RETRY_TIMEOUT, the original 429 response is returned
310
310
  * to let higher-level error handling take over.
311
311
  *
312
312
  * @param options Configuration options
@@ -322,8 +322,8 @@ export namespace RetryFetch {
322
322
  ): Promise<Response> {
323
323
  let attempt = 0;
324
324
  const startTime = Date.now();
325
- const maxRetryTimeout = Flag.RETRY_TIMEOUT() * 1000;
326
- const maxBackoffDelay = Flag.MAX_RETRY_DELAY();
325
+ const maxRetryTimeout = config.retryTimeout * 1000;
326
+ const maxBackoffDelay = config.maxRetryDelay * 1000;
327
327
 
328
328
  while (true) {
329
329
  attempt++;
@@ -371,7 +371,7 @@ export namespace RetryFetch {
371
371
  }
372
372
 
373
373
  // If retry on rate limits is disabled, return 429 immediately
374
- if (!Flag.RETRY_ON_RATE_LIMITS) {
374
+ if (!config.retryOnRateLimits) {
375
375
  log.info(() => ({
376
376
  message:
377
377
  'rate limit retry disabled (--no-retry-on-rate-limits), returning 429',
@@ -442,7 +442,7 @@ export namespace RetryFetch {
442
442
  // Wait before retrying using ISOLATED signal
443
443
  // This is critical for issue #183: Rate limit waits can be hours long (e.g., 15 hours),
444
444
  // but provider timeouts are typically 5 minutes. By using an isolated AbortController
445
- // that only respects AGENT_RETRY_TIMEOUT, we prevent the provider timeout from
445
+ // that only respects LINK_ASSISTANT_AGENT_RETRY_TIMEOUT, we prevent the provider timeout from
446
446
  // aborting long rate limit waits.
447
447
  //
448
448
  // The isolated signal periodically checks the user's abort signal (every 10 seconds)
@@ -211,8 +211,10 @@ export class Agent {
211
211
  ...data,
212
212
  };
213
213
  // Pretty-print JSON for human readability, compact for programmatic use
214
- // Use AGENT_CLI_COMPACT=1 for compact output (tests, automation)
215
- const compact = process.env.AGENT_CLI_COMPACT === '1';
214
+ // Use LINK_ASSISTANT_AGENT_COMPACT_JSON=1 for compact output (tests, automation)
215
+ const compact =
216
+ process.env.LINK_ASSISTANT_AGENT_COMPACT_JSON === 'true' ||
217
+ process.env.LINK_ASSISTANT_AGENT_COMPACT_JSON === '1';
216
218
  process.stdout.write(
217
219
  `${compact ? JSON.stringify(event) : JSON.stringify(event, null, 2)}\n`
218
220
  );
@@ -9,7 +9,7 @@ import { Bus } from '../bus';
9
9
  import z from 'zod';
10
10
  import type { ModelsDev } from '../provider/models';
11
11
  import { SessionPrompt } from './prompt';
12
- import { Flag } from '../flag/flag';
12
+ import { config, isVerbose } from '../config/config';
13
13
  import { Token } from '../util/token';
14
14
  import { Log } from '../util/log';
15
15
  import { ProviderTransform } from '../provider/transform';
@@ -98,7 +98,7 @@ export namespace SessionCompaction {
98
98
  compactionModel?: CompactionModelConfig;
99
99
  compactionModelContextLimit?: number;
100
100
  }) {
101
- if (Flag.OPENCODE_DISABLE_AUTOCOMPACT) return false;
101
+ if (config.disableAutocompact) return false;
102
102
  const baseModelContextLimit = input.model.limit.context;
103
103
  if (baseModelContextLimit === 0) return false;
104
104
  const count =
@@ -180,7 +180,7 @@ export namespace SessionCompaction {
180
180
  // calls. then erases output of previous tool calls. idea is to throw away old
181
181
  // tool calls that are no longer relevant.
182
182
  export async function prune(input: { sessionID: string }) {
183
- if (Flag.OPENCODE_DISABLE_PRUNE) return;
183
+ if (config.disablePrune) return;
184
184
  log.info(() => ({ message: 'pruning' }));
185
185
  const msgs = await Session.messages({ sessionID: input.sessionID });
186
186
  let total = 0;
@@ -240,7 +240,7 @@ export namespace SessionCompaction {
240
240
  input.model.providerID,
241
241
  input.model.modelID
242
242
  );
243
- if (Flag.OPENCODE_VERBOSE) {
243
+ if (isVerbose()) {
244
244
  log.info(() => ({
245
245
  message: 'compaction model loaded',
246
246
  providerID: model.providerID,
@@ -303,7 +303,7 @@ export namespace SessionCompaction {
303
303
  // Defensive check: ensure modelMessages is iterable (AI SDK 6.0.1 compatibility fix)
304
304
  const safeModelMessages = Array.isArray(modelMessages) ? modelMessages : [];
305
305
 
306
- if (Flag.OPENCODE_VERBOSE) {
306
+ if (isVerbose()) {
307
307
  log.info(() => ({
308
308
  message: 'compaction streamText call',
309
309
  providerID: model.providerID,
@@ -2,8 +2,8 @@ import { Decimal } from 'decimal.js';
2
2
  import z from 'zod';
3
3
  import { type LanguageModelUsage, type ProviderMetadata } from 'ai';
4
4
  import { Bus } from '../bus';
5
- import { Config } from '../config/config';
6
- import { Flag } from '../flag/flag';
5
+ import { Config } from '../config/file-config';
6
+ import { isVerbose } from '../config/config';
7
7
  import { Identifier } from '../id/id';
8
8
  import type { ModelsDev } from '../provider/models';
9
9
  import { Storage } from '../storage/storage';
@@ -35,7 +35,7 @@ export namespace Session {
35
35
  */
36
36
  export const toDecimal = (value: unknown, context?: string): Decimal => {
37
37
  // Log input data in verbose mode to help identify issues
38
- if (Flag.OPENCODE_VERBOSE) {
38
+ if (isVerbose()) {
39
39
  log.debug(() => ({
40
40
  message: 'toDecimal input',
41
41
  context,
@@ -51,7 +51,7 @@ export namespace Session {
51
51
  const result = new Decimal(value as any);
52
52
 
53
53
  // Log successful conversion in verbose mode
54
- if (Flag.OPENCODE_VERBOSE) {
54
+ if (isVerbose()) {
55
55
  log.debug(() => ({
56
56
  message: 'toDecimal success',
57
57
  context,
@@ -62,7 +62,7 @@ export namespace Session {
62
62
  return result;
63
63
  } catch (error) {
64
64
  // Log the error and return Decimal(NaN)
65
- if (Flag.OPENCODE_VERBOSE) {
65
+ if (isVerbose()) {
66
66
  log.debug(() => ({
67
67
  message: 'toDecimal error - returning Decimal(NaN)',
68
68
  context,
@@ -397,7 +397,7 @@ export namespace Session {
397
397
  */
398
398
  export const toNumber = (value: unknown, context?: string): number => {
399
399
  // Log input data in verbose mode to help identify issues
400
- if (Flag.OPENCODE_VERBOSE) {
400
+ if (isVerbose()) {
401
401
  log.debug(() => ({
402
402
  message: 'toNumber input',
403
403
  context,
@@ -412,7 +412,7 @@ export namespace Session {
412
412
  // These are expected for optional fields like cachedInputTokens, reasoningTokens
413
413
  // See: https://github.com/link-assistant/agent/issues/127
414
414
  if (value === undefined || value === null) {
415
- if (Flag.OPENCODE_VERBOSE) {
415
+ if (isVerbose()) {
416
416
  log.debug(() => ({
417
417
  message: 'toNumber received undefined/null, returning 0',
418
418
  context,
@@ -432,7 +432,7 @@ export namespace Session {
432
432
  typeof (value as { total: unknown }).total === 'number'
433
433
  ) {
434
434
  const result = (value as { total: number }).total;
435
- if (Flag.OPENCODE_VERBOSE) {
435
+ if (isVerbose()) {
436
436
  log.debug(() => ({
437
437
  message: 'toNumber extracted total from object',
438
438
  context,
@@ -452,7 +452,7 @@ export namespace Session {
452
452
  }
453
453
 
454
454
  // Log successful conversion in verbose mode
455
- if (Flag.OPENCODE_VERBOSE) {
455
+ if (isVerbose()) {
456
456
  log.debug(() => ({
457
457
  message: 'toNumber success',
458
458
  context,
@@ -463,7 +463,7 @@ export namespace Session {
463
463
  return result;
464
464
  } catch (error) {
465
465
  // Log the error and return NaN
466
- if (Flag.OPENCODE_VERBOSE) {
466
+ if (isVerbose()) {
467
467
  log.debug(() => ({
468
468
  message: 'toNumber error - returning NaN',
469
469
  context,
@@ -493,7 +493,7 @@ export namespace Session {
493
493
  */
494
494
  export const toFinishReason = (value: unknown): string => {
495
495
  // Log input data in verbose mode to help identify issues
496
- if (Flag.OPENCODE_VERBOSE) {
496
+ if (isVerbose()) {
497
497
  log.debug(() => ({
498
498
  message: 'toFinishReason input',
499
499
  valueType: typeof value,
@@ -518,7 +518,7 @@ export namespace Session {
518
518
 
519
519
  // Try common field names that might contain the reason
520
520
  if (typeof obj.type === 'string') {
521
- if (Flag.OPENCODE_VERBOSE) {
521
+ if (isVerbose()) {
522
522
  log.debug(() => ({
523
523
  message: 'toFinishReason extracted type from object',
524
524
  result: obj.type,
@@ -528,7 +528,7 @@ export namespace Session {
528
528
  }
529
529
 
530
530
  if (typeof obj.finishReason === 'string') {
531
- if (Flag.OPENCODE_VERBOSE) {
531
+ if (isVerbose()) {
532
532
  log.debug(() => ({
533
533
  message: 'toFinishReason extracted finishReason from object',
534
534
  result: obj.finishReason,
@@ -538,7 +538,7 @@ export namespace Session {
538
538
  }
539
539
 
540
540
  if (typeof obj.reason === 'string') {
541
- if (Flag.OPENCODE_VERBOSE) {
541
+ if (isVerbose()) {
542
542
  log.debug(() => ({
543
543
  message: 'toFinishReason extracted reason from object',
544
544
  result: obj.reason,
@@ -550,7 +550,7 @@ export namespace Session {
550
550
  // Handle AI SDK unified/raw format: {unified: "tool-calls", raw: "tool_calls"}
551
551
  // See: https://github.com/link-assistant/agent/issues/129
552
552
  if (typeof obj.unified === 'string') {
553
- if (Flag.OPENCODE_VERBOSE) {
553
+ if (isVerbose()) {
554
554
  log.debug(() => ({
555
555
  message: 'toFinishReason extracted unified from object',
556
556
  result: obj.unified,
@@ -560,7 +560,7 @@ export namespace Session {
560
560
  }
561
561
 
562
562
  // If we can't extract a specific field, return JSON representation
563
- if (Flag.OPENCODE_VERBOSE) {
563
+ if (isVerbose()) {
564
564
  log.debug(() => ({
565
565
  message: 'toFinishReason could not extract string, using JSON',
566
566
  result: JSON.stringify(value),
@@ -602,7 +602,7 @@ export namespace Session {
602
602
  }
603
603
 
604
604
  // Log raw usage data in verbose mode for debugging
605
- if (Flag.OPENCODE_VERBOSE) {
605
+ if (isVerbose()) {
606
606
  log.debug(() => ({
607
607
  message: 'getUsage called with raw data',
608
608
  rawUsage: JSON.stringify(input.usage),
@@ -642,7 +642,7 @@ export namespace Session {
642
642
  // If standard usage is empty but openrouter metadata has usage, use it as source
643
643
  let effectiveUsage = input.usage;
644
644
  if (standardUsageIsEmpty && openrouterUsage) {
645
- if (Flag.OPENCODE_VERBOSE) {
645
+ if (isVerbose()) {
646
646
  log.debug(() => ({
647
647
  message:
648
648
  'Standard usage empty, falling back to openrouter metadata',
@@ -682,7 +682,7 @@ export namespace Session {
682
682
  anthropicUsage &&
683
683
  (anthropicUsage.input_tokens || anthropicUsage.output_tokens)
684
684
  ) {
685
- if (Flag.OPENCODE_VERBOSE) {
685
+ if (isVerbose()) {
686
686
  log.debug(() => ({
687
687
  message:
688
688
  'Standard usage empty, falling back to anthropic provider metadata',
@@ -16,7 +16,7 @@ import { SessionSummary } from './summary';
16
16
  import { Bus } from '../bus';
17
17
  import { SessionRetry } from './retry';
18
18
  import { SessionStatus } from './status';
19
- import { Flag } from '../flag/flag';
19
+ import { config, isVerbose } from '../config/config';
20
20
  import { SessionCompaction } from './compaction';
21
21
 
22
22
  export namespace SessionProcessor {
@@ -356,7 +356,7 @@ export namespace SessionProcessor {
356
356
  // Build model info if --output-response-model flag is enabled
357
357
  // @see https://github.com/link-assistant/agent/issues/179
358
358
  const modelInfo: MessageV2.ModelInfo | undefined =
359
- Flag.OUTPUT_RESPONSE_MODEL
359
+ config.outputResponseModel
360
360
  ? {
361
361
  providerID: input.providerID,
362
362
  requestedModelID: input.model.id,
@@ -374,7 +374,7 @@ export namespace SessionProcessor {
374
374
  model: input.model,
375
375
  });
376
376
 
377
- if (Flag.OPENCODE_VERBOSE && contextDiag) {
377
+ if (isVerbose() && contextDiag) {
378
378
  log.info(() => ({
379
379
  message: 'step-finish context diagnostics',
380
380
  providerID: input.providerID,
@@ -547,7 +547,7 @@ export namespace SessionProcessor {
547
547
  ? SessionRetry.timeoutDelay(attempt)
548
548
  : SessionRetry.delay(error, attempt);
549
549
  } catch (delayError) {
550
- // If retry-after exceeds AGENT_RETRY_TIMEOUT, fail immediately
550
+ // If retry-after exceeds LINK_ASSISTANT_AGENT_RETRY_TIMEOUT, fail immediately
551
551
  if (
552
552
  delayError instanceof SessionRetry.RetryTimeoutExceededError
553
553
  ) {