@steipete/oracle 0.9.0 → 0.11.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/LICENSE +1 -1
- package/README.md +107 -49
- package/dist/bin/oracle-cli.js +551 -410
- package/dist/bin/oracle-mcp.js +2 -2
- package/dist/bin/oracle.js +165 -279
- package/dist/scripts/agent-send.js +31 -31
- package/dist/scripts/check.js +6 -6
- package/dist/scripts/debug/extract-chatgpt-response.js +10 -10
- package/dist/scripts/docs-list.js +30 -30
- package/dist/scripts/git-policy.js +25 -23
- package/dist/scripts/run-cli.js +8 -8
- package/dist/scripts/runner.js +203 -195
- package/dist/scripts/test-browser.js +21 -18
- package/dist/scripts/test-remote-chrome.js +20 -20
- package/dist/src/bridge/connection.js +18 -18
- package/dist/src/bridge/userConfigFile.js +7 -7
- package/dist/src/browser/actions/archiveConversation.js +224 -0
- package/dist/src/browser/actions/assistantResponse.js +175 -101
- package/dist/src/browser/actions/attachmentDataTransfer.js +49 -47
- package/dist/src/browser/actions/attachments.js +246 -150
- package/dist/src/browser/actions/deepResearch.js +662 -0
- package/dist/src/browser/actions/domEvents.js +2 -2
- package/dist/src/browser/actions/modelSelection.js +342 -119
- package/dist/src/browser/actions/navigation.js +183 -137
- package/dist/src/browser/actions/projectSources.js +491 -0
- package/dist/src/browser/actions/promptComposer.js +152 -91
- package/dist/src/browser/actions/remoteFileTransfer.js +10 -10
- package/dist/src/browser/actions/thinkingStatus.js +391 -0
- package/dist/src/browser/actions/thinkingTime.js +207 -110
- package/dist/src/browser/artifacts.js +150 -0
- package/dist/src/browser/attachRunning.js +31 -0
- package/dist/src/browser/chatgptImages.js +315 -0
- package/dist/src/browser/chromeLifecycle.js +276 -63
- package/dist/src/browser/config.js +59 -16
- package/dist/src/browser/constants.js +25 -12
- package/dist/src/browser/controlPlan.js +81 -0
- package/dist/src/browser/cookies.js +19 -19
- package/dist/src/browser/detect.js +250 -77
- package/dist/src/browser/domDebug.js +50 -1
- package/dist/src/browser/index.js +1559 -692
- package/dist/src/browser/liveTabs.js +434 -0
- package/dist/src/browser/modelStrategy.js +1 -1
- package/dist/src/browser/pageActions.js +5 -5
- package/dist/src/browser/policies.js +16 -13
- package/dist/src/browser/profileState.js +127 -42
- package/dist/src/browser/projectSourcesRunner.js +366 -0
- package/dist/src/browser/prompt.js +72 -42
- package/dist/src/browser/promptSummary.js +5 -5
- package/dist/src/browser/providerDomFlow.js +1 -1
- package/dist/src/browser/providers/chatgptDomProvider.js +9 -9
- package/dist/src/browser/providers/geminiDeepThinkDomProvider.js +51 -42
- package/dist/src/browser/providers/index.js +2 -2
- package/dist/src/browser/reattach.js +178 -73
- package/dist/src/browser/reattachHelpers.js +32 -27
- package/dist/src/browser/sessionRunner.js +89 -25
- package/dist/src/browser/tabLeaseRegistry.js +182 -0
- package/dist/src/browser/utils.js +9 -9
- package/dist/src/browserMode.js +1 -1
- package/dist/src/cli/bridge/claudeConfig.js +24 -20
- package/dist/src/cli/bridge/client.js +28 -20
- package/dist/src/cli/bridge/codexConfig.js +16 -16
- package/dist/src/cli/bridge/doctor.js +47 -39
- package/dist/src/cli/bridge/host.js +58 -56
- package/dist/src/cli/browserConfig.js +102 -48
- package/dist/src/cli/browserDefaults.js +51 -26
- package/dist/src/cli/browserTabs.js +228 -0
- package/dist/src/cli/bundleWarnings.js +1 -1
- package/dist/src/cli/clipboard.js +11 -2
- package/dist/src/cli/detach.js +2 -2
- package/dist/src/cli/dryRun.js +62 -26
- package/dist/src/cli/duplicatePromptGuard.js +12 -4
- package/dist/src/cli/engine.js +9 -9
- package/dist/src/cli/errorUtils.js +1 -1
- package/dist/src/cli/fileSize.js +3 -3
- package/dist/src/cli/format.js +2 -2
- package/dist/src/cli/help.js +28 -28
- package/dist/src/cli/hiddenAliases.js +3 -3
- package/dist/src/cli/markdownBundle.js +7 -7
- package/dist/src/cli/markdownRenderer.js +15 -15
- package/dist/src/cli/notifier.js +77 -67
- package/dist/src/cli/options.js +131 -106
- package/dist/src/cli/oscUtils.js +1 -1
- package/dist/src/cli/projectSources.js +116 -0
- package/dist/src/cli/promptRequirement.js +2 -2
- package/dist/src/cli/renderOutput.js +1 -1
- package/dist/src/cli/rootAlias.js +1 -1
- package/dist/src/cli/runOptions.js +32 -28
- package/dist/src/cli/sessionCommand.js +82 -21
- package/dist/src/cli/sessionDisplay.js +213 -87
- package/dist/src/cli/sessionLineage.js +6 -2
- package/dist/src/cli/sessionRunner.js +149 -95
- package/dist/src/cli/sessionTable.js +26 -23
- package/dist/src/cli/stdin.js +22 -0
- package/dist/src/cli/tagline.js +121 -124
- package/dist/src/cli/tui/index.js +139 -128
- package/dist/src/cli/writeOutputPath.js +5 -5
- package/dist/src/config.js +7 -7
- package/dist/src/gemini-web/browserSessionManager.js +19 -15
- package/dist/src/gemini-web/client.js +76 -70
- package/dist/src/gemini-web/executionMode.js +6 -8
- package/dist/src/gemini-web/executor.js +98 -93
- package/dist/src/gemini-web/index.js +1 -1
- package/dist/src/mcp/consultPresets.js +19 -0
- package/dist/src/mcp/server.js +18 -12
- package/dist/src/mcp/tools/consult.js +246 -67
- package/dist/src/mcp/tools/projectSources.js +123 -0
- package/dist/src/mcp/tools/sessionResources.js +12 -12
- package/dist/src/mcp/tools/sessions.js +26 -17
- package/dist/src/mcp/types.js +12 -5
- package/dist/src/mcp/utils.js +21 -8
- package/dist/src/oracle/background.js +15 -15
- package/dist/src/oracle/claude.js +53 -25
- package/dist/src/oracle/client.js +50 -41
- package/dist/src/oracle/config.js +96 -66
- package/dist/src/oracle/errors.js +38 -38
- package/dist/src/oracle/files.js +55 -46
- package/dist/src/oracle/finishLine.js +10 -8
- package/dist/src/oracle/format.js +3 -3
- package/dist/src/oracle/gemini.js +37 -33
- package/dist/src/oracle/logging.js +7 -7
- package/dist/src/oracle/markdown.js +28 -28
- package/dist/src/oracle/modelResolver.js +16 -16
- package/dist/src/oracle/multiModelRunner.js +12 -12
- package/dist/src/oracle/oscProgress.js +8 -8
- package/dist/src/oracle/promptAssembly.js +6 -3
- package/dist/src/oracle/request.js +16 -13
- package/dist/src/oracle/run.js +160 -135
- package/dist/src/oracle/runUtils.js +8 -5
- package/dist/src/oracle/tokenEstimate.js +6 -6
- package/dist/src/oracle/tokenStats.js +5 -5
- package/dist/src/oracle/tokenStringifier.js +5 -5
- package/dist/src/oracle.js +12 -12
- package/dist/src/oracleHome.js +3 -3
- package/dist/src/projectSources/plan.js +27 -0
- package/dist/src/projectSources/url.js +23 -0
- package/dist/src/remote/client.js +25 -25
- package/dist/src/remote/health.js +20 -20
- package/dist/src/remote/remoteServiceConfig.js +9 -9
- package/dist/src/remote/server.js +129 -118
- package/dist/src/sessionManager.js +78 -75
- package/dist/src/sessionStore.js +3 -3
- package/dist/src/version.js +10 -10
- package/dist/vendor/oracle-notifier/README.md +2 -0
- package/package.json +67 -62
- package/vendor/oracle-notifier/README.md +2 -0
- package/dist/markdansi/types/index.js +0 -4
- package/dist/oracle/bin/oracle-cli.js +0 -472
- package/dist/oracle/src/browser/actions/assistantResponse.js +0 -471
- package/dist/oracle/src/browser/actions/attachments.js +0 -82
- package/dist/oracle/src/browser/actions/modelSelection.js +0 -190
- package/dist/oracle/src/browser/actions/navigation.js +0 -75
- package/dist/oracle/src/browser/actions/promptComposer.js +0 -167
- package/dist/oracle/src/browser/chromeLifecycle.js +0 -104
- package/dist/oracle/src/browser/config.js +0 -33
- package/dist/oracle/src/browser/constants.js +0 -40
- package/dist/oracle/src/browser/cookies.js +0 -210
- package/dist/oracle/src/browser/domDebug.js +0 -36
- package/dist/oracle/src/browser/index.js +0 -331
- package/dist/oracle/src/browser/pageActions.js +0 -5
- package/dist/oracle/src/browser/prompt.js +0 -88
- package/dist/oracle/src/browser/promptSummary.js +0 -20
- package/dist/oracle/src/browser/sessionRunner.js +0 -80
- package/dist/oracle/src/browser/utils.js +0 -62
- package/dist/oracle/src/browserMode.js +0 -1
- package/dist/oracle/src/cli/browserConfig.js +0 -44
- package/dist/oracle/src/cli/dryRun.js +0 -59
- package/dist/oracle/src/cli/engine.js +0 -17
- package/dist/oracle/src/cli/errorUtils.js +0 -9
- package/dist/oracle/src/cli/help.js +0 -70
- package/dist/oracle/src/cli/markdownRenderer.js +0 -15
- package/dist/oracle/src/cli/options.js +0 -103
- package/dist/oracle/src/cli/promptRequirement.js +0 -14
- package/dist/oracle/src/cli/rootAlias.js +0 -30
- package/dist/oracle/src/cli/sessionCommand.js +0 -77
- package/dist/oracle/src/cli/sessionDisplay.js +0 -270
- package/dist/oracle/src/cli/sessionRunner.js +0 -94
- package/dist/oracle/src/heartbeat.js +0 -43
- package/dist/oracle/src/oracle/client.js +0 -48
- package/dist/oracle/src/oracle/config.js +0 -29
- package/dist/oracle/src/oracle/errors.js +0 -101
- package/dist/oracle/src/oracle/files.js +0 -220
- package/dist/oracle/src/oracle/format.js +0 -33
- package/dist/oracle/src/oracle/fsAdapter.js +0 -7
- package/dist/oracle/src/oracle/oscProgress.js +0 -60
- package/dist/oracle/src/oracle/request.js +0 -48
- package/dist/oracle/src/oracle/run.js +0 -444
- package/dist/oracle/src/oracle/tokenStats.js +0 -39
- package/dist/oracle/src/oracle/types.js +0 -1
- package/dist/oracle/src/oracle.js +0 -9
- package/dist/oracle/src/sessionManager.js +0 -205
- package/dist/oracle/src/version.js +0 -39
- package/dist/scripts/chrome/browser-tools.js +0 -295
- package/dist/src/browser/profileSync.js +0 -141
- /package/dist/{oracle/src/browser → src/projectSources}/types.js +0 -0
|
@@ -1,21 +1,24 @@
|
|
|
1
|
-
import { z } from
|
|
2
|
-
import { sessionStore } from
|
|
3
|
-
import { sessionsInputSchema } from
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { sessionStore } from "../../sessionStore.js";
|
|
3
|
+
import { sessionsInputSchema } from "../types.js";
|
|
4
4
|
const sessionsInputShape = {
|
|
5
5
|
id: z
|
|
6
6
|
.string()
|
|
7
7
|
.optional()
|
|
8
|
-
.describe(
|
|
9
|
-
hours: z
|
|
10
|
-
|
|
8
|
+
.describe("Session id or slug. If set, returns a single session (use detail:true to include metadata/request)."),
|
|
9
|
+
hours: z
|
|
10
|
+
.number()
|
|
11
|
+
.optional()
|
|
12
|
+
.describe("Look back this many hours when listing sessions (default: 24)."),
|
|
13
|
+
limit: z.number().optional().describe("Maximum sessions to return when listing (default: 100)."),
|
|
11
14
|
includeAll: z
|
|
12
15
|
.boolean()
|
|
13
16
|
.optional()
|
|
14
|
-
.describe(
|
|
17
|
+
.describe("Include sessions outside the time window when listing (mirrors `oracle status --all`)."),
|
|
15
18
|
detail: z
|
|
16
19
|
.boolean()
|
|
17
20
|
.optional()
|
|
18
|
-
.describe(
|
|
21
|
+
.describe("When id is set, include session metadata + stored request + full log text."),
|
|
19
22
|
};
|
|
20
23
|
const sessionsOutputShape = {
|
|
21
24
|
entries: z
|
|
@@ -38,14 +41,14 @@ const sessionsOutputShape = {
|
|
|
38
41
|
.optional(),
|
|
39
42
|
};
|
|
40
43
|
export function registerSessionsTool(server) {
|
|
41
|
-
server.registerTool(
|
|
42
|
-
title:
|
|
43
|
-
description:
|
|
44
|
+
server.registerTool("sessions", {
|
|
45
|
+
title: "List or fetch oracle sessions",
|
|
46
|
+
description: "Inspect Oracle session history stored under `ORACLE_HOME_DIR` (shared with the CLI). List recent sessions or fetch one by id/slug (optionally including metadata + request + log).",
|
|
44
47
|
inputSchema: sessionsInputShape,
|
|
45
48
|
outputSchema: sessionsOutputShape,
|
|
46
49
|
}, async (input) => {
|
|
47
|
-
const textContent = (text) => [{ type:
|
|
48
|
-
const { id, hours = 24, limit = 100, includeAll = false, detail = false } = sessionsInputSchema.parse(input);
|
|
50
|
+
const textContent = (text) => [{ type: "text", text }];
|
|
51
|
+
const { id, hours = 24, limit = 100, includeAll = false, detail = false, } = sessionsInputSchema.parse(input);
|
|
49
52
|
if (id) {
|
|
50
53
|
if (!detail) {
|
|
51
54
|
const metadata = await sessionStore.readSession(id);
|
|
@@ -53,7 +56,7 @@ export function registerSessionsTool(server) {
|
|
|
53
56
|
throw new Error(`Session "${id}" not found.`);
|
|
54
57
|
}
|
|
55
58
|
return {
|
|
56
|
-
content: textContent(`${metadata.createdAt} | ${metadata.status} | ${metadata.model ??
|
|
59
|
+
content: textContent(`${metadata.createdAt} | ${metadata.status} | ${metadata.model ?? "n/a"} | ${metadata.id}`),
|
|
57
60
|
structuredContent: {
|
|
58
61
|
entries: [
|
|
59
62
|
{
|
|
@@ -81,12 +84,18 @@ export function registerSessionsTool(server) {
|
|
|
81
84
|
};
|
|
82
85
|
}
|
|
83
86
|
const metas = await sessionStore.listSessions();
|
|
84
|
-
const { entries, truncated, total } = sessionStore.filterSessions(metas, {
|
|
87
|
+
const { entries, truncated, total } = sessionStore.filterSessions(metas, {
|
|
88
|
+
hours,
|
|
89
|
+
includeAll,
|
|
90
|
+
limit,
|
|
91
|
+
});
|
|
85
92
|
return {
|
|
86
93
|
content: [
|
|
87
94
|
{
|
|
88
|
-
type:
|
|
89
|
-
text: entries
|
|
95
|
+
type: "text",
|
|
96
|
+
text: entries
|
|
97
|
+
.map((entry) => `${entry.createdAt} | ${entry.status} | ${entry.model ?? "n/a"} | ${entry.id}`)
|
|
98
|
+
.join("\n"),
|
|
90
99
|
},
|
|
91
100
|
],
|
|
92
101
|
structuredContent: {
|
package/dist/src/mcp/types.js
CHANGED
|
@@ -1,15 +1,22 @@
|
|
|
1
|
-
import { z } from
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const CONSULT_PRESETS = ["chatgpt-pro-heavy"];
|
|
2
3
|
export const consultInputSchema = z.object({
|
|
3
|
-
|
|
4
|
+
preset: z.enum(CONSULT_PRESETS).optional(),
|
|
5
|
+
prompt: z.string().min(1, "Prompt is required."),
|
|
4
6
|
files: z.array(z.string()).default([]),
|
|
5
7
|
model: z.string().optional(),
|
|
6
8
|
models: z.array(z.string()).optional(),
|
|
7
|
-
engine: z.enum([
|
|
9
|
+
engine: z.enum(["api", "browser"]).optional(),
|
|
8
10
|
browserModelLabel: z.string().optional(),
|
|
9
|
-
browserAttachments: z.enum([
|
|
11
|
+
browserAttachments: z.enum(["auto", "never", "always"]).optional(),
|
|
10
12
|
browserBundleFiles: z.boolean().optional(),
|
|
11
|
-
browserThinkingTime: z.enum([
|
|
13
|
+
browserThinkingTime: z.enum(["light", "standard", "extended", "heavy"]).optional(),
|
|
14
|
+
browserModelStrategy: z.enum(["select", "current", "ignore"]).optional(),
|
|
15
|
+
browserResearchMode: z.enum(["deep"]).optional(),
|
|
16
|
+
browserArchive: z.enum(["auto", "always", "never"]).optional(),
|
|
17
|
+
browserFollowUps: z.array(z.string()).optional(),
|
|
12
18
|
browserKeepBrowser: z.boolean().optional(),
|
|
19
|
+
dryRun: z.boolean().optional(),
|
|
13
20
|
search: z.boolean().optional(),
|
|
14
21
|
slug: z.string().optional(),
|
|
15
22
|
});
|
package/dist/src/mcp/utils.js
CHANGED
|
@@ -1,25 +1,38 @@
|
|
|
1
|
-
import { resolveRunOptionsFromConfig } from
|
|
2
|
-
import { Launcher } from
|
|
3
|
-
export function mapConsultToRunOptions({ prompt, files, model, models, engine, search, browserAttachments, browserBundleFiles, userConfig, env = process.env, }) {
|
|
1
|
+
import { resolveRunOptionsFromConfig } from "../cli/runOptions.js";
|
|
2
|
+
import { Launcher } from "chrome-launcher";
|
|
3
|
+
export function mapConsultToRunOptions({ prompt, files, model, models, engine, search, browserAttachments, browserBundleFiles, browserFollowUps, userConfig, env = process.env, }) {
|
|
4
4
|
// Normalize CLI-style inputs through the shared resolver so config/env defaults apply,
|
|
5
5
|
// then overlay MCP-only overrides such as explicit search toggles.
|
|
6
6
|
const mergedModels = Array.isArray(models) && models.length > 0
|
|
7
7
|
? [model, ...models].filter((entry) => Boolean(entry?.trim()))
|
|
8
8
|
: models;
|
|
9
|
-
const result = resolveRunOptionsFromConfig({
|
|
10
|
-
|
|
9
|
+
const result = resolveRunOptionsFromConfig({
|
|
10
|
+
prompt,
|
|
11
|
+
files,
|
|
12
|
+
model,
|
|
13
|
+
models: mergedModels,
|
|
14
|
+
engine,
|
|
15
|
+
userConfig,
|
|
16
|
+
env,
|
|
17
|
+
});
|
|
18
|
+
if (typeof search === "boolean") {
|
|
11
19
|
result.runOptions.search = search;
|
|
12
20
|
}
|
|
13
21
|
if (browserAttachments) {
|
|
14
22
|
result.runOptions.browserAttachments = browserAttachments;
|
|
15
23
|
}
|
|
16
|
-
if (typeof browserBundleFiles ===
|
|
24
|
+
if (typeof browserBundleFiles === "boolean") {
|
|
17
25
|
result.runOptions.browserBundleFiles = browserBundleFiles;
|
|
18
26
|
}
|
|
27
|
+
if (Array.isArray(browserFollowUps)) {
|
|
28
|
+
result.runOptions.browserFollowUps = browserFollowUps
|
|
29
|
+
.map((entry) => entry.trim())
|
|
30
|
+
.filter(Boolean);
|
|
31
|
+
}
|
|
19
32
|
return result;
|
|
20
33
|
}
|
|
21
34
|
export function ensureBrowserAvailable(engine, options) {
|
|
22
|
-
if (engine !==
|
|
35
|
+
if (engine !== "browser") {
|
|
23
36
|
return null;
|
|
24
37
|
}
|
|
25
38
|
const remoteHost = options?.remoteHost?.trim() || process.env.ORACLE_REMOTE_HOST?.trim();
|
|
@@ -31,7 +44,7 @@ export function ensureBrowserAvailable(engine, options) {
|
|
|
31
44
|
}
|
|
32
45
|
const found = Launcher.getFirstInstallation();
|
|
33
46
|
if (!found) {
|
|
34
|
-
return
|
|
47
|
+
return "Browser engine unavailable: no Chrome installation found and CHROME_PATH is unset.";
|
|
35
48
|
}
|
|
36
49
|
return null;
|
|
37
50
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { APIConnectionError, APIConnectionTimeoutError } from
|
|
2
|
-
import chalk from
|
|
3
|
-
import { formatElapsed } from
|
|
4
|
-
import { startHeartbeat } from
|
|
5
|
-
import { OracleResponseError, OracleTransportError, describeTransportError, toTransportError, } from
|
|
1
|
+
import { APIConnectionError, APIConnectionTimeoutError } from "openai";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { formatElapsed } from "./format.js";
|
|
4
|
+
import { startHeartbeat } from "../heartbeat.js";
|
|
5
|
+
import { OracleResponseError, OracleTransportError, describeTransportError, toTransportError, } from "./errors.js";
|
|
6
6
|
const BACKGROUND_POLL_INTERVAL_MS = 5000;
|
|
7
7
|
const BACKGROUND_RETRY_BASE_MS = 3000;
|
|
8
8
|
const BACKGROUND_RETRY_MAX_MS = 15000;
|
|
@@ -18,10 +18,10 @@ export async function executeBackgroundResponse(params) {
|
|
|
18
18
|
throw transportError;
|
|
19
19
|
}
|
|
20
20
|
if (!initialResponse || !initialResponse.id) {
|
|
21
|
-
throw new OracleResponseError(
|
|
21
|
+
throw new OracleResponseError("API did not return a response ID for the background run.", initialResponse);
|
|
22
22
|
}
|
|
23
23
|
const responseId = initialResponse.id;
|
|
24
|
-
log(chalk.dim(`API scheduled background response ${responseId} (status=${initialResponse.status ??
|
|
24
|
+
log(chalk.dim(`API scheduled background response ${responseId} (status=${initialResponse.status ?? "unknown"}). Monitoring up to ${Math.round(maxWaitMs / 60000)} minutes for completion...`));
|
|
25
25
|
let heartbeatActive = false;
|
|
26
26
|
let stopHeartbeat = null;
|
|
27
27
|
const stopHeartbeatNow = () => {
|
|
@@ -66,29 +66,29 @@ async function pollBackgroundResponse(params) {
|
|
|
66
66
|
let lastStatus = response.status;
|
|
67
67
|
// biome-ignore lint/nursery/noUnnecessaryConditions: intentional polling loop.
|
|
68
68
|
while (true) {
|
|
69
|
-
const status = response.status ??
|
|
69
|
+
const status = response.status ?? "completed";
|
|
70
70
|
// firstCycle toggles immediately; keep for clarity in logs.
|
|
71
71
|
if (firstCycle) {
|
|
72
72
|
firstCycle = false;
|
|
73
73
|
log(chalk.dim(`API background response status=${status}. We'll keep retrying automatically.`));
|
|
74
74
|
}
|
|
75
|
-
else if (status !== lastStatus && status !==
|
|
75
|
+
else if (status !== lastStatus && status !== "completed") {
|
|
76
76
|
log(chalk.dim(`API background response status=${status}.`));
|
|
77
77
|
}
|
|
78
78
|
lastStatus = status;
|
|
79
|
-
if (status ===
|
|
79
|
+
if (status === "completed") {
|
|
80
80
|
return response;
|
|
81
81
|
}
|
|
82
|
-
if (status !==
|
|
82
|
+
if (status !== "in_progress" && status !== "queued") {
|
|
83
83
|
const detail = response.error?.message || response.incomplete_details?.reason || status;
|
|
84
84
|
throw new OracleResponseError(`Response did not complete: ${detail}`, response);
|
|
85
85
|
}
|
|
86
86
|
if (now() - startMark >= maxWaitMs) {
|
|
87
|
-
throw new OracleTransportError(
|
|
87
|
+
throw new OracleTransportError("client-timeout", "Timed out waiting for API background response to finish.");
|
|
88
88
|
}
|
|
89
89
|
await wait(BACKGROUND_POLL_INTERVAL_MS);
|
|
90
90
|
if (now() - startMark >= maxWaitMs) {
|
|
91
|
-
throw new OracleTransportError(
|
|
91
|
+
throw new OracleTransportError("client-timeout", "Timed out waiting for API background response to finish.");
|
|
92
92
|
}
|
|
93
93
|
const { response: nextResponse, reconnected } = await retrieveBackgroundResponseWithRetry({
|
|
94
94
|
client,
|
|
@@ -100,7 +100,7 @@ async function pollBackgroundResponse(params) {
|
|
|
100
100
|
log,
|
|
101
101
|
});
|
|
102
102
|
if (reconnected) {
|
|
103
|
-
const nextStatus = nextResponse.status ??
|
|
103
|
+
const nextStatus = nextResponse.status ?? "in_progress";
|
|
104
104
|
log(chalk.dim(`Reconnected to API background response (status=${nextStatus}). API is still working...`));
|
|
105
105
|
}
|
|
106
106
|
response = nextResponse;
|
|
@@ -125,7 +125,7 @@ async function retrieveBackgroundResponseWithRetry(params) {
|
|
|
125
125
|
log(chalk.yellow(`${describeTransportError(transportError, maxWaitMs)} Retrying in ${formatElapsed(delay)}...`));
|
|
126
126
|
await wait(delay);
|
|
127
127
|
if (now() - startMark >= maxWaitMs) {
|
|
128
|
-
throw new OracleTransportError(
|
|
128
|
+
throw new OracleTransportError("client-timeout", "Timed out waiting for API background response to finish.");
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
131
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
const DEFAULT_CLAUDE_ENDPOINT =
|
|
2
|
-
const ANTHROPIC_VERSION =
|
|
1
|
+
const DEFAULT_CLAUDE_ENDPOINT = "https://api.anthropic.com/v1/messages";
|
|
2
|
+
const ANTHROPIC_VERSION = "2023-06-01";
|
|
3
3
|
function extractPrompt(body) {
|
|
4
4
|
const first = body.input?.[0]?.content?.[0];
|
|
5
|
-
if (first && first.type ===
|
|
6
|
-
return first.text ??
|
|
5
|
+
if (first && first.type === "input_text") {
|
|
6
|
+
return first.text ?? "";
|
|
7
7
|
}
|
|
8
|
-
return
|
|
8
|
+
return "";
|
|
9
9
|
}
|
|
10
10
|
async function callClaude({ apiKey, model, prompt, endpoint, stream = false, }) {
|
|
11
11
|
const url = endpoint?.trim() || DEFAULT_CLAUDE_ENDPOINT;
|
|
@@ -14,34 +14,45 @@ async function callClaude({ apiKey, model, prompt, endpoint, stream = false, })
|
|
|
14
14
|
max_tokens: 2048,
|
|
15
15
|
messages: [
|
|
16
16
|
{
|
|
17
|
-
role:
|
|
17
|
+
role: "user",
|
|
18
18
|
content: prompt,
|
|
19
19
|
},
|
|
20
20
|
],
|
|
21
21
|
stream,
|
|
22
22
|
};
|
|
23
23
|
return fetch(url, {
|
|
24
|
-
method:
|
|
24
|
+
method: "POST",
|
|
25
25
|
headers: {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
"content-type": "application/json",
|
|
27
|
+
"x-api-key": apiKey,
|
|
28
|
+
"anthropic-version": ANTHROPIC_VERSION,
|
|
29
29
|
},
|
|
30
30
|
body: JSON.stringify(payload),
|
|
31
31
|
});
|
|
32
32
|
}
|
|
33
33
|
async function parseClaudeResponse(raw) {
|
|
34
|
-
const
|
|
34
|
+
const body = await raw.text();
|
|
35
|
+
if (!body.trim()) {
|
|
36
|
+
throw new Error(`Claude request failed (${raw.status} ${raw.statusText || "unknown status"}): empty response`);
|
|
37
|
+
}
|
|
38
|
+
let json;
|
|
39
|
+
try {
|
|
40
|
+
json = JSON.parse(body);
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
const snippet = body.slice(0, 160).replace(/\s+/g, " ").trim();
|
|
44
|
+
throw new Error(`Claude request failed (${raw.status} ${raw.statusText || "unknown status"}): invalid JSON response${snippet ? `: ${snippet}` : ""}`, { cause: error });
|
|
45
|
+
}
|
|
35
46
|
if (json.error) {
|
|
36
|
-
throw new Error(json.error.message ||
|
|
47
|
+
throw new Error(json.error.message || "Claude request failed");
|
|
37
48
|
}
|
|
38
|
-
const textParts = json.content?.map((part) => part.text ??
|
|
39
|
-
const outputText = textParts.join(
|
|
49
|
+
const textParts = json.content?.map((part) => part.text ?? "").filter(Boolean) ?? [];
|
|
50
|
+
const outputText = textParts.join("");
|
|
40
51
|
return {
|
|
41
52
|
id: json.id ?? `claude-${Date.now()}`,
|
|
42
|
-
status:
|
|
53
|
+
status: "completed",
|
|
43
54
|
output_text: [outputText],
|
|
44
|
-
output: [{ type:
|
|
55
|
+
output: [{ type: "text", text: outputText }],
|
|
45
56
|
usage: {
|
|
46
57
|
input_tokens: json.usage?.input_tokens ?? 0,
|
|
47
58
|
output_tokens: json.usage?.output_tokens ?? 0,
|
|
@@ -53,11 +64,17 @@ export function createClaudeClient(apiKey, modelName, resolvedModelId, baseUrl)
|
|
|
53
64
|
const modelId = resolveClaudeModelId(resolvedModelId ?? modelName);
|
|
54
65
|
const stream = async (body) => {
|
|
55
66
|
const prompt = extractPrompt(body);
|
|
56
|
-
const resp = await callClaude({
|
|
67
|
+
const resp = await callClaude({
|
|
68
|
+
apiKey,
|
|
69
|
+
model: modelId,
|
|
70
|
+
prompt,
|
|
71
|
+
stream: false,
|
|
72
|
+
endpoint: baseUrl,
|
|
73
|
+
});
|
|
57
74
|
const parsed = await parseClaudeResponse(resp);
|
|
58
75
|
const iterator = async function* () {
|
|
59
76
|
if (parsed.output_text?.[0]) {
|
|
60
|
-
yield { type:
|
|
77
|
+
yield { type: "response.output_text.delta", delta: parsed.output_text[0] };
|
|
61
78
|
}
|
|
62
79
|
return;
|
|
63
80
|
};
|
|
@@ -68,13 +85,19 @@ export function createClaudeClient(apiKey, modelName, resolvedModelId, baseUrl)
|
|
|
68
85
|
};
|
|
69
86
|
const create = async (body) => {
|
|
70
87
|
const prompt = extractPrompt(body);
|
|
71
|
-
const resp = await callClaude({
|
|
88
|
+
const resp = await callClaude({
|
|
89
|
+
apiKey,
|
|
90
|
+
model: modelId,
|
|
91
|
+
prompt,
|
|
92
|
+
stream: false,
|
|
93
|
+
endpoint: baseUrl,
|
|
94
|
+
});
|
|
72
95
|
return parseClaudeResponse(resp);
|
|
73
96
|
};
|
|
74
97
|
const retrieve = async (id) => ({
|
|
75
98
|
id,
|
|
76
|
-
status:
|
|
77
|
-
error: { message:
|
|
99
|
+
status: "error",
|
|
100
|
+
error: { message: "Retrieve by ID not supported for Claude API yet." },
|
|
78
101
|
});
|
|
79
102
|
return {
|
|
80
103
|
responses: {
|
|
@@ -85,11 +108,16 @@ export function createClaudeClient(apiKey, modelName, resolvedModelId, baseUrl)
|
|
|
85
108
|
};
|
|
86
109
|
}
|
|
87
110
|
export function resolveClaudeModelId(modelName) {
|
|
88
|
-
if (modelName ===
|
|
89
|
-
return
|
|
111
|
+
if (modelName === "claude-4.6-sonnet" || modelName === "claude-sonnet-4-6") {
|
|
112
|
+
return "claude-sonnet-4-6";
|
|
113
|
+
}
|
|
114
|
+
if (modelName === "claude-4.5-sonnet" ||
|
|
115
|
+
modelName === "claude-sonnet-4-5" ||
|
|
116
|
+
modelName === "claude-sonnet-4-5-20250929") {
|
|
117
|
+
return "claude-sonnet-4-5";
|
|
90
118
|
}
|
|
91
|
-
if (modelName ===
|
|
92
|
-
return
|
|
119
|
+
if (modelName === "claude-4.1-opus" || modelName === "claude-opus-4-1-20240808") {
|
|
120
|
+
return "claude-opus-4-1";
|
|
93
121
|
}
|
|
94
122
|
return modelName;
|
|
95
123
|
}
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import OpenAI from
|
|
2
|
-
import path from
|
|
3
|
-
import { createRequire } from
|
|
4
|
-
import { createGeminiClient } from
|
|
5
|
-
import { createClaudeClient } from
|
|
6
|
-
import { isOpenRouterBaseUrl } from
|
|
1
|
+
import OpenAI from "openai";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import { createGeminiClient } from "./gemini.js";
|
|
5
|
+
import { createClaudeClient } from "./claude.js";
|
|
6
|
+
import { isOpenRouterBaseUrl } from "./modelResolver.js";
|
|
7
7
|
/**
|
|
8
8
|
* Known native API base URLs that should still use their dedicated SDKs.
|
|
9
9
|
* Any other custom base URL is treated as an OpenAI-compatible proxy and
|
|
10
10
|
* all models are routed through the chat/completions adapter.
|
|
11
11
|
*/
|
|
12
12
|
const NATIVE_API_HOSTS = [
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
"api.openai.com",
|
|
14
|
+
"api.anthropic.com",
|
|
15
|
+
"generativelanguage.googleapis.com",
|
|
16
|
+
"api.x.ai",
|
|
17
17
|
];
|
|
18
18
|
export function isCustomBaseUrl(baseUrl) {
|
|
19
19
|
if (!baseUrl)
|
|
@@ -27,7 +27,7 @@ export function isCustomBaseUrl(baseUrl) {
|
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
export function buildAzureResponsesBaseUrl(endpoint) {
|
|
30
|
-
return `${endpoint.replace(/\/+$/,
|
|
30
|
+
return `${endpoint.replace(/\/+$/, "")}/openai/v1`;
|
|
31
31
|
}
|
|
32
32
|
export function createDefaultClientFactory() {
|
|
33
33
|
const customFactory = loadCustomClientFactory();
|
|
@@ -40,17 +40,21 @@ export function createDefaultClientFactory() {
|
|
|
40
40
|
// route ALL models through the OpenAI chat/completions adapter instead of native SDKs
|
|
41
41
|
// which would reject the proxy's API key.
|
|
42
42
|
if (!openRouter && !customProxy) {
|
|
43
|
-
if (options?.model?.startsWith(
|
|
43
|
+
if (options?.model?.startsWith("gemini")) {
|
|
44
44
|
// Gemini client uses its own SDK; allow passing the already-resolved id for transparency/logging.
|
|
45
45
|
return createGeminiClient(key, options.model, options.resolvedModelId);
|
|
46
46
|
}
|
|
47
|
-
if (options?.model?.startsWith(
|
|
47
|
+
if (options?.model?.startsWith("claude")) {
|
|
48
48
|
return createClaudeClient(key, options.model, options.resolvedModelId, options.baseUrl);
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
let instance;
|
|
52
|
-
const defaultHeaders = openRouter
|
|
53
|
-
|
|
52
|
+
const defaultHeaders = openRouter
|
|
53
|
+
? buildOpenRouterHeaders()
|
|
54
|
+
: undefined;
|
|
55
|
+
const httpTimeoutMs = typeof options?.httpTimeoutMs === "number" &&
|
|
56
|
+
Number.isFinite(options.httpTimeoutMs) &&
|
|
57
|
+
options.httpTimeoutMs > 0
|
|
54
58
|
? options.httpTimeoutMs
|
|
55
59
|
: 20 * 60 * 1000;
|
|
56
60
|
if (options?.azure?.endpoint) {
|
|
@@ -82,13 +86,15 @@ export function createDefaultClientFactory() {
|
|
|
82
86
|
}
|
|
83
87
|
function buildOpenRouterHeaders() {
|
|
84
88
|
const headers = {};
|
|
85
|
-
const referer = process.env.OPENROUTER_REFERER ??
|
|
86
|
-
|
|
89
|
+
const referer = process.env.OPENROUTER_REFERER ??
|
|
90
|
+
process.env.OPENROUTER_HTTP_REFERER ??
|
|
91
|
+
"https://github.com/steipete/oracle";
|
|
92
|
+
const title = process.env.OPENROUTER_TITLE ?? "Oracle CLI";
|
|
87
93
|
if (referer) {
|
|
88
|
-
headers[
|
|
94
|
+
headers["HTTP-Referer"] = referer;
|
|
89
95
|
}
|
|
90
96
|
if (title) {
|
|
91
|
-
headers[
|
|
97
|
+
headers["X-Title"] = title;
|
|
92
98
|
}
|
|
93
99
|
return headers;
|
|
94
100
|
}
|
|
@@ -97,19 +103,19 @@ function loadCustomClientFactory() {
|
|
|
97
103
|
if (!override) {
|
|
98
104
|
return null;
|
|
99
105
|
}
|
|
100
|
-
if (override ===
|
|
106
|
+
if (override === "INLINE_TEST_FACTORY") {
|
|
101
107
|
return () => ({
|
|
102
108
|
responses: {
|
|
103
|
-
create: async () => ({ id:
|
|
109
|
+
create: async () => ({ id: "inline-test", status: "completed" }),
|
|
104
110
|
stream: async () => ({
|
|
105
111
|
[Symbol.asyncIterator]: () => ({
|
|
106
112
|
async next() {
|
|
107
113
|
return { done: true, value: undefined };
|
|
108
114
|
},
|
|
109
115
|
}),
|
|
110
|
-
finalResponse: async () => ({ id:
|
|
116
|
+
finalResponse: async () => ({ id: "inline-test", status: "completed" }),
|
|
111
117
|
}),
|
|
112
|
-
retrieve: async (id) => ({ id, status:
|
|
118
|
+
retrieve: async (id) => ({ id, status: "completed" }),
|
|
113
119
|
},
|
|
114
120
|
});
|
|
115
121
|
}
|
|
@@ -117,14 +123,14 @@ function loadCustomClientFactory() {
|
|
|
117
123
|
const require = createRequire(import.meta.url);
|
|
118
124
|
const resolved = path.isAbsolute(override) ? override : path.resolve(process.cwd(), override);
|
|
119
125
|
const moduleExports = require(resolved);
|
|
120
|
-
const factory = typeof moduleExports ===
|
|
126
|
+
const factory = typeof moduleExports === "function"
|
|
121
127
|
? moduleExports
|
|
122
|
-
: typeof moduleExports?.default ===
|
|
128
|
+
: typeof moduleExports?.default === "function"
|
|
123
129
|
? moduleExports.default
|
|
124
|
-
: typeof moduleExports?.createClientFactory ===
|
|
130
|
+
: typeof moduleExports?.createClientFactory === "function"
|
|
125
131
|
? moduleExports.createClientFactory
|
|
126
132
|
: null;
|
|
127
|
-
if (typeof factory ===
|
|
133
|
+
if (typeof factory === "function") {
|
|
128
134
|
return factory;
|
|
129
135
|
}
|
|
130
136
|
console.warn(`Custom client factory at ${resolved} did not export a function.`);
|
|
@@ -140,14 +146,17 @@ function buildOpenRouterCompletionClient(instance) {
|
|
|
140
146
|
const adaptRequest = (body) => {
|
|
141
147
|
const messages = [];
|
|
142
148
|
if (body.instructions) {
|
|
143
|
-
messages.push({ role:
|
|
149
|
+
messages.push({ role: "system", content: body.instructions });
|
|
144
150
|
}
|
|
145
151
|
for (const entry of body.input) {
|
|
146
152
|
const textParts = entry.content
|
|
147
|
-
.map((c) => (c.type ===
|
|
153
|
+
.map((c) => (c.type === "input_text" ? c.text : ""))
|
|
148
154
|
.filter((t) => t)
|
|
149
|
-
.join(
|
|
150
|
-
messages.push({
|
|
155
|
+
.join("\n\n");
|
|
156
|
+
messages.push({
|
|
157
|
+
role: entry.role ?? "user",
|
|
158
|
+
content: textParts,
|
|
159
|
+
});
|
|
151
160
|
}
|
|
152
161
|
const base = {
|
|
153
162
|
model: body.model,
|
|
@@ -159,7 +168,7 @@ function buildOpenRouterCompletionClient(instance) {
|
|
|
159
168
|
return { streaming, nonStreaming };
|
|
160
169
|
};
|
|
161
170
|
const adaptResponse = (response) => {
|
|
162
|
-
const text = response.choices?.[0]?.message?.content ??
|
|
171
|
+
const text = response.choices?.[0]?.message?.content ?? "";
|
|
163
172
|
const usage = {
|
|
164
173
|
input_tokens: response.usage?.prompt_tokens ?? 0,
|
|
165
174
|
output_tokens: response.usage?.completion_tokens ?? 0,
|
|
@@ -167,9 +176,9 @@ function buildOpenRouterCompletionClient(instance) {
|
|
|
167
176
|
};
|
|
168
177
|
return {
|
|
169
178
|
id: response.id ?? `openrouter-${Date.now()}`,
|
|
170
|
-
status:
|
|
179
|
+
status: "completed",
|
|
171
180
|
output_text: [text],
|
|
172
|
-
output: [{ type:
|
|
181
|
+
output: [{ type: "text", text }],
|
|
173
182
|
usage,
|
|
174
183
|
};
|
|
175
184
|
};
|
|
@@ -177,15 +186,15 @@ function buildOpenRouterCompletionClient(instance) {
|
|
|
177
186
|
const { streaming } = adaptRequest(body);
|
|
178
187
|
let finalUsage;
|
|
179
188
|
let finalId;
|
|
180
|
-
let aggregated =
|
|
189
|
+
let aggregated = "";
|
|
181
190
|
async function* iterator() {
|
|
182
191
|
const completion = await instance.chat.completions.create(streaming);
|
|
183
192
|
for await (const chunk of completion) {
|
|
184
193
|
finalId = chunk.id ?? finalId;
|
|
185
|
-
const delta = chunk.choices?.[0]?.delta?.content ??
|
|
194
|
+
const delta = chunk.choices?.[0]?.delta?.content ?? "";
|
|
186
195
|
if (delta) {
|
|
187
196
|
aggregated += delta;
|
|
188
|
-
yield { type:
|
|
197
|
+
yield { type: "chunk", delta };
|
|
189
198
|
}
|
|
190
199
|
if (chunk.usage) {
|
|
191
200
|
finalUsage = chunk.usage;
|
|
@@ -200,11 +209,11 @@ function buildOpenRouterCompletionClient(instance) {
|
|
|
200
209
|
async finalResponse() {
|
|
201
210
|
return adaptResponse({
|
|
202
211
|
id: finalId ?? `openrouter-${Date.now()}`,
|
|
203
|
-
choices: [{ message: { role:
|
|
212
|
+
choices: [{ message: { role: "assistant", content: aggregated } }],
|
|
204
213
|
usage: finalUsage ?? { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },
|
|
205
214
|
created: Math.floor(Date.now() / 1000),
|
|
206
|
-
model:
|
|
207
|
-
object:
|
|
215
|
+
model: "",
|
|
216
|
+
object: "chat.completion",
|
|
208
217
|
});
|
|
209
218
|
},
|
|
210
219
|
};
|
|
@@ -219,7 +228,7 @@ function buildOpenRouterCompletionClient(instance) {
|
|
|
219
228
|
stream,
|
|
220
229
|
create,
|
|
221
230
|
retrieve: async () => {
|
|
222
|
-
throw new Error(
|
|
231
|
+
throw new Error("retrieve is not supported for OpenRouter chat/completions fallback.");
|
|
223
232
|
},
|
|
224
233
|
},
|
|
225
234
|
};
|