@nexface/agent 0.1.1-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/THIRD_PARTY_LICENSES.txt +245 -0
  2. package/dist/agent.d.ts +19 -0
  3. package/dist/agent.js +1509 -0
  4. package/dist/agent.js.map +1 -0
  5. package/dist/browser-prompt.d.ts +5 -0
  6. package/dist/browser-prompt.generated.d.ts +1 -0
  7. package/dist/browser-prompt.generated.js +50 -0
  8. package/dist/browser-prompt.generated.js.map +1 -0
  9. package/dist/browser-prompt.js +9 -0
  10. package/dist/browser-prompt.js.map +1 -0
  11. package/dist/errors.d.ts +21 -0
  12. package/dist/errors.js +55 -0
  13. package/dist/errors.js.map +1 -0
  14. package/dist/index.d.ts +6 -0
  15. package/dist/index.js +4 -0
  16. package/dist/index.js.map +1 -0
  17. package/dist/model/binding.d.ts +22 -0
  18. package/dist/model/binding.js +31 -0
  19. package/dist/model/binding.js.map +1 -0
  20. package/dist/model/error.d.ts +6 -0
  21. package/dist/model/error.js +10 -0
  22. package/dist/model/error.js.map +1 -0
  23. package/dist/model/internal-adapter.d.ts +8 -0
  24. package/dist/model/internal-adapter.js +13 -0
  25. package/dist/model/internal-adapter.js.map +1 -0
  26. package/dist/model/types.d.ts +9 -0
  27. package/dist/model/types.js +2 -0
  28. package/dist/model/types.js.map +1 -0
  29. package/dist/models/885.js +630 -0
  30. package/dist/models/956.js +5 -0
  31. package/dist/models/_chunks/35-e1813138.js +8304 -0
  32. package/dist/models/_chunks/879-7e580bb2.js +1189 -0
  33. package/dist/models/_chunks/958-bedced75.js +453 -0
  34. package/dist/models/_chunks/anthropic-messages~1-a4b25b48.js +8057 -0
  35. package/dist/models/_chunks/deferred-tools-90f3c977.js +37 -0
  36. package/dist/models/_chunks/error-body-8bee35c2.js +134 -0
  37. package/dist/models/_chunks/google-generative-ai~1-78bb2822.js +22104 -0
  38. package/dist/models/_chunks/openai-completions~1-7677fb83.js +1285 -0
  39. package/dist/models/_chunks/openai-responses~1-cc0a71cd.js +958 -0
  40. package/dist/models/anthropic-messages.d.ts +4 -0
  41. package/dist/models/anthropic-messages.js +32 -0
  42. package/dist/models/google-generative-ai.d.ts +4 -0
  43. package/dist/models/google-generative-ai.js +32 -0
  44. package/dist/models/openai-completions.d.ts +5 -0
  45. package/dist/models/openai-completions.js +16 -0
  46. package/dist/models/openai-responses.d.ts +5 -0
  47. package/dist/models/openai-responses.js +16 -0
  48. package/dist/models/rslib-runtime.js +59 -0
  49. package/dist/models/types.d.ts +26 -0
  50. package/dist/tool-bridge.d.ts +36 -0
  51. package/dist/tool-bridge.js +299 -0
  52. package/dist/tool-bridge.js.map +1 -0
  53. package/dist/types.d.ts +129 -0
  54. package/dist/types.js +2 -0
  55. package/dist/types.js.map +1 -0
  56. package/package.json +55 -0
@@ -0,0 +1,1189 @@
1
+
2
+
3
+
4
+
5
+
6
+
7
+
8
+ function mergeHeaders(base, override) {
9
+ if (!base && !override)
10
+ return undefined;
11
+ const merged = { ...base };
12
+ for (const [name, value] of Object.entries(override ?? {})) {
13
+ const lowerName = name.toLowerCase();
14
+ for (const existingName of Object.keys(merged)) {
15
+ if (existingName.toLowerCase() === lowerName)
16
+ delete merged[existingName];
17
+ }
18
+ merged[name] = value;
19
+ }
20
+ return merged;
21
+ }
22
+ class ModelsImpl {
23
+ providers = new Map();
24
+ credentials;
25
+ modelsStore;
26
+ authContext;
27
+ refreshGenerations = new Map();
28
+ refreshControllers = new Map();
29
+ publicationChains = new Map();
30
+ constructor(options) {
31
+ this.credentials = options?.credentials ?? new InMemoryCredentialStore();
32
+ this.modelsStore = options?.modelsStore ?? new InMemoryModelsStore();
33
+ this.authContext = options?.authContext ?? defaultAuthContext();
34
+ }
35
+ setProvider(provider) {
36
+ this.supersedeProviderRefresh(provider.id);
37
+ this.providers.set(provider.id, provider);
38
+ }
39
+ deleteProvider(id) {
40
+ this.supersedeProviderRefresh(id);
41
+ this.providers.delete(id);
42
+ }
43
+ clearProviders() {
44
+ for (const id of new Set([...this.providers.keys(), ...this.refreshControllers.keys()])) {
45
+ this.supersedeProviderRefresh(id);
46
+ }
47
+ this.providers.clear();
48
+ }
49
+ getProviders() {
50
+ return Array.from(this.providers.values());
51
+ }
52
+ getProvider(id) {
53
+ return this.providers.get(id);
54
+ }
55
+ getModels(provider) {
56
+ if (provider !== undefined) {
57
+ const entry = this.providers.get(provider);
58
+ if (!entry)
59
+ return [];
60
+ try {
61
+ return entry.getModels();
62
+ }
63
+ catch {
64
+ return [];
65
+ }
66
+ }
67
+ const models = [];
68
+ for (const entry of this.providers.values()) {
69
+ try {
70
+ models.push(...entry.getModels());
71
+ }
72
+ catch {
73
+ // Best-effort: ill-behaved providers yield no models.
74
+ }
75
+ }
76
+ return models;
77
+ }
78
+ getModel(provider, id) {
79
+ return this.getModels(provider).find((model) => model.id === id);
80
+ }
81
+ supersedeProviderRefresh(providerId) {
82
+ const generation = (this.refreshGenerations.get(providerId) ?? 0) + 1;
83
+ this.refreshGenerations.set(providerId, generation);
84
+ const previous = this.refreshControllers.get(providerId);
85
+ if (previous) {
86
+ this.refreshControllers.delete(providerId);
87
+ previous.abort();
88
+ }
89
+ return generation;
90
+ }
91
+ beginProviderRefresh(providerId) {
92
+ const generation = this.supersedeProviderRefresh(providerId);
93
+ const controller = new AbortController();
94
+ this.refreshControllers.set(providerId, controller);
95
+ return { generation, controller };
96
+ }
97
+ publishProviderModels(providerId, generation, signal, publication) {
98
+ const previous = this.publicationChains.get(providerId) ?? Promise.resolve();
99
+ const queued = (async () => {
100
+ await previous.catch(() => { });
101
+ if (signal.aborted || this.refreshGenerations.get(providerId) !== generation)
102
+ return false;
103
+ if (publication.persist === null) {
104
+ await this.modelsStore.delete(providerId, { signal });
105
+ }
106
+ else if (publication.persist !== undefined) {
107
+ await this.modelsStore.write(providerId, structuredClone(publication.persist), { signal });
108
+ }
109
+ if (signal.aborted || this.refreshGenerations.get(providerId) !== generation)
110
+ return false;
111
+ publication.update?.();
112
+ return true;
113
+ })();
114
+ const tail = queued.catch(() => { });
115
+ this.publicationChains.set(providerId, tail);
116
+ void tail.then(() => {
117
+ if (this.publicationChains.get(providerId) === tail)
118
+ this.publicationChains.delete(providerId);
119
+ });
120
+ return raceWithAbortSignal(queued, signal);
121
+ }
122
+ async runProviderRefreshPhase(provider, credential, allowNetwork, force, generation, signal) {
123
+ const stored = await this.modelsStore.read(provider.id, { signal });
124
+ await provider.refreshModels({
125
+ credential,
126
+ stored: stored ? structuredClone(stored) : undefined,
127
+ publish: (publication) => this.publishProviderModels(provider.id, generation, signal, publication),
128
+ allowNetwork,
129
+ force: allowNetwork ? force : undefined,
130
+ signal,
131
+ });
132
+ }
133
+ async refresh(options = {}) {
134
+ const allowNetwork = options.allowNetwork ?? true;
135
+ const callerSignal = operationSignal(options.signal);
136
+ const errors = new Map();
137
+ if (callerSignal.aborted)
138
+ return { aborted: true, errors };
139
+ const selected = options.providers ? new Set(options.providers) : undefined;
140
+ const refreshable = Array.from(this.providers.values()).filter((provider) => provider.refreshModels !== undefined && (!selected || selected.has(provider.id)));
141
+ const refresh = Promise.all(refreshable.map(async (provider) => {
142
+ const { generation, controller } = this.beginProviderRefresh(provider.id);
143
+ const signal = AbortSignal.any([callerSignal, controller.signal]);
144
+ const operation = (async () => {
145
+ let storedCredential;
146
+ let credentialError;
147
+ try {
148
+ storedCredential = await this.readCredential(provider.id, signal);
149
+ }
150
+ catch (error) {
151
+ credentialError = error;
152
+ }
153
+ // Restore cached provider state before auth resolution or network access.
154
+ await this.runProviderRefreshPhase(provider, storedCredential, false, undefined, generation, signal);
155
+ if (credentialError !== undefined)
156
+ throw credentialError;
157
+ if (!allowNetwork || signal.aborted)
158
+ return;
159
+ const credential = await this.resolveRefreshCredential(provider, storedCredential, signal);
160
+ if (!credential)
161
+ return;
162
+ await this.runProviderRefreshPhase(provider, credential, true, options.force, generation, signal);
163
+ })();
164
+ try {
165
+ await raceWithAbortSignal(operation, signal);
166
+ }
167
+ catch (error) {
168
+ if (!signal.aborted) {
169
+ errors.set(provider.id, error instanceof Error
170
+ ? error
171
+ : new ModelsError("model_source", `Model refresh failed for ${provider.id}`, { cause: error }));
172
+ }
173
+ }
174
+ finally {
175
+ if (this.refreshControllers.get(provider.id) === controller) {
176
+ this.refreshControllers.delete(provider.id);
177
+ }
178
+ }
179
+ }));
180
+ try {
181
+ await raceWithAbortSignal(refresh, callerSignal);
182
+ }
183
+ catch (error) {
184
+ if (!callerSignal.aborted)
185
+ throw error;
186
+ }
187
+ return { aborted: callerSignal.aborted, errors: new Map(errors) };
188
+ }
189
+ async resolveRefreshCredential(provider, stored, signal) {
190
+ if (stored?.type === "oauth") {
191
+ const oauth = provider.auth.oauth;
192
+ if (!oauth)
193
+ return undefined;
194
+ if (Date.now() < stored.expires)
195
+ return stored;
196
+ if (signal.aborted)
197
+ return undefined;
198
+ const post = await this.credentials.modify(provider.id, async (current) => {
199
+ if (current?.type !== "oauth" || Date.now() < current.expires)
200
+ return undefined;
201
+ return oauth.refresh(current, signal);
202
+ }, { signal });
203
+ return post?.type === "oauth" ? post : undefined;
204
+ }
205
+ const apiKey = provider.auth.apiKey;
206
+ if (!apiKey)
207
+ return undefined;
208
+ const credential = stored?.type === "api_key" ? stored : undefined;
209
+ const result = await apiKey.resolve({ ctx: this.authContext, credential, signal });
210
+ if (!result)
211
+ return undefined;
212
+ return { type: "api_key", key: result.auth.apiKey, env: result.env };
213
+ }
214
+ async readCredential(providerId, signal) {
215
+ try {
216
+ return await this.credentials.read(providerId, { signal });
217
+ }
218
+ catch (error) {
219
+ throw new ModelsError("auth", `Credential store read failed for ${providerId}`, { cause: error });
220
+ }
221
+ }
222
+ async checkProviderAuth(provider, credential, signal) {
223
+ if (credential?.type === "oauth") {
224
+ return provider.auth.oauth ? { source: "OAuth", type: "oauth" } : undefined;
225
+ }
226
+ const apiKey = provider.auth.apiKey;
227
+ if (!apiKey)
228
+ return undefined;
229
+ if (apiKey.check) {
230
+ try {
231
+ return await apiKey.check({
232
+ ctx: this.authContext,
233
+ credential: credential?.type === "api_key" ? credential : undefined,
234
+ signal,
235
+ });
236
+ }
237
+ catch (error) {
238
+ throw new ModelsError("auth", `API key auth check failed for provider ${provider.id}`, { cause: error });
239
+ }
240
+ }
241
+ const resolution = await resolveProviderAuth(provider, this.credentials, this.authContext, { signal });
242
+ return resolution ? { source: resolution.source, type: "api_key" } : undefined;
243
+ }
244
+ checkAuth(providerId, options) {
245
+ const signal = operationSignal(options?.signal);
246
+ const check = (async () => {
247
+ signal.throwIfAborted();
248
+ const provider = this.providers.get(providerId);
249
+ if (!provider)
250
+ return undefined;
251
+ return this.checkProviderAuth(provider, await this.readCredential(providerId, signal), signal);
252
+ })();
253
+ return raceWithAbortSignal(check, signal);
254
+ }
255
+ getAvailable(providerId, options) {
256
+ const signal = operationSignal(options?.signal);
257
+ const available = (async () => {
258
+ signal.throwIfAborted();
259
+ const providers = providerId
260
+ ? [this.providers.get(providerId)].filter((entry) => entry !== undefined)
261
+ : this.getProviders();
262
+ const checks = await Promise.all(providers.map(async (provider) => {
263
+ const credential = await this.readCredential(provider.id, signal);
264
+ return { provider, credential, auth: await this.checkProviderAuth(provider, credential, signal) };
265
+ }));
266
+ return checks.flatMap(({ provider, credential, auth }) => {
267
+ if (!auth)
268
+ return [];
269
+ const models = provider.getModels();
270
+ return provider.filterModels?.(models, credential) ?? models;
271
+ });
272
+ })();
273
+ return raceWithAbortSignal(available, signal);
274
+ }
275
+ async getAuth(providerOrModel, overrides) {
276
+ const signal = operationSignal(overrides?.signal);
277
+ const providerId = typeof providerOrModel === "string" ? providerOrModel : providerOrModel.provider;
278
+ const provider = this.providers.get(providerId);
279
+ if (!provider)
280
+ return undefined;
281
+ const result = await resolveProviderAuth(provider, this.credentials, this.authContext, { ...overrides, signal });
282
+ if (!result || typeof providerOrModel === "string" || !providerOrModel.headers)
283
+ return result;
284
+ return {
285
+ ...result,
286
+ auth: {
287
+ ...result.auth,
288
+ headers: mergeHeaders(result.auth.headers, providerOrModel.headers),
289
+ },
290
+ };
291
+ }
292
+ async login(providerId, type, interaction) {
293
+ const signal = operationSignal(interaction.signal);
294
+ signal.throwIfAborted();
295
+ const provider = this.providers.get(providerId);
296
+ if (!provider)
297
+ throw new ModelsError("provider", `Unknown provider: ${providerId}`);
298
+ const method = type === "oauth" ? provider.auth.oauth : provider.auth.apiKey;
299
+ if (!method?.login) {
300
+ throw new ModelsError("auth", `${provider.name} does not support ${type} login`);
301
+ }
302
+ const loginOperation = method.login({ ...interaction, signal });
303
+ const credential = await raceWithAbortSignal(loginOperation, signal);
304
+ let mutationStarted = false;
305
+ let markMutationStarted;
306
+ const started = new Promise((resolve) => {
307
+ markMutationStarted = resolve;
308
+ });
309
+ const mutation = this.credentials.modify(providerId, async () => {
310
+ mutationStarted = true;
311
+ markMutationStarted?.();
312
+ return credential;
313
+ }, { signal });
314
+ void mutation.catch(() => { });
315
+ try {
316
+ await new Promise((resolve, reject) => {
317
+ const onAbort = () => {
318
+ if (!mutationStarted)
319
+ reject(signal.reason);
320
+ };
321
+ signal.addEventListener("abort", onAbort, { once: true });
322
+ void Promise.race([started, mutation]).then(() => {
323
+ signal.removeEventListener("abort", onAbort);
324
+ resolve();
325
+ }, (error) => {
326
+ signal.removeEventListener("abort", onAbort);
327
+ reject(error);
328
+ });
329
+ if (signal.aborted)
330
+ onAbort();
331
+ });
332
+ await mutation;
333
+ }
334
+ catch (error) {
335
+ signal.throwIfAborted();
336
+ throw new ModelsError("auth", `Credential store modify failed for ${providerId}`, { cause: error });
337
+ }
338
+ return credential;
339
+ }
340
+ async logout(providerId, options) {
341
+ const signal = operationSignal(options?.signal);
342
+ signal.throwIfAborted();
343
+ try {
344
+ await this.credentials.delete(providerId, { signal });
345
+ }
346
+ catch (error) {
347
+ signal.throwIfAborted();
348
+ throw new ModelsError("auth", `Credential store delete failed for ${providerId}`, { cause: error });
349
+ }
350
+ }
351
+ requireProvider(model) {
352
+ const provider = this.providers.get(model.provider);
353
+ if (!provider) {
354
+ throw new ModelsError("provider", `Unknown provider: ${model.provider}`);
355
+ }
356
+ return provider;
357
+ }
358
+ async applyAuth(model, options) {
359
+ this.requireProvider(model);
360
+ const resolution = await this.getAuth(model, {
361
+ apiKey: options?.apiKey,
362
+ env: options?.env,
363
+ signal: options?.signal,
364
+ });
365
+ if (!resolution) {
366
+ throw new ModelsError("auth", `Provider is not configured: ${model.provider}`);
367
+ }
368
+ const auth = resolution.auth;
369
+ // Explicit request options win per-field; the Models-only transform runs last.
370
+ const apiKey = options?.apiKey ?? auth.apiKey;
371
+ let headers = mergeHeaders(auth.headers, options?.headers);
372
+ if (options?.transformHeaders)
373
+ headers = await options.transformHeaders(headers ?? {});
374
+ const env = resolution.env || options?.env ? { ...(resolution.env ?? {}), ...(options?.env ?? {}) } : undefined;
375
+ const requestModel = auth.baseUrl ? { ...model, baseUrl: auth.baseUrl } : model;
376
+ const { transformHeaders: _transformHeaders, ...providerOptions } = options ?? {};
377
+ const requestOptions = { ...providerOptions, apiKey, headers, env };
378
+ return { requestModel, requestOptions };
379
+ }
380
+ stream(model, context, options) {
381
+ return lazyStream(model, async () => {
382
+ const provider = this.requireProvider(model);
383
+ const { requestModel, requestOptions } = await this.applyAuth(model, options);
384
+ return provider.stream(requestModel, context, requestOptions);
385
+ });
386
+ }
387
+ async complete(model, context, options) {
388
+ return this.stream(model, context, options).result();
389
+ }
390
+ streamSimple(model, context, options) {
391
+ return lazyStream(model, async () => {
392
+ const provider = this.requireProvider(model);
393
+ const { requestModel, requestOptions } = await this.applyAuth(model, options);
394
+ return provider.streamSimple(requestModel, context, requestOptions);
395
+ });
396
+ }
397
+ async completeSimple(model, context, options) {
398
+ return this.streamSimple(model, context, options).result();
399
+ }
400
+ async fetchDeferred(model, handle, options) {
401
+ return lazyStream(model, async () => {
402
+ const provider = this.requireProvider(model);
403
+ if (!provider.fetchDeferred) {
404
+ throw new ModelsError("provider", `Provider ${model.provider} does not support deferred responses`);
405
+ }
406
+ const { requestModel, requestOptions } = await this.applyAuth(model, options);
407
+ return provider.fetchDeferred(requestModel, handle, requestOptions);
408
+ }).result();
409
+ }
410
+ async cancelDeferred(model, handle, options) {
411
+ const provider = this.requireProvider(model);
412
+ if (!provider.cancelDeferred) {
413
+ throw new ModelsError("provider", `Provider ${model.provider} does not support deferred responses`);
414
+ }
415
+ const { requestModel, requestOptions } = await this.applyAuth(model, options);
416
+ await provider.cancelDeferred(requestModel, handle, requestOptions);
417
+ }
418
+ }
419
+ function createModels(options) {
420
+ return new ModelsImpl(options);
421
+ }
422
+ /**
423
+ * Builds a provider from parts. Built-in provider factories and models.json
424
+ * custom providers both go through this. A single `api` streams all models;
425
+ * an `api` map dispatches on `model.api`, and a model whose api has no entry
426
+ * produces a stream error.
427
+ */
428
+ function createProvider(input) {
429
+ const baselineModels = input.models;
430
+ let dynamicModels = [];
431
+ const fetchModels = input.fetchModels;
432
+ const currentModels = () => {
433
+ const merged = [...baselineModels];
434
+ for (const model of dynamicModels) {
435
+ const index = merged.findIndex((entry) => entry.id === model.id);
436
+ if (index >= 0)
437
+ merged[index] = model;
438
+ else
439
+ merged.push(model);
440
+ }
441
+ return merged;
442
+ };
443
+ const single = typeof input.api.stream === "function" ? input.api : undefined;
444
+ const byApi = single ? undefined : input.api;
445
+ const apiFor = (model) => single ?? byApi?.[model.api];
446
+ const dispatch = (model, run) => {
447
+ const streams = apiFor(model);
448
+ if (!streams) {
449
+ return lazyStream(model, async () => {
450
+ throw new ModelsError("stream", `Provider ${input.id} has no API implementation for "${model.api}"`);
451
+ });
452
+ }
453
+ return run(streams);
454
+ };
455
+ const provider = {
456
+ id: input.id,
457
+ name: input.name ?? input.id,
458
+ baseUrl: input.baseUrl,
459
+ headers: input.headers,
460
+ auth: input.auth,
461
+ getModels: currentModels,
462
+ refreshModels: fetchModels
463
+ ? async (context) => {
464
+ if (context.stored) {
465
+ const restored = context.stored.models
466
+ .filter((model) => model.provider === input.id)
467
+ .map((model) => model);
468
+ if (!(await context.publish({
469
+ update: () => {
470
+ dynamicModels = restored;
471
+ },
472
+ }))) {
473
+ return;
474
+ }
475
+ }
476
+ if (!context.allowNetwork || context.signal.aborted)
477
+ return;
478
+ const refreshed = await fetchModels(context);
479
+ if (context.signal.aborted)
480
+ return;
481
+ await context.publish({
482
+ persist: { models: refreshed, checkedAt: Date.now() },
483
+ update: () => {
484
+ dynamicModels = refreshed;
485
+ },
486
+ });
487
+ }
488
+ : undefined,
489
+ filterModels: input.filterModels,
490
+ stream: (model, context, options) => dispatch(model, (streams) => streams.stream(model, context, options)),
491
+ streamSimple: (model, context, options) => dispatch(model, (streams) => streams.streamSimple(model, context, options)),
492
+ };
493
+ const streams = single ? [single] : Object.values(byApi ?? {}).filter((entry) => entry !== undefined);
494
+ if (streams.some((entry) => entry.fetchDeferred !== undefined)) {
495
+ provider.fetchDeferred = (model, handle, options) => lazyStream(model, async () => {
496
+ const implementation = apiFor(model);
497
+ if (!implementation?.fetchDeferred) {
498
+ throw new ModelsError("provider", `Provider ${input.id} does not support deferred responses for "${model.api}"`);
499
+ }
500
+ return implementation.fetchDeferred(model, handle, options);
501
+ });
502
+ }
503
+ if (streams.some((entry) => entry.cancelDeferred !== undefined)) {
504
+ provider.cancelDeferred = async (model, handle, options) => {
505
+ const implementation = apiFor(model);
506
+ if (!implementation?.cancelDeferred) {
507
+ throw new ModelsError("provider", `Provider ${input.id} cannot cancel deferred responses for "${model.api}"`);
508
+ }
509
+ await implementation.cancelDeferred(model, handle, options);
510
+ };
511
+ }
512
+ return provider;
513
+ }
514
+ /**
515
+ * Runtime-checked narrowing for dynamically looked-up models:
516
+ *
517
+ * ```ts
518
+ * const model = models.getModel("anthropic", "claude-opus-4-7");
519
+ * if (model && hasApi(model, "anthropic-messages")) {
520
+ * // model: Model<"anthropic-messages">, stream options fully typed
521
+ * }
522
+ * ```
523
+ */
524
+ function hasApi(model, api) {
525
+ return model.api === api;
526
+ }
527
+ function calculateCost(model, usage) {
528
+ const inputTokens = usage.input + usage.cacheRead + usage.cacheWrite;
529
+ let rates = model.cost;
530
+ let matchedThreshold = -1;
531
+ for (const tier of model.cost.tiers ?? []) {
532
+ if (inputTokens > tier.inputTokensAbove && tier.inputTokensAbove > matchedThreshold) {
533
+ rates = tier;
534
+ matchedThreshold = tier.inputTokensAbove;
535
+ }
536
+ }
537
+ // Anthropic charges 2x base input for 1h cache writes.
538
+ const longWrite = usage.cacheWrite1h ?? 0;
539
+ const shortWrite = usage.cacheWrite - longWrite;
540
+ usage.cost.input = (rates.input / 1000000) * usage.input;
541
+ usage.cost.output = (rates.output / 1000000) * usage.output;
542
+ usage.cost.cacheRead = (rates.cacheRead / 1000000) * usage.cacheRead;
543
+ usage.cost.cacheWrite = (rates.cacheWrite * shortWrite + rates.input * 2 * longWrite) / 1000000;
544
+ usage.cost.total = usage.cost.input + usage.cost.output + usage.cost.cacheRead + usage.cost.cacheWrite;
545
+ return usage.cost;
546
+ }
547
+ const EXTENDED_THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
548
+ function getSupportedThinkingLevels(model) {
549
+ if (!model.reasoning)
550
+ return ["off"];
551
+ return EXTENDED_THINKING_LEVELS.filter((level) => {
552
+ const mapped = model.thinkingLevelMap?.[level];
553
+ if (mapped === null)
554
+ return false;
555
+ if (level === "xhigh" || level === "max")
556
+ return mapped !== undefined;
557
+ return true;
558
+ });
559
+ }
560
+ function clampThinkingLevel(model, level) {
561
+ const availableLevels = getSupportedThinkingLevels(model);
562
+ if (availableLevels.includes(level))
563
+ return level;
564
+ const requestedIndex = EXTENDED_THINKING_LEVELS.indexOf(level);
565
+ if (requestedIndex === -1)
566
+ return availableLevels[0] ?? "off";
567
+ for (let i = requestedIndex; i < EXTENDED_THINKING_LEVELS.length; i++) {
568
+ const candidate = EXTENDED_THINKING_LEVELS[i];
569
+ if (availableLevels.includes(candidate))
570
+ return candidate;
571
+ }
572
+ for (let i = requestedIndex - 1; i >= 0; i--) {
573
+ const candidate = EXTENDED_THINKING_LEVELS[i];
574
+ if (availableLevels.includes(candidate))
575
+ return candidate;
576
+ }
577
+ return availableLevels[0] ?? "off";
578
+ }
579
+ /**
580
+ * Check if two models are equal by comparing both their id and provider.
581
+ * Returns false if either model is null or undefined.
582
+ */
583
+ function modelsAreEqual(a, b) {
584
+ if (!a || !b)
585
+ return false;
586
+ return a.id === b.id && a.provider === b.provider;
587
+ }
588
+ //# sourceMappingURL=models.js.map
589
+ function headersToRecord(headers) {
590
+ const result = {};
591
+ for (const [key, value] of headers.entries()) {
592
+ result[key] = value;
593
+ }
594
+ return result;
595
+ }
596
+ function providerHeadersToRecord(headers) {
597
+ if (!headers)
598
+ return undefined;
599
+ const result = {};
600
+ for (const [key, value] of Object.entries(headers)) {
601
+ if (value !== null)
602
+ result[key] = value;
603
+ }
604
+ return Object.keys(result).length > 0 ? result : undefined;
605
+ }
606
+ //# sourceMappingURL=headers.js.map
607
+ const DEFAULT_MAX_RETRY_DELAY_MS = 60_000;
608
+ function isProviderError(error) {
609
+ if (!(error instanceof Error) || !("status" in error) || !("headers" in error))
610
+ return false;
611
+ return ((error.status === undefined || typeof error.status === "number") &&
612
+ (error.headers === undefined || error.headers instanceof Headers));
613
+ }
614
+ /** Mirrors the pinned OpenAI/Anthropic SDK retry policy; review when either SDK is upgraded. */
615
+ function isRetryableProviderError(error) {
616
+ const shouldRetry = error.headers?.get("x-should-retry");
617
+ if (shouldRetry === "true")
618
+ return true;
619
+ if (shouldRetry === "false")
620
+ return false;
621
+ if (error.status === undefined)
622
+ return true;
623
+ return (error.status === 408 ||
624
+ error.status === 409 ||
625
+ error.status === 429 ||
626
+ (typeof error.status === "number" && error.status >= 500));
627
+ }
628
+ function validateServerRetryDelayMs(delayMs, maxRetryDelayMs, providerErrorMessage) {
629
+ const maxDelayMs = maxRetryDelayMs ?? DEFAULT_MAX_RETRY_DELAY_MS;
630
+ if (maxDelayMs > 0 && delayMs > maxDelayMs) {
631
+ throw new Error(`Server requested ${Math.ceil(delayMs / 1000)}s retry delay (max: ${Math.ceil(maxDelayMs / 1000)}s). ${providerErrorMessage}`);
632
+ }
633
+ return delayMs;
634
+ }
635
+ function getRetryDelayMs(error, retryIndex, maxRetryDelayMs) {
636
+ const retryAfterMs = error.headers?.get("retry-after-ms");
637
+ if (retryAfterMs) {
638
+ const value = Number.parseFloat(retryAfterMs);
639
+ if (!Number.isNaN(value))
640
+ return validateServerRetryDelayMs(value, maxRetryDelayMs, error.message);
641
+ }
642
+ const retryAfter = error.headers?.get("retry-after");
643
+ if (retryAfter) {
644
+ const seconds = Number.parseFloat(retryAfter);
645
+ const delayMs = Number.isNaN(seconds) ? Date.parse(retryAfter) - Date.now() : seconds * 1000;
646
+ return validateServerRetryDelayMs(delayMs, maxRetryDelayMs, error.message);
647
+ }
648
+ const exponentialDelay = Math.min(0.5 * 2 ** retryIndex, 8) * 1000;
649
+ return exponentialDelay * (1 - Math.random() * 0.25);
650
+ }
651
+ function createAbortError() {
652
+ const error = new Error("Request aborted");
653
+ error.name = "AbortError";
654
+ return error;
655
+ }
656
+ function abortableSleep(ms, signal) {
657
+ return new Promise((resolve, reject) => {
658
+ if (signal?.aborted) {
659
+ reject(createAbortError());
660
+ return;
661
+ }
662
+ const onAbort = () => {
663
+ clearTimeout(timeout);
664
+ reject(createAbortError());
665
+ };
666
+ const timeout = setTimeout(() => {
667
+ signal?.removeEventListener("abort", onAbort);
668
+ resolve();
669
+ }, Math.max(0, ms));
670
+ signal?.addEventListener("abort", onAbort, { once: true });
671
+ });
672
+ }
673
+ /**
674
+ * Reproduce the retry behavior used by the OpenAI and Anthropic SDKs while making
675
+ * their backoff sleep interruptible. Their built-in retry timers ignore the
676
+ * request AbortSignal, so callers must invoke the SDK with `maxRetries: 0` and
677
+ * wrap the request with this helper. Provider-requested delays above
678
+ * `maxRetryDelayMs` fail immediately (60 seconds by default); set it to zero to
679
+ * disable the limit.
680
+ */
681
+ async function retryProviderRequest(request, options = {}) {
682
+ const maxRetries = options.maxRetries ?? 0;
683
+ let retriesRemaining = maxRetries;
684
+ for (;;) {
685
+ try {
686
+ // Each retry is a fresh SDK request, so X-Stainless-Retry-Count remains zero.
687
+ return await request();
688
+ }
689
+ catch (error) {
690
+ if (options.signal?.aborted)
691
+ throw createAbortError();
692
+ if (retriesRemaining <= 0 || !isProviderError(error) || !isRetryableProviderError(error))
693
+ throw error;
694
+ const retryIndex = maxRetries - retriesRemaining;
695
+ retriesRemaining--;
696
+ await abortableSleep(getRetryDelayMs(error, retryIndex, options.maxRetryDelayMs), options.signal);
697
+ }
698
+ }
699
+ }
700
+ //# sourceMappingURL=provider-retry.js.map
701
+ /**
702
+ * Removes unpaired Unicode surrogate characters from a string.
703
+ *
704
+ * Unpaired surrogates (high surrogates 0xD800-0xDBFF without matching low surrogates 0xDC00-0xDFFF,
705
+ * or vice versa) cause JSON serialization errors in many API providers.
706
+ *
707
+ * Valid emoji and other characters outside the Basic Multilingual Plane use properly paired
708
+ * surrogates and will NOT be affected by this function.
709
+ *
710
+ * @param text - The text to sanitize
711
+ * @returns The sanitized text with unpaired surrogates removed
712
+ *
713
+ * @example
714
+ * // Valid emoji (properly paired surrogates) are preserved
715
+ * sanitizeSurrogates("Hello 🙈 World") // => "Hello 🙈 World"
716
+ *
717
+ * // Unpaired high surrogate is removed
718
+ * const unpaired = String.fromCharCode(0xD83D); // high surrogate without low
719
+ * sanitizeSurrogates(`Text ${unpaired} here`) // => "Text here"
720
+ */
721
+ function sanitizeSurrogates(text) {
722
+ // Replace unpaired high surrogates (0xD800-0xDBFF not followed by low surrogate)
723
+ // Replace unpaired low surrogates (0xDC00-0xDFFF not preceded by high surrogate)
724
+ return text.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g, "");
725
+ }
726
+ //# sourceMappingURL=sanitize-unicode.js.map
727
+ function getGrammarToolInput(toolName, arguments_, inputProperty) {
728
+ const input = arguments_[inputProperty];
729
+ if (typeof input !== "string") {
730
+ throw new Error(`Grammar tool call "${toolName}" requires argument "${inputProperty}" to be a string.`);
731
+ }
732
+ return input;
733
+ }
734
+ function appendGrammarToolInputJsonDelta(buffer, inputProperty, nextInput, close) {
735
+ if (buffer.closed) {
736
+ if (close && nextInput === buffer.input)
737
+ return undefined;
738
+ throw new Error(`grammar tool input for property "${inputProperty}" changed after it was closed`);
739
+ }
740
+ if (!nextInput.startsWith(buffer.input)) {
741
+ throw new Error(`grammar tool input for property "${inputProperty}" changed non-monotonically`);
742
+ }
743
+ const inputDelta = nextInput.slice(buffer.input.length);
744
+ if (!close && inputDelta.length === 0)
745
+ return undefined;
746
+ let delta = "";
747
+ if (!buffer.started) {
748
+ delta += `{${JSON.stringify(inputProperty)}:"`;
749
+ buffer.started = true;
750
+ }
751
+ delta += JSON.stringify(inputDelta).slice(1, -1);
752
+ buffer.input = nextInput;
753
+ if (close) {
754
+ delta += '"}';
755
+ buffer.closed = true;
756
+ }
757
+ return delta;
758
+ }
759
+ function inferGrammarInputProperty(tool) {
760
+ const schema = tool.parameters;
761
+ if (schema.type !== "object") {
762
+ throw new Error("grammar constrained sampling requires an object parameter schema");
763
+ }
764
+ if (!Array.isArray(schema.required) || schema.required.length !== 1 || typeof schema.required[0] !== "string") {
765
+ throw new Error("grammar constrained sampling requires exactly one required string property");
766
+ }
767
+ const inputProperty = schema.required[0];
768
+ if (!schema.properties?.[inputProperty]) {
769
+ throw new Error(`grammar constrained sampling requires a properties entry for ${inputProperty}`);
770
+ }
771
+ if (schema.properties[inputProperty]?.type !== "string") {
772
+ throw new Error(`grammar constrained sampling property ${inputProperty} must have type string`);
773
+ }
774
+ return inputProperty;
775
+ }
776
+ function resolveJsonSchemaStrictSampling(tool, supportsStrictMode) {
777
+ const config = tool.constrainedSampling;
778
+ if (!config || config.type !== "json_schema") {
779
+ return undefined;
780
+ }
781
+ if (supportsStrictMode) {
782
+ return true;
783
+ }
784
+ if (config.strict === "require") {
785
+ throw new Error(`Tool "${tool.name}" requires JSON-schema constrained sampling, but strict tools are unsupported.`);
786
+ }
787
+ return undefined;
788
+ }
789
+ function resolveGrammarConstrainedSampling(tool, supportsOpenAIGrammarTools) {
790
+ const config = tool.constrainedSampling;
791
+ if (!config || config.type !== "grammar") {
792
+ return undefined;
793
+ }
794
+ if (!supportsOpenAIGrammarTools) {
795
+ return undefined;
796
+ }
797
+ const larkDefinition = config.variants.openai_lark;
798
+ const regexDefinition = config.variants.openai_regex;
799
+ const hasLarkDefinition = typeof larkDefinition === "string" && larkDefinition.trim().length > 0;
800
+ const hasRegexDefinition = typeof regexDefinition === "string" && regexDefinition.trim().length > 0;
801
+ if (!hasLarkDefinition && !hasRegexDefinition) {
802
+ throw new Error(`Tool "${tool.name}" cannot use grammar constrained sampling: no supported grammar variant was provided.`);
803
+ }
804
+ try {
805
+ return {
806
+ format: hasLarkDefinition ? "lark" : "regex",
807
+ definition: hasLarkDefinition ? larkDefinition : regexDefinition,
808
+ inputProperty: inferGrammarInputProperty(tool),
809
+ };
810
+ }
811
+ catch (error) {
812
+ const message = error instanceof Error ? error.message : String(error);
813
+ throw new Error(`Tool "${tool.name}" cannot use grammar constrained sampling: ${message}.`);
814
+ }
815
+ }
816
+ function createGrammarToolInputProperties(tools, supportsOpenAIGrammarTools) {
817
+ const properties = new Map();
818
+ for (const tool of tools ?? []) {
819
+ const grammar = resolveGrammarConstrainedSampling(tool, supportsOpenAIGrammarTools);
820
+ if (grammar) {
821
+ properties.set(tool.name, grammar.inputProperty);
822
+ }
823
+ }
824
+ return properties;
825
+ }
826
+ //# sourceMappingURL=constrained-sampling.js.map
827
+ const CHARS_PER_TOKEN = 4;
828
+ const ESTIMATED_IMAGE_CHARS = 4800;
829
+ function calculateContextTokens(usage) {
830
+ return usage.totalTokens || usage.input + usage.output + usage.cacheRead + usage.cacheWrite;
831
+ }
832
+ function safeJsonStringify(value) {
833
+ try {
834
+ return JSON.stringify(value) ?? "undefined";
835
+ }
836
+ catch {
837
+ return "[unserializable]";
838
+ }
839
+ }
840
+ function estimateTextAndImageContentChars(content) {
841
+ if (typeof content === "string")
842
+ return content.length;
843
+ let chars = 0;
844
+ for (const block of content)
845
+ chars += block.type === "text" ? block.text.length : ESTIMATED_IMAGE_CHARS;
846
+ return chars;
847
+ }
848
+ function estimateTextTokens(text) {
849
+ return Math.ceil(text.length / CHARS_PER_TOKEN);
850
+ }
851
+ function estimateTextAndImageContentTokens(content) {
852
+ return Math.ceil(estimateTextAndImageContentChars(content) / CHARS_PER_TOKEN);
853
+ }
854
+ function estimateMessageTokens(message) {
855
+ let chars = 0;
856
+ if (message.role === "user")
857
+ return estimateTextAndImageContentTokens(message.content);
858
+ if (message.role === "toolResult")
859
+ return estimateTextAndImageContentTokens(message.content);
860
+ for (const block of message.content) {
861
+ if (block.type === "text") {
862
+ chars += block.text.length;
863
+ }
864
+ else if (block.type === "thinking") {
865
+ chars += block.thinking.length;
866
+ }
867
+ else {
868
+ chars += block.name.length + safeJsonStringify(block.arguments).length;
869
+ }
870
+ }
871
+ return Math.ceil(chars / CHARS_PER_TOKEN);
872
+ }
873
+ function getLastAssistantUsageInfo(messages) {
874
+ let latestPrefixTimestamp = Number.NEGATIVE_INFINITY;
875
+ let usageInfo;
876
+ for (let i = 0; i < messages.length; i++) {
877
+ const message = messages[i];
878
+ if (message.role === "assistant") {
879
+ const assistant = message;
880
+ // A newer prefix message was inserted after this response (for example, a
881
+ // compaction summary), so its usage cannot describe the current prefix.
882
+ const usageAppliesToPrefix = assistant.timestamp >= latestPrefixTimestamp;
883
+ if (usageAppliesToPrefix &&
884
+ assistant.stopReason !== "aborted" &&
885
+ assistant.stopReason !== "error" &&
886
+ calculateContextTokens(assistant.usage) > 0) {
887
+ usageInfo = { usage: assistant.usage, index: i };
888
+ }
889
+ }
890
+ latestPrefixTimestamp = Math.max(latestPrefixTimestamp, message.timestamp);
891
+ }
892
+ return usageInfo;
893
+ }
894
+ function estimateMessages(messages) {
895
+ const usageInfo = getLastAssistantUsageInfo(messages);
896
+ if (usageInfo) {
897
+ const usageTokens = calculateContextTokens(usageInfo.usage);
898
+ let trailingTokens = 0;
899
+ for (let i = usageInfo.index + 1; i < messages.length; i++) {
900
+ trailingTokens += estimateMessageTokens(messages[i]);
901
+ }
902
+ return { tokens: usageTokens + trailingTokens, usageTokens, trailingTokens, lastUsageIndex: usageInfo.index };
903
+ }
904
+ let tokens = 0;
905
+ for (const message of messages)
906
+ tokens += estimateMessageTokens(message);
907
+ return { tokens, usageTokens: 0, trailingTokens: tokens, lastUsageIndex: null };
908
+ }
909
+ function estimateToolsTokens(tools) {
910
+ if (!tools || tools.length === 0)
911
+ return 0;
912
+ return estimateTextTokens(safeJsonStringify(tools));
913
+ }
914
+ function isMessageArray(value) {
915
+ return Array.isArray(value);
916
+ }
917
+ function estimateContextTokens(context) {
918
+ if (isMessageArray(context))
919
+ return estimateMessages(context);
920
+ const estimate = estimateMessages(context.messages);
921
+ if (estimate.lastUsageIndex !== null) {
922
+ const addedNames = new Set(context.messages
923
+ .slice(estimate.lastUsageIndex + 1)
924
+ .filter((message) => message.role === "toolResult")
925
+ .flatMap((message) => message.addedToolNames ?? []));
926
+ const addedToolTokens = estimateToolsTokens(context.tools?.filter((tool) => addedNames.has(tool.name)));
927
+ return {
928
+ tokens: estimate.tokens + addedToolTokens,
929
+ usageTokens: estimate.usageTokens,
930
+ trailingTokens: estimate.trailingTokens + addedToolTokens,
931
+ lastUsageIndex: estimate.lastUsageIndex,
932
+ };
933
+ }
934
+ const prefixTokens = (context.systemPrompt ? estimateTextTokens(context.systemPrompt) : 0) + estimateToolsTokens(context.tools);
935
+ return {
936
+ tokens: estimate.tokens + prefixTokens,
937
+ usageTokens: estimate.usageTokens,
938
+ trailingTokens: estimate.trailingTokens + prefixTokens,
939
+ lastUsageIndex: estimate.lastUsageIndex,
940
+ };
941
+ }
942
+ //# sourceMappingURL=estimate.js.map
943
+
944
+ const CONTEXT_SAFETY_TOKENS = 4096;
945
+ const MIN_MAX_TOKENS = 1;
946
+ function clampMaxTokensToContext(model, context, maxTokens) {
947
+ if (model.contextWindow <= 0)
948
+ return Math.max(MIN_MAX_TOKENS, maxTokens);
949
+ const available = model.contextWindow - estimateContextTokens(context).tokens - CONTEXT_SAFETY_TOKENS;
950
+ return Math.min(maxTokens, Math.max(MIN_MAX_TOKENS, available));
951
+ }
952
+ function buildBaseOptions(model, context, options, apiKey) {
953
+ const samplingParams = model.samplingParams || options?.samplingParams
954
+ ? { ...model.samplingParams, ...options?.samplingParams }
955
+ : undefined;
956
+ return {
957
+ temperature: options?.temperature,
958
+ samplingParams,
959
+ maxTokens: clampMaxTokensToContext(model, context, options?.maxTokens ?? model.maxTokens),
960
+ signal: options?.signal,
961
+ telemetryContext: options?.telemetryContext,
962
+ apiKey: apiKey || options?.apiKey,
963
+ fetch: options?.fetch,
964
+ transport: options?.transport,
965
+ cacheRetention: options?.cacheRetention,
966
+ sessionId: options?.sessionId,
967
+ headers: options?.headers,
968
+ onPayload: options?.onPayload,
969
+ onResponse: options?.onResponse,
970
+ timeoutMs: options?.timeoutMs,
971
+ websocketConnectTimeoutMs: options?.websocketConnectTimeoutMs,
972
+ maxRetries: options?.maxRetries,
973
+ maxRetryDelayMs: options?.maxRetryDelayMs,
974
+ metadata: options?.metadata,
975
+ env: options?.env,
976
+ };
977
+ }
978
+ /** Tokens always left for the answer when a thinking budget shares the response ceiling. */
979
+ const MIN_ANSWER_TOKENS = 1024;
980
+ function clampReasoning(effort) {
981
+ return effort === "xhigh" || effort === "max" ? "high" : effort;
982
+ }
983
+ function adjustMaxTokensForThinking(
984
+ // Undefined means no explicit caller cap. Use the model cap and fit thinking inside it.
985
+ baseMaxTokens, modelMaxTokens, reasoningLevel, customBudgets) {
986
+ const defaultBudgets = {
987
+ minimal: 1024,
988
+ low: 2048,
989
+ medium: 8192,
990
+ high: 16384,
991
+ };
992
+ const budgets = { ...defaultBudgets, ...customBudgets };
993
+ const level = clampReasoning(reasoningLevel);
994
+ let thinkingBudget = budgets[level];
995
+ const maxTokens = baseMaxTokens === undefined ? modelMaxTokens : Math.min(baseMaxTokens + thinkingBudget, modelMaxTokens);
996
+ if (maxTokens <= thinkingBudget) {
997
+ thinkingBudget = Math.max(0, maxTokens - MIN_ANSWER_TOKENS);
998
+ }
999
+ return { maxTokens, thinkingBudget };
1000
+ }
1001
+ //# sourceMappingURL=simple-options.js.map
1002
+ const NON_VISION_USER_IMAGE_PLACEHOLDER = "(image omitted: model does not support images)";
1003
+ const NON_VISION_TOOL_IMAGE_PLACEHOLDER = "(tool image omitted: model does not support images)";
1004
+ function replaceImagesWithPlaceholder(content, placeholder) {
1005
+ const result = [];
1006
+ let previousWasPlaceholder = false;
1007
+ for (const block of content) {
1008
+ if (block.type === "image") {
1009
+ if (!previousWasPlaceholder) {
1010
+ result.push({ type: "text", text: placeholder });
1011
+ }
1012
+ previousWasPlaceholder = true;
1013
+ continue;
1014
+ }
1015
+ result.push(block);
1016
+ previousWasPlaceholder = block.text === placeholder;
1017
+ }
1018
+ return result;
1019
+ }
1020
+ function downgradeUnsupportedImages(messages, model) {
1021
+ if (model.input.includes("image")) {
1022
+ return messages;
1023
+ }
1024
+ return messages.map((msg) => {
1025
+ if (msg.role === "user" && Array.isArray(msg.content)) {
1026
+ return {
1027
+ ...msg,
1028
+ content: replaceImagesWithPlaceholder(msg.content, NON_VISION_USER_IMAGE_PLACEHOLDER),
1029
+ };
1030
+ }
1031
+ if (msg.role === "toolResult") {
1032
+ return {
1033
+ ...msg,
1034
+ content: replaceImagesWithPlaceholder(msg.content, NON_VISION_TOOL_IMAGE_PLACEHOLDER),
1035
+ };
1036
+ }
1037
+ return msg;
1038
+ });
1039
+ }
1040
+ /**
1041
+ * Normalize tool call ID for cross-provider compatibility.
1042
+ * OpenAI Responses API generates IDs that are 450+ chars with special characters like `|`.
1043
+ * Anthropic APIs require IDs matching ^[a-zA-Z0-9_-]+$ (max 64 chars).
1044
+ */
1045
+ function transformMessages(messages, model, normalizeToolCallId) {
1046
+ // Build a map of original tool call IDs to normalized IDs
1047
+ const toolCallIdMap = new Map();
1048
+ // Normalize null/undefined content from untyped callers (custom tools, hand-built
1049
+ // histories, old session files) so downstream code can rely on the type contract.
1050
+ const normalizedMessages = messages.map((msg) => (msg.content == null ? { ...msg, content: [] } : msg));
1051
+ const imageAwareMessages = downgradeUnsupportedImages(normalizedMessages, model);
1052
+ // First pass: transform messages (unsupported image downgrade, thinking blocks, tool call ID normalization)
1053
+ const transformed = imageAwareMessages.map((msg) => {
1054
+ // User messages pass through unchanged
1055
+ if (msg.role === "user") {
1056
+ return msg;
1057
+ }
1058
+ // Handle toolResult messages - normalize toolCallId if we have a mapping
1059
+ if (msg.role === "toolResult") {
1060
+ const normalizedId = toolCallIdMap.get(msg.toolCallId);
1061
+ if (normalizedId && normalizedId !== msg.toolCallId) {
1062
+ return { ...msg, toolCallId: normalizedId };
1063
+ }
1064
+ return msg;
1065
+ }
1066
+ // Assistant messages need transformation check
1067
+ if (msg.role === "assistant") {
1068
+ const assistantMsg = msg;
1069
+ const isSameModel = assistantMsg.provider === model.provider &&
1070
+ assistantMsg.api === model.api &&
1071
+ assistantMsg.model === model.id;
1072
+ const transformedContent = assistantMsg.content.flatMap((block) => {
1073
+ if (block.type === "thinking") {
1074
+ // Redacted thinking is opaque encrypted content, only valid for the same model.
1075
+ // Drop it for cross-model to avoid API errors.
1076
+ if (block.redacted) {
1077
+ return isSameModel ? block : [];
1078
+ }
1079
+ // For same model: keep thinking blocks with signatures (needed for replay)
1080
+ // even if the thinking text is empty (OpenAI encrypted reasoning)
1081
+ if (isSameModel && block.thinkingSignature)
1082
+ return block;
1083
+ // Skip empty thinking blocks, convert others to plain text
1084
+ if (!block.thinking || block.thinking.trim() === "")
1085
+ return [];
1086
+ if (isSameModel)
1087
+ return block;
1088
+ return {
1089
+ type: "text",
1090
+ text: block.thinking,
1091
+ };
1092
+ }
1093
+ if (block.type === "text") {
1094
+ if (isSameModel)
1095
+ return block;
1096
+ return {
1097
+ type: "text",
1098
+ text: block.text,
1099
+ };
1100
+ }
1101
+ if (block.type === "toolCall") {
1102
+ const toolCall = block;
1103
+ let normalizedToolCall = toolCall;
1104
+ if (!isSameModel && toolCall.thoughtSignature) {
1105
+ normalizedToolCall = { ...toolCall };
1106
+ delete normalizedToolCall.thoughtSignature;
1107
+ }
1108
+ if (!isSameModel && normalizeToolCallId) {
1109
+ const normalizedId = normalizeToolCallId(toolCall.id, model, assistantMsg);
1110
+ if (normalizedId !== toolCall.id) {
1111
+ toolCallIdMap.set(toolCall.id, normalizedId);
1112
+ normalizedToolCall = { ...normalizedToolCall, id: normalizedId };
1113
+ }
1114
+ }
1115
+ return normalizedToolCall;
1116
+ }
1117
+ return block;
1118
+ });
1119
+ return {
1120
+ ...assistantMsg,
1121
+ content: transformedContent,
1122
+ };
1123
+ }
1124
+ return msg;
1125
+ });
1126
+ // Second pass: insert synthetic empty tool results for orphaned tool calls
1127
+ // This preserves thinking signatures and satisfies API requirements
1128
+ const result = [];
1129
+ let pendingToolCalls = [];
1130
+ let existingToolResultIds = new Set();
1131
+ const insertSyntheticToolResults = () => {
1132
+ if (pendingToolCalls.length > 0) {
1133
+ for (const tc of pendingToolCalls) {
1134
+ if (!existingToolResultIds.has(tc.id)) {
1135
+ result.push({
1136
+ role: "toolResult",
1137
+ toolCallId: tc.id,
1138
+ toolName: tc.name,
1139
+ content: [{ type: "text", text: "No result provided" }],
1140
+ isError: true,
1141
+ timestamp: Date.now(),
1142
+ });
1143
+ }
1144
+ }
1145
+ pendingToolCalls = [];
1146
+ existingToolResultIds = new Set();
1147
+ }
1148
+ };
1149
+ for (let i = 0; i < transformed.length; i++) {
1150
+ const msg = transformed[i];
1151
+ if (msg.role === "assistant") {
1152
+ // If we have pending orphaned tool calls from a previous assistant, insert synthetic results now
1153
+ insertSyntheticToolResults();
1154
+ // Skip errored/aborted assistant messages entirely.
1155
+ // These are incomplete turns that shouldn't be replayed:
1156
+ // - May have partial content (reasoning without message, incomplete tool calls)
1157
+ // - Replaying them can cause API errors (e.g., OpenAI "reasoning without following item")
1158
+ // - The model should retry from the last valid state
1159
+ const assistantMsg = msg;
1160
+ if (assistantMsg.stopReason === "error" || assistantMsg.stopReason === "aborted") {
1161
+ continue;
1162
+ }
1163
+ // Track tool calls from this assistant message
1164
+ const toolCalls = assistantMsg.content.filter((b) => b.type === "toolCall");
1165
+ if (toolCalls.length > 0) {
1166
+ pendingToolCalls = toolCalls;
1167
+ existingToolResultIds = new Set();
1168
+ }
1169
+ result.push(msg);
1170
+ }
1171
+ else if (msg.role === "toolResult") {
1172
+ existingToolResultIds.add(msg.toolCallId);
1173
+ result.push(msg);
1174
+ }
1175
+ else if (msg.role === "user") {
1176
+ // User message interrupts tool flow - insert synthetic results for orphaned calls
1177
+ insertSyntheticToolResults();
1178
+ result.push(msg);
1179
+ }
1180
+ else {
1181
+ result.push(msg);
1182
+ }
1183
+ }
1184
+ // If the conversation ends with unresolved tool calls, synthesize results now.
1185
+ insertSyntheticToolResults();
1186
+ return result;
1187
+ }
1188
+ //# sourceMappingURL=transform-messages.js.map
1189
+ export { adjustMaxTokensForThinking, appendGrammarToolInputJsonDelta, buildBaseOptions, calculateCost, clampMaxTokensToContext, clampReasoning, clampThinkingLevel, createGrammarToolInputProperties, getGrammarToolInput, headersToRecord, providerHeadersToRecord, resolveGrammarConstrainedSampling, resolveJsonSchemaStrictSampling, retryProviderRequest, sanitizeSurrogates, transformMessages };