@link-assistant/agent 0.8.4 → 0.8.6
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 +1 -1
- package/src/session/index.ts +104 -0
- package/src/session/processor.ts +15 -4
package/package.json
CHANGED
package/src/session/index.ts
CHANGED
|
@@ -413,6 +413,26 @@ export namespace Session {
|
|
|
413
413
|
throw new Error(`Cannot convert ${value} to number`);
|
|
414
414
|
}
|
|
415
415
|
|
|
416
|
+
// Handle objects with a 'total' field (e.g., { total: 8707, noCache: 6339, cacheRead: 2368 })
|
|
417
|
+
// Some AI providers return token counts as objects instead of plain numbers
|
|
418
|
+
// See: https://github.com/link-assistant/agent/issues/125
|
|
419
|
+
if (
|
|
420
|
+
typeof value === 'object' &&
|
|
421
|
+
value !== null &&
|
|
422
|
+
'total' in value &&
|
|
423
|
+
typeof (value as { total: unknown }).total === 'number'
|
|
424
|
+
) {
|
|
425
|
+
const result = (value as { total: number }).total;
|
|
426
|
+
if (Flag.OPENCODE_VERBOSE) {
|
|
427
|
+
log.debug(() => ({
|
|
428
|
+
message: 'toNumber extracted total from object',
|
|
429
|
+
context,
|
|
430
|
+
result,
|
|
431
|
+
}));
|
|
432
|
+
}
|
|
433
|
+
return result;
|
|
434
|
+
}
|
|
435
|
+
|
|
416
436
|
// Try to convert to number
|
|
417
437
|
const result = Number(value);
|
|
418
438
|
|
|
@@ -448,6 +468,90 @@ export namespace Session {
|
|
|
448
468
|
}
|
|
449
469
|
};
|
|
450
470
|
|
|
471
|
+
/**
|
|
472
|
+
* Safely converts a finishReason value to a string.
|
|
473
|
+
* Some AI providers return finishReason as an object instead of a string.
|
|
474
|
+
*
|
|
475
|
+
* For example, OpenCode provider on certain Bun versions may return:
|
|
476
|
+
* - { type: "stop" } instead of "stop"
|
|
477
|
+
* - { finishReason: "tool-calls" } instead of "tool-calls"
|
|
478
|
+
*
|
|
479
|
+
* This function handles these cases gracefully.
|
|
480
|
+
*
|
|
481
|
+
* @param value - The finishReason value (string, object, or undefined)
|
|
482
|
+
* @returns A string representing the finish reason, or 'unknown' if conversion fails
|
|
483
|
+
* @see https://github.com/link-assistant/agent/issues/125
|
|
484
|
+
*/
|
|
485
|
+
export const toFinishReason = (value: unknown): string => {
|
|
486
|
+
// Log input data in verbose mode to help identify issues
|
|
487
|
+
if (Flag.OPENCODE_VERBOSE) {
|
|
488
|
+
log.debug(() => ({
|
|
489
|
+
message: 'toFinishReason input',
|
|
490
|
+
valueType: typeof value,
|
|
491
|
+
value:
|
|
492
|
+
typeof value === 'object' ? JSON.stringify(value) : String(value),
|
|
493
|
+
}));
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// If it's already a string, return it
|
|
497
|
+
if (typeof value === 'string') {
|
|
498
|
+
return value;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// If it's undefined or null, return 'unknown'
|
|
502
|
+
if (value === undefined || value === null) {
|
|
503
|
+
return 'unknown';
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// If it's an object, try to extract a meaningful string
|
|
507
|
+
if (typeof value === 'object') {
|
|
508
|
+
const obj = value as Record<string, unknown>;
|
|
509
|
+
|
|
510
|
+
// Try common field names that might contain the reason
|
|
511
|
+
if (typeof obj.type === 'string') {
|
|
512
|
+
if (Flag.OPENCODE_VERBOSE) {
|
|
513
|
+
log.debug(() => ({
|
|
514
|
+
message: 'toFinishReason extracted type from object',
|
|
515
|
+
result: obj.type,
|
|
516
|
+
}));
|
|
517
|
+
}
|
|
518
|
+
return obj.type;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
if (typeof obj.finishReason === 'string') {
|
|
522
|
+
if (Flag.OPENCODE_VERBOSE) {
|
|
523
|
+
log.debug(() => ({
|
|
524
|
+
message: 'toFinishReason extracted finishReason from object',
|
|
525
|
+
result: obj.finishReason,
|
|
526
|
+
}));
|
|
527
|
+
}
|
|
528
|
+
return obj.finishReason;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
if (typeof obj.reason === 'string') {
|
|
532
|
+
if (Flag.OPENCODE_VERBOSE) {
|
|
533
|
+
log.debug(() => ({
|
|
534
|
+
message: 'toFinishReason extracted reason from object',
|
|
535
|
+
result: obj.reason,
|
|
536
|
+
}));
|
|
537
|
+
}
|
|
538
|
+
return obj.reason;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// If we can't extract a specific field, return JSON representation
|
|
542
|
+
if (Flag.OPENCODE_VERBOSE) {
|
|
543
|
+
log.debug(() => ({
|
|
544
|
+
message: 'toFinishReason could not extract string, using JSON',
|
|
545
|
+
result: JSON.stringify(value),
|
|
546
|
+
}));
|
|
547
|
+
}
|
|
548
|
+
return JSON.stringify(value);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// For any other type, convert to string
|
|
552
|
+
return String(value);
|
|
553
|
+
};
|
|
554
|
+
|
|
451
555
|
export const getUsage = fn(
|
|
452
556
|
z.object({
|
|
453
557
|
model: z.custom<ModelsDev.Model>(),
|
package/src/session/processor.ts
CHANGED
|
@@ -224,12 +224,17 @@ export namespace SessionProcessor {
|
|
|
224
224
|
usage: value.usage,
|
|
225
225
|
metadata: value.providerMetadata,
|
|
226
226
|
});
|
|
227
|
-
|
|
227
|
+
// Use toFinishReason to safely convert object/string finishReason to string
|
|
228
|
+
// See: https://github.com/link-assistant/agent/issues/125
|
|
229
|
+
const finishReason = Session.toFinishReason(
|
|
230
|
+
value.finishReason
|
|
231
|
+
);
|
|
232
|
+
input.assistantMessage.finish = finishReason;
|
|
228
233
|
input.assistantMessage.cost += usage.cost;
|
|
229
234
|
input.assistantMessage.tokens = usage.tokens;
|
|
230
235
|
await Session.updatePart({
|
|
231
236
|
id: Identifier.ascending('part'),
|
|
232
|
-
reason:
|
|
237
|
+
reason: finishReason,
|
|
233
238
|
snapshot: await Snapshot.track(),
|
|
234
239
|
messageID: input.assistantMessage.id,
|
|
235
240
|
sessionID: input.assistantMessage.sessionID,
|
|
@@ -274,13 +279,19 @@ export namespace SessionProcessor {
|
|
|
274
279
|
|
|
275
280
|
case 'text-delta':
|
|
276
281
|
if (currentText) {
|
|
277
|
-
|
|
282
|
+
// Handle case where value.text might be an object instead of string
|
|
283
|
+
// See: https://github.com/link-assistant/agent/issues/125
|
|
284
|
+
const textDelta =
|
|
285
|
+
typeof value.text === 'string'
|
|
286
|
+
? value.text
|
|
287
|
+
: String(value.text);
|
|
288
|
+
currentText.text += textDelta;
|
|
278
289
|
if (value.providerMetadata)
|
|
279
290
|
currentText.metadata = value.providerMetadata;
|
|
280
291
|
if (currentText.text)
|
|
281
292
|
await Session.updatePart({
|
|
282
293
|
part: currentText,
|
|
283
|
-
delta:
|
|
294
|
+
delta: textDelta,
|
|
284
295
|
});
|
|
285
296
|
}
|
|
286
297
|
break;
|