@oh-my-pi/pi-coding-agent 15.10.0 → 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 +75 -1
- package/dist/types/cli/dry-balance-cli.d.ts +15 -1
- 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/model-registry.d.ts +17 -1
- package/dist/types/config/settings-schema.d.ts +9 -0
- package/dist/types/dap/config.d.ts +14 -1
- package/dist/types/dap/types.d.ts +10 -0
- package/dist/types/lsp/utils.d.ts +3 -2
- package/dist/types/modes/components/chat-block.d.ts +64 -0
- package/dist/types/modes/components/custom-editor.d.ts +3 -0
- 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/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/streaming-reveal.d.ts +22 -0
- package/dist/types/modes/controllers/tan-command-controller.d.ts +6 -0
- package/dist/types/modes/interactive-mode.d.ts +15 -5
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/modes/types.d.ts +18 -5
- 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 +2 -0
- 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 +7 -0
- package/dist/types/task/index.d.ts +1 -0
- package/dist/types/task/render.d.ts +3 -2
- 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/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 +5 -9
- package/dist/types/tools/search.d.ts +4 -0
- 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 +3 -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/search/providers/kimi.d.ts +1 -1
- package/package.json +9 -9
- package/src/auto-thinking/classifier.ts +5 -1
- package/src/cli/dry-balance-cli.ts +52 -17
- package/src/cli/gallery-cli.ts +4 -1
- package/src/cli/gallery-fixtures/misc.ts +29 -0
- 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/model-registry.ts +25 -2
- package/src/config/settings-schema.ts +10 -0
- package/src/config/settings.ts +20 -2
- 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 +82 -78
- package/src/eval/__tests__/llm-bridge.test.ts +90 -31
- package/src/eval/llm-bridge.ts +8 -3
- package/src/goals/tools/goal-tool.ts +36 -26
- package/src/internal-urls/docs-index.generated.ts +6 -6
- package/src/lsp/utils.ts +3 -2
- package/src/main.ts +9 -7
- package/src/memories/index.ts +12 -5
- 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 +23 -0
- 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/tips.txt +1 -0
- package/src/modes/components/todo-reminder.ts +0 -2
- package/src/modes/components/tool-execution.ts +68 -88
- 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 +57 -55
- 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 +124 -119
- package/src/modes/controllers/mcp-command-controller.ts +69 -60
- package/src/modes/controllers/selector-controller.ts +23 -25
- package/src/modes/controllers/streaming-reveal.ts +212 -0
- package/src/modes/controllers/tan-command-controller.ts +173 -0
- package/src/modes/interactive-mode.ts +169 -94
- 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 +18 -7
- package/src/modes/utils/copy-targets.ts +133 -27
- 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 +11 -37
- package/src/session/agent-session.ts +82 -6
- package/src/session/messages.ts +26 -0
- package/src/session/session-manager.ts +13 -5
- package/src/slash-commands/builtin-registry.ts +36 -9
- package/src/slash-commands/types.ts +4 -6
- package/src/task/executor.ts +5 -2
- package/src/task/index.ts +4 -0
- package/src/task/render.ts +212 -147
- 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/fetch.ts +297 -7
- 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 +21 -37
- package/src/tools/resolve.ts +14 -0
- package/src/tools/search-tool-bm25.ts +36 -23
- package/src/tools/search.ts +80 -78
- package/src/tools/sqlite-reader.ts +9 -12
- package/src/tools/todo.ts +118 -52
- package/src/tools/write.ts +81 -62
- 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/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/synthetic.ts +8 -6
- package/src/web/search/providers/tavily.ts +9 -8
- package/src/web/search/providers/zai.ts +8 -6
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);
|
|
@@ -2,8 +2,8 @@ import type { Component } from "@oh-my-pi/pi-tui";
|
|
|
2
2
|
import { Text } from "@oh-my-pi/pi-tui";
|
|
3
3
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
4
4
|
import type { Theme } from "../modes/theme/theme";
|
|
5
|
-
import { renderStatusLine } from "../tui";
|
|
6
|
-
import { formatExpandHint, replaceTabs, shortenPath, truncateToWidth } from "./render-utils";
|
|
5
|
+
import { framedBlock, renderStatusLine } from "../tui";
|
|
6
|
+
import { formatErrorDetail, formatExpandHint, replaceTabs, shortenPath, truncateToWidth } from "./render-utils";
|
|
7
7
|
|
|
8
8
|
interface InspectImageRenderArgs {
|
|
9
9
|
path?: string;
|
|
@@ -27,17 +27,21 @@ const INSPECT_OUTPUT_COLLAPSED_LINES = 4;
|
|
|
27
27
|
const INSPECT_OUTPUT_EXPANDED_LINES = 16;
|
|
28
28
|
const INSPECT_OUTPUT_LINE_WIDTH = 120;
|
|
29
29
|
|
|
30
|
+
function questionLine(question: string, uiTheme: Theme): string {
|
|
31
|
+
return `${uiTheme.fg("dim", "Question:")} ${uiTheme.fg("accent", truncateToWidth(replaceTabs(question), INSPECT_QUESTION_PREVIEW_WIDTH))}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
30
34
|
export const inspectImageToolRenderer = {
|
|
31
35
|
renderCall(args: InspectImageRenderArgs, _options: RenderResultOptions, uiTheme: Theme): Component {
|
|
32
36
|
const rawPath = args.path ?? "";
|
|
33
37
|
const pathDisplay = rawPath ? shortenPath(rawPath) : "…";
|
|
34
|
-
const header = renderStatusLine({ icon: "pending", title: "Inspect
|
|
38
|
+
const header = renderStatusLine({ icon: "pending", title: "Inspect", description: pathDisplay }, uiTheme);
|
|
35
39
|
const question = args.question?.trim();
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
return new Text(`${header}\n${
|
|
40
|
+
// Call is at most a status line plus a one-line question — too small to box.
|
|
41
|
+
// The container renders a lone Text cleanly with no chrome.
|
|
42
|
+
if (!question) return new Text(header, 0, 0);
|
|
43
|
+
const tree = ` ${uiTheme.fg("dim", uiTheme.tree.last)} ${questionLine(question, uiTheme)}`;
|
|
44
|
+
return new Text(`${header}\n${tree}`, 0, 0);
|
|
41
45
|
},
|
|
42
46
|
|
|
43
47
|
renderResult(
|
|
@@ -49,55 +53,73 @@ export const inspectImageToolRenderer = {
|
|
|
49
53
|
const details = result.details;
|
|
50
54
|
const rawPath = details?.imagePath ?? args?.path ?? "";
|
|
51
55
|
const pathDisplay = rawPath ? shortenPath(rawPath) : "image";
|
|
52
|
-
const metaParts: string[] = [];
|
|
53
|
-
if (details?.model) metaParts.push(details.model);
|
|
54
|
-
if (details?.mimeType) metaParts.push(details.mimeType);
|
|
55
56
|
const header = renderStatusLine(
|
|
56
57
|
{
|
|
57
58
|
icon: result.isError ? "error" : "success",
|
|
58
|
-
title: "Inspect
|
|
59
|
+
title: "Inspect",
|
|
59
60
|
description: pathDisplay,
|
|
60
61
|
},
|
|
61
62
|
uiTheme,
|
|
62
63
|
);
|
|
63
64
|
|
|
64
|
-
const lines: string[] = [header];
|
|
65
65
|
const question = args?.question?.trim();
|
|
66
|
-
if (question) {
|
|
67
|
-
lines.push(
|
|
68
|
-
` ${uiTheme.fg("dim", uiTheme.tree.branch)} ${uiTheme.fg("dim", "Question:")} ${uiTheme.fg("accent", truncateToWidth(replaceTabs(question), INSPECT_QUESTION_PREVIEW_WIDTH))}`,
|
|
69
|
-
);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
66
|
const outputText = result.content.find(content => content.type === "text")?.text?.trimEnd() ?? "";
|
|
73
|
-
if (!outputText) {
|
|
74
|
-
lines.push(uiTheme.fg("dim", "(no output)"));
|
|
75
|
-
if (metaParts.length > 0) {
|
|
76
|
-
lines.push("");
|
|
77
|
-
lines.push(uiTheme.fg("dim", metaParts.join(" · ")));
|
|
78
|
-
}
|
|
79
|
-
return new Text(lines.join("\n"), 0, 0);
|
|
80
|
-
}
|
|
81
67
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
68
|
+
if (result.isError) {
|
|
69
|
+
return framedBlock(uiTheme, width => {
|
|
70
|
+
const bodyLines: string[] = [];
|
|
71
|
+
if (question) bodyLines.push(questionLine(question, uiTheme));
|
|
72
|
+
bodyLines.push(formatErrorDetail(outputText || "inspection failed", uiTheme));
|
|
73
|
+
return {
|
|
74
|
+
header,
|
|
75
|
+
sections: [{ lines: bodyLines }],
|
|
76
|
+
state: "error",
|
|
77
|
+
borderColor: "error",
|
|
78
|
+
applyBg: false,
|
|
79
|
+
width,
|
|
80
|
+
};
|
|
81
|
+
});
|
|
87
82
|
}
|
|
88
83
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
84
|
+
const metaParts: string[] = [];
|
|
85
|
+
if (details?.model) metaParts.push(details.model);
|
|
86
|
+
if (details?.mimeType) metaParts.push(details.mimeType);
|
|
87
|
+
const metaLine = metaParts.length > 0 ? uiTheme.fg("dim", metaParts.join(" · ")) : "";
|
|
94
88
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
89
|
+
// No answer text: nothing worth boxing — keep it to a clean status line
|
|
90
|
+
// (plus a trailing meta line, when present).
|
|
91
|
+
if (!outputText) {
|
|
92
|
+
return new Text(metaLine ? `${header}\n${metaLine}` : header, 0, 0);
|
|
98
93
|
}
|
|
99
94
|
|
|
100
|
-
return
|
|
95
|
+
return framedBlock(uiTheme, width => {
|
|
96
|
+
const bodyLines: string[] = [];
|
|
97
|
+
if (question) {
|
|
98
|
+
bodyLines.push(questionLine(question, uiTheme));
|
|
99
|
+
bodyLines.push("");
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const outputLines = replaceTabs(outputText).split("\n");
|
|
103
|
+
const maxLines = options.expanded ? INSPECT_OUTPUT_EXPANDED_LINES : INSPECT_OUTPUT_COLLAPSED_LINES;
|
|
104
|
+
for (const line of outputLines.slice(0, maxLines)) {
|
|
105
|
+
bodyLines.push(uiTheme.fg("toolOutput", truncateToWidth(line, INSPECT_OUTPUT_LINE_WIDTH)));
|
|
106
|
+
}
|
|
107
|
+
if (outputLines.length > maxLines) {
|
|
108
|
+
const remaining = outputLines.length - maxLines;
|
|
109
|
+
const hint = formatExpandHint(uiTheme, options.expanded, true);
|
|
110
|
+
bodyLines.push(`${uiTheme.fg("dim", `… ${remaining} more lines`)}${hint ? ` ${hint}` : ""}`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
header,
|
|
115
|
+
headerMeta: metaLine || undefined,
|
|
116
|
+
sections: [{ lines: bodyLines }],
|
|
117
|
+
state: "success",
|
|
118
|
+
borderColor: "borderMuted",
|
|
119
|
+
applyBg: false,
|
|
120
|
+
width,
|
|
121
|
+
};
|
|
122
|
+
});
|
|
101
123
|
},
|
|
102
124
|
mergeCallAndResult: true,
|
|
103
125
|
};
|
|
@@ -4,6 +4,7 @@ import { type Api, completeSimple, type Model } from "@oh-my-pi/pi-ai";
|
|
|
4
4
|
import { prompt } from "@oh-my-pi/pi-utils";
|
|
5
5
|
import * as z from "zod/v4";
|
|
6
6
|
import { extractTextContent } from "../commit/utils";
|
|
7
|
+
|
|
7
8
|
import { expandRoleAlias, resolveModelFromString } from "../config/model-resolver";
|
|
8
9
|
import inspectImageDescription from "../prompts/tools/inspect-image.md" with { type: "text" };
|
|
9
10
|
import inspectImageSystemPromptTemplate from "../prompts/tools/inspect-image-system.md" with { type: "text" };
|
|
@@ -136,7 +137,13 @@ export class InspectImageTool implements AgentTool<typeof inspectImageSchema, In
|
|
|
136
137
|
},
|
|
137
138
|
],
|
|
138
139
|
},
|
|
139
|
-
{
|
|
140
|
+
{
|
|
141
|
+
apiKey: modelRegistry.resolver(model.provider, {
|
|
142
|
+
sessionId: this.session.getSessionId?.() ?? undefined,
|
|
143
|
+
baseUrl: model.baseUrl,
|
|
144
|
+
}),
|
|
145
|
+
signal,
|
|
146
|
+
},
|
|
140
147
|
{ telemetry, oneshotKind: "inspect_image", completeImpl: this.completeImageRequest },
|
|
141
148
|
);
|
|
142
149
|
|
package/src/tools/job.ts
CHANGED
|
@@ -396,7 +396,7 @@ export const jobToolRenderer = {
|
|
|
396
396
|
inline: true,
|
|
397
397
|
|
|
398
398
|
renderCall(args: JobRenderArgs, _options: RenderResultOptions, uiTheme: Theme): Component {
|
|
399
|
-
const text = renderStatusLine({ icon: "pending", title:
|
|
399
|
+
const text = renderStatusLine({ icon: "pending", title: describeTarget(args) || "Job" }, uiTheme);
|
|
400
400
|
return new Text(text, 0, 0);
|
|
401
401
|
},
|
|
402
402
|
|
|
@@ -410,7 +410,7 @@ export const jobToolRenderer = {
|
|
|
410
410
|
|
|
411
411
|
if (jobs.length === 0) {
|
|
412
412
|
const fallback = result.content?.find(c => c.type === "text")?.text || "No jobs to process";
|
|
413
|
-
const header = renderStatusLine({ icon: "warning", title:
|
|
413
|
+
const header = renderStatusLine({ icon: "warning", title: describeTarget(args) || "Job" }, uiTheme);
|
|
414
414
|
return new Text([header, formatEmptyMessage(fallback, uiTheme)].join("\n"), 0, 0);
|
|
415
415
|
}
|
|
416
416
|
|
|
@@ -433,8 +433,7 @@ export const jobToolRenderer = {
|
|
|
433
433
|
{
|
|
434
434
|
icon: headerIcon,
|
|
435
435
|
spinnerFrame: counts.running > 0 ? options.spinnerFrame : undefined,
|
|
436
|
-
title:
|
|
437
|
-
description,
|
|
436
|
+
title: description,
|
|
438
437
|
meta,
|
|
439
438
|
},
|
|
440
439
|
uiTheme,
|
|
@@ -4,7 +4,10 @@
|
|
|
4
4
|
*
|
|
5
5
|
* These keep the transcript terse — one status line plus, for `retain`, one
|
|
6
6
|
* `Remember: …` line per stored item — instead of the generic JSON arg tree,
|
|
7
|
-
* which exploded multi-line memory blobs into an unreadable wall.
|
|
7
|
+
* which exploded multi-line memory blobs into an unreadable wall. The tool
|
|
8
|
+
* container is a transparent passthrough, so these renderers stay frameless:
|
|
9
|
+
* a status line with a couple of dim bullets reads far cleaner than boxing a
|
|
10
|
+
* one-line memory note.
|
|
8
11
|
*/
|
|
9
12
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
10
13
|
import { Text } from "@oh-my-pi/pi-tui";
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import * as path from "node:path";
|
|
2
1
|
import { resolveLocalUrlToPath, resolveVaultUrlToPath } from "../internal-urls";
|
|
3
2
|
import type { ToolSession } from ".";
|
|
4
3
|
import { normalizeLocalScheme, resolveToCwd } from "./path-utils";
|
|
@@ -6,10 +5,19 @@ import { ToolError } from "./tool-errors";
|
|
|
6
5
|
|
|
7
6
|
const VAULT_SCHEME_PREFIX = "vault:";
|
|
8
7
|
const LOCAL_SCHEME_PREFIX = "local:";
|
|
9
|
-
const PLAN_ALIAS_FILE = "PLAN.md";
|
|
10
|
-
const LOCAL_PLAN_ALIAS = "local://PLAN.md";
|
|
11
8
|
|
|
12
|
-
|
|
9
|
+
/** True when `targetPath` addresses the session-local artifact sandbox
|
|
10
|
+
* (`local://…`). Those files are not part of the working tree, so plan mode
|
|
11
|
+
* treats them as freely writable scratch/plan space. */
|
|
12
|
+
function targetsLocalSandbox(targetPath: string): boolean {
|
|
13
|
+
return normalizeLocalScheme(targetPath).startsWith(LOCAL_SCHEME_PREFIX);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Resolve a write/edit target to its absolute filesystem path, honoring the
|
|
18
|
+
* `local://` and `vault://` schemes. Plain paths resolve against the session cwd.
|
|
19
|
+
*/
|
|
20
|
+
export function resolvePlanPath(session: ToolSession, targetPath: string): string {
|
|
13
21
|
const normalized = normalizeLocalScheme(targetPath);
|
|
14
22
|
if (normalized.startsWith(LOCAL_SCHEME_PREFIX)) {
|
|
15
23
|
return resolveLocalUrlToPath(normalized, {
|
|
@@ -25,37 +33,12 @@ function resolveRawPath(session: ToolSession, targetPath: string): string {
|
|
|
25
33
|
return resolveToCwd(normalized, session.cwd);
|
|
26
34
|
}
|
|
27
35
|
|
|
28
|
-
function isPlanAliasTarget(session: ToolSession, targetPath: string, resolved: string): boolean {
|
|
29
|
-
const normalized = normalizeLocalScheme(targetPath);
|
|
30
|
-
if (normalized === LOCAL_PLAN_ALIAS) return true;
|
|
31
|
-
return resolved === resolveToCwd(PLAN_ALIAS_FILE, session.cwd);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
36
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* location at `state.planFilePath`. This lets `write` and `edit` accept the
|
|
40
|
-
* habitual plan filename after approval even when the active artifact has a
|
|
41
|
-
* titled path such as `local://APPROVED.md`.
|
|
42
|
-
*
|
|
43
|
-
* Outside plan mode (or when the basename does not match) this is a no-op.
|
|
37
|
+
* Plan mode keeps the working tree read-only while letting the agent draft its
|
|
38
|
+
* plan. Writes and edits to the `local://` artifact sandbox are allowed (that is
|
|
39
|
+
* where the plan and any scratch notes live); anything that would touch the
|
|
40
|
+
* working tree — or rename/delete a file — is rejected.
|
|
44
41
|
*/
|
|
45
|
-
export function resolvePlanPath(session: ToolSession, targetPath: string): string {
|
|
46
|
-
const resolved = resolveRawPath(session, targetPath);
|
|
47
|
-
|
|
48
|
-
const state = session.getPlanModeState?.();
|
|
49
|
-
if (!state?.enabled) return resolved;
|
|
50
|
-
|
|
51
|
-
const planResolved = resolveRawPath(session, state.planFilePath);
|
|
52
|
-
if (resolved === planResolved) return resolved;
|
|
53
|
-
if (isPlanAliasTarget(session, targetPath, resolved)) return planResolved;
|
|
54
|
-
if (path.basename(resolved) !== path.basename(planResolved)) return resolved;
|
|
55
|
-
|
|
56
|
-
return planResolved;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
42
|
export function enforcePlanModeWrite(
|
|
60
43
|
session: ToolSession,
|
|
61
44
|
targetPath: string,
|
|
@@ -64,9 +47,6 @@ export function enforcePlanModeWrite(
|
|
|
64
47
|
const state = session.getPlanModeState?.();
|
|
65
48
|
if (!state?.enabled) return;
|
|
66
49
|
|
|
67
|
-
const resolvedTarget = resolvePlanPath(session, targetPath);
|
|
68
|
-
const resolvedPlan = resolvePlanPath(session, state.planFilePath);
|
|
69
|
-
|
|
70
50
|
if (options?.move) {
|
|
71
51
|
throw new ToolError("Plan mode: renaming files is not allowed.");
|
|
72
52
|
}
|
|
@@ -75,7 +55,9 @@ export function enforcePlanModeWrite(
|
|
|
75
55
|
throw new ToolError("Plan mode: deleting files is not allowed.");
|
|
76
56
|
}
|
|
77
57
|
|
|
78
|
-
if (
|
|
79
|
-
|
|
80
|
-
|
|
58
|
+
if (targetsLocalSandbox(targetPath)) return;
|
|
59
|
+
|
|
60
|
+
throw new ToolError(
|
|
61
|
+
"Plan mode: the working tree is read-only. Write your plan to a local://<slug>-plan.md file instead.",
|
|
62
|
+
);
|
|
81
63
|
}
|