@full-self-browsing/lattice 1.4.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/dist/agent-run-C6miAzwI.d.ts +45 -0
  2. package/dist/agent-run-C6miAzwI.d.ts.map +1 -0
  3. package/dist/agent-run-CgPVFl0Z.js +47 -0
  4. package/dist/agent-run-CgPVFl0Z.js.map +1 -0
  5. package/dist/agents.d.ts +5 -0
  6. package/dist/agents.js +6 -0
  7. package/dist/artifact-Bg6mJGnm.d.ts +125 -0
  8. package/dist/artifact-Bg6mJGnm.d.ts.map +1 -0
  9. package/dist/artifact-DOfpeXLb.js +140 -0
  10. package/dist/artifact-DOfpeXLb.js.map +1 -0
  11. package/dist/artifacts.d.ts +2 -0
  12. package/dist/artifacts.js +2 -0
  13. package/dist/audit.d.ts +3 -0
  14. package/dist/audit.js +4 -0
  15. package/dist/catalog-CAfYwB_-.js +91 -0
  16. package/dist/catalog-CAfYwB_-.js.map +1 -0
  17. package/dist/context-pack-Bz3GXmjv.js +99 -0
  18. package/dist/context-pack-Bz3GXmjv.js.map +1 -0
  19. package/dist/context.d.ts +2 -0
  20. package/dist/context.js +2 -0
  21. package/dist/contract-S3oJGlc9.d.ts +74 -0
  22. package/dist/contract-S3oJGlc9.d.ts.map +1 -0
  23. package/dist/core.d.ts +48 -0
  24. package/dist/core.d.ts.map +1 -0
  25. package/dist/core.js +95 -0
  26. package/dist/core.js.map +1 -0
  27. package/dist/errors-eEuEIx6X.js +407 -0
  28. package/dist/errors-eEuEIx6X.js.map +1 -0
  29. package/dist/eval.d.ts +2 -0
  30. package/dist/eval.js +2 -0
  31. package/dist/fingerprint-DodDbQKN.js +34 -0
  32. package/dist/fingerprint-DodDbQKN.js.map +1 -0
  33. package/dist/index-DpnHGHVL.d.ts +53 -0
  34. package/dist/index-DpnHGHVL.d.ts.map +1 -0
  35. package/dist/index.d.ts +90 -3533
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +26 -15968
  38. package/dist/index.js.map +1 -1
  39. package/dist/infer-DLqp5QIM.d.ts +96 -0
  40. package/dist/infer-DLqp5QIM.d.ts.map +1 -0
  41. package/dist/lineage-DBgoPWAZ.js +137 -0
  42. package/dist/lineage-DBgoPWAZ.js.map +1 -0
  43. package/dist/local-CXOGPJ1f.js +139 -0
  44. package/dist/local-CXOGPJ1f.js.map +1 -0
  45. package/dist/local-Dy--7peL.d.ts +10 -0
  46. package/dist/local-Dy--7peL.d.ts.map +1 -0
  47. package/dist/memory-CkQEW6m5.js +62 -0
  48. package/dist/memory-CkQEW6m5.js.map +1 -0
  49. package/dist/memory-DRig5EHV.d.ts +10 -0
  50. package/dist/memory-DRig5EHV.d.ts.map +1 -0
  51. package/dist/negotiate-ClD88hkc.js +10967 -0
  52. package/dist/negotiate-ClD88hkc.js.map +1 -0
  53. package/dist/otel-BgM4e55_.d.ts +421 -0
  54. package/dist/otel-BgM4e55_.d.ts.map +1 -0
  55. package/dist/permission-context-CUKMo79F.js +134 -0
  56. package/dist/permission-context-CUKMo79F.js.map +1 -0
  57. package/dist/plan-DFm8Llep.js +125 -0
  58. package/dist/plan-DFm8Llep.js.map +1 -0
  59. package/dist/preflight-DNHWuJ46.d.ts +64 -0
  60. package/dist/preflight-DNHWuJ46.d.ts.map +1 -0
  61. package/dist/provider-C2IfKsvz.d.ts +1178 -0
  62. package/dist/provider-C2IfKsvz.d.ts.map +1 -0
  63. package/dist/providers.d.ts +4 -0
  64. package/dist/providers.js +4 -0
  65. package/dist/rate-limit-group-nDsBJqSu.d.ts +235 -0
  66. package/dist/rate-limit-group-nDsBJqSu.d.ts.map +1 -0
  67. package/dist/receipt-FYouoPHv.js +205 -0
  68. package/dist/receipt-FYouoPHv.js.map +1 -0
  69. package/dist/replay-CtIhpLek.js +964 -0
  70. package/dist/replay-CtIhpLek.js.map +1 -0
  71. package/dist/result-DLEx2WvU.d.ts +38 -0
  72. package/dist/result-DLEx2WvU.d.ts.map +1 -0
  73. package/dist/router-DU4Z3pTd.js +314 -0
  74. package/dist/router-DU4Z3pTd.js.map +1 -0
  75. package/dist/router-Yo1-aDOv.d.ts +42 -0
  76. package/dist/router-Yo1-aDOv.d.ts.map +1 -0
  77. package/dist/routing.d.ts +6 -0
  78. package/dist/routing.js +4 -0
  79. package/dist/{run-crew-CKdBjh5P.js → run-crew-B2fQLmgB.js} +7 -136
  80. package/dist/run-crew-B2fQLmgB.js.map +1 -0
  81. package/dist/run-crew-Bnve5dyI.d.ts +721 -0
  82. package/dist/run-crew-Bnve5dyI.d.ts.map +1 -0
  83. package/dist/{runtime-D25ehzCj.js → runtime-Dxiet5YS.js} +98 -641
  84. package/dist/runtime-Dxiet5YS.js.map +1 -0
  85. package/dist/scaffolds-DKQrCRqh.d.ts +535 -0
  86. package/dist/scaffolds-DKQrCRqh.d.ts.map +1 -0
  87. package/dist/scaffolds-ekPIlBeU.js +3139 -0
  88. package/dist/scaffolds-ekPIlBeU.js.map +1 -0
  89. package/dist/schema-CNfa_VEy.d.ts +15 -0
  90. package/dist/schema-CNfa_VEy.d.ts.map +1 -0
  91. package/dist/storage-DJKmsaEI.d.ts +26 -0
  92. package/dist/storage-DJKmsaEI.d.ts.map +1 -0
  93. package/dist/storage.d.ts +10 -0
  94. package/dist/storage.d.ts.map +1 -0
  95. package/dist/storage.js +4 -0
  96. package/dist/tool-call-validation-BFoXkwbf.js +107 -0
  97. package/dist/tool-call-validation-BFoXkwbf.js.map +1 -0
  98. package/dist/tools-C4wHgGKQ.js +49 -0
  99. package/dist/tools-C4wHgGKQ.js.map +1 -0
  100. package/dist/tools.d.ts +46 -0
  101. package/dist/tools.d.ts.map +1 -0
  102. package/dist/tools.js +106 -0
  103. package/dist/tools.js.map +1 -0
  104. package/dist/validate-c7EL5uuH.js +224 -0
  105. package/dist/validate-c7EL5uuH.js.map +1 -0
  106. package/package.json +99 -2
  107. package/dist/run-crew-CKdBjh5P.js.map +0 -1
  108. package/dist/runtime-D25ehzCj.js.map +0 -1
@@ -0,0 +1,3139 @@
1
+ import { r as defaultCapabilityForProvider } from "./catalog-CAfYwB_-.js";
2
+ import { c as getCapabilityProfile, i as synthesizeNegotiatedCapabilitiesFromRegistry, l as stripOpenRouterVariant, n as mapProfileToNegotiatedCapabilities, o as getRecommendedSanitizers, t as NegotiationAuthError } from "./negotiate-ClD88hkc.js";
3
+ import { i as standardSchemaToJsonSchema, o as parseToolUseEnvelope } from "./validate-c7EL5uuH.js";
4
+ import { n as validateToolCallRequests } from "./tool-call-validation-BFoXkwbf.js";
5
+ import canonicalize from "canonicalize";
6
+ //#region src/providers/multimodal.ts
7
+ function packagedPlanForArtifact(request, artifactId) {
8
+ return request.providerPackaging?.artifacts.find((item) => item.artifactId === artifactId) ?? request.plan?.providerPackaging?.artifacts.find((item) => item.artifactId === artifactId);
9
+ }
10
+ function metadataString(artifact, keys) {
11
+ const metadata = artifact.metadata;
12
+ if (metadata === void 0) return;
13
+ for (const key of keys) {
14
+ const value = metadata[key];
15
+ if (typeof value === "string" && value.length > 0) return value;
16
+ }
17
+ }
18
+ function artifactHttpUrl(artifact) {
19
+ if (isHttpUrl(artifact.value)) return artifact.value;
20
+ const url = metadataString(artifact, ["url"]);
21
+ return isHttpUrl(url) ? url : void 0;
22
+ }
23
+ function anthropicFileId(artifact) {
24
+ return metadataString(artifact, [
25
+ "anthropicFileId",
26
+ "providerFileId",
27
+ "fileId"
28
+ ]);
29
+ }
30
+ function geminiFileUri(artifact) {
31
+ return metadataString(artifact, [
32
+ "geminiFileUri",
33
+ "providerFileUri",
34
+ "fileUri"
35
+ ]);
36
+ }
37
+ function mediaTypeForArtifact(artifact, fallback) {
38
+ if (artifact.mediaType !== void 0) return artifact.mediaType;
39
+ if (typeof artifact.value === "string") {
40
+ const dataUrl = parseDataUrl(artifact.value);
41
+ if (dataUrl?.mediaType !== void 0) return dataUrl.mediaType;
42
+ }
43
+ return fallback;
44
+ }
45
+ async function artifactBase64Data(artifact) {
46
+ const metadataData = metadataString(artifact, ["base64Data"]);
47
+ if (metadataData !== void 0) return metadataData;
48
+ const value = artifact.value;
49
+ if (typeof value === "string") {
50
+ const dataUrl = parseDataUrl(value);
51
+ if (dataUrl !== void 0) return dataUrl.data;
52
+ if (artifact.metadata?.encoding === "base64") return value;
53
+ }
54
+ if (isBlobLike(value)) return bufferToBase64(await value.arrayBuffer());
55
+ if (value instanceof ArrayBuffer) return bufferToBase64(value);
56
+ if (ArrayBuffer.isView(value)) return Buffer.from(value.buffer, value.byteOffset, value.byteLength).toString("base64");
57
+ }
58
+ function parseDataUrl(value) {
59
+ const match = /^data:([^;,]+)?;base64,(.*)$/su.exec(value);
60
+ if (match === null) return;
61
+ const mediaType = match[1];
62
+ const data = match[2] ?? "";
63
+ return {
64
+ ...mediaType !== void 0 && mediaType.length > 0 ? { mediaType } : {},
65
+ data
66
+ };
67
+ }
68
+ function bufferToBase64(value) {
69
+ return Buffer.from(value).toString("base64");
70
+ }
71
+ function isHttpUrl(value) {
72
+ if (typeof value !== "string") return false;
73
+ try {
74
+ const url = new URL(value);
75
+ return url.protocol === "http:" || url.protocol === "https:";
76
+ } catch {
77
+ return false;
78
+ }
79
+ }
80
+ function isBlobLike(value) {
81
+ return typeof Blob !== "undefined" && value instanceof Blob;
82
+ }
83
+ //#endregion
84
+ //#region src/providers/no-public-url.ts
85
+ /**
86
+ * Thrown when a run with `policy.noPublicUrl: true` is about to dispatch a
87
+ * request whose serialized body still contains a public http(s) URL derived
88
+ * from `request.artifacts` (value or string metadata entry).
89
+ *
90
+ * This is the single shared egress error class for all three adapter families
91
+ * (OpenAI-compatible, Anthropic, Gemini). Callers may `instanceof`-check it.
92
+ */
93
+ var NoPublicUrlEgressError = class extends Error {
94
+ constructor(providerId, artifactId, offendingUrl) {
95
+ super(`noPublicUrl policy violated: provider '${providerId}' artifact '${artifactId}' would leak public URL '${offendingUrl}'`);
96
+ this.providerId = providerId;
97
+ this.artifactId = artifactId;
98
+ this.offendingUrl = offendingUrl;
99
+ this.name = "NoPublicUrlEgressError";
100
+ Object.setPrototypeOf(this, new.target.prototype);
101
+ }
102
+ };
103
+ /**
104
+ * Shared egress assertion called immediately before every run-request `fetch`.
105
+ *
106
+ * When `policy.noPublicUrl` is not `true` this function is a zero-cost no-op.
107
+ *
108
+ * When the policy IS active it builds a forbidden-URL set from ARTIFACT-DERIVED
109
+ * sources only:
110
+ * - `artifact.value` (if it is a string and `isHttpUrl(value)` is true)
111
+ * - Every `string` value inside `artifact.metadata` (if `isHttpUrl(v)` is true)
112
+ *
113
+ * URLs in `policy.gateway.metadata` are NOT artifact-derived and are therefore
114
+ * NOT in scope. They are naturally excluded because they never appear in
115
+ * `request.artifacts`.
116
+ *
117
+ * A URL is considered "leaked" only if the string is present in `serializedBody`.
118
+ * If packaging already replaced the URL with a `data:` URL, `serializedBody`
119
+ * will not contain the original http(s) URL and this function will not throw.
120
+ * `data:` URLs are never forbidden because `isHttpUrl` rejects the `data:` scheme.
121
+ *
122
+ * @throws {NoPublicUrlEgressError} if any forbidden URL appears in `serializedBody`
123
+ */
124
+ function assertNoPublicUrlEgress(request, providerId, serializedBody) {
125
+ if (request.policy?.noPublicUrl !== true) return;
126
+ const forbidden = [];
127
+ for (const artifact of request.artifacts) {
128
+ const artifactId = artifact.id ?? "";
129
+ if (typeof artifact.value === "string" && isHttpUrl(artifact.value)) forbidden.push({
130
+ url: artifact.value,
131
+ id: artifactId
132
+ });
133
+ const metadata = artifact.metadata ?? {};
134
+ for (const v of Object.values(metadata)) if (typeof v === "string" && isHttpUrl(v)) forbidden.push({
135
+ url: v,
136
+ id: artifactId
137
+ });
138
+ }
139
+ for (const entry of forbidden) if (serializedBody.includes(entry.url)) throw new NoPublicUrlEgressError(providerId, entry.id, entry.url);
140
+ }
141
+ //#endregion
142
+ //#region src/tracing/tracing.ts
143
+ function createRunEvent(kind, input) {
144
+ return {
145
+ kind,
146
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
147
+ ...input
148
+ };
149
+ }
150
+ //#endregion
151
+ //#region src/sanitizers/sanitizers.ts
152
+ async function applyOutputSanitizers(rawOutputs, sanitizeOutput, context) {
153
+ if (sanitizeOutput === void 0) return rawOutputs;
154
+ const sanitizers = Array.isArray(sanitizeOutput) ? sanitizeOutput : [sanitizeOutput];
155
+ const sanitizedEntries = await Promise.all(Object.entries(rawOutputs).map(async ([outputName, value]) => {
156
+ if (typeof value !== "string") return [outputName, value];
157
+ let sanitized = value;
158
+ const sanitizerContext = {
159
+ ...context,
160
+ outputName
161
+ };
162
+ for (const sanitizer of sanitizers) sanitized = await sanitizer(sanitized, sanitizerContext);
163
+ return [outputName, sanitized];
164
+ }));
165
+ return Object.fromEntries(sanitizedEntries);
166
+ }
167
+ function stripReasoningTags() {
168
+ return (text) => {
169
+ let next = text;
170
+ next = next.replace(/^\s*(?:reasoning|analysis|scratchpad)\s*:\s*(?:.|\n)*?(?:\n\s*(?:final|answer)\s*:\s*)/iu, "");
171
+ next = stripDelimitedBlock(next, "think");
172
+ next = stripDelimitedBlock(next, "reasoning");
173
+ next = stripDelimitedBlock(next, "scratchpad");
174
+ return next === text ? text : next.trim();
175
+ };
176
+ }
177
+ function stripChatTemplateArtifacts() {
178
+ return (text) => {
179
+ let next = text;
180
+ next = next.replace(/<\|im_start\|>\s*(?:system|user|assistant)?\s*/giu, "");
181
+ next = next.replace(/\s*<\|im_end\|>/giu, "");
182
+ next = next.replace(/\[\/?INST\]/giu, "");
183
+ next = next.replace(/<<SYS>>|<<\/SYS>>/giu, "");
184
+ next = next.replace(/^\s*(?:system|user|assistant)\s*:\s*/iu, "");
185
+ return next === text ? text : next.trim();
186
+ };
187
+ }
188
+ function unwrapInternalEnvelope(schemaOrPath) {
189
+ const options = parseEnvelopeOptions(schemaOrPath);
190
+ return async (text) => {
191
+ const parsed = parseJsonObject$3(text);
192
+ if (parsed === void 0) return text;
193
+ if (options.schema !== void 0) {
194
+ if (!(await validateSchema(options.schema, parsed)).ok) return text;
195
+ }
196
+ const value = options.kind === "path" ? getPathValue(parsed, options.path) : findOnlyStringField(parsed);
197
+ return typeof value === "string" ? value : text;
198
+ };
199
+ }
200
+ function stripDelimitedBlock(text, tag) {
201
+ const pattern = new RegExp(`<${tag}\\b[^>]*>[\\s\\S]*?<\\/${tag}>`, "giu");
202
+ return text.replace(pattern, "");
203
+ }
204
+ function parseEnvelopeOptions(schemaOrPath) {
205
+ if (typeof schemaOrPath === "string") return {
206
+ kind: "path",
207
+ path: splitPath(schemaOrPath)
208
+ };
209
+ if (isStandardSchema(schemaOrPath)) return {
210
+ kind: "schema",
211
+ schema: schemaOrPath
212
+ };
213
+ const path = schemaOrPath.path ?? schemaOrPath.field;
214
+ if (path !== void 0) return {
215
+ kind: "path",
216
+ path: splitPath(path),
217
+ ...schemaOrPath.schema !== void 0 ? { schema: schemaOrPath.schema } : {}
218
+ };
219
+ if (schemaOrPath.schema !== void 0) return {
220
+ kind: "schema",
221
+ schema: schemaOrPath.schema
222
+ };
223
+ return {
224
+ kind: "path",
225
+ path: []
226
+ };
227
+ }
228
+ function splitPath(path) {
229
+ return path.split(".").map((part) => part.trim()).filter(Boolean);
230
+ }
231
+ function parseJsonObject$3(text) {
232
+ let parsed;
233
+ try {
234
+ parsed = JSON.parse(text);
235
+ } catch {
236
+ return;
237
+ }
238
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return;
239
+ return parsed;
240
+ }
241
+ function getPathValue(value, path) {
242
+ if (path.length === 0) return void 0;
243
+ let current = value;
244
+ for (const segment of path) {
245
+ if (typeof current !== "object" || current === null || Array.isArray(current)) return;
246
+ if (!Object.prototype.hasOwnProperty.call(current, segment)) return;
247
+ current = current[segment];
248
+ }
249
+ return current;
250
+ }
251
+ function findOnlyStringField(value) {
252
+ const stringValues = Object.values(value).filter((field) => typeof field === "string");
253
+ return stringValues.length === 1 ? stringValues[0] : void 0;
254
+ }
255
+ async function validateSchema(schema, value) {
256
+ const result = schema["~standard"].validate(value);
257
+ return "issues" in (result instanceof Promise ? await result : result) ? { ok: false } : { ok: true };
258
+ }
259
+ function isStandardSchema(value) {
260
+ return typeof value === "object" && value !== null && "~standard" in value && typeof value["~standard"]?.validate === "function";
261
+ }
262
+ //#endregion
263
+ //#region src/providers/sse.ts
264
+ async function* readSseEvents(response) {
265
+ if (response.body === null) {
266
+ yield* parseFrames(await response.text(), true).events;
267
+ return;
268
+ }
269
+ const reader = response.body.getReader();
270
+ const decoder = new TextDecoder();
271
+ let buffer = "";
272
+ try {
273
+ while (true) {
274
+ const { done, value } = await reader.read();
275
+ if (done) break;
276
+ buffer += decoder.decode(value, { stream: true });
277
+ const parsed = parseFrames(buffer, false);
278
+ buffer = parsed.remaining;
279
+ yield* parsed.events;
280
+ }
281
+ } finally {
282
+ reader.releaseLock();
283
+ }
284
+ buffer += decoder.decode();
285
+ yield* parseFrames(buffer, true).events;
286
+ }
287
+ function parseFrames(input, flush) {
288
+ const events = [];
289
+ let remaining = input;
290
+ while (true) {
291
+ const match = /\r?\n\r?\n/u.exec(remaining);
292
+ if (match?.index === void 0) break;
293
+ const frame = remaining.slice(0, match.index);
294
+ remaining = remaining.slice(match.index + match[0].length);
295
+ const event = parseFrame(frame);
296
+ if (event !== void 0) events.push(event);
297
+ }
298
+ if (flush && remaining.trim().length > 0) {
299
+ const event = parseFrame(remaining);
300
+ if (event !== void 0) events.push(event);
301
+ remaining = "";
302
+ }
303
+ return {
304
+ events,
305
+ remaining
306
+ };
307
+ }
308
+ function parseFrame(frame) {
309
+ const data = [];
310
+ let event;
311
+ for (const line of frame.split(/\r?\n/u)) {
312
+ if (line.length === 0 || line.startsWith(":")) continue;
313
+ const separator = line.indexOf(":");
314
+ const field = separator === -1 ? line : line.slice(0, separator);
315
+ const rawValue = separator === -1 ? "" : line.slice(separator + 1);
316
+ const value = rawValue.startsWith(" ") ? rawValue.slice(1) : rawValue;
317
+ if (field === "event") event = value;
318
+ else if (field === "data") data.push(value);
319
+ }
320
+ if (data.length === 0) return;
321
+ return {
322
+ ...event !== void 0 ? { event } : {},
323
+ data: data.join("\n")
324
+ };
325
+ }
326
+ //#endregion
327
+ //#region src/providers/adapters.ts
328
+ function isRecord$3(value) {
329
+ return typeof value === "object" && value !== null && !Array.isArray(value);
330
+ }
331
+ function isGatewayMetadataValue(value) {
332
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean" || value === null) return true;
333
+ if (Array.isArray(value)) return value.every(isGatewayMetadataValue);
334
+ if (isRecord$3(value)) return Object.values(value).every(isGatewayMetadataValue);
335
+ return false;
336
+ }
337
+ function isSecretMetadataKey(key) {
338
+ return /api[-_]?key|authorization|headers?|secret|token|password/iu.test(key);
339
+ }
340
+ function isSecretMetadataValue(value) {
341
+ if (typeof value === "string") return /^sk-[\w-]+/u.test(value);
342
+ if (Array.isArray(value)) return value.some(isSecretMetadataValue);
343
+ if (typeof value === "object" && value !== null) return Object.entries(value).some(([key, nested]) => isSecretMetadataKey(key) || isSecretMetadataValue(nested));
344
+ return false;
345
+ }
346
+ function sanitizeGatewayMetadata(metadata) {
347
+ if (metadata === void 0) return;
348
+ const sanitized = Object.fromEntries(Object.entries(metadata).filter(([key, value]) => !isSecretMetadataKey(key) && !isSecretMetadataValue(value)));
349
+ return Object.keys(sanitized).length > 0 ? sanitized : void 0;
350
+ }
351
+ function normalizeGatewayPolicy(value) {
352
+ if (!isRecord$3(value)) return {};
353
+ const routeTags = Array.isArray(value.routeTags) ? value.routeTags.filter((tag) => typeof tag === "string") : void 0;
354
+ const providerPreferences = Array.isArray(value.providerPreferences) ? value.providerPreferences.filter((provider) => typeof provider === "string") : void 0;
355
+ const gatewayMetadata = {};
356
+ if (isRecord$3(value.metadata)) {
357
+ for (const [key, metadataValue] of Object.entries(value.metadata)) if (isGatewayMetadataValue(metadataValue)) gatewayMetadata[key] = metadataValue;
358
+ }
359
+ const metadata = Object.keys(gatewayMetadata).length > 0 ? sanitizeGatewayMetadata(gatewayMetadata) : void 0;
360
+ const allowFallbacks = typeof value.allowFallbacks === "boolean" ? value.allowFallbacks : void 0;
361
+ return {
362
+ ...routeTags !== void 0 ? { routeTags } : {},
363
+ ...providerPreferences !== void 0 ? { providerPreferences } : {},
364
+ ...metadata !== void 0 ? { metadata } : {},
365
+ ...allowFallbacks !== void 0 ? { allowFallbacks } : {}
366
+ };
367
+ }
368
+ function readGatewayPolicy(policy) {
369
+ if (!isRecord$3(policy) || !isRecord$3(policy.gateway)) return;
370
+ return normalizeGatewayPolicy(policy.gateway);
371
+ }
372
+ function mergeGatewayPolicy(providerGateway, requestGateway) {
373
+ if (providerGateway === void 0 && requestGateway === void 0) return;
374
+ const providerMetadata = sanitizeGatewayMetadata(providerGateway?.metadata);
375
+ const requestMetadata = sanitizeGatewayMetadata(requestGateway?.metadata);
376
+ const metadata = {
377
+ ...providerMetadata ?? {},
378
+ ...requestMetadata ?? {}
379
+ };
380
+ return {
381
+ routeTags: [...providerGateway?.routeTags ?? [], ...requestGateway?.routeTags ?? []],
382
+ providerPreferences: [...providerGateway?.providerPreferences ?? [], ...requestGateway?.providerPreferences ?? []],
383
+ ...Object.keys(metadata).length > 0 ? { metadata } : {},
384
+ ...requestGateway?.allowFallbacks !== void 0 ? { allowFallbacks: requestGateway.allowFallbacks } : providerGateway?.allowFallbacks !== void 0 ? { allowFallbacks: providerGateway.allowFallbacks } : {}
385
+ };
386
+ }
387
+ function gatewayPolicyToMetadata(policy) {
388
+ if (policy === void 0) return;
389
+ const metadata = { ...sanitizeGatewayMetadata(policy.metadata) ?? {} };
390
+ const latticeGateway = {};
391
+ if (policy.routeTags !== void 0 && policy.routeTags.length > 0) latticeGateway.route_tags = [...policy.routeTags];
392
+ if (policy.providerPreferences !== void 0 && policy.providerPreferences.length > 0) latticeGateway.provider_preferences = [...policy.providerPreferences];
393
+ if (policy.allowFallbacks !== void 0) latticeGateway.allow_fallbacks = policy.allowFallbacks;
394
+ if (Object.keys(latticeGateway).length > 0) metadata.lattice_gateway = latticeGateway;
395
+ return Object.keys(metadata).length > 0 ? metadata : void 0;
396
+ }
397
+ function sanitizedGatewayPolicyForPlan(policy) {
398
+ if (policy === void 0) return;
399
+ const metadata = sanitizeGatewayMetadata(policy.metadata);
400
+ return {
401
+ ...policy.routeTags !== void 0 && policy.routeTags.length > 0 ? { routeTags: [...policy.routeTags] } : {},
402
+ ...policy.providerPreferences !== void 0 && policy.providerPreferences.length > 0 ? { providerPreferences: [...policy.providerPreferences] } : {},
403
+ ...metadata !== void 0 ? { metadata } : {},
404
+ ...policy.allowFallbacks !== void 0 ? { allowFallbacks: policy.allowFallbacks } : {}
405
+ };
406
+ }
407
+ function observedModelFromResponse(body) {
408
+ if (!isRecord$3(body)) return;
409
+ const model = body.model;
410
+ return typeof model === "string" ? model : void 0;
411
+ }
412
+ function createOpenAICompatibleRequestBody(input) {
413
+ const nativeTools = openAIToolDefinitions(input.request.nativeTools);
414
+ const nativeToolChoice = openAIToolChoice(input.request.nativeToolChoice);
415
+ const responseFormat = openAIResponseFormat(input.request.nativeStructuredOutput);
416
+ return {
417
+ model: input.model,
418
+ ...input.metadata !== void 0 ? { metadata: input.metadata } : {},
419
+ messages: [{
420
+ role: "user",
421
+ content: [
422
+ {
423
+ type: "text",
424
+ text: input.request.task
425
+ },
426
+ {
427
+ type: "text",
428
+ text: JSON.stringify({ contextPack: input.request.contextPack === void 0 ? void 0 : {
429
+ id: input.request.contextPack.id,
430
+ tokenBudget: input.request.contextPack.tokenBudget,
431
+ estimatedTokens: input.request.contextPack.estimatedTokens,
432
+ included: input.request.contextPack.included,
433
+ summarized: input.request.contextPack.summarized,
434
+ archived: input.request.contextPack.archived,
435
+ omitted: input.request.contextPack.omitted,
436
+ warnings: input.request.contextPack.warnings
437
+ } })
438
+ },
439
+ ...input.request.artifacts.map((inputArtifact) => {
440
+ const resolvedTransport = input.request.providerPackaging?.artifacts.find((item) => item.artifactId === inputArtifact.id)?.transport ?? input.request.plan?.providerPackaging?.artifacts.find((item) => item.artifactId === inputArtifact.id)?.transport;
441
+ return {
442
+ type: "text",
443
+ text: JSON.stringify({
444
+ artifactId: inputArtifact.id,
445
+ kind: inputArtifact.kind,
446
+ mediaType: inputArtifact.mediaType,
447
+ privacy: inputArtifact.privacy,
448
+ transport: resolvedTransport,
449
+ value: typeof inputArtifact.value === "string" && inputArtifact.kind !== "url" && !(isHttpUrl(inputArtifact.value) && resolvedTransport !== "url") ? inputArtifact.value : void 0,
450
+ url: inputArtifact.kind === "url" && typeof inputArtifact.value === "string" && resolvedTransport === "url" ? inputArtifact.value : void 0
451
+ })
452
+ };
453
+ })
454
+ ]
455
+ }],
456
+ ...input.stream === true ? {
457
+ stream: true,
458
+ stream_options: { include_usage: true }
459
+ } : {},
460
+ ...nativeTools.length > 0 ? { tools: nativeTools } : {},
461
+ ...nativeToolChoice !== void 0 ? { tool_choice: nativeToolChoice } : {},
462
+ ...responseFormat !== void 0 ? { response_format: responseFormat } : {}
463
+ };
464
+ }
465
+ function openAIToolDefinitions(tools) {
466
+ return (tools ?? []).map((tool) => ({
467
+ type: "function",
468
+ function: {
469
+ name: tool.name,
470
+ ...tool.description !== void 0 ? { description: tool.description } : {},
471
+ parameters: standardSchemaToJsonSchema(tool.inputSchema)
472
+ }
473
+ }));
474
+ }
475
+ function openAIToolChoice(choice) {
476
+ if (choice === void 0) return;
477
+ if (choice === "auto" || choice === "none" || choice === "required") return choice;
478
+ return {
479
+ type: "function",
480
+ function: { name: choice.name }
481
+ };
482
+ }
483
+ function openAIResponseFormat(request) {
484
+ if (request === void 0) return;
485
+ return {
486
+ type: "json_schema",
487
+ json_schema: {
488
+ name: request.name ?? request.output,
489
+ schema: standardSchemaToJsonSchema(request.schema),
490
+ strict: request.strict ?? true
491
+ }
492
+ };
493
+ }
494
+ /**
495
+ * Phase 34 — D-04 / QUIRK-02 — OpenAI-compatible provider factory.
496
+ *
497
+ * This factory is the prototypical "intentional no remote /models endpoint"
498
+ * adapter per D-04. The consumer points this adapter at any OpenAI-shaped
499
+ * endpoint (vLLM, TGI, Ollama, custom), and the factory returns conservative
500
+ * defaults for the quirks block because the server could be anything.
501
+ *
502
+ * The `negotiateCapabilities` method performs NO fetch; it returns
503
+ * synthesizeNegotiatedCapabilitiesFromRegistry with source: "registry"
504
+ * (the intentional-no-endpoint signal, as distinct from "registry-fallback"
505
+ * which signals a transient failure). Plan 34-05 (LM Studio) reuses this
506
+ * same pattern.
507
+ *
508
+ * D-04 citation: "consumer adapters without a /models endpoint skip the
509
+ * fetch layer entirely and delegate to synthesizeNegotiatedCapabilitiesFromRegistry."
510
+ */
511
+ function createOpenAICompatibleProvider(options) {
512
+ const id = options.id ?? "openai-compatible";
513
+ const fetchImpl = options.fetch ?? fetch;
514
+ const baseUrl = options.baseUrl.replace(/\/$/u, "");
515
+ const negotiate = async (modelId) => {
516
+ return synthesizeNegotiatedCapabilitiesFromRegistry(id, modelId, "registry");
517
+ };
518
+ return {
519
+ id,
520
+ kind: "provider-adapter",
521
+ quirks: {
522
+ supportsToolChoice: false,
523
+ parallelToolCalls: false,
524
+ structuredOutputs: false,
525
+ responseFormatHonored: false,
526
+ streamingDiverges: true
527
+ },
528
+ negotiateCapabilities: negotiate,
529
+ capabilities: [{
530
+ ...defaultCapabilityForProvider(id),
531
+ modelId: options.model,
532
+ fileTransport: [
533
+ "inline",
534
+ "json",
535
+ "url",
536
+ "base64",
537
+ "extracted-text",
538
+ "transcript"
539
+ ],
540
+ streaming: true
541
+ }],
542
+ async execute(request) {
543
+ const mergedGatewayPolicy = mergeGatewayPolicy(options.gateway, readGatewayPolicy(request.policy));
544
+ const metadata = gatewayPolicyToMetadata(mergedGatewayPolicy);
545
+ const bodyStr = JSON.stringify(createOpenAICompatibleRequestBody({
546
+ model: options.model,
547
+ request,
548
+ ...metadata !== void 0 ? { metadata } : {}
549
+ }));
550
+ assertNoPublicUrlEgress(request, id, bodyStr);
551
+ const init = {
552
+ method: "POST",
553
+ headers: {
554
+ "content-type": "application/json",
555
+ ...options.apiKey !== void 0 ? { authorization: `Bearer ${options.apiKey}` } : {}
556
+ },
557
+ body: bodyStr,
558
+ ...request.signal !== void 0 ? { signal: request.signal } : {}
559
+ };
560
+ const response = await fetchImpl(`${baseUrl}/chat/completions`, init);
561
+ if (!response.ok) throw new Error(`OpenAI-compatible provider failed with ${response.status}.`);
562
+ const body = await response.json();
563
+ const observedModel = observedModelFromResponse(body);
564
+ const choice = firstOpenAIChoice(body);
565
+ const message = openAIMessageFromChoice(choice);
566
+ const text = openAIMessageText(message);
567
+ const structuredOutput = openAIStructuredOutputValue(request.nativeStructuredOutput, message, text);
568
+ const sanitizedOutputs = await applyOutputSanitizers(rawOutputsForRequest$2({
569
+ outputs: request.outputs,
570
+ text,
571
+ structuredOutputRequest: request.nativeStructuredOutput,
572
+ structuredOutput
573
+ }), options.sanitizeOutput, {
574
+ providerId: id,
575
+ modelId: options.model
576
+ });
577
+ const parsedToolCalls = parseToolUseEnvelope(text);
578
+ const promptToolCalls = parsedToolCalls === null ? void 0 : await validateToolCallRequests(parsedToolCalls, options.validateToolCalls);
579
+ const nativeToolRequests = openAIToolUseRequestsFromMessage(message);
580
+ const nativeToolCalls = nativeToolRequests.length === 0 ? void 0 : await validateToolCallRequests(nativeToolRequests, options.validateToolCalls);
581
+ const hasToolCallResult = promptToolCalls !== void 0 || nativeToolCalls !== void 0;
582
+ const toolCalls = [...promptToolCalls ?? [], ...nativeToolCalls ?? []];
583
+ const usage = normalizeUsage(body.usage);
584
+ const normalizedUsage = normalizeUsageToRunUsage(body.usage, options.pricing);
585
+ const sanitizedGatewayPolicy = sanitizedGatewayPolicyForPlan(mergedGatewayPolicy);
586
+ const gateway = id === "litellm" || mergedGatewayPolicy !== void 0 ? {
587
+ used: true,
588
+ requestedModel: options.model,
589
+ ...observedModel !== void 0 ? { observedModel } : {},
590
+ ...sanitizedGatewayPolicy !== void 0 ? { policy: sanitizedGatewayPolicy } : {}
591
+ } : void 0;
592
+ const finish = openAIFinishMetadata(choice, toolCalls);
593
+ return {
594
+ rawOutputs: sanitizedOutputs,
595
+ ...usage !== void 0 ? { usage } : {},
596
+ normalizedUsage,
597
+ ...hasToolCallResult ? { toolCalls } : {},
598
+ ...gateway !== void 0 ? { gateway } : {},
599
+ ...finish !== void 0 ? { finish } : {},
600
+ rawResponse: body
601
+ };
602
+ },
603
+ executeStream(request) {
604
+ return streamOpenAICompatibleResponse({
605
+ id,
606
+ model: options.model,
607
+ baseUrl,
608
+ fetchImpl,
609
+ request,
610
+ ...options.apiKey !== void 0 ? { apiKey: options.apiKey } : {},
611
+ ...options.gateway !== void 0 ? { providerGateway: options.gateway } : {},
612
+ ...options.pricing !== void 0 ? { pricing: options.pricing } : {},
613
+ ...options.sanitizeOutput !== void 0 ? { sanitizeOutput: options.sanitizeOutput } : {},
614
+ ...options.validateToolCalls !== void 0 ? { validateToolCalls: options.validateToolCalls } : {}
615
+ });
616
+ }
617
+ };
618
+ }
619
+ async function* streamOpenAICompatibleResponse(input) {
620
+ const mergedGatewayPolicy = mergeGatewayPolicy(input.providerGateway, readGatewayPolicy(input.request.policy));
621
+ const metadata = gatewayPolicyToMetadata(mergedGatewayPolicy);
622
+ const streamBodyStr = JSON.stringify(createOpenAICompatibleRequestBody({
623
+ model: input.model,
624
+ request: input.request,
625
+ ...metadata !== void 0 ? { metadata } : {},
626
+ stream: true
627
+ }));
628
+ assertNoPublicUrlEgress(input.request, input.id, streamBodyStr);
629
+ const response = await input.fetchImpl(`${input.baseUrl}/chat/completions`, {
630
+ method: "POST",
631
+ headers: {
632
+ "content-type": "application/json",
633
+ ...input.apiKey !== void 0 ? { authorization: `Bearer ${input.apiKey}` } : {}
634
+ },
635
+ body: streamBodyStr,
636
+ ...input.request.signal !== void 0 ? { signal: input.request.signal } : {}
637
+ });
638
+ if (!response.ok) throw new Error(`OpenAI-compatible provider failed with ${response.status}.`);
639
+ const textParts = [];
640
+ const rawChunks = [];
641
+ const nativeToolCalls = /* @__PURE__ */ new Map();
642
+ let usagePayload;
643
+ let observedModel;
644
+ let finishReason;
645
+ for await (const event of readSseEvents(response)) {
646
+ const data = event.data.trim();
647
+ if (data.length === 0) continue;
648
+ if (data === "[DONE]") break;
649
+ const chunk = parseJsonObject$2(data);
650
+ rawChunks.push(chunk);
651
+ const chunkObservedModel = observedModelFromResponse(chunk);
652
+ if (chunkObservedModel !== void 0) observedModel = chunkObservedModel;
653
+ if (isRecord$3(chunk) && chunk.usage !== void 0) usagePayload = chunk.usage;
654
+ for (const choice of streamChoices(chunk)) {
655
+ const choiceFinishReason = stringField(choice, "finish_reason");
656
+ if (choiceFinishReason !== void 0) finishReason = choiceFinishReason;
657
+ const delta = isRecord$3(choice.delta) ? choice.delta : {};
658
+ const content = typeof delta.content === "string" ? delta.content : void 0;
659
+ if (content !== void 0 && content.length > 0) {
660
+ textParts.push(content);
661
+ for (const output of input.request.outputs) yield {
662
+ kind: "text-delta",
663
+ output,
664
+ text: content
665
+ };
666
+ }
667
+ accumulateOpenAIToolCalls(nativeToolCalls, delta.tool_calls);
668
+ }
669
+ }
670
+ const text = textParts.join("");
671
+ const structuredOutput = input.request.nativeStructuredOutput === void 0 ? void 0 : parseJsonValue$1(text);
672
+ const sanitizedOutputs = await applyOutputSanitizers(rawOutputsForRequest$2({
673
+ outputs: input.request.outputs,
674
+ text,
675
+ structuredOutputRequest: input.request.nativeStructuredOutput,
676
+ structuredOutput
677
+ }), input.sanitizeOutput, {
678
+ providerId: input.id,
679
+ modelId: input.model
680
+ });
681
+ const parsedToolCalls = parseToolUseEnvelope(text);
682
+ const promptToolCalls = parsedToolCalls === null ? void 0 : await validateToolCallRequests(parsedToolCalls, input.validateToolCalls);
683
+ const nativeToolRequests = openAIToolUseRequests(nativeToolCalls);
684
+ const nativeValidatedToolCalls = nativeToolRequests.length === 0 ? void 0 : await validateToolCallRequests(nativeToolRequests, input.validateToolCalls);
685
+ const toolCalls = [...promptToolCalls ?? [], ...nativeValidatedToolCalls ?? []];
686
+ const usage = normalizeUsage(usagePayload);
687
+ const normalizedUsage = normalizeUsageToRunUsage(usagePayload, input.pricing);
688
+ const sanitizedGatewayPolicy = sanitizedGatewayPolicyForPlan(mergedGatewayPolicy);
689
+ const gateway = input.id === "litellm" || input.id === "openrouter" || mergedGatewayPolicy !== void 0 ? {
690
+ used: true,
691
+ requestedModel: input.model,
692
+ ...observedModel !== void 0 ? { observedModel } : {},
693
+ ...sanitizedGatewayPolicy !== void 0 ? { policy: sanitizedGatewayPolicy } : {}
694
+ } : void 0;
695
+ const finish = finishMetadata$2({
696
+ reason: finishReason,
697
+ toolCallIds: toolCalls.map((toolCall) => toolCall.id)
698
+ });
699
+ yield {
700
+ kind: "complete",
701
+ rawOutputs: sanitizedOutputs,
702
+ ...usage !== void 0 ? { usage } : {},
703
+ normalizedUsage,
704
+ ...toolCalls.length > 0 ? { toolCalls } : {},
705
+ ...gateway !== void 0 ? { gateway } : {},
706
+ ...finish !== void 0 ? { finish } : {},
707
+ rawResponse: {
708
+ kind: "openai-compatible-stream",
709
+ chunks: rawChunks
710
+ }
711
+ };
712
+ }
713
+ function firstOpenAIChoice(body) {
714
+ if (!isRecord$3(body) || !Array.isArray(body.choices)) return;
715
+ return body.choices.find(isRecord$3);
716
+ }
717
+ function openAIMessageFromChoice(choice) {
718
+ return choice !== void 0 && isRecord$3(choice.message) ? choice.message : void 0;
719
+ }
720
+ function openAIMessageText(message) {
721
+ if (message === void 0) return "";
722
+ if (typeof message.content === "string") return message.content;
723
+ if (Array.isArray(message.content)) return message.content.flatMap((part) => isRecord$3(part) && typeof part.text === "string" ? [part.text] : []).join("");
724
+ return "";
725
+ }
726
+ function openAIToolUseRequestsFromMessage(message) {
727
+ if (message === void 0 || !Array.isArray(message.tool_calls)) return [];
728
+ const calls = /* @__PURE__ */ new Map();
729
+ for (const [index, item] of message.tool_calls.entries()) {
730
+ if (!isRecord$3(item)) continue;
731
+ const current = { arguments: "" };
732
+ if (typeof item.id === "string") current.id = item.id;
733
+ if (isRecord$3(item.function)) {
734
+ if (typeof item.function.name === "string") current.name = item.function.name;
735
+ if (typeof item.function.arguments === "string") current.arguments = item.function.arguments;
736
+ }
737
+ calls.set(index, current);
738
+ }
739
+ return openAIToolUseRequests(calls);
740
+ }
741
+ function openAIStructuredOutputValue(request, message, text) {
742
+ if (request === void 0) return;
743
+ if (message !== void 0 && "parsed" in message) return message.parsed;
744
+ return parseJsonValue$1(text);
745
+ }
746
+ function rawOutputsForRequest$2(input) {
747
+ const rawOutputs = Object.fromEntries(input.outputs.map((name) => [name, input.text]));
748
+ if (input.structuredOutputRequest !== void 0 && input.structuredOutput !== void 0) rawOutputs[input.structuredOutputRequest.output] = input.structuredOutput;
749
+ return rawOutputs;
750
+ }
751
+ function parseJsonValue$1(text) {
752
+ const trimmed = text.trim();
753
+ if (trimmed.length === 0) return;
754
+ try {
755
+ return JSON.parse(trimmed);
756
+ } catch {
757
+ return;
758
+ }
759
+ }
760
+ function openAIFinishMetadata(choice, toolCalls) {
761
+ return finishMetadata$2({
762
+ reason: stringField(choice, "finish_reason"),
763
+ toolCallIds: toolCalls.map((toolCall) => toolCall.id)
764
+ });
765
+ }
766
+ function finishMetadata$2(input) {
767
+ const toolCallIds = input.toolCallIds.filter((id) => id.length > 0);
768
+ if (input.reason === void 0 && toolCallIds.length === 0) return;
769
+ return {
770
+ ...input.reason !== void 0 ? { reason: input.reason } : {},
771
+ ...toolCallIds.length > 0 ? { toolCallIds } : {}
772
+ };
773
+ }
774
+ function stringField(record, key) {
775
+ const value = record?.[key];
776
+ return typeof value === "string" ? value : void 0;
777
+ }
778
+ function parseJsonObject$2(data) {
779
+ try {
780
+ return JSON.parse(data);
781
+ } catch (error) {
782
+ const message = error instanceof Error ? error.message : "Invalid JSON.";
783
+ throw new Error(`OpenAI-compatible stream returned invalid JSON: ${message}`);
784
+ }
785
+ }
786
+ function streamChoices(chunk) {
787
+ if (!isRecord$3(chunk) || !Array.isArray(chunk.choices)) return [];
788
+ return chunk.choices.filter(isRecord$3);
789
+ }
790
+ function accumulateOpenAIToolCalls(calls, deltas) {
791
+ if (!Array.isArray(deltas)) return;
792
+ for (const delta of deltas) {
793
+ if (!isRecord$3(delta) || typeof delta.index !== "number") continue;
794
+ const current = calls.get(delta.index) ?? { arguments: "" };
795
+ if (typeof delta.id === "string") current.id = delta.id;
796
+ if (isRecord$3(delta.function)) {
797
+ if (typeof delta.function.name === "string") current.name = `${current.name ?? ""}${delta.function.name}`;
798
+ if (typeof delta.function.arguments === "string") current.arguments += delta.function.arguments;
799
+ }
800
+ calls.set(delta.index, current);
801
+ }
802
+ }
803
+ function openAIToolUseRequests(calls) {
804
+ return [...calls.entries()].sort(([left], [right]) => left - right).flatMap(([index, call]) => {
805
+ if (call.name === void 0) return [];
806
+ return [{
807
+ id: call.id ?? `tool-call-${index}`,
808
+ name: call.name,
809
+ args: parseToolArguments(call.arguments)
810
+ }];
811
+ });
812
+ }
813
+ function parseToolArguments(value) {
814
+ const trimmed = value.trim();
815
+ if (trimmed.length === 0) return {};
816
+ try {
817
+ return JSON.parse(trimmed);
818
+ } catch (error) {
819
+ const message = error instanceof Error ? error.message : "Invalid JSON.";
820
+ throw new Error(`OpenAI-compatible stream returned invalid tool arguments: ${message}`);
821
+ }
822
+ }
823
+ /**
824
+ * Phase 7 normalization: maps raw provider usage payloads (OpenAI's
825
+ * `prompt_tokens`/`completion_tokens`, the Responses API's
826
+ * `input_tokens`/`output_tokens`, or camelCase variants) to the shared
827
+ * `Usage` shape. When `pricing` is supplied, `costUsd` is computed from
828
+ * the normalized token counts. Otherwise `costUsd` is `null` so consumers
829
+ * can distinguish "unmeasured" from "zero".
830
+ */
831
+ function normalizeUsageToRunUsage(rawUsage, pricing) {
832
+ let promptTokens = 0;
833
+ let completionTokens = 0;
834
+ if (typeof rawUsage === "object" && rawUsage !== null) {
835
+ const record = rawUsage;
836
+ promptTokens = numberField$2(record, "prompt_tokens") ?? numberField$2(record, "input_tokens") ?? numberField$2(record, "inputTokens") ?? 0;
837
+ completionTokens = numberField$2(record, "completion_tokens") ?? numberField$2(record, "output_tokens") ?? numberField$2(record, "outputTokens") ?? 0;
838
+ }
839
+ let costUsd = null;
840
+ if (pricing !== void 0 && (pricing.inputPer1kTokens !== void 0 || pricing.outputPer1kTokens !== void 0)) costUsd = (pricing.inputPer1kTokens ?? 0) * promptTokens / 1e3 + (pricing.outputPer1kTokens ?? 0) * completionTokens / 1e3;
841
+ return {
842
+ promptTokens,
843
+ completionTokens,
844
+ costUsd
845
+ };
846
+ }
847
+ function normalizeUsage(usage) {
848
+ if (typeof usage !== "object" || usage === null) return;
849
+ const record = usage;
850
+ const inputTokens = numberField$2(record, "prompt_tokens") ?? numberField$2(record, "input_tokens");
851
+ const outputTokens = numberField$2(record, "completion_tokens") ?? numberField$2(record, "output_tokens");
852
+ const totalTokens = numberField$2(record, "total_tokens");
853
+ return {
854
+ ...inputTokens !== void 0 ? { inputTokens } : {},
855
+ ...outputTokens !== void 0 ? { outputTokens } : {},
856
+ ...totalTokens !== void 0 ? { totalTokens } : {}
857
+ };
858
+ }
859
+ function numberField$2(record, key) {
860
+ const value = record[key];
861
+ return typeof value === "number" ? value : void 0;
862
+ }
863
+ /**
864
+ * Phase 34 — D-12 — Emits a "capabilities.negotiation.fallback" RunEvent if
865
+ * a sink is provided. The runId uses a synthetic value since negotiation
866
+ * happens outside a run context.
867
+ *
868
+ * T-34-03-01: errorReason is produced by stringifyErr (message only, NOT
869
+ * stack) to prevent apiKey leaking in error strings that include request headers.
870
+ */
871
+ function emitFallbackEvent$1(sink, payload) {
872
+ if (sink === void 0) return;
873
+ sink(createRunEvent("capabilities.negotiation.fallback", {
874
+ runId: `negotiate-${payload.adapter}-${payload.modelId}`,
875
+ providerId: payload.adapter,
876
+ modelId: payload.modelId,
877
+ metadata: {
878
+ adapter: payload.adapter,
879
+ modelId: payload.modelId,
880
+ errorReason: payload.errorReason,
881
+ fallbackSource: payload.fallbackSource
882
+ }
883
+ }));
884
+ }
885
+ /**
886
+ * Stringify an error for event metadata. Returns only the message (NOT the
887
+ * stack) to prevent apiKey or sensitive header values from leaking into
888
+ * event payloads via fetch errors that may embed the request init.
889
+ * T-34-03-01 mitigation.
890
+ */
891
+ function stringifyErr$3(err) {
892
+ return err instanceof Error ? err.message : String(err);
893
+ }
894
+ /**
895
+ * Phase 34 — QUIRK-02 / NEG-01 / NEG-02 — Merge an OpenAI /v1/models
896
+ * sparse response with the Phase 33 registry.
897
+ *
898
+ * OpenAI's /models response is famously SPARSE per RESEARCH §Q2:
899
+ * `{ id, object, created, owned_by }` only. No capabilities block.
900
+ * The /models call confirms the model EXISTS in the user's org, but
901
+ * tells us nothing about its capabilities. We source supports.* from
902
+ * the Phase 33 registry instead.
903
+ *
904
+ * Source semantics per D-09:
905
+ * - "live" when the model id is found in the /models response
906
+ * (the id was verified to exist — useful signal for org membership)
907
+ * - "registry-fallback" when the model is NOT in the /models response
908
+ * (model not in org, or stale; emit fallback event)
909
+ *
910
+ * Anti-pattern warning (RESEARCH §Anti-patterns): DO NOT assume OpenAI
911
+ * /v1/models returns capability flags. It doesn't. Only id/object/created/
912
+ * owned_by are returned. Source supports.* ONLY from the registry.
913
+ */
914
+ function mergeOpenAIModelsWithRegistry(modelId, body, emitFallback) {
915
+ const data = body?.data;
916
+ if ((Array.isArray(data) ? data.find((m) => typeof m === "object" && m !== null && m.id === modelId) : void 0) === void 0) {
917
+ emitFallback();
918
+ return synthesizeNegotiatedCapabilitiesFromRegistry("openai", modelId, "registry-fallback");
919
+ }
920
+ const registryProfile = getCapabilityProfile(`openai:${modelId}`);
921
+ if (registryProfile !== void 0) return mapProfileToNegotiatedCapabilities(registryProfile, "live");
922
+ return {
923
+ modelId,
924
+ contextWindow: 0,
925
+ supports: {
926
+ nativeToolCalling: false,
927
+ structuredOutputs: false,
928
+ parallelToolCalls: false,
929
+ extendedThinking: false,
930
+ streaming: true
931
+ },
932
+ knownFailureModes: [],
933
+ recommendedSanitizers: [],
934
+ source: "live"
935
+ };
936
+ }
937
+ /**
938
+ * Phase 34 — QUIRK-02 / NEG-01 / NEG-02 — OpenAI provider factory.
939
+ *
940
+ * Extends the base OpenAI-compat factory with:
941
+ * 1. `quirks: OpenAIQuirks` — verified per RESEARCH §Q6 OpenAI vocabulary.
942
+ * 2. `negotiateCapabilities(modelId)` — queries OpenAI /v1/models GET with
943
+ * Authorization: Bearer header; SPARSE response; intersects with Phase 33
944
+ * registry for supports.* (per RESEARCH §Anti-patterns — don't assume
945
+ * OpenAI /v1/models returns capability flags, it doesn't).
946
+ *
947
+ * The negotiate() pattern mirrors Plan 34-02 (Anthropic thick reference):
948
+ * - Per-instance TTL cache (modelsCacheTtlMs, default 300_000ms)
949
+ * - Single-flight inflight coalescing with .finally cleanup (Pitfall 4)
950
+ * - Retry with [0, 200, 1000]ms backoff (modelsRetryCount, default 2)
951
+ * - 401/403 throws NegotiationAuthError (D-10: no retry, no fallback, no event)
952
+ * - 5xx/network/timeout falls back to registry with source: "registry-fallback"
953
+ * - emitFallbackEvent fires the "capabilities.negotiation.fallback" RunEvent
954
+ *
955
+ * SECURITY (T-34-03-07): inflight Map MUST use .finally cleanup to prevent
956
+ * leak on rejection. Verifiable: grep `.finally` in this file.
957
+ */
958
+ function createOpenAIProvider(options) {
959
+ const id = options.id ?? "openai";
960
+ const fetchImpl = options.fetch ?? fetch;
961
+ const baseUrl = (options.baseUrl ?? "https://api.openai.com").replace(/\/$/u, "");
962
+ const ttlMs = options.modelsCacheTtlMs ?? 3e5;
963
+ const retryCount = options.modelsRetryCount ?? 2;
964
+ const cache = /* @__PURE__ */ new Map();
965
+ const inflight = /* @__PURE__ */ new Map();
966
+ async function fetchAndNegotiate(modelId) {
967
+ const url = `${baseUrl}/v1/models`;
968
+ const headers = {
969
+ "accept": "application/json",
970
+ ...options.apiKey !== void 0 ? { authorization: `Bearer ${options.apiKey}` } : {}
971
+ };
972
+ const attempts = retryCount + 1;
973
+ const backoffMs = [
974
+ 0,
975
+ 200,
976
+ 1e3
977
+ ];
978
+ let lastErr;
979
+ for (let i = 0; i < attempts; i += 1) {
980
+ if (i > 0) {
981
+ const delay = backoffMs[Math.min(i, backoffMs.length - 1)] ?? 1e3;
982
+ await new Promise((r) => setTimeout(r, delay));
983
+ }
984
+ try {
985
+ const resp = await fetchImpl(url, {
986
+ method: "GET",
987
+ headers,
988
+ signal: AbortSignal.timeout(3e4)
989
+ });
990
+ if (resp.status === 401 || resp.status === 403) throw new NegotiationAuthError("openai", modelId, resp.status, `OpenAI /v1/models returned ${resp.status}: check apiKey config.`);
991
+ if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
992
+ return mergeOpenAIModelsWithRegistry(modelId, await resp.json(), () => {
993
+ emitFallbackEvent$1(options.runEventSink, {
994
+ adapter: "openai",
995
+ modelId,
996
+ errorReason: "model not found in /v1/models response",
997
+ fallbackSource: "registry-fallback"
998
+ });
999
+ });
1000
+ } catch (err) {
1001
+ if (err instanceof NegotiationAuthError) throw err;
1002
+ lastErr = err;
1003
+ }
1004
+ }
1005
+ emitFallbackEvent$1(options.runEventSink, {
1006
+ adapter: "openai",
1007
+ modelId,
1008
+ errorReason: stringifyErr$3(lastErr),
1009
+ fallbackSource: "registry-fallback"
1010
+ });
1011
+ return synthesizeNegotiatedCapabilitiesFromRegistry("openai", modelId, "registry-fallback");
1012
+ }
1013
+ async function negotiate(modelId) {
1014
+ const cached = cache.get(modelId);
1015
+ if (cached !== void 0 && cached.expiresAt > Date.now()) return cached.result;
1016
+ const existing = inflight.get(modelId);
1017
+ if (existing !== void 0) return existing;
1018
+ const fetchPromise = (async () => {
1019
+ try {
1020
+ const result = await fetchAndNegotiate(modelId);
1021
+ if (ttlMs > 0) cache.set(modelId, {
1022
+ result,
1023
+ expiresAt: Date.now() + ttlMs
1024
+ });
1025
+ return result;
1026
+ } finally {
1027
+ inflight.delete(modelId);
1028
+ }
1029
+ })();
1030
+ inflight.set(modelId, fetchPromise);
1031
+ return fetchPromise;
1032
+ }
1033
+ return {
1034
+ ...createOpenAICompatibleProvider({
1035
+ ...options,
1036
+ id,
1037
+ baseUrl
1038
+ }),
1039
+ quirks: {
1040
+ supportsToolChoice: true,
1041
+ parallelToolCalls: true,
1042
+ structuredOutputs: true,
1043
+ responseFormatHonored: true,
1044
+ streamingDiverges: false,
1045
+ strictModeSupported: true,
1046
+ structuredOutputsTier2: true
1047
+ },
1048
+ negotiateCapabilities: negotiate
1049
+ };
1050
+ }
1051
+ function createAISdkProvider(options) {
1052
+ const id = options.id ?? "ai-sdk";
1053
+ return {
1054
+ id,
1055
+ kind: "provider-adapter",
1056
+ capabilities: [{
1057
+ ...defaultCapabilityForProvider(id),
1058
+ modelId: options.model,
1059
+ toolUse: true,
1060
+ streaming: true
1061
+ }],
1062
+ execute: async (request) => {
1063
+ const response = await options.generate({
1064
+ task: request.task,
1065
+ outputNames: request.outputs
1066
+ });
1067
+ const normalizedUsage = {
1068
+ promptTokens: response.usage?.inputTokens ?? 0,
1069
+ completionTokens: response.usage?.outputTokens ?? 0,
1070
+ costUsd: null
1071
+ };
1072
+ return {
1073
+ ...response,
1074
+ normalizedUsage
1075
+ };
1076
+ }
1077
+ };
1078
+ }
1079
+ //#endregion
1080
+ //#region src/providers/anthropic.ts
1081
+ const DEFAULT_BASE_URL$1 = "https://api.anthropic.com";
1082
+ const DEFAULT_ANTHROPIC_VERSION = "2023-06-01";
1083
+ const DEFAULT_MAX_TOKENS = 2e3;
1084
+ const DEFAULT_MODELS_CACHE_TTL_MS = 3e5;
1085
+ const DEFAULT_MODELS_RETRY_COUNT = 2;
1086
+ /** D-11: Backoff schedule for transient /v1/models failures -- immediate, 200ms, 1s. */
1087
+ const MODELS_BACKOFF_MS = [
1088
+ 0,
1089
+ 200,
1090
+ 1e3
1091
+ ];
1092
+ async function createAnthropicMessagesBody(input) {
1093
+ const system = input.request.cacheSystemPrefix !== void 0 ? [{
1094
+ type: "text",
1095
+ text: input.request.cacheSystemPrefix,
1096
+ cache_control: { type: "ephemeral" }
1097
+ }] : "";
1098
+ const content = await createAnthropicUserContent(input.request);
1099
+ const nativeTools = anthropicToolDefinitions(input.request.nativeTools);
1100
+ const structuredTool = anthropicStructuredOutputTool(input.request.nativeStructuredOutput);
1101
+ const tools = [...nativeTools, ...structuredTool !== void 0 ? [structuredTool] : []];
1102
+ const toolChoice = structuredTool !== void 0 ? {
1103
+ type: "tool",
1104
+ name: structuredTool.name
1105
+ } : anthropicToolChoice(input.request.nativeToolChoice);
1106
+ return {
1107
+ body: {
1108
+ model: input.model,
1109
+ system,
1110
+ messages: [{
1111
+ role: "user",
1112
+ content: content.blocks.length === 0 ? input.request.task : [...content.blocks, {
1113
+ type: "text",
1114
+ text: input.request.task
1115
+ }]
1116
+ }],
1117
+ max_tokens: DEFAULT_MAX_TOKENS,
1118
+ ...input.stream === true ? { stream: true } : {},
1119
+ ...tools.length > 0 ? { tools } : {},
1120
+ ...toolChoice !== void 0 ? { tool_choice: toolChoice } : {}
1121
+ },
1122
+ usesFilesApi: content.usesFilesApi
1123
+ };
1124
+ }
1125
+ function anthropicToolDefinitions(tools) {
1126
+ return (tools ?? []).map((tool) => ({
1127
+ name: tool.name,
1128
+ ...tool.description !== void 0 ? { description: tool.description } : {},
1129
+ input_schema: standardSchemaToJsonSchema(tool.inputSchema)
1130
+ }));
1131
+ }
1132
+ function anthropicToolChoice(choice) {
1133
+ if (choice === void 0) return;
1134
+ if (choice === "required") return { type: "any" };
1135
+ if (choice === "auto" || choice === "none") return { type: choice };
1136
+ return {
1137
+ type: "tool",
1138
+ name: choice.name
1139
+ };
1140
+ }
1141
+ function anthropicStructuredOutputTool(request) {
1142
+ if (request === void 0) return;
1143
+ return {
1144
+ name: anthropicStructuredOutputToolName(request),
1145
+ description: `Return structured output for ${request.output}.`,
1146
+ input_schema: standardSchemaToJsonSchema(request.schema)
1147
+ };
1148
+ }
1149
+ async function createAnthropicUserContent(request) {
1150
+ const blocks = [];
1151
+ let usesFilesApi = false;
1152
+ for (const inputArtifact of request.artifacts) {
1153
+ if (inputArtifact.kind !== "image") continue;
1154
+ const packaged = packagedPlanForArtifact(request, inputArtifact.id);
1155
+ if (packaged === void 0) continue;
1156
+ if (packaged.transport === "file-id") {
1157
+ const fileId = anthropicFileId(inputArtifact);
1158
+ if (fileId === void 0) continue;
1159
+ blocks.push({
1160
+ type: "image",
1161
+ source: {
1162
+ type: "file",
1163
+ file_id: fileId
1164
+ }
1165
+ });
1166
+ usesFilesApi = true;
1167
+ continue;
1168
+ }
1169
+ if (packaged.transport === "url") {
1170
+ const url = artifactHttpUrl(inputArtifact);
1171
+ if (url === void 0) continue;
1172
+ blocks.push({
1173
+ type: "image",
1174
+ source: {
1175
+ type: "url",
1176
+ url
1177
+ }
1178
+ });
1179
+ continue;
1180
+ }
1181
+ if (packaged.transport === "base64" || packaged.transport === "inline") {
1182
+ const data = await artifactBase64Data(inputArtifact);
1183
+ if (data === void 0) continue;
1184
+ blocks.push({
1185
+ type: "image",
1186
+ source: {
1187
+ type: "base64",
1188
+ media_type: mediaTypeForArtifact(inputArtifact, "image/jpeg"),
1189
+ data
1190
+ }
1191
+ });
1192
+ }
1193
+ }
1194
+ return {
1195
+ blocks,
1196
+ usesFilesApi
1197
+ };
1198
+ }
1199
+ function createAnthropicProvider(options) {
1200
+ const id = options.id ?? "anthropic";
1201
+ const fetchImpl = options.fetch ?? fetch;
1202
+ const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL$1).replace(/\/$/u, "");
1203
+ const anthropicVersion = options.anthropicVersion ?? DEFAULT_ANTHROPIC_VERSION;
1204
+ const ttlMs = options.modelsCacheTtlMs ?? DEFAULT_MODELS_CACHE_TTL_MS;
1205
+ const retryCount = options.modelsRetryCount ?? DEFAULT_MODELS_RETRY_COUNT;
1206
+ const cache = /* @__PURE__ */ new Map();
1207
+ const inflight = /* @__PURE__ */ new Map();
1208
+ /**
1209
+ * D-12: Emits the `capabilities.negotiation.fallback` RunEvent via the
1210
+ * consumer-supplied sink. If no sink is provided, this is a no-op.
1211
+ *
1212
+ * SECURITY (T-34-02-01): errorReason is derived from `err.message` ONLY --
1213
+ * not `err.stack`, `err.toString()`, or any serialization that could include
1214
+ * request headers (which carry the apiKey). `stringifyErr` enforces this.
1215
+ *
1216
+ * JSDoc synthetic runId: negotiate() runs outside of a Lattice run context
1217
+ * (no ai.run() in scope). The runId `"negotiate-${id}-${modelId}"` is a
1218
+ * synthetic value that scopes the event to this adapter instance + modelId.
1219
+ * Consumers filtering on runId should treat "negotiate-" prefix as a signal
1220
+ * that this event originated from capability negotiation, not a user-facing run.
1221
+ */
1222
+ function emitFallbackEvent(payload) {
1223
+ if (options.runEventSink === void 0) return;
1224
+ const event = createRunEvent("capabilities.negotiation.fallback", {
1225
+ runId: `negotiate-${id}-${payload.modelId}`,
1226
+ providerId: id,
1227
+ modelId: payload.modelId,
1228
+ metadata: {
1229
+ adapter: payload.adapter,
1230
+ modelId: payload.modelId,
1231
+ errorReason: payload.errorReason,
1232
+ fallbackSource: payload.fallbackSource
1233
+ }
1234
+ });
1235
+ options.runEventSink(event);
1236
+ }
1237
+ /**
1238
+ * Pure error message extractor. Returns `err.message` for Error instances,
1239
+ * `String(err)` for everything else. Deliberately does NOT include stack,
1240
+ * headers, or other fields (T-34-02-01 mitigation).
1241
+ */
1242
+ function stringifyErr(err) {
1243
+ return err instanceof Error ? err.message : String(err);
1244
+ }
1245
+ /**
1246
+ * Merges a live /v1/models response body with the Phase 33 static registry
1247
+ * profile for the given modelId. Called on HTTP 200 responses only.
1248
+ *
1249
+ * LENIENT PARSING (Pitfall 1): every field access uses optional chaining.
1250
+ * Missing `capabilities.thinking` or other sub-fields default to false rather
1251
+ * than throwing. This ensures forward-compatibility with future API shape changes.
1252
+ *
1253
+ * contextWindow policy: Anthropic's max_input_tokens is set to 0 in the fixture
1254
+ * for models where it is unreliable. When 0, falls through to the registry profile's
1255
+ * contextWindow (if present) or 0 as a final default (RESEARCH §Q1).
1256
+ */
1257
+ function mergeAnthropicModelsWithRegistry(modelId, body) {
1258
+ const found = body?.data?.find?.((m) => {
1259
+ if (typeof m !== "object" || m === null) return false;
1260
+ return m["id"] === modelId;
1261
+ });
1262
+ if (found === void 0) {
1263
+ emitFallbackEvent({
1264
+ adapter: "anthropic",
1265
+ modelId,
1266
+ errorReason: "model not found in /v1/models response",
1267
+ fallbackSource: "registry-fallback"
1268
+ });
1269
+ return synthesizeNegotiatedCapabilitiesFromRegistry("anthropic", modelId, "registry-fallback");
1270
+ }
1271
+ const caps = found["capabilities"] ?? {};
1272
+ const structuredOutputsSupported = caps["structured_outputs"]?.["supported"] === true;
1273
+ const thinkingSupported = caps["thinking"]?.["supported"] === true;
1274
+ const maxInputTokensRaw = found["max_input_tokens"];
1275
+ const maxInputTokens = typeof maxInputTokensRaw === "number" && maxInputTokensRaw > 0 ? maxInputTokensRaw : void 0;
1276
+ const registryProfile = getCapabilityProfile(`anthropic:${modelId}`);
1277
+ const contextWindow = maxInputTokens ?? registryProfile?.contextWindow ?? 0;
1278
+ const knownFailureModes = registryProfile?.knownFailureModes ?? [];
1279
+ return {
1280
+ modelId,
1281
+ contextWindow,
1282
+ supports: {
1283
+ nativeToolCalling: true,
1284
+ structuredOutputs: structuredOutputsSupported,
1285
+ parallelToolCalls: true,
1286
+ extendedThinking: thinkingSupported,
1287
+ streaming: true
1288
+ },
1289
+ knownFailureModes,
1290
+ recommendedSanitizers: getRecommendedSanitizers(knownFailureModes),
1291
+ source: "live"
1292
+ };
1293
+ }
1294
+ /**
1295
+ * D-09 / D-10 / D-11: Core /v1/models fetch with retry-backoff, auth-error-throw,
1296
+ * and transient-fallback. Called only once per modelId (inflight coalescing prevents
1297
+ * concurrent duplicate fetches).
1298
+ *
1299
+ * URL shape: `${baseUrl}/v1/models?limit=1000` to page all models in one request.
1300
+ * Headers per RESEARCH §Q1: x-api-key, anthropic-version, accept.
1301
+ */
1302
+ async function fetchAndNegotiate(modelId) {
1303
+ const url = `${baseUrl}/v1/models?limit=1000`;
1304
+ const headers = {
1305
+ "x-api-key": options.apiKey,
1306
+ "anthropic-version": anthropicVersion,
1307
+ "accept": "application/json"
1308
+ };
1309
+ const attempts = retryCount + 1;
1310
+ let lastErr;
1311
+ for (let i = 0; i < attempts; i += 1) {
1312
+ const delayMs = MODELS_BACKOFF_MS[i] ?? MODELS_BACKOFF_MS[MODELS_BACKOFF_MS.length - 1];
1313
+ if (delayMs > 0) await new Promise((resolve) => setTimeout(resolve, delayMs));
1314
+ try {
1315
+ const resp = await fetchImpl(url, {
1316
+ method: "GET",
1317
+ headers,
1318
+ signal: AbortSignal.timeout(3e4)
1319
+ });
1320
+ if (resp.status === 401 || resp.status === 403) throw new NegotiationAuthError("anthropic", modelId, resp.status, `Anthropic /v1/models returned ${resp.status}: check apiKey config.`);
1321
+ if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
1322
+ return mergeAnthropicModelsWithRegistry(modelId, await resp.json());
1323
+ } catch (err) {
1324
+ if (err instanceof NegotiationAuthError) throw err;
1325
+ lastErr = err;
1326
+ }
1327
+ }
1328
+ emitFallbackEvent({
1329
+ adapter: "anthropic",
1330
+ modelId,
1331
+ errorReason: stringifyErr(lastErr),
1332
+ fallbackSource: "registry-fallback"
1333
+ });
1334
+ return synthesizeNegotiatedCapabilitiesFromRegistry("anthropic", modelId, "registry-fallback");
1335
+ }
1336
+ /**
1337
+ * D-07: Lazy expiry cache check + D-Q7: inflight coalescing.
1338
+ *
1339
+ * Cache check: stale entries are evicted lazily on read (no background setInterval
1340
+ * -- library must not pin the Node event loop).
1341
+ *
1342
+ * Inflight coalescing: concurrent calls for the same modelId share one fetch
1343
+ * Promise. Pitfall 4 mitigation: `.finally` block ALWAYS clears the inflight
1344
+ * Map entry, even on rejection. This ensures that a rejected Promise doesn't
1345
+ * "poison" the Map -- the next caller after all concurrent calls settle will
1346
+ * trigger a fresh fetch attempt.
1347
+ */
1348
+ async function negotiateCapabilities(modelId) {
1349
+ const cached = cache.get(modelId);
1350
+ if (cached !== void 0 && cached.expiresAt > Date.now()) return cached.result;
1351
+ const existing = inflight.get(modelId);
1352
+ if (existing !== void 0) return existing;
1353
+ const fetchPromise = (async () => {
1354
+ try {
1355
+ const result = await fetchAndNegotiate(modelId);
1356
+ if (ttlMs > 0) cache.set(modelId, {
1357
+ result,
1358
+ expiresAt: ttlMs === Infinity ? Infinity : Date.now() + ttlMs
1359
+ });
1360
+ return result;
1361
+ } finally {
1362
+ inflight.delete(modelId);
1363
+ }
1364
+ })();
1365
+ inflight.set(modelId, fetchPromise);
1366
+ return fetchPromise;
1367
+ }
1368
+ return {
1369
+ id,
1370
+ kind: "provider-adapter",
1371
+ capabilities: [{
1372
+ ...defaultCapabilityForProvider(id),
1373
+ modelId: options.model,
1374
+ fileTransport: [
1375
+ "inline",
1376
+ "json",
1377
+ "url",
1378
+ "base64",
1379
+ "file-id",
1380
+ "extracted-text",
1381
+ "transcript"
1382
+ ],
1383
+ streaming: true
1384
+ }],
1385
+ quirks: {
1386
+ supportsToolChoice: true,
1387
+ parallelToolCalls: true,
1388
+ structuredOutputs: true,
1389
+ responseFormatHonored: true,
1390
+ streamingDiverges: false,
1391
+ promptCachingSupported: true,
1392
+ extendedThinkingSupported: true,
1393
+ toolUseInputSchemaStrict: true
1394
+ },
1395
+ negotiateCapabilities,
1396
+ async execute(request) {
1397
+ const messagesBody = await createAnthropicMessagesBody({
1398
+ model: options.model,
1399
+ request
1400
+ });
1401
+ const bodyStr = JSON.stringify(messagesBody.body);
1402
+ assertNoPublicUrlEgress(request, id, bodyStr);
1403
+ const init = {
1404
+ method: "POST",
1405
+ headers: {
1406
+ "content-type": "application/json",
1407
+ "x-api-key": options.apiKey,
1408
+ "anthropic-version": anthropicVersion,
1409
+ ...messagesBody.usesFilesApi ? { "anthropic-beta": "files-api-2025-04-14" } : {}
1410
+ },
1411
+ body: bodyStr,
1412
+ ...request.signal !== void 0 ? { signal: request.signal } : {}
1413
+ };
1414
+ const response = await fetchImpl(`${baseUrl}/v1/messages`, init);
1415
+ if (!response.ok) throw new Error(`Anthropic provider failed with ${response.status}.`);
1416
+ const body = await response.json();
1417
+ const text = anthropicTextFromContent(body.content);
1418
+ const structuredOutput = anthropicStructuredOutputFromContent(body.content, request.nativeStructuredOutput);
1419
+ const sanitizedOutputs = await applyOutputSanitizers(rawOutputsForRequest$1({
1420
+ outputs: request.outputs,
1421
+ text,
1422
+ structuredOutputRequest: request.nativeStructuredOutput,
1423
+ structuredOutput
1424
+ }), options.sanitizeOutput, {
1425
+ providerId: id,
1426
+ modelId: options.model
1427
+ });
1428
+ const parsedToolCalls = parseToolUseEnvelope(text);
1429
+ const promptToolCalls = parsedToolCalls === null ? void 0 : await validateToolCallRequests(parsedToolCalls, options.validateToolCalls);
1430
+ const nativeToolRequests = anthropicToolUseRequestsFromContent(body.content, request.nativeStructuredOutput);
1431
+ const nativeToolCalls = nativeToolRequests.length === 0 ? void 0 : await validateToolCallRequests(nativeToolRequests, options.validateToolCalls);
1432
+ const hasToolCallResult = promptToolCalls !== void 0 || nativeToolCalls !== void 0;
1433
+ const toolCalls = [...promptToolCalls ?? [], ...nativeToolCalls ?? []];
1434
+ const usage = normalizeAnthropicUsage(body.usage);
1435
+ const normalizedUsage = normalizeAnthropicUsageToRunUsage(body.usage, options.pricing);
1436
+ const finish = finishMetadata$1({
1437
+ reason: typeof body.stop_reason === "string" ? body.stop_reason : void 0,
1438
+ toolCallIds: toolCalls.map((toolCall) => toolCall.id)
1439
+ });
1440
+ return {
1441
+ rawOutputs: sanitizedOutputs,
1442
+ ...usage !== void 0 ? { usage } : {},
1443
+ normalizedUsage,
1444
+ ...hasToolCallResult ? { toolCalls } : {},
1445
+ ...finish !== void 0 ? { finish } : {},
1446
+ rawResponse: body
1447
+ };
1448
+ },
1449
+ executeStream(request) {
1450
+ return streamAnthropicResponse({
1451
+ id,
1452
+ model: options.model,
1453
+ baseUrl,
1454
+ apiKey: options.apiKey,
1455
+ anthropicVersion,
1456
+ fetchImpl,
1457
+ request,
1458
+ ...options.pricing !== void 0 ? { pricing: options.pricing } : {},
1459
+ ...options.sanitizeOutput !== void 0 ? { sanitizeOutput: options.sanitizeOutput } : {},
1460
+ ...options.validateToolCalls !== void 0 ? { validateToolCalls: options.validateToolCalls } : {}
1461
+ });
1462
+ }
1463
+ };
1464
+ }
1465
+ async function* streamAnthropicResponse(input) {
1466
+ const messagesBody = await createAnthropicMessagesBody({
1467
+ model: input.model,
1468
+ request: input.request,
1469
+ stream: true
1470
+ });
1471
+ const streamBodyStr = JSON.stringify(messagesBody.body);
1472
+ assertNoPublicUrlEgress(input.request, input.id, streamBodyStr);
1473
+ const response = await input.fetchImpl(`${input.baseUrl}/v1/messages`, {
1474
+ method: "POST",
1475
+ headers: {
1476
+ "content-type": "application/json",
1477
+ "x-api-key": input.apiKey,
1478
+ "anthropic-version": input.anthropicVersion,
1479
+ ...messagesBody.usesFilesApi ? { "anthropic-beta": "files-api-2025-04-14" } : {}
1480
+ },
1481
+ body: streamBodyStr,
1482
+ ...input.request.signal !== void 0 ? { signal: input.request.signal } : {}
1483
+ });
1484
+ if (!response.ok) throw new Error(`Anthropic provider failed with ${response.status}.`);
1485
+ const textParts = [];
1486
+ const rawChunks = [];
1487
+ const toolBlocks = /* @__PURE__ */ new Map();
1488
+ const nativeToolRequests = [];
1489
+ let usagePayload;
1490
+ let finishReason;
1491
+ for await (const event of readSseEvents(response)) {
1492
+ const data = event.data.trim();
1493
+ if (data.length === 0) continue;
1494
+ if (data === "[DONE]") break;
1495
+ const chunk = parseJsonObject$1(data, "Anthropic");
1496
+ rawChunks.push(event.event === void 0 ? chunk : {
1497
+ event: event.event,
1498
+ data: chunk
1499
+ });
1500
+ usagePayload = mergeAnthropicUsage(usagePayload, usageFromAnthropicChunk(chunk));
1501
+ finishReason = anthropicStopReason(chunk) ?? finishReason;
1502
+ const eventType = eventTypeFromAnthropicChunk(chunk) ?? event.event;
1503
+ if (eventType === "content_block_start") {
1504
+ startAnthropicToolBlock(toolBlocks, chunk);
1505
+ continue;
1506
+ }
1507
+ if (eventType === "content_block_delta") {
1508
+ const text = anthropicTextDelta(chunk);
1509
+ if (text !== void 0 && text.length > 0) {
1510
+ textParts.push(text);
1511
+ for (const output of input.request.outputs) yield {
1512
+ kind: "text-delta",
1513
+ output,
1514
+ text
1515
+ };
1516
+ }
1517
+ appendAnthropicToolInput(toolBlocks, chunk);
1518
+ continue;
1519
+ }
1520
+ if (eventType === "content_block_stop") {
1521
+ const request = completeAnthropicToolBlock(toolBlocks, chunk);
1522
+ if (request !== void 0) nativeToolRequests.push(request);
1523
+ }
1524
+ }
1525
+ const text = textParts.join("");
1526
+ const structuredToolName = anthropicStructuredOutputToolName(input.request.nativeStructuredOutput);
1527
+ const providerToolRequests = structuredToolName === void 0 ? nativeToolRequests : nativeToolRequests.filter((request) => request.name !== structuredToolName);
1528
+ const structuredOutput = structuredToolName === void 0 ? void 0 : nativeToolRequests.find((request) => request.name === structuredToolName)?.args;
1529
+ const sanitizedOutputs = await applyOutputSanitizers(rawOutputsForRequest$1({
1530
+ outputs: input.request.outputs,
1531
+ text,
1532
+ structuredOutputRequest: input.request.nativeStructuredOutput,
1533
+ structuredOutput
1534
+ }), input.sanitizeOutput, {
1535
+ providerId: input.id,
1536
+ modelId: input.model
1537
+ });
1538
+ const parsedToolCalls = parseToolUseEnvelope(text);
1539
+ const promptToolCalls = parsedToolCalls === null ? void 0 : await validateToolCallRequests(parsedToolCalls, input.validateToolCalls);
1540
+ const nativeToolCalls = providerToolRequests.length === 0 ? void 0 : await validateToolCallRequests(providerToolRequests, input.validateToolCalls);
1541
+ const toolCalls = [...promptToolCalls ?? [], ...nativeToolCalls ?? []];
1542
+ const usage = normalizeAnthropicUsage(usagePayload);
1543
+ const normalizedUsage = normalizeAnthropicUsageToRunUsage(usagePayload, input.pricing);
1544
+ const finish = finishMetadata$1({
1545
+ reason: finishReason,
1546
+ toolCallIds: toolCalls.map((toolCall) => toolCall.id)
1547
+ });
1548
+ yield {
1549
+ kind: "complete",
1550
+ rawOutputs: sanitizedOutputs,
1551
+ ...usage !== void 0 ? { usage } : {},
1552
+ normalizedUsage,
1553
+ ...toolCalls.length > 0 ? { toolCalls } : {},
1554
+ ...finish !== void 0 ? { finish } : {},
1555
+ rawResponse: {
1556
+ kind: "anthropic-stream",
1557
+ chunks: rawChunks
1558
+ }
1559
+ };
1560
+ }
1561
+ function parseJsonObject$1(data, providerName) {
1562
+ try {
1563
+ return JSON.parse(data);
1564
+ } catch (error) {
1565
+ const message = error instanceof Error ? error.message : "Invalid JSON.";
1566
+ throw new Error(`${providerName} stream returned invalid JSON: ${message}`);
1567
+ }
1568
+ }
1569
+ function eventTypeFromAnthropicChunk(chunk) {
1570
+ return isRecord$2(chunk) && typeof chunk.type === "string" ? chunk.type : void 0;
1571
+ }
1572
+ function anthropicStopReason(chunk) {
1573
+ if (!isRecord$2(chunk)) return;
1574
+ if (typeof chunk.stop_reason === "string") return chunk.stop_reason;
1575
+ if (isRecord$2(chunk.delta) && typeof chunk.delta.stop_reason === "string") return chunk.delta.stop_reason;
1576
+ }
1577
+ function usageFromAnthropicChunk(chunk) {
1578
+ if (!isRecord$2(chunk)) return;
1579
+ if (isRecord$2(chunk.usage)) return chunk.usage;
1580
+ if (isRecord$2(chunk.message) && isRecord$2(chunk.message.usage)) return chunk.message.usage;
1581
+ }
1582
+ function mergeAnthropicUsage(current, next) {
1583
+ if (!isRecord$2(next)) return current;
1584
+ return {
1585
+ ...current ?? {},
1586
+ ...next
1587
+ };
1588
+ }
1589
+ function anthropicIndex(chunk) {
1590
+ return isRecord$2(chunk) && typeof chunk.index === "number" ? chunk.index : void 0;
1591
+ }
1592
+ function startAnthropicToolBlock(blocks, chunk) {
1593
+ const index = anthropicIndex(chunk);
1594
+ const contentBlock = isRecord$2(chunk) && isRecord$2(chunk.content_block) ? chunk.content_block : void 0;
1595
+ if (index === void 0 || contentBlock === void 0 || contentBlock.type !== "tool_use" || typeof contentBlock.id !== "string" || typeof contentBlock.name !== "string") return;
1596
+ blocks.set(index, {
1597
+ id: contentBlock.id,
1598
+ name: contentBlock.name,
1599
+ jsonParts: []
1600
+ });
1601
+ }
1602
+ function anthropicTextDelta(chunk) {
1603
+ if (!isRecord$2(chunk) || !isRecord$2(chunk.delta)) return;
1604
+ return chunk.delta.type === "text_delta" && typeof chunk.delta.text === "string" ? chunk.delta.text : void 0;
1605
+ }
1606
+ function appendAnthropicToolInput(blocks, chunk) {
1607
+ const index = anthropicIndex(chunk);
1608
+ if (index === void 0 || !isRecord$2(chunk) || !isRecord$2(chunk.delta)) return;
1609
+ if (chunk.delta.type !== "input_json_delta" || typeof chunk.delta.partial_json !== "string") return;
1610
+ blocks.get(index)?.jsonParts.push(chunk.delta.partial_json);
1611
+ }
1612
+ function completeAnthropicToolBlock(blocks, chunk) {
1613
+ const index = anthropicIndex(chunk);
1614
+ if (index === void 0) return;
1615
+ const block = blocks.get(index);
1616
+ if (block === void 0) return;
1617
+ blocks.delete(index);
1618
+ return {
1619
+ id: block.id,
1620
+ name: block.name,
1621
+ args: parseAnthropicToolInput(block)
1622
+ };
1623
+ }
1624
+ function parseAnthropicToolInput(block) {
1625
+ const value = block.jsonParts.join("").trim();
1626
+ if (value.length === 0) return {};
1627
+ try {
1628
+ return JSON.parse(value);
1629
+ } catch (error) {
1630
+ const message = error instanceof Error ? error.message : "Invalid JSON.";
1631
+ throw new Error(`Anthropic stream returned invalid tool input JSON: ${message}`);
1632
+ }
1633
+ }
1634
+ function anthropicTextFromContent(content) {
1635
+ if (!Array.isArray(content)) return "";
1636
+ return content.flatMap((block) => isRecord$2(block) && block.type === "text" && typeof block.text === "string" ? [block.text] : []).join("");
1637
+ }
1638
+ function anthropicToolUseRequestsFromContent(content, structuredOutput) {
1639
+ if (!Array.isArray(content)) return [];
1640
+ const structuredToolName = anthropicStructuredOutputToolName(structuredOutput);
1641
+ return content.flatMap((block, index) => {
1642
+ if (!isRecord$2(block) || block.type !== "tool_use" || typeof block.name !== "string" || block.name === structuredToolName) return [];
1643
+ return [{
1644
+ id: typeof block.id === "string" ? block.id : `anthropic-tool-use-${index}`,
1645
+ name: block.name,
1646
+ args: block.input ?? {}
1647
+ }];
1648
+ });
1649
+ }
1650
+ function anthropicStructuredOutputFromContent(content, request) {
1651
+ const toolName = anthropicStructuredOutputToolName(request);
1652
+ if (toolName === void 0 || !Array.isArray(content)) return;
1653
+ const block = content.find((item) => isRecord$2(item) && item.type === "tool_use" && item.name === toolName && "input" in item);
1654
+ return isRecord$2(block) ? block.input : void 0;
1655
+ }
1656
+ function anthropicStructuredOutputToolName(request) {
1657
+ if (request === void 0) return;
1658
+ return sanitizeToolName(request.name ?? `lattice_${request.output}`);
1659
+ }
1660
+ function sanitizeToolName(name) {
1661
+ const sanitized = name.replace(/[^A-Za-z0-9_-]/gu, "_").slice(0, 64);
1662
+ return sanitized.length > 0 ? sanitized : "lattice_output";
1663
+ }
1664
+ function rawOutputsForRequest$1(input) {
1665
+ const rawOutputs = Object.fromEntries(input.outputs.map((name) => [name, input.text]));
1666
+ if (input.structuredOutputRequest !== void 0 && input.structuredOutput !== void 0) rawOutputs[input.structuredOutputRequest.output] = input.structuredOutput;
1667
+ return rawOutputs;
1668
+ }
1669
+ function finishMetadata$1(input) {
1670
+ const toolCallIds = input.toolCallIds.filter((id) => id.length > 0);
1671
+ if (input.reason === void 0 && toolCallIds.length === 0) return;
1672
+ return {
1673
+ ...input.reason !== void 0 ? { reason: input.reason } : {},
1674
+ ...toolCallIds.length > 0 ? { toolCallIds } : {}
1675
+ };
1676
+ }
1677
+ function isRecord$2(value) {
1678
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1679
+ }
1680
+ /**
1681
+ * Anthropic uses `input_tokens` / `output_tokens` (not OpenAI's
1682
+ * `prompt_tokens` / `completion_tokens`). This helper maps to Lattice's
1683
+ * `Usage` shape and applies pricing when supplied (Phase 7 pattern).
1684
+ */
1685
+ function normalizeAnthropicUsageToRunUsage(rawUsage, pricing) {
1686
+ let promptTokens = 0;
1687
+ let completionTokens = 0;
1688
+ if (typeof rawUsage === "object" && rawUsage !== null) {
1689
+ const record = rawUsage;
1690
+ promptTokens = numberField$1(record, "input_tokens") ?? numberField$1(record, "inputTokens") ?? 0;
1691
+ completionTokens = numberField$1(record, "output_tokens") ?? numberField$1(record, "outputTokens") ?? 0;
1692
+ }
1693
+ let costUsd = null;
1694
+ if (pricing !== void 0 && (pricing.inputPer1kTokens !== void 0 || pricing.outputPer1kTokens !== void 0)) costUsd = (pricing.inputPer1kTokens ?? 0) * promptTokens / 1e3 + (pricing.outputPer1kTokens ?? 0) * completionTokens / 1e3;
1695
+ return {
1696
+ promptTokens,
1697
+ completionTokens,
1698
+ costUsd
1699
+ };
1700
+ }
1701
+ function normalizeAnthropicUsage(usage) {
1702
+ if (typeof usage !== "object" || usage === null) return;
1703
+ const record = usage;
1704
+ const inputTokens = numberField$1(record, "input_tokens");
1705
+ const outputTokens = numberField$1(record, "output_tokens");
1706
+ const totalTokens = inputTokens !== void 0 && outputTokens !== void 0 ? inputTokens + outputTokens : void 0;
1707
+ return {
1708
+ ...inputTokens !== void 0 ? { inputTokens } : {},
1709
+ ...outputTokens !== void 0 ? { outputTokens } : {},
1710
+ ...totalTokens !== void 0 ? { totalTokens } : {}
1711
+ };
1712
+ }
1713
+ function numberField$1(record, key) {
1714
+ const value = record[key];
1715
+ return typeof value === "number" ? value : void 0;
1716
+ }
1717
+ //#endregion
1718
+ //#region src/providers/fake.ts
1719
+ const DEFAULT_FAKE_USAGE = {
1720
+ promptTokens: 0,
1721
+ completionTokens: 0,
1722
+ costUsd: null
1723
+ };
1724
+ function createFakeProvider(options = {}) {
1725
+ const id = options.id ?? "fake";
1726
+ const modelId = options.modelId ?? `${id}:deterministic`;
1727
+ const defaultCapability = {
1728
+ ...defaultCapabilityForProvider(id),
1729
+ modelId,
1730
+ inputModalities: [
1731
+ "text",
1732
+ "json",
1733
+ "image",
1734
+ "audio",
1735
+ "document",
1736
+ "file",
1737
+ "url",
1738
+ "tool"
1739
+ ],
1740
+ outputModalities: ["text", "json"],
1741
+ toolUse: true
1742
+ };
1743
+ return {
1744
+ id,
1745
+ kind: "provider-adapter",
1746
+ capabilities: options.capabilities ?? [defaultCapability],
1747
+ async execute(request) {
1748
+ const baseResponse = typeof options.response === "function" ? await options.response(request) : options.response;
1749
+ if (baseResponse !== void 0) return baseResponse.normalizedUsage !== void 0 ? baseResponse : {
1750
+ ...baseResponse,
1751
+ normalizedUsage: { ...DEFAULT_FAKE_USAGE }
1752
+ };
1753
+ return {
1754
+ rawOutputs: Object.fromEntries(request.outputs.map((name) => [name, defaultOutputForName(name)])),
1755
+ ...options.artifacts !== void 0 ? { artifactRefs: options.artifacts } : {},
1756
+ normalizedUsage: { ...DEFAULT_FAKE_USAGE }
1757
+ };
1758
+ }
1759
+ };
1760
+ }
1761
+ function defaultOutputForName(name) {
1762
+ if (/action|json|data|decision/u.test(name)) return {
1763
+ kind: "clarify",
1764
+ reason: "fake provider default structured response"
1765
+ };
1766
+ if (/citations|evidence/u.test(name)) return [];
1767
+ if (/generated|artifacts/u.test(name)) return [];
1768
+ return `Fake response for ${name}.`;
1769
+ }
1770
+ //#endregion
1771
+ //#region src/providers/gemini.ts
1772
+ const DEFAULT_BASE_URL = "https://generativelanguage.googleapis.com";
1773
+ const DEFAULT_MAX_OUTPUT_TOKENS = 2e3;
1774
+ const DEFAULT_TEMPERATURE = .7;
1775
+ const DEFAULT_TOP_P = .9;
1776
+ /**
1777
+ * 4 HARM_CATEGORY entries at BLOCK_NONE (FSB convention mirrored from
1778
+ * `extension/ai/universal-provider.js:255-272`). If Google restricts
1779
+ * BLOCK_NONE in the future, that is a re-spec concern, not a Phase 4
1780
+ * design defect (CONTEXT.md Specific Ideas note).
1781
+ */
1782
+ const SAFETY_SETTINGS = [
1783
+ {
1784
+ category: "HARM_CATEGORY_HARASSMENT",
1785
+ threshold: "BLOCK_NONE"
1786
+ },
1787
+ {
1788
+ category: "HARM_CATEGORY_HATE_SPEECH",
1789
+ threshold: "BLOCK_NONE"
1790
+ },
1791
+ {
1792
+ category: "HARM_CATEGORY_SEXUALLY_EXPLICIT",
1793
+ threshold: "BLOCK_NONE"
1794
+ },
1795
+ {
1796
+ category: "HARM_CATEGORY_DANGEROUS_CONTENT",
1797
+ threshold: "BLOCK_NONE"
1798
+ }
1799
+ ];
1800
+ /**
1801
+ * Phase 34 — D-03 — Gemini quirks block. Values verified against
1802
+ * Gemini API documentation and gemini.ts:50-55 (safety settings) behavior.
1803
+ *
1804
+ * CITED: https://ai.google.dev/api/generate-content#v1beta.GenerationConfig
1805
+ * - responseSchemaSupported: gemini-1.5-pro+ and gemini-2.x
1806
+ * - safetySettingsConfigurable: verified in gemini.ts:50-55
1807
+ * - systemInstructionSupported: gemini-1.5+ systemInstruction field
1808
+ */
1809
+ const GEMINI_QUIRKS = {
1810
+ supportsToolChoice: true,
1811
+ parallelToolCalls: true,
1812
+ structuredOutputs: true,
1813
+ responseFormatHonored: true,
1814
+ streamingDiverges: false,
1815
+ responseSchemaSupported: true,
1816
+ safetySettingsConfigurable: true,
1817
+ systemInstructionSupported: true
1818
+ };
1819
+ async function createGeminiGenerateContentBody(request) {
1820
+ const parts = await createGeminiUserParts(request);
1821
+ const functionDeclarations = geminiFunctionDeclarations(request.nativeTools);
1822
+ const toolConfig = geminiToolConfig(request.nativeToolChoice);
1823
+ const structuredOutputConfig = geminiStructuredOutputConfig(request.nativeStructuredOutput);
1824
+ return {
1825
+ contents: [{
1826
+ role: "user",
1827
+ parts
1828
+ }],
1829
+ generationConfig: {
1830
+ temperature: DEFAULT_TEMPERATURE,
1831
+ topP: DEFAULT_TOP_P,
1832
+ maxOutputTokens: DEFAULT_MAX_OUTPUT_TOKENS,
1833
+ ...structuredOutputConfig !== void 0 ? structuredOutputConfig : {}
1834
+ },
1835
+ safetySettings: SAFETY_SETTINGS,
1836
+ ...functionDeclarations.length > 0 ? { tools: [{ functionDeclarations }] } : {},
1837
+ ...toolConfig !== void 0 ? { toolConfig } : {}
1838
+ };
1839
+ }
1840
+ function geminiFunctionDeclarations(tools) {
1841
+ return (tools ?? []).map((tool) => ({
1842
+ name: tool.name,
1843
+ ...tool.description !== void 0 ? { description: tool.description } : {},
1844
+ parameters: standardSchemaToJsonSchema(tool.inputSchema)
1845
+ }));
1846
+ }
1847
+ function geminiToolConfig(choice) {
1848
+ if (choice === void 0) return;
1849
+ if (choice === "auto") return { functionCallingConfig: { mode: "AUTO" } };
1850
+ if (choice === "none") return { functionCallingConfig: { mode: "NONE" } };
1851
+ if (choice === "required") return { functionCallingConfig: { mode: "ANY" } };
1852
+ return { functionCallingConfig: {
1853
+ mode: "ANY",
1854
+ allowedFunctionNames: [choice.name]
1855
+ } };
1856
+ }
1857
+ function geminiStructuredOutputConfig(request) {
1858
+ if (request === void 0) return;
1859
+ return {
1860
+ responseMimeType: "application/json",
1861
+ responseSchema: standardSchemaToJsonSchema(request.schema)
1862
+ };
1863
+ }
1864
+ async function createGeminiUserParts(request) {
1865
+ const parts = [{ text: request.task }];
1866
+ for (const inputArtifact of request.artifacts) {
1867
+ if (!isGeminiMediaArtifact(inputArtifact.kind)) continue;
1868
+ const packaged = packagedPlanForArtifact(request, inputArtifact.id);
1869
+ if (packaged === void 0) continue;
1870
+ if (packaged.transport === "file-id") {
1871
+ const fileUri = geminiFileUri(inputArtifact);
1872
+ if (fileUri === void 0) continue;
1873
+ parts.push({ fileData: {
1874
+ mimeType: mediaTypeForArtifact(inputArtifact, fallbackGeminiMimeType(inputArtifact.kind)),
1875
+ fileUri
1876
+ } });
1877
+ continue;
1878
+ }
1879
+ if (packaged.transport === "url") {
1880
+ const fileUri = artifactHttpUrl(inputArtifact);
1881
+ if (fileUri === void 0) continue;
1882
+ parts.push({ fileData: {
1883
+ mimeType: mediaTypeForArtifact(inputArtifact, fallbackGeminiMimeType(inputArtifact.kind)),
1884
+ fileUri
1885
+ } });
1886
+ continue;
1887
+ }
1888
+ if (packaged.transport === "base64" || packaged.transport === "inline") {
1889
+ const data = await artifactBase64Data(inputArtifact);
1890
+ if (data === void 0) continue;
1891
+ parts.push({ inlineData: {
1892
+ mimeType: mediaTypeForArtifact(inputArtifact, fallbackGeminiMimeType(inputArtifact.kind)),
1893
+ data
1894
+ } });
1895
+ }
1896
+ }
1897
+ return parts;
1898
+ }
1899
+ function isGeminiMediaArtifact(kind) {
1900
+ return kind === "image" || kind === "audio" || kind === "video";
1901
+ }
1902
+ function fallbackGeminiMimeType(kind) {
1903
+ switch (kind) {
1904
+ case "image": return "image/jpeg";
1905
+ case "audio": return "audio/mpeg";
1906
+ case "video": return "video/mp4";
1907
+ }
1908
+ }
1909
+ function geminiGenerateContentUrl(input) {
1910
+ const method = input.stream === true ? "streamGenerateContent" : "generateContent";
1911
+ const params = new URLSearchParams({ key: input.apiKey });
1912
+ if (input.stream === true) params.set("alt", "sse");
1913
+ const encodedModel = encodeURIComponent(input.model);
1914
+ return `${input.baseUrl}/v1beta/models/${encodedModel}:${method}?${params.toString()}`;
1915
+ }
1916
+ /**
1917
+ * Phase 34 — D-03 / D-05..D-12 — Extended Gemini provider factory.
1918
+ *
1919
+ * Returns a `ProviderAdapter` narrowed to expose:
1920
+ * - `quirks: GeminiQuirks` — static adapter capability flags
1921
+ * - `negotiateCapabilities(modelId)` — live /v1beta/models fetch with medium-thick
1922
+ * derivation (inputTokenLimit + thinking + supportedGenerationMethods from upstream)
1923
+ * intersected with Phase 33 registry; TTL cache + inflight coalescing + retry +
1924
+ * auth-throw + transient-fallback + event.
1925
+ *
1926
+ * NOTE on auth strategy (T-34-04-01): negotiate() uses x-goog-api-key HEADER
1927
+ * (preferred per RESEARCH §Q3 -- avoids leaking the key in server-side logs that
1928
+ * capture URL query strings). The existing execute() path uses ?key= query string
1929
+ * and is NOT changed by Phase 34 (out-of-scope migration).
1930
+ */
1931
+ function createGeminiProvider(options) {
1932
+ const id = options.id ?? "gemini";
1933
+ const fetchImpl = options.fetch ?? fetch;
1934
+ const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/u, "");
1935
+ const ttlMs = options.modelsCacheTtlMs ?? 3e5;
1936
+ const retryCount = options.modelsRetryCount ?? 2;
1937
+ const cache = /* @__PURE__ */ new Map();
1938
+ const inflight = /* @__PURE__ */ new Map();
1939
+ /**
1940
+ * D-07 lazy expiry + Q7 inflight coalescing + Pitfall 4 .finally cleanup.
1941
+ * Public surface: `adapter.negotiateCapabilities(modelId)`.
1942
+ */
1943
+ async function negotiate(modelId) {
1944
+ const cached = cache.get(modelId);
1945
+ if (cached !== void 0 && cached.expiresAt > Date.now()) return cached.result;
1946
+ const existing = inflight.get(modelId);
1947
+ if (existing !== void 0) return existing;
1948
+ const fetchPromise = (async () => {
1949
+ try {
1950
+ const result = await fetchAndNegotiate(modelId);
1951
+ if (ttlMs > 0) cache.set(modelId, {
1952
+ result,
1953
+ expiresAt: Date.now() + ttlMs
1954
+ });
1955
+ return result;
1956
+ } finally {
1957
+ inflight.delete(modelId);
1958
+ }
1959
+ })();
1960
+ inflight.set(modelId, fetchPromise);
1961
+ return fetchPromise;
1962
+ }
1963
+ /**
1964
+ * Phase 34 — D-09..D-11 — Fetches /v1beta/models and merges with registry.
1965
+ *
1966
+ * URL: ${baseUrl}/v1beta/models (NOT /v1/models -- Gemini uses /v1beta/ prefix)
1967
+ * Auth: x-goog-api-key HEADER (preferred per RESEARCH §Q3 -- NOT ?key= query-string;
1968
+ * avoids leaking the key in server-side log captures of request URLs).
1969
+ * Retry: [0ms, 200ms, 1000ms] backoff on transient errors (D-11).
1970
+ * Auth error (401/403): throws NegotiationAuthError (D-10, no fallback).
1971
+ * Transient error (5xx/network): falls back to registry with "registry-fallback" (D-09).
1972
+ */
1973
+ async function fetchAndNegotiate(modelId) {
1974
+ const url = `${baseUrl}/v1beta/models`;
1975
+ const headers = {
1976
+ "x-goog-api-key": options.apiKey,
1977
+ "accept": "application/json"
1978
+ };
1979
+ const attempts = retryCount + 1;
1980
+ const backoffSchedule = [
1981
+ 0,
1982
+ 200,
1983
+ 1e3
1984
+ ];
1985
+ let lastErr;
1986
+ for (let i = 0; i < attempts; i += 1) {
1987
+ const delay = backoffSchedule[i] ?? 1e3;
1988
+ if (delay > 0) await new Promise((r) => setTimeout(r, delay));
1989
+ try {
1990
+ const resp = await fetchImpl(url, {
1991
+ method: "GET",
1992
+ headers,
1993
+ signal: AbortSignal.timeout(3e4)
1994
+ });
1995
+ if (resp.status === 401 || resp.status === 403) throw new NegotiationAuthError("gemini", modelId, resp.status, `Gemini /v1beta/models returned ${resp.status}: check apiKey config.`);
1996
+ if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
1997
+ return mergeGeminiModelsWithRegistry(modelId, await resp.json());
1998
+ } catch (err) {
1999
+ if (err instanceof NegotiationAuthError) throw err;
2000
+ lastErr = err;
2001
+ }
2002
+ }
2003
+ emitFallbackEvent({
2004
+ adapter: "gemini",
2005
+ modelId,
2006
+ errorReason: stringifyErr$2(lastErr),
2007
+ fallbackSource: "registry-fallback"
2008
+ });
2009
+ return synthesizeNegotiatedCapabilitiesFromRegistry("gemini", modelId, "registry-fallback");
2010
+ }
2011
+ /**
2012
+ * MEDIUM-THICK derivation: consumes upstream truth from Gemini /v1beta/models
2013
+ * where available (inputTokenLimit -> contextWindow, thinking -> extendedThinking,
2014
+ * supportedGenerationMethods -> streaming + nativeToolCalling) and falls back
2015
+ * to registry for the rest (knownFailureModes, recommendedSanitizers).
2016
+ *
2017
+ * Lenient parsing per Pitfall 1: all field accesses use optional chaining.
2018
+ * Missing `thinking` field does not crash -- defaults to false.
2019
+ */
2020
+ function mergeGeminiModelsWithRegistry(modelId, body) {
2021
+ const models = body?.models;
2022
+ const found = Array.isArray(models) ? models.find((m) => {
2023
+ const rec = m;
2024
+ return rec?.name === `models/${modelId}` || rec?.baseModelId === modelId || rec?.name === modelId;
2025
+ }) : void 0;
2026
+ if (found === void 0) {
2027
+ emitFallbackEvent({
2028
+ adapter: "gemini",
2029
+ modelId,
2030
+ errorReason: "model not found in /v1beta/models response",
2031
+ fallbackSource: "registry-fallback"
2032
+ });
2033
+ return synthesizeNegotiatedCapabilitiesFromRegistry("gemini", modelId, "registry-fallback");
2034
+ }
2035
+ const foundRec = found;
2036
+ const registryProfile = getCapabilityProfile(`gemini:${modelId}`);
2037
+ const contextWindow = typeof foundRec.inputTokenLimit === "number" && foundRec.inputTokenLimit > 0 ? foundRec.inputTokenLimit : registryProfile?.contextWindow ?? 0;
2038
+ const extendedThinking = foundRec.thinking === true;
2039
+ const methods = Array.isArray(foundRec.supportedGenerationMethods) ? foundRec.supportedGenerationMethods.map(String) : [];
2040
+ const streaming = methods.includes("streamGenerateContent");
2041
+ const nativeToolCalling = methods.includes("generateContent") || methods.length > 0;
2042
+ const structuredOutputs = true;
2043
+ const parallelToolCalls = true;
2044
+ const knownFailureModes = registryProfile?.knownFailureModes ?? [];
2045
+ const recommendedSanitizers = getRecommendedSanitizers(knownFailureModes);
2046
+ return {
2047
+ modelId,
2048
+ contextWindow,
2049
+ supports: {
2050
+ nativeToolCalling,
2051
+ structuredOutputs,
2052
+ parallelToolCalls,
2053
+ extendedThinking,
2054
+ streaming
2055
+ },
2056
+ knownFailureModes,
2057
+ recommendedSanitizers,
2058
+ source: "live"
2059
+ };
2060
+ }
2061
+ /**
2062
+ * D-12: Emit capabilities.negotiation.fallback RunEvent via the optional sink.
2063
+ * SECURITY (T-34-04-02): stringifyErr extracts err.message only -- NOT err.stack
2064
+ * or JSON.stringify(headers), so the apiKey cannot leak into the event payload.
2065
+ * Synthetic runId pattern: negotiate happens outside a run; documented here.
2066
+ */
2067
+ function emitFallbackEvent(payload) {
2068
+ if (options.runEventSink === void 0) return;
2069
+ const event = createRunEvent("capabilities.negotiation.fallback", {
2070
+ runId: `negotiate-gemini-${payload.modelId}`,
2071
+ providerId: id,
2072
+ modelId: payload.modelId,
2073
+ metadata: {
2074
+ adapter: payload.adapter,
2075
+ modelId: payload.modelId,
2076
+ errorReason: payload.errorReason,
2077
+ fallbackSource: payload.fallbackSource
2078
+ }
2079
+ });
2080
+ options.runEventSink(event);
2081
+ }
2082
+ return {
2083
+ id,
2084
+ kind: "provider-adapter",
2085
+ capabilities: [{
2086
+ ...defaultCapabilityForProvider(id),
2087
+ modelId: options.model,
2088
+ fileTransport: [
2089
+ "inline",
2090
+ "json",
2091
+ "url",
2092
+ "base64",
2093
+ "file-id",
2094
+ "extracted-text",
2095
+ "transcript"
2096
+ ],
2097
+ streaming: true
2098
+ }],
2099
+ quirks: GEMINI_QUIRKS,
2100
+ negotiateCapabilities: negotiate,
2101
+ async execute(request) {
2102
+ const requestBody = await createGeminiGenerateContentBody(request);
2103
+ const bodyStr = JSON.stringify(requestBody);
2104
+ assertNoPublicUrlEgress(request, id, bodyStr);
2105
+ const init = {
2106
+ method: "POST",
2107
+ headers: { "content-type": "application/json" },
2108
+ body: bodyStr,
2109
+ ...request.signal !== void 0 ? { signal: request.signal } : {}
2110
+ };
2111
+ const response = await fetchImpl(geminiGenerateContentUrl({
2112
+ baseUrl,
2113
+ model: options.model,
2114
+ apiKey: options.apiKey
2115
+ }), init);
2116
+ if (!response.ok) throw new Error(`Gemini provider failed with ${response.status}.`);
2117
+ const body = await response.json();
2118
+ if (!Array.isArray(body.candidates) || body.candidates.length === 0) throw new Error("Gemini provider returned no candidates.");
2119
+ const text = geminiTextFromBody(body);
2120
+ const structuredOutput = request.nativeStructuredOutput === void 0 ? void 0 : parseJsonValue(text);
2121
+ const sanitizedOutputs = await applyOutputSanitizers(rawOutputsForRequest({
2122
+ outputs: request.outputs,
2123
+ text,
2124
+ structuredOutputRequest: request.nativeStructuredOutput,
2125
+ structuredOutput
2126
+ }), options.sanitizeOutput, {
2127
+ providerId: id,
2128
+ modelId: options.model
2129
+ });
2130
+ const parsedToolCalls = parseToolUseEnvelope(text);
2131
+ const promptToolCalls = parsedToolCalls === null ? void 0 : await validateToolCallRequests(parsedToolCalls, options.validateToolCalls);
2132
+ const nativeToolRequests = geminiParts(body).flatMap(({ part, candidateIndex, partIndex }) => {
2133
+ const request = geminiFunctionCallRequest(part, candidateIndex, partIndex);
2134
+ return request === void 0 ? [] : [request];
2135
+ });
2136
+ const nativeToolCalls = nativeToolRequests.length === 0 ? void 0 : await validateToolCallRequests(nativeToolRequests, options.validateToolCalls);
2137
+ const hasToolCallResult = promptToolCalls !== void 0 || nativeToolCalls !== void 0;
2138
+ const toolCalls = [...promptToolCalls ?? [], ...nativeToolCalls ?? []];
2139
+ const usage = normalizeGeminiUsage(body.usageMetadata);
2140
+ const normalizedUsage = normalizeGeminiUsageToRunUsage(body.usageMetadata, options.pricing);
2141
+ const finish = finishMetadata({
2142
+ reason: geminiFinishReason(body),
2143
+ toolCallIds: toolCalls.map((toolCall) => toolCall.id)
2144
+ });
2145
+ return {
2146
+ rawOutputs: sanitizedOutputs,
2147
+ ...usage !== void 0 ? { usage } : {},
2148
+ normalizedUsage,
2149
+ ...hasToolCallResult ? { toolCalls } : {},
2150
+ ...finish !== void 0 ? { finish } : {},
2151
+ rawResponse: body
2152
+ };
2153
+ },
2154
+ executeStream(request) {
2155
+ return streamGeminiResponse({
2156
+ id,
2157
+ model: options.model,
2158
+ baseUrl,
2159
+ apiKey: options.apiKey,
2160
+ fetchImpl,
2161
+ request,
2162
+ ...options.pricing !== void 0 ? { pricing: options.pricing } : {},
2163
+ ...options.sanitizeOutput !== void 0 ? { sanitizeOutput: options.sanitizeOutput } : {},
2164
+ ...options.validateToolCalls !== void 0 ? { validateToolCalls: options.validateToolCalls } : {}
2165
+ });
2166
+ }
2167
+ };
2168
+ }
2169
+ async function* streamGeminiResponse(input) {
2170
+ const requestBody = await createGeminiGenerateContentBody(input.request);
2171
+ const streamBodyStr = JSON.stringify(requestBody);
2172
+ assertNoPublicUrlEgress(input.request, input.id, streamBodyStr);
2173
+ const response = await input.fetchImpl(geminiGenerateContentUrl({
2174
+ baseUrl: input.baseUrl,
2175
+ model: input.model,
2176
+ apiKey: input.apiKey,
2177
+ stream: true
2178
+ }), {
2179
+ method: "POST",
2180
+ headers: { "content-type": "application/json" },
2181
+ body: streamBodyStr,
2182
+ ...input.request.signal !== void 0 ? { signal: input.request.signal } : {}
2183
+ });
2184
+ if (!response.ok) throw new Error(`Gemini provider failed with ${response.status}.`);
2185
+ const textParts = [];
2186
+ const rawChunks = [];
2187
+ const nativeToolRequests = [];
2188
+ let usagePayload;
2189
+ let finishReason;
2190
+ for await (const event of readSseEvents(response)) {
2191
+ const data = event.data.trim();
2192
+ if (data.length === 0) continue;
2193
+ if (data === "[DONE]") break;
2194
+ const chunk = parseJsonObject(data, "Gemini");
2195
+ rawChunks.push(chunk);
2196
+ const usage = geminiUsageMetadata(chunk);
2197
+ if (usage !== void 0) usagePayload = usage;
2198
+ finishReason = geminiFinishReason(chunk) ?? finishReason;
2199
+ for (const { part, candidateIndex, partIndex } of geminiParts(chunk)) {
2200
+ if (typeof part.text === "string" && part.text.length > 0) {
2201
+ textParts.push(part.text);
2202
+ for (const output of input.request.outputs) yield {
2203
+ kind: "text-delta",
2204
+ output,
2205
+ text: part.text
2206
+ };
2207
+ }
2208
+ const toolRequest = geminiFunctionCallRequest(part, candidateIndex, partIndex);
2209
+ if (toolRequest !== void 0) nativeToolRequests.push(toolRequest);
2210
+ }
2211
+ }
2212
+ const text = textParts.join("");
2213
+ const structuredOutput = input.request.nativeStructuredOutput === void 0 ? void 0 : parseJsonValue(text);
2214
+ const sanitizedOutputs = await applyOutputSanitizers(rawOutputsForRequest({
2215
+ outputs: input.request.outputs,
2216
+ text,
2217
+ structuredOutputRequest: input.request.nativeStructuredOutput,
2218
+ structuredOutput
2219
+ }), input.sanitizeOutput, {
2220
+ providerId: input.id,
2221
+ modelId: input.model
2222
+ });
2223
+ const parsedToolCalls = parseToolUseEnvelope(text);
2224
+ const promptToolCalls = parsedToolCalls === null ? void 0 : await validateToolCallRequests(parsedToolCalls, input.validateToolCalls);
2225
+ const nativeToolCalls = nativeToolRequests.length === 0 ? void 0 : await validateToolCallRequests(nativeToolRequests, input.validateToolCalls);
2226
+ const toolCalls = [...promptToolCalls ?? [], ...nativeToolCalls ?? []];
2227
+ const usage = normalizeGeminiUsage(usagePayload);
2228
+ const normalizedUsage = normalizeGeminiUsageToRunUsage(usagePayload, input.pricing);
2229
+ const finish = finishMetadata({
2230
+ reason: finishReason,
2231
+ toolCallIds: toolCalls.map((toolCall) => toolCall.id)
2232
+ });
2233
+ yield {
2234
+ kind: "complete",
2235
+ rawOutputs: sanitizedOutputs,
2236
+ ...usage !== void 0 ? { usage } : {},
2237
+ normalizedUsage,
2238
+ ...toolCalls.length > 0 ? { toolCalls } : {},
2239
+ ...finish !== void 0 ? { finish } : {},
2240
+ rawResponse: {
2241
+ kind: "gemini-stream",
2242
+ chunks: rawChunks
2243
+ }
2244
+ };
2245
+ }
2246
+ function parseJsonObject(data, providerName) {
2247
+ try {
2248
+ return JSON.parse(data);
2249
+ } catch (error) {
2250
+ const message = error instanceof Error ? error.message : "Invalid JSON.";
2251
+ throw new Error(`${providerName} stream returned invalid JSON: ${message}`);
2252
+ }
2253
+ }
2254
+ function geminiUsageMetadata(chunk) {
2255
+ return isRecord$1(chunk) ? chunk.usageMetadata : void 0;
2256
+ }
2257
+ function geminiFinishReason(chunk) {
2258
+ if (!isRecord$1(chunk) || !Array.isArray(chunk.candidates)) return;
2259
+ for (const candidate of chunk.candidates) if (isRecord$1(candidate) && typeof candidate.finishReason === "string") return candidate.finishReason;
2260
+ }
2261
+ function geminiTextFromBody(body) {
2262
+ return geminiParts(body).flatMap(({ part }) => typeof part.text === "string" ? [part.text] : []).join("");
2263
+ }
2264
+ function geminiParts(chunk) {
2265
+ if (!isRecord$1(chunk) || !Array.isArray(chunk.candidates)) return [];
2266
+ return chunk.candidates.flatMap((candidate, candidateIndex) => {
2267
+ if (!isRecord$1(candidate) || !isRecord$1(candidate.content)) return [];
2268
+ const parts = candidate.content.parts;
2269
+ if (!Array.isArray(parts)) return [];
2270
+ return parts.flatMap((part, partIndex) => isRecord$1(part) ? [{
2271
+ part,
2272
+ candidateIndex,
2273
+ partIndex
2274
+ }] : []);
2275
+ });
2276
+ }
2277
+ function geminiFunctionCallRequest(part, candidateIndex, partIndex) {
2278
+ if (!isRecord$1(part.functionCall)) return;
2279
+ const name = part.functionCall.name;
2280
+ if (typeof name !== "string") return;
2281
+ return {
2282
+ id: `gemini-function-call-${candidateIndex}-${partIndex}`,
2283
+ name,
2284
+ args: part.functionCall.args ?? {}
2285
+ };
2286
+ }
2287
+ function parseJsonValue(text) {
2288
+ const trimmed = text.trim();
2289
+ if (trimmed.length === 0) return;
2290
+ try {
2291
+ return JSON.parse(trimmed);
2292
+ } catch {
2293
+ return;
2294
+ }
2295
+ }
2296
+ function rawOutputsForRequest(input) {
2297
+ const rawOutputs = Object.fromEntries(input.outputs.map((name) => [name, input.text]));
2298
+ if (input.structuredOutputRequest !== void 0 && input.structuredOutput !== void 0) rawOutputs[input.structuredOutputRequest.output] = input.structuredOutput;
2299
+ return rawOutputs;
2300
+ }
2301
+ function finishMetadata(input) {
2302
+ const toolCallIds = input.toolCallIds.filter((id) => id.length > 0);
2303
+ if (input.reason === void 0 && toolCallIds.length === 0) return;
2304
+ return {
2305
+ ...input.reason !== void 0 ? { reason: input.reason } : {},
2306
+ ...toolCallIds.length > 0 ? { toolCallIds } : {}
2307
+ };
2308
+ }
2309
+ function isRecord$1(value) {
2310
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2311
+ }
2312
+ /**
2313
+ * Gemini uses `usageMetadata.promptTokenCount` / `candidatesTokenCount` /
2314
+ * `totalTokenCount` (NOT OpenAI's `prompt_tokens` / `completion_tokens`).
2315
+ * This helper maps to Lattice's `Usage` shape and applies pricing when supplied.
2316
+ */
2317
+ function normalizeGeminiUsageToRunUsage(rawUsage, pricing) {
2318
+ let promptTokens = 0;
2319
+ let completionTokens = 0;
2320
+ if (typeof rawUsage === "object" && rawUsage !== null) {
2321
+ const record = rawUsage;
2322
+ promptTokens = numberField(record, "promptTokenCount") ?? 0;
2323
+ completionTokens = numberField(record, "candidatesTokenCount") ?? 0;
2324
+ }
2325
+ let costUsd = null;
2326
+ if (pricing !== void 0 && (pricing.inputPer1kTokens !== void 0 || pricing.outputPer1kTokens !== void 0)) costUsd = (pricing.inputPer1kTokens ?? 0) * promptTokens / 1e3 + (pricing.outputPer1kTokens ?? 0) * completionTokens / 1e3;
2327
+ return {
2328
+ promptTokens,
2329
+ completionTokens,
2330
+ costUsd
2331
+ };
2332
+ }
2333
+ function normalizeGeminiUsage(usage) {
2334
+ if (typeof usage !== "object" || usage === null) return;
2335
+ const record = usage;
2336
+ const inputTokens = numberField(record, "promptTokenCount");
2337
+ const outputTokens = numberField(record, "candidatesTokenCount");
2338
+ const totalTokens = numberField(record, "totalTokenCount");
2339
+ return {
2340
+ ...inputTokens !== void 0 ? { inputTokens } : {},
2341
+ ...outputTokens !== void 0 ? { outputTokens } : {},
2342
+ ...totalTokens !== void 0 ? { totalTokens } : {}
2343
+ };
2344
+ }
2345
+ function numberField(record, key) {
2346
+ const value = record[key];
2347
+ return typeof value === "number" ? value : void 0;
2348
+ }
2349
+ /**
2350
+ * T-34-04-02: Returns err.message only -- NOT err.stack (which could include
2351
+ * headers or the apiKey via a fetch rejection), NOT JSON.stringify(err).
2352
+ */
2353
+ function stringifyErr$2(err) {
2354
+ return err instanceof Error ? err.message : String(err);
2355
+ }
2356
+ //#endregion
2357
+ //#region src/providers/litellm.ts
2358
+ const DEFAULT_LITELLM_BASE_URL = "http://localhost:4000";
2359
+ function createLiteLLMProvider(options) {
2360
+ const resolvedId = options.id ?? "litellm";
2361
+ const resolvedBaseUrl = options.baseUrl ?? DEFAULT_LITELLM_BASE_URL;
2362
+ const gateway = {
2363
+ allowFallbacks: false,
2364
+ ...options.gateway
2365
+ };
2366
+ const inner = createOpenAICompatibleProvider({
2367
+ ...options,
2368
+ id: resolvedId,
2369
+ baseUrl: resolvedBaseUrl,
2370
+ gateway
2371
+ });
2372
+ const negotiate = async (modelId) => {
2373
+ return synthesizeNegotiatedCapabilitiesFromRegistry("litellm", modelId, "registry");
2374
+ };
2375
+ return {
2376
+ ...inner,
2377
+ quirks: {
2378
+ supportsToolChoice: false,
2379
+ parallelToolCalls: false,
2380
+ structuredOutputs: false,
2381
+ responseFormatHonored: false,
2382
+ streamingDiverges: true,
2383
+ gatewayMetadataSupported: true,
2384
+ gatewayFallbacksSupported: true,
2385
+ openAIErrorMapping: true
2386
+ },
2387
+ negotiateCapabilities: negotiate
2388
+ };
2389
+ }
2390
+ //#endregion
2391
+ //#region src/providers/lm-studio.ts
2392
+ const DEFAULT_LM_STUDIO_BASE_URL = "http://localhost:1234/v1";
2393
+ /**
2394
+ * Phase 34 — D-04 / QUIRK-02 — LM Studio provider factory.
2395
+ *
2396
+ * LM Studio is the prototypical "intentional no remote /models endpoint"
2397
+ * adapter per D-04 (alongside OpenAI-compat). The factory returns conservative
2398
+ * defaults for the quirks block because LM Studio runs LOCAL quantized models
2399
+ * whose capabilities vary wildly by chat template + model file.
2400
+ *
2401
+ * The `negotiateCapabilities` method performs NO fetch; it returns
2402
+ * `synthesizeNegotiatedCapabilitiesFromRegistry` with source: "registry"
2403
+ * (the intentional-no-endpoint signal, distinct from "registry-fallback"
2404
+ * which signals a transient failure). Mirrors Plan 34-03 Task 2 (OpenAI-compat
2405
+ * registry-only pattern) verbatim.
2406
+ *
2407
+ * D-04 citation: "consumer adapters without a /models endpoint skip the
2408
+ * fetch layer entirely and delegate to synthesizeNegotiatedCapabilitiesFromRegistry."
2409
+ *
2410
+ * Open Question 5 (RESEARCH §): no event emitted for source: "registry" because
2411
+ * this is the intentional happy path for LM Studio -- emitting a "fallback" event
2412
+ * would produce false-positive noise for consumers monitoring the event stream.
2413
+ */
2414
+ function createLmStudioProvider(options) {
2415
+ const resolvedId = options.id ?? "lm-studio";
2416
+ const resolvedBaseUrl = options.baseUrl ?? DEFAULT_LM_STUDIO_BASE_URL;
2417
+ const negotiate = async (modelId) => {
2418
+ return synthesizeNegotiatedCapabilitiesFromRegistry("lm-studio", modelId, "registry");
2419
+ };
2420
+ return {
2421
+ ...createOpenAICompatibleProvider({
2422
+ ...options,
2423
+ id: resolvedId,
2424
+ baseUrl: resolvedBaseUrl
2425
+ }),
2426
+ quirks: {
2427
+ supportsToolChoice: false,
2428
+ parallelToolCalls: false,
2429
+ structuredOutputs: false,
2430
+ responseFormatHonored: false,
2431
+ streamingDiverges: true,
2432
+ customChatTemplateRiskFlag: true,
2433
+ noAuthRequired: true
2434
+ },
2435
+ negotiateCapabilities: negotiate
2436
+ };
2437
+ }
2438
+ //#endregion
2439
+ //#region src/providers/openrouter.ts
2440
+ const DEFAULT_OPENROUTER_BASE_URL = "https://openrouter.ai/api/v1";
2441
+ function normalizeFallbackModels(models) {
2442
+ if (models === void 0) return void 0;
2443
+ const normalized = models.map((model) => model.trim()).filter((model) => model.length > 0);
2444
+ return normalized.length > 0 ? normalized : void 0;
2445
+ }
2446
+ function observedModelFromRawResponse(rawResponse) {
2447
+ if (typeof rawResponse !== "object" || rawResponse === null || Array.isArray(rawResponse)) return;
2448
+ const model = rawResponse.model;
2449
+ return typeof model === "string" ? model : void 0;
2450
+ }
2451
+ /**
2452
+ * Phase 34 — D-03 — OpenRouter quirks block. Values verified against
2453
+ * OpenRouter API documentation and observed behavior.
2454
+ *
2455
+ * CITED: https://openrouter.ai/docs/provider-routing
2456
+ * - providerRoutingArraySupported: provider.order/only/ignore arrays for explicit routing
2457
+ * CITED: https://openrouter.ai/docs pricing / sort options
2458
+ * - floorPricingHints: max_price, sort: "throughput" | "price" hints for cost-aware routing
2459
+ * CITED: https://openrouter.ai/docs allow_fallbacks
2460
+ * - allowFallbacks: provider.allow_fallbacks boolean controls upstream-provider fallback behavior
2461
+ */
2462
+ const OPENROUTER_QUIRKS = {
2463
+ supportsToolChoice: true,
2464
+ parallelToolCalls: true,
2465
+ structuredOutputs: true,
2466
+ responseFormatHonored: true,
2467
+ streamingDiverges: false,
2468
+ providerRoutingArraySupported: true,
2469
+ floorPricingHints: true,
2470
+ allowFallbacks: true
2471
+ };
2472
+ /**
2473
+ * Phase 34 — D-03 / D-05..D-12 — Extended OpenRouter provider factory.
2474
+ *
2475
+ * Returns a `ProviderAdapter` narrowed to expose:
2476
+ * - `quirks: OpenRouterQuirks` — static adapter capability flags (8 booleans)
2477
+ * - `negotiateCapabilities(modelId)` — live /api/v1/models fetch with rich /models
2478
+ * intersection (supported_parameters -> nativeToolCalling + structuredOutputs,
2479
+ * top_provider.context_length -> contextWindow) intersected with Phase 33 registry
2480
+ * for knownFailureModes + recommendedSanitizers.
2481
+ *
2482
+ * CRITICAL for ANCHOR CASE STUDY (session_1780792387779):
2483
+ * negotiate("openai/gpt-oss-120b:free") MUST resolve to:
2484
+ * - result.knownFailureModes.includes("internal_envelope_leak") -> TRUE
2485
+ * - result.recommendedSanitizers.includes("unwrapInternalEnvelope") -> TRUE
2486
+ * - result.source === "live" -> TRUE
2487
+ * This proves: live-fetch -> id suffix-strip via stripOpenRouterVariant
2488
+ * -> registry intersection -> getRecommendedSanitizers derivation.
2489
+ *
2490
+ * Anti-pattern (RESEARCH §Anti-pattern, lines 534-535):
2491
+ * The /api/v1/models endpoint is UNAUTHENTICATED (public discovery surface verified
2492
+ * Phase 33). Do NOT send Authorization Bearer to this endpoint -- it is NOT required
2493
+ * and would add unnecessary API key exposure surface in transit logs.
2494
+ */
2495
+ function createOpenRouterProvider(options) {
2496
+ const baseUrl = (options.baseUrl ?? DEFAULT_OPENROUTER_BASE_URL).replace(/\/$/u, "");
2497
+ const fetchImpl = options.fetch ?? fetch;
2498
+ const fallbackModels = normalizeFallbackModels(options.fallbackModels);
2499
+ const ttlMs = options.modelsCacheTtlMs ?? 3e5;
2500
+ const retryCount = options.modelsRetryCount ?? 2;
2501
+ const cache = /* @__PURE__ */ new Map();
2502
+ const inflight = /* @__PURE__ */ new Map();
2503
+ /**
2504
+ * D-07 lazy expiry + Q7 inflight coalescing + Pitfall 4 .finally cleanup.
2505
+ * Public surface: `adapter.negotiateCapabilities(modelId)`.
2506
+ */
2507
+ async function negotiate(modelId) {
2508
+ const cached = cache.get(modelId);
2509
+ if (cached !== void 0 && cached.expiresAt > Date.now()) return cached.result;
2510
+ const existing = inflight.get(modelId);
2511
+ if (existing !== void 0) return existing;
2512
+ const fetchPromise = (async () => {
2513
+ try {
2514
+ const result = await fetchAndNegotiate(modelId);
2515
+ if (ttlMs > 0) cache.set(modelId, {
2516
+ result,
2517
+ expiresAt: Date.now() + ttlMs
2518
+ });
2519
+ return result;
2520
+ } finally {
2521
+ inflight.delete(modelId);
2522
+ }
2523
+ })();
2524
+ inflight.set(modelId, fetchPromise);
2525
+ return fetchPromise;
2526
+ }
2527
+ /**
2528
+ * Phase 34 — D-09..D-11 — Fetches /api/v1/models and merges with registry.
2529
+ *
2530
+ * URL: ${baseUrl}/api/v1/models (NOTE: /api/v1/models -- different prefix from
2531
+ * OpenAI's /v1/models; OpenRouter's discovery endpoint is under /api/v1/)
2532
+ * Auth: NONE -- OpenRouter /api/v1/models is a public unauthenticated endpoint.
2533
+ * Per RESEARCH §Anti-pattern (lines 534-535): do NOT send Authorization Bearer
2534
+ * to this endpoint. This is a known anti-pattern; do not "fix" it.
2535
+ * Retry: [0ms, 200ms, 1000ms] backoff on transient errors (D-11).
2536
+ * Auth error (401/403): throws NegotiationAuthError (D-10, no fallback) -- defensive,
2537
+ * even though the endpoint is unauthenticated today, OpenRouter may add auth later.
2538
+ * Transient error (5xx/network): falls back to registry with "registry-fallback" (D-09).
2539
+ */
2540
+ async function fetchAndNegotiate(modelId) {
2541
+ const url = `${baseUrl}/api/v1/models`;
2542
+ const headers = { "accept": "application/json" };
2543
+ const attempts = retryCount + 1;
2544
+ const backoffSchedule = [
2545
+ 0,
2546
+ 200,
2547
+ 1e3
2548
+ ];
2549
+ let lastErr;
2550
+ for (let i = 0; i < attempts; i += 1) {
2551
+ const delay = backoffSchedule[i] ?? 1e3;
2552
+ if (delay > 0) await new Promise((r) => setTimeout(r, delay));
2553
+ try {
2554
+ const resp = await fetchImpl(url, {
2555
+ method: "GET",
2556
+ headers,
2557
+ signal: AbortSignal.timeout(3e4)
2558
+ });
2559
+ if (resp.status === 401 || resp.status === 403) throw new NegotiationAuthError("openrouter", modelId, resp.status, `OpenRouter /api/v1/models returned ${resp.status}.`);
2560
+ if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
2561
+ return mergeOpenRouterModelsWithRegistry(modelId, await resp.json());
2562
+ } catch (err) {
2563
+ if (err instanceof NegotiationAuthError) throw err;
2564
+ lastErr = err;
2565
+ }
2566
+ }
2567
+ emitFallbackEvent({
2568
+ adapter: "openrouter",
2569
+ modelId,
2570
+ errorReason: stringifyErr$1(lastErr),
2571
+ fallbackSource: "registry-fallback"
2572
+ });
2573
+ return synthesizeNegotiatedCapabilitiesFromRegistry("openrouter", modelId, "registry-fallback");
2574
+ }
2575
+ /**
2576
+ * RICH /models intersection: consumes OpenRouter's /api/v1/models structured data
2577
+ * to populate NegotiatedCapabilities.supports.* from upstream (THICK derivation where
2578
+ * available), then intersects with Phase 33 registry for knownFailureModes +
2579
+ * recommendedSanitizers.
2580
+ *
2581
+ * ANCHOR CASE STUDY (session_1780792387779) flow:
2582
+ * 1. Find "openai/gpt-oss-120b:free" (or strip suffix -> "openai/gpt-oss-120b")
2583
+ * 2. Build canonical key: "openrouter:openai/gpt-oss-120b" (via stripOpenRouterVariant)
2584
+ * 3. getCapabilityProfile("openrouter:openai/gpt-oss-120b") -> Phase 33 profile with
2585
+ * knownFailureModes: ["internal_envelope_leak", "system_prompt_echo", "malformed_tool_arguments"]
2586
+ * 4. getRecommendedSanitizers(knownFailureModes) -> ["unwrapInternalEnvelope"]
2587
+ * 5. result.recommendedSanitizers.includes("unwrapInternalEnvelope") -> TRUE
2588
+ *
2589
+ * Pitfall 3 / A1 precedence chain (RESEARCH §Q5):
2590
+ * contextWindow = top_provider.context_length ?? context_length ?? registryProfile.contextWindow
2591
+ *
2592
+ * Lenient parsing per Pitfall 1: all field accesses use optional chaining.
2593
+ */
2594
+ function mergeOpenRouterModelsWithRegistry(modelId, body) {
2595
+ const rows = body?.data;
2596
+ const found = Array.isArray(rows) ? rows.find((m) => {
2597
+ const rec = m;
2598
+ if (typeof rec?.id !== "string") return false;
2599
+ const rowId = rec.id;
2600
+ if (rowId === modelId || rowId === stripOpenRouterVariant(modelId)) return true;
2601
+ const strippedModelId = stripOpenRouterVariant(modelId);
2602
+ return stripOpenRouterVariant(rowId) === strippedModelId;
2603
+ }) : void 0;
2604
+ const stripped = stripOpenRouterVariant(modelId);
2605
+ const registryProfile = getCapabilityProfile(`openrouter:${stripped}`);
2606
+ if (found === void 0) {
2607
+ emitFallbackEvent({
2608
+ adapter: "openrouter",
2609
+ modelId,
2610
+ errorReason: "model not found in /api/v1/models response",
2611
+ fallbackSource: "registry-fallback"
2612
+ });
2613
+ return {
2614
+ ...synthesizeNegotiatedCapabilitiesFromRegistry("openrouter", stripped, "registry-fallback"),
2615
+ modelId
2616
+ };
2617
+ }
2618
+ const foundRec = found;
2619
+ const topProvider = foundRec.top_provider;
2620
+ const contextWindow = typeof topProvider?.context_length === "number" && topProvider.context_length > 0 ? topProvider.context_length : typeof foundRec.context_length === "number" && foundRec.context_length > 0 ? foundRec.context_length : registryProfile?.contextWindow ?? 0;
2621
+ const supportedParams = Array.isArray(foundRec.supported_parameters) ? foundRec.supported_parameters.map(String) : [];
2622
+ const nativeToolCalling = supportedParams.includes("tools");
2623
+ const structuredOutputs = supportedParams.includes("response_format");
2624
+ const parallelToolCalls = supportedParams.includes("tool_choice");
2625
+ const extendedThinking = supportedParams.includes("reasoning") || supportedParams.includes("thinking");
2626
+ const streaming = true;
2627
+ const knownFailureModes = registryProfile?.knownFailureModes ?? [];
2628
+ const recommendedSanitizers = getRecommendedSanitizers(knownFailureModes);
2629
+ return {
2630
+ modelId,
2631
+ contextWindow,
2632
+ supports: {
2633
+ nativeToolCalling,
2634
+ structuredOutputs,
2635
+ parallelToolCalls,
2636
+ extendedThinking,
2637
+ streaming
2638
+ },
2639
+ knownFailureModes,
2640
+ recommendedSanitizers,
2641
+ source: "live"
2642
+ };
2643
+ }
2644
+ /**
2645
+ * D-12: Emit capabilities.negotiation.fallback RunEvent via the optional sink.
2646
+ * SECURITY (T-34-04-02): stringifyErr extracts err.message only -- NOT err.stack
2647
+ * or JSON.stringify(headers), so the apiKey cannot leak into the event payload.
2648
+ * Synthetic runId pattern: negotiate happens outside a run; documented here.
2649
+ */
2650
+ function emitFallbackEvent(payload) {
2651
+ if (options.runEventSink === void 0) return;
2652
+ const event = createRunEvent("capabilities.negotiation.fallback", {
2653
+ runId: `negotiate-openrouter-${payload.modelId}`,
2654
+ providerId: options.id ?? "openrouter",
2655
+ modelId: payload.modelId,
2656
+ metadata: {
2657
+ adapter: payload.adapter,
2658
+ modelId: payload.modelId,
2659
+ errorReason: payload.errorReason,
2660
+ fallbackSource: payload.fallbackSource
2661
+ }
2662
+ });
2663
+ options.runEventSink(event);
2664
+ }
2665
+ const executeFetch = fallbackModels === void 0 ? fetchImpl : (async (url, init) => {
2666
+ if (typeof init?.body !== "string") return fetchImpl(url, init);
2667
+ const body = JSON.parse(init.body);
2668
+ return fetchImpl(url, {
2669
+ ...init,
2670
+ body: JSON.stringify({
2671
+ ...body,
2672
+ models: [...fallbackModels]
2673
+ })
2674
+ });
2675
+ });
2676
+ const baseAdapter = createOpenAICompatibleProvider({
2677
+ ...options,
2678
+ id: options.id ?? "openrouter",
2679
+ baseUrl,
2680
+ fetch: executeFetch
2681
+ });
2682
+ const baseExecuteStream = baseAdapter.executeStream;
2683
+ return {
2684
+ ...baseAdapter,
2685
+ async execute(request) {
2686
+ const response = await baseAdapter.execute(request);
2687
+ const observedModel = response.gateway?.observedModel ?? observedModelFromRawResponse(response.rawResponse);
2688
+ return {
2689
+ ...response,
2690
+ gateway: {
2691
+ ...response.gateway ?? { used: true },
2692
+ used: true,
2693
+ requestedModel: options.model,
2694
+ ...fallbackModels !== void 0 ? { fallbackModels } : {},
2695
+ ...observedModel !== void 0 ? { observedModel } : {}
2696
+ }
2697
+ };
2698
+ },
2699
+ ...baseExecuteStream !== void 0 ? { executeStream: async (request) => {
2700
+ return withOpenRouterStreamGateway(await baseExecuteStream(request));
2701
+ } } : {},
2702
+ quirks: OPENROUTER_QUIRKS,
2703
+ negotiateCapabilities: negotiate
2704
+ };
2705
+ async function* withOpenRouterStreamGateway(stream) {
2706
+ for await (const chunk of stream) {
2707
+ if (chunk.kind !== "complete") {
2708
+ yield chunk;
2709
+ continue;
2710
+ }
2711
+ const observedModel = chunk.gateway?.observedModel ?? observedModelFromRawResponse(chunk.rawResponse);
2712
+ yield {
2713
+ ...chunk,
2714
+ gateway: {
2715
+ ...chunk.gateway ?? { used: true },
2716
+ used: true,
2717
+ requestedModel: options.model,
2718
+ ...fallbackModels !== void 0 ? { fallbackModels } : {},
2719
+ ...observedModel !== void 0 ? { observedModel } : {}
2720
+ }
2721
+ };
2722
+ }
2723
+ }
2724
+ }
2725
+ /**
2726
+ * T-34-04-02: Returns err.message only -- NOT err.stack (which could include
2727
+ * headers or the apiKey via a fetch rejection), NOT JSON.stringify(err).
2728
+ */
2729
+ function stringifyErr$1(err) {
2730
+ return err instanceof Error ? err.message : String(err);
2731
+ }
2732
+ //#endregion
2733
+ //#region src/providers/xai.ts
2734
+ const DEFAULT_XAI_BASE_URL = "https://api.x.ai/v1";
2735
+ /**
2736
+ * Phase 34 — D-12 — Emit a "capabilities.negotiation.fallback" RunEvent if
2737
+ * a sink is provided. Uses a synthetic runId (negotiation is outside a run).
2738
+ *
2739
+ * T-34-03-01: errorReason uses stringifyErr (message only, not stack) to
2740
+ * prevent apiKey leaking via fetch error strings that may embed request headers.
2741
+ */
2742
+ function emitFallbackEvent(sink, payload) {
2743
+ if (sink === void 0) return;
2744
+ sink(createRunEvent("capabilities.negotiation.fallback", {
2745
+ runId: `negotiate-${payload.adapter}-${payload.modelId}`,
2746
+ providerId: payload.adapter,
2747
+ modelId: payload.modelId,
2748
+ metadata: {
2749
+ adapter: payload.adapter,
2750
+ modelId: payload.modelId,
2751
+ errorReason: payload.errorReason,
2752
+ fallbackSource: payload.fallbackSource
2753
+ }
2754
+ }));
2755
+ }
2756
+ /**
2757
+ * Stringify an error for event metadata. Returns only the message (NOT the
2758
+ * stack) to prevent apiKey or sensitive header values from leaking into event
2759
+ * payloads via fetch errors that may embed the request init.
2760
+ * T-34-03-01 mitigation.
2761
+ */
2762
+ function stringifyErr(err) {
2763
+ return err instanceof Error ? err.message : String(err);
2764
+ }
2765
+ /**
2766
+ * Phase 34 — QUIRK-02 / NEG-01 / NEG-02 — Merge an xAI /v1/models sparse
2767
+ * OpenAI-shaped response with the Phase 33 registry.
2768
+ *
2769
+ * RESEARCH §A1 (INFERRED): xAI's /v1/models shape is undocumented; the
2770
+ * endpoint is assumed to return an OpenAI-compatible sparse list based on
2771
+ * the shared OpenAI-compat wire format used for chat completions.
2772
+ * LENIENT-PARSE is mandatory per Pitfall 1 (RESEARCH §Q4): if xAI changes
2773
+ * their response shape, the adapter must not crash.
2774
+ *
2775
+ * CITED: Pitfall 1 — "When integrating with less-documented endpoints like
2776
+ * xAI's /v1/models, lenient parsing prevents runtime crashes when the
2777
+ * endpoint returns an unexpected shape."
2778
+ *
2779
+ * Source semantics per D-09:
2780
+ * - "live" when the model id is found in the response AND body.data is an array
2781
+ * - "registry-fallback" when body.data is not an array (unexpected shape)
2782
+ * OR when the model id is not in the response
2783
+ */
2784
+ function mergeXaiModelsWithRegistry(modelId, body, emitFallback) {
2785
+ const rawData = body?.data;
2786
+ if (!Array.isArray(rawData)) {
2787
+ emitFallback();
2788
+ return synthesizeNegotiatedCapabilitiesFromRegistry("xai", modelId, "registry-fallback");
2789
+ }
2790
+ if (rawData.find((m) => typeof m === "object" && m !== null && m.id === modelId) === void 0) {
2791
+ emitFallback();
2792
+ return synthesizeNegotiatedCapabilitiesFromRegistry("xai", modelId, "registry-fallback");
2793
+ }
2794
+ const registryProfile = getCapabilityProfile(`xai:${modelId}`);
2795
+ if (registryProfile !== void 0) return mapProfileToNegotiatedCapabilities(registryProfile, "live");
2796
+ return {
2797
+ modelId,
2798
+ contextWindow: 0,
2799
+ supports: {
2800
+ nativeToolCalling: true,
2801
+ structuredOutputs: true,
2802
+ parallelToolCalls: true,
2803
+ extendedThinking: false,
2804
+ streaming: true
2805
+ },
2806
+ knownFailureModes: [],
2807
+ recommendedSanitizers: [],
2808
+ source: "live"
2809
+ };
2810
+ }
2811
+ /**
2812
+ * Phase 34 — QUIRK-02 / NEG-01 / NEG-02 — xAI provider factory.
2813
+ *
2814
+ * Extends the base OpenAI-compat execution wrapper with:
2815
+ * 1. `quirks: XaiQuirks` — verified per RESEARCH §Q6 xAI vocabulary.
2816
+ * 2. `negotiateCapabilities(modelId)` — queries xAI /v1/models GET with
2817
+ * Authorization: Bearer header; LENIENT-PARSE sparse OpenAI-shaped
2818
+ * response; intersects with Phase 33 registry for supports.*.
2819
+ *
2820
+ * CITED: RESEARCH §Q4 (INFERRED) — xAI /v1/models shape is undocumented;
2821
+ * assumed OpenAI-compatible based on the chat completions wire format.
2822
+ *
2823
+ * CITED: RESEARCH §A1 — Pitfall 1 lenient parse: if xAI publishes a
2824
+ * different /models shape, only the parsing logic updates; the contract
2825
+ * (source values, NegotiatedCapabilities shape) holds.
2826
+ *
2827
+ * The negotiate() pattern mirrors Plan 34-02 (Anthropic thick reference):
2828
+ * - Per-instance TTL cache (modelsCacheTtlMs, default 300_000ms)
2829
+ * - Single-flight inflight coalescing with .finally cleanup (Pitfall 4)
2830
+ * - Retry with [0, 200, 1000]ms backoff (modelsRetryCount, default 2)
2831
+ * - 401/403 throws NegotiationAuthError with adapter: "xai" (D-10)
2832
+ * - 5xx/network/timeout falls back to registry + emits fallback event
2833
+ *
2834
+ * SECURITY (T-34-03-07): inflight Map MUST use .finally cleanup to prevent
2835
+ * leak on rejection. Verifiable: grep `.finally` in this file.
2836
+ */
2837
+ function createXaiProvider(options) {
2838
+ const resolvedBaseUrl = (options.baseUrl ?? DEFAULT_XAI_BASE_URL).replace(/\/$/u, "");
2839
+ const ttlMs = options.modelsCacheTtlMs ?? 3e5;
2840
+ const retryCount = options.modelsRetryCount ?? 2;
2841
+ const fetchImpl = options.fetch ?? fetch;
2842
+ const cache = /* @__PURE__ */ new Map();
2843
+ const inflight = /* @__PURE__ */ new Map();
2844
+ async function fetchAndNegotiate(modelId) {
2845
+ const url = `${resolvedBaseUrl}/models`;
2846
+ const headers = {
2847
+ "accept": "application/json",
2848
+ ...options.apiKey !== void 0 ? { authorization: `Bearer ${options.apiKey}` } : {}
2849
+ };
2850
+ const attempts = retryCount + 1;
2851
+ const backoffMs = [
2852
+ 0,
2853
+ 200,
2854
+ 1e3
2855
+ ];
2856
+ let lastErr;
2857
+ for (let i = 0; i < attempts; i += 1) {
2858
+ if (i > 0) {
2859
+ const delay = backoffMs[Math.min(i, backoffMs.length - 1)] ?? 1e3;
2860
+ await new Promise((r) => setTimeout(r, delay));
2861
+ }
2862
+ try {
2863
+ const resp = await fetchImpl(url, {
2864
+ method: "GET",
2865
+ headers,
2866
+ signal: AbortSignal.timeout(3e4)
2867
+ });
2868
+ if (resp.status === 401 || resp.status === 403) throw new NegotiationAuthError("xai", modelId, resp.status, `xAI /v1/models returned ${resp.status}: check apiKey config.`);
2869
+ if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
2870
+ return mergeXaiModelsWithRegistry(modelId, await resp.json(), () => {
2871
+ emitFallbackEvent(options.runEventSink, {
2872
+ adapter: "xai",
2873
+ modelId,
2874
+ errorReason: "model not found in /v1/models response or unexpected body shape",
2875
+ fallbackSource: "registry-fallback"
2876
+ });
2877
+ });
2878
+ } catch (err) {
2879
+ if (err instanceof NegotiationAuthError) throw err;
2880
+ lastErr = err;
2881
+ }
2882
+ }
2883
+ emitFallbackEvent(options.runEventSink, {
2884
+ adapter: "xai",
2885
+ modelId,
2886
+ errorReason: stringifyErr(lastErr),
2887
+ fallbackSource: "registry-fallback"
2888
+ });
2889
+ return synthesizeNegotiatedCapabilitiesFromRegistry("xai", modelId, "registry-fallback");
2890
+ }
2891
+ async function negotiate(modelId) {
2892
+ const cached = cache.get(modelId);
2893
+ if (cached !== void 0 && cached.expiresAt > Date.now()) return cached.result;
2894
+ const existing = inflight.get(modelId);
2895
+ if (existing !== void 0) return existing;
2896
+ const fetchPromise = (async () => {
2897
+ try {
2898
+ const result = await fetchAndNegotiate(modelId);
2899
+ if (ttlMs > 0) cache.set(modelId, {
2900
+ result,
2901
+ expiresAt: Date.now() + ttlMs
2902
+ });
2903
+ return result;
2904
+ } finally {
2905
+ inflight.delete(modelId);
2906
+ }
2907
+ })();
2908
+ inflight.set(modelId, fetchPromise);
2909
+ return fetchPromise;
2910
+ }
2911
+ const inner = createOpenAICompatibleProvider({
2912
+ ...options,
2913
+ id: options.id ?? "xai",
2914
+ baseUrl: resolvedBaseUrl
2915
+ });
2916
+ const innerExecute = inner.execute;
2917
+ const innerExecuteStream = inner.executeStream;
2918
+ const wrappedExecute = innerExecute === void 0 ? void 0 : async (request) => {
2919
+ const response = await innerExecute(request);
2920
+ const reasoningTokens = reasoningTokensFromRawResponse(response.rawResponse);
2921
+ if (typeof reasoningTokens === "number" && response.usage !== void 0) {
2922
+ const inputTokens = response.usage.inputTokens ?? 0;
2923
+ const outputTokens = response.usage.outputTokens ?? 0;
2924
+ return {
2925
+ ...response,
2926
+ usage: {
2927
+ ...response.usage,
2928
+ totalTokens: inputTokens + outputTokens + reasoningTokens
2929
+ }
2930
+ };
2931
+ }
2932
+ return response;
2933
+ };
2934
+ const wrappedExecuteStream = innerExecuteStream === void 0 ? void 0 : async function* (request) {
2935
+ const stream = await innerExecuteStream(request);
2936
+ for await (const chunk of stream) {
2937
+ if (chunk.kind !== "complete") {
2938
+ yield chunk;
2939
+ continue;
2940
+ }
2941
+ const reasoningTokens = reasoningTokensFromRawResponse(chunk.rawResponse);
2942
+ if (typeof reasoningTokens === "number" && chunk.usage !== void 0) {
2943
+ const inputTokens = chunk.usage.inputTokens ?? 0;
2944
+ const outputTokens = chunk.usage.outputTokens ?? 0;
2945
+ yield {
2946
+ ...chunk,
2947
+ usage: {
2948
+ ...chunk.usage,
2949
+ totalTokens: inputTokens + outputTokens + reasoningTokens
2950
+ }
2951
+ };
2952
+ continue;
2953
+ }
2954
+ yield chunk;
2955
+ }
2956
+ };
2957
+ return {
2958
+ id: inner.id,
2959
+ kind: inner.kind,
2960
+ quirks: {
2961
+ supportsToolChoice: true,
2962
+ parallelToolCalls: true,
2963
+ structuredOutputs: true,
2964
+ responseFormatHonored: true,
2965
+ streamingDiverges: false,
2966
+ reasoningTokensReported: true,
2967
+ logprobsSupported: false
2968
+ },
2969
+ negotiateCapabilities: negotiate,
2970
+ ...inner.capabilities !== void 0 ? { capabilities: inner.capabilities } : {},
2971
+ ...wrappedExecute !== void 0 ? { execute: wrappedExecute } : {},
2972
+ ...wrappedExecuteStream !== void 0 ? { executeStream: wrappedExecuteStream } : {}
2973
+ };
2974
+ }
2975
+ function reasoningTokensFromRawResponse(rawResponse) {
2976
+ const direct = reasoningTokensFromUsage(rawResponse?.usage);
2977
+ if (direct !== void 0) return direct;
2978
+ if (!isRecord(rawResponse) || !Array.isArray(rawResponse.chunks)) return;
2979
+ for (let index = rawResponse.chunks.length - 1; index >= 0; index -= 1) {
2980
+ const chunk = rawResponse.chunks[index];
2981
+ const fromChunk = reasoningTokensFromUsage(chunk?.usage);
2982
+ if (fromChunk !== void 0) return fromChunk;
2983
+ }
2984
+ }
2985
+ function reasoningTokensFromUsage(usage) {
2986
+ if (!isRecord(usage) || !isRecord(usage.completion_tokens_details)) return;
2987
+ const value = usage.completion_tokens_details.reasoning_tokens;
2988
+ return typeof value === "number" ? value : void 0;
2989
+ }
2990
+ function isRecord(value) {
2991
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2992
+ }
2993
+ //#endregion
2994
+ //#region src/providers/streaming.ts
2995
+ async function collectStream(stream, options = {}) {
2996
+ const rawOutputs = {};
2997
+ const textParts = /* @__PURE__ */ new Map();
2998
+ const toolCalls = [];
2999
+ let artifactRefs;
3000
+ let usage;
3001
+ let normalizedUsage;
3002
+ let gateway;
3003
+ let finish;
3004
+ let rawResponse;
3005
+ let rawResponseProvided = false;
3006
+ let chunkCount = 0;
3007
+ for await (const chunk of stream) {
3008
+ chunkCount += 1;
3009
+ switch (chunk.kind) {
3010
+ case "text-delta": {
3011
+ const output = chunk.output ?? options.defaultOutput ?? "text";
3012
+ const parts = textParts.get(output);
3013
+ if (parts === void 0) textParts.set(output, [chunk.text]);
3014
+ else parts.push(chunk.text);
3015
+ break;
3016
+ }
3017
+ case "output":
3018
+ rawOutputs[chunk.output] = chunk.value;
3019
+ break;
3020
+ case "usage":
3021
+ if (chunk.usage !== void 0) usage = chunk.usage;
3022
+ if (chunk.normalizedUsage !== void 0) normalizedUsage = chunk.normalizedUsage;
3023
+ break;
3024
+ case "gateway":
3025
+ gateway = chunk.gateway;
3026
+ break;
3027
+ case "tool-call":
3028
+ toolCalls.push(chunk.toolCall);
3029
+ break;
3030
+ case "complete":
3031
+ if (chunk.rawOutputs !== void 0) Object.assign(rawOutputs, chunk.rawOutputs);
3032
+ if (chunk.artifactRefs !== void 0) artifactRefs = chunk.artifactRefs;
3033
+ if (chunk.usage !== void 0) usage = chunk.usage;
3034
+ if (chunk.normalizedUsage !== void 0) normalizedUsage = chunk.normalizedUsage;
3035
+ if (chunk.gateway !== void 0) gateway = chunk.gateway;
3036
+ if (chunk.toolCalls !== void 0) toolCalls.push(...chunk.toolCalls);
3037
+ if (chunk.finish !== void 0) finish = chunk.finish;
3038
+ if ("rawResponse" in chunk) {
3039
+ rawResponse = chunk.rawResponse;
3040
+ rawResponseProvided = true;
3041
+ }
3042
+ break;
3043
+ }
3044
+ }
3045
+ for (const [output, parts] of textParts) if (!(output in rawOutputs)) rawOutputs[output] = parts.join("");
3046
+ return {
3047
+ rawOutputs,
3048
+ ...artifactRefs !== void 0 ? { artifactRefs } : {},
3049
+ ...usage !== void 0 ? { usage } : {},
3050
+ ...normalizedUsage !== void 0 ? { normalizedUsage } : {},
3051
+ ...toolCalls.length > 0 ? { toolCalls } : {},
3052
+ ...gateway !== void 0 ? { gateway } : {},
3053
+ ...finish !== void 0 ? { finish } : {},
3054
+ rawResponse: rawResponseProvided ? rawResponse : {
3055
+ kind: "lattice-stream-summary",
3056
+ chunkCount,
3057
+ outputNames: Object.keys(rawOutputs)
3058
+ }
3059
+ };
3060
+ }
3061
+ //#endregion
3062
+ //#region src/prompts/scaffolds.ts
3063
+ const PROMPT_SCAFFOLD_VERSION = "lattice.prompt-scaffold/v1";
3064
+ const PROMPT_STRATEGIES = [
3065
+ "frontier",
3066
+ "mid_tier",
3067
+ "open_weight",
3068
+ "reasoning",
3069
+ "local"
3070
+ ];
3071
+ const JSON_SERIALIZATION_ERRORS = {
3072
+ schema: "getStructuredOutputContract: schema must be JSON-serializable for deterministic prompt scaffolds.",
3073
+ tools: "getToolUseContract: tools must be JSON-serializable for deterministic prompt scaffolds."
3074
+ };
3075
+ const STRATEGY_INSTRUCTIONS = {
3076
+ frontier: {
3077
+ structuredOutput: ["Return only content that satisfies the contract.", "Do not include prose before or after the structured output."],
3078
+ toolUse: ["Use a tool only when the task requires an external action or lookup.", "Return a normal answer when the user request can be completed without a tool."]
3079
+ },
3080
+ mid_tier: {
3081
+ structuredOutput: ["Treat the contract as instructions, not as prose to repeat.", "Return the requested answer in the shape required by the contract."],
3082
+ toolUse: ["The tool definitions are available actions, not answer text.", "Call only a listed tool, and only with arguments that match its definition."]
3083
+ },
3084
+ open_weight: {
3085
+ structuredOutput: [
3086
+ "The contract below is an instruction, not text to output.",
3087
+ "Do not answer with the schema, envelope, or any field name unless that field belongs in the final user-visible JSON.",
3088
+ "Bad: {\"summary\":\"Greeted the user.\"} when the user asked for a natural-language reply.",
3089
+ "Good: Greeted the user."
3090
+ ],
3091
+ toolUse: [
3092
+ "The tool list below is action metadata, not text to output.",
3093
+ "Do not copy the tool descriptor into the final answer.",
3094
+ "If no listed tool is needed, answer normally without fabricating a tool call."
3095
+ ]
3096
+ },
3097
+ reasoning: {
3098
+ structuredOutput: ["Do not expose hidden reasoning, scratchpad text, or analysis in the final answer.", "Return only the final content that satisfies the contract."],
3099
+ toolUse: ["Keep tool selection separate from hidden reasoning.", "Do not include scratchpad text when explaining whether a tool was used."]
3100
+ },
3101
+ local: {
3102
+ structuredOutput: ["Do not copy the contract, chat template, or wrapper text into the answer.", "Return the requested answer directly in the required shape."],
3103
+ toolUse: ["Do not invent tool names or arguments.", "If the task does not require a listed tool, answer directly."]
3104
+ }
3105
+ };
3106
+ function canonicalPromptJson(value, payload) {
3107
+ const errorMessage = JSON_SERIALIZATION_ERRORS[payload];
3108
+ let json;
3109
+ try {
3110
+ json = canonicalize(value);
3111
+ if (json === void 0) throw new Error(errorMessage);
3112
+ JSON.parse(json);
3113
+ } catch {
3114
+ throw new Error(errorMessage);
3115
+ }
3116
+ return json;
3117
+ }
3118
+ function renderPromptScaffold(strategy, purpose, instructions, payloadHeading, payload) {
3119
+ return [
3120
+ `Lattice Prompt Scaffold: ${PROMPT_SCAFFOLD_VERSION}`,
3121
+ `Strategy: ${strategy}`,
3122
+ `Purpose: ${purpose}`,
3123
+ "",
3124
+ ...instructions,
3125
+ "",
3126
+ `${payloadHeading}:`,
3127
+ payload
3128
+ ].join("\n");
3129
+ }
3130
+ function getStructuredOutputContract(strategy, schema) {
3131
+ return renderPromptScaffold(strategy, "structured-output", STRATEGY_INSTRUCTIONS[strategy].structuredOutput, "Contract", canonicalPromptJson(schema, "schema"));
3132
+ }
3133
+ function getToolUseContract(strategy, tools) {
3134
+ return renderPromptScaffold(strategy, "tool-use", STRATEGY_INSTRUCTIONS[strategy].toolUse, "Tools", canonicalPromptJson(tools, "tools"));
3135
+ }
3136
+ //#endregion
3137
+ export { stripReasoningTags as _, collectStream as a, NoPublicUrlEgressError as b, createLmStudioProvider as c, createFakeProvider as d, createAnthropicProvider as f, stripChatTemplateArtifacts as g, createOpenAIProvider as h, getToolUseContract as i, createLiteLLMProvider as l, createOpenAICompatibleProvider as m, PROMPT_STRATEGIES as n, createXaiProvider as o, createAISdkProvider as p, getStructuredOutputContract as r, createOpenRouterProvider as s, PROMPT_SCAFFOLD_VERSION as t, createGeminiProvider as u, unwrapInternalEnvelope as v, mediaTypeForArtifact as x, createRunEvent as y };
3138
+
3139
+ //# sourceMappingURL=scaffolds-ekPIlBeU.js.map