@oh-my-pi/pi-coding-agent 13.15.2 → 13.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -16
- package/package.json +7 -7
- package/src/config/keybindings.ts +6 -0
- package/src/config/model-registry.ts +215 -57
- package/src/config/settings-schema.ts +27 -0
- package/src/extensibility/extensions/types.ts +6 -1
- package/src/extensibility/hooks/types.ts +1 -1
- package/src/internal-urls/docs-index.generated.ts +1 -1
- package/src/modes/components/custom-editor.ts +6 -4
- package/src/modes/components/hook-editor.ts +57 -8
- package/src/modes/components/model-selector.ts +48 -29
- package/src/modes/components/settings-defs.ts +10 -1
- package/src/modes/components/settings-selector.ts +92 -5
- package/src/modes/controllers/extension-ui-controller.ts +32 -4
- package/src/modes/controllers/input-controller.ts +22 -9
- package/src/modes/controllers/selector-controller.ts +2 -2
- package/src/modes/interactive-mode.ts +7 -2
- package/src/modes/rpc/rpc-mode.ts +78 -30
- package/src/modes/rpc/rpc-types.ts +9 -1
- package/src/modes/theme/theme.ts +70 -0
- package/src/modes/types.ts +6 -1
- package/src/modes/utils/hotkeys-markdown.ts +1 -1
- package/src/prompts/system/custom-system-prompt.md +5 -0
- package/src/prompts/system/system-prompt.md +6 -0
- package/src/prompts/tools/ask.md +1 -0
- package/src/prompts/tools/hashline.md +20 -5
- package/src/sdk.ts +9 -1
- package/src/session/agent-session.ts +338 -80
- package/src/session/messages.ts +23 -0
- package/src/session/session-manager.ts +65 -0
- package/src/system-prompt.ts +63 -2
- package/src/tools/ask.ts +109 -61
- package/src/tools/ast-edit.ts +2 -16
- package/src/tools/ast-grep.ts +2 -17
- package/src/tools/browser.ts +35 -17
- package/src/tools/grep.ts +4 -17
- package/src/tools/path-utils.ts +7 -0
- package/src/tools/render-utils.ts +27 -0
- package/src/tui/tree-list.ts +51 -22
- package/src/utils/image-input.ts +11 -1
- package/src/web/search/providers/codex.ts +10 -3
package/src/tui/tree-list.ts
CHANGED
|
@@ -10,42 +10,71 @@ export interface TreeListOptions<T> {
|
|
|
10
10
|
items: T[];
|
|
11
11
|
expanded?: boolean;
|
|
12
12
|
maxCollapsed?: number;
|
|
13
|
+
/** Strict total-line budget for collapsed mode. When set (and not expanded),
|
|
14
|
+
* rendered item lines plus the trailing summary line must fit within this budget.
|
|
15
|
+
*/
|
|
16
|
+
maxCollapsedLines?: number;
|
|
13
17
|
itemType?: string;
|
|
18
|
+
/** Called once per item with `isLast: false` during budget calculation;
|
|
19
|
+
* line count MUST NOT vary based on `isLast`. */
|
|
14
20
|
renderItem: (item: T, context: TreeContext) => string | string[];
|
|
15
21
|
}
|
|
16
22
|
|
|
17
23
|
export function renderTreeList<T>(options: TreeListOptions<T>, theme: Theme): string[] {
|
|
18
|
-
const { items, expanded = false, maxCollapsed = 8, itemType = "item", renderItem } = options;
|
|
19
|
-
const lines: string[] = [];
|
|
24
|
+
const { items, expanded = false, maxCollapsed = 8, maxCollapsedLines, itemType = "item", renderItem } = options;
|
|
20
25
|
const maxItems = expanded ? items.length : Math.min(items.length, maxCollapsed);
|
|
26
|
+
const linesBudget = !expanded && maxCollapsedLines !== undefined ? maxCollapsedLines : Infinity;
|
|
21
27
|
|
|
28
|
+
// Pre-render each candidate item once.
|
|
29
|
+
// isLast cannot be known at this point (fittingCount is not yet determined);
|
|
30
|
+
// renderItem implementations MUST NOT vary line count based on isLast.
|
|
31
|
+
const preRendered: string[][] = [];
|
|
22
32
|
for (let i = 0; i < maxItems; i++) {
|
|
23
|
-
const
|
|
24
|
-
const branch = getTreeBranch(isLast, theme);
|
|
25
|
-
const prefix = `${theme.fg("dim", branch)} `;
|
|
26
|
-
const continuePrefix = `${theme.fg("dim", getTreeContinuePrefix(isLast, theme))}`;
|
|
27
|
-
const context: TreeContext = {
|
|
33
|
+
const rendered = renderItem(items[i], {
|
|
28
34
|
index: i,
|
|
29
|
-
isLast,
|
|
35
|
+
isLast: false,
|
|
30
36
|
depth: 0,
|
|
31
37
|
theme,
|
|
32
|
-
prefix,
|
|
33
|
-
continuePrefix,
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
38
|
+
prefix: "",
|
|
39
|
+
continuePrefix: "",
|
|
40
|
+
});
|
|
41
|
+
preRendered.push(Array.isArray(rendered) ? rendered : rendered ? [rendered] : []);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Determine how many items fit within the line budget.
|
|
45
|
+
let fittingCount = maxItems;
|
|
46
|
+
let fittedLineCount = 0;
|
|
47
|
+
if (linesBudget !== Infinity) {
|
|
48
|
+
fittingCount = 0;
|
|
49
|
+
for (let i = 0; i < maxItems; i++) {
|
|
50
|
+
const count = preRendered[i]!.length;
|
|
51
|
+
const remainingAfter = items.length - (i + 1);
|
|
52
|
+
const reservedSummaryLines = remainingAfter > 0 ? 1 : 0;
|
|
53
|
+
if (fittedLineCount + count + reservedSummaryLines > linesBudget) break;
|
|
54
|
+
fittedLineCount += count;
|
|
55
|
+
fittingCount = i + 1;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const remaining = items.length - fittingCount;
|
|
60
|
+
const hasSummary = !expanded && remaining > 0 && (linesBudget === Infinity || fittedLineCount < linesBudget);
|
|
61
|
+
|
|
62
|
+
// Emit pre-rendered content with correct isLast-based branch prefixes.
|
|
63
|
+
const lines: string[] = [];
|
|
64
|
+
for (let i = 0; i < fittingCount; i++) {
|
|
65
|
+
const isLast = !hasSummary && i === fittingCount - 1;
|
|
66
|
+
const branch = getTreeBranch(isLast, theme);
|
|
67
|
+
const prefix = `${theme.fg("dim", branch)} `;
|
|
68
|
+
const continuePrefix = `${theme.fg("dim", getTreeContinuePrefix(isLast, theme))}`;
|
|
69
|
+
const itemLines = preRendered[i]!;
|
|
70
|
+
if (itemLines.length === 0) continue;
|
|
71
|
+
lines.push(`${prefix}${replaceTabs(itemLines[0]!)}`);
|
|
72
|
+
for (let j = 1; j < itemLines.length; j++) {
|
|
73
|
+
lines.push(`${continuePrefix}${replaceTabs(itemLines[j]!)}`);
|
|
44
74
|
}
|
|
45
75
|
}
|
|
46
76
|
|
|
47
|
-
if (
|
|
48
|
-
const remaining = items.length - maxItems;
|
|
77
|
+
if (hasSummary) {
|
|
49
78
|
lines.push(`${theme.fg("dim", theme.tree.last)} ${theme.fg("muted", formatMoreItems(remaining, itemType))}`);
|
|
50
79
|
}
|
|
51
80
|
|
package/src/utils/image-input.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
|
+
import type { ImageContent } from "@oh-my-pi/pi-ai";
|
|
2
3
|
import { formatBytes } from "@oh-my-pi/pi-utils";
|
|
3
4
|
import { resolveReadPath } from "../tools/path-utils";
|
|
5
|
+
import { convertToPng } from "./image-convert";
|
|
4
6
|
import { formatDimensionNote, resizeImage } from "./image-resize";
|
|
5
7
|
import { detectSupportedImageMimeTypeFromFile } from "./mime";
|
|
6
8
|
|
|
7
9
|
export const MAX_IMAGE_INPUT_BYTES = 20 * 1024 * 1024;
|
|
8
10
|
const MAX_IMAGE_METADATA_HEADER_BYTES = 256 * 1024;
|
|
9
|
-
|
|
11
|
+
export const SUPPORTED_INPUT_IMAGE_MIME_TYPES = new Set(["image/png", "image/jpeg", "image/gif", "image/webp"]);
|
|
10
12
|
export interface ImageMetadata {
|
|
11
13
|
mimeType: string;
|
|
12
14
|
bytes: number;
|
|
@@ -25,6 +27,14 @@ export interface LoadedImageInput {
|
|
|
25
27
|
bytes: number;
|
|
26
28
|
}
|
|
27
29
|
|
|
30
|
+
export async function ensureSupportedImageInput(image: ImageContent): Promise<ImageContent | null> {
|
|
31
|
+
if (SUPPORTED_INPUT_IMAGE_MIME_TYPES.has(image.mimeType)) {
|
|
32
|
+
return image;
|
|
33
|
+
}
|
|
34
|
+
const converted = await convertToPng(image.data, image.mimeType);
|
|
35
|
+
return converted ? { type: "image", data: converted.data, mimeType: converted.mimeType } : null;
|
|
36
|
+
}
|
|
37
|
+
|
|
28
38
|
export interface ReadImageMetadataOptions {
|
|
29
39
|
path: string;
|
|
30
40
|
cwd: string;
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* Returns synthesized answers with web search sources.
|
|
7
7
|
*/
|
|
8
8
|
import * as os from "node:os";
|
|
9
|
-
import { getAgentDbPath, readSseJson } from "@oh-my-pi/pi-utils";
|
|
9
|
+
import { $env, getAgentDbPath, readSseJson } from "@oh-my-pi/pi-utils";
|
|
10
10
|
import packageJson from "../../../../package.json" with { type: "json" };
|
|
11
11
|
import { AgentStorage } from "../../../session/agent-storage";
|
|
12
12
|
import type { SearchResponse, SearchSource } from "../../../web/search/types";
|
|
@@ -21,6 +21,11 @@ const JWT_CLAIM_PATH = "https://api.openai.com/auth";
|
|
|
21
21
|
const DEFAULT_INSTRUCTIONS =
|
|
22
22
|
"You are a helpful assistant with web search capabilities. Search the web to answer the user's question accurately and cite your sources.";
|
|
23
23
|
|
|
24
|
+
function getModel(): string {
|
|
25
|
+
const configuredModel = $env.PI_CODEX_WEB_SEARCH_MODEL?.trim();
|
|
26
|
+
return configuredModel ? configuredModel : DEFAULT_MODEL;
|
|
27
|
+
}
|
|
28
|
+
|
|
24
29
|
export interface CodexSearchParams {
|
|
25
30
|
signal?: AbortSignal;
|
|
26
31
|
query: string;
|
|
@@ -188,8 +193,10 @@ async function callCodexSearch(
|
|
|
188
193
|
const url = `${CODEX_BASE_URL}${CODEX_RESPONSES_PATH}`;
|
|
189
194
|
const headers = buildCodexHeaders(auth.accessToken, auth.accountId);
|
|
190
195
|
|
|
196
|
+
const requestedModel = getModel();
|
|
197
|
+
|
|
191
198
|
const body: Record<string, unknown> = {
|
|
192
|
-
model:
|
|
199
|
+
model: requestedModel,
|
|
193
200
|
stream: true,
|
|
194
201
|
store: false,
|
|
195
202
|
input: [
|
|
@@ -226,7 +233,7 @@ async function callCodexSearch(
|
|
|
226
233
|
// Parse SSE stream
|
|
227
234
|
const answerParts: string[] = [];
|
|
228
235
|
const sources: SearchSource[] = [];
|
|
229
|
-
let model =
|
|
236
|
+
let model = requestedModel;
|
|
230
237
|
let requestId = "";
|
|
231
238
|
let usage: { inputTokens: number; outputTokens: number; totalTokens: number } | undefined;
|
|
232
239
|
|