@fayegram-ai/decision-bridge 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  Notable user-facing changes by version.
4
4
 
5
+ ## 0.1.1
6
+
7
+ - Add OpenAI `default`/`luna` selectors and requested/resolved model metadata; improve CLI configuration and documentation.
8
+
5
9
  ## 0.1.0
6
10
 
7
11
  Initial release of Decision Bridge as `@fayegram-ai/decision-bridge`.
package/README.md CHANGED
@@ -29,7 +29,7 @@ embedded WAV/MP3 audio and MP4/WebM video, ordered images → audio → videos
29
29
  The common API checks provider/model capabilities before dispatch; refusals are
30
30
  errors.
31
31
 
32
- **Version 0.1.0 · Node.js 22.16+ or modern browsers (SDK) · ESM · zero runtime dependencies**
32
+ **Version 0.1.1 · Node.js 22.16+ or modern browsers (SDK) · ESM · zero runtime dependencies**
33
33
 
34
34
  The npm package is
35
35
  [@fayegram-ai/decision-bridge](https://www.npmjs.com/package/@fayegram-ai/decision-bridge).
@@ -41,24 +41,30 @@ This is an independent project. The custom generative presets identify their
41
41
  probabilities as elicited model outputs.
42
42
  Changing providers does **not** make their accuracy or calibration equivalent.
43
43
 
44
- ## Supported models
44
+ ## Model selectors
45
45
 
46
- The built-in decision providers support these model selectors in this release:
46
+ Use these model selectors with the built-in decision providers:
47
47
 
48
- | Decision provider | Model selector | Accepted alias | Provider default |
49
- | --- | --- | --- | --- |
50
- | Jev through TypeSafe (`jev`) | `jev-latest` | — | `jev-latest` |
51
- | OpenAI Decisions (`openai-decisions`) | `gpt-6-luna` | — | `gpt-6-luna` |
52
- | Cloudflare (`cloudflare-decisions`) | `clef` | `@cf/cloudflare/clef` | `clef` |
53
- | Cloudflare (`cloudflare-decisions`) | `clef-flash` | `@cf/cloudflare/clef-flash` | — |
54
- | Cloudflare (`cloudflare-decisions`) | `clef-omni` | `@cf/cloudflare/clef-omni` | — |
48
+ | Decision provider | Model selectors | Provider default |
49
+ | --- | --- | --- |
50
+ | Jev through TypeSafe (`jev`) | `jev-latest` | `jev-latest` |
51
+ | OpenAI Decisions (`openai-decisions`) | `default`, `luna` | `default` |
52
+ | Cloudflare (`cloudflare-decisions`) | `clef`, `@cf/cloudflare/clef` | `clef` |
53
+ | Cloudflare (`cloudflare-decisions`) | `clef-flash`, `@cf/cloudflare/clef-flash` | — |
54
+ | Cloudflare (`cloudflare-decisions`) | `clef-omni`, `@cf/cloudflare/clef-omni` | — |
55
55
 
56
56
  `jev-latest` is Jev's default alias. Jev also accepts explicit model IDs available through your TypeSafe account;
57
57
  use `bridge.listModels()` or the CLI `models` command to inspect that catalog.
58
58
  The bridge does not maintain a static list of Jev versions. A moving alias or a
59
59
  catalog entry does not guarantee inference access or reproducible model quality.
60
- OpenAI Decisions currently accepts only `gpt-6-luna` in this integration.
61
- Cloudflare accepts only the three models above, in short or full-alias form.
60
+ For OpenAI Decisions, `default` selects the provider default and `luna` selects
61
+ the supported Luna-family model. Decision Bridge resolves selectors to supported
62
+ upstream model IDs before calling the service. Mappings are fixed by the installed
63
+ Decision Bridge release. Explicit supported model IDs can also be selected; see
64
+ the [OpenAI model selection guide](docs/guides/OPENAI_DECISIONS.md#model-selection)
65
+ for details. Results retain the selected value in `meta.requestedModel`,
66
+ the outgoing ID in `meta.resolvedModel`, and the service-reported model in `model`.
67
+ Cloudflare accepts only the three models above, in short or full form.
62
68
  Model discovery is unsupported for OpenAI Decisions and Cloudflare.
63
69
 
64
70
  Additional decision protocols use [custom decision providers](docs/guides/CUSTOM_DECISION.md).
package/docs/README.md CHANGED
@@ -7,8 +7,8 @@ Install `@fayegram-ai/decision-bridge` with npm; the
7
7
  [getting started guide](guides/GETTING_STARTED.md#install-into-your-application)
8
8
  also covers installation from a verified archive.
9
9
  The hosted playground is [Decision Playground](https://decision-pg.fayegram.com/).
10
- The root README lists [supported model selectors and aliases](../README.md#supported-models),
11
- including the distinction between built-in decision models and custom generative presets.
10
+ See [model selectors and provider defaults](../README.md#model-selectors) for
11
+ built-in decision models and the separate custom generative presets.
12
12
 
13
13
  Decision Bridge is a JavaScript SDK with an optional command runner. It evaluates
14
14
  supplied `state` or ordered `input`, plus `questions`, with an explicitly selected
@@ -27,7 +27,7 @@ the OpenAI and Cloudflare guides cover both.
27
27
  | [Browser integration](guides/BROWSER.md) | Direct browser use, CORS requirements, and credential exposure |
28
28
  | [Platforms](guides/PLATFORMS.md) | Windows, macOS, and Linux setup |
29
29
  | [Switching models](guides/SWITCHING_MODELS.md) | Backend and model selection |
30
- | [OpenAI Decisions](guides/OPENAI_DECISIONS.md) | Native/common decision access, ordered images, typed values, and capability checks |
30
+ | [OpenAI Decisions](guides/OPENAI_DECISIONS.md) | Model selectors and upstream targets, selection metadata, common/native access, ordered images, and typed values |
31
31
  | [Cloudflare Clef / Clef-flash / Clef-omni](guides/CLOUDFLARE_DECISIONS.md) | Common/native decisions, embedded images and Omni audio/video, REST and Workers AI binding, busy-mode compatibility, and optional Gateway |
32
32
  | [Customization](guides/CUSTOMIZATION.md) | Questions, templates, extensions, and policies |
33
33
  | [Custom providers](guides/CUSTOM_PROVIDERS.md) | Common classified contract and explicit CLI module loading |
@@ -5,7 +5,7 @@
5
5
  Decision Bridge exposes one application contract for structured decisions. The
6
6
  bridge owns request snapshots, common validation, extension sequencing, the total
7
7
  deadline, final result validation, immutable results, and bounded observations.
8
- A Provider owns its API encoding, transport, decoding, model defaults,
8
+ A provider owns its API encoding, transport, decoding, model selection,
9
9
  capabilities, and native operations. Multiple models sharing a protocol can use
10
10
  one provider implementation.
11
11
 
@@ -46,6 +46,26 @@ shared evaluation deadline and extension lifecycle.
46
46
  Application policy decides whether a downstream action is permitted. The SDK
47
47
  returns decisions without executing actions or maintaining an agent loop.
48
48
 
49
+ ## Model selection and request preparation
50
+
51
+ The bridge applies the provider's configured default when a request omits `model`.
52
+ Its validated request and extension context retain that selected string. A provider
53
+ may resolve a selection to an upstream ID while preparing its outgoing request;
54
+ the shared provider contract has no selector-resolution hook.
55
+
56
+ OpenAI's `openai-profile.mjs` owns the `default` selection, accepted selectors,
57
+ upstream targets, and capabilities. Common and native preparation retain the
58
+ validated selection separately from the resolved native request. Preflight returns
59
+ the selected request, while serialization and byte-limit checks use the resolved
60
+ request. Common evaluation and native `createDecision()` share dispatch, which
61
+ sends only that resolved request.
62
+
63
+ Successful OpenAI calls report the selected string in `meta.requestedModel` and
64
+ the outgoing ID in `meta.resolvedModel`. The response's `model` retains the
65
+ service-reported identity. CLI offline validation and execution use the same
66
+ preflight, and generated OpenAI requests inherit selection from configuration.
67
+ See [OpenAI model selection](../guides/OPENAI_DECISIONS.md#model-selection).
68
+
49
69
  ## Module map
50
70
 
51
71
  | Module | Responsibility |
@@ -61,6 +81,7 @@ returns decisions without executing actions or maintaining an agent loop.
61
81
  | `src/custom/generative/` | Opt-in protocol presets, schema, prompts, and result derivation |
62
82
  | `schemas/decision.config.schema.json` | Shipped CLI configuration structure and editor support |
63
83
  | `src/providers/` | API orchestration, model profiles, preflight, codecs, bounded image/audio/video inspection, and transports |
84
+ | `src/providers/openai-profile.mjs`, `src/providers/decisions-preflight.mjs` | Release-owned OpenAI selection, capabilities, and preparation of selected and outgoing requests |
64
85
  | `src/embedded.mjs` | Shared embedded data URL and canonical base64 checks |
65
86
  | `src/transport.mjs`, `src/response-transport.mjs` | Credential destinations, bounded bodies, safe errors, retries |
66
87
  | `src/async.mjs` | Deadlines and cancellation races |
@@ -43,6 +43,17 @@ Read JSON config only when named. Load custom ESM only when explicitly selected.
43
43
  Protect credential destinations and expose bounded callbacks. Consequence: plugins
44
44
  are powerful and must be trusted; cancellation is not a JavaScript sandbox.
45
45
 
46
+ ## Release-owned model selectors
47
+
48
+ OpenAI Decisions offers `default` for the provider's default supported model and
49
+ `luna` for the supported Luna-family model, alongside explicit supported upstream
50
+ IDs. The installed package fixes the target and capability profile for each
51
+ selector. The provider resolves it locally before serialization; no discovery,
52
+ automatic upgrade, or fallback occurs during a call. Consequence: applications can
53
+ use a stable selection string while inspecting selected, outgoing, and reported
54
+ model identities separately. A future target change requires an intentional
55
+ package update and verification of that model's protocol and capabilities.
56
+
46
57
  ## Conservative retries and independent observations
47
58
 
48
59
  Retry selected explicit transient HTTP statuses within a deadline. Do not retry
@@ -42,7 +42,7 @@ as evidence that every environment passed.
42
42
  - `transport.test.mjs`: HTTP policy, limits, retries, cancellation, and deadlines.
43
43
  - `deadlines.test.mjs`: synchronous preparation/dispatch/completion and inherited budgets.
44
44
  - `result-validation.test.mjs`: provider-selected precision and policy-hook boundaries.
45
- - `decisions*.test.mjs`: native/common OpenAI codecs, boundaries, preflight, CLI, and smoke-helper fixtures.
45
+ - `decisions*.test.mjs`: native/common OpenAI codecs, selectors and model identities, boundaries, preflight, CLI, and smoke-helper fixtures.
46
46
  - `cloudflare*.test.mjs`: models, images, codecs, REST/binding/Gateway arguments, errors, and CLI limits.
47
47
  - `custom-providers.test.mjs`, `custom-config.test.mjs`: classified SDK paths, provenance, presets/modules, schema and initialization.
48
48
  - `generative-helpers.test.mjs`: exported prompt/normalization helper contracts and unsupported input rejection.
@@ -63,6 +63,28 @@ All model responses used by ordinary tests are synthetic. HTTP integration serve
63
63
  listen on loopback. Tests do not require account keys or make paid model calls.
64
64
  Test workspaces are temporary.
65
65
 
66
+ ### OpenAI model-selector coverage
67
+
68
+ For changes to OpenAI selection or preparation, run the focused suites from the
69
+ source root:
70
+
71
+ ```text
72
+ node --test test/decisions-selectors.test.mjs test/decisions-preflight.test.mjs test/decisions-cli.test.mjs
73
+ ```
74
+
75
+ These cover omitted, `default`, `luna`, and explicit upstream selection; unsupported
76
+ and malformed values; independently selected, outgoing, and reported identities;
77
+ capabilities and extension context; immutable input; SDK batch preflight; resolved
78
+ wire-byte limits; CLI precedence and mixed-selector rows; and generated requests
79
+ that inherit later configuration changes. Existing Decisions suites also cover
80
+ revalidation after input preparation, refusals, retries, and deadlines.
81
+
82
+ Type and installed-package checks exercise metadata declarations, SDK/CLI
83
+ selection, and browser execution without Node globals. Use `npm run verify` for
84
+ the complete integration check. A selector mapping change must be verified against
85
+ the target model's protocol and capabilities; local fixtures alone do not establish
86
+ compatibility with a newly selected upstream model.
87
+
66
88
  ## Optional authenticated provider smoke check
67
89
 
68
90
  Live inference is explicitly opted in and can incur charges. Configure credentials
@@ -4,8 +4,8 @@ Use `cloudflare-decisions` for hosted inference with `clef` (the default),
4
4
  `clef-flash`, or `clef-omni`. All three support common bridge requests and the
5
5
  dedicated native API. Each accepts its full `@cf/cloudflare/<model>` alias.
6
6
  The provider normalizes the endpoint identifier and the required input selector together.
7
- See the root README's [model list](../../README.md#supported-models) for the exact
8
- short selectors and full aliases.
7
+ See the [model selector inventory](../../README.md#model-selectors) for supported
8
+ short selectors and full forms.
9
9
 
10
10
  | Model | Hosted context, in tokens | Default client request budget | Media |
11
11
  | --- | ---: | ---: | --- |
@@ -17,7 +17,7 @@ extension path.
17
17
  `Provider` requires a nonempty `name`, explicit `kind: 'decision' | 'generative'`,
18
18
  and `evaluate(request, { signal? })`. Optional fields and hooks are:
19
19
 
20
- - `defaultModel`: a nonempty model ID, otherwise the request must supply one.
20
+ - `defaultModel`: a nonempty selection string, otherwise the request must supply one.
21
21
  - `probabilitySource`: known provenance; generative providers use `elicited`.
22
22
  - `getCapabilities(model)`: a pure synchronous capability profile.
23
23
  - `validateRequest(request)`: pure synchronous provider preflight; no inference.
@@ -31,7 +31,9 @@ that implement this contract directly. Missing or invalid kinds fail at construc
31
31
  `bridge.providerKind` and `result.meta.providerKind` come from the contract and
32
32
  cannot be overridden by response metadata.
33
33
 
34
- The request is already snapshotted, validated, resolved to a model, and frozen.
34
+ The request is already snapshotted, validated, supplied with an effective model
35
+ selection, and frozen. Its model string retains the selection; any mapping to an
36
+ upstream ID belongs to the provider's preparation and transport.
35
37
  Return canonical `{ model, answers, usage, meta? }`; the bridge validates and
36
38
  freezes the result. Custom providers own API encoding, transport, response mapping,
37
39
  credential destinations, bounded bodies, safe errors, and cancellation cleanup.
@@ -25,7 +25,7 @@ You can also install a verified local archive, replacing the path below. See
25
25
  a source checkout:
26
26
 
27
27
  ```bash
28
- npm install /path/to/fayegram-ai-decision-bridge-0.1.0.tgz
28
+ npm install /path/to/fayegram-ai-decision-bridge-0.1.1.tgz
29
29
  ```
30
30
 
31
31
  The installed package contains the SDK, declarations, command runner, examples,
@@ -1,13 +1,50 @@
1
1
  # OpenAI Decisions
2
2
 
3
- The `openai-decisions` provider implements the native `POST /v1/decisions` API,
4
- supporting `gpt-6-luna` as its default and only accepted model in this release.
3
+ The `openai-decisions` provider implements the native `POST /v1/decisions` API.
4
+ Its default model selector is `default`; `luna` selects the supported Luna-family
5
+ model, and `gpt-6-luna` selects that explicit upstream ID. Decision Bridge resolves
6
+ all three selections to `gpt-6-luna` in this release before calling the service.
5
7
  This integration follows the official [Decisions guide](https://developers.openai.com/api/docs/guides/decisions)
6
8
  and [API reference](https://developers.openai.com/api/reference/resources/decisions/methods/create).
7
9
  Account access and remote limits still apply. Unknown models fail locally with
8
10
  `UNSUPPORTED_CAPABILITY`. The opt-in `openai-compatible` preset in `custom/generative` continues
9
11
  using Chat Completions.
10
12
 
13
+ ## Model selection
14
+
15
+ ```javascript
16
+ import { createProvider } from '@fayegram-ai/decision-bridge';
17
+
18
+ createProvider({ type: 'openai-decisions' }); // Selects default.
19
+ createProvider({ type: 'openai-decisions', model: 'luna' });
20
+ createProvider({ type: 'openai-decisions', model: 'gpt-6-luna' });
21
+ ```
22
+
23
+ These are alternative configurations. An omitted request `model` inherits the
24
+ provider's configured selection; an explicit request `model` overrides it.
25
+ With no provider or request model configured, the selection is `default`:
26
+
27
+ | Selection | Selected value | Current upstream target |
28
+ | --- | --- | --- |
29
+ | Omitted, with no configured model | `default` | `gpt-6-luna` |
30
+ | `default` | `default` | `gpt-6-luna` |
31
+ | `luna` | `luna` | `gpt-6-luna` |
32
+ | `gpt-6-luna` | `gpt-6-luna` | `gpt-6-luna` |
33
+
34
+ Mappings are fixed by the installed Decision Bridge release; there is
35
+ no runtime model discovery, automatic upgrading, or fallback after failure.
36
+ The `default` selector follows the provider default, while `luna` stays within
37
+ the Luna family. The resolved upstream model must have a supported capability
38
+ profile before a selector can target it.
39
+
40
+ `provider.defaultModel` and `bridge.defaultModel` expose the configured selection,
41
+ using `default` when provider configuration omits `model`. Validated requests and
42
+ extension context retain the effective selected value. Successful common
43
+ and native calls expose `meta.requestedModel` for that selection and
44
+ `meta.resolvedModel` for the upstream ID sent. The common `result.model` and native
45
+ `decision.model` preserve the service-reported identity, which may differ from
46
+ the outgoing ID. Byte limits apply to the resolved wire request.
47
+
11
48
  ## Common bridge access
12
49
 
13
50
  Set `OPENAI_API_KEY` in the process environment:
@@ -141,14 +178,15 @@ const { decision, meta } = await provider.createDecision({
141
178
  }, { signal: controller.signal });
142
179
  ```
143
180
 
144
- An omitted model resolves to the provider default. Native input accepts text or
181
+ An omitted model inherits the provider's configured selection. Native input accepts text or
145
182
  multiple user messages containing text or ordered `input_text`/`input_image`
146
183
  parts. Only user roles are supported. Files, external image URLs, audio, tools,
147
184
  and item references are rejected locally. Exported `NativeDecisionRequest` and
148
185
  related types describe this format.
149
186
 
150
187
  `decision` preserves native answer arrays, optional question names, probabilities,
151
- confidence, and full usage. `meta` contains transport duration, attempt count,
188
+ confidence, and full usage. `meta` has the exported `OpenAIDecisionsTransportMetadata`
189
+ shape: requested/resolved model identities, transport duration, attempt count,
152
190
  and request ID when available. Results are immutable. This method bypasses
153
191
  common bridge extensions.
154
192
 
@@ -197,6 +235,10 @@ provider. Native evaluation/validation/batch require an explicit file or `-`.
197
235
  Mode is never inferred from JSON. Initialization writes a native request, which
198
236
  must still be evaluated with `--native`. Offline validation checks built-in
199
237
  capabilities without credentials; custom capabilities are checked at evaluation.
238
+ OpenAI initialization stores the model selection in configuration and omits it
239
+ from the generated request so later configuration changes apply. The default
240
+ configuration uses `default`. Offline validation reports the selected value as
241
+ `model` and the outgoing upstream ID as `resolvedModel`.
200
242
  Decisions offline validation also checks native field limits and the configured
201
243
  request byte limit in both modes.
202
244
 
@@ -22,7 +22,7 @@ compatible native Node runtime. See [testing](../development/TESTING.md).
22
22
  Open PowerShell at the cloned repository or extracted source directory:
23
23
 
24
24
  ```powershell
25
- cd "C:\path\to\decision-bridge-0.1.0"
25
+ cd "C:\path\to\decision-bridge-0.1.1"
26
26
  node --version
27
27
  node bin/decision.mjs validate examples/request.json
28
28
  Copy-Item .env.example .env
@@ -48,7 +48,7 @@ the archive rather than reusing generated `node_modules` or executable shims.
48
48
  ## macOS / Linux
49
49
 
50
50
  ```bash
51
- cd "/path/to/decision-bridge-0.1.0"
51
+ cd "/path/to/decision-bridge-0.1.1"
52
52
  node --version
53
53
  node bin/decision.mjs validate examples/request.json
54
54
  cp .env.example .env
@@ -4,7 +4,7 @@ The bridge's request contains `questions`, either `state` or ordered `input`, an
4
4
  optionally `model`. Ordered input and richer question types require provider capabilities.
5
5
  The provider translates it into that service's wire protocol and validates the
6
6
  response. Select a provider when constructing your SDK bridge.
7
- The root README lists [supported model selectors and aliases](../../README.md#supported-models).
7
+ See [model selectors and provider defaults](../../README.md#model-selectors).
8
8
 
9
9
  ## Native and custom selection
10
10
 
@@ -72,7 +72,7 @@ example and provide that backend's credential in `.env`:
72
72
  | Native SDK `createProvider` type | Credential variable | Model selection |
73
73
  | --- | --- | --- |
74
74
  | `jev` | `TYPESAFE_API_KEY` | Defaults to `jev-latest`; an explicit model overrides it |
75
- | `openai-decisions` | `OPENAI_API_KEY` | Defaults to `gpt-6-luna` |
75
+ | `openai-decisions` | `OPENAI_API_KEY` | Defaults to `default`; also accepts `luna` and explicit `gpt-6-luna` |
76
76
  | `cloudflare-decisions` | `CLOUDFLARE_API_TOKEN` and `CLOUDFLARE_ACCOUNT_ID` for REST | Defaults to `clef`; also supports `clef-flash` and `clef-omni` |
77
77
 
78
78
  Custom generative presets use `createGenerativeProvider` instead. Their types are
@@ -83,7 +83,7 @@ using the following native alternatives:
83
83
 
84
84
  ```javascript
85
85
  createProvider({ type: 'jev', model: 'jev-latest' });
86
- createProvider({ type: 'openai-decisions', model: 'gpt-6-luna' });
86
+ createProvider({ type: 'openai-decisions', model: 'luna' });
87
87
  createProvider({ type: 'cloudflare-decisions', model: 'clef-flash' });
88
88
  createGenerativeProvider({ type: 'openai-compatible', model: process.env.DECISION_MODEL });
89
89
  createGenerativeProvider({ type: 'anthropic', model: process.env.DECISION_MODEL });
@@ -96,6 +96,12 @@ The SDK does not automatically read `DECISION_*` settings: this example passes
96
96
  `process.env.DECISION_MODEL` explicitly. The SDK factory property is **`type`**;
97
97
  a command-runner JSON profile uses **`provider`**.
98
98
 
99
+ OpenAI's `default` selector chooses the provider default and `luna` chooses the
100
+ supported Luna-family model. Both currently resolve to upstream `gpt-6-luna`
101
+ through Decision Bridge. Use `model: 'gpt-6-luna'` to select that explicit upstream
102
+ ID. Mappings are fixed by the installed package release; results expose the
103
+ selection in `meta.requestedModel` and outgoing ID in `meta.resolvedModel`.
104
+
99
105
  For Jev and the generative providers, list models with `await bridge.listModels()`.
100
106
  OpenAI Decisions and Cloudflare Clef do not implement model discovery and return
101
107
  `UNSUPPORTED_OPERATION`; their capability profiles do not prove account availability.
@@ -143,7 +149,7 @@ uppercase model placeholders with IDs available to your account/server:
143
149
 
144
150
  ```bash
145
151
  npm exec -- decision-bridge evaluate ./my-decisions/request.json --provider jev --model jev-latest --env-file .env
146
- npm exec -- decision-bridge evaluate ./my-decisions/request.json --provider openai-decisions --model gpt-6-luna --env-file .env
152
+ npm exec -- decision-bridge evaluate ./my-decisions/request.json --provider openai-decisions --model default --env-file .env
147
153
  npm exec -- decision-bridge evaluate ./my-decisions/request.json --provider cloudflare-decisions --model clef-flash --env-file .env
148
154
  npm exec -- decision-bridge evaluate ./my-decisions/request.json --provider custom-generative --preset openrouter --model VENDOR/MODEL_ID --env-file .env
149
155
  npm exec -- decision-bridge evaluate ./my-decisions/request.json --provider custom-generative --preset openai-compatible --model MODEL_ID --env-file .env
@@ -18,6 +18,20 @@ explicit config. A compatible base includes its version prefix, not the final
18
18
  `/chat/completions` path. Jev and the custom Anthropic preset require origins
19
19
  without a path. See [configuration](../reference/CONFIGURATION.md#precedence).
20
20
 
21
+ ## OpenAI reports a versioned model after I select default or luna
22
+
23
+ `default` and `luna` are Decision Bridge model selectors. Both currently resolve
24
+ to `gpt-6-luna`, which is the upstream ID sent to OpenAI. Successful common and
25
+ native calls expose the selection as `meta.requestedModel` and the outgoing ID
26
+ as `meta.resolvedModel`. `result.model` or native `decision.model` preserves the
27
+ service-reported identity, which may differ from the outgoing ID.
28
+
29
+ An omitted request model inherits the configured model. OpenAI initialization
30
+ omits `model` from its generated requests so later configuration edits apply.
31
+ If selection fails with `UNSUPPORTED_CAPABILITY`, check the
32
+ [accepted selectors](OPENAI_DECISIONS.md#model-selection); the bridge does not
33
+ discover another model or fall back after failure.
34
+
21
35
  ## Structured output is rejected
22
36
 
23
37
  Check that the selected endpoint/model supports the requested `json_schema` mode.
@@ -20,8 +20,9 @@ it with `node bin/decision.mjs` from a source checkout. `decision` and
20
20
  The CLI defaults to Jev. Flags override `DECISION_*` environment settings, which
21
21
  override an explicit config file and then built-in defaults. It constructs an
22
22
  explicit provider for the SDK; `createBridge` itself has no implicit selection.
23
- Use the root README's [model list](../../README.md#supported-models) to select a
24
- supported native model. Custom generative presets require an explicit model ID.
23
+ Choose a supported native model from the
24
+ [model selector inventory](../../README.md#model-selectors).
25
+ Custom generative presets require an explicit model ID.
25
26
 
26
27
  ## Commands
27
28
 
@@ -32,6 +33,12 @@ for native request/response structures on `init`, `evaluate`, `validate`, or
32
33
  or `-`. See [OpenAI Decisions](../guides/OPENAI_DECISIONS.md) and
33
34
  [Cloudflare Clef](../guides/CLOUDFLARE_DECISIONS.md) for examples. Both providers report
34
35
  model listing as unsupported.
36
+ OpenAI model selection defaults to `default`; use `--model luna` for the Luna
37
+ family or an explicit supported upstream ID. Both stable selectors currently
38
+ resolve to `gpt-6-luna` within Decision Bridge. OpenAI `validate` reports the
39
+ selected value in `model` and the outgoing ID in `resolvedModel`, without credentials.
40
+ Successful common and native calls include `meta.requestedModel` and
41
+ `meta.resolvedModel`; response models retain the service-reported identity.
35
42
  Cloudflare CLI execution uses REST. The Workers AI binding is an SDK transport;
36
43
  there is no CLI binding selector. Gateway settings route the selected REST request.
37
44
 
@@ -71,6 +78,10 @@ explicit service implementation error until you connect it. An existing
71
78
  `--provider-module` is referenced without execution or overwrite. All profiles
72
79
  include a copied JSON Schema for editor validation. See [custom providers](../guides/CUSTOM_PROVIDERS.md).
73
80
 
81
+ OpenAI initialization writes the selected model into configuration, using `default`
82
+ when omitted. Its generated common and native requests omit `model`, allowing
83
+ later configuration changes to control selection.
84
+
74
85
  ## Flags
75
86
 
76
87
  | Flag | Meaning |
@@ -80,7 +91,7 @@ include a copied JSON Schema for editor validation. See [custom providers](../gu
80
91
  | `--provider NAME` | `jev`, `openai-decisions`, `cloudflare-decisions`, `custom-decision`, `custom-generative` |
81
92
  | `--preset NAME` | custom-generative preset: `openai-compatible`, `openrouter`, or `anthropic`; exclusive with module |
82
93
  | `--native` | Explicit native Decisions input/output; init/evaluate/validate/batch only |
83
- | `--model ID` | Overrides input JSON model and provider default |
94
+ | `--model SELECTOR` | Model selector or supported upstream ID; overrides input JSON model and configured default |
84
95
  | `--base-url URL` | Custom endpoint; requires explicit credential selection |
85
96
  | `--api-key-env NAME` | Environment variable name, never the key itself |
86
97
  | `--account-id ID` | Cloudflare REST account ID |
@@ -40,7 +40,7 @@ profile. Gateway configuration is optional for either SDK transport and for CLI
40
40
  | `schemaVersion` | 1; optional, but generated profiles include it |
41
41
  | `provider` | `jev` default, `openai-decisions`, `cloudflare-decisions`, `custom-decision`, `custom-generative` |
42
42
  | `preset` | `custom-generative` only: `openai-compatible`, `openrouter`, `anthropic`; mutually exclusive with module |
43
- | `model` | Nonempty model ID; built-in native providers have defaults; required for generative evaluation unless the request supplies it |
43
+ | `model` | Nonempty model selector or supported upstream ID; built-in native providers have defaults; required for generative evaluation unless the request supplies it |
44
44
  | `baseURL` | HTTPS API base; loopback may use HTTP |
45
45
  | `apiKeyEnv` | Credential environment variable name; inline `apiKey` is rejected |
46
46
  | `noAuth` | Boolean; true only for loopback OpenAI-compatible endpoints |
@@ -88,8 +88,8 @@ existing process environment > --env-file values
88
88
  The loader does not mutate the process environment. The SDK independently reads
89
89
  its documented ambient credential variables at provider construction.
90
90
 
91
- The [supported-model list](../../README.md#supported-models) records native
92
- selectors, aliases and defaults. Configuration precedence does not expand a
91
+ The [model selector list](../../README.md#model-selectors) records native
92
+ selectors and defaults. Configuration precedence does not expand a
93
93
  provider's accepted models or grant account access.
94
94
 
95
95
  For evaluation model selection:
@@ -99,7 +99,9 @@ For evaluation model selection:
99
99
  ```
100
100
 
101
101
  Jev defaults to `jev-latest`, with `JEV_MODEL` as a legacy/direct convenience.
102
- Native OpenAI Decisions defaults to `gpt-6-luna`; it accepts transport settings
102
+ Native OpenAI Decisions defaults to `default` and accepts `luna` or explicit
103
+ `gpt-6-luna`; Decision Bridge currently resolves each to upstream `gpt-6-luna`.
104
+ OpenAI accepts transport settings
103
105
  and rejects generative output settings. Its default retry count is zero. Cloudflare Clef defaults to `clef`, uses
104
106
  account/token credentials, rejects generation settings, and also defaults to zero retries.
105
107
  A `DECISION_MODEL` or file model takes precedence over that convenience.
@@ -77,6 +77,9 @@ validation failures can still return HTTP 422/5012 and retain safe diagnostics.
77
77
  Keep the default disabled setting for ordinary REST
78
78
  inference; see the [Cloudflare guide](../guides/CLOUDFLARE_DECISIONS.md#capacity-rejection-and-ai-gateway).
79
79
 
80
- Keep a request ID and available Gateway log ID, bridge version, configured provider, requested/reported model,
81
- and template revision for incident analysis. Log input or results only under your
80
+ Keep a request ID and available Gateway log ID, bridge version, configured provider,
81
+ model selection, reported model, and template revision for incident analysis.
82
+ For successful OpenAI calls, retain
83
+ the requested selection, resolved outgoing ID, and reported model separately;
84
+ see [model identities](../guides/OPENAI_DECISIONS.md#model-selection). Log input or results only under your
82
85
  own data-handling policy. No logging or telemetry is enabled by default.
@@ -2,10 +2,10 @@
2
2
 
3
3
  ## Native routes
4
4
 
5
- | Provider type | Wire API | Credential | Default model | Output strategy |
5
+ | Provider type | Wire API | Credential | Default selector | Output strategy |
6
6
  | --- | --- | --- | --- | --- |
7
7
  | `jev` | `POST /v1/systemone` at api.typesafe.ai | `TYPESAFE_API_KEY` | `jev-latest` | Native typed decisions |
8
- | `openai-decisions` | `POST /v1/decisions` at api.openai.com | `OPENAI_API_KEY` | `gpt-6-luna` | Native Decisions with common and dedicated access |
8
+ | `openai-decisions` | `POST /v1/decisions` at api.openai.com | `OPENAI_API_KEY` | `default` | Native Decisions with common and dedicated access |
9
9
  | `cloudflare-decisions` | Workers AI REST model route or `AI.run` binding | `CLOUDFLARE_API_TOKEN` and account ID for REST | `clef` | Native Clef decisions with common and dedicated access |
10
10
 
11
11
  `createProvider()` and the root SDK export these three native providers only.
@@ -15,9 +15,12 @@ maintained, but are not native decision models. See [custom providers](../guides
15
15
 
16
16
  ## Supported model selection
17
17
 
18
- See the root README's [model list](../../README.md#supported-models) for the
19
- built-in selectors and aliases. Jev accepts `jev-latest` and explicit Jev IDs from
20
- its account catalog. OpenAI Decisions supports only `gpt-6-luna` in this release.
18
+ The [model selector inventory](../../README.md#model-selectors) summarizes the
19
+ built-in providers. Jev accepts `jev-latest` and explicit Jev IDs from
20
+ its account catalog. OpenAI Decisions accepts `default`, `luna`, and explicit
21
+ `gpt-6-luna`. Decision Bridge resolves these to the upstream `gpt-6-luna` model
22
+ in this release. The selected value remains in `meta.requestedModel`, the outgoing
23
+ ID in `meta.resolvedModel`, and the service-reported identity in the response's `model`.
21
24
  Cloudflare supports `clef`, `clef-flash`, and `clef-omni`, including each full
22
25
  `@cf/cloudflare/<model>` alias. Unsupported OpenAI/Cloudflare selectors fail
23
26
  local preflight with `UNSUPPORTED_CAPABILITY`.
@@ -72,7 +72,10 @@ they interact; [errors](ERRORS.md#retry-policy) lists retryable statuses.
72
72
  `model` and `safetyIdentifier`. New forms require declared provider capabilities;
73
73
  see [OpenAI Decisions](../guides/OPENAI_DECISIONS.md) and
74
74
  [Cloudflare Clef](../guides/CLOUDFLARE_DECISIONS.md). The request model
75
- wins over the provider's default. No default is assumed for a generative provider.
75
+ wins over the provider's configured default, which an omitted request model inherits.
76
+ OpenAI Decisions uses the `default` selector when its provider configuration omits
77
+ `model`; `luna` and explicit `gpt-6-luna` are also supported. Its selectors
78
+ resolve to upstream IDs within the provider. No default is assumed for a generative provider.
76
79
  `options` accepts `signal: AbortSignal` and `timeoutMs`, overriding the bridge's
77
80
  60-second evaluation budget for this call. It does not mutate the provider's
78
81
  independent transport timeout, including Workers AI binding calls.
@@ -92,10 +95,12 @@ when client retries are configured.
92
95
  ### `bridge.getCapabilities(model?)` and `bridge.validate(request)`
93
96
 
94
97
  Capability lookup is synchronous and reports implemented features and limits.
95
- `validate` resolves the model, snapshots/freezes the request, and checks capabilities
98
+ `validate` applies the default model selection, snapshots/freezes the request, and checks capabilities
96
99
  and preparation-extension compatibility without inference. Unsupported features
97
100
  fail with `UNSUPPORTED_CAPABILITY` before dispatch. Native Decisions has no model
98
101
  listing operation; capability lookup is not account model discovery.
102
+ OpenAI validated requests retain the selected value, while preflight checks the
103
+ resolved outgoing request, including its model ID, against the byte limit.
99
104
 
100
105
  Providers can implement optional `validateRequest(resolvedRequest)` for additional
101
106
  static checks. This synchronous hook receives a frozen request, must return no
@@ -207,7 +212,8 @@ estimated token count is fabricated.
207
212
  | --- | --- |
208
213
  | `provider` | Bridge backend name; native Jev reports `typesafe` |
209
214
  | `providerKind` | `decision` or `generative`; authoritative provider classification, never response-selected |
210
- | `requestedModel` | Resolved model requested by the caller |
215
+ | `requestedModel` | Selected model after applying the provider default; OpenAI retains `default`, `luna`, or the explicit ID |
216
+ | `resolvedModel` | Upstream model ID sent by OpenAI Decisions; optional for other providers |
211
217
  | `probabilitySource` | `native`, `elicited`, or custom/`unspecified` provenance |
212
218
  | `confidenceSource` | `native` or `unavailable` for built-in providers |
213
219
  | `modelSource` | `reported` or `requested` fallback for built-in providers |
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "provider": "openai-decisions",
4
- "model": "gpt-6-luna",
4
+ "model": "default",
5
5
  "apiKeyEnv": "OPENAI_API_KEY",
6
6
  "maxRetries": 0
7
7
  }
@@ -1,5 +1,5 @@
1
1
  {
2
- "model": "gpt-6-luna",
2
+ "model": "default",
3
3
  "input": "Export fails in Safari but works in Chrome.",
4
4
  "questions": [
5
5
  { "type": "predicate", "name": "browser_specific", "instructions": "Is the failure browser-specific?" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fayegram-ai/decision-bridge",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "JavaScript SDK and CLI for structured decisions across native decision APIs and configurable model providers",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,6 +1,7 @@
1
1
  import { DecisionError, requireCondition as check } from './errors.mjs';
2
2
  import { cloneJson, freezeJson, isRecord } from './validation.mjs';
3
3
  import { CLOUDFLARE_MODELS } from './providers/cloudflare-profile.mjs';
4
+ import { openaiModelProfile } from './providers/openai-profile.mjs';
4
5
 
5
6
  /** Conservative defaults preserve the pre-existing custom-provider contract. */
6
7
  export const LEGACY_CAPABILITIES = freezeJson({
@@ -9,11 +10,6 @@ export const LEGACY_CAPABILITIES = freezeJson({
9
10
  maxImages: 0, maxChoiceOptions: 255, maxScoreLevels: 10,
10
11
  audio: false, videos: false, maxAudioClips: 0, maxVideos: 0,
11
12
  });
12
- const OPENAI_CAPABILITIES = freezeJson({
13
- supported: true, orderedInput: true, images: true, typedChoices: true,
14
- labeledScores: true, safetyIdentifier: true,
15
- maxImages: 128, maxChoiceOptions: 255, maxScoreLevels: null,
16
- });
17
13
 
18
14
  const CLOUDFLARE_CAPABILITIES = freezeJson({
19
15
  supported: true, orderedInput: true, images: true, typedChoices: true,
@@ -29,7 +25,8 @@ export function builtInCapabilities(type, model) {
29
25
  maxAudioClips: profile?.omni ? 4 : 0, maxVideos: profile?.omni ? 2 : 0 });
30
26
  }
31
27
  if (type !== 'openai-decisions') return LEGACY_CAPABILITIES;
32
- return freezeJson({ ...LEGACY_CAPABILITIES, ...OPENAI_CAPABILITIES, supported: model === 'gpt-6-luna' });
28
+ const profile = openaiModelProfile(model);
29
+ return freezeJson({ ...LEGACY_CAPABILITIES, ...profile, supported: !!profile });
33
30
  }
34
31
 
35
32
  export function providerCapabilities(provider, model) {
@@ -1,6 +1,7 @@
1
1
  import { readFile, stat } from 'node:fs/promises';
2
2
  import { resolve, dirname } from 'node:path';
3
3
  import { parseEnv } from 'node:util';
4
+ import { OPENAI_DEFAULT_MODEL } from '../providers/openai-profile.mjs';
4
5
  import { DecisionError, requireCondition as check } from '../errors.mjs';
5
6
  import { validateConfig, NUMERIC, PROVIDER_FIELDS } from './config-validation.mjs';
6
7
  export { validateConfig } from './config-validation.mjs';
@@ -78,7 +79,7 @@ export async function loadConfig({ configPath, envFile, overrides = {}, cwd = pr
78
79
  if (config.provider === 'jev' && config.model === undefined && effectiveEnv.JEV_MODEL?.trim()) {
79
80
  config.model = effectiveEnv.JEV_MODEL.trim();
80
81
  }
81
- if (config.provider === 'openai-decisions' && config.model === undefined) config.model = 'gpt-6-luna';
82
+ if (config.provider === 'openai-decisions' && config.model === undefined) config.model = OPENAI_DEFAULT_MODEL;
82
83
  if (config.provider === 'cloudflare-decisions' && config.model === undefined) config.model = 'clef';
83
84
  return { config, env: effectiveEnv, moduleBase };
84
85
  }
@@ -3,11 +3,13 @@ import { encodeCommonDecision } from '../providers/decisions-codec.mjs';
3
3
  import { preflightCloudflareDecision, preflightCommonCloudflareDecision } from '../providers/cloudflare-preflight.mjs';
4
4
  import { encodeCloudflareDecision } from '../providers/cloudflare-codec.mjs';
5
5
  import { CLOUDFLARE_MAX_REQUEST_LIMIT } from '../providers/cloudflare-profile.mjs';
6
+ import { resolveOpenAIModel } from '../providers/openai-profile.mjs';
6
7
 
7
8
  /** Provider-specific native dispatch is shared by validation, initialization and calls. */
8
9
  export function decisionProfile(provider) {
9
10
  if (provider === 'openai-decisions') return {
10
11
  common: preflightCommonDecision, native: preflightDecision, encode: encodeCommonDecision,
12
+ resolveModel: resolveOpenAIModel,
11
13
  options: config => ({ model: config.model, maxRequestBytes: config.maxRequestBytes }),
12
14
  };
13
15
  if (provider === 'cloudflare-decisions') return {
@@ -4,6 +4,7 @@ import { DecisionError } from '../errors.mjs';
4
4
  import { validateRequest } from '../validation.mjs';
5
5
  import { decisionProfile } from './decisions.mjs';
6
6
  import { validateConfig, KEY_ENVS } from './config-validation.mjs';
7
+ import { OPENAI_DEFAULT_MODEL } from '../providers/openai-profile.mjs';
7
8
 
8
9
  /** Templates are honest edit points for an unknown protocol, never fake inference. */
9
10
  function providerTemplate(kind) {
@@ -38,6 +39,7 @@ export async function initialize(directory, config, native = false, moduleBase =
38
39
  ...(generateModule ? { module: './provider.mjs' } : {}),
39
40
  ...(config.module ? { module: resolve(moduleBase, config.module) } : {}),
40
41
  ...(config.provider === 'jev' && config.model === undefined ? { model: 'jev-latest' } : {}),
42
+ ...(config.provider === 'openai-decisions' && config.model === undefined ? { model: OPENAI_DEFAULT_MODEL } : {}),
41
43
  ...(custom && config.model === undefined ? { model: 'YOUR_MODEL' } : {}),
42
44
  }, { complete: true });
43
45
  const request = { state: "ModuleNotFoundError: No module named 'pytest'", questions: {
@@ -47,12 +49,16 @@ export async function initialize(directory, config, native = false, moduleBase =
47
49
  missing_dependency: { type: 'noul', instructions: 'Does this indicate a missing Python package?' },
48
50
  } };
49
51
  const profile = decisionProfile(config.provider);
52
+ const sample = native ? profile.native(profile.encode(validateRequest(request, template.model)),
53
+ profile.options(template)) : request;
54
+ // OpenAI request files inherit selection from configuration, including after later edits.
55
+ const generated = { ...sample };
56
+ if (config.provider === 'openai-decisions') delete generated.model;
50
57
  const schema = await readFile(new URL('../../schemas/decision.config.schema.json', import.meta.url), 'utf8');
51
58
  const files = {
52
59
  'decision.config.json': JSON.stringify(template, null, 2) + '\n',
53
60
  'decision.config.schema.json': schema,
54
- 'request.json': JSON.stringify(native ? profile.native(profile.encode(validateRequest(request, template.model)),
55
- profile.options(template)) : request, null, 2) + '\n',
61
+ 'request.json': JSON.stringify(generated, null, 2) + '\n',
56
62
  '.env.example': `${template.apiKeyEnv}=replace_with_your_api_key\n` +
57
63
  (config.provider === 'cloudflare-decisions' ? 'CLOUDFLARE_ACCOUNT_ID=replace_with_your_account_id\n' : ''),
58
64
  '.gitignore': '.env\n.env.*\n!.env.example\nnode_modules/\n*.tmp\n',
package/src/cli/main.mjs CHANGED
@@ -214,6 +214,7 @@ export async function runCli(argv, { stdin = process.stdin, stdout = process.std
214
214
  ? (await import('../custom/generative/schema.mjs')).buildResponseSchema : undefined;
215
215
  await writeJson(command === 'schema' ? buildResponseSchema(request.questions)
216
216
  : { valid: true, validationScope: isModule(loaded.config) ? 'structure' : 'provider', questionCount: Object.keys(request.questions).length, model: selectedModel,
217
+ ...(profile?.resolveModel ? { resolvedModel: profile.resolveModel(request.model) } : {}),
217
218
  ...(values.native ? { native: true } : {}) });
218
219
  } else {
219
220
  let request;
@@ -39,6 +39,11 @@ export interface OpenAIDecisionResponse {
39
39
  export interface TransportMetadata {
40
40
  readonly attempts: number; readonly durationMs: number; readonly requestId?: string;
41
41
  }
42
+ /** Selected model, resolved outgoing ID, and transport diagnostics for native calls. */
43
+ export interface OpenAIDecisionsTransportMetadata extends TransportMetadata {
44
+ readonly requestedModel: string;
45
+ readonly resolvedModel: string;
46
+ }
42
47
  export type OpenAIDecisionsMetadata = {
43
48
  readonly usage: OpenAIDecisionUsage;
44
49
  readonly answers: Readonly<Record<string, { readonly type: 'predicate' | 'choice' | 'score'; readonly confidence?: number }>>;
package/src/index.d.mts CHANGED
@@ -1,8 +1,9 @@
1
1
  /** JSON values are read-only because the bridge snapshots/freezes its boundaries. */
2
2
  import type { NativeDecisionRequest, OpenAIDecisionResponse, OpenAIDecisionUsage,
3
- OpenAIDecisionsMetadata, TransportMetadata } from './decisions.d.mts';
3
+ OpenAIDecisionsMetadata, OpenAIDecisionsTransportMetadata } from './decisions.d.mts';
4
4
  export type { NativeDecisionInputPart, NativeDecisionMessage, NativeDecisionQuestion, NativeDecisionRequest,
5
- NativeDecisionAnswer, OpenAIDecisionResponse, OpenAIDecisionUsage, OpenAIDecisionsMetadata, TransportMetadata } from './decisions.d.mts';
5
+ NativeDecisionAnswer, OpenAIDecisionResponse, OpenAIDecisionUsage, OpenAIDecisionsMetadata,
6
+ OpenAIDecisionsTransportMetadata, TransportMetadata } from './decisions.d.mts';
6
7
  import type { CloudflareDecisionsOptions, CloudflareDecisionRequest, CloudflareDecisionResponse,
7
8
  CloudflareTransportMetadata, CloudflareDecisionsMetadata } from './cloudflare-decisions.d.mts';
8
9
  export type { CloudflareDecisionModel, CloudflareDecisionImage, CloudflareDecisionAudio, CloudflareDecisionVideo,
@@ -88,6 +89,8 @@ export interface ProviderResult<Q extends Questions = Questions> {
88
89
  }
89
90
  export interface Metadata {
90
91
  readonly provider: string; readonly providerKind: ProviderKind; readonly requestedModel: string;
92
+ /** Upstream model ID sent after provider selection, when supplied by the provider. */
93
+ readonly resolvedModel?: string;
91
94
  readonly probabilitySource: 'native' | 'elicited' | 'unspecified' | (string & {});
92
95
  readonly confidenceSource?: 'native' | 'unavailable' | (string & {});
93
96
  readonly modelSource?: 'reported' | 'requested';
@@ -171,7 +174,7 @@ export class OpenAIDecisionsProvider implements DecisionProvider {
171
174
  validateRequest(request: ResolvedRequest): void;
172
175
  evaluate<const Q extends Questions>(request: EvaluationRequest<Q>, options?: ProviderCallOptions): Promise<ProviderResult<Q>>;
173
176
  createDecision(request: NativeDecisionRequest, options?: ProviderCallOptions):
174
- Promise<{ readonly decision: OpenAIDecisionResponse; readonly meta: TransportMetadata }>;
177
+ Promise<{ readonly decision: OpenAIDecisionResponse; readonly meta: OpenAIDecisionsTransportMetadata }>;
175
178
  }
176
179
  export class CloudflareDecisionsProvider implements DecisionProvider {
177
180
  readonly kind: 'decision'; readonly name: 'cloudflare-decisions'; readonly probabilitySource: 'native'; readonly defaultModel: string;
@@ -4,17 +4,28 @@ import { assertCapabilities, builtInCapabilities } from '../capabilities.mjs';
4
4
  import { serializeRequestBody } from '../transport.mjs';
5
5
  import { validateDecisionRequest } from './decisions-validation.mjs';
6
6
  import { encodeCommonDecision } from './decisions-codec.mjs';
7
+ import { OPENAI_DEFAULT_MODEL, resolveOpenAIModel } from './openai-profile.mjs';
7
8
 
8
- export function preflightDecision(input, { model = 'gpt-6-luna', maxRequestBytes } = {}) {
9
+ /** Keep the caller selection separate from the resolved, size-checked wire request. */
10
+ export function prepareDecision(input, { model = OPENAI_DEFAULT_MODEL, maxRequestBytes } = {}) {
9
11
  const request = validateDecisionRequest(input, model);
10
- serializeRequestBody(request, maxRequestBytes);
11
- return request;
12
+ const native = freezeJson({ ...request, model: resolveOpenAIModel(request.model) });
13
+ serializeRequestBody(native, maxRequestBytes);
14
+ return { request, native };
15
+ }
16
+
17
+ export function preflightDecision(input, options = {}) {
18
+ return prepareDecision(input, options).request;
12
19
  }
13
20
 
14
21
  /** Check converted JSON text and wire bytes, not just the common request shape. */
15
- export function preflightCommonDecision(input, options = {}) {
16
- const request = validateRequest(input, options.model ?? 'gpt-6-luna');
22
+ export function prepareCommonDecision(input, options = {}) {
23
+ const request = validateRequest(input, options.model ?? OPENAI_DEFAULT_MODEL);
17
24
  assertCapabilities(request, builtInCapabilities('openai-decisions', request.model));
18
- preflightDecision(encodeCommonDecision(request), options);
19
- return freezeJson(request);
25
+ const { native } = prepareDecision(encodeCommonDecision(request), options);
26
+ return { request: freezeJson(request), native };
27
+ }
28
+
29
+ export function preflightCommonDecision(input, options = {}) {
30
+ return prepareCommonDecision(input, options).request;
20
31
  }
@@ -2,6 +2,7 @@ import { DecisionError, requireCondition as check } from '../errors.mjs';
2
2
  import { cloneJson, freezeJson, isRecord } from '../validation.mjs';
3
3
  import { isTextWithinLimit, validateImage } from '../input.mjs';
4
4
  import { builtInCapabilities } from '../capabilities.mjs';
5
+ import { OPENAI_DEFAULT_MODEL } from './openai-profile.mjs';
5
6
 
6
7
  const own = (value, key) => Object.hasOwn(value, key);
7
8
  function fields(value, allowed, required, label, code = 'VALIDATION_ERROR') {
@@ -12,8 +13,8 @@ function text(value, label, max = 1048576) {
12
13
  check(isTextWithinLimit(value, max), `Invalid ${label}.`);
13
14
  }
14
15
 
15
- /** Validate the Decisions wire contract independently of the common API limits. */
16
- export function validateDecisionRequest(input, defaultModel = 'gpt-6-luna') {
16
+ /** Validate native fields while retaining the selected model for preparation and metadata. */
17
+ export function validateDecisionRequest(input, defaultModel = OPENAI_DEFAULT_MODEL) {
17
18
  const request = cloneJson(input, 'native decision request');
18
19
  fields(request, ['input', 'model', 'questions', 'safety_identifier'], ['input', 'questions'], 'decision request');
19
20
  const model = request.model ?? defaultModel;
@@ -5,8 +5,9 @@ import { builtInCapabilities } from '../capabilities.mjs';
5
5
  import { JsonTransport, parseBaseURL, validateApiKey } from '../transport.mjs';
6
6
  import { environmentValue } from '../runtime-env.mjs';
7
7
  import { validateDecisionResponse } from './decisions-validation.mjs';
8
- import { encodeCommonDecision, decodeCommonDecision } from './decisions-codec.mjs';
9
- import { preflightCommonDecision, preflightDecision } from './decisions-preflight.mjs';
8
+ import { decodeCommonDecision } from './decisions-codec.mjs';
9
+ import { preflightCommonDecision, prepareCommonDecision, prepareDecision } from './decisions-preflight.mjs';
10
+ import { OPENAI_DEFAULT_MODEL } from './openai-profile.mjs';
10
11
 
11
12
  /** Native Decisions transport. Common and dedicated access share a single codec. */
12
13
  export class OpenAIDecisionsProvider {
@@ -17,7 +18,7 @@ export class OpenAIDecisionsProvider {
17
18
  #maxRequestBytes;
18
19
 
19
20
  constructor(options = {}) {
20
- const { apiKey, model = 'gpt-6-luna', baseURL = 'https://api.openai.com', maxRetries = 0, ...transportOptions } =
21
+ const { apiKey, model = OPENAI_DEFAULT_MODEL, baseURL = 'https://api.openai.com', maxRetries = 0, ...transportOptions } =
21
22
  optionsObject(options, ['apiKey', 'model', 'baseURL', ...TRANSPORT_FIELDS], 'OpenAI Decisions options');
22
23
  const endpoint = parseBaseURL(baseURL, { originOnly: true });
23
24
  const supplied = apiKey === undefined && endpoint.origin === 'https://api.openai.com'
@@ -36,16 +37,22 @@ export class OpenAIDecisionsProvider {
36
37
  preflightCommonDecision(request, { model: this.defaultModel, maxRequestBytes: this.#maxRequestBytes });
37
38
  }
38
39
 
39
- async createDecision(input, options = {}) {
40
- const request = preflightDecision(input, { model: this.defaultModel, maxRequestBytes: this.#maxRequestBytes });
41
- const { data, meta } = await this.#transport.request('POST', '/v1/decisions', request, options);
42
- const decision = withResponseMeta(meta, () => validateDecisionResponse(data, request));
40
+ /** Dispatch only prepared wire input; keep selection and reported identity independent. */
41
+ async #perform({ request, native }, options) {
42
+ const { data, meta: transport } = await this.#transport.request('POST', '/v1/decisions', native, options);
43
+ const meta = { ...transport, requestedModel: request.model, resolvedModel: native.model };
44
+ const decision = withResponseMeta(meta, () => validateDecisionResponse(data, native));
43
45
  return freezeJson({ decision, meta });
44
46
  }
45
47
 
48
+ async createDecision(input, options = {}) {
49
+ const prepared = prepareDecision(input, { model: this.defaultModel, maxRequestBytes: this.#maxRequestBytes });
50
+ return this.#perform(prepared, options);
51
+ }
52
+
46
53
  async evaluate(input, options = {}) {
47
- const request = preflightCommonDecision(input, { model: this.defaultModel, maxRequestBytes: this.#maxRequestBytes });
48
- const { decision, meta } = await this.createDecision(encodeCommonDecision(request), options);
49
- return freezeJson(withResponseMeta(meta, () => decodeCommonDecision(decision, request, meta)));
54
+ const prepared = prepareCommonDecision(input, { model: this.defaultModel, maxRequestBytes: this.#maxRequestBytes });
55
+ const { decision, meta } = await this.#perform(prepared, options);
56
+ return freezeJson(withResponseMeta(meta, () => decodeCommonDecision(decision, prepared.request, meta)));
50
57
  }
51
58
  }
@@ -0,0 +1,24 @@
1
+ import { requireCondition as check } from '../errors.mjs';
2
+
3
+ /** Release-owned selectors resolve locally; upstream responses never change this mapping. */
4
+ export const OPENAI_DEFAULT_MODEL = 'default';
5
+ const LUNA_MODEL = 'gpt-6-luna';
6
+ const SELECTORS = Object.freeze({ default: LUNA_MODEL, luna: LUNA_MODEL, [LUNA_MODEL]: LUNA_MODEL });
7
+ const PROFILES = Object.freeze({
8
+ [LUNA_MODEL]: Object.freeze({
9
+ supported: true, orderedInput: true, images: true, typedChoices: true,
10
+ labeledScores: true, safetyIdentifier: true,
11
+ maxImages: 128, maxChoiceOptions: 255, maxScoreLevels: null,
12
+ }),
13
+ });
14
+
15
+ /** Lookup does not throw for unsupported selections, matching capability-query semantics. */
16
+ export function openaiModelProfile(selector) {
17
+ return typeof selector === 'string' && Object.hasOwn(SELECTORS, selector)
18
+ ? PROFILES[SELECTORS[selector]] : undefined;
19
+ }
20
+
21
+ export function resolveOpenAIModel(selector) {
22
+ check(openaiModelProfile(selector), 'Selected provider/model does not support this model.', 'UNSUPPORTED_CAPABILITY');
23
+ return SELECTORS[selector];
24
+ }
package/src/version.mjs CHANGED
@@ -1,2 +1,2 @@
1
1
  /** Keep in sync with package.json; scripts/check.mjs verifies the release version. */
2
- export const VERSION = '0.1.0';
2
+ export const VERSION = '0.1.1';