@link-assistant/agent 0.22.7 → 0.22.9
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/message-v2.ts +46 -0
- package/src/session/processor.ts +56 -25
package/package.json
CHANGED
|
@@ -849,6 +849,48 @@ export namespace MessageV2 {
|
|
|
849
849
|
return result;
|
|
850
850
|
}
|
|
851
851
|
|
|
852
|
+
function normalizeProviderErrorObject(e: unknown) {
|
|
853
|
+
if (!e || typeof e !== 'object') return;
|
|
854
|
+
|
|
855
|
+
const root = e as Record<string, unknown>;
|
|
856
|
+
const candidate =
|
|
857
|
+
root['error'] && typeof root['error'] === 'object'
|
|
858
|
+
? (root['error'] as Record<string, unknown>)
|
|
859
|
+
: root;
|
|
860
|
+
const rawCode =
|
|
861
|
+
candidate['code'] ?? candidate['statusCode'] ?? candidate['status'];
|
|
862
|
+
const statusCode =
|
|
863
|
+
typeof rawCode === 'number'
|
|
864
|
+
? rawCode
|
|
865
|
+
: typeof rawCode === 'string' && /^\d+$/.test(rawCode)
|
|
866
|
+
? Number.parseInt(rawCode, 10)
|
|
867
|
+
: undefined;
|
|
868
|
+
|
|
869
|
+
if (statusCode === undefined) return;
|
|
870
|
+
|
|
871
|
+
let responseBody: string | undefined;
|
|
872
|
+
try {
|
|
873
|
+
responseBody = JSON.stringify(e);
|
|
874
|
+
} catch {
|
|
875
|
+
responseBody = undefined;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
const message =
|
|
879
|
+
typeof candidate['message'] === 'string'
|
|
880
|
+
? candidate['message']
|
|
881
|
+
: typeof root['message'] === 'string'
|
|
882
|
+
? root['message']
|
|
883
|
+
: `Provider returned HTTP ${statusCode}`;
|
|
884
|
+
|
|
885
|
+
return {
|
|
886
|
+
message,
|
|
887
|
+
statusCode,
|
|
888
|
+
isRetryable:
|
|
889
|
+
statusCode === 429 || (statusCode >= 500 && statusCode < 600),
|
|
890
|
+
responseBody,
|
|
891
|
+
};
|
|
892
|
+
}
|
|
893
|
+
|
|
852
894
|
export function fromError(e: unknown, ctx: { providerID: string }) {
|
|
853
895
|
switch (true) {
|
|
854
896
|
case e instanceof DOMException && e.name === 'AbortError':
|
|
@@ -884,6 +926,10 @@ export namespace MessageV2 {
|
|
|
884
926
|
},
|
|
885
927
|
{ cause: e }
|
|
886
928
|
).toObject();
|
|
929
|
+
case normalizeProviderErrorObject(e) !== undefined: {
|
|
930
|
+
const providerError = normalizeProviderErrorObject(e)!;
|
|
931
|
+
return new MessageV2.APIError(providerError, { cause: e }).toObject();
|
|
932
|
+
}
|
|
887
933
|
case e instanceof Error: {
|
|
888
934
|
const message = e.message || e.toString();
|
|
889
935
|
// Detect Bun socket connection errors (known Bun issue with 10s idle timeout)
|
package/src/session/processor.ts
CHANGED
|
@@ -52,6 +52,40 @@ export namespace SessionProcessor {
|
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Detect AI SDK/provider usage-shape crashes.
|
|
57
|
+
*
|
|
58
|
+
* Some provider error streams end before a valid finish-step usage object is
|
|
59
|
+
* available. Depending on the provider adapter, that can surface as a
|
|
60
|
+
* TypeError while reading token fields instead of the original provider
|
|
61
|
+
* error. Treat it as retryable provider failure rather than a generic crash.
|
|
62
|
+
*
|
|
63
|
+
* See: https://github.com/link-assistant/agent/issues/264
|
|
64
|
+
*/
|
|
65
|
+
export function isUsageDataTypeError(error: unknown): boolean {
|
|
66
|
+
const message =
|
|
67
|
+
error instanceof Error
|
|
68
|
+
? `${error.name}: ${error.message}`
|
|
69
|
+
: typeof error === 'string'
|
|
70
|
+
? error
|
|
71
|
+
: '';
|
|
72
|
+
|
|
73
|
+
if (!(error instanceof TypeError) && !message.startsWith('TypeError:')) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return [
|
|
78
|
+
'input_tokens',
|
|
79
|
+
'output_tokens',
|
|
80
|
+
'usage.inputTokens',
|
|
81
|
+
'usage.outputTokens',
|
|
82
|
+
'inputTokens.total',
|
|
83
|
+
'outputTokens.total',
|
|
84
|
+
"reading 'inputTokens'",
|
|
85
|
+
"reading 'outputTokens'",
|
|
86
|
+
].some((pattern) => message.includes(pattern));
|
|
87
|
+
}
|
|
88
|
+
|
|
55
89
|
export type Info = Awaited<ReturnType<typeof create>>;
|
|
56
90
|
export type Result = Awaited<ReturnType<Info['process']>>;
|
|
57
91
|
|
|
@@ -542,37 +576,34 @@ export namespace SessionProcessor {
|
|
|
542
576
|
} catch (e) {
|
|
543
577
|
log.error(() => ({ message: 'process', error: e }));
|
|
544
578
|
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
) {
|
|
579
|
+
let error = MessageV2.fromError(e, {
|
|
580
|
+
providerID: input.providerID,
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
// Check for AI SDK usage-related TypeErrors. These usually come
|
|
584
|
+
// from provider adapters after malformed/incomplete usage data, so
|
|
585
|
+
// retry them with normal API-error backoff instead of surfacing a
|
|
586
|
+
// misleading UnknownError.
|
|
587
|
+
if (isUsageDataTypeError(e)) {
|
|
555
588
|
log.warn(() => ({
|
|
556
589
|
message:
|
|
557
|
-
'
|
|
558
|
-
errorMessage: e.message,
|
|
590
|
+
'provider returned invalid usage data; retrying as provider API error',
|
|
591
|
+
errorMessage: e instanceof Error ? e.message : String(e),
|
|
559
592
|
providerID: input.providerID,
|
|
593
|
+
issue: 'https://github.com/link-assistant/agent/issues/264',
|
|
560
594
|
}));
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
595
|
+
error = new MessageV2.APIError(
|
|
596
|
+
{
|
|
597
|
+
message:
|
|
598
|
+
e instanceof Error
|
|
599
|
+
? e.message
|
|
600
|
+
: 'Provider returned invalid usage data',
|
|
601
|
+
isRetryable: true,
|
|
602
|
+
},
|
|
603
|
+
{ cause: e }
|
|
604
|
+
).toObject();
|
|
570
605
|
}
|
|
571
606
|
|
|
572
|
-
const error = MessageV2.fromError(e, {
|
|
573
|
-
providerID: input.providerID,
|
|
574
|
-
});
|
|
575
|
-
|
|
576
607
|
// Check if error is retryable (APIError, SocketConnectionError, TimeoutError)
|
|
577
608
|
const isRetryableAPIError =
|
|
578
609
|
error?.name === 'APIError' && error.data.isRetryable;
|