@link-assistant/agent 0.18.3 → 0.19.2

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,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
  */
@@ -24,6 +24,21 @@ const log = Log.create({ service: 'http' });
24
24
  /** Global call counter shared across all verbose fetch wrappers */
25
25
  let globalHttpCallCount = 0;
26
26
 
27
+ /**
28
+ * Track pending async stream log operations (#231).
29
+ * When the process exits while stream logging is in progress, we log a warning
30
+ * so missing HTTP response bodies are visible in the logs rather than silently lost.
31
+ */
32
+ let pendingStreamLogs = 0;
33
+
34
+ /**
35
+ * Get the current count of pending stream log operations.
36
+ * Useful for diagnostics and testing.
37
+ */
38
+ export function getPendingStreamLogCount(): number {
39
+ return pendingStreamLogs;
40
+ }
41
+
27
42
  /**
28
43
  * Sanitize HTTP headers by masking sensitive values.
29
44
  * Masks authorization, x-api-key, and api-key headers.
@@ -98,7 +113,7 @@ export interface VerboseFetchOptions {
98
113
  /**
99
114
  * Wrap a fetch function with verbose HTTP logging.
100
115
  *
101
- * When Flag.OPENCODE_VERBOSE is true, logs all HTTP requests and responses
116
+ * When isVerbose() returns true, logs all HTTP requests and responses
102
117
  * as JSON objects. When verbose is false, returns a no-op passthrough.
103
118
  *
104
119
  * All logging is wrapped in try/catch so it never breaks the actual HTTP request.
@@ -121,8 +136,11 @@ export function createVerboseFetch(
121
136
  input: RequestInfo | URL,
122
137
  init?: RequestInit
123
138
  ): Promise<Response> => {
124
- // Check verbose flag at call time
125
- if (!Flag.OPENCODE_VERBOSE) {
139
+ // Check verbose flag at call time, with env var fallback for resilience.
140
+ // Uses isVerbose() which checks both the in-memory flag and environment
141
+ // variables, preventing silent logging loss when the flag state is disrupted.
142
+ // See: https://github.com/link-assistant/agent/issues/227
143
+ if (!isVerbose()) {
126
144
  return innerFetch(input, init);
127
145
  }
128
146
 
@@ -193,7 +211,8 @@ export function createVerboseFetch(
193
211
  if (isStreaming) {
194
212
  const [sdkStream, logStream] = response.body.tee();
195
213
 
196
- // Consume log stream asynchronously
214
+ // Consume log stream asynchronously, tracking pending operations (#231)
215
+ pendingStreamLogs++;
197
216
  (async () => {
198
217
  try {
199
218
  const reader = logStream.getReader();
@@ -222,6 +241,8 @@ export function createVerboseFetch(
222
241
  });
223
242
  } catch {
224
243
  // Ignore logging errors
244
+ } finally {
245
+ pendingStreamLogs--;
225
246
  }
226
247
  })();
227
248
 
@@ -301,3 +322,18 @@ export function getHttpCallCount(): number {
301
322
  export function resetHttpCallCount(): void {
302
323
  globalHttpCallCount = 0;
303
324
  }
325
+
326
+ /**
327
+ * Register a process exit handler that warns about pending stream log operations.
328
+ * Call this once at startup when verbose mode is enabled (#231).
329
+ */
330
+ export function registerPendingStreamLogExitHandler(): void {
331
+ process.once('exit', () => {
332
+ if (pendingStreamLogs > 0) {
333
+ // Use stderr directly since the process is exiting and log infrastructure may be unavailable
334
+ process.stderr.write(
335
+ `[verbose] warning: ${pendingStreamLogs} HTTP stream response log(s) were still pending at process exit — response bodies may be missing from logs\n`
336
+ );
337
+ }
338
+ });
339
+ }
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
- }