@robota-sdk/agent-provider-openai 3.0.0-beta.83 → 3.0.0-beta.85
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 +40 -0
- package/README.md +42 -17
- package/dist/node/index.d.cts +7 -15
- package/dist/node/index.d.cts.map +1 -1
- package/dist/node/index.d.ts +7 -15
- package/dist/node/index.d.ts.map +1 -1
- package/package.json +4 -3
- package/src/openai/__tests__/provider-errors.test.ts +34 -5
- package/src/openai/types.ts +7 -15
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,45 @@
|
|
|
1
1
|
# @robota-sdk/agent-provider-openai
|
|
2
2
|
|
|
3
|
+
## 3.0.0-beta.85
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 3ab2eca: - External presets accept every permission mode a session does, `auto` included; `auto` used to fail validation.
|
|
8
|
+
- `createDagFramework({ ports: { costMeta } })` wires cost-metadata management; without it cost operations still report that they are unsupported.
|
|
9
|
+
- `startCli()` runs the subagent worker when a subagent starts the embedder's entry script again, as the `robota` executable already did; an embedded CLI used to start a second CLI there.
|
|
10
|
+
- The provider `executor` option docs no longer import a `RemoteExecutor` that does not exist, and `IRemoteExecutorConfig` is marked deprecated: nothing implements a remote executor.
|
|
11
|
+
- Updated dependencies [41cca13]
|
|
12
|
+
- Updated dependencies [ada3841]
|
|
13
|
+
- Updated dependencies [5093a30]
|
|
14
|
+
- Updated dependencies [94b2c87]
|
|
15
|
+
- Updated dependencies [3ab2eca]
|
|
16
|
+
- @robota-sdk/agent-core@3.0.0-beta.85
|
|
17
|
+
- @robota-sdk/agent-provider-openai-compatible@3.0.0-beta.85
|
|
18
|
+
|
|
19
|
+
## 3.0.0-beta.84
|
|
20
|
+
|
|
21
|
+
### Patch Changes
|
|
22
|
+
|
|
23
|
+
- 9c6a8db: Provider adapters now throw the specific typed failure a vendor response describes, instead of a
|
|
24
|
+
generic `ProviderError` carrying only its status. A 401/403 (or a vendor-specific authentication type)
|
|
25
|
+
becomes an `AuthenticationError`; a response naming an unavailable model — by code or, when the vendor
|
|
26
|
+
gives neither (Anthropic's and Gemini's real bodies carry no distinguishing code), by its message —
|
|
27
|
+
becomes a `ModelNotAvailableError`, whose `model` is now optional since a bare HTTP status does not
|
|
28
|
+
always name one; a transport failure becomes a `NetworkError`, which now also carries the provider
|
|
29
|
+
name. A 429's `retry-after` header (a delta or an HTTP-date) is read into `RateLimitError.retryAfter`
|
|
30
|
+
when the vendor sends one.
|
|
31
|
+
|
|
32
|
+
Vendor error text is scrubbed of anything that reads like a credential — an `Authorization`/`Bearer`
|
|
33
|
+
value, an API key in a header, query string or JSON body, a bare `sk-`/`AIza…` token — before it is
|
|
34
|
+
kept on any of these typed errors, so a misconfigured self-hosted gateway that echoes a request back
|
|
35
|
+
can no longer leak one into a message or a "Details" disclosure. The new `scrubSecrets` export applies
|
|
36
|
+
the same scrubbing to arbitrary text.
|
|
37
|
+
|
|
38
|
+
- Updated dependencies [9f46375]
|
|
39
|
+
- Updated dependencies [9c6a8db]
|
|
40
|
+
- @robota-sdk/agent-core@3.0.0-beta.84
|
|
41
|
+
- @robota-sdk/agent-provider-openai-compatible@3.0.0-beta.84
|
|
42
|
+
|
|
3
43
|
## 3.0.0-beta.83
|
|
4
44
|
|
|
5
45
|
### 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/dist/node/index.d.cts
CHANGED
|
@@ -173,26 +173,18 @@ interface IOpenAIProviderOptions {
|
|
|
173
173
|
/**
|
|
174
174
|
* Optional executor for handling AI requests
|
|
175
175
|
*
|
|
176
|
-
* When provided, the provider
|
|
177
|
-
*
|
|
176
|
+
* When provided, the provider delegates every chat call to this executor instead of calling the
|
|
177
|
+
* API itself — for example to route calls through your own server. Implement `IExecutor` from
|
|
178
|
+
* `@robota-sdk/agent-core`; `LocalExecutor` is the in-process one.
|
|
178
179
|
*
|
|
179
180
|
* @example
|
|
180
181
|
* ```typescript
|
|
181
|
-
* import { LocalExecutor
|
|
182
|
+
* import { LocalExecutor } from '@robota-sdk/agent-core';
|
|
182
183
|
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
* localExecutor.registerProvider('openai', new OpenAIProvider({ apiKey: 'sk-...' }));
|
|
184
|
+
* const executor = new LocalExecutor();
|
|
185
|
+
* executor.registerProvider('openai', new OpenAIProvider({ apiKey: 'sk-...' }));
|
|
186
186
|
*
|
|
187
|
-
*
|
|
188
|
-
* const remoteExecutor = new RemoteExecutor({
|
|
189
|
-
* serverUrl: 'https://api.robota.io',
|
|
190
|
-
* userApiKey: 'user-token-123'
|
|
191
|
-
* });
|
|
192
|
-
*
|
|
193
|
-
* const provider = new OpenAIProvider({
|
|
194
|
-
* executor: remoteExecutor // No direct API key needed
|
|
195
|
-
* });
|
|
187
|
+
* const provider = new OpenAIProvider({ executor });
|
|
196
188
|
* ```
|
|
197
189
|
*/
|
|
198
190
|
executor?: IExecutor;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.cts","names":[],"sources":["../../src/openai/types.ts","../../src/openai/provider.ts","../../src/openai/adapter.ts","../../src/openai/provider-definition.ts"],"mappings":";;;;KAIY;UAEK;EACf;EACA;EACA,SAAS,eAAe;EACxB;;UAGe;EACf;EACA;;UAGe;EACf;EACA;;;;;KAMU,4EAMR,8BACA,+BACA,mCACA,SACA,iBACA,UACA,YACA,2BACA;GACG,cAAc;;;;;UAKJ;;;;GAId,cAAc;;;;EAKf;;;;EAKA;;;;EAKA;;;;;;;EAQA;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA6BA;;;;EAKA;;;;;;;EAQA,aAAa;;;;;;;EAQb;;;;EAKA,aAAa;;;;;EAMb,YAAY;;;;EAKZ;;;;EAKA;;;;;;;;;;;;;;;;;EAkBA;;;;;;;;EASA,iBAAiB;;;;EAKjB,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA6BT,gBAAgB
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../../src/openai/types.ts","../../src/openai/provider.ts","../../src/openai/adapter.ts","../../src/openai/provider-definition.ts"],"mappings":";;;;KAIY;UAEK;EACf;EACA;EACA,SAAS,eAAe;EACxB;;UAGe;EACf;EACA;;UAGe;EACf;EACA;;;;;KAMU,4EAMR,8BACA,+BACA,mCACA,SACA,iBACA,UACA,YACA,2BACA;GACG,cAAc;;;;;UAKJ;;;;GAId,cAAc;;;;EAKf;;;;EAKA;;;;EAKA;;;;;;;EAQA;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA6BA;;;;EAKA;;;;;;;EAQA,aAAa;;;;;;;EAQb;;;;EAKA,aAAa;;;;;EAMb,YAAY;;;;EAKZ;;;;EAKA;;;;;;;;;;;;;;;;;EAkBA;;;;;;;;EASA,iBAAiB;;;;EAKjB,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA6BT,gBAAgB;;;;;;;;;;;;;;;;;;EAmBhB,WAAW;;;;;EAMX,SAAS;;;;;;;;;;;;cClME,uBAAuB;WAChB;WACA;mBAED;mBACA;mBACA;mBACA;mBACA;;;;;;;EAQjB,cAAc;EAEF,YAAA,SAAS;EA6BN,KACb,UAAU,qBACV,UAAU,eACT,QAAQ;EAgDK,WACd,UAAU,qBACV,UAAU,eACT,cAAc;UAiCF;;;;;;;;;;EAuCf;;;;;;EASA;UAIQ;UAKA;EAKC,eAAe;UAMhB;;;;;YAcW,qBAAqB;;;;;;;;UAahC;UAYA;EAiCC;EAIA,mBAAmB;EAwBnB;EAIM,WAAW;YAIP,iBAAiB,UAAU;;;;;;;;;;;;cC/UnC;;;;;;;;;SASJ,wBAAwB,UAAU,sBAAsB;;;;;SAqBxD,eAAe,UAAU,sBAAsB,OAAO,KAAK;;;;;SAU3D,eAAe,KAAK,oBAAoB,OAAO,KAAK;;;;SA2EpD,wBACL,UAAU,OAAO,KAAK,8BACtB,wBACC,OAAO,KAAK;;;;cC1HJ;cACA;iBAeG,kCAAkC"}
|
package/dist/node/index.d.ts
CHANGED
|
@@ -173,26 +173,18 @@ interface IOpenAIProviderOptions {
|
|
|
173
173
|
/**
|
|
174
174
|
* Optional executor for handling AI requests
|
|
175
175
|
*
|
|
176
|
-
* When provided, the provider
|
|
177
|
-
*
|
|
176
|
+
* When provided, the provider delegates every chat call to this executor instead of calling the
|
|
177
|
+
* API itself — for example to route calls through your own server. Implement `IExecutor` from
|
|
178
|
+
* `@robota-sdk/agent-core`; `LocalExecutor` is the in-process one.
|
|
178
179
|
*
|
|
179
180
|
* @example
|
|
180
181
|
* ```typescript
|
|
181
|
-
* import { LocalExecutor
|
|
182
|
+
* import { LocalExecutor } from '@robota-sdk/agent-core';
|
|
182
183
|
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
* localExecutor.registerProvider('openai', new OpenAIProvider({ apiKey: 'sk-...' }));
|
|
184
|
+
* const executor = new LocalExecutor();
|
|
185
|
+
* executor.registerProvider('openai', new OpenAIProvider({ apiKey: 'sk-...' }));
|
|
186
186
|
*
|
|
187
|
-
*
|
|
188
|
-
* const remoteExecutor = new RemoteExecutor({
|
|
189
|
-
* serverUrl: 'https://api.robota.io',
|
|
190
|
-
* userApiKey: 'user-token-123'
|
|
191
|
-
* });
|
|
192
|
-
*
|
|
193
|
-
* const provider = new OpenAIProvider({
|
|
194
|
-
* executor: remoteExecutor // No direct API key needed
|
|
195
|
-
* });
|
|
187
|
+
* const provider = new OpenAIProvider({ executor });
|
|
196
188
|
* ```
|
|
197
189
|
*/
|
|
198
190
|
executor?: IExecutor;
|
package/dist/node/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/openai/types.ts","../../src/openai/provider.ts","../../src/openai/adapter.ts","../../src/openai/provider-definition.ts"],"mappings":";;;;KAIY;UAEK;EACf;EACA;EACA,SAAS,eAAe;EACxB;;UAGe;EACf;EACA;;UAGe;EACf;EACA;;;;;KAMU,4EAMR,8BACA,+BACA,mCACA,SACA,iBACA,UACA,YACA,2BACA;GACG,cAAc;;;;;UAKJ;;;;GAId,cAAc;;;;EAKf;;;;EAKA;;;;EAKA;;;;;;;EAQA;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA6BA;;;;EAKA;;;;;;;EAQA,aAAa;;;;;;;EAQb;;;;EAKA,aAAa;;;;;EAMb,YAAY;;;;EAKZ;;;;EAKA;;;;;;;;;;;;;;;;;EAkBA;;;;;;;;EASA,iBAAiB;;;;EAKjB,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA6BT,gBAAgB
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/openai/types.ts","../../src/openai/provider.ts","../../src/openai/adapter.ts","../../src/openai/provider-definition.ts"],"mappings":";;;;KAIY;UAEK;EACf;EACA;EACA,SAAS,eAAe;EACxB;;UAGe;EACf;EACA;;UAGe;EACf;EACA;;;;;KAMU,4EAMR,8BACA,+BACA,mCACA,SACA,iBACA,UACA,YACA,2BACA;GACG,cAAc;;;;;UAKJ;;;;GAId,cAAc;;;;EAKf;;;;EAKA;;;;EAKA;;;;;;;EAQA;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA6BA;;;;EAKA;;;;;;;EAQA,aAAa;;;;;;;EAQb;;;;EAKA,aAAa;;;;;EAMb,YAAY;;;;EAKZ;;;;EAKA;;;;;;;;;;;;;;;;;EAkBA;;;;;;;;EASA,iBAAiB;;;;EAKjB,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA6BT,gBAAgB;;;;;;;;;;;;;;;;;;EAmBhB,WAAW;;;;;EAMX,SAAS;;;;;;;;;;;;cClME,uBAAuB;WAChB;WACA;mBAED;mBACA;mBACA;mBACA;mBACA;;;;;;;EAQjB,cAAc;EAEF,YAAA,SAAS;EA6BN,KACb,UAAU,qBACV,UAAU,eACT,QAAQ;EAgDK,WACd,UAAU,qBACV,UAAU,eACT,cAAc;UAiCF;;;;;;;;;;EAuCf;;;;;;EASA;UAIQ;UAKA;EAKC,eAAe;UAMhB;;;;;YAcW,qBAAqB;;;;;;;;UAahC;UAYA;EAiCC;EAIA,mBAAmB;EAwBnB;EAIM,WAAW;YAIP,iBAAiB,UAAU;;;;;;;;;;;;cC/UnC;;;;;;;;;SASJ,wBAAwB,UAAU,sBAAsB;;;;;SAqBxD,eAAe,UAAU,sBAAsB,OAAO,KAAK;;;;;SAU3D,eAAe,KAAK,oBAAoB,OAAO,KAAK;;;;SA2EpD,wBACL,UAAU,OAAO,KAAK,8BACtB,wBACC,OAAO,KAAK;;;;cC1HJ;cACA;iBAeG,kCAAkC"}
|
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.85",
|
|
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.85",
|
|
85
|
+
"@robota-sdk/agent-provider-openai-compatible": "3.0.0-beta.85"
|
|
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
|
});
|
package/src/openai/types.ts
CHANGED
|
@@ -208,26 +208,18 @@ export interface IOpenAIProviderOptions {
|
|
|
208
208
|
/**
|
|
209
209
|
* Optional executor for handling AI requests
|
|
210
210
|
*
|
|
211
|
-
* When provided, the provider
|
|
212
|
-
*
|
|
211
|
+
* When provided, the provider delegates every chat call to this executor instead of calling the
|
|
212
|
+
* API itself — for example to route calls through your own server. Implement `IExecutor` from
|
|
213
|
+
* `@robota-sdk/agent-core`; `LocalExecutor` is the in-process one.
|
|
213
214
|
*
|
|
214
215
|
* @example
|
|
215
216
|
* ```typescript
|
|
216
|
-
* import { LocalExecutor
|
|
217
|
+
* import { LocalExecutor } from '@robota-sdk/agent-core';
|
|
217
218
|
*
|
|
218
|
-
*
|
|
219
|
-
*
|
|
220
|
-
* localExecutor.registerProvider('openai', new OpenAIProvider({ apiKey: 'sk-...' }));
|
|
219
|
+
* const executor = new LocalExecutor();
|
|
220
|
+
* executor.registerProvider('openai', new OpenAIProvider({ apiKey: 'sk-...' }));
|
|
221
221
|
*
|
|
222
|
-
*
|
|
223
|
-
* const remoteExecutor = new RemoteExecutor({
|
|
224
|
-
* serverUrl: 'https://api.robota.io',
|
|
225
|
-
* userApiKey: 'user-token-123'
|
|
226
|
-
* });
|
|
227
|
-
*
|
|
228
|
-
* const provider = new OpenAIProvider({
|
|
229
|
-
* executor: remoteExecutor // No direct API key needed
|
|
230
|
-
* });
|
|
222
|
+
* const provider = new OpenAIProvider({ executor });
|
|
231
223
|
* ```
|
|
232
224
|
*/
|
|
233
225
|
executor?: IExecutor;
|