## 從免管理員 Node.js 環境建置到 `vscode.lm` 模型偵測與 checkpoint 輸出
這份教學記錄如何從零開始執行這個 VS Code extension demo,
包含公司電腦常見的安裝權限限制、免管理員 Node.js 安裝方式、
三語同義詞批次擴展,以及如何列出目前可用的 LLM model。
## 目標
這個專案示範三件事:
1. 使用 VS Code extension 呼叫 `vscode.lm`。
2. 讀取 `keywords.json`,批次產生英文、簡中、西語墨西哥同義詞。
3. 偵測目前 VS Code Copilot LM API 實際可用的模型。
目前命令如下:
“`text
Demo: Ask LM and Save Markdown
Demo: Expand Synonyms Batch
Demo: List Available LM Models
“`
定義於 package.json中:
{
"name": "vscode-lm-demo-js",
"displayName": "VS Code LM Demo JS",
"description": "Minimal JavaScript demo for asking vscode.lm and saving the answer as Markdown.",
"version": "0.0.1",
"engines": {
"vscode": "^1.90.0"
},
"categories": [
"Other"
],
"main": "./extension.js",
"contributes": {
"commands": [
{
"command": "demo.askLmAndSaveMd",
"title": "Demo: Ask LM and Save Markdown"
},
{
"command": "demo.expandSynonymsBatch",
"title": "Demo: Expand Synonyms Batch"
},
{
"command": "demo.listAvailableLmModels",
"title": "Demo: List Available LM Models"
}
]
},
"devDependencies": {
"@types/vscode": "^1.90.0"
}
}## 專案位置
“`powershell
D:\user\Python\vscode-lm-demo-js
“`
主要檔案:
extension.js VS Code extension 主程式
package.json extension 命令註冊
keywords.json expand synonyms 的輸入 keyword
lm_config.json 預設 LLM model 設定
export\ batch/state/final/model list 輸出資料夾## 1. 安裝 Node.js
VS Code extension 開發需要 Node.js 和 npm。
Node.js 可以理解成「讓 JavaScript 在瀏覽器外執行的環境」。
平常 JavaScript 常見於瀏覽器裡,例如網頁互動;
但 VS Code extension 不是跑在一般網頁裡,
而是跑在 VS Code 的 extension host 裡,
因此需要 Node.js 這個 JavaScript 執行環境來開發、安裝套件、執行工具。
用 Python 類比的話,可以先這樣理解:
Python 語言 你寫 .py 時使用的程式語言
python.exe 負責執行 .py 程式的執行器 / interpreter
pip 負責安裝 Python 套件
requirements.txt 常用來記錄 Python 專案依賴
JavaScript 語言 你寫 .js 時使用的程式語言
node.exe / Node.js 負責執行 .js 程式的執行器 / runtime
npm 負責安裝 JavaScript / Node.js 套件
package.json 記錄 Node.js 專案資訊、命令、依賴與 VS Code extension 設定
VS Code 編輯器,不是 JavaScript 的執行器本身所以在 terminal 裡,兩邊概念大概像這樣:
python my_script.py
node my_script.js在這個專案裡,真正的 extension 主程式是 `extension.js`。
Node.js 提供開發時需要的 JavaScript 執行環境,
npm 則根據 `package.json` 安裝開發依賴。
npm 是 JavaScript / Node.js 專案的套件管理工具,
角色接近 Python 裡的 `pip`。
Python 常用 `pip install -r requirements.txt`
安裝 `requirements.txt` 裡列出的套件;
Node.js 專案則常用 `npm install` 安裝 `package.json` 裡列出的套件。
以本專案的 `package.json` 來看:
{
"main": "./extension.js",
"contributes": {
"commands": [
{
"command": "demo.expandSynonymsBatch",
"title": "Demo: Expand Synonyms Batch"
}
]
},
"devDependencies": {
"@types/vscode": "^1.90.0"
}
}其中:
main 告訴 VS Code extension 的入口檔是 extension.js
contributes 告訴 VS Code 這個 extension 要貢獻哪些功能,例如命令面板命令
devDependencies 開發時需要安裝的套件,這裡的 @types/vscode 用來提供 VS Code API 的型別提示`”@types/vscode”: “^1.90.0″` 可以拆成兩部分看:
@types/vscode 套件名稱
^1.90.0 版本範圍`@types/vscode` 前面的 `@` 不是 email 的小老鼠意思,
而是 npm 的「scope」命名規則。
白話來說,scope 可以先理解成 npm 套件名稱前面的「群組名稱」或「分類資料夾」。
它的格式通常是:
“`text
@群組名稱/套件名稱
“`
所以:
“`text
@types/vscode
“`
可以理解成:
“`text
@types 這個群組底下的 vscode 套件
“`
這裡的 `@types` 不是這個專案自己隨便取的名字,
而是 npm 生態裡常見的型別定義套件群組。
`@types/*` 這類套件通常是 TypeScript/JavaScript 開發時用的型別定義檔,
讓 VS Code 知道
`vscode.window`、`vscode.commands`、`vscode.lm`
這些 API 大概有哪些方法和屬性。
例如:
@types/node
@types/react
@types/express
@types/vscode這些通常都代表「替某個 JavaScript 套件或環境補型別提示」。
但是 `@xxx/yyy` 這種格式本身不限定只能叫 `@types`。
其他團隊或工具也可以有自己的群組名稱,例如:
@babel/core
@vitejs/plugin-react
@microsoft/signalr
@angular/core所以可以這樣記:
@types 是一個常見且有固定用途的群組名稱,用來放型別定義套件。
@xxx/yyy 這種寫法是 npm 的 scoped package 格式,xxx 可以是不同組織或工具自己的名稱。那 `1.90.0` 是哪來的?它不是隨便寫的,
而是跟 `package.json` 裡的這段對齊:
"engines": {
"vscode": "^1.90.0"
}`engines.vscode` 表示
這個 extension 預期支援的 VS Code 版本範圍。
這裡寫 `^1.90.0`,
意思是這個 extension 以 VS Code `1.90.0`
這個 API 版本作為最低相容基準。
所以 `devDependencies` 裡的 `@types/vscode` 也用 `^1.90.0`,
讓開發時看到的 VS Code API 型別提示,
跟 extension 宣告支援的 VS Code API 版本一致。
它跟你目前安裝的 VS Code 版本有關,
但不是一定要完全一樣。
你現在的 VS Code 版本是 `1.122.1`,
比 `1.90.0` 新很多,所以符合 `^1.90.0` 的要求,
可以執行這個 extension。
可以把它想成 Python 套件常見的最低版本要求:
需要 Python >= 3.9
你電腦是 Python 3.12
所以可以跑對應到這個 extension 就是:
需要 VS Code >= 1.90.0
你電腦是 VS Code 1.122.1
所以可以跑那為什麼不是直接寫 `1.122.1`?
因為 `1.90.0` 是這個 demo extension 建立時採用的相容基準,
意思是「不要要求使用者一定要裝到最新 VS Code,
只要版本不低於這個基準就好」。
如果未來真的需要使用 VS Code `1.122` 才有的新 API,
才需要把 `engines.vscode` 和
`@types/vscode` 一起提高到接近 `^1.122.0`。
`1.90.0` 和 `1.122.1` 的差別,
可以理解成 VS Code API 的新舊差距。
VS Code 每個版本可能會新增 extension 能使用的 API,
例如新的 editor 功能、新的 Copilot/Language Model API 能力、或新的資料格式。
如果某個功能是在 `1.90.0` 之後才加入的,
那用 `@types/vscode@1.90.0` 開發時可能看不到那個 API,
程式也不應該宣告自己支援 VS Code `1.90.0`。
例如以後如果想把圖片送給 LLM,
這就很可能需要比較新的 VS Code Language Model API
支援圖片或 binary data 的 message part。
如果圖片輸入 API 是在比較新的 VS Code 才支援,
那就應該做兩件事:
1. 確認目前安裝的 VS Code 版本真的支援該 API
2. 把 package.json 裡的 engines.vscode 和 @types/vscode 提高到支援該 API 的版本也就是說,不能只看你電腦現在是 `1.122.1` 就直接放心使用新 API;
extension 的 `package.json` 也要誠實宣告「這個 extension 至少需要哪個 VS Code 版本」。
否則如果 package 寫 `^1.90.0`,但程式使用只有 `1.122` 才有的 API,
那在舊版 VS Code 上就可能出現找不到 API、命令失敗、或 extension 啟動錯誤。
`^1.90.0` 前面的 `^` 是 npm 的版本範圍語法,意思是允許安裝相容的新版本。
以 `^1.90.0` 來說,npm 可以安裝 `1.90.0`、`1.91.0`、`1.92.3` 這類仍在 `1.x.x` 範圍內的版本,
但不會自動升到 `2.0.0`,因為主版本改成 2 可能代表不相容。
如果寫成:
"@types/vscode": "1.90.0"就表示固定只能用 `1.90.0`,彈性比較小。
所以執行:
npm.cmd install它會讀取 `package.json`,把 `devDependencies` 裡的 `@types/vscode` 安裝到 `node_modules`。
這個動作類似 Python 專案裡用 `pip install` 安裝開發或執行所需的套件。
一般情況可以直接安裝 Node.js LTS MSI,
但公司電腦可能沒有管理員權限,或 `winget` 不存在。
### 一般安裝方式
到 Node.js 官網下載 Windows Installer:
“`text
“`
選:
“`text
Windows Installer (.msi)
“`
安裝完成後重新開啟 PowerShell / VS Code,檢查:
“`powershell
node -v
npm -v
“`
### 如果 winget 不存在
如果執行:
“`powershell
winget install OpenJS.NodeJS.LTS
“`
出現:
“`text
winget : 無法辨識 ‘winget’
“`
代表 Windows 沒有安裝 winget / App Installer。
這時不用卡在 winget,直接用 Node.js 官網下載 MSI 或 ZIP。
## 2. 繞過安裝權限的 Node.js 安裝方式
這次實際採用的是「用 MSI 抽出檔案,不正式安裝」的方式。
這適合 MSI 正式安裝時被權限、公司政策或檔案寫入限制擋住的情境。
### 2.1 先下載 MSI
從 Node.js 官網下載:
“`text
node-v24.16.0-x64.msi
“`
假設下載到:
C:\Users\SavingKing\Downloads\node-v24.16.0-x64.msi### 2.2 嘗試正式 per-user 安裝
這一步不一定成功,但可以先試:
$msi = "$env:USERPROFILE\Downloads\node-v24.16.0-x64.msi"
$log = Join-Path $env:TEMP 'node-msi-user-install.log'
Start-Process msiexec.exe -ArgumentList @('/i', $msi, 'ALLUSERS=2', 'MSIINSTALLPERUSER=1', '/passive', '/norestart', '/L*v', $log) -Wait如果最後 `node -v` / `npm -v` 還是找不到,或 log 裡看到類似:
Error 1310. Error writing to file: ...\nodejs\corepack
Verify that you have access to that directory.就改用 MSI 抽取法。
### 2.3 用 msiexec 抽出 MSI 內容
這個方式不走正式安裝,不需要寫系統層 registry,也比較能避開權限問題。
$msi = "$env:USERPROFILE\Downloads\node-v24.16.0-x64.msi"
$target = "$env:LOCALAPPDATA\Programs\nodejs-msi-extract"
$log = Join-Path $env:TEMP 'node-msi-admin-extract.log'
if (Test-Path $target) {
Remove-Item $target -Recurse -Force
}
New-Item -ItemType Directory -Force -Path $target | Out-Null
Start-Process msiexec.exe -ArgumentList @('/a', $msi, '/qn', "TARGETDIR=$target", '/L*v', $log) -Wait抽出後,真正的 Node.js 目錄會在:
C:\Users\SavingKing\AppData\Local\Programs\nodejs-msi-extract\PFiles64\nodejs檢查:
$nodeDir = "$env:LOCALAPPDATA\Programs\nodejs-msi-extract\PFiles64\nodejs"
& "$nodeDir\node.exe" -v
& "$nodeDir\npm.cmd" -v這次成功的版本是:
“`text
node v24.16.0
npm 11.13.0
“`
### 2.4 加到使用者 PATH
$nodeDir = "$env:LOCALAPPDATA\Programs\nodejs-msi-extract\PFiles64\nodejs"
$currentUserPath = [Environment]::GetEnvironmentVariable('Path', 'User')
if (-not (($currentUserPath -split ';') -contains $nodeDir)) {
[Environment]::SetEnvironmentVariable('Path', (($currentUserPath.TrimEnd(';') + ';' + $nodeDir).TrimStart(';')), 'User')
}這段比較像 Python 的:
os.environ["PATH"] += r";C:\Users\SavingKing\AppData\Local\Programs\nodejs-msi-extract\PFiles64\nodejs"但它不是只改目前 process,而是寫到 Windows 的 User PATH,重開 PowerShell / VS Code 後仍然有效。
它不是 `sys.path.append(…)`。
`sys.path` 是 Python `import` 模組時搜尋 `.py` 檔的路徑;
這裡要改的是 Windows shell 尋找 `node.exe` / `npm.cmd` 這類可執行檔的路徑。
然後關掉並重開 PowerShell / VS Code。
## 3. npm 被 PowerShell 執行原則擋住怎麼辦
如果執行:
npm install
出現:
因為這個系統上已停用指令碼執行,所以無法載入 npm.ps1
不要改系統政策,直接用 Windows command shim:
npm.cmd install
或用完整路徑:
$nodeDir = "$env:LOCALAPPDATA\Programs\nodejs-msi-extract\PFiles64\nodejs"
& "$nodeDir\npm.cmd" install## 4. 安裝本專案依賴
進入專案:
cd D:\user\Python\vscode-lm-demo-js
npm.cmd install成功時會看到類似:
added 1 package
found 0 vulnerabilities## 5. 啟動 Extension Development Host
在 VS Code 開啟此資料夾:
“`text
D:\user\Python\vscode-lm-demo-js
“`
按:
“`text
F5
“`
如果 F5 沒反應,可以從上方選:
“`text
Start Debugging
“`