ai-git-tools 2.0.80 → 2.0.81
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +94 -20
- package/bin/cli.js +9 -0
- package/package.json +2 -2
- package/src/commands/model-info.js +195 -0
- package/src/core/model-client.js +117 -0
- package/src/data/copilot-models.js +108 -0
package/README.md
CHANGED
|
@@ -51,6 +51,27 @@ npm install --save-dev ai-git-tools
|
|
|
51
51
|
}
|
|
52
52
|
```
|
|
53
53
|
|
|
54
|
+
### 本機直接使用(尚未發布到 npm)
|
|
55
|
+
|
|
56
|
+
如果你想在套件正式發布到 npm 前,直接從本機原始碼執行:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# 方式一:使用 npm link 將本機套件連結到全域
|
|
60
|
+
npm link
|
|
61
|
+
npx ai-git-tools model-info
|
|
62
|
+
|
|
63
|
+
# 方式二:直接使用 node 執行 bin/cli.js
|
|
64
|
+
node bin/cli.js model-info
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
其他命令也適用相同方式,例如:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
npx ai-git-tools init
|
|
71
|
+
npx ai-git-tools commit
|
|
72
|
+
npx ai-git-tools pr
|
|
73
|
+
```
|
|
74
|
+
|
|
54
75
|
## 🚀 快速開始
|
|
55
76
|
|
|
56
77
|
### 1. 初始化配置
|
|
@@ -100,7 +121,7 @@ npx ai-git-tools wf
|
|
|
100
121
|
|
|
101
122
|
## 📖 命令說明
|
|
102
123
|
|
|
103
|
-
### \`
|
|
124
|
+
### \`ai-git-tools init\`
|
|
104
125
|
|
|
105
126
|
初始化配置檔案
|
|
106
127
|
|
|
@@ -108,7 +129,7 @@ npx ai-git-tools wf
|
|
|
108
129
|
npx ai-git-tools init
|
|
109
130
|
\`\`\`
|
|
110
131
|
|
|
111
|
-
### \`
|
|
132
|
+
### \`ai-git-tools commit\`
|
|
112
133
|
|
|
113
134
|
為已 staged 的變更生成並執行 commit
|
|
114
135
|
|
|
@@ -130,7 +151,7 @@ npx ai-git-tools commit --model claude-haiku-4.5
|
|
|
130
151
|
npx ai-git-tools commit --verbose
|
|
131
152
|
\`\`\`
|
|
132
153
|
|
|
133
|
-
### \`
|
|
154
|
+
### \`ai-git-tools commit-all\` (別名: \`ca\`)
|
|
134
155
|
|
|
135
156
|
智能分析所有變更並自動分組提交
|
|
136
157
|
|
|
@@ -151,7 +172,7 @@ npx ai-git-tools commit-all
|
|
|
151
172
|
npx ai-git-tools ca --verbose
|
|
152
173
|
\`\`\`
|
|
153
174
|
|
|
154
|
-
### \`
|
|
175
|
+
### \`ai-git-tools pr\`
|
|
155
176
|
|
|
156
177
|
生成 PR 並發送到 GitHub
|
|
157
178
|
|
|
@@ -177,7 +198,7 @@ npx ai-git-tools pr --preview
|
|
|
177
198
|
npx ai-git-tools pr --no-confirm
|
|
178
199
|
\`\`\`
|
|
179
200
|
|
|
180
|
-
### \`
|
|
201
|
+
### \`ai-git-tools workflow\` (別名: \`wf\`)
|
|
181
202
|
|
|
182
203
|
完整工作流程:commit-all + pr
|
|
183
204
|
|
|
@@ -199,6 +220,31 @@ npx ai-git-tools workflow
|
|
|
199
220
|
npx ai-git-tools wf --preview
|
|
200
221
|
\`\`\`
|
|
201
222
|
|
|
223
|
+
### \`ai-git-tools model-info\`
|
|
224
|
+
|
|
225
|
+
查詢目前 GitHub Copilot SDK 支援的 AI 模型清單與詳細資訊
|
|
226
|
+
|
|
227
|
+
\`\`\`bash
|
|
228
|
+
npx ai-git-tools model-info [選項]
|
|
229
|
+
|
|
230
|
+
選項:
|
|
231
|
+
--json 以 JSON 格式輸出完整模型資料
|
|
232
|
+
--filter <keyword> 依模型名稱、供應商或描述關鍵字過濾
|
|
233
|
+
--model <model> 查詢單一模型的詳細資訊
|
|
234
|
+
--no-cache 忽略快取,強制重新連線 Copilot 取得最新資料
|
|
235
|
+
\`\`\`
|
|
236
|
+
|
|
237
|
+
**範例:**
|
|
238
|
+
|
|
239
|
+
\`\`\`bash
|
|
240
|
+
npx ai-git-tools model-info
|
|
241
|
+
npx ai-git-tools model-info --model gpt-4.1
|
|
242
|
+
npx ai-git-tools model-info --filter claude
|
|
243
|
+
npx ai-git-tools model-info --json
|
|
244
|
+
\`\`\`
|
|
245
|
+
|
|
246
|
+
> 第一次執行會連線到 Copilot CLI 取得模型清單,可能需要數秒鐘;後續預設使用 24 小時快取。
|
|
247
|
+
|
|
202
248
|
## ⚙️ 配置
|
|
203
249
|
|
|
204
250
|
配置檔案範例(\`.ai-git-config.js\`):
|
|
@@ -352,6 +398,49 @@ feat(auth): 新增使用者登入功能
|
|
|
352
398
|
- 整合 JWT 認證機制
|
|
353
399
|
\`\`\`
|
|
354
400
|
|
|
401
|
+
## 🚀 發布到 npm
|
|
402
|
+
|
|
403
|
+
若你是本專案維護者,發布新版本到 npm 的步驟如下:
|
|
404
|
+
|
|
405
|
+
### 1. 確認已登入 npm
|
|
406
|
+
|
|
407
|
+
```bash
|
|
408
|
+
npm whoami
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
若未登入,請執行:
|
|
412
|
+
|
|
413
|
+
```bash
|
|
414
|
+
npm login
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
### 2. 確認版本號
|
|
418
|
+
|
|
419
|
+
修改 [package.json](package.json) 中的 `version` 欄位(例如 `2.0.80`)。
|
|
420
|
+
|
|
421
|
+
> 每次發布版本號必須遞增,否則 npm 會拒絕。
|
|
422
|
+
|
|
423
|
+
### 3. 執行發布
|
|
424
|
+
|
|
425
|
+
```bash
|
|
426
|
+
npm run lint
|
|
427
|
+
npm publish --access public
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
`prepublishOnly` 會自動執行 `npm run lint`,`prepare` 會自動設定 `bin/cli.js` 為可執行檔。
|
|
431
|
+
|
|
432
|
+
### 4. 驗證發布
|
|
433
|
+
|
|
434
|
+
```bash
|
|
435
|
+
npm view ai-git-tools version
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
### 注意事項
|
|
439
|
+
|
|
440
|
+
- 發布前請確認工作目錄乾淨(`git status` 無未提交變更)
|
|
441
|
+
- npm 會自動修正 `package.json` 格式問題(例如 `repository.url` 會正規化為 `git+https://...`)
|
|
442
|
+
- 公開套件請使用 `--access public`,私有套件可省略或改為 `--access restricted`
|
|
443
|
+
|
|
355
444
|
## 🤔 常見問題
|
|
356
445
|
|
|
357
446
|
### Q: 需要安裝 GitHub Copilot 嗎?
|
|
@@ -374,18 +463,3 @@ A: 目前遵循 Conventional Commits 規範。未來版本會支援自訂格式
|
|
|
374
463
|
|
|
375
464
|
A: 支援。工具會分析整個倉庫的變更。
|
|
376
465
|
|
|
377
|
-
## 📝 授權
|
|
378
|
-
|
|
379
|
-
MIT License
|
|
380
|
-
|
|
381
|
-
## 🤝 貢獻
|
|
382
|
-
|
|
383
|
-
歡迎提交 Issue 和 Pull Request!
|
|
384
|
-
|
|
385
|
-
## 📧 聯絡
|
|
386
|
-
|
|
387
|
-
有問題或建議?歡迎[提交 Issue](https://github.com/yourusername/ai-git-tools/issues)
|
|
388
|
-
|
|
389
|
-
---
|
|
390
|
-
|
|
391
|
-
Made with ❤️ by AI Git Tools Team
|
package/bin/cli.js
CHANGED
|
@@ -15,6 +15,7 @@ import { commitAllCommand } from '../src/commands/commit-all.js';
|
|
|
15
15
|
import { prCommand } from '../src/commands/pr.js';
|
|
16
16
|
import { initCommand } from '../src/commands/init.js';
|
|
17
17
|
import { usageCommand } from '../src/commands/usage.js';
|
|
18
|
+
import { modelInfoCommand } from '../src/commands/model-info.js';
|
|
18
19
|
import { registerCommand } from '../src/utils/cli-helpers.js';
|
|
19
20
|
|
|
20
21
|
// 讀取 package.json 獲取版本號
|
|
@@ -75,4 +76,12 @@ registerCommand(program, 'usage', '查看組織 GitHub Copilot 使用狀態與
|
|
|
75
76
|
{ flags: '--json', description: '以 JSON 格式輸出完整資料' },
|
|
76
77
|
], usageCommand);
|
|
77
78
|
|
|
79
|
+
// Model Info 命令
|
|
80
|
+
registerCommand(program, 'model-info', '查看目前可用的 AI 模型資訊', [
|
|
81
|
+
{ flags: '--json', description: '以 JSON 格式輸出完整模型資料' },
|
|
82
|
+
{ flags: '--filter <keyword>', description: '依模型名稱或描述關鍵字過濾' },
|
|
83
|
+
{ flags: '--model <model>', description: '查詢單一模型的詳細資訊' },
|
|
84
|
+
{ flags: '--no-cache', description: '忽略快取,強制重新連線 Copilot 取得最新模型清單' },
|
|
85
|
+
], modelInfoCommand);
|
|
86
|
+
|
|
78
87
|
program.parse();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-git-tools",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.81",
|
|
4
4
|
"description": "AI-powered Git automation tools for commit messages and PR generation",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
},
|
|
52
52
|
"repository": {
|
|
53
53
|
"type": "git",
|
|
54
|
-
"url": "https://github.com/YisoTsao/ai-git-tools.git"
|
|
54
|
+
"url": "git+https://github.com/YisoTsao/ai-git-tools.git"
|
|
55
55
|
},
|
|
56
56
|
"bugs": {
|
|
57
57
|
"url": "https://github.com/YisoTsao/ai-git-tools/issues"
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* model-info 命令
|
|
3
|
+
* 顯示 GitHub Copilot SDK 可用模型資訊
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import ora from 'ora';
|
|
7
|
+
import { Logger } from '../utils/logger.js';
|
|
8
|
+
import { fetchCopilotModels } from '../core/model-client.js';
|
|
9
|
+
import {
|
|
10
|
+
enrichModel,
|
|
11
|
+
getModelById,
|
|
12
|
+
filterModels,
|
|
13
|
+
formatPrice,
|
|
14
|
+
formatTokenCount,
|
|
15
|
+
} from '../data/copilot-models.js';
|
|
16
|
+
|
|
17
|
+
const logger = new Logger();
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 格式化建議用途陣列為可讀字串
|
|
21
|
+
* @param {string[]} items
|
|
22
|
+
* @returns {string}
|
|
23
|
+
*/
|
|
24
|
+
function formatRecommendedFor(items) {
|
|
25
|
+
if (!items || items.length === 0) return '—';
|
|
26
|
+
return items.join('、');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 格式化支援能力陣列
|
|
31
|
+
* @param {object} supports
|
|
32
|
+
* @returns {string}
|
|
33
|
+
*/
|
|
34
|
+
function formatCapabilities(supports = {}) {
|
|
35
|
+
const caps = [];
|
|
36
|
+
if (supports.vision) caps.push('vision');
|
|
37
|
+
if (supports.tool_calls) caps.push('tools');
|
|
38
|
+
if (supports.reasoningEffort || supports.reasoning_effort) caps.push('reasoning');
|
|
39
|
+
if (supports.streaming) caps.push('streaming');
|
|
40
|
+
if (supports.parallel_tool_calls) caps.push('parallel-tools');
|
|
41
|
+
return caps.length > 0 ? caps.join('、') : '—';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 取得模型狀態圖示
|
|
46
|
+
* @param {string} state
|
|
47
|
+
* @returns {string}
|
|
48
|
+
*/
|
|
49
|
+
function policyStateLabel(state) {
|
|
50
|
+
switch (state) {
|
|
51
|
+
case 'enabled':
|
|
52
|
+
return '啟用';
|
|
53
|
+
case 'disabled':
|
|
54
|
+
return '停用';
|
|
55
|
+
case 'unconfigured':
|
|
56
|
+
return '未配置';
|
|
57
|
+
default:
|
|
58
|
+
return state || '—';
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 印出單一模型詳細資訊
|
|
64
|
+
* @param {object} model
|
|
65
|
+
*/
|
|
66
|
+
function printModel(model) {
|
|
67
|
+
const billing = model.billing?.token_prices;
|
|
68
|
+
|
|
69
|
+
console.log(`\n ${logger.colors?.cyan ?? ''}${model.id}${logger.colors?.reset ?? ''}`);
|
|
70
|
+
console.log(' ─────────────────────────────────────────────────────────────');
|
|
71
|
+
console.log(` 名稱: ${model.name}`);
|
|
72
|
+
console.log(` 供應商: ${model.provider}`);
|
|
73
|
+
console.log(` 描述: ${model.description}`);
|
|
74
|
+
console.log(` 上下文: ${model.contextWindow}`);
|
|
75
|
+
console.log(` 最大輸出: ${model.maxOutputTokens}`);
|
|
76
|
+
console.log(` 建議用途: ${formatRecommendedFor(model.recommendedFor)}`);
|
|
77
|
+
console.log(` 速度: ${model.speed}`);
|
|
78
|
+
console.log(` 狀態: ${policyStateLabel(model.policy?.state)}`);
|
|
79
|
+
console.log(` 能力: ${formatCapabilities(model.capabilities?.supports)}`);
|
|
80
|
+
if (billing && billing.batch_size > 0) {
|
|
81
|
+
const hasAnyPrice =
|
|
82
|
+
billing.input_price > 0 || billing.output_price > 0 || billing.cache_price > 0;
|
|
83
|
+
if (hasAnyPrice) {
|
|
84
|
+
console.log(` 價格(預估):`);
|
|
85
|
+
console.log(
|
|
86
|
+
` 輸入: ${formatPrice(billing.input_price, billing.batch_size)} / ${formatTokenCount(billing.batch_size)} tokens`
|
|
87
|
+
);
|
|
88
|
+
console.log(
|
|
89
|
+
` 輸出: ${formatPrice(billing.output_price, billing.batch_size)} / ${formatTokenCount(billing.batch_size)} tokens`
|
|
90
|
+
);
|
|
91
|
+
if (billing.cache_price !== undefined) {
|
|
92
|
+
console.log(
|
|
93
|
+
` 快取: ${formatPrice(billing.cache_price, billing.batch_size)} / ${formatTokenCount(billing.batch_size)} tokens`
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (model.supportedReasoningEfforts && model.supportedReasoningEfforts.length > 0) {
|
|
99
|
+
console.log(
|
|
100
|
+
` Reasoning: ${model.supportedReasoningEfforts.join('、')}(預設:${model.defaultReasoningEffort || '—'})`
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
if (model.notes) {
|
|
104
|
+
console.log(` 備註: ${model.notes}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* 印出模型列表(精簡版)
|
|
110
|
+
* @param {object[]} models
|
|
111
|
+
*/
|
|
112
|
+
function printModelList(models) {
|
|
113
|
+
console.log();
|
|
114
|
+
for (const model of models) {
|
|
115
|
+
const idPart = `${logger.colors?.cyan ?? ''}${model.id.padEnd(18)}${logger.colors?.reset ?? ''}`;
|
|
116
|
+
console.log(` ${idPart} ${model.description}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* model-info 命令入口
|
|
122
|
+
* @param {object} options - commander 解析後的選項
|
|
123
|
+
*/
|
|
124
|
+
export async function modelInfoCommand(options) {
|
|
125
|
+
const { json, filter, model: modelId, cache } = options;
|
|
126
|
+
const noCache = cache === false;
|
|
127
|
+
|
|
128
|
+
let sdkModels = [];
|
|
129
|
+
let usedCache = false;
|
|
130
|
+
|
|
131
|
+
const spinner = ora('正在連線到 Copilot 取得模型清單...').start();
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
sdkModels = await fetchCopilotModels({ noCache });
|
|
135
|
+
spinner.stop();
|
|
136
|
+
} catch (error) {
|
|
137
|
+
spinner.stop();
|
|
138
|
+
if (error.cachedModels) {
|
|
139
|
+
sdkModels = error.cachedModels;
|
|
140
|
+
usedCache = true;
|
|
141
|
+
} else {
|
|
142
|
+
logger.error(`無法取得模型清單:${error.message}`);
|
|
143
|
+
logger.info('請確認已安裝 GitHub CLI 並完成 Copilot 授權(gh auth login)');
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const models = sdkModels.map(enrichModel);
|
|
149
|
+
|
|
150
|
+
if (json) {
|
|
151
|
+
let output = models;
|
|
152
|
+
if (filter) output = filterModels(filter, output);
|
|
153
|
+
if (modelId) {
|
|
154
|
+
const model = getModelById(modelId, output);
|
|
155
|
+
output = model ? [model] : [];
|
|
156
|
+
}
|
|
157
|
+
console.log(JSON.stringify(output, null, 2));
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
logger.section('🤖 Copilot 可用模型');
|
|
162
|
+
|
|
163
|
+
if (usedCache) {
|
|
164
|
+
logger.warning('即時取得失敗,顯示快取資料');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (modelId) {
|
|
168
|
+
const model = getModelById(modelId, models);
|
|
169
|
+
if (!model) {
|
|
170
|
+
logger.error(`找不到模型「${modelId}」`);
|
|
171
|
+
logger.info(`可用模型:${models.map(m => m.id).join('、')}`);
|
|
172
|
+
process.exit(1);
|
|
173
|
+
}
|
|
174
|
+
printModel(model);
|
|
175
|
+
console.log();
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const filteredModels = filterModels(filter, models);
|
|
180
|
+
if (filteredModels.length === 0) {
|
|
181
|
+
logger.warning('沒有符合條件的模型');
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (filter) {
|
|
186
|
+
printModelList(filteredModels);
|
|
187
|
+
} else {
|
|
188
|
+
for (const model of filteredModels) {
|
|
189
|
+
printModel(model);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
console.log();
|
|
194
|
+
logger.info(`共 ${filteredModels.length} 個模型,使用 --model <id> 查看詳細資訊`);
|
|
195
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copilot 模型列表客戶端
|
|
3
|
+
* 透過 @github/copilot-sdk 動態取得可用模型清單
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { writeFileSync, readFileSync, existsSync, mkdirSync } from 'fs';
|
|
7
|
+
import { tmpdir } from 'os';
|
|
8
|
+
import { join } from 'path';
|
|
9
|
+
import { CopilotClient } from '@github/copilot-sdk';
|
|
10
|
+
|
|
11
|
+
// 暫存快取檔案路徑
|
|
12
|
+
const CACHE_DIR = join(tmpdir(), 'ai-git-tools');
|
|
13
|
+
const CACHE_FILE = join(CACHE_DIR, 'copilot-models-cache.json');
|
|
14
|
+
const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 小時
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 確保快取目錄存在
|
|
18
|
+
*/
|
|
19
|
+
function ensureCacheDir() {
|
|
20
|
+
if (!existsSync(CACHE_DIR)) {
|
|
21
|
+
mkdirSync(CACHE_DIR, { recursive: true });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 讀取快取的模型清單
|
|
27
|
+
* @returns {object | null}
|
|
28
|
+
*/
|
|
29
|
+
function readCache() {
|
|
30
|
+
try {
|
|
31
|
+
if (!existsSync(CACHE_FILE)) return null;
|
|
32
|
+
const raw = readFileSync(CACHE_FILE, 'utf-8');
|
|
33
|
+
const parsed = JSON.parse(raw);
|
|
34
|
+
if (!parsed.fetchedAt || !Array.isArray(parsed.models)) return null;
|
|
35
|
+
|
|
36
|
+
const age = Date.now() - new Date(parsed.fetchedAt).getTime();
|
|
37
|
+
if (age > CACHE_TTL_MS) return null;
|
|
38
|
+
|
|
39
|
+
return parsed;
|
|
40
|
+
} catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 寫入快取
|
|
47
|
+
* @param {Array<object>} models
|
|
48
|
+
*/
|
|
49
|
+
function writeCache(models) {
|
|
50
|
+
try {
|
|
51
|
+
ensureCacheDir();
|
|
52
|
+
writeFileSync(
|
|
53
|
+
CACHE_FILE,
|
|
54
|
+
JSON.stringify({ fetchedAt: new Date().toISOString(), models }, null, 2),
|
|
55
|
+
'utf-8'
|
|
56
|
+
);
|
|
57
|
+
} catch {
|
|
58
|
+
// 快取寫入失敗不影響主要功能
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 清除模型快取
|
|
64
|
+
*/
|
|
65
|
+
export function clearModelCache() {
|
|
66
|
+
try {
|
|
67
|
+
if (existsSync(CACHE_FILE)) {
|
|
68
|
+
writeFileSync(CACHE_FILE, '', 'utf-8');
|
|
69
|
+
}
|
|
70
|
+
} catch {
|
|
71
|
+
// 忽略清除失敗
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* 從 Copilot SDK 取得可用模型清單
|
|
77
|
+
* @param {object} options
|
|
78
|
+
* @param {boolean} [options.noCache=false] - 是否忽略快取強制重新抓取
|
|
79
|
+
* @param {boolean} [options.fallbackToCache=true] - SDK 失敗時是否回退使用快取
|
|
80
|
+
* @returns {Promise<Array<object>>}
|
|
81
|
+
*/
|
|
82
|
+
export async function fetchCopilotModels({ noCache = false, fallbackToCache = true } = {}) {
|
|
83
|
+
if (!noCache) {
|
|
84
|
+
const cached = readCache();
|
|
85
|
+
if (cached) {
|
|
86
|
+
return cached.models;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// 抑制 Copilot CLI 子程序的 SQLite ExperimentalWarning
|
|
91
|
+
process.env.NODE_NO_WARNINGS = '1';
|
|
92
|
+
|
|
93
|
+
const client = new CopilotClient();
|
|
94
|
+
try {
|
|
95
|
+
await client.start();
|
|
96
|
+
const models = await client.listModels();
|
|
97
|
+
|
|
98
|
+
writeCache(models);
|
|
99
|
+
return models;
|
|
100
|
+
} catch (error) {
|
|
101
|
+
if (fallbackToCache) {
|
|
102
|
+
const cached = readCache();
|
|
103
|
+
if (cached) {
|
|
104
|
+
error.fallbackToCache = true;
|
|
105
|
+
error.cachedModels = cached.models;
|
|
106
|
+
throw error;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
throw error;
|
|
110
|
+
} finally {
|
|
111
|
+
try {
|
|
112
|
+
await client.stop();
|
|
113
|
+
} catch {
|
|
114
|
+
// 忽略關閉錯誤
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copilot 模型本機增強資料表
|
|
3
|
+
* 用來補充 Copilot SDK listModels() 回傳的動態資料
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const MODEL_ENHANCEMENTS = {};
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 推斷模型供應商
|
|
10
|
+
* @param {string} modelId
|
|
11
|
+
* @returns {string}
|
|
12
|
+
*/
|
|
13
|
+
export function inferProvider(modelId) {
|
|
14
|
+
if (modelId.startsWith('claude')) return 'Anthropic';
|
|
15
|
+
if (modelId.startsWith('gpt')) return 'OpenAI';
|
|
16
|
+
return 'Unknown';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 將 token 數格式化為可讀字串(例如 264000 → 264K)
|
|
21
|
+
* @param {number} tokens
|
|
22
|
+
* @returns {string}
|
|
23
|
+
*/
|
|
24
|
+
export function formatTokenCount(tokens) {
|
|
25
|
+
if (!tokens && tokens !== 0) return '—';
|
|
26
|
+
if (tokens >= 1_000_000) return `${(tokens / 1_000_000).toFixed(1).replace(/\.0$/, '')}M`;
|
|
27
|
+
if (tokens >= 1_000) return `${Math.round(tokens / 1_000)}K`;
|
|
28
|
+
return `${tokens}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 取得模型的本機增強資料
|
|
33
|
+
* @param {string} modelId
|
|
34
|
+
* @returns {object}
|
|
35
|
+
*/
|
|
36
|
+
export function getModelEnhancement(modelId) {
|
|
37
|
+
return (
|
|
38
|
+
MODEL_ENHANCEMENTS[modelId] || {
|
|
39
|
+
description: 'Copilot SDK 回報的可用模型',
|
|
40
|
+
recommendedFor: [],
|
|
41
|
+
speed: 'medium',
|
|
42
|
+
notes: '',
|
|
43
|
+
}
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 合併 SDK 模型資料與本機增強資料
|
|
49
|
+
* @param {object} sdkModel
|
|
50
|
+
* @returns {object}
|
|
51
|
+
*/
|
|
52
|
+
export function enrichModel(sdkModel) {
|
|
53
|
+
const enhancement = getModelEnhancement(sdkModel.id);
|
|
54
|
+
const limits = sdkModel.capabilities?.limits || {};
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
...sdkModel,
|
|
58
|
+
provider: inferProvider(sdkModel.id),
|
|
59
|
+
description: enhancement.description,
|
|
60
|
+
recommendedFor: enhancement.recommendedFor,
|
|
61
|
+
speed: enhancement.speed,
|
|
62
|
+
notes: enhancement.notes,
|
|
63
|
+
contextWindow: formatTokenCount(limits.max_context_window_tokens),
|
|
64
|
+
maxOutputTokens: formatTokenCount(limits.max_output_tokens),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* 格式化價格(將 SDK 內部單位轉換為美元 / 1M tokens)
|
|
70
|
+
* @param {number} price
|
|
71
|
+
* @param {number} batchSize
|
|
72
|
+
* @returns {string}
|
|
73
|
+
*/
|
|
74
|
+
export function formatPrice(price, batchSize) {
|
|
75
|
+
if (price === undefined || price === null) return '—';
|
|
76
|
+
if (!batchSize || batchSize === 0 || price === 0) return '—';
|
|
77
|
+
// SDK 內部單位為 10^-11 美元 / batch_size tokens
|
|
78
|
+
const dollars = price / 100_000_000_000;
|
|
79
|
+
return `$${dollars.toFixed(2)}`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* 依模型 ID 取得增強後資訊(從 SDK 模型清單中)
|
|
84
|
+
* @param {string} id
|
|
85
|
+
* @param {Array<object>} models
|
|
86
|
+
* @returns {object | undefined}
|
|
87
|
+
*/
|
|
88
|
+
export function getModelById(id, models = []) {
|
|
89
|
+
return models.find(m => m.id === id);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* 依關鍵字過濾模型
|
|
94
|
+
* @param {string} keyword
|
|
95
|
+
* @param {Array<object>} models
|
|
96
|
+
* @returns {Array<object>}
|
|
97
|
+
*/
|
|
98
|
+
export function filterModels(keyword, models = []) {
|
|
99
|
+
if (!keyword) return models;
|
|
100
|
+
const lower = keyword.toLowerCase();
|
|
101
|
+
return models.filter(
|
|
102
|
+
m =>
|
|
103
|
+
m.id.toLowerCase().includes(lower) ||
|
|
104
|
+
m.name.toLowerCase().includes(lower) ||
|
|
105
|
+
(m.description && m.description.toLowerCase().includes(lower)) ||
|
|
106
|
+
(m.provider && m.provider.toLowerCase().includes(lower))
|
|
107
|
+
);
|
|
108
|
+
}
|