@ottocode/sdk 0.1.192 → 0.1.194

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.192",
3
+ "version": "0.1.194",
4
4
  "description": "AI agent SDK for building intelligent assistants - tree-shakable and comprehensive",
5
5
  "author": "nitishxyz",
6
6
  "license": "MIT",
@@ -79,6 +79,7 @@ export async function authorizeOpenAI(): Promise<OpenAIOAuthResult> {
79
79
  id_token_add_organizations: 'true',
80
80
  codex_cli_simplified_flow: 'true',
81
81
  state: state,
82
+ originator: 'codex_cli_rs',
82
83
  });
83
84
 
84
85
  const authUrl = `${OPENAI_ISSUER}/oauth/authorize?${params.toString()}`;
@@ -253,14 +254,13 @@ export async function refreshOpenAIToken(refreshToken: string) {
253
254
  const response = await fetch(`${OPENAI_ISSUER}/oauth/token`, {
254
255
  method: 'POST',
255
256
  headers: {
256
- 'Content-Type': 'application/json',
257
+ 'Content-Type': 'application/x-www-form-urlencoded',
257
258
  },
258
- body: JSON.stringify({
259
- client_id: OPENAI_CLIENT_ID,
259
+ body: new URLSearchParams({
260
260
  grant_type: 'refresh_token',
261
261
  refresh_token: refreshToken,
262
- scope: 'openid profile email',
263
- }),
262
+ client_id: OPENAI_CLIENT_ID,
263
+ }).toString(),
264
264
  });
265
265
 
266
266
  if (!response.ok) {
@@ -336,6 +336,7 @@ export function authorizeOpenAIWeb(redirectUri: string): {
336
336
  id_token_add_organizations: 'true',
337
337
  codex_cli_simplified_flow: 'true',
338
338
  state: state,
339
+ originator: 'codex_cli_rs',
339
340
  });
340
341
 
341
342
  return {
@@ -30,6 +30,16 @@ function resolveSafePath(projectRoot: string, p: string) {
30
30
  return abs;
31
31
  }
32
32
 
33
+ function killProcessTree(pid: number) {
34
+ try {
35
+ process.kill(-pid, 'SIGTERM');
36
+ } catch {
37
+ try {
38
+ process.kill(pid, 'SIGTERM');
39
+ } catch {}
40
+ }
41
+ }
42
+
33
43
  export function buildBashTool(projectRoot: string): {
34
44
  name: string;
35
45
  tool: Tool;
@@ -55,23 +65,34 @@ export function buildBashTool(projectRoot: string): {
55
65
  .describe('Timeout in milliseconds (default: 300000 = 5 minutes)'),
56
66
  })
57
67
  .strict(),
58
- async execute({
59
- cmd,
60
- cwd,
61
- allowNonZeroExit,
62
- timeout = 300000,
63
- }: {
64
- cmd: string;
65
- cwd?: string;
66
- allowNonZeroExit?: boolean;
67
- timeout?: number;
68
- }): Promise<
68
+ async execute(
69
+ {
70
+ cmd,
71
+ cwd,
72
+ allowNonZeroExit,
73
+ timeout = 300000,
74
+ }: {
75
+ cmd: string;
76
+ cwd?: string;
77
+ allowNonZeroExit?: boolean;
78
+ timeout?: number;
79
+ },
80
+ options?: { abortSignal?: AbortSignal },
81
+ ): Promise<
69
82
  ToolResponse<{
70
83
  exitCode: number;
71
84
  stdout: string;
72
85
  stderr: string;
73
86
  }>
74
87
  > {
88
+ const abortSignal = options?.abortSignal;
89
+
90
+ if (abortSignal?.aborted) {
91
+ return createToolError('Command aborted before execution', 'abort', {
92
+ cmd,
93
+ });
94
+ }
95
+
75
96
  const absCwd = resolveSafePath(projectRoot, cwd || '.');
76
97
 
77
98
  return new Promise((resolve) => {
@@ -80,17 +101,48 @@ export function buildBashTool(projectRoot: string): {
80
101
  shell: true,
81
102
  stdio: ['ignore', 'pipe', 'pipe'],
82
103
  env: { ...process.env, PATH: getAugmentedPath() },
104
+ detached: true,
83
105
  });
84
106
 
85
107
  let stdout = '';
86
108
  let stderr = '';
87
109
  let didTimeout = false;
110
+ let didAbort = false;
111
+ let settled = false;
88
112
  let timeoutId: ReturnType<typeof setTimeout> | null = null;
89
113
 
114
+ const settle = (
115
+ result: ToolResponse<{
116
+ exitCode: number;
117
+ stdout: string;
118
+ stderr: string;
119
+ }>,
120
+ ) => {
121
+ if (settled) return;
122
+ settled = true;
123
+ if (timeoutId) clearTimeout(timeoutId);
124
+ if (abortSignal) {
125
+ abortSignal.removeEventListener('abort', onAbort);
126
+ }
127
+ resolve(result);
128
+ };
129
+
130
+ const onAbort = () => {
131
+ if (settled) return;
132
+ didAbort = true;
133
+ if (proc.pid) killProcessTree(proc.pid);
134
+ else proc.kill('SIGTERM');
135
+ };
136
+
137
+ if (abortSignal) {
138
+ abortSignal.addEventListener('abort', onAbort, { once: true });
139
+ }
140
+
90
141
  if (timeout > 0) {
91
142
  timeoutId = setTimeout(() => {
92
143
  didTimeout = true;
93
- proc.kill();
144
+ if (proc.pid) killProcessTree(proc.pid);
145
+ else proc.kill();
94
146
  }, timeout);
95
147
  }
96
148
 
@@ -103,10 +155,16 @@ export function buildBashTool(projectRoot: string): {
103
155
  });
104
156
 
105
157
  proc.on('close', (exitCode) => {
106
- if (timeoutId) clearTimeout(timeoutId);
107
-
108
- if (didTimeout) {
109
- resolve(
158
+ if (didAbort) {
159
+ settle(
160
+ createToolError(`Command aborted by user: ${cmd}`, 'abort', {
161
+ cmd,
162
+ stdout,
163
+ stderr,
164
+ }),
165
+ );
166
+ } else if (didTimeout) {
167
+ settle(
110
168
  createToolError(
111
169
  `Command timed out after ${timeout}ms: ${cmd}`,
112
170
  'timeout',
@@ -120,7 +178,7 @@ export function buildBashTool(projectRoot: string): {
120
178
  } else if (exitCode !== 0 && !allowNonZeroExit) {
121
179
  const errorDetail = stderr.trim() || stdout.trim() || '';
122
180
  const errorMsg = `Command failed with exit code ${exitCode}${errorDetail ? `\n\n${errorDetail}` : ''}`;
123
- resolve(
181
+ settle(
124
182
  createToolError(errorMsg, 'execution', {
125
183
  exitCode,
126
184
  stdout,
@@ -131,7 +189,7 @@ export function buildBashTool(projectRoot: string): {
131
189
  }),
132
190
  );
133
191
  } else {
134
- resolve({
192
+ settle({
135
193
  ok: true,
136
194
  exitCode: exitCode ?? 0,
137
195
  stdout,
@@ -141,8 +199,7 @@ export function buildBashTool(projectRoot: string): {
141
199
  });
142
200
 
143
201
  proc.on('error', (err) => {
144
- if (timeoutId) clearTimeout(timeoutId);
145
- resolve(
202
+ settle(
146
203
  createToolError(
147
204
  `Command execution failed: ${err.message}`,
148
205
  'execution',
@@ -4,7 +4,8 @@ export type ToolErrorType =
4
4
  | 'permission'
5
5
  | 'execution'
6
6
  | 'timeout'
7
- | 'unsupported';
7
+ | 'unsupported'
8
+ | 'abort';
8
9
 
9
10
  export type ToolErrorResponse = {
10
11
  ok: false;
@@ -422,3 +422,14 @@ To create a new plan, call `update_todos` with a short list of 1‑sentence step
422
422
  When steps have been completed, use `update_todos` to mark each finished step as `completed` and the next step you are working on as `in_progress`. There should always be exactly one `in_progress` step until everything is done. You can mark multiple items as complete in a single `update_todos` call.
423
423
 
424
424
  If all steps are complete, ensure you call `update_todos` to mark all steps as `completed`.
425
+
426
+ ## `finish` — MANDATORY
427
+
428
+ A tool named `finish` is available to you. You MUST call it as your very last action when you are completely done with the user's request. No exceptions.
429
+
430
+ The workflow is:
431
+ 1. Do all work (tool calls, edits, searches, etc.)
432
+ 2. Stream your final summary/response text to the user
433
+ 3. Call `finish` tool with `{}` — this is REQUIRED
434
+
435
+ **If you do not call `finish`, the system cannot detect that you are done and will force a continuation turn.** Always call `finish` as your absolute last action.
@@ -11,6 +11,7 @@ const SETU_ID: ProviderId = 'setu';
11
11
 
12
12
  const isAllowedOpenAIModel = (id: string): boolean => {
13
13
  if (id === 'codex-mini-latest') return true;
14
+ if (id === 'gpt-5.3' || id.startsWith('gpt-5.3-')) return false;
14
15
  if (id.startsWith('gpt-5')) return true;
15
16
  if (id.includes('codex')) return true;
16
17
  return false;
@@ -1496,7 +1496,7 @@ export const catalog: Partial<Record<ProviderId, ProviderCatalogEntry>> = {
1496
1496
  cacheWrite: 6.25,
1497
1497
  },
1498
1498
  limit: {
1499
- context: 1000000,
1499
+ context: 200000,
1500
1500
  output: 128000,
1501
1501
  },
1502
1502
  },
@@ -2446,6 +2446,32 @@ export const catalog: Partial<Record<ProviderId, ProviderCatalogEntry>> = {
2446
2446
  output: 32000,
2447
2447
  },
2448
2448
  },
2449
+ {
2450
+ id: 'anthropic/claude-opus-4.6',
2451
+ label: 'Claude Opus 4.6',
2452
+ modalities: {
2453
+ input: ['text', 'image', 'pdf'],
2454
+ output: ['text'],
2455
+ },
2456
+ toolCall: true,
2457
+ reasoningText: true,
2458
+ attachment: true,
2459
+ temperature: true,
2460
+ knowledge: '2025-05-30',
2461
+ releaseDate: '2026-02-05',
2462
+ lastUpdated: '2026-02-05',
2463
+ openWeights: false,
2464
+ cost: {
2465
+ input: 5,
2466
+ output: 25,
2467
+ cacheRead: 0.5,
2468
+ cacheWrite: 6.25,
2469
+ },
2470
+ limit: {
2471
+ context: 1000000,
2472
+ output: 128000,
2473
+ },
2474
+ },
2449
2475
  {
2450
2476
  id: 'anthropic/claude-sonnet-4',
2451
2477
  label: 'Claude Sonnet 4',
@@ -5203,6 +5229,29 @@ export const catalog: Partial<Record<ProviderId, ProviderCatalogEntry>> = {
5203
5229
  output: 100000,
5204
5230
  },
5205
5231
  },
5232
+ {
5233
+ id: 'openrouter/pony-alpha',
5234
+ label: 'Pony Alpha',
5235
+ modalities: {
5236
+ input: ['text'],
5237
+ output: ['text'],
5238
+ },
5239
+ toolCall: true,
5240
+ reasoningText: true,
5241
+ attachment: false,
5242
+ temperature: true,
5243
+ releaseDate: '2026-02-06',
5244
+ lastUpdated: '2026-02-06',
5245
+ openWeights: false,
5246
+ cost: {
5247
+ input: 0,
5248
+ output: 0,
5249
+ },
5250
+ limit: {
5251
+ context: 200000,
5252
+ output: 131000,
5253
+ },
5254
+ },
5206
5255
  {
5207
5256
  id: 'openrouter/sherlock-dash-alpha',
5208
5257
  label: 'Sherlock Dash Alpha',
@@ -6273,6 +6322,31 @@ export const catalog: Partial<Record<ProviderId, ProviderCatalogEntry>> = {
6273
6322
  output: 10000,
6274
6323
  },
6275
6324
  },
6325
+ {
6326
+ id: 'xiaomi/mimo-v2-flash',
6327
+ label: 'MiMo-V2-Flash',
6328
+ modalities: {
6329
+ input: ['text'],
6330
+ output: ['text'],
6331
+ },
6332
+ toolCall: true,
6333
+ reasoningText: true,
6334
+ attachment: false,
6335
+ temperature: true,
6336
+ knowledge: '2024-12',
6337
+ releaseDate: '2025-12-14',
6338
+ lastUpdated: '2025-12-14',
6339
+ openWeights: true,
6340
+ cost: {
6341
+ input: 0.1,
6342
+ output: 0.3,
6343
+ cacheRead: 0.01,
6344
+ },
6345
+ limit: {
6346
+ context: 262144,
6347
+ output: 65536,
6348
+ },
6349
+ },
6276
6350
  {
6277
6351
  id: 'z-ai/glm-4.5',
6278
6352
  label: 'GLM 4.5',
@@ -2,8 +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 os from 'node:os';
5
6
 
6
7
  const CODEX_BASE_URL = 'https://chatgpt.com/backend-api/codex';
8
+ const CODEX_RESPONSES_URL = 'https://chatgpt.com/backend-api/codex/responses';
7
9
 
8
10
  export type OpenAIOAuthConfig = {
9
11
  oauth: OAuth;
@@ -56,7 +58,7 @@ function rewriteUrl(url: string): string {
56
58
  parsed.pathname.includes('/v1/responses') ||
57
59
  parsed.pathname.includes('/chat/completions')
58
60
  ) {
59
- return `${CODEX_BASE_URL}${parsed.pathname.replace(/^.*\/v1/, '/v1')}`;
61
+ return CODEX_RESPONSES_URL;
60
62
  }
61
63
  return url;
62
64
  }
@@ -70,7 +72,14 @@ function buildHeaders(
70
72
  headers.delete('Authorization');
71
73
  headers.delete('authorization');
72
74
  headers.set('authorization', `Bearer ${accessToken}`);
73
- headers.set('originator', 'otto');
75
+ headers.set('originator', 'codex_cli_rs');
76
+ headers.set('version', '0.98.0');
77
+ headers.set('x-oai-web-search-eligible', 'true');
78
+ headers.set('accept', 'text/event-stream');
79
+ headers.set(
80
+ 'User-Agent',
81
+ `codex_cli_rs/0.98.0 (${os.platform()} ${os.release()}; ${os.arch()})`,
82
+ );
74
83
  if (accountId) {
75
84
  headers.set('ChatGPT-Account-Id', accountId);
76
85
  }
@@ -96,6 +105,7 @@ export function createOpenAIOAuthFetch(config: OpenAIOAuthConfig) {
96
105
  const targetUrl = rewriteUrl(originalUrl);
97
106
 
98
107
  const headers = buildHeaders(init, validated.access, validated.accountId);
108
+ headers.set('accept-encoding', 'identity');
99
109
 
100
110
  const response = await fetch(targetUrl, {
101
111
  ...init,
@@ -103,6 +113,7 @@ export function createOpenAIOAuthFetch(config: OpenAIOAuthConfig) {
103
113
  // biome-ignore lint/suspicious/noTsIgnore: Bun-specific fetch option
104
114
  // @ts-ignore
105
115
  timeout: false,
116
+ decompress: false,
106
117
  });
107
118
 
108
119
  if (response.status === 401) {
@@ -125,6 +136,7 @@ export function createOpenAIOAuthFetch(config: OpenAIOAuthConfig) {
125
136
  currentOAuth.access,
126
137
  currentOAuth.accountId,
127
138
  );
139
+ retryHeaders.set('accept-encoding', 'identity');
128
140
 
129
141
  return fetch(targetUrl, {
130
142
  ...init,
@@ -132,6 +144,7 @@ export function createOpenAIOAuthFetch(config: OpenAIOAuthConfig) {
132
144
  // biome-ignore lint/suspicious/noTsIgnore: Bun-specific fetch option
133
145
  // @ts-ignore
134
146
  timeout: false,
147
+ decompress: false,
135
148
  });
136
149
  } catch {
137
150
  console.error(
@@ -30,7 +30,7 @@ const PREFERRED_FAST_MODELS: Partial<Record<ProviderId, string[]>> = {
30
30
  google: ['gemini-2.0-flash-lite'],
31
31
  openrouter: ['anthropic/claude-3.5-haiku'],
32
32
  opencode: ['claude-3-5-haiku'],
33
- setu: ['kimi-k2'],
33
+ setu: ['kimi-k2-turbo-preview'],
34
34
  zai: ['glm-4.5-flash'],
35
35
  copilot: ['gpt-4.1-mini'],
36
36
  };