@ottocode/server 0.1.245 → 0.1.247
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 +4 -3
- package/src/index.ts +5 -0
- package/src/openapi/paths/config.ts +118 -2
- package/src/openapi/paths/skills.ts +122 -0
- package/src/openapi/schemas.ts +35 -3
- package/src/presets.ts +1 -1
- package/src/routes/auth.ts +24 -30
- package/src/routes/branch.ts +3 -2
- package/src/routes/config/defaults.ts +10 -3
- package/src/routes/config/main.ts +3 -0
- package/src/routes/config/models.ts +84 -14
- package/src/routes/config/providers.ts +137 -4
- package/src/routes/config/utils.ts +72 -2
- package/src/routes/doctor.ts +15 -27
- package/src/routes/git/commit.ts +16 -5
- package/src/routes/research.ts +3 -3
- package/src/routes/session-messages.ts +14 -8
- package/src/routes/sessions.ts +12 -18
- package/src/routes/skills.ts +140 -59
- package/src/runtime/agent/registry.ts +8 -5
- package/src/runtime/agent/runner-setup.ts +136 -39
- package/src/runtime/agent/runner.ts +140 -4
- package/src/runtime/ask/service.ts +13 -10
- package/src/runtime/message/history-builder.ts +22 -6
- package/src/runtime/message/service.ts +7 -1
- package/src/runtime/prompt/builder.ts +12 -0
- package/src/runtime/prompt/capabilities.ts +200 -0
- package/src/runtime/provider/index.ts +98 -0
- package/src/runtime/provider/reasoning.ts +73 -17
- package/src/runtime/provider/selection.ts +16 -14
- package/src/runtime/session/manager.ts +1 -1
- package/src/runtime/session/queue.ts +7 -2
- 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
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
import type { OttoConfig, ProviderId } from '@ottocode/sdk';
|
|
2
|
+
import {
|
|
3
|
+
getConfiguredProviderApiKey,
|
|
4
|
+
getProviderDefinition,
|
|
5
|
+
isBuiltInProviderId,
|
|
6
|
+
normalizeOllamaBaseURL,
|
|
7
|
+
} from '@ottocode/sdk';
|
|
8
|
+
import { createOpenAI } from '@ai-sdk/openai';
|
|
9
|
+
import { createAnthropic } from '@ai-sdk/anthropic';
|
|
10
|
+
import { createGoogleGenerativeAI } from '@ai-sdk/google';
|
|
11
|
+
import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
|
|
12
|
+
import { createOllama } from 'ai-sdk-ollama';
|
|
13
|
+
import { createOpenRouter } from '@openrouter/ai-sdk-provider';
|
|
2
14
|
import { getAnthropicInstance } from './anthropic.ts';
|
|
3
15
|
import { resolveOpenAIModel } from './openai.ts';
|
|
4
16
|
import { resolveGoogleModel } from './google.ts';
|
|
@@ -23,6 +35,7 @@ export async function resolveModel(
|
|
|
23
35
|
systemPrompt?: string;
|
|
24
36
|
sessionId?: string;
|
|
25
37
|
messageId?: string;
|
|
38
|
+
reasoningText?: boolean;
|
|
26
39
|
topupApprovalMode?: ResolveOttoRouterModelOptions['topupApprovalMode'];
|
|
27
40
|
autoPayThresholdUsd?: ResolveOttoRouterModelOptions['autoPayThresholdUsd'];
|
|
28
41
|
},
|
|
@@ -37,6 +50,13 @@ export async function resolveModel(
|
|
|
37
50
|
if (provider === 'google') {
|
|
38
51
|
return resolveGoogleModel(model, cfg);
|
|
39
52
|
}
|
|
53
|
+
if (provider === 'ollama-cloud') {
|
|
54
|
+
const definition = getProviderDefinition(cfg, provider);
|
|
55
|
+
if (!definition) {
|
|
56
|
+
throw new Error(`Unsupported provider: ${provider}`);
|
|
57
|
+
}
|
|
58
|
+
return resolveCustomConfiguredModel(definition, cfg, model, options);
|
|
59
|
+
}
|
|
40
60
|
if (provider === 'openrouter') {
|
|
41
61
|
return resolveOpenRouterModel(model);
|
|
42
62
|
}
|
|
@@ -65,5 +85,83 @@ export async function resolveModel(
|
|
|
65
85
|
if (provider === 'minimax') {
|
|
66
86
|
return getMinimaxInstance(cfg, model);
|
|
67
87
|
}
|
|
88
|
+
|
|
89
|
+
const definition = getProviderDefinition(cfg, provider);
|
|
90
|
+
if (definition && !isBuiltInProviderId(provider)) {
|
|
91
|
+
return resolveCustomConfiguredModel(definition, cfg, model, options);
|
|
92
|
+
}
|
|
68
93
|
throw new Error(`Unsupported provider: ${provider}`);
|
|
69
94
|
}
|
|
95
|
+
|
|
96
|
+
function needsResponsesApi(model: string): boolean {
|
|
97
|
+
const lower = model.toLowerCase();
|
|
98
|
+
return (
|
|
99
|
+
lower.includes('gpt-5') ||
|
|
100
|
+
lower.startsWith('o1') ||
|
|
101
|
+
lower.startsWith('o3') ||
|
|
102
|
+
lower.startsWith('o4') ||
|
|
103
|
+
lower.includes('codex-mini')
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function resolveCustomConfiguredModel(
|
|
108
|
+
definition: NonNullable<ReturnType<typeof getProviderDefinition>>,
|
|
109
|
+
cfg: OttoConfig,
|
|
110
|
+
model: string,
|
|
111
|
+
options?: {
|
|
112
|
+
reasoningText?: boolean;
|
|
113
|
+
},
|
|
114
|
+
) {
|
|
115
|
+
const apiKey = getConfiguredProviderApiKey(cfg, definition.id) || '';
|
|
116
|
+
const baseURL =
|
|
117
|
+
definition.baseURL ||
|
|
118
|
+
(definition.id === 'ollama-cloud' ? 'https://ollama.com' : undefined);
|
|
119
|
+
|
|
120
|
+
if (!baseURL) {
|
|
121
|
+
throw new Error(
|
|
122
|
+
`Custom provider ${definition.id} requires a baseURL in config.`,
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (definition.compatibility === 'openai') {
|
|
127
|
+
const instance = createOpenAI({ apiKey, baseURL });
|
|
128
|
+
return needsResponsesApi(model)
|
|
129
|
+
? instance.responses(model)
|
|
130
|
+
: instance(model);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (definition.compatibility === 'anthropic') {
|
|
134
|
+
const instance = createAnthropic({ apiKey, baseURL });
|
|
135
|
+
return instance(model);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (definition.compatibility === 'google') {
|
|
139
|
+
const instance = createGoogleGenerativeAI({ apiKey, baseURL });
|
|
140
|
+
return instance(model);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (definition.compatibility === 'openrouter') {
|
|
144
|
+
const instance = createOpenRouter({ apiKey, baseURL });
|
|
145
|
+
return instance.chat(model);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (definition.compatibility === 'ollama') {
|
|
149
|
+
const headers = apiKey ? { Authorization: `Bearer ${apiKey}` } : undefined;
|
|
150
|
+
const ollamaBaseURL = normalizeOllamaBaseURL(baseURL);
|
|
151
|
+
const instance = createOllama({
|
|
152
|
+
baseURL: ollamaBaseURL,
|
|
153
|
+
headers,
|
|
154
|
+
});
|
|
155
|
+
return instance(model, {
|
|
156
|
+
...(options?.reasoningText ? { think: true } : {}),
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const headers = apiKey ? { Authorization: `Bearer ${apiKey}` } : undefined;
|
|
161
|
+
const instance = createOpenAICompatible({
|
|
162
|
+
name: definition.label,
|
|
163
|
+
baseURL,
|
|
164
|
+
headers,
|
|
165
|
+
});
|
|
166
|
+
return instance(model);
|
|
167
|
+
}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
catalog,
|
|
3
|
+
getConfiguredProviderFamily,
|
|
4
|
+
getProviderDefinition,
|
|
3
5
|
getModelNpmBinding,
|
|
4
6
|
getUnderlyingProviderKey,
|
|
5
7
|
modelSupportsReasoning,
|
|
8
|
+
type OttoConfig,
|
|
6
9
|
type ProviderId,
|
|
7
10
|
type ReasoningLevel,
|
|
8
11
|
} from '@ottocode/sdk';
|
|
@@ -35,7 +38,6 @@ function toAnthropicEffort(
|
|
|
35
38
|
case 'max':
|
|
36
39
|
case 'xhigh':
|
|
37
40
|
return 'max';
|
|
38
|
-
case 'high':
|
|
39
41
|
default:
|
|
40
42
|
return 'high';
|
|
41
43
|
}
|
|
@@ -54,7 +56,6 @@ function toOpenAIEffort(
|
|
|
54
56
|
case 'max':
|
|
55
57
|
case 'xhigh':
|
|
56
58
|
return 'xhigh';
|
|
57
|
-
case 'high':
|
|
58
59
|
default:
|
|
59
60
|
return 'high';
|
|
60
61
|
}
|
|
@@ -70,9 +71,6 @@ function toGoogleThinkingLevel(
|
|
|
70
71
|
return 'low';
|
|
71
72
|
case 'medium':
|
|
72
73
|
return 'medium';
|
|
73
|
-
case 'max':
|
|
74
|
-
case 'xhigh':
|
|
75
|
-
case 'high':
|
|
76
74
|
default:
|
|
77
75
|
return 'high';
|
|
78
76
|
}
|
|
@@ -95,7 +93,6 @@ function toThinkingBudget(
|
|
|
95
93
|
case 'max':
|
|
96
94
|
case 'xhigh':
|
|
97
95
|
return Math.min(24000, cap);
|
|
98
|
-
case 'high':
|
|
99
96
|
default:
|
|
100
97
|
return Math.min(16000, cap);
|
|
101
98
|
}
|
|
@@ -114,11 +111,16 @@ function toCamelCaseKey(value: string): string {
|
|
|
114
111
|
.join('');
|
|
115
112
|
}
|
|
116
113
|
|
|
117
|
-
function getOpenAICompatibleProviderOptionKeys(
|
|
114
|
+
function getOpenAICompatibleProviderOptionKeys(
|
|
115
|
+
provider: ProviderId,
|
|
116
|
+
cfg?: OttoConfig,
|
|
117
|
+
): string[] {
|
|
118
|
+
const definition = cfg ? getProviderDefinition(cfg, provider) : undefined;
|
|
118
119
|
const entry = catalog[provider];
|
|
119
120
|
const keys = new Set<string>(['openaiCompatible', toCamelCaseKey(provider)]);
|
|
120
|
-
|
|
121
|
-
|
|
121
|
+
const label = definition?.label ?? entry?.label;
|
|
122
|
+
if (label) {
|
|
123
|
+
keys.add(toCamelCaseKey(label));
|
|
122
124
|
}
|
|
123
125
|
return Array.from(keys).filter(Boolean);
|
|
124
126
|
}
|
|
@@ -126,8 +128,9 @@ function getOpenAICompatibleProviderOptionKeys(provider: ProviderId): string[] {
|
|
|
126
128
|
function buildSharedProviderOptions(
|
|
127
129
|
provider: ProviderId,
|
|
128
130
|
options: Record<string, unknown>,
|
|
131
|
+
cfg?: OttoConfig,
|
|
129
132
|
): Record<string, unknown> {
|
|
130
|
-
const keys = getOpenAICompatibleProviderOptionKeys(provider);
|
|
133
|
+
const keys = getOpenAICompatibleProviderOptionKeys(provider, cfg);
|
|
131
134
|
return Object.fromEntries(keys.map((key) => [key, options]));
|
|
132
135
|
}
|
|
133
136
|
|
|
@@ -146,14 +149,28 @@ function usesAdaptiveAnthropicThinking(model: string): boolean {
|
|
|
146
149
|
function getReasoningProviderTarget(
|
|
147
150
|
provider: ProviderId,
|
|
148
151
|
model: string,
|
|
152
|
+
cfg?: OttoConfig,
|
|
149
153
|
):
|
|
150
154
|
| 'anthropic'
|
|
151
155
|
| 'openai'
|
|
152
156
|
| 'google'
|
|
157
|
+
| 'ollama'
|
|
153
158
|
| 'openai-compatible'
|
|
154
159
|
| 'openrouter'
|
|
155
160
|
| null {
|
|
161
|
+
const definition = cfg ? getProviderDefinition(cfg, provider) : undefined;
|
|
162
|
+
if (definition?.source === 'custom') {
|
|
163
|
+
if (definition.compatibility === 'anthropic') return 'anthropic';
|
|
164
|
+
if (definition.compatibility === 'openai') return 'openai';
|
|
165
|
+
if (definition.compatibility === 'google') return 'google';
|
|
166
|
+
if (definition.compatibility === 'ollama') return 'ollama';
|
|
167
|
+
if (definition.compatibility === 'openrouter') return 'openrouter';
|
|
168
|
+
return 'openai-compatible';
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (provider === 'ottorouter') return 'openrouter';
|
|
156
172
|
if (provider === 'openrouter') return 'openrouter';
|
|
173
|
+
if (definition?.compatibility === 'ollama') return 'ollama';
|
|
157
174
|
if (
|
|
158
175
|
provider === 'moonshot' ||
|
|
159
176
|
provider === 'zai' ||
|
|
@@ -167,6 +184,7 @@ function getReasoningProviderTarget(
|
|
|
167
184
|
if (npmBinding === '@ai-sdk/anthropic') return 'anthropic';
|
|
168
185
|
if (npmBinding === '@ai-sdk/openai') return 'openai';
|
|
169
186
|
if (npmBinding === '@ai-sdk/google') return 'google';
|
|
187
|
+
if (npmBinding === 'ai-sdk-ollama') return 'ollama';
|
|
170
188
|
if (npmBinding === '@ai-sdk/openai-compatible') return 'openai-compatible';
|
|
171
189
|
if (npmBinding === '@openrouter/ai-sdk-provider') return 'openrouter';
|
|
172
190
|
|
|
@@ -175,19 +193,41 @@ function getReasoningProviderTarget(
|
|
|
175
193
|
if (underlyingProvider === 'openai') return 'openai';
|
|
176
194
|
if (underlyingProvider === 'google') return 'google';
|
|
177
195
|
if (underlyingProvider === 'openai-compatible') return 'openai-compatible';
|
|
196
|
+
|
|
197
|
+
const family = cfg ? getConfiguredProviderFamily(cfg, provider, model) : null;
|
|
198
|
+
if (family === 'anthropic') return 'anthropic';
|
|
199
|
+
if (family === 'openai') return 'openai';
|
|
200
|
+
if (family === 'google') return 'google';
|
|
201
|
+
if (family === 'openai-compatible') return 'openai-compatible';
|
|
178
202
|
return null;
|
|
179
203
|
}
|
|
180
204
|
|
|
181
205
|
export function buildReasoningConfig(args: {
|
|
206
|
+
cfg?: OttoConfig;
|
|
182
207
|
provider: ProviderId;
|
|
183
208
|
model: string;
|
|
184
209
|
reasoningText?: boolean;
|
|
185
210
|
reasoningLevel?: ReasoningLevel;
|
|
186
211
|
maxOutputTokens: number | undefined;
|
|
187
212
|
}): ReasoningConfigResult {
|
|
188
|
-
const {
|
|
189
|
-
|
|
190
|
-
|
|
213
|
+
const {
|
|
214
|
+
cfg,
|
|
215
|
+
provider,
|
|
216
|
+
model,
|
|
217
|
+
reasoningText,
|
|
218
|
+
reasoningLevel,
|
|
219
|
+
maxOutputTokens,
|
|
220
|
+
} = args;
|
|
221
|
+
const definition = cfg ? getProviderDefinition(cfg, provider) : undefined;
|
|
222
|
+
const supportsReasoning =
|
|
223
|
+
definition?.compatibility === 'ollama'
|
|
224
|
+
? true
|
|
225
|
+
: definition?.source === 'custom'
|
|
226
|
+
? true
|
|
227
|
+
: provider === 'ottorouter'
|
|
228
|
+
? true
|
|
229
|
+
: modelSupportsReasoning(provider, model);
|
|
230
|
+
if (!reasoningText || !supportsReasoning) {
|
|
191
231
|
return {
|
|
192
232
|
providerOptions: {},
|
|
193
233
|
effectiveMaxOutputTokens: maxOutputTokens,
|
|
@@ -195,7 +235,7 @@ export function buildReasoningConfig(args: {
|
|
|
195
235
|
};
|
|
196
236
|
}
|
|
197
237
|
|
|
198
|
-
const reasoningTarget = getReasoningProviderTarget(provider, model);
|
|
238
|
+
const reasoningTarget = getReasoningProviderTarget(provider, model, cfg);
|
|
199
239
|
if (reasoningTarget === 'anthropic') {
|
|
200
240
|
if (usesAdaptiveAnthropicThinking(model)) {
|
|
201
241
|
return {
|
|
@@ -263,6 +303,18 @@ export function buildReasoningConfig(args: {
|
|
|
263
303
|
};
|
|
264
304
|
}
|
|
265
305
|
|
|
306
|
+
if (reasoningTarget === 'ollama') {
|
|
307
|
+
return {
|
|
308
|
+
providerOptions: {
|
|
309
|
+
ollama: {
|
|
310
|
+
think: true,
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
effectiveMaxOutputTokens: maxOutputTokens,
|
|
314
|
+
enabled: true,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
266
318
|
if (reasoningTarget === 'openrouter') {
|
|
267
319
|
return {
|
|
268
320
|
providerOptions: {
|
|
@@ -277,9 +329,13 @@ export function buildReasoningConfig(args: {
|
|
|
277
329
|
|
|
278
330
|
if (reasoningTarget === 'openai-compatible') {
|
|
279
331
|
return {
|
|
280
|
-
providerOptions: buildSharedProviderOptions(
|
|
281
|
-
|
|
282
|
-
|
|
332
|
+
providerOptions: buildSharedProviderOptions(
|
|
333
|
+
provider,
|
|
334
|
+
{
|
|
335
|
+
reasoningEffort: normalizeReasoningLevel(reasoningLevel),
|
|
336
|
+
},
|
|
337
|
+
cfg,
|
|
338
|
+
),
|
|
283
339
|
effectiveMaxOutputTokens: maxOutputTokens,
|
|
284
340
|
enabled: true,
|
|
285
341
|
};
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { OttoConfig } from '@ottocode/sdk';
|
|
2
2
|
import {
|
|
3
|
-
catalog,
|
|
4
3
|
type ProviderId,
|
|
5
4
|
isProviderAuthorized,
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
getConfiguredProviderIds,
|
|
6
|
+
getConfiguredProviderDefaultModel,
|
|
7
|
+
hasConfiguredModel,
|
|
8
|
+
hasConfiguredProvider,
|
|
9
9
|
} from '@ottocode/sdk';
|
|
10
10
|
|
|
11
11
|
const FALLBACK_ORDER: ProviderId[] = [
|
|
@@ -59,6 +59,7 @@ export async function selectProviderAndModel(
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
const model = resolveModelForProvider({
|
|
62
|
+
cfg,
|
|
62
63
|
provider,
|
|
63
64
|
explicitModel,
|
|
64
65
|
agentModelDefault,
|
|
@@ -81,9 +82,10 @@ async function pickAuthorizedProvider(args: {
|
|
|
81
82
|
const candidates = uniqueProviders([
|
|
82
83
|
candidate,
|
|
83
84
|
...FALLBACK_ORDER,
|
|
84
|
-
...
|
|
85
|
+
...getConfiguredProviderIds(cfg),
|
|
85
86
|
]);
|
|
86
87
|
for (const provider of candidates) {
|
|
88
|
+
if (!hasConfiguredProvider(cfg, provider)) continue;
|
|
87
89
|
const ok = await isProviderAuthorized(cfg, provider);
|
|
88
90
|
if (ok) return provider;
|
|
89
91
|
}
|
|
@@ -94,7 +96,6 @@ function uniqueProviders(list: ProviderId[]): ProviderId[] {
|
|
|
94
96
|
const seen = new Set<ProviderId>();
|
|
95
97
|
const ordered: ProviderId[] = [];
|
|
96
98
|
for (const provider of list) {
|
|
97
|
-
if (!providerIds.includes(provider)) continue;
|
|
98
99
|
if (seen.has(provider)) continue;
|
|
99
100
|
seen.add(provider);
|
|
100
101
|
ordered.push(provider);
|
|
@@ -103,16 +104,17 @@ function uniqueProviders(list: ProviderId[]): ProviderId[] {
|
|
|
103
104
|
}
|
|
104
105
|
|
|
105
106
|
function resolveModelForProvider(args: {
|
|
107
|
+
cfg: OttoConfig;
|
|
106
108
|
provider: ProviderId;
|
|
107
109
|
explicitModel?: string;
|
|
108
110
|
agentModelDefault: string;
|
|
109
111
|
}): string {
|
|
110
|
-
const { provider, explicitModel, agentModelDefault } = args;
|
|
111
|
-
if (explicitModel &&
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
);
|
|
112
|
+
const { cfg, provider, explicitModel, agentModelDefault } = args;
|
|
113
|
+
if (explicitModel && hasConfiguredModel(cfg, provider, explicitModel)) {
|
|
114
|
+
return explicitModel;
|
|
115
|
+
}
|
|
116
|
+
if (hasConfiguredModel(cfg, provider, agentModelDefault)) {
|
|
117
|
+
return agentModelDefault;
|
|
118
|
+
}
|
|
119
|
+
return getConfiguredProviderDefaultModel(cfg, provider) ?? agentModelDefault;
|
|
118
120
|
}
|
|
@@ -29,7 +29,7 @@ export async function createSession({
|
|
|
29
29
|
model,
|
|
30
30
|
title,
|
|
31
31
|
}: CreateSessionInput): Promise<SessionRow> {
|
|
32
|
-
validateProviderModel(provider, model);
|
|
32
|
+
validateProviderModel(provider, model, cfg);
|
|
33
33
|
const authorized = await isProviderAuthorized(cfg, provider);
|
|
34
34
|
if (!authorized) {
|
|
35
35
|
throw new Error(
|
|
@@ -10,6 +10,7 @@ export type RunOpts = {
|
|
|
10
10
|
provider: ProviderName;
|
|
11
11
|
model: string;
|
|
12
12
|
projectRoot: string;
|
|
13
|
+
queuedAt?: number;
|
|
13
14
|
oneShot?: boolean;
|
|
14
15
|
userContext?: string;
|
|
15
16
|
estimatedInputTokens?: number;
|
|
@@ -74,7 +75,7 @@ function publishQueueState(sessionId: string) {
|
|
|
74
75
|
* Creates an abort controller per message.
|
|
75
76
|
*/
|
|
76
77
|
export function enqueueAssistantRun(
|
|
77
|
-
opts: Omit<RunOpts, 'abortSignal'>,
|
|
78
|
+
opts: Omit<RunOpts, 'abortSignal' | 'queuedAt'>,
|
|
78
79
|
processQueueFn: (sessionId: string) => Promise<void>,
|
|
79
80
|
) {
|
|
80
81
|
const abortController = new AbortController();
|
|
@@ -85,7 +86,11 @@ export function enqueueAssistantRun(
|
|
|
85
86
|
running: false,
|
|
86
87
|
currentMessageId: null,
|
|
87
88
|
};
|
|
88
|
-
state.queue.push({
|
|
89
|
+
state.queue.push({
|
|
90
|
+
...opts,
|
|
91
|
+
queuedAt: globalThis.performance?.now?.() ?? Date.now(),
|
|
92
|
+
abortSignal: abortController.signal,
|
|
93
|
+
});
|
|
89
94
|
runners.set(opts.sessionId, state);
|
|
90
95
|
|
|
91
96
|
publishQueueState(opts.sessionId);
|
|
@@ -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';
|