@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.
- package/dist/formatters/anthropic-xml.d.ts.map +1 -1
- package/dist/formatters/anthropic-xml.js +14 -8
- package/dist/formatters/anthropic-xml.js.map +1 -1
- package/dist/formatters/index.d.ts +1 -0
- package/dist/formatters/index.d.ts.map +1 -1
- package/dist/formatters/index.js +1 -0
- package/dist/formatters/index.js.map +1 -1
- package/dist/formatters/native.d.ts +3 -0
- package/dist/formatters/native.d.ts.map +1 -1
- package/dist/formatters/native.js +70 -14
- package/dist/formatters/native.js.map +1 -1
- package/dist/formatters/openai-responses.d.ts +23 -0
- package/dist/formatters/openai-responses.d.ts.map +1 -0
- package/dist/formatters/openai-responses.js +176 -0
- package/dist/formatters/openai-responses.js.map +1 -0
- package/dist/membrane.d.ts.map +1 -1
- package/dist/membrane.js +61 -14
- package/dist/membrane.js.map +1 -1
- package/dist/providers/anthropic-tool-schema.d.ts +43 -0
- package/dist/providers/anthropic-tool-schema.d.ts.map +1 -0
- package/dist/providers/anthropic-tool-schema.js +165 -0
- package/dist/providers/anthropic-tool-schema.js.map +1 -0
- package/dist/providers/anthropic.d.ts.map +1 -1
- package/dist/providers/anthropic.js +130 -7
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/gemini.d.ts.map +1 -1
- package/dist/providers/gemini.js +15 -0
- package/dist/providers/gemini.js.map +1 -1
- package/dist/providers/index.d.ts +2 -0
- package/dist/providers/index.d.ts.map +1 -1
- package/dist/providers/index.js +2 -0
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/openai-responses-api.d.ts +129 -0
- package/dist/providers/openai-responses-api.d.ts.map +1 -0
- package/dist/providers/openai-responses-api.js +475 -0
- package/dist/providers/openai-responses-api.js.map +1 -0
- package/dist/providers/openrouter.d.ts +6 -0
- package/dist/providers/openrouter.d.ts.map +1 -1
- package/dist/providers/openrouter.js +87 -4
- package/dist/providers/openrouter.js.map +1 -1
- package/dist/types/provider.d.ts +8 -0
- package/dist/types/provider.d.ts.map +1 -1
- package/dist/types/request.d.ts +8 -0
- package/dist/types/request.d.ts.map +1 -1
- package/dist/utils/image-media.d.ts +54 -0
- package/dist/utils/image-media.d.ts.map +1 -0
- package/dist/utils/image-media.js +135 -0
- package/dist/utils/image-media.js.map +1 -0
- package/dist/utils/tool-parser.d.ts.map +1 -1
- package/dist/utils/tool-parser.js +15 -9
- package/dist/utils/tool-parser.js.map +1 -1
- package/package.json +1 -1
- package/src/formatters/anthropic-xml.ts +13 -8
- package/src/formatters/index.ts +4 -0
- package/src/formatters/native.ts +70 -14
- package/src/formatters/openai-responses.ts +206 -0
- package/src/membrane.ts +66 -16
- package/src/providers/anthropic-tool-schema.ts +194 -0
- package/src/providers/anthropic.ts +140 -10
- package/src/providers/gemini.ts +14 -0
- package/src/providers/index.ts +13 -0
- package/src/providers/openai-responses-api.ts +644 -0
- package/src/providers/openrouter.ts +94 -5
- package/src/types/provider.ts +7 -1
- package/src/types/request.ts +8 -0
- package/src/utils/image-media.ts +150 -0
- 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,
|
|
164
|
-
inputTokens = usage.input_tokens
|
|
165
|
-
cacheCreationTokens = usage.cache_creation_input_tokens
|
|
166
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
369
|
-
(params 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
|
|
package/src/providers/gemini.ts
CHANGED
|
@@ -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: {
|
package/src/providers/index.ts
CHANGED
|
@@ -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';
|