@open-mercato/ai-assistant 0.4.2-canary-c02407ff85

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 (193) hide show
  1. package/AGENTS.md +1090 -0
  2. package/README.md +607 -0
  3. package/build.mjs +92 -0
  4. package/dist/di.js +8 -0
  5. package/dist/di.js.map +7 -0
  6. package/dist/frontend/components/CommandPalette/CommandFooter.js +80 -0
  7. package/dist/frontend/components/CommandPalette/CommandFooter.js.map +7 -0
  8. package/dist/frontend/components/CommandPalette/CommandHeader.js +53 -0
  9. package/dist/frontend/components/CommandPalette/CommandHeader.js.map +7 -0
  10. package/dist/frontend/components/CommandPalette/CommandInput.js +29 -0
  11. package/dist/frontend/components/CommandPalette/CommandInput.js.map +7 -0
  12. package/dist/frontend/components/CommandPalette/CommandItem.js +92 -0
  13. package/dist/frontend/components/CommandPalette/CommandItem.js.map +7 -0
  14. package/dist/frontend/components/CommandPalette/CommandPalette.js +244 -0
  15. package/dist/frontend/components/CommandPalette/CommandPalette.js.map +7 -0
  16. package/dist/frontend/components/CommandPalette/CommandPaletteProvider.js +42 -0
  17. package/dist/frontend/components/CommandPalette/CommandPaletteProvider.js.map +7 -0
  18. package/dist/frontend/components/CommandPalette/CommandPaletteWrapper.js +18 -0
  19. package/dist/frontend/components/CommandPalette/CommandPaletteWrapper.js.map +7 -0
  20. package/dist/frontend/components/CommandPalette/DebugPanel.js +215 -0
  21. package/dist/frontend/components/CommandPalette/DebugPanel.js.map +7 -0
  22. package/dist/frontend/components/CommandPalette/MessageBubble.js +64 -0
  23. package/dist/frontend/components/CommandPalette/MessageBubble.js.map +7 -0
  24. package/dist/frontend/components/CommandPalette/ToolCallConfirmation.js +91 -0
  25. package/dist/frontend/components/CommandPalette/ToolCallConfirmation.js.map +7 -0
  26. package/dist/frontend/components/CommandPalette/ToolCallDisplay.js +47 -0
  27. package/dist/frontend/components/CommandPalette/ToolCallDisplay.js.map +7 -0
  28. package/dist/frontend/components/CommandPalette/ToolChatPage.js +74 -0
  29. package/dist/frontend/components/CommandPalette/ToolChatPage.js.map +7 -0
  30. package/dist/frontend/components/CommandPalette/index.js +28 -0
  31. package/dist/frontend/components/CommandPalette/index.js.map +7 -0
  32. package/dist/frontend/constants.js +41 -0
  33. package/dist/frontend/constants.js.map +7 -0
  34. package/dist/frontend/hooks/index.js +13 -0
  35. package/dist/frontend/hooks/index.js.map +7 -0
  36. package/dist/frontend/hooks/useCommandPalette.js +1094 -0
  37. package/dist/frontend/hooks/useCommandPalette.js.map +7 -0
  38. package/dist/frontend/hooks/useMcpTools.js +66 -0
  39. package/dist/frontend/hooks/useMcpTools.js.map +7 -0
  40. package/dist/frontend/hooks/usePageContext.js +48 -0
  41. package/dist/frontend/hooks/usePageContext.js.map +7 -0
  42. package/dist/frontend/hooks/useRecentActions.js +56 -0
  43. package/dist/frontend/hooks/useRecentActions.js.map +7 -0
  44. package/dist/frontend/hooks/useRecentTools.js +55 -0
  45. package/dist/frontend/hooks/useRecentTools.js.map +7 -0
  46. package/dist/frontend/index.js +35 -0
  47. package/dist/frontend/index.js.map +7 -0
  48. package/dist/frontend/types.js +1 -0
  49. package/dist/frontend/types.js.map +7 -0
  50. package/dist/frontend/utils/index.js +7 -0
  51. package/dist/frontend/utils/index.js.map +7 -0
  52. package/dist/frontend/utils/toolMatcher.js +95 -0
  53. package/dist/frontend/utils/toolMatcher.js.map +7 -0
  54. package/dist/index.js +57 -0
  55. package/dist/index.js.map +7 -0
  56. package/dist/modules/ai_assistant/acl.js +14 -0
  57. package/dist/modules/ai_assistant/acl.js.map +7 -0
  58. package/dist/modules/ai_assistant/api/chat/route.js +152 -0
  59. package/dist/modules/ai_assistant/api/chat/route.js.map +7 -0
  60. package/dist/modules/ai_assistant/api/health/route.js +27 -0
  61. package/dist/modules/ai_assistant/api/health/route.js.map +7 -0
  62. package/dist/modules/ai_assistant/api/route/route.js +123 -0
  63. package/dist/modules/ai_assistant/api/route/route.js.map +7 -0
  64. package/dist/modules/ai_assistant/api/settings/route.js +60 -0
  65. package/dist/modules/ai_assistant/api/settings/route.js.map +7 -0
  66. package/dist/modules/ai_assistant/api/tools/execute/route.js +58 -0
  67. package/dist/modules/ai_assistant/api/tools/execute/route.js.map +7 -0
  68. package/dist/modules/ai_assistant/api/tools/route.js +48 -0
  69. package/dist/modules/ai_assistant/api/tools/route.js.map +7 -0
  70. package/dist/modules/ai_assistant/backend/config/ai-assistant/page.js +10 -0
  71. package/dist/modules/ai_assistant/backend/config/ai-assistant/page.js.map +7 -0
  72. package/dist/modules/ai_assistant/backend/config/ai-assistant/page.meta.js +28 -0
  73. package/dist/modules/ai_assistant/backend/config/ai-assistant/page.meta.js.map +7 -0
  74. package/dist/modules/ai_assistant/cli.js +192 -0
  75. package/dist/modules/ai_assistant/cli.js.map +7 -0
  76. package/dist/modules/ai_assistant/di.js +11 -0
  77. package/dist/modules/ai_assistant/di.js.map +7 -0
  78. package/dist/modules/ai_assistant/frontend/components/AiAssistantSettingsPageClient.js +257 -0
  79. package/dist/modules/ai_assistant/frontend/components/AiAssistantSettingsPageClient.js.map +7 -0
  80. package/dist/modules/ai_assistant/index.js +13 -0
  81. package/dist/modules/ai_assistant/index.js.map +7 -0
  82. package/dist/modules/ai_assistant/lib/ai-sdk.js +13 -0
  83. package/dist/modules/ai_assistant/lib/ai-sdk.js.map +7 -0
  84. package/dist/modules/ai_assistant/lib/api-discovery-tools.js +249 -0
  85. package/dist/modules/ai_assistant/lib/api-discovery-tools.js.map +7 -0
  86. package/dist/modules/ai_assistant/lib/api-endpoint-index-config.js +177 -0
  87. package/dist/modules/ai_assistant/lib/api-endpoint-index-config.js.map +7 -0
  88. package/dist/modules/ai_assistant/lib/api-endpoint-index.js +210 -0
  89. package/dist/modules/ai_assistant/lib/api-endpoint-index.js.map +7 -0
  90. package/dist/modules/ai_assistant/lib/auth.js +87 -0
  91. package/dist/modules/ai_assistant/lib/auth.js.map +7 -0
  92. package/dist/modules/ai_assistant/lib/chat-config.js +117 -0
  93. package/dist/modules/ai_assistant/lib/chat-config.js.map +7 -0
  94. package/dist/modules/ai_assistant/lib/client-factory.js +60 -0
  95. package/dist/modules/ai_assistant/lib/client-factory.js.map +7 -0
  96. package/dist/modules/ai_assistant/lib/http-server.js +367 -0
  97. package/dist/modules/ai_assistant/lib/http-server.js.map +7 -0
  98. package/dist/modules/ai_assistant/lib/in-process-client.js +126 -0
  99. package/dist/modules/ai_assistant/lib/in-process-client.js.map +7 -0
  100. package/dist/modules/ai_assistant/lib/mcp-client.js +146 -0
  101. package/dist/modules/ai_assistant/lib/mcp-client.js.map +7 -0
  102. package/dist/modules/ai_assistant/lib/mcp-dev-server.js +283 -0
  103. package/dist/modules/ai_assistant/lib/mcp-dev-server.js.map +7 -0
  104. package/dist/modules/ai_assistant/lib/mcp-server-config.js +160 -0
  105. package/dist/modules/ai_assistant/lib/mcp-server-config.js.map +7 -0
  106. package/dist/modules/ai_assistant/lib/mcp-server.js +156 -0
  107. package/dist/modules/ai_assistant/lib/mcp-server.js.map +7 -0
  108. package/dist/modules/ai_assistant/lib/mcp-tool-adapter.js +44 -0
  109. package/dist/modules/ai_assistant/lib/mcp-tool-adapter.js.map +7 -0
  110. package/dist/modules/ai_assistant/lib/opencode-client.js +247 -0
  111. package/dist/modules/ai_assistant/lib/opencode-client.js.map +7 -0
  112. package/dist/modules/ai_assistant/lib/opencode-handlers.js +398 -0
  113. package/dist/modules/ai_assistant/lib/opencode-handlers.js.map +7 -0
  114. package/dist/modules/ai_assistant/lib/schema-utils.js +94 -0
  115. package/dist/modules/ai_assistant/lib/schema-utils.js.map +7 -0
  116. package/dist/modules/ai_assistant/lib/tool-executor.js +55 -0
  117. package/dist/modules/ai_assistant/lib/tool-executor.js.map +7 -0
  118. package/dist/modules/ai_assistant/lib/tool-index-config.js +125 -0
  119. package/dist/modules/ai_assistant/lib/tool-index-config.js.map +7 -0
  120. package/dist/modules/ai_assistant/lib/tool-loader.js +88 -0
  121. package/dist/modules/ai_assistant/lib/tool-loader.js.map +7 -0
  122. package/dist/modules/ai_assistant/lib/tool-registry.js +65 -0
  123. package/dist/modules/ai_assistant/lib/tool-registry.js.map +7 -0
  124. package/dist/modules/ai_assistant/lib/tool-search.js +192 -0
  125. package/dist/modules/ai_assistant/lib/tool-search.js.map +7 -0
  126. package/dist/modules/ai_assistant/lib/types.js +1 -0
  127. package/dist/modules/ai_assistant/lib/types.js.map +7 -0
  128. package/package.json +108 -0
  129. package/src/di.ts +11 -0
  130. package/src/frontend/components/CommandPalette/CommandFooter.tsx +113 -0
  131. package/src/frontend/components/CommandPalette/CommandHeader.tsx +76 -0
  132. package/src/frontend/components/CommandPalette/CommandInput.tsx +50 -0
  133. package/src/frontend/components/CommandPalette/CommandItem.tsx +111 -0
  134. package/src/frontend/components/CommandPalette/CommandPalette.tsx +276 -0
  135. package/src/frontend/components/CommandPalette/CommandPaletteProvider.tsx +60 -0
  136. package/src/frontend/components/CommandPalette/CommandPaletteWrapper.tsx +21 -0
  137. package/src/frontend/components/CommandPalette/DebugPanel.tsx +257 -0
  138. package/src/frontend/components/CommandPalette/MessageBubble.tsx +73 -0
  139. package/src/frontend/components/CommandPalette/ToolCallConfirmation.tsx +130 -0
  140. package/src/frontend/components/CommandPalette/ToolCallDisplay.tsx +57 -0
  141. package/src/frontend/components/CommandPalette/ToolChatPage.tsx +125 -0
  142. package/src/frontend/components/CommandPalette/index.ts +14 -0
  143. package/src/frontend/constants.ts +35 -0
  144. package/src/frontend/hooks/index.ts +5 -0
  145. package/src/frontend/hooks/useCommandPalette.ts +1389 -0
  146. package/src/frontend/hooks/useMcpTools.ts +73 -0
  147. package/src/frontend/hooks/usePageContext.ts +61 -0
  148. package/src/frontend/hooks/useRecentActions.ts +64 -0
  149. package/src/frontend/hooks/useRecentTools.ts +69 -0
  150. package/src/frontend/index.ts +39 -0
  151. package/src/frontend/types.ts +260 -0
  152. package/src/frontend/utils/index.ts +1 -0
  153. package/src/frontend/utils/toolMatcher.ts +127 -0
  154. package/src/index.ts +92 -0
  155. package/src/modules/ai_assistant/acl.ts +10 -0
  156. package/src/modules/ai_assistant/api/chat/route.ts +213 -0
  157. package/src/modules/ai_assistant/api/health/route.ts +30 -0
  158. package/src/modules/ai_assistant/api/route/route.ts +149 -0
  159. package/src/modules/ai_assistant/api/settings/route.ts +73 -0
  160. package/src/modules/ai_assistant/api/tools/execute/route.ts +71 -0
  161. package/src/modules/ai_assistant/api/tools/route.ts +57 -0
  162. package/src/modules/ai_assistant/backend/config/ai-assistant/page.meta.ts +26 -0
  163. package/src/modules/ai_assistant/backend/config/ai-assistant/page.tsx +12 -0
  164. package/src/modules/ai_assistant/cli.ts +233 -0
  165. package/src/modules/ai_assistant/di.ts +9 -0
  166. package/src/modules/ai_assistant/frontend/components/AiAssistantSettingsPageClient.tsx +418 -0
  167. package/src/modules/ai_assistant/index.ts +11 -0
  168. package/src/modules/ai_assistant/lib/ai-sdk.ts +5 -0
  169. package/src/modules/ai_assistant/lib/api-discovery-tools.ts +334 -0
  170. package/src/modules/ai_assistant/lib/api-endpoint-index-config.ts +243 -0
  171. package/src/modules/ai_assistant/lib/api-endpoint-index.ts +381 -0
  172. package/src/modules/ai_assistant/lib/auth.ts +185 -0
  173. package/src/modules/ai_assistant/lib/chat-config.ts +152 -0
  174. package/src/modules/ai_assistant/lib/client-factory.ts +130 -0
  175. package/src/modules/ai_assistant/lib/http-server.ts +498 -0
  176. package/src/modules/ai_assistant/lib/in-process-client.ts +205 -0
  177. package/src/modules/ai_assistant/lib/mcp-client.ts +221 -0
  178. package/src/modules/ai_assistant/lib/mcp-dev-server.ts +373 -0
  179. package/src/modules/ai_assistant/lib/mcp-server-config.ts +287 -0
  180. package/src/modules/ai_assistant/lib/mcp-server.ts +214 -0
  181. package/src/modules/ai_assistant/lib/mcp-tool-adapter.ts +76 -0
  182. package/src/modules/ai_assistant/lib/opencode-client.ts +426 -0
  183. package/src/modules/ai_assistant/lib/opencode-handlers.ts +676 -0
  184. package/src/modules/ai_assistant/lib/schema-utils.ts +142 -0
  185. package/src/modules/ai_assistant/lib/tool-executor.ts +71 -0
  186. package/src/modules/ai_assistant/lib/tool-index-config.ts +178 -0
  187. package/src/modules/ai_assistant/lib/tool-loader.ts +149 -0
  188. package/src/modules/ai_assistant/lib/tool-registry.ts +114 -0
  189. package/src/modules/ai_assistant/lib/tool-search.ts +308 -0
  190. package/src/modules/ai_assistant/lib/types.ts +147 -0
  191. package/test-schema.ts +37 -0
  192. package/tsconfig.json +10 -0
  193. package/watch.mjs +6 -0
package/AGENTS.md ADDED
@@ -0,0 +1,1090 @@
1
+ # AI Assistant Module - Agent Guide
2
+
3
+ > **IMPORTANT**: This file must be updated with every major change to this module. When implementing new features, modifying architecture, or changing key interfaces, update the relevant sections of this document to keep it accurate for future agents.
4
+
5
+ ## Overview
6
+
7
+ The `ai-assistant` module provides AI-powered assistance capabilities for Open Mercato. It includes:
8
+
9
+ 1. **OpenCode Agent** - AI backend that processes natural language and executes tools
10
+ 2. **MCP (Model Context Protocol) Server** - Exposes tools to OpenCode via HTTP
11
+ 3. **API Discovery Tools** - Meta-tools for dynamic API access (replaces 600+ individual tools)
12
+ 4. **Command Palette UI** - Raycast-style interface for users to interact with AI
13
+ 5. **Hybrid Tool Discovery** - Combines search-based discovery with OpenAPI introspection
14
+
15
+ ## Architecture
16
+
17
+ ```
18
+ ┌─────────────────────────────────────────────────────────────────────────────┐
19
+ │ AI ASSISTANT MODULE │
20
+ ├─────────────────────────────────────────────────────────────────────────────┤
21
+ │ │
22
+ │ ┌─────────────────────────────────────────────────────────────────────┐ │
23
+ │ │ Frontend (Command Palette) │ │
24
+ │ │ • Raycast-style dialog (Cmd+K) │ │
25
+ │ │ • Phase-based navigation (idle → routing → chatting → executing) │ │
26
+ │ │ • "Agent is working..." indicator │ │
27
+ │ │ • Debug panel for tool calls │ │
28
+ │ └─────────────────────────────────────────────────────────────────────┘ │
29
+ │ │ │
30
+ │ ▼ │
31
+ │ ┌─────────────────────────────────────────────────────────────────────┐ │
32
+ │ │ POST /api/chat (SSE) │ │
33
+ │ │ • Receives user message │ │
34
+ │ │ • Emits 'thinking' event immediately │ │
35
+ │ │ • Calls OpenCode → waits for response │ │
36
+ │ │ • Emits 'text' and 'done' events │ │
37
+ │ │ • Maintains session ID for conversation context │ │
38
+ │ └─────────────────────────────────────────────────────────────────────┘ │
39
+ │ │ │
40
+ │ ▼ │
41
+ │ ┌─────────────────────────────────────────────────────────────────────┐ │
42
+ │ │ OpenCode Client │ │
43
+ │ │ • handleOpenCodeMessage() - Send message, get response │ │
44
+ │ │ • extractTextFromResponse() - Parse response text │ │
45
+ │ │ • Session management (create, resume) │ │
46
+ │ └─────────────────────────────────────────────────────────────────────┘ │
47
+ │ │ │
48
+ │ ▼ │
49
+ │ ┌─────────────────────────────────────────────────────────────────────┐ │
50
+ │ │ OpenCode Server (Docker :4096) │ │
51
+ │ │ • Go-based AI agent in headless mode │ │
52
+ │ │ • Connects to MCP server for tools │ │
53
+ │ │ • Executes multi-step tool workflows │ │
54
+ │ │ • Uses Anthropic Claude as LLM │ │
55
+ │ └─────────────────────────────────────────────────────────────────────┘ │
56
+ │ │ │
57
+ │ ▼ │
58
+ │ ┌─────────────────────────────────────────────────────────────────────┐ │
59
+ │ │ MCP HTTP Server (:3001) │ │
60
+ │ │ • Exposes 10 tools to OpenCode │ │
61
+ │ │ • API discovery tools (api_discover, api_execute, api_schema) │ │
62
+ │ │ • Search tools (search, search_status, etc.) │ │
63
+ │ │ • Authentication via x-api-key header │ │
64
+ │ └─────────────────────────────────────────────────────────────────────┘ │
65
+ │ │
66
+ └─────────────────────────────────────────────────────────────────────────────┘
67
+ ```
68
+
69
+ ## Directory Structure
70
+
71
+ ```
72
+ packages/ai-assistant/
73
+ ├── src/
74
+ │ ├── index.ts # Package exports
75
+ │ ├── di.ts # Dependency injection setup
76
+ │ ├── types.ts # Shared TypeScript types
77
+ │ │
78
+ │ ├── modules/ai_assistant/
79
+ │ │ ├── index.ts # Module exports
80
+ │ │ ├── acl.ts # Permission definitions
81
+ │ │ ├── cli.ts # CLI commands (mcp:serve, mcp:serve-http)
82
+ │ │ ├── di.ts # Module DI container
83
+ │ │ │
84
+ │ │ ├── lib/
85
+ │ │ │ ├── opencode-client.ts # OpenCode server client
86
+ │ │ │ ├── opencode-handlers.ts # Request handlers for OpenCode
87
+ │ │ │ ├── api-discovery-tools.ts # api_discover, api_execute, api_schema
88
+ │ │ │ ├── api-endpoint-index.ts # OpenAPI endpoint indexing
89
+ │ │ │ ├── http-server.ts # MCP HTTP server implementation
90
+ │ │ │ ├── mcp-server.ts # MCP stdio server implementation
91
+ │ │ │ ├── tool-registry.ts # Global tool registration
92
+ │ │ │ ├── tool-executor.ts # Tool execution logic
93
+ │ │ │ ├── tool-loader.ts # Discovers tools from modules
94
+ │ │ │ ├── mcp-tool-adapter.ts # Converts MCP tools to AI SDK format
95
+ │ │ │ └── types.ts # Module-specific types
96
+ │ │ │
97
+ │ │ ├── frontend/components/
98
+ │ │ │ ├── AiAssistantSettingsPageClient.tsx # Settings page
99
+ │ │ │ └── McpServersSection.tsx # MCP server management UI
100
+ │ │ │
101
+ │ │ └── backend/config/ai-assistant/
102
+ │ │ └── page.tsx # Settings page route
103
+ │ │
104
+ │ └── frontend/
105
+ │ ├── index.ts # Frontend exports
106
+ │ ├── types.ts # Frontend TypeScript types
107
+ │ ├── constants.ts # UI constants
108
+ │ │
109
+ │ ├── hooks/
110
+ │ │ ├── useCommandPalette.ts # Main command palette state/logic
111
+ │ │ ├── useMcpTools.ts # Tool fetching and execution
112
+ │ │ ├── useRecentTools.ts # Recent tools tracking
113
+ │ │ ├── useRecentActions.ts # Recent actions tracking
114
+ │ │ └── usePageContext.ts # Page context detection
115
+ │ │
116
+ │ └── components/CommandPalette/
117
+ │ ├── CommandPalette.tsx # Main component
118
+ │ ├── CommandPaletteProvider.tsx # Context provider
119
+ │ ├── CommandHeader.tsx # Back button + phase info
120
+ │ ├── CommandFooter.tsx # Connection status + debug toggle
121
+ │ ├── CommandInput.tsx # Search input
122
+ │ ├── ToolChatPage.tsx # Chat UI with thinking indicator
123
+ │ ├── ToolCallConfirmation.tsx # Tool execution confirmation
124
+ │ ├── MessageBubble.tsx # Chat message display
125
+ │ ├── DebugPanel.tsx # Debug events viewer
126
+ │ └── ...
127
+ ```
128
+
129
+ ## Key Concepts
130
+
131
+ ### 1. OpenCode as AI Backend
132
+
133
+ OpenCode is a Go-based AI agent that runs in headless mode. It:
134
+ - Processes natural language requests
135
+ - Connects to MCP servers for tool access
136
+ - Executes multi-step workflows autonomously
137
+ - Maintains conversation sessions
138
+
139
+ **Configuration** (`opencode.json` in Docker):
140
+ ```json
141
+ {
142
+ "mcp": {
143
+ "open-mercato": {
144
+ "type": "sse",
145
+ "url": "http://host.docker.internal:3001/mcp",
146
+ "headers": {
147
+ "x-api-key": "omk_xxx..."
148
+ }
149
+ }
150
+ }
151
+ }
152
+ ```
153
+
154
+ ### 2. API Discovery Tools
155
+
156
+ Instead of 600+ individual tools, we expose 3 meta-tools:
157
+
158
+ | Tool | Description |
159
+ |------|-------------|
160
+ | `api_discover` | Search for APIs by keyword, module, or HTTP method |
161
+ | `api_schema` | Get detailed schema for a specific endpoint |
162
+ | `api_execute` | Execute an API call with parameters |
163
+
164
+ **Example workflow**:
165
+ 1. Agent receives: "Find all customers in New York"
166
+ 2. Agent calls `api_discover("customers search")`
167
+ 3. Agent calls `api_schema("/api/v1/customers")` to see parameters
168
+ 4. Agent calls `api_execute({ method: "GET", path: "/api/v1/customers", query: { city: "New York" } })`
169
+
170
+ ### 3. Hybrid Tool Discovery
171
+
172
+ Tools are discovered through two sources:
173
+
174
+ 1. **Search-based**: Semantic search over tool descriptions
175
+ 2. **OpenAPI-based**: Direct introspection of API endpoints
176
+
177
+ The `api_discover` tool combines both for comprehensive results.
178
+
179
+ ### 4. Chat Flow (Frontend → OpenCode)
180
+
181
+ ```
182
+ User types in Command Palette
183
+
184
+
185
+ POST /api/chat { messages, sessionId }
186
+
187
+ ├── Emit SSE: { type: 'thinking' }
188
+
189
+
190
+ handleOpenCodeMessage({ message, sessionId })
191
+
192
+ ├── Create/resume OpenCode session
193
+ ├── Send message to OpenCode
194
+ ├── OpenCode may call MCP tools
195
+ ├── Wait for response
196
+
197
+
198
+ extractTextFromResponse(result)
199
+
200
+ ├── Emit SSE: { type: 'text', content: '...' }
201
+ ├── Emit SSE: { type: 'done', sessionId: '...' }
202
+
203
+
204
+ Frontend displays response
205
+ ```
206
+
207
+ ### 5. Session Management
208
+
209
+ OpenCode maintains conversation sessions for context:
210
+
211
+ ```typescript
212
+ // First message creates a session
213
+ const result1 = await handleOpenCodeMessage({
214
+ message: "Search for customers"
215
+ })
216
+ // result1.sessionId = "ses_abc123"
217
+
218
+ // Subsequent messages reuse the session
219
+ const result2 = await handleOpenCodeMessage({
220
+ message: "Now filter by New York",
221
+ sessionId: "ses_abc123" // Continues conversation
222
+ })
223
+ ```
224
+
225
+ ## API Routes
226
+
227
+ | Route | Method | Description |
228
+ |-------|--------|-------------|
229
+ | `/api/chat` | POST | Chat with OpenCode agent (SSE stream) |
230
+ | `/api/tools` | GET | List available tools |
231
+ | `/api/tools/execute` | POST | Execute a specific tool directly |
232
+ | `/api/settings` | GET/POST | AI provider configuration |
233
+ | `/api/mcp-servers` | GET/POST | External MCP server management |
234
+
235
+ ### Chat API Request/Response
236
+
237
+ **Request**:
238
+ ```typescript
239
+ {
240
+ messages: Array<{ role: 'user' | 'assistant'; content: string }>
241
+ sessionId?: string // Optional, for continuing conversation
242
+ }
243
+ ```
244
+
245
+ **SSE Events**:
246
+ ```typescript
247
+ type ChatSSEEvent =
248
+ | { type: 'thinking' } // Agent is processing
249
+ | { type: 'text'; content: string } // Response text
250
+ | { type: 'tool-call'; id: string; toolName: string; args: unknown }
251
+ | { type: 'tool-result'; id: string; toolName: string; result: unknown }
252
+ | { type: 'done'; sessionId?: string } // Complete, with session ID
253
+ | { type: 'error'; error: string } // Error occurred
254
+ ```
255
+
256
+ ## Frontend State Management
257
+
258
+ The command palette uses phases instead of pages:
259
+
260
+ ```typescript
261
+ type PalettePhase =
262
+ | 'idle' // Empty, waiting for input
263
+ | 'routing' // Analyzing intent (fast)
264
+ | 'chatting' // Conversational mode
265
+ | 'confirming' // Waiting for tool confirmation
266
+ | 'executing' // Tool running
267
+
268
+ interface CommandPaletteContextValue {
269
+ state: {
270
+ isOpen: boolean
271
+ phase: PalettePhase
272
+ inputValue: string
273
+ isLoading: boolean
274
+ isStreaming: boolean
275
+ connectionStatus: ConnectionStatus
276
+ }
277
+ isThinking: boolean // OpenCode is processing
278
+
279
+ // Actions
280
+ handleSubmit: (query: string) => Promise<void>
281
+ sendAgenticMessage: (content: string) => Promise<void>
282
+ approveToolCall: (id: string) => Promise<void>
283
+ rejectToolCall: (id: string) => void
284
+
285
+ // Debug
286
+ debugEvents: DebugEvent[]
287
+ showDebug: boolean
288
+ setShowDebug: (show: boolean) => void
289
+ }
290
+ ```
291
+
292
+ ## Running the Stack
293
+
294
+ ### MCP Server Modes
295
+
296
+ The module provides two MCP HTTP server modes:
297
+
298
+ #### Development Server (`yarn mcp:dev`)
299
+
300
+ For local development and Claude Code integration. Authenticates once using an API key - no session tokens required.
301
+
302
+ ```bash
303
+ # Reads API key from .mcp.json headers.x-api-key or OPEN_MERCATO_API_KEY env
304
+ yarn mcp:dev
305
+ ```
306
+
307
+ **Key characteristics:**
308
+ - API key authentication at startup (no per-request session tokens)
309
+ - Tools filtered by API key permissions once
310
+ - Ideal for Claude Code, MCP Inspector, local testing
311
+ - Configuration via `.mcp.json`:
312
+ ```json
313
+ {
314
+ "mcpServers": {
315
+ "open-mercato": {
316
+ "type": "http",
317
+ "url": "http://localhost:3001/mcp",
318
+ "headers": {
319
+ "x-api-key": "omk_your_key_here"
320
+ }
321
+ }
322
+ }
323
+ }
324
+ ```
325
+
326
+ #### Production Server (`yarn mcp:serve`)
327
+
328
+ For web-based AI chat. Requires two-tier auth: server API key + user session tokens.
329
+
330
+ ```bash
331
+ # Requires MCP_SERVER_API_KEY in .env
332
+ yarn mcp:serve
333
+ ```
334
+
335
+ **Key characteristics:**
336
+ - Server-level auth via `x-api-key` header (validated against `MCP_SERVER_API_KEY`)
337
+ - User-level auth via `_sessionToken` parameter in each tool call
338
+ - Per-request permission checks based on user's session
339
+ - 2-hour session token TTL
340
+
341
+ #### Comparison
342
+
343
+ | Feature | Dev (`mcp:dev`) | Production (`mcp:serve`) |
344
+ |---------|-----------------|-------------------------|
345
+ | Auth | API key only | API key + session tokens |
346
+ | Permission check | Once at startup | Per tool call |
347
+ | Session tokens | Not required | Required |
348
+ | Use case | Claude Code, dev | Web AI chat |
349
+
350
+ ### 1. Start MCP Server
351
+ ```bash
352
+ # For development/Claude Code:
353
+ yarn mcp:dev
354
+
355
+ # For production/web chat:
356
+ yarn mcp:serve
357
+ ```
358
+
359
+ ### 2. Start OpenCode (Docker)
360
+ ```bash
361
+ docker start opencode-mvp
362
+ # Or: docker-compose up opencode
363
+ ```
364
+
365
+ ### 3. Verify Connectivity
366
+ ```bash
367
+ # MCP health
368
+ curl http://localhost:3001/health
369
+ # {"status":"ok","mode":"development","tools":10}
370
+
371
+ # OpenCode health
372
+ curl http://localhost:4096/global/health
373
+ # {"healthy":true,"version":"1.1.21"}
374
+
375
+ # OpenCode MCP connection
376
+ curl http://localhost:4096/mcp
377
+ # {"open-mercato":{"status":"connected"}}
378
+ ```
379
+
380
+ ### 4. Start Next.js
381
+ ```bash
382
+ yarn dev
383
+ ```
384
+
385
+ ### 5. Test
386
+ - Open browser → Press Cmd+K
387
+ - Type: "What tools do you have?"
388
+ - Should see "Agent is working..." then response
389
+
390
+ ## Common Tasks
391
+
392
+ ### Adding a New Tool
393
+
394
+ Register tools via `registerMcpTool()`:
395
+
396
+ ```typescript
397
+ import { registerMcpTool } from '@open-mercato/ai-assistant'
398
+ import { z } from 'zod'
399
+
400
+ registerMcpTool({
401
+ name: 'mymodule.action',
402
+ description: 'Does something useful',
403
+ inputSchema: z.object({ param: z.string() }),
404
+ requiredFeatures: ['mymodule.view'],
405
+ handler: async (args, ctx) => {
406
+ // Implementation
407
+ return { result: 'done' }
408
+ }
409
+ }, { moduleId: 'mymodule' })
410
+ ```
411
+
412
+ ### Modifying OpenCode Configuration
413
+
414
+ 1. Edit `docker/opencode/opencode.json`
415
+ 2. Rebuild: `docker-compose build opencode`
416
+ 3. Restart: `docker-compose up -d opencode`
417
+
418
+ ### Adding New API Endpoints to Discovery
419
+
420
+ APIs are automatically discovered from the OpenAPI spec (`openapi.yaml`). To add:
421
+
422
+ 1. Define the endpoint in your module's route file
423
+ 2. Regenerate the OpenAPI spec
424
+ 3. Restart MCP server
425
+
426
+ ### Debugging Tool Calls
427
+
428
+ 1. Open Command Palette (Cmd+K)
429
+ 2. Click "Debug" in footer to toggle debug panel
430
+ 3. Debug panel shows all tool calls, results, errors
431
+
432
+ ## Permissions (ACL)
433
+
434
+ | Feature ID | Description |
435
+ |------------|-------------|
436
+ | `ai_assistant.view` | View AI Assistant |
437
+ | `ai_assistant.settings.manage` | Manage AI settings |
438
+ | `ai_assistant.mcp.serve` | Start MCP Server |
439
+ | `ai_assistant.tools.list` | List MCP Tools |
440
+ | `ai_assistant.mcp_servers.view` | View MCP server configs |
441
+ | `ai_assistant.mcp_servers.manage` | Manage MCP server configs |
442
+
443
+ ## Critical Technical Details
444
+
445
+ ### OpenCode Client
446
+
447
+ Located in `lib/opencode-client.ts`:
448
+
449
+ ```typescript
450
+ class OpenCodeClient {
451
+ health(): Promise<OpenCodeHealth>
452
+ mcpStatus(): Promise<OpenCodeMcpStatus>
453
+ createSession(): Promise<OpenCodeSession>
454
+ getSession(id: string): Promise<OpenCodeSession>
455
+ sendMessage(sessionId: string, message: string): Promise<OpenCodeMessage>
456
+ }
457
+
458
+ // Factory function with env defaults
459
+ function createOpenCodeClient(config?: Partial<OpenCodeClientConfig>): OpenCodeClient
460
+ ```
461
+
462
+ ### OpenCode Handlers
463
+
464
+ Located in `lib/opencode-handlers.ts`:
465
+
466
+ ```typescript
467
+ // Main handler for chat API
468
+ async function handleOpenCodeMessage(options: {
469
+ message: string
470
+ sessionId?: string
471
+ }): Promise<OpenCodeTestResponse>
472
+
473
+ // Extract text from OpenCode response parts
474
+ function extractTextFromResponse(result: OpenCodeMessage): string
475
+ ```
476
+
477
+ ### API Discovery Tools
478
+
479
+ Located in `lib/api-discovery-tools.ts`:
480
+
481
+ ```typescript
482
+ // Registered tools:
483
+ // - api_discover: Search endpoints by keyword
484
+ // - api_schema: Get endpoint details
485
+ // - api_execute: Execute API call
486
+
487
+ // Internal functions:
488
+ function searchEndpoints(query: string, options?: SearchOptions): EndpointMatch[]
489
+ function executeApiCall(params: ExecuteParams, ctx: McpToolContext): Promise<unknown>
490
+ ```
491
+
492
+ ### API Endpoint Index
493
+
494
+ Located in `lib/api-endpoint-index.ts`:
495
+
496
+ ```typescript
497
+ class ApiEndpointIndex {
498
+ static getInstance(): ApiEndpointIndex
499
+ searchEndpoints(query: string, options?: SearchOptions): EndpointMatch[]
500
+ getEndpoint(operationId: string): EndpointInfo | null
501
+ getEndpointByPath(method: string, path: string): EndpointInfo | null
502
+ }
503
+ ```
504
+
505
+ ## Docker Configuration
506
+
507
+ ### OpenCode Container
508
+
509
+ ```yaml
510
+ # docker-compose.yml
511
+ services:
512
+ opencode:
513
+ build: ./docker/opencode
514
+ container_name: opencode-mvp
515
+ ports:
516
+ - "4096:4096"
517
+ environment:
518
+ - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
519
+ volumes:
520
+ - ./docker/opencode/opencode.json:/root/.opencode/opencode.json
521
+ ```
522
+
523
+ ### OpenCode Config
524
+
525
+ ```json
526
+ {
527
+ "provider": "anthropic",
528
+ "model": "claude-sonnet-4-20250514",
529
+ "mcp": {
530
+ "open-mercato": {
531
+ "type": "sse",
532
+ "url": "http://host.docker.internal:3001/mcp",
533
+ "headers": {
534
+ "x-api-key": "omk_..."
535
+ }
536
+ }
537
+ }
538
+ }
539
+ ```
540
+
541
+ ## Debug Features
542
+
543
+ ### Debug Panel
544
+
545
+ Toggle with "Debug" button in Command Palette footer. Shows:
546
+ - `thinking` - Agent processing started
547
+ - `tool-call` - Tool invocation with args
548
+ - `tool-result` - Tool response
549
+ - `text` - Response text chunks
550
+ - `error` - Errors
551
+ - `done` - Completion
552
+
553
+ ### Debug Event Types
554
+
555
+ ```typescript
556
+ type DebugEventType =
557
+ | 'thinking' // Agent started processing
558
+ | 'tool-call' // Tool called
559
+ | 'tool-result' // Tool result
560
+ | 'text' // Text response
561
+ | 'error' // Error occurred
562
+ | 'done' // Complete
563
+ | 'message' // Chat message
564
+ | 'connection' // Connection status change
565
+ ```
566
+
567
+ ## Changelog
568
+
569
+ ### 2026-01-17 - Session Persistence Fix
570
+
571
+ **Bug fixed**: Chat context lost between messages (AI asked "Who is 'his'?" instead of remembering Taylor).
572
+
573
+ **Root causes**:
574
+ 1. **Promise.race bug**: `handleOpenCodeMessageStreaming` used `Promise.race([eventPromise, sendPromise])` which resolved when HTTP completed, before SSE could emit `done` event with sessionId.
575
+ 2. **React stale closure**: `handleSubmit` callback captured initial `null` sessionId value.
576
+
577
+ **Fixes applied**:
578
+ - `opencode-handlers.ts`: Removed Promise.race, await only SSE eventPromise
579
+ - `useCommandPalette.ts`: Added `opencodeSessionIdRef` (ref) alongside state to avoid stale closures
580
+
581
+ **Files modified**:
582
+ - `src/modules/ai_assistant/lib/opencode-handlers.ts` - Fixed Promise.race completion bug
583
+ - `src/frontend/hooks/useCommandPalette.ts` - Added ref pattern for sessionId
584
+
585
+ **Diagnostic logging added** (can be removed after verification):
586
+ - `[handleSubmit] DIAGNOSTIC` - Session check before routing
587
+ - `[sendAgenticMessage] DIAGNOSTIC` - Request payload before fetch
588
+ - `[startAgenticChat] DIAGNOSTIC` - Done event handling
589
+ - `[AI Chat] DIAGNOSTIC` - Backend request received
590
+
591
+ ### 2026-01 - OpenCode Integration
592
+
593
+ **Major change**: Replaced Vercel AI SDK with OpenCode as the AI backend.
594
+
595
+ **What changed**:
596
+ - Chat API now routes all requests to OpenCode
597
+ - Added session management for conversation context
598
+ - Added "Agent is working..." indicator
599
+ - OpenCode connects to MCP server for tools
600
+ - Removed direct AI provider integration
601
+
602
+ **Files modified**:
603
+ - `src/modules/ai_assistant/api/chat/route.ts` - Complete rewrite to use OpenCode
604
+ - `src/frontend/hooks/useCommandPalette.ts` - Added session state, thinking indicator
605
+ - `src/frontend/components/CommandPalette/ToolChatPage.tsx` - Added thinking UI
606
+ - `src/frontend/types.ts` - Added ChatSSEEvent, isThinking
607
+
608
+ ### 2026-01 - API Discovery Tools
609
+
610
+ **Major change**: Replaced 600+ individual tools with 3 meta-tools.
611
+
612
+ **What changed**:
613
+ - Added `api_discover`, `api_execute`, `api_schema` tools
614
+ - Created `ApiEndpointIndex` for OpenAPI introspection
615
+ - Hybrid discovery: search + OpenAPI
616
+ - 405 endpoints available via discovery
617
+
618
+ **Files created**:
619
+ - `lib/api-discovery-tools.ts`
620
+ - `lib/api-endpoint-index.ts`
621
+
622
+ ### 2026-01 - Hybrid Tool Discovery
623
+
624
+ **What changed**:
625
+ - Combined semantic search with OpenAPI introspection
626
+ - Tools indexed for fulltext search
627
+ - API endpoints indexed from OpenAPI spec
628
+
629
+ ### Previous Changes
630
+
631
+ See git history for earlier changes including:
632
+ - Zod 4 schema handling fixes
633
+ - Debug panel addition
634
+ - CLI tools fixes
635
+ - Raycast-style command palette rewrite
636
+
637
+ ---
638
+
639
+ ## Session Management Deep Dive
640
+
641
+ ### Architecture Overview
642
+
643
+ The session flow spans multiple layers:
644
+
645
+ ```
646
+ Frontend (useCommandPalette.ts)
647
+ ↓ sessionId in request body
648
+ Backend (route.ts)
649
+ ↓ sessionId passed to handler
650
+ OpenCode Handler (opencode-handlers.ts)
651
+ ↓ client.getSession(sessionId) or createSession()
652
+ OpenCode Client (opencode-client.ts)
653
+ ↓ GET /session/{id} or POST /session
654
+ OpenCode Server (Docker :4096)
655
+ → Maintains conversation context
656
+ ```
657
+
658
+ ### Session ID Flow
659
+
660
+ 1. **First message**: No sessionId → `startAgenticChat()` creates new session
661
+ 2. **OpenCode responds**: SSE stream emits `{ type: 'done', sessionId: 'ses_xxx' }`
662
+ 3. **Frontend stores**: `opencodeSessionIdRef.current = sessionId`
663
+ 4. **Subsequent messages**: `sendAgenticMessage()` includes sessionId in request body
664
+ 5. **Backend receives**: Uses existing session instead of creating new one
665
+
666
+ ### Critical: React Refs vs State for Session ID
667
+
668
+ **Problem**: Using `useState` alone for sessionId causes stale closure issues in callbacks.
669
+
670
+ ```typescript
671
+ // BAD: Stale closure - callback captures initial null value
672
+ const [sessionId, setSessionId] = useState<string | null>(null)
673
+ const handleSubmit = useCallback(async (query) => {
674
+ if (sessionId) { // Always null in closure!
675
+ await continueSession(query)
676
+ }
677
+ }, [sessionId]) // Even with dependency, timing issues persist
678
+ ```
679
+
680
+ **Solution**: Use both state (for React reactivity) AND ref (for callbacks):
681
+
682
+ ```typescript
683
+ // GOOD: Ref always has current value
684
+ const [opencodeSessionId, setOpencodeSessionId] = useState<string | null>(null)
685
+ const opencodeSessionIdRef = useRef<string | null>(null)
686
+
687
+ const updateOpencodeSessionId = useCallback((id: string | null) => {
688
+ opencodeSessionIdRef.current = id // Update ref first
689
+ setOpencodeSessionId(id) // Then state for React
690
+ }, [])
691
+
692
+ const handleSubmit = useCallback(async (query) => {
693
+ if (opencodeSessionIdRef.current) { // Ref has latest value!
694
+ await continueSession(query)
695
+ }
696
+ }, []) // No dependency needed - ref is always current
697
+ ```
698
+
699
+ ### SSE Stream Completion Bug (Fixed 2026-01)
700
+
701
+ **Problem**: `done` event with sessionId was never emitted to frontend.
702
+
703
+ **Root Cause**: In `opencode-handlers.ts`, the code used `Promise.race()`:
704
+
705
+ ```typescript
706
+ // BUG: sendPromise resolves before SSE emits session.status: idle
707
+ await Promise.race([eventPromise, sendPromise.catch(err => Promise.reject(err))])
708
+ ```
709
+
710
+ When `sendMessage()` HTTP call completed, `Promise.race` resolved immediately, BEFORE the SSE handler could receive the `session.status: idle` event and emit `done`.
711
+
712
+ **Fix**: Only wait for SSE completion, catch send errors separately:
713
+
714
+ ```typescript
715
+ // FIXED: SSE determines completion, not HTTP response
716
+ client.sendMessage(session.id, message, { model }).catch((err) => {
717
+ console.error('[OpenCode] Send error (SSE should handle):', err)
718
+ })
719
+ await eventPromise // Only SSE determines completion
720
+ ```
721
+
722
+ ### OpenCode SSE Event Flow
723
+
724
+ OpenCode emits events via Server-Sent Events. The completion flow:
725
+
726
+ 1. `session.status: busy` - Processing started
727
+ 2. `message.part.updated` - Text chunks, tool calls, tool results
728
+ 3. `message.updated` - Message completed (with tokens, timing)
729
+ 4. `session.status: idle` - Processing complete → emit `done` event
730
+
731
+ **Key insight**: The `session.status: idle` event triggers `done`, not HTTP completion.
732
+
733
+ ### Debugging Session Issues
734
+
735
+ Add diagnostic logging to trace session flow:
736
+
737
+ ```typescript
738
+ // Frontend: useCommandPalette.ts
739
+ console.log('[handleSubmit] DIAGNOSTIC - Session check:', {
740
+ refValue: opencodeSessionIdRef.current,
741
+ willContinue: !!opencodeSessionIdRef.current,
742
+ })
743
+
744
+ // Backend: route.ts
745
+ console.log('[AI Chat] DIAGNOSTIC - Request received:', {
746
+ hasSessionId: !!sessionId,
747
+ sessionId: sessionId ? sessionId.substring(0, 20) + '...' : null,
748
+ })
749
+ ```
750
+
751
+ **What to check**:
752
+ 1. First message: `refValue: null, willContinue: false` ✓
753
+ 2. After first response: Look for `Done event` with sessionId
754
+ 3. Second message: `refValue: 'ses_xxx', willContinue: true` ✓
755
+ 4. Backend: `hasSessionId: true` ✓
756
+
757
+ ### Common Session Problems
758
+
759
+ | Symptom | Likely Cause | Fix |
760
+ |---------|--------------|-----|
761
+ | Second message loses context | sessionId not stored | Check `done` event has sessionId |
762
+ | `refValue: null` on second message | Stale closure | Use ref pattern (see above) |
763
+ | Backend `hasSessionId: false` | Request serialization issue | Check JSON.stringify includes sessionId |
764
+ | `done` event never emitted | Promise.race bug | See SSE completion fix above |
765
+ | Multiple `session-authorized` events | Creating new session each time | sessionId not passed to backend |
766
+
767
+ ### Testing Session Persistence
768
+
769
+ 1. Open browser console (F12)
770
+ 2. Open AI Assistant (Cmd+K)
771
+ 3. Send: "find customer Taylor"
772
+ 4. Check console for `Done event` with sessionId
773
+ 5. Send: "find his related companies"
774
+ 6. Check: `willContinue: true` and AI knows about Taylor
775
+
776
+ ## Troubleshooting
777
+
778
+ | Symptom | Likely Cause | Fix |
779
+ |---------|--------------|-----|
780
+ | "Agent is working..." forever | OpenCode not responding | Check `curl http://localhost:4096/global/health` |
781
+ | "MCP connection failed" | MCP server not running | Start with `yarn mercato ai_assistant mcp:serve-http --port 3001` |
782
+ | Empty response | OpenCode not connected to MCP | Check `curl http://localhost:4096/mcp` |
783
+ | "Unauthorized" error | Missing/invalid API key | Check x-api-key in opencode.json |
784
+ | Tools not found | Endpoint not in OpenAPI | Regenerate OpenAPI spec |
785
+ | Context lost between messages | Session ID not persisted | See "Session Management Deep Dive" above |
786
+ | "Session expired" errors | Session token TTL exceeded | Close and reopen chat (creates new 2-hour token) |
787
+ | Tools fail with UNAUTHORIZED | Missing _sessionToken | Verify AI is passing token in tool args |
788
+
789
+ ---
790
+
791
+ ## Two-Tier Authentication Architecture
792
+
793
+ The MCP HTTP server implements two distinct authentication layers:
794
+
795
+ ### Tier 1: Server-Level Authentication
796
+
797
+ **Purpose**: Validates that requests come from an authorized AI agent (e.g., OpenCode)
798
+
799
+ ```
800
+ Request → Check x-api-key header → Compare with MCP_SERVER_API_KEY env var
801
+ ```
802
+
803
+ | Aspect | Details |
804
+ |--------|---------|
805
+ | **Header** | `x-api-key` |
806
+ | **Value** | Static API key from `MCP_SERVER_API_KEY` environment variable |
807
+ | **Configured In** | `opencode.json` or `opencode.jsonc` |
808
+ | **Validation** | Direct string comparison |
809
+ | **Result** | Grants access to call MCP endpoints (but no user permissions) |
810
+
811
+ **Code reference**: `packages/ai-assistant/src/modules/ai_assistant/lib/http-server.ts:370-391`
812
+
813
+ ### Tier 2: User-Level Authentication (Session Tokens)
814
+
815
+ **Purpose**: Identifies the actual user and loads their permissions for each tool call
816
+
817
+ ```
818
+ Tool call → Extract _sessionToken → Lookup in DB → Load ACL → Check permissions
819
+ ```
820
+
821
+ | Aspect | Details |
822
+ |--------|---------|
823
+ | **Parameter** | `_sessionToken` in tool call args |
824
+ | **Format** | `sess_{32 hex chars}` (e.g., `sess_a1b2c3d4e5f6...`) |
825
+ | **TTL** | 120 minutes (2 hours) |
826
+ | **Storage** | `api_keys` table |
827
+ | **Lookup** | `findApiKeyBySessionToken()` |
828
+ | **ACL** | `rbacService.loadAcl()` |
829
+
830
+ **Code references**:
831
+ - Session creation: `packages/ai-assistant/src/modules/ai_assistant/api/chat/route.ts:133-157`
832
+ - Token lookup: `packages/core/src/modules/api_keys/services/apiKeyService.ts:143-158`
833
+ - Context resolution: `packages/ai-assistant/src/modules/ai_assistant/lib/http-server.ts:32-88`
834
+
835
+ ---
836
+
837
+ ## Session Token System
838
+
839
+ ### Token Generation
840
+
841
+ ```typescript
842
+ // packages/core/src/modules/api_keys/services/apiKeyService.ts:99-101
843
+ export function generateSessionToken(): string {
844
+ return `sess_${randomBytes(16).toString('hex')}`
845
+ }
846
+ // Result: "sess_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"
847
+ ```
848
+
849
+ ### Token Storage (api_keys table)
850
+
851
+ Session tokens create ephemeral API keys with additional columns:
852
+
853
+ | Column | Type | Description |
854
+ |--------|------|-------------|
855
+ | `sessionToken` | string | The `sess_xxx` token for lookup |
856
+ | `sessionUserId` | string | ID of the user this session represents |
857
+ | `rolesJson` | string[] | User's role IDs (inherited from user) |
858
+ | `tenantId` | string | Tenant scope |
859
+ | `organizationId` | string | Organization scope |
860
+ | `expiresAt` | Date | TTL (default: 120 minutes from creation) |
861
+
862
+ ### Token Injection Into Messages
863
+
864
+ When a new chat session starts, the backend injects a system instruction:
865
+
866
+ ```typescript
867
+ // packages/ai-assistant/src/modules/ai_assistant/api/chat/route.ts:161-164
868
+ let messageToSend = lastUserMessage
869
+ if (sessionToken) {
870
+ messageToSend = `[SYSTEM: Your session token is "${sessionToken}". You MUST include "_sessionToken": "${sessionToken}" in EVERY tool call argument object. Without this, tools will fail with authorization errors.]\n\n${lastUserMessage}`
871
+ }
872
+ ```
873
+
874
+ This ensures the AI agent (Claude via OpenCode) includes the token in all tool calls.
875
+
876
+ ### Token in Tool Calls
877
+
878
+ The MCP server schema transformation injects `_sessionToken` into every tool:
879
+
880
+ ```typescript
881
+ // packages/ai-assistant/src/modules/ai_assistant/lib/http-server.ts:128-131
882
+ properties._sessionToken = {
883
+ type: 'string',
884
+ description: 'Session authorization token (REQUIRED for all tool calls)',
885
+ }
886
+ ```
887
+
888
+ AI sees this parameter and includes it:
889
+
890
+ ```json
891
+ {
892
+ "method": "tools/call",
893
+ "params": {
894
+ "name": "api_execute",
895
+ "arguments": {
896
+ "_sessionToken": "sess_a1b2c3d4...",
897
+ "method": "GET",
898
+ "path": "/customers/companies"
899
+ }
900
+ }
901
+ }
902
+ ```
903
+
904
+ ---
905
+
906
+ ## Session Context Resolution
907
+
908
+ When a tool call arrives with `_sessionToken`:
909
+
910
+ ### Step 1: Extract Token
911
+
912
+ ```typescript
913
+ // http-server.ts:169-170
914
+ const sessionToken = toolArgs._sessionToken as string | undefined
915
+ delete toolArgs._sessionToken // Remove before passing to handler
916
+ ```
917
+
918
+ ### Step 2: Lookup Session Key
919
+
920
+ ```typescript
921
+ // http-server.ts:42 → apiKeyService.ts:143-158
922
+ const sessionKey = await findApiKeyBySessionToken(em, sessionToken)
923
+ // Returns null if: not found, deleted, or expired
924
+ ```
925
+
926
+ ### Step 3: Load ACL
927
+
928
+ ```typescript
929
+ // http-server.ts:59-62
930
+ const acl = await rbacService.loadAcl(`api_key:${sessionKey.id}`, {
931
+ tenantId: sessionKey.tenantId ?? null,
932
+ organizationId: sessionKey.organizationId ?? null,
933
+ })
934
+ ```
935
+
936
+ ### Step 4: Build User Context
937
+
938
+ ```typescript
939
+ // http-server.ts:73-81
940
+ return {
941
+ tenantId: sessionKey.tenantId ?? null,
942
+ organizationId: sessionKey.organizationId ?? null,
943
+ userId: sessionKey.sessionUserId,
944
+ container: baseContext.container,
945
+ userFeatures: acl.features,
946
+ isSuperAdmin: acl.isSuperAdmin,
947
+ apiKeySecret: baseContext.apiKeySecret,
948
+ }
949
+ ```
950
+
951
+ ### Step 5: Check Tool Permissions
952
+
953
+ ```typescript
954
+ // http-server.ts:219-238 → auth.ts:127-148
955
+ if (tool.requiredFeatures?.length) {
956
+ const hasAccess = hasRequiredFeatures(
957
+ tool.requiredFeatures,
958
+ effectiveContext.userFeatures,
959
+ effectiveContext.isSuperAdmin
960
+ )
961
+ if (!hasAccess) {
962
+ return { error: `Insufficient permissions. Required: ${tool.requiredFeatures.join(', ')}` }
963
+ }
964
+ }
965
+ ```
966
+
967
+ ---
968
+
969
+ ## Updated SSE Events
970
+
971
+ ### Full Event Types
972
+
973
+ ```typescript
974
+ // packages/ai-assistant/src/modules/ai_assistant/lib/opencode-handlers.ts:218-227
975
+ export type OpenCodeStreamEvent =
976
+ | { type: 'thinking' }
977
+ | { type: 'text'; content: string }
978
+ | { type: 'tool-call'; id: string; toolName: string; args: unknown }
979
+ | { type: 'tool-result'; id: string; result: unknown }
980
+ | { type: 'question'; question: OpenCodeQuestion }
981
+ | { type: 'metadata'; model?: string; provider?: string; tokens?: { input: number; output: number }; durationMs?: number }
982
+ | { type: 'debug'; partType: string; data: unknown }
983
+ | { type: 'done'; sessionId: string }
984
+ | { type: 'error'; error: string }
985
+ ```
986
+
987
+ ### Additional Events from Chat API
988
+
989
+ | Event | Emitted By | Purpose |
990
+ |-------|------------|---------|
991
+ | `session-authorized` | `chat/route.ts:170-175` | Confirms session token created for new chat |
992
+
993
+ ### Debug Events (partType values)
994
+
995
+ | partType | Description |
996
+ |----------|-------------|
997
+ | `question-asked` | OpenCode asking a confirmation question |
998
+ | `message-completed` | Assistant message with token counts |
999
+ | `step-start` | Agentic step beginning |
1000
+ | `step-finish` | Agentic step complete |
1001
+
1002
+ ---
1003
+
1004
+ ## MCP HTTP Server Details
1005
+
1006
+ ### Stateless Request Model
1007
+
1008
+ Each HTTP request creates a fresh MCP server instance:
1009
+
1010
+ ```typescript
1011
+ // http-server.ts:95-278
1012
+ function createMcpServerForRequest(config, toolContext): McpServer {
1013
+ const server = new McpServer(
1014
+ { name: config.name, version: config.version },
1015
+ { capabilities: { tools: {} } }
1016
+ )
1017
+ // Register all tools (ACL checked per-call)
1018
+ // ...
1019
+ return server
1020
+ }
1021
+ ```
1022
+
1023
+ **Benefits**:
1024
+ - No session state to manage
1025
+ - Clean isolation between requests
1026
+ - Scales horizontally
1027
+
1028
+ ### Schema Transformation
1029
+
1030
+ Tool schemas are transformed to include `_sessionToken`:
1031
+
1032
+ 1. **Convert** Zod schema → JSON Schema (`z.toJSONSchema()`)
1033
+ 2. **Inject** `_sessionToken` property into `properties`
1034
+ 3. **Convert** JSON Schema → Zod with `.passthrough()`
1035
+ 4. **Result**: AI agent sees token as available parameter
1036
+
1037
+ ```typescript
1038
+ // http-server.ts:121-155
1039
+ const jsonSchema = z.toJSONSchema(tool.inputSchema, { unrepresentable: 'any' })
1040
+ const properties = jsonSchema.properties ?? {}
1041
+ properties._sessionToken = {
1042
+ type: 'string',
1043
+ description: 'Session authorization token (REQUIRED for all tool calls)',
1044
+ }
1045
+ jsonSchema.properties = properties
1046
+ const converted = jsonSchemaToZod(jsonSchema)
1047
+ safeSchema = (converted as z.ZodObject<any>).passthrough()
1048
+ ```
1049
+
1050
+ ### Per-Tool ACL Checks
1051
+
1052
+ Each tool call validates permissions using the session's ACL:
1053
+
1054
+ ```typescript
1055
+ // http-server.ts:219-239
1056
+ if (tool.requiredFeatures?.length) {
1057
+ const hasAccess = hasRequiredFeatures(
1058
+ tool.requiredFeatures,
1059
+ effectiveContext.userFeatures,
1060
+ effectiveContext.isSuperAdmin
1061
+ )
1062
+ if (!hasAccess) {
1063
+ return {
1064
+ content: [{ type: 'text', text: JSON.stringify({
1065
+ error: `Insufficient permissions for tool "${tool.name}". Required: ${tool.requiredFeatures.join(', ')}`,
1066
+ code: 'UNAUTHORIZED'
1067
+ })}],
1068
+ isError: true
1069
+ }
1070
+ }
1071
+ }
1072
+ ```
1073
+
1074
+ ### Error Responses
1075
+
1076
+ | Code | Message | Cause |
1077
+ |------|---------|-------|
1078
+ | `SESSION_EXPIRED` | "Your chat session has expired..." | Token TTL exceeded (>2 hours) |
1079
+ | `UNAUTHORIZED` | "Session token required" | No `_sessionToken` in args |
1080
+ | `UNAUTHORIZED` | "Insufficient permissions" | User lacks required features |
1081
+
1082
+ ---
1083
+
1084
+ ## Future Development
1085
+
1086
+ See the original AGENTS.md for planned features:
1087
+ - AI Agent Authorization & Impersonation
1088
+ - Actor + Subject model for audit trails
1089
+ - Permission tiers for rate limiting
1090
+ - Enhanced confirmation flow