@ottocode/sdk 0.1.264 → 0.1.266

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 (36) hide show
  1. package/package.json +3 -2
  2. package/src/config/src/index.ts +1 -0
  3. package/src/core/src/providers/resolver.ts +33 -64
  4. package/src/core/src/tools/bin-manager/cache.ts +13 -0
  5. package/src/core/src/tools/bin-manager/filesystem.ts +32 -0
  6. package/src/core/src/tools/bin-manager/paths.ts +36 -0
  7. package/src/core/src/tools/bin-manager/vendor.ts +80 -0
  8. package/src/core/src/tools/bin-manager.ts +14 -140
  9. package/src/core/src/tools/builtin/patch/apply-hunk.ts +308 -0
  10. package/src/core/src/tools/builtin/patch/apply-report.ts +99 -0
  11. package/src/core/src/tools/builtin/patch/apply.ts +6 -663
  12. package/src/core/src/tools/builtin/patch/hunk-header.ts +17 -0
  13. package/src/core/src/tools/builtin/patch/indentation.ts +160 -0
  14. package/src/core/src/tools/builtin/patch/matching.ts +58 -0
  15. package/src/core/src/tools/builtin/patch/parse-enveloped.ts +10 -72
  16. package/src/core/src/tools/builtin/patch/parse-unified.ts +15 -105
  17. package/src/core/src/tools/builtin/patch/replace-builder.ts +64 -0
  18. package/src/core/src/tools/builtin/patch/unified-state.ts +86 -0
  19. package/src/core/src/tools/builtin/websearch-strategies.ts +197 -0
  20. package/src/core/src/tools/builtin/websearch.ts +9 -187
  21. package/src/core/src/tools/loader.ts +6 -49
  22. package/src/core/src/tools/plugin-discovery.ts +86 -0
  23. package/src/core/src/utils/logger/format.ts +50 -0
  24. package/src/core/src/utils/logger/sinks.ts +61 -0
  25. package/src/core/src/utils/logger.ts +2 -119
  26. package/src/index.ts +4 -0
  27. package/src/providers/src/catalog.ts +1126 -220
  28. package/src/providers/src/env.ts +1 -0
  29. package/src/providers/src/index.ts +6 -0
  30. package/src/providers/src/model-resolution.ts +21 -0
  31. package/src/providers/src/pricing.ts +3 -0
  32. package/src/providers/src/registry.ts +2 -0
  33. package/src/providers/src/utils.ts +3 -0
  34. package/src/providers/src/xai-client.ts +15 -0
  35. package/src/providers/src/zai-client.ts +5 -2
  36. package/src/types/src/provider.ts +1 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ottocode/sdk",
3
- "version": "0.1.264",
3
+ "version": "0.1.266",
4
4
  "description": "AI agent SDK for building intelligent assistants - tree-shakable and comprehensive",
5
5
  "author": "nitishxyz",
6
6
  "license": "MIT",
@@ -98,10 +98,11 @@
98
98
  "@ai-sdk/google": "^3.0.0",
99
99
  "@ai-sdk/openai": "^3.0.0",
100
100
  "@ai-sdk/openai-compatible": "^2.0.0",
101
+ "@ai-sdk/xai": "^3.0.0",
101
102
  "@modelcontextprotocol/sdk": "^1.12",
102
103
  "@openauthjs/openauth": "^0.4.3",
103
104
  "@openrouter/ai-sdk-provider": "^1.2.0",
104
- "@ottorouter/ai-sdk": "0.2.1",
105
+ "@ottorouter/ai-sdk": "0.2.2",
105
106
  "@solana/web3.js": "^1.98.0",
106
107
  "ai": "^6.0.170",
107
108
  "ai-sdk-ollama": "^3.8.3",
@@ -18,6 +18,7 @@ const DEFAULT_PROVIDER_SETTINGS: OttoConfig['providers'] = {
18
18
  opencode: { enabled: false },
19
19
  copilot: { enabled: false },
20
20
  ottorouter: { enabled: true },
21
+ xai: { enabled: false },
21
22
  zai: { enabled: false },
22
23
  'zai-coding': { enabled: false },
23
24
  moonshot: { enabled: false },
@@ -6,30 +6,20 @@ import { createOpenRouter } from '@openrouter/ai-sdk-provider';
6
6
  import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
7
7
  import {
8
8
  catalog,
9
+ createMinimaxModel,
10
+ createMoonshotModel,
9
11
  createOttoRouterModel,
10
12
  createOpenAIOAuthModel,
13
+ createXaiModel,
14
+ createZaiCodingModel,
15
+ createZaiModel,
11
16
  normalizeOllamaBaseURL,
17
+ resolveOpenAIResponsesModel,
18
+ shouldUseOpenAIResponsesApi,
12
19
  } from '../../../providers/src/index.ts';
13
20
  import { createCopilotModel } from '../../../providers/src/copilot-client.ts';
14
21
  import type { OAuth } from '../../../types/src/index.ts';
15
22
 
16
- function needsResponsesApi(model: string): boolean {
17
- const m = model.toLowerCase();
18
- if (m.includes('gpt-5')) return true;
19
- if (m.startsWith('o1')) return true;
20
- if (m.startsWith('o3')) return true;
21
- if (m.startsWith('o4')) return true;
22
- if (m.includes('codex-mini')) return true;
23
- return false;
24
- }
25
-
26
- function resolveOpenAIModel(
27
- instance: ReturnType<typeof createOpenAI>,
28
- model: string,
29
- ) {
30
- return needsResponsesApi(model) ? instance.responses(model) : instance(model);
31
- }
32
-
33
23
  export type ProviderName =
34
24
  | 'openai'
35
25
  | 'anthropic'
@@ -39,6 +29,7 @@ export type ProviderName =
39
29
  | 'opencode'
40
30
  | 'copilot'
41
31
  | 'ottorouter'
32
+ | 'xai'
42
33
  | 'zai'
43
34
  | 'zai-coding'
44
35
  | 'moonshot'
@@ -69,13 +60,15 @@ export async function resolveModel(
69
60
  apiKey: config.apiKey || 'oauth-token',
70
61
  fetch: config.customFetch,
71
62
  });
72
- return resolveOpenAIModel(instance, model);
63
+ return resolveOpenAIResponsesModel(instance, model);
73
64
  }
74
65
  if (config.apiKey) {
75
66
  const instance = createOpenAI({ apiKey: config.apiKey });
76
- return resolveOpenAIModel(instance, model);
67
+ return resolveOpenAIResponsesModel(instance, model);
77
68
  }
78
- return needsResponsesApi(model) ? openai.responses(model) : openai(model);
69
+ return shouldUseOpenAIResponsesApi(model)
70
+ ? openai.responses(model)
71
+ : openai(model);
79
72
  }
80
73
 
81
74
  if (provider === 'anthropic') {
@@ -201,63 +194,39 @@ export async function resolveModel(
201
194
  );
202
195
  }
203
196
 
197
+ if (provider === 'xai') {
198
+ return createXaiModel(model, {
199
+ apiKey: config.apiKey,
200
+ baseURL: config.baseURL,
201
+ });
202
+ }
203
+
204
204
  if (provider === 'zai') {
205
- const entry = catalog[provider];
206
- const apiKey =
207
- config.apiKey ||
208
- process.env.ZAI_API_KEY ||
209
- process.env.ZHIPU_API_KEY ||
210
- '';
211
- const baseURL =
212
- config.baseURL || entry?.api || 'https://api.z.ai/api/paas/v4';
213
- const headers = apiKey ? { Authorization: `Bearer ${apiKey}` } : undefined;
214
- const instance = createOpenAICompatible({
215
- name: entry?.label ?? 'Z.AI',
216
- baseURL,
217
- headers,
205
+ return createZaiModel(model, {
206
+ apiKey: config.apiKey,
207
+ baseURL: config.baseURL,
218
208
  });
219
- return instance(model);
220
209
  }
221
210
 
222
211
  if (provider === 'zai-coding') {
223
- const entry = catalog[provider];
224
- const apiKey =
225
- config.apiKey ||
226
- process.env.ZAI_CODING_API_KEY ||
227
- process.env.ZHIPU_API_KEY ||
228
- '';
229
- const baseURL =
230
- config.baseURL || entry?.api || 'https://api.z.ai/api/coding/paas/v4';
231
- const headers = apiKey ? { Authorization: `Bearer ${apiKey}` } : undefined;
232
- const instance = createOpenAICompatible({
233
- name: entry?.label ?? 'Z.AI Coding',
234
- baseURL,
235
- headers,
212
+ return createZaiCodingModel(model, {
213
+ apiKey: config.apiKey,
214
+ baseURL: config.baseURL,
236
215
  });
237
- return instance(model);
238
216
  }
239
217
 
240
218
  if (provider === 'moonshot') {
241
- const entry = catalog[provider];
242
- const apiKey = config.apiKey || process.env.MOONSHOT_API_KEY || '';
243
- const baseURL =
244
- config.baseURL || entry?.api || 'https://api.moonshot.ai/v1';
245
- const headers = apiKey ? { Authorization: `Bearer ${apiKey}` } : undefined;
246
- const instance = createOpenAICompatible({
247
- name: entry?.label ?? 'Moonshot AI',
248
- baseURL,
249
- headers,
219
+ return createMoonshotModel(model, {
220
+ apiKey: config.apiKey,
221
+ baseURL: config.baseURL,
250
222
  });
251
- return instance(model);
252
223
  }
253
224
 
254
225
  if (provider === 'minimax') {
255
- const entry = catalog[provider];
256
- const apiKey = config.apiKey || process.env.MINIMAX_API_KEY || '';
257
- const baseURL =
258
- config.baseURL || entry?.api || 'https://api.minimax.io/anthropic/v1';
259
- const instance = createAnthropic({ apiKey, baseURL });
260
- return instance(model);
226
+ return createMinimaxModel(model, {
227
+ apiKey: config.apiKey,
228
+ baseURL: config.baseURL,
229
+ });
261
230
  }
262
231
 
263
232
  throw new Error(`Unsupported provider: ${provider}`);
@@ -0,0 +1,13 @@
1
+ const resolvedPaths = new Map<string, string>();
2
+
3
+ export function getCachedBinary(name: string): string | undefined {
4
+ return resolvedPaths.get(name);
5
+ }
6
+
7
+ export function setCachedBinary(name: string, path: string): void {
8
+ resolvedPaths.set(name, path);
9
+ }
10
+
11
+ export function clearCachedBinaries(): void {
12
+ resolvedPaths.clear();
13
+ }
@@ -0,0 +1,32 @@
1
+ import { promises as fs } from 'node:fs';
2
+
3
+ export async function ensureDir(dir: string): Promise<void> {
4
+ await fs.mkdir(dir, { recursive: true });
5
+ }
6
+
7
+ export async function fileExists(p: string): Promise<boolean> {
8
+ try {
9
+ await fs.access(p);
10
+ return true;
11
+ } catch {
12
+ return false;
13
+ }
14
+ }
15
+
16
+ export async function isExecutable(p: string): Promise<boolean> {
17
+ try {
18
+ await fs.access(p, 0o1);
19
+ return true;
20
+ } catch {
21
+ return false;
22
+ }
23
+ }
24
+
25
+ export async function makeExecutable(p: string): Promise<void> {
26
+ if (process.platform === 'win32') return;
27
+ try {
28
+ await fs.chmod(p, 0o755);
29
+ } catch {}
30
+ }
31
+
32
+ export { fs };
@@ -0,0 +1,36 @@
1
+ import { join } from 'node:path';
2
+
3
+ const OTTO_BIN_DIR_NAME = 'bin';
4
+
5
+ let cachedBinDir: string | null = null;
6
+
7
+ function getConfigHome(): string {
8
+ const cfgHome = process.env.XDG_CONFIG_HOME;
9
+ if (cfgHome?.trim()) return cfgHome.replace(/\\/g, '/');
10
+ const home = process.env.HOME || process.env.USERPROFILE || '';
11
+ return join(home, '.config');
12
+ }
13
+
14
+ export function getAgiBinDir(): string {
15
+ if (cachedBinDir) return cachedBinDir;
16
+ cachedBinDir = join(getConfigHome(), 'otto', OTTO_BIN_DIR_NAME);
17
+ return cachedBinDir;
18
+ }
19
+
20
+ export function getPlatformKey(): string {
21
+ const platform = process.platform;
22
+ const arch = process.arch;
23
+ const os =
24
+ platform === 'darwin'
25
+ ? 'darwin'
26
+ : platform === 'win32'
27
+ ? 'windows'
28
+ : 'linux';
29
+ const cpu = arch === 'arm64' ? 'arm64' : 'x64';
30
+ return `${os}-${cpu}`;
31
+ }
32
+
33
+ export function getBinaryFileName(name: string): string {
34
+ if (process.platform === 'win32') return `${name}.exe`;
35
+ return name;
36
+ }
@@ -0,0 +1,80 @@
1
+ import { join } from 'node:path';
2
+ import {
3
+ ensureDir,
4
+ fileExists,
5
+ fs,
6
+ isExecutable,
7
+ makeExecutable,
8
+ } from './filesystem.ts';
9
+ import { getAgiBinDir, getBinaryFileName, getPlatformKey } from './paths.ts';
10
+
11
+ function getVendorSearchPaths(binaryName: string): string[] {
12
+ const platformKey = getPlatformKey();
13
+ const paths: string[] = [];
14
+
15
+ const tauriResource = process.env.TAURI_RESOURCE_DIR;
16
+ if (tauriResource) {
17
+ paths.push(join(tauriResource, 'vendor', 'bin', platformKey, binaryName));
18
+ paths.push(join(tauriResource, 'vendor', 'bin', binaryName));
19
+ }
20
+
21
+ try {
22
+ const exePath = process.execPath;
23
+ if (exePath) {
24
+ const exeDir = join(exePath, '..');
25
+ paths.push(join(exeDir, 'vendor', 'bin', platformKey, binaryName));
26
+ paths.push(
27
+ join(
28
+ exeDir,
29
+ '..',
30
+ 'Resources',
31
+ 'vendor',
32
+ 'bin',
33
+ platformKey,
34
+ binaryName,
35
+ ),
36
+ );
37
+ }
38
+ } catch {}
39
+
40
+ if (process.env.CARGO_MANIFEST_DIR) {
41
+ paths.push(
42
+ join(
43
+ process.env.CARGO_MANIFEST_DIR,
44
+ 'resources',
45
+ 'vendor',
46
+ 'bin',
47
+ platformKey,
48
+ binaryName,
49
+ ),
50
+ );
51
+ }
52
+
53
+ const cwd = process.cwd();
54
+ paths.push(join(cwd, 'vendor', 'bin', platformKey, binaryName));
55
+
56
+ return paths;
57
+ }
58
+
59
+ export async function extractFromVendor(name: string): Promise<string | null> {
60
+ const binaryName = getBinaryFileName(name);
61
+ const binDir = getAgiBinDir();
62
+ const targetPath = join(binDir, binaryName);
63
+
64
+ if ((await fileExists(targetPath)) && (await isExecutable(targetPath))) {
65
+ return targetPath;
66
+ }
67
+
68
+ const searchPaths = getVendorSearchPaths(binaryName);
69
+
70
+ for (const src of searchPaths) {
71
+ if (await fileExists(src)) {
72
+ await ensureDir(binDir);
73
+ await fs.copyFile(src, targetPath);
74
+ await makeExecutable(targetPath);
75
+ return targetPath;
76
+ }
77
+ }
78
+
79
+ return null;
80
+ }
@@ -1,73 +1,18 @@
1
1
  import { join } from 'node:path';
2
- import { promises as fs } from 'node:fs';
3
2
  import { spawn, execSync } from 'node:child_process';
4
3
  import { homedir } from 'node:os';
4
+ import {
5
+ clearCachedBinaries,
6
+ getCachedBinary,
7
+ setCachedBinary,
8
+ } from './bin-manager/cache.ts';
9
+ import { fileExists, isExecutable } from './bin-manager/filesystem.ts';
10
+ import { getAgiBinDir, getBinaryFileName } from './bin-manager/paths.ts';
11
+ import { extractFromVendor } from './bin-manager/vendor.ts';
5
12
 
6
- const OTTO_BIN_DIR_NAME = 'bin';
7
-
8
- let cachedBinDir: string | null = null;
9
- const resolvedPaths = new Map<string, string>();
10
13
  let cachedLoginPath: string | null = null;
11
14
 
12
- function getConfigHome(): string {
13
- const cfgHome = process.env.XDG_CONFIG_HOME;
14
- if (cfgHome?.trim()) return cfgHome.replace(/\\/g, '/');
15
- const home = process.env.HOME || process.env.USERPROFILE || '';
16
- return join(home, '.config');
17
- }
18
-
19
- export function getAgiBinDir(): string {
20
- if (cachedBinDir) return cachedBinDir;
21
- cachedBinDir = join(getConfigHome(), 'otto', OTTO_BIN_DIR_NAME);
22
- return cachedBinDir;
23
- }
24
-
25
- function getPlatformKey(): string {
26
- const platform = process.platform;
27
- const arch = process.arch;
28
- const os =
29
- platform === 'darwin'
30
- ? 'darwin'
31
- : platform === 'win32'
32
- ? 'windows'
33
- : 'linux';
34
- const cpu = arch === 'arm64' ? 'arm64' : 'x64';
35
- return `${os}-${cpu}`;
36
- }
37
-
38
- function getBinaryFileName(name: string): string {
39
- if (process.platform === 'win32') return `${name}.exe`;
40
- return name;
41
- }
42
-
43
- async function ensureDir(dir: string): Promise<void> {
44
- await fs.mkdir(dir, { recursive: true });
45
- }
46
-
47
- async function fileExists(p: string): Promise<boolean> {
48
- try {
49
- await fs.access(p);
50
- return true;
51
- } catch {
52
- return false;
53
- }
54
- }
55
-
56
- async function isExecutable(p: string): Promise<boolean> {
57
- try {
58
- await fs.access(p, 0o1);
59
- return true;
60
- } catch {
61
- return false;
62
- }
63
- }
64
-
65
- async function makeExecutable(p: string): Promise<void> {
66
- if (process.platform === 'win32') return;
67
- try {
68
- await fs.chmod(p, 0o755);
69
- } catch {}
70
- }
15
+ export { getAgiBinDir } from './bin-manager/paths.ts';
71
16
 
72
17
  async function whichBinary(name: string): Promise<string | null> {
73
18
  const cmd = process.platform === 'win32' ? 'where' : 'which';
@@ -85,79 +30,8 @@ async function whichBinary(name: string): Promise<string | null> {
85
30
  });
86
31
  }
87
32
 
88
- function getVendorSearchPaths(binaryName: string): string[] {
89
- const platformKey = getPlatformKey();
90
- const paths: string[] = [];
91
-
92
- const tauriResource = process.env.TAURI_RESOURCE_DIR;
93
- if (tauriResource) {
94
- paths.push(join(tauriResource, 'vendor', 'bin', platformKey, binaryName));
95
- paths.push(join(tauriResource, 'vendor', 'bin', binaryName));
96
- }
97
-
98
- try {
99
- const exePath = process.execPath;
100
- if (exePath) {
101
- const exeDir = join(exePath, '..');
102
- paths.push(join(exeDir, 'vendor', 'bin', platformKey, binaryName));
103
- paths.push(
104
- join(
105
- exeDir,
106
- '..',
107
- 'Resources',
108
- 'vendor',
109
- 'bin',
110
- platformKey,
111
- binaryName,
112
- ),
113
- );
114
- }
115
- } catch {}
116
-
117
- if (process.env.CARGO_MANIFEST_DIR) {
118
- paths.push(
119
- join(
120
- process.env.CARGO_MANIFEST_DIR,
121
- 'resources',
122
- 'vendor',
123
- 'bin',
124
- platformKey,
125
- binaryName,
126
- ),
127
- );
128
- }
129
-
130
- const cwd = process.cwd();
131
- paths.push(join(cwd, 'vendor', 'bin', platformKey, binaryName));
132
-
133
- return paths;
134
- }
135
-
136
- async function extractFromVendor(name: string): Promise<string | null> {
137
- const binaryName = getBinaryFileName(name);
138
- const binDir = getAgiBinDir();
139
- const targetPath = join(binDir, binaryName);
140
-
141
- if ((await fileExists(targetPath)) && (await isExecutable(targetPath))) {
142
- return targetPath;
143
- }
144
-
145
- const searchPaths = getVendorSearchPaths(binaryName);
146
-
147
- for (const src of searchPaths) {
148
- if (await fileExists(src)) {
149
- await ensureDir(binDir);
150
- await fs.copyFile(src, targetPath);
151
- await makeExecutable(targetPath);
152
- return targetPath;
153
- }
154
- }
155
-
156
- return null;
157
- }
158
-
159
33
  export async function resolveBinary(name: string): Promise<string> {
160
- const cached = resolvedPaths.get(name);
34
+ const cached = getCachedBinary(name);
161
35
  if (cached) return cached;
162
36
 
163
37
  const binaryName = getBinaryFileName(name);
@@ -167,19 +41,19 @@ export async function resolveBinary(name: string): Promise<string> {
167
41
  (await fileExists(installedPath)) &&
168
42
  (await isExecutable(installedPath))
169
43
  ) {
170
- resolvedPaths.set(name, installedPath);
44
+ setCachedBinary(name, installedPath);
171
45
  return installedPath;
172
46
  }
173
47
 
174
48
  const vendorPath = await extractFromVendor(name);
175
49
  if (vendorPath) {
176
- resolvedPaths.set(name, vendorPath);
50
+ setCachedBinary(name, vendorPath);
177
51
  return vendorPath;
178
52
  }
179
53
 
180
54
  const systemPath = await whichBinary(binaryName);
181
55
  if (systemPath) {
182
- resolvedPaths.set(name, systemPath);
56
+ setCachedBinary(name, systemPath);
183
57
  return systemPath;
184
58
  }
185
59
 
@@ -187,7 +61,7 @@ export async function resolveBinary(name: string): Promise<string> {
187
61
  }
188
62
 
189
63
  export function clearBinaryCache(): void {
190
- resolvedPaths.clear();
64
+ clearCachedBinaries();
191
65
  }
192
66
 
193
67
  function getLoginShellPath(): string | null {