@oh-my-pi/pi-coding-agent 15.11.3 → 15.11.4
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 +54 -0
- package/dist/cli.js +353 -294
- 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 +458 -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/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 +12 -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/theme/theme.d.ts +23 -3
- package/dist/types/session/agent-session.d.ts +14 -7
- package/dist/types/session/auth-storage.d.ts +1 -1
- package/dist/types/session/snapcompact-inline.d.ts +28 -0
- package/dist/types/slash-commands/helpers/active-oauth-account.d.ts +14 -0
- package/dist/types/system-prompt.d.ts +3 -1
- package/dist/types/task/render.d.ts +16 -6
- package/dist/types/tools/gh.d.ts +3 -0
- package/dist/types/tools/render-utils.d.ts +8 -16
- 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/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 +601 -153
- 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 +5 -5
- 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/settings-defs.ts +19 -4
- package/src/modes/components/settings-selector.ts +493 -93
- 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 +69 -12
- package/src/modes/components/transcript-container.ts +26 -0
- package/src/modes/components/tree-selector.ts +16 -6
- package/src/modes/controllers/command-controller.ts +37 -7
- 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 +81 -61
- package/src/modes/interactive-mode.ts +4 -2
- package/src/modes/rpc/rpc-mode.ts +2 -1
- package/src/modes/shared.ts +2 -0
- package/src/modes/theme/theme.ts +100 -7
- package/src/modes/utils/context-usage.ts +3 -1
- 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-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/task.md +3 -3
- package/src/sdk.ts +22 -1
- package/src/session/agent-session.ts +91 -24
- package/src/session/auth-storage.ts +1 -0
- package/src/session/session-dump-format.ts +8 -1
- package/src/session/session-manager.ts +5 -5
- package/src/session/snapcompact-inline.ts +187 -0
- package/src/slash-commands/helpers/active-oauth-account.ts +44 -0
- package/src/slash-commands/helpers/usage-report.ts +24 -3
- package/src/system-prompt.ts +15 -1
- package/src/task/render.ts +29 -19
- 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/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/system-prompt.ts
CHANGED
|
@@ -9,17 +9,27 @@ import { $ } from "bun";
|
|
|
9
9
|
import { contextFileCapability } from "./capability/context-file";
|
|
10
10
|
import { systemPromptCapability } from "./capability/system-prompt";
|
|
11
11
|
import { findConfigFile } from "./config";
|
|
12
|
-
import type { SkillsSettings } from "./config/settings";
|
|
12
|
+
import type { Personality, SkillsSettings } from "./config/settings";
|
|
13
13
|
import { type ContextFile, loadCapability, type SystemPrompt as SystemPromptFile } from "./discovery";
|
|
14
14
|
import { expandAtImports } from "./discovery/at-imports";
|
|
15
15
|
import { loadSkills, type Skill } from "./extensibility/skills";
|
|
16
16
|
import { hasObsidian } from "./internal-urls/vault-protocol";
|
|
17
17
|
import customSystemPromptTemplate from "./prompts/system/custom-system-prompt.md" with { type: "text" };
|
|
18
|
+
import defaultPersonality from "./prompts/system/personalities/default.md" with { type: "text" };
|
|
19
|
+
import friendlyPersonality from "./prompts/system/personalities/friendly.md" with { type: "text" };
|
|
20
|
+
import pragmaticPersonality from "./prompts/system/personalities/pragmatic.md" with { type: "text" };
|
|
18
21
|
import projectPromptTemplate from "./prompts/system/project-prompt.md" with { type: "text" };
|
|
19
22
|
import systemPromptTemplate from "./prompts/system/system-prompt.md" with { type: "text" };
|
|
20
23
|
import { shortenPath } from "./tools/render-utils";
|
|
21
24
|
import { AGENTS_MD_LIMIT, buildWorkspaceTree, type WorkspaceTree } from "./workspace-tree";
|
|
22
25
|
|
|
26
|
+
/** Bundled personality specs, keyed by the `personality` setting value. */
|
|
27
|
+
const PERSONALITY_SPECS: Record<Exclude<Personality, "none">, string> = {
|
|
28
|
+
default: defaultPersonality,
|
|
29
|
+
friendly: friendlyPersonality,
|
|
30
|
+
pragmatic: pragmaticPersonality,
|
|
31
|
+
};
|
|
32
|
+
|
|
23
33
|
interface AlwaysApplyRule {
|
|
24
34
|
name: string;
|
|
25
35
|
content: string;
|
|
@@ -385,6 +395,8 @@ export interface BuildSystemPromptOptions {
|
|
|
385
395
|
memoryRootEnabled?: boolean;
|
|
386
396
|
/** Active model identifier (e.g. "anthropic/claude-opus-4") surfaced to the agent. */
|
|
387
397
|
model?: string;
|
|
398
|
+
/** Personality preset rendered into the default system prompt. "none" omits the block. Default: "default" */
|
|
399
|
+
personality?: Personality;
|
|
388
400
|
}
|
|
389
401
|
|
|
390
402
|
/** Result of building provider-facing system prompt messages. */
|
|
@@ -419,6 +431,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
419
431
|
workspaceTree: providedWorkspaceTree,
|
|
420
432
|
memoryRootEnabled = false,
|
|
421
433
|
model,
|
|
434
|
+
personality = "default",
|
|
422
435
|
} = options;
|
|
423
436
|
const resolvedCwd = cwd ?? getProjectDir();
|
|
424
437
|
|
|
@@ -590,6 +603,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
590
603
|
dateTime,
|
|
591
604
|
cwd: promptCwd,
|
|
592
605
|
model: model ?? "",
|
|
606
|
+
personality: personality === "none" ? "" : PERSONALITY_SPECS[personality].trim(),
|
|
593
607
|
intentTracing: !!intentField,
|
|
594
608
|
intentField: intentField ?? "",
|
|
595
609
|
mcpDiscoveryMode,
|
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.
|
|
@@ -614,11 +625,7 @@ 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
|
const header = renderStatusLine({ icon: "pending", title: "Task", description: args.agent }, theme);
|
|
624
631
|
const assignmentSection = createAssignmentSectionRenderer(args, theme);
|
|
@@ -666,6 +673,7 @@ function renderAgentProgress(
|
|
|
666
673
|
expanded: boolean,
|
|
667
674
|
theme: Theme,
|
|
668
675
|
spinnerFrame?: number,
|
|
676
|
+
frozen = false,
|
|
669
677
|
): string[] {
|
|
670
678
|
const lines: string[] = [];
|
|
671
679
|
|
|
@@ -684,16 +692,16 @@ function renderAgentProgress(
|
|
|
684
692
|
const indent = prefix ? `${prefix} ` : "";
|
|
685
693
|
let statusLine: string;
|
|
686
694
|
if (progress.status === "running" || progress.status === "pending") {
|
|
687
|
-
// Live (or queued) agents
|
|
688
|
-
//
|
|
689
|
-
// the
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
const
|
|
693
|
-
|
|
695
|
+
// Live (or queued) agents use the task icon: detached async spawns can
|
|
696
|
+
// stay "pending" while real work is running, so a pending/hourglass glyph
|
|
697
|
+
// reads wrong in the transcript. Keep the row static; the Task tool header
|
|
698
|
+
// already carries any live animation.
|
|
699
|
+
const taskIcon = theme.styledSymbol("tool.task", frozen ? "dim" : "accent");
|
|
700
|
+
const nameColor = frozen ? "dim" : "accent";
|
|
701
|
+
const name = theme.fg(nameColor, description ? theme.bold(displayId) : displayId);
|
|
702
|
+
statusLine = `${indent}${taskIcon} ${name}`;
|
|
694
703
|
if (description) {
|
|
695
|
-
|
|
696
|
-
statusLine += `${theme.fg("accent", ":")} ${desc}`;
|
|
704
|
+
statusLine += `${theme.fg(nameColor, ":")} ${theme.fg(nameColor, description)}`;
|
|
697
705
|
}
|
|
698
706
|
} else {
|
|
699
707
|
const glyph =
|
|
@@ -836,7 +844,7 @@ function renderAgentProgress(
|
|
|
836
844
|
const inflight = progress.inflightTaskDetails;
|
|
837
845
|
if (completedTaskCalls.length > 0 || inflight) {
|
|
838
846
|
const snapshots = inflight ? [...completedTaskCalls, inflight] : completedTaskCalls;
|
|
839
|
-
const nestedLines = renderNestedTaskTree(snapshots, expanded, theme, spinnerFrame);
|
|
847
|
+
const nestedLines = renderNestedTaskTree(snapshots, expanded, theme, spinnerFrame, frozen);
|
|
840
848
|
for (const line of nestedLines) {
|
|
841
849
|
lines.push(`${continuePrefix}${line}`);
|
|
842
850
|
}
|
|
@@ -1160,7 +1168,7 @@ function orderResultsForDisplay(results: readonly SingleResult[]): SingleResult[
|
|
|
1160
1168
|
*/
|
|
1161
1169
|
export function renderResult(
|
|
1162
1170
|
result: { content: Array<{ type: string; text?: string }>; details?: TaskToolDetails; isError?: boolean },
|
|
1163
|
-
options:
|
|
1171
|
+
options: TaskRenderOptions,
|
|
1164
1172
|
theme: Theme,
|
|
1165
1173
|
args?: TaskParams,
|
|
1166
1174
|
): Component {
|
|
@@ -1219,13 +1227,14 @@ export function renderResult(
|
|
|
1219
1227
|
|
|
1220
1228
|
return framedBlock(theme, width => {
|
|
1221
1229
|
const { expanded, isPartial, spinnerFrame } = options;
|
|
1230
|
+
const frozen = options.renderContext?.frozen === true;
|
|
1222
1231
|
const lines: string[] = [];
|
|
1223
1232
|
|
|
1224
1233
|
const shouldRenderProgress =
|
|
1225
1234
|
Boolean(details.progress && details.progress.length > 0) && (isPartial || details.results.length === 0);
|
|
1226
1235
|
if (shouldRenderProgress && details.progress) {
|
|
1227
1236
|
orderProgressForDisplay(details.progress).forEach(progress => {
|
|
1228
|
-
lines.push(...renderAgentProgress(progress, "", " ", expanded, theme, spinnerFrame));
|
|
1237
|
+
lines.push(...renderAgentProgress(progress, "", " ", expanded, theme, spinnerFrame, frozen));
|
|
1229
1238
|
});
|
|
1230
1239
|
} else if (details.results && details.results.length > 0) {
|
|
1231
1240
|
orderResultsForDisplay(details.results).forEach(res => {
|
|
@@ -1345,6 +1354,7 @@ function renderNestedTaskTree(
|
|
|
1345
1354
|
expanded: boolean,
|
|
1346
1355
|
theme: Theme,
|
|
1347
1356
|
spinnerFrame?: number,
|
|
1357
|
+
frozen = false,
|
|
1348
1358
|
): string[] {
|
|
1349
1359
|
const lines: string[] = [];
|
|
1350
1360
|
for (const details of detailsList) {
|
|
@@ -1362,7 +1372,7 @@ function renderNestedTaskTree(
|
|
|
1362
1372
|
const ordered = orderProgressForDisplay(inflight);
|
|
1363
1373
|
ordered.forEach((prog, index) => {
|
|
1364
1374
|
const { prefix, continuePrefix } = nestedMarkers(index === ordered.length - 1, theme);
|
|
1365
|
-
lines.push(...renderAgentProgress(prog, prefix, continuePrefix, expanded, theme, spinnerFrame));
|
|
1375
|
+
lines.push(...renderAgentProgress(prog, prefix, continuePrefix, expanded, theme, spinnerFrame, frozen));
|
|
1366
1376
|
});
|
|
1367
1377
|
}
|
|
1368
1378
|
}
|
|
@@ -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 },
|