From 14938495b50b22c18ae935ab8ae558e971bb75c8 Mon Sep 17 00:00:00 2001 From: James Padgett Date: Mon, 27 Apr 2026 00:46:48 -0400 Subject: [PATCH 1/2] fix: add NVIDIA API host to reasoning_content allowlist for DeepSeek models --- src/services/api/openaiShim.ts | 88 ++++++++++++++++++++++++++++++++-- 1 file changed, 84 insertions(+), 4 deletions(-) diff --git a/src/services/api/openaiShim.ts b/src/services/api/openaiShim.ts index 015c0e2e4b..02389398ed 100644 --- a/src/services/api/openaiShim.ts +++ b/src/services/api/openaiShim.ts @@ -99,6 +99,11 @@ const KIMI_CODE_API_HOST = 'api.kimi.com' const DEEPSEEK_API_HOSTS = new Set([ 'api.deepseek.com', ]) + +/** API hosts that run reasoning models requiring `reasoning_content` echo-back. */ +const REASONING_API_HOSTS = new Set([ + 'integrate.api.nvidia.com', +]) const COPILOT_HEADERS: Record = { 'User-Agent': 'GitHubCopilotChat/0.26.7', 'Editor-Version': 'vscode/1.99.3', @@ -187,6 +192,54 @@ function isDeepSeekBaseUrl(baseUrl: string | undefined): boolean { } } +function isReasoningApiHost(baseUrl: string | undefined): boolean { + if (!baseUrl) return false + try { + return REASONING_API_HOSTS.has(new URL(baseUrl).hostname.toLowerCase()) + } catch { + return false + } +} + +/** + * Combined check: returns true when the provider is recognized as one capable + * of emitting `reasoning_content` on assistant messages. + * + * Uses a two-tier approach: + * 1. Explicit host allowlist — fast path for known reasoning providers + * (DeepSeek, Moonshot, Z.AI, NVIDIA). + * 2. History-based heuristic — when the host is unknown but there's already + * a `thinking` block in the conversation. This handles edge cases where + * a reasoning model is served behind an unlisted host, while avoiding + * false positives for providers that never return reasoning content + * (specifically api.openai.com). + */ +function providerSupportsReasoning( + baseUrl: string | undefined, + messages: Array<{ + role: string + message?: { role?: string; content?: unknown } + content?: unknown + }>, +): boolean { + if ( + isMoonshotCompatibleBaseUrl(baseUrl) || + isDeepSeekBaseUrl(baseUrl) || + isZaiBaseUrl(baseUrl) || + isReasoningApiHost(baseUrl) + ) { + return true + } + if (!baseUrl) return false + try { + const host = new URL(baseUrl).hostname.toLowerCase() + if (host === 'api.openai.com') return false + } catch { + return false + } + return hasThinkingBlockInHistory(messages) +} + function normalizeDeepSeekReasoningEffort( effort: 'low' | 'medium' | 'high' | 'xhigh', ): 'high' | 'max' { @@ -230,6 +283,33 @@ function sleepMs(ms: number): Promise { return new Promise(resolve => setTimeout(resolve, ms)) } +/** + * Check whether the conversation history contains any assistant message with a + * `thinking` block — meaning a prior turn already received reasoning content + * from the API. When combined with `tool_use` blocks, the outgoing request + * must echo `reasoning_content` on those assistant messages for providers + * (like DeepSeek, Kimi, Moonshot, NVIDIA-hosted reasoning models) that + * require continuity of reasoning content across tool-call rounds. + */ +function hasThinkingBlockInHistory( + messages: Array<{ + role: string + message?: { role?: string; content?: unknown } + content?: unknown + }>, +): boolean { + for (const msg of messages) { + const content = msg.content ?? msg.message?.content + if (!Array.isArray(content)) continue + + const types = content.map(c => (c as Record).type).filter(Boolean) + if (types.includes('thinking') || types.includes('redacted_thinking')) { + return true + } + } + return false +} + // --------------------------------------------------------------------------- // Types — minimal subset of Anthropic SDK types we need to produce // --------------------------------------------------------------------------- @@ -1525,10 +1605,10 @@ class OpenAIShimMessages { // reasoning_content when its thinking feature is active. DeepSeek does // the same for tool-call turns in thinking mode. Echo it back from the // thinking block we captured on the inbound response. - preserveReasoningContent: - isMoonshotCompatibleBaseUrl(request.baseUrl) || - isDeepSeekBaseUrl(request.baseUrl) || - isZaiBaseUrl(request.baseUrl), + preserveReasoningContent: providerSupportsReasoning( + request.baseUrl, + compressedMessages, + ), }) const body: Record = { From ad5bc2a9af833c650ce5a7cc31450a878afe3867 Mon Sep 17 00:00:00 2001 From: SnotacusNexus Date: Fri, 1 May 2026 15:33:54 -0400 Subject: [PATCH 2/2] Add GitHub Actions workflow for Node.js with Webpack --- .github/workflows/webpack.yml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .github/workflows/webpack.yml diff --git a/.github/workflows/webpack.yml b/.github/workflows/webpack.yml new file mode 100644 index 0000000000..9626ff6d35 --- /dev/null +++ b/.github/workflows/webpack.yml @@ -0,0 +1,28 @@ +name: NodeJS with Webpack + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + build: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [18.x, 20.x, 22.x] + + steps: + - uses: actions/checkout@v4 + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + + - name: Build + run: | + npm install + npx webpack