@fayegram-ai/decision-bridge 0.0.0-stage → 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.
Files changed (113) hide show
  1. package/.env.example +10 -0
  2. package/CHANGELOG.md +79 -0
  3. package/CONTRIBUTING.md +57 -0
  4. package/LICENSE +21 -0
  5. package/README.md +313 -2
  6. package/SECURITY.md +88 -0
  7. package/bin/decision.mjs +15 -0
  8. package/docs/README.md +58 -0
  9. package/docs/development/ARCHITECTURE.md +139 -0
  10. package/docs/development/DESIGN.md +77 -0
  11. package/docs/development/README.md +12 -0
  12. package/docs/development/RELEASING.md +100 -0
  13. package/docs/development/REPOSITORY_LAYOUT.md +33 -0
  14. package/docs/development/TESTING.md +153 -0
  15. package/docs/guides/BROWSER.md +43 -0
  16. package/docs/guides/CLOUDFLARE_DECISIONS.md +374 -0
  17. package/docs/guides/CUSTOMIZATION.md +141 -0
  18. package/docs/guides/CUSTOM_DECISION.md +66 -0
  19. package/docs/guides/CUSTOM_GENERATIVE.md +164 -0
  20. package/docs/guides/CUSTOM_PROVIDERS.md +83 -0
  21. package/docs/guides/GETTING_STARTED.md +252 -0
  22. package/docs/guides/OPENAI_DECISIONS.md +252 -0
  23. package/docs/guides/PLATFORMS.md +107 -0
  24. package/docs/guides/SWITCHING_MODELS.md +208 -0
  25. package/docs/guides/TROUBLESHOOTING.md +96 -0
  26. package/docs/reference/CLI.md +213 -0
  27. package/docs/reference/CONFIGURATION.md +170 -0
  28. package/docs/reference/ERRORS.md +85 -0
  29. package/docs/reference/PROVIDERS.md +116 -0
  30. package/docs/reference/SDK.md +394 -0
  31. package/docs/reference/SOURCES.md +51 -0
  32. package/examples/basic.mjs +17 -0
  33. package/examples/batch.mjs +13 -0
  34. package/examples/cloudflare-decisions.mjs +22 -0
  35. package/examples/cloudflare-omni.mjs +32 -0
  36. package/examples/cloudflare-worker.mjs +21 -0
  37. package/examples/cloudflare.native.json +28 -0
  38. package/examples/configs/anthropic.json +9 -0
  39. package/examples/configs/cloudflare-decisions.json +8 -0
  40. package/examples/configs/custom-decision.json +9 -0
  41. package/examples/configs/custom-generative.json +14 -0
  42. package/examples/configs/jev.json +7 -0
  43. package/examples/configs/local.json +12 -0
  44. package/examples/configs/openai-decisions.json +7 -0
  45. package/examples/configs/openai.json +10 -0
  46. package/examples/configs/openrouter.json +10 -0
  47. package/examples/custom/decision-provider.mjs +15 -0
  48. package/examples/custom/generative-provider.mjs +13 -0
  49. package/examples/custom/openrouter.mjs +13 -0
  50. package/examples/customize.mjs +23 -0
  51. package/examples/decisions.native.json +19 -0
  52. package/examples/extended.mjs +43 -0
  53. package/examples/openai-decisions.mjs +20 -0
  54. package/examples/request.json +31 -0
  55. package/examples/requests.jsonl +2 -0
  56. package/package.json +87 -4
  57. package/schemas/decision.config.schema.json +553 -0
  58. package/src/async.mjs +63 -0
  59. package/src/batch.mjs +38 -0
  60. package/src/bridge.mjs +184 -0
  61. package/src/capabilities.mjs +92 -0
  62. package/src/cli/config-validation.mjs +89 -0
  63. package/src/cli/config.mjs +85 -0
  64. package/src/cli/decisions.mjs +21 -0
  65. package/src/cli/initialize.mjs +85 -0
  66. package/src/cli/io.mjs +171 -0
  67. package/src/cli/main.mjs +281 -0
  68. package/src/cli/ordered.mjs +43 -0
  69. package/src/cli/provider-loader.mjs +58 -0
  70. package/src/cloudflare-decisions.d.mts +78 -0
  71. package/src/custom/decision.d.mts +5 -0
  72. package/src/custom/decision.mjs +6 -0
  73. package/src/custom/definition.mjs +19 -0
  74. package/src/custom/generative/anthropic.mjs +75 -0
  75. package/src/custom/generative/index.d.mts +42 -0
  76. package/src/custom/generative/index.mjs +29 -0
  77. package/src/custom/generative/openai-compatible.mjs +101 -0
  78. package/src/custom/generative/schema.mjs +95 -0
  79. package/src/custom/generative/shared.mjs +39 -0
  80. package/src/decisions.d.mts +50 -0
  81. package/src/embedded.mjs +24 -0
  82. package/src/errors.mjs +49 -0
  83. package/src/index.d.mts +232 -0
  84. package/src/index.mjs +7 -0
  85. package/src/input.mjs +37 -0
  86. package/src/options.mjs +15 -0
  87. package/src/policy.mjs +44 -0
  88. package/src/probabilities.mjs +27 -0
  89. package/src/provider-contract.mjs +42 -0
  90. package/src/providers/cloudflare-audio.mjs +83 -0
  91. package/src/providers/cloudflare-codec.mjs +39 -0
  92. package/src/providers/cloudflare-decisions.mjs +81 -0
  93. package/src/providers/cloudflare-images.mjs +95 -0
  94. package/src/providers/cloudflare-media.mjs +36 -0
  95. package/src/providers/cloudflare-preflight.mjs +92 -0
  96. package/src/providers/cloudflare-profile.mjs +102 -0
  97. package/src/providers/cloudflare-transport.mjs +63 -0
  98. package/src/providers/cloudflare-validation.mjs +48 -0
  99. package/src/providers/cloudflare-video.mjs +84 -0
  100. package/src/providers/decisions-codec.mjs +61 -0
  101. package/src/providers/decisions-preflight.mjs +31 -0
  102. package/src/providers/decisions-validation.mjs +151 -0
  103. package/src/providers/index.mjs +26 -0
  104. package/src/providers/jev.mjs +51 -0
  105. package/src/providers/media-binary.mjs +51 -0
  106. package/src/providers/openai-decisions.mjs +58 -0
  107. package/src/providers/openai-profile.mjs +24 -0
  108. package/src/questions.mjs +15 -0
  109. package/src/response-transport.mjs +176 -0
  110. package/src/runtime-env.mjs +4 -0
  111. package/src/transport.mjs +51 -0
  112. package/src/validation.mjs +245 -0
  113. package/src/version.mjs +2 -0
@@ -0,0 +1,164 @@
1
+ # Custom generative providers
2
+
3
+ This opt-in path elicits decisions from general-purpose models. It retains the
4
+ maintained OpenAI-compatible, OpenRouter, and Anthropic adapters. Import them from
5
+ `@fayegram-ai/decision-bridge/custom/generative`; the root factory accepts native
6
+ decision providers only.
7
+
8
+ ## Use a maintained preset
9
+
10
+ ```javascript
11
+ import { createBridge, noul } from '@fayegram-ai/decision-bridge';
12
+ import { createGenerativeProvider } from '@fayegram-ai/decision-bridge/custom/generative';
13
+
14
+ const provider = createGenerativeProvider({
15
+ type: 'openrouter',
16
+ model: process.env.DECISION_MODEL, // A model available through your account.
17
+ });
18
+ const bridge = createBridge({ provider });
19
+ const result = await bridge.evaluate({
20
+ state: 'Test report: two assertions failed.',
21
+ questions: { failure: noul('Does the report explicitly state that a test failed?') },
22
+ });
23
+ console.log(result.meta.providerKind); // generative
24
+ console.log(result.meta.probabilitySource); // elicited
25
+ ```
26
+
27
+ Set `OPENROUTER_API_KEY` and a real `DECISION_MODEL`, then load your environment
28
+ explicitly with Node or your application's configuration. Evaluation is a real
29
+ service call. Presets have no default model. The classes `OpenAICompatibleProvider`,
30
+ `OpenRouterProvider`, and `AnthropicProvider` and their option types are exported
31
+ from the same subpath.
32
+
33
+ ## CLI presets
34
+
35
+ ```bash
36
+ npm exec -- decision-bridge init ./my-model --provider custom-generative --preset openrouter --model VENDOR/MODEL_ID
37
+ npm exec -- decision-bridge evaluate ./my-model/request.json --config ./my-model/decision.config.json --env-file .env
38
+ ```
39
+
40
+ Use `--preset openai-compatible` or `--preset anthropic` for the other protocols.
41
+ A configuration selects exactly one preset or module:
42
+
43
+ ```json
44
+ {
45
+ "schemaVersion": 1,
46
+ "provider": "custom-generative",
47
+ "preset": "openai-compatible",
48
+ "model": "YOUR_MODEL_ID",
49
+ "apiKeyEnv": "OPENAI_API_KEY",
50
+ "responseFormat": "json_schema",
51
+ "timeoutMs": 30000
52
+ }
53
+ ```
54
+
55
+ For a loopback server, set `baseURL`, `noAuth: true`, the served model, and the
56
+ appropriate `tokenParameter`. Remote custom origins require explicit credentials.
57
+ See [configuration](../reference/CONFIGURATION.md).
58
+
59
+ ## A new protocol
60
+
61
+ Use `defineGenerativeProvider` for a module implementing a new wire protocol.
62
+ `init --provider custom-generative` creates a module with a clear unimplemented
63
+ service call; it never produces synthetic decisions as a fallback.
64
+
65
+ The subpath exports `buildResponseSchema(questions)`, `buildPrompt(request)`,
66
+ `parseGeneratedJson(text)`, and `normalizeGenerated(payload, request, options)`.
67
+ Build a prompt/schema from a validated, resolved legacy request, send it using
68
+ transport you own, parse the generated JSON, then normalize it. The normalizer
69
+ expects exactly `{ answers }`: each `noul` answer contains `{ noul }`, and each
70
+ choice/score contains `{ probabilities }` with exactly the supplied option keys
71
+ or zero-based level indices. It checks probabilities, derives argmax/weighted
72
+ scores and matching legends, and marks absent usage as unknown. It does not repair
73
+ JSON, strip Markdown, or create confidence. `buildPrompt` and `normalizeGenerated`
74
+ independently validate and snapshot the request. They require shared state and
75
+ legacy questions, and reject ordered input, typed choices, labelled scores, and
76
+ safety identifiers with `UNSUPPORTED_CAPABILITY`. Malformed request JSON raises
77
+ `VALIDATION_ERROR`; unsupported input is never silently discarded. New modules
78
+ implement their own encoding and normalization for richer capabilities.
79
+
80
+ | Helper | Return value and responsibility |
81
+ | --- | --- |
82
+ | `buildResponseSchema(questions)` | JSON Schema for legacy question answers; validates the definitions without selecting an inference model |
83
+ | `buildPrompt(request)` | `{ system, user }` strings for a resolved shared-state request; requires a model in the request |
84
+ | `parseGeneratedJson(text)` | Parsed JSON; rejects empty or malformed content with `INVALID_RESPONSE`; answer validation happens in normalization |
85
+ | `normalizeGenerated(payload, request, options?)` | Canonical, validated `{ model, answers, usage, meta }`; returns a snapshot that the bridge subsequently freezes |
86
+
87
+ The normalizer's optional third argument accepts `model`, `usage`, and `meta`:
88
+
89
+ | Field | Meaning |
90
+ | --- | --- |
91
+ | `model` | Reported nonempty model ID; omission or null uses `request.model` and marks `modelSource: 'requested'` |
92
+ | `usage` | Validated common token counts, with null for unreported counts; omission produces null input/output counts |
93
+ | `meta` | Additional JSON metadata from your transport; normalization supplies authoritative `requestedModel`, `modelSource`, `probabilitySource: 'elicited'`, and `confidenceSource: 'unavailable'` |
94
+
95
+ None of these helpers sends a request. Your provider supplies the transport,
96
+ cancellation, response extraction, and safe error handling. Use the
97
+ [common usage and metadata contract](../reference/SDK.md#returned-data) for the
98
+ reported fields; the bridge adds its own classification and final timings.
99
+
100
+ A runnable delegation module is provided at
101
+ [examples/custom/generative-provider.mjs](../../examples/custom/generative-provider.mjs).
102
+ Use the shared [module contract](CUSTOM_PROVIDERS.md) for loader options, timeouts,
103
+ capabilities, and testing requirements. No arbitrary declarative HTTP mapping
104
+ language is supported.
105
+
106
+ ## Result semantics
107
+
108
+ These providers have `kind: 'generative'`. The bridge requires elicited probability
109
+ provenance and `confidenceSource: 'unavailable'`, and rejects returned native
110
+ confidence claims or answer confidence fields. Their output is an estimate elicited
111
+ from a model, not a native probability, logprob, or calibration guarantee.
112
+ Presets support shared state and legacy `choice`, `noul`, and `score`; ordered
113
+ media, typed choices, labelled scores, and safety identifiers are unsupported.
114
+ A new module may declare capabilities only after implementing and validating them.
115
+
116
+ ## OpenAI-compatible servers
117
+
118
+ Default base URL: `https://api.openai.com/v1`. Base paths such as `/v1` are preserved;
119
+ provide the API base, not the final `/chat/completions` path.
120
+
121
+ `json_schema` sends `response_format.type = json_schema`, `strict: true`, and a
122
+ schema whose required object fields disallow additional properties. Ranges and
123
+ normalization are validated locally to accommodate different supported schema
124
+ subsets. `json_object` requests JSON without schema enforcement. `text` omits the
125
+ format field and relies on instructions plus strict local validation.
126
+
127
+ There is **no auto mode**: an unsupported schema error is visible rather than
128
+ triggering a second request with weaker enforcement. A refusal, truncated output,
129
+ malformed JSON, probability violation, or unexpected tool call is an explicit
130
+ error. No Markdown stripping or JSON repair occurs.
131
+
132
+ The default token field is `max_completion_tokens`. Some compatible servers need
133
+ `max_tokens`; set `tokenParameter` explicitly. No temperature or reasoning setting
134
+ is forced. Additional generation parameters must be supported by the selected
135
+ model. Presets reserve current and legacy tool/function controls, including
136
+ `functions` and `function_call`, to preserve their answer-output protocol. Use a
137
+ custom module when you need to implement a different protocol.
138
+ This provider does not implement OpenAI Responses API, realtime streaming,
139
+ multimodal inputs, or a general function-calling agent loop.
140
+
141
+ ## OpenRouter
142
+
143
+ `OpenRouterProvider` is a fixed-endpoint generative preset. It reads its own key
144
+ and defaults to `max_tokens`. It sets
145
+ `parameters.provider.require_parameters = true` so required request features are
146
+ not silently ignored by routing. Other explicit routing settings are preserved.
147
+ The preset does not guarantee which upstream host serves a model; use OpenRouter's
148
+ routing controls for stricter requirements.
149
+
150
+ Select a **generative model** that supports the chosen output mode. Other API
151
+ protocols require an explicit [custom provider](CUSTOM_PROVIDERS.md).
152
+
153
+ ## Anthropic
154
+
155
+ The provider sends Messages API headers `x-api-key` and
156
+ `anthropic-version: 2023-06-01`. It defines a synthetic `submit_decisions` tool whose
157
+ input schema is the expected answer object, then forces exactly that tool. The
158
+ returned `tool_use.input` carries the data; **no tool is executed** and no follow-up
159
+ agent cycle is started.
160
+
161
+ Extended thinking is not enabled in this forced-tool strategy; a `thinking`
162
+ parameter is rejected. Refusals, truncated output, unexpected stop reasons, and
163
+ missing/extra named tools fail. `GET /v1/models` returns the first page with
164
+ `has_more` / `last_id` preserved. Automatic pagination is not implemented.
@@ -0,0 +1,83 @@
1
+ # Custom provider contracts
2
+
3
+ Decision Bridge has one bridge and two explicit extension paths:
4
+
5
+ | Path | SDK helper | CLI provider | Purpose |
6
+ | --- | --- | --- | --- |
7
+ | [Custom decision](CUSTOM_DECISION.md) | `defineDecisionProvider` from `custom/decision` | `custom-decision` with `module` | A new decision model protocol |
8
+ | [Custom generative](CUSTOM_GENERATIVE.md) | `defineGenerativeProvider` from `custom/generative` | `custom-generative` with `preset` or `module` | Elicit structured decisions from a general-purpose model |
9
+
10
+ The root `createProvider()` factory supports native Jev, OpenAI Decisions, and
11
+ Cloudflare only. `createGenerativeProvider()` selects maintained generative
12
+ presets. Select the corresponding custom entry point explicitly for either
13
+ extension path.
14
+
15
+ ## Common SDK contract
16
+
17
+ `Provider` requires a nonempty `name`, explicit `kind: 'decision' | 'generative'`,
18
+ and `evaluate(request, { signal? })`. Optional fields and hooks are:
19
+
20
+ - `defaultModel`: a nonempty selection string, otherwise the request must supply one.
21
+ - `probabilitySource`: known provenance; generative providers use `elicited`.
22
+ - `getCapabilities(model)`: a pure synchronous capability profile.
23
+ - `validateRequest(request)`: pure synchronous provider preflight; no inference.
24
+ - `getResultValidationOptions(model)`: pure synchronous probability precision
25
+ policy, never derived from a response or its metadata.
26
+ - `listModels({ signal? })`: a model catalog when the service implements one.
27
+
28
+ Both definition helpers validate plain data definitions and freeze their wrappers.
29
+ Callbacks keep their original receiver. The bridge also accepts provider classes
30
+ that implement this contract directly. Missing or invalid kinds fail at construction.
31
+ `bridge.providerKind` and `result.meta.providerKind` come from the contract and
32
+ cannot be overridden by response metadata.
33
+
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.
37
+ Return canonical `{ model, answers, usage, meta? }`; the bridge validates and
38
+ freezes the result. Custom providers own API encoding, transport, response mapping,
39
+ credential destinations, bounded bodies, safe errors, and cancellation cleanup.
40
+ Internal transport helpers are not stable public exports. Delegate a maintained
41
+ preset for a protocol already supported, or implement and test your own transport.
42
+
43
+ Capability omissions retain the legacy shared-state and question profile. Audio
44
+ and video additions default to false/zero. Declare richer forms only when their
45
+ encoding and validation are implemented. The bridge runs preflight again after
46
+ preparation hooks. Unknown usage is null; no provider should invent token counts,
47
+ confidence, or calibration. Generative native-provenance claims are rejected.
48
+ Supplied `probabilitySource` and `confidenceSource` metadata must be nonempty
49
+ strings. Decision providers may use custom labels; omitted probability provenance
50
+ uses the provider's declared value, or `unspecified` when none is declared.
51
+
52
+ ## Explicit CLI modules
53
+
54
+ Select a custom kind and `module` in JSON, or pass both `--provider custom-decision`
55
+ (or `custom-generative`) and `--provider-module FILE`. A module flag does not infer
56
+ a kind. A generative profile chooses exactly one `preset` or `module`; module
57
+ profiles require JSON `options` for implementation-specific transport/tuning.
58
+ Inline `apiKey`/`api_key` options are rejected; use top-level `apiKeyEnv`.
59
+
60
+ The module exports `createProvider(options)`, synchronously or asynchronously.
61
+ Import/factory waiting is bounded by `timeoutMs` (30 seconds by default) and CLI
62
+ cancellation, separately from evaluation. The loader passes JSON `options`, then
63
+ the selected `apiKey` and `model` when present. Its returned kind must match the
64
+ selected path. The CLI also applies its configured model to common requests that
65
+ omit a model, independently of the factory's own default; an explicit request
66
+ model takes precedence, and `--model` overrides both. Module paths in JSON resolve
67
+ relative to that file; flag paths
68
+ resolve relative to the working directory. Explicit modules execute trusted code;
69
+ this extension point is not a sandbox. No module is discovered automatically.
70
+
71
+ `init` creates an honest service implementation placeholder when no implementation
72
+ is selected. When given an existing module, it references that module without
73
+ loading or overwriting it. Offline `validate` never imports custom modules or
74
+ requires credentials; it reports structure-only validation for unknown protocols.
75
+ `bridge.validate()` on an instantiated provider includes capabilities and preflight.
76
+
77
+ ## Tests for a new protocol
78
+
79
+ Test serialized bodies, credentials/headers, endpoint paths, all supported question
80
+ mappings, usage/provenance, refusals/truncation, malformed distributions, timeouts,
81
+ cancellation, and safe errors. Use local HTTP fixtures for deterministic protocol
82
+ checks. Live tests must be explicit and credential-dependent; fixture tests do
83
+ not establish model quality. See [testing](../development/TESTING.md).
@@ -0,0 +1,252 @@
1
+ # Getting started
2
+
3
+ Use the SDK from your JavaScript or TypeScript application to submit `state` and
4
+ `questions` and receive structured answers. The optional command runner can also
5
+ evaluate request files.
6
+
7
+ ## Prerequisites
8
+
9
+ This guide's example uses Node.js **22.16 or newer**, npm, and a TypeSafe API key
10
+ for the explicitly selected Jev provider. The SDK also loads in modern browsers; direct calls
11
+ depend on the provider's CORS policy and an explicit client-safe credential. See
12
+ [browser integration](BROWSER.md). Other backends use their own keys; see
13
+ [switching models](SWITCHING_MODELS.md).
14
+
15
+ ## Install into your application
16
+
17
+ In your application's directory, install the package from npm:
18
+
19
+ ```bash
20
+ npm install @fayegram-ai/decision-bridge
21
+ ```
22
+
23
+ You can also install a verified local archive, replacing the path below. See
24
+ [packaging and release](../development/RELEASING.md) to build and verify one from
25
+ a source checkout:
26
+
27
+ ```bash
28
+ npm install /path/to/fayegram-ai-decision-bridge-0.1.1.tgz
29
+ ```
30
+
31
+ The installed package contains the SDK, declarations, command runner, examples,
32
+ and docs. It runs without compilation; source development scripts and tests are
33
+ excluded.
34
+
35
+ ## Configure credentials
36
+
37
+ Create `.env` in your application's directory and edit it locally:
38
+
39
+ ```dotenv
40
+ TYPESAFE_API_KEY=your_real_key_here
41
+ ```
42
+
43
+ Add `.env` to your application's `.gitignore`. The SDK reads `TYPESAFE_API_KEY`
44
+ from the process environment when constructing the selected Jev provider. It does not load
45
+ `.env` itself; the Node startup command below loads it explicitly. In a deployed
46
+ application, you can supply the same variable through your runtime's environment.
47
+
48
+ ## Make your first SDK call
49
+
50
+ Save this as `app.mjs` beside `.env`. The `.mjs` extension enables ESM regardless
51
+ of your application's `package.json` settings:
52
+
53
+ ```javascript
54
+ import { createBridge, choice, noul, publicError } from '@fayegram-ai/decision-bridge';
55
+
56
+ try {
57
+ const bridge = createBridge({ jev: {} }); // Jev; defaults to model jev-latest.
58
+ const result = await bridge.evaluate({
59
+ state: { message: 'I was charged twice for the same invoice.' },
60
+ questions: {
61
+ route: choice('Which team should inspect this message?', {
62
+ billing: 'Invoices, charges, or payment problems.',
63
+ technical: 'Software defects or connectivity problems.',
64
+ unknown: 'The evidence does not establish a team.',
65
+ }),
66
+ duplicate_charge: noul('Does the message report a duplicate charge?'),
67
+ },
68
+ });
69
+ console.log(result.answers.route.choice);
70
+ console.log(result.answers.duplicate_charge.noul);
71
+ console.log(result.model, result.meta.probabilitySource);
72
+ } catch (error) {
73
+ console.error(publicError(error));
74
+ process.exitCode = 1;
75
+ }
76
+ ```
77
+
78
+ Run it from your application directory:
79
+
80
+ ```bash
81
+ node --env-file=.env app.mjs
82
+ ```
83
+
84
+ This sends one real evaluation and may incur charges. `route.choice` is one of
85
+ `billing`, `technical`, or `unknown`; `duplicate_charge.noul` is a probability
86
+ between 0 and 1. `result.model` identifies the returned model, and Jev marks its
87
+ probability source as `native`. Predictions depend on the model. Your application
88
+ decides how to use them; the SDK does not route messages or execute actions.
89
+
90
+ Change `state` and the question instructions/criteria on each call. No prompt
91
+ registration is required. Reuse the bridge for subsequent requests. To choose
92
+ a Jev model explicitly, construct it with `createBridge({ jev: { model: 'YOUR_MODEL' } })`,
93
+ using an ID available to your account. An individual request's `model` overrides
94
+ that default. See the [SDK reference](../reference/SDK.md) for scores, result
95
+ metadata, cancellation, batching, and extensions.
96
+
97
+ Bridge construction can fail for invalid configuration or missing credentials;
98
+ evaluation can fail for invalid input or provider errors. Both are inside the
99
+ example's `try` block. `publicError(error)` returns a safe `code` and `message`, plus
100
+ HTTP status, request ID, and attempt count when available. See [errors and recovery](../reference/ERRORS.md).
101
+
102
+ SDK selection is explicit: use `createBridge({ provider })` with a constructed
103
+ provider or `createBridge({ jev: options })` for Jev. Empty options and omitted
104
+ options are errors. `createProvider` also requires its `type` explicitly.
105
+ The CLI has its own Jev default and does not change SDK selection rules.
106
+
107
+ ## TypeScript
108
+
109
+ Declarations ship with the package. For a standalone Node TypeScript example,
110
+ install these development tools in the consuming application (TypeScript 5.8.3
111
+ is the compiler used by this repository's CI):
112
+
113
+ ```bash
114
+ npm install --save-dev typescript@5.8.3 @types/node@22
115
+ ```
116
+
117
+ Create `tsconfig.json`, or incorporate these settings into your existing config:
118
+
119
+ ```json
120
+ {
121
+ "compilerOptions": {
122
+ "target": "ES2022",
123
+ "module": "NodeNext",
124
+ "moduleResolution": "NodeNext",
125
+ "strict": true,
126
+ "outDir": "dist",
127
+ "types": ["node"]
128
+ },
129
+ "include": ["app.mts"]
130
+ }
131
+ ```
132
+
133
+ Save this as `app.mts`. The `.mts` extension makes it an ESM module and produces
134
+ `dist/app.mjs`; no `"type": "module"` setting is needed for this example:
135
+
136
+ ```typescript
137
+ import { createBridge, choice, publicError } from '@fayegram-ai/decision-bridge';
138
+
139
+ try {
140
+ const bridge = createBridge({ jev: {} });
141
+ const result = await bridge.evaluate({
142
+ state: 'An invoice appears twice.',
143
+ questions: {
144
+ route: choice('Which team should inspect this?', {
145
+ billing: 'An invoice or payment issue.',
146
+ unknown: 'Insufficient evidence.',
147
+ }),
148
+ },
149
+ });
150
+ // The builder preserves the allowed labels in the answer type.
151
+ const route: 'billing' | 'unknown' = result.answers.route.choice;
152
+ const probability: number = result.answers.route.probabilities[route];
153
+ console.log(route, probability);
154
+ } catch (error) {
155
+ console.error(publicError(error));
156
+ process.exitCode = 1;
157
+ }
158
+ ```
159
+
160
+ Compile, then run with the same environment file:
161
+
162
+ ```bash
163
+ npm exec -- tsc --project tsconfig.json
164
+ node --env-file=.env dist/app.mjs
165
+ ```
166
+
167
+ ## Existing CommonJS applications
168
+
169
+ Use dynamic import from an async function in a `.cjs` module:
170
+
171
+ ```javascript
172
+ async function main() {
173
+ const { createBridge, noul } = await import('@fayegram-ai/decision-bridge');
174
+ const bridge = createBridge({ jev: {} });
175
+ const result = await bridge.evaluate({
176
+ state: 'The test log reports a failure.',
177
+ questions: { failed: noul('Does the log report a failed test?') },
178
+ });
179
+ console.log(result.answers.failed.noul);
180
+ }
181
+
182
+ main().catch(() => {
183
+ console.error('Evaluation failed. Check credentials and request configuration.');
184
+ process.exitCode = 1;
185
+ });
186
+ ```
187
+
188
+ Save it as `app.cjs` and run `node --env-file=.env app.cjs`. The package exposes an
189
+ ESM import entry; it does not provide a native `require()` entry.
190
+
191
+ ## Optional command runner
192
+
193
+ After a local package installation, use `npm exec` from the consuming project:
194
+
195
+ ```bash
196
+ npm exec -- decision-bridge init ./my-decisions
197
+ npm exec -- decision-bridge validate ./my-decisions/request.json
198
+ ```
199
+
200
+ `init` creates `decision.config.json`, `decision.config.schema.json`, `request.json`,
201
+ `.env.example`, and `.gitignore`; existing files are never overwritten. The config's
202
+ `$schema` points to the copied schema for editor completion. `validate` checks the request offline.
203
+ Copy the generated `.env.example` to `.env` inside `my-decisions`, then edit its key.
204
+ Evaluate the generated request with explicit paths:
205
+
206
+ ```bash
207
+ npm exec -- decision-bridge evaluate ./my-decisions/request.json --config ./my-decisions/decision.config.json --env-file ./my-decisions/.env
208
+ ```
209
+
210
+ The command runner does not discover `.env` or configuration files automatically.
211
+ Paths are relative to the working directory, except custom provider module paths
212
+ inside a config file, which are relative to that file. The [command reference](../reference/CLI.md)
213
+ covers commands, flags, streams, and exit codes.
214
+
215
+ Global installation is optional:
216
+
217
+ ```bash
218
+ npm install --global @fayegram-ai/decision-bridge
219
+ decision-bridge --version
220
+ ```
221
+
222
+ The package also installs `decision` and `faye-decision` as equivalent CLI aliases.
223
+ For example, `npm exec -- faye-decision --version` runs the same command runner.
224
+ The shorter `decision` alias may collide with an existing executable; prefer
225
+ `decision-bridge` or `faye-decision` in automation.
226
+
227
+ ## Run from a source checkout
228
+
229
+ For repository development, open a terminal at the source root. Copy `.env.example`
230
+ to `.env` (`cp .env.example .env` on macOS/Linux or `Copy-Item .env.example .env`
231
+ in PowerShell), then edit the key locally. Run:
232
+
233
+ ```bash
234
+ npm ci --ignore-scripts
235
+ npm test
236
+ npm run validate
237
+ npm run models
238
+ npm run ask
239
+ npm run example
240
+ ```
241
+
242
+ `npm ci --ignore-scripts` installs the pinned development dependencies from the
243
+ lockfile; it can access the npm registry and requires no model credentials.
244
+ `npm test` and `npm run validate` are offline. `models` lists account models; `ask` evaluates
245
+ `examples/request.json`; `example` runs the source SDK example. The last three
246
+ explicitly load `.env` when present and access the provider. A model listing does
247
+ not prove inference access; evaluation verifies that path.
248
+
249
+ Source examples import `../src/index.mjs` and run directly without installing the
250
+ package. Application code should use `@fayegram-ai/decision-bridge` as shown above.
251
+ See [platform setup](PLATFORMS.md) for operating-system details and
252
+ [contributor documentation](../development/README.md) for development checks.