mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-08-04 06:52:47 +00:00
🫧 feat: Claude Opus 4.7 Reasoning Visibility (#12701)
* 🫧 fix: Restore Claude Opus 4.7 Reasoning Visibility Claude Opus 4.7 omits `thinking` content from Messages API responses by default — empty thinking blocks still stream, but the `thinking` field is blank unless the caller passes `display: "summarized"` in the adaptive thinking config. This silenced the LibreChat "Thoughts" UI for Anthropic (and Anthropic-on-Bedrock) adaptive models. - Extend `ThinkingConfigAdaptive` in `packages/api/src/types/anthropic.ts` with an optional `display: 'summarized' | 'omitted'` field - Emit `{ type: 'adaptive', display: 'summarized' }` from `configureReasoning` in `packages/api/src/endpoints/anthropic/helpers.ts` - Emit `{ type: 'adaptive', display: 'summarized' }` from `bedrockInputParser` in `packages/data-provider/src/bedrock.ts` and update the local `ThinkingConfig` union - Update existing adaptive-thinking assertions to include the new field - Add dedicated tests asserting `display: 'summarized'` flows through both the Anthropic endpoint and the Bedrock parser See https://platform.claude.com/docs/en/about-claude/models/whats-new-claude-4-7#thinking-content-omitted-by-default * refactor: Gate `display: summarized` on Opus 4.7+ Narrow the reasoning-visibility opt-in to the models that actually omit thinking content by default, instead of applying it to every adaptive model. Pre-Opus-4.7 adaptive models (Opus 4.6, Sonnet 4.6) already return summaries, so sending the field is unnecessary noise. - Add `omitsThinkingByDefault(model)` in `packages/data-provider/src/bedrock.ts` that returns true only for Opus 4.7+ (including future majors like Opus 5+) - Bedrock parser now only attaches `display: 'summarized'` when the helper matches, keeping the adaptive object unchanged for older models - Anthropic endpoint `configureReasoning` uses the same helper so its emit path matches the Bedrock one - Tests: replace the blanket `display: 'summarized'` assertions with model-specific ones (Opus 4.7 gets it, Opus 4.6 / Sonnet 4.6 do not), add a dedicated `omitsThinkingByDefault` suite covering naming variants and future versions * feat: Configurable Thought Visibility for Anthropic Adaptive Models Expose the Anthropic `thinking.display` API field as a user-facing parameter so users can override the `auto` default (which stays as the Opus-4.7+ opt-in added earlier in this PR). Also fixes the CI type error by widening the adaptive thinking type assignment via a resolver helper that returns a properly-typed object. - Add `ThinkingDisplay` enum (`auto` | `summarized` | `omitted`) and matching zod schema in `packages/data-provider/src/schemas.ts` - Add `thinkingDisplay` to `tConversationSchema`, `anthropicSettings`, and the pick lists for Bedrock input/parser + Anthropic agent params - Add `resolveThinkingDisplay(model, explicit)` helper in `packages/data-provider/src/bedrock.ts` that returns the wire value or undefined (auto → model default, explicit → always honored) - `bedrockInputParser` now reads `thinkingDisplay` from input and emits `display` only when the resolver returns a value; strips the field on non-adaptive-model branches so it does not leak - `configureReasoning` in the Anthropic endpoint threads `thinkingDisplay` through, uses the resolver, and casts the adaptive config to `AnthropicClientOptions['thinking']` so the widened shape compiles against the stale installed SDK types - Add UI slider for `thinkingDisplay` in `parameterSettings.ts` next to `effort`, with three-position `com_ui_auto` / `com_ui_summarized` / `com_ui_omitted` labels - Add translation keys `com_endpoint_anthropic_thinking_display`, `com_endpoint_anthropic_thinking_display_desc`, `com_ui_summarized`, `com_ui_omitted` - Add tests: `resolveThinkingDisplay` suite (5 cases covering auto / explicit / unknown input), parser round-trip tests for all three modes on Opus 4.6 and Opus 4.7, Anthropic endpoint tests for explicit summarized/omitted overrides * fix: Drop `thinkingDisplay` When Adaptive Thinking Is Disabled If a user turns adaptive thinking off but had previously selected a `thinkingDisplay` value, the stale field was left in `additionalFields` and ended up merged into the Bedrock request's `additionalModelRequestFields`. That leaks a non-Bedrock key into the payload and can round-trip back into `llmConfig`. - Delete `additionalFields.thinkingDisplay` alongside `thinking` and `thinkingBudget` in the `thinking === false` branch of `bedrockInputParser` - Add a regression test asserting `thinking`, `thinkingBudget`, and `thinkingDisplay` are all absent when adaptive thinking is disabled on an Opus 4.7 request Reported by chatgpt-codex-connector on PR #12701. * refactor: Consolidate `ThinkingDisplay` Types and Preserve Persisted Display Address review findings on PR #12701: - [Codex P2] `bedrockInputSchema.transform` now extracts `thinking.display` from persisted `additionalModelRequestFields` back into the top-level `thinkingDisplay` field so explicit `'omitted'` round-trips through storage instead of being silently reverted to `'summarized'` on the next parse. - [Codex P2] `getLLMConfig` in the Anthropic endpoint now reads `.display` from a persisted `thinking` object (agents store the full Anthropic shape) and uses it as the fallback for `thinkingDisplay` when no top-level override is present. - [Audit #2] Collapse the three parallel wire-value types into a single `ThinkingDisplayWireValue = Exclude<ThinkingDisplay, 'auto'>` exported from `schemas.ts`; remove the duplicate `ThinkingDisplay` alias in `packages/api/src/types/anthropic.ts` (which collided with the enum name) and the `ThinkingDisplayValue` alias in `bedrock.ts`. - [Audit #3] Add `thinkingDisplay` to the `TEndpointOption` pick list next to `effort`. - [Audit #4] Add a TODO comment next to the `as AnthropicClientOptions['thinking']` cast explaining the stale `@librechat/agents` SDK types that require it. - Add tests: four round-trip cases asserting `bedrockInputSchema` recovers `display` from persisted AMRF (Opus 4.7 omitted, pre-4.7 summarized, unknown-value ignore, explicit top-level wins), and two `getLLMConfig` cases asserting the Anthropic endpoint preserves and overrides persisted `thinking.display`. * fix: Preserve Persisted `thinking.display` in bedrockInputParser The parser constructed a fresh adaptive thinking config without looking at any `display` already embedded in the incoming `additionalModelRequestFields.thinking`. On round-trip through `initializeBedrock`, a persisted user choice of `'omitted'` on Opus 4.7+ was silently reverted to `'summarized'` by the auto fallback. - Extract `extractPersistedDisplay` helper and reuse it in both the schema transform (form-state round-trip) and the parser (wire-request round-trip) - `bedrockInputParser` now feeds the persisted display as the resolver's explicit value when no top-level `thinkingDisplay` override is set - Add regression tests: parser preserves `display: 'omitted'` for persisted Opus 4.7 AMRF, and top-level `thinkingDisplay` still wins over persisted AMRF display Reported by chatgpt-codex-connector (P1) on PR #12701.
This commit is contained in:
parent
e2e3284713
commit
034b672d0c
10 changed files with 571 additions and 7 deletions
|
|
@ -226,6 +226,8 @@
|
|||
"com_endpoint_agent_placeholder": "Please select an Agent",
|
||||
"com_endpoint_ai": "AI",
|
||||
"com_endpoint_anthropic_effort": "Controls how much computational effort Claude applies. Lower effort saves tokens and reduces latency; higher effort produces more thorough responses. 'Max' enables the deepest reasoning (Opus 4.6 only).",
|
||||
"com_endpoint_anthropic_thinking_display": "Thought Visibility",
|
||||
"com_endpoint_anthropic_thinking_display_desc": "Controls whether Claude's reasoning is returned. 'Auto' opts in to summarized thoughts for models that hide them by default (Opus 4.7+); 'Summarized' always shows them; 'Omitted' always hides them for slightly lower latency.",
|
||||
"com_endpoint_anthropic_maxoutputtokens": "Maximum number of tokens that can be generated in the response. Specify a lower value for shorter responses and a higher value for longer responses. Note: models may stop before reaching this maximum.",
|
||||
"com_endpoint_anthropic_prompt_cache": "Prompt caching allows reusing large context or instructions across API calls, reducing costs and latency",
|
||||
"com_endpoint_anthropic_temp": "Ranges from 0 to 1. Use temp closer to 0 for analytical / multiple choice, and closer to 1 for creative and generative tasks. We recommend altering this or Top P but not both.",
|
||||
|
|
@ -1251,6 +1253,7 @@
|
|||
"com_ui_open_source_chat_new_tab_title": "Open Source Chat in New Tab - {{title}}",
|
||||
"com_ui_open_var": "Open {{0}}",
|
||||
"com_ui_openai": "OpenAI",
|
||||
"com_ui_omitted": "Omitted",
|
||||
"com_ui_optional": "(optional)",
|
||||
"com_ui_options": "options",
|
||||
"com_ui_output": "Output",
|
||||
|
|
@ -1451,6 +1454,7 @@
|
|||
"com_ui_storage": "Storage",
|
||||
"com_ui_storage_filter_sort": "Filter and Sort by Storage",
|
||||
"com_ui_submit": "Submit",
|
||||
"com_ui_summarized": "Summarized",
|
||||
"com_ui_summarizing": "Summarizing...",
|
||||
"com_ui_support_contact": "Support Contact",
|
||||
"com_ui_support_contact_email": "Email",
|
||||
|
|
|
|||
|
|
@ -2,9 +2,11 @@ import { logger } from '@librechat/data-schemas';
|
|||
import { AnthropicClientOptions } from '@librechat/agents';
|
||||
import {
|
||||
EModelEndpoint,
|
||||
ThinkingDisplay,
|
||||
AnthropicEffort,
|
||||
anthropicSettings,
|
||||
supportsContext1m,
|
||||
resolveThinkingDisplay,
|
||||
supportsAdaptiveThinking,
|
||||
} from 'librechat-data-provider';
|
||||
import { matchModelName } from '~/utils/tokens';
|
||||
|
|
@ -73,6 +75,7 @@ function configureReasoning(
|
|||
thinking?: boolean;
|
||||
thinkingBudget?: number | null;
|
||||
effort?: AnthropicEffort | string | null;
|
||||
thinkingDisplay?: ThinkingDisplay | string | null;
|
||||
} = {},
|
||||
): AnthropicClientOptions & { max_tokens?: number } {
|
||||
const updatedOptions = { ...anthropicInput };
|
||||
|
|
@ -80,7 +83,25 @@ function configureReasoning(
|
|||
const modelName = updatedOptions.model ?? '';
|
||||
|
||||
if (extendedOptions.thinking && modelName && supportsAdaptiveThinking(modelName)) {
|
||||
updatedOptions.thinking = { type: 'adaptive' };
|
||||
/**
|
||||
* For Opus 4.7+, Anthropic omits thinking content from responses by
|
||||
* default. Resolver returns `'summarized'` for those models (so the
|
||||
* LibreChat "Thoughts" UI keeps working) and leaves the field off for
|
||||
* older adaptive models, while honoring an explicit user choice.
|
||||
*
|
||||
* https://platform.claude.com/docs/en/about-claude/models/whats-new-claude-4-7#thinking-content-omitted-by-default
|
||||
*/
|
||||
const display = resolveThinkingDisplay(modelName, extendedOptions.thinkingDisplay);
|
||||
const adaptive = display
|
||||
? { type: 'adaptive' as const, display }
|
||||
: { type: 'adaptive' as const };
|
||||
/**
|
||||
* TODO: Remove the cast once `@librechat/agents` updates its
|
||||
* `ChatAnthropicMessages['thinking']` type to include the `display` field
|
||||
* added with Claude Opus 4.7. The cast is required because the installed
|
||||
* agents SDK still uses the pre-4.7 `ThinkingConfigAdaptive` shape.
|
||||
*/
|
||||
updatedOptions.thinking = adaptive as AnthropicClientOptions['thinking'];
|
||||
|
||||
const effort = extendedOptions.effort;
|
||||
if (effort && effort !== AnthropicEffort.unset) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { AnthropicEffort } from 'librechat-data-provider';
|
||||
import { AnthropicEffort, ThinkingDisplay } from 'librechat-data-provider';
|
||||
import type * as t from '~/types';
|
||||
import { getLLMConfig } from './llm';
|
||||
|
||||
|
|
@ -1041,6 +1041,105 @@ describe('getLLMConfig', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('should request summarized thinking display for Opus 4.7 (opt back in)', () => {
|
||||
const result = getLLMConfig('test-key', {
|
||||
modelOptions: { model: 'claude-opus-4-7', thinking: true },
|
||||
});
|
||||
|
||||
const thinking = result.llmConfig.thinking as unknown as {
|
||||
type: string;
|
||||
display?: string;
|
||||
};
|
||||
expect(thinking.type).toBe('adaptive');
|
||||
expect(thinking.display).toBe('summarized');
|
||||
});
|
||||
|
||||
it('should NOT set thinking.display for pre-Opus-4.7 adaptive models', () => {
|
||||
const pre47Models = ['claude-opus-4-6', 'claude-sonnet-4-6'];
|
||||
|
||||
pre47Models.forEach((model) => {
|
||||
const result = getLLMConfig('test-key', {
|
||||
modelOptions: { model, thinking: true },
|
||||
});
|
||||
|
||||
const thinking = result.llmConfig.thinking as unknown as {
|
||||
type: string;
|
||||
display?: string;
|
||||
};
|
||||
expect(thinking.type).toBe('adaptive');
|
||||
expect(thinking.display).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
it('should honor explicit thinkingDisplay="summarized" on Opus 4.6', () => {
|
||||
const result = getLLMConfig('test-key', {
|
||||
modelOptions: {
|
||||
model: 'claude-opus-4-6',
|
||||
thinking: true,
|
||||
thinkingDisplay: ThinkingDisplay.summarized,
|
||||
},
|
||||
});
|
||||
|
||||
const thinking = result.llmConfig.thinking as unknown as {
|
||||
type: string;
|
||||
display?: string;
|
||||
};
|
||||
expect(thinking.type).toBe('adaptive');
|
||||
expect(thinking.display).toBe('summarized');
|
||||
});
|
||||
|
||||
it('should honor explicit thinkingDisplay="omitted" on Opus 4.7', () => {
|
||||
const result = getLLMConfig('test-key', {
|
||||
modelOptions: {
|
||||
model: 'claude-opus-4-7',
|
||||
thinking: true,
|
||||
thinkingDisplay: ThinkingDisplay.omitted,
|
||||
},
|
||||
});
|
||||
|
||||
const thinking = result.llmConfig.thinking as unknown as {
|
||||
type: string;
|
||||
display?: string;
|
||||
};
|
||||
expect(thinking.type).toBe('adaptive');
|
||||
expect(thinking.display).toBe('omitted');
|
||||
});
|
||||
|
||||
it('should recover display from persisted agent thinking object (Opus 4.7 omitted)', () => {
|
||||
/** Agents persist `thinking` as the full Anthropic object. Without
|
||||
* extracting `.display` back into `thinkingDisplay`, Opus 4.7's auto
|
||||
* resolver would silently flip it to 'summarized'. */
|
||||
const result = getLLMConfig('test-key', {
|
||||
modelOptions: {
|
||||
model: 'claude-opus-4-7',
|
||||
thinking: { type: 'adaptive', display: 'omitted' },
|
||||
} as unknown as t.AnthropicModelOptions,
|
||||
});
|
||||
|
||||
const thinking = result.llmConfig.thinking as unknown as {
|
||||
type: string;
|
||||
display?: string;
|
||||
};
|
||||
expect(thinking.type).toBe('adaptive');
|
||||
expect(thinking.display).toBe('omitted');
|
||||
});
|
||||
|
||||
it('explicit thinkingDisplay wins over persisted thinking.display', () => {
|
||||
const result = getLLMConfig('test-key', {
|
||||
modelOptions: {
|
||||
model: 'claude-opus-4-7',
|
||||
thinking: { type: 'adaptive', display: 'summarized' },
|
||||
thinkingDisplay: ThinkingDisplay.omitted,
|
||||
} as unknown as t.AnthropicModelOptions,
|
||||
});
|
||||
|
||||
const thinking = result.llmConfig.thinking as unknown as {
|
||||
type: string;
|
||||
display?: string;
|
||||
};
|
||||
expect(thinking.display).toBe('omitted');
|
||||
});
|
||||
|
||||
it('should exclude topP/topK for Sonnet 4.6 with adaptive thinking', () => {
|
||||
const result = getLLMConfig('test-key', {
|
||||
modelOptions: {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,12 @@
|
|||
import { Dispatcher, ProxyAgent } from 'undici';
|
||||
import { logger } from '@librechat/data-schemas';
|
||||
import { AnthropicClientOptions } from '@librechat/agents';
|
||||
import { anthropicSettings, removeNullishValues, AuthKeys } from 'librechat-data-provider';
|
||||
import {
|
||||
anthropicSettings,
|
||||
removeNullishValues,
|
||||
ThinkingDisplay,
|
||||
AuthKeys,
|
||||
} from 'librechat-data-provider';
|
||||
import type {
|
||||
AnthropicLLMConfigResult,
|
||||
AnthropicConfigOptions,
|
||||
|
|
@ -83,12 +88,32 @@ function getLLMConfig(
|
|||
credentials: string | AnthropicCredentials | undefined,
|
||||
options: AnthropicConfigOptions = {},
|
||||
): AnthropicLLMConfigResult {
|
||||
/**
|
||||
* Persisted agent `model_parameters` may round-trip `thinking` as the full
|
||||
* Anthropic object `{ type: 'adaptive', display: 'omitted' }` rather than a
|
||||
* boolean. Pull any `.display` out of it so an explicit user choice is not
|
||||
* silently demoted to `'auto'` (which would then resolve to `'summarized'`
|
||||
* on Opus 4.7+).
|
||||
*/
|
||||
const persistedThinking = options.modelOptions?.thinking;
|
||||
const persistedDisplay =
|
||||
typeof persistedThinking === 'object' &&
|
||||
persistedThinking != null &&
|
||||
'display' in persistedThinking &&
|
||||
typeof (persistedThinking as { display?: unknown }).display === 'string'
|
||||
? ((persistedThinking as { display: string }).display as ThinkingDisplay | string)
|
||||
: undefined;
|
||||
|
||||
const systemOptions = {
|
||||
thinking: options.modelOptions?.thinking ?? anthropicSettings.thinking.default,
|
||||
promptCache: options.modelOptions?.promptCache ?? anthropicSettings.promptCache.default,
|
||||
thinkingBudget:
|
||||
options.modelOptions?.thinkingBudget ?? anthropicSettings.thinkingBudget.default,
|
||||
effort: options.modelOptions?.effort ?? anthropicSettings.effort.default,
|
||||
thinkingDisplay:
|
||||
options.modelOptions?.thinkingDisplay ??
|
||||
persistedDisplay ??
|
||||
anthropicSettings.thinkingDisplay.default,
|
||||
};
|
||||
|
||||
if (options.modelOptions) {
|
||||
|
|
@ -96,6 +121,7 @@ function getLLMConfig(
|
|||
delete options.modelOptions.promptCache;
|
||||
delete options.modelOptions.thinkingBudget;
|
||||
delete options.modelOptions.effort;
|
||||
delete options.modelOptions.thinkingDisplay;
|
||||
} else {
|
||||
throw new Error('No modelOptions provided');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { z } from 'zod';
|
|||
import { Dispatcher } from 'undici';
|
||||
import { AuthKeys, anthropicSchema, TVertexAISchema } from 'librechat-data-provider';
|
||||
import type { AnthropicClientOptions } from '@librechat/agents';
|
||||
import type { ThinkingDisplayWireValue } from 'librechat-data-provider';
|
||||
import type { LLMConfigResult } from './openai';
|
||||
import type { GoogleServiceKey } from '../utils/key';
|
||||
|
||||
|
|
@ -46,6 +47,7 @@ export interface ThinkingConfigEnabled {
|
|||
|
||||
export interface ThinkingConfigAdaptive {
|
||||
type: 'adaptive';
|
||||
display?: ThinkingDisplayWireValue;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
import { ThinkingDisplay } from '../src/schemas';
|
||||
import {
|
||||
supportsAdaptiveThinking,
|
||||
omitsThinkingByDefault,
|
||||
resolveThinkingDisplay,
|
||||
bedrockOutputParser,
|
||||
bedrockInputParser,
|
||||
bedrockInputSchema,
|
||||
supportsContext1m,
|
||||
} from '../src/bedrock';
|
||||
|
||||
|
|
@ -193,6 +197,101 @@ describe('supportsContext1m', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('omitsThinkingByDefault', () => {
|
||||
test('returns true for claude-opus-4-7', () => {
|
||||
expect(omitsThinkingByDefault('claude-opus-4-7')).toBe(true);
|
||||
});
|
||||
|
||||
test('returns true for claude-opus-4.7', () => {
|
||||
expect(omitsThinkingByDefault('claude-opus-4.7')).toBe(true);
|
||||
});
|
||||
|
||||
test('returns true for anthropic.claude-opus-4-7 (Bedrock)', () => {
|
||||
expect(omitsThinkingByDefault('anthropic.claude-opus-4-7')).toBe(true);
|
||||
});
|
||||
|
||||
test('returns true for us.anthropic.claude-opus-4-7 (cross-region Bedrock)', () => {
|
||||
expect(omitsThinkingByDefault('us.anthropic.claude-opus-4-7')).toBe(true);
|
||||
});
|
||||
|
||||
test('returns true for claude-opus-4-8 (future Opus 4.x)', () => {
|
||||
expect(omitsThinkingByDefault('claude-opus-4-8')).toBe(true);
|
||||
});
|
||||
|
||||
test('returns true for claude-opus-5 (future major Opus)', () => {
|
||||
expect(omitsThinkingByDefault('claude-opus-5')).toBe(true);
|
||||
});
|
||||
|
||||
test('returns true for claude-opus-9 (far-future Opus)', () => {
|
||||
expect(omitsThinkingByDefault('claude-opus-9')).toBe(true);
|
||||
});
|
||||
|
||||
test('returns false for claude-opus-4-6 (adaptive but pre-4.7)', () => {
|
||||
expect(omitsThinkingByDefault('claude-opus-4-6')).toBe(false);
|
||||
});
|
||||
|
||||
test('returns false for claude-opus-4-5', () => {
|
||||
expect(omitsThinkingByDefault('claude-opus-4-5')).toBe(false);
|
||||
});
|
||||
|
||||
test('returns false for claude-sonnet-4-6', () => {
|
||||
expect(omitsThinkingByDefault('claude-sonnet-4-6')).toBe(false);
|
||||
});
|
||||
|
||||
test('returns false for claude-sonnet-4-7 (Sonnet is not affected by the Opus 4.7 default)', () => {
|
||||
expect(omitsThinkingByDefault('claude-sonnet-4-7')).toBe(false);
|
||||
});
|
||||
|
||||
test('returns false for claude-haiku-4-5', () => {
|
||||
expect(omitsThinkingByDefault('claude-haiku-4-5')).toBe(false);
|
||||
});
|
||||
|
||||
test('returns false for claude-3-7-sonnet', () => {
|
||||
expect(omitsThinkingByDefault('claude-3-7-sonnet')).toBe(false);
|
||||
});
|
||||
|
||||
test('returns false for unrelated models', () => {
|
||||
expect(omitsThinkingByDefault('gpt-4o')).toBe(false);
|
||||
expect(omitsThinkingByDefault('')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveThinkingDisplay', () => {
|
||||
test('returns "summarized" for Opus 4.7 when explicit is auto/null/undefined', () => {
|
||||
expect(resolveThinkingDisplay('claude-opus-4-7', ThinkingDisplay.auto)).toBe('summarized');
|
||||
expect(resolveThinkingDisplay('claude-opus-4-7', null)).toBe('summarized');
|
||||
expect(resolveThinkingDisplay('claude-opus-4-7', undefined)).toBe('summarized');
|
||||
});
|
||||
|
||||
test('returns undefined for Opus 4.6 when explicit is auto/null/undefined', () => {
|
||||
expect(resolveThinkingDisplay('claude-opus-4-6', ThinkingDisplay.auto)).toBeUndefined();
|
||||
expect(resolveThinkingDisplay('claude-opus-4-6', null)).toBeUndefined();
|
||||
expect(resolveThinkingDisplay('claude-opus-4-6', undefined)).toBeUndefined();
|
||||
});
|
||||
|
||||
test('explicit summarized wins for any adaptive model', () => {
|
||||
expect(resolveThinkingDisplay('claude-opus-4-6', ThinkingDisplay.summarized)).toBe(
|
||||
'summarized',
|
||||
);
|
||||
expect(resolveThinkingDisplay('claude-sonnet-4-6', ThinkingDisplay.summarized)).toBe(
|
||||
'summarized',
|
||||
);
|
||||
expect(resolveThinkingDisplay('claude-opus-4-7', ThinkingDisplay.summarized)).toBe(
|
||||
'summarized',
|
||||
);
|
||||
});
|
||||
|
||||
test('explicit omitted wins even for Opus 4.7', () => {
|
||||
expect(resolveThinkingDisplay('claude-opus-4-7', ThinkingDisplay.omitted)).toBe('omitted');
|
||||
expect(resolveThinkingDisplay('claude-opus-4-6', ThinkingDisplay.omitted)).toBe('omitted');
|
||||
});
|
||||
|
||||
test('unknown string values fall through to auto behavior', () => {
|
||||
expect(resolveThinkingDisplay('claude-opus-4-7', 'bogus')).toBe('summarized');
|
||||
expect(resolveThinkingDisplay('claude-opus-4-6', 'bogus')).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('bedrockInputParser', () => {
|
||||
describe('Model Matching for Reasoning Configuration', () => {
|
||||
test('should match anthropic.claude-3-7-sonnet model', () => {
|
||||
|
|
@ -226,7 +325,7 @@ describe('bedrockInputParser', () => {
|
|||
};
|
||||
const result = bedrockInputParser.parse(input) as Record<string, unknown>;
|
||||
const additionalFields = result.additionalModelRequestFields as Record<string, unknown>;
|
||||
expect(additionalFields.thinking).toEqual({ type: 'adaptive' });
|
||||
expect(additionalFields.thinking).toEqual({ type: 'adaptive', display: 'summarized' });
|
||||
expect(additionalFields.thinkingBudget).toBeUndefined();
|
||||
expect(additionalFields.anthropic_beta).toEqual([
|
||||
'output-128k-2025-02-19',
|
||||
|
|
@ -448,11 +547,168 @@ describe('bedrockInputParser', () => {
|
|||
};
|
||||
const result = bedrockInputParser.parse(input) as Record<string, unknown>;
|
||||
const additionalFields = result.additionalModelRequestFields as Record<string, unknown>;
|
||||
expect(additionalFields.thinking).toEqual({ type: 'adaptive' });
|
||||
expect(additionalFields.thinking).toEqual({ type: 'adaptive', display: 'summarized' });
|
||||
expect(additionalFields.output_config).toEqual({ effort: 'xhigh' });
|
||||
expect(additionalFields.effort).toBeUndefined();
|
||||
});
|
||||
|
||||
test('should set thinking.display to "summarized" so Opus 4.7 returns reasoning blocks', () => {
|
||||
const input = {
|
||||
model: 'anthropic.claude-opus-4-7',
|
||||
};
|
||||
const result = bedrockInputParser.parse(input) as Record<string, unknown>;
|
||||
const additionalFields = result.additionalModelRequestFields as Record<string, unknown>;
|
||||
expect(additionalFields.thinking).toEqual({ type: 'adaptive', display: 'summarized' });
|
||||
});
|
||||
|
||||
test('should NOT set thinking.display for pre-Opus-4.7 adaptive models', () => {
|
||||
const pre47Models = [
|
||||
'anthropic.claude-opus-4-6-v1',
|
||||
'anthropic.claude-sonnet-4-6',
|
||||
'us.anthropic.claude-opus-4-6-v1',
|
||||
];
|
||||
|
||||
pre47Models.forEach((model) => {
|
||||
const result = bedrockInputParser.parse({ model }) as Record<string, unknown>;
|
||||
const additionalFields = result.additionalModelRequestFields as Record<string, unknown>;
|
||||
expect(additionalFields.thinking).toEqual({ type: 'adaptive' });
|
||||
expect(additionalFields.thinking).not.toHaveProperty('display');
|
||||
});
|
||||
});
|
||||
|
||||
test('explicit thinkingDisplay="summarized" forces display even on Opus 4.6', () => {
|
||||
const result = bedrockInputParser.parse({
|
||||
model: 'anthropic.claude-opus-4-6-v1',
|
||||
thinkingDisplay: ThinkingDisplay.summarized,
|
||||
}) as Record<string, unknown>;
|
||||
const additionalFields = result.additionalModelRequestFields as Record<string, unknown>;
|
||||
expect(additionalFields.thinking).toEqual({ type: 'adaptive', display: 'summarized' });
|
||||
expect(additionalFields.thinkingDisplay).toBeUndefined();
|
||||
});
|
||||
|
||||
test('explicit thinkingDisplay="omitted" wins even on Opus 4.7', () => {
|
||||
const result = bedrockInputParser.parse({
|
||||
model: 'anthropic.claude-opus-4-7',
|
||||
thinkingDisplay: ThinkingDisplay.omitted,
|
||||
}) as Record<string, unknown>;
|
||||
const additionalFields = result.additionalModelRequestFields as Record<string, unknown>;
|
||||
expect(additionalFields.thinking).toEqual({ type: 'adaptive', display: 'omitted' });
|
||||
expect(additionalFields.thinkingDisplay).toBeUndefined();
|
||||
});
|
||||
|
||||
test('thinkingDisplay="auto" defers to model default', () => {
|
||||
const opus47 = bedrockInputParser.parse({
|
||||
model: 'anthropic.claude-opus-4-7',
|
||||
thinkingDisplay: ThinkingDisplay.auto,
|
||||
}) as Record<string, unknown>;
|
||||
expect((opus47.additionalModelRequestFields as Record<string, unknown>).thinking).toEqual({
|
||||
type: 'adaptive',
|
||||
display: 'summarized',
|
||||
});
|
||||
|
||||
const opus46 = bedrockInputParser.parse({
|
||||
model: 'anthropic.claude-opus-4-6-v1',
|
||||
thinkingDisplay: ThinkingDisplay.auto,
|
||||
}) as Record<string, unknown>;
|
||||
expect((opus46.additionalModelRequestFields as Record<string, unknown>).thinking).toEqual({
|
||||
type: 'adaptive',
|
||||
});
|
||||
});
|
||||
|
||||
test('thinkingDisplay is stripped when model does not support adaptive thinking', () => {
|
||||
const result = bedrockInputParser.parse({
|
||||
model: 'anthropic.claude-3-5-sonnet-20241022-v2:0',
|
||||
thinkingDisplay: ThinkingDisplay.summarized,
|
||||
}) as Record<string, unknown>;
|
||||
const additionalFields = result.additionalModelRequestFields as Record<string, unknown>;
|
||||
expect(additionalFields?.thinkingDisplay).toBeUndefined();
|
||||
});
|
||||
|
||||
test('thinkingDisplay is stripped when adaptive thinking is disabled', () => {
|
||||
const result = bedrockInputParser.parse({
|
||||
model: 'anthropic.claude-opus-4-7',
|
||||
thinking: false,
|
||||
thinkingDisplay: ThinkingDisplay.summarized,
|
||||
}) as Record<string, unknown>;
|
||||
const additionalFields = result.additionalModelRequestFields as Record<string, unknown>;
|
||||
expect(additionalFields.thinking).toBeUndefined();
|
||||
expect(additionalFields.thinkingBudget).toBeUndefined();
|
||||
expect(additionalFields.thinkingDisplay).toBeUndefined();
|
||||
});
|
||||
|
||||
test('round-trips persisted display from AMRF.thinking.display (Opus 4.7 omitted)', () => {
|
||||
/** Simulates a persisted conversation where the prior parse already set
|
||||
* display on the nested AMRF.thinking object but did not persist the
|
||||
* top-level thinkingDisplay field. The schema (bedrockInputSchema) should
|
||||
* recover display → thinkingDisplay so the parser can honor the explicit
|
||||
* choice on subsequent requests. */
|
||||
const persisted = bedrockInputSchema.parse({
|
||||
model: 'anthropic.claude-opus-4-7',
|
||||
additionalModelRequestFields: {
|
||||
thinking: { type: 'adaptive', display: 'omitted' },
|
||||
},
|
||||
}) as Record<string, unknown>;
|
||||
expect(persisted.thinkingDisplay).toBe('omitted');
|
||||
});
|
||||
|
||||
test('round-trips persisted display from AMRF.thinking.display (summarized)', () => {
|
||||
const persisted = bedrockInputSchema.parse({
|
||||
model: 'anthropic.claude-opus-4-6-v1',
|
||||
additionalModelRequestFields: {
|
||||
thinking: { type: 'adaptive', display: 'summarized' },
|
||||
},
|
||||
}) as Record<string, unknown>;
|
||||
expect(persisted.thinkingDisplay).toBe('summarized');
|
||||
});
|
||||
|
||||
test('ignores unknown display values during round-trip extraction', () => {
|
||||
const persisted = bedrockInputSchema.parse({
|
||||
model: 'anthropic.claude-opus-4-7',
|
||||
additionalModelRequestFields: {
|
||||
thinking: { type: 'adaptive', display: 'bogus' },
|
||||
},
|
||||
}) as Record<string, unknown>;
|
||||
expect(persisted.thinkingDisplay).toBeUndefined();
|
||||
});
|
||||
|
||||
test('top-level thinkingDisplay wins over persisted AMRF display', () => {
|
||||
const persisted = bedrockInputSchema.parse({
|
||||
model: 'anthropic.claude-opus-4-7',
|
||||
thinkingDisplay: ThinkingDisplay.omitted,
|
||||
additionalModelRequestFields: {
|
||||
thinking: { type: 'adaptive', display: 'summarized' },
|
||||
},
|
||||
}) as Record<string, unknown>;
|
||||
expect(persisted.thinkingDisplay).toBe(ThinkingDisplay.omitted);
|
||||
});
|
||||
|
||||
test('bedrockInputParser preserves persisted AMRF.thinking.display (Opus 4.7 omitted)', () => {
|
||||
/** initializeBedrock calls bedrockInputParser directly on persisted
|
||||
* model_parameters. Without the parser-side extraction, the 'omitted'
|
||||
* user choice baked into AMRF would be silently reverted to
|
||||
* 'summarized' by the Opus 4.7+ auto fallback. */
|
||||
const result = bedrockInputParser.parse({
|
||||
model: 'anthropic.claude-opus-4-7',
|
||||
additionalModelRequestFields: {
|
||||
thinking: { type: 'adaptive', display: 'omitted' },
|
||||
},
|
||||
}) as Record<string, unknown>;
|
||||
const amrf = result.additionalModelRequestFields as Record<string, unknown>;
|
||||
expect(amrf.thinking).toEqual({ type: 'adaptive', display: 'omitted' });
|
||||
});
|
||||
|
||||
test('bedrockInputParser: top-level thinkingDisplay wins over persisted AMRF display', () => {
|
||||
const result = bedrockInputParser.parse({
|
||||
model: 'anthropic.claude-opus-4-7',
|
||||
thinkingDisplay: ThinkingDisplay.summarized,
|
||||
additionalModelRequestFields: {
|
||||
thinking: { type: 'adaptive', display: 'omitted' },
|
||||
},
|
||||
}) as Record<string, unknown>;
|
||||
const amrf = result.additionalModelRequestFields as Record<string, unknown>;
|
||||
expect(amrf.thinking).toEqual({ type: 'adaptive', display: 'summarized' });
|
||||
});
|
||||
|
||||
test('should not include output_config when effort is unset (empty string)', () => {
|
||||
const input = {
|
||||
model: 'anthropic.claude-opus-4-6-v1',
|
||||
|
|
|
|||
|
|
@ -6,7 +6,53 @@ const DEFAULT_THINKING_BUDGET = 2000;
|
|||
|
||||
const bedrockReasoningConfigValues = new Set<string>(Object.values(s.BedrockReasoningConfig));
|
||||
|
||||
type ThinkingConfig = { type: 'enabled'; budget_tokens: number } | { type: 'adaptive' };
|
||||
type ThinkingConfig =
|
||||
| { type: 'enabled'; budget_tokens: number }
|
||||
| { type: 'adaptive'; display?: s.ThinkingDisplayWireValue };
|
||||
|
||||
/**
|
||||
* Resolves the final `thinking.display` value for an adaptive-thinking request.
|
||||
*
|
||||
* Starting with Claude Opus 4.7, the Messages API returns empty `thinking`
|
||||
* blocks unless the request sets `thinking.display`. This helper encodes the
|
||||
* three user-facing modes — `'auto'` (LibreChat decides), `'summarized'`, and
|
||||
* `'omitted'` — into the wire value (or `undefined` when the field should be
|
||||
* left off).
|
||||
*
|
||||
* See https://platform.claude.com/docs/en/about-claude/models/whats-new-claude-4-7#thinking-content-omitted-by-default
|
||||
*/
|
||||
/**
|
||||
* Safely extracts a nested `thinking.display` string from a persisted
|
||||
* `additionalModelRequestFields` object, returning `undefined` if the shape
|
||||
* isn't what we expect.
|
||||
*/
|
||||
function extractPersistedDisplay(amrf: unknown): string | undefined {
|
||||
if (typeof amrf !== 'object' || amrf === null) {
|
||||
return undefined;
|
||||
}
|
||||
const thinking = (amrf as Record<string, unknown>).thinking;
|
||||
if (typeof thinking !== 'object' || thinking === null) {
|
||||
return undefined;
|
||||
}
|
||||
const display = (thinking as Record<string, unknown>).display;
|
||||
return typeof display === 'string' ? display : undefined;
|
||||
}
|
||||
|
||||
export function resolveThinkingDisplay(
|
||||
model: string,
|
||||
explicit?: s.ThinkingDisplay | string | null,
|
||||
): s.ThinkingDisplayWireValue | undefined {
|
||||
if (explicit === s.ThinkingDisplay.summarized) {
|
||||
return s.ThinkingDisplay.summarized;
|
||||
}
|
||||
if (explicit === s.ThinkingDisplay.omitted) {
|
||||
return s.ThinkingDisplay.omitted;
|
||||
}
|
||||
if (omitsThinkingByDefault(model)) {
|
||||
return s.ThinkingDisplay.summarized;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
type AnthropicReasoning = {
|
||||
thinking?: ThinkingConfig | boolean;
|
||||
|
|
@ -70,6 +116,24 @@ export function supportsAdaptiveThinking(model: string): boolean {
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a model omits `thinking` content from responses by default.
|
||||
*
|
||||
* Starting with Claude Opus 4.7, the Messages API returns empty `thinking`
|
||||
* blocks unless the request explicitly opts in via `thinking.display =
|
||||
* "summarized"`. This helper narrows the opt-in to Opus 4.7+ (and any future
|
||||
* major Opus version) so older adaptive-thinking models are left untouched.
|
||||
*
|
||||
* See https://platform.claude.com/docs/en/about-claude/models/whats-new-claude-4-7#thinking-content-omitted-by-default
|
||||
*/
|
||||
export function omitsThinkingByDefault(model: string): boolean {
|
||||
const opus = parseOpusVersion(model);
|
||||
if (opus && (opus.major > 4 || (opus.major === 4 && opus.minor >= 7))) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Checks if a model qualifies for the context-1m beta header (Sonnet 4+, Opus 4.6+, Opus 5+) */
|
||||
export function supportsContext1m(model: string): boolean {
|
||||
const sonnet = parseSonnetVersion(model);
|
||||
|
|
@ -136,6 +200,7 @@ export const bedrockInputSchema = s.tConversationSchema
|
|||
thinking: true,
|
||||
thinkingBudget: true,
|
||||
effort: true,
|
||||
thinkingDisplay: true,
|
||||
reasoning_effort: true,
|
||||
promptCache: true,
|
||||
/* Catch-all fields */
|
||||
|
|
@ -151,6 +216,15 @@ export const bedrockInputSchema = s.tConversationSchema
|
|||
typeof thinking === 'object' && 'budget_tokens' in thinking
|
||||
? thinking.budget_tokens
|
||||
: undefined;
|
||||
if (obj.thinkingDisplay == null) {
|
||||
const persistedDisplay = extractPersistedDisplay({ thinking });
|
||||
if (
|
||||
persistedDisplay === s.ThinkingDisplay.summarized ||
|
||||
persistedDisplay === s.ThinkingDisplay.omitted
|
||||
) {
|
||||
obj.thinkingDisplay = persistedDisplay as s.ThinkingDisplay;
|
||||
}
|
||||
}
|
||||
delete obj.additionalModelRequestFields;
|
||||
}
|
||||
return s.removeNullishValues(obj);
|
||||
|
|
@ -181,6 +255,7 @@ export const bedrockInputParser = s.tConversationSchema
|
|||
thinking: true,
|
||||
thinkingBudget: true,
|
||||
effort: true,
|
||||
thinkingDisplay: true,
|
||||
reasoning_effort: true,
|
||||
promptCache: true,
|
||||
/* Catch-all fields */
|
||||
|
|
@ -242,9 +317,33 @@ export const bedrockInputParser = s.tConversationSchema
|
|||
if (additionalFields.thinking === false) {
|
||||
delete additionalFields.thinking;
|
||||
delete additionalFields.thinkingBudget;
|
||||
delete additionalFields.thinkingDisplay;
|
||||
} else {
|
||||
additionalFields.thinking = { type: 'adaptive' };
|
||||
/**
|
||||
* Persisted agent `model_parameters` round-trip back through this
|
||||
* parser with the prior `thinking.display` embedded in
|
||||
* `additionalModelRequestFields`. Surface it as the resolver's
|
||||
* explicit value when no top-level `thinkingDisplay` is set so the
|
||||
* prior user choice (e.g. 'omitted') survives instead of being
|
||||
* clobbered by the Opus 4.7+ auto → 'summarized' fallback.
|
||||
*/
|
||||
const topLevelDisplay = additionalFields.thinkingDisplay as
|
||||
| s.ThinkingDisplay
|
||||
| string
|
||||
| null
|
||||
| undefined;
|
||||
const persistedDisplay = extractPersistedDisplay(typedData.additionalModelRequestFields);
|
||||
const thinkingConfig: ThinkingConfig = { type: 'adaptive' };
|
||||
const display = resolveThinkingDisplay(
|
||||
typedData.model as string,
|
||||
topLevelDisplay ?? persistedDisplay,
|
||||
);
|
||||
if (display) {
|
||||
thinkingConfig.display = display;
|
||||
}
|
||||
additionalFields.thinking = thinkingConfig;
|
||||
delete additionalFields.thinkingBudget;
|
||||
delete additionalFields.thinkingDisplay;
|
||||
}
|
||||
} else {
|
||||
if (additionalFields.thinking === undefined) {
|
||||
|
|
@ -258,6 +357,7 @@ export const bedrockInputParser = s.tConversationSchema
|
|||
additionalFields.thinkingBudget = DEFAULT_THINKING_BUDGET;
|
||||
}
|
||||
delete additionalFields.effort;
|
||||
delete additionalFields.thinkingDisplay;
|
||||
}
|
||||
|
||||
/** Anthropic uses 'effort' via output_config, not reasoning_config */
|
||||
|
|
@ -273,6 +373,7 @@ export const bedrockInputParser = s.tConversationSchema
|
|||
delete additionalFields.thinking;
|
||||
delete additionalFields.thinkingBudget;
|
||||
delete additionalFields.effort;
|
||||
delete additionalFields.thinkingDisplay;
|
||||
delete additionalFields.output_config;
|
||||
delete additionalFields.anthropic_beta;
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import {
|
|||
Verbosity,
|
||||
ImageDetail,
|
||||
ThinkingLevel,
|
||||
ThinkingDisplay,
|
||||
EModelEndpoint,
|
||||
openAISettings,
|
||||
googleSettings,
|
||||
|
|
@ -468,6 +469,24 @@ const anthropic: Record<string, SettingDefinition> = {
|
|||
optionType: 'model',
|
||||
columnSpan: 4,
|
||||
},
|
||||
thinkingDisplay: {
|
||||
key: 'thinkingDisplay',
|
||||
label: 'com_endpoint_anthropic_thinking_display',
|
||||
labelCode: true,
|
||||
description: 'com_endpoint_anthropic_thinking_display_desc',
|
||||
descriptionCode: true,
|
||||
type: 'enum',
|
||||
default: anthropicSettings.thinkingDisplay.default,
|
||||
component: 'slider',
|
||||
options: anthropicSettings.thinkingDisplay.options,
|
||||
enumMappings: {
|
||||
[ThinkingDisplay.auto]: 'com_ui_auto',
|
||||
[ThinkingDisplay.summarized]: 'com_ui_summarized',
|
||||
[ThinkingDisplay.omitted]: 'com_ui_omitted',
|
||||
},
|
||||
optionType: 'model',
|
||||
columnSpan: 4,
|
||||
},
|
||||
};
|
||||
|
||||
const bedrock: Record<string, SettingDefinition> = {
|
||||
|
|
@ -810,6 +829,7 @@ const anthropicConfig: SettingsConfiguration = [
|
|||
anthropic.thinking,
|
||||
anthropic.thinkingBudget,
|
||||
anthropic.effort,
|
||||
anthropic.thinkingDisplay,
|
||||
anthropic.web_search,
|
||||
librechat.fileTokenLimit,
|
||||
];
|
||||
|
|
@ -831,6 +851,7 @@ const anthropicCol2: SettingsConfiguration = [
|
|||
anthropic.thinking,
|
||||
anthropic.thinkingBudget,
|
||||
anthropic.effort,
|
||||
anthropic.thinkingDisplay,
|
||||
anthropic.web_search,
|
||||
librechat.fileTokenLimit,
|
||||
];
|
||||
|
|
@ -850,6 +871,7 @@ const bedrockAnthropic: SettingsConfiguration = [
|
|||
anthropic.thinking,
|
||||
anthropic.thinkingBudget,
|
||||
anthropic.effort,
|
||||
anthropic.thinkingDisplay,
|
||||
librechat.fileTokenLimit,
|
||||
];
|
||||
|
||||
|
|
@ -908,6 +930,7 @@ const bedrockAnthropicCol2: SettingsConfiguration = [
|
|||
anthropic.thinking,
|
||||
anthropic.thinkingBudget,
|
||||
anthropic.effort,
|
||||
anthropic.thinkingDisplay,
|
||||
librechat.fileTokenLimit,
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -186,6 +186,28 @@ export enum AnthropicEffort {
|
|||
max = 'max',
|
||||
}
|
||||
|
||||
/**
|
||||
* Controls whether the model's reasoning content is returned in responses.
|
||||
*
|
||||
* - `'auto'` - LibreChat decides: opt in to `'summarized'` for models that
|
||||
* omit by default (Opus 4.7+), leave the field off for older models.
|
||||
* - `'summarized'` - always request a post-hoc summary of the reasoning.
|
||||
* - `'omitted'` - always suppress reasoning content. Slightly lower latency.
|
||||
*
|
||||
* See https://platform.claude.com/docs/en/about-claude/models/whats-new-claude-4-7#thinking-content-omitted-by-default
|
||||
*/
|
||||
export enum ThinkingDisplay {
|
||||
auto = 'auto',
|
||||
summarized = 'summarized',
|
||||
omitted = 'omitted',
|
||||
}
|
||||
|
||||
/**
|
||||
* Wire-level values accepted by the Anthropic Messages API `thinking.display`
|
||||
* field. Excludes the LibreChat-only `'auto'` sentinel.
|
||||
*/
|
||||
export type ThinkingDisplayWireValue = Exclude<ThinkingDisplay, ThinkingDisplay.auto>;
|
||||
|
||||
export enum BedrockReasoningConfig {
|
||||
low = 'low',
|
||||
medium = 'medium',
|
||||
|
|
@ -229,6 +251,7 @@ export const imageDetailValue = {
|
|||
export const eImageDetailSchema = z.nativeEnum(ImageDetail);
|
||||
export const eReasoningEffortSchema = z.nativeEnum(ReasoningEffort);
|
||||
export const eAnthropicEffortSchema = z.nativeEnum(AnthropicEffort);
|
||||
export const eThinkingDisplaySchema = z.nativeEnum(ThinkingDisplay);
|
||||
export const eReasoningSummarySchema = z.nativeEnum(ReasoningSummary);
|
||||
export const eVerbositySchema = z.nativeEnum(Verbosity);
|
||||
export const eThinkingLevelSchema = z.nativeEnum(ThinkingLevel);
|
||||
|
|
@ -496,6 +519,10 @@ export const anthropicSettings = {
|
|||
AnthropicEffort.max,
|
||||
],
|
||||
},
|
||||
thinkingDisplay: {
|
||||
default: ThinkingDisplay.auto,
|
||||
options: [ThinkingDisplay.auto, ThinkingDisplay.summarized, ThinkingDisplay.omitted],
|
||||
},
|
||||
web_search: {
|
||||
default: false as const,
|
||||
},
|
||||
|
|
@ -774,6 +801,8 @@ export const tConversationSchema = z.object({
|
|||
useResponsesApi: z.boolean().optional(),
|
||||
/* Anthropic: Effort control */
|
||||
effort: eAnthropicEffortSchema.optional().nullable(),
|
||||
/* Anthropic: Thinking visibility (Opus 4.7+ opt-in) */
|
||||
thinkingDisplay: eThinkingDisplaySchema.optional().nullable(),
|
||||
/* OpenAI Responses API / Anthropic API / Google API */
|
||||
web_search: z.boolean().optional(),
|
||||
/* disable streaming */
|
||||
|
|
@ -898,6 +927,7 @@ export const tQueryParamsSchema = tConversationSchema
|
|||
thinkingBudget: true,
|
||||
thinkingLevel: true,
|
||||
effort: true,
|
||||
thinkingDisplay: true,
|
||||
/** @endpoints bedrock */
|
||||
region: true,
|
||||
/** @endpoints bedrock */
|
||||
|
|
@ -1222,6 +1252,7 @@ export const anthropicBaseSchema = tConversationSchema.pick({
|
|||
thinking: true,
|
||||
thinkingBudget: true,
|
||||
effort: true,
|
||||
thinkingDisplay: true,
|
||||
artifacts: true,
|
||||
iconURL: true,
|
||||
greeting: true,
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ export type TEndpointOption = Pick<
|
|||
| 'thinkingBudget'
|
||||
| 'thinkingLevel'
|
||||
| 'effort'
|
||||
| 'thinkingDisplay'
|
||||
// Assistant/Agent fields
|
||||
| 'assistant_id'
|
||||
| 'agent_id'
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue