@tensorcost/sdk 1.0.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +53 -0
- package/README.md +268 -77
- package/dist/admit-client.js +2 -2
- package/dist/admit-client.js.map +1 -1
- package/dist/circuit.d.ts +11 -3
- package/dist/circuit.d.ts.map +1 -1
- package/dist/circuit.js +28 -5
- package/dist/circuit.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +3 -1
- package/dist/config.js.map +1 -1
- package/dist/errors.d.ts +3 -3
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/layer.d.ts +6 -0
- package/dist/layer.d.ts.map +1 -1
- package/dist/layer.js +6 -0
- package/dist/layer.js.map +1 -1
- package/dist/prewarm.d.ts +14 -0
- package/dist/prewarm.d.ts.map +1 -0
- package/dist/prewarm.js +29 -0
- package/dist/prewarm.js.map +1 -0
- package/dist/providers/anthropic.d.ts +1 -0
- package/dist/providers/anthropic.d.ts.map +1 -1
- package/dist/providers/anthropic.js +98 -96
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/bedrock.d.ts.map +1 -1
- package/dist/providers/bedrock.js +12 -32
- package/dist/providers/bedrock.js.map +1 -1
- package/dist/providers/openai.d.ts +1 -0
- package/dist/providers/openai.d.ts.map +1 -1
- package/dist/providers/openai.js +104 -99
- package/dist/providers/openai.js.map +1 -1
- package/dist/providers/vertex.d.ts.map +1 -1
- package/dist/providers/vertex.js +9 -25
- package/dist/providers/vertex.js.map +1 -1
- package/dist/proxy-client.d.ts +14 -2
- package/dist/proxy-client.d.ts.map +1 -1
- package/dist/proxy-client.js +72 -17
- package/dist/proxy-client.js.map +1 -1
- package/dist/runtime.d.ts +24 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +65 -0
- package/dist/runtime.js.map +1 -0
- package/dist/sdk-layer.d.ts +7 -0
- package/dist/sdk-layer.d.ts.map +1 -1
- package/dist/sdk-layer.js +57 -23
- package/dist/sdk-layer.js.map +1 -1
- package/dist/transport.d.ts +7 -0
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +14 -0
- package/dist/transport.js.map +1 -1
- package/dist/types.d.ts +12 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/wrap-overhead.d.ts +32 -0
- package/dist/wrap-overhead.d.ts.map +1 -0
- package/dist/wrap-overhead.js +60 -0
- package/dist/wrap-overhead.js.map +1 -0
- package/dist/wrap.d.ts.map +1 -1
- package/dist/wrap.js +4 -12
- package/dist/wrap.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,59 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 1.1.1 — 2026-09-19
|
|
6
|
+
|
|
7
|
+
Enterprise architect-hour fixes: fail-open correctness, honest streaming stance,
|
|
8
|
+
observation flush for Lambda.
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
**Circuit HALF_OPEN probe** — successful probe no longer triggers a second direct
|
|
13
|
+
provider call (`proxyFailed || circuitWasOpen` → `proxyFailed` only).
|
|
14
|
+
|
|
15
|
+
**Buffered JSON headers timeout** — non-streaming steer/route waits up to
|
|
16
|
+
`timeoutMs` for proxy headers (avoids fail-open double-bill while proxy buffers
|
|
17
|
+
upstream).
|
|
18
|
+
|
|
19
|
+
**Single-flight HALF_OPEN probe** — concurrent OPEN callers bypass until the
|
|
20
|
+
probe completes.
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
**`flushObservations()`** — best-effort drain of fire-and-forget observation POSTs
|
|
25
|
+
before Lambda freeze.
|
|
26
|
+
|
|
27
|
+
**`stream: true` through steer proxy** — refused (direct provider when fail-open
|
|
28
|
+
enabled; `TensorCostConfigError` when fail-open disabled).
|
|
29
|
+
|
|
30
|
+
## 1.1.0 — 2026-09-19
|
|
31
|
+
|
|
32
|
+
Agent hot-path hardening: fail-open faster on wedged proxy connections, optional
|
|
33
|
+
cold-start warmup, and non-blocking sdk-layer refresh.
|
|
34
|
+
|
|
35
|
+
### Added
|
|
36
|
+
|
|
37
|
+
**`prewarm()`** — prefetch JWT + console published layer at Lambda/container
|
|
38
|
+
init. Shares the same process-wide runtime cache as `wrap()`.
|
|
39
|
+
|
|
40
|
+
**`headersTimeoutMs`** (default 2s) — separate deadline for proxy response
|
|
41
|
+
headers vs body read (`timeoutMs`, default 60s). Missing headers within the
|
|
42
|
+
budget fail-open to the direct provider without retry.
|
|
43
|
+
|
|
44
|
+
### Changed
|
|
45
|
+
|
|
46
|
+
**Sdk-layer fetch** — 250ms cap on cold `GET /sdk-layer`; stale-while-revalidate
|
|
47
|
+
after the 30s TTL so refresh never blocks a model call.
|
|
48
|
+
|
|
49
|
+
**Admit** — HTTP timeout tightened to 500ms (still fail-open on errors).
|
|
50
|
+
|
|
51
|
+
**`wrap()`** — background-warms the shared runtime on first use.
|
|
52
|
+
|
|
53
|
+
### Fixed
|
|
54
|
+
|
|
55
|
+
Body JSON parse no longer leaves an unhandled rejection when the body-read
|
|
56
|
+
deadline fires before `response.json()` settles.
|
|
57
|
+
|
|
5
58
|
## 1.0.0 — 2026-09-13
|
|
6
59
|
|
|
7
60
|
Major release: control-layer ceilings replace the `appliedMode` boolean.
|
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# @tensorcost/sdk
|
|
2
2
|
|
|
3
|
-
One-line wrapper
|
|
3
|
+
One-line wrapper for TensorCost — fire-and-forget LLM observability and optional inference control for OpenAI, Anthropic, AWS Bedrock, and Google Vertex AI clients in Node.js.
|
|
4
|
+
|
|
5
|
+
**Current version:** 1.1.1 — fail-open correctness (no probe double-bill), buffered-json headers contract, `stream: true` refusal through steer proxy, `flushObservations()`. See [CHANGELOG](./CHANGELOG.md), [Architect hour Q&A](https://github.com/vaadhlabs/tensorcost/blob/main/docs/demo/architect-hour.md), and [Migrating from 0.x](#migrating-from-0x).
|
|
4
6
|
|
|
5
7
|
## Install
|
|
6
8
|
|
|
@@ -10,18 +12,20 @@ npm install @tensorcost/sdk
|
|
|
10
12
|
pnpm add @tensorcost/sdk
|
|
11
13
|
```
|
|
12
14
|
|
|
13
|
-
Requires Node.js 18+ (
|
|
15
|
+
Requires Node.js 18+ (native `fetch`).
|
|
16
|
+
|
|
17
|
+
## Quick start (observe)
|
|
14
18
|
|
|
15
|
-
|
|
19
|
+
The default layer is **`observe`**: telemetry only, provider call unchanged.
|
|
16
20
|
|
|
17
21
|
```ts
|
|
18
22
|
import OpenAI from "openai";
|
|
19
23
|
import { wrap } from "@tensorcost/sdk";
|
|
20
24
|
|
|
21
|
-
const client = wrap(new OpenAI({ apiKey: process.env.OPENAI_API_KEY })
|
|
25
|
+
const client = wrap(new OpenAI({ apiKey: process.env.OPENAI_API_KEY }), {
|
|
26
|
+
apiKey: process.env.TENSORCOST_API_KEY,
|
|
27
|
+
});
|
|
22
28
|
|
|
23
|
-
// Use the client exactly as before — TensorCost observes every call
|
|
24
|
-
// and ships fire-and-forget observations to the platform.
|
|
25
29
|
const resp = await client.chat.completions.create({
|
|
26
30
|
model: "gpt-4o-mini",
|
|
27
31
|
messages: [{ role: "user", content: "hi" }],
|
|
@@ -34,7 +38,9 @@ Anthropic works the same way:
|
|
|
34
38
|
import Anthropic from "@anthropic-ai/sdk";
|
|
35
39
|
import { wrap } from "@tensorcost/sdk";
|
|
36
40
|
|
|
37
|
-
const client = wrap(new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY })
|
|
41
|
+
const client = wrap(new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY }), {
|
|
42
|
+
apiKey: process.env.TENSORCOST_API_KEY,
|
|
43
|
+
});
|
|
38
44
|
|
|
39
45
|
await client.messages.create({
|
|
40
46
|
model: "claude-3-5-sonnet-20241022",
|
|
@@ -43,61 +49,142 @@ await client.messages.create({
|
|
|
43
49
|
});
|
|
44
50
|
```
|
|
45
51
|
|
|
46
|
-
|
|
52
|
+
Create a long-lived SDK key in the TensorCost console (**Setup → Keys**). The SDK exchanges it for a short-lived JWT via `POST /api/inference-proxy/sdk-token/exchange` and refreshes ~60s before expiry.
|
|
47
53
|
|
|
48
|
-
|
|
54
|
+
## Control layers (`maxLayer`)
|
|
49
55
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
56
|
+
v1.0 uses a single ladder for how much TensorCost may intervene on a call:
|
|
57
|
+
|
|
58
|
+
`off → observe → govern → steer → route`
|
|
59
|
+
|
|
60
|
+
A layer is a **ceiling**, not a mode switch. The effective layer is always:
|
|
61
|
+
|
|
62
|
+
```text
|
|
63
|
+
min(your maxLayer, console published layer)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
When routing is paused in the console, the published ceiling is capped at `govern`.
|
|
60
67
|
|
|
61
|
-
|
|
68
|
+
| Layer | Provider path | Behavior |
|
|
69
|
+
|---|---|---|
|
|
70
|
+
| `off` | Direct | No SDK wiring; client returned unchanged |
|
|
71
|
+
| `observe` | Direct | Telemetry only (**default** when `maxLayer` is omitted) |
|
|
72
|
+
| `govern` | Direct | Metadata-only `POST /v1/admit` before the provider call; may refuse unsafe calls |
|
|
73
|
+
| `steer` | Via proxy | Proxy on the path; may steer without full live routing |
|
|
74
|
+
| `route` | Via proxy | Full applied-mode routing through inference-proxy |
|
|
62
75
|
|
|
63
76
|
```ts
|
|
64
|
-
const client = wrap(new OpenAI(
|
|
65
|
-
apiKey:
|
|
66
|
-
|
|
77
|
+
const client = wrap(new OpenAI({ apiKey: process.env.OPENAI_API_KEY }), {
|
|
78
|
+
apiKey: process.env.TENSORCOST_API_KEY,
|
|
79
|
+
maxLayer: "route",
|
|
80
|
+
proxyUrl: process.env.TENSORCOST_PROXY_URL, // required for steer | route
|
|
67
81
|
});
|
|
68
82
|
```
|
|
69
83
|
|
|
70
|
-
|
|
84
|
+
`wrap()` throws `TensorCostConfigError` when `maxLayer` is `steer` or `route` and no `proxyUrl` / `TENSORCOST_PROXY_URL` is set. **`govern` does not require `proxyUrl`** — admit uses `baseUrl`.
|
|
71
85
|
|
|
72
|
-
|
|
86
|
+
### Console-published ceiling
|
|
73
87
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
88
|
+
Operators set the tenant ceiling in the console (**Policy → Routing**) or via `PATCH /api/ai/router/layer` (`published_layer`, default `observe`). The SDK fetches the effective value from `GET /api/inference-proxy/v1/sdk-layer`:
|
|
89
|
+
|
|
90
|
+
- **30s cache** while the endpoint is reachable
|
|
91
|
+
- **250ms cap** on a cold fetch — a hung TensorCost cannot block the model call
|
|
92
|
+
- **Stale-while-revalidate** after TTL: serve the last snapshot immediately and refresh in the background
|
|
93
|
+
- **Decays to `govern`** after 5 minutes unreachable (fail-safe: still run admit, never silently upgrade to route)
|
|
94
|
+
|
|
95
|
+
Publishing `steer` or `route` in the console requires a **content-retention data grant** (`retain_completions`, etc.). Steer/route without a grant returns **409**.
|
|
96
|
+
|
|
97
|
+
### Govern admission
|
|
98
|
+
|
|
99
|
+
At `govern` and above, the SDK calls `POST /api/inference-proxy/v1/admit` with metadata only — **no prompt or completion bodies**:
|
|
100
|
+
|
|
101
|
+
- **204** + `x-tc-decision: layer=govern; action=allow` → proceed to the provider
|
|
102
|
+
- **403** + `x-tc-decision: layer=govern; action=refuse; reason=…` → call blocked (budget, model governance, etc.)
|
|
103
|
+
|
|
104
|
+
Admit is **fail-open** on transport errors: the provider call proceeds and an `admit_unavailable` observation is emitted when transport recovers. The admit HTTP call is capped at **500ms**.
|
|
105
|
+
|
|
106
|
+
### Route layer prerequisites
|
|
107
|
+
|
|
108
|
+
`maxLayer: "route"` is necessary but not sufficient for live provider swaps. ADR-0016 gates still apply on the proxy side: LaunchDarkly flag, `AI_ROUTING_LIVE_EXECUTION_PERMITTED`, non-`dry_run` policy with rollout, and routing not paused. When gated off, traffic pass-throughs to the original provider and the decision is recorded.
|
|
109
|
+
|
|
110
|
+
See [ADR-0026](https://github.com/vaadhlabs/tensorcost/blob/main/docs/adr/0026-sdk-control-layer-ceilings.md) and [inference-proxy applied mode](https://github.com/vaadhlabs/tensorcost/blob/main/docs/features/inference-proxy-applied-mode.md).
|
|
111
|
+
|
|
112
|
+
## Cold start (`prewarm`)
|
|
113
|
+
|
|
114
|
+
At Lambda/container init, the first steer/route call can otherwise pay for JWT exchange and sdk-layer fetch on the hot path. Call `prewarm()` once at module scope — it shares the same process-wide runtime that `wrap()` uses, so a later `wrap()` hits a warm cache.
|
|
115
|
+
|
|
116
|
+
`wrap()` also background-warms that shared runtime on first use; explicit `prewarm()` is recommended when you know traffic is about to arrive (e.g. before the handler is invoked).
|
|
81
117
|
|
|
82
118
|
```ts
|
|
83
|
-
|
|
84
|
-
|
|
119
|
+
import { prewarm, wrap } from "@tensorcost/sdk";
|
|
120
|
+
|
|
121
|
+
await prewarm({
|
|
122
|
+
apiKey: process.env.TENSORCOST_API_KEY,
|
|
123
|
+
baseUrl: process.env.TENSORCOST_BASE_URL,
|
|
85
124
|
maxLayer: "route",
|
|
86
|
-
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
export const client = wrap(new OpenAI({ apiKey: process.env.OPENAI_API_KEY }), {
|
|
128
|
+
apiKey: process.env.TENSORCOST_API_KEY,
|
|
129
|
+
maxLayer: "route",
|
|
130
|
+
proxyUrl: process.env.TENSORCOST_PROXY_URL,
|
|
87
131
|
});
|
|
88
132
|
```
|
|
89
133
|
|
|
90
|
-
`
|
|
134
|
+
`prewarm()` is best-effort: failures are swallowed and the first real call retries the fetches.
|
|
91
135
|
|
|
92
|
-
|
|
136
|
+
## Migrating from 0.x
|
|
93
137
|
|
|
94
|
-
|
|
138
|
+
| 0.x | 1.0 |
|
|
139
|
+
|---|---|
|
|
140
|
+
| `appliedMode: true` | `maxLayer: "route"` (deprecated alias still works with a console warning) |
|
|
141
|
+
| Default `x-tc-max-layer: route` on proxy calls | Default is now `observe` |
|
|
142
|
+
| Boolean on/off | Full layer ladder + console ceiling |
|
|
95
143
|
|
|
96
|
-
|
|
144
|
+
```ts
|
|
145
|
+
// Before (0.6.x)
|
|
146
|
+
wrap(client, { appliedMode: true, proxyUrl: "..." });
|
|
147
|
+
|
|
148
|
+
// After (1.0)
|
|
149
|
+
wrap(client, { maxLayer: "route", proxyUrl: "..." });
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Recommended rollout: **`observe` → `govern` → `route`**, raising the console `published_layer` only after each stage is validated.
|
|
153
|
+
|
|
154
|
+
## Configuration
|
|
155
|
+
|
|
156
|
+
`wrap()` resolves options in order: explicit kwargs → environment variables → defaults.
|
|
157
|
+
|
|
158
|
+
| Variable | Purpose | Required |
|
|
159
|
+
|---|---|---|
|
|
160
|
+
| `TENSORCOST_API_KEY` | Long-lived SDK key from **Setup → Keys** | Yes |
|
|
161
|
+
| `TENSORCOST_BASE_URL` | Backend base URL (admit, sdk-layer, token exchange) | No (default `https://api.tensorcost.com`) |
|
|
162
|
+
| `TENSORCOST_PROXY_URL` | Inference-proxy base URL | Required when `maxLayer` is `steer` or `route` |
|
|
163
|
+
| `TENSORCOST_TENANT_ID` | Explicit tenant id (multi-tenant tests) | No |
|
|
164
|
+
| `TENSORCOST_ENVIRONMENT` | Environment tag on every observation | No |
|
|
165
|
+
| `TENSORCOST_CONNECTION_ID` | Provider-connection id for environment resolution | No |
|
|
166
|
+
| `TENSORCOST_AGENT_ID` / `TENSORCOST_WORKFLOW_ID` | Agent-run attribution defaults | No |
|
|
167
|
+
| `TENSORCOST_CUSTOMER` / `TENSORCOST_FEATURE` | Chargeback tag defaults | No |
|
|
168
|
+
| `TENSORCOST_DEPLOYMENT` | Git SHA / release label (`metadata.deployment`) | No |
|
|
169
|
+
| `TENSORCOST_PROMPT_TEMPLATE_ID` | Default prompt-template id | No |
|
|
170
|
+
| `TC_APPLICATION` / `TENSORCOST_APPLICATION` | Routing-policy application scope (proxy) | No |
|
|
171
|
+
| `TC_TAGS` / `TENSORCOST_TAGS` | Comma-separated routing tags (proxy) | No |
|
|
172
|
+
|
|
173
|
+
```ts
|
|
174
|
+
const client = wrap(new OpenAI(...), {
|
|
175
|
+
apiKey: "tc_live_xxx",
|
|
176
|
+
baseUrl: "https://api.tensorcost.com",
|
|
177
|
+
maxLayer: "govern",
|
|
178
|
+
deployment: process.env.GIT_SHA,
|
|
179
|
+
promptTemplateId: "support-chat-v2",
|
|
180
|
+
application: "customer-support",
|
|
181
|
+
tags: ["tier:enterprise"],
|
|
182
|
+
});
|
|
183
|
+
```
|
|
97
184
|
|
|
98
185
|
## Proxy hardening
|
|
99
186
|
|
|
100
|
-
|
|
187
|
+
Applies when `maxLayer` is `govern`, `steer`, or `route` (admit + proxy paths).
|
|
101
188
|
|
|
102
189
|
### Retries
|
|
103
190
|
|
|
@@ -111,21 +198,33 @@ const client = wrap(new OpenAI(...), {
|
|
|
111
198
|
});
|
|
112
199
|
```
|
|
113
200
|
|
|
114
|
-
5xx
|
|
201
|
+
5xx, network errors, and 429 are retried with jittered exponential backoff. `Retry-After` on 429 is honoured. 4xx (except 429) are never retried.
|
|
115
202
|
|
|
116
203
|
### Timeouts
|
|
117
204
|
|
|
205
|
+
Proxy timeouts are split so a wedged TCP connection fails open quickly while slow model bodies still get a full budget:
|
|
206
|
+
|
|
118
207
|
```ts
|
|
119
208
|
const client = wrap(new OpenAI(...), {
|
|
120
209
|
maxLayer: "route",
|
|
121
210
|
proxyUrl: "...",
|
|
122
|
-
|
|
211
|
+
headersTimeoutMs: 2_000, // wait for response headers (default 2_000)
|
|
212
|
+
timeoutMs: 60_000, // non-streaming body read (default 60_000)
|
|
213
|
+
idleTimeoutMs: 30_000, // streaming idle (default 30_000)
|
|
123
214
|
});
|
|
124
215
|
```
|
|
125
216
|
|
|
126
|
-
|
|
217
|
+
| Option | Default | `TensorCostTimeoutError.kind` | On timeout (steer/route) |
|
|
218
|
+
|---|---|---|---|
|
|
219
|
+
| `headersTimeoutMs` | 2s | `"headers"` | Streaming only (when SSE client ships); **not used for buffered JSON** |
|
|
220
|
+
| `timeoutMs` | 60s | `"total"` | TTFB + body for non-streaming proxy calls; fail-open when `failOpenEnabled: true` |
|
|
221
|
+
| `idleTimeoutMs` | 30s | `"idle"` | Reserved for future steer SSE client |
|
|
222
|
+
|
|
223
|
+
For **non-streaming** steer/route, the SDK waits up to **`timeoutMs` for response headers** because the proxy may buffer the full upstream response before sending headers — using a 2s headers cap would fail-open and **double-bill** while the proxy still completes.
|
|
224
|
+
|
|
225
|
+
**Streaming (`stream: true`):** not implemented through the steer proxy client (1.1.1). With `failOpenEnabled: true` (default), calls go **direct** to the provider. With `failOpenEnabled: false`, `TensorCostConfigError` is thrown.
|
|
127
226
|
|
|
128
|
-
### Lifecycle
|
|
227
|
+
### Lifecycle hooks
|
|
129
228
|
|
|
130
229
|
```ts
|
|
131
230
|
import { wrap, LifecycleEvent } from "@tensorcost/sdk";
|
|
@@ -140,11 +239,17 @@ const client = wrap(new OpenAI(...), {
|
|
|
140
239
|
});
|
|
141
240
|
```
|
|
142
241
|
|
|
143
|
-
|
|
242
|
+
Payloads contain request metadata only — never prompt content, response bodies, or credentials.
|
|
144
243
|
|
|
145
|
-
###
|
|
244
|
+
### Circuit breaker
|
|
245
|
+
|
|
246
|
+
After consecutive proxy 5xx failures (default 3), the circuit opens and requests route directly to the provider (fail-open). After consecutive successful probe calls (default 5), the circuit closes.
|
|
247
|
+
|
|
248
|
+
Set `failOpenEnabled: false` when direct provider access is unavailable (e.g. in-VPC sidecar-only). Proxy failures then surface as `TensorCostProxyError` instead of falling back.
|
|
146
249
|
|
|
147
|
-
|
|
250
|
+
HALF_OPEN recovery uses a **single-flight probe** (1.1.1) — a successful probe does **not** invoke the provider twice.
|
|
251
|
+
|
|
252
|
+
### Typed errors
|
|
148
253
|
|
|
149
254
|
```ts
|
|
150
255
|
import {
|
|
@@ -154,20 +259,53 @@ import {
|
|
|
154
259
|
TensorCostProxyError,
|
|
155
260
|
TensorCostQuotaError,
|
|
156
261
|
TensorCostProviderError,
|
|
262
|
+
TensorCostRunBudgetExceededError,
|
|
263
|
+
TensorCostModelGovernanceDeniedError,
|
|
264
|
+
TensorCostGuardrailHardStopError,
|
|
157
265
|
} from "@tensorcost/sdk";
|
|
158
266
|
```
|
|
159
267
|
|
|
160
|
-
|
|
268
|
+
Govern refusals and proxy decisions are also reflected in the `x-tc-decision` header and attached to observations as `decision_action` / `decision_reason` metadata when present.
|
|
161
269
|
|
|
162
|
-
|
|
270
|
+
## Data grants (`maxGrants`)
|
|
163
271
|
|
|
164
|
-
|
|
272
|
+
Code can cap which console data grants apply:
|
|
165
273
|
|
|
166
|
-
|
|
274
|
+
```ts
|
|
275
|
+
import { wrap, intersectGrants } from "@tensorcost/sdk";
|
|
276
|
+
|
|
277
|
+
const client = wrap(new OpenAI(...), {
|
|
278
|
+
apiKey: "...",
|
|
279
|
+
maxGrants: ["telemetry", "retain_completions"],
|
|
280
|
+
});
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
Effective grants = `intersect(maxGrants, console.data_grants)`. Omit `maxGrants` to allow all console grants. An empty array allows none. Grant vocabulary: `telemetry`, `retain_completions`, `retain_prompts`, `retain_tools`.
|
|
284
|
+
|
|
285
|
+
## Trace metadata & attribution
|
|
286
|
+
|
|
287
|
+
### Deployment & prompt templates
|
|
288
|
+
|
|
289
|
+
Group traces in the dashboard by deploy or template:
|
|
167
290
|
|
|
168
291
|
```ts
|
|
169
292
|
const client = wrap(new OpenAI(...), {
|
|
170
|
-
apiKey: "
|
|
293
|
+
apiKey: "...",
|
|
294
|
+
deployment: process.env.GIT_SHA ?? "local",
|
|
295
|
+
promptTemplateId: "onboarding-email-v2",
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// Per-call override (OpenAI + Anthropic)
|
|
299
|
+
await client.withMeta({ promptTemplateId: "research-agent-v5" }).chat.completions.create({ ... });
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### Agent runs
|
|
303
|
+
|
|
304
|
+
Tag spend for **Router · Agent runs**:
|
|
305
|
+
|
|
306
|
+
```ts
|
|
307
|
+
const client = wrap(new OpenAI(...), {
|
|
308
|
+
apiKey: "...",
|
|
171
309
|
agentId: "support-bot",
|
|
172
310
|
});
|
|
173
311
|
|
|
@@ -175,57 +313,110 @@ const scoped = client.withMeta({ workflowId: crypto.randomUUID() });
|
|
|
175
313
|
await scoped.chat.completions.create({ model: "gpt-4o-mini", messages: [...] });
|
|
176
314
|
```
|
|
177
315
|
|
|
178
|
-
`customer` / `feature` are separate chargeback
|
|
316
|
+
`customer` / `feature` are separate chargeback dimensions — not substitutes for `agentId` / `workflowId`. Bedrock and Vertex accept wrap-time defaults only (no `withMeta()`).
|
|
179
317
|
|
|
180
318
|
## Fail-open guarantee
|
|
181
319
|
|
|
182
|
-
If TensorCost is slow, down, or misconfigured, your customer call still succeeds.
|
|
320
|
+
If TensorCost is slow, down, or misconfigured, your customer call still succeeds. Internal errors are swallowed and logged via `console.warn` (or your lifecycle hook). Exceptions:
|
|
321
|
+
|
|
322
|
+
- Missing `TENSORCOST_API_KEY` → `MissingConfigError` at `wrap()` time
|
|
323
|
+
- Missing `proxyUrl` when `maxLayer` is `steer` or `route` → `TensorCostConfigError` at `wrap()` time
|
|
324
|
+
- Govern **refusals** (403 from `/admit`) → call blocked by design
|
|
325
|
+
- Steer **run-budget** refusals → not fail-opened
|
|
326
|
+
|
|
327
|
+
### Duplicate spend (honest)
|
|
328
|
+
|
|
329
|
+
Fail-open may call the provider **after** a failed proxy attempt. If the proxy already forwarded to the provider before failing, you can be billed twice. Mitigations:
|
|
330
|
+
|
|
331
|
+
- **Sidecar / in-VPC steer:** set `failOpenEnabled: false` — no silent direct fallback
|
|
332
|
+
- **Buffered JSON:** SDK 1.1.1+ waits `timeoutMs` for headers (not 2s) to avoid aborting a healthy slow proxy call
|
|
333
|
+
|
|
334
|
+
Set `failOpen: false` only in tests to surface observation-pipeline errors.
|
|
335
|
+
|
|
336
|
+
### Lambda: flush observations
|
|
337
|
+
|
|
338
|
+
```ts
|
|
339
|
+
import { flushObservations } from "@tensorcost/sdk";
|
|
340
|
+
|
|
341
|
+
export const handler = async (event) => {
|
|
342
|
+
// ... model call via wrapped client ...
|
|
343
|
+
await flushObservations({ apiKey: process.env.TENSORCOST_API_KEY }, 3_000);
|
|
344
|
+
};
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
## SDK version matrix (1.0.0 vs 1.1.x)
|
|
348
|
+
|
|
349
|
+
| Feature | 1.0.0 | 1.1.0+ | 1.1.1+ |
|
|
350
|
+
|---------|-------|--------|--------|
|
|
351
|
+
| `prewarm()` | No | Yes | Yes |
|
|
352
|
+
| `headersTimeoutMs` / split timeouts | No | Yes | Yes |
|
|
353
|
+
| Admit 500ms cap | Partial | Yes | Yes |
|
|
354
|
+
| Buffered JSON headers = `timeoutMs` | No | No | **Yes** |
|
|
355
|
+
| Probe success no double-call | No | No | **Yes** |
|
|
356
|
+
| `stream: true` steer refusal | No | No | **Yes** |
|
|
357
|
+
| `flushObservations()` | No | No | **Yes** |
|
|
183
358
|
|
|
184
359
|
## What is captured
|
|
185
360
|
|
|
186
|
-
|
|
361
|
+
Metadata only — never prompt or completion content:
|
|
187
362
|
|
|
188
|
-
-
|
|
363
|
+
- SDK version, provider, model, operation, modality
|
|
189
364
|
- Request / response timestamps and token counts
|
|
190
365
|
- Status (`success` / `error`) and error class/message on failure
|
|
191
366
|
- Correlation UUID, environment, agent/workflow ids, chargeback tags
|
|
367
|
+
- `decision_action` / `decision_reason` when `x-tc-decision` is present
|
|
192
368
|
|
|
193
|
-
|
|
369
|
+
Proxied requests include `x-tc-sdk-version`, `x-tc-sdk-capabilities`, and `x-tc-max-layer`.
|
|
194
370
|
|
|
195
|
-
|
|
196
|
-
import { BedrockRuntimeClient, InvokeModelCommand } from "@aws-sdk/client-bedrock-runtime";
|
|
197
|
-
import { wrap } from "@tensorcost/sdk";
|
|
371
|
+
## Supported providers & operations
|
|
198
372
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
373
|
+
| Provider | Package | Layers | Operations |
|
|
374
|
+
|---|---|---|---|
|
|
375
|
+
| OpenAI | `openai` ≥ 4.x | observe → route | chat, completions, embeddings, responses |
|
|
376
|
+
| Anthropic | `@anthropic-ai/sdk` | observe → route | messages |
|
|
377
|
+
| Azure OpenAI | `openai` (`AzureOpenAI`) | observe → route | same as OpenAI (`provider: "openai"`) |
|
|
378
|
+
| AWS Bedrock | `@aws-sdk/client-bedrock-runtime` | observe, govern only | InvokeModel, Converse (token placeholders) |
|
|
379
|
+
| Google Vertex | `@google/genai` (`vertexai: true`) | observe, govern only | generateContent (token placeholders) |
|
|
380
|
+
|
|
381
|
+
**Steer/route streaming:** SDK routes `stream: true` **direct** to the provider (or errors if fail-open off). Proxy server supports SSE; SDK SSE client not shipped yet.
|
|
203
382
|
|
|
204
|
-
|
|
383
|
+
Full matrix: [`docs/demo/data-path-one-pager.md`](https://github.com/vaadhlabs/tensorcost/blob/main/docs/demo/data-path-one-pager.md).
|
|
205
384
|
|
|
206
|
-
**
|
|
385
|
+
**Bedrock and Vertex:** SigV4 / Vertex signing happens inside the AWS / Google SDK before the proxy can intercept — **`steer` and `route` are not supported**. Use `maxLayer: "govern"` or lower.
|
|
207
386
|
|
|
208
|
-
|
|
387
|
+
Bedrock peer dependency is optional:
|
|
209
388
|
|
|
210
|
-
|
|
389
|
+
```ts
|
|
390
|
+
import { BedrockRuntimeClient, InvokeModelCommand } from "@aws-sdk/client-bedrock-runtime";
|
|
391
|
+
import { wrap } from "@tensorcost/sdk";
|
|
211
392
|
|
|
212
|
-
|
|
393
|
+
const client = wrap(new BedrockRuntimeClient({ region: "us-east-1" }), {
|
|
394
|
+
apiKey: process.env.TENSORCOST_API_KEY,
|
|
395
|
+
});
|
|
396
|
+
```
|
|
213
397
|
|
|
214
398
|
## Framework adapters
|
|
215
399
|
|
|
216
|
-
|
|
400
|
+
Thin passthroughs that call `wrap()` on the underlying LLM client:
|
|
217
401
|
|
|
218
402
|
```ts
|
|
219
|
-
import {
|
|
403
|
+
import { ChatOpenAI } from "@langchain/openai";
|
|
404
|
+
import { wrapLangChain } from "@tensorcost/sdk";
|
|
405
|
+
|
|
406
|
+
const llm = wrapLangChain(new ChatOpenAI({ model: "gpt-4o-mini" }), {
|
|
407
|
+
apiKey: process.env.TENSORCOST_API_KEY,
|
|
408
|
+
maxLayer: "observe",
|
|
409
|
+
});
|
|
220
410
|
```
|
|
221
411
|
|
|
222
|
-
|
|
412
|
+
Also exported: `wrapLlamaIndex`, `wrapCrewAI`, `wrapVercelAi`.
|
|
413
|
+
|
|
414
|
+
## Further reading
|
|
223
415
|
|
|
224
|
-
-
|
|
225
|
-
-
|
|
226
|
-
-
|
|
227
|
-
-
|
|
228
|
-
- Azure OpenAI — wrap the standard `AzureOpenAI` client (observations report `provider: "openai"`)
|
|
416
|
+
- [ADR-0026 — SDK control-layer ceilings](https://github.com/vaadhlabs/tensorcost/blob/main/docs/adr/0026-sdk-control-layer-ceilings.md)
|
|
417
|
+
- [Inference proxy SDK feature doc](https://github.com/vaadhlabs/tensorcost/blob/main/docs/features/inference-proxy-sdk.md)
|
|
418
|
+
- [Applied mode / live routing](https://github.com/vaadhlabs/tensorcost/blob/main/docs/features/inference-proxy-applied-mode.md)
|
|
419
|
+
- [CHANGELOG](./CHANGELOG.md)
|
|
229
420
|
|
|
230
421
|
## License
|
|
231
422
|
|
package/dist/admit-client.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Fail-open on transport failure: the customer's call proceeds to the
|
|
5
5
|
* provider and an error observation is emitted when transport is available.
|
|
6
6
|
*/
|
|
7
|
-
import { ADMIT_PATH } from "./layer.js";
|
|
7
|
+
import { ADMIT_PATH, ADMIT_TIMEOUT_MS } from "./layer.js";
|
|
8
8
|
import { SDK_VERSION, sdkCapabilityHeader } from "./version.js";
|
|
9
9
|
export async function admitRequest(opts) {
|
|
10
10
|
const fetchImpl = opts.fetchImpl ?? globalThis.fetch.bind(globalThis);
|
|
@@ -33,7 +33,7 @@ export async function admitRequest(opts) {
|
|
|
33
33
|
: {}),
|
|
34
34
|
};
|
|
35
35
|
const controller = new AbortController();
|
|
36
|
-
const timer = setTimeout(() => controller.abort(), opts.timeoutMs ??
|
|
36
|
+
const timer = setTimeout(() => controller.abort(), opts.timeoutMs ?? ADMIT_TIMEOUT_MS);
|
|
37
37
|
try {
|
|
38
38
|
const resp = await fetchImpl(url, {
|
|
39
39
|
method: "POST",
|
package/dist/admit-client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admit-client.js","sourceRoot":"","sources":["../src/admit-client.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"admit-client.js","sourceRoot":"","sources":["../src/admit-client.ts"],"names":[],"mappings":"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"}
|
package/dist/circuit.d.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* State machine:
|
|
11
11
|
*
|
|
12
12
|
* CLOSED ──(failures >= openThreshold)──> OPEN
|
|
13
|
-
* OPEN ──(
|
|
13
|
+
* OPEN ──(single-flight probe)───────> HALF_OPEN
|
|
14
14
|
* HALF_OPEN ──(proxy 2xx)──────────────> probeSuccesses++
|
|
15
15
|
* ──(proxy 5xx/err)──────────> back to OPEN (resets probes)
|
|
16
16
|
* HALF_OPEN ──(probeSuccesses >= closeThreshold)──> CLOSED
|
|
@@ -31,6 +31,8 @@ export declare class CircuitBreaker {
|
|
|
31
31
|
private state;
|
|
32
32
|
private consecutiveFailures;
|
|
33
33
|
private probeSuccesses;
|
|
34
|
+
/** True while a HALF_OPEN probe is in flight (single-flight). */
|
|
35
|
+
private probeInFlight;
|
|
34
36
|
readonly config: CircuitBreakerConfig;
|
|
35
37
|
private readonly onTransition?;
|
|
36
38
|
constructor(config?: CircuitBreakerConfig, onTransition?: CircuitTransitionListener);
|
|
@@ -46,12 +48,18 @@ export declare class CircuitBreaker {
|
|
|
46
48
|
*/
|
|
47
49
|
shouldBypass(): boolean;
|
|
48
50
|
/**
|
|
49
|
-
* Returns true when
|
|
50
|
-
* OPEN
|
|
51
|
+
* Returns true when this caller should run the single-flight HALF_OPEN
|
|
52
|
+
* probe (OPEN → HALF_OPEN). Concurrent OPEN callers get false and bypass.
|
|
51
53
|
*/
|
|
52
54
|
shouldProbe(): boolean;
|
|
53
55
|
/** Call after a proxy request succeeds. */
|
|
54
56
|
recordSuccess(): void;
|
|
57
|
+
/**
|
|
58
|
+
* Clear a HALF_OPEN probe that never reached recordSuccess/recordFailure
|
|
59
|
+
* (e.g. token exchange threw before the HTTP call). Idempotent once the
|
|
60
|
+
* probe has settled.
|
|
61
|
+
*/
|
|
62
|
+
releaseStuckProbe(): void;
|
|
55
63
|
/** Call after a proxy request fails (5xx or network error). */
|
|
56
64
|
recordFailure(): void;
|
|
57
65
|
private reset;
|
package/dist/circuit.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"circuit.d.ts","sourceRoot":"","sources":["../src/circuit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,MAAM,GAAG,WAAW,CAAC;AAE3D,MAAM,WAAW,oBAAoB;IACnC,iFAAiF;IACjF,aAAa,EAAE,MAAM,CAAC;IACtB,qFAAqF;IACrF,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,eAAO,MAAM,sBAAsB,EAAE,oBAGpC,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG,CACtC,IAAI,EAAE,YAAY,EAClB,EAAE,EAAE,YAAY,KACb,IAAI,CAAC;AAEV,qBAAa,cAAc;IACzB,OAAO,CAAC,KAAK,CAA0B;IACvC,OAAO,CAAC,mBAAmB,CAAK;IAChC,OAAO,CAAC,cAAc,CAAK;IAC3B,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAC;IACtC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAA4B;gBAGxD,MAAM,GAAE,oBAA6C,EACrD,YAAY,CAAC,EAAE,yBAAyB;IAM1C,OAAO,CAAC,UAAU;IAOlB,6BAA6B;IAC7B,IAAI,YAAY,IAAI,YAAY,CAE/B;IAED,wDAAwD;IACxD,IAAI,QAAQ,IAAI,MAAM,CAErB;IAED;;;;OAIG;IACH,YAAY,IAAI,OAAO;
|
|
1
|
+
{"version":3,"file":"circuit.d.ts","sourceRoot":"","sources":["../src/circuit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,MAAM,GAAG,WAAW,CAAC;AAE3D,MAAM,WAAW,oBAAoB;IACnC,iFAAiF;IACjF,aAAa,EAAE,MAAM,CAAC;IACtB,qFAAqF;IACrF,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,eAAO,MAAM,sBAAsB,EAAE,oBAGpC,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG,CACtC,IAAI,EAAE,YAAY,EAClB,EAAE,EAAE,YAAY,KACb,IAAI,CAAC;AAEV,qBAAa,cAAc;IACzB,OAAO,CAAC,KAAK,CAA0B;IACvC,OAAO,CAAC,mBAAmB,CAAK;IAChC,OAAO,CAAC,cAAc,CAAK;IAC3B,iEAAiE;IACjE,OAAO,CAAC,aAAa,CAAS;IAC9B,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAC;IACtC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAA4B;gBAGxD,MAAM,GAAE,oBAA6C,EACrD,YAAY,CAAC,EAAE,yBAAyB;IAM1C,OAAO,CAAC,UAAU;IAOlB,6BAA6B;IAC7B,IAAI,YAAY,IAAI,YAAY,CAE/B;IAED,wDAAwD;IACxD,IAAI,QAAQ,IAAI,MAAM,CAErB;IAED;;;;OAIG;IACH,YAAY,IAAI,OAAO;IAOvB;;;OAGG;IACH,WAAW,IAAI,OAAO;IAStB,2CAA2C;IAC3C,aAAa,IAAI,IAAI;IAYrB;;;;OAIG;IACH,iBAAiB,IAAI,IAAI;IAQzB,+DAA+D;IAC/D,aAAa,IAAI,IAAI;IAcrB,OAAO,CAAC,KAAK;CAMd"}
|