@ottocode/sdk 0.1.265 → 0.1.267

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 (31) hide show
  1. package/package.json +2 -2
  2. package/src/config/src/index.ts +4 -0
  3. package/src/config/src/manager.ts +8 -14
  4. package/src/config/src/paths.ts +4 -0
  5. package/src/core/src/providers/resolver.ts +29 -70
  6. package/src/core/src/tools/bin-manager/cache.ts +13 -0
  7. package/src/core/src/tools/bin-manager/filesystem.ts +32 -0
  8. package/src/core/src/tools/bin-manager/paths.ts +36 -0
  9. package/src/core/src/tools/bin-manager/vendor.ts +80 -0
  10. package/src/core/src/tools/bin-manager.ts +14 -140
  11. package/src/core/src/tools/builtin/patch/apply-hunk.ts +308 -0
  12. package/src/core/src/tools/builtin/patch/apply-report.ts +99 -0
  13. package/src/core/src/tools/builtin/patch/apply.ts +6 -663
  14. package/src/core/src/tools/builtin/patch/hunk-header.ts +17 -0
  15. package/src/core/src/tools/builtin/patch/indentation.ts +160 -0
  16. package/src/core/src/tools/builtin/patch/matching.ts +58 -0
  17. package/src/core/src/tools/builtin/patch/parse-enveloped.ts +10 -72
  18. package/src/core/src/tools/builtin/patch/parse-unified.ts +15 -105
  19. package/src/core/src/tools/builtin/patch/replace-builder.ts +64 -0
  20. package/src/core/src/tools/builtin/patch/unified-state.ts +86 -0
  21. package/src/core/src/tools/builtin/websearch-strategies.ts +197 -0
  22. package/src/core/src/tools/builtin/websearch.ts +9 -187
  23. package/src/core/src/tools/loader.ts +6 -49
  24. package/src/core/src/tools/plugin-discovery.ts +86 -0
  25. package/src/core/src/utils/logger/format.ts +50 -0
  26. package/src/core/src/utils/logger/sinks.ts +61 -0
  27. package/src/core/src/utils/logger.ts +2 -119
  28. package/src/index.ts +3 -0
  29. package/src/providers/src/index.ts +4 -0
  30. package/src/providers/src/model-resolution.ts +21 -0
  31. package/src/providers/src/zai-client.ts +5 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ottocode/sdk",
3
- "version": "0.1.265",
3
+ "version": "0.1.267",
4
4
  "description": "AI agent SDK for building intelligent assistants - tree-shakable and comprehensive",
5
5
  "author": "nitishxyz",
6
6
  "license": "MIT",
@@ -102,7 +102,7 @@
102
102
  "@modelcontextprotocol/sdk": "^1.12",
103
103
  "@openauthjs/openauth": "^0.4.3",
104
104
  "@openrouter/ai-sdk-provider": "^1.2.0",
105
- "@ottorouter/ai-sdk": "0.2.1",
105
+ "@ottorouter/ai-sdk": "0.2.2",
106
106
  "@solana/web3.js": "^1.98.0",
107
107
  "ai": "^6.0.170",
108
108
  "ai-sdk-ollama": "^3.8.3",
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  getGlobalConfigPath,
3
+ getGlobalSkillsConfigPath,
3
4
  getLocalDataDir,
4
5
  ensureDir,
5
6
  fileExists,
@@ -54,13 +55,16 @@ export async function loadConfig(
54
55
  const dbPath = joinPath(dataDir, 'otto.sqlite');
55
56
  const projectConfigPath = joinPath(dataDir, 'config.json');
56
57
  const globalConfigPath = getGlobalConfigPath();
58
+ const globalSkillsConfigPath = getGlobalSkillsConfigPath();
57
59
 
58
60
  const projectCfg = await readJsonOptional(projectConfigPath);
59
61
  const globalCfg = await readJsonOptional(globalConfigPath);
62
+ const globalSkillsCfg = await readJsonOptional(globalSkillsConfigPath);
60
63
 
61
64
  const merged = deepMerge(
62
65
  DEFAULTS,
63
66
  globalCfg,
67
+ globalSkillsCfg ? { skills: globalSkillsCfg } : undefined,
64
68
  omitGlobalOnlySettings(projectCfg),
65
69
  );
66
70
 
@@ -13,6 +13,7 @@ import type {
13
13
  import {
14
14
  getGlobalConfigDir,
15
15
  getGlobalConfigPath,
16
+ getGlobalSkillsConfigPath,
16
17
  getGlobalDebugDir,
17
18
  getGlobalDebugLogPath,
18
19
  getGlobalDebugSessionsDir,
@@ -151,25 +152,18 @@ export async function writeSkillSettings(
151
152
  updates: SkillSettings,
152
153
  _projectRoot?: string,
153
154
  ) {
154
- const filePath = getConfigFilePath('global');
155
+ const filePath = getGlobalSkillsConfigPath();
155
156
  const existing = await readJsonFile(filePath);
156
- const prevSkills =
157
- existing && typeof existing.skills === 'object'
158
- ? (existing.skills as Record<string, unknown>)
159
- : {};
160
157
  const prevItems =
161
- prevSkills.items && typeof prevSkills.items === 'object'
162
- ? (prevSkills.items as Record<string, unknown>)
158
+ existing?.items && typeof existing.items === 'object'
159
+ ? (existing.items as Record<string, unknown>)
163
160
  : {};
164
161
  const next = {
165
162
  ...existing,
166
- skills: {
167
- ...prevSkills,
168
- ...updates,
169
- items: {
170
- ...prevItems,
171
- ...(updates.items ?? {}),
172
- },
163
+ ...updates,
164
+ items: {
165
+ ...prevItems,
166
+ ...(updates.items ?? {}),
173
167
  },
174
168
  };
175
169
  await writeConfigFile(filePath, next);
@@ -31,6 +31,10 @@ export function getGlobalConfigPath(): string {
31
31
  return joinPath(getGlobalConfigDir(), 'config.json');
32
32
  }
33
33
 
34
+ export function getGlobalSkillsConfigPath(): string {
35
+ return joinPath(getGlobalConfigDir(), 'skills.json');
36
+ }
37
+
34
38
  export function getGlobalAuthPath(): string {
35
39
  return joinPath(getGlobalConfigDir(), 'auth.json');
36
40
  }
@@ -4,33 +4,22 @@ import { google, createGoogleGenerativeAI } from '@ai-sdk/google';
4
4
  import { createOllama } from 'ai-sdk-ollama';
5
5
  import { createOpenRouter } from '@openrouter/ai-sdk-provider';
6
6
  import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
7
- import { createXai } from '@ai-sdk/xai';
8
7
  import {
9
8
  catalog,
9
+ createMinimaxModel,
10
+ createMoonshotModel,
10
11
  createOttoRouterModel,
11
12
  createOpenAIOAuthModel,
13
+ createXaiModel,
14
+ createZaiCodingModel,
15
+ createZaiModel,
12
16
  normalizeOllamaBaseURL,
17
+ resolveOpenAIResponsesModel,
18
+ shouldUseOpenAIResponsesApi,
13
19
  } from '../../../providers/src/index.ts';
14
20
  import { createCopilotModel } from '../../../providers/src/copilot-client.ts';
15
21
  import type { OAuth } from '../../../types/src/index.ts';
16
22
 
17
- function needsResponsesApi(model: string): boolean {
18
- const m = model.toLowerCase();
19
- if (m.includes('gpt-5')) return true;
20
- if (m.startsWith('o1')) return true;
21
- if (m.startsWith('o3')) return true;
22
- if (m.startsWith('o4')) return true;
23
- if (m.includes('codex-mini')) return true;
24
- return false;
25
- }
26
-
27
- function resolveOpenAIModel(
28
- instance: ReturnType<typeof createOpenAI>,
29
- model: string,
30
- ) {
31
- return needsResponsesApi(model) ? instance.responses(model) : instance(model);
32
- }
33
-
34
23
  export type ProviderName =
35
24
  | 'openai'
36
25
  | 'anthropic'
@@ -71,13 +60,15 @@ export async function resolveModel(
71
60
  apiKey: config.apiKey || 'oauth-token',
72
61
  fetch: config.customFetch,
73
62
  });
74
- return resolveOpenAIModel(instance, model);
63
+ return resolveOpenAIResponsesModel(instance, model);
75
64
  }
76
65
  if (config.apiKey) {
77
66
  const instance = createOpenAI({ apiKey: config.apiKey });
78
- return resolveOpenAIModel(instance, model);
67
+ return resolveOpenAIResponsesModel(instance, model);
79
68
  }
80
- return needsResponsesApi(model) ? openai.responses(model) : openai(model);
69
+ return shouldUseOpenAIResponsesApi(model)
70
+ ? openai.responses(model)
71
+ : openai(model);
81
72
  }
82
73
 
83
74
  if (provider === 'anthropic') {
@@ -204,70 +195,38 @@ export async function resolveModel(
204
195
  }
205
196
 
206
197
  if (provider === 'xai') {
207
- const entry = catalog[provider];
208
- const apiKey = config.apiKey || process.env.XAI_API_KEY || '';
209
- const baseURL = config.baseURL || entry?.api;
210
- const instance = createXai({ apiKey, baseURL });
211
- return instance(model);
198
+ return createXaiModel(model, {
199
+ apiKey: config.apiKey,
200
+ baseURL: config.baseURL,
201
+ });
212
202
  }
213
203
 
214
204
  if (provider === 'zai') {
215
- const entry = catalog[provider];
216
- const apiKey =
217
- config.apiKey ||
218
- process.env.ZAI_API_KEY ||
219
- process.env.ZHIPU_API_KEY ||
220
- '';
221
- const baseURL =
222
- config.baseURL || entry?.api || 'https://api.z.ai/api/paas/v4';
223
- const headers = apiKey ? { Authorization: `Bearer ${apiKey}` } : undefined;
224
- const instance = createOpenAICompatible({
225
- name: entry?.label ?? 'Z.AI',
226
- baseURL,
227
- headers,
205
+ return createZaiModel(model, {
206
+ apiKey: config.apiKey,
207
+ baseURL: config.baseURL,
228
208
  });
229
- return instance(model);
230
209
  }
231
210
 
232
211
  if (provider === 'zai-coding') {
233
- const entry = catalog[provider];
234
- const apiKey =
235
- config.apiKey ||
236
- process.env.ZAI_CODING_API_KEY ||
237
- process.env.ZHIPU_API_KEY ||
238
- '';
239
- const baseURL =
240
- config.baseURL || entry?.api || 'https://api.z.ai/api/coding/paas/v4';
241
- const headers = apiKey ? { Authorization: `Bearer ${apiKey}` } : undefined;
242
- const instance = createOpenAICompatible({
243
- name: entry?.label ?? 'Z.AI Coding',
244
- baseURL,
245
- headers,
212
+ return createZaiCodingModel(model, {
213
+ apiKey: config.apiKey,
214
+ baseURL: config.baseURL,
246
215
  });
247
- return instance(model);
248
216
  }
249
217
 
250
218
  if (provider === 'moonshot') {
251
- const entry = catalog[provider];
252
- const apiKey = config.apiKey || process.env.MOONSHOT_API_KEY || '';
253
- const baseURL =
254
- config.baseURL || entry?.api || 'https://api.moonshot.ai/v1';
255
- const headers = apiKey ? { Authorization: `Bearer ${apiKey}` } : undefined;
256
- const instance = createOpenAICompatible({
257
- name: entry?.label ?? 'Moonshot AI',
258
- baseURL,
259
- headers,
219
+ return createMoonshotModel(model, {
220
+ apiKey: config.apiKey,
221
+ baseURL: config.baseURL,
260
222
  });
261
- return instance(model);
262
223
  }
263
224
 
264
225
  if (provider === 'minimax') {
265
- const entry = catalog[provider];
266
- const apiKey = config.apiKey || process.env.MINIMAX_API_KEY || '';
267
- const baseURL =
268
- config.baseURL || entry?.api || 'https://api.minimax.io/anthropic/v1';
269
- const instance = createAnthropic({ apiKey, baseURL });
270
- return instance(model);
226
+ return createMinimaxModel(model, {
227
+ apiKey: config.apiKey,
228
+ baseURL: config.baseURL,
229
+ });
271
230
  }
272
231
 
273
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 {