@steipete/oracle 1.0.8 → 1.1.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 (58) hide show
  1. package/README.md +3 -0
  2. package/dist/.DS_Store +0 -0
  3. package/dist/bin/oracle-cli.js +9 -3
  4. package/dist/markdansi/types/index.js +4 -0
  5. package/dist/oracle/bin/oracle-cli.js +472 -0
  6. package/dist/oracle/src/browser/actions/assistantResponse.js +471 -0
  7. package/dist/oracle/src/browser/actions/attachments.js +82 -0
  8. package/dist/oracle/src/browser/actions/modelSelection.js +190 -0
  9. package/dist/oracle/src/browser/actions/navigation.js +75 -0
  10. package/dist/oracle/src/browser/actions/promptComposer.js +167 -0
  11. package/dist/oracle/src/browser/chromeLifecycle.js +104 -0
  12. package/dist/oracle/src/browser/config.js +33 -0
  13. package/dist/oracle/src/browser/constants.js +40 -0
  14. package/dist/oracle/src/browser/cookies.js +210 -0
  15. package/dist/oracle/src/browser/domDebug.js +36 -0
  16. package/dist/oracle/src/browser/index.js +331 -0
  17. package/dist/oracle/src/browser/pageActions.js +5 -0
  18. package/dist/oracle/src/browser/prompt.js +88 -0
  19. package/dist/oracle/src/browser/promptSummary.js +20 -0
  20. package/dist/oracle/src/browser/sessionRunner.js +80 -0
  21. package/dist/oracle/src/browser/types.js +1 -0
  22. package/dist/oracle/src/browser/utils.js +62 -0
  23. package/dist/oracle/src/browserMode.js +1 -0
  24. package/dist/oracle/src/cli/browserConfig.js +44 -0
  25. package/dist/oracle/src/cli/dryRun.js +59 -0
  26. package/dist/oracle/src/cli/engine.js +17 -0
  27. package/dist/oracle/src/cli/errorUtils.js +9 -0
  28. package/dist/oracle/src/cli/help.js +70 -0
  29. package/dist/oracle/src/cli/markdownRenderer.js +15 -0
  30. package/dist/oracle/src/cli/options.js +103 -0
  31. package/dist/oracle/src/cli/promptRequirement.js +14 -0
  32. package/dist/oracle/src/cli/rootAlias.js +30 -0
  33. package/dist/oracle/src/cli/sessionCommand.js +77 -0
  34. package/dist/oracle/src/cli/sessionDisplay.js +270 -0
  35. package/dist/oracle/src/cli/sessionRunner.js +94 -0
  36. package/dist/oracle/src/heartbeat.js +43 -0
  37. package/dist/oracle/src/oracle/client.js +48 -0
  38. package/dist/oracle/src/oracle/config.js +29 -0
  39. package/dist/oracle/src/oracle/errors.js +101 -0
  40. package/dist/oracle/src/oracle/files.js +220 -0
  41. package/dist/oracle/src/oracle/format.js +33 -0
  42. package/dist/oracle/src/oracle/fsAdapter.js +7 -0
  43. package/dist/oracle/src/oracle/oscProgress.js +60 -0
  44. package/dist/oracle/src/oracle/request.js +48 -0
  45. package/dist/oracle/src/oracle/run.js +444 -0
  46. package/dist/oracle/src/oracle/tokenStats.js +39 -0
  47. package/dist/oracle/src/oracle/types.js +1 -0
  48. package/dist/oracle/src/oracle.js +9 -0
  49. package/dist/oracle/src/sessionManager.js +205 -0
  50. package/dist/oracle/src/version.js +39 -0
  51. package/dist/src/cli/markdownRenderer.js +18 -0
  52. package/dist/src/cli/rootAlias.js +14 -0
  53. package/dist/src/cli/sessionCommand.js +60 -2
  54. package/dist/src/cli/sessionDisplay.js +129 -4
  55. package/dist/src/oracle/oscProgress.js +60 -0
  56. package/dist/src/oracle/run.js +63 -51
  57. package/dist/src/sessionManager.js +17 -0
  58. package/package.json +14 -22
@@ -0,0 +1,270 @@
1
+ import chalk from 'chalk';
2
+ import kleur from 'kleur';
3
+ import { filterSessionsByRange, listSessionsMetadata, readSessionLog, readSessionMetadata, SESSIONS_DIR, wait, } from '../sessionManager.js';
4
+ import { renderMarkdownAnsi } from './markdownRenderer.js';
5
+ const isTty = () => Boolean(process.stdout.isTTY);
6
+ const dim = (text) => (isTty() ? kleur.dim(text) : text);
7
+ const MAX_RENDER_BYTES = 200_000;
8
+ const CLEANUP_TIP = 'Tip: Run "oracle session --clear --hours 24" to prune cached runs (add --all to wipe everything).';
9
+ export async function showStatus({ hours, includeAll, limit, showExamples = false }) {
10
+ const metas = await listSessionsMetadata();
11
+ const { entries, truncated, total } = filterSessionsByRange(metas, { hours, includeAll, limit });
12
+ const richTty = process.stdout.isTTY && chalk.level > 0;
13
+ if (!entries.length) {
14
+ console.log(CLEANUP_TIP);
15
+ if (showExamples) {
16
+ printStatusExamples();
17
+ }
18
+ return;
19
+ }
20
+ console.log(chalk.bold('Recent Sessions'));
21
+ for (const entry of entries) {
22
+ const statusRaw = (entry.status || 'unknown').padEnd(9);
23
+ const status = richTty ? colorStatus(entry.status ?? 'unknown', statusRaw) : statusRaw;
24
+ const model = (entry.model || 'n/a').padEnd(9);
25
+ const created = entry.createdAt.replace('T', ' ').replace('Z', '');
26
+ console.log(`${created} | ${status} | ${model} | ${entry.id}`);
27
+ }
28
+ if (truncated) {
29
+ console.log(chalk.yellow(`Showing ${entries.length} of ${total} sessions from the requested range. Run "oracle session --clear" or delete entries in ${SESSIONS_DIR} to free space, or rerun with --status-limit/--status-all.`));
30
+ }
31
+ if (showExamples) {
32
+ printStatusExamples();
33
+ }
34
+ }
35
+ function colorStatus(status, padded) {
36
+ switch (status) {
37
+ case 'completed':
38
+ return chalk.green(padded);
39
+ case 'error':
40
+ return chalk.red(padded);
41
+ case 'running':
42
+ return chalk.yellow(padded);
43
+ default:
44
+ return padded;
45
+ }
46
+ }
47
+ export async function attachSession(sessionId, options) {
48
+ const metadata = await readSessionMetadata(sessionId);
49
+ if (!metadata) {
50
+ console.error(chalk.red(`No session found with ID ${sessionId}`));
51
+ process.exitCode = 1;
52
+ return;
53
+ }
54
+ const initialStatus = metadata.status;
55
+ const wantsRender = Boolean(options?.renderMarkdown);
56
+ const isVerbose = Boolean(process.env.ORACLE_VERBOSE_RENDER);
57
+ if (!options?.suppressMetadata) {
58
+ const reattachLine = buildReattachLine(metadata);
59
+ if (reattachLine) {
60
+ console.log(chalk.blue(reattachLine));
61
+ }
62
+ console.log(`Created: ${metadata.createdAt}`);
63
+ console.log(`Status: ${metadata.status}`);
64
+ console.log(`Model: ${metadata.model}`);
65
+ const responseSummary = formatResponseMetadata(metadata.response);
66
+ if (responseSummary) {
67
+ console.log(dim(`Response: ${responseSummary}`));
68
+ }
69
+ const transportSummary = formatTransportMetadata(metadata.transport);
70
+ if (transportSummary) {
71
+ console.log(dim(`Transport: ${transportSummary}`));
72
+ }
73
+ const userErrorSummary = formatUserErrorMetadata(metadata.error);
74
+ if (userErrorSummary) {
75
+ console.log(dim(`User error: ${userErrorSummary}`));
76
+ }
77
+ }
78
+ const shouldTrimIntro = initialStatus === 'completed' || initialStatus === 'error';
79
+ if (shouldTrimIntro) {
80
+ const fullLog = await readSessionLog(sessionId);
81
+ const trimmed = trimBeforeFirstAnswer(fullLog);
82
+ const size = Buffer.byteLength(trimmed, 'utf8');
83
+ const canRender = wantsRender && isTty() && size <= MAX_RENDER_BYTES;
84
+ if (wantsRender && size > MAX_RENDER_BYTES) {
85
+ const msg = `Render skipped (log too large: ${size} bytes > ${MAX_RENDER_BYTES}). Showing raw text.`;
86
+ console.log(dim(msg));
87
+ if (isVerbose) {
88
+ console.log(dim(`Verbose: renderMarkdown=true tty=${isTty()} size=${size}`));
89
+ }
90
+ }
91
+ else if (wantsRender && !isTty()) {
92
+ const msg = 'Render requested but stdout is not a TTY; showing raw text.';
93
+ console.log(dim(msg));
94
+ if (isVerbose) {
95
+ console.log(dim(`Verbose: renderMarkdown=true tty=${isTty()} size=${size}`));
96
+ }
97
+ }
98
+ if (canRender) {
99
+ if (isVerbose) {
100
+ console.log(dim(`Verbose: rendering markdown (size=${size}, tty=${isTty()})`));
101
+ }
102
+ process.stdout.write(renderMarkdownAnsi(trimmed));
103
+ }
104
+ else {
105
+ process.stdout.write(trimmed);
106
+ }
107
+ return;
108
+ }
109
+ if (wantsRender) {
110
+ console.log(dim('Render will apply after completion; streaming raw text meanwhile...'));
111
+ if (isVerbose) {
112
+ console.log(dim(`Verbose: streaming phase renderMarkdown=true tty=${isTty()}`));
113
+ }
114
+ }
115
+ let lastLength = 0;
116
+ const printNew = async () => {
117
+ const text = await readSessionLog(sessionId);
118
+ const nextChunk = text.slice(lastLength);
119
+ if (nextChunk.length > 0) {
120
+ process.stdout.write(nextChunk);
121
+ lastLength = text.length;
122
+ }
123
+ };
124
+ await printNew();
125
+ // biome-ignore lint/nursery/noUnnecessaryConditions: deliberate infinite poll
126
+ while (true) {
127
+ const latest = await readSessionMetadata(sessionId);
128
+ if (!latest) {
129
+ break;
130
+ }
131
+ if (latest.status === 'completed' || latest.status === 'error') {
132
+ await printNew();
133
+ if (!options?.suppressMetadata) {
134
+ if (latest.status === 'error' && latest.errorMessage) {
135
+ console.log('\nResult:');
136
+ console.log(`Session failed: ${latest.errorMessage}`);
137
+ }
138
+ if (latest.usage && initialStatus === 'running') {
139
+ const usage = latest.usage;
140
+ console.log(`\nFinished (tok i/o/r/t: ${usage.inputTokens}/${usage.outputTokens}/${usage.reasoningTokens}/${usage.totalTokens})`);
141
+ }
142
+ }
143
+ break;
144
+ }
145
+ await wait(1000);
146
+ await printNew();
147
+ }
148
+ }
149
+ export function formatResponseMetadata(metadata) {
150
+ if (!metadata) {
151
+ return null;
152
+ }
153
+ const parts = [];
154
+ if (metadata.responseId) {
155
+ parts.push(`response=${metadata.responseId}`);
156
+ }
157
+ if (metadata.requestId) {
158
+ parts.push(`request=${metadata.requestId}`);
159
+ }
160
+ if (metadata.status) {
161
+ parts.push(`status=${metadata.status}`);
162
+ }
163
+ if (metadata.incompleteReason) {
164
+ parts.push(`incomplete=${metadata.incompleteReason}`);
165
+ }
166
+ return parts.length > 0 ? parts.join(' | ') : null;
167
+ }
168
+ export function formatTransportMetadata(metadata) {
169
+ if (!metadata?.reason) {
170
+ return null;
171
+ }
172
+ const reasonLabels = {
173
+ 'client-timeout': 'client timeout (20m deadline hit)',
174
+ 'connection-lost': 'connection lost before completion',
175
+ 'client-abort': 'request aborted locally',
176
+ unknown: 'unknown transport failure',
177
+ };
178
+ const label = reasonLabels[metadata.reason] ?? 'transport error';
179
+ return `${metadata.reason} — ${label}`;
180
+ }
181
+ export function formatUserErrorMetadata(metadata) {
182
+ if (!metadata) {
183
+ return null;
184
+ }
185
+ const parts = [];
186
+ if (metadata.category) {
187
+ parts.push(metadata.category);
188
+ }
189
+ if (metadata.message) {
190
+ parts.push(`message=${metadata.message}`);
191
+ }
192
+ if (metadata.details && Object.keys(metadata.details).length > 0) {
193
+ parts.push(`details=${JSON.stringify(metadata.details)}`);
194
+ }
195
+ return parts.length > 0 ? parts.join(' | ') : null;
196
+ }
197
+ export function buildReattachLine(metadata) {
198
+ if (!metadata.id) {
199
+ return null;
200
+ }
201
+ const referenceTime = metadata.startedAt ?? metadata.createdAt;
202
+ if (!referenceTime) {
203
+ return null;
204
+ }
205
+ const elapsedLabel = formatRelativeDuration(referenceTime);
206
+ if (!elapsedLabel) {
207
+ return null;
208
+ }
209
+ if (metadata.status === 'running') {
210
+ return `Session ${metadata.id} reattached, request started ${elapsedLabel} ago.`;
211
+ }
212
+ return null;
213
+ }
214
+ export function trimBeforeFirstAnswer(logText) {
215
+ const marker = 'Answer:';
216
+ const index = logText.indexOf(marker);
217
+ if (index === -1) {
218
+ return logText;
219
+ }
220
+ return logText.slice(index);
221
+ }
222
+ function formatRelativeDuration(referenceIso) {
223
+ const timestamp = Date.parse(referenceIso);
224
+ if (Number.isNaN(timestamp)) {
225
+ return null;
226
+ }
227
+ const diffMs = Date.now() - timestamp;
228
+ if (diffMs < 0) {
229
+ return null;
230
+ }
231
+ const seconds = Math.max(1, Math.round(diffMs / 1000));
232
+ if (seconds < 60) {
233
+ return `${seconds}s`;
234
+ }
235
+ const minutes = Math.floor(seconds / 60);
236
+ const remainingSeconds = seconds % 60;
237
+ if (minutes < 60) {
238
+ return remainingSeconds > 0 ? `${minutes}m ${remainingSeconds}s` : `${minutes}m`;
239
+ }
240
+ const hours = Math.floor(minutes / 60);
241
+ const remainingMinutes = minutes % 60;
242
+ if (hours < 24) {
243
+ const parts = [`${hours}h`];
244
+ if (remainingMinutes > 0) {
245
+ parts.push(`${remainingMinutes}m`);
246
+ }
247
+ return parts.join(' ');
248
+ }
249
+ const days = Math.floor(hours / 24);
250
+ const remainingHours = hours % 24;
251
+ const parts = [`${days}d`];
252
+ if (remainingHours > 0) {
253
+ parts.push(`${remainingHours}h`);
254
+ }
255
+ if (remainingMinutes > 0 && days === 0) {
256
+ parts.push(`${remainingMinutes}m`);
257
+ }
258
+ return parts.join(' ');
259
+ }
260
+ function printStatusExamples() {
261
+ console.log('');
262
+ console.log(chalk.bold('Usage Examples'));
263
+ console.log(`${chalk.bold(' oracle status --hours 72 --limit 50')}`);
264
+ console.log(dim(' Show 72h of history capped at 50 entries.'));
265
+ console.log(`${chalk.bold(' oracle status --clear --hours 168')}`);
266
+ console.log(dim(' Delete sessions older than 7 days (use --all to wipe everything).'));
267
+ console.log(`${chalk.bold(' oracle session <session-id>')}`);
268
+ console.log(dim(' Attach to a specific running/completed session to stream its output.'));
269
+ console.log(dim(CLEANUP_TIP));
270
+ }
@@ -0,0 +1,94 @@
1
+ import kleur from 'kleur';
2
+ import { updateSessionMetadata } from '../sessionManager.js';
3
+ import { runOracle, OracleResponseError, OracleTransportError, extractResponseMetadata, asOracleUserError, } from '../oracle.js';
4
+ import { runBrowserSessionExecution } from '../browser/sessionRunner.js';
5
+ import { formatResponseMetadata, formatTransportMetadata } from './sessionDisplay.js';
6
+ import { markErrorLogged } from './errorUtils.js';
7
+ const isTty = process.stdout.isTTY;
8
+ const dim = (text) => (isTty ? kleur.dim(text) : text);
9
+ export async function performSessionRun({ sessionMeta, runOptions, mode, browserConfig, cwd, log, write, version, }) {
10
+ await updateSessionMetadata(sessionMeta.id, {
11
+ status: 'running',
12
+ startedAt: new Date().toISOString(),
13
+ mode,
14
+ ...(browserConfig ? { browser: { config: browserConfig } } : {}),
15
+ });
16
+ try {
17
+ if (mode === 'browser') {
18
+ if (!browserConfig) {
19
+ throw new Error('Missing browser configuration for session.');
20
+ }
21
+ const result = await runBrowserSessionExecution({ runOptions, browserConfig, cwd, log, cliVersion: version }, {});
22
+ await updateSessionMetadata(sessionMeta.id, {
23
+ status: 'completed',
24
+ completedAt: new Date().toISOString(),
25
+ usage: result.usage,
26
+ elapsedMs: result.elapsedMs,
27
+ browser: {
28
+ config: browserConfig,
29
+ runtime: result.runtime,
30
+ },
31
+ response: undefined,
32
+ transport: undefined,
33
+ error: undefined,
34
+ });
35
+ return;
36
+ }
37
+ const result = await runOracle(runOptions, {
38
+ cwd,
39
+ log,
40
+ write,
41
+ });
42
+ if (result.mode !== 'live') {
43
+ throw new Error('Unexpected preview result while running a session.');
44
+ }
45
+ await updateSessionMetadata(sessionMeta.id, {
46
+ status: 'completed',
47
+ completedAt: new Date().toISOString(),
48
+ usage: result.usage,
49
+ elapsedMs: result.elapsedMs,
50
+ response: extractResponseMetadata(result.response),
51
+ transport: undefined,
52
+ error: undefined,
53
+ });
54
+ }
55
+ catch (error) {
56
+ const message = formatError(error);
57
+ log(`ERROR: ${message}`);
58
+ markErrorLogged(error);
59
+ const userError = asOracleUserError(error);
60
+ if (userError) {
61
+ log(dim(`User error (${userError.category}): ${userError.message}`));
62
+ }
63
+ const responseMetadata = error instanceof OracleResponseError ? error.metadata : undefined;
64
+ const metadataLine = formatResponseMetadata(responseMetadata);
65
+ if (metadataLine) {
66
+ log(dim(`Response metadata: ${metadataLine}`));
67
+ }
68
+ const transportMetadata = error instanceof OracleTransportError ? { reason: error.reason } : undefined;
69
+ const transportLine = formatTransportMetadata(transportMetadata);
70
+ if (transportLine) {
71
+ log(dim(`Transport: ${transportLine}`));
72
+ }
73
+ await updateSessionMetadata(sessionMeta.id, {
74
+ status: 'error',
75
+ completedAt: new Date().toISOString(),
76
+ errorMessage: message,
77
+ mode,
78
+ browser: browserConfig ? { config: browserConfig } : undefined,
79
+ response: responseMetadata,
80
+ transport: transportMetadata,
81
+ error: userError
82
+ ? {
83
+ category: userError.category,
84
+ message: userError.message,
85
+ details: userError.details,
86
+ }
87
+ : undefined,
88
+ });
89
+ throw error;
90
+ }
91
+ }
92
+ function formatError(error) {
93
+ return error instanceof Error ? error.message : String(error);
94
+ }
@@ -0,0 +1,43 @@
1
+ export function startHeartbeat(config) {
2
+ const { intervalMs, log, isActive, makeMessage } = config;
3
+ if (!intervalMs || intervalMs <= 0) {
4
+ return () => { };
5
+ }
6
+ let stopped = false;
7
+ let pending = false;
8
+ const start = Date.now();
9
+ const timer = setInterval(async () => {
10
+ // biome-ignore lint/nursery/noUnnecessaryConditions: stop flag flips asynchronously
11
+ if (stopped || pending) {
12
+ return;
13
+ }
14
+ if (!isActive()) {
15
+ stop();
16
+ return;
17
+ }
18
+ pending = true;
19
+ try {
20
+ const elapsed = Date.now() - start;
21
+ const message = await makeMessage(elapsed);
22
+ if (message && !stopped) {
23
+ log(message);
24
+ }
25
+ }
26
+ catch {
27
+ // ignore heartbeat errors
28
+ }
29
+ finally {
30
+ pending = false;
31
+ }
32
+ }, intervalMs);
33
+ timer.unref?.();
34
+ const stop = () => {
35
+ // biome-ignore lint/nursery/noUnnecessaryConditions: multiple callers may race to stop
36
+ if (stopped) {
37
+ return;
38
+ }
39
+ stopped = true;
40
+ clearInterval(timer);
41
+ };
42
+ return stop;
43
+ }
@@ -0,0 +1,48 @@
1
+ import OpenAI from 'openai';
2
+ import path from 'node:path';
3
+ import { createRequire } from 'node:module';
4
+ const CUSTOM_CLIENT_FACTORY = loadCustomClientFactory();
5
+ export function createDefaultClientFactory() {
6
+ if (CUSTOM_CLIENT_FACTORY) {
7
+ return CUSTOM_CLIENT_FACTORY;
8
+ }
9
+ return (key) => {
10
+ const instance = new OpenAI({
11
+ apiKey: key,
12
+ timeout: 20 * 60 * 1000,
13
+ });
14
+ return {
15
+ responses: {
16
+ stream: (body) => instance.responses.stream(body),
17
+ create: (body) => instance.responses.create(body),
18
+ retrieve: (id) => instance.responses.retrieve(id),
19
+ },
20
+ };
21
+ };
22
+ }
23
+ function loadCustomClientFactory() {
24
+ const override = process.env.ORACLE_CLIENT_FACTORY;
25
+ if (!override) {
26
+ return null;
27
+ }
28
+ try {
29
+ const require = createRequire(import.meta.url);
30
+ const resolved = path.isAbsolute(override) ? override : path.resolve(process.cwd(), override);
31
+ const moduleExports = require(resolved);
32
+ const factory = typeof moduleExports === 'function'
33
+ ? moduleExports
34
+ : typeof moduleExports?.default === 'function'
35
+ ? moduleExports.default
36
+ : typeof moduleExports?.createClientFactory === 'function'
37
+ ? moduleExports.createClientFactory
38
+ : null;
39
+ if (typeof factory === 'function') {
40
+ return factory;
41
+ }
42
+ console.warn(`Custom client factory at ${resolved} did not export a function.`);
43
+ }
44
+ catch (error) {
45
+ console.warn(`Failed to load ORACLE_CLIENT_FACTORY module "${override}":`, error);
46
+ }
47
+ return null;
48
+ }
@@ -0,0 +1,29 @@
1
+ import { countTokens as countTokensGpt5 } from 'gpt-tokenizer/model/gpt-5';
2
+ import { countTokens as countTokensGpt5Pro } from 'gpt-tokenizer/model/gpt-5-pro';
3
+ export const MODEL_CONFIGS = {
4
+ 'gpt-5-pro': {
5
+ model: 'gpt-5-pro',
6
+ tokenizer: countTokensGpt5Pro,
7
+ inputLimit: 196000,
8
+ pricing: {
9
+ inputPerToken: 15 / 1_000_000,
10
+ outputPerToken: 120 / 1_000_000,
11
+ },
12
+ reasoning: null,
13
+ },
14
+ 'gpt-5.1': {
15
+ model: 'gpt-5.1',
16
+ tokenizer: countTokensGpt5,
17
+ inputLimit: 196000,
18
+ pricing: {
19
+ inputPerToken: 1.25 / 1_000_000,
20
+ outputPerToken: 10 / 1_000_000,
21
+ },
22
+ reasoning: { effort: 'high' },
23
+ },
24
+ };
25
+ export const DEFAULT_SYSTEM_PROMPT = [
26
+ 'You are Oracle, a focused one-shot problem solver.',
27
+ 'Emphasize direct answers, cite any files referenced, and clearly note when the search tool was used.',
28
+ ].join(' ');
29
+ export const TOKENIZER_OPTIONS = { allowedSpecial: 'all' };
@@ -0,0 +1,101 @@
1
+ import { APIConnectionError, APIConnectionTimeoutError, APIUserAbortError, } from 'openai';
2
+ export class OracleUserError extends Error {
3
+ category;
4
+ details;
5
+ constructor(category, message, details, cause) {
6
+ super(message);
7
+ this.name = 'OracleUserError';
8
+ this.category = category;
9
+ this.details = details;
10
+ if (cause) {
11
+ this.cause = cause;
12
+ }
13
+ }
14
+ }
15
+ export class FileValidationError extends OracleUserError {
16
+ constructor(message, details, cause) {
17
+ super('file-validation', message, details, cause);
18
+ this.name = 'FileValidationError';
19
+ }
20
+ }
21
+ export class BrowserAutomationError extends OracleUserError {
22
+ constructor(message, details, cause) {
23
+ super('browser-automation', message, details, cause);
24
+ this.name = 'BrowserAutomationError';
25
+ }
26
+ }
27
+ export class PromptValidationError extends OracleUserError {
28
+ constructor(message, details, cause) {
29
+ super('prompt-validation', message, details, cause);
30
+ this.name = 'PromptValidationError';
31
+ }
32
+ }
33
+ export function asOracleUserError(error) {
34
+ if (error instanceof OracleUserError) {
35
+ return error;
36
+ }
37
+ return null;
38
+ }
39
+ export class OracleTransportError extends Error {
40
+ reason;
41
+ constructor(reason, message, cause) {
42
+ super(message);
43
+ this.name = 'OracleTransportError';
44
+ this.reason = reason;
45
+ if (cause) {
46
+ this.cause = cause;
47
+ }
48
+ }
49
+ }
50
+ export class OracleResponseError extends Error {
51
+ metadata;
52
+ response;
53
+ constructor(message, response) {
54
+ super(message);
55
+ this.name = 'OracleResponseError';
56
+ this.response = response;
57
+ this.metadata = extractResponseMetadata(response);
58
+ }
59
+ }
60
+ export function extractResponseMetadata(response) {
61
+ if (!response) {
62
+ return {};
63
+ }
64
+ const metadata = {
65
+ responseId: response.id,
66
+ status: response.status,
67
+ incompleteReason: response.incomplete_details?.reason ?? undefined,
68
+ };
69
+ const requestId = response._request_id;
70
+ if (requestId !== undefined) {
71
+ metadata.requestId = requestId;
72
+ }
73
+ return metadata;
74
+ }
75
+ export function toTransportError(error) {
76
+ if (error instanceof OracleTransportError) {
77
+ return error;
78
+ }
79
+ if (error instanceof APIConnectionTimeoutError) {
80
+ return new OracleTransportError('client-timeout', 'OpenAI request timed out before completion.', error);
81
+ }
82
+ if (error instanceof APIUserAbortError) {
83
+ return new OracleTransportError('client-abort', 'The request was aborted before OpenAI finished responding.', error);
84
+ }
85
+ if (error instanceof APIConnectionError) {
86
+ return new OracleTransportError('connection-lost', 'Connection to OpenAI dropped before the response completed.', error);
87
+ }
88
+ return new OracleTransportError('unknown', error instanceof Error ? error.message : 'Unknown transport failure.', error);
89
+ }
90
+ export function describeTransportError(error) {
91
+ switch (error.reason) {
92
+ case 'client-timeout':
93
+ return 'Client-side timeout: OpenAI streaming call exceeded the 20m deadline.';
94
+ case 'connection-lost':
95
+ return 'Connection to OpenAI ended unexpectedly before the response completed.';
96
+ case 'client-abort':
97
+ return 'Request was aborted before OpenAI completed the response.';
98
+ default:
99
+ return 'OpenAI streaming call ended with an unknown transport error.';
100
+ }
101
+ }