@spendguard/sdk 0.5.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 +190 -0
- package/LICENSE_NOTICES.md +127 -0
- package/README.md +151 -0
- package/dist/adapter-D9T3yEEw.d.ts +3441 -0
- package/dist/cache-DOnw8QtJ.d.ts +1164 -0
- package/dist/cache.d.ts +6 -0
- package/dist/cache.js +74 -0
- package/dist/client.d.ts +6 -0
- package/dist/client.js +4815 -0
- package/dist/errors.d.ts +269 -0
- package/dist/errors.js +148 -0
- package/dist/ids.d.ts +69 -0
- package/dist/ids.js +61 -0
- package/dist/index.d.ts +61 -0
- package/dist/index.js +5295 -0
- package/dist/otel.d.ts +118 -0
- package/dist/otel.js +84 -0
- package/dist/pricing/demo.d.ts +26 -0
- package/dist/pricing/demo.js +138 -0
- package/dist/pricing.d.ts +70 -0
- package/dist/pricing.js +92 -0
- package/dist/promptHash.d.ts +23 -0
- package/dist/promptHash.js +25 -0
- package/dist/proto.d.ts +609 -0
- package/dist/proto.js +3055 -0
- package/dist/retry.d.ts +121 -0
- package/dist/retry.js +92 -0
- package/dist/runPlan.d.ts +69 -0
- package/dist/runPlan.js +35 -0
- package/fixtures/cross-language/v1.json +327 -0
- package/package.json +123 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# `@spendguard/sdk` Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to the TypeScript half of the SpendGuard SDK.
|
|
4
|
+
|
|
5
|
+
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
6
|
+
This package adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
The Python SDK ([spendguard-sdk on PyPI](https://pypi.org/project/spendguard-sdk/),
|
|
9
|
+
currently v0.5.1) and this TypeScript SDK are kept in lockstep on the public
|
|
10
|
+
surface — see
|
|
11
|
+
[`docs/specs/coverage/D05_ts_sdk_substrate/design.md`](../../docs/specs/coverage/D05_ts_sdk_substrate/design.md)
|
|
12
|
+
§9 for the lockstep contract.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## [0.5.0] — 2026-06-19
|
|
17
|
+
|
|
18
|
+
First public npm release of `@spendguard/sdk`. Version chosen to track the
|
|
19
|
+
Python SDK line (PyPI at 0.6.0) rather than continue the internal 0.1.x
|
|
20
|
+
sequence; no 0.2–0.4 npm releases exist.
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- `UnitRef.unitId` — optional canonical-truth UUID of the ledger unit row.
|
|
25
|
+
Adapters that issue ledger-backed reserve calls now thread this through to
|
|
26
|
+
`BudgetClaim.unit.unit_id` on the wire. Closes the HARDEN_D05_UR substrate
|
|
27
|
+
gap that previously blocked DENY+STREAM full assertion across ~14 adapter
|
|
28
|
+
demos. Backward-compat: omitting `unitId` matches prior behavior.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## [0.1.0] — 2026-06-07
|
|
33
|
+
|
|
34
|
+
First public release. Mirrors `spendguard-sdk` (Python) v0.5.1 public surface.
|
|
35
|
+
Closes deliverable D05 (TypeScript SDK substrate).
|
|
36
|
+
|
|
37
|
+
### Added
|
|
38
|
+
|
|
39
|
+
#### Client (SLICE 3-5)
|
|
40
|
+
|
|
41
|
+
- `SpendGuardClient` — gRPC-over-UDS client for the SpendGuard sidecar.
|
|
42
|
+
- `handshake()` — protocol-version + capabilities negotiation.
|
|
43
|
+
- `reserve()` — pre-flight budget decision (the canonical method name).
|
|
44
|
+
- `requestDecision()` — `reserve()` alias for migration ergonomics; `===`
|
|
45
|
+
identity is asserted as a P0 surface invariant
|
|
46
|
+
(review-standards §1.5).
|
|
47
|
+
- `commitEstimated()` — observed-output commit (cost rounded up).
|
|
48
|
+
- `release()` — terminal reservation release with explicit
|
|
49
|
+
`releaseReason`.
|
|
50
|
+
- `queryBudget()` — wire stub (throws "not yet wired" until cross-component
|
|
51
|
+
slice; documented in JSDoc per review-standards §11.3).
|
|
52
|
+
- `SpendGuardClientOptions`, `HandshakeOutcome`, `DecisionOutcome`,
|
|
53
|
+
`ReleaseOutcome` types.
|
|
54
|
+
- Default deadlines: `DEFAULT_DECISION_TIMEOUT_MS`,
|
|
55
|
+
`DEFAULT_HANDSHAKE_TIMEOUT_MS`, `DEFAULT_PUBLISH_TIMEOUT_MS`,
|
|
56
|
+
`DEFAULT_TRACE_TIMEOUT_MS`.
|
|
57
|
+
- `unix:` URI handling matches Python (sets
|
|
58
|
+
`grpc.default_authority: "localhost"` so `tonic` does not 400 with
|
|
59
|
+
`PROTOCOL_ERROR` on the URL-encoded path).
|
|
60
|
+
|
|
61
|
+
#### IDs (SLICE 6)
|
|
62
|
+
|
|
63
|
+
- `newUuid7()` — UUIDv7 generator (sortable, embedded ms timestamp).
|
|
64
|
+
- `deriveIdempotencyKey()` — deterministic `sg-…` key. **P0 cross-language
|
|
65
|
+
byte-equivalent with Python `spendguard.derive_idempotency_key()` and the
|
|
66
|
+
Rust sidecar's `audit_chain::derive_key`** (sidecar-side dedup correctness).
|
|
67
|
+
- `deriveUuidFromSignature()` — BLAKE2b-based UUID derivation from an
|
|
68
|
+
application signature + scope. **Byte-equivalent with Python.** Uses
|
|
69
|
+
`@noble/hashes` BLAKE2b implementation.
|
|
70
|
+
- `defaultCallSignature()` — framework-agnostic signature helper.
|
|
71
|
+
- `workloadInstanceId()` — process-stable identifier.
|
|
72
|
+
|
|
73
|
+
#### Pricing (SLICE 6)
|
|
74
|
+
|
|
75
|
+
- `PricingLookup` — typed lookup with provider+model+kind keys.
|
|
76
|
+
- `USD_MICROS_PER_USD` — wire-unit conversion constant.
|
|
77
|
+
- `DEMO_PRICING` — embedded pricing snapshot for the demo seed
|
|
78
|
+
(`pricing_version: v2026.05.09-1`, regenerated at release time from
|
|
79
|
+
`deploy/demo/init/pricing/seed.yaml`).
|
|
80
|
+
|
|
81
|
+
#### Prompt hash (SLICE 6)
|
|
82
|
+
|
|
83
|
+
- `computePromptHash()` — HMAC-SHA256 lowercase hex. **P0 cross-language
|
|
84
|
+
byte-equivalent with Python `spendguard.compute_prompt_hash()` and the
|
|
85
|
+
sidecar `prompt_hash::compute`**. Tenant ID is canonicalised to lowercase
|
|
86
|
+
before hashing — the `crossLanguage` test suite pins this.
|
|
87
|
+
|
|
88
|
+
#### Run plan / Signal 3 (SLICE 7)
|
|
89
|
+
|
|
90
|
+
- `withRunPlan()` + `currentRunPlan()` — `AsyncLocalStorage`-scoped
|
|
91
|
+
budget-hint context. Adapters set the plan once at run boundary; nested
|
|
92
|
+
`reserve()` calls read it transparently.
|
|
93
|
+
|
|
94
|
+
#### OTel (SLICE 8)
|
|
95
|
+
|
|
96
|
+
- `otelTracer` config field — optional `@opentelemetry/api` `Tracer`.
|
|
97
|
+
- Per-RPC spans named `spendguard.<rpc>` with attributes documented in
|
|
98
|
+
`design.md` §6.4 + `SPENDGUARD_OTEL_ATTR` constant export.
|
|
99
|
+
- `@opentelemetry/api` is a peer dep, marked optional in
|
|
100
|
+
`peerDependenciesMeta`.
|
|
101
|
+
|
|
102
|
+
#### Retry (SLICE 8)
|
|
103
|
+
|
|
104
|
+
- Bounded retry for the `UNAVAILABLE` / `DEADLINE_EXCEEDED` / `CANCELLED`
|
|
105
|
+
cluster. Max 2 attempts, constant 25 ms + jitter backoff. Idempotency-key
|
|
106
|
+
required (the substrate enforces).
|
|
107
|
+
|
|
108
|
+
#### Idempotency cache (SLICE 8)
|
|
109
|
+
|
|
110
|
+
- In-process LRU keyed by `(tenantId, idempotencyKey)` so re-attempts after
|
|
111
|
+
retry observation surface the cached decision rather than re-RPC'ing.
|
|
112
|
+
|
|
113
|
+
#### Cross-language fixtures (SLICE 9)
|
|
114
|
+
|
|
115
|
+
- `sdk/fixtures/cross-language/v1.json` — the SINGLE SOURCE OF TRUTH for
|
|
116
|
+
byte-equivalence between the Rust sidecar, Python SDK, and TS SDK. v1.json
|
|
117
|
+
ships with ≥20 fixtures spanning `compute_prompt_hash`,
|
|
118
|
+
`derive_idempotency_key`, and `derive_uuid_from_signature` (see
|
|
119
|
+
`sdk/fixtures/cross-language/README.md` for the add-a-fixture / mint-v2
|
|
120
|
+
runbook).
|
|
121
|
+
- The fixture file is **included in the published npm tarball** under
|
|
122
|
+
`fixtures/cross-language/v1.json` so consumer-side conformance suites can
|
|
123
|
+
pin against the exact same vectors.
|
|
124
|
+
|
|
125
|
+
#### Errors
|
|
126
|
+
|
|
127
|
+
- `SpendGuardError`, `HandshakeError`, `SidecarUnavailable`,
|
|
128
|
+
`DecisionDenied`, `DecisionStopped`, `DecisionSkipped`, `ApprovalRequired`,
|
|
129
|
+
`ApprovalDeniedError`, `ApprovalLapsedError`,
|
|
130
|
+
`ApprovalBundleHotReloadedError`, `MutationApplyFailed`,
|
|
131
|
+
`SpendGuardConfigError` — full hierarchy mirror of the Python SDK.
|
|
132
|
+
|
|
133
|
+
### Locked invariants
|
|
134
|
+
|
|
135
|
+
- **P0 cross-language byte-equivalence** with the Python SDK (and the Rust
|
|
136
|
+
sidecar) on `computePromptHash`, `deriveIdempotencyKey`, and
|
|
137
|
+
`deriveUuidFromSignature`. Drift breaks audit-chain dedup and the
|
|
138
|
+
idempotency replay collapse contract. Enforced by the
|
|
139
|
+
`tests/crossLanguage.test.ts` suite consuming
|
|
140
|
+
`sdk/fixtures/cross-language/v1.json` (also consumed by
|
|
141
|
+
`sdk/python/tests/test_cross_language_fixtures.py`).
|
|
142
|
+
- **`reserve()` === `requestDecision()` reference identity** (P0 surface
|
|
143
|
+
invariant, `tests/locked-surface.test.ts`).
|
|
144
|
+
- **ESM-only.** No CJS shim. `"type": "module"`.
|
|
145
|
+
- **camelCase on the public surface, snake_case on the wire** — every wire
|
|
146
|
+
conversion is centralised in `src/client.ts` so adapter authors never see
|
|
147
|
+
snake_case.
|
|
148
|
+
|
|
149
|
+
### Compatibility
|
|
150
|
+
|
|
151
|
+
- Node 20.10+ (uses `using` / `await using`, stable `AsyncLocalStorage`,
|
|
152
|
+
Web Crypto).
|
|
153
|
+
- Bun 1.1+ tested as secondary target.
|
|
154
|
+
- Deno 1.46+ tested as secondary target.
|
|
155
|
+
- **Browser is NOT supported in v0.1.x.** UDS gRPC is server-only. A future
|
|
156
|
+
v0.x ASP HTTP gateway transport is forward-reserved in the config type but
|
|
157
|
+
not built.
|
|
158
|
+
|
|
159
|
+
### Known limitations (deferred to future slices)
|
|
160
|
+
|
|
161
|
+
- `queryBudget()` wire body — stub-throws "not yet wired". Cross-component
|
|
162
|
+
slice unblocks.
|
|
163
|
+
- LLM_CALL_OUTCOME proto bump — deferred to a cross-component slice that
|
|
164
|
+
spans D05 + Rust sidecar + Python SDK simultaneously.
|
|
165
|
+
- Per-framework `defaultCallSignature()` defaults for LangChain / Vercel AI
|
|
166
|
+
/ OpenAI Agents / Inngest — each adapter package (D04 / D06 / D08 / D29)
|
|
167
|
+
provides its own message-type-specific signature.
|
|
168
|
+
- Identity-propagation `RunContext` (parentRunId / traceparent threading) —
|
|
169
|
+
deferred per SLICE 7 R2. Use the explicit `ReserveRequest` fields for now.
|
|
170
|
+
|
|
171
|
+
### Verification
|
|
172
|
+
|
|
173
|
+
- 366 tests pass under vitest 2.x (Node 20).
|
|
174
|
+
- `npm pack` tarball ≤ 250 KB (`scripts/size-budget.sh`).
|
|
175
|
+
- Minified `dist/index.js` ≤ 120 KB (design.md §10 budget).
|
|
176
|
+
- `package.json#version` == `src/version.ts#VERSION` (`scripts/version-check.sh`).
|
|
177
|
+
- All P0 byte-equivalence fixtures match the Python SDK.
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## Future versions
|
|
182
|
+
|
|
183
|
+
The lockstep contract with `spendguard-sdk` (Python) v0.5.1 means the next
|
|
184
|
+
minor (v0.2.x) will track whatever Python's next minor mints. Any change to
|
|
185
|
+
the v0.1.0 public surface listed under "Added" requires a coordinated
|
|
186
|
+
v0.minor bump on both packages — see
|
|
187
|
+
[`docs/specs/coverage/D05_ts_sdk_substrate/design.md`](../../docs/specs/coverage/D05_ts_sdk_substrate/design.md)
|
|
188
|
+
§§4 / 9 for the contract.
|
|
189
|
+
|
|
190
|
+
[0.1.0]: https://github.com/m24927605/agentic-spendguard/releases/tag/ts-sdk-v0.1.0
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# `@spendguard/sdk` — Third-Party License Notices
|
|
2
|
+
|
|
3
|
+
This package (`@spendguard/sdk`) is itself licensed under Apache License 2.0
|
|
4
|
+
— see the repository root `LICENSE` file
|
|
5
|
+
(<https://github.com/m24927605/agentic-spendguard/blob/main/LICENSE>) for the
|
|
6
|
+
full Apache-2.0 text.
|
|
7
|
+
|
|
8
|
+
This document satisfies review-standards §11.6 by listing every direct
|
|
9
|
+
runtime dependency and its license. Transitive deps that flow in via these
|
|
10
|
+
direct deps follow their own license; this notice is required only for the
|
|
11
|
+
direct edges below.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Runtime dependencies (shipped to consumers)
|
|
16
|
+
|
|
17
|
+
These are the packages declared under `"dependencies"` in `package.json`
|
|
18
|
+
that ship inside the consumer's `node_modules/@spendguard/sdk` tree and
|
|
19
|
+
that the published `dist/*.js` imports at runtime.
|
|
20
|
+
|
|
21
|
+
### `@grpc/grpc-js`
|
|
22
|
+
|
|
23
|
+
- License: **Apache License 2.0**
|
|
24
|
+
- Project: <https://github.com/grpc/grpc-node/tree/master/packages/grpc-js>
|
|
25
|
+
- Use: Node-native gRPC client transport for the UDS sidecar connection
|
|
26
|
+
(`SpendGuardClient.handshake() / reserve() / commitEstimated() / release()`).
|
|
27
|
+
- Full Apache-2.0 text:
|
|
28
|
+
<https://github.com/grpc/grpc-node/blob/master/LICENSE>
|
|
29
|
+
|
|
30
|
+
### `@noble/hashes`
|
|
31
|
+
|
|
32
|
+
- License: **MIT**
|
|
33
|
+
- Project: <https://github.com/paulmillr/noble-hashes>
|
|
34
|
+
- Use: BLAKE2b primitive backing `deriveUuidFromSignature()` (P0
|
|
35
|
+
byte-equivalent with the Python SDK and Rust sidecar). The package is also
|
|
36
|
+
used via Node's `node:crypto` HMAC-SHA256 for `computePromptHash()` — the
|
|
37
|
+
`@noble/hashes` dependency is BLAKE2b-only.
|
|
38
|
+
- Full MIT text:
|
|
39
|
+
<https://github.com/paulmillr/noble-hashes/blob/main/LICENSE>
|
|
40
|
+
|
|
41
|
+
> Copyright (c) 2022 Paul Miller (https://paulmillr.com)
|
|
42
|
+
>
|
|
43
|
+
> Permission is hereby granted, free of charge, to any person obtaining
|
|
44
|
+
> a copy of this software and associated documentation files (the
|
|
45
|
+
> "Software"), to deal in the Software without restriction, including
|
|
46
|
+
> without limitation the rights to use, copy, modify, merge, publish,
|
|
47
|
+
> distribute, sublicense, and/or sell copies of the Software, and to
|
|
48
|
+
> permit persons to whom the Software is furnished to do so, subject to
|
|
49
|
+
> the following conditions:
|
|
50
|
+
>
|
|
51
|
+
> The above copyright notice and this permission notice shall be included
|
|
52
|
+
> in all copies or substantial portions of the Software.
|
|
53
|
+
>
|
|
54
|
+
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
55
|
+
> OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
56
|
+
> MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
57
|
+
> IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
58
|
+
> CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
59
|
+
> TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
60
|
+
> SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
61
|
+
|
|
62
|
+
### `@protobuf-ts/runtime`
|
|
63
|
+
|
|
64
|
+
- License: **Apache License 2.0**
|
|
65
|
+
- Project: <https://github.com/timostamm/protobuf-ts>
|
|
66
|
+
- Use: Generated `src/_proto/**/*.ts` modules import this runtime for
|
|
67
|
+
message encode/decode.
|
|
68
|
+
|
|
69
|
+
### `@protobuf-ts/runtime-rpc`
|
|
70
|
+
|
|
71
|
+
- License: **Apache License 2.0**
|
|
72
|
+
- Project: <https://github.com/timostamm/protobuf-ts>
|
|
73
|
+
- Use: gRPC service-client surface that `_proto/spendguard/sidecar_adapter/v1/adapter.client.ts`
|
|
74
|
+
builds against.
|
|
75
|
+
|
|
76
|
+
### `@protobuf-ts/grpc-transport`
|
|
77
|
+
|
|
78
|
+
- License: **Apache License 2.0**
|
|
79
|
+
- Project: <https://github.com/timostamm/protobuf-ts>
|
|
80
|
+
- Use: Adapter from the generated `runtime-rpc` clients to `@grpc/grpc-js`
|
|
81
|
+
channels (the UDS transport).
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Optional peer dependencies
|
|
86
|
+
|
|
87
|
+
### `@opentelemetry/api`
|
|
88
|
+
|
|
89
|
+
- License: **Apache License 2.0**
|
|
90
|
+
- Project: <https://github.com/open-telemetry/opentelemetry-js-api>
|
|
91
|
+
- Use: Optional. Consumers that supply an `otelTracer` config field get
|
|
92
|
+
per-RPC spans (design.md §6.4). Not shipped inside `@spendguard/sdk`'s
|
|
93
|
+
tree because it is declared under `"peerDependencies"`; consumers install
|
|
94
|
+
the version they want.
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Build/Dev-only dependencies
|
|
99
|
+
|
|
100
|
+
Dev-only dependencies (`tsup`, `vitest`, `typescript`, `@biomejs/biome`,
|
|
101
|
+
`@protobuf-ts/plugin`, `tsx`, `@types/node`) are NOT shipped to consumers
|
|
102
|
+
— they live under `"devDependencies"` and are stripped from the published
|
|
103
|
+
tarball. They do not require notice attribution because they do not appear
|
|
104
|
+
in the consumer's runtime tree.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Generated proto sources
|
|
109
|
+
|
|
110
|
+
`src/_proto/spendguard/**` is generated from `proto/spendguard/**` at
|
|
111
|
+
build time via the `@protobuf-ts/plugin` codegen. The `.proto` source files
|
|
112
|
+
in this repository are Apache-2.0 licensed under the repo root `LICENSE`.
|
|
113
|
+
|
|
114
|
+
The generated TypeScript files inherit the original proto's license
|
|
115
|
+
(Apache-2.0); they are not third-party code and do not need separate notice
|
|
116
|
+
beyond the repo root `LICENSE`.
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Apache-2.0 compliance summary
|
|
121
|
+
|
|
122
|
+
Per Apache-2.0 §4(c), this `LICENSE_NOTICES.md` file is the NOTICE document
|
|
123
|
+
for redistribution. Consumers that re-distribute `@spendguard/sdk` in
|
|
124
|
+
binary form (e.g. as part of a larger Node application's deploy artifact)
|
|
125
|
+
should preserve this file. Consumers that depend on `@spendguard/sdk` via
|
|
126
|
+
npm and ship it as-installed are already compliant — npm's install tree
|
|
127
|
+
preserves this file under `node_modules/@spendguard/sdk/LICENSE_NOTICES.md`.
|
package/README.md
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# `@spendguard/sdk`
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@spendguard/sdk)
|
|
4
|
+
[](https://github.com/m24927605/agentic-spendguard/blob/main/LICENSE)
|
|
5
|
+
[](https://github.com/m24927605/agentic-spendguard/actions/workflows/sdk-ts-publish.yml)
|
|
6
|
+
|
|
7
|
+
> Runtime safety-layer client for AI-agent frameworks (TypeScript).
|
|
8
|
+
> Mirror of [`spendguard-sdk`](https://pypi.org/project/spendguard-sdk/) (Python).
|
|
9
|
+
|
|
10
|
+
`@spendguard/sdk` is the shared substrate that per-framework adapters
|
|
11
|
+
(`@spendguard/langchain`, `@spendguard/vercel-ai`, `@spendguard/openai-agents`,
|
|
12
|
+
`@spendguard/inngest-agentkit`) build against. It implements the gRPC-over-UDS
|
|
13
|
+
client for the SpendGuard sidecar, plus the deterministic helpers that produce
|
|
14
|
+
**byte-identical** output to the Python SDK and the Rust sidecar — the
|
|
15
|
+
audit-chain invariants that make idempotency replay and dedup correct
|
|
16
|
+
across languages.
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pnpm add @spendguard/sdk @opentelemetry/api
|
|
22
|
+
# or
|
|
23
|
+
npm install @spendguard/sdk @opentelemetry/api
|
|
24
|
+
# or
|
|
25
|
+
bun add @spendguard/sdk @opentelemetry/api
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`@opentelemetry/api` is an **optional** peer dep — install it only if you
|
|
29
|
+
plan to pass an `otelTracer`. Adapters that never enable OTel pay zero
|
|
30
|
+
extra dependency cost.
|
|
31
|
+
|
|
32
|
+
## Quick start
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
// reserve -> commitEstimated -> release (the v0.1.0 happy path)
|
|
36
|
+
import {
|
|
37
|
+
SpendGuardClient,
|
|
38
|
+
deriveIdempotencyKey,
|
|
39
|
+
newUuid7,
|
|
40
|
+
USD_MICROS_PER_USD,
|
|
41
|
+
} from "@spendguard/sdk";
|
|
42
|
+
|
|
43
|
+
const client = new SpendGuardClient({
|
|
44
|
+
tenantId: "acme-prod",
|
|
45
|
+
socketPath: "/run/spendguard/sidecar.sock",
|
|
46
|
+
handshake: { protocolVersion: 1, capabilities: {} },
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
await client.handshake();
|
|
50
|
+
|
|
51
|
+
const decision = await client.reserve({
|
|
52
|
+
trigger: "LLM_CALL_PRE",
|
|
53
|
+
runId: newUuid7(),
|
|
54
|
+
stepId: newUuid7(),
|
|
55
|
+
llmCallId: newUuid7(),
|
|
56
|
+
decisionId: newUuid7(),
|
|
57
|
+
route: "openai:gpt-4o-mini",
|
|
58
|
+
projectedClaims: [
|
|
59
|
+
{ scopeId: "team:eng", amountAtomic: "150000", unit: { unit: "usd_micros", denomination: 1 } },
|
|
60
|
+
],
|
|
61
|
+
idempotencyKey: deriveIdempotencyKey({
|
|
62
|
+
tenantId: "acme-prod",
|
|
63
|
+
sessionId: "sess-1",
|
|
64
|
+
runId: "run-1",
|
|
65
|
+
stepId: "step-1",
|
|
66
|
+
llmCallId: "call-1",
|
|
67
|
+
trigger: "LLM_CALL_PRE",
|
|
68
|
+
}),
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
if (decision.decision === "CONTINUE") {
|
|
72
|
+
// ...call the provider (OpenAI, Anthropic, etc.) here...
|
|
73
|
+
await client.commitEstimated({
|
|
74
|
+
runId: "run-1", stepId: "step-1", llmCallId: "call-1",
|
|
75
|
+
decisionId: decision.decisionId,
|
|
76
|
+
reservationId: decision.reservationIds[0]!,
|
|
77
|
+
estimatedAmountAtomic: String(123 * USD_MICROS_PER_USD / 1_000_000),
|
|
78
|
+
unit: { unit: "usd_micros", denomination: 1 },
|
|
79
|
+
pricing: { pricingVersion: "v2026.05.09-1", pricingHash: new Uint8Array() },
|
|
80
|
+
providerEventId: "openai:chatcmpl-abc",
|
|
81
|
+
outcome: "SUCCESS",
|
|
82
|
+
});
|
|
83
|
+
await client.release({
|
|
84
|
+
runId: "run-1", stepId: "step-1", llmCallId: "call-1",
|
|
85
|
+
decisionId: decision.decisionId,
|
|
86
|
+
reservationId: decision.reservationIds[0]!,
|
|
87
|
+
releaseReason: "COMPLETED",
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
The substrate also exposes `requestDecision()` as a referential alias for
|
|
93
|
+
`reserve()` (`client.reserve === client.requestDecision`) so call-sites
|
|
94
|
+
written against the Python SDK transliterate without semantic surprise.
|
|
95
|
+
|
|
96
|
+
## Subpath exports
|
|
97
|
+
|
|
98
|
+
Tree-shake what you need:
|
|
99
|
+
|
|
100
|
+
| Subpath | Contents |
|
|
101
|
+
|---|---|
|
|
102
|
+
| `@spendguard/sdk` | Full barrel (everything below). |
|
|
103
|
+
| `@spendguard/sdk/client` | `SpendGuardClient` + request/response types. |
|
|
104
|
+
| `@spendguard/sdk/errors` | Typed error hierarchy. |
|
|
105
|
+
| `@spendguard/sdk/ids` | `newUuid7`, `deriveIdempotencyKey`, `deriveUuidFromSignature`. |
|
|
106
|
+
| `@spendguard/sdk/pricing` | `PricingLookup`, `USD_MICROS_PER_USD`. |
|
|
107
|
+
| `@spendguard/sdk/pricing/demo` | Embedded `DEMO_PRICING` snapshot. |
|
|
108
|
+
| `@spendguard/sdk/promptHash` | `computePromptHash`. |
|
|
109
|
+
| `@spendguard/sdk/runPlan` | `withRunPlan`, `currentRunPlan` (AsyncLocalStorage). |
|
|
110
|
+
| `@spendguard/sdk/otel` | Optional OTel span wrapper. |
|
|
111
|
+
| `@spendguard/sdk/retry` | Bounded retry helper. |
|
|
112
|
+
| `@spendguard/sdk/cache` | In-memory idempotency cache. |
|
|
113
|
+
| `@spendguard/sdk/proto` | Generated proto types. |
|
|
114
|
+
|
|
115
|
+
## Cross-language byte-equivalence (P0)
|
|
116
|
+
|
|
117
|
+
Three functions in this SDK MUST produce byte-identical output to the
|
|
118
|
+
Python SDK and the Rust sidecar:
|
|
119
|
+
|
|
120
|
+
- `computePromptHash(text, tenantId)` — HMAC-SHA256 lowercase hex.
|
|
121
|
+
- `deriveIdempotencyKey({ ... })` — `sg-` + 32 hex chars.
|
|
122
|
+
- `deriveUuidFromSignature(sig, { scope })` — BLAKE2b-based UUID.
|
|
123
|
+
|
|
124
|
+
Drift breaks audit-chain rule dedup and the idempotency-replay collapse
|
|
125
|
+
contract. The corpus that pins this is shipped inside the npm tarball at
|
|
126
|
+
`fixtures/cross-language/v1.json` — both this SDK and the Python SDK consume
|
|
127
|
+
the same file. See `sdk/fixtures/cross-language/README.md` in the source repo
|
|
128
|
+
for the runbook.
|
|
129
|
+
|
|
130
|
+
## Compatibility
|
|
131
|
+
|
|
132
|
+
- **Node 20.10+** is the primary target.
|
|
133
|
+
- **Bun 1.1+** and **Deno 1.46+** are tested as secondary targets.
|
|
134
|
+
- Browser is **NOT supported in v0.1.x** — UDS transport is server-only.
|
|
135
|
+
|
|
136
|
+
## Links
|
|
137
|
+
|
|
138
|
+
- [Full design spec](https://github.com/m24927605/agentic-spendguard/blob/main/docs/specs/coverage/D05_ts_sdk_substrate/design.md)
|
|
139
|
+
— public surface (§4), architecture (§6), locked decisions (§9), bundle-size budget (§10).
|
|
140
|
+
- [Implementation spec](https://github.com/m24927605/agentic-spendguard/blob/main/docs/specs/coverage/D05_ts_sdk_substrate/implementation.md)
|
|
141
|
+
— repo layout + codegen pipeline.
|
|
142
|
+
- [Review standards](https://github.com/m24927605/agentic-spendguard/blob/main/docs/specs/coverage/D05_ts_sdk_substrate/review-standards.md)
|
|
143
|
+
— P0/P1/P2 review gates applied to every D05 slice.
|
|
144
|
+
- [Python SDK on PyPI](https://pypi.org/project/spendguard-sdk/) — the
|
|
145
|
+
lockstep counterpart.
|
|
146
|
+
- [CHANGELOG.md](./CHANGELOG.md) — release history.
|
|
147
|
+
- [LICENSE_NOTICES.md](./LICENSE_NOTICES.md) — third-party license attribution.
|
|
148
|
+
|
|
149
|
+
## License
|
|
150
|
+
|
|
151
|
+
Apache-2.0. See `LICENSE` in the repository root.
|