@steipete/oracle 0.18.0 → 0.20.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 (169) hide show
  1. package/README.md +1 -1
  2. package/dist/bin/oracle-cli.js +76 -76
  3. package/dist/bin/oracle-mcp.js +0 -0
  4. package/dist/src/browser/actions/archiveConversation.js +31 -7
  5. package/dist/src/browser/actions/assistantResponse.js +43 -1
  6. package/dist/src/browser/actions/attachmentContext.js +239 -0
  7. package/dist/src/browser/actions/attachmentDataTransfer.js +19 -1
  8. package/dist/src/browser/actions/attachmentEvidence.js +78 -0
  9. package/dist/src/browser/actions/attachmentProgress.js +41 -0
  10. package/dist/src/browser/actions/attachmentPrompt.js +130 -0
  11. package/dist/src/browser/actions/attachments.js +306 -355
  12. package/dist/src/browser/actions/deepResearch.js +170 -45
  13. package/dist/src/browser/actions/modelSelection.js +91 -4
  14. package/dist/src/browser/actions/navigation.js +1 -1
  15. package/dist/src/browser/actions/promptComposer.js +364 -186
  16. package/dist/src/browser/actions/remoteFileTransfer.js +4 -2
  17. package/dist/src/browser/actions/thinkingStatus.js +2 -5
  18. package/dist/src/browser/actions/thinkingTime.js +156 -16
  19. package/dist/src/browser/actions/webSearch.js +96 -0
  20. package/dist/src/browser/attachRunning.js +46 -2
  21. package/dist/src/browser/attachmentValidation.js +53 -0
  22. package/dist/src/browser/cancellation.js +105 -0
  23. package/dist/src/browser/chatgptImages.js +2 -0
  24. package/dist/src/browser/chatgptThrottle.js +104 -0
  25. package/dist/src/browser/chromeLifecycle.js +272 -47
  26. package/dist/src/browser/config.js +14 -1
  27. package/dist/src/browser/detect.js +1 -1
  28. package/dist/src/browser/index.js +525 -274
  29. package/dist/src/browser/liveTabs.js +3 -4
  30. package/dist/src/browser/modelDisplay.js +9 -0
  31. package/dist/src/browser/pageActions.js +2 -2
  32. package/dist/src/browser/profileState.js +114 -9
  33. package/dist/src/browser/projectSourcesRunner.js +83 -15
  34. package/dist/src/browser/prompt.js +272 -113
  35. package/dist/src/browser/providers/chatgptDomProvider.js +3 -0
  36. package/dist/src/browser/reattach.js +31 -4
  37. package/dist/src/browser/recoveryTarget.js +131 -0
  38. package/dist/src/browser/sessionRunner.js +73 -11
  39. package/dist/src/browser/tabLeaseRegistry.js +434 -38
  40. package/dist/src/browser/targetClaim.js +54 -0
  41. package/dist/src/browser/utils.js +20 -2
  42. package/dist/src/cli/browserConfig.js +36 -4
  43. package/dist/src/cli/browserDefaults.js +10 -0
  44. package/dist/src/cli/browserTabs.js +15 -29
  45. package/dist/src/cli/detachedSession.js +69 -0
  46. package/dist/src/cli/dryRun.js +58 -48
  47. package/dist/src/cli/harvestIntegrity.js +140 -0
  48. package/dist/src/cli/options.js +15 -0
  49. package/dist/src/cli/recoveredBrowserHarvest.js +50 -0
  50. package/dist/src/cli/runOptions.js +19 -4
  51. package/dist/src/cli/serveBrowserConfig.js +24 -0
  52. package/dist/src/cli/sessionDisplay.js +13 -7
  53. package/dist/src/cli/sessionRunner.js +95 -12
  54. package/dist/src/config.js +1 -0
  55. package/dist/src/gemini-web/client.js +67 -21
  56. package/dist/src/gemini-web/executor.js +4 -0
  57. package/dist/src/gemini-web/http.js +15 -0
  58. package/dist/src/gemini-web/models.js +15 -11
  59. package/dist/src/mcp/server.js +11 -14
  60. package/dist/src/mcp/tools/chatgptImage.js +4 -4
  61. package/dist/src/mcp/tools/consult.js +86 -24
  62. package/dist/src/mcp/tools/projectSources.js +4 -6
  63. package/dist/src/mcp/tools/sessionResources.js +1 -1
  64. package/dist/src/mcp/tools/sessions.js +2 -2
  65. package/dist/src/mcp/tools/wait.js +182 -0
  66. package/dist/src/mcp/types.js +8 -1
  67. package/dist/src/oracle/config.js +14 -0
  68. package/dist/src/oracle/errors.js +11 -0
  69. package/dist/src/oracle/geminiModels.js +1 -0
  70. package/dist/src/oracle/providerRoutePlan.js +3 -2
  71. package/dist/src/oracle/run.js +27 -4
  72. package/dist/src/remote/client.js +73 -6
  73. package/dist/src/remote/health.js +6 -2
  74. package/dist/src/remote/runSlots.js +83 -0
  75. package/dist/src/remote/server.js +346 -71
  76. package/dist/src/sessionManager.js +36 -1
  77. package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
  78. package/dist/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
  79. package/package.json +25 -23
  80. package/vendor/oracle-notifier/OracleNotifier.app/Contents/CodeResources +0 -0
  81. package/vendor/oracle-notifier/OracleNotifier.app/Contents/MacOS/OracleNotifier +0 -0
  82. package/dist/bin/oracle.js +0 -683
  83. package/dist/docs-site/.nojekyll +0 -0
  84. package/dist/docs-site/CNAME +0 -1
  85. package/dist/docs-site/RELEASING.html +0 -410
  86. package/dist/docs-site/agents.html +0 -374
  87. package/dist/docs-site/anthropic.html +0 -368
  88. package/dist/docs-site/bridge.html +0 -400
  89. package/dist/docs-site/browser-mode.html +0 -588
  90. package/dist/docs-site/chromium-forks.html +0 -347
  91. package/dist/docs-site/cli-reference.html +0 -346
  92. package/dist/docs-site/configuration.html +0 -436
  93. package/dist/docs-site/favicon.svg +0 -14
  94. package/dist/docs-site/followup.html +0 -368
  95. package/dist/docs-site/gemini.html +0 -379
  96. package/dist/docs-site/grok.html +0 -325
  97. package/dist/docs-site/index.html +0 -360
  98. package/dist/docs-site/install.html +0 -335
  99. package/dist/docs-site/linux.html +0 -321
  100. package/dist/docs-site/llms.txt +0 -43
  101. package/dist/docs-site/manual-tests.html +0 -595
  102. package/dist/docs-site/mcp.html +0 -368
  103. package/dist/docs-site/multimodel.html +0 -364
  104. package/dist/docs-site/mythical-pro-agents.html +0 -360
  105. package/dist/docs-site/notifier.html +0 -338
  106. package/dist/docs-site/openai-endpoints.html +0 -387
  107. package/dist/docs-site/openrouter.html +0 -344
  108. package/dist/docs-site/quickstart.html +0 -369
  109. package/dist/docs-site/refactor/ux.html +0 -532
  110. package/dist/docs-site/sessions.html +0 -388
  111. package/dist/docs-site/social-card.png +0 -0
  112. package/dist/docs-site/social-card.svg +0 -79
  113. package/dist/docs-site/spec.html +0 -363
  114. package/dist/docs-site/testing.html +0 -320
  115. package/dist/docs-site/tui-debug.html +0 -326
  116. package/dist/docs-site/windows-work.html +0 -323
  117. package/dist/docs-site/windows.html +0 -320
  118. package/dist/markdansi/types/index.js +0 -4
  119. package/dist/oracle/bin/oracle-cli.js +0 -472
  120. package/dist/oracle/src/browser/actions/assistantResponse.js +0 -471
  121. package/dist/oracle/src/browser/actions/attachments.js +0 -82
  122. package/dist/oracle/src/browser/actions/modelSelection.js +0 -190
  123. package/dist/oracle/src/browser/actions/navigation.js +0 -75
  124. package/dist/oracle/src/browser/actions/promptComposer.js +0 -167
  125. package/dist/oracle/src/browser/chromeLifecycle.js +0 -104
  126. package/dist/oracle/src/browser/config.js +0 -33
  127. package/dist/oracle/src/browser/constants.js +0 -40
  128. package/dist/oracle/src/browser/cookies.js +0 -210
  129. package/dist/oracle/src/browser/domDebug.js +0 -36
  130. package/dist/oracle/src/browser/index.js +0 -331
  131. package/dist/oracle/src/browser/pageActions.js +0 -5
  132. package/dist/oracle/src/browser/prompt.js +0 -88
  133. package/dist/oracle/src/browser/promptSummary.js +0 -20
  134. package/dist/oracle/src/browser/sessionRunner.js +0 -80
  135. package/dist/oracle/src/browser/types.js +0 -1
  136. package/dist/oracle/src/browser/utils.js +0 -62
  137. package/dist/oracle/src/browserMode.js +0 -1
  138. package/dist/oracle/src/cli/browserConfig.js +0 -44
  139. package/dist/oracle/src/cli/dryRun.js +0 -59
  140. package/dist/oracle/src/cli/engine.js +0 -17
  141. package/dist/oracle/src/cli/errorUtils.js +0 -9
  142. package/dist/oracle/src/cli/help.js +0 -70
  143. package/dist/oracle/src/cli/markdownRenderer.js +0 -15
  144. package/dist/oracle/src/cli/options.js +0 -103
  145. package/dist/oracle/src/cli/promptRequirement.js +0 -14
  146. package/dist/oracle/src/cli/rootAlias.js +0 -30
  147. package/dist/oracle/src/cli/sessionCommand.js +0 -77
  148. package/dist/oracle/src/cli/sessionDisplay.js +0 -270
  149. package/dist/oracle/src/cli/sessionRunner.js +0 -94
  150. package/dist/oracle/src/heartbeat.js +0 -43
  151. package/dist/oracle/src/oracle/client.js +0 -48
  152. package/dist/oracle/src/oracle/config.js +0 -29
  153. package/dist/oracle/src/oracle/errors.js +0 -101
  154. package/dist/oracle/src/oracle/files.js +0 -220
  155. package/dist/oracle/src/oracle/format.js +0 -33
  156. package/dist/oracle/src/oracle/fsAdapter.js +0 -7
  157. package/dist/oracle/src/oracle/oscProgress.js +0 -60
  158. package/dist/oracle/src/oracle/request.js +0 -48
  159. package/dist/oracle/src/oracle/run.js +0 -444
  160. package/dist/oracle/src/oracle/tokenStats.js +0 -39
  161. package/dist/oracle/src/oracle/types.js +0 -1
  162. package/dist/oracle/src/oracle.js +0 -9
  163. package/dist/oracle/src/sessionManager.js +0 -205
  164. package/dist/oracle/src/version.js +0 -39
  165. package/dist/scripts/chrome/browser-tools.js +0 -295
  166. package/dist/src/browser/chromeCookies.js +0 -312
  167. package/dist/src/browser/keytarShim.js +0 -56
  168. package/dist/src/browser/profileSync.js +0 -141
  169. package/dist/src/browser/windowsCookies.js +0 -219
@@ -1,444 +0,0 @@
1
- import chalk from 'chalk';
2
- import kleur from 'kleur';
3
- import fs from 'node:fs/promises';
4
- import path from 'node:path';
5
- import process from 'node:process';
6
- import { performance } from 'node:perf_hooks';
7
- import { APIConnectionError, APIConnectionTimeoutError } from 'openai';
8
- import { DEFAULT_SYSTEM_PROMPT, MODEL_CONFIGS, TOKENIZER_OPTIONS } from './config.js';
9
- import { readFiles } from './files.js';
10
- import { buildPrompt, buildRequestBody } from './request.js';
11
- import { formatElapsed, formatUSD } from './format.js';
12
- import { getFileTokenStats, printFileTokenStats } from './tokenStats.js';
13
- import { OracleResponseError, OracleTransportError, PromptValidationError, describeTransportError, toTransportError, } from './errors.js';
14
- import { createDefaultClientFactory } from './client.js';
15
- import { startHeartbeat } from '../heartbeat.js';
16
- import { startOscProgress } from './oscProgress.js';
17
- import { getCliVersion } from '../version.js';
18
- import { createFsAdapter } from './fsAdapter.js';
19
- const isTty = process.stdout.isTTY;
20
- const dim = (text) => (isTty ? kleur.dim(text) : text);
21
- const BACKGROUND_MAX_WAIT_MS = 30 * 60 * 1000;
22
- const BACKGROUND_POLL_INTERVAL_MS = 5000;
23
- const BACKGROUND_RETRY_BASE_MS = 3000;
24
- const BACKGROUND_RETRY_MAX_MS = 15000;
25
- const defaultWait = (ms) => new Promise((resolve) => {
26
- setTimeout(resolve, ms);
27
- });
28
- export async function runOracle(options, deps = {}) {
29
- const { apiKey = options.apiKey ?? process.env.OPENAI_API_KEY, cwd = process.cwd(), fs: fsModule = createFsAdapter(fs), log = console.log, write = (text) => process.stdout.write(text), now = () => performance.now(), clientFactory = createDefaultClientFactory(), client, wait = defaultWait, } = deps;
30
- const maskApiKey = (key) => {
31
- if (!key)
32
- return null;
33
- if (key.length <= 8)
34
- return `${key[0] ?? ''}***${key[key.length - 1] ?? ''}`;
35
- const prefix = key.slice(0, 4);
36
- const suffix = key.slice(-4);
37
- return `${prefix}****${suffix}`;
38
- };
39
- const logVerbose = (message) => {
40
- if (options.verbose) {
41
- log(dim(`[verbose] ${message}`));
42
- }
43
- };
44
- const previewMode = resolvePreviewMode(options.previewMode ?? options.preview);
45
- const isPreview = Boolean(previewMode);
46
- if (!apiKey) {
47
- throw new PromptValidationError('Missing OPENAI_API_KEY. Set it via the environment or a .env file.', {
48
- env: 'OPENAI_API_KEY',
49
- });
50
- }
51
- const maskedKey = maskApiKey(apiKey);
52
- if (maskedKey) {
53
- log(dim(`Using OPENAI_API_KEY=${maskedKey}`));
54
- }
55
- const modelConfig = MODEL_CONFIGS[options.model];
56
- if (!modelConfig) {
57
- throw new PromptValidationError(`Unsupported model "${options.model}". Choose one of: ${Object.keys(MODEL_CONFIGS).join(', ')}`, { model: options.model });
58
- }
59
- const useBackground = options.background ?? (options.model === 'gpt-5-pro');
60
- const inputTokenBudget = options.maxInput ?? modelConfig.inputLimit;
61
- const files = await readFiles(options.file ?? [], { cwd, fsModule });
62
- const searchEnabled = options.search !== false;
63
- logVerbose(`cwd: ${cwd}`);
64
- if (files.length > 0) {
65
- const displayPaths = files
66
- .map((file) => path.relative(cwd, file.path) || file.path)
67
- .slice(0, 10)
68
- .join(', ');
69
- const extra = files.length > 10 ? ` (+${files.length - 10} more)` : '';
70
- logVerbose(`Attached files (${files.length}): ${displayPaths}${extra}`);
71
- }
72
- else {
73
- logVerbose('No files attached.');
74
- if (!isPreview) {
75
- log(dim('Tip: no files attached — Oracle works best with project context. Add files via --file path/to/code or docs.'));
76
- }
77
- }
78
- const fileTokenInfo = getFileTokenStats(files, {
79
- cwd,
80
- tokenizer: modelConfig.tokenizer,
81
- tokenizerOptions: TOKENIZER_OPTIONS,
82
- inputTokenBudget,
83
- });
84
- const totalFileTokens = fileTokenInfo.totalTokens;
85
- logVerbose(`Attached files use ${totalFileTokens.toLocaleString()} tokens`);
86
- const systemPrompt = options.system?.trim() || DEFAULT_SYSTEM_PROMPT;
87
- const promptWithFiles = buildPrompt(options.prompt, files, cwd);
88
- const tokenizerInput = [
89
- { role: 'system', content: systemPrompt },
90
- { role: 'user', content: promptWithFiles },
91
- ];
92
- const estimatedInputTokens = modelConfig.tokenizer(tokenizerInput, TOKENIZER_OPTIONS);
93
- logVerbose(`Estimated tokens (prompt + files): ${estimatedInputTokens.toLocaleString()}`);
94
- const fileCount = files.length;
95
- const cliVersion = getCliVersion();
96
- const richTty = process.stdout.isTTY && chalk.level > 0;
97
- const headerModelLabel = richTty ? chalk.cyan(modelConfig.model) : modelConfig.model;
98
- const tokenLabel = richTty ? chalk.green(estimatedInputTokens.toLocaleString()) : estimatedInputTokens.toLocaleString();
99
- const fileLabel = richTty ? chalk.magenta(fileCount.toString()) : fileCount.toString();
100
- const headerLine = `Oracle (${cliVersion}) consulting ${headerModelLabel}'s crystal ball with ${tokenLabel} tokens and ${fileLabel} files...`;
101
- const shouldReportFiles = (options.filesReport || fileTokenInfo.totalTokens > inputTokenBudget) && fileTokenInfo.stats.length > 0;
102
- if (!isPreview) {
103
- log(headerLine);
104
- if (options.model === 'gpt-5-pro') {
105
- log(dim('Pro is thinking, this can take up to 30 minutes...'));
106
- }
107
- log(dim('Press Ctrl+C to cancel.'));
108
- }
109
- if (shouldReportFiles) {
110
- printFileTokenStats(fileTokenInfo, { inputTokenBudget, log });
111
- }
112
- if (estimatedInputTokens > inputTokenBudget) {
113
- throw new PromptValidationError(`Input too large (${estimatedInputTokens.toLocaleString()} tokens). Limit is ${inputTokenBudget.toLocaleString()} tokens.`, { estimatedInputTokens, inputTokenBudget });
114
- }
115
- const requestBody = buildRequestBody({
116
- modelConfig,
117
- systemPrompt,
118
- userPrompt: promptWithFiles,
119
- searchEnabled,
120
- maxOutputTokens: options.maxOutput,
121
- background: useBackground,
122
- storeResponse: useBackground,
123
- });
124
- if (isPreview && previewMode) {
125
- if (previewMode === 'json' || previewMode === 'full') {
126
- log('Request JSON');
127
- log(JSON.stringify(requestBody, null, 2));
128
- log('');
129
- }
130
- if (previewMode === 'full') {
131
- log('Assembled Prompt');
132
- log(promptWithFiles);
133
- log('');
134
- }
135
- log(`Estimated input tokens: ${estimatedInputTokens.toLocaleString()} / ${inputTokenBudget.toLocaleString()} (model: ${modelConfig.model})`);
136
- return {
137
- mode: 'preview',
138
- previewMode,
139
- requestBody,
140
- estimatedInputTokens,
141
- inputTokenBudget,
142
- };
143
- }
144
- const openAiClient = client ?? clientFactory(apiKey);
145
- logVerbose('Dispatching request to OpenAI Responses API...');
146
- const stopOscProgress = startOscProgress({
147
- label: useBackground ? 'Waiting for OpenAI (background)' : 'Waiting for OpenAI',
148
- targetMs: useBackground ? BACKGROUND_MAX_WAIT_MS : 10 * 60_000,
149
- write,
150
- });
151
- const runStart = now();
152
- let response = null;
153
- let elapsedMs = 0;
154
- let sawTextDelta = false;
155
- let answerHeaderPrinted = false;
156
- const ensureAnswerHeader = () => {
157
- if (!options.silent && !answerHeaderPrinted) {
158
- log('');
159
- log(chalk.bold('Answer:'));
160
- answerHeaderPrinted = true;
161
- }
162
- };
163
- try {
164
- if (useBackground) {
165
- response = await executeBackgroundResponse({
166
- client: openAiClient,
167
- requestBody,
168
- log,
169
- wait,
170
- heartbeatIntervalMs: options.heartbeatIntervalMs,
171
- now,
172
- });
173
- elapsedMs = now() - runStart;
174
- }
175
- else {
176
- const stream = await openAiClient.responses.stream(requestBody);
177
- let heartbeatActive = false;
178
- let stopHeartbeat = null;
179
- const stopHeartbeatNow = () => {
180
- if (!heartbeatActive) {
181
- return;
182
- }
183
- heartbeatActive = false;
184
- stopHeartbeat?.();
185
- stopHeartbeat = null;
186
- };
187
- if (options.heartbeatIntervalMs && options.heartbeatIntervalMs > 0) {
188
- heartbeatActive = true;
189
- stopHeartbeat = startHeartbeat({
190
- intervalMs: options.heartbeatIntervalMs,
191
- log: (message) => log(message),
192
- isActive: () => heartbeatActive,
193
- makeMessage: (elapsedMs) => {
194
- const elapsedText = formatElapsed(elapsedMs);
195
- return `API connection active — ${elapsedText} elapsed. Expect up to ~10 min before GPT-5 responds.`;
196
- },
197
- });
198
- }
199
- try {
200
- for await (const event of stream) {
201
- if (event.type === 'response.output_text.delta') {
202
- stopOscProgress();
203
- stopHeartbeatNow();
204
- sawTextDelta = true;
205
- ensureAnswerHeader();
206
- if (!options.silent && typeof event.delta === 'string') {
207
- write(event.delta);
208
- }
209
- }
210
- }
211
- }
212
- catch (streamError) {
213
- if (typeof stream.abort === 'function') {
214
- stream.abort();
215
- }
216
- stopHeartbeatNow();
217
- const transportError = toTransportError(streamError);
218
- log(chalk.yellow(describeTransportError(transportError)));
219
- throw transportError;
220
- }
221
- response = await stream.finalResponse();
222
- stopHeartbeatNow();
223
- elapsedMs = now() - runStart;
224
- }
225
- }
226
- finally {
227
- stopOscProgress();
228
- }
229
- if (!response) {
230
- throw new Error('OpenAI did not return a response.');
231
- }
232
- logVerbose(`Response status: ${response.status ?? 'completed'}`);
233
- if (response.status && response.status !== 'completed') {
234
- const detail = response.error?.message || response.incomplete_details?.reason || response.status;
235
- log(chalk.yellow(`OpenAI ended the run early (status=${response.status}${response.incomplete_details?.reason ? `, reason=${response.incomplete_details.reason}` : ''}).`));
236
- throw new OracleResponseError(`Response did not complete: ${detail}`, response);
237
- }
238
- const answerText = extractTextOutput(response);
239
- if (!options.silent) {
240
- // biome-ignore lint/nursery/noUnnecessaryConditions: flips true when streaming events arrive
241
- if (sawTextDelta) {
242
- write('\n\n');
243
- }
244
- else {
245
- ensureAnswerHeader();
246
- log(answerText || chalk.dim('(no text output)'));
247
- log('');
248
- }
249
- }
250
- const usage = response.usage ?? {};
251
- const inputTokens = usage.input_tokens ?? estimatedInputTokens;
252
- const outputTokens = usage.output_tokens ?? 0;
253
- const reasoningTokens = usage.reasoning_tokens ?? 0;
254
- const totalTokens = usage.total_tokens ?? inputTokens + outputTokens + reasoningTokens;
255
- const cost = inputTokens * modelConfig.pricing.inputPerToken + outputTokens * modelConfig.pricing.outputPerToken;
256
- const elapsedDisplay = formatElapsed(elapsedMs);
257
- const statsParts = [];
258
- const modelLabel = modelConfig.model + (modelConfig.reasoning ? '[high]' : '');
259
- statsParts.push(modelLabel);
260
- statsParts.push(formatUSD(cost));
261
- const tokensDisplay = [inputTokens, outputTokens, reasoningTokens, totalTokens]
262
- .map((value, index) => formatTokenValue(value, usage, index))
263
- .join('/');
264
- statsParts.push(`tok(i/o/r/t)=${tokensDisplay}`);
265
- if (!searchEnabled) {
266
- statsParts.push('search=off');
267
- }
268
- if (files.length > 0) {
269
- statsParts.push(`files=${files.length}`);
270
- }
271
- log(chalk.blue(`Finished in ${elapsedDisplay} (${statsParts.join(' | ')})`));
272
- return {
273
- mode: 'live',
274
- response,
275
- usage: { inputTokens, outputTokens, reasoningTokens, totalTokens },
276
- elapsedMs,
277
- };
278
- }
279
- function formatTokenValue(value, usage, index) {
280
- const estimatedFlag = (index === 0 && usage?.input_tokens == null) ||
281
- (index === 1 && usage?.output_tokens == null) ||
282
- (index === 2 && usage?.reasoning_tokens == null) ||
283
- (index === 3 && usage?.total_tokens == null);
284
- const text = value.toLocaleString();
285
- return estimatedFlag ? `${text}*` : text;
286
- }
287
- function resolvePreviewMode(value) {
288
- const allowed = new Set(['summary', 'json', 'full']);
289
- if (typeof value === 'string' && value.length > 0) {
290
- return allowed.has(value) ? value : 'summary';
291
- }
292
- if (value) {
293
- return 'summary';
294
- }
295
- return undefined;
296
- }
297
- export function extractTextOutput(response) {
298
- if (Array.isArray(response.output_text) && response.output_text.length > 0) {
299
- return response.output_text.join('\n');
300
- }
301
- if (Array.isArray(response.output)) {
302
- const segments = [];
303
- for (const item of response.output) {
304
- if (Array.isArray(item.content)) {
305
- for (const chunk of item.content) {
306
- if (chunk && (chunk.type === 'output_text' || chunk.type === 'text') && chunk.text) {
307
- segments.push(chunk.text);
308
- }
309
- }
310
- }
311
- else if (typeof item.text === 'string') {
312
- segments.push(item.text);
313
- }
314
- }
315
- return segments.join('\n');
316
- }
317
- return '';
318
- }
319
- async function executeBackgroundResponse(params) {
320
- const { client, requestBody, log, wait, heartbeatIntervalMs, now } = params;
321
- const initialResponse = await client.responses.create(requestBody);
322
- if (!initialResponse || !initialResponse.id) {
323
- throw new OracleResponseError('OpenAI did not return a response ID for the background run.', initialResponse);
324
- }
325
- const responseId = initialResponse.id;
326
- log(dim(`OpenAI scheduled background response ${responseId} (status=${initialResponse.status ?? 'unknown'}). Monitoring up to ${Math.round(BACKGROUND_MAX_WAIT_MS / 60000)} minutes for completion...`));
327
- let heartbeatActive = false;
328
- let stopHeartbeat = null;
329
- const stopHeartbeatNow = () => {
330
- if (!heartbeatActive) {
331
- return;
332
- }
333
- heartbeatActive = false;
334
- stopHeartbeat?.();
335
- stopHeartbeat = null;
336
- };
337
- if (heartbeatIntervalMs && heartbeatIntervalMs > 0) {
338
- heartbeatActive = true;
339
- stopHeartbeat = startHeartbeat({
340
- intervalMs: heartbeatIntervalMs,
341
- log: (message) => log(message),
342
- isActive: () => heartbeatActive,
343
- makeMessage: (elapsedMs) => {
344
- const elapsedText = formatElapsed(elapsedMs);
345
- return `OpenAI background run still in progress — ${elapsedText} elapsed.`;
346
- },
347
- });
348
- }
349
- try {
350
- return await pollBackgroundResponse({
351
- client,
352
- responseId,
353
- initialResponse,
354
- log,
355
- wait,
356
- now,
357
- maxWaitMs: BACKGROUND_MAX_WAIT_MS,
358
- });
359
- }
360
- finally {
361
- stopHeartbeatNow();
362
- }
363
- }
364
- async function pollBackgroundResponse(params) {
365
- const { client, responseId, initialResponse, log, wait, now, maxWaitMs } = params;
366
- const startMark = now();
367
- let response = initialResponse;
368
- let firstCycle = true;
369
- let lastStatus = response.status;
370
- // biome-ignore lint/nursery/noUnnecessaryConditions: intentional polling loop
371
- while (true) {
372
- const status = response.status ?? 'completed';
373
- // biome-ignore lint/nursery/noUnnecessaryConditions: guard only for first iteration
374
- if (firstCycle) {
375
- firstCycle = false;
376
- log(dim(`OpenAI background response status=${status}. We'll keep retrying automatically.`));
377
- }
378
- else if (status !== lastStatus && status !== 'completed') {
379
- log(dim(`OpenAI background response status=${status}.`));
380
- }
381
- lastStatus = status;
382
- if (status === 'completed') {
383
- return response;
384
- }
385
- if (status !== 'in_progress' && status !== 'queued') {
386
- const detail = response.error?.message || response.incomplete_details?.reason || status;
387
- throw new OracleResponseError(`Response did not complete: ${detail}`, response);
388
- }
389
- if (now() - startMark >= maxWaitMs) {
390
- throw new OracleTransportError('client-timeout', 'Timed out waiting for OpenAI background response to finish.');
391
- }
392
- await wait(BACKGROUND_POLL_INTERVAL_MS);
393
- if (now() - startMark >= maxWaitMs) {
394
- throw new OracleTransportError('client-timeout', 'Timed out waiting for OpenAI background response to finish.');
395
- }
396
- const { response: nextResponse, reconnected } = await retrieveBackgroundResponseWithRetry({
397
- client,
398
- responseId,
399
- wait,
400
- now,
401
- maxWaitMs,
402
- startMark,
403
- log,
404
- });
405
- if (reconnected) {
406
- const nextStatus = nextResponse.status ?? 'in_progress';
407
- log(dim(`Reconnected to OpenAI background response (status=${nextStatus}). OpenAI is still working...`));
408
- }
409
- response = nextResponse;
410
- }
411
- }
412
- async function retrieveBackgroundResponseWithRetry(params) {
413
- const { client, responseId, wait, now, maxWaitMs, startMark, log } = params;
414
- let retries = 0;
415
- // biome-ignore lint/nursery/noUnnecessaryConditions: intentional retry loop
416
- while (true) {
417
- try {
418
- const next = await client.responses.retrieve(responseId);
419
- return { response: next, reconnected: retries > 0 };
420
- }
421
- catch (error) {
422
- const transportError = asRetryableTransportError(error);
423
- if (!transportError) {
424
- throw error;
425
- }
426
- retries += 1;
427
- const delay = Math.min(BACKGROUND_RETRY_BASE_MS * 2 ** (retries - 1), BACKGROUND_RETRY_MAX_MS);
428
- log(chalk.yellow(`${describeTransportError(transportError)} Retrying in ${formatElapsed(delay)}...`));
429
- await wait(delay);
430
- if (now() - startMark >= maxWaitMs) {
431
- throw new OracleTransportError('client-timeout', 'Timed out waiting for OpenAI background response to finish.');
432
- }
433
- }
434
- }
435
- }
436
- function asRetryableTransportError(error) {
437
- if (error instanceof OracleTransportError) {
438
- return error;
439
- }
440
- if (error instanceof APIConnectionError || error instanceof APIConnectionTimeoutError) {
441
- return toTransportError(error);
442
- }
443
- return null;
444
- }
@@ -1,39 +0,0 @@
1
- import chalk from 'chalk';
2
- import { createFileSections } from './files.js';
3
- export function getFileTokenStats(files, { cwd = process.cwd(), tokenizer, tokenizerOptions, inputTokenBudget, }) {
4
- if (!files.length) {
5
- return { stats: [], totalTokens: 0 };
6
- }
7
- const sections = createFileSections(files, cwd);
8
- const stats = sections
9
- .map((section) => {
10
- const tokens = tokenizer(section.sectionText, tokenizerOptions);
11
- const percent = inputTokenBudget ? (tokens / inputTokenBudget) * 100 : undefined;
12
- return {
13
- path: section.absolutePath,
14
- displayPath: section.displayPath,
15
- tokens,
16
- percent,
17
- };
18
- })
19
- .sort((a, b) => b.tokens - a.tokens);
20
- const totalTokens = stats.reduce((sum, entry) => sum + entry.tokens, 0);
21
- return { stats, totalTokens };
22
- }
23
- export function printFileTokenStats({ stats, totalTokens }, { inputTokenBudget, log = console.log }) {
24
- if (!stats.length) {
25
- return;
26
- }
27
- log(chalk.bold('File Token Usage'));
28
- for (const entry of stats) {
29
- const percentLabel = inputTokenBudget && entry.percent != null ? `${entry.percent.toFixed(2)}%` : 'n/a';
30
- log(`${entry.tokens.toLocaleString().padStart(10)} ${percentLabel.padStart(8)} ${entry.displayPath}`);
31
- }
32
- if (inputTokenBudget) {
33
- const totalPercent = (totalTokens / inputTokenBudget) * 100;
34
- log(`Total: ${totalTokens.toLocaleString()} tokens (${totalPercent.toFixed(2)}% of ${inputTokenBudget.toLocaleString()})`);
35
- }
36
- else {
37
- log(`Total: ${totalTokens.toLocaleString()} tokens`);
38
- }
39
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,9 +0,0 @@
1
- export * from './oracle/types.js';
2
- export { MODEL_CONFIGS, DEFAULT_SYSTEM_PROMPT, TOKENIZER_OPTIONS, } from './oracle/config.js';
3
- export { readFiles, createFileSections } from './oracle/files.js';
4
- export { buildPrompt, buildRequestBody, renderPromptMarkdown } from './oracle/request.js';
5
- export { formatUSD, formatNumber, formatElapsed } from './oracle/format.js';
6
- export { getFileTokenStats, printFileTokenStats } from './oracle/tokenStats.js';
7
- export { OracleResponseError, OracleTransportError, OracleUserError, FileValidationError, BrowserAutomationError, PromptValidationError, describeTransportError, extractResponseMetadata, asOracleUserError, toTransportError, } from './oracle/errors.js';
8
- export { createDefaultClientFactory } from './oracle/client.js';
9
- export { runOracle, extractTextOutput } from './oracle/run.js';