@steipete/oracle 0.8.6 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +130 -45
- package/dist/bin/oracle-cli.js +613 -379
- package/dist/bin/oracle-mcp.js +2 -2
- package/dist/bin/oracle.js +165 -279
- package/dist/scripts/agent-send.js +31 -31
- package/dist/scripts/check.js +6 -6
- package/dist/scripts/debug/extract-chatgpt-response.js +10 -10
- package/dist/scripts/docs-list.js +30 -30
- package/dist/scripts/git-policy.js +25 -23
- package/dist/scripts/run-cli.js +8 -8
- package/dist/scripts/runner.js +203 -195
- package/dist/scripts/test-browser.js +21 -18
- package/dist/scripts/test-remote-chrome.js +20 -20
- package/dist/src/bridge/connection.js +18 -18
- package/dist/src/bridge/userConfigFile.js +7 -7
- package/dist/src/browser/actions/assistantResponse.js +149 -101
- package/dist/src/browser/actions/attachmentDataTransfer.js +49 -47
- package/dist/src/browser/actions/attachments.js +246 -150
- package/dist/src/browser/actions/domEvents.js +2 -2
- package/dist/src/browser/actions/modelSelection.js +314 -104
- package/dist/src/browser/actions/navigation.js +161 -136
- package/dist/src/browser/actions/promptComposer.js +100 -64
- package/dist/src/browser/actions/remoteFileTransfer.js +10 -10
- package/dist/src/browser/actions/thinkingTime.js +207 -110
- package/dist/src/browser/chromeLifecycle.js +62 -60
- package/dist/src/browser/config.js +34 -15
- package/dist/src/browser/constants.js +17 -12
- package/dist/src/browser/cookies.js +19 -19
- package/dist/src/browser/detect.js +62 -62
- package/dist/src/browser/domDebug.js +1 -1
- package/dist/src/browser/index.js +452 -303
- package/dist/src/browser/modelStrategy.js +1 -1
- package/dist/src/browser/pageActions.js +5 -5
- package/dist/src/browser/policies.js +16 -13
- package/dist/src/browser/profileState.js +44 -39
- package/dist/src/browser/prompt.js +72 -42
- package/dist/src/browser/promptSummary.js +5 -5
- package/dist/src/browser/providerDomFlow.js +17 -0
- package/dist/src/browser/providers/chatgptDomProvider.js +49 -0
- package/dist/src/browser/providers/geminiDeepThinkDomProvider.js +254 -0
- package/dist/src/browser/providers/index.js +2 -0
- package/dist/src/browser/reattach.js +67 -34
- package/dist/src/browser/reattachHelpers.js +31 -26
- package/dist/src/browser/sessionRunner.js +37 -25
- package/dist/src/browser/utils.js +9 -9
- package/dist/src/browserMode.js +1 -1
- package/dist/src/cli/bridge/claudeConfig.js +16 -16
- package/dist/src/cli/bridge/client.js +28 -20
- package/dist/src/cli/bridge/codexConfig.js +16 -16
- package/dist/src/cli/bridge/doctor.js +47 -39
- package/dist/src/cli/bridge/host.js +58 -56
- package/dist/src/cli/browserConfig.js +65 -45
- package/dist/src/cli/browserDefaults.js +27 -26
- package/dist/src/cli/bundleWarnings.js +1 -1
- package/dist/src/cli/clipboard.js +11 -2
- package/dist/src/cli/detach.js +7 -4
- package/dist/src/cli/dryRun.js +29 -25
- package/dist/src/cli/duplicatePromptGuard.js +3 -3
- package/dist/src/cli/engine.js +9 -9
- package/dist/src/cli/errorUtils.js +1 -1
- package/dist/src/cli/fileSize.js +11 -0
- package/dist/src/cli/format.js +2 -2
- package/dist/src/cli/help.js +28 -28
- package/dist/src/cli/hiddenAliases.js +3 -3
- package/dist/src/cli/markdownBundle.js +12 -8
- package/dist/src/cli/markdownRenderer.js +15 -15
- package/dist/src/cli/notifier.js +77 -67
- package/dist/src/cli/options.js +145 -87
- package/dist/src/cli/oscUtils.js +1 -1
- package/dist/src/cli/promptRequirement.js +2 -2
- package/dist/src/cli/renderOutput.js +1 -1
- package/dist/src/cli/rootAlias.js +1 -1
- package/dist/src/cli/runOptions.js +37 -25
- package/dist/src/cli/sessionCommand.js +31 -21
- package/dist/src/cli/sessionDisplay.js +182 -79
- package/dist/src/cli/sessionLineage.js +60 -0
- package/dist/src/cli/sessionRunner.js +118 -90
- package/dist/src/cli/sessionTable.js +28 -24
- package/dist/src/cli/stdin.js +22 -0
- package/dist/src/cli/tagline.js +121 -124
- package/dist/src/cli/tui/index.js +140 -127
- package/dist/src/cli/writeOutputPath.js +5 -5
- package/dist/src/config.js +7 -7
- package/dist/src/gemini-web/browserSessionManager.js +80 -0
- package/dist/src/gemini-web/client.js +81 -64
- package/dist/src/gemini-web/executionMode.js +16 -0
- package/dist/src/gemini-web/executor.js +327 -169
- package/dist/src/gemini-web/index.js +1 -1
- package/dist/src/mcp/server.js +16 -12
- package/dist/src/mcp/tools/consult.js +81 -64
- package/dist/src/mcp/tools/sessionResources.js +12 -12
- package/dist/src/mcp/tools/sessions.js +26 -17
- package/dist/src/mcp/types.js +5 -5
- package/dist/src/mcp/utils.js +15 -7
- package/dist/src/oracle/background.js +15 -15
- package/dist/src/oracle/claude.js +53 -25
- package/dist/src/oracle/client.js +84 -46
- package/dist/src/oracle/config.js +124 -58
- package/dist/src/oracle/errors.js +38 -38
- package/dist/src/oracle/files.js +69 -45
- package/dist/src/oracle/finishLine.js +10 -8
- package/dist/src/oracle/format.js +3 -3
- package/dist/src/oracle/gemini.js +37 -30
- package/dist/src/oracle/logging.js +7 -7
- package/dist/src/oracle/markdown.js +28 -28
- package/dist/src/oracle/modelResolver.js +16 -16
- package/dist/src/oracle/multiModelRunner.js +12 -12
- package/dist/src/oracle/oscProgress.js +8 -8
- package/dist/src/oracle/promptAssembly.js +6 -3
- package/dist/src/oracle/request.js +23 -15
- package/dist/src/oracle/run.js +172 -140
- package/dist/src/oracle/runUtils.js +8 -5
- package/dist/src/oracle/tokenEstimate.js +6 -6
- package/dist/src/oracle/tokenStats.js +5 -5
- package/dist/src/oracle/tokenStringifier.js +5 -5
- package/dist/src/oracle.js +12 -12
- package/dist/src/oracleHome.js +3 -3
- package/dist/src/remote/client.js +25 -25
- package/dist/src/remote/health.js +20 -20
- package/dist/src/remote/remoteServiceConfig.js +9 -9
- package/dist/src/remote/server.js +129 -118
- package/dist/src/sessionManager.js +81 -75
- package/dist/src/sessionStore.js +3 -3
- package/dist/src/version.js +10 -10
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
- package/dist/vendor/oracle-notifier/README.md +2 -0
- package/package.json +69 -65
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
- package/vendor/oracle-notifier/README.md +2 -0
- package/dist/markdansi/types/index.js +0 -4
- package/dist/oracle/bin/oracle-cli.js +0 -472
- package/dist/oracle/src/browser/actions/assistantResponse.js +0 -471
- package/dist/oracle/src/browser/actions/attachments.js +0 -82
- package/dist/oracle/src/browser/actions/modelSelection.js +0 -190
- package/dist/oracle/src/browser/actions/navigation.js +0 -75
- package/dist/oracle/src/browser/actions/promptComposer.js +0 -167
- package/dist/oracle/src/browser/chromeLifecycle.js +0 -104
- package/dist/oracle/src/browser/config.js +0 -33
- package/dist/oracle/src/browser/constants.js +0 -40
- package/dist/oracle/src/browser/cookies.js +0 -210
- package/dist/oracle/src/browser/domDebug.js +0 -36
- package/dist/oracle/src/browser/index.js +0 -331
- package/dist/oracle/src/browser/pageActions.js +0 -5
- package/dist/oracle/src/browser/prompt.js +0 -88
- package/dist/oracle/src/browser/promptSummary.js +0 -20
- package/dist/oracle/src/browser/sessionRunner.js +0 -80
- package/dist/oracle/src/browser/utils.js +0 -62
- package/dist/oracle/src/browserMode.js +0 -1
- package/dist/oracle/src/cli/browserConfig.js +0 -44
- package/dist/oracle/src/cli/dryRun.js +0 -59
- package/dist/oracle/src/cli/engine.js +0 -17
- package/dist/oracle/src/cli/errorUtils.js +0 -9
- package/dist/oracle/src/cli/help.js +0 -70
- package/dist/oracle/src/cli/markdownRenderer.js +0 -15
- package/dist/oracle/src/cli/options.js +0 -103
- package/dist/oracle/src/cli/promptRequirement.js +0 -14
- package/dist/oracle/src/cli/rootAlias.js +0 -30
- package/dist/oracle/src/cli/sessionCommand.js +0 -77
- package/dist/oracle/src/cli/sessionDisplay.js +0 -270
- package/dist/oracle/src/cli/sessionRunner.js +0 -94
- package/dist/oracle/src/heartbeat.js +0 -43
- package/dist/oracle/src/oracle/client.js +0 -48
- package/dist/oracle/src/oracle/config.js +0 -29
- package/dist/oracle/src/oracle/errors.js +0 -101
- package/dist/oracle/src/oracle/files.js +0 -220
- package/dist/oracle/src/oracle/format.js +0 -33
- package/dist/oracle/src/oracle/fsAdapter.js +0 -7
- package/dist/oracle/src/oracle/oscProgress.js +0 -60
- package/dist/oracle/src/oracle/request.js +0 -48
- package/dist/oracle/src/oracle/run.js +0 -444
- package/dist/oracle/src/oracle/tokenStats.js +0 -39
- package/dist/oracle/src/oracle/types.js +0 -1
- package/dist/oracle/src/oracle.js +0 -9
- package/dist/oracle/src/sessionManager.js +0 -205
- package/dist/oracle/src/version.js +0 -39
- package/dist/scripts/chrome/browser-tools.js +0 -295
- package/dist/src/browser/profileSync.js +0 -141
- /package/dist/{oracle/src/browser/types.js → src/gemini-web/executionClients.js} +0 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { APIConnectionError, APIConnectionTimeoutError, APIUserAbortError } from
|
|
2
|
-
import { APIError } from
|
|
3
|
-
import { formatElapsed } from
|
|
1
|
+
import { APIConnectionError, APIConnectionTimeoutError, APIUserAbortError } from "openai";
|
|
2
|
+
import { APIError } from "openai/error";
|
|
3
|
+
import { formatElapsed } from "./format.js";
|
|
4
4
|
export class OracleUserError extends Error {
|
|
5
5
|
category;
|
|
6
6
|
details;
|
|
7
7
|
constructor(category, message, details, cause) {
|
|
8
8
|
super(message);
|
|
9
|
-
this.name =
|
|
9
|
+
this.name = "OracleUserError";
|
|
10
10
|
this.category = category;
|
|
11
11
|
this.details = details;
|
|
12
12
|
if (cause) {
|
|
@@ -16,20 +16,20 @@ export class OracleUserError extends Error {
|
|
|
16
16
|
}
|
|
17
17
|
export class FileValidationError extends OracleUserError {
|
|
18
18
|
constructor(message, details, cause) {
|
|
19
|
-
super(
|
|
20
|
-
this.name =
|
|
19
|
+
super("file-validation", message, details, cause);
|
|
20
|
+
this.name = "FileValidationError";
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
23
|
export class BrowserAutomationError extends OracleUserError {
|
|
24
24
|
constructor(message, details, cause) {
|
|
25
|
-
super(
|
|
26
|
-
this.name =
|
|
25
|
+
super("browser-automation", message, details, cause);
|
|
26
|
+
this.name = "BrowserAutomationError";
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
export class PromptValidationError extends OracleUserError {
|
|
30
30
|
constructor(message, details, cause) {
|
|
31
|
-
super(
|
|
32
|
-
this.name =
|
|
31
|
+
super("prompt-validation", message, details, cause);
|
|
32
|
+
this.name = "PromptValidationError";
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
export function asOracleUserError(error) {
|
|
@@ -42,7 +42,7 @@ export class OracleTransportError extends Error {
|
|
|
42
42
|
reason;
|
|
43
43
|
constructor(reason, message, cause) {
|
|
44
44
|
super(message);
|
|
45
|
-
this.name =
|
|
45
|
+
this.name = "OracleTransportError";
|
|
46
46
|
this.reason = reason;
|
|
47
47
|
if (cause) {
|
|
48
48
|
this.cause = cause;
|
|
@@ -54,7 +54,7 @@ export class OracleResponseError extends Error {
|
|
|
54
54
|
response;
|
|
55
55
|
constructor(message, response) {
|
|
56
56
|
super(message);
|
|
57
|
-
this.name =
|
|
57
|
+
this.name = "OracleResponseError";
|
|
58
58
|
this.response = response;
|
|
59
59
|
this.metadata = extractResponseMetadata(response);
|
|
60
60
|
}
|
|
@@ -79,54 +79,54 @@ export function toTransportError(error, model) {
|
|
|
79
79
|
return error;
|
|
80
80
|
}
|
|
81
81
|
if (error instanceof APIConnectionTimeoutError) {
|
|
82
|
-
return new OracleTransportError(
|
|
82
|
+
return new OracleTransportError("client-timeout", "OpenAI request timed out before completion.", error);
|
|
83
83
|
}
|
|
84
84
|
if (error instanceof APIUserAbortError) {
|
|
85
|
-
return new OracleTransportError(
|
|
85
|
+
return new OracleTransportError("client-abort", "The request was aborted before OpenAI finished responding.", error);
|
|
86
86
|
}
|
|
87
87
|
if (error instanceof APIConnectionError) {
|
|
88
|
-
return new OracleTransportError(
|
|
88
|
+
return new OracleTransportError("connection-lost", "Connection to OpenAI dropped before the response completed.", error);
|
|
89
89
|
}
|
|
90
|
-
const isApiError = error instanceof APIError || error?.name ===
|
|
90
|
+
const isApiError = error instanceof APIError || error?.name === "APIError";
|
|
91
91
|
if (isApiError) {
|
|
92
92
|
const apiError = error;
|
|
93
93
|
const code = apiError.code ?? apiError.error?.code;
|
|
94
|
-
const messageText = apiError.message?.toLowerCase?.() ??
|
|
94
|
+
const messageText = apiError.message?.toLowerCase?.() ?? "";
|
|
95
95
|
const apiMessage = apiError.error?.message ||
|
|
96
96
|
apiError.message ||
|
|
97
|
-
(apiError.status ? `${apiError.status} OpenAI API error` :
|
|
97
|
+
(apiError.status ? `${apiError.status} OpenAI API error` : "OpenAI API error");
|
|
98
98
|
// Friendly guidance when a pro-tier model isn't available on this base URL / API key.
|
|
99
|
-
if (model ===
|
|
100
|
-
(code ===
|
|
101
|
-
messageText.includes(
|
|
102
|
-
messageText.includes(
|
|
103
|
-
messageText.includes(
|
|
104
|
-
return new OracleTransportError(
|
|
99
|
+
if ((model === "gpt-5.5-pro" || model === "gpt-5.4-pro") &&
|
|
100
|
+
(code === "model_not_found" ||
|
|
101
|
+
messageText.includes("does not exist") ||
|
|
102
|
+
messageText.includes("unknown model") ||
|
|
103
|
+
messageText.includes("model_not_found"))) {
|
|
104
|
+
return new OracleTransportError("model-unavailable", `${model} is not available on this API base/key. Try gpt-5.5, gpt-5-pro, or switch to the browser engine.`, apiError);
|
|
105
105
|
}
|
|
106
106
|
if (apiError.status === 404 || apiError.status === 405) {
|
|
107
|
-
return new OracleTransportError(
|
|
107
|
+
return new OracleTransportError("unsupported-endpoint", "HTTP 404/405 from the Responses API; this base URL or gateway likely does not expose /v1/responses. Set OPENAI_BASE_URL to api.openai.com/v1, update your Azure API version/deployment, or use the browser engine.", apiError);
|
|
108
108
|
}
|
|
109
|
-
return new OracleTransportError(
|
|
109
|
+
return new OracleTransportError("api-error", apiMessage, apiError);
|
|
110
110
|
}
|
|
111
|
-
return new OracleTransportError(
|
|
111
|
+
return new OracleTransportError("unknown", error instanceof Error ? error.message : "Unknown transport failure.", error);
|
|
112
112
|
}
|
|
113
113
|
export function describeTransportError(error, deadlineMs) {
|
|
114
114
|
switch (error.reason) {
|
|
115
|
-
case
|
|
115
|
+
case "client-timeout":
|
|
116
116
|
return deadlineMs
|
|
117
117
|
? `Client-side timeout: OpenAI streaming call exceeded the ${formatElapsed(deadlineMs)} deadline.`
|
|
118
|
-
:
|
|
119
|
-
case
|
|
120
|
-
return
|
|
121
|
-
case
|
|
122
|
-
return
|
|
123
|
-
case
|
|
118
|
+
: "Client-side timeout: OpenAI streaming call exceeded the configured deadline.";
|
|
119
|
+
case "connection-lost":
|
|
120
|
+
return "Connection to OpenAI ended unexpectedly before the response completed.";
|
|
121
|
+
case "client-abort":
|
|
122
|
+
return "Request was aborted before OpenAI completed the response.";
|
|
123
|
+
case "api-error":
|
|
124
124
|
return error.message;
|
|
125
|
-
case
|
|
125
|
+
case "model-unavailable":
|
|
126
126
|
return error.message;
|
|
127
|
-
case
|
|
128
|
-
return
|
|
127
|
+
case "unsupported-endpoint":
|
|
128
|
+
return "The Responses API returned 404/405 — your base URL/gateway probably lacks /v1/responses (check OPENAI_BASE_URL or switch to api.openai.com / browser engine).";
|
|
129
129
|
default:
|
|
130
|
-
return
|
|
130
|
+
return "OpenAI streaming call ended with an unknown transport error.";
|
|
131
131
|
}
|
|
132
132
|
}
|
package/dist/src/oracle/files.js
CHANGED
|
@@ -1,11 +1,20 @@
|
|
|
1
|
-
import fs from
|
|
2
|
-
import path from
|
|
3
|
-
import fg from
|
|
4
|
-
import { FileValidationError } from
|
|
5
|
-
const
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import fg from "fast-glob";
|
|
4
|
+
import { FileValidationError } from "./errors.js";
|
|
5
|
+
export const DEFAULT_MAX_FILE_SIZE_BYTES = 1 * 1024 * 1024; // 1 MB
|
|
6
6
|
const DEFAULT_FS = fs;
|
|
7
|
-
const DEFAULT_IGNORED_DIRS = [
|
|
8
|
-
|
|
7
|
+
const DEFAULT_IGNORED_DIRS = new Set([
|
|
8
|
+
"node_modules",
|
|
9
|
+
"dist",
|
|
10
|
+
"coverage",
|
|
11
|
+
".git",
|
|
12
|
+
".turbo",
|
|
13
|
+
".next",
|
|
14
|
+
"build",
|
|
15
|
+
"tmp",
|
|
16
|
+
]);
|
|
17
|
+
export async function readFiles(filePaths, { cwd = process.cwd(), fsModule = DEFAULT_FS, maxFileSizeBytes = DEFAULT_MAX_FILE_SIZE_BYTES, readContents = true, } = {}) {
|
|
9
18
|
if (!filePaths || filePaths.length === 0) {
|
|
10
19
|
return [];
|
|
11
20
|
}
|
|
@@ -24,13 +33,13 @@ export async function readFiles(filePaths, { cwd = process.cwd(), fsModule = DEF
|
|
|
24
33
|
}
|
|
25
34
|
else {
|
|
26
35
|
if (partitioned.globPatterns.length > 0 || partitioned.excludePatterns.length > 0) {
|
|
27
|
-
throw new Error(
|
|
36
|
+
throw new Error("Glob patterns and exclusions are only supported for on-disk files.");
|
|
28
37
|
}
|
|
29
38
|
candidatePaths = await expandWithCustomFs(partitioned, fsModule);
|
|
30
39
|
}
|
|
31
40
|
const allowedLiteralDirs = partitioned.literalDirectories
|
|
32
41
|
.map((dir) => path.resolve(dir))
|
|
33
|
-
.filter((dir) => DEFAULT_IGNORED_DIRS.
|
|
42
|
+
.filter((dir) => DEFAULT_IGNORED_DIRS.has(path.basename(dir)));
|
|
34
43
|
const allowedLiteralFiles = partitioned.literalFiles.map((file) => path.resolve(file));
|
|
35
44
|
const resolvedLiteralDirs = new Set(allowedLiteralDirs);
|
|
36
45
|
const allowedPaths = new Set([...allowedLiteralDirs, ...allowedLiteralFiles]);
|
|
@@ -50,7 +59,7 @@ export async function readFiles(filePaths, { cwd = process.cwd(), fsModule = DEF
|
|
|
50
59
|
return false;
|
|
51
60
|
});
|
|
52
61
|
if (filteredCandidates.length === 0) {
|
|
53
|
-
throw new FileValidationError(
|
|
62
|
+
throw new FileValidationError("No files matched the provided --file patterns.", {
|
|
54
63
|
patterns: partitioned.globPatterns,
|
|
55
64
|
excludes: partitioned.excludePatterns,
|
|
56
65
|
});
|
|
@@ -68,7 +77,7 @@ export async function readFiles(filePaths, { cwd = process.cwd(), fsModule = DEF
|
|
|
68
77
|
if (!stats.isFile()) {
|
|
69
78
|
continue;
|
|
70
79
|
}
|
|
71
|
-
if (maxFileSizeBytes && typeof stats.size ===
|
|
80
|
+
if (maxFileSizeBytes && typeof stats.size === "number" && stats.size > maxFileSizeBytes) {
|
|
72
81
|
const relative = path.relative(cwd, filePath) || filePath;
|
|
73
82
|
oversized.push(`${relative} (${formatBytes(stats.size)})`);
|
|
74
83
|
continue;
|
|
@@ -76,14 +85,14 @@ export async function readFiles(filePaths, { cwd = process.cwd(), fsModule = DEF
|
|
|
76
85
|
accepted.push(filePath);
|
|
77
86
|
}
|
|
78
87
|
if (oversized.length > 0) {
|
|
79
|
-
throw new FileValidationError(`The following files exceed the
|
|
88
|
+
throw new FileValidationError(`The following files exceed the ${formatBytes(maxFileSizeBytes)} limit:\n- ${oversized.join("\n- ")}`, {
|
|
80
89
|
files: oversized,
|
|
81
90
|
limitBytes: maxFileSizeBytes,
|
|
82
91
|
});
|
|
83
92
|
}
|
|
84
93
|
const files = [];
|
|
85
94
|
for (const filePath of accepted) {
|
|
86
|
-
const content = readContents ? await fsModule.readFile(filePath,
|
|
95
|
+
const content = readContents ? await fsModule.readFile(filePath, "utf8") : "";
|
|
87
96
|
files.push({ path: filePath, content });
|
|
88
97
|
}
|
|
89
98
|
return files;
|
|
@@ -100,7 +109,7 @@ async function partitionFileInputs(rawPaths, cwd, fsModule) {
|
|
|
100
109
|
if (!raw) {
|
|
101
110
|
continue;
|
|
102
111
|
}
|
|
103
|
-
if (raw.startsWith(
|
|
112
|
+
if (raw.startsWith("!")) {
|
|
104
113
|
const normalized = normalizeGlob(raw.slice(1), cwd);
|
|
105
114
|
if (normalized) {
|
|
106
115
|
result.excludePatterns.push(normalized);
|
|
@@ -153,11 +162,13 @@ async function expandWithNativeGlob(partitioned, cwd) {
|
|
|
153
162
|
}));
|
|
154
163
|
const resolved = matches.map((match) => path.resolve(cwd, match));
|
|
155
164
|
const filtered = resolved.filter((filePath) => !isGitignored(filePath, gitignoreSets));
|
|
156
|
-
const finalFiles = dotfileOptIn
|
|
165
|
+
const finalFiles = dotfileOptIn
|
|
166
|
+
? filtered
|
|
167
|
+
: filtered.filter((filePath) => !path.basename(filePath).startsWith("."));
|
|
157
168
|
return Array.from(new Set(finalFiles));
|
|
158
169
|
}
|
|
159
170
|
async function loadGitignoreSets(cwd) {
|
|
160
|
-
const gitignorePaths = await fg(
|
|
171
|
+
const gitignorePaths = await fg("**/.gitignore", {
|
|
161
172
|
cwd,
|
|
162
173
|
dot: true,
|
|
163
174
|
absolute: true,
|
|
@@ -168,11 +179,11 @@ async function loadGitignoreSets(cwd) {
|
|
|
168
179
|
const sets = [];
|
|
169
180
|
for (const filePath of gitignorePaths) {
|
|
170
181
|
try {
|
|
171
|
-
const raw = await fs.readFile(filePath,
|
|
182
|
+
const raw = await fs.readFile(filePath, "utf8");
|
|
172
183
|
const patterns = raw
|
|
173
|
-
.split(
|
|
184
|
+
.split("\n")
|
|
174
185
|
.map((line) => line.trim())
|
|
175
|
-
.filter((line) => line.length > 0 && !line.startsWith(
|
|
186
|
+
.filter((line) => line.length > 0 && !line.startsWith("#"));
|
|
176
187
|
if (patterns.length > 0) {
|
|
177
188
|
sets.push({ dir: path.dirname(filePath), patterns });
|
|
178
189
|
}
|
|
@@ -204,7 +215,7 @@ async function buildIgnoredWhitelist(filePaths, cwd, fsModule) {
|
|
|
204
215
|
const parts = rel.split(path.sep).filter(Boolean);
|
|
205
216
|
for (let i = 0; i < parts.length - 1; i += 1) {
|
|
206
217
|
const part = parts[i];
|
|
207
|
-
if (!DEFAULT_IGNORED_DIRS.
|
|
218
|
+
if (!DEFAULT_IGNORED_DIRS.has(part)) {
|
|
208
219
|
continue;
|
|
209
220
|
}
|
|
210
221
|
const dirPath = path.resolve(cwd, ...parts.slice(0, i + 1));
|
|
@@ -212,7 +223,7 @@ async function buildIgnoredWhitelist(filePaths, cwd, fsModule) {
|
|
|
212
223
|
continue;
|
|
213
224
|
}
|
|
214
225
|
try {
|
|
215
|
-
const stats = await fsModule.stat(path.join(dirPath,
|
|
226
|
+
const stats = await fsModule.stat(path.join(dirPath, ".gitignore"));
|
|
216
227
|
if (stats.isFile()) {
|
|
217
228
|
whitelist.add(dirPath);
|
|
218
229
|
}
|
|
@@ -233,7 +244,7 @@ function findIgnoredAncestor(filePath, cwd, _literalDirs, allowedPaths, ignoredW
|
|
|
233
244
|
const parts = rel.split(path.sep);
|
|
234
245
|
for (let idx = 0; idx < parts.length; idx += 1) {
|
|
235
246
|
const part = parts[idx];
|
|
236
|
-
if (!DEFAULT_IGNORED_DIRS.
|
|
247
|
+
if (!DEFAULT_IGNORED_DIRS.has(part)) {
|
|
237
248
|
continue;
|
|
238
249
|
}
|
|
239
250
|
const ignoredDir = path.resolve(cwd, parts.slice(0, idx + 1).join(path.sep));
|
|
@@ -251,9 +262,9 @@ function matchesPattern(relativePath, pattern) {
|
|
|
251
262
|
if (!pattern) {
|
|
252
263
|
return false;
|
|
253
264
|
}
|
|
254
|
-
const normalized = pattern.replace(/\\+/g,
|
|
265
|
+
const normalized = pattern.replace(/\\+/g, "/");
|
|
255
266
|
// Directory rule
|
|
256
|
-
if (normalized.endsWith(
|
|
267
|
+
if (normalized.endsWith("/")) {
|
|
257
268
|
const dir = normalized.slice(0, -1);
|
|
258
269
|
return relativePath === dir || relativePath.startsWith(`${dir}/`);
|
|
259
270
|
}
|
|
@@ -262,16 +273,14 @@ function matchesPattern(relativePath, pattern) {
|
|
|
262
273
|
return regex.test(relativePath);
|
|
263
274
|
}
|
|
264
275
|
function globToRegex(pattern) {
|
|
265
|
-
const withMarkers = pattern.replace(/\*\*/g,
|
|
266
|
-
const escaped = withMarkers.replace(/[.+?^${}()|[\]\\]/g,
|
|
267
|
-
const restored = escaped
|
|
268
|
-
.replace(/§§DOUBLESTAR§§/g, '.*')
|
|
269
|
-
.replace(/§§SINGLESTAR§§/g, '[^/]*');
|
|
276
|
+
const withMarkers = pattern.replace(/\*\*/g, "§§DOUBLESTAR§§").replace(/\*/g, "§§SINGLESTAR§§");
|
|
277
|
+
const escaped = withMarkers.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
|
|
278
|
+
const restored = escaped.replace(/§§DOUBLESTAR§§/g, ".*").replace(/§§SINGLESTAR§§/g, "[^/]*");
|
|
270
279
|
return new RegExp(`^${restored}$`);
|
|
271
280
|
}
|
|
272
281
|
function includesDotfileSegment(pattern) {
|
|
273
|
-
const segments = pattern.split(
|
|
274
|
-
return segments.some((segment) => segment.startsWith(
|
|
282
|
+
const segments = pattern.split("/");
|
|
283
|
+
return segments.some((segment) => segment.startsWith(".") && segment.length > 1);
|
|
275
284
|
}
|
|
276
285
|
async function expandWithCustomFs(partitioned, fsModule) {
|
|
277
286
|
const paths = new Set();
|
|
@@ -302,38 +311,38 @@ async function expandDirectoryRecursive(directory, fsModule) {
|
|
|
302
311
|
return results;
|
|
303
312
|
}
|
|
304
313
|
function makeDirectoryPattern(relative) {
|
|
305
|
-
if (relative ===
|
|
306
|
-
return
|
|
314
|
+
if (relative === "." || relative === "") {
|
|
315
|
+
return "**/*";
|
|
307
316
|
}
|
|
308
317
|
return `${stripTrailingSlashes(relative)}/**/*`;
|
|
309
318
|
}
|
|
310
319
|
function isNativeFsModule(fsModule) {
|
|
311
|
-
return (
|
|
320
|
+
return (fsModule.__nativeFs === true ||
|
|
312
321
|
(fsModule.readFile === DEFAULT_FS.readFile &&
|
|
313
322
|
fsModule.stat === DEFAULT_FS.stat &&
|
|
314
|
-
fsModule.readdir === DEFAULT_FS.readdir))
|
|
323
|
+
fsModule.readdir === DEFAULT_FS.readdir));
|
|
315
324
|
}
|
|
316
325
|
function normalizeGlob(pattern, cwd) {
|
|
317
326
|
if (!pattern) {
|
|
318
|
-
return
|
|
327
|
+
return "";
|
|
319
328
|
}
|
|
320
329
|
let normalized = pattern;
|
|
321
330
|
if (path.isAbsolute(normalized)) {
|
|
322
331
|
normalized = path.relative(cwd, normalized);
|
|
323
332
|
}
|
|
324
333
|
normalized = toPosix(normalized);
|
|
325
|
-
if (normalized.startsWith(
|
|
334
|
+
if (normalized.startsWith("./")) {
|
|
326
335
|
normalized = normalized.slice(2);
|
|
327
336
|
}
|
|
328
337
|
return normalized;
|
|
329
338
|
}
|
|
330
339
|
function toPosix(value) {
|
|
331
|
-
return value.replace(/\\/g,
|
|
340
|
+
return value.replace(/\\/g, "/");
|
|
332
341
|
}
|
|
333
342
|
function toPosixRelative(absPath, cwd) {
|
|
334
343
|
const relative = path.relative(cwd, absPath);
|
|
335
344
|
if (!relative) {
|
|
336
|
-
return
|
|
345
|
+
return ".";
|
|
337
346
|
}
|
|
338
347
|
return toPosix(relative);
|
|
339
348
|
}
|
|
@@ -343,17 +352,32 @@ function toPosixRelativeOrBasename(absPath, cwd) {
|
|
|
343
352
|
}
|
|
344
353
|
function stripTrailingSlashes(value) {
|
|
345
354
|
const normalized = toPosix(value);
|
|
346
|
-
return normalized.replace(/\/+$/g,
|
|
355
|
+
return normalized.replace(/\/+$/g, "");
|
|
347
356
|
}
|
|
348
357
|
function formatBytes(size) {
|
|
349
358
|
if (size >= 1024 * 1024) {
|
|
350
|
-
return `${(size / (1024 * 1024))
|
|
359
|
+
return `${formatScaled(size / (1024 * 1024))} MB`;
|
|
351
360
|
}
|
|
352
361
|
if (size >= 1024) {
|
|
353
|
-
return `${(size / 1024)
|
|
362
|
+
return `${formatScaled(size / 1024)} KB`;
|
|
354
363
|
}
|
|
355
364
|
return `${size} B`;
|
|
356
365
|
}
|
|
366
|
+
function formatScaled(value) {
|
|
367
|
+
return value.toFixed(1).replace(/\.0$/, "");
|
|
368
|
+
}
|
|
369
|
+
export function normalizeMaxFileSizeBytes(value, source = "max file size") {
|
|
370
|
+
if (value == null || value === "") {
|
|
371
|
+
return undefined;
|
|
372
|
+
}
|
|
373
|
+
const parsed = typeof value === "number"
|
|
374
|
+
? value
|
|
375
|
+
: Number.parseInt(typeof value === "string" ? value.trim() : String(value), 10);
|
|
376
|
+
if (!Number.isSafeInteger(parsed) || parsed <= 0) {
|
|
377
|
+
throw new Error(`${source} must be a positive integer number of bytes.`);
|
|
378
|
+
}
|
|
379
|
+
return parsed;
|
|
380
|
+
}
|
|
357
381
|
function relativePath(targetPath, cwd) {
|
|
358
382
|
const relative = path.relative(cwd, targetPath);
|
|
359
383
|
return relative || targetPath;
|
|
@@ -363,10 +387,10 @@ export function createFileSections(files, cwd = process.cwd()) {
|
|
|
363
387
|
const relative = toPosix(path.relative(cwd, file.path) || file.path);
|
|
364
388
|
const sectionText = [
|
|
365
389
|
`### File ${index + 1}: ${relative}`,
|
|
366
|
-
|
|
390
|
+
"```",
|
|
367
391
|
file.content.trimEnd(),
|
|
368
|
-
|
|
369
|
-
].join(
|
|
392
|
+
"```",
|
|
393
|
+
].join("\n");
|
|
370
394
|
return {
|
|
371
395
|
index: index + 1,
|
|
372
396
|
absolutePath: file.path,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { formatUSD } from
|
|
1
|
+
import { formatUSD } from "./format.js";
|
|
2
2
|
export function formatElapsedCompact(ms) {
|
|
3
3
|
if (!Number.isFinite(ms) || ms < 0) {
|
|
4
|
-
return
|
|
4
|
+
return "unknown";
|
|
5
5
|
}
|
|
6
6
|
if (ms < 60_000) {
|
|
7
7
|
return `${(ms / 1000).toFixed(1)}s`;
|
|
@@ -9,24 +9,26 @@ export function formatElapsedCompact(ms) {
|
|
|
9
9
|
if (ms < 60 * 60_000) {
|
|
10
10
|
const minutes = Math.floor(ms / 60_000);
|
|
11
11
|
const seconds = Math.floor((ms % 60_000) / 1000);
|
|
12
|
-
return `${minutes}m${seconds.toString().padStart(2,
|
|
12
|
+
return `${minutes}m${seconds.toString().padStart(2, "0")}s`;
|
|
13
13
|
}
|
|
14
14
|
const hours = Math.floor(ms / (60 * 60_000));
|
|
15
15
|
const minutes = Math.floor((ms % (60 * 60_000)) / 60_000);
|
|
16
|
-
return `${hours}h${minutes.toString().padStart(2,
|
|
16
|
+
return `${hours}h${minutes.toString().padStart(2, "0")}m`;
|
|
17
17
|
}
|
|
18
18
|
export function formatFinishLine({ elapsedMs, model, costUsd, tokensPart, summaryExtraParts, detailParts, }) {
|
|
19
19
|
const line1Parts = [
|
|
20
20
|
formatElapsedCompact(elapsedMs),
|
|
21
|
-
typeof costUsd ===
|
|
21
|
+
typeof costUsd === "number" ? formatUSD(costUsd) : null,
|
|
22
22
|
model,
|
|
23
23
|
tokensPart,
|
|
24
24
|
...(summaryExtraParts ?? []),
|
|
25
25
|
];
|
|
26
|
-
const line1 = line1Parts
|
|
27
|
-
|
|
26
|
+
const line1 = line1Parts
|
|
27
|
+
.filter((part) => typeof part === "string" && part.length > 0)
|
|
28
|
+
.join(" · ");
|
|
29
|
+
const line2Parts = (detailParts ?? []).filter((part) => typeof part === "string" && part.length > 0);
|
|
28
30
|
if (line2Parts.length === 0) {
|
|
29
31
|
return { line1 };
|
|
30
32
|
}
|
|
31
|
-
return { line1, line2: line2Parts.join(
|
|
33
|
+
return { line1, line2: line2Parts.join(" | ") };
|
|
32
34
|
}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
export function formatUSD(value) {
|
|
2
2
|
if (!Number.isFinite(value)) {
|
|
3
|
-
return
|
|
3
|
+
return "n/a";
|
|
4
4
|
}
|
|
5
5
|
// Display with 4 decimal places, rounding to $0.0001 minimum granularity.
|
|
6
6
|
return `$${value.toFixed(4)}`;
|
|
7
7
|
}
|
|
8
8
|
export function formatNumber(value, { estimated = false } = {}) {
|
|
9
9
|
if (value == null) {
|
|
10
|
-
return
|
|
10
|
+
return "n/a";
|
|
11
11
|
}
|
|
12
|
-
const suffix = estimated ?
|
|
12
|
+
const suffix = estimated ? " (est.)" : "";
|
|
13
13
|
return `${value.toLocaleString()}${suffix}`;
|
|
14
14
|
}
|
|
15
15
|
export function formatElapsed(ms) {
|
|
@@ -1,30 +1,35 @@
|
|
|
1
|
-
import { GoogleGenAI, HarmCategory, HarmBlockThreshold } from
|
|
1
|
+
import { GoogleGenAI, HarmCategory, HarmBlockThreshold, } from "@google/genai";
|
|
2
2
|
const MODEL_ID_MAP = {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
3
|
+
"gemini-3.1-pro": "gemini-3.1-pro-preview",
|
|
4
|
+
"gemini-3-pro": "gemini-3-pro-preview",
|
|
5
|
+
"gpt-5.5": "gpt-5.5",
|
|
6
|
+
"gpt-5.5-pro": "gpt-5.5-pro",
|
|
7
|
+
"gpt-5.4": "gpt-5.4",
|
|
8
|
+
"gpt-5.4-pro": "gpt-5.4-pro",
|
|
9
|
+
"gpt-5.1-pro": "gpt-5.1-pro",
|
|
10
|
+
"gpt-5-pro": "gpt-5-pro",
|
|
11
|
+
"gpt-5.1": "gpt-5.1",
|
|
12
|
+
"gpt-5.1-codex": "gpt-5.1-codex",
|
|
13
|
+
"gpt-5.2": "gpt-5.2",
|
|
14
|
+
"gpt-5.2-instant": "gpt-5.2-instant",
|
|
15
|
+
"gpt-5.2-pro": "gpt-5.2-pro",
|
|
16
|
+
"claude-4.6-sonnet": "claude-4.6-sonnet",
|
|
17
|
+
"claude-4.1-opus": "claude-4.1-opus",
|
|
18
|
+
"grok-4.1": "grok-4.1",
|
|
14
19
|
};
|
|
15
20
|
export function resolveGeminiModelId(modelName) {
|
|
16
21
|
// Map our logical Gemini names to the exact model ids expected by the SDK.
|
|
17
22
|
return MODEL_ID_MAP[modelName] ?? modelName;
|
|
18
23
|
}
|
|
19
|
-
export function createGeminiClient(apiKey, modelName =
|
|
24
|
+
export function createGeminiClient(apiKey, modelName = "gemini-3-pro", resolvedModelId) {
|
|
20
25
|
const modelId = resolvedModelId ?? resolveGeminiModelId(modelName);
|
|
21
26
|
const genAI = new GoogleGenAI({ apiKey });
|
|
22
27
|
const adaptBodyToGemini = (body) => {
|
|
23
28
|
const contents = body.input.map((inputItem) => ({
|
|
24
|
-
role: inputItem.role ===
|
|
29
|
+
role: inputItem.role === "user" ? "user" : "model",
|
|
25
30
|
parts: inputItem.content
|
|
26
31
|
.map((contentPart) => {
|
|
27
|
-
if (contentPart.type ===
|
|
32
|
+
if (contentPart.type === "input_text") {
|
|
28
33
|
return { text: contentPart.text };
|
|
29
34
|
}
|
|
30
35
|
return null;
|
|
@@ -33,7 +38,7 @@ export function createGeminiClient(apiKey, modelName = 'gemini-3-pro', resolvedM
|
|
|
33
38
|
}));
|
|
34
39
|
const tools = body.tools
|
|
35
40
|
?.map((tool) => {
|
|
36
|
-
if (tool.type ===
|
|
41
|
+
if (tool.type === "web_search_preview") {
|
|
37
42
|
return {
|
|
38
43
|
googleSearch: {},
|
|
39
44
|
};
|
|
@@ -60,7 +65,7 @@ export function createGeminiClient(apiKey, modelName = 'gemini-3-pro', resolvedM
|
|
|
60
65
|
},
|
|
61
66
|
];
|
|
62
67
|
const systemInstruction = body.instructions
|
|
63
|
-
? { role:
|
|
68
|
+
? { role: "system", parts: [{ text: body.instructions }] }
|
|
64
69
|
: undefined;
|
|
65
70
|
return {
|
|
66
71
|
model: modelId,
|
|
@@ -80,18 +85,19 @@ export function createGeminiClient(apiKey, modelName = 'gemini-3-pro', resolvedM
|
|
|
80
85
|
candidate.content?.parts?.forEach((part) => {
|
|
81
86
|
if (part.text) {
|
|
82
87
|
outputText.push(part.text);
|
|
83
|
-
output.push({ type:
|
|
88
|
+
output.push({ type: "text", text: part.text });
|
|
84
89
|
}
|
|
85
90
|
});
|
|
86
91
|
});
|
|
87
92
|
const usage = {
|
|
88
93
|
input_tokens: geminiResponse.usageMetadata?.promptTokenCount || 0,
|
|
89
94
|
output_tokens: geminiResponse.usageMetadata?.candidatesTokenCount || 0,
|
|
90
|
-
total_tokens: (geminiResponse.usageMetadata?.promptTokenCount || 0) +
|
|
95
|
+
total_tokens: (geminiResponse.usageMetadata?.promptTokenCount || 0) +
|
|
96
|
+
(geminiResponse.usageMetadata?.candidatesTokenCount || 0),
|
|
91
97
|
};
|
|
92
98
|
return {
|
|
93
99
|
id: geminiResponse.responseId ?? `gemini-${Date.now()}`,
|
|
94
|
-
status:
|
|
100
|
+
status: "completed",
|
|
95
101
|
output_text: outputText,
|
|
96
102
|
output,
|
|
97
103
|
usage,
|
|
@@ -105,15 +111,15 @@ export function createGeminiClient(apiKey, modelName = 'gemini-3-pro', resolvedM
|
|
|
105
111
|
};
|
|
106
112
|
return {
|
|
107
113
|
id: responseId ?? `gemini-${Date.now()}`,
|
|
108
|
-
status:
|
|
114
|
+
status: "completed",
|
|
109
115
|
output_text: [text],
|
|
110
|
-
output: [{ type:
|
|
116
|
+
output: [{ type: "text", text }],
|
|
111
117
|
usage,
|
|
112
118
|
};
|
|
113
119
|
};
|
|
114
120
|
const enrichGeminiError = (error) => {
|
|
115
121
|
const message = error instanceof Error ? error.message : String(error);
|
|
116
|
-
if (message.includes(
|
|
122
|
+
if (message.includes("404")) {
|
|
117
123
|
return new Error(`Gemini model not available to this API key/region. Confirm preview access and model ID (${modelId}). Original: ${message}`);
|
|
118
124
|
}
|
|
119
125
|
return error instanceof Error ? error : new Error(message);
|
|
@@ -123,7 +129,7 @@ export function createGeminiClient(apiKey, modelName = 'gemini-3-pro', resolvedM
|
|
|
123
129
|
stream: (body) => {
|
|
124
130
|
const geminiBody = adaptBodyToGemini(body);
|
|
125
131
|
let finalResponsePromise = null;
|
|
126
|
-
let aggregatedText =
|
|
132
|
+
let aggregatedText = "";
|
|
127
133
|
let lastUsage;
|
|
128
134
|
let responseId;
|
|
129
135
|
async function* iterator() {
|
|
@@ -138,7 +144,7 @@ export function createGeminiClient(apiKey, modelName = 'gemini-3-pro', resolvedM
|
|
|
138
144
|
const text = chunk.text;
|
|
139
145
|
if (text) {
|
|
140
146
|
aggregatedText += text;
|
|
141
|
-
yield { type:
|
|
147
|
+
yield { type: "chunk", delta: text };
|
|
142
148
|
}
|
|
143
149
|
if (chunk.usageMetadata) {
|
|
144
150
|
lastUsage = chunk.usageMetadata;
|
|
@@ -157,13 +163,14 @@ export function createGeminiClient(apiKey, modelName = 'gemini-3-pro', resolvedM
|
|
|
157
163
|
if (!finalResponsePromise) {
|
|
158
164
|
// In case the user calls finalResponse before iterating, we need to consume the stream
|
|
159
165
|
// This is a bit edge-casey but safe.
|
|
160
|
-
for await (const _ of generator) {
|
|
166
|
+
for await (const _ of generator) {
|
|
167
|
+
}
|
|
161
168
|
}
|
|
162
169
|
if (!finalResponsePromise) {
|
|
163
|
-
throw new Error(
|
|
170
|
+
throw new Error("Response promise not initialized");
|
|
164
171
|
}
|
|
165
172
|
return finalResponsePromise;
|
|
166
|
-
}
|
|
173
|
+
},
|
|
167
174
|
};
|
|
168
175
|
},
|
|
169
176
|
create: async (body) => {
|
|
@@ -180,8 +187,8 @@ export function createGeminiClient(apiKey, modelName = 'gemini-3-pro', resolvedM
|
|
|
180
187
|
retrieve: async (id) => {
|
|
181
188
|
return {
|
|
182
189
|
id,
|
|
183
|
-
status:
|
|
184
|
-
error: { message:
|
|
190
|
+
status: "error",
|
|
191
|
+
error: { message: "Retrieve by ID not supported for Gemini API yet." },
|
|
185
192
|
};
|
|
186
193
|
},
|
|
187
194
|
},
|