@open-mercato/ai-assistant 0.4.11-develop.1917.b6098e78cb → 0.4.11-develop.1922.46b23ba3b5

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 (31) hide show
  1. package/dist/frontend/components/DockableChat/DockableChat.js +5 -0
  2. package/dist/frontend/components/DockableChat/DockableChat.js.map +2 -2
  3. package/dist/frontend/hooks/useCommandPalette.js +16 -12
  4. package/dist/frontend/hooks/useCommandPalette.js.map +2 -2
  5. package/dist/frontend/hooks/useMcpTools.js +22 -21
  6. package/dist/frontend/hooks/useMcpTools.js.map +2 -2
  7. package/dist/modules/ai_assistant/api/chat/route.js +9 -1
  8. package/dist/modules/ai_assistant/api/chat/route.js.map +2 -2
  9. package/dist/modules/ai_assistant/api/health/route.js +9 -1
  10. package/dist/modules/ai_assistant/api/health/route.js.map +2 -2
  11. package/dist/modules/ai_assistant/api/route/route.js +9 -1
  12. package/dist/modules/ai_assistant/api/route/route.js.map +2 -2
  13. package/dist/modules/ai_assistant/api/settings/route.js +9 -1
  14. package/dist/modules/ai_assistant/api/settings/route.js.map +2 -2
  15. package/dist/modules/ai_assistant/api/tools/execute/route.js +9 -1
  16. package/dist/modules/ai_assistant/api/tools/execute/route.js.map +2 -2
  17. package/dist/modules/ai_assistant/api/tools/route.js +9 -1
  18. package/dist/modules/ai_assistant/api/tools/route.js.map +2 -2
  19. package/dist/modules/ai_assistant/lib/opencode-client.js +24 -8
  20. package/dist/modules/ai_assistant/lib/opencode-client.js.map +2 -2
  21. package/package.json +4 -4
  22. package/src/frontend/components/DockableChat/DockableChat.tsx +5 -0
  23. package/src/frontend/hooks/useCommandPalette.ts +17 -12
  24. package/src/frontend/hooks/useMcpTools.ts +22 -22
  25. package/src/modules/ai_assistant/api/chat/route.ts +9 -0
  26. package/src/modules/ai_assistant/api/health/route.ts +9 -0
  27. package/src/modules/ai_assistant/api/route/route.ts +9 -0
  28. package/src/modules/ai_assistant/api/settings/route.ts +9 -0
  29. package/src/modules/ai_assistant/api/tools/execute/route.ts +9 -0
  30. package/src/modules/ai_assistant/api/tools/route.ts +9 -0
  31. package/src/modules/ai_assistant/lib/opencode-client.ts +24 -8
@@ -19,6 +19,8 @@ import type {
19
19
  DebugEventType,
20
20
  OpenCodeQuestion,
21
21
  } from '../types'
22
+ import { apiCall } from '@open-mercato/ui/backend/utils/apiCall'
23
+ import { apiFetch } from '@open-mercato/ui/backend/utils/api'
22
24
  import { COMMAND_PALETTE_SHORTCUT, AI_CHAT_SHORTCUT } from '../constants'
23
25
  import { filterTools } from '../utils/toolMatcher'
24
26
  import { useMcpTools } from './useMcpTools'
@@ -497,7 +499,7 @@ export function useCommandPalette(options: UseCommandPaletteOptions) {
497
499
  // Route query using fast model
498
500
  const routeQuery = useCallback(
499
501
  async (query: string): Promise<RouteResult> => {
500
- const response = await fetch('/api/ai_assistant/route', {
502
+ const { ok, result, status } = await apiCall<RouteResult>('/api/ai_assistant/route', {
501
503
  method: 'POST',
502
504
  headers: { 'Content-Type': 'application/json' },
503
505
  body: JSON.stringify({
@@ -509,11 +511,15 @@ export function useCommandPalette(options: UseCommandPaletteOptions) {
509
511
  }),
510
512
  })
511
513
 
512
- if (!response.ok) {
513
- throw new Error(`Routing failed: ${response.status}`)
514
+ if (!ok) {
515
+ throw new Error(`Routing failed: ${status}`)
514
516
  }
515
517
 
516
- return response.json()
518
+ if (!result) {
519
+ throw new Error('Routing returned empty response')
520
+ }
521
+
522
+ return result
517
523
  },
518
524
  [tools]
519
525
  )
@@ -547,7 +553,7 @@ export function useCommandPalette(options: UseCommandPaletteOptions) {
547
553
  const controller = new AbortController()
548
554
  currentStreamController.current = controller
549
555
 
550
- const response = await fetch('/api/ai_assistant/chat', {
556
+ const response = await apiFetch('/api/ai_assistant/chat', {
551
557
  method: 'POST',
552
558
  headers: { 'Content-Type': 'application/json' },
553
559
  body: JSON.stringify({
@@ -765,7 +771,7 @@ export function useCommandPalette(options: UseCommandPaletteOptions) {
765
771
  }
766
772
  setMessages([userMessage])
767
773
 
768
- const response = await fetch('/api/ai_assistant/chat', {
774
+ const response = await apiFetch('/api/ai_assistant/chat', {
769
775
  method: 'POST',
770
776
  headers: { 'Content-Type': 'application/json' },
771
777
  body: JSON.stringify({
@@ -978,7 +984,7 @@ export function useCommandPalette(options: UseCommandPaletteOptions) {
978
984
  currentStreamController.current = controller
979
985
 
980
986
  // Send to chat API with OpenCode session for context persistence
981
- const response = await fetch('/api/ai_assistant/chat', {
987
+ const response = await apiFetch('/api/ai_assistant/chat', {
982
988
  method: 'POST',
983
989
  headers: { 'Content-Type': 'application/json' },
984
990
  body: JSON.stringify({
@@ -1271,7 +1277,7 @@ export function useCommandPalette(options: UseCommandPaletteOptions) {
1271
1277
  try {
1272
1278
  // Send answer as simple POST - the original SSE stream will receive the follow-up
1273
1279
  const sessionId = pendingQuestion.sessionID
1274
- const response = await fetch('/api/ai_assistant/chat', {
1280
+ const { ok, result, status } = await apiCall<{ error?: string }>('/api/ai_assistant/chat', {
1275
1281
  method: 'POST',
1276
1282
  headers: { 'Content-Type': 'application/json' },
1277
1283
  body: JSON.stringify({
@@ -1283,9 +1289,8 @@ export function useCommandPalette(options: UseCommandPaletteOptions) {
1283
1289
  }),
1284
1290
  })
1285
1291
 
1286
- if (!response.ok) {
1287
- const errorData = await response.json().catch(() => ({}))
1288
- throw new Error(errorData.error || `Answer request failed: ${response.status}`)
1292
+ if (!ok) {
1293
+ throw new Error(result?.error || `Answer request failed: ${status}`)
1289
1294
  }
1290
1295
 
1291
1296
  // Answer sent successfully - the original stream will handle the response
@@ -1325,7 +1330,7 @@ export function useCommandPalette(options: UseCommandPaletteOptions) {
1325
1330
 
1326
1331
  try {
1327
1332
  // Send to chat API
1328
- const response = await fetch('/api/ai_assistant/chat', {
1333
+ const response = await apiFetch('/api/ai_assistant/chat', {
1329
1334
  method: 'POST',
1330
1335
  headers: { 'Content-Type': 'application/json' },
1331
1336
  body: JSON.stringify({
@@ -1,6 +1,7 @@
1
1
  'use client'
2
2
 
3
3
  import { useState, useCallback, useEffect } from 'react'
4
+ import { apiCall } from '@open-mercato/ui/backend/utils/apiCall'
4
5
  import type { ToolInfo, ToolExecutionResult } from '../types'
5
6
 
6
7
  export function useMcpTools() {
@@ -12,16 +13,14 @@ export function useMcpTools() {
12
13
  setIsLoading(true)
13
14
  setError(null)
14
15
  try {
15
- const response = await fetch('/api/ai_assistant/tools', {
16
- headers: {
17
- 'x-om-forbidden-redirect': '0',
18
- },
19
- })
20
- if (!response.ok) {
21
- throw new Error(`Failed to fetch tools: ${response.status}`)
16
+ const { ok, result, status } = await apiCall<{ tools: ToolInfo[] }>(
17
+ '/api/ai_assistant/tools',
18
+ { headers: { 'x-om-forbidden-redirect': '0' } }
19
+ )
20
+ if (!ok) {
21
+ throw new Error(`Failed to fetch tools: ${status}`)
22
22
  }
23
- const data = await response.json()
24
- setTools(data.tools || [])
23
+ setTools(result?.tools || [])
25
24
  } catch (err) {
26
25
  setError(err instanceof Error ? err.message : 'Failed to load tools')
27
26
  setTools([])
@@ -33,27 +32,28 @@ export function useMcpTools() {
33
32
  const executeTool = useCallback(
34
33
  async (toolName: string, args: Record<string, unknown> = {}): Promise<ToolExecutionResult> => {
35
34
  try {
36
- const response = await fetch('/api/ai_assistant/tools/execute', {
37
- method: 'POST',
38
- headers: {
39
- 'Content-Type': 'application/json',
40
- 'x-om-forbidden-redirect': '0',
41
- },
42
- body: JSON.stringify({ toolName, args }),
43
- })
44
-
45
- const data = await response.json()
35
+ const { ok, result, status } = await apiCall<{ result: unknown; error?: string }>(
36
+ '/api/ai_assistant/tools/execute',
37
+ {
38
+ method: 'POST',
39
+ headers: {
40
+ 'Content-Type': 'application/json',
41
+ 'x-om-forbidden-redirect': '0',
42
+ },
43
+ body: JSON.stringify({ toolName, args }),
44
+ }
45
+ )
46
46
 
47
- if (!response.ok) {
47
+ if (!ok) {
48
48
  return {
49
49
  success: false,
50
- error: data.error || `Tool execution failed: ${response.status}`,
50
+ error: result?.error || `Tool execution failed: ${status}`,
51
51
  }
52
52
  }
53
53
 
54
54
  return {
55
55
  success: true,
56
- result: data.result,
56
+ result: result?.result,
57
57
  }
58
58
  } catch (err) {
59
59
  return {
@@ -1,4 +1,5 @@
1
1
  import { NextResponse, type NextRequest } from 'next/server'
2
+ import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
2
3
  import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
3
4
  import {
4
5
  handleOpenCodeMessageStreaming,
@@ -119,6 +120,14 @@ RESPONSE RULES:
119
120
  - Only ask for confirmation before create/update/delete operations.
120
121
  `.trim()
121
122
 
123
+ export const openApi: OpenApiRouteDoc = {
124
+ tag: 'AI Assistant',
125
+ summary: 'AI chat',
126
+ methods: {
127
+ POST: { summary: 'Send message to AI agent via SSE stream' },
128
+ },
129
+ }
130
+
122
131
  export const metadata = {
123
132
  POST: { requireAuth: true, requireFeatures: ['ai_assistant.view'] },
124
133
  }
@@ -1,7 +1,16 @@
1
1
  import { NextResponse, type NextRequest } from 'next/server'
2
+ import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
2
3
  import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
3
4
  import { handleOpenCodeHealth } from '../../lib/opencode-handlers'
4
5
 
6
+ export const openApi: OpenApiRouteDoc = {
7
+ tag: 'AI Assistant',
8
+ summary: 'AI assistant health check',
9
+ methods: {
10
+ GET: { summary: 'Check OpenCode and MCP connection status' },
11
+ },
12
+ }
13
+
5
14
  export const metadata = {
6
15
  GET: { requireAuth: true, requireFeatures: ['ai_assistant.view'] },
7
16
  }
@@ -1,4 +1,5 @@
1
1
  import { NextResponse, type NextRequest } from 'next/server'
2
+ import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
2
3
  import { generateObject } from '../../lib/ai-sdk'
3
4
  import {
4
5
  createOpenAI,
@@ -19,6 +20,14 @@ import {
19
20
  type ChatProviderId,
20
21
  } from '../../lib/chat-config'
21
22
 
23
+ export const openApi: OpenApiRouteDoc = {
24
+ tag: 'AI Assistant',
25
+ summary: 'AI query routing',
26
+ methods: {
27
+ POST: { summary: 'Route user query to appropriate AI handler' },
28
+ },
29
+ }
30
+
22
31
  export const metadata = {
23
32
  POST: { requireAuth: true, requireFeatures: ['ai_assistant.view'] },
24
33
  }
@@ -1,4 +1,5 @@
1
1
  import { NextResponse, type NextRequest } from 'next/server'
2
+ import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
2
3
  import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
3
4
  import {
4
5
  OPEN_CODE_PROVIDER_IDS,
@@ -9,6 +10,14 @@ import {
9
10
  resolveOpenCodeProviderId,
10
11
  } from '@open-mercato/shared/lib/ai/opencode-provider'
11
12
 
13
+ export const openApi: OpenApiRouteDoc = {
14
+ tag: 'AI Assistant',
15
+ summary: 'AI assistant settings',
16
+ methods: {
17
+ GET: { summary: 'Get AI provider configuration' },
18
+ },
19
+ }
20
+
12
21
  export const metadata = {
13
22
  GET: { requireAuth: true, requireFeatures: ['ai_assistant.view'] },
14
23
  }
@@ -1,4 +1,5 @@
1
1
  import { NextResponse, type NextRequest } from 'next/server'
2
+ import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
2
3
  import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
3
4
  import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
4
5
  import { executeTool } from '../../../lib/tool-executor'
@@ -6,6 +7,14 @@ import { loadAllModuleTools } from '../../../lib/tool-loader'
6
7
  import type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'
7
8
  import type { McpToolContext } from '../../../lib/types'
8
9
 
10
+ export const openApi: OpenApiRouteDoc = {
11
+ tag: 'AI Assistant',
12
+ summary: 'Execute AI tool',
13
+ methods: {
14
+ POST: { summary: 'Execute a specific MCP tool by name' },
15
+ },
16
+ }
17
+
9
18
  export const metadata = {
10
19
  POST: { requireAuth: true, requireFeatures: ['ai_assistant.view'] },
11
20
  }
@@ -1,4 +1,5 @@
1
1
  import { NextResponse, type NextRequest } from 'next/server'
2
+ import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
2
3
  import { zodToJsonSchema } from 'zod-to-json-schema'
3
4
  import { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'
4
5
  import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
@@ -7,6 +8,14 @@ import { loadAllModuleTools } from '../../lib/tool-loader'
7
8
  import { hasRequiredFeatures } from '../../lib/auth'
8
9
  import type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'
9
10
 
11
+ export const openApi: OpenApiRouteDoc = {
12
+ tag: 'AI Assistant',
13
+ summary: 'List AI tools',
14
+ methods: {
15
+ GET: { summary: 'List available MCP tools filtered by user permissions' },
16
+ },
17
+ }
18
+
10
19
  export const metadata = {
11
20
  GET: { requireAuth: true, requireFeatures: ['ai_assistant.view'] },
12
21
  }
@@ -4,6 +4,7 @@
4
4
  * Client for communicating with OpenCode server running in headless mode.
5
5
  * OpenCode is used as an AI agent that can execute MCP tools.
6
6
  */
7
+ import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
7
8
 
8
9
  export type OpenCodeClientConfig = {
9
10
  baseUrl: string
@@ -195,7 +196,9 @@ export class OpenCodeClient {
195
196
  throw new Error(`Health check failed: ${res.status}`)
196
197
  }
197
198
 
198
- return res.json()
199
+ const data = await readJsonSafe<OpenCodeHealth>(res, null)
200
+ if (!data) throw new Error('Health check returned invalid JSON response')
201
+ return data
199
202
  }
200
203
 
201
204
  /**
@@ -210,7 +213,9 @@ export class OpenCodeClient {
210
213
  throw new Error(`MCP status check failed: ${res.status}`)
211
214
  }
212
215
 
213
- return res.json()
216
+ const data = await readJsonSafe<OpenCodeMcpStatus>(res, null)
217
+ if (!data) throw new Error('MCP status check returned invalid JSON response')
218
+ return data
214
219
  }
215
220
 
216
221
  /**
@@ -228,7 +233,9 @@ export class OpenCodeClient {
228
233
  throw new Error(`Failed to create session: ${error}`)
229
234
  }
230
235
 
231
- return res.json()
236
+ const data = await readJsonSafe<OpenCodeSession>(res, null)
237
+ if (!data) throw new Error('Create session returned invalid JSON response')
238
+ return data
232
239
  }
233
240
 
234
241
  /**
@@ -243,7 +250,9 @@ export class OpenCodeClient {
243
250
  throw new Error(`Failed to get session: ${res.status}`)
244
251
  }
245
252
 
246
- return res.json()
253
+ const data = await readJsonSafe<OpenCodeSession>(res, null)
254
+ if (!data) throw new Error('Get session returned invalid JSON response')
255
+ return data
247
256
  }
248
257
 
249
258
  /**
@@ -275,7 +284,9 @@ export class OpenCodeClient {
275
284
  throw new Error(`Failed to send message: ${error}`)
276
285
  }
277
286
 
278
- return res.json()
287
+ const data = await readJsonSafe<OpenCodeMessage>(res, null)
288
+ if (!data) throw new Error('Send message returned invalid JSON response')
289
+ return data
279
290
  }
280
291
 
281
292
  /**
@@ -305,7 +316,9 @@ export class OpenCodeClient {
305
316
  throw new Error(`Failed to get config: ${res.status}`)
306
317
  }
307
318
 
308
- return res.json()
319
+ const data = await readJsonSafe<Record<string, unknown>>(res, null)
320
+ if (!data) throw new Error('Get config returned invalid JSON response')
321
+ return data
309
322
  }
310
323
 
311
324
  /**
@@ -320,7 +333,9 @@ export class OpenCodeClient {
320
333
  throw new Error(`Failed to get questions: ${res.status}`)
321
334
  }
322
335
 
323
- return res.json()
336
+ const data = await readJsonSafe<OpenCodeQuestion[]>(res, null)
337
+ if (!data) throw new Error('Get questions returned invalid JSON response')
338
+ return data
324
339
  }
325
340
 
326
341
  /**
@@ -395,7 +410,8 @@ export class OpenCodeClient {
395
410
  if (res.ok) {
396
411
  const contentType = res.headers.get('content-type')
397
412
  if (contentType && contentType.includes('application/json')) {
398
- return res.json()
413
+ const data = await readJsonSafe<{ status: string; questionId?: string }>(res, null)
414
+ if (data) return data
399
415
  }
400
416
  }
401
417
  } catch {