@link-assistant/agent 0.5.3 → 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.
@@ -36,7 +36,7 @@ export namespace FileWatcher {
36
36
  const state = Instance.state(
37
37
  async () => {
38
38
  if (Instance.project.vcs !== 'git') return {};
39
- log.info('init');
39
+ log.info(() => ({ message: 'init' }));
40
40
  const cfg = await Config.get();
41
41
  const backend = (() => {
42
42
  if (process.platform === 'win32') return 'windows';
@@ -44,18 +44,23 @@ export namespace FileWatcher {
44
44
  if (process.platform === 'linux') return 'inotify';
45
45
  })();
46
46
  if (!backend) {
47
- log.error('watcher backend not supported', {
47
+ log.error(() => ({
48
+ message: 'watcher backend not supported',
48
49
  platform: process.platform,
49
- });
50
+ }));
50
51
  return {};
51
52
  }
52
- log.info('watcher backend', { platform: process.platform, backend });
53
+ log.info(() => ({
54
+ message: 'watcher backend',
55
+ platform: process.platform,
56
+ backend,
57
+ }));
53
58
  const sub = await watcher().subscribe(
54
59
  Instance.directory,
55
60
  (err, evts) => {
56
61
  if (err) return;
57
62
  for (const evt of evts) {
58
- log.info('event', evt);
63
+ log.info(() => ({ message: 'event', ...evt }));
59
64
  if (evt.type === 'create')
60
65
  Bus.publish(Event.Updated, { file: evt.path, event: 'add' });
61
66
  if (evt.type === 'update')
@@ -29,7 +29,7 @@ export namespace Format {
29
29
 
30
30
  const formatters: Record<string, Formatter.Info> = {};
31
31
  if (cfg.formatter === false) {
32
- log.info('all formatters are disabled');
32
+ log.info(() => ({ message: 'all formatters are disabled' }));
33
33
  return {
34
34
  enabled,
35
35
  formatters,
@@ -77,10 +77,10 @@ export namespace Format {
77
77
  const formatters = await state().then((x) => x.formatters);
78
78
  const result = [];
79
79
  for (const item of Object.values(formatters)) {
80
- log.info('checking', { name: item.name, ext });
80
+ log.info(() => ({ message: 'checking', name: item.name, ext }));
81
81
  if (!item.extensions.includes(ext)) continue;
82
82
  if (!(await isEnabled(item))) continue;
83
- log.info('enabled', { name: item.name, ext });
83
+ log.info(() => ({ message: 'enabled', name: item.name, ext }));
84
84
  result.push(item);
85
85
  }
86
86
  return result;
@@ -101,14 +101,14 @@ export namespace Format {
101
101
  }
102
102
 
103
103
  export function init() {
104
- log.info('init');
104
+ log.info(() => ({ message: 'init' }));
105
105
  Bus.subscribe(File.Event.Edited, async (payload) => {
106
106
  const file = payload.properties.file;
107
- log.info('formatting', { file });
107
+ log.info(() => ({ message: 'formatting', file }));
108
108
  const ext = path.extname(file);
109
109
 
110
110
  for (const item of await getFormatter(ext)) {
111
- log.info('running', { command: item.command });
111
+ log.info(() => ({ message: 'running', command: item.command }));
112
112
  try {
113
113
  const proc = Bun.spawn({
114
114
  cmd: item.command.map((x) => x.replace('$FILE', file)),
@@ -119,17 +119,19 @@ export namespace Format {
119
119
  });
120
120
  const exit = await proc.exited;
121
121
  if (exit !== 0)
122
- log.error('failed', {
122
+ log.error(() => ({
123
+ message: 'failed',
123
124
  command: item.command,
124
125
  ...item.environment,
125
- });
126
+ }));
126
127
  } catch (error) {
127
- log.error('failed to format file', {
128
+ log.error(() => ({
129
+ message: 'failed to format file',
128
130
  error,
129
131
  command: item.command,
130
132
  ...item.environment,
131
133
  file,
132
- });
134
+ }));
133
135
  }
134
136
  }
135
137
  });
package/src/index.js CHANGED
@@ -246,22 +246,19 @@ async function readSystemMessages(argv) {
246
246
  }
247
247
 
248
248
  async function runAgentMode(argv, request) {
249
- // Note: verbose flag and logging are now initialized in middleware
250
- // See main() function for the middleware that sets up Flag and Log.init()
251
-
252
- // Log version and command info in verbose mode
253
- if (Flag.OPENCODE_VERBOSE) {
254
- console.error(`Agent version: ${pkg.version}`);
255
- console.error(`Command: ${process.argv.join(' ')}`);
256
- console.error(`Working directory: ${process.cwd()}`);
257
- console.error(`Script path: ${import.meta.path}`);
258
- }
259
-
260
- // Log dry-run mode if enabled
249
+ // Log version and command info in verbose mode using lazy logging
250
+ Log.Default.lazy.info(() => ({
251
+ message: 'Agent started',
252
+ version: pkg.version,
253
+ command: process.argv.join(' '),
254
+ workingDirectory: process.cwd(),
255
+ scriptPath: import.meta.path,
256
+ }));
261
257
  if (Flag.OPENCODE_DRY_RUN) {
262
- console.error(
263
- `[DRY RUN MODE] No actual API calls or package installations will be made`
264
- );
258
+ Log.Default.lazy.info(() => ({
259
+ message: 'Dry run mode enabled',
260
+ mode: 'dry-run',
261
+ }));
265
262
  }
266
263
 
267
264
  const { providerID, modelID } = await parseModelConfig(argv);
@@ -269,9 +266,11 @@ async function runAgentMode(argv, request) {
269
266
  // Validate and get JSON standard
270
267
  const jsonStandard = argv['json-standard'];
271
268
  if (!isValidJsonStandard(jsonStandard)) {
272
- console.error(
273
- `Invalid JSON standard: ${jsonStandard}. Use "opencode" or "claude".`
274
- );
269
+ outputStatus({
270
+ type: 'error',
271
+ errorType: 'ValidationError',
272
+ message: `Invalid JSON standard: ${jsonStandard}. Use "opencode" or "claude".`,
273
+ });
275
274
  process.exit(1);
276
275
  }
277
276
 
@@ -317,24 +316,20 @@ async function runAgentMode(argv, request) {
317
316
  * @param {object} argv - Command line arguments
318
317
  */
319
318
  async function runContinuousAgentMode(argv) {
320
- // Note: verbose flag and logging are now initialized in middleware
321
- // See main() function for the middleware that sets up Flag and Log.init()
322
-
323
319
  const compactJson = argv['compact-json'] === true;
324
-
325
- // Log version and command info in verbose mode
326
- if (Flag.OPENCODE_VERBOSE) {
327
- console.error(`Agent version: ${pkg.version}`);
328
- console.error(`Command: ${process.argv.join(' ')}`);
329
- console.error(`Working directory: ${process.cwd()}`);
330
- console.error(`Script path: ${import.meta.path}`);
331
- }
332
-
333
- // Log dry-run mode if enabled
320
+ // Log version and command info in verbose mode using lazy logging
321
+ Log.Default.lazy.info(() => ({
322
+ message: 'Agent started (continuous mode)',
323
+ version: pkg.version,
324
+ command: process.argv.join(' '),
325
+ workingDirectory: process.cwd(),
326
+ scriptPath: import.meta.path,
327
+ }));
334
328
  if (Flag.OPENCODE_DRY_RUN) {
335
- console.error(
336
- `[DRY RUN MODE] No actual API calls or package installations will be made`
337
- );
329
+ Log.Default.lazy.info(() => ({
330
+ message: 'Dry run mode enabled',
331
+ mode: 'dry-run',
332
+ }));
338
333
  }
339
334
 
340
335
  const { providerID, modelID } = await parseModelConfig(argv);
@@ -935,7 +930,7 @@ async function main() {
935
930
  }
936
931
 
937
932
  // Initialize logging system
938
- // - If verbose: print logs to stderr for debugging
933
+ // - If verbose: print logs to stderr for debugging in JSON format
939
934
  // - Otherwise: write logs to file to keep CLI output clean
940
935
  await Log.init({
941
936
  print: Flag.OPENCODE_VERBOSE,
package/src/mcp/index.ts CHANGED
@@ -81,9 +81,10 @@ export namespace MCP {
81
81
  await Promise.all(
82
82
  Object.values(state.clients).map((client) =>
83
83
  client.close().catch((error) => {
84
- log.error('Failed to close MCP client', {
84
+ log.error(() => ({
85
+ message: 'Failed to close MCP client',
85
86
  error,
86
- });
87
+ }));
87
88
  })
88
89
  )
89
90
  );
@@ -119,10 +120,10 @@ export namespace MCP {
119
120
 
120
121
  async function create(key: string, mcp: Config.Mcp) {
121
122
  if (mcp.enabled === false) {
122
- log.info('mcp server disabled', { key });
123
+ log.info(() => ({ message: 'mcp server disabled', key }));
123
124
  return;
124
125
  }
125
- log.info('found', { key, type: mcp.type });
126
+ log.info(() => ({ message: 'found', key, type: mcp.type }));
126
127
  let mcpClient: MCPClient | undefined;
127
128
  let status: Status | undefined = undefined;
128
129
 
@@ -152,7 +153,11 @@ export namespace MCP {
152
153
  transport,
153
154
  })
154
155
  .then((client) => {
155
- log.info('connected', { key, transport: name });
156
+ log.info(() => ({
157
+ message: 'connected',
158
+ key,
159
+ transport: name,
160
+ }));
156
161
  mcpClient = client;
157
162
  status = { status: 'connected' };
158
163
  return true;
@@ -160,12 +165,13 @@ export namespace MCP {
160
165
  .catch((error) => {
161
166
  lastError =
162
167
  error instanceof Error ? error : new Error(String(error));
163
- log.debug('transport connection failed', {
168
+ log.debug(() => ({
169
+ message: 'transport connection failed',
164
170
  key,
165
171
  transport: name,
166
172
  url: mcp.url,
167
173
  error: lastError.message,
168
- });
174
+ }));
169
175
  status = {
170
176
  status: 'failed' as const,
171
177
  error: lastError.message,
@@ -198,11 +204,12 @@ export namespace MCP {
198
204
  };
199
205
  })
200
206
  .catch((error) => {
201
- log.error('local mcp startup failed', {
207
+ log.error(() => ({
208
+ message: 'local mcp startup failed',
202
209
  key,
203
210
  command: mcp.command,
204
211
  error: error instanceof Error ? error.message : String(error),
205
- });
212
+ }));
206
213
  status = {
207
214
  status: 'failed' as const,
208
215
  error: error instanceof Error ? error.message : String(error),
@@ -228,14 +235,19 @@ export namespace MCP {
228
235
  mcpClient.tools(),
229
236
  mcp.timeout ?? 5000
230
237
  ).catch((err) => {
231
- log.error('failed to get tools from client', { key, error: err });
238
+ log.error(() => ({
239
+ message: 'failed to get tools from client',
240
+ key,
241
+ error: err,
242
+ }));
232
243
  return undefined;
233
244
  });
234
245
  if (!result) {
235
246
  await mcpClient.close().catch((error) => {
236
- log.error('Failed to close MCP client', {
247
+ log.error(() => ({
248
+ message: 'Failed to close MCP client',
237
249
  error,
238
- });
250
+ }));
239
251
  });
240
252
  status = {
241
253
  status: 'failed',
@@ -250,10 +262,11 @@ export namespace MCP {
250
262
  };
251
263
  }
252
264
 
253
- log.info('create() successfully created client', {
265
+ log.info(() => ({
266
+ message: 'create() successfully created client',
254
267
  key,
255
268
  toolCount: Object.keys(result).length,
256
- });
269
+ }));
257
270
  return {
258
271
  mcpClient,
259
272
  status,
@@ -274,7 +287,11 @@ export namespace MCP {
274
287
  const clientsSnapshot = await clients();
275
288
  for (const [clientName, client] of Object.entries(clientsSnapshot)) {
276
289
  const tools = await client.tools().catch((e) => {
277
- log.error('failed to get tools', { clientName, error: e.message });
290
+ log.error(() => ({
291
+ message: 'failed to get tools',
292
+ clientName,
293
+ error: e.message,
294
+ }));
278
295
  const failedStatus = {
279
296
  status: 'failed' as const,
280
297
  error: e instanceof Error ? e.message : String(e),
@@ -532,13 +532,13 @@ export namespace Patch {
532
532
 
533
533
  await fs.writeFile(hunk.path, hunk.contents, 'utf-8');
534
534
  added.push(hunk.path);
535
- log.info(`Added file: ${hunk.path}`);
535
+ log.info(() => ({ message: 'Added file', path: hunk.path }));
536
536
  break;
537
537
 
538
538
  case 'delete':
539
539
  await fs.unlink(hunk.path);
540
540
  deleted.push(hunk.path);
541
- log.info(`Deleted file: ${hunk.path}`);
541
+ log.info(() => ({ message: 'Deleted file', path: hunk.path }));
542
542
  break;
543
543
 
544
544
  case 'update':
@@ -557,12 +557,16 @@ export namespace Patch {
557
557
  await fs.writeFile(hunk.move_path, fileUpdate.content, 'utf-8');
558
558
  await fs.unlink(hunk.path);
559
559
  modified.push(hunk.move_path);
560
- log.info(`Moved file: ${hunk.path} -> ${hunk.move_path}`);
560
+ log.info(() => ({
561
+ message: 'Moved file',
562
+ from: hunk.path,
563
+ to: hunk.move_path,
564
+ }));
561
565
  } else {
562
566
  // Regular update
563
567
  await fs.writeFile(hunk.path, fileUpdate.content, 'utf-8');
564
568
  modified.push(hunk.path);
565
- log.info(`Updated file: ${hunk.path}`);
569
+ log.info(() => ({ message: 'Updated file', path: hunk.path }));
566
570
  }
567
571
  break;
568
572
  }
@@ -24,7 +24,7 @@ export namespace Project {
24
24
  export type Info = z.infer<typeof Info>;
25
25
 
26
26
  export async function fromDirectory(directory: string) {
27
- log.info('fromDirectory', { directory });
27
+ log.info(() => ({ message: 'fromDirectory', directory }));
28
28
  const matches = Filesystem.up({ targets: ['.git'], start: directory });
29
29
  const git = await matches.next().then((x) => x.value);
30
30
  await matches.return();
@@ -36,16 +36,20 @@ export namespace State {
36
36
  const entries = recordsByKey.get(key);
37
37
  if (!entries) return;
38
38
 
39
- log.info('waiting for state disposal to complete', { key });
39
+ log.info(() => ({
40
+ message: 'waiting for state disposal to complete',
41
+ key,
42
+ }));
40
43
 
41
44
  let disposalFinished = false;
42
45
 
43
46
  setTimeout(() => {
44
47
  if (!disposalFinished) {
45
- log.warn(
46
- 'state disposal is taking an unusually long time - if it does not complete in a reasonable time, please report this as a bug',
47
- { key }
48
- );
48
+ log.warn(() => ({
49
+ message:
50
+ 'state disposal is taking an unusually long time - if it does not complete in a reasonable time, please report this as a bug',
51
+ key,
52
+ }));
49
53
  }
50
54
  }, 10000).unref();
51
55
 
@@ -56,7 +60,11 @@ export namespace State {
56
60
  const task = Promise.resolve(entry.state)
57
61
  .then((state) => entry.dispose!(state))
58
62
  .catch((error) => {
59
- log.error('Error while disposing state:', { error, key });
63
+ log.error(() => ({
64
+ message: 'Error while disposing state',
65
+ error,
66
+ key,
67
+ }));
60
68
  });
61
69
 
62
70
  tasks.push(task);
@@ -64,6 +72,6 @@ export namespace State {
64
72
  await Promise.all(tasks);
65
73
  recordsByKey.delete(key);
66
74
  disposalFinished = true;
67
- log.info('state disposal completed', { key });
75
+ log.info(() => ({ message: 'state disposal completed', key }));
68
76
  }
69
77
  }
@@ -78,18 +78,17 @@ export namespace ModelsDev {
78
78
 
79
79
  export async function refresh() {
80
80
  const file = Bun.file(filepath);
81
- log.info('refreshing', {
82
- file,
83
- });
81
+ log.info(() => ({ message: 'refreshing', file }));
84
82
  const result = await fetch('https://models.dev/api.json', {
85
83
  headers: {
86
84
  'User-Agent': 'agent-cli/1.0.0',
87
85
  },
88
86
  signal: AbortSignal.timeout(10 * 1000),
89
87
  }).catch((e) => {
90
- log.error('Failed to fetch models.dev', {
88
+ log.error(() => ({
89
+ message: 'Failed to fetch models.dev',
91
90
  error: e,
92
- });
91
+ }));
93
92
  });
94
93
  if (result && result.ok) await Bun.write(file, await result.text());
95
94
  }
@@ -37,7 +37,7 @@ export namespace Provider {
37
37
  // Check if OAuth credentials are available via the auth plugin
38
38
  const auth = await Auth.get('anthropic');
39
39
  if (auth?.type === 'oauth') {
40
- log.info('using anthropic oauth credentials');
40
+ log.info(() => ({ message: 'using anthropic oauth credentials' }));
41
41
  const loaderFn = await AuthPlugins.getLoader('anthropic');
42
42
  if (loaderFn) {
43
43
  const result = await loaderFn(() => Auth.get('anthropic'), input);
@@ -330,7 +330,7 @@ export namespace Provider {
330
330
  google: async (input) => {
331
331
  const auth = await Auth.get('google');
332
332
  if (auth?.type === 'oauth') {
333
- log.info('using google oauth credentials');
333
+ log.info(() => ({ message: 'using google oauth credentials' }));
334
334
  const loaderFn = await AuthPlugins.getLoader('google');
335
335
  if (loaderFn) {
336
336
  const result = await loaderFn(() => Auth.get('google'), input);
@@ -355,7 +355,9 @@ export namespace Provider {
355
355
  'github-copilot': async (input) => {
356
356
  const auth = await Auth.get('github-copilot');
357
357
  if (auth?.type === 'oauth') {
358
- log.info('using github copilot oauth credentials');
358
+ log.info(() => ({
359
+ message: 'using github copilot oauth credentials',
360
+ }));
359
361
  const loaderFn = await AuthPlugins.getLoader('github-copilot');
360
362
  if (loaderFn) {
361
363
  const result = await loaderFn(
@@ -383,7 +385,9 @@ export namespace Provider {
383
385
  'github-copilot-enterprise': async (input) => {
384
386
  const auth = await Auth.get('github-copilot-enterprise');
385
387
  if (auth?.type === 'oauth') {
386
- log.info('using github copilot enterprise oauth credentials');
388
+ log.info(() => ({
389
+ message: 'using github copilot enterprise oauth credentials',
390
+ }));
387
391
  const loaderFn = await AuthPlugins.getLoader('github-copilot');
388
392
  if (loaderFn) {
389
393
  const result = await loaderFn(
@@ -435,7 +439,10 @@ export namespace Provider {
435
439
  return { autoload: false };
436
440
  }
437
441
 
438
- log.info('using claude oauth credentials', { source: tokenSource });
442
+ log.info(() => ({
443
+ message: 'using claude oauth credentials',
444
+ source: tokenSource,
445
+ }));
439
446
 
440
447
  // Create authenticated fetch with Bearer token and OAuth beta header
441
448
  const customFetch = ClaudeOAuth.createAuthenticatedFetch(oauthToken);
@@ -537,7 +544,7 @@ export namespace Provider {
537
544
  // Maps `${provider}/${key}` to the provider’s actual model ID for custom aliases.
538
545
  const realIdByKey = new Map<string, string>();
539
546
 
540
- log.info('init');
547
+ log.info(() => ({ message: 'init' }));
541
548
 
542
549
  function mergeProvider(
543
550
  id: string,
@@ -783,7 +790,7 @@ export namespace Provider {
783
790
  delete providers[providerID];
784
791
  continue;
785
792
  }
786
- log.info('found', { providerID });
793
+ log.info(() => ({ message: 'found', providerID }));
787
794
  }
788
795
 
789
796
  return {
@@ -818,19 +825,21 @@ export namespace Provider {
818
825
 
819
826
  let installedPath: string;
820
827
  if (!pkg.startsWith('file://')) {
821
- log.info('installing provider package', {
828
+ log.info(() => ({
829
+ message: 'installing provider package',
822
830
  providerID: provider.id,
823
831
  pkg,
824
832
  version: 'latest',
825
- });
833
+ }));
826
834
  installedPath = await BunProc.install(pkg, 'latest');
827
- log.info('provider package installed successfully', {
835
+ log.info(() => ({
836
+ message: 'provider package installed successfully',
828
837
  providerID: provider.id,
829
838
  pkg,
830
839
  installedPath,
831
- });
840
+ }));
832
841
  } else {
833
- log.info('loading local provider', { pkg });
842
+ log.info(() => ({ message: 'loading local provider', pkg }));
834
843
  installedPath = pkg;
835
844
  }
836
845
 
@@ -876,13 +885,14 @@ export namespace Provider {
876
885
  s.sdk.set(key, loaded);
877
886
  return loaded as SDK;
878
887
  })().catch((e) => {
879
- log.error('provider initialization failed', {
888
+ log.error(() => ({
889
+ message: 'provider initialization failed',
880
890
  providerID: provider.id,
881
891
  pkg: model.provider?.npm ?? provider.npm ?? provider.id,
882
892
  error: e instanceof Error ? e.message : String(e),
883
893
  stack: e instanceof Error ? e.stack : undefined,
884
894
  cause: e instanceof Error && e.cause ? String(e.cause) : undefined,
885
- });
895
+ }));
886
896
  throw new InitError({ providerID: provider.id }, { cause: e });
887
897
  });
888
898
  }
@@ -896,10 +906,7 @@ export namespace Provider {
896
906
  const s = await state();
897
907
  if (s.models.has(key)) return s.models.get(key)!;
898
908
 
899
- log.info('getModel', {
900
- providerID,
901
- modelID,
902
- });
909
+ log.info(() => ({ message: 'getModel', providerID, modelID }));
903
910
 
904
911
  const provider = s.providers[providerID];
905
912
  if (!provider) throw new ModelNotFoundError({ providerID, modelID });
@@ -929,7 +936,7 @@ export namespace Provider {
929
936
  ? await provider.getModel(sdk, realID, provider.options)
930
937
  : sdk.languageModel(realID);
931
938
  }
932
- log.info('found', { providerID, modelID });
939
+ log.info(() => ({ message: 'found', providerID, modelID }));
933
940
  s.models.set(key, {
934
941
  providerID,
935
942
  modelID,
@@ -1032,10 +1039,11 @@ export namespace Provider {
1032
1039
  if (opencodeProvider) {
1033
1040
  const [model] = sort(Object.values(opencodeProvider.info.models));
1034
1041
  if (model) {
1035
- log.info('using opencode provider as default', {
1042
+ log.info(() => ({
1043
+ message: 'using opencode provider as default',
1036
1044
  provider: opencodeProvider.info.id,
1037
1045
  model: model.id,
1038
- });
1046
+ }));
1039
1047
  return {
1040
1048
  providerID: opencodeProvider.info.id,
1041
1049
  modelID: model.id,
@@ -55,9 +55,7 @@ export namespace Server {
55
55
  export const App = lazy(() =>
56
56
  app
57
57
  .onError((err, c) => {
58
- log.error('failed', {
59
- error: err,
60
- });
58
+ log.error(() => ({ message: 'failed', error: err }));
61
59
  if (err instanceof NamedError) {
62
60
  let status: ContentfulStatusCode;
63
61
  if (err instanceof Storage.NotFoundError) status = 404;
@@ -71,10 +69,11 @@ export namespace Server {
71
69
  });
72
70
  })
73
71
  .use(async (c, next) => {
74
- log.info('request', {
72
+ log.info(() => ({
73
+ message: 'request',
75
74
  method: c.req.method,
76
75
  path: c.req.path,
77
- });
76
+ }));
78
77
  const timer = log.time('request', {
79
78
  method: c.req.method,
80
79
  path: c.req.path,
@@ -96,8 +96,22 @@ export class Agent {
96
96
  const errorTime = Date.now();
97
97
  const callID = `call_${Math.floor(Math.random() * 100000000)}`;
98
98
 
99
- // Log full error to stderr for debugging
100
- console.error('Tool execution error:', error);
99
+ // Log full error to stderr for debugging in JSON format
100
+ console.error(
101
+ JSON.stringify({
102
+ log: {
103
+ level: 'error',
104
+ timestamp: new Date().toISOString(),
105
+ message: 'Tool execution error',
106
+ tool: tool.name,
107
+ error: {
108
+ name: error.name,
109
+ message: error.message,
110
+ stack: error.stack,
111
+ },
112
+ },
113
+ })
114
+ );
101
115
 
102
116
  // Emit tool_use event with error
103
117
  this.emitEvent('tool_use', {
@@ -52,7 +52,7 @@ export namespace SessionCompaction {
52
52
  // tool calls that are no longer relevant.
53
53
  export async function prune(input: { sessionID: string }) {
54
54
  if (Flag.OPENCODE_DISABLE_PRUNE) return;
55
- log.info('pruning');
55
+ log.info(() => ({ message: 'pruning' }));
56
56
  const msgs = await Session.messages({ sessionID: input.sessionID });
57
57
  let total = 0;
58
58
  let pruned = 0;
@@ -78,7 +78,7 @@ export namespace SessionCompaction {
78
78
  }
79
79
  }
80
80
  }
81
- log.info('found', { pruned, total });
81
+ log.info(() => ({ message: 'found', pruned, total }));
82
82
  if (pruned > PRUNE_MINIMUM) {
83
83
  for (const part of toPrune) {
84
84
  if (part.state.status === 'completed') {
@@ -86,7 +86,7 @@ export namespace SessionCompaction {
86
86
  await Session.updatePart(part);
87
87
  }
88
88
  }
89
- log.info('pruned', { count: toPrune.length });
89
+ log.info(() => ({ message: 'pruned', count: toPrune.length }));
90
90
  }
91
91
  }
92
92
 
@@ -139,9 +139,7 @@ export namespace SessionCompaction {
139
139
  const result = await processor.process(() =>
140
140
  streamText({
141
141
  onError(error) {
142
- log.error('stream error', {
143
- error,
144
- });
142
+ log.error(() => ({ message: 'stream error', error }));
145
143
  },
146
144
  // set to 0, we handle loop
147
145
  maxRetries: 0,