@plumbus/ai-decision-openai 0.0.0-stage → 0.2.0

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 ADDED
@@ -0,0 +1,12 @@
1
+ # Changelog
2
+
3
+ ## 0.2.0 — 2026-10-08
4
+
5
+ - Initial optional OpenAI Decisions API (`POST /v1/decisions`) adapter for typed choices, scores, and probabilities (#72), using `@plumbus/ai-decision` 0.2.3 contracts, validation, and the shared bounded HTTP transport. No OpenAI SDK dependency. OpenAI's Decisions API is in public beta; the adapter follows its documentation as of 2026-10-08 and may need a patch if the API changes at general availability.
6
+ - The adapter's provider name is `openai-decisions`, so its cost rows are distinct from rows of the `openai` text provider.
7
+ - Map `probability` questions to `predicate`, `choice` criteria to `choices`, and `score` criteria to ordered `levels`. Each Plumbus question key becomes the OpenAI question `name`. Answers are matched by name, and score levels are checked against the requested labels and indices.
8
+ - Accept two-decimal rounding in OpenAI's numbers: distributions that sum to 1 only within rounding, a chosen option that is within rounding of the top option, and a score that matches its distribution only within rounding. Options and levels that OpenAI leaves out count as probability 0. The chosen option is never changed. Unknown, repeated, or mistyped answers, options, and levels, probabilities outside [0, 1], and sums far from 1 still fail.
9
+ - A `refusal` answer fails the whole call with `invalid_response` and sets `error.refusedQuestions` to the refused question keys. Refusals and malformed answers keep the billed model, usage, and known cost, so core records the failed call.
10
+ - Price the actual response model with the bundled `gpt-6-luna` input rate ($0.10 / 1M tokens, verified 2026-10-08), doubled above 272K input tokens as OpenAI bills long prompts, or with configured `inputRates`, which are applied flat. Dated snapshots inherit their alias rate; unknown models return `cost: null`. Regional processing is not modeled.
11
+ - Ship offline adapter tests, consumer agent instructions (`instructions/`), and a one-request live smoke script (`scripts/smoke.mjs`, a repository tool that is not in the npm package).
12
+ - Requires core `0.7.x`; `ctx.ai.decide()` needs core 0.7.3+, `ctx.ai.classify({ provider })` needs 0.7.4+, and agent wiring v19 (core 0.7.9) links these instructions. Core checks results again with its own `@plumbus/ai-decision`, which must be 0.2.3+ for OpenAI's rounding: use core 0.7.9, or on core 0.7.3–0.7.8 run `pnpm dedupe` and check that `pnpm why @plumbus/ai-decision` lists one copy.
package/README.md CHANGED
@@ -1,3 +1,220 @@
1
- # Temporary Holding Version
1
+ # @plumbus/ai-decision-openai
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ > **OpenAI Decisions API for [Plumbus](https://github.com/plumbus-framework/plumbus) AI.** [OpenAI Decisions API](https://developers.openai.com/api/docs/guides/decisions) provider for typed choices, scores, and probabilities (`POST /v1/decisions`, default model `gpt-6-luna`), called through `ctx.ai.decide()` / `ctx.ai.classify()` with **input pricing** of the actual response model.
4
+
5
+ [![npm](https://img.shields.io/npm/v/@plumbus/ai-decision-openai.svg)](https://www.npmjs.com/package/@plumbus/ai-decision-openai)
6
+ [![license](https://img.shields.io/npm/l/@plumbus/ai-decision-openai.svg)](https://github.com/plumbus-framework/plumbus/blob/main/LICENSE)
7
+ [![peer: @plumbus/core 0.7.x](https://img.shields.io/badge/peer-%40plumbus%2Fcore%200.7.x-blue)](https://www.npmjs.com/package/@plumbus/core)
8
+
9
+ ## What is this?
10
+
11
+ [Plumbus](https://github.com/plumbus-framework/plumbus) is an **AI-native, contract-driven TypeScript application framework**. Apps call `ctx.ai.decide()` (core 0.7.3+) and `ctx.ai.classify()` (core 0.7.4+) against decision adapters registered under `decisions.providers`, separately from text-generation providers.
12
+
13
+ `@plumbus/ai-decision-openai` is the **OpenAI Decisions API adapter**. It translates the shared [`@plumbus/ai-decision`](../ai-decision/) contract to `POST /v1/decisions` and back, checks the answers with the shared validator, and prices the actual response model. It calls the documented HTTP endpoint directly through the shared transport; there is no OpenAI SDK to install.
14
+
15
+ ## Why?
16
+
17
+ Typed decision models answer bounded questions (one option from a list, a level on an ordered scale, a probability) instead of generating text, so Plumbus registers them apart from text-generation providers. Each vendor ships as its own optional package:
18
+
19
+ - Apps install only the provider they use; no provider depends on another.
20
+ - The contract, validation, and transport live in `@plumbus/ai-decision`, so every provider takes the same request and core checks every result the same way.
21
+
22
+ This package gives apps that already use OpenAI a registered decision provider with per-call cost rows, kept apart from the `openai` text provider.
23
+
24
+ ## What you get
25
+
26
+ | Surface | What it does |
27
+ |---|---|
28
+ | `createOpenAIDecisionAdapter()` | `DecisionProviderAdapter` (provider name `openai-decisions`) for `decisions.providers` in `app/server.ts`. |
29
+ | Decisions API mapping | Shared questions to `predicate`, `choice`, and `score` questions and back; answers validated by `@plumbus/ai-decision` with two-decimal rounding. |
30
+ | Input pricing | Bundled `gpt-6-luna` rate (`OpenAIDecisionInputRates`), `inputRates` overrides, priced by the actual response model. |
31
+ | Refusals | A refused question fails the call with `error.refusedQuestions`, keeping the billed model, usage, and known cost. |
32
+ | Bounded HTTP | Shared transport: deadlines, cancellation, retries on 429 and transient 5xx with `Retry-After`, redirects disabled. |
33
+ | Re-exports | `DecisionProviderError` and the protocol types `DecisionProviderAdapter`, `DecisionQuestions`, `DecisionRequest`, `DecisionResult`. |
34
+
35
+ ## When to use this vs alternatives
36
+
37
+ | You want | Reach for |
38
+ |---|---|
39
+ | Text responses, structured generation, or embeddings | Built-in text providers in `@plumbus/core` |
40
+ | Labels from a generative model | `ctx.ai.classify()` with a text provider (the default) |
41
+ | Hosted typed decisions from TypeSafe/Jev | [`@plumbus/ai-decision-typesafe`](../ai-decision-typesafe/) |
42
+ | Typed decisions on your own servers, no hosted API | [`@plumbus/ai-decision-laya`](../ai-decision-laya/) |
43
+ | Hosted typed decisions from OpenAI (public beta) | **`@plumbus/ai-decision-openai`** (this package) |
44
+ | Named contracts, answer types, or your own adapter | [`@plumbus/ai-decision`](../ai-decision/) |
45
+
46
+ ## Status
47
+
48
+ Optional decision provider, version `0.2.0` (version-locked **`0.2.x`**); required peer `@plumbus/core` exactly **`0.7.x`**; Node.js 20.6+. **Runtime floor:** `ctx.ai.decide()` needs `@plumbus/core` **≥ 0.7.3** and `ctx.ai.classify({ provider })` needs **≥ 0.7.4**; use core **0.7.9** (see [Install](#install)); agent wiring **v19** (core 0.7.9) links these instructions. Depends on `@plumbus/ai-decision` `~0.2.3`. Install alone is not enough until the adapter is registered under `decisions.providers`.
49
+
50
+ OpenAI's Decisions API is in public beta. This adapter follows OpenAI's documentation as of
51
+ 2026-10-08 and may need a patch release if the API changes when it becomes generally available.
52
+
53
+ Not included: vendor model catalogs, automatic environment provider discovery, and dedicated decision CLI commands.
54
+
55
+ Release notes: [changelog](./CHANGELOG.md) and [version/upgrade guide](../../docs/upgrading-classification.md).
56
+
57
+ ## Install
58
+
59
+ ```bash
60
+ pnpm add @plumbus/ai-decision-openai
61
+ ```
62
+
63
+ Install explicitly with `pnpm add @plumbus/ai-decision-openai`. Provider packages install
64
+ `@plumbus/ai-decision` (`0.2.3+`) transitively. There is no dependency on the OpenAI SDK
65
+ or on the other decision providers.
66
+
67
+ Through `ctx.ai`, core checks every result again with its own copy of
68
+ `@plumbus/ai-decision`, and only 0.2.3+ allows for this adapter's rounding. Core 0.7.9
69
+ depends on `~0.2.3`. On core 0.7.3–0.7.8, run `pnpm dedupe` after installing and check
70
+ that `pnpm why @plumbus/ai-decision` lists a single 0.2.3+ copy. With an older copy,
71
+ core rejects OpenAI's rounded answers as `invalid_response` after the call is billed.
72
+
73
+ Peer (copy literally): `@plumbus/core` `0.7.x`. See `node_modules/@plumbus/core/instructions/peer-dependencies.md`.
74
+
75
+ If agent wiring predates the decision provider instructions, refresh:
76
+
77
+ ```bash
78
+ plumbus init --patch --agent all
79
+ plumbus doctor
80
+ ```
81
+
82
+ ## Quick start
83
+
84
+ Core `0.7.3+` provides `ctx.ai.decide()` with shared validation, security, budgets,
85
+ and per-call cost recording; core **0.7.4+** routes `ctx.ai.classify({ text, labels,
86
+ provider, model, threshold })` to decision adapters. Register this adapter under
87
+ `decisions.providers` in `app/server.ts`. Use a key such as `openai-decisions` so it does
88
+ not collide with an `openai` text-generation provider. Cost rows record the adapter's
89
+ provider name, `openai-decisions`, whatever key you choose. See the
90
+ [decision integration guide](../../docs/ai/decision-providers.md#application-integration-and-cost-recording)
91
+ and `node_modules/@plumbus/core/instructions/ai-classification.md`.
92
+
93
+ ```ts
94
+ // app/server.ts
95
+ import { createOpenAIDecisionAdapter } from '@plumbus/ai-decision-openai';
96
+
97
+ export const decisions = {
98
+ providers: {
99
+ 'openai-decisions': createOpenAIDecisionAdapter({
100
+ apiKey: process.env.OPENAI_API_KEY ?? '',
101
+ model: 'gpt-6-luna',
102
+ }),
103
+ },
104
+ defaultProvider: 'openai-decisions',
105
+ };
106
+ ```
107
+
108
+ Then, inside a capability handler:
109
+
110
+ ```ts
111
+ const result = await ctx.ai.decide({
112
+ state: { message: input.message },
113
+ questions: {
114
+ refund: { type: 'probability', instructions: 'Does the customer request a refund?' },
115
+ },
116
+ signal: ctx.signal,
117
+ });
118
+ result.answers.refund.probability; // P(true), not a boolean
119
+ ```
120
+
121
+ ### Infrastructure / smoke testing
122
+
123
+ Direct adapter calls remain useful for infrastructure tests and do not record costs
124
+ in core. Keep these adapters separate from the text-generation provider registry.
125
+
126
+ ```ts
127
+ import { createOpenAIDecisionAdapter } from '@plumbus/ai-decision-openai';
128
+
129
+ const adapter = createOpenAIDecisionAdapter({ apiKey: process.env.OPENAI_API_KEY ?? '' });
130
+ const result = await adapter.decide({
131
+ state: 'Please refund the duplicate charge.',
132
+ questions: { refund: { type: 'probability', instructions: 'Is a refund requested?' } },
133
+ });
134
+ console.log(result.answers.refund.probability);
135
+ ```
136
+
137
+ Business logic belongs in Plumbus capabilities/flows and `ctx.*`. These examples
138
+ exercise the provider protocol only; they do not provide the core execution lifecycle.
139
+
140
+ ## How requests flow
141
+
142
+ | Plumbus question | OpenAI question | Notes |
143
+ | --- | --- | --- |
144
+ | `probability` | `predicate` | `criteria.true` / `criteria.false` are appended to the instructions |
145
+ | `choice` | `choice` | Criteria keys become choice values; descriptions are optional |
146
+ | `score` | `score` | Criteria become ordered level labels; returned level indices are validated |
147
+
148
+ `state` is sent as `input`: strings unchanged, JSON objects/arrays serialized as JSON
149
+ text. Image inputs and `safety_identifier` are not supported yet.
150
+
151
+ OpenAI does not document the precision of its numbers, so answers are accepted when they
152
+ are consistent within two-decimal rounding; options or levels that OpenAI omits get
153
+ probability 0. A `refusal` for any question fails the whole call with a
154
+ `DecisionProviderError` whose `refusedQuestions` lists the refused question keys.
155
+
156
+ ## Pricing (important)
157
+
158
+ The adapter prices the **actual response model** from input tokens. The bundled `gpt-6-luna`
159
+ rate is $0.10 per million input tokens, doubled above 272K input tokens as OpenAI bills long
160
+ prompts ([model pricing](https://developers.openai.com/api/docs/models/gpt-6-luna)), verified
161
+ against the [Decisions guide](https://developers.openai.com/api/docs/guides/decisions#pricing-and-availability)
162
+ on 2026-10-08. A dated snapshot such as `gpt-6-luna-2026-10-01` uses its alias rate. An
163
+ `inputRates` entry replaces the bundled rate and is applied flat, without the long-context
164
+ doubling. Use one to include regional processing, for example `{ 'gpt-6-luna': 0.11 }` for
165
+ OpenAI's 10% uplift.
166
+
167
+ Unknown response models return `cost: null`; they are not free. Core records such calls as unpriced, and when a dollar budget is configured, unpriced prior calls block further calls.
168
+
169
+ ## Configuration
170
+
171
+ | Option | Default | Notes |
172
+ | --- | --- | --- |
173
+ | `apiKey` | required | OpenAI API key; keep it on the server |
174
+ | `model` | `gpt-6-luna` | Overridden by per-call `model`, then a `defineDecision` contract's `model`, then `decisions.defaultModel` |
175
+ | `baseUrl` | `https://api.openai.com/v1` | API prefix; the adapter appends `/decisions` |
176
+ | `inputRates` | bundled `gpt-6-luna` rate | Input USD per million tokens, keyed by response model ID |
177
+ | `timeoutMs` | `30000` | Whole-call deadline, at most five minutes |
178
+ | `maxRetries` | `2` | Retries HTTP 429 and transient 5xx only (0–5) |
179
+ | `fetch` | `globalThis.fetch` | Injected HTTP implementation for tests |
180
+
181
+ ## Key gotchas
182
+
183
+ - **Register under `decisions`, not as a text provider.** Export `decisions = { providers, defaultProvider }` from `app/server.ts`; API and workers share it. Provider names must be distinct across the text and decision registries, so do not reuse the key `openai` when the app also registers the OpenAI text provider.
184
+ - **Direct adapter calls skip core.** They do not enforce core's PII checks, budgets, identity attribution, action confirmation, or ledger hooks. Use `ctx.ai.decide()` / `ctx.ai.classify()` in capabilities and flows.
185
+ - **Unknown cost is `null`, not free.** Only `gpt-6-luna` (and its dated snapshots) is bundled; another response model stays unpriced until you add its `inputRates` entry. Regional processing is not modeled, so those calls are priced without OpenAI's 10% uplift unless an `inputRates` entry includes it.
186
+ - **Failed calls are recorded.** Core records one row per dispatched call, including failed and cancelled calls; refusals and malformed answers keep the billed model, usage, and known cost.
187
+ - **Probabilities are not permissions.** A probability is P(true), not a boolean, and a high confidence value is not authorization or proof of correctness. Validate thresholds on the actual task, model, and language.
188
+ - **Retries can repeat cost.** The transport retries HTTP 429, 500, 502, 503, 504, and 529 (two retries by default, honoring `Retry-After`); repeated requests can still incur cost, with no exactly-once guarantee.
189
+ - **Refusals fail the whole call.** `decide()` and `classify()` throw `invalid_response` with `error.refusedQuestions` (for `classify()`, `label_<index>` by position in `labels`); `refusedQuestions` is set only for refusals. Handle it as a business outcome, such as routing to review, not by retrying in a loop.
190
+ - **Core needs its own `@plumbus/ai-decision` 0.2.3+.** Use core 0.7.9, or dedupe on core 0.7.3–0.7.8 (see [Install](#install)).
191
+ - **Public beta.** OpenAI may change the Decisions API at general availability.
192
+
193
+ ## Documentation / Agent recipes
194
+
195
+ - **Concept docs:** read the [decision providers guide](https://github.com/plumbus-framework/plumbus/blob/main/docs/ai/decision-providers.md)
196
+ for the complete contract, failure behavior, pricing, and live test environment.
197
+ Local monorepo copy: [docs/ai/decision-providers.md](../../docs/ai/decision-providers.md).
198
+ - **Classification recipe** (after install): `node_modules/@plumbus/core/instructions/ai-classification.md`
199
+ - **Live smoke (monorepo):** `scripts/smoke.mjs` sends one live request when `PLUMBUS_LIVE_DECISION_TESTS=1` and `OPENAI_API_KEY` are set (a repository tool, not in the npm package).
200
+ - **Agent recipes** (ship in this package, readable from `node_modules/@plumbus/ai-decision-openai/instructions/`):
201
+ - [Instructions index](instructions/README.md)
202
+ - [Framework boundary and usage](instructions/framework.md)
203
+ - [Testing](instructions/testing.md)
204
+
205
+ ## The Plumbus ecosystem
206
+
207
+ `@plumbus/ai-decision-openai` is one package in the Plumbus framework. For the full list of packages and when to use each, see the [Plumbus monorepo README](https://github.com/plumbus-framework/plumbus#packages).
208
+
209
+ ## Links
210
+
211
+ - **Plumbus framework** — [github.com/plumbus-framework/plumbus](https://github.com/plumbus-framework/plumbus)
212
+ - **Parent / peer** — [`@plumbus/core`](../plumbus-core/)
213
+ - **Shared contracts** — [`@plumbus/ai-decision`](../ai-decision/)
214
+ - **Full documentation** — [docs/](../../docs/) in the monorepo
215
+ - **Top-level README** — [`../../README.md`](../../README.md)
216
+ - **Issues** — [github.com/plumbus-framework/plumbus/issues](https://github.com/plumbus-framework/plumbus/issues)
217
+
218
+ ## License
219
+
220
+ MIT