@steipete/oracle 1.1.0 → 1.3.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.
Files changed (69) hide show
  1. package/README.md +40 -7
  2. package/assets-oracle-icon.png +0 -0
  3. package/dist/.DS_Store +0 -0
  4. package/dist/bin/oracle-cli.js +315 -47
  5. package/dist/bin/oracle-mcp.js +6 -0
  6. package/dist/src/browser/actions/modelSelection.js +117 -29
  7. package/dist/src/browser/config.js +6 -0
  8. package/dist/src/browser/cookies.js +50 -12
  9. package/dist/src/browser/index.js +19 -5
  10. package/dist/src/browser/prompt.js +6 -5
  11. package/dist/src/browser/sessionRunner.js +14 -3
  12. package/dist/src/cli/browserConfig.js +109 -2
  13. package/dist/src/cli/detach.js +12 -0
  14. package/dist/src/cli/dryRun.js +60 -8
  15. package/dist/src/cli/engine.js +7 -0
  16. package/dist/src/cli/help.js +3 -1
  17. package/dist/src/cli/hiddenAliases.js +17 -0
  18. package/dist/src/cli/markdownRenderer.js +79 -0
  19. package/dist/src/cli/notifier.js +223 -0
  20. package/dist/src/cli/options.js +22 -0
  21. package/dist/src/cli/promptRequirement.js +3 -0
  22. package/dist/src/cli/runOptions.js +43 -0
  23. package/dist/src/cli/sessionCommand.js +1 -1
  24. package/dist/src/cli/sessionDisplay.js +94 -7
  25. package/dist/src/cli/sessionRunner.js +32 -2
  26. package/dist/src/cli/tui/index.js +457 -0
  27. package/dist/src/config.js +27 -0
  28. package/dist/src/mcp/server.js +36 -0
  29. package/dist/src/mcp/tools/consult.js +158 -0
  30. package/dist/src/mcp/tools/sessionResources.js +64 -0
  31. package/dist/src/mcp/tools/sessions.js +106 -0
  32. package/dist/src/mcp/types.js +17 -0
  33. package/dist/src/mcp/utils.js +24 -0
  34. package/dist/src/oracle/client.js +24 -6
  35. package/dist/src/oracle/config.js +10 -0
  36. package/dist/src/oracle/files.js +151 -8
  37. package/dist/src/oracle/format.js +2 -7
  38. package/dist/src/oracle/fsAdapter.js +4 -1
  39. package/dist/src/oracle/gemini.js +161 -0
  40. package/dist/src/oracle/logging.js +36 -0
  41. package/dist/src/oracle/oscProgress.js +7 -1
  42. package/dist/src/oracle/run.js +148 -64
  43. package/dist/src/oracle/tokenEstimate.js +34 -0
  44. package/dist/src/oracle.js +1 -0
  45. package/dist/src/sessionManager.js +50 -3
  46. package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
  47. package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/Info.plist +20 -0
  48. package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
  49. package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/Resources/OracleIcon.icns +0 -0
  50. package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/_CodeSignature/CodeResources +128 -0
  51. package/dist/vendor/oracle-notifier/OracleNotifier.swift +45 -0
  52. package/dist/vendor/oracle-notifier/README.md +24 -0
  53. package/dist/vendor/oracle-notifier/build-notifier.sh +93 -0
  54. package/dist/vendor/oracle-notifier/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
  55. package/dist/vendor/oracle-notifier/oracle-notifier/OracleNotifier.app/Contents/Info.plist +20 -0
  56. package/dist/vendor/oracle-notifier/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
  57. package/dist/vendor/oracle-notifier/oracle-notifier/OracleNotifier.app/Contents/Resources/OracleIcon.icns +0 -0
  58. package/dist/vendor/oracle-notifier/oracle-notifier/OracleNotifier.app/Contents/_CodeSignature/CodeResources +128 -0
  59. package/dist/vendor/oracle-notifier/oracle-notifier/OracleNotifier.swift +45 -0
  60. package/dist/vendor/oracle-notifier/oracle-notifier/README.md +24 -0
  61. package/dist/vendor/oracle-notifier/oracle-notifier/build-notifier.sh +93 -0
  62. package/package.json +22 -6
  63. package/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
  64. package/vendor/oracle-notifier/OracleNotifier.app/Contents/Info.plist +20 -0
  65. package/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
  66. package/vendor/oracle-notifier/OracleNotifier.app/Contents/Resources/OracleIcon.icns +0 -0
  67. package/vendor/oracle-notifier/OracleNotifier.app/Contents/_CodeSignature/CodeResources +128 -0
  68. package/vendor/oracle-notifier/OracleNotifier.swift +45 -0
  69. package/vendor/oracle-notifier/build-notifier.sh +93 -0
@@ -0,0 +1,27 @@
1
+ import fs from 'node:fs/promises';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import JSON5 from 'json5';
5
+ function resolveConfigPath() {
6
+ const oracleHome = process.env.ORACLE_HOME_DIR ?? path.join(os.homedir(), '.oracle');
7
+ return path.join(oracleHome, 'config.json');
8
+ }
9
+ export async function loadUserConfig() {
10
+ const CONFIG_PATH = resolveConfigPath();
11
+ try {
12
+ const raw = await fs.readFile(CONFIG_PATH, 'utf8');
13
+ const parsed = JSON5.parse(raw);
14
+ return { config: parsed ?? {}, path: CONFIG_PATH, loaded: true };
15
+ }
16
+ catch (error) {
17
+ const code = error.code;
18
+ if (code === 'ENOENT') {
19
+ return { config: {}, path: CONFIG_PATH, loaded: false };
20
+ }
21
+ console.warn(`Failed to read ${CONFIG_PATH}: ${error instanceof Error ? error.message : String(error)}`);
22
+ return { config: {}, path: CONFIG_PATH, loaded: false };
23
+ }
24
+ }
25
+ export function configPath() {
26
+ return resolveConfigPath();
27
+ }
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env node
2
+ import 'dotenv/config';
3
+ import process from 'node:process';
4
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
5
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
6
+ import { getCliVersion } from '../version.js';
7
+ import { registerConsultTool } from './tools/consult.js';
8
+ import { registerSessionsTool } from './tools/sessions.js';
9
+ import { registerSessionResources } from './tools/sessionResources.js';
10
+ export async function startMcpServer() {
11
+ const server = new McpServer({
12
+ name: 'oracle-mcp',
13
+ version: getCliVersion(),
14
+ }, {
15
+ capabilities: {
16
+ logging: {},
17
+ },
18
+ });
19
+ registerConsultTool(server);
20
+ registerSessionsTool(server);
21
+ registerSessionResources(server);
22
+ const transport = new StdioServerTransport();
23
+ transport.onerror = (error) => {
24
+ console.error('MCP transport error:', error);
25
+ };
26
+ transport.onclose = () => {
27
+ // Keep quiet on normal close; caller owns lifecycle.
28
+ };
29
+ await server.connect(transport);
30
+ }
31
+ if (import.meta.url === `file://${process.argv[1]}` || process.argv[1]?.endsWith('oracle-mcp')) {
32
+ startMcpServer().catch((error) => {
33
+ console.error('Failed to start oracle-mcp:', error);
34
+ process.exitCode = 1;
35
+ });
36
+ }
@@ -0,0 +1,158 @@
1
+ import { z } from 'zod';
2
+ import { getCliVersion } from '../../version.js';
3
+ import { LoggingMessageNotificationParamsSchema } from '@modelcontextprotocol/sdk/types.js';
4
+ import { ensureBrowserAvailable, mapConsultToRunOptions } from '../utils.js';
5
+ import { createSessionLogWriter, initializeSession, readSessionMetadata, readSessionLog, } from '../../sessionManager.js';
6
+ async function readSessionLogTail(sessionId, maxBytes) {
7
+ try {
8
+ const log = await readSessionLog(sessionId);
9
+ if (log.length <= maxBytes) {
10
+ return log;
11
+ }
12
+ return log.slice(-maxBytes);
13
+ }
14
+ catch {
15
+ return null;
16
+ }
17
+ }
18
+ import { performSessionRun } from '../../cli/sessionRunner.js';
19
+ import { CHATGPT_URL } from '../../browser/constants.js';
20
+ import { consultInputSchema } from '../types.js';
21
+ import { loadUserConfig } from '../../config.js';
22
+ import { resolveNotificationSettings } from '../../cli/notifier.js';
23
+ import { mapModelToBrowserLabel, resolveBrowserModelLabel } from '../../cli/browserConfig.js';
24
+ // Use raw shapes so the MCP SDK (with its bundled Zod) wraps them and emits valid JSON Schema.
25
+ const consultInputShape = {
26
+ prompt: z.string().min(1, 'Prompt is required.'),
27
+ files: z.array(z.string()).default([]),
28
+ model: z.string().optional(),
29
+ engine: z.enum(['api', 'browser']).optional(),
30
+ browserModelLabel: z.string().optional(),
31
+ search: z.boolean().optional(),
32
+ slug: z.string().optional(),
33
+ };
34
+ const consultOutputShape = {
35
+ sessionId: z.string(),
36
+ status: z.string(),
37
+ output: z.string(),
38
+ };
39
+ export function registerConsultTool(server) {
40
+ server.registerTool('consult', {
41
+ title: 'Run an oracle session',
42
+ description: 'Run a one-shot Oracle session (API or browser). Attach files/dirs for context, optional model/engine overrides, and an optional slug. Background handling follows the CLI defaults; browser runs only start when Chrome is available.',
43
+ // Cast to any to satisfy SDK typings across differing Zod versions.
44
+ inputSchema: consultInputShape,
45
+ outputSchema: consultOutputShape,
46
+ }, async (input) => {
47
+ const textContent = (text) => [{ type: 'text', text }];
48
+ const { prompt, files, model, engine, search, browserModelLabel, slug } = consultInputSchema.parse(input);
49
+ const { config: userConfig } = await loadUserConfig();
50
+ const { runOptions, resolvedEngine } = mapConsultToRunOptions({
51
+ prompt,
52
+ files: files ?? [],
53
+ model,
54
+ engine,
55
+ search,
56
+ userConfig,
57
+ env: process.env,
58
+ });
59
+ const cwd = process.cwd();
60
+ const browserGuard = ensureBrowserAvailable(resolvedEngine);
61
+ if (resolvedEngine === 'browser' &&
62
+ (browserGuard ||
63
+ (process.platform === 'linux' && !process.env.DISPLAY && !process.env.CHROME_PATH))) {
64
+ return {
65
+ isError: true,
66
+ content: textContent(browserGuard ?? 'Browser engine unavailable: set DISPLAY or CHROME_PATH.'),
67
+ };
68
+ }
69
+ let browserConfig;
70
+ if (resolvedEngine === 'browser') {
71
+ const preferredLabel = (browserModelLabel ?? model)?.trim();
72
+ const desiredModelLabel = resolveBrowserModelLabel(preferredLabel, runOptions.model);
73
+ // Keep the browser path minimal; only forward a desired model label for the ChatGPT picker.
74
+ browserConfig = {
75
+ url: CHATGPT_URL,
76
+ cookieSync: true,
77
+ headless: false,
78
+ hideWindow: false,
79
+ keepBrowser: false,
80
+ desiredModel: desiredModelLabel || mapModelToBrowserLabel(runOptions.model),
81
+ };
82
+ }
83
+ const notifications = resolveNotificationSettings({
84
+ cliNotify: undefined,
85
+ cliNotifySound: undefined,
86
+ env: process.env,
87
+ config: userConfig.notify,
88
+ });
89
+ const sessionMeta = await initializeSession({
90
+ ...runOptions,
91
+ mode: resolvedEngine,
92
+ slug,
93
+ browserConfig,
94
+ }, cwd, notifications);
95
+ const logWriter = createSessionLogWriter(sessionMeta.id);
96
+ // Best-effort: emit MCP logging notifications for live chunks but never block the run.
97
+ const sendLog = (text, level = 'info') => server.server
98
+ .sendLoggingMessage(LoggingMessageNotificationParamsSchema.parse({
99
+ level,
100
+ data: { text, bytes: Buffer.byteLength(text, 'utf8') },
101
+ }))
102
+ .catch(() => { });
103
+ // Stream logs to both the session log and MCP logging notifications, but avoid buffering in memory
104
+ const log = (line) => {
105
+ logWriter.logLine(line);
106
+ if (line !== undefined) {
107
+ sendLog(line);
108
+ }
109
+ };
110
+ const write = (chunk) => {
111
+ logWriter.writeChunk(chunk);
112
+ sendLog(chunk, 'debug');
113
+ return true;
114
+ };
115
+ try {
116
+ await performSessionRun({
117
+ sessionMeta,
118
+ runOptions,
119
+ mode: resolvedEngine,
120
+ browserConfig,
121
+ cwd,
122
+ log,
123
+ write,
124
+ version: getCliVersion(),
125
+ notifications,
126
+ });
127
+ }
128
+ catch (error) {
129
+ log(`Run failed: ${error instanceof Error ? error.message : String(error)}`);
130
+ return {
131
+ isError: true,
132
+ content: textContent(`Session ${sessionMeta.id} failed: ${error instanceof Error ? error.message : String(error)}`),
133
+ };
134
+ }
135
+ finally {
136
+ logWriter.stream.end();
137
+ }
138
+ try {
139
+ const finalMeta = (await readSessionMetadata(sessionMeta.id)) ?? sessionMeta;
140
+ const summary = `Session ${sessionMeta.id} (${finalMeta.status})`;
141
+ const logTail = await readSessionLogTail(sessionMeta.id, 4000);
142
+ return {
143
+ content: textContent([summary, logTail || '(log empty)'].join('\n').trim()),
144
+ structuredContent: {
145
+ sessionId: sessionMeta.id,
146
+ status: finalMeta.status,
147
+ output: logTail ?? '',
148
+ },
149
+ };
150
+ }
151
+ catch (error) {
152
+ return {
153
+ isError: true,
154
+ content: textContent(`Session completed but metadata fetch failed: ${error instanceof Error ? error.message : String(error)}`),
155
+ };
156
+ }
157
+ });
158
+ }
@@ -0,0 +1,64 @@
1
+ import { ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import fs from 'node:fs/promises';
3
+ import { getSessionPaths, readSessionLog, readSessionMetadata } from '../../sessionManager.js';
4
+ // URIs:
5
+ // - oracle-session://<id>/metadata
6
+ // - oracle-session://<id>/log
7
+ // - oracle-session://<id>/request
8
+ export function registerSessionResources(server) {
9
+ const template = new ResourceTemplate('oracle-session://{id}/{kind}', { list: undefined });
10
+ server.registerResource('oracle-session', template, {
11
+ title: 'oracle session resources',
12
+ description: 'Read stored session metadata, log, or request payload.',
13
+ }, async (uri, variables) => {
14
+ const idRaw = variables?.id;
15
+ const kindRaw = variables?.kind;
16
+ // uri-template variables arrive as string | string[]; collapse to first value.
17
+ const id = Array.isArray(idRaw) ? idRaw[0] : idRaw;
18
+ const kind = Array.isArray(kindRaw) ? kindRaw[0] : kindRaw;
19
+ if (!id || !kind) {
20
+ throw new Error('Missing id or kind');
21
+ }
22
+ const paths = await getSessionPaths(id);
23
+ switch (kind) {
24
+ case 'metadata': {
25
+ const metadata = await readSessionMetadata(id);
26
+ if (!metadata) {
27
+ throw new Error(`Session "${id}" not found.`);
28
+ }
29
+ return {
30
+ contents: [
31
+ {
32
+ uri: uri.href,
33
+ text: JSON.stringify(metadata, null, 2),
34
+ },
35
+ ],
36
+ };
37
+ }
38
+ case 'log': {
39
+ const log = await readSessionLog(id);
40
+ return {
41
+ contents: [
42
+ {
43
+ uri: uri.href,
44
+ text: log,
45
+ },
46
+ ],
47
+ };
48
+ }
49
+ case 'request': {
50
+ const raw = await fs.readFile(paths.request, 'utf8');
51
+ return {
52
+ contents: [
53
+ {
54
+ uri: uri.href,
55
+ text: raw,
56
+ },
57
+ ],
58
+ };
59
+ }
60
+ default:
61
+ throw new Error(`Unsupported resource kind: ${kind}`);
62
+ }
63
+ });
64
+ }
@@ -0,0 +1,106 @@
1
+ import fs from 'node:fs/promises';
2
+ import { z } from 'zod';
3
+ import { filterSessionsByRange, getSessionPaths, listSessionsMetadata, readSessionLog, readSessionMetadata, } from '../../sessionManager.js';
4
+ import { sessionsInputSchema } from '../types.js';
5
+ const sessionsInputShape = {
6
+ id: z.string().optional(),
7
+ hours: z.number().optional(),
8
+ limit: z.number().optional(),
9
+ includeAll: z.boolean().optional(),
10
+ detail: z.boolean().optional(),
11
+ };
12
+ const sessionsOutputShape = {
13
+ entries: z
14
+ .array(z.object({
15
+ id: z.string(),
16
+ createdAt: z.string(),
17
+ status: z.string(),
18
+ model: z.string().optional(),
19
+ mode: z.string().optional(),
20
+ }))
21
+ .optional(),
22
+ total: z.number().optional(),
23
+ truncated: z.boolean().optional(),
24
+ session: z
25
+ .object({
26
+ metadata: z.record(z.string(), z.any()),
27
+ log: z.string(),
28
+ request: z.record(z.string(), z.any()).optional(),
29
+ })
30
+ .optional(),
31
+ };
32
+ export function registerSessionsTool(server) {
33
+ server.registerTool('sessions', {
34
+ title: 'List or fetch oracle sessions',
35
+ description: 'List stored sessions (same defaults as `oracle status`) or, with id/slug, return a summary row. Pass detail:true to include metadata, log, and stored request for that session.',
36
+ inputSchema: sessionsInputShape,
37
+ outputSchema: sessionsOutputShape,
38
+ }, async (input) => {
39
+ const textContent = (text) => [{ type: 'text', text }];
40
+ const { id, hours = 24, limit = 100, includeAll = false, detail = false } = sessionsInputSchema.parse(input);
41
+ if (id) {
42
+ if (!detail) {
43
+ const metadata = await readSessionMetadata(id);
44
+ if (!metadata) {
45
+ throw new Error(`Session "${id}" not found.`);
46
+ }
47
+ return {
48
+ content: textContent(`${metadata.createdAt} | ${metadata.status} | ${metadata.model ?? 'n/a'} | ${metadata.id}`),
49
+ structuredContent: {
50
+ entries: [
51
+ {
52
+ id: metadata.id,
53
+ createdAt: metadata.createdAt,
54
+ status: metadata.status,
55
+ model: metadata.model,
56
+ mode: metadata.mode,
57
+ },
58
+ ],
59
+ total: 1,
60
+ truncated: false,
61
+ },
62
+ };
63
+ }
64
+ const metadata = await readSessionMetadata(id);
65
+ if (!metadata) {
66
+ throw new Error(`Session "${id}" not found.`);
67
+ }
68
+ const log = await readSessionLog(id);
69
+ let request;
70
+ try {
71
+ const paths = await getSessionPaths(id);
72
+ const raw = await fs.readFile(paths.request, 'utf8');
73
+ // Old sessions may lack a request payload; treat it as best-effort metadata.
74
+ request = JSON.parse(raw);
75
+ }
76
+ catch {
77
+ request = undefined;
78
+ }
79
+ return {
80
+ content: textContent(log),
81
+ structuredContent: { session: { metadata, log, request } },
82
+ };
83
+ }
84
+ const metas = await listSessionsMetadata();
85
+ const { entries, truncated, total } = filterSessionsByRange(metas, { hours, includeAll, limit });
86
+ return {
87
+ content: [
88
+ {
89
+ type: 'text',
90
+ text: entries.map((entry) => `${entry.createdAt} | ${entry.status} | ${entry.model ?? 'n/a'} | ${entry.id}`).join('\n'),
91
+ },
92
+ ],
93
+ structuredContent: {
94
+ entries: entries.map((entry) => ({
95
+ id: entry.id,
96
+ createdAt: entry.createdAt,
97
+ status: entry.status,
98
+ model: entry.model,
99
+ mode: entry.mode,
100
+ })),
101
+ total,
102
+ truncated,
103
+ },
104
+ };
105
+ });
106
+ }
@@ -0,0 +1,17 @@
1
+ import { z } from 'zod';
2
+ export const consultInputSchema = z.object({
3
+ prompt: z.string().min(1, 'Prompt is required.'),
4
+ files: z.array(z.string()).default([]),
5
+ model: z.string().optional(),
6
+ engine: z.enum(['api', 'browser']).optional(),
7
+ browserModelLabel: z.string().optional(),
8
+ search: z.boolean().optional(),
9
+ slug: z.string().optional(),
10
+ });
11
+ export const sessionsInputSchema = z.object({
12
+ id: z.string().optional(),
13
+ hours: z.number().optional(),
14
+ limit: z.number().optional(),
15
+ includeAll: z.boolean().optional(),
16
+ detail: z.boolean().optional(),
17
+ });
@@ -0,0 +1,24 @@
1
+ import { resolveRunOptionsFromConfig } from '../cli/runOptions.js';
2
+ import { Launcher } from 'chrome-launcher';
3
+ export function mapConsultToRunOptions({ prompt, files, model, engine, search, userConfig, env = process.env, }) {
4
+ // Normalize CLI-style inputs through the shared resolver so config/env defaults apply,
5
+ // then overlay MCP-only overrides such as explicit search toggles.
6
+ const result = resolveRunOptionsFromConfig({ prompt, files, model, engine, userConfig, env });
7
+ if (typeof search === 'boolean') {
8
+ result.runOptions.search = search;
9
+ }
10
+ return result;
11
+ }
12
+ export function ensureBrowserAvailable(engine) {
13
+ if (engine !== 'browser') {
14
+ return null;
15
+ }
16
+ if (process.env.CHROME_PATH) {
17
+ return null;
18
+ }
19
+ const found = Launcher.getFirstInstallation();
20
+ if (!found) {
21
+ return 'Browser engine unavailable: no Chrome installation found and CHROME_PATH is unset.';
22
+ }
23
+ return null;
24
+ }
@@ -1,16 +1,34 @@
1
- import OpenAI from 'openai';
1
+ import OpenAI, { AzureOpenAI } from 'openai';
2
2
  import path from 'node:path';
3
3
  import { createRequire } from 'node:module';
4
+ import { createGeminiClient } from './gemini.js';
4
5
  const CUSTOM_CLIENT_FACTORY = loadCustomClientFactory();
5
6
  export function createDefaultClientFactory() {
6
7
  if (CUSTOM_CLIENT_FACTORY) {
7
8
  return CUSTOM_CLIENT_FACTORY;
8
9
  }
9
- return (key) => {
10
- const instance = new OpenAI({
11
- apiKey: key,
12
- timeout: 20 * 60 * 1000,
13
- });
10
+ return (key, options) => {
11
+ if (options?.model?.startsWith('gemini')) {
12
+ // Gemini client uses its own SDK; allow passing the already-resolved id for transparency/logging.
13
+ return createGeminiClient(key, options.model, options.resolvedModelId);
14
+ }
15
+ let instance;
16
+ if (options?.azure?.endpoint) {
17
+ instance = new AzureOpenAI({
18
+ apiKey: key,
19
+ endpoint: options.azure.endpoint,
20
+ apiVersion: options.azure.apiVersion,
21
+ deployment: options.azure.deployment,
22
+ timeout: 20 * 60 * 1000,
23
+ });
24
+ }
25
+ else {
26
+ instance = new OpenAI({
27
+ apiKey: key,
28
+ timeout: 20 * 60 * 1000,
29
+ baseURL: options?.baseUrl,
30
+ });
31
+ }
14
32
  return {
15
33
  responses: {
16
34
  stream: (body) => instance.responses.stream(body),
@@ -21,6 +21,16 @@ export const MODEL_CONFIGS = {
21
21
  },
22
22
  reasoning: { effort: 'high' },
23
23
  },
24
+ 'gemini-3-pro': {
25
+ model: 'gemini-3-pro',
26
+ tokenizer: countTokensGpt5Pro,
27
+ inputLimit: 200000,
28
+ pricing: {
29
+ inputPerToken: 2 / 1_000_000,
30
+ outputPerToken: 12 / 1_000_000,
31
+ },
32
+ reasoning: null,
33
+ },
24
34
  };
25
35
  export const DEFAULT_SYSTEM_PROMPT = [
26
36
  'You are Oracle, a focused one-shot problem solver.',