@oh-my-pi/pi-coding-agent 15.10.0 → 15.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +75 -1
- package/dist/types/cli/dry-balance-cli.d.ts +15 -1
- package/dist/types/commit/analysis/conventional.d.ts +2 -2
- package/dist/types/commit/analysis/summary.d.ts +2 -2
- package/dist/types/commit/changelog/generate.d.ts +2 -2
- package/dist/types/commit/changelog/index.d.ts +2 -2
- package/dist/types/commit/map-reduce/index.d.ts +3 -3
- package/dist/types/commit/map-reduce/map-phase.d.ts +2 -2
- package/dist/types/commit/map-reduce/reduce-phase.d.ts +2 -2
- package/dist/types/commit/model-selection.d.ts +10 -4
- package/dist/types/config/api-key-resolver.d.ts +34 -0
- package/dist/types/config/model-registry.d.ts +17 -1
- package/dist/types/config/settings-schema.d.ts +9 -0
- package/dist/types/dap/config.d.ts +14 -1
- package/dist/types/dap/types.d.ts +10 -0
- package/dist/types/lsp/utils.d.ts +3 -2
- package/dist/types/modes/components/chat-block.d.ts +64 -0
- package/dist/types/modes/components/custom-editor.d.ts +3 -0
- package/dist/types/modes/components/overlay-box.d.ts +17 -0
- package/dist/types/modes/components/plan-review-overlay.d.ts +59 -0
- package/dist/types/modes/components/plan-toc.d.ts +41 -0
- package/dist/types/modes/components/read-tool-group.d.ts +2 -0
- package/dist/types/modes/components/transcript-container.d.ts +11 -0
- package/dist/types/modes/controllers/command-controller.d.ts +1 -0
- package/dist/types/modes/controllers/event-controller.d.ts +0 -1
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +0 -1
- package/dist/types/modes/controllers/input-controller.d.ts +1 -1
- package/dist/types/modes/controllers/streaming-reveal.d.ts +22 -0
- package/dist/types/modes/controllers/tan-command-controller.d.ts +6 -0
- package/dist/types/modes/interactive-mode.d.ts +15 -5
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/modes/types.d.ts +18 -5
- package/dist/types/modes/utils/copy-targets.d.ts +21 -1
- package/dist/types/plan-mode/approved-plan.d.ts +27 -8
- package/dist/types/plan-mode/plan-protection.d.ts +4 -4
- package/dist/types/sdk.d.ts +2 -0
- package/dist/types/session/agent-session.d.ts +21 -0
- package/dist/types/session/messages.d.ts +12 -0
- package/dist/types/session/session-manager.d.ts +3 -1
- package/dist/types/slash-commands/types.d.ts +4 -6
- package/dist/types/task/executor.d.ts +7 -0
- package/dist/types/task/index.d.ts +1 -0
- package/dist/types/task/render.d.ts +3 -2
- package/dist/types/tools/archive-reader.d.ts +5 -0
- package/dist/types/tools/ast-edit.d.ts +3 -0
- package/dist/types/tools/ast-grep.d.ts +3 -0
- package/dist/types/tools/bash.d.ts +1 -0
- package/dist/types/tools/find.d.ts +8 -4
- package/dist/types/tools/grouped-file-output.d.ts +95 -12
- package/dist/types/tools/memory-render.d.ts +4 -1
- package/dist/types/tools/plan-mode-guard.d.ts +8 -9
- package/dist/types/tools/render-utils.d.ts +5 -9
- package/dist/types/tools/search.d.ts +4 -0
- package/dist/types/tools/sqlite-reader.d.ts +1 -0
- package/dist/types/tools/todo.d.ts +3 -2
- package/dist/types/tools/write.d.ts +3 -0
- package/dist/types/tui/output-block.d.ts +16 -4
- package/dist/types/tui/status-line.d.ts +3 -0
- package/dist/types/utils/enhanced-paste.d.ts +20 -0
- package/dist/types/web/search/providers/kimi.d.ts +1 -1
- package/package.json +9 -9
- package/src/auto-thinking/classifier.ts +5 -1
- package/src/cli/dry-balance-cli.ts +52 -17
- package/src/cli/gallery-cli.ts +4 -1
- package/src/cli/gallery-fixtures/misc.ts +29 -0
- package/src/commit/analysis/conventional.ts +2 -2
- package/src/commit/analysis/summary.ts +2 -2
- package/src/commit/changelog/generate.ts +2 -2
- package/src/commit/changelog/index.ts +2 -2
- package/src/commit/map-reduce/index.ts +3 -3
- package/src/commit/map-reduce/map-phase.ts +2 -2
- package/src/commit/map-reduce/reduce-phase.ts +2 -2
- package/src/commit/model-selection.ts +33 -9
- package/src/commit/pipeline.ts +4 -4
- package/src/config/api-key-resolver.ts +58 -0
- package/src/config/model-registry.ts +25 -2
- package/src/config/settings-schema.ts +10 -0
- package/src/config/settings.ts +20 -2
- package/src/dap/config.ts +41 -2
- package/src/dap/defaults.json +1 -0
- package/src/dap/session.ts +1 -0
- package/src/dap/types.ts +10 -0
- package/src/debug/index.ts +40 -54
- package/src/edit/renderer.ts +82 -78
- package/src/eval/__tests__/llm-bridge.test.ts +90 -31
- package/src/eval/llm-bridge.ts +8 -3
- package/src/goals/tools/goal-tool.ts +36 -26
- package/src/internal-urls/docs-index.generated.ts +6 -6
- package/src/lsp/utils.ts +3 -2
- package/src/main.ts +9 -7
- package/src/memories/index.ts +12 -5
- package/src/mnemopi/backend.ts +5 -1
- package/src/modes/acp/acp-agent.ts +33 -26
- package/src/modes/components/assistant-message.ts +2 -9
- package/src/modes/components/chat-block.ts +111 -0
- package/src/modes/components/copy-selector.ts +1 -44
- package/src/modes/components/custom-editor.ts +23 -0
- package/src/modes/components/custom-message.ts +1 -3
- package/src/modes/components/execution-shared.ts +1 -2
- package/src/modes/components/hook-message.ts +1 -3
- package/src/modes/components/overlay-box.ts +108 -0
- package/src/modes/components/plan-review-overlay.ts +799 -0
- package/src/modes/components/plan-toc.ts +138 -0
- package/src/modes/components/read-tool-group.ts +20 -4
- package/src/modes/components/skill-message.ts +0 -1
- package/src/modes/components/tips.txt +1 -0
- package/src/modes/components/todo-reminder.ts +0 -2
- package/src/modes/components/tool-execution.ts +68 -88
- package/src/modes/components/transcript-container.ts +84 -24
- package/src/modes/components/user-message.ts +1 -2
- package/src/modes/controllers/command-controller-shared.ts +7 -6
- package/src/modes/controllers/command-controller.ts +57 -55
- package/src/modes/controllers/event-controller.ts +41 -40
- package/src/modes/controllers/extension-ui-controller.ts +10 -73
- package/src/modes/controllers/input-controller.ts +124 -119
- package/src/modes/controllers/mcp-command-controller.ts +69 -60
- package/src/modes/controllers/selector-controller.ts +23 -25
- package/src/modes/controllers/streaming-reveal.ts +212 -0
- package/src/modes/controllers/tan-command-controller.ts +173 -0
- package/src/modes/interactive-mode.ts +169 -94
- package/src/modes/setup-wizard/wizard-overlay.ts +1 -1
- package/src/modes/theme/theme-schema.json +1 -1
- package/src/modes/theme/theme.ts +8 -4
- package/src/modes/types.ts +18 -7
- package/src/modes/utils/copy-targets.ts +133 -27
- package/src/modes/utils/ui-helpers.ts +44 -46
- package/src/plan-mode/approved-plan.ts +66 -43
- package/src/plan-mode/plan-protection.ts +4 -4
- package/src/prompts/system/background-tan-dispatch.md +8 -0
- package/src/prompts/system/plan-mode-active.md +67 -58
- package/src/prompts/system/plan-mode-approved.md +1 -1
- package/src/sdk.ts +11 -37
- package/src/session/agent-session.ts +82 -6
- package/src/session/messages.ts +26 -0
- package/src/session/session-manager.ts +13 -5
- package/src/slash-commands/builtin-registry.ts +36 -9
- package/src/slash-commands/types.ts +4 -6
- package/src/task/executor.ts +5 -2
- package/src/task/index.ts +4 -0
- package/src/task/render.ts +212 -147
- package/src/tools/archive-reader.ts +64 -0
- package/src/tools/ask.ts +119 -164
- package/src/tools/ast-edit.ts +98 -71
- package/src/tools/ast-grep.ts +37 -43
- package/src/tools/bash.ts +50 -6
- package/src/tools/debug.ts +20 -8
- package/src/tools/fetch.ts +297 -7
- package/src/tools/find.ts +44 -30
- package/src/tools/gh-renderer.ts +81 -42
- package/src/tools/grouped-file-output.ts +272 -48
- package/src/tools/image-gen.ts +150 -103
- package/src/tools/inspect-image-renderer.ts +63 -41
- package/src/tools/inspect-image.ts +8 -1
- package/src/tools/job.ts +3 -4
- package/src/tools/memory-render.ts +4 -1
- package/src/tools/plan-mode-guard.ts +21 -39
- package/src/tools/read.ts +23 -16
- package/src/tools/render-utils.ts +21 -37
- package/src/tools/resolve.ts +14 -0
- package/src/tools/search-tool-bm25.ts +36 -23
- package/src/tools/search.ts +80 -78
- package/src/tools/sqlite-reader.ts +9 -12
- package/src/tools/todo.ts +118 -52
- package/src/tools/write.ts +81 -62
- package/src/tui/output-block.ts +60 -13
- package/src/tui/status-line.ts +5 -1
- package/src/utils/commit-message-generator.ts +9 -1
- package/src/utils/enhanced-paste.ts +202 -0
- package/src/utils/title-generator.ts +2 -1
- package/src/web/search/providers/anthropic.ts +25 -19
- package/src/web/search/providers/exa.ts +11 -3
- package/src/web/search/providers/kimi.ts +28 -17
- package/src/web/search/providers/parallel.ts +35 -24
- package/src/web/search/providers/synthetic.ts +8 -6
- package/src/web/search/providers/tavily.ts +9 -8
- package/src/web/search/providers/zai.ts +8 -6
package/src/debug/index.ts
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
} from "@oh-my-pi/pi-tui";
|
|
20
20
|
import { getSessionsDir } from "@oh-my-pi/pi-utils";
|
|
21
21
|
import { DynamicBorder } from "../modes/components/dynamic-border";
|
|
22
|
+
import { TranscriptBlock } from "../modes/components/transcript-container";
|
|
22
23
|
import { getSelectListTheme, getSymbolTheme, theme } from "../modes/theme/theme";
|
|
23
24
|
import type { InteractiveModeContext } from "../modes/types";
|
|
24
25
|
import { formatBytes } from "../tools/render-utils";
|
|
@@ -150,13 +151,13 @@ export class DebugSelectorComponent extends Container {
|
|
|
150
151
|
}
|
|
151
152
|
|
|
152
153
|
// Show message and wait for keypress
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
154
|
+
const block = new TranscriptBlock();
|
|
155
|
+
block.addChild(new Text(theme.fg("accent", `${theme.status.info} CPU profiling started`), 1, 0));
|
|
156
|
+
block.addChild(new Spacer(1));
|
|
157
|
+
block.addChild(
|
|
157
158
|
new Text(theme.fg("muted", "Reproduce the performance issue, then press Enter to stop profiling."), 1, 0),
|
|
158
159
|
);
|
|
159
|
-
this.ctx.
|
|
160
|
+
this.ctx.present(block);
|
|
160
161
|
|
|
161
162
|
// Wait for Enter keypress
|
|
162
163
|
const { promise, resolve } = Promise.withResolvers<void>();
|
|
@@ -201,19 +202,16 @@ export class DebugSelectorComponent extends Container {
|
|
|
201
202
|
loader.stop();
|
|
202
203
|
this.ctx.statusContainer.clear();
|
|
203
204
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
);
|
|
208
|
-
this.ctx.
|
|
209
|
-
this.ctx.chatContainer.addChild(new Text(theme.fg("dim", `Files: ${result.files.length}`), 1, 0));
|
|
205
|
+
const block = new TranscriptBlock();
|
|
206
|
+
block.addChild(new Text(theme.fg("success", `${theme.status.success} Performance report saved`), 1, 0));
|
|
207
|
+
block.addChild(new Text(theme.fg("dim", formatFileHyperlink(result.path)), 1, 0));
|
|
208
|
+
block.addChild(new Text(theme.fg("dim", `Files: ${result.files.length}`), 1, 0));
|
|
209
|
+
this.ctx.present(block);
|
|
210
210
|
} catch (err) {
|
|
211
211
|
loader.stop();
|
|
212
212
|
this.ctx.statusContainer.clear();
|
|
213
213
|
this.ctx.showError(`Failed to create report: ${err instanceof Error ? err.message : String(err)}`);
|
|
214
214
|
}
|
|
215
|
-
|
|
216
|
-
this.ctx.ui.requestRender();
|
|
217
215
|
}
|
|
218
216
|
|
|
219
217
|
async #handleWorkReport(): Promise<void> {
|
|
@@ -231,15 +229,13 @@ export class DebugSelectorComponent extends Container {
|
|
|
231
229
|
|
|
232
230
|
openPath(tmpPath);
|
|
233
231
|
|
|
234
|
-
this.ctx.
|
|
235
|
-
|
|
232
|
+
this.ctx.present([
|
|
233
|
+
new Spacer(1),
|
|
236
234
|
new Text(theme.fg("dim", `Opened flamegraph (${workProfile.sampleCount} samples)`), 1, 0),
|
|
237
|
-
);
|
|
235
|
+
]);
|
|
238
236
|
} catch (err) {
|
|
239
237
|
this.ctx.showError(`Failed to open profile: ${err instanceof Error ? err.message : String(err)}`);
|
|
240
238
|
}
|
|
241
|
-
|
|
242
|
-
this.ctx.ui.requestRender();
|
|
243
239
|
}
|
|
244
240
|
|
|
245
241
|
async #handleDumpReport(): Promise<void> {
|
|
@@ -262,19 +258,16 @@ export class DebugSelectorComponent extends Container {
|
|
|
262
258
|
loader.stop();
|
|
263
259
|
this.ctx.statusContainer.clear();
|
|
264
260
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
);
|
|
269
|
-
this.ctx.
|
|
270
|
-
this.ctx.chatContainer.addChild(new Text(theme.fg("dim", `Files: ${result.files.length}`), 1, 0));
|
|
261
|
+
const block = new TranscriptBlock();
|
|
262
|
+
block.addChild(new Text(theme.fg("success", `${theme.status.success} Report bundle saved`), 1, 0));
|
|
263
|
+
block.addChild(new Text(theme.fg("dim", formatFileHyperlink(result.path)), 1, 0));
|
|
264
|
+
block.addChild(new Text(theme.fg("dim", `Files: ${result.files.length}`), 1, 0));
|
|
265
|
+
this.ctx.present(block);
|
|
271
266
|
} catch (err) {
|
|
272
267
|
loader.stop();
|
|
273
268
|
this.ctx.statusContainer.clear();
|
|
274
269
|
this.ctx.showError(`Failed to create report: ${err instanceof Error ? err.message : String(err)}`);
|
|
275
270
|
}
|
|
276
|
-
|
|
277
|
-
this.ctx.ui.requestRender();
|
|
278
271
|
}
|
|
279
272
|
|
|
280
273
|
async #handleMemoryReport(): Promise<void> {
|
|
@@ -301,19 +294,16 @@ export class DebugSelectorComponent extends Container {
|
|
|
301
294
|
loader.stop();
|
|
302
295
|
this.ctx.statusContainer.clear();
|
|
303
296
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
);
|
|
308
|
-
this.ctx.
|
|
309
|
-
this.ctx.chatContainer.addChild(new Text(theme.fg("dim", `Files: ${result.files.length}`), 1, 0));
|
|
297
|
+
const block = new TranscriptBlock();
|
|
298
|
+
block.addChild(new Text(theme.fg("success", `${theme.status.success} Memory report saved`), 1, 0));
|
|
299
|
+
block.addChild(new Text(theme.fg("dim", formatFileHyperlink(result.path)), 1, 0));
|
|
300
|
+
block.addChild(new Text(theme.fg("dim", `Files: ${result.files.length}`), 1, 0));
|
|
301
|
+
this.ctx.present(block);
|
|
310
302
|
} catch (err) {
|
|
311
303
|
loader.stop();
|
|
312
304
|
this.ctx.statusContainer.clear();
|
|
313
305
|
this.ctx.showError(`Failed to create report: ${err instanceof Error ? err.message : String(err)}`);
|
|
314
306
|
}
|
|
315
|
-
|
|
316
|
-
this.ctx.ui.requestRender();
|
|
317
307
|
}
|
|
318
308
|
|
|
319
309
|
async #handleViewLogs(): Promise<void> {
|
|
@@ -365,15 +355,14 @@ export class DebugSelectorComponent extends Container {
|
|
|
365
355
|
const info = await collectSystemInfo();
|
|
366
356
|
const formatted = formatSystemInfo(info);
|
|
367
357
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
358
|
+
const block = new TranscriptBlock();
|
|
359
|
+
block.addChild(new DynamicBorder());
|
|
360
|
+
block.addChild(new Text(formatted, 1, 0));
|
|
361
|
+
block.addChild(new DynamicBorder());
|
|
362
|
+
this.ctx.present(block);
|
|
372
363
|
} catch (err) {
|
|
373
364
|
this.ctx.showError(`Failed to collect system info: ${err instanceof Error ? err.message : String(err)}`);
|
|
374
365
|
}
|
|
375
|
-
|
|
376
|
-
this.ctx.ui.requestRender();
|
|
377
366
|
}
|
|
378
367
|
|
|
379
368
|
async #handleViewTerminalState(): Promise<void> {
|
|
@@ -384,11 +373,11 @@ export class DebugSelectorComponent extends Container {
|
|
|
384
373
|
});
|
|
385
374
|
const formatted = formatTerminalState(info);
|
|
386
375
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
this.ctx.
|
|
376
|
+
const block = new TranscriptBlock();
|
|
377
|
+
block.addChild(new DynamicBorder());
|
|
378
|
+
block.addChild(new Text(formatted, 1, 0));
|
|
379
|
+
block.addChild(new DynamicBorder());
|
|
380
|
+
this.ctx.present(block);
|
|
392
381
|
}
|
|
393
382
|
|
|
394
383
|
async #handleViewProtocols(): Promise<void> {
|
|
@@ -407,15 +396,14 @@ export class DebugSelectorComponent extends Container {
|
|
|
407
396
|
TERMINAL.sendNotification(notification);
|
|
408
397
|
}
|
|
409
398
|
|
|
410
|
-
this.ctx.
|
|
411
|
-
|
|
399
|
+
this.ctx.present([
|
|
400
|
+
new Spacer(1),
|
|
412
401
|
new ProtocolProbeComponent({
|
|
413
402
|
image: buildSampleImage(),
|
|
414
403
|
imageBudget: this.ctx.ui.imageBudget,
|
|
415
404
|
notificationSuppressed: suppressed,
|
|
416
405
|
}),
|
|
417
|
-
);
|
|
418
|
-
this.ctx.ui.requestRender();
|
|
406
|
+
]);
|
|
419
407
|
}
|
|
420
408
|
|
|
421
409
|
async #handleTranscriptExport(): Promise<void> {
|
|
@@ -487,21 +475,19 @@ export class DebugSelectorComponent extends Container {
|
|
|
487
475
|
loader.stop();
|
|
488
476
|
this.ctx.statusContainer.clear();
|
|
489
477
|
|
|
490
|
-
this.ctx.
|
|
491
|
-
|
|
478
|
+
this.ctx.present([
|
|
479
|
+
new Spacer(1),
|
|
492
480
|
new Text(
|
|
493
481
|
theme.fg("success", `${theme.status.success} Cleared ${result.removed} artifact directories`),
|
|
494
482
|
1,
|
|
495
483
|
0,
|
|
496
484
|
),
|
|
497
|
-
);
|
|
485
|
+
]);
|
|
498
486
|
} catch (err) {
|
|
499
487
|
loader.stop();
|
|
500
488
|
this.ctx.statusContainer.clear();
|
|
501
489
|
this.ctx.showError(`Failed to clear cache: ${err instanceof Error ? err.message : String(err)}`);
|
|
502
490
|
}
|
|
503
|
-
|
|
504
|
-
this.ctx.ui.requestRender();
|
|
505
491
|
}
|
|
506
492
|
|
|
507
493
|
#getResolvedSettings(): Record<string, unknown> {
|
package/src/edit/renderer.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { HL_FILE_PREFIX, HL_FILE_SUFFIX } from "@oh-my-pi/hashline";
|
|
6
6
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
7
|
-
import {
|
|
7
|
+
import { visibleWidth, wrapTextWithAnsi } from "@oh-my-pi/pi-tui";
|
|
8
8
|
import { sanitizeText } from "@oh-my-pi/pi-utils";
|
|
9
9
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
10
10
|
import type { FileDiagnosticsResult } from "../lsp";
|
|
@@ -16,7 +16,6 @@ import {
|
|
|
16
16
|
formatDiffStats,
|
|
17
17
|
formatExpandHint,
|
|
18
18
|
formatStatusIcon,
|
|
19
|
-
formatTitle,
|
|
20
19
|
getDiffStats,
|
|
21
20
|
getLspBatchRequest,
|
|
22
21
|
type LspBatchRequest,
|
|
@@ -25,7 +24,7 @@ import {
|
|
|
25
24
|
shortenPath,
|
|
26
25
|
truncateDiffByHunk,
|
|
27
26
|
} from "../tools/render-utils";
|
|
28
|
-
import { fileHyperlink, Hasher, type RenderCache, renderStatusLine, truncateToWidth } from "../tui";
|
|
27
|
+
import { fileHyperlink, framedBlock, Hasher, type RenderCache, renderStatusLine, truncateToWidth } from "../tui";
|
|
29
28
|
import type { EditMode } from "../utils/edit-mode";
|
|
30
29
|
import type { DiffError, DiffResult } from "./diff";
|
|
31
30
|
import { type ApplyPatchEntry, expandApplyPatchToEntries, expandApplyPatchToPreviewEntries } from "./modes/apply-patch";
|
|
@@ -186,10 +185,14 @@ function getOperationTitle(op: Operation | undefined): string {
|
|
|
186
185
|
function formatEditPathDisplay(
|
|
187
186
|
rawPath: string,
|
|
188
187
|
uiTheme: Theme,
|
|
189
|
-
options?: { rename?: string; firstChangedLine?: number },
|
|
188
|
+
options?: { rename?: string; firstChangedLine?: number; linkPath?: string; renameLinkPath?: string },
|
|
190
189
|
): string {
|
|
190
|
+
// `rawPath`/`rename` are shown (cwd-relative) but the OSC 8 link targets the
|
|
191
|
+
// absolute path when known — a relative `rawPath` would yield a `file:///rel`
|
|
192
|
+
// URI that resolves against filesystem root instead of cwd.
|
|
193
|
+
const linkTarget = options?.linkPath || rawPath;
|
|
191
194
|
let pathDisplay = rawPath
|
|
192
|
-
? fileHyperlink(
|
|
195
|
+
? fileHyperlink(linkTarget, uiTheme.fg("accent", shortenPath(rawPath)))
|
|
193
196
|
: uiTheme.fg("toolOutput", "…");
|
|
194
197
|
|
|
195
198
|
if (options?.firstChangedLine) {
|
|
@@ -197,7 +200,8 @@ function formatEditPathDisplay(
|
|
|
197
200
|
}
|
|
198
201
|
|
|
199
202
|
if (options?.rename) {
|
|
200
|
-
|
|
203
|
+
const renameTarget = options.renameLinkPath || options.rename;
|
|
204
|
+
pathDisplay += ` ${uiTheme.fg("dim", "→")} ${fileHyperlink(renameTarget, uiTheme.fg("accent", shortenPath(options.rename)))}`;
|
|
201
205
|
}
|
|
202
206
|
|
|
203
207
|
return pathDisplay;
|
|
@@ -206,7 +210,7 @@ function formatEditPathDisplay(
|
|
|
206
210
|
function formatEditDescription(
|
|
207
211
|
rawPath: string,
|
|
208
212
|
uiTheme: Theme,
|
|
209
|
-
options?: { rename?: string; firstChangedLine?: number },
|
|
213
|
+
options?: { rename?: string; firstChangedLine?: number; linkPath?: string; renameLinkPath?: string },
|
|
210
214
|
): { language: string; description: string } {
|
|
211
215
|
const language = getLanguageFromPath(rawPath) ?? "text";
|
|
212
216
|
const icon = uiTheme.fg("muted", uiTheme.getLangIcon(language));
|
|
@@ -459,23 +463,31 @@ export const editToolRenderer = {
|
|
|
459
463
|
const rename = editArgs.rename || firstEdit?.rename || firstEdit?.move || firstApplyPatchEntry?.rename;
|
|
460
464
|
const op = editArgs.op || firstEdit?.op || firstApplyPatchEntry?.op;
|
|
461
465
|
const { description } = formatEditDescription(rawPath, uiTheme, { rename });
|
|
462
|
-
const spinner =
|
|
463
|
-
options?.spinnerFrame !== undefined ? formatStatusIcon("running", uiTheme, options.spinnerFrame) : "";
|
|
464
|
-
let text = `${formatTitle(getOperationTitle(op), uiTheme)} ${spinner ? `${spinner} ` : ""}${description}`;
|
|
465
|
-
// Show file count hint for multi-file edits
|
|
466
466
|
let fileCount = hashlineInputSummary?.entries.length ?? applyPatchSummary?.entries.length ?? 0;
|
|
467
467
|
if (Array.isArray(editArgs.edits)) {
|
|
468
468
|
fileCount = countEditFiles(editArgs.edits);
|
|
469
469
|
}
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
470
|
+
return framedBlock(uiTheme, width => {
|
|
471
|
+
let header = renderStatusLine(
|
|
472
|
+
{ icon: "pending", spinnerFrame: options?.spinnerFrame, title: getOperationTitle(op), description },
|
|
473
|
+
uiTheme,
|
|
474
|
+
);
|
|
475
|
+
if (fileCount > 1) header += uiTheme.fg("dim", ` (+${fileCount - 1} more)`);
|
|
476
|
+
let body = getCallPreview(editArgs, rawPath, uiTheme, renderContext, options.expanded);
|
|
477
|
+
if (applyPatchSummary?.error) {
|
|
478
|
+
body += `\n${uiTheme.fg("error", truncateToWidth(replaceTabs(applyPatchSummary.error, rawPath), Math.max(1, width - 2)))}`;
|
|
479
|
+
}
|
|
480
|
+
const bodyLines = body ? body.split("\n") : [];
|
|
481
|
+
while (bodyLines.length > 0 && bodyLines[0].trim() === "") bodyLines.shift();
|
|
482
|
+
return {
|
|
483
|
+
header,
|
|
484
|
+
sections: bodyLines.length > 0 ? [{ lines: bodyLines }] : [],
|
|
485
|
+
state: applyPatchSummary?.error ? "error" : "pending",
|
|
486
|
+
borderColor: applyPatchSummary?.error ? "error" : "borderMuted",
|
|
487
|
+
width,
|
|
488
|
+
contentPaddingLeft: 0,
|
|
489
|
+
};
|
|
490
|
+
});
|
|
479
491
|
},
|
|
480
492
|
|
|
481
493
|
renderResult(
|
|
@@ -525,65 +537,57 @@ function renderSingleFileResult(
|
|
|
525
537
|
(result.content?.find(c => c.type === "text")?.text ?? "")
|
|
526
538
|
: "";
|
|
527
539
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
540
|
+
return framedBlock(uiTheme, width => {
|
|
541
|
+
const { expanded, renderContext } = options;
|
|
542
|
+
const editDiffPreview = renderContext?.editDiffPreview;
|
|
543
|
+
const renderDiffFn = renderContext?.renderDiff ?? ((t: string) => t);
|
|
544
|
+
|
|
545
|
+
const firstChangedLine =
|
|
546
|
+
(editDiffPreview && "firstChangedLine" in editDiffPreview ? editDiffPreview.firstChangedLine : undefined) ||
|
|
547
|
+
(details && !isError ? details.firstChangedLine : undefined);
|
|
548
|
+
const linkPath = details && "path" in details ? details.path : undefined;
|
|
549
|
+
const { description } = formatEditDescription(rawPath, uiTheme, { rename, firstChangedLine, linkPath });
|
|
550
|
+
|
|
551
|
+
// Change stats ride inline on the header bar next to the path.
|
|
552
|
+
const previewDiff = editDiffPreview && !("error" in editDiffPreview) ? editDiffPreview.diff : undefined;
|
|
553
|
+
const headerDiff = isError ? undefined : details?.diff || previewDiff;
|
|
554
|
+
const statsSuffix = headerDiff ? formatDiffStatsSuffix(headerDiff, uiTheme) : "";
|
|
555
|
+
const header =
|
|
556
|
+
renderStatusLine({ icon: isError ? "error" : "success", title: getOperationTitle(op), description }, uiTheme) +
|
|
557
|
+
statsSuffix;
|
|
558
|
+
|
|
559
|
+
let body = "";
|
|
560
|
+
if (isError) {
|
|
561
|
+
if (errorText) body = uiTheme.fg("error", replaceTabs(errorText, rawPath));
|
|
562
|
+
} else if (details?.diff) {
|
|
563
|
+
body = renderDiffSection(details.diff, rawPath, expanded, uiTheme, renderDiffFn);
|
|
564
|
+
} else if (editDiffPreview) {
|
|
565
|
+
if ("error" in editDiffPreview) body = uiTheme.fg("error", replaceTabs(editDiffPreview.error, rawPath));
|
|
566
|
+
else if (editDiffPreview.diff)
|
|
567
|
+
body = renderDiffSection(editDiffPreview.diff, rawPath, expanded, uiTheme, renderDiffFn);
|
|
568
|
+
}
|
|
569
|
+
if (details?.diagnostics) {
|
|
570
|
+
body += formatDiagnostics(details.diagnostics, expanded, uiTheme, (fp: string) =>
|
|
571
|
+
uiTheme.getLangIcon(getLanguageFromPath(fp)),
|
|
555
572
|
);
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
if (isError) {
|
|
559
|
-
if (errorText) {
|
|
560
|
-
text += `\n\n${uiTheme.fg("error", replaceTabs(errorText, rawPath))}`;
|
|
561
|
-
}
|
|
562
|
-
} else if (details?.diff) {
|
|
563
|
-
text += renderDiffSection(details.diff, rawPath, expanded, uiTheme, renderDiffFn);
|
|
564
|
-
} else if (editDiffPreview) {
|
|
565
|
-
if ("error" in editDiffPreview) {
|
|
566
|
-
text += `\n\n${uiTheme.fg("error", replaceTabs(editDiffPreview.error, rawPath))}`;
|
|
567
|
-
} else if (editDiffPreview.diff) {
|
|
568
|
-
text += renderDiffSection(editDiffPreview.diff, rawPath, expanded, uiTheme, renderDiffFn);
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
if (details?.diagnostics) {
|
|
573
|
-
text += formatDiagnostics(details.diagnostics, expanded, uiTheme, (fp: string) =>
|
|
574
|
-
uiTheme.getLangIcon(getLanguageFromPath(fp)),
|
|
575
|
-
);
|
|
576
|
-
}
|
|
573
|
+
}
|
|
577
574
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
575
|
+
// Diff lines self-wrap with a continuation gutter; pre-wrap to the frame's
|
|
576
|
+
// inner width so renderOutputBlock's generic wrap is a no-op. Edit frames
|
|
577
|
+
// use a flush left border because code-frame gutters already provide padding.
|
|
578
|
+
const innerWidth = Math.max(1, width - 2);
|
|
579
|
+
const bodyLines = body.length > 0 ? body.split("\n").flatMap(line => wrapEditRendererLine(line, innerWidth)) : [];
|
|
580
|
+
while (bodyLines.length > 0 && bodyLines[0].trim() === "") bodyLines.shift();
|
|
581
|
+
|
|
582
|
+
return {
|
|
583
|
+
header,
|
|
584
|
+
sections: bodyLines.length > 0 ? [{ lines: bodyLines }] : [],
|
|
585
|
+
state: isError ? "error" : options.isPartial ? "pending" : "success",
|
|
586
|
+
borderColor: isError ? "error" : "borderMuted",
|
|
587
|
+
width,
|
|
588
|
+
contentPaddingLeft: 0,
|
|
589
|
+
};
|
|
590
|
+
});
|
|
587
591
|
}
|
|
588
592
|
|
|
589
593
|
function renderMultiFileResult(
|
|
@@ -638,7 +642,7 @@ function renderMultiFileResult(
|
|
|
638
642
|
},
|
|
639
643
|
invalidate() {
|
|
640
644
|
cached = undefined;
|
|
641
|
-
for (const c of fileComponents) c.invalidate();
|
|
645
|
+
for (const c of fileComponents) c.invalidate?.();
|
|
642
646
|
},
|
|
643
647
|
};
|
|
644
648
|
}
|
|
@@ -4,6 +4,7 @@ import type { Api, AssistantMessage, Model } from "@oh-my-pi/pi-ai";
|
|
|
4
4
|
import * as ai from "@oh-my-pi/pi-ai";
|
|
5
5
|
import { Effort } from "@oh-my-pi/pi-ai";
|
|
6
6
|
import { TempDir } from "@oh-my-pi/pi-utils";
|
|
7
|
+
import { $ } from "bun";
|
|
7
8
|
import type { ModelRegistry } from "../../config/model-registry";
|
|
8
9
|
import { Settings } from "../../config/settings";
|
|
9
10
|
import type { ToolSession } from "../../tools";
|
|
@@ -13,7 +14,7 @@ import { IdleTimeout } from "../idle-timeout";
|
|
|
13
14
|
import { disposeAllVmContexts } from "../js/context-manager";
|
|
14
15
|
import { executeJs } from "../js/executor";
|
|
15
16
|
import { runEvalLlm } from "../llm-bridge";
|
|
16
|
-
import { disposeAllKernelSessions,
|
|
17
|
+
import { disposeAllKernelSessions, type PythonResult } from "../py/executor";
|
|
17
18
|
|
|
18
19
|
function makeModel(provider: string, id: string, extra: Partial<Model<Api>> = {}): Model<Api> {
|
|
19
20
|
return {
|
|
@@ -57,6 +58,7 @@ function makeSession(opts: SessionOptions = {}): ToolSession {
|
|
|
57
58
|
const modelRegistry = {
|
|
58
59
|
getAvailable: () => opts.available ?? [SMOL, DEFAULT, SLOW],
|
|
59
60
|
getApiKey: async () => (opts.apiKey === undefined ? "test-key" : opts.apiKey),
|
|
61
|
+
resolver: () => async () => (opts.apiKey === undefined ? "test-key" : opts.apiKey),
|
|
60
62
|
} as unknown as ModelRegistry;
|
|
61
63
|
return {
|
|
62
64
|
settings,
|
|
@@ -96,6 +98,77 @@ function assistant(opts: {
|
|
|
96
98
|
};
|
|
97
99
|
}
|
|
98
100
|
|
|
101
|
+
async function runPythonLlmInSubprocess(options: { structured: boolean; tempDir: TempDir }): Promise<PythonResult> {
|
|
102
|
+
const repoRoot = path.resolve(import.meta.dir, "../../../..");
|
|
103
|
+
const scriptPath = path.join(options.tempDir.path(), "run-python-llm.ts");
|
|
104
|
+
const resultPath = path.join(options.tempDir.path(), "python-llm-result.json");
|
|
105
|
+
const aiPath = path.resolve(import.meta.dir, "../../../../ai/src/index.ts");
|
|
106
|
+
const executorPath = path.resolve(import.meta.dir, "../py/executor.ts");
|
|
107
|
+
const settingsPath = path.resolve(import.meta.dir, "../../config/settings.ts");
|
|
108
|
+
const code = options.structured
|
|
109
|
+
? 'import json\nprint(json.dumps(llm("hi", schema={"type": "object"})))'
|
|
110
|
+
: 'print(llm("hi", model="smol"))';
|
|
111
|
+
const responseContent = options.structured
|
|
112
|
+
? '[{ type: "toolCall", id: "tc-1", name: "respond", arguments: { ok: true } }]'
|
|
113
|
+
: '[{ type: "text", text: "hello from python" }]';
|
|
114
|
+
await Bun.write(
|
|
115
|
+
scriptPath,
|
|
116
|
+
`
|
|
117
|
+
import { vi } from "bun:test";
|
|
118
|
+
import * as ai from ${JSON.stringify(aiPath)};
|
|
119
|
+
import { executePython } from ${JSON.stringify(executorPath)};
|
|
120
|
+
import { Settings } from ${JSON.stringify(settingsPath)};
|
|
121
|
+
|
|
122
|
+
const SMOL = {
|
|
123
|
+
id: "smol",
|
|
124
|
+
name: "smol",
|
|
125
|
+
api: "openai-responses",
|
|
126
|
+
provider: "p",
|
|
127
|
+
baseUrl: "https://example.test/v1",
|
|
128
|
+
reasoning: false,
|
|
129
|
+
input: ["text"],
|
|
130
|
+
cost: { input: 1, output: 1, cacheRead: 0, cacheWrite: 1 },
|
|
131
|
+
contextWindow: 128000,
|
|
132
|
+
maxTokens: 4096,
|
|
133
|
+
};
|
|
134
|
+
const settings = Settings.isolated({ "async.enabled": false, "task.isolation.mode": "none" });
|
|
135
|
+
settings.setModelRole("smol", "p/smol");
|
|
136
|
+
settings.setModelRole("slow", "p/slow");
|
|
137
|
+
const session = {
|
|
138
|
+
settings,
|
|
139
|
+
modelRegistry: {
|
|
140
|
+
getAvailable: () => [SMOL],
|
|
141
|
+
getApiKey: async () => "test-key",
|
|
142
|
+
resolver: () => async () => "test-key",
|
|
143
|
+
},
|
|
144
|
+
getActiveModelString: () => "p/smol",
|
|
145
|
+
};
|
|
146
|
+
vi.spyOn(ai, "completeSimple").mockResolvedValue({
|
|
147
|
+
role: "assistant",
|
|
148
|
+
api: "openai-responses",
|
|
149
|
+
provider: "p",
|
|
150
|
+
model: "smol",
|
|
151
|
+
stopReason: "stop",
|
|
152
|
+
content: ${responseContent},
|
|
153
|
+
});
|
|
154
|
+
const result = await executePython(${JSON.stringify(code)}, {
|
|
155
|
+
cwd: ${JSON.stringify(options.tempDir.path())},
|
|
156
|
+
sessionId: ${JSON.stringify(`py-llm:${options.structured ? "struct" : "plain"}`)},
|
|
157
|
+
sessionFile: ${JSON.stringify(path.join(options.tempDir.path(), "session.jsonl"))},
|
|
158
|
+
toolSession: session,
|
|
159
|
+
kernelMode: "per-call",
|
|
160
|
+
});
|
|
161
|
+
await Bun.write(${JSON.stringify(resultPath)}, JSON.stringify(result));
|
|
162
|
+
process.exit(0);
|
|
163
|
+
`,
|
|
164
|
+
);
|
|
165
|
+
const child = await $`bun ${scriptPath}`.cwd(repoRoot).quiet().nothrow();
|
|
166
|
+
const stdout = child.stdout.toString();
|
|
167
|
+
const stderr = child.stderr.toString();
|
|
168
|
+
if (child.exitCode !== 0) throw new Error(stderr || stdout || `Python llm subprocess exited with ${child.exitCode}`);
|
|
169
|
+
return (await Bun.file(resultPath).json()) as PythonResult;
|
|
170
|
+
}
|
|
171
|
+
|
|
99
172
|
describe("runEvalLlm", () => {
|
|
100
173
|
afterEach(() => {
|
|
101
174
|
vi.restoreAllMocks();
|
|
@@ -290,38 +363,24 @@ describe("llm() through eval runtimes", () => {
|
|
|
290
363
|
});
|
|
291
364
|
|
|
292
365
|
it("exposes llm() in the Python runtime", async () => {
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
sessionFile,
|
|
302
|
-
toolSession: makeSession(),
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
expect(result.exitCode).toBe(0);
|
|
306
|
-
expect(result.output.trim()).toBe("hello from python");
|
|
366
|
+
const tempDir = TempDir.createSync("@omp-eval-llm-py-");
|
|
367
|
+
try {
|
|
368
|
+
const result = await runPythonLlmInSubprocess({ structured: false, tempDir });
|
|
369
|
+
expect(result.exitCode).toBe(0);
|
|
370
|
+
expect(result.output.trim()).toBe("hello from python");
|
|
371
|
+
} finally {
|
|
372
|
+
tempDir.removeSync();
|
|
373
|
+
}
|
|
307
374
|
});
|
|
308
375
|
|
|
309
376
|
it("parses structured llm() output in the Python runtime", async () => {
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
cwd: tempDir.path(),
|
|
319
|
-
sessionId,
|
|
320
|
-
sessionFile,
|
|
321
|
-
toolSession: makeSession(),
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
expect(result.exitCode).toBe(0);
|
|
325
|
-
expect(JSON.parse(result.output.trim())).toEqual({ ok: true });
|
|
377
|
+
const tempDir = TempDir.createSync("@omp-eval-llm-py-struct-");
|
|
378
|
+
try {
|
|
379
|
+
const result = await runPythonLlmInSubprocess({ structured: true, tempDir });
|
|
380
|
+
expect(result.exitCode).toBe(0);
|
|
381
|
+
expect(JSON.parse(result.output.trim())).toEqual({ ok: true });
|
|
382
|
+
} finally {
|
|
383
|
+
tempDir.removeSync();
|
|
384
|
+
}
|
|
326
385
|
});
|
|
327
386
|
});
|
package/src/eval/llm-bridge.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { instrumentedCompleteSimple, resolveTelemetry } from "@oh-my-pi/pi-agent
|
|
|
15
15
|
import { type Api, Effort, getSupportedEfforts, type Model, type Tool } from "@oh-my-pi/pi-ai";
|
|
16
16
|
import * as z from "zod/v4";
|
|
17
17
|
import { extractTextContent, extractToolCall, parseJsonPayload } from "../commit/utils";
|
|
18
|
+
|
|
18
19
|
import { expandRoleAlias, formatModelString, resolveModelFromString } from "../config/model-resolver";
|
|
19
20
|
import type { ToolSession } from "../tools";
|
|
20
21
|
import { ToolError } from "../tools/tool-errors";
|
|
@@ -112,8 +113,9 @@ export async function runEvalLlm(args: unknown, options: EvalLlmBridgeOptions):
|
|
|
112
113
|
);
|
|
113
114
|
}
|
|
114
115
|
|
|
115
|
-
const
|
|
116
|
-
|
|
116
|
+
const registry = options.session.modelRegistry;
|
|
117
|
+
const apiKey = await registry?.getApiKey(model);
|
|
118
|
+
if (!registry || !apiKey) {
|
|
117
119
|
throw new ToolError(
|
|
118
120
|
`llm() has no API key for ${formatModelString(model)}. Configure credentials for this provider or choose another tier.`,
|
|
119
121
|
);
|
|
@@ -143,7 +145,10 @@ export async function runEvalLlm(args: unknown, options: EvalLlmBridgeOptions):
|
|
|
143
145
|
tools,
|
|
144
146
|
},
|
|
145
147
|
{
|
|
146
|
-
apiKey,
|
|
148
|
+
apiKey: registry.resolver(model.provider, {
|
|
149
|
+
sessionId: options.session.getSessionId?.() ?? undefined,
|
|
150
|
+
baseUrl: model.baseUrl,
|
|
151
|
+
}),
|
|
147
152
|
signal: options.signal,
|
|
148
153
|
reasoning: reasoningForTier(tier, model),
|
|
149
154
|
toolChoice: schema ? { type: "tool", name: STRUCTURED_TOOL_NAME } : undefined,
|