@steipete/oracle 0.11.1 → 0.12.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/README.md +55 -10
- package/dist/bin/oracle-cli.js +440 -98
- package/dist/src/browser/actions/modelSelection.js +53 -15
- package/dist/src/browser/actions/navigation.js +5 -3
- package/dist/src/browser/actions/promptComposer.js +75 -18
- package/dist/src/browser/actions/thinkingTime.js +23 -8
- package/dist/src/browser/constants.js +1 -1
- package/dist/src/browser/index.js +41 -7
- package/dist/src/browser/manualLoginProfile.js +54 -0
- package/dist/src/browser/projectSourcesRunner.js +16 -5
- package/dist/src/browser/prompt.js +56 -37
- package/dist/src/browser/sessionRunner.js +72 -1
- package/dist/src/browser/utils.js +1 -47
- package/dist/src/browser/zipBundle.js +152 -0
- package/dist/src/cli/browserConfig.js +13 -11
- package/dist/src/cli/browserDefaults.js +2 -1
- package/dist/src/cli/docsCheck.js +186 -0
- package/dist/src/cli/engine.js +11 -4
- package/dist/src/cli/options.js +12 -6
- package/dist/src/cli/perfTrace.js +242 -0
- package/dist/src/cli/promptRequirement.js +2 -0
- package/dist/src/cli/providerDoctor.js +85 -0
- package/dist/src/cli/runOptions.js +46 -16
- package/dist/src/cli/sessionDisplay.js +39 -4
- package/dist/src/cli/sessionLifecycle.js +38 -0
- package/dist/src/cli/sessionRunner.js +228 -3
- package/dist/src/cli/sessionTable.js +2 -1
- package/dist/src/duration.js +47 -0
- package/dist/src/mcp/tools/consult.js +19 -3
- package/dist/src/mcp/types.js +1 -0
- package/dist/src/mcp/utils.js +4 -1
- package/dist/src/oracle/baseUrl.js +17 -0
- package/dist/src/oracle/client.js +1 -22
- package/dist/src/oracle/config.js +17 -4
- package/dist/src/oracle/gemini.js +2 -22
- package/dist/src/oracle/geminiModels.js +21 -0
- package/dist/src/oracle/modelResolver.js +7 -1
- package/dist/src/oracle/multiModelRunner.js +20 -2
- package/dist/src/oracle/providerFailures.js +204 -0
- package/dist/src/oracle/providerRoutePlan.js +281 -0
- package/dist/src/oracle/providerRouting.js +92 -0
- package/dist/src/oracle/run.js +157 -54
- package/dist/src/oracle.js +1 -0
- package/dist/src/remote/client.js +8 -0
- package/dist/src/remote/server.js +26 -0
- package/dist/src/sessionManager.js +5 -1
- package/package.json +3 -1
package/dist/src/oracle/run.js
CHANGED
|
@@ -4,7 +4,7 @@ import fs from "node:fs/promises";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import process from "node:process";
|
|
6
6
|
import { performance } from "node:perf_hooks";
|
|
7
|
-
import { DEFAULT_SYSTEM_PROMPT,
|
|
7
|
+
import { DEFAULT_SYSTEM_PROMPT, TOKENIZER_OPTIONS } from "./config.js";
|
|
8
8
|
import { readFiles } from "./files.js";
|
|
9
9
|
import { buildPrompt, buildRequestBody } from "./request.js";
|
|
10
10
|
import { estimateRequestTokens } from "./tokenEstimate.js";
|
|
@@ -12,7 +12,8 @@ import { formatElapsed } from "./format.js";
|
|
|
12
12
|
import { formatFinishLine } from "./finishLine.js";
|
|
13
13
|
import { getFileTokenStats, printFileTokenStats } from "./tokenStats.js";
|
|
14
14
|
import { OracleResponseError, OracleTransportError, PromptValidationError, describeTransportError, toTransportError, } from "./errors.js";
|
|
15
|
-
import {
|
|
15
|
+
import { isCustomBaseUrl } from "./baseUrl.js";
|
|
16
|
+
import { createDefaultClientFactory } from "./client.js";
|
|
16
17
|
import { formatBaseUrlForLog, maskApiKey } from "./logging.js";
|
|
17
18
|
import { startHeartbeat } from "../heartbeat.js";
|
|
18
19
|
import { startOscProgress } from "./oscProgress.js";
|
|
@@ -24,15 +25,61 @@ import { createMarkdownStreamer } from "markdansi";
|
|
|
24
25
|
import { executeBackgroundResponse } from "./background.js";
|
|
25
26
|
import { formatTokenEstimate, formatTokenValue, resolvePreviewMode } from "./runUtils.js";
|
|
26
27
|
import { estimateUsdCost } from "tokentally";
|
|
27
|
-
import { defaultOpenRouterBaseUrl,
|
|
28
|
+
import { defaultOpenRouterBaseUrl, isOpenRouterBaseUrl, isProModel, resolveModelConfig, normalizeOpenRouterBaseUrl, } from "./modelResolver.js";
|
|
29
|
+
import { validateProviderRouting } from "./providerRouting.js";
|
|
28
30
|
const isStdoutTty = process.stdout.isTTY && chalk.level > 0;
|
|
29
31
|
const dim = (text) => (isStdoutTty ? kleur.dim(text) : text);
|
|
30
32
|
// Default timeout for non-pro API runs (fast models) — give them up to 120s.
|
|
31
33
|
const DEFAULT_TIMEOUT_NON_PRO_MS = 120_000;
|
|
32
34
|
const DEFAULT_TIMEOUT_PRO_MS = 60 * 60 * 1000;
|
|
35
|
+
const DEFAULT_PROVIDER_HOSTS = {
|
|
36
|
+
anthropic: "api.anthropic.com",
|
|
37
|
+
google: "generativelanguage.googleapis.com",
|
|
38
|
+
openai: "api.openai.com",
|
|
39
|
+
xai: "api.x.ai",
|
|
40
|
+
};
|
|
33
41
|
const defaultWait = (ms) => new Promise((resolve) => {
|
|
34
42
|
setTimeout(resolve, ms);
|
|
35
43
|
});
|
|
44
|
+
function formatRouteTargetForLog(raw, fallbackHost = "") {
|
|
45
|
+
if (!raw)
|
|
46
|
+
return fallbackHost;
|
|
47
|
+
try {
|
|
48
|
+
const parsed = new URL(raw);
|
|
49
|
+
const segments = parsed.pathname.split("/").filter(Boolean);
|
|
50
|
+
let path = "";
|
|
51
|
+
if (segments.length > 0) {
|
|
52
|
+
path = `/${segments[0]}`;
|
|
53
|
+
if (segments.length > 1) {
|
|
54
|
+
path += "/...";
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return `${parsed.host}${path}`;
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
const formatted = formatBaseUrlForLog(raw).replace(/^https?:\/\//u, "");
|
|
61
|
+
return formatted || fallbackHost;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function formatProviderRouteLogLine({ provider, baseUrl, openRouterFallback, isAzureOpenAI, azureEndpoint, azureDeploymentName, envVar, }) {
|
|
65
|
+
if (isAzureOpenAI) {
|
|
66
|
+
return `Provider: Azure OpenAI | endpoint: ${formatRouteTargetForLog(azureEndpoint)} | deployment: ${azureDeploymentName || "none"} | key: ${envVar}`;
|
|
67
|
+
}
|
|
68
|
+
const isOpenRouter = isOpenRouterBaseUrl(baseUrl) || openRouterFallback;
|
|
69
|
+
const routeProvider = isOpenRouter
|
|
70
|
+
? "OpenRouter"
|
|
71
|
+
: baseUrl && isCustomBaseUrl(baseUrl)
|
|
72
|
+
? "OpenAI-compatible"
|
|
73
|
+
: provider === "anthropic"
|
|
74
|
+
? "Anthropic"
|
|
75
|
+
: provider === "google"
|
|
76
|
+
? "Google Gemini"
|
|
77
|
+
: provider === "xai"
|
|
78
|
+
? "xAI"
|
|
79
|
+
: "OpenAI";
|
|
80
|
+
const fallbackHost = DEFAULT_PROVIDER_HOSTS[provider] ?? DEFAULT_PROVIDER_HOSTS.openai;
|
|
81
|
+
return `Provider: ${routeProvider} | base: ${formatRouteTargetForLog(baseUrl, fallbackHost)} | key: ${envVar}`;
|
|
82
|
+
}
|
|
36
83
|
export async function runOracle(options, deps = {}) {
|
|
37
84
|
const { apiKey: optionsApiKey = options.apiKey, cwd = process.cwd(), fs: fsModule = createFsAdapter(fs), log = console.log, write: sinkWrite = (_text) => true, allowStdout = true, stdoutWrite: stdoutWriteDep, now = () => performance.now(), clientFactory = createDefaultClientFactory(), client, wait = defaultWait, } = deps;
|
|
38
85
|
const stdoutWrite = allowStdout
|
|
@@ -42,32 +89,64 @@ export async function runOracle(options, deps = {}) {
|
|
|
42
89
|
const resolvedXaiBaseUrl = process.env.XAI_BASE_URL?.trim() || "https://api.x.ai/v1";
|
|
43
90
|
const openRouterApiKey = process.env.OPENROUTER_API_KEY?.trim();
|
|
44
91
|
const defaultOpenRouterBase = defaultOpenRouterBaseUrl();
|
|
45
|
-
const
|
|
46
|
-
const
|
|
92
|
+
const previewMode = resolvePreviewMode(options.previewMode ?? options.preview);
|
|
93
|
+
const isPreview = Boolean(previewMode);
|
|
94
|
+
const providerMode = options.provider ?? "auto";
|
|
95
|
+
const routing = validateProviderRouting({
|
|
96
|
+
model: options.model,
|
|
97
|
+
providerMode,
|
|
98
|
+
azure: options.azure,
|
|
99
|
+
}, {
|
|
100
|
+
onAzureDeploymentMissing: (state) => {
|
|
101
|
+
if (!isPreview && !options.suppressHeader) {
|
|
102
|
+
log(dim(`Provider: Azure OpenAI | endpoint: ${formatRouteTargetForLog(state.azureEndpoint)} | deployment: none | key: ${optionsApiKey ? "apiKey option" : "AZURE_OPENAI_API_KEY|OPENAI_API_KEY"}`));
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
const { provider, isAzureOpenAI, azureEndpoint, azureDeploymentName } = routing;
|
|
47
107
|
const hasOpenAIKey = Boolean(optionsApiKey) ||
|
|
48
108
|
Boolean(process.env.OPENAI_API_KEY) ||
|
|
49
|
-
Boolean(process.env.AZURE_OPENAI_API_KEY && options.azure?.endpoint);
|
|
109
|
+
Boolean(providerMode !== "openai" && process.env.AZURE_OPENAI_API_KEY && options.azure?.endpoint);
|
|
50
110
|
const hasAnthropicKey = Boolean(optionsApiKey) || Boolean(process.env.ANTHROPIC_API_KEY);
|
|
51
111
|
const hasGeminiKey = Boolean(optionsApiKey) || Boolean(process.env.GEMINI_API_KEY);
|
|
52
112
|
const hasXaiKey = Boolean(optionsApiKey) || Boolean(process.env.XAI_API_KEY);
|
|
53
113
|
let baseUrl = options.baseUrl?.trim();
|
|
114
|
+
const providerQualifiedOpenRouterCandidate = !isAzureOpenAI && providerMode !== "openai" && options.model.includes("/");
|
|
115
|
+
if (baseUrl &&
|
|
116
|
+
providerQualifiedOpenRouterCandidate &&
|
|
117
|
+
!isOpenRouterBaseUrl(baseUrl) &&
|
|
118
|
+
!isCustomBaseUrl(baseUrl)) {
|
|
119
|
+
baseUrl = undefined;
|
|
120
|
+
}
|
|
54
121
|
if (!baseUrl) {
|
|
122
|
+
let envBaseUrl;
|
|
55
123
|
if (options.model.startsWith("grok")) {
|
|
56
|
-
|
|
124
|
+
envBaseUrl = resolvedXaiBaseUrl;
|
|
57
125
|
}
|
|
58
126
|
else if (provider === "anthropic") {
|
|
59
|
-
|
|
127
|
+
envBaseUrl = process.env.ANTHROPIC_BASE_URL?.trim();
|
|
60
128
|
}
|
|
61
129
|
else {
|
|
62
|
-
|
|
130
|
+
envBaseUrl = process.env.OPENAI_BASE_URL?.trim();
|
|
131
|
+
}
|
|
132
|
+
if (!providerQualifiedOpenRouterCandidate || (envBaseUrl && isCustomBaseUrl(envBaseUrl))) {
|
|
133
|
+
baseUrl = envBaseUrl;
|
|
63
134
|
}
|
|
64
135
|
}
|
|
65
|
-
const providerKeyMissing =
|
|
66
|
-
(
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
136
|
+
const providerKeyMissing = !isAzureOpenAI &&
|
|
137
|
+
(providerMode === "openai"
|
|
138
|
+
? !hasOpenAIKey
|
|
139
|
+
: (provider === "openai" && !hasOpenAIKey) ||
|
|
140
|
+
(provider === "anthropic" && !hasAnthropicKey) ||
|
|
141
|
+
(provider === "google" && !hasGeminiKey) ||
|
|
142
|
+
(provider === "xai" && !hasXaiKey) ||
|
|
143
|
+
provider === "other");
|
|
144
|
+
const providerQualifiedOpenRouterRoute = providerQualifiedOpenRouterCandidate && !baseUrl;
|
|
145
|
+
const openRouterFallback = !baseUrl &&
|
|
146
|
+
(providerQualifiedOpenRouterRoute ||
|
|
147
|
+
(providerMode !== "openai" &&
|
|
148
|
+
providerKeyMissing &&
|
|
149
|
+
(provider === "other" || Boolean(openRouterApiKey))));
|
|
71
150
|
if (!baseUrl || openRouterFallback) {
|
|
72
151
|
if (openRouterFallback) {
|
|
73
152
|
baseUrl = defaultOpenRouterBase;
|
|
@@ -81,20 +160,24 @@ export async function runOracle(options, deps = {}) {
|
|
|
81
160
|
log(dim(`[verbose] ${message}`));
|
|
82
161
|
}
|
|
83
162
|
};
|
|
84
|
-
const previewMode = resolvePreviewMode(options.previewMode ?? options.preview);
|
|
85
|
-
const isPreview = Boolean(previewMode);
|
|
86
|
-
const isAzureOpenAI = Boolean(options.azure?.endpoint);
|
|
87
163
|
const getApiKeyForModel = (model) => {
|
|
164
|
+
if (isAzureOpenAI) {
|
|
165
|
+
if (optionsApiKey)
|
|
166
|
+
return { key: optionsApiKey, source: "apiKey option" };
|
|
167
|
+
const key = process.env.AZURE_OPENAI_API_KEY ?? process.env.OPENAI_API_KEY;
|
|
168
|
+
return { key, source: "AZURE_OPENAI_API_KEY|OPENAI_API_KEY" };
|
|
169
|
+
}
|
|
170
|
+
if (providerMode === "openai") {
|
|
171
|
+
if (optionsApiKey)
|
|
172
|
+
return { key: optionsApiKey, source: "apiKey option" };
|
|
173
|
+
return { key: process.env.OPENAI_API_KEY, source: "OPENAI_API_KEY" };
|
|
174
|
+
}
|
|
88
175
|
if (isOpenRouterBaseUrl(baseUrl) || openRouterFallback) {
|
|
89
176
|
return { key: optionsApiKey ?? openRouterApiKey, source: "OPENROUTER_API_KEY" };
|
|
90
177
|
}
|
|
91
178
|
if (typeof model === "string" && model.startsWith("gpt")) {
|
|
92
179
|
if (optionsApiKey)
|
|
93
180
|
return { key: optionsApiKey, source: "apiKey option" };
|
|
94
|
-
if (isAzureOpenAI) {
|
|
95
|
-
const key = process.env.AZURE_OPENAI_API_KEY ?? process.env.OPENAI_API_KEY;
|
|
96
|
-
return { key, source: "AZURE_OPENAI_API_KEY|OPENAI_API_KEY" };
|
|
97
|
-
}
|
|
98
181
|
return { key: process.env.OPENAI_API_KEY, source: "OPENAI_API_KEY" };
|
|
99
182
|
}
|
|
100
183
|
if (typeof model === "string" && model.startsWith("gemini")) {
|
|
@@ -114,19 +197,21 @@ export async function runOracle(options, deps = {}) {
|
|
|
114
197
|
const apiKeyResult = getApiKeyForModel(options.model);
|
|
115
198
|
const apiKey = apiKeyResult.key;
|
|
116
199
|
if (!apiKey) {
|
|
117
|
-
const envVar =
|
|
118
|
-
? "
|
|
119
|
-
:
|
|
120
|
-
?
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
200
|
+
const envVar = isAzureOpenAI
|
|
201
|
+
? "AZURE_OPENAI_API_KEY (or OPENAI_API_KEY)"
|
|
202
|
+
: providerMode === "openai"
|
|
203
|
+
? "OPENAI_API_KEY"
|
|
204
|
+
: isOpenRouterBaseUrl(baseUrl) || openRouterFallback
|
|
205
|
+
? "OPENROUTER_API_KEY"
|
|
206
|
+
: options.model.startsWith("gpt")
|
|
207
|
+
? "OPENAI_API_KEY"
|
|
208
|
+
: options.model.startsWith("gemini")
|
|
209
|
+
? "GEMINI_API_KEY"
|
|
210
|
+
: options.model.startsWith("claude")
|
|
211
|
+
? "ANTHROPIC_API_KEY"
|
|
212
|
+
: options.model.startsWith("grok")
|
|
213
|
+
? "XAI_API_KEY"
|
|
214
|
+
: "OPENROUTER_API_KEY";
|
|
130
215
|
const browserModeHint = options.model.startsWith("gpt")
|
|
131
216
|
? ' If you have a ChatGPT Pro subscription, retry with --engine browser (or MCP engine:"browser" / preset:"chatgpt-pro-heavy"); browser mode uses your signed-in ChatGPT session instead of an API key.'
|
|
132
217
|
: "";
|
|
@@ -199,14 +284,17 @@ export async function runOracle(options, deps = {}) {
|
|
|
199
284
|
: DEFAULT_TIMEOUT_NON_PRO_MS / 1000
|
|
200
285
|
: options.timeoutSeconds;
|
|
201
286
|
const timeoutMs = timeoutSeconds * 1000;
|
|
202
|
-
const
|
|
287
|
+
const httpTimeoutMs = typeof options.httpTimeoutMs === "number" &&
|
|
288
|
+
Number.isFinite(options.httpTimeoutMs) &&
|
|
289
|
+
options.httpTimeoutMs > 0
|
|
290
|
+
? options.httpTimeoutMs
|
|
291
|
+
: timeoutMs;
|
|
203
292
|
// Track the concrete model id we dispatch to (especially for Gemini preview aliases)
|
|
204
|
-
const effectiveModelId =
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
: (modelConfig.apiModel ?? modelConfig.model));
|
|
293
|
+
const effectiveModelId = azureDeploymentName ??
|
|
294
|
+
options.effectiveModelId ??
|
|
295
|
+
(options.model.startsWith("gemini")
|
|
296
|
+
? resolveGeminiModelId(options.model)
|
|
297
|
+
: (modelConfig.apiModel ?? modelConfig.model));
|
|
210
298
|
if (!isPreview && options.previousResponseId) {
|
|
211
299
|
log(dim(`Continuing from response ${options.previousResponseId}`));
|
|
212
300
|
}
|
|
@@ -238,6 +326,15 @@ export async function runOracle(options, deps = {}) {
|
|
|
238
326
|
if (!isPreview) {
|
|
239
327
|
if (!options.suppressHeader) {
|
|
240
328
|
log(headerLine);
|
|
329
|
+
log(dim(formatProviderRouteLogLine({
|
|
330
|
+
provider,
|
|
331
|
+
baseUrl,
|
|
332
|
+
openRouterFallback,
|
|
333
|
+
isAzureOpenAI,
|
|
334
|
+
azureEndpoint,
|
|
335
|
+
azureDeploymentName,
|
|
336
|
+
envVar,
|
|
337
|
+
})));
|
|
241
338
|
}
|
|
242
339
|
const maskedKey = maskApiKey(apiKey);
|
|
243
340
|
if (maskedKey && options.verbose) {
|
|
@@ -249,9 +346,6 @@ export async function runOracle(options, deps = {}) {
|
|
|
249
346
|
effectiveModelId === "gpt-5.5-pro") {
|
|
250
347
|
log(dim(`Note: \`${modelConfig.model}\` is a stable CLI alias; OpenAI API uses \`gpt-5.5-pro\`.`));
|
|
251
348
|
}
|
|
252
|
-
if (baseUrl) {
|
|
253
|
-
log(dim(`Base URL: ${formatBaseUrlForLog(baseUrl)}`));
|
|
254
|
-
}
|
|
255
349
|
if (effectiveModelId !== modelConfig.model) {
|
|
256
350
|
log(dim(`Resolved model: ${modelConfig.model} → ${effectiveModelId}`));
|
|
257
351
|
}
|
|
@@ -269,6 +363,11 @@ export async function runOracle(options, deps = {}) {
|
|
|
269
363
|
if (isLongRunningModel) {
|
|
270
364
|
log(dim("This model can take up to 60 minutes (usually replies much faster)."));
|
|
271
365
|
}
|
|
366
|
+
if (options.verbose || isLongRunningModel || httpTimeoutMs < timeoutMs) {
|
|
367
|
+
const timeoutLine = `Timeouts | overall: ${formatElapsed(timeoutMs)} | transport: ${formatElapsed(httpTimeoutMs)}`;
|
|
368
|
+
const timeoutNote = httpTimeoutMs < timeoutMs ? " | note: transport can fail before overall timeout" : "";
|
|
369
|
+
log(dim(`${timeoutLine}${timeoutNote}`));
|
|
370
|
+
}
|
|
272
371
|
if (options.verbose || isLongRunningModel) {
|
|
273
372
|
log(dim("Press Ctrl+C to cancel."));
|
|
274
373
|
}
|
|
@@ -300,25 +399,29 @@ export async function runOracle(options, deps = {}) {
|
|
|
300
399
|
inputTokenBudget,
|
|
301
400
|
};
|
|
302
401
|
}
|
|
303
|
-
const proxyCompatibleBaseUrl = baseUrl && (isOpenRouterBaseUrl(baseUrl) || isCustomBaseUrl(baseUrl))
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
402
|
+
const proxyCompatibleBaseUrl = !isAzureOpenAI && baseUrl && (isOpenRouterBaseUrl(baseUrl) || isCustomBaseUrl(baseUrl))
|
|
403
|
+
? baseUrl
|
|
404
|
+
: undefined;
|
|
405
|
+
const apiEndpoint = isAzureOpenAI
|
|
406
|
+
? undefined
|
|
407
|
+
: modelConfig.model.startsWith("gemini")
|
|
307
408
|
? proxyCompatibleBaseUrl
|
|
308
|
-
:
|
|
309
|
-
?
|
|
310
|
-
:
|
|
409
|
+
: proxyCompatibleBaseUrl
|
|
410
|
+
? proxyCompatibleBaseUrl
|
|
411
|
+
: modelConfig.model.startsWith("claude")
|
|
412
|
+
? (process.env.ANTHROPIC_BASE_URL ?? baseUrl)
|
|
413
|
+
: baseUrl;
|
|
311
414
|
const clientInstance = client ??
|
|
312
415
|
clientFactory(apiKey, {
|
|
313
416
|
baseUrl: apiEndpoint,
|
|
314
|
-
azure: options.azure,
|
|
417
|
+
azure: isAzureOpenAI ? options.azure : undefined,
|
|
315
418
|
model: options.model,
|
|
316
419
|
resolvedModelId: modelConfig.model.startsWith("claude")
|
|
317
420
|
? resolveClaudeModelId(effectiveModelId)
|
|
318
421
|
: modelConfig.model.startsWith("gemini")
|
|
319
422
|
? resolveGeminiModelId(effectiveModelId)
|
|
320
423
|
: effectiveModelId,
|
|
321
|
-
httpTimeoutMs
|
|
424
|
+
httpTimeoutMs,
|
|
322
425
|
});
|
|
323
426
|
logVerbose("Dispatching request to API...");
|
|
324
427
|
if (options.verbose) {
|
package/dist/src/oracle.js
CHANGED
|
@@ -10,3 +10,4 @@ export { OracleResponseError, OracleTransportError, OracleUserError, FileValidat
|
|
|
10
10
|
export { createDefaultClientFactory } from "./oracle/client.js";
|
|
11
11
|
export { runOracle, extractTextOutput } from "./oracle/run.js";
|
|
12
12
|
export { resolveGeminiModelId } from "./oracle/gemini.js";
|
|
13
|
+
export { classifyProviderFailure } from "./oracle/providerFailures.js";
|
|
@@ -8,10 +8,18 @@ export function createRemoteBrowserExecutor({ host, token }) {
|
|
|
8
8
|
const payload = {
|
|
9
9
|
prompt: options.prompt,
|
|
10
10
|
attachments: await serializeAttachments(options.attachments ?? []),
|
|
11
|
+
fallbackSubmission: options.fallbackSubmission
|
|
12
|
+
? {
|
|
13
|
+
prompt: options.fallbackSubmission.prompt,
|
|
14
|
+
attachments: await serializeAttachments(options.fallbackSubmission.attachments ?? []),
|
|
15
|
+
}
|
|
16
|
+
: undefined,
|
|
11
17
|
browserConfig: options.config ?? {},
|
|
12
18
|
options: {
|
|
13
19
|
heartbeatIntervalMs: options.heartbeatIntervalMs,
|
|
14
20
|
verbose: options.verbose,
|
|
21
|
+
sessionId: options.sessionId,
|
|
22
|
+
followUpPrompts: options.followUpPrompts,
|
|
15
23
|
},
|
|
16
24
|
};
|
|
17
25
|
const body = Buffer.from(JSON.stringify(payload));
|
|
@@ -119,6 +119,7 @@ export async function createRemoteServer(options = {}, deps = {}) {
|
|
|
119
119
|
res.write(`${JSON.stringify(event)}\n`);
|
|
120
120
|
};
|
|
121
121
|
const attachments = [];
|
|
122
|
+
let fallbackSubmission;
|
|
122
123
|
try {
|
|
123
124
|
const attachmentsPayload = Array.isArray(payload.attachments) ? payload.attachments : [];
|
|
124
125
|
for (const [index, attachment] of attachmentsPayload.entries()) {
|
|
@@ -131,6 +132,28 @@ export async function createRemoteServer(options = {}, deps = {}) {
|
|
|
131
132
|
sizeBytes: attachment.sizeBytes,
|
|
132
133
|
});
|
|
133
134
|
}
|
|
135
|
+
if (payload.fallbackSubmission) {
|
|
136
|
+
const fallbackAttachmentDir = path.join(runDir, "fallback-attachments");
|
|
137
|
+
await mkdir(fallbackAttachmentDir, { recursive: true });
|
|
138
|
+
const fallbackAttachments = [];
|
|
139
|
+
const fallbackPayload = Array.isArray(payload.fallbackSubmission.attachments)
|
|
140
|
+
? payload.fallbackSubmission.attachments
|
|
141
|
+
: [];
|
|
142
|
+
for (const [index, attachment] of fallbackPayload.entries()) {
|
|
143
|
+
const safeName = sanitizeName(attachment.fileName ?? `fallback-attachment-${index + 1}`);
|
|
144
|
+
const filePath = path.join(fallbackAttachmentDir, safeName);
|
|
145
|
+
await writeFile(filePath, Buffer.from(attachment.contentBase64, "base64"));
|
|
146
|
+
fallbackAttachments.push({
|
|
147
|
+
path: filePath,
|
|
148
|
+
displayPath: attachment.displayPath,
|
|
149
|
+
sizeBytes: attachment.sizeBytes,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
fallbackSubmission = {
|
|
153
|
+
prompt: payload.fallbackSubmission.prompt,
|
|
154
|
+
attachments: fallbackAttachments,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
134
157
|
// Reuse the existing browser logger surface so clients see the same log stream.
|
|
135
158
|
const automationLogger = ((message) => {
|
|
136
159
|
if (typeof message === "string") {
|
|
@@ -159,10 +182,13 @@ export async function createRemoteServer(options = {}, deps = {}) {
|
|
|
159
182
|
const result = await runBrowser({
|
|
160
183
|
prompt: payload.prompt,
|
|
161
184
|
attachments,
|
|
185
|
+
fallbackSubmission,
|
|
162
186
|
config: payload.browserConfig,
|
|
163
187
|
log: automationLogger,
|
|
164
188
|
heartbeatIntervalMs: payload.options.heartbeatIntervalMs,
|
|
165
189
|
verbose: payload.options.verbose,
|
|
190
|
+
sessionId: payload.options.sessionId,
|
|
191
|
+
followUpPrompts: payload.options.followUpPrompts,
|
|
166
192
|
});
|
|
167
193
|
sendEvent({ type: "result", result: sanitizeResult(result) });
|
|
168
194
|
logger(`[serve] Run ${runId} completed in ${Date.now() - runStartedAt}ms`);
|
|
@@ -2,7 +2,8 @@ import path from "node:path";
|
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import { createWriteStream } from "node:fs";
|
|
4
4
|
import net from "node:net";
|
|
5
|
-
import { DEFAULT_MODEL
|
|
5
|
+
import { DEFAULT_MODEL } from "./oracle/config.js";
|
|
6
|
+
import { formatElapsed } from "./oracle/format.js";
|
|
6
7
|
import { safeModelSlug } from "./oracle/modelResolver.js";
|
|
7
8
|
import { getOracleHomeDir } from "./oracleHome.js";
|
|
8
9
|
export function getSessionsDir() {
|
|
@@ -199,8 +200,10 @@ export async function initializeSession(options, cwd, notifications, baseSlugOve
|
|
|
199
200
|
browserAttachments: options.browserAttachments,
|
|
200
201
|
browserInlineFiles: options.browserInlineFiles,
|
|
201
202
|
browserBundleFiles: options.browserBundleFiles,
|
|
203
|
+
browserBundleFormat: options.browserBundleFormat,
|
|
202
204
|
background: options.background,
|
|
203
205
|
search: options.search,
|
|
206
|
+
provider: options.provider,
|
|
204
207
|
baseUrl: options.baseUrl,
|
|
205
208
|
azure: options.azure,
|
|
206
209
|
timeoutSeconds: options.timeoutSeconds,
|
|
@@ -208,6 +211,7 @@ export async function initializeSession(options, cwd, notifications, baseSlugOve
|
|
|
208
211
|
zombieTimeoutMs: options.zombieTimeoutMs,
|
|
209
212
|
zombieUseLastActivity: options.zombieUseLastActivity,
|
|
210
213
|
writeOutputPath: options.writeOutputPath,
|
|
214
|
+
partialMode: options.partialMode,
|
|
211
215
|
waitPreference: options.waitPreference,
|
|
212
216
|
youtube: options.youtube,
|
|
213
217
|
generateImage: options.generateImage,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@steipete/oracle",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "CLI wrapper around OpenAI Responses API with GPT-5.5 Pro, GPT-5.5, GPT-5.4, GPT-5.2, GPT-5.1, and GPT-5.1 Codex high reasoning modes.",
|
|
5
5
|
"keywords": [],
|
|
6
6
|
"homepage": "https://github.com/steipete/oracle#readme",
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"main": "dist/bin/oracle-cli.js",
|
|
31
31
|
"scripts": {
|
|
32
32
|
"docs:list": "tsx scripts/docs-list.ts",
|
|
33
|
+
"docs:check": "node --no-deprecation --import tsx bin/oracle-cli.ts docs check",
|
|
33
34
|
"docs:site": "node scripts/build-docs-site.mjs",
|
|
34
35
|
"build": "tsgo -p tsconfig.build.json && pnpm run build:vendor",
|
|
35
36
|
"build:vendor": "node -e \"const fs=require('fs'); const path=require('path'); const vendorRoot=path.join('dist','vendor'); fs.rmSync(vendorRoot,{recursive:true,force:true}); const vendors=[['oracle-notifier']]; vendors.forEach(([name])=>{const src=path.join('vendor',name); const dest=path.join(vendorRoot,name); fs.mkdirSync(dest,{recursive:true}); if(fs.existsSync(src)){fs.cpSync(src,dest,{recursive:true,force:true});}});\"",
|
|
@@ -47,6 +48,7 @@
|
|
|
47
48
|
"test:mcp:unit": "vitest run tests/mcp*.test.ts tests/mcp/**/*.test.ts",
|
|
48
49
|
"test:mcp:mcporter": "pnpm dlx mcporter list oracle-local --schema --config config/mcporter.json && pnpm dlx mcporter call oracle-local.sessions limit:1 --config config/mcporter.json",
|
|
49
50
|
"test:browser": "pnpm run build && tsx scripts/test-browser.ts && ./scripts/browser-smoke.sh",
|
|
51
|
+
"test:packed-cli": "node scripts/packed-cli-smoke.mjs",
|
|
50
52
|
"test:live": "ORACLE_LIVE_TEST=1 vitest run tests/live --exclude tests/live/openai-live.test.ts",
|
|
51
53
|
"test:live:fast": "ORACLE_LIVE_TEST=1 ORACLE_LIVE_TEST_FAST=1 vitest run tests/live/browser-fast-live.test.ts",
|
|
52
54
|
"test:pro": "ORACLE_LIVE_TEST=1 vitest run tests/live/openai-live.test.ts",
|