@tensorcost/sdk 0.5.0 → 1.0.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.
Files changed (100) hide show
  1. package/CHANGELOG.md +117 -0
  2. package/README.md +63 -95
  3. package/dist/adapters/crewai.d.ts +4 -0
  4. package/dist/adapters/crewai.d.ts.map +1 -0
  5. package/dist/adapters/crewai.js +6 -0
  6. package/dist/adapters/crewai.js.map +1 -0
  7. package/dist/adapters/langchain.d.ts +4 -0
  8. package/dist/adapters/langchain.d.ts.map +1 -0
  9. package/dist/adapters/langchain.js +6 -0
  10. package/dist/adapters/langchain.js.map +1 -0
  11. package/dist/adapters/llamaindex.d.ts +4 -0
  12. package/dist/adapters/llamaindex.d.ts.map +1 -0
  13. package/dist/adapters/llamaindex.js +6 -0
  14. package/dist/adapters/llamaindex.js.map +1 -0
  15. package/dist/adapters/vercel-ai.d.ts +4 -0
  16. package/dist/adapters/vercel-ai.d.ts.map +1 -0
  17. package/dist/adapters/vercel-ai.js +6 -0
  18. package/dist/adapters/vercel-ai.js.map +1 -0
  19. package/dist/admit-client.d.ts +39 -0
  20. package/dist/admit-client.d.ts.map +1 -0
  21. package/dist/admit-client.js +93 -0
  22. package/dist/admit-client.js.map +1 -0
  23. package/dist/applied.d.ts +6 -1
  24. package/dist/applied.d.ts.map +1 -1
  25. package/dist/applied.js +13 -1
  26. package/dist/applied.js.map +1 -1
  27. package/dist/circuit-observations.d.ts +12 -0
  28. package/dist/circuit-observations.d.ts.map +1 -0
  29. package/dist/circuit-observations.js +37 -0
  30. package/dist/circuit-observations.js.map +1 -0
  31. package/dist/circuit.d.ts +4 -1
  32. package/dist/circuit.d.ts.map +1 -1
  33. package/dist/circuit.js +13 -7
  34. package/dist/circuit.js.map +1 -1
  35. package/dist/config.d.ts +2 -3
  36. package/dist/config.d.ts.map +1 -1
  37. package/dist/config.js +74 -6
  38. package/dist/config.js.map +1 -1
  39. package/dist/decision.d.ts +11 -0
  40. package/dist/decision.d.ts.map +1 -0
  41. package/dist/decision.js +40 -0
  42. package/dist/decision.js.map +1 -0
  43. package/dist/errors.d.ts +40 -0
  44. package/dist/errors.d.ts.map +1 -1
  45. package/dist/errors.js +46 -0
  46. package/dist/errors.js.map +1 -1
  47. package/dist/index.d.ts +8 -3
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +7 -2
  50. package/dist/index.js.map +1 -1
  51. package/dist/layer.d.ts +27 -0
  52. package/dist/layer.d.ts.map +1 -0
  53. package/dist/layer.js +77 -0
  54. package/dist/layer.js.map +1 -0
  55. package/dist/providers/anthropic.d.ts +11 -1
  56. package/dist/providers/anthropic.d.ts.map +1 -1
  57. package/dist/providers/anthropic.js +196 -18
  58. package/dist/providers/anthropic.js.map +1 -1
  59. package/dist/providers/bedrock.d.ts +1 -1
  60. package/dist/providers/bedrock.d.ts.map +1 -1
  61. package/dist/providers/bedrock.js +21 -3
  62. package/dist/providers/bedrock.js.map +1 -1
  63. package/dist/providers/detect.d.ts.map +1 -1
  64. package/dist/providers/detect.js +17 -2
  65. package/dist/providers/detect.js.map +1 -1
  66. package/dist/providers/index.d.ts +1 -0
  67. package/dist/providers/index.d.ts.map +1 -1
  68. package/dist/providers/index.js +1 -0
  69. package/dist/providers/index.js.map +1 -1
  70. package/dist/providers/openai.d.ts +11 -1
  71. package/dist/providers/openai.d.ts.map +1 -1
  72. package/dist/providers/openai.js +255 -20
  73. package/dist/providers/openai.js.map +1 -1
  74. package/dist/providers/vertex.d.ts +14 -0
  75. package/dist/providers/vertex.d.ts.map +1 -0
  76. package/dist/providers/vertex.js +174 -0
  77. package/dist/providers/vertex.js.map +1 -0
  78. package/dist/proxy-client.d.ts +37 -1
  79. package/dist/proxy-client.d.ts.map +1 -1
  80. package/dist/proxy-client.js +124 -4
  81. package/dist/proxy-client.js.map +1 -1
  82. package/dist/sdk-layer.d.ts +26 -0
  83. package/dist/sdk-layer.d.ts.map +1 -0
  84. package/dist/sdk-layer.js +73 -0
  85. package/dist/sdk-layer.js.map +1 -0
  86. package/dist/transport.d.ts +6 -1
  87. package/dist/transport.d.ts.map +1 -1
  88. package/dist/transport.js +14 -3
  89. package/dist/transport.js.map +1 -1
  90. package/dist/types.d.ts +178 -17
  91. package/dist/types.d.ts.map +1 -1
  92. package/dist/version.d.ts +9 -0
  93. package/dist/version.d.ts.map +1 -0
  94. package/dist/version.js +10 -0
  95. package/dist/version.js.map +1 -0
  96. package/dist/wrap.d.ts +13 -14
  97. package/dist/wrap.d.ts.map +1 -1
  98. package/dist/wrap.js +66 -59
  99. package/dist/wrap.js.map +1 -1
  100. package/package.json +7 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,122 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
4
+
5
+ ## 1.0.0 — 2026-09-13
6
+
7
+ Major release: control-layer ceilings replace the `appliedMode` boolean.
8
+ Effective layer is always `min(code.maxLayer, console published layer)`.
9
+
10
+ ### Added
11
+
12
+ **Control layers (`maxLayer`)** — `off`, `observe`, `govern`, `steer`, `route`.
13
+ At `govern`, metadata-only `POST /admit` may refuse before the provider is
14
+ called. SDK fetches console ceiling via `GET /sdk-layer` (30s cache).
15
+
16
+ **Wire headers** — `x-tc-sdk-version`, `x-tc-sdk-capabilities`, `x-tc-max-layer`
17
+ on proxied requests; parses `x-tc-decision` on responses.
18
+
19
+ **Framework adapters** — `wrapLangChain`, `wrapLlamaIndex`, `wrapCrewAI`,
20
+ `wrapVercelAi`.
21
+
22
+ **Google Vertex AI (observe-only)** — `@google/genai` clients with
23
+ `vertexai: true`; `models.generateContent` / streaming variants.
24
+
25
+ **Grant intersection** — `maxGrants` caps console data grants from code;
26
+ prompt cache writes gated on `retain_completions`.
27
+
28
+ ### Changed
29
+
30
+ **Breaking:** `appliedMode` is deprecated — use `maxLayer: "route"`. Default
31
+ proxy header `x-tc-max-layer` is now `observe` (was `route`).
32
+
33
+ ### Fixed
34
+
35
+ Circuit breaker re-probes proxy after fail-open; cold-start layer cache no
36
+ longer skips decay window.
37
+
38
+ ## 0.7.0 — 2026-08-22
39
+
40
+ A-04 chargeback attribution — stamp end-customer cohort and product feature on
41
+ every observation and forward them through applied-mode proxy headers.
42
+
43
+ ### Added
44
+
45
+ **`customer` / `feature` options** — optional chargeback dimensions (max 128
46
+ chars each). Set at wrap time via `wrap({ customer, feature })` or
47
+ `TENSORCOST_CUSTOMER` / `TENSORCOST_FEATURE` env vars. Per-call overrides via
48
+ `.withMeta({ customer, feature })` on OpenAI and Anthropic clients. Values land
49
+ as top-level `customer` / `feature` fields on observations (not inside
50
+ `metadata`) and as `x-tc-customer` / `x-tc-feature` in applied mode.
51
+
52
+ Bedrock and Vertex clients support wrap-time defaults only (no `withMeta()`).
53
+
54
+ ## 0.6.0 — 2026-05-28
55
+
56
+ SDK metadata story — lets you group traces by prompt template or deployment in
57
+ the TensorCost dashboard.
58
+
59
+ ### Added
60
+
61
+ **`deployment` option** — stamp a git SHA, CI run id, or release label on every
62
+ observation emitted by a wrapped client. The backend groups by this value when
63
+ you select "Group by deployment" in the Traces view.
64
+
65
+ ```ts
66
+ const client = wrap(new OpenAI({ apiKey: "..." }), {
67
+ apiKey: process.env.TENSORCOST_API_KEY,
68
+ deployment: process.env.GIT_SHA ?? "local",
69
+ });
70
+ ```
71
+
72
+ Falls back to `TENSORCOST_DEPLOYMENT` env var. When absent, no deployment field
73
+ is written and the group-by row shows as "(unset)".
74
+
75
+ **`promptTemplateId` option** — per-client default template marker. Useful when
76
+ one SDK client always drives the same call-site template.
77
+
78
+ ```ts
79
+ const client = wrap(new Anthropic({ apiKey: "..." }), {
80
+ apiKey: process.env.TENSORCOST_API_KEY,
81
+ promptTemplateId: "onboarding-email-v2",
82
+ });
83
+ ```
84
+
85
+ Falls back to `TENSORCOST_PROMPT_TEMPLATE_ID` env var.
86
+
87
+ **`client.withMeta(overrides)` helper** — per-call metadata override. Returns a
88
+ thin proxy of the wrapped client with the metadata overridden for that single
89
+ call. The wrap-time `deployment` default is still inherited unless you explicitly
90
+ pass a new `deployment` here; `promptTemplateId` is also overridable.
91
+
92
+ ```ts
93
+ // One client, multiple templates:
94
+ const baseClient = wrap(new OpenAI({ apiKey: "..." }), {
95
+ apiKey: process.env.TENSORCOST_API_KEY,
96
+ deployment: process.env.GIT_SHA,
97
+ });
98
+
99
+ // Template override per call-site:
100
+ const result = await (baseClient as any)
101
+ .withMeta({ promptTemplateId: "personalization-rerank-v3" })
102
+ .chat.completions.create({ model: "gpt-4o", messages });
103
+ ```
104
+
105
+ Both fields are stored as `metadata.deployment` and `metadata.prompt_template_id`
106
+ in the `trace_span` row. The backend's `GET /api/ai/traces?group_by=deployment`
107
+ and `group_by=prompt_template` endpoints index these keys.
108
+
109
+ ### No breaking changes
110
+
111
+ All new options are optional. Existing `wrap()` calls without metadata options
112
+ are completely unaffected — the metadata field is absent from the observation
113
+ envelope when nothing is configured.
114
+
115
+ ## 0.5.0 — 2026-05-13
116
+
117
+ Phase A4 batch 5 (environment + connectionId GA). See 0.2.1 for the original
118
+ `connectionId` addition; this entry is a placeholder for any 0.5.x patches.
119
+
3
120
  ## 0.4.0 — 2026-05-13
4
121
 
5
122
  Applied-mode hardening. Five new subsystems sit between `wrap()` and the
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @tensorcost/sdk
2
2
 
3
- One-line wrapper SDK for TensorCost — fire-and-forget LLM observability for OpenAI, Anthropic, and AWS Bedrock clients in Node.js.
3
+ One-line wrapper SDK 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
4
 
5
5
  ## Install
6
6
 
@@ -28,7 +28,7 @@ const resp = await client.chat.completions.create({
28
28
  });
29
29
  ```
30
30
 
31
- Anthropic is just as simple:
31
+ Anthropic works the same way:
32
32
 
33
33
  ```ts
34
34
  import Anthropic from "@anthropic-ai/sdk";
@@ -52,9 +52,11 @@ await client.messages.create({
52
52
  | `TENSORCOST_API_KEY` | Long-lived API key from the TensorCost console | Yes |
53
53
  | `TENSORCOST_BASE_URL` | Backend base URL | No (default `https://api.tensorcost.com`) |
54
54
  | `TENSORCOST_TENANT_ID` | Optional explicit tenant id | No |
55
- | `TENSORCOST_PROXY_URL` | Proxy base URL for applied mode | Required when `appliedMode: true` |
55
+ | `TENSORCOST_PROXY_URL` | Inference-proxy base URL | Required when `maxLayer` is `steer` or `route` |
56
56
  | `TENSORCOST_ENVIRONMENT` | Environment tag stamped on every observation | No |
57
57
  | `TENSORCOST_CONNECTION_ID` | Provider-connection identifier | No |
58
+ | `TENSORCOST_AGENT_ID` / `TENSORCOST_WORKFLOW_ID` | Agent-run attribution defaults | No |
59
+ | `TENSORCOST_CUSTOMER` / `TENSORCOST_FEATURE` | Chargeback tag defaults | No |
58
60
 
59
61
  Or pass explicitly:
60
62
 
@@ -65,32 +67,37 @@ const client = wrap(new OpenAI(...), {
65
67
  });
66
68
  ```
67
69
 
68
- ## Applied mode (Layer 2)
70
+ ## Control layers (`maxLayer`)
69
71
 
70
- When you enable applied mode, the SDK routes inference requests through the TensorCost inference-proxy instead of directly to the AI provider. The proxy decides per-request whether to re-route to an alternate provider or pass through unchanged — the SDK doesn't make that call.
72
+ v1.0 replaces the old `appliedMode` boolean with a **control-layer ceiling**. A layer is a ceiling, not a mode — the effective layer is always `min(your maxLayer, console published layer)`.
73
+
74
+ | Layer | Behavior |
75
+ |---|---|
76
+ | `off` | No SDK wiring; client returned unchanged |
77
+ | `observe` | Telemetry only (**default** when `maxLayer` is omitted) |
78
+ | `govern` | Metadata-only `POST /admit` before the provider call; may refuse unsafe calls |
79
+ | `steer` | Proxy on the path; proxy may steer without full live routing |
80
+ | `route` | Full applied-mode routing through the inference-proxy |
71
81
 
72
82
  ```ts
73
83
  const client = wrap(new OpenAI({ apiKey: process.env.OPENAI_API_KEY }), {
74
84
  apiKey: "tc_live_xxx",
75
- appliedMode: true,
76
- proxyUrl: "https://proxy.tensorcost.com", // or set TENSORCOST_PROXY_URL
77
- });
78
-
79
- // Use normally — the SDK redirects the client to the proxy transparently.
80
- // Observe-only telemetry still fires after every call.
81
- const resp = await client.chat.completions.create({
82
- model: "gpt-4o-mini",
83
- messages: [{ role: "user", content: "hi" }],
85
+ maxLayer: "route",
86
+ proxyUrl: "https://api.my-instance.tensorcost.com", // or TENSORCOST_PROXY_URL
84
87
  });
85
88
  ```
86
89
 
87
- `wrap()` throws `TensorCostConfigError` at `wrap()` time if `appliedMode: true` and neither `proxyUrl` nor `TENSORCOST_PROXY_URL` is set. AWS Bedrock is not supported in applied mode — SigV4 signing happens inside the AWS SDK before the proxy layer can intercept it; passing a Bedrock client with `appliedMode: true` throws `TensorCostConfigError` immediately. Observe-only mode works fine for Bedrock.
90
+ `wrap()` throws `TensorCostConfigError` when `maxLayer` is `steer` or `route` and no `proxyUrl` / `TENSORCOST_PROXY_URL` is set.
88
91
 
89
- See [`docs/features/inference-proxy-applied-mode.md`](../../docs/features/inference-proxy-applied-mode.md) for the proxy-side design.
92
+ The SDK fetches the console-published ceiling from `GET /api/inference-proxy/v1/sdk-layer` (30s cache, decays to `govern` after 5 minutes unreachable). Govern refusals and proxy decisions surface in the `x-tc-decision` response header.
90
93
 
91
- ## Applied-mode hardening (v0.4.0)
94
+ `appliedMode: true` still works but is **deprecated** — it maps to `maxLayer: "route"` with a console warning. Prefer `maxLayer` in new code.
92
95
 
93
- When `appliedMode: true`, you can configure retry policy, timeouts, lifecycle hooks, and the fail-open circuit breaker.
96
+ See [ADR-0026](https://github.com/vaadhlabs/tensorcost/blob/main/docs/adr/0026-sdk-control-layer-ceilings.md) and [`docs/features/inference-proxy-applied-mode.md`](../../docs/features/inference-proxy-applied-mode.md).
97
+
98
+ ## Proxy hardening
99
+
100
+ When `maxLayer` is `govern`, `steer`, or `route`, you can configure retries, timeouts, lifecycle hooks, and the fail-open circuit breaker.
94
101
 
95
102
  ### Retries
96
103
 
@@ -98,7 +105,7 @@ When `appliedMode: true`, you can configure retry policy, timeouts, lifecycle ho
98
105
  import { wrap, RetryConfig } from "@tensorcost/sdk";
99
106
 
100
107
  const client = wrap(new OpenAI(...), {
101
- appliedMode: true,
108
+ maxLayer: "route",
102
109
  proxyUrl: "...",
103
110
  retry: { maxAttempts: 5, baseDelayMs: 500, maxDelayMs: 30_000 },
104
111
  });
@@ -110,9 +117,9 @@ const client = wrap(new OpenAI(...), {
110
117
 
111
118
  ```ts
112
119
  const client = wrap(new OpenAI(...), {
113
- appliedMode: true,
120
+ maxLayer: "route",
114
121
  proxyUrl: "...",
115
- timeoutMs: 30_000, // 30 seconds; default 60_000
122
+ timeoutMs: 30_000, // default 60_000
116
123
  });
117
124
  ```
118
125
 
@@ -124,22 +131,20 @@ Surfaces as `TensorCostTimeoutError` (with `timeoutKind: "total"`) when exceeded
124
131
  import { wrap, LifecycleEvent } from "@tensorcost/sdk";
125
132
 
126
133
  const client = wrap(new OpenAI(...), {
127
- appliedMode: true,
134
+ maxLayer: "route",
128
135
  proxyUrl: "...",
129
136
  onLifecycleEvent(event: LifecycleEvent) {
130
- // event.kind is one of:
131
- // "before_request" | "after_response" | "on_retry"
132
- // | "on_error" | "on_fallback"
137
+ // "before_request" | "after_response" | "on_retry" | "on_error" | "on_fallback"
133
138
  console.log(event.kind, event.model, event.attemptNumber);
134
139
  },
135
140
  });
136
141
  ```
137
142
 
138
- Events contain only request metadata — never prompt content, response bodies, or credentials. Errors thrown by the callback are swallowed.
143
+ Events contain only request metadata — never prompt content, response bodies, or credentials.
139
144
 
140
145
  ### Typed errors
141
146
 
142
- Every failure from the proxy path is a subclass of `TensorCostError`. Import the classes from `@tensorcost/sdk`:
147
+ Every failure from the proxy path is a subclass of `TensorCostError`:
143
148
 
144
149
  ```ts
145
150
  import {
@@ -150,115 +155,78 @@ import {
150
155
  TensorCostQuotaError,
151
156
  TensorCostProviderError,
152
157
  } from "@tensorcost/sdk";
153
-
154
- try {
155
- await client.chat.completions.create({ model: "gpt-4o-mini", messages: [] });
156
- } catch (err) {
157
- if (err instanceof TensorCostQuotaError) {
158
- console.log("rate limited; retry after", err.retryAfterMs, "ms");
159
- } else if (err instanceof TensorCostError) {
160
- console.log("tensorcost error", err.status, err.attempt);
161
- }
162
- }
163
158
  ```
164
159
 
165
160
  ### Circuit breaker
166
161
 
167
- After 3 consecutive proxy 5xx responses the circuit opens and requests route directly to the provider (fail-open). The circuit closes after 5 consecutive probe successes.
162
+ After consecutive proxy failures the circuit opens and requests route directly to the provider (fail-open). Set `failOpenEnabled: false` when direct provider access is unavailable — proxy failures then surface as `TensorCostProxyError` instead of falling back.
168
163
 
169
- Set `failOpenEnabled: false` if direct provider access is unavailable (e.g. in-VPC deployments) — proxy failures then surface as `TensorCostProxyError` instead of falling back.
164
+ ## Agent-run attribution
165
+
166
+ Tag spend so **Router · Agent runs** can roll up cost per workflow:
170
167
 
171
168
  ```ts
172
169
  const client = wrap(new OpenAI(...), {
173
- appliedMode: true,
174
- proxyUrl: "...",
175
- failOpenEnabled: false, // raise on proxy error instead of falling through
170
+ apiKey: "tc_live_xxx",
171
+ agentId: "support-bot",
176
172
  });
173
+
174
+ const scoped = client.withMeta({ workflowId: crypto.randomUUID() });
175
+ await scoped.chat.completions.create({ model: "gpt-4o-mini", messages: [...] });
177
176
  ```
178
177
 
178
+ `customer` / `feature` are separate chargeback tags — not substitutes for `agentId` / `workflowId`.
179
+
179
180
  ## Fail-open guarantee
180
181
 
181
- If TensorCost is slow, down, or misconfigured, your customer call STILL succeeds. The SDK swallows internal errors and surfaces them via `console.warn`. The one exception: a missing API key throws `MissingConfigError` at `wrap()` time — silently dropping every observation would be worse than a loud import-time failure.
182
+ If TensorCost is slow, down, or misconfigured, your customer call still succeeds. The SDK swallows internal errors and surfaces them via `console.warn`. The one exception: a missing API key throws `MissingConfigError` at `wrap()` time.
182
183
 
183
184
  ## What is captured
184
185
 
185
186
  Only metadata — never prompt or completion content:
186
187
 
187
- - Provider + model
188
- - Operation (`chat.completions`, `completions`, `messages`)
189
- - Request / response timestamps
190
- - Input / output token counts (from the provider's `usage` field)
188
+ - Provider + model + operation + modality
189
+ - Request / response timestamps and token counts
191
190
  - Status (`success` / `error`) and error class/message on failure
192
- - A correlation UUID
191
+ - Correlation UUID, environment, agent/workflow ids, chargeback tags
193
192
 
194
193
  ## AWS Bedrock (observe-only)
195
194
 
196
195
  ```ts
197
- import {
198
- BedrockRuntimeClient,
199
- InvokeModelCommand,
200
- } from "@aws-sdk/client-bedrock-runtime";
196
+ import { BedrockRuntimeClient, InvokeModelCommand } from "@aws-sdk/client-bedrock-runtime";
201
197
  import { wrap } from "@tensorcost/sdk";
202
198
 
203
- // The only change: wrap() before passing the client to the rest of your code.
204
199
  const client = wrap(
205
200
  new BedrockRuntimeClient({ region: process.env.AWS_REGION ?? "us-east-1" }),
206
201
  );
207
-
208
- // Everything else is unchanged.
209
- const response = await client.send(
210
- new InvokeModelCommand({
211
- modelId: "anthropic.claude-3-haiku-20240307-v1:0",
212
- body: JSON.stringify({
213
- anthropic_version: "bedrock-2023-05-31",
214
- max_tokens: 64,
215
- messages: [{ role: "user", content: "What is 2 + 2?" }],
216
- }),
217
- contentType: "application/json",
218
- accept: "application/json",
219
- }),
220
- );
221
202
  ```
222
203
 
223
- The SDK registers a Smithy middleware on `client.middlewareStack` that intercepts every `client.send()` call and emits a fire-and-forget observation with the model id, token counts, latency, and status. Your code is otherwise unchanged.
224
-
225
- ### Supported commands
204
+ Supported commands: `InvokeModelCommand`, `ConverseCommand`, and their streaming counterparts (streaming records latency/status; per-chunk token accounting is a follow-up).
226
205
 
227
- | Command | Operation recorded | Token counts |
228
- |---|---|---|
229
- | `InvokeModelCommand` | `bedrock.invoke_model` | Parsed from response body (Anthropic-on-Bedrock and Nova/Titan shapes) |
230
- | `ConverseCommand` | `bedrock.converse` | Read from `response.usage.inputTokens / outputTokens` |
231
- | `InvokeModelWithResponseStreamCommand` | `bedrock.invoke_model_stream` | Not yet — placeholder observation with `null` tokens |
232
- | `ConverseStreamCommand` | `bedrock.converse_stream` | Not yet — placeholder observation with `null` tokens |
206
+ **Steer/route are not supported for Bedrock** — SigV4 signing happens inside the AWS SDK before the proxy can intercept. Use `maxLayer: "govern"` or lower for Bedrock telemetry.
233
207
 
234
- Per-chunk token accounting for streaming commands is a follow-up workstream. The placeholder observations record latency and success/error status, which is enough for alerting and anomaly detection. Token cost attribution will arrive once per-model pricing is wired in on the backend.
208
+ `@aws-sdk/client-bedrock-runtime` is an optional peer dependency.
235
209
 
236
- ### Applied mode
210
+ ## Google Vertex AI (observe-only)
237
211
 
238
- Applied mode (`{ appliedMode: true }`) is not supported for Bedrock. AWS Bedrock uses SigV4 request signing inside the AWS SDK before our middleware sees the request, so we cannot intercept and re-route it the way we do for OpenAI/Anthropic HTTP-Bearer clients. Calling `wrap(bedrockClient, { appliedMode: true })` throws `TensorCostConfigError` immediately.
212
+ Wrap `@google/genai` clients constructed with `vertexai: true`. `models.generateContent` and streaming variants emit observations with `provider: "vertex"`. Steer/route are not supported (same signing constraint as Bedrock).
239
213
 
240
- ### Installation
214
+ ## Framework adapters
241
215
 
242
- `@aws-sdk/client-bedrock-runtime` is an optional peer dependency — you only need it if you're using Bedrock. The SDK will not pull it in for OpenAI/Anthropic-only users.
216
+ Optional helpers for common LLM frameworks (observe + proxy paths where supported):
243
217
 
244
- ```bash
245
- npm install @aws-sdk/client-bedrock-runtime
246
- # or
247
- pnpm add @aws-sdk/client-bedrock-runtime
218
+ ```ts
219
+ import { wrapLangChain, wrapLlamaIndex, wrapCrewAI, wrapVercelAi } from "@tensorcost/sdk";
248
220
  ```
249
221
 
250
222
  ## Currently supported
251
223
 
252
- - OpenAI (`openai` >= 4.x) — `chat.completions.create`, `completions.create`
253
- - Anthropic (`@anthropic-ai/sdk` >= 0.x) — `messages.create`
254
- - AWS Bedrock (`@aws-sdk/client-bedrock-runtime` >= 3.x) — `InvokeModelCommand`, `ConverseCommand` (observe-only)
255
-
256
- ## Coming soon
257
-
258
- - Azure OpenAI, Vertex
259
- - Streaming token accounting for Bedrock (`InvokeModelWithResponseStreamCommand`, `ConverseStreamCommand`)
260
- - Tool / function calling (multi-call trace stitching)
224
+ - OpenAI (`openai` >= 4.x) — chat, completions, embeddings, responses
225
+ - Anthropic (`@anthropic-ai/sdk`) — messages
226
+ - AWS Bedrock (`@aws-sdk/client-bedrock-runtime`) — invoke/converse (+ streaming placeholders)
227
+ - Google Vertex AI (`@google/genai` with `vertexai: true`) — generateContent
228
+ - Azure OpenAI — wrap the standard `AzureOpenAI` client (observations report `provider: "openai"`)
261
229
 
262
230
  ## License
263
231
 
264
- Proprietary — TensorCost.
232
+ Apache-2.0
@@ -0,0 +1,4 @@
1
+ import type { WrapOptions } from "../types.js";
2
+ /** Thin passthrough — wraps the LLM used by CrewAI agents. */
3
+ export declare function wrapCrewAI<T>(client: T, options?: WrapOptions): T;
4
+ //# sourceMappingURL=crewai.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crewai.d.ts","sourceRoot":"","sources":["../../src/adapters/crewai.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG/C,8DAA8D;AAC9D,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,CAAC,CAEjE"}
@@ -0,0 +1,6 @@
1
+ import { wrap } from "../wrap.js";
2
+ /** Thin passthrough — wraps the LLM used by CrewAI agents. */
3
+ export function wrapCrewAI(client, options) {
4
+ return wrap(client, options);
5
+ }
6
+ //# sourceMappingURL=crewai.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crewai.js","sourceRoot":"","sources":["../../src/adapters/crewai.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAElC,8DAA8D;AAC9D,MAAM,UAAU,UAAU,CAAI,MAAS,EAAE,OAAqB;IAC5D,OAAO,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAC/B,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { WrapOptions } from "../types.js";
2
+ /** Thin passthrough — wraps the underlying OpenAI/Anthropic client. */
3
+ export declare function wrapLangChain<T>(client: T, options?: WrapOptions): T;
4
+ //# sourceMappingURL=langchain.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"langchain.d.ts","sourceRoot":"","sources":["../../src/adapters/langchain.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG/C,uEAAuE;AACvE,wBAAgB,aAAa,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,CAAC,CAEpE"}
@@ -0,0 +1,6 @@
1
+ import { wrap } from "../wrap.js";
2
+ /** Thin passthrough — wraps the underlying OpenAI/Anthropic client. */
3
+ export function wrapLangChain(client, options) {
4
+ return wrap(client, options);
5
+ }
6
+ //# sourceMappingURL=langchain.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"langchain.js","sourceRoot":"","sources":["../../src/adapters/langchain.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAElC,uEAAuE;AACvE,MAAM,UAAU,aAAa,CAAI,MAAS,EAAE,OAAqB;IAC/D,OAAO,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAC/B,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { WrapOptions } from "../types.js";
2
+ /** Thin passthrough — wraps the underlying LLM client instance. */
3
+ export declare function wrapLlamaIndex<T>(client: T, options?: WrapOptions): T;
4
+ //# sourceMappingURL=llamaindex.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"llamaindex.d.ts","sourceRoot":"","sources":["../../src/adapters/llamaindex.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG/C,mEAAmE;AACnE,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,CAAC,CAErE"}
@@ -0,0 +1,6 @@
1
+ import { wrap } from "../wrap.js";
2
+ /** Thin passthrough — wraps the underlying LLM client instance. */
3
+ export function wrapLlamaIndex(client, options) {
4
+ return wrap(client, options);
5
+ }
6
+ //# sourceMappingURL=llamaindex.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"llamaindex.js","sourceRoot":"","sources":["../../src/adapters/llamaindex.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAElC,mEAAmE;AACnE,MAAM,UAAU,cAAc,CAAI,MAAS,EAAE,OAAqB;IAChE,OAAO,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAC/B,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { WrapOptions } from "../types.js";
2
+ /** Thin passthrough — wraps the provider client passed to Vercel AI SDK. */
3
+ export declare function wrapVercelAi<T>(client: T, options?: WrapOptions): T;
4
+ //# sourceMappingURL=vercel-ai.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vercel-ai.d.ts","sourceRoot":"","sources":["../../src/adapters/vercel-ai.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG/C,4EAA4E;AAC5E,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,CAAC,CAEnE"}
@@ -0,0 +1,6 @@
1
+ import { wrap } from "../wrap.js";
2
+ /** Thin passthrough — wraps the provider client passed to Vercel AI SDK. */
3
+ export function wrapVercelAi(client, options) {
4
+ return wrap(client, options);
5
+ }
6
+ //# sourceMappingURL=vercel-ai.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vercel-ai.js","sourceRoot":"","sources":["../../src/adapters/vercel-ai.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAElC,4EAA4E;AAC5E,MAAM,UAAU,YAAY,CAAI,MAAS,EAAE,OAAqB;IAC9D,OAAO,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAC/B,CAAC"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Govern-layer admit — metadata-only POST /api/inference-proxy/v1/admit.
3
+ *
4
+ * Fail-open on transport failure: the customer's call proceeds to the
5
+ * provider and an error observation is emitted when transport is available.
6
+ */
7
+ import type { ControlLayer } from "./layer.js";
8
+ import type { Operation, Provider } from "./types.js";
9
+ import type { ObservationTransport } from "./transport.js";
10
+ export interface AdmitMetadata {
11
+ provider: Provider;
12
+ model: string;
13
+ operation: Operation;
14
+ correlation_id: string;
15
+ environment?: string | null;
16
+ connection_id?: string | null;
17
+ agent_id?: string;
18
+ workflow_id?: string;
19
+ customer?: string;
20
+ feature?: string;
21
+ application?: string | null;
22
+ tags?: string[];
23
+ }
24
+ export interface AdmitRequestOptions {
25
+ baseUrl: string;
26
+ bearerToken: string;
27
+ maxLayer: ControlLayer;
28
+ metadata: AdmitMetadata;
29
+ transport?: ObservationTransport;
30
+ failOpen?: boolean;
31
+ timeoutMs?: number;
32
+ fetchImpl?: typeof fetch;
33
+ }
34
+ export interface AdmitResult {
35
+ allowed: boolean;
36
+ decisionHeader: string | null;
37
+ }
38
+ export declare function admitRequest(opts: AdmitRequestOptions): Promise<AdmitResult>;
39
+ //# sourceMappingURL=admit-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admit-client.d.ts","sourceRoot":"","sources":["../src/admit-client.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC/C,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAI3D,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,QAAQ,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,SAAS,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,YAAY,CAAC;IACvB,QAAQ,EAAE,aAAa,CAAC;IACxB,SAAS,CAAC,EAAE,oBAAoB,CAAC;IACjC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED,wBAAsB,YAAY,CAChC,IAAI,EAAE,mBAAmB,GACxB,OAAO,CAAC,WAAW,CAAC,CA8DtB"}
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Govern-layer admit — metadata-only POST /api/inference-proxy/v1/admit.
3
+ *
4
+ * Fail-open on transport failure: the customer's call proceeds to the
5
+ * provider and an error observation is emitted when transport is available.
6
+ */
7
+ import { ADMIT_PATH } from "./layer.js";
8
+ import { SDK_VERSION, sdkCapabilityHeader } from "./version.js";
9
+ export async function admitRequest(opts) {
10
+ const fetchImpl = opts.fetchImpl ?? globalThis.fetch.bind(globalThis);
11
+ const url = `${opts.baseUrl.replace(/\/+$/, "")}${ADMIT_PATH}`;
12
+ const headers = {
13
+ Authorization: `Bearer ${opts.bearerToken}`,
14
+ "Content-Type": "application/json",
15
+ "x-tc-sdk-version": SDK_VERSION,
16
+ "x-tc-sdk-capabilities": sdkCapabilityHeader(),
17
+ "x-tc-max-layer": opts.maxLayer,
18
+ };
19
+ const body = {
20
+ provider: opts.metadata.provider,
21
+ model: opts.metadata.model,
22
+ operation: opts.metadata.operation,
23
+ correlation_id: opts.metadata.correlation_id,
24
+ ...(opts.metadata.environment ? { environment: opts.metadata.environment } : {}),
25
+ ...(opts.metadata.connection_id ? { connection_id: opts.metadata.connection_id } : {}),
26
+ ...(opts.metadata.agent_id ? { agent_id: opts.metadata.agent_id } : {}),
27
+ ...(opts.metadata.workflow_id ? { workflow_id: opts.metadata.workflow_id } : {}),
28
+ ...(opts.metadata.customer ? { customer: opts.metadata.customer } : {}),
29
+ ...(opts.metadata.feature ? { feature: opts.metadata.feature } : {}),
30
+ ...(opts.metadata.application ? { application: opts.metadata.application } : {}),
31
+ ...(opts.metadata.tags && opts.metadata.tags.length > 0
32
+ ? { tags: opts.metadata.tags.join(",") }
33
+ : {}),
34
+ };
35
+ const controller = new AbortController();
36
+ const timer = setTimeout(() => controller.abort(), opts.timeoutMs ?? 5_000);
37
+ try {
38
+ const resp = await fetchImpl(url, {
39
+ method: "POST",
40
+ headers,
41
+ body: JSON.stringify(body),
42
+ signal: controller.signal,
43
+ });
44
+ const decisionHeader = resp.headers.get("x-tc-decision");
45
+ if (resp.status === 403) {
46
+ return { allowed: false, decisionHeader };
47
+ }
48
+ if (!resp.ok) {
49
+ throw new Error(`admit POST failed status=${resp.status}`);
50
+ }
51
+ return { allowed: true, decisionHeader };
52
+ }
53
+ catch (err) {
54
+ if (opts.failOpen !== false) {
55
+ postAdmitFailureObservation(opts, err);
56
+ return { allowed: true, decisionHeader: null };
57
+ }
58
+ throw err;
59
+ }
60
+ finally {
61
+ clearTimeout(timer);
62
+ }
63
+ }
64
+ function postAdmitFailureObservation(opts, err) {
65
+ if (!opts.transport)
66
+ return;
67
+ try {
68
+ opts.transport.post({
69
+ sdk_version: SDK_VERSION,
70
+ provider: opts.metadata.provider,
71
+ model: opts.metadata.model,
72
+ operation: opts.metadata.operation,
73
+ request_at: new Date().toISOString(),
74
+ response_at: new Date().toISOString(),
75
+ input_tokens: null,
76
+ output_tokens: null,
77
+ cost_usd_cents: null,
78
+ status: "success",
79
+ error_message: null,
80
+ correlation_id: opts.metadata.correlation_id,
81
+ ...(opts.metadata.environment ? { environment: opts.metadata.environment } : {}),
82
+ ...(opts.metadata.connection_id ? { connection_id: opts.metadata.connection_id } : {}),
83
+ metadata: {
84
+ decision_action: "allow",
85
+ decision_reason: "admit_unavailable",
86
+ },
87
+ });
88
+ }
89
+ catch {
90
+ /* belt-and-suspenders */
91
+ }
92
+ }
93
+ //# sourceMappingURL=admit-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admit-client.js","sourceRoot":"","sources":["../src/admit-client.ts"],"names":[],"mappings":"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"}