@ottocode/sdk 0.1.233 → 0.1.235

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ottocode/sdk",
3
- "version": "0.1.233",
3
+ "version": "0.1.235",
4
4
  "description": "AI agent SDK for building intelligent assistants - tree-shakable and comprehensive",
5
5
  "author": "nitishxyz",
6
6
  "license": "MIT",
@@ -29,14 +29,6 @@ export class TerminalManager {
29
29
  const id = this.generateId();
30
30
 
31
31
  try {
32
- logger.debug('TerminalManager: creating terminal', {
33
- id,
34
- command: options.command,
35
- args: options.args,
36
- cwd: options.cwd,
37
- purpose: options.purpose,
38
- });
39
-
40
32
  const ptyOptions: PtyOptions = {
41
33
  name: 'xterm-256color',
42
34
  cols: 80,
@@ -52,10 +44,6 @@ export class TerminalManager {
52
44
 
53
45
  const pty = spawnPty(options.command, options.args || [], ptyOptions);
54
46
 
55
- logger.debug('TerminalManager: PTY created', {
56
- pid: pty.pid,
57
- });
58
-
59
47
  const terminal = new Terminal(id, pty, options);
60
48
 
61
49
  terminal.onExit((_exitCode) => {
@@ -68,8 +56,6 @@ export class TerminalManager {
68
56
 
69
57
  this.terminals.set(id, terminal);
70
58
 
71
- logger.debug('TerminalManager: terminal added to map', { id });
72
-
73
59
  return terminal;
74
60
  } catch (error) {
75
61
  logger.error('TerminalManager: failed to create terminal', error);
@@ -179,10 +179,7 @@ export async function discoverProjectTools(
179
179
  try {
180
180
  const plugin = await loadPlugin(absPath, folder, projectRoot);
181
181
  if (plugin) tools.set(plugin.name, plugin.tool);
182
- } catch (err) {
183
- if (process.env.OTTO_DEBUG_TOOLS === '1')
184
- console.error('Failed to load tool', absPath, err);
185
- }
182
+ } catch {}
186
183
  }
187
184
  }
188
185
  // Fallback: manual directory scan
@@ -1,40 +1,7 @@
1
- const TRUTHY = new Set(['1', 'true', 'yes', 'on']);
2
-
3
- type GlobalDebugFlags = {
4
- __OTTO_DEBUG_ENABLED__?: boolean;
5
- __OTTO_TRACE_ENABLED__?: boolean;
6
- };
7
-
8
- function readGlobalFlag(
9
- key: '__OTTO_DEBUG_ENABLED__' | '__OTTO_TRACE_ENABLED__',
10
- ) {
11
- const globalState = globalThis as GlobalDebugFlags;
12
- return globalState[key];
13
- }
14
-
15
- function envEnabled(keys: string[]): boolean {
16
- for (const key of keys) {
17
- const raw = typeof process !== 'undefined' ? process.env?.[key] : undefined;
18
- if (!raw) continue;
19
- const trimmed = raw.trim().toLowerCase();
20
- if (!trimmed) continue;
21
- if (TRUTHY.has(trimmed) || trimmed === 'all') return true;
22
- }
23
- return false;
24
- }
25
-
26
1
  export function isDebugEnabled(): boolean {
27
- const globalFlag = readGlobalFlag('__OTTO_DEBUG_ENABLED__');
28
- if (typeof globalFlag === 'boolean') {
29
- return globalFlag;
30
- }
31
- return envEnabled(['OTTO_DEBUG', 'DEBUG_OTTO']);
2
+ return false;
32
3
  }
33
4
 
34
5
  export function isTraceEnabled(): boolean {
35
- const globalFlag = readGlobalFlag('__OTTO_TRACE_ENABLED__');
36
- if (typeof globalFlag === 'boolean') {
37
- return Boolean(globalFlag) && isDebugEnabled();
38
- }
39
- return envEnabled(['OTTO_TRACE', 'TRACE_OTTO']) && isDebugEnabled();
6
+ return false;
40
7
  }
@@ -1,3 +1,5 @@
1
+ import { appendFileSync, mkdirSync } from 'node:fs';
2
+ import { dirname } from 'node:path';
1
3
  import { isDebugEnabled, isTraceEnabled } from './debug.ts';
2
4
 
3
5
  export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
@@ -8,13 +10,44 @@ function safeHasMeta(
8
10
  return Boolean(meta && Object.keys(meta).length);
9
11
  }
10
12
 
13
+ function getDebugLogFilePath(): string | undefined {
14
+ return undefined;
15
+ }
16
+
17
+ function serializeLogMeta(meta?: Record<string, unknown>): string {
18
+ if (!safeHasMeta(meta)) return '';
19
+ try {
20
+ return ` ${JSON.stringify(meta)}`;
21
+ } catch {
22
+ return ' [unserializable-meta]';
23
+ }
24
+ }
25
+
26
+ function writeLogLine(line: string, meta?: Record<string, unknown>) {
27
+ const suffix = serializeLogMeta(meta);
28
+ const fullLine = `${new Date().toISOString()} ${line}${suffix}`;
29
+ const logFile = getDebugLogFilePath();
30
+
31
+ if (logFile) {
32
+ try {
33
+ mkdirSync(dirname(logFile), { recursive: true });
34
+ appendFileSync(logFile, `${fullLine}\n`, 'utf-8');
35
+ } catch {
36
+ // ignore file logging errors
37
+ }
38
+ }
39
+
40
+ return fullLine;
41
+ }
42
+
11
43
  export function debug(message: string, meta?: Record<string, unknown>): void {
12
44
  if (!isDebugEnabled()) return;
13
45
  try {
46
+ const line = writeLogLine(`[debug] ${message}`, meta);
14
47
  if (safeHasMeta(meta)) {
15
- console.log(`[debug] ${message}`, meta);
48
+ console.log(line, meta);
16
49
  } else {
17
- console.log(`[debug] ${message}`);
50
+ console.log(line);
18
51
  }
19
52
  } catch {
20
53
  // ignore logging errors
@@ -24,10 +57,11 @@ export function debug(message: string, meta?: Record<string, unknown>): void {
24
57
  export function info(message: string, meta?: Record<string, unknown>): void {
25
58
  if (!isDebugEnabled() && !isTraceEnabled()) return;
26
59
  try {
60
+ const line = writeLogLine(`[info] ${message}`, meta);
27
61
  if (safeHasMeta(meta)) {
28
- console.log(`[info] ${message}`, meta);
62
+ console.log(line, meta);
29
63
  } else {
30
- console.log(`[info] ${message}`);
64
+ console.log(line);
31
65
  }
32
66
  } catch {
33
67
  // ignore logging errors
@@ -36,10 +70,11 @@ export function info(message: string, meta?: Record<string, unknown>): void {
36
70
 
37
71
  export function warn(message: string, meta?: Record<string, unknown>): void {
38
72
  try {
73
+ const line = writeLogLine(`[warn] ${message}`, meta);
39
74
  if (safeHasMeta(meta)) {
40
- console.warn(`[warn] ${message}`, meta);
75
+ console.warn(line, meta);
41
76
  } else {
42
- console.warn(`[warn] ${message}`);
77
+ console.warn(line);
43
78
  }
44
79
  } catch {
45
80
  // ignore logging errors
@@ -91,9 +126,11 @@ export function error(
91
126
  }
92
127
 
93
128
  if (safeHasMeta(logMeta)) {
94
- console.error(`[error] ${message}`, logMeta);
129
+ const line = writeLogLine(`[error] ${message}`, logMeta);
130
+ console.error(line, logMeta);
95
131
  } else {
96
- console.error(`[error] ${message}`);
132
+ const line = writeLogLine(`[error] ${message}`);
133
+ console.error(line);
97
134
  }
98
135
  } catch (logErr) {
99
136
  try {
@@ -136,7 +173,10 @@ export function time(label: string): Timer {
136
173
  finished = true;
137
174
  const duration = nowMs() - start;
138
175
  try {
139
- const base = `[timing] ${label} ${duration.toFixed(1)}ms`;
176
+ const base = writeLogLine(
177
+ `[timing] ${label} ${duration.toFixed(1)}ms`,
178
+ meta,
179
+ );
140
180
  if (safeHasMeta(meta)) {
141
181
  console.log(base, meta);
142
182
  } else {
@@ -3,6 +3,12 @@ You are a helpful, concise assistant.
3
3
  - Do not print pseudo tool calls like `call:tool{}`; invoke tools directly.
4
4
  - Use sensible default filenames when needed.
5
5
  - Prefer minimal, precise outputs and actionable steps.
6
+ - Keep user-facing responses short, scannable, and suitable for a terminal UI.
7
+ - Default to a concise teammate tone unless the user explicitly asks for detail.
8
+ - Avoid long retrospective narratives of every step you took.
9
+ - Do not restate tool outputs unless they are necessary for the user.
10
+ - Final responses should usually be 3-6 short bullets or a few short paragraphs.
11
+ - Focus on outcome, key files, verification, and only the most relevant next step.
6
12
 
7
13
  ## Finish Tool - CRITICAL
8
14
 
@@ -1,77 +1,6 @@
1
- const TRUTHY = new Set(['1', 'true', 'yes', 'on']);
2
-
3
- const SYNONYMS: Record<string, string> = {
4
- debug: 'log',
5
- logs: 'log',
6
- logging: 'log',
7
- trace: 'log',
8
- verbose: 'log',
9
- log: 'log',
10
- time: 'timing',
11
- timing: 'timing',
12
- timings: 'timing',
13
- perf: 'timing',
14
- };
15
-
16
- type DebugConfig = { flags: Set<string> };
17
-
18
- let cachedConfig: DebugConfig | null = null;
19
-
20
- function isTruthy(raw: string | undefined): boolean {
21
- if (!raw) return false;
22
- const trimmed = raw.trim().toLowerCase();
23
- if (!trimmed) return false;
24
- return TRUTHY.has(trimmed) || trimmed === 'all';
25
- }
26
-
27
- function normalizeToken(token: string): string {
28
- const trimmed = token.trim().toLowerCase();
29
- if (!trimmed) return '';
30
- if (TRUTHY.has(trimmed) || trimmed === 'all') return 'all';
31
- return SYNONYMS[trimmed] ?? trimmed;
32
- }
33
-
34
- function parseDebugConfig(): DebugConfig {
35
- const flags = new Set<string>();
36
- const sources = [process.env.OTTO_DEBUG, process.env.DEBUG_OTTO];
37
- let sawValue = false;
38
- for (const raw of sources) {
39
- if (typeof raw !== 'string') continue;
40
- const trimmed = raw.trim();
41
- if (!trimmed) continue;
42
- sawValue = true;
43
- const tokens = trimmed.split(/[\s,]+/);
44
- let matched = false;
45
- for (const token of tokens) {
46
- const normalized = normalizeToken(token);
47
- if (!normalized) continue;
48
- matched = true;
49
- flags.add(normalized);
50
- }
51
- if (!matched && isTruthy(trimmed)) flags.add('all');
52
- }
53
- if (isTruthy(process.env.OTTO_DEBUG_TIMING)) flags.add('timing');
54
- if (!flags.size && sawValue) flags.add('all');
55
- return { flags };
56
- }
57
-
58
- function getDebugConfig(): DebugConfig {
59
- if (!cachedConfig) cachedConfig = parseDebugConfig();
60
- return cachedConfig;
61
- }
62
-
63
1
  export function isDebugEnabled(flag?: string): boolean {
64
- const config = getDebugConfig();
65
- if (config.flags.has('all')) return true;
66
- if (flag) return config.flags.has(flag);
67
- return config.flags.has('log');
68
- }
69
-
70
- export function debugLog(...args: unknown[]) {
71
- if (!isDebugEnabled('log')) return;
72
- try {
73
- console.log('[debug]', ...args);
74
- } catch {}
2
+ void flag;
3
+ return false;
75
4
  }
76
5
 
77
6
  function nowMs(): number {
@@ -84,21 +13,10 @@ function nowMs(): number {
84
13
  type Timer = { end(meta?: Record<string, unknown>): void };
85
14
 
86
15
  export function time(label: string): Timer {
87
- if (!isDebugEnabled('timing')) {
88
- return { end() {} };
89
- }
90
- const start = nowMs();
91
- let finished = false;
16
+ void label;
92
17
  return {
93
18
  end(meta?: Record<string, unknown>) {
94
- if (finished) return;
95
- finished = true;
96
- const duration = nowMs() - start;
97
- try {
98
- const line = `[timing] ${label} ${duration.toFixed(1)}ms`;
99
- if (meta && Object.keys(meta).length) console.log(line, meta);
100
- else console.log(line);
101
- } catch {}
19
+ void meta;
102
20
  },
103
21
  };
104
22
  }
@@ -1,4 +1,3 @@
1
- import { debugLog } from './debug.ts';
2
1
  import {
3
2
  getModelFamily,
4
3
  getModelInfo,
@@ -91,9 +90,6 @@ export async function providerBasePrompt(
91
90
  const modelText = await readIfExists(modelPath);
92
91
  if (!modelText) continue;
93
92
  const promptType = `model:${sanitized}`;
94
- debugLog(
95
- `[provider] prompt: ${promptType} (${modelText.length} chars) from ${modelPath}`,
96
- );
97
93
  return { prompt: modelText, resolvedType: promptType };
98
94
  }
99
95
  }
@@ -101,9 +97,6 @@ export async function providerBasePrompt(
101
97
  for (const providerPath of providerPaths) {
102
98
  const providerText = await readIfExists(providerPath);
103
99
  if (!providerText) continue;
104
- debugLog(
105
- `[provider] prompt: custom:${id} (${providerText.length} chars) from ${providerPath}`,
106
- );
107
100
  return { prompt: providerText, resolvedType: `custom:${id}` };
108
101
  }
109
102
 
@@ -112,49 +105,37 @@ export async function providerBasePrompt(
112
105
  if (info?.ownedBy) {
113
106
  const family = getModelFamily(id, modelId);
114
107
  const result = promptForFamily(family);
115
- debugLog(
116
- `[provider] prompt: ownedBy:${info.ownedBy} (via ${id}/${modelId}, ${result.length} chars)`,
117
- );
118
108
  return { prompt: result, resolvedType: family ?? info.ownedBy };
119
109
  }
120
110
 
121
111
  const family = getModelFamily(id, modelId);
122
112
  if (family) {
123
113
  const result = promptForFamily(family);
124
- debugLog(
125
- `[provider] prompt: family:${family} (via ${id}/${modelId}, ${result.length} chars)`,
126
- );
127
114
  return { prompt: result, resolvedType: family };
128
115
  }
129
116
  }
130
117
 
131
118
  if (id === 'openai') {
132
119
  const result = PROVIDER_OPENAI.trim();
133
- debugLog(`[provider] prompt: openai (${result.length} chars)`);
134
120
  return { prompt: result, resolvedType: 'openai' };
135
121
  }
136
122
  if (id === 'anthropic') {
137
123
  const result = PROVIDER_ANTHROPIC.trim();
138
- debugLog(`[provider] prompt: anthropic (${result.length} chars)`);
139
124
  return { prompt: result, resolvedType: 'anthropic' };
140
125
  }
141
126
  if (id === 'google') {
142
127
  const result = PROVIDER_GOOGLE.trim();
143
- debugLog(`[provider] prompt: google (${result.length} chars)`);
144
128
  return { prompt: result, resolvedType: 'google' };
145
129
  }
146
130
  if (id === 'moonshot') {
147
131
  const result = PROVIDER_MOONSHOT.trim();
148
- debugLog(`[provider] prompt: moonshot (${result.length} chars)`);
149
132
  return { prompt: result, resolvedType: 'moonshot' };
150
133
  }
151
134
  if (id === 'zai' || id === 'zai-coding') {
152
135
  const result = PROVIDER_GLM.trim();
153
- debugLog(`[provider] prompt: glm (${result.length} chars)`);
154
136
  return { prompt: result, resolvedType: 'glm' };
155
137
  }
156
138
 
157
139
  const result = PROVIDER_DEFAULT.trim();
158
- debugLog(`[provider] prompt: default (${result.length} chars)`);
159
140
  return { prompt: result, resolvedType: 'default' };
160
141
  }
@@ -2,6 +2,10 @@ import { createOpenAI } from '@ai-sdk/openai';
2
2
  import type { OAuth } from '../../types/src/index.ts';
3
3
  import { refreshOpenAIToken } from '../../auth/src/openai-oauth.ts';
4
4
  import { setAuth, getAuth } from '../../auth/src/index.ts';
5
+ import {
6
+ debug as loggerDebug,
7
+ warn as loggerWarn,
8
+ } from '../../core/src/utils/logger.ts';
5
9
  import os from 'node:os';
6
10
 
7
11
  const CODEX_BASE_URL = 'https://chatgpt.com/backend-api/codex';
@@ -11,12 +15,48 @@ const TOKEN_EXPIRY_BUFFER_MS = 5 * 60 * 1000;
11
15
  const TOKEN_REFRESH_MAX_RETRIES = 2;
12
16
  const TOKEN_REFRESH_RETRY_DELAY_MS = 1000;
13
17
 
18
+ type OpenAIOAuthSessionState = {
19
+ responseId?: string;
20
+ model?: string;
21
+ status?: string;
22
+ incompleteReason?: string;
23
+ };
24
+
25
+ const openAIOAuthSessionState = new Map<string, OpenAIOAuthSessionState>();
26
+
14
27
  export type OpenAIOAuthConfig = {
15
28
  oauth: OAuth;
16
29
  projectRoot?: string;
17
30
  sessionId?: string;
18
31
  };
19
32
 
33
+ function shouldDebugOpenAIOAuth() {
34
+ return false;
35
+ }
36
+
37
+ function logOpenAIOAuth(message: string) {
38
+ if (shouldDebugOpenAIOAuth()) {
39
+ loggerDebug(`[openai-oauth] ${message}`);
40
+ }
41
+ }
42
+
43
+ function shouldUsePreviousResponseId() {
44
+ return process.env.OTTO_OPENAI_OAUTH_PREVIOUS_RESPONSE_ID === '1';
45
+ }
46
+
47
+ export function clearOpenAIOAuthSessionState(sessionId?: string) {
48
+ if (sessionId) {
49
+ openAIOAuthSessionState.delete(sessionId);
50
+ return;
51
+ }
52
+ openAIOAuthSessionState.clear();
53
+ }
54
+
55
+ export function getOpenAIOAuthSessionState(sessionId: string) {
56
+ const state = openAIOAuthSessionState.get(sessionId);
57
+ return state ? { ...state } : undefined;
58
+ }
59
+
20
60
  function sleep(ms: number) {
21
61
  return new Promise((resolve) => setTimeout(resolve, ms));
22
62
  }
@@ -65,7 +105,7 @@ async function ensureValidToken(
65
105
  accountId: updated.accountId,
66
106
  };
67
107
  } catch {
68
- console.error(
108
+ loggerWarn(
69
109
  '[openai-oauth] Token refresh failed after retries, falling back to current token',
70
110
  );
71
111
  return { oauth, access: oauth.access, accountId: oauth.accountId };
@@ -83,6 +123,187 @@ function rewriteUrl(url: string): string {
83
123
  return url;
84
124
  }
85
125
 
126
+ function readSessionState(sessionId?: string) {
127
+ if (!sessionId) return undefined;
128
+ return openAIOAuthSessionState.get(sessionId);
129
+ }
130
+
131
+ function writeSessionState(sessionId: string, next: OpenAIOAuthSessionState) {
132
+ openAIOAuthSessionState.set(sessionId, next);
133
+ }
134
+
135
+ function rewriteRequestBody(
136
+ body: string,
137
+ sessionId?: string,
138
+ ): { body: string; previousResponseId?: string; model?: string } {
139
+ try {
140
+ const parsed = JSON.parse(body) as Record<string, unknown>;
141
+ const model = typeof parsed.model === 'string' ? parsed.model : undefined;
142
+ if (!sessionId) {
143
+ return { body, model };
144
+ }
145
+
146
+ const prior = readSessionState(sessionId);
147
+ if (
148
+ prior?.responseId &&
149
+ !parsed.previous_response_id &&
150
+ (!prior.model || !model || prior.model === model)
151
+ ) {
152
+ if (!shouldUsePreviousResponseId()) {
153
+ logOpenAIOAuth(
154
+ `not injecting previous_response_id=${prior.responseId} for session=${sessionId} model=${model ?? 'unknown'} because Codex HTTP backend rejects it; enable OTTO_OPENAI_OAUTH_PREVIOUS_RESPONSE_ID=1 only for validation`,
155
+ );
156
+ return { body, model };
157
+ }
158
+ parsed.previous_response_id = prior.responseId;
159
+ logOpenAIOAuth(
160
+ `injecting previous_response_id=${prior.responseId} for session=${sessionId} model=${model ?? 'unknown'}`,
161
+ );
162
+ return {
163
+ body: JSON.stringify(parsed),
164
+ previousResponseId: prior.responseId,
165
+ model,
166
+ };
167
+ }
168
+
169
+ return { body, model };
170
+ } catch {
171
+ return { body };
172
+ }
173
+ }
174
+
175
+ function previewText(value: unknown, maxLength = 240): string | undefined {
176
+ if (typeof value !== 'string') return undefined;
177
+ const normalized = value.replace(/\s+/g, ' ').trim();
178
+ if (!normalized) return undefined;
179
+ return normalized.length > maxLength
180
+ ? `${normalized.slice(0, maxLength)}…`
181
+ : normalized;
182
+ }
183
+
184
+ function summarizeRequestBody(body: string): string {
185
+ try {
186
+ const parsed = JSON.parse(body) as Record<string, unknown>;
187
+ const input = Array.isArray(parsed.input) ? parsed.input : [];
188
+ const systemMessages = input.filter((item) => {
189
+ if (!item || typeof item !== 'object') return false;
190
+ const role = (item as Record<string, unknown>).role;
191
+ return role === 'system';
192
+ });
193
+ const systemPreview = previewText(
194
+ (systemMessages[0] as Record<string, unknown> | undefined)?.content,
195
+ );
196
+ const instructionsPreview = previewText(parsed.instructions);
197
+ return [
198
+ `model=${typeof parsed.model === 'string' ? parsed.model : 'unknown'}`,
199
+ `instructionsPresent=${typeof parsed.instructions === 'string'}`,
200
+ `instructionsPreview=${instructionsPreview ?? 'none'}`,
201
+ `inputCount=${input.length}`,
202
+ `systemMessageCount=${systemMessages.length}`,
203
+ `firstSystemPreview=${systemPreview ?? 'none'}`,
204
+ `previousResponseId=${typeof parsed.previous_response_id === 'string' ? parsed.previous_response_id : 'none'}`,
205
+ ].join(' ');
206
+ } catch {
207
+ return 'unparseable-body';
208
+ }
209
+ }
210
+
211
+ function trackResponseEvent(data: string, sessionId?: string) {
212
+ if (!sessionId) return;
213
+
214
+ try {
215
+ const parsed = JSON.parse(data) as Record<string, unknown>;
216
+ const type = typeof parsed.type === 'string' ? parsed.type : undefined;
217
+ const response =
218
+ parsed.response && typeof parsed.response === 'object'
219
+ ? (parsed.response as Record<string, unknown>)
220
+ : undefined;
221
+ const responseId =
222
+ typeof response?.id === 'string'
223
+ ? response.id
224
+ : typeof parsed.response_id === 'string'
225
+ ? parsed.response_id
226
+ : undefined;
227
+ const responseModel =
228
+ typeof response?.model === 'string' ? response.model : undefined;
229
+ const responseStatus =
230
+ typeof response?.status === 'string' ? response.status : undefined;
231
+ const incompleteReason =
232
+ response?.incomplete_details &&
233
+ typeof response.incomplete_details === 'object' &&
234
+ typeof (response.incomplete_details as Record<string, unknown>).reason ===
235
+ 'string'
236
+ ? ((response.incomplete_details as Record<string, unknown>)
237
+ .reason as string)
238
+ : undefined;
239
+
240
+ if (responseId) {
241
+ const prior = readSessionState(sessionId);
242
+ writeSessionState(sessionId, {
243
+ responseId,
244
+ model: responseModel ?? prior?.model,
245
+ status: responseStatus ?? type,
246
+ incompleteReason,
247
+ });
248
+ logOpenAIOAuth(
249
+ `tracked response event type=${type ?? 'unknown'} responseId=${responseId} session=${sessionId} status=${responseStatus ?? 'unknown'} incompleteReason=${incompleteReason ?? 'none'}`,
250
+ );
251
+ }
252
+ } catch {
253
+ // ignore non-JSON data chunks
254
+ }
255
+ }
256
+
257
+ function trackResponsesStream(
258
+ response: Response,
259
+ sessionId?: string,
260
+ ): Response {
261
+ if (!response.body || !sessionId) {
262
+ return response;
263
+ }
264
+
265
+ const decoder = new TextDecoder();
266
+ const encoder = new TextEncoder();
267
+ let buffer = '';
268
+
269
+ const transform = new TransformStream<Uint8Array, Uint8Array>({
270
+ transform(chunk, controller) {
271
+ buffer += decoder.decode(chunk, { stream: true }).replace(/\r\n/g, '\n');
272
+ let boundary = buffer.indexOf('\n\n');
273
+ while (boundary !== -1) {
274
+ const rawEvent = buffer.slice(0, boundary);
275
+ buffer = buffer.slice(boundary + 2);
276
+
277
+ const dataLines: string[] = [];
278
+ for (const line of rawEvent.split('\n')) {
279
+ if (line.startsWith('data:')) {
280
+ dataLines.push(line.slice('data:'.length).trimStart());
281
+ }
282
+ }
283
+ const data = dataLines.join('\n');
284
+ if (data && data !== '[DONE]') {
285
+ trackResponseEvent(data, sessionId);
286
+ }
287
+
288
+ controller.enqueue(encoder.encode(`${rawEvent}\n\n`));
289
+ boundary = buffer.indexOf('\n\n');
290
+ }
291
+ },
292
+ flush(controller) {
293
+ buffer += decoder.decode().replace(/\r\n/g, '\n');
294
+ if (buffer.length > 0) {
295
+ controller.enqueue(encoder.encode(buffer));
296
+ }
297
+ },
298
+ });
299
+
300
+ return new Response(response.body.pipeThrough(transform), {
301
+ status: response.status,
302
+ statusText: response.statusText,
303
+ headers: response.headers,
304
+ });
305
+ }
306
+
86
307
  function buildHeaders(
87
308
  init: RequestInit | undefined,
88
309
  accessToken: string,
@@ -124,20 +345,44 @@ export function createOpenAIOAuthFetch(config: OpenAIOAuthConfig) {
124
345
  ? input.href
125
346
  : input.url;
126
347
  const targetUrl = rewriteUrl(originalUrl);
348
+ const isResponsesRequest = targetUrl === CODEX_RESPONSES_URL;
349
+ let requestInit = init;
350
+ let requestModel: string | undefined;
351
+ if (isResponsesRequest && typeof init?.body === 'string') {
352
+ const rewritten = rewriteRequestBody(init.body, config.sessionId);
353
+ requestModel = rewritten.model;
354
+ requestInit =
355
+ rewritten.body !== init.body ? { ...init, body: rewritten.body } : init;
356
+ logOpenAIOAuth(
357
+ `request payload summary: ${summarizeRequestBody(requestInit?.body && typeof requestInit.body === 'string' ? requestInit.body : init.body)}`,
358
+ );
359
+ if (config.sessionId && requestModel) {
360
+ const prior = readSessionState(config.sessionId);
361
+ writeSessionState(config.sessionId, {
362
+ responseId: prior?.responseId,
363
+ model: requestModel,
364
+ status: prior?.status,
365
+ incompleteReason: prior?.incompleteReason,
366
+ });
367
+ }
368
+ }
127
369
 
128
370
  const headers = buildHeaders(
129
- init,
371
+ requestInit,
130
372
  validated.access,
131
373
  validated.accountId,
132
374
  config.sessionId,
133
375
  );
134
376
 
135
377
  const response = await fetch(targetUrl, {
136
- ...init,
378
+ ...requestInit,
137
379
  headers,
138
380
  // @ts-expect-error Bun-specific fetch option
139
381
  timeout: false,
140
382
  });
383
+ const trackedResponse = isResponsesRequest
384
+ ? trackResponsesStream(response, config.sessionId)
385
+ : response;
141
386
 
142
387
  if (response.status === 401) {
143
388
  try {
@@ -155,18 +400,21 @@ export function createOpenAIOAuthFetch(config: OpenAIOAuthConfig) {
155
400
  }
156
401
 
157
402
  const retryHeaders = buildHeaders(
158
- init,
403
+ requestInit,
159
404
  currentOAuth.access,
160
405
  currentOAuth.accountId,
161
406
  config.sessionId,
162
407
  );
163
408
 
164
- return fetch(targetUrl, {
165
- ...init,
409
+ const retryResponse = await fetch(targetUrl, {
410
+ ...requestInit,
166
411
  headers: retryHeaders,
167
412
  // @ts-expect-error Bun-specific fetch option
168
413
  timeout: false,
169
414
  });
415
+ return isResponsesRequest
416
+ ? trackResponsesStream(retryResponse, config.sessionId)
417
+ : retryResponse;
170
418
  } catch {
171
419
  console.error(
172
420
  '[openai-oauth] 401 retry failed, returning original 401 response',
@@ -175,7 +423,7 @@ export function createOpenAIOAuthFetch(config: OpenAIOAuthConfig) {
175
423
  }
176
424
  }
177
425
 
178
- return response;
426
+ return trackedResponse;
179
427
  };
180
428
 
181
429
  return customFetch as typeof fetch;
@@ -14,6 +14,8 @@ const skillCache = new Map<string, SkillDefinition>();
14
14
 
15
15
  const SKILL_DIRS = [
16
16
  '.otto/skills',
17
+ '.agents/skills',
18
+ '.agenst/skills',
17
19
  '.claude/skills',
18
20
  '.opencode/skills',
19
21
  '.codex/skills',
@@ -53,6 +55,8 @@ export async function discoverSkills(
53
55
 
54
56
  const globalDirs = [
55
57
  join(getGlobalConfigDir(), 'skills'),
58
+ join(home, '.agents/skills'),
59
+ join(home, '.agenst/skills'),
56
60
  join(home, '.claude/skills'),
57
61
  join(home, '.config/opencode/skills'),
58
62
  join(home, '.codex/skills'),
@@ -194,20 +198,10 @@ async function loadSkillsFromDir(
194
198
  const skill = parseSkillFile(content, filePath, scope);
195
199
 
196
200
  const dirName = dirname(filePath).split(/[\\/]/).pop();
197
- if (dirName !== skill.metadata.name) {
198
- if (process.env.OTTO_DEBUG === '1') {
199
- console.warn(
200
- `Skill name '${skill.metadata.name}' doesn't match directory '${dirName}' in ${filePath}`,
201
- );
202
- }
203
- }
201
+ void dirName;
204
202
 
205
203
  skills.set(skill.metadata.name, skill);
206
- } catch (err) {
207
- if (process.env.OTTO_DEBUG === '1') {
208
- console.error(`Failed to load skill from ${filePath}:`, err);
209
- }
210
- }
204
+ } catch {}
211
205
  }
212
206
  }
213
207