@oh-my-pi/pi-coding-agent 15.11.3 → 15.11.6
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 +107 -0
- package/dist/cli.js +692 -607
- package/dist/types/cli/usage-cli.d.ts +10 -1
- package/dist/types/commands/usage.d.ts +9 -0
- package/dist/types/config/api-key-resolver.d.ts +9 -3
- package/dist/types/config/keybindings.d.ts +1 -1
- package/dist/types/config/model-discovery.d.ts +6 -4
- package/dist/types/config/model-registry.d.ts +7 -4
- package/dist/types/config/settings-schema.d.ts +508 -155
- package/dist/types/export/html/template.generated.d.ts +1 -1
- package/dist/types/mnemopi/config.d.ts +3 -1
- package/dist/types/modes/components/reset-usage-selector.d.ts +12 -0
- package/dist/types/modes/components/session-selector.d.ts +1 -1
- package/dist/types/modes/components/settings-defs.d.ts +9 -2
- package/dist/types/modes/components/settings-selector.d.ts +9 -4
- package/dist/types/modes/components/tool-execution.d.ts +26 -1
- package/dist/types/modes/components/transcript-container.d.ts +12 -0
- package/dist/types/modes/controllers/input-controller.d.ts +9 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +10 -0
- package/dist/types/modes/session-observer-registry.d.ts +2 -0
- package/dist/types/modes/theme/theme.d.ts +23 -3
- package/dist/types/modes/types.d.ts +2 -0
- package/dist/types/modes/utils/context-usage.d.ts +6 -1
- package/dist/types/session/agent-session.d.ts +28 -8
- package/dist/types/session/auth-storage.d.ts +1 -1
- package/dist/types/session/codex-auto-reset.d.ts +107 -0
- package/dist/types/session/snapcompact-inline.d.ts +129 -0
- package/dist/types/slash-commands/helpers/active-oauth-account.d.ts +14 -0
- package/dist/types/slash-commands/helpers/reset-usage.d.ts +27 -0
- package/dist/types/system-prompt.d.ts +3 -1
- package/dist/types/task/render.d.ts +17 -6
- package/dist/types/tools/gh.d.ts +3 -0
- package/dist/types/tools/render-utils.d.ts +8 -16
- package/dist/types/tools/todo.d.ts +0 -11
- package/dist/types/utils/session-color.d.ts +15 -3
- package/dist/types/web/kagi.d.ts +1 -2
- package/dist/types/web/search/providers/codex.d.ts +1 -1
- package/dist/types/web/search/providers/gemini.d.ts +9 -6
- package/package.json +11 -11
- package/src/auto-thinking/classifier.ts +1 -5
- package/src/cli/usage-cli.ts +187 -16
- package/src/commands/usage.ts +8 -0
- package/src/commit/model-selection.ts +3 -6
- package/src/config/api-key-resolver.ts +10 -3
- package/src/config/keybindings.ts +1 -1
- package/src/config/model-discovery.ts +60 -46
- package/src/config/model-registry.ts +21 -8
- package/src/config/model-resolver.ts +57 -3
- package/src/config/settings-schema.ts +654 -153
- package/src/config/settings.ts +9 -0
- package/src/eval/completion-bridge.ts +1 -5
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +13 -6
- package/src/internal-urls/docs-index.generated.ts +6 -6
- package/src/internal-urls/issue-pr-protocol.ts +10 -4
- package/src/memories/index.ts +2 -10
- package/src/mnemopi/backend.ts +30 -8
- package/src/mnemopi/config.ts +6 -1
- package/src/mnemopi/state.ts +6 -0
- package/src/modes/components/extensions/inspector-panel.ts +6 -2
- package/src/modes/components/plan-review-overlay.ts +15 -17
- package/src/modes/components/plugin-settings.ts +22 -5
- package/src/modes/components/reset-usage-selector.ts +161 -0
- package/src/modes/components/session-selector.ts +8 -2
- package/src/modes/components/settings-defs.ts +19 -4
- package/src/modes/components/settings-selector.ts +510 -95
- package/src/modes/components/status-line/component.ts +3 -1
- package/src/modes/components/status-line/segments.ts +3 -1
- package/src/modes/components/tool-execution.ts +87 -12
- package/src/modes/components/transcript-container.ts +49 -1
- package/src/modes/components/tree-selector.ts +16 -6
- package/src/modes/controllers/command-controller.ts +61 -8
- package/src/modes/controllers/event-controller.ts +1 -0
- package/src/modes/controllers/input-controller.ts +68 -6
- package/src/modes/controllers/selector-controller.ts +149 -61
- package/src/modes/interactive-mode.ts +63 -2
- package/src/modes/rpc/rpc-mode.ts +2 -1
- package/src/modes/session-observer-registry.ts +61 -3
- package/src/modes/shared.ts +2 -0
- package/src/modes/theme/theme.ts +102 -9
- package/src/modes/types.ts +2 -0
- package/src/modes/utils/context-usage.ts +78 -2
- package/src/modes/utils/hotkeys-markdown.ts +1 -1
- package/src/modes/utils/ui-helpers.ts +9 -5
- package/src/prompts/system/personalities/default.md +26 -0
- package/src/prompts/system/personalities/friendly.md +17 -0
- package/src/prompts/system/personalities/pragmatic.md +15 -0
- package/src/prompts/system/snapcompact-context-frames-note.md +1 -0
- package/src/prompts/system/snapcompact-context-stub.md +1 -0
- package/src/prompts/system/snapcompact-system-frames-note.md +1 -0
- package/src/prompts/system/snapcompact-system-stub.md +1 -0
- package/src/prompts/system/snapcompact-toolresult-note.md +1 -0
- package/src/prompts/system/system-prompt.md +5 -22
- package/src/prompts/tools/browser.md +33 -43
- package/src/prompts/tools/eval.md +27 -50
- package/src/prompts/tools/irc.md +29 -31
- package/src/prompts/tools/read.md +31 -37
- package/src/prompts/tools/task.md +3 -3
- package/src/prompts/tools/todo.md +1 -2
- package/src/sdk.ts +23 -1
- package/src/session/agent-session.ts +221 -29
- package/src/session/auth-storage.ts +4 -0
- package/src/session/codex-auto-reset.ts +190 -0
- package/src/session/session-dump-format.ts +8 -1
- package/src/session/session-manager.ts +5 -5
- package/src/session/snapcompact-inline.ts +524 -0
- package/src/slash-commands/builtin-registry.ts +145 -8
- package/src/slash-commands/helpers/active-oauth-account.ts +44 -0
- package/src/slash-commands/helpers/context-report.ts +28 -1
- package/src/slash-commands/helpers/reset-usage.ts +66 -0
- package/src/slash-commands/helpers/usage-report.ts +36 -3
- package/src/system-prompt.ts +15 -1
- package/src/task/index.ts +30 -7
- package/src/task/render.ts +57 -32
- package/src/tool-discovery/tool-index.ts +2 -0
- package/src/tools/bash.ts +10 -3
- package/src/tools/eval-render.ts +13 -8
- package/src/tools/gh.ts +39 -1
- package/src/tools/image-gen.ts +114 -78
- package/src/tools/inspect-image.ts +1 -5
- package/src/tools/job.ts +25 -5
- package/src/tools/read.ts +1 -57
- package/src/tools/render-utils.ts +29 -31
- package/src/tools/ssh.ts +3 -3
- package/src/tools/todo.ts +8 -128
- package/src/tools/tts.ts +40 -20
- package/src/utils/clipboard.ts +56 -4
- package/src/utils/commit-message-generator.ts +1 -5
- package/src/utils/session-color.ts +83 -9
- package/src/utils/title-generator.ts +1 -1
- package/src/web/kagi.ts +26 -27
- package/src/web/search/providers/codex.ts +42 -40
- package/src/web/search/providers/gemini.ts +42 -22
- package/src/web/search/providers/perplexity.ts +22 -10
package/src/task/render.ts
CHANGED
|
@@ -11,7 +11,6 @@ import { formatNumber } from "@oh-my-pi/pi-utils";
|
|
|
11
11
|
import { settings } from "../config/settings";
|
|
12
12
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
13
13
|
import { formatContextUsage } from "../modes/components/status-line/context-thresholds";
|
|
14
|
-
import { shimmerEnabled, shimmerText } from "../modes/theme/shimmer";
|
|
15
14
|
import { getMarkdownTheme, type Theme } from "../modes/theme/theme";
|
|
16
15
|
import {
|
|
17
16
|
formatBadge,
|
|
@@ -35,6 +34,18 @@ import { repairDoubleEncodedJsonString } from "./repair-args";
|
|
|
35
34
|
import { subprocessToolRegistry } from "./subprocess-tool-registry";
|
|
36
35
|
import type { AgentProgress, SingleResult, TaskItem, TaskParams, TaskToolDetails } from "./types";
|
|
37
36
|
|
|
37
|
+
/** Render context threaded in from `ToolExecutionComponent.#buildRenderContext`. */
|
|
38
|
+
interface TaskRenderContext {
|
|
39
|
+
hasResult?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* The block left the transcript live region (detached spawn the transcript
|
|
42
|
+
* has moved past, or a sealed block): progress rows render static gray, so
|
|
43
|
+
* commit-eligible rows do not repaint after entering native scrollback.
|
|
44
|
+
*/
|
|
45
|
+
frozen?: boolean;
|
|
46
|
+
}
|
|
47
|
+
type TaskRenderOptions = RenderResultOptions & { renderContext?: TaskRenderContext };
|
|
48
|
+
|
|
38
49
|
/**
|
|
39
50
|
* Get status icon for agent state.
|
|
40
51
|
* For running status, uses animated spinner if spinnerFrame is provided.
|
|
@@ -154,7 +165,7 @@ function formatJsonScalar(value: unknown, _theme: Theme): string {
|
|
|
154
165
|
return "";
|
|
155
166
|
}
|
|
156
167
|
|
|
157
|
-
function formatTaskId(id: string): string {
|
|
168
|
+
export function formatTaskId(id: string): string {
|
|
158
169
|
// Ids are name-based (e.g. "Anna", "Anna-2"); a "." separates nesting levels
|
|
159
170
|
// (e.g. "Anna.Bob"). Render the hierarchy with a ">" breadcrumb.
|
|
160
171
|
const segments = id.split(".");
|
|
@@ -614,13 +625,15 @@ function createMarkdownSectionRenderer(text: string, theme: Theme): AssignmentSe
|
|
|
614
625
|
/**
|
|
615
626
|
* Render the tool call arguments.
|
|
616
627
|
*/
|
|
617
|
-
export function renderCall(
|
|
618
|
-
args: TaskParams,
|
|
619
|
-
options: RenderResultOptions & { renderContext?: { hasResult?: boolean } },
|
|
620
|
-
theme: Theme,
|
|
621
|
-
): Component {
|
|
628
|
+
export function renderCall(args: TaskParams, options: TaskRenderOptions, theme: Theme): Component {
|
|
622
629
|
const showIsolated = "isolated" in args && args.isolated === true;
|
|
623
|
-
|
|
630
|
+
// Dispatch glyph from the first frame: spawning is non-blocking, so a
|
|
631
|
+
// pending/hourglass icon would misread the call as something the turn
|
|
632
|
+
// waits on.
|
|
633
|
+
const header = renderStatusLine(
|
|
634
|
+
{ iconOverride: theme.styledSymbol("tool.task", "accent"), title: "Task", description: args.agent },
|
|
635
|
+
theme,
|
|
636
|
+
);
|
|
624
637
|
const assignmentSection = createAssignmentSectionRenderer(args, theme);
|
|
625
638
|
const contextSection = createContextSectionRenderer(args, theme);
|
|
626
639
|
return framedBlock(theme, width => {
|
|
@@ -666,6 +679,7 @@ function renderAgentProgress(
|
|
|
666
679
|
expanded: boolean,
|
|
667
680
|
theme: Theme,
|
|
668
681
|
spinnerFrame?: number,
|
|
682
|
+
frozen = false,
|
|
669
683
|
): string[] {
|
|
670
684
|
const lines: string[] = [];
|
|
671
685
|
|
|
@@ -684,21 +698,23 @@ function renderAgentProgress(
|
|
|
684
698
|
const indent = prefix ? `${prefix} ` : "";
|
|
685
699
|
let statusLine: string;
|
|
686
700
|
if (progress.status === "running" || progress.status === "pending") {
|
|
687
|
-
// Live (or queued) agents
|
|
688
|
-
//
|
|
689
|
-
//
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
const
|
|
693
|
-
|
|
701
|
+
// Live (or queued) agents use the same dot finished rows keep: detached
|
|
702
|
+
// async spawns can stay "pending" while real work is running, so a
|
|
703
|
+
// pending/hourglass or spinner glyph reads wrong in the transcript. Keep
|
|
704
|
+
// the row static; the Task tool header already carries the dispatch icon.
|
|
705
|
+
const dot = theme.styledSymbol("status.done", frozen ? "dim" : "accent");
|
|
706
|
+
const nameColor = frozen ? "dim" : "accent";
|
|
707
|
+
const name = theme.fg(nameColor, description ? theme.bold(displayId) : displayId);
|
|
708
|
+
statusLine = `${indent}${dot} ${name}`;
|
|
694
709
|
if (description) {
|
|
695
|
-
|
|
696
|
-
statusLine += `${theme.fg("accent", ":")} ${desc}`;
|
|
710
|
+
statusLine += `${theme.fg(nameColor, ":")} ${theme.fg(nameColor, description)}`;
|
|
697
711
|
}
|
|
712
|
+
} else if (progress.status === "completed") {
|
|
713
|
+
// Finished rows keep the dot but settle from accent to the plain
|
|
714
|
+
// foreground: completion reads as a color change, not a new glyph.
|
|
715
|
+
statusLine = `${indent}${theme.styledSymbol("status.done", "text")} ${theme.fg("text", titlePart)}`;
|
|
698
716
|
} else {
|
|
699
|
-
|
|
700
|
-
progress.status === "completed" ? theme.styledSymbol("status.done", "accent") : theme.fg(iconColor, icon);
|
|
701
|
-
statusLine = `${indent}${glyph} ${theme.fg("accent", titlePart)}`;
|
|
717
|
+
statusLine = `${indent}${theme.fg(iconColor, icon)} ${theme.fg("accent", titlePart)}`;
|
|
702
718
|
}
|
|
703
719
|
|
|
704
720
|
// Show retry-blocked badge so the parent immediately sees that a child
|
|
@@ -836,7 +852,7 @@ function renderAgentProgress(
|
|
|
836
852
|
const inflight = progress.inflightTaskDetails;
|
|
837
853
|
if (completedTaskCalls.length > 0 || inflight) {
|
|
838
854
|
const snapshots = inflight ? [...completedTaskCalls, inflight] : completedTaskCalls;
|
|
839
|
-
const nestedLines = renderNestedTaskTree(snapshots, expanded, theme, spinnerFrame);
|
|
855
|
+
const nestedLines = renderNestedTaskTree(snapshots, expanded, theme, spinnerFrame, frozen);
|
|
840
856
|
for (const line of nestedLines) {
|
|
841
857
|
lines.push(`${continuePrefix}${line}`);
|
|
842
858
|
}
|
|
@@ -974,7 +990,7 @@ function renderAgentResult(
|
|
|
974
990
|
: needsWarning
|
|
975
991
|
? theme.status.warning
|
|
976
992
|
: success
|
|
977
|
-
? theme.styledSymbol("status.done", "
|
|
993
|
+
? theme.styledSymbol("status.done", "text")
|
|
978
994
|
: theme.status.error;
|
|
979
995
|
const iconColor = needsWarning ? "warning" : success ? "success" : mergeFailed ? "warning" : "error";
|
|
980
996
|
const statusText = aborted
|
|
@@ -991,11 +1007,10 @@ function renderAgentResult(
|
|
|
991
1007
|
const description = result.description?.trim();
|
|
992
1008
|
const displayId = formatTaskId(result.id);
|
|
993
1009
|
const titlePart = description ? `${theme.bold(displayId)}: ${description}` : displayId;
|
|
994
|
-
let statusLine = `${prefix ? `${prefix} ` : ""}${theme.fg(iconColor, icon)} ${theme.fg(
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
)}`;
|
|
1010
|
+
let statusLine = `${prefix ? `${prefix} ` : ""}${theme.fg(iconColor, icon)} ${theme.fg(
|
|
1011
|
+
success && !needsWarning ? "text" : "accent",
|
|
1012
|
+
titlePart,
|
|
1013
|
+
)} ${formatBadge(statusText, iconColor, theme)}`;
|
|
999
1014
|
const showBadge = settings.get("task.showResolvedModelBadge");
|
|
1000
1015
|
statusLine = appendAgentStats(
|
|
1001
1016
|
statusLine,
|
|
@@ -1160,7 +1175,7 @@ function orderResultsForDisplay(results: readonly SingleResult[]): SingleResult[
|
|
|
1160
1175
|
*/
|
|
1161
1176
|
export function renderResult(
|
|
1162
1177
|
result: { content: Array<{ type: string; text?: string }>; details?: TaskToolDetails; isError?: boolean },
|
|
1163
|
-
options:
|
|
1178
|
+
options: TaskRenderOptions,
|
|
1164
1179
|
theme: Theme,
|
|
1165
1180
|
args?: TaskParams,
|
|
1166
1181
|
): Component {
|
|
@@ -1209,8 +1224,16 @@ export function renderResult(
|
|
|
1209
1224
|
const metaLabel = countLabel ? (agentLabel ? `${countLabel}: ${agentLabel}` : countLabel) : agentLabel;
|
|
1210
1225
|
const header = renderStatusLine(
|
|
1211
1226
|
{
|
|
1212
|
-
icon: icon === "success" ? undefined : icon,
|
|
1213
|
-
|
|
1227
|
+
icon: icon === "success" || icon === "running" ? undefined : icon,
|
|
1228
|
+
// While agents are in flight the header shows the dispatch glyph, not a
|
|
1229
|
+
// spinner: async spawns return immediately, so "running" means
|
|
1230
|
+
// "delegated to peers", not "this call is blocking the turn".
|
|
1231
|
+
iconOverride:
|
|
1232
|
+
icon === "running"
|
|
1233
|
+
? theme.styledSymbol("tool.task", "accent")
|
|
1234
|
+
: icon === "success"
|
|
1235
|
+
? theme.styledSymbol("status.done", "accent")
|
|
1236
|
+
: undefined,
|
|
1214
1237
|
title: "Task",
|
|
1215
1238
|
meta: metaLabel ? [metaLabel] : undefined,
|
|
1216
1239
|
},
|
|
@@ -1219,13 +1242,14 @@ export function renderResult(
|
|
|
1219
1242
|
|
|
1220
1243
|
return framedBlock(theme, width => {
|
|
1221
1244
|
const { expanded, isPartial, spinnerFrame } = options;
|
|
1245
|
+
const frozen = options.renderContext?.frozen === true;
|
|
1222
1246
|
const lines: string[] = [];
|
|
1223
1247
|
|
|
1224
1248
|
const shouldRenderProgress =
|
|
1225
1249
|
Boolean(details.progress && details.progress.length > 0) && (isPartial || details.results.length === 0);
|
|
1226
1250
|
if (shouldRenderProgress && details.progress) {
|
|
1227
1251
|
orderProgressForDisplay(details.progress).forEach(progress => {
|
|
1228
|
-
lines.push(...renderAgentProgress(progress, "", " ", expanded, theme, spinnerFrame));
|
|
1252
|
+
lines.push(...renderAgentProgress(progress, "", " ", expanded, theme, spinnerFrame, frozen));
|
|
1229
1253
|
});
|
|
1230
1254
|
} else if (details.results && details.results.length > 0) {
|
|
1231
1255
|
orderResultsForDisplay(details.results).forEach(res => {
|
|
@@ -1345,6 +1369,7 @@ function renderNestedTaskTree(
|
|
|
1345
1369
|
expanded: boolean,
|
|
1346
1370
|
theme: Theme,
|
|
1347
1371
|
spinnerFrame?: number,
|
|
1372
|
+
frozen = false,
|
|
1348
1373
|
): string[] {
|
|
1349
1374
|
const lines: string[] = [];
|
|
1350
1375
|
for (const details of detailsList) {
|
|
@@ -1362,7 +1387,7 @@ function renderNestedTaskTree(
|
|
|
1362
1387
|
const ordered = orderProgressForDisplay(inflight);
|
|
1363
1388
|
ordered.forEach((prog, index) => {
|
|
1364
1389
|
const { prefix, continuePrefix } = nestedMarkers(index === ordered.length - 1, theme);
|
|
1365
|
-
lines.push(...renderAgentProgress(prog, prefix, continuePrefix, expanded, theme, spinnerFrame));
|
|
1390
|
+
lines.push(...renderAgentProgress(prog, prefix, continuePrefix, expanded, theme, spinnerFrame, frozen));
|
|
1366
1391
|
});
|
|
1367
1392
|
}
|
|
1368
1393
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
|
|
2
|
+
import { isZodSchema, zodToWireSchema } from "@oh-my-pi/pi-ai/utils/schema";
|
|
2
3
|
|
|
3
4
|
// ─── Generic Tool Discovery Types ────────────────────────────────────────────
|
|
4
5
|
|
|
@@ -65,6 +66,7 @@ export function isMCPToolName(name: string): boolean {
|
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
function getSchemaPropertyKeys(parameters: unknown): string[] {
|
|
69
|
+
if (isZodSchema(parameters)) parameters = zodToWireSchema(parameters);
|
|
68
70
|
if (!parameters || typeof parameters !== "object" || Array.isArray(parameters)) return [];
|
|
69
71
|
const properties = (parameters as { properties?: unknown }).properties;
|
|
70
72
|
if (!properties || typeof properties !== "object" || Array.isArray(properties)) return [];
|
package/src/tools/bash.ts
CHANGED
|
@@ -31,7 +31,7 @@ import { expandInternalUrls, type InternalUrlExpansionOptions } from "./bash-ski
|
|
|
31
31
|
import { invalidateGithubCacheForBashCommand } from "./gh-cache-invalidation";
|
|
32
32
|
import { formatStyledTruncationWarning, type OutputMeta, stripOutputNotice } from "./output-meta";
|
|
33
33
|
import { resolveToCwd } from "./path-utils";
|
|
34
|
-
import { capPreviewLines, formatToolWorkingDirectory, replaceTabs } from "./render-utils";
|
|
34
|
+
import { capPreviewLines, formatToolWorkingDirectory, previewWindowRows, replaceTabs } from "./render-utils";
|
|
35
35
|
import { ToolAbortError, ToolError } from "./tool-errors";
|
|
36
36
|
import { toolResult } from "./tool-result";
|
|
37
37
|
import { clampTimeout, TOOL_TIMEOUTS } from "./tool-timeouts";
|
|
@@ -1241,6 +1241,7 @@ export function createShellRenderer<TArgs>(config: ShellRendererConfig<TArgs>) {
|
|
|
1241
1241
|
let cachedRawOutput: string | undefined;
|
|
1242
1242
|
let cachedIsPartial: boolean | undefined;
|
|
1243
1243
|
let cachedLines: readonly string[] | undefined;
|
|
1244
|
+
let cachedPreviewWindow: number | undefined;
|
|
1244
1245
|
|
|
1245
1246
|
return markFramedBlockComponent({
|
|
1246
1247
|
render: (width: number): readonly string[] => {
|
|
@@ -1255,6 +1256,7 @@ export function createShellRenderer<TArgs>(config: ShellRendererConfig<TArgs>) {
|
|
|
1255
1256
|
const rawOutput = renderContext?.output ?? result.content?.find(c => c.type === "text")?.text ?? "";
|
|
1256
1257
|
|
|
1257
1258
|
const isPartial = options.isPartial === true;
|
|
1259
|
+
const previewWindow = previewWindowRows();
|
|
1258
1260
|
|
|
1259
1261
|
if (
|
|
1260
1262
|
cachedLines !== undefined &&
|
|
@@ -1262,7 +1264,8 @@ export function createShellRenderer<TArgs>(config: ShellRendererConfig<TArgs>) {
|
|
|
1262
1264
|
cachedPreviewLines === previewLines &&
|
|
1263
1265
|
cachedExpanded === expanded &&
|
|
1264
1266
|
cachedRawOutput === rawOutput &&
|
|
1265
|
-
cachedIsPartial === isPartial
|
|
1267
|
+
cachedIsPartial === isPartial &&
|
|
1268
|
+
cachedPreviewWindow === previewWindow
|
|
1266
1269
|
) {
|
|
1267
1270
|
return cachedLines;
|
|
1268
1271
|
}
|
|
@@ -1348,7 +1351,9 @@ export function createShellRenderer<TArgs>(config: ShellRendererConfig<TArgs>) {
|
|
|
1348
1351
|
state: isPartial ? "pending" : isError ? "error" : "success",
|
|
1349
1352
|
sections: [
|
|
1350
1353
|
{
|
|
1351
|
-
|
|
1354
|
+
// Viewport-sized tail window in every state — streaming and final
|
|
1355
|
+
// render identically; only ctrl+o uncaps.
|
|
1356
|
+
lines: capPreviewLines(cmdLines ?? [], uiTheme, { expanded }),
|
|
1352
1357
|
},
|
|
1353
1358
|
{ label: uiTheme.fg("toolTitle", "Output"), lines: outputLines },
|
|
1354
1359
|
],
|
|
@@ -1362,6 +1367,7 @@ export function createShellRenderer<TArgs>(config: ShellRendererConfig<TArgs>) {
|
|
|
1362
1367
|
cachedExpanded = expanded;
|
|
1363
1368
|
cachedRawOutput = rawOutput;
|
|
1364
1369
|
cachedIsPartial = isPartial;
|
|
1370
|
+
cachedPreviewWindow = previewWindow;
|
|
1365
1371
|
cachedLines = framed;
|
|
1366
1372
|
return framed;
|
|
1367
1373
|
},
|
|
@@ -1373,6 +1379,7 @@ export function createShellRenderer<TArgs>(config: ShellRendererConfig<TArgs>) {
|
|
|
1373
1379
|
cachedExpanded = undefined;
|
|
1374
1380
|
cachedRawOutput = undefined;
|
|
1375
1381
|
cachedIsPartial = undefined;
|
|
1382
|
+
cachedPreviewWindow = undefined;
|
|
1376
1383
|
},
|
|
1377
1384
|
});
|
|
1378
1385
|
},
|
package/src/tools/eval-render.ts
CHANGED
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
formatDuration,
|
|
34
34
|
formatStatusIcon,
|
|
35
35
|
formatTitle,
|
|
36
|
+
previewWindowRows,
|
|
36
37
|
replaceTabs,
|
|
37
38
|
shortenPath,
|
|
38
39
|
truncateToWidth,
|
|
@@ -493,7 +494,7 @@ export const evalToolRenderer = {
|
|
|
493
494
|
|
|
494
495
|
return markFramedBlockComponent({
|
|
495
496
|
render: (width: number): readonly string[] => {
|
|
496
|
-
const key = `${options.expanded ? 1 : 0}|${cells.map(c => `${c.language}:${c.title ?? ""}:${c.code.length}`).join("|")}`;
|
|
497
|
+
const key = `${options.expanded ? 1 : 0}|${previewWindowRows()}|${cells.map(c => `${c.language}:${c.title ?? ""}:${c.code.length}`).join("|")}`;
|
|
497
498
|
if (cached && cached.key === key && cached.width === width) {
|
|
498
499
|
return cached.result;
|
|
499
500
|
}
|
|
@@ -510,9 +511,11 @@ export const evalToolRenderer = {
|
|
|
510
511
|
title: cell.title,
|
|
511
512
|
status: "pending",
|
|
512
513
|
width,
|
|
513
|
-
//
|
|
514
|
-
//
|
|
515
|
-
|
|
514
|
+
// Viewport-sized tail window following the newest streamed code
|
|
515
|
+
// line; renderResult keeps the same cap so the cell never snaps
|
|
516
|
+
// open on completion. Only ctrl+o uncaps.
|
|
517
|
+
codeTail: true,
|
|
518
|
+
codeMaxLines: previewWindowRows(),
|
|
516
519
|
expanded: options.expanded,
|
|
517
520
|
},
|
|
518
521
|
uiTheme,
|
|
@@ -576,7 +579,7 @@ export const evalToolRenderer = {
|
|
|
576
579
|
render: (width: number): readonly string[] => {
|
|
577
580
|
const expanded = options.renderContext?.expanded ?? options.expanded;
|
|
578
581
|
const previewLines = options.renderContext?.previewLines ?? EVAL_DEFAULT_PREVIEW_LINES;
|
|
579
|
-
const key = `${expanded}|${previewLines}|${options.spinnerFrame}`;
|
|
582
|
+
const key = `${expanded}|${previewLines}|${options.spinnerFrame}|${previewWindowRows()}`;
|
|
580
583
|
if (cached && cached.key === key && cached.width === width) {
|
|
581
584
|
return cached.result;
|
|
582
585
|
}
|
|
@@ -613,9 +616,11 @@ export const evalToolRenderer = {
|
|
|
613
616
|
duration: cell.durationMs,
|
|
614
617
|
output: outputLines.length > 0 ? outputLines.join("\n") : undefined,
|
|
615
618
|
outputMaxLines: outputLines.length,
|
|
616
|
-
//
|
|
617
|
-
//
|
|
618
|
-
|
|
619
|
+
// Same viewport-sized tail window as the pending preview so the
|
|
620
|
+
// cell never snaps open on completion; only ctrl+o uncaps.
|
|
621
|
+
// `output` keeps its own preview cap from above.
|
|
622
|
+
codeTail: true,
|
|
623
|
+
codeMaxLines: previewWindowRows(),
|
|
619
624
|
expanded,
|
|
620
625
|
width,
|
|
621
626
|
},
|
package/src/tools/gh.ts
CHANGED
|
@@ -63,6 +63,44 @@ const GH_ISSUE_FIELDS_NO_COMMENTS = [
|
|
|
63
63
|
"updatedAt",
|
|
64
64
|
"url",
|
|
65
65
|
];
|
|
66
|
+
|
|
67
|
+
const GH_ISSUE_STATE_REASON_FIELD = "stateReason";
|
|
68
|
+
|
|
69
|
+
function ghJsonErrorNamesField(err: unknown, field: string): boolean {
|
|
70
|
+
if (!(err instanceof Error) || !err.message.includes("Unknown JSON field")) return false;
|
|
71
|
+
return err.message.includes(`"${field}"`) || err.message.includes(`'${field}'`) || err.message.includes(field);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function dropJsonField(args: readonly string[], field: string): string[] | undefined {
|
|
75
|
+
const next = [...args];
|
|
76
|
+
const jsonIndex = next.indexOf("--json");
|
|
77
|
+
if (jsonIndex < 0) return undefined;
|
|
78
|
+
const fields = next[jsonIndex + 1];
|
|
79
|
+
if (!fields) return undefined;
|
|
80
|
+
const splitFields = fields.split(",");
|
|
81
|
+
const kept = splitFields.filter(candidate => candidate !== field);
|
|
82
|
+
if (kept.length === splitFields.length) return undefined;
|
|
83
|
+
next[jsonIndex + 1] = kept.join(",");
|
|
84
|
+
return next;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Runs `gh --json` for issue data, retrying without optional stateReason on older gh releases. */
|
|
88
|
+
export async function githubIssueJsonWithStateReasonFallback<T>(
|
|
89
|
+
cwd: string,
|
|
90
|
+
args: readonly string[],
|
|
91
|
+
signal: AbortSignal | undefined,
|
|
92
|
+
options?: git.GhCommandOptions,
|
|
93
|
+
): Promise<T> {
|
|
94
|
+
try {
|
|
95
|
+
return await git.github.json<T>(cwd, [...args], signal, options);
|
|
96
|
+
} catch (err) {
|
|
97
|
+
if (!ghJsonErrorNamesField(err, GH_ISSUE_STATE_REASON_FIELD)) throw err;
|
|
98
|
+
const retryArgs = dropJsonField(args, GH_ISSUE_STATE_REASON_FIELD);
|
|
99
|
+
if (!retryArgs) throw err;
|
|
100
|
+
return await git.github.json<T>(cwd, retryArgs, signal, options);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
66
104
|
const GH_PR_FIELDS = [
|
|
67
105
|
"author",
|
|
68
106
|
"baseRefName",
|
|
@@ -2549,7 +2587,7 @@ async function fetchIssueViewFresh(
|
|
|
2549
2587
|
const args = ["issue", "view", identifier];
|
|
2550
2588
|
appendRepoFlag(args, repo, identifier);
|
|
2551
2589
|
args.push("--json", (includeComments ? GH_ISSUE_FIELDS : GH_ISSUE_FIELDS_NO_COMMENTS).join(","));
|
|
2552
|
-
const data = await
|
|
2590
|
+
const data = await githubIssueJsonWithStateReasonFallback<GhIssueViewData>(cwd, args, signal, {
|
|
2553
2591
|
repoProvided: Boolean(repo),
|
|
2554
2592
|
});
|
|
2555
2593
|
const rendered = formatIssueView(data, { issue: identifier, repo, comments: includeComments });
|
package/src/tools/image-gen.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as os from "node:os";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import { type ApiKey, type FetchImpl, getEnvApiKey, type Model, withAuth } from "@oh-my-pi/pi-ai";
|
|
3
|
+
import { type ApiKey, type FetchImpl, getEnvApiKey, type Model, ProviderHttpError, withAuth } from "@oh-my-pi/pi-ai";
|
|
4
4
|
import {
|
|
5
5
|
CODEX_BASE_URL,
|
|
6
6
|
getCodexAccountId,
|
|
@@ -47,7 +47,7 @@ export type ImageProviderPreference = Exclude<ImageProvider, "openai-codex"> | "
|
|
|
47
47
|
|
|
48
48
|
interface ImageApiKey {
|
|
49
49
|
provider: ImageProvider;
|
|
50
|
-
apiKey:
|
|
50
|
+
apiKey: ApiKey;
|
|
51
51
|
projectId?: string;
|
|
52
52
|
model?: Model;
|
|
53
53
|
}
|
|
@@ -502,6 +502,39 @@ async function findXAIImageCredentials(modelRegistry?: ModelRegistry): Promise<I
|
|
|
502
502
|
return null;
|
|
503
503
|
}
|
|
504
504
|
|
|
505
|
+
async function findOpenRouterImageCredentials(
|
|
506
|
+
modelRegistry?: ModelRegistry,
|
|
507
|
+
sessionId?: string,
|
|
508
|
+
): Promise<ImageApiKey | null> {
|
|
509
|
+
if (modelRegistry) {
|
|
510
|
+
// AuthStorage.getApiKey already falls back to env keys, so this covers OPENROUTER_API_KEY too.
|
|
511
|
+
const apiKey = await modelRegistry.getApiKeyForProvider("openrouter", sessionId);
|
|
512
|
+
if (apiKey) return { provider: "openrouter", apiKey: modelRegistry.resolver("openrouter", { sessionId }) };
|
|
513
|
+
return null;
|
|
514
|
+
}
|
|
515
|
+
const apiKey = getEnvApiKey("openrouter");
|
|
516
|
+
if (apiKey) return { provider: "openrouter", apiKey };
|
|
517
|
+
return null;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
async function findGeminiImageCredentials(
|
|
521
|
+
modelRegistry?: ModelRegistry,
|
|
522
|
+
sessionId?: string,
|
|
523
|
+
): Promise<ImageApiKey | null> {
|
|
524
|
+
if (modelRegistry) {
|
|
525
|
+
// AuthStorage.getApiKey already falls back to env keys (GEMINI_API_KEY), so only
|
|
526
|
+
// GOOGLE_API_KEY needs the explicit check below.
|
|
527
|
+
const apiKey = await modelRegistry.getApiKeyForProvider("google", sessionId);
|
|
528
|
+
if (apiKey) return { provider: "gemini", apiKey: modelRegistry.resolver("google", { sessionId }) };
|
|
529
|
+
} else {
|
|
530
|
+
const envKey = getEnvApiKey("google");
|
|
531
|
+
if (envKey) return { provider: "gemini", apiKey: envKey };
|
|
532
|
+
}
|
|
533
|
+
const googleKey = $env.GOOGLE_API_KEY;
|
|
534
|
+
if (googleKey) return { provider: "gemini", apiKey: googleKey };
|
|
535
|
+
return null;
|
|
536
|
+
}
|
|
537
|
+
|
|
505
538
|
async function findOpenAIHostedImageCredentials(
|
|
506
539
|
modelRegistry: ModelRegistry | undefined,
|
|
507
540
|
activeModel: Model | undefined,
|
|
@@ -532,14 +565,12 @@ async function findImageApiKey(
|
|
|
532
565
|
if (antigravity) return antigravity;
|
|
533
566
|
// Fall through to auto-detect if preferred provider key not found.
|
|
534
567
|
} else if (preferredImageProvider === "gemini") {
|
|
535
|
-
const
|
|
536
|
-
if (
|
|
537
|
-
const googleKey = $env.GOOGLE_API_KEY;
|
|
538
|
-
if (googleKey) return { provider: "gemini", apiKey: googleKey };
|
|
568
|
+
const gemini = await findGeminiImageCredentials(modelRegistry, sessionId);
|
|
569
|
+
if (gemini) return gemini;
|
|
539
570
|
// Fall through to auto-detect if preferred provider key not found.
|
|
540
571
|
} else if (preferredImageProvider === "openrouter") {
|
|
541
|
-
const
|
|
542
|
-
if (
|
|
572
|
+
const openRouter = await findOpenRouterImageCredentials(modelRegistry, sessionId);
|
|
573
|
+
if (openRouter) return openRouter;
|
|
543
574
|
// Fall through to auto-detect if preferred provider key not found.
|
|
544
575
|
} else if (preferredImageProvider === "xai") {
|
|
545
576
|
const xai = await findXAIImageCredentials(modelRegistry);
|
|
@@ -559,14 +590,11 @@ async function findImageApiKey(
|
|
|
559
590
|
const xai = await findXAIImageCredentials(modelRegistry);
|
|
560
591
|
if (xai) return xai;
|
|
561
592
|
|
|
562
|
-
const
|
|
563
|
-
if (
|
|
593
|
+
const openRouter = await findOpenRouterImageCredentials(modelRegistry, sessionId);
|
|
594
|
+
if (openRouter) return openRouter;
|
|
564
595
|
|
|
565
|
-
const
|
|
566
|
-
if (
|
|
567
|
-
|
|
568
|
-
const googleKey = $env.GOOGLE_API_KEY;
|
|
569
|
-
if (googleKey) return { provider: "gemini", apiKey: googleKey };
|
|
596
|
+
const gemini = await findGeminiImageCredentials(modelRegistry, sessionId);
|
|
597
|
+
if (gemini) return gemini;
|
|
570
598
|
|
|
571
599
|
return null;
|
|
572
600
|
}
|
|
@@ -1054,11 +1082,7 @@ export const imageGenTool: CustomTool<typeof imageGenSchema, ImageGenToolDetails
|
|
|
1054
1082
|
}
|
|
1055
1083
|
|
|
1056
1084
|
const hostedModel = apiKey.model;
|
|
1057
|
-
const hostedKey: ApiKey = ctx.modelRegistry.resolver(hostedModel
|
|
1058
|
-
sessionId,
|
|
1059
|
-
baseUrl: hostedModel.baseUrl,
|
|
1060
|
-
modelId: hostedModel.id,
|
|
1061
|
-
});
|
|
1085
|
+
const hostedKey: ApiKey = ctx.modelRegistry.resolver(hostedModel, sessionId);
|
|
1062
1086
|
|
|
1063
1087
|
const parsed = await withAuth(
|
|
1064
1088
|
hostedKey,
|
|
@@ -1161,9 +1185,11 @@ export const imageGenTool: CustomTool<typeof imageGenSchema, ImageGenToolDetails
|
|
|
1161
1185
|
} catch {
|
|
1162
1186
|
// Keep raw text.
|
|
1163
1187
|
}
|
|
1164
|
-
throw
|
|
1165
|
-
|
|
1166
|
-
|
|
1188
|
+
throw new ProviderHttpError(
|
|
1189
|
+
`Antigravity image request failed (${resp.status}): ${message}`,
|
|
1190
|
+
resp.status,
|
|
1191
|
+
{ headers: resp.headers },
|
|
1192
|
+
);
|
|
1167
1193
|
}
|
|
1168
1194
|
return resp;
|
|
1169
1195
|
},
|
|
@@ -1267,8 +1293,8 @@ export const imageGenTool: CustomTool<typeof imageGenSchema, ImageGenToolDetails
|
|
|
1267
1293
|
} catch {
|
|
1268
1294
|
// Keep raw text.
|
|
1269
1295
|
}
|
|
1270
|
-
throw
|
|
1271
|
-
|
|
1296
|
+
throw new ProviderHttpError(`xAI image request failed (${resp.status}): ${message}`, resp.status, {
|
|
1297
|
+
headers: resp.headers,
|
|
1272
1298
|
});
|
|
1273
1299
|
}
|
|
1274
1300
|
return rawText;
|
|
@@ -1331,34 +1357,40 @@ export const imageGenTool: CustomTool<typeof imageGenSchema, ImageGenToolDetails
|
|
|
1331
1357
|
messages: [{ role: "user" as const, content: contentParts }],
|
|
1332
1358
|
};
|
|
1333
1359
|
|
|
1334
|
-
const rawText = await withAuth(
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
if (!resp.ok) {
|
|
1349
|
-
let message = text;
|
|
1350
|
-
try {
|
|
1351
|
-
const parsed = JSON.parse(text) as { error?: { message?: string } };
|
|
1352
|
-
message = parsed.error?.message ?? message;
|
|
1353
|
-
} catch {
|
|
1354
|
-
// Keep raw text.
|
|
1355
|
-
}
|
|
1356
|
-
throw Object.assign(new Error(`OpenRouter image request failed (${resp.status}): ${message}`), {
|
|
1357
|
-
status: resp.status,
|
|
1360
|
+
const rawText = await withAuth(
|
|
1361
|
+
apiKey.apiKey,
|
|
1362
|
+
async key => {
|
|
1363
|
+
const resp = await fetchImpl("https://openrouter.ai/api/v1/chat/completions", {
|
|
1364
|
+
method: "POST",
|
|
1365
|
+
headers: {
|
|
1366
|
+
"Content-Type": "application/json",
|
|
1367
|
+
Authorization: `Bearer ${key}`,
|
|
1368
|
+
"HTTP-Referer": "https://omp.sh/",
|
|
1369
|
+
"X-OpenRouter-Title": "Oh-My-Pi",
|
|
1370
|
+
"X-OpenRouter-Categories": "cli-agent",
|
|
1371
|
+
},
|
|
1372
|
+
body: JSON.stringify(requestBody),
|
|
1373
|
+
signal: requestSignal,
|
|
1358
1374
|
});
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1375
|
+
const text = await resp.text();
|
|
1376
|
+
if (!resp.ok) {
|
|
1377
|
+
let message = text;
|
|
1378
|
+
try {
|
|
1379
|
+
const parsed = JSON.parse(text) as { error?: { message?: string } };
|
|
1380
|
+
message = parsed.error?.message ?? message;
|
|
1381
|
+
} catch {
|
|
1382
|
+
// Keep raw text.
|
|
1383
|
+
}
|
|
1384
|
+
throw new ProviderHttpError(
|
|
1385
|
+
`OpenRouter image request failed (${resp.status}): ${message}`,
|
|
1386
|
+
resp.status,
|
|
1387
|
+
{ headers: resp.headers },
|
|
1388
|
+
);
|
|
1389
|
+
}
|
|
1390
|
+
return text;
|
|
1391
|
+
},
|
|
1392
|
+
{ signal: requestSignal },
|
|
1393
|
+
);
|
|
1362
1394
|
|
|
1363
1395
|
const data = JSON.parse(rawText) as OpenRouterResponse;
|
|
1364
1396
|
const message = data.choices?.[0]?.message;
|
|
@@ -1426,34 +1458,38 @@ export const imageGenTool: CustomTool<typeof imageGenSchema, ImageGenToolDetails
|
|
|
1426
1458
|
generationConfig,
|
|
1427
1459
|
};
|
|
1428
1460
|
|
|
1429
|
-
const rawText = await withAuth(
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1461
|
+
const rawText = await withAuth(
|
|
1462
|
+
apiKey.apiKey,
|
|
1463
|
+
async key => {
|
|
1464
|
+
const resp = await fetchImpl(
|
|
1465
|
+
`https://generativelanguage.googleapis.com/v1beta/models/${encodeURIComponent(model)}:generateContent`,
|
|
1466
|
+
{
|
|
1467
|
+
method: "POST",
|
|
1468
|
+
headers: {
|
|
1469
|
+
"Content-Type": "application/json",
|
|
1470
|
+
"x-goog-api-key": key,
|
|
1471
|
+
},
|
|
1472
|
+
body: JSON.stringify(requestBody),
|
|
1473
|
+
signal: requestSignal,
|
|
1437
1474
|
},
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1475
|
+
);
|
|
1476
|
+
const text = await resp.text();
|
|
1477
|
+
if (!resp.ok) {
|
|
1478
|
+
let message = text;
|
|
1479
|
+
try {
|
|
1480
|
+
const parsed = JSON.parse(text) as { error?: { message?: string } };
|
|
1481
|
+
message = parsed.error?.message ?? message;
|
|
1482
|
+
} catch {
|
|
1483
|
+
// Keep raw text.
|
|
1484
|
+
}
|
|
1485
|
+
throw new ProviderHttpError(`Gemini image request failed (${resp.status}): ${message}`, resp.status, {
|
|
1486
|
+
headers: resp.headers,
|
|
1487
|
+
});
|
|
1450
1488
|
}
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
return text;
|
|
1456
|
-
});
|
|
1489
|
+
return text;
|
|
1490
|
+
},
|
|
1491
|
+
{ signal: requestSignal },
|
|
1492
|
+
);
|
|
1457
1493
|
|
|
1458
1494
|
const data = JSON.parse(rawText) as GeminiGenerateContentResponse;
|
|
1459
1495
|
const responseParts = combineParts(data);
|
|
@@ -138,11 +138,7 @@ export class InspectImageTool implements AgentTool<typeof inspectImageSchema, In
|
|
|
138
138
|
],
|
|
139
139
|
},
|
|
140
140
|
{
|
|
141
|
-
apiKey: modelRegistry.resolver(model.
|
|
142
|
-
sessionId: this.session.getSessionId?.() ?? undefined,
|
|
143
|
-
baseUrl: model.baseUrl,
|
|
144
|
-
modelId: model.id,
|
|
145
|
-
}),
|
|
141
|
+
apiKey: modelRegistry.resolver(model, this.session.getSessionId?.() ?? undefined),
|
|
146
142
|
signal,
|
|
147
143
|
},
|
|
148
144
|
{ telemetry, oneshotKind: "inspect_image", completeImpl: this.completeImageRequest },
|