workers-ai-provider 3.2.1 → 3.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/README.md +28 -19
- package/dist/anthropic.d.mts +1 -1
- package/dist/{gateway-delegate-BfaUTwDZ.d.mts → gateway-delegate-D_zkIp5r.d.mts} +90 -15
- package/dist/{gateway-provider-1USFWm7c.mjs → gateway-provider-CQU-v2IO.mjs} +562 -170
- package/dist/gateway-provider-CQU-v2IO.mjs.map +1 -0
- package/dist/gateway-provider.mjs +1 -1
- package/dist/google.d.mts +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +348 -284
- package/dist/index.mjs.map +1 -1
- package/dist/openai.d.mts +1 -1
- package/package.json +5 -4
- package/src/gateway-delegate.ts +318 -89
- package/src/gateway-provider.ts +10 -34
- package/src/gateway-providers.ts +13 -455
- package/src/index.ts +64 -20
- package/src/resumable-stream.ts +9 -221
- package/src/streaming.ts +1 -41
- package/src/utils.ts +40 -125
- package/src/workersai-chat-language-model.ts +37 -16
- package/src/workersai-embedding-model.ts +22 -11
- package/src/workersai-error.ts +70 -0
- package/src/workersai-image-model.ts +19 -11
- package/src/workersai-reranking-model.ts +16 -5
- package/src/workersai-speech-model.ts +35 -13
- package/src/workersai-transcription-model.ts +15 -4
- package/dist/gateway-provider-1USFWm7c.mjs.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//#region \0@oxc-project+runtime@0.
|
|
1
|
+
//#region \0@oxc-project+runtime@0.137.0/helpers/esm/typeof.js
|
|
2
2
|
function _typeof(o) {
|
|
3
3
|
"@babel/helpers - typeof";
|
|
4
4
|
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(o) {
|
|
@@ -8,7 +8,7 @@ function _typeof(o) {
|
|
|
8
8
|
}, _typeof(o);
|
|
9
9
|
}
|
|
10
10
|
//#endregion
|
|
11
|
-
//#region \0@oxc-project+runtime@0.
|
|
11
|
+
//#region \0@oxc-project+runtime@0.137.0/helpers/esm/toPrimitive.js
|
|
12
12
|
function toPrimitive(t, r) {
|
|
13
13
|
if ("object" != _typeof(t) || !t) return t;
|
|
14
14
|
var e = t[Symbol.toPrimitive];
|
|
@@ -20,13 +20,13 @@ function toPrimitive(t, r) {
|
|
|
20
20
|
return ("string" === r ? String : Number)(t);
|
|
21
21
|
}
|
|
22
22
|
//#endregion
|
|
23
|
-
//#region \0@oxc-project+runtime@0.
|
|
23
|
+
//#region \0@oxc-project+runtime@0.137.0/helpers/esm/toPropertyKey.js
|
|
24
24
|
function toPropertyKey(t) {
|
|
25
25
|
var i = toPrimitive(t, "string");
|
|
26
26
|
return "symbol" == _typeof(i) ? i : i + "";
|
|
27
27
|
}
|
|
28
28
|
//#endregion
|
|
29
|
-
//#region \0@oxc-project+runtime@0.
|
|
29
|
+
//#region \0@oxc-project+runtime@0.137.0/helpers/esm/defineProperty.js
|
|
30
30
|
function _defineProperty(e, r, t) {
|
|
31
31
|
return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
|
|
32
32
|
value: t,
|
|
@@ -36,148 +36,85 @@ function _defineProperty(e, r, t) {
|
|
|
36
36
|
}) : e[r] = t, e;
|
|
37
37
|
}
|
|
38
38
|
//#endregion
|
|
39
|
-
//#region src/errors.ts
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if (status === 401 || status === 403) return {
|
|
43
|
-
code: "auth",
|
|
44
|
-
recoverable: false
|
|
45
|
-
};
|
|
46
|
-
if (status === 429) return {
|
|
47
|
-
code: "rate-limit",
|
|
48
|
-
recoverable: true
|
|
49
|
-
};
|
|
50
|
-
if (status === 404) return {
|
|
51
|
-
code: "not-found",
|
|
52
|
-
recoverable: false
|
|
53
|
-
};
|
|
54
|
-
if (status === 400 || status === 422) return {
|
|
55
|
-
code: "bad-request",
|
|
56
|
-
recoverable: false
|
|
57
|
-
};
|
|
58
|
-
if (status >= 500) return {
|
|
59
|
-
code: "provider-error",
|
|
60
|
-
recoverable: true
|
|
61
|
-
};
|
|
62
|
-
return {
|
|
63
|
-
code: "unknown",
|
|
64
|
-
recoverable: false
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
/** Best-effort extraction of a human message from a CF/provider error envelope. */
|
|
68
|
-
function extractErrorMessage(raw) {
|
|
69
|
-
if (typeof raw === "string") {
|
|
70
|
-
const trimmed = raw.trim();
|
|
71
|
-
if (!trimmed) return void 0;
|
|
72
|
-
try {
|
|
73
|
-
return extractErrorMessage(JSON.parse(trimmed));
|
|
74
|
-
} catch {
|
|
75
|
-
return trimmed.slice(0, 500);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
if (!raw || typeof raw !== "object") return void 0;
|
|
79
|
-
const obj = raw;
|
|
80
|
-
if (Array.isArray(obj.errors) && obj.errors.length > 0) {
|
|
81
|
-
const first = obj.errors[0];
|
|
82
|
-
if (typeof first?.message === "string") return first.message;
|
|
83
|
-
}
|
|
84
|
-
if (obj.error && typeof obj.error === "object") {
|
|
85
|
-
const err = obj.error;
|
|
86
|
-
if (typeof err.message === "string") return err.message;
|
|
87
|
-
}
|
|
88
|
-
if (typeof obj.error === "string") return obj.error;
|
|
89
|
-
if (typeof obj.message === "string") return obj.message;
|
|
90
|
-
}
|
|
91
|
-
/** A single dispatch failure through AI Gateway (run or gateway path). */
|
|
92
|
-
var WorkersAIGatewayError = class WorkersAIGatewayError extends Error {
|
|
93
|
-
constructor(code, message, opts = {}) {
|
|
39
|
+
//#region ../gateway-core/src/errors.ts
|
|
40
|
+
var GatewayDelegateError = class extends Error {
|
|
41
|
+
constructor(kind, message, cause) {
|
|
94
42
|
super(message);
|
|
95
|
-
_defineProperty(this, "
|
|
96
|
-
_defineProperty(this, "recoverable", void 0);
|
|
97
|
-
_defineProperty(this, "status", void 0);
|
|
98
|
-
_defineProperty(this, "context", void 0);
|
|
99
|
-
_defineProperty(this, "raw", void 0);
|
|
43
|
+
_defineProperty(this, "kind", void 0);
|
|
100
44
|
_defineProperty(this, "cause", void 0);
|
|
101
|
-
this.name = "
|
|
102
|
-
this.
|
|
103
|
-
this.
|
|
104
|
-
this.status = opts.status ?? null;
|
|
105
|
-
this.context = opts.context ?? {};
|
|
106
|
-
this.raw = opts.raw;
|
|
107
|
-
this.cause = opts.cause;
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* Classify an arbitrary thrown value. Understands AI SDK `APICallError`
|
|
111
|
-
* (reads `statusCode` / `responseBody` / `isRetryable`); falls back to a
|
|
112
|
-
* recoverable `gateway-error` for transport/connection failures so a fallback
|
|
113
|
-
* chain keeps trying.
|
|
114
|
-
*/
|
|
115
|
-
static fromUnknown(e) {
|
|
116
|
-
if (e instanceof WorkersAIGatewayError) return e;
|
|
117
|
-
const obj = e && typeof e === "object" ? e : {};
|
|
118
|
-
const status = typeof obj.statusCode === "number" ? obj.statusCode : null;
|
|
119
|
-
const responseBody = typeof obj.responseBody === "string" ? obj.responseBody : void 0;
|
|
120
|
-
if (status !== null) {
|
|
121
|
-
const classified = classifyStatus(status);
|
|
122
|
-
const recoverable = typeof obj.isRetryable === "boolean" ? obj.isRetryable : classified.recoverable;
|
|
123
|
-
const message = extractErrorMessage(responseBody) ?? (e instanceof Error ? e.message : `Gateway dispatch failed (HTTP ${status}).`);
|
|
124
|
-
let raw = responseBody;
|
|
125
|
-
try {
|
|
126
|
-
raw = responseBody ? JSON.parse(responseBody) : responseBody;
|
|
127
|
-
} catch {}
|
|
128
|
-
return new WorkersAIGatewayError(classified.code, message, {
|
|
129
|
-
recoverable,
|
|
130
|
-
status,
|
|
131
|
-
raw,
|
|
132
|
-
cause: e
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
return new WorkersAIGatewayError("gateway-error", e instanceof Error ? e.message : String(e), {
|
|
136
|
-
recoverable: true,
|
|
137
|
-
cause: e
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
/** Build from an HTTP `Response` (reads the body for the envelope). */
|
|
141
|
-
static async fromResponse(resp, context = {}) {
|
|
142
|
-
const text = await resp.text().catch(() => "");
|
|
143
|
-
const { code, recoverable } = classifyStatus(resp.status);
|
|
144
|
-
const message = extractErrorMessage(text) ?? `Gateway dispatch failed (HTTP ${resp.status}).`;
|
|
145
|
-
let raw = text;
|
|
146
|
-
try {
|
|
147
|
-
raw = text ? JSON.parse(text) : text;
|
|
148
|
-
} catch {}
|
|
149
|
-
return new WorkersAIGatewayError(code, message, {
|
|
150
|
-
recoverable,
|
|
151
|
-
status: resp.status,
|
|
152
|
-
raw,
|
|
153
|
-
context: {
|
|
154
|
-
...context,
|
|
155
|
-
status: resp.status,
|
|
156
|
-
logId: resp.headers.get("cf-aig-log-id"),
|
|
157
|
-
runId: resp.headers.get("cf-aig-run-id")
|
|
158
|
-
}
|
|
159
|
-
});
|
|
45
|
+
this.name = "GatewayDelegateError";
|
|
46
|
+
this.kind = kind;
|
|
47
|
+
this.cause = cause;
|
|
160
48
|
}
|
|
161
49
|
};
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
50
|
+
//#endregion
|
|
51
|
+
//#region ../gateway-core/src/gateway-fetch.ts
|
|
52
|
+
/** JSON-encode metadata for the `cf-aig-metadata` header (`bigint` → string). */
|
|
53
|
+
function serializeMetadata(metadata) {
|
|
54
|
+
return JSON.stringify(metadata, (_k, v) => typeof v === "bigint" ? v.toString() : v);
|
|
55
|
+
}
|
|
56
|
+
/** Hop-by-hop headers that must never be forwarded to the gateway. */
|
|
57
|
+
const STRIP_HEADERS_BASE = ["content-length", "host"];
|
|
58
|
+
/** Normalize any `HeadersInit` shape into a plain object. */
|
|
59
|
+
function headersToObject(h) {
|
|
60
|
+
const out = {};
|
|
61
|
+
if (!h) return out;
|
|
62
|
+
if (h instanceof Headers) for (const [k, v] of h) out[k] = v;
|
|
63
|
+
else if (Array.isArray(h)) for (const [k, v] of h) out[k] = v;
|
|
64
|
+
else Object.assign(out, h);
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
/** Best-effort decode of a request body to text for re-parsing as JSON. */
|
|
68
|
+
function asText(body) {
|
|
69
|
+
if (typeof body === "string") return body;
|
|
70
|
+
if (body instanceof Uint8Array) return new TextDecoder().decode(body);
|
|
71
|
+
if (body instanceof ArrayBuffer) return new TextDecoder().decode(body);
|
|
72
|
+
return "{}";
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Set the `cf-aig-*` cache/log/request headers on `headers` for every option
|
|
76
|
+
* that is defined. Mutates `headers` in place (callers pass the entry's header
|
|
77
|
+
* object).
|
|
78
|
+
*/
|
|
79
|
+
function applyGatewayCacheHeaders(headers, opts) {
|
|
80
|
+
if (opts.cacheTtl !== void 0) headers["cf-aig-cache-ttl"] = String(opts.cacheTtl);
|
|
81
|
+
if (opts.skipCache) headers["cf-aig-skip-cache"] = "true";
|
|
82
|
+
if (opts.cacheKey !== void 0) headers["cf-aig-cache-key"] = opts.cacheKey;
|
|
83
|
+
if (opts.metadata) headers["cf-aig-metadata"] = serializeMetadata(opts.metadata);
|
|
84
|
+
if (opts.collectLog !== void 0) headers["cf-aig-collect-log"] = String(opts.collectLog);
|
|
85
|
+
if (opts.eventId !== void 0) headers["cf-aig-event-id"] = opts.eventId;
|
|
86
|
+
if (opts.requestTimeoutMs !== void 0) headers["cf-aig-request-timeout"] = String(opts.requestTimeoutMs);
|
|
87
|
+
if (opts.byokAlias !== void 0) headers["cf-aig-byok-alias"] = opts.byokAlias;
|
|
88
|
+
if (opts.zdr !== void 0) headers["cf-aig-zdr"] = String(opts.zdr);
|
|
89
|
+
if (opts.retries) {
|
|
90
|
+
const { maxAttempts, retryDelayMs, backoff } = opts.retries;
|
|
91
|
+
if (maxAttempts !== void 0) headers["cf-aig-max-attempts"] = String(maxAttempts);
|
|
92
|
+
if (retryDelayMs !== void 0) headers["cf-aig-retry-delay"] = String(retryDelayMs);
|
|
93
|
+
if (backoff !== void 0) headers["cf-aig-backoff"] = backoff;
|
|
177
94
|
}
|
|
178
|
-
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Assemble a single gateway entry: strip hop-by-hop + provider-auth headers,
|
|
98
|
+
* layer on `extraHeaders` and `cf-aig-*` cache headers, and attach the body as
|
|
99
|
+
* `query`. Endpoint derivation stays with the caller because the gateway-path
|
|
100
|
+
* (registry host-strip) and the REST path (`/v1/` strip) differ.
|
|
101
|
+
*/
|
|
102
|
+
function buildGatewayEntry(params) {
|
|
103
|
+
const strip = new Set(STRIP_HEADERS_BASE);
|
|
104
|
+
if (params.stripAuthHeaders) for (const h of params.stripAuthHeaders) strip.add(h.toLowerCase());
|
|
105
|
+
const headers = {};
|
|
106
|
+
for (const [k, v] of Object.entries(headersToObject(params.initHeaders))) if (!strip.has(k.toLowerCase())) headers[k] = v;
|
|
107
|
+
if (params.extraHeaders) Object.assign(headers, params.extraHeaders);
|
|
108
|
+
if (params.cache) applyGatewayCacheHeaders(headers, params.cache);
|
|
109
|
+
return {
|
|
110
|
+
provider: params.providerId,
|
|
111
|
+
endpoint: params.endpoint,
|
|
112
|
+
headers,
|
|
113
|
+
query: params.body
|
|
114
|
+
};
|
|
115
|
+
}
|
|
179
116
|
//#endregion
|
|
180
|
-
//#region src/gateway-providers.ts
|
|
117
|
+
//#region ../gateway-core/src/gateway-providers.ts
|
|
181
118
|
/** Strip a leading `https://<host>/` prefix, leaving the endpoint path + query. */
|
|
182
119
|
function hostStrip(pattern) {
|
|
183
120
|
return (url) => url.replace(pattern, "");
|
|
@@ -309,8 +246,8 @@ const GATEWAY_PROVIDERS = [
|
|
|
309
246
|
gatewayProviderId: "deepseek",
|
|
310
247
|
wireFormat: "openai",
|
|
311
248
|
baseURL: "https://api.deepseek.com",
|
|
312
|
-
runCatalog:
|
|
313
|
-
billing: "
|
|
249
|
+
runCatalog: true,
|
|
250
|
+
billing: "unified",
|
|
314
251
|
authHeaders: ["authorization"],
|
|
315
252
|
hostPattern: DEEPSEEK_HOST,
|
|
316
253
|
transformEndpoint: hostStrip(DEEPSEEK_HOST)
|
|
@@ -498,22 +435,478 @@ function wireableProviders() {
|
|
|
498
435
|
return GATEWAY_PROVIDERS.filter((p) => p.wireFormat !== void 0);
|
|
499
436
|
}
|
|
500
437
|
//#endregion
|
|
501
|
-
//#region src/
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
if (body instanceof ArrayBuffer) return new TextDecoder().decode(body);
|
|
507
|
-
return "{}";
|
|
508
|
-
}
|
|
509
|
-
function headersToObject(h) {
|
|
510
|
-
const out = {};
|
|
511
|
-
if (!h) return out;
|
|
512
|
-
if (h instanceof Headers) for (const [k, v] of h) out[k] = v;
|
|
513
|
-
else if (Array.isArray(h)) for (const [k, v] of h) out[k] = v;
|
|
514
|
-
else Object.assign(out, h);
|
|
438
|
+
//#region ../gateway-core/src/resumable-stream.ts
|
|
439
|
+
function concat(a, b) {
|
|
440
|
+
const out = new Uint8Array(new ArrayBuffer(a.length + b.length));
|
|
441
|
+
out.set(a, 0);
|
|
442
|
+
out.set(b, a.length);
|
|
515
443
|
return out;
|
|
516
444
|
}
|
|
445
|
+
/** Index just past the last `\n\n` in `buf`, or -1 if there is no complete event. */
|
|
446
|
+
function lastEventBoundary(buf) {
|
|
447
|
+
for (let i = buf.length - 2; i >= 0; i--) if (buf[i] === 10 && buf[i + 1] === 10) return i + 2;
|
|
448
|
+
return -1;
|
|
449
|
+
}
|
|
450
|
+
/** Count of `\n\n` terminators (= complete SSE events) in `buf`. */
|
|
451
|
+
function countEvents(buf) {
|
|
452
|
+
let n = 0;
|
|
453
|
+
for (let i = 0; i + 1 < buf.length; i++) if (buf[i] === 10 && buf[i + 1] === 10) {
|
|
454
|
+
n++;
|
|
455
|
+
i++;
|
|
456
|
+
}
|
|
457
|
+
return n;
|
|
458
|
+
}
|
|
459
|
+
function resumeUrl(gateway, runId, from) {
|
|
460
|
+
return `https://workers-binding.ai/ai-gateway/gateways/${gateway}/run/${runId}/resume?from=${from}`;
|
|
461
|
+
}
|
|
462
|
+
function createResumableStream(options) {
|
|
463
|
+
const { binding, gateway, runId } = options;
|
|
464
|
+
const maxReconnects = options.maxReconnects ?? 5;
|
|
465
|
+
const onExpired = options.onResumeExpired ?? "error";
|
|
466
|
+
let emittedEvents = options.fromEvent ?? 0;
|
|
467
|
+
let pending = new Uint8Array(/* @__PURE__ */ new ArrayBuffer(0));
|
|
468
|
+
let reconnects = 0;
|
|
469
|
+
let canceled = false;
|
|
470
|
+
let activeReader = null;
|
|
471
|
+
const isAborted = () => canceled || options.signal?.aborted === true;
|
|
472
|
+
async function fetchResume(controller) {
|
|
473
|
+
let res;
|
|
474
|
+
try {
|
|
475
|
+
res = await binding.fetch(resumeUrl(gateway, runId, emittedEvents), {
|
|
476
|
+
method: "GET",
|
|
477
|
+
signal: options.signal
|
|
478
|
+
});
|
|
479
|
+
} catch (fetchErr) {
|
|
480
|
+
controller.error(new GatewayDelegateError("dispatch", `Resume request threw at event ${emittedEvents}.`, fetchErr));
|
|
481
|
+
return null;
|
|
482
|
+
}
|
|
483
|
+
if (res.status === 404) {
|
|
484
|
+
if (onExpired === "accept-partial") {
|
|
485
|
+
controller.close();
|
|
486
|
+
return null;
|
|
487
|
+
}
|
|
488
|
+
controller.error(new GatewayDelegateError("resume-expired", `Resume buffer expired (404) at event ${emittedEvents}. The gateway buffer TTL (~5.5 min) elapsed; fall back to continuation or regeneration.`));
|
|
489
|
+
return null;
|
|
490
|
+
}
|
|
491
|
+
if (!res.ok || !res.body) {
|
|
492
|
+
controller.error(new GatewayDelegateError("dispatch", `Resume failed (${res.status}) at event ${emittedEvents}.`));
|
|
493
|
+
return null;
|
|
494
|
+
}
|
|
495
|
+
return res.body;
|
|
496
|
+
}
|
|
497
|
+
return new ReadableStream({
|
|
498
|
+
async start(controller) {
|
|
499
|
+
let current;
|
|
500
|
+
if (options.initial) current = options.initial;
|
|
501
|
+
else {
|
|
502
|
+
const body = await fetchResume(controller);
|
|
503
|
+
if (!body) return;
|
|
504
|
+
current = body;
|
|
505
|
+
}
|
|
506
|
+
for (;;) {
|
|
507
|
+
const reader = current.getReader();
|
|
508
|
+
activeReader = reader;
|
|
509
|
+
try {
|
|
510
|
+
for (;;) {
|
|
511
|
+
const { done, value } = await reader.read();
|
|
512
|
+
if (done) {
|
|
513
|
+
if (pending.length > 0) {
|
|
514
|
+
controller.enqueue(pending);
|
|
515
|
+
pending = new Uint8Array(/* @__PURE__ */ new ArrayBuffer(0));
|
|
516
|
+
}
|
|
517
|
+
controller.close();
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
if (!value || value.length === 0) continue;
|
|
521
|
+
pending = concat(pending, value);
|
|
522
|
+
const boundary = lastEventBoundary(pending);
|
|
523
|
+
if (boundary > 0) {
|
|
524
|
+
const complete = pending.slice(0, boundary);
|
|
525
|
+
controller.enqueue(complete);
|
|
526
|
+
emittedEvents += countEvents(complete);
|
|
527
|
+
options.onProgress?.(emittedEvents);
|
|
528
|
+
pending = pending.slice(boundary);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
} catch (err) {
|
|
532
|
+
try {
|
|
533
|
+
reader.releaseLock();
|
|
534
|
+
} catch {}
|
|
535
|
+
if (isAborted()) return;
|
|
536
|
+
if (reconnects >= maxReconnects) {
|
|
537
|
+
controller.error(new GatewayDelegateError("resume-expired", `Exceeded ${maxReconnects} reconnect attempts at event ${emittedEvents}.`, err));
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
pending = new Uint8Array(/* @__PURE__ */ new ArrayBuffer(0));
|
|
541
|
+
reconnects++;
|
|
542
|
+
options.onReconnect?.(emittedEvents, reconnects);
|
|
543
|
+
const body = await fetchResume(controller);
|
|
544
|
+
if (!body) return;
|
|
545
|
+
current = body;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
},
|
|
549
|
+
cancel(reason) {
|
|
550
|
+
canceled = true;
|
|
551
|
+
if (activeReader) activeReader.cancel(reason).catch(() => {});
|
|
552
|
+
}
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
//#endregion
|
|
556
|
+
//#region ../gateway-core/src/workers-ai.ts
|
|
557
|
+
/**
|
|
558
|
+
* Shared, framework-agnostic Workers AI helpers.
|
|
559
|
+
*
|
|
560
|
+
* These are the pieces that `workers-ai-provider` (native `LanguageModelV3`) and
|
|
561
|
+
* `@cloudflare/tanstack-ai` (OpenAI-SDK shim) both need: the SSE byte decoder,
|
|
562
|
+
* message normalization for the binding's stricter schema, response-text
|
|
563
|
+
* extraction across WAI's response shapes, and the gpt-oss forced-tool-call
|
|
564
|
+
* salvage.
|
|
565
|
+
*
|
|
566
|
+
* IMPORTANT — id/dependency decoupling: nothing here depends on the `ai` package
|
|
567
|
+
* or mints framework tool-call ids. `parseLeakedToolCalls` returns neutral
|
|
568
|
+
* `{ toolName, input }` records; each consumer assigns its own ids and adapts to
|
|
569
|
+
* its own tool-call shape. This keeps `gateway-core` free of an `ai` dependency.
|
|
570
|
+
*/
|
|
571
|
+
/**
|
|
572
|
+
* TransformStream that decodes a raw byte stream into SSE `data:` payloads.
|
|
573
|
+
* Each output chunk is the string content after `data: ` (one per SSE event),
|
|
574
|
+
* with line buffering for partial chunks.
|
|
575
|
+
*/
|
|
576
|
+
var SSEDecoder = class extends TransformStream {
|
|
577
|
+
constructor() {
|
|
578
|
+
let buffer = "";
|
|
579
|
+
const decoder = new TextDecoder();
|
|
580
|
+
const emit = (line, controller) => {
|
|
581
|
+
const trimmed = line.trim();
|
|
582
|
+
if (!trimmed) return;
|
|
583
|
+
if (trimmed.startsWith("data: ")) controller.enqueue(trimmed.slice(6));
|
|
584
|
+
else if (trimmed.startsWith("data:")) controller.enqueue(trimmed.slice(5));
|
|
585
|
+
};
|
|
586
|
+
super({
|
|
587
|
+
transform(chunk, controller) {
|
|
588
|
+
buffer += decoder.decode(chunk, { stream: true });
|
|
589
|
+
const lines = buffer.split("\n");
|
|
590
|
+
buffer = lines.pop() || "";
|
|
591
|
+
for (const line of lines) emit(line, controller);
|
|
592
|
+
},
|
|
593
|
+
flush(controller) {
|
|
594
|
+
if (buffer.trim()) emit(buffer, controller);
|
|
595
|
+
}
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
};
|
|
599
|
+
/**
|
|
600
|
+
* Normalize messages before passing to the Workers AI binding.
|
|
601
|
+
*
|
|
602
|
+
* The binding has strict schema validation that differs from the OpenAI API:
|
|
603
|
+
* `content` must not be `null`/`undefined` (coerced to `""`). Content arrays
|
|
604
|
+
* (image_url parts) pass through untouched for vision-capable models.
|
|
605
|
+
*/
|
|
606
|
+
function normalizeMessagesForBinding(messages) {
|
|
607
|
+
return messages.map((msg) => {
|
|
608
|
+
const normalized = { ...msg };
|
|
609
|
+
if (normalized.content === null || normalized.content === void 0) normalized.content = "";
|
|
610
|
+
return normalized;
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Extract text from a Workers AI response, handling multiple response shapes:
|
|
615
|
+
* - OpenAI format: `{ choices: [{ message: { content: "..." } }] }`
|
|
616
|
+
* - Native format: `{ response: "..." }`
|
|
617
|
+
* - Structured-output quirk: `{ response: { ... } }` (object) / `"{ ... }"` (JSON string)
|
|
618
|
+
* - Numeric `{ response: 42 }`
|
|
619
|
+
*/
|
|
620
|
+
function processText(output) {
|
|
621
|
+
const choiceContent = output.choices?.[0]?.message?.content;
|
|
622
|
+
if (choiceContent != null && String(choiceContent).length > 0) return String(choiceContent);
|
|
623
|
+
if ("response" in output) {
|
|
624
|
+
const response = output.response;
|
|
625
|
+
if (typeof response === "object" && response !== null) return JSON.stringify(response);
|
|
626
|
+
if (typeof response === "number") return String(response);
|
|
627
|
+
if (response === null || response === void 0) return;
|
|
628
|
+
return String(response);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* Was a specific tool forced for this request?
|
|
633
|
+
*
|
|
634
|
+
* True for both `tool_choice: "required"` and the named-function form
|
|
635
|
+
* `{ type: "function", function: { name } }`.
|
|
636
|
+
*/
|
|
637
|
+
function isForcedToolChoice(toolChoice) {
|
|
638
|
+
if (toolChoice === "required") return true;
|
|
639
|
+
return typeof toolChoice === "object" && toolChoice !== null && toolChoice.type === "function";
|
|
640
|
+
}
|
|
641
|
+
/** Collect the requested tool names from mapped tools. */
|
|
642
|
+
function getToolNames(tools) {
|
|
643
|
+
return new Set((tools ?? []).map((tool) => tool.function?.name).filter((name) => typeof name === "string"));
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* Parse tool calls that a model leaked as JSON text instead of structured
|
|
647
|
+
* `tool_calls`. Shared by the non-streaming salvage and the streaming buffer.
|
|
648
|
+
*
|
|
649
|
+
* Only JSON objects whose `name` is one of `knownToolNames` are recovered;
|
|
650
|
+
* everything else (prose, harmony channel/role leaks like `{"name":"analysis"}`,
|
|
651
|
+
* hallucinated names) is ignored to avoid fabricating bogus calls.
|
|
652
|
+
*
|
|
653
|
+
* Returns neutral `{ toolName, input }` records — callers assign their own ids.
|
|
654
|
+
*/
|
|
655
|
+
function parseLeakedToolCalls(text, knownToolNames) {
|
|
656
|
+
let parsed;
|
|
657
|
+
try {
|
|
658
|
+
parsed = JSON.parse(text.trim());
|
|
659
|
+
} catch {
|
|
660
|
+
return [];
|
|
661
|
+
}
|
|
662
|
+
const candidates = Array.isArray(parsed) ? parsed : [parsed];
|
|
663
|
+
const salvaged = [];
|
|
664
|
+
for (const candidate of candidates) {
|
|
665
|
+
if (typeof candidate !== "object" || candidate === null) continue;
|
|
666
|
+
const obj = candidate;
|
|
667
|
+
const name = obj.name;
|
|
668
|
+
if (typeof name !== "string" || !knownToolNames.has(name)) continue;
|
|
669
|
+
let args;
|
|
670
|
+
if ("arguments" in obj) args = obj.arguments;
|
|
671
|
+
else if ("parameters" in obj) args = obj.parameters;
|
|
672
|
+
else {
|
|
673
|
+
const { name: _name, ...rest } = obj;
|
|
674
|
+
args = rest;
|
|
675
|
+
}
|
|
676
|
+
salvaged.push({
|
|
677
|
+
toolName: name,
|
|
678
|
+
input: typeof args === "string" ? args : JSON.stringify(args ?? {})
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
return salvaged;
|
|
682
|
+
}
|
|
683
|
+
//#endregion
|
|
684
|
+
//#region ../gateway-core/src/workers-ai-errors.ts
|
|
685
|
+
/**
|
|
686
|
+
* Shared, dependency-free Workers AI error classification used by every
|
|
687
|
+
* consumer of `@cloudflare/gateway-core` (`workers-ai-provider` and
|
|
688
|
+
* `@cloudflare/tanstack-ai`).
|
|
689
|
+
*
|
|
690
|
+
* The Workers AI **binding** (`env.AI.run`) throws plain `Error`s whose message
|
|
691
|
+
* carries an internal code (e.g. `"3040: Capacity temporarily exceeded"`) but
|
|
692
|
+
* no HTTP status. Retry machinery (the AI SDK's `APICallError` retry, the OpenAI
|
|
693
|
+
* SDK's status-based retry, and our own non-chat retry loop) all key off an HTTP
|
|
694
|
+
* status, so we translate the internal code into the documented status and let
|
|
695
|
+
* each layer derive retryability from it — this is what makes transient failures
|
|
696
|
+
* like "out of capacity" (3040 → 429) automatically retried.
|
|
697
|
+
*
|
|
698
|
+
* This module is intentionally free of any provider-SDK types so it can be
|
|
699
|
+
* inlined into each consumer's bundle.
|
|
700
|
+
*/
|
|
701
|
+
/**
|
|
702
|
+
* Workers AI internal error code → HTTP status code.
|
|
703
|
+
*
|
|
704
|
+
* Source: https://developers.cloudflare.com/workers-ai/platform/errors/
|
|
705
|
+
*/
|
|
706
|
+
const WORKERS_AI_ERROR_CODE_TO_STATUS = {
|
|
707
|
+
5007: 400,
|
|
708
|
+
5004: 400,
|
|
709
|
+
3039: 400,
|
|
710
|
+
3003: 400,
|
|
711
|
+
5018: 403,
|
|
712
|
+
5016: 403,
|
|
713
|
+
3023: 403,
|
|
714
|
+
3041: 403,
|
|
715
|
+
5019: 405,
|
|
716
|
+
5005: 405,
|
|
717
|
+
3042: 404,
|
|
718
|
+
3006: 413,
|
|
719
|
+
3007: 408,
|
|
720
|
+
3008: 408,
|
|
721
|
+
3036: 429,
|
|
722
|
+
3040: 429
|
|
723
|
+
};
|
|
724
|
+
/** Read a human-readable message from any thrown value (Error, DOMException, plain object, string). */
|
|
725
|
+
function messageOf(error) {
|
|
726
|
+
if (error instanceof Error) return error.message;
|
|
727
|
+
if (typeof error === "string") return error;
|
|
728
|
+
if (error && typeof error === "object") {
|
|
729
|
+
const message = error.message;
|
|
730
|
+
if (typeof message === "string") return message;
|
|
731
|
+
}
|
|
732
|
+
return String(error);
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* Best-effort extraction of a Workers AI internal error code from a thrown
|
|
736
|
+
* binding error. Prefers a numeric `code` property when present, otherwise
|
|
737
|
+
* parses the `"<code>: <message>"` form the binding uses (optionally prefixed,
|
|
738
|
+
* e.g. `"InferenceUpstreamError: 3040: ..."`). Only recognized codes are
|
|
739
|
+
* returned, and when several `"<number>:"` groups appear (e.g. a leading
|
|
740
|
+
* request id) the first that maps to a known code wins, so unrelated numbers
|
|
741
|
+
* can't be misread as a code.
|
|
742
|
+
*/
|
|
743
|
+
function parseWorkersAIErrorCode(error) {
|
|
744
|
+
if (error && typeof error === "object") {
|
|
745
|
+
const code = error.code;
|
|
746
|
+
if (typeof code === "number" && code in WORKERS_AI_ERROR_CODE_TO_STATUS) return code;
|
|
747
|
+
if (typeof code === "string") {
|
|
748
|
+
const parsed = Number.parseInt(code, 10);
|
|
749
|
+
if (Number.isFinite(parsed) && parsed in WORKERS_AI_ERROR_CODE_TO_STATUS) return parsed;
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
const message = messageOf(error);
|
|
753
|
+
for (const match of message.matchAll(/\b(\d{3,5})\s*:/g)) {
|
|
754
|
+
const parsed = Number.parseInt(match[1], 10);
|
|
755
|
+
if (parsed in WORKERS_AI_ERROR_CODE_TO_STATUS) return parsed;
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
/**
|
|
759
|
+
* True for cancellation errors that must propagate untouched (never wrapped or
|
|
760
|
+
* retried, so each layer's own abort detection still fires). Mirrors the AI
|
|
761
|
+
* SDK's `isAbortError`: a real abort from `fetch`/the binding is a
|
|
762
|
+
* `DOMException`, which is NOT `instanceof Error`, so both must be checked.
|
|
763
|
+
*/
|
|
764
|
+
function isAbortError(error) {
|
|
765
|
+
return (error instanceof Error || error instanceof DOMException) && (error.name === "AbortError" || error.name === "ResponseAborted" || error.name === "TimeoutError");
|
|
766
|
+
}
|
|
767
|
+
//#endregion
|
|
768
|
+
//#region src/errors.ts
|
|
769
|
+
/** Map an HTTP status to a {@link GatewayErrorCode} + recoverability hint. */
|
|
770
|
+
function classifyStatus(status) {
|
|
771
|
+
if (status === 401 || status === 403) return {
|
|
772
|
+
code: "auth",
|
|
773
|
+
recoverable: false
|
|
774
|
+
};
|
|
775
|
+
if (status === 429) return {
|
|
776
|
+
code: "rate-limit",
|
|
777
|
+
recoverable: true
|
|
778
|
+
};
|
|
779
|
+
if (status === 404) return {
|
|
780
|
+
code: "not-found",
|
|
781
|
+
recoverable: false
|
|
782
|
+
};
|
|
783
|
+
if (status === 400 || status === 422) return {
|
|
784
|
+
code: "bad-request",
|
|
785
|
+
recoverable: false
|
|
786
|
+
};
|
|
787
|
+
if (status >= 500) return {
|
|
788
|
+
code: "provider-error",
|
|
789
|
+
recoverable: true
|
|
790
|
+
};
|
|
791
|
+
return {
|
|
792
|
+
code: "unknown",
|
|
793
|
+
recoverable: false
|
|
794
|
+
};
|
|
795
|
+
}
|
|
796
|
+
/** Best-effort extraction of a human message from a CF/provider error envelope. */
|
|
797
|
+
function extractErrorMessage(raw) {
|
|
798
|
+
if (typeof raw === "string") {
|
|
799
|
+
const trimmed = raw.trim();
|
|
800
|
+
if (!trimmed) return void 0;
|
|
801
|
+
try {
|
|
802
|
+
return extractErrorMessage(JSON.parse(trimmed));
|
|
803
|
+
} catch {
|
|
804
|
+
return trimmed.slice(0, 500);
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
if (!raw || typeof raw !== "object") return void 0;
|
|
808
|
+
const obj = raw;
|
|
809
|
+
if (Array.isArray(obj.errors) && obj.errors.length > 0) {
|
|
810
|
+
const first = obj.errors[0];
|
|
811
|
+
if (typeof first?.message === "string") return first.message;
|
|
812
|
+
}
|
|
813
|
+
if (obj.error && typeof obj.error === "object") {
|
|
814
|
+
const err = obj.error;
|
|
815
|
+
if (typeof err.message === "string") return err.message;
|
|
816
|
+
}
|
|
817
|
+
if (typeof obj.error === "string") return obj.error;
|
|
818
|
+
if (typeof obj.message === "string") return obj.message;
|
|
819
|
+
}
|
|
820
|
+
/** A single dispatch failure through AI Gateway (run or gateway path). */
|
|
821
|
+
var WorkersAIGatewayError = class WorkersAIGatewayError extends Error {
|
|
822
|
+
constructor(code, message, opts = {}) {
|
|
823
|
+
super(message);
|
|
824
|
+
_defineProperty(this, "code", void 0);
|
|
825
|
+
_defineProperty(this, "recoverable", void 0);
|
|
826
|
+
_defineProperty(this, "status", void 0);
|
|
827
|
+
_defineProperty(this, "context", void 0);
|
|
828
|
+
_defineProperty(this, "raw", void 0);
|
|
829
|
+
_defineProperty(this, "cause", void 0);
|
|
830
|
+
this.name = "WorkersAIGatewayError";
|
|
831
|
+
this.code = code;
|
|
832
|
+
this.recoverable = opts.recoverable ?? false;
|
|
833
|
+
this.status = opts.status ?? null;
|
|
834
|
+
this.context = opts.context ?? {};
|
|
835
|
+
this.raw = opts.raw;
|
|
836
|
+
this.cause = opts.cause;
|
|
837
|
+
}
|
|
838
|
+
/**
|
|
839
|
+
* Classify an arbitrary thrown value. Understands AI SDK `APICallError`
|
|
840
|
+
* (reads `statusCode` / `responseBody` / `isRetryable`); falls back to a
|
|
841
|
+
* recoverable `gateway-error` for transport/connection failures so a fallback
|
|
842
|
+
* chain keeps trying.
|
|
843
|
+
*/
|
|
844
|
+
static fromUnknown(e) {
|
|
845
|
+
if (e instanceof WorkersAIGatewayError) return e;
|
|
846
|
+
const obj = e && typeof e === "object" ? e : {};
|
|
847
|
+
const status = typeof obj.statusCode === "number" ? obj.statusCode : null;
|
|
848
|
+
const responseBody = typeof obj.responseBody === "string" ? obj.responseBody : void 0;
|
|
849
|
+
if (status !== null) {
|
|
850
|
+
const classified = classifyStatus(status);
|
|
851
|
+
const recoverable = typeof obj.isRetryable === "boolean" ? obj.isRetryable : classified.recoverable;
|
|
852
|
+
const message = extractErrorMessage(responseBody) ?? (e instanceof Error ? e.message : `Gateway dispatch failed (HTTP ${status}).`);
|
|
853
|
+
let raw = responseBody;
|
|
854
|
+
try {
|
|
855
|
+
raw = responseBody ? JSON.parse(responseBody) : responseBody;
|
|
856
|
+
} catch {}
|
|
857
|
+
return new WorkersAIGatewayError(classified.code, message, {
|
|
858
|
+
recoverable,
|
|
859
|
+
status,
|
|
860
|
+
raw,
|
|
861
|
+
cause: e
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
return new WorkersAIGatewayError("gateway-error", e instanceof Error ? e.message : String(e), {
|
|
865
|
+
recoverable: true,
|
|
866
|
+
cause: e
|
|
867
|
+
});
|
|
868
|
+
}
|
|
869
|
+
/** Build from an HTTP `Response` (reads the body for the envelope). */
|
|
870
|
+
static async fromResponse(resp, context = {}) {
|
|
871
|
+
const text = await resp.text().catch(() => "");
|
|
872
|
+
const { code, recoverable } = classifyStatus(resp.status);
|
|
873
|
+
const message = extractErrorMessage(text) ?? `Gateway dispatch failed (HTTP ${resp.status}).`;
|
|
874
|
+
let raw = text;
|
|
875
|
+
try {
|
|
876
|
+
raw = text ? JSON.parse(text) : text;
|
|
877
|
+
} catch {}
|
|
878
|
+
return new WorkersAIGatewayError(code, message, {
|
|
879
|
+
recoverable,
|
|
880
|
+
status: resp.status,
|
|
881
|
+
raw,
|
|
882
|
+
context: {
|
|
883
|
+
...context,
|
|
884
|
+
status: resp.status,
|
|
885
|
+
logId: resp.headers.get("cf-aig-log-id"),
|
|
886
|
+
runId: resp.headers.get("cf-aig-run-id")
|
|
887
|
+
}
|
|
888
|
+
});
|
|
889
|
+
}
|
|
890
|
+
};
|
|
891
|
+
/** Every model in a client-side fallback chain failed. */
|
|
892
|
+
var WorkersAIFallbackError = class extends Error {
|
|
893
|
+
constructor(attempts, message) {
|
|
894
|
+
const tried = attempts.map((a) => a.model).join(" → ");
|
|
895
|
+
super(message ?? `All fallback models failed: ${tried}.`);
|
|
896
|
+
_defineProperty(this, "attempts", void 0);
|
|
897
|
+
this.name = "WorkersAIFallbackError";
|
|
898
|
+
this.attempts = attempts;
|
|
899
|
+
}
|
|
900
|
+
/** The last (most recent) attempt's error, if any. */
|
|
901
|
+
get lastError() {
|
|
902
|
+
for (let i = this.attempts.length - 1; i >= 0; i--) {
|
|
903
|
+
const e = this.attempts[i].error;
|
|
904
|
+
if (e) return e;
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
};
|
|
908
|
+
//#endregion
|
|
909
|
+
//#region src/gateway-provider.ts
|
|
517
910
|
/**
|
|
518
911
|
* A `fetch` that dispatches the wrapped provider's request through AI Gateway.
|
|
519
912
|
* Detects the gateway provider id from the request URL (or uses `config.provider`),
|
|
@@ -534,19 +927,18 @@ function createGatewayFetch(config) {
|
|
|
534
927
|
const providerId = config.provider ?? info.gatewayProviderId;
|
|
535
928
|
const endpoint = info?.transformEndpoint ? info.transformEndpoint(rawUrl) : rawUrl.replace(/^https?:\/\/[^/]+\//, "");
|
|
536
929
|
const body = JSON.parse(asText(init?.body));
|
|
537
|
-
const
|
|
538
|
-
|
|
539
|
-
const headers = {};
|
|
540
|
-
for (const [k, v] of Object.entries(headersToObject(init?.headers))) if (!strip.has(k.toLowerCase())) headers[k] = v;
|
|
541
|
-
if (config.extraHeaders) Object.assign(headers, config.extraHeaders);
|
|
542
|
-
if (config.cacheTtl !== void 0) headers["cf-aig-cache-ttl"] = String(config.cacheTtl);
|
|
543
|
-
if (config.skipCache) headers["cf-aig-skip-cache"] = "true";
|
|
544
|
-
const entry = {
|
|
545
|
-
provider: providerId,
|
|
930
|
+
const entry = buildGatewayEntry({
|
|
931
|
+
providerId,
|
|
546
932
|
endpoint,
|
|
547
|
-
headers,
|
|
548
|
-
|
|
549
|
-
|
|
933
|
+
initHeaders: init?.headers,
|
|
934
|
+
body,
|
|
935
|
+
...!config.byok && info ? { stripAuthHeaders: info.authHeaders } : {},
|
|
936
|
+
...config.extraHeaders ? { extraHeaders: config.extraHeaders } : {},
|
|
937
|
+
cache: {
|
|
938
|
+
cacheTtl: config.cacheTtl,
|
|
939
|
+
skipCache: config.skipCache
|
|
940
|
+
}
|
|
941
|
+
});
|
|
550
942
|
const gw = config.binding.gateway(gatewayId);
|
|
551
943
|
const runOptions = {};
|
|
552
944
|
if (init?.signal) runOptions.signal = init.signal;
|
|
@@ -578,6 +970,6 @@ function createGatewayProvider(factory, config) {
|
|
|
578
970
|
});
|
|
579
971
|
}
|
|
580
972
|
//#endregion
|
|
581
|
-
export {
|
|
973
|
+
export { GatewayDelegateError as C, headersToObject as S, detectProviderByUrl as _, WORKERS_AI_ERROR_CODE_TO_STATUS as a, asText as b, parseWorkersAIErrorCode as c, isForcedToolChoice as d, normalizeMessagesForBinding as f, GATEWAY_PROVIDERS as g, createResumableStream as h, WorkersAIGatewayError as i, SSEDecoder as l, processText as m, createGatewayProvider as n, isAbortError as o, parseLeakedToolCalls as p, WorkersAIFallbackError as r, messageOf as s, createGatewayFetch as t, getToolNames as u, findProviderBySlug as v, _defineProperty as w, buildGatewayEntry as x, wireableProviders as y };
|
|
582
974
|
|
|
583
|
-
//# sourceMappingURL=gateway-provider-
|
|
975
|
+
//# sourceMappingURL=gateway-provider-CQU-v2IO.mjs.map
|