mini-coder 0.5.12 → 0.5.13
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/BENCHMARK.md +15 -316
- package/PROGRESS.md +1 -2
- package/README.md +53 -21
- package/benchmark-baseline.sh +15 -0
- package/bun.lock +265 -90
- package/package.json +8 -7
- package/skills-lock.json +15 -0
- package/src/agent.ts +20 -0
- package/src/cli.ts +2 -1
- package/src/headless.ts +97 -24
- package/src/index.ts +78 -93
- package/src/input.ts +13 -1
- package/src/mcp.ts +609 -0
- package/src/prompt.ts +2 -12
- package/src/settings.ts +199 -7
- package/src/skills.ts +12 -3
- package/src/submit.ts +17 -0
- package/src/theme.ts +186 -3
- package/src/tool-common.ts +2 -0
- package/src/tool-shell.ts +138 -6
- package/src/tools.ts +4 -0
- package/src/ui/agent.ts +7 -0
- package/src/ui/commands.test.ts +475 -4
- package/src/ui/commands.ts +210 -8
- package/src/ui/conversation.test.ts +252 -27
- package/src/ui/conversation.ts +468 -390
- package/src/ui/help.ts +27 -11
- package/src/ui.ts +230 -75
- package/src/plugins.ts +0 -183
package/src/settings.ts
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* User settings persistence and startup resolution.
|
|
3
3
|
*
|
|
4
4
|
* Stores global defaults such as model, effort, reasoning visibility,
|
|
5
|
-
*
|
|
5
|
+
* verbose tool output, custom providers, and MCP server settings in a JSON
|
|
6
|
+
* file under the app data directory.
|
|
6
7
|
*
|
|
7
8
|
* @module
|
|
8
9
|
*/
|
|
@@ -23,6 +24,22 @@ export interface CustomProvider {
|
|
|
23
24
|
apiKey?: string;
|
|
24
25
|
}
|
|
25
26
|
|
|
27
|
+
/** A single configured MCP server endpoint. */
|
|
28
|
+
export interface McpServerConfig {
|
|
29
|
+
/** Stable server identifier. Used as the imported tool-name prefix. */
|
|
30
|
+
name: string;
|
|
31
|
+
/** Absolute Streamable HTTP MCP endpoint URL. */
|
|
32
|
+
url: string;
|
|
33
|
+
/** Whether the server should start enabled. */
|
|
34
|
+
enabled: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** MCP-related user settings. */
|
|
38
|
+
export interface McpSettings {
|
|
39
|
+
/** MCP servers to connect to at startup. */
|
|
40
|
+
servers?: McpServerConfig[];
|
|
41
|
+
}
|
|
42
|
+
|
|
26
43
|
/** Default reasoning effort when no saved setting exists. */
|
|
27
44
|
const DEFAULT_EFFORT: ThinkingLevel = "medium";
|
|
28
45
|
|
|
@@ -44,6 +61,8 @@ export interface UserSettings {
|
|
|
44
61
|
verbose?: boolean;
|
|
45
62
|
/** Custom OpenAI-compatible provider endpoints. */
|
|
46
63
|
customProviders?: CustomProvider[];
|
|
64
|
+
/** MCP server definitions to manage and connect when enabled. */
|
|
65
|
+
mcp?: McpSettings;
|
|
47
66
|
}
|
|
48
67
|
|
|
49
68
|
/** Resolved startup settings after applying defaults and availability checks. */
|
|
@@ -65,6 +84,8 @@ const THINKING_LEVELS = new Set<ThinkingLevel>([
|
|
|
65
84
|
"xhigh",
|
|
66
85
|
]);
|
|
67
86
|
|
|
87
|
+
const MCP_SERVER_NAME_PATTERN = /^[A-Za-z0-9_-]+$/;
|
|
88
|
+
|
|
68
89
|
/**
|
|
69
90
|
* Load and validate user settings from disk.
|
|
70
91
|
*
|
|
@@ -80,13 +101,81 @@ export function loadSettings(path: string): UserSettings {
|
|
|
80
101
|
}
|
|
81
102
|
|
|
82
103
|
try {
|
|
83
|
-
|
|
84
|
-
return sanitizeSettings(raw);
|
|
104
|
+
return parseSettingsFile(path);
|
|
85
105
|
} catch (error) {
|
|
86
|
-
throw
|
|
87
|
-
|
|
88
|
-
|
|
106
|
+
throw createSettingsReadError(path, error);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Load settings for startup without aborting on invalid JSON.
|
|
112
|
+
*
|
|
113
|
+
* Missing files and invalid JSON content are treated as empty settings so
|
|
114
|
+
* startup behaves like there are no saved settings. Other filesystem errors
|
|
115
|
+
* still fail with the same descriptive read error as {@link loadSettings}.
|
|
116
|
+
*
|
|
117
|
+
* @param path - Absolute path to `settings.json`.
|
|
118
|
+
* @returns The validated settings object, or `{}` when startup should ignore invalid JSON.
|
|
119
|
+
*/
|
|
120
|
+
export function loadStartupSettings(path: string): UserSettings {
|
|
121
|
+
if (!existsSync(path)) {
|
|
122
|
+
return {};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
return parseSettingsFile(path);
|
|
127
|
+
} catch (error) {
|
|
128
|
+
if (error instanceof SyntaxError) {
|
|
129
|
+
return {};
|
|
130
|
+
}
|
|
131
|
+
throw createSettingsReadError(path, error);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Merge two settings objects using startup overlay semantics.
|
|
137
|
+
*
|
|
138
|
+
* Scalar fields use override-wins. `customProviders` merges by provider name,
|
|
139
|
+
* and `mcp.servers` merges by server name. Same-name override entries replace
|
|
140
|
+
* base entries while keeping the base ordering stable; new override entries are
|
|
141
|
+
* appended in override order.
|
|
142
|
+
*
|
|
143
|
+
* @param base - Base settings, usually the global settings file.
|
|
144
|
+
* @param override - Higher-priority settings, usually a repo-local overlay.
|
|
145
|
+
* @returns The merged effective settings.
|
|
146
|
+
*/
|
|
147
|
+
export function mergeUserSettings(
|
|
148
|
+
base: UserSettings,
|
|
149
|
+
override: UserSettings,
|
|
150
|
+
): UserSettings {
|
|
151
|
+
const sanitizedBase = sanitizeSettings(base);
|
|
152
|
+
const sanitizedOverride = sanitizeSettings(override);
|
|
153
|
+
const merged: UserSettings = {
|
|
154
|
+
...sanitizedBase,
|
|
155
|
+
...sanitizedOverride,
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const customProviders = mergeNamedEntries(
|
|
159
|
+
sanitizedBase.customProviders,
|
|
160
|
+
sanitizedOverride.customProviders,
|
|
161
|
+
);
|
|
162
|
+
if (customProviders) {
|
|
163
|
+
merged.customProviders = customProviders;
|
|
164
|
+
} else {
|
|
165
|
+
delete merged.customProviders;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const servers = mergeNamedEntries(
|
|
169
|
+
sanitizedBase.mcp?.servers,
|
|
170
|
+
sanitizedOverride.mcp?.servers,
|
|
171
|
+
);
|
|
172
|
+
if (servers) {
|
|
173
|
+
merged.mcp = { servers };
|
|
174
|
+
} else {
|
|
175
|
+
delete merged.mcp;
|
|
89
176
|
}
|
|
177
|
+
|
|
178
|
+
return merged;
|
|
90
179
|
}
|
|
91
180
|
|
|
92
181
|
/**
|
|
@@ -123,7 +212,7 @@ export function updateSettings(
|
|
|
123
212
|
update: Partial<UserSettings>,
|
|
124
213
|
): UserSettings {
|
|
125
214
|
const current = loadSettings(path);
|
|
126
|
-
const merged =
|
|
215
|
+
const merged = mergeUserSettings(current, sanitizeSettings(update));
|
|
127
216
|
return saveSettings(path, merged);
|
|
128
217
|
}
|
|
129
218
|
|
|
@@ -156,6 +245,46 @@ export function resolveStartupSettings(
|
|
|
156
245
|
};
|
|
157
246
|
}
|
|
158
247
|
|
|
248
|
+
function mergeNamedEntries<T extends { name: string }>(
|
|
249
|
+
base: readonly T[] | undefined,
|
|
250
|
+
override: readonly T[] | undefined,
|
|
251
|
+
): T[] | undefined {
|
|
252
|
+
if (!base?.length && !override?.length) {
|
|
253
|
+
return undefined;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const merged = [...(base ?? [])];
|
|
257
|
+
const indexes = new Map<string, number>();
|
|
258
|
+
|
|
259
|
+
for (const [index, entry] of merged.entries()) {
|
|
260
|
+
indexes.set(entry.name, index);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
for (const entry of override ?? []) {
|
|
264
|
+
const existingIndex = indexes.get(entry.name);
|
|
265
|
+
if (existingIndex === undefined) {
|
|
266
|
+
indexes.set(entry.name, merged.length);
|
|
267
|
+
merged.push(entry);
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
merged[existingIndex] = entry;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return merged.length > 0 ? merged : undefined;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function parseSettingsFile(path: string): UserSettings {
|
|
278
|
+
const raw = JSON.parse(readFileSync(path, "utf-8")) as unknown;
|
|
279
|
+
return sanitizeSettings(raw);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function createSettingsReadError(path: string, error: unknown): Error {
|
|
283
|
+
return new Error(
|
|
284
|
+
`Failed to read settings ${path}: ${getErrorMessage(error)}`,
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
|
|
159
288
|
/**
|
|
160
289
|
* Validate and normalize a parsed settings object.
|
|
161
290
|
*
|
|
@@ -193,6 +322,11 @@ function sanitizeSettings(value: unknown): UserSettings {
|
|
|
193
322
|
settings.customProviders = customProviders;
|
|
194
323
|
}
|
|
195
324
|
|
|
325
|
+
const mcp = sanitizeMcpSettings(candidate.mcp);
|
|
326
|
+
if (mcp) {
|
|
327
|
+
settings.mcp = mcp;
|
|
328
|
+
}
|
|
329
|
+
|
|
196
330
|
return settings;
|
|
197
331
|
}
|
|
198
332
|
|
|
@@ -244,6 +378,64 @@ function sanitizeCustomProviders(value: unknown): CustomProvider[] | undefined {
|
|
|
244
378
|
return result.length > 0 ? result : undefined;
|
|
245
379
|
}
|
|
246
380
|
|
|
381
|
+
function sanitizeMcpSettings(value: unknown): McpSettings | undefined {
|
|
382
|
+
const candidate = toRecord(value);
|
|
383
|
+
if (!candidate) {
|
|
384
|
+
return undefined;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const servers = sanitizeMcpServers(candidate.servers);
|
|
388
|
+
if (!servers) {
|
|
389
|
+
return undefined;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return { servers };
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/** Try to parse a single MCP server entry, returning null on failure. */
|
|
396
|
+
function parseMcpServer(item: unknown): McpServerConfig | null {
|
|
397
|
+
const candidate = toRecord(item);
|
|
398
|
+
if (!candidate) {
|
|
399
|
+
return null;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const name = readString(candidate, "name")?.trim() ?? "";
|
|
403
|
+
const url = readString(candidate, "url")?.trim() ?? "";
|
|
404
|
+
const enabled = readBoolean(candidate, "enabled") ?? true;
|
|
405
|
+
|
|
406
|
+
if (!name || !url || !MCP_SERVER_NAME_PATTERN.test(name)) {
|
|
407
|
+
return null;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
return { name, url, enabled };
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Validate and normalize configured MCP servers.
|
|
415
|
+
*
|
|
416
|
+
* Drops entries with missing/invalid names or URLs, and deduplicates by name
|
|
417
|
+
* (first entry wins).
|
|
418
|
+
*/
|
|
419
|
+
function sanitizeMcpServers(value: unknown): McpServerConfig[] | undefined {
|
|
420
|
+
if (!Array.isArray(value)) {
|
|
421
|
+
return undefined;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const result: McpServerConfig[] = [];
|
|
425
|
+
const seen = new Set<string>();
|
|
426
|
+
|
|
427
|
+
for (const item of value) {
|
|
428
|
+
const entry = parseMcpServer(item);
|
|
429
|
+
if (!entry || seen.has(entry.name)) {
|
|
430
|
+
continue;
|
|
431
|
+
}
|
|
432
|
+
seen.add(entry.name);
|
|
433
|
+
result.push(entry);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
return result.length > 0 ? result : undefined;
|
|
437
|
+
}
|
|
438
|
+
|
|
247
439
|
/**
|
|
248
440
|
* Check whether a value is a valid thinking level.
|
|
249
441
|
*
|
package/src/skills.ts
CHANGED
|
@@ -226,6 +226,15 @@ export function discoverSkills(scanPaths: string[]): Skill[] {
|
|
|
226
226
|
// Catalog generation
|
|
227
227
|
// ---------------------------------------------------------------------------
|
|
228
228
|
|
|
229
|
+
function escapeXml(value: string): string {
|
|
230
|
+
return value
|
|
231
|
+
.replaceAll("&", "&")
|
|
232
|
+
.replaceAll("<", "<")
|
|
233
|
+
.replaceAll(">", ">")
|
|
234
|
+
.replaceAll('"', """)
|
|
235
|
+
.replaceAll("'", "'");
|
|
236
|
+
}
|
|
237
|
+
|
|
229
238
|
/**
|
|
230
239
|
* Build the XML skill catalog for the system prompt.
|
|
231
240
|
*
|
|
@@ -243,9 +252,9 @@ export function buildSkillCatalog(skills: Skill[]): string {
|
|
|
243
252
|
.map(
|
|
244
253
|
(s) =>
|
|
245
254
|
` <skill>\n` +
|
|
246
|
-
` <name>${s.name}</name>\n` +
|
|
247
|
-
` <description>${s.description}</description>\n` +
|
|
248
|
-
` <location>${s.path}</location>\n` +
|
|
255
|
+
` <name>${escapeXml(s.name)}</name>\n` +
|
|
256
|
+
` <description>${escapeXml(s.description)}</description>\n` +
|
|
257
|
+
` <location>${escapeXml(s.path)}</location>\n` +
|
|
249
258
|
` </skill>`,
|
|
250
259
|
)
|
|
251
260
|
.join("\n");
|
package/src/submit.ts
CHANGED
|
@@ -215,6 +215,20 @@ function recordRawPromptHistory(
|
|
|
215
215
|
truncatePromptHistory(state.db, MAX_PROMPT_HISTORY);
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
+
/**
|
|
219
|
+
* Drop any resolved steering messages still queued on the active app state.
|
|
220
|
+
*
|
|
221
|
+
* Raw prompt-history rows are intentionally left untouched because queue resets
|
|
222
|
+
* must not rewrite global input-history behavior.
|
|
223
|
+
*
|
|
224
|
+
* @param state - Mutable application state containing the queued steering list.
|
|
225
|
+
*/
|
|
226
|
+
export function clearQueuedUserMessages(
|
|
227
|
+
state: Pick<AppState, "queuedUserMessages">,
|
|
228
|
+
): void {
|
|
229
|
+
state.queuedUserMessages.length = 0;
|
|
230
|
+
}
|
|
231
|
+
|
|
218
232
|
/**
|
|
219
233
|
* Queue resolved user content for the next model-request boundary of an active run.
|
|
220
234
|
*
|
|
@@ -301,6 +315,8 @@ export async function submitResolvedInput(
|
|
|
301
315
|
throw new Error("Cannot submit empty input.");
|
|
302
316
|
}
|
|
303
317
|
|
|
318
|
+
clearQueuedUserMessages(state);
|
|
319
|
+
|
|
304
320
|
const session = ensureSession(state);
|
|
305
321
|
recordRawPromptHistory(rawInput, state, session.id);
|
|
306
322
|
|
|
@@ -347,6 +363,7 @@ export async function submitResolvedInput(
|
|
|
347
363
|
stopReason = result.stopReason;
|
|
348
364
|
return result.stopReason;
|
|
349
365
|
} finally {
|
|
366
|
+
clearQueuedUserMessages(state);
|
|
350
367
|
state.running = false;
|
|
351
368
|
state.abortController = null;
|
|
352
369
|
hooks?.onTurnEnd?.(state, stopReason);
|
package/src/theme.ts
CHANGED
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
* UI theme definition and default colors.
|
|
3
3
|
*
|
|
4
4
|
* All UI colors are read from the active {@link Theme} object — the UI
|
|
5
|
-
* never hardcodes colors.
|
|
6
|
-
* result to override any color. Multiple overrides are merged left-to-right.
|
|
5
|
+
* never hardcodes colors. Theme overrides are merged left-to-right.
|
|
7
6
|
*
|
|
8
7
|
* Theme values are cel-tui {@link Color} palette references. The default
|
|
9
8
|
* theme prefers ANSI 16-color palette entries so it adapts cleanly to the
|
|
@@ -12,11 +11,47 @@
|
|
|
12
11
|
* @module
|
|
13
12
|
*/
|
|
14
13
|
|
|
14
|
+
import type { SyntaxHighlightTheme } from "@cel-tui/components";
|
|
15
15
|
import type { Color } from "@cel-tui/types";
|
|
16
16
|
|
|
17
17
|
/** A cel-tui palette color or the terminal default when undefined. */
|
|
18
18
|
type ThemeColor = Color | undefined;
|
|
19
19
|
|
|
20
|
+
type SyntaxThemeRegistration = Exclude<SyntaxHighlightTheme, string>;
|
|
21
|
+
type SyntaxThemeTokenColor = NonNullable<
|
|
22
|
+
SyntaxThemeRegistration["tokenColors"]
|
|
23
|
+
>[number];
|
|
24
|
+
type SyntaxThemeVariant = "markdown" | "code" | "shell";
|
|
25
|
+
|
|
26
|
+
/** ANSI16 fallback hex values for syntax-highlighter theme overrides. */
|
|
27
|
+
const ANSI_COLOR_HEX: Readonly<Record<Color, string>> = {
|
|
28
|
+
color00: "#000000",
|
|
29
|
+
color01: "#cd3131",
|
|
30
|
+
color02: "#0dbc79",
|
|
31
|
+
color03: "#e5e510",
|
|
32
|
+
color04: "#2472c8",
|
|
33
|
+
color05: "#bc3fbc",
|
|
34
|
+
color06: "#11a8cd",
|
|
35
|
+
color07: "#e5e5e5",
|
|
36
|
+
color08: "#666666",
|
|
37
|
+
color09: "#f14c4c",
|
|
38
|
+
color10: "#23d18b",
|
|
39
|
+
color11: "#f5f543",
|
|
40
|
+
color12: "#3b8eea",
|
|
41
|
+
color13: "#d670d6",
|
|
42
|
+
color14: "#29b8db",
|
|
43
|
+
color15: "#ffffff",
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const syntaxThemeCache: Record<
|
|
47
|
+
SyntaxThemeVariant,
|
|
48
|
+
WeakMap<Theme, SyntaxThemeRegistration>
|
|
49
|
+
> = {
|
|
50
|
+
markdown: new WeakMap(),
|
|
51
|
+
code: new WeakMap(),
|
|
52
|
+
shell: new WeakMap(),
|
|
53
|
+
};
|
|
54
|
+
|
|
20
55
|
// ---------------------------------------------------------------------------
|
|
21
56
|
// Types
|
|
22
57
|
// ---------------------------------------------------------------------------
|
|
@@ -132,7 +167,7 @@ export const DEFAULT_THEME: Theme = {
|
|
|
132
167
|
* mutated.
|
|
133
168
|
*
|
|
134
169
|
* @param base - The base theme to start from.
|
|
135
|
-
* @param overrides - Partial theme objects to merge
|
|
170
|
+
* @param overrides - Partial theme objects to merge.
|
|
136
171
|
* @returns A complete {@link Theme} with all overrides applied.
|
|
137
172
|
*/
|
|
138
173
|
export function mergeThemes(
|
|
@@ -145,3 +180,151 @@ export function mergeThemes(
|
|
|
145
180
|
}
|
|
146
181
|
return merged;
|
|
147
182
|
}
|
|
183
|
+
|
|
184
|
+
function colorToHex(color: ThemeColor): string | undefined {
|
|
185
|
+
return color ? ANSI_COLOR_HEX[color] : undefined;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function pushSyntaxTokenColor(
|
|
189
|
+
tokenColors: SyntaxThemeTokenColor[],
|
|
190
|
+
scope: string | readonly string[],
|
|
191
|
+
foreground: ThemeColor,
|
|
192
|
+
fontStyle?: string,
|
|
193
|
+
): void {
|
|
194
|
+
const foregroundHex = colorToHex(foreground);
|
|
195
|
+
if (!foregroundHex && !fontStyle) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
tokenColors.push({
|
|
200
|
+
scope,
|
|
201
|
+
settings: {
|
|
202
|
+
...(foregroundHex ? { foreground: foregroundHex } : {}),
|
|
203
|
+
...(fontStyle ? { fontStyle } : {}),
|
|
204
|
+
},
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function pushCodeSyntaxTokenColors(
|
|
209
|
+
tokenColors: SyntaxThemeTokenColor[],
|
|
210
|
+
theme: Theme,
|
|
211
|
+
): void {
|
|
212
|
+
pushSyntaxTokenColor(
|
|
213
|
+
tokenColors,
|
|
214
|
+
["comment", "quote", "doctag", "markup.quote"],
|
|
215
|
+
theme.mutedText,
|
|
216
|
+
"italic",
|
|
217
|
+
);
|
|
218
|
+
pushSyntaxTokenColor(
|
|
219
|
+
tokenColors,
|
|
220
|
+
["keyword", "operator"],
|
|
221
|
+
theme.secondaryAccentText,
|
|
222
|
+
);
|
|
223
|
+
pushSyntaxTokenColor(
|
|
224
|
+
tokenColors,
|
|
225
|
+
["command", "function_", "function", "title"],
|
|
226
|
+
theme.accentText,
|
|
227
|
+
);
|
|
228
|
+
pushSyntaxTokenColor(
|
|
229
|
+
tokenColors,
|
|
230
|
+
["builtin", "built_in", "class_", "class", "inherited__", "type"],
|
|
231
|
+
theme.accentText,
|
|
232
|
+
);
|
|
233
|
+
pushSyntaxTokenColor(
|
|
234
|
+
tokenColors,
|
|
235
|
+
["escape", "literal", "number", "symbol"],
|
|
236
|
+
theme.secondaryAccentText ?? theme.accentText,
|
|
237
|
+
);
|
|
238
|
+
pushSyntaxTokenColor(
|
|
239
|
+
tokenColors,
|
|
240
|
+
["code", "string", "markup.code"],
|
|
241
|
+
theme.diffAdded,
|
|
242
|
+
);
|
|
243
|
+
pushSyntaxTokenColor(tokenColors, "regexp", theme.diffRemoved);
|
|
244
|
+
pushSyntaxTokenColor(
|
|
245
|
+
tokenColors,
|
|
246
|
+
["attr", "attribute", "params", "property", "selector-attr"],
|
|
247
|
+
theme.accentText,
|
|
248
|
+
);
|
|
249
|
+
pushSyntaxTokenColor(
|
|
250
|
+
tokenColors,
|
|
251
|
+
[
|
|
252
|
+
"name",
|
|
253
|
+
"tag",
|
|
254
|
+
"selector-class",
|
|
255
|
+
"selector-id",
|
|
256
|
+
"selector-pseudo",
|
|
257
|
+
"selector-tag",
|
|
258
|
+
],
|
|
259
|
+
theme.accentText,
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function pushMarkdownSyntaxTokenColors(
|
|
264
|
+
tokenColors: SyntaxThemeTokenColor[],
|
|
265
|
+
theme: Theme,
|
|
266
|
+
): void {
|
|
267
|
+
pushSyntaxTokenColor(
|
|
268
|
+
tokenColors,
|
|
269
|
+
["quote", "markup.quote"],
|
|
270
|
+
theme.mutedText,
|
|
271
|
+
"italic",
|
|
272
|
+
);
|
|
273
|
+
pushSyntaxTokenColor(
|
|
274
|
+
tokenColors,
|
|
275
|
+
["section", "markup.heading"],
|
|
276
|
+
theme.accentText,
|
|
277
|
+
"bold",
|
|
278
|
+
);
|
|
279
|
+
pushSyntaxTokenColor(
|
|
280
|
+
tokenColors,
|
|
281
|
+
["bullet", "markup.list"],
|
|
282
|
+
theme.secondaryAccentText,
|
|
283
|
+
"bold",
|
|
284
|
+
);
|
|
285
|
+
pushSyntaxTokenColor(
|
|
286
|
+
tokenColors,
|
|
287
|
+
["code", "string", "markup.code"],
|
|
288
|
+
theme.diffAdded,
|
|
289
|
+
);
|
|
290
|
+
pushSyntaxTokenColor(tokenColors, "link", theme.accentText, "underline");
|
|
291
|
+
pushSyntaxTokenColor(tokenColors, "strong", undefined, "bold");
|
|
292
|
+
pushSyntaxTokenColor(tokenColors, "emphasis", undefined, "italic");
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Build the shared syntax-highlight theme registration for a UI theme variant.
|
|
297
|
+
*
|
|
298
|
+
* Markdown, read/code blocks, and shell/tool previews all flow through this
|
|
299
|
+
* helper so active-theme overrides stay consistent in one place. Variant names
|
|
300
|
+
* are included in the registration so cel-tui's internal SyntaxHighlight cache
|
|
301
|
+
* does not collapse code and shell renders onto the same custom-theme key.
|
|
302
|
+
*
|
|
303
|
+
* @param theme - Active UI theme.
|
|
304
|
+
* @param variant - Semantic highlighting variant for the rendered content.
|
|
305
|
+
* @returns A cached cel-tui syntax-highlight theme registration.
|
|
306
|
+
*/
|
|
307
|
+
export function getSyntaxHighlightTheme(
|
|
308
|
+
theme: Theme,
|
|
309
|
+
variant: SyntaxThemeVariant,
|
|
310
|
+
): SyntaxThemeRegistration {
|
|
311
|
+
const cache = syntaxThemeCache[variant];
|
|
312
|
+
const cached = cache.get(theme);
|
|
313
|
+
if (cached) {
|
|
314
|
+
return cached;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const tokenColors: SyntaxThemeTokenColor[] = [];
|
|
318
|
+
if (variant === "markdown") {
|
|
319
|
+
pushMarkdownSyntaxTokenColors(tokenColors, theme);
|
|
320
|
+
} else {
|
|
321
|
+
pushCodeSyntaxTokenColors(tokenColors, theme);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const syntaxTheme: SyntaxThemeRegistration = {
|
|
325
|
+
name: `mini-coder-${variant}`,
|
|
326
|
+
tokenColors,
|
|
327
|
+
};
|
|
328
|
+
cache.set(theme, syntaxTheme);
|
|
329
|
+
return syntaxTheme;
|
|
330
|
+
}
|
package/src/tool-common.ts
CHANGED
|
@@ -23,6 +23,8 @@ import { validateToolArguments } from "@mariozechner/pi-ai";
|
|
|
23
23
|
export interface ToolExecResult {
|
|
24
24
|
/** Content blocks for the tool result. */
|
|
25
25
|
content: (TextContent | ImageContent)[];
|
|
26
|
+
/** Optional structured metadata preserved on the tool-result message. */
|
|
27
|
+
details?: unknown;
|
|
26
28
|
/** Whether the execution encountered an error. */
|
|
27
29
|
isError: boolean;
|
|
28
30
|
}
|