@link-assistant/agent 0.5.2 → 0.6.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.
@@ -216,7 +216,7 @@ export namespace SessionPrompt {
216
216
  }
217
217
 
218
218
  export function cancel(sessionID: string) {
219
- log.info('cancel', { sessionID });
219
+ log.info(() => ({ message: 'cancel', sessionID }));
220
220
  const s = state();
221
221
  const match = s[sessionID];
222
222
  if (!match) return;
@@ -242,7 +242,7 @@ export namespace SessionPrompt {
242
242
 
243
243
  let step = 0;
244
244
  while (true) {
245
- log.info('loop', { step, sessionID });
245
+ log.info(() => ({ message: 'loop', step, sessionID }));
246
246
  if (abort.aborted) break;
247
247
  let msgs = await MessageV2.filterCompacted(MessageV2.stream(sessionID));
248
248
 
@@ -276,7 +276,7 @@ export namespace SessionPrompt {
276
276
  lastAssistant.finish !== 'tool-calls' &&
277
277
  lastUser.id < lastAssistant.id
278
278
  ) {
279
- log.info('exiting loop', { sessionID });
279
+ log.info(() => ({ message: 'exiting loop', sessionID }));
280
280
  break;
281
281
  }
282
282
 
@@ -297,14 +297,13 @@ export namespace SessionPrompt {
297
297
  lastUser.model.modelID
298
298
  );
299
299
  } catch (error) {
300
- log.warn(
301
- 'Failed to initialize specified model, falling back to default model',
302
- {
303
- providerID: lastUser.model.providerID,
304
- modelID: lastUser.model.modelID,
305
- error: error instanceof Error ? error.message : String(error),
306
- }
307
- );
300
+ log.warn(() => ({
301
+ message:
302
+ 'Failed to initialize specified model, falling back to default model',
303
+ providerID: lastUser.model.providerID,
304
+ modelID: lastUser.model.modelID,
305
+ error: error instanceof Error ? error.message : String(error),
306
+ }));
308
307
  const defaultModel = await Provider.defaultModel();
309
308
  model = await Provider.getModel(
310
309
  defaultModel.providerID,
@@ -447,7 +446,7 @@ export namespace SessionPrompt {
447
446
  lastFinished.summary !== true &&
448
447
  SessionCompaction.isOverflow({
449
448
  tokens: lastFinished.tokens,
450
- model: model.info,
449
+ model: model.info ?? { id: model.modelID },
451
450
  })
452
451
  ) {
453
452
  await SessionCompaction.create({
@@ -488,13 +487,13 @@ export namespace SessionPrompt {
488
487
  sessionID,
489
488
  })) as MessageV2.Assistant,
490
489
  sessionID: sessionID,
491
- model: model.info,
490
+ model: model.info ?? { id: model.modelID },
492
491
  providerID: model.providerID,
493
492
  abort,
494
493
  });
495
494
  const system = await resolveSystemPrompt({
496
495
  providerID: model.providerID,
497
- modelID: model.info.id,
496
+ modelID: model.info?.id ?? model.modelID,
498
497
  agent,
499
498
  system: lastUser.system,
500
499
  appendSystem: lastUser.appendSystem,
@@ -507,10 +506,11 @@ export namespace SessionPrompt {
507
506
  processor,
508
507
  });
509
508
  const params = {
510
- temperature: model.info.temperature
511
- ? (agent.temperature ??
512
- ProviderTransform.temperature(model.providerID, model.modelID))
513
- : undefined,
509
+ temperature:
510
+ (model.info?.temperature ?? false)
511
+ ? (agent.temperature ??
512
+ ProviderTransform.temperature(model.providerID, model.modelID))
513
+ : undefined,
514
514
  topP:
515
515
  agent.topP ?? ProviderTransform.topP(model.providerID, model.modelID),
516
516
  options: {
@@ -520,7 +520,7 @@ export namespace SessionPrompt {
520
520
  model.npm ?? '',
521
521
  sessionID
522
522
  ),
523
- ...model.info.options,
523
+ ...(model.info?.options ?? {}),
524
524
  ...agent.options,
525
525
  },
526
526
  };
@@ -550,51 +550,79 @@ export namespace SessionPrompt {
550
550
  );
551
551
  const totalEstimatedTokens = systemTokens + userTokens;
552
552
 
553
- log.info('=== VERBOSE: API Request Details ===');
554
- log.info(`Model: ${model.providerID}/${model.modelID}`);
555
- log.info(`Session ID: ${sessionID}`);
556
- log.info(`Agent: ${agent.name}`);
557
- log.info(`Temperature: ${params.temperature ?? 'default'}`);
558
- log.info(`Top P: ${params.topP ?? 'default'}`);
559
- log.info(
560
- `Active Tools: ${Object.keys(tools)
561
- .filter((x) => x !== 'invalid')
562
- .join(', ')}`
563
- );
564
- log.info('--- System Prompt ---');
553
+ log.info(() => ({
554
+ message: '=== VERBOSE: API Request Details ===',
555
+ }));
556
+ log.info(() => ({
557
+ message: 'Model',
558
+ model: `${model.providerID}/${model.modelID}`,
559
+ }));
560
+ log.info(() => ({ message: 'Session ID', sessionID }));
561
+ log.info(() => ({ message: 'Agent', agent: agent.name }));
562
+ log.info(() => ({
563
+ message: 'Temperature',
564
+ temperature: params.temperature ?? 'default',
565
+ }));
566
+ log.info(() => ({
567
+ message: 'Top P',
568
+ topP: params.topP ?? 'default',
569
+ }));
570
+ log.info(() => ({
571
+ message: 'Active Tools',
572
+ tools: Object.keys(tools).filter((x) => x !== 'invalid'),
573
+ }));
574
+ log.info(() => ({ message: '--- System Prompt ---' }));
565
575
  for (let i = 0; i < system.length; i++) {
566
576
  const tokens = Token.estimate(system[i]);
567
- log.info(`System Message ${i + 1} (${tokens} tokens estimated):`);
568
- log.info(
569
- system[i].slice(0, 2000) +
570
- (system[i].length > 2000 ? '... [truncated]' : '')
571
- );
577
+ log.info(() => ({
578
+ message: 'System Message',
579
+ index: i + 1,
580
+ tokens,
581
+ }));
582
+ log.info(() => ({
583
+ message: 'System Message Content',
584
+ content:
585
+ system[i].slice(0, 2000) +
586
+ (system[i].length > 2000 ? '... [truncated]' : ''),
587
+ }));
572
588
  }
573
- log.info('--- Token Summary ---');
574
- log.info(`System prompt tokens (estimated): ${systemTokens}`);
575
- log.info(`User message tokens (estimated): ${userTokens}`);
576
- log.info(`Total estimated tokens: ${totalEstimatedTokens}`);
577
- log.info(
578
- `Model context limit: ${model.info.limit.context || 'unknown'}`
579
- );
580
- log.info(`Model output limit: ${model.info.limit.output || 'unknown'}`);
581
- log.info('=== END VERBOSE ===');
589
+ log.info(() => ({ message: '--- Token Summary ---' }));
590
+ log.info(() => ({
591
+ message: 'System prompt tokens (estimated)',
592
+ systemTokens,
593
+ }));
594
+ log.info(() => ({
595
+ message: 'User message tokens (estimated)',
596
+ userTokens,
597
+ }));
598
+ log.info(() => ({
599
+ message: 'Total estimated tokens',
600
+ totalEstimatedTokens,
601
+ }));
602
+ log.info(() => ({
603
+ message: 'Model context limit',
604
+ contextLimit: model.info?.limit?.context || 'unknown',
605
+ }));
606
+ log.info(() => ({
607
+ message: 'Model output limit',
608
+ outputLimit: model.info?.limit?.output || 'unknown',
609
+ }));
610
+ log.info(() => ({ message: '=== END VERBOSE ===' }));
582
611
  }
583
612
 
584
613
  const result = await processor.process(() =>
585
614
  streamText({
586
615
  onError(error) {
587
- log.error('stream error', {
588
- error,
589
- });
616
+ log.error(() => ({ message: 'stream error', error }));
590
617
  },
591
618
  async experimental_repairToolCall(input) {
592
619
  const lower = input.toolCall.toolName.toLowerCase();
593
620
  if (lower !== input.toolCall.toolName && tools[lower]) {
594
- log.info('repairing tool call', {
621
+ log.info(() => ({
622
+ message: 'repairing tool call',
595
623
  tool: input.toolCall.toolName,
596
624
  repaired: lower,
597
- });
625
+ }));
598
626
  return {
599
627
  ...input.toolCall,
600
628
  toolName: lower,
@@ -616,7 +644,7 @@ export namespace SessionPrompt {
616
644
  'x-opencode-request': lastUser.id,
617
645
  }
618
646
  : undefined),
619
- ...model.info.headers,
647
+ ...(model.info?.headers ?? {}),
620
648
  },
621
649
  // set to 0, we handle loop
622
650
  maxRetries: 0,
@@ -624,7 +652,7 @@ export namespace SessionPrompt {
624
652
  maxOutputTokens: ProviderTransform.maxOutputTokens(
625
653
  model.providerID,
626
654
  params.options,
627
- model.info.limit.output,
655
+ model.info?.limit?.output ?? 100000,
628
656
  OUTPUT_TOKEN_MAX
629
657
  ),
630
658
  abortSignal: abort,
@@ -662,7 +690,7 @@ export namespace SessionPrompt {
662
690
  })
663
691
  ),
664
692
  ],
665
- tools: model.info.tool_call === false ? undefined : tools,
693
+ tools: model.info?.tool_call === false ? undefined : tools,
666
694
  model: wrapLanguageModel({
667
695
  model: model.language,
668
696
  middleware: [
@@ -942,7 +970,7 @@ export namespace SessionPrompt {
942
970
  }
943
971
  break;
944
972
  case 'file:':
945
- log.info('file', { mime: part.mime });
973
+ log.info(() => ({ message: 'file', mime: part.mime }));
946
974
  // have to normalize, symbol search returns absolute paths
947
975
  // Decode the pathname since URL constructor doesn't automatically decode it
948
976
  const filepath = fileURLToPath(part.url);
@@ -1009,7 +1037,10 @@ export namespace SessionPrompt {
1009
1037
  );
1010
1038
  })
1011
1039
  .catch((error) => {
1012
- log.error('failed to read file', { error });
1040
+ log.error(() => ({
1041
+ message: 'failed to read file',
1042
+ error,
1043
+ }));
1013
1044
  const message =
1014
1045
  error instanceof Error ? error.message : error.toString();
1015
1046
  Bus.publish(Session.Event.Error, {
@@ -1373,7 +1404,7 @@ export namespace SessionPrompt {
1373
1404
  */
1374
1405
 
1375
1406
  export async function command(input: CommandInput) {
1376
- log.info('command', input);
1407
+ log.info(() => ({ message: 'command', ...input }));
1377
1408
  const command = await Command.get(input.command);
1378
1409
  const agentName = command.agent ?? input.agent ?? 'build';
1379
1410
 
@@ -1494,7 +1525,7 @@ export namespace SessionPrompt {
1494
1525
  small.npm ?? '',
1495
1526
  input.session.id
1496
1527
  ),
1497
- ...small.info.options,
1528
+ ...(small.info?.options ?? {}),
1498
1529
  };
1499
1530
  if (small.providerID === 'openai' || small.modelID.includes('gpt-5')) {
1500
1531
  if (small.modelID.includes('5.1')) {
@@ -1509,7 +1540,7 @@ export namespace SessionPrompt {
1509
1540
  };
1510
1541
  }
1511
1542
  await generateText({
1512
- maxOutputTokens: small.info.reasoning ? 1500 : 20,
1543
+ maxOutputTokens: small.info?.reasoning ? 1500 : 20,
1513
1544
  providerOptions: ProviderTransform.providerOptions(
1514
1545
  small.npm,
1515
1546
  small.providerID,
@@ -1550,7 +1581,7 @@ export namespace SessionPrompt {
1550
1581
  },
1551
1582
  ]),
1552
1583
  ],
1553
- headers: small.info.headers,
1584
+ headers: small.info?.headers ?? {},
1554
1585
  model: small.language,
1555
1586
  })
1556
1587
  .then((result) => {
@@ -1569,7 +1600,11 @@ export namespace SessionPrompt {
1569
1600
  });
1570
1601
  })
1571
1602
  .catch((error) => {
1572
- log.error('failed to generate title', { error, model: small.info.id });
1603
+ log.error(() => ({
1604
+ message: 'failed to generate title',
1605
+ error,
1606
+ model: small.info?.id ?? small.modelID,
1607
+ }));
1573
1608
  });
1574
1609
  }
1575
1610
  }
@@ -72,7 +72,7 @@ export namespace SessionRevert {
72
72
  }
73
73
 
74
74
  export async function unrevert(input: { sessionID: string }) {
75
- log.info('unreverting', input);
75
+ log.info(() => ({ message: 'unreverting', ...input }));
76
76
  SessionPrompt.assertNotBusy(input.sessionID);
77
77
  const session = await Session.get(input.sessionID);
78
78
  if (!session.revert) return session;
@@ -115,7 +115,7 @@ export namespace SessionSummary {
115
115
  headers: small.info.headers,
116
116
  model: small.language,
117
117
  });
118
- log.info('title', { title: result.text });
118
+ log.info(() => ({ message: 'title', title: result.text }));
119
119
  userMsg.summary.title = result.text;
120
120
  await Session.updateMessage(userMsg);
121
121
  }
@@ -152,7 +152,7 @@ export namespace SessionSummary {
152
152
  if (result) summary = result.text;
153
153
  }
154
154
  userMsg.summary.body = summary;
155
- log.info('body', { body: summary });
155
+ log.info(() => ({ message: 'body', body: summary }));
156
156
  await Session.updateMessage(userMsg);
157
157
  }
158
158
  }
@@ -28,7 +28,7 @@ export namespace Snapshot {
28
28
  await $`git --git-dir ${git} config core.autocrlf false`
29
29
  .quiet()
30
30
  .nothrow();
31
- log.info('initialized');
31
+ log.info(() => ({ message: 'initialized' }));
32
32
  }
33
33
  await $`git --git-dir ${git} --work-tree ${Instance.worktree} add .`
34
34
  .quiet()
@@ -40,7 +40,12 @@ export namespace Snapshot {
40
40
  .cwd(Instance.directory)
41
41
  .nothrow()
42
42
  .text();
43
- log.info('tracking', { hash, cwd: Instance.directory, git });
43
+ log.info(() => ({
44
+ message: 'tracking',
45
+ hash,
46
+ cwd: Instance.directory,
47
+ git,
48
+ }));
44
49
  return hash.trim();
45
50
  }
46
51
 
@@ -64,7 +69,11 @@ export namespace Snapshot {
64
69
 
65
70
  // If git diff fails, return empty patch
66
71
  if (result.exitCode !== 0) {
67
- log.warn('failed to get diff', { hash, exitCode: result.exitCode });
72
+ log.warn(() => ({
73
+ message: 'failed to get diff',
74
+ hash,
75
+ exitCode: result.exitCode,
76
+ }));
68
77
  return { hash, files: [] };
69
78
  }
70
79
 
@@ -81,7 +90,7 @@ export namespace Snapshot {
81
90
  }
82
91
 
83
92
  export async function restore(snapshot: string) {
84
- log.info('restore', { commit: snapshot });
93
+ log.info(() => ({ message: 'restore', commit: snapshot }));
85
94
  const git = gitdir();
86
95
  const result =
87
96
  await $`git --git-dir ${git} --work-tree ${Instance.worktree} read-tree ${snapshot} && git --git-dir ${git} --work-tree ${Instance.worktree} checkout-index -a -f`
@@ -90,12 +99,13 @@ export namespace Snapshot {
90
99
  .nothrow();
91
100
 
92
101
  if (result.exitCode !== 0) {
93
- log.error('failed to restore snapshot', {
102
+ log.error(() => ({
103
+ message: 'failed to restore snapshot',
94
104
  snapshot,
95
105
  exitCode: result.exitCode,
96
106
  stderr: result.stderr.toString(),
97
107
  stdout: result.stdout.toString(),
98
- });
108
+ }));
99
109
  }
100
110
  }
101
111
 
@@ -105,7 +115,7 @@ export namespace Snapshot {
105
115
  for (const item of patches) {
106
116
  for (const file of item.files) {
107
117
  if (files.has(file)) continue;
108
- log.info('reverting', { file, hash: item.hash });
118
+ log.info(() => ({ message: 'reverting', file, hash: item.hash }));
109
119
  const result =
110
120
  await $`git --git-dir ${git} --work-tree ${Instance.worktree} checkout ${item.hash} -- ${file}`
111
121
  .quiet()
@@ -119,11 +129,15 @@ export namespace Snapshot {
119
129
  .cwd(Instance.worktree)
120
130
  .nothrow();
121
131
  if (checkTree.exitCode === 0 && checkTree.text().trim()) {
122
- log.info('file existed in snapshot but checkout failed, keeping', {
132
+ log.info(() => ({
133
+ message: 'file existed in snapshot but checkout failed, keeping',
123
134
  file,
124
- });
135
+ }));
125
136
  } else {
126
- log.info('file did not exist in snapshot, deleting', { file });
137
+ log.info(() => ({
138
+ message: 'file did not exist in snapshot, deleting',
139
+ file,
140
+ }));
127
141
  await fs.unlink(file).catch(() => {});
128
142
  }
129
143
  }
@@ -145,12 +159,13 @@ export namespace Snapshot {
145
159
  .nothrow();
146
160
 
147
161
  if (result.exitCode !== 0) {
148
- log.warn('failed to get diff', {
162
+ log.warn(() => ({
163
+ message: 'failed to get diff',
149
164
  hash,
150
165
  exitCode: result.exitCode,
151
166
  stderr: result.stderr.toString(),
152
167
  stdout: result.stdout.toString(),
153
- });
168
+ }));
154
169
  return '';
155
170
  }
156
171
 
@@ -28,7 +28,7 @@ export namespace Storage {
28
28
  cwd: project,
29
29
  onlyFiles: false,
30
30
  })) {
31
- log.info(`migrating project ${projectDir}`);
31
+ log.info(() => ({ message: 'migrating project', projectDir }));
32
32
  let projectID = projectDir;
33
33
  const fullProjectDir = path.join(project, projectDir);
34
34
  let worktree = '/';
@@ -74,7 +74,10 @@ export namespace Storage {
74
74
  })
75
75
  );
76
76
 
77
- log.info(`migrating sessions for project ${projectID}`);
77
+ log.info(() => ({
78
+ message: 'migrating sessions for project',
79
+ projectID,
80
+ }));
78
81
  for await (const sessionFile of new Bun.Glob(
79
82
  'storage/session/info/*.json'
80
83
  ).scan({
@@ -87,13 +90,13 @@ export namespace Storage {
87
90
  projectID,
88
91
  path.basename(sessionFile)
89
92
  );
90
- log.info('copying', {
91
- sessionFile,
92
- dest,
93
- });
93
+ log.info(() => ({ message: 'copying', sessionFile, dest }));
94
94
  const session = await Bun.file(sessionFile).json();
95
95
  await Bun.write(dest, JSON.stringify(session));
96
- log.info(`migrating messages for session ${session.id}`);
96
+ log.info(() => ({
97
+ message: 'migrating messages for session',
98
+ sessionID: session.id,
99
+ }));
97
100
  for await (const msgFile of new Bun.Glob(
98
101
  `storage/session/message/${session.id}/*.json`
99
102
  ).scan({
@@ -106,14 +109,14 @@ export namespace Storage {
106
109
  session.id,
107
110
  path.basename(msgFile)
108
111
  );
109
- log.info('copying', {
110
- msgFile,
111
- dest,
112
- });
112
+ log.info(() => ({ message: 'copying', msgFile, dest }));
113
113
  const message = await Bun.file(msgFile).json();
114
114
  await Bun.write(dest, JSON.stringify(message));
115
115
 
116
- log.info(`migrating parts for message ${message.id}`);
116
+ log.info(() => ({
117
+ message: 'migrating parts for message',
118
+ messageID: message.id,
119
+ }));
117
120
  for await (const partFile of new Bun.Glob(
118
121
  `storage/session/part/${session.id}/${message.id}/*.json`
119
122
  ).scan({
@@ -127,10 +130,7 @@ export namespace Storage {
127
130
  path.basename(partFile)
128
131
  );
129
132
  const part = await Bun.file(partFile).json();
130
- log.info('copying', {
131
- partFile,
132
- dest,
133
- });
133
+ log.info(() => ({ message: 'copying', partFile, dest }));
134
134
  await Bun.write(dest, JSON.stringify(part));
135
135
  }
136
136
  }
@@ -178,10 +178,10 @@ export namespace Storage {
178
178
  .then((x) => parseInt(x))
179
179
  .catch(() => 0);
180
180
  for (let index = migration; index < MIGRATIONS.length; index++) {
181
- log.info('running migration', { index });
181
+ log.info(() => ({ message: 'running migration', index }));
182
182
  const migration = MIGRATIONS[index];
183
183
  await migration(dir).catch(() =>
184
- log.error('failed to run migration', { index })
184
+ log.error(() => ({ message: 'failed to run migration', index }))
185
185
  );
186
186
  await Bun.write(path.join(dir, 'migration'), (index + 1).toString());
187
187
  }