miii-agent 0.1.13 → 0.1.14
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/dist/cli.js +627 -168
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -9,10 +9,98 @@ var __export = (target, all) => {
|
|
|
9
9
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
-
// src/
|
|
12
|
+
// src/config.ts
|
|
13
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
|
|
14
|
+
import { join } from "path";
|
|
15
|
+
import { homedir } from "os";
|
|
16
|
+
function defaultProviders() {
|
|
17
|
+
return {
|
|
18
|
+
ollama: {
|
|
19
|
+
type: "ollama",
|
|
20
|
+
baseUrl: process.env.OLLAMA_HOST ?? "http://localhost:11434"
|
|
21
|
+
},
|
|
22
|
+
lmstudio: {
|
|
23
|
+
type: "openai",
|
|
24
|
+
baseUrl: process.env.LMSTUDIO_HOST ?? process.env.LLM_HOST ?? "http://localhost:1234",
|
|
25
|
+
...process.env.LMSTUDIO_API_KEY ? { apiKey: process.env.LMSTUDIO_API_KEY } : {}
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function migrate(raw) {
|
|
30
|
+
const providers = { ...defaultProviders(), ...raw.providers ?? {} };
|
|
31
|
+
if (raw.ollamaHost) providers.ollama = { ...providers.ollama, baseUrl: raw.ollamaHost };
|
|
32
|
+
if (raw.lmstudioHost) providers.lmstudio = { ...providers.lmstudio, baseUrl: raw.lmstudioHost };
|
|
33
|
+
return {
|
|
34
|
+
model: raw.model,
|
|
35
|
+
provider: raw.provider,
|
|
36
|
+
effort: raw.effort,
|
|
37
|
+
providers
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function readRawConfig() {
|
|
41
|
+
if (!existsSync(CONFIG_PATH)) return {};
|
|
42
|
+
try {
|
|
43
|
+
return JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
|
|
44
|
+
} catch {
|
|
45
|
+
return {};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
function loadConfig() {
|
|
49
|
+
return migrate(readRawConfig());
|
|
50
|
+
}
|
|
51
|
+
function saveConfig(config) {
|
|
52
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
53
|
+
writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), "utf-8");
|
|
54
|
+
}
|
|
55
|
+
function providersOf(cfg) {
|
|
56
|
+
return cfg.providers && Object.keys(cfg.providers).length ? cfg.providers : defaultProviders();
|
|
57
|
+
}
|
|
58
|
+
function resolveProvider(cfg = loadConfig()) {
|
|
59
|
+
const providers = providersOf(cfg);
|
|
60
|
+
const name = cfg.provider && providers[cfg.provider] ? cfg.provider : providers.ollama ? "ollama" : Object.keys(providers)[0];
|
|
61
|
+
return { name, entry: providers[name] };
|
|
62
|
+
}
|
|
63
|
+
function listProviders(cfg = loadConfig()) {
|
|
64
|
+
return Object.keys(providersOf(cfg));
|
|
65
|
+
}
|
|
66
|
+
function providerEntries(cfg = loadConfig()) {
|
|
67
|
+
const providers = providersOf(cfg);
|
|
68
|
+
return Object.entries(providers).map(([name, entry]) => {
|
|
69
|
+
const local = entry.type === "ollama" || /localhost|127\.0\.0\.1|0\.0\.0\.0/.test(entry.baseUrl);
|
|
70
|
+
return { name, entry, kind: local ? "local" : "api" };
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
function setModel(model) {
|
|
74
|
+
saveConfig({ ...readRawConfig(), model });
|
|
75
|
+
}
|
|
76
|
+
function setEffort(effort) {
|
|
77
|
+
saveConfig({ ...readRawConfig(), effort });
|
|
78
|
+
}
|
|
79
|
+
function setProvider(provider) {
|
|
80
|
+
saveConfig({ ...readRawConfig(), provider });
|
|
81
|
+
}
|
|
82
|
+
var CONFIG_DIR, CONFIG_PATH;
|
|
83
|
+
var init_config = __esm({
|
|
84
|
+
"src/config.ts"() {
|
|
85
|
+
"use strict";
|
|
86
|
+
CONFIG_DIR = join(homedir(), ".miii");
|
|
87
|
+
CONFIG_PATH = join(CONFIG_DIR, "config.json");
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// src/llm/ollama.ts
|
|
13
92
|
import { Ollama } from "ollama";
|
|
14
93
|
import { execFileSync } from "child_process";
|
|
15
|
-
function
|
|
94
|
+
function makeClient(entry, signal) {
|
|
95
|
+
const opts = { host: entry.baseUrl };
|
|
96
|
+
if (entry.apiKey) opts.headers = { Authorization: `Bearer ${entry.apiKey}` };
|
|
97
|
+
if (signal) {
|
|
98
|
+
opts.fetch = ((input, init) => fetch(input, { ...init, signal }));
|
|
99
|
+
}
|
|
100
|
+
return new Ollama(opts);
|
|
101
|
+
}
|
|
102
|
+
function isAvailable(entry) {
|
|
103
|
+
if (!LOCAL_HOST_RE.test(entry.baseUrl)) return true;
|
|
16
104
|
try {
|
|
17
105
|
const cmd2 = process.platform === "win32" ? "where" : "which";
|
|
18
106
|
execFileSync(cmd2, ["ollama"], { stdio: "ignore" });
|
|
@@ -31,20 +119,20 @@ function isConnectionError(err) {
|
|
|
31
119
|
const msg = err instanceof Error ? err.message : String(err);
|
|
32
120
|
return msg.includes("ECONNREFUSED") || msg.includes("fetch failed") || msg.includes("connect");
|
|
33
121
|
}
|
|
34
|
-
async function listModels() {
|
|
122
|
+
async function listModels(entry) {
|
|
35
123
|
try {
|
|
36
|
-
const { models } = await
|
|
124
|
+
const { models } = await makeClient(entry).list();
|
|
37
125
|
return models.map((m) => m.name);
|
|
38
126
|
} catch (err) {
|
|
39
127
|
if (isConnectionError(err)) {
|
|
40
|
-
throw new Error(
|
|
128
|
+
throw new Error(NOT_RUNNING);
|
|
41
129
|
}
|
|
42
130
|
throw err;
|
|
43
131
|
}
|
|
44
132
|
}
|
|
45
|
-
async function modelContext(model) {
|
|
133
|
+
async function modelContext(entry, model) {
|
|
46
134
|
try {
|
|
47
|
-
const info = await
|
|
135
|
+
const info = await makeClient(entry).show({ model });
|
|
48
136
|
const modelInfo = info.model_info;
|
|
49
137
|
if (modelInfo) {
|
|
50
138
|
const ctxKey = Object.keys(modelInfo).find((k) => k.includes("context_length"));
|
|
@@ -56,7 +144,7 @@ async function modelContext(model) {
|
|
|
56
144
|
return 2048;
|
|
57
145
|
} catch (err) {
|
|
58
146
|
if (isConnectionError(err)) {
|
|
59
|
-
throw new Error(
|
|
147
|
+
throw new Error(NOT_RUNNING);
|
|
60
148
|
}
|
|
61
149
|
const msg = err instanceof Error ? err.message : String(err);
|
|
62
150
|
if (msg.toLowerCase().includes("not found") || msg.toLowerCase().includes("unknown model")) {
|
|
@@ -65,13 +153,10 @@ async function modelContext(model) {
|
|
|
65
153
|
throw err;
|
|
66
154
|
}
|
|
67
155
|
}
|
|
68
|
-
async function* chat(model, messages, tools, opts) {
|
|
156
|
+
async function* chat(entry, model, messages, tools, opts) {
|
|
69
157
|
if (opts?.signal?.aborted) return;
|
|
70
158
|
const signal = opts?.signal;
|
|
71
|
-
const client = signal
|
|
72
|
-
host: process.env.OLLAMA_HOST ?? "http://localhost:11434",
|
|
73
|
-
fetch: ((input, init) => fetch(input, { ...init, signal }))
|
|
74
|
-
}) : ollama;
|
|
159
|
+
const client = makeClient(entry, signal);
|
|
75
160
|
let stream;
|
|
76
161
|
const onAbort = () => {
|
|
77
162
|
try {
|
|
@@ -99,7 +184,7 @@ async function* chat(model, messages, tools, opts) {
|
|
|
99
184
|
} catch (err) {
|
|
100
185
|
if (signal?.aborted) return;
|
|
101
186
|
if (isConnectionError(err)) {
|
|
102
|
-
throw new Error(
|
|
187
|
+
throw new Error(NOT_RUNNING);
|
|
103
188
|
}
|
|
104
189
|
const msg = err instanceof Error ? err.message : String(err);
|
|
105
190
|
if (msg.toLowerCase().includes("not found") || msg.toLowerCase().includes("unknown model")) {
|
|
@@ -123,27 +208,278 @@ async function* chat(model, messages, tools, opts) {
|
|
|
123
208
|
} catch (err) {
|
|
124
209
|
if (opts?.signal?.aborted) return;
|
|
125
210
|
if (isConnectionError(err)) {
|
|
126
|
-
throw new Error(
|
|
211
|
+
throw new Error(NOT_RUNNING);
|
|
127
212
|
}
|
|
128
213
|
throw err;
|
|
129
214
|
} finally {
|
|
130
215
|
if (opts?.signal) opts.signal.removeEventListener("abort", onAbort);
|
|
131
216
|
}
|
|
132
217
|
}
|
|
133
|
-
var
|
|
134
|
-
var
|
|
135
|
-
"src/ollama
|
|
218
|
+
var NOT_INSTALLED, NOT_RUNNING, LOCAL_HOST_RE, HARMONY_RE, CHANNEL_LABEL_RE;
|
|
219
|
+
var init_ollama = __esm({
|
|
220
|
+
"src/llm/ollama.ts"() {
|
|
136
221
|
"use strict";
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
OLLAMA_NOT_INSTALLED = "Ollama is not installed. Install it with: npm i -g ollama\nOr download from https://ollama.com/download";
|
|
141
|
-
OLLAMA_NOT_RUNNING = "Ollama is not running. Start it with: ollama serve";
|
|
222
|
+
NOT_INSTALLED = "Ollama is not installed. Install it with: npm i -g ollama\nOr download from https://ollama.com/download";
|
|
223
|
+
NOT_RUNNING = "Ollama is not running. Start it with: ollama serve";
|
|
224
|
+
LOCAL_HOST_RE = /localhost|127\.0\.0\.1|0\.0\.0\.0|\[::1\]/;
|
|
142
225
|
HARMONY_RE = /<\|?\/?(?:channel|message|start|end|return|constrain|assistant|user|system|developer|tool|tool_call|tool_response|final|analysis|commentary)\|?>/gi;
|
|
143
226
|
CHANNEL_LABEL_RE = /^(?:analysis|commentary|final)\s*(?=\w)/i;
|
|
144
227
|
}
|
|
145
228
|
});
|
|
146
229
|
|
|
230
|
+
// src/llm/openai.ts
|
|
231
|
+
function notAvailable(entry) {
|
|
232
|
+
return `Cannot reach OpenAI-compatible provider at ${entry.baseUrl}. Make sure the server is running and the baseUrl is correct.`;
|
|
233
|
+
}
|
|
234
|
+
function headers(entry) {
|
|
235
|
+
const h = { "Content-Type": "application/json" };
|
|
236
|
+
if (entry.apiKey) h["Authorization"] = `Bearer ${entry.apiKey}`;
|
|
237
|
+
return h;
|
|
238
|
+
}
|
|
239
|
+
function isConnectionError2(err) {
|
|
240
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
241
|
+
return msg.includes("ECONNREFUSED") || msg.includes("fetch failed") || msg.includes("connect");
|
|
242
|
+
}
|
|
243
|
+
function isAvailable2(_entry) {
|
|
244
|
+
return true;
|
|
245
|
+
}
|
|
246
|
+
async function listModels2(entry) {
|
|
247
|
+
try {
|
|
248
|
+
const res = await fetch(`${entry.baseUrl}/v1/models`, {
|
|
249
|
+
headers: headers(entry),
|
|
250
|
+
signal: AbortSignal.timeout(5e3)
|
|
251
|
+
});
|
|
252
|
+
if (!res.ok) {
|
|
253
|
+
let detail = "";
|
|
254
|
+
try {
|
|
255
|
+
detail = await res.text();
|
|
256
|
+
} catch {
|
|
257
|
+
}
|
|
258
|
+
throw new Error(`Provider error (HTTP ${res.status}): ${detail || res.statusText}`);
|
|
259
|
+
}
|
|
260
|
+
const body = await res.json();
|
|
261
|
+
return body.data.map((m) => m.id);
|
|
262
|
+
} catch (err) {
|
|
263
|
+
if (isConnectionError2(err)) {
|
|
264
|
+
throw new Error(notAvailable(entry));
|
|
265
|
+
}
|
|
266
|
+
throw err;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
async function modelContext2(_entry, _model) {
|
|
270
|
+
return DEFAULT_CONTEXT;
|
|
271
|
+
}
|
|
272
|
+
function toOpenAIMessages(msgs) {
|
|
273
|
+
return msgs.map((m) => {
|
|
274
|
+
if (m.role === "assistant" && m.tool_calls && m.tool_calls.length > 0) {
|
|
275
|
+
return {
|
|
276
|
+
role: "assistant",
|
|
277
|
+
content: m.content || null,
|
|
278
|
+
tool_calls: m.tool_calls.map((tc) => ({
|
|
279
|
+
id: tc.id ?? `call_${Math.random().toString(36).slice(2, 10)}`,
|
|
280
|
+
type: "function",
|
|
281
|
+
function: {
|
|
282
|
+
name: tc.function.name,
|
|
283
|
+
arguments: JSON.stringify(tc.function.arguments)
|
|
284
|
+
}
|
|
285
|
+
}))
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
if (m.role === "tool") {
|
|
289
|
+
return {
|
|
290
|
+
role: "tool",
|
|
291
|
+
content: m.content,
|
|
292
|
+
tool_call_id: m.tool_call_id ?? ""
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
return { role: m.role, content: m.content };
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
function toOpenAITools(tools) {
|
|
299
|
+
if (!tools || tools.length === 0) return void 0;
|
|
300
|
+
return tools.map((t) => ({
|
|
301
|
+
type: "function",
|
|
302
|
+
function: {
|
|
303
|
+
name: t.function.name,
|
|
304
|
+
description: t.function.description,
|
|
305
|
+
parameters: t.function.parameters
|
|
306
|
+
}
|
|
307
|
+
}));
|
|
308
|
+
}
|
|
309
|
+
function parseSSELine(line) {
|
|
310
|
+
if (!line.startsWith("data: ")) return null;
|
|
311
|
+
const data = line.slice(6).trim();
|
|
312
|
+
if (data === "[DONE]") return null;
|
|
313
|
+
try {
|
|
314
|
+
return JSON.parse(data);
|
|
315
|
+
} catch {
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
async function* chat2(entry, model, messages, tools, opts) {
|
|
320
|
+
if (opts?.signal?.aborted) return;
|
|
321
|
+
const oaMessages = toOpenAIMessages(messages);
|
|
322
|
+
const oaTools = toOpenAITools(tools);
|
|
323
|
+
const body = {
|
|
324
|
+
model,
|
|
325
|
+
messages: oaMessages,
|
|
326
|
+
stream: true,
|
|
327
|
+
temperature: opts?.temperature ?? 0.2
|
|
328
|
+
};
|
|
329
|
+
if (oaTools) body.tools = oaTools;
|
|
330
|
+
if (opts?.num_predict && opts.num_predict > 0) body.max_tokens = opts.num_predict;
|
|
331
|
+
const toolCallAccum = /* @__PURE__ */ new Map();
|
|
332
|
+
const TIMEOUT_MS = 18e4;
|
|
333
|
+
const timeoutSignal = AbortSignal.timeout(TIMEOUT_MS);
|
|
334
|
+
const combinedSignal = opts?.signal && typeof AbortSignal.any === "function" ? AbortSignal.any([opts.signal, timeoutSignal]) : opts?.signal ?? timeoutSignal;
|
|
335
|
+
try {
|
|
336
|
+
const res = await fetch(`${entry.baseUrl}/v1/chat/completions`, {
|
|
337
|
+
method: "POST",
|
|
338
|
+
headers: headers(entry),
|
|
339
|
+
body: JSON.stringify(body),
|
|
340
|
+
signal: combinedSignal
|
|
341
|
+
});
|
|
342
|
+
if (!res.ok) {
|
|
343
|
+
let detail = "";
|
|
344
|
+
try {
|
|
345
|
+
detail = await res.text();
|
|
346
|
+
} catch {
|
|
347
|
+
}
|
|
348
|
+
if (res.status === 404) {
|
|
349
|
+
throw new Error(`Model "${model}" not found at ${entry.baseUrl}. Make sure it's available.`);
|
|
350
|
+
}
|
|
351
|
+
throw new Error(`Provider error (HTTP ${res.status}): ${detail || res.statusText}`);
|
|
352
|
+
}
|
|
353
|
+
const reader = res.body?.getReader();
|
|
354
|
+
if (!reader) throw new Error("No response body");
|
|
355
|
+
const decoder = new TextDecoder();
|
|
356
|
+
let buffer = "";
|
|
357
|
+
try {
|
|
358
|
+
readLoop: while (true) {
|
|
359
|
+
const { done: readerDone, value } = await reader.read();
|
|
360
|
+
if (readerDone || opts?.signal?.aborted) break;
|
|
361
|
+
buffer += decoder.decode(value, { stream: true });
|
|
362
|
+
const lines = buffer.split("\n");
|
|
363
|
+
buffer = lines.pop() ?? "";
|
|
364
|
+
for (const line of lines) {
|
|
365
|
+
const parsed = parseSSELine(line);
|
|
366
|
+
if (!parsed) continue;
|
|
367
|
+
const choices = parsed.choices;
|
|
368
|
+
if (!choices || choices.length === 0) continue;
|
|
369
|
+
const delta = choices[0].delta ?? {};
|
|
370
|
+
const finishReason = choices[0].finish_reason;
|
|
371
|
+
if (delta.content) {
|
|
372
|
+
yield { content: delta.content, done: false };
|
|
373
|
+
}
|
|
374
|
+
const deltaToolCalls = delta.tool_calls;
|
|
375
|
+
if (deltaToolCalls) {
|
|
376
|
+
for (const tc of deltaToolCalls) {
|
|
377
|
+
const idx = tc.index;
|
|
378
|
+
if (!toolCallAccum.has(idx)) {
|
|
379
|
+
toolCallAccum.set(idx, { args: "" });
|
|
380
|
+
}
|
|
381
|
+
const acc = toolCallAccum.get(idx);
|
|
382
|
+
if (tc.id) acc.id = tc.id;
|
|
383
|
+
if (tc.type) acc.type = tc.type;
|
|
384
|
+
if (tc.function) {
|
|
385
|
+
if (tc.function.name) acc.name = tc.function.name;
|
|
386
|
+
if (tc.function.arguments) acc.args += tc.function.arguments;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
if (finishReason) {
|
|
391
|
+
break readLoop;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
} finally {
|
|
396
|
+
reader.cancel().catch(() => {
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
} catch (err) {
|
|
400
|
+
if (opts?.signal?.aborted) {
|
|
401
|
+
yield { content: "", done: true, prompt_eval_count: 0, eval_count: 0 };
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
if (isConnectionError2(err)) {
|
|
405
|
+
throw new Error(notAvailable(entry));
|
|
406
|
+
}
|
|
407
|
+
if (timeoutSignal.aborted && !opts?.signal?.aborted) {
|
|
408
|
+
throw new Error(`Provider request timed out after ${TIMEOUT_MS / 1e3}s. The model may still be loading or thinking.`);
|
|
409
|
+
}
|
|
410
|
+
throw err;
|
|
411
|
+
}
|
|
412
|
+
if (opts?.signal?.aborted) {
|
|
413
|
+
yield { content: "", done: true, prompt_eval_count: 0, eval_count: 0 };
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
const toolCalls = [];
|
|
417
|
+
for (const [, acc] of toolCallAccum) {
|
|
418
|
+
let parsedArgs = {};
|
|
419
|
+
try {
|
|
420
|
+
parsedArgs = JSON.parse(acc.args);
|
|
421
|
+
} catch {
|
|
422
|
+
parsedArgs = { _raw: acc.args };
|
|
423
|
+
}
|
|
424
|
+
toolCalls.push({
|
|
425
|
+
id: acc.id,
|
|
426
|
+
function: {
|
|
427
|
+
name: acc.name ?? "",
|
|
428
|
+
arguments: parsedArgs
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
yield {
|
|
433
|
+
content: "",
|
|
434
|
+
done: true,
|
|
435
|
+
tool_calls: toolCalls.length > 0 ? toolCalls : void 0
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
var DEFAULT_CONTEXT;
|
|
439
|
+
var init_openai = __esm({
|
|
440
|
+
"src/llm/openai.ts"() {
|
|
441
|
+
"use strict";
|
|
442
|
+
DEFAULT_CONTEXT = 4096;
|
|
443
|
+
}
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
// src/llm/client.ts
|
|
447
|
+
function active() {
|
|
448
|
+
return resolveProvider();
|
|
449
|
+
}
|
|
450
|
+
function isAvailable3() {
|
|
451
|
+
const { entry } = active();
|
|
452
|
+
return entry.type === "ollama" ? isAvailable(entry) : isAvailable2(entry);
|
|
453
|
+
}
|
|
454
|
+
function NOT_AVAILABLE() {
|
|
455
|
+
const { entry } = active();
|
|
456
|
+
return entry.type === "ollama" ? NOT_INSTALLED : notAvailable(entry);
|
|
457
|
+
}
|
|
458
|
+
async function listModels3() {
|
|
459
|
+
const { entry } = active();
|
|
460
|
+
return entry.type === "ollama" ? listModels(entry) : listModels2(entry);
|
|
461
|
+
}
|
|
462
|
+
async function modelContext3(model) {
|
|
463
|
+
const { entry } = active();
|
|
464
|
+
return entry.type === "ollama" ? modelContext(entry, model) : modelContext2(entry, model);
|
|
465
|
+
}
|
|
466
|
+
async function* chat3(model, messages, tools, opts) {
|
|
467
|
+
const { entry } = active();
|
|
468
|
+
if (entry.type === "ollama") {
|
|
469
|
+
yield* chat(entry, model, messages, tools, opts);
|
|
470
|
+
} else {
|
|
471
|
+
yield* chat2(entry, model, messages, tools, opts);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
var init_client = __esm({
|
|
475
|
+
"src/llm/client.ts"() {
|
|
476
|
+
"use strict";
|
|
477
|
+
init_config();
|
|
478
|
+
init_ollama();
|
|
479
|
+
init_openai();
|
|
480
|
+
}
|
|
481
|
+
});
|
|
482
|
+
|
|
147
483
|
// src/tools/paths.ts
|
|
148
484
|
import { resolve, relative as relative2, isAbsolute, sep, join as join4 } from "path";
|
|
149
485
|
import { homedir as homedir3 } from "os";
|
|
@@ -533,18 +869,18 @@ var init_grep = __esm({
|
|
|
533
869
|
const limit = max_results ?? 200;
|
|
534
870
|
const ci = case_insensitive === true || String(case_insensitive) === "true";
|
|
535
871
|
const tryRg = async () => {
|
|
536
|
-
const
|
|
537
|
-
if (ci)
|
|
538
|
-
if (glob2)
|
|
539
|
-
|
|
540
|
-
return execa2("rg",
|
|
872
|
+
const args2 = ["--line-number", "--no-heading", "--color=never", "-m", String(limit)];
|
|
873
|
+
if (ci) args2.push("-i");
|
|
874
|
+
if (glob2) args2.push("--glob", glob2);
|
|
875
|
+
args2.push("--", pattern, root);
|
|
876
|
+
return execa2("rg", args2, { reject: false, timeout: 2e4 });
|
|
541
877
|
};
|
|
542
878
|
const tryGrep = async () => {
|
|
543
|
-
const
|
|
544
|
-
if (ci)
|
|
545
|
-
if (glob2)
|
|
546
|
-
|
|
547
|
-
return execa2("grep",
|
|
879
|
+
const args2 = ["-R", "-n", "--color=never"];
|
|
880
|
+
if (ci) args2.push("-i");
|
|
881
|
+
if (glob2) args2.push("--include", glob2);
|
|
882
|
+
args2.push("--", pattern, root);
|
|
883
|
+
return execa2("grep", args2, { reject: false, timeout: 2e4 });
|
|
548
884
|
};
|
|
549
885
|
try {
|
|
550
886
|
let res;
|
|
@@ -891,6 +1227,7 @@ function toOllamaMessages(history, system) {
|
|
|
891
1227
|
const ollamaMsg = { role: "assistant", content: text };
|
|
892
1228
|
if (tool_uses.length > 0) {
|
|
893
1229
|
ollamaMsg.tool_calls = tool_uses.map((u) => ({
|
|
1230
|
+
id: u.id,
|
|
894
1231
|
function: { name: u.name, arguments: u.input }
|
|
895
1232
|
}));
|
|
896
1233
|
}
|
|
@@ -901,7 +1238,7 @@ function toOllamaMessages(history, system) {
|
|
|
901
1238
|
const tool_results = msg.content.filter((b) => b.type === "tool_result");
|
|
902
1239
|
const texts = msg.content.filter((b) => b.type === "text");
|
|
903
1240
|
for (const tr of tool_results) {
|
|
904
|
-
out.push({ role: "tool", content: tr.content });
|
|
1241
|
+
out.push({ role: "tool", content: tr.content, tool_call_id: tr.tool_use_id });
|
|
905
1242
|
}
|
|
906
1243
|
if (texts.length > 0) {
|
|
907
1244
|
out.push({ role: "user", content: texts.map((t) => t.text).join("") });
|
|
@@ -947,9 +1284,9 @@ function tryParse(raw, knownToolNames) {
|
|
|
947
1284
|
try {
|
|
948
1285
|
const obj = JSON.parse(s);
|
|
949
1286
|
const name = typeof obj.name === "string" ? obj.name : void 0;
|
|
950
|
-
const
|
|
1287
|
+
const args2 = obj.arguments ?? obj.parameters ?? obj.input ?? {};
|
|
951
1288
|
if (!name || !knownToolNames.includes(name)) return null;
|
|
952
|
-
return { function: { name, arguments:
|
|
1289
|
+
return { function: { name, arguments: args2 } };
|
|
953
1290
|
} catch {
|
|
954
1291
|
return null;
|
|
955
1292
|
}
|
|
@@ -1032,7 +1369,7 @@ async function* runAgent(opts) {
|
|
|
1032
1369
|
const composedSignal = signal ? AbortSignal.any ? AbortSignal.any([signal, ac.signal]) : ac.signal : ac.signal;
|
|
1033
1370
|
if (signal) signal.addEventListener("abort", () => ac.abort(), { once: true });
|
|
1034
1371
|
try {
|
|
1035
|
-
for await (const chunk of
|
|
1372
|
+
for await (const chunk of chat3(model, toOllamaMessages(history, system), ollamaTools, { signal: composedSignal, num_ctx, num_predict: NUM_PREDICT })) {
|
|
1036
1373
|
if (signal?.aborted) break;
|
|
1037
1374
|
if (chunk.content) {
|
|
1038
1375
|
text += chunk.content;
|
|
@@ -1191,7 +1528,7 @@ var init_loop = __esm({
|
|
|
1191
1528
|
init_policy();
|
|
1192
1529
|
init_adapter();
|
|
1193
1530
|
MAX_TURNS = 25;
|
|
1194
|
-
NUM_PREDICT =
|
|
1531
|
+
NUM_PREDICT = 8192;
|
|
1195
1532
|
REPEAT_TAIL = 120;
|
|
1196
1533
|
REPEAT_KILL = 4;
|
|
1197
1534
|
}
|
|
@@ -1338,7 +1675,7 @@ function pad(s, n) {
|
|
|
1338
1675
|
}
|
|
1339
1676
|
async function resolveModels(modelsArg) {
|
|
1340
1677
|
if (modelsArg !== "all") return modelsArg.split(",").map((m) => m.trim()).filter(Boolean);
|
|
1341
|
-
return (await
|
|
1678
|
+
return (await listModels3()).filter((m) => !m.includes("cloud"));
|
|
1342
1679
|
}
|
|
1343
1680
|
function verdict(passed, total) {
|
|
1344
1681
|
const ratio = total === 0 ? 0 : passed / total;
|
|
@@ -1385,10 +1722,10 @@ function printMatrix(models, picked, grid) {
|
|
|
1385
1722
|
}
|
|
1386
1723
|
console.log("\n + pass . fail ? not run");
|
|
1387
1724
|
}
|
|
1388
|
-
async function runEval(
|
|
1725
|
+
async function runEval(args2) {
|
|
1389
1726
|
const strip = (s) => (s ?? "").replace(/^-+/, "");
|
|
1390
|
-
const modelsArg = strip(
|
|
1391
|
-
const filter = strip(
|
|
1727
|
+
const modelsArg = strip(args2[0]) || process.env.MIII_EVAL_MODEL || "all";
|
|
1728
|
+
const filter = strip(args2[1]);
|
|
1392
1729
|
const picked = filter ? scenarios.filter((s) => s.name.includes(filter)) : scenarios;
|
|
1393
1730
|
if (picked.length === 0) {
|
|
1394
1731
|
console.error(`No scenarios match "${filter}"`);
|
|
@@ -1421,36 +1758,12 @@ import { createElement } from "react";
|
|
|
1421
1758
|
|
|
1422
1759
|
// src/ui/App.tsx
|
|
1423
1760
|
init_client();
|
|
1424
|
-
|
|
1761
|
+
init_config();
|
|
1762
|
+
import { useState as useState5, useEffect as useEffect4, useRef as useRef2 } from "react";
|
|
1425
1763
|
import { Box as Box10, Text as Text10, useApp } from "ink";
|
|
1426
1764
|
import { homedir as homedir6 } from "os";
|
|
1427
1765
|
import { sep as sep2 } from "path";
|
|
1428
1766
|
|
|
1429
|
-
// src/config.ts
|
|
1430
|
-
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
|
|
1431
|
-
import { join } from "path";
|
|
1432
|
-
import { homedir } from "os";
|
|
1433
|
-
var CONFIG_DIR = join(homedir(), ".miii");
|
|
1434
|
-
var CONFIG_PATH = join(CONFIG_DIR, "config.json");
|
|
1435
|
-
function loadConfig() {
|
|
1436
|
-
if (!existsSync(CONFIG_PATH)) return {};
|
|
1437
|
-
try {
|
|
1438
|
-
return JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
|
|
1439
|
-
} catch {
|
|
1440
|
-
return {};
|
|
1441
|
-
}
|
|
1442
|
-
}
|
|
1443
|
-
function saveConfig(config) {
|
|
1444
|
-
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
1445
|
-
writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), "utf-8");
|
|
1446
|
-
}
|
|
1447
|
-
function setModel(model) {
|
|
1448
|
-
saveConfig({ ...loadConfig(), model });
|
|
1449
|
-
}
|
|
1450
|
-
function setEffort(effort) {
|
|
1451
|
-
saveConfig({ ...loadConfig(), effort });
|
|
1452
|
-
}
|
|
1453
|
-
|
|
1454
1767
|
// src/ui/WelcomeBlock.tsx
|
|
1455
1768
|
import { Box, Text } from "ink";
|
|
1456
1769
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -1483,27 +1796,10 @@ function WelcomeBlock({ model, activeCtx, effort, cwd }) {
|
|
|
1483
1796
|
);
|
|
1484
1797
|
}
|
|
1485
1798
|
|
|
1486
|
-
// src/ui/ModelList.tsx
|
|
1487
|
-
import { Box as Box2, Text as Text2 } from "ink";
|
|
1488
|
-
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1489
|
-
function ModelList({ models, cursor, activeModel, showActive }) {
|
|
1490
|
-
if (models.length === 0) {
|
|
1491
|
-
return /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
|
|
1492
|
-
"no models found. run: ollama pull ",
|
|
1493
|
-
"<model>"
|
|
1494
|
-
] });
|
|
1495
|
-
}
|
|
1496
|
-
return /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, children: models.map((m, i) => /* @__PURE__ */ jsxs2(Text2, { color: i === cursor ? "blue" : void 0, dimColor: i !== cursor, children: [
|
|
1497
|
-
i === cursor ? "\u276F " : " ",
|
|
1498
|
-
m,
|
|
1499
|
-
showActive && m === activeModel ? /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " (active)" }) : null
|
|
1500
|
-
] }, m)) });
|
|
1501
|
-
}
|
|
1502
|
-
|
|
1503
1799
|
// src/ui/InputBar.tsx
|
|
1504
1800
|
import { useEffect, useState } from "react";
|
|
1505
|
-
import { Box as
|
|
1506
|
-
import { Fragment, jsx as
|
|
1801
|
+
import { Box as Box2, Text as Text2 } from "ink";
|
|
1802
|
+
import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1507
1803
|
var SPIN = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
1508
1804
|
function InputBar({ input, disabled, processingLabel }) {
|
|
1509
1805
|
const [frame, setFrame] = useState(0);
|
|
@@ -1512,8 +1808,8 @@ function InputBar({ input, disabled, processingLabel }) {
|
|
|
1512
1808
|
const t = setInterval(() => setFrame((f) => (f + 1) % SPIN.length), 150);
|
|
1513
1809
|
return () => clearInterval(t);
|
|
1514
1810
|
}, [disabled]);
|
|
1515
|
-
return /* @__PURE__ */
|
|
1516
|
-
|
|
1811
|
+
return /* @__PURE__ */ jsx2(
|
|
1812
|
+
Box2,
|
|
1517
1813
|
{
|
|
1518
1814
|
borderStyle: "single",
|
|
1519
1815
|
borderTop: true,
|
|
@@ -1522,45 +1818,83 @@ function InputBar({ input, disabled, processingLabel }) {
|
|
|
1522
1818
|
borderRight: false,
|
|
1523
1819
|
borderColor: disabled ? "yellow" : "white dim",
|
|
1524
1820
|
paddingX: 1,
|
|
1525
|
-
children: disabled ? /* @__PURE__ */
|
|
1526
|
-
/* @__PURE__ */
|
|
1527
|
-
/* @__PURE__ */
|
|
1528
|
-
/* @__PURE__ */
|
|
1529
|
-
] }) : /* @__PURE__ */
|
|
1530
|
-
/* @__PURE__ */
|
|
1531
|
-
/* @__PURE__ */
|
|
1532
|
-
/* @__PURE__ */
|
|
1821
|
+
children: disabled ? /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
1822
|
+
/* @__PURE__ */ jsx2(Text2, { color: "yellow", children: SPIN[frame] + " " }),
|
|
1823
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, italic: true, children: processingLabel ?? "processing\u2026" }),
|
|
1824
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " (esc to cancel)" })
|
|
1825
|
+
] }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
1826
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "> " }),
|
|
1827
|
+
/* @__PURE__ */ jsx2(Text2, { children: input }),
|
|
1828
|
+
/* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "\u258C" })
|
|
1533
1829
|
] })
|
|
1534
1830
|
}
|
|
1535
1831
|
);
|
|
1536
1832
|
}
|
|
1537
1833
|
|
|
1538
1834
|
// src/ui/ModelsView.tsx
|
|
1835
|
+
import { Box as Box3, Text as Text3 } from "ink";
|
|
1836
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1837
|
+
function ModelsView({ models, cursor, model, host, provider, effort, query, requireSelection }) {
|
|
1838
|
+
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", marginLeft: 2, children: [
|
|
1839
|
+
/* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", marginBottom: 1, children: [
|
|
1840
|
+
/* @__PURE__ */ jsxs3(Text3, { children: [
|
|
1841
|
+
/* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "provider " }),
|
|
1842
|
+
/* @__PURE__ */ jsx3(Text3, { color: "cyan", children: provider }),
|
|
1843
|
+
/* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
|
|
1844
|
+
" ",
|
|
1845
|
+
"host "
|
|
1846
|
+
] }),
|
|
1847
|
+
/* @__PURE__ */ jsx3(Text3, { children: host })
|
|
1848
|
+
] }),
|
|
1849
|
+
/* @__PURE__ */ jsxs3(Text3, { children: [
|
|
1850
|
+
/* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "effort " }),
|
|
1851
|
+
/* @__PURE__ */ jsx3(Text3, { children: effort }),
|
|
1852
|
+
/* @__PURE__ */ jsx3(Text3, { dimColor: true, children: " (\u2190 \u2192)" })
|
|
1853
|
+
] })
|
|
1854
|
+
] }),
|
|
1855
|
+
/* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "select model" }),
|
|
1856
|
+
/* @__PURE__ */ jsx3(Box3, { marginTop: 1, flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, children: models.length === 0 ? /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: query ? `no models match "${query}"` : provider === "lmstudio" ? "no models. load a model in LM Studio and start the server." : "no models found." }) : models.map((m, i) => {
|
|
1857
|
+
const sel = i === cursor;
|
|
1858
|
+
return /* @__PURE__ */ jsxs3(Text3, { color: sel ? "blue" : void 0, dimColor: !sel, children: [
|
|
1859
|
+
sel ? "\u276F " : " ",
|
|
1860
|
+
m,
|
|
1861
|
+
m === model ? /* @__PURE__ */ jsx3(Text3, { color: "green", children: " \u25CF" }) : null
|
|
1862
|
+
] }, m);
|
|
1863
|
+
}) }),
|
|
1864
|
+
/* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", children: [
|
|
1865
|
+
query ? /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: `filter: ${query}` }) : null,
|
|
1866
|
+
/* @__PURE__ */ jsx3(Text3, { dimColor: true, children: `\u2191\u2193 navigate enter select \u2190\u2192 effort tab provider type to filter${requireSelection ? " ctrl+c quit" : " esc close"}` })
|
|
1867
|
+
] })
|
|
1868
|
+
] });
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
// src/ui/ProviderPicker.tsx
|
|
1539
1872
|
import { Box as Box4, Text as Text4 } from "ink";
|
|
1540
1873
|
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1541
|
-
function
|
|
1874
|
+
function ProviderPicker({ entries, cursor, activeName, query }) {
|
|
1875
|
+
const nameWidth = Math.max(8, ...entries.map((e) => e.name.length));
|
|
1542
1876
|
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", marginLeft: 2, children: [
|
|
1543
|
-
/* @__PURE__ */
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1877
|
+
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "select provider" }),
|
|
1878
|
+
/* @__PURE__ */ jsx4(Box4, { marginTop: 1, flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, children: entries.length === 0 ? /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "no providers configured \u2014 add one in ~/.miii/config.json" }) : entries.map((e, i) => {
|
|
1879
|
+
const sel = i === cursor;
|
|
1880
|
+
return /* @__PURE__ */ jsxs4(Text4, { color: sel ? "blue" : void 0, dimColor: !sel, children: [
|
|
1881
|
+
sel ? "\u276F " : " ",
|
|
1882
|
+
e.name.padEnd(nameWidth),
|
|
1883
|
+
/* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
|
|
1884
|
+
" ",
|
|
1885
|
+
e.kind.padEnd(5)
|
|
1549
1886
|
] }),
|
|
1550
|
-
/* @__PURE__ */ jsxs4(Text4, { children: [
|
|
1551
|
-
|
|
1552
|
-
|
|
1887
|
+
/* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
|
|
1888
|
+
" ",
|
|
1889
|
+
e.entry.baseUrl
|
|
1553
1890
|
] }),
|
|
1554
|
-
/* @__PURE__ */
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
] })
|
|
1561
|
-
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "switch model" }),
|
|
1562
|
-
/* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(ModelList, { models, cursor, activeModel: model, showActive: true }) }),
|
|
1563
|
-
/* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "\u2191\u2193 navigate enter switch \u2190\u2192 effort esc close" }) })
|
|
1891
|
+
e.name === activeName ? /* @__PURE__ */ jsx4(Text4, { color: "green", children: " \u25CF" }) : null
|
|
1892
|
+
] }, e.name);
|
|
1893
|
+
}) }),
|
|
1894
|
+
/* @__PURE__ */ jsxs4(Box4, { marginTop: 1, flexDirection: "column", children: [
|
|
1895
|
+
query ? /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: `filter: ${query}` }) : null,
|
|
1896
|
+
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "\u2191\u2193 navigate enter select type to filter esc back" })
|
|
1897
|
+
] })
|
|
1564
1898
|
] });
|
|
1565
1899
|
}
|
|
1566
1900
|
|
|
@@ -1581,11 +1915,11 @@ function SessionsView({ sessions, cursor }) {
|
|
|
1581
1915
|
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", marginLeft: 2, children: [
|
|
1582
1916
|
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "resume session" }),
|
|
1583
1917
|
/* @__PURE__ */ jsx5(Box5, { marginTop: 1, flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, children: sessions.length === 0 ? /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "no saved sessions yet" }) : sessions.map((s, i) => {
|
|
1584
|
-
const
|
|
1918
|
+
const active2 = i === cursor;
|
|
1585
1919
|
const label = s.title;
|
|
1586
1920
|
return /* @__PURE__ */ jsxs5(Box5, { gap: 1, children: [
|
|
1587
|
-
/* @__PURE__ */ jsxs5(Text5, { color:
|
|
1588
|
-
|
|
1921
|
+
/* @__PURE__ */ jsxs5(Text5, { color: active2 ? "blue" : void 0, dimColor: !active2, children: [
|
|
1922
|
+
active2 ? "\u276F " : " ",
|
|
1589
1923
|
label
|
|
1590
1924
|
] }),
|
|
1591
1925
|
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: `\xB7 ${s.messageCount} msgs \xB7 ${relativeTime(s.updatedAt)}` })
|
|
@@ -1599,7 +1933,8 @@ function SessionsView({ sessions, cursor }) {
|
|
|
1599
1933
|
import { Box as Box6, Text as Text6 } from "ink";
|
|
1600
1934
|
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1601
1935
|
var COMMANDS = [
|
|
1602
|
-
{ name: "/models", description: "
|
|
1936
|
+
{ name: "/models", description: "pick model \xB7 tab to change provider \xB7 \u2190\u2192 effort" },
|
|
1937
|
+
{ name: "/provider", description: "open provider picker (configured in ~/.miii/config.json)" },
|
|
1603
1938
|
{ name: "/new", description: "save current session and start fresh" },
|
|
1604
1939
|
{ name: "/sessions", description: "list sessions and resume one" },
|
|
1605
1940
|
{ name: "/clear", description: "clear chat and reset context" },
|
|
@@ -1620,10 +1955,10 @@ function CommandPalette({ filter, cursor }) {
|
|
|
1620
1955
|
paddingX: 1,
|
|
1621
1956
|
children: [
|
|
1622
1957
|
filtered.map((cmd2, i) => {
|
|
1623
|
-
const
|
|
1958
|
+
const active2 = i === cursor;
|
|
1624
1959
|
return /* @__PURE__ */ jsxs6(Box6, { gap: 2, children: [
|
|
1625
|
-
/* @__PURE__ */ jsxs6(Text6, { bold:
|
|
1626
|
-
|
|
1960
|
+
/* @__PURE__ */ jsxs6(Text6, { bold: active2, color: active2 ? "blue" : void 0, dimColor: !active2, children: [
|
|
1961
|
+
active2 ? "\u276F " : " ",
|
|
1627
1962
|
cmd2.name.padEnd(nameWidth)
|
|
1628
1963
|
] }),
|
|
1629
1964
|
/* @__PURE__ */ jsx6(Text6, { dimColor: true, children: cmd2.description })
|
|
@@ -1645,7 +1980,7 @@ import { join as join2 } from "path";
|
|
|
1645
1980
|
import { homedir as homedir2 } from "os";
|
|
1646
1981
|
import { randomUUID } from "crypto";
|
|
1647
1982
|
function encodeProjectDir(cwd) {
|
|
1648
|
-
return cwd.replace(/[
|
|
1983
|
+
return cwd.replace(/[:/\\]+/g, "-").replace(/^-+/, "");
|
|
1649
1984
|
}
|
|
1650
1985
|
var SESSION_DIR = join2(homedir2(), ".miii", "projects", encodeProjectDir(process.cwd()), "session");
|
|
1651
1986
|
function newSessionId() {
|
|
@@ -1768,7 +2103,7 @@ Request:
|
|
|
1768
2103
|
${text.slice(0, 2e3)}`;
|
|
1769
2104
|
try {
|
|
1770
2105
|
let out = "";
|
|
1771
|
-
for await (const chunk of
|
|
2106
|
+
for await (const chunk of chat3(
|
|
1772
2107
|
model,
|
|
1773
2108
|
[{ role: "user", content: prompt }],
|
|
1774
2109
|
void 0,
|
|
@@ -1850,9 +2185,9 @@ function FilePicker({ matches: matches2, cursor }) {
|
|
|
1850
2185
|
paddingX: 1,
|
|
1851
2186
|
children: [
|
|
1852
2187
|
matches2.map((f, i) => {
|
|
1853
|
-
const
|
|
1854
|
-
return /* @__PURE__ */ jsx7(Box7, { children: /* @__PURE__ */ jsxs7(Text7, { bold:
|
|
1855
|
-
|
|
2188
|
+
const active2 = i === cursor;
|
|
2189
|
+
return /* @__PURE__ */ jsx7(Box7, { children: /* @__PURE__ */ jsxs7(Text7, { bold: active2, color: active2 ? "blue" : void 0, dimColor: !active2, children: [
|
|
2190
|
+
active2 ? "\u276F " : " ",
|
|
1856
2191
|
f
|
|
1857
2192
|
] }) }, f);
|
|
1858
2193
|
}),
|
|
@@ -2451,6 +2786,7 @@ function useAgentRunner(model, activeCtx) {
|
|
|
2451
2786
|
}
|
|
2452
2787
|
|
|
2453
2788
|
// src/ui/hooks/useKeyboard.ts
|
|
2789
|
+
init_config();
|
|
2454
2790
|
import { useInput } from "ink";
|
|
2455
2791
|
var EFFORTS = ["low", "medium", "high"];
|
|
2456
2792
|
function useKeyboard(opts) {
|
|
@@ -2465,6 +2801,9 @@ function useKeyboard(opts) {
|
|
|
2465
2801
|
cfg,
|
|
2466
2802
|
setCfg,
|
|
2467
2803
|
setActiveCtx,
|
|
2804
|
+
providers,
|
|
2805
|
+
pickerQuery,
|
|
2806
|
+
setPickerQuery,
|
|
2468
2807
|
agent,
|
|
2469
2808
|
input,
|
|
2470
2809
|
setInput,
|
|
@@ -2476,7 +2815,8 @@ function useKeyboard(opts) {
|
|
|
2476
2815
|
setSessionId,
|
|
2477
2816
|
sessions,
|
|
2478
2817
|
setSessions,
|
|
2479
|
-
setNotice
|
|
2818
|
+
setNotice,
|
|
2819
|
+
switchProvider
|
|
2480
2820
|
} = opts;
|
|
2481
2821
|
const {
|
|
2482
2822
|
pendingPermissionRef,
|
|
@@ -2523,6 +2863,38 @@ function useKeyboard(opts) {
|
|
|
2523
2863
|
abortRef.current.abort();
|
|
2524
2864
|
return;
|
|
2525
2865
|
}
|
|
2866
|
+
if (state === "providers") {
|
|
2867
|
+
if (key.upArrow) {
|
|
2868
|
+
setCursor((i) => Math.max(0, i - 1));
|
|
2869
|
+
return;
|
|
2870
|
+
}
|
|
2871
|
+
if (key.downArrow) {
|
|
2872
|
+
setCursor((i) => Math.min(providers.length - 1, i + 1));
|
|
2873
|
+
return;
|
|
2874
|
+
}
|
|
2875
|
+
if (key.escape) {
|
|
2876
|
+
setPickerQuery("");
|
|
2877
|
+
setCursor(() => 0);
|
|
2878
|
+
setState(cfg.model ? "models" : "select-model");
|
|
2879
|
+
return;
|
|
2880
|
+
}
|
|
2881
|
+
if (key.return && providers[cursor]) {
|
|
2882
|
+
const chosen = providers[cursor].name;
|
|
2883
|
+
setNotice(`switched to ${chosen}`);
|
|
2884
|
+
switchProvider(chosen);
|
|
2885
|
+
return;
|
|
2886
|
+
}
|
|
2887
|
+
if (key.backspace || key.delete) {
|
|
2888
|
+
setPickerQuery(pickerQuery.slice(0, -1));
|
|
2889
|
+
setCursor(() => 0);
|
|
2890
|
+
return;
|
|
2891
|
+
}
|
|
2892
|
+
if (char && !key.ctrl && !key.meta && char.length === 1 && char >= " ") {
|
|
2893
|
+
setPickerQuery(pickerQuery + char);
|
|
2894
|
+
setCursor(() => 0);
|
|
2895
|
+
}
|
|
2896
|
+
return;
|
|
2897
|
+
}
|
|
2526
2898
|
if (state === "select-model" || state === "models") {
|
|
2527
2899
|
if (key.upArrow) {
|
|
2528
2900
|
setCursor((i) => Math.max(0, i - 1));
|
|
@@ -2537,21 +2909,45 @@ function useKeyboard(opts) {
|
|
|
2537
2909
|
setModel(chosen);
|
|
2538
2910
|
setCfg((c) => ({ ...c, model: chosen }));
|
|
2539
2911
|
if (contexts[chosen]) setActiveCtx(contexts[chosen]);
|
|
2912
|
+
setPickerQuery("");
|
|
2913
|
+
setCursor(() => 0);
|
|
2540
2914
|
setState("ready");
|
|
2541
2915
|
return;
|
|
2542
2916
|
}
|
|
2543
|
-
if (
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2917
|
+
if (key.tab) {
|
|
2918
|
+
setPickerQuery("");
|
|
2919
|
+
setCursor(() => 0);
|
|
2920
|
+
setState("providers");
|
|
2921
|
+
return;
|
|
2922
|
+
}
|
|
2923
|
+
if (key.rightArrow) {
|
|
2924
|
+
const next = EFFORTS[Math.min(EFFORTS.indexOf(effort) + 1, EFFORTS.length - 1)];
|
|
2925
|
+
setEffort(next);
|
|
2926
|
+
setCfg((c) => ({ ...c, effort: next }));
|
|
2927
|
+
return;
|
|
2928
|
+
}
|
|
2929
|
+
if (key.leftArrow) {
|
|
2930
|
+
const next = EFFORTS[Math.max(EFFORTS.indexOf(effort) - 1, 0)];
|
|
2931
|
+
setEffort(next);
|
|
2932
|
+
setCfg((c) => ({ ...c, effort: next }));
|
|
2933
|
+
return;
|
|
2934
|
+
}
|
|
2935
|
+
if (key.escape) {
|
|
2936
|
+
if (state === "models") {
|
|
2937
|
+
setPickerQuery("");
|
|
2938
|
+
setCursor(() => 0);
|
|
2553
2939
|
setState("ready");
|
|
2554
2940
|
}
|
|
2941
|
+
return;
|
|
2942
|
+
}
|
|
2943
|
+
if (key.backspace || key.delete) {
|
|
2944
|
+
setPickerQuery(pickerQuery.slice(0, -1));
|
|
2945
|
+
setCursor(() => 0);
|
|
2946
|
+
return;
|
|
2947
|
+
}
|
|
2948
|
+
if (char && !key.ctrl && !key.meta && char.length === 1 && char >= " ") {
|
|
2949
|
+
setPickerQuery(pickerQuery + char);
|
|
2950
|
+
setCursor(() => 0);
|
|
2555
2951
|
}
|
|
2556
2952
|
return;
|
|
2557
2953
|
}
|
|
@@ -2654,8 +3050,13 @@ function useKeyboard(opts) {
|
|
|
2654
3050
|
if (key.return) {
|
|
2655
3051
|
const trimmed = input.trim();
|
|
2656
3052
|
if (trimmed === "/models") {
|
|
3053
|
+
setPickerQuery("");
|
|
2657
3054
|
setCursor(() => Math.max(0, models.findIndex((m) => m === cfg.model)));
|
|
2658
3055
|
setState("models");
|
|
3056
|
+
} else if (trimmed === "/provider" || trimmed === "/providers") {
|
|
3057
|
+
setPickerQuery("");
|
|
3058
|
+
setCursor(() => Math.max(0, providers.findIndex((p) => p.name === cfg.provider)));
|
|
3059
|
+
setState("providers");
|
|
2659
3060
|
} else if (trimmed === "/clear") {
|
|
2660
3061
|
clearSession();
|
|
2661
3062
|
} else if (trimmed === "/new") {
|
|
@@ -2668,6 +3069,15 @@ function useKeyboard(opts) {
|
|
|
2668
3069
|
setState("sessions");
|
|
2669
3070
|
} else if (trimmed === "/exit") {
|
|
2670
3071
|
exit();
|
|
3072
|
+
} else if (trimmed.startsWith("/provider ")) {
|
|
3073
|
+
const p = trimmed.slice("/provider ".length).trim();
|
|
3074
|
+
const names = providers.map((x) => x.name);
|
|
3075
|
+
if (names.includes(p)) {
|
|
3076
|
+
setNotice(`switched to ${p}`);
|
|
3077
|
+
switchProvider(p);
|
|
3078
|
+
} else {
|
|
3079
|
+
setNotice(`unknown provider "${p}" \u2014 configured: ${names.join(", ")}`);
|
|
3080
|
+
}
|
|
2671
3081
|
} else if (trimmed) {
|
|
2672
3082
|
setNotice(null);
|
|
2673
3083
|
if (!agentHistory.length && cfg.model) {
|
|
@@ -2750,8 +3160,9 @@ function App() {
|
|
|
2750
3160
|
const [activeCtx, setActiveCtx] = useState5(null);
|
|
2751
3161
|
const [state, setState] = useState5("loading");
|
|
2752
3162
|
const [cursor, setCursor] = useState5(0);
|
|
3163
|
+
const [pickerQuery, setPickerQuery] = useState5("");
|
|
2753
3164
|
const [updateAvailable, setUpdateAvailable] = useState5(null);
|
|
2754
|
-
const [
|
|
3165
|
+
const [providerDown, setProviderDown] = useState5(false);
|
|
2755
3166
|
const [sessionId, setSessionId] = useState5(() => newSessionId());
|
|
2756
3167
|
const [sessions, setSessions] = useState5([]);
|
|
2757
3168
|
const [notice, setNotice] = useState5(null);
|
|
@@ -2767,36 +3178,67 @@ function App() {
|
|
|
2767
3178
|
useEffect4(() => {
|
|
2768
3179
|
if (agent.agentHistory.length) persistSession(sessionId, agent.agentHistory);
|
|
2769
3180
|
}, [agent.agentHistory, sessionId]);
|
|
2770
|
-
|
|
2771
|
-
|
|
3181
|
+
const loadGen = useRef2(0);
|
|
3182
|
+
const loadModels = (afterProvider = false) => {
|
|
3183
|
+
const gen = ++loadGen.current;
|
|
3184
|
+
const stale = () => gen !== loadGen.current;
|
|
3185
|
+
setProviderDown(false);
|
|
3186
|
+
listModels3().then((m) => {
|
|
3187
|
+
if (stale()) return;
|
|
2772
3188
|
setModels(m);
|
|
2773
|
-
|
|
2774
|
-
|
|
3189
|
+
const hasModel = !!cfg.model && m.includes(cfg.model);
|
|
3190
|
+
if (afterProvider) {
|
|
3191
|
+
setState(hasModel ? "models" : "select-model");
|
|
3192
|
+
} else {
|
|
3193
|
+
setState(hasModel ? "ready" : "select-model");
|
|
3194
|
+
}
|
|
3195
|
+
Promise.all(m.map((name) => modelContext3(name).then((ctx) => [name, ctx]))).then((pairs) => {
|
|
3196
|
+
if (stale()) return;
|
|
2775
3197
|
const map = Object.fromEntries(pairs);
|
|
2776
3198
|
setContexts(map);
|
|
2777
|
-
const
|
|
2778
|
-
if (
|
|
3199
|
+
const active2 = (hasModel ? cfg.model : void 0) ?? m[0];
|
|
3200
|
+
if (active2 && map[active2]) setActiveCtx(map[active2]);
|
|
2779
3201
|
}).catch(() => {
|
|
2780
3202
|
});
|
|
2781
3203
|
}).catch((err) => {
|
|
3204
|
+
if (stale()) return;
|
|
2782
3205
|
const msg = err instanceof Error ? err.message : String(err);
|
|
2783
|
-
agent.setError(
|
|
2784
|
-
|
|
3206
|
+
agent.setError(isAvailable3() ? msg : NOT_AVAILABLE());
|
|
3207
|
+
setProviderDown(true);
|
|
2785
3208
|
setModels([]);
|
|
2786
|
-
|
|
3209
|
+
setPickerQuery("");
|
|
3210
|
+
setCursor(() => 0);
|
|
3211
|
+
setState("ready");
|
|
2787
3212
|
});
|
|
2788
|
-
}
|
|
3213
|
+
};
|
|
3214
|
+
useEffect4(loadModels, []);
|
|
3215
|
+
function switchProvider(p) {
|
|
3216
|
+
setProvider(p);
|
|
3217
|
+
setCfg((c) => ({ ...c, provider: p }));
|
|
3218
|
+
setPickerQuery("");
|
|
3219
|
+
setCursor(() => 0);
|
|
3220
|
+
agent.setError(null);
|
|
3221
|
+
loadModels(true);
|
|
3222
|
+
}
|
|
3223
|
+
const { name: provName, entry: provEntry } = resolveProvider(cfg);
|
|
3224
|
+
const q = pickerQuery.toLowerCase();
|
|
3225
|
+
const filteredModels = q ? models.filter((m) => m.toLowerCase().includes(q)) : models;
|
|
3226
|
+
const allProviders = providerEntries(cfg);
|
|
3227
|
+
const filteredProviders = q ? allProviders.filter((p) => p.name.toLowerCase().includes(q)) : allProviders;
|
|
2789
3228
|
useKeyboard({
|
|
2790
3229
|
exit,
|
|
2791
3230
|
state,
|
|
2792
3231
|
setState,
|
|
2793
|
-
models,
|
|
3232
|
+
models: filteredModels,
|
|
2794
3233
|
cursor,
|
|
2795
3234
|
setCursor,
|
|
2796
3235
|
contexts,
|
|
2797
3236
|
cfg,
|
|
2798
3237
|
setCfg,
|
|
2799
3238
|
setActiveCtx,
|
|
3239
|
+
providers: filteredProviders,
|
|
3240
|
+
pickerQuery,
|
|
3241
|
+
setPickerQuery,
|
|
2800
3242
|
agent,
|
|
2801
3243
|
input,
|
|
2802
3244
|
setInput,
|
|
@@ -2808,7 +3250,8 @@ function App() {
|
|
|
2808
3250
|
setSessionId,
|
|
2809
3251
|
sessions,
|
|
2810
3252
|
setSessions,
|
|
2811
|
-
setNotice
|
|
3253
|
+
setNotice,
|
|
3254
|
+
switchProvider
|
|
2812
3255
|
});
|
|
2813
3256
|
const effort = cfg.effort ?? "medium";
|
|
2814
3257
|
const contextWarning = (() => {
|
|
@@ -2821,7 +3264,7 @@ function App() {
|
|
|
2821
3264
|
return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", paddingX: 1, children: [
|
|
2822
3265
|
/* @__PURE__ */ jsx10(WelcomeBlock, { model: cfg.model, activeCtx, effort, cwd, error: agent.error }),
|
|
2823
3266
|
updateAvailable && /* @__PURE__ */ jsx10(Box10, { marginLeft: 2, marginBottom: 1, children: /* @__PURE__ */ jsx10(Text10, { color: "yellow", children: `\u2191 update available: v${updateAvailable} \u2014 run: miii --update` }) }),
|
|
2824
|
-
state === "loading" && !agent.error && /* @__PURE__ */ jsx10(Box10, { marginLeft: 2, marginBottom: 1, children: /* @__PURE__ */ jsx10(Text10, { dimColor: true, children:
|
|
3267
|
+
state === "loading" && !agent.error && /* @__PURE__ */ jsx10(Box10, { marginLeft: 2, marginBottom: 1, children: /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: `connecting to ${provName}\u2026` }) }),
|
|
2825
3268
|
agent.error && state !== "ready" && /* @__PURE__ */ jsx10(
|
|
2826
3269
|
ChatView,
|
|
2827
3270
|
{
|
|
@@ -2832,19 +3275,26 @@ function App() {
|
|
|
2832
3275
|
error: agent.error
|
|
2833
3276
|
}
|
|
2834
3277
|
),
|
|
2835
|
-
state === "select-model" && /* @__PURE__ */
|
|
2836
|
-
/* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "no model configured \u2014 select one" }),
|
|
2837
|
-
/* @__PURE__ */ jsx10(Box10, { marginTop: 1, children: /* @__PURE__ */ jsx10(ModelList, { models, cursor }) }),
|
|
2838
|
-
models.length > 0 && /* @__PURE__ */ jsx10(Box10, { marginTop: 1, children: /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "\u2191\u2193 navigate enter select ctrl+c quit" }) })
|
|
2839
|
-
] }),
|
|
2840
|
-
state === "models" && /* @__PURE__ */ jsx10(
|
|
3278
|
+
(state === "select-model" || state === "models") && /* @__PURE__ */ jsx10(
|
|
2841
3279
|
ModelsView,
|
|
2842
3280
|
{
|
|
2843
|
-
models,
|
|
3281
|
+
models: filteredModels,
|
|
2844
3282
|
cursor,
|
|
2845
3283
|
model: cfg.model,
|
|
2846
|
-
|
|
2847
|
-
|
|
3284
|
+
host: provEntry.baseUrl,
|
|
3285
|
+
provider: provName,
|
|
3286
|
+
effort,
|
|
3287
|
+
query: pickerQuery,
|
|
3288
|
+
requireSelection: state === "select-model"
|
|
3289
|
+
}
|
|
3290
|
+
),
|
|
3291
|
+
state === "providers" && /* @__PURE__ */ jsx10(
|
|
3292
|
+
ProviderPicker,
|
|
3293
|
+
{
|
|
3294
|
+
entries: filteredProviders,
|
|
3295
|
+
cursor,
|
|
3296
|
+
activeName: provName,
|
|
3297
|
+
query: pickerQuery
|
|
2848
3298
|
}
|
|
2849
3299
|
),
|
|
2850
3300
|
state === "sessions" && /* @__PURE__ */ jsx10(SessionsView, { sessions, cursor }),
|
|
@@ -2872,24 +3322,33 @@ function App() {
|
|
|
2872
3322
|
if (!m) return null;
|
|
2873
3323
|
return /* @__PURE__ */ jsx10(FilePicker, { matches: searchFiles(process.cwd(), m.query), cursor: filePickerCursor });
|
|
2874
3324
|
})(),
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
!agent.busy && /* @__PURE__ */ jsx10(Box10, { marginLeft: 2, marginBottom: 1, children: /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "type / to see commands" }) })
|
|
2878
|
-
] })
|
|
3325
|
+
/* @__PURE__ */ jsx10(InputBar, { input, disabled: agent.busy, processingLabel: agent.processingLabel }),
|
|
3326
|
+
!agent.busy && /* @__PURE__ */ jsx10(Box10, { marginLeft: 2, marginBottom: 1, children: /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: providerDown ? "provider unavailable \u2014 /provider to switch \xB7 /models to pick a model" : "type / to see commands" }) })
|
|
2879
3327
|
] })
|
|
2880
3328
|
] });
|
|
2881
3329
|
}
|
|
2882
3330
|
|
|
2883
3331
|
// src/cli.tsx
|
|
2884
3332
|
init_spill();
|
|
3333
|
+
init_config();
|
|
2885
3334
|
cleanupSpill();
|
|
2886
|
-
var
|
|
3335
|
+
var args = process.argv.slice(2);
|
|
3336
|
+
var cmd;
|
|
3337
|
+
for (let i = 0; i < args.length; i++) {
|
|
3338
|
+
if ((args[i] === "--provider" || args[i] === "-p") && i + 1 < args.length) {
|
|
3339
|
+
const p = args[++i];
|
|
3340
|
+
if (listProviders().includes(p)) setProvider(p);
|
|
3341
|
+
} else if (!cmd) {
|
|
3342
|
+
cmd = args[i];
|
|
3343
|
+
}
|
|
3344
|
+
}
|
|
2887
3345
|
if (cmd === "update" || cmd === "--update" || cmd === "-u") {
|
|
2888
3346
|
const { spawnSync } = await import("child_process");
|
|
2889
3347
|
console.log("Updating miii-agent\u2026");
|
|
2890
3348
|
const r = spawnSync("npm", ["i", "-g", "miii-agent@latest"], { stdio: "inherit", shell: process.platform === "win32" });
|
|
2891
3349
|
process.exit(r.status ?? 1);
|
|
2892
3350
|
} else if (cmd === "doctor" || cmd === "eval") {
|
|
3351
|
+
const rest = args.filter((a) => a !== cmd);
|
|
2893
3352
|
const { runEval: runEval2 } = await Promise.resolve().then(() => (init_run(), run_exports));
|
|
2894
3353
|
process.exit(await runEval2(rest));
|
|
2895
3354
|
} else {
|