@steipete/oracle 0.9.0 → 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 +61 -48
- package/dist/bin/oracle-cli.js +455 -402
- 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 +275 -117
- package/dist/src/browser/actions/navigation.js +161 -137
- 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 +390 -295
- 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 +1 -1
- package/dist/src/browser/providers/chatgptDomProvider.js +9 -9
- package/dist/src/browser/providers/geminiDeepThinkDomProvider.js +51 -42
- package/dist/src/browser/providers/index.js +2 -2
- 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 +62 -48
- 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 +2 -2
- 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 +3 -3
- 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 +7 -7
- package/dist/src/cli/markdownRenderer.js +15 -15
- package/dist/src/cli/notifier.js +77 -67
- package/dist/src/cli/options.js +127 -106
- 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 +32 -28
- package/dist/src/cli/sessionCommand.js +31 -21
- package/dist/src/cli/sessionDisplay.js +95 -81
- package/dist/src/cli/sessionLineage.js +6 -2
- package/dist/src/cli/sessionRunner.js +103 -93
- package/dist/src/cli/sessionTable.js +26 -23
- package/dist/src/cli/stdin.js +22 -0
- package/dist/src/cli/tagline.js +121 -124
- package/dist/src/cli/tui/index.js +139 -128
- package/dist/src/cli/writeOutputPath.js +5 -5
- package/dist/src/config.js +7 -7
- package/dist/src/gemini-web/browserSessionManager.js +19 -15
- package/dist/src/gemini-web/client.js +76 -70
- package/dist/src/gemini-web/executionMode.js +6 -8
- package/dist/src/gemini-web/executor.js +98 -93
- package/dist/src/gemini-web/index.js +1 -1
- package/dist/src/mcp/server.js +16 -12
- package/dist/src/mcp/tools/consult.js +51 -47
- 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 +50 -41
- package/dist/src/oracle/config.js +96 -66
- package/dist/src/oracle/errors.js +38 -38
- package/dist/src/oracle/files.js +55 -46
- package/dist/src/oracle/finishLine.js +10 -8
- package/dist/src/oracle/format.js +3 -3
- package/dist/src/oracle/gemini.js +37 -33
- 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 +16 -13
- package/dist/src/oracle/run.js +156 -134
- 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 +77 -75
- package/dist/src/sessionStore.js +3 -3
- package/dist/src/version.js +10 -10
- package/dist/vendor/oracle-notifier/README.md +2 -0
- package/package.json +66 -62
- 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/types.js +0 -1
- 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
|
@@ -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,10 +1,19 @@
|
|
|
1
|
-
import fs from
|
|
2
|
-
import path from
|
|
3
|
-
import fg from
|
|
4
|
-
import { FileValidationError } from
|
|
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
5
|
export const DEFAULT_MAX_FILE_SIZE_BYTES = 1 * 1024 * 1024; // 1 MB
|
|
6
6
|
const DEFAULT_FS = fs;
|
|
7
|
-
const DEFAULT_IGNORED_DIRS = [
|
|
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
|
+
]);
|
|
8
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 [];
|
|
@@ -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 ${formatBytes(maxFileSizeBytes)} limit:\n- ${oversized.join(
|
|
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,7 +352,7 @@ 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) {
|
|
@@ -355,15 +364,15 @@ function formatBytes(size) {
|
|
|
355
364
|
return `${size} B`;
|
|
356
365
|
}
|
|
357
366
|
function formatScaled(value) {
|
|
358
|
-
return value.toFixed(1).replace(/\.0$/,
|
|
367
|
+
return value.toFixed(1).replace(/\.0$/, "");
|
|
359
368
|
}
|
|
360
|
-
export function normalizeMaxFileSizeBytes(value, source =
|
|
361
|
-
if (value == null || value ===
|
|
369
|
+
export function normalizeMaxFileSizeBytes(value, source = "max file size") {
|
|
370
|
+
if (value == null || value === "") {
|
|
362
371
|
return undefined;
|
|
363
372
|
}
|
|
364
|
-
const parsed = typeof value ===
|
|
373
|
+
const parsed = typeof value === "number"
|
|
365
374
|
? value
|
|
366
|
-
: Number.parseInt(typeof value ===
|
|
375
|
+
: Number.parseInt(typeof value === "string" ? value.trim() : String(value), 10);
|
|
367
376
|
if (!Number.isSafeInteger(parsed) || parsed <= 0) {
|
|
368
377
|
throw new Error(`${source} must be a positive integer number of bytes.`);
|
|
369
378
|
}
|
|
@@ -378,10 +387,10 @@ export function createFileSections(files, cwd = process.cwd()) {
|
|
|
378
387
|
const relative = toPosix(path.relative(cwd, file.path) || file.path);
|
|
379
388
|
const sectionText = [
|
|
380
389
|
`### File ${index + 1}: ${relative}`,
|
|
381
|
-
|
|
390
|
+
"```",
|
|
382
391
|
file.content.trimEnd(),
|
|
383
|
-
|
|
384
|
-
].join(
|
|
392
|
+
"```",
|
|
393
|
+
].join("\n");
|
|
385
394
|
return {
|
|
386
395
|
index: index + 1,
|
|
387
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,33 +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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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",
|
|
17
19
|
};
|
|
18
20
|
export function resolveGeminiModelId(modelName) {
|
|
19
21
|
// Map our logical Gemini names to the exact model ids expected by the SDK.
|
|
20
22
|
return MODEL_ID_MAP[modelName] ?? modelName;
|
|
21
23
|
}
|
|
22
|
-
export function createGeminiClient(apiKey, modelName =
|
|
24
|
+
export function createGeminiClient(apiKey, modelName = "gemini-3-pro", resolvedModelId) {
|
|
23
25
|
const modelId = resolvedModelId ?? resolveGeminiModelId(modelName);
|
|
24
26
|
const genAI = new GoogleGenAI({ apiKey });
|
|
25
27
|
const adaptBodyToGemini = (body) => {
|
|
26
28
|
const contents = body.input.map((inputItem) => ({
|
|
27
|
-
role: inputItem.role ===
|
|
29
|
+
role: inputItem.role === "user" ? "user" : "model",
|
|
28
30
|
parts: inputItem.content
|
|
29
31
|
.map((contentPart) => {
|
|
30
|
-
if (contentPart.type ===
|
|
32
|
+
if (contentPart.type === "input_text") {
|
|
31
33
|
return { text: contentPart.text };
|
|
32
34
|
}
|
|
33
35
|
return null;
|
|
@@ -36,7 +38,7 @@ export function createGeminiClient(apiKey, modelName = 'gemini-3-pro', resolvedM
|
|
|
36
38
|
}));
|
|
37
39
|
const tools = body.tools
|
|
38
40
|
?.map((tool) => {
|
|
39
|
-
if (tool.type ===
|
|
41
|
+
if (tool.type === "web_search_preview") {
|
|
40
42
|
return {
|
|
41
43
|
googleSearch: {},
|
|
42
44
|
};
|
|
@@ -63,7 +65,7 @@ export function createGeminiClient(apiKey, modelName = 'gemini-3-pro', resolvedM
|
|
|
63
65
|
},
|
|
64
66
|
];
|
|
65
67
|
const systemInstruction = body.instructions
|
|
66
|
-
? { role:
|
|
68
|
+
? { role: "system", parts: [{ text: body.instructions }] }
|
|
67
69
|
: undefined;
|
|
68
70
|
return {
|
|
69
71
|
model: modelId,
|
|
@@ -83,18 +85,19 @@ export function createGeminiClient(apiKey, modelName = 'gemini-3-pro', resolvedM
|
|
|
83
85
|
candidate.content?.parts?.forEach((part) => {
|
|
84
86
|
if (part.text) {
|
|
85
87
|
outputText.push(part.text);
|
|
86
|
-
output.push({ type:
|
|
88
|
+
output.push({ type: "text", text: part.text });
|
|
87
89
|
}
|
|
88
90
|
});
|
|
89
91
|
});
|
|
90
92
|
const usage = {
|
|
91
93
|
input_tokens: geminiResponse.usageMetadata?.promptTokenCount || 0,
|
|
92
94
|
output_tokens: geminiResponse.usageMetadata?.candidatesTokenCount || 0,
|
|
93
|
-
total_tokens: (geminiResponse.usageMetadata?.promptTokenCount || 0) +
|
|
95
|
+
total_tokens: (geminiResponse.usageMetadata?.promptTokenCount || 0) +
|
|
96
|
+
(geminiResponse.usageMetadata?.candidatesTokenCount || 0),
|
|
94
97
|
};
|
|
95
98
|
return {
|
|
96
99
|
id: geminiResponse.responseId ?? `gemini-${Date.now()}`,
|
|
97
|
-
status:
|
|
100
|
+
status: "completed",
|
|
98
101
|
output_text: outputText,
|
|
99
102
|
output,
|
|
100
103
|
usage,
|
|
@@ -108,15 +111,15 @@ export function createGeminiClient(apiKey, modelName = 'gemini-3-pro', resolvedM
|
|
|
108
111
|
};
|
|
109
112
|
return {
|
|
110
113
|
id: responseId ?? `gemini-${Date.now()}`,
|
|
111
|
-
status:
|
|
114
|
+
status: "completed",
|
|
112
115
|
output_text: [text],
|
|
113
|
-
output: [{ type:
|
|
116
|
+
output: [{ type: "text", text }],
|
|
114
117
|
usage,
|
|
115
118
|
};
|
|
116
119
|
};
|
|
117
120
|
const enrichGeminiError = (error) => {
|
|
118
121
|
const message = error instanceof Error ? error.message : String(error);
|
|
119
|
-
if (message.includes(
|
|
122
|
+
if (message.includes("404")) {
|
|
120
123
|
return new Error(`Gemini model not available to this API key/region. Confirm preview access and model ID (${modelId}). Original: ${message}`);
|
|
121
124
|
}
|
|
122
125
|
return error instanceof Error ? error : new Error(message);
|
|
@@ -126,7 +129,7 @@ export function createGeminiClient(apiKey, modelName = 'gemini-3-pro', resolvedM
|
|
|
126
129
|
stream: (body) => {
|
|
127
130
|
const geminiBody = adaptBodyToGemini(body);
|
|
128
131
|
let finalResponsePromise = null;
|
|
129
|
-
let aggregatedText =
|
|
132
|
+
let aggregatedText = "";
|
|
130
133
|
let lastUsage;
|
|
131
134
|
let responseId;
|
|
132
135
|
async function* iterator() {
|
|
@@ -141,7 +144,7 @@ export function createGeminiClient(apiKey, modelName = 'gemini-3-pro', resolvedM
|
|
|
141
144
|
const text = chunk.text;
|
|
142
145
|
if (text) {
|
|
143
146
|
aggregatedText += text;
|
|
144
|
-
yield { type:
|
|
147
|
+
yield { type: "chunk", delta: text };
|
|
145
148
|
}
|
|
146
149
|
if (chunk.usageMetadata) {
|
|
147
150
|
lastUsage = chunk.usageMetadata;
|
|
@@ -160,13 +163,14 @@ export function createGeminiClient(apiKey, modelName = 'gemini-3-pro', resolvedM
|
|
|
160
163
|
if (!finalResponsePromise) {
|
|
161
164
|
// In case the user calls finalResponse before iterating, we need to consume the stream
|
|
162
165
|
// This is a bit edge-casey but safe.
|
|
163
|
-
for await (const _ of generator) {
|
|
166
|
+
for await (const _ of generator) {
|
|
167
|
+
}
|
|
164
168
|
}
|
|
165
169
|
if (!finalResponsePromise) {
|
|
166
|
-
throw new Error(
|
|
170
|
+
throw new Error("Response promise not initialized");
|
|
167
171
|
}
|
|
168
172
|
return finalResponsePromise;
|
|
169
|
-
}
|
|
173
|
+
},
|
|
170
174
|
};
|
|
171
175
|
},
|
|
172
176
|
create: async (body) => {
|
|
@@ -183,8 +187,8 @@ export function createGeminiClient(apiKey, modelName = 'gemini-3-pro', resolvedM
|
|
|
183
187
|
retrieve: async (id) => {
|
|
184
188
|
return {
|
|
185
189
|
id,
|
|
186
|
-
status:
|
|
187
|
-
error: { message:
|
|
190
|
+
status: "error",
|
|
191
|
+
error: { message: "Retrieve by ID not supported for Gemini API yet." },
|
|
188
192
|
};
|
|
189
193
|
},
|
|
190
194
|
},
|