@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
package/dist/src/oracle/run.js
CHANGED
|
@@ -14,7 +14,7 @@ import { getFileTokenStats, printFileTokenStats } from "./tokenStats.js";
|
|
|
14
14
|
import { OracleResponseError, OracleTransportError, PromptValidationError, describeTransportError, toTransportError, } from "./errors.js";
|
|
15
15
|
import { isCustomBaseUrl } from "./baseUrl.js";
|
|
16
16
|
import { createDefaultClientFactory } from "./client.js";
|
|
17
|
-
import {
|
|
17
|
+
import { maskApiKey } from "./logging.js";
|
|
18
18
|
import { startHeartbeat } from "../heartbeat.js";
|
|
19
19
|
import { startOscProgress } from "./oscProgress.js";
|
|
20
20
|
import { createFsAdapter } from "./fsAdapter.js";
|
|
@@ -25,60 +25,49 @@ import { createMarkdownStreamer } from "markdansi";
|
|
|
25
25
|
import { executeBackgroundResponse } from "./background.js";
|
|
26
26
|
import { formatTokenEstimate, formatTokenValue, resolvePreviewMode } from "./runUtils.js";
|
|
27
27
|
import { estimateUsdCost } from "tokentally";
|
|
28
|
-
import {
|
|
28
|
+
import { isOpenRouterBaseUrl, isProModel, resolveModelConfig } from "./modelResolver.js";
|
|
29
29
|
import { validateProviderRouting } from "./providerRouting.js";
|
|
30
|
+
import { formatRouteTargetForLog, resolveProviderRoute, } from "./providerRoutePlan.js";
|
|
30
31
|
const isStdoutTty = process.stdout.isTTY && chalk.level > 0;
|
|
31
32
|
const dim = (text) => (isStdoutTty ? kleur.dim(text) : text);
|
|
32
33
|
// Default timeout for non-pro API runs (fast models) — give them up to 120s.
|
|
33
34
|
const DEFAULT_TIMEOUT_NON_PRO_MS = 120_000;
|
|
34
35
|
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
|
-
};
|
|
41
36
|
const defaultWait = (ms) => new Promise((resolve) => {
|
|
42
37
|
setTimeout(resolve, ms);
|
|
43
38
|
});
|
|
44
|
-
function
|
|
45
|
-
if (
|
|
46
|
-
return
|
|
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;
|
|
39
|
+
function formatProviderRouteLogLine(route, keySource) {
|
|
40
|
+
if (route.isAzureOpenAI) {
|
|
41
|
+
return `Provider: Azure OpenAI | endpoint: ${formatRouteTargetForLog(route.azureEndpoint)} | deployment: ${route.azureDeploymentName || "none"} | key: ${keySource}`;
|
|
62
42
|
}
|
|
43
|
+
return `Provider: ${route.providerLabel} | base: ${route.base} | key: ${keySource}`;
|
|
63
44
|
}
|
|
64
|
-
function
|
|
65
|
-
if (
|
|
66
|
-
|
|
45
|
+
function runtimeKeySource({ route, providerMode, optionsApiKey, }) {
|
|
46
|
+
if (optionsApiKey &&
|
|
47
|
+
(route.isAzureOpenAI ||
|
|
48
|
+
providerMode === "openai" ||
|
|
49
|
+
route.provider === "openai" ||
|
|
50
|
+
route.providerLabel === "OpenAI-compatible")) {
|
|
51
|
+
return "apiKey option";
|
|
67
52
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
53
|
+
if (route.isAzureOpenAI) {
|
|
54
|
+
return "AZURE_OPENAI_API_KEY|OPENAI_API_KEY";
|
|
55
|
+
}
|
|
56
|
+
if (providerMode === "openai") {
|
|
57
|
+
return "OPENAI_API_KEY";
|
|
58
|
+
}
|
|
59
|
+
if (isOpenRouterBaseUrl(route.baseUrl) || route.openRouterFallback || route.model.includes("/")) {
|
|
60
|
+
return "OPENROUTER_API_KEY";
|
|
61
|
+
}
|
|
62
|
+
if (route.model.startsWith("gpt"))
|
|
63
|
+
return "OPENAI_API_KEY";
|
|
64
|
+
if (route.model.startsWith("gemini"))
|
|
65
|
+
return "GEMINI_API_KEY";
|
|
66
|
+
if (route.model.startsWith("claude"))
|
|
67
|
+
return "ANTHROPIC_API_KEY";
|
|
68
|
+
if (route.model.startsWith("grok"))
|
|
69
|
+
return "XAI_API_KEY";
|
|
70
|
+
return optionsApiKey ? "apiKey option" : route.keySource;
|
|
82
71
|
}
|
|
83
72
|
export async function runOracle(options, deps = {}) {
|
|
84
73
|
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;
|
|
@@ -86,13 +75,10 @@ export async function runOracle(options, deps = {}) {
|
|
|
86
75
|
? (stdoutWriteDep ?? process.stdout.write.bind(process.stdout))
|
|
87
76
|
: () => true;
|
|
88
77
|
const isTty = allowStdout && isStdoutTty;
|
|
89
|
-
const resolvedXaiBaseUrl = process.env.XAI_BASE_URL?.trim() || "https://api.x.ai/v1";
|
|
90
|
-
const openRouterApiKey = process.env.OPENROUTER_API_KEY?.trim();
|
|
91
|
-
const defaultOpenRouterBase = defaultOpenRouterBaseUrl();
|
|
92
78
|
const previewMode = resolvePreviewMode(options.previewMode ?? options.preview);
|
|
93
79
|
const isPreview = Boolean(previewMode);
|
|
94
80
|
const providerMode = options.provider ?? "auto";
|
|
95
|
-
|
|
81
|
+
validateProviderRouting({
|
|
96
82
|
model: options.model,
|
|
97
83
|
providerMode,
|
|
98
84
|
azure: options.azure,
|
|
@@ -103,99 +89,23 @@ export async function runOracle(options, deps = {}) {
|
|
|
103
89
|
}
|
|
104
90
|
},
|
|
105
91
|
});
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
!isOpenRouterBaseUrl(baseUrl) &&
|
|
118
|
-
!isCustomBaseUrl(baseUrl)) {
|
|
119
|
-
baseUrl = undefined;
|
|
120
|
-
}
|
|
121
|
-
if (!baseUrl) {
|
|
122
|
-
let envBaseUrl;
|
|
123
|
-
if (options.model.startsWith("grok")) {
|
|
124
|
-
envBaseUrl = resolvedXaiBaseUrl;
|
|
125
|
-
}
|
|
126
|
-
else if (provider === "anthropic") {
|
|
127
|
-
envBaseUrl = process.env.ANTHROPIC_BASE_URL?.trim();
|
|
128
|
-
}
|
|
129
|
-
else {
|
|
130
|
-
envBaseUrl = process.env.OPENAI_BASE_URL?.trim();
|
|
131
|
-
}
|
|
132
|
-
if (!providerQualifiedOpenRouterCandidate || (envBaseUrl && isCustomBaseUrl(envBaseUrl))) {
|
|
133
|
-
baseUrl = envBaseUrl;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
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))));
|
|
150
|
-
if (!baseUrl || openRouterFallback) {
|
|
151
|
-
if (openRouterFallback) {
|
|
152
|
-
baseUrl = defaultOpenRouterBase;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
if (baseUrl && isOpenRouterBaseUrl(baseUrl)) {
|
|
156
|
-
baseUrl = normalizeOpenRouterBaseUrl(baseUrl);
|
|
157
|
-
}
|
|
92
|
+
const route = resolveProviderRoute({
|
|
93
|
+
model: options.model,
|
|
94
|
+
providerMode,
|
|
95
|
+
azure: options.azure,
|
|
96
|
+
baseUrl: options.baseUrl,
|
|
97
|
+
apiKey: optionsApiKey,
|
|
98
|
+
env: process.env,
|
|
99
|
+
});
|
|
100
|
+
const { isAzureOpenAI, azureDeploymentName } = route;
|
|
101
|
+
const baseUrl = route.baseUrl;
|
|
102
|
+
const openRouterFallback = route.openRouterFallback;
|
|
158
103
|
const logVerbose = (message) => {
|
|
159
104
|
if (options.verbose) {
|
|
160
105
|
log(dim(`[verbose] ${message}`));
|
|
161
106
|
}
|
|
162
107
|
};
|
|
163
|
-
const
|
|
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
|
-
}
|
|
175
|
-
if (isOpenRouterBaseUrl(baseUrl) || openRouterFallback) {
|
|
176
|
-
return { key: optionsApiKey ?? openRouterApiKey, source: "OPENROUTER_API_KEY" };
|
|
177
|
-
}
|
|
178
|
-
if (typeof model === "string" && model.startsWith("gpt")) {
|
|
179
|
-
if (optionsApiKey)
|
|
180
|
-
return { key: optionsApiKey, source: "apiKey option" };
|
|
181
|
-
return { key: process.env.OPENAI_API_KEY, source: "OPENAI_API_KEY" };
|
|
182
|
-
}
|
|
183
|
-
if (typeof model === "string" && model.startsWith("gemini")) {
|
|
184
|
-
return { key: optionsApiKey ?? process.env.GEMINI_API_KEY, source: "GEMINI_API_KEY" };
|
|
185
|
-
}
|
|
186
|
-
if (typeof model === "string" && model.startsWith("claude")) {
|
|
187
|
-
return { key: optionsApiKey ?? process.env.ANTHROPIC_API_KEY, source: "ANTHROPIC_API_KEY" };
|
|
188
|
-
}
|
|
189
|
-
if (typeof model === "string" && model.startsWith("grok")) {
|
|
190
|
-
return { key: optionsApiKey ?? process.env.XAI_API_KEY, source: "XAI_API_KEY" };
|
|
191
|
-
}
|
|
192
|
-
return {
|
|
193
|
-
key: optionsApiKey ?? openRouterApiKey,
|
|
194
|
-
source: optionsApiKey ? "apiKey option" : "OPENROUTER_API_KEY",
|
|
195
|
-
};
|
|
196
|
-
};
|
|
197
|
-
const apiKeyResult = getApiKeyForModel(options.model);
|
|
198
|
-
const apiKey = apiKeyResult.key;
|
|
108
|
+
const apiKey = route.apiKey;
|
|
199
109
|
if (!apiKey) {
|
|
200
110
|
const envVar = isAzureOpenAI
|
|
201
111
|
? "AZURE_OPENAI_API_KEY (or OPENAI_API_KEY)"
|
|
@@ -203,15 +113,7 @@ export async function runOracle(options, deps = {}) {
|
|
|
203
113
|
? "OPENAI_API_KEY"
|
|
204
114
|
: isOpenRouterBaseUrl(baseUrl) || openRouterFallback
|
|
205
115
|
? "OPENROUTER_API_KEY"
|
|
206
|
-
:
|
|
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";
|
|
116
|
+
: route.keySource;
|
|
215
117
|
const browserModeHint = options.model.startsWith("gpt")
|
|
216
118
|
? ' 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.'
|
|
217
119
|
: "";
|
|
@@ -219,7 +121,7 @@ export async function runOracle(options, deps = {}) {
|
|
|
219
121
|
env: envVar,
|
|
220
122
|
});
|
|
221
123
|
}
|
|
222
|
-
const envVar =
|
|
124
|
+
const envVar = runtimeKeySource({ route, providerMode, optionsApiKey });
|
|
223
125
|
const minPromptLength = Number.parseInt(process.env.ORACLE_MIN_PROMPT_CHARS ?? "10", 10);
|
|
224
126
|
const promptLength = options.prompt?.trim().length ?? 0;
|
|
225
127
|
// Enforce the short-prompt guardrail on pro-tier models because they're costly; cheaper models can run short prompts without blocking.
|
|
@@ -227,7 +129,7 @@ export async function runOracle(options, deps = {}) {
|
|
|
227
129
|
if (isProTierModel && !Number.isNaN(minPromptLength) && promptLength < minPromptLength) {
|
|
228
130
|
throw new PromptValidationError(`Prompt is too short (<${minPromptLength} chars). This was likely accidental; please provide more detail.`, { minPromptLength, promptLength });
|
|
229
131
|
}
|
|
230
|
-
const resolverOpenRouterApiKey = openRouterFallback || isOpenRouterBaseUrl(baseUrl) ?
|
|
132
|
+
const resolverOpenRouterApiKey = openRouterFallback || isOpenRouterBaseUrl(baseUrl) ? apiKey : undefined;
|
|
231
133
|
const modelConfig = await resolveModelConfig(options.model, {
|
|
232
134
|
baseUrl,
|
|
233
135
|
openRouterApiKey: resolverOpenRouterApiKey,
|
|
@@ -326,15 +228,7 @@ export async function runOracle(options, deps = {}) {
|
|
|
326
228
|
if (!isPreview) {
|
|
327
229
|
if (!options.suppressHeader) {
|
|
328
230
|
log(headerLine);
|
|
329
|
-
log(dim(formatProviderRouteLogLine(
|
|
330
|
-
provider,
|
|
331
|
-
baseUrl,
|
|
332
|
-
openRouterFallback,
|
|
333
|
-
isAzureOpenAI,
|
|
334
|
-
azureEndpoint,
|
|
335
|
-
azureDeploymentName,
|
|
336
|
-
envVar,
|
|
337
|
-
})));
|
|
231
|
+
log(dim(formatProviderRouteLogLine(route, envVar)));
|
|
338
232
|
}
|
|
339
233
|
const maskedKey = maskApiKey(apiKey);
|
|
340
234
|
if (maskedKey && options.verbose) {
|
|
@@ -409,7 +303,7 @@ export async function runOracle(options, deps = {}) {
|
|
|
409
303
|
: proxyCompatibleBaseUrl
|
|
410
304
|
? proxyCompatibleBaseUrl
|
|
411
305
|
: modelConfig.model.startsWith("claude")
|
|
412
|
-
?
|
|
306
|
+
? baseUrl
|
|
413
307
|
: baseUrl;
|
|
414
308
|
const clientInstance = client ??
|
|
415
309
|
clientFactory(apiKey, {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
|
-
import { createWriteStream } from "node:fs";
|
|
3
|
+
import { createWriteStream, mkdirSync } from "node:fs";
|
|
4
4
|
import net from "node:net";
|
|
5
5
|
import { DEFAULT_MODEL } from "./oracle/config.js";
|
|
6
6
|
import { formatElapsed } from "./oracle/format.js";
|
|
@@ -86,14 +86,26 @@ async function fileExists(targetPath) {
|
|
|
86
86
|
return false;
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
|
-
|
|
89
|
+
function isFileExistsError(error) {
|
|
90
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "EEXIST";
|
|
91
|
+
}
|
|
92
|
+
async function reserveUniqueSessionDir(baseSlug) {
|
|
90
93
|
let candidate = baseSlug;
|
|
91
94
|
let suffix = 2;
|
|
92
|
-
|
|
95
|
+
for (;;) {
|
|
96
|
+
const dir = sessionDir(candidate);
|
|
97
|
+
try {
|
|
98
|
+
await fs.mkdir(dir, { recursive: false });
|
|
99
|
+
return candidate;
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
if (!isFileExistsError(error)) {
|
|
103
|
+
throw error;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
93
106
|
candidate = `${baseSlug}-${suffix}`;
|
|
94
107
|
suffix += 1;
|
|
95
108
|
}
|
|
96
|
-
return candidate;
|
|
97
109
|
}
|
|
98
110
|
async function listModelRunFiles(sessionId) {
|
|
99
111
|
const dir = modelsDir(sessionId);
|
|
@@ -153,9 +165,7 @@ export async function readModelRunMetadata(sessionId, model) {
|
|
|
153
165
|
export async function initializeSession(options, cwd, notifications, baseSlugOverride) {
|
|
154
166
|
await ensureSessionStorage();
|
|
155
167
|
const baseSlug = baseSlugOverride || createSessionId(options.prompt || DEFAULT_SLUG, options.slug);
|
|
156
|
-
const sessionId = await
|
|
157
|
-
const dir = sessionDir(sessionId);
|
|
158
|
-
await ensureDir(dir);
|
|
168
|
+
const sessionId = await reserveUniqueSessionDir(baseSlug);
|
|
159
169
|
const mode = options.mode ?? "api";
|
|
160
170
|
const browserConfig = options.browserConfig;
|
|
161
171
|
const modelList = Array.isArray(options.models) && options.models.length > 0
|
|
@@ -239,25 +249,25 @@ export async function initializeSession(options, cwd, notifications, baseSlugOve
|
|
|
239
249
|
return metadata;
|
|
240
250
|
}
|
|
241
251
|
export async function readSessionMetadata(sessionId) {
|
|
242
|
-
const modern = await readModernSessionMetadata(sessionId);
|
|
252
|
+
const modern = await readModernSessionMetadata(sessionId, { reconcile: true, persist: false });
|
|
243
253
|
if (modern) {
|
|
244
254
|
return modern;
|
|
245
255
|
}
|
|
246
|
-
const legacy = await readLegacySessionMetadata(sessionId);
|
|
256
|
+
const legacy = await readLegacySessionMetadata(sessionId, { reconcile: true, persist: false });
|
|
247
257
|
if (legacy) {
|
|
248
258
|
return legacy;
|
|
249
259
|
}
|
|
250
260
|
return null;
|
|
251
261
|
}
|
|
252
262
|
export async function updateSessionMetadata(sessionId, updates) {
|
|
253
|
-
const existing = (await readModernSessionMetadata(sessionId)) ??
|
|
254
|
-
(await readLegacySessionMetadata(sessionId)) ??
|
|
263
|
+
const existing = (await readModernSessionMetadata(sessionId, { reconcile: false, persist: false })) ??
|
|
264
|
+
(await readLegacySessionMetadata(sessionId, { reconcile: false, persist: false })) ??
|
|
255
265
|
{ id: sessionId };
|
|
256
266
|
const next = { ...existing, ...updates };
|
|
257
267
|
await fs.writeFile(metaPath(sessionId), JSON.stringify(next, null, 2), "utf8");
|
|
258
268
|
return next;
|
|
259
269
|
}
|
|
260
|
-
async function readModernSessionMetadata(sessionId) {
|
|
270
|
+
async function readModernSessionMetadata(sessionId, options) {
|
|
261
271
|
try {
|
|
262
272
|
const raw = await fs.readFile(metaPath(sessionId), "utf8");
|
|
263
273
|
const parsed = JSON.parse(raw);
|
|
@@ -265,25 +275,31 @@ async function readModernSessionMetadata(sessionId) {
|
|
|
265
275
|
return null;
|
|
266
276
|
}
|
|
267
277
|
const enriched = await attachModelRuns(parsed, sessionId);
|
|
268
|
-
|
|
269
|
-
return await markZombie(runtimeChecked, { persist: false });
|
|
278
|
+
return options.reconcile ? reconcileSessionMetadata(enriched, options) : enriched;
|
|
270
279
|
}
|
|
271
280
|
catch {
|
|
272
281
|
return null;
|
|
273
282
|
}
|
|
274
283
|
}
|
|
275
|
-
async function readLegacySessionMetadata(sessionId) {
|
|
284
|
+
async function readLegacySessionMetadata(sessionId, options) {
|
|
276
285
|
try {
|
|
277
286
|
const raw = await fs.readFile(legacySessionPath(sessionId), "utf8");
|
|
278
287
|
const parsed = JSON.parse(raw);
|
|
279
288
|
const enriched = await attachModelRuns(parsed, sessionId);
|
|
280
|
-
|
|
281
|
-
return await markZombie(runtimeChecked, { persist: false });
|
|
289
|
+
return options.reconcile ? reconcileSessionMetadata(enriched, options) : enriched;
|
|
282
290
|
}
|
|
283
291
|
catch {
|
|
284
292
|
return null;
|
|
285
293
|
}
|
|
286
294
|
}
|
|
295
|
+
async function readRawSessionMetadata(sessionId) {
|
|
296
|
+
return ((await readModernSessionMetadata(sessionId, { reconcile: false, persist: false })) ??
|
|
297
|
+
(await readLegacySessionMetadata(sessionId, { reconcile: false, persist: false })));
|
|
298
|
+
}
|
|
299
|
+
async function reconcileSessionMetadata(meta, { persist }) {
|
|
300
|
+
const runtimeChecked = await markDeadBrowser(meta, { persist });
|
|
301
|
+
return await markZombie(runtimeChecked, { persist });
|
|
302
|
+
}
|
|
287
303
|
function isSessionMetadataRecord(value) {
|
|
288
304
|
return Boolean(value && typeof value.id === "string" && value.status);
|
|
289
305
|
}
|
|
@@ -297,7 +313,7 @@ async function attachModelRuns(meta, sessionId) {
|
|
|
297
313
|
export function createSessionLogWriter(sessionId, model) {
|
|
298
314
|
const targetPath = model ? modelLogPath(sessionId, model) : logPath(sessionId);
|
|
299
315
|
if (model) {
|
|
300
|
-
|
|
316
|
+
mkdirSync(modelsDir(sessionId), { recursive: true });
|
|
301
317
|
}
|
|
302
318
|
const stream = createWriteStream(targetPath, { flags: "a" });
|
|
303
319
|
const logLine = (line = "") => {
|
|
@@ -314,10 +330,10 @@ export async function listSessionsMetadata() {
|
|
|
314
330
|
const entries = await fs.readdir(getSessionsDir()).catch(() => []);
|
|
315
331
|
const metas = [];
|
|
316
332
|
for (const entry of entries) {
|
|
317
|
-
let meta = await
|
|
333
|
+
let meta = await readRawSessionMetadata(entry);
|
|
318
334
|
if (meta) {
|
|
319
|
-
|
|
320
|
-
meta = await
|
|
335
|
+
// Keep stored metadata consistent with status reconciliation done by `oracle status`.
|
|
336
|
+
meta = await reconcileSessionMetadata(meta, { persist: true });
|
|
321
337
|
metas.push(meta);
|
|
322
338
|
}
|
|
323
339
|
}
|
|
@@ -388,7 +404,7 @@ export async function readModelLog(sessionId, model) {
|
|
|
388
404
|
}
|
|
389
405
|
}
|
|
390
406
|
export async function readSessionRequest(sessionId) {
|
|
391
|
-
const modern = await readModernSessionMetadata(sessionId);
|
|
407
|
+
const modern = await readModernSessionMetadata(sessionId, { reconcile: false, persist: false });
|
|
392
408
|
if (modern?.options) {
|
|
393
409
|
return modern.options;
|
|
394
410
|
}
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@steipete/oracle",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.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
|
-
"homepage": "https://
|
|
6
|
+
"homepage": "https://askoracle.sh",
|
|
7
7
|
"bugs": {
|
|
8
8
|
"url": "https://github.com/steipete/oracle/issues"
|
|
9
9
|
},
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"test": "vitest run",
|
|
47
47
|
"test:mcp": "pnpm run build && pnpm run test:mcp:unit && pnpm run test:mcp:mcporter",
|
|
48
48
|
"test:mcp:unit": "vitest run tests/mcp*.test.ts tests/mcp/**/*.test.ts",
|
|
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
|
+
"test:mcp:mcporter": "CHROME_DEVTOOLS_URL=http://127.0.0.1:0 pnpm dlx mcporter list oracle-local --schema --config config/mcporter.json && CHROME_DEVTOOLS_URL=http://127.0.0.1:0 pnpm dlx mcporter call oracle-local.sessions limit:1 --config config/mcporter.json",
|
|
50
50
|
"test:browser": "pnpm run build && tsx scripts/test-browser.ts && ./scripts/browser-smoke.sh",
|
|
51
51
|
"test:packed-cli": "node scripts/packed-cli-smoke.mjs",
|
|
52
52
|
"test:live": "ORACLE_LIVE_TEST=1 vitest run tests/live --exclude tests/live/openai-live.test.ts",
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"@google/genai": "^2.0.1",
|
|
62
62
|
"@google/generative-ai": "^0.24.1",
|
|
63
63
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
64
|
-
"@steipete/sweet-cookie": "^0.
|
|
64
|
+
"@steipete/sweet-cookie": "^0.3.0",
|
|
65
65
|
"chalk": "^5.6.2",
|
|
66
66
|
"chrome-launcher": "^1.2.1",
|
|
67
67
|
"chrome-remote-interface": "^0.34.0",
|
|
@@ -70,11 +70,11 @@
|
|
|
70
70
|
"dotenv": "^17.4.2",
|
|
71
71
|
"fast-glob": "^3.3.3",
|
|
72
72
|
"gpt-tokenizer": "^3.4.0",
|
|
73
|
-
"inquirer": "13.4.
|
|
73
|
+
"inquirer": "13.4.3",
|
|
74
74
|
"json5": "^2.2.3",
|
|
75
75
|
"kleur": "^4.1.5",
|
|
76
76
|
"markdansi": "0.2.1",
|
|
77
|
-
"openai": "^6.
|
|
77
|
+
"openai": "^6.38.0",
|
|
78
78
|
"osc-progress": "^0.3.0",
|
|
79
79
|
"qs": "^6.15.1",
|
|
80
80
|
"shiki": "^4.0.2",
|
|
@@ -84,17 +84,17 @@
|
|
|
84
84
|
},
|
|
85
85
|
"devDependencies": {
|
|
86
86
|
"@anthropic-ai/tokenizer": "^0.0.4",
|
|
87
|
-
"@types/chrome-remote-interface": "^0.
|
|
87
|
+
"@types/chrome-remote-interface": "^0.34.0",
|
|
88
88
|
"@types/inquirer": "^9.0.9",
|
|
89
89
|
"@types/node": "^25.6.0",
|
|
90
|
-
"@typescript/native-preview": "7.0.0-dev.
|
|
91
|
-
"@vitest/coverage-v8": "4.1.
|
|
92
|
-
"devtools-protocol": "0.0.
|
|
90
|
+
"@typescript/native-preview": "7.0.0-dev.20260516.1",
|
|
91
|
+
"@vitest/coverage-v8": "4.1.6",
|
|
92
|
+
"devtools-protocol": "0.0.1629771",
|
|
93
93
|
"es-toolkit": "^1.46.1",
|
|
94
94
|
"esbuild": "^0.28.0",
|
|
95
|
-
"oxfmt": "0.
|
|
95
|
+
"oxfmt": "0.50.0",
|
|
96
96
|
"oxlint": "^1.62.0",
|
|
97
|
-
"puppeteer-core": "^
|
|
97
|
+
"puppeteer-core": "^25.0.2",
|
|
98
98
|
"tsx": "^4.21.0",
|
|
99
99
|
"typescript": "^6.0.3",
|
|
100
100
|
"vitest": "^4.1.5"
|
|
@@ -113,7 +113,8 @@
|
|
|
113
113
|
"packageManager": "pnpm@10.33.2",
|
|
114
114
|
"pnpm": {
|
|
115
115
|
"overrides": {
|
|
116
|
-
"devtools-protocol": "0.0.
|
|
116
|
+
"devtools-protocol": "0.0.1629771",
|
|
117
|
+
"vite": "7.3.2"
|
|
117
118
|
},
|
|
118
119
|
"onlyBuiltDependencies": [
|
|
119
120
|
"esbuild"
|