@modelprofile.com/flexharness-models 9.3.0 → 9.3.1
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.
|
@@ -2,7 +2,8 @@ import { type IModelLimitInfo } from './smartai.limits.js';
|
|
|
2
2
|
/**
|
|
3
3
|
* `limit`: a usage or rate limit refused the call; `limit` describes it.
|
|
4
4
|
* `http`: the provider answered with an HTTP error status.
|
|
5
|
-
* `stream`: the provider
|
|
5
|
+
* `stream`: the provider answered with a response stream and reported an error inside it, before or
|
|
6
|
+
* after output began.
|
|
6
7
|
* `response`: the provider answered, but its response could not be read or processed.
|
|
7
8
|
* `network`: no response arrived because the connection failed.
|
|
8
9
|
* `aborted`: an abort signal ended the call, a timeout signal included.
|
|
@@ -12,9 +13,15 @@ export type TModelFailureKind = 'limit' | 'http' | 'stream' | 'response' | 'netw
|
|
|
12
13
|
/** Why a model call failed, without provider response text, headers or credentials. */
|
|
13
14
|
export interface IModelFailureDescription {
|
|
14
15
|
kind: TModelFailureKind;
|
|
15
|
-
/**
|
|
16
|
+
/**
|
|
17
|
+
* The HTTP status the provider answered with. A `stream` failure has none: its answer was a
|
|
18
|
+
* success, and the status the AI SDK derives from the error's code is no HTTP answer.
|
|
19
|
+
*/
|
|
16
20
|
status?: number;
|
|
17
|
-
/**
|
|
21
|
+
/**
|
|
22
|
+
* The provider's machine-readable name of a stream error: its code, for example `server_error`,
|
|
23
|
+
* or, for an error without one, its type, for example Anthropic's `overloaded_error`.
|
|
24
|
+
*/
|
|
18
25
|
providerCode?: string;
|
|
19
26
|
/** The limit of a `limit` failure. */
|
|
20
27
|
limit?: Readonly<IModelLimitInfo>;
|
|
@@ -22,6 +29,6 @@ export interface IModelFailureDescription {
|
|
|
22
29
|
/**
|
|
23
30
|
* Names why a model call failed, from the error a call threw or a stream reported. It follows
|
|
24
31
|
* `cause` and the last attempt of a retried call, so a wrapped failure is told by its own kind.
|
|
25
|
-
* The description holds only the kind,
|
|
32
|
+
* The description holds only the kind, an HTTP status, a provider error code and a limit.
|
|
26
33
|
*/
|
|
27
34
|
export declare function describeModelFailure(error: unknown): IModelFailureDescription;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as plugins from './plugins.js';
|
|
2
|
-
import { isModelLimitError } from './smartai.limits.js';
|
|
2
|
+
import { isModelLimitError, readJsonRecord } from './smartai.limits.js';
|
|
3
3
|
/** How many causes are followed before a failure counts as unknown. */
|
|
4
4
|
const MAX_CAUSE_DEPTH = 8;
|
|
5
5
|
/** The longest provider code that is kept. */
|
|
@@ -36,21 +36,68 @@ function readProviderCode(value) {
|
|
|
36
36
|
const code = typeof value === 'number' && Number.isSafeInteger(value) ? String(value) : value;
|
|
37
37
|
return typeof code === 'string' && isProviderCode(code) ? code : undefined;
|
|
38
38
|
}
|
|
39
|
+
/** The media type of a response stream. */
|
|
40
|
+
const EVENT_STREAM_MEDIA_TYPE = 'text/event-stream';
|
|
41
|
+
/**
|
|
42
|
+
* Whether the call's answer was a response stream. The AI SDK reads a stream only from a successful
|
|
43
|
+
* answer; when the stream reports an error before output began, the provider throws an
|
|
44
|
+
* APICallError that carries the stream's headers and a status derived from the error's code.
|
|
45
|
+
*/
|
|
46
|
+
function answeredWithStream(error) {
|
|
47
|
+
const contentType = Object.entries(error.responseHeaders ?? {})
|
|
48
|
+
.find(([name]) => name.toLowerCase() === 'content-type')?.[1];
|
|
49
|
+
return contentType?.trim().toLowerCase().startsWith(EVENT_STREAM_MEDIA_TYPE) === true;
|
|
50
|
+
}
|
|
51
|
+
function parseJson(text) {
|
|
52
|
+
if (text === undefined)
|
|
53
|
+
return undefined;
|
|
54
|
+
try {
|
|
55
|
+
return JSON.parse(text);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/** The `type` Anthropic and OpenAI Responses give an error event; it marks the event and names no failure. */
|
|
62
|
+
const ERROR_EVENT_TYPE = 'error';
|
|
63
|
+
/**
|
|
64
|
+
* The code in a provider's stream error frame. The error record is the frame's `response.error`
|
|
65
|
+
* (an OpenAI `response.failed` event), its `error` (an Anthropic or OpenAI error event) or the frame
|
|
66
|
+
* itself (a bare Anthropic error, an OpenAI Chat error or a flat OpenAI Responses error event). A
|
|
67
|
+
* record names its failure by `code` and, when it has none, by `type`, which is the only name
|
|
68
|
+
* Anthropic gives an error (`overloaded_error`); the `type` of a flat error event is the event's.
|
|
69
|
+
*/
|
|
70
|
+
function readFrameCode(frame) {
|
|
71
|
+
const event = readJsonRecord(frame);
|
|
72
|
+
if (event === undefined)
|
|
73
|
+
return undefined;
|
|
74
|
+
const record = readJsonRecord(readJsonRecord(event.response)?.error) ?? readJsonRecord(event.error) ?? event;
|
|
75
|
+
const code = record.code ?? event.code;
|
|
76
|
+
if (code !== undefined && code !== null)
|
|
77
|
+
return code;
|
|
78
|
+
return record.type === ERROR_EVENT_TYPE ? undefined : record.type;
|
|
79
|
+
}
|
|
80
|
+
function describeStreamFailure(code) {
|
|
81
|
+
const providerCode = readProviderCode(code);
|
|
82
|
+
return providerCode === undefined ? { kind: 'stream' } : { kind: 'stream', providerCode };
|
|
83
|
+
}
|
|
39
84
|
function describeKnownFailure(error) {
|
|
40
85
|
if (isModelLimitError(error))
|
|
41
86
|
return { kind: 'limit', limit: error.limit };
|
|
42
87
|
if (plugins.isAbortError(error))
|
|
43
88
|
return { kind: 'aborted' };
|
|
44
89
|
if (plugins.StreamProviderError.isInstance(error) || plugins.isProviderStreamError(error)) {
|
|
45
|
-
|
|
46
|
-
const providerCode = readProviderCode(error.code);
|
|
47
|
-
return {
|
|
48
|
-
kind: 'stream',
|
|
49
|
-
...(status === undefined ? {} : { status }),
|
|
50
|
-
...(providerCode === undefined ? {} : { providerCode }),
|
|
51
|
-
};
|
|
90
|
+
return describeStreamFailure(error.code ?? readFrameCode(error.data));
|
|
52
91
|
}
|
|
53
92
|
if (plugins.APICallError.isInstance(error)) {
|
|
93
|
+
if (answeredWithStream(error)) {
|
|
94
|
+
// A stream error frame always brings a status the SDK derived from its code; a stream error
|
|
95
|
+
// without one is an answer the SDK could not read as the API it called, such as Chat
|
|
96
|
+
// Completions chunks on the Responses API.
|
|
97
|
+
if (error.statusCode === undefined)
|
|
98
|
+
return { kind: 'response' };
|
|
99
|
+
return describeStreamFailure(readFrameCode(error.data ?? parseJson(error.responseBody)));
|
|
100
|
+
}
|
|
54
101
|
const status = readStatus(error.statusCode);
|
|
55
102
|
if (status === undefined)
|
|
56
103
|
return { kind: 'network' };
|
|
@@ -61,7 +108,7 @@ function describeKnownFailure(error) {
|
|
|
61
108
|
/**
|
|
62
109
|
* Names why a model call failed, from the error a call threw or a stream reported. It follows
|
|
63
110
|
* `cause` and the last attempt of a retried call, so a wrapped failure is told by its own kind.
|
|
64
|
-
* The description holds only the kind,
|
|
111
|
+
* The description holds only the kind, an HTTP status, a provider error code and a limit.
|
|
65
112
|
*/
|
|
66
113
|
export function describeModelFailure(error) {
|
|
67
114
|
const visited = new Set();
|
|
@@ -77,4 +124,4 @@ export function describeModelFailure(error) {
|
|
|
77
124
|
}
|
|
78
125
|
return { kind: 'unknown' };
|
|
79
126
|
}
|
|
80
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
127
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRhaS5mYWlsdXJlcy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzX21vZGVscy9zbWFydGFpLmZhaWx1cmVzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sY0FBYyxDQUFDO0FBQ3hDLE9BQU8sRUFBRSxpQkFBaUIsRUFBRSxjQUFjLEVBQXdCLE1BQU0scUJBQXFCLENBQUM7QUErQjlGLHVFQUF1RTtBQUN2RSxNQUFNLGVBQWUsR0FBRyxDQUFDLENBQUM7QUFFMUIsOENBQThDO0FBQzlDLE1BQU0sd0JBQXdCLEdBQUcsRUFBRSxDQUFDO0FBRXBDOzs7O0dBSUc7QUFDSCxNQUFNLG1CQUFtQixHQUFHLGlGQUFpRixDQUFDO0FBRTlHLG9EQUFvRDtBQUNwRCxNQUFNLHVCQUF1QixHQUFHLG1GQUFtRixDQUFDO0FBRXBILDhFQUE4RTtBQUM5RSxNQUFNLG9CQUFvQixHQUFHLEVBQUUsQ0FBQztBQUVoQyxxRkFBcUY7QUFDckYsTUFBTSw0QkFBNEIsR0FBRyxDQUFDLENBQUM7QUFFdkMsdUdBQXVHO0FBQ3ZHLFNBQVMsVUFBVSxDQUFDLElBQVk7SUFDOUIsSUFBSSxJQUFJLENBQUMsTUFBTSxHQUFHLG9CQUFvQjtRQUFFLE9BQU8sS0FBSyxDQUFDO0lBQ3JELE9BQU8sSUFBSSxDQUFDLE1BQU0sR0FBRyw0QkFBNEIsSUFBSSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDO0FBQ3JHLENBQUM7QUFFRCx1R0FBdUc7QUFDdkcsU0FBUyxjQUFjLENBQUMsSUFBWTtJQUNsQyxPQUFPLElBQUksQ0FBQyxNQUFNLElBQUksd0JBQXdCO1dBQ3pDLG1CQUFtQixDQUFDLElBQUksQ0FBQyxJQUFJLENBQUM7V0FDOUIsQ0FBQyx1QkFBdUIsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDO1dBQ25DLElBQUksQ0FB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
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"author": "Task Venture Capital GmbH",
|
|
3
3
|
"license": "MIT",
|
|
4
4
|
"name": "@modelprofile.com/flexharness-models",
|
|
5
|
-
"version": "9.3.
|
|
5
|
+
"version": "9.3.1",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"description": "Provider-independent model contracts, prompt caching and explicit model registration.",
|
|
8
8
|
"main": "./dist_ts_models/index.js",
|
package/readme.md
CHANGED
|
@@ -28,8 +28,11 @@ limit. `createModelLimitInfo`, `isModelLimitError`, `isModelLimitInfo`, `describ
|
|
|
28
28
|
`describeModelFailure(error)` returns an `IModelFailureDescription` that names why a model call
|
|
29
29
|
failed and never holds provider response text, headers or credentials. Its `kind`
|
|
30
30
|
(`TModelFailureKind`) is `limit` (with the `limit`), `http` (with the answer's `status`), `stream`
|
|
31
|
-
(an error
|
|
32
|
-
provider states
|
|
31
|
+
(an error a successful response stream reported before or after output began, with the
|
|
32
|
+
`providerCode` the provider states, its error `code` or, for an error with none such as
|
|
33
|
+
Anthropic's, its error `type` (`overloaded_error`), and no `status`: the answer succeeded, and
|
|
34
|
+
the HTTP-equivalent status the AI SDK derives from the code, such as 500 for `server_error`, is
|
|
35
|
+
no HTTP answer), `response` (an answer that could not be processed, with its `status`),
|
|
33
36
|
`network` (no answer arrived), `aborted` (an abort or timeout signal) or `unknown`. A provider code
|
|
34
37
|
is kept only in a shape providers name errors in (snake_case words, optionally dotted, such as
|
|
35
38
|
`insufficient_quota`; upper-case status names such as `RESOURCE_EXHAUSTED`; numeric codes) of at
|
package/ts_models/readme.md
CHANGED
|
@@ -28,8 +28,11 @@ limit. `createModelLimitInfo`, `isModelLimitError`, `isModelLimitInfo`, `describ
|
|
|
28
28
|
`describeModelFailure(error)` returns an `IModelFailureDescription` that names why a model call
|
|
29
29
|
failed and never holds provider response text, headers or credentials. Its `kind`
|
|
30
30
|
(`TModelFailureKind`) is `limit` (with the `limit`), `http` (with the answer's `status`), `stream`
|
|
31
|
-
(an error
|
|
32
|
-
provider states
|
|
31
|
+
(an error a successful response stream reported before or after output began, with the
|
|
32
|
+
`providerCode` the provider states, its error `code` or, for an error with none such as
|
|
33
|
+
Anthropic's, its error `type` (`overloaded_error`), and no `status`: the answer succeeded, and
|
|
34
|
+
the HTTP-equivalent status the AI SDK derives from the code, such as 500 for `server_error`, is
|
|
35
|
+
no HTTP answer), `response` (an answer that could not be processed, with its `status`),
|
|
33
36
|
`network` (no answer arrived), `aborted` (an abort or timeout signal) or `unknown`. A provider code
|
|
34
37
|
is kept only in a shape providers name errors in (snake_case words, optionally dotted, such as
|
|
35
38
|
`insufficient_quota`; upper-case status names such as `RESOURCE_EXHAUSTED`; numeric codes) of at
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import * as plugins from './plugins.js';
|
|
2
|
-
import { isModelLimitError, type IModelLimitInfo } from './smartai.limits.js';
|
|
2
|
+
import { isModelLimitError, readJsonRecord, type IModelLimitInfo } from './smartai.limits.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* `limit`: a usage or rate limit refused the call; `limit` describes it.
|
|
6
6
|
* `http`: the provider answered with an HTTP error status.
|
|
7
|
-
* `stream`: the provider
|
|
7
|
+
* `stream`: the provider answered with a response stream and reported an error inside it, before or
|
|
8
|
+
* after output began.
|
|
8
9
|
* `response`: the provider answered, but its response could not be read or processed.
|
|
9
10
|
* `network`: no response arrived because the connection failed.
|
|
10
11
|
* `aborted`: an abort signal ended the call, a timeout signal included.
|
|
@@ -15,9 +16,15 @@ export type TModelFailureKind = 'limit' | 'http' | 'stream' | 'response' | 'netw
|
|
|
15
16
|
/** Why a model call failed, without provider response text, headers or credentials. */
|
|
16
17
|
export interface IModelFailureDescription {
|
|
17
18
|
kind: TModelFailureKind;
|
|
18
|
-
/**
|
|
19
|
+
/**
|
|
20
|
+
* The HTTP status the provider answered with. A `stream` failure has none: its answer was a
|
|
21
|
+
* success, and the status the AI SDK derives from the error's code is no HTTP answer.
|
|
22
|
+
*/
|
|
19
23
|
status?: number;
|
|
20
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* The provider's machine-readable name of a stream error: its code, for example `server_error`,
|
|
26
|
+
* or, for an error without one, its type, for example Anthropic's `overloaded_error`.
|
|
27
|
+
*/
|
|
21
28
|
providerCode?: string;
|
|
22
29
|
/** The limit of a `limit` failure. */
|
|
23
30
|
limit?: Readonly<IModelLimitInfo>;
|
|
@@ -63,24 +70,72 @@ function readStatus(value: number | undefined): number | undefined {
|
|
|
63
70
|
return typeof value === 'number' && Number.isInteger(value) && value >= 100 && value <= 599 ? value : undefined;
|
|
64
71
|
}
|
|
65
72
|
|
|
66
|
-
function readProviderCode(value:
|
|
73
|
+
function readProviderCode(value: unknown): string | undefined {
|
|
67
74
|
const code = typeof value === 'number' && Number.isSafeInteger(value) ? String(value) : value;
|
|
68
75
|
return typeof code === 'string' && isProviderCode(code) ? code : undefined;
|
|
69
76
|
}
|
|
70
77
|
|
|
78
|
+
/** The media type of a response stream. */
|
|
79
|
+
const EVENT_STREAM_MEDIA_TYPE = 'text/event-stream';
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Whether the call's answer was a response stream. The AI SDK reads a stream only from a successful
|
|
83
|
+
* answer; when the stream reports an error before output began, the provider throws an
|
|
84
|
+
* APICallError that carries the stream's headers and a status derived from the error's code.
|
|
85
|
+
*/
|
|
86
|
+
function answeredWithStream(error: plugins.APICallError): boolean {
|
|
87
|
+
const contentType = Object.entries(error.responseHeaders ?? {})
|
|
88
|
+
.find(([name]) => name.toLowerCase() === 'content-type')?.[1];
|
|
89
|
+
return contentType?.trim().toLowerCase().startsWith(EVENT_STREAM_MEDIA_TYPE) === true;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function parseJson(text: string | undefined): unknown {
|
|
93
|
+
if (text === undefined) return undefined;
|
|
94
|
+
try {
|
|
95
|
+
return JSON.parse(text);
|
|
96
|
+
} catch {
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** The `type` Anthropic and OpenAI Responses give an error event; it marks the event and names no failure. */
|
|
102
|
+
const ERROR_EVENT_TYPE = 'error';
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* The code in a provider's stream error frame. The error record is the frame's `response.error`
|
|
106
|
+
* (an OpenAI `response.failed` event), its `error` (an Anthropic or OpenAI error event) or the frame
|
|
107
|
+
* itself (a bare Anthropic error, an OpenAI Chat error or a flat OpenAI Responses error event). A
|
|
108
|
+
* record names its failure by `code` and, when it has none, by `type`, which is the only name
|
|
109
|
+
* Anthropic gives an error (`overloaded_error`); the `type` of a flat error event is the event's.
|
|
110
|
+
*/
|
|
111
|
+
function readFrameCode(frame: unknown): unknown {
|
|
112
|
+
const event = readJsonRecord(frame);
|
|
113
|
+
if (event === undefined) return undefined;
|
|
114
|
+
const record = readJsonRecord(readJsonRecord(event.response)?.error) ?? readJsonRecord(event.error) ?? event;
|
|
115
|
+
const code = record.code ?? event.code;
|
|
116
|
+
if (code !== undefined && code !== null) return code;
|
|
117
|
+
return record.type === ERROR_EVENT_TYPE ? undefined : record.type;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function describeStreamFailure(code: unknown): IModelFailureDescription {
|
|
121
|
+
const providerCode = readProviderCode(code);
|
|
122
|
+
return providerCode === undefined ? { kind: 'stream' } : { kind: 'stream', providerCode };
|
|
123
|
+
}
|
|
124
|
+
|
|
71
125
|
function describeKnownFailure(error: unknown): IModelFailureDescription | undefined {
|
|
72
126
|
if (isModelLimitError(error)) return { kind: 'limit', limit: error.limit };
|
|
73
127
|
if (plugins.isAbortError(error)) return { kind: 'aborted' };
|
|
74
128
|
if (plugins.StreamProviderError.isInstance(error) || plugins.isProviderStreamError(error)) {
|
|
75
|
-
|
|
76
|
-
const providerCode = readProviderCode(error.code);
|
|
77
|
-
return {
|
|
78
|
-
kind: 'stream',
|
|
79
|
-
...(status === undefined ? {} : { status }),
|
|
80
|
-
...(providerCode === undefined ? {} : { providerCode }),
|
|
81
|
-
};
|
|
129
|
+
return describeStreamFailure(error.code ?? readFrameCode(error.data));
|
|
82
130
|
}
|
|
83
131
|
if (plugins.APICallError.isInstance(error)) {
|
|
132
|
+
if (answeredWithStream(error)) {
|
|
133
|
+
// A stream error frame always brings a status the SDK derived from its code; a stream error
|
|
134
|
+
// without one is an answer the SDK could not read as the API it called, such as Chat
|
|
135
|
+
// Completions chunks on the Responses API.
|
|
136
|
+
if (error.statusCode === undefined) return { kind: 'response' };
|
|
137
|
+
return describeStreamFailure(readFrameCode(error.data ?? parseJson(error.responseBody)));
|
|
138
|
+
}
|
|
84
139
|
const status = readStatus(error.statusCode);
|
|
85
140
|
if (status === undefined) return { kind: 'network' };
|
|
86
141
|
return { kind: status >= 400 ? 'http' : 'response', status };
|
|
@@ -91,7 +146,7 @@ function describeKnownFailure(error: unknown): IModelFailureDescription | undefi
|
|
|
91
146
|
/**
|
|
92
147
|
* Names why a model call failed, from the error a call threw or a stream reported. It follows
|
|
93
148
|
* `cause` and the last attempt of a retried call, so a wrapped failure is told by its own kind.
|
|
94
|
-
* The description holds only the kind,
|
|
149
|
+
* The description holds only the kind, an HTTP status, a provider error code and a limit.
|
|
95
150
|
*/
|
|
96
151
|
export function describeModelFailure(error: unknown): IModelFailureDescription {
|
|
97
152
|
const visited = new Set<unknown>();
|