@oh-my-pi/pi-coding-agent 15.10.4 → 15.10.5
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 +52 -0
- package/dist/types/capability/rule-buckets.d.ts +1 -1
- package/dist/types/capability/rule.d.ts +6 -1
- package/dist/types/cli/update-cli.d.ts +11 -1
- package/dist/types/config/model-registry.d.ts +18 -1
- package/dist/types/discovery/at-imports.d.ts +15 -0
- package/dist/types/edit/diff.d.ts +3 -2
- package/dist/types/eval/__tests__/helpers-local-roots.test.d.ts +1 -0
- package/dist/types/eval/backend.d.ts +7 -0
- package/dist/types/eval/js/context-manager.d.ts +1 -0
- package/dist/types/eval/js/executor.d.ts +2 -0
- package/dist/types/eval/js/index.d.ts +1 -1
- package/dist/types/eval/js/shared/helpers.d.ts +6 -0
- package/dist/types/eval/js/shared/runtime.d.ts +5 -0
- package/dist/types/eval/js/worker-protocol.d.ts +6 -0
- package/dist/types/eval/py/executor.d.ts +7 -0
- package/dist/types/eval/py/index.d.ts +1 -1
- package/dist/types/export/ttsr.d.ts +14 -0
- package/dist/types/extensibility/extensions/types.d.ts +8 -1
- package/dist/types/extensibility/legacy-pi-ai-shim.d.ts +1 -1
- package/dist/types/internal-urls/local-protocol.d.ts +10 -0
- package/dist/types/mcp/oauth-flow.d.ts +2 -2
- package/dist/types/modes/components/custom-editor.d.ts +3 -0
- package/dist/types/modes/components/{status-line.d.ts → status-line/component.d.ts} +2 -32
- package/dist/types/modes/components/status-line/index.d.ts +1 -0
- package/dist/types/modes/components/status-line/types.d.ts +31 -2
- package/dist/types/modes/image-references.d.ts +8 -3
- package/dist/types/modes/interactive-mode.d.ts +1 -1
- package/dist/types/modes/theme/theme.d.ts +2 -1
- package/dist/types/modes/types.d.ts +2 -1
- package/dist/types/modes/utils/ui-helpers.d.ts +2 -2
- package/dist/types/session/agent-session.d.ts +0 -2
- package/dist/types/tools/ask.d.ts +1 -0
- package/dist/types/tools/browser/tab-worker.d.ts +15 -0
- package/dist/types/tools/index.d.ts +17 -0
- package/dist/types/tools/render-utils.d.ts +1 -1
- package/dist/types/tools/tool-timeouts.d.ts +1 -1
- package/dist/types/utils/block-context.d.ts +35 -0
- package/dist/types/utils/image-loading.d.ts +12 -0
- package/package.json +29 -9
- package/src/capability/rule-buckets.ts +4 -2
- package/src/capability/rule.ts +10 -1
- package/src/cli/auth-broker-cli.ts +6 -7
- package/src/cli/auth-gateway-cli.ts +1 -1
- package/src/cli/list-models.ts +5 -0
- package/src/cli/update-cli.ts +138 -16
- package/src/config/model-registry.ts +81 -2
- package/src/debug/index.ts +4 -8
- package/src/discovery/at-imports.ts +273 -0
- package/src/discovery/builtin-rules/index.ts +4 -0
- package/src/discovery/builtin-rules/ts-no-test-timers.md +55 -0
- package/src/discovery/builtin-rules/ts-redundant-clear-guard.md +75 -0
- package/src/discovery/helpers.ts +2 -1
- package/src/edit/diff.ts +114 -4
- package/src/edit/hashline/diff.ts +1 -1
- package/src/edit/hashline/execute.ts +1 -1
- package/src/edit/modes/patch.ts +6 -2
- package/src/edit/modes/replace.ts +1 -1
- package/src/edit/renderer.ts +12 -2
- package/src/eval/__tests__/helpers-local-roots.test.ts +58 -0
- package/src/eval/backend.ts +15 -0
- package/src/eval/js/context-manager.ts +4 -2
- package/src/eval/js/executor.ts +3 -0
- package/src/eval/js/index.ts +7 -1
- package/src/eval/js/shared/helpers.ts +53 -6
- package/src/eval/js/shared/runtime.ts +8 -0
- package/src/eval/js/worker-core.ts +1 -0
- package/src/eval/js/worker-protocol.ts +6 -0
- package/src/eval/py/executor.ts +12 -0
- package/src/eval/py/index.ts +7 -1
- package/src/eval/py/prelude.py +43 -4
- package/src/eval/py/runner.py +1 -0
- package/src/exa/render.ts +1 -1
- package/src/export/ttsr.ts +122 -1
- package/src/extensibility/extensions/types.ts +8 -1
- package/src/extensibility/legacy-pi-ai-shim.ts +1 -1
- package/src/extensibility/plugins/doctor.ts +1 -1
- package/src/extensibility/plugins/legacy-pi-compat.ts +6 -5
- package/src/goals/tools/goal-tool.ts +1 -1
- package/src/internal-urls/docs-index.generated.ts +6 -5
- package/src/internal-urls/local-protocol.ts +13 -0
- package/src/lsp/render.ts +8 -6
- package/src/mcp/oauth-flow.ts +3 -3
- package/src/mcp/render.ts +7 -1
- package/src/modes/components/custom-editor.ts +12 -6
- package/src/modes/components/login-dialog.ts +1 -1
- package/src/modes/components/oauth-selector.ts +4 -4
- package/src/modes/components/read-tool-group.ts +10 -3
- package/src/modes/components/{status-line.ts → status-line/component.ts} +18 -40
- package/src/modes/components/status-line/index.ts +1 -0
- package/src/modes/components/status-line/types.ts +23 -8
- package/src/modes/components/tool-execution.ts +1 -1
- package/src/modes/components/transcript-container.ts +17 -10
- package/src/modes/components/user-message.ts +6 -3
- package/src/modes/components/welcome.ts +1 -1
- package/src/modes/controllers/extension-ui-controller.ts +143 -127
- package/src/modes/controllers/input-controller.ts +36 -10
- package/src/modes/controllers/mcp-command-controller.ts +28 -12
- package/src/modes/controllers/selector-controller.ts +4 -11
- package/src/modes/controllers/ssh-command-controller.ts +2 -2
- package/src/modes/image-references.ts +13 -7
- package/src/modes/interactive-mode.ts +2 -2
- package/src/modes/rpc/rpc-mode.ts +1 -1
- package/src/modes/setup-wizard/scenes/sign-in.ts +3 -11
- package/src/modes/theme/theme.ts +95 -1
- package/src/modes/types.ts +2 -1
- package/src/modes/utils/ui-helpers.ts +14 -5
- package/src/prompts/tools/bash.md +1 -1
- package/src/prompts/tools/eval.md +4 -4
- package/src/sdk.ts +31 -14
- package/src/session/agent-session.ts +213 -155
- package/src/session/session-manager.ts +1 -1
- package/src/slash-commands/builtin-registry.ts +1 -1
- package/src/system-prompt.ts +15 -9
- package/src/task/render.ts +20 -8
- package/src/tools/ask.ts +14 -5
- package/src/tools/bash-interactive.ts +1 -1
- package/src/tools/bash.ts +14 -2
- package/src/tools/browser/render.ts +5 -2
- package/src/tools/browser/tab-worker.ts +211 -91
- package/src/tools/debug.ts +5 -2
- package/src/tools/eval-render.ts +6 -3
- package/src/tools/eval.ts +1 -1
- package/src/tools/gh-renderer.ts +29 -15
- package/src/tools/index.ts +32 -0
- package/src/tools/inspect-image-renderer.ts +12 -5
- package/src/tools/job.ts +9 -6
- package/src/tools/memory-render.ts +19 -5
- package/src/tools/read.ts +165 -18
- package/src/tools/render-utils.ts +3 -1
- package/src/tools/resolve.ts +1 -1
- package/src/tools/review.ts +1 -1
- package/src/tools/ssh.ts +4 -1
- package/src/tools/todo.ts +8 -1
- package/src/tools/tool-timeouts.ts +1 -1
- package/src/tools/write.ts +1 -1
- package/src/tui/code-cell.ts +1 -1
- package/src/utils/block-context.ts +312 -0
- package/src/utils/image-loading.ts +31 -1
- package/src/web/search/providers/codex.ts +1 -1
- package/src/web/search/render.ts +14 -6
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import { enclosingBlockBoundaries } from "@oh-my-pi/pi-natives";
|
|
2
|
+
import { logger } from "@oh-my-pi/pi-utils";
|
|
3
|
+
|
|
4
|
+
const OPEN_TO_CLOSE: Record<string, string> = {
|
|
5
|
+
"(": ")",
|
|
6
|
+
"[": "]",
|
|
7
|
+
"{": "}",
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const CLOSE_TO_OPEN: Record<string, string> = {
|
|
11
|
+
")": "(",
|
|
12
|
+
"]": "[",
|
|
13
|
+
"}": "{",
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export interface LineSpan {
|
|
17
|
+
startLine: number;
|
|
18
|
+
endLine: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Where the source came from, so tree-sitter can pick a grammar. */
|
|
22
|
+
export interface BlockContextSource {
|
|
23
|
+
path?: string;
|
|
24
|
+
lang?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type LineEntry = { kind: "line"; lineNumber: number; text: string; context: boolean } | { kind: "ellipsis" };
|
|
28
|
+
|
|
29
|
+
interface StackEntry {
|
|
30
|
+
opener: string;
|
|
31
|
+
lineNumber: number;
|
|
32
|
+
text: string;
|
|
33
|
+
visible: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
type ScannerMode = "code" | "single" | "double" | "template" | "blockComment";
|
|
37
|
+
|
|
38
|
+
function normalizeLineSpans(spans: readonly LineSpan[], totalLines: number): LineSpan[] {
|
|
39
|
+
if (totalLines <= 0) return [];
|
|
40
|
+
const normalized: LineSpan[] = [];
|
|
41
|
+
for (const span of spans) {
|
|
42
|
+
const startLine = Math.max(1, Math.trunc(span.startLine));
|
|
43
|
+
const endLine = Math.min(totalLines, Math.trunc(span.endLine));
|
|
44
|
+
if (endLine < startLine) continue;
|
|
45
|
+
normalized.push({ startLine, endLine });
|
|
46
|
+
}
|
|
47
|
+
if (normalized.length <= 1) return normalized;
|
|
48
|
+
normalized.sort((left, right) => left.startLine - right.startLine || left.endLine - right.endLine);
|
|
49
|
+
const merged: LineSpan[] = [];
|
|
50
|
+
for (const span of normalized) {
|
|
51
|
+
const previous = merged[merged.length - 1];
|
|
52
|
+
if (previous && span.startLine <= previous.endLine + 1) {
|
|
53
|
+
previous.endLine = Math.max(previous.endLine, span.endLine);
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
merged.push({ ...span });
|
|
57
|
+
}
|
|
58
|
+
return merged;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function visibleLineNumbers(spans: readonly LineSpan[]): Set<number> {
|
|
62
|
+
const visible = new Set<number>();
|
|
63
|
+
for (const span of spans) {
|
|
64
|
+
for (let line = span.startLine; line <= span.endLine; line++) {
|
|
65
|
+
visible.add(line);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return visible;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function hasEveryLineVisible(visible: ReadonlySet<number>, totalLines: number): boolean {
|
|
72
|
+
return totalLines > 0 && visible.size >= totalLines;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Collapse a set of visible line numbers into sorted, merged inclusive spans. */
|
|
76
|
+
function visibleSetToSpans(visible: ReadonlySet<number>): LineSpan[] {
|
|
77
|
+
const sorted = [...visible].sort((left, right) => left - right);
|
|
78
|
+
const spans: LineSpan[] = [];
|
|
79
|
+
for (const line of sorted) {
|
|
80
|
+
const previous = spans[spans.length - 1];
|
|
81
|
+
if (previous && line <= previous.endLine + 1) {
|
|
82
|
+
previous.endLine = line;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
spans.push({ startLine: line, endLine: line });
|
|
86
|
+
}
|
|
87
|
+
return spans;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Tree-sitter-backed block boundaries. For each multi-line named node whose
|
|
92
|
+
* span crosses the visible window, the native side returns the boundary line
|
|
93
|
+
* outside that window (closer when the opener is shown, opener when the closer
|
|
94
|
+
* is shown). Returns `null` when the language is unrecognized or the source has
|
|
95
|
+
* a syntax error so the caller can fall back to a lexical bracket scan.
|
|
96
|
+
*/
|
|
97
|
+
function nativeBlockContext(
|
|
98
|
+
fullLines: readonly string[],
|
|
99
|
+
visible: ReadonlySet<number>,
|
|
100
|
+
source: BlockContextSource,
|
|
101
|
+
): Map<number, string> | null {
|
|
102
|
+
if (!source.path && !source.lang) return null;
|
|
103
|
+
const ranges = visibleSetToSpans(visible);
|
|
104
|
+
if (ranges.length === 0) return new Map();
|
|
105
|
+
let boundaries: number[] | null;
|
|
106
|
+
try {
|
|
107
|
+
boundaries = enclosingBlockBoundaries({
|
|
108
|
+
code: fullLines.join("\n"),
|
|
109
|
+
path: source.path,
|
|
110
|
+
lang: source.lang,
|
|
111
|
+
ranges,
|
|
112
|
+
});
|
|
113
|
+
} catch (error) {
|
|
114
|
+
logger.debug("enclosingBlockBoundaries failed; using lexical bracket fallback", { error });
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
if (boundaries === null) return null;
|
|
118
|
+
const context = new Map<number, string>();
|
|
119
|
+
for (const lineNumber of boundaries) {
|
|
120
|
+
if (visible.has(lineNumber)) continue;
|
|
121
|
+
context.set(lineNumber, fullLines[lineNumber - 1] ?? "");
|
|
122
|
+
}
|
|
123
|
+
return context;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function findMatchingStackIndex(stack: readonly StackEntry[], opener: string): number {
|
|
127
|
+
for (let index = stack.length - 1; index >= 0; index--) {
|
|
128
|
+
if (stack[index].opener === opener) return index;
|
|
129
|
+
}
|
|
130
|
+
return -1;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function isHashCommentStart(line: string, index: number): boolean {
|
|
134
|
+
if (line[index] !== "#") return false;
|
|
135
|
+
for (let i = 0; i < index; i++) {
|
|
136
|
+
const ch = line[i];
|
|
137
|
+
if (ch !== " " && ch !== "\t") return false;
|
|
138
|
+
}
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Lexical bracket-matching fallback for sources tree-sitter can't parse
|
|
144
|
+
* (unknown extensions, syntax errors). Pairs `()[]{}` while skipping strings
|
|
145
|
+
* and line/block comments, and reports the matching line when one endpoint is
|
|
146
|
+
* visible and the other is not.
|
|
147
|
+
*/
|
|
148
|
+
function lexicalBracketContext(fullLines: readonly string[], visible: ReadonlySet<number>): Map<number, string> {
|
|
149
|
+
const context = new Map<number, string>();
|
|
150
|
+
const stack: StackEntry[] = [];
|
|
151
|
+
let mode: ScannerMode = "code";
|
|
152
|
+
let escaped = false;
|
|
153
|
+
|
|
154
|
+
for (let lineIndex = 0; lineIndex < fullLines.length; lineIndex++) {
|
|
155
|
+
const lineNumber = lineIndex + 1;
|
|
156
|
+
const line = fullLines[lineIndex] ?? "";
|
|
157
|
+
const lineVisible = visible.has(lineNumber);
|
|
158
|
+
let index = 0;
|
|
159
|
+
while (index < line.length) {
|
|
160
|
+
const ch = line[index];
|
|
161
|
+
const next = index + 1 < line.length ? line[index + 1] : "";
|
|
162
|
+
|
|
163
|
+
if (mode === "blockComment") {
|
|
164
|
+
if (ch === "*" && next === "/") {
|
|
165
|
+
mode = "code";
|
|
166
|
+
index += 2;
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
index++;
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (mode === "single" || mode === "double" || mode === "template") {
|
|
174
|
+
if (escaped) {
|
|
175
|
+
escaped = false;
|
|
176
|
+
index++;
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
if (ch === "\\") {
|
|
180
|
+
escaped = true;
|
|
181
|
+
index++;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (
|
|
185
|
+
(mode === "single" && ch === "'") ||
|
|
186
|
+
(mode === "double" && ch === '"') ||
|
|
187
|
+
(mode === "template" && ch === "`")
|
|
188
|
+
) {
|
|
189
|
+
mode = "code";
|
|
190
|
+
}
|
|
191
|
+
index++;
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (ch === "/" && next === "/") break;
|
|
196
|
+
if (ch === "/" && next === "*") {
|
|
197
|
+
mode = "blockComment";
|
|
198
|
+
index += 2;
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
if (isHashCommentStart(line, index)) break;
|
|
202
|
+
if (ch === "'") {
|
|
203
|
+
mode = "single";
|
|
204
|
+
escaped = false;
|
|
205
|
+
index++;
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
if (ch === '"') {
|
|
209
|
+
mode = "double";
|
|
210
|
+
escaped = false;
|
|
211
|
+
index++;
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
if (ch === "`") {
|
|
215
|
+
mode = "template";
|
|
216
|
+
escaped = false;
|
|
217
|
+
index++;
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (OPEN_TO_CLOSE[ch]) {
|
|
222
|
+
stack.push({ opener: ch, lineNumber, text: line, visible: lineVisible });
|
|
223
|
+
index++;
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const opener = CLOSE_TO_OPEN[ch];
|
|
228
|
+
if (opener) {
|
|
229
|
+
const matchIndex = findMatchingStackIndex(stack, opener);
|
|
230
|
+
if (matchIndex !== -1) {
|
|
231
|
+
const [matched] = stack.splice(matchIndex);
|
|
232
|
+
if (matched) {
|
|
233
|
+
if (lineVisible && !matched.visible) context.set(matched.lineNumber, matched.text);
|
|
234
|
+
if (matched.visible && !lineVisible) context.set(lineNumber, line);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
index++;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (mode === "single" || mode === "double") {
|
|
243
|
+
mode = "code";
|
|
244
|
+
escaped = false;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
for (const lineNumber of visible) context.delete(lineNumber);
|
|
249
|
+
return context;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Resolve the off-window boundary lines for a visible window: tree-sitter
|
|
254
|
+
* syntactic spans first (covers brace and indentation languages), falling back
|
|
255
|
+
* to a lexical bracket scan when the grammar is unavailable. Returns a map of
|
|
256
|
+
* `lineNumber → source text` for the lines to surface, never including a line
|
|
257
|
+
* already visible.
|
|
258
|
+
*/
|
|
259
|
+
export function findBlockContextLines(
|
|
260
|
+
fullLines: readonly string[],
|
|
261
|
+
visibleInput: ReadonlySet<number> | readonly number[],
|
|
262
|
+
source: BlockContextSource = {},
|
|
263
|
+
): Map<number, string> {
|
|
264
|
+
const visible = visibleInput instanceof Set ? visibleInput : new Set(visibleInput);
|
|
265
|
+
if (visible.size === 0 || hasEveryLineVisible(visible, fullLines.length)) return new Map();
|
|
266
|
+
return nativeBlockContext(fullLines, visible, source) ?? lexicalBracketContext(fullLines, visible);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Build display entries for `visibleSpans` plus any off-window block-boundary
|
|
271
|
+
* lines, in source order, with `{ kind: "ellipsis" }` markers inserted across
|
|
272
|
+
* non-contiguous gaps. `options.lineText` lets callers substitute display text
|
|
273
|
+
* (e.g. column-truncated lines) for a given line number.
|
|
274
|
+
*/
|
|
275
|
+
export function buildLineEntriesWithBlockContext(
|
|
276
|
+
fullLines: readonly string[],
|
|
277
|
+
visibleSpans: readonly LineSpan[],
|
|
278
|
+
source: BlockContextSource = {},
|
|
279
|
+
options: {
|
|
280
|
+
lineText?: (lineNumber: number, sourceText: string, context: boolean) => string;
|
|
281
|
+
} = {},
|
|
282
|
+
): LineEntry[] {
|
|
283
|
+
const spans = normalizeLineSpans(visibleSpans, fullLines.length);
|
|
284
|
+
const visible = visibleLineNumbers(spans);
|
|
285
|
+
const context = findBlockContextLines(fullLines, visible, source);
|
|
286
|
+
const allLines = new Set<number>(visible);
|
|
287
|
+
for (const lineNumber of context.keys()) allLines.add(lineNumber);
|
|
288
|
+
|
|
289
|
+
const sorted = [...allLines].sort((left, right) => left - right);
|
|
290
|
+
const entries: LineEntry[] = [];
|
|
291
|
+
let previousLine: number | undefined;
|
|
292
|
+
for (const lineNumber of sorted) {
|
|
293
|
+
if (previousLine !== undefined && lineNumber > previousLine + 1) {
|
|
294
|
+
entries.push({ kind: "ellipsis" });
|
|
295
|
+
}
|
|
296
|
+
const sourceText = fullLines[lineNumber - 1] ?? "";
|
|
297
|
+
const isContext = context.has(lineNumber);
|
|
298
|
+
entries.push({
|
|
299
|
+
kind: "line",
|
|
300
|
+
lineNumber,
|
|
301
|
+
text: options.lineText?.(lineNumber, sourceText, isContext) ?? sourceText,
|
|
302
|
+
context: isContext,
|
|
303
|
+
});
|
|
304
|
+
previousLine = lineNumber;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return entries;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
export function lineEntriesToPlainText(entries: readonly LineEntry[], ellipsis = "…"): string {
|
|
311
|
+
return entries.map(entry => (entry.kind === "ellipsis" ? ellipsis : entry.text)).join("\n");
|
|
312
|
+
}
|
|
@@ -2,7 +2,7 @@ import * as fs from "node:fs/promises";
|
|
|
2
2
|
import type { ImageContent } from "@oh-my-pi/pi-ai";
|
|
3
3
|
import { formatBytes, readImageMetadata, SUPPORTED_IMAGE_MIME_TYPES } from "@oh-my-pi/pi-utils";
|
|
4
4
|
import { resolveReadPath } from "../tools/path-utils";
|
|
5
|
-
import { formatDimensionNote, resizeImage } from "./image-resize";
|
|
5
|
+
import { formatDimensionNote, type ImageResizeOptions, resizeImage } from "./image-resize";
|
|
6
6
|
|
|
7
7
|
export const MAX_IMAGE_INPUT_BYTES = 20 * 1024 * 1024;
|
|
8
8
|
export const SUPPORTED_INPUT_IMAGE_MIME_TYPES = SUPPORTED_IMAGE_MIME_TYPES;
|
|
@@ -50,6 +50,36 @@ export async function ensureSupportedImageInput(image: ImageContent): Promise<Im
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
export interface NormalizeModelContextImagesOptions {
|
|
54
|
+
resize?: ImageResizeOptions;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Normalize image blocks before they enter agent/model context. This keeps
|
|
59
|
+
* provider request construction from having to resize an unbounded batch of
|
|
60
|
+
* large images on the streaming hot path. Images are processed sequentially on
|
|
61
|
+
* purpose: `resizeImage` may fan out multiple encoders for one image, so the
|
|
62
|
+
* outer image batch must stay bounded.
|
|
63
|
+
*/
|
|
64
|
+
export async function normalizeModelContextImages(
|
|
65
|
+
images: ImageContent[] | undefined,
|
|
66
|
+
options?: NormalizeModelContextImagesOptions,
|
|
67
|
+
): Promise<ImageContent[] | undefined> {
|
|
68
|
+
if (!images || images.length === 0) return undefined;
|
|
69
|
+
const normalized: ImageContent[] = [];
|
|
70
|
+
for (const image of images) {
|
|
71
|
+
try {
|
|
72
|
+
const resized = await resizeImage(image, options?.resize);
|
|
73
|
+
normalized.push({ type: "image", data: resized.data, mimeType: resized.mimeType });
|
|
74
|
+
} catch {
|
|
75
|
+
// Preserve existing caller behavior for decode/resize failures: keep the
|
|
76
|
+
// user's image block rather than dropping it from the turn.
|
|
77
|
+
normalized.push(image);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return normalized;
|
|
81
|
+
}
|
|
82
|
+
|
|
53
83
|
export async function loadImageInput(options: LoadImageInputOptions): Promise<LoadedImageInput | null> {
|
|
54
84
|
const maxBytes = options.maxBytes ?? MAX_IMAGE_INPUT_BYTES;
|
|
55
85
|
const resolvedPath = options.resolvedPath ?? resolveReadPath(options.path, options.cwd);
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import * as os from "node:os";
|
|
10
10
|
import { type AuthStorage, getBundledModels } from "@oh-my-pi/pi-ai";
|
|
11
|
-
import { decodeJwt } from "@oh-my-pi/pi-ai/
|
|
11
|
+
import { decodeJwt } from "@oh-my-pi/pi-ai/oauth/openai-codex";
|
|
12
12
|
import { $env, readSseJson } from "@oh-my-pi/pi-utils";
|
|
13
13
|
import packageJson from "../../../../package.json" with { type: "json" };
|
|
14
14
|
import type { SearchResponse, SearchSource } from "../../../web/search/types";
|
package/src/web/search/render.ts
CHANGED
|
@@ -117,13 +117,21 @@ export function renderSearchResult(
|
|
|
117
117
|
: searchQueries[0]
|
|
118
118
|
? truncateToWidth(searchQueries[0], 80)
|
|
119
119
|
: undefined;
|
|
120
|
+
const success = sourceCount > 0;
|
|
120
121
|
const header = renderStatusLine(
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
122
|
+
success
|
|
123
|
+
? {
|
|
124
|
+
iconOverride: theme.styledSymbol("tool.webSearch", "accent"),
|
|
125
|
+
title: "Web Search",
|
|
126
|
+
description: providerLabel,
|
|
127
|
+
meta: [formatCount("source", sourceCount)],
|
|
128
|
+
}
|
|
129
|
+
: {
|
|
130
|
+
icon: "warning",
|
|
131
|
+
title: "Web Search",
|
|
132
|
+
description: providerLabel,
|
|
133
|
+
meta: [formatCount("source", sourceCount)],
|
|
134
|
+
},
|
|
127
135
|
theme,
|
|
128
136
|
);
|
|
129
137
|
|