@animalabs/membrane 0.5.68 → 0.5.70

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 (67) hide show
  1. package/dist/formatters/anthropic-xml.d.ts.map +1 -1
  2. package/dist/formatters/anthropic-xml.js +14 -8
  3. package/dist/formatters/anthropic-xml.js.map +1 -1
  4. package/dist/formatters/index.d.ts +1 -0
  5. package/dist/formatters/index.d.ts.map +1 -1
  6. package/dist/formatters/index.js +1 -0
  7. package/dist/formatters/index.js.map +1 -1
  8. package/dist/formatters/native.d.ts +3 -0
  9. package/dist/formatters/native.d.ts.map +1 -1
  10. package/dist/formatters/native.js +70 -14
  11. package/dist/formatters/native.js.map +1 -1
  12. package/dist/formatters/openai-responses.d.ts +23 -0
  13. package/dist/formatters/openai-responses.d.ts.map +1 -0
  14. package/dist/formatters/openai-responses.js +176 -0
  15. package/dist/formatters/openai-responses.js.map +1 -0
  16. package/dist/membrane.d.ts.map +1 -1
  17. package/dist/membrane.js +61 -14
  18. package/dist/membrane.js.map +1 -1
  19. package/dist/providers/anthropic-tool-schema.d.ts +43 -0
  20. package/dist/providers/anthropic-tool-schema.d.ts.map +1 -0
  21. package/dist/providers/anthropic-tool-schema.js +165 -0
  22. package/dist/providers/anthropic-tool-schema.js.map +1 -0
  23. package/dist/providers/anthropic.d.ts.map +1 -1
  24. package/dist/providers/anthropic.js +130 -7
  25. package/dist/providers/anthropic.js.map +1 -1
  26. package/dist/providers/gemini.d.ts.map +1 -1
  27. package/dist/providers/gemini.js +15 -0
  28. package/dist/providers/gemini.js.map +1 -1
  29. package/dist/providers/index.d.ts +2 -0
  30. package/dist/providers/index.d.ts.map +1 -1
  31. package/dist/providers/index.js +2 -0
  32. package/dist/providers/index.js.map +1 -1
  33. package/dist/providers/openai-responses-api.d.ts +129 -0
  34. package/dist/providers/openai-responses-api.d.ts.map +1 -0
  35. package/dist/providers/openai-responses-api.js +475 -0
  36. package/dist/providers/openai-responses-api.js.map +1 -0
  37. package/dist/providers/openrouter.d.ts +6 -0
  38. package/dist/providers/openrouter.d.ts.map +1 -1
  39. package/dist/providers/openrouter.js +87 -4
  40. package/dist/providers/openrouter.js.map +1 -1
  41. package/dist/types/provider.d.ts +8 -0
  42. package/dist/types/provider.d.ts.map +1 -1
  43. package/dist/types/request.d.ts +8 -0
  44. package/dist/types/request.d.ts.map +1 -1
  45. package/dist/utils/image-media.d.ts +54 -0
  46. package/dist/utils/image-media.d.ts.map +1 -0
  47. package/dist/utils/image-media.js +135 -0
  48. package/dist/utils/image-media.js.map +1 -0
  49. package/dist/utils/tool-parser.d.ts.map +1 -1
  50. package/dist/utils/tool-parser.js +15 -9
  51. package/dist/utils/tool-parser.js.map +1 -1
  52. package/package.json +1 -1
  53. package/src/formatters/anthropic-xml.ts +13 -8
  54. package/src/formatters/index.ts +4 -0
  55. package/src/formatters/native.ts +70 -14
  56. package/src/formatters/openai-responses.ts +206 -0
  57. package/src/membrane.ts +66 -16
  58. package/src/providers/anthropic-tool-schema.ts +194 -0
  59. package/src/providers/anthropic.ts +140 -10
  60. package/src/providers/gemini.ts +14 -0
  61. package/src/providers/index.ts +13 -0
  62. package/src/providers/openai-responses-api.ts +644 -0
  63. package/src/providers/openrouter.ts +94 -5
  64. package/src/types/provider.ts +7 -1
  65. package/src/types/request.ts +8 -0
  66. package/src/utils/image-media.ts +150 -0
  67. package/src/utils/tool-parser.ts +14 -9
@@ -0,0 +1,194 @@
1
+ /**
2
+ * Anthropic tool-schema normalization
3
+ *
4
+ * MCP permits a tool's input JSON Schema to have a root-level `oneOf` /
5
+ * `anyOf` / `allOf` (a union of alternative argument shapes). The Anthropic
6
+ * API does not: `input_schema` must be a single object-type schema, and a
7
+ * root-level combinator is rejected with 400
8
+ * ("input_schema does not support oneOf, allOf, or anyOf at the top level").
9
+ * One such tool 400s the entire inference — same philosophy as
10
+ * `normalize-tool-pairs`: repair at the wire boundary instead of letting a
11
+ * producer-side quirk kill the turn.
12
+ *
13
+ * `flattenRootSchemaUnion` rewrites the common case — every union variant is
14
+ * itself an object schema — into a single object schema:
15
+ *
16
+ * - `properties` is the merge of all variants' properties (first wins on
17
+ * key collision).
18
+ * - `required`: for `allOf` (intersective — every variant applies) the
19
+ * union of the variants' required lists; for `oneOf`/`anyOf`
20
+ * (alternatives) only keys required by *every* variant stay required.
21
+ * - A short note enumerating the alternative argument groups is appended
22
+ * to the description so the model still sees the union intent.
23
+ * - Variant-level `additionalProperties: false` is dropped: the merged
24
+ * object is a permissive superset of the alternatives, and `false`
25
+ * could reject payloads valid under one of the original variants.
26
+ *
27
+ * If any variant is not object-shaped (e.g. a root `oneOf` of a string and
28
+ * an object), the union cannot be merged into properties; we fall back to a
29
+ * permissive object schema carrying the serialized union in the description.
30
+ * Degenerate, but it does not 400 — the tool stays callable and the model
31
+ * sees the accepted shapes.
32
+ *
33
+ * Nested combinators (inside `properties`, array `items`, etc.) are legal
34
+ * for Anthropic and are left untouched; only the root is rewritten.
35
+ */
36
+
37
+ function isPlainObject(value: unknown): value is Record<string, unknown> {
38
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
39
+ }
40
+
41
+ function isMergeableObjectVariant(variant: Record<string, unknown>): boolean {
42
+ return (
43
+ variant.type === 'object' ||
44
+ (variant.type === undefined && isPlainObject(variant.properties))
45
+ );
46
+ }
47
+
48
+ function stringRequired(value: unknown): string[] {
49
+ return Array.isArray(value)
50
+ ? value.filter((entry): entry is string => typeof entry === 'string')
51
+ : [];
52
+ }
53
+
54
+ const ROOT_UNION_KEYS = ['oneOf', 'anyOf', 'allOf'] as const;
55
+
56
+ /** Max length of the description we synthesize on the fallback path. */
57
+ const MAX_FALLBACK_DESCRIPTION = 4000;
58
+
59
+ /**
60
+ * Rewrite a root-level `oneOf`/`anyOf`/`allOf` in a tool input schema into a
61
+ * single Anthropic-acceptable object schema. Returns the input unchanged
62
+ * (same reference) when there is nothing to repair, so callers can cheaply
63
+ * detect whether a rewrite happened.
64
+ */
65
+ export function flattenRootSchemaUnion(schema: unknown): unknown {
66
+ if (!isPlainObject(schema)) return schema;
67
+
68
+ // A root may carry more than one combinator at once (e.g. `{ oneOf, anyOf }`).
69
+ // These are sibling keywords: a valid instance must satisfy every one of
70
+ // them (an implicit `allOf` across the combinators). Process *all* present
71
+ // keys — handling only the first would strip the others' variants (the
72
+ // destructuring below removes all three keys), silently losing their
73
+ // properties and required lists.
74
+ const presentKeys = ROOT_UNION_KEYS.filter(
75
+ (key) => Array.isArray(schema[key]) && (schema[key] as unknown[]).length > 0,
76
+ );
77
+ if (presentKeys.length === 0) return schema;
78
+
79
+ const combinators = presentKeys.map((key) => {
80
+ const raw = schema[key] as unknown[];
81
+ return { key, raw, variants: raw.filter(isPlainObject) };
82
+ });
83
+ const rawVariantsAll: unknown[] = combinators.flatMap(({ raw }) => raw);
84
+
85
+ // Keep every root key except the combinators themselves.
86
+ const { oneOf: _oneOf, anyOf: _anyOf, allOf: _allOf, ...rest } = schema;
87
+
88
+ const allMergeable = combinators.every(
89
+ ({ raw, variants }) =>
90
+ variants.length === raw.length && variants.every(isMergeableObjectVariant),
91
+ );
92
+
93
+ if (allMergeable) {
94
+ // Common case: every variant of every present combinator is an object
95
+ // schema — merge them all.
96
+ const properties: Record<string, unknown> = isPlainObject(rest.properties)
97
+ ? { ...rest.properties }
98
+ : {};
99
+ for (const { variants } of combinators) {
100
+ for (const variant of variants) {
101
+ if (isPlainObject(variant.properties)) {
102
+ for (const [key, propSchema] of Object.entries(variant.properties)) {
103
+ if (!(key in properties)) properties[key] = propSchema;
104
+ }
105
+ }
106
+ }
107
+ }
108
+
109
+ // Per-combinator required semantics: `allOf` unions its variants' required
110
+ // lists (every variant applies); `oneOf`/`anyOf` keep only keys required by
111
+ // every variant (alternatives). Across combinators they all apply at once,
112
+ // so the effective required set is the union of the per-combinator results.
113
+ const mergedRequired = combinators.flatMap(({ key, variants }) => {
114
+ const variantRequired = variants.map((variant) => stringRequired(variant.required));
115
+ return key === 'allOf'
116
+ ? [...new Set(variantRequired.flat())]
117
+ : variantRequired.reduce(
118
+ (acc, req) => acc.filter((k) => req.includes(k)),
119
+ variantRequired[0] ?? [],
120
+ );
121
+ });
122
+ const required = [
123
+ ...new Set([...stringRequired(rest.required), ...mergedRequired]),
124
+ ].filter((key) => key in properties);
125
+
126
+ const {
127
+ properties: _properties,
128
+ required: _required,
129
+ additionalProperties: _additionalProperties,
130
+ ...restSansObjectKeys
131
+ } = rest;
132
+
133
+ const result: Record<string, unknown> = {
134
+ ...restSansObjectKeys,
135
+ type: 'object',
136
+ properties,
137
+ };
138
+ if (required.length > 0) result.required = required;
139
+
140
+ // For alternatives, preserve the union intent in prose so the model
141
+ // still knows the arguments come in groups (one line per combinator).
142
+ const noteParts = combinators
143
+ .filter(({ key, variants }) => key !== 'allOf' && variants.length > 1)
144
+ .map(({ variants }) => {
145
+ const groups = variants
146
+ .map((variant) => {
147
+ const req = stringRequired(variant.required);
148
+ return req.length > 0 ? `(${req.join(', ')})` : '(no required fields)';
149
+ })
150
+ .join(' | ');
151
+ return `Provide one of the following argument groups: ${groups}.`;
152
+ });
153
+ if (noteParts.length > 0) {
154
+ const note = noteParts.join('\n');
155
+ result.description =
156
+ typeof result.description === 'string' && result.description.length > 0
157
+ ? `${result.description}\n${note}`
158
+ : note;
159
+ }
160
+
161
+ return result;
162
+ }
163
+
164
+ // Fallback: at least one variant (across the present combinators) is not an
165
+ // object schema (or not a schema at all). Emit a permissive object schema and
166
+ // carry the union(s) into the description so the intent survives.
167
+ const label = presentKeys.join('/');
168
+ let note: string;
169
+ try {
170
+ note = `Accepts one of the following input shapes (flattened from a root-level ${label}): ${JSON.stringify(rawVariantsAll)}`;
171
+ } catch {
172
+ note = `Accepts one of ${rawVariantsAll.length} alternative input shapes (root-level ${label} flattened).`;
173
+ }
174
+ const baseDescription =
175
+ typeof rest.description === 'string' && rest.description.length > 0
176
+ ? `${rest.description}\n${note}`
177
+ : note;
178
+
179
+ // Spread `rest` (already sans the combinator keys) so sibling definitions —
180
+ // `$defs`/`definitions` — survive. Without this, a variant serialized into
181
+ // the description as `{"$ref":"#/definitions/A"}` would point at a definition
182
+ // that no longer exists anywhere in the tool. The mergeable path preserves
183
+ // `restSansObjectKeys` for the same reason; the fallback extends the courtesy.
184
+ return {
185
+ ...rest,
186
+ type: 'object',
187
+ properties: isPlainObject(rest.properties) ? rest.properties : {},
188
+ additionalProperties: true,
189
+ description:
190
+ baseDescription.length > MAX_FALLBACK_DESCRIPTION
191
+ ? baseDescription.slice(0, MAX_FALLBACK_DESCRIPTION)
192
+ : baseDescription,
193
+ };
194
+ }
@@ -3,6 +3,7 @@
3
3
  */
4
4
 
5
5
  import Anthropic, { type ClientOptions } from '@anthropic-ai/sdk';
6
+ import { assertWithinByteBudget, shedImagesToFitByteBudget } from '../utils/image-media.js';
6
7
  import type {
7
8
  ProviderAdapter,
8
9
  ProviderRequest,
@@ -20,6 +21,52 @@ import {
20
21
  serverError,
21
22
  abortError,
22
23
  } from '../types/index.js';
24
+ import { flattenRootSchemaUnion } from './anthropic-tool-schema.js';
25
+
26
+ // ============================================================================
27
+ // Model capability gates
28
+ // ============================================================================
29
+
30
+ /**
31
+ * Models that reject the sampling parameters (`temperature`, `top_p`,
32
+ * `top_k`) with a 400 invalid_request_error — which Membrane classifies as
33
+ * non-retryable, so a single stray `temperature` kills the whole turn.
34
+ * Mirrors the `noTemperatureSupport` gate in the OpenAI provider.
35
+ *
36
+ * This is the always-on-thinking / reasoning-forward tier, which removes the
37
+ * sampling parameters from the API surface entirely (Sonnet 5 rejects only
38
+ * non-default values). Everything else — Haiku 4.5, Sonnet 4.6, Opus 4.6 and
39
+ * older — ACCEPTS `temperature`, so it must NOT be listed here.
40
+ *
41
+ * - Opus 4.7 / Opus 4.8 / Sonnet 5 / Fable 5 / Mythos 5 / Mythos preview:
42
+ * documented 400 on any sampling parameter.
43
+ *
44
+ * NB: claude-haiku-4-5 was previously listed here on the strength of a single
45
+ * "observed 400 in production when temperature is sent" anecdote. Haiku 4.5
46
+ * documentably supports `temperature`; the production 400 was almost certainly
47
+ * the `extra`-params bypass fixed in this same PR (a sampling param smuggled
48
+ * through `extra` and re-inserted after the gate — 400s on any model), not a
49
+ * capability of Haiku. Listing it silently discarded a valid parameter on the
50
+ * most common cheap model, so it has been removed.
51
+ *
52
+ * Prefix-matched, so dated snapshots (e.g. claude-opus-4-8-20251001) are
53
+ * covered. Keep this list updated as models launch.
54
+ */
55
+ const NO_TEMPERATURE_MODELS = [
56
+ 'claude-opus-4-7',
57
+ 'claude-opus-4-8',
58
+ 'claude-sonnet-5',
59
+ 'claude-fable-5',
60
+ 'claude-mythos-5',
61
+ 'claude-mythos-preview',
62
+ ];
63
+
64
+ /**
65
+ * Check if a model doesn't support custom sampling parameters
66
+ */
67
+ function noTemperatureSupport(model: string): boolean {
68
+ return NO_TEMPERATURE_MODELS.some(prefix => model.startsWith(prefix));
69
+ }
23
70
 
24
71
  // ============================================================================
25
72
  // Adapter Configuration
@@ -139,6 +186,11 @@ export class AnthropicAdapter implements ProviderAdapter {
139
186
  let outputTokens = 0;
140
187
  let cacheCreationTokens: number | undefined;
141
188
  let cacheReadTokens: number | undefined;
189
+ let cacheCreation5mTokens: number | undefined;
190
+ let cacheCreation1hTokens: number | undefined;
191
+ let hasCacheCreationBreakdown = false;
192
+ let inferenceGeo: string | undefined;
193
+ let serviceTier: string | undefined;
142
194
  let stopReason: string = 'end_turn';
143
195
  let stopSequence: string | undefined;
144
196
  let stopDetails: unknown;
@@ -160,10 +212,22 @@ export class AnthropicAdapter implements ProviderAdapter {
160
212
  resetIdleTimer();
161
213
  if (event.type === 'message_start') {
162
214
  model = event.message.model;
163
- const usage = event.message.usage as unknown as Record<string, number>;
164
- inputTokens = usage.input_tokens ?? 0;
165
- cacheCreationTokens = usage.cache_creation_input_tokens;
166
- cacheReadTokens = usage.cache_read_input_tokens;
215
+ const usage = event.message.usage as unknown as Record<string, unknown>;
216
+ inputTokens = typeof usage.input_tokens === 'number' ? usage.input_tokens : 0;
217
+ cacheCreationTokens = typeof usage.cache_creation_input_tokens === 'number'
218
+ ? usage.cache_creation_input_tokens : undefined;
219
+ cacheReadTokens = typeof usage.cache_read_input_tokens === 'number'
220
+ ? usage.cache_read_input_tokens : undefined;
221
+ const cacheCreation = usage.cache_creation as Record<string, unknown> | undefined;
222
+ if (cacheCreation) {
223
+ hasCacheCreationBreakdown = true;
224
+ cacheCreation5mTokens = typeof cacheCreation.ephemeral_5m_input_tokens === 'number'
225
+ ? cacheCreation.ephemeral_5m_input_tokens : 0;
226
+ cacheCreation1hTokens = typeof cacheCreation.ephemeral_1h_input_tokens === 'number'
227
+ ? cacheCreation.ephemeral_1h_input_tokens : 0;
228
+ }
229
+ inferenceGeo = typeof usage.inference_geo === 'string' ? usage.inference_geo : undefined;
230
+ serviceTier = typeof usage.service_tier === 'string' ? usage.service_tier : undefined;
167
231
 
168
232
  } else if (event.type === 'content_block_start') {
169
233
  currentBlockIndex = event.index;
@@ -277,6 +341,14 @@ export class AnthropicAdapter implements ProviderAdapter {
277
341
  output_tokens: outputTokens,
278
342
  cache_creation_input_tokens: cacheCreationTokens,
279
343
  cache_read_input_tokens: cacheReadTokens,
344
+ ...(hasCacheCreationBreakdown ? {
345
+ cache_creation: {
346
+ ephemeral_5m_input_tokens: cacheCreation5mTokens ?? 0,
347
+ ephemeral_1h_input_tokens: cacheCreation1hTokens ?? 0,
348
+ },
349
+ } : {}),
350
+ ...(inferenceGeo ? { inference_geo: inferenceGeo } : {}),
351
+ ...(serviceTier ? { service_tier: serviceTier } : {}),
280
352
  },
281
353
  },
282
354
  };
@@ -326,6 +398,13 @@ export class AnthropicAdapter implements ProviderAdapter {
326
398
  };
327
399
  });
328
400
 
401
+ // Byte-wall INVARIANT at the last exit before the SDK (2026-07-12): the
402
+ // policy decision (shed with explicit opt-in, or fail loudly) happens
403
+ // upstream at the request-build sites. Reaching this point oversize means
404
+ // a compile path bypassed the policy — throw with the breakdown rather
405
+ // than silently mutate or eat a 413 round-trip.
406
+ assertWithinByteBudget(sanitizedMessages, undefined, 'anthropic-provider');
407
+
329
408
  const params: Anthropic.MessageCreateParams = {
330
409
  model: request.model,
331
410
  max_tokens: request.maxTokens || this.defaultMaxTokens,
@@ -342,17 +421,42 @@ export class AnthropicAdapter implements ProviderAdapter {
342
421
  }
343
422
  }
344
423
 
345
- if (request.temperature !== undefined) {
424
+ // Sampling-parameter gates:
425
+ // - Some models reject temperature/top_p/top_k outright with a 400
426
+ // (see NO_TEMPERATURE_MODELS) — strip rather than let the whole
427
+ // inference die on a non-retryable invalid_request_error.
428
+ // - Extended thinking rejects custom temperature/top_k on every model
429
+ // (only the defaults are accepted while thinking is on) — strip those
430
+ // too when a thinking config is present and not disabled.
431
+ const stripSampling = noTemperatureSupport(request.model);
432
+ // Thinking can arrive top-level OR smuggled through `extra` — the same
433
+ // `Object.assign(params, rest)` below installs `extra.thinking` into the
434
+ // request AFTER this gate ran. Resolve from both sources so an enabled
435
+ // thinking config strips sampling params no matter where it came from;
436
+ // otherwise `extra: { thinking, temperature }` reproduces the exact 400
437
+ // this gate exists to prevent (same bug class as the extra-sampling bypass,
438
+ // one field over).
439
+ const extraThinking = (request.extra as { thinking?: { type?: string } } | undefined)?.thinking;
440
+ const thinkingConfig = request.thinking ?? extraThinking;
441
+ const thinkingOn = thinkingConfig !== undefined && thinkingConfig.type !== 'disabled';
442
+
443
+ if (request.temperature !== undefined && !stripSampling && !thinkingOn) {
346
444
  params.temperature = request.temperature;
347
445
  }
348
446
 
349
447
  // Anthropic API rejects requests with both temperature and top_p set.
350
448
  // When both are provided, prefer temperature (more commonly tuned) and drop top_p.
351
- if (request.topP !== undefined && request.temperature === undefined) {
449
+ // With thinking on, top_p is only accepted in [0.95, 1] strip values below.
450
+ if (
451
+ request.topP !== undefined &&
452
+ params.temperature === undefined &&
453
+ !stripSampling &&
454
+ (!thinkingOn || request.topP >= 0.95)
455
+ ) {
352
456
  params.top_p = request.topP;
353
457
  }
354
458
 
355
- if (request.topK !== undefined) {
459
+ if (request.topK !== undefined && !stripSampling && !thinkingOn) {
356
460
  params.top_k = request.topK;
357
461
  }
358
462
 
@@ -361,17 +465,43 @@ export class AnthropicAdapter implements ProviderAdapter {
361
465
  }
362
466
 
363
467
  if (request.tools && request.tools.length > 0) {
364
- params.tools = request.tools as Anthropic.Tool[];
468
+ // MCP allows a root-level oneOf/anyOf/allOf in a tool's input schema,
469
+ // but the Anthropic API rejects it ("input_schema does not support
470
+ // oneOf, allOf, or anyOf at the top level") — one bad tool 400s the
471
+ // entire inference. Flatten such roots into a single object schema
472
+ // before shipping (see anthropic-tool-schema.ts).
473
+ params.tools = (request.tools as Anthropic.Tool[]).map(tool => {
474
+ const inputSchema = (tool as { input_schema?: unknown }).input_schema;
475
+ const flattened = flattenRootSchemaUnion(inputSchema);
476
+ return flattened === inputSchema
477
+ ? tool
478
+ : ({ ...tool, input_schema: flattened } as Anthropic.Tool);
479
+ });
365
480
  }
366
481
 
367
482
  // Handle extended thinking
368
- if ((request as any).thinking) {
369
- (params as any).thinking = (request as any).thinking;
483
+ if (request.thinking) {
484
+ (params as any).thinking = request.thinking;
370
485
  }
371
486
 
372
487
  // Apply extra params, excluding internal membrane fields
373
488
  if (request.extra) {
374
489
  const { normalizedMessages, prompt, ...rest } = request.extra as Record<string, unknown>;
490
+ // Sampling params passed through `extra` must obey the same gates as the
491
+ // top-level ones. Otherwise a caller passing e.g. `extra: { temperature }`
492
+ // for a reject-list model (or under extended thinking) would re-insert the
493
+ // stripped value here — via Object.assign, after the gate above — and
494
+ // reproduce the exact non-retryable 400 this stripping is meant to prevent.
495
+ if (stripSampling || thinkingOn) {
496
+ delete rest.temperature;
497
+ delete rest.top_k;
498
+ // top_p is accepted in [0.95, 1] while thinking is on, but never when
499
+ // the model rejects sampling params outright.
500
+ const extraTopP = rest.top_p;
501
+ if (stripSampling || typeof extraTopP !== 'number' || extraTopP < 0.95) {
502
+ delete rest.top_p;
503
+ }
504
+ }
375
505
  Object.assign(params, rest);
376
506
  }
377
507
 
@@ -428,6 +428,20 @@ export class GeminiAdapter implements ProviderAdapter {
428
428
  },
429
429
  });
430
430
  }
431
+ } else if (block.type === 'audio') {
432
+ // Audio input → Gemini inlineData (same mechanism as images). This is
433
+ // pure plumbing: whether a given model accepts/understands audio is the
434
+ // model's concern (the caller decides what to send). Common MIME types:
435
+ // audio/mp3, audio/wav, audio/ogg, audio/flac.
436
+ const source = block.source;
437
+ if (source?.type === 'base64' && source.data) {
438
+ parts.push({
439
+ inlineData: {
440
+ mimeType: source.mediaType ?? source.media_type ?? 'audio/mpeg',
441
+ data: source.data,
442
+ },
443
+ });
444
+ }
431
445
  } else if (block.type === 'tool_use') {
432
446
  parts.push({
433
447
  functionCall: {
@@ -9,6 +9,8 @@ export {
9
9
  type AnthropicAdapterConfig,
10
10
  } from './anthropic.js';
11
11
 
12
+ export { flattenRootSchemaUnion } from './anthropic-tool-schema.js';
13
+
12
14
  export {
13
15
  OpenRouterAdapter,
14
16
  toOpenRouterMessages,
@@ -58,3 +60,14 @@ export {
58
60
  OpenAIResponsesAdapter,
59
61
  type OpenAIResponsesAdapterConfig,
60
62
  } from './openai-responses.js';
63
+
64
+ export {
65
+ OpenAIResponsesAPIAdapter,
66
+ type OpenAIResponsesAPIAdapterConfig,
67
+ type OpenAIResponsesAPIContentBlock,
68
+ type OpenAIResponsesAPIProviderResponse,
69
+ type OpenAIResponsesAPIRequest,
70
+ type OpenAIResponsesAPIResponse,
71
+ type OpenAIResponsesInputItem,
72
+ type OpenAIResponsesOutputItem,
73
+ } from './openai-responses-api.js';