@coze-arch/cli 0.0.15-alpha.a0f5b9 → 0.0.15-alpha.dfd4c4

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.
Files changed (103) hide show
  1. package/lib/__templates__/expo/.coze +1 -0
  2. package/lib/__templates__/expo/.cozeproj/scripts/validate.sh +8 -0
  3. package/lib/__templates__/expo/package.json +2 -1
  4. package/lib/__templates__/nextjs/.coze +1 -0
  5. package/lib/__templates__/nextjs/package.json +3 -1
  6. package/lib/__templates__/nextjs/scripts/validate.sh +10 -0
  7. package/lib/__templates__/nuxt-vue/.coze +1 -0
  8. package/lib/__templates__/nuxt-vue/eslint.config.mjs +25 -0
  9. package/lib/__templates__/nuxt-vue/package.json +9 -2
  10. package/lib/__templates__/nuxt-vue/pnpm-lock.yaml +790 -10
  11. package/lib/__templates__/nuxt-vue/scripts/validate.sh +10 -0
  12. package/lib/__templates__/taro/.coze +1 -0
  13. package/lib/__templates__/taro/.cozeproj/scripts/validate.sh +8 -0
  14. package/lib/__templates__/taro/package.json +1 -1
  15. package/lib/__templates__/templates.json +0 -24
  16. package/lib/__templates__/vite/.coze +1 -0
  17. package/lib/__templates__/vite/package.json +3 -1
  18. package/lib/__templates__/vite/scripts/validate.sh +10 -0
  19. package/lib/cli.js +330 -97
  20. package/package.json +1 -1
  21. package/lib/__templates__/pi-agent/.coze +0 -10
  22. package/lib/__templates__/pi-agent/AGENTS.md +0 -140
  23. package/lib/__templates__/pi-agent/README.md +0 -172
  24. package/lib/__templates__/pi-agent/_gitignore +0 -3
  25. package/lib/__templates__/pi-agent/_npmrc +0 -23
  26. package/lib/__templates__/pi-agent/docs/project-overview.md +0 -356
  27. package/lib/__templates__/pi-agent/docs/user/getting-started.md +0 -47
  28. package/lib/__templates__/pi-agent/package.json +0 -60
  29. package/lib/__templates__/pi-agent/pi-resources/SYSTEM.md +0 -15
  30. package/lib/__templates__/pi-agent/pi-resources/extensions/preference-memory/index.ts +0 -355
  31. package/lib/__templates__/pi-agent/pi-resources/extensions/test-ping.ts +0 -19
  32. package/lib/__templates__/pi-agent/pi-resources/prompts/test-prompt.md +0 -11
  33. package/lib/__templates__/pi-agent/pi-resources/skills/coze-asr/SKILL.md +0 -36
  34. package/lib/__templates__/pi-agent/pi-resources/skills/coze-asr/scripts/asr.mjs +0 -9
  35. package/lib/__templates__/pi-agent/pi-resources/skills/coze-image-gen/SKILL.md +0 -41
  36. package/lib/__templates__/pi-agent/pi-resources/skills/coze-image-gen/scripts/gen.mjs +0 -9
  37. package/lib/__templates__/pi-agent/pi-resources/skills/coze-tts/SKILL.md +0 -85
  38. package/lib/__templates__/pi-agent/pi-resources/skills/coze-tts/scripts/tts.mjs +0 -9
  39. package/lib/__templates__/pi-agent/pi-resources/skills/coze-video-gen/SKILL.md +0 -53
  40. package/lib/__templates__/pi-agent/pi-resources/skills/coze-video-gen/scripts/gen.mjs +0 -9
  41. package/lib/__templates__/pi-agent/pnpm-lock.yaml +0 -8285
  42. package/lib/__templates__/pi-agent/scripts/dev.sh +0 -14
  43. package/lib/__templates__/pi-agent/scripts/prepare.sh +0 -2
  44. package/lib/__templates__/pi-agent/src/agent.ts +0 -363
  45. package/lib/__templates__/pi-agent/src/channels/feishu/index.ts +0 -760
  46. package/lib/__templates__/pi-agent/src/channels/feishu/streaming-card.ts +0 -297
  47. package/lib/__templates__/pi-agent/src/channels/wechat/index.ts +0 -171
  48. package/lib/__templates__/pi-agent/src/config.ts +0 -596
  49. package/lib/__templates__/pi-agent/src/core.ts +0 -218
  50. package/lib/__templates__/pi-agent/src/dashboard/api/channels.ts +0 -148
  51. package/lib/__templates__/pi-agent/src/dashboard/api/docs.ts +0 -204
  52. package/lib/__templates__/pi-agent/src/dashboard/api/models.ts +0 -141
  53. package/lib/__templates__/pi-agent/src/dashboard/api/overview.ts +0 -33
  54. package/lib/__templates__/pi-agent/src/dashboard/config-store.ts +0 -64
  55. package/lib/__templates__/pi-agent/src/dashboard/index.ts +0 -39
  56. package/lib/__templates__/pi-agent/src/dashboard/server.ts +0 -622
  57. package/lib/__templates__/pi-agent/src/dashboard/types.ts +0 -25
  58. package/lib/__templates__/pi-agent/src/dashboard/web/index.html +0 -13
  59. package/lib/__templates__/pi-agent/src/dashboard/web/postcss.config.cjs +0 -7
  60. package/lib/__templates__/pi-agent/src/dashboard/web/src/components/app-layout.tsx +0 -186
  61. package/lib/__templates__/pi-agent/src/dashboard/web/src/components/page-title.tsx +0 -17
  62. package/lib/__templates__/pi-agent/src/dashboard/web/src/components/ui/alert.tsx +0 -22
  63. package/lib/__templates__/pi-agent/src/dashboard/web/src/components/ui/badge.tsx +0 -25
  64. package/lib/__templates__/pi-agent/src/dashboard/web/src/components/ui/button.tsx +0 -40
  65. package/lib/__templates__/pi-agent/src/dashboard/web/src/components/ui/card.tsx +0 -29
  66. package/lib/__templates__/pi-agent/src/dashboard/web/src/components/ui/input.tsx +0 -18
  67. package/lib/__templates__/pi-agent/src/dashboard/web/src/components/ui/label.tsx +0 -8
  68. package/lib/__templates__/pi-agent/src/dashboard/web/src/components/ui/select.tsx +0 -80
  69. package/lib/__templates__/pi-agent/src/dashboard/web/src/components/ui/separator.tsx +0 -23
  70. package/lib/__templates__/pi-agent/src/dashboard/web/src/hooks/use-fetch.ts +0 -32
  71. package/lib/__templates__/pi-agent/src/dashboard/web/src/hooks/use-local-storage-state.ts +0 -23
  72. package/lib/__templates__/pi-agent/src/dashboard/web/src/main.tsx +0 -30
  73. package/lib/__templates__/pi-agent/src/dashboard/web/src/pages/channels-page.tsx +0 -188
  74. package/lib/__templates__/pi-agent/src/dashboard/web/src/pages/chat-page.tsx +0 -451
  75. package/lib/__templates__/pi-agent/src/dashboard/web/src/pages/docs-page.tsx +0 -65
  76. package/lib/__templates__/pi-agent/src/dashboard/web/src/pages/models-page.tsx +0 -122
  77. package/lib/__templates__/pi-agent/src/dashboard/web/src/pages/overview-page.tsx +0 -134
  78. package/lib/__templates__/pi-agent/src/dashboard/web/src/services/chat-ws-service.ts +0 -167
  79. package/lib/__templates__/pi-agent/src/dashboard/web/src/styles.css +0 -294
  80. package/lib/__templates__/pi-agent/src/dashboard/web/src/utils/index.ts +0 -11
  81. package/lib/__templates__/pi-agent/src/dashboard/web/tsconfig.json +0 -13
  82. package/lib/__templates__/pi-agent/src/dashboard/web/vite.config.ts +0 -17
  83. package/lib/__templates__/pi-agent/src/index.ts +0 -123
  84. package/lib/__templates__/pi-agent/src/pi-resources.ts +0 -125
  85. package/lib/__templates__/pi-agent/src/session-store.ts +0 -223
  86. package/lib/__templates__/pi-agent/src/tools/common/format-coze-error.ts +0 -12
  87. package/lib/__templates__/pi-agent/src/tools/index.ts +0 -2
  88. package/lib/__templates__/pi-agent/src/tools/web-fetch/index.ts +0 -195
  89. package/lib/__templates__/pi-agent/src/tools/web-search/index.ts +0 -206
  90. package/lib/__templates__/pi-agent/template.config.js +0 -45
  91. package/lib/__templates__/pi-agent/tests/config.test.ts +0 -315
  92. package/lib/__templates__/pi-agent/tests/dashboard-docs-api.test.ts +0 -125
  93. package/lib/__templates__/pi-agent/tests/dashboard-models-api.test.ts +0 -171
  94. package/lib/__templates__/pi-agent/tests/feishu-channel.test.ts +0 -149
  95. package/lib/__templates__/pi-agent/tests/feishu-streaming-card.test.ts +0 -15
  96. package/lib/__templates__/pi-agent/tests/pi-resources.test.ts +0 -73
  97. package/lib/__templates__/pi-agent/tests/preference-memory.test.ts +0 -43
  98. package/lib/__templates__/pi-agent/tests/session-store.test.ts +0 -61
  99. package/lib/__templates__/pi-agent/tests/smoke/run-smoke.ts +0 -275
  100. package/lib/__templates__/pi-agent/tests/web-fetch.test.ts +0 -157
  101. package/lib/__templates__/pi-agent/tests/web-search.test.ts +0 -208
  102. package/lib/__templates__/pi-agent/tsconfig.json +0 -21
  103. package/lib/__templates__/pi-agent/types/larksuiteoapi-node-sdk.d.ts +0 -113
@@ -1,315 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
3
- import { tmpdir } from "node:os";
4
- import { dirname, join, resolve } from "node:path";
5
- import test from "node:test";
6
- import { AuthStorage, ModelRegistry } from "@mariozechner/pi-coding-agent";
7
- import {
8
- getDefaultConfigPath,
9
- loadConfig,
10
- loadBotAppConfig,
11
- resolveRuntimeModel
12
- } from "../src/config.js";
13
-
14
- function createConfigFile(config: unknown): string {
15
- const tempDir = mkdtempSync(join(tmpdir(), "pi-bot-workspace-config-"));
16
- const workspaceDir = join(tempDir, "workspace");
17
- const configPath = join(workspaceDir, "config.json");
18
-
19
- mkdirSync(workspaceDir, { recursive: true });
20
- writeFileSync(configPath, JSON.stringify(config, null, 2));
21
-
22
- return configPath;
23
- }
24
-
25
- test("loadConfig resolves workspace directory and default model", () => {
26
- const configPath = createConfigFile({
27
- agents: {
28
- defaults: {
29
- workspace: ".",
30
- thinkingLevel: "medium",
31
- model: {
32
- primary: "coze/glm-4-7-251222"
33
- }
34
- }
35
- },
36
- models: {
37
- providers: {
38
- coze: {
39
- api: "openai-completions",
40
- baseUrl: "${COZE_BASE_URL}",
41
- apiKey: "${COZE_API_KEY}",
42
- models: [
43
- {
44
- id: "glm-4-7-251222",
45
- name: "GLM 4.7",
46
- input: ["text"],
47
- reasoning: false,
48
- contextWindow: 200000,
49
- maxTokens: 8192,
50
- cost: {
51
- input: 0,
52
- output: 0,
53
- cacheRead: 0,
54
- cacheWrite: 0
55
- }
56
- }
57
- ]
58
- }
59
- }
60
- }
61
- });
62
-
63
- const loaded = loadConfig(configPath);
64
- const tempRoot = dirname(dirname(configPath));
65
-
66
- rmSync(tempRoot, { recursive: true, force: true });
67
-
68
- assert.equal(loaded.workspaceDir, dirname(configPath));
69
- assert.equal(loaded.thinkingLevel, "medium");
70
- assert.deepEqual(loaded.defaultModel, {
71
- provider: "coze",
72
- modelId: "glm-4-7-251222"
73
- });
74
- });
75
-
76
- test("loadBotAppConfig defaults agent mode to pi when env override is absent", () => {
77
- const configPath = createConfigFile({
78
- agents: {
79
- defaults: {
80
- workspace: ".",
81
- model: {
82
- primary: "openai/gpt-5-mini"
83
- }
84
- }
85
- }
86
- });
87
-
88
- const loaded = loadBotAppConfig({
89
- configPath,
90
- env: {}
91
- });
92
- const tempRoot = dirname(dirname(configPath));
93
-
94
- rmSync(tempRoot, { recursive: true, force: true });
95
-
96
- assert.equal(loaded.agent.mode, "pi");
97
- });
98
-
99
- test("resolveRuntimeModel builds a custom provider model from workspace/config.json", async () => {
100
- const configPath = createConfigFile({
101
- agents: {
102
- defaults: {
103
- workspace: ".",
104
- model: {
105
- primary: "coze/glm-4-7-251222"
106
- }
107
- }
108
- },
109
- models: {
110
- providers: {
111
- missing: {
112
- api: "openai-completions",
113
- baseUrl: "${MISSING_BASE_URL}",
114
- apiKey: "${MISSING_API_KEY}",
115
- models: [
116
- {
117
- id: "unused-model",
118
- name: "Unused",
119
- input: ["text"],
120
- reasoning: false
121
- }
122
- ]
123
- },
124
- coze: {
125
- api: "openai-completions",
126
- baseUrl: "${COZE_BASE_URL}",
127
- apiKey: "${COZE_API_KEY}",
128
- headers: {
129
- "X-Workspace": "${WORKSPACE_ID}"
130
- },
131
- models: [
132
- {
133
- id: "glm-4-7-251222",
134
- name: "GLM 4.7",
135
- input: ["text"],
136
- reasoning: false,
137
- contextWindow: 200000,
138
- maxTokens: 8192,
139
- headers: {
140
- "X-Trace": "${TRACE_ID}"
141
- },
142
- cost: {
143
- input: 0,
144
- output: 0,
145
- cacheRead: 0,
146
- cacheWrite: 0
147
- }
148
- }
149
- ]
150
- }
151
- }
152
- }
153
- });
154
-
155
- const tempRoot = dirname(dirname(configPath));
156
- const env = {
157
- COZE_BASE_URL: "https://coze.example.com/v1",
158
- COZE_API_KEY: "coze-secret",
159
- WORKSPACE_ID: "workspace-1",
160
- TRACE_ID: "trace-1"
161
- };
162
-
163
- const resolved = resolveRuntimeModel({
164
- configPath,
165
- env
166
- });
167
-
168
- assert.ok(resolved);
169
- assert.equal(resolved.model.provider, "coze");
170
- assert.equal(resolved.model.api, "openai-completions");
171
- assert.equal(resolved.model.baseUrl, env.COZE_BASE_URL);
172
- assert.equal(resolved.apiKey, env.COZE_API_KEY);
173
- assert.deepEqual(resolved.model.headers, {
174
- "X-Workspace": env.WORKSPACE_ID,
175
- "X-Trace": env.TRACE_ID
176
- });
177
-
178
- const authStorage = AuthStorage.inMemory();
179
- const modelRegistry = new ModelRegistry(authStorage, join(tempRoot, "absent-models.json"));
180
-
181
- authStorage.setRuntimeApiKey(resolved.model.provider, resolved.apiKey!);
182
-
183
- const auth = await modelRegistry.getApiKeyAndHeaders(resolved.model);
184
- rmSync(tempRoot, { recursive: true, force: true });
185
-
186
- assert.deepEqual(auth, {
187
- ok: true,
188
- apiKey: env.COZE_API_KEY,
189
- headers: {
190
- "X-Workspace": env.WORKSPACE_ID,
191
- "X-Trace": env.TRACE_ID
192
- }
193
- });
194
- });
195
-
196
- test("resolveRuntimeModel applies workspace overrides to built-in providers", () => {
197
- const configPath = createConfigFile({
198
- agents: {
199
- defaults: {
200
- workspace: ".",
201
- model: {
202
- primary: "openai/gpt-5-mini"
203
- }
204
- }
205
- },
206
- models: {
207
- providers: {
208
- openai: {
209
- baseUrl: "https://proxy.example.com/v1",
210
- headers: {
211
- "X-Proxy": "${PROXY_ID}"
212
- },
213
- modelOverrides: {
214
- "gpt-5-mini": {
215
- maxTokens: 4096
216
- }
217
- }
218
- }
219
- }
220
- }
221
- });
222
-
223
- const tempRoot = dirname(dirname(configPath));
224
- const resolved = resolveRuntimeModel({
225
- provider: "openai",
226
- model: "gpt-5-mini",
227
- baseUrl: "https://forced.example.com/v1",
228
- configPath,
229
- env: {
230
- PROXY_ID: "proxy-1"
231
- }
232
- });
233
-
234
- rmSync(tempRoot, { recursive: true, force: true });
235
-
236
- assert.ok(resolved);
237
- assert.equal(resolved.model.provider, "openai");
238
- assert.equal(resolved.model.id, "gpt-5-mini");
239
- assert.equal(resolved.model.baseUrl, "https://forced.example.com/v1");
240
- assert.equal(resolved.model.maxTokens, 4096);
241
- assert.equal(resolved.model.headers?.["X-Proxy"], "proxy-1");
242
- });
243
-
244
- test("loadBotAppConfig builds the app config from config.json plus env-backed channels", () => {
245
- const configPath = createConfigFile({
246
- agents: {
247
- defaults: {
248
- workspace: ".",
249
- thinkingLevel: "high",
250
- model: {
251
- primary: "coze/auto"
252
- }
253
- }
254
- },
255
- models: {
256
- providers: {
257
- coze: {
258
- api: "openai-completions",
259
- apiKey: "${COZE_API_KEY}",
260
- baseUrl: "${COZE_BASE_URL}",
261
- models: [
262
- {
263
- id: "auto",
264
- name: "Auto",
265
- input: ["text"],
266
- reasoning: false
267
- }
268
- ]
269
- }
270
- }
271
- },
272
- channels: {
273
- feishu: {
274
- enabled: true,
275
- appId: "${FEISHU_APP_ID}",
276
- appSecret: "${FEISHU_APP_SECRET}",
277
- domain: "${FEISHU_DOMAIN}",
278
- encryptKey: "${FEISHU_ENCRYPT_KEY}",
279
- verificationToken: "${FEISHU_VERIFICATION_TOKEN}",
280
- requireMention: false,
281
- thinkingReaction: {
282
- enabled: false,
283
- emojiType: "OneSecond"
284
- }
285
- }
286
- }
287
- });
288
- const tempRoot = dirname(dirname(configPath));
289
- const loaded = loadBotAppConfig({
290
- configPath,
291
- env: {
292
- PI_BOT_AGENT_MODE: "pi",
293
- FEISHU_APP_ID: "app-id",
294
- FEISHU_APP_SECRET: "app-secret",
295
- FEISHU_DOMAIN: "feishu.example.com",
296
- FEISHU_ENCRYPT_KEY: "encrypt-key",
297
- FEISHU_VERIFICATION_TOKEN: "verify-token"
298
- }
299
- });
300
-
301
- rmSync(tempRoot, { recursive: true, force: true });
302
-
303
- assert.equal(getDefaultConfigPath(), resolve(process.cwd(), "../workspace/config.json"));
304
- assert.equal(loaded.agent.mode, "pi");
305
- assert.equal(loaded.agent.provider, "coze");
306
- assert.equal(loaded.agent.model, "auto");
307
- assert.equal(loaded.agent.configPath, configPath);
308
- assert.equal(loaded.agent.thinkingLevel, "high");
309
- assert.equal(loaded.agent.cwd, dirname(configPath));
310
- assert.equal(loaded.channels.feishu?.appId, "app-id");
311
- assert.equal(loaded.channels.feishu?.verificationToken, "verify-token");
312
- assert.equal(loaded.routing.feishuGroupRequireMention, false);
313
- assert.equal(loaded.channels.feishu?.thinkingReaction?.enabled, false);
314
- assert.equal(loaded.channels.feishu?.thinkingReaction?.emojiType, "OneSecond");
315
- });
@@ -1,125 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
3
- import { tmpdir } from "node:os";
4
- import { dirname, join } from "node:path";
5
- import test from "node:test";
6
- import { readDocsResponse } from "../src/dashboard/api/docs.js";
7
-
8
- function writeDoc(rootDir: string, relativePath: string, content: string) {
9
- const filePath = join(rootDir, relativePath);
10
- mkdirSync(dirname(filePath), { recursive: true });
11
- writeFileSync(filePath, content, "utf-8");
12
- }
13
-
14
- test("readDocsResponse lists markdown docs by order and picks the first doc by default", () => {
15
- const docsDir = mkdtempSync(join(tmpdir(), "pi-bot-dashboard-docs-"));
16
-
17
- try {
18
- writeDoc(
19
- docsDir,
20
- "channels/feishu.md",
21
- `---
22
- title: 飞书接入
23
- summary: 飞书文档
24
- group: 渠道
25
- order: 30
26
- ---
27
- # 飞书接入
28
-
29
- 这里是飞书说明。
30
- `
31
- );
32
- writeDoc(
33
- docsDir,
34
- "getting-started.md",
35
- `---
36
- title: 快速开始
37
- summary: 启动项目
38
- group: 入门
39
- order: 10
40
- ---
41
- # 快速开始
42
-
43
- 先启动项目。
44
- `
45
- );
46
-
47
- const response = readDocsResponse({ docsDir });
48
-
49
- assert.deepEqual(
50
- response.docs.map((doc) => doc.slug),
51
- ["getting-started", "channels/feishu"]
52
- );
53
- assert.equal(response.selectedDoc?.slug, "getting-started");
54
- assert.equal(response.requestedSlugFound, true);
55
- } finally {
56
- rmSync(docsDir, { recursive: true, force: true });
57
- }
58
- });
59
-
60
- test("readDocsResponse returns the requested doc and strips frontmatter from content", () => {
61
- const docsDir = mkdtempSync(join(tmpdir(), "pi-bot-dashboard-docs-selected-"));
62
-
63
- try {
64
- writeDoc(
65
- docsDir,
66
- "getting-started.md",
67
- `---
68
- title: 快速开始
69
- summary: 启动项目
70
- group: 入门
71
- order: 10
72
- ---
73
- # 快速开始
74
-
75
- 先启动项目。
76
- `
77
- );
78
- writeDoc(
79
- docsDir,
80
- "channels/feishu.md",
81
- `---
82
- title: 飞书接入
83
- summary: 飞书文档
84
- group: 渠道
85
- order: 30
86
- ---
87
- # 飞书接入
88
-
89
- 这里是飞书说明。
90
- `
91
- );
92
-
93
- const response = readDocsResponse({ docsDir, slug: "channels/feishu" });
94
-
95
- assert.equal(response.selectedDoc?.slug, "channels/feishu");
96
- assert.match(response.selectedDoc?.content ?? "", /^# 飞书接入/m);
97
- assert.doesNotMatch(response.selectedDoc?.content ?? "", /^---$/m);
98
- assert.equal(response.requestedSlugFound, true);
99
- } finally {
100
- rmSync(docsDir, { recursive: true, force: true });
101
- }
102
- });
103
-
104
- test("readDocsResponse falls back to the default doc when the requested slug is missing", () => {
105
- const docsDir = mkdtempSync(join(tmpdir(), "pi-bot-dashboard-docs-fallback-"));
106
-
107
- try {
108
- writeDoc(
109
- docsDir,
110
- "getting-started.md",
111
- `# 快速开始
112
-
113
- 先启动项目。
114
- `
115
- );
116
-
117
- const response = readDocsResponse({ docsDir, slug: "does-not-exist" });
118
-
119
- assert.equal(response.selectedDoc?.slug, "getting-started");
120
- assert.equal(response.requestedSlug, "does-not-exist");
121
- assert.equal(response.requestedSlugFound, false);
122
- } finally {
123
- rmSync(docsDir, { recursive: true, force: true });
124
- }
125
- });
@@ -1,171 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
3
- import { tmpdir } from "node:os";
4
- import { join } from "node:path";
5
- import test from "node:test";
6
- import { ValidationError, readModelsResponse, saveModelsRequest } from "../src/dashboard/api/models.js";
7
-
8
- function createConfigFile(config: unknown): { configPath: string; cleanup: () => void } {
9
- const tempDir = mkdtempSync(join(tmpdir(), "pi-bot-dashboard-models-"));
10
- const workspaceDir = join(tempDir, "workspace");
11
- const configPath = join(workspaceDir, "config.json");
12
-
13
- mkdirSync(workspaceDir, { recursive: true });
14
- writeFileSync(configPath, JSON.stringify(config, null, 2));
15
-
16
- return {
17
- configPath,
18
- cleanup: () => rmSync(tempDir, { recursive: true, force: true }),
19
- };
20
- }
21
-
22
- test("readModelsResponse exposes default model and selectable options", () => {
23
- const { configPath, cleanup } = createConfigFile({
24
- agents: {
25
- defaults: {
26
- model: {
27
- primary: "coze/auto",
28
- },
29
- },
30
- },
31
- models: {
32
- providers: {
33
- coze: {
34
- api: "openai-completions",
35
- apiKey: "${COZE_API_KEY}",
36
- baseUrl: "https://coze.example.com/v1",
37
- authHeader: true,
38
- models: [
39
- {
40
- id: "auto",
41
- name: "Auto",
42
- input: ["text", "image"],
43
- reasoning: true,
44
- contextWindow: 200000,
45
- maxTokens: 8192,
46
- },
47
- ],
48
- },
49
- },
50
- },
51
- });
52
-
53
- const result = readModelsResponse({ configPath });
54
- cleanup();
55
-
56
- assert.equal(result.defaultModel, "coze/auto");
57
- assert.deepEqual(result.options, [{ value: "coze/auto", label: "coze / Auto" }]);
58
- });
59
-
60
- test("saveModelsRequest updates default model while preserving unrelated config", () => {
61
- const { configPath, cleanup } = createConfigFile({
62
- agents: {
63
- defaults: {
64
- model: {
65
- primary: "coze/auto",
66
- },
67
- },
68
- },
69
- models: {
70
- providers: {
71
- coze: {
72
- api: "openai-completions",
73
- apiKey: "${COZE_API_KEY}",
74
- baseUrl: "https://coze.example.com/v1",
75
- headers: {
76
- "X-Provider": "keep-me",
77
- },
78
- models: [
79
- {
80
- id: "auto",
81
- name: "Auto",
82
- input: ["text"],
83
- reasoning: false,
84
- contextWindow: 200000,
85
- maxTokens: 8192,
86
- cost: {
87
- input: 0,
88
- output: 0,
89
- cacheRead: 0,
90
- cacheWrite: 0,
91
- },
92
- },
93
- {
94
- id: "glm-4.7",
95
- name: "GLM-4.7",
96
- input: ["text"],
97
- reasoning: false,
98
- contextWindow: 256000,
99
- maxTokens: 8192,
100
- cost: {
101
- input: 0,
102
- output: 0,
103
- cacheRead: 0,
104
- cacheWrite: 0,
105
- },
106
- },
107
- ],
108
- },
109
- },
110
- },
111
- channels: {
112
- feishu: {
113
- enabled: true,
114
- },
115
- },
116
- });
117
-
118
- saveModelsRequest({
119
- configPath,
120
- body: {
121
- models: {
122
- defaultModel: "coze/glm-4.7",
123
- },
124
- },
125
- });
126
-
127
- const saved = JSON.parse(readFileSync(configPath, "utf-8")) as {
128
- agents: { defaults: { model: { primary: string } } };
129
- models: {
130
- providers: {
131
- coze: {
132
- apiKey: string;
133
- baseUrl: string;
134
- authHeader: boolean;
135
- headers: Record<string, string>;
136
- models: Array<Record<string, unknown>>;
137
- };
138
- };
139
- };
140
- channels: { feishu: { enabled: boolean } };
141
- };
142
-
143
- cleanup();
144
-
145
- assert.equal(saved.agents.defaults.model.primary, "coze/glm-4.7");
146
- assert.deepEqual(saved.models.providers.coze.headers, {
147
- "X-Provider": "keep-me",
148
- });
149
- assert.equal(saved.channels.feishu.enabled, true);
150
- });
151
-
152
- test("saveModelsRequest rejects invalid defaultModel", () => {
153
- const { configPath, cleanup } = createConfigFile({
154
- agents: { defaults: { model: { primary: "coze/auto" } } },
155
- models: { providers: { coze: { models: [{ id: "auto", name: "Auto" }] } } },
156
- });
157
- try {
158
- assert.throws(() => {
159
- saveModelsRequest({
160
- configPath,
161
- body: {
162
- models: {
163
- defaultModel: "coze/does-not-exist",
164
- },
165
- },
166
- });
167
- }, ValidationError);
168
- } finally {
169
- cleanup();
170
- }
171
- });