min-agent 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +169 -284
- package/dist/agent.js +36 -22
- package/dist/cli/commands/chat.js +3 -0
- package/dist/cli/commands/exec.js +3 -0
- package/dist/cli/commands/index.js +22 -5
- package/dist/cli/commands/memory.js +33 -15
- package/dist/cli/commands/think.js +12 -0
- package/dist/cli/commands/write-config.js +22 -0
- package/dist/cli/option-helpers.js +13 -1
- package/dist/cli/program.js +50 -13
- package/dist/code-mode.js +1 -1
- package/dist/config.js +41 -0
- package/dist/context-window.js +8 -28
- package/dist/memory-cli.js +33 -0
- package/dist/memory.js +127 -46
- package/dist/model-catalog.js +285 -0
- package/dist/permission-cli.js +1 -4
- package/dist/provider.js +4 -1
- package/dist/reasoning-stream.js +158 -0
- package/dist/sandbox-cli.js +1 -4
- package/dist/scope.js +23 -0
- package/dist/serve/common.js +22 -1
- package/dist/serve/routes-chat.js +21 -1
- package/dist/serve/routes-memory.js +31 -2
- package/dist/serve/routes-meta.js +34 -6
- package/dist/think-cli.js +36 -0
- package/dist/thinking-wire.js +228 -0
- package/dist/thinking.js +142 -0
- package/dist/token-display.js +10 -7
- package/dist/tools/todo.js +22 -8
- package/dist/tui/App.js +36 -8
- package/dist/tui/InputBar.js +109 -36
- package/dist/tui/MessageList.js +53 -22
- package/dist/tui/StatusBar.js +7 -3
- package/dist/tui/ThinkPicker.js +77 -0
- package/dist/tui/bracketed-paste.js +37 -0
- package/dist/tui/caret-pos.js +10 -8
- package/dist/tui/index.js +7 -1
- package/dist/tui/layout.js +17 -0
- package/dist/tui/overlay-input.js +12 -0
- package/dist/tui/paste-draft.js +173 -0
- package/dist/tui/selection.js +8 -2
- package/dist/tui/slash-commands.js +18 -1
- package/dist/tui/slash-handler.js +61 -17
- package/dist/tui/text-width.js +6 -6
- package/dist/tui-chat.js +63 -7
- package/docs/API.md +50 -4
- package/docs/superpowers/plans/2026-08-23-input-paste-attachments.md +475 -0
- package/docs/superpowers/plans/2026-08-23-thinking-wire-profile.md +450 -0
- package/docs/superpowers/specs/2026-08-23-input-paste-attachments-design.md +174 -0
- package/docs/superpowers/specs/2026-08-23-thinking-wire-profile-design.md +140 -0
- package/package.json +1 -1
- package/skills/self-config/SKILL.md +5 -4
- package/skills/self-config/reference.md +10 -5
package/dist/provider.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { createOpenAI } from "@ai-sdk/openai";
|
|
2
2
|
import { getEffectiveConfig, getActiveProvider, normalizeOllamaBaseURL } from "./config.js";
|
|
3
3
|
import { createTimeoutFetch, DEFAULT_FIRST_BYTE_TIMEOUT_MS, DEFAULT_STREAM_IDLE_TIMEOUT_MS, } from "./fetch-timeout.js";
|
|
4
|
+
import { foldReasoningInChatResponse } from "./reasoning-stream.js";
|
|
5
|
+
import { fetchWithThinkingWire } from "./thinking-wire.js";
|
|
4
6
|
function positiveMs(raw, fallback) {
|
|
5
7
|
const value = typeof raw === "string" ? Number(raw) : raw;
|
|
6
8
|
if (typeof value !== "number" || !Number.isFinite(value) || value < 0)
|
|
@@ -10,11 +12,12 @@ function positiveMs(raw, fallback) {
|
|
|
10
12
|
/** Timeout/trace-aware fetch shared by every provider client. */
|
|
11
13
|
export function modelFetch() {
|
|
12
14
|
const cfg = getEffectiveConfig();
|
|
13
|
-
|
|
15
|
+
const inner = createTimeoutFetch({
|
|
14
16
|
firstByteTimeoutMs: positiveMs(process.env.MIN_AGENT_REQUEST_TIMEOUT_MS ?? cfg.agent?.requestTimeoutMs, DEFAULT_FIRST_BYTE_TIMEOUT_MS),
|
|
15
17
|
idleTimeoutMs: positiveMs(process.env.MIN_AGENT_STREAM_IDLE_TIMEOUT_MS ?? cfg.agent?.streamIdleTimeoutMs, DEFAULT_STREAM_IDLE_TIMEOUT_MS),
|
|
16
18
|
trace: process.env.MIN_AGENT_TRACE === "1" || process.env.MIN_AGENT_TRACE === "true",
|
|
17
19
|
});
|
|
20
|
+
return (input, init) => fetchWithThinkingWire(inner, input, init).then(foldReasoningInChatResponse);
|
|
18
21
|
}
|
|
19
22
|
export function resolveModelForProvider(provider, modelId) {
|
|
20
23
|
if (!provider.baseURL || !provider.apiKey) {
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI-compatible chat completions put the thinking chain in
|
|
3
|
+
* `reasoning_content` / `reasoning` / `reasoning_details`. The @ai-sdk/openai
|
|
4
|
+
* chat parser only reads `delta.content`, so those fields never become
|
|
5
|
+
* reasoning events. Fold them into `<think>` tags that ThinkingBodySplitter
|
|
6
|
+
* already turns into on-screen thinking.
|
|
7
|
+
*/
|
|
8
|
+
function isRecord(v) {
|
|
9
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
10
|
+
}
|
|
11
|
+
function sanitizeReasoning(text) {
|
|
12
|
+
return text.replace(/<\/?(?:think|thinking)>/gi, "");
|
|
13
|
+
}
|
|
14
|
+
function stringField(v, key) {
|
|
15
|
+
if (!isRecord(v))
|
|
16
|
+
return "";
|
|
17
|
+
return typeof v[key] === "string" ? v[key] : "";
|
|
18
|
+
}
|
|
19
|
+
function reasoningFromDetails(details) {
|
|
20
|
+
if (!Array.isArray(details))
|
|
21
|
+
return "";
|
|
22
|
+
return details
|
|
23
|
+
.map((item) => {
|
|
24
|
+
const text = stringField(item, "text");
|
|
25
|
+
if (text)
|
|
26
|
+
return text;
|
|
27
|
+
return stringField(item, "content");
|
|
28
|
+
})
|
|
29
|
+
.join("");
|
|
30
|
+
}
|
|
31
|
+
function reasoningFromValue(v) {
|
|
32
|
+
if (typeof v === "string")
|
|
33
|
+
return v;
|
|
34
|
+
if (!isRecord(v))
|
|
35
|
+
return "";
|
|
36
|
+
return stringField(v, "content") || stringField(v, "text");
|
|
37
|
+
}
|
|
38
|
+
export function reasoningTextFromDelta(delta) {
|
|
39
|
+
const details = reasoningFromDetails(delta.reasoning_details);
|
|
40
|
+
const primary = reasoningFromValue(delta.reasoning_content) ||
|
|
41
|
+
reasoningFromValue(delta.reasoning) ||
|
|
42
|
+
reasoningFromValue(delta.thinking);
|
|
43
|
+
return sanitizeReasoning(`${details}${primary}`);
|
|
44
|
+
}
|
|
45
|
+
export function createReasoningDeltaTracker() {
|
|
46
|
+
let acc = "";
|
|
47
|
+
return (text) => {
|
|
48
|
+
if (!text)
|
|
49
|
+
return "";
|
|
50
|
+
if (acc && text.startsWith(acc)) {
|
|
51
|
+
const extra = text.slice(acc.length);
|
|
52
|
+
acc = text;
|
|
53
|
+
return extra;
|
|
54
|
+
}
|
|
55
|
+
acc += text;
|
|
56
|
+
return text;
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
export function foldReasoningIntoDelta(delta, takeDelta) {
|
|
60
|
+
const reasoning = takeDelta(reasoningTextFromDelta(delta));
|
|
61
|
+
if (!reasoning)
|
|
62
|
+
return false;
|
|
63
|
+
const content = typeof delta.content === "string" ? delta.content : "";
|
|
64
|
+
delta.content = `<think>${reasoning}</think>${content}`;
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
export function foldReasoningInChunk(chunk, takeDelta = (t) => t) {
|
|
68
|
+
if (!isRecord(chunk) || !Array.isArray(chunk.choices))
|
|
69
|
+
return false;
|
|
70
|
+
let changed = false;
|
|
71
|
+
for (const choice of chunk.choices) {
|
|
72
|
+
if (!isRecord(choice))
|
|
73
|
+
continue;
|
|
74
|
+
if (isRecord(choice.delta) && foldReasoningIntoDelta(choice.delta, takeDelta))
|
|
75
|
+
changed = true;
|
|
76
|
+
if (isRecord(choice.message) && foldReasoningIntoDelta(choice.message, takeDelta))
|
|
77
|
+
changed = true;
|
|
78
|
+
}
|
|
79
|
+
return changed;
|
|
80
|
+
}
|
|
81
|
+
export function foldReasoningIntoSseLine(line, takeDelta) {
|
|
82
|
+
const trimmed = line.replace(/\r$/, "");
|
|
83
|
+
if (!trimmed.startsWith("data:"))
|
|
84
|
+
return line;
|
|
85
|
+
const payload = trimmed.slice("data:".length).trim();
|
|
86
|
+
if (!payload || payload === "[DONE]")
|
|
87
|
+
return line;
|
|
88
|
+
try {
|
|
89
|
+
const parsed = JSON.parse(payload);
|
|
90
|
+
if (!foldReasoningInChunk(parsed, takeDelta))
|
|
91
|
+
return line;
|
|
92
|
+
const prefix = line.startsWith("data: ") ? "data: " : "data:";
|
|
93
|
+
const nl = line.endsWith("\r") ? "\r" : "";
|
|
94
|
+
return `${prefix}${JSON.stringify(parsed)}${nl}`;
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
return line;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function rewriteSseFragment(fragment, takeDelta) {
|
|
101
|
+
const keepLastNl = fragment.endsWith("\n");
|
|
102
|
+
const raw = keepLastNl ? fragment.slice(0, -1) : fragment;
|
|
103
|
+
return (raw
|
|
104
|
+
.split("\n")
|
|
105
|
+
.map((line) => foldReasoningIntoSseLine(line, takeDelta))
|
|
106
|
+
.join("\n") + (keepLastNl ? "\n" : ""));
|
|
107
|
+
}
|
|
108
|
+
export function transformReasoningSse(source) {
|
|
109
|
+
const decoder = new TextDecoder();
|
|
110
|
+
const encoder = new TextEncoder();
|
|
111
|
+
const reader = source.getReader();
|
|
112
|
+
const takeDelta = createReasoningDeltaTracker();
|
|
113
|
+
let pending = "";
|
|
114
|
+
return new ReadableStream({
|
|
115
|
+
async pull(controller) {
|
|
116
|
+
for (;;) {
|
|
117
|
+
const { done, value } = await reader.read();
|
|
118
|
+
if (done) {
|
|
119
|
+
pending += decoder.decode();
|
|
120
|
+
if (pending)
|
|
121
|
+
controller.enqueue(encoder.encode(rewriteSseFragment(pending, takeDelta)));
|
|
122
|
+
controller.close();
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
pending += decoder.decode(value, { stream: true });
|
|
126
|
+
const nl = pending.lastIndexOf("\n");
|
|
127
|
+
if (nl < 0)
|
|
128
|
+
continue;
|
|
129
|
+
const complete = pending.slice(0, nl + 1);
|
|
130
|
+
pending = pending.slice(nl + 1);
|
|
131
|
+
controller.enqueue(encoder.encode(rewriteSseFragment(complete, takeDelta)));
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
cancel(reason) {
|
|
136
|
+
return reader.cancel(reason);
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
function isChatSseResponse(response) {
|
|
141
|
+
const ctype = (response.headers.get("content-type") ?? "").toLowerCase();
|
|
142
|
+
if (ctype.includes("event-stream"))
|
|
143
|
+
return true;
|
|
144
|
+
if (!ctype || ctype.startsWith("text/plain"))
|
|
145
|
+
return true;
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
export function foldReasoningInChatResponse(response) {
|
|
149
|
+
if (!response.body || response.status >= 400)
|
|
150
|
+
return response;
|
|
151
|
+
if (!isChatSseResponse(response))
|
|
152
|
+
return response;
|
|
153
|
+
return new Response(transformReasoningSse(response.body), {
|
|
154
|
+
status: response.status,
|
|
155
|
+
statusText: response.statusText,
|
|
156
|
+
headers: response.headers,
|
|
157
|
+
});
|
|
158
|
+
}
|
package/dist/sandbox-cli.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { takeScopeFlags } from "./
|
|
1
|
+
import { takeScopeFlags, scopeLabel } from "./scope.js";
|
|
2
2
|
import { getSandboxSnapshot, setSandboxConfig } from "./config.js";
|
|
3
3
|
import { parseNetworkPolicy, parseSandboxMode, getEffectiveSandboxPolicy, sandboxEnforcementCaveat, sandboxModeLabel, sandboxStatusLabel, setSandboxOverride, } from "./sandbox.js";
|
|
4
4
|
export const SANDBOX_CLI_USAGE = [
|
|
@@ -17,9 +17,6 @@ function sourceLabel(source) {
|
|
|
17
17
|
? "global"
|
|
18
18
|
: "default";
|
|
19
19
|
}
|
|
20
|
-
function scopeLabel(scope) {
|
|
21
|
-
return scope === "project" ? "project" : "global";
|
|
22
|
-
}
|
|
23
20
|
export function runSandboxCli(input) {
|
|
24
21
|
const { scope: posScope, rest } = takeScopeFlags(input.positionals);
|
|
25
22
|
const scope = posScope ?? input.scope ?? "global";
|
package/dist/scope.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export function readScope(value) {
|
|
2
|
+
if (value === undefined || value === null || value === "")
|
|
3
|
+
return { ok: true, scope: undefined };
|
|
4
|
+
if (value === "global" || value === "project")
|
|
5
|
+
return { ok: true, scope: value };
|
|
6
|
+
return { ok: false };
|
|
7
|
+
}
|
|
8
|
+
export function takeScopeFlags(tokens) {
|
|
9
|
+
let scope;
|
|
10
|
+
const rest = [];
|
|
11
|
+
for (const t of tokens) {
|
|
12
|
+
if (t === "--project")
|
|
13
|
+
scope = "project";
|
|
14
|
+
else if (t === "--global")
|
|
15
|
+
scope = "global";
|
|
16
|
+
else
|
|
17
|
+
rest.push(t);
|
|
18
|
+
}
|
|
19
|
+
return { scope, rest };
|
|
20
|
+
}
|
|
21
|
+
export function scopeLabel(scope) {
|
|
22
|
+
return scope === "project" ? "project" : "global";
|
|
23
|
+
}
|
package/dist/serve/common.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { randomUUID } from "crypto";
|
|
2
|
-
import { getSandboxSnapshot, getPermissionSnapshot, permissionModeLabel } from "../config.js";
|
|
2
|
+
import { getSandboxSnapshot, getPermissionSnapshot, permissionModeLabel, parseThinkingEffort, parseMemoryMode, } from "../config.js";
|
|
3
3
|
import { parseNetworkPolicy, parseSandboxMode, runWithSandboxPolicy } from "../sandbox.js";
|
|
4
4
|
import { runWithConfirmContext, getPermissionOverride } from "../confirm.js";
|
|
5
|
+
import { thinkingPayload } from "../thinking.js";
|
|
6
|
+
import { memoryPayload } from "../memory.js";
|
|
5
7
|
import { runOnceWithSystem } from "../agent.js";
|
|
6
8
|
import { saveSession, sanitizeForStorage } from "../sessions.js";
|
|
7
9
|
import { createSaveThrottle } from "../save-throttle.js";
|
|
@@ -266,6 +268,23 @@ export function permissionPayload() {
|
|
|
266
268
|
label: permissionModeLabel(permission),
|
|
267
269
|
};
|
|
268
270
|
}
|
|
271
|
+
export { thinkingPayload, memoryPayload };
|
|
272
|
+
export function readThinking(body) {
|
|
273
|
+
if (body.thinking === undefined)
|
|
274
|
+
return { ok: true };
|
|
275
|
+
const thinking = parseThinkingEffort(body.thinking);
|
|
276
|
+
if (!thinking)
|
|
277
|
+
return { ok: false, error: "invalid_thinking", detail: "thinking must be off, low, medium, high, or max" };
|
|
278
|
+
return { ok: true, thinking };
|
|
279
|
+
}
|
|
280
|
+
export function readMemory(body) {
|
|
281
|
+
if (body.memory === undefined)
|
|
282
|
+
return { ok: true };
|
|
283
|
+
const memory = parseMemoryMode(body.memory);
|
|
284
|
+
if (!memory)
|
|
285
|
+
return { ok: false, error: "invalid_memory", detail: "memory must be on or off" };
|
|
286
|
+
return { ok: true, memory };
|
|
287
|
+
}
|
|
269
288
|
export function normalizeMessages(body) {
|
|
270
289
|
if (body.messages && Array.isArray(body.messages)) {
|
|
271
290
|
if (body.messages.length === 0)
|
|
@@ -282,6 +301,8 @@ export function chatRunOptions(body) {
|
|
|
282
301
|
temperature: typeof body.temperature === "number" ? body.temperature : undefined,
|
|
283
302
|
maxTokens: typeof body.maxTokens === "number" ? body.maxTokens : undefined,
|
|
284
303
|
topP: typeof body.topP === "number" ? body.topP : undefined,
|
|
304
|
+
thinking: parseThinkingEffort(body.thinking),
|
|
305
|
+
memory: parseMemoryMode(body.memory),
|
|
285
306
|
providerName: typeof body.provider === "string" ? body.provider : undefined,
|
|
286
307
|
...(body.plan_mode === true ? { planMode: true } : {}),
|
|
287
308
|
};
|
|
@@ -5,7 +5,7 @@ import { lastUserText } from "../tui/hydrate.js";
|
|
|
5
5
|
import { compactMessages } from "../compaction.js";
|
|
6
6
|
import { resolveModel } from "../provider.js";
|
|
7
7
|
import { emptyTaskState } from "../tools/todo.js";
|
|
8
|
-
import { MAX_CONCURRENT_CHATS, sendJson, sendJsonError, readJsonBody, readSandboxTighten, normalizeMessages, chatRunOptions, executeChat, } from "./common.js";
|
|
8
|
+
import { MAX_CONCURRENT_CHATS, sendJson, sendJsonError, readJsonBody, readSandboxTighten, readThinking, readMemory, normalizeMessages, chatRunOptions, executeChat, } from "./common.js";
|
|
9
9
|
/** Shared handler for /v1/chat and /v1/code (streaming + JSON). */
|
|
10
10
|
async function handleChatRequest(ctx, req, res) {
|
|
11
11
|
if (req.headers["content-type"]?.split(";")[0]?.trim() !== "application/json") {
|
|
@@ -33,6 +33,16 @@ async function handleChatRequest(ctx, req, res) {
|
|
|
33
33
|
return;
|
|
34
34
|
}
|
|
35
35
|
const sandboxTighten = sandboxParsed.policy;
|
|
36
|
+
const thinkingParsed = readThinking(body);
|
|
37
|
+
if (!thinkingParsed.ok) {
|
|
38
|
+
sendJson(res, 400, { error: thinkingParsed.error, detail: thinkingParsed.detail });
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const memoryParsed = readMemory(body);
|
|
42
|
+
if (!memoryParsed.ok) {
|
|
43
|
+
sendJson(res, 400, { error: memoryParsed.error, detail: memoryParsed.detail });
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
36
46
|
const modelId = typeof body.model === "string" ? body.model : undefined;
|
|
37
47
|
const stream = body.stream === true;
|
|
38
48
|
const sessionId = typeof body.session_id === "string" ? body.session_id : undefined;
|
|
@@ -132,6 +142,11 @@ export const handleChatRoutes = async (req, res, ctx, _url, pathname) => {
|
|
|
132
142
|
sendJson(res, 400, { error: sandboxParsed.error, detail: sandboxParsed.detail });
|
|
133
143
|
return true;
|
|
134
144
|
}
|
|
145
|
+
const thinkingParsed = readThinking(body);
|
|
146
|
+
if (!thinkingParsed.ok) {
|
|
147
|
+
sendJson(res, 400, { error: thinkingParsed.error, detail: thinkingParsed.detail });
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
135
150
|
if (ctx.activeChats >= MAX_CONCURRENT_CHATS) {
|
|
136
151
|
sendJson(res, 429, {
|
|
137
152
|
error: "too_many_concurrent_requests",
|
|
@@ -242,6 +257,11 @@ export const handleChatRoutes = async (req, res, ctx, _url, pathname) => {
|
|
|
242
257
|
sendJson(res, 400, { error: sandboxParsed.error, detail: sandboxParsed.detail });
|
|
243
258
|
return true;
|
|
244
259
|
}
|
|
260
|
+
const thinkingParsed = readThinking(body);
|
|
261
|
+
if (!thinkingParsed.ok) {
|
|
262
|
+
sendJson(res, 400, { error: thinkingParsed.error, detail: thinkingParsed.detail });
|
|
263
|
+
return true;
|
|
264
|
+
}
|
|
245
265
|
const imageBuffer = Buffer.from(body.image_base64, "base64");
|
|
246
266
|
const mimeType = body.mime_type ?? "image/png";
|
|
247
267
|
const text = body.message ?? "What's in this image?";
|
|
@@ -1,8 +1,37 @@
|
|
|
1
|
-
import { loadMemories, addMemory, deleteMemory, searchMemories, readMemoryScope } from "../memory.js";
|
|
1
|
+
import { loadMemories, addMemory, deleteMemory, searchMemories, readMemoryScope, parseMemoryMode, setMemoryMode, memoryPayload, } from "../memory.js";
|
|
2
2
|
import { sendJson, sendJsonError, readJsonBody } from "./common.js";
|
|
3
3
|
export const handleMemoryRoutes = async (req, res, _ctx, url, pathname) => {
|
|
4
|
+
if (req.method === "GET" && pathname === "/v1/memory/mode") {
|
|
5
|
+
sendJson(res, 200, memoryPayload());
|
|
6
|
+
return true;
|
|
7
|
+
}
|
|
8
|
+
if (req.method === "POST" && pathname === "/v1/memory/mode") {
|
|
9
|
+
const parsed = await readJsonBody(req);
|
|
10
|
+
if (!parsed.ok) {
|
|
11
|
+
sendJsonError(res, parsed);
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
const mode = parseMemoryMode(parsed.value.memory);
|
|
15
|
+
if (!mode) {
|
|
16
|
+
sendJson(res, 400, { error: "invalid_memory", detail: "memory must be on or off" });
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
const scopeParsed = readMemoryScope(parsed.value.scope);
|
|
20
|
+
if (!scopeParsed.ok) {
|
|
21
|
+
sendJson(res, 400, { error: "invalid_scope", allowed: ["global", "project"] });
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
const scope = scopeParsed.scope ?? "global";
|
|
25
|
+
setMemoryMode(mode, scope);
|
|
26
|
+
sendJson(res, 200, { ok: true, scope, ...memoryPayload() });
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
4
29
|
if (req.method === "GET" && pathname === "/v1/memory") {
|
|
5
|
-
sendJson(res, 200, {
|
|
30
|
+
sendJson(res, 200, {
|
|
31
|
+
...memoryPayload(),
|
|
32
|
+
memories: loadMemories("global"),
|
|
33
|
+
project_memories: loadMemories("project"),
|
|
34
|
+
});
|
|
6
35
|
return true;
|
|
7
36
|
}
|
|
8
37
|
if (req.method === "POST" && pathname === "/v1/memory") {
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import { fetchModels, getActiveProvider, getEffectiveConfig, getBudgetSnapshot, setBudgetMaxCost, setSandboxConfig, setPermissionMode, parsePermissionMode, } from "../config.js";
|
|
1
|
+
import { fetchModels, getActiveProvider, getEffectiveConfig, getBudgetSnapshot, setBudgetMaxCost, setSandboxConfig, setPermissionMode, parsePermissionMode, parseThinkingEffort, } from "../config.js";
|
|
2
|
+
import { setThinkingEffort } from "../thinking.js";
|
|
2
3
|
import { parseNetworkPolicy, parseSandboxMode } from "../sandbox.js";
|
|
3
|
-
import {
|
|
4
|
+
import { readScope } from "../scope.js";
|
|
4
5
|
import { getWorkingTreeDiff } from "../tui/diff-view.js";
|
|
5
6
|
import { scanProject } from "../code-mode.js";
|
|
6
7
|
import { getContextWindowInfo } from "../context-window.js";
|
|
7
8
|
import { getModelPrice, estimateCost } from "../pricing.js";
|
|
8
9
|
import { checkForUpdate } from "../updater.js";
|
|
9
|
-
import { sendJson, sendJsonError, readJsonBody, readStringPathList, sandboxPayload, permissionPayload, } from "./common.js";
|
|
10
|
+
import { sendJson, sendJsonError, readJsonBody, readStringPathList, sandboxPayload, permissionPayload, thinkingPayload, memoryPayload, } from "./common.js";
|
|
10
11
|
export const handleMetaRoutes = async (req, res, ctx, url, pathname) => {
|
|
11
12
|
if (req.method === "GET" && pathname === "/health") {
|
|
12
13
|
sendJson(res, 200, { ok: true, service: "min-agent", version: ctx.version });
|
|
@@ -19,6 +20,8 @@ export const handleMetaRoutes = async (req, res, ctx, url, pathname) => {
|
|
|
19
20
|
instructions_chars: ctx.instructions.join("\n").length,
|
|
20
21
|
sandbox: sandboxPayload(),
|
|
21
22
|
permission: permissionPayload(),
|
|
23
|
+
thinking: thinkingPayload(),
|
|
24
|
+
memory: memoryPayload(),
|
|
22
25
|
});
|
|
23
26
|
return true;
|
|
24
27
|
}
|
|
@@ -64,7 +67,7 @@ export const handleMetaRoutes = async (req, res, ctx, url, pathname) => {
|
|
|
64
67
|
sendJson(res, 400, { error: "invalid_max_cost", detail: "maxCostUSD must be a positive number" });
|
|
65
68
|
return true;
|
|
66
69
|
}
|
|
67
|
-
const scopeParsed =
|
|
70
|
+
const scopeParsed = readScope(parsed.value.scope);
|
|
68
71
|
if (!scopeParsed.ok) {
|
|
69
72
|
sendJson(res, 400, { error: "invalid_scope", allowed: ["global", "project"] });
|
|
70
73
|
return true;
|
|
@@ -94,7 +97,7 @@ export const handleMetaRoutes = async (req, res, ctx, url, pathname) => {
|
|
|
94
97
|
sendJson(res, 400, { error: "invalid_permission", detail: "permission must be ask, accept-edits, or allow-all" });
|
|
95
98
|
return true;
|
|
96
99
|
}
|
|
97
|
-
const scopeParsed =
|
|
100
|
+
const scopeParsed = readScope(parsed.value.scope);
|
|
98
101
|
if (!scopeParsed.ok) {
|
|
99
102
|
sendJson(res, 400, { error: "invalid_scope", allowed: ["global", "project"] });
|
|
100
103
|
return true;
|
|
@@ -104,6 +107,31 @@ export const handleMetaRoutes = async (req, res, ctx, url, pathname) => {
|
|
|
104
107
|
sendJson(res, 200, { ok: true, scope, ...permissionPayload() });
|
|
105
108
|
return true;
|
|
106
109
|
}
|
|
110
|
+
if (req.method === "GET" && pathname === "/v1/thinking") {
|
|
111
|
+
sendJson(res, 200, thinkingPayload());
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
if (req.method === "POST" && pathname === "/v1/thinking") {
|
|
115
|
+
const parsed = await readJsonBody(req);
|
|
116
|
+
if (!parsed.ok) {
|
|
117
|
+
sendJsonError(res, parsed);
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
const effort = parseThinkingEffort(parsed.value.thinking);
|
|
121
|
+
if (!effort) {
|
|
122
|
+
sendJson(res, 400, { error: "invalid_thinking", detail: "thinking must be off, low, medium, high, or max" });
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
const scopeParsed = readScope(parsed.value.scope);
|
|
126
|
+
if (!scopeParsed.ok) {
|
|
127
|
+
sendJson(res, 400, { error: "invalid_scope", allowed: ["global", "project"] });
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
const scope = scopeParsed.scope ?? "global";
|
|
131
|
+
setThinkingEffort(effort, scope);
|
|
132
|
+
sendJson(res, 200, { ok: true, scope, ...thinkingPayload() });
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
107
135
|
if (req.method === "POST" && pathname === "/v1/sandbox") {
|
|
108
136
|
const parsed = await readJsonBody(req);
|
|
109
137
|
if (!parsed.ok) {
|
|
@@ -134,7 +162,7 @@ export const handleMetaRoutes = async (req, res, ctx, url, pathname) => {
|
|
|
134
162
|
sendJson(res, 400, { error: "invalid_body", detail: "Provide mode, network, extraWriteRoots, or extraReadRoots" });
|
|
135
163
|
return true;
|
|
136
164
|
}
|
|
137
|
-
const scopeParsed =
|
|
165
|
+
const scopeParsed = readScope(body.scope);
|
|
138
166
|
if (!scopeParsed.ok) {
|
|
139
167
|
sendJson(res, 400, { error: "invalid_scope", allowed: ["global", "project"] });
|
|
140
168
|
return true;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { takeScopeFlags, scopeLabel } from "./scope.js";
|
|
2
|
+
import { parseThinkingEffort, resolveThinkingEffort, setThinkingEffort, setThinkingOverride, thinkingEffortLabel, } from "./thinking.js";
|
|
3
|
+
export const THINK_CLI_USAGE = [
|
|
4
|
+
"Usage: min-agent think [off|low|medium|high|max] [--project|--global]",
|
|
5
|
+
" min-agent --think off|low|medium|high|max [--project|--global]",
|
|
6
|
+
].join("\n");
|
|
7
|
+
function sourceLabel(source) {
|
|
8
|
+
return source === "cli" ? "this run" : source === "project" ? "project" : source === "global" ? "global" : "default";
|
|
9
|
+
}
|
|
10
|
+
export function runThinkCli(input) {
|
|
11
|
+
const { scope: posScope, rest } = takeScopeFlags(input.positionals);
|
|
12
|
+
const scope = posScope ?? input.scope ?? "global";
|
|
13
|
+
if (rest[0] === "--help" || rest[0] === "-h") {
|
|
14
|
+
return { ok: true, lines: [THINK_CLI_USAGE] };
|
|
15
|
+
}
|
|
16
|
+
if (rest.length === 1) {
|
|
17
|
+
const parsed = parseThinkingEffort(rest[0]);
|
|
18
|
+
if (!parsed)
|
|
19
|
+
return { ok: false, lines: [THINK_CLI_USAGE] };
|
|
20
|
+
setThinkingEffort(parsed, scope);
|
|
21
|
+
setThinkingOverride(parsed);
|
|
22
|
+
return { ok: true, lines: [`✓ Thinking set to ${thinkingEffortLabel(parsed)} (${scopeLabel(scope)})`] };
|
|
23
|
+
}
|
|
24
|
+
if (rest.length > 1)
|
|
25
|
+
return { ok: false, lines: [THINK_CLI_USAGE] };
|
|
26
|
+
if (input.flagEffort) {
|
|
27
|
+
setThinkingEffort(input.flagEffort, scope);
|
|
28
|
+
setThinkingOverride(input.flagEffort);
|
|
29
|
+
return { ok: true, lines: [`✓ Thinking set to ${thinkingEffortLabel(input.flagEffort)} (${scopeLabel(scope)})`] };
|
|
30
|
+
}
|
|
31
|
+
const { thinking, source } = resolveThinkingEffort();
|
|
32
|
+
return {
|
|
33
|
+
ok: true,
|
|
34
|
+
lines: [`Current thinking: ${thinkingEffortLabel(thinking)} (${sourceLabel(source)})`, THINK_CLI_USAGE],
|
|
35
|
+
};
|
|
36
|
+
}
|