@tensorcost/sdk 0.3.0 → 0.3.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 +74 -0
- package/README.md +89 -1
- package/dist/circuit.d.ts +56 -0
- package/dist/circuit.d.ts.map +1 -0
- package/dist/circuit.js +93 -0
- package/dist/circuit.js.map +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +17 -0
- package/dist/config.js.map +1 -1
- package/dist/errors.d.ts +67 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +84 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/providers/anthropic.d.ts +17 -1
- package/dist/providers/anthropic.d.ts.map +1 -1
- package/dist/providers/anthropic.js +179 -20
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/openai.d.ts +19 -1
- package/dist/providers/openai.d.ts.map +1 -1
- package/dist/providers/openai.js +217 -20
- package/dist/providers/openai.js.map +1 -1
- package/dist/proxy-client.d.ts +64 -0
- package/dist/proxy-client.d.ts.map +1 -0
- package/dist/proxy-client.js +259 -0
- package/dist/proxy-client.js.map +1 -0
- package/dist/retry.d.ts +50 -0
- package/dist/retry.d.ts.map +1 -0
- package/dist/retry.js +80 -0
- package/dist/retry.js.map +1 -0
- package/dist/telemetry.d.ts +70 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +41 -0
- package/dist/telemetry.js.map +1 -0
- package/dist/types.d.ts +61 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/wrap.d.ts +4 -0
- package/dist/wrap.d.ts.map +1 -1
- package/dist/wrap.js +26 -2
- package/dist/wrap.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,64 @@
|
|
|
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 } from "./errors.js";
|
|
33
|
+
import type { RetryConfig } from "./retry.js";
|
|
34
|
+
import type { CircuitBreaker } from "./circuit.js";
|
|
35
|
+
import type { LifecycleEventCallback } from "./telemetry.js";
|
|
36
|
+
import type { Provider } from "./telemetry.js";
|
|
37
|
+
export interface ProxyRequestOptions {
|
|
38
|
+
proxyUrl: string;
|
|
39
|
+
bearerToken: string;
|
|
40
|
+
providerUrl: string;
|
|
41
|
+
providerAuth: string;
|
|
42
|
+
body: Record<string, unknown>;
|
|
43
|
+
model: string;
|
|
44
|
+
provider: Provider;
|
|
45
|
+
operation: string;
|
|
46
|
+
correlationId: string;
|
|
47
|
+
environment: string | null;
|
|
48
|
+
retry?: RetryConfig;
|
|
49
|
+
timeoutMs?: number;
|
|
50
|
+
onLifecycleEvent?: LifecycleEventCallback;
|
|
51
|
+
circuit: CircuitBreaker;
|
|
52
|
+
/** Injected fetch implementation (for testing). Defaults to globalThis.fetch. */
|
|
53
|
+
fetchImpl?: typeof fetch;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Execute a single proxied request with retries, timeouts, telemetry,
|
|
57
|
+
* and circuit-breaker integration.
|
|
58
|
+
*
|
|
59
|
+
* Throws a `TensorCostError` subclass on any failure.
|
|
60
|
+
* Returns the parsed JSON body on success.
|
|
61
|
+
*/
|
|
62
|
+
export declare function proxyRequest(opts: ProxyRequestOptions): Promise<unknown>;
|
|
63
|
+
export { TensorCostError };
|
|
64
|
+
//# sourceMappingURL=proxy-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proxy-client.d.ts","sourceRoot":"","sources":["../src/proxy-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,EACL,eAAe,EAMhB,MAAM,aAAa,CAAC;AAQrB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAEnD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAC7D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE/C,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,QAAQ,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAE3B,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,sBAAsB,CAAC;IAC1C,OAAO,EAAE,cAAc,CAAC;IAExB,iFAAiF;IACjF,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;CAC1B;AAID;;;;;;GAMG;AACH,wBAAsB,YAAY,CAChC,IAAI,EAAE,mBAAmB,GACxB,OAAO,CAAC,OAAO,CAAC,CAiQlB;AAED,OAAO,EAAE,eAAe,EAAE,CAAC"}
|
|
@@ -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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"}
|
package/dist/retry.d.ts
ADDED
|
@@ -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"}
|