@ottocode/server 0.1.246 → 0.1.248
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 +3 -3
- package/src/presets.ts +1 -1
- package/src/routes/config/models.ts +189 -42
- package/src/runtime/agent/registry.ts +3 -3
- package/src/runtime/agent/runner-setup.ts +13 -1
- package/src/runtime/tools/approval.ts +1 -0
- package/src/runtime/tools/guards.ts +4 -3
- package/src/runtime/tools/mapping.ts +4 -2
- package/src/tools/adapter.ts +3 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ottocode/server",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.248",
|
|
4
4
|
"description": "HTTP API server for ottocode",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -49,8 +49,8 @@
|
|
|
49
49
|
"typecheck": "tsc --noEmit"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@ottocode/database": "0.1.
|
|
53
|
-
"@ottocode/sdk": "0.1.
|
|
52
|
+
"@ottocode/database": "0.1.248",
|
|
53
|
+
"@ottocode/sdk": "0.1.248",
|
|
54
54
|
"ai-sdk-ollama": "^3.8.3",
|
|
55
55
|
"drizzle-orm": "^0.44.5",
|
|
56
56
|
"hono": "^4.9.9",
|
package/src/presets.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Hono } from 'hono';
|
|
2
2
|
import {
|
|
3
|
+
DEFAULT_REMOTE_MODEL_CATALOG_URL,
|
|
3
4
|
discoverOllamaModels,
|
|
4
5
|
loadConfig,
|
|
5
6
|
catalog,
|
|
@@ -12,6 +13,9 @@ import {
|
|
|
12
13
|
type ModelInfo,
|
|
13
14
|
type ProviderId,
|
|
14
15
|
filterModelsForAuthType,
|
|
16
|
+
mergeCachedModelCatalog,
|
|
17
|
+
normalizeModelCatalogPayload,
|
|
18
|
+
readCachedModelCatalog,
|
|
15
19
|
} from '@ottocode/sdk';
|
|
16
20
|
import type { EmbeddedAppConfig } from '../../index.ts';
|
|
17
21
|
import { serializeError } from '../../runtime/errors/api-error.ts';
|
|
@@ -23,6 +27,121 @@ import {
|
|
|
23
27
|
} from './utils.ts';
|
|
24
28
|
|
|
25
29
|
const COPILOT_MODELS_URL = 'https://api.githubcopilot.com/models';
|
|
30
|
+
const REMOTE_CATALOG_REFRESH_TTL_MS = 6 * 60 * 60 * 1000;
|
|
31
|
+
const PROVIDER_MODEL_REFRESH_TTL_MS = 60 * 1000;
|
|
32
|
+
|
|
33
|
+
type UiModel = {
|
|
34
|
+
id: string;
|
|
35
|
+
label: string;
|
|
36
|
+
toolCall?: boolean;
|
|
37
|
+
reasoningText?: boolean;
|
|
38
|
+
vision?: boolean;
|
|
39
|
+
attachment?: boolean;
|
|
40
|
+
free?: boolean;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
type UiProviderModels = {
|
|
44
|
+
label: string;
|
|
45
|
+
authType?: 'api' | 'oauth' | 'wallet';
|
|
46
|
+
allowAnyModel?: boolean;
|
|
47
|
+
dynamicModels?: boolean;
|
|
48
|
+
models: UiModel[];
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const remoteCatalogRefreshes = new Set<string>();
|
|
52
|
+
const providerModelRefreshes = new Set<string>();
|
|
53
|
+
const providerModelRefreshAt = new Map<string, number>();
|
|
54
|
+
let remoteCatalogRefreshAt = 0;
|
|
55
|
+
|
|
56
|
+
function toUiModel(model: ModelInfo): UiModel {
|
|
57
|
+
return {
|
|
58
|
+
id: model.id,
|
|
59
|
+
label: model.label || model.id,
|
|
60
|
+
toolCall: model.toolCall,
|
|
61
|
+
reasoningText: model.reasoningText,
|
|
62
|
+
vision: model.modalities?.input?.includes('image') ?? false,
|
|
63
|
+
attachment: model.attachment ?? false,
|
|
64
|
+
free: model.cost?.input === 0 && model.cost?.output === 0,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function getRemoteCatalogUrl(): string {
|
|
69
|
+
return (
|
|
70
|
+
process.env.OTTO_MODEL_CATALOG_URL?.trim() ||
|
|
71
|
+
DEFAULT_REMOTE_MODEL_CATALOG_URL
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function refreshRemoteCatalogInBackground(): Promise<void> {
|
|
76
|
+
const url = getRemoteCatalogUrl();
|
|
77
|
+
const now = Date.now();
|
|
78
|
+
if (now - remoteCatalogRefreshAt < REMOTE_CATALOG_REFRESH_TTL_MS) return;
|
|
79
|
+
const cachedCatalog = await readCachedModelCatalog();
|
|
80
|
+
const cachedAt = cachedCatalog ? Date.parse(cachedCatalog.updatedAt) : 0;
|
|
81
|
+
if (Number.isFinite(cachedAt)) {
|
|
82
|
+
remoteCatalogRefreshAt = Math.max(remoteCatalogRefreshAt, cachedAt);
|
|
83
|
+
if (now - remoteCatalogRefreshAt < REMOTE_CATALOG_REFRESH_TTL_MS) return;
|
|
84
|
+
}
|
|
85
|
+
if (remoteCatalogRefreshes.has(url)) return;
|
|
86
|
+
remoteCatalogRefreshes.add(url);
|
|
87
|
+
remoteCatalogRefreshAt = now;
|
|
88
|
+
try {
|
|
89
|
+
const response = await fetch(url);
|
|
90
|
+
if (!response.ok) {
|
|
91
|
+
throw new Error(`${response.status} ${response.statusText}`);
|
|
92
|
+
}
|
|
93
|
+
const providers = normalizeModelCatalogPayload(await response.json());
|
|
94
|
+
if (Object.keys(providers).length > 0) {
|
|
95
|
+
await mergeCachedModelCatalog(providers);
|
|
96
|
+
}
|
|
97
|
+
} catch (error) {
|
|
98
|
+
logger.debug('Failed to refresh remote model catalog', {
|
|
99
|
+
url,
|
|
100
|
+
error: error instanceof Error ? error.message : String(error),
|
|
101
|
+
});
|
|
102
|
+
} finally {
|
|
103
|
+
remoteCatalogRefreshes.delete(url);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function refreshProviderModelsInBackground(args: {
|
|
108
|
+
provider: ProviderId;
|
|
109
|
+
providerDefinition: NonNullable<ReturnType<typeof getProviderDefinition>>;
|
|
110
|
+
projectRoot: string;
|
|
111
|
+
}): Promise<void> {
|
|
112
|
+
const refreshKey = `${args.projectRoot}:${args.provider}`;
|
|
113
|
+
const now = Date.now();
|
|
114
|
+
const lastRefresh = providerModelRefreshAt.get(refreshKey) ?? 0;
|
|
115
|
+
if (now - lastRefresh < PROVIDER_MODEL_REFRESH_TTL_MS) return;
|
|
116
|
+
if (providerModelRefreshes.has(refreshKey)) return;
|
|
117
|
+
providerModelRefreshes.add(refreshKey);
|
|
118
|
+
providerModelRefreshAt.set(refreshKey, now);
|
|
119
|
+
try {
|
|
120
|
+
const { provider, providerDefinition, projectRoot } = args;
|
|
121
|
+
const discoveredModels = await discoverProviderModels({
|
|
122
|
+
provider,
|
|
123
|
+
providerDefinition,
|
|
124
|
+
projectRoot,
|
|
125
|
+
});
|
|
126
|
+
const models =
|
|
127
|
+
discoveredModels ??
|
|
128
|
+
getConfiguredProviderModels(await loadConfig(projectRoot), provider);
|
|
129
|
+
await mergeCachedModelCatalog({
|
|
130
|
+
[provider]: {
|
|
131
|
+
id: provider,
|
|
132
|
+
label: providerDefinition.label,
|
|
133
|
+
models,
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
} catch (error) {
|
|
137
|
+
logger.debug('Failed to refresh provider model cache', {
|
|
138
|
+
provider: args.provider,
|
|
139
|
+
error: error instanceof Error ? error.message : String(error),
|
|
140
|
+
});
|
|
141
|
+
} finally {
|
|
142
|
+
providerModelRefreshes.delete(refreshKey);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
26
145
|
|
|
27
146
|
function filterCopilotAvailability<T extends { id: string }>(
|
|
28
147
|
provider: ProviderId,
|
|
@@ -127,6 +246,26 @@ function shouldLazyLoadProviderModels(
|
|
|
127
246
|
);
|
|
128
247
|
}
|
|
129
248
|
|
|
249
|
+
function getCachedOrConfiguredModels(args: {
|
|
250
|
+
models: ModelInfo[] | undefined;
|
|
251
|
+
cfg: Awaited<ReturnType<typeof loadConfig>>;
|
|
252
|
+
provider: ProviderId;
|
|
253
|
+
}): ModelInfo[] {
|
|
254
|
+
const cachedModels = args.models;
|
|
255
|
+
return cachedModels && cachedModels.length > 0
|
|
256
|
+
? cachedModels
|
|
257
|
+
: getConfiguredProviderModels(args.cfg, args.provider);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function getUiProviderLabel(
|
|
261
|
+
providerDefinition: NonNullable<ReturnType<typeof getProviderDefinition>>,
|
|
262
|
+
): string {
|
|
263
|
+
if (providerDefinition.source !== 'custom') return providerDefinition.label;
|
|
264
|
+
return providerDefinition.label.includes('(custom)')
|
|
265
|
+
? providerDefinition.label
|
|
266
|
+
: `${providerDefinition.label} (custom)`;
|
|
267
|
+
}
|
|
268
|
+
|
|
130
269
|
export function registerModelsRoutes(app: Hono) {
|
|
131
270
|
app.get('/v1/config/providers/:provider/models', async (c) => {
|
|
132
271
|
try {
|
|
@@ -151,26 +290,36 @@ export function registerModelsRoutes(app: Hono) {
|
|
|
151
290
|
return c.json({ error: 'Provider not authorized' }, 403);
|
|
152
291
|
}
|
|
153
292
|
|
|
154
|
-
const
|
|
293
|
+
const cachedCatalog = await readCachedModelCatalog();
|
|
294
|
+
const providerCatalog =
|
|
295
|
+
cachedCatalog?.providers[provider] ??
|
|
296
|
+
catalog[provider as keyof typeof catalog];
|
|
155
297
|
const providerDefinition = getProviderDefinition(cfg, provider);
|
|
156
298
|
if (!providerDefinition) {
|
|
157
299
|
logger.warn('Provider not found in catalog', { provider });
|
|
158
300
|
return c.json({ error: 'Provider not found' }, 404);
|
|
159
301
|
}
|
|
302
|
+
void refreshRemoteCatalogInBackground();
|
|
160
303
|
|
|
161
304
|
const authType = await getAuthTypeForProvider(
|
|
162
305
|
embeddedConfig,
|
|
163
306
|
provider,
|
|
164
307
|
projectRoot,
|
|
165
308
|
);
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
309
|
+
if (shouldLazyLoadProviderModels(providerDefinition)) {
|
|
310
|
+
void refreshProviderModelsInBackground({
|
|
311
|
+
provider,
|
|
312
|
+
providerDefinition,
|
|
313
|
+
projectRoot,
|
|
314
|
+
});
|
|
315
|
+
}
|
|
171
316
|
const filteredModels =
|
|
172
317
|
providerDefinition.compatibility === 'ollama'
|
|
173
|
-
? (
|
|
318
|
+
? getCachedOrConfiguredModels({
|
|
319
|
+
models: providerCatalog?.models,
|
|
320
|
+
cfg,
|
|
321
|
+
provider,
|
|
322
|
+
})
|
|
174
323
|
: providerCatalog
|
|
175
324
|
? filterModelsForAuthType(
|
|
176
325
|
provider,
|
|
@@ -190,22 +339,14 @@ export function registerModelsRoutes(app: Hono) {
|
|
|
190
339
|
);
|
|
191
340
|
|
|
192
341
|
return c.json({
|
|
193
|
-
models: availableModels.map(
|
|
194
|
-
id: m.id,
|
|
195
|
-
label: m.label || m.id,
|
|
196
|
-
toolCall: m.toolCall,
|
|
197
|
-
reasoningText: m.reasoningText,
|
|
198
|
-
vision: m.modalities?.input?.includes('image') ?? false,
|
|
199
|
-
attachment: m.attachment ?? false,
|
|
200
|
-
free: m.cost?.input === 0 && m.cost?.output === 0,
|
|
201
|
-
})),
|
|
342
|
+
models: availableModels.map(toUiModel),
|
|
202
343
|
default: getDefault(
|
|
203
344
|
embeddedConfig?.model,
|
|
204
345
|
embeddedConfig?.defaults?.model,
|
|
205
346
|
cfg.defaults.model,
|
|
206
347
|
),
|
|
207
348
|
allowAnyModel: providerAllowsAnyModel(cfg, provider),
|
|
208
|
-
label: providerDefinition
|
|
349
|
+
label: getUiProviderLabel(providerDefinition),
|
|
209
350
|
});
|
|
210
351
|
} catch (error) {
|
|
211
352
|
logger.error('Failed to get provider models', error);
|
|
@@ -230,22 +371,16 @@ export function registerModelsRoutes(app: Hono) {
|
|
|
230
371
|
cfg,
|
|
231
372
|
);
|
|
232
373
|
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
models: Array<{
|
|
239
|
-
id: string;
|
|
240
|
-
label: string;
|
|
241
|
-
toolCall?: boolean;
|
|
242
|
-
reasoningText?: boolean;
|
|
243
|
-
}>;
|
|
244
|
-
}
|
|
245
|
-
> = {};
|
|
374
|
+
const cachedCatalog = await readCachedModelCatalog();
|
|
375
|
+
void refreshRemoteCatalogInBackground();
|
|
376
|
+
|
|
377
|
+
const modelsMap: Record<string, UiProviderModels> = {};
|
|
378
|
+
const cacheUpdates: Parameters<typeof mergeCachedModelCatalog>[0] = {};
|
|
246
379
|
|
|
247
380
|
for (const provider of authorizedProviders) {
|
|
248
|
-
const providerCatalog =
|
|
381
|
+
const providerCatalog =
|
|
382
|
+
cachedCatalog?.providers[provider] ??
|
|
383
|
+
catalog[provider as keyof typeof catalog];
|
|
249
384
|
const providerDefinition = getProviderDefinition(cfg, provider);
|
|
250
385
|
if (providerDefinition) {
|
|
251
386
|
const dynamicModels =
|
|
@@ -255,8 +390,19 @@ export function registerModelsRoutes(app: Hono) {
|
|
|
255
390
|
provider,
|
|
256
391
|
projectRoot,
|
|
257
392
|
);
|
|
393
|
+
if (dynamicModels) {
|
|
394
|
+
void refreshProviderModelsInBackground({
|
|
395
|
+
provider,
|
|
396
|
+
providerDefinition,
|
|
397
|
+
projectRoot,
|
|
398
|
+
});
|
|
399
|
+
}
|
|
258
400
|
const filteredModels = dynamicModels
|
|
259
|
-
?
|
|
401
|
+
? getCachedOrConfiguredModels({
|
|
402
|
+
models: providerCatalog?.models,
|
|
403
|
+
cfg,
|
|
404
|
+
provider,
|
|
405
|
+
})
|
|
260
406
|
: providerCatalog
|
|
261
407
|
? filterModelsForAuthType(
|
|
262
408
|
provider,
|
|
@@ -265,23 +411,24 @@ export function registerModelsRoutes(app: Hono) {
|
|
|
265
411
|
)
|
|
266
412
|
: getConfiguredProviderModels(cfg, provider);
|
|
267
413
|
modelsMap[provider] = {
|
|
268
|
-
label: providerDefinition
|
|
414
|
+
label: getUiProviderLabel(providerDefinition),
|
|
269
415
|
authType,
|
|
270
416
|
allowAnyModel: providerDefinition.allowAnyModel,
|
|
271
417
|
dynamicModels,
|
|
272
|
-
models: filteredModels.map(
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
attachment: m.attachment ?? false,
|
|
279
|
-
free: m.cost?.input === 0 && m.cost?.output === 0,
|
|
280
|
-
})),
|
|
418
|
+
models: filteredModels.map(toUiModel),
|
|
419
|
+
};
|
|
420
|
+
cacheUpdates[provider] = {
|
|
421
|
+
id: provider,
|
|
422
|
+
label: providerDefinition.label,
|
|
423
|
+
models: filteredModels,
|
|
281
424
|
};
|
|
282
425
|
}
|
|
283
426
|
}
|
|
284
427
|
|
|
428
|
+
if (Object.keys(cacheUpdates).length > 0) {
|
|
429
|
+
void mergeCachedModelCatalog(cacheUpdates);
|
|
430
|
+
}
|
|
431
|
+
|
|
285
432
|
return c.json(modelsMap);
|
|
286
433
|
} catch (error) {
|
|
287
434
|
logger.error('Failed to get all models', error);
|
|
@@ -126,7 +126,7 @@ const defaultToolExtras: Record<string, string[]> = {
|
|
|
126
126
|
'write',
|
|
127
127
|
'ls',
|
|
128
128
|
'tree',
|
|
129
|
-
'
|
|
129
|
+
'shell',
|
|
130
130
|
'update_todos',
|
|
131
131
|
'glob',
|
|
132
132
|
'ripgrep',
|
|
@@ -144,7 +144,7 @@ const defaultToolExtras: Record<string, string[]> = {
|
|
|
144
144
|
'write',
|
|
145
145
|
'ls',
|
|
146
146
|
'tree',
|
|
147
|
-
'
|
|
147
|
+
'shell',
|
|
148
148
|
'ripgrep',
|
|
149
149
|
'glob',
|
|
150
150
|
'websearch',
|
|
@@ -158,7 +158,7 @@ const defaultToolExtras: Record<string, string[]> = {
|
|
|
158
158
|
'write',
|
|
159
159
|
'ls',
|
|
160
160
|
'tree',
|
|
161
|
-
'
|
|
161
|
+
'shell',
|
|
162
162
|
'update_todos',
|
|
163
163
|
'glob',
|
|
164
164
|
'ripgrep',
|
|
@@ -122,6 +122,14 @@ const MODEL_FAMILY_EDIT_TOOL_POLICY_AGENTS = new Set([
|
|
|
122
122
|
'init',
|
|
123
123
|
]);
|
|
124
124
|
|
|
125
|
+
function normalizeToolName(toolName: string): string {
|
|
126
|
+
return toolName === 'bash' ? 'shell' : toolName;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function normalizeToolNames(toolNames: string[]): string[] {
|
|
130
|
+
return Array.from(new Set(toolNames.map(normalizeToolName)));
|
|
131
|
+
}
|
|
132
|
+
|
|
125
133
|
export function applyModelFamilyEditToolPolicy(
|
|
126
134
|
agent: string,
|
|
127
135
|
tools: string[],
|
|
@@ -129,6 +137,7 @@ export function applyModelFamilyEditToolPolicy(
|
|
|
129
137
|
model: string,
|
|
130
138
|
cfg?: OttoConfig,
|
|
131
139
|
): string[] {
|
|
140
|
+
tools = normalizeToolNames(tools);
|
|
132
141
|
if (!MODEL_FAMILY_EDIT_TOOL_POLICY_AGENTS.has(agent)) return tools;
|
|
133
142
|
|
|
134
143
|
const family = cfg
|
|
@@ -328,7 +337,10 @@ export async function setupRunner(opts: RunOpts): Promise<SetupResult> {
|
|
|
328
337
|
opts.provider,
|
|
329
338
|
opts.model,
|
|
330
339
|
);
|
|
331
|
-
const allowedNames = new Set([
|
|
340
|
+
const allowedNames = new Set([
|
|
341
|
+
...normalizeToolNames(allowedToolNames),
|
|
342
|
+
'finish',
|
|
343
|
+
]);
|
|
332
344
|
const gated = allTools.filter(
|
|
333
345
|
(tool) => allowedNames.has(tool.name) || tool.name === 'load_mcp_tools',
|
|
334
346
|
);
|
|
@@ -17,8 +17,9 @@ export function guardToolCall(
|
|
|
17
17
|
const a = (args ?? {}) as Record<string, unknown>;
|
|
18
18
|
|
|
19
19
|
switch (toolName) {
|
|
20
|
+
case 'shell':
|
|
20
21
|
case 'bash':
|
|
21
|
-
return
|
|
22
|
+
return guardShellCommand(String(a.cmd ?? ''));
|
|
22
23
|
case 'terminal':
|
|
23
24
|
return guardTerminal(a);
|
|
24
25
|
case 'read':
|
|
@@ -30,7 +31,7 @@ export function guardToolCall(
|
|
|
30
31
|
}
|
|
31
32
|
}
|
|
32
33
|
|
|
33
|
-
function
|
|
34
|
+
function guardShellCommand(cmd: string): GuardAction {
|
|
34
35
|
const n = cmd.trim();
|
|
35
36
|
if (!n) return { type: 'allow' };
|
|
36
37
|
|
|
@@ -104,7 +105,7 @@ function checkApprovalCommand(cmd: string): string | null {
|
|
|
104
105
|
function guardTerminal(args: Record<string, unknown>): GuardAction {
|
|
105
106
|
const op = String(args.operation ?? '');
|
|
106
107
|
if (op === 'start' && typeof args.command === 'string') {
|
|
107
|
-
return
|
|
108
|
+
return guardShellCommand(args.command);
|
|
108
109
|
}
|
|
109
110
|
return { type: 'allow' };
|
|
110
111
|
}
|
|
@@ -30,7 +30,8 @@ export const CANONICAL_TO_PASCAL: Record<string, string> = {
|
|
|
30
30
|
ripgrep: 'Grep',
|
|
31
31
|
|
|
32
32
|
// Execution
|
|
33
|
-
|
|
33
|
+
shell: 'Shell',
|
|
34
|
+
bash: 'Shell',
|
|
34
35
|
terminal: 'Terminal',
|
|
35
36
|
|
|
36
37
|
// Git operations
|
|
@@ -70,7 +71,8 @@ export const PASCAL_TO_CANONICAL: Record<string, string> = {
|
|
|
70
71
|
Grep: 'ripgrep', // Maps back to ripgrep (primary search tool)
|
|
71
72
|
|
|
72
73
|
// Execution
|
|
73
|
-
|
|
74
|
+
Shell: 'shell',
|
|
75
|
+
Bash: 'shell',
|
|
74
76
|
Terminal: 'terminal',
|
|
75
77
|
|
|
76
78
|
// Git operations
|
package/src/tools/adapter.ts
CHANGED
|
@@ -134,8 +134,8 @@ export function adaptTools(
|
|
|
134
134
|
const stepStates = ctx.stepExecution.states;
|
|
135
135
|
|
|
136
136
|
// Anthropic allows max 4 cache_control blocks
|
|
137
|
-
// Cache only the most frequently used tools: read, write,
|
|
138
|
-
const cacheableTools = new Set(['read', 'write', '
|
|
137
|
+
// Cache only the most frequently used tools: read, write, shell
|
|
138
|
+
const cacheableTools = new Set(['read', 'write', 'shell']);
|
|
139
139
|
let cachedToolCount = 0;
|
|
140
140
|
|
|
141
141
|
for (const { name: canonicalName, tool } of tools) {
|
|
@@ -502,7 +502,7 @@ export function adaptTools(
|
|
|
502
502
|
} as ToolExecuteInput;
|
|
503
503
|
// biome-ignore lint/suspicious/noExplicitAny: AI SDK types are complex
|
|
504
504
|
res = base.execute?.(nextInput, options as any);
|
|
505
|
-
} else if (name === 'bash') {
|
|
505
|
+
} else if (name === 'shell' || name === 'bash') {
|
|
506
506
|
const needsCwd =
|
|
507
507
|
!input ||
|
|
508
508
|
typeof (input as Record<string, unknown>).cwd !== 'string';
|