@ottocode/server 0.1.259 → 0.1.261

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 (69) hide show
  1. package/package.json +4 -3
  2. package/src/index.ts +5 -4
  3. package/src/openapi/register.ts +92 -0
  4. package/src/openapi/route.ts +22 -0
  5. package/src/routes/ask.ts +210 -99
  6. package/src/routes/auth.ts +1701 -626
  7. package/src/routes/branch.ts +281 -90
  8. package/src/routes/config/agents.ts +79 -32
  9. package/src/routes/config/cwd.ts +46 -14
  10. package/src/routes/config/debug.ts +159 -30
  11. package/src/routes/config/defaults.ts +182 -64
  12. package/src/routes/config/main.ts +109 -73
  13. package/src/routes/config/models.ts +304 -137
  14. package/src/routes/config/providers.ts +462 -166
  15. package/src/routes/config/utils.ts +2 -2
  16. package/src/routes/doctor.ts +395 -161
  17. package/src/routes/files.ts +650 -260
  18. package/src/routes/git/branch.ts +143 -52
  19. package/src/routes/git/commit.ts +347 -141
  20. package/src/routes/git/diff.ts +239 -116
  21. package/src/routes/git/init.ts +103 -23
  22. package/src/routes/git/pull.ts +167 -65
  23. package/src/routes/git/push.ts +222 -117
  24. package/src/routes/git/remote.ts +401 -100
  25. package/src/routes/git/staging.ts +502 -141
  26. package/src/routes/git/status.ts +171 -78
  27. package/src/routes/mcp.ts +1129 -404
  28. package/src/routes/openapi.ts +27 -4
  29. package/src/routes/ottorouter.ts +1221 -389
  30. package/src/routes/provider-usage.ts +153 -36
  31. package/src/routes/research.ts +817 -370
  32. package/src/routes/root.ts +50 -6
  33. package/src/routes/session-approval.ts +228 -54
  34. package/src/routes/session-files.ts +265 -134
  35. package/src/routes/session-messages.ts +330 -150
  36. package/src/routes/session-stream.ts +83 -2
  37. package/src/routes/sessions.ts +1830 -780
  38. package/src/routes/skills.ts +849 -161
  39. package/src/routes/terminals.ts +469 -103
  40. package/src/routes/tunnel.ts +394 -118
  41. package/src/runtime/agent/runner-reasoning.ts +38 -3
  42. package/src/runtime/agent/runner.ts +1 -0
  43. package/src/runtime/ask/service.ts +1 -0
  44. package/src/runtime/message/compaction-limits.ts +3 -3
  45. package/src/runtime/provider/reasoning.ts +18 -7
  46. package/src/runtime/session/db-operations.ts +4 -3
  47. package/src/runtime/utils/token.ts +7 -2
  48. package/src/tools/adapter.ts +21 -0
  49. package/src/openapi/paths/ask.ts +0 -81
  50. package/src/openapi/paths/auth.ts +0 -687
  51. package/src/openapi/paths/branch.ts +0 -102
  52. package/src/openapi/paths/config.ts +0 -485
  53. package/src/openapi/paths/doctor.ts +0 -165
  54. package/src/openapi/paths/files.ts +0 -236
  55. package/src/openapi/paths/git.ts +0 -690
  56. package/src/openapi/paths/mcp.ts +0 -339
  57. package/src/openapi/paths/messages.ts +0 -103
  58. package/src/openapi/paths/ottorouter.ts +0 -594
  59. package/src/openapi/paths/provider-usage.ts +0 -59
  60. package/src/openapi/paths/research.ts +0 -227
  61. package/src/openapi/paths/session-approval.ts +0 -93
  62. package/src/openapi/paths/session-extras.ts +0 -336
  63. package/src/openapi/paths/session-files.ts +0 -91
  64. package/src/openapi/paths/sessions.ts +0 -210
  65. package/src/openapi/paths/skills.ts +0 -377
  66. package/src/openapi/paths/stream.ts +0 -26
  67. package/src/openapi/paths/terminals.ts +0 -226
  68. package/src/openapi/paths/tunnel.ts +0 -163
  69. package/src/openapi/spec.ts +0 -73
@@ -24,6 +24,7 @@ import {
24
24
  getDefault,
25
25
  getAuthTypeForProvider,
26
26
  } from './utils.ts';
27
+ import { openApiRoute } from '../../openapi/route.ts';
27
28
 
28
29
  const COPILOT_MODELS_URL = 'https://api.githubcopilot.com/models';
29
30
  const REMOTE_CATALOG_REFRESH_TTL_MS = 5 * 60 * 1000;
@@ -78,7 +79,7 @@ function getRemoteCatalogUrl(): string {
78
79
 
79
80
  function getModelCatalogProviders(
80
81
  cachedCatalog: Awaited<ReturnType<typeof readCachedModelCatalog>>,
81
- ) {
82
+ ): Record<string, { models?: ModelInfo[]; label?: string }> {
82
83
  return cachedCatalog?.providers ?? (USE_BUILTIN_MODEL_CATALOG ? catalog : {});
83
84
  }
84
85
 
@@ -278,154 +279,320 @@ function getUiProviderLabel(
278
279
  }
279
280
 
280
281
  export function registerModelsRoutes(app: Hono) {
281
- app.get('/v1/config/providers/:provider/models', async (c) => {
282
- try {
283
- const embeddedConfig = (
284
- c as unknown as {
285
- get: (key: 'embeddedConfig') => EmbeddedAppConfig | undefined;
286
- }
287
- ).get('embeddedConfig');
288
- const provider = c.req.param('provider') as ProviderId;
289
-
290
- const projectRoot = c.req.query('project') || process.cwd();
291
- const cfg = await loadConfig(projectRoot);
292
- const cachedCatalog = await readCachedModelCatalog();
293
- const modelCatalogProviders = getModelCatalogProviders(cachedCatalog);
294
- const providerCatalog = modelCatalogProviders[provider];
295
-
296
- const authorized = await isProviderAuthorizedHybrid(
297
- embeddedConfig,
298
- cfg,
299
- provider,
300
- );
301
-
302
- if (!authorized) {
303
- logger.warn('Provider not authorized', { provider });
304
- return c.json({ error: 'Provider not authorized' }, 403);
305
- }
282
+ openApiRoute(
283
+ app,
284
+ {
285
+ method: 'get',
286
+ path: '/v1/config/providers/{provider}/models',
287
+ tags: ['config'],
288
+ operationId: 'getProviderModels',
289
+ summary: 'Get available models for a provider',
290
+ parameters: [
291
+ {
292
+ in: 'query',
293
+ name: 'project',
294
+ required: false,
295
+ schema: {
296
+ type: 'string',
297
+ },
298
+ description:
299
+ 'Project root override (defaults to current working directory).',
300
+ },
301
+ {
302
+ in: 'path',
303
+ name: 'provider',
304
+ required: true,
305
+ schema: {
306
+ $ref: '#/components/schemas/Provider',
307
+ },
308
+ },
309
+ ],
310
+ responses: {
311
+ '200': {
312
+ description: 'OK',
313
+ content: {
314
+ 'application/json': {
315
+ schema: {
316
+ type: 'object',
317
+ properties: {
318
+ models: {
319
+ type: 'array',
320
+ items: {
321
+ $ref: '#/components/schemas/Model',
322
+ },
323
+ },
324
+ default: {
325
+ type: 'string',
326
+ nullable: true,
327
+ },
328
+ allowAnyModel: {
329
+ type: 'boolean',
330
+ },
331
+ label: {
332
+ type: 'string',
333
+ },
334
+ },
335
+ required: ['models', 'allowAnyModel', 'label'],
336
+ },
337
+ },
338
+ },
339
+ },
340
+ '403': {
341
+ description: 'Provider not authorized',
342
+ content: {
343
+ 'application/json': {
344
+ schema: {
345
+ type: 'object',
346
+ properties: {
347
+ error: {
348
+ type: 'string',
349
+ },
350
+ },
351
+ required: ['error'],
352
+ },
353
+ },
354
+ },
355
+ },
356
+ '404': {
357
+ description: 'Provider not found',
358
+ content: {
359
+ 'application/json': {
360
+ schema: {
361
+ type: 'object',
362
+ properties: {
363
+ error: {
364
+ type: 'string',
365
+ },
366
+ },
367
+ required: ['error'],
368
+ },
369
+ },
370
+ },
371
+ },
372
+ },
373
+ },
374
+ async (c) => {
375
+ try {
376
+ const embeddedConfig = (
377
+ c as unknown as {
378
+ get: (key: 'embeddedConfig') => EmbeddedAppConfig | undefined;
379
+ }
380
+ ).get('embeddedConfig');
381
+ const provider = c.req.param('provider') as ProviderId;
306
382
 
307
- const providerDefinition = getProviderDefinition(cfg, provider);
308
- if (!providerDefinition && !providerCatalog) {
309
- logger.warn('Provider not found in catalog', { provider });
310
- return c.json({ error: 'Provider not found' }, 404);
311
- }
312
- void refreshRemoteCatalogInBackground();
313
-
314
- const authType = await getAuthTypeForProvider(
315
- embeddedConfig,
316
- provider,
317
- projectRoot,
318
- );
319
- if (
320
- providerDefinition &&
321
- shouldLazyLoadProviderModels(providerDefinition)
322
- ) {
323
- void refreshProviderModelsInBackground({
383
+ const projectRoot = c.req.query('project') || process.cwd();
384
+ const cfg = await loadConfig(projectRoot);
385
+ const cachedCatalog = await readCachedModelCatalog();
386
+ const modelCatalogProviders = getModelCatalogProviders(cachedCatalog);
387
+ const providerCatalog = modelCatalogProviders[provider];
388
+
389
+ const authorized = await isProviderAuthorizedHybrid(
390
+ embeddedConfig,
391
+ cfg,
324
392
  provider,
325
- providerDefinition,
326
- projectRoot,
327
- });
328
- }
329
- const filteredModels = getProviderModelsForUi({
330
- catalogModels: providerCatalog?.models,
331
- provider,
332
- authType,
333
- });
334
- const copilotAllowedModels =
335
- provider === 'copilot'
336
- ? await getAuthorizedCopilotModels(projectRoot)
337
- : null;
338
-
339
- const availableModels = filterCopilotAvailability(
340
- provider,
341
- filteredModels,
342
- copilotAllowedModels,
343
- );
344
-
345
- return c.json({
346
- models: availableModels.map(toUiModel),
347
- default: getDefault(
348
- embeddedConfig?.model,
349
- embeddedConfig?.defaults?.model,
350
- cfg.defaults.model,
351
- ),
352
- allowAnyModel: providerDefinition
353
- ? providerAllowsAnyModel(cfg, provider)
354
- : undefined,
355
- label: providerDefinition
356
- ? getUiProviderLabel(providerDefinition)
357
- : (providerCatalog?.label ?? provider),
358
- });
359
- } catch (error) {
360
- logger.error('Failed to get provider models', error);
361
- const errorResponse = serializeError(error);
362
- return c.json(errorResponse, errorResponse.error.status || 500);
363
- }
364
- });
393
+ );
365
394
 
366
- app.get('/v1/config/models', async (c) => {
367
- try {
368
- const embeddedConfig = (
369
- c as unknown as {
370
- get: (key: 'embeddedConfig') => EmbeddedAppConfig | undefined;
395
+ if (!authorized) {
396
+ logger.warn('Provider not authorized', { provider });
397
+ return c.json({ error: 'Provider not authorized' }, 403);
371
398
  }
372
- ).get('embeddedConfig');
373
-
374
- const projectRoot = c.req.query('project') || process.cwd();
375
- const cfg = await loadConfig(projectRoot);
376
-
377
- const authorizedProviders = await getAuthorizedProviders(
378
- embeddedConfig,
379
- cfg,
380
- );
381
399
 
382
- const cachedCatalog = await readCachedModelCatalog();
383
- const modelCatalogProviders = getModelCatalogProviders(cachedCatalog);
384
- void refreshRemoteCatalogInBackground();
385
-
386
- const modelsMap: Record<string, UiProviderModels> = {};
387
-
388
- for (const provider of authorizedProviders) {
389
- const providerCatalog = modelCatalogProviders[provider];
390
400
  const providerDefinition = getProviderDefinition(cfg, provider);
391
- if (providerCatalog) {
392
- const dynamicModels =
393
- providerDefinition &&
394
- shouldLazyLoadProviderModels(providerDefinition);
395
- const authType = await getAuthTypeForProvider(
396
- embeddedConfig,
401
+ if (!providerDefinition && !providerCatalog) {
402
+ logger.warn('Provider not found in catalog', { provider });
403
+ return c.json({ error: 'Provider not found' }, 404);
404
+ }
405
+ void refreshRemoteCatalogInBackground();
406
+
407
+ const authType = await getAuthTypeForProvider(
408
+ embeddedConfig,
409
+ provider,
410
+ projectRoot,
411
+ );
412
+ if (
413
+ providerDefinition &&
414
+ shouldLazyLoadProviderModels(providerDefinition)
415
+ ) {
416
+ void refreshProviderModelsInBackground({
397
417
  provider,
418
+ providerDefinition,
398
419
  projectRoot,
399
- );
400
- if (dynamicModels && providerDefinition) {
401
- void refreshProviderModelsInBackground({
420
+ });
421
+ }
422
+ const filteredModels = getProviderModelsForUi({
423
+ catalogModels: providerCatalog?.models,
424
+ provider,
425
+ authType,
426
+ });
427
+ const copilotAllowedModels =
428
+ provider === 'copilot'
429
+ ? await getAuthorizedCopilotModels(projectRoot)
430
+ : null;
431
+
432
+ const availableModels = filterCopilotAvailability(
433
+ provider,
434
+ filteredModels,
435
+ copilotAllowedModels,
436
+ );
437
+
438
+ return c.json({
439
+ models: availableModels.map(toUiModel),
440
+ default: getDefault(
441
+ embeddedConfig?.model,
442
+ embeddedConfig?.defaults?.model,
443
+ cfg.defaults.model,
444
+ ),
445
+ allowAnyModel: providerDefinition
446
+ ? providerAllowsAnyModel(cfg, provider)
447
+ : undefined,
448
+ label: providerDefinition
449
+ ? getUiProviderLabel(providerDefinition)
450
+ : (providerCatalog?.label ?? provider),
451
+ });
452
+ } catch (error) {
453
+ logger.error('Failed to get provider models', error);
454
+ const errorResponse = serializeError(error);
455
+ return c.json(errorResponse, errorResponse.error.status || 500);
456
+ }
457
+ },
458
+ );
459
+
460
+ openApiRoute(
461
+ app,
462
+ {
463
+ method: 'get',
464
+ path: '/v1/config/models',
465
+ tags: ['config'],
466
+ operationId: 'getAllModels',
467
+ summary: 'Get all models across authorized providers',
468
+ parameters: [
469
+ {
470
+ in: 'query',
471
+ name: 'project',
472
+ required: false,
473
+ schema: {
474
+ type: 'string',
475
+ },
476
+ description:
477
+ 'Project root override (defaults to current working directory).',
478
+ },
479
+ ],
480
+ responses: {
481
+ '200': {
482
+ description: 'OK',
483
+ content: {
484
+ 'application/json': {
485
+ schema: {
486
+ type: 'object',
487
+ additionalProperties: {
488
+ type: 'object',
489
+ properties: {
490
+ label: {
491
+ type: 'string',
492
+ },
493
+ authType: {
494
+ type: 'string',
495
+ },
496
+ models: {
497
+ type: 'array',
498
+ items: {
499
+ type: 'object',
500
+ properties: {
501
+ id: {
502
+ type: 'string',
503
+ },
504
+ label: {
505
+ type: 'string',
506
+ },
507
+ toolCall: {
508
+ type: 'boolean',
509
+ },
510
+ reasoningText: {
511
+ type: 'boolean',
512
+ },
513
+ vision: {
514
+ type: 'boolean',
515
+ },
516
+ attachment: {
517
+ type: 'boolean',
518
+ },
519
+ },
520
+ required: ['id', 'label'],
521
+ },
522
+ },
523
+ },
524
+ required: ['label', 'models'],
525
+ },
526
+ },
527
+ },
528
+ },
529
+ },
530
+ },
531
+ },
532
+ async (c) => {
533
+ try {
534
+ const embeddedConfig = (
535
+ c as unknown as {
536
+ get: (key: 'embeddedConfig') => EmbeddedAppConfig | undefined;
537
+ }
538
+ ).get('embeddedConfig');
539
+
540
+ const projectRoot = c.req.query('project') || process.cwd();
541
+ const cfg = await loadConfig(projectRoot);
542
+
543
+ const authorizedProviders = await getAuthorizedProviders(
544
+ embeddedConfig,
545
+ cfg,
546
+ );
547
+
548
+ const cachedCatalog = await readCachedModelCatalog();
549
+ const modelCatalogProviders = getModelCatalogProviders(cachedCatalog);
550
+ void refreshRemoteCatalogInBackground();
551
+
552
+ const modelsMap: Record<string, UiProviderModels> = {};
553
+
554
+ for (const provider of authorizedProviders) {
555
+ const providerCatalog = modelCatalogProviders[provider];
556
+ const providerDefinition = getProviderDefinition(cfg, provider);
557
+ if (providerCatalog) {
558
+ const dynamicModels =
559
+ providerDefinition &&
560
+ shouldLazyLoadProviderModels(providerDefinition);
561
+ const authType = await getAuthTypeForProvider(
562
+ embeddedConfig,
402
563
  provider,
403
- providerDefinition,
404
564
  projectRoot,
565
+ );
566
+ if (dynamicModels && providerDefinition) {
567
+ void refreshProviderModelsInBackground({
568
+ provider,
569
+ providerDefinition,
570
+ projectRoot,
571
+ });
572
+ }
573
+ const filteredModels = getProviderModelsForUi({
574
+ catalogModels: providerCatalog?.models,
575
+ provider,
576
+ authType,
405
577
  });
578
+ modelsMap[provider] = {
579
+ label: providerDefinition
580
+ ? getUiProviderLabel(providerDefinition)
581
+ : (providerCatalog.label ?? provider),
582
+ authType,
583
+ allowAnyModel: providerDefinition?.allowAnyModel,
584
+ dynamicModels,
585
+ models: filteredModels.map(toUiModel),
586
+ };
406
587
  }
407
- const filteredModels = getProviderModelsForUi({
408
- catalogModels: providerCatalog?.models,
409
- provider,
410
- authType,
411
- });
412
- modelsMap[provider] = {
413
- label: providerDefinition
414
- ? getUiProviderLabel(providerDefinition)
415
- : (providerCatalog.label ?? provider),
416
- authType,
417
- allowAnyModel: providerDefinition?.allowAnyModel,
418
- dynamicModels,
419
- models: filteredModels.map(toUiModel),
420
- };
421
588
  }
422
- }
423
589
 
424
- return c.json(modelsMap);
425
- } catch (error) {
426
- logger.error('Failed to get all models', error);
427
- const errorResponse = serializeError(error);
428
- return c.json(errorResponse, errorResponse.error.status || 500);
429
- }
430
- });
590
+ return c.json(modelsMap);
591
+ } catch (error) {
592
+ logger.error('Failed to get all models', error);
593
+ const errorResponse = serializeError(error);
594
+ return c.json(errorResponse, errorResponse.error.status || 500);
595
+ }
596
+ },
597
+ );
431
598
  }