@steipete/oracle 0.12.0 → 0.13.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 +54 -54
- package/dist/bin/oracle-cli.js +15 -6
- package/dist/bin/oracle-mcp.js +0 -0
- package/dist/src/browser/actions/modelSelection.js +126 -25
- package/dist/src/browser/actions/navigation.js +89 -27
- package/dist/src/browser/actions/promptComposer.js +196 -46
- package/dist/src/browser/actions/thinkingTime.js +111 -12
- package/dist/src/browser/config.js +2 -0
- package/dist/src/browser/index.js +43 -4
- package/dist/src/browser/providers/chatgptDomProvider.js +2 -0
- package/dist/src/browser/reattachability.js +22 -0
- package/dist/src/browser/sessionRunner.js +1 -0
- package/dist/src/cli/bridge/doctor.js +7 -2
- package/dist/src/cli/browserConfig.js +9 -1
- package/dist/src/cli/browserDefaults.js +3 -0
- package/dist/src/cli/engine.js +6 -2
- package/dist/src/cli/options.js +4 -0
- package/dist/src/cli/runOptions.js +9 -20
- package/dist/src/cli/sessionDisplay.js +8 -0
- package/dist/src/cli/sessionRunner.js +49 -5
- package/dist/src/config.js +164 -9
- package/dist/src/oracle/providerRoutePlan.js +29 -2
- package/dist/src/oracle/run.js +50 -156
- package/dist/src/sessionManager.js +38 -22
- package/package.json +14 -13
|
@@ -7,14 +7,17 @@ import { normalizeChatGptModelForBrowser } from "./browserConfig.js";
|
|
|
7
7
|
import { resolveConfiguredMaxFileSizeBytes } from "./fileSize.js";
|
|
8
8
|
import { isAzureOpenAICandidateModel } from "../oracle/providerRouting.js";
|
|
9
9
|
export function resolveRunOptionsFromConfig({ prompt, files = [], model, models, engine, userConfig, env = process.env, }) {
|
|
10
|
-
const resolvedEngine =
|
|
10
|
+
const resolvedEngine = resolveEngine({
|
|
11
11
|
engine,
|
|
12
12
|
configEngine: userConfig?.engine,
|
|
13
13
|
env,
|
|
14
14
|
});
|
|
15
|
+
const envEnginePreference = (env.ORACLE_ENGINE ?? "").trim().toLowerCase();
|
|
15
16
|
const browserRequested = engine === "browser";
|
|
16
|
-
const
|
|
17
|
-
const
|
|
17
|
+
const explicitApiEngineRequested = engine === "api" || (!engine && envEnginePreference === "api");
|
|
18
|
+
const browserConfigured = userConfig?.engine === "browser" && !explicitApiEngineRequested;
|
|
19
|
+
const envBrowserConfigured = !engine && envEnginePreference === "browser";
|
|
20
|
+
const browserEngineRequested = browserRequested || browserConfigured || envBrowserConfigured;
|
|
18
21
|
const requestedModelList = Array.isArray(models) ? models : [];
|
|
19
22
|
const normalizedRequestedModels = requestedModelList
|
|
20
23
|
.map((entry) => normalizeModelOption(entry))
|
|
@@ -32,20 +35,17 @@ export function resolveRunOptionsFromConfig({ prompt, files = [], model, models,
|
|
|
32
35
|
: [apiModel];
|
|
33
36
|
const browserCompatibilityModels = normalizedRequestedModels.length > 0 ? allModels : [browserModel];
|
|
34
37
|
const includesGeminiApiOnly = allModels.some((m) => m === "gemini-3.1-pro");
|
|
35
|
-
if (
|
|
38
|
+
if (browserEngineRequested && includesGeminiApiOnly) {
|
|
36
39
|
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 });
|
|
37
40
|
}
|
|
38
41
|
const isBrowserCompatible = (m) => m.startsWith("gpt-") || m.startsWith("gemini");
|
|
39
|
-
const hasNonBrowserCompatibleTarget = (
|
|
40
|
-
browserCompatibilityModels.some((m) => !isBrowserCompatible(m));
|
|
42
|
+
const hasNonBrowserCompatibleTarget = browserEngineRequested && browserCompatibilityModels.some((m) => !isBrowserCompatible(m));
|
|
41
43
|
if (hasNonBrowserCompatibleTarget) {
|
|
42
44
|
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 });
|
|
43
45
|
}
|
|
44
46
|
const azure = resolveAzureOptions(userConfig, env);
|
|
45
47
|
const azureAutoApi = Boolean(azure?.endpoint) &&
|
|
46
|
-
!
|
|
47
|
-
!browserConfigured &&
|
|
48
|
-
!envBrowserConfigured &&
|
|
48
|
+
!browserEngineRequested &&
|
|
49
49
|
allModels.some(isAzureOpenAICandidateModel);
|
|
50
50
|
const engineCoercedToApi = engineWasBrowser && (isCodex || isClaude || isGrok || isGeminiApiOnly || azureAutoApi);
|
|
51
51
|
const fixedEngine = isCodex ||
|
|
@@ -89,17 +89,6 @@ export function resolveRunOptionsFromConfig({ prompt, files = [], model, models,
|
|
|
89
89
|
};
|
|
90
90
|
return { runOptions, resolvedEngine: fixedEngine, engineCoercedToApi };
|
|
91
91
|
}
|
|
92
|
-
function resolveEngineWithConfig({ engine, configEngine, apiProviderRequested, env, }) {
|
|
93
|
-
if (engine)
|
|
94
|
-
return engine;
|
|
95
|
-
const envOverride = (env.ORACLE_ENGINE ?? "").trim().toLowerCase();
|
|
96
|
-
if (envOverride === "api" || envOverride === "browser") {
|
|
97
|
-
return envOverride;
|
|
98
|
-
}
|
|
99
|
-
if (configEngine)
|
|
100
|
-
return configEngine;
|
|
101
|
-
return resolveEngine({ engine: undefined, apiProviderRequested, env });
|
|
102
|
-
}
|
|
103
92
|
function resolveAzureOptions(userConfig, env) {
|
|
104
93
|
const endpoint = env.AZURE_OPENAI_ENDPOINT ?? userConfig?.azure?.endpoint;
|
|
105
94
|
if (!endpoint?.trim()) {
|
|
@@ -6,6 +6,7 @@ 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";
|
|
@@ -174,9 +175,16 @@ export async function attachSession(sessionId, options) {
|
|
|
174
175
|
hasFallbackSessionInfo &&
|
|
175
176
|
isDeepResearchPlaceholderCapture(metadata, await sessionStore.readLog(sessionId).catch(() => ""));
|
|
176
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));
|
|
177
181
|
const canReattach = (statusAllowsReattach || completedDeepResearchPlaceholder) &&
|
|
178
182
|
metadata.mode === "browser" &&
|
|
179
183
|
hasFallbackSessionInfo &&
|
|
184
|
+
(hasRecoverableConversation ||
|
|
185
|
+
runtime?.promptSubmitted ||
|
|
186
|
+
hasLiveChromeFallback ||
|
|
187
|
+
completedDeepResearchPlaceholder) &&
|
|
180
188
|
(hasChromeDisconnect ||
|
|
181
189
|
hasIncompleteCapture ||
|
|
182
190
|
completedDeepResearchPlaceholder ||
|
|
@@ -21,6 +21,7 @@ import { sanitizeOscProgress } from "./oscUtils.js";
|
|
|
21
21
|
import { readFiles } from "../oracle/files.js";
|
|
22
22
|
import { cwd as getCwd } from "node:process";
|
|
23
23
|
import { resumeBrowserSession } from "../browser/reattach.js";
|
|
24
|
+
import { hasRecoverableChatGptConversation } from "../browser/reattachability.js";
|
|
24
25
|
import { estimateTokenCount } from "../browser/utils.js";
|
|
25
26
|
import { formatElapsed } from "../oracle/format.js";
|
|
26
27
|
const isTty = process.stdout.isTTY;
|
|
@@ -390,6 +391,40 @@ export async function performSessionRun({ sessionMeta, runOptions, mode, browser
|
|
|
390
391
|
if (connectionLost && mode === "browser") {
|
|
391
392
|
const runtime = userError.details
|
|
392
393
|
?.runtime;
|
|
394
|
+
const recoverableRuntime = runtime ?? sessionMeta.browser?.runtime;
|
|
395
|
+
if (!hasRecoverableChatGptConversation(recoverableRuntime) &&
|
|
396
|
+
recoverableRuntime?.promptSubmitted !== true) {
|
|
397
|
+
log(dim("Chrome disconnected before a ChatGPT conversation was created; marking session error."));
|
|
398
|
+
if (modelForStatus) {
|
|
399
|
+
await sessionStore.updateModelRun(sessionMeta.id, modelForStatus, {
|
|
400
|
+
status: "error",
|
|
401
|
+
completedAt: new Date().toISOString(),
|
|
402
|
+
response: { status: "error", incompleteReason: "chrome-disconnected" },
|
|
403
|
+
error: {
|
|
404
|
+
category: userError.category,
|
|
405
|
+
message: userError.message,
|
|
406
|
+
details: userError.details,
|
|
407
|
+
},
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
await sessionStore.updateSession(sessionMeta.id, {
|
|
411
|
+
status: "error",
|
|
412
|
+
completedAt: new Date().toISOString(),
|
|
413
|
+
errorMessage: message,
|
|
414
|
+
mode,
|
|
415
|
+
browser: {
|
|
416
|
+
config: browserConfig,
|
|
417
|
+
runtime: recoverableRuntime,
|
|
418
|
+
},
|
|
419
|
+
response: { status: "error", incompleteReason: "chrome-disconnected" },
|
|
420
|
+
error: {
|
|
421
|
+
category: userError.category,
|
|
422
|
+
message: userError.message,
|
|
423
|
+
details: userError.details,
|
|
424
|
+
},
|
|
425
|
+
});
|
|
426
|
+
throw error;
|
|
427
|
+
}
|
|
393
428
|
log(dim("Chrome disconnected before completion; keeping session running for reattach."));
|
|
394
429
|
if (modelForStatus) {
|
|
395
430
|
await sessionStore.updateModelRun(sessionMeta.id, modelForStatus, {
|
|
@@ -576,13 +611,22 @@ function sanitizeMultiModelFailureForThrow(error, context) {
|
|
|
576
611
|
if (!(error instanceof Error)) {
|
|
577
612
|
return new Error(message);
|
|
578
613
|
}
|
|
579
|
-
|
|
614
|
+
let sanitized;
|
|
615
|
+
if (error instanceof OracleTransportError) {
|
|
616
|
+
sanitized = new OracleTransportError(error.reason, message);
|
|
617
|
+
}
|
|
618
|
+
else if (error instanceof OracleResponseError) {
|
|
619
|
+
sanitized = new OracleResponseError(message, error.response);
|
|
620
|
+
}
|
|
621
|
+
else {
|
|
622
|
+
sanitized = new Error(message);
|
|
623
|
+
sanitized.name = error.name;
|
|
624
|
+
}
|
|
580
625
|
if (error.stack) {
|
|
581
|
-
const [
|
|
582
|
-
|
|
583
|
-
error.stack = [prefix ? `${prefix}: ${message}` : message, ...rest].join("\n");
|
|
626
|
+
const [, ...rest] = error.stack.split("\n");
|
|
627
|
+
sanitized.stack = [sanitized.name ? `${sanitized.name}: ${message}` : message, ...rest].join("\n");
|
|
584
628
|
}
|
|
585
|
-
return
|
|
629
|
+
return sanitized;
|
|
586
630
|
}
|
|
587
631
|
export function deriveOutputManifestPath(basePath) {
|
|
588
632
|
const ext = path.extname(basePath);
|
package/dist/src/config.js
CHANGED
|
@@ -1,26 +1,181 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
|
+
import os from "node:os";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
import JSON5 from "json5";
|
|
4
5
|
import { getOracleHomeDir } from "./oracleHome.js";
|
|
5
|
-
|
|
6
|
+
export const PROJECT_CONFIG_RELATIVE_PATH = path.join(".oracle", "config.json");
|
|
7
|
+
function resolveUserConfigPath() {
|
|
6
8
|
return path.join(getOracleHomeDir(), "config.json");
|
|
7
9
|
}
|
|
8
|
-
export async function loadUserConfig() {
|
|
9
|
-
const
|
|
10
|
+
export async function loadUserConfig(options = {}) {
|
|
11
|
+
const userConfigPath = resolveUserConfigPath();
|
|
12
|
+
const userConfig = await readConfigFile(userConfigPath);
|
|
13
|
+
const projectConfigPaths = options.includeProject === false
|
|
14
|
+
? []
|
|
15
|
+
: await discoverProjectConfigPaths({
|
|
16
|
+
cwd: options.cwd ?? process.cwd(),
|
|
17
|
+
userConfigPath,
|
|
18
|
+
});
|
|
19
|
+
const loadedConfigs = [];
|
|
20
|
+
if (userConfig.loaded) {
|
|
21
|
+
loadedConfigs.push(userConfig);
|
|
22
|
+
}
|
|
23
|
+
let merged = userConfig.loaded ? userConfig.config : {};
|
|
24
|
+
for (const projectConfigPath of projectConfigPaths) {
|
|
25
|
+
const projectConfig = await readConfigFile(projectConfigPath);
|
|
26
|
+
if (!projectConfig.loaded)
|
|
27
|
+
continue;
|
|
28
|
+
loadedConfigs.push(projectConfig);
|
|
29
|
+
merged = mergeUserConfig(merged, sanitizeProjectConfig(projectConfig.config));
|
|
30
|
+
}
|
|
31
|
+
const loadedPaths = loadedConfigs.map((entry) => entry.path);
|
|
32
|
+
return {
|
|
33
|
+
config: merged,
|
|
34
|
+
path: userConfigPath,
|
|
35
|
+
paths: loadedPaths,
|
|
36
|
+
loaded: userConfig.loaded,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
async function readConfigFile(configPath) {
|
|
10
40
|
try {
|
|
11
|
-
const raw = await fs.readFile(
|
|
41
|
+
const raw = await fs.readFile(configPath, "utf8");
|
|
12
42
|
const parsed = JSON5.parse(raw);
|
|
13
|
-
return { config: parsed ?? {}, path:
|
|
43
|
+
return { config: parsed ?? {}, path: configPath, loaded: true };
|
|
14
44
|
}
|
|
15
45
|
catch (error) {
|
|
16
46
|
const code = error.code;
|
|
17
47
|
if (code === "ENOENT") {
|
|
18
|
-
return { config: {}, path:
|
|
48
|
+
return { config: {}, path: configPath, loaded: false };
|
|
19
49
|
}
|
|
20
|
-
console.warn(`Failed to read ${
|
|
21
|
-
return { config: {}, path:
|
|
50
|
+
console.warn(`Failed to read ${configPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
51
|
+
return { config: {}, path: configPath, loaded: false };
|
|
22
52
|
}
|
|
23
53
|
}
|
|
24
54
|
export function configPath() {
|
|
25
|
-
return
|
|
55
|
+
return resolveUserConfigPath();
|
|
56
|
+
}
|
|
57
|
+
async function discoverProjectConfigPaths({ cwd, userConfigPath, }) {
|
|
58
|
+
const start = path.resolve(cwd);
|
|
59
|
+
const home = os.homedir();
|
|
60
|
+
const candidates = [];
|
|
61
|
+
const seen = new Set([path.resolve(userConfigPath)]);
|
|
62
|
+
let current = start;
|
|
63
|
+
while (true) {
|
|
64
|
+
if (current === home) {
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
const candidate = path.join(current, PROJECT_CONFIG_RELATIVE_PATH);
|
|
68
|
+
const resolved = path.resolve(candidate);
|
|
69
|
+
if (!seen.has(resolved)) {
|
|
70
|
+
try {
|
|
71
|
+
const stat = await fs.stat(resolved);
|
|
72
|
+
if (stat.isFile()) {
|
|
73
|
+
candidates.unshift(resolved);
|
|
74
|
+
seen.add(resolved);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
if (error.code !== "ENOENT") {
|
|
79
|
+
console.warn(`Failed to inspect ${resolved}: ${error instanceof Error ? error.message : String(error)}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const parent = path.dirname(current);
|
|
84
|
+
if (parent === current) {
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
current = parent;
|
|
88
|
+
}
|
|
89
|
+
return candidates;
|
|
90
|
+
}
|
|
91
|
+
function mergeUserConfig(base, override) {
|
|
92
|
+
return deepMerge(base, override);
|
|
93
|
+
}
|
|
94
|
+
function isRecord(value) {
|
|
95
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
96
|
+
}
|
|
97
|
+
function deepMerge(base, override) {
|
|
98
|
+
if (!isRecord(base) || !isRecord(override)) {
|
|
99
|
+
return override;
|
|
100
|
+
}
|
|
101
|
+
const result = { ...base };
|
|
102
|
+
for (const [key, value] of Object.entries(override)) {
|
|
103
|
+
const existing = result[key];
|
|
104
|
+
result[key] = isRecord(existing) && isRecord(value) ? deepMerge(existing, value) : value;
|
|
105
|
+
}
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
function sanitizeProjectConfig(config) {
|
|
109
|
+
const sanitized = {};
|
|
110
|
+
if (config.engine !== undefined)
|
|
111
|
+
sanitized.engine = config.engine;
|
|
112
|
+
if (config.model !== undefined)
|
|
113
|
+
sanitized.model = config.model;
|
|
114
|
+
if (config.search !== undefined)
|
|
115
|
+
sanitized.search = config.search;
|
|
116
|
+
if (config.maxFileSizeBytes !== undefined)
|
|
117
|
+
sanitized.maxFileSizeBytes = config.maxFileSizeBytes;
|
|
118
|
+
if (config.notify !== undefined)
|
|
119
|
+
sanitized.notify = config.notify;
|
|
120
|
+
if (config.heartbeatSeconds !== undefined)
|
|
121
|
+
sanitized.heartbeatSeconds = config.heartbeatSeconds;
|
|
122
|
+
if (config.filesReport !== undefined)
|
|
123
|
+
sanitized.filesReport = config.filesReport;
|
|
124
|
+
if (config.background !== undefined)
|
|
125
|
+
sanitized.background = config.background;
|
|
126
|
+
if (config.promptSuffix !== undefined)
|
|
127
|
+
sanitized.promptSuffix = config.promptSuffix;
|
|
128
|
+
if (config.browser) {
|
|
129
|
+
sanitized.browser = {};
|
|
130
|
+
const browser = config.browser;
|
|
131
|
+
const allowedBrowserKeys = [
|
|
132
|
+
"attachRunning",
|
|
133
|
+
"timeoutMs",
|
|
134
|
+
"inputTimeoutMs",
|
|
135
|
+
"attachmentTimeoutMs",
|
|
136
|
+
"assistantRecheckDelayMs",
|
|
137
|
+
"assistantRecheckTimeoutMs",
|
|
138
|
+
"reuseChromeWaitMs",
|
|
139
|
+
"profileLockTimeoutMs",
|
|
140
|
+
"maxConcurrentTabs",
|
|
141
|
+
"autoReattachDelayMs",
|
|
142
|
+
"autoReattachIntervalMs",
|
|
143
|
+
"autoReattachTimeoutMs",
|
|
144
|
+
"cookieSyncWaitMs",
|
|
145
|
+
"hideWindow",
|
|
146
|
+
"keepBrowser",
|
|
147
|
+
"modelStrategy",
|
|
148
|
+
"thinkingTime",
|
|
149
|
+
"researchMode",
|
|
150
|
+
"archiveConversations",
|
|
151
|
+
"manualLogin",
|
|
152
|
+
];
|
|
153
|
+
for (const key of allowedBrowserKeys) {
|
|
154
|
+
if (browser[key] !== undefined) {
|
|
155
|
+
sanitized.browser[key] = browser[key];
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
const chatgptUrl = browser.chatgptUrl ?? browser.url;
|
|
159
|
+
if (chatgptUrl === null ||
|
|
160
|
+
(chatgptUrl !== undefined && isTrustedProjectChatgptUrl(chatgptUrl))) {
|
|
161
|
+
sanitized.browser.chatgptUrl = chatgptUrl;
|
|
162
|
+
sanitized.browser.url = chatgptUrl;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return sanitized;
|
|
166
|
+
}
|
|
167
|
+
function isTrustedProjectChatgptUrl(rawUrl) {
|
|
168
|
+
if (!rawUrl) {
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
try {
|
|
172
|
+
const parsed = new URL(rawUrl);
|
|
173
|
+
if (parsed.protocol !== "https:") {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
return parsed.hostname === "chatgpt.com" || parsed.hostname === "chat.openai.com";
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
26
181
|
}
|
|
@@ -8,7 +8,14 @@ const DEFAULT_PROVIDER_HOSTS = {
|
|
|
8
8
|
openai: "api.openai.com",
|
|
9
9
|
xai: "api.x.ai",
|
|
10
10
|
};
|
|
11
|
+
export function resolveProviderRoute(input) {
|
|
12
|
+
return buildResolvedProviderRoute(input);
|
|
13
|
+
}
|
|
11
14
|
export function buildProviderRoutePlan(input) {
|
|
15
|
+
const { apiKey: _apiKey, baseUrl: _baseUrl, nativeProvider: _nativeProvider, openRouterFallback: _openRouterFallback, azureEndpoint: _azureEndpoint, ...plan } = buildResolvedProviderRoute(input);
|
|
16
|
+
return plan;
|
|
17
|
+
}
|
|
18
|
+
function buildResolvedProviderRoute(input) {
|
|
12
19
|
const env = input.env ?? process.env;
|
|
13
20
|
const providerMode = input.providerMode ?? "auto";
|
|
14
21
|
const azureConfigured = Boolean(input.azure?.endpoint?.trim());
|
|
@@ -49,7 +56,12 @@ export function buildProviderRoutePlan(input) {
|
|
|
49
56
|
keySource: key.source,
|
|
50
57
|
keyPreview: key.preview,
|
|
51
58
|
keyPresent: key.present,
|
|
59
|
+
apiKey: key.value,
|
|
60
|
+
nativeProvider: provider,
|
|
61
|
+
baseUrl: input.baseUrl,
|
|
62
|
+
openRouterFallback: false,
|
|
52
63
|
isAzureOpenAI,
|
|
64
|
+
azureEndpoint: state?.azureEndpoint ?? input.azure?.endpoint,
|
|
53
65
|
azureConfigured,
|
|
54
66
|
azureDeploymentName: state?.azureDeploymentName,
|
|
55
67
|
azureNote: azureNote(providerMode, azureConfigured, isAzureOpenAI),
|
|
@@ -145,7 +157,12 @@ export function buildProviderRoutePlan(input) {
|
|
|
145
157
|
keySource: key.source,
|
|
146
158
|
keyPreview: key.preview,
|
|
147
159
|
keyPresent: key.present,
|
|
160
|
+
apiKey: key.value,
|
|
161
|
+
nativeProvider: provider,
|
|
162
|
+
baseUrl,
|
|
163
|
+
openRouterFallback,
|
|
148
164
|
isAzureOpenAI,
|
|
165
|
+
azureEndpoint: state.azureEndpoint,
|
|
149
166
|
azureConfigured,
|
|
150
167
|
azureDeploymentName: state.azureDeploymentName,
|
|
151
168
|
azureNote: azureNote(providerMode, azureConfigured, isAzureOpenAI),
|
|
@@ -166,7 +183,12 @@ function getNativeKey({ model, provider, providerMode, isAzureOpenAI, apiKey, en
|
|
|
166
183
|
}
|
|
167
184
|
function getKeyForRoute({ model, provider, providerMode, isAzureOpenAI, baseUrl, openRouterFallback, apiKey, env, }) {
|
|
168
185
|
if (apiKey) {
|
|
169
|
-
return {
|
|
186
|
+
return {
|
|
187
|
+
source: "apiKey option",
|
|
188
|
+
preview: maskApiKey(apiKey) ?? "set",
|
|
189
|
+
present: true,
|
|
190
|
+
value: apiKey,
|
|
191
|
+
};
|
|
170
192
|
}
|
|
171
193
|
if (isAzureOpenAI) {
|
|
172
194
|
return readKey(["AZURE_OPENAI_API_KEY", "OPENAI_API_KEY"], env);
|
|
@@ -201,7 +223,12 @@ function readKey(names, env) {
|
|
|
201
223
|
for (const name of names) {
|
|
202
224
|
const value = env[name]?.trim();
|
|
203
225
|
if (value) {
|
|
204
|
-
return {
|
|
226
|
+
return {
|
|
227
|
+
source: name,
|
|
228
|
+
preview: `${name}=${maskApiKey(value) ?? "set"}`,
|
|
229
|
+
present: true,
|
|
230
|
+
value,
|
|
231
|
+
};
|
|
205
232
|
}
|
|
206
233
|
}
|
|
207
234
|
return { source: names.join("|"), preview: "missing", present: false };
|