@steipete/oracle 0.11.1 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -10
- package/dist/bin/oracle-cli.js +440 -98
- package/dist/src/browser/actions/modelSelection.js +74 -20
- package/dist/src/browser/actions/navigation.js +5 -3
- package/dist/src/browser/actions/promptComposer.js +76 -18
- package/dist/src/browser/actions/thinkingTime.js +133 -19
- package/dist/src/browser/constants.js +1 -1
- package/dist/src/browser/index.js +78 -9
- 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/providers/chatgptDomProvider.js +1 -0
- package/dist/src/browser/reattachability.js +22 -0
- package/dist/src/browser/sessionRunner.js +73 -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 +47 -4
- package/dist/src/cli/sessionLifecycle.js +38 -0
- package/dist/src/cli/sessionRunner.js +272 -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 +308 -0
- package/dist/src/oracle/providerRouting.js +92 -0
- package/dist/src/oracle/run.js +104 -107
- 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 +43 -23
- package/package.json +15 -12
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { writeFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { performance } from "node:perf_hooks";
|
|
4
|
+
const SECRET_VALUE_FLAGS = new Set([
|
|
5
|
+
"--api-key",
|
|
6
|
+
"--browser-follow-up",
|
|
7
|
+
"--browser-inline-cookies",
|
|
8
|
+
"--browser-inline-cookies-file",
|
|
9
|
+
"--message",
|
|
10
|
+
"--prompt",
|
|
11
|
+
"--remote-token",
|
|
12
|
+
"--token",
|
|
13
|
+
"-p",
|
|
14
|
+
]);
|
|
15
|
+
const VALUE_FLAGS = new Set([
|
|
16
|
+
"--aspect",
|
|
17
|
+
"--azure-api-version",
|
|
18
|
+
"--azure-deployment",
|
|
19
|
+
"--azure-endpoint",
|
|
20
|
+
"--base-url",
|
|
21
|
+
"--browser-archive",
|
|
22
|
+
"--browser-attachments",
|
|
23
|
+
"--browser-auto-reattach-delay",
|
|
24
|
+
"--browser-auto-reattach-interval",
|
|
25
|
+
"--browser-auto-reattach-timeout",
|
|
26
|
+
"--browser-bundle-format",
|
|
27
|
+
"--browser-cookie-names",
|
|
28
|
+
"--browser-cookie-path",
|
|
29
|
+
"--browser-cookie-wait",
|
|
30
|
+
"--browser-input-timeout",
|
|
31
|
+
"--browser-max-concurrent-tabs",
|
|
32
|
+
"--browser-model-strategy",
|
|
33
|
+
"--browser-port",
|
|
34
|
+
"--browser-profile-lock-timeout",
|
|
35
|
+
"--browser-recheck-delay",
|
|
36
|
+
"--browser-recheck-timeout",
|
|
37
|
+
"--browser-research",
|
|
38
|
+
"--browser-reuse-wait",
|
|
39
|
+
"--browser-tab",
|
|
40
|
+
"--browser-timeout",
|
|
41
|
+
"--browser-url",
|
|
42
|
+
"--chatgpt-url",
|
|
43
|
+
"--engine",
|
|
44
|
+
"--followup",
|
|
45
|
+
"--followup-model",
|
|
46
|
+
"--heartbeat",
|
|
47
|
+
"--http-timeout",
|
|
48
|
+
"--max-file-size-bytes",
|
|
49
|
+
"--model",
|
|
50
|
+
"--models",
|
|
51
|
+
"--output",
|
|
52
|
+
"--partial",
|
|
53
|
+
"--perf-trace-path",
|
|
54
|
+
"--provider",
|
|
55
|
+
"--remote-chrome",
|
|
56
|
+
"--remote-host",
|
|
57
|
+
"--slug",
|
|
58
|
+
"--timeout",
|
|
59
|
+
"--write-output",
|
|
60
|
+
"--youtube",
|
|
61
|
+
"--zombie-timeout",
|
|
62
|
+
"-e",
|
|
63
|
+
"-m",
|
|
64
|
+
"-s",
|
|
65
|
+
]);
|
|
66
|
+
export function isTraceValueFlag(flag) {
|
|
67
|
+
return SECRET_VALUE_FLAGS.has(flag) || VALUE_FLAGS.has(flag);
|
|
68
|
+
}
|
|
69
|
+
class DisabledPerfTrace {
|
|
70
|
+
mark() { }
|
|
71
|
+
wrapFirstOutput() { }
|
|
72
|
+
flush() { }
|
|
73
|
+
}
|
|
74
|
+
class FilePerfTrace {
|
|
75
|
+
outputPath;
|
|
76
|
+
options;
|
|
77
|
+
events = [];
|
|
78
|
+
wrapped = false;
|
|
79
|
+
firstOutput = false;
|
|
80
|
+
flushed = false;
|
|
81
|
+
constructor(outputPath, options) {
|
|
82
|
+
this.outputPath = outputPath;
|
|
83
|
+
this.options = options;
|
|
84
|
+
}
|
|
85
|
+
mark(name, data) {
|
|
86
|
+
this.events.push({
|
|
87
|
+
name,
|
|
88
|
+
ms: Number(performance.now().toFixed(3)),
|
|
89
|
+
data,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
wrapFirstOutput() {
|
|
93
|
+
if (this.wrapped)
|
|
94
|
+
return;
|
|
95
|
+
this.wrapped = true;
|
|
96
|
+
const wrap = (stream) => {
|
|
97
|
+
const original = stream.write.bind(stream);
|
|
98
|
+
stream.write = ((...args) => {
|
|
99
|
+
if (!this.firstOutput) {
|
|
100
|
+
this.firstOutput = true;
|
|
101
|
+
this.mark("first-output", {
|
|
102
|
+
stream: stream === process.stderr ? "stderr" : "stdout",
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
return original(...args);
|
|
106
|
+
});
|
|
107
|
+
};
|
|
108
|
+
wrap(process.stdout);
|
|
109
|
+
wrap(process.stderr);
|
|
110
|
+
}
|
|
111
|
+
flush(exitCode) {
|
|
112
|
+
if (this.flushed)
|
|
113
|
+
return;
|
|
114
|
+
this.flushed = true;
|
|
115
|
+
this.mark("exit", { exitCode: exitCode ?? 0 });
|
|
116
|
+
const payload = {
|
|
117
|
+
version: this.options.version,
|
|
118
|
+
argv: sanitizeTraceArgv(this.options.argv),
|
|
119
|
+
cwd: this.options.cwd ?? process.cwd(),
|
|
120
|
+
pid: process.pid,
|
|
121
|
+
node: process.version,
|
|
122
|
+
timeOrigin: performance.timeOrigin,
|
|
123
|
+
totalMs: Number(performance.now().toFixed(3)),
|
|
124
|
+
events: this.events,
|
|
125
|
+
};
|
|
126
|
+
writeFileSync(this.outputPath, `${JSON.stringify(payload, null, 2)}\n`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
export function createPerfTrace(options) {
|
|
130
|
+
const envValue = process.env.ORACLE_PERF_TRACE;
|
|
131
|
+
const optionValue = options.value;
|
|
132
|
+
if (!optionValue && !envValue) {
|
|
133
|
+
return new DisabledPerfTrace();
|
|
134
|
+
}
|
|
135
|
+
const rawValue = typeof optionValue === "string" ? optionValue : envValue;
|
|
136
|
+
const outputPath = rawValue && rawValue !== "1" && rawValue !== "true"
|
|
137
|
+
? path.resolve(options.cwd ?? process.cwd(), rawValue)
|
|
138
|
+
: path.join(options.cwd ?? process.cwd(), `.oracle-perf-${new Date().toISOString().replace(/[:.]/g, "-")}-${process.pid}.json`);
|
|
139
|
+
const trace = new FilePerfTrace(outputPath, options);
|
|
140
|
+
trace.wrapFirstOutput();
|
|
141
|
+
trace.mark("cli-module-ready");
|
|
142
|
+
return trace;
|
|
143
|
+
}
|
|
144
|
+
export function deriveDetachedPerfTraceEnv(value, sessionId) {
|
|
145
|
+
const trimmed = value?.trim();
|
|
146
|
+
if (!trimmed || trimmed === "1" || trimmed.toLowerCase() === "true")
|
|
147
|
+
return value;
|
|
148
|
+
const safeSessionId = sessionId.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
149
|
+
const lastSlash = Math.max(trimmed.lastIndexOf("/"), trimmed.lastIndexOf("\\"));
|
|
150
|
+
const lastDot = trimmed.lastIndexOf(".");
|
|
151
|
+
if (lastDot > lastSlash) {
|
|
152
|
+
return `${trimmed.slice(0, lastDot)}.${safeSessionId}${trimmed.slice(lastDot)}`;
|
|
153
|
+
}
|
|
154
|
+
return `${trimmed}.${safeSessionId}.json`;
|
|
155
|
+
}
|
|
156
|
+
export function resolveDetachedPerfTraceEnv(cliValue, envValue, sessionId) {
|
|
157
|
+
if (typeof cliValue === "string") {
|
|
158
|
+
return deriveDetachedPerfTraceEnv(cliValue, sessionId);
|
|
159
|
+
}
|
|
160
|
+
if (cliValue === true) {
|
|
161
|
+
return "1";
|
|
162
|
+
}
|
|
163
|
+
return deriveDetachedPerfTraceEnv(envValue, sessionId);
|
|
164
|
+
}
|
|
165
|
+
export function buildDetachedPerfTraceEnv(env, cliValue, sessionId) {
|
|
166
|
+
const nextEnv = { ...env };
|
|
167
|
+
const traceValue = resolveDetachedPerfTraceEnv(cliValue, env.ORACLE_PERF_TRACE, sessionId);
|
|
168
|
+
if (traceValue) {
|
|
169
|
+
nextEnv.ORACLE_PERF_TRACE = traceValue;
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
delete nextEnv.ORACLE_PERF_TRACE;
|
|
173
|
+
}
|
|
174
|
+
return nextEnv;
|
|
175
|
+
}
|
|
176
|
+
export function sanitizeTraceArgv(argv) {
|
|
177
|
+
const sanitized = [];
|
|
178
|
+
let redactNext = false;
|
|
179
|
+
let valueNext = false;
|
|
180
|
+
let afterDoubleDash = false;
|
|
181
|
+
for (const arg of argv) {
|
|
182
|
+
if (afterDoubleDash) {
|
|
183
|
+
sanitized.push("[redacted-positional]");
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
if (arg === "--") {
|
|
187
|
+
sanitized.push(arg);
|
|
188
|
+
afterDoubleDash = true;
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
if (redactNext) {
|
|
192
|
+
sanitized.push("[redacted]");
|
|
193
|
+
redactNext = false;
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
if (valueNext) {
|
|
197
|
+
sanitized.push(redactPotentialSecret(arg));
|
|
198
|
+
valueNext = false;
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
const equalsIndex = arg.indexOf("=");
|
|
202
|
+
const flag = equalsIndex >= 0 ? arg.slice(0, equalsIndex) : arg;
|
|
203
|
+
if (equalsIndex >= 0 && SECRET_VALUE_FLAGS.has(flag)) {
|
|
204
|
+
sanitized.push(`${flag}=[redacted]`);
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
if (arg.startsWith("-p") && arg.length > 2) {
|
|
208
|
+
sanitized.push("-p[redacted]");
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
if (equalsIndex >= 0) {
|
|
212
|
+
sanitized.push(`${flag}=${redactPotentialSecret(arg.slice(equalsIndex + 1))}`);
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
if (SECRET_VALUE_FLAGS.has(arg)) {
|
|
216
|
+
sanitized.push(arg);
|
|
217
|
+
redactNext = true;
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
if (VALUE_FLAGS.has(arg)) {
|
|
221
|
+
sanitized.push(arg);
|
|
222
|
+
valueNext = true;
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
if (!arg.startsWith("-")) {
|
|
226
|
+
sanitized.push("[redacted-positional]");
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
sanitized.push(arg);
|
|
230
|
+
}
|
|
231
|
+
return sanitized;
|
|
232
|
+
}
|
|
233
|
+
function redactPotentialSecret(value) {
|
|
234
|
+
return value
|
|
235
|
+
.replace(/\bBearer\s+[A-Za-z0-9._\-+/=]+/gi, "Bearer [redacted]")
|
|
236
|
+
.replace(/:\/\/([^:/?#\s]+):([^@/?#\s]+)@/g, "://$1:[redacted]@")
|
|
237
|
+
.replace(/([?&](?:access_)?token=)[^&#\s]+/gi, "$1[redacted]")
|
|
238
|
+
.replace(/([?&](?:api[-_]?key|auth|authorization|password|secret)=)[^&#\s]+/gi, "$1[redacted]")
|
|
239
|
+
.replace(/\bsk-(?:ant-|or-)?[A-Za-z0-9_-]{8,}\b/g, "sk-...[redacted]")
|
|
240
|
+
.replace(/\bxai-[A-Za-z0-9_-]{8,}\b/g, "xai-...[redacted]")
|
|
241
|
+
.replace(/\bAIza[0-9A-Za-z_-]{8,}\b/g, "AIza...[redacted]");
|
|
242
|
+
}
|
|
@@ -10,6 +10,8 @@ export function shouldRequirePrompt(rawArgs, options) {
|
|
|
10
10
|
options.execSession ||
|
|
11
11
|
options.status ||
|
|
12
12
|
options.debugHelp ||
|
|
13
|
+
options.route ||
|
|
14
|
+
options.preflight ||
|
|
13
15
|
firstArg === "status" ||
|
|
14
16
|
firstArg === "session");
|
|
15
17
|
const requiresPrompt = options.renderMarkdown || Boolean(options.preview) || Boolean(options.dryRun) || !bypassPrompt;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { DEFAULT_MODEL } from "../oracle/config.js";
|
|
3
|
+
import { resolveApiModel } from "./options.js";
|
|
4
|
+
import { loadUserConfig } from "../config.js";
|
|
5
|
+
import { buildProviderRoutePlan } from "../oracle/providerRoutePlan.js";
|
|
6
|
+
export async function runProviderDoctor(options) {
|
|
7
|
+
if (!options.providers) {
|
|
8
|
+
console.log("Run `oracle doctor --providers` to inspect API provider readiness.");
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
const { config: userConfig } = await loadUserConfig();
|
|
12
|
+
const providerMode = resolveProviderMode(options);
|
|
13
|
+
const azure = resolveAzureOptions(options, userConfig);
|
|
14
|
+
const models = resolveModels(options, userConfig);
|
|
15
|
+
const plans = models.map((model) => buildProviderRoutePlan({
|
|
16
|
+
model,
|
|
17
|
+
providerMode,
|
|
18
|
+
azure,
|
|
19
|
+
baseUrl: options.baseUrl ?? userConfig.apiBaseUrl,
|
|
20
|
+
env: process.env,
|
|
21
|
+
}));
|
|
22
|
+
if (options.json) {
|
|
23
|
+
console.log(JSON.stringify({ providers: plans }, null, 2));
|
|
24
|
+
process.exitCode = plans.some((plan) => !plan.ok) ? 1 : 0;
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
printProviderPlans(plans);
|
|
28
|
+
process.exitCode = plans.some((plan) => !plan.ok) ? 1 : 0;
|
|
29
|
+
}
|
|
30
|
+
export function printProviderPlans(plans, { title = "Provider readiness" } = {}) {
|
|
31
|
+
console.log(chalk.bold(title));
|
|
32
|
+
console.log("");
|
|
33
|
+
for (const plan of plans) {
|
|
34
|
+
const status = plan.ok ? chalk.green("ok") : chalk.red("not ready");
|
|
35
|
+
console.log(`${plan.model}: ${status}`);
|
|
36
|
+
console.log(chalk.dim(` provider: ${plan.providerLabel}`));
|
|
37
|
+
console.log(chalk.dim(` base: ${plan.base || "(none)"}`));
|
|
38
|
+
console.log(chalk.dim(` key: ${plan.keyPreview}`));
|
|
39
|
+
if (plan.isAzureOpenAI || plan.azureDeploymentName) {
|
|
40
|
+
console.log(chalk.dim(` azure deployment: ${plan.azureDeploymentName ?? "none"}`));
|
|
41
|
+
}
|
|
42
|
+
if (plan.azureNote) {
|
|
43
|
+
console.log(chalk.dim(` azure: ${plan.azureNote}`));
|
|
44
|
+
}
|
|
45
|
+
if (plan.error) {
|
|
46
|
+
console.log(chalk.dim(` error: ${plan.error}`));
|
|
47
|
+
}
|
|
48
|
+
console.log("");
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function resolveModels(options, userConfig) {
|
|
52
|
+
const entries = Array.isArray(options.models) && options.models.length > 0
|
|
53
|
+
? options.models
|
|
54
|
+
: typeof options.models === "string" && options.models.trim().length > 0
|
|
55
|
+
? options.models
|
|
56
|
+
.split(",")
|
|
57
|
+
.map((entry) => entry.trim())
|
|
58
|
+
.filter(Boolean)
|
|
59
|
+
: [options.model ?? userConfig.model ?? DEFAULT_MODEL];
|
|
60
|
+
return Array.from(new Set(entries.map((entry) => resolveApiModel(entry))));
|
|
61
|
+
}
|
|
62
|
+
function resolveProviderMode(options) {
|
|
63
|
+
const provider = options.provider ?? "auto";
|
|
64
|
+
if (provider === "azure" && options.azure === false) {
|
|
65
|
+
throw new Error("--provider azure cannot be combined with --no-azure.");
|
|
66
|
+
}
|
|
67
|
+
if (options.azure === false) {
|
|
68
|
+
return "openai";
|
|
69
|
+
}
|
|
70
|
+
return provider;
|
|
71
|
+
}
|
|
72
|
+
function resolveAzureOptions(options, userConfig) {
|
|
73
|
+
const endpoint = firstNonEmpty(options.azureEndpoint, process.env.AZURE_OPENAI_ENDPOINT, userConfig.azure?.endpoint);
|
|
74
|
+
if (!endpoint) {
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
endpoint,
|
|
79
|
+
deployment: firstNonEmpty(options.azureDeployment, process.env.AZURE_OPENAI_DEPLOYMENT, userConfig.azure?.deployment),
|
|
80
|
+
apiVersion: firstNonEmpty(options.azureApiVersion, process.env.AZURE_OPENAI_API_VERSION, userConfig.azure?.apiVersion),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function firstNonEmpty(...values) {
|
|
84
|
+
return values.find((value) => value?.trim());
|
|
85
|
+
}
|
|
@@ -5,41 +5,59 @@ import { resolveGeminiModelId } from "../oracle/gemini.js";
|
|
|
5
5
|
import { PromptValidationError } from "../oracle/errors.js";
|
|
6
6
|
import { normalizeChatGptModelForBrowser } from "./browserConfig.js";
|
|
7
7
|
import { resolveConfiguredMaxFileSizeBytes } from "./fileSize.js";
|
|
8
|
+
import { isAzureOpenAICandidateModel } from "../oracle/providerRouting.js";
|
|
8
9
|
export function resolveRunOptionsFromConfig({ prompt, files = [], model, models, engine, userConfig, env = process.env, }) {
|
|
9
|
-
const resolvedEngine = resolveEngineWithConfig({
|
|
10
|
+
const resolvedEngine = resolveEngineWithConfig({
|
|
11
|
+
engine,
|
|
12
|
+
configEngine: userConfig?.engine,
|
|
13
|
+
env,
|
|
14
|
+
});
|
|
10
15
|
const browserRequested = engine === "browser";
|
|
11
16
|
const browserConfigured = userConfig?.engine === "browser";
|
|
17
|
+
const envBrowserConfigured = (env.ORACLE_ENGINE ?? "").trim().toLowerCase() === "browser";
|
|
12
18
|
const requestedModelList = Array.isArray(models) ? models : [];
|
|
13
19
|
const normalizedRequestedModels = requestedModelList
|
|
14
20
|
.map((entry) => normalizeModelOption(entry))
|
|
15
21
|
.filter(Boolean);
|
|
16
22
|
const cliModelArg = normalizeModelOption(model ?? userConfig?.model) || DEFAULT_MODEL;
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
const isClaude = resolvedModel.startsWith("claude");
|
|
24
|
-
const isGrok = resolvedModel.startsWith("grok");
|
|
25
|
-
const isGeminiApiOnly = resolvedModel === "gemini-3.1-pro";
|
|
23
|
+
const apiModel = resolveApiModel(cliModelArg);
|
|
24
|
+
const browserModel = normalizeChatGptModelForBrowser(inferModelFromLabel(cliModelArg));
|
|
25
|
+
const isCodex = apiModel.startsWith("gpt-5.1-codex");
|
|
26
|
+
const isClaude = apiModel.startsWith("claude");
|
|
27
|
+
const isGrok = apiModel.startsWith("grok");
|
|
28
|
+
const isGeminiApiOnly = apiModel === "gemini-3.1-pro";
|
|
26
29
|
const engineWasBrowser = resolvedEngine === "browser";
|
|
27
30
|
const allModels = normalizedRequestedModels.length > 0
|
|
28
31
|
? Array.from(new Set(normalizedRequestedModels.map((entry) => resolveApiModel(entry))))
|
|
29
|
-
: [
|
|
32
|
+
: [apiModel];
|
|
33
|
+
const browserCompatibilityModels = normalizedRequestedModels.length > 0 ? allModels : [browserModel];
|
|
30
34
|
const includesGeminiApiOnly = allModels.some((m) => m === "gemini-3.1-pro");
|
|
31
35
|
if ((browserRequested || browserConfigured) && includesGeminiApiOnly) {
|
|
32
36
|
throw new PromptValidationError("gemini-3.1-pro is API-only today. Use --engine api or switch to gemini-3-pro for Gemini web.", { engine: "browser", models: allModels });
|
|
33
37
|
}
|
|
34
38
|
const isBrowserCompatible = (m) => m.startsWith("gpt-") || m.startsWith("gemini");
|
|
35
|
-
const hasNonBrowserCompatibleTarget = (browserRequested || browserConfigured) &&
|
|
39
|
+
const hasNonBrowserCompatibleTarget = (browserRequested || browserConfigured) &&
|
|
40
|
+
browserCompatibilityModels.some((m) => !isBrowserCompatible(m));
|
|
36
41
|
if (hasNonBrowserCompatibleTarget) {
|
|
37
42
|
throw new PromptValidationError("Browser engine only supports GPT and Gemini models. Re-run with --engine api for Grok, Claude, or other models.", { engine: "browser", models: allModels });
|
|
38
43
|
}
|
|
39
|
-
const
|
|
40
|
-
const
|
|
44
|
+
const azure = resolveAzureOptions(userConfig, env);
|
|
45
|
+
const azureAutoApi = Boolean(azure?.endpoint) &&
|
|
46
|
+
!browserRequested &&
|
|
47
|
+
!browserConfigured &&
|
|
48
|
+
!envBrowserConfigured &&
|
|
49
|
+
allModels.some(isAzureOpenAICandidateModel);
|
|
50
|
+
const engineCoercedToApi = engineWasBrowser && (isCodex || isClaude || isGrok || isGeminiApiOnly || azureAutoApi);
|
|
51
|
+
const fixedEngine = isCodex ||
|
|
52
|
+
isClaude ||
|
|
53
|
+
isGrok ||
|
|
54
|
+
isGeminiApiOnly ||
|
|
55
|
+
azureAutoApi ||
|
|
56
|
+
normalizedRequestedModels.length > 0
|
|
41
57
|
? "api"
|
|
42
58
|
: resolvedEngine;
|
|
59
|
+
// Browser runs use ChatGPT picker labels/aliases; API runs must keep API model ids intact.
|
|
60
|
+
const resolvedModel = fixedEngine === "browser" ? browserModel : apiModel;
|
|
43
61
|
const promptWithSuffix = userConfig?.promptSuffix && userConfig.promptSuffix.trim().length > 0
|
|
44
62
|
? `${prompt.trim()}\n${userConfig.promptSuffix}`
|
|
45
63
|
: prompt;
|
|
@@ -66,11 +84,12 @@ export function resolveRunOptionsFromConfig({ prompt, files = [], model, models,
|
|
|
66
84
|
filesReport: userConfig?.filesReport,
|
|
67
85
|
background: userConfig?.background,
|
|
68
86
|
baseUrl,
|
|
87
|
+
azure,
|
|
69
88
|
effectiveModelId,
|
|
70
89
|
};
|
|
71
90
|
return { runOptions, resolvedEngine: fixedEngine, engineCoercedToApi };
|
|
72
91
|
}
|
|
73
|
-
function resolveEngineWithConfig({ engine, configEngine, env, }) {
|
|
92
|
+
function resolveEngineWithConfig({ engine, configEngine, apiProviderRequested, env, }) {
|
|
74
93
|
if (engine)
|
|
75
94
|
return engine;
|
|
76
95
|
const envOverride = (env.ORACLE_ENGINE ?? "").trim().toLowerCase();
|
|
@@ -79,7 +98,18 @@ function resolveEngineWithConfig({ engine, configEngine, env, }) {
|
|
|
79
98
|
}
|
|
80
99
|
if (configEngine)
|
|
81
100
|
return configEngine;
|
|
82
|
-
return resolveEngine({ engine: undefined, env });
|
|
101
|
+
return resolveEngine({ engine: undefined, apiProviderRequested, env });
|
|
102
|
+
}
|
|
103
|
+
function resolveAzureOptions(userConfig, env) {
|
|
104
|
+
const endpoint = env.AZURE_OPENAI_ENDPOINT ?? userConfig?.azure?.endpoint;
|
|
105
|
+
if (!endpoint?.trim()) {
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
endpoint,
|
|
110
|
+
deployment: env.AZURE_OPENAI_DEPLOYMENT ?? userConfig?.azure?.deployment,
|
|
111
|
+
apiVersion: env.AZURE_OPENAI_API_VERSION ?? userConfig?.azure?.apiVersion,
|
|
112
|
+
};
|
|
83
113
|
}
|
|
84
114
|
function resolveEffectiveModelId(model) {
|
|
85
115
|
if (typeof model === "string" && model.startsWith("gemini")) {
|
|
@@ -6,10 +6,12 @@ import { formatFinishLine } from "../oracle/finishLine.js";
|
|
|
6
6
|
import { sessionStore, wait } from "../sessionStore.js";
|
|
7
7
|
import { formatTokenCount, formatTokenValue } from "../oracle/runUtils.js";
|
|
8
8
|
import { resumeBrowserSession } from "../browser/reattach.js";
|
|
9
|
+
import { hasRecoverableChatGptConversation } from "../browser/reattachability.js";
|
|
9
10
|
import { appendArtifacts, saveBrowserTranscriptArtifact, saveDeepResearchReportArtifact, } from "../browser/artifacts.js";
|
|
10
11
|
import { estimateTokenCount } from "../browser/utils.js";
|
|
11
12
|
import { formatSessionTableHeader, formatSessionTableRow, resolveSessionCost, } from "./sessionTable.js";
|
|
12
13
|
import { abbreviateResponseId, buildResponseOwnerIndex, resolveSessionLineage, } from "./sessionLineage.js";
|
|
14
|
+
import { formatSessionExecutionLabel } from "./sessionLifecycle.js";
|
|
13
15
|
const isTty = () => Boolean(process.stdout.isTTY);
|
|
14
16
|
const dim = (text) => (isTty() ? kleur.dim(text) : text);
|
|
15
17
|
export const MAX_RENDER_BYTES = 200_000;
|
|
@@ -173,9 +175,16 @@ export async function attachSession(sessionId, options) {
|
|
|
173
175
|
hasFallbackSessionInfo &&
|
|
174
176
|
isDeepResearchPlaceholderCapture(metadata, await sessionStore.readLog(sessionId).catch(() => ""));
|
|
175
177
|
const completedDeepResearchPlaceholder = metadata.status === "completed" && deepResearchPlaceholderCapture;
|
|
178
|
+
const hasRecoverableConversation = hasRecoverableChatGptConversation(runtime);
|
|
179
|
+
const hasLiveChromeFallback = Boolean((metadata.status === "running" || hasIncompleteCapture || completedDeepResearchPlaceholder) &&
|
|
180
|
+
(runtime?.chromePort || runtime?.chromeBrowserWSEndpoint || runtime?.chromeProfileRoot));
|
|
176
181
|
const canReattach = (statusAllowsReattach || completedDeepResearchPlaceholder) &&
|
|
177
182
|
metadata.mode === "browser" &&
|
|
178
183
|
hasFallbackSessionInfo &&
|
|
184
|
+
(hasRecoverableConversation ||
|
|
185
|
+
runtime?.promptSubmitted ||
|
|
186
|
+
hasLiveChromeFallback ||
|
|
187
|
+
completedDeepResearchPlaceholder) &&
|
|
179
188
|
(hasChromeDisconnect ||
|
|
180
189
|
hasIncompleteCapture ||
|
|
181
190
|
completedDeepResearchPlaceholder ||
|
|
@@ -219,6 +228,8 @@ export async function attachSession(sessionId, options) {
|
|
|
219
228
|
browser: {
|
|
220
229
|
config: metadata.browser?.config,
|
|
221
230
|
runtime,
|
|
231
|
+
modelSelection: metadata.browser?.modelSelection,
|
|
232
|
+
warnings: metadata.browser?.warnings,
|
|
222
233
|
},
|
|
223
234
|
artifacts,
|
|
224
235
|
response: { status: "completed" },
|
|
@@ -266,6 +277,11 @@ export async function attachSession(sessionId, options) {
|
|
|
266
277
|
}
|
|
267
278
|
console.log(`Created: ${metadata.createdAt}`);
|
|
268
279
|
console.log(`Status: ${metadata.status}`);
|
|
280
|
+
if (metadata.lifecycle) {
|
|
281
|
+
const attached = metadata.lifecycle.attached ? "attached" : "detached";
|
|
282
|
+
console.log(`Execution: ${formatSessionExecutionLabel(metadata)} (${attached})`);
|
|
283
|
+
console.log(`Reattach: ${metadata.lifecycle.reattachCommand}`);
|
|
284
|
+
}
|
|
269
285
|
if (metadata.models && metadata.models.length > 0) {
|
|
270
286
|
console.log("Models:");
|
|
271
287
|
for (const run of metadata.models) {
|
|
@@ -278,6 +294,13 @@ export async function attachSession(sessionId, options) {
|
|
|
278
294
|
else if (metadata.model) {
|
|
279
295
|
console.log(`Model: ${metadata.model}`);
|
|
280
296
|
}
|
|
297
|
+
const browserEvidence = formatBrowserEvidence(metadata);
|
|
298
|
+
if (browserEvidence) {
|
|
299
|
+
console.log("Browser evidence:");
|
|
300
|
+
for (const line of browserEvidence) {
|
|
301
|
+
console.log(dim(`- ${line}`));
|
|
302
|
+
}
|
|
303
|
+
}
|
|
281
304
|
if (metadata.artifacts && metadata.artifacts.length > 0) {
|
|
282
305
|
console.log("Artifacts:");
|
|
283
306
|
for (const artifact of metadata.artifacts) {
|
|
@@ -299,7 +322,7 @@ export async function attachSession(sessionId, options) {
|
|
|
299
322
|
console.log(dim(`User error: ${userErrorSummary}`));
|
|
300
323
|
}
|
|
301
324
|
}
|
|
302
|
-
const shouldTrimIntro = initialStatus === "completed" || initialStatus === "error";
|
|
325
|
+
const shouldTrimIntro = initialStatus === "completed" || initialStatus === "partial" || initialStatus === "error";
|
|
303
326
|
if (options?.renderPrompt !== false) {
|
|
304
327
|
const prompt = await readStoredPrompt(sessionId);
|
|
305
328
|
if (prompt) {
|
|
@@ -422,7 +445,7 @@ export async function attachSession(sessionId, options) {
|
|
|
422
445
|
if (!latest) {
|
|
423
446
|
break;
|
|
424
447
|
}
|
|
425
|
-
if (latest.status === "completed" || latest.status === "error") {
|
|
448
|
+
if (latest.status === "completed" || latest.status === "partial" || latest.status === "error") {
|
|
426
449
|
await printNew();
|
|
427
450
|
flushRemainder();
|
|
428
451
|
if (!options?.suppressMetadata) {
|
|
@@ -430,10 +453,11 @@ export async function attachSession(sessionId, options) {
|
|
|
430
453
|
console.log("\nResult:");
|
|
431
454
|
console.log(`Session failed: ${latest.errorMessage}`);
|
|
432
455
|
}
|
|
433
|
-
if (latest.status === "completed" && latest.usage) {
|
|
456
|
+
if ((latest.status === "completed" || latest.status === "partial") && latest.usage) {
|
|
434
457
|
const summary = formatCompletionSummary(latest, { includeSlug: true });
|
|
435
458
|
if (summary) {
|
|
436
|
-
|
|
459
|
+
const color = latest.status === "partial" ? chalk.yellow.bold : chalk.green.bold;
|
|
460
|
+
console.log(`\n${color(summary)}`);
|
|
437
461
|
}
|
|
438
462
|
else {
|
|
439
463
|
const usage = latest.usage;
|
|
@@ -495,6 +519,25 @@ export function formatUserErrorMetadata(metadata) {
|
|
|
495
519
|
}
|
|
496
520
|
return parts.length > 0 ? parts.join(" | ") : null;
|
|
497
521
|
}
|
|
522
|
+
export function formatBrowserEvidence(metadata) {
|
|
523
|
+
const browser = metadata.browser;
|
|
524
|
+
if (!browser?.modelSelection && (!browser?.warnings || browser.warnings.length === 0)) {
|
|
525
|
+
return null;
|
|
526
|
+
}
|
|
527
|
+
const lines = [];
|
|
528
|
+
const evidence = browser.modelSelection;
|
|
529
|
+
if (evidence) {
|
|
530
|
+
const requested = evidence.requestedModel ?? "(none)";
|
|
531
|
+
const resolved = evidence.resolvedLabel ?? "(unavailable)";
|
|
532
|
+
const strategy = evidence.strategy ?? "(default)";
|
|
533
|
+
const verified = evidence.verified ? "yes" : "no";
|
|
534
|
+
lines.push(`model requested=${requested}; resolved=${resolved}; status=${evidence.status}; strategy=${strategy}; verified=${verified}`);
|
|
535
|
+
}
|
|
536
|
+
for (const warning of browser.warnings ?? []) {
|
|
537
|
+
lines.push(`warning ${warning.code}: ${warning.message}`);
|
|
538
|
+
}
|
|
539
|
+
return lines.length > 0 ? lines : null;
|
|
540
|
+
}
|
|
498
541
|
export function buildReattachLine(metadata) {
|
|
499
542
|
if (!metadata.id) {
|
|
500
543
|
return null;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export function buildSessionLifecycle({ engine, detached, reattachCommand, }) {
|
|
2
|
+
return {
|
|
3
|
+
engine,
|
|
4
|
+
execution: detached ? "background" : "foreground",
|
|
5
|
+
attached: !detached,
|
|
6
|
+
detached,
|
|
7
|
+
reattachCommand,
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export function formatSessionLifecycleBlock(meta) {
|
|
11
|
+
const lifecycle = meta.lifecycle;
|
|
12
|
+
if (!lifecycle) {
|
|
13
|
+
return [];
|
|
14
|
+
}
|
|
15
|
+
const modelCount = meta.models?.length ?? (meta.model ? 1 : 0);
|
|
16
|
+
const detachValue = lifecycle.detached
|
|
17
|
+
? lifecycle.execution === "background"
|
|
18
|
+
? "yes, polling"
|
|
19
|
+
: "yes"
|
|
20
|
+
: "no";
|
|
21
|
+
const lines = [
|
|
22
|
+
`Session: ${meta.id}`,
|
|
23
|
+
`Mode: ${lifecycle.engine} ${lifecycle.execution}`,
|
|
24
|
+
`Models: ${modelCount > 1 ? `${modelCount} parallel` : String(modelCount || 1)}`,
|
|
25
|
+
`Detach: ${detachValue}`,
|
|
26
|
+
`Reattach: ${lifecycle.reattachCommand}`,
|
|
27
|
+
];
|
|
28
|
+
return lines;
|
|
29
|
+
}
|
|
30
|
+
export function formatSessionExecutionLabel(meta) {
|
|
31
|
+
const lifecycle = meta.lifecycle;
|
|
32
|
+
if (!lifecycle) {
|
|
33
|
+
return meta.mode ?? meta.options?.mode ?? "api";
|
|
34
|
+
}
|
|
35
|
+
const engine = lifecycle.engine === "browser" ? "br" : lifecycle.engine;
|
|
36
|
+
const execution = lifecycle.execution === "background" ? "bg" : "fg";
|
|
37
|
+
return `${engine}/${execution}`;
|
|
38
|
+
}
|