min-agent 0.5.1 → 0.6.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.
Files changed (169) hide show
  1. package/README.md +18 -18
  2. package/dist/cli.js +538 -12
  3. package/docs/API.md +6 -6
  4. package/package.json +6 -6
  5. package/skills/self-config/SKILL.md +4 -2
  6. package/skills/self-config/reference.md +4 -1
  7. package/dist/agent.js +0 -1388
  8. package/dist/assistant-stream.js +0 -131
  9. package/dist/cli/commands/chat.js +0 -13
  10. package/dist/cli/commands/ctx.js +0 -7
  11. package/dist/cli/commands/exec.js +0 -35
  12. package/dist/cli/commands/history.js +0 -58
  13. package/dist/cli/commands/index.js +0 -249
  14. package/dist/cli/commands/init.js +0 -18
  15. package/dist/cli/commands/mcp.js +0 -173
  16. package/dist/cli/commands/memory.js +0 -87
  17. package/dist/cli/commands/models.js +0 -21
  18. package/dist/cli/commands/permission.js +0 -12
  19. package/dist/cli/commands/rules.js +0 -33
  20. package/dist/cli/commands/sandbox.js +0 -13
  21. package/dist/cli/commands/serve.js +0 -9
  22. package/dist/cli/commands/setup.js +0 -56
  23. package/dist/cli/commands/shared.js +0 -25
  24. package/dist/cli/commands/skills.js +0 -119
  25. package/dist/cli/commands/think.js +0 -12
  26. package/dist/cli/commands/update.js +0 -7
  27. package/dist/cli/commands/write-config.js +0 -52
  28. package/dist/cli/errors.js +0 -36
  29. package/dist/cli/exec-prompt.js +0 -26
  30. package/dist/cli/option-helpers.js +0 -65
  31. package/dist/cli/program.js +0 -223
  32. package/dist/cli/setup/detect.js +0 -17
  33. package/dist/cli/setup/flags.js +0 -12
  34. package/dist/cli/setup/flow.js +0 -108
  35. package/dist/cli/setup/provider-form.js +0 -102
  36. package/dist/cli/setup/ui.js +0 -534
  37. package/dist/clipboard.js +0 -142
  38. package/dist/code-mode.js +0 -184
  39. package/dist/compaction.js +0 -644
  40. package/dist/config.js +0 -363
  41. package/dist/confirm.js +0 -172
  42. package/dist/context-window.js +0 -256
  43. package/dist/ctx-cli.js +0 -30
  44. package/dist/ctx.js +0 -80
  45. package/dist/doom-loop.js +0 -278
  46. package/dist/fetch-timeout.js +0 -152
  47. package/dist/http-approvals.js +0 -60
  48. package/dist/http.js +0 -119
  49. package/dist/instructions.js +0 -196
  50. package/dist/logger.js +0 -95
  51. package/dist/markdown.js +0 -259
  52. package/dist/mcp.js +0 -1036
  53. package/dist/memory-cli.js +0 -33
  54. package/dist/memory.js +0 -295
  55. package/dist/model-catalog.js +0 -285
  56. package/dist/ollama-model.js +0 -234
  57. package/dist/ollama-openai-bridge.js +0 -383
  58. package/dist/output.js +0 -76
  59. package/dist/paste-handler.js +0 -41
  60. package/dist/permission-cli.js +0 -40
  61. package/dist/plugins.js +0 -131
  62. package/dist/pricing.js +0 -119
  63. package/dist/provider.js +0 -63
  64. package/dist/question-format.js +0 -60
  65. package/dist/reasoning-stream.js +0 -158
  66. package/dist/sandbox-cli.js +0 -79
  67. package/dist/sandbox.js +0 -403
  68. package/dist/save-throttle.js +0 -45
  69. package/dist/scope.js +0 -23
  70. package/dist/serve/common.js +0 -425
  71. package/dist/serve/routes-chat.js +0 -367
  72. package/dist/serve/routes-mcp.js +0 -212
  73. package/dist/serve/routes-memory.js +0 -95
  74. package/dist/serve/routes-meta.js +0 -268
  75. package/dist/serve/routes-sessions.js +0 -61
  76. package/dist/serve/routes-skills.js +0 -70
  77. package/dist/serve.js +0 -138
  78. package/dist/sessions.js +0 -265
  79. package/dist/skills.js +0 -599
  80. package/dist/structured-output.js +0 -29
  81. package/dist/synthetic.js +0 -7
  82. package/dist/think-cli.js +0 -36
  83. package/dist/thinking-wire.js +0 -239
  84. package/dist/thinking.js +0 -166
  85. package/dist/title-gen.js +0 -55
  86. package/dist/token-display.js +0 -39
  87. package/dist/tool-display.js +0 -178
  88. package/dist/tool-output.js +0 -126
  89. package/dist/tools/apply_patch.js +0 -265
  90. package/dist/tools/atomic-file.js +0 -35
  91. package/dist/tools/backend.js +0 -61
  92. package/dist/tools/bash.js +0 -231
  93. package/dist/tools/code_search.js +0 -98
  94. package/dist/tools/edit.js +0 -68
  95. package/dist/tools/explore.js +0 -232
  96. package/dist/tools/glob.js +0 -40
  97. package/dist/tools/grep.js +0 -174
  98. package/dist/tools/index.js +0 -34
  99. package/dist/tools/question.js +0 -54
  100. package/dist/tools/read.js +0 -109
  101. package/dist/tools/search-searxng.js +0 -223
  102. package/dist/tools/search-serper.js +0 -189
  103. package/dist/tools/task.js +0 -165
  104. package/dist/tools/todo.js +0 -213
  105. package/dist/tools/web_fetch.js +0 -195
  106. package/dist/tools/web_search.js +0 -228
  107. package/dist/tools/write.js +0 -55
  108. package/dist/tui/App.js +0 -138
  109. package/dist/tui/ConfirmBar.js +0 -66
  110. package/dist/tui/CtxPicker.js +0 -68
  111. package/dist/tui/InputBar.js +0 -618
  112. package/dist/tui/MessageList.js +0 -718
  113. package/dist/tui/ModelPicker.js +0 -113
  114. package/dist/tui/QuestionBar.js +0 -136
  115. package/dist/tui/SessionPicker.js +0 -79
  116. package/dist/tui/Spinner.js +0 -6
  117. package/dist/tui/StatusBar.js +0 -22
  118. package/dist/tui/ThinkPicker.js +0 -75
  119. package/dist/tui/agent-runner.js +0 -223
  120. package/dist/tui/bracketed-paste.js +0 -37
  121. package/dist/tui/caret-pos.js +0 -179
  122. package/dist/tui/caret.js +0 -69
  123. package/dist/tui/click-count.js +0 -13
  124. package/dist/tui/diff-view.js +0 -61
  125. package/dist/tui/drag-state.js +0 -49
  126. package/dist/tui/hydrate.js +0 -129
  127. package/dist/tui/index.js +0 -264
  128. package/dist/tui/input-history.js +0 -125
  129. package/dist/tui/layout.js +0 -105
  130. package/dist/tui/mouse.js +0 -46
  131. package/dist/tui/overlay-input.js +0 -12
  132. package/dist/tui/paste-draft.js +0 -173
  133. package/dist/tui/prompt-queue.js +0 -24
  134. package/dist/tui/selection.js +0 -232
  135. package/dist/tui/session-switch.js +0 -28
  136. package/dist/tui/slash-commands.js +0 -129
  137. package/dist/tui/slash-handler.js +0 -615
  138. package/dist/tui/text-width.js +0 -113
  139. package/dist/tui/theme.js +0 -12
  140. package/dist/tui/token-info.js +0 -7
  141. package/dist/tui/tool-children.js +0 -19
  142. package/dist/tui/types.js +0 -1
  143. package/dist/tui/undo-stack.js +0 -14
  144. package/dist/tui/use-sgr-mouse.js +0 -29
  145. package/dist/tui-chat.js +0 -478
  146. package/dist/updater.js +0 -116
  147. package/dist/xml-search.js +0 -194
  148. package/docs/superpowers/plans/2026-08-16-batch1-tui-improvements.md +0 -1510
  149. package/docs/superpowers/plans/2026-08-16-batch2-cli-tools-api.md +0 -2105
  150. package/docs/superpowers/plans/2026-08-16-batch3-config-engineering.md +0 -1595
  151. package/docs/superpowers/plans/2026-08-16-input-caret.md +0 -782
  152. package/docs/superpowers/plans/2026-08-20-tui-completeness.md +0 -873
  153. package/docs/superpowers/plans/2026-08-20-unified-tui-default.md +0 -631
  154. package/docs/superpowers/plans/2026-08-23-cli-setup.md +0 -501
  155. package/docs/superpowers/plans/2026-08-23-input-paste-attachments.md +0 -475
  156. package/docs/superpowers/plans/2026-08-23-thinking-wire-profile.md +0 -450
  157. package/docs/superpowers/specs/2026-08-16-batch1-tui-improvements-design.md +0 -183
  158. package/docs/superpowers/specs/2026-08-16-batch2-cli-tools-api-design.md +0 -220
  159. package/docs/superpowers/specs/2026-08-16-batch3-config-engineering-design.md +0 -196
  160. package/docs/superpowers/specs/2026-08-16-input-caret-design.md +0 -63
  161. package/docs/superpowers/specs/2026-08-17-mouse-selection-design.md +0 -116
  162. package/docs/superpowers/specs/2026-08-20-config-http-alignment-design.md +0 -47
  163. package/docs/superpowers/specs/2026-08-20-mcp-plugins-alignment-design.md +0 -37
  164. package/docs/superpowers/specs/2026-08-20-sandbox-permissions-design.md +0 -68
  165. package/docs/superpowers/specs/2026-08-20-tui-completeness-design.md +0 -273
  166. package/docs/superpowers/specs/2026-08-20-unified-tui-default-design.md +0 -165
  167. package/docs/superpowers/specs/2026-08-23-cli-setup-design.md +0 -282
  168. package/docs/superpowers/specs/2026-08-23-input-paste-attachments-design.md +0 -174
  169. package/docs/superpowers/specs/2026-08-23-thinking-wire-profile-design.md +0 -140
@@ -1,234 +0,0 @@
1
- import { readFileSync, mkdirSync, existsSync } from "fs";
2
- import path from "path";
3
- import { getActiveProvider, getConfigDir, getEffectiveConfig } from "./config.js";
4
- import { atomicWriteFileSync } from "./tools/atomic-file.js";
5
- const OLLAMA_ENTRY_VERSION = 2;
6
- const CACHE_TTL = 7 * 24 * 60 * 60 * 1000;
7
- const NEGATIVE_TTL = 60 * 60 * 1000;
8
- const FETCH_TIMEOUT_MS = 8000;
9
- const OLLAMA_MIN_CONTEXT = 512;
10
- /** Native Ollama default when /api/show has no num_ctx and no architecture length. */
11
- export const OLLAMA_DEFAULT_CONTEXT_WINDOW = 2048;
12
- /** Architecture max is not the loaded window; cap what we request/display for agents. */
13
- export const OLLAMA_AGENT_CONTEXT_CAP = 32_768;
14
- const memoryCache = new Map();
15
- const inFlight = new Map();
16
- const negativeCache = new Map();
17
- function isRecord(value) {
18
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
19
- }
20
- export function ollamaNativeBase(baseURL) {
21
- return baseURL.replace(/\/$/, "").replace(/\/v1$/i, "");
22
- }
23
- export function ollamaModelCacheKey(baseURL, modelId) {
24
- let host = "";
25
- try {
26
- host = new URL(baseURL).hostname.toLowerCase();
27
- }
28
- catch {
29
- host = ollamaNativeBase(baseURL).toLowerCase();
30
- }
31
- const model = modelId.trim().toLowerCase();
32
- return host ? `${host}::${model}` : model;
33
- }
34
- function asContextTokens(value) {
35
- const n = typeof value === "string" && value.trim() !== "" ? Number(value) : value;
36
- if (typeof n !== "number" || !Number.isFinite(n) || n < OLLAMA_MIN_CONTEXT)
37
- return undefined;
38
- return Math.floor(n);
39
- }
40
- function contextFromModelInfo(info) {
41
- const found = [];
42
- for (const [key, value] of Object.entries(info)) {
43
- if (key === "context_length" || key.endsWith(".context_length")) {
44
- const n = asContextTokens(value);
45
- if (n != null)
46
- found.push(n);
47
- }
48
- }
49
- if (found.length === 0)
50
- return undefined;
51
- return Math.max(...found);
52
- }
53
- function numCtxFromParameters(raw) {
54
- if (typeof raw === "number")
55
- return asContextTokens(raw);
56
- if (isRecord(raw))
57
- return asContextTokens(raw.num_ctx);
58
- if (typeof raw !== "string")
59
- return undefined;
60
- const match = raw.match(/(?:^|\n)\s*num_ctx\s+(\d+)/);
61
- if (match?.[1])
62
- return asContextTokens(Number(match[1]));
63
- return asContextTokens(raw);
64
- }
65
- function thinkingFromCapabilities(raw) {
66
- if (!Array.isArray(raw))
67
- return null;
68
- return raw.some((item) => String(item).toLowerCase() === "thinking");
69
- }
70
- export function ollamaOperationalContext(numCtx, architecture) {
71
- if (numCtx != null)
72
- return numCtx;
73
- if (architecture != null)
74
- return Math.min(architecture, OLLAMA_AGENT_CONTEXT_CAP);
75
- return undefined;
76
- }
77
- export function applyOllamaChatOptions(body, numCtx) {
78
- const prev = isRecord(body.options) ? body.options : {};
79
- return {
80
- ...body,
81
- options: { ...prev, num_ctx: numCtx },
82
- };
83
- }
84
- export function ollamaThinkValue(spec, modelThinking) {
85
- if (modelThinking === false)
86
- return false;
87
- if (!spec)
88
- return undefined;
89
- if (!spec.thinkingEnabled)
90
- return false;
91
- const effort = spec.reasoningEffort;
92
- if (effort === "low" || effort === "medium" || effort === "high")
93
- return effort;
94
- if (effort === "none")
95
- return false;
96
- return "high";
97
- }
98
- export function resolveOllamaNumCtx(modelId, requestUrl) {
99
- const provider = getActiveProvider(getEffectiveConfig());
100
- if (typeof provider?.contextWindow === "number" &&
101
- Number.isFinite(provider.contextWindow) &&
102
- provider.contextWindow >= OLLAMA_MIN_CONTEXT) {
103
- return Math.floor(provider.contextWindow);
104
- }
105
- const cached = getCachedOllamaModel(modelId, requestUrl ?? provider?.baseURL);
106
- return cached?.contextWindow ?? OLLAMA_DEFAULT_CONTEXT_WINDOW;
107
- }
108
- export function parseOllamaShow(payload, modelId) {
109
- const root = isRecord(payload) ? payload : {};
110
- const modelInfo = isRecord(root.model_info) ? root.model_info : {};
111
- const contextWindow = ollamaOperationalContext(numCtxFromParameters(root.parameters), contextFromModelInfo(modelInfo));
112
- return {
113
- version: OLLAMA_ENTRY_VERSION,
114
- modelId,
115
- ...(contextWindow != null ? { contextWindow } : {}),
116
- thinking: thinkingFromCapabilities(root.capabilities),
117
- timestamp: Date.now(),
118
- };
119
- }
120
- export function thinkingChoicesFromOllama(info) {
121
- if (info.thinking === false)
122
- return ["off"];
123
- return ["off", "low", "medium", "high", "max"];
124
- }
125
- function cacheFile() {
126
- return path.join(getConfigDir(), "ollama-model-cache.json");
127
- }
128
- function loadDiskCache() {
129
- const file = cacheFile();
130
- if (!existsSync(file))
131
- return {};
132
- try {
133
- const parsed = JSON.parse(readFileSync(file, "utf-8"));
134
- if (!isRecord(parsed))
135
- return {};
136
- const out = {};
137
- for (const [key, value] of Object.entries(parsed)) {
138
- if (!isRecord(value) || value.version !== OLLAMA_ENTRY_VERSION)
139
- continue;
140
- if (typeof value.modelId !== "string")
141
- continue;
142
- const thinking = value.thinking === true || value.thinking === false ? value.thinking : null;
143
- const contextWindow = asContextTokens(value.contextWindow);
144
- const timestamp = typeof value.timestamp === "number" ? value.timestamp : 0;
145
- out[key] = {
146
- version: OLLAMA_ENTRY_VERSION,
147
- modelId: value.modelId,
148
- ...(contextWindow != null ? { contextWindow } : {}),
149
- thinking,
150
- timestamp,
151
- };
152
- }
153
- return out;
154
- }
155
- catch {
156
- return {};
157
- }
158
- }
159
- function saveDiskCache(cache) {
160
- const file = cacheFile();
161
- mkdirSync(path.dirname(file), { recursive: true });
162
- atomicWriteFileSync(file, JSON.stringify(cache));
163
- }
164
- export function clearOllamaModelCache() {
165
- memoryCache.clear();
166
- inFlight.clear();
167
- negativeCache.clear();
168
- }
169
- export function getCachedOllamaModel(modelId, baseURL) {
170
- const key = ollamaModelCacheKey(baseURL ?? "", modelId);
171
- const memory = memoryCache.get(key);
172
- if (memory && memory.version === OLLAMA_ENTRY_VERSION && Date.now() - memory.timestamp <= CACHE_TTL)
173
- return memory;
174
- const disk = loadDiskCache()[key];
175
- if (!disk || disk.version !== OLLAMA_ENTRY_VERSION || Date.now() - disk.timestamp > CACHE_TTL)
176
- return null;
177
- memoryCache.set(key, disk);
178
- return disk;
179
- }
180
- function setCache(key, entry) {
181
- memoryCache.set(key, entry);
182
- const cache = loadDiskCache();
183
- cache[key] = entry;
184
- saveDiskCache(cache);
185
- }
186
- async function fetchShow(baseURL, modelId) {
187
- try {
188
- const response = await fetch(`${ollamaNativeBase(baseURL)}/api/show`, {
189
- method: "POST",
190
- headers: { "Content-Type": "application/json" },
191
- body: JSON.stringify({ name: modelId }),
192
- signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
193
- });
194
- if (!response.ok)
195
- return null;
196
- return await response.json();
197
- }
198
- catch {
199
- return null;
200
- }
201
- }
202
- /**
203
- * Load context window and thinking capability from a user-configured Ollama
204
- * provider via native POST /api/show. Cached 7 days after a successful lookup.
205
- */
206
- export async function getOllamaModel(modelId, baseURL) {
207
- const url = baseURL ?? "";
208
- const key = ollamaModelCacheKey(url, modelId);
209
- const cached = getCachedOllamaModel(modelId, url);
210
- if (cached)
211
- return cached;
212
- const negAt = negativeCache.get(key);
213
- if (negAt !== undefined && Date.now() - negAt <= NEGATIVE_TTL)
214
- return null;
215
- const pending = inFlight.get(key);
216
- if (pending)
217
- return pending;
218
- const probing = fetchShow(url, modelId).then((payload) => {
219
- if (!payload) {
220
- negativeCache.set(key, Date.now());
221
- return null;
222
- }
223
- const entry = parseOllamaShow(payload, modelId);
224
- setCache(key, entry);
225
- return entry;
226
- });
227
- inFlight.set(key, probing);
228
- try {
229
- return await probing;
230
- }
231
- finally {
232
- inFlight.delete(key);
233
- }
234
- }
@@ -1,383 +0,0 @@
1
- import { applyOllamaChatOptions, getCachedOllamaModel, ollamaNativeBase, ollamaThinkValue, resolveOllamaNumCtx, } from "./ollama-model.js";
2
- function isRecord(value) {
3
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
4
- }
5
- export function requestHref(input) {
6
- return typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
7
- }
8
- export function isOllamaChatCompletionsUrl(href) {
9
- try {
10
- return /\/chat\/completions\/?$/i.test(new URL(href).pathname);
11
- }
12
- catch {
13
- return /\/chat\/completions\/?$/i.test(href);
14
- }
15
- }
16
- export function ollamaNativeChatUrl(href) {
17
- const stripped = href.replace(/\/v1\/chat\/completions\/?$/i, "").replace(/\/chat\/completions\/?$/i, "");
18
- return `${ollamaNativeBase(stripped)}/api/chat`;
19
- }
20
- export async function readFetchBody(body) {
21
- if (body == null)
22
- return null;
23
- if (typeof body === "string")
24
- return body;
25
- if (body instanceof Uint8Array)
26
- return new TextDecoder().decode(body);
27
- if (body instanceof ArrayBuffer)
28
- return new TextDecoder().decode(body);
29
- if (Buffer?.isBuffer(body))
30
- return body.toString("utf8");
31
- if (typeof Blob !== "undefined" && body instanceof Blob)
32
- return await body.text();
33
- if (typeof ReadableStream !== "undefined" && body instanceof ReadableStream) {
34
- const reader = body.getReader();
35
- const chunks = [];
36
- for (;;) {
37
- const { done, value } = await reader.read();
38
- if (done)
39
- break;
40
- if (value)
41
- chunks.push(value);
42
- }
43
- const total = chunks.reduce((n, c) => n + c.length, 0);
44
- const out = new Uint8Array(total);
45
- let offset = 0;
46
- for (const chunk of chunks) {
47
- out.set(chunk, offset);
48
- offset += chunk.length;
49
- }
50
- return new TextDecoder().decode(out);
51
- }
52
- return null;
53
- }
54
- function dataUrlBase64(url) {
55
- const match = url.match(/^data:[^;]+;base64,(.+)$/s);
56
- return match?.[1] ?? null;
57
- }
58
- function textFromContent(content) {
59
- if (typeof content === "string")
60
- return { text: content, images: [] };
61
- if (!Array.isArray(content))
62
- return { text: content == null ? "" : JSON.stringify(content), images: [] };
63
- const texts = [];
64
- const images = [];
65
- for (const part of content) {
66
- if (!isRecord(part))
67
- continue;
68
- if (part.type === "text" && typeof part.text === "string")
69
- texts.push(part.text);
70
- else if (part.type === "image_url") {
71
- const image = isRecord(part.image_url) ? part.image_url.url : undefined;
72
- if (typeof image === "string") {
73
- const b64 = dataUrlBase64(image);
74
- if (b64)
75
- images.push(b64);
76
- }
77
- }
78
- else if (typeof part.text === "string")
79
- texts.push(part.text);
80
- }
81
- return { text: texts.join("\n"), images };
82
- }
83
- function argsAsObject(raw) {
84
- if (isRecord(raw))
85
- return raw;
86
- if (typeof raw !== "string" || !raw.trim())
87
- return {};
88
- try {
89
- const parsed = JSON.parse(raw);
90
- return isRecord(parsed) ? parsed : { value: parsed };
91
- }
92
- catch {
93
- return { value: raw };
94
- }
95
- }
96
- function argsAsString(raw) {
97
- if (typeof raw === "string")
98
- return raw;
99
- try {
100
- return JSON.stringify(raw ?? {});
101
- }
102
- catch {
103
- return "{}";
104
- }
105
- }
106
- function convertToolCallsOut(raw) {
107
- if (!Array.isArray(raw) || raw.length === 0)
108
- return undefined;
109
- return raw.map((item) => {
110
- const rec = isRecord(item) ? item : {};
111
- const fn = isRecord(rec.function) ? rec.function : rec;
112
- return {
113
- function: {
114
- name: typeof fn.name === "string" ? fn.name : "",
115
- arguments: argsAsObject(fn.arguments),
116
- },
117
- };
118
- });
119
- }
120
- export function openaiMessagesToOllama(messages) {
121
- if (!Array.isArray(messages))
122
- return [];
123
- return messages.map((item) => {
124
- const rec = isRecord(item) ? item : {};
125
- const { text, images } = textFromContent(rec.content);
126
- const out = {
127
- role: typeof rec.role === "string" ? rec.role : "user",
128
- content: text,
129
- };
130
- if (images.length > 0)
131
- out.images = images;
132
- const toolCalls = convertToolCallsOut(rec.tool_calls);
133
- if (toolCalls)
134
- out.tool_calls = toolCalls;
135
- return out;
136
- });
137
- }
138
- function copySampling(body) {
139
- const options = isRecord(body.options) ? { ...body.options } : {};
140
- if (typeof body.temperature === "number")
141
- options.temperature = body.temperature;
142
- if (typeof body.top_p === "number")
143
- options.top_p = body.top_p;
144
- if (typeof body.seed === "number")
145
- options.seed = body.seed;
146
- if (typeof body.max_tokens === "number")
147
- options.num_predict = body.max_tokens;
148
- if (typeof body.max_completion_tokens === "number")
149
- options.num_predict = body.max_completion_tokens;
150
- if (Array.isArray(body.stop) || typeof body.stop === "string")
151
- options.stop = body.stop;
152
- return options;
153
- }
154
- export function openaiChatToOllamaNative(body, opts) {
155
- const native = {
156
- model: body.model,
157
- messages: openaiMessagesToOllama(body.messages),
158
- stream: body.stream !== false,
159
- options: copySampling(body),
160
- };
161
- if (Array.isArray(body.tools) && body.tools.length > 0)
162
- native.tools = body.tools;
163
- if (body.format != null)
164
- native.format = body.format;
165
- if (opts.think !== undefined)
166
- native.think = opts.think;
167
- return applyOllamaChatOptions(native, opts.numCtx);
168
- }
169
- function finishReasonFor(doneReason, toolCalls) {
170
- if (Array.isArray(toolCalls) && toolCalls.length > 0)
171
- return "tool_calls";
172
- if (doneReason === "length" || doneReason === "max_tokens")
173
- return "length";
174
- return "stop";
175
- }
176
- function usageFromNative(row) {
177
- const prompt = typeof row.prompt_eval_count === "number" ? row.prompt_eval_count : undefined;
178
- const completion = typeof row.eval_count === "number" ? row.eval_count : undefined;
179
- if (prompt == null && completion == null)
180
- return undefined;
181
- const promptTokens = prompt ?? 0;
182
- const completionTokens = completion ?? 0;
183
- return {
184
- prompt_tokens: promptTokens,
185
- completion_tokens: completionTokens,
186
- total_tokens: promptTokens + completionTokens,
187
- };
188
- }
189
- function openaiToolCallDeltas(raw) {
190
- if (!Array.isArray(raw) || raw.length === 0)
191
- return undefined;
192
- return raw.map((item, index) => {
193
- const rec = isRecord(item) ? item : {};
194
- const fn = isRecord(rec.function) ? rec.function : rec;
195
- const id = typeof rec.id === "string" && rec.id ? rec.id : `ollama_call_${index}`;
196
- return {
197
- index,
198
- id,
199
- type: "function",
200
- function: {
201
- name: typeof fn.name === "string" ? fn.name : "",
202
- arguments: argsAsString(fn.arguments),
203
- },
204
- };
205
- });
206
- }
207
- export function nativeChatRowToOpenAIChunk(row, state) {
208
- const message = isRecord(row.message) ? row.message : {};
209
- const content = typeof message.content === "string" ? message.content : "";
210
- const thinking = typeof message.thinking === "string" ? message.thinking : "";
211
- const toolCalls = openaiToolCallDeltas(message.tool_calls);
212
- const done = row.done === true;
213
- const delta = {};
214
- if (!state.sentRole) {
215
- delta.role = "assistant";
216
- state.sentRole = true;
217
- }
218
- if (thinking)
219
- delta.reasoning = thinking;
220
- if (content)
221
- delta.content = content;
222
- if (toolCalls)
223
- delta.tool_calls = toolCalls;
224
- const chunk = {
225
- id: "chatcmpl-ollama",
226
- object: "chat.completion.chunk",
227
- created: Math.floor(Date.now() / 1000),
228
- model: typeof row.model === "string" ? row.model : "",
229
- choices: [
230
- {
231
- index: 0,
232
- delta,
233
- finish_reason: done ? finishReasonFor(row.done_reason, message.tool_calls) : null,
234
- },
235
- ],
236
- };
237
- const usage = usageFromNative(row);
238
- if (usage)
239
- chunk.usage = usage;
240
- return chunk;
241
- }
242
- export function nativeChatToOpenAIResponse(row) {
243
- const message = isRecord(row.message) ? row.message : {};
244
- const content = typeof message.content === "string" ? message.content : "";
245
- const thinking = typeof message.thinking === "string" ? message.thinking : "";
246
- const toolCalls = openaiToolCallDeltas(message.tool_calls);
247
- const assistant = { role: "assistant", content };
248
- if (thinking)
249
- assistant.reasoning = thinking;
250
- if (toolCalls)
251
- assistant.tool_calls = toolCalls;
252
- const out = {
253
- id: "chatcmpl-ollama",
254
- object: "chat.completion",
255
- created: Math.floor(Date.now() / 1000),
256
- model: typeof row.model === "string" ? row.model : "",
257
- choices: [
258
- {
259
- index: 0,
260
- message: assistant,
261
- finish_reason: finishReasonFor(row.done_reason, message.tool_calls),
262
- },
263
- ],
264
- };
265
- const usage = usageFromNative(row);
266
- if (usage)
267
- out.usage = usage;
268
- return out;
269
- }
270
- function sseLine(payload) {
271
- return `data: ${JSON.stringify(payload)}\n\n`;
272
- }
273
- export function transformOllamaNdjsonToSse(source) {
274
- const decoder = new TextDecoder();
275
- const encoder = new TextEncoder();
276
- const reader = source.getReader();
277
- const state = { sentRole: false };
278
- let pending = "";
279
- return new ReadableStream({
280
- async pull(controller) {
281
- for (;;) {
282
- const { done, value } = await reader.read();
283
- if (done) {
284
- pending += decoder.decode();
285
- const leftover = pending.trim();
286
- if (leftover) {
287
- try {
288
- const parsed = JSON.parse(leftover);
289
- if (isRecord(parsed))
290
- controller.enqueue(encoder.encode(sseLine(nativeChatRowToOpenAIChunk(parsed, state))));
291
- }
292
- catch {
293
- /* ignore trailing garbage */
294
- }
295
- }
296
- controller.enqueue(encoder.encode("data: [DONE]\n\n"));
297
- controller.close();
298
- return;
299
- }
300
- pending += decoder.decode(value, { stream: true });
301
- const nl = pending.lastIndexOf("\n");
302
- if (nl < 0)
303
- continue;
304
- const complete = pending.slice(0, nl + 1);
305
- pending = pending.slice(nl + 1);
306
- const out = [];
307
- for (const line of complete.split("\n")) {
308
- const trimmed = line.trim();
309
- if (!trimmed)
310
- continue;
311
- try {
312
- const parsed = JSON.parse(trimmed);
313
- if (isRecord(parsed))
314
- out.push(sseLine(nativeChatRowToOpenAIChunk(parsed, state)));
315
- }
316
- catch {
317
- /* skip malformed native lines */
318
- }
319
- }
320
- if (out.length > 0) {
321
- controller.enqueue(encoder.encode(out.join("")));
322
- return;
323
- }
324
- }
325
- },
326
- cancel(reason) {
327
- return reader.cancel(reason);
328
- },
329
- });
330
- }
331
- function jsonHeaders(headers) {
332
- const next = new Headers(headers);
333
- next.set("content-type", "application/json");
334
- return next;
335
- }
336
- function sseHeaders(headers) {
337
- const next = new Headers(headers);
338
- next.set("content-type", "text/event-stream");
339
- next.delete("content-length");
340
- return next;
341
- }
342
- export async function fetchOllamaNativeChat(inner, input, init, body, spec) {
343
- const href = requestHref(input);
344
- if (!isOllamaChatCompletionsUrl(href)) {
345
- return inner(input, { ...init, body: JSON.stringify(body) });
346
- }
347
- const modelId = typeof body.model === "string" ? body.model : "";
348
- const cached = getCachedOllamaModel(modelId, href);
349
- const nativeBody = openaiChatToOllamaNative(body, {
350
- numCtx: resolveOllamaNumCtx(modelId, href),
351
- think: ollamaThinkValue(spec, cached?.thinking ?? null),
352
- });
353
- const response = await inner(ollamaNativeChatUrl(href), {
354
- ...init,
355
- method: init.method ?? "POST",
356
- headers: { ...Object.fromEntries(new Headers(init.headers).entries()), "Content-Type": "application/json" },
357
- body: JSON.stringify(nativeBody),
358
- });
359
- if (response.status >= 400)
360
- return response;
361
- const streaming = body.stream !== false;
362
- if (!streaming) {
363
- try {
364
- const payload = await response.json();
365
- const converted = isRecord(payload) ? nativeChatToOpenAIResponse(payload) : nativeChatToOpenAIResponse({});
366
- return new Response(JSON.stringify(converted), {
367
- status: response.status,
368
- statusText: response.statusText,
369
- headers: jsonHeaders(response.headers),
370
- });
371
- }
372
- catch {
373
- return response;
374
- }
375
- }
376
- if (!response.body)
377
- return response;
378
- return new Response(transformOllamaNdjsonToSse(response.body), {
379
- status: response.status,
380
- statusText: response.statusText,
381
- headers: sseHeaders(response.headers),
382
- });
383
- }
package/dist/output.js DELETED
@@ -1,76 +0,0 @@
1
- import { loadConfig, getActiveProvider } from "./config.js";
2
- import { summarizeToolCall, toolResultText, truncateDisplay } from "./tool-display.js";
3
- const COLORS = {
4
- reset: "\x1b[0m",
5
- dim: "\x1b[2m",
6
- bold: "\x1b[1m",
7
- cyan: "\x1b[36m",
8
- green: "\x1b[32m",
9
- yellow: "\x1b[33m",
10
- red: "\x1b[31m",
11
- magenta: "\x1b[35m",
12
- gray: "\x1b[90m",
13
- };
14
- export function printHeader(modelId) {
15
- const model = modelId ?? getActiveProvider(loadConfig())?.defaultModel ?? "unknown";
16
- console.log(`${COLORS.bold}🤖 min-agent${COLORS.reset} ${COLORS.dim}(${model})${COLORS.reset}`);
17
- }
18
- export function printDivider() {
19
- console.log(`${COLORS.dim}${"─".repeat(60)}${COLORS.reset}`);
20
- }
21
- export function printToolCall(name, input) {
22
- const argsStr = summarizeToolCall(name, input, 160);
23
- console.log(`\n${COLORS.yellow}⚡ ${name}${COLORS.reset} ${COLORS.dim}${argsStr}${COLORS.reset}`);
24
- }
25
- /** Console preview limits for a tool result (the full text goes to the model). */
26
- const RESULT_PREVIEW_MAX_LINES = 20;
27
- const RESULT_PREVIEW_MAX_CHARS = 1200;
28
- /**
29
- * Print a tool result preview. Lines are kept whole and both limits are
30
- * applied in one pass, so the "N more lines" count always matches what was
31
- * actually withheld.
32
- */
33
- export function printToolResult(_name, result, isError = false) {
34
- const text = toolResultText(result).replace(/\s+$/, "");
35
- const lines = text === "" ? [] : text.split("\n");
36
- const kept = [];
37
- let chars = 0;
38
- for (const line of lines) {
39
- if (kept.length >= RESULT_PREVIEW_MAX_LINES)
40
- break;
41
- if (chars + line.length > RESULT_PREVIEW_MAX_CHARS)
42
- break;
43
- kept.push(line);
44
- chars += line.length + 1;
45
- }
46
- // A single very long first line still deserves a preview.
47
- if (kept.length === 0 && lines.length > 0) {
48
- kept.push(truncateDisplay(lines[0], RESULT_PREVIEW_MAX_CHARS));
49
- }
50
- const hidden = lines.length - kept.length;
51
- const suffix = hidden > 0 ? ` (${hidden} more line${hidden === 1 ? "" : "s"})` : "";
52
- const marker = isError ? `${COLORS.red} ✗${COLORS.reset}` : `${COLORS.green} ✓${COLORS.reset}`;
53
- const body = kept.join(`\n${COLORS.dim} `);
54
- console.log(`${marker} ${COLORS.dim}${body}${suffix}${COLORS.reset}\n`);
55
- }
56
- export function printDone(steps, usage, contextWindow, contextTokens) {
57
- const input = usage?.inputTokens ?? 0;
58
- const output = usage?.outputTokens ?? 0;
59
- const total = input + output;
60
- const occupied = contextTokens !== undefined ? contextTokens : input;
61
- let contextInfo = "";
62
- if (occupied > 0 && contextWindow && contextWindow > 0) {
63
- const pct = Math.round((occupied / contextWindow) * 100);
64
- const bar = renderBar(pct);
65
- contextInfo = ` | Context: ${bar} ${pct}%`;
66
- }
67
- console.log(`${COLORS.dim}Done in ${steps} step(s) | Tokens: ${input} in / ${output} out / ${total} total${contextInfo}${COLORS.reset}`);
68
- }
69
- function renderBar(pct) {
70
- const width = 10;
71
- const safePct = Math.max(0, Math.min(100, pct));
72
- const filled = Math.round((safePct / 100) * width);
73
- const empty = width - filled;
74
- const color = pct >= 80 ? "\x1b[31m" : pct >= 50 ? "\x1b[33m" : "\x1b[32m";
75
- return `${color}${"█".repeat(filled)}${"░".repeat(empty)}\x1b[0m\x1b[90m`;
76
- }