@ottocode/sdk 0.1.273 → 0.1.274
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/package.json
CHANGED
|
@@ -11,6 +11,19 @@ import {
|
|
|
11
11
|
import { assertFreshRead, rememberFileWrite } from './read-tracker.ts';
|
|
12
12
|
import { createToolError, type ToolResponse } from '../../error.ts';
|
|
13
13
|
|
|
14
|
+
const lineEndpointSchema = z.union([
|
|
15
|
+
z.number().int().min(1),
|
|
16
|
+
z.literal('end'),
|
|
17
|
+
z.literal('eof'),
|
|
18
|
+
]);
|
|
19
|
+
|
|
20
|
+
const insertAtLineSchema = z.union([
|
|
21
|
+
z.number().int().min(1),
|
|
22
|
+
z.literal('append'),
|
|
23
|
+
z.literal('end'),
|
|
24
|
+
z.literal('eof'),
|
|
25
|
+
]);
|
|
26
|
+
|
|
14
27
|
const copyIntoSchema = z.object({
|
|
15
28
|
sourcePath: z
|
|
16
29
|
.string()
|
|
@@ -20,21 +33,16 @@ const copyIntoSchema = z.object({
|
|
|
20
33
|
.int()
|
|
21
34
|
.min(1)
|
|
22
35
|
.describe('First source line to copy, 1-indexed and inclusive.'),
|
|
23
|
-
endLine:
|
|
24
|
-
.
|
|
25
|
-
|
|
26
|
-
.min(1)
|
|
27
|
-
.describe('Last source line to copy, 1-indexed and inclusive.'),
|
|
36
|
+
endLine: lineEndpointSchema.describe(
|
|
37
|
+
'Last source line to copy, 1-indexed and inclusive. Use "end" or "eof" to copy through the end of the file.',
|
|
38
|
+
),
|
|
28
39
|
targetPath: z
|
|
29
40
|
.string()
|
|
30
41
|
.describe('Relative target file path within the project.'),
|
|
31
|
-
insertAtLine:
|
|
32
|
-
.number()
|
|
33
|
-
.int()
|
|
34
|
-
.min(1)
|
|
42
|
+
insertAtLine: insertAtLineSchema
|
|
35
43
|
.optional()
|
|
36
44
|
.describe(
|
|
37
|
-
'Line to insert before, 1-indexed. Use
|
|
45
|
+
'Line to insert before, 1-indexed. Use "append" to add to the end.',
|
|
38
46
|
),
|
|
39
47
|
mode: z
|
|
40
48
|
.enum(['insert_before', 'insert_after', 'replace_range'])
|
|
@@ -47,15 +55,15 @@ const copyIntoSchema = z.object({
|
|
|
47
55
|
.min(1)
|
|
48
56
|
.optional()
|
|
49
57
|
.describe('First target line to replace when mode is replace_range.'),
|
|
50
|
-
targetEndLine:
|
|
51
|
-
.number()
|
|
52
|
-
.int()
|
|
53
|
-
.min(1)
|
|
58
|
+
targetEndLine: lineEndpointSchema
|
|
54
59
|
.optional()
|
|
55
|
-
.describe(
|
|
60
|
+
.describe(
|
|
61
|
+
'Last target line to replace when mode is replace_range. Use "end" or "eof" to replace through the end of the file.',
|
|
62
|
+
),
|
|
56
63
|
});
|
|
57
64
|
|
|
58
65
|
type CopyIntoInput = z.infer<typeof copyIntoSchema>;
|
|
66
|
+
type LineEndpoint = z.infer<typeof lineEndpointSchema>;
|
|
59
67
|
|
|
60
68
|
function splitLinesForEdit(content: string): {
|
|
61
69
|
lines: string[];
|
|
@@ -83,27 +91,37 @@ function validateRelativePath(path: string, label: string): string | undefined {
|
|
|
83
91
|
return undefined;
|
|
84
92
|
}
|
|
85
93
|
|
|
94
|
+
function resolveEndLine(value: LineEndpoint, lineCount: number): number {
|
|
95
|
+
if (typeof value === 'number') return Math.min(value, lineCount);
|
|
96
|
+
return lineCount;
|
|
97
|
+
}
|
|
98
|
+
|
|
86
99
|
function getLineRange(
|
|
87
100
|
lines: string[],
|
|
88
101
|
startLine: number,
|
|
89
|
-
|
|
90
|
-
): string[] {
|
|
91
|
-
if (startLine >
|
|
92
|
-
throw new Error(
|
|
102
|
+
endLineInput: LineEndpoint,
|
|
103
|
+
): { copied: string[]; endLine: number } {
|
|
104
|
+
if (startLine > lines.length) {
|
|
105
|
+
throw new Error(
|
|
106
|
+
`Source start line ${startLine} exceeds source file length (${lines.length} lines). Use read to confirm line numbers first.`,
|
|
107
|
+
);
|
|
93
108
|
}
|
|
94
|
-
|
|
109
|
+
|
|
110
|
+
const endLine = resolveEndLine(endLineInput, lines.length);
|
|
111
|
+
if (startLine > endLine) {
|
|
95
112
|
throw new Error(
|
|
96
|
-
`
|
|
113
|
+
`startLine must be less than or equal to endLine. Source file has ${lines.length} lines; use endLine: "end" to copy through EOF.`,
|
|
97
114
|
);
|
|
98
115
|
}
|
|
99
|
-
|
|
116
|
+
|
|
117
|
+
return { copied: lines.slice(startLine - 1, endLine), endLine };
|
|
100
118
|
}
|
|
101
119
|
|
|
102
120
|
function applyCopiedLines(
|
|
103
121
|
input: CopyIntoInput,
|
|
104
122
|
targetLines: string[],
|
|
105
123
|
copied: string[],
|
|
106
|
-
): string[] {
|
|
124
|
+
): { lines: string[]; targetRange: string } {
|
|
107
125
|
const mode = input.mode ?? 'insert_before';
|
|
108
126
|
if (mode === 'replace_range') {
|
|
109
127
|
if (
|
|
@@ -114,21 +132,30 @@ function applyCopiedLines(
|
|
|
114
132
|
'targetStartLine and targetEndLine are required when mode is replace_range.',
|
|
115
133
|
);
|
|
116
134
|
}
|
|
117
|
-
if (input.targetStartLine >
|
|
135
|
+
if (input.targetStartLine > targetLines.length) {
|
|
118
136
|
throw new Error(
|
|
119
|
-
|
|
137
|
+
`Target start line ${input.targetStartLine} exceeds target file length (${targetLines.length} lines). Use insertAtLine: "append" to append instead.`,
|
|
120
138
|
);
|
|
121
139
|
}
|
|
122
|
-
|
|
140
|
+
|
|
141
|
+
const targetEndLine = resolveEndLine(
|
|
142
|
+
input.targetEndLine,
|
|
143
|
+
targetLines.length,
|
|
144
|
+
);
|
|
145
|
+
if (input.targetStartLine > targetEndLine) {
|
|
123
146
|
throw new Error(
|
|
124
|
-
`
|
|
147
|
+
`targetStartLine must be less than or equal to targetEndLine. Target file has ${targetLines.length} lines; use targetEndLine: "end" to replace through EOF.`,
|
|
125
148
|
);
|
|
126
149
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
lines: [
|
|
153
|
+
...targetLines.slice(0, input.targetStartLine - 1),
|
|
154
|
+
...copied,
|
|
155
|
+
...targetLines.slice(targetEndLine),
|
|
156
|
+
],
|
|
157
|
+
targetRange: `${input.targetStartLine}-${targetEndLine}`,
|
|
158
|
+
};
|
|
132
159
|
}
|
|
133
160
|
|
|
134
161
|
if (input.insertAtLine === undefined) {
|
|
@@ -136,25 +163,31 @@ function applyCopiedLines(
|
|
|
136
163
|
'insertAtLine is required for insert_before and insert_after modes.',
|
|
137
164
|
);
|
|
138
165
|
}
|
|
139
|
-
if (input.insertAtLine > targetLines.length + 1) {
|
|
140
|
-
throw new Error(
|
|
141
|
-
`insertAtLine ${input.insertAtLine} exceeds append position (${targetLines.length + 1}).`,
|
|
142
|
-
);
|
|
143
|
-
}
|
|
144
166
|
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
167
|
+
const insertAtLine =
|
|
168
|
+
typeof input.insertAtLine === 'number'
|
|
169
|
+
? Math.min(
|
|
170
|
+
input.insertAtLine,
|
|
171
|
+
mode === 'insert_after' ? targetLines.length : targetLines.length + 1,
|
|
172
|
+
)
|
|
173
|
+
: mode === 'insert_after'
|
|
174
|
+
? targetLines.length
|
|
175
|
+
: targetLines.length + 1;
|
|
176
|
+
const insertIndex = mode === 'insert_after' ? insertAtLine : insertAtLine - 1;
|
|
177
|
+
if (insertIndex < 0 || insertIndex > targetLines.length) {
|
|
148
178
|
throw new Error(
|
|
149
|
-
`insertAtLine ${input.insertAtLine}
|
|
179
|
+
`insertAtLine ${String(input.insertAtLine)} is outside the target file. Use insertAtLine: "append" to append.`,
|
|
150
180
|
);
|
|
151
181
|
}
|
|
152
182
|
|
|
153
|
-
return
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
183
|
+
return {
|
|
184
|
+
lines: [
|
|
185
|
+
...targetLines.slice(0, insertIndex),
|
|
186
|
+
...copied,
|
|
187
|
+
...targetLines.slice(insertIndex),
|
|
188
|
+
],
|
|
189
|
+
targetRange: `${insertAtLine}`,
|
|
190
|
+
};
|
|
158
191
|
}
|
|
159
192
|
|
|
160
193
|
export function buildCopyIntoTool(projectRoot: string): {
|
|
@@ -168,6 +201,9 @@ export function buildCopyIntoTool(projectRoot: string): {
|
|
|
168
201
|
ToolResponse<{
|
|
169
202
|
sourcePath: string;
|
|
170
203
|
targetPath: string;
|
|
204
|
+
sourceRange: string;
|
|
205
|
+
targetRange: string;
|
|
206
|
+
mode: string;
|
|
171
207
|
linesCopied: number;
|
|
172
208
|
bytes: number;
|
|
173
209
|
artifact: unknown;
|
|
@@ -206,15 +242,19 @@ export function buildCopyIntoTool(projectRoot: string): {
|
|
|
206
242
|
readFile(targetAbs, 'utf-8'),
|
|
207
243
|
]);
|
|
208
244
|
const source = splitLinesForEdit(sourceContent);
|
|
209
|
-
const
|
|
245
|
+
const sourceRange = getLineRange(
|
|
210
246
|
source.lines,
|
|
211
247
|
input.startLine,
|
|
212
248
|
input.endLine,
|
|
213
249
|
);
|
|
214
250
|
const target = splitLinesForEdit(targetContent);
|
|
215
|
-
const
|
|
251
|
+
const applied = applyCopiedLines(
|
|
252
|
+
input,
|
|
253
|
+
target.lines,
|
|
254
|
+
sourceRange.copied,
|
|
255
|
+
);
|
|
216
256
|
const nextNormalized = joinLinesForEdit(
|
|
217
|
-
|
|
257
|
+
applied.lines,
|
|
218
258
|
target.trailingNewline,
|
|
219
259
|
);
|
|
220
260
|
const nextContent = convertToLineEnding(
|
|
@@ -241,7 +281,10 @@ export function buildCopyIntoTool(projectRoot: string): {
|
|
|
241
281
|
ok: true,
|
|
242
282
|
sourcePath: input.sourcePath,
|
|
243
283
|
targetPath: input.targetPath,
|
|
244
|
-
|
|
284
|
+
sourceRange: `${input.startLine}-${sourceRange.endLine}`,
|
|
285
|
+
targetRange: applied.targetRange,
|
|
286
|
+
mode: input.mode ?? 'insert_before',
|
|
287
|
+
linesCopied: sourceRange.copied.length,
|
|
245
288
|
bytes: nextContent.length,
|
|
246
289
|
artifact,
|
|
247
290
|
};
|
|
@@ -6,6 +6,9 @@ Rules:
|
|
|
6
6
|
- Source and target paths must be relative paths within the project.
|
|
7
7
|
- You must read the target file first in the current session before modifying it.
|
|
8
8
|
- Line numbers are 1-indexed and inclusive.
|
|
9
|
-
- `
|
|
9
|
+
- `endLine` may be a line number, `"end"`, or `"eof"`. Prefer `"end"` over guessed sentinel values like `999`.
|
|
10
|
+
- `insertAtLine` inserts before that line. Use `"append"` to add to the end.
|
|
10
11
|
- Use `mode: "replace_range"` with `targetStartLine` and `targetEndLine` to replace target lines.
|
|
12
|
+
- `targetEndLine` may be a line number, `"end"`, or `"eof"`.
|
|
13
|
+
- If an end line is past EOF, it is treated as EOF. Start lines must still exist.
|
|
11
14
|
- This tool does not use the system clipboard.
|
|
@@ -2,11 +2,7 @@ You are a research assistant with access to session history and codebase search
|
|
|
2
2
|
|
|
3
3
|
## Primary Job
|
|
4
4
|
|
|
5
|
-
Help users find information from past sessions and the codebase.
|
|
6
|
-
|
|
7
|
-
## Critical: "This Session" Means Parent Session
|
|
8
|
-
|
|
9
|
-
When the user refers to "this session", they mean the PARENT SESSION, not this research chat. **ALWAYS call `get_parent_session` FIRST** for these questions.
|
|
5
|
+
Help users find information from past sessions, session history, and the codebase.
|
|
10
6
|
|
|
11
7
|
## Database Structure
|
|
12
8
|
|
|
@@ -16,12 +12,11 @@ When the user refers to "this session", they mean the PARENT SESSION, not this r
|
|
|
16
12
|
|
|
17
13
|
## Available Tools
|
|
18
14
|
|
|
19
|
-
1. **
|
|
20
|
-
2. **
|
|
21
|
-
3. **
|
|
22
|
-
4. **
|
|
23
|
-
5. **
|
|
24
|
-
6. **present_action** - Present clickable session links at the end.
|
|
15
|
+
1. **search_history** - Full-text search across message content.
|
|
16
|
+
2. **query_sessions** - List/search sessions by agent, type, date range.
|
|
17
|
+
3. **query_messages** - Search messages across sessions.
|
|
18
|
+
4. **get_session_context** - Get details about a specific session by ID.
|
|
19
|
+
5. **present_action** - Present clickable session links when relevant.
|
|
25
20
|
|
|
26
21
|
## Codebase Tools
|
|
27
22
|
|
|
@@ -29,22 +24,24 @@ When the user refers to "this session", they mean the PARENT SESSION, not this r
|
|
|
29
24
|
|
|
30
25
|
## Research Strategy
|
|
31
26
|
|
|
32
|
-
**"What did we do" questions:**
|
|
33
|
-
1. Call `get_parent_session`
|
|
34
|
-
2. Summarize key activities
|
|
35
|
-
|
|
36
27
|
**"Find past work on X" questions:**
|
|
37
|
-
1. Use `search_history` with keywords
|
|
38
|
-
2.
|
|
28
|
+
1. Use `search_history` with focused keywords.
|
|
29
|
+
2. Use `query_sessions` or `query_messages` to narrow by agent, dates, or tool usage.
|
|
30
|
+
3. Use `get_session_context` on promising sessions.
|
|
31
|
+
|
|
32
|
+
**"What did we do" questions:**
|
|
33
|
+
1. Search session history for the topic the user mentions.
|
|
34
|
+
2. Open the most relevant sessions.
|
|
35
|
+
3. Summarize concrete activities and outcomes.
|
|
39
36
|
|
|
40
37
|
**"What tools were used" questions:**
|
|
41
|
-
1.
|
|
42
|
-
2.
|
|
38
|
+
1. Use `query_messages` with `toolName` when the tool is known.
|
|
39
|
+
2. Use `get_session_context` for relevant sessions to inspect tool calls.
|
|
43
40
|
|
|
44
41
|
## Response Guidelines
|
|
45
42
|
|
|
46
|
-
1. Be specific - quote actual content
|
|
47
|
-
2. Cite sources - reference session IDs and timestamps
|
|
48
|
-
3. Summarize clearly
|
|
49
|
-
4. Don't hallucinate - only report what you find
|
|
50
|
-
5.
|
|
43
|
+
1. Be specific - quote actual content when helpful.
|
|
44
|
+
2. Cite sources - reference session titles, IDs, and timestamps.
|
|
45
|
+
3. Summarize clearly and avoid over-explaining.
|
|
46
|
+
4. Don't hallucinate - only report what you find.
|
|
47
|
+
5. Use `present_action` only when session links would help the user navigate.
|
|
@@ -31,7 +31,7 @@ export type OpenAIOAuthConfig = {
|
|
|
31
31
|
};
|
|
32
32
|
|
|
33
33
|
function shouldDebugOpenAIOAuth() {
|
|
34
|
-
return
|
|
34
|
+
return process.env.OTTO_OPENAI_OAUTH_DEBUG === '1';
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
function logOpenAIOAuth(message: string) {
|
|
@@ -40,6 +40,45 @@ function logOpenAIOAuth(message: string) {
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
function summarizeError(error: unknown): Record<string, unknown> {
|
|
44
|
+
if (error instanceof Error) {
|
|
45
|
+
return { name: error.name, message: error.message };
|
|
46
|
+
}
|
|
47
|
+
if (error && typeof error === 'object') {
|
|
48
|
+
const err = error as Record<string, unknown>;
|
|
49
|
+
return {
|
|
50
|
+
name: typeof err.name === 'string' ? err.name : undefined,
|
|
51
|
+
message: typeof err.message === 'string' ? err.message : undefined,
|
|
52
|
+
code: typeof err.code === 'string' ? err.code : undefined,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
return { message: String(error) };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function getBodySize(body: unknown): number | undefined {
|
|
59
|
+
if (typeof body === 'string') return body.length;
|
|
60
|
+
if (body instanceof URLSearchParams) return body.toString().length;
|
|
61
|
+
if (body instanceof Blob) return body.size;
|
|
62
|
+
if (body instanceof ArrayBuffer) return body.byteLength;
|
|
63
|
+
if (ArrayBuffer.isView(body)) return body.byteLength;
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function previewResponseBody(
|
|
68
|
+
response: Response,
|
|
69
|
+
): Promise<string | undefined> {
|
|
70
|
+
try {
|
|
71
|
+
const text = await response.clone().text();
|
|
72
|
+
const normalized = text.replace(/\s+/g, ' ').trim();
|
|
73
|
+
if (!normalized) return undefined;
|
|
74
|
+
return normalized.length > 500
|
|
75
|
+
? `${normalized.slice(0, 500)}…`
|
|
76
|
+
: normalized;
|
|
77
|
+
} catch {
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
43
82
|
function shouldUsePreviousResponseId() {
|
|
44
83
|
return process.env.OTTO_OPENAI_OAUTH_PREVIOUS_RESPONSE_ID === '1';
|
|
45
84
|
}
|
|
@@ -335,6 +374,7 @@ export function createOpenAIOAuthFetch(config: OpenAIOAuthConfig) {
|
|
|
335
374
|
input: Parameters<typeof fetch>[0],
|
|
336
375
|
init?: Parameters<typeof fetch>[1],
|
|
337
376
|
): Promise<Response> => {
|
|
377
|
+
const requestStartedAt = Date.now();
|
|
338
378
|
const validated = await ensureValidToken(currentOAuth, config.projectRoot);
|
|
339
379
|
currentOAuth = validated.oauth;
|
|
340
380
|
|
|
@@ -373,18 +413,70 @@ export function createOpenAIOAuthFetch(config: OpenAIOAuthConfig) {
|
|
|
373
413
|
validated.accountId,
|
|
374
414
|
config.sessionId,
|
|
375
415
|
);
|
|
416
|
+
const requestBodySize = getBodySize(requestInit?.body);
|
|
417
|
+
const method = requestInit?.method ?? 'POST';
|
|
418
|
+
loggerDebug('[openai-oauth] request start', {
|
|
419
|
+
sessionId: config.sessionId,
|
|
420
|
+
target: isResponsesRequest ? 'codex.responses' : 'other',
|
|
421
|
+
method,
|
|
422
|
+
bodyCharsApprox: requestBodySize,
|
|
423
|
+
model: requestModel,
|
|
424
|
+
});
|
|
376
425
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
426
|
+
let response: Response;
|
|
427
|
+
try {
|
|
428
|
+
response = await fetch(targetUrl, {
|
|
429
|
+
...requestInit,
|
|
430
|
+
headers,
|
|
431
|
+
// @ts-expect-error Bun-specific fetch option
|
|
432
|
+
timeout: false,
|
|
433
|
+
});
|
|
434
|
+
} catch (error) {
|
|
435
|
+
loggerWarn('[openai-oauth] request failed before response', {
|
|
436
|
+
sessionId: config.sessionId,
|
|
437
|
+
target: isResponsesRequest ? 'codex.responses' : 'other',
|
|
438
|
+
method,
|
|
439
|
+
bodyCharsApprox: requestBodySize,
|
|
440
|
+
model: requestModel,
|
|
441
|
+
durationMs: Date.now() - requestStartedAt,
|
|
442
|
+
error: summarizeError(error),
|
|
443
|
+
});
|
|
444
|
+
throw error;
|
|
445
|
+
}
|
|
446
|
+
loggerDebug('[openai-oauth] response received', {
|
|
447
|
+
sessionId: config.sessionId,
|
|
448
|
+
target: isResponsesRequest ? 'codex.responses' : 'other',
|
|
449
|
+
status: response.status,
|
|
450
|
+
statusText: response.statusText,
|
|
451
|
+
ok: response.ok,
|
|
452
|
+
durationMs: Date.now() - requestStartedAt,
|
|
453
|
+
bodyCharsApprox: requestBodySize,
|
|
454
|
+
model: requestModel,
|
|
382
455
|
});
|
|
456
|
+
if (!response.ok && response.status !== 401) {
|
|
457
|
+
loggerWarn('[openai-oauth] non-OK response', {
|
|
458
|
+
sessionId: config.sessionId,
|
|
459
|
+
target: isResponsesRequest ? 'codex.responses' : 'other',
|
|
460
|
+
status: response.status,
|
|
461
|
+
statusText: response.statusText,
|
|
462
|
+
durationMs: Date.now() - requestStartedAt,
|
|
463
|
+
bodyCharsApprox: requestBodySize,
|
|
464
|
+
model: requestModel,
|
|
465
|
+
bodyPreview: await previewResponseBody(response),
|
|
466
|
+
});
|
|
467
|
+
}
|
|
383
468
|
const trackedResponse = isResponsesRequest
|
|
384
469
|
? trackResponsesStream(response, config.sessionId)
|
|
385
470
|
: response;
|
|
386
471
|
|
|
387
472
|
if (response.status === 401) {
|
|
473
|
+
loggerWarn('[openai-oauth] 401 response, refreshing token and retrying', {
|
|
474
|
+
sessionId: config.sessionId,
|
|
475
|
+
target: isResponsesRequest ? 'codex.responses' : 'other',
|
|
476
|
+
durationMs: Date.now() - requestStartedAt,
|
|
477
|
+
bodyCharsApprox: requestBodySize,
|
|
478
|
+
model: requestModel,
|
|
479
|
+
});
|
|
388
480
|
try {
|
|
389
481
|
const refreshedFromDisk = await getAuth('openai', config.projectRoot);
|
|
390
482
|
if (
|
|
@@ -406,18 +498,48 @@ export function createOpenAIOAuthFetch(config: OpenAIOAuthConfig) {
|
|
|
406
498
|
config.sessionId,
|
|
407
499
|
);
|
|
408
500
|
|
|
501
|
+
const retryStartedAt = Date.now();
|
|
409
502
|
const retryResponse = await fetch(targetUrl, {
|
|
410
503
|
...requestInit,
|
|
411
504
|
headers: retryHeaders,
|
|
412
505
|
// @ts-expect-error Bun-specific fetch option
|
|
413
506
|
timeout: false,
|
|
414
507
|
});
|
|
508
|
+
loggerDebug('[openai-oauth] retry response received', {
|
|
509
|
+
sessionId: config.sessionId,
|
|
510
|
+
target: isResponsesRequest ? 'codex.responses' : 'other',
|
|
511
|
+
status: retryResponse.status,
|
|
512
|
+
statusText: retryResponse.statusText,
|
|
513
|
+
ok: retryResponse.ok,
|
|
514
|
+
durationMs: Date.now() - retryStartedAt,
|
|
515
|
+
bodyCharsApprox: requestBodySize,
|
|
516
|
+
model: requestModel,
|
|
517
|
+
});
|
|
518
|
+
if (!retryResponse.ok) {
|
|
519
|
+
loggerWarn('[openai-oauth] retry non-OK response', {
|
|
520
|
+
sessionId: config.sessionId,
|
|
521
|
+
target: isResponsesRequest ? 'codex.responses' : 'other',
|
|
522
|
+
status: retryResponse.status,
|
|
523
|
+
statusText: retryResponse.statusText,
|
|
524
|
+
durationMs: Date.now() - retryStartedAt,
|
|
525
|
+
bodyCharsApprox: requestBodySize,
|
|
526
|
+
model: requestModel,
|
|
527
|
+
bodyPreview: await previewResponseBody(retryResponse),
|
|
528
|
+
});
|
|
529
|
+
}
|
|
415
530
|
return isResponsesRequest
|
|
416
531
|
? trackResponsesStream(retryResponse, config.sessionId)
|
|
417
532
|
: retryResponse;
|
|
418
|
-
} catch {
|
|
419
|
-
|
|
533
|
+
} catch (error) {
|
|
534
|
+
loggerWarn(
|
|
420
535
|
'[openai-oauth] 401 retry failed, returning original 401 response',
|
|
536
|
+
{
|
|
537
|
+
sessionId: config.sessionId,
|
|
538
|
+
target: isResponsesRequest ? 'codex.responses' : 'other',
|
|
539
|
+
bodyCharsApprox: requestBodySize,
|
|
540
|
+
model: requestModel,
|
|
541
|
+
error: summarizeError(error),
|
|
542
|
+
},
|
|
421
543
|
);
|
|
422
544
|
return response;
|
|
423
545
|
}
|