@yanlinglabs/winter-provider-conformance 0.0.2
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/LICENSE +21 -0
- package/NOTICE +41 -0
- package/README.md +51 -0
- package/dist/corpus/azure.d.ts +7 -0
- package/dist/corpus/classifier-safety.d.ts +146 -0
- package/dist/corpus/continuity.d.ts +52 -0
- package/dist/corpus/cross-vendor-headers.d.ts +38 -0
- package/dist/corpus/harness.d.ts +57 -0
- package/dist/corpus/runner.d.ts +69 -0
- package/dist/fakes/anthropic-console-oauth.d.ts +36 -0
- package/dist/fakes/anthropic-messages.d.ts +115 -0
- package/dist/fakes/azure-openai.d.ts +18 -0
- package/dist/fakes/bedrock.d.ts +90 -0
- package/dist/fakes/codex-oauth.d.ts +46 -0
- package/dist/fakes/gemini.d.ts +121 -0
- package/dist/fakes/index.d.ts +15 -0
- package/dist/fakes/index.js +56 -0
- package/dist/fakes/jwt-verify.d.ts +13 -0
- package/dist/fakes/openai-chat.d.ts +61 -0
- package/dist/fakes/openai-models.d.ts +49 -0
- package/dist/fakes/openai-responses.d.ts +86 -0
- package/dist/fakes/redact-opaque.d.ts +4 -0
- package/dist/fakes/server.d.ts +143 -0
- package/dist/fakes/vertex.d.ts +60 -0
- package/dist/fakes/xai-oauth.d.ts +2 -0
- package/dist/index-k4mhh1q5.js +4377 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +1671 -0
- package/dist/live/cases.d.ts +87 -0
- package/dist/live/index.d.ts +101 -0
- package/package.json +50 -0
|
@@ -0,0 +1,4377 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __returnValue = (v) => v;
|
|
3
|
+
function __exportSetter(name, newValue) {
|
|
4
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
5
|
+
}
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, {
|
|
9
|
+
get: all[name],
|
|
10
|
+
enumerable: true,
|
|
11
|
+
configurable: true,
|
|
12
|
+
set: __exportSetter.bind(all, name)
|
|
13
|
+
});
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// src/fakes/server.ts
|
|
17
|
+
import { serve } from "bun";
|
|
18
|
+
var CREDENTIAL_HEADERS = new Set(["authorization", "x-api-key", "api-key", "x-goog-api-key", "proxy-authorization", "x-amz-security-token"]);
|
|
19
|
+
function redactHeaderValue(name, value) {
|
|
20
|
+
if (!CREDENTIAL_HEADERS.has(name.toLowerCase()))
|
|
21
|
+
return value;
|
|
22
|
+
const space = value.indexOf(" ");
|
|
23
|
+
return space > 0 ? `${value.slice(0, space)} ***` : "***";
|
|
24
|
+
}
|
|
25
|
+
async function startFake2(opts) {
|
|
26
|
+
const requests = [];
|
|
27
|
+
const closeDeadlineMs = opts.closeDeadlineMs ?? 2000;
|
|
28
|
+
const server = serve({
|
|
29
|
+
hostname: "127.0.0.1",
|
|
30
|
+
port: 0,
|
|
31
|
+
async fetch(req) {
|
|
32
|
+
const url = new URL(req.url);
|
|
33
|
+
const headers = {};
|
|
34
|
+
req.headers.forEach((value, name) => {
|
|
35
|
+
headers[name.toLowerCase()] = redactHeaderValue(name, value);
|
|
36
|
+
});
|
|
37
|
+
const body = req.method === "GET" || req.method === "HEAD" ? "" : await req.text();
|
|
38
|
+
const recorded = { method: req.method, path: url.pathname, search: url.search, headers, body };
|
|
39
|
+
requests.push(recorded);
|
|
40
|
+
for (const route of opts.routes) {
|
|
41
|
+
if (route.method !== undefined && route.method !== req.method)
|
|
42
|
+
continue;
|
|
43
|
+
const matches = route.path.endsWith("*") ? url.pathname.startsWith(route.path.slice(0, -1)) : url.pathname === route.path;
|
|
44
|
+
if (matches)
|
|
45
|
+
return route.handler(req, recorded);
|
|
46
|
+
}
|
|
47
|
+
if (opts.fallback !== undefined)
|
|
48
|
+
return opts.fallback(req, recorded);
|
|
49
|
+
return new Response(`no fake route for ${req.method} ${url.pathname}`, { status: 404 });
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
return {
|
|
53
|
+
url: `http://127.0.0.1:${server.port}`,
|
|
54
|
+
requests,
|
|
55
|
+
async close() {
|
|
56
|
+
const stopped = Promise.resolve(server.stop(true));
|
|
57
|
+
const timeout = new Promise((resolve) => setTimeout(() => resolve("timeout"), closeDeadlineMs));
|
|
58
|
+
const result = await Promise.race([stopped.then(() => "stopped"), timeout]);
|
|
59
|
+
if (result === "timeout")
|
|
60
|
+
throw new Error(`fake server on port ${server.port} did not stop within ${closeDeadlineMs}ms`);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
async function withFake2(opts, fn) {
|
|
65
|
+
const fake = await startFake2(opts);
|
|
66
|
+
try {
|
|
67
|
+
return await fn(fake);
|
|
68
|
+
} finally {
|
|
69
|
+
await fake.close();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function sseResponse2(frames, opts = {}) {
|
|
73
|
+
const encoder = new TextEncoder;
|
|
74
|
+
let written = 0;
|
|
75
|
+
const body = new ReadableStream({
|
|
76
|
+
async pull(controller) {
|
|
77
|
+
if (written >= frames.length) {
|
|
78
|
+
controller.close();
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (opts.dropAfter !== undefined && written >= opts.dropAfter) {
|
|
82
|
+
controller.close();
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const frame = frames[written];
|
|
86
|
+
written++;
|
|
87
|
+
if (frame.delayMs !== undefined && frame.delayMs > 0)
|
|
88
|
+
await new Promise((r) => setTimeout(r, frame.delayMs));
|
|
89
|
+
const prefix = frame.event !== undefined ? `event: ${frame.event}
|
|
90
|
+
` : "";
|
|
91
|
+
controller.enqueue(encoder.encode(`${prefix}data: ${frame.data}
|
|
92
|
+
|
|
93
|
+
`));
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
return new Response(body, {
|
|
97
|
+
status: opts.status ?? 200,
|
|
98
|
+
headers: { "content-type": "text/event-stream", "cache-control": "no-cache", ...opts.headers ?? {} }
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
function jsonResponse2(value, status = 200, headers = {}) {
|
|
102
|
+
return new Response(JSON.stringify(value), { status, headers: { "content-type": "application/json", ...headers } });
|
|
103
|
+
}
|
|
104
|
+
function errorResponse2(status, body, headers = {}) {
|
|
105
|
+
return jsonResponse2(body, status, headers);
|
|
106
|
+
}
|
|
107
|
+
function redirectResponse2(location, status = 307) {
|
|
108
|
+
return new Response(null, { status, headers: { location } });
|
|
109
|
+
}
|
|
110
|
+
function stalledResponse2(holdMs = 1e4) {
|
|
111
|
+
let timer;
|
|
112
|
+
let done = false;
|
|
113
|
+
const body = new ReadableStream({
|
|
114
|
+
start(controller) {
|
|
115
|
+
controller.enqueue(new TextEncoder().encode(`: open
|
|
116
|
+
|
|
117
|
+
`));
|
|
118
|
+
timer = setTimeout(() => {
|
|
119
|
+
if (done)
|
|
120
|
+
return;
|
|
121
|
+
done = true;
|
|
122
|
+
try {
|
|
123
|
+
controller.close();
|
|
124
|
+
} catch {}
|
|
125
|
+
}, holdMs);
|
|
126
|
+
},
|
|
127
|
+
cancel() {
|
|
128
|
+
done = true;
|
|
129
|
+
if (timer !== undefined)
|
|
130
|
+
clearTimeout(timer);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
return new Response(body, { status: 200, headers: { "content-type": "text/event-stream" } });
|
|
134
|
+
}
|
|
135
|
+
function scenarioTable2(opts) {
|
|
136
|
+
const attempts = new Map;
|
|
137
|
+
return async (_req, recorded) => {
|
|
138
|
+
const model = opts.modelOf(recorded);
|
|
139
|
+
const attempt = (attempts.get(model ?? "") ?? 0) + 1;
|
|
140
|
+
attempts.set(model ?? "", attempt);
|
|
141
|
+
const entry = model !== undefined ? opts.scenarios[model] : undefined;
|
|
142
|
+
if (entry === undefined) {
|
|
143
|
+
if (opts.unknownModel !== undefined)
|
|
144
|
+
return opts.unknownModel(recorded, attempt);
|
|
145
|
+
return jsonResponse2({ error: { message: `fake: no scenario for model ${JSON.stringify(model)}` } }, 400);
|
|
146
|
+
}
|
|
147
|
+
if (Array.isArray(entry)) {
|
|
148
|
+
const index = Math.min(attempt - 1, entry.length - 1);
|
|
149
|
+
return entry[index].clone();
|
|
150
|
+
}
|
|
151
|
+
return entry(recorded, attempt);
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
function requestsTo2(fake, path) {
|
|
155
|
+
return fake.requests.filter((r) => r.path === path);
|
|
156
|
+
}
|
|
157
|
+
function noRequestContains2(fake, needle) {
|
|
158
|
+
return !fake.requests.some((r) => r.body.includes(needle) || Object.values(r.headers).some((v) => v.includes(needle)));
|
|
159
|
+
}
|
|
160
|
+
// src/fakes/anthropic-console-oauth.ts
|
|
161
|
+
var exports_anthropic_console_oauth = {};
|
|
162
|
+
__export(exports_anthropic_console_oauth, {
|
|
163
|
+
FAKE_CONSOLE_ACCESS_TOKEN: () => FAKE_CONSOLE_ACCESS_TOKEN,
|
|
164
|
+
FAKE_CONSOLE_ACCOUNT_ID: () => FAKE_CONSOLE_ACCOUNT_ID,
|
|
165
|
+
FAKE_CONSOLE_REFRESHED_ACCESS_TOKEN: () => FAKE_CONSOLE_REFRESHED_ACCESS_TOKEN,
|
|
166
|
+
FAKE_CONSOLE_REFRESH_TOKEN: () => FAKE_CONSOLE_REFRESH_TOKEN,
|
|
167
|
+
startAnthropicConsoleOauthFake: () => startAnthropicConsoleOauthFake
|
|
168
|
+
});
|
|
169
|
+
var FAKE_CONSOLE_ACCOUNT_ID = "acct-test-console-0001";
|
|
170
|
+
var FAKE_CONSOLE_ACCESS_TOKEN = "test-token-anthropic-console-access";
|
|
171
|
+
var FAKE_CONSOLE_REFRESHED_ACCESS_TOKEN = "test-token-anthropic-console-access-refreshed";
|
|
172
|
+
var FAKE_CONSOLE_REFRESH_TOKEN = "test-token-anthropic-console-refresh";
|
|
173
|
+
function base64Url(bytes) {
|
|
174
|
+
let binary = "";
|
|
175
|
+
for (const byte of bytes)
|
|
176
|
+
binary += String.fromCharCode(byte);
|
|
177
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
178
|
+
}
|
|
179
|
+
async function startAnthropicConsoleOauthFake(opts = {}) {
|
|
180
|
+
const tokenRequests = [];
|
|
181
|
+
const profileRequests = [];
|
|
182
|
+
const accountId = opts.accountId ?? FAKE_CONSOLE_ACCOUNT_ID;
|
|
183
|
+
const challenges = new Set;
|
|
184
|
+
const fake = await startFake2({
|
|
185
|
+
routes: [
|
|
186
|
+
{
|
|
187
|
+
path: "/v1/oauth/token",
|
|
188
|
+
method: "POST",
|
|
189
|
+
handler: async (_req, recorded) => {
|
|
190
|
+
tokenRequests.push(recorded);
|
|
191
|
+
if (opts.failTokenWith !== undefined) {
|
|
192
|
+
return jsonResponse2({ error: "invalid_grant", error_description: "the fake refused this grant" }, opts.failTokenWith);
|
|
193
|
+
}
|
|
194
|
+
const contentType = (recorded.headers["content-type"] ?? "").split(";")[0].trim();
|
|
195
|
+
if (contentType !== "application/json") {
|
|
196
|
+
return jsonResponse2({ error: "invalid_request", error_description: `this endpoint accepts application/json only, not ${JSON.stringify(contentType)}` }, 400);
|
|
197
|
+
}
|
|
198
|
+
let body;
|
|
199
|
+
try {
|
|
200
|
+
const parsed = JSON.parse(recorded.body);
|
|
201
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
|
|
202
|
+
throw new Error("not an object");
|
|
203
|
+
body = parsed;
|
|
204
|
+
} catch {
|
|
205
|
+
return jsonResponse2({ error: "invalid_request", error_description: "the grant body is not a JSON object" }, 400);
|
|
206
|
+
}
|
|
207
|
+
const grant = body["grant_type"];
|
|
208
|
+
if (grant === "authorization_code") {
|
|
209
|
+
if (typeof body["state"] !== "string" || body["state"].length === 0) {
|
|
210
|
+
return jsonResponse2({ error: "invalid_request", error_description: "the authorization_code grant carried no state" }, 400);
|
|
211
|
+
}
|
|
212
|
+
const verifier = typeof body["code_verifier"] === "string" ? body["code_verifier"] : "";
|
|
213
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
|
|
214
|
+
if (!challenges.has(base64Url(new Uint8Array(digest)))) {
|
|
215
|
+
return jsonResponse2({ error: "invalid_grant", error_description: "PKCE verifier does not match any challenge this fake saw" }, 400);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return jsonResponse2({
|
|
219
|
+
access_token: grant === "refresh_token" ? FAKE_CONSOLE_REFRESHED_ACCESS_TOKEN : FAKE_CONSOLE_ACCESS_TOKEN,
|
|
220
|
+
...opts.omitRefreshToken === true ? {} : { refresh_token: FAKE_CONSOLE_REFRESH_TOKEN },
|
|
221
|
+
expires_in: opts.expiresIn ?? 3600,
|
|
222
|
+
scope: "user:inference user:profile",
|
|
223
|
+
token_type: "Bearer"
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
path: "/api/oauth/profile",
|
|
229
|
+
method: "GET",
|
|
230
|
+
handler: (req, recorded) => {
|
|
231
|
+
profileRequests.push(recorded);
|
|
232
|
+
if ((req.headers.get("authorization") ?? "") === "")
|
|
233
|
+
return jsonResponse2({ error: "unauthorized" }, 401);
|
|
234
|
+
return jsonResponse2({
|
|
235
|
+
...opts.omitAccount === true ? {} : { account: { uuid: accountId, email: "person@example.test" } },
|
|
236
|
+
organization: { uuid: "org-test-console-0001" }
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
]
|
|
241
|
+
});
|
|
242
|
+
return Object.assign(fake, {
|
|
243
|
+
authorizeUrl: `${fake.url}/oauth/authorize`,
|
|
244
|
+
tokenUrl: `${fake.url}/v1/oauth/token`,
|
|
245
|
+
profileUrl: `${fake.url}/api/oauth/profile`,
|
|
246
|
+
tokenRequests,
|
|
247
|
+
profileRequests,
|
|
248
|
+
async completeAuthorization(url, overrides = {}) {
|
|
249
|
+
const authorize = new URL(url);
|
|
250
|
+
const challenge = authorize.searchParams.get("code_challenge");
|
|
251
|
+
if (challenge !== null)
|
|
252
|
+
challenges.add(challenge);
|
|
253
|
+
const redirectUri = authorize.searchParams.get("redirect_uri");
|
|
254
|
+
if (redirectUri === null)
|
|
255
|
+
throw new Error("the authorize URL carried no redirect_uri");
|
|
256
|
+
const callback = new URL(redirectUri);
|
|
257
|
+
callback.searchParams.set("state", overrides.state ?? authorize.searchParams.get("state") ?? "");
|
|
258
|
+
if (overrides.code !== null)
|
|
259
|
+
callback.searchParams.set("code", overrides.code ?? "test-code-anthropic-console");
|
|
260
|
+
await fetch(callback.toString()).catch(() => {
|
|
261
|
+
return;
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
// src/fakes/anthropic-messages.ts
|
|
267
|
+
var exports_anthropic_messages = {};
|
|
268
|
+
__export(exports_anthropic_messages, {
|
|
269
|
+
anthropicBody: () => anthropicBody,
|
|
270
|
+
anthropicError: () => anthropicError,
|
|
271
|
+
anthropicFakeRoutes: () => anthropicFakeRoutes,
|
|
272
|
+
anthropicModelOf: () => anthropicModelOf,
|
|
273
|
+
anthropicSseFrames: () => anthropicSseFrames,
|
|
274
|
+
anthropicTurnResponse: () => anthropicTurnResponse,
|
|
275
|
+
assertAnthropicRequest: () => assertAnthropicRequest,
|
|
276
|
+
findToolResultOrderingViolation: () => findToolResultOrderingViolation,
|
|
277
|
+
flattenBlockTypes: () => flattenBlockTypes,
|
|
278
|
+
messageBlocks: () => messageBlocks
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
// src/fakes/redact-opaque.ts
|
|
282
|
+
var OPAQUE_FIELD_NAMES2 = ["signature", "thoughtSignature", "data"];
|
|
283
|
+
var MARKER = "[redacted: opaque provider state]";
|
|
284
|
+
function walk(value) {
|
|
285
|
+
if (Array.isArray(value))
|
|
286
|
+
return value.map(walk);
|
|
287
|
+
if (value === null || typeof value !== "object")
|
|
288
|
+
return value;
|
|
289
|
+
const out = {};
|
|
290
|
+
for (const [key, inner] of Object.entries(value)) {
|
|
291
|
+
out[key] = OPAQUE_FIELD_NAMES2.includes(key) && typeof inner === "string" ? MARKER : walk(inner);
|
|
292
|
+
}
|
|
293
|
+
return out;
|
|
294
|
+
}
|
|
295
|
+
function redactOpaqueFields2(body) {
|
|
296
|
+
if (body.length === 0)
|
|
297
|
+
return body;
|
|
298
|
+
try {
|
|
299
|
+
return JSON.stringify(walk(JSON.parse(body)));
|
|
300
|
+
} catch {
|
|
301
|
+
return body.replace(new RegExp(`"(${OPAQUE_FIELD_NAMES2.join("|")})"\\s*:\\s*"(?:[^"\\\\]|\\\\.)*"`, "g"), (m) => `${m.slice(0, m.indexOf(":") + 1)}"${MARKER}"`);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// src/fakes/anthropic-messages.ts
|
|
306
|
+
var frame = (event, data) => ({ event, data: JSON.stringify(data) });
|
|
307
|
+
function anthropicSseFrames(script) {
|
|
308
|
+
const usage = script.usage ?? {};
|
|
309
|
+
const frames = [
|
|
310
|
+
frame("message_start", {
|
|
311
|
+
type: "message_start",
|
|
312
|
+
message: {
|
|
313
|
+
id: script.id ?? "msg_fake_1",
|
|
314
|
+
type: "message",
|
|
315
|
+
role: "assistant",
|
|
316
|
+
model: script.model ?? "fake-model",
|
|
317
|
+
content: [],
|
|
318
|
+
usage: {
|
|
319
|
+
input_tokens: usage.input_tokens ?? 0,
|
|
320
|
+
output_tokens: usage.output_tokens ?? 0,
|
|
321
|
+
...usage.cache_creation_input_tokens !== undefined ? { cache_creation_input_tokens: usage.cache_creation_input_tokens } : {},
|
|
322
|
+
...usage.cache_read_input_tokens !== undefined ? { cache_read_input_tokens: usage.cache_read_input_tokens } : {}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
})
|
|
326
|
+
];
|
|
327
|
+
script.blocks.forEach((block, index) => {
|
|
328
|
+
switch (block.type) {
|
|
329
|
+
case "text":
|
|
330
|
+
frames.push(frame("content_block_start", { type: "content_block_start", index, content_block: { type: "text", text: "" } }));
|
|
331
|
+
if (index === 0 && script.ping === true)
|
|
332
|
+
frames.push(frame("ping", { type: "ping" }));
|
|
333
|
+
for (const chunk of block.chunks) {
|
|
334
|
+
frames.push(frame("content_block_delta", { type: "content_block_delta", index, delta: { type: "text_delta", text: chunk } }));
|
|
335
|
+
}
|
|
336
|
+
break;
|
|
337
|
+
case "thinking":
|
|
338
|
+
frames.push(frame("content_block_start", { type: "content_block_start", index, content_block: { type: "thinking", thinking: "" } }));
|
|
339
|
+
if (index === 0 && script.ping === true)
|
|
340
|
+
frames.push(frame("ping", { type: "ping" }));
|
|
341
|
+
for (const chunk of block.chunks) {
|
|
342
|
+
frames.push(frame("content_block_delta", { type: "content_block_delta", index, delta: { type: "thinking_delta", thinking: chunk } }));
|
|
343
|
+
}
|
|
344
|
+
if (block.signature !== undefined) {
|
|
345
|
+
frames.push(frame("content_block_delta", { type: "content_block_delta", index, delta: { type: "signature_delta", signature: block.signature } }));
|
|
346
|
+
}
|
|
347
|
+
break;
|
|
348
|
+
case "redacted_thinking":
|
|
349
|
+
frames.push(frame("content_block_start", { type: "content_block_start", index, content_block: { type: "redacted_thinking", data: block.data } }));
|
|
350
|
+
if (index === 0 && script.ping === true)
|
|
351
|
+
frames.push(frame("ping", { type: "ping" }));
|
|
352
|
+
break;
|
|
353
|
+
case "tool_use":
|
|
354
|
+
frames.push(frame("content_block_start", { type: "content_block_start", index, content_block: { type: "tool_use", id: block.id, name: block.name, input: {} } }));
|
|
355
|
+
if (index === 0 && script.ping === true)
|
|
356
|
+
frames.push(frame("ping", { type: "ping" }));
|
|
357
|
+
for (const chunk of block.jsonChunks) {
|
|
358
|
+
frames.push(frame("content_block_delta", { type: "content_block_delta", index, delta: { type: "input_json_delta", partial_json: chunk } }));
|
|
359
|
+
}
|
|
360
|
+
break;
|
|
361
|
+
}
|
|
362
|
+
frames.push(frame("content_block_stop", { type: "content_block_stop", index }));
|
|
363
|
+
});
|
|
364
|
+
if (script.errorAfterBlocks !== undefined) {
|
|
365
|
+
frames.push(frame("error", { type: "error", error: script.errorAfterBlocks }));
|
|
366
|
+
return frames;
|
|
367
|
+
}
|
|
368
|
+
frames.push(frame("message_delta", {
|
|
369
|
+
type: "message_delta",
|
|
370
|
+
delta: { stop_reason: script.stopReason ?? "end_turn", stop_sequence: null },
|
|
371
|
+
usage: { output_tokens: usage.output_tokens ?? 0 }
|
|
372
|
+
}));
|
|
373
|
+
frames.push(frame("message_stop", { type: "message_stop" }));
|
|
374
|
+
return frames;
|
|
375
|
+
}
|
|
376
|
+
function anthropicTurnResponse(script, opts = {}) {
|
|
377
|
+
return sseResponse2(anthropicSseFrames(script), opts);
|
|
378
|
+
}
|
|
379
|
+
function anthropicModelOf(recorded) {
|
|
380
|
+
try {
|
|
381
|
+
const body = JSON.parse(recorded.body);
|
|
382
|
+
return typeof body.model === "string" ? body.model : undefined;
|
|
383
|
+
} catch {
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
function anthropicBody(recorded) {
|
|
388
|
+
return JSON.parse(recorded.body);
|
|
389
|
+
}
|
|
390
|
+
function fail(message, recorded) {
|
|
391
|
+
throw new Error(`${message}
|
|
392
|
+
live request: ${recorded.method} ${recorded.path}${recorded.search}
|
|
393
|
+
headers: ${JSON.stringify(recorded.headers)}
|
|
394
|
+
body: ${redactOpaqueFields2(recorded.body)}`);
|
|
395
|
+
}
|
|
396
|
+
function assertAnthropicRequest(recorded, expected = {}) {
|
|
397
|
+
if (recorded.method !== "POST")
|
|
398
|
+
fail(`expected a POST, saw ${recorded.method}`, recorded);
|
|
399
|
+
if (recorded.headers["x-api-key"] !== "***")
|
|
400
|
+
fail(`expected a redacted x-api-key header, saw ${JSON.stringify(recorded.headers["x-api-key"])}`, recorded);
|
|
401
|
+
if (recorded.headers["authorization"] !== undefined)
|
|
402
|
+
fail("an Anthropic request must authenticate with x-api-key, never Authorization", recorded);
|
|
403
|
+
const version = expected.apiVersion ?? "2023-06-01";
|
|
404
|
+
if (recorded.headers["anthropic-version"] !== version)
|
|
405
|
+
fail(`expected anthropic-version ${version}, saw ${JSON.stringify(recorded.headers["anthropic-version"])}`, recorded);
|
|
406
|
+
if (!(recorded.headers["content-type"] ?? "").startsWith("application/json"))
|
|
407
|
+
fail(`expected a JSON content-type, saw ${JSON.stringify(recorded.headers["content-type"])}`, recorded);
|
|
408
|
+
if (expected.beta !== undefined && recorded.headers["anthropic-beta"] !== expected.beta) {
|
|
409
|
+
fail(`expected anthropic-beta ${expected.beta}, saw ${JSON.stringify(recorded.headers["anthropic-beta"])}`, recorded);
|
|
410
|
+
}
|
|
411
|
+
const body = anthropicBody(recorded);
|
|
412
|
+
if (expected.model !== undefined && body["model"] !== expected.model)
|
|
413
|
+
fail(`expected model ${expected.model}, saw ${JSON.stringify(body["model"])}`, recorded);
|
|
414
|
+
if (expected.stream !== undefined && body["stream"] !== expected.stream)
|
|
415
|
+
fail(`expected stream ${expected.stream}, saw ${JSON.stringify(body["stream"])}`, recorded);
|
|
416
|
+
if (expected.maxTokens !== undefined && body["max_tokens"] !== expected.maxTokens)
|
|
417
|
+
fail(`expected max_tokens ${expected.maxTokens}, saw ${JSON.stringify(body["max_tokens"])}`, recorded);
|
|
418
|
+
if (expected.system !== undefined && body["system"] !== expected.system)
|
|
419
|
+
fail(`expected system ${JSON.stringify(expected.system)}, saw ${JSON.stringify(body["system"])}`, recorded);
|
|
420
|
+
if (expected.thinking !== undefined && JSON.stringify(body["thinking"]) !== JSON.stringify(expected.thinking)) {
|
|
421
|
+
fail(`expected thinking ${JSON.stringify(expected.thinking)}, saw ${JSON.stringify(body["thinking"])}`, recorded);
|
|
422
|
+
}
|
|
423
|
+
if (expected.toolChoice !== undefined && JSON.stringify(body["tool_choice"]) !== JSON.stringify(expected.toolChoice)) {
|
|
424
|
+
fail(`expected tool_choice ${JSON.stringify(expected.toolChoice)}, saw ${JSON.stringify(body["tool_choice"])}`, recorded);
|
|
425
|
+
}
|
|
426
|
+
if (expected.toolNames !== undefined) {
|
|
427
|
+
const tools = Array.isArray(body["tools"]) ? body["tools"].map((t) => t.name) : [];
|
|
428
|
+
if (JSON.stringify(tools) !== JSON.stringify(expected.toolNames))
|
|
429
|
+
fail(`expected tools ${JSON.stringify(expected.toolNames)}, saw ${JSON.stringify(tools)}`, recorded);
|
|
430
|
+
}
|
|
431
|
+
const messages = Array.isArray(body["messages"]) ? body["messages"] : [];
|
|
432
|
+
if (expected.roles !== undefined) {
|
|
433
|
+
const roles = messages.map((m) => m.role);
|
|
434
|
+
if (JSON.stringify(roles) !== JSON.stringify(expected.roles))
|
|
435
|
+
fail(`expected roles ${JSON.stringify(expected.roles)}, saw ${JSON.stringify(roles)}`, recorded);
|
|
436
|
+
}
|
|
437
|
+
if (expected.blockTypes !== undefined) {
|
|
438
|
+
const types = flattenBlockTypes(messages);
|
|
439
|
+
if (JSON.stringify(types) !== JSON.stringify(expected.blockTypes))
|
|
440
|
+
fail(`expected block ordering ${JSON.stringify(expected.blockTypes)}, saw ${JSON.stringify(types)}`, recorded);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
function flattenBlockTypes(messages) {
|
|
444
|
+
const out = [];
|
|
445
|
+
for (const message of messages) {
|
|
446
|
+
if (typeof message.content === "string") {
|
|
447
|
+
out.push("text");
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
450
|
+
if (!Array.isArray(message.content))
|
|
451
|
+
continue;
|
|
452
|
+
for (const block of message.content)
|
|
453
|
+
out.push(String(block.type));
|
|
454
|
+
}
|
|
455
|
+
return out;
|
|
456
|
+
}
|
|
457
|
+
function messageBlocks(recorded, index) {
|
|
458
|
+
const body = anthropicBody(recorded);
|
|
459
|
+
const messages = Array.isArray(body["messages"]) ? body["messages"] : [];
|
|
460
|
+
const message = messages[index];
|
|
461
|
+
if (message === undefined)
|
|
462
|
+
return [];
|
|
463
|
+
if (typeof message.content === "string")
|
|
464
|
+
return [{ type: "text", text: message.content }];
|
|
465
|
+
return Array.isArray(message.content) ? message.content : [];
|
|
466
|
+
}
|
|
467
|
+
function anthropicError(status, type, message = "fake error", headers = {}) {
|
|
468
|
+
return errorResponse2(status, { type: "error", error: { type, message } }, headers);
|
|
469
|
+
}
|
|
470
|
+
function findToolResultOrderingViolation(recorded) {
|
|
471
|
+
let body;
|
|
472
|
+
try {
|
|
473
|
+
body = JSON.parse(recorded.body);
|
|
474
|
+
} catch {
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
const messages = Array.isArray(body.messages) ? body.messages : [];
|
|
478
|
+
for (const [index, message] of messages.entries()) {
|
|
479
|
+
if (!Array.isArray(message.content))
|
|
480
|
+
continue;
|
|
481
|
+
const blocks = message.content;
|
|
482
|
+
let sawOther = false;
|
|
483
|
+
for (const block of blocks) {
|
|
484
|
+
if (block.type === "tool_result") {
|
|
485
|
+
if (sawOther)
|
|
486
|
+
return index;
|
|
487
|
+
} else {
|
|
488
|
+
sawOther = true;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
function anthropicFakeRoutes(opts) {
|
|
495
|
+
const attempts = new Map;
|
|
496
|
+
return [
|
|
497
|
+
{
|
|
498
|
+
path: "/v1/messages/count_tokens",
|
|
499
|
+
method: "POST",
|
|
500
|
+
handler: (_req, recorded) => opts.countTokens?.(recorded) ?? new Response(JSON.stringify({ input_tokens: 42 }), { status: 200, headers: { "content-type": "application/json" } })
|
|
501
|
+
},
|
|
502
|
+
{
|
|
503
|
+
path: "/v1/messages",
|
|
504
|
+
method: "POST",
|
|
505
|
+
handler: async (_req, recorded) => {
|
|
506
|
+
const badTurn = findToolResultOrderingViolation(recorded);
|
|
507
|
+
if (badTurn !== undefined) {
|
|
508
|
+
return anthropicError(400, "invalid_request_error", `messages.${badTurn}: \`tool_result\` blocks must be at the beginning of a turn`);
|
|
509
|
+
}
|
|
510
|
+
const model = anthropicModelOf(recorded) ?? "";
|
|
511
|
+
const attempt = (attempts.get(model) ?? 0) + 1;
|
|
512
|
+
attempts.set(model, attempt);
|
|
513
|
+
const entry = opts.messages[model];
|
|
514
|
+
if (entry === undefined)
|
|
515
|
+
return anthropicError(400, "invalid_request_error", `fake: no scenario for model ${JSON.stringify(model)}`);
|
|
516
|
+
if (Array.isArray(entry))
|
|
517
|
+
return entry[Math.min(attempt - 1, entry.length - 1)];
|
|
518
|
+
return await entry(recorded, attempt);
|
|
519
|
+
}
|
|
520
|
+
},
|
|
521
|
+
{
|
|
522
|
+
path: "/v1/models",
|
|
523
|
+
method: "GET",
|
|
524
|
+
handler: async (_req, recorded) => await opts.models?.(recorded) ?? new Response(JSON.stringify({ data: [], has_more: false }), { status: 200, headers: { "content-type": "application/json" } })
|
|
525
|
+
}
|
|
526
|
+
];
|
|
527
|
+
}
|
|
528
|
+
// src/fakes/azure-openai.ts
|
|
529
|
+
var exports_azure_openai = {};
|
|
530
|
+
__export(exports_azure_openai, {
|
|
531
|
+
FAKE_AZURE_KEY: () => FAKE_AZURE_KEY,
|
|
532
|
+
FAKE_ENTRA_TOKEN: () => FAKE_ENTRA_TOKEN,
|
|
533
|
+
apiVersionOf: () => apiVersionOf,
|
|
534
|
+
deploymentOf: () => deploymentOf,
|
|
535
|
+
startAzureFake: () => startAzureFake
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
// src/fakes/openai-chat.ts
|
|
539
|
+
var exports_openai_chat = {};
|
|
540
|
+
__export(exports_openai_chat, {
|
|
541
|
+
chatFrames: () => chatFrames,
|
|
542
|
+
chatModelOf: () => chatModelOf,
|
|
543
|
+
chatStream: () => chatStream,
|
|
544
|
+
deepSeekMissingReasoningError: () => deepSeekMissingReasoningError,
|
|
545
|
+
startOpenAiChatFake: () => startOpenAiChatFake,
|
|
546
|
+
toolAdjacencyRefusal: () => toolAdjacencyRefusal
|
|
547
|
+
});
|
|
548
|
+
function chunk(payload, delayMs) {
|
|
549
|
+
return { data: JSON.stringify(payload), ...delayMs !== undefined ? { delayMs } : {} };
|
|
550
|
+
}
|
|
551
|
+
function chatFrames(script) {
|
|
552
|
+
const delay = script.frameDelayMs;
|
|
553
|
+
const frames = [];
|
|
554
|
+
const id = script.id ?? "chatcmpl-fake-1";
|
|
555
|
+
const model = script.model ?? "fake-model";
|
|
556
|
+
const base = { id, object: "chat.completion.chunk", model };
|
|
557
|
+
frames.push(chunk({ ...base, choices: [{ index: 0, delta: { role: "assistant", content: "" } }] }, delay));
|
|
558
|
+
if (script.inlineError !== undefined) {
|
|
559
|
+
frames.push(chunk({ error: { message: script.inlineError.message, code: script.inlineError.code } }, delay));
|
|
560
|
+
return frames;
|
|
561
|
+
}
|
|
562
|
+
for (const text of script.reasoning ?? []) {
|
|
563
|
+
const field = script.reasoningFieldIsPlain === true ? "reasoning" : "reasoning_content";
|
|
564
|
+
frames.push(chunk({ ...base, choices: [{ index: 0, delta: { [field]: text } }] }, delay));
|
|
565
|
+
}
|
|
566
|
+
for (const call of script.toolCalls ?? []) {
|
|
567
|
+
frames.push(chunk({ ...base, choices: [{ index: 0, delta: { tool_calls: [{ index: call.index, id: call.id, type: "function", function: { name: call.name, arguments: "" } }] } }] }, delay));
|
|
568
|
+
}
|
|
569
|
+
const maxFragments = Math.max(0, ...(script.toolCalls ?? []).map((c) => c.argumentChunks.length));
|
|
570
|
+
for (let position = 0;position < maxFragments; position++) {
|
|
571
|
+
for (const call of script.toolCalls ?? []) {
|
|
572
|
+
const fragment = call.argumentChunks[position];
|
|
573
|
+
if (fragment === undefined)
|
|
574
|
+
continue;
|
|
575
|
+
frames.push(chunk({ ...base, choices: [{ index: 0, delta: { tool_calls: [{ index: call.index, function: { arguments: fragment } }] } }] }, delay));
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
if (script.anonymousToolCall === true) {
|
|
579
|
+
frames.push(chunk({ ...base, choices: [{ index: 0, delta: { tool_calls: [{ index: 7, function: { arguments: "{}" } }] } }] }, delay));
|
|
580
|
+
}
|
|
581
|
+
for (const text of script.text ?? [])
|
|
582
|
+
frames.push(chunk({ ...base, choices: [{ index: 0, delta: { content: text } }] }, delay));
|
|
583
|
+
if (script.omitFinish !== true) {
|
|
584
|
+
frames.push(chunk({ ...base, choices: [{ index: 0, delta: {}, finish_reason: script.finishReason ?? (script.toolCalls !== undefined && script.toolCalls.length > 0 ? "tool_calls" : "stop") }] }, delay));
|
|
585
|
+
}
|
|
586
|
+
if (script.usage !== undefined) {
|
|
587
|
+
frames.push(chunk({
|
|
588
|
+
...base,
|
|
589
|
+
choices: [],
|
|
590
|
+
usage: {
|
|
591
|
+
prompt_tokens: script.usage.prompt,
|
|
592
|
+
completion_tokens: script.usage.completion,
|
|
593
|
+
total_tokens: script.usage.prompt + script.usage.completion,
|
|
594
|
+
...script.usage.cachedPrompt !== undefined ? { prompt_tokens_details: { cached_tokens: script.usage.cachedPrompt } } : {}
|
|
595
|
+
}
|
|
596
|
+
}, delay));
|
|
597
|
+
}
|
|
598
|
+
if (script.omitDone !== true)
|
|
599
|
+
frames.push({ data: "[DONE]", ...delay !== undefined ? { delayMs: delay } : {} });
|
|
600
|
+
return frames;
|
|
601
|
+
}
|
|
602
|
+
function chatStream(script, opts = {}) {
|
|
603
|
+
return sseResponse2(chatFrames(script), opts.dropAfter !== undefined ? { dropAfter: opts.dropAfter } : {});
|
|
604
|
+
}
|
|
605
|
+
function chatModelOf(recorded) {
|
|
606
|
+
try {
|
|
607
|
+
const body = JSON.parse(recorded.body);
|
|
608
|
+
return typeof body.model === "string" ? body.model : undefined;
|
|
609
|
+
} catch {
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
function toolAdjacencyRefusal(body) {
|
|
614
|
+
let parsed;
|
|
615
|
+
try {
|
|
616
|
+
parsed = JSON.parse(body);
|
|
617
|
+
} catch {
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
const messages = Array.isArray(parsed.messages) ? parsed.messages : [];
|
|
621
|
+
for (let i = 0;i < messages.length; i++) {
|
|
622
|
+
const message = messages[i];
|
|
623
|
+
if (message === null || typeof message !== "object" || message.role !== "tool")
|
|
624
|
+
continue;
|
|
625
|
+
const previous = i > 0 ? messages[i - 1] : undefined;
|
|
626
|
+
const previousRole = previous !== null && typeof previous === "object" ? previous.role : undefined;
|
|
627
|
+
const previousCalls = previous !== null && typeof previous === "object" ? previous.tool_calls : undefined;
|
|
628
|
+
const respondsToACall = previousRole === "assistant" && Array.isArray(previousCalls) && previousCalls.length > 0;
|
|
629
|
+
const followsAnotherResult = previousRole === "tool";
|
|
630
|
+
if (respondsToACall || followsAnotherResult)
|
|
631
|
+
continue;
|
|
632
|
+
return jsonResponse2({
|
|
633
|
+
error: {
|
|
634
|
+
message: `Invalid parameter: messages with role 'tool' must be a response to a preceeding message with 'tool_calls'.`,
|
|
635
|
+
type: "invalid_request_error",
|
|
636
|
+
param: `messages[${i}].role`,
|
|
637
|
+
code: null
|
|
638
|
+
}
|
|
639
|
+
}, 400);
|
|
640
|
+
}
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
async function startOpenAiChatFake(opts) {
|
|
644
|
+
const dispatch = scenarioTable2({ modelOf: chatModelOf, scenarios: opts.scenarios, ...opts.unknownModel !== undefined ? { unknownModel: opts.unknownModel } : {} });
|
|
645
|
+
const handler = (req, recorded) => toolAdjacencyRefusal(recorded.body) ?? dispatch(req, recorded);
|
|
646
|
+
return startFake2({
|
|
647
|
+
routes: [
|
|
648
|
+
{ path: "/chat/completions", method: "POST", handler },
|
|
649
|
+
{ path: "/v1/chat/completions", method: "POST", handler },
|
|
650
|
+
{ path: "/openai/deployments/*", method: "POST", handler },
|
|
651
|
+
...opts.routes ?? []
|
|
652
|
+
]
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
function deepSeekMissingReasoningError() {
|
|
656
|
+
return jsonResponse2({
|
|
657
|
+
error: {
|
|
658
|
+
message: "The last assistant message must contain reasoning_content when tools are used. Please pass back all preceding reasoning_content.",
|
|
659
|
+
type: "invalid_request_error",
|
|
660
|
+
param: "messages",
|
|
661
|
+
code: "invalid_request_error"
|
|
662
|
+
}
|
|
663
|
+
}, 400);
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// src/fakes/openai-responses.ts
|
|
667
|
+
var exports_openai_responses = {};
|
|
668
|
+
__export(exports_openai_responses, {
|
|
669
|
+
callPairingRefusal: () => callPairingRefusal,
|
|
670
|
+
htmlErrorResponse: () => htmlErrorResponse,
|
|
671
|
+
notSseResponse: () => notSseResponse,
|
|
672
|
+
openAiErrorBody: () => openAiErrorBody,
|
|
673
|
+
recordedBody: () => recordedBody,
|
|
674
|
+
responsesFrames: () => responsesFrames,
|
|
675
|
+
responsesModelOf: () => responsesModelOf,
|
|
676
|
+
responsesStream: () => responsesStream,
|
|
677
|
+
startOpenAiResponsesFake: () => startOpenAiResponsesFake
|
|
678
|
+
});
|
|
679
|
+
function frame2(payload, delayMs) {
|
|
680
|
+
const event = typeof payload.type === "string" ? payload.type : "message";
|
|
681
|
+
return { event, data: JSON.stringify(payload), ...delayMs !== undefined ? { delayMs } : {} };
|
|
682
|
+
}
|
|
683
|
+
function responsesFrames(script) {
|
|
684
|
+
const delay = script.frameDelayMs;
|
|
685
|
+
const frames = [];
|
|
686
|
+
const responseId = script.id ?? "resp_fake_1";
|
|
687
|
+
frames.push(frame2({ type: "response.created", response: { id: responseId, ...script.model !== undefined ? { model: script.model } : {}, status: "in_progress" } }, delay));
|
|
688
|
+
for (const item of script.reasoningItems ?? []) {
|
|
689
|
+
frames.push(frame2({ type: "response.output_item.added", output_index: item.index, item: { id: `rs_${item.index}`, type: "reasoning", encrypted_content: `PARTIAL-${item.encrypted}`, status: "in_progress" } }, delay));
|
|
690
|
+
}
|
|
691
|
+
for (const text of script.summary ?? [])
|
|
692
|
+
frames.push(frame2({ type: "response.reasoning_summary_text.delta", delta: text }, delay));
|
|
693
|
+
for (const call of script.calls ?? []) {
|
|
694
|
+
frames.push(frame2({ type: "response.output_item.added", output_index: call.index, item: { id: call.itemId, type: "function_call", call_id: call.callId, name: call.name, arguments: "" } }, delay));
|
|
695
|
+
for (const chunk of call.argumentChunks ?? [])
|
|
696
|
+
frames.push(frame2({ type: "response.function_call_arguments.delta", item_id: call.itemId, output_index: call.index, delta: chunk }, delay));
|
|
697
|
+
}
|
|
698
|
+
for (const text of script.text ?? [])
|
|
699
|
+
frames.push(frame2({ type: "response.output_text.delta", delta: text }, delay));
|
|
700
|
+
if (script.unrepresentableCall !== undefined) {
|
|
701
|
+
frames.push(frame2({ type: "response.output_item.done", output_index: 99, item: { id: "unrep_1", type: script.unrepresentableCall, status: "completed" } }, delay));
|
|
702
|
+
}
|
|
703
|
+
for (const item of script.reasoningItems ?? []) {
|
|
704
|
+
frames.push(frame2({
|
|
705
|
+
type: "response.output_item.done",
|
|
706
|
+
output_index: item.index,
|
|
707
|
+
item: {
|
|
708
|
+
id: `rs_${item.index}`,
|
|
709
|
+
type: "reasoning",
|
|
710
|
+
...item.summaryText !== undefined ? { summary: [{ type: "summary_text", text: item.summaryText }] } : {},
|
|
711
|
+
encrypted_content: item.encrypted,
|
|
712
|
+
status: "completed"
|
|
713
|
+
}
|
|
714
|
+
}, delay));
|
|
715
|
+
}
|
|
716
|
+
for (const call of script.calls ?? []) {
|
|
717
|
+
frames.push(frame2({
|
|
718
|
+
type: "response.output_item.done",
|
|
719
|
+
output_index: call.index,
|
|
720
|
+
item: { id: call.itemId, type: "function_call", call_id: call.callId, name: call.name, arguments: call.argumentsJson ?? (call.argumentChunks ?? []).join(""), status: "completed" }
|
|
721
|
+
}, delay));
|
|
722
|
+
}
|
|
723
|
+
if (script.failed !== undefined) {
|
|
724
|
+
frames.push(frame2({ type: "response.failed", response: { id: responseId, status: "failed", error: { code: "server_error", message: script.failed } } }, delay));
|
|
725
|
+
return frames;
|
|
726
|
+
}
|
|
727
|
+
if (script.omitCompleted === true)
|
|
728
|
+
return frames;
|
|
729
|
+
const output = [];
|
|
730
|
+
if (script.refusal === true)
|
|
731
|
+
output.push({ type: "message", role: "assistant", content: [{ type: "refusal", refusal: "I cannot help with that." }] });
|
|
732
|
+
frames.push(frame2({
|
|
733
|
+
type: "response.completed",
|
|
734
|
+
response: {
|
|
735
|
+
id: responseId,
|
|
736
|
+
status: script.incompleteReason !== undefined ? "incomplete" : "completed",
|
|
737
|
+
output,
|
|
738
|
+
...script.incompleteReason !== undefined ? { incomplete_details: { reason: script.incompleteReason } } : {},
|
|
739
|
+
...script.usage !== undefined ? {
|
|
740
|
+
usage: {
|
|
741
|
+
input_tokens: script.usage.input,
|
|
742
|
+
output_tokens: script.usage.output,
|
|
743
|
+
...script.usage.cachedInput !== undefined ? { input_tokens_details: { cached_tokens: script.usage.cachedInput } } : {}
|
|
744
|
+
}
|
|
745
|
+
} : {}
|
|
746
|
+
}
|
|
747
|
+
}, delay));
|
|
748
|
+
return frames;
|
|
749
|
+
}
|
|
750
|
+
function responsesStream(script, opts = {}) {
|
|
751
|
+
return sseResponse2(responsesFrames(script), opts.dropAfter !== undefined ? { dropAfter: opts.dropAfter } : {});
|
|
752
|
+
}
|
|
753
|
+
function responsesModelOf(recorded) {
|
|
754
|
+
try {
|
|
755
|
+
const body = JSON.parse(recorded.body);
|
|
756
|
+
return typeof body.model === "string" ? body.model : undefined;
|
|
757
|
+
} catch {
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
function recordedBody(recorded) {
|
|
762
|
+
return JSON.parse(recorded.body);
|
|
763
|
+
}
|
|
764
|
+
function callPairingRefusal(body) {
|
|
765
|
+
let parsed;
|
|
766
|
+
try {
|
|
767
|
+
parsed = JSON.parse(body);
|
|
768
|
+
} catch {
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
const input = Array.isArray(parsed.input) ? parsed.input : [];
|
|
772
|
+
const typeAt = (index) => {
|
|
773
|
+
const item = input[index];
|
|
774
|
+
return item !== null && typeof item === "object" ? item.type : undefined;
|
|
775
|
+
};
|
|
776
|
+
for (let i = 0;i < input.length; i++) {
|
|
777
|
+
if (typeAt(i) !== "function_call_output")
|
|
778
|
+
continue;
|
|
779
|
+
let previous = i - 1;
|
|
780
|
+
while (previous >= 0 && typeAt(previous) === "function_call_output")
|
|
781
|
+
previous--;
|
|
782
|
+
if (previous >= 0 && typeAt(previous) === "function_call")
|
|
783
|
+
continue;
|
|
784
|
+
return errorResponse2(400, openAiErrorBody(`Item ${i} of type 'function_call_output' must follow the 'function_call' it answers.`, "invalid_value"));
|
|
785
|
+
}
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
async function startOpenAiResponsesFake(opts) {
|
|
789
|
+
const dispatch = scenarioTable2({ modelOf: responsesModelOf, scenarios: opts.scenarios, ...opts.unknownModel !== undefined ? { unknownModel: opts.unknownModel } : {} });
|
|
790
|
+
const handler = (req, recorded) => callPairingRefusal(recorded.body) ?? dispatch(req, recorded);
|
|
791
|
+
return startFake2({
|
|
792
|
+
routes: [
|
|
793
|
+
{ path: "/responses", method: "POST", handler },
|
|
794
|
+
{ path: "/v1/responses", method: "POST", handler },
|
|
795
|
+
{ path: "/openai/v1/responses", method: "POST", handler },
|
|
796
|
+
...opts.routes ?? []
|
|
797
|
+
]
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
function openAiErrorBody(message, code, type = "invalid_request_error") {
|
|
801
|
+
return { error: { message, type, param: null, code } };
|
|
802
|
+
}
|
|
803
|
+
function htmlErrorResponse(status) {
|
|
804
|
+
return new Response("<html><body>gateway error</body></html>", { status, headers: { "content-type": "text/html" } });
|
|
805
|
+
}
|
|
806
|
+
function notSseResponse() {
|
|
807
|
+
return jsonResponse2({ definitely: "not a stream" }, 200);
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
// src/fakes/azure-openai.ts
|
|
811
|
+
var FAKE_AZURE_KEY = "test-key-azure-0000";
|
|
812
|
+
var FAKE_ENTRA_TOKEN = "test-token-entra-0000";
|
|
813
|
+
function deploymentOf(recorded) {
|
|
814
|
+
const match = /^\/openai\/deployments\/([^/]+)\//.exec(recorded.path);
|
|
815
|
+
return match?.[1] === undefined ? undefined : decodeURIComponent(match[1]);
|
|
816
|
+
}
|
|
817
|
+
function apiVersionOf(recorded) {
|
|
818
|
+
return new URLSearchParams(recorded.search).get("api-version") ?? undefined;
|
|
819
|
+
}
|
|
820
|
+
function requireApiVersion(recorded) {
|
|
821
|
+
if (apiVersionOf(recorded) !== undefined)
|
|
822
|
+
return;
|
|
823
|
+
return jsonResponse2({ error: { code: "MissingApiVersionParameter", message: "The api-version query parameter is required." } }, 400);
|
|
824
|
+
}
|
|
825
|
+
function bodyRefusal(recorded) {
|
|
826
|
+
return toolAdjacencyRefusal(recorded.body) ?? callPairingRefusal(recorded.body);
|
|
827
|
+
}
|
|
828
|
+
function dispatch(scenarios, modelOf) {
|
|
829
|
+
const attempts = new Map;
|
|
830
|
+
return (_req, recorded) => {
|
|
831
|
+
const refusal = requireApiVersion(recorded) ?? bodyRefusal(recorded);
|
|
832
|
+
if (refusal !== undefined)
|
|
833
|
+
return refusal;
|
|
834
|
+
const model = modelOf(recorded) ?? deploymentOf(recorded);
|
|
835
|
+
const key = model ?? "";
|
|
836
|
+
const attempt = (attempts.get(key) ?? 0) + 1;
|
|
837
|
+
attempts.set(key, attempt);
|
|
838
|
+
const entry = model !== undefined ? scenarios?.[model] : undefined;
|
|
839
|
+
if (entry === undefined)
|
|
840
|
+
return jsonResponse2({ error: { code: "DeploymentNotFound", message: `fake: no scenario for ${JSON.stringify(model)}` } }, 404);
|
|
841
|
+
if (Array.isArray(entry))
|
|
842
|
+
return entry[Math.min(attempt - 1, entry.length - 1)];
|
|
843
|
+
return entry(recorded, attempt);
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
async function startAzureFake(opts) {
|
|
847
|
+
const models = (_req, recorded) => {
|
|
848
|
+
const refusal = requireApiVersion(recorded);
|
|
849
|
+
if (refusal !== undefined)
|
|
850
|
+
return refusal;
|
|
851
|
+
return jsonResponse2({ object: "list", data: opts.models ?? [] });
|
|
852
|
+
};
|
|
853
|
+
return startFake2({
|
|
854
|
+
routes: [
|
|
855
|
+
{ path: "/openai/deployments/*", method: "POST", handler: dispatch(opts.chatScenarios, chatModelOf) },
|
|
856
|
+
{ path: "/openai/v1/responses", method: "POST", handler: dispatch(opts.responsesScenarios, responsesModelOf) },
|
|
857
|
+
{ path: "/openai/models", method: "GET", handler: models },
|
|
858
|
+
{ path: "/openai/v1/models", method: "GET", handler: models },
|
|
859
|
+
...opts.routes ?? []
|
|
860
|
+
]
|
|
861
|
+
});
|
|
862
|
+
}
|
|
863
|
+
// src/fakes/bedrock.ts
|
|
864
|
+
var exports_bedrock = {};
|
|
865
|
+
__export(exports_bedrock, {
|
|
866
|
+
FAKE_ACCESS_KEY_ID: () => FAKE_ACCESS_KEY_ID,
|
|
867
|
+
FAKE_REGION: () => FAKE_REGION,
|
|
868
|
+
FAKE_SECRET_ACCESS_KEY: () => FAKE_SECRET_ACCESS_KEY,
|
|
869
|
+
bedrockError: () => bedrockError,
|
|
870
|
+
bedrockModelOf: () => bedrockModelOf,
|
|
871
|
+
concatFrames: () => concatFrames,
|
|
872
|
+
converseStreamEvent: () => converseStreamEvent,
|
|
873
|
+
converseStreamException: () => converseStreamException,
|
|
874
|
+
eventStreamResponse: () => eventStreamResponse,
|
|
875
|
+
isStreamingPath: () => isStreamingPath,
|
|
876
|
+
startBedrockFake: () => startBedrockFake,
|
|
877
|
+
textTurnFrames: () => textTurnFrames
|
|
878
|
+
});
|
|
879
|
+
|
|
880
|
+
// ../provider-runtime/src/adapters/bedrock/crc32.ts
|
|
881
|
+
var POLYNOMIAL = 3988292384;
|
|
882
|
+
var table;
|
|
883
|
+
function crcTable() {
|
|
884
|
+
if (table !== undefined)
|
|
885
|
+
return table;
|
|
886
|
+
const built = new Uint32Array(256);
|
|
887
|
+
for (let n = 0;n < 256; n++) {
|
|
888
|
+
let c = n;
|
|
889
|
+
for (let k = 0;k < 8; k++) {
|
|
890
|
+
c = (c & 1) !== 0 ? POLYNOMIAL ^ c >>> 1 : c >>> 1;
|
|
891
|
+
}
|
|
892
|
+
built[n] = c >>> 0;
|
|
893
|
+
}
|
|
894
|
+
table = built;
|
|
895
|
+
return built;
|
|
896
|
+
}
|
|
897
|
+
function crc32(bytes, seed = 0) {
|
|
898
|
+
const t = crcTable();
|
|
899
|
+
let c = (seed ^ 4294967295) >>> 0;
|
|
900
|
+
for (let i = 0;i < bytes.length; i++) {
|
|
901
|
+
c = (t[(c ^ bytes[i]) & 255] ^ c >>> 8) >>> 0;
|
|
902
|
+
}
|
|
903
|
+
return (c ^ 4294967295) >>> 0;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
// ../provider-runtime/src/adapters/bedrock/sigv4.ts
|
|
907
|
+
var SIGV4_ALGORITHM = "AWS4-HMAC-SHA256";
|
|
908
|
+
function awsUriEncode(value, encodeSlash) {
|
|
909
|
+
let out = "";
|
|
910
|
+
for (const byte of new TextEncoder().encode(value)) {
|
|
911
|
+
const char = String.fromCharCode(byte);
|
|
912
|
+
if (byte >= 65 && byte <= 90 || byte >= 97 && byte <= 122 || byte >= 48 && byte <= 57 || char === "-" || char === "_" || char === "." || char === "~") {
|
|
913
|
+
out += char;
|
|
914
|
+
} else if (char === "/" && !encodeSlash) {
|
|
915
|
+
out += "/";
|
|
916
|
+
} else {
|
|
917
|
+
out += `%${byte.toString(16).toUpperCase().padStart(2, "0")}`;
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
return out;
|
|
921
|
+
}
|
|
922
|
+
function canonicalUri(pathname) {
|
|
923
|
+
const segments = [];
|
|
924
|
+
for (const segment of pathname.split("/")) {
|
|
925
|
+
if (segment.length === 0 || segment === ".")
|
|
926
|
+
continue;
|
|
927
|
+
if (segment === "..")
|
|
928
|
+
segments.pop();
|
|
929
|
+
else
|
|
930
|
+
segments.push(segment);
|
|
931
|
+
}
|
|
932
|
+
const normalized = `${pathname.startsWith("/") ? "/" : ""}${segments.join("/")}${segments.length > 0 && pathname.endsWith("/") ? "/" : ""}`;
|
|
933
|
+
if (normalized.length === 0)
|
|
934
|
+
return "/";
|
|
935
|
+
return awsUriEncode(normalized, true).replace(/%2F/g, "/");
|
|
936
|
+
}
|
|
937
|
+
function canonicalQuery(search) {
|
|
938
|
+
const params = new URLSearchParams(search);
|
|
939
|
+
const pairs = [];
|
|
940
|
+
params.forEach((value, name) => {
|
|
941
|
+
pairs.push([awsUriEncode(name, true), awsUriEncode(value, true)]);
|
|
942
|
+
});
|
|
943
|
+
pairs.sort((a, b) => a[0] === b[0] ? a[1] < b[1] ? -1 : a[1] > b[1] ? 1 : 0 : a[0] < b[0] ? -1 : 1);
|
|
944
|
+
return pairs.map(([name, value]) => `${name}=${value}`).join("&");
|
|
945
|
+
}
|
|
946
|
+
function canonicalHeaders(headers) {
|
|
947
|
+
const names = [...new Set(Object.keys(headers).map((n) => n.toLowerCase()))].sort();
|
|
948
|
+
const byLower = new Map(Object.entries(headers).map(([name, value]) => [name.toLowerCase(), value]));
|
|
949
|
+
const canonical = names.map((name) => `${name}:${(byLower.get(name) ?? "").trim().replace(/\s+/g, " ")}
|
|
950
|
+
`).join("");
|
|
951
|
+
return { canonical, signed: names.join(";") };
|
|
952
|
+
}
|
|
953
|
+
async function sha256Hex(bytes) {
|
|
954
|
+
const digest = await crypto.subtle.digest("SHA-256", bytes.slice().buffer);
|
|
955
|
+
return toHex(new Uint8Array(digest));
|
|
956
|
+
}
|
|
957
|
+
function toHex(bytes) {
|
|
958
|
+
let out = "";
|
|
959
|
+
for (const byte of bytes)
|
|
960
|
+
out += byte.toString(16).padStart(2, "0");
|
|
961
|
+
return out;
|
|
962
|
+
}
|
|
963
|
+
async function hmac(key, message) {
|
|
964
|
+
const cryptoKey = await crypto.subtle.importKey("raw", key.slice().buffer, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
965
|
+
return new Uint8Array(await crypto.subtle.sign("HMAC", cryptoKey, new TextEncoder().encode(message)));
|
|
966
|
+
}
|
|
967
|
+
async function signingKey(secretAccessKey, datestamp, region, service) {
|
|
968
|
+
let key = await hmac(new TextEncoder().encode(`AWS4${secretAccessKey}`), datestamp);
|
|
969
|
+
key = await hmac(key, region);
|
|
970
|
+
key = await hmac(key, service);
|
|
971
|
+
return await hmac(key, "aws4_request");
|
|
972
|
+
}
|
|
973
|
+
function buildCanonicalRequest(input) {
|
|
974
|
+
const { canonical, signed } = canonicalHeaders(input.headers);
|
|
975
|
+
const canonicalRequest = [input.method.toUpperCase(), canonicalUri(input.pathname), canonicalQuery(input.search), canonical, signed, input.payloadHash].join(`
|
|
976
|
+
`);
|
|
977
|
+
return { canonicalRequest, signedHeaders: signed };
|
|
978
|
+
}
|
|
979
|
+
async function buildStringToSign(canonicalRequest, stamp, scope) {
|
|
980
|
+
return [SIGV4_ALGORITHM, stamp, scope, await sha256Hex(new TextEncoder().encode(canonicalRequest))].join(`
|
|
981
|
+
`);
|
|
982
|
+
}
|
|
983
|
+
async function computeSignature(secretAccessKey, datestamp, region, service, stringToSign) {
|
|
984
|
+
return toHex(await hmac(await signingKey(secretAccessKey, datestamp, region, service), stringToSign));
|
|
985
|
+
}
|
|
986
|
+
function parseAuthorization(header) {
|
|
987
|
+
if (header === null || header === undefined)
|
|
988
|
+
return;
|
|
989
|
+
if (!header.startsWith(`${SIGV4_ALGORITHM} `))
|
|
990
|
+
return;
|
|
991
|
+
const parts = new Map;
|
|
992
|
+
for (const chunk of header.slice(SIGV4_ALGORITHM.length + 1).split(",")) {
|
|
993
|
+
const eq = chunk.indexOf("=");
|
|
994
|
+
if (eq < 0)
|
|
995
|
+
continue;
|
|
996
|
+
parts.set(chunk.slice(0, eq).trim(), chunk.slice(eq + 1).trim());
|
|
997
|
+
}
|
|
998
|
+
const credential = parts.get("Credential");
|
|
999
|
+
const signedHeaders = parts.get("SignedHeaders");
|
|
1000
|
+
const signature = parts.get("Signature");
|
|
1001
|
+
if (credential === undefined || signedHeaders === undefined || signature === undefined)
|
|
1002
|
+
return;
|
|
1003
|
+
const [accessKeyId, datestamp, region, service, terminator] = credential.split("/");
|
|
1004
|
+
if (accessKeyId === undefined || datestamp === undefined || region === undefined || service === undefined || terminator !== "aws4_request")
|
|
1005
|
+
return;
|
|
1006
|
+
return { accessKeyId, datestamp, region, service, signedHeaders: signedHeaders.split(";"), signature };
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
// ../provider-runtime/src/adapters/bedrock/testing.ts
|
|
1010
|
+
function encodeHeader(header) {
|
|
1011
|
+
const encoder = new TextEncoder;
|
|
1012
|
+
const name = encoder.encode(header.name);
|
|
1013
|
+
const value = encoder.encode(header.value);
|
|
1014
|
+
const out = new Uint8Array(1 + name.length + 1 + 2 + value.length);
|
|
1015
|
+
const view = new DataView(out.buffer);
|
|
1016
|
+
let offset = 0;
|
|
1017
|
+
view.setUint8(offset, name.length);
|
|
1018
|
+
offset += 1;
|
|
1019
|
+
out.set(name, offset);
|
|
1020
|
+
offset += name.length;
|
|
1021
|
+
view.setUint8(offset, 7);
|
|
1022
|
+
offset += 1;
|
|
1023
|
+
view.setUint16(offset, value.length);
|
|
1024
|
+
offset += 2;
|
|
1025
|
+
out.set(value, offset);
|
|
1026
|
+
return out;
|
|
1027
|
+
}
|
|
1028
|
+
function encodeEventStreamMessage(headers, payload) {
|
|
1029
|
+
const encodedHeaders = headers.map(encodeHeader);
|
|
1030
|
+
const headersLength = encodedHeaders.reduce((sum, h) => sum + h.length, 0);
|
|
1031
|
+
const totalLength = 16 + headersLength + payload.length;
|
|
1032
|
+
const out = new Uint8Array(totalLength);
|
|
1033
|
+
const view = new DataView(out.buffer);
|
|
1034
|
+
view.setUint32(0, totalLength);
|
|
1035
|
+
view.setUint32(4, headersLength);
|
|
1036
|
+
view.setUint32(8, crc32(out.subarray(0, 8)));
|
|
1037
|
+
let offset = 16 - 4;
|
|
1038
|
+
for (const header of encodedHeaders) {
|
|
1039
|
+
out.set(header, offset);
|
|
1040
|
+
offset += header.length;
|
|
1041
|
+
}
|
|
1042
|
+
out.set(payload, offset);
|
|
1043
|
+
view.setUint32(totalLength - 4, crc32(out.subarray(0, totalLength - 4)));
|
|
1044
|
+
return out;
|
|
1045
|
+
}
|
|
1046
|
+
function converseStreamEvent(eventType, payload) {
|
|
1047
|
+
return encodeEventStreamMessage([
|
|
1048
|
+
{ name: ":message-type", value: "event" },
|
|
1049
|
+
{ name: ":event-type", value: eventType },
|
|
1050
|
+
{ name: ":content-type", value: "application/json" }
|
|
1051
|
+
], new TextEncoder().encode(JSON.stringify(payload)));
|
|
1052
|
+
}
|
|
1053
|
+
function converseStreamException(exceptionType, payload = {}) {
|
|
1054
|
+
return encodeEventStreamMessage([
|
|
1055
|
+
{ name: ":message-type", value: "exception" },
|
|
1056
|
+
{ name: ":exception-type", value: exceptionType },
|
|
1057
|
+
{ name: ":content-type", value: "application/json" }
|
|
1058
|
+
], new TextEncoder().encode(JSON.stringify(payload)));
|
|
1059
|
+
}
|
|
1060
|
+
function concatFrames(frames) {
|
|
1061
|
+
const total = frames.reduce((sum, f) => sum + f.length, 0);
|
|
1062
|
+
const out = new Uint8Array(total);
|
|
1063
|
+
let offset = 0;
|
|
1064
|
+
for (const frame of frames) {
|
|
1065
|
+
out.set(frame, offset);
|
|
1066
|
+
offset += frame.length;
|
|
1067
|
+
}
|
|
1068
|
+
return out;
|
|
1069
|
+
}
|
|
1070
|
+
async function verifySigV4(input) {
|
|
1071
|
+
const parsed = parseAuthorization(input.headers.get("authorization"));
|
|
1072
|
+
if (parsed === undefined)
|
|
1073
|
+
return { ok: false, reason: "the request carried no parseable AWS4-HMAC-SHA256 Authorization header" };
|
|
1074
|
+
if (input.expectedAccessKeyId !== undefined && parsed.accessKeyId !== input.expectedAccessKeyId) {
|
|
1075
|
+
return { ok: false, reason: `the credential names access key "${parsed.accessKeyId}", which this fake does not know` };
|
|
1076
|
+
}
|
|
1077
|
+
const url = new URL(input.url);
|
|
1078
|
+
const headers = {};
|
|
1079
|
+
for (const name of parsed.signedHeaders) {
|
|
1080
|
+
const value = name === "host" ? url.host : input.headers.get(name);
|
|
1081
|
+
if (value === null || value === undefined)
|
|
1082
|
+
return { ok: false, reason: `the signature covers header "${name}", which the request does not carry` };
|
|
1083
|
+
headers[name] = value;
|
|
1084
|
+
}
|
|
1085
|
+
const payloadHash = await sha256Hex(input.body);
|
|
1086
|
+
const declaredHash = input.headers.get("x-amz-content-sha256");
|
|
1087
|
+
if (declaredHash !== null && declaredHash !== payloadHash) {
|
|
1088
|
+
return { ok: false, reason: "x-amz-content-sha256 does not match the body the server received" };
|
|
1089
|
+
}
|
|
1090
|
+
const stamp = input.headers.get("x-amz-date");
|
|
1091
|
+
if (stamp === null)
|
|
1092
|
+
return { ok: false, reason: "the request carried no x-amz-date" };
|
|
1093
|
+
const { canonicalRequest } = buildCanonicalRequest({ method: input.method, pathname: url.pathname, search: url.search, headers, payloadHash });
|
|
1094
|
+
const scope = `${parsed.datestamp}/${parsed.region}/${parsed.service}/aws4_request`;
|
|
1095
|
+
const stringToSign = await buildStringToSign(canonicalRequest, stamp, scope);
|
|
1096
|
+
const expected = await computeSignature(input.secretAccessKey, parsed.datestamp, parsed.region, parsed.service, stringToSign);
|
|
1097
|
+
if (expected !== parsed.signature)
|
|
1098
|
+
return { ok: false, reason: "the request signature does not match the one this fake computes for it" };
|
|
1099
|
+
return { ok: true, accessKeyId: parsed.accessKeyId };
|
|
1100
|
+
}
|
|
1101
|
+
// ../provider-catalog/src/families.ts
|
|
1102
|
+
var OTHER_FAMILY_ID = "other";
|
|
1103
|
+
var NAMESPACE_PREFIXES = ["models/", "anthropic/", "openai/", "google/", "deepseek/", "deepseek-ai/", "meta-llama/", "meta/", "qwen/", "x-ai/", "xai/", "moonshot/", "moonshotai/", "zai-org/", "z-ai/", "minimax/", "mistralai/", "nvidia/", "cline-pass/", "hf:", "aphrodite/"];
|
|
1104
|
+
var BEDROCK_PREFIXES = ["us.", "eu.", "apac.", "global.", "anthropic.", "openai."];
|
|
1105
|
+
function canonicalModelIdOf(upstreamId) {
|
|
1106
|
+
let id = upstreamId.trim().toLowerCase();
|
|
1107
|
+
const account = /^accounts\/[^/]+\/models\/(.+)$/.exec(id);
|
|
1108
|
+
if (account !== null)
|
|
1109
|
+
id = account[1];
|
|
1110
|
+
let namespaced = true;
|
|
1111
|
+
while (namespaced) {
|
|
1112
|
+
namespaced = false;
|
|
1113
|
+
for (const prefix of NAMESPACE_PREFIXES) {
|
|
1114
|
+
if (id.startsWith(prefix)) {
|
|
1115
|
+
id = id.slice(prefix.length);
|
|
1116
|
+
namespaced = true;
|
|
1117
|
+
break;
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
let stripped = true;
|
|
1122
|
+
while (stripped) {
|
|
1123
|
+
stripped = false;
|
|
1124
|
+
for (const prefix of BEDROCK_PREFIXES) {
|
|
1125
|
+
if (id.startsWith(prefix)) {
|
|
1126
|
+
id = id.slice(prefix.length);
|
|
1127
|
+
stripped = true;
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
id = id.replace(/-(?:v)?\d+:\d+$/, "");
|
|
1132
|
+
if (id.startsWith("zai-glm"))
|
|
1133
|
+
id = id.slice("zai-".length);
|
|
1134
|
+
id = id.replace(/-(\d)-(\d)(?=-|$)/g, "-$1.$2");
|
|
1135
|
+
id = id.replace(/:(\d+[bB])$/, "-$1");
|
|
1136
|
+
return id;
|
|
1137
|
+
}
|
|
1138
|
+
function familyIdOf(canonicalModelId, families) {
|
|
1139
|
+
for (const family of families) {
|
|
1140
|
+
for (const matcher of family.matchers) {
|
|
1141
|
+
if (new RegExp(matcher.pattern).test(canonicalModelId))
|
|
1142
|
+
return family.id;
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
return OTHER_FAMILY_ID;
|
|
1146
|
+
}
|
|
1147
|
+
function stampFamilyFields(rows, families) {
|
|
1148
|
+
return rows.map((row) => {
|
|
1149
|
+
const canonicalModelId = row.canonicalModelId ?? canonicalModelIdOf(row.upstreamId);
|
|
1150
|
+
const modelFamily = row.modelFamily ?? familyIdOf(canonicalModelId, families);
|
|
1151
|
+
return { ...row, canonicalModelId, modelFamily };
|
|
1152
|
+
});
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
// ../provider-catalog/src/validate.ts
|
|
1156
|
+
var SECRET_FIELD_NAME_RE = /^(?:api[_-]?key|apikey|secret|secret[_-]?key|password|passwd|token|access[_-]?token|refresh[_-]?token|id[_-]?token|bearer|private[_-]?key|client[_-]?secret|session[_-]?token|credential|credentials|authorization|auth[_-]?token|aws[_-]?secret[_-]?access[_-]?key|aws[_-]?access[_-]?key[_-]?id)$/i;
|
|
1157
|
+
var SECRET_VALUE_PATTERNS = [
|
|
1158
|
+
{ name: "PEM private key block", re: /-----BEGIN [A-Z ]*PRIVATE KEY-----/ },
|
|
1159
|
+
{ name: "OpenAI/Anthropic-style `sk-` key", re: /\bsk-(?:ant-)?[A-Za-z0-9_-]{16,}/ },
|
|
1160
|
+
{ name: "AWS access key id", re: /\b(?:AKIA|ASIA|AGPA|AIDA|AROA|ANPA|ANVA)[0-9A-Z]{16}\b/ },
|
|
1161
|
+
{ name: "Google API key", re: /\bAIza[0-9A-Za-z_-]{35,}/ },
|
|
1162
|
+
{ name: "GitHub token", re: /\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{20,}|\bgithub_pat_[A-Za-z0-9_]{20,}/ },
|
|
1163
|
+
{ name: "Slack token", re: /\bxox[baprse]-[A-Za-z0-9-]{10,}/ },
|
|
1164
|
+
{ name: "JWT", re: /\bey[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/ },
|
|
1165
|
+
{ name: "inline Bearer credential", re: /\bBearer\s+[A-Za-z0-9._~+/-]{20,}={0,2}/i },
|
|
1166
|
+
{ name: "OpenRouter-style `sk-or-` key", re: /\bsk-or-[A-Za-z0-9_-]{10,}/ }
|
|
1167
|
+
];
|
|
1168
|
+
function scanForSecrets(value, path = "", seen = new Set, depth = 0) {
|
|
1169
|
+
const findings = [];
|
|
1170
|
+
if (depth > 64)
|
|
1171
|
+
return [`${path || "<root>"}: nesting deeper than 64 levels — refusing to scan further`];
|
|
1172
|
+
if (typeof value === "string") {
|
|
1173
|
+
for (const { name, re } of SECRET_VALUE_PATTERNS) {
|
|
1174
|
+
if (re.test(value))
|
|
1175
|
+
findings.push(`${path || "<root>"}: looks like a secret (${name})`);
|
|
1176
|
+
}
|
|
1177
|
+
return findings;
|
|
1178
|
+
}
|
|
1179
|
+
if (value === null || typeof value !== "object")
|
|
1180
|
+
return findings;
|
|
1181
|
+
if (seen.has(value))
|
|
1182
|
+
return findings;
|
|
1183
|
+
seen.add(value);
|
|
1184
|
+
if (Array.isArray(value)) {
|
|
1185
|
+
for (let i = 0;i < value.length; i++)
|
|
1186
|
+
findings.push(...scanForSecrets(value[i], `${path}[${i}]`, seen, depth + 1));
|
|
1187
|
+
return findings;
|
|
1188
|
+
}
|
|
1189
|
+
for (const [k, v] of Object.entries(value)) {
|
|
1190
|
+
const child = path ? `${path}.${k}` : k;
|
|
1191
|
+
if (SECRET_FIELD_NAME_RE.test(k) && typeof v === "string" && v.length > 0) {
|
|
1192
|
+
findings.push(`${child}: a descriptor must never carry a credential-shaped FIELD (\`${k}\`)`);
|
|
1193
|
+
}
|
|
1194
|
+
findings.push(...scanForSecrets(v, child, seen, depth + 1));
|
|
1195
|
+
}
|
|
1196
|
+
return findings;
|
|
1197
|
+
}
|
|
1198
|
+
// ../provider-runtime/src/credentials/types.ts
|
|
1199
|
+
class CredentialResolutionError extends Error {
|
|
1200
|
+
code;
|
|
1201
|
+
constructor(code, message) {
|
|
1202
|
+
super(message);
|
|
1203
|
+
this.name = "CredentialResolutionError";
|
|
1204
|
+
this.code = code;
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
function redactRef(ref) {
|
|
1208
|
+
switch (ref.kind) {
|
|
1209
|
+
case "keychain":
|
|
1210
|
+
return `keychain:${ref.service ?? "<default service>"}/${ref.account}`;
|
|
1211
|
+
case "env":
|
|
1212
|
+
return `env:${ref.name}`;
|
|
1213
|
+
case "file":
|
|
1214
|
+
return `file:${ref.path} (${ref.format}${ref.profile === undefined ? "" : `, profile ${ref.profile}`})`;
|
|
1215
|
+
case "inline":
|
|
1216
|
+
return "inline:***";
|
|
1217
|
+
case "aws-default-chain":
|
|
1218
|
+
return "aws-default-chain";
|
|
1219
|
+
case "none":
|
|
1220
|
+
return "none";
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
function isNoCredential(ref) {
|
|
1224
|
+
return ref.kind === "none";
|
|
1225
|
+
}
|
|
1226
|
+
function unsupported(storeName, ref) {
|
|
1227
|
+
return new CredentialResolutionError("unsupported", `${storeName}: cannot resolve a credential reference of kind "${ref.kind}" (${redactRef(ref)})`);
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
// ../provider-runtime/src/credentials/memory.ts
|
|
1231
|
+
var NAME = "memory credential store";
|
|
1232
|
+
function recordKey(ref) {
|
|
1233
|
+
return `${ref.service ?? ""} ${ref.account}`;
|
|
1234
|
+
}
|
|
1235
|
+
function createMemoryCredentialStore(seed) {
|
|
1236
|
+
const records = new Map;
|
|
1237
|
+
for (const [ref, material] of seed ?? [])
|
|
1238
|
+
records.set(recordKey(ref), material);
|
|
1239
|
+
return {
|
|
1240
|
+
async get(ref) {
|
|
1241
|
+
if (isNoCredential(ref))
|
|
1242
|
+
return null;
|
|
1243
|
+
if (ref.kind === "keychain")
|
|
1244
|
+
return records.get(recordKey(ref)) ?? null;
|
|
1245
|
+
if (ref.kind === "inline")
|
|
1246
|
+
return ref.value.length > 0 ? { kind: "api-key", key: ref.value } : null;
|
|
1247
|
+
throw unsupported(NAME, ref);
|
|
1248
|
+
},
|
|
1249
|
+
async set(ref, material) {
|
|
1250
|
+
records.set(recordKey(ref), material);
|
|
1251
|
+
},
|
|
1252
|
+
async delete(ref) {
|
|
1253
|
+
records.delete(recordKey(ref));
|
|
1254
|
+
},
|
|
1255
|
+
size() {
|
|
1256
|
+
return records.size;
|
|
1257
|
+
}
|
|
1258
|
+
};
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
// ../provider-runtime/src/adapters/openai/testing.ts
|
|
1262
|
+
var TEST_API_KEY = "test-key-openai-0000";
|
|
1263
|
+
function evidence(value, confidence = "verified") {
|
|
1264
|
+
return { value, source: "official-doc", confidence, observedAt: "2026-09-05T00:00:00Z" };
|
|
1265
|
+
}
|
|
1266
|
+
var stampRow = (row) => stampFamilyFields([row], [])[0];
|
|
1267
|
+
function descriptor(overrides = {}) {
|
|
1268
|
+
const key = overrides.key ?? "openai/o4-mini";
|
|
1269
|
+
return stampRow({
|
|
1270
|
+
key,
|
|
1271
|
+
providerId: overrides.providerId ?? key.split("/")[0],
|
|
1272
|
+
upstreamId: overrides.upstreamId ?? key.slice(key.indexOf("/") + 1),
|
|
1273
|
+
displayName: key,
|
|
1274
|
+
aliases: [],
|
|
1275
|
+
endpoints: ["responses"],
|
|
1276
|
+
inputModalities: evidence(overrides.inputModalities ?? ["text", "image"]),
|
|
1277
|
+
outputModalities: evidence(["text"]),
|
|
1278
|
+
toolCalling: evidence(overrides.toolCalling ?? "native"),
|
|
1279
|
+
nativeTools: evidence(true),
|
|
1280
|
+
...overrides.parallelTools !== undefined ? { parallelTools: evidence(overrides.parallelTools) } : {},
|
|
1281
|
+
...overrides.maxOutputTokens !== undefined ? { maxOutputTokens: evidence(overrides.maxOutputTokens) } : {},
|
|
1282
|
+
...overrides.noReasoning === true ? {} : {
|
|
1283
|
+
reasoning: {
|
|
1284
|
+
supported: evidence(true),
|
|
1285
|
+
efforts: overrides.efforts ?? ["low", "medium", "high"],
|
|
1286
|
+
...overrides.defaultEffort !== undefined ? { defaultEffort: overrides.defaultEffort } : {},
|
|
1287
|
+
continuation: overrides.continuation ?? "opaque-provider-state",
|
|
1288
|
+
readableState: evidence(overrides.readableState ?? "summary"),
|
|
1289
|
+
...overrides.summaryValues !== undefined ? { summaryRequest: evidence({ field: "reasoning.summary", values: overrides.summaryValues }) } : {},
|
|
1290
|
+
...overrides.continuationDomain !== undefined ? { continuationDomain: evidence(overrides.continuationDomain) } : {}
|
|
1291
|
+
}
|
|
1292
|
+
},
|
|
1293
|
+
unsupportedParameters: overrides.unsupportedParameters ?? [],
|
|
1294
|
+
status: "candidate"
|
|
1295
|
+
});
|
|
1296
|
+
}
|
|
1297
|
+
var KEYCHAIN_REF = { kind: "keychain", account: "openai:test" };
|
|
1298
|
+
function testContext(opts = {}) {
|
|
1299
|
+
const key = opts.apiKey === undefined ? TEST_API_KEY : opts.apiKey;
|
|
1300
|
+
const credentials = createMemoryCredentialStore(key === null ? [] : [[KEYCHAIN_REF, { kind: "api-key", key }]]);
|
|
1301
|
+
const connection = {
|
|
1302
|
+
providerId: opts.providerId ?? "openai",
|
|
1303
|
+
...opts.baseUrl !== undefined ? { baseUrl: opts.baseUrl } : {},
|
|
1304
|
+
...opts.local !== undefined ? { local: opts.local } : {},
|
|
1305
|
+
...opts.headers !== undefined ? { headers: opts.headers } : {},
|
|
1306
|
+
...opts.deployment !== undefined ? { deployment: opts.deployment } : {},
|
|
1307
|
+
...opts.apiVersion !== undefined ? { apiVersion: opts.apiVersion } : {}
|
|
1308
|
+
};
|
|
1309
|
+
return {
|
|
1310
|
+
connection,
|
|
1311
|
+
credentials,
|
|
1312
|
+
authRef: opts.authRef ?? (key === null ? { kind: "none" } : KEYCHAIN_REF),
|
|
1313
|
+
stallTimeoutMs: opts.stallTimeoutMs ?? 2000,
|
|
1314
|
+
log: (event) => opts.logs?.push(event)
|
|
1315
|
+
};
|
|
1316
|
+
}
|
|
1317
|
+
function testDiscoveryContext(opts = {}) {
|
|
1318
|
+
return {
|
|
1319
|
+
...testContext(opts),
|
|
1320
|
+
...opts.signal !== undefined ? { signal: opts.signal } : {},
|
|
1321
|
+
limits: { maxBytes: opts.maxBytes ?? 1024 * 1024, maxItems: opts.maxItems ?? 100, timeoutMs: opts.timeoutMs ?? 5000 }
|
|
1322
|
+
};
|
|
1323
|
+
}
|
|
1324
|
+
var FAST_RETRY = { maxRetries: 3, random: () => 0.5, sleep: (_ms) => new Promise((r) => setTimeout(r, 1)) };
|
|
1325
|
+
// ../provider-runtime/src/bun-required.ts
|
|
1326
|
+
function brandedInstanceOf(brand) {
|
|
1327
|
+
return (candidate) => typeof candidate === "object" && candidate !== null && (brand in candidate);
|
|
1328
|
+
}
|
|
1329
|
+
var BUN_REQUIRED_BRAND = Symbol.for("@yanlinglabs/winter-provider-runtime:BunRequiredError");
|
|
1330
|
+
|
|
1331
|
+
class BunRequiredError extends Error {
|
|
1332
|
+
name = "BunRequiredError";
|
|
1333
|
+
[BUN_REQUIRED_BRAND] = true;
|
|
1334
|
+
static [Symbol.hasInstance] = brandedInstanceOf(BUN_REQUIRED_BRAND);
|
|
1335
|
+
functionName;
|
|
1336
|
+
bunApi;
|
|
1337
|
+
constructor(functionName, bunApi, detail) {
|
|
1338
|
+
super(`${functionName}() requires the Bun runtime: it uses ${bunApi}, which has no Node equivalent this package implements. ${detail}`);
|
|
1339
|
+
this.functionName = functionName;
|
|
1340
|
+
this.bunApi = bunApi;
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
function hasBunRuntime() {
|
|
1344
|
+
return typeof globalThis.Bun !== "undefined";
|
|
1345
|
+
}
|
|
1346
|
+
function requireBunRuntime(functionName, bunApi, detail) {
|
|
1347
|
+
if (!hasBunRuntime())
|
|
1348
|
+
throw new BunRequiredError(functionName, bunApi, detail);
|
|
1349
|
+
}
|
|
1350
|
+
// ../provider-runtime/src/adapters/openai/xai-oauth.testing.ts
|
|
1351
|
+
var ACCOUNT_ID = "acct-x";
|
|
1352
|
+
var REFRESHED_ACCESS_TOKEN = "test-token-xai-access-refreshed";
|
|
1353
|
+
function fakeIdToken(sub = ACCOUNT_ID) {
|
|
1354
|
+
const b64 = (value) => btoa(JSON.stringify(value)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
1355
|
+
return `${b64({ alg: "ES256", typ: "JWT" })}.${b64({ sub, email: "someone@example.invalid" })}.c2ln`;
|
|
1356
|
+
}
|
|
1357
|
+
async function startXaiOauthFake(opts = {}) {
|
|
1358
|
+
requireBunRuntime("startXaiOauthFake", "Bun.serve", "It binds a loopback HTTP server on 127.0.0.1:0 to stand in for the vendor. Drive it from a Bun test process; a Node consumer has no fake to start.");
|
|
1359
|
+
const requests = [];
|
|
1360
|
+
let polls = 0;
|
|
1361
|
+
const server = Bun.serve({
|
|
1362
|
+
hostname: "127.0.0.1",
|
|
1363
|
+
port: 0,
|
|
1364
|
+
fetch: async (req) => {
|
|
1365
|
+
const url = new URL(req.url);
|
|
1366
|
+
const body = await req.text();
|
|
1367
|
+
const headers = {};
|
|
1368
|
+
for (const [name, value] of req.headers) {
|
|
1369
|
+
headers[name.toLowerCase()] = /^(authorization|x-api-key|api-key)$/i.test(name) ? `${value.split(" ")[0] ?? ""} ***`.trim() : value;
|
|
1370
|
+
}
|
|
1371
|
+
requests.push({ method: req.method, path: url.pathname, headers, body });
|
|
1372
|
+
const form = new URLSearchParams(body);
|
|
1373
|
+
const identity = form.get("referrer");
|
|
1374
|
+
if (identity === null || identity.length === 0) {
|
|
1375
|
+
return Response.json({ error: "invalid_request", error_description: "the client did not identify itself" }, { status: 400 });
|
|
1376
|
+
}
|
|
1377
|
+
if (url.pathname === "/oauth2/device/code") {
|
|
1378
|
+
return Response.json({
|
|
1379
|
+
device_code: "test-device-code-xai",
|
|
1380
|
+
user_code: "WXYZ-1234",
|
|
1381
|
+
verification_uri: "https://example.invalid/activate",
|
|
1382
|
+
verification_uri_complete: "https://example.invalid/activate?user_code=WXYZ-1234",
|
|
1383
|
+
interval: 0,
|
|
1384
|
+
expires_in: 900
|
|
1385
|
+
});
|
|
1386
|
+
}
|
|
1387
|
+
if (url.pathname === "/oauth2/token") {
|
|
1388
|
+
if (form.get("grant_type") === "refresh_token") {
|
|
1389
|
+
return Response.json({ access_token: REFRESHED_ACCESS_TOKEN, expires_in: 3600, token_type: "Bearer" });
|
|
1390
|
+
}
|
|
1391
|
+
if (opts.rejectIdentity !== undefined && identity === opts.rejectIdentity) {
|
|
1392
|
+
return Response.json({ error: "access_denied", error_description: "client not allowed" }, { status: 400 });
|
|
1393
|
+
}
|
|
1394
|
+
if (opts.tokenError !== undefined) {
|
|
1395
|
+
return Response.json({ error: opts.tokenError, error_description: "the fixture's ordinary failure" }, { status: 400 });
|
|
1396
|
+
}
|
|
1397
|
+
polls += 1;
|
|
1398
|
+
if (polls <= (opts.pendingPolls ?? 0))
|
|
1399
|
+
return Response.json({ error: "authorization_pending" }, { status: 400 });
|
|
1400
|
+
return Response.json({
|
|
1401
|
+
access_token: "test-token-xai-access",
|
|
1402
|
+
refresh_token: "test-token-xai-refresh",
|
|
1403
|
+
...opts.omitIdToken === true ? {} : { id_token: fakeIdToken() },
|
|
1404
|
+
expires_in: 3600,
|
|
1405
|
+
token_type: "Bearer"
|
|
1406
|
+
});
|
|
1407
|
+
}
|
|
1408
|
+
return Response.json({ error: "not_found" }, { status: 404 });
|
|
1409
|
+
}
|
|
1410
|
+
});
|
|
1411
|
+
return {
|
|
1412
|
+
deviceCodeUrl: `http://127.0.0.1:${server.port}/oauth2/device/code`,
|
|
1413
|
+
tokenUrl: `http://127.0.0.1:${server.port}/oauth2/token`,
|
|
1414
|
+
requests,
|
|
1415
|
+
close: async () => {
|
|
1416
|
+
await server.stop(true);
|
|
1417
|
+
}
|
|
1418
|
+
};
|
|
1419
|
+
}
|
|
1420
|
+
// ../sdk/src/brand.ts
|
|
1421
|
+
var WINTER_BRAND = Object.freeze({
|
|
1422
|
+
productName: "Winter",
|
|
1423
|
+
packageName: "winter-agent-sdk",
|
|
1424
|
+
homeDirName: ".winter",
|
|
1425
|
+
projectDirName: ".winter",
|
|
1426
|
+
instructionsFile: "WINTER.md",
|
|
1427
|
+
envPrefix: "WINTER_",
|
|
1428
|
+
keychainService: "com.winter.core",
|
|
1429
|
+
mcpServerName: "winter",
|
|
1430
|
+
presetName: "winter_code",
|
|
1431
|
+
processLabel: "winter",
|
|
1432
|
+
codexOriginator: "winter",
|
|
1433
|
+
tempRootName: "winter",
|
|
1434
|
+
pluginManifestDir: ".winter-plugin",
|
|
1435
|
+
contactUrl: "https://github.com/yanlingLabs/winter-agent-sdk"
|
|
1436
|
+
});
|
|
1437
|
+
|
|
1438
|
+
// ../sdk/src/options.ts
|
|
1439
|
+
var DEFAULT_PLANS_DIRECTORY = `${WINTER_BRAND.projectDirName}/plans`;
|
|
1440
|
+
var DEFAULT_KEYCHAIN_SERVICE = WINTER_BRAND.keychainService;
|
|
1441
|
+
// ../sdk/src/messaging/outcomes.ts
|
|
1442
|
+
var DEFAULT_MESSAGE_TTL_MS = 24 * 60 * 60 * 1000;
|
|
1443
|
+
var DEFAULT_HOLD_EXPIRY_MS = 5 * 60 * 1000;
|
|
1444
|
+
var NOTIFY_IDLE_EXPIRY_MS = 12 * 60 * 60 * 1000;
|
|
1445
|
+
// ../sdk/src/messaging/inbound.ts
|
|
1446
|
+
var PROMPTING_MODES = new Set(["default", "acceptEdits", "dontAsk", "auto"]);
|
|
1447
|
+
// ../sdk/src/messaging/router.ts
|
|
1448
|
+
var SUCCESS_CLASS_STATUSES = new Set(["delivered", "queued", "resumed_and_delivered"]);
|
|
1449
|
+
// ../sdk/src/protocol/messaging.ts
|
|
1450
|
+
var MESSAGING_CONTROL_SUBTYPES = {
|
|
1451
|
+
listReachable: "messaging.list_reachable",
|
|
1452
|
+
deliver: "messaging.deliver",
|
|
1453
|
+
steerChild: "messaging.steer_child",
|
|
1454
|
+
resumeChild: "messaging.resume_child",
|
|
1455
|
+
subscribeIdle: "messaging.subscribe_idle",
|
|
1456
|
+
senderClass: "messaging.sender_class",
|
|
1457
|
+
readNotifications: "messaging.read_notifications",
|
|
1458
|
+
idleNotice: "messaging.idle_notice"
|
|
1459
|
+
};
|
|
1460
|
+
var MESSAGING_HOST_REQUEST_SUBTYPES = [
|
|
1461
|
+
MESSAGING_CONTROL_SUBTYPES.listReachable,
|
|
1462
|
+
MESSAGING_CONTROL_SUBTYPES.deliver,
|
|
1463
|
+
MESSAGING_CONTROL_SUBTYPES.steerChild,
|
|
1464
|
+
MESSAGING_CONTROL_SUBTYPES.resumeChild,
|
|
1465
|
+
MESSAGING_CONTROL_SUBTYPES.subscribeIdle,
|
|
1466
|
+
MESSAGING_CONTROL_SUBTYPES.senderClass,
|
|
1467
|
+
MESSAGING_CONTROL_SUBTYPES.readNotifications
|
|
1468
|
+
];
|
|
1469
|
+
var MESSAGING_RUNTIME_REQUEST_SUBTYPES = [MESSAGING_CONTROL_SUBTYPES.idleNotice];
|
|
1470
|
+
var MESSAGING_CONTROL_SUBTYPE_LIST = Object.values(MESSAGING_CONTROL_SUBTYPES);
|
|
1471
|
+
|
|
1472
|
+
// ../sdk/src/query.ts
|
|
1473
|
+
var DEFAULT_MAX_BUFFER_SIZE = 1024 * 1024;
|
|
1474
|
+
// ../sdk/src/store/session-store.ts
|
|
1475
|
+
import {
|
|
1476
|
+
mkdirSync,
|
|
1477
|
+
lstatSync,
|
|
1478
|
+
chmodSync,
|
|
1479
|
+
readdirSync,
|
|
1480
|
+
readFileSync,
|
|
1481
|
+
statSync,
|
|
1482
|
+
openSync,
|
|
1483
|
+
fsyncSync,
|
|
1484
|
+
closeSync,
|
|
1485
|
+
renameSync,
|
|
1486
|
+
rmSync,
|
|
1487
|
+
ftruncateSync,
|
|
1488
|
+
constants as fsConstants
|
|
1489
|
+
} from "node:fs";
|
|
1490
|
+
var APPEND_FLAGS = fsConstants.O_WRONLY | fsConstants.O_APPEND | fsConstants.O_CREAT | fsConstants.O_NOFOLLOW;
|
|
1491
|
+
var RW_EXISTING_FLAGS = fsConstants.O_RDWR | fsConstants.O_NOFOLLOW;
|
|
1492
|
+
// ../sdk/src/settings/model-slots.ts
|
|
1493
|
+
import { CLAUDE_RESERVED_SLOT_NAMES as CLAUDE_RESERVED_SLOT_NAMES2, CURRENCY_RE as CURRENCY_RE2, SLOT_NAME_RE as SLOT_NAME_RE2 } from "@yanlinglabs/winter-provider-catalog/families";
|
|
1494
|
+
// ../provider-runtime/src/adapters/openai/codex-config.ts
|
|
1495
|
+
var CODEX = {
|
|
1496
|
+
clientId: "app_EMoamEEZ73f0CkXaXp7hrann",
|
|
1497
|
+
authorizeUrl: "https://auth.openai.com/oauth/authorize",
|
|
1498
|
+
tokenUrl: "https://auth.openai.com/oauth/token",
|
|
1499
|
+
callbackPort: 1455,
|
|
1500
|
+
fallbackCallbackPort: 1457,
|
|
1501
|
+
scope: "openid profile email offline_access api.connectors.read api.connectors.invoke",
|
|
1502
|
+
backendUrl: "https://chatgpt.com/backend-api/codex",
|
|
1503
|
+
headers: {
|
|
1504
|
+
"OpenAI-Beta": "responses=experimental",
|
|
1505
|
+
originator: WINTER_BRAND.codexOriginator
|
|
1506
|
+
}
|
|
1507
|
+
};
|
|
1508
|
+
var CODEX_ORIGINATOR = WINTER_BRAND.codexOriginator;
|
|
1509
|
+
// ../provider-runtime/src/endpoint-policy.ts
|
|
1510
|
+
import { isIP } from "node:net";
|
|
1511
|
+
|
|
1512
|
+
// ../provider-runtime/src/address-classifier.ts
|
|
1513
|
+
var LOCAL_CLASSES = new Set(["loopback", "private", "link-local", "unique-local"]);
|
|
1514
|
+
function isLocalAddressClass(cls) {
|
|
1515
|
+
return LOCAL_CLASSES.has(cls);
|
|
1516
|
+
}
|
|
1517
|
+
function classifyIPv4(ip) {
|
|
1518
|
+
const parts = ip.split(".").map(Number);
|
|
1519
|
+
if (parts.length !== 4 || parts.some((n) => Number.isNaN(n) || n < 0 || n > 255))
|
|
1520
|
+
return "invalid";
|
|
1521
|
+
const [a, b] = parts;
|
|
1522
|
+
if (a === 0)
|
|
1523
|
+
return "unspecified";
|
|
1524
|
+
if (a === 127)
|
|
1525
|
+
return "loopback";
|
|
1526
|
+
if (a === 10)
|
|
1527
|
+
return "private";
|
|
1528
|
+
if (a === 172 && b >= 16 && b <= 31)
|
|
1529
|
+
return "private";
|
|
1530
|
+
if (a === 192 && b === 168)
|
|
1531
|
+
return "private";
|
|
1532
|
+
if (a === 169 && b === 254)
|
|
1533
|
+
return "link-local";
|
|
1534
|
+
if (a === 100 && b >= 64 && b <= 127)
|
|
1535
|
+
return "cgnat";
|
|
1536
|
+
if (a >= 224 && a <= 239)
|
|
1537
|
+
return "multicast";
|
|
1538
|
+
return "public";
|
|
1539
|
+
}
|
|
1540
|
+
function ipv4FromHexGroups(g1, g2) {
|
|
1541
|
+
const h1 = parseInt(g1, 16);
|
|
1542
|
+
const h2 = parseInt(g2, 16);
|
|
1543
|
+
return `${h1 >> 8 & 255}.${h1 & 255}.${h2 >> 8 & 255}.${h2 & 255}`;
|
|
1544
|
+
}
|
|
1545
|
+
function classifyIPv6(ip) {
|
|
1546
|
+
const lower = ip.toLowerCase();
|
|
1547
|
+
const mappedDotted = /^::ffff:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/.exec(lower);
|
|
1548
|
+
if (mappedDotted)
|
|
1549
|
+
return classifyIPv4(mappedDotted[1]);
|
|
1550
|
+
const mappedHex = /^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/.exec(lower);
|
|
1551
|
+
if (mappedHex)
|
|
1552
|
+
return classifyIPv4(ipv4FromHexGroups(mappedHex[1], mappedHex[2]));
|
|
1553
|
+
if (lower === "::1")
|
|
1554
|
+
return "loopback";
|
|
1555
|
+
if (lower === "::")
|
|
1556
|
+
return "unspecified";
|
|
1557
|
+
const firstGroupText = lower.split(":")[0] ?? "";
|
|
1558
|
+
const firstGroup = firstGroupText.length > 0 ? parseInt(firstGroupText, 16) : NaN;
|
|
1559
|
+
if (!Number.isNaN(firstGroup)) {
|
|
1560
|
+
if (firstGroup >= 65152 && firstGroup <= 65215)
|
|
1561
|
+
return "link-local";
|
|
1562
|
+
if (firstGroup >= 64512 && firstGroup <= 65023)
|
|
1563
|
+
return "unique-local";
|
|
1564
|
+
}
|
|
1565
|
+
return "public";
|
|
1566
|
+
}
|
|
1567
|
+
function classifyAddress(address, family) {
|
|
1568
|
+
return family === 6 ? classifyIPv6(address) : classifyIPv4(address);
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
// ../provider-runtime/src/endpoint-policy.ts
|
|
1572
|
+
function connectionEndpointOptions(connection) {
|
|
1573
|
+
return {
|
|
1574
|
+
generated: connection.endpointOrigin === "reviewed",
|
|
1575
|
+
...connection.local === true ? { local: true } : {}
|
|
1576
|
+
};
|
|
1577
|
+
}
|
|
1578
|
+
var CREDENTIAL_HEADER_NAMES = [
|
|
1579
|
+
"authorization",
|
|
1580
|
+
"proxy-authorization",
|
|
1581
|
+
"cookie",
|
|
1582
|
+
"cookie2",
|
|
1583
|
+
"x-api-key",
|
|
1584
|
+
"api-key",
|
|
1585
|
+
"x-goog-api-key",
|
|
1586
|
+
"x-amz-security-token",
|
|
1587
|
+
"x-amz-date",
|
|
1588
|
+
"x-amz-content-sha256",
|
|
1589
|
+
"openai-organization",
|
|
1590
|
+
"openai-project",
|
|
1591
|
+
"x-goog-user-project"
|
|
1592
|
+
];
|
|
1593
|
+
function stripCredentialHeaders(headers) {
|
|
1594
|
+
const out = new Headers(headers);
|
|
1595
|
+
for (const name of CREDENTIAL_HEADER_NAMES)
|
|
1596
|
+
out.delete(name);
|
|
1597
|
+
return out;
|
|
1598
|
+
}
|
|
1599
|
+
function applyPrivilegedHeaders(policy, headers) {
|
|
1600
|
+
return policy.generated ? { ...headers } : {};
|
|
1601
|
+
}
|
|
1602
|
+
function stripBrackets(hostname) {
|
|
1603
|
+
return hostname.startsWith("[") && hostname.endsWith("]") ? hostname.slice(1, -1) : hostname;
|
|
1604
|
+
}
|
|
1605
|
+
function classifyHost(hostname) {
|
|
1606
|
+
const bare = stripBrackets(hostname);
|
|
1607
|
+
const family = isIP(bare);
|
|
1608
|
+
if (family !== 0)
|
|
1609
|
+
return classifyAddress(bare, family);
|
|
1610
|
+
return bare.toLowerCase() === "localhost" ? "loopback" : undefined;
|
|
1611
|
+
}
|
|
1612
|
+
function evaluateUrlShape(rawUrl, opts) {
|
|
1613
|
+
let url;
|
|
1614
|
+
try {
|
|
1615
|
+
url = new URL(rawUrl);
|
|
1616
|
+
} catch {
|
|
1617
|
+
return { ok: false, reason: `endpoint "${rawUrl}" is not a parseable absolute URL` };
|
|
1618
|
+
}
|
|
1619
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") {
|
|
1620
|
+
return { ok: false, reason: `endpoint "${rawUrl}" has an unsupported scheme "${url.protocol}" — only http and https are endpoints` };
|
|
1621
|
+
}
|
|
1622
|
+
if (url.username.length > 0 || url.password.length > 0) {
|
|
1623
|
+
return { ok: false, reason: `endpoint "${url.origin}${url.pathname}" carries userinfo — a credential must never ride a URL` };
|
|
1624
|
+
}
|
|
1625
|
+
const cls = classifyHost(url.hostname);
|
|
1626
|
+
if (cls === "invalid" || cls === "unspecified" || cls === "multicast" || cls === "cgnat") {
|
|
1627
|
+
return { ok: false, reason: `endpoint "${url.origin}" resolves to a ${cls} address, which is never a provider endpoint` };
|
|
1628
|
+
}
|
|
1629
|
+
const literalLocal = cls !== undefined && isLocalAddressClass(cls);
|
|
1630
|
+
const treatAsLocal = literalLocal && (opts.local === true || opts.generated);
|
|
1631
|
+
if (literalLocal && !treatAsLocal) {
|
|
1632
|
+
return {
|
|
1633
|
+
ok: false,
|
|
1634
|
+
reason: `endpoint "${url.origin}" points at a ${cls} address but the connection is not declared local — set \`connection.local: true\` to reach a local installation deliberately`
|
|
1635
|
+
};
|
|
1636
|
+
}
|
|
1637
|
+
if (url.protocol === "http:" && !treatAsLocal) {
|
|
1638
|
+
return {
|
|
1639
|
+
ok: false,
|
|
1640
|
+
reason: `endpoint "${url.origin}" uses plain http, which is permitted only for a literal loopback/private/link-local address on a connection declared local`
|
|
1641
|
+
};
|
|
1642
|
+
}
|
|
1643
|
+
return { ok: true, origin: url.origin, local: treatAsLocal };
|
|
1644
|
+
}
|
|
1645
|
+
function evaluateEndpoint(baseUrl, opts) {
|
|
1646
|
+
const shape = evaluateUrlShape(baseUrl, opts);
|
|
1647
|
+
if (!shape.ok)
|
|
1648
|
+
return shape;
|
|
1649
|
+
const url = new URL(baseUrl);
|
|
1650
|
+
if (url.search.length > 0) {
|
|
1651
|
+
return { ok: false, reason: `endpoint "${url.origin}${url.pathname}" carries a query string — request parameters belong in the adapter's own request, never in a stored endpoint` };
|
|
1652
|
+
}
|
|
1653
|
+
if (url.hash.length > 0) {
|
|
1654
|
+
return { ok: false, reason: `endpoint "${url.origin}${url.pathname}" carries a fragment, which is meaningless to a request` };
|
|
1655
|
+
}
|
|
1656
|
+
return shape;
|
|
1657
|
+
}
|
|
1658
|
+
function sameHost(a, b) {
|
|
1659
|
+
try {
|
|
1660
|
+
return new URL(a).hostname === new URL(b).hostname;
|
|
1661
|
+
} catch {
|
|
1662
|
+
return false;
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
function createEndpointPolicy(baseUrl, opts) {
|
|
1666
|
+
const evaluated = evaluateEndpoint(baseUrl, opts);
|
|
1667
|
+
if (!evaluated.ok)
|
|
1668
|
+
return evaluated;
|
|
1669
|
+
const { origin, local } = evaluated;
|
|
1670
|
+
const generated = opts.generated;
|
|
1671
|
+
return {
|
|
1672
|
+
ok: true,
|
|
1673
|
+
policy: {
|
|
1674
|
+
origin,
|
|
1675
|
+
local,
|
|
1676
|
+
generated,
|
|
1677
|
+
evaluateRedirect(target) {
|
|
1678
|
+
const strict = evaluateUrlShape(target, { generated: false });
|
|
1679
|
+
if (strict.ok)
|
|
1680
|
+
return { ok: true, origin: strict.origin, sameOrigin: strict.origin === origin };
|
|
1681
|
+
if (local) {
|
|
1682
|
+
const lenient = evaluateUrlShape(target, { generated: false, local: true });
|
|
1683
|
+
if (lenient.ok && sameHost(lenient.origin, origin)) {
|
|
1684
|
+
return { ok: true, origin: lenient.origin, sameOrigin: lenient.origin === origin };
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
return { ok: false, reason: `redirect refused: ${strict.reason}` };
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
};
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
// ../provider-runtime/src/http.ts
|
|
1694
|
+
var DEFAULT_MAX_REDIRECTS = 5;
|
|
1695
|
+
|
|
1696
|
+
class ProviderRequestError extends Error {
|
|
1697
|
+
code;
|
|
1698
|
+
retryable;
|
|
1699
|
+
constructor(fields) {
|
|
1700
|
+
super(fields.message);
|
|
1701
|
+
this.name = "ProviderRequestError";
|
|
1702
|
+
this.code = fields.code;
|
|
1703
|
+
this.retryable = fields.retryable;
|
|
1704
|
+
Object.assign(this, {
|
|
1705
|
+
...fields.status !== undefined ? { status: fields.status } : {},
|
|
1706
|
+
...fields.providerCode !== undefined ? { providerCode: fields.providerCode } : {},
|
|
1707
|
+
...fields.retryAfterMs !== undefined ? { retryAfterMs: fields.retryAfterMs } : {}
|
|
1708
|
+
});
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1712
|
+
class ProviderBodyLimitError extends ProviderRequestError {
|
|
1713
|
+
constructor(maxBodyBytes) {
|
|
1714
|
+
super({ code: "capability", message: `provider response body exceeded the ${maxBodyBytes}-byte limit`, retryable: false });
|
|
1715
|
+
this.name = "ProviderBodyLimitError";
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
function policyRefusal(reason) {
|
|
1719
|
+
return new ProviderRequestError({ code: "capability", message: reason, retryable: false });
|
|
1720
|
+
}
|
|
1721
|
+
function capBody(body, maxBodyBytes, signal) {
|
|
1722
|
+
const reader = body.getReader();
|
|
1723
|
+
let seen = 0;
|
|
1724
|
+
let onAbort;
|
|
1725
|
+
const detach = () => {
|
|
1726
|
+
if (signal !== undefined && onAbort !== undefined)
|
|
1727
|
+
signal.removeEventListener("abort", onAbort);
|
|
1728
|
+
onAbort = undefined;
|
|
1729
|
+
};
|
|
1730
|
+
return new ReadableStream({
|
|
1731
|
+
start(controller) {
|
|
1732
|
+
if (signal === undefined)
|
|
1733
|
+
return;
|
|
1734
|
+
const abortError = () => {
|
|
1735
|
+
const err = new Error("provider response body aborted by the caller");
|
|
1736
|
+
err.name = "AbortError";
|
|
1737
|
+
return err;
|
|
1738
|
+
};
|
|
1739
|
+
if (signal.aborted) {
|
|
1740
|
+
reader.cancel().catch(() => {});
|
|
1741
|
+
controller.error(abortError());
|
|
1742
|
+
return;
|
|
1743
|
+
}
|
|
1744
|
+
onAbort = () => {
|
|
1745
|
+
reader.cancel().catch(() => {});
|
|
1746
|
+
controller.error(abortError());
|
|
1747
|
+
};
|
|
1748
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
1749
|
+
},
|
|
1750
|
+
async pull(controller) {
|
|
1751
|
+
const { done, value } = await reader.read();
|
|
1752
|
+
if (done) {
|
|
1753
|
+
detach();
|
|
1754
|
+
controller.close();
|
|
1755
|
+
return;
|
|
1756
|
+
}
|
|
1757
|
+
seen += value.byteLength;
|
|
1758
|
+
if (seen > maxBodyBytes) {
|
|
1759
|
+
detach();
|
|
1760
|
+
reader.cancel().catch(() => {});
|
|
1761
|
+
controller.error(new ProviderBodyLimitError(maxBodyBytes));
|
|
1762
|
+
return;
|
|
1763
|
+
}
|
|
1764
|
+
controller.enqueue(value);
|
|
1765
|
+
},
|
|
1766
|
+
cancel(reason) {
|
|
1767
|
+
detach();
|
|
1768
|
+
reader.cancel(reason).catch(() => {});
|
|
1769
|
+
}
|
|
1770
|
+
});
|
|
1771
|
+
}
|
|
1772
|
+
function isNonReplayableBody(body) {
|
|
1773
|
+
if (body === undefined || body === null || typeof body !== "object")
|
|
1774
|
+
return false;
|
|
1775
|
+
const candidate = body;
|
|
1776
|
+
return "getReader" in candidate || Symbol.asyncIterator in candidate;
|
|
1777
|
+
}
|
|
1778
|
+
function transportError(err, callerAborted, timedOut, timeoutMs) {
|
|
1779
|
+
if (callerAborted)
|
|
1780
|
+
return new ProviderRequestError({ code: "aborted", message: "provider request aborted by the caller", retryable: false });
|
|
1781
|
+
if (timedOut)
|
|
1782
|
+
return new ProviderRequestError({ code: "timeout", message: `provider did not send response headers within ${timeoutMs}ms`, retryable: true });
|
|
1783
|
+
return new ProviderRequestError({ code: "network", message: err instanceof Error ? err.message : String(err), retryable: true });
|
|
1784
|
+
}
|
|
1785
|
+
async function boundedFetch(url, init) {
|
|
1786
|
+
const maxRedirects = init.maxRedirects ?? DEFAULT_MAX_REDIRECTS;
|
|
1787
|
+
const { timeoutMs, maxBodyBytes, policy, signal, maxRedirects: _ignored, body: initialBody, method: initialMethod, ...requestInit } = init;
|
|
1788
|
+
const firstHop = policy.evaluateRedirect(url);
|
|
1789
|
+
if (!firstHop.ok)
|
|
1790
|
+
throw policyRefusal(firstHop.reason);
|
|
1791
|
+
if (!firstHop.sameOrigin) {
|
|
1792
|
+
throw policyRefusal(`request URL origin ${firstHop.origin} is not this connection's endpoint origin ${policy.origin}`);
|
|
1793
|
+
}
|
|
1794
|
+
let currentUrl = url;
|
|
1795
|
+
let headers = new Headers(requestInit.headers ?? {});
|
|
1796
|
+
let method = initialMethod ?? "GET";
|
|
1797
|
+
let body = initialBody;
|
|
1798
|
+
for (let hop = 0;; hop++) {
|
|
1799
|
+
const controller = new AbortController;
|
|
1800
|
+
let timedOut = false;
|
|
1801
|
+
let callerAborted = signal?.aborted === true;
|
|
1802
|
+
const onCallerAbort = () => {
|
|
1803
|
+
callerAborted = true;
|
|
1804
|
+
controller.abort();
|
|
1805
|
+
};
|
|
1806
|
+
if (callerAborted)
|
|
1807
|
+
throw transportError(undefined, true, false, timeoutMs);
|
|
1808
|
+
signal?.addEventListener("abort", onCallerAbort, { once: true });
|
|
1809
|
+
const timer = setTimeout(() => {
|
|
1810
|
+
timedOut = true;
|
|
1811
|
+
controller.abort();
|
|
1812
|
+
}, timeoutMs);
|
|
1813
|
+
let response;
|
|
1814
|
+
try {
|
|
1815
|
+
response = await fetch(currentUrl, { ...requestInit, method, ...body !== undefined && body !== null ? { body } : {}, headers, redirect: "manual", signal: controller.signal });
|
|
1816
|
+
} catch (err) {
|
|
1817
|
+
throw transportError(err, callerAborted, timedOut, timeoutMs);
|
|
1818
|
+
} finally {
|
|
1819
|
+
clearTimeout(timer);
|
|
1820
|
+
signal?.removeEventListener("abort", onCallerAbort);
|
|
1821
|
+
}
|
|
1822
|
+
const location = response.status >= 300 && response.status < 400 ? response.headers.get("location") : null;
|
|
1823
|
+
if (location === null) {
|
|
1824
|
+
if (response.body === null)
|
|
1825
|
+
return response;
|
|
1826
|
+
return new Response(capBody(response.body, maxBodyBytes, signal), {
|
|
1827
|
+
status: response.status,
|
|
1828
|
+
statusText: response.statusText,
|
|
1829
|
+
headers: response.headers
|
|
1830
|
+
});
|
|
1831
|
+
}
|
|
1832
|
+
if (hop >= maxRedirects)
|
|
1833
|
+
throw policyRefusal(`provider request exceeded ${maxRedirects} redirects — refusing to follow further`);
|
|
1834
|
+
let target;
|
|
1835
|
+
try {
|
|
1836
|
+
target = new URL(location, currentUrl).toString();
|
|
1837
|
+
} catch {
|
|
1838
|
+
throw policyRefusal(`provider returned an unparseable Location header`);
|
|
1839
|
+
}
|
|
1840
|
+
const verdict = policy.evaluateRedirect(target);
|
|
1841
|
+
if (!verdict.ok)
|
|
1842
|
+
throw policyRefusal(verdict.reason);
|
|
1843
|
+
response.body?.cancel().catch(() => {});
|
|
1844
|
+
if (response.status === 303) {
|
|
1845
|
+
method = "GET";
|
|
1846
|
+
body = undefined;
|
|
1847
|
+
headers.delete("content-type");
|
|
1848
|
+
headers.delete("content-length");
|
|
1849
|
+
} else if (isNonReplayableBody(body)) {
|
|
1850
|
+
throw policyRefusal(`provider redirected a request whose body is a stream, which cannot be replayed — buffer the body or point the connection at the final URL`);
|
|
1851
|
+
}
|
|
1852
|
+
if (!verdict.sameOrigin) {
|
|
1853
|
+
if (body !== undefined && body !== null) {
|
|
1854
|
+
throw policyRefusal(`provider redirected a request WITH A BODY from ${policy.origin} to ${verdict.origin}; refusing to re-send the request payload (which may carry conversation content and opaque provider state) to another origin`);
|
|
1855
|
+
}
|
|
1856
|
+
headers = stripCredentialHeaders(headers);
|
|
1857
|
+
}
|
|
1858
|
+
currentUrl = target;
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
// ../provider-runtime/package.json
|
|
1862
|
+
var package_default = {
|
|
1863
|
+
name: "@yanlinglabs/winter-provider-runtime",
|
|
1864
|
+
version: "0.0.2",
|
|
1865
|
+
license: "MIT",
|
|
1866
|
+
type: "module",
|
|
1867
|
+
engines: {
|
|
1868
|
+
node: ">=18"
|
|
1869
|
+
},
|
|
1870
|
+
repository: {
|
|
1871
|
+
type: "git",
|
|
1872
|
+
url: "git+https://github.com/yanlingLabs/winter-agent-sdk.git",
|
|
1873
|
+
directory: "packages/provider-runtime"
|
|
1874
|
+
},
|
|
1875
|
+
homepage: "https://github.com/yanlingLabs/winter-agent-sdk",
|
|
1876
|
+
bugs: {
|
|
1877
|
+
url: "https://github.com/yanlingLabs/winter-agent-sdk/issues"
|
|
1878
|
+
},
|
|
1879
|
+
main: "./dist/index.js",
|
|
1880
|
+
types: "./dist/index.d.ts",
|
|
1881
|
+
exports: {
|
|
1882
|
+
".": {
|
|
1883
|
+
types: "./dist/index.d.ts",
|
|
1884
|
+
bun: "./src/index.ts",
|
|
1885
|
+
default: "./dist/index.js"
|
|
1886
|
+
},
|
|
1887
|
+
"./testing": {
|
|
1888
|
+
types: "./dist/testing.d.ts",
|
|
1889
|
+
bun: "./src/testing.ts",
|
|
1890
|
+
default: "./dist/testing.js"
|
|
1891
|
+
}
|
|
1892
|
+
},
|
|
1893
|
+
files: [
|
|
1894
|
+
"dist",
|
|
1895
|
+
"NOTICE",
|
|
1896
|
+
"README.md",
|
|
1897
|
+
"LICENSE"
|
|
1898
|
+
],
|
|
1899
|
+
scripts: {
|
|
1900
|
+
prepack: `node -e "if(!/\\bpnpm\\//.test(process.env.npm_config_user_agent||''))throw new Error('pack/publish this package with pnpm. publishConfig.exports is a pnpm-only feature: under npm the override is ignored, so the packed manifest keeps a bun condition pointing at src/ -- which files does not ship -- and the tarball is broken for every Bun consumer while installing fine under Node. See RELEASING.md.')"`
|
|
1901
|
+
},
|
|
1902
|
+
publishConfig: {
|
|
1903
|
+
access: "restricted",
|
|
1904
|
+
exports: {
|
|
1905
|
+
".": {
|
|
1906
|
+
types: "./dist/index.d.ts",
|
|
1907
|
+
default: "./dist/index.js"
|
|
1908
|
+
},
|
|
1909
|
+
"./testing": {
|
|
1910
|
+
types: "./dist/testing.d.ts",
|
|
1911
|
+
default: "./dist/testing.js"
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
},
|
|
1915
|
+
winter: {
|
|
1916
|
+
publish: {
|
|
1917
|
+
npm: true
|
|
1918
|
+
}
|
|
1919
|
+
},
|
|
1920
|
+
dependencies: {
|
|
1921
|
+
"@yanlinglabs/winter-agent-sdk": "workspace:*",
|
|
1922
|
+
"@yanlinglabs/winter-provider-catalog": "workspace:*"
|
|
1923
|
+
}
|
|
1924
|
+
};
|
|
1925
|
+
|
|
1926
|
+
// ../provider-runtime/src/identity.ts
|
|
1927
|
+
var DEFAULT_IDENTITY = Object.freeze({ product: WINTER_BRAND.packageName, codexOriginator: WINTER_BRAND.codexOriginator, contactUrl: WINTER_BRAND.contactUrl });
|
|
1928
|
+
var activeIdentity = DEFAULT_IDENTITY;
|
|
1929
|
+
function winterUserAgent() {
|
|
1930
|
+
return `${activeIdentity.product}/${package_default.version}`;
|
|
1931
|
+
}
|
|
1932
|
+
var VERSION_PLACEHOLDER = "<version>";
|
|
1933
|
+
var PRODUCT_PLACEHOLDER = "<product>";
|
|
1934
|
+
var CONTACT_PLACEHOLDER = "<contact>";
|
|
1935
|
+
function renderIdentityHeaders(declared, ctx) {
|
|
1936
|
+
const out = {};
|
|
1937
|
+
for (const [name, value] of Object.entries(declared)) {
|
|
1938
|
+
out[name] = value.split(PRODUCT_PLACEHOLDER).join(ctx.product).split(VERSION_PLACEHOLDER).join(ctx.version).split(CONTACT_PLACEHOLDER).join(ctx.contact);
|
|
1939
|
+
}
|
|
1940
|
+
return out;
|
|
1941
|
+
}
|
|
1942
|
+
function winterIdentityHeaders(lookup, providerId) {
|
|
1943
|
+
const declared = lookup?.(providerId);
|
|
1944
|
+
if (declared === undefined)
|
|
1945
|
+
return {};
|
|
1946
|
+
return renderIdentityHeaders(declared, { version: package_default.version, product: activeIdentity.product, contact: activeIdentity.contactUrl });
|
|
1947
|
+
}
|
|
1948
|
+
// ../provider-runtime/src/errors.ts
|
|
1949
|
+
var BODY_SNIPPET_CHARS = 200;
|
|
1950
|
+
|
|
1951
|
+
class ProviderStallError extends Error {
|
|
1952
|
+
constructor(message) {
|
|
1953
|
+
super(message);
|
|
1954
|
+
this.name = "ProviderStallError";
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
function parseProviderErrorCode(body) {
|
|
1958
|
+
if (!body)
|
|
1959
|
+
return;
|
|
1960
|
+
let parsed;
|
|
1961
|
+
try {
|
|
1962
|
+
parsed = JSON.parse(body);
|
|
1963
|
+
} catch {
|
|
1964
|
+
return;
|
|
1965
|
+
}
|
|
1966
|
+
if (parsed === null || typeof parsed !== "object")
|
|
1967
|
+
return;
|
|
1968
|
+
const err = parsed.error;
|
|
1969
|
+
if (err === null || typeof err !== "object")
|
|
1970
|
+
return;
|
|
1971
|
+
const envelope = err;
|
|
1972
|
+
for (const candidate of [envelope.code, envelope.type, envelope.status]) {
|
|
1973
|
+
if (typeof candidate === "string" && candidate.length > 0)
|
|
1974
|
+
return candidate;
|
|
1975
|
+
}
|
|
1976
|
+
return;
|
|
1977
|
+
}
|
|
1978
|
+
function parseRetryAfterMs(value, now = Date.now()) {
|
|
1979
|
+
if (value === null || value === undefined)
|
|
1980
|
+
return;
|
|
1981
|
+
const trimmed = value.trim();
|
|
1982
|
+
if (trimmed.length === 0)
|
|
1983
|
+
return;
|
|
1984
|
+
if (/^\d+$/.test(trimmed)) {
|
|
1985
|
+
const seconds = Number(trimmed);
|
|
1986
|
+
return Number.isFinite(seconds) && seconds > 0 ? Math.round(seconds * 1000) : undefined;
|
|
1987
|
+
}
|
|
1988
|
+
const at = Date.parse(trimmed);
|
|
1989
|
+
if (Number.isNaN(at))
|
|
1990
|
+
return;
|
|
1991
|
+
const delta = at - now;
|
|
1992
|
+
return delta > 0 ? delta : undefined;
|
|
1993
|
+
}
|
|
1994
|
+
var BILLING_CODES = new Set(["insufficient_quota", "billing_hard_limit_reached", "billing_not_active", "credit_balance_too_low"]);
|
|
1995
|
+
var MODEL_NOT_FOUND_CODES = new Set(["model_not_found", "not_found_error", "NOT_FOUND", "model_not_supported"]);
|
|
1996
|
+
var MAX_OUTPUT_CODES = new Set(["max_tokens_exceeded", "max_output_tokens_exceeded", "string_above_max_length"]);
|
|
1997
|
+
var OVERLOADED_CODES = new Set(["overloaded_error", "overloaded", "server_overloaded", "UNAVAILABLE"]);
|
|
1998
|
+
function scrubbedSnippet(body) {
|
|
1999
|
+
if (body.length === 0)
|
|
2000
|
+
return "";
|
|
2001
|
+
if (scanForSecrets(body).length > 0)
|
|
2002
|
+
return "[redacted: the provider's error body contained a credential-shaped string]";
|
|
2003
|
+
return body.slice(0, BODY_SNIPPET_CHARS);
|
|
2004
|
+
}
|
|
2005
|
+
function redactCredentialMaterial(text, secrets) {
|
|
2006
|
+
let out = text;
|
|
2007
|
+
for (const secret of secrets) {
|
|
2008
|
+
if (secret.length < 8)
|
|
2009
|
+
continue;
|
|
2010
|
+
out = out.split(secret).join("***");
|
|
2011
|
+
}
|
|
2012
|
+
return out;
|
|
2013
|
+
}
|
|
2014
|
+
function normalizeHttpError(status, headers, body, secrets = []) {
|
|
2015
|
+
const providerCode = parseProviderErrorCode(body);
|
|
2016
|
+
const snippet = scrubbedSnippet(redactCredentialMaterial(body, secrets));
|
|
2017
|
+
const message = `HTTP ${status}${snippet.length > 0 ? ` — ${snippet}` : ""}`;
|
|
2018
|
+
const retryAfterMs = parseRetryAfterMs(headers.get("retry-after"));
|
|
2019
|
+
const extra = {
|
|
2020
|
+
...providerCode !== undefined ? { providerCode } : {},
|
|
2021
|
+
...retryAfterMs !== undefined ? { retryAfterMs } : {}
|
|
2022
|
+
};
|
|
2023
|
+
if (status === 401 || status === 403)
|
|
2024
|
+
return { code: "auth", message, status, retryable: false, ...extra };
|
|
2025
|
+
if (status === 429) {
|
|
2026
|
+
const billing = providerCode !== undefined && BILLING_CODES.has(providerCode);
|
|
2027
|
+
return { code: "rate_limit", message, status, retryable: !billing, ...extra };
|
|
2028
|
+
}
|
|
2029
|
+
if (status === 408)
|
|
2030
|
+
return { code: "timeout", message, status, retryable: true, ...extra };
|
|
2031
|
+
if (status >= 500)
|
|
2032
|
+
return { code: "server", message, status, retryable: true, ...extra };
|
|
2033
|
+
if (status >= 400)
|
|
2034
|
+
return { code: "bad_request", message, status, retryable: status === 409, ...extra };
|
|
2035
|
+
return { code: "server", message: `${message} (unexpected non-error status)`, status, retryable: false, ...extra };
|
|
2036
|
+
}
|
|
2037
|
+
function isProviderError(value) {
|
|
2038
|
+
if (value === null || typeof value !== "object")
|
|
2039
|
+
return false;
|
|
2040
|
+
const v = value;
|
|
2041
|
+
return typeof v.code === "string" && typeof v.message === "string" && typeof v.retryable === "boolean";
|
|
2042
|
+
}
|
|
2043
|
+
function normalizeThrown(err) {
|
|
2044
|
+
if (isProviderError(err))
|
|
2045
|
+
return err;
|
|
2046
|
+
if (err instanceof ProviderStallError)
|
|
2047
|
+
return { code: "stall", message: err.message, retryable: false };
|
|
2048
|
+
if (err instanceof Error && (err.name === "AbortError" || err.name === "TimeoutError")) {
|
|
2049
|
+
return err.name === "AbortError" ? { code: "aborted", message: err.message, retryable: false } : { code: "timeout", message: err.message, retryable: true };
|
|
2050
|
+
}
|
|
2051
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2052
|
+
return { code: "network", message, retryable: true };
|
|
2053
|
+
}
|
|
2054
|
+
function toSdkAssistantMessageError(err) {
|
|
2055
|
+
const code = err.providerCode;
|
|
2056
|
+
switch (err.code) {
|
|
2057
|
+
case "auth":
|
|
2058
|
+
return "authentication_failed";
|
|
2059
|
+
case "rate_limit":
|
|
2060
|
+
return code !== undefined && BILLING_CODES.has(code) ? "billing_error" : "rate_limit";
|
|
2061
|
+
case "server":
|
|
2062
|
+
if (err.status === 529)
|
|
2063
|
+
return "overloaded";
|
|
2064
|
+
if (code !== undefined && OVERLOADED_CODES.has(code))
|
|
2065
|
+
return "overloaded";
|
|
2066
|
+
return "server_error";
|
|
2067
|
+
case "bad_request":
|
|
2068
|
+
if (err.status === 402)
|
|
2069
|
+
return "billing_error";
|
|
2070
|
+
if (code !== undefined && MODEL_NOT_FOUND_CODES.has(code))
|
|
2071
|
+
return "model_not_found";
|
|
2072
|
+
if (code !== undefined && MAX_OUTPUT_CODES.has(code))
|
|
2073
|
+
return "max_output_tokens";
|
|
2074
|
+
if (code !== undefined && BILLING_CODES.has(code))
|
|
2075
|
+
return "billing_error";
|
|
2076
|
+
return "invalid_request";
|
|
2077
|
+
case "network":
|
|
2078
|
+
case "timeout":
|
|
2079
|
+
case "stall":
|
|
2080
|
+
case "aborted":
|
|
2081
|
+
case "capability":
|
|
2082
|
+
return "unknown";
|
|
2083
|
+
}
|
|
2084
|
+
}
|
|
2085
|
+
|
|
2086
|
+
// ../provider-runtime/src/sse.ts
|
|
2087
|
+
function abortError() {
|
|
2088
|
+
const err = new Error("provider stream aborted");
|
|
2089
|
+
err.name = "AbortError";
|
|
2090
|
+
return err;
|
|
2091
|
+
}
|
|
2092
|
+
var EVENT_BOUNDARY = /\r\n\r\n|\n\n|\r\r/;
|
|
2093
|
+
var LINE_SPLIT = /\r\n|\n|\r/;
|
|
2094
|
+
async function readWithStall(read, stallTimeoutMs, signal) {
|
|
2095
|
+
let timer;
|
|
2096
|
+
let onAbort;
|
|
2097
|
+
try {
|
|
2098
|
+
return await Promise.race([
|
|
2099
|
+
read,
|
|
2100
|
+
new Promise((_resolve, reject) => {
|
|
2101
|
+
timer = setTimeout(() => reject(new ProviderStallError(`provider stream produced no bytes for ${stallTimeoutMs}ms`)), stallTimeoutMs);
|
|
2102
|
+
if (signal !== undefined) {
|
|
2103
|
+
onAbort = () => reject(abortError());
|
|
2104
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
2105
|
+
}
|
|
2106
|
+
})
|
|
2107
|
+
]);
|
|
2108
|
+
} finally {
|
|
2109
|
+
if (timer !== undefined)
|
|
2110
|
+
clearTimeout(timer);
|
|
2111
|
+
if (signal !== undefined && onAbort !== undefined)
|
|
2112
|
+
signal.removeEventListener("abort", onAbort);
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
2115
|
+
function parseBlock(block) {
|
|
2116
|
+
let eventName;
|
|
2117
|
+
const dataLines = [];
|
|
2118
|
+
for (const line of block.split(LINE_SPLIT)) {
|
|
2119
|
+
if (line.length === 0)
|
|
2120
|
+
continue;
|
|
2121
|
+
if (line.startsWith(":"))
|
|
2122
|
+
continue;
|
|
2123
|
+
const colon = line.indexOf(":");
|
|
2124
|
+
const field = colon < 0 ? line : line.slice(0, colon);
|
|
2125
|
+
let value = colon < 0 ? "" : line.slice(colon + 1);
|
|
2126
|
+
if (value.startsWith(" "))
|
|
2127
|
+
value = value.slice(1);
|
|
2128
|
+
if (field === "data")
|
|
2129
|
+
dataLines.push(value);
|
|
2130
|
+
else if (field === "event")
|
|
2131
|
+
eventName = value;
|
|
2132
|
+
}
|
|
2133
|
+
if (dataLines.length === 0)
|
|
2134
|
+
return;
|
|
2135
|
+
return eventName === undefined ? { data: dataLines.join(`
|
|
2136
|
+
`) } : { event: eventName, data: dataLines.join(`
|
|
2137
|
+
`) };
|
|
2138
|
+
}
|
|
2139
|
+
async function* parseSse(body, opts) {
|
|
2140
|
+
if (opts.signal?.aborted === true)
|
|
2141
|
+
throw abortError();
|
|
2142
|
+
const reader = body.getReader();
|
|
2143
|
+
const decoder = new TextDecoder;
|
|
2144
|
+
let buffer = "";
|
|
2145
|
+
try {
|
|
2146
|
+
for (;; ) {
|
|
2147
|
+
const chunk = await readWithStall(reader.read(), opts.stallTimeoutMs, opts.signal);
|
|
2148
|
+
if (chunk.done)
|
|
2149
|
+
break;
|
|
2150
|
+
const bytes = chunk.value;
|
|
2151
|
+
opts.onBytes?.(bytes.byteLength);
|
|
2152
|
+
buffer += decoder.decode(bytes, { stream: true });
|
|
2153
|
+
for (;; ) {
|
|
2154
|
+
const match = EVENT_BOUNDARY.exec(buffer);
|
|
2155
|
+
if (match === null)
|
|
2156
|
+
break;
|
|
2157
|
+
const block = buffer.slice(0, match.index);
|
|
2158
|
+
buffer = buffer.slice(match.index + match[0].length);
|
|
2159
|
+
const event = parseBlock(block);
|
|
2160
|
+
if (event !== undefined)
|
|
2161
|
+
yield event;
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
2164
|
+
buffer += decoder.decode();
|
|
2165
|
+
const trailing = parseBlock(buffer);
|
|
2166
|
+
if (trailing !== undefined)
|
|
2167
|
+
yield trailing;
|
|
2168
|
+
} finally {
|
|
2169
|
+
reader.cancel().catch(() => {});
|
|
2170
|
+
}
|
|
2171
|
+
}
|
|
2172
|
+
|
|
2173
|
+
// ../provider-runtime/src/adapters/privileged-headers.ts
|
|
2174
|
+
var PRIVILEGED_IDENTITY_HEADERS = ["x-goog-user-project", "x-goog-quota-project", "openai-organization", "openai-project"];
|
|
2175
|
+
var WINTER_IDENTITY_HEADERS = ["user-agent", "client-agent"];
|
|
2176
|
+
function hostHeaders(policy, headers, extraPrivileged = []) {
|
|
2177
|
+
if (headers === undefined)
|
|
2178
|
+
return {};
|
|
2179
|
+
const blocked = new Set((policy.generated ? [...CREDENTIAL_HEADER_NAMES, ...WINTER_IDENTITY_HEADERS] : [...CREDENTIAL_HEADER_NAMES, ...PRIVILEGED_IDENTITY_HEADERS, ...extraPrivileged]).map((name) => name.toLowerCase()));
|
|
2180
|
+
const out = {};
|
|
2181
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
2182
|
+
if (blocked.has(name.toLowerCase()))
|
|
2183
|
+
continue;
|
|
2184
|
+
out[name] = value;
|
|
2185
|
+
}
|
|
2186
|
+
return out;
|
|
2187
|
+
}
|
|
2188
|
+
|
|
2189
|
+
// ../provider-runtime/src/retry.ts
|
|
2190
|
+
var DEFAULT_MAX_RETRIES = 10;
|
|
2191
|
+
var RETRY_BACKOFF_BASE_MS = 1000;
|
|
2192
|
+
var RETRY_BACKOFF_CAP_MS = 30000;
|
|
2193
|
+
var RETRY_AFTER_HONOUR_CEILING_MS = 60000;
|
|
2194
|
+
var defaultSleep = (ms, signal) => new Promise((resolve, reject) => {
|
|
2195
|
+
if (signal?.aborted === true) {
|
|
2196
|
+
const err = new Error("retry backoff aborted");
|
|
2197
|
+
err.name = "AbortError";
|
|
2198
|
+
reject(err);
|
|
2199
|
+
return;
|
|
2200
|
+
}
|
|
2201
|
+
const timer = setTimeout(() => {
|
|
2202
|
+
signal?.removeEventListener("abort", onAbort);
|
|
2203
|
+
resolve();
|
|
2204
|
+
}, ms);
|
|
2205
|
+
function onAbort() {
|
|
2206
|
+
clearTimeout(timer);
|
|
2207
|
+
const err = new Error("retry backoff aborted");
|
|
2208
|
+
err.name = "AbortError";
|
|
2209
|
+
reject(err);
|
|
2210
|
+
}
|
|
2211
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
2212
|
+
});
|
|
2213
|
+
function createRetryPolicy(opts = {}) {
|
|
2214
|
+
const maxRetries = opts.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
2215
|
+
const random = opts.random ?? Math.random;
|
|
2216
|
+
const sleep = opts.sleep ?? defaultSleep;
|
|
2217
|
+
let committed = false;
|
|
2218
|
+
return {
|
|
2219
|
+
maxRetries,
|
|
2220
|
+
get committed() {
|
|
2221
|
+
return committed;
|
|
2222
|
+
},
|
|
2223
|
+
commit() {
|
|
2224
|
+
committed = true;
|
|
2225
|
+
},
|
|
2226
|
+
delayMs(attempt, retryAfterMs) {
|
|
2227
|
+
if (retryAfterMs !== undefined && retryAfterMs > 0)
|
|
2228
|
+
return Math.min(retryAfterMs, RETRY_AFTER_HONOUR_CEILING_MS);
|
|
2229
|
+
const ceiling = Math.min(RETRY_BACKOFF_CAP_MS, RETRY_BACKOFF_BASE_MS * 2 ** Math.max(0, attempt - 1));
|
|
2230
|
+
return Math.round(random() * ceiling);
|
|
2231
|
+
},
|
|
2232
|
+
sleep
|
|
2233
|
+
};
|
|
2234
|
+
}
|
|
2235
|
+
async function withRetry(attempt, policy, onRetry, signal) {
|
|
2236
|
+
for (let n = 1;; n++) {
|
|
2237
|
+
try {
|
|
2238
|
+
return await attempt(n);
|
|
2239
|
+
} catch (err) {
|
|
2240
|
+
const normalized = normalizeThrown(err);
|
|
2241
|
+
if (policy.committed || !normalized.retryable || n > policy.maxRetries)
|
|
2242
|
+
throw err;
|
|
2243
|
+
const delayMs = policy.delayMs(n, normalized.retryAfterMs);
|
|
2244
|
+
onRetry({
|
|
2245
|
+
type: "retry",
|
|
2246
|
+
attempt: n,
|
|
2247
|
+
maxRetries: policy.maxRetries,
|
|
2248
|
+
retryDelayMs: delayMs,
|
|
2249
|
+
...normalized.status !== undefined ? { errorStatus: normalized.status } : {},
|
|
2250
|
+
error: toSdkAssistantMessageError(normalized)
|
|
2251
|
+
});
|
|
2252
|
+
await policy.sleep(delayMs, signal);
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2255
|
+
}
|
|
2256
|
+
|
|
2257
|
+
// ../provider-runtime/src/adapters/openai/shared.ts
|
|
2258
|
+
var DEFAULT_STREAM_BODY_BYTES = 32 * 1024 * 1024;
|
|
2259
|
+
var DEFAULT_HEADER_TIMEOUT_MS = 60000;
|
|
2260
|
+
var MAX_DISCOVERY_PAGES = 20;
|
|
2261
|
+
function capabilityRefusal(reason) {
|
|
2262
|
+
return new ProviderRequestError({ code: "capability", message: reason, retryable: false });
|
|
2263
|
+
}
|
|
2264
|
+
function badRequestRefusal(reason) {
|
|
2265
|
+
return new ProviderRequestError({ code: "bad_request", message: reason, retryable: false });
|
|
2266
|
+
}
|
|
2267
|
+
function THINKING_ENABLED_NEEDS_EFFORT(model) {
|
|
2268
|
+
return `thinking \`{ type: "enabled" }\` carries no effort, and model "${model}" declares no defaultEffort to fall back on. Pass \`effort\`, or select a model whose row records one.`;
|
|
2269
|
+
}
|
|
2270
|
+
function trimSlash(url) {
|
|
2271
|
+
return url.endsWith("/") ? url.slice(0, -1) : url;
|
|
2272
|
+
}
|
|
2273
|
+
function resolveEndpoint(ctx, options, fallbackGeneratedBaseUrl) {
|
|
2274
|
+
const userBase = ctx.connection.baseUrl;
|
|
2275
|
+
const generatedBase = options.generatedBaseUrl ?? fallbackGeneratedBaseUrl;
|
|
2276
|
+
if (userBase !== undefined && userBase.length > 0) {
|
|
2277
|
+
const opts = connectionEndpointOptions(ctx.connection);
|
|
2278
|
+
const built = createEndpointPolicy(userBase, opts);
|
|
2279
|
+
if (!built.ok)
|
|
2280
|
+
throw capabilityRefusal(built.reason);
|
|
2281
|
+
return { baseUrl: trimSlash(userBase), policy: built.policy, generated: opts.generated };
|
|
2282
|
+
}
|
|
2283
|
+
if (generatedBase === undefined) {
|
|
2284
|
+
throw capabilityRefusal(`provider "${ctx.connection.providerId}" has no endpoint: this adapter has no generated default, so \`connection.baseUrl\` must name the server (and \`connection.local: true\` for a local installation)`);
|
|
2285
|
+
}
|
|
2286
|
+
const built = createEndpointPolicy(generatedBase, { generated: true });
|
|
2287
|
+
if (!built.ok)
|
|
2288
|
+
throw capabilityRefusal(built.reason);
|
|
2289
|
+
return { baseUrl: trimSlash(generatedBase), policy: built.policy, generated: true };
|
|
2290
|
+
}
|
|
2291
|
+
async function resolveAuth(ctx, style) {
|
|
2292
|
+
const material = await ctx.credentials.get(ctx.authRef);
|
|
2293
|
+
if (material === null)
|
|
2294
|
+
return { headers: {}, material: null };
|
|
2295
|
+
switch (material.kind) {
|
|
2296
|
+
case "api-key":
|
|
2297
|
+
return style === "azure-api-key" ? { headers: { "api-key": material.key }, material } : { headers: { authorization: `Bearer ${material.key}` }, material };
|
|
2298
|
+
case "bearer":
|
|
2299
|
+
return { headers: { authorization: `Bearer ${material.token}` }, material };
|
|
2300
|
+
case "oauth":
|
|
2301
|
+
return {
|
|
2302
|
+
headers: { authorization: `Bearer ${material.accessToken}` },
|
|
2303
|
+
material
|
|
2304
|
+
};
|
|
2305
|
+
default:
|
|
2306
|
+
throw capabilityRefusal(`the OpenAI family cannot use a credential of kind "${material.kind}" — it speaks api-key, bearer and oauth only`);
|
|
2307
|
+
}
|
|
2308
|
+
}
|
|
2309
|
+
function buildHeaders(plan) {
|
|
2310
|
+
const out = { "user-agent": winterUserAgent(), ...plan.identity ?? {} };
|
|
2311
|
+
Object.assign(out, hostHeaders(plan.policy, plan.userSupplied));
|
|
2312
|
+
Object.assign(out, applyPrivilegedHeaders(plan.policy, plan.privileged ?? {}));
|
|
2313
|
+
Object.assign(out, plan.protocol);
|
|
2314
|
+
return out;
|
|
2315
|
+
}
|
|
2316
|
+
function identityFor(options, ctx) {
|
|
2317
|
+
return winterIdentityHeaders(options.identityHeaders, ctx.connection.providerId);
|
|
2318
|
+
}
|
|
2319
|
+
var EFFORT_LADDER = ["low", "medium", "high", "xhigh", "max"];
|
|
2320
|
+
function snapNumericEffort(value, verified) {
|
|
2321
|
+
const available = EFFORT_LADDER.map((tier, index) => ({ tier, index })).filter((t) => verified.includes(t.tier));
|
|
2322
|
+
if (available.length === 0)
|
|
2323
|
+
return;
|
|
2324
|
+
const clamped = Math.min(EFFORT_LADDER.length, Math.max(1, Math.round(value)));
|
|
2325
|
+
const wanted = clamped - 1;
|
|
2326
|
+
let best = available[0];
|
|
2327
|
+
for (const candidate of available) {
|
|
2328
|
+
const better = Math.abs(candidate.index - wanted) < Math.abs(best.index - wanted);
|
|
2329
|
+
if (better)
|
|
2330
|
+
best = candidate;
|
|
2331
|
+
}
|
|
2332
|
+
return best.tier;
|
|
2333
|
+
}
|
|
2334
|
+
function mapEffortAgainst(effort, descriptor) {
|
|
2335
|
+
if (effort === undefined)
|
|
2336
|
+
return { ok: true, value: undefined };
|
|
2337
|
+
if (descriptor === undefined) {
|
|
2338
|
+
if (typeof effort === "number") {
|
|
2339
|
+
return { ok: false, reason: `a numeric effort (${effort}) cannot be mapped for an unlisted model: snapping it to the nearest verified tier needs the model's own effort vocabulary, and this model has no catalog descriptor` };
|
|
2340
|
+
}
|
|
2341
|
+
return { ok: true, value: effort };
|
|
2342
|
+
}
|
|
2343
|
+
const verified = descriptor.reasoning?.efforts ?? [];
|
|
2344
|
+
if (descriptor.reasoning === undefined || verified.length === 0) {
|
|
2345
|
+
return {
|
|
2346
|
+
ok: false,
|
|
2347
|
+
reason: `model "${descriptor.key}" declares no reasoning effort vocabulary, so effort ${JSON.stringify(effort)} cannot be mapped onto it — Winter rejects the selection rather than silently sending the provider's default (WS-13 §8.2)`
|
|
2348
|
+
};
|
|
2349
|
+
}
|
|
2350
|
+
if (typeof effort === "number") {
|
|
2351
|
+
const snapped = snapNumericEffort(effort, verified);
|
|
2352
|
+
if (snapped === undefined)
|
|
2353
|
+
return { ok: false, reason: `model "${descriptor.key}" verifies no tier of the pinned effort ladder, so numeric effort ${effort} has nothing to snap to` };
|
|
2354
|
+
return { ok: true, value: snapped };
|
|
2355
|
+
}
|
|
2356
|
+
if (!verified.includes(effort)) {
|
|
2357
|
+
return { ok: false, reason: `effort "${effort}" is not in model "${descriptor.key}"'s verified vocabulary [${verified.join(", ")}] — rejected before the request (WS-13 §8.2)` };
|
|
2358
|
+
}
|
|
2359
|
+
return { ok: true, value: effort };
|
|
2360
|
+
}
|
|
2361
|
+
function resolveReasoning(req, descriptor) {
|
|
2362
|
+
const thinking = req.thinking;
|
|
2363
|
+
if (thinking?.type === "disabled") {
|
|
2364
|
+
if (req.effort !== undefined) {
|
|
2365
|
+
throw capabilityRefusal(`thinking is disabled for this turn but an effort (${JSON.stringify(req.effort)}) was also requested — the two contradict, and Winter refuses rather than picking one`);
|
|
2366
|
+
}
|
|
2367
|
+
return { wantsEncryptedContent: false, enabled: false };
|
|
2368
|
+
}
|
|
2369
|
+
if (thinking?.type === "enabled" && thinking.budgetTokens !== undefined) {
|
|
2370
|
+
throw capabilityRefusal(`thinking { type: "enabled", budgetTokens: ${thinking.budgetTokens} } cannot be represented on an OpenAI-family surface: reasoning here is EFFORT-controlled and has no token-budget field, so honouring this would mean silently dropping the budget (WS-13 §8.2)`);
|
|
2371
|
+
}
|
|
2372
|
+
const reasoningEvidence = descriptor?.reasoning;
|
|
2373
|
+
if (thinking !== undefined && descriptor !== undefined && reasoningEvidence === undefined) {
|
|
2374
|
+
throw capabilityRefusal(`model "${descriptor.key}" declares no reasoning capability, so a \`thinking\` configuration cannot be honoured — rejected before the request`);
|
|
2375
|
+
}
|
|
2376
|
+
const mapped = mapEffortAgainst(req.effort, descriptor);
|
|
2377
|
+
if (!mapped.ok)
|
|
2378
|
+
throw capabilityRefusal(mapped.reason);
|
|
2379
|
+
const effort = mapped.value ?? (thinking !== undefined ? reasoningEvidence?.defaultEffort : undefined);
|
|
2380
|
+
const reasoningRequested = effort !== undefined || thinking?.type === "adaptive" || thinking?.type === "enabled";
|
|
2381
|
+
if (reasoningRequested && effort === undefined) {
|
|
2382
|
+
throw capabilityRefusal(THINKING_ENABLED_NEEDS_EFFORT(descriptor?.key ?? req.model));
|
|
2383
|
+
}
|
|
2384
|
+
const summaryEvidence = reasoningEvidence?.summaryRequest?.value;
|
|
2385
|
+
const summary = req.requestSummary === true && reasoningRequested && summaryEvidence !== undefined && summaryEvidence.field === "reasoning.summary" && summaryEvidence.values.length > 0 ? summaryEvidence.values[0] : undefined;
|
|
2386
|
+
return {
|
|
2387
|
+
...effort !== undefined ? { effort } : {},
|
|
2388
|
+
...summary !== undefined ? { summary } : {},
|
|
2389
|
+
wantsEncryptedContent: reasoningRequested,
|
|
2390
|
+
enabled: true
|
|
2391
|
+
};
|
|
2392
|
+
}
|
|
2393
|
+
function assertWithinLimits(req, descriptor, parametersInPlay) {
|
|
2394
|
+
if (descriptor === undefined)
|
|
2395
|
+
return;
|
|
2396
|
+
const maxOutput = descriptor.maxOutputTokens?.value;
|
|
2397
|
+
if (req.maxOutputTokens !== undefined && maxOutput !== undefined && req.maxOutputTokens > maxOutput) {
|
|
2398
|
+
throw capabilityRefusal(`requested ${req.maxOutputTokens} output tokens but model "${descriptor.key}" declares a maximum of ${maxOutput} — rejected before the request rather than failed upstream`);
|
|
2399
|
+
}
|
|
2400
|
+
const unsupported = descriptor.unsupportedParameters ?? [];
|
|
2401
|
+
for (const parameter of parametersInPlay) {
|
|
2402
|
+
if (unsupported.includes(parameter)) {
|
|
2403
|
+
throw capabilityRefusal(`model "${descriptor.key}" lists "${parameter}" among its unsupported parameters, and this request would send it — rejected before the request (WS-13 §8.2)`);
|
|
2404
|
+
}
|
|
2405
|
+
}
|
|
2406
|
+
}
|
|
2407
|
+
function capabilitiesFrom(descriptor) {
|
|
2408
|
+
const members = descriptor.reasoning?.continuationDomain?.value;
|
|
2409
|
+
const domain = descriptor.reasoning === undefined || descriptor.reasoning.continuation === "none" ? undefined : members !== undefined && members.length > 0 ? [...members].sort()[0] : descriptor.key;
|
|
2410
|
+
return {
|
|
2411
|
+
toolCalling: descriptor.toolCalling.value,
|
|
2412
|
+
...domain !== undefined ? { continuationDomain: domain } : {},
|
|
2413
|
+
readableState: descriptor.reasoning?.readableState?.value ?? "none"
|
|
2414
|
+
};
|
|
2415
|
+
}
|
|
2416
|
+
function assertRepresentableTools(tools) {
|
|
2417
|
+
for (const tool of tools ?? []) {
|
|
2418
|
+
if (typeof tool.name !== "string" || tool.name.length === 0) {
|
|
2419
|
+
throw badRequestRefusal(`a tool with no name cannot be represented on an OpenAI-family surface — Winter refuses the turn rather than dropping the tool silently (WS-13 §9)`);
|
|
2420
|
+
}
|
|
2421
|
+
if (tool.inputSchema === null || typeof tool.inputSchema !== "object" || Array.isArray(tool.inputSchema)) {
|
|
2422
|
+
throw badRequestRefusal(`tool "${tool.name}" has an input schema that is not a JSON-Schema object, so its constraints cannot be represented — Winter refuses the turn rather than dropping the tool silently (WS-13 §9)`);
|
|
2423
|
+
}
|
|
2424
|
+
}
|
|
2425
|
+
}
|
|
2426
|
+
function asBlocks(content) {
|
|
2427
|
+
return typeof content === "string" ? [{ type: "text", text: content }] : content;
|
|
2428
|
+
}
|
|
2429
|
+
function toolResultText(content) {
|
|
2430
|
+
if (typeof content === "string")
|
|
2431
|
+
return content;
|
|
2432
|
+
return content.map((block) => block.type === "text" ? block.text : block.type === "image" ? "[image]" : "").filter((s) => s.length > 0).join(`
|
|
2433
|
+
`);
|
|
2434
|
+
}
|
|
2435
|
+
function decorationText(message) {
|
|
2436
|
+
const decoration = message.decoration;
|
|
2437
|
+
if (decoration === undefined || decoration.text.length === 0)
|
|
2438
|
+
return;
|
|
2439
|
+
return decoration.text;
|
|
2440
|
+
}
|
|
2441
|
+
function prefixToolResult(decoration, output) {
|
|
2442
|
+
return decoration === undefined ? output : output.length > 0 ? `${decoration}
|
|
2443
|
+
${output}` : decoration;
|
|
2444
|
+
}
|
|
2445
|
+
function imageDataUrl(block) {
|
|
2446
|
+
return `data:${block.source.media_type};base64,${block.source.data}`;
|
|
2447
|
+
}
|
|
2448
|
+
|
|
2449
|
+
class EventQueue {
|
|
2450
|
+
items = [];
|
|
2451
|
+
waker;
|
|
2452
|
+
push(event) {
|
|
2453
|
+
this.items.push(event);
|
|
2454
|
+
this.wake();
|
|
2455
|
+
}
|
|
2456
|
+
wake() {
|
|
2457
|
+
const waker = this.waker;
|
|
2458
|
+
this.waker = undefined;
|
|
2459
|
+
waker?.();
|
|
2460
|
+
}
|
|
2461
|
+
drain() {
|
|
2462
|
+
if (this.items.length === 0)
|
|
2463
|
+
return [];
|
|
2464
|
+
const out = this.items;
|
|
2465
|
+
this.items = [];
|
|
2466
|
+
return out;
|
|
2467
|
+
}
|
|
2468
|
+
wait() {
|
|
2469
|
+
return new Promise((resolve) => {
|
|
2470
|
+
this.waker = resolve;
|
|
2471
|
+
});
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2474
|
+
async function* pumpEvents(queue, work) {
|
|
2475
|
+
let finished = false;
|
|
2476
|
+
const settled = work.then((value) => {
|
|
2477
|
+
finished = true;
|
|
2478
|
+
queue.wake();
|
|
2479
|
+
return { ok: true, value };
|
|
2480
|
+
}, (error) => {
|
|
2481
|
+
finished = true;
|
|
2482
|
+
queue.wake();
|
|
2483
|
+
return { ok: false, error };
|
|
2484
|
+
});
|
|
2485
|
+
for (;; ) {
|
|
2486
|
+
for (const event of queue.drain())
|
|
2487
|
+
yield event;
|
|
2488
|
+
if (finished)
|
|
2489
|
+
break;
|
|
2490
|
+
await Promise.race([queue.wait(), settled]);
|
|
2491
|
+
}
|
|
2492
|
+
for (const event of queue.drain())
|
|
2493
|
+
yield event;
|
|
2494
|
+
const result = await settled;
|
|
2495
|
+
if (!result.ok)
|
|
2496
|
+
throw result.error;
|
|
2497
|
+
return result.value;
|
|
2498
|
+
}
|
|
2499
|
+
async function openStream(plan, policy, onEvent) {
|
|
2500
|
+
const maxBodyBytes = plan.options.maxBodyBytes ?? DEFAULT_STREAM_BODY_BYTES;
|
|
2501
|
+
const timeoutMs = plan.options.headerTimeoutMs ?? DEFAULT_HEADER_TIMEOUT_MS;
|
|
2502
|
+
let headers = plan.headers;
|
|
2503
|
+
return withRetry(async (attempt) => {
|
|
2504
|
+
await plan.beforeAttempt?.(attempt);
|
|
2505
|
+
let response = await boundedFetch(plan.url, {
|
|
2506
|
+
method: "POST",
|
|
2507
|
+
headers,
|
|
2508
|
+
body: plan.body,
|
|
2509
|
+
policy: plan.policy,
|
|
2510
|
+
maxBodyBytes,
|
|
2511
|
+
timeoutMs,
|
|
2512
|
+
...plan.signal !== undefined ? { signal: plan.signal } : {}
|
|
2513
|
+
});
|
|
2514
|
+
if (!response.ok && plan.recover !== undefined) {
|
|
2515
|
+
const recovered = await plan.recover(response.status, attempt);
|
|
2516
|
+
if (recovered !== undefined) {
|
|
2517
|
+
response.body?.cancel().catch(() => {});
|
|
2518
|
+
headers = recovered;
|
|
2519
|
+
response = await boundedFetch(plan.url, {
|
|
2520
|
+
method: "POST",
|
|
2521
|
+
headers,
|
|
2522
|
+
body: plan.body,
|
|
2523
|
+
policy: plan.policy,
|
|
2524
|
+
maxBodyBytes,
|
|
2525
|
+
timeoutMs,
|
|
2526
|
+
...plan.signal !== undefined ? { signal: plan.signal } : {}
|
|
2527
|
+
});
|
|
2528
|
+
}
|
|
2529
|
+
}
|
|
2530
|
+
if (!response.ok) {
|
|
2531
|
+
plan.onRefused?.(response);
|
|
2532
|
+
throw await httpErrorFrom(response);
|
|
2533
|
+
}
|
|
2534
|
+
return response;
|
|
2535
|
+
}, policy, onEvent, plan.signal);
|
|
2536
|
+
}
|
|
2537
|
+
async function httpErrorFrom(response) {
|
|
2538
|
+
const body = await response.text().catch(() => "");
|
|
2539
|
+
return new ProviderRequestError(normalizeHttpError(response.status, response.headers, body));
|
|
2540
|
+
}
|
|
2541
|
+
function errorEvent(err) {
|
|
2542
|
+
return { type: "error", error: normalizeThrown(err) };
|
|
2543
|
+
}
|
|
2544
|
+
function makeRetryPolicy(options) {
|
|
2545
|
+
return createRetryPolicy(options.retry ?? {});
|
|
2546
|
+
}
|
|
2547
|
+
async function fetchOpenAiModels(ctx, endpoint, headers, options, extraQuery = {}) {
|
|
2548
|
+
const warnings = [];
|
|
2549
|
+
const models = [];
|
|
2550
|
+
let partial = false;
|
|
2551
|
+
let after;
|
|
2552
|
+
for (let page = 0;page < MAX_DISCOVERY_PAGES; page++) {
|
|
2553
|
+
const url = new URL(`${endpoint.baseUrl}/models`);
|
|
2554
|
+
for (const [name, value] of Object.entries(extraQuery))
|
|
2555
|
+
url.searchParams.set(name, value);
|
|
2556
|
+
if (after !== undefined)
|
|
2557
|
+
url.searchParams.set("after", after);
|
|
2558
|
+
const response = await boundedFetch(url.toString(), {
|
|
2559
|
+
method: "GET",
|
|
2560
|
+
headers,
|
|
2561
|
+
policy: endpoint.policy,
|
|
2562
|
+
maxBodyBytes: ctx.limits.maxBytes,
|
|
2563
|
+
timeoutMs: options.headerTimeoutMs ?? DEFAULT_HEADER_TIMEOUT_MS,
|
|
2564
|
+
...ctx.signal !== undefined ? { signal: ctx.signal } : {}
|
|
2565
|
+
});
|
|
2566
|
+
if (!response.ok)
|
|
2567
|
+
throw await httpErrorFrom(response);
|
|
2568
|
+
const text = await response.text();
|
|
2569
|
+
let payload;
|
|
2570
|
+
try {
|
|
2571
|
+
payload = JSON.parse(text);
|
|
2572
|
+
} catch {
|
|
2573
|
+
throw new ProviderRequestError({ code: "bad_request", message: `model discovery for "${ctx.connection.providerId}" returned a body that is not JSON`, retryable: false });
|
|
2574
|
+
}
|
|
2575
|
+
const rows = Array.isArray(payload.data) ? payload.data : [];
|
|
2576
|
+
if (!Array.isArray(payload.data))
|
|
2577
|
+
warnings.push(`discovery page ${page + 1} had no \`data\` array; it contributed no models`);
|
|
2578
|
+
for (const row of rows) {
|
|
2579
|
+
if (models.length >= ctx.limits.maxItems) {
|
|
2580
|
+
partial = true;
|
|
2581
|
+
warnings.push(`discovery returned more than the ${ctx.limits.maxItems}-model limit; the list was truncated and is PARTIAL`);
|
|
2582
|
+
break;
|
|
2583
|
+
}
|
|
2584
|
+
models.push(rowToModel(row));
|
|
2585
|
+
}
|
|
2586
|
+
const last = rows.at(-1);
|
|
2587
|
+
const lastId = last !== null && typeof last === "object" ? last.id : undefined;
|
|
2588
|
+
if (partial || payload.has_more !== true || rows.length === 0 || typeof lastId !== "string") {
|
|
2589
|
+
if (payload.has_more === true && !partial) {
|
|
2590
|
+
partial = true;
|
|
2591
|
+
warnings.push(`discovery reported more pages but the last row carried no usable id to page from; the list is PARTIAL`);
|
|
2592
|
+
}
|
|
2593
|
+
return { models, partial, cached: false, warnings };
|
|
2594
|
+
}
|
|
2595
|
+
after = lastId;
|
|
2596
|
+
if (page === MAX_DISCOVERY_PAGES - 1) {
|
|
2597
|
+
partial = true;
|
|
2598
|
+
warnings.push(`discovery stopped after ${MAX_DISCOVERY_PAGES} pages; the list is PARTIAL`);
|
|
2599
|
+
}
|
|
2600
|
+
}
|
|
2601
|
+
return { models, partial, cached: false, warnings };
|
|
2602
|
+
}
|
|
2603
|
+
function rowToModel(row) {
|
|
2604
|
+
if (row === null || typeof row !== "object")
|
|
2605
|
+
return { id: "" };
|
|
2606
|
+
const record = row;
|
|
2607
|
+
const id = typeof record.id === "string" ? record.id : "";
|
|
2608
|
+
const displayName = typeof record.display_name === "string" ? record.display_name : typeof record.name === "string" ? record.name : undefined;
|
|
2609
|
+
const contextWindow = typeof record.context_window === "number" ? record.context_window : typeof record.context_length === "number" ? record.context_length : undefined;
|
|
2610
|
+
return { id, ...displayName !== undefined ? { displayName } : {}, ...contextWindow !== undefined ? { contextWindow } : {} };
|
|
2611
|
+
}
|
|
2612
|
+
async function validateViaModels(ref, ctx, endpoint, headers, options, hasCredential, extraQuery = {}) {
|
|
2613
|
+
if (ref.kind === "aws-default-chain" || ref.kind === "file") {
|
|
2614
|
+
return { ok: false, code: "unsupported", message: `the OpenAI family cannot validate a credential reference of kind "${ref.kind}"` };
|
|
2615
|
+
}
|
|
2616
|
+
if (!hasCredential && !endpoint.policy.local) {
|
|
2617
|
+
return { ok: false, code: "missing", message: `no credential is configured for provider "${ctx.connection.providerId}"` };
|
|
2618
|
+
}
|
|
2619
|
+
try {
|
|
2620
|
+
const probeUrl = new URL(`${endpoint.baseUrl}/models`);
|
|
2621
|
+
for (const [name, value] of Object.entries(extraQuery))
|
|
2622
|
+
probeUrl.searchParams.set(name, value);
|
|
2623
|
+
const response = await boundedFetch(probeUrl.toString(), {
|
|
2624
|
+
method: "GET",
|
|
2625
|
+
headers,
|
|
2626
|
+
policy: endpoint.policy,
|
|
2627
|
+
maxBodyBytes: 1024 * 1024,
|
|
2628
|
+
timeoutMs: options.headerTimeoutMs ?? DEFAULT_HEADER_TIMEOUT_MS
|
|
2629
|
+
});
|
|
2630
|
+
const body = await response.text().catch(() => "");
|
|
2631
|
+
if (response.ok)
|
|
2632
|
+
return { ok: true };
|
|
2633
|
+
const normalized = normalizeHttpError(response.status, response.headers, body);
|
|
2634
|
+
if (normalized.code === "auth")
|
|
2635
|
+
return { ok: false, code: "invalid", message: normalized.message };
|
|
2636
|
+
return { ok: false, code: "network", message: normalized.message };
|
|
2637
|
+
} catch (err) {
|
|
2638
|
+
const normalized = normalizeThrown(err);
|
|
2639
|
+
return { ok: false, code: normalized.code === "auth" ? "invalid" : "network", message: normalized.message };
|
|
2640
|
+
}
|
|
2641
|
+
}
|
|
2642
|
+
function isStreamTerminator(data) {
|
|
2643
|
+
return data.trim() === "[DONE]";
|
|
2644
|
+
}
|
|
2645
|
+
function parseSseJson(data) {
|
|
2646
|
+
const trimmed = data.trim();
|
|
2647
|
+
if (trimmed.length === 0 || isStreamTerminator(trimmed))
|
|
2648
|
+
return;
|
|
2649
|
+
try {
|
|
2650
|
+
const parsed = JSON.parse(trimmed);
|
|
2651
|
+
return parsed !== null && typeof parsed === "object" ? parsed : undefined;
|
|
2652
|
+
} catch {
|
|
2653
|
+
return;
|
|
2654
|
+
}
|
|
2655
|
+
}
|
|
2656
|
+
|
|
2657
|
+
// ../provider-runtime/src/adapters/openai/responses.ts
|
|
2658
|
+
function mapResponsesInput(messages) {
|
|
2659
|
+
const out = [];
|
|
2660
|
+
for (const message of messages) {
|
|
2661
|
+
if (message.role === "assistant" && message.nativeState !== undefined) {
|
|
2662
|
+
for (const item of message.nativeState.items)
|
|
2663
|
+
out.push(item);
|
|
2664
|
+
}
|
|
2665
|
+
const wireRole = message.role === "tool" ? "user" : message.role;
|
|
2666
|
+
const partType = wireRole === "assistant" ? "output_text" : "input_text";
|
|
2667
|
+
const blocks = asBlocks(message.content);
|
|
2668
|
+
const contentParts = [];
|
|
2669
|
+
const decoration = decorationText(message);
|
|
2670
|
+
const carriesToolResults = blocks.some((block) => block.type === "tool_result");
|
|
2671
|
+
if (decoration !== undefined && !carriesToolResults)
|
|
2672
|
+
contentParts.push({ type: partType, text: decoration });
|
|
2673
|
+
let resultPrefix = carriesToolResults ? decoration : undefined;
|
|
2674
|
+
for (const block of blocks) {
|
|
2675
|
+
switch (block.type) {
|
|
2676
|
+
case "text":
|
|
2677
|
+
if (block.text.length > 0)
|
|
2678
|
+
contentParts.push({ type: partType, text: block.text });
|
|
2679
|
+
break;
|
|
2680
|
+
case "image":
|
|
2681
|
+
contentParts.push({ type: "input_image", image_url: imageDataUrl(block) });
|
|
2682
|
+
break;
|
|
2683
|
+
case "tool_use":
|
|
2684
|
+
if (contentParts.length > 0) {
|
|
2685
|
+
out.push({ type: "message", role: wireRole, content: [...contentParts] });
|
|
2686
|
+
contentParts.length = 0;
|
|
2687
|
+
}
|
|
2688
|
+
out.push({ type: "function_call", call_id: block.id, name: block.name, arguments: typeof block.input === "string" ? block.input : JSON.stringify(block.input ?? {}) });
|
|
2689
|
+
break;
|
|
2690
|
+
case "tool_result":
|
|
2691
|
+
out.push({ type: "function_call_output", call_id: block.tool_use_id, output: prefixToolResult(resultPrefix, toolResultText(block.content)) });
|
|
2692
|
+
resultPrefix = undefined;
|
|
2693
|
+
break;
|
|
2694
|
+
default:
|
|
2695
|
+
break;
|
|
2696
|
+
}
|
|
2697
|
+
}
|
|
2698
|
+
if (contentParts.length > 0)
|
|
2699
|
+
out.push({ type: "message", role: wireRole, content: contentParts });
|
|
2700
|
+
}
|
|
2701
|
+
return out;
|
|
2702
|
+
}
|
|
2703
|
+
function mapResponsesTools(tools) {
|
|
2704
|
+
return (tools ?? []).map((tool) => ({ type: "function", name: tool.name, description: tool.description, parameters: tool.inputSchema, strict: false }));
|
|
2705
|
+
}
|
|
2706
|
+
function mapToolChoice(choice) {
|
|
2707
|
+
if (choice === undefined)
|
|
2708
|
+
return "auto";
|
|
2709
|
+
if (choice.type === "auto")
|
|
2710
|
+
return "auto";
|
|
2711
|
+
if (choice.type === "any")
|
|
2712
|
+
return "required";
|
|
2713
|
+
return { type: "function", name: choice.name };
|
|
2714
|
+
}
|
|
2715
|
+
function buildResponsesBody(req, reasoning, descriptor) {
|
|
2716
|
+
const reasoningObject = reasoning.enabled && (reasoning.effort !== undefined || reasoning.summary !== undefined) ? { ...reasoning.effort !== undefined ? { effort: reasoning.effort } : {}, ...reasoning.summary !== undefined ? { summary: reasoning.summary } : {} } : undefined;
|
|
2717
|
+
return {
|
|
2718
|
+
model: req.model,
|
|
2719
|
+
...req.system !== undefined && req.system.length > 0 ? { instructions: req.system } : {},
|
|
2720
|
+
input: mapResponsesInput(req.messages),
|
|
2721
|
+
tools: mapResponsesTools(req.tools),
|
|
2722
|
+
tool_choice: mapToolChoice(req.toolChoice),
|
|
2723
|
+
parallel_tool_calls: descriptor?.parallelTools?.value === false ? false : true,
|
|
2724
|
+
store: false,
|
|
2725
|
+
stream: true,
|
|
2726
|
+
include: reasoning.wantsEncryptedContent ? ["reasoning.encrypted_content"] : [],
|
|
2727
|
+
...reasoningObject !== undefined ? { reasoning: reasoningObject } : {},
|
|
2728
|
+
...req.maxOutputTokens !== undefined ? { max_output_tokens: req.maxOutputTokens } : {}
|
|
2729
|
+
};
|
|
2730
|
+
}
|
|
2731
|
+
function isUnrepresentableCall(itemType) {
|
|
2732
|
+
return itemType !== "function_call" && (itemType.endsWith("_call") || itemType === "custom_tool_call");
|
|
2733
|
+
}
|
|
2734
|
+
function responsesCompletionEvent(descriptor) {
|
|
2735
|
+
const declared = descriptor?.reasoning?.completionEvent?.value;
|
|
2736
|
+
return typeof declared === "string" && declared.trim().length > 0 ? declared.trim() : "response.completed";
|
|
2737
|
+
}
|
|
2738
|
+
|
|
2739
|
+
class ResponsesStreamMapper {
|
|
2740
|
+
completionEvent;
|
|
2741
|
+
constructor(completionEvent = "response.completed") {
|
|
2742
|
+
this.completionEvent = completionEvent;
|
|
2743
|
+
}
|
|
2744
|
+
sawToolCall = false;
|
|
2745
|
+
sawRefusal = false;
|
|
2746
|
+
started = false;
|
|
2747
|
+
reasoningItems = [];
|
|
2748
|
+
arrivals = 0;
|
|
2749
|
+
reportedUnrepresentable = new Set;
|
|
2750
|
+
callIdByItem = new Map;
|
|
2751
|
+
streamedArguments = new Set;
|
|
2752
|
+
completed = false;
|
|
2753
|
+
map(data) {
|
|
2754
|
+
const payload = parseSseJson(data);
|
|
2755
|
+
if (payload === undefined)
|
|
2756
|
+
return [];
|
|
2757
|
+
const type = typeof payload.type === "string" ? payload.type : "";
|
|
2758
|
+
if (type === this.completionEvent)
|
|
2759
|
+
return this.onCompleted(payload);
|
|
2760
|
+
switch (type) {
|
|
2761
|
+
case "response.created":
|
|
2762
|
+
return this.onCreated(payload);
|
|
2763
|
+
case "response.output_text.delta": {
|
|
2764
|
+
const delta = typeof payload.delta === "string" ? payload.delta : "";
|
|
2765
|
+
return delta.length > 0 ? [{ type: "text_delta", text: delta }] : [];
|
|
2766
|
+
}
|
|
2767
|
+
case "response.reasoning_summary_text.delta": {
|
|
2768
|
+
const delta = typeof payload.delta === "string" ? payload.delta : "";
|
|
2769
|
+
return delta.length > 0 ? [{ type: "thinking_summary_delta", text: delta }] : [];
|
|
2770
|
+
}
|
|
2771
|
+
case "response.output_item.added":
|
|
2772
|
+
return this.onItemAdded(payload);
|
|
2773
|
+
case "response.function_call_arguments.delta":
|
|
2774
|
+
return this.onArgumentsDelta(payload);
|
|
2775
|
+
case "response.output_item.done":
|
|
2776
|
+
return this.onItemDone(payload);
|
|
2777
|
+
case "response.incomplete":
|
|
2778
|
+
return this.onCompleted(payload);
|
|
2779
|
+
case "response.failed":
|
|
2780
|
+
return [{ type: "error", error: { code: "server", message: this.failureMessage(payload), retryable: false } }];
|
|
2781
|
+
case "error":
|
|
2782
|
+
return [{ type: "error", error: { code: "server", message: this.failureMessage(payload), retryable: false } }];
|
|
2783
|
+
default:
|
|
2784
|
+
return [];
|
|
2785
|
+
}
|
|
2786
|
+
}
|
|
2787
|
+
finish() {
|
|
2788
|
+
if (this.completed)
|
|
2789
|
+
return [];
|
|
2790
|
+
return [{ type: "error", error: { code: "network", message: "the provider's stream ended before `response.completed` — the turn is incomplete", retryable: false } }];
|
|
2791
|
+
}
|
|
2792
|
+
onCreated(payload) {
|
|
2793
|
+
if (this.started)
|
|
2794
|
+
return [];
|
|
2795
|
+
this.started = true;
|
|
2796
|
+
const response = payload.response;
|
|
2797
|
+
const record = response !== null && typeof response === "object" ? response : {};
|
|
2798
|
+
return [
|
|
2799
|
+
{
|
|
2800
|
+
type: "message_start",
|
|
2801
|
+
...typeof record.id === "string" ? { id: record.id } : {},
|
|
2802
|
+
...typeof record.model === "string" ? { model: record.model } : {}
|
|
2803
|
+
}
|
|
2804
|
+
];
|
|
2805
|
+
}
|
|
2806
|
+
onItemAdded(payload) {
|
|
2807
|
+
const item = itemOf(payload);
|
|
2808
|
+
if (item === undefined)
|
|
2809
|
+
return [];
|
|
2810
|
+
const itemType = typeof item.type === "string" ? item.type : "";
|
|
2811
|
+
if (isUnrepresentableCall(itemType))
|
|
2812
|
+
return this.unrepresentable(itemType, item);
|
|
2813
|
+
if (itemType !== "function_call")
|
|
2814
|
+
return [];
|
|
2815
|
+
const callId = typeof item.call_id === "string" ? item.call_id : typeof item.id === "string" ? item.id : undefined;
|
|
2816
|
+
const name = typeof item.name === "string" ? item.name : undefined;
|
|
2817
|
+
if (callId === undefined || name === undefined) {
|
|
2818
|
+
return [{ type: "error", error: { code: "bad_request", message: "the provider opened a function call with no call id or name, which cannot be represented as a tool call", retryable: false } }];
|
|
2819
|
+
}
|
|
2820
|
+
if (typeof item.id === "string")
|
|
2821
|
+
this.callIdByItem.set(item.id, callId);
|
|
2822
|
+
this.sawToolCall = true;
|
|
2823
|
+
return [{ type: "tool_call_start", id: callId, name }];
|
|
2824
|
+
}
|
|
2825
|
+
onArgumentsDelta(payload) {
|
|
2826
|
+
const delta = typeof payload.delta === "string" ? payload.delta : "";
|
|
2827
|
+
if (delta.length === 0)
|
|
2828
|
+
return [];
|
|
2829
|
+
const itemId = typeof payload.item_id === "string" ? payload.item_id : undefined;
|
|
2830
|
+
const callId = itemId !== undefined ? this.callIdByItem.get(itemId) : undefined;
|
|
2831
|
+
if (callId === undefined)
|
|
2832
|
+
return [];
|
|
2833
|
+
this.streamedArguments.add(callId);
|
|
2834
|
+
return [{ type: "tool_call_delta", id: callId, argumentsJsonDelta: delta }];
|
|
2835
|
+
}
|
|
2836
|
+
onItemDone(payload) {
|
|
2837
|
+
const item = itemOf(payload);
|
|
2838
|
+
if (item === undefined)
|
|
2839
|
+
return [];
|
|
2840
|
+
const itemType = typeof item.type === "string" ? item.type : "";
|
|
2841
|
+
if (itemType === "reasoning") {
|
|
2842
|
+
const encrypted = item.encrypted_content;
|
|
2843
|
+
if (typeof encrypted === "string" && encrypted.length > 0) {
|
|
2844
|
+
const { id: _id, status: _status, ...replayable } = item;
|
|
2845
|
+
const index = typeof payload.output_index === "number" ? payload.output_index : Number.MAX_SAFE_INTEGER;
|
|
2846
|
+
this.reasoningItems.push({ index, arrival: this.arrivals++, item: replayable });
|
|
2847
|
+
}
|
|
2848
|
+
return [];
|
|
2849
|
+
}
|
|
2850
|
+
if (isUnrepresentableCall(itemType))
|
|
2851
|
+
return this.unrepresentable(itemType, item);
|
|
2852
|
+
if (itemType === "function_call") {
|
|
2853
|
+
const callId = typeof item.call_id === "string" ? item.call_id : undefined;
|
|
2854
|
+
if (callId === undefined)
|
|
2855
|
+
return [];
|
|
2856
|
+
const events = [];
|
|
2857
|
+
if (!this.streamedArguments.has(callId)) {
|
|
2858
|
+
const args = typeof item.arguments === "string" ? item.arguments : JSON.stringify(item.arguments ?? {});
|
|
2859
|
+
if (args.length > 0)
|
|
2860
|
+
events.push({ type: "tool_call_delta", id: callId, argumentsJsonDelta: args });
|
|
2861
|
+
}
|
|
2862
|
+
events.push({ type: "tool_call_end", id: callId });
|
|
2863
|
+
return events;
|
|
2864
|
+
}
|
|
2865
|
+
if (itemType === "message")
|
|
2866
|
+
this.noteRefusal(item);
|
|
2867
|
+
return [];
|
|
2868
|
+
}
|
|
2869
|
+
onCompleted(payload) {
|
|
2870
|
+
if (this.completed)
|
|
2871
|
+
return [];
|
|
2872
|
+
this.completed = true;
|
|
2873
|
+
const events = [];
|
|
2874
|
+
const response = payload.response;
|
|
2875
|
+
const record = response !== null && typeof response === "object" ? response : {};
|
|
2876
|
+
if (this.reasoningItems.length > 0) {
|
|
2877
|
+
const ordered = [...this.reasoningItems].sort((a, b) => a.index - b.index || a.arrival - b.arrival).map((entry) => entry.item);
|
|
2878
|
+
events.push({ type: "native_state", items: ordered });
|
|
2879
|
+
}
|
|
2880
|
+
const usage = record.usage;
|
|
2881
|
+
if (usage !== null && typeof usage === "object") {
|
|
2882
|
+
const u = usage;
|
|
2883
|
+
const cached = u.input_tokens_details !== null && typeof u.input_tokens_details === "object" ? u.input_tokens_details.cached_tokens : undefined;
|
|
2884
|
+
events.push({
|
|
2885
|
+
type: "usage",
|
|
2886
|
+
inputTokens: typeof u.input_tokens === "number" ? u.input_tokens : 0,
|
|
2887
|
+
outputTokens: typeof u.output_tokens === "number" ? u.output_tokens : 0,
|
|
2888
|
+
...typeof cached === "number" ? { cacheReadTokens: cached } : {}
|
|
2889
|
+
});
|
|
2890
|
+
}
|
|
2891
|
+
const output = record.output;
|
|
2892
|
+
if (Array.isArray(output)) {
|
|
2893
|
+
for (const item of output)
|
|
2894
|
+
if (item !== null && typeof item === "object")
|
|
2895
|
+
this.noteRefusal(item);
|
|
2896
|
+
}
|
|
2897
|
+
const incomplete = record.incomplete_details;
|
|
2898
|
+
const incompleteReason = incomplete !== null && typeof incomplete === "object" ? incomplete.reason : undefined;
|
|
2899
|
+
const stopReason = this.sawRefusal ? "refusal" : incompleteReason === "max_output_tokens" ? "max_tokens" : this.sawToolCall ? "tool_use" : "end_turn";
|
|
2900
|
+
events.push({ type: "done", stopReason });
|
|
2901
|
+
return events;
|
|
2902
|
+
}
|
|
2903
|
+
noteRefusal(item) {
|
|
2904
|
+
const content = item.content;
|
|
2905
|
+
if (!Array.isArray(content))
|
|
2906
|
+
return;
|
|
2907
|
+
for (const part of content) {
|
|
2908
|
+
if (part !== null && typeof part === "object" && part.type === "refusal")
|
|
2909
|
+
this.sawRefusal = true;
|
|
2910
|
+
}
|
|
2911
|
+
}
|
|
2912
|
+
unrepresentable(itemType, item) {
|
|
2913
|
+
const id = typeof item.id === "string" ? item.id : itemType;
|
|
2914
|
+
if (this.reportedUnrepresentable.has(id))
|
|
2915
|
+
return [];
|
|
2916
|
+
this.reportedUnrepresentable.add(id);
|
|
2917
|
+
return [this.unrepresentableError(itemType)];
|
|
2918
|
+
}
|
|
2919
|
+
unrepresentableError(itemType) {
|
|
2920
|
+
return {
|
|
2921
|
+
type: "error",
|
|
2922
|
+
error: {
|
|
2923
|
+
code: "capability",
|
|
2924
|
+
message: `the model invoked a "${itemType}", which this adapter cannot represent as a tool call — Winter fails the turn rather than dropping the call silently (WS-13 §9)`,
|
|
2925
|
+
retryable: false
|
|
2926
|
+
}
|
|
2927
|
+
};
|
|
2928
|
+
}
|
|
2929
|
+
failureMessage(payload) {
|
|
2930
|
+
const response = payload.response;
|
|
2931
|
+
const error = response !== null && typeof response === "object" ? response.error : payload.error;
|
|
2932
|
+
const message = error !== null && typeof error === "object" ? error.message : undefined;
|
|
2933
|
+
return typeof message === "string" && message.length > 0 ? message : "the provider reported the response failed";
|
|
2934
|
+
}
|
|
2935
|
+
}
|
|
2936
|
+
function itemOf(payload) {
|
|
2937
|
+
const item = payload.item;
|
|
2938
|
+
return item !== null && typeof item === "object" ? item : undefined;
|
|
2939
|
+
}
|
|
2940
|
+
async function* streamResponsesTurn(plan, signal) {
|
|
2941
|
+
const queue = plan.queue ?? new EventQueue;
|
|
2942
|
+
const policy = makeRetryPolicy(plan.options);
|
|
2943
|
+
const mapper = new ResponsesStreamMapper(responsesCompletionEvent(plan.options.descriptors?.(plan.model)));
|
|
2944
|
+
let response;
|
|
2945
|
+
try {
|
|
2946
|
+
response = yield* pumpEvents(queue, openStream({
|
|
2947
|
+
url: plan.url,
|
|
2948
|
+
headers: plan.headers,
|
|
2949
|
+
body: plan.body,
|
|
2950
|
+
policy: plan.endpoint.policy,
|
|
2951
|
+
ctx: plan.ctx,
|
|
2952
|
+
options: plan.options,
|
|
2953
|
+
...signal !== undefined ? { signal } : {},
|
|
2954
|
+
...plan.beforeAttempt !== undefined ? { beforeAttempt: plan.beforeAttempt } : {},
|
|
2955
|
+
...plan.recover !== undefined ? { recover: plan.recover } : {},
|
|
2956
|
+
...plan.onRefused !== undefined ? { onRefused: plan.onRefused } : {}
|
|
2957
|
+
}, policy, (event) => {
|
|
2958
|
+
if (event.type === "retry" && event.errorStatus === 429)
|
|
2959
|
+
plan.onRateLimited?.(event, queue);
|
|
2960
|
+
queue.push(event);
|
|
2961
|
+
}));
|
|
2962
|
+
} catch (err) {
|
|
2963
|
+
yield errorEvent(err);
|
|
2964
|
+
return;
|
|
2965
|
+
}
|
|
2966
|
+
if (response.body === null) {
|
|
2967
|
+
yield { type: "error", error: { code: "network", message: "the provider returned no response body", retryable: false } };
|
|
2968
|
+
return;
|
|
2969
|
+
}
|
|
2970
|
+
try {
|
|
2971
|
+
for await (const sse of parseSse(response.body, {
|
|
2972
|
+
stallTimeoutMs: plan.ctx.stallTimeoutMs,
|
|
2973
|
+
...signal !== undefined ? { signal } : {},
|
|
2974
|
+
onBytes: (n) => plan.ctx.log({ kind: "provider.stream", providerId: plan.ctx.connection.providerId, bytes: n })
|
|
2975
|
+
})) {
|
|
2976
|
+
policy.commit();
|
|
2977
|
+
for (const event of mapper.map(sse.data))
|
|
2978
|
+
yield event;
|
|
2979
|
+
for (const event of queue.drain())
|
|
2980
|
+
yield event;
|
|
2981
|
+
}
|
|
2982
|
+
for (const event of mapper.finish())
|
|
2983
|
+
yield event;
|
|
2984
|
+
plan.onSuccess?.();
|
|
2985
|
+
for (const event of queue.drain())
|
|
2986
|
+
yield event;
|
|
2987
|
+
} catch (err) {
|
|
2988
|
+
yield errorEvent(err);
|
|
2989
|
+
}
|
|
2990
|
+
}
|
|
2991
|
+
function privilegedHeaders(options) {
|
|
2992
|
+
return {
|
|
2993
|
+
...options.organization !== undefined ? { "OpenAI-Organization": options.organization } : {},
|
|
2994
|
+
...options.project !== undefined ? { "OpenAI-Project": options.project } : {}
|
|
2995
|
+
};
|
|
2996
|
+
}
|
|
2997
|
+
async function* responsesTurn(req, ctx, options, fallbackBaseUrl, urlFor, extraProtocolHeaders = {}) {
|
|
2998
|
+
let plan;
|
|
2999
|
+
try {
|
|
3000
|
+
const descriptor = options.descriptors?.(req.model);
|
|
3001
|
+
assertRepresentableTools(req.tools);
|
|
3002
|
+
const reasoning = resolveReasoning(req, descriptor);
|
|
3003
|
+
const parametersInPlay = [
|
|
3004
|
+
...reasoning.enabled && reasoning.effort !== undefined ? ["reasoning", "reasoning.effort"] : [],
|
|
3005
|
+
...reasoning.summary !== undefined ? ["reasoning.summary"] : [],
|
|
3006
|
+
...reasoning.wantsEncryptedContent ? ["include"] : [],
|
|
3007
|
+
...req.maxOutputTokens !== undefined ? ["max_output_tokens"] : [],
|
|
3008
|
+
...(req.tools?.length ?? 0) > 0 ? ["tools"] : []
|
|
3009
|
+
];
|
|
3010
|
+
assertWithinLimits(req, descriptor, parametersInPlay);
|
|
3011
|
+
const endpoint = resolveEndpoint(ctx, options, fallbackBaseUrl);
|
|
3012
|
+
const auth = await resolveAuth(ctx, "bearer");
|
|
3013
|
+
if (auth.material === null && !endpoint.policy.local) {
|
|
3014
|
+
throw capabilityRefusal(`no credential is configured for provider "${ctx.connection.providerId}" — an OpenAI-family endpoint that is not a declared local installation needs one`);
|
|
3015
|
+
}
|
|
3016
|
+
const headers = buildHeaders({
|
|
3017
|
+
policy: endpoint.policy,
|
|
3018
|
+
protocol: { "content-type": "application/json", accept: "text/event-stream", ...extraProtocolHeaders, ...auth.headers },
|
|
3019
|
+
privileged: privilegedHeaders(options),
|
|
3020
|
+
identity: identityFor(options, ctx),
|
|
3021
|
+
userSupplied: ctx.connection.headers
|
|
3022
|
+
});
|
|
3023
|
+
plan = { model: req.model, url: urlFor(endpoint.baseUrl), headers, endpoint, ctx, options, body: JSON.stringify(buildResponsesBody(req, reasoning, descriptor)) };
|
|
3024
|
+
} catch (err) {
|
|
3025
|
+
yield errorEvent(err);
|
|
3026
|
+
return;
|
|
3027
|
+
}
|
|
3028
|
+
yield* streamResponsesTurn(plan, req.signal);
|
|
3029
|
+
}
|
|
3030
|
+
|
|
3031
|
+
// ../provider-runtime/src/adapters/openai/chat-completions.ts
|
|
3032
|
+
function isExposedReasoningItem(item) {
|
|
3033
|
+
return item !== null && typeof item === "object" && item.type === "winter.exposed_reasoning" && typeof item.text === "string";
|
|
3034
|
+
}
|
|
3035
|
+
function userContentParts(blocks, decoration) {
|
|
3036
|
+
const parts = [];
|
|
3037
|
+
let text = decoration ?? "";
|
|
3038
|
+
let sawImage = false;
|
|
3039
|
+
if (decoration !== undefined)
|
|
3040
|
+
parts.push({ type: "text", text: decoration });
|
|
3041
|
+
for (const block of blocks) {
|
|
3042
|
+
if (block.type === "text") {
|
|
3043
|
+
text += text.length > 0 ? `
|
|
3044
|
+
${block.text}` : block.text;
|
|
3045
|
+
parts.push({ type: "text", text: block.text });
|
|
3046
|
+
} else if (block.type === "image") {
|
|
3047
|
+
sawImage = true;
|
|
3048
|
+
parts.push({ type: "image_url", image_url: { url: imageDataUrl(block) } });
|
|
3049
|
+
}
|
|
3050
|
+
}
|
|
3051
|
+
return sawImage ? { content: parts, hasParts: true } : { content: text, hasParts: false };
|
|
3052
|
+
}
|
|
3053
|
+
function mapChatMessages(messages, replayExposedReasoning) {
|
|
3054
|
+
const out = [];
|
|
3055
|
+
for (const message of messages) {
|
|
3056
|
+
const blocks = asBlocks(message.content);
|
|
3057
|
+
if (message.role === "tool") {
|
|
3058
|
+
let toolPrefix = decorationText(message);
|
|
3059
|
+
let rendered = false;
|
|
3060
|
+
for (const block of blocks) {
|
|
3061
|
+
if (block.type !== "tool_result")
|
|
3062
|
+
continue;
|
|
3063
|
+
out.push({ role: "tool", tool_call_id: block.tool_use_id, content: prefixToolResult(toolPrefix, toolResultText(block.content)) });
|
|
3064
|
+
toolPrefix = undefined;
|
|
3065
|
+
rendered = true;
|
|
3066
|
+
}
|
|
3067
|
+
if (!rendered)
|
|
3068
|
+
out.push({ role: "user", content: userContentParts(blocks, toolPrefix).content });
|
|
3069
|
+
continue;
|
|
3070
|
+
}
|
|
3071
|
+
if (message.role === "assistant") {
|
|
3072
|
+
const toolCalls = [];
|
|
3073
|
+
let text = decorationText(message) ?? "";
|
|
3074
|
+
for (const block of blocks) {
|
|
3075
|
+
if (block.type === "text")
|
|
3076
|
+
text += text.length > 0 ? `
|
|
3077
|
+
${block.text}` : block.text;
|
|
3078
|
+
else if (block.type === "tool_use") {
|
|
3079
|
+
toolCalls.push({ id: block.id, type: "function", function: { name: block.name, arguments: typeof block.input === "string" ? block.input : JSON.stringify(block.input ?? {}) } });
|
|
3080
|
+
}
|
|
3081
|
+
}
|
|
3082
|
+
const exposed = replayExposedReasoning ? (message.nativeState?.items ?? []).filter(isExposedReasoningItem).map((i) => i.text).join("") : "";
|
|
3083
|
+
out.push({
|
|
3084
|
+
role: "assistant",
|
|
3085
|
+
content: text,
|
|
3086
|
+
...toolCalls.length > 0 ? { tool_calls: toolCalls } : {},
|
|
3087
|
+
...exposed.length > 0 ? { reasoning_content: exposed } : {}
|
|
3088
|
+
});
|
|
3089
|
+
continue;
|
|
3090
|
+
}
|
|
3091
|
+
const results = blocks.filter((b) => b.type === "tool_result");
|
|
3092
|
+
if (results.length > 0) {
|
|
3093
|
+
let resultPrefix = decorationText(message);
|
|
3094
|
+
for (const block of results) {
|
|
3095
|
+
if (block.type !== "tool_result")
|
|
3096
|
+
continue;
|
|
3097
|
+
out.push({ role: "tool", tool_call_id: block.tool_use_id, content: prefixToolResult(resultPrefix, toolResultText(block.content)) });
|
|
3098
|
+
resultPrefix = undefined;
|
|
3099
|
+
}
|
|
3100
|
+
const rest = blocks.filter((b) => b.type !== "tool_result");
|
|
3101
|
+
if (rest.length > 0)
|
|
3102
|
+
out.push({ role: "user", content: userContentParts(rest).content });
|
|
3103
|
+
continue;
|
|
3104
|
+
}
|
|
3105
|
+
out.push({ role: "user", content: userContentParts(blocks, decorationText(message)).content });
|
|
3106
|
+
}
|
|
3107
|
+
return out;
|
|
3108
|
+
}
|
|
3109
|
+
function mapChatTools(tools) {
|
|
3110
|
+
return (tools ?? []).map((tool) => ({ type: "function", function: { name: tool.name, description: tool.description, parameters: tool.inputSchema } }));
|
|
3111
|
+
}
|
|
3112
|
+
function mapChatToolChoice(choice) {
|
|
3113
|
+
if (choice === undefined)
|
|
3114
|
+
return "auto";
|
|
3115
|
+
if (choice.type === "auto")
|
|
3116
|
+
return "auto";
|
|
3117
|
+
if (choice.type === "any")
|
|
3118
|
+
return "required";
|
|
3119
|
+
return { type: "function", function: { name: choice.name } };
|
|
3120
|
+
}
|
|
3121
|
+
function buildChatBody(req, reasoning, descriptor, replayExposedReasoning) {
|
|
3122
|
+
const messages = [];
|
|
3123
|
+
if (req.system !== undefined && req.system.length > 0)
|
|
3124
|
+
messages.push({ role: "system", content: req.system });
|
|
3125
|
+
messages.push(...mapChatMessages(req.messages, replayExposedReasoning));
|
|
3126
|
+
const tools = mapChatTools(req.tools);
|
|
3127
|
+
const budgetField = descriptor?.reasoning !== undefined ? "max_completion_tokens" : "max_tokens";
|
|
3128
|
+
return {
|
|
3129
|
+
model: req.model,
|
|
3130
|
+
messages,
|
|
3131
|
+
stream: true,
|
|
3132
|
+
stream_options: { include_usage: true },
|
|
3133
|
+
...tools.length > 0 ? { tools, tool_choice: mapChatToolChoice(req.toolChoice) } : {},
|
|
3134
|
+
...reasoning.enabled && reasoning.effort !== undefined ? { reasoning_effort: reasoning.effort } : {},
|
|
3135
|
+
...req.maxOutputTokens !== undefined ? { [budgetField]: req.maxOutputTokens } : {}
|
|
3136
|
+
};
|
|
3137
|
+
}
|
|
3138
|
+
|
|
3139
|
+
class ChatStreamMapper {
|
|
3140
|
+
captureExposedReasoning;
|
|
3141
|
+
started = false;
|
|
3142
|
+
stopReason;
|
|
3143
|
+
exposed = "";
|
|
3144
|
+
completed = false;
|
|
3145
|
+
callsByIndex = new Map;
|
|
3146
|
+
order = [];
|
|
3147
|
+
constructor(captureExposedReasoning) {
|
|
3148
|
+
this.captureExposedReasoning = captureExposedReasoning;
|
|
3149
|
+
}
|
|
3150
|
+
map(data) {
|
|
3151
|
+
if (isStreamTerminator(data))
|
|
3152
|
+
return this.finalize();
|
|
3153
|
+
const payload = parseSseJson(data);
|
|
3154
|
+
if (payload === undefined)
|
|
3155
|
+
return [];
|
|
3156
|
+
const events = [];
|
|
3157
|
+
const inlineError = payload.error;
|
|
3158
|
+
if (inlineError !== null && typeof inlineError === "object") {
|
|
3159
|
+
const message = inlineError.message;
|
|
3160
|
+
const code = inlineError.code;
|
|
3161
|
+
return [
|
|
3162
|
+
{
|
|
3163
|
+
type: "error",
|
|
3164
|
+
error: { code: "server", message: typeof message === "string" ? message : "the provider reported an error mid-stream", retryable: false, ...typeof code === "string" ? { providerCode: code } : {} }
|
|
3165
|
+
}
|
|
3166
|
+
];
|
|
3167
|
+
}
|
|
3168
|
+
if (!this.started) {
|
|
3169
|
+
this.started = true;
|
|
3170
|
+
events.push({
|
|
3171
|
+
type: "message_start",
|
|
3172
|
+
...typeof payload.id === "string" ? { id: payload.id } : {},
|
|
3173
|
+
...typeof payload.model === "string" ? { model: payload.model } : {}
|
|
3174
|
+
});
|
|
3175
|
+
}
|
|
3176
|
+
const usage = payload.usage;
|
|
3177
|
+
if (usage !== null && typeof usage === "object") {
|
|
3178
|
+
const u = usage;
|
|
3179
|
+
const details = u.prompt_tokens_details !== null && typeof u.prompt_tokens_details === "object" ? u.prompt_tokens_details.cached_tokens : undefined;
|
|
3180
|
+
const cached = typeof details === "number" ? details : typeof u.prompt_cache_hit_tokens === "number" ? u.prompt_cache_hit_tokens : undefined;
|
|
3181
|
+
events.push({
|
|
3182
|
+
type: "usage",
|
|
3183
|
+
inputTokens: typeof u.prompt_tokens === "number" ? u.prompt_tokens : 0,
|
|
3184
|
+
outputTokens: typeof u.completion_tokens === "number" ? u.completion_tokens : 0,
|
|
3185
|
+
...cached !== undefined ? { cacheReadTokens: cached } : {}
|
|
3186
|
+
});
|
|
3187
|
+
}
|
|
3188
|
+
const choices = Array.isArray(payload.choices) ? payload.choices : [];
|
|
3189
|
+
for (const choice of choices) {
|
|
3190
|
+
if (choice === null || typeof choice !== "object")
|
|
3191
|
+
continue;
|
|
3192
|
+
const record = choice;
|
|
3193
|
+
events.push(...this.mapDelta(record.delta));
|
|
3194
|
+
const finish = record.finish_reason;
|
|
3195
|
+
if (typeof finish === "string" && finish.length > 0) {
|
|
3196
|
+
for (const index of this.order) {
|
|
3197
|
+
const call = this.callsByIndex.get(index);
|
|
3198
|
+
if (call !== undefined)
|
|
3199
|
+
events.push({ type: "tool_call_end", id: call.id });
|
|
3200
|
+
}
|
|
3201
|
+
this.stopReason = finish === "tool_calls" ? "tool_use" : finish === "length" ? "max_tokens" : finish === "content_filter" ? "refusal" : "end_turn";
|
|
3202
|
+
}
|
|
3203
|
+
}
|
|
3204
|
+
return events;
|
|
3205
|
+
}
|
|
3206
|
+
finish() {
|
|
3207
|
+
if (this.completed)
|
|
3208
|
+
return [];
|
|
3209
|
+
if (this.stopReason !== undefined)
|
|
3210
|
+
return this.finalize();
|
|
3211
|
+
return [{ type: "error", error: { code: "network", message: "the provider's stream ended before any finish_reason — the turn is incomplete", retryable: false } }];
|
|
3212
|
+
}
|
|
3213
|
+
finalize() {
|
|
3214
|
+
if (this.completed)
|
|
3215
|
+
return [];
|
|
3216
|
+
this.completed = true;
|
|
3217
|
+
const events = [];
|
|
3218
|
+
if (this.captureExposedReasoning && this.exposed.length > 0) {
|
|
3219
|
+
events.push({ type: "native_state", items: [{ type: "winter.exposed_reasoning", text: this.exposed }] });
|
|
3220
|
+
}
|
|
3221
|
+
events.push({ type: "done", stopReason: this.stopReason ?? "end_turn" });
|
|
3222
|
+
return events;
|
|
3223
|
+
}
|
|
3224
|
+
mapDelta(delta) {
|
|
3225
|
+
if (delta === null || typeof delta !== "object")
|
|
3226
|
+
return [];
|
|
3227
|
+
const record = delta;
|
|
3228
|
+
const events = [];
|
|
3229
|
+
if (typeof record.content === "string" && record.content.length > 0)
|
|
3230
|
+
events.push({ type: "text_delta", text: record.content });
|
|
3231
|
+
const exposedDelta = typeof record.reasoning_content === "string" ? record.reasoning_content : typeof record.reasoning === "string" ? record.reasoning : undefined;
|
|
3232
|
+
if (exposedDelta !== undefined && exposedDelta.length > 0) {
|
|
3233
|
+
this.exposed += exposedDelta;
|
|
3234
|
+
events.push({ type: "thinking_exposed_delta", text: exposedDelta });
|
|
3235
|
+
}
|
|
3236
|
+
const toolCalls = record.tool_calls;
|
|
3237
|
+
if (Array.isArray(toolCalls)) {
|
|
3238
|
+
for (const fragment of toolCalls) {
|
|
3239
|
+
if (fragment === null || typeof fragment !== "object")
|
|
3240
|
+
continue;
|
|
3241
|
+
const f = fragment;
|
|
3242
|
+
const index = typeof f.index === "number" ? f.index : 0;
|
|
3243
|
+
const fn = f.function !== null && typeof f.function === "object" ? f.function : {};
|
|
3244
|
+
const existing = this.callsByIndex.get(index);
|
|
3245
|
+
if (existing === undefined) {
|
|
3246
|
+
const id = typeof f.id === "string" && f.id.length > 0 ? f.id : undefined;
|
|
3247
|
+
const name = typeof fn.name === "string" && fn.name.length > 0 ? fn.name : undefined;
|
|
3248
|
+
if (id === undefined || name === undefined) {
|
|
3249
|
+
events.push({
|
|
3250
|
+
type: "error",
|
|
3251
|
+
error: {
|
|
3252
|
+
code: "capability",
|
|
3253
|
+
message: `the provider opened tool call slot ${index} with no id or function name, so the call cannot be represented — Winter fails the turn rather than dropping it silently (WS-13 §9)`,
|
|
3254
|
+
retryable: false
|
|
3255
|
+
}
|
|
3256
|
+
});
|
|
3257
|
+
continue;
|
|
3258
|
+
}
|
|
3259
|
+
this.callsByIndex.set(index, { id, name });
|
|
3260
|
+
this.order.push(index);
|
|
3261
|
+
events.push({ type: "tool_call_start", id, name });
|
|
3262
|
+
}
|
|
3263
|
+
const call = this.callsByIndex.get(index);
|
|
3264
|
+
if (call !== undefined && typeof fn.arguments === "string" && fn.arguments.length > 0) {
|
|
3265
|
+
events.push({ type: "tool_call_delta", id: call.id, argumentsJsonDelta: fn.arguments });
|
|
3266
|
+
}
|
|
3267
|
+
}
|
|
3268
|
+
}
|
|
3269
|
+
return events;
|
|
3270
|
+
}
|
|
3271
|
+
}
|
|
3272
|
+
async function* chatTurn(req, ctx, options, fallbackBaseUrl, urlFor, extraProtocolHeaders = {}) {
|
|
3273
|
+
const queue = new EventQueue;
|
|
3274
|
+
const policy = makeRetryPolicy(options);
|
|
3275
|
+
let url;
|
|
3276
|
+
let headers;
|
|
3277
|
+
let endpoint;
|
|
3278
|
+
let body;
|
|
3279
|
+
let captureExposed;
|
|
3280
|
+
try {
|
|
3281
|
+
const descriptor = options.descriptors?.(req.model);
|
|
3282
|
+
assertRepresentableTools(req.tools);
|
|
3283
|
+
const reasoning = resolveReasoning(req, descriptor);
|
|
3284
|
+
assertWithinLimits(req, descriptor, [
|
|
3285
|
+
...reasoning.effort !== undefined ? ["reasoning_effort"] : [],
|
|
3286
|
+
...req.maxOutputTokens !== undefined ? [descriptor?.reasoning !== undefined ? "max_completion_tokens" : "max_tokens"] : [],
|
|
3287
|
+
...(req.tools?.length ?? 0) > 0 ? ["tools"] : []
|
|
3288
|
+
]);
|
|
3289
|
+
captureExposed = descriptor?.reasoning?.readableState?.value === "full-exposed";
|
|
3290
|
+
endpoint = resolveEndpoint(ctx, options, fallbackBaseUrl);
|
|
3291
|
+
const auth = await resolveAuth(ctx, options.authStyle ?? "bearer");
|
|
3292
|
+
if (auth.material === null && !endpoint.policy.local) {
|
|
3293
|
+
throw capabilityRefusal(`no credential is configured for provider "${ctx.connection.providerId}" — an OpenAI-compatible endpoint that is not a declared local installation needs one`);
|
|
3294
|
+
}
|
|
3295
|
+
headers = buildHeaders({
|
|
3296
|
+
policy: endpoint.policy,
|
|
3297
|
+
protocol: { "content-type": "application/json", accept: "text/event-stream", ...extraProtocolHeaders, ...auth.headers },
|
|
3298
|
+
privileged: privilegedHeaders(options),
|
|
3299
|
+
identity: identityFor(options, ctx),
|
|
3300
|
+
userSupplied: ctx.connection.headers
|
|
3301
|
+
});
|
|
3302
|
+
url = urlFor(endpoint);
|
|
3303
|
+
body = JSON.stringify(buildChatBody(req, reasoning, descriptor, captureExposed));
|
|
3304
|
+
} catch (err) {
|
|
3305
|
+
yield errorEvent(err);
|
|
3306
|
+
return;
|
|
3307
|
+
}
|
|
3308
|
+
let response;
|
|
3309
|
+
try {
|
|
3310
|
+
response = yield* pumpEvents(queue, openStream({ url, headers, body, policy: endpoint.policy, ctx, options, ...req.signal !== undefined ? { signal: req.signal } : {} }, policy, (event) => queue.push(event)));
|
|
3311
|
+
} catch (err) {
|
|
3312
|
+
yield errorEvent(err);
|
|
3313
|
+
return;
|
|
3314
|
+
}
|
|
3315
|
+
if (response.body === null) {
|
|
3316
|
+
yield { type: "error", error: { code: "network", message: "the provider returned no response body", retryable: false } };
|
|
3317
|
+
return;
|
|
3318
|
+
}
|
|
3319
|
+
const mapper = new ChatStreamMapper(captureExposed);
|
|
3320
|
+
try {
|
|
3321
|
+
for await (const sse of parseSse(response.body, {
|
|
3322
|
+
stallTimeoutMs: ctx.stallTimeoutMs,
|
|
3323
|
+
...req.signal !== undefined ? { signal: req.signal } : {},
|
|
3324
|
+
onBytes: (n) => ctx.log({ kind: "provider.stream", providerId: ctx.connection.providerId, bytes: n })
|
|
3325
|
+
})) {
|
|
3326
|
+
policy.commit();
|
|
3327
|
+
for (const event of mapper.map(sse.data))
|
|
3328
|
+
yield event;
|
|
3329
|
+
}
|
|
3330
|
+
for (const event of mapper.finish())
|
|
3331
|
+
yield event;
|
|
3332
|
+
} catch (err) {
|
|
3333
|
+
yield errorEvent(err);
|
|
3334
|
+
}
|
|
3335
|
+
}
|
|
3336
|
+
|
|
3337
|
+
// ../provider-runtime/src/adapters/openai/azure.ts
|
|
3338
|
+
var AZURE_PREVIEW_API_VERSION = "preview";
|
|
3339
|
+
function azureRouting(ctx, options) {
|
|
3340
|
+
const apiVersion = ctx.connection.apiVersion ?? options.defaultApiVersion;
|
|
3341
|
+
if (apiVersion === undefined || apiVersion.length === 0) {
|
|
3342
|
+
throw capabilityRefusal(`azure-openai needs \`connection.apiVersion\`: every Azure OpenAI call carries an \`api-version\` query parameter, and Winter will not guess which surface you meant`);
|
|
3343
|
+
}
|
|
3344
|
+
const preview = apiVersion === AZURE_PREVIEW_API_VERSION;
|
|
3345
|
+
const deployment = ctx.connection.deployment;
|
|
3346
|
+
if (!preview && (deployment === undefined || deployment.length === 0)) {
|
|
3347
|
+
throw capabilityRefusal(`azure-openai needs \`connection.deployment\`: on the classic api-version surface the deployment name IS the address of the model`);
|
|
3348
|
+
}
|
|
3349
|
+
return { apiVersion, preview, ...deployment !== undefined ? { deployment } : {} };
|
|
3350
|
+
}
|
|
3351
|
+
function azureTurnUrl(baseUrl, routing) {
|
|
3352
|
+
const url = routing.preview ? new URL(`${baseUrl}/openai/v1/responses`) : new URL(`${baseUrl}/openai/deployments/${encodeURIComponent(routing.deployment ?? "")}/chat/completions`);
|
|
3353
|
+
url.searchParams.set("api-version", routing.apiVersion);
|
|
3354
|
+
return url.toString();
|
|
3355
|
+
}
|
|
3356
|
+
function createAzureOpenAIAdapter(options) {
|
|
3357
|
+
const withAuthStyle = { ...options, authStyle: options.authStyle ?? "azure-api-key" };
|
|
3358
|
+
return {
|
|
3359
|
+
id: "winter.azure-openai",
|
|
3360
|
+
version: "1",
|
|
3361
|
+
family: "openai",
|
|
3362
|
+
protocol: "azure-openai",
|
|
3363
|
+
async validateCredential(ref, ctx) {
|
|
3364
|
+
const routing = azureRouting(ctx, withAuthStyle);
|
|
3365
|
+
const endpoint = resolveEndpoint(ctx, withAuthStyle);
|
|
3366
|
+
const auth = await resolveAuth(ctx, withAuthStyle.authStyle ?? "azure-api-key");
|
|
3367
|
+
const headers = buildHeaders({ policy: endpoint.policy, protocol: { accept: "application/json", ...auth.headers }, identity: identityFor(options, ctx), userSupplied: ctx.connection.headers });
|
|
3368
|
+
return validateViaModels(ref, ctx, { ...endpoint, baseUrl: azureModelsBase(endpoint.baseUrl, routing) }, headers, withAuthStyle, auth.material !== null, { "api-version": routing.apiVersion });
|
|
3369
|
+
},
|
|
3370
|
+
async listModels(ctx) {
|
|
3371
|
+
const routing = azureRouting(ctx, withAuthStyle);
|
|
3372
|
+
const endpoint = resolveEndpoint(ctx, withAuthStyle);
|
|
3373
|
+
const auth = await resolveAuth(ctx, withAuthStyle.authStyle ?? "azure-api-key");
|
|
3374
|
+
const headers = buildHeaders({ policy: endpoint.policy, protocol: { accept: "application/json", ...auth.headers }, identity: identityFor(options, ctx), userSupplied: ctx.connection.headers });
|
|
3375
|
+
return fetchOpenAiModels(ctx, { ...endpoint, baseUrl: azureModelsBase(endpoint.baseUrl, routing) }, headers, withAuthStyle, { "api-version": routing.apiVersion });
|
|
3376
|
+
},
|
|
3377
|
+
streamTurn(req, ctx) {
|
|
3378
|
+
return azureTurn(req, ctx, withAuthStyle);
|
|
3379
|
+
},
|
|
3380
|
+
mapEffort(effort, model) {
|
|
3381
|
+
const mapped = mapEffortAgainst(effort, model);
|
|
3382
|
+
return mapped.ok ? { ok: true, value: mapped.value } : mapped;
|
|
3383
|
+
},
|
|
3384
|
+
capabilities: capabilitiesFrom
|
|
3385
|
+
};
|
|
3386
|
+
}
|
|
3387
|
+
function azureModelsBase(baseUrl, routing) {
|
|
3388
|
+
return routing.preview ? `${baseUrl}/openai/v1` : `${baseUrl}/openai`;
|
|
3389
|
+
}
|
|
3390
|
+
async function* azureTurn(req, ctx, options) {
|
|
3391
|
+
let routing;
|
|
3392
|
+
try {
|
|
3393
|
+
routing = azureRouting(ctx, options);
|
|
3394
|
+
resolveEndpoint(ctx, options);
|
|
3395
|
+
} catch (err) {
|
|
3396
|
+
yield errorEvent(err);
|
|
3397
|
+
return;
|
|
3398
|
+
}
|
|
3399
|
+
if (routing.preview) {
|
|
3400
|
+
yield* responsesTurn(req, ctx, options, undefined, (baseUrl) => azureTurnUrl(baseUrl, routing));
|
|
3401
|
+
return;
|
|
3402
|
+
}
|
|
3403
|
+
yield* chatTurn(req, ctx, options, undefined, (endpoint) => azureTurnUrl(endpoint.baseUrl, routing));
|
|
3404
|
+
}
|
|
3405
|
+
// ../provider-runtime/src/adapters/google/jwt-rs256.ts
|
|
3406
|
+
var RS256 = { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" };
|
|
3407
|
+
function base64UrlEncode(bytes) {
|
|
3408
|
+
let binary = "";
|
|
3409
|
+
for (let i = 0;i < bytes.length; i++)
|
|
3410
|
+
binary += String.fromCharCode(bytes[i]);
|
|
3411
|
+
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
3412
|
+
}
|
|
3413
|
+
// ../provider-runtime/src/adapters/google/generate-content.ts
|
|
3414
|
+
var DEFAULT_MAX_BODY_BYTES = 32 * 1024 * 1024;
|
|
3415
|
+
var ITEM_KINDS = new Set(["function-call", "text", "thought", "other"]);
|
|
3416
|
+
// ../provider-runtime/src/adapters/google/adc.ts
|
|
3417
|
+
var TOKEN_RESPONSE_MAX_BYTES = 64 * 1024;
|
|
3418
|
+
// ../provider-runtime/src/registry.ts
|
|
3419
|
+
class WinterProviderResolutionError extends Error {
|
|
3420
|
+
code;
|
|
3421
|
+
constructor(code, message) {
|
|
3422
|
+
super(message);
|
|
3423
|
+
this.name = "WinterProviderResolutionError";
|
|
3424
|
+
this.code = code;
|
|
3425
|
+
}
|
|
3426
|
+
}
|
|
3427
|
+
var PINNED_EFFORT_LEVELS = ["low", "medium", "high", "xhigh", "max"];
|
|
3428
|
+
function continuationDomainOf(descriptor) {
|
|
3429
|
+
const reasoning = descriptor.reasoning;
|
|
3430
|
+
if (reasoning === undefined || reasoning.continuation === "none")
|
|
3431
|
+
return;
|
|
3432
|
+
const members = reasoning.continuationDomain?.value;
|
|
3433
|
+
if (members !== undefined && members.length > 0)
|
|
3434
|
+
return [...members].sort()[0];
|
|
3435
|
+
return descriptor.key;
|
|
3436
|
+
}
|
|
3437
|
+
function createRegistry(catalog) {
|
|
3438
|
+
const adapters = new Map;
|
|
3439
|
+
const providersById = new Map(catalog.providers.map((p) => [p.id, p]));
|
|
3440
|
+
const modelsByKey = new Map(catalog.models.map((m) => [m.key, m]));
|
|
3441
|
+
const namesByProvider = new Map;
|
|
3442
|
+
for (const model of catalog.models) {
|
|
3443
|
+
let names = namesByProvider.get(model.providerId);
|
|
3444
|
+
if (names === undefined) {
|
|
3445
|
+
names = new Map;
|
|
3446
|
+
namesByProvider.set(model.providerId, names);
|
|
3447
|
+
}
|
|
3448
|
+
names.set(model.upstreamId, model);
|
|
3449
|
+
for (const alias of model.aliases)
|
|
3450
|
+
names.set(alias, model);
|
|
3451
|
+
}
|
|
3452
|
+
function build(provider, descriptor, providerModelId) {
|
|
3453
|
+
if (provider.risk.class === "blocked") {
|
|
3454
|
+
return new WinterProviderResolutionError("blocked", `provider "${provider.id}" is blocked: ${provider.risk.reasons.join("; ")}`);
|
|
3455
|
+
}
|
|
3456
|
+
if (descriptor !== undefined && descriptor.status === "blocked") {
|
|
3457
|
+
return new WinterProviderResolutionError("blocked", `model "${descriptor.key}" is blocked in the catalog`);
|
|
3458
|
+
}
|
|
3459
|
+
const adapter = adapters.get(provider.adapterId);
|
|
3460
|
+
if (adapter === undefined) {
|
|
3461
|
+
return new WinterProviderResolutionError("no-adapter", `no adapter registered for "${provider.adapterId}" (provider "${provider.id}")`);
|
|
3462
|
+
}
|
|
3463
|
+
const domain = descriptor === undefined ? undefined : continuationDomainOf(descriptor);
|
|
3464
|
+
return {
|
|
3465
|
+
providerId: provider.id,
|
|
3466
|
+
modelKey: descriptor?.key ?? `${provider.id}/${providerModelId}`,
|
|
3467
|
+
providerModelId,
|
|
3468
|
+
adapterId: provider.adapterId,
|
|
3469
|
+
adapter,
|
|
3470
|
+
descriptor,
|
|
3471
|
+
provider,
|
|
3472
|
+
...domain !== undefined ? { continuationDomain: domain } : {},
|
|
3473
|
+
catalogVersion: catalog.catalogVersion
|
|
3474
|
+
};
|
|
3475
|
+
}
|
|
3476
|
+
function resolveWithin(provider, name, allowUnlisted) {
|
|
3477
|
+
const descriptor = namesByProvider.get(provider.id)?.get(name);
|
|
3478
|
+
if (descriptor !== undefined)
|
|
3479
|
+
return build(provider, descriptor, descriptor.upstreamId);
|
|
3480
|
+
if (allowUnlisted && provider.liveCatalogAuthority !== "authoritative")
|
|
3481
|
+
return build(provider, undefined, name);
|
|
3482
|
+
return new WinterProviderResolutionError("unknown-model", `model "${name}" is not in provider "${provider.id}"'s catalog${provider.liveCatalogAuthority === "authoritative" ? " (its live catalog is authoritative, so absence is definitive)" : " — set `provider.allowUnlisted: true` to pass an unlisted id through"}`);
|
|
3483
|
+
}
|
|
3484
|
+
return {
|
|
3485
|
+
register(adapter) {
|
|
3486
|
+
adapters.set(adapter.id, adapter);
|
|
3487
|
+
},
|
|
3488
|
+
resolve(request) {
|
|
3489
|
+
const allowUnlisted = request.provider?.allowUnlisted === true;
|
|
3490
|
+
const slash = request.model.indexOf("/");
|
|
3491
|
+
const prefix = slash > 0 ? request.model.slice(0, slash) : undefined;
|
|
3492
|
+
const rest = slash > 0 ? request.model.slice(slash + 1) : undefined;
|
|
3493
|
+
const sessionProviderId = request.provider?.providerId;
|
|
3494
|
+
if (sessionProviderId !== undefined) {
|
|
3495
|
+
const sessionProvider = providersById.get(sessionProviderId);
|
|
3496
|
+
if (sessionProvider === undefined) {
|
|
3497
|
+
return new WinterProviderResolutionError("unknown-provider", `no provider "${sessionProviderId}" in catalog ${catalog.catalogVersion}`);
|
|
3498
|
+
}
|
|
3499
|
+
const sessionNames = namesByProvider.get(sessionProviderId);
|
|
3500
|
+
const selfQualified = prefix === sessionProviderId && rest !== undefined;
|
|
3501
|
+
const withinProvider = selfQualified ? rest : request.model;
|
|
3502
|
+
const own = sessionNames?.get(request.model);
|
|
3503
|
+
if (own !== undefined)
|
|
3504
|
+
return build(sessionProvider, own, own.upstreamId);
|
|
3505
|
+
if (selfQualified) {
|
|
3506
|
+
const byKey = modelsByKey.get(request.model);
|
|
3507
|
+
if (byKey !== undefined && byKey.providerId === sessionProviderId)
|
|
3508
|
+
return build(sessionProvider, byKey, byKey.upstreamId);
|
|
3509
|
+
const byName = sessionNames?.get(rest);
|
|
3510
|
+
if (byName !== undefined)
|
|
3511
|
+
return build(sessionProvider, byName, byName.upstreamId);
|
|
3512
|
+
}
|
|
3513
|
+
if (allowUnlisted && sessionProvider.liveCatalogAuthority !== "authoritative") {
|
|
3514
|
+
return build(sessionProvider, undefined, withinProvider);
|
|
3515
|
+
}
|
|
3516
|
+
if (prefix !== undefined && rest !== undefined && prefix !== sessionProviderId && providersById.has(prefix)) {
|
|
3517
|
+
return new WinterProviderResolutionError("provider-mismatch", `model "${request.model}" is qualified for provider "${prefix}" but this session's provider is "${sessionProviderId}" — Winter never substitutes one provider for another (WS-13 §9); pass an id in "${sessionProviderId}"'s own namespace, or change the session provider`);
|
|
3518
|
+
}
|
|
3519
|
+
return resolveWithin(sessionProvider, withinProvider, false);
|
|
3520
|
+
}
|
|
3521
|
+
if (prefix === undefined || rest === undefined) {
|
|
3522
|
+
return new WinterProviderResolutionError("no-provider-for-bare-model", `bare model id "${request.model}" needs a provider — pass a qualified "<providerId>/<model>" key or set \`provider.providerId\``);
|
|
3523
|
+
}
|
|
3524
|
+
const provider = providersById.get(prefix);
|
|
3525
|
+
if (provider === undefined) {
|
|
3526
|
+
return new WinterProviderResolutionError("unknown-provider", `no provider "${prefix}" in catalog ${catalog.catalogVersion}`);
|
|
3527
|
+
}
|
|
3528
|
+
const byKey = modelsByKey.get(request.model);
|
|
3529
|
+
if (byKey !== undefined)
|
|
3530
|
+
return build(provider, byKey, byKey.upstreamId);
|
|
3531
|
+
return resolveWithin(provider, rest, allowUnlisted);
|
|
3532
|
+
},
|
|
3533
|
+
list() {
|
|
3534
|
+
return {
|
|
3535
|
+
catalogVersion: catalog.catalogVersion,
|
|
3536
|
+
adapters: [...adapters.values()].map((a) => ({ id: a.id, version: a.version, family: a.family, protocol: a.protocol })),
|
|
3537
|
+
providers: catalog.providers.map((p) => ({
|
|
3538
|
+
id: p.id,
|
|
3539
|
+
displayName: p.displayName,
|
|
3540
|
+
adapterId: p.adapterId,
|
|
3541
|
+
adapterRegistered: adapters.has(p.adapterId),
|
|
3542
|
+
modelCount: catalog.models.filter((m) => m.providerId === p.id).length,
|
|
3543
|
+
riskClass: p.risk.class
|
|
3544
|
+
}))
|
|
3545
|
+
};
|
|
3546
|
+
},
|
|
3547
|
+
listModelInfo(sessionProviderId) {
|
|
3548
|
+
const rows = [];
|
|
3549
|
+
for (const model of catalog.models) {
|
|
3550
|
+
if (model.providerId !== sessionProviderId)
|
|
3551
|
+
continue;
|
|
3552
|
+
const efforts = model.reasoning?.efforts ?? [];
|
|
3553
|
+
const levels = PINNED_EFFORT_LEVELS.filter((l) => efforts.includes(l));
|
|
3554
|
+
const capability = levels.length > 0 ? { supportsEffort: true, supportedEffortLevels: levels } : {};
|
|
3555
|
+
const base = {
|
|
3556
|
+
displayName: model.displayName,
|
|
3557
|
+
description: `${model.displayName} — ${model.status}${model.reasoning !== undefined ? ", reasoning-capable" : ""} (catalog ${catalog.catalogVersion})`,
|
|
3558
|
+
...capability
|
|
3559
|
+
};
|
|
3560
|
+
rows.push({ value: model.key, resolvedModel: model.upstreamId, ...base });
|
|
3561
|
+
for (const alias of model.aliases)
|
|
3562
|
+
rows.push({ value: alias, resolvedModel: model.upstreamId, ...base });
|
|
3563
|
+
}
|
|
3564
|
+
return rows;
|
|
3565
|
+
}
|
|
3566
|
+
};
|
|
3567
|
+
}
|
|
3568
|
+
|
|
3569
|
+
// ../provider-runtime/src/continuity/domains.ts
|
|
3570
|
+
function sameDomain(a, b) {
|
|
3571
|
+
const left = a?.continuationDomain;
|
|
3572
|
+
const right = b?.continuationDomain;
|
|
3573
|
+
if (left === undefined || right === undefined)
|
|
3574
|
+
return false;
|
|
3575
|
+
return left === right;
|
|
3576
|
+
}
|
|
3577
|
+
var CERTIFIED_DOMAIN_CONFIDENCES = new Set(["verified", "declared"]);
|
|
3578
|
+
function readableStateOf(descriptor) {
|
|
3579
|
+
return descriptor?.reasoning?.readableState?.value ?? "none";
|
|
3580
|
+
}
|
|
3581
|
+
function summaryRequestOf(descriptor) {
|
|
3582
|
+
return descriptor?.reasoning?.summaryRequest?.value;
|
|
3583
|
+
}
|
|
3584
|
+
function shouldRequestSummary(descriptor) {
|
|
3585
|
+
return summaryRequestOf(descriptor) !== undefined;
|
|
3586
|
+
}
|
|
3587
|
+
function createEndpointResolver(registry) {
|
|
3588
|
+
const cache = new Map;
|
|
3589
|
+
return (origin) => {
|
|
3590
|
+
const cached = cache.get(origin.modelKey);
|
|
3591
|
+
if (cached !== undefined)
|
|
3592
|
+
return cached;
|
|
3593
|
+
const facts = endpointFromRegistry(registry, origin);
|
|
3594
|
+
cache.set(origin.modelKey, facts);
|
|
3595
|
+
return facts;
|
|
3596
|
+
};
|
|
3597
|
+
}
|
|
3598
|
+
function endpointFromRegistry(registry, origin) {
|
|
3599
|
+
const resolved = registry.resolve({ model: origin.modelKey, provider: { providerId: origin.providerId } });
|
|
3600
|
+
if (resolved instanceof WinterProviderResolutionError)
|
|
3601
|
+
return endpointFromOrigin(origin);
|
|
3602
|
+
const descriptor = resolved.descriptor;
|
|
3603
|
+
const summaryRequest = summaryRequestOf(descriptor);
|
|
3604
|
+
const domain = certifiedDomain(resolved.continuationDomain, descriptor);
|
|
3605
|
+
return {
|
|
3606
|
+
providerId: resolved.providerId,
|
|
3607
|
+
modelKey: resolved.modelKey,
|
|
3608
|
+
family: origin.family,
|
|
3609
|
+
...domain !== undefined ? { continuationDomain: domain } : resolved.continuationDomain === undefined && origin.continuationDomain !== undefined ? { continuationDomain: origin.continuationDomain } : {},
|
|
3610
|
+
...descriptor !== undefined ? { continuation: descriptor.reasoning?.continuation ?? "none" } : {},
|
|
3611
|
+
readableState: readableStateOf(descriptor),
|
|
3612
|
+
...summaryRequest !== undefined ? { summaryRequest } : {}
|
|
3613
|
+
};
|
|
3614
|
+
}
|
|
3615
|
+
function certifiedDomain(domain, descriptor) {
|
|
3616
|
+
if (domain === undefined)
|
|
3617
|
+
return;
|
|
3618
|
+
const evidence = descriptor?.reasoning?.continuationDomain;
|
|
3619
|
+
if (evidence === undefined)
|
|
3620
|
+
return domain;
|
|
3621
|
+
if (CERTIFIED_DOMAIN_CONFIDENCES.has(evidence.confidence))
|
|
3622
|
+
return domain;
|
|
3623
|
+
const key = descriptor?.key;
|
|
3624
|
+
if (key !== undefined && evidence.value.length === 1 && evidence.value[0] === key)
|
|
3625
|
+
return key;
|
|
3626
|
+
return;
|
|
3627
|
+
}
|
|
3628
|
+
function endpointFromOrigin(origin) {
|
|
3629
|
+
return {
|
|
3630
|
+
providerId: origin.providerId,
|
|
3631
|
+
modelKey: origin.modelKey,
|
|
3632
|
+
family: typeof origin.family === "string" ? origin.family : String(origin.family),
|
|
3633
|
+
...origin.continuationDomain !== undefined ? { continuationDomain: origin.continuationDomain } : {},
|
|
3634
|
+
readableState: "none"
|
|
3635
|
+
};
|
|
3636
|
+
}
|
|
3637
|
+
|
|
3638
|
+
// ../provider-runtime/src/continuity/fixtures.ts
|
|
3639
|
+
var evidence2 = (value, confidence = "verified") => ({
|
|
3640
|
+
value,
|
|
3641
|
+
source: "official-doc",
|
|
3642
|
+
observedAt: "2026-09-05",
|
|
3643
|
+
confidence
|
|
3644
|
+
});
|
|
3645
|
+
var stampRow2 = (row) => stampFamilyFields([row], [])[0];
|
|
3646
|
+
function fixtureModel(init) {
|
|
3647
|
+
return stampRow2({
|
|
3648
|
+
key: init.key,
|
|
3649
|
+
providerId: init.providerId,
|
|
3650
|
+
upstreamId: init.upstreamId ?? init.key.slice(init.key.indexOf("/") + 1),
|
|
3651
|
+
displayName: init.key,
|
|
3652
|
+
aliases: init.aliases ?? [],
|
|
3653
|
+
endpoints: ["responses"],
|
|
3654
|
+
...init.contextWindow !== undefined ? { contextWindow: evidence2(init.contextWindow) } : {},
|
|
3655
|
+
inputModalities: evidence2(["text"]),
|
|
3656
|
+
outputModalities: evidence2(["text"]),
|
|
3657
|
+
toolCalling: evidence2(init.toolCalling ?? "native"),
|
|
3658
|
+
nativeTools: evidence2(true),
|
|
3659
|
+
...init.reasoning !== undefined ? { reasoning: init.reasoning } : {},
|
|
3660
|
+
unsupportedParameters: [],
|
|
3661
|
+
status: "supported"
|
|
3662
|
+
});
|
|
3663
|
+
}
|
|
3664
|
+
function fixtureReasoning(init) {
|
|
3665
|
+
return {
|
|
3666
|
+
supported: evidence2(true),
|
|
3667
|
+
efforts: init.efforts ?? [],
|
|
3668
|
+
continuation: init.continuation ?? "opaque-provider-state",
|
|
3669
|
+
readableState: evidence2(init.readableState, init.confidence),
|
|
3670
|
+
...init.summaryRequest !== undefined ? { summaryRequest: evidence2(init.summaryRequest, init.confidence) } : {},
|
|
3671
|
+
...init.domain !== undefined ? { continuationDomain: evidence2(init.domain, init.confidence) } : {}
|
|
3672
|
+
};
|
|
3673
|
+
}
|
|
3674
|
+
function fixtureProvider(init) {
|
|
3675
|
+
return {
|
|
3676
|
+
id: init.id,
|
|
3677
|
+
displayName: init.id,
|
|
3678
|
+
protocols: ["openai-responses"],
|
|
3679
|
+
authKinds: ["api-key"],
|
|
3680
|
+
defaultEndpoints: { base: init.baseUrl ?? "https://example.invalid/v1/responses" },
|
|
3681
|
+
modelDiscovery: "none",
|
|
3682
|
+
liveCatalogAuthority: "partial",
|
|
3683
|
+
adapterId: init.adapterId ?? `${init.id}-adapter`,
|
|
3684
|
+
family: init.family ?? "openai",
|
|
3685
|
+
upstream: { project: "winter", commit: "", sourcePaths: [] },
|
|
3686
|
+
risk: { class: "approved", reasons: [] },
|
|
3687
|
+
scope: "llm",
|
|
3688
|
+
pricingBasis: "token",
|
|
3689
|
+
admission: { basis: "api-key", citation: "fixture:continuity", tier: "local" }
|
|
3690
|
+
};
|
|
3691
|
+
}
|
|
3692
|
+
function fixtureCatalog(providers, models) {
|
|
3693
|
+
return {
|
|
3694
|
+
schemaVersion: 2,
|
|
3695
|
+
families: [],
|
|
3696
|
+
catalogVersion: "0.0.0-continuity-fixture",
|
|
3697
|
+
upstream: { tag: "", tagObject: "", commit: "", extractorVersion: "", overlayVersion: "" },
|
|
3698
|
+
providers,
|
|
3699
|
+
models
|
|
3700
|
+
};
|
|
3701
|
+
}
|
|
3702
|
+
function scriptedAdapter(init) {
|
|
3703
|
+
const requests = [];
|
|
3704
|
+
return {
|
|
3705
|
+
id: init.id,
|
|
3706
|
+
version: "0.0.0-fixture",
|
|
3707
|
+
family: init.family ?? "openai",
|
|
3708
|
+
protocol: "openai-responses",
|
|
3709
|
+
requests,
|
|
3710
|
+
async validateCredential() {
|
|
3711
|
+
return { ok: true };
|
|
3712
|
+
},
|
|
3713
|
+
async listModels() {
|
|
3714
|
+
return { models: [], partial: false, cached: false, warnings: [] };
|
|
3715
|
+
},
|
|
3716
|
+
streamTurn(req) {
|
|
3717
|
+
requests.push(req);
|
|
3718
|
+
const events = init.events ?? [{ type: "done", stopReason: "end_turn" }];
|
|
3719
|
+
return async function* () {
|
|
3720
|
+
for (const event of events)
|
|
3721
|
+
yield event;
|
|
3722
|
+
}();
|
|
3723
|
+
},
|
|
3724
|
+
mapEffort() {
|
|
3725
|
+
return { ok: true, value: undefined };
|
|
3726
|
+
},
|
|
3727
|
+
capabilities(model) {
|
|
3728
|
+
const domain = model.reasoning?.continuationDomain?.value;
|
|
3729
|
+
return {
|
|
3730
|
+
toolCalling: model.toolCalling.value,
|
|
3731
|
+
...domain !== undefined && domain.length > 0 ? { continuationDomain: [...domain].sort()[0] } : {},
|
|
3732
|
+
readableState: readableStateOf(model)
|
|
3733
|
+
};
|
|
3734
|
+
}
|
|
3735
|
+
};
|
|
3736
|
+
}
|
|
3737
|
+
// src/fakes/bedrock.ts
|
|
3738
|
+
var FAKE_ACCESS_KEY_ID = "AKIDEXAMPLE";
|
|
3739
|
+
var FAKE_SECRET_ACCESS_KEY = "wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY";
|
|
3740
|
+
var FAKE_REGION = "us-east-1";
|
|
3741
|
+
function bedrockModelOf(recorded) {
|
|
3742
|
+
const match = /^\/model\/([^/]+)\/converse(?:-stream)?$/.exec(recorded.path);
|
|
3743
|
+
if (match === null)
|
|
3744
|
+
return;
|
|
3745
|
+
try {
|
|
3746
|
+
return decodeURIComponent(match[1]);
|
|
3747
|
+
} catch {
|
|
3748
|
+
return match[1];
|
|
3749
|
+
}
|
|
3750
|
+
}
|
|
3751
|
+
function isStreamingPath(recorded) {
|
|
3752
|
+
return recorded.path.endsWith("/converse-stream");
|
|
3753
|
+
}
|
|
3754
|
+
function eventStreamResponse(frames, opts = {}) {
|
|
3755
|
+
const kept = opts.dropAfter !== undefined ? frames.slice(0, opts.dropAfter) : frames;
|
|
3756
|
+
const pieces = [];
|
|
3757
|
+
for (const frame of kept) {
|
|
3758
|
+
if (opts.chunkSize === undefined) {
|
|
3759
|
+
pieces.push({ bytes: frame, startsFrame: true });
|
|
3760
|
+
continue;
|
|
3761
|
+
}
|
|
3762
|
+
for (let offset = 0;offset < frame.length; offset += opts.chunkSize) {
|
|
3763
|
+
pieces.push({ bytes: frame.slice(offset, Math.min(offset + opts.chunkSize, frame.length)), startsFrame: offset === 0 });
|
|
3764
|
+
}
|
|
3765
|
+
}
|
|
3766
|
+
let index = 0;
|
|
3767
|
+
let holdTimer;
|
|
3768
|
+
let releaseHold;
|
|
3769
|
+
let closed = false;
|
|
3770
|
+
const body = new ReadableStream({
|
|
3771
|
+
async pull(controller) {
|
|
3772
|
+
if (closed)
|
|
3773
|
+
return;
|
|
3774
|
+
if (index >= pieces.length) {
|
|
3775
|
+
if (opts.holdOpenMs !== undefined) {
|
|
3776
|
+
await new Promise((resolve) => {
|
|
3777
|
+
releaseHold = resolve;
|
|
3778
|
+
holdTimer = setTimeout(resolve, opts.holdOpenMs);
|
|
3779
|
+
});
|
|
3780
|
+
if (closed)
|
|
3781
|
+
return;
|
|
3782
|
+
}
|
|
3783
|
+
closed = true;
|
|
3784
|
+
controller.close();
|
|
3785
|
+
return;
|
|
3786
|
+
}
|
|
3787
|
+
const piece = pieces[index];
|
|
3788
|
+
index++;
|
|
3789
|
+
if (index > 1 && piece.startsFrame && opts.delayMs !== undefined && opts.delayMs > 0)
|
|
3790
|
+
await new Promise((r) => setTimeout(r, opts.delayMs));
|
|
3791
|
+
if (closed)
|
|
3792
|
+
return;
|
|
3793
|
+
controller.enqueue(piece.bytes);
|
|
3794
|
+
},
|
|
3795
|
+
cancel() {
|
|
3796
|
+
closed = true;
|
|
3797
|
+
if (holdTimer !== undefined)
|
|
3798
|
+
clearTimeout(holdTimer);
|
|
3799
|
+
releaseHold?.();
|
|
3800
|
+
}
|
|
3801
|
+
});
|
|
3802
|
+
return new Response(body, {
|
|
3803
|
+
status: opts.status ?? 200,
|
|
3804
|
+
headers: { "content-type": "application/vnd.amazon.eventstream" }
|
|
3805
|
+
});
|
|
3806
|
+
}
|
|
3807
|
+
function bedrockError(status, errorType, message, headers = {}) {
|
|
3808
|
+
return jsonResponse2({ message }, status, { "x-amzn-errortype": `${errorType}:http://internal.amazon.com/coral/com.amazon.bedrock/`, ...headers });
|
|
3809
|
+
}
|
|
3810
|
+
function textTurnFrames(text, usage = { inputTokens: 7, outputTokens: 3 }) {
|
|
3811
|
+
return [
|
|
3812
|
+
converseStreamEvent("messageStart", { role: "assistant" }),
|
|
3813
|
+
converseStreamEvent("contentBlockDelta", { contentBlockIndex: 0, delta: { text } }),
|
|
3814
|
+
converseStreamEvent("contentBlockStop", { contentBlockIndex: 0 }),
|
|
3815
|
+
converseStreamEvent("messageStop", { stopReason: "end_turn" }),
|
|
3816
|
+
converseStreamEvent("metadata", { usage: { ...usage, totalTokens: usage.inputTokens + usage.outputTokens }, metrics: { latencyMs: 12 } })
|
|
3817
|
+
];
|
|
3818
|
+
}
|
|
3819
|
+
function shapeRefusal(bodyText) {
|
|
3820
|
+
let parsed;
|
|
3821
|
+
try {
|
|
3822
|
+
parsed = JSON.parse(bodyText);
|
|
3823
|
+
} catch {
|
|
3824
|
+
return "the request body is not valid JSON";
|
|
3825
|
+
}
|
|
3826
|
+
const messages = parsed.messages;
|
|
3827
|
+
if (!Array.isArray(messages))
|
|
3828
|
+
return "messages is required";
|
|
3829
|
+
let previous;
|
|
3830
|
+
for (const message of messages) {
|
|
3831
|
+
const role = message.role;
|
|
3832
|
+
const content = message.content;
|
|
3833
|
+
if (role !== "user" && role !== "assistant")
|
|
3834
|
+
return `a message declares role ${JSON.stringify(role)}; only "user" and "assistant" are accepted`;
|
|
3835
|
+
if (role === previous)
|
|
3836
|
+
return `messages must alternate between user and assistant; two consecutive "${role}" messages were sent`;
|
|
3837
|
+
previous = role;
|
|
3838
|
+
if (!Array.isArray(content) || content.length === 0)
|
|
3839
|
+
return "a message carries no content blocks";
|
|
3840
|
+
for (const block of content) {
|
|
3841
|
+
if (typeof block.text === "string" && block.text.length === 0)
|
|
3842
|
+
return "a text content block is empty";
|
|
3843
|
+
}
|
|
3844
|
+
}
|
|
3845
|
+
return;
|
|
3846
|
+
}
|
|
3847
|
+
async function startBedrockFake(opts) {
|
|
3848
|
+
const secret = opts.secretAccessKey ?? FAKE_SECRET_ACCESS_KEY;
|
|
3849
|
+
const table = scenarioTable2({ modelOf: bedrockModelOf, scenarios: opts.scenarios });
|
|
3850
|
+
const signatures = [];
|
|
3851
|
+
const checkSignature = async (req, recorded) => {
|
|
3852
|
+
if (opts.skipSignatureCheck === true)
|
|
3853
|
+
return;
|
|
3854
|
+
const verdict = await verifySigV4({
|
|
3855
|
+
method: recorded.method,
|
|
3856
|
+
url: req.url,
|
|
3857
|
+
headers: req.headers,
|
|
3858
|
+
body: new TextEncoder().encode(recorded.body),
|
|
3859
|
+
secretAccessKey: secret,
|
|
3860
|
+
expectedAccessKeyId: FAKE_ACCESS_KEY_ID
|
|
3861
|
+
});
|
|
3862
|
+
const parsed = parseAuthorization(req.headers.get("authorization"));
|
|
3863
|
+
if (parsed !== undefined) {
|
|
3864
|
+
signatures.push({ path: recorded.path, signedHeaders: parsed.signedHeaders, accessKeyId: parsed.accessKeyId, region: parsed.region, service: parsed.service, verified: verdict.ok });
|
|
3865
|
+
}
|
|
3866
|
+
if (verdict.ok)
|
|
3867
|
+
return;
|
|
3868
|
+
return bedrockError(403, "InvalidSignatureException", `the request signature is invalid: ${verdict.reason}`);
|
|
3869
|
+
};
|
|
3870
|
+
const fake = await startFake2({
|
|
3871
|
+
routes: [
|
|
3872
|
+
{
|
|
3873
|
+
path: "/model/*",
|
|
3874
|
+
method: "POST",
|
|
3875
|
+
handler: async (req, recorded) => {
|
|
3876
|
+
const refusedSignature = await checkSignature(req, recorded);
|
|
3877
|
+
if (refusedSignature !== undefined)
|
|
3878
|
+
return refusedSignature;
|
|
3879
|
+
if (opts.skipShapeCheck !== true) {
|
|
3880
|
+
const refusal = shapeRefusal(recorded.body);
|
|
3881
|
+
if (refusal !== undefined)
|
|
3882
|
+
return bedrockError(400, "ValidationException", refusal);
|
|
3883
|
+
}
|
|
3884
|
+
return await table(req, recorded);
|
|
3885
|
+
}
|
|
3886
|
+
},
|
|
3887
|
+
{
|
|
3888
|
+
path: "/foundation-models",
|
|
3889
|
+
method: "GET",
|
|
3890
|
+
handler: async (req, recorded) => {
|
|
3891
|
+
const refusedSignature = await checkSignature(req, recorded);
|
|
3892
|
+
if (refusedSignature !== undefined)
|
|
3893
|
+
return refusedSignature;
|
|
3894
|
+
if (opts.discovery !== undefined)
|
|
3895
|
+
return await opts.discovery(recorded, 1);
|
|
3896
|
+
return jsonResponse2({
|
|
3897
|
+
modelSummaries: [
|
|
3898
|
+
{ modelId: "anthropic.claude-3-5-sonnet-20241022-v2:0", modelName: "Claude 3.5 Sonnet v2", inputModalities: ["TEXT", "IMAGE"], responseStreamingSupported: true },
|
|
3899
|
+
{ modelId: "amazon.titan-text-express-v1", modelName: "Titan Text Express", inputModalities: ["TEXT"], responseStreamingSupported: false }
|
|
3900
|
+
]
|
|
3901
|
+
});
|
|
3902
|
+
}
|
|
3903
|
+
}
|
|
3904
|
+
]
|
|
3905
|
+
});
|
|
3906
|
+
return Object.assign(fake, { signatures });
|
|
3907
|
+
}
|
|
3908
|
+
// src/fakes/codex-oauth.ts
|
|
3909
|
+
var exports_codex_oauth = {};
|
|
3910
|
+
__export(exports_codex_oauth, {
|
|
3911
|
+
FAKE_ACCESS_TOKEN: () => FAKE_ACCESS_TOKEN,
|
|
3912
|
+
FAKE_ACCOUNT_ID: () => FAKE_ACCOUNT_ID,
|
|
3913
|
+
FAKE_REFRESHED_ACCESS_TOKEN: () => FAKE_REFRESHED_ACCESS_TOKEN,
|
|
3914
|
+
FAKE_REFRESH_TOKEN: () => FAKE_REFRESH_TOKEN,
|
|
3915
|
+
codexTokenRoute: () => codexTokenRoute,
|
|
3916
|
+
fakeIdToken: () => fakeIdToken2,
|
|
3917
|
+
startCodexFake: () => startCodexFake
|
|
3918
|
+
});
|
|
3919
|
+
var FAKE_ACCOUNT_ID = "acct-test-0001";
|
|
3920
|
+
var FAKE_ACCESS_TOKEN = "test-token-codex-access";
|
|
3921
|
+
var FAKE_REFRESHED_ACCESS_TOKEN = "test-token-codex-access-refreshed";
|
|
3922
|
+
var FAKE_REFRESH_TOKEN = "test-token-codex-refresh";
|
|
3923
|
+
function b64url(value) {
|
|
3924
|
+
return btoa(value).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
3925
|
+
}
|
|
3926
|
+
function fakeIdToken2(accountId = FAKE_ACCOUNT_ID) {
|
|
3927
|
+
const header = b64url(JSON.stringify({ alg: "none", typ: "JWT" }));
|
|
3928
|
+
const payload = b64url(JSON.stringify({ sub: "user-test", "https://api.openai.com/auth": { chatgpt_account_id: accountId } }));
|
|
3929
|
+
return `${header}.${payload}.`;
|
|
3930
|
+
}
|
|
3931
|
+
function codexTokenRoute(opts = {}) {
|
|
3932
|
+
return {
|
|
3933
|
+
path: "/oauth/token",
|
|
3934
|
+
method: "POST",
|
|
3935
|
+
handler: (_req, recorded) => {
|
|
3936
|
+
if (opts.failWith !== undefined)
|
|
3937
|
+
return jsonResponse2({ error: "invalid_grant", error_description: "the fake refused this grant" }, opts.failWith);
|
|
3938
|
+
const form = new URLSearchParams(recorded.body);
|
|
3939
|
+
const isRefresh = form.get("grant_type") === "refresh_token";
|
|
3940
|
+
return jsonResponse2({
|
|
3941
|
+
access_token: opts.accessToken ?? (isRefresh ? FAKE_REFRESHED_ACCESS_TOKEN : FAKE_ACCESS_TOKEN),
|
|
3942
|
+
...opts.omitRefreshToken === true ? {} : { refresh_token: FAKE_REFRESH_TOKEN },
|
|
3943
|
+
...opts.omitIdToken === true ? {} : { id_token: fakeIdToken2(opts.accountId ?? FAKE_ACCOUNT_ID) },
|
|
3944
|
+
expires_in: opts.expiresIn ?? 3600,
|
|
3945
|
+
token_type: "Bearer"
|
|
3946
|
+
});
|
|
3947
|
+
}
|
|
3948
|
+
};
|
|
3949
|
+
}
|
|
3950
|
+
async function startCodexFake(opts) {
|
|
3951
|
+
const bearers = [];
|
|
3952
|
+
const attempts = new Map;
|
|
3953
|
+
const needsRefresh = new Set(opts.requireRefreshFor ?? []);
|
|
3954
|
+
const handler = (req, recorded) => {
|
|
3955
|
+
const pairing = callPairingRefusal(recorded.body);
|
|
3956
|
+
if (pairing !== undefined)
|
|
3957
|
+
return pairing;
|
|
3958
|
+
const authorization = req.headers.get("authorization") ?? "";
|
|
3959
|
+
bearers.push(authorization.startsWith("Bearer ") ? authorization.slice("Bearer ".length) : authorization);
|
|
3960
|
+
const model = responsesModelOf(recorded);
|
|
3961
|
+
const key = model ?? "";
|
|
3962
|
+
const attempt = (attempts.get(key) ?? 0) + 1;
|
|
3963
|
+
attempts.set(key, attempt);
|
|
3964
|
+
if (model !== undefined && needsRefresh.has(model) && attempt === 1) {
|
|
3965
|
+
return jsonResponse2({ error: { message: "your credential is no longer valid", type: "invalid_request_error", code: "invalid_api_key" } }, 401);
|
|
3966
|
+
}
|
|
3967
|
+
const entry = model !== undefined ? opts.scenarios[model] : undefined;
|
|
3968
|
+
if (entry === undefined) {
|
|
3969
|
+
if (opts.unknownModel !== undefined)
|
|
3970
|
+
return opts.unknownModel(recorded, attempt);
|
|
3971
|
+
return jsonResponse2({ error: { message: `fake: no scenario for model ${JSON.stringify(model)}` } }, 400);
|
|
3972
|
+
}
|
|
3973
|
+
if (Array.isArray(entry)) {
|
|
3974
|
+
const served = needsRefresh.has(key) ? attempt - 2 : attempt - 1;
|
|
3975
|
+
return entry[Math.min(Math.max(0, served), entry.length - 1)];
|
|
3976
|
+
}
|
|
3977
|
+
return entry(recorded, attempt);
|
|
3978
|
+
};
|
|
3979
|
+
const fake = await startFake2({
|
|
3980
|
+
routes: [
|
|
3981
|
+
{ path: "/responses", method: "POST", handler },
|
|
3982
|
+
{ path: "/backend-api/codex/responses", method: "POST", handler },
|
|
3983
|
+
codexTokenRoute(opts.token ?? {}),
|
|
3984
|
+
...opts.routes ?? []
|
|
3985
|
+
]
|
|
3986
|
+
});
|
|
3987
|
+
return Object.assign(fake, { bearers });
|
|
3988
|
+
}
|
|
3989
|
+
// src/fakes/gemini.ts
|
|
3990
|
+
var exports_gemini = {};
|
|
3991
|
+
__export(exports_gemini, {
|
|
3992
|
+
assertGeminiRequest: () => assertGeminiRequest,
|
|
3993
|
+
findFunctionResponseOrderingViolation: () => findFunctionResponseOrderingViolation,
|
|
3994
|
+
findRoleAlternationViolation: () => findRoleAlternationViolation,
|
|
3995
|
+
geminiBody: () => geminiBody,
|
|
3996
|
+
geminiContents: () => geminiContents,
|
|
3997
|
+
geminiError: () => geminiError,
|
|
3998
|
+
geminiFakeRoutes: () => geminiFakeRoutes,
|
|
3999
|
+
geminiModelOf: () => geminiModelOf,
|
|
4000
|
+
geminiSseFrames: () => geminiSseFrames,
|
|
4001
|
+
geminiStreamResponse: () => geminiStreamResponse,
|
|
4002
|
+
partKind: () => partKind
|
|
4003
|
+
});
|
|
4004
|
+
function geminiSseFrames(chunks) {
|
|
4005
|
+
return chunks.map((chunk) => {
|
|
4006
|
+
const payload = {};
|
|
4007
|
+
if (chunk.promptFeedback !== undefined)
|
|
4008
|
+
payload["promptFeedback"] = chunk.promptFeedback;
|
|
4009
|
+
else {
|
|
4010
|
+
payload["candidates"] = [
|
|
4011
|
+
{
|
|
4012
|
+
content: { role: "model", parts: chunk.parts ?? [] },
|
|
4013
|
+
...chunk.finishReason !== undefined ? { finishReason: chunk.finishReason } : {},
|
|
4014
|
+
index: 0
|
|
4015
|
+
}
|
|
4016
|
+
];
|
|
4017
|
+
}
|
|
4018
|
+
if (chunk.usageMetadata !== undefined)
|
|
4019
|
+
payload["usageMetadata"] = chunk.usageMetadata;
|
|
4020
|
+
if (chunk.modelVersion !== undefined)
|
|
4021
|
+
payload["modelVersion"] = chunk.modelVersion;
|
|
4022
|
+
return { data: JSON.stringify(payload), ...chunk.delayMs !== undefined ? { delayMs: chunk.delayMs } : {} };
|
|
4023
|
+
});
|
|
4024
|
+
}
|
|
4025
|
+
function geminiStreamResponse(chunks, opts = {}) {
|
|
4026
|
+
return sseResponse2(geminiSseFrames(chunks), opts);
|
|
4027
|
+
}
|
|
4028
|
+
function geminiError(status, googleStatus, message = "fake error", headers = {}) {
|
|
4029
|
+
return errorResponse2(status, { error: { code: status, message, status: googleStatus } }, headers);
|
|
4030
|
+
}
|
|
4031
|
+
function geminiModelOf(recorded) {
|
|
4032
|
+
const match = /\/models\/([^:/]+):/.exec(recorded.path);
|
|
4033
|
+
return match?.[1];
|
|
4034
|
+
}
|
|
4035
|
+
function geminiBody(recorded) {
|
|
4036
|
+
return JSON.parse(recorded.body);
|
|
4037
|
+
}
|
|
4038
|
+
function geminiContents(recorded) {
|
|
4039
|
+
const contents = geminiBody(recorded)["contents"];
|
|
4040
|
+
return Array.isArray(contents) ? contents : [];
|
|
4041
|
+
}
|
|
4042
|
+
function fail2(message, recorded) {
|
|
4043
|
+
throw new Error(`${message}
|
|
4044
|
+
live request: ${recorded.method} ${recorded.path}${recorded.search}
|
|
4045
|
+
headers: ${JSON.stringify(recorded.headers)}
|
|
4046
|
+
body: ${redactOpaqueFields2(recorded.body)}`);
|
|
4047
|
+
}
|
|
4048
|
+
function partKind(part) {
|
|
4049
|
+
if ("functionCall" in part)
|
|
4050
|
+
return "functionCall";
|
|
4051
|
+
if ("functionResponse" in part)
|
|
4052
|
+
return "functionResponse";
|
|
4053
|
+
if ("inlineData" in part)
|
|
4054
|
+
return "inlineData";
|
|
4055
|
+
if ("thought" in part && part.thought === true)
|
|
4056
|
+
return "thought";
|
|
4057
|
+
return "text";
|
|
4058
|
+
}
|
|
4059
|
+
function assertGeminiRequest(recorded, expected = {}) {
|
|
4060
|
+
if (recorded.method !== "POST")
|
|
4061
|
+
fail2(`expected a POST, saw ${recorded.method}`, recorded);
|
|
4062
|
+
if (recorded.headers["x-goog-api-key"] !== "***")
|
|
4063
|
+
fail2(`expected a redacted x-goog-api-key header, saw ${JSON.stringify(recorded.headers["x-goog-api-key"])}`, recorded);
|
|
4064
|
+
if (!(recorded.headers["content-type"] ?? "").startsWith("application/json"))
|
|
4065
|
+
fail2(`expected a JSON content-type, saw ${JSON.stringify(recorded.headers["content-type"])}`, recorded);
|
|
4066
|
+
if (expected.model !== undefined && geminiModelOf(recorded) !== expected.model)
|
|
4067
|
+
fail2(`expected model ${expected.model} in the PATH, saw ${JSON.stringify(geminiModelOf(recorded))}`, recorded);
|
|
4068
|
+
if (expected.search !== undefined && recorded.search !== expected.search)
|
|
4069
|
+
fail2(`expected search ${expected.search}, saw ${recorded.search}`, recorded);
|
|
4070
|
+
const body = geminiBody(recorded);
|
|
4071
|
+
const generationConfig = body["generationConfig"] ?? {};
|
|
4072
|
+
if (expected.maxOutputTokens !== undefined && generationConfig["maxOutputTokens"] !== expected.maxOutputTokens) {
|
|
4073
|
+
fail2(`expected maxOutputTokens ${expected.maxOutputTokens}, saw ${JSON.stringify(generationConfig["maxOutputTokens"])}`, recorded);
|
|
4074
|
+
}
|
|
4075
|
+
if (expected.thinkingConfig !== undefined && JSON.stringify(generationConfig["thinkingConfig"]) !== JSON.stringify(expected.thinkingConfig)) {
|
|
4076
|
+
fail2(`expected thinkingConfig ${JSON.stringify(expected.thinkingConfig)}, saw ${JSON.stringify(generationConfig["thinkingConfig"])}`, recorded);
|
|
4077
|
+
}
|
|
4078
|
+
if (expected.toolConfig !== undefined && JSON.stringify(body["toolConfig"]) !== JSON.stringify(expected.toolConfig)) {
|
|
4079
|
+
fail2(`expected toolConfig ${JSON.stringify(expected.toolConfig)}, saw ${JSON.stringify(body["toolConfig"])}`, recorded);
|
|
4080
|
+
}
|
|
4081
|
+
if (expected.functionNames !== undefined) {
|
|
4082
|
+
const tools = Array.isArray(body["tools"]) ? body["tools"] : [];
|
|
4083
|
+
const names = tools.flatMap((t) => (t.functionDeclarations ?? []).map((d) => d.name));
|
|
4084
|
+
if (JSON.stringify(names) !== JSON.stringify(expected.functionNames))
|
|
4085
|
+
fail2(`expected functionDeclarations ${JSON.stringify(expected.functionNames)}, saw ${JSON.stringify(names)}`, recorded);
|
|
4086
|
+
}
|
|
4087
|
+
if (expected.systemInstruction !== undefined) {
|
|
4088
|
+
const system = body["systemInstruction"];
|
|
4089
|
+
const text = system?.parts?.map((p) => p.text).join("") ?? undefined;
|
|
4090
|
+
if (text !== expected.systemInstruction)
|
|
4091
|
+
fail2(`expected systemInstruction ${JSON.stringify(expected.systemInstruction)}, saw ${JSON.stringify(text)}`, recorded);
|
|
4092
|
+
}
|
|
4093
|
+
const contents = geminiContents(recorded);
|
|
4094
|
+
if (expected.roles !== undefined) {
|
|
4095
|
+
const roles = contents.map((c) => c.role);
|
|
4096
|
+
if (JSON.stringify(roles) !== JSON.stringify(expected.roles))
|
|
4097
|
+
fail2(`expected roles ${JSON.stringify(expected.roles)}, saw ${JSON.stringify(roles)}`, recorded);
|
|
4098
|
+
}
|
|
4099
|
+
if (expected.partKinds !== undefined) {
|
|
4100
|
+
const kinds = contents.flatMap((c) => (c.parts ?? []).map(partKind));
|
|
4101
|
+
if (JSON.stringify(kinds) !== JSON.stringify(expected.partKinds))
|
|
4102
|
+
fail2(`expected part ordering ${JSON.stringify(expected.partKinds)}, saw ${JSON.stringify(kinds)}`, recorded);
|
|
4103
|
+
}
|
|
4104
|
+
}
|
|
4105
|
+
function findFunctionResponseOrderingViolation(recorded) {
|
|
4106
|
+
let body;
|
|
4107
|
+
try {
|
|
4108
|
+
body = JSON.parse(recorded.body);
|
|
4109
|
+
} catch {
|
|
4110
|
+
return;
|
|
4111
|
+
}
|
|
4112
|
+
const contents = Array.isArray(body.contents) ? body.contents : [];
|
|
4113
|
+
for (const [index, entry] of contents.entries()) {
|
|
4114
|
+
if (!Array.isArray(entry.parts))
|
|
4115
|
+
continue;
|
|
4116
|
+
let sawOther = false;
|
|
4117
|
+
for (const part of entry.parts) {
|
|
4118
|
+
const kind = partKind(part);
|
|
4119
|
+
if (kind === "functionResponse") {
|
|
4120
|
+
if (sawOther)
|
|
4121
|
+
return index;
|
|
4122
|
+
} else if (kind !== "inlineData") {
|
|
4123
|
+
sawOther = true;
|
|
4124
|
+
}
|
|
4125
|
+
}
|
|
4126
|
+
}
|
|
4127
|
+
return;
|
|
4128
|
+
}
|
|
4129
|
+
function findRoleAlternationViolation(recorded) {
|
|
4130
|
+
let body;
|
|
4131
|
+
try {
|
|
4132
|
+
body = JSON.parse(recorded.body);
|
|
4133
|
+
} catch {
|
|
4134
|
+
return;
|
|
4135
|
+
}
|
|
4136
|
+
const contents = Array.isArray(body.contents) ? body.contents : [];
|
|
4137
|
+
for (let i = 1;i < contents.length; i++) {
|
|
4138
|
+
if (contents[i]?.role === contents[i - 1]?.role)
|
|
4139
|
+
return i;
|
|
4140
|
+
}
|
|
4141
|
+
return;
|
|
4142
|
+
}
|
|
4143
|
+
function geminiFakeRoutes(opts) {
|
|
4144
|
+
const prefix = opts.prefix ?? "/v1beta";
|
|
4145
|
+
const attempts = new Map;
|
|
4146
|
+
return [
|
|
4147
|
+
{
|
|
4148
|
+
path: `${prefix}/models*`,
|
|
4149
|
+
method: "POST",
|
|
4150
|
+
handler: async (_req, recorded) => {
|
|
4151
|
+
const badEntry = findFunctionResponseOrderingViolation(recorded);
|
|
4152
|
+
if (badEntry !== undefined) {
|
|
4153
|
+
return geminiError(400, "INVALID_ARGUMENT", `contents[${badEntry}]: functionResponse parts must precede any other content in their turn`);
|
|
4154
|
+
}
|
|
4155
|
+
const badRole = findRoleAlternationViolation(recorded);
|
|
4156
|
+
if (badRole !== undefined) {
|
|
4157
|
+
return geminiError(400, "INVALID_ARGUMENT", `contents[${badRole}]: consecutive entries must not share a role — a conversation alternates`);
|
|
4158
|
+
}
|
|
4159
|
+
const model = geminiModelOf(recorded) ?? "";
|
|
4160
|
+
if (recorded.path.endsWith(":countTokens")) {
|
|
4161
|
+
return opts.countTokens?.(recorded) ?? new Response(JSON.stringify({ totalTokens: 42 }), { status: 200, headers: { "content-type": "application/json" } });
|
|
4162
|
+
}
|
|
4163
|
+
const attempt = (attempts.get(model) ?? 0) + 1;
|
|
4164
|
+
attempts.set(model, attempt);
|
|
4165
|
+
const entry = opts.stream[model];
|
|
4166
|
+
if (entry === undefined)
|
|
4167
|
+
return geminiError(400, "INVALID_ARGUMENT", `fake: no scenario for model ${JSON.stringify(model)}`);
|
|
4168
|
+
if (Array.isArray(entry))
|
|
4169
|
+
return entry[Math.min(attempt - 1, entry.length - 1)];
|
|
4170
|
+
return await entry(recorded, attempt);
|
|
4171
|
+
}
|
|
4172
|
+
},
|
|
4173
|
+
{
|
|
4174
|
+
path: `${prefix}/models`,
|
|
4175
|
+
method: "GET",
|
|
4176
|
+
handler: async (_req, recorded) => await opts.models?.(recorded) ?? new Response(JSON.stringify({ models: [] }), { status: 200, headers: { "content-type": "application/json" } })
|
|
4177
|
+
}
|
|
4178
|
+
];
|
|
4179
|
+
}
|
|
4180
|
+
// src/fakes/openai-models.ts
|
|
4181
|
+
var exports_openai_models = {};
|
|
4182
|
+
__export(exports_openai_models, {
|
|
4183
|
+
modelsNotFoundRoutes: () => modelsNotFoundRoutes,
|
|
4184
|
+
ollamaTagsRoute: () => ollamaTagsRoute,
|
|
4185
|
+
openAiModelsRoutes: () => openAiModelsRoutes
|
|
4186
|
+
});
|
|
4187
|
+
function openAiModelsRoutes(opts) {
|
|
4188
|
+
let calls = 0;
|
|
4189
|
+
const handler = (_req, recorded) => {
|
|
4190
|
+
calls += 1;
|
|
4191
|
+
if (opts.failOnCall !== undefined && calls >= opts.failOnCall.call) {
|
|
4192
|
+
return jsonResponse2(opts.failOnCall.body ?? { error: { message: "discovery is unavailable", type: "server_error", code: "server_error" } }, opts.failOnCall.status);
|
|
4193
|
+
}
|
|
4194
|
+
if (opts.notJson === true)
|
|
4195
|
+
return new Response("not json at all", { status: 200, headers: { "content-type": "application/json" } });
|
|
4196
|
+
const after = new URLSearchParams(recorded.search).get("after");
|
|
4197
|
+
let index = 0;
|
|
4198
|
+
if (after !== null) {
|
|
4199
|
+
const found = opts.pages.findIndex((page) => {
|
|
4200
|
+
const last = page.rows.at(-1);
|
|
4201
|
+
return last !== undefined && last.id === after;
|
|
4202
|
+
});
|
|
4203
|
+
index = found >= 0 ? found + 1 : 0;
|
|
4204
|
+
}
|
|
4205
|
+
const page = opts.pages[index] ?? { rows: [] };
|
|
4206
|
+
return jsonResponse2({ object: "list", data: page.rows, has_more: page.hasMore === true });
|
|
4207
|
+
};
|
|
4208
|
+
const prefix = opts.pathPrefix ?? "";
|
|
4209
|
+
return [
|
|
4210
|
+
{ path: `${prefix}/models`, method: "GET", handler },
|
|
4211
|
+
{ path: `${prefix}/v1/models`, method: "GET", handler }
|
|
4212
|
+
];
|
|
4213
|
+
}
|
|
4214
|
+
function ollamaTagsRoute(models) {
|
|
4215
|
+
return {
|
|
4216
|
+
path: "/api/tags",
|
|
4217
|
+
method: "GET",
|
|
4218
|
+
handler: () => jsonResponse2({ models: models.map((m) => ({ name: m.name, model: m.model ?? m.name, size: 1, details: { family: "llama" } })) })
|
|
4219
|
+
};
|
|
4220
|
+
}
|
|
4221
|
+
function modelsNotFoundRoutes() {
|
|
4222
|
+
const handler = () => jsonResponse2({ error: { message: "not found", code: "not_found" } }, 404);
|
|
4223
|
+
return [
|
|
4224
|
+
{ path: "/models", method: "GET", handler },
|
|
4225
|
+
{ path: "/v1/models", method: "GET", handler }
|
|
4226
|
+
];
|
|
4227
|
+
}
|
|
4228
|
+
// src/fakes/vertex.ts
|
|
4229
|
+
var exports_vertex = {};
|
|
4230
|
+
__export(exports_vertex, {
|
|
4231
|
+
VERTEX_TEST_ACCESS_TOKEN: () => VERTEX_TEST_ACCESS_TOKEN,
|
|
4232
|
+
assertVertexRequest: () => assertVertexRequest,
|
|
4233
|
+
generateTestKeyPair: () => generateTestKeyPair,
|
|
4234
|
+
vertexFakeRoutes: () => vertexFakeRoutes,
|
|
4235
|
+
vertexTokenUrl: () => vertexTokenUrl
|
|
4236
|
+
});
|
|
4237
|
+
|
|
4238
|
+
// src/fakes/jwt-verify.ts
|
|
4239
|
+
function base64UrlDecodeText2(value) {
|
|
4240
|
+
const padded = value.replace(/-/g, "+").replace(/_/g, "/") + "=".repeat((4 - value.length % 4) % 4);
|
|
4241
|
+
const binary = atob(padded);
|
|
4242
|
+
const bytes = new Uint8Array(binary.length);
|
|
4243
|
+
for (let i = 0;i < binary.length; i++)
|
|
4244
|
+
bytes[i] = binary.charCodeAt(i);
|
|
4245
|
+
return new TextDecoder().decode(bytes);
|
|
4246
|
+
}
|
|
4247
|
+
function base64UrlDecodeBytes2(value) {
|
|
4248
|
+
const padded = value.replace(/-/g, "+").replace(/_/g, "/") + "=".repeat((4 - value.length % 4) % 4);
|
|
4249
|
+
const binary = atob(padded);
|
|
4250
|
+
const bytes = new Uint8Array(binary.length);
|
|
4251
|
+
for (let i = 0;i < binary.length; i++)
|
|
4252
|
+
bytes[i] = binary.charCodeAt(i);
|
|
4253
|
+
return bytes;
|
|
4254
|
+
}
|
|
4255
|
+
async function verifyRs256Jwt2(jwt, publicKey) {
|
|
4256
|
+
const parts = jwt.split(".");
|
|
4257
|
+
if (parts.length !== 3)
|
|
4258
|
+
return;
|
|
4259
|
+
const [header, payload, signature] = parts;
|
|
4260
|
+
let ok;
|
|
4261
|
+
try {
|
|
4262
|
+
ok = await crypto.subtle.verify(RS256.name, publicKey, base64UrlDecodeBytes2(signature).slice().buffer, new TextEncoder().encode(`${header}.${payload}`));
|
|
4263
|
+
} catch {
|
|
4264
|
+
return;
|
|
4265
|
+
}
|
|
4266
|
+
if (!ok)
|
|
4267
|
+
return;
|
|
4268
|
+
try {
|
|
4269
|
+
return { header: JSON.parse(base64UrlDecodeText2(header)), claims: JSON.parse(base64UrlDecodeText2(payload)) };
|
|
4270
|
+
} catch {
|
|
4271
|
+
return;
|
|
4272
|
+
}
|
|
4273
|
+
}
|
|
4274
|
+
|
|
4275
|
+
// src/fakes/vertex.ts
|
|
4276
|
+
async function generateTestKeyPair() {
|
|
4277
|
+
const pair = await crypto.subtle.generateKey({ name: "RSASSA-PKCS1-v1_5", modulusLength: 2048, publicExponent: new Uint8Array([1, 0, 1]), hash: "SHA-256" }, true, ["sign", "verify"]);
|
|
4278
|
+
const pkcs8 = new Uint8Array(await crypto.subtle.exportKey("pkcs8", pair.privateKey));
|
|
4279
|
+
const body = base64UrlEncode(pkcs8).replace(/-/g, "+").replace(/_/g, "/");
|
|
4280
|
+
const padded = body + "=".repeat((4 - body.length % 4) % 4);
|
|
4281
|
+
const lines = padded.match(/.{1,64}/g) ?? [];
|
|
4282
|
+
return { privateKeyPem: `-----BEGIN PRIVATE KEY-----
|
|
4283
|
+
${lines.join(`
|
|
4284
|
+
`)}
|
|
4285
|
+
-----END PRIVATE KEY-----
|
|
4286
|
+
`, publicKey: pair.publicKey };
|
|
4287
|
+
}
|
|
4288
|
+
var VERTEX_TEST_ACCESS_TOKEN = "test-vertex-access-token-1";
|
|
4289
|
+
var REQUIRED_SCOPE = "https://www.googleapis.com/auth/cloud-platform";
|
|
4290
|
+
async function handleTokenExchange(recorded, opts) {
|
|
4291
|
+
if (opts.rejectExchange === true)
|
|
4292
|
+
return errorResponse2(401, { error: "invalid_grant", error_description: "fake: the exchange was scripted to fail" });
|
|
4293
|
+
const form = new URLSearchParams(recorded.body);
|
|
4294
|
+
if (form.get("grant_type") !== "urn:ietf:params:oauth:grant-type:jwt-bearer") {
|
|
4295
|
+
return errorResponse2(400, { error: "unsupported_grant_type", error_description: `saw ${JSON.stringify(form.get("grant_type"))}` });
|
|
4296
|
+
}
|
|
4297
|
+
const assertion = form.get("assertion");
|
|
4298
|
+
if (assertion === null)
|
|
4299
|
+
return errorResponse2(400, { error: "invalid_request", error_description: "no assertion" });
|
|
4300
|
+
const verified = await verifyRs256Jwt2(assertion, opts.publicKey);
|
|
4301
|
+
if (verified === undefined)
|
|
4302
|
+
return errorResponse2(401, { error: "invalid_grant", error_description: "the assertion's RS256 signature did not verify" });
|
|
4303
|
+
if (verified.header["alg"] !== "RS256" || verified.header["typ"] !== "JWT") {
|
|
4304
|
+
return errorResponse2(401, { error: "invalid_grant", error_description: `unexpected JOSE header ${JSON.stringify(verified.header)}` });
|
|
4305
|
+
}
|
|
4306
|
+
const { claims } = verified;
|
|
4307
|
+
if (claims["aud"] !== opts.tokenUri)
|
|
4308
|
+
return errorResponse2(401, { error: "invalid_grant", error_description: `wrong audience ${JSON.stringify(claims["aud"])}` });
|
|
4309
|
+
if (claims["scope"] !== REQUIRED_SCOPE)
|
|
4310
|
+
return errorResponse2(401, { error: "invalid_grant", error_description: `wrong scope ${JSON.stringify(claims["scope"])}` });
|
|
4311
|
+
if (typeof claims["iss"] !== "string" || !claims["iss"].includes("@"))
|
|
4312
|
+
return errorResponse2(401, { error: "invalid_grant", error_description: "iss is not a service-account email" });
|
|
4313
|
+
const iat = claims["iat"];
|
|
4314
|
+
const exp = claims["exp"];
|
|
4315
|
+
if (typeof iat !== "number" || typeof exp !== "number" || exp <= iat)
|
|
4316
|
+
return errorResponse2(401, { error: "invalid_grant", error_description: "iat/exp are missing or inverted" });
|
|
4317
|
+
if (exp - iat > 3600)
|
|
4318
|
+
return errorResponse2(401, { error: "invalid_grant", error_description: "the assertion lifetime exceeds one hour" });
|
|
4319
|
+
opts.verified.push(verified);
|
|
4320
|
+
return jsonResponse2({ access_token: opts.accessToken ?? VERTEX_TEST_ACCESS_TOKEN, token_type: "Bearer", expires_in: opts.expiresIn ?? 3600 });
|
|
4321
|
+
}
|
|
4322
|
+
function vertexTokenUrl(fakeUrl) {
|
|
4323
|
+
return `${fakeUrl}/token`;
|
|
4324
|
+
}
|
|
4325
|
+
function vertexFakeRoutes(opts) {
|
|
4326
|
+
const attempts = new Map;
|
|
4327
|
+
return [
|
|
4328
|
+
{ path: "/token", method: "POST", handler: async (_req, recorded) => await handleTokenExchange(recorded, opts) },
|
|
4329
|
+
{
|
|
4330
|
+
path: `/v1/projects/${opts.project}/locations/${opts.location}/publishers/google/models*`,
|
|
4331
|
+
method: "POST",
|
|
4332
|
+
handler: async (_req, recorded) => {
|
|
4333
|
+
const badEntry = findFunctionResponseOrderingViolation(recorded);
|
|
4334
|
+
if (badEntry !== undefined) {
|
|
4335
|
+
return geminiError(400, "INVALID_ARGUMENT", `contents[${badEntry}]: functionResponse parts must precede any other content in their turn`);
|
|
4336
|
+
}
|
|
4337
|
+
const badRole = findRoleAlternationViolation(recorded);
|
|
4338
|
+
if (badRole !== undefined) {
|
|
4339
|
+
return geminiError(400, "INVALID_ARGUMENT", `contents[${badRole}]: consecutive entries must not share a role — a conversation alternates`);
|
|
4340
|
+
}
|
|
4341
|
+
const model = geminiModelOf(recorded) ?? "";
|
|
4342
|
+
if (recorded.path.endsWith(":countTokens")) {
|
|
4343
|
+
return opts.countTokens?.(recorded) ?? jsonResponse2({ totalTokens: 42 });
|
|
4344
|
+
}
|
|
4345
|
+
const attempt = (attempts.get(model) ?? 0) + 1;
|
|
4346
|
+
attempts.set(model, attempt);
|
|
4347
|
+
const entry = opts.stream[model];
|
|
4348
|
+
if (entry === undefined)
|
|
4349
|
+
return geminiError(400, "INVALID_ARGUMENT", `fake: no scenario for model ${JSON.stringify(model)}`);
|
|
4350
|
+
if (Array.isArray(entry))
|
|
4351
|
+
return entry[Math.min(attempt - 1, entry.length - 1)];
|
|
4352
|
+
return await entry(recorded, attempt);
|
|
4353
|
+
}
|
|
4354
|
+
}
|
|
4355
|
+
];
|
|
4356
|
+
}
|
|
4357
|
+
function assertVertexRequest(recorded, expected) {
|
|
4358
|
+
const method = expected.method ?? "streamGenerateContent";
|
|
4359
|
+
const path = `/v1/projects/${expected.project}/locations/${expected.location}/publishers/google/models/${expected.model}:${method}`;
|
|
4360
|
+
const detail = `
|
|
4361
|
+
live request: ${recorded.method} ${recorded.path}${recorded.search}
|
|
4362
|
+
headers: ${JSON.stringify(recorded.headers)}`;
|
|
4363
|
+
if (recorded.path !== path)
|
|
4364
|
+
throw new Error(`expected the Vertex location path ${path}${detail}`);
|
|
4365
|
+
if (expected.search !== undefined && recorded.search !== expected.search)
|
|
4366
|
+
throw new Error(`expected search ${expected.search}${detail}`);
|
|
4367
|
+
if (recorded.headers["authorization"] !== "Bearer ***")
|
|
4368
|
+
throw new Error(`expected a redacted bearer token${detail}`);
|
|
4369
|
+
if (recorded.headers["x-goog-api-key"] !== undefined)
|
|
4370
|
+
throw new Error(`a Vertex request must not carry an api key${detail}`);
|
|
4371
|
+
}
|
|
4372
|
+
// src/fakes/xai-oauth.ts
|
|
4373
|
+
var exports_xai_oauth = {};
|
|
4374
|
+
__export(exports_xai_oauth, {
|
|
4375
|
+
startXaiOauthFake: () => startXaiOauthFake
|
|
4376
|
+
});
|
|
4377
|
+
export { __export, ProviderRequestError, WINTER_BRAND, createRegistry, AZURE_PREVIEW_API_VERSION, createAzureOpenAIAdapter, sameDomain, summaryRequestOf, shouldRequestSummary, createEndpointResolver, descriptor, testContext, testDiscoveryContext, FAST_RETRY, fixtureModel, fixtureReasoning, fixtureProvider, fixtureCatalog, scriptedAdapter, startFake2, withFake2, sseResponse2, jsonResponse2, errorResponse2, redirectResponse2, stalledResponse2, scenarioTable2, requestsTo2, noRequestContains2, exports_openai_chat, exports_openai_responses, deploymentOf, apiVersionOf, exports_azure_openai, exports_anthropic_console_oauth, OPAQUE_FIELD_NAMES2, redactOpaqueFields2, exports_anthropic_messages, exports_bedrock, exports_codex_oauth, exports_gemini, exports_openai_models, base64UrlDecodeText2, base64UrlDecodeBytes2, verifyRs256Jwt2, exports_vertex, exports_xai_oauth };
|