Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@
const FINAL_ANSWER = '_final_answer';
const FINAL_ANSWER_DESCRIPTION = 'Provide your final answer by calling this tool with the required fields. This is the only way to return your response.';

// The levels Anthropic's own effort parameter accepts, which is what a
// reasoning level names on a model whose thinking is adaptive. Not every
// model accepts every level, so the provider has the final word
const EFFORT_LEVELS = Object.freeze([ 'low', 'medium', 'high', 'xhigh', 'max' ]);

module.exports = {
options: {
// The anthropic-version request header
Expand All @@ -24,12 +29,18 @@ module.exports = {
// transient failure the engine retries
timeout: 600000,
// reasoning level - extended-thinking budget_tokens (Anthropic's
// floor for a budget is 1024).
// floor for a budget is 1024). Read for the models that take a
// budget; the adaptive ones below reject one outright
thinkingBudgets: {
low: 1024,
medium: 4096,
high: 16384
}
},
// Models whose thinking is adaptive: the model decides when and how
// deeply to think, and a reasoning level names an effort level
// instead of a token budget. Extend the list when configuring a
// newer model of the same kind
adaptiveModels: [ 'claude-opus-5', 'claude-sonnet-5' ]
},
init(self) {
self.apos.ai.addAdapter(self.adapter());
Expand Down Expand Up @@ -58,24 +69,44 @@ module.exports = {
},
effort: {
low: { model: 'claude-haiku-4-5' },
medium: { model: 'claude-sonnet-4-6' },
// The adaptive models think whether or not a request says
// so, so both rungs name the level they think at rather
// than leaving it to the provider's default
medium: {
model: 'claude-sonnet-5',
reasoning: 'medium'
},
high: {
model: 'claude-opus-4-8',
model: 'claude-opus-5',
reasoning: 'high'
}
},
// maxOutputTokens is the default cap a call inherits, not the
// model's ceiling: this adapter posts and waits for a whole
// answer, so the default stays where one response comfortably
// completes inside the timeout. The published ceilings are
// 128k for the Claude 5 models and 64k for Haiku 4.5.
// `reasoning` declares what a call may pass, in this dialect's
// own vocabulary: effort levels on the adaptive models, the
// configured budget names on the budgeted ones.
models: {
'claude-haiku-4-5': {
label: 'Haiku 4.5',
contextWindow: 200000,
maxOutputTokens: 32000
maxOutputTokens: 32000,
reasoning: reasoningValues('claude-haiku-4-5')
},
'claude-sonnet-4-6': {
contextWindow: 200000,
maxOutputTokens: 64000
'claude-sonnet-5': {
label: 'Sonnet 5',
contextWindow: 1000000,
maxOutputTokens: 64000,
reasoning: reasoningValues('claude-sonnet-5')
},
'claude-opus-4-8': {
contextWindow: 200000,
maxOutputTokens: 64000
'claude-opus-5': {
label: 'Opus 5',
contextWindow: 1000000,
maxOutputTokens: 64000,
reasoning: reasoningValues('claude-opus-5')
}
},
validate() {
Expand All @@ -97,11 +128,17 @@ module.exports = {
return self.normalizeError(error);
}
};
function reasoningValues(model) {
return self.options.adaptiveModels.includes(model)
? [ ...EFFORT_LEVELS ]
: Object.keys(self.options.thinkingBudgets);
}
},
// Translate a normalized adapter request (see the engine's
// buildRequest) to an Anthropic Messages API body: content parts
// become Anthropic blocks, tool definitions become `tools`,
// `reasoning` becomes a thinking budget, and the cache policy is
// `reasoning` becomes whichever thinking the model speaks — a
// token budget, or a mode plus an effort level — and the cache is
// placed as `cache_control` markers — one on the system tail (the
// static prefix) and a rolling one on the last message, so the
// next call in a conversation reads what this one wrote. Tool
Expand All @@ -126,6 +163,10 @@ module.exports = {
if (!Number.isInteger(maxTokens)) {
invalid(`"maxTokens" is required: model "${model}" declares no maxOutputTokens to default to`);
}
const adaptive = self.options.adaptiveModels.includes(model);
// A turn that reserves part of max_tokens for thinking, which is
// the shape of thinking a forced tool cannot share the turn with
const budgeted = reasoning !== undefined && !adaptive;
const wireTools = [
...(tools || []).map(toTool),
...(schema
Expand All @@ -142,11 +183,12 @@ module.exports = {
...(system !== undefined && { system }),
...(wireTools.length && { tools: wireTools }),
// Force the structured answer only when nothing else needs the
// turn: a real tool the model must be free to call first, or
// extended thinking, which Anthropic forbids alongside a forced
// tool. Otherwise the tool's description drives it and the
// engine's backstop retries a miss.
...(schema && !(tools && tools.length) && reasoning === undefined && {
// turn: a real tool the model must be free to call first, or a
// budgeted thinking turn, which Anthropic forbids alongside a
// forced tool — adaptive thinking carries no such restriction.
// Otherwise the tool's description drives it and the engine's
// backstop retries a miss.
...(schema && !(tools && tools.length) && !budgeted && {
tool_choice: {
type: 'tool',
name: FINAL_ANSWER
Expand All @@ -158,7 +200,9 @@ module.exports = {
}))
};
if (reasoning !== undefined) {
body.thinking = toThinking(reasoning);
Object.assign(body, adaptive
? toAdaptive(reasoning)
: { thinking: toThinking(reasoning) });
}
if (cache) {
const marker = {
Expand Down Expand Up @@ -238,6 +282,18 @@ module.exports = {
// Another dialect's part; not ours to translate
return null;
}
// An adaptive model is told to think and how hard to work,
// never how many tokens to spend: a budget is refused outright
// by the models that take this path
function toAdaptive(reasoning) {
if (!EFFORT_LEVELS.includes(reasoning)) {
invalid(`reasoning "${reasoning}" is not an effort level; model "${model}" takes one of ${EFFORT_LEVELS.join(', ')}`);
}
return {
thinking: { type: 'adaptive' },
output_config: { effort: reasoning }
};
}
// Anthropic wants an absolute token budget, mapped by the
// thinkingBudgets option; the budget must leave max_tokens
// room for the answer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ const FINISH_REASONS = {
const ASPECTS = [
'1:1', '3:2', '2:3', '3:4', '4:3', '4:5', '5:4', '9:16', '16:9', '21:9'
];
// The `thinkingLevel` values every current text model accepts
const THINKING_LEVELS = Object.freeze([
'minimal', 'low', 'medium', 'high'
]);
// The normalized quality tiers → the dialect's output resolution
// (the uppercase K is required)
const IMAGE_SIZES = {
Expand Down Expand Up @@ -93,22 +97,31 @@ module.exports = {
reasoning: 'high'
}
},
// `reasoning` is the dialect's `thinkingLevel` vocabulary,
// shared by both current text models
models: {
'gemini-3.1-flash-lite': {
label: 'Gemini 3.1 Flash-Lite',
contextWindow: 1048576,
maxOutputTokens: 65536
maxOutputTokens: 65536,
reasoning: THINKING_LEVELS
},
'gemini-3.5-flash': {
label: 'Gemini 3.5 Flash',
contextWindow: 1048576,
maxOutputTokens: 65536
maxOutputTokens: 65536,
reasoning: THINKING_LEVELS
},
'gemini-3.1-flash-image': {
label: 'Gemini 3.1 Flash Image',
aspects: ASPECTS
},
'gemini-3-pro-image': {
label: 'Gemini 3 Pro Image',
aspects: ASPECTS
},
'gemini-3.1-flash-lite-image': {
label: 'Gemini 3.1 Flash-Lite Image',
aspects: ASPECTS
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@

const image = require('../ai-adapter-openai/lib/image');

// The `reasoning_effort` values the native service accepts on every
// current text model. The native tools-request degrade drops reasoning
// as behavior; the vocabulary itself is unchanged by it. Aliased
// entries describe other services and declare their own.
const REASONING_EFFORTS = Object.freeze([
'none', 'low', 'medium', 'high', 'xhigh', 'max'
]);

module.exports = {
options: {
// Per-request timeout in milliseconds; a timed-out call is a
Expand Down Expand Up @@ -66,16 +74,22 @@ module.exports = {
},
models: {
'gpt-5.6-luna': {
label: 'GPT-5.6 Luna',
contextWindow: 1050000,
maxOutputTokens: 128000
maxOutputTokens: 128000,
reasoning: REASONING_EFFORTS
},
'gpt-5.6-terra': {
label: 'GPT-5.6 Terra',
contextWindow: 1050000,
maxOutputTokens: 128000
maxOutputTokens: 128000,
reasoning: REASONING_EFFORTS
},
'gpt-5.6-sol': {
label: 'GPT-5.6 Sol',
contextWindow: 1050000,
maxOutputTokens: 128000
maxOutputTokens: 128000,
reasoning: REASONING_EFFORTS
},
...image.models
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@

const image = require('./lib/image');

// The `reasoning.effort` values every current text model accepts —
// the whole GPT-5.6 tier shares one vocabulary
const REASONING_EFFORTS = Object.freeze([
'none', 'low', 'medium', 'high', 'xhigh', 'max'
]);

module.exports = {
options: {
// Per-request timeout in milliseconds; a timed-out call is a
Expand Down Expand Up @@ -60,16 +66,22 @@ module.exports = {
},
models: {
'gpt-5.6-luna': {
label: 'GPT-5.6 Luna',
contextWindow: 1050000,
maxOutputTokens: 128000
maxOutputTokens: 128000,
reasoning: REASONING_EFFORTS
},
'gpt-5.6-terra': {
label: 'GPT-5.6 Terra',
contextWindow: 1050000,
maxOutputTokens: 128000
maxOutputTokens: 128000,
reasoning: REASONING_EFFORTS
},
'gpt-5.6-sol': {
label: 'GPT-5.6 Sol',
contextWindow: 1050000,
maxOutputTokens: 128000
maxOutputTokens: 128000,
reasoning: REASONING_EFFORTS
},
...image.models
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,11 @@ module.exports = async function image(deps, request) {
// fixed to its three.
module.exports.models = {
'gpt-image-2': {
label: 'GPT Image 2',
aspects: Object.keys(SIZES)
},
'gpt-image-1': {
label: 'GPT Image 1',
aspects: [ '1:1', '3:2', '2:3' ]
}
};
Expand Down
Loading
Loading