@robota-sdk/agent-provider-openai 3.0.0-beta.83 → 3.0.0-beta.84
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/CHANGELOG.md +24 -0
- package/README.md +42 -17
- package/package.json +4 -3
- package/src/openai/__tests__/provider-errors.test.ts +34 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# @robota-sdk/agent-provider-openai
|
|
2
2
|
|
|
3
|
+
## 3.0.0-beta.84
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 9c6a8db: Provider adapters now throw the specific typed failure a vendor response describes, instead of a
|
|
8
|
+
generic `ProviderError` carrying only its status. A 401/403 (or a vendor-specific authentication type)
|
|
9
|
+
becomes an `AuthenticationError`; a response naming an unavailable model — by code or, when the vendor
|
|
10
|
+
gives neither (Anthropic's and Gemini's real bodies carry no distinguishing code), by its message —
|
|
11
|
+
becomes a `ModelNotAvailableError`, whose `model` is now optional since a bare HTTP status does not
|
|
12
|
+
always name one; a transport failure becomes a `NetworkError`, which now also carries the provider
|
|
13
|
+
name. A 429's `retry-after` header (a delta or an HTTP-date) is read into `RateLimitError.retryAfter`
|
|
14
|
+
when the vendor sends one.
|
|
15
|
+
|
|
16
|
+
Vendor error text is scrubbed of anything that reads like a credential — an `Authorization`/`Bearer`
|
|
17
|
+
value, an API key in a header, query string or JSON body, a bare `sk-`/`AIza…` token — before it is
|
|
18
|
+
kept on any of these typed errors, so a misconfigured self-hosted gateway that echoes a request back
|
|
19
|
+
can no longer leak one into a message or a "Details" disclosure. The new `scrubSecrets` export applies
|
|
20
|
+
the same scrubbing to arbitrary text.
|
|
21
|
+
|
|
22
|
+
- Updated dependencies [9f46375]
|
|
23
|
+
- Updated dependencies [9c6a8db]
|
|
24
|
+
- @robota-sdk/agent-core@3.0.0-beta.84
|
|
25
|
+
- @robota-sdk/agent-provider-openai-compatible@3.0.0-beta.84
|
|
26
|
+
|
|
3
27
|
## 3.0.0-beta.83
|
|
4
28
|
|
|
5
29
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -44,6 +44,7 @@ const provider = new OpenAIProvider({
|
|
|
44
44
|
baseURL: 'https://ai-gateway.vercel.sh/v1',
|
|
45
45
|
});
|
|
46
46
|
// then: defaultModel: { provider: 'openai', model: 'anthropic/claude-sonnet-4-5' }
|
|
47
|
+
// (a gateway model slug, passed through to the gateway as written)
|
|
47
48
|
```
|
|
48
49
|
|
|
49
50
|
The API surface follows `baseURL` unless `apiSurface` is set:
|
|
@@ -63,28 +64,52 @@ tool is sent with `strict: true` on both the Responses and Chat Completions surf
|
|
|
63
64
|
OpenAI-compatible endpoint that rejects the `strict` field needs `strictTools` left off. With
|
|
64
65
|
`strictTools` off (the default) tool schemas are forwarded as authored and no `strict` field is sent.
|
|
65
66
|
|
|
67
|
+
### Reasoning effort and web tools
|
|
68
|
+
|
|
69
|
+
A reasoning-effort selection (`defaultModel.effort` on a `Robota` agent, or `effort` on a chat call)
|
|
70
|
+
is sent as `reasoning.effort` only on the official Responses API (no `baseURL`) and only for models
|
|
71
|
+
in the provider's verified effort table. On Chat Completions, behind a custom `baseURL`, or for other
|
|
72
|
+
models, the effort is reported as not applied and no native field is sent. When a call selects an
|
|
73
|
+
effort, a static `reasoning` option's `effort` must equal the value sent; a different value, or any
|
|
74
|
+
value when no native effort is sent, is rejected.
|
|
75
|
+
|
|
76
|
+
Native web search and web fetch are not available through this provider on either surface. Setting
|
|
77
|
+
`nativeWebTools.webSearch` or `nativeWebTools.webFetch` to `true` makes the constructor throw.
|
|
78
|
+
|
|
66
79
|
## Options
|
|
67
80
|
|
|
68
81
|
`new OpenAIProvider(options: IOpenAIProviderOptions)`. One of `apiKey`, `client` or `executor` is
|
|
69
82
|
required; the constructor throws otherwise.
|
|
70
83
|
|
|
71
|
-
| Option
|
|
72
|
-
|
|
|
73
|
-
| `apiKey`
|
|
74
|
-
| `baseURL`
|
|
75
|
-
| `apiSurface`
|
|
76
|
-
| `defaultModel`
|
|
77
|
-
| `organization`
|
|
78
|
-
| `timeout`
|
|
79
|
-
| `strictTools`
|
|
80
|
-
| `responseFormat`
|
|
81
|
-
| `jsonSchema`
|
|
82
|
-
| `reasoning`
|
|
83
|
-
| `
|
|
84
|
-
| `
|
|
85
|
-
| `
|
|
86
|
-
| `
|
|
87
|
-
| `
|
|
84
|
+
| Option | Type | Description |
|
|
85
|
+
| --------------------------- | ------------------------------------------ | --------------------------------------------------------------------------------------------------------------- |
|
|
86
|
+
| `apiKey` | `string` | API key used to create the `openai` client. |
|
|
87
|
+
| `baseURL` | `string` | Endpoint base URL (default `https://api.openai.com/v1`). Also switches the default surface to Chat Completions. |
|
|
88
|
+
| `apiSurface` | `'responses' \| 'chat-completions'` | Forces the API surface instead of deriving it from `baseURL`. |
|
|
89
|
+
| `defaultModel` | `string` | Model used when a call does not name one. |
|
|
90
|
+
| `organization` | `string` | OpenAI organization ID. |
|
|
91
|
+
| `timeout` | `number` | Request timeout in milliseconds. |
|
|
92
|
+
| `strictTools` | `boolean` | Closes tool object schemas and requests OpenAI strict function calling (see above). |
|
|
93
|
+
| `responseFormat` | `'text' \| 'json_object' \| 'json_schema'` | Response format; `'json_schema'` uses `jsonSchema`. |
|
|
94
|
+
| `jsonSchema` | `IOpenAIJsonSchemaDefinition` | Schema for Structured Outputs when `responseFormat` is `'json_schema'`. |
|
|
95
|
+
| `reasoning` | `IOpenAIResponsesReasoningOptions` | Responses API reasoning controls (`effort`, `summary`). |
|
|
96
|
+
| `store` | `boolean` | Responses API only: whether OpenAI stores the response; when unset, OpenAI's default applies. |
|
|
97
|
+
| `includeEncryptedReasoning` | `boolean` | Responses API only: asks for encrypted reasoning items, so reasoning can continue without server-side state. |
|
|
98
|
+
| `nativeWebTools` | `IOpenAINativeWebToolsOptions` | Not supported: `true` for `webSearch` or `webFetch` throws (see above). |
|
|
99
|
+
| `includeStreamUsage` | `boolean` | Requests token usage on streaming Chat Completions turns (default `true`). |
|
|
100
|
+
| `client` | `OpenAI` | A pre-built `openai` client, used instead of `apiKey`. |
|
|
101
|
+
| `executor` | `IExecutor` | Delegates chat calls to an executor instead of calling the API directly. |
|
|
102
|
+
| `payloadLogger` | `IPayloadLogger` | Logs a summary of each Chat Completions request (model, message count, tools present, temperature, max tokens). |
|
|
103
|
+
| `logger` | `ILogger` | Logger for internal provider messages (silent by default). |
|
|
104
|
+
|
|
105
|
+
## Errors
|
|
106
|
+
|
|
107
|
+
A failed API call throws a typed error from `@robota-sdk/agent-core`: `RateLimitError` (with
|
|
108
|
+
`retryAfter` from the `retry-after` header), `AuthenticationError`, `ModelNotAvailableError` or
|
|
109
|
+
`NetworkError` when the failure is one of those, otherwise a `ProviderError` that carries the HTTP
|
|
110
|
+
`status` and the error `type` the endpoint reported and keeps the SDK error as `originalError`. Both
|
|
111
|
+
API surfaces, streaming included, map failures the same way. An aborted call rethrows the abort
|
|
112
|
+
unchanged. See [Provider failures](../../content/guide/error-handling.md#provider-failures).
|
|
88
113
|
|
|
89
114
|
## Exports
|
|
90
115
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@robota-sdk/agent-provider-openai",
|
|
3
|
-
"version": "3.0.0-beta.
|
|
3
|
+
"version": "3.0.0-beta.84",
|
|
4
4
|
"description": "OpenAI provider implementation for Robota SDK",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -78,10 +78,11 @@
|
|
|
78
78
|
"url": "https://github.com/woojubb/robota.git",
|
|
79
79
|
"directory": "packages/agent-provider-openai"
|
|
80
80
|
},
|
|
81
|
+
"homepage": "https://docs.robota.io/en/packages/agent-provider-openai/",
|
|
81
82
|
"dependencies": {
|
|
82
83
|
"openai": "^4.104.0",
|
|
83
|
-
"@robota-sdk/agent-core": "3.0.0-beta.
|
|
84
|
-
"@robota-sdk/agent-provider-openai-compatible": "3.0.0-beta.
|
|
84
|
+
"@robota-sdk/agent-core": "3.0.0-beta.84",
|
|
85
|
+
"@robota-sdk/agent-provider-openai-compatible": "3.0.0-beta.84"
|
|
85
86
|
},
|
|
86
87
|
"volta": {
|
|
87
88
|
"extends": "../../package.json"
|
|
@@ -3,7 +3,14 @@
|
|
|
3
3
|
* collapsing into a bare `Error` whose only trace of the status is its message text.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
AuthenticationError,
|
|
8
|
+
ModelNotAvailableError,
|
|
9
|
+
NetworkError,
|
|
10
|
+
ProviderError,
|
|
11
|
+
RateLimitError,
|
|
12
|
+
classifyProviderFailure,
|
|
13
|
+
} from '@robota-sdk/agent-core';
|
|
7
14
|
import { APIConnectionError, APIError, APIUserAbortError } from 'openai';
|
|
8
15
|
import { describe, expect, it, vi } from 'vitest';
|
|
9
16
|
|
|
@@ -44,7 +51,6 @@ const statuses: Array<[number, string]> = [
|
|
|
44
51
|
[529, 'overloaded_error'],
|
|
45
52
|
[503, 'server_error'],
|
|
46
53
|
[500, 'server_error'],
|
|
47
|
-
[401, 'invalid_api_key'],
|
|
48
54
|
];
|
|
49
55
|
|
|
50
56
|
describe('OpenAI provider errors', () => {
|
|
@@ -73,7 +79,7 @@ describe('OpenAI provider errors', () => {
|
|
|
73
79
|
expect(classifyProviderFailure(error)).toEqual({ switchable: false, reason: 'aborted' });
|
|
74
80
|
});
|
|
75
81
|
|
|
76
|
-
it(`${surface} classifies an SDK connection failure as
|
|
82
|
+
it(`${surface} classifies an SDK connection failure as a NetworkError`, async () => {
|
|
77
83
|
const provider = providerWith(
|
|
78
84
|
surface,
|
|
79
85
|
new APIConnectionError({
|
|
@@ -82,11 +88,11 @@ describe('OpenAI provider errors', () => {
|
|
|
82
88
|
}),
|
|
83
89
|
);
|
|
84
90
|
const error = await failure(() => provider.chat(messages, { model: 'gpt-4o' }));
|
|
85
|
-
expect(error).toBeInstanceOf(
|
|
91
|
+
expect(error).toBeInstanceOf(NetworkError);
|
|
86
92
|
expect(classifyProviderFailure(error)).toEqual({ switchable: false, reason: 'network' });
|
|
87
93
|
});
|
|
88
94
|
|
|
89
|
-
it(`${surface}
|
|
95
|
+
it(`${surface} maps a 400 model_not_found to ModelNotAvailableError`, async () => {
|
|
90
96
|
const provider = providerWith(
|
|
91
97
|
surface,
|
|
92
98
|
APIError.generate(
|
|
@@ -99,6 +105,7 @@ describe('OpenAI provider errors', () => {
|
|
|
99
105
|
),
|
|
100
106
|
);
|
|
101
107
|
const error = await failure(() => provider.chat(messages, { model: 'gpt-4o' }));
|
|
108
|
+
expect(error).toBeInstanceOf(ModelNotAvailableError);
|
|
102
109
|
expect(classifyProviderFailure(error)).toEqual({
|
|
103
110
|
switchable: true,
|
|
104
111
|
reason: 'model-unavailable',
|
|
@@ -110,5 +117,27 @@ describe('OpenAI provider errors', () => {
|
|
|
110
117
|
const error = await failure(() => provider.chat(messages, { model: 'gpt-4o' }));
|
|
111
118
|
expect(error).toBeInstanceOf(RateLimitError);
|
|
112
119
|
});
|
|
120
|
+
|
|
121
|
+
it(`${surface} chat maps 401 to AuthenticationError, carrying the provider name`, async () => {
|
|
122
|
+
const provider = providerWith(surface, httpError(401, 'invalid_api_key'));
|
|
123
|
+
const error = await failure(() => provider.chat(messages, { model: 'gpt-4o' }));
|
|
124
|
+
expect(error).toBeInstanceOf(AuthenticationError);
|
|
125
|
+
expect((error as AuthenticationError).provider).toBe('openai');
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it(`${surface} chat reads the retry-after header off a 429 into RateLimitError.retryAfter`, async () => {
|
|
129
|
+
const provider = providerWith(
|
|
130
|
+
surface,
|
|
131
|
+
APIError.generate(
|
|
132
|
+
429,
|
|
133
|
+
{ error: { type: 'rate_limit_exceeded', message: 'slow down' } },
|
|
134
|
+
undefined,
|
|
135
|
+
{ 'retry-after': '12' },
|
|
136
|
+
),
|
|
137
|
+
);
|
|
138
|
+
const error = await failure(() => provider.chat(messages, { model: 'gpt-4o' }));
|
|
139
|
+
expect(error).toBeInstanceOf(RateLimitError);
|
|
140
|
+
expect((error as RateLimitError).retryAfter).toBe(12);
|
|
141
|
+
});
|
|
113
142
|
}
|
|
114
143
|
});
|