@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.
- package/README.md +1 -1
- package/package.json +3 -2
- package/src/agent/agent.ts +1 -1
- package/src/auth/plugins.ts +4 -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/model-config.js +20 -10
- 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 +50 -38
- 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 +23 -26
- package/src/provider/retry-fetch.ts +109 -23
- 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/storage/storage.ts +13 -2
- 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 +42 -6
- package/src/flag/flag.ts +0 -212
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
|
) {
|
package/src/session/prompt.ts
CHANGED
|
@@ -24,7 +24,7 @@ import { Instance } from '../project/instance';
|
|
|
24
24
|
import { Bus } from '../bus';
|
|
25
25
|
import { ProviderTransform } from '../provider/transform';
|
|
26
26
|
import { SystemPrompt } from './system';
|
|
27
|
-
import {
|
|
27
|
+
import { config, isVerbose } from '../config/config';
|
|
28
28
|
import { Token } from '../util/token';
|
|
29
29
|
|
|
30
30
|
import PROMPT_PLAN from '../session/prompt/plan.txt';
|
|
@@ -684,7 +684,7 @@ export namespace SessionPrompt {
|
|
|
684
684
|
: [];
|
|
685
685
|
|
|
686
686
|
// Verbose logging: output request details for debugging
|
|
687
|
-
if (
|
|
687
|
+
if (isVerbose()) {
|
|
688
688
|
const systemTokens = system.reduce(
|
|
689
689
|
(acc, s) => acc + Token.estimate(s),
|
|
690
690
|
0
|
|
@@ -766,8 +766,8 @@ export namespace SessionPrompt {
|
|
|
766
766
|
const result = await processor.process(() =>
|
|
767
767
|
streamText({
|
|
768
768
|
timeout: {
|
|
769
|
-
chunkMs:
|
|
770
|
-
stepMs:
|
|
769
|
+
chunkMs: config.streamChunkTimeoutMs,
|
|
770
|
+
stepMs: config.streamStepTimeoutMs,
|
|
771
771
|
},
|
|
772
772
|
onError(error) {
|
|
773
773
|
log.error(() => ({ message: 'stream error', error }));
|
|
@@ -1671,7 +1671,7 @@ export namespace SessionPrompt {
|
|
|
1671
1671
|
modelID: string;
|
|
1672
1672
|
}) {
|
|
1673
1673
|
// Skip title generation if disabled (default)
|
|
1674
|
-
if (!
|
|
1674
|
+
if (!config.generateTitle) {
|
|
1675
1675
|
log.info(() => ({
|
|
1676
1676
|
message: 'title generation disabled',
|
|
1677
1677
|
hint: 'Enable with --generate-title flag or AGENT_GENERATE_TITLE=true',
|
package/src/session/retry.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { MessageV2 } from './message-v2';
|
|
2
|
-
import {
|
|
2
|
+
import { config } from '../config/config';
|
|
3
3
|
import { Log } from '../util/log';
|
|
4
4
|
|
|
5
5
|
export namespace SessionRetry {
|
|
@@ -13,16 +13,16 @@ export namespace SessionRetry {
|
|
|
13
13
|
// This caps exponential backoff when headers are not available
|
|
14
14
|
// Can be configured via AGENT_MAX_RETRY_DELAY env var
|
|
15
15
|
export function getMaxRetryDelay(): number {
|
|
16
|
-
return
|
|
16
|
+
return config.maxRetryDelay * 1000;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
// Get retry timeout in milliseconds
|
|
20
20
|
export function getRetryTimeout(): number {
|
|
21
|
-
return
|
|
21
|
+
return config.retryTimeout * 1000;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
|
-
* Error thrown when retry-after exceeds
|
|
25
|
+
* Error thrown when retry-after exceeds LINK_ASSISTANT_AGENT_RETRY_TIMEOUT
|
|
26
26
|
* This indicates the wait time is too long and we should fail immediately
|
|
27
27
|
*/
|
|
28
28
|
export class RetryTimeoutExceededError extends Error {
|
|
@@ -34,7 +34,7 @@ export namespace SessionRetry {
|
|
|
34
34
|
const maxTimeoutHours = (maxTimeoutMs / 1000 / 3600).toFixed(2);
|
|
35
35
|
super(
|
|
36
36
|
`API returned retry-after of ${retryAfterHours} hours, which exceeds the maximum retry timeout of ${maxTimeoutHours} hours. ` +
|
|
37
|
-
`Failing immediately instead of waiting. You can adjust
|
|
37
|
+
`Failing immediately instead of waiting. You can adjust LINK_ASSISTANT_AGENT_RETRY_TIMEOUT env var to increase the limit.`
|
|
38
38
|
);
|
|
39
39
|
this.name = 'RetryTimeoutExceededError';
|
|
40
40
|
this.retryAfterMs = retryAfterMs;
|
|
@@ -75,7 +75,7 @@ export namespace SessionRetry {
|
|
|
75
75
|
sessionID: string,
|
|
76
76
|
errorType: string
|
|
77
77
|
): { shouldRetry: boolean; elapsedTime: number; maxTime: number } {
|
|
78
|
-
const maxTime =
|
|
78
|
+
const maxTime = config.retryTimeout * 1000; // Convert to ms
|
|
79
79
|
const state = retryStates.get(sessionID);
|
|
80
80
|
|
|
81
81
|
if (!state || state.errorType !== errorType) {
|
|
@@ -198,15 +198,15 @@ export namespace SessionRetry {
|
|
|
198
198
|
*
|
|
199
199
|
* RETRY LOGIC (per issue #157 requirements):
|
|
200
200
|
* 1. If retry-after header is available:
|
|
201
|
-
* - If retry-after <=
|
|
202
|
-
* - If retry-after >
|
|
201
|
+
* - If retry-after <= LINK_ASSISTANT_AGENT_RETRY_TIMEOUT: use it directly (exact time)
|
|
202
|
+
* - If retry-after > LINK_ASSISTANT_AGENT_RETRY_TIMEOUT: throw RetryTimeoutExceededError (fail immediately)
|
|
203
203
|
* 2. If no retry-after header:
|
|
204
204
|
* - Use exponential backoff up to AGENT_MAX_RETRY_DELAY
|
|
205
205
|
*
|
|
206
206
|
* Adds jitter to prevent thundering herd when multiple requests retry.
|
|
207
207
|
* See: https://github.com/link-assistant/agent/issues/157
|
|
208
208
|
*
|
|
209
|
-
* @throws {RetryTimeoutExceededError} When retry-after exceeds
|
|
209
|
+
* @throws {RetryTimeoutExceededError} When retry-after exceeds LINK_ASSISTANT_AGENT_RETRY_TIMEOUT
|
|
210
210
|
*/
|
|
211
211
|
export function delay(error: MessageV2.APIError, attempt: number): number {
|
|
212
212
|
const maxRetryTimeout = getRetryTimeout();
|
package/src/session/summary.ts
CHANGED
|
@@ -13,7 +13,7 @@ import path from 'path';
|
|
|
13
13
|
import { Instance } from '../project/instance';
|
|
14
14
|
import { Storage } from '../storage/storage';
|
|
15
15
|
import { Bus } from '../bus';
|
|
16
|
-
import {
|
|
16
|
+
import { config, isVerbose } from '../config/config';
|
|
17
17
|
import { Token } from '../util/token';
|
|
18
18
|
|
|
19
19
|
export namespace SessionSummary {
|
|
@@ -83,7 +83,7 @@ export namespace SessionSummary {
|
|
|
83
83
|
|
|
84
84
|
// Skip AI-powered summarization if disabled
|
|
85
85
|
// See: https://github.com/link-assistant/agent/issues/217
|
|
86
|
-
if (!
|
|
86
|
+
if (!config.summarizeSession) {
|
|
87
87
|
log.info(() => ({
|
|
88
88
|
message: 'session summarization disabled',
|
|
89
89
|
hint: 'Enable with --summarize-session flag (enabled by default) or AGENT_SUMMARIZE_SESSION=true',
|
|
@@ -141,7 +141,7 @@ export namespace SessionSummary {
|
|
|
141
141
|
return;
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
-
if (
|
|
144
|
+
if (isVerbose()) {
|
|
145
145
|
log.info(() => ({
|
|
146
146
|
message: 'summarization model loaded',
|
|
147
147
|
providerID: model.providerID,
|
|
@@ -167,7 +167,7 @@ export namespace SessionSummary {
|
|
|
167
167
|
</text>
|
|
168
168
|
`;
|
|
169
169
|
|
|
170
|
-
if (
|
|
170
|
+
if (isVerbose()) {
|
|
171
171
|
log.info(() => ({
|
|
172
172
|
message: 'generating title via API',
|
|
173
173
|
providerID: model.providerID,
|
|
@@ -203,7 +203,7 @@ export namespace SessionSummary {
|
|
|
203
203
|
model: model.language,
|
|
204
204
|
});
|
|
205
205
|
|
|
206
|
-
if (
|
|
206
|
+
if (isVerbose()) {
|
|
207
207
|
log.info(() => ({
|
|
208
208
|
message: 'title API response received',
|
|
209
209
|
providerID: model.providerID,
|
|
@@ -234,7 +234,7 @@ export namespace SessionSummary {
|
|
|
234
234
|
const modelMessages = await MessageV2.toModelMessage(messages);
|
|
235
235
|
const conversationContent = JSON.stringify(modelMessages);
|
|
236
236
|
|
|
237
|
-
if (
|
|
237
|
+
if (isVerbose()) {
|
|
238
238
|
log.info(() => ({
|
|
239
239
|
message: 'generating body summary via API',
|
|
240
240
|
providerID: model.providerID,
|
|
@@ -264,7 +264,7 @@ export namespace SessionSummary {
|
|
|
264
264
|
],
|
|
265
265
|
headers: model.info.headers,
|
|
266
266
|
}).catch((err) => {
|
|
267
|
-
if (
|
|
267
|
+
if (isVerbose()) {
|
|
268
268
|
log.warn(() => ({
|
|
269
269
|
message: 'body summary API call failed',
|
|
270
270
|
providerID: model.providerID,
|
|
@@ -276,7 +276,7 @@ export namespace SessionSummary {
|
|
|
276
276
|
return undefined;
|
|
277
277
|
});
|
|
278
278
|
if (result) {
|
|
279
|
-
if (
|
|
279
|
+
if (isVerbose()) {
|
|
280
280
|
log.info(() => ({
|
|
281
281
|
message: 'body summary API response received',
|
|
282
282
|
providerID: model.providerID,
|
package/src/session/system.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Ripgrep } from '../file/ripgrep';
|
|
2
2
|
import { Global } from '../global';
|
|
3
3
|
import { Filesystem } from '../util/filesystem';
|
|
4
|
-
import { Config } from '../config/config';
|
|
4
|
+
import { Config } from '../config/file-config';
|
|
5
5
|
|
|
6
6
|
import { Instance } from '../project/instance';
|
|
7
7
|
import path from 'path';
|
package/src/snapshot/index.ts
CHANGED
|
@@ -4,7 +4,7 @@ import fs from 'fs/promises';
|
|
|
4
4
|
import { Log } from '../util/log';
|
|
5
5
|
import { Global } from '../global';
|
|
6
6
|
import z from 'zod';
|
|
7
|
-
import { Config } from '../config/config';
|
|
7
|
+
import { Config } from '../config/file-config';
|
|
8
8
|
import { Instance } from '../project/instance';
|
|
9
9
|
|
|
10
10
|
export namespace Snapshot {
|
package/src/storage/storage.ts
CHANGED
|
@@ -180,8 +180,19 @@ export namespace Storage {
|
|
|
180
180
|
for (let index = migration; index < MIGRATIONS.length; index++) {
|
|
181
181
|
log.info(() => ({ message: 'running migration', index }));
|
|
182
182
|
const migration = MIGRATIONS[index];
|
|
183
|
-
await migration(dir).catch(() =>
|
|
184
|
-
log.error(() => ({
|
|
183
|
+
await migration(dir).catch((migrationError) =>
|
|
184
|
+
log.error(() => ({
|
|
185
|
+
message: 'failed to run migration',
|
|
186
|
+
index,
|
|
187
|
+
error:
|
|
188
|
+
migrationError instanceof Error
|
|
189
|
+
? {
|
|
190
|
+
name: migrationError.name,
|
|
191
|
+
message: migrationError.message,
|
|
192
|
+
stack: migrationError.stack,
|
|
193
|
+
}
|
|
194
|
+
: String(migrationError),
|
|
195
|
+
}))
|
|
185
196
|
);
|
|
186
197
|
await Bun.write(path.join(dir, 'migration'), (index + 1).toString());
|
|
187
198
|
}
|
package/src/tool/read.ts
CHANGED
|
@@ -4,6 +4,7 @@ import * as path from 'path';
|
|
|
4
4
|
import { Tool } from './tool';
|
|
5
5
|
import { FileTime } from '../file/time';
|
|
6
6
|
import DESCRIPTION from './read.txt';
|
|
7
|
+
import { config } from '../config/config';
|
|
7
8
|
import { Filesystem } from '../util/filesystem';
|
|
8
9
|
import { Instance } from '../project/instance';
|
|
9
10
|
import { Provider } from '../provider/provider';
|
|
@@ -70,8 +71,8 @@ export const ReadTool = Tool.define('read', {
|
|
|
70
71
|
return model.info.modalities?.input?.includes('image') ?? false;
|
|
71
72
|
})();
|
|
72
73
|
if (isImage) {
|
|
73
|
-
// Image format validation (can be disabled via
|
|
74
|
-
const verifyImages =
|
|
74
|
+
// Image format validation (can be disabled via LINK_ASSISTANT_AGENT_VERIFY_IMAGES_AT_READ_TOOL)
|
|
75
|
+
const verifyImages = config.verifyImagesAtReadTool;
|
|
75
76
|
if (verifyImages && !supportsImages) {
|
|
76
77
|
throw new Error(
|
|
77
78
|
`Failed to read image: ${filepath}, model may not be able to read images`
|
|
@@ -90,7 +91,7 @@ export const ReadTool = Tool.define('read', {
|
|
|
90
91
|
)
|
|
91
92
|
.map((b) => b.toString(16).padStart(2, '0'))
|
|
92
93
|
.join(' ')}\n` +
|
|
93
|
-
`To disable image validation, set environment variable:
|
|
94
|
+
`To disable image validation, set environment variable: LINK_ASSISTANT_AGENT_VERIFY_IMAGES_AT_READ_TOOL=false`
|
|
94
95
|
);
|
|
95
96
|
}
|
|
96
97
|
}
|
package/src/tool/registry.ts
CHANGED
|
@@ -13,10 +13,9 @@ import { InvalidTool } from './invalid';
|
|
|
13
13
|
import type { Agent } from '../agent/agent';
|
|
14
14
|
import { Tool } from './tool';
|
|
15
15
|
import { Instance } from '../project/instance';
|
|
16
|
-
import { Config } from '../config/config';
|
|
16
|
+
import { Config } from '../config/file-config';
|
|
17
17
|
import { WebSearchTool } from './websearch';
|
|
18
18
|
import { CodeSearchTool } from './codesearch';
|
|
19
|
-
import { Flag } from '../flag/flag';
|
|
20
19
|
|
|
21
20
|
export namespace ToolRegistry {
|
|
22
21
|
export const state = Instance.state(async () => {
|
package/src/tool/websearch.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import z from 'zod';
|
|
2
2
|
import { Tool } from './tool';
|
|
3
3
|
import DESCRIPTION from './websearch.txt';
|
|
4
|
-
import { Config } from '../config/config';
|
|
4
|
+
import { Config } from '../config/file-config';
|
|
5
5
|
import { createVerboseFetch } from '../util/verbose-fetch';
|
|
6
6
|
|
|
7
7
|
const verboseFetch = createVerboseFetch(fetch, { caller: 'websearch' });
|
package/src/util/log-lazy.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import makeLog, { levels, LogLevel } from 'log-lazy';
|
|
2
|
-
import {
|
|
2
|
+
import { config, isVerbose } from '../config/config';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* JSON Lazy Logger
|
|
@@ -53,10 +53,8 @@ const LEVEL_PRESETS = {
|
|
|
53
53
|
|
|
54
54
|
type LevelPreset = keyof typeof LEVEL_PRESETS;
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
// Compact JSON mode (can be set at runtime, initialized from Flag)
|
|
59
|
-
let compactJsonMode = Flag.COMPACT_JSON();
|
|
56
|
+
// Compact JSON mode (can be set at runtime, initialized from config)
|
|
57
|
+
let compactJsonMode = false; // Deferred to avoid circular init; config.compactJson checked at runtime
|
|
60
58
|
|
|
61
59
|
/**
|
|
62
60
|
* Set compact JSON output mode
|
|
@@ -99,8 +97,8 @@ function formatLogEntry(
|
|
|
99
97
|
logEntry.message = String(data);
|
|
100
98
|
}
|
|
101
99
|
|
|
102
|
-
// Check both local setting and global
|
|
103
|
-
const useCompact = compactJsonMode ||
|
|
100
|
+
// Check both local setting and global config
|
|
101
|
+
const useCompact = compactJsonMode || config.compactJson;
|
|
104
102
|
return useCompact
|
|
105
103
|
? JSON.stringify(logEntry)
|
|
106
104
|
: JSON.stringify(logEntry, null, 2);
|
|
@@ -158,7 +156,7 @@ export function createLazyLogger(
|
|
|
158
156
|
initialTags?: Record<string, unknown>
|
|
159
157
|
): LazyLogger {
|
|
160
158
|
// Determine initial log level based on verbose flag
|
|
161
|
-
const initialLevel =
|
|
159
|
+
const initialLevel = isVerbose() ? LEVEL_VERBOSE : LEVEL_DISABLED;
|
|
162
160
|
|
|
163
161
|
// Create base log-lazy instance
|
|
164
162
|
const baseLog = makeLog({ level: initialLevel });
|
|
@@ -281,7 +279,7 @@ export function createLazyLogger(
|
|
|
281
279
|
|
|
282
280
|
// Configuration
|
|
283
281
|
Object.defineProperty(wrappedLog, 'enabled', {
|
|
284
|
-
get: () =>
|
|
282
|
+
get: () => isVerbose(),
|
|
285
283
|
enumerable: true,
|
|
286
284
|
});
|
|
287
285
|
|
|
@@ -296,10 +294,10 @@ export const lazyLog = createLazyLogger({ service: 'agent' });
|
|
|
296
294
|
|
|
297
295
|
/**
|
|
298
296
|
* Utility to update the global logger level at runtime
|
|
299
|
-
* Call this after
|
|
297
|
+
* Call this after setVerbose() to sync the logger state
|
|
300
298
|
*/
|
|
301
299
|
export function syncLoggerWithVerboseFlag(): void {
|
|
302
|
-
if (
|
|
300
|
+
if (isVerbose()) {
|
|
303
301
|
lazyLog.setLevel('verbose');
|
|
304
302
|
} else {
|
|
305
303
|
lazyLog.setLevel('disabled');
|
package/src/util/log.ts
CHANGED
|
@@ -3,7 +3,7 @@ import fs from 'fs/promises';
|
|
|
3
3
|
import { Global } from '../global';
|
|
4
4
|
import z from 'zod';
|
|
5
5
|
import makeLog, { levels } from 'log-lazy';
|
|
6
|
-
import {
|
|
6
|
+
import { config, isVerbose } from '../config/config';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Logging module with JSON output and lazy evaluation support.
|
|
@@ -32,7 +32,7 @@ export namespace Log {
|
|
|
32
32
|
|
|
33
33
|
let level: Level = 'INFO';
|
|
34
34
|
let jsonOutput = false; // Whether to output JSON format (enabled in verbose mode)
|
|
35
|
-
let compactJsonOutput =
|
|
35
|
+
let compactJsonOutput = false; // Whether to use compact JSON (single line); deferred to avoid circular init
|
|
36
36
|
|
|
37
37
|
function shouldLog(input: Level): boolean {
|
|
38
38
|
return levelPriority[input] >= levelPriority[level];
|
|
@@ -109,8 +109,9 @@ export namespace Log {
|
|
|
109
109
|
// Always use JSON output format for logs
|
|
110
110
|
jsonOutput = true;
|
|
111
111
|
|
|
112
|
-
// Configure lazy logging level based on verbose flag
|
|
113
|
-
|
|
112
|
+
// Configure lazy logging level based on verbose flag (with env var fallback)
|
|
113
|
+
// See: https://github.com/link-assistant/agent/issues/227
|
|
114
|
+
if (isVerbose() || options.print) {
|
|
114
115
|
// Enable all levels for lazy logging when verbose
|
|
115
116
|
lazyLogInstance = makeLog({
|
|
116
117
|
level: levels.debug | levels.info | levels.warn | levels.error,
|
|
@@ -208,8 +209,8 @@ export namespace Log {
|
|
|
208
209
|
}
|
|
209
210
|
|
|
210
211
|
// Use compact or pretty format based on configuration
|
|
211
|
-
// Check both local setting and global
|
|
212
|
-
const useCompact = compactJsonOutput ||
|
|
212
|
+
// Check both local setting and global config
|
|
213
|
+
const useCompact = compactJsonOutput || config.compactJson;
|
|
213
214
|
return useCompact
|
|
214
215
|
? JSON.stringify(logEntry)
|
|
215
216
|
: JSON.stringify(logEntry, null, 2);
|
|
@@ -371,10 +372,10 @@ export namespace Log {
|
|
|
371
372
|
|
|
372
373
|
/**
|
|
373
374
|
* Sync lazy logging with verbose flag at runtime
|
|
374
|
-
* Call after
|
|
375
|
+
* Call after setVerbose() to update lazy logging state
|
|
375
376
|
*/
|
|
376
377
|
export function syncWithVerboseFlag(): void {
|
|
377
|
-
if (
|
|
378
|
+
if (isVerbose()) {
|
|
378
379
|
jsonOutput = true;
|
|
379
380
|
// Use stdout for verbose output (following Unix conventions)
|
|
380
381
|
write = (msg: any) => process.stdout.write(msg);
|