@tensorcost/sdk 0.3.0 → 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.
Files changed (56) hide show
  1. package/CHANGELOG.md +74 -0
  2. package/README.md +150 -5
  3. package/dist/circuit.d.ts +56 -0
  4. package/dist/circuit.d.ts.map +1 -0
  5. package/dist/circuit.js +93 -0
  6. package/dist/circuit.js.map +1 -0
  7. package/dist/config.d.ts.map +1 -1
  8. package/dist/config.js +17 -0
  9. package/dist/config.js.map +1 -1
  10. package/dist/errors.d.ts +67 -0
  11. package/dist/errors.d.ts.map +1 -0
  12. package/dist/errors.js +84 -0
  13. package/dist/errors.js.map +1 -0
  14. package/dist/index.d.ts +4 -1
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +2 -1
  17. package/dist/index.js.map +1 -1
  18. package/dist/providers/anthropic.d.ts +17 -1
  19. package/dist/providers/anthropic.d.ts.map +1 -1
  20. package/dist/providers/anthropic.js +179 -20
  21. package/dist/providers/anthropic.js.map +1 -1
  22. package/dist/providers/bedrock.d.ts +48 -0
  23. package/dist/providers/bedrock.d.ts.map +1 -0
  24. package/dist/providers/bedrock.js +277 -0
  25. package/dist/providers/bedrock.js.map +1 -0
  26. package/dist/providers/detect.d.ts +9 -3
  27. package/dist/providers/detect.d.ts.map +1 -1
  28. package/dist/providers/detect.js +24 -6
  29. package/dist/providers/detect.js.map +1 -1
  30. package/dist/providers/index.d.ts +1 -0
  31. package/dist/providers/index.d.ts.map +1 -1
  32. package/dist/providers/index.js +1 -0
  33. package/dist/providers/index.js.map +1 -1
  34. package/dist/providers/openai.d.ts +19 -1
  35. package/dist/providers/openai.d.ts.map +1 -1
  36. package/dist/providers/openai.js +217 -20
  37. package/dist/providers/openai.js.map +1 -1
  38. package/dist/proxy-client.d.ts +64 -0
  39. package/dist/proxy-client.d.ts.map +1 -0
  40. package/dist/proxy-client.js +259 -0
  41. package/dist/proxy-client.js.map +1 -0
  42. package/dist/retry.d.ts +50 -0
  43. package/dist/retry.d.ts.map +1 -0
  44. package/dist/retry.js +80 -0
  45. package/dist/retry.js.map +1 -0
  46. package/dist/telemetry.d.ts +70 -0
  47. package/dist/telemetry.d.ts.map +1 -0
  48. package/dist/telemetry.js +41 -0
  49. package/dist/telemetry.js.map +1 -0
  50. package/dist/types.d.ts +63 -2
  51. package/dist/types.d.ts.map +1 -1
  52. package/dist/wrap.d.ts +9 -0
  53. package/dist/wrap.d.ts.map +1 -1
  54. package/dist/wrap.js +47 -2
  55. package/dist/wrap.js.map +1 -1
  56. package/package.json +12 -2
@@ -0,0 +1,259 @@
1
+ /**
2
+ * Applied-mode proxy HTTP client.
3
+ *
4
+ * This is the single place that combines retries, timeouts, typed errors,
5
+ * telemetry hooks, and the fail-open circuit breaker. The provider wrappers
6
+ * (openai.ts, anthropic.ts) call `proxyRequest()` instead of hitting the
7
+ * proxy directly.
8
+ *
9
+ * The contract:
10
+ * - Returns the parsed response body on success.
11
+ * - On all non-success outcomes, throws a subclass of TensorCostError.
12
+ * - Never swallows errors — the provider wrapper's fail-open logic
13
+ * (fall back to direct call on proxy failure) lives in the provider
14
+ * wrapper, not here.
15
+ *
16
+ * Timeout handling:
17
+ * - Non-streaming: AbortController fires after `timeoutMs` total.
18
+ * - Streaming is not handled in v1 (no stream: true support yet).
19
+ *
20
+ * Retry logic:
21
+ * - Retries on 5xx + network errors + 429.
22
+ * - Respects Retry-After on 429.
23
+ * - Stops at maxAttempts.
24
+ * - Does not retry on 4xx (except 429).
25
+ *
26
+ * Circuit breaker:
27
+ * - Maintained on the CircuitBreaker instance passed in.
28
+ * - When open, throws TensorCostProxyError immediately with a clear
29
+ * message so the provider wrapper can activate the direct-provider
30
+ * fallback.
31
+ */
32
+ import { TensorCostError, TensorCostNetworkError, TensorCostTimeoutError, TensorCostProxyError, TensorCostQuotaError, TensorCostProviderError, } from "./errors.js";
33
+ import { DEFAULT_RETRY_CONFIG, computeDelay, retryAfterMs, sleep, } from "./retry.js";
34
+ import { emitEvent } from "./telemetry.js";
35
+ const PROXY_CHAT_PATH = "/api/inference-proxy/v1/chat/completions";
36
+ /**
37
+ * Execute a single proxied request with retries, timeouts, telemetry,
38
+ * and circuit-breaker integration.
39
+ *
40
+ * Throws a `TensorCostError` subclass on any failure.
41
+ * Returns the parsed JSON body on success.
42
+ */
43
+ export async function proxyRequest(opts) {
44
+ const retryConfig = opts.retry ?? DEFAULT_RETRY_CONFIG;
45
+ const timeoutMs = opts.timeoutMs ?? 60_000;
46
+ const fetchImpl = opts.fetchImpl ?? globalThis.fetch.bind(globalThis);
47
+ const callStart = Date.now();
48
+ // Check circuit breaker — if open, throw immediately so the caller can
49
+ // activate the direct-provider fallback path.
50
+ if (opts.circuit.shouldBypass()) {
51
+ throw new TensorCostProxyError("tensorcost: proxy circuit open — routing directly to provider", { attempt: 1 });
52
+ }
53
+ const url = `${opts.proxyUrl.replace(/\/+$/, "")}${PROXY_CHAT_PATH}`;
54
+ const headers = {
55
+ Authorization: `Bearer ${opts.bearerToken}`,
56
+ "Content-Type": "application/json",
57
+ "x-tc-provider-url": opts.providerUrl,
58
+ "x-tc-provider-auth": opts.providerAuth,
59
+ "x-tc-correlation-id": opts.correlationId,
60
+ };
61
+ if (opts.environment) {
62
+ headers["x-tc-environment"] = opts.environment;
63
+ }
64
+ let lastError = null;
65
+ for (let attempt = 0; attempt < retryConfig.maxAttempts; attempt++) {
66
+ const elapsedMs = Date.now() - callStart;
67
+ emitEvent(opts.onLifecycleEvent, {
68
+ kind: "before_request",
69
+ provider: opts.provider,
70
+ model: opts.model,
71
+ operation: opts.operation,
72
+ attemptNumber: attempt + 1,
73
+ elapsedMs,
74
+ });
75
+ const controller = new AbortController();
76
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
77
+ let resp;
78
+ try {
79
+ resp = await fetchImpl(url, {
80
+ method: "POST",
81
+ headers,
82
+ body: JSON.stringify(opts.body),
83
+ signal: controller.signal,
84
+ });
85
+ }
86
+ catch (err) {
87
+ clearTimeout(timer);
88
+ // AbortController fired → timeout.
89
+ if (err instanceof Error &&
90
+ (err.name === "AbortError" || err.name === "TimeoutError")) {
91
+ const tcErr = new TensorCostTimeoutError(`tensorcost: proxy request timed out after ${timeoutMs}ms`, "total", { cause: err, attempt: attempt + 1 });
92
+ lastError = tcErr;
93
+ opts.circuit.recordFailure();
94
+ // Timeouts are retriable.
95
+ if (attempt + 1 < retryConfig.maxAttempts) {
96
+ const delay = computeDelay(attempt, retryConfig);
97
+ emitEvent(opts.onLifecycleEvent, {
98
+ kind: "on_retry",
99
+ provider: opts.provider,
100
+ model: opts.model,
101
+ operation: opts.operation,
102
+ attemptNumber: attempt + 1,
103
+ elapsedMs: Date.now() - callStart,
104
+ reason: "network_error",
105
+ status: null,
106
+ delayMs: delay,
107
+ });
108
+ await sleep(delay);
109
+ continue;
110
+ }
111
+ emitEvent(opts.onLifecycleEvent, {
112
+ kind: "on_error",
113
+ provider: opts.provider,
114
+ model: opts.model,
115
+ operation: opts.operation,
116
+ attemptNumber: attempt + 1,
117
+ elapsedMs: Date.now() - callStart,
118
+ errorClass: tcErr.constructor.name,
119
+ status: null,
120
+ });
121
+ throw tcErr;
122
+ }
123
+ // Network error (ECONNREFUSED, DNS failure, etc.)
124
+ const tcErr = new TensorCostNetworkError(`tensorcost: network error reaching proxy: ${err.message}`, { cause: err, attempt: attempt + 1 });
125
+ lastError = tcErr;
126
+ opts.circuit.recordFailure();
127
+ if (attempt + 1 < retryConfig.maxAttempts) {
128
+ const delay = computeDelay(attempt, retryConfig);
129
+ emitEvent(opts.onLifecycleEvent, {
130
+ kind: "on_retry",
131
+ provider: opts.provider,
132
+ model: opts.model,
133
+ operation: opts.operation,
134
+ attemptNumber: attempt + 1,
135
+ elapsedMs: Date.now() - callStart,
136
+ reason: "network_error",
137
+ status: null,
138
+ delayMs: delay,
139
+ });
140
+ await sleep(delay);
141
+ continue;
142
+ }
143
+ emitEvent(opts.onLifecycleEvent, {
144
+ kind: "on_error",
145
+ provider: opts.provider,
146
+ model: opts.model,
147
+ operation: opts.operation,
148
+ attemptNumber: attempt + 1,
149
+ elapsedMs: Date.now() - callStart,
150
+ errorClass: tcErr.constructor.name,
151
+ status: null,
152
+ });
153
+ throw tcErr;
154
+ }
155
+ finally {
156
+ clearTimeout(timer);
157
+ }
158
+ const requestId = resp.headers.get("x-tc-request-id");
159
+ // 2xx — success.
160
+ if (resp.ok) {
161
+ opts.circuit.recordSuccess();
162
+ emitEvent(opts.onLifecycleEvent, {
163
+ kind: "after_response",
164
+ provider: opts.provider,
165
+ model: opts.model,
166
+ operation: opts.operation,
167
+ attemptNumber: attempt + 1,
168
+ elapsedMs: Date.now() - callStart,
169
+ status: resp.status,
170
+ requestId,
171
+ });
172
+ return resp.json();
173
+ }
174
+ // 429 — quota error, may have Retry-After.
175
+ if (resp.status === 429) {
176
+ const raMs = retryAfterMs(resp);
177
+ const tcErr = new TensorCostQuotaError(`tensorcost: proxy returned 429 Too Many Requests`, raMs ?? null, { status: 429, requestId, attempt: attempt + 1 });
178
+ lastError = tcErr;
179
+ // Don't count quota errors against the circuit (the proxy is healthy).
180
+ if (attempt + 1 < retryConfig.maxAttempts) {
181
+ const delay = computeDelay(attempt, retryConfig, raMs);
182
+ emitEvent(opts.onLifecycleEvent, {
183
+ kind: "on_retry",
184
+ provider: opts.provider,
185
+ model: opts.model,
186
+ operation: opts.operation,
187
+ attemptNumber: attempt + 1,
188
+ elapsedMs: Date.now() - callStart,
189
+ reason: "quota_429",
190
+ status: 429,
191
+ delayMs: delay,
192
+ });
193
+ await sleep(delay);
194
+ continue;
195
+ }
196
+ emitEvent(opts.onLifecycleEvent, {
197
+ kind: "on_error",
198
+ provider: opts.provider,
199
+ model: opts.model,
200
+ operation: opts.operation,
201
+ attemptNumber: attempt + 1,
202
+ elapsedMs: Date.now() - callStart,
203
+ errorClass: tcErr.constructor.name,
204
+ status: 429,
205
+ });
206
+ throw tcErr;
207
+ }
208
+ // 5xx — proxy infrastructure error.
209
+ if (resp.status >= 500) {
210
+ const tcErr = new TensorCostProxyError(`tensorcost: proxy returned ${resp.status}`, { status: resp.status, requestId, attempt: attempt + 1 });
211
+ lastError = tcErr;
212
+ opts.circuit.recordFailure();
213
+ if (attempt + 1 < retryConfig.maxAttempts) {
214
+ const delay = computeDelay(attempt, retryConfig);
215
+ emitEvent(opts.onLifecycleEvent, {
216
+ kind: "on_retry",
217
+ provider: opts.provider,
218
+ model: opts.model,
219
+ operation: opts.operation,
220
+ attemptNumber: attempt + 1,
221
+ elapsedMs: Date.now() - callStart,
222
+ reason: "5xx",
223
+ status: resp.status,
224
+ delayMs: delay,
225
+ });
226
+ await sleep(delay);
227
+ continue;
228
+ }
229
+ emitEvent(opts.onLifecycleEvent, {
230
+ kind: "on_error",
231
+ provider: opts.provider,
232
+ model: opts.model,
233
+ operation: opts.operation,
234
+ attemptNumber: attempt + 1,
235
+ elapsedMs: Date.now() - callStart,
236
+ errorClass: tcErr.constructor.name,
237
+ status: resp.status,
238
+ });
239
+ throw tcErr;
240
+ }
241
+ // 4xx (not 429) — provider error forwarded by the proxy. Non-retriable.
242
+ const tcErr = new TensorCostProviderError(`tensorcost: upstream provider returned ${resp.status}`, { status: resp.status, requestId, attempt: attempt + 1 });
243
+ emitEvent(opts.onLifecycleEvent, {
244
+ kind: "on_error",
245
+ provider: opts.provider,
246
+ model: opts.model,
247
+ operation: opts.operation,
248
+ attemptNumber: attempt + 1,
249
+ elapsedMs: Date.now() - callStart,
250
+ errorClass: tcErr.constructor.name,
251
+ status: resp.status,
252
+ });
253
+ throw tcErr;
254
+ }
255
+ // Should be unreachable — every loop arm either returns or throws.
256
+ throw lastError ?? new TensorCostProxyError("tensorcost: max attempts reached");
257
+ }
258
+ export { TensorCostError };
259
+ //# sourceMappingURL=proxy-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy-client.js","sourceRoot":"","sources":["../src/proxy-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Retry utilities for the TensorCost applied-mode request path.
3
+ *
4
+ * Retries use jittered exponential backoff:
5
+ *
6
+ * delay = min(maxDelayMs, baseDelayMs * 2^attempt) * (0.5 + Math.random() * 0.5)
7
+ *
8
+ * where `attempt` is 0-indexed. With defaults (baseDelayMs=500, maxDelayMs=30 000),
9
+ * the sequence of *maximum* delays is:
10
+ * attempt 1: 500ms
11
+ * attempt 2: 1 000ms
12
+ * attempt 3: 2 000ms
13
+ * attempt 4: 4 000ms
14
+ * attempt 5: 8 000ms
15
+ * The jitter factor halves the minimum so actual delays are 50–100% of those
16
+ * maximums. This matches the posture used by the OpenAI Node SDK.
17
+ *
18
+ * Rules:
19
+ * - Never retry 4xx responses (except 429).
20
+ * - Always retry 5xx responses and network errors.
21
+ * - On 429: honour the `Retry-After` response header when present
22
+ * (overrides the backoff delay).
23
+ * - Non-idempotent verbs (POST without `stream:false` is still idempotent
24
+ * at the proxy level — the proxy is stateless) default to idempotent for
25
+ * all chat/completions calls.
26
+ */
27
+ export interface RetryConfig {
28
+ /** Maximum number of total attempts (1 = no retries). Default: 5. */
29
+ maxAttempts: number;
30
+ /** Base backoff interval in milliseconds. Default: 500. */
31
+ baseDelayMs: number;
32
+ /** Cap on any single backoff interval. Default: 30 000 (30s). */
33
+ maxDelayMs: number;
34
+ }
35
+ export declare const DEFAULT_RETRY_CONFIG: RetryConfig;
36
+ /**
37
+ * Compute the delay before the next attempt.
38
+ *
39
+ * When `retryAfterMs` is provided (from a 429 Retry-After header) it
40
+ * overrides the exponential calculation but is still capped at maxDelayMs.
41
+ */
42
+ export declare function computeDelay(attempt: number, // 0-indexed
43
+ config: RetryConfig, retryAfterMs?: number): number;
44
+ /** True for HTTP status codes the SDK should retry on. */
45
+ export declare function isRetryableStatus(status: number): boolean;
46
+ /** Extract Retry-After delay in milliseconds from a fetch Response. */
47
+ export declare function retryAfterMs(resp: Response): number | undefined;
48
+ /** Sleep for `ms` milliseconds (abortable). */
49
+ export declare function sleep(ms: number, signal?: AbortSignal): Promise<void>;
50
+ //# sourceMappingURL=retry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../src/retry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,MAAM,WAAW,WAAW;IAC1B,qEAAqE;IACrE,WAAW,EAAE,MAAM,CAAC;IACpB,2DAA2D;IAC3D,WAAW,EAAE,MAAM,CAAC;IACpB,iEAAiE;IACjE,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,oBAAoB,EAAE,WAIlC,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,MAAM,EAAE,YAAY;AAC7B,MAAM,EAAE,WAAW,EACnB,YAAY,CAAC,EAAE,MAAM,GACpB,MAAM,CAQR;AAED,0DAA0D;AAC1D,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAEzD;AAED,uEAAuE;AACvE,wBAAgB,YAAY,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,GAAG,SAAS,CAS/D;AAED,+CAA+C;AAC/C,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAYrE"}
package/dist/retry.js ADDED
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Retry utilities for the TensorCost applied-mode request path.
3
+ *
4
+ * Retries use jittered exponential backoff:
5
+ *
6
+ * delay = min(maxDelayMs, baseDelayMs * 2^attempt) * (0.5 + Math.random() * 0.5)
7
+ *
8
+ * where `attempt` is 0-indexed. With defaults (baseDelayMs=500, maxDelayMs=30 000),
9
+ * the sequence of *maximum* delays is:
10
+ * attempt 1: 500ms
11
+ * attempt 2: 1 000ms
12
+ * attempt 3: 2 000ms
13
+ * attempt 4: 4 000ms
14
+ * attempt 5: 8 000ms
15
+ * The jitter factor halves the minimum so actual delays are 50–100% of those
16
+ * maximums. This matches the posture used by the OpenAI Node SDK.
17
+ *
18
+ * Rules:
19
+ * - Never retry 4xx responses (except 429).
20
+ * - Always retry 5xx responses and network errors.
21
+ * - On 429: honour the `Retry-After` response header when present
22
+ * (overrides the backoff delay).
23
+ * - Non-idempotent verbs (POST without `stream:false` is still idempotent
24
+ * at the proxy level — the proxy is stateless) default to idempotent for
25
+ * all chat/completions calls.
26
+ */
27
+ export const DEFAULT_RETRY_CONFIG = {
28
+ maxAttempts: 5,
29
+ baseDelayMs: 500,
30
+ maxDelayMs: 30_000,
31
+ };
32
+ /**
33
+ * Compute the delay before the next attempt.
34
+ *
35
+ * When `retryAfterMs` is provided (from a 429 Retry-After header) it
36
+ * overrides the exponential calculation but is still capped at maxDelayMs.
37
+ */
38
+ export function computeDelay(attempt, // 0-indexed
39
+ config, retryAfterMs) {
40
+ if (retryAfterMs !== null && retryAfterMs !== undefined && retryAfterMs >= 0) {
41
+ return Math.min(retryAfterMs, config.maxDelayMs);
42
+ }
43
+ const exponential = config.baseDelayMs * Math.pow(2, attempt);
44
+ const capped = Math.min(exponential, config.maxDelayMs);
45
+ // Jitter: uniform in [0.5 * capped, 1.0 * capped]
46
+ return capped * (0.5 + Math.random() * 0.5);
47
+ }
48
+ /** True for HTTP status codes the SDK should retry on. */
49
+ export function isRetryableStatus(status) {
50
+ return status === 429 || status >= 500;
51
+ }
52
+ /** Extract Retry-After delay in milliseconds from a fetch Response. */
53
+ export function retryAfterMs(resp) {
54
+ const header = resp.headers.get("retry-after");
55
+ if (!header)
56
+ return undefined;
57
+ // RFC 7231: numeric seconds OR HTTP-date
58
+ const secs = parseFloat(header);
59
+ if (!isNaN(secs))
60
+ return Math.ceil(secs * 1000);
61
+ const date = Date.parse(header);
62
+ if (!isNaN(date))
63
+ return Math.max(0, date - Date.now());
64
+ return undefined;
65
+ }
66
+ /** Sleep for `ms` milliseconds (abortable). */
67
+ export function sleep(ms, signal) {
68
+ return new Promise((resolve, reject) => {
69
+ if (signal?.aborted) {
70
+ reject(new DOMException("Aborted", "AbortError"));
71
+ return;
72
+ }
73
+ const timer = setTimeout(resolve, ms);
74
+ signal?.addEventListener("abort", () => {
75
+ clearTimeout(timer);
76
+ reject(new DOMException("Aborted", "AbortError"));
77
+ });
78
+ });
79
+ }
80
+ //# sourceMappingURL=retry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retry.js","sourceRoot":"","sources":["../src/retry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAWH,MAAM,CAAC,MAAM,oBAAoB,GAAgB;IAC/C,WAAW,EAAE,CAAC;IACd,WAAW,EAAE,GAAG;IAChB,UAAU,EAAE,MAAM;CACnB,CAAC;AAEF;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAC1B,OAAe,EAAE,YAAY;AAC7B,MAAmB,EACnB,YAAqB;IAErB,IAAI,YAAY,KAAK,IAAI,IAAI,YAAY,KAAK,SAAS,IAAI,YAAY,IAAI,CAAC,EAAE,CAAC;QAC7E,OAAO,IAAI,CAAC,GAAG,CAAC,YAAY,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC;IACnD,CAAC;IACD,MAAM,WAAW,GAAG,MAAM,CAAC,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;IAC9D,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC;IACxD,kDAAkD;IAClD,OAAO,MAAM,GAAG,CAAC,GAAG,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,GAAG,CAAC,CAAC;AAC9C,CAAC;AAED,0DAA0D;AAC1D,MAAM,UAAU,iBAAiB,CAAC,MAAc;IAC9C,OAAO,MAAM,KAAK,GAAG,IAAI,MAAM,IAAI,GAAG,CAAC;AACzC,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,YAAY,CAAC,IAAc;IACzC,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;IAC/C,IAAI,CAAC,MAAM;QAAE,OAAO,SAAS,CAAC;IAC9B,yCAAyC;IACzC,MAAM,IAAI,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;IAChC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,CAAC;IAChD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAChC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;IACxD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,+CAA+C;AAC/C,MAAM,UAAU,KAAK,CAAC,EAAU,EAAE,MAAoB;IACpD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,IAAI,MAAM,EAAE,OAAO,EAAE,CAAC;YACpB,MAAM,CAAC,IAAI,YAAY,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC,CAAC;YAClD,OAAO;QACT,CAAC;QACD,MAAM,KAAK,GAAG,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;QACtC,MAAM,EAAE,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE;YACrC,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,MAAM,CAAC,IAAI,YAAY,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC,CAAC;QACpD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Telemetry hook types for the TensorCost Node SDK.
3
+ *
4
+ * Customers pass `onLifecycleEvent` in WrapOptions to receive typed events
5
+ * at each stage of a request lifecycle. The default is no-op — not providing
6
+ * the callback adds zero overhead to the hot path.
7
+ *
8
+ * What's included in every event:
9
+ * - provider, model, operation — request identity
10
+ * - attemptNumber — 1-based; increments on each retry
11
+ * - elapsedMs — wall-clock time since the first attempt began
12
+ *
13
+ * What is never included:
14
+ * - prompt / completion content
15
+ * - request or response bodies
16
+ * - credentials
17
+ *
18
+ * The five event kinds map to the lifecycle stages:
19
+ *
20
+ * before_request fires before each attempt (including retried ones)
21
+ * after_response fires when a request completes successfully
22
+ * on_retry fires when a request is going to be retried, with the
23
+ * reason and how long the SDK will wait before the next
24
+ * attempt
25
+ * on_error fires when the SDK gives up (all retries exhausted or
26
+ * a non-retriable error occurred)
27
+ * on_fallback fires when the circuit breaker opens and the SDK routes
28
+ * to the provider directly instead of the proxy
29
+ */
30
+ export type Provider = "openai" | "anthropic";
31
+ /** Common fields shared by every lifecycle event. */
32
+ export interface TelemetryBase {
33
+ provider: Provider;
34
+ model: string;
35
+ operation: string;
36
+ attemptNumber: number;
37
+ /** Wall-clock milliseconds since the first attempt for this call began. */
38
+ elapsedMs: number;
39
+ }
40
+ export interface BeforeRequestEvent extends TelemetryBase {
41
+ kind: "before_request";
42
+ }
43
+ export interface AfterResponseEvent extends TelemetryBase {
44
+ kind: "after_response";
45
+ status: number;
46
+ /** Request ID from the X-TC-Request-ID header, when the proxy set it. */
47
+ requestId: string | null;
48
+ }
49
+ export interface OnRetryEvent extends TelemetryBase {
50
+ kind: "on_retry";
51
+ reason: "5xx" | "network_error" | "quota_429";
52
+ status: number | null;
53
+ /** How many milliseconds the SDK will wait before the next attempt. */
54
+ delayMs: number;
55
+ }
56
+ export interface OnErrorEvent extends TelemetryBase {
57
+ kind: "on_error";
58
+ errorClass: string;
59
+ status: number | null;
60
+ }
61
+ export interface OnFallbackEvent extends TelemetryBase {
62
+ kind: "on_fallback";
63
+ /** Number of consecutive proxy failures that tripped the circuit. */
64
+ consecutiveFailures: number;
65
+ }
66
+ export type LifecycleEvent = BeforeRequestEvent | AfterResponseEvent | OnRetryEvent | OnErrorEvent | OnFallbackEvent;
67
+ export type LifecycleEventCallback = (event: LifecycleEvent) => void;
68
+ /** Call `cb` if defined — errors in the callback are always swallowed. */
69
+ export declare function emitEvent(cb: LifecycleEventCallback | undefined, event: LifecycleEvent): void;
70
+ //# sourceMappingURL=telemetry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telemetry.d.ts","sourceRoot":"","sources":["../src/telemetry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG,WAAW,CAAC;AAE9C,qDAAqD;AACrD,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,QAAQ,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,2EAA2E;IAC3E,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,kBAAmB,SAAQ,aAAa;IACvD,IAAI,EAAE,gBAAgB,CAAC;CACxB;AAED,MAAM,WAAW,kBAAmB,SAAQ,aAAa;IACvD,IAAI,EAAE,gBAAgB,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,yEAAyE;IACzE,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,MAAM,WAAW,YAAa,SAAQ,aAAa;IACjD,IAAI,EAAE,UAAU,CAAC;IACjB,MAAM,EAAE,KAAK,GAAG,eAAe,GAAG,WAAW,CAAC;IAC9C,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,uEAAuE;IACvE,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAa,SAAQ,aAAa;IACjD,IAAI,EAAE,UAAU,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED,MAAM,WAAW,eAAgB,SAAQ,aAAa;IACpD,IAAI,EAAE,aAAa,CAAC;IACpB,qEAAqE;IACrE,mBAAmB,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,MAAM,cAAc,GACtB,kBAAkB,GAClB,kBAAkB,GAClB,YAAY,GACZ,YAAY,GACZ,eAAe,CAAC;AAEpB,MAAM,MAAM,sBAAsB,GAAG,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;AAErE,0EAA0E;AAC1E,wBAAgB,SAAS,CACvB,EAAE,EAAE,sBAAsB,GAAG,SAAS,EACtC,KAAK,EAAE,cAAc,GACpB,IAAI,CAON"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Telemetry hook types for the TensorCost Node SDK.
3
+ *
4
+ * Customers pass `onLifecycleEvent` in WrapOptions to receive typed events
5
+ * at each stage of a request lifecycle. The default is no-op — not providing
6
+ * the callback adds zero overhead to the hot path.
7
+ *
8
+ * What's included in every event:
9
+ * - provider, model, operation — request identity
10
+ * - attemptNumber — 1-based; increments on each retry
11
+ * - elapsedMs — wall-clock time since the first attempt began
12
+ *
13
+ * What is never included:
14
+ * - prompt / completion content
15
+ * - request or response bodies
16
+ * - credentials
17
+ *
18
+ * The five event kinds map to the lifecycle stages:
19
+ *
20
+ * before_request fires before each attempt (including retried ones)
21
+ * after_response fires when a request completes successfully
22
+ * on_retry fires when a request is going to be retried, with the
23
+ * reason and how long the SDK will wait before the next
24
+ * attempt
25
+ * on_error fires when the SDK gives up (all retries exhausted or
26
+ * a non-retriable error occurred)
27
+ * on_fallback fires when the circuit breaker opens and the SDK routes
28
+ * to the provider directly instead of the proxy
29
+ */
30
+ /** Call `cb` if defined — errors in the callback are always swallowed. */
31
+ export function emitEvent(cb, event) {
32
+ if (!cb)
33
+ return;
34
+ try {
35
+ cb(event);
36
+ }
37
+ catch {
38
+ // Customer callbacks must never blow up the SDK's control flow.
39
+ }
40
+ }
41
+ //# sourceMappingURL=telemetry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telemetry.js","sourceRoot":"","sources":["../src/telemetry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAsDH,0EAA0E;AAC1E,MAAM,UAAU,SAAS,CACvB,EAAsC,EACtC,KAAqB;IAErB,IAAI,CAAC,EAAE;QAAE,OAAO;IAChB,IAAI,CAAC;QACH,EAAE,CAAC,KAAK,CAAC,CAAC;IACZ,CAAC;IAAC,MAAM,CAAC;QACP,gEAAgE;IAClE,CAAC;AACH,CAAC"}
package/dist/types.d.ts CHANGED
@@ -5,8 +5,10 @@
5
5
  * the customer's client type, so most provider shapes are duck-typed
6
6
  * at runtime rather than expressed in TypeScript.
7
7
  */
8
- export type Provider = "openai" | "anthropic";
9
- export type Operation = "chat.completions" | "completions" | "messages";
8
+ import type { LifecycleEventCallback } from "./telemetry.js";
9
+ import type { RetryConfig } from "./retry.js";
10
+ export type Provider = "openai" | "anthropic" | "bedrock";
11
+ export type Operation = "chat.completions" | "completions" | "messages" | "bedrock.invoke_model" | "bedrock.converse" | "bedrock.invoke_model_stream" | "bedrock.converse_stream";
10
12
  export type ObservationStatus = "success" | "error";
11
13
  /**
12
14
  * Wire shape posted to `POST /api/proxy/observation`. Mirrors the
@@ -112,6 +114,59 @@ export interface WrapOptions {
112
114
  * Example: `https://proxy.tensorcost.com`
113
115
  */
114
116
  proxyUrl?: string;
117
+ /**
118
+ * Retry configuration. Applies to applied-mode proxy requests only.
119
+ * Observe-only telemetry posts do not retry (they are fire-and-forget).
120
+ *
121
+ * Defaults: maxAttempts=5, baseDelayMs=500, maxDelayMs=30 000.
122
+ * Set `maxAttempts: 1` to disable retries entirely.
123
+ */
124
+ retry?: Partial<RetryConfig>;
125
+ /**
126
+ * Timeout for non-streaming proxy requests, in milliseconds.
127
+ * Default: 60 000 (60s). Surfaces as `TensorCostTimeoutError` (kind: "total").
128
+ */
129
+ timeoutMs?: number;
130
+ /**
131
+ * Idle timeout for streaming proxy requests (no bytes received for N ms).
132
+ * Default: 30 000 (30s). Surfaces as `TensorCostTimeoutError` (kind: "idle").
133
+ */
134
+ idleTimeoutMs?: number;
135
+ /**
136
+ * Optional lifecycle event callback. Fires on `before_request`,
137
+ * `after_response`, `on_retry`, `on_error`, and `on_fallback`.
138
+ * The payload contains only request metadata — never prompt / completion
139
+ * content or credentials. Errors in the callback are swallowed.
140
+ */
141
+ onLifecycleEvent?: LifecycleEventCallback;
142
+ /**
143
+ * Fail-open circuit breaker for the proxy. When the proxy returns 5xx
144
+ * for `openThreshold` consecutive calls (default 3), the SDK routes
145
+ * subsequent calls directly to the upstream provider, bypassing the
146
+ * proxy. The circuit closes again after `closeThreshold` consecutive
147
+ * successful probe calls (default 5).
148
+ *
149
+ * Default: true (enabled). Set to false when running in-VPC with a
150
+ * sidecar proxy where direct-provider access is not available.
151
+ *
152
+ * When false, proxy 5xx errors surface as `TensorCostProxyError` with
153
+ * no automatic fallback.
154
+ *
155
+ * The fallback requires that the customer's provider SDK client has its
156
+ * original credentials intact (pre-wrap). If the client credentials are
157
+ * not available, the SDK throws `TensorCostProxyError` instead.
158
+ */
159
+ failOpenEnabled?: boolean;
160
+ /**
161
+ * Provider API key used for the fail-open direct-provider fallback.
162
+ * Only relevant when `failOpenEnabled=true` and `appliedMode=true`.
163
+ *
164
+ * In most cases the SDK reads the provider key directly from the wrapped
165
+ * client (client.apiKey), so this is not needed. Supply it explicitly if
166
+ * the client exposes the key under a non-standard property name, or if
167
+ * you are using the SDK in a context where the client's key is unavailable.
168
+ */
169
+ providerApiKey?: string;
115
170
  }
116
171
  export interface ResolvedConfig {
117
172
  apiKey: string;
@@ -139,5 +194,11 @@ export interface ResolvedConfig {
139
194
  * a config with `appliedMode=true` and `proxyUrl=null`.
140
195
  */
141
196
  proxyUrl: string | null;
197
+ retry: RetryConfig;
198
+ timeoutMs: number;
199
+ idleTimeoutMs: number;
200
+ onLifecycleEvent: LifecycleEventCallback | undefined;
201
+ failOpenEnabled: boolean;
202
+ providerApiKey: string | null;
142
203
  }
143
204
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG,WAAW,CAAC;AAE9C,MAAM,MAAM,SAAS,GAAG,kBAAkB,GAAG,aAAa,GAAG,UAAU,CAAC;AAExE,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,OAAO,CAAC;AAEpD;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,QAAQ,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,SAAS,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,MAAM,EAAE,iBAAiB,CAAC;IAC1B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;;OAQG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,WAAW;IAC1B,yEAAyE;IACzE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2EAA2E;IAC3E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;;;;OAQG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;;;;;;;;;;;;OAiBG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;;;;;;;;;;;OAcG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB;;;;;OAKG;IACH,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B;;;;OAIG;IACH,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,QAAQ,EAAE,OAAO,CAAC;IAClB,kCAAkC;IAClC,WAAW,EAAE,OAAO,CAAC;IACrB;;;;OAIG;IACH,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/wrap.d.ts CHANGED
@@ -10,6 +10,15 @@
10
10
  * two custom headers (`x-tc-provider-url`, `x-tc-provider-auth`) are
11
11
  * injected into `client.defaultHeaders`. The proxy handles per-request
12
12
  * routing decisions. Observe-only telemetry still fires post-call.
13
+ *
14
+ * Hardening (v0.4.0): applied-mode requests now go through the proxy-client
15
+ * module which adds retries, timeouts, typed errors, telemetry hooks, and
16
+ * the fail-open circuit breaker.
17
+ *
18
+ * Bedrock (v0.4.0): observe-only. Applied mode is not supported for Bedrock —
19
+ * wrap(bedrockClient, { appliedMode: true }) throws TensorCostConfigError.
20
+ * The Smithy middleware registered on client.middlewareStack intercepts
21
+ * every client.send() call transparently.
13
22
  */
14
23
  import type { WrapOptions } from "./types.js";
15
24
  export declare function wrap<T>(client: T, options?: WrapOptions): T;
@@ -1 +1 @@
1
- {"version":3,"file":"wrap.d.ts","sourceRoot":"","sources":["../src/wrap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAQH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,wBAAgB,IAAI,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,GAAE,WAAgB,GAAG,CAAC,CAyC/D"}
1
+ {"version":3,"file":"wrap.d.ts","sourceRoot":"","sources":["../src/wrap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAWH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,wBAAgB,IAAI,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,OAAO,GAAE,WAAgB,GAAG,CAAC,CAqF/D"}