Skip to content
Merged
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
178 changes: 178 additions & 0 deletions src/services/api/openaiShim.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7993,6 +7993,81 @@ test('DeepSeek sends thinking toggle and normalized reasoning effort', async ()
expect(requestBody?.store).toBeUndefined()
})

test('NVIDIA NIM DeepSeek sends chat template thinking kwargs', async () => {
process.env.OPENAI_BASE_URL = 'https://integrate.api.nvidia.com/v1'
process.env.NVIDIA_API_KEY = 'nvapi-test'

let requestBody: Record<string, unknown> | undefined
globalThis.fetch = (async (_input, init) => {
requestBody = JSON.parse(String(init?.body))
return new Response(
JSON.stringify({
id: 'chatcmpl-1',
model: 'deepseek-ai/deepseek-v4-pro',
choices: [
{ message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' },
],
usage: { prompt_tokens: 3, completion_tokens: 1, total_tokens: 4 },
}),
{ headers: { 'Content-Type': 'application/json' } },
)
}) as unknown as FetchType

const client = createOpenAIShimClient({
reasoningEffort: 'xhigh',
}) as OpenAIShimClient
await client.beta.messages.create({
model: 'deepseek-ai/deepseek-v4-pro',
system: 'test',
messages: [{ role: 'user', content: 'hi' }],
max_tokens: 64,
stream: false,
thinking: { type: 'enabled' },
})

expect(requestBody?.thinking).toEqual({ type: 'enabled' })
expect(requestBody?.reasoning_effort).toBe('max')
expect(requestBody?.chat_template_kwargs).toEqual({
thinking: true,
enable_thinking: true,
})
})
Comment thread
coderabbitai[bot] marked this conversation as resolved.

test('NVIDIA NIM DeepSeek omits chat template thinking kwargs when thinking is disabled', async () => {
process.env.OPENAI_BASE_URL = 'https://integrate.api.nvidia.com/v1'
process.env.NVIDIA_API_KEY = 'nvapi-test'

let requestBody: Record<string, unknown> | undefined
globalThis.fetch = (async (_input, init) => {
requestBody = JSON.parse(String(init?.body))
return new Response(
JSON.stringify({
id: 'chatcmpl-1',
model: 'deepseek-ai/deepseek-v4-pro',
choices: [
{ message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' },
],
}),
{ headers: { 'Content-Type': 'application/json' } },
)
}) as unknown as FetchType

const client = createOpenAIShimClient({
reasoningEffort: 'xhigh',
}) as OpenAIShimClient
await client.beta.messages.create({
model: 'deepseek-ai/deepseek-v4-pro?thinking=disabled',
system: 'test',
messages: [{ role: 'user', content: 'hi' }],
max_tokens: 64,
stream: false,
})

expect(requestBody?.thinking).toBeUndefined()
expect(requestBody?.reasoning_effort).toBeUndefined()
expect(requestBody?.chat_template_kwargs).toBeUndefined()
})

test('DeepSeek omits thinking controls when the Anthropic-side request does not set them', async () => {
process.env.OPENAI_BASE_URL = 'https://api.deepseek.com/v1'
process.env.OPENAI_API_KEY = 'sk-deepseek'
Expand Down Expand Up @@ -8565,6 +8640,109 @@ test('Z.AI GLM-5.2: per-turn thinking overrides model-query default', async () =
expect(requestBody?.reasoning_effort).toBe('high')
})

test('NVIDIA NIM Z.AI GLM sends chat template thinking kwargs', async () => {
process.env.OPENAI_BASE_URL = 'https://integrate.api.nvidia.com/v1'
process.env.NVIDIA_API_KEY = 'nvapi-test'

let requestBody: Record<string, unknown> | undefined
globalThis.fetch = (async (_input, init) => {
requestBody = JSON.parse(String(init?.body))
return new Response(
JSON.stringify({
id: 'chatcmpl-1',
model: 'z-ai/glm-5.2',
choices: [
{ message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' },
],
}),
{ headers: { 'Content-Type': 'application/json' } },
)
}) as unknown as FetchType

const client = createOpenAIShimClient({
reasoningEffort: 'xhigh',
}) as OpenAIShimClient
await client.beta.messages.create({
model: 'z-ai/glm-5.2',
messages: [{ role: 'user', content: 'hi' }],
max_tokens: 64,
stream: false,
})

expect(requestBody?.thinking).toEqual({ type: 'enabled' })
expect(requestBody?.reasoning_effort).toBe('max')
expect(requestBody?.chat_template_kwargs).toEqual({
thinking: true,
enable_thinking: true,
})
})

test('NVIDIA NIM Z.AI GLM omits chat template thinking kwargs without a reasoning request', async () => {
process.env.OPENAI_BASE_URL = 'https://integrate.api.nvidia.com/v1'
process.env.NVIDIA_API_KEY = 'nvapi-test'

let requestBody: Record<string, unknown> | undefined
globalThis.fetch = (async (_input, init) => {
requestBody = JSON.parse(String(init?.body))
return new Response(
JSON.stringify({
id: 'chatcmpl-1',
model: 'z-ai/glm-5.2',
choices: [
{ message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' },
],
}),
{ headers: { 'Content-Type': 'application/json' } },
)
}) as unknown as FetchType

const client = createOpenAIShimClient({}) as OpenAIShimClient
await client.beta.messages.create({
model: 'z-ai/glm-5.2',
messages: [{ role: 'user', content: 'hi' }],
max_tokens: 64,
stream: false,
})

expect(requestBody?.thinking).toBeUndefined()
expect(requestBody?.reasoning_effort).toBeUndefined()
expect(requestBody?.chat_template_kwargs).toBeUndefined()
})

test('NVIDIA NIM Z.AI GLM omits chat template thinking kwargs when thinking is disabled', async () => {
process.env.OPENAI_BASE_URL = 'https://integrate.api.nvidia.com/v1'
process.env.NVIDIA_API_KEY = 'nvapi-test'

let requestBody: Record<string, unknown> | undefined
globalThis.fetch = (async (_input, init) => {
requestBody = JSON.parse(String(init?.body))
return new Response(
JSON.stringify({
id: 'chatcmpl-1',
model: 'z-ai/glm-5.2',
choices: [
{ message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' },
],
}),
{ headers: { 'Content-Type': 'application/json' } },
)
}) as unknown as FetchType

const client = createOpenAIShimClient({
reasoningEffort: 'xhigh',
}) as OpenAIShimClient
await client.beta.messages.create({
model: 'z-ai/glm-5.2?thinking=disabled',
messages: [{ role: 'user', content: 'hi' }],
max_tokens: 64,
stream: false,
})

expect(requestBody?.thinking).toEqual({ type: 'disabled' })
expect(requestBody?.reasoning_effort).toBeUndefined()
expect(requestBody?.chat_template_kwargs).toBeUndefined()
})

test('Z.AI GLM-5.2: streaming requests with tools send tool_stream', async () => {
process.env.OPENAI_BASE_URL = 'https://api.z.ai/api/coding/paas/v4'
process.env.OPENAI_API_KEY = 'sk-zai-test'
Expand Down
43 changes: 43 additions & 0 deletions src/services/api/openaiShim.ts
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,47 @@ export function hasMistralApiHost(baseUrl: string | undefined): boolean {
}
}

function hasNvidiaNimApiHost(baseUrl: string | undefined): boolean {
if (!baseUrl) return false

try {
return new URL(baseUrl).hostname.toLowerCase() === 'integrate.api.nvidia.com'
} catch {
return false
}
}

function setNvidiaNimChatTemplateThinking(body: Record<string, unknown>): void {
const existing = body.chat_template_kwargs
const kwargs =
existing && typeof existing === 'object' && !Array.isArray(existing)
? { ...(existing as Record<string, unknown>) }
: {}

kwargs.thinking = true
kwargs.enable_thinking = true
body.chat_template_kwargs = kwargs
}

function maybeSetNvidiaNimChatTemplateThinking(
body: Record<string, unknown>,
baseUrl: string | undefined,
reasoningRequestPlan: {
thinkingType?: string
reasoningEffort?: string
},
): void {
if (!hasNvidiaNimApiHost(baseUrl)) return
if (
reasoningRequestPlan.thinkingType !== 'enabled' &&
!reasoningRequestPlan.reasoningEffort
) {
return
}

setNvidiaNimChatTemplateThinking(body)
}

function formatRetryAfterHint(response: Response): string {
const ra = response.headers.get('retry-after')
return ra ? ` (Retry-After: ${ra})` : ''
Expand Down Expand Up @@ -3756,6 +3797,7 @@ class OpenAIShimMessages {
if (reasoningRequestPlan.reasoningEffort) {
body.reasoning_effort = reasoningRequestPlan.reasoningEffort
}
maybeSetNvidiaNimChatTemplateThinking(body, request.baseUrl, reasoningRequestPlan)
}

if (reasoningRequestPlan.wireFormat === 'zai_compatible') {
Expand All @@ -3769,6 +3811,7 @@ class OpenAIShimMessages {
} else {
delete body.reasoning_effort
}
maybeSetNvidiaNimChatTemplateThinking(body, request.baseUrl, reasoningRequestPlan)
}

// Route/model strip rules are authoritative even when compatibility
Expand Down
115 changes: 115 additions & 0 deletions src/utils/providerProfile.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,121 @@ test('buildStartupEnvFromProfile preserves explicit OpenAI-compatible env withou
assert.equal(isDefaultStartupProviderEnv(env), false)
})

test('buildStartupEnvFromProfile preserves concrete env-only NIM setup over stale profile', async () => {
const processEnv = {
OPENAI_BASE_URL: 'https://integrate.api.nvidia.com/v1',
OPENAI_MODEL: 'qwen/qwen3.5-397b-a17b',
NVIDIA_API_KEY: 'nvapi-live',
NVIDIA_NIM: '1',
}

const env = await buildStartupEnvFromProfile({
persisted: profile('openai', {
OPENAI_BASE_URL: 'https://integrate.api.nvidia.com/v1',
OPENAI_MODEL: 'z-ai/glm-5.2',
NVIDIA_API_KEY: 'nvapi-stale',
NVIDIA_NIM: '1',
}),
processEnv,
})

assert.notEqual(env, processEnv)
assert.equal(env.CLAUDE_CODE_USE_OPENAI, '1')
assert.equal(env.CLAUDE_CODE_PROVIDER_ROUTE_ID, 'nvidia-nim')
assert.equal(env.OPENAI_MODEL, 'qwen/qwen3.5-397b-a17b')
assert.equal(env.OPENAI_BASE_URL, 'https://integrate.api.nvidia.com/v1')
assert.equal(env.NVIDIA_API_KEY, 'nvapi-live')
assert.equal(env.NVIDIA_NIM, '1')
assert.equal(resolveActiveRouteIdFromEnv(env), 'nvidia-nim')
})
Comment thread
coderabbitai[bot] marked this conversation as resolved.

test('buildStartupEnvFromProfile does not activate non-NIM env-only OpenAI-compatible setup', async () => {
const env = await buildStartupEnvFromProfile({
persisted: null,
processEnv: {
OPENAI_BASE_URL: 'https://openrouter.ai/api/v1',
OPENAI_MODEL: 'openrouter/zhipu/glm-5.2',
OPENAI_API_KEY: 'sk-live',
},
})

assert.equal(env.CLAUDE_CODE_USE_OPENAI, '1')
assert.equal(env.CLAUDE_CODE_PROVIDER_ROUTE_ID, undefined)
assert.equal(env.OPENAI_BASE_URL, 'https://opengateway.gitlawb.com/v1')
assert.equal(env.OPENAI_MODEL, 'mimo-v2.5-pro')
assert.equal(env.OPENAI_API_KEY, undefined)
assert.equal(resolveActiveRouteIdFromEnv(env), 'gitlawb-opengateway')
assert.equal(isDefaultStartupProviderEnv(env), true)
})

test('buildStartupEnvFromProfile documents no-flag Gemini env does not beat concrete NIM setup', async () => {
const processEnv = {
GEMINI_API_KEY: 'gemini-live',
GEMINI_MODEL: 'gemini-2.5-flash',
OPENAI_BASE_URL: 'https://integrate.api.nvidia.com/v1',
OPENAI_MODEL: 'qwen/qwen3.5-397b-a17b',
NVIDIA_API_KEY: 'nvapi-live',
NVIDIA_NIM: '1',
}

const env = await buildStartupEnvFromProfile({
persisted: null,
processEnv,
})

assert.notEqual(env, processEnv)
assert.equal(env.CLAUDE_CODE_USE_OPENAI, '1')
assert.equal(env.CLAUDE_CODE_USE_GEMINI, undefined)
assert.equal(env.CLAUDE_CODE_PROVIDER_ROUTE_ID, 'nvidia-nim')
assert.equal(env.GEMINI_API_KEY, undefined)
assert.equal(env.OPENAI_MODEL, 'qwen/qwen3.5-397b-a17b')
assert.equal(resolveActiveRouteIdFromEnv(env), 'nvidia-nim')
})

test('buildStartupEnvFromProfile preserves explicit OpenAI opt-out over concrete env-only NIM setup', async () => {
const processEnv: NodeJS.ProcessEnv = {
CLAUDE_CODE_USE_OPENAI: '0',
OPENAI_BASE_URL: 'https://integrate.api.nvidia.com/v1',
OPENAI_MODEL: 'qwen/qwen3.5-397b-a17b',
NVIDIA_API_KEY: 'nvapi-live',
NVIDIA_NIM: '1',
}

const env = await buildStartupEnvFromProfile({
persisted: null,
processEnv,
})

assert.equal(env, processEnv)
assert.equal(env.CLAUDE_CODE_USE_OPENAI, '0')
assert.equal(env.CLAUDE_CODE_PROVIDER_ROUTE_ID, undefined)
assert.equal(isDefaultStartupProviderEnv(env), false)
})

test('buildStartupEnvFromProfile preserves explicit Gemini selection over concrete env-only NIM setup', async () => {
const processEnv: NodeJS.ProcessEnv = {
CLAUDE_CODE_USE_GEMINI: '1',
GEMINI_API_KEY: 'gemini-live',
GEMINI_MODEL: 'gemini-2.5-flash',
OPENAI_BASE_URL: 'https://integrate.api.nvidia.com/v1',
OPENAI_MODEL: 'qwen/qwen3.5-397b-a17b',
NVIDIA_API_KEY: 'nvapi-live',
NVIDIA_NIM: '1',
}

const env = await buildStartupEnvFromProfile({
persisted: null,
processEnv,
})

assert.equal(env, processEnv)
assert.equal(env.CLAUDE_CODE_USE_GEMINI, '1')
assert.equal(env.CLAUDE_CODE_USE_OPENAI, undefined)
assert.equal(env.CLAUDE_CODE_PROVIDER_ROUTE_ID, undefined)
assert.equal(resolveActiveRouteIdFromEnv(env), 'gemini')
assert.equal(isDefaultStartupProviderEnv(env), false)
})
Comment thread
coderabbitai[bot] marked this conversation as resolved.

test('buildStartupEnvFromProfile respects an explicit CLAUDE_CODE_USE_OPENAI=0 opt-out (issue #1245)', async () => {
const env = await buildStartupEnvFromProfile({
persisted: null,
Expand Down
Loading