@oh-my-pi/pi-coding-agent 14.0.4 → 14.1.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 +83 -0
- package/package.json +11 -8
- package/src/async/index.ts +1 -0
- package/src/async/support.ts +5 -0
- package/src/cli/list-models.ts +96 -57
- package/src/commit/model-selection.ts +16 -13
- package/src/config/model-equivalence.ts +674 -0
- package/src/config/model-registry.ts +182 -13
- package/src/config/model-resolver.ts +203 -74
- package/src/config/settings-schema.ts +23 -0
- package/src/config/settings.ts +9 -2
- package/src/dap/session.ts +31 -39
- package/src/debug/log-formatting.ts +2 -2
- package/src/edit/modes/chunk.ts +8 -3
- package/src/export/html/template.css +82 -0
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +612 -97
- package/src/internal-urls/docs-index.generated.ts +1 -1
- package/src/internal-urls/jobs-protocol.ts +2 -1
- package/src/lsp/client.ts +5 -3
- package/src/lsp/index.ts +4 -9
- package/src/lsp/utils.ts +26 -0
- package/src/main.ts +6 -1
- package/src/memories/index.ts +7 -6
- package/src/modes/components/diff.ts +1 -1
- package/src/modes/components/model-selector.ts +221 -64
- package/src/modes/controllers/command-controller.ts +18 -0
- package/src/modes/controllers/event-controller.ts +438 -426
- package/src/modes/controllers/selector-controller.ts +13 -5
- package/src/modes/theme/mermaid-cache.ts +5 -7
- package/src/priority.json +8 -0
- package/src/prompts/agents/designer.md +1 -2
- package/src/prompts/system/system-prompt.md +5 -1
- package/src/prompts/tools/bash.md +15 -0
- package/src/prompts/tools/cancel-job.md +1 -1
- package/src/prompts/tools/chunk-edit.md +39 -40
- package/src/prompts/tools/read-chunk.md +13 -1
- package/src/prompts/tools/read.md +9 -0
- package/src/prompts/tools/write.md +1 -0
- package/src/sdk.ts +7 -4
- package/src/session/agent-session.ts +33 -6
- package/src/session/compaction/compaction.ts +1 -1
- package/src/task/executor.ts +5 -1
- package/src/tools/await-tool.ts +2 -1
- package/src/tools/bash.ts +221 -56
- package/src/tools/browser.ts +84 -21
- package/src/tools/cancel-job.ts +2 -1
- package/src/tools/fetch.ts +1 -1
- package/src/tools/find.ts +40 -94
- package/src/tools/gemini-image.ts +1 -0
- package/src/tools/inspect-image.ts +1 -1
- package/src/tools/read.ts +218 -1
- package/src/tools/render-utils.ts +1 -1
- package/src/tools/sqlite-reader.ts +623 -0
- package/src/tools/write.ts +187 -1
- package/src/utils/commit-message-generator.ts +1 -0
- package/src/utils/git.ts +24 -1
- package/src/utils/image-resize.ts +73 -37
- package/src/utils/title-generator.ts +1 -1
- package/src/web/scrapers/types.ts +50 -32
- package/src/web/search/providers/codex.ts +21 -2
package/src/tools/find.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
4
|
-
import
|
|
4
|
+
import * as natives from "@oh-my-pi/pi-natives";
|
|
5
5
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
6
6
|
import { Text } from "@oh-my-pi/pi-tui";
|
|
7
7
|
import { isEnoent, prompt, untilAborted } from "@oh-my-pi/pi-utils";
|
|
@@ -124,46 +124,15 @@ export class FindTool implements AgentTool<typeof findSchema, FindToolDetails> {
|
|
|
124
124
|
throw new ToolError("Limit must be a positive number");
|
|
125
125
|
}
|
|
126
126
|
const includeHidden = hidden ?? true;
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
if (
|
|
131
|
-
throw new ToolError(`Path not found: ${scopePath}`);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
if (!hasGlob && this.#customOps.stat) {
|
|
135
|
-
const stat = await this.#customOps.stat(searchPath);
|
|
136
|
-
if (stat.isFile()) {
|
|
137
|
-
const files = [scopePath];
|
|
138
|
-
const details: FindToolDetails = {
|
|
139
|
-
scopePath,
|
|
140
|
-
fileCount: 1,
|
|
141
|
-
files,
|
|
142
|
-
truncated: false,
|
|
143
|
-
};
|
|
144
|
-
return toolResult(details).text(files.join("\n")).done();
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
const results = await this.#customOps.glob(globPattern, searchPath, {
|
|
149
|
-
ignore: ["**/node_modules/**", "**/.git/**"],
|
|
150
|
-
limit: effectiveLimit,
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
if (results.length === 0) {
|
|
127
|
+
const timeoutSignal = AbortSignal.timeout(GLOB_TIMEOUT_MS);
|
|
128
|
+
const combinedSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
|
|
129
|
+
const buildResult = (files: string[]): AgentToolResult<FindToolDetails> => {
|
|
130
|
+
if (files.length === 0) {
|
|
154
131
|
const details: FindToolDetails = { scopePath, fileCount: 0, files: [], truncated: false };
|
|
155
132
|
return toolResult(details).text("No files found matching pattern").done();
|
|
156
133
|
}
|
|
157
134
|
|
|
158
|
-
|
|
159
|
-
const relativized = results.map(p => {
|
|
160
|
-
if (p.startsWith(searchPath)) {
|
|
161
|
-
return p.slice(searchPath.length + 1);
|
|
162
|
-
}
|
|
163
|
-
return path.relative(searchPath, p);
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
const listLimit = applyListLimit(relativized, { limit: effectiveLimit });
|
|
135
|
+
const listLimit = applyListLimit(files, { limit: effectiveLimit });
|
|
167
136
|
const limited = listLimit.items;
|
|
168
137
|
const limitMeta = listLimit.meta;
|
|
169
138
|
const rawOutput = limited.join("\n");
|
|
@@ -186,6 +155,32 @@ export class FindTool implements AgentTool<typeof findSchema, FindToolDetails> {
|
|
|
186
155
|
}
|
|
187
156
|
|
|
188
157
|
return resultBuilder.done();
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
if (this.#customOps?.glob) {
|
|
161
|
+
if (!(await this.#customOps.exists(searchPath))) {
|
|
162
|
+
throw new ToolError(`Path not found: ${scopePath}`);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (!hasGlob && this.#customOps.stat) {
|
|
166
|
+
const stat = await this.#customOps.stat(searchPath);
|
|
167
|
+
if (stat.isFile()) {
|
|
168
|
+
return buildResult([scopePath]);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const results = await this.#customOps.glob(globPattern, searchPath, {
|
|
173
|
+
ignore: ["**/node_modules/**", "**/.git/**"],
|
|
174
|
+
limit: effectiveLimit,
|
|
175
|
+
});
|
|
176
|
+
const relativized = results.map(p => {
|
|
177
|
+
if (p.startsWith(searchPath)) {
|
|
178
|
+
return p.slice(searchPath.length + 1);
|
|
179
|
+
}
|
|
180
|
+
return path.relative(searchPath, p);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
return buildResult(relativized);
|
|
189
184
|
}
|
|
190
185
|
|
|
191
186
|
let searchStat: fs.Stats;
|
|
@@ -199,20 +194,13 @@ export class FindTool implements AgentTool<typeof findSchema, FindToolDetails> {
|
|
|
199
194
|
}
|
|
200
195
|
|
|
201
196
|
if (!hasGlob && searchStat.isFile()) {
|
|
202
|
-
|
|
203
|
-
const details: FindToolDetails = {
|
|
204
|
-
scopePath,
|
|
205
|
-
fileCount: 1,
|
|
206
|
-
files,
|
|
207
|
-
truncated: false,
|
|
208
|
-
};
|
|
209
|
-
return toolResult(details).text(files.join("\n")).done();
|
|
197
|
+
return buildResult([scopePath]);
|
|
210
198
|
}
|
|
211
199
|
if (!searchStat.isDirectory()) {
|
|
212
200
|
throw new ToolError(`Path is not a directory: ${searchPath}`);
|
|
213
201
|
}
|
|
214
202
|
|
|
215
|
-
let matches: GlobMatch[];
|
|
203
|
+
let matches: natives.GlobMatch[];
|
|
216
204
|
const onUpdateMatches: string[] = [];
|
|
217
205
|
const updateIntervalMs = 200;
|
|
218
206
|
let lastUpdate = 0;
|
|
@@ -233,27 +221,25 @@ export class FindTool implements AgentTool<typeof findSchema, FindToolDetails> {
|
|
|
233
221
|
});
|
|
234
222
|
};
|
|
235
223
|
const onMatch = onUpdate
|
|
236
|
-
? (err: Error | null, match: GlobMatch | null) => {
|
|
224
|
+
? (err: Error | null, match: natives.GlobMatch | null) => {
|
|
237
225
|
if (err || signal?.aborted || !match) return;
|
|
238
226
|
let relativePath = match.path;
|
|
239
227
|
if (!relativePath) return;
|
|
240
|
-
if (match.fileType === FileType.Dir && !relativePath.endsWith("/")) {
|
|
228
|
+
if (match.fileType === natives.FileType.Dir && !relativePath.endsWith("/")) {
|
|
241
229
|
relativePath += "/";
|
|
242
230
|
}
|
|
243
231
|
onUpdateMatches.push(relativePath);
|
|
244
232
|
emitUpdate();
|
|
245
233
|
}
|
|
246
234
|
: undefined;
|
|
247
|
-
const timeoutSignal = AbortSignal.timeout(GLOB_TIMEOUT_MS);
|
|
248
|
-
const combinedSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
|
|
249
235
|
|
|
250
236
|
const doGlob = async (useGitignore: boolean) =>
|
|
251
237
|
untilAborted(combinedSignal, () =>
|
|
252
|
-
glob(
|
|
238
|
+
natives.glob(
|
|
253
239
|
{
|
|
254
240
|
pattern: globPattern,
|
|
255
241
|
path: searchPath,
|
|
256
|
-
fileType: FileType.File,
|
|
242
|
+
fileType: natives.FileType.File,
|
|
257
243
|
hidden: includeHidden,
|
|
258
244
|
maxResults: effectiveLimit,
|
|
259
245
|
sortByMtime: true,
|
|
@@ -266,7 +252,6 @@ export class FindTool implements AgentTool<typeof findSchema, FindToolDetails> {
|
|
|
266
252
|
|
|
267
253
|
try {
|
|
268
254
|
let result = await doGlob(true);
|
|
269
|
-
// If gitignore filtering yielded nothing, retry without it
|
|
270
255
|
if (result.matches.length === 0) {
|
|
271
256
|
result = await doGlob(false);
|
|
272
257
|
}
|
|
@@ -282,12 +267,7 @@ export class FindTool implements AgentTool<typeof findSchema, FindToolDetails> {
|
|
|
282
267
|
throw error;
|
|
283
268
|
}
|
|
284
269
|
|
|
285
|
-
if (matches.length === 0) {
|
|
286
|
-
const details: FindToolDetails = { scopePath, fileCount: 0, files: [], truncated: false };
|
|
287
|
-
return toolResult(details).text("No files found matching pattern").done();
|
|
288
|
-
}
|
|
289
270
|
const relativized: string[] = [];
|
|
290
|
-
|
|
291
271
|
for (const match of matches) {
|
|
292
272
|
throwIfAborted(signal);
|
|
293
273
|
const line = match.path;
|
|
@@ -297,9 +277,7 @@ export class FindTool implements AgentTool<typeof findSchema, FindToolDetails> {
|
|
|
297
277
|
|
|
298
278
|
const hadTrailingSlash = line.endsWith("/") || line.endsWith("\\");
|
|
299
279
|
let relativePath = line;
|
|
300
|
-
|
|
301
|
-
const isDirectory = match.fileType === FileType.Dir;
|
|
302
|
-
|
|
280
|
+
const isDirectory = match.fileType === natives.FileType.Dir;
|
|
303
281
|
if ((isDirectory || hadTrailingSlash) && !relativePath.endsWith("/")) {
|
|
304
282
|
relativePath += "/";
|
|
305
283
|
}
|
|
@@ -307,39 +285,7 @@ export class FindTool implements AgentTool<typeof findSchema, FindToolDetails> {
|
|
|
307
285
|
relativized.push(relativePath);
|
|
308
286
|
}
|
|
309
287
|
|
|
310
|
-
|
|
311
|
-
const details: FindToolDetails = { scopePath, fileCount: 0, files: [], truncated: false };
|
|
312
|
-
return toolResult(details).text("No files found matching pattern").done();
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
// Results are already sorted by mtime from native (sortByMtime: true)
|
|
316
|
-
|
|
317
|
-
const listLimit = applyListLimit(relativized, { limit: effectiveLimit });
|
|
318
|
-
const limited = listLimit.items;
|
|
319
|
-
const limitMeta = listLimit.meta;
|
|
320
|
-
|
|
321
|
-
// Apply byte truncation (no line limit since we already have result limit)
|
|
322
|
-
const rawOutput = limited.join("\n");
|
|
323
|
-
const truncation = truncateHead(rawOutput, { maxLines: Number.MAX_SAFE_INTEGER });
|
|
324
|
-
|
|
325
|
-
const resultOutput = truncation.content;
|
|
326
|
-
const details: FindToolDetails = {
|
|
327
|
-
scopePath,
|
|
328
|
-
fileCount: limited.length,
|
|
329
|
-
files: limited,
|
|
330
|
-
truncated: Boolean(limitMeta.resultLimit || truncation.truncated),
|
|
331
|
-
resultLimitReached: limitMeta.resultLimit?.reached,
|
|
332
|
-
truncation: truncation.truncated ? truncation : undefined,
|
|
333
|
-
};
|
|
334
|
-
|
|
335
|
-
const resultBuilder = toolResult(details)
|
|
336
|
-
.text(resultOutput)
|
|
337
|
-
.limits({ resultLimit: limitMeta.resultLimit?.reached });
|
|
338
|
-
if (truncation.truncated) {
|
|
339
|
-
resultBuilder.truncation(truncation, { direction: "head" });
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
return resultBuilder.done();
|
|
288
|
+
return buildResult(relativized);
|
|
343
289
|
});
|
|
344
290
|
}
|
|
345
291
|
}
|
|
@@ -728,6 +728,7 @@ export const geminiImageTool: CustomTool<typeof geminiImageSchema, GeminiImageTo
|
|
|
728
728
|
headers: {
|
|
729
729
|
"Content-Type": "application/json",
|
|
730
730
|
Authorization: `Bearer ${apiKey.apiKey}`,
|
|
731
|
+
"X-Title": "Oh-My-Pi",
|
|
731
732
|
},
|
|
732
733
|
body: JSON.stringify(requestBody),
|
|
733
734
|
signal: requestSignal,
|
|
@@ -79,7 +79,7 @@ export class InspectImageTool implements AgentTool<typeof inspectImageSchema, In
|
|
|
79
79
|
const resolvePattern = (pattern: string | undefined): Model<Api> | undefined => {
|
|
80
80
|
if (!pattern) return undefined;
|
|
81
81
|
const expanded = expandRoleAlias(pattern, this.session.settings);
|
|
82
|
-
return resolveModelFromString(expanded, availableModels, matchPreferences);
|
|
82
|
+
return resolveModelFromString(expanded, availableModels, matchPreferences, modelRegistry);
|
|
83
83
|
};
|
|
84
84
|
|
|
85
85
|
const activeModelPattern = this.session.getActiveModelString?.() ?? this.session.getModelString?.();
|
package/src/tools/read.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Database } from "bun:sqlite";
|
|
1
2
|
import * as fs from "node:fs/promises";
|
|
2
3
|
import * as path from "node:path";
|
|
3
4
|
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
@@ -38,7 +39,6 @@ import { resolveFileDisplayMode } from "../utils/file-display-mode";
|
|
|
38
39
|
import { ImageInputTooLargeError, loadImageInput, MAX_IMAGE_INPUT_BYTES } from "../utils/image-loading";
|
|
39
40
|
import { convertFileWithMarkit } from "../utils/markit";
|
|
40
41
|
import { type ArchiveReader, openArchive, parseArchivePathCandidates } from "./archive-reader";
|
|
41
|
-
|
|
42
42
|
import {
|
|
43
43
|
executeReadUrl,
|
|
44
44
|
isReadableUrlPath,
|
|
@@ -52,6 +52,22 @@ import { applyListLimit } from "./list-limit";
|
|
|
52
52
|
import { formatFullOutputReference, formatStyledTruncationWarning, type OutputMeta } from "./output-meta";
|
|
53
53
|
import { expandPath, resolveReadPath } from "./path-utils";
|
|
54
54
|
import { formatAge, formatBytes, shortenPath, wrapBrackets } from "./render-utils";
|
|
55
|
+
import {
|
|
56
|
+
executeReadQuery,
|
|
57
|
+
getRowByKey,
|
|
58
|
+
getRowByRowId,
|
|
59
|
+
getTableSchema,
|
|
60
|
+
isSqliteFile,
|
|
61
|
+
listTables,
|
|
62
|
+
parseSqlitePathCandidates,
|
|
63
|
+
parseSqliteSelector,
|
|
64
|
+
queryRows,
|
|
65
|
+
renderRow,
|
|
66
|
+
renderSchema,
|
|
67
|
+
renderTable,
|
|
68
|
+
renderTableList,
|
|
69
|
+
resolveTableRowLookup,
|
|
70
|
+
} from "./sqlite-reader";
|
|
55
71
|
import { ToolAbortError, ToolError, throwIfAborted } from "./tool-errors";
|
|
56
72
|
import { toolResult } from "./tool-result";
|
|
57
73
|
|
|
@@ -420,6 +436,29 @@ interface ResolvedArchiveReadPath {
|
|
|
420
436
|
suffixResolution?: { from: string; to: string };
|
|
421
437
|
}
|
|
422
438
|
|
|
439
|
+
interface ResolvedSqliteReadPath {
|
|
440
|
+
absolutePath: string;
|
|
441
|
+
sqliteSubPath: string;
|
|
442
|
+
queryString: string;
|
|
443
|
+
suffixResolution?: { from: string; to: string };
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function parseSqliteSelectorInput(selector: string | undefined): { subPath: string; queryString: string } {
|
|
447
|
+
if (!selector) {
|
|
448
|
+
return { subPath: "", queryString: "" };
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const queryIndex = selector.indexOf("?");
|
|
452
|
+
if (queryIndex === -1) {
|
|
453
|
+
return { subPath: selector.replace(/^:+/, ""), queryString: "" };
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
return {
|
|
457
|
+
subPath: selector.slice(0, queryIndex).replace(/^:+/, ""),
|
|
458
|
+
queryString: selector.slice(queryIndex + 1),
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
|
|
423
462
|
/**
|
|
424
463
|
* Read tool implementation.
|
|
425
464
|
*
|
|
@@ -502,6 +541,53 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
502
541
|
return null;
|
|
503
542
|
}
|
|
504
543
|
|
|
544
|
+
async #resolveSqliteReadPath(readPath: string, signal?: AbortSignal): Promise<ResolvedSqliteReadPath | null> {
|
|
545
|
+
const candidates = parseSqlitePathCandidates(readPath);
|
|
546
|
+
for (const candidate of candidates) {
|
|
547
|
+
let absolutePath = resolveReadPath(candidate.sqlitePath, this.session.cwd);
|
|
548
|
+
let suffixResolution: { from: string; to: string } | undefined;
|
|
549
|
+
|
|
550
|
+
try {
|
|
551
|
+
const stat = await Bun.file(absolutePath).stat();
|
|
552
|
+
if (stat.isDirectory()) continue;
|
|
553
|
+
if (!(await isSqliteFile(absolutePath))) continue;
|
|
554
|
+
|
|
555
|
+
return {
|
|
556
|
+
absolutePath,
|
|
557
|
+
sqliteSubPath: candidate.subPath,
|
|
558
|
+
queryString: candidate.queryString,
|
|
559
|
+
suffixResolution,
|
|
560
|
+
};
|
|
561
|
+
} catch (error) {
|
|
562
|
+
if (!isNotFoundError(error) || isRemoteMountPath(absolutePath)) continue;
|
|
563
|
+
|
|
564
|
+
const suffixMatch = await findUniqueSuffixMatch(candidate.sqlitePath, this.session.cwd, signal);
|
|
565
|
+
if (!suffixMatch) continue;
|
|
566
|
+
|
|
567
|
+
try {
|
|
568
|
+
const retryStat = await Bun.file(suffixMatch.absolutePath).stat();
|
|
569
|
+
if (retryStat.isDirectory()) continue;
|
|
570
|
+
if (!(await isSqliteFile(suffixMatch.absolutePath))) continue;
|
|
571
|
+
|
|
572
|
+
absolutePath = suffixMatch.absolutePath;
|
|
573
|
+
suffixResolution = { from: candidate.sqlitePath, to: suffixMatch.displayPath };
|
|
574
|
+
return {
|
|
575
|
+
absolutePath,
|
|
576
|
+
sqliteSubPath: candidate.subPath,
|
|
577
|
+
queryString: candidate.queryString,
|
|
578
|
+
suffixResolution,
|
|
579
|
+
};
|
|
580
|
+
} catch (retryError) {
|
|
581
|
+
if (!isNotFoundError(retryError)) {
|
|
582
|
+
throw retryError;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
return null;
|
|
589
|
+
}
|
|
590
|
+
|
|
505
591
|
#buildInMemoryTextResult(
|
|
506
592
|
text: string,
|
|
507
593
|
offset: number | undefined,
|
|
@@ -709,6 +795,132 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
709
795
|
return result;
|
|
710
796
|
}
|
|
711
797
|
|
|
798
|
+
async #readSqlite(
|
|
799
|
+
sel: string | undefined,
|
|
800
|
+
resolvedSqlitePath: ResolvedSqliteReadPath,
|
|
801
|
+
signal?: AbortSignal,
|
|
802
|
+
): Promise<AgentToolResult<ReadToolDetails>> {
|
|
803
|
+
throwIfAborted(signal);
|
|
804
|
+
|
|
805
|
+
const selectorInput = sel
|
|
806
|
+
? parseSqliteSelectorInput(sel)
|
|
807
|
+
: { subPath: resolvedSqlitePath.sqliteSubPath, queryString: resolvedSqlitePath.queryString };
|
|
808
|
+
const selector = parseSqliteSelector(selectorInput.subPath, selectorInput.queryString);
|
|
809
|
+
const details: ReadToolDetails = {
|
|
810
|
+
resolvedPath: resolvedSqlitePath.absolutePath,
|
|
811
|
+
suffixResolution: resolvedSqlitePath.suffixResolution,
|
|
812
|
+
};
|
|
813
|
+
|
|
814
|
+
let db: Database | null = null;
|
|
815
|
+
try {
|
|
816
|
+
db = new Database(resolvedSqlitePath.absolutePath, { readonly: true, strict: true });
|
|
817
|
+
db.run("PRAGMA busy_timeout = 3000");
|
|
818
|
+
throwIfAborted(signal);
|
|
819
|
+
|
|
820
|
+
switch (selector.kind) {
|
|
821
|
+
case "list": {
|
|
822
|
+
const listLimit = applyListLimit(listTables(db), { limit: 500 });
|
|
823
|
+
const output = prependSuffixResolutionNotice(
|
|
824
|
+
renderTableList(listLimit.items),
|
|
825
|
+
resolvedSqlitePath.suffixResolution,
|
|
826
|
+
);
|
|
827
|
+
const truncation = truncateHead(output, { maxLines: Number.MAX_SAFE_INTEGER });
|
|
828
|
+
details.truncation = truncation.truncated ? truncation : undefined;
|
|
829
|
+
const resultBuilder = toolResult<ReadToolDetails>(details)
|
|
830
|
+
.text(truncation.content)
|
|
831
|
+
.sourcePath(resolvedSqlitePath.absolutePath)
|
|
832
|
+
.limits({ resultLimit: listLimit.meta.resultLimit?.reached });
|
|
833
|
+
if (truncation.truncated) {
|
|
834
|
+
resultBuilder.truncation(truncation, { direction: "head" });
|
|
835
|
+
}
|
|
836
|
+
return resultBuilder.done();
|
|
837
|
+
}
|
|
838
|
+
case "schema": {
|
|
839
|
+
const sampleRows = queryRows(db, selector.table, { limit: selector.sampleLimit, offset: 0 });
|
|
840
|
+
let output = renderSchema(getTableSchema(db, selector.table), {
|
|
841
|
+
columns: sampleRows.columns,
|
|
842
|
+
rows: sampleRows.rows,
|
|
843
|
+
});
|
|
844
|
+
if (sampleRows.rows.length < sampleRows.totalCount) {
|
|
845
|
+
const remaining = sampleRows.totalCount - sampleRows.rows.length;
|
|
846
|
+
output += `\n[${remaining} more rows; use sel="${selector.table}?limit=20&offset=${sampleRows.rows.length}" to continue]`;
|
|
847
|
+
}
|
|
848
|
+
return toolResult<ReadToolDetails>(details)
|
|
849
|
+
.text(prependSuffixResolutionNotice(output, resolvedSqlitePath.suffixResolution))
|
|
850
|
+
.sourcePath(resolvedSqlitePath.absolutePath)
|
|
851
|
+
.done();
|
|
852
|
+
}
|
|
853
|
+
case "row": {
|
|
854
|
+
const lookup = resolveTableRowLookup(db, selector.table);
|
|
855
|
+
const row =
|
|
856
|
+
lookup.kind === "pk"
|
|
857
|
+
? getRowByKey(db, selector.table, lookup, selector.key)
|
|
858
|
+
: getRowByRowId(db, selector.table, selector.key);
|
|
859
|
+
if (!row) {
|
|
860
|
+
return toolResult<ReadToolDetails>(details)
|
|
861
|
+
.text(
|
|
862
|
+
prependSuffixResolutionNotice(
|
|
863
|
+
`No row found in table '${selector.table}' for key '${selector.key}'.`,
|
|
864
|
+
resolvedSqlitePath.suffixResolution,
|
|
865
|
+
),
|
|
866
|
+
)
|
|
867
|
+
.sourcePath(resolvedSqlitePath.absolutePath)
|
|
868
|
+
.done();
|
|
869
|
+
}
|
|
870
|
+
return toolResult<ReadToolDetails>(details)
|
|
871
|
+
.text(prependSuffixResolutionNotice(renderRow(row), resolvedSqlitePath.suffixResolution))
|
|
872
|
+
.sourcePath(resolvedSqlitePath.absolutePath)
|
|
873
|
+
.done();
|
|
874
|
+
}
|
|
875
|
+
case "query": {
|
|
876
|
+
const page = queryRows(db, selector.table, selector);
|
|
877
|
+
return toolResult<ReadToolDetails>(details)
|
|
878
|
+
.text(
|
|
879
|
+
prependSuffixResolutionNotice(
|
|
880
|
+
renderTable(page.columns, page.rows, {
|
|
881
|
+
totalCount: page.totalCount,
|
|
882
|
+
offset: selector.offset,
|
|
883
|
+
limit: selector.limit,
|
|
884
|
+
table: selector.table,
|
|
885
|
+
dbPath: resolvedSqlitePath.absolutePath,
|
|
886
|
+
}),
|
|
887
|
+
resolvedSqlitePath.suffixResolution,
|
|
888
|
+
),
|
|
889
|
+
)
|
|
890
|
+
.sourcePath(resolvedSqlitePath.absolutePath)
|
|
891
|
+
.done();
|
|
892
|
+
}
|
|
893
|
+
case "raw": {
|
|
894
|
+
const result = executeReadQuery(db, selector.sql);
|
|
895
|
+
return toolResult<ReadToolDetails>(details)
|
|
896
|
+
.text(
|
|
897
|
+
prependSuffixResolutionNotice(
|
|
898
|
+
renderTable(result.columns, result.rows, {
|
|
899
|
+
totalCount: result.rows.length,
|
|
900
|
+
offset: 0,
|
|
901
|
+
limit: result.rows.length || DEFAULT_MAX_LINES,
|
|
902
|
+
table: "query",
|
|
903
|
+
dbPath: resolvedSqlitePath.absolutePath,
|
|
904
|
+
}),
|
|
905
|
+
resolvedSqlitePath.suffixResolution,
|
|
906
|
+
),
|
|
907
|
+
)
|
|
908
|
+
.sourcePath(resolvedSqlitePath.absolutePath)
|
|
909
|
+
.done();
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
throw new ToolError("Unsupported SQLite selector");
|
|
914
|
+
} catch (error) {
|
|
915
|
+
if (error instanceof ToolError) {
|
|
916
|
+
throw error;
|
|
917
|
+
}
|
|
918
|
+
throw new ToolError(error instanceof Error ? error.message : String(error));
|
|
919
|
+
} finally {
|
|
920
|
+
db?.close();
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
|
|
712
924
|
async execute(
|
|
713
925
|
_toolCallId: string,
|
|
714
926
|
params: ReadParams,
|
|
@@ -769,6 +981,11 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
769
981
|
return this.#readArchive(readPath, offset, limit, archivePath, signal);
|
|
770
982
|
}
|
|
771
983
|
|
|
984
|
+
const sqlitePath = await this.#resolveSqliteReadPath(readPath, signal);
|
|
985
|
+
if (sqlitePath) {
|
|
986
|
+
return this.#readSqlite(sel, sqlitePath, signal);
|
|
987
|
+
}
|
|
988
|
+
|
|
772
989
|
let absolutePath = resolveReadPath(localReadPath, this.session.cwd);
|
|
773
990
|
let suffixResolution: { from: string; to: string } | undefined;
|
|
774
991
|
|
|
@@ -14,7 +14,7 @@ import type { Theme } from "../modes/theme/theme";
|
|
|
14
14
|
import { formatDimensionNote, type ResizedImage } from "../utils/image-resize";
|
|
15
15
|
|
|
16
16
|
export { Ellipsis } from "@oh-my-pi/pi-natives";
|
|
17
|
-
export { replaceTabs, truncateToWidth } from "@oh-my-pi/pi-tui";
|
|
17
|
+
export { replaceTabs, truncateToWidth, wrapTextWithAnsi } from "@oh-my-pi/pi-tui";
|
|
18
18
|
|
|
19
19
|
// =============================================================================
|
|
20
20
|
// Standardized Display Constants
|