@link-assistant/agent 0.8.18 → 0.8.20

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@link-assistant/agent",
3
- "version": "0.8.18",
3
+ "version": "0.8.20",
4
4
  "description": "A minimal, public domain AI CLI agent compatible with OpenCode's JSON interface. Bun-only runtime.",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -71,7 +71,7 @@
71
71
  "@standard-community/standard-json": "^0.3.5",
72
72
  "@standard-community/standard-openapi": "^0.2.9",
73
73
  "@zip.js/zip.js": "^2.8.10",
74
- "ai": "6.0.0-beta.99",
74
+ "ai": "^6.0.1",
75
75
  "chokidar": "^4.0.3",
76
76
  "clipboardy": "^5.0.0",
77
77
  "command-stream": "^0.7.1",
@@ -0,0 +1,41 @@
1
+ import { platform } from 'os';
2
+ import { dlopen, FFIType, ptr } from 'bun:ffi';
3
+
4
+ /**
5
+ * Set the process name visible in system monitoring tools (top, ps, htop, etc.).
6
+ *
7
+ * Bun does not implement the process.title setter (unlike Node.js), so we use
8
+ * platform-specific system calls via Bun's FFI:
9
+ * - Linux: prctl(PR_SET_NAME, name) sets /proc/<pid>/comm
10
+ * - macOS: relies on the binary/symlink name (set by `bun install -g`)
11
+ * - Windows: no-op (Task Manager shows the executable name)
12
+ */
13
+ export function setProcessName(name: string): void {
14
+ // Set in-process values for any JS code that checks them
15
+ process.title = name;
16
+ process.argv0 = name;
17
+
18
+ const os = platform();
19
+
20
+ if (os === 'linux') {
21
+ try {
22
+ const PR_SET_NAME = 15;
23
+ const libc = dlopen('libc.so.6', {
24
+ prctl: {
25
+ args: [FFIType.i32, FFIType.ptr],
26
+ returns: FFIType.i32,
27
+ },
28
+ });
29
+ // PR_SET_NAME accepts up to 16 bytes including the null terminator
30
+ const buf = Buffer.from(name.slice(0, 15) + '\0');
31
+ libc.symbols.prctl(PR_SET_NAME, ptr(buf));
32
+ libc.close();
33
+ } catch (_e) {
34
+ // Silently ignore - process name is cosmetic
35
+ }
36
+ }
37
+ // macOS: no userspace API changes the process comm shown in ps/top.
38
+ // When installed via `bun install -g`, the symlink is named 'agent',
39
+ // so macOS will already show 'agent' in ps/top.
40
+ // Windows: Task Manager always shows the executable name.
41
+ }
package/src/index.js CHANGED
@@ -1,9 +1,8 @@
1
1
  #!/usr/bin/env bun
2
2
 
3
- // Set process title to 'agent' so it appears correctly in process monitoring tools like top/ps
4
- // Both process.title and process.argv0 need to be set for maximum compatibility
5
- process.title = 'agent';
6
- process.argv0 = 'agent';
3
+ import { setProcessName } from './cli/process-name.ts';
4
+
5
+ setProcessName('agent');
7
6
 
8
7
  import { Server } from './server/server.ts';
9
8
  import { Instance } from './project/instance.ts';
@@ -580,6 +580,27 @@ export namespace Session {
580
580
  metadata: z.custom<ProviderMetadata>().optional(),
581
581
  }),
582
582
  (input) => {
583
+ // Handle undefined or null usage gracefully
584
+ // Some providers (e.g., OpenCode with Kimi K2.5) may return incomplete usage data
585
+ // See: https://github.com/link-assistant/agent/issues/152
586
+ if (!input.usage) {
587
+ log.warn(() => ({
588
+ message: 'getUsage received undefined usage, returning zero values',
589
+ providerMetadata: input.metadata
590
+ ? JSON.stringify(input.metadata)
591
+ : 'none',
592
+ }));
593
+ return {
594
+ cost: 0,
595
+ tokens: {
596
+ input: 0,
597
+ output: 0,
598
+ reasoning: 0,
599
+ cache: { read: 0, write: 0 },
600
+ },
601
+ };
602
+ }
603
+
583
604
  // Log raw usage data in verbose mode for debugging
584
605
  if (Flag.OPENCODE_VERBOSE) {
585
606
  log.debug(() => ({
@@ -219,9 +219,17 @@ export namespace SessionProcessor {
219
219
  break;
220
220
 
221
221
  case 'finish-step':
222
+ // Safely handle missing or undefined usage data
223
+ // Some providers (e.g., OpenCode with Kimi K2.5) may return incomplete usage
224
+ // See: https://github.com/link-assistant/agent/issues/152
225
+ const safeUsage = value.usage ?? {
226
+ inputTokens: 0,
227
+ outputTokens: 0,
228
+ totalTokens: 0,
229
+ };
222
230
  const usage = Session.getUsage({
223
231
  model: input.model,
224
- usage: value.usage,
232
+ usage: safeUsage,
225
233
  metadata: value.providerMetadata,
226
234
  });
227
235
  // Use toFinishReason to safely convert object/string finishReason to string
@@ -322,6 +330,34 @@ export namespace SessionProcessor {
322
330
  }
323
331
  } catch (e) {
324
332
  log.error(() => ({ message: 'process', error: e }));
333
+
334
+ // Check for AI SDK usage-related TypeError (input_tokens undefined)
335
+ // This happens when providers return incomplete usage data
336
+ // See: https://github.com/link-assistant/agent/issues/152
337
+ if (
338
+ e instanceof TypeError &&
339
+ (e.message.includes('input_tokens') ||
340
+ e.message.includes('output_tokens') ||
341
+ e.message.includes("reading 'input_tokens'") ||
342
+ e.message.includes("reading 'output_tokens'"))
343
+ ) {
344
+ log.warn(() => ({
345
+ message:
346
+ 'Provider returned invalid usage data, continuing with zero usage',
347
+ errorMessage: e.message,
348
+ providerID: input.providerID,
349
+ }));
350
+ // Set default token values to prevent crash
351
+ input.assistantMessage.tokens = {
352
+ input: 0,
353
+ output: 0,
354
+ reasoning: 0,
355
+ cache: { read: 0, write: 0 },
356
+ };
357
+ // Continue processing instead of failing
358
+ continue;
359
+ }
360
+
325
361
  const error = MessageV2.fromError(e, {
326
362
  providerID: input.providerID,
327
363
  });