@oh-my-pi/pi-coding-agent 16.0.4 → 16.0.6
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 +94 -0
- package/dist/cli.js +2027 -1396
- package/dist/types/advisor/advise-tool.d.ts +31 -19
- package/dist/types/autoresearch/tools/init-experiment.d.ts +13 -17
- package/dist/types/autoresearch/tools/log-experiment.d.ts +17 -19
- package/dist/types/autoresearch/tools/run-experiment.d.ts +3 -4
- package/dist/types/autoresearch/tools/update-notes.d.ts +4 -5
- package/dist/types/cli/args.d.ts +1 -0
- package/dist/types/cli/bench-cli.d.ts +6 -0
- package/dist/types/cli/ttsr-cli.d.ts +39 -0
- package/dist/types/commands/launch.d.ts +3 -0
- package/dist/types/commands/ttsr.d.ts +57 -0
- package/dist/types/commit/agentic/tools/analyze-file.d.ts +4 -5
- package/dist/types/commit/agentic/tools/git-file-diff.d.ts +4 -5
- package/dist/types/commit/agentic/tools/git-hunk.d.ts +5 -6
- package/dist/types/commit/agentic/tools/git-overview.d.ts +4 -5
- package/dist/types/commit/agentic/tools/propose-changelog.d.ts +23 -24
- package/dist/types/commit/agentic/tools/propose-commit.d.ts +11 -32
- package/dist/types/commit/agentic/tools/recent-commits.d.ts +3 -4
- package/dist/types/commit/agentic/tools/schemas.d.ts +6 -27
- package/dist/types/commit/agentic/tools/split-commit.d.ts +28 -49
- package/dist/types/commit/changelog/generate.d.ts +12 -13
- package/dist/types/commit/shared-llm.d.ts +10 -37
- package/dist/types/config/config-file.d.ts +4 -4
- package/dist/types/config/keybindings.d.ts +5 -0
- package/dist/types/config/models-config-schema.d.ts +625 -990
- package/dist/types/config/models-config.d.ts +229 -217
- package/dist/types/config/settings-schema.d.ts +144 -25
- package/dist/types/edit/hashline/params.d.ts +7 -11
- package/dist/types/edit/index.d.ts +2 -1
- package/dist/types/edit/modes/apply-patch.d.ts +4 -5
- package/dist/types/edit/modes/patch.d.ts +15 -24
- package/dist/types/edit/modes/replace.d.ts +16 -17
- package/dist/types/eval/js/index.d.ts +1 -0
- package/dist/types/extensibility/custom-commands/types.d.ts +6 -3
- package/dist/types/extensibility/custom-tools/types.d.ts +8 -5
- package/dist/types/extensibility/extensions/runner.d.ts +5 -2
- package/dist/types/extensibility/extensions/types.d.ts +14 -10
- package/dist/types/extensibility/hooks/types.d.ts +7 -4
- package/dist/types/extensibility/legacy-pi-ai-shim.d.ts +13 -5
- package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +17 -0
- package/dist/types/extensibility/shared-events.d.ts +22 -1
- package/dist/types/extensibility/typebox.d.ts +80 -58
- package/dist/types/goals/tools/goal-tool.d.ts +11 -24
- package/dist/types/index.d.ts +2 -0
- package/dist/types/lsp/index.d.ts +11 -26
- package/dist/types/lsp/types.d.ts +12 -28
- package/dist/types/main.d.ts +1 -0
- package/dist/types/mcp/client.d.ts +8 -0
- package/dist/types/modes/components/btw-panel.d.ts +1 -0
- package/dist/types/modes/components/custom-editor.d.ts +3 -1
- package/dist/types/modes/components/status-line/component.d.ts +1 -1
- package/dist/types/modes/components/status-line/context-thresholds.d.ts +0 -1
- package/dist/types/modes/controllers/btw-controller.d.ts +2 -0
- package/dist/types/modes/controllers/input-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +3 -0
- package/dist/types/modes/rpc/rpc-types.d.ts +1 -1
- package/dist/types/modes/setup-wizard/index.d.ts +1 -0
- package/dist/types/modes/setup-wizard/startup-splash.d.ts +7 -0
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/modes/types.d.ts +3 -0
- package/dist/types/modes/utils/context-usage.d.ts +12 -0
- package/dist/types/sdk.d.ts +8 -1
- package/dist/types/session/agent-session.d.ts +24 -0
- package/dist/types/session/session-persistence.d.ts +4 -0
- package/dist/types/startup-splash.d.ts +12 -0
- package/dist/types/task/types.d.ts +47 -48
- package/dist/types/tools/ask.d.ts +26 -27
- package/dist/types/tools/ast-edit.d.ts +17 -17
- package/dist/types/tools/ast-grep.d.ts +12 -13
- package/dist/types/tools/bash.d.ts +20 -17
- package/dist/types/tools/browser.d.ts +46 -71
- package/dist/types/tools/checkpoint.d.ts +14 -15
- package/dist/types/tools/debug.d.ts +82 -145
- package/dist/types/tools/eval.d.ts +30 -40
- package/dist/types/tools/find.d.ts +17 -18
- package/dist/types/tools/gh.d.ts +49 -78
- package/dist/types/tools/image-gen.d.ts +20 -36
- package/dist/types/tools/inspect-image.d.ts +10 -11
- package/dist/types/tools/irc.d.ts +22 -33
- package/dist/types/tools/job.d.ts +11 -12
- package/dist/types/tools/learn.d.ts +21 -28
- package/dist/types/tools/manage-skill.d.ts +13 -22
- package/dist/types/tools/memory-edit.d.ts +15 -24
- package/dist/types/tools/memory-recall.d.ts +7 -8
- package/dist/types/tools/memory-reflect.d.ts +9 -10
- package/dist/types/tools/memory-retain.d.ts +13 -14
- package/dist/types/tools/read.d.ts +8 -8
- package/dist/types/tools/resolve.d.ts +11 -18
- package/dist/types/tools/review.d.ts +9 -15
- package/dist/types/tools/search-tool-bm25.d.ts +9 -10
- package/dist/types/tools/search.d.ts +16 -17
- package/dist/types/tools/ssh.d.ts +14 -15
- package/dist/types/tools/todo.d.ts +27 -43
- package/dist/types/tools/tts.d.ts +8 -9
- package/dist/types/tools/write.d.ts +9 -10
- package/dist/types/tui/code-cell.d.ts +2 -0
- package/dist/types/tui/index.d.ts +1 -0
- package/dist/types/tui/width-aware-text.d.ts +23 -0
- package/dist/types/utils/image-vision-fallback.d.ts +28 -0
- package/dist/types/utils/markit.d.ts +10 -1
- package/dist/types/web/search/index.d.ts +17 -28
- package/dist/types/web/search/providers/base.d.ts +1 -0
- package/dist/types/web/search/providers/gemini.d.ts +1 -0
- package/dist/types/web/search/providers/perplexity.d.ts +0 -2
- package/dist/types/web/search/types.d.ts +32 -26
- package/package.json +14 -13
- package/scripts/omp +1 -1
- package/src/advisor/__tests__/advisor.test.ts +103 -1
- package/src/advisor/advise-tool.ts +47 -11
- package/src/autoresearch/tools/init-experiment.ts +13 -16
- package/src/autoresearch/tools/log-experiment.ts +15 -18
- package/src/autoresearch/tools/run-experiment.ts +3 -3
- package/src/autoresearch/tools/update-notes.ts +4 -4
- package/src/cli/args.ts +1 -0
- package/src/cli/bench-cli.ts +30 -7
- package/src/cli/flag-tables.ts +8 -0
- package/src/cli/ttsr-cli.ts +995 -0
- package/src/cli-commands.ts +1 -0
- package/src/cli.ts +7 -1
- package/src/collab/host.ts +2 -2
- package/src/commands/launch.ts +3 -0
- package/src/commands/ttsr.ts +125 -0
- package/src/commit/agentic/tools/analyze-file.ts +4 -4
- package/src/commit/agentic/tools/git-file-diff.ts +4 -4
- package/src/commit/agentic/tools/git-hunk.ts +7 -5
- package/src/commit/agentic/tools/git-overview.ts +4 -4
- package/src/commit/agentic/tools/propose-changelog.ts +18 -15
- package/src/commit/agentic/tools/propose-commit.ts +6 -6
- package/src/commit/agentic/tools/recent-commits.ts +3 -3
- package/src/commit/agentic/tools/schemas.ts +8 -20
- package/src/commit/agentic/tools/split-commit.ts +19 -23
- package/src/commit/analysis/summary.ts +7 -5
- package/src/commit/changelog/generate.ts +15 -11
- package/src/commit/shared-llm.ts +17 -24
- package/src/config/config-file.ts +13 -15
- package/src/config/keybindings.ts +6 -0
- package/src/config/models-config-schema.ts +206 -179
- package/src/config/settings-schema.ts +118 -2
- package/src/discovery/builtin-rules/index.ts +2 -0
- package/src/discovery/builtin-rules/ts-import-type.md +2 -2
- package/src/discovery/builtin-rules/ts-no-any.md +11 -2
- package/src/discovery/builtin-rules/ts-no-inline-cast-access.md +55 -0
- package/src/edit/hashline/params.ts +12 -11
- package/src/edit/index.ts +5 -4
- package/src/edit/modes/apply-patch.ts +4 -4
- package/src/edit/modes/patch.ts +15 -18
- package/src/edit/modes/replace.ts +13 -17
- package/src/edit/renderer.ts +0 -1
- package/src/eval/agent-bridge.ts +11 -13
- package/src/eval/completion-bridge.ts +25 -17
- package/src/eval/js/context-manager.ts +17 -2
- package/src/eval/js/index.ts +1 -1
- package/src/eval/py/executor.ts +2 -2
- package/src/eval/py/runner.py +44 -0
- package/src/extensibility/custom-commands/loader.ts +5 -3
- package/src/extensibility/custom-commands/types.ts +6 -3
- package/src/extensibility/custom-tools/loader.ts +4 -2
- package/src/extensibility/custom-tools/types.ts +8 -5
- package/src/extensibility/extensions/loader.ts +4 -2
- package/src/extensibility/extensions/runner.ts +20 -2
- package/src/extensibility/extensions/types.ts +22 -8
- package/src/extensibility/hooks/loader.ts +5 -2
- package/src/extensibility/hooks/types.ts +7 -4
- package/src/extensibility/legacy-pi-ai-shim.ts +42 -5
- package/src/extensibility/legacy-pi-coding-agent-shim.ts +113 -0
- package/src/extensibility/plugins/legacy-pi-compat.ts +13 -13
- package/src/extensibility/shared-events.ts +24 -0
- package/src/extensibility/tool-proxy.ts +4 -1
- package/src/extensibility/typebox.ts +778 -251
- package/src/goals/guided-setup.ts +12 -3
- package/src/goals/tools/goal-tool.ts +6 -6
- package/src/index.ts +2 -0
- package/src/internal-urls/docs-index.generated.ts +15 -13
- package/src/lsp/types.ts +13 -27
- package/src/main.ts +29 -21
- package/src/mcp/client.ts +38 -13
- package/src/mcp/render.ts +102 -89
- package/src/modes/components/agent-hub.ts +11 -4
- package/src/modes/components/branch-summary-message.ts +1 -0
- package/src/modes/components/btw-panel.ts +5 -1
- package/src/modes/components/collab-prompt-message.ts +9 -7
- package/src/modes/components/compaction-summary-message.ts +1 -0
- package/src/modes/components/custom-editor.ts +18 -0
- package/src/modes/components/custom-message.ts +1 -0
- package/src/modes/components/footer.ts +6 -5
- package/src/modes/components/hook-message.ts +1 -0
- package/src/modes/components/read-tool-group.ts +9 -3
- package/src/modes/components/skill-message.ts +1 -0
- package/src/modes/components/status-line/component.ts +139 -15
- package/src/modes/components/status-line/context-thresholds.ts +0 -1
- package/src/modes/components/todo-reminder.ts +1 -0
- package/src/modes/components/tool-execution.ts +17 -10
- package/src/modes/components/ttsr-notification.ts +1 -0
- package/src/modes/components/user-message.ts +6 -6
- package/src/modes/controllers/btw-controller.ts +69 -1
- package/src/modes/controllers/event-controller.ts +2 -7
- package/src/modes/controllers/input-controller.ts +29 -0
- package/src/modes/controllers/selector-controller.ts +10 -3
- package/src/modes/interactive-mode.ts +42 -10
- package/src/modes/rpc/rpc-types.ts +1 -1
- package/src/modes/setup-wizard/index.ts +1 -0
- package/src/modes/setup-wizard/scenes/sign-in.ts +77 -5
- package/src/modes/setup-wizard/startup-splash.ts +107 -0
- package/src/modes/theme/theme.ts +133 -143
- package/src/modes/types.ts +3 -0
- package/src/modes/utils/context-usage.ts +37 -20
- package/src/modes/utils/hotkeys-markdown.ts +1 -0
- package/src/prompts/system/system-prompt.md +1 -0
- package/src/prompts/tools/image-attachment-describe-system.md +8 -0
- package/src/prompts/tools/image-attachment-describe.md +10 -0
- package/src/sdk.ts +35 -22
- package/src/session/agent-session.ts +715 -255
- package/src/session/session-history-format.ts +11 -2
- package/src/session/session-loader.ts +19 -32
- package/src/session/session-persistence.ts +27 -11
- package/src/session/snapcompact-inline.ts +1 -1
- package/src/slash-commands/builtin-registry.ts +4 -11
- package/src/ssh/connection-manager.ts +3 -2
- package/src/startup-splash.ts +19 -0
- package/src/task/executor.ts +12 -7
- package/src/task/types.ts +44 -41
- package/src/tool-discovery/tool-index.ts +17 -4
- package/src/tools/ask.ts +14 -14
- package/src/tools/ast-edit.ts +17 -14
- package/src/tools/ast-grep.ts +10 -9
- package/src/tools/bash.ts +15 -10
- package/src/tools/browser/launch.ts +13 -0
- package/src/tools/browser.ts +26 -32
- package/src/tools/checkpoint.ts +7 -7
- package/src/tools/debug.ts +72 -69
- package/src/tools/eval.ts +18 -19
- package/src/tools/find.ts +20 -13
- package/src/tools/gh.ts +29 -49
- package/src/tools/image-gen.ts +94 -57
- package/src/tools/inspect-image.ts +8 -9
- package/src/tools/irc.ts +12 -12
- package/src/tools/job.ts +6 -6
- package/src/tools/learn.ts +11 -14
- package/src/tools/manage-skill.ts +19 -23
- package/src/tools/memory-edit.ts +8 -8
- package/src/tools/memory-recall.ts +4 -4
- package/src/tools/memory-reflect.ts +5 -5
- package/src/tools/memory-retain.ts +9 -11
- package/src/tools/puppeteer/02_stealth_hairline.txt +1 -1
- package/src/tools/puppeteer/04_stealth_iframe.txt +4 -4
- package/src/tools/puppeteer/05_stealth_webgl.txt +1 -1
- package/src/tools/puppeteer/10_stealth_plugins.txt +6 -4
- package/src/tools/puppeteer/12_stealth_codecs.txt +2 -2
- package/src/tools/puppeteer/13_stealth_worker.txt +1 -1
- package/src/tools/read.ts +197 -19
- package/src/tools/report-tool-issue.ts +6 -6
- package/src/tools/resolve.ts +6 -6
- package/src/tools/review.ts +10 -12
- package/src/tools/search-tool-bm25.ts +5 -5
- package/src/tools/search.ts +20 -29
- package/src/tools/ssh.ts +8 -8
- package/src/tools/todo.ts +16 -19
- package/src/tools/tts.ts +16 -15
- package/src/tools/write.ts +5 -5
- package/src/tui/code-cell.ts +44 -3
- package/src/tui/index.ts +1 -0
- package/src/tui/width-aware-text.ts +58 -0
- package/src/utils/image-vision-fallback.ts +197 -0
- package/src/utils/markit.ts +17 -2
- package/src/web/search/index.ts +21 -9
- package/src/web/search/providers/base.ts +1 -0
- package/src/web/search/providers/gemini.ts +56 -18
- package/src/web/search/providers/perplexity.ts +373 -126
- package/src/web/search/types.ts +28 -48
package/src/tools/tts.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import type { AgentToolResult } from "@oh-my-pi/pi-agent-core";
|
|
7
7
|
import { type ApiKey, ProviderHttpError, withAuth } from "@oh-my-pi/pi-ai";
|
|
8
|
-
import {
|
|
8
|
+
import { type } from "arktype";
|
|
9
9
|
import { settings } from "../config/settings";
|
|
10
10
|
import type { CustomTool, CustomToolContext } from "../extensibility/custom-tools/types";
|
|
11
11
|
import { ohMyPiXAIUserAgent, resolveXAIHttpCredentials } from "../lib/xai-http";
|
|
@@ -16,7 +16,6 @@ import { formatPathRelativeToCwd, resolveToCwd } from "./path-utils";
|
|
|
16
16
|
|
|
17
17
|
// Hermes tts_tool.py L167-171
|
|
18
18
|
const DEFAULT_XAI_VOICE_ID = "eve" as const;
|
|
19
|
-
const DEFAULT_XAI_LANGUAGE = "en" as const;
|
|
20
19
|
const DEFAULT_XAI_SAMPLE_RATE = 24_000;
|
|
21
20
|
const DEFAULT_XAI_BIT_RATE = 128_000;
|
|
22
21
|
const XAI_MAX_TEXT_LENGTH = 15_000;
|
|
@@ -31,15 +30,17 @@ const formatVoiceList = (): string =>
|
|
|
31
30
|
type TtsCodec = "mp3" | "wav";
|
|
32
31
|
type TtsBackend = "local" | "xai";
|
|
33
32
|
|
|
34
|
-
const ttsSchema =
|
|
35
|
-
text:
|
|
36
|
-
voice_id:
|
|
37
|
-
language:
|
|
38
|
-
output_path:
|
|
39
|
-
sample_rate:
|
|
40
|
-
bit_rate:
|
|
33
|
+
const ttsSchema = type({
|
|
34
|
+
text: "1 <= string <= 15000",
|
|
35
|
+
voice_id: "string = 'eve'",
|
|
36
|
+
language: "string = 'en'",
|
|
37
|
+
output_path: "string",
|
|
38
|
+
sample_rate: "number.integer?",
|
|
39
|
+
bit_rate: "number.integer?",
|
|
41
40
|
});
|
|
42
41
|
|
|
42
|
+
type TtsSchemaType = typeof ttsSchema.infer;
|
|
43
|
+
|
|
43
44
|
interface TtsToolDetails {
|
|
44
45
|
bytes: number;
|
|
45
46
|
voiceId: string;
|
|
@@ -87,13 +88,13 @@ function readStringSetting(key: "providers.tts" | "tts.localModel" | "tts.localV
|
|
|
87
88
|
}
|
|
88
89
|
|
|
89
90
|
async function synthesizeXai(
|
|
90
|
-
params:
|
|
91
|
+
params: TtsSchemaType,
|
|
91
92
|
ctx: CustomToolContext,
|
|
92
93
|
outputPath: string,
|
|
93
94
|
displayPath: string,
|
|
94
95
|
codec: TtsCodec,
|
|
95
96
|
signal: AbortSignal | undefined,
|
|
96
|
-
): Promise<AgentToolResult<TtsToolDetails,
|
|
97
|
+
): Promise<AgentToolResult<TtsToolDetails, TtsSchemaType>> {
|
|
97
98
|
const creds = await resolveXAIHttpCredentials(ctx.modelRegistry);
|
|
98
99
|
if (!creds) {
|
|
99
100
|
return {
|
|
@@ -187,11 +188,11 @@ async function synthesizeXai(
|
|
|
187
188
|
}
|
|
188
189
|
|
|
189
190
|
async function synthesizeLocal(
|
|
190
|
-
params:
|
|
191
|
+
params: TtsSchemaType,
|
|
191
192
|
cwd: string,
|
|
192
193
|
outputPath: string,
|
|
193
194
|
signal: AbortSignal | undefined,
|
|
194
|
-
): Promise<AgentToolResult<TtsToolDetails,
|
|
195
|
+
): Promise<AgentToolResult<TtsToolDetails, TtsSchemaType>> {
|
|
195
196
|
const modelSetting = readStringSetting("tts.localModel");
|
|
196
197
|
const modelKey = modelSetting && isTtsLocalModelKey(modelSetting) ? modelSetting : DEFAULT_TTS_LOCAL_MODEL_KEY;
|
|
197
198
|
const voice = readStringSetting("tts.localVoice") || DEFAULT_TTS_VOICE;
|
|
@@ -242,11 +243,11 @@ export const ttsTool: CustomTool<typeof ttsSchema, TtsToolDetails> = {
|
|
|
242
243
|
parameters: ttsSchema,
|
|
243
244
|
async execute(
|
|
244
245
|
_toolCallId: string,
|
|
245
|
-
params:
|
|
246
|
+
params: TtsSchemaType,
|
|
246
247
|
_onUpdate,
|
|
247
248
|
ctx: CustomToolContext,
|
|
248
249
|
signal?: AbortSignal,
|
|
249
|
-
): Promise<AgentToolResult<TtsToolDetails,
|
|
250
|
+
): Promise<AgentToolResult<TtsToolDetails, TtsSchemaType>> {
|
|
250
251
|
const cwd = ctx.sessionManager.getCwd();
|
|
251
252
|
const outputPath = resolveToCwd(params.output_path, cwd);
|
|
252
253
|
const displayPath = formatPathRelativeToCwd(outputPath, cwd);
|
package/src/tools/write.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { formatHashlineHeader, stripHashlinePrefixes } from "@oh-my-pi/hashline"
|
|
|
6
6
|
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
7
7
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
8
8
|
import { isEnoent, isRecord, prompt, untilAborted } from "@oh-my-pi/pi-utils";
|
|
9
|
-
import {
|
|
9
|
+
import { type } from "arktype";
|
|
10
10
|
|
|
11
11
|
import { canonicalSnapshotKey, getFileSnapshotStore } from "../edit/file-snapshot-store";
|
|
12
12
|
import { normalizeToLF } from "../edit/normalize";
|
|
@@ -71,12 +71,12 @@ async function loadFflate(): Promise<typeof import("fflate")> {
|
|
|
71
71
|
return fflateModulePromise;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
const writeSchema =
|
|
75
|
-
path:
|
|
76
|
-
content:
|
|
74
|
+
const writeSchema = type({
|
|
75
|
+
path: type("string").describe("file path"),
|
|
76
|
+
content: type("string").describe("file content"),
|
|
77
77
|
});
|
|
78
78
|
|
|
79
|
-
export type WriteToolInput =
|
|
79
|
+
export type WriteToolInput = typeof writeSchema.infer;
|
|
80
80
|
|
|
81
81
|
/** Details returned by the write tool for TUI rendering */
|
|
82
82
|
export interface WriteToolDetails {
|
package/src/tui/code-cell.ts
CHANGED
|
@@ -33,6 +33,8 @@ export interface CodeCellOptions {
|
|
|
33
33
|
codeTail?: boolean;
|
|
34
34
|
expanded?: boolean;
|
|
35
35
|
width: number;
|
|
36
|
+
codeStartLine?: number;
|
|
37
|
+
codeLineNumbers?: Array<number | null>;
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
function getState(status?: CodeCellOptions["status"]): State | undefined {
|
|
@@ -99,7 +101,17 @@ function collapseCarriageReturns(line: string): string {
|
|
|
99
101
|
return idx < 0 ? line : line.slice(idx + 1);
|
|
100
102
|
}
|
|
101
103
|
export function renderCodeCell(options: CodeCellOptions, theme: Theme): string[] {
|
|
102
|
-
const {
|
|
104
|
+
const {
|
|
105
|
+
code,
|
|
106
|
+
language,
|
|
107
|
+
output,
|
|
108
|
+
expanded = false,
|
|
109
|
+
outputMaxLines = 6,
|
|
110
|
+
codeMaxLines = 12,
|
|
111
|
+
width,
|
|
112
|
+
codeStartLine,
|
|
113
|
+
codeLineNumbers,
|
|
114
|
+
} = options;
|
|
103
115
|
const { title, meta } = formatHeader(options, theme);
|
|
104
116
|
const state = getState(options.status);
|
|
105
117
|
|
|
@@ -111,16 +123,45 @@ export function renderCodeCell(options: CodeCellOptions, theme: Theme): string[]
|
|
|
111
123
|
const startIndex = tail ? rawCodeLines.length - maxCodeLines : 0;
|
|
112
124
|
const visibleCode = rawCodeLines.slice(startIndex, startIndex + maxCodeLines).join("\n");
|
|
113
125
|
const codeLines = highlightCode(visibleCode, language);
|
|
126
|
+
|
|
127
|
+
let visibleLineNumbers: Array<number | null> | undefined;
|
|
128
|
+
let lineNumberWidth = 0;
|
|
129
|
+
if (codeLineNumbers) {
|
|
130
|
+
visibleLineNumbers = codeLineNumbers.slice(startIndex, startIndex + maxCodeLines);
|
|
131
|
+
} else if (codeStartLine !== undefined) {
|
|
132
|
+
visibleLineNumbers = Array.from({ length: maxCodeLines }, (_, i) => codeStartLine + startIndex + i);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (visibleLineNumbers) {
|
|
136
|
+
const validLineNums = visibleLineNumbers.filter((n): n is number => n !== null && n !== undefined);
|
|
137
|
+
const maxVal = validLineNums.length > 0 ? Math.max(...validLineNums) : 0;
|
|
138
|
+
if (maxVal > 0) {
|
|
139
|
+
lineNumberWidth = Math.max(2, String(maxVal).length);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (lineNumberWidth > 0 && visibleLineNumbers) {
|
|
144
|
+
for (let i = 0; i < codeLines.length; i++) {
|
|
145
|
+
const lineNum = visibleLineNumbers[i];
|
|
146
|
+
const gutter =
|
|
147
|
+
lineNum !== null && lineNum !== undefined
|
|
148
|
+
? String(lineNum).padStart(lineNumberWidth, " ")
|
|
149
|
+
: " ".repeat(lineNumberWidth);
|
|
150
|
+
codeLines[i] = theme.fg("dim", `${gutter} `) + codeLines[i];
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
114
154
|
if (hiddenCodeLines > 0) {
|
|
115
155
|
const hint = formatExpandHint(theme, expanded, hiddenCodeLines > 0);
|
|
156
|
+
const gutterPad = lineNumberWidth > 0 ? " ".repeat(lineNumberWidth + 1) : "";
|
|
116
157
|
if (tail) {
|
|
117
158
|
// Earlier rows scrolled above the live tail window — mark them on top so
|
|
118
159
|
// the newest streamed line stays pinned to the bottom of the box.
|
|
119
160
|
const earlier = `… ${hiddenCodeLines} earlier line${hiddenCodeLines === 1 ? "" : "s"}${hint ? ` ${hint}` : ""}`;
|
|
120
|
-
codeLines.unshift(theme.fg("dim", earlier));
|
|
161
|
+
codeLines.unshift(theme.fg("dim", gutterPad + earlier));
|
|
121
162
|
} else {
|
|
122
163
|
const moreLine = `${formatMoreItems(hiddenCodeLines, "line")}${hint ? ` ${hint}` : ""}`;
|
|
123
|
-
codeLines.push(theme.fg("dim", moreLine));
|
|
164
|
+
codeLines.push(theme.fg("dim", gutterPad + moreLine));
|
|
124
165
|
}
|
|
125
166
|
}
|
|
126
167
|
|
package/src/tui/index.ts
CHANGED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { type Component, getPaddingX, Text } from "@oh-my-pi/pi-tui";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Text whose content is (re)formatted against the actual render width.
|
|
5
|
+
*
|
|
6
|
+
* A plain `Text` receives an already-formatted string and only wraps it at
|
|
7
|
+
* render time, so width-dependent layout (per-line truncation, inline previews)
|
|
8
|
+
* has to be decided before the width is known. Renderers used to cope by
|
|
9
|
+
* hard-capping output lines at a fixed column count (e.g. 80), which truncated
|
|
10
|
+
* to roughly a third of a wide terminal. This defers formatting to
|
|
11
|
+
* `render(width)`: it computes the same content width the inner `Text` uses
|
|
12
|
+
* (mirroring its tight-layout flag so the budget can't desync), hands that to
|
|
13
|
+
* the formatter, and delegates margins/background/vertical padding to the inner
|
|
14
|
+
* `Text`. Lines the formatter caps at `contentWidth` fit exactly and so never
|
|
15
|
+
* wrap.
|
|
16
|
+
*/
|
|
17
|
+
export class WidthAwareText implements Component {
|
|
18
|
+
#format: (contentWidth: number) => string;
|
|
19
|
+
readonly #paddingX: number;
|
|
20
|
+
#inner: Text;
|
|
21
|
+
#cachedContentWidth = -1;
|
|
22
|
+
#cachedText: string | undefined;
|
|
23
|
+
#ignoreTight = false;
|
|
24
|
+
|
|
25
|
+
constructor(format: (contentWidth: number) => string, paddingX = 1, paddingY = 1) {
|
|
26
|
+
this.#format = format;
|
|
27
|
+
this.#paddingX = paddingX;
|
|
28
|
+
this.#inner = new Text("", paddingX, paddingY);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
setCustomBgFn(customBgFn?: (text: string) => string): void {
|
|
32
|
+
this.#inner.setCustomBgFn(customBgFn);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
setIgnoreTight(ignore: boolean): this {
|
|
36
|
+
this.#ignoreTight = ignore;
|
|
37
|
+
this.#inner.setIgnoreTight(ignore);
|
|
38
|
+
this.invalidate();
|
|
39
|
+
return this;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
invalidate(): void {
|
|
43
|
+
this.#cachedContentWidth = -1;
|
|
44
|
+
this.#cachedText = undefined;
|
|
45
|
+
this.#inner.invalidate();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
render(width: number): readonly string[] {
|
|
49
|
+
const paddingX = this.#ignoreTight ? this.#paddingX : getPaddingX(this.#paddingX);
|
|
50
|
+
const contentWidth = Math.max(1, width - paddingX * 2);
|
|
51
|
+
if (this.#cachedText === undefined || contentWidth !== this.#cachedContentWidth) {
|
|
52
|
+
this.#cachedContentWidth = contentWidth;
|
|
53
|
+
this.#cachedText = this.#format(contentWidth);
|
|
54
|
+
this.#inner.setText(this.#cachedText);
|
|
55
|
+
}
|
|
56
|
+
return this.#inner.render(width);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vision fallback for text-only models. When a user attaches an image to a model
|
|
3
|
+
* that cannot accept image input, this:
|
|
4
|
+
* 1. saves each image under the session `local://` root (for later analysis), and
|
|
5
|
+
* 2. asks a vision-capable model to describe it and injects that description as
|
|
6
|
+
* a text block in place of the image:
|
|
7
|
+
*
|
|
8
|
+
* <image path="local://image-<hash>.png">
|
|
9
|
+
* <description>
|
|
10
|
+
* </image>
|
|
11
|
+
*
|
|
12
|
+
* Without this the provider layer drops the image entirely (NON_VISION_IMAGE_PLACEHOLDER).
|
|
13
|
+
*/
|
|
14
|
+
import * as path from "node:path";
|
|
15
|
+
import {
|
|
16
|
+
type AgentTelemetry,
|
|
17
|
+
type AgentTelemetryConfig,
|
|
18
|
+
instrumentedCompleteSimple,
|
|
19
|
+
resolveTelemetry,
|
|
20
|
+
} from "@oh-my-pi/pi-agent-core";
|
|
21
|
+
import type { Api, completeSimple, ImageContent, Model, TextContent } from "@oh-my-pi/pi-ai";
|
|
22
|
+
import { logger, prompt, toError } from "@oh-my-pi/pi-utils";
|
|
23
|
+
import { extractTextContent } from "../commit/utils";
|
|
24
|
+
import type { ModelRegistry } from "../config/model-registry";
|
|
25
|
+
import { expandRoleAlias, getModelMatchPreferences, resolveModelFromString } from "../config/model-resolver";
|
|
26
|
+
import type { Settings } from "../config/settings";
|
|
27
|
+
import { type LocalProtocolOptions, resolveLocalRoot } from "../internal-urls";
|
|
28
|
+
import describeUserPrompt from "../prompts/tools/image-attachment-describe.md" with { type: "text" };
|
|
29
|
+
import describeSystemPrompt from "../prompts/tools/image-attachment-describe-system.md" with { type: "text" };
|
|
30
|
+
|
|
31
|
+
/** Telemetry tag for the oneshot vision-description calls. */
|
|
32
|
+
const ONESHOT_KIND = "image_attachment_describe";
|
|
33
|
+
|
|
34
|
+
const NO_VISION_MODEL_NOTE =
|
|
35
|
+
"[No vision-capable model is configured, so this image could not be described automatically. " +
|
|
36
|
+
"The image was saved; configure a vision model role (modelRoles.vision) and use the inspect_image tool to analyze it.]";
|
|
37
|
+
|
|
38
|
+
const DESCRIPTION_UNAVAILABLE_NOTE =
|
|
39
|
+
"[Image description unavailable: the vision model returned no usable text. The image was saved for further analysis.]";
|
|
40
|
+
|
|
41
|
+
/** Registry surface needed to resolve a vision model and authorize requests. */
|
|
42
|
+
export type VisionFallbackRegistry = Pick<ModelRegistry, "getAvailable" | "getApiKey" | "resolver"> &
|
|
43
|
+
Partial<Pick<ModelRegistry, "resolveCanonicalModel" | "getCanonicalVariants" | "getCanonicalId">>;
|
|
44
|
+
|
|
45
|
+
export interface DescribeAttachedImagesDeps {
|
|
46
|
+
/** Active (text-only) model the prompt is destined for. */
|
|
47
|
+
activeModel: Model<Api>;
|
|
48
|
+
modelRegistry: VisionFallbackRegistry;
|
|
49
|
+
settings: Settings;
|
|
50
|
+
/** Inputs for resolving the session-scoped `local://` root. */
|
|
51
|
+
localProtocolOptions: LocalProtocolOptions;
|
|
52
|
+
/** `provider/id` of the active model; a last-resort vision-model candidate (filtered to image-capable). */
|
|
53
|
+
activeModelString?: string;
|
|
54
|
+
telemetryConfig?: AgentTelemetryConfig;
|
|
55
|
+
sessionId?: string;
|
|
56
|
+
/** Test seam: overrides the underlying completeSimple call. */
|
|
57
|
+
completeImpl?: typeof completeSimple;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Map an image MIME type to a file extension for the saved artifact. */
|
|
61
|
+
function extensionForMime(mimeType: string): string {
|
|
62
|
+
const subtype = mimeType.split("/")[1]?.toLowerCase() ?? "";
|
|
63
|
+
switch (subtype) {
|
|
64
|
+
case "jpeg":
|
|
65
|
+
case "jpg":
|
|
66
|
+
return "jpg";
|
|
67
|
+
case "png":
|
|
68
|
+
return "png";
|
|
69
|
+
case "gif":
|
|
70
|
+
return "gif";
|
|
71
|
+
case "webp":
|
|
72
|
+
return "webp";
|
|
73
|
+
default: {
|
|
74
|
+
const sanitized = subtype.replace(/[^a-z0-9]/g, "");
|
|
75
|
+
return sanitized || "png";
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Content-addressed file name so re-pasting the same image reuses one artifact. */
|
|
81
|
+
function imageFileName(image: ImageContent): string {
|
|
82
|
+
const hash = Bun.hash(image.data).toString(16);
|
|
83
|
+
return `image-${hash}.${extensionForMime(image.mimeType)}`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Persist an image under the local root; returns its `local://` URL. */
|
|
87
|
+
async function saveImage(image: ImageContent, localRoot: string): Promise<string> {
|
|
88
|
+
const fileName = imageFileName(image);
|
|
89
|
+
const filePath = path.join(localRoot, fileName);
|
|
90
|
+
// Content-addressed: identical bytes overwrite themselves harmlessly. Bun.write creates parent dirs.
|
|
91
|
+
await Bun.write(filePath, Buffer.from(image.data, "base64"));
|
|
92
|
+
return `local://${fileName}`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function formatImageBlock(localUrl: string, description: string): string {
|
|
96
|
+
return `<image path="${localUrl}">\n${description}\n</image>`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Resolve a vision-capable model, mirroring the inspect_image priority
|
|
101
|
+
* (`pi/vision` → `pi/default` → active → first image-capable available), but
|
|
102
|
+
* never returning a text-only model.
|
|
103
|
+
*/
|
|
104
|
+
function resolveVisionModel(deps: DescribeAttachedImagesDeps): Model<Api> | undefined {
|
|
105
|
+
const available = deps.modelRegistry.getAvailable();
|
|
106
|
+
if (available.length === 0) return undefined;
|
|
107
|
+
const preferences = getModelMatchPreferences(deps.settings);
|
|
108
|
+
const resolvePattern = (pattern: string | undefined): Model<Api> | undefined => {
|
|
109
|
+
if (!pattern) return undefined;
|
|
110
|
+
const expanded = expandRoleAlias(pattern, deps.settings);
|
|
111
|
+
const model = resolveModelFromString(expanded, available, preferences, deps.modelRegistry);
|
|
112
|
+
return model?.input.includes("image") ? model : undefined;
|
|
113
|
+
};
|
|
114
|
+
return (
|
|
115
|
+
resolvePattern("pi/vision") ??
|
|
116
|
+
resolvePattern("pi/default") ??
|
|
117
|
+
resolvePattern(deps.activeModelString) ??
|
|
118
|
+
available.find(model => model.input.includes("image"))
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Run one vision-description round-trip; returns trimmed text or `null` on any failure. */
|
|
123
|
+
async function describeImage(
|
|
124
|
+
image: ImageContent,
|
|
125
|
+
visionModel: Model<Api>,
|
|
126
|
+
deps: DescribeAttachedImagesDeps,
|
|
127
|
+
telemetry: AgentTelemetry | undefined,
|
|
128
|
+
signal: AbortSignal | undefined,
|
|
129
|
+
): Promise<string | null> {
|
|
130
|
+
try {
|
|
131
|
+
const response = await instrumentedCompleteSimple(
|
|
132
|
+
visionModel,
|
|
133
|
+
{
|
|
134
|
+
systemPrompt: [prompt.render(describeSystemPrompt)],
|
|
135
|
+
messages: [
|
|
136
|
+
{
|
|
137
|
+
role: "user",
|
|
138
|
+
content: [
|
|
139
|
+
{ type: "image", data: image.data, mimeType: image.mimeType },
|
|
140
|
+
{ type: "text", text: prompt.render(describeUserPrompt) },
|
|
141
|
+
],
|
|
142
|
+
timestamp: Date.now(),
|
|
143
|
+
},
|
|
144
|
+
],
|
|
145
|
+
},
|
|
146
|
+
{ apiKey: deps.modelRegistry.resolver(visionModel, deps.sessionId), signal },
|
|
147
|
+
{ telemetry, oneshotKind: ONESHOT_KIND, completeImpl: deps.completeImpl },
|
|
148
|
+
);
|
|
149
|
+
if (response.stopReason === "error" || response.stopReason === "aborted") {
|
|
150
|
+
logger.warn("image attachment description did not complete", {
|
|
151
|
+
stopReason: response.stopReason,
|
|
152
|
+
model: `${visionModel.provider}/${visionModel.id}`,
|
|
153
|
+
});
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
const text = extractTextContent(response).trim();
|
|
157
|
+
return text.length > 0 ? text : null;
|
|
158
|
+
} catch (err) {
|
|
159
|
+
logger.warn("image attachment description failed", {
|
|
160
|
+
error: toError(err).message,
|
|
161
|
+
model: `${visionModel.provider}/${visionModel.id}`,
|
|
162
|
+
});
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Save each attached image under `local://` and replace it with a descriptive
|
|
169
|
+
* text block. Returns one {@link TextContent} per input image, in order. Never
|
|
170
|
+
* throws for an individual image: a failed description falls back to a note while
|
|
171
|
+
* the saved-path block is still emitted.
|
|
172
|
+
*/
|
|
173
|
+
export async function describeAttachedImagesForTextModel(
|
|
174
|
+
images: readonly ImageContent[],
|
|
175
|
+
deps: DescribeAttachedImagesDeps,
|
|
176
|
+
signal?: AbortSignal,
|
|
177
|
+
): Promise<TextContent[]> {
|
|
178
|
+
const localRoot = resolveLocalRoot(deps.localProtocolOptions);
|
|
179
|
+
const visionModel = resolveVisionModel(deps);
|
|
180
|
+
const apiKey = visionModel ? await deps.modelRegistry.getApiKey(visionModel, deps.sessionId) : undefined;
|
|
181
|
+
const canDescribe = Boolean(visionModel && apiKey);
|
|
182
|
+
const telemetry = resolveTelemetry(deps.telemetryConfig, deps.sessionId);
|
|
183
|
+
|
|
184
|
+
return Promise.all(
|
|
185
|
+
images.map(async (image): Promise<TextContent> => {
|
|
186
|
+
const localUrl = await saveImage(image, localRoot);
|
|
187
|
+
let description: string;
|
|
188
|
+
if (canDescribe && visionModel) {
|
|
189
|
+
description =
|
|
190
|
+
(await describeImage(image, visionModel, deps, telemetry, signal)) ?? DESCRIPTION_UNAVAILABLE_NOTE;
|
|
191
|
+
} else {
|
|
192
|
+
description = NO_VISION_MODEL_NOTE;
|
|
193
|
+
}
|
|
194
|
+
return { type: "text", text: formatImageBlock(localUrl, description) };
|
|
195
|
+
}),
|
|
196
|
+
);
|
|
197
|
+
}
|
package/src/utils/markit.ts
CHANGED
|
@@ -8,6 +8,16 @@ export interface MarkitConversionResult {
|
|
|
8
8
|
error?: string;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
export interface MarkitFileConversionOptions {
|
|
12
|
+
/**
|
|
13
|
+
* Directory the PDF converter writes extracted images/diagrams into. When
|
|
14
|
+
* set, each embedded image is rendered to `<id>.png` and referenced by path
|
|
15
|
+
* in the markdown; when unset, markit emits an `<!-- image: <id> ... -->`
|
|
16
|
+
* placeholder comment instead.
|
|
17
|
+
*/
|
|
18
|
+
imageDir?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
11
21
|
interface MuPdfWasmModuleConfig {
|
|
12
22
|
print?: (...values: unknown[]) => void;
|
|
13
23
|
printErr?: (...values: unknown[]) => void;
|
|
@@ -77,9 +87,14 @@ function finalizeConversion(markdown?: string): MarkitConversionResult {
|
|
|
77
87
|
return { content: "", ok: false, error: "Conversion produced no output" };
|
|
78
88
|
}
|
|
79
89
|
|
|
80
|
-
export async function convertFileWithMarkit(
|
|
90
|
+
export async function convertFileWithMarkit(
|
|
91
|
+
filePath: string,
|
|
92
|
+
signal?: AbortSignal,
|
|
93
|
+
options?: MarkitFileConversionOptions,
|
|
94
|
+
): Promise<MarkitConversionResult> {
|
|
95
|
+
const extra = options?.imageDir ? { imageDir: options.imageDir } : undefined;
|
|
81
96
|
try {
|
|
82
|
-
const result = await runMarkitConversion(markit => markit.convertFile(filePath), signal);
|
|
97
|
+
const result = await runMarkitConversion(markit => markit.convertFile(filePath, extra), signal);
|
|
83
98
|
return finalizeConversion(result.markdown);
|
|
84
99
|
} catch (error) {
|
|
85
100
|
if (error instanceof ToolAbortError) {
|
package/src/web/search/index.ts
CHANGED
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
8
8
|
import type { AuthStorage } from "@oh-my-pi/pi-ai";
|
|
9
9
|
import { prompt } from "@oh-my-pi/pi-utils";
|
|
10
|
-
import {
|
|
10
|
+
import { type } from "arktype";
|
|
11
|
+
import { settings } from "../../config/settings";
|
|
11
12
|
import type { CustomTool, CustomToolContext, RenderResultOptions } from "../../extensibility/custom-tools/types";
|
|
12
13
|
import type { Theme } from "../../modes/theme/theme";
|
|
13
14
|
import webSearchSystemPrompt from "../../prompts/system/web-search.md" with { type: "text" };
|
|
@@ -22,16 +23,16 @@ import type { SearchProviderId, SearchResponse } from "./types";
|
|
|
22
23
|
import { SearchProviderError } from "./types";
|
|
23
24
|
|
|
24
25
|
/** Web search tool parameters schema */
|
|
25
|
-
export const webSearchSchema =
|
|
26
|
-
query:
|
|
27
|
-
recency:
|
|
28
|
-
limit:
|
|
29
|
-
max_tokens:
|
|
30
|
-
temperature:
|
|
31
|
-
num_search_results:
|
|
26
|
+
export const webSearchSchema = type({
|
|
27
|
+
query: "string",
|
|
28
|
+
recency: "'day' | 'week' | 'month' | 'year'?",
|
|
29
|
+
limit: "number?",
|
|
30
|
+
max_tokens: "number?",
|
|
31
|
+
temperature: "number?",
|
|
32
|
+
num_search_results: "number?",
|
|
32
33
|
});
|
|
33
34
|
|
|
34
|
-
export type SearchToolParams =
|
|
35
|
+
export type SearchToolParams = typeof webSearchSchema.infer;
|
|
35
36
|
|
|
36
37
|
export interface SearchQueryParams extends SearchToolParams {
|
|
37
38
|
provider?: SearchProviderId | "auto";
|
|
@@ -153,6 +154,16 @@ async function executeSearch(
|
|
|
153
154
|
};
|
|
154
155
|
}
|
|
155
156
|
|
|
157
|
+
// Invariant across providers; read once and tolerate an uninitialized
|
|
158
|
+
// Settings singleton (e.g. `omp q ...` CLI path, unit tests) so the
|
|
159
|
+
// provider-fallback loop never aborts before any provider runs.
|
|
160
|
+
let antigravityEndpointMode: "auto" | "production" | "sandbox" | undefined;
|
|
161
|
+
try {
|
|
162
|
+
antigravityEndpointMode = settings.get("providers.antigravityEndpoint");
|
|
163
|
+
} catch {
|
|
164
|
+
antigravityEndpointMode = undefined;
|
|
165
|
+
}
|
|
166
|
+
|
|
156
167
|
const failures: Array<{ provider: SearchProvider; error: unknown }> = [];
|
|
157
168
|
let lastProvider = providers[0];
|
|
158
169
|
for (const provider of providers) {
|
|
@@ -169,6 +180,7 @@ async function executeSearch(
|
|
|
169
180
|
signal,
|
|
170
181
|
authStorage,
|
|
171
182
|
sessionId,
|
|
183
|
+
antigravityEndpointMode,
|
|
172
184
|
});
|
|
173
185
|
|
|
174
186
|
if (!hasRenderableSearchContent(response)) {
|
|
@@ -52,6 +52,7 @@ export interface GeminiSearchParams extends GeminiToolParams {
|
|
|
52
52
|
authStorage: AuthStorage;
|
|
53
53
|
sessionId?: string;
|
|
54
54
|
fetch?: FetchImpl;
|
|
55
|
+
antigravityEndpointMode?: "auto" | "production" | "sandbox";
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
export function buildGeminiRequestTools(params: GeminiToolParams): Array<Record<string, Record<string, unknown>>> {
|
|
@@ -163,6 +164,7 @@ async function callGeminiSearch(
|
|
|
163
164
|
toolParams: GeminiToolParams,
|
|
164
165
|
fetchImpl: FetchImpl | undefined,
|
|
165
166
|
signal: AbortSignal | undefined,
|
|
167
|
+
mode?: "auto" | "production" | "sandbox",
|
|
166
168
|
): Promise<{
|
|
167
169
|
answer: string;
|
|
168
170
|
sources: SearchSource[];
|
|
@@ -171,7 +173,19 @@ async function callGeminiSearch(
|
|
|
171
173
|
model: string;
|
|
172
174
|
usage?: { inputTokens: number; outputTokens: number; totalTokens: number };
|
|
173
175
|
}> {
|
|
174
|
-
|
|
176
|
+
let endpoints: string[];
|
|
177
|
+
if (auth.isAntigravity) {
|
|
178
|
+
const m = mode ?? "auto";
|
|
179
|
+
if (m === "sandbox") {
|
|
180
|
+
endpoints = [ANTIGRAVITY_SANDBOX_ENDPOINT];
|
|
181
|
+
} else if (m === "production") {
|
|
182
|
+
endpoints = [ANTIGRAVITY_DAILY_ENDPOINT];
|
|
183
|
+
} else {
|
|
184
|
+
endpoints = [...ANTIGRAVITY_ENDPOINT_FALLBACKS];
|
|
185
|
+
}
|
|
186
|
+
} else {
|
|
187
|
+
endpoints = [DEFAULT_ENDPOINT];
|
|
188
|
+
}
|
|
175
189
|
const headers = auth.isAntigravity ? { "User-Agent": getAntigravityUserAgent() } : getGeminiCliHeaders();
|
|
176
190
|
|
|
177
191
|
const requestMetadata = auth.isAntigravity
|
|
@@ -187,12 +201,7 @@ async function callGeminiSearch(
|
|
|
187
201
|
|
|
188
202
|
const normalizedSystemPrompt = systemPrompt?.toWellFormed();
|
|
189
203
|
const systemInstructionParts: Array<{ text: string }> = [
|
|
190
|
-
...(auth.isAntigravity
|
|
191
|
-
? [
|
|
192
|
-
{ text: ANTIGRAVITY_SYSTEM_INSTRUCTION },
|
|
193
|
-
{ text: `Please ignore following [ignore]${ANTIGRAVITY_SYSTEM_INSTRUCTION}[/ignore]` },
|
|
194
|
-
]
|
|
195
|
-
: []),
|
|
204
|
+
...(auth.isAntigravity ? [{ text: ANTIGRAVITY_SYSTEM_INSTRUCTION }] : []),
|
|
196
205
|
...(normalizedSystemPrompt ? [{ text: normalizedSystemPrompt }] : []),
|
|
197
206
|
];
|
|
198
207
|
|
|
@@ -238,16 +247,45 @@ async function callGeminiSearch(
|
|
|
238
247
|
body: JSON.stringify(requestBody),
|
|
239
248
|
signal: withHardTimeout(signal),
|
|
240
249
|
});
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
250
|
+
|
|
251
|
+
let response: Response | undefined;
|
|
252
|
+
|
|
253
|
+
for (let i = 0; i < endpoints.length; i++) {
|
|
254
|
+
const endpoint = endpoints[i];
|
|
255
|
+
const isLastEndpoint = i === endpoints.length - 1;
|
|
256
|
+
try {
|
|
257
|
+
response = await fetchWithRetry(() => `${endpoint}/v1internal:streamGenerateContent?alt=sse`, {
|
|
258
|
+
...buildInit(),
|
|
259
|
+
fetch: fetchImpl,
|
|
260
|
+
maxAttempts: isLastEndpoint ? MAX_RETRIES + 1 : 1,
|
|
261
|
+
defaultDelayMs: attempt => BASE_DELAY_MS * 2 ** attempt,
|
|
262
|
+
maxDelayMs: RATE_LIMIT_BUDGET_MS,
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
if (response.ok) {
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (response.status === 429 || (response.status >= 500 && response.status < 600)) {
|
|
270
|
+
if (!isLastEndpoint) {
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
break;
|
|
275
|
+
} catch (error) {
|
|
276
|
+
if (isLastEndpoint) {
|
|
277
|
+
throw error;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
if (!response?.ok) {
|
|
283
|
+
const errorText = response ? await response.text() : "Network error";
|
|
284
|
+
const status = response?.status ?? 502;
|
|
285
|
+
const classified = classifyProviderHttpError("gemini", status, errorText);
|
|
286
|
+
if (classified) throw classified;
|
|
287
|
+
throw new SearchProviderError("gemini", `Gemini Cloud Code API error (${status}): ${errorText}`, status);
|
|
288
|
+
}
|
|
251
289
|
|
|
252
290
|
if (!response.ok) {
|
|
253
291
|
const errorText = await response.text();
|
|
@@ -410,7 +448,6 @@ export async function searchGemini(params: GeminiSearchParams): Promise<SearchRe
|
|
|
410
448
|
// re-resolved access may omit projectId, in which case the seed's
|
|
411
449
|
// project is still the right tenant for the credential. The
|
|
412
450
|
// `fetchWithRetry` transport backoff stays INSIDE this attempt — auth
|
|
413
|
-
// retry wraps transport retry.
|
|
414
451
|
callGeminiSearch(
|
|
415
452
|
{
|
|
416
453
|
accessToken: access.accessToken,
|
|
@@ -428,6 +465,7 @@ export async function searchGemini(params: GeminiSearchParams): Promise<SearchRe
|
|
|
428
465
|
},
|
|
429
466
|
params.fetch,
|
|
430
467
|
params.signal,
|
|
468
|
+
params.antigravityEndpointMode,
|
|
431
469
|
),
|
|
432
470
|
{ sessionId: params.sessionId, signal: params.signal, seed: seed.access },
|
|
433
471
|
);
|