opencode-effect-enforcer 0.3.0 → 0.4.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/README.md CHANGED
@@ -17,7 +17,7 @@ Effect-first design, schema-first modeling, typed dependencies, and how to
17
17
  choose the relevant skills.
18
18
 
19
19
  **Skills** explain how to use specific Effect APIs. The agent loads the relevant
20
- guides through OpenCode's native skill tool, with 53 to choose from across
20
+ guides through OpenCode's native skill tool, with 54 to choose from across
21
21
  services, streams, HTTP, SQL, React, AI, and more.
22
22
 
23
23
  **Patterns** check the code after edits. 45 tested checks look for common
@@ -59,7 +59,7 @@ look for.
59
59
  - [Effect, and the Near-Inexpressible Majesty of Layers](guidance/post__effect-and-the-near-inexpressible-majesty-of-layers.md): Explains services, Layers, typed dependencies, and testable implementations.
60
60
  - [Parse, don't validate](guidance/post__parse-dont-validate.md): Shows how refined types preserve validation knowledge and make illegal states unrepresentable.
61
61
 
62
- ### Skills (53)
62
+ ### Skills (54)
63
63
 
64
64
  #### Modeling and core APIs
65
65
 
@@ -113,6 +113,7 @@ look for.
113
113
  #### AI and MCP
114
114
 
115
115
  - [`effect-ai-language-model`](skills/effect-ai-language-model/SKILL.md): Generate text, structured output, streams, and tool calls through `LanguageModel`.
116
+ - [`effect-ai-decision-model`](skills/effect-ai-decision-model/SKILL.md): Use System One models such as Jev through typed decisions, provider layers, and validated answers; includes an [OpenRouter adapter reference](skills/effect-ai-decision-model/openrouter.md).
116
117
  - [`effect-ai-prompt`](skills/effect-ai-prompt/SKILL.md): Construct and compose prompts from messages and multimodal parts.
117
118
  - [`effect-ai-tool`](skills/effect-ai-tool/SKILL.md): Define type-safe AI tools, toolkits, schemas, and handlers.
118
119
  - [`effect-ai-provider`](skills/effect-ai-provider/SKILL.md): Configure provider Layers, models, runtime overrides, and fallback execution plans.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://www.schemastore.org/package.json",
3
3
  "name": "opencode-effect-enforcer",
4
- "version": "0.3.0",
4
+ "version": "0.4.0",
5
5
  "description": "OpenCode V2 plugin for Effect v4 skills, guidance, and pattern enforcement",
6
6
  "keywords": [
7
7
  "opencode",
@@ -51,6 +51,10 @@
51
51
  "yaml": "^2.8.1"
52
52
  },
53
53
  "devDependencies": {
54
+ "@effect/ai-openrouter": "4.0.0",
55
+ "@effect/ai-typesafe": "4.0.0",
56
+ "@effect/platform-bun": "4.0.0",
57
+ "@effect/platform-node-shared": "4.0.0",
54
58
  "@types/bun": "^1.3.0",
55
59
  "@types/diff": "^8.0.0",
56
60
  "@types/json-schema": "^7.0.15",
@@ -0,0 +1,301 @@
1
+ ---
2
+ name: effect-ai-decision-model
3
+ description: Use Effect's Decision and DecisionModel APIs for System One models such as Jev. Covers typed classify/rate/probability definitions, input encoding, answer contracts, TypeSafe and OpenRouter provider layers, errors, and test implementations.
4
+ ---
5
+
6
+ # Effect AI Decision Model
7
+
8
+ Baseline: `effect@4.0.0` and matching `@effect/*` packages. These AI APIs carry
9
+ `@stability unstable`; inspect the consuming version's source before using newer
10
+ contracts. References below are pinned to the stable tag.
11
+
12
+ ## Define and call
13
+
14
+ `Decision.make({ input, decisions })` pairs an input schema with a nonempty record
15
+ of named decisions. One `DecisionModel.decide(definition, { input })` encodes one
16
+ input and sends every decision together in one provider call. Answers retain the
17
+ decision keys and inferred label literals. Keep definitions inferred rather than
18
+ widening them to `Record<string, Decision.Any>`.
19
+
20
+ <!-- typecheck -->
21
+ ```ts
22
+ import * as Decision from 'effect/ai/Decision';
23
+ import * as DecisionModel from 'effect/ai/DecisionModel';
24
+ import * as Effect from 'effect/Effect';
25
+ import * as Schema from 'effect/Schema';
26
+
27
+ class Ticket extends Schema.Class<Ticket>('Ticket')({
28
+ subject: Schema.String,
29
+ body: Schema.String
30
+ }) {}
31
+
32
+ const Triage = Decision.make({
33
+ input: Ticket,
34
+ decisions: {
35
+ department: Decision.classify({
36
+ instructions: 'Which team should handle the ticket?',
37
+ criteria: {
38
+ billing: 'Payments, invoices, and refunds',
39
+ technical: 'Bugs, outages, and integrations',
40
+ other: 'Neither billing nor technical'
41
+ }
42
+ }),
43
+ frustration: Decision.rate({
44
+ instructions: 'How frustrated is the customer?',
45
+ criteria: ['Calm or neutral', 'Expresses frustration', 'Expresses intense anger']
46
+ }),
47
+ urgent: Decision.probability({
48
+ instructions: 'Does the ticket need immediate attention?',
49
+ criteria: { false: 'Can wait', true: 'Needs action now' }
50
+ })
51
+ }
52
+ });
53
+
54
+ type TriageAnswers = Decision.Answers<typeof Triage.decisions>;
55
+
56
+ const triage = Effect.fn('Ticket.triage')(function* (raw: unknown) {
57
+ const input = yield* Schema.decodeUnknownEffect(Ticket)(raw);
58
+ return yield* DecisionModel.decide(Triage, { input });
59
+ });
60
+ ```
61
+
62
+ ### Constructors and answers
63
+
64
+ Every constructor takes an options object; all `instructions` are strings.
65
+
66
+ | Constructor | Criteria | Answer |
67
+ | --- | --- | --- |
68
+ | `Decision.classify` | Record of at least two label keys to string descriptions | `{ label, probabilities, confidence? }`; both label and distribution keys use the inferred label union |
69
+ | `Decision.rate` | Array of at least two distinct string levels, lowest to highest | `{ rating, label, probabilities, confidence? }`; label and distribution keys are the level strings |
70
+ | `Decision.probability` | Optional `{ false: string, true: string }`; both required when supplied | `{ probability }`, the probability of true in `[0, 1]` |
71
+
72
+ - `classify` preserves the provider's chosen label, even if it differs from the
73
+ distribution's maximum. The core checks membership, not argmax consistency.
74
+ - `rate.rating` is the provider's probability-weighted zero-based position, in
75
+ `[0, criteria.length - 1]`, potentially fractional. The core range-checks it but
76
+ does not recompute it or check agreement with the distribution. `rate.label` is
77
+ derived from the highest probability, breaking ties by criteria order.
78
+ - Classify/rate confidence is optional and provider-defined in `[0, 1]`; preserve
79
+ absence (for example with `Option.fromNullishOr`). Probability answers have
80
+ neither confidence nor a distribution. Public answers have no `_tag` field.
81
+ - `classify`, `rate`, and `make` synchronously throw on the cardinality/uniqueness
82
+ violations above. Define valid static literals; parse dynamic definitions at
83
+ their boundary before construction. These throws are outside `decide`'s error
84
+ channel. Constructors retain their input objects; treat definitions as immutable.
85
+
86
+ Exported types: `Classify<Label>`, `Rate<Level>`, `Probability`, their union `Any`,
87
+ `ClassifyAnswer<Label>`, `RateAnswer<Level>`, `ProbabilityAnswer`, conditional
88
+ `Answer<D>`, mapped `Answers<Decisions>`, and `Definition<Input, Decisions>`.
89
+ `Definition` holds `input`, `decisions`, and the `Decision.TypeId` brand.
90
+
91
+ ### Execution and encoding
92
+
93
+ - Static `DecisionModel.decide` requires `DecisionModel.DecisionModel` plus
94
+ `Input["EncodingServices"]`. Alternatively, `yield* DecisionModel.DecisionModel`
95
+ once and call `model.decide(definition, { input })`; only encoding services then
96
+ remain in that call's requirements.
97
+ - `DecideOptions<Input>` contains only `input: Input["Type"]`. Pass the decoded
98
+ value, not its encoded representation. Unknown input still needs boundary
99
+ decoding; its `SchemaError` is separate from `decide`'s `AiError`.
100
+ - Encoding uses `Schema.encodeEffect(Schema.toCodecJson(definition.input))`.
101
+ Providers receive JSON **values**, not a pre-stringified document. Existing
102
+ schema transformations apply: e.g. `FiniteFromString` sends a number as a string;
103
+ derived codecs can encode nested bigint/date values. Explicit `undefined`
104
+ becomes `null`, while absent fields stay absent. Custom declarations need JSON
105
+ codec annotations or encoding fails before the provider runs.
106
+ - `DecideResponse<Decisions>` is `{ answers: Decision.Answers<Decisions>, usage }`.
107
+ `usage` is a `DecisionUsage` schema-class instance with optional finite-number
108
+ `inputTokens` / `outputTokens`. Unknown counts are `undefined`, not zero.
109
+ Answer and distribution dictionaries have null prototypes; use record helpers
110
+ rather than methods inherited from `Object.prototype`.
111
+ - This is a single-response API with no prompt/history, tools, streaming, model,
112
+ or sampling options on `decide`. Provider configuration belongs in layers or
113
+ scoped provider services. Multiple inputs require separate calls; use bounded
114
+ `Effect.forEach` concurrency. The built-in span is `DecisionModel.decide`.
115
+
116
+ ## TypeSafe / Jev provider
117
+
118
+ Install `@effect/ai-typesafe` at the same release as `effect`; for Bun also install
119
+ matching `@effect/platform-bun`. Compose the graph:
120
+ HTTP transport → TypeSafe client → decision model.
121
+
122
+ <!-- typecheck -->
123
+ ```ts
124
+ import * as TypeSafeClient from '@effect/ai-typesafe/TypeSafeClient';
125
+ import * as TypeSafeDecisionModel from '@effect/ai-typesafe/TypeSafeDecisionModel';
126
+ import * as BunHttpClient from '@effect/platform-bun/BunHttpClient';
127
+ import * as Decision from 'effect/ai/Decision';
128
+ import * as DecisionModel from 'effect/ai/DecisionModel';
129
+ import * as Effect from 'effect/Effect';
130
+ import * as Layer from 'effect/Layer';
131
+ import * as Schema from 'effect/Schema';
132
+
133
+ const JevLive = TypeSafeDecisionModel.layer({ model: 'jev-latest' }).pipe(
134
+ Layer.provide(TypeSafeClient.layerConfig()),
135
+ Layer.provide(BunHttpClient.layer)
136
+ );
137
+
138
+ const Urgency = Decision.make({
139
+ input: Schema.String,
140
+ decisions: {
141
+ urgent: Decision.probability({ instructions: 'Does this need action today?' })
142
+ }
143
+ });
144
+
145
+ const program = DecisionModel.decide(Urgency, {
146
+ input: 'My card was charged twice; please fix this today.'
147
+ }).pipe(Effect.provide(JevLive));
148
+ // Run at the application entrypoint; program has no remaining service requirements.
149
+ ```
150
+
151
+ ### Provider and client surface
152
+
153
+ | API | Contract |
154
+ | --- | --- |
155
+ | `TypeSafeDecisionModel.make({ model })` | `Effect<DecisionModel, never, TypeSafeClient>`; captures the client |
156
+ | `TypeSafeDecisionModel.layer({ model })` | `Layer<DecisionModel, never, TypeSafeClient>` |
157
+ | `TypeSafeDecisionModel.model(modelId)` | `Model.Model<"typesafe", DecisionModel, TypeSafeClient>`; usable as a layer, adding `Model.ProviderName` and `Model.ModelName` metadata |
158
+ | Model IDs | Known union: `jev-latest`, `jev-preview`, `jev-1.13.0`; arbitrary strings also accepted. Use client model discovery for current availability |
159
+ | `TypeSafeClient.make(options)` / `layer(options)` | Require `HttpClient.HttpClient`; construction error is `never` |
160
+ | `TypeSafeClient.layerConfig(options?)` | Same HTTP requirement, construction error `Config.ConfigError`; defaults to `Config.Redacted('TYPESAFE_API_KEY')` |
161
+
162
+ Client options: optional `apiKey: Redacted<string>`, `apiUrl: string`, and
163
+ `transformClient: (HttpClient) => HttpClient`. Default URL:
164
+ `https://api.typesafe.ai/v1`. Explicit `make`/`layer` options do not read the
165
+ environment. In `layerConfig`, `apiKey` and `apiUrl` are `Config` values;
166
+ `transformClient` remains a plain function.
167
+
168
+ `TypeSafeConfig.withClientTransform(effect, transform)` or
169
+ `effect.pipe(TypeSafeConfig.withClientTransform(transform))` scopes HTTP
170
+ customization at request execution time, after the constructor transform. Nested
171
+ scoped transforms replace rather than compose; compose explicitly when both are
172
+ needed. The module also exports the `TypeSafeConfig` service and its
173
+ `getOrUndefined` effect. There is no per-call TypeSafe model override; provide a
174
+ different decision-model layer.
175
+
176
+ ### Wire mapping and adapter limits
177
+
178
+ - `classify` → Choice (`choice` → `label`); `rate` → Score (`score` → `rating`,
179
+ index-keyed probabilities → level-string keys); `probability` → Noul (`noul` →
180
+ `probability`). The decision provider drops response `model` and Score `legend`.
181
+ TypeSafe's Choice/Score wire codecs require confidence even though the generic
182
+ decision answer types allow it to be absent.
183
+ - Both built-in decision providers use `probabilityPrecision: 2` for rounded
184
+ distributions. Core normalization does not recompute the supplied rating or
185
+ confidence.
186
+ - Effect 4.0.0 constructors **and `TypeSafeSchema` codecs use string instructions
187
+ and string criteria**, even though the live TypeSafe API supports structured
188
+ descriptions. Put supporting structure in schema-defined input/state and refer
189
+ to it from string instructions. For structured question descriptions, inspect a
190
+ newer matching adapter or implement a separately schema-decoded HTTP boundary;
191
+ widening types or switching to this version's low-level client is insufficient.
192
+ - Core state is `Schema.Json`, but the live TypeSafe endpoint documents top-level
193
+ string/object/array. Wrap scalar boolean/number/null state in a named input field.
194
+ Current HTTP limits (255 Choice options, 10 Score levels) are provider limits,
195
+ not constructor checks; consult the [live API](https://docs.typesafe.ai/api.md)
196
+ when building dynamic definitions.
197
+
198
+ For model discovery or wire-level access, yield
199
+ `TypeSafeClient.TypeSafeClient`: `listModels()` returns `{ models }` (entries have
200
+ `name`, optional `description` / `release_date`); `systemOne({ model, state,
201
+ questions })` returns decoded `{ model, answers, usage? }`; both fail with
202
+ `AiError`. The service also exposes its configured `client`. Low-level calls keep
203
+ wire names, discriminated `type` fields, and optional snake-case token counts;
204
+ they do not apply `DecisionModel`'s definition-relative validation/normalization.
205
+ `TypeSafeSchema` exports Choice/Score/Noul question and answer codecs, `Question`,
206
+ `Answer`, `SystemOneRequest`, `SystemOneResponse`, and `ListModelsResponse`.
207
+
208
+ For **Jev through OpenRouter**, including request overrides and raw metadata,
209
+ read [the OpenRouter adapter reference](openrouter.md).
210
+
211
+ ## Failures
212
+
213
+ `decide` fails with outer `AiError.AiError` (`_tag: 'AiError'`) containing a tagged
214
+ `reason`, `module`, and `method`. Encoding failures use `InvalidUserInputError`;
215
+ missing, mismatched, or invalid answers use `InvalidOutputError`. A provider's
216
+ `AiError` propagates unchanged. Recover by reason with
217
+ `Effect.catchReason('AiError', 'RateLimitError', handler)` or `catchReasons`;
218
+ `catchTag('RateLimitError', ...)` targets the wrong level.
219
+
220
+ TypeSafe's client maps response decoding failures to `InvalidOutputError`, body
221
+ encoding failures to `InvalidRequestError`, transport/URL/HTTP encoding failures
222
+ to `NetworkError`, 404/422 to `InvalidRequestError`, and 429 to `RateLimitError`.
223
+ Other statuses use the shared `AiError.reasonFromHttpStatus` mapping. Rate-limit
224
+ reasons preserve optional `retryAfter` as `Duration` (milliseconds header first,
225
+ then seconds or HTTP-date) and TypeSafe request/error metadata.
226
+
227
+ The Effect TypeSafe client has **no automatic retries**. Add a bounded retry
228
+ policy at the owned request boundary when appropriate, honoring retry metadata;
229
+ native TypeSafe SDK retry defaults do not apply here. See `effect-scheduling` for
230
+ policy construction and `effect-error-handling` for reason-based recovery.
231
+
232
+ ## Test implementations and custom providers
233
+
234
+ `DecisionModel.make({ decide, probabilityPrecision? })` returns
235
+ `Effect<DecisionModel>`; provide it with `Layer.effect`. Its provider callback is
236
+ `(ProviderOptions) => Effect<ProviderResponse, AiError>` with no remaining service
237
+ requirements: acquire dependencies while constructing the layer and close over
238
+ them. `ProviderOptions` contains encoded `state: Schema.Json` and all `decisions`.
239
+ `ProviderResponse` contains `answers` and a required `usage` object with
240
+ `inputTokens` / `outputTokens`, each `number | undefined`.
241
+
242
+ Provider answers use `_tag: 'Classify' | 'Rate' | 'Probability'` and the public
243
+ answer fields above, except a provider Rate answer has **no label**. The core
244
+ derives it. `ProviderAnswer` is their union; individual exports are
245
+ `ProviderClassifyAnswer`, `ProviderRateAnswer`, `ProviderProbabilityAnswer`.
246
+
247
+ <!-- typecheck -->
248
+ ```ts
249
+ import * as Decision from 'effect/ai/Decision';
250
+ import * as DecisionModel from 'effect/ai/DecisionModel';
251
+ import * as Effect from 'effect/Effect';
252
+ import * as Layer from 'effect/Layer';
253
+ import * as Schema from 'effect/Schema';
254
+
255
+ const Urgency = Decision.make({
256
+ input: Schema.String,
257
+ decisions: {
258
+ urgent: Decision.probability({ instructions: 'Does this need action today?' })
259
+ }
260
+ });
261
+
262
+ const UrgencyTest = Layer.effect(
263
+ DecisionModel.DecisionModel,
264
+ DecisionModel.make({
265
+ decide: Effect.fnUntraced(function* (_request) {
266
+ return {
267
+ answers: { urgent: { _tag: 'Probability', probability: 0.9 } },
268
+ usage: { inputTokens: undefined, outputTokens: undefined }
269
+ } satisfies DecisionModel.ProviderResponse;
270
+ })
271
+ })
272
+ );
273
+
274
+ const result = DecisionModel.decide(Urgency, { input: 'Please fix this today.' })
275
+ .pipe(Effect.provide(UrgencyTest));
276
+ ```
277
+
278
+ This fixture handles the `Urgency` definition specifically. In an `it.effect`
279
+ test, yield `result` and assert `answers.urgent.probability`; inspect the provider
280
+ request when testing encoding. Returning incomplete/wrong answers through `make`
281
+ exercises real validation rather than bypassing it with a mocked `decide` method.
282
+
283
+ Validation requires every requested answer with a matching tag, every expected
284
+ distribution key with a finite probability in `[0, 1]`, a known classify label,
285
+ finite/range-valid ratings and probabilities, and optional finite/range-valid
286
+ confidence. Extra answers, probability keys, and answer fields are discarded.
287
+ Distributions must have a positive total within `1e-6` of 1 by default. With
288
+ `probabilityPrecision: p`, permitted drift is
289
+ `labelCount * 0.5 * 10 ** -p + 1e-6`; accepted drift greater than `1e-6` is
290
+ normalized. Precision describes provider rounding, not output rounding.
291
+
292
+ ## Pinned source
293
+
294
+ Resolve these paths under the Effect reference at tag `effect@4.0.0`:
295
+
296
+ - `packages/effect/src/ai/{Decision,DecisionModel}.ts` — complete core surface.
297
+ - `packages/effect/test/ai/DecisionModel.test.ts` and `typetest/ai/DecisionModel.tst.ts`
298
+ — runtime edge cases and inference contracts.
299
+ - `packages/ai/typesafe/src/{TypeSafeDecisionModel,TypeSafeClient,TypeSafeConfig,TypeSafeSchema}.ts`
300
+ — adapter, transport, scoped customization, and wire codecs.
301
+ - `packages/ai/typesafe/test/` and `typetest/` — adapter contract tests.
@@ -0,0 +1,70 @@
1
+ # OpenRouter decision adapter
2
+
3
+ Baseline: `@effect/ai-openrouter@4.0.0`, aligned with `effect`. This adapter calls
4
+ OpenRouter's **alpha Decisions API**, not chat completions.
5
+
6
+ `OpenRouterDecisionModel.make({ model, config? })` returns
7
+ `Effect<DecisionModel, never, OpenRouterClient>`; `layer` takes the same options
8
+ and provides `DecisionModel`. `model(modelId, config?)` returns
9
+ `Model.Model<'openrouter', DecisionModel, OpenRouterClient>`, including provider
10
+ and model-name metadata. All accept a string model ID; use a currently available
11
+ decision-capable ID from OpenRouter rather than the direct TypeSafe alias.
12
+
13
+ <!-- typecheck -->
14
+ ```ts
15
+ import * as OpenRouterClient from '@effect/ai-openrouter/OpenRouterClient';
16
+ import * as OpenRouterDecisionModel from '@effect/ai-openrouter/OpenRouterDecisionModel';
17
+ import * as BunHttpClient from '@effect/platform-bun/BunHttpClient';
18
+ import * as AiError from 'effect/ai/AiError';
19
+ import * as DecisionModel from 'effect/ai/DecisionModel';
20
+ import * as Config from 'effect/Config';
21
+ import * as Effect from 'effect/Effect';
22
+ import * as Layer from 'effect/Layer';
23
+
24
+ const DecisionsLive = Layer.unwrap(
25
+ Config.String('OPENROUTER_DECISION_MODEL').pipe(
26
+ Effect.map((model) => OpenRouterDecisionModel.layer({ model }))
27
+ )
28
+ ).pipe(
29
+ Layer.provide(OpenRouterClient.layerConfig({
30
+ apiKey: Config.Redacted('OPENROUTER_API_KEY')
31
+ })),
32
+ Layer.provide(BunHttpClient.layer)
33
+ );
34
+
35
+ declare const decision: Effect.Effect<unknown, AiError.AiError, DecisionModel.DecisionModel>;
36
+
37
+ const configured = decision.pipe(
38
+ Effect.provideService(OpenRouterDecisionModel.Config, { user: 'tenant-42' }),
39
+ Effect.provide(DecisionsLive)
40
+ );
41
+ ```
42
+
43
+ - `Config` is the service exported by **OpenRouterDecisionModel**, distinct from
44
+ Effect's configuration module and `OpenRouterConfig`. Its shape is
45
+ `Pick<typeof OpenRouterSchema.DecisionsRequest.Encoded, 'provider' | 'session_id' | 'user' | 'trace'>`.
46
+ Runtime fields shallowly override constructor `config`; nested provider options
47
+ are replaced, not deep-merged. It cannot override `model`, state, or questions.
48
+ - `OpenRouterClient.layerConfig` loads only supplied config fields; unlike
49
+ `TypeSafeClient.layerConfig()`, it does not default an API-key environment name.
50
+ Client options also include `apiUrl`, `siteReferrer`, `siteTitle`, and
51
+ `transformClient`; scoped `OpenRouterConfig.withClientTransform` applies to
52
+ decision requests.
53
+ - Default endpoint: `https://openrouter.ai/api/alpha/decisions`. Custom `apiUrl`
54
+ removes a trailing `/v1` (optional trailing slash) before appending
55
+ `/alpha/decisions`, preserving proxy prefixes.
56
+ - Encoded state must be a string, object, or array; scalar null/number/boolean
57
+ fails locally with `AiError` reason `InvalidUserInputError`.
58
+ - Choice/Score **must include full distributions** for the high-level adapter;
59
+ missing distributions fail with `InvalidOutputError` even if a wire response
60
+ otherwise decodes. Score index keys map to criteria strings. The adapter uses
61
+ two-decimal probability normalization and common core answer validation.
62
+ - For response ID, cost, provider metadata, or HTTP response details, yield
63
+ `OpenRouterClient.OpenRouterClient` and call `createDecisions(request)`. It
64
+ returns `[decodedBody, HttpClientResponse]` with `AiError` failures. Its schemas
65
+ are `OpenRouterSchema.DecisionsRequest`, `DecisionsQuestion`, and
66
+ `DecisionsResponse`. High-level `decide` projects only answers and token usage.
67
+
68
+ Pinned source: `packages/ai/openrouter/src/{OpenRouterDecisionModel,OpenRouterClient,OpenRouterConfig,OpenRouterSchema}.ts`
69
+ at `effect@4.0.0`; corresponding `test/OpenRouterDecisionModel.test.ts` and
70
+ `typetest/OpenRouterDecisionModel.tst.ts` cover adapter behavior and configuration.
@@ -11,6 +11,9 @@ Baseline: `effect@4.0.0`. Inspect that tag in the Effect source reference, not
11
11
  unreleased main. Keep Effect-family packages on the same version. `effect/ai`
12
12
  APIs carry `@stability unstable` and may change incompatibly in minor releases.
13
13
 
14
+ For System One classification, rating, and probability calls through
15
+ `DecisionModel`, use `effect-ai-decision-model`.
16
+
14
17
  ## Import Patterns
15
18
 
16
19
  **CRITICAL**: Always use namespace imports:
@@ -12,6 +12,9 @@ Keep `effect` and every `@effect/*` package on the same version. Core AI APIs an
12
12
  provider clients, models, and generated schemas carry `@stability unstable`:
13
13
  minor releases may include breaking changes, even with `effect/ai` import paths.
14
14
 
15
+ For TypeSafe/Jev and OpenRouter **decision-model** layers, use
16
+ `effect-ai-decision-model`; its provider contracts differ from language models.
17
+
15
18
  ## When to Use This Skill
16
19
 
17
20
  Use this skill when: