mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-07-10 01:29:17 +00:00
* refactor(providers): unify provider config into core, remove CLI re-exports Move all ProviderConfig definitions, registry (ALL_PROVIDERS), and utility functions (buildInstallPlan, resolveBaseUrl, etc.) from packages/cli/src/auth/ into packages/core/src/providers/ so both CLI and VSCode can share the same provider system. - Add core providers module with types, presets, install logic - Rewrite VSCode AuthMessageHandler to dynamically generate provider choices from ALL_PROVIDERS instead of hardcoding 3 providers - Add applyProviderInstallPlanToFile in VSCode settingsWriter using the ProviderSettingsAdapter abstraction - Delete 11 CLI re-export wrapper files, update ~20 import sites - Keep CLI-specific applyProviderInstallPlan (uses LoadedSettings) and openrouterOAuth.ts (CLI-only OAuth runtime) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * refactor(cli): drop OpenRouter OAuth + /manage-models, simplify /auth OpenRouter now uses the standard API-key flow under "Third-party Providers" (issue #4108). The whole OpenRouter OAuth implementation (PKCE, callback server, model auto-install) and the /manage-models command (only OpenRouter was wired in; /auth Step 2 already covers model selection) are removed. /auth is renamed around the "Connect a Provider" mental model: - Dialog title is now "Connect a Provider"; the OAuth main entry is gone - handleAuthSelect (mixed close + auth trigger) is split into a single-purpose closeAuthDialog; legacy wrappers (handleSubscriptionPlanSubmit, handleApiKeyProviderSubmit, handleCustomApiKeySubmit, ...) are dropped in favor of the unified handleProviderSubmit Core: openRouterProvider switches to authMethod='input', uiGroup='third-party', ships with two recommended free models, and is reordered to the end of the third-party list to keep DeepSeek as the default highlight. Net diff: 34 files, +124 / -3835. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * refactor(auth): unify applyProviderInstallPlan in core, drop cli/auth CLI and vscode now share core's applyProviderInstallPlan instead of keeping two parallel implementations. The CLI-only env rollback (snapshot process.env, restore on error) is folded into the core version so vscode also benefits from it. CLI ships a LoadedSettingsAdapter that maps LoadedSettings to core's ProviderSettingsAdapter contract. Backup/restore is layered: write a .orig file, structuredClone settings + originalSettings, then recomputeMerged() on restore — same guarantees as before, just routed through the adapter. Tests for the install logic are migrated to core and rewritten against the adapter mock (more focused than the previous LoadedSettings/Config mocks). packages/cli/src/auth/ is gone entirely. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * refactor(providers): drop unused authMethod field from ProviderConfig Every preset has had authMethod='input' since OpenRouter switched to the standard API-key flow, making the field a dead dimension. Removing it cleans up three never-taken branches and aligns the type with reality: connecting a provider always means entering an API key. - core: remove ProviderConfig.authMethod; shouldShowStep('apiKey') is now unconditionally true; drop authMethod from 9 presets - vscode AuthMessageHandler: drop the OAuth branch in handleAuthInteractive - vscode WebViewProvider: simplify the apiKey-required guard - tests: update provider-config.test and custom-provider.test If a future provider needs a browser-based flow, the field can be re-introduced; for now the smaller surface is worth more. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * refactor(providers): prefix Alibaba plan presets with alibaba- Rename coding-plan.{ts,test.ts} → alibaba-coding-plan.{ts,test.ts} and token-plan.{ts,test.ts} → alibaba-token-plan.{ts,test.ts} so the file names line up with the existing alibaba-standard preset and make it obvious at a glance which presets belong to Alibaba ModelStudio. Export names (codingPlanProvider, tokenPlanProvider, TOKEN_PLAN_*, CODING_PLAN_*) are unchanged — only the file paths and the two imports in all-providers.ts / index.ts move. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(vscode): guard ProviderSettingsAdapter against prototype pollution The dotted-key writer in createFileSettingsAdapter walked through any segment, including __proto__/constructor/prototype, which would let a malicious or malformed ProviderInstallPlan reach Object.prototype. Refuse to write paths containing reserved segments and use hasOwnProperty when traversing intermediate objects so that inherited properties cannot redirect the walk. Addresses CodeQL alert #226 surfaced on PR #4287. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(auth): default Audio modality to off in provider advanced config In the /auth Custom Provider advanced-config step, "Enable modality" should default to Image + Video only. Audio was on by default, which implied the model accepts audio input even though most providers people configure here don't. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(auth): show base URL default as placeholder, not prefilled value In Custom Provider Step 2/6 (and on protocol switch), the base URL input started with the protocol's default URL pre-filled. Users who wanted a non-default endpoint had to manually clear the field first. Switch to placeholder semantics: the input starts empty, the default URL is shown as a hint, and submitting blank falls back to that default (then writes it back to baseUrl so downstream steps see a real value). Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * refactor(cli): rename /auth description to "Connect an LLM provider" The old description ("Configure authentication information for login") implied a Qwen-account login. After the /auth refactor it's really about picking an LLM provider and entering credentials, so the menu entry should say that. Also add 'connect' as an alt-name alongside the existing 'login' so users can type /connect when 'auth' feels wrong. Keep 'login' for muscle-memory compatibility. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * i18n(cli): translate "Connect an LLM provider" in all locales Strict-parity locales (zh, zh-TW) require every built-in command description to be translated; the renamed /auth description was falling back to English and breaking the must-translate test. Add translations for zh / zh-TW (required) and refresh the other seven locales (en, ru, de, ja, fr, ca, pt) so the old "Configure authentication information for login" key is removed everywhere rather than left as a dangling dictionary entry. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(vscode): await applyProviderInstallPlanToFile and grow test coverage Critical: applyProviderInstallPlanToFile fired the install plan with `void`, so any rejection (EACCES from persist(), prototype-pollution guard throw, etc.) was silently swallowed and WebViewProvider proceeded to disconnect/reconnect the agent as if the write had succeeded. Make the wrapper `async` and `await` it in the only caller. Tests added: - core/install.test: isSameModelIdentity fallback path (prepend-and-remove-owned with no ownsModel) — verifies models are matched on id+baseUrl, not just id. - vscode/AuthMessageHandler.test: happy-path with a fixed-baseUrl third-party provider, validateApiKey error branch, and BaseUrlOption picker presentation. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(auth): address PR #4287 review (critical + suggestion) vscode AuthMessageHandler (Critical): - Add the missing protocol-selection step so custom-provider users can pick Anthropic/Gemini instead of being silently locked to OpenAI. - Validate free-form base URL with the same /^https?:\/\// check the CLI uses; reject file:/javascript: schemes. vscode AuthMessageHandler (Suggestion): - Stop filtering separator entries from the provider QuickPick so groups (Alibaba Cloud / Third Party / Custom) actually show as headers instead of a flat list. - Treat a null authInteractiveHandler as an error: surface an authError + cancellation notification instead of silently dropping the user's input. - Call notifyAuthCancelled when validateApiKey rejects so the webview state resets and the user can retry. core/providers/presets/openrouter.ts (Critical): - Replace the substring includes() in ownsModel with a URL-hostname match so paths like https://api.example.com/openrouter.ai/v1 stop being misidentified as OpenRouter models (and getting removed on re-install). vscode/services/settingsWriter.ts (Critical): - stripTrailingCommas() so JSONC files with trailing commas (VSCode's default style) parse instead of silently returning {} and then overwriting the entire settings file. - readSettings() distinguishes ENOENT (return {}) from parse errors (log + rethrow) so a malformed file never gets clobbered. - writeSettings() writes through a temp file + fs.renameSync atomic rename, eliminating the half-written file window on EACCES / disk-full / crash. - setValue() refuses to overwrite a scalar at an intermediate path segment (would have silently destroyed e.g. {"env": "legacy-string"}). core/providers/install.ts (Suggestion): - Move settings.backup?.() inside the try block so a backup failure still triggers the env-rollback path in catch. cli/config/loadedSettingsAdapter.ts (Suggestion): - Add the same UNSAFE_KEY_PARTS guard the vscode adapter has, so __proto__/constructor/prototype segments are rejected before reaching the underlying setNestedPropertySafe walker. Defense in depth: not exploitable today but the utility has no built-in guard. vscode/webview/providers/WebViewProvider.ts (Suggestion): - Hoist buildInstallPlan / applyProviderInstallPlanToFile to static imports (both modules already top-level imported); drops two per-call await import() round-trips. cli/utils/doctorChecks.ts (Suggestion): - Whitespace nit before the comma in the qwen-code-core import. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(auth): second round of PR #4287 review fixes Critical: - settingsWriter: stripTrailingCommas now uses a char-by-char scanner so literal ",]" inside a string value is preserved (the previous regex silently corrupted it). - install.ts: wrap settings.restore() in try/catch so a restore failure doesn't mask the original error or skip the env-rollback loop. - install.ts: snapshot the runtime ModelProvidersConfig before applying patches and reload it in the catch path, so an in-flight refreshAuth() failure doesn't leave the live session holding providers that were never successfully installed. - AuthMessageHandler: custom-provider Base URL is now a placeholder instead of a pre-filled value, with the default selected by the user's chosen protocol (openai/anthropic/gemini). Empty input falls back to the protocol-appropriate URL, preventing the pick-Anthropic-but-keep-OpenAI-URL footgun. Suggestion: - AuthDialog: replace the isCurrentlyCodingPlan misnomer with a uiGroup check — resolveMetadataKey returns config.id for *any* provider with a static models[], so the old guard made DeepSeek/MiniMax/OpenRouter users land on the Alibaba tab instead of Third-party Providers. - AuthMessageHandler: guard against modelIds being [] after splitting comma input (matches the CLI's "Model IDs cannot be empty."). - WebViewProvider: restore the explanatory comment for the authState === true success-toast guard that the previous diff accidentally dropped. Tests: - settingsWriter.test: new applyProviderInstallPlanToFile suite covering happy path, prototype-pollution guard (built via Object.defineProperty to bypass __proto__ literal semantics), intermediate-scalar rejection, malformed-file no-clobber, JSONC-with-trailing-commas parsing (including a string containing ",]"), and the atomic-write tmp-file cleanup. - loadedSettingsAdapter.test: new file — forwarding, UNSAFE_KEY_PARTS rejection, getValue against merged settings, backup/restore round-trip, cleanupBackup semantics. - provider-config.test: added findProviderByCredentials and getAllProviderBaseUrls coverage (preset hits, unknown-key misses, BaseUrlOption[] preset expansion). Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): satisfy strict tsc --build in loadedSettingsAdapter.test CI's `tsc --build` (with emit) enforced two strict checks that `tsc --noEmit` had been letting through: - `noPropertyAccessFromIndexSignature` flagged `file.settings['env']` reads against `Record<string, unknown>`. Switched the test fixture shape to a named `SettingsShape` interface with explicit `env` and `modelProviders` keys (plus an index signature for setValue's arbitrary writes), so dot access on the known keys is no longer "through" the index signature. - Calling optional methods via `adapter.backup?.()` produced TS2722 (`Cannot invoke an object which is possibly 'undefined'`) under the build flags. createLoadedSettingsAdapter always installs backup/restore/cleanupBackup, so the tests now assert `toBeTypeOf('function')` first and then call via non-null assertion, which both documents the invariant and makes the call typesafe. - Dropped the `({} as Record<string, unknown>)['polluted']` sanity check; `expect(setValue).not.toHaveBeenCalled()` already proves the guard short-circuits before any write reaches LoadedSettings. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): guard mock setValue against prototype pollution in adapter test CodeQL flagged the mock setValue's recursive property assignment as a prototype-pollution sink. Add UNSAFE_KEY_PARTS check at the top of the mock to align with the real setNestedPropertySafe contract. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): use literal === guards for CodeQL prototype-pollution sanitiser CodeQL re-flagged the mock setValue write even after the Set.has guard added in2e6adf8a6d— the scanner only recognises inline literal === comparisons as prototype-pollution sanitisers, not Set lookups. Reworked the mock to (1) merge the guard into the loop so every current[part] write is preceded by a literal === check against '__proto__'/'constructor'/'prototype', and (2) collapse the dual leaf/branch logic into a single loop body. Runtime behaviour is identical; CodeQL should now treat the write as sanitised. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(auth): third round of PR #4287 review fixes (8 comments) Critical: - useAuth: handleProviderSubmit now calls setPendingAuthType at the start of the try, so handleAuthFailure can record the AuthEvent telemetry on applyProviderInstallPlan rejection (previously dropped silently because pendingAuthType was undefined). - settingsWriter: readQwenSettingsForVSCode wraps readSettings in try/catch so a malformed settings.json no longer crashes the VSCode extension on activation; the write paths (writeCodingPlanConfig, writeModelProvidersConfig) deliberately keep propagating to avoid silently overwriting a corrupt file with partial data. Suggestions: - settingsWriter.setValue: intermediate-segment guard now also rejects arrays (typeof [] === 'object' previously slipped through and would let us set string keys on an array). Loop restructured so the literal-=== prototype-pollution guard runs at every step, satisfying CodeQL's sanitiser detector on both the leaf and intermediate writes. - settingsWriter atomic write: SETTINGS_FILE_MODE = 0o600 + SETTINGS_DIR_MODE = 0o700 + best-effort chmod on existing files. API keys persisted into env.* are no longer world-readable on multi-user systems. - loadedSettingsAdapter: switched its prototype-pollution guard to the same inline literal === pattern so the two adapters stay symmetric and CodeQL recognises both as sanitisers (Comment 6 — explicit 'keep in sync' comment + same shape rather than a shared helper that CodeQL wouldn't trace through). - AuthMessageHandler: protocol QuickPick now shows 'OpenAI Compatible' / 'Anthropic' / 'Gemini' instead of the raw AuthType enum values. - WebViewProvider: authInteractive log now records only the parsed hostname, not the full inputs.baseUrl, so credentials embedded in userinfo or query strings don't leak into extension-host logs. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(auth): cover the rollback safety nets in applyProviderInstallPlan + useAuth failure path Addresses the missing-coverage points in the latest review pass: every deliberately-engineered rollback path in install.ts and the visible side effects of handleAuthFailure now have a regression test, so a future refactor that 'simplifies' these paths can't silently break them. applyProviderInstallPlan (install.test.ts, +4 cases): - restores runtime model providers when refreshAuth rejects after reloadModelProviders ran (asserts the second reloadModelProviders call receives the pre-install snapshot). - still rolls back env vars when backup() throws before persist (pins the 'backup inside try' invariant added in38a214d0ec). - continues env rollback even when settings.restore itself throws (pins the nested try/catch around restore added in38a214d0ec). - continues throw + env rollback when the rollback-time reloadModelProviders itself throws (the original error must still surface; env vars must still revert). useAuth (useAuth.test.ts, +1 case): - surfaces install-plan rejection as an auth error and records telemetry — refreshAuth throws, the test asserts authError is set, the dialog reopens, isAuthenticating clears, no success toast is added, and pendingAuthType is populated (which is what the new setPendingAuthType call lets handleAuthFailure key the AuthEvent on). - createSettings now mocks recomputeMerged + forScope.settings so the loaded-settings-adapter restore() path doesn't emit a noisy stderr. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(auth): fourth round of PR #4287 review fixes Critical: - settingsWriter JSONC scanner: \uXXXX is a 6-char escape, not 2. The previous stripJsonComments / stripTrailingCommas used j+=2 for every backslash, so a value containing \u0022 would let the embedded quote terminate the string early — turning a single string value into multiple top-level keys after the strip passes. That's a parser differential vs JSON.parse and enables settings.json key injection (e.g. an attacker-controlled API_KEY string could inject env.NODE_OPTIONS). Now we branch on text[j+1] === 'u' and skip 6, satisfying both scanners. - resolveBaseUrl no longer crashes on an empty baseUrl array. The previous config.baseUrl[0].url threw 'Cannot read undefined.url' on [] and brought down the whole install flow. Falls back to selectedBaseUrl or '' instead. - providerMatchesCredentials now resolves function-typed envKey by calling it with (protocol, baseUrl). The previous typeof-string gate made the custom provider invisible to findProviderByCredentials — /doctor and system-info diagnostics couldn't see custom-provider users. Catches the function call so a misbehaving custom envKey can't crash the matcher. Suggestions: - AuthDialog: defaultMainIndex now also returns 2 for uiGroup === 'custom' so a custom-provider user lands on the Custom Provider tab instead of Alibaba ModelStudio. - install.ts: env-var rollback loop is now wrapped in try/catch matching the same shape as the settings.restore() and reloadModelProviders rollbacks. A process.env write throwing (custom property descriptors, some sandboxes) won't skip the runtime-providers rollback below. - readSettings: SyntaxError is now wrapped in an actionable Error ('Cannot parse ~/.qwen/settings.json ($name: $message). Standard JSONC is supported... Please fix or delete $path...') so users facing a corrupt file get a clear message instead of a bare SyntaxError. The cause is preserved via Error.cause. Tests: - settingsWriter: new \u0022 injection regression — asserts that a string containing \u0022 stays a single string and no injected key lands at the top level. - provider-config: new edge-case suite for resolveBaseUrl with [] and providerMatchesCredentials with function-typed envKey (matching path, wrong-key path, function-throws path). Re-imports via the relative source path so the new behaviour is exercised even before dist/ is rebuilt. Not addressed: - handleProviderSubmit error-path test (Comment 3264567491) was already added in7d8b4785ad— same test, same surface (refreshAuth rejection + authError set + dialog reopen + isAuthenticating false + no success toast + pendingAuthType populated). Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(vscode): import AuthType as value not type AuthMessageHandler now references AuthType.USE_OPENAI etc. as enum values (for the protocolLabels map added incdc17cbba0), but the import was 'import type AuthType' which strips the runtime binding. TS1361 fired in CI's emitting build even though --noEmit was happy locally. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(providers): restore modelscope test + tighten openrouter ownsModel Two findings from the latest /review pass that survived earlier rounds: 1. modelscope.test.ts was deleted in the move-from-CLI step (60 lines / 4 cases under packages/cli/src/auth/providers/thirdParty/) but never recreated in core's preset test folder. Re-added a 3-case suite (config shape, install plan with per-model metadata for known IDs, graceful fallback for unknown IDs) so the third-party preset coverage is symmetric again. Also exported modelscopeProvider from packages/core/src/providers/index.ts so the public API matches the other presets. 2. openrouter.ts ownsModel previously claimed any model on an openrouter.ai hostname, which would silently delete a user's hand-added entry that happened to route through openrouter.ai under a different envKey (e.g. a personal gateway). Now requires both model.envKey === OPENROUTER_ENV_KEY AND the openrouter.ai hostname match. Existing openrouter.test.ts updated and extended to cover: matching path, envKey mismatch path, host mismatch path, missing/malformed baseUrl. The remaining findings in that /review were either already addressed in earlier rounds (custom provider visibility / resolveBaseUrl empty array / useAuth telemetry / TS4111 errors — verified 0 locally) or architectural concerns beyond this PR's scope (LoadedSettings.setValue's per-call saveSettings). Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(auth): fifth round of PR #4287 review fixes Critical: - provider-config.ts providerMatchesCredentials: iterate config.protocolOptions when resolving a function-typed envKey instead of relying on the default config.protocol. A custom provider configured under USE_ANTHROPIC or USE_GEMINI persists an envKey derived from THAT protocol, not from USE_OPENAI — without iteration the matcher silently misses them and custom-provider users disappear from /doctor + AppHeader + systemInfoFields + AuthDialog.defaultMainIndex. - provider-config.test.ts: the existing test asserting 'returns false for function-typed envKey' was holding on the old broken behaviour. Flipped to assert toBe(true) for the matching path, and routed it through the relative source import so it doesn't run against stale dist. Suggestions: - settingsWriter.clearPersistedAuth: now wipes every preset's string envKey (iterates ALL_PROVIDERS, plus the existing subscription-plan loop kept for explicitness) and every QWEN_CUSTOM_API_KEY_* key by prefix match. Previously DeepSeek / MiniMax / Z.AI / IdeaLab / ModelScope / OpenRouter / custom keys lingered on disk after clearing auth. - custom-provider.ts generateCustomEnvKey: the readable-only normalization collapsed 'api.example.com', 'api-example.com', and 'api_example.com' into the same env key, so two structurally different custom providers would overwrite each other's API key. Now appends a 6-hex-char SHA-256 suffix derived from (protocol, baseUrl-with-trailing-slash-stripped). The trailing-slash invariant from the prior implementation is preserved (api/v1 and api/v1/ still hash equal). Suffix collision probability at 6 hex chars is ~1/16M per pair — fine for an interactive flow. Tests: - provider-config.test.ts: added a 'iterates protocolOptions' case that configures a custom-style provider, derives the key under USE_ANTHROPIC, and asserts the matcher finds it. - custom-provider.test.ts: regex-matches the new readable+hash format for the deterministic / special-character / empty-string cases, and a new 'disambiguates structurally distinct URLs that normalize identically' case that pins down the collision fix (api.example.com vs api-example.com vs api_example.com all differ). Not addressed: - TS1361 'type AuthType' import — already fixed in8f94b018bd- modelscope re-export — already fixed in7228d73d80Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(custom-provider): replace polynomial regex with linear char scans CodeQL alerts 225 + 232 flagged `/_+/g`, `/^_+|_+$/g`, and `/\/+$/` in generateCustomEnvKey as polynomial regex on user input. V8 handles these patterns linearly in practice, but the scanner can't see that and any baseUrl with many '_' or '/' would be flagged as a theoretical worst case. Replaced both passes with single-pass character scans: - normalizeEnvSegment: walks the string once, emits alphanumerics verbatim, collapses any non-alphanumeric run to a single '_', then trims leading/trailing underscores via charCodeAt index walks. Equivalent to the prior three regexes but with no quantifier backtracking surface. - stripTrailingSlashes: walks backwards from the end while charCodeAt === 47, then slices. Equivalent to `replace(/\/+$/, '')`. All 11 custom-provider tests still pass — output format and invariants (trailing-slash equivalence, hash suffix, protocol/URL disambiguation) are unchanged. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(auth): seventh round of PR #4287 review fixes Critical: - i18n: 9 locale files updated to replace orphaned 'Select Authentication Method' / 'You must select an auth method...' keys with the new 'Connect a Provider' / 'You must connect a provider...' keys the AuthDialog actually references. Non-English users no longer see the English fallback for the main heading + exit-prevention warning. - settingsWriter.writeSettings: renameSync is now wrapped in try/catch that unlinks the temp file on failure (EPERM/EBUSY on Windows from watchers/AV would otherwise orphan a secret-bearing .tmp file in ~/.qwen on every failed write). - settingsWriter.restore(): write to disk FIRST, then update in-memory data. The previous order left memory clean while disk retained the failed install's partial state if writeSettings threw. Now matches the CLI adapter's order. - AuthMessageHandler custom-provider tests: added 4 cases covering protocol picker → free-form URL → API key → comma-split model IDs → advanced config (one happy path), plus the http(s) scheme guard, the protocol-aware blank-URL fallback, and the whitespace-only model IDs guard. Previously the entire custom path through runProviderSetupFlow had zero coverage. - settingsWriter clearPersistedAuth tests: added cases for the expanded preset/custom/subscription cleanup (asserts NODE_OPTIONS survives, every QWEN_CUSTOM_API_KEY_* is wiped, providerMetadata entries for every preset are gone) plus a no-settings-file no-op. Suggestions: - loadedSettingsAdapter.restore(): now checks restoreSettingsFromBackup's boolean return value and logs an explicit warning when on-disk rollback fails (EACCES / missing .orig). Previously the failure was silent and the next CLI restart would read a corrupted file. - generateCustomEnvKey: hash suffix lengthened from 6 → 12 hex chars (24 → 48 bits). Brings collision search out of milliseconds-range enumeration; offline 'pick a URL that collides' attack is no longer practical at interactive setup time. - getDefaultBaseUrlForProtocol: new shared helper in core consumed by both the CLI (useProviderSetupFlow) and VS Code (AuthMessageHandler) flows. Removes the duplicated DEFAULT_BASE_URLS map; one source of truth for the OpenAI/Anthropic/Gemini placeholder URLs. - settingsWriter.clearPersistedAuth: providerMetadata cleanup now iterates ALL_PROVIDERS with resolveMetadataKey instead of hardcoding coding-plan/token-plan. Stale metadata for deepseek/minimax/zai/ idealab/modelscope/openrouter no longer lingers after logout. - resolveMetadataKey: explicit guard against provider ids containing '.'. A dotted id would split into multiple nested objects under providerMetadata, silently corrupting the settings tree. Now throws loudly at registration time. - customProvider: added explicit ownsModel that prefix-matches against QWEN_CUSTOM_API_KEY_*. Reinstalling a custom provider under a different baseUrl now reliably replaces (not accumulates) the old entries. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(auth): eighth round of PR #4287 review fixes Suggestions: - clearPersistedAuth metadata cleanup loop: per-iteration try/catch around resolveMetadataKey so a future dotted-id provider can't abort the loop and leave secrets on disk. - VS Code AuthMessageHandler: removed the hardcoded || 'https://api.openai.com/v1' fallback after getDefaultBaseUrlForProtocol — defaults must live in core. The CLI flow has no such fallback, and the silent OpenAI default would mask a new AuthType core hadn't been taught about. - settingsWriter restore() comment: clarified the deliberate divergence from the CLI adapter's trade-off (disk-fail-throws here, disk-fail- logs-and-continues there) so the comment doesn't read 'same order'. - useAuth handleAuthFailure: closure staleness — setPendingAuthType queues an async React update, so handleAuthFailure's pendingAuthType read could see undefined when a synchronous throw beats the next render. Added an optional protocolForTelemetry argument that the new handleProviderSubmit passes explicitly; closure fallback kept for legacy callers. AuthEvent error telemetry is no longer silently dropped. - install.ts: track currentStep before each phase (backup → env → modelProviders → authType → legacyCredentials → modelSelection → providerState → persist → reloadModelProviders → syncAuthState → refreshAuth → cleanupBackup) and annotate the rethrown error with the failing step + authType. Original error preserved via Error.cause so callers matching on err.code still work. - custom-provider.ts: stale '6-hex-char' comment updated to 12. Added a migration note explaining that old 6-char keys persist as harmless orphan disk state until clear-auth. - settingsUtils.restoreSettingsFromBackup: was swallowing fs errors with catch(_e); now logs the underlying cause so the adapter's on-disk-rollback-failed warning has something specific to point at. Tests: - useAuth: new cancelAuthentication case asserts isAuthenticating clears, externalAuthState clears, dialog opens, authError clears. - provider-config: new resolveMetadataKey suite — normal id, no-models → undefined, dotted id → throws. - install: new case asserting the rethrown error names the failing step ('refreshAuth') + authType and preserves the original error via Error.cause. Not addressed: - 6→12 hash backward compat (Comment 3267562667): The 6-char keys are orphan disk state — never read by applyProviderInstallPlan (the new model provider entries reference the new 12-char key), so no security or correctness issue, just disk noise that clears on next sign-out. Documented in custom-provider.ts. A full clean-up pass would need a new ProviderSettingsAdapter delete API + a migration scan — better as its own PR. - writeSettings renameSync error path test + loadedSettingsAdapter restore-failure log test (terminal-only findings): adding these requires fs mocking surgery that's worth its own PR. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(format): four prettier/JSDoc nitpicks from review All four are Critical-tagged formatter / docs issues caught by the latest /review pass: - AppHeader.tsx: `AuthType ,` (stray space before comma) → standard newline-after-{ form. Was breaking CI Lint. - useProviderUpdates.test.ts: same `AuthType ,` pattern → standard form. - apiPreconnect.ts: double blank line after the closing `}` of the import block (left behind when getAllProviderBaseUrls was removed from the old auth/allProviders path) → single blank line. - types.ts (Suggestion): JSDoc for `modelsEditable` said "false → skip model step; use models as-is (e.g. Coding Plan)" but codingPlanProvider actually sets modelsEditable: true (every preset in the registry does), so the example contradicts the registry. Dropped the parenthetical. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(scripts): raise install-script suite timeout to survive Windows Windows CI flaked on `standalone release packaging > rejects unexpected dist assets` with a 5000ms timeout. The test shells out to `node scripts/create-standalone-package.js` which produces a tar.gz; observed real runtimes from sibling tests in the same run: 4780ms / 1666ms / 1079ms — the 4.8s case is already at vitest's default 5s limit, so a slightly slower subprocess startup (antivirus inspection, contended runner) tips it over. Pre-existing test (added 2026-05-11 incb7059f54d), unrelated to this PR's auth refactor. Bumped the suite-wide testTimeout to 30s in scripts/tests/vitest.config.ts — the tests still complete in seconds when subprocess startup is healthy; the headroom only kicks in to cover Windows-slow variance. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(auth): ninth round of PR #4287 review fixes Critical: - WebViewProvider.handleAuthInteractive: roll back bad credentials when the agent reconnect rejects them. applyProviderInstallPlanToFile commits the key + calls cleanupBackup before the disconnect/reconnect runs, so the plan's own rollback can't cover an authState=false outcome. Now snapshot settings before the write (snapshotSettingsForRollback) and restore it (restoreSettingsSnapshot) on both the authState!=true branch and the catch branch. Without this a rejected key persisted and every VS Code restart retried it. Two new helpers added to settingsWriter; never-throw snapshot so a malformed pre-state degrades to a no-op restore. Suggestions: - AuthMessageHandler: trim the API key before validateApiKey + persistence, matching the CLI flow (useProviderSetupFlow trims in two places). A key pasted with trailing whitespace no longer causes silent auth failures or VS-Code-only validateApiKey rejections. - install.ts: the annotated rethrow no longer bakes 'step "persist"' into the user-facing message. Step + authType are now structured properties on a new exported ProviderInstallError (message stays the underlying error text, cause preserved). Callers can show a clean message and log err.step/err.authType to the dev console. - provider-config.ts: providerMatchesCredentials no longer swallows a throw from a function-typed envKey — console.warn surfaces the programming error so a custom provider silently vanishing from /doctor has a trace. - types.ts: documented that ProviderSettingsAdapter.setValue MAY flush to disk eagerly (the CLI LoadedSettings adapter does) and that persist() can be a no-op for such adapters — so future authors don't insert pre-persist steps assuming atomicity. - settingsWriter: moved the orphaned stripJsonComments JSDoc off jsonEscapeLength (the \u-escape helper inserted between the doc and its function) back onto stripJsonComments itself. Tests: - settingsWriter: snapshot/restore round-trip, malformed→null→no-op-restore, no-file→{} snapshot. - install: updated the step-annotation test to assert err.step/err.authType structured properties + clean message instead of the embedded string. - WebViewProvider.test: settingsWriter mock extended with applyProviderInstallPlanToFile/snapshotSettingsForRollback/ restoreSettingsSnapshot. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(auth): tenth round of PR #4287 review fixes Critical (both from the previous round's own changes): - WebViewProvider.handleAuthInteractive: restoreSettingsSnapshot → writeSettings can throw (EPERM on Windows renameSync / disk full / EACCES). Both rollback call sites are now routed through a local safeRollback() that try/catches and logs, so a rollback failure can never (a) re-throw out of the else-branch into the outer catch and trigger a second rollback that skips the error message, nor (b) throw out of the catch-branch and leave the webview auth dialog hanging with no feedback. - provider-config.providerMatchesCredentials: the new envKey-throw console.warn logged the full baseUrl, which can embed credentials (https://user:sk-secret@host). Now logs only new URL(baseUrl).hostname (with an [invalid] fallback) and err.message, matching the sanitization WebViewProvider already uses. Tests: - WebViewProvider.test: new 'credential rollback' describe with three cases — (1) authState!==true after reconnect → restoreSettingsSnapshot called with the snapshot, (2) authState===true → restore NOT called, (3) restore throws (EPERM) → handleAuthInteractive still resolves and the authError message is still sent. Hoisted mocks extended with applyProviderInstallPlanToFile / snapshotSettingsForRollback / restoreSettingsSnapshot refs so the scenario is controllable. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(auth): eleventh round of PR #4287 review fixes Critical: - AuthMessageHandler: validation-failure paths (bad URL scheme, invalid API key, empty model IDs, handler-not-set) no longer call notifyAuthCancelled after sendToWebView({authError}). The webview's ProviderSetupForm clears the error on authCancelled, so the two messages raced and the error flashed away before the user could read it. authCancelled is now reserved for genuine user dismissals (Escape on a QuickPick/InputBox); authError already clears the connecting state. - WebViewProvider: after rolling back rejected credentials, also disconnect the agent. The reconnect spawned a process holding the bad key in memory; without disconnect a subsequent chat message hit a stale-credential error unrelated to the original auth failure. Now agentManager.disconnect() + agentInitialized=false so the next /auth reconnects cleanly. Suggestions: - install.ts: added a DENY_ENV_KEYS denylist (NODE_OPTIONS, NODE_PATH, LD_PRELOAD, LD_LIBRARY_PATH, DYLD_INSERT_LIBRARIES, DYLD_LIBRARY_PATH, PATH, HOME, TMPDIR), checked case-insensitively before writing any plan.env entry to settings + process.env. Defense in depth: all callers go through buildInstallPlan with hardcoded keys today, but ProviderInstallPlan is exported. - settingsUtils: setNestedPropertySafe AND setNestedPropertyForce now refuse __proto__/constructor/prototype path segments (inline literal === so CodeQL recognises the sanitiser). migrateProviderMetadata feeds field names from Object.entries on user settings.json, and JSON.parse keeps __proto__ as an own property — guarding at the utility protects every caller, not just the adapters. Already fixed inf31224bac1(review ran against9f45a7536b): - restoreSettingsSnapshot throw masking the original error → safeRollback. - baseUrl logged verbatim in providerMatchesCredentials → hostname only. Tests: - install: NODE_OPTIONS rejected + not leaked to process.env/settings; case-insensitive Path rejection. - AuthMessageHandler: validation authError is NOT followed by authCancelled. - WebViewProvider: rollback path disconnects the agent + clears agentInitialized. - settingsUtils: setNestedPropertySafe/Force refuse __proto__/ constructor/prototype and don't pollute Object.prototype. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(test): use bracket access in settingsUtils prototype-pollution tests The new setNestedProperty guard tests asserted obj.a.b.c / obj.x.y dot-access on Record<string, unknown>, which trips noPropertyAccessFromIndexSignature (TS4111) under the emitting tsc --build the CI 'Install dependencies' step runs. Local npm run typecheck (--noEmit) had a stale tsbuildinfo and didn't re-check the file. Switched to bracket access (obj['a']['b']['c']) to match the strict option. Behaviour unchanged; 78 settingsUtils tests still pass. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(vscode): cover the outer-catch rollback path in handleAuthInteractive All prior rollback tests exercised the else-branch (authState !== true). The outer catch — reached when applyProviderInstallPlanToFile or doInitializeAgentConnection throws (disk errors, partial writes) — had no coverage, and that's the higher-risk path. New test makes doInitializeAgentConnection reject and asserts (1) restoreSettingsSnapshot called with the snapshot, (2) authError sent containing 'Configuration failed', (3) handleAuthInteractive resolves without throwing. Guards against a regression that drops the safeRollback wrapper in the catch. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(providers): make NODE_OPTIONS denylist test env-independent The test asserted process.env.NODE_OPTIONS toBeUndefined after the rejected plan, but CI sets NODE_OPTIONS (--max-old-space-size=3072 from the build script), so it failed there while passing locally where NODE_OPTIONS is unset. Snapshot the original value and assert the rejected plan left it UNCHANGED (and specifically not the evil --require value) — that's the actual invariant: the denylist throws before mutating process.env. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(vscode): disconnect stale agent in handleAuthInteractive catch block too The else-branch (authState !== true) disconnected the agent after rollback, but the outer catch only rolled back. If doInitializeAgentConnection partially initializes (agentInitialized=true, agent process spawned) then throws — e.g. a disk error during post-connect setup — the stale-credential agent stayed connected. Extracted a disconnectStaleAgent() local helper (alongside safeRollback) and called it in both the else-branch and the catch, so the two paths are symmetric. Extended the outer-catch test to spawn a partial agent before the throw and assert disconnect() is called + agentInitialized cleared. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(auth): twelfth round of PR #4287 review fixes (5 suggestions) All from DeepSeek's pass, all on recent commits: - settingsUtils: stale comment referenced a non-existent UNSAFE_PATH_SEGMENTS const; the actual guard is pathHasUnsafeSegment(). Fixed both comment sites. - settingsWriter.snapshotSettingsForRollback: was silently returning null on a readSettings throw (disabling credential rollback with no signal). Now console.warn's the cause so oncall can tie repeated cross-restart auth failures back to a transient unreadable settings file. - provider-config.providerMatchesCredentials: the envKey-throw warn logged err.message, which a user-defined envKey fn could populate with the API key (new Error(`bad config: ${apiKey}`)). Now logs only err.constructor.name — no message, no URL. - install.ProviderInstallError: was an interface (erased at compile time → instanceof always false). Converted to a class extending Error so instanceof works at runtime; exported as a value (not type) from the barrel. Construction simplified to new ProviderInstallError(msg, step, authType, { cause }). - install.DENY_ENV_KEYS: added Windows TMP/TEMP alongside TMPDIR so a crafted plan can't redirect temp-file creation on Windows. Tests: - install: assert the thrown error is instanceof ProviderInstallError; new it.each covering TMP/TEMP/tmp rejection (case-insensitive). Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(vscode): log error class name not message in snapshotSettingsForRollback Consistency with the err.constructor.name approach applied in provider-config.providerMatchesCredentials. The risk here is lower (the catch is filesystem errors from readSettings/structuredClone, not user-defined functions), but logging only the class name keeps the security stance uniform across the codebase. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
33 lines
968 B
TypeScript
33 lines
968 B
TypeScript
/**
|
|
* @license
|
|
* Copyright 2025 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import { defineConfig } from 'vitest/config';
|
|
|
|
export default defineConfig({
|
|
test: {
|
|
globals: true,
|
|
environment: 'node',
|
|
include: ['scripts/tests/**/*.test.{js,ts}'],
|
|
setupFiles: ['scripts/tests/test-setup.ts'],
|
|
// Several tests in install-script.test.js shell out to `node` to run
|
|
// create-standalone-package.js, which on Windows runs a full
|
|
// tar+gzip pass under antivirus inspection. Real runtimes observed on
|
|
// Windows CI: 4780ms / 1666ms / 1079ms — the 4.8s one is right at
|
|
// vitest's 5s default and flakes. Bump the suite timeout so a single
|
|
// slow subprocess startup doesn't fail an otherwise-healthy test run.
|
|
testTimeout: 30_000,
|
|
coverage: {
|
|
provider: 'v8',
|
|
reporter: ['text', 'lcov'],
|
|
},
|
|
poolOptions: {
|
|
threads: {
|
|
minThreads: 8,
|
|
maxThreads: 16,
|
|
},
|
|
},
|
|
},
|
|
});
|