@oh-my-pi/pi-coding-agent 15.9.67 → 15.10.1
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 +136 -0
- package/dist/types/cli/args.d.ts +1 -1
- package/dist/types/cli/dry-balance-cli.d.ts +15 -1
- package/dist/types/cli/gallery-cli.d.ts +43 -0
- package/dist/types/cli/gallery-fixtures/agentic.d.ts +2 -0
- package/dist/types/cli/gallery-fixtures/codeintel.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/edit.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/fs.d.ts +2 -0
- package/dist/types/cli/gallery-fixtures/index.d.ts +4 -0
- package/dist/types/cli/gallery-fixtures/interaction.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/memory.d.ts +2 -0
- package/dist/types/cli/gallery-fixtures/misc.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/search.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/shell.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/types.d.ts +44 -0
- package/dist/types/cli/gallery-fixtures/web.d.ts +2 -0
- package/dist/types/cli/gallery-screenshot.d.ts +35 -0
- package/dist/types/commands/gallery.d.ts +47 -0
- package/dist/types/commit/analysis/conventional.d.ts +2 -2
- package/dist/types/commit/analysis/summary.d.ts +2 -2
- package/dist/types/commit/changelog/generate.d.ts +2 -2
- package/dist/types/commit/changelog/index.d.ts +2 -2
- package/dist/types/commit/map-reduce/index.d.ts +3 -3
- package/dist/types/commit/map-reduce/map-phase.d.ts +2 -2
- package/dist/types/commit/map-reduce/reduce-phase.d.ts +2 -2
- package/dist/types/commit/model-selection.d.ts +10 -4
- package/dist/types/config/api-key-resolver.d.ts +34 -0
- package/dist/types/config/keybindings.d.ts +6 -1
- package/dist/types/config/model-id-affixes.d.ts +2 -0
- package/dist/types/config/model-registry.d.ts +25 -2
- package/dist/types/config/settings-schema.d.ts +41 -6
- package/dist/types/dap/config.d.ts +14 -1
- package/dist/types/dap/types.d.ts +10 -0
- package/dist/types/extensibility/plugins/marketplace-auto-update.d.ts +8 -0
- package/dist/types/lsp/types.d.ts +10 -0
- package/dist/types/lsp/utils.d.ts +3 -2
- package/dist/types/main.d.ts +3 -2
- package/dist/types/memory-backend/index.d.ts +2 -1
- package/dist/types/memory-backend/resolve.d.ts +1 -1
- package/dist/types/memory-backend/types.d.ts +1 -1
- package/dist/types/modes/components/chat-block.d.ts +64 -0
- package/dist/types/modes/components/custom-editor.d.ts +5 -1
- package/dist/types/modes/components/overlay-box.d.ts +17 -0
- package/dist/types/modes/components/plan-review-overlay.d.ts +59 -0
- package/dist/types/modes/components/plan-toc.d.ts +41 -0
- package/dist/types/modes/components/read-tool-group.d.ts +2 -0
- package/dist/types/modes/components/tool-execution.d.ts +18 -0
- package/dist/types/modes/components/transcript-container.d.ts +11 -0
- package/dist/types/modes/controllers/command-controller.d.ts +1 -0
- package/dist/types/modes/controllers/event-controller.d.ts +0 -1
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +0 -1
- package/dist/types/modes/controllers/input-controller.d.ts +1 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -1
- package/dist/types/modes/controllers/streaming-reveal.d.ts +22 -0
- package/dist/types/modes/controllers/tan-command-controller.d.ts +6 -0
- package/dist/types/modes/index.d.ts +5 -4
- package/dist/types/modes/interactive-mode.d.ts +16 -6
- package/dist/types/modes/setup-version.d.ts +11 -0
- package/dist/types/modes/setup-wizard/index.d.ts +2 -1
- package/dist/types/modes/setup-wizard/scenes/web-search.d.ts +2 -1
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/modes/types.d.ts +19 -6
- package/dist/types/modes/utils/copy-targets.d.ts +21 -1
- package/dist/types/plan-mode/approved-plan.d.ts +27 -8
- package/dist/types/plan-mode/plan-protection.d.ts +4 -4
- package/dist/types/sdk.d.ts +3 -1
- package/dist/types/session/agent-session.d.ts +21 -0
- package/dist/types/session/messages.d.ts +12 -0
- package/dist/types/session/session-manager.d.ts +3 -1
- package/dist/types/slash-commands/types.d.ts +4 -6
- package/dist/types/task/executor.d.ts +14 -0
- package/dist/types/task/index.d.ts +1 -0
- package/dist/types/task/render.d.ts +3 -2
- package/dist/types/telemetry-export.d.ts +1 -1
- package/dist/types/tools/archive-reader.d.ts +5 -0
- package/dist/types/tools/ast-edit.d.ts +3 -0
- package/dist/types/tools/ast-grep.d.ts +3 -0
- package/dist/types/tools/bash.d.ts +1 -0
- package/dist/types/tools/eval-render.d.ts +1 -8
- package/dist/types/tools/fetch.d.ts +15 -7
- package/dist/types/tools/find.d.ts +8 -4
- package/dist/types/tools/grouped-file-output.d.ts +95 -12
- package/dist/types/tools/memory-render.d.ts +4 -1
- package/dist/types/tools/plan-mode-guard.d.ts +8 -9
- package/dist/types/tools/render-utils.d.ts +13 -9
- package/dist/types/tools/renderers.d.ts +16 -2
- package/dist/types/tools/search.d.ts +5 -1
- package/dist/types/tools/sqlite-reader.d.ts +1 -0
- package/dist/types/tools/todo.d.ts +3 -2
- package/dist/types/tools/write.d.ts +5 -0
- package/dist/types/tui/output-block.d.ts +16 -4
- package/dist/types/tui/status-line.d.ts +3 -0
- package/dist/types/utils/enhanced-paste.d.ts +20 -0
- package/dist/types/web/scrapers/github.d.ts +22 -0
- package/dist/types/web/search/providers/kimi.d.ts +1 -1
- package/dist/types/web/search/providers/perplexity.d.ts +8 -1
- package/dist/types/web/search/types.d.ts +1 -1
- package/package.json +9 -9
- package/scripts/dev-launch +42 -0
- package/scripts/dev-launch-preload.ts +19 -0
- package/src/auto-thinking/classifier.ts +5 -1
- package/src/cli/args.ts +2 -2
- package/src/cli/dry-balance-cli.ts +52 -17
- package/src/cli/gallery-cli.ts +226 -0
- package/src/cli/gallery-fixtures/agentic.ts +292 -0
- package/src/cli/gallery-fixtures/codeintel.ts +188 -0
- package/src/cli/gallery-fixtures/edit.ts +194 -0
- package/src/cli/gallery-fixtures/fs.ts +153 -0
- package/src/cli/gallery-fixtures/index.ts +40 -0
- package/src/cli/gallery-fixtures/interaction.ts +49 -0
- package/src/cli/gallery-fixtures/memory.ts +81 -0
- package/src/cli/gallery-fixtures/misc.ts +250 -0
- package/src/cli/gallery-fixtures/search.ts +213 -0
- package/src/cli/gallery-fixtures/shell.ts +167 -0
- package/src/cli/gallery-fixtures/types.ts +41 -0
- package/src/cli/gallery-fixtures/web.ts +158 -0
- package/src/cli/gallery-screenshot.ts +279 -0
- package/src/cli-commands.ts +1 -0
- package/src/commands/gallery.ts +52 -0
- package/src/commands/launch.ts +1 -1
- package/src/commit/analysis/conventional.ts +2 -2
- package/src/commit/analysis/summary.ts +2 -2
- package/src/commit/changelog/generate.ts +2 -2
- package/src/commit/changelog/index.ts +2 -2
- package/src/commit/map-reduce/index.ts +3 -3
- package/src/commit/map-reduce/map-phase.ts +2 -2
- package/src/commit/map-reduce/reduce-phase.ts +2 -2
- package/src/commit/model-selection.ts +33 -9
- package/src/commit/pipeline.ts +4 -4
- package/src/config/api-key-resolver.ts +58 -0
- package/src/config/keybindings.ts +15 -6
- package/src/config/model-equivalence.ts +35 -12
- package/src/config/model-id-affixes.ts +39 -22
- package/src/config/model-registry.ts +41 -18
- package/src/config/settings-schema.ts +28 -5
- package/src/config/settings.ts +31 -2
- package/src/dap/client.ts +14 -16
- package/src/dap/config.ts +41 -2
- package/src/dap/defaults.json +1 -0
- package/src/dap/session.ts +1 -0
- package/src/dap/types.ts +10 -0
- package/src/debug/index.ts +40 -54
- package/src/edit/renderer.ts +111 -119
- package/src/eval/__tests__/agent-bridge.test.ts +75 -32
- package/src/eval/__tests__/llm-bridge.test.ts +90 -31
- package/src/eval/agent-bridge.ts +34 -7
- package/src/eval/llm-bridge.ts +8 -3
- package/src/extensibility/extensions/runner.ts +1 -0
- package/src/extensibility/plugins/doctor.ts +0 -1
- package/src/extensibility/plugins/marketplace-auto-update.ts +49 -0
- package/src/goals/tools/goal-tool.ts +37 -27
- package/src/internal-urls/docs-index.generated.ts +10 -10
- package/src/lsp/client.ts +104 -55
- package/src/lsp/types.ts +10 -0
- package/src/lsp/utils.ts +3 -2
- package/src/main.ts +53 -56
- package/src/memories/index.ts +12 -5
- package/src/memory-backend/index.ts +13 -1
- package/src/memory-backend/resolve.ts +3 -5
- package/src/memory-backend/types.ts +1 -1
- package/src/mnemopi/backend.ts +5 -1
- package/src/modes/acp/acp-agent.ts +33 -26
- package/src/modes/components/assistant-message.ts +2 -9
- package/src/modes/components/chat-block.ts +111 -0
- package/src/modes/components/copy-selector.ts +1 -44
- package/src/modes/components/custom-editor.ts +33 -1
- package/src/modes/components/custom-message.ts +1 -3
- package/src/modes/components/execution-shared.ts +1 -2
- package/src/modes/components/hook-message.ts +1 -3
- package/src/modes/components/overlay-box.ts +108 -0
- package/src/modes/components/plan-review-overlay.ts +799 -0
- package/src/modes/components/plan-toc.ts +138 -0
- package/src/modes/components/read-tool-group.ts +20 -4
- package/src/modes/components/skill-message.ts +0 -1
- package/src/modes/components/status-line.ts +3 -5
- package/src/modes/components/tips.txt +1 -0
- package/src/modes/components/todo-reminder.ts +0 -2
- package/src/modes/components/tool-execution.ts +115 -90
- package/src/modes/components/transcript-container.ts +84 -24
- package/src/modes/components/user-message.ts +1 -2
- package/src/modes/controllers/command-controller-shared.ts +7 -6
- package/src/modes/controllers/command-controller.ts +70 -57
- package/src/modes/controllers/event-controller.ts +41 -40
- package/src/modes/controllers/extension-ui-controller.ts +10 -73
- package/src/modes/controllers/input-controller.ts +135 -122
- package/src/modes/controllers/mcp-command-controller.ts +69 -60
- package/src/modes/controllers/selector-controller.ts +25 -27
- package/src/modes/controllers/streaming-reveal.ts +212 -0
- package/src/modes/controllers/tan-command-controller.ts +173 -0
- package/src/modes/index.ts +5 -4
- package/src/modes/interactive-mode.ts +171 -82
- package/src/modes/setup-version.ts +11 -0
- package/src/modes/setup-wizard/index.ts +3 -2
- package/src/modes/setup-wizard/scenes/web-search.ts +3 -2
- package/src/modes/setup-wizard/wizard-overlay.ts +1 -1
- package/src/modes/theme/theme-schema.json +1 -1
- package/src/modes/theme/theme.ts +8 -4
- package/src/modes/types.ts +19 -8
- package/src/modes/utils/context-usage.ts +10 -6
- package/src/modes/utils/copy-targets.ts +133 -27
- package/src/modes/utils/hotkeys-markdown.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +44 -46
- package/src/plan-mode/approved-plan.ts +66 -43
- package/src/plan-mode/plan-protection.ts +4 -4
- package/src/prompts/system/background-tan-dispatch.md +8 -0
- package/src/prompts/system/plan-mode-active.md +67 -58
- package/src/prompts/system/plan-mode-approved.md +1 -1
- package/src/sdk.ts +32 -60
- package/src/session/agent-session.ts +89 -13
- package/src/session/messages.ts +26 -0
- package/src/session/session-manager.ts +13 -5
- package/src/slash-commands/builtin-registry.ts +37 -10
- package/src/slash-commands/helpers/usage-report.ts +2 -0
- package/src/slash-commands/types.ts +4 -6
- package/src/task/executor.ts +25 -4
- package/src/task/index.ts +4 -0
- package/src/task/render.ts +212 -148
- package/src/telemetry-export.ts +25 -7
- package/src/tools/archive-reader.ts +64 -0
- package/src/tools/ask.ts +119 -164
- package/src/tools/ast-edit.ts +98 -71
- package/src/tools/ast-grep.ts +37 -43
- package/src/tools/bash.ts +50 -6
- package/src/tools/debug.ts +20 -8
- package/src/tools/eval-backends.ts +6 -17
- package/src/tools/eval-render.ts +21 -18
- package/src/tools/eval.ts +5 -4
- package/src/tools/fetch.ts +391 -91
- package/src/tools/find.ts +44 -30
- package/src/tools/gh-renderer.ts +81 -42
- package/src/tools/grouped-file-output.ts +272 -48
- package/src/tools/image-gen.ts +150 -103
- package/src/tools/inspect-image-renderer.ts +63 -41
- package/src/tools/inspect-image.ts +8 -1
- package/src/tools/job.ts +3 -4
- package/src/tools/memory-render.ts +4 -1
- package/src/tools/plan-mode-guard.ts +21 -39
- package/src/tools/read.ts +23 -16
- package/src/tools/render-utils.ts +38 -40
- package/src/tools/renderers.ts +16 -1
- package/src/tools/report-tool-issue.ts +1 -1
- package/src/tools/resolve.ts +14 -0
- package/src/tools/search-tool-bm25.ts +36 -23
- package/src/tools/search.ts +189 -95
- package/src/tools/sqlite-reader.ts +9 -12
- package/src/tools/todo.ts +138 -59
- package/src/tools/write.ts +100 -60
- package/src/tui/output-block.ts +60 -13
- package/src/tui/status-line.ts +5 -1
- package/src/utils/commit-message-generator.ts +9 -1
- package/src/utils/enhanced-paste.ts +202 -0
- package/src/utils/title-generator.ts +2 -1
- package/src/web/scrapers/github.ts +255 -3
- package/src/web/scrapers/youtube.ts +3 -2
- package/src/web/search/providers/anthropic.ts +25 -19
- package/src/web/search/providers/exa.ts +11 -3
- package/src/web/search/providers/kimi.ts +28 -17
- package/src/web/search/providers/parallel.ts +35 -24
- package/src/web/search/providers/perplexity.ts +199 -51
- package/src/web/search/providers/synthetic.ts +8 -6
- package/src/web/search/providers/tavily.ts +9 -8
- package/src/web/search/providers/zai.ts +8 -6
- package/src/web/search/render.ts +39 -54
- package/src/web/search/types.ts +5 -1
- package/dist/types/eval/__tests__/shared-executors.test.d.ts +0 -1
- package/src/eval/__tests__/shared-executors.test.ts +0 -609
|
@@ -6,10 +6,127 @@ function isUrlLikePath(filePath: string): boolean {
|
|
|
6
6
|
return URL_LIKE_PATH_RE.test(filePath);
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
+
// =============================================================================
|
|
10
|
+
// Multi-level path tree
|
|
11
|
+
// =============================================================================
|
|
12
|
+
//
|
|
13
|
+
// File listings (grep / ast-grep / ast-edit / lsp diagnostics / find) used to
|
|
14
|
+
// group by the *immediate* parent directory and print the full directory path in
|
|
15
|
+
// every header. For results spread across a deep tree — or rooted outside cwd,
|
|
16
|
+
// where paths stay absolute — that repeated the shared prefix on every line. The
|
|
17
|
+
// tree below folds single-child directory chains (so the common prefix collapses
|
|
18
|
+
// into one header) and nests the rest, charging the model one token per path
|
|
19
|
+
// segment instead of one per file.
|
|
20
|
+
|
|
21
|
+
interface PathTreeNode {
|
|
22
|
+
/** Direct file leaves, in first-seen order. */
|
|
23
|
+
files: Array<{ name: string; key: string }>;
|
|
24
|
+
/** Dedup set for `files` (a glob can surface the same path twice on retry). */
|
|
25
|
+
fileNames: Set<string>;
|
|
26
|
+
/** Child directories, in first-seen order. */
|
|
27
|
+
subdirs: Array<{ name: string; node: PathTreeNode }>;
|
|
28
|
+
/** Dedup index for `subdirs`. */
|
|
29
|
+
dirIndex: Map<string, PathTreeNode>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface PathTreeInput {
|
|
33
|
+
/** Path string; absolute, cwd-relative, or url-like. Backslashes are normalized. */
|
|
34
|
+
path: string;
|
|
35
|
+
/** Whether the leaf itself is a directory (trailing-slash match from find). */
|
|
36
|
+
isDir: boolean;
|
|
37
|
+
/** Opaque key carried onto file events for section lookup. Defaults to `path`. */
|
|
38
|
+
key?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** One node emitted while walking the tree: a folded directory or a file leaf. */
|
|
42
|
+
export interface GroupedTreeEvent {
|
|
43
|
+
kind: "dir" | "file";
|
|
44
|
+
/** 0-based nesting depth (root children are depth 0). */
|
|
45
|
+
depth: number;
|
|
46
|
+
/** Folded chain for dirs (e.g. `a/b/c`, no trailing slash); basename for files. */
|
|
47
|
+
name: string;
|
|
48
|
+
/** File key for `kind === "file"`; empty string for directories. */
|
|
49
|
+
key: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function createNode(): PathTreeNode {
|
|
53
|
+
return { files: [], fileNames: new Set(), subdirs: [], dirIndex: new Map() };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function addFile(node: PathTreeNode, name: string, key: string): void {
|
|
57
|
+
if (node.fileNames.has(name)) return;
|
|
58
|
+
node.fileNames.add(name);
|
|
59
|
+
node.files.push({ name, key });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Build a directory tree from a flat list of paths. URL-like entries are kept
|
|
64
|
+
* whole as root-level file leaves (they have no meaningful directory structure).
|
|
65
|
+
* Absolute paths carry a leading empty segment so they share a common `/` root
|
|
66
|
+
* and fold like any other prefix.
|
|
67
|
+
*/
|
|
68
|
+
export function buildPathTree(entries: Iterable<PathTreeInput>): PathTreeNode {
|
|
69
|
+
const root = createNode();
|
|
70
|
+
for (const { path: rawPath, isDir, key } of entries) {
|
|
71
|
+
const normalized = rawPath.replace(/\\/g, "/");
|
|
72
|
+
const fileKey = key ?? rawPath;
|
|
73
|
+
if (isUrlLikePath(normalized)) {
|
|
74
|
+
addFile(root, normalized, fileKey);
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const trimmed = normalized.endsWith("/") ? normalized.slice(0, -1) : normalized;
|
|
78
|
+
if (trimmed.length === 0) continue;
|
|
79
|
+
const segments = trimmed.split("/");
|
|
80
|
+
const dirCount = isDir ? segments.length : segments.length - 1;
|
|
81
|
+
let node = root;
|
|
82
|
+
for (let i = 0; i < dirCount; i++) {
|
|
83
|
+
const segment = segments[i]!;
|
|
84
|
+
let child = node.dirIndex.get(segment);
|
|
85
|
+
if (!child) {
|
|
86
|
+
child = createNode();
|
|
87
|
+
node.dirIndex.set(segment, child);
|
|
88
|
+
node.subdirs.push({ name: segment, node: child });
|
|
89
|
+
}
|
|
90
|
+
node = child;
|
|
91
|
+
}
|
|
92
|
+
if (!isDir) {
|
|
93
|
+
addFile(node, segments[segments.length - 1]!, fileKey);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return root;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Depth-first walk yielding directory and file events. Directories collapse their
|
|
101
|
+
* single-child chains (`a` → `a/b` → `a/b/c`) so a shared prefix becomes one
|
|
102
|
+
* header. Each node's direct files are emitted before its subdirectories, keeping
|
|
103
|
+
* a file unambiguously attached to the header above it.
|
|
104
|
+
*/
|
|
105
|
+
export function* walkPathTree(node: PathTreeNode, depth = 0): Generator<GroupedTreeEvent> {
|
|
106
|
+
for (const file of node.files) {
|
|
107
|
+
yield { kind: "file", depth, name: file.name, key: file.key };
|
|
108
|
+
}
|
|
109
|
+
for (const subdir of node.subdirs) {
|
|
110
|
+
let dirNode = subdir.node;
|
|
111
|
+
const parts = [subdir.name];
|
|
112
|
+
while (dirNode.files.length === 0 && dirNode.subdirs.length === 1) {
|
|
113
|
+
const only = dirNode.subdirs[0]!;
|
|
114
|
+
parts.push(only.name);
|
|
115
|
+
dirNode = only.node;
|
|
116
|
+
}
|
|
117
|
+
yield { kind: "dir", depth, name: parts.join("/"), key: "" };
|
|
118
|
+
yield* walkPathTree(dirNode, depth + 1);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// =============================================================================
|
|
123
|
+
// Grouped file output (grep / ast-grep / ast-edit / lsp diagnostics)
|
|
124
|
+
// =============================================================================
|
|
125
|
+
|
|
9
126
|
/**
|
|
10
127
|
* One file's contribution to a grouped file output. The header itself is generated
|
|
11
|
-
* by `formatGroupedFiles` (
|
|
12
|
-
*
|
|
128
|
+
* by `formatGroupedFiles` (one `#` per nesting level); use `headerSuffix` to tack
|
|
129
|
+
* on extras like ` (1 replacement)`.
|
|
13
130
|
*/
|
|
14
131
|
export interface GroupedFileSection {
|
|
15
132
|
/** Optional suffix appended to the file header. */
|
|
@@ -28,76 +145,183 @@ export interface GroupedFilesOutput {
|
|
|
28
145
|
}
|
|
29
146
|
|
|
30
147
|
/**
|
|
31
|
-
* Render a list of files as
|
|
32
|
-
* ast-edit, and the LSP diagnostic formatter.
|
|
148
|
+
* Render a list of files as a multi-level, prefix-folded directory tree shared by
|
|
149
|
+
* grep, ast-grep, ast-edit, and the LSP diagnostic formatter.
|
|
33
150
|
*
|
|
34
|
-
* Layout:
|
|
35
|
-
* #
|
|
36
|
-
* ##
|
|
151
|
+
* Layout (one `#` per level; the shared prefix folds into the top header):
|
|
152
|
+
* # packages/pkg/src/
|
|
153
|
+
* ## root.ts
|
|
37
154
|
* …body…
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* ## other.ts
|
|
155
|
+
* ## nested/
|
|
156
|
+
* ### child.ts
|
|
41
157
|
* …body…
|
|
42
158
|
*
|
|
43
|
-
* Files in the project root
|
|
44
|
-
*
|
|
159
|
+
* Files in the (folded) project root become single-`#` headers with no parent
|
|
160
|
+
* directory line. A blank line precedes every directory header and every
|
|
161
|
+
* root-level file so the renderers can split the output into collapsible groups.
|
|
45
162
|
*/
|
|
46
163
|
export function formatGroupedFiles(
|
|
47
164
|
files: string[],
|
|
48
165
|
renderFile: (filePath: string) => GroupedFileSection,
|
|
49
166
|
): GroupedFilesOutput {
|
|
50
|
-
const
|
|
167
|
+
const sections = new Map<string, GroupedFileSection>();
|
|
168
|
+
const inputs: PathTreeInput[] = [];
|
|
51
169
|
for (const filePath of files) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
170
|
+
if (sections.has(filePath)) continue;
|
|
171
|
+
const section = renderFile(filePath);
|
|
172
|
+
if (section.skip) continue;
|
|
173
|
+
sections.set(filePath, section);
|
|
174
|
+
inputs.push({ path: filePath, isDir: false, key: filePath });
|
|
57
175
|
}
|
|
58
176
|
|
|
177
|
+
const tree = buildPathTree(inputs);
|
|
59
178
|
const model: string[] = [];
|
|
60
179
|
const display: string[] = [];
|
|
180
|
+
let emitted = false;
|
|
61
181
|
|
|
62
|
-
const
|
|
63
|
-
|
|
182
|
+
for (const event of walkPathTree(tree)) {
|
|
183
|
+
const hashes = "#".repeat(event.depth + 1);
|
|
184
|
+
const needsSeparator = emitted && (event.depth === 0 || event.kind === "dir");
|
|
185
|
+
if (needsSeparator) {
|
|
64
186
|
model.push("");
|
|
65
187
|
display.push("");
|
|
66
188
|
}
|
|
189
|
+
emitted = true;
|
|
190
|
+
if (event.kind === "dir") {
|
|
191
|
+
const header = `${hashes} ${event.name}/`;
|
|
192
|
+
model.push(header);
|
|
193
|
+
display.push(header);
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
const section = sections.get(event.key)!;
|
|
197
|
+
const header = `${hashes} ${event.name}${section.headerSuffix ?? ""}`;
|
|
198
|
+
model.push(header, ...section.modelLines);
|
|
199
|
+
display.push(header, ...(section.displayLines ?? section.modelLines));
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return { model, display };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// =============================================================================
|
|
206
|
+
// Parsing grouped output back into per-line context (TUI renderers)
|
|
207
|
+
// =============================================================================
|
|
208
|
+
|
|
209
|
+
const GROUPED_HEADER_RE = /^(#+)\s+(.*)$/;
|
|
210
|
+
const HEADER_SUFFIX_RE = /\s+\([^)]*\)\s*$/;
|
|
211
|
+
const HEADER_HASH_TAG_RE = /#[0-9a-f]+$/i;
|
|
212
|
+
|
|
213
|
+
/** Per-line classification of grouped output, used by renderers for hyperlinks. */
|
|
214
|
+
export interface GroupedLineContext {
|
|
215
|
+
/** Directory header, file header, or any non-header body/content line. */
|
|
216
|
+
kind: "dir" | "file" | "content";
|
|
217
|
+
/** Number of leading `#` for headers; 0 for content lines. */
|
|
218
|
+
depth: number;
|
|
219
|
+
/** Resolved absolute path of the dir/file a header points at (when resolvable). */
|
|
220
|
+
headerPath?: string;
|
|
221
|
+
/** For content lines, the absolute path of the owning file (line hyperlinks). */
|
|
222
|
+
filePath?: string;
|
|
223
|
+
/** Header is an internal/url-like target the caller resolves itself. */
|
|
224
|
+
isUrl?: boolean;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function resolveGroupedPath(parent: string | undefined, name: string): string | undefined {
|
|
228
|
+
if (parent === undefined) return undefined;
|
|
229
|
+
if (name === "" || name === ".") return parent;
|
|
230
|
+
// `path.resolve` keeps an absolute `name` intact (out-of-cwd results) while
|
|
231
|
+
// joining a relative folded chain (`packages/pkg/src`) onto the parent.
|
|
232
|
+
return path.resolve(parent, name);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Walk grouped output lines, tracking a directory stack keyed by header depth, so
|
|
237
|
+
* each header and body line can be linked back to its absolute filesystem path.
|
|
238
|
+
* Reconstruction is stack-based (not per-blank-group) so nested directory headers
|
|
239
|
+
* resolve correctly across the whole output.
|
|
240
|
+
*
|
|
241
|
+
* `headerBase` is the directory the displayed (folded) header paths are relative
|
|
242
|
+
* to — for grep/ast tools that is the session cwd, since display paths are
|
|
243
|
+
* formatted relative to cwd regardless of the (sub)directory the search was
|
|
244
|
+
* scoped to. `fileScope` is the initial owning file for body lines that appear
|
|
245
|
+
* before any header (single-file scopes have no `#` headers); it defaults to
|
|
246
|
+
* `headerBase` and should be passed the scoped file's absolute path.
|
|
247
|
+
*/
|
|
248
|
+
export function classifyGroupedLines(
|
|
249
|
+
lines: readonly string[],
|
|
250
|
+
headerBase: string | undefined,
|
|
251
|
+
fileScope: string | undefined = headerBase,
|
|
252
|
+
): GroupedLineContext[] {
|
|
253
|
+
const result: GroupedLineContext[] = [];
|
|
254
|
+
const dirAtDepth = new Map<number, string>();
|
|
255
|
+
// Body lines before any header (single-file scopes) link to the scoped file.
|
|
256
|
+
let currentFile = fileScope;
|
|
257
|
+
|
|
258
|
+
const clearDeeper = (depth: number) => {
|
|
259
|
+
for (const key of dirAtDepth.keys()) {
|
|
260
|
+
if (key >= depth) dirAtDepth.delete(key);
|
|
261
|
+
}
|
|
67
262
|
};
|
|
68
263
|
|
|
69
|
-
for (const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if (section.skip) continue;
|
|
74
|
-
pushSeparatorIfNeeded();
|
|
75
|
-
const headerName = isUrlLikePath(filePath) ? filePath : path.basename(filePath);
|
|
76
|
-
const header = `# ${headerName}${section.headerSuffix ?? ""}`;
|
|
77
|
-
model.push(header, ...section.modelLines);
|
|
78
|
-
display.push(header, ...(section.displayLines ?? section.modelLines));
|
|
79
|
-
}
|
|
264
|
+
for (const line of lines) {
|
|
265
|
+
const match = GROUPED_HEADER_RE.exec(line);
|
|
266
|
+
if (!match) {
|
|
267
|
+
result.push({ kind: "content", depth: 0, filePath: currentFile });
|
|
80
268
|
continue;
|
|
81
269
|
}
|
|
82
|
-
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
270
|
+
const depth = match[1]!.length;
|
|
271
|
+
const rest = match[2]!.trimEnd();
|
|
272
|
+
if (isUrlLikePath(rest)) {
|
|
273
|
+
clearDeeper(depth);
|
|
274
|
+
currentFile = undefined;
|
|
275
|
+
result.push({ kind: "file", depth, isUrl: true });
|
|
276
|
+
continue;
|
|
88
277
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
display.push(fileHeader, ...(section.displayLines ?? section.modelLines));
|
|
278
|
+
const parent = depth > 1 ? dirAtDepth.get(depth - 1) : headerBase;
|
|
279
|
+
if (rest.endsWith("/")) {
|
|
280
|
+
const name = rest.slice(0, -1).replace(HEADER_SUFFIX_RE, "");
|
|
281
|
+
const abs = resolveGroupedPath(parent, name);
|
|
282
|
+
clearDeeper(depth);
|
|
283
|
+
if (abs !== undefined) dirAtDepth.set(depth, abs);
|
|
284
|
+
currentFile = undefined;
|
|
285
|
+
result.push({ kind: "dir", depth, headerPath: abs });
|
|
286
|
+
continue;
|
|
99
287
|
}
|
|
288
|
+
const name = rest.replace(HEADER_SUFFIX_RE, "").replace(HEADER_HASH_TAG_RE, "");
|
|
289
|
+
const abs = name ? resolveGroupedPath(parent, name) : undefined;
|
|
290
|
+
currentFile = abs;
|
|
291
|
+
result.push({ kind: "file", depth, headerPath: abs });
|
|
100
292
|
}
|
|
101
293
|
|
|
102
|
-
return
|
|
294
|
+
return result;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Split line indices into blank-line-separated groups, mirroring
|
|
299
|
+
* `splitGroupsByBlankLine`: when any blank line is present, break on runs of
|
|
300
|
+
* blanks; otherwise return a single group of the non-empty lines. Returning
|
|
301
|
+
* indices lets callers slice parallel arrays (raw lines, styled lines, contexts).
|
|
302
|
+
*/
|
|
303
|
+
export function groupLineIndicesByBlank(rawLines: readonly string[]): number[][] {
|
|
304
|
+
const hasSeparators = rawLines.some(line => line.trim().length === 0);
|
|
305
|
+
const groups: number[][] = [];
|
|
306
|
+
if (hasSeparators) {
|
|
307
|
+
let current: number[] = [];
|
|
308
|
+
for (let i = 0; i < rawLines.length; i++) {
|
|
309
|
+
if (rawLines[i]!.trim().length === 0) {
|
|
310
|
+
if (current.length > 0) {
|
|
311
|
+
groups.push(current);
|
|
312
|
+
current = [];
|
|
313
|
+
}
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
current.push(i);
|
|
317
|
+
}
|
|
318
|
+
if (current.length > 0) groups.push(current);
|
|
319
|
+
} else {
|
|
320
|
+
const current: number[] = [];
|
|
321
|
+
for (let i = 0; i < rawLines.length; i++) {
|
|
322
|
+
if (rawLines[i]!.trim().length > 0) current.push(i);
|
|
323
|
+
}
|
|
324
|
+
if (current.length > 0) groups.push(current);
|
|
325
|
+
}
|
|
326
|
+
return groups;
|
|
103
327
|
}
|
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 { getAntigravityUserAgent, getEnvApiKey, type Model } from "@oh-my-pi/pi-ai";
|
|
3
|
+
import { type ApiKey, getAntigravityUserAgent, getEnvApiKey, type Model, withAuth } from "@oh-my-pi/pi-ai";
|
|
4
4
|
import {
|
|
5
5
|
CODEX_BASE_URL,
|
|
6
6
|
getCodexAccountId,
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
} from "@oh-my-pi/pi-utils";
|
|
21
21
|
import * as z from "zod/v4";
|
|
22
22
|
import packageJson from "../../package.json" with { type: "json" };
|
|
23
|
+
|
|
23
24
|
import { isAuthenticated, type ModelRegistry } from "../config/model-registry";
|
|
24
25
|
import type { CustomTool } from "../extensibility/custom-tools/types";
|
|
25
26
|
import { ohMyPiXAIUserAgent, resolveXAIHttpCredentials } from "../lib/xai-http";
|
|
@@ -864,7 +865,10 @@ async function generateOpenAIHostedImage(
|
|
|
864
865
|
|
|
865
866
|
if (!response.ok) {
|
|
866
867
|
const errorText = await response.text();
|
|
867
|
-
throw
|
|
868
|
+
throw Object.assign(
|
|
869
|
+
new Error(`OpenAI image request failed (${response.status}): ${getOpenAIResponseErrorMessage(errorText)}`),
|
|
870
|
+
{ status: response.status },
|
|
871
|
+
);
|
|
868
872
|
}
|
|
869
873
|
|
|
870
874
|
const contentType = response.headers.get("content-type") ?? "";
|
|
@@ -1037,13 +1041,16 @@ export const imageGenTool: CustomTool<typeof imageGenSchema, ImageGenToolDetails
|
|
|
1037
1041
|
throw new Error("Missing active GPT model for OpenAI image generation");
|
|
1038
1042
|
}
|
|
1039
1043
|
|
|
1040
|
-
const
|
|
1041
|
-
|
|
1042
|
-
apiKey.model,
|
|
1043
|
-
params,
|
|
1044
|
-
resolvedImages,
|
|
1045
|
-
requestSignal,
|
|
1044
|
+
const hostedModel = apiKey.model;
|
|
1045
|
+
const hostedKey: ApiKey = ctx.modelRegistry.resolver(hostedModel.provider, {
|
|
1046
1046
|
sessionId,
|
|
1047
|
+
baseUrl: hostedModel.baseUrl,
|
|
1048
|
+
});
|
|
1049
|
+
|
|
1050
|
+
const parsed = await withAuth(
|
|
1051
|
+
hostedKey,
|
|
1052
|
+
key => generateOpenAIHostedImage(key, hostedModel, params, resolvedImages, requestSignal, sessionId),
|
|
1053
|
+
{ signal: requestSignal },
|
|
1047
1054
|
);
|
|
1048
1055
|
|
|
1049
1056
|
if (parsed.images.length === 0) {
|
|
@@ -1088,38 +1095,57 @@ export const imageGenTool: CustomTool<typeof imageGenSchema, ImageGenToolDetails
|
|
|
1088
1095
|
}
|
|
1089
1096
|
|
|
1090
1097
|
const prompt = assemblePrompt(params);
|
|
1091
|
-
const
|
|
1092
|
-
|
|
1093
|
-
model,
|
|
1094
|
-
apiKey.projectId,
|
|
1095
|
-
params.aspect_ratio,
|
|
1096
|
-
params.image_size,
|
|
1097
|
-
resolvedImages,
|
|
1098
|
-
);
|
|
1099
|
-
|
|
1100
|
-
const response = await fetch(`${ANTIGRAVITY_ENDPOINT}/v1internal:streamGenerateContent?alt=sse`, {
|
|
1101
|
-
method: "POST",
|
|
1102
|
-
headers: {
|
|
1103
|
-
Authorization: `Bearer ${apiKey.apiKey}`,
|
|
1104
|
-
"Content-Type": "application/json",
|
|
1105
|
-
Accept: "text/event-stream",
|
|
1106
|
-
"User-Agent": getAntigravityUserAgent(),
|
|
1107
|
-
},
|
|
1108
|
-
body: JSON.stringify(requestBody),
|
|
1109
|
-
signal: requestSignal,
|
|
1098
|
+
const antigravityKey: ApiKey = ctx.modelRegistry.resolver("google-antigravity", {
|
|
1099
|
+
sessionId,
|
|
1110
1100
|
});
|
|
1111
1101
|
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1102
|
+
const response = await withAuth(
|
|
1103
|
+
antigravityKey,
|
|
1104
|
+
async key => {
|
|
1105
|
+
// On a retry the resolver yields the raw stored credential JSON
|
|
1106
|
+
// ({ token, projectId }); the initial seed is the already-parsed
|
|
1107
|
+
// access token. Tolerate both, falling back to the seed projectId.
|
|
1108
|
+
const rotated = parseAntigravityCredentials(key);
|
|
1109
|
+
const bearer = rotated?.accessToken ?? key;
|
|
1110
|
+
const projectId = rotated?.projectId ?? apiKey.projectId!;
|
|
1111
|
+
const requestBody = buildAntigravityRequest(
|
|
1112
|
+
prompt,
|
|
1113
|
+
model,
|
|
1114
|
+
projectId,
|
|
1115
|
+
params.aspect_ratio,
|
|
1116
|
+
params.image_size,
|
|
1117
|
+
resolvedImages,
|
|
1118
|
+
);
|
|
1119
|
+
|
|
1120
|
+
const resp = await fetch(`${ANTIGRAVITY_ENDPOINT}/v1internal:streamGenerateContent?alt=sse`, {
|
|
1121
|
+
method: "POST",
|
|
1122
|
+
headers: {
|
|
1123
|
+
Authorization: `Bearer ${bearer}`,
|
|
1124
|
+
"Content-Type": "application/json",
|
|
1125
|
+
Accept: "text/event-stream",
|
|
1126
|
+
"User-Agent": getAntigravityUserAgent(),
|
|
1127
|
+
},
|
|
1128
|
+
body: JSON.stringify(requestBody),
|
|
1129
|
+
signal: requestSignal,
|
|
1130
|
+
});
|
|
1131
|
+
|
|
1132
|
+
if (!resp.ok) {
|
|
1133
|
+
const errorText = await resp.text();
|
|
1134
|
+
let message = errorText;
|
|
1135
|
+
try {
|
|
1136
|
+
const parsedErr = JSON.parse(errorText) as { error?: { message?: string } };
|
|
1137
|
+
message = parsedErr.error?.message ?? message;
|
|
1138
|
+
} catch {
|
|
1139
|
+
// Keep raw text.
|
|
1140
|
+
}
|
|
1141
|
+
throw Object.assign(new Error(`Antigravity image request failed (${resp.status}): ${message}`), {
|
|
1142
|
+
status: resp.status,
|
|
1143
|
+
});
|
|
1144
|
+
}
|
|
1145
|
+
return resp;
|
|
1146
|
+
},
|
|
1147
|
+
{ signal: requestSignal },
|
|
1148
|
+
);
|
|
1123
1149
|
|
|
1124
1150
|
const parsed = await parseAntigravitySseForImage(response, requestSignal);
|
|
1125
1151
|
const responseText = parsed.text.length > 0 ? parsed.text.join(" ") : undefined;
|
|
@@ -1191,28 +1217,41 @@ export const imageGenTool: CustomTool<typeof imageGenSchema, ImageGenToolDetails
|
|
|
1191
1217
|
: xaiBaseBody;
|
|
1192
1218
|
const xaiEndpoint = isEdit ? "/images/edits" : "/images/generations";
|
|
1193
1219
|
|
|
1194
|
-
const
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
Authorization: `Bearer ${xaiCreds.apiKey}`,
|
|
1198
|
-
"Content-Type": "application/json",
|
|
1199
|
-
"User-Agent": ohMyPiXAIUserAgent(),
|
|
1200
|
-
},
|
|
1201
|
-
body: JSON.stringify(xaiBody),
|
|
1202
|
-
signal: requestSignal,
|
|
1220
|
+
const xaiKey: ApiKey = ctx.modelRegistry.resolver(xaiCreds.provider, {
|
|
1221
|
+
sessionId,
|
|
1222
|
+
baseUrl: xaiCreds.baseURL,
|
|
1203
1223
|
});
|
|
1204
1224
|
|
|
1205
|
-
const xaiRawText = await
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1225
|
+
const xaiRawText = await withAuth(
|
|
1226
|
+
xaiKey,
|
|
1227
|
+
async key => {
|
|
1228
|
+
const resp = await fetch(`${xaiCreds.baseURL}${xaiEndpoint}`, {
|
|
1229
|
+
method: "POST",
|
|
1230
|
+
headers: {
|
|
1231
|
+
Authorization: `Bearer ${key}`,
|
|
1232
|
+
"Content-Type": "application/json",
|
|
1233
|
+
"User-Agent": ohMyPiXAIUserAgent(),
|
|
1234
|
+
},
|
|
1235
|
+
body: JSON.stringify(xaiBody),
|
|
1236
|
+
signal: requestSignal,
|
|
1237
|
+
});
|
|
1238
|
+
const rawText = await resp.text();
|
|
1239
|
+
if (!resp.ok) {
|
|
1240
|
+
let message = rawText;
|
|
1241
|
+
try {
|
|
1242
|
+
const parsedErr = JSON.parse(rawText) as { error?: { message?: string } };
|
|
1243
|
+
message = parsedErr.error?.message ?? message;
|
|
1244
|
+
} catch {
|
|
1245
|
+
// Keep raw text.
|
|
1246
|
+
}
|
|
1247
|
+
throw Object.assign(new Error(`xAI image request failed (${resp.status}): ${message}`), {
|
|
1248
|
+
status: resp.status,
|
|
1249
|
+
});
|
|
1250
|
+
}
|
|
1251
|
+
return rawText;
|
|
1252
|
+
},
|
|
1253
|
+
{ signal: requestSignal },
|
|
1254
|
+
);
|
|
1216
1255
|
|
|
1217
1256
|
const xaiData = JSON.parse(xaiRawText) as {
|
|
1218
1257
|
data?: Array<{ b64_json?: string; url?: string }>;
|
|
@@ -1269,30 +1308,34 @@ export const imageGenTool: CustomTool<typeof imageGenSchema, ImageGenToolDetails
|
|
|
1269
1308
|
messages: [{ role: "user" as const, content: contentParts }],
|
|
1270
1309
|
};
|
|
1271
1310
|
|
|
1272
|
-
const
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1311
|
+
const rawText = await withAuth(apiKey.apiKey, async key => {
|
|
1312
|
+
const resp = await fetch("https://openrouter.ai/api/v1/chat/completions", {
|
|
1313
|
+
method: "POST",
|
|
1314
|
+
headers: {
|
|
1315
|
+
"Content-Type": "application/json",
|
|
1316
|
+
Authorization: `Bearer ${key}`,
|
|
1317
|
+
"HTTP-Referer": "https://omp.sh/",
|
|
1318
|
+
"X-OpenRouter-Title": "Oh-My-Pi",
|
|
1319
|
+
"X-OpenRouter-Categories": "cli-agent",
|
|
1320
|
+
},
|
|
1321
|
+
body: JSON.stringify(requestBody),
|
|
1322
|
+
signal: requestSignal,
|
|
1323
|
+
});
|
|
1324
|
+
const text = await resp.text();
|
|
1325
|
+
if (!resp.ok) {
|
|
1326
|
+
let message = text;
|
|
1327
|
+
try {
|
|
1328
|
+
const parsed = JSON.parse(text) as { error?: { message?: string } };
|
|
1329
|
+
message = parsed.error?.message ?? message;
|
|
1330
|
+
} catch {
|
|
1331
|
+
// Keep raw text.
|
|
1332
|
+
}
|
|
1333
|
+
throw Object.assign(new Error(`OpenRouter image request failed (${resp.status}): ${message}`), {
|
|
1334
|
+
status: resp.status,
|
|
1335
|
+
});
|
|
1293
1336
|
}
|
|
1294
|
-
|
|
1295
|
-
}
|
|
1337
|
+
return text;
|
|
1338
|
+
});
|
|
1296
1339
|
|
|
1297
1340
|
const data = JSON.parse(rawText) as OpenRouterResponse;
|
|
1298
1341
|
const message = data.choices?.[0]?.message;
|
|
@@ -1360,30 +1403,34 @@ export const imageGenTool: CustomTool<typeof imageGenSchema, ImageGenToolDetails
|
|
|
1360
1403
|
generationConfig,
|
|
1361
1404
|
};
|
|
1362
1405
|
|
|
1363
|
-
const
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1406
|
+
const rawText = await withAuth(apiKey.apiKey, async key => {
|
|
1407
|
+
const resp = await fetch(
|
|
1408
|
+
`https://generativelanguage.googleapis.com/v1beta/models/${encodeURIComponent(model)}:generateContent`,
|
|
1409
|
+
{
|
|
1410
|
+
method: "POST",
|
|
1411
|
+
headers: {
|
|
1412
|
+
"Content-Type": "application/json",
|
|
1413
|
+
"x-goog-api-key": key,
|
|
1414
|
+
},
|
|
1415
|
+
body: JSON.stringify(requestBody),
|
|
1416
|
+
signal: requestSignal,
|
|
1370
1417
|
},
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1418
|
+
);
|
|
1419
|
+
const text = await resp.text();
|
|
1420
|
+
if (!resp.ok) {
|
|
1421
|
+
let message = text;
|
|
1422
|
+
try {
|
|
1423
|
+
const parsed = JSON.parse(text) as { error?: { message?: string } };
|
|
1424
|
+
message = parsed.error?.message ?? message;
|
|
1425
|
+
} catch {
|
|
1426
|
+
// Keep raw text.
|
|
1427
|
+
}
|
|
1428
|
+
throw Object.assign(new Error(`Gemini image request failed (${resp.status}): ${message}`), {
|
|
1429
|
+
status: resp.status,
|
|
1430
|
+
});
|
|
1384
1431
|
}
|
|
1385
|
-
|
|
1386
|
-
}
|
|
1432
|
+
return text;
|
|
1433
|
+
});
|
|
1387
1434
|
|
|
1388
1435
|
const data = JSON.parse(rawText) as GeminiGenerateContentResponse;
|
|
1389
1436
|
const responseParts = combineParts(data);
|