@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 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 | Type | Description |
72
- | -------------------- | ------------------------------------------ | --------------------------------------------------------------------------------------------------------------- |
73
- | `apiKey` | `string` | API key used to create the `openai` client. |
74
- | `baseURL` | `string` | Endpoint base URL (default `https://api.openai.com/v1`). Also switches the default surface to Chat Completions. |
75
- | `apiSurface` | `'responses' \| 'chat-completions'` | Forces the API surface instead of deriving it from `baseURL`. |
76
- | `defaultModel` | `string` | Model used when a call does not name one. |
77
- | `organization` | `string` | OpenAI organization ID. |
78
- | `timeout` | `number` | Request timeout in milliseconds. |
79
- | `strictTools` | `boolean` | Closes tool object schemas and requests OpenAI strict function calling (see above). |
80
- | `responseFormat` | `'text' \| 'json_object' \| 'json_schema'` | Response format; `'json_schema'` uses `jsonSchema`. |
81
- | `jsonSchema` | `IOpenAIJsonSchemaDefinition` | Schema for Structured Outputs when `responseFormat` is `'json_schema'`. |
82
- | `reasoning` | `IOpenAIResponsesReasoningOptions` | Responses API reasoning controls (`effort`, `summary`). |
83
- | `includeStreamUsage` | `boolean` | Requests token usage on streaming Chat Completions turns (default `true`). |
84
- | `client` | `OpenAI` | A pre-built `openai` client, used instead of `apiKey`. |
85
- | `executor` | `IExecutor` | Delegates chat calls to an executor instead of calling the API directly. |
86
- | `payloadLogger` | `IPayloadLogger` | Logs a summary of each Chat Completions request (model, message count, tools present, temperature, max tokens). |
87
- | `logger` | `ILogger` | Logger for internal provider messages (silent by default). |
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
 
@@ -173,26 +173,18 @@ interface IOpenAIProviderOptions {
173
173
  /**
174
174
  * Optional executor for handling AI requests
175
175
  *
176
- * When provided, the provider will delegate all chat operations to this executor
177
- * instead of making direct API calls. This enables remote execution capabilities.
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, RemoteExecutor } from '@robota-sdk/agent-core';
182
+ * import { LocalExecutor } from '@robota-sdk/agent-core';
182
183
  *
183
- * // Local execution (registers this provider)
184
- * const localExecutor = new LocalExecutor();
185
- * localExecutor.registerProvider('openai', new OpenAIProvider({ apiKey: 'sk-...' }));
184
+ * const executor = new LocalExecutor();
185
+ * executor.registerProvider('openai', new OpenAIProvider({ apiKey: 'sk-...' }));
186
186
  *
187
- * // Remote execution
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;;;;;;;;;;;;;;;;;;;;;;;;;;EA2BhB,WAAW;;;;;EAMX,SAAS;;;;;;;;;;;;cC1ME,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"}
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"}
@@ -173,26 +173,18 @@ interface IOpenAIProviderOptions {
173
173
  /**
174
174
  * Optional executor for handling AI requests
175
175
  *
176
- * When provided, the provider will delegate all chat operations to this executor
177
- * instead of making direct API calls. This enables remote execution capabilities.
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, RemoteExecutor } from '@robota-sdk/agent-core';
182
+ * import { LocalExecutor } from '@robota-sdk/agent-core';
182
183
  *
183
- * // Local execution (registers this provider)
184
- * const localExecutor = new LocalExecutor();
185
- * localExecutor.registerProvider('openai', new OpenAIProvider({ apiKey: 'sk-...' }));
184
+ * const executor = new LocalExecutor();
185
+ * executor.registerProvider('openai', new OpenAIProvider({ apiKey: 'sk-...' }));
186
186
  *
187
- * // Remote execution
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.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;;;;;;;;;;;;;;;;;;;;;;;;;;EA2BhB,WAAW;;;;;EAMX,SAAS;;;;;;;;;;;;cC1ME,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"}
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.83",
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.83",
84
- "@robota-sdk/agent-provider-openai-compatible": "3.0.0-beta.83"
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 { ProviderError, RateLimitError, classifyProviderFailure } from '@robota-sdk/agent-core';
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 network`, async () => {
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(ProviderError);
91
+ expect(error).toBeInstanceOf(NetworkError);
86
92
  expect(classifyProviderFailure(error)).toEqual({ switchable: false, reason: 'network' });
87
93
  });
88
94
 
89
- it(`${surface} classifies a 400 model_not_found as model-unavailable`, async () => {
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
  });
@@ -208,26 +208,18 @@ export interface IOpenAIProviderOptions {
208
208
  /**
209
209
  * Optional executor for handling AI requests
210
210
  *
211
- * When provided, the provider will delegate all chat operations to this executor
212
- * instead of making direct API calls. This enables remote execution capabilities.
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, RemoteExecutor } from '@robota-sdk/agent-core';
217
+ * import { LocalExecutor } from '@robota-sdk/agent-core';
217
218
  *
218
- * // Local execution (registers this provider)
219
- * const localExecutor = new LocalExecutor();
220
- * localExecutor.registerProvider('openai', new OpenAIProvider({ apiKey: 'sk-...' }));
219
+ * const executor = new LocalExecutor();
220
+ * executor.registerProvider('openai', new OpenAIProvider({ apiKey: 'sk-...' }));
221
221
  *
222
- * // Remote execution
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;