@steipete/oracle 0.8.5 → 0.9.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 +99 -5
- package/dist/bin/oracle-cli.js +376 -13
- package/dist/src/browser/actions/assistantResponse.js +72 -37
- package/dist/src/browser/actions/modelSelection.js +60 -8
- package/dist/src/browser/actions/navigation.js +2 -1
- package/dist/src/browser/actions/promptComposer.js +141 -32
- package/dist/src/browser/chromeLifecycle.js +25 -9
- package/dist/src/browser/config.js +14 -0
- package/dist/src/browser/constants.js +1 -1
- package/dist/src/browser/index.js +414 -43
- package/dist/src/browser/profileState.js +93 -0
- package/dist/src/browser/providerDomFlow.js +17 -0
- package/dist/src/browser/providers/chatgptDomProvider.js +49 -0
- package/dist/src/browser/providers/geminiDeepThinkDomProvider.js +245 -0
- package/dist/src/browser/providers/index.js +2 -0
- package/dist/src/cli/browserConfig.js +33 -6
- package/dist/src/cli/browserDefaults.js +21 -0
- package/dist/src/cli/detach.js +5 -2
- package/dist/src/cli/fileSize.js +11 -0
- package/dist/src/cli/help.js +3 -3
- package/dist/src/cli/markdownBundle.js +5 -1
- package/dist/src/cli/options.js +40 -3
- package/dist/src/cli/runOptions.js +11 -3
- package/dist/src/cli/sessionDisplay.js +91 -2
- package/dist/src/cli/sessionLineage.js +56 -0
- package/dist/src/cli/sessionRunner.js +169 -2
- package/dist/src/cli/sessionTable.js +2 -1
- package/dist/src/cli/tui/index.js +3 -0
- package/dist/src/gemini-web/browserSessionManager.js +76 -0
- package/dist/src/gemini-web/client.js +16 -5
- package/dist/src/gemini-web/executionClients.js +1 -0
- package/dist/src/gemini-web/executionMode.js +18 -0
- package/dist/src/gemini-web/executor.js +273 -120
- package/dist/src/mcp/tools/consult.js +35 -21
- package/dist/src/oracle/client.js +42 -13
- package/dist/src/oracle/config.js +43 -7
- package/dist/src/oracle/errors.js +2 -2
- package/dist/src/oracle/files.js +20 -5
- package/dist/src/oracle/gemini.js +3 -0
- package/dist/src/oracle/modelResolver.js +33 -1
- package/dist/src/oracle/request.js +7 -2
- package/dist/src/oracle/run.js +22 -12
- package/dist/src/sessionManager.js +13 -2
- package/dist/src/sessionStore.js +2 -2
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
- package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
- package/package.json +24 -24
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
- package/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
|
@@ -1,34 +1,63 @@
|
|
|
1
|
-
import OpenAI
|
|
1
|
+
import OpenAI from 'openai';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { createRequire } from 'node:module';
|
|
4
4
|
import { createGeminiClient } from './gemini.js';
|
|
5
5
|
import { createClaudeClient } from './claude.js';
|
|
6
6
|
import { isOpenRouterBaseUrl } from './modelResolver.js';
|
|
7
|
+
/**
|
|
8
|
+
* Known native API base URLs that should still use their dedicated SDKs.
|
|
9
|
+
* Any other custom base URL is treated as an OpenAI-compatible proxy and
|
|
10
|
+
* all models are routed through the chat/completions adapter.
|
|
11
|
+
*/
|
|
12
|
+
const NATIVE_API_HOSTS = [
|
|
13
|
+
'api.openai.com',
|
|
14
|
+
'api.anthropic.com',
|
|
15
|
+
'generativelanguage.googleapis.com',
|
|
16
|
+
'api.x.ai',
|
|
17
|
+
];
|
|
18
|
+
export function isCustomBaseUrl(baseUrl) {
|
|
19
|
+
if (!baseUrl)
|
|
20
|
+
return false;
|
|
21
|
+
try {
|
|
22
|
+
const url = new URL(baseUrl);
|
|
23
|
+
return !NATIVE_API_HOSTS.some((host) => url.hostname === host || url.hostname.endsWith(`.${host}`));
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export function buildAzureResponsesBaseUrl(endpoint) {
|
|
30
|
+
return `${endpoint.replace(/\/+$/, '')}/openai/v1`;
|
|
31
|
+
}
|
|
7
32
|
export function createDefaultClientFactory() {
|
|
8
33
|
const customFactory = loadCustomClientFactory();
|
|
9
34
|
if (customFactory)
|
|
10
35
|
return customFactory;
|
|
11
36
|
return (key, options) => {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
37
|
+
const openRouter = isOpenRouterBaseUrl(options?.baseUrl);
|
|
38
|
+
const customProxy = isCustomBaseUrl(options?.baseUrl);
|
|
39
|
+
// When using any custom/proxy base URL (OpenRouter, LiteLLM, vLLM, Together, etc.),
|
|
40
|
+
// route ALL models through the OpenAI chat/completions adapter instead of native SDKs
|
|
41
|
+
// which would reject the proxy's API key.
|
|
42
|
+
if (!openRouter && !customProxy) {
|
|
43
|
+
if (options?.model?.startsWith('gemini')) {
|
|
44
|
+
// Gemini client uses its own SDK; allow passing the already-resolved id for transparency/logging.
|
|
45
|
+
return createGeminiClient(key, options.model, options.resolvedModelId);
|
|
46
|
+
}
|
|
47
|
+
if (options?.model?.startsWith('claude')) {
|
|
48
|
+
return createClaudeClient(key, options.model, options.resolvedModelId, options.baseUrl);
|
|
49
|
+
}
|
|
18
50
|
}
|
|
19
51
|
let instance;
|
|
20
|
-
const openRouter = isOpenRouterBaseUrl(options?.baseUrl);
|
|
21
52
|
const defaultHeaders = openRouter ? buildOpenRouterHeaders() : undefined;
|
|
22
53
|
const httpTimeoutMs = typeof options?.httpTimeoutMs === 'number' && Number.isFinite(options.httpTimeoutMs) && options.httpTimeoutMs > 0
|
|
23
54
|
? options.httpTimeoutMs
|
|
24
55
|
: 20 * 60 * 1000;
|
|
25
56
|
if (options?.azure?.endpoint) {
|
|
26
|
-
instance = new
|
|
57
|
+
instance = new OpenAI({
|
|
27
58
|
apiKey: key,
|
|
28
|
-
endpoint: options.azure.endpoint,
|
|
29
|
-
apiVersion: options.azure.apiVersion,
|
|
30
|
-
deployment: options.azure.deployment,
|
|
31
59
|
timeout: httpTimeoutMs,
|
|
60
|
+
baseURL: buildAzureResponsesBaseUrl(options.azure.endpoint),
|
|
32
61
|
});
|
|
33
62
|
}
|
|
34
63
|
else {
|
|
@@ -39,7 +68,7 @@ export function createDefaultClientFactory() {
|
|
|
39
68
|
defaultHeaders,
|
|
40
69
|
});
|
|
41
70
|
}
|
|
42
|
-
if (openRouter) {
|
|
71
|
+
if (openRouter || customProxy) {
|
|
43
72
|
return buildOpenRouterCompletionClient(instance);
|
|
44
73
|
}
|
|
45
74
|
return {
|
|
@@ -2,19 +2,19 @@ import { countTokens as countTokensGpt5 } from 'gpt-tokenizer/model/gpt-5';
|
|
|
2
2
|
import { countTokens as countTokensGpt5Pro } from 'gpt-tokenizer/model/gpt-5-pro';
|
|
3
3
|
import { countTokens as countTokensAnthropicRaw } from '@anthropic-ai/tokenizer';
|
|
4
4
|
import { stringifyTokenizerInput } from './tokenStringifier.js';
|
|
5
|
-
export const DEFAULT_MODEL = 'gpt-5.
|
|
6
|
-
export const PRO_MODELS = new Set(['gpt-5.1-pro', 'gpt-5-pro', 'gpt-5.2-pro', 'claude-4.5-sonnet', 'claude-4.1-opus']);
|
|
5
|
+
export const DEFAULT_MODEL = 'gpt-5.4-pro';
|
|
6
|
+
export const PRO_MODELS = new Set(['gpt-5.4-pro', 'gpt-5.1-pro', 'gpt-5-pro', 'gpt-5.2-pro', 'claude-4.5-sonnet', 'claude-4.1-opus']);
|
|
7
7
|
const countTokensAnthropic = (input) => countTokensAnthropicRaw(stringifyTokenizerInput(input));
|
|
8
8
|
export const MODEL_CONFIGS = {
|
|
9
9
|
'gpt-5.1-pro': {
|
|
10
10
|
model: 'gpt-5.1-pro',
|
|
11
|
-
apiModel: 'gpt-5.
|
|
11
|
+
apiModel: 'gpt-5.4-pro',
|
|
12
12
|
provider: 'openai',
|
|
13
13
|
tokenizer: countTokensGpt5Pro,
|
|
14
14
|
inputLimit: 196000,
|
|
15
15
|
pricing: {
|
|
16
|
-
inputPerToken:
|
|
17
|
-
outputPerToken:
|
|
16
|
+
inputPerToken: 30 / 1_000_000,
|
|
17
|
+
outputPerToken: 180 / 1_000_000,
|
|
18
18
|
},
|
|
19
19
|
reasoning: null,
|
|
20
20
|
},
|
|
@@ -51,6 +51,28 @@ export const MODEL_CONFIGS = {
|
|
|
51
51
|
},
|
|
52
52
|
reasoning: { effort: 'high' },
|
|
53
53
|
},
|
|
54
|
+
'gpt-5.4': {
|
|
55
|
+
model: 'gpt-5.4',
|
|
56
|
+
provider: 'openai',
|
|
57
|
+
tokenizer: countTokensGpt5,
|
|
58
|
+
inputLimit: 196000,
|
|
59
|
+
pricing: {
|
|
60
|
+
inputPerToken: 2.5 / 1_000_000,
|
|
61
|
+
outputPerToken: 15 / 1_000_000,
|
|
62
|
+
},
|
|
63
|
+
reasoning: { effort: 'xhigh' },
|
|
64
|
+
},
|
|
65
|
+
'gpt-5.4-pro': {
|
|
66
|
+
model: 'gpt-5.4-pro',
|
|
67
|
+
provider: 'openai',
|
|
68
|
+
tokenizer: countTokensGpt5Pro,
|
|
69
|
+
inputLimit: 196000,
|
|
70
|
+
pricing: {
|
|
71
|
+
inputPerToken: 30 / 1_000_000,
|
|
72
|
+
outputPerToken: 180 / 1_000_000,
|
|
73
|
+
},
|
|
74
|
+
reasoning: { effort: 'xhigh' },
|
|
75
|
+
},
|
|
54
76
|
'gpt-5.2': {
|
|
55
77
|
model: 'gpt-5.2',
|
|
56
78
|
provider: 'openai',
|
|
@@ -76,15 +98,29 @@ export const MODEL_CONFIGS = {
|
|
|
76
98
|
},
|
|
77
99
|
'gpt-5.2-pro': {
|
|
78
100
|
model: 'gpt-5.2-pro',
|
|
101
|
+
apiModel: 'gpt-5.4-pro',
|
|
79
102
|
provider: 'openai',
|
|
80
103
|
tokenizer: countTokensGpt5Pro,
|
|
81
104
|
inputLimit: 196000,
|
|
82
105
|
pricing: {
|
|
83
|
-
inputPerToken:
|
|
84
|
-
outputPerToken:
|
|
106
|
+
inputPerToken: 30 / 1_000_000,
|
|
107
|
+
outputPerToken: 180 / 1_000_000,
|
|
85
108
|
},
|
|
86
109
|
reasoning: { effort: 'xhigh' },
|
|
87
110
|
},
|
|
111
|
+
'gemini-3.1-pro': {
|
|
112
|
+
model: 'gemini-3.1-pro',
|
|
113
|
+
provider: 'google',
|
|
114
|
+
tokenizer: countTokensGpt5Pro,
|
|
115
|
+
inputLimit: 200000,
|
|
116
|
+
pricing: {
|
|
117
|
+
inputPerToken: 2 / 1_000_000,
|
|
118
|
+
outputPerToken: 12 / 1_000_000,
|
|
119
|
+
},
|
|
120
|
+
reasoning: null,
|
|
121
|
+
supportsBackground: false,
|
|
122
|
+
supportsSearch: true,
|
|
123
|
+
},
|
|
88
124
|
'gemini-3-pro': {
|
|
89
125
|
model: 'gemini-3-pro',
|
|
90
126
|
provider: 'google',
|
|
@@ -96,12 +96,12 @@ export function toTransportError(error, model) {
|
|
|
96
96
|
apiError.message ||
|
|
97
97
|
(apiError.status ? `${apiError.status} OpenAI API error` : 'OpenAI API error');
|
|
98
98
|
// Friendly guidance when a pro-tier model isn't available on this base URL / API key.
|
|
99
|
-
if (model === 'gpt-5.
|
|
99
|
+
if (model === 'gpt-5.4-pro' &&
|
|
100
100
|
(code === 'model_not_found' ||
|
|
101
101
|
messageText.includes('does not exist') ||
|
|
102
102
|
messageText.includes('unknown model') ||
|
|
103
103
|
messageText.includes('model_not_found'))) {
|
|
104
|
-
return new OracleTransportError('model-unavailable', 'gpt-5.
|
|
104
|
+
return new OracleTransportError('model-unavailable', 'gpt-5.4-pro is not available on this API base/key. Try gpt-5-pro or gpt-5.4, or switch to the browser engine.', apiError);
|
|
105
105
|
}
|
|
106
106
|
if (apiError.status === 404 || apiError.status === 405) {
|
|
107
107
|
return new OracleTransportError('unsupported-endpoint', 'HTTP 404/405 from the Responses API; this base URL or gateway likely does not expose /v1/responses. Set OPENAI_BASE_URL to api.openai.com/v1, update your Azure API version/deployment, or use the browser engine.', apiError);
|
package/dist/src/oracle/files.js
CHANGED
|
@@ -2,10 +2,10 @@ import fs from 'node:fs/promises';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import fg from 'fast-glob';
|
|
4
4
|
import { FileValidationError } from './errors.js';
|
|
5
|
-
const
|
|
5
|
+
export const DEFAULT_MAX_FILE_SIZE_BYTES = 1 * 1024 * 1024; // 1 MB
|
|
6
6
|
const DEFAULT_FS = fs;
|
|
7
7
|
const DEFAULT_IGNORED_DIRS = ['node_modules', 'dist', 'coverage', '.git', '.turbo', '.next', 'build', 'tmp'];
|
|
8
|
-
export async function readFiles(filePaths, { cwd = process.cwd(), fsModule = DEFAULT_FS, maxFileSizeBytes =
|
|
8
|
+
export async function readFiles(filePaths, { cwd = process.cwd(), fsModule = DEFAULT_FS, maxFileSizeBytes = DEFAULT_MAX_FILE_SIZE_BYTES, readContents = true, } = {}) {
|
|
9
9
|
if (!filePaths || filePaths.length === 0) {
|
|
10
10
|
return [];
|
|
11
11
|
}
|
|
@@ -76,7 +76,7 @@ export async function readFiles(filePaths, { cwd = process.cwd(), fsModule = DEF
|
|
|
76
76
|
accepted.push(filePath);
|
|
77
77
|
}
|
|
78
78
|
if (oversized.length > 0) {
|
|
79
|
-
throw new FileValidationError(`The following files exceed the
|
|
79
|
+
throw new FileValidationError(`The following files exceed the ${formatBytes(maxFileSizeBytes)} limit:\n- ${oversized.join('\n- ')}`, {
|
|
80
80
|
files: oversized,
|
|
81
81
|
limitBytes: maxFileSizeBytes,
|
|
82
82
|
});
|
|
@@ -347,13 +347,28 @@ function stripTrailingSlashes(value) {
|
|
|
347
347
|
}
|
|
348
348
|
function formatBytes(size) {
|
|
349
349
|
if (size >= 1024 * 1024) {
|
|
350
|
-
return `${(size / (1024 * 1024))
|
|
350
|
+
return `${formatScaled(size / (1024 * 1024))} MB`;
|
|
351
351
|
}
|
|
352
352
|
if (size >= 1024) {
|
|
353
|
-
return `${(size / 1024)
|
|
353
|
+
return `${formatScaled(size / 1024)} KB`;
|
|
354
354
|
}
|
|
355
355
|
return `${size} B`;
|
|
356
356
|
}
|
|
357
|
+
function formatScaled(value) {
|
|
358
|
+
return value.toFixed(1).replace(/\.0$/, '');
|
|
359
|
+
}
|
|
360
|
+
export function normalizeMaxFileSizeBytes(value, source = 'max file size') {
|
|
361
|
+
if (value == null || value === '') {
|
|
362
|
+
return undefined;
|
|
363
|
+
}
|
|
364
|
+
const parsed = typeof value === 'number'
|
|
365
|
+
? value
|
|
366
|
+
: Number.parseInt(typeof value === 'string' ? value.trim() : String(value), 10);
|
|
367
|
+
if (!Number.isSafeInteger(parsed) || parsed <= 0) {
|
|
368
|
+
throw new Error(`${source} must be a positive integer number of bytes.`);
|
|
369
|
+
}
|
|
370
|
+
return parsed;
|
|
371
|
+
}
|
|
357
372
|
function relativePath(targetPath, cwd) {
|
|
358
373
|
const relative = path.relative(cwd, targetPath);
|
|
359
374
|
return relative || targetPath;
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { GoogleGenAI, HarmCategory, HarmBlockThreshold } from '@google/genai';
|
|
2
2
|
const MODEL_ID_MAP = {
|
|
3
|
+
'gemini-3.1-pro': 'gemini-3.1-pro-preview',
|
|
3
4
|
'gemini-3-pro': 'gemini-3-pro-preview',
|
|
5
|
+
'gpt-5.4': 'gpt-5.4',
|
|
6
|
+
'gpt-5.4-pro': 'gpt-5.4-pro',
|
|
4
7
|
'gpt-5.1-pro': 'gpt-5.1-pro',
|
|
5
8
|
'gpt-5-pro': 'gpt-5-pro',
|
|
6
9
|
'gpt-5.1': 'gpt-5.1',
|
|
@@ -38,9 +38,30 @@ export function safeModelSlug(model) {
|
|
|
38
38
|
}
|
|
39
39
|
const catalogCache = new Map();
|
|
40
40
|
const CACHE_TTL_MS = 5 * 60 * 1000;
|
|
41
|
+
const MAX_CACHE_ENTRIES = 20;
|
|
42
|
+
/**
|
|
43
|
+
* Prune stale entries from the catalog cache to prevent unbounded growth.
|
|
44
|
+
* Removes entries older than TTL and enforces a maximum cache size.
|
|
45
|
+
*/
|
|
46
|
+
function pruneCatalogCache(now) {
|
|
47
|
+
// Remove stale entries first
|
|
48
|
+
for (const [key, entry] of catalogCache) {
|
|
49
|
+
if (now - entry.fetchedAt >= CACHE_TTL_MS) {
|
|
50
|
+
catalogCache.delete(key);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// If still over limit, evict oldest fetched entries (not true LRU; no last-access tracking).
|
|
54
|
+
if (catalogCache.size > MAX_CACHE_ENTRIES) {
|
|
55
|
+
const entries = [...catalogCache.entries()].sort((a, b) => a[1].fetchedAt - b[1].fetchedAt);
|
|
56
|
+
const toRemove = entries.slice(0, catalogCache.size - MAX_CACHE_ENTRIES);
|
|
57
|
+
for (const [key] of toRemove) {
|
|
58
|
+
catalogCache.delete(key);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
41
62
|
async function fetchOpenRouterCatalog(apiKey, fetcher) {
|
|
42
|
-
const cached = catalogCache.get(apiKey);
|
|
43
63
|
const now = Date.now();
|
|
64
|
+
const cached = catalogCache.get(apiKey);
|
|
44
65
|
if (cached && now - cached.fetchedAt < CACHE_TTL_MS) {
|
|
45
66
|
return cached.models;
|
|
46
67
|
}
|
|
@@ -55,6 +76,8 @@ async function fetchOpenRouterCatalog(apiKey, fetcher) {
|
|
|
55
76
|
const json = (await response.json());
|
|
56
77
|
const models = json?.data ?? [];
|
|
57
78
|
catalogCache.set(apiKey, { fetchedAt: now, models });
|
|
79
|
+
// Prune after insert so the max-size constraint is strictly enforced.
|
|
80
|
+
pruneCatalogCache(now);
|
|
58
81
|
return models;
|
|
59
82
|
}
|
|
60
83
|
function mapToOpenRouterId(candidate, catalog, providerHint) {
|
|
@@ -149,3 +172,12 @@ export async function resolveModelConfig(model, options = {}) {
|
|
|
149
172
|
export function isProModel(model) {
|
|
150
173
|
return isKnownModel(model) && PRO_MODELS.has(model);
|
|
151
174
|
}
|
|
175
|
+
export function resetOpenRouterCatalogCacheForTest() {
|
|
176
|
+
catalogCache.clear();
|
|
177
|
+
}
|
|
178
|
+
export function getOpenRouterCatalogCacheSizeForTest() {
|
|
179
|
+
return catalogCache.size;
|
|
180
|
+
}
|
|
181
|
+
export function getOpenRouterCatalogCacheMaxEntriesForTest() {
|
|
182
|
+
return MAX_CACHE_ENTRIES;
|
|
183
|
+
}
|
|
@@ -11,10 +11,11 @@ export function buildPrompt(basePrompt, files, cwd = process.cwd()) {
|
|
|
11
11
|
const sectionText = sections.map((section) => section.sectionText).join('\n\n');
|
|
12
12
|
return `${basePrompt.trim()}\n\n${sectionText}`;
|
|
13
13
|
}
|
|
14
|
-
export function buildRequestBody({ modelConfig, systemPrompt, userPrompt, searchEnabled, maxOutputTokens, background, storeResponse, }) {
|
|
14
|
+
export function buildRequestBody({ modelConfig, systemPrompt, userPrompt, searchEnabled, maxOutputTokens, background, storeResponse, previousResponseId, }) {
|
|
15
15
|
const searchToolType = modelConfig.searchToolType ?? 'web_search_preview';
|
|
16
16
|
return {
|
|
17
17
|
model: modelConfig.apiModel ?? modelConfig.model,
|
|
18
|
+
previous_response_id: previousResponseId ? previousResponseId : undefined,
|
|
18
19
|
instructions: systemPrompt,
|
|
19
20
|
input: [
|
|
20
21
|
{
|
|
@@ -37,7 +38,11 @@ export function buildRequestBody({ modelConfig, systemPrompt, userPrompt, search
|
|
|
37
38
|
export async function renderPromptMarkdown(options, deps = {}) {
|
|
38
39
|
const cwd = deps.cwd ?? process.cwd();
|
|
39
40
|
const fsModule = deps.fs ?? createFsAdapter(fs);
|
|
40
|
-
const files = await readFiles(options.file ?? [], {
|
|
41
|
+
const files = await readFiles(options.file ?? [], {
|
|
42
|
+
cwd,
|
|
43
|
+
fsModule,
|
|
44
|
+
maxFileSizeBytes: options.maxFileSizeBytes,
|
|
45
|
+
});
|
|
41
46
|
const sections = createFileSections(files, cwd);
|
|
42
47
|
const systemPrompt = options.system?.trim() || DEFAULT_SYSTEM_PROMPT;
|
|
43
48
|
const userPrompt = (options.prompt ?? '').trim();
|
package/dist/src/oracle/run.js
CHANGED
|
@@ -12,7 +12,7 @@ import { formatElapsed } from './format.js';
|
|
|
12
12
|
import { formatFinishLine } from './finishLine.js';
|
|
13
13
|
import { getFileTokenStats, printFileTokenStats } from './tokenStats.js';
|
|
14
14
|
import { OracleResponseError, OracleTransportError, PromptValidationError, describeTransportError, toTransportError, } from './errors.js';
|
|
15
|
-
import { createDefaultClientFactory } from './client.js';
|
|
15
|
+
import { createDefaultClientFactory, isCustomBaseUrl } from './client.js';
|
|
16
16
|
import { formatBaseUrlForLog, maskApiKey } from './logging.js';
|
|
17
17
|
import { startHeartbeat } from '../heartbeat.js';
|
|
18
18
|
import { startOscProgress } from './oscProgress.js';
|
|
@@ -145,7 +145,7 @@ export async function runOracle(options, deps = {}) {
|
|
|
145
145
|
const supportsBackground = modelConfig.supportsBackground !== false;
|
|
146
146
|
const useBackground = supportsBackground ? options.background ?? isLongRunningModel : false;
|
|
147
147
|
const inputTokenBudget = options.maxInput ?? modelConfig.inputLimit;
|
|
148
|
-
const files = await readFiles(options.file ?? [], { cwd, fsModule });
|
|
148
|
+
const files = await readFiles(options.file ?? [], { cwd, fsModule, maxFileSizeBytes: options.maxFileSizeBytes });
|
|
149
149
|
const searchEnabled = options.search !== false;
|
|
150
150
|
logVerbose(`cwd: ${cwd}`);
|
|
151
151
|
let pendingNoFilesTip = null;
|
|
@@ -189,11 +189,17 @@ export async function runOracle(options, deps = {}) {
|
|
|
189
189
|
: DEFAULT_TIMEOUT_NON_PRO_MS / 1000
|
|
190
190
|
: options.timeoutSeconds;
|
|
191
191
|
const timeoutMs = timeoutSeconds * 1000;
|
|
192
|
+
const azureDeploymentName = isAzureOpenAI ? options.azure?.deployment?.trim() : undefined;
|
|
192
193
|
// Track the concrete model id we dispatch to (especially for Gemini preview aliases)
|
|
193
194
|
const effectiveModelId = options.effectiveModelId ??
|
|
194
|
-
(
|
|
195
|
-
?
|
|
196
|
-
:
|
|
195
|
+
(azureDeploymentName
|
|
196
|
+
? azureDeploymentName
|
|
197
|
+
: options.model.startsWith('gemini')
|
|
198
|
+
? resolveGeminiModelId(options.model)
|
|
199
|
+
: (modelConfig.apiModel ?? modelConfig.model));
|
|
200
|
+
if (!isPreview && options.previousResponseId) {
|
|
201
|
+
log(dim(`Continuing from response ${options.previousResponseId}`));
|
|
202
|
+
}
|
|
197
203
|
const requestBody = buildRequestBody({
|
|
198
204
|
modelConfig,
|
|
199
205
|
systemPrompt,
|
|
@@ -201,8 +207,11 @@ export async function runOracle(options, deps = {}) {
|
|
|
201
207
|
searchEnabled,
|
|
202
208
|
maxOutputTokens: options.maxOutput,
|
|
203
209
|
background: useBackground,
|
|
204
|
-
|
|
210
|
+
// Storing makes follow-ups possible (Responses API chaining relies on stored response state).
|
|
211
|
+
storeResponse: useBackground || Boolean(options.previousResponseId),
|
|
212
|
+
previousResponseId: options.previousResponseId,
|
|
205
213
|
});
|
|
214
|
+
requestBody.model = effectiveModelId;
|
|
206
215
|
const estimatedInputTokens = estimateRequestTokens(requestBody, modelConfig);
|
|
207
216
|
const tokenLabel = formatTokenEstimate(estimatedInputTokens, (text) => (richTty ? chalk.green(text) : text));
|
|
208
217
|
const fileLabel = richTty ? chalk.magenta(fileCount.toString()) : fileCount.toString();
|
|
@@ -225,9 +234,9 @@ export async function runOracle(options, deps = {}) {
|
|
|
225
234
|
log(dim(`Using ${envVar}=${maskedKey} for model ${modelConfig.model}${resolvedSuffix}`));
|
|
226
235
|
}
|
|
227
236
|
if (!options.suppressHeader &&
|
|
228
|
-
modelConfig.model === 'gpt-5.1-pro' &&
|
|
229
|
-
effectiveModelId === 'gpt-5.
|
|
230
|
-
log(dim(
|
|
237
|
+
(modelConfig.model === 'gpt-5.1-pro' || modelConfig.model === 'gpt-5.2-pro') &&
|
|
238
|
+
effectiveModelId === 'gpt-5.4-pro') {
|
|
239
|
+
log(dim(`Note: \`${modelConfig.model}\` is a stable CLI alias; OpenAI API uses \`gpt-5.4-pro\`.`));
|
|
231
240
|
}
|
|
232
241
|
if (baseUrl) {
|
|
233
242
|
log(dim(`Base URL: ${formatBaseUrlForLog(baseUrl)}`));
|
|
@@ -280,10 +289,11 @@ export async function runOracle(options, deps = {}) {
|
|
|
280
289
|
inputTokenBudget,
|
|
281
290
|
};
|
|
282
291
|
}
|
|
292
|
+
const proxyCompatibleBaseUrl = baseUrl && (isOpenRouterBaseUrl(baseUrl) || isCustomBaseUrl(baseUrl)) ? baseUrl : undefined;
|
|
283
293
|
const apiEndpoint = modelConfig.model.startsWith('gemini')
|
|
284
|
-
?
|
|
285
|
-
:
|
|
286
|
-
?
|
|
294
|
+
? proxyCompatibleBaseUrl
|
|
295
|
+
: proxyCompatibleBaseUrl
|
|
296
|
+
? proxyCompatibleBaseUrl
|
|
287
297
|
: modelConfig.model.startsWith('claude')
|
|
288
298
|
? process.env.ANTHROPIC_BASE_URL ?? baseUrl
|
|
289
299
|
: baseUrl;
|
|
@@ -151,9 +151,9 @@ export async function updateModelRunMetadata(sessionId, model, updates) {
|
|
|
151
151
|
export async function readModelRunMetadata(sessionId, model) {
|
|
152
152
|
return readModelRunFile(sessionId, model);
|
|
153
153
|
}
|
|
154
|
-
export async function initializeSession(options, cwd, notifications) {
|
|
154
|
+
export async function initializeSession(options, cwd, notifications, baseSlugOverride) {
|
|
155
155
|
await ensureSessionStorage();
|
|
156
|
-
const baseSlug = createSessionId(options.prompt || DEFAULT_SLUG, options.slug);
|
|
156
|
+
const baseSlug = baseSlugOverride || createSessionId(options.prompt || DEFAULT_SLUG, options.slug);
|
|
157
157
|
const sessionId = await ensureUniqueSessionId(baseSlug);
|
|
158
158
|
const dir = sessionDir(sessionId);
|
|
159
159
|
await ensureDir(dir);
|
|
@@ -181,8 +181,12 @@ export async function initializeSession(options, cwd, notifications) {
|
|
|
181
181
|
options: {
|
|
182
182
|
prompt: options.prompt,
|
|
183
183
|
file: options.file ?? [],
|
|
184
|
+
maxFileSizeBytes: options.maxFileSizeBytes,
|
|
184
185
|
model: options.model,
|
|
185
186
|
models: modelList,
|
|
187
|
+
previousResponseId: options.previousResponseId,
|
|
188
|
+
followupSessionId: options.followupSessionId,
|
|
189
|
+
followupModel: options.followupModel,
|
|
186
190
|
effectiveModelId: options.effectiveModelId,
|
|
187
191
|
maxInput: options.maxInput,
|
|
188
192
|
system: options.system,
|
|
@@ -206,6 +210,13 @@ export async function initializeSession(options, cwd, notifications) {
|
|
|
206
210
|
zombieTimeoutMs: options.zombieTimeoutMs,
|
|
207
211
|
zombieUseLastActivity: options.zombieUseLastActivity,
|
|
208
212
|
writeOutputPath: options.writeOutputPath,
|
|
213
|
+
waitPreference: options.waitPreference,
|
|
214
|
+
youtube: options.youtube,
|
|
215
|
+
generateImage: options.generateImage,
|
|
216
|
+
editImage: options.editImage,
|
|
217
|
+
outputPath: options.outputPath,
|
|
218
|
+
aspectRatio: options.aspectRatio,
|
|
219
|
+
geminiShowThoughts: options.geminiShowThoughts,
|
|
209
220
|
},
|
|
210
221
|
};
|
|
211
222
|
await ensureDir(modelsDir(sessionId));
|
package/dist/src/sessionStore.js
CHANGED
|
@@ -3,8 +3,8 @@ class FileSessionStore {
|
|
|
3
3
|
ensureStorage() {
|
|
4
4
|
return ensureSessionStorage();
|
|
5
5
|
}
|
|
6
|
-
createSession(options, cwd, notifications) {
|
|
7
|
-
return initializeSession(options, cwd, notifications);
|
|
6
|
+
createSession(options, cwd, notifications, baseSlugOverride) {
|
|
7
|
+
return initializeSession(options, cwd, notifications, baseSlugOverride);
|
|
8
8
|
}
|
|
9
9
|
readSession(sessionId) {
|
|
10
10
|
return readSessionMetadata(sessionId);
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@steipete/oracle",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "CLI wrapper around OpenAI Responses API with GPT-5.
|
|
3
|
+
"version": "0.9.0",
|
|
4
|
+
"description": "CLI wrapper around OpenAI Responses API with GPT-5.4 Pro, GPT-5.4, GPT-5.2, GPT-5.1, and GPT-5.1 Codex high reasoning modes.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/bin/oracle-cli.js",
|
|
7
7
|
"bin": {
|
|
@@ -62,49 +62,49 @@
|
|
|
62
62
|
"homepage": "https://github.com/steipete/oracle#readme",
|
|
63
63
|
"dependencies": {
|
|
64
64
|
"@anthropic-ai/tokenizer": "^0.0.4",
|
|
65
|
-
"@google/genai": "^1.
|
|
65
|
+
"@google/genai": "^1.44.0",
|
|
66
66
|
"@google/generative-ai": "^0.24.1",
|
|
67
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
68
|
-
"@steipete/sweet-cookie": "^0.
|
|
67
|
+
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
68
|
+
"@steipete/sweet-cookie": "^0.2.0",
|
|
69
69
|
"chalk": "^5.6.2",
|
|
70
70
|
"chrome-launcher": "^1.2.1",
|
|
71
|
-
"chrome-remote-interface": "^0.
|
|
72
|
-
"clipboardy": "^5.
|
|
73
|
-
"commander": "^14.0.
|
|
74
|
-
"dotenv": "^17.
|
|
71
|
+
"chrome-remote-interface": "^0.34.0",
|
|
72
|
+
"clipboardy": "^5.3.1",
|
|
73
|
+
"commander": "^14.0.3",
|
|
74
|
+
"dotenv": "^17.3.1",
|
|
75
75
|
"fast-glob": "^3.3.3",
|
|
76
76
|
"gpt-tokenizer": "^3.4.0",
|
|
77
|
-
"inquirer": "13.
|
|
77
|
+
"inquirer": "13.3.0",
|
|
78
78
|
"json5": "^2.2.3",
|
|
79
79
|
"kleur": "^4.1.5",
|
|
80
80
|
"markdansi": "0.2.1",
|
|
81
|
-
"openai": "^6.
|
|
82
|
-
"osc-progress": "^0.
|
|
83
|
-
"qs": "^6.
|
|
84
|
-
"shiki": "^
|
|
81
|
+
"openai": "^6.27.0",
|
|
82
|
+
"osc-progress": "^0.3.0",
|
|
83
|
+
"qs": "^6.15.0",
|
|
84
|
+
"shiki": "^4.0.1",
|
|
85
85
|
"toasted-notifier": "^10.1.0",
|
|
86
86
|
"tokentally": "^0.1.1",
|
|
87
|
-
"zod": "^4.3.
|
|
87
|
+
"zod": "^4.3.6"
|
|
88
88
|
},
|
|
89
89
|
"devDependencies": {
|
|
90
90
|
"@anthropic-ai/tokenizer": "^0.0.4",
|
|
91
|
-
"@biomejs/biome": "^2.
|
|
91
|
+
"@biomejs/biome": "^2.4.6",
|
|
92
92
|
"@cdktf/node-pty-prebuilt-multiarch": "0.10.2",
|
|
93
93
|
"@types/chrome-remote-interface": "^0.33.0",
|
|
94
94
|
"@types/inquirer": "^9.0.9",
|
|
95
|
-
"@types/node": "^25.
|
|
96
|
-
"@vitest/coverage-v8": "4.0.
|
|
97
|
-
"devtools-protocol": "0.0.
|
|
98
|
-
"es-toolkit": "^1.
|
|
99
|
-
"esbuild": "^0.27.
|
|
100
|
-
"puppeteer-core": "^24.
|
|
95
|
+
"@types/node": "^25.3.5",
|
|
96
|
+
"@vitest/coverage-v8": "4.0.18",
|
|
97
|
+
"devtools-protocol": "0.0.1595872",
|
|
98
|
+
"es-toolkit": "^1.45.1",
|
|
99
|
+
"esbuild": "^0.27.3",
|
|
100
|
+
"puppeteer-core": "^24.38.0",
|
|
101
101
|
"tsx": "^4.21.0",
|
|
102
102
|
"typescript": "^5.9.3",
|
|
103
|
-
"vitest": "^4.0.
|
|
103
|
+
"vitest": "^4.0.18"
|
|
104
104
|
},
|
|
105
105
|
"pnpm": {
|
|
106
106
|
"overrides": {
|
|
107
|
-
"devtools-protocol": "0.0.
|
|
107
|
+
"devtools-protocol": "0.0.1595872"
|
|
108
108
|
},
|
|
109
109
|
"onlyBuiltDependencies": [
|
|
110
110
|
"@cdktf/node-pty-prebuilt-multiarch",
|
|
Binary file
|
|
Binary file
|