@steipete/oracle 0.11.0 → 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 +56 -11
- package/dist/bin/oracle-cli.js +440 -98
- package/dist/src/browser/actions/archiveConversation.js +12 -0
- package/dist/src/browser/actions/modelSelection.js +61 -18
- 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/config.js +1 -7
- package/dist/src/browser/constants.js +1 -1
- package/dist/src/browser/index.js +65 -48
- 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 -18
- 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 +5 -2
- 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 +8 -6
|
@@ -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")) {
|
|
@@ -10,6 +10,7 @@ import { appendArtifacts, saveBrowserTranscriptArtifact, saveDeepResearchReportA
|
|
|
10
10
|
import { estimateTokenCount } from "../browser/utils.js";
|
|
11
11
|
import { formatSessionTableHeader, formatSessionTableRow, resolveSessionCost, } from "./sessionTable.js";
|
|
12
12
|
import { abbreviateResponseId, buildResponseOwnerIndex, resolveSessionLineage, } from "./sessionLineage.js";
|
|
13
|
+
import { formatSessionExecutionLabel } from "./sessionLifecycle.js";
|
|
13
14
|
const isTty = () => Boolean(process.stdout.isTTY);
|
|
14
15
|
const dim = (text) => (isTty() ? kleur.dim(text) : text);
|
|
15
16
|
export const MAX_RENDER_BYTES = 200_000;
|
|
@@ -219,6 +220,8 @@ export async function attachSession(sessionId, options) {
|
|
|
219
220
|
browser: {
|
|
220
221
|
config: metadata.browser?.config,
|
|
221
222
|
runtime,
|
|
223
|
+
modelSelection: metadata.browser?.modelSelection,
|
|
224
|
+
warnings: metadata.browser?.warnings,
|
|
222
225
|
},
|
|
223
226
|
artifacts,
|
|
224
227
|
response: { status: "completed" },
|
|
@@ -266,6 +269,11 @@ export async function attachSession(sessionId, options) {
|
|
|
266
269
|
}
|
|
267
270
|
console.log(`Created: ${metadata.createdAt}`);
|
|
268
271
|
console.log(`Status: ${metadata.status}`);
|
|
272
|
+
if (metadata.lifecycle) {
|
|
273
|
+
const attached = metadata.lifecycle.attached ? "attached" : "detached";
|
|
274
|
+
console.log(`Execution: ${formatSessionExecutionLabel(metadata)} (${attached})`);
|
|
275
|
+
console.log(`Reattach: ${metadata.lifecycle.reattachCommand}`);
|
|
276
|
+
}
|
|
269
277
|
if (metadata.models && metadata.models.length > 0) {
|
|
270
278
|
console.log("Models:");
|
|
271
279
|
for (const run of metadata.models) {
|
|
@@ -278,6 +286,13 @@ export async function attachSession(sessionId, options) {
|
|
|
278
286
|
else if (metadata.model) {
|
|
279
287
|
console.log(`Model: ${metadata.model}`);
|
|
280
288
|
}
|
|
289
|
+
const browserEvidence = formatBrowserEvidence(metadata);
|
|
290
|
+
if (browserEvidence) {
|
|
291
|
+
console.log("Browser evidence:");
|
|
292
|
+
for (const line of browserEvidence) {
|
|
293
|
+
console.log(dim(`- ${line}`));
|
|
294
|
+
}
|
|
295
|
+
}
|
|
281
296
|
if (metadata.artifacts && metadata.artifacts.length > 0) {
|
|
282
297
|
console.log("Artifacts:");
|
|
283
298
|
for (const artifact of metadata.artifacts) {
|
|
@@ -299,7 +314,7 @@ export async function attachSession(sessionId, options) {
|
|
|
299
314
|
console.log(dim(`User error: ${userErrorSummary}`));
|
|
300
315
|
}
|
|
301
316
|
}
|
|
302
|
-
const shouldTrimIntro = initialStatus === "completed" || initialStatus === "error";
|
|
317
|
+
const shouldTrimIntro = initialStatus === "completed" || initialStatus === "partial" || initialStatus === "error";
|
|
303
318
|
if (options?.renderPrompt !== false) {
|
|
304
319
|
const prompt = await readStoredPrompt(sessionId);
|
|
305
320
|
if (prompt) {
|
|
@@ -422,7 +437,7 @@ export async function attachSession(sessionId, options) {
|
|
|
422
437
|
if (!latest) {
|
|
423
438
|
break;
|
|
424
439
|
}
|
|
425
|
-
if (latest.status === "completed" || latest.status === "error") {
|
|
440
|
+
if (latest.status === "completed" || latest.status === "partial" || latest.status === "error") {
|
|
426
441
|
await printNew();
|
|
427
442
|
flushRemainder();
|
|
428
443
|
if (!options?.suppressMetadata) {
|
|
@@ -430,10 +445,11 @@ export async function attachSession(sessionId, options) {
|
|
|
430
445
|
console.log("\nResult:");
|
|
431
446
|
console.log(`Session failed: ${latest.errorMessage}`);
|
|
432
447
|
}
|
|
433
|
-
if (latest.status === "completed" && latest.usage) {
|
|
448
|
+
if ((latest.status === "completed" || latest.status === "partial") && latest.usage) {
|
|
434
449
|
const summary = formatCompletionSummary(latest, { includeSlug: true });
|
|
435
450
|
if (summary) {
|
|
436
|
-
|
|
451
|
+
const color = latest.status === "partial" ? chalk.yellow.bold : chalk.green.bold;
|
|
452
|
+
console.log(`\n${color(summary)}`);
|
|
437
453
|
}
|
|
438
454
|
else {
|
|
439
455
|
const usage = latest.usage;
|
|
@@ -495,6 +511,25 @@ export function formatUserErrorMetadata(metadata) {
|
|
|
495
511
|
}
|
|
496
512
|
return parts.length > 0 ? parts.join(" | ") : null;
|
|
497
513
|
}
|
|
514
|
+
export function formatBrowserEvidence(metadata) {
|
|
515
|
+
const browser = metadata.browser;
|
|
516
|
+
if (!browser?.modelSelection && (!browser?.warnings || browser.warnings.length === 0)) {
|
|
517
|
+
return null;
|
|
518
|
+
}
|
|
519
|
+
const lines = [];
|
|
520
|
+
const evidence = browser.modelSelection;
|
|
521
|
+
if (evidence) {
|
|
522
|
+
const requested = evidence.requestedModel ?? "(none)";
|
|
523
|
+
const resolved = evidence.resolvedLabel ?? "(unavailable)";
|
|
524
|
+
const strategy = evidence.strategy ?? "(default)";
|
|
525
|
+
const verified = evidence.verified ? "yes" : "no";
|
|
526
|
+
lines.push(`model requested=${requested}; resolved=${resolved}; status=${evidence.status}; strategy=${strategy}; verified=${verified}`);
|
|
527
|
+
}
|
|
528
|
+
for (const warning of browser.warnings ?? []) {
|
|
529
|
+
lines.push(`warning ${warning.code}: ${warning.message}`);
|
|
530
|
+
}
|
|
531
|
+
return lines.length > 0 ? lines : null;
|
|
532
|
+
}
|
|
498
533
|
export function buildReattachLine(metadata) {
|
|
499
534
|
if (!metadata.id) {
|
|
500
535
|
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
|
+
}
|