@ottocode/server 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 (74) hide show
  1. package/package.json +3 -3
  2. package/src/routes/auth/copilot.ts +699 -0
  3. package/src/routes/auth/oauth.ts +578 -0
  4. package/src/routes/auth/onboarding.ts +45 -0
  5. package/src/routes/auth/providers.ts +189 -0
  6. package/src/routes/auth/service.ts +167 -0
  7. package/src/routes/auth/state.ts +23 -0
  8. package/src/routes/auth/status.ts +203 -0
  9. package/src/routes/auth/wallet.ts +229 -0
  10. package/src/routes/auth.ts +12 -2080
  11. package/src/routes/config/models-service.ts +411 -0
  12. package/src/routes/config/models.ts +6 -426
  13. package/src/routes/config/providers-service.ts +237 -0
  14. package/src/routes/config/providers.ts +10 -242
  15. package/src/routes/files/handlers.ts +297 -0
  16. package/src/routes/files/service.ts +313 -0
  17. package/src/routes/files.ts +12 -608
  18. package/src/routes/git/commit-service.ts +207 -0
  19. package/src/routes/git/commit.ts +6 -220
  20. package/src/routes/git/remote-service.ts +116 -0
  21. package/src/routes/git/remote.ts +8 -115
  22. package/src/routes/git/staging-service.ts +111 -0
  23. package/src/routes/git/staging.ts +10 -205
  24. package/src/routes/mcp/auth.ts +338 -0
  25. package/src/routes/mcp/lifecycle.ts +263 -0
  26. package/src/routes/mcp/servers.ts +212 -0
  27. package/src/routes/mcp/service.ts +664 -0
  28. package/src/routes/mcp/state.ts +13 -0
  29. package/src/routes/mcp.ts +6 -1233
  30. package/src/routes/ottorouter/billing.ts +593 -0
  31. package/src/routes/ottorouter/service.ts +92 -0
  32. package/src/routes/ottorouter/topup.ts +301 -0
  33. package/src/routes/ottorouter/wallet.ts +370 -0
  34. package/src/routes/ottorouter.ts +6 -1319
  35. package/src/routes/research/service.ts +339 -0
  36. package/src/routes/research.ts +12 -390
  37. package/src/routes/sessions/crud.ts +563 -0
  38. package/src/routes/sessions/queue.ts +242 -0
  39. package/src/routes/sessions/retry.ts +121 -0
  40. package/src/routes/sessions/service.ts +768 -0
  41. package/src/routes/sessions/share.ts +434 -0
  42. package/src/routes/sessions.ts +8 -1977
  43. package/src/routes/skills/service.ts +221 -0
  44. package/src/routes/skills/spec.ts +309 -0
  45. package/src/routes/skills.ts +31 -909
  46. package/src/routes/terminals/service.ts +326 -0
  47. package/src/routes/terminals.ts +19 -295
  48. package/src/routes/tunnel/service.ts +217 -0
  49. package/src/routes/tunnel.ts +29 -219
  50. package/src/runtime/agent/registry-prompts.ts +147 -0
  51. package/src/runtime/agent/registry.ts +6 -124
  52. package/src/runtime/agent/runner-errors.ts +116 -0
  53. package/src/runtime/agent/runner-reminders.ts +45 -0
  54. package/src/runtime/agent/runner-setup-model.ts +75 -0
  55. package/src/runtime/agent/runner-setup-prompt.ts +185 -0
  56. package/src/runtime/agent/runner-setup-tools.ts +103 -0
  57. package/src/runtime/agent/runner-setup-utils.ts +21 -0
  58. package/src/runtime/agent/runner-setup.ts +54 -288
  59. package/src/runtime/agent/runner-telemetry.ts +112 -0
  60. package/src/runtime/agent/runner-text.ts +108 -0
  61. package/src/runtime/agent/runner-tool-observer.ts +86 -0
  62. package/src/runtime/agent/runner.ts +79 -378
  63. package/src/runtime/prompt/builder.ts +5 -1
  64. package/src/runtime/prompt/capabilities.ts +13 -8
  65. package/src/runtime/provider/custom.ts +73 -0
  66. package/src/runtime/provider/index.ts +2 -85
  67. package/src/runtime/provider/reasoning-builders.ts +280 -0
  68. package/src/runtime/provider/reasoning.ts +67 -264
  69. package/src/tools/adapter/events.ts +116 -0
  70. package/src/tools/adapter/execution.ts +160 -0
  71. package/src/tools/adapter/pending.ts +37 -0
  72. package/src/tools/adapter/persistence.ts +166 -0
  73. package/src/tools/adapter/results.ts +97 -0
  74. package/src/tools/adapter.ts +124 -451
@@ -1,6 +1,5 @@
1
1
  import {
2
2
  filterDiscoveredSkills,
3
- getDiscoveredSkills,
4
3
  getMCPManager,
5
4
  summarizeDescription,
6
5
  type DiscoveredSkill,
@@ -31,7 +30,9 @@ export function buildCapabilitySummary(options?: {
31
30
  skills?: DiscoveredSkill[];
32
31
  mcpTools?: CapabilitySummaryMCPTool[];
33
32
  }): CapabilitySummaryResult {
34
- const skillLines = buildSkillLines(options?.skills, options?.skillSettings);
33
+ const skillLines = options?.skills
34
+ ? buildSkillLines(options.skills, options.skillSettings)
35
+ : [];
35
36
  const mcpLines = buildMCPLines(options?.mcpTools);
36
37
  const components = ['capabilities'];
37
38
  const sections: string[] = [];
@@ -64,13 +65,10 @@ export function buildCapabilitySummary(options?: {
64
65
  }
65
66
 
66
67
  function buildSkillLines(
67
- providedSkills: DiscoveredSkill[] | undefined,
68
+ providedSkills: DiscoveredSkill[],
68
69
  skillSettings: OttoConfig['skills'] | undefined,
69
70
  ): string[] {
70
- const skills = filterDiscoveredSkills(
71
- providedSkills ?? getDiscoveredSkills(),
72
- skillSettings,
73
- );
71
+ const skills = filterDiscoveredSkills(providedSkills, skillSettings);
74
72
  const seen = new Set<string>();
75
73
  const unique: DiscoveredSkill[] = [];
76
74
 
@@ -81,7 +79,14 @@ function buildSkillLines(
81
79
  unique.push(skill);
82
80
  }
83
81
 
84
- unique.sort((a, b) => a.name.localeCompare(b.name));
82
+ unique.sort((a, b) => {
83
+ const aExplicitlyEnabled = skillSettings?.items?.[a.name]?.enabled === true;
84
+ const bExplicitlyEnabled = skillSettings?.items?.[b.name]?.enabled === true;
85
+ if (aExplicitlyEnabled !== bExplicitlyEnabled) {
86
+ return aExplicitlyEnabled ? -1 : 1;
87
+ }
88
+ return a.name.localeCompare(b.name);
89
+ });
85
90
 
86
91
  const visible = unique.slice(0, MAX_SKILLS).map((skill) => {
87
92
  const summary = finalizeSentence(summarizeDescription(skill.description));
@@ -0,0 +1,73 @@
1
+ import {
2
+ getConfiguredProviderApiKey,
3
+ normalizeOllamaBaseURL,
4
+ resolveOpenAIResponsesModel,
5
+ type getProviderDefinition,
6
+ type OttoConfig,
7
+ } from '@ottocode/sdk';
8
+ import { createAnthropic } from '@ai-sdk/anthropic';
9
+ import { createGoogleGenerativeAI } from '@ai-sdk/google';
10
+ import { createOpenAI } from '@ai-sdk/openai';
11
+ import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
12
+ import { createOpenRouter } from '@openrouter/ai-sdk-provider';
13
+ import { createOllama } from 'ai-sdk-ollama';
14
+
15
+ export function resolveCustomConfiguredModel(
16
+ definition: NonNullable<ReturnType<typeof getProviderDefinition>>,
17
+ cfg: OttoConfig,
18
+ model: string,
19
+ options?: {
20
+ reasoningText?: boolean;
21
+ },
22
+ ) {
23
+ const apiKey = getConfiguredProviderApiKey(cfg, definition.id) || '';
24
+ const baseURL =
25
+ definition.baseURL ||
26
+ (definition.id === 'ollama-cloud' ? 'https://ollama.com' : undefined);
27
+
28
+ if (!baseURL) {
29
+ throw new Error(
30
+ `Custom provider ${definition.id} requires a baseURL in config.`,
31
+ );
32
+ }
33
+
34
+ if (definition.compatibility === 'openai') {
35
+ const instance = createOpenAI({ apiKey, baseURL });
36
+ return resolveOpenAIResponsesModel(instance, model);
37
+ }
38
+
39
+ if (definition.compatibility === 'anthropic') {
40
+ const instance = createAnthropic({ apiKey, baseURL });
41
+ return instance(model);
42
+ }
43
+
44
+ if (definition.compatibility === 'google') {
45
+ const instance = createGoogleGenerativeAI({ apiKey, baseURL });
46
+ return instance(model);
47
+ }
48
+
49
+ if (definition.compatibility === 'openrouter') {
50
+ const instance = createOpenRouter({ apiKey, baseURL });
51
+ return instance.chat(model);
52
+ }
53
+
54
+ if (definition.compatibility === 'ollama') {
55
+ const headers = apiKey ? { Authorization: `Bearer ${apiKey}` } : undefined;
56
+ const ollamaBaseURL = normalizeOllamaBaseURL(baseURL);
57
+ const instance = createOllama({
58
+ baseURL: ollamaBaseURL,
59
+ headers,
60
+ });
61
+ return instance(model, {
62
+ ...(options?.reasoningText ? { think: true } : {}),
63
+ });
64
+ }
65
+
66
+ const headers = apiKey ? { Authorization: `Bearer ${apiKey}` } : undefined;
67
+ const instance = createOpenAICompatible({
68
+ name: definition.label,
69
+ baseURL,
70
+ headers,
71
+ });
72
+ return instance(model);
73
+ }
@@ -1,16 +1,5 @@
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
+ import { getProviderDefinition, isBuiltInProviderId } from '@ottocode/sdk';
14
3
  import { getAnthropicInstance } from './anthropic.ts';
15
4
  import { resolveOpenAIModel } from './openai.ts';
16
5
  import { resolveGoogleModel } from './google.ts';
@@ -25,6 +14,7 @@ import { resolveOpencodeModel } from './opencode.ts';
25
14
  import { getMoonshotInstance } from './moonshot.ts';
26
15
  import { getMinimaxInstance } from './minimax.ts';
27
16
  import { resolveCopilotModel } from './copilot.ts';
17
+ import { resolveCustomConfiguredModel } from './custom.ts';
28
18
 
29
19
  export type ProviderName = ProviderId;
30
20
 
@@ -96,76 +86,3 @@ export async function resolveModel(
96
86
  }
97
87
  throw new Error(`Unsupported provider: ${provider}`);
98
88
  }
99
-
100
- function needsResponsesApi(model: string): boolean {
101
- const lower = model.toLowerCase();
102
- return (
103
- lower.includes('gpt-5') ||
104
- lower.startsWith('o1') ||
105
- lower.startsWith('o3') ||
106
- lower.startsWith('o4') ||
107
- lower.includes('codex-mini')
108
- );
109
- }
110
-
111
- function resolveCustomConfiguredModel(
112
- definition: NonNullable<ReturnType<typeof getProviderDefinition>>,
113
- cfg: OttoConfig,
114
- model: string,
115
- options?: {
116
- reasoningText?: boolean;
117
- },
118
- ) {
119
- const apiKey = getConfiguredProviderApiKey(cfg, definition.id) || '';
120
- const baseURL =
121
- definition.baseURL ||
122
- (definition.id === 'ollama-cloud' ? 'https://ollama.com' : undefined);
123
-
124
- if (!baseURL) {
125
- throw new Error(
126
- `Custom provider ${definition.id} requires a baseURL in config.`,
127
- );
128
- }
129
-
130
- if (definition.compatibility === 'openai') {
131
- const instance = createOpenAI({ apiKey, baseURL });
132
- return needsResponsesApi(model)
133
- ? instance.responses(model)
134
- : instance(model);
135
- }
136
-
137
- if (definition.compatibility === 'anthropic') {
138
- const instance = createAnthropic({ apiKey, baseURL });
139
- return instance(model);
140
- }
141
-
142
- if (definition.compatibility === 'google') {
143
- const instance = createGoogleGenerativeAI({ apiKey, baseURL });
144
- return instance(model);
145
- }
146
-
147
- if (definition.compatibility === 'openrouter') {
148
- const instance = createOpenRouter({ apiKey, baseURL });
149
- return instance.chat(model);
150
- }
151
-
152
- if (definition.compatibility === 'ollama') {
153
- const headers = apiKey ? { Authorization: `Bearer ${apiKey}` } : undefined;
154
- const ollamaBaseURL = normalizeOllamaBaseURL(baseURL);
155
- const instance = createOllama({
156
- baseURL: ollamaBaseURL,
157
- headers,
158
- });
159
- return instance(model, {
160
- ...(options?.reasoningText ? { think: true } : {}),
161
- });
162
- }
163
-
164
- const headers = apiKey ? { Authorization: `Bearer ${apiKey}` } : undefined;
165
- const instance = createOpenAICompatible({
166
- name: definition.label,
167
- baseURL,
168
- headers,
169
- });
170
- return instance(model);
171
- }
@@ -0,0 +1,280 @@
1
+ import {
2
+ catalog,
3
+ getProviderDefinition,
4
+ isBuiltInProviderId,
5
+ type OttoConfig,
6
+ type ProviderId,
7
+ type ReasoningLevel,
8
+ } from '@ottocode/sdk';
9
+ import type { ReasoningConfigResult } from './reasoning.ts';
10
+
11
+ const THINKING_BUDGET = 16000;
12
+
13
+ type ReasoningBuilderArgs = {
14
+ cfg?: OttoConfig;
15
+ provider: ProviderId;
16
+ model: string;
17
+ reasoningLevel?: ReasoningLevel;
18
+ maxOutputTokens: number | undefined;
19
+ };
20
+
21
+ function normalizeReasoningLevel(
22
+ level: ReasoningLevel | undefined,
23
+ ): Exclude<ReasoningLevel, 'xhigh'> {
24
+ if (!level) return 'high';
25
+ if (level === 'xhigh') return 'high';
26
+ return level;
27
+ }
28
+
29
+ function toAnthropicEffort(
30
+ model: string,
31
+ level: ReasoningLevel | undefined,
32
+ ): 'low' | 'medium' | 'high' | 'xhigh' | 'max' {
33
+ switch (level) {
34
+ case 'minimal':
35
+ case 'low':
36
+ return 'low';
37
+ case 'medium':
38
+ return 'medium';
39
+ case 'max':
40
+ return 'max';
41
+ case 'xhigh':
42
+ return isClaudeOpus47(model) ? 'xhigh' : 'max';
43
+ default:
44
+ return 'high';
45
+ }
46
+ }
47
+
48
+ function toOpenAIEffort(
49
+ level: ReasoningLevel | undefined,
50
+ ): 'minimal' | 'low' | 'medium' | 'high' | 'xhigh' {
51
+ switch (level) {
52
+ case 'minimal':
53
+ return 'minimal';
54
+ case 'low':
55
+ return 'low';
56
+ case 'medium':
57
+ return 'medium';
58
+ case 'max':
59
+ case 'xhigh':
60
+ return 'xhigh';
61
+ default:
62
+ return 'high';
63
+ }
64
+ }
65
+
66
+ function toGoogleThinkingLevel(
67
+ level: ReasoningLevel | undefined,
68
+ ): 'minimal' | 'low' | 'medium' | 'high' {
69
+ switch (level) {
70
+ case 'minimal':
71
+ return 'minimal';
72
+ case 'low':
73
+ return 'low';
74
+ case 'medium':
75
+ return 'medium';
76
+ default:
77
+ return 'high';
78
+ }
79
+ }
80
+
81
+ function toThinkingBudget(
82
+ level: ReasoningLevel | undefined,
83
+ maxOutputTokens: number | undefined,
84
+ ): number {
85
+ const cap = maxOutputTokens
86
+ ? Math.max(maxOutputTokens, THINKING_BUDGET)
87
+ : THINKING_BUDGET;
88
+ switch (level) {
89
+ case 'minimal':
90
+ return Math.min(2048, cap);
91
+ case 'low':
92
+ return Math.min(4096, cap);
93
+ case 'medium':
94
+ return Math.min(8192, cap);
95
+ case 'max':
96
+ case 'xhigh':
97
+ return Math.min(24000, cap);
98
+ default:
99
+ return Math.min(16000, cap);
100
+ }
101
+ }
102
+
103
+ function toCamelCaseKey(value: string): string {
104
+ return value
105
+ .replace(/[^a-zA-Z0-9]+/g, ' ')
106
+ .trim()
107
+ .split(/\s+/)
108
+ .map((segment, index) => {
109
+ const lower = segment.toLowerCase();
110
+ if (index === 0) return lower;
111
+ return lower.charAt(0).toUpperCase() + lower.slice(1);
112
+ })
113
+ .join('');
114
+ }
115
+
116
+ function getOpenAICompatibleProviderOptionKeys(
117
+ provider: ProviderId,
118
+ cfg?: OttoConfig,
119
+ ): string[] {
120
+ const definition = cfg ? getProviderDefinition(cfg, provider) : undefined;
121
+ const entry = isBuiltInProviderId(provider) ? catalog[provider] : undefined;
122
+ const keys = new Set<string>(['openaiCompatible', toCamelCaseKey(provider)]);
123
+ const label = definition?.label ?? entry?.label;
124
+ if (label) {
125
+ keys.add(toCamelCaseKey(label));
126
+ }
127
+ return Array.from(keys).filter(Boolean);
128
+ }
129
+
130
+ function buildSharedProviderOptions(
131
+ provider: ProviderId,
132
+ options: Record<string, unknown>,
133
+ cfg?: OttoConfig,
134
+ ): Record<string, unknown> {
135
+ const keys = getOpenAICompatibleProviderOptionKeys(provider, cfg);
136
+ return Object.fromEntries(keys.map((key) => [key, options]));
137
+ }
138
+
139
+ export function isClaudeOpus47(model: string): boolean {
140
+ const lower = model.toLowerCase();
141
+ return lower.includes('claude-opus-4-7') || lower.includes('claude-opus-4.7');
142
+ }
143
+
144
+ function usesAdaptiveAnthropicThinking(model: string): boolean {
145
+ const lower = model.toLowerCase();
146
+ return (
147
+ isClaudeOpus47(model) ||
148
+ lower.includes('claude-opus-4-6') ||
149
+ lower.includes('claude-opus-4.6') ||
150
+ lower.includes('claude-sonnet-4-6') ||
151
+ lower.includes('claude-sonnet-4.6')
152
+ );
153
+ }
154
+
155
+ export function buildAnthropicReasoningOptions({
156
+ model,
157
+ reasoningLevel,
158
+ maxOutputTokens,
159
+ }: ReasoningBuilderArgs): ReasoningConfigResult {
160
+ if (usesAdaptiveAnthropicThinking(model)) {
161
+ const thinking = isClaudeOpus47(model)
162
+ ? { type: 'adaptive', display: 'summarized' }
163
+ : { type: 'adaptive' };
164
+
165
+ return {
166
+ providerOptions: {
167
+ anthropic: {
168
+ thinking,
169
+ effort: toAnthropicEffort(model, reasoningLevel),
170
+ },
171
+ },
172
+ effectiveMaxOutputTokens: maxOutputTokens,
173
+ enabled: true,
174
+ };
175
+ }
176
+
177
+ const thinkingBudget = toThinkingBudget(reasoningLevel, maxOutputTokens);
178
+
179
+ return {
180
+ providerOptions: {
181
+ anthropic: {
182
+ thinking: { type: 'enabled', budgetTokens: thinkingBudget },
183
+ },
184
+ },
185
+ effectiveMaxOutputTokens:
186
+ maxOutputTokens && maxOutputTokens > thinkingBudget
187
+ ? maxOutputTokens - thinkingBudget
188
+ : maxOutputTokens,
189
+ enabled: true,
190
+ };
191
+ }
192
+
193
+ export function buildOpenAIReasoningOptions({
194
+ reasoningLevel,
195
+ maxOutputTokens,
196
+ }: ReasoningBuilderArgs): ReasoningConfigResult {
197
+ return {
198
+ providerOptions: {
199
+ openai: {
200
+ reasoningEffort: toOpenAIEffort(reasoningLevel),
201
+ reasoningSummary: 'auto',
202
+ },
203
+ },
204
+ effectiveMaxOutputTokens: maxOutputTokens,
205
+ enabled: true,
206
+ };
207
+ }
208
+
209
+ export function buildGoogleReasoningOptions({
210
+ model,
211
+ reasoningLevel,
212
+ maxOutputTokens,
213
+ }: ReasoningBuilderArgs): ReasoningConfigResult {
214
+ const isGemini3 = model.includes('gemini-3');
215
+ return {
216
+ providerOptions: {
217
+ google: {
218
+ thinkingConfig: isGemini3
219
+ ? {
220
+ thinkingLevel: toGoogleThinkingLevel(reasoningLevel),
221
+ includeThoughts: true,
222
+ }
223
+ : {
224
+ thinkingBudget: toThinkingBudget(reasoningLevel, maxOutputTokens),
225
+ includeThoughts: true,
226
+ },
227
+ },
228
+ },
229
+ effectiveMaxOutputTokens: maxOutputTokens,
230
+ enabled: true,
231
+ };
232
+ }
233
+
234
+ export function buildOllamaReasoningOptions({
235
+ maxOutputTokens,
236
+ }: ReasoningBuilderArgs): ReasoningConfigResult {
237
+ return {
238
+ providerOptions: {
239
+ ollama: {
240
+ think: true,
241
+ },
242
+ },
243
+ effectiveMaxOutputTokens: maxOutputTokens,
244
+ enabled: true,
245
+ };
246
+ }
247
+
248
+ export function buildOpenRouterReasoningOptions({
249
+ reasoningLevel,
250
+ maxOutputTokens,
251
+ }: ReasoningBuilderArgs): ReasoningConfigResult {
252
+ return {
253
+ providerOptions: {
254
+ openrouter: {
255
+ reasoning: { effort: normalizeReasoningLevel(reasoningLevel) },
256
+ },
257
+ },
258
+ effectiveMaxOutputTokens: maxOutputTokens,
259
+ enabled: true,
260
+ };
261
+ }
262
+
263
+ export function buildOpenAICompatibleReasoningOptions({
264
+ cfg,
265
+ provider,
266
+ reasoningLevel,
267
+ maxOutputTokens,
268
+ }: ReasoningBuilderArgs): ReasoningConfigResult {
269
+ return {
270
+ providerOptions: buildSharedProviderOptions(
271
+ provider,
272
+ {
273
+ reasoningEffort: normalizeReasoningLevel(reasoningLevel),
274
+ },
275
+ cfg,
276
+ ),
277
+ effectiveMaxOutputTokens: maxOutputTokens,
278
+ enabled: true,
279
+ };
280
+ }