@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ottocode/server",
3
- "version": "0.1.265",
3
+ "version": "0.1.267",
4
4
  "description": "HTTP API server for ottocode",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -61,8 +61,8 @@
61
61
  "typecheck": "tsc --noEmit"
62
62
  },
63
63
  "dependencies": {
64
- "@ottocode/database": "0.1.265",
65
- "@ottocode/sdk": "0.1.265",
64
+ "@ottocode/database": "0.1.267",
65
+ "@ottocode/sdk": "0.1.267",
66
66
  "@hono/zod-openapi": "^1.1.5",
67
67
  "ai-sdk-ollama": "^3.8.3",
68
68
  "drizzle-orm": "^0.44.5",
@@ -0,0 +1,699 @@
1
+ import type { Hono } from 'hono';
2
+ import {
3
+ authorizeCopilot,
4
+ getAuth,
5
+ pollForCopilotTokenOnce,
6
+ readEnvKey,
7
+ setAuth,
8
+ } from '@ottocode/sdk';
9
+ import { execFileSync } from 'node:child_process';
10
+ import { logger } from '@ottocode/sdk';
11
+ import { openApiRoute } from '../../openapi/route.ts';
12
+ import {
13
+ detectOAuthOrgRestriction,
14
+ fetchCopilotModels,
15
+ getGhImportCapability,
16
+ } from './service.ts';
17
+ import { copilotDeviceSessions } from './state.ts';
18
+
19
+ export function registerAuthCopilotRoutes(app: Hono) {
20
+ openApiRoute(
21
+ app,
22
+ {
23
+ method: 'post',
24
+ path: '/v1/auth/copilot/device/start',
25
+ tags: ['auth'],
26
+ operationId: 'startCopilotDeviceFlow',
27
+ summary: 'Start Copilot device flow authentication',
28
+ responses: {
29
+ '200': {
30
+ description: 'OK',
31
+ content: {
32
+ 'application/json': {
33
+ schema: {
34
+ type: 'object',
35
+ properties: {
36
+ sessionId: {
37
+ type: 'string',
38
+ },
39
+ userCode: {
40
+ type: 'string',
41
+ },
42
+ verificationUri: {
43
+ type: 'string',
44
+ },
45
+ interval: {
46
+ type: 'integer',
47
+ },
48
+ },
49
+ required: [
50
+ 'sessionId',
51
+ 'userCode',
52
+ 'verificationUri',
53
+ 'interval',
54
+ ],
55
+ },
56
+ },
57
+ },
58
+ },
59
+ },
60
+ },
61
+ async (c) => {
62
+ try {
63
+ const deviceData = await authorizeCopilot();
64
+ const sessionId = crypto.randomUUID();
65
+ copilotDeviceSessions.set(sessionId, {
66
+ deviceCode: deviceData.deviceCode,
67
+ interval: deviceData.interval,
68
+ provider: 'copilot',
69
+ createdAt: Date.now(),
70
+ });
71
+ return c.json({
72
+ sessionId,
73
+ userCode: deviceData.userCode,
74
+ verificationUri: deviceData.verificationUri,
75
+ interval: deviceData.interval,
76
+ });
77
+ } catch (error) {
78
+ const message =
79
+ error instanceof Error
80
+ ? error.message
81
+ : 'Failed to start Copilot device flow';
82
+ logger.error('Copilot device flow start failed', error);
83
+ return c.json({ error: message }, 500);
84
+ }
85
+ },
86
+ );
87
+
88
+ openApiRoute(
89
+ app,
90
+ {
91
+ method: 'post',
92
+ path: '/v1/auth/copilot/device/poll',
93
+ tags: ['auth'],
94
+ operationId: 'pollCopilotDeviceFlow',
95
+ summary: 'Poll Copilot device flow for completion',
96
+ requestBody: {
97
+ required: true,
98
+ content: {
99
+ 'application/json': {
100
+ schema: {
101
+ type: 'object',
102
+ properties: {
103
+ sessionId: {
104
+ type: 'string',
105
+ },
106
+ },
107
+ required: ['sessionId'],
108
+ },
109
+ },
110
+ },
111
+ },
112
+ responses: {
113
+ '200': {
114
+ description: 'OK',
115
+ content: {
116
+ 'application/json': {
117
+ schema: {
118
+ type: 'object',
119
+ properties: {
120
+ status: {
121
+ type: 'string',
122
+ enum: ['complete', 'pending', 'error'],
123
+ },
124
+ error: {
125
+ type: 'string',
126
+ },
127
+ },
128
+ required: ['status'],
129
+ },
130
+ },
131
+ },
132
+ },
133
+ '400': {
134
+ description: 'Bad Request',
135
+ content: {
136
+ 'application/json': {
137
+ schema: {
138
+ type: 'object',
139
+ properties: {
140
+ error: {
141
+ type: 'string',
142
+ },
143
+ },
144
+ required: ['error'],
145
+ },
146
+ },
147
+ },
148
+ },
149
+ },
150
+ },
151
+ async (c) => {
152
+ try {
153
+ const { sessionId } = await c.req.json<{ sessionId: string }>();
154
+ if (!sessionId || !copilotDeviceSessions.has(sessionId)) {
155
+ return c.json({ error: 'Session expired or invalid' }, 400);
156
+ }
157
+ const session = copilotDeviceSessions.get(sessionId);
158
+ if (!session) {
159
+ return c.json({ error: 'Session expired or invalid' }, 400);
160
+ }
161
+ const result = await pollForCopilotTokenOnce(session.deviceCode);
162
+ if (result.status === 'complete') {
163
+ copilotDeviceSessions.delete(sessionId);
164
+ await setAuth(
165
+ 'copilot',
166
+ {
167
+ type: 'oauth',
168
+ refresh: result.accessToken,
169
+ access: result.accessToken,
170
+ expires: 0,
171
+ },
172
+ undefined,
173
+ 'global',
174
+ );
175
+ return c.json({ status: 'complete' });
176
+ }
177
+ if (result.status === 'pending') {
178
+ return c.json({ status: 'pending' });
179
+ }
180
+ if (result.status === 'error') {
181
+ copilotDeviceSessions.delete(sessionId);
182
+ return c.json({ status: 'error', error: result.error });
183
+ }
184
+ return c.json({ status: 'pending' });
185
+ } catch (error) {
186
+ const message = error instanceof Error ? error.message : 'Poll failed';
187
+ logger.error('Copilot device poll failed', error);
188
+ return c.json({ error: message }, 500);
189
+ }
190
+ },
191
+ );
192
+
193
+ openApiRoute(
194
+ app,
195
+ {
196
+ method: 'get',
197
+ path: '/v1/auth/copilot/methods',
198
+ tags: ['auth'],
199
+ operationId: 'getCopilotAuthMethods',
200
+ summary: 'Get available Copilot auth methods',
201
+ responses: {
202
+ '200': {
203
+ description: 'OK',
204
+ content: {
205
+ 'application/json': {
206
+ schema: {
207
+ type: 'object',
208
+ properties: {
209
+ oauth: {
210
+ type: 'boolean',
211
+ },
212
+ token: {
213
+ type: 'boolean',
214
+ },
215
+ ghImport: {
216
+ type: 'object',
217
+ properties: {
218
+ available: {
219
+ type: 'boolean',
220
+ },
221
+ authenticated: {
222
+ type: 'boolean',
223
+ },
224
+ reason: {
225
+ type: 'string',
226
+ },
227
+ },
228
+ required: ['available', 'authenticated'],
229
+ },
230
+ },
231
+ required: ['oauth', 'token', 'ghImport'],
232
+ },
233
+ },
234
+ },
235
+ },
236
+ },
237
+ },
238
+ async (c) => {
239
+ const ghImport = getGhImportCapability();
240
+ return c.json({
241
+ oauth: true,
242
+ token: true,
243
+ ghImport,
244
+ });
245
+ },
246
+ );
247
+
248
+ openApiRoute(
249
+ app,
250
+ {
251
+ method: 'post',
252
+ path: '/v1/auth/copilot/token',
253
+ tags: ['auth'],
254
+ operationId: 'saveCopilotToken',
255
+ summary: 'Save Copilot token after validating model access',
256
+ requestBody: {
257
+ required: true,
258
+ content: {
259
+ 'application/json': {
260
+ schema: {
261
+ type: 'object',
262
+ properties: {
263
+ token: {
264
+ type: 'string',
265
+ },
266
+ },
267
+ required: ['token'],
268
+ },
269
+ },
270
+ },
271
+ },
272
+ responses: {
273
+ '200': {
274
+ description: 'OK',
275
+ content: {
276
+ 'application/json': {
277
+ schema: {
278
+ type: 'object',
279
+ properties: {
280
+ success: {
281
+ type: 'boolean',
282
+ },
283
+ provider: {
284
+ type: 'string',
285
+ },
286
+ source: {
287
+ type: 'string',
288
+ enum: ['token'],
289
+ },
290
+ modelCount: {
291
+ type: 'integer',
292
+ },
293
+ hasGpt52Codex: {
294
+ type: 'boolean',
295
+ },
296
+ sampleModels: {
297
+ type: 'array',
298
+ items: {
299
+ type: 'string',
300
+ },
301
+ },
302
+ },
303
+ required: [
304
+ 'success',
305
+ 'provider',
306
+ 'source',
307
+ 'modelCount',
308
+ 'hasGpt52Codex',
309
+ 'sampleModels',
310
+ ],
311
+ },
312
+ },
313
+ },
314
+ },
315
+ '400': {
316
+ description: 'Bad Request',
317
+ content: {
318
+ 'application/json': {
319
+ schema: {
320
+ type: 'object',
321
+ properties: {
322
+ error: {
323
+ type: 'string',
324
+ },
325
+ },
326
+ required: ['error'],
327
+ },
328
+ },
329
+ },
330
+ },
331
+ },
332
+ },
333
+ async (c) => {
334
+ try {
335
+ const { token } = await c.req.json<{ token: string }>();
336
+ const sanitized = token?.trim();
337
+ if (!sanitized) {
338
+ return c.json({ error: 'Copilot token is required' }, 400);
339
+ }
340
+
341
+ const modelsResult = await fetchCopilotModels(sanitized);
342
+ if (!modelsResult.ok) {
343
+ return c.json(
344
+ {
345
+ error: `Invalid Copilot token: ${modelsResult.message}`,
346
+ },
347
+ 400,
348
+ );
349
+ }
350
+
351
+ await setAuth(
352
+ 'copilot',
353
+ {
354
+ type: 'oauth',
355
+ refresh: sanitized,
356
+ access: sanitized,
357
+ expires: 0,
358
+ },
359
+ undefined,
360
+ 'global',
361
+ );
362
+
363
+ const models = Array.from(modelsResult.models).sort();
364
+ return c.json({
365
+ success: true,
366
+ provider: 'copilot',
367
+ source: 'token',
368
+ modelCount: models.length,
369
+ hasGpt52Codex: modelsResult.models.has('gpt-5.2-codex'),
370
+ sampleModels: models.slice(0, 25),
371
+ });
372
+ } catch (error) {
373
+ const message =
374
+ error instanceof Error
375
+ ? error.message
376
+ : 'Failed to save Copilot token';
377
+ logger.error('Failed to save Copilot token', error);
378
+ return c.json({ error: message }, 500);
379
+ }
380
+ },
381
+ );
382
+
383
+ openApiRoute(
384
+ app,
385
+ {
386
+ method: 'post',
387
+ path: '/v1/auth/copilot/gh/import',
388
+ tags: ['auth'],
389
+ operationId: 'importCopilotTokenFromGh',
390
+ summary: 'Import Copilot token from GitHub CLI (gh)',
391
+ responses: {
392
+ '200': {
393
+ description: 'OK',
394
+ content: {
395
+ 'application/json': {
396
+ schema: {
397
+ type: 'object',
398
+ properties: {
399
+ success: {
400
+ type: 'boolean',
401
+ },
402
+ provider: {
403
+ type: 'string',
404
+ },
405
+ source: {
406
+ type: 'string',
407
+ enum: ['gh'],
408
+ },
409
+ modelCount: {
410
+ type: 'integer',
411
+ },
412
+ hasGpt52Codex: {
413
+ type: 'boolean',
414
+ },
415
+ sampleModels: {
416
+ type: 'array',
417
+ items: {
418
+ type: 'string',
419
+ },
420
+ },
421
+ },
422
+ required: [
423
+ 'success',
424
+ 'provider',
425
+ 'source',
426
+ 'modelCount',
427
+ 'hasGpt52Codex',
428
+ 'sampleModels',
429
+ ],
430
+ },
431
+ },
432
+ },
433
+ },
434
+ '400': {
435
+ description: 'Bad Request',
436
+ content: {
437
+ 'application/json': {
438
+ schema: {
439
+ type: 'object',
440
+ properties: {
441
+ error: {
442
+ type: 'string',
443
+ },
444
+ },
445
+ required: ['error'],
446
+ },
447
+ },
448
+ },
449
+ },
450
+ },
451
+ },
452
+ async (c) => {
453
+ try {
454
+ const ghImport = getGhImportCapability();
455
+ if (!ghImport.available) {
456
+ return c.json(
457
+ {
458
+ error: ghImport.reason || 'GitHub CLI is not available',
459
+ },
460
+ 400,
461
+ );
462
+ }
463
+ if (!ghImport.authenticated) {
464
+ return c.json(
465
+ {
466
+ error: ghImport.reason || 'GitHub CLI is not authenticated',
467
+ },
468
+ 400,
469
+ );
470
+ }
471
+
472
+ const ghToken = execFileSync('gh', ['auth', 'token'], {
473
+ encoding: 'utf8',
474
+ stdio: ['ignore', 'pipe', 'pipe'],
475
+ }).trim();
476
+ if (!ghToken) {
477
+ return c.json({ error: 'GitHub CLI returned an empty token' }, 400);
478
+ }
479
+
480
+ const modelsResult = await fetchCopilotModels(ghToken);
481
+ if (!modelsResult.ok) {
482
+ return c.json(
483
+ {
484
+ error: `Imported gh token is not valid for Copilot: ${modelsResult.message}`,
485
+ },
486
+ 400,
487
+ );
488
+ }
489
+
490
+ await setAuth(
491
+ 'copilot',
492
+ {
493
+ type: 'oauth',
494
+ refresh: ghToken,
495
+ access: ghToken,
496
+ expires: 0,
497
+ },
498
+ undefined,
499
+ 'global',
500
+ );
501
+
502
+ const models = Array.from(modelsResult.models).sort();
503
+ return c.json({
504
+ success: true,
505
+ provider: 'copilot',
506
+ source: 'gh',
507
+ modelCount: models.length,
508
+ hasGpt52Codex: modelsResult.models.has('gpt-5.2-codex'),
509
+ sampleModels: models.slice(0, 25),
510
+ });
511
+ } catch (error) {
512
+ const message =
513
+ error instanceof Error
514
+ ? error.message
515
+ : 'Failed to import GitHub CLI token';
516
+ logger.error('Failed to import Copilot token from GitHub CLI', error);
517
+ return c.json({ error: message }, 500);
518
+ }
519
+ },
520
+ );
521
+
522
+ openApiRoute(
523
+ app,
524
+ {
525
+ method: 'get',
526
+ path: '/v1/auth/copilot/diagnostics',
527
+ tags: ['auth'],
528
+ operationId: 'getCopilotDiagnostics',
529
+ summary: 'Get Copilot token diagnostics and model visibility',
530
+ responses: {
531
+ '200': {
532
+ description: 'OK',
533
+ content: {
534
+ 'application/json': {
535
+ schema: {
536
+ type: 'object',
537
+ properties: {
538
+ tokenSources: {
539
+ type: 'array',
540
+ items: {
541
+ type: 'object',
542
+ properties: {
543
+ source: {
544
+ type: 'string',
545
+ enum: ['env', 'stored'],
546
+ },
547
+ configured: {
548
+ type: 'boolean',
549
+ },
550
+ modelCount: {
551
+ type: 'integer',
552
+ },
553
+ hasGpt52Codex: {
554
+ type: 'boolean',
555
+ },
556
+ sampleModels: {
557
+ type: 'array',
558
+ items: {
559
+ type: 'string',
560
+ },
561
+ },
562
+ restrictedByOrgPolicy: {
563
+ type: 'boolean',
564
+ },
565
+ restrictedOrg: {
566
+ type: 'string',
567
+ },
568
+ restrictionMessage: {
569
+ type: 'string',
570
+ },
571
+ error: {
572
+ type: 'string',
573
+ },
574
+ },
575
+ required: ['source', 'configured'],
576
+ },
577
+ },
578
+ methods: {
579
+ type: 'object',
580
+ properties: {
581
+ oauth: {
582
+ type: 'boolean',
583
+ },
584
+ token: {
585
+ type: 'boolean',
586
+ },
587
+ ghImport: {
588
+ type: 'object',
589
+ properties: {
590
+ available: {
591
+ type: 'boolean',
592
+ },
593
+ authenticated: {
594
+ type: 'boolean',
595
+ },
596
+ reason: {
597
+ type: 'string',
598
+ },
599
+ },
600
+ required: ['available', 'authenticated'],
601
+ },
602
+ },
603
+ required: ['oauth', 'token', 'ghImport'],
604
+ },
605
+ },
606
+ required: ['tokenSources', 'methods'],
607
+ },
608
+ },
609
+ },
610
+ },
611
+ },
612
+ },
613
+ async (c) => {
614
+ try {
615
+ const projectRoot = process.cwd();
616
+ const entries: Array<{
617
+ source: 'env' | 'stored';
618
+ configured: boolean;
619
+ modelCount?: number;
620
+ hasGpt52Codex?: boolean;
621
+ sampleModels?: string[];
622
+ restrictedByOrgPolicy?: boolean;
623
+ restrictedOrg?: string;
624
+ restrictionMessage?: string;
625
+ error?: string;
626
+ }> = [];
627
+
628
+ const envToken = readEnvKey('copilot');
629
+ if (envToken) {
630
+ const modelsResult = await fetchCopilotModels(envToken);
631
+ if (modelsResult.ok) {
632
+ const models = Array.from(modelsResult.models).sort();
633
+ entries.push({
634
+ source: 'env',
635
+ configured: true,
636
+ modelCount: models.length,
637
+ hasGpt52Codex: modelsResult.models.has('gpt-5.2-codex'),
638
+ sampleModels: models.slice(0, 25),
639
+ });
640
+ } else {
641
+ entries.push({
642
+ source: 'env',
643
+ configured: true,
644
+ error: modelsResult.message,
645
+ });
646
+ }
647
+ } else {
648
+ entries.push({ source: 'env', configured: false });
649
+ }
650
+
651
+ const storedAuth = await getAuth('copilot', projectRoot);
652
+ if (storedAuth?.type === 'oauth') {
653
+ const modelsResult = await fetchCopilotModels(storedAuth.refresh);
654
+ const restriction = await detectOAuthOrgRestriction(
655
+ storedAuth.refresh,
656
+ );
657
+ if (modelsResult.ok) {
658
+ const models = Array.from(modelsResult.models).sort();
659
+ entries.push({
660
+ source: 'stored',
661
+ configured: true,
662
+ modelCount: models.length,
663
+ hasGpt52Codex: modelsResult.models.has('gpt-5.2-codex'),
664
+ sampleModels: models.slice(0, 25),
665
+ restrictedByOrgPolicy: restriction.restricted,
666
+ restrictedOrg: restriction.org,
667
+ restrictionMessage: restriction.message,
668
+ });
669
+ } else {
670
+ entries.push({
671
+ source: 'stored',
672
+ configured: true,
673
+ error: modelsResult.message,
674
+ restrictedByOrgPolicy: restriction.restricted,
675
+ restrictedOrg: restriction.org,
676
+ restrictionMessage: restriction.message,
677
+ });
678
+ }
679
+ } else {
680
+ entries.push({ source: 'stored', configured: false });
681
+ }
682
+
683
+ return c.json({
684
+ tokenSources: entries,
685
+ methods: {
686
+ oauth: true,
687
+ token: true,
688
+ ghImport: getGhImportCapability(),
689
+ },
690
+ });
691
+ } catch (error) {
692
+ const message =
693
+ error instanceof Error ? error.message : 'Failed to inspect Copilot';
694
+ logger.error('Failed to build Copilot diagnostics', error);
695
+ return c.json({ error: message }, 500);
696
+ }
697
+ },
698
+ );
699
+ }