Spaces:
Running
Running
File size: 8,944 Bytes
034e664 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 | import { describe, it, expect } from 'vitest';
import {
normalizeThinking,
anthropicThinking,
geminiThinkingConfig,
openaiCompatThinkingBody,
openAiCompatThinkingPolicy,
isGlmModel,
} from '../../lib/thinking.js';
// `thinking` is the unified inbound knob. The translators here emit wire-
// shape fragments (`thinking` + `output_config` for Anthropic, etc.) that
// per-provider code folds into the final body. Tests cover all four real
// provider paths plus the unified effort/type normalization.
describe('normalizeThinking', () => {
it('returns undefined when neither field is set', () => {
expect(normalizeThinking({})).toBeUndefined();
});
it('treats bare `reasoning_effort` as enable-implied', () => {
const out = normalizeThinking({ reasoning_effort: 'high' });
expect(out).toEqual({ effort: 'high', enabled: true });
});
it('treats `thinking.type=disabled` explicitly as disabled', () => {
const out = normalizeThinking({ thinking: { type: 'disabled' } });
expect(out?.enabled).toBe(false);
});
it('treats `thinking.type=adaptive` as enabled with the adaptive flag', () => {
const out = normalizeThinking({ thinking: { type: 'adaptive', effort: 'medium' } });
expect(out).toMatchObject({ adaptive: true, enabled: true, effort: 'medium' });
});
it('treats `thinking.budget` as budget', () => {
const out = normalizeThinking({ thinking: { type: 'enabled', budget: 5000 } });
expect(out).toMatchObject({ enabled: true, budget: 5000 });
});
it('merges explicit effort into enabled-implied view when only reasoning_effort is set', () => {
const out = normalizeThinking({ reasoning_effort: 'low' });
expect(out).toMatchObject({ enabled: true, effort: 'low' });
});
});
describe('anthropicThinking', () => {
it('emits thinking=enabled with budget_tokens when budget is set', () => {
const out = anthropicThinking({
enabled: true, effort: 'high', budget: 4000, display: 'summarized',
});
expect(out.thinking).toEqual({ type: 'enabled', budget_tokens: 4000, display: 'summarized' });
expect(out.output_config).toEqual({ effort: 'high' });
});
it('emits thinking=adaptive + output_config.effort on adaptive mode', () => {
const out = anthropicThinking({ adaptive: true, effort: 'medium' });
expect(out.thinking).toEqual({ type: 'adaptive' });
expect(out.output_config).toEqual({ effort: 'medium' });
});
it('honors explicit disabled', () => {
const out = anthropicThinking({ enabled: false });
expect(out.thinking).toEqual({ type: 'disabled' });
expect(out.output_config).toBeUndefined();
});
it('omits fields when nothing was set', () => {
expect(anthropicThinking(undefined)).toEqual({});
});
});
describe('geminiThinkingConfig', () => {
it('emits thinkingBudget=0 to disable on 2.5-series', () => {
expect(geminiThinkingConfig({ enabled: false }, 'gemini-2.5-flash'))
.toEqual({ thinkingBudget: 0 });
});
it('emits thinkingLevel=minimal to disable on 3-series', () => {
expect(geminiThinkingConfig({ enabled: false }, 'gemini-3-flash'))
.toEqual({ thinkingLevel: 'minimal' });
});
it('emits thinkingLevel on 3-series when effort is set', () => {
expect(geminiThinkingConfig({ enabled: true, effort: 'medium' }, 'gemini-3-flash'))
.toMatchObject({ includeThoughts: true, thinkingLevel: 'medium' });
});
it('emits thinkingBudget on 2.5-series when effort is set', () => {
expect(geminiThinkingConfig({ enabled: true, effort: 'low' }, 'gemini-2.5-flash'))
.toMatchObject({ includeThoughts: true, thinkingBudget: 2048 });
});
it('uses explicit budget when no effort is set', () => {
expect(geminiThinkingConfig({ enabled: true, budget: 4096 }, 'gemini-3-pro'))
.toMatchObject({ includeThoughts: true, thinkingBudget: 4096 });
});
it('returns undefined when neither enabled nor disabled', () => {
expect(geminiThinkingConfig(undefined, 'gemini-3-flash')).toBeUndefined();
});
});
describe('openAiCompatThinkingPolicy', () => {
it('returns "glm_mapped" for GLM host platforms', () => {
expect(openAiCompatThinkingPolicy('modalresearch')).toBe('glm_mapped');
expect(openAiCompatThinkingPolicy('z-ai')).toBe('glm_mapped');
expect(openAiCompatThinkingPolicy('zai')).toBe('glm_mapped');
expect(openAiCompatThinkingPolicy('bluesminds')).toBe('glm_mapped');
expect(openAiCompatThinkingPolicy('zhipu')).toBe('glm_mapped');
});
it('returns "reasoning_effort_only" for unknown hosts (safe default)', () => {
expect(openAiCompatThinkingPolicy('nvidia')).toBe('reasoning_effort_only');
expect(openAiCompatThinkingPolicy('groq')).toBe('reasoning_effort_only');
expect(openAiCompatThinkingPolicy('some-future-provider')).toBe('reasoning_effort_only');
});
it('returns "glm_mapped" for a GLM model id even on a non-GLM host', () => {
// nvidia/z-ai/glm-5.1 — a GLM model hosted on NVIDIA NIM must still get
// GLM's narrow effort enum; the rich `thinking` object would 400. (#292)
expect(openAiCompatThinkingPolicy('nvidia', 'z-ai/glm-5.1')).toBe('glm_mapped');
expect(openAiCompatThinkingPolicy('openrouter', 'zai-org/glm-5.1-fp8')).toBe('glm_mapped');
});
});
describe('isGlmModel', () => {
it('matches GLM 4.x and 5.x ids with org prefixes', () => {
expect(isGlmModel('z-ai/glm-5.1')).toBe(true);
expect(isGlmModel('zai-org/GLM-5.1-FP8')).toBe(true);
expect(isGlmModel('glm-4.5-flash')).toBe(true);
expect(isGlmModel('glm-4-plus')).toBe(true);
});
it('does not match unrelated ids that merely contain "glm"', () => {
expect(isGlmModel('glmist-7b')).toBe(false);
expect(isGlmModel('llama-3.3-70b')).toBe(false);
});
});
describe('openaiCompatThinkingBody', () => {
it('emits reasoning_effort shorthand when only that is set (default policy)', () => {
const out = openaiCompatThinkingBody('reasoning_effort_only', { reasoning_effort: 'medium' });
expect(out).toEqual({ reasoning_effort: 'medium' });
});
it('derives reasoning_effort from thinking.effort when only the rich object was sent', () => {
const out = openaiCompatThinkingBody(
'reasoning_effort_only',
{ thinking: { type: 'enabled', effort: 'high' } },
);
expect(out).toEqual({ reasoning_effort: 'high' });
});
it('clamps unsupported effort levels into GLM range under "glm_mapped" — never disables thinking (#292)', () => {
// GLM accepts ONLY low|medium|high. max/xhigh → high, minimal → low.
// Thinking stays ON; the user's more/less intent is preserved.
expect(openaiCompatThinkingBody('glm_mapped', { reasoning_effort: 'max' })).toEqual({ reasoning_effort: 'high' });
expect(openaiCompatThinkingBody('glm_mapped', { reasoning_effort: 'xhigh' })).toEqual({ reasoning_effort: 'high' });
expect(openaiCompatThinkingBody('glm_mapped', { reasoning_effort: 'minimal' })).toEqual({ reasoning_effort: 'low' });
expect(openaiCompatThinkingBody('glm_mapped', { reasoning_effort: 'low' })).toEqual({ reasoning_effort: 'low' });
expect(openaiCompatThinkingBody('glm_mapped', { reasoning_effort: 'medium' })).toEqual({ reasoning_effort: 'medium' });
expect(openaiCompatThinkingBody('glm_mapped', { reasoning_effort: 'high' })).toEqual({ reasoning_effort: 'high' });
});
it('DROPS the rich thinking object for GLM (glm_mapped) but keeps the mapped effort', () => {
// The rich `thinking` object (type/effort/budget) is what triggers GLM's
// literal_error — it must never be forwarded. The effort is extracted and
// mapped to GLM's enum instead. (#292)
expect(openaiCompatThinkingBody(
'glm_mapped',
{ reasoning_effort: 'xhigh', thinking: { type: 'enabled', effort: 'xhigh', budget: 4000 } },
)).toEqual({ reasoning_effort: 'high' });
// effort from the rich object alone (no top-level reasoning_effort):
expect(openaiCompatThinkingBody(
'glm_mapped',
{ thinking: { type: 'enabled', effort: 'minimal' } },
)).toEqual({ reasoning_effort: 'low' });
});
it('emits nothing for GLM when no effort was requested (GLM default = thinking on)', () => {
expect(openaiCompatThinkingBody('glm_mapped', undefined)).toEqual({});
});
it('forwards both fields verbatim under the "both" policy', () => {
const obj = { type: 'enabled' as const, effort: 'high' as const, budget: 4000 };
const out = openaiCompatThinkingBody('both', { reasoning_effort: 'high', thinking: obj });
expect(out).toEqual({ reasoning_effort: 'high', thinking: obj });
});
it('returns an empty object when no thinking info is present', () => {
expect(openaiCompatThinkingBody('reasoning_effort_only', undefined)).toEqual({});
});
});
|