@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/file/watcher.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import z from 'zod';
|
|
2
2
|
import { Bus } from '../bus';
|
|
3
|
-
import {
|
|
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 (!
|
|
86
|
+
if (!config.experimentalWatcher) return;
|
|
87
87
|
state();
|
|
88
88
|
}
|
|
89
89
|
}
|
package/src/format/index.ts
CHANGED
|
@@ -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 {
|
|
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 (
|
|
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 (
|
|
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 ||
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
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:
|
|
801
|
-
level:
|
|
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
|
|
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 {
|
|
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
|
|
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
|
|
64
|
-
if (
|
|
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
|
-
|
|
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
|
-
|
|
104
|
-
BUILTIN_DEFAULT_TOOL_CALL_TIMEOUT,
|
|
98
|
+
config.mcpDefaultToolCallTimeout,
|
|
105
99
|
maxTimeout:
|
|
106
100
|
cfg.mcp_defaults?.max_tool_call_timeout ??
|
|
107
|
-
|
|
108
|
-
BUILTIN_MAX_TOOL_CALL_TIMEOUT,
|
|
101
|
+
config.mcpMaxToolCallTimeout,
|
|
109
102
|
};
|
|
110
103
|
|
|
111
104
|
await Promise.all(
|
package/src/project/bootstrap.ts
CHANGED
|
@@ -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';
|
package/src/project/project.ts
CHANGED
package/src/provider/provider.ts
CHANGED
|
@@ -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 {
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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
|
-
//
|
|
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
|
-
|
|
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 (
|
|
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 {
|
|
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
|
-
* -
|
|
12
|
-
* -
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
326
|
-
const maxBackoffDelay =
|
|
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 (!
|
|
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
|
|
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)
|
package/src/session/agent.js
CHANGED
|
@@ -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
|
|
215
|
-
const compact =
|
|
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 {
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
306
|
+
if (isVerbose()) {
|
|
307
307
|
log.info(() => ({
|
|
308
308
|
message: 'compaction streamText call',
|
|
309
309
|
providerID: model.providerID,
|
package/src/session/index.ts
CHANGED
|
@@ -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 {
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
685
|
+
if (isVerbose()) {
|
|
686
686
|
log.debug(() => ({
|
|
687
687
|
message:
|
|
688
688
|
'Standard usage empty, falling back to anthropic provider metadata',
|
package/src/session/processor.ts
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
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 (
|
|
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
|
|
550
|
+
// If retry-after exceeds LINK_ASSISTANT_AGENT_RETRY_TIMEOUT, fail immediately
|
|
551
551
|
if (
|
|
552
552
|
delayError instanceof SessionRetry.RetryTimeoutExceededError
|
|
553
553
|
) {
|