Skip to content

Commit 4d4af4a

Browse files
fix(vscode-ide-companion): fix blank screen in VS Code 0.14.1 webview (#2959)
* fix(vscode-ide-companion): avoid pulling Node.js modules into webview bundle - Add wildcard to esbuild external config to exclude deep sub-path imports - Inline isSupportedImageMimeType to remove core package dependency - Create browser-safe tokenLimits.ts to avoid dynamic require at runtime - Update App.tsx to use local tokenLimits module Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * chore(vscode-ide-companion): bump version to 0.14.2 Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
1 parent 9b22c9f commit 4d4af4a

5 files changed

Lines changed: 229 additions & 8 deletions

File tree

packages/vscode-ide-companion/esbuild.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,12 @@ async function main() {
179179
// Since @qwen-code/webui marks it as external in its own Vite build, the
180180
// browser bundle must also mark it external to avoid bundling Node.js-only
181181
// modules (undici, @grpc/grpc-js, fs, stream, etc.) into the webview.
182-
external: ['@qwen-code/qwen-code-core'],
182+
// The wildcard ensures deep sub-path imports (e.g.
183+
// '@qwen-code/qwen-code-core/src/core/tokenLimits.js') are also excluded;
184+
// without it esbuild only matches the bare package name and attempts to
185+
// bundle the sub-path, which triggers "Dynamic require is not supported"
186+
// at runtime in the browser.
187+
external: ['@qwen-code/qwen-code-core', '@qwen-code/qwen-code-core/*'],
183188
logLevel: 'silent',
184189
plugins: [reactDedupPlugin, cssInjectPlugin, esbuildProblemMatcherPlugin],
185190
jsx: 'automatic', // Use new JSX transform (React 17+)

packages/vscode-ide-companion/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "qwen-code-vscode-ide-companion",
33
"displayName": "Qwen Code Companion",
44
"description": "Enable Qwen Code with direct access to your VS Code workspace.",
5-
"version": "0.14.1",
5+
"version": "0.14.2",
66
"publisher": "qwenlm",
77
"icon": "assets/icon.png",
88
"repository": {

packages/vscode-ide-companion/src/utils/imageSupport.ts

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44
* SPDX-License-Identifier: Apache-2.0
55
*/
66

7-
import { isSupportedImageMimeType } from '@qwen-code/qwen-code-core/src/utils/request-tokenizer/supportedImageFormats.js';
8-
97
// ---------- Types ----------
108

119
export interface ImageAttachment {
@@ -61,6 +59,31 @@ export function unescapePath(filePath: string): string {
6159
);
6260
}
6361

62+
// ---------- Supported image MIME types ----------
63+
// Inlined from @qwen-code/qwen-code-core to avoid pulling Node.js-only modules
64+
// into the browser webview bundle (esbuild marks core as external, but deep
65+
// sub-path imports like core/src/utils/... bypass the external filter and cause
66+
// "Dynamic require is not supported" at runtime).
67+
68+
const SUPPORTED_IMAGE_MIME_TYPES: readonly string[] = [
69+
'image/bmp',
70+
'image/jpeg',
71+
'image/jpg',
72+
'image/png',
73+
'image/tiff',
74+
'image/webp',
75+
'image/heic',
76+
];
77+
78+
/**
79+
* Check whether a MIME type is supported for pasted-image processing.
80+
* @param mimeType - The MIME type string to validate
81+
* @returns `true` when the type is in the supported list
82+
*/
83+
function isSupportedImageMimeType(mimeType: string): boolean {
84+
return SUPPORTED_IMAGE_MIME_TYPES.includes(mimeType);
85+
}
86+
6487
// ---------- Image format detection ----------
6588

6689
const PASTED_IMAGE_MIME_TO_EXTENSION: Record<string, string> = {
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
/**
2+
* @license
3+
* Copyright 2025 Qwen Team
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
/**
8+
* Browser-safe subset of @qwen-code/qwen-code-core tokenLimits.
9+
*
10+
* The webview bundle (IIFE, platform: browser) cannot `require` Node.js
11+
* packages. This module replicates the constants and logic the webview
12+
* actually uses so that the core package never needs to be pulled into the
13+
* browser bundle.
14+
*
15+
* Keep this file in sync with:
16+
* packages/core/src/core/tokenLimits.ts
17+
*/
18+
19+
type TokenCount = number;
20+
21+
// ---------------------------------------------------------------------------
22+
// Public constants
23+
// ---------------------------------------------------------------------------
24+
25+
/** Default input context window size: 128 K tokens (power-of-two). */
26+
export const DEFAULT_TOKEN_LIMIT: TokenCount = 131_072;
27+
28+
// ---------------------------------------------------------------------------
29+
// Token limit types
30+
// ---------------------------------------------------------------------------
31+
32+
export type TokenLimitType = 'input' | 'output';
33+
34+
// ---------------------------------------------------------------------------
35+
// Internal constants
36+
// ---------------------------------------------------------------------------
37+
38+
const LIMITS = {
39+
'32k': 32_768,
40+
'64k': 65_536,
41+
'128k': 131_072,
42+
'192k': 196_608,
43+
'200k': 200_000,
44+
'256k': 262_144,
45+
'272k': 272_000,
46+
'400k': 400_000,
47+
'512k': 524_288,
48+
'1m': 1_000_000,
49+
'4k': 4_096,
50+
'8k': 8_192,
51+
'16k': 16_384,
52+
} as const;
53+
54+
const DEFAULT_OUTPUT_TOKEN_LIMIT: TokenCount = 32_000;
55+
56+
// ---------------------------------------------------------------------------
57+
// Model name normaliser
58+
// ---------------------------------------------------------------------------
59+
60+
/**
61+
* Robust normaliser: strips provider prefixes, pipes/colons, date/version
62+
* suffixes, quantisation markers, etc.
63+
* @param model - Raw model identifier string
64+
* @returns Normalised lowercase model name
65+
*/
66+
function normalize(model: string): string {
67+
let s = (model ?? '').toLowerCase().trim();
68+
69+
s = s.replace(/^.*\//, '');
70+
s = s.split('|').pop() ?? s;
71+
s = s.split(':').pop() ?? s;
72+
s = s.replace(/\s+/g, '-');
73+
s = s.replace(/-preview/g, '');
74+
75+
if (
76+
!s.match(/^qwen-(?:plus|flash|vl-max)-latest$/) &&
77+
!s.match(/^kimi-k2-\d{4}$/)
78+
) {
79+
s = s.replace(
80+
/-(?:\d{4,}|\d+x\d+b|v\d+(?:\.\d+)*|(?<=-[^-]+-)\d+(?:\.\d+)+|latest|exp)$/g,
81+
'',
82+
);
83+
}
84+
85+
s = s.replace(/-(?:\d?bit|int[48]|bf16|fp16|q[45]|quantized)$/g, '');
86+
return s;
87+
}
88+
89+
// ---------------------------------------------------------------------------
90+
// Input context-window patterns (most specific → most general)
91+
// ---------------------------------------------------------------------------
92+
93+
const INPUT_PATTERNS: Array<[RegExp, TokenCount]> = [
94+
// Google Gemini
95+
[/^gemini-3/, LIMITS['1m']],
96+
[/^gemini-/, LIMITS['1m']],
97+
98+
// OpenAI
99+
[/^gpt-5/, LIMITS['272k']],
100+
[/^gpt-/, LIMITS['128k']],
101+
[/^o\d/, LIMITS['200k']],
102+
103+
// Anthropic Claude
104+
[/^claude-/, LIMITS['200k']],
105+
106+
// Alibaba / Qwen
107+
[/^qwen3-coder-plus/, LIMITS['1m']],
108+
[/^qwen3-coder-flash/, LIMITS['1m']],
109+
[/^qwen3\.\d/, LIMITS['1m']],
110+
[/^qwen-plus-latest$/, LIMITS['1m']],
111+
[/^qwen-flash-latest$/, LIMITS['1m']],
112+
[/^coder-model$/, LIMITS['1m']],
113+
[/^qwen3-max/, LIMITS['256k']],
114+
[/^qwen3-coder-/, LIMITS['256k']],
115+
[/^qwen/, LIMITS['256k']],
116+
117+
// DeepSeek
118+
[/^deepseek/, LIMITS['128k']],
119+
120+
// Zhipu GLM
121+
[/^glm-5/, 202_752 as TokenCount],
122+
[/^glm-/, 202_752 as TokenCount],
123+
124+
// MiniMax
125+
[/^minimax-m2\.5/i, LIMITS['192k']],
126+
[/^minimax-/i, LIMITS['200k']],
127+
128+
// Moonshot / Kimi
129+
[/^kimi-/, LIMITS['256k']],
130+
131+
// ByteDance Seed-OSS
132+
[/^seed-oss/, LIMITS['512k']],
133+
];
134+
135+
// ---------------------------------------------------------------------------
136+
// Output token-limit patterns
137+
// ---------------------------------------------------------------------------
138+
139+
const OUTPUT_PATTERNS: Array<[RegExp, TokenCount]> = [
140+
[/^gemini-3/, LIMITS['64k']],
141+
[/^gemini-/, LIMITS['8k']],
142+
143+
[/^gpt-5/, LIMITS['128k']],
144+
[/^gpt-/, LIMITS['16k']],
145+
[/^o\d/, LIMITS['128k']],
146+
147+
[/^claude-opus-4-6/, LIMITS['128k']],
148+
[/^claude-sonnet-4-6/, LIMITS['64k']],
149+
[/^claude-/, LIMITS['64k']],
150+
151+
[/^qwen3\.\d/, LIMITS['64k']],
152+
[/^coder-model$/, LIMITS['64k']],
153+
[/^qwen/, LIMITS['32k']],
154+
155+
[/^deepseek-reasoner/, LIMITS['64k']],
156+
[/^deepseek-r1/, LIMITS['64k']],
157+
[/^deepseek-chat/, LIMITS['8k']],
158+
159+
[/^glm-5/, LIMITS['16k']],
160+
[/^glm-4\.7/, LIMITS['16k']],
161+
162+
[/^minimax-m2\.5/i, LIMITS['64k']],
163+
164+
[/^kimi-k2\.5/, LIMITS['32k']],
165+
];
166+
167+
// ---------------------------------------------------------------------------
168+
// Public API
169+
// ---------------------------------------------------------------------------
170+
171+
/**
172+
* Return the token limit for a given model name.
173+
*
174+
* This is a browser-safe mirror of `tokenLimit()` in
175+
* `@qwen-code/qwen-code-core`. The webview only calls this as a fallback
176+
* when `modelInfo._meta.contextLimit` is unavailable.
177+
*
178+
* @param model - The model identifier string
179+
* @param type - 'input' for context window, 'output' for generation limit
180+
* @returns Maximum token count for the model and type
181+
*/
182+
export function tokenLimit(
183+
model: string,
184+
type: TokenLimitType = 'input',
185+
): TokenCount {
186+
const norm = normalize(model);
187+
const patterns = type === 'output' ? OUTPUT_PATTERNS : INPUT_PATTERNS;
188+
189+
for (const [regex, limit] of patterns) {
190+
if (regex.test(norm)) {
191+
return limit;
192+
}
193+
}
194+
195+
return type === 'output' ? DEFAULT_OUTPUT_TOKEN_LIMIT : DEFAULT_TOKEN_LIMIT;
196+
}

packages/vscode-ide-companion/src/webview/App.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,7 @@ import type { ApprovalModeValue } from '../types/approvalModeValueTypes.js';
5252
import type { PlanEntry, UsageStatsPayload } from '../types/chatTypes.js';
5353
import type { ModelInfo, AvailableCommand } from '@agentclientprotocol/sdk';
5454
import type { Question } from '../types/acpTypes.js';
55-
import {
56-
DEFAULT_TOKEN_LIMIT,
57-
tokenLimit,
58-
} from '@qwen-code/qwen-code-core/src/core/tokenLimits.js';
55+
import { DEFAULT_TOKEN_LIMIT, tokenLimit } from '../utils/tokenLimits.js';
5956
import { useImagePaste, type WebViewImageMessage } from './hooks/useImage.js';
6057

6158
export const App: React.FC = () => {

0 commit comments

Comments
 (0)