@miphamai/cli 0.39.0 → 0.39.1
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/package.json +1 -1
- package/src/shared/constants.ts +472 -4
- package/src/shared/mipham-models.json +50 -0
- package/src/shared/types.ts +439 -4
- package/src/ui/input.tsx +8 -1
package/package.json
CHANGED
package/src/shared/constants.ts
CHANGED
|
@@ -1,4 +1,472 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import type { ProviderConfig, ModelInfo } from './types'
|
|
2
|
+
import miphamModelsSnapshot from './mipham-models.json' with { type: 'json' }
|
|
3
|
+
|
|
4
|
+
// Build MiphamAI provider from synced model definitions.
|
|
5
|
+
// Source of truth: Engine GET /v1/mipham-models → packages/shared/src/mipham-models.json
|
|
6
|
+
// Run `bun run scripts/sync-mipham-models.ts` to update from Engine.
|
|
7
|
+
const MIPHAM_MODELS: ModelInfo[] = miphamModelsSnapshot.models.map((m) => ({
|
|
8
|
+
id: m.id,
|
|
9
|
+
name: m.name,
|
|
10
|
+
providerId: m.providerId,
|
|
11
|
+
contextWindow: m.contextWindow,
|
|
12
|
+
maxOutput: m.maxOutput,
|
|
13
|
+
vision: m.vision,
|
|
14
|
+
status: m.status as 'active' | 'upcoming' | 'deprecated',
|
|
15
|
+
}))
|
|
16
|
+
|
|
17
|
+
const MIPHAM_PROVIDER: ProviderConfig = {
|
|
18
|
+
id: miphamModelsSnapshot.provider.id,
|
|
19
|
+
name: miphamModelsSnapshot.provider.name,
|
|
20
|
+
protocol: miphamModelsSnapshot.provider.protocol as 'openai-compatible',
|
|
21
|
+
baseUrl: miphamModelsSnapshot.provider.baseUrl,
|
|
22
|
+
apiKey: miphamModelsSnapshot.provider.apiKey,
|
|
23
|
+
models: MIPHAM_MODELS,
|
|
24
|
+
status: miphamModelsSnapshot.provider.status as 'active' | 'upcoming',
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Providers: alphabetical by id. Models: Lite/Flash → Text → Vision → Code → Pro/Think
|
|
28
|
+
export const DEFAULT_PROVIDERS: ProviderConfig[] = [
|
|
29
|
+
{
|
|
30
|
+
id: 'anthropic',
|
|
31
|
+
name: 'Anthropic Claude',
|
|
32
|
+
protocol: 'anthropic',
|
|
33
|
+
apiKey: '${ANTHROPIC_API_KEY}',
|
|
34
|
+
models: [
|
|
35
|
+
{
|
|
36
|
+
id: 'claude-haiku-4-5-20251001',
|
|
37
|
+
name: 'Claude Haiku 4.5',
|
|
38
|
+
providerId: 'anthropic',
|
|
39
|
+
contextWindow: 200_000,
|
|
40
|
+
maxOutput: 32_000,
|
|
41
|
+
vision: true,
|
|
42
|
+
status: 'active',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: 'claude-sonnet-4-6',
|
|
46
|
+
name: 'Claude Sonnet 4.6',
|
|
47
|
+
providerId: 'anthropic',
|
|
48
|
+
contextWindow: 1_000_000,
|
|
49
|
+
maxOutput: 128_000,
|
|
50
|
+
vision: true,
|
|
51
|
+
status: 'active',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
id: 'claude-opus-4-8',
|
|
55
|
+
name: 'Claude Opus 4.8',
|
|
56
|
+
providerId: 'anthropic',
|
|
57
|
+
contextWindow: 1_000_000,
|
|
58
|
+
maxOutput: 128_000,
|
|
59
|
+
vision: true,
|
|
60
|
+
status: 'active',
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
id: 'claude-fable-5',
|
|
64
|
+
name: 'Claude Fable 5',
|
|
65
|
+
providerId: 'anthropic',
|
|
66
|
+
contextWindow: 1_000_000,
|
|
67
|
+
maxOutput: 128_000,
|
|
68
|
+
vision: true,
|
|
69
|
+
status: 'active',
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
id: 'claude-sonnet-5',
|
|
73
|
+
name: 'Claude Sonnet 5',
|
|
74
|
+
providerId: 'anthropic',
|
|
75
|
+
contextWindow: 1_000_000,
|
|
76
|
+
maxOutput: 128_000,
|
|
77
|
+
vision: true,
|
|
78
|
+
status: 'active',
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
id: 'claude-opus-5',
|
|
82
|
+
name: 'Claude Opus 5',
|
|
83
|
+
providerId: 'anthropic',
|
|
84
|
+
contextWindow: 1_000_000,
|
|
85
|
+
maxOutput: 128_000,
|
|
86
|
+
vision: true,
|
|
87
|
+
status: 'active',
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
id: 'deepseek',
|
|
93
|
+
name: 'DeepSeek',
|
|
94
|
+
protocol: 'openai-compatible',
|
|
95
|
+
baseUrl: 'https://api.deepseek.com/v1',
|
|
96
|
+
apiKey: '${DEEPSEEK_API_KEY}',
|
|
97
|
+
models: [
|
|
98
|
+
{
|
|
99
|
+
id: 'deepseek-v4-flash',
|
|
100
|
+
name: 'DeepSeek V4 Flash',
|
|
101
|
+
providerId: 'deepseek',
|
|
102
|
+
contextWindow: 1_000_000,
|
|
103
|
+
maxOutput: 384_000,
|
|
104
|
+
vision: false,
|
|
105
|
+
status: 'active',
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
id: 'deepseek-v4-pro',
|
|
109
|
+
name: 'DeepSeek V4 Pro',
|
|
110
|
+
providerId: 'deepseek',
|
|
111
|
+
contextWindow: 1_000_000,
|
|
112
|
+
maxOutput: 384_000,
|
|
113
|
+
vision: false,
|
|
114
|
+
status: 'active',
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
id: 'google',
|
|
120
|
+
name: 'Google Gemini',
|
|
121
|
+
protocol: 'openai-compatible',
|
|
122
|
+
baseUrl: 'https://generativelanguage.googleapis.com/v1beta/openai',
|
|
123
|
+
apiKey: '${GEMINI_API_KEY}',
|
|
124
|
+
models: [
|
|
125
|
+
{
|
|
126
|
+
id: 'gemini-3.0-flash',
|
|
127
|
+
name: 'Gemini 3.0 Flash',
|
|
128
|
+
providerId: 'google',
|
|
129
|
+
contextWindow: 1_000_000,
|
|
130
|
+
maxOutput: 64_000,
|
|
131
|
+
vision: true,
|
|
132
|
+
status: 'active',
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
id: 'gemini-3.0-pro',
|
|
136
|
+
name: 'Gemini 3.0 Pro',
|
|
137
|
+
providerId: 'google',
|
|
138
|
+
contextWindow: 1_000_000,
|
|
139
|
+
maxOutput: 128_000,
|
|
140
|
+
vision: true,
|
|
141
|
+
status: 'active',
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
id: 'gemini-2.5-pro',
|
|
145
|
+
name: 'Gemini 2.5 Pro',
|
|
146
|
+
providerId: 'google',
|
|
147
|
+
contextWindow: 1_000_000,
|
|
148
|
+
maxOutput: 64_000,
|
|
149
|
+
vision: true,
|
|
150
|
+
status: 'active',
|
|
151
|
+
},
|
|
152
|
+
],
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
id: 'doubao',
|
|
156
|
+
name: '豆包 (字节跳动)',
|
|
157
|
+
protocol: 'openai-compatible',
|
|
158
|
+
baseUrl: 'https://ark.cn-beijing.volces.com/api/v3',
|
|
159
|
+
apiKey: '${DOUBAO_API_KEY}',
|
|
160
|
+
models: [
|
|
161
|
+
{
|
|
162
|
+
id: 'doubao-seed-1.6-flash',
|
|
163
|
+
name: '豆包 Seed 1.6 Flash',
|
|
164
|
+
providerId: 'doubao',
|
|
165
|
+
contextWindow: 256_000,
|
|
166
|
+
maxOutput: 128_000,
|
|
167
|
+
vision: true,
|
|
168
|
+
status: 'active',
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
id: 'doubao-seed-2.0-mini',
|
|
172
|
+
name: '豆包 Seed 2.0 Mini',
|
|
173
|
+
providerId: 'doubao',
|
|
174
|
+
contextWindow: 256_000,
|
|
175
|
+
maxOutput: 128_000,
|
|
176
|
+
vision: true,
|
|
177
|
+
status: 'active',
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
id: 'doubao-seed-2.0-lite',
|
|
181
|
+
name: '豆包 Seed 2.0 Lite',
|
|
182
|
+
providerId: 'doubao',
|
|
183
|
+
contextWindow: 256_000,
|
|
184
|
+
maxOutput: 128_000,
|
|
185
|
+
vision: true,
|
|
186
|
+
status: 'active',
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
id: 'doubao-seed-1.6',
|
|
190
|
+
name: '豆包 Seed 1.6',
|
|
191
|
+
providerId: 'doubao',
|
|
192
|
+
contextWindow: 256_000,
|
|
193
|
+
maxOutput: 128_000,
|
|
194
|
+
vision: true,
|
|
195
|
+
status: 'active',
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
id: 'doubao-seed-2.0-pro',
|
|
199
|
+
name: '豆包 Seed 2.0 Pro',
|
|
200
|
+
providerId: 'doubao',
|
|
201
|
+
contextWindow: 256_000,
|
|
202
|
+
maxOutput: 128_000,
|
|
203
|
+
vision: true,
|
|
204
|
+
status: 'active',
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
id: 'doubao-seed-2.0-code',
|
|
208
|
+
name: '豆包 Seed 2.0 Code',
|
|
209
|
+
providerId: 'doubao',
|
|
210
|
+
contextWindow: 256_000,
|
|
211
|
+
maxOutput: 128_000,
|
|
212
|
+
vision: true,
|
|
213
|
+
status: 'active',
|
|
214
|
+
},
|
|
215
|
+
],
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
id: 'hunyuan',
|
|
219
|
+
name: '腾讯混元',
|
|
220
|
+
protocol: 'openai-compatible',
|
|
221
|
+
baseUrl: 'https://api.hunyuan.cloud.tencent.com/v1',
|
|
222
|
+
apiKey: '${HUNYUAN_API_KEY}',
|
|
223
|
+
models: [
|
|
224
|
+
{
|
|
225
|
+
id: 'hunyuan-lite',
|
|
226
|
+
name: '混元 Lite (免费)',
|
|
227
|
+
providerId: 'hunyuan',
|
|
228
|
+
contextWindow: 256_000,
|
|
229
|
+
maxOutput: 6_000,
|
|
230
|
+
vision: false,
|
|
231
|
+
status: 'active',
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
id: 'hunyuan-turbos-latest',
|
|
235
|
+
name: '混元 TurboS',
|
|
236
|
+
providerId: 'hunyuan',
|
|
237
|
+
contextWindow: 32_000,
|
|
238
|
+
maxOutput: 16_000,
|
|
239
|
+
vision: false,
|
|
240
|
+
status: 'active',
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
id: 'hunyuan-2.0-instruct-20251111',
|
|
244
|
+
name: '混元 2.0 Instruct',
|
|
245
|
+
providerId: 'hunyuan',
|
|
246
|
+
contextWindow: 144_000,
|
|
247
|
+
maxOutput: 16_000,
|
|
248
|
+
vision: false,
|
|
249
|
+
status: 'active',
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
id: 'hunyuan-t1-vision-20250916',
|
|
253
|
+
name: '混元 T1 Vision',
|
|
254
|
+
providerId: 'hunyuan',
|
|
255
|
+
contextWindow: 32_000,
|
|
256
|
+
maxOutput: 64_000,
|
|
257
|
+
vision: true,
|
|
258
|
+
status: 'active',
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
id: 'hunyuan-a13b',
|
|
262
|
+
name: '混元 A13B',
|
|
263
|
+
providerId: 'hunyuan',
|
|
264
|
+
contextWindow: 224_000,
|
|
265
|
+
maxOutput: 32_000,
|
|
266
|
+
vision: false,
|
|
267
|
+
status: 'active',
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
id: 'hunyuan-2.0-thinking-20251109',
|
|
271
|
+
name: '混元 2.0 Think',
|
|
272
|
+
providerId: 'hunyuan',
|
|
273
|
+
contextWindow: 192_000,
|
|
274
|
+
maxOutput: 64_000,
|
|
275
|
+
vision: false,
|
|
276
|
+
status: 'active',
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
id: 'hunyuan-t1-latest',
|
|
280
|
+
name: '混元 T1 (推理)',
|
|
281
|
+
providerId: 'hunyuan',
|
|
282
|
+
contextWindow: 32_000,
|
|
283
|
+
maxOutput: 64_000,
|
|
284
|
+
vision: false,
|
|
285
|
+
status: 'active',
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
id: 'hy3-preview',
|
|
289
|
+
name: '混元 Hy3 Preview',
|
|
290
|
+
providerId: 'hunyuan',
|
|
291
|
+
contextWindow: 256_000,
|
|
292
|
+
maxOutput: 128_000,
|
|
293
|
+
vision: false,
|
|
294
|
+
status: 'active',
|
|
295
|
+
},
|
|
296
|
+
],
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
id: 'kimi',
|
|
300
|
+
name: 'Kimi (月之暗面)',
|
|
301
|
+
protocol: 'openai-compatible',
|
|
302
|
+
baseUrl: 'https://api.moonshot.cn/v1',
|
|
303
|
+
apiKey: '${KIMI_API_KEY}',
|
|
304
|
+
models: [
|
|
305
|
+
{
|
|
306
|
+
id: 'kimi-latest',
|
|
307
|
+
name: 'Kimi Latest',
|
|
308
|
+
providerId: 'kimi',
|
|
309
|
+
contextWindow: 128_000,
|
|
310
|
+
maxOutput: 16_000,
|
|
311
|
+
vision: true,
|
|
312
|
+
status: 'active',
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
id: 'kimi-k3',
|
|
316
|
+
name: 'Kimi K3',
|
|
317
|
+
providerId: 'kimi',
|
|
318
|
+
contextWindow: 1_000_000,
|
|
319
|
+
maxOutput: 128_000,
|
|
320
|
+
vision: true,
|
|
321
|
+
status: 'active',
|
|
322
|
+
},
|
|
323
|
+
{
|
|
324
|
+
id: 'moonshot-v1-8k',
|
|
325
|
+
name: 'Moonshot v1 8K',
|
|
326
|
+
providerId: 'kimi',
|
|
327
|
+
contextWindow: 8_000,
|
|
328
|
+
maxOutput: 4_000,
|
|
329
|
+
vision: false,
|
|
330
|
+
status: 'active',
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
id: 'moonshot-v1-32k',
|
|
334
|
+
name: 'Moonshot v1 32K',
|
|
335
|
+
providerId: 'kimi',
|
|
336
|
+
contextWindow: 32_000,
|
|
337
|
+
maxOutput: 4_000,
|
|
338
|
+
vision: false,
|
|
339
|
+
status: 'active',
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
id: 'moonshot-v1-128k',
|
|
343
|
+
name: 'Moonshot v1 128K',
|
|
344
|
+
providerId: 'kimi',
|
|
345
|
+
contextWindow: 128_000,
|
|
346
|
+
maxOutput: 4_000,
|
|
347
|
+
vision: false,
|
|
348
|
+
status: 'active',
|
|
349
|
+
},
|
|
350
|
+
],
|
|
351
|
+
},
|
|
352
|
+
MIPHAM_PROVIDER,
|
|
353
|
+
{
|
|
354
|
+
id: 'ollama',
|
|
355
|
+
name: 'Ollama (本地)',
|
|
356
|
+
protocol: 'openai-compatible',
|
|
357
|
+
baseUrl: 'http://localhost:11434/v1',
|
|
358
|
+
apiKey: 'ollama-local',
|
|
359
|
+
models: [], // 动态发现,初值为空
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
id: 'openai',
|
|
363
|
+
name: 'OpenAI',
|
|
364
|
+
protocol: 'openai-compatible',
|
|
365
|
+
baseUrl: 'https://api.openai.com/v1',
|
|
366
|
+
apiKey: '${OPENAI_API_KEY}',
|
|
367
|
+
models: [
|
|
368
|
+
{
|
|
369
|
+
id: 'gpt-5.4-mini',
|
|
370
|
+
name: 'GPT-5.4 Mini',
|
|
371
|
+
providerId: 'openai',
|
|
372
|
+
contextWindow: 400_000,
|
|
373
|
+
maxOutput: 32_000,
|
|
374
|
+
vision: true,
|
|
375
|
+
status: 'active',
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
id: 'gpt-5.4',
|
|
379
|
+
name: 'GPT-5.4',
|
|
380
|
+
providerId: 'openai',
|
|
381
|
+
contextWindow: 1_050_000,
|
|
382
|
+
maxOutput: 128_000,
|
|
383
|
+
vision: true,
|
|
384
|
+
status: 'active',
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
id: 'gpt-5.5',
|
|
388
|
+
name: 'GPT-5.5',
|
|
389
|
+
providerId: 'openai',
|
|
390
|
+
contextWindow: 1_050_000,
|
|
391
|
+
maxOutput: 128_000,
|
|
392
|
+
vision: true,
|
|
393
|
+
status: 'active',
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
id: 'gpt-5.3-codex',
|
|
397
|
+
name: 'GPT-5.3 Codex',
|
|
398
|
+
providerId: 'openai',
|
|
399
|
+
contextWindow: 400_000,
|
|
400
|
+
maxOutput: 64_000,
|
|
401
|
+
vision: false,
|
|
402
|
+
status: 'active',
|
|
403
|
+
},
|
|
404
|
+
],
|
|
405
|
+
},
|
|
406
|
+
{
|
|
407
|
+
id: 'qwen',
|
|
408
|
+
name: '通义千问',
|
|
409
|
+
protocol: 'openai-compatible',
|
|
410
|
+
baseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
|
|
411
|
+
apiKey: '${QWEN_API_KEY}',
|
|
412
|
+
models: [
|
|
413
|
+
{
|
|
414
|
+
id: 'qwen-plus',
|
|
415
|
+
name: 'Qwen Plus',
|
|
416
|
+
providerId: 'qwen',
|
|
417
|
+
contextWindow: 128_000,
|
|
418
|
+
maxOutput: 32_000,
|
|
419
|
+
vision: true,
|
|
420
|
+
status: 'active',
|
|
421
|
+
},
|
|
422
|
+
{
|
|
423
|
+
id: 'qwen-max',
|
|
424
|
+
name: 'Qwen Max',
|
|
425
|
+
providerId: 'qwen',
|
|
426
|
+
contextWindow: 128_000,
|
|
427
|
+
maxOutput: 32_000,
|
|
428
|
+
vision: true,
|
|
429
|
+
status: 'active',
|
|
430
|
+
},
|
|
431
|
+
],
|
|
432
|
+
},
|
|
433
|
+
]
|
|
434
|
+
|
|
435
|
+
export const PROTOCOL_LABELS: Record<string, string> = {
|
|
436
|
+
'openai-compatible': 'OpenAI Compatible',
|
|
437
|
+
anthropic: 'Anthropic',
|
|
438
|
+
custom: 'Custom Protocol',
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
export const TOOL_CATEGORIES = [
|
|
442
|
+
'file',
|
|
443
|
+
'exec',
|
|
444
|
+
'agent',
|
|
445
|
+
'network',
|
|
446
|
+
'system',
|
|
447
|
+
'artifact',
|
|
448
|
+
'scheduling',
|
|
449
|
+
] as const
|
|
450
|
+
export const CONFIG_FILE_NAME = 'config.yml'
|
|
451
|
+
export const MIPHAM_DIR = '.mipham'
|
|
452
|
+
export const USER_CONFIG_DIR = '.mipham'
|
|
453
|
+
export const MEMORY_DIR = 'memory'
|
|
454
|
+
|
|
455
|
+
/** 预置 Ollama 模型 — 与 ollama list 结果合并去重后展示 */
|
|
456
|
+
export const OLLAMA_PRESET_MODELS = [
|
|
457
|
+
{ id: 'om-v5-flash', source: 'MiphamAI' },
|
|
458
|
+
{ id: 'om-v5-pro', source: 'MiphamAI' },
|
|
459
|
+
{ id: 'om-v5-visual', source: 'MiphamAI' },
|
|
460
|
+
{ id: 'om-v5-apex', source: 'MiphamAI' },
|
|
461
|
+
{ id: 'deepseek-r1:70b', source: '热门' },
|
|
462
|
+
{ id: 'deepseek-v3', source: '热门' },
|
|
463
|
+
{ id: 'qwen2.5:72b', source: '热门' },
|
|
464
|
+
{ id: 'qwen3', source: '热门' },
|
|
465
|
+
] as const
|
|
466
|
+
|
|
467
|
+
// ── Artifact Constants ──
|
|
468
|
+
export const ARTIFACTS_DIR = 'artifacts'
|
|
469
|
+
export const ARTIFACT_PORT = 9876
|
|
470
|
+
export const ARTIFACT_MAX_PORT_TRIES = 10
|
|
471
|
+
export const ARTIFACT_MAX_SIZE = 5 * 1024 * 1024 // 5MB
|
|
472
|
+
export const ARTIFACT_ALLOWED_EXTENSIONS = ['.html', '.svg']
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": "Mipham model definitions — synced from Engine GET /v1/mipham-models. Run 'bun run scripts/sync-mipham-models.ts' to update.",
|
|
3
|
+
"provider": {
|
|
4
|
+
"id": "mipham",
|
|
5
|
+
"name": "MiphamAI",
|
|
6
|
+
"protocol": "openai-compatible",
|
|
7
|
+
"baseUrl": "https://api.onemipham.com/v1",
|
|
8
|
+
"apiKey": "${MIPHAM_API_KEY}",
|
|
9
|
+
"status": "active"
|
|
10
|
+
},
|
|
11
|
+
"models": [
|
|
12
|
+
{
|
|
13
|
+
"id": "om-v5-flash",
|
|
14
|
+
"name": "OM V5 Flash",
|
|
15
|
+
"providerId": "mipham",
|
|
16
|
+
"contextWindow": 16384,
|
|
17
|
+
"maxOutput": 4096,
|
|
18
|
+
"vision": false,
|
|
19
|
+
"status": "active"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"id": "om-v5-pro",
|
|
23
|
+
"name": "OM V5 Pro",
|
|
24
|
+
"providerId": "mipham",
|
|
25
|
+
"contextWindow": 131072,
|
|
26
|
+
"maxOutput": 16384,
|
|
27
|
+
"vision": false,
|
|
28
|
+
"status": "active"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"id": "om-v5-visual",
|
|
32
|
+
"name": "OM V5 Visual",
|
|
33
|
+
"providerId": "mipham",
|
|
34
|
+
"contextWindow": 65536,
|
|
35
|
+
"maxOutput": 4096,
|
|
36
|
+
"vision": true,
|
|
37
|
+
"status": "active"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"id": "om-v5-apex",
|
|
41
|
+
"name": "OM V5 Apex",
|
|
42
|
+
"providerId": "mipham",
|
|
43
|
+
"contextWindow": 200000,
|
|
44
|
+
"maxOutput": 32768,
|
|
45
|
+
"vision": false,
|
|
46
|
+
"status": "active"
|
|
47
|
+
}
|
|
48
|
+
],
|
|
49
|
+
"synced_at": "2026-08-04T00:00:00Z"
|
|
50
|
+
}
|
package/src/shared/types.ts
CHANGED
|
@@ -1,8 +1,443 @@
|
|
|
1
|
-
// ──
|
|
2
|
-
//
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
// ── 类型唯一真源(vendored from packages/shared/src/types.ts)──
|
|
2
|
+
// 为让 npm 包自包含(运行时无需 @mipham/shared workspace 依赖),
|
|
3
|
+
// 此处从 packages/shared/src/types.ts 同步。改动共享类型时两边同步。
|
|
4
|
+
// ── Provider Types ──
|
|
5
|
+
export type ProtocolType = 'openai-compatible' | 'anthropic' | 'custom'
|
|
6
|
+
|
|
7
|
+
export interface ModelInfo {
|
|
8
|
+
id: string
|
|
9
|
+
name: string
|
|
10
|
+
providerId: string
|
|
11
|
+
contextWindow: number
|
|
12
|
+
maxOutput: number
|
|
13
|
+
vision: boolean
|
|
14
|
+
status: 'active' | 'upcoming' | 'deprecated'
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ProviderConfig {
|
|
18
|
+
id: string
|
|
19
|
+
name: string
|
|
20
|
+
protocol: ProtocolType
|
|
21
|
+
baseUrl?: string
|
|
22
|
+
apiKey: string
|
|
23
|
+
models: ModelInfo[]
|
|
24
|
+
status?: 'active' | 'upcoming'
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// ── Message Types ──
|
|
28
|
+
export interface TextContent {
|
|
29
|
+
type: 'text'
|
|
30
|
+
text: string
|
|
31
|
+
}
|
|
32
|
+
export interface ImageContent {
|
|
33
|
+
type: 'image_url'
|
|
34
|
+
image_url: { url: string }
|
|
35
|
+
}
|
|
36
|
+
export interface ToolUseContent {
|
|
37
|
+
type: 'tool_use'
|
|
38
|
+
id: string
|
|
39
|
+
name: string
|
|
40
|
+
input: Record<string, unknown>
|
|
41
|
+
}
|
|
42
|
+
export interface ToolResultContent {
|
|
43
|
+
type: 'tool_result'
|
|
44
|
+
tool_use_id: string
|
|
45
|
+
content: string
|
|
46
|
+
}
|
|
47
|
+
export interface ThinkingContent {
|
|
48
|
+
type: 'thinking'
|
|
49
|
+
thinking: string
|
|
50
|
+
}
|
|
51
|
+
export type ContentBlock =
|
|
52
|
+
TextContent | ImageContent | ToolUseContent | ToolResultContent | ThinkingContent
|
|
53
|
+
|
|
54
|
+
export interface Message {
|
|
55
|
+
role: 'system' | 'user' | 'assistant'
|
|
56
|
+
content: string | ContentBlock[]
|
|
57
|
+
/** DeepSeek reasoning tokens — must be passed back to the API in multi-turn conversations. */
|
|
58
|
+
reasoning_content?: string
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ── Tool Types ──
|
|
62
|
+
export type ToolPermission = 'auto' | 'ask' | 'bypass'
|
|
63
|
+
export type ToolCategory =
|
|
64
|
+
'file' | 'exec' | 'agent' | 'network' | 'system' | 'artifact' | 'scheduling'
|
|
65
|
+
|
|
66
|
+
// ── Artifact Types ──
|
|
67
|
+
export interface ArtifactEntry {
|
|
68
|
+
name: string
|
|
69
|
+
path: string
|
|
70
|
+
url: string
|
|
71
|
+
size: number
|
|
72
|
+
type: 'html' | 'svg'
|
|
73
|
+
createdAt: string
|
|
74
|
+
sessionId: string
|
|
75
|
+
versions?: string[] // version tags e.g. ['v1', 'v2']
|
|
76
|
+
versionCount?: number
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface ArtifactManifest {
|
|
80
|
+
version: 1
|
|
81
|
+
artifacts: ArtifactEntry[]
|
|
82
|
+
port?: number
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface ToolResult {
|
|
86
|
+
success: boolean
|
|
87
|
+
content: string
|
|
88
|
+
error?: string
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ── Task Notification Types ──
|
|
92
|
+
export interface TaskNotification {
|
|
93
|
+
taskId: string
|
|
94
|
+
status: 'started' | 'completed' | 'failed'
|
|
95
|
+
description: string
|
|
96
|
+
content?: string
|
|
97
|
+
error?: string
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ── Stream Types ──
|
|
101
|
+
export interface StreamChunk {
|
|
102
|
+
type:
|
|
103
|
+
| 'text'
|
|
104
|
+
| 'tool_use'
|
|
105
|
+
| 'tool_result'
|
|
106
|
+
| 'thinking'
|
|
107
|
+
| 'stop'
|
|
108
|
+
| 'error'
|
|
109
|
+
| 'warning'
|
|
110
|
+
| 'task_notification'
|
|
111
|
+
| 'usage'
|
|
112
|
+
content?: string
|
|
113
|
+
toolUse?: ToolUseContent
|
|
114
|
+
tool_use_id?: string
|
|
115
|
+
error?: string
|
|
116
|
+
/** DeepSeek reasoning tokens accumulated during this stream. */
|
|
117
|
+
reasoning_content?: string
|
|
118
|
+
/** Anthropic thinking block content (DeepSeek Anthropic endpoint). */
|
|
119
|
+
thinking?: string
|
|
120
|
+
/** Background task notification payload (type: 'task_notification'). */
|
|
121
|
+
taskNotification?: TaskNotification
|
|
122
|
+
/** API-reported input token count (type: 'usage'). */
|
|
123
|
+
inputTokens?: number
|
|
124
|
+
/** API-reported output token count (type: 'usage'). */
|
|
125
|
+
outputTokens?: number
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ── Config Types ──
|
|
129
|
+
export interface MiphamConfig {
|
|
130
|
+
version: string
|
|
131
|
+
defaultProvider: string
|
|
132
|
+
defaultModel: string
|
|
133
|
+
permission: ToolPermission
|
|
134
|
+
/** Org-level permission restrictions (forbiddenModes, maxAllowedMode). */
|
|
135
|
+
permissionRestrictions?: PermissionRestrictions
|
|
136
|
+
providers: ProviderConfig[]
|
|
137
|
+
skills?: { paths: string[]; mcpServers: McpServerConfig[] }
|
|
138
|
+
marketplace?: {
|
|
139
|
+
/** If set, only allow installs from matching repos (e.g. ["One-Mipham/*"]) */
|
|
140
|
+
strictKnownMarketplaces?: string[]
|
|
141
|
+
/** Block installs from matching repos (e.g. ["malicious-org/*"]) */
|
|
142
|
+
blockedMarketplaces?: string[]
|
|
143
|
+
}
|
|
144
|
+
/** Phase 9 feature flags. All default to true. */
|
|
145
|
+
features?: Partial<FeatureFlags>
|
|
146
|
+
/** Phase 10 CRSI feature flags. All default to true. */
|
|
147
|
+
crsi?: Partial<CrsiConfig>
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export interface FeatureFlags {
|
|
151
|
+
mcp: { oauthEnabled: boolean }
|
|
152
|
+
context: { useRealTokenizer: boolean; adaptiveThresholds: boolean }
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export interface CrsiConfig {
|
|
156
|
+
/** Extract and inject experience rules into agent system prompts. */
|
|
157
|
+
ruleInjection: boolean
|
|
158
|
+
/** Intercept tool calls and auto-fix known failure patterns before execution. */
|
|
159
|
+
preToolHook: boolean
|
|
160
|
+
/** Analyze agent outcomes for recurring failure patterns. */
|
|
161
|
+
autoPatternAnalysis: boolean
|
|
162
|
+
/** Auto-degrade/disable low-effectiveness rules based on success-rate tracking. */
|
|
163
|
+
autoRuleManagement: boolean
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export interface McpServerConfig {
|
|
167
|
+
name: string
|
|
168
|
+
/** stdio: executable to spawn (mutually exclusive with `url`). */
|
|
169
|
+
command?: string
|
|
170
|
+
/** stdio: args passed to `command`. */
|
|
171
|
+
args?: string[]
|
|
172
|
+
/** HTTP: Streamable HTTP endpoint (mutually exclusive with `command`). */
|
|
173
|
+
url?: string
|
|
174
|
+
/** HTTP: extra request headers (e.g. Authorization). */
|
|
175
|
+
headers?: Record<string, string>
|
|
176
|
+
env?: Record<string, string>
|
|
177
|
+
auth?: {
|
|
178
|
+
type: 'oauth'
|
|
179
|
+
authorizationUrl: string
|
|
180
|
+
tokenUrl: string
|
|
181
|
+
clientId: string
|
|
182
|
+
scopes?: string[]
|
|
183
|
+
redirectPort?: number
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// ── Hook Types ──
|
|
188
|
+
export type HookEvent =
|
|
189
|
+
| 'PreToolUse'
|
|
190
|
+
| 'PostToolUse'
|
|
191
|
+
| 'PostToolUseFailure'
|
|
192
|
+
| 'SessionStart'
|
|
193
|
+
| 'SessionEnd'
|
|
194
|
+
| 'Notification'
|
|
195
|
+
| 'Stop'
|
|
196
|
+
| 'UserPromptSubmit'
|
|
197
|
+
| 'PreCompact'
|
|
198
|
+
| 'PostCompact'
|
|
199
|
+
| 'ConfigChange'
|
|
200
|
+
| 'SubagentStart'
|
|
201
|
+
| 'SubagentStop'
|
|
202
|
+
| 'PreInference'
|
|
203
|
+
|
|
204
|
+
export type HookType = 'command' | 'http' | 'code' | 'mcp_tool'
|
|
205
|
+
|
|
206
|
+
export interface HookConfig {
|
|
207
|
+
type: HookType
|
|
208
|
+
command?: string
|
|
209
|
+
args?: string[]
|
|
210
|
+
url?: string
|
|
211
|
+
method?: 'GET' | 'POST'
|
|
212
|
+
headers?: Record<string, string>
|
|
213
|
+
mcpServer?: string
|
|
214
|
+
mcpTool?: string
|
|
215
|
+
continueOnBlock?: boolean
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export interface HookDefinition {
|
|
219
|
+
event: HookEvent
|
|
220
|
+
toolName?: string
|
|
221
|
+
handler: (context: HookContext) => Promise<HookResult>
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export interface HookContext {
|
|
225
|
+
event: HookEvent
|
|
226
|
+
toolName?: string
|
|
227
|
+
toolInput?: Record<string, unknown>
|
|
228
|
+
toolResult?: ToolResult
|
|
229
|
+
sessionId: string
|
|
230
|
+
userPrompt?: string
|
|
231
|
+
configKey?: string
|
|
232
|
+
configValue?: unknown
|
|
233
|
+
/** PreInference: full conversation messages for DLP inspection. */
|
|
234
|
+
messages?: Array<{ role: string; content: string }>
|
|
235
|
+
/** PreInference: recent tool calls and their results. */
|
|
236
|
+
toolCalls?: Array<{
|
|
237
|
+
name: string
|
|
238
|
+
input: Record<string, unknown>
|
|
239
|
+
resultPreview: string
|
|
240
|
+
}>
|
|
241
|
+
/** PreInference: current provider ID. */
|
|
242
|
+
provider?: string
|
|
243
|
+
/** PreInference: current model ID. */
|
|
244
|
+
model?: string
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export interface HookResult {
|
|
248
|
+
allowed: boolean
|
|
249
|
+
reason?: string
|
|
250
|
+
modifiedInput?: Record<string, unknown>
|
|
251
|
+
decision?: 'allow' | 'block'
|
|
252
|
+
permissionDecision?: 'allow' | 'deny' | 'ask' | 'defer'
|
|
253
|
+
additionalContext?: string
|
|
254
|
+
updatedOutput?: string
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// ── Instruction Types ──
|
|
258
|
+
export interface InstructionFile {
|
|
259
|
+
path: string
|
|
260
|
+
level: 'group' | 'company' | 'project' | 'directory' | 'user'
|
|
261
|
+
privacy: 'public' | 'project' | 'private'
|
|
262
|
+
language: string
|
|
263
|
+
content: string
|
|
264
|
+
frontmatter: Record<string, unknown>
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// ── Permission Types ──
|
|
268
|
+
/** Six explicit permission modes matching Claude Code's permission architecture */
|
|
269
|
+
export type PermissionMode =
|
|
270
|
+
'default' | 'acceptEdits' | 'plan' | 'auto' | 'dontAsk' | 'bypassPermissions'
|
|
5
271
|
|
|
272
|
+
/** Backward-compatible alias: PermissionMode plus legacy 'ask' and 'bypass' */
|
|
273
|
+
export type PermissionLevel = PermissionMode | 'ask' | 'bypass'
|
|
274
|
+
|
|
275
|
+
/** Org-level restrictions that cap or forbid specific permission modes. */
|
|
276
|
+
export interface PermissionRestrictions {
|
|
277
|
+
/** Modes that may not be entered (cycle skips them). */
|
|
278
|
+
forbiddenModes?: PermissionMode[]
|
|
279
|
+
/** Ceiling — modes ranked higher (more permissive) than this are treated as forbidden. */
|
|
280
|
+
maxAllowedMode?: PermissionMode
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export interface PermissionConfig {
|
|
284
|
+
mode: PermissionMode
|
|
285
|
+
allow: string[]
|
|
286
|
+
deny: string[]
|
|
287
|
+
/** Optional org-level restrictions enforced on every mode transition. */
|
|
288
|
+
restrictions?: PermissionRestrictions
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export interface PermissionRuleEntry {
|
|
292
|
+
pattern: string // e.g., "Bash(git:*)"
|
|
293
|
+
level: 'allow' | 'deny' | 'ask'
|
|
294
|
+
compiled: RegExp
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
export interface PermissionRule {
|
|
298
|
+
toolName: string
|
|
299
|
+
level: PermissionLevel
|
|
300
|
+
pattern?: string
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// ── Inference Hook (DLP) Types ──
|
|
304
|
+
|
|
305
|
+
/** Configuration for the PreInference DLP hook, loaded from config.yml. */
|
|
306
|
+
export interface InferenceHookConfig {
|
|
307
|
+
/** DLP server endpoint (HTTPS). Empty = feature disabled. */
|
|
308
|
+
endpoint: string
|
|
309
|
+
/** HMAC signing secret (format: mis_<random>). */
|
|
310
|
+
signing_secret: string
|
|
311
|
+
/** Request timeout in milliseconds. Default 5000. */
|
|
312
|
+
timeout: number
|
|
313
|
+
/** Failure posture: 'fail-closed' blocks on error, 'fail-open' allows. */
|
|
314
|
+
on_failure: 'fail-closed' | 'fail-open'
|
|
315
|
+
/** Organization identifier (optional, sent in payload). */
|
|
316
|
+
organization_id: string
|
|
317
|
+
/** Additional custom headers to send with each request. */
|
|
318
|
+
headers: Record<string, string>
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/** Outgoing request to the DLP server. */
|
|
322
|
+
export interface InferenceCheckRequest {
|
|
323
|
+
type: 'inference_check'
|
|
324
|
+
id: string
|
|
325
|
+
created_at: string
|
|
326
|
+
data: {
|
|
327
|
+
type: 'pre_inference'
|
|
328
|
+
session_id: string
|
|
329
|
+
organization_id?: string
|
|
330
|
+
provider: string
|
|
331
|
+
model: string
|
|
332
|
+
messages: Array<{ role: string; content: string }>
|
|
333
|
+
tool_calls: Array<{
|
|
334
|
+
name: string
|
|
335
|
+
input: Record<string, unknown>
|
|
336
|
+
result_preview: string
|
|
337
|
+
}>
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/** Response from the DLP server. */
|
|
342
|
+
export interface InferenceCheckResponse {
|
|
343
|
+
verdict: 'allow' | 'deny'
|
|
344
|
+
reason?: string
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// ── Credential Masking Types ──
|
|
348
|
+
|
|
349
|
+
/** Full-file masking rule: entire file content replaced with sentinel. */
|
|
350
|
+
export interface CredentialFullMaskRule {
|
|
351
|
+
path: string
|
|
352
|
+
mode: 'full'
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/** Per-extract-pattern configuration with optional field-based extraction. */
|
|
356
|
+
export interface CredentialExtractPattern {
|
|
357
|
+
/** Regex pattern to match. When `field` is set, applied to field value only. */
|
|
358
|
+
pattern: string
|
|
359
|
+
/** Optional replacement string. Defaults to CREDENTIAL_SENTINEL. */
|
|
360
|
+
replacement?: string
|
|
361
|
+
/** 🆕 JSON key to extract before applying pattern. If unset, pattern runs against full content. */
|
|
362
|
+
field?: string
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/** Extract-based masking rule: only regex-matched tokens are replaced. */
|
|
366
|
+
export interface CredentialExtractRule {
|
|
367
|
+
path: string
|
|
368
|
+
mode: 'extract'
|
|
369
|
+
extract: CredentialExtractPattern[]
|
|
370
|
+
/** 🆕 Behavior when no extract pattern matches: 'mask' (replace all) or 'passthrough' (keep). Default: 'mask'. */
|
|
371
|
+
onExtractNoMatch?: 'mask' | 'passthrough'
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/** 🆕 JWT-aware masking rule: decode payload and mask specified claims. */
|
|
375
|
+
export interface JwtMaskingRule {
|
|
376
|
+
path: string
|
|
377
|
+
type: 'jwt'
|
|
378
|
+
decode: 'jwt'
|
|
379
|
+
/** Claim names to mask in the JWT payload (e.g. ["sub", "email"]). */
|
|
380
|
+
maskClaims: string[]
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/** 🆕 AWS credential pair masking rule: detect key pairs and optionally re-sign. */
|
|
384
|
+
export interface AwsMaskingRule {
|
|
385
|
+
path: string
|
|
386
|
+
type: 'aws'
|
|
387
|
+
awsPairs: boolean
|
|
388
|
+
sigv4: boolean
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
export type CredentialFileRule =
|
|
392
|
+
CredentialFullMaskRule | CredentialExtractRule | JwtMaskingRule | AwsMaskingRule
|
|
393
|
+
|
|
394
|
+
/** Configuration for credential masking, loaded from config.yml. */
|
|
395
|
+
export interface CredentialMaskingConfig {
|
|
396
|
+
enabled: boolean
|
|
397
|
+
files: CredentialFileRule[]
|
|
398
|
+
output_scrubbing: {
|
|
399
|
+
enabled: boolean
|
|
400
|
+
patterns: string[]
|
|
401
|
+
}
|
|
402
|
+
env_filter: {
|
|
403
|
+
enabled: boolean
|
|
404
|
+
patterns: string[]
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// ── Cross-Session Messaging Types ──
|
|
409
|
+
|
|
410
|
+
/** Controls how inbound cross-session messages are handled. */
|
|
411
|
+
export type CrossSessionInbound = 'allow' | 'ask' | 'deny'
|
|
412
|
+
|
|
413
|
+
/** 🆕 Configuration for cross-session messaging. */
|
|
414
|
+
export interface CrossSessionConfig {
|
|
415
|
+
crossSessionInbound: CrossSessionInbound
|
|
416
|
+
dialogExpiry: number // seconds
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/** Session information exposed via ListAgents. */
|
|
420
|
+
export interface SessionInfo {
|
|
421
|
+
id: string
|
|
422
|
+
name: string
|
|
423
|
+
machine: string
|
|
424
|
+
pid: number
|
|
425
|
+
startedAt: string
|
|
426
|
+
cwd?: string
|
|
427
|
+
provider?: string
|
|
428
|
+
model?: string
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// ── Background Agent Types ──
|
|
432
|
+
|
|
433
|
+
export interface BackgroundAgentConfig {
|
|
434
|
+
auto_commit: boolean
|
|
435
|
+
auto_push: boolean
|
|
436
|
+
auto_worktree: boolean
|
|
437
|
+
commit_coauthors: boolean
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// ── CLI 内部类型(引用活服务,不可共享)──
|
|
6
441
|
export interface ToolContext {
|
|
7
442
|
cwd: string
|
|
8
443
|
sessionId: string
|
package/src/ui/input.tsx
CHANGED
|
@@ -231,7 +231,14 @@ export function InputBar({
|
|
|
231
231
|
if (submittedHistory.length === 0) return
|
|
232
232
|
// Save current draft the first time we enter history browsing
|
|
233
233
|
if (historyIndexRef.current === -1) {
|
|
234
|
-
|
|
234
|
+
// Flush any pending throttle so its deferred setValue doesn't overwrite
|
|
235
|
+
// history navigation (paste → immediate ↑ race). Save the latest value
|
|
236
|
+
// from the ref — state `value` is stale inside the throttle window.
|
|
237
|
+
if (onChangeTimerRef.current) {
|
|
238
|
+
clearTimeout(onChangeTimerRef.current)
|
|
239
|
+
onChangeTimerRef.current = null
|
|
240
|
+
}
|
|
241
|
+
savedDraftRef.current = valueRef.current
|
|
235
242
|
}
|
|
236
243
|
const newIndex = Math.min(historyIndexRef.current + 1, submittedHistory.length - 1)
|
|
237
244
|
historyIndexRef.current = newIndex
|