@alvin0/ai-agent-sdk-provider-openai 0.1.2 → 0.1.4

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/README.md CHANGED
@@ -6,8 +6,12 @@ Compatible gateways can configure `baseUrl`, `models`, `fetch`, and `headers`
6
6
  (a string record or synchronous function returning one per operation).
7
7
  Generation uses Responses; embedding uses the OpenAI embeddings protocol.
8
8
  The embedding adapter/plugin also supports custom headers.
9
- Header names are case-insensitive; collisions and reserved auth/transport headers
10
- are rejected. Supply credentials with `apiKey`. Static records are copied;
9
+ Header names are case-insensitive. A `headers` entry overrides what the SDK
10
+ would otherwise send for that name (`accept`, `content-type`, `user-agent`,
11
+ protocol headers) — only a connection-level name `fetch` itself forbids
12
+ (`host`, `content-length`, …) or a credential-shaped name (must go through
13
+ `apiKey`/`auth` instead, so it can be redacted in logs) stays rejected.
14
+ Supply credentials with `apiKey`. Static records are copied;
11
15
  prepared embedding calls keep the same header snapshot across all batches.
12
16
  Trusted local HTTP gateways require `allowInsecureHttp: true`.
13
17
 
@@ -38,3 +42,77 @@ and precedence rules. These declarations do not increase server-side limits.
38
42
 
39
43
  Composition: `runtime.providers`. Lifecycle: `inert-runtime-owned-registration`;
40
44
  the runtime activates and removes the captured provider registration.
45
+
46
+ ## Connecting a compatible endpoint
47
+
48
+ This package speaks the whole OpenAI API family, not just `api.openai.com`. Two
49
+ wires are supported — `api: 'responses'` (default) and `api: 'chat-completions'`
50
+ — because most third-party OpenAI-compatible endpoints (DeepSeek, Groq, Together,
51
+ Qwen/DashScope, vLLM, Ollama, LM Studio, many gateways) only implement
52
+ `/chat/completions`. Give each vendor its own plugin instance with `id`,
53
+ `baseUrl`, `displayName` (so an error names the real vendor, not "OpenAI"), and
54
+ `api`:
55
+
56
+ ```ts
57
+ import { envCredential } from '@alvin0/ai-agent-sdk-auth-node'
58
+
59
+ openAiPlugin({
60
+ id: 'deepseek',
61
+ displayName: 'DeepSeek',
62
+ baseUrl: 'https://api.deepseek.com',
63
+ api: 'chat-completions',
64
+ apiKey: envCredential('DEEPSEEK_API_KEY'),
65
+ compat: { reasoningFormat: 'deepseek' }, // effort → thinking + reasoning_effort
66
+ })
67
+ ```
68
+
69
+ `compat` exposes the Chat Completions wire knobs one endpoint at a time:
70
+ `reasoningFormat` (`'openai'` default — `reasoning_effort` verbatim — or
71
+ `'deepseek'`, or `false` to send no effort field at all), `maxTokensField`
72
+ (`'max_tokens'` default, or `'max_completion_tokens'`, or `false`),
73
+ `systemRole`, `structuredOutputs`, `tools`, `parallelToolCalls`, `streamUsage`,
74
+ `stop`, `seed`, `promptCacheKey`. Unset knobs keep the protocol's own
75
+ conservative defaults.
76
+
77
+ ## Prompt caching
78
+
79
+ Set `promptCaching: true` to generate one stable `prompt_cache_key` per adapter
80
+ instance, or pass `promptCacheKey` when the application already owns a stable
81
+ session key. The same key is used by Responses and Chat Completions, including a
82
+ mixed per-model route. Keep one adapter/plugin instance scoped to one cache
83
+ identity; do not share an auto-generated key across unrelated tenants.
84
+
85
+ Compatible gateways are handled opportunistically: if a gateway returns a 400
86
+ that specifically names `prompt_cache_key`, the adapter retries that call once
87
+ without the field and remembers the downgrade for the rest of its lifetime.
88
+ Unrelated request errors are never swallowed.
89
+
90
+ An endpoint mounted somewhere other than its protocol's own path (an Azure
91
+ OpenAI deployment, a gateway that rewrites the route) can override `path` and
92
+ add `query` (never for secrets — those belong in `auth`/`apiKey`):
93
+
94
+ ```ts
95
+ openAiPlugin({
96
+ id: 'azure-gpt',
97
+ baseUrl: 'https://my-res.openai.azure.com/openai/deployments/gpt-5-6',
98
+ path: '/chat/completions',
99
+ api: 'chat-completions',
100
+ query: { 'api-version': '2026-06-01' },
101
+ apiKey: envCredential('AZURE_KEY'),
102
+ })
103
+ ```
104
+
105
+ ## Default configuration precedence
106
+
107
+ `contextWindow`, `maxTokens`, and `inputModalities` resolve top-to-bottom, first
108
+ match wins: per-call `maxTokens` → **model** (`models[].contextWindow`,
109
+ `models[].maxTokens`, `models[].inputModalities`) → **route**
110
+ (`defaultContextWindow`/`defaultMaxTokens` on this plugin) → **runtime**
111
+ (`createAgentRuntime({ defaults })`) → **SDK constant** (`200_000` context,
112
+ `['text','image','document']` modalities; no default `maxTokens` — unset means
113
+ the field is not sent at all, except Anthropic which requires one). An
114
+ agent-level override (`runtime.agent({ contextWindow, inputModalities })`) is
115
+ not implemented yet; only effort and per-call `maxTokens` are agent/call-scoped
116
+ today. This package's adapter never guesses a vendor's real context window for
117
+ an uncatalogued model id; declare it via `models: [{ id, contextWindow }]` when
118
+ the SDK default doesn't match reality.
package/dist/index.d.ts CHANGED
@@ -1,13 +1,58 @@
1
+ import { ModelAdapter, ModelProviderPlugin, RetryPolicyConfig } from "@alvin0/ai-agent-sdk-core";
1
2
  import { ComposableEmbeddingProviderPlugin, ComposableModelProviderPlugin, CredentialInput, ModelTarget } from "@alvin0/ai-agent-sdk-core/provider";
2
- import { CredentialSource, EmbeddingCatalogModel, HttpModelAdapter, ProviderCatalogModel, ProviderRequestLogger } from "@alvin0/ai-agent-sdk-provider-http";
3
+ import { CredentialSource, EmbeddingCatalogModel, HeaderContext, ProviderCatalogModel, ProviderRequestLogger, ProviderResponseLogger, RequestContext } from "@alvin0/ai-agent-sdk-provider-http";
3
4
  import { ResponsesDialect, openAiResponsesProtocol } from "@alvin0/ai-agent-sdk-protocol-responses";
4
- import { ModelProviderPlugin, RetryPolicyConfig } from "@alvin0/ai-agent-sdk-core";
5
+ import { ChatCompletionsDialect, ChatCompletionsDialect as ChatCompletionsDialect$1, openAiChatCompletionsProtocol } from "@alvin0/ai-agent-sdk-protocol-openai-chat-completions";
5
6
  import { EmbeddingAdapter } from "@alvin0/ai-agent-sdk-core/embedding";
7
+ //#region src/dual-api.d.ts
8
+ /** Which OpenAI wire one exact model id speaks. */
9
+ type OpenAiApi = 'responses' | 'chat-completions';
10
+ //#endregion
6
11
  //#region src/adapter.d.ts
7
12
  /** The OpenAI API base. */
8
13
  declare const OPENAI_BASE_URL = "https://api.openai.com/v1";
9
14
  /** How the API key is obtained. */
10
15
  type OpenAiCredential = CredentialSource;
16
+ /**
17
+ * Endpoint-specific knobs for the Chat Completions wire, exposed only when
18
+ * `api: 'chat-completions'`. Named `compat` because each field exists for one
19
+ * reason: some OpenAI-compatible endpoint needs the field sent differently, or
20
+ * not at all. Unset fields keep this protocol's own conservative defaults.
21
+ */
22
+ interface OpenAiChatCompletionsCompat {
23
+ /**
24
+ * How this endpoint wants to be told how hard to think.
25
+ *
26
+ * Defaults to `'openai'` (`reasoning_effort`, sent verbatim) — the official
27
+ * field, live-verified on `/v1/chat/completions` (see the redesign plan's
28
+ * codex2claudecode probe). `'deepseek'` matches an endpoint that reasons
29
+ * unless told not to. `false` sends nothing regardless of agent effort.
30
+ */
31
+ reasoningFormat?: ChatCompletionsDialect$1['reasoningFormat'];
32
+ /** Name of the output-length field; `false` sends none. */
33
+ maxTokensField?: ChatCompletionsDialect$1['maxTokensField'];
34
+ /** Role the system prompt travels under. */
35
+ systemRole?: ChatCompletionsDialect$1['systemRole'];
36
+ /** `response_format` support. */
37
+ structuredOutputs?: ChatCompletionsDialect$1['structuredOutputs'];
38
+ /** Send `tools` + `tool_choice`. */
39
+ tools?: boolean;
40
+ /** Send `parallel_tool_calls`. */
41
+ parallelToolCalls?: boolean;
42
+ /** Send `stream_options: { include_usage: true }`. */
43
+ streamUsage?: boolean;
44
+ /** Send `stop`. */
45
+ stop?: boolean;
46
+ /** Send `seed`. */
47
+ seed?: boolean;
48
+ /** Prompt/session cache key, when the endpoint accepts one. */
49
+ promptCacheKey?: string;
50
+ }
51
+ /** A catalog entry that can also name which OpenAI wire this exact model speaks. */
52
+ interface OpenAiCatalogModel extends ProviderCatalogModel {
53
+ /** Overrides the route's own `api` for this one model id. */
54
+ api?: OpenAiApi;
55
+ }
11
56
  /** Options for {@link openAiAdapter}. */
12
57
  interface OpenAiAdapterOptions {
13
58
  /** Injected API key or resolver. Universal packages never read environment variables. */
@@ -18,24 +63,78 @@ interface OpenAiAdapterOptions {
18
63
  * Point this at a compatible gateway to reuse this provider wholesale.
19
64
  */
20
65
  baseUrl?: string;
66
+ /**
67
+ * Name this endpoint uses in diagnostics and error messages. Defaults to
68
+ * `'OpenAI'`; set it to the real vendor name (e.g. `'DeepSeek'`) when pointing
69
+ * this provider at a compatible gateway, so a rejection names who rejected it.
70
+ */
71
+ displayName?: string;
21
72
  /** Extra endpoint headers, captured once per operation. Reserved names and collisions fail. */
22
- headers?: Readonly<Record<string, string>> | (() => Readonly<Record<string, string>>);
73
+ headers?: Readonly<Record<string, string>> | ((ctx: HeaderContext) => Readonly<Record<string, string>>);
23
74
  /** Permit cleartext HTTP explicitly for trusted local gateways. */
24
75
  allowInsecureHttp?: boolean;
76
+ /** Override the request path this protocol would otherwise pick (e.g. an Azure deployment path). */
77
+ path?: string;
78
+ /** Extra query-string parameters, or a resolver for them (e.g. Azure's `api-version`). Never for secrets. */
79
+ query?: Readonly<Record<string, string>> | (() => Readonly<Record<string, string>>);
80
+ /**
81
+ * Fields to deep-merge into the serialized body. The caller's value always
82
+ * wins, even over a field the SDK set. A `null` value deletes the field.
83
+ */
84
+ body?: Readonly<Record<string, unknown>>;
85
+ /** Last-resort hook with full authority over the body, run after `body` is merged in. */
86
+ transformRequest?: (body: unknown, ctx: RequestContext) => unknown;
25
87
  /** Organization to bill, when the key belongs to several. */
26
88
  organization?: string;
27
89
  /** Project to attribute usage to. */
28
90
  project?: string;
91
+ /**
92
+ * Which OpenAI wire this endpoint speaks. Defaults to `'responses'`.
93
+ *
94
+ * Most third-party OpenAI-compatible endpoints (DeepSeek, Groq, Together,
95
+ * Qwen/DashScope, vLLM, Ollama, LM Studio, many gateways) only implement
96
+ * `/chat/completions` — set `'chat-completions'` to reach those.
97
+ */
98
+ api?: 'responses' | 'chat-completions';
99
+ /** Chat Completions wire knobs. Ignored unless `api: 'chat-completions'`. */
100
+ compat?: OpenAiChatCompletionsCompat;
29
101
  /**
30
102
  * Advisory model catalog.
31
103
  *
32
104
  * Empty by default: this package cannot know which model ids are current, and a
33
105
  * stale built-in list would name retired models. Supply entries to declare
34
106
  * capabilities the SDK cannot infer, such as image support.
107
+ *
108
+ * `api` on an entry lets ONE route serve both OpenAI wires, model by model —
109
+ * a gateway that fronts both classic chat models (only on `/chat/completions`)
110
+ * and newer reasoning models (only on `/responses`), for instance. Omitted,
111
+ * an entry follows the route's own `api`.
35
112
  */
36
- models?: readonly ProviderCatalogModel[];
113
+ models?: readonly OpenAiCatalogModel[];
37
114
  /** Whether the provider may retain responses server-side. Defaults to false. */
38
115
  store?: boolean;
116
+ /**
117
+ * Stable key letting the provider route a session's calls to the same
118
+ * cached-prefix-warm backend, cutting cost and latency on a long
119
+ * conversation that resends its own history every turn. Applies to
120
+ * whichever wire is active — Responses or Chat Completions — and, for a
121
+ * mixed route (`models[].api`), to both.
122
+ *
123
+ * Leave unset and set {@link promptCaching} instead to have the SDK invent
124
+ * one per adapter instance (one per conversation, in the common case of
125
+ * building a fresh registry per session) rather than naming your own.
126
+ */
127
+ promptCacheKey?: string;
128
+ /**
129
+ * Auto-generate a stable {@link promptCacheKey} when none is given.
130
+ *
131
+ * Off by default: not every account or OpenAI-COMPATIBLE gateway behind
132
+ * this adapter understands `prompt_cache_key`, and a route that doesn't
133
+ * should not silently be asked to guess. If a live dispatch is ever
134
+ * rejected specifically for it, this adapter turns caching off for
135
+ * itself, permanently, and retries once without it.
136
+ */
137
+ promptCaching?: boolean;
39
138
  /** Output cap when neither caller nor catalog names one. */
40
139
  defaultMaxTokens?: number;
41
140
  /** Context capacity assumed for an uncatalogued model. */
@@ -54,14 +153,23 @@ interface OpenAiAdapterOptions {
54
153
  retryPolicy?: RetryPolicyConfig;
55
154
  /** Optional exact wire-request logger; credentials are redacted. */
56
155
  requestLogger?: ProviderRequestLogger;
156
+ /** Optional exact wire-response logger, fired once a stream ends. */
157
+ responseLogger?: ProviderResponseLogger;
57
158
  fetch?: typeof globalThis.fetch;
58
159
  }
59
160
  /**
60
161
  * Create an OpenAI adapter.
162
+ *
163
+ * One route can serve BOTH OpenAI wires at once: if any `models[]` entry
164
+ * names an `api` different from the route's own, this returns a facade
165
+ * ({@link OpenAiDualApiAdapter}) holding one Responses adapter and one Chat
166
+ * Completions adapter, delegating each call by model id. Otherwise — the
167
+ * common case — this is a single `createHttpProvider` adapter, unchanged
168
+ * from before per-model `api` existed.
61
169
  * @param options - credential, endpoint, and catalog overrides.
62
170
  * @returns the adapter, ready to register.
63
171
  */
64
- declare function openAiAdapter(options: OpenAiAdapterOptions): HttpModelAdapter;
172
+ declare function openAiAdapter(options: OpenAiAdapterOptions): ModelAdapter;
65
173
  interface OpenAiPluginOptions extends OpenAiAdapterOptions {
66
174
  /** Registry routes installed by the plugin. Defaults to `['openai']`. */
67
175
  readonly routes?: readonly string[];
@@ -140,5 +248,5 @@ declare function openAiEmbeddingPlugin(options: OpenAiEmbeddingProviderOptions):
140
248
  readonly family: 'openai';
141
249
  };
142
250
  //#endregion
143
- export { OPENAI_BASE_URL, type OpenAiAdapterOptions, type OpenAiCredential, type OpenAiEmbeddingProviderOptions, type OpenAiPluginOptions, type OpenAiProviderOptions, type ResponsesDialect, openAiAdapter, openAiEmbeddingAdapter, openAiEmbeddingPlugin, openAiPlugin, openAiResponsesProtocol };
251
+ export { type ChatCompletionsDialect, OPENAI_BASE_URL, type OpenAiAdapterOptions, type OpenAiApi, type OpenAiCatalogModel, type OpenAiChatCompletionsCompat, type OpenAiCredential, type OpenAiEmbeddingProviderOptions, type OpenAiPluginOptions, type OpenAiProviderOptions, type ResponsesDialect, openAiAdapter, openAiChatCompletionsProtocol, openAiEmbeddingAdapter, openAiEmbeddingPlugin, openAiPlugin, openAiResponsesProtocol };
144
252
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/adapter.ts","../src/embedding.ts"],"mappings":";;;;;;;cAoCa;;KAGD,mBAAmB;;UAGd;;EAEf,QAAQ;;;;;;EAMR;;EAEA,UAAU,SAAS,iCAAiC,SAAS;;EAE7D;;EAEA;;EAEA;;;;;;;;EAQA,kBAAkB;;EAElB;;EAEA;;EAEA;;EAEA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;EAEA,cAAc;;EAEd,gBAAgB;EAChB,eAAe,WAAW;;;;;;;iBAQZ,cAAc,SAAS,uBAAuB;UAkC7C,4BAA4B;;WAElC;;UAGM,8BAA8B,KAAK;WACzC,wBAAwB;WACxB,QAAQ;WACR;WACA;;;iBAIK,aACd,SAAS,wBACR;WAA2C;;iBAC9B,aAAa,SAAS,sBAAsB;;;;UC9B3C;;WAEN,QAAQ;;WAER,UAAU,SAAS,iCAAiC,SAAS;;;;;;;;WAQ7D;;WAEA;;WAEA;;WAEA;;WAEA;;;;;;;;;WASA,kBAAkB;;WAElB;WACA;WACA;WACA;WACA;WACA;WACA;;WAEA,cAAc;WACd,eAAe,WAAW;;;;;;;iBAkcrB,uBACd,SAAS,iCACR;;;;;;;;;iBAYa,sBACd,SAAS,iCACR;WAA+C"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/dual-api.ts","../src/adapter.ts","../src/embedding.ts"],"mappings":";;;;;;;;KAyCY;;;;cCIC;;KAGD,mBAAmB;;;;;;;UAQd;;;;;;;;;EASf,kBAAkB;;EAElB,iBAAiB;;EAEjB,aAAa;;EAEb,oBAAoB;;EAEpB;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;;UAIe,2BAA2B;;EAE1C,MAAM;;;UAIS;;EAEf,QAAQ;;;;;;EAMR;;;;;;EAMA;;EAEA,UAAU,SAAS,4BAA4B,KAAK,kBAAkB,SAAS;;EAE/E;;EAEA;;EAEA,QAAQ,SAAS,iCAAiC,SAAS;;;;;EAK3D,OAAO,SAAS;;EAEhB,oBAAoB,eAAe,KAAK;;EAExC;;EAEA;;;;;;;;EAQA;;EAEA,SAAS;;;;;;;;;;;;;EAaT,kBAAkB;;EAElB;;;;;;;;;;;;EAYA;;;;;;;;;;EAUA;;EAEA;;EAEA;;EAEA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;EAEA,cAAc;;EAEd,gBAAgB;;EAEhB,iBAAiB;EACjB,eAAe,WAAW;;;;;;;;;;;;;;iBAeZ,cAAc,SAAS,uBAAuB;UAkK7C,4BAA4B;;WAElC;;UAGM,8BAA8B,KAAK;WACzC,wBAAwB;WACxB,QAAQ;WACR;WACA;;;iBAIK,aACd,SAAS,wBACR;WAA2C;;iBAC9B,aAAa,SAAS,sBAAsB;;;;UC/Q3C;;WAEN,QAAQ;;WAER,UAAU,SAAS,iCAAiC,SAAS;;;;;;;;WAQ7D;;WAEA;;WAEA;;WAEA;;WAEA;;;;;;;;;WASA,kBAAkB;;WAElB;WACA;WACA;WACA;WACA;WACA;WACA;;WAEA,cAAc;WACd,eAAe,WAAW;;;;;;;iBAucrB,uBACd,SAAS,iCACR;;;;;;;;;iBAYa,sBACd,SAAS,iCACR;WAA+C"}
package/dist/index.js CHANGED
@@ -1,22 +1,86 @@
1
+ import { ModelAdapter, ModelError, resolveRetryPolicy } from "@alvin0/ai-agent-sdk-core";
1
2
  import { defineEmbeddingProviderPlugin, defineModelProviderPlugin } from "@alvin0/ai-agent-sdk-core/provider";
2
- import { captureTransportConnection, createHttpProvider, createModelContextPolicy, createRuntimeHttpProvider, embeddingCatalogModelInfo, endpointHeaders, resolvedEmbeddingCatalogModelInfo, transportJson } from "@alvin0/ai-agent-sdk-provider-http";
3
+ import { FieldFallbackAdapter, captureTransportConnection, createHttpProvider, createRuntimeHttpProvider, embeddingCatalogModelInfo, endpointHeaders, resolvedEmbeddingCatalogModelInfo, transportJson } from "@alvin0/ai-agent-sdk-provider-http";
3
4
  import { openAiResponsesProtocol, openAiResponsesProtocol as openAiResponsesProtocol$1 } from "@alvin0/ai-agent-sdk-protocol-responses";
4
- import { resolveRetryPolicy } from "@alvin0/ai-agent-sdk-core";
5
+ import { openAiChatCompletionsProtocol, openAiChatCompletionsProtocol as openAiChatCompletionsProtocol$1 } from "@alvin0/ai-agent-sdk-protocol-openai-chat-completions";
5
6
  import { EMBEDDING_ERROR_CODES, EmbeddingAdapter, EmbeddingError, defaultEmbeddingProfile, deriveSpaceId, resolveBatchLimits, validateBatchResult } from "@alvin0/ai-agent-sdk-core/embedding";
6
7
 
7
- //#region src/context-policy.ts
8
- const GPT_56 = {
9
- defaultContextWindow: 272e3,
10
- maxContextWindow: 105e4,
11
- standardPriceInputTokens: 272e3
8
+ //#region src/dual-api.ts
9
+ /**
10
+ * One route, two OpenAI wires: Responses for some models, Chat Completions for
11
+ * others — chosen per model id, from `models[].api`.
12
+ *
13
+ * ## Why a delegating adapter rather than a composite protocol
14
+ *
15
+ * `provider-copilot`'s `copilotDualProtocol` solves the same shape of problem
16
+ * (Requirement 9 there) by building one `RuntimeWireProtocol` whose
17
+ * `endpointPath`/`serialize`/`translate` each branch on `request.model.id`, so
18
+ * a single `HttpModelAdapter` instance still owns one shared connection.
19
+ *
20
+ * That approach earns its complexity when the two wires must share one
21
+ * connection (Copilot's device-code credential, one client identity for
22
+ * both). Here they do not: Responses and Chat Completions each already have
23
+ * their own complete, independently-tested `createHttpProvider` wiring in
24
+ * `./adapter.ts`, and `ModelAdapter` (`@alvin0/ai-agent-sdk-core`) is an
25
+ * abstract class with exactly one required method (`stream`) and sensible
26
+ * defaults for the rest — built specifically so "one adapter instance can
27
+ * serve many routes" (see its own module doc). A facade that HOLDS two
28
+ * complete adapters and delegates each call by model id reuses both wires
29
+ * exactly as built, with no new protocol-level branching to keep in sync
30
+ * with either one's own evolution.
31
+ *
32
+ * @module ai-agent-sdk/providers/openai/dual-api
33
+ */
34
+ /**
35
+ * Delegates every call to whichever underlying adapter the model id maps to,
36
+ * falling back to `defaultApi` for an id neither catalog named explicitly.
37
+ */
38
+ var OpenAiDualApiAdapter = class extends ModelAdapter {
39
+ #responses;
40
+ #chat;
41
+ #apiOf;
42
+ #defaultApi;
43
+ constructor(responses, chat, apiOf, defaultApi) {
44
+ super();
45
+ this.#responses = responses;
46
+ this.#chat = chat;
47
+ this.#apiOf = apiOf;
48
+ this.#defaultApi = defaultApi;
49
+ }
50
+ #adapterFor(model) {
51
+ return (this.#apiOf.get(model) ?? this.#defaultApi) === "chat-completions" ? this.#chat : this.#responses;
52
+ }
53
+ providerInfo(provider) {
54
+ return this.#responses.providerInfo(provider);
55
+ }
56
+ providerRetryPolicy(provider) {
57
+ return this.#responses.providerRetryPolicy(provider);
58
+ }
59
+ async listModels(provider, signal) {
60
+ const [responsesModels, chatModels] = await Promise.all([this.#responses.listModels(provider, signal), this.#chat.listModels(provider, signal)]);
61
+ return Object.freeze([...responsesModels, ...chatModels]);
62
+ }
63
+ async modelCatalog(provider, options = {}) {
64
+ const [responsesCatalog, chatCatalog] = await Promise.all([this.#responses.modelCatalog(provider, options), this.#chat.modelCatalog(provider, options)]);
65
+ const models = Object.freeze([...responsesCatalog.models, ...chatCatalog.models]);
66
+ return Object.freeze({
67
+ provider: responsesCatalog.provider,
68
+ state: models.length === 0 ? "empty" : "fresh",
69
+ revision: `dual-api:${responsesCatalog.revision}+${chatCatalog.revision}`,
70
+ models,
71
+ observedAt: (/* @__PURE__ */ new Date()).toISOString()
72
+ });
73
+ }
74
+ resolveModel(provider, model, signal) {
75
+ return this.#adapterFor(model).resolveModel(provider, model, signal);
76
+ }
77
+ prepareCall(provider, model, signal, context) {
78
+ return this.#adapterFor(model).prepareCall(provider, model, signal, context);
79
+ }
80
+ stream(options, context) {
81
+ return this.#adapterFor(options.model).stream(options, context);
82
+ }
12
83
  };
13
- function openAiContextPolicy(options) {
14
- return createModelContextPolicy(options.baseUrl === void 0 || options.baseUrl.replace(/\/$/, "") === "https://api.openai.com/v1" ? {
15
- "gpt-5.6-luna": GPT_56,
16
- "gpt-5.6-sol": GPT_56,
17
- "gpt-5.6-terra": GPT_56
18
- } : {}, options.models, options.defaultContextWindow);
19
- }
20
84
 
21
85
  //#endregion
22
86
  //#region src/adapter.ts
@@ -24,35 +88,124 @@ function openAiContextPolicy(options) {
24
88
  const OPENAI_BASE_URL = "https://api.openai.com/v1";
25
89
  /**
26
90
  * Create an OpenAI adapter.
91
+ *
92
+ * One route can serve BOTH OpenAI wires at once: if any `models[]` entry
93
+ * names an `api` different from the route's own, this returns a facade
94
+ * ({@link OpenAiDualApiAdapter}) holding one Responses adapter and one Chat
95
+ * Completions adapter, delegating each call by model id. Otherwise — the
96
+ * common case — this is a single `createHttpProvider` adapter, unchanged
97
+ * from before per-model `api` existed.
27
98
  * @param options - credential, endpoint, and catalog overrides.
28
99
  * @returns the adapter, ready to register.
29
100
  */
30
101
  function openAiAdapter(options) {
31
- const dialect = options.store === void 0 ? {} : { store: options.store };
32
- return createHttpProvider({
33
- displayName: "OpenAI",
34
- describeModel: openAiContextPolicy(options),
35
- protocol: openAiResponsesProtocol$1,
102
+ const effectiveKey = effectivePromptCacheKey(options);
103
+ const primary = buildOpenAiAdapterTree(options, effectiveKey);
104
+ if (effectiveKey === void 0) return primary;
105
+ return new FieldFallbackAdapter(primary, buildOpenAiAdapterTree(options, false), { isFieldRejection: isPromptCacheKeyRejection });
106
+ }
107
+ /**
108
+ * Resolve the key every wire this route serves should share, so a mixed
109
+ * route (`models[].api`) still routes both wires' calls to one session.
110
+ * Explicit settings win over auto-generation, and the newer top-level
111
+ * option wins over the older Chat-Completions-only `compat` one.
112
+ */
113
+ function effectivePromptCacheKey(options) {
114
+ return options.promptCacheKey ?? options.compat?.promptCacheKey ?? (options.promptCaching === true ? randomId() : void 0);
115
+ }
116
+ function randomId() {
117
+ return globalThis.crypto?.randomUUID?.() ?? `sdk-${Date.now().toString(36)}`;
118
+ }
119
+ /**
120
+ * Recognize a dispatch rejection caused specifically by `prompt_cache_key` —
121
+ * the one signal {@link FieldFallbackAdapter} is allowed to react to. Scoped
122
+ * narrowly (a 400 whose message names the field) rather than treating every
123
+ * 400 as a reason to give up on caching, which would mask a real, unrelated
124
+ * request error behind a silent feature downgrade.
125
+ */
126
+ function isPromptCacheKeyRejection(error) {
127
+ return error instanceof ModelError && error.failure.status === 400 && error.message.toLowerCase().includes("prompt_cache_key");
128
+ }
129
+ /**
130
+ * One route can serve BOTH OpenAI wires at once: if any `models[]` entry
131
+ * names an `api` different from the route's own, this returns a facade
132
+ * ({@link OpenAiDualApiAdapter}) holding one Responses adapter and one Chat
133
+ * Completions adapter, delegating each call by model id. Otherwise — the
134
+ * common case — this is a single `createHttpProvider` adapter.
135
+ * @param promptCacheKeyOverride - `false` forces caching off regardless of
136
+ * `options`, for {@link FieldFallbackAdapter}'s fallback build.
137
+ */
138
+ function buildOpenAiAdapterTree(options, promptCacheKeyOverride) {
139
+ const defaultApi = options.api ?? "responses";
140
+ const models = options.models;
141
+ if (!(models?.some((entry) => entry.api !== void 0 && entry.api !== defaultApi) ?? false)) return buildOpenAiApiAdapter(options, defaultApi, models, promptCacheKeyOverride);
142
+ const apiOf = new Map((models ?? []).map((entry) => [entry.id, entry.api ?? defaultApi]));
143
+ const responsesModels = (models ?? []).filter((entry) => (entry.api ?? defaultApi) === "responses");
144
+ const chatModels = (models ?? []).filter((entry) => (entry.api ?? defaultApi) === "chat-completions");
145
+ return new OpenAiDualApiAdapter(buildOpenAiApiAdapter(options, "responses", responsesModels, promptCacheKeyOverride), buildOpenAiApiAdapter(options, "chat-completions", chatModels, promptCacheKeyOverride), apiOf, defaultApi);
146
+ }
147
+ function buildOpenAiApiAdapter(options, api, models, promptCacheKeyOverride) {
148
+ const shared = {
36
149
  baseUrl: options.baseUrl ?? "https://api.openai.com/v1",
37
150
  auth: {
38
151
  kind: "bearer",
39
152
  token: options.apiKey,
40
153
  label: "the `apiKey` option"
41
154
  },
42
- dialect,
43
155
  headers: endpointHeaders(options.headers, {
44
156
  ...options.organization === void 0 ? {} : { "openai-organization": options.organization },
45
157
  ...options.project === void 0 ? {} : { "openai-project": options.project }
46
158
  }),
47
- ...options.models === void 0 ? {} : { models: options.models },
48
- defaultMaxTokens: options.defaultMaxTokens ?? 32e3,
49
- defaultContextWindow: options.defaultContextWindow ?? 128e3,
159
+ ...options.path === void 0 ? {} : { path: options.path },
160
+ ...options.query === void 0 ? {} : { query: options.query },
161
+ ...options.body === void 0 ? {} : { body: options.body },
162
+ ...options.transformRequest === void 0 ? {} : { transformRequest: options.transformRequest },
163
+ ...models === void 0 ? {} : { models },
164
+ ...options.defaultMaxTokens === void 0 ? {} : { defaultMaxTokens: options.defaultMaxTokens },
165
+ ...options.defaultContextWindow === void 0 ? {} : { defaultContextWindow: options.defaultContextWindow },
50
166
  ...options.streamIdleTimeoutMs === void 0 ? {} : { streamIdleTimeoutMs: options.streamIdleTimeoutMs },
51
167
  ...transportLimits(options),
52
168
  ...options.retryPolicy === void 0 ? {} : { retryPolicy: options.retryPolicy },
53
- ...options.requestLogger === void 0 ? {} : { requestLogger: options.requestLogger }
169
+ ...options.requestLogger === void 0 ? {} : { requestLogger: options.requestLogger },
170
+ ...options.responseLogger === void 0 ? {} : { responseLogger: options.responseLogger }
171
+ };
172
+ const promptCacheKey = promptCacheKeyOverride === false ? void 0 : promptCacheKeyOverride;
173
+ if (api === "chat-completions") return createHttpProvider({
174
+ displayName: options.displayName ?? "OpenAI",
175
+ protocol: openAiChatCompletionsProtocol$1,
176
+ dialect: chatCompletionsDialectOf(options.compat, promptCacheKey),
177
+ ...shared
178
+ });
179
+ return createHttpProvider({
180
+ displayName: options.displayName ?? "OpenAI",
181
+ protocol: openAiResponsesProtocol$1,
182
+ dialect: {
183
+ ...options.store === void 0 ? {} : { store: options.store },
184
+ ...promptCacheKey === void 0 ? {} : { promptCacheKey }
185
+ },
186
+ ...shared
54
187
  });
55
188
  }
189
+ const CHAT_COMPLETIONS_REASONING_FORMATS = /* @__PURE__ */ new Set([
190
+ "openai",
191
+ "deepseek",
192
+ false
193
+ ]);
194
+ function chatCompletionsDialectOf(compat, promptCacheKey) {
195
+ if (compat?.reasoningFormat !== void 0 && !CHAT_COMPLETIONS_REASONING_FORMATS.has(compat.reasoningFormat)) throw new TypeError(`Chat Completions reasoningFormat must be 'openai', 'deepseek', or false, received ${JSON.stringify(compat.reasoningFormat)}`);
196
+ return {
197
+ reasoningFormat: compat?.reasoningFormat ?? "openai",
198
+ ...compat?.maxTokensField === void 0 ? {} : { maxTokensField: compat.maxTokensField },
199
+ ...compat?.systemRole === void 0 ? {} : { systemRole: compat.systemRole },
200
+ ...compat?.structuredOutputs === void 0 ? {} : { structuredOutputs: compat.structuredOutputs },
201
+ ...compat?.tools === void 0 ? {} : { tools: compat.tools },
202
+ ...compat?.parallelToolCalls === void 0 ? {} : { parallelToolCalls: compat.parallelToolCalls },
203
+ ...compat?.streamUsage === void 0 ? {} : { streamUsage: compat.streamUsage },
204
+ ...compat?.stop === void 0 ? {} : { stop: compat.stop },
205
+ ...compat?.seed === void 0 ? {} : { seed: compat.seed },
206
+ ...promptCacheKey === void 0 ? {} : { promptCacheKey }
207
+ };
208
+ }
56
209
  function openAiPlugin(options) {
57
210
  if (!usesRuntimeComposition(options)) return legacyOpenAiPlugin(options);
58
211
  const id = options.id ?? "openai";
@@ -60,7 +213,7 @@ function openAiPlugin(options) {
60
213
  return defineModelProviderPlugin({
61
214
  id,
62
215
  family: "openai",
63
- displayName: "OpenAI",
216
+ displayName: options.displayName ?? "OpenAI",
64
217
  routes,
65
218
  ...runtimeDefaultModel(options.defaultModel, routes),
66
219
  setup(registrar) {
@@ -77,35 +230,67 @@ function legacyOpenAiPlugin(options) {
77
230
  const adapter = openAiAdapter(options);
78
231
  return Object.freeze({
79
232
  id: "openai",
80
- displayName: "OpenAI",
233
+ displayName: options.displayName ?? "OpenAI",
81
234
  setup(registrar) {
82
235
  registrar.registerAdapter(routes, adapter);
83
236
  }
84
237
  });
85
238
  }
86
239
  function createRuntimeOpenAiAdapter(options) {
87
- return createRuntimeHttpProvider({
88
- describeModel: openAiContextPolicy(options),
89
- displayName: "OpenAI",
90
- protocol: openAiResponsesProtocol$1,
240
+ const effectiveKey = effectivePromptCacheKey(options);
241
+ const primary = buildRuntimeOpenAiAdapterTree(options, effectiveKey);
242
+ if (effectiveKey === void 0) return primary;
243
+ return new FieldFallbackAdapter(primary, buildRuntimeOpenAiAdapterTree(options, false), { isFieldRejection: isPromptCacheKeyRejection });
244
+ }
245
+ function buildRuntimeOpenAiAdapterTree(options, promptCacheKeyOverride) {
246
+ const defaultApi = options.api ?? "responses";
247
+ const models = options.models;
248
+ if (!(models?.some((entry) => entry.api !== void 0 && entry.api !== defaultApi) ?? false)) return buildRuntimeOpenAiApiAdapter(options, defaultApi, models, promptCacheKeyOverride);
249
+ const apiOf = new Map((models ?? []).map((entry) => [entry.id, entry.api ?? defaultApi]));
250
+ const responsesModels = (models ?? []).filter((entry) => (entry.api ?? defaultApi) === "responses");
251
+ const chatModels = (models ?? []).filter((entry) => (entry.api ?? defaultApi) === "chat-completions");
252
+ return new OpenAiDualApiAdapter(buildRuntimeOpenAiApiAdapter(options, "responses", responsesModels, promptCacheKeyOverride), buildRuntimeOpenAiApiAdapter(options, "chat-completions", chatModels, promptCacheKeyOverride), apiOf, defaultApi);
253
+ }
254
+ function buildRuntimeOpenAiApiAdapter(options, api, models, promptCacheKeyOverride) {
255
+ const shared = {
91
256
  baseUrl: options.baseUrl ?? "https://api.openai.com/v1",
92
257
  auth: {
93
258
  kind: "bearer",
94
259
  token: options.apiKey,
95
260
  label: "the `apiKey` option"
96
261
  },
97
- dialect: options.store === void 0 ? {} : { store: options.store },
98
262
  headers: endpointHeaders(options.headers, {
99
263
  ...options.organization === void 0 ? {} : { "openai-organization": options.organization },
100
264
  ...options.project === void 0 ? {} : { "openai-project": options.project }
101
265
  }),
102
- ...options.models === void 0 ? {} : { models: options.models },
103
- defaultMaxTokens: options.defaultMaxTokens ?? 32e3,
104
- defaultContextWindow: options.defaultContextWindow ?? 128e3,
266
+ ...options.path === void 0 ? {} : { path: options.path },
267
+ ...options.query === void 0 ? {} : { query: options.query },
268
+ ...options.body === void 0 ? {} : { body: options.body },
269
+ ...options.transformRequest === void 0 ? {} : { transformRequest: options.transformRequest },
270
+ ...models === void 0 ? {} : { models },
271
+ ...options.defaultMaxTokens === void 0 ? {} : { defaultMaxTokens: options.defaultMaxTokens },
272
+ ...options.defaultContextWindow === void 0 ? {} : { defaultContextWindow: options.defaultContextWindow },
105
273
  ...options.streamIdleTimeoutMs === void 0 ? {} : { streamIdleTimeoutMs: options.streamIdleTimeoutMs },
106
274
  ...transportLimits(options),
107
275
  ...options.retryPolicy === void 0 ? {} : { retryPolicy: options.retryPolicy },
108
- ...options.requestLogger === void 0 ? {} : { requestLogger: options.requestLogger }
276
+ ...options.requestLogger === void 0 ? {} : { requestLogger: options.requestLogger },
277
+ ...options.responseLogger === void 0 ? {} : { responseLogger: options.responseLogger }
278
+ };
279
+ const promptCacheKey = promptCacheKeyOverride === false ? void 0 : promptCacheKeyOverride;
280
+ if (api === "chat-completions") return createRuntimeHttpProvider({
281
+ displayName: options.displayName ?? "OpenAI",
282
+ protocol: openAiChatCompletionsProtocol$1,
283
+ dialect: chatCompletionsDialectOf(options.compat, promptCacheKey),
284
+ ...shared
285
+ });
286
+ return createRuntimeHttpProvider({
287
+ displayName: options.displayName ?? "OpenAI",
288
+ protocol: openAiResponsesProtocol$1,
289
+ dialect: {
290
+ ...options.store === void 0 ? {} : { store: options.store },
291
+ ...promptCacheKey === void 0 ? {} : { promptCacheKey }
292
+ },
293
+ ...shared
109
294
  });
110
295
  }
111
296
  function usesRuntimeComposition(options) {
@@ -273,7 +458,7 @@ var OpenAiEmbeddingAdapter = class extends EmbeddingAdapter {
273
458
  * went out under another (Requirements 2.2, 2.3, 2.4).
274
459
  */
275
460
  async prepareEmbeddingCall(provider, model, options, signal, context) {
276
- const connection = await this.connect(signal, context);
461
+ const connection = await this.connect(provider, signal, context);
277
462
  const resolved = resolvedEmbeddingCatalogModelInfo(provider, model, connection.models);
278
463
  const profile = this.embeddingProfile(resolved, options);
279
464
  return Object.freeze({
@@ -292,13 +477,17 @@ var OpenAiEmbeddingAdapter = class extends EmbeddingAdapter {
292
477
  * guards.
293
478
  */
294
479
  async embedBatch(batch, context) {
295
- const connection = await this.connect(batch.signal, context);
480
+ const connection = await this.connect(batch.provider, batch.signal, context);
296
481
  const model = resolvedEmbeddingCatalogModelInfo(batch.provider, batch.model, connection.models);
297
482
  return this.dispatch(connection, model, batch, context);
298
483
  }
299
484
  /** Everything one embedding request needs, read together, once per operation. */
300
- async connect(signal, context) {
301
- const extraHeaders = this.headers();
485
+ async connect(provider, signal, context) {
486
+ const extraHeaders = this.headers({
487
+ provider,
488
+ ...context?.agentId === void 0 ? {} : { agentId: context.agentId },
489
+ ...signal === void 0 ? {} : { signal }
490
+ });
302
491
  const token = await resolveApiKey(this.options.apiKey, signal, context);
303
492
  const headers = {
304
493
  ...extraHeaders,
@@ -544,5 +733,5 @@ function openAiEmbeddingPlugin(options) {
544
733
  }
545
734
 
546
735
  //#endregion
547
- export { OPENAI_BASE_URL, openAiAdapter, openAiEmbeddingAdapter, openAiEmbeddingPlugin, openAiPlugin, openAiResponsesProtocol };
736
+ export { OPENAI_BASE_URL, openAiAdapter, openAiChatCompletionsProtocol, openAiEmbeddingAdapter, openAiEmbeddingPlugin, openAiPlugin, openAiResponsesProtocol };
548
737
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["openAiResponsesProtocol"],"sources":["../src/context-policy.ts","../src/adapter.ts","../src/embedding.ts"],"sourcesContent":["import { createModelContextPolicy } from '@alvin0/ai-agent-sdk-provider-http'\nimport type { ModelContextPolicyOptions } from '@alvin0/ai-agent-sdk-provider-http'\n\n// Verified 2026-09-13: https://developers.openai.com/api/docs/models/gpt-5.6-luna\n// https://developers.openai.com/api/docs/models/gpt-5.6-sol\n// https://developers.openai.com/api/docs/models/gpt-5.6-terra\nconst GPT_56 = { defaultContextWindow: 272_000, maxContextWindow: 1_050_000, standardPriceInputTokens: 272_000 }\nexport function openAiContextPolicy(options: ModelContextPolicyOptions) {\n return createModelContextPolicy(\n options.baseUrl === undefined || options.baseUrl.replace(/\\/$/, '') === 'https://api.openai.com/v1'\n ? { 'gpt-5.6-luna': GPT_56, 'gpt-5.6-sol': GPT_56, 'gpt-5.6-terra': GPT_56 }\n : {}, options.models, options.defaultContextWindow)\n}\n","/**\n * The OpenAI provider: the Responses API on `api.openai.com`.\n *\n * Note how little there is here. Every endpoint fact is configuration handed to\n * {@link createHttpProvider}; the protocol, the pipeline, and the error mapping are\n * all shared. That is the intended shape for any endpoint speaking a protocol this\n * package already implements — including your own gateway.\n *\n * @module ai-agent-sdk/providers/openai/adapter\n */\n\nimport type { ModelProviderPlugin, ModelProviderRegistrar, RetryPolicyConfig } from '@alvin0/ai-agent-sdk-core'\nimport {\n defineModelProviderPlugin,\n type ComposableModelProviderPlugin,\n type CredentialInput,\n type ModelTarget,\n} from '@alvin0/ai-agent-sdk-core/provider'\nimport type {\n HttpModelAdapter,\n ProviderCatalogModel,\n ProviderRequestLogger,\n} from '@alvin0/ai-agent-sdk-provider-http'\nimport {\n createHttpProvider,\n endpointHeaders,\n createRuntimeHttpProvider,\n type CredentialSource,\n} from '@alvin0/ai-agent-sdk-provider-http'\nimport {\n openAiResponsesProtocol,\n type ResponsesDialect,\n} from '@alvin0/ai-agent-sdk-protocol-responses'\nimport { openAiContextPolicy } from './context-policy.ts'\n\n/** The OpenAI API base. */\nexport const OPENAI_BASE_URL = 'https://api.openai.com/v1'\n\n/** How the API key is obtained. */\nexport type OpenAiCredential = CredentialSource\n\n/** Options for {@link openAiAdapter}. */\nexport interface OpenAiAdapterOptions {\n /** Injected API key or resolver. Universal packages never read environment variables. */\n apiKey: OpenAiCredential\n /**\n * Endpoint base; defaults to {@link OPENAI_BASE_URL}.\n *\n * Point this at a compatible gateway to reuse this provider wholesale.\n */\n baseUrl?: string\n /** Extra endpoint headers, captured once per operation. Reserved names and collisions fail. */\n headers?: Readonly<Record<string, string>> | (() => Readonly<Record<string, string>>)\n /** Permit cleartext HTTP explicitly for trusted local gateways. */\n allowInsecureHttp?: boolean\n /** Organization to bill, when the key belongs to several. */\n organization?: string\n /** Project to attribute usage to. */\n project?: string\n /**\n * Advisory model catalog.\n *\n * Empty by default: this package cannot know which model ids are current, and a\n * stale built-in list would name retired models. Supply entries to declare\n * capabilities the SDK cannot infer, such as image support.\n */\n models?: readonly ProviderCatalogModel[]\n /** Whether the provider may retain responses server-side. Defaults to false. */\n store?: boolean\n /** Output cap when neither caller nor catalog names one. */\n defaultMaxTokens?: number\n /** Context capacity assumed for an uncatalogued model. */\n defaultContextWindow?: number\n /** Idle bound while a stream read is outstanding. */\n streamIdleTimeoutMs?: number\n requestTimeoutMs?: number\n maxRequestBytes?: number\n maxResponseBytes?: number\n maxResponseChunks?: number\n maxSseEvents?: number\n maxSseEventChars?: number\n maxErrorBodyBytes?: number\n requestLoggerTimeoutMs?: number\n /** Retry policy this route owns. */\n retryPolicy?: RetryPolicyConfig\n /** Optional exact wire-request logger; credentials are redacted. */\n requestLogger?: ProviderRequestLogger\n fetch?: typeof globalThis.fetch\n}\n\n/**\n * Create an OpenAI adapter.\n * @param options - credential, endpoint, and catalog overrides.\n * @returns the adapter, ready to register.\n */\nexport function openAiAdapter(options: OpenAiAdapterOptions): HttpModelAdapter {\n const dialect: Partial<ResponsesDialect> = options.store === undefined\n ? {}\n : { store: options.store }\n\n return createHttpProvider({\n displayName: 'OpenAI',\n describeModel: openAiContextPolicy(options),\n protocol: openAiResponsesProtocol,\n baseUrl: options.baseUrl ?? OPENAI_BASE_URL,\n auth: {\n kind: 'bearer',\n token: options.apiKey,\n label: 'the `apiKey` option',\n },\n dialect,\n headers: endpointHeaders(options.headers, {\n ...options.organization === undefined\n ? {}\n : { 'openai-organization': options.organization },\n ...options.project === undefined ? {} : { 'openai-project': options.project },\n }),\n ...options.models === undefined ? {} : { models: options.models },\n defaultMaxTokens: options.defaultMaxTokens ?? 32_000,\n defaultContextWindow: options.defaultContextWindow ?? 128_000,\n ...options.streamIdleTimeoutMs === undefined\n ? {}\n : { streamIdleTimeoutMs: options.streamIdleTimeoutMs },\n ...transportLimits(options),\n ...options.retryPolicy === undefined ? {} : { retryPolicy: options.retryPolicy },\n ...options.requestLogger === undefined ? {} : { requestLogger: options.requestLogger },\n })\n}\n\nexport interface OpenAiPluginOptions extends OpenAiAdapterOptions {\n /** Registry routes installed by the plugin. Defaults to `['openai']`. */\n readonly routes?: readonly string[]\n}\n\nexport interface OpenAiProviderOptions extends Omit<OpenAiAdapterOptions, 'apiKey'> {\n readonly defaultModel?: string | ModelTarget\n readonly apiKey: CredentialInput\n readonly id?: string\n readonly routes?: readonly string[]\n}\n\n/** Preferred transactional plugin for installing the OpenAI provider. */\nexport function openAiPlugin(\n options: OpenAiProviderOptions,\n): ComposableModelProviderPlugin & { readonly family: 'openai' }\nexport function openAiPlugin(options: OpenAiPluginOptions): ModelProviderPlugin\nexport function openAiPlugin(\n options: OpenAiProviderOptions | OpenAiPluginOptions,\n): ModelProviderPlugin | (ComposableModelProviderPlugin & { readonly family: 'openai' }) {\n if (!usesRuntimeComposition(options)) return legacyOpenAiPlugin(options)\n const id = options.id ?? 'openai'\n const routes = Object.freeze([...(options.routes ?? [id])])\n return defineModelProviderPlugin({\n id,\n family: 'openai',\n displayName: 'OpenAI',\n routes,\n ...runtimeDefaultModel(options.defaultModel, routes),\n setup(registrar) {\n const adapter = createRuntimeOpenAiAdapter(options)\n const remove = registrar.registerAdapter(adapter)\n return () => { remove(); return undefined }\n },\n }) as ComposableModelProviderPlugin & { readonly family: 'openai' }\n}\n\nfunction legacyOpenAiPlugin(options: OpenAiPluginOptions): ModelProviderPlugin {\n const routes = Object.freeze([...(options.routes ?? ['openai'])])\n const adapter = openAiAdapter(options)\n return Object.freeze({\n id: 'openai',\n displayName: 'OpenAI',\n setup(registrar: ModelProviderRegistrar) {\n registrar.registerAdapter(routes, adapter)\n },\n })\n}\n\nfunction createRuntimeOpenAiAdapter(options: OpenAiProviderOptions): HttpModelAdapter {\n return createRuntimeHttpProvider({\n describeModel: openAiContextPolicy(options),\n displayName: 'OpenAI',\n protocol: openAiResponsesProtocol,\n baseUrl: options.baseUrl ?? OPENAI_BASE_URL,\n auth: { kind: 'bearer', token: options.apiKey, label: 'the `apiKey` option' },\n dialect: options.store === undefined ? {} : { store: options.store },\n headers: endpointHeaders(options.headers, {\n ...(options.organization === undefined ? {} : { 'openai-organization': options.organization }),\n ...(options.project === undefined ? {} : { 'openai-project': options.project }),\n }),\n ...(options.models === undefined ? {} : { models: options.models }),\n defaultMaxTokens: options.defaultMaxTokens ?? 32_000,\n defaultContextWindow: options.defaultContextWindow ?? 128_000,\n ...(options.streamIdleTimeoutMs === undefined ? {} : { streamIdleTimeoutMs: options.streamIdleTimeoutMs }),\n ...transportLimits(options),\n ...(options.retryPolicy === undefined ? {} : { retryPolicy: options.retryPolicy }),\n ...(options.requestLogger === undefined ? {} : { requestLogger: options.requestLogger }),\n })\n}\n\nfunction usesRuntimeComposition(\n options: OpenAiProviderOptions | OpenAiPluginOptions,\n): options is OpenAiProviderOptions {\n if ('id' in options || 'defaultModel' in options) return true\n if (typeof options.apiKey === 'object' && options.apiKey !== null) return true\n return typeof options.apiKey !== 'function'\n}\n\nfunction runtimeDefaultModel(\n value: string | ModelTarget | undefined,\n routes: readonly string[],\n): { readonly defaultModel?: ModelTarget } {\n if (value === undefined) return {}\n if (typeof value !== 'string') return { defaultModel: value }\n if (routes.length !== 1) {\n throw new TypeError('A string defaultModel requires exactly one OpenAI route')\n }\n return { defaultModel: Object.freeze({ provider: routes[0]!, id: value }) }\n}\n\nfunction transportLimits(options: OpenAiAdapterOptions | OpenAiProviderOptions) {\n return {\n ...(options.allowInsecureHttp === undefined ? {} : { allowInsecureHttp: options.allowInsecureHttp }),\n ...options.requestTimeoutMs === undefined ? {} : { requestTimeoutMs: options.requestTimeoutMs },\n ...options.maxRequestBytes === undefined ? {} : { maxRequestBytes: options.maxRequestBytes },\n ...options.maxResponseBytes === undefined ? {} : { maxResponseBytes: options.maxResponseBytes },\n ...options.maxResponseChunks === undefined ? {} : { maxResponseChunks: options.maxResponseChunks },\n ...options.maxSseEvents === undefined ? {} : { maxSseEvents: options.maxSseEvents },\n ...options.maxSseEventChars === undefined ? {} : { maxSseEventChars: options.maxSseEventChars },\n ...options.maxErrorBodyBytes === undefined ? {} : { maxErrorBodyBytes: options.maxErrorBodyBytes },\n ...options.requestLoggerTimeoutMs === undefined ? {} : { requestLoggerTimeoutMs: options.requestLoggerTimeoutMs },\n ...options.fetch === undefined ? {} : { fetch: options.fetch },\n }\n}\n","/**\n * The OpenAI embeddings endpoint: `POST {baseUrl}/embeddings`.\n *\n * Deliberately a separate module from {@link ./adapter.ts}: it shares no protocol,\n * no dialect and no request vocabulary with `openAiResponsesProtocol`, and wiring\n * embedding through the generation pipeline would mean one of the two shapes\n * standing in for the other (Requirement 14.1). What the two DO share is the\n * transport — the fused signal, the redirect guard, the attempt ledger, the\n * attribution headers — because that chain is where a missing step is expensive.\n *\n * Three facts about this endpoint decide most of what is here, and each has a\n * plausible-looking wrong answer:\n *\n * - **There is no purpose parameter.** A route declares\n * `purposeHandling: 'unsupported'` and this adapter sends the caller's text\n * verbatim. Inventing a `\"query: \"` prefix would change every vector the caller\n * gets while looking like a helpful default (Requirement 7.5).\n * - **There is no truncation parameter.** `truncation: 'allow'` is therefore\n * refused with `EMBEDDING_TRUNCATION_UNSUPPORTED` rather than accepted and\n * quietly not honoured (Requirement 9.7).\n * - **`dimensions` is a model-line capability, not an endpoint one.** It goes on\n * the wire only when the route DECLARES the widths it supports; an `unknown`\n * catalog is not a licence to send a parameter an endpoint may reject\n * (Requirement 14.4).\n *\n * `baseUrl` is configurable because a self-hosted OpenAI-compatible endpoint is\n * the only mechanism this needs (Requirement 15.1). What it is NOT is an\n * inference: compatibility is a profile someone declared through\n * `EmbeddingCatalogModel`, never something read off the path, and a cleartext\n * `http://` base still requires `allowInsecureHttp` (Requirements 15.3, 15.5).\n *\n * @module ai-agent-sdk/providers/openai/embedding\n */\n\nimport { resolveRetryPolicy, type RetryPolicyConfig } from '@alvin0/ai-agent-sdk-core'\nimport {\n EMBEDDING_ERROR_CODES,\n EmbeddingAdapter,\n EmbeddingError,\n defaultEmbeddingProfile,\n deriveSpaceId,\n resolveBatchLimits,\n validateBatchResult,\n type EmbeddingBatchRequest,\n type EmbeddingBatchResult,\n type EmbeddingItem,\n type EmbeddingModelInfo,\n type EmbeddingProfile,\n type EmbeddingProfileInput,\n type EmbeddingVector,\n type PrepareEmbeddingOptions,\n type PreparedEmbeddingCall,\n type ResolvedEmbeddingModelInfo,\n} from '@alvin0/ai-agent-sdk-core/embedding'\nimport {\n defineEmbeddingProviderPlugin,\n type ComposableEmbeddingProviderPlugin,\n type CredentialInput,\n type ModelInvocationContext,\n type ProviderInfo,\n type ProviderRequestId,\n type ResolvedRetryPolicy,\n type SdkLogger,\n} from '@alvin0/ai-agent-sdk-core/provider'\nimport {\n captureTransportConnection,\n endpointHeaders,\n embeddingCatalogModelInfo,\n resolvedEmbeddingCatalogModelInfo,\n transportJson,\n type EmbeddingCatalogModel,\n type EmbeddingHttpConnection,\n} from '@alvin0/ai-agent-sdk-provider-http'\nimport { OPENAI_BASE_URL } from './adapter.ts'\n\n/** Display name used in every diagnostic this module raises. */\nconst DISPLAY_NAME = 'OpenAI'\n\n/** Path appended to the configured base. */\nconst EMBEDDINGS_PATH = '/embeddings'\n\n/** Media type this endpoint answers with, and the only one accepted back. */\nconst JSON_MEDIA_TYPE = 'application/json'\n\n/**\n * The one encoding this adapter asks for.\n *\n * Base64 would halve the bytes on the wire and cost a decode step that could\n * silently reorder or requantise values; float arrays are what the contract's\n * fidelity rule (Requirement 14.8) is cheapest to keep.\n */\nconst ENCODING_FORMAT = 'float'\n\n/**\n * Compatibility identity prefix for an OpenAI embedding model line.\n *\n * Route-independent on purpose: two routes pointing at the same model line — the\n * public API and a mirror of it — produce vectors in the SAME space, so keying\n * the identity on the route name would make them look incompatible.\n */\nconst IDENTITY_PREFIX = 'openai'\n\nconst NEVER_ABORTED_SIGNAL = new AbortController().signal\n\nconst NULL_LOGGER: SdkLogger = Object.freeze({\n child: () => NULL_LOGGER,\n trace: () => undefined,\n debug: () => undefined,\n info: () => undefined,\n warn: () => undefined,\n error: () => undefined,\n fatal: () => undefined,\n})\n\n/** Options for {@link openAiEmbeddingAdapter} and {@link openAiEmbeddingPlugin}. */\nexport interface OpenAiEmbeddingProviderOptions {\n /** Injected API key or credential source; universal packages never read the environment. */\n readonly apiKey: CredentialInput\n /** Extra endpoint headers, captured once per logical call; reserved names fail. */\n readonly headers?: Readonly<Record<string, string>> | (() => Readonly<Record<string, string>>)\n /**\n * Endpoint base; defaults to {@link OPENAI_BASE_URL}.\n *\n * Point this at a self-hosted OpenAI-compatible endpoint. Declare that\n * endpoint's models through {@link models}: the compatibility claim is the\n * configuration's, not this adapter's (Requirement 15.3).\n */\n readonly baseUrl?: string\n /** Organization to bill, when the key belongs to several. */\n readonly organization?: string\n /** Project to attribute usage to. */\n readonly project?: string\n /** Plugin id; also the default route. Defaults to `'openai'`. */\n readonly id?: string\n /** Registry routes the plugin claims. Defaults to `[id]`. */\n readonly routes?: readonly string[]\n /**\n * Advisory embedding catalog.\n *\n * Empty by default, for the same reason the generation adapter ships no model\n * list: a stale built-in catalog would name retired models. A declared entry is\n * what makes `dimensions` reachable on the wire and what states the embedding\n * space, so a route that cares about either declares its models.\n */\n readonly models?: readonly EmbeddingCatalogModel[]\n /** Permit cleartext HTTP explicitly, for trusted local endpoints only. */\n readonly allowInsecureHttp?: boolean\n readonly requestTimeoutMs?: number\n readonly maxRequestBytes?: number\n readonly maxResponseBytes?: number\n readonly maxResponseChunks?: number\n readonly maxErrorBodyBytes?: number\n readonly requestLoggerTimeoutMs?: number\n /** Retry policy this route owns. */\n readonly retryPolicy?: RetryPolicyConfig\n readonly fetch?: typeof globalThis.fetch\n}\n\n/**\n * `POST /embeddings` as an {@link EmbeddingAdapter}.\n *\n * Private: the exported surface is {@link openAiEmbeddingAdapter}, so this class\n * can change shape without a caller having come to depend on it.\n */\nclass OpenAiEmbeddingAdapter extends EmbeddingAdapter {\n private readonly baseUrl: string\n private readonly models: readonly EmbeddingCatalogModel[]\n private readonly retry: ResolvedRetryPolicy\n private readonly headers: () => Readonly<Record<string, string>>\n\n constructor(private readonly options: OpenAiEmbeddingProviderOptions) {\n super()\n this.headers = endpointHeaders(options.headers, {\n ...(options.organization === undefined ? {} : { 'openai-organization': options.organization }),\n ...(options.project === undefined ? {} : { 'openai-project': options.project }),\n })\n this.baseUrl = (options.baseUrl ?? OPENAI_BASE_URL).replace(/\\/+$/, '')\n this.models = Object.freeze([...(options.models ?? [])])\n this.retry = resolveRetryPolicy(options.retryPolicy, 'openAiEmbedding.retryPolicy')\n }\n\n override providerInfo(provider: string): ProviderInfo {\n return { id: provider, name: DISPLAY_NAME }\n }\n\n override providerRetryPolicy(_provider: string): ResolvedRetryPolicy {\n return this.retry\n }\n\n override listEmbeddingModels(\n provider: string,\n signal?: AbortSignal,\n ): Promise<readonly EmbeddingModelInfo[]> {\n signal?.throwIfAborted()\n return Promise.resolve(Object.freeze(\n this.models.map(model => embeddingCatalogModelInfo(provider, model)),\n ))\n }\n\n override resolveEmbeddingModel(\n provider: string,\n model: string,\n signal?: AbortSignal,\n ): Promise<ResolvedEmbeddingModelInfo> {\n signal?.throwIfAborted()\n return Promise.resolve(resolvedEmbeddingCatalogModelInfo(provider, model, this.models))\n }\n\n /**\n * States what this model line declares about its embedding space.\n *\n * Overridden rather than inherited because the default answer is derived from\n * `${route}:${modelId}`, which makes one model line look like two spaces when\n * it is reachable through two routes. Two things differ here: the identity is\n * the model line's (see {@link IDENTITY_PREFIX}), and normalization is reported\n * only when the ROUTE declared it — never inferred from OpenAI's reputation for\n * returning unit vectors (Requirements 6.1, 6.3).\n */\n override embeddingProfile(\n model: ResolvedEmbeddingModelInfo,\n request: EmbeddingProfileInput,\n ): EmbeddingProfile {\n const base = defaultEmbeddingProfile(model, request)\n return Object.freeze({\n ...base,\n compatibilityIdentity: model.compatibilityIdentity.state === 'supported'\n ? model.compatibilityIdentity.value\n : `${IDENTITY_PREFIX}:${model.id}`,\n normalization: model.normalization.state === 'supported'\n ? model.normalization.value\n : base.normalization,\n })\n }\n\n /**\n * Captures endpoint, credential and bounds ONCE, then binds dispatch to that\n * capture.\n *\n * Overridden because this adapter's connection facts come from configuration\n * and a credential resolved per operation: reading them again inside\n * `embedBatch()` would let a rotating secret pair with another generation's URL,\n * and would let dimensions be validated against one catalog while the request\n * went out under another (Requirements 2.2, 2.3, 2.4).\n */\n override async prepareEmbeddingCall(\n provider: string,\n model: string,\n options: PrepareEmbeddingOptions,\n signal?: AbortSignal,\n context?: ModelInvocationContext,\n ): Promise<PreparedEmbeddingCall> {\n const connection = await this.connect(signal, context)\n const resolved = resolvedEmbeddingCatalogModelInfo(provider, model, connection.models)\n const profile = this.embeddingProfile(resolved, options)\n return Object.freeze({\n model: resolved,\n profile,\n spaceId: deriveSpaceId(profile),\n limits: resolveBatchLimits(resolved, options.limits),\n embedBatch: (batch: EmbeddingBatchRequest, invocation = context) =>\n this.dispatch(connection, resolved, batch, invocation),\n })\n }\n\n /**\n * One `Physical_Batch`, one `Provider_Attempt`.\n *\n * Reachable directly for an adapter used without a prepared call; the shared\n * path is {@link dispatch}, so both routes send the same body under the same\n * guards.\n */\n override async embedBatch(\n batch: EmbeddingBatchRequest,\n context?: ModelInvocationContext,\n ): Promise<EmbeddingBatchResult> {\n const connection = await this.connect(batch.signal, context)\n const model = resolvedEmbeddingCatalogModelInfo(\n batch.provider,\n batch.model,\n connection.models,\n )\n return this.dispatch(connection, model, batch, context)\n }\n\n /** Everything one embedding request needs, read together, once per operation. */\n private async connect(\n signal?: AbortSignal,\n context?: ModelInvocationContext,\n ): Promise<EmbeddingHttpConnection> {\n const extraHeaders = this.headers()\n const token = await resolveApiKey(this.options.apiKey, signal, context)\n // The credential and the endpoint-scoped account headers travel together as\n // the auth layer; the transport merges its own layer and attribution on top,\n // which is what makes attribution unforgettable (Requirement 14.7).\n const headers: Record<string, string> = { ...extraHeaders, authorization: `Bearer ${token}` }\n return Object.freeze({\n baseUrl: this.baseUrl,\n headers: Object.freeze(headers),\n sensitiveHeaderNames: Object.freeze(Object.keys(headers)),\n models: this.models,\n retryPolicy: this.retry,\n ...(this.options.allowInsecureHttp === undefined\n ? {}\n : { allowInsecureHttp: this.options.allowInsecureHttp }),\n ...(this.options.requestTimeoutMs === undefined\n ? {}\n : { requestTimeoutMs: this.options.requestTimeoutMs }),\n ...(this.options.maxRequestBytes === undefined\n ? {}\n : { maxRequestBytes: this.options.maxRequestBytes }),\n ...(this.options.maxResponseBytes === undefined\n ? {}\n : { maxResponseBytes: this.options.maxResponseBytes }),\n ...(this.options.maxResponseChunks === undefined\n ? {}\n : { maxResponseChunks: this.options.maxResponseChunks }),\n ...(this.options.maxErrorBodyBytes === undefined\n ? {}\n : { maxErrorBodyBytes: this.options.maxErrorBodyBytes }),\n ...(this.options.requestLoggerTimeoutMs === undefined\n ? {}\n : { requestLoggerTimeoutMs: this.options.requestLoggerTimeoutMs }),\n ...(this.options.fetch === undefined ? {} : { fetch: this.options.fetch }),\n })\n }\n\n /** Serialize, send through the shared chain, and map the parsed body back. */\n private async dispatch(\n connection: EmbeddingHttpConnection,\n model: ResolvedEmbeddingModelInfo,\n batch: EmbeddingBatchRequest,\n context?: ModelInvocationContext,\n ): Promise<EmbeddingBatchResult> {\n if (batch.truncation === 'allow') {\n // Accepting this would promise a behaviour nothing on this wire can express.\n throw new EmbeddingError(\n `${DISPLAY_NAME} embeddings exposes no truncation parameter`,\n EMBEDDING_ERROR_CODES.TRUNCATION_UNSUPPORTED,\n { provider: batch.provider, model: batch.model },\n )\n }\n\n const body: Record<string, unknown> = {\n model: batch.model,\n // One element per item, in item order. The purpose does NOT appear here:\n // this endpoint has no parameter for it and no prefix is invented.\n input: batch.items.map(itemInput),\n encoding_format: ENCODING_FORMAT,\n }\n // Declared widths only. An `unknown` capability states nothing, and sending a\n // parameter on the strength of nothing is how a compatible endpoint 400s.\n if (batch.dimensions !== undefined && model.dimensions.state === 'supported') {\n body['dimensions'] = batch.dimensions\n }\n\n const encoded = JSON.stringify(body)\n // `decode` extracts the payload and NOTHING else. The shared chain normalizes\n // anything thrown inside `decode` through `normalizeHttpBoundaryError`, which\n // has no data twin to read off an `EmbeddingError` and would flatten every\n // stable embedding code into `ModelError{code:'UNKNOWN'}`. Validating after\n // `transportJson` returns is what lets `EMBEDDING_VECTOR_COUNT_MISMATCH` and\n // its siblings reach the caller as themselves (Requirement 9.3–9.5).\n const received = await transportJson({\n connection: captureTransportConnection(connection, {\n 'content-type': JSON_MEDIA_TYPE,\n accept: JSON_MEDIA_TYPE,\n }),\n displayName: DISPLAY_NAME,\n provider: batch.provider,\n model: batch.model,\n path: EMBEDDINGS_PATH,\n accept: JSON_MEDIA_TYPE,\n body: {\n value: body,\n encoded,\n bytes: new TextEncoder().encode(encoded).length,\n },\n ...(batch.signal === undefined ? {} : { signal: batch.signal }),\n ...(context === undefined ? {} : { context }),\n }, (session, payload) => Object.freeze({\n payload,\n ...(session.providerRequestId === undefined\n ? {}\n : { providerRequestId: session.providerRequestId }),\n }))\n return decodeEmbeddingResponse(batch, received)\n }\n}\n\n/** A response body that cleared every transport guard, plus its correlation id. */\ninterface ReceivedEmbeddingResponse {\n readonly payload: unknown\n readonly providerRequestId?: ProviderRequestId\n}\n\n/**\n * Concatenates one item's content parts into a single wire input.\n *\n * The rule is recorded as `documentRecipeRevision` on the profile, and revision\n * `'1'` is exactly this: the text of each part, in order, with no separator and no\n * added markup. It matches the effective text the contract's own length check and\n * cache key derive, so a batch split, a rejection and a wire body can never\n * disagree about what an item's text is. One item produces one element, so the\n * provider returns exactly one vector for it (Requirement 8.7).\n */\nfunction itemInput(item: EmbeddingItem): string {\n let text = ''\n for (const part of item.contentParts) {\n if (part.type === 'text') text += part.text\n }\n return text\n}\n\n/**\n * Maps one parsed response body onto the batch that produced it, in ONE fixed\n * order.\n *\n * The order is the contract, not an implementation detail — the same malformed\n * response has to produce the same code here as it does for every other provider,\n * which is what lets a single conformance suite judge all of them:\n *\n * 1. a shape this contract does not recognise ⇒ `RESPONSE_MALFORMED`\n * 2. `data.length !== items.length` ⇒ `VECTOR_COUNT_MISMATCH`\n * 3. `{ data[i].index }` is not a permutation of `0..N-1` ⇒ `VECTOR_INDEX_INVALID`\n * 4. a non-finite element ⇒ `VECTOR_VALUE_INVALID`\n * 5. a width other than the one requested ⇒ `VECTOR_DIMENSIONS_MISMATCH`\n *\n * Steps 4 and 5 are delegated to the shared {@link validateBatchResult}, which\n * already runs them in exactly this order; re-deriving them here would be a second\n * place for the ordering to drift.\n *\n * The count check comes FIRST on purpose. Reading the entries one at a time and\n * refusing the first bad index would report a mapping failure for a response whose\n * real fault is that it answered a different number of inputs — two different\n * repairs for a caller, told apart by which check ran first.\n *\n * Nothing here slices, pads, sorts or repairs a value. The `index` a vector carries\n * out is the item's index in the `Logical_Call`, taken from `items[data[i].index]`,\n * never its position in this batch (Requirement 8.4).\n */\nfunction decodeEmbeddingResponse(\n batch: EmbeddingBatchRequest,\n received: ReceivedEmbeddingResponse,\n): EmbeddingBatchResult {\n const entries = readEntries(batch, received.payload)\n checkEntryCount(batch, entries)\n const positions = readPositions(batch, entries)\n const vectors: EmbeddingVector[] = entries.map((entry, at) => Object.freeze({\n // The item's index in the `Logical_Call`, not its position in this batch.\n index: batch.items[positions[at]!]!.index,\n values: readValues(batch, entry),\n }))\n const usage = readUsage(received.payload)\n const result: EmbeddingBatchResult = Object.freeze({\n vectors: Object.freeze(vectors),\n ...(usage === undefined ? {} : { usage }),\n ...(received.providerRequestId === undefined\n ? {}\n : { providerRequestId: received.providerRequestId }),\n })\n // Steps 4 and 5, plus the count and logical-index invariants restated against\n // the `Logical_Call` indexes this result now carries.\n validateBatchResult(batch, result)\n return result\n}\n\n/** A `data` array of objects, or a structural refusal naming the batch it belongs to. */\nfunction readEntries(\n batch: EmbeddingBatchRequest,\n payload: unknown,\n): readonly Readonly<Record<string, unknown>>[] {\n const data = record(payload)?.['data']\n if (!Array.isArray(data)) throw malformed(batch, 'response carries no `data` array')\n return data.map(entry => {\n const source = record(entry)\n if (source === undefined) throw malformed(batch, 'response `data` entry is not an object')\n return source\n })\n}\n\n/** Step 2: one vector per input sent, counted before anything is interpreted. */\nfunction checkEntryCount(\n batch: EmbeddingBatchRequest,\n entries: readonly unknown[],\n): void {\n if (entries.length === batch.items.length) return\n throw new EmbeddingError(\n `${DISPLAY_NAME} returned ${entries.length} vectors for ${batch.items.length} inputs`,\n EMBEDDING_ERROR_CODES.VECTOR_COUNT_MISMATCH,\n { provider: batch.provider, model: batch.model },\n )\n}\n\n/**\n * Step 3: the reported positions, once they are known to be a permutation of\n * `0..N-1`.\n *\n * A duplicate, a gap, a non-integer and an out-of-range value all land in the same\n * refusal, because they all break the same thing: without a bijection between\n * response entries and batch items, restoring input order would be guesswork, and\n * a position to \"fall back on\" would silently attach one input's vector to another\n * (Requirement 8.2).\n */\nfunction readPositions(\n batch: EmbeddingBatchRequest,\n entries: readonly Readonly<Record<string, unknown>>[],\n): readonly number[] {\n const positions: number[] = []\n const seen = new Set<number>()\n for (const entry of entries) {\n const position = entry['index']\n if (typeof position !== 'number' || !Number.isInteger(position)\n || position < 0 || position >= batch.items.length || seen.has(position)) {\n throw new EmbeddingError(\n `${DISPLAY_NAME} returned a duplicate, missing or out-of-range vector index`,\n EMBEDDING_ERROR_CODES.VECTOR_INDEX_INVALID,\n { provider: batch.provider, model: batch.model },\n )\n }\n seen.add(position)\n positions.push(position)\n }\n return positions\n}\n\n/**\n * Reads one `embedding` array with every number exactly as it arrived.\n *\n * Only the SHAPE is judged here: a non-finite or non-numeric element is step 4's\n * business, so it travels through untouched and is refused by\n * {@link validateBatchResult} under `VECTOR_VALUE_INVALID` rather than being\n * repaired, dropped, or relabelled as a malformed shape.\n */\nfunction readValues(\n batch: EmbeddingBatchRequest,\n entry: Readonly<Record<string, unknown>>,\n): readonly number[] {\n const values = entry['embedding']\n if (!Array.isArray(values)) {\n throw malformed(batch, 'response vector is not an array')\n }\n return Object.freeze([...values as readonly number[]])\n}\n\n/**\n * Maps `prompt_tokens` and `total_tokens` onto the two embedding counters.\n *\n * There is no `outputTokens`, which is why embedding reports\n * `EmbeddingTokenUsage` rather than generation's `TokenUsage`. A counter that is\n * absent or unreadable stays ABSENT: a zero here would be indistinguishable from\n * a provider that reported no cost at all.\n */\nfunction readUsage(payload: unknown): { inputTokens?: number; totalTokens?: number } | undefined {\n const usage = record(record(payload)?.['usage'])\n if (usage === undefined) return undefined\n const inputTokens = usage['prompt_tokens']\n const totalTokens = usage['total_tokens']\n const counters = {\n ...(typeof inputTokens === 'number' ? { inputTokens } : {}),\n ...(typeof totalTokens === 'number' ? { totalTokens } : {}),\n }\n return Reflect.ownKeys(counters).length === 0 ? undefined : counters\n}\n\n/** A plain-record view of an unknown value, or `undefined`. */\nfunction record(value: unknown): Readonly<Record<string, unknown>> | undefined {\n if (value === null || typeof value !== 'object' || Array.isArray(value)) return undefined\n return value as Readonly<Record<string, unknown>>\n}\n\n/** A structural refusal: the response is not a shape this contract recognises. */\nfunction malformed(batch: EmbeddingBatchRequest, detail: string): EmbeddingError {\n return new EmbeddingError(\n `${DISPLAY_NAME} embeddings ${detail}`,\n EMBEDDING_ERROR_CODES.RESPONSE_MALFORMED,\n { provider: batch.provider, model: batch.model },\n )\n}\n\n/** Resolves a literal key or a credential source, once per operation. */\nasync function resolveApiKey(\n apiKey: CredentialInput,\n signal?: AbortSignal,\n context?: ModelInvocationContext,\n): Promise<string> {\n const value = typeof apiKey === 'string'\n ? apiKey\n : await apiKey.resolve({\n signal: signal ?? NEVER_ABORTED_SIGNAL,\n logger: context?.logger ?? NULL_LOGGER,\n })\n if (typeof value !== 'string' || value.trim().length === 0) {\n throw new EmbeddingError(\n `${DISPLAY_NAME} embeddings requires a non-empty \\`apiKey\\``,\n EMBEDDING_ERROR_CODES.CONFIGURATION_INVALID,\n )\n }\n return value\n}\n\n/**\n * Create an OpenAI embedding adapter.\n * @param options - credential, endpoint, catalog and transport bounds.\n * @returns the adapter, ready to register on an embedding route.\n */\nexport function openAiEmbeddingAdapter(\n options: OpenAiEmbeddingProviderOptions,\n): EmbeddingAdapter {\n return new OpenAiEmbeddingAdapter(options)\n}\n\n/**\n * Preferred transactional plugin for installing OpenAI embeddings.\n *\n * Its `kind` is `'embedding-provider-plugin'`, so it installs beside\n * {@link openAiPlugin} on the same route without either standing in for the other.\n * @param options - credential, endpoint, catalog and transport bounds.\n * @returns an inert plugin; the adapter is constructed during activation.\n */\nexport function openAiEmbeddingPlugin(\n options: OpenAiEmbeddingProviderOptions,\n): ComposableEmbeddingProviderPlugin & { readonly family: 'openai' } {\n const id = options.id ?? 'openai'\n const routes = Object.freeze([...(options.routes ?? [id])])\n return defineEmbeddingProviderPlugin({\n id,\n family: 'openai',\n displayName: DISPLAY_NAME,\n routes,\n setup(registrar) {\n const remove = registrar.registerEmbeddingAdapter(openAiEmbeddingAdapter(options))\n return () => {\n remove()\n return undefined\n }\n },\n }) as ComposableEmbeddingProviderPlugin & { readonly family: 'openai' }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["openAiChatCompletionsProtocol","openAiResponsesProtocol"],"sources":["../src/dual-api.ts","../src/adapter.ts","../src/embedding.ts"],"sourcesContent":["/**\n * One route, two OpenAI wires: Responses for some models, Chat Completions for\n * others — chosen per model id, from `models[].api`.\n *\n * ## Why a delegating adapter rather than a composite protocol\n *\n * `provider-copilot`'s `copilotDualProtocol` solves the same shape of problem\n * (Requirement 9 there) by building one `RuntimeWireProtocol` whose\n * `endpointPath`/`serialize`/`translate` each branch on `request.model.id`, so\n * a single `HttpModelAdapter` instance still owns one shared connection.\n *\n * That approach earns its complexity when the two wires must share one\n * connection (Copilot's device-code credential, one client identity for\n * both). Here they do not: Responses and Chat Completions each already have\n * their own complete, independently-tested `createHttpProvider` wiring in\n * `./adapter.ts`, and `ModelAdapter` (`@alvin0/ai-agent-sdk-core`) is an\n * abstract class with exactly one required method (`stream`) and sensible\n * defaults for the rest — built specifically so \"one adapter instance can\n * serve many routes\" (see its own module doc). A facade that HOLDS two\n * complete adapters and delegates each call by model id reuses both wires\n * exactly as built, with no new protocol-level branching to keep in sync\n * with either one's own evolution.\n *\n * @module ai-agent-sdk/providers/openai/dual-api\n */\n\nimport {\n ModelAdapter,\n type GenerateOptions,\n type ModelCatalogOptions,\n type ModelCatalogSnapshot,\n type ModelInfo,\n type ModelInvocationContext,\n type PreparedAdapterCall,\n type ProviderInfo,\n type ResolvedModelInfo,\n type ResolvedRetryPolicy,\n type StreamChunk,\n} from '@alvin0/ai-agent-sdk-core'\n\n/** Which OpenAI wire one exact model id speaks. */\nexport type OpenAiApi = 'responses' | 'chat-completions'\n\n/**\n * Delegates every call to whichever underlying adapter the model id maps to,\n * falling back to `defaultApi` for an id neither catalog named explicitly.\n */\nexport class OpenAiDualApiAdapter extends ModelAdapter {\n readonly #responses: ModelAdapter\n readonly #chat: ModelAdapter\n readonly #apiOf: ReadonlyMap<string, OpenAiApi>\n readonly #defaultApi: OpenAiApi\n\n constructor(\n responses: ModelAdapter,\n chat: ModelAdapter,\n apiOf: ReadonlyMap<string, OpenAiApi>,\n defaultApi: OpenAiApi,\n ) {\n super()\n this.#responses = responses\n this.#chat = chat\n this.#apiOf = apiOf\n this.#defaultApi = defaultApi\n }\n\n #adapterFor(model: string): ModelAdapter {\n return (this.#apiOf.get(model) ?? this.#defaultApi) === 'chat-completions' ? this.#chat : this.#responses\n }\n\n override providerInfo(provider: string): ProviderInfo {\n return this.#responses.providerInfo(provider)\n }\n\n override providerRetryPolicy(provider: string): ResolvedRetryPolicy | undefined {\n return this.#responses.providerRetryPolicy(provider)\n }\n\n override async listModels(provider: string, signal?: AbortSignal): Promise<readonly ModelInfo[]> {\n const [responsesModels, chatModels] = await Promise.all([\n this.#responses.listModels(provider, signal),\n this.#chat.listModels(provider, signal),\n ])\n return Object.freeze([...responsesModels, ...chatModels])\n }\n\n override async modelCatalog(\n provider: string,\n options: ModelCatalogOptions = {},\n ): Promise<ModelCatalogSnapshot> {\n const [responsesCatalog, chatCatalog] = await Promise.all([\n this.#responses.modelCatalog(provider, options),\n this.#chat.modelCatalog(provider, options),\n ])\n const models = Object.freeze([...responsesCatalog.models, ...chatCatalog.models])\n return Object.freeze({\n provider: responsesCatalog.provider,\n state: models.length === 0 ? 'empty' as const : 'fresh' as const,\n revision: `dual-api:${responsesCatalog.revision}+${chatCatalog.revision}`,\n models,\n observedAt: new Date().toISOString(),\n })\n }\n\n override resolveModel(provider: string, model: string, signal?: AbortSignal): Promise<ResolvedModelInfo> {\n return this.#adapterFor(model).resolveModel(provider, model, signal)\n }\n\n override prepareCall(\n provider: string,\n model: string,\n signal?: AbortSignal,\n context?: ModelInvocationContext,\n ): Promise<PreparedAdapterCall> {\n return this.#adapterFor(model).prepareCall(provider, model, signal, context)\n }\n\n override stream(options: GenerateOptions, context?: ModelInvocationContext): AsyncIterable<StreamChunk> {\n return this.#adapterFor(options.model).stream(options, context)\n }\n}\n","/**\n * The OpenAI provider: the Responses API on `api.openai.com`.\n *\n * Note how little there is here. Every endpoint fact is configuration handed to\n * {@link createHttpProvider}; the protocol, the pipeline, and the error mapping are\n * all shared. That is the intended shape for any endpoint speaking a protocol this\n * package already implements — including your own gateway.\n *\n * @module ai-agent-sdk/providers/openai/adapter\n */\n\nimport type { ModelProviderPlugin, ModelProviderRegistrar, RetryPolicyConfig } from '@alvin0/ai-agent-sdk-core'\nimport { ModelAdapter, ModelError } from '@alvin0/ai-agent-sdk-core'\nimport {\n defineModelProviderPlugin,\n type ComposableModelProviderPlugin,\n type CredentialInput,\n type ModelTarget,\n} from '@alvin0/ai-agent-sdk-core/provider'\nimport { OpenAiDualApiAdapter, type OpenAiApi } from './dual-api.ts'\nimport type {\n HeaderContext,\n HttpModelAdapter,\n ProviderCatalogModel,\n ProviderRequestLogger,\n ProviderResponseLogger,\n RequestContext,\n} from '@alvin0/ai-agent-sdk-provider-http'\nimport {\n createHttpProvider,\n endpointHeaders,\n createRuntimeHttpProvider,\n FieldFallbackAdapter,\n type CredentialSource,\n} from '@alvin0/ai-agent-sdk-provider-http'\nimport {\n openAiResponsesProtocol,\n type ResponsesDialect,\n} from '@alvin0/ai-agent-sdk-protocol-responses'\nimport {\n openAiChatCompletionsProtocol,\n type ChatCompletionsDialect,\n} from '@alvin0/ai-agent-sdk-protocol-openai-chat-completions'\n\n/** The OpenAI API base. */\nexport const OPENAI_BASE_URL = 'https://api.openai.com/v1'\n\n/** How the API key is obtained. */\nexport type OpenAiCredential = CredentialSource\n\n/**\n * Endpoint-specific knobs for the Chat Completions wire, exposed only when\n * `api: 'chat-completions'`. Named `compat` because each field exists for one\n * reason: some OpenAI-compatible endpoint needs the field sent differently, or\n * not at all. Unset fields keep this protocol's own conservative defaults.\n */\nexport interface OpenAiChatCompletionsCompat {\n /**\n * How this endpoint wants to be told how hard to think.\n *\n * Defaults to `'openai'` (`reasoning_effort`, sent verbatim) — the official\n * field, live-verified on `/v1/chat/completions` (see the redesign plan's\n * codex2claudecode probe). `'deepseek'` matches an endpoint that reasons\n * unless told not to. `false` sends nothing regardless of agent effort.\n */\n reasoningFormat?: ChatCompletionsDialect['reasoningFormat']\n /** Name of the output-length field; `false` sends none. */\n maxTokensField?: ChatCompletionsDialect['maxTokensField']\n /** Role the system prompt travels under. */\n systemRole?: ChatCompletionsDialect['systemRole']\n /** `response_format` support. */\n structuredOutputs?: ChatCompletionsDialect['structuredOutputs']\n /** Send `tools` + `tool_choice`. */\n tools?: boolean\n /** Send `parallel_tool_calls`. */\n parallelToolCalls?: boolean\n /** Send `stream_options: { include_usage: true }`. */\n streamUsage?: boolean\n /** Send `stop`. */\n stop?: boolean\n /** Send `seed`. */\n seed?: boolean\n /** Prompt/session cache key, when the endpoint accepts one. */\n promptCacheKey?: string\n}\n\n/** A catalog entry that can also name which OpenAI wire this exact model speaks. */\nexport interface OpenAiCatalogModel extends ProviderCatalogModel {\n /** Overrides the route's own `api` for this one model id. */\n api?: OpenAiApi\n}\n\n/** Options for {@link openAiAdapter}. */\nexport interface OpenAiAdapterOptions {\n /** Injected API key or resolver. Universal packages never read environment variables. */\n apiKey: OpenAiCredential\n /**\n * Endpoint base; defaults to {@link OPENAI_BASE_URL}.\n *\n * Point this at a compatible gateway to reuse this provider wholesale.\n */\n baseUrl?: string\n /**\n * Name this endpoint uses in diagnostics and error messages. Defaults to\n * `'OpenAI'`; set it to the real vendor name (e.g. `'DeepSeek'`) when pointing\n * this provider at a compatible gateway, so a rejection names who rejected it.\n */\n displayName?: string\n /** Extra endpoint headers, captured once per operation. Reserved names and collisions fail. */\n headers?: Readonly<Record<string, string>> | ((ctx: HeaderContext) => Readonly<Record<string, string>>)\n /** Permit cleartext HTTP explicitly for trusted local gateways. */\n allowInsecureHttp?: boolean\n /** Override the request path this protocol would otherwise pick (e.g. an Azure deployment path). */\n path?: string\n /** Extra query-string parameters, or a resolver for them (e.g. Azure's `api-version`). Never for secrets. */\n query?: Readonly<Record<string, string>> | (() => Readonly<Record<string, string>>)\n /**\n * Fields to deep-merge into the serialized body. The caller's value always\n * wins, even over a field the SDK set. A `null` value deletes the field.\n */\n body?: Readonly<Record<string, unknown>>\n /** Last-resort hook with full authority over the body, run after `body` is merged in. */\n transformRequest?: (body: unknown, ctx: RequestContext) => unknown\n /** Organization to bill, when the key belongs to several. */\n organization?: string\n /** Project to attribute usage to. */\n project?: string\n /**\n * Which OpenAI wire this endpoint speaks. Defaults to `'responses'`.\n *\n * Most third-party OpenAI-compatible endpoints (DeepSeek, Groq, Together,\n * Qwen/DashScope, vLLM, Ollama, LM Studio, many gateways) only implement\n * `/chat/completions` — set `'chat-completions'` to reach those.\n */\n api?: 'responses' | 'chat-completions'\n /** Chat Completions wire knobs. Ignored unless `api: 'chat-completions'`. */\n compat?: OpenAiChatCompletionsCompat\n /**\n * Advisory model catalog.\n *\n * Empty by default: this package cannot know which model ids are current, and a\n * stale built-in list would name retired models. Supply entries to declare\n * capabilities the SDK cannot infer, such as image support.\n *\n * `api` on an entry lets ONE route serve both OpenAI wires, model by model —\n * a gateway that fronts both classic chat models (only on `/chat/completions`)\n * and newer reasoning models (only on `/responses`), for instance. Omitted,\n * an entry follows the route's own `api`.\n */\n models?: readonly OpenAiCatalogModel[]\n /** Whether the provider may retain responses server-side. Defaults to false. */\n store?: boolean\n /**\n * Stable key letting the provider route a session's calls to the same\n * cached-prefix-warm backend, cutting cost and latency on a long\n * conversation that resends its own history every turn. Applies to\n * whichever wire is active — Responses or Chat Completions — and, for a\n * mixed route (`models[].api`), to both.\n *\n * Leave unset and set {@link promptCaching} instead to have the SDK invent\n * one per adapter instance (one per conversation, in the common case of\n * building a fresh registry per session) rather than naming your own.\n */\n promptCacheKey?: string\n /**\n * Auto-generate a stable {@link promptCacheKey} when none is given.\n *\n * Off by default: not every account or OpenAI-COMPATIBLE gateway behind\n * this adapter understands `prompt_cache_key`, and a route that doesn't\n * should not silently be asked to guess. If a live dispatch is ever\n * rejected specifically for it, this adapter turns caching off for\n * itself, permanently, and retries once without it.\n */\n promptCaching?: boolean\n /** Output cap when neither caller nor catalog names one. */\n defaultMaxTokens?: number\n /** Context capacity assumed for an uncatalogued model. */\n defaultContextWindow?: number\n /** Idle bound while a stream read is outstanding. */\n streamIdleTimeoutMs?: number\n requestTimeoutMs?: number\n maxRequestBytes?: number\n maxResponseBytes?: number\n maxResponseChunks?: number\n maxSseEvents?: number\n maxSseEventChars?: number\n maxErrorBodyBytes?: number\n requestLoggerTimeoutMs?: number\n /** Retry policy this route owns. */\n retryPolicy?: RetryPolicyConfig\n /** Optional exact wire-request logger; credentials are redacted. */\n requestLogger?: ProviderRequestLogger\n /** Optional exact wire-response logger, fired once a stream ends. */\n responseLogger?: ProviderResponseLogger\n fetch?: typeof globalThis.fetch\n}\n\n/**\n * Create an OpenAI adapter.\n *\n * One route can serve BOTH OpenAI wires at once: if any `models[]` entry\n * names an `api` different from the route's own, this returns a facade\n * ({@link OpenAiDualApiAdapter}) holding one Responses adapter and one Chat\n * Completions adapter, delegating each call by model id. Otherwise — the\n * common case — this is a single `createHttpProvider` adapter, unchanged\n * from before per-model `api` existed.\n * @param options - credential, endpoint, and catalog overrides.\n * @returns the adapter, ready to register.\n */\nexport function openAiAdapter(options: OpenAiAdapterOptions): ModelAdapter {\n const effectiveKey = effectivePromptCacheKey(options)\n const primary = buildOpenAiAdapterTree(options, effectiveKey)\n // Only wrapped when a key is actually in play (explicit or auto-generated):\n // an adapter nobody asked to cache anything with pays nothing extra.\n if (effectiveKey === undefined) return primary\n return new FieldFallbackAdapter(\n primary,\n buildOpenAiAdapterTree(options, false),\n { isFieldRejection: isPromptCacheKeyRejection },\n )\n}\n\n/**\n * Resolve the key every wire this route serves should share, so a mixed\n * route (`models[].api`) still routes both wires' calls to one session.\n * Explicit settings win over auto-generation, and the newer top-level\n * option wins over the older Chat-Completions-only `compat` one.\n */\nfunction effectivePromptCacheKey(\n options: Pick<OpenAiAdapterOptions, 'promptCacheKey' | 'promptCaching' | 'compat'>,\n): string | undefined {\n return options.promptCacheKey\n ?? options.compat?.promptCacheKey\n ?? (options.promptCaching === true ? randomId() : undefined)\n}\n\nfunction randomId(): string {\n return globalThis.crypto?.randomUUID?.() ?? `sdk-${Date.now().toString(36)}`\n}\n\n/**\n * Recognize a dispatch rejection caused specifically by `prompt_cache_key` —\n * the one signal {@link FieldFallbackAdapter} is allowed to react to. Scoped\n * narrowly (a 400 whose message names the field) rather than treating every\n * 400 as a reason to give up on caching, which would mask a real, unrelated\n * request error behind a silent feature downgrade.\n */\nfunction isPromptCacheKeyRejection(error: unknown): boolean {\n return error instanceof ModelError\n && error.failure.status === 400\n && error.message.toLowerCase().includes('prompt_cache_key')\n}\n\n/**\n * One route can serve BOTH OpenAI wires at once: if any `models[]` entry\n * names an `api` different from the route's own, this returns a facade\n * ({@link OpenAiDualApiAdapter}) holding one Responses adapter and one Chat\n * Completions adapter, delegating each call by model id. Otherwise — the\n * common case — this is a single `createHttpProvider` adapter.\n * @param promptCacheKeyOverride - `false` forces caching off regardless of\n * `options`, for {@link FieldFallbackAdapter}'s fallback build.\n */\nfunction buildOpenAiAdapterTree(\n options: OpenAiAdapterOptions,\n promptCacheKeyOverride: string | undefined | false,\n): ModelAdapter {\n const defaultApi: OpenAiApi = options.api ?? 'responses'\n const models = options.models\n const mixed = models?.some(entry => entry.api !== undefined && entry.api !== defaultApi) ?? false\n if (!mixed) return buildOpenAiApiAdapter(options, defaultApi, models, promptCacheKeyOverride)\n\n const apiOf = new Map<string, OpenAiApi>(\n (models ?? []).map(entry => [entry.id, entry.api ?? defaultApi]),\n )\n const responsesModels = (models ?? []).filter(entry => (entry.api ?? defaultApi) === 'responses')\n const chatModels = (models ?? []).filter(entry => (entry.api ?? defaultApi) === 'chat-completions')\n return new OpenAiDualApiAdapter(\n buildOpenAiApiAdapter(options, 'responses', responsesModels, promptCacheKeyOverride),\n buildOpenAiApiAdapter(options, 'chat-completions', chatModels, promptCacheKeyOverride),\n apiOf,\n defaultApi,\n )\n}\n\nfunction buildOpenAiApiAdapter(\n options: OpenAiAdapterOptions,\n api: OpenAiApi,\n models: readonly OpenAiCatalogModel[] | undefined,\n promptCacheKeyOverride: string | undefined | false,\n): HttpModelAdapter {\n const shared = {\n baseUrl: options.baseUrl ?? OPENAI_BASE_URL,\n auth: {\n kind: 'bearer' as const,\n token: options.apiKey,\n label: 'the `apiKey` option',\n },\n headers: endpointHeaders(options.headers, {\n ...options.organization === undefined\n ? {}\n : { 'openai-organization': options.organization },\n ...options.project === undefined ? {} : { 'openai-project': options.project },\n }),\n ...options.path === undefined ? {} : { path: options.path },\n ...options.query === undefined ? {} : { query: options.query },\n ...options.body === undefined ? {} : { body: options.body },\n ...options.transformRequest === undefined ? {} : { transformRequest: options.transformRequest },\n ...models === undefined ? {} : { models },\n ...options.defaultMaxTokens === undefined ? {} : { defaultMaxTokens: options.defaultMaxTokens },\n ...options.defaultContextWindow === undefined ? {} : { defaultContextWindow: options.defaultContextWindow },\n ...options.streamIdleTimeoutMs === undefined\n ? {}\n : { streamIdleTimeoutMs: options.streamIdleTimeoutMs },\n ...transportLimits(options),\n ...options.retryPolicy === undefined ? {} : { retryPolicy: options.retryPolicy },\n ...options.requestLogger === undefined ? {} : { requestLogger: options.requestLogger },\n ...options.responseLogger === undefined ? {} : { responseLogger: options.responseLogger },\n }\n const promptCacheKey = promptCacheKeyOverride === false ? undefined : promptCacheKeyOverride\n if (api === 'chat-completions') {\n return createHttpProvider({\n displayName: options.displayName ?? 'OpenAI',\n protocol: openAiChatCompletionsProtocol,\n dialect: chatCompletionsDialectOf(options.compat, promptCacheKey),\n ...shared,\n })\n }\n return createHttpProvider({\n displayName: options.displayName ?? 'OpenAI',\n protocol: openAiResponsesProtocol,\n dialect: {\n ...options.store === undefined ? {} : { store: options.store },\n ...promptCacheKey === undefined ? {} : { promptCacheKey },\n } satisfies Partial<ResponsesDialect>,\n ...shared,\n })\n}\n\nconst CHAT_COMPLETIONS_REASONING_FORMATS = new Set(['openai', 'deepseek', false])\n\nfunction chatCompletionsDialectOf(\n compat: OpenAiChatCompletionsCompat | undefined,\n promptCacheKey: string | undefined,\n): Partial<ChatCompletionsDialect> {\n if (compat?.reasoningFormat !== undefined && !CHAT_COMPLETIONS_REASONING_FORMATS.has(compat.reasoningFormat)) {\n throw new TypeError(\n `Chat Completions reasoningFormat must be 'openai', 'deepseek', or false, received ${JSON.stringify(compat.reasoningFormat)}`,\n )\n }\n return {\n // A JS caller (or one that fought past TypeScript with `as any`) mistyping\n // a value from a DIFFERENT protocol — Anthropic's `'thinking-budget'`, for\n // instance — would otherwise fall through `reasoningFieldsOf`'s `=== false`\n // / `=== 'openai'` checks in serialize.ts and silently behave as\n // `'deepseek'` instead of failing loudly.\n reasoningFormat: compat?.reasoningFormat ?? 'openai',\n ...compat?.maxTokensField === undefined ? {} : { maxTokensField: compat.maxTokensField },\n ...compat?.systemRole === undefined ? {} : { systemRole: compat.systemRole },\n ...compat?.structuredOutputs === undefined ? {} : { structuredOutputs: compat.structuredOutputs },\n ...compat?.tools === undefined ? {} : { tools: compat.tools },\n ...compat?.parallelToolCalls === undefined ? {} : { parallelToolCalls: compat.parallelToolCalls },\n ...compat?.streamUsage === undefined ? {} : { streamUsage: compat.streamUsage },\n ...compat?.stop === undefined ? {} : { stop: compat.stop },\n ...compat?.seed === undefined ? {} : { seed: compat.seed },\n // The resolved key — explicit, auto-generated, or the older `compat`-only\n // spelling — always wins over `compat.promptCacheKey` restated here, since\n // both already fed into the SAME resolution in `effectivePromptCacheKey`.\n ...promptCacheKey === undefined ? {} : { promptCacheKey },\n }\n}\n\nexport interface OpenAiPluginOptions extends OpenAiAdapterOptions {\n /** Registry routes installed by the plugin. Defaults to `['openai']`. */\n readonly routes?: readonly string[]\n}\n\nexport interface OpenAiProviderOptions extends Omit<OpenAiAdapterOptions, 'apiKey'> {\n readonly defaultModel?: string | ModelTarget\n readonly apiKey: CredentialInput\n readonly id?: string\n readonly routes?: readonly string[]\n}\n\n/** Preferred transactional plugin for installing the OpenAI provider. */\nexport function openAiPlugin(\n options: OpenAiProviderOptions,\n): ComposableModelProviderPlugin & { readonly family: 'openai' }\nexport function openAiPlugin(options: OpenAiPluginOptions): ModelProviderPlugin\nexport function openAiPlugin(\n options: OpenAiProviderOptions | OpenAiPluginOptions,\n): ModelProviderPlugin | (ComposableModelProviderPlugin & { readonly family: 'openai' }) {\n if (!usesRuntimeComposition(options)) return legacyOpenAiPlugin(options)\n const id = options.id ?? 'openai'\n const routes = Object.freeze([...(options.routes ?? [id])])\n return defineModelProviderPlugin({\n id,\n family: 'openai',\n displayName: options.displayName ?? 'OpenAI',\n routes,\n ...runtimeDefaultModel(options.defaultModel, routes),\n setup(registrar) {\n const adapter = createRuntimeOpenAiAdapter(options)\n const remove = registrar.registerAdapter(adapter)\n return () => { remove(); return undefined }\n },\n }) as ComposableModelProviderPlugin & { readonly family: 'openai' }\n}\n\nfunction legacyOpenAiPlugin(options: OpenAiPluginOptions): ModelProviderPlugin {\n const routes = Object.freeze([...(options.routes ?? ['openai'])])\n const adapter = openAiAdapter(options)\n return Object.freeze({\n id: 'openai',\n displayName: options.displayName ?? 'OpenAI',\n setup(registrar: ModelProviderRegistrar) {\n registrar.registerAdapter(routes, adapter)\n },\n })\n}\n\nfunction createRuntimeOpenAiAdapter(options: OpenAiProviderOptions): ModelAdapter {\n const effectiveKey = effectivePromptCacheKey(options)\n const primary = buildRuntimeOpenAiAdapterTree(options, effectiveKey)\n if (effectiveKey === undefined) return primary\n return new FieldFallbackAdapter(\n primary,\n buildRuntimeOpenAiAdapterTree(options, false),\n { isFieldRejection: isPromptCacheKeyRejection },\n )\n}\n\nfunction buildRuntimeOpenAiAdapterTree(\n options: OpenAiProviderOptions,\n promptCacheKeyOverride: string | undefined | false,\n): ModelAdapter {\n const defaultApi: OpenAiApi = options.api ?? 'responses'\n const models = options.models\n const mixed = models?.some(entry => entry.api !== undefined && entry.api !== defaultApi) ?? false\n if (!mixed) return buildRuntimeOpenAiApiAdapter(options, defaultApi, models, promptCacheKeyOverride)\n\n const apiOf = new Map<string, OpenAiApi>(\n (models ?? []).map(entry => [entry.id, entry.api ?? defaultApi]),\n )\n const responsesModels = (models ?? []).filter(entry => (entry.api ?? defaultApi) === 'responses')\n const chatModels = (models ?? []).filter(entry => (entry.api ?? defaultApi) === 'chat-completions')\n return new OpenAiDualApiAdapter(\n buildRuntimeOpenAiApiAdapter(options, 'responses', responsesModels, promptCacheKeyOverride),\n buildRuntimeOpenAiApiAdapter(options, 'chat-completions', chatModels, promptCacheKeyOverride),\n apiOf,\n defaultApi,\n )\n}\n\nfunction buildRuntimeOpenAiApiAdapter(\n options: OpenAiProviderOptions,\n api: OpenAiApi,\n models: readonly OpenAiCatalogModel[] | undefined,\n promptCacheKeyOverride: string | undefined | false,\n): HttpModelAdapter {\n const shared = {\n baseUrl: options.baseUrl ?? OPENAI_BASE_URL,\n auth: { kind: 'bearer' as const, token: options.apiKey, label: 'the `apiKey` option' },\n headers: endpointHeaders(options.headers, {\n ...(options.organization === undefined ? {} : { 'openai-organization': options.organization }),\n ...(options.project === undefined ? {} : { 'openai-project': options.project }),\n }),\n ...(options.path === undefined ? {} : { path: options.path }),\n ...(options.query === undefined ? {} : { query: options.query }),\n ...(options.body === undefined ? {} : { body: options.body }),\n ...(options.transformRequest === undefined ? {} : { transformRequest: options.transformRequest }),\n ...(models === undefined ? {} : { models }),\n ...(options.defaultMaxTokens === undefined ? {} : { defaultMaxTokens: options.defaultMaxTokens }),\n ...(options.defaultContextWindow === undefined ? {} : { defaultContextWindow: options.defaultContextWindow }),\n ...(options.streamIdleTimeoutMs === undefined ? {} : { streamIdleTimeoutMs: options.streamIdleTimeoutMs }),\n ...transportLimits(options),\n ...(options.retryPolicy === undefined ? {} : { retryPolicy: options.retryPolicy }),\n ...(options.requestLogger === undefined ? {} : { requestLogger: options.requestLogger }),\n ...(options.responseLogger === undefined ? {} : { responseLogger: options.responseLogger }),\n }\n const promptCacheKey = promptCacheKeyOverride === false ? undefined : promptCacheKeyOverride\n if (api === 'chat-completions') {\n return createRuntimeHttpProvider({\n displayName: options.displayName ?? 'OpenAI',\n protocol: openAiChatCompletionsProtocol,\n dialect: chatCompletionsDialectOf(options.compat, promptCacheKey),\n ...shared,\n })\n }\n return createRuntimeHttpProvider({\n displayName: options.displayName ?? 'OpenAI',\n protocol: openAiResponsesProtocol,\n dialect: {\n ...options.store === undefined ? {} : { store: options.store },\n ...promptCacheKey === undefined ? {} : { promptCacheKey },\n },\n ...shared,\n })\n}\n\nfunction usesRuntimeComposition(\n options: OpenAiProviderOptions | OpenAiPluginOptions,\n): options is OpenAiProviderOptions {\n if ('id' in options || 'defaultModel' in options) return true\n if (typeof options.apiKey === 'object' && options.apiKey !== null) return true\n return typeof options.apiKey !== 'function'\n}\n\nfunction runtimeDefaultModel(\n value: string | ModelTarget | undefined,\n routes: readonly string[],\n): { readonly defaultModel?: ModelTarget } {\n if (value === undefined) return {}\n if (typeof value !== 'string') return { defaultModel: value }\n if (routes.length !== 1) {\n throw new TypeError('A string defaultModel requires exactly one OpenAI route')\n }\n return { defaultModel: Object.freeze({ provider: routes[0]!, id: value }) }\n}\n\nfunction transportLimits(options: OpenAiAdapterOptions | OpenAiProviderOptions) {\n return {\n ...(options.allowInsecureHttp === undefined ? {} : { allowInsecureHttp: options.allowInsecureHttp }),\n ...options.requestTimeoutMs === undefined ? {} : { requestTimeoutMs: options.requestTimeoutMs },\n ...options.maxRequestBytes === undefined ? {} : { maxRequestBytes: options.maxRequestBytes },\n ...options.maxResponseBytes === undefined ? {} : { maxResponseBytes: options.maxResponseBytes },\n ...options.maxResponseChunks === undefined ? {} : { maxResponseChunks: options.maxResponseChunks },\n ...options.maxSseEvents === undefined ? {} : { maxSseEvents: options.maxSseEvents },\n ...options.maxSseEventChars === undefined ? {} : { maxSseEventChars: options.maxSseEventChars },\n ...options.maxErrorBodyBytes === undefined ? {} : { maxErrorBodyBytes: options.maxErrorBodyBytes },\n ...options.requestLoggerTimeoutMs === undefined ? {} : { requestLoggerTimeoutMs: options.requestLoggerTimeoutMs },\n ...options.fetch === undefined ? {} : { fetch: options.fetch },\n }\n}\n","/**\n * The OpenAI embeddings endpoint: `POST {baseUrl}/embeddings`.\n *\n * Deliberately a separate module from {@link ./adapter.ts}: it shares no protocol,\n * no dialect and no request vocabulary with `openAiResponsesProtocol`, and wiring\n * embedding through the generation pipeline would mean one of the two shapes\n * standing in for the other (Requirement 14.1). What the two DO share is the\n * transport — the fused signal, the redirect guard, the attempt ledger, the\n * attribution headers — because that chain is where a missing step is expensive.\n *\n * Three facts about this endpoint decide most of what is here, and each has a\n * plausible-looking wrong answer:\n *\n * - **There is no purpose parameter.** A route declares\n * `purposeHandling: 'unsupported'` and this adapter sends the caller's text\n * verbatim. Inventing a `\"query: \"` prefix would change every vector the caller\n * gets while looking like a helpful default (Requirement 7.5).\n * - **There is no truncation parameter.** `truncation: 'allow'` is therefore\n * refused with `EMBEDDING_TRUNCATION_UNSUPPORTED` rather than accepted and\n * quietly not honoured (Requirement 9.7).\n * - **`dimensions` is a model-line capability, not an endpoint one.** It goes on\n * the wire only when the route DECLARES the widths it supports; an `unknown`\n * catalog is not a licence to send a parameter an endpoint may reject\n * (Requirement 14.4).\n *\n * `baseUrl` is configurable because a self-hosted OpenAI-compatible endpoint is\n * the only mechanism this needs (Requirement 15.1). What it is NOT is an\n * inference: compatibility is a profile someone declared through\n * `EmbeddingCatalogModel`, never something read off the path, and a cleartext\n * `http://` base still requires `allowInsecureHttp` (Requirements 15.3, 15.5).\n *\n * @module ai-agent-sdk/providers/openai/embedding\n */\n\nimport { resolveRetryPolicy, type RetryPolicyConfig } from '@alvin0/ai-agent-sdk-core'\nimport {\n EMBEDDING_ERROR_CODES,\n EmbeddingAdapter,\n EmbeddingError,\n defaultEmbeddingProfile,\n deriveSpaceId,\n resolveBatchLimits,\n validateBatchResult,\n type EmbeddingBatchRequest,\n type EmbeddingBatchResult,\n type EmbeddingItem,\n type EmbeddingModelInfo,\n type EmbeddingProfile,\n type EmbeddingProfileInput,\n type EmbeddingVector,\n type PrepareEmbeddingOptions,\n type PreparedEmbeddingCall,\n type ResolvedEmbeddingModelInfo,\n} from '@alvin0/ai-agent-sdk-core/embedding'\nimport {\n defineEmbeddingProviderPlugin,\n type ComposableEmbeddingProviderPlugin,\n type CredentialInput,\n type ModelInvocationContext,\n type ProviderInfo,\n type ProviderRequestId,\n type ResolvedRetryPolicy,\n type SdkLogger,\n} from '@alvin0/ai-agent-sdk-core/provider'\nimport {\n captureTransportConnection,\n endpointHeaders,\n embeddingCatalogModelInfo,\n resolvedEmbeddingCatalogModelInfo,\n transportJson,\n type EmbeddingCatalogModel,\n type EmbeddingHttpConnection,\n type HeaderContext,\n} from '@alvin0/ai-agent-sdk-provider-http'\nimport { OPENAI_BASE_URL } from './adapter.ts'\n\n/** Display name used in every diagnostic this module raises. */\nconst DISPLAY_NAME = 'OpenAI'\n\n/** Path appended to the configured base. */\nconst EMBEDDINGS_PATH = '/embeddings'\n\n/** Media type this endpoint answers with, and the only one accepted back. */\nconst JSON_MEDIA_TYPE = 'application/json'\n\n/**\n * The one encoding this adapter asks for.\n *\n * Base64 would halve the bytes on the wire and cost a decode step that could\n * silently reorder or requantise values; float arrays are what the contract's\n * fidelity rule (Requirement 14.8) is cheapest to keep.\n */\nconst ENCODING_FORMAT = 'float'\n\n/**\n * Compatibility identity prefix for an OpenAI embedding model line.\n *\n * Route-independent on purpose: two routes pointing at the same model line — the\n * public API and a mirror of it — produce vectors in the SAME space, so keying\n * the identity on the route name would make them look incompatible.\n */\nconst IDENTITY_PREFIX = 'openai'\n\nconst NEVER_ABORTED_SIGNAL = new AbortController().signal\n\nconst NULL_LOGGER: SdkLogger = Object.freeze({\n child: () => NULL_LOGGER,\n trace: () => undefined,\n debug: () => undefined,\n info: () => undefined,\n warn: () => undefined,\n error: () => undefined,\n fatal: () => undefined,\n})\n\n/** Options for {@link openAiEmbeddingAdapter} and {@link openAiEmbeddingPlugin}. */\nexport interface OpenAiEmbeddingProviderOptions {\n /** Injected API key or credential source; universal packages never read the environment. */\n readonly apiKey: CredentialInput\n /** Extra endpoint headers, captured once per logical call; reserved names fail. */\n readonly headers?: Readonly<Record<string, string>> | (() => Readonly<Record<string, string>>)\n /**\n * Endpoint base; defaults to {@link OPENAI_BASE_URL}.\n *\n * Point this at a self-hosted OpenAI-compatible endpoint. Declare that\n * endpoint's models through {@link models}: the compatibility claim is the\n * configuration's, not this adapter's (Requirement 15.3).\n */\n readonly baseUrl?: string\n /** Organization to bill, when the key belongs to several. */\n readonly organization?: string\n /** Project to attribute usage to. */\n readonly project?: string\n /** Plugin id; also the default route. Defaults to `'openai'`. */\n readonly id?: string\n /** Registry routes the plugin claims. Defaults to `[id]`. */\n readonly routes?: readonly string[]\n /**\n * Advisory embedding catalog.\n *\n * Empty by default, for the same reason the generation adapter ships no model\n * list: a stale built-in catalog would name retired models. A declared entry is\n * what makes `dimensions` reachable on the wire and what states the embedding\n * space, so a route that cares about either declares its models.\n */\n readonly models?: readonly EmbeddingCatalogModel[]\n /** Permit cleartext HTTP explicitly, for trusted local endpoints only. */\n readonly allowInsecureHttp?: boolean\n readonly requestTimeoutMs?: number\n readonly maxRequestBytes?: number\n readonly maxResponseBytes?: number\n readonly maxResponseChunks?: number\n readonly maxErrorBodyBytes?: number\n readonly requestLoggerTimeoutMs?: number\n /** Retry policy this route owns. */\n readonly retryPolicy?: RetryPolicyConfig\n readonly fetch?: typeof globalThis.fetch\n}\n\n/**\n * `POST /embeddings` as an {@link EmbeddingAdapter}.\n *\n * Private: the exported surface is {@link openAiEmbeddingAdapter}, so this class\n * can change shape without a caller having come to depend on it.\n */\nclass OpenAiEmbeddingAdapter extends EmbeddingAdapter {\n private readonly baseUrl: string\n private readonly models: readonly EmbeddingCatalogModel[]\n private readonly retry: ResolvedRetryPolicy\n private readonly headers: (ctx: HeaderContext) => Readonly<Record<string, string>>\n\n constructor(private readonly options: OpenAiEmbeddingProviderOptions) {\n super()\n this.headers = endpointHeaders(options.headers, {\n ...(options.organization === undefined ? {} : { 'openai-organization': options.organization }),\n ...(options.project === undefined ? {} : { 'openai-project': options.project }),\n })\n this.baseUrl = (options.baseUrl ?? OPENAI_BASE_URL).replace(/\\/+$/, '')\n this.models = Object.freeze([...(options.models ?? [])])\n this.retry = resolveRetryPolicy(options.retryPolicy, 'openAiEmbedding.retryPolicy')\n }\n\n override providerInfo(provider: string): ProviderInfo {\n return { id: provider, name: DISPLAY_NAME }\n }\n\n override providerRetryPolicy(_provider: string): ResolvedRetryPolicy {\n return this.retry\n }\n\n override listEmbeddingModels(\n provider: string,\n signal?: AbortSignal,\n ): Promise<readonly EmbeddingModelInfo[]> {\n signal?.throwIfAborted()\n return Promise.resolve(Object.freeze(\n this.models.map(model => embeddingCatalogModelInfo(provider, model)),\n ))\n }\n\n override resolveEmbeddingModel(\n provider: string,\n model: string,\n signal?: AbortSignal,\n ): Promise<ResolvedEmbeddingModelInfo> {\n signal?.throwIfAborted()\n return Promise.resolve(resolvedEmbeddingCatalogModelInfo(provider, model, this.models))\n }\n\n /**\n * States what this model line declares about its embedding space.\n *\n * Overridden rather than inherited because the default answer is derived from\n * `${route}:${modelId}`, which makes one model line look like two spaces when\n * it is reachable through two routes. Two things differ here: the identity is\n * the model line's (see {@link IDENTITY_PREFIX}), and normalization is reported\n * only when the ROUTE declared it — never inferred from OpenAI's reputation for\n * returning unit vectors (Requirements 6.1, 6.3).\n */\n override embeddingProfile(\n model: ResolvedEmbeddingModelInfo,\n request: EmbeddingProfileInput,\n ): EmbeddingProfile {\n const base = defaultEmbeddingProfile(model, request)\n return Object.freeze({\n ...base,\n compatibilityIdentity: model.compatibilityIdentity.state === 'supported'\n ? model.compatibilityIdentity.value\n : `${IDENTITY_PREFIX}:${model.id}`,\n normalization: model.normalization.state === 'supported'\n ? model.normalization.value\n : base.normalization,\n })\n }\n\n /**\n * Captures endpoint, credential and bounds ONCE, then binds dispatch to that\n * capture.\n *\n * Overridden because this adapter's connection facts come from configuration\n * and a credential resolved per operation: reading them again inside\n * `embedBatch()` would let a rotating secret pair with another generation's URL,\n * and would let dimensions be validated against one catalog while the request\n * went out under another (Requirements 2.2, 2.3, 2.4).\n */\n override async prepareEmbeddingCall(\n provider: string,\n model: string,\n options: PrepareEmbeddingOptions,\n signal?: AbortSignal,\n context?: ModelInvocationContext,\n ): Promise<PreparedEmbeddingCall> {\n const connection = await this.connect(provider, signal, context)\n const resolved = resolvedEmbeddingCatalogModelInfo(provider, model, connection.models)\n const profile = this.embeddingProfile(resolved, options)\n return Object.freeze({\n model: resolved,\n profile,\n spaceId: deriveSpaceId(profile),\n limits: resolveBatchLimits(resolved, options.limits),\n embedBatch: (batch: EmbeddingBatchRequest, invocation = context) =>\n this.dispatch(connection, resolved, batch, invocation),\n })\n }\n\n /**\n * One `Physical_Batch`, one `Provider_Attempt`.\n *\n * Reachable directly for an adapter used without a prepared call; the shared\n * path is {@link dispatch}, so both routes send the same body under the same\n * guards.\n */\n override async embedBatch(\n batch: EmbeddingBatchRequest,\n context?: ModelInvocationContext,\n ): Promise<EmbeddingBatchResult> {\n const connection = await this.connect(batch.provider, batch.signal, context)\n const model = resolvedEmbeddingCatalogModelInfo(\n batch.provider,\n batch.model,\n connection.models,\n )\n return this.dispatch(connection, model, batch, context)\n }\n\n /** Everything one embedding request needs, read together, once per operation. */\n private async connect(\n provider: string,\n signal?: AbortSignal,\n context?: ModelInvocationContext,\n ): Promise<EmbeddingHttpConnection> {\n const extraHeaders = this.headers({\n provider,\n ...(context?.agentId === undefined ? {} : { agentId: context.agentId }),\n ...(signal === undefined ? {} : { signal }),\n })\n const token = await resolveApiKey(this.options.apiKey, signal, context)\n // The credential and the endpoint-scoped account headers travel together as\n // the auth layer; the transport merges its own layer and attribution on top,\n // which is what makes attribution unforgettable (Requirement 14.7).\n const headers: Record<string, string> = { ...extraHeaders, authorization: `Bearer ${token}` }\n return Object.freeze({\n baseUrl: this.baseUrl,\n headers: Object.freeze(headers),\n sensitiveHeaderNames: Object.freeze(Object.keys(headers)),\n models: this.models,\n retryPolicy: this.retry,\n ...(this.options.allowInsecureHttp === undefined\n ? {}\n : { allowInsecureHttp: this.options.allowInsecureHttp }),\n ...(this.options.requestTimeoutMs === undefined\n ? {}\n : { requestTimeoutMs: this.options.requestTimeoutMs }),\n ...(this.options.maxRequestBytes === undefined\n ? {}\n : { maxRequestBytes: this.options.maxRequestBytes }),\n ...(this.options.maxResponseBytes === undefined\n ? {}\n : { maxResponseBytes: this.options.maxResponseBytes }),\n ...(this.options.maxResponseChunks === undefined\n ? {}\n : { maxResponseChunks: this.options.maxResponseChunks }),\n ...(this.options.maxErrorBodyBytes === undefined\n ? {}\n : { maxErrorBodyBytes: this.options.maxErrorBodyBytes }),\n ...(this.options.requestLoggerTimeoutMs === undefined\n ? {}\n : { requestLoggerTimeoutMs: this.options.requestLoggerTimeoutMs }),\n ...(this.options.fetch === undefined ? {} : { fetch: this.options.fetch }),\n })\n }\n\n /** Serialize, send through the shared chain, and map the parsed body back. */\n private async dispatch(\n connection: EmbeddingHttpConnection,\n model: ResolvedEmbeddingModelInfo,\n batch: EmbeddingBatchRequest,\n context?: ModelInvocationContext,\n ): Promise<EmbeddingBatchResult> {\n if (batch.truncation === 'allow') {\n // Accepting this would promise a behaviour nothing on this wire can express.\n throw new EmbeddingError(\n `${DISPLAY_NAME} embeddings exposes no truncation parameter`,\n EMBEDDING_ERROR_CODES.TRUNCATION_UNSUPPORTED,\n { provider: batch.provider, model: batch.model },\n )\n }\n\n const body: Record<string, unknown> = {\n model: batch.model,\n // One element per item, in item order. The purpose does NOT appear here:\n // this endpoint has no parameter for it and no prefix is invented.\n input: batch.items.map(itemInput),\n encoding_format: ENCODING_FORMAT,\n }\n // Declared widths only. An `unknown` capability states nothing, and sending a\n // parameter on the strength of nothing is how a compatible endpoint 400s.\n if (batch.dimensions !== undefined && model.dimensions.state === 'supported') {\n body['dimensions'] = batch.dimensions\n }\n\n const encoded = JSON.stringify(body)\n // `decode` extracts the payload and NOTHING else. The shared chain normalizes\n // anything thrown inside `decode` through `normalizeHttpBoundaryError`, which\n // has no data twin to read off an `EmbeddingError` and would flatten every\n // stable embedding code into `ModelError{code:'UNKNOWN'}`. Validating after\n // `transportJson` returns is what lets `EMBEDDING_VECTOR_COUNT_MISMATCH` and\n // its siblings reach the caller as themselves (Requirement 9.3–9.5).\n const received = await transportJson({\n connection: captureTransportConnection(connection, {\n 'content-type': JSON_MEDIA_TYPE,\n accept: JSON_MEDIA_TYPE,\n }),\n displayName: DISPLAY_NAME,\n provider: batch.provider,\n model: batch.model,\n path: EMBEDDINGS_PATH,\n accept: JSON_MEDIA_TYPE,\n body: {\n value: body,\n encoded,\n bytes: new TextEncoder().encode(encoded).length,\n },\n ...(batch.signal === undefined ? {} : { signal: batch.signal }),\n ...(context === undefined ? {} : { context }),\n }, (session, payload) => Object.freeze({\n payload,\n ...(session.providerRequestId === undefined\n ? {}\n : { providerRequestId: session.providerRequestId }),\n }))\n return decodeEmbeddingResponse(batch, received)\n }\n}\n\n/** A response body that cleared every transport guard, plus its correlation id. */\ninterface ReceivedEmbeddingResponse {\n readonly payload: unknown\n readonly providerRequestId?: ProviderRequestId\n}\n\n/**\n * Concatenates one item's content parts into a single wire input.\n *\n * The rule is recorded as `documentRecipeRevision` on the profile, and revision\n * `'1'` is exactly this: the text of each part, in order, with no separator and no\n * added markup. It matches the effective text the contract's own length check and\n * cache key derive, so a batch split, a rejection and a wire body can never\n * disagree about what an item's text is. One item produces one element, so the\n * provider returns exactly one vector for it (Requirement 8.7).\n */\nfunction itemInput(item: EmbeddingItem): string {\n let text = ''\n for (const part of item.contentParts) {\n if (part.type === 'text') text += part.text\n }\n return text\n}\n\n/**\n * Maps one parsed response body onto the batch that produced it, in ONE fixed\n * order.\n *\n * The order is the contract, not an implementation detail — the same malformed\n * response has to produce the same code here as it does for every other provider,\n * which is what lets a single conformance suite judge all of them:\n *\n * 1. a shape this contract does not recognise ⇒ `RESPONSE_MALFORMED`\n * 2. `data.length !== items.length` ⇒ `VECTOR_COUNT_MISMATCH`\n * 3. `{ data[i].index }` is not a permutation of `0..N-1` ⇒ `VECTOR_INDEX_INVALID`\n * 4. a non-finite element ⇒ `VECTOR_VALUE_INVALID`\n * 5. a width other than the one requested ⇒ `VECTOR_DIMENSIONS_MISMATCH`\n *\n * Steps 4 and 5 are delegated to the shared {@link validateBatchResult}, which\n * already runs them in exactly this order; re-deriving them here would be a second\n * place for the ordering to drift.\n *\n * The count check comes FIRST on purpose. Reading the entries one at a time and\n * refusing the first bad index would report a mapping failure for a response whose\n * real fault is that it answered a different number of inputs — two different\n * repairs for a caller, told apart by which check ran first.\n *\n * Nothing here slices, pads, sorts or repairs a value. The `index` a vector carries\n * out is the item's index in the `Logical_Call`, taken from `items[data[i].index]`,\n * never its position in this batch (Requirement 8.4).\n */\nfunction decodeEmbeddingResponse(\n batch: EmbeddingBatchRequest,\n received: ReceivedEmbeddingResponse,\n): EmbeddingBatchResult {\n const entries = readEntries(batch, received.payload)\n checkEntryCount(batch, entries)\n const positions = readPositions(batch, entries)\n const vectors: EmbeddingVector[] = entries.map((entry, at) => Object.freeze({\n // The item's index in the `Logical_Call`, not its position in this batch.\n index: batch.items[positions[at]!]!.index,\n values: readValues(batch, entry),\n }))\n const usage = readUsage(received.payload)\n const result: EmbeddingBatchResult = Object.freeze({\n vectors: Object.freeze(vectors),\n ...(usage === undefined ? {} : { usage }),\n ...(received.providerRequestId === undefined\n ? {}\n : { providerRequestId: received.providerRequestId }),\n })\n // Steps 4 and 5, plus the count and logical-index invariants restated against\n // the `Logical_Call` indexes this result now carries.\n validateBatchResult(batch, result)\n return result\n}\n\n/** A `data` array of objects, or a structural refusal naming the batch it belongs to. */\nfunction readEntries(\n batch: EmbeddingBatchRequest,\n payload: unknown,\n): readonly Readonly<Record<string, unknown>>[] {\n const data = record(payload)?.['data']\n if (!Array.isArray(data)) throw malformed(batch, 'response carries no `data` array')\n return data.map(entry => {\n const source = record(entry)\n if (source === undefined) throw malformed(batch, 'response `data` entry is not an object')\n return source\n })\n}\n\n/** Step 2: one vector per input sent, counted before anything is interpreted. */\nfunction checkEntryCount(\n batch: EmbeddingBatchRequest,\n entries: readonly unknown[],\n): void {\n if (entries.length === batch.items.length) return\n throw new EmbeddingError(\n `${DISPLAY_NAME} returned ${entries.length} vectors for ${batch.items.length} inputs`,\n EMBEDDING_ERROR_CODES.VECTOR_COUNT_MISMATCH,\n { provider: batch.provider, model: batch.model },\n )\n}\n\n/**\n * Step 3: the reported positions, once they are known to be a permutation of\n * `0..N-1`.\n *\n * A duplicate, a gap, a non-integer and an out-of-range value all land in the same\n * refusal, because they all break the same thing: without a bijection between\n * response entries and batch items, restoring input order would be guesswork, and\n * a position to \"fall back on\" would silently attach one input's vector to another\n * (Requirement 8.2).\n */\nfunction readPositions(\n batch: EmbeddingBatchRequest,\n entries: readonly Readonly<Record<string, unknown>>[],\n): readonly number[] {\n const positions: number[] = []\n const seen = new Set<number>()\n for (const entry of entries) {\n const position = entry['index']\n if (typeof position !== 'number' || !Number.isInteger(position)\n || position < 0 || position >= batch.items.length || seen.has(position)) {\n throw new EmbeddingError(\n `${DISPLAY_NAME} returned a duplicate, missing or out-of-range vector index`,\n EMBEDDING_ERROR_CODES.VECTOR_INDEX_INVALID,\n { provider: batch.provider, model: batch.model },\n )\n }\n seen.add(position)\n positions.push(position)\n }\n return positions\n}\n\n/**\n * Reads one `embedding` array with every number exactly as it arrived.\n *\n * Only the SHAPE is judged here: a non-finite or non-numeric element is step 4's\n * business, so it travels through untouched and is refused by\n * {@link validateBatchResult} under `VECTOR_VALUE_INVALID` rather than being\n * repaired, dropped, or relabelled as a malformed shape.\n */\nfunction readValues(\n batch: EmbeddingBatchRequest,\n entry: Readonly<Record<string, unknown>>,\n): readonly number[] {\n const values = entry['embedding']\n if (!Array.isArray(values)) {\n throw malformed(batch, 'response vector is not an array')\n }\n return Object.freeze([...values as readonly number[]])\n}\n\n/**\n * Maps `prompt_tokens` and `total_tokens` onto the two embedding counters.\n *\n * There is no `outputTokens`, which is why embedding reports\n * `EmbeddingTokenUsage` rather than generation's `TokenUsage`. A counter that is\n * absent or unreadable stays ABSENT: a zero here would be indistinguishable from\n * a provider that reported no cost at all.\n */\nfunction readUsage(payload: unknown): { inputTokens?: number; totalTokens?: number } | undefined {\n const usage = record(record(payload)?.['usage'])\n if (usage === undefined) return undefined\n const inputTokens = usage['prompt_tokens']\n const totalTokens = usage['total_tokens']\n const counters = {\n ...(typeof inputTokens === 'number' ? { inputTokens } : {}),\n ...(typeof totalTokens === 'number' ? { totalTokens } : {}),\n }\n return Reflect.ownKeys(counters).length === 0 ? undefined : counters\n}\n\n/** A plain-record view of an unknown value, or `undefined`. */\nfunction record(value: unknown): Readonly<Record<string, unknown>> | undefined {\n if (value === null || typeof value !== 'object' || Array.isArray(value)) return undefined\n return value as Readonly<Record<string, unknown>>\n}\n\n/** A structural refusal: the response is not a shape this contract recognises. */\nfunction malformed(batch: EmbeddingBatchRequest, detail: string): EmbeddingError {\n return new EmbeddingError(\n `${DISPLAY_NAME} embeddings ${detail}`,\n EMBEDDING_ERROR_CODES.RESPONSE_MALFORMED,\n { provider: batch.provider, model: batch.model },\n )\n}\n\n/** Resolves a literal key or a credential source, once per operation. */\nasync function resolveApiKey(\n apiKey: CredentialInput,\n signal?: AbortSignal,\n context?: ModelInvocationContext,\n): Promise<string> {\n const value = typeof apiKey === 'string'\n ? apiKey\n : await apiKey.resolve({\n signal: signal ?? NEVER_ABORTED_SIGNAL,\n logger: context?.logger ?? NULL_LOGGER,\n })\n if (typeof value !== 'string' || value.trim().length === 0) {\n throw new EmbeddingError(\n `${DISPLAY_NAME} embeddings requires a non-empty \\`apiKey\\``,\n EMBEDDING_ERROR_CODES.CONFIGURATION_INVALID,\n )\n }\n return value\n}\n\n/**\n * Create an OpenAI embedding adapter.\n * @param options - credential, endpoint, catalog and transport bounds.\n * @returns the adapter, ready to register on an embedding route.\n */\nexport function openAiEmbeddingAdapter(\n options: OpenAiEmbeddingProviderOptions,\n): EmbeddingAdapter {\n return new OpenAiEmbeddingAdapter(options)\n}\n\n/**\n * Preferred transactional plugin for installing OpenAI embeddings.\n *\n * Its `kind` is `'embedding-provider-plugin'`, so it installs beside\n * {@link openAiPlugin} on the same route without either standing in for the other.\n * @param options - credential, endpoint, catalog and transport bounds.\n * @returns an inert plugin; the adapter is constructed during activation.\n */\nexport function openAiEmbeddingPlugin(\n options: OpenAiEmbeddingProviderOptions,\n): ComposableEmbeddingProviderPlugin & { readonly family: 'openai' } {\n const id = options.id ?? 'openai'\n const routes = Object.freeze([...(options.routes ?? [id])])\n return defineEmbeddingProviderPlugin({\n id,\n family: 'openai',\n displayName: DISPLAY_NAME,\n routes,\n setup(registrar) {\n const remove = registrar.registerEmbeddingAdapter(openAiEmbeddingAdapter(options))\n return () => {\n remove()\n return undefined\n }\n },\n }) as ComposableEmbeddingProviderPlugin & { readonly family: 'openai' }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "name": "alvin0 - chaulamdinhai",
5
5
  "email": "chaulamdinhai@gmail.com"
6
6
  },
7
- "version": "0.1.2",
7
+ "version": "0.1.4",
8
8
  "description": "Universal OpenAI adapter and provider plugin for ai-agent-sdk",
9
9
  "license": "MIT",
10
10
  "repository": {
@@ -38,15 +38,16 @@
38
38
  "provenance": true
39
39
  },
40
40
  "dependencies": {
41
- "@alvin0/ai-agent-sdk-provider-http": "^0.1.2",
42
- "@alvin0/ai-agent-sdk-protocol-responses": "^0.1.2"
41
+ "@alvin0/ai-agent-sdk-provider-http": "^0.1.4",
42
+ "@alvin0/ai-agent-sdk-protocol-responses": "^0.1.4",
43
+ "@alvin0/ai-agent-sdk-protocol-openai-chat-completions": "^0.1.4"
43
44
  },
44
45
  "peerDependencies": {
45
- "@alvin0/ai-agent-sdk-core": "^0.1.2"
46
+ "@alvin0/ai-agent-sdk-core": "^0.1.4"
46
47
  },
47
48
  "devDependencies": {
48
- "@alvin0/ai-agent-sdk-core": "^0.1.2",
49
- "@alvin0/ai-agent-sdk-testkit": "^0.1.2",
49
+ "@alvin0/ai-agent-sdk-core": "^0.1.4",
50
+ "@alvin0/ai-agent-sdk-testkit": "^0.1.4",
50
51
  "@arethetypeswrong/cli": "0.18.5",
51
52
  "playwright": "1.62.1",
52
53
  "publint": "0.3.24",