@link-assistant/agent 0.26.3 → 0.26.5

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.26.3",
3
+ "version": "0.26.5",
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",
@@ -57,8 +57,17 @@ export namespace ClaudeOAuth {
57
57
  refreshToken: z.string(),
58
58
  expiresAt: z.number(),
59
59
  scopes: z.array(z.string()).optional(),
60
- subscriptionType: z.string().optional(),
61
- rateLimitTier: z.string().optional(),
60
+ // Claude Code writes null when subscription metadata is unavailable.
61
+ // Normalize that JSON representation to the same value as an omitted
62
+ // optional field while keeping every other non-string value invalid.
63
+ subscriptionType: z
64
+ .string()
65
+ .nullish()
66
+ .transform((value) => value ?? undefined),
67
+ rateLimitTier: z
68
+ .string()
69
+ .nullish()
70
+ .transform((value) => value ?? undefined),
62
71
  })
63
72
  .optional(),
64
73
  });
@@ -96,6 +96,7 @@ export function outputBusEvent({
96
96
  type: 'error',
97
97
  timestamp: Date.now(),
98
98
  sessionID,
99
+ part,
99
100
  message: errorText(stateError, 'Tool execution failed'),
100
101
  error: stateError || 'Tool execution failed',
101
102
  });
@@ -28,6 +28,7 @@ export interface OpenCodeEvent {
28
28
  timestamp: number;
29
29
  sessionID: string;
30
30
  part?: Record<string, unknown>;
31
+ message?: string;
31
32
  error?: string | Record<string, unknown>;
32
33
  }
33
34
 
@@ -116,12 +117,44 @@ export function convertOpenCodeToClaude(
116
117
  case 'tool_use':
117
118
  if (event.part && event.part.state) {
118
119
  const state = event.part.state as Record<string, unknown>;
120
+ const input = state.input || {};
121
+ const status = state.status as string | undefined;
122
+
123
+ // A newly-created pending part has not received its parsed arguments
124
+ // yet. Populated pending parts are retained for compatibility with
125
+ // producers that publish the complete call in their first snapshot.
126
+ if (
127
+ status === 'pending' &&
128
+ typeof input === 'object' &&
129
+ input !== null &&
130
+ Object.keys(input).length === 0
131
+ ) {
132
+ return null;
133
+ }
134
+
135
+ if (status === 'completed') {
136
+ return {
137
+ type: 'tool_result',
138
+ timestamp,
139
+ session_id,
140
+ tool_use_id: event.part.id as string,
141
+ output: state.output as string,
142
+ status: 'success',
143
+ };
144
+ }
145
+
146
+ // outputBusEvent follows an errored tool snapshot with an error event
147
+ // carrying the same part, which is converted to the terminal result.
148
+ if (status === 'error') {
149
+ return null;
150
+ }
151
+
119
152
  return {
120
153
  type: 'tool_use',
121
154
  timestamp,
122
155
  session_id,
123
156
  name: (event.part.tool as string) || 'unknown',
124
- input: state.input || {},
157
+ input,
125
158
  tool_use_id: event.part.id as string,
126
159
  };
127
160
  }
@@ -144,6 +177,21 @@ export function convertOpenCodeToClaude(
144
177
  };
145
178
 
146
179
  case 'error':
180
+ if (event.part?.type === 'tool' && event.part.state) {
181
+ const state = event.part.state as Record<string, unknown>;
182
+ return {
183
+ type: 'tool_result',
184
+ timestamp,
185
+ session_id,
186
+ tool_use_id: event.part.id as string,
187
+ output:
188
+ event.message ||
189
+ (typeof state.error === 'string'
190
+ ? state.error
191
+ : 'Tool execution failed'),
192
+ status: 'error',
193
+ };
194
+ }
147
195
  return {
148
196
  type: 'result',
149
197
  timestamp,
@@ -173,6 +221,8 @@ export function createEventHandler(
173
221
  ) {
174
222
  const startTime = Date.now();
175
223
  const { model } = options;
224
+ const emittedToolUseIds = new Set<string>();
225
+ const emittedToolResultIds = new Set<string>();
176
226
 
177
227
  return {
178
228
  /**
@@ -184,6 +234,14 @@ export function createEventHandler(
184
234
  if (standard === 'claude') {
185
235
  const claudeEvent = convertOpenCodeToClaude(event, startTime, model);
186
236
  if (claudeEvent) {
237
+ if (claudeEvent.type === 'tool_use' && claudeEvent.tool_use_id) {
238
+ if (emittedToolUseIds.has(claudeEvent.tool_use_id)) return;
239
+ emittedToolUseIds.add(claudeEvent.tool_use_id);
240
+ }
241
+ if (claudeEvent.type === 'tool_result' && claudeEvent.tool_use_id) {
242
+ if (emittedToolResultIds.has(claudeEvent.tool_use_id)) return;
243
+ emittedToolResultIds.add(claudeEvent.tool_use_id);
244
+ }
187
245
  outputStream.write(serializeOutput(claudeEvent, standard));
188
246
  }
189
247
  } else {