@wener/mcps 1.0.1

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 (141) hide show
  1. package/LICENSE +21 -0
  2. package/dist/index.mjs +15 -0
  3. package/dist/mcps-cli.mjs +174727 -0
  4. package/lib/chat/agent.js +187 -0
  5. package/lib/chat/agent.js.map +1 -0
  6. package/lib/chat/audit.js +238 -0
  7. package/lib/chat/audit.js.map +1 -0
  8. package/lib/chat/converters.js +467 -0
  9. package/lib/chat/converters.js.map +1 -0
  10. package/lib/chat/handler.js +1068 -0
  11. package/lib/chat/handler.js.map +1 -0
  12. package/lib/chat/index.js +12 -0
  13. package/lib/chat/index.js.map +1 -0
  14. package/lib/chat/types.js +35 -0
  15. package/lib/chat/types.js.map +1 -0
  16. package/lib/contracts/AuditContract.js +85 -0
  17. package/lib/contracts/AuditContract.js.map +1 -0
  18. package/lib/contracts/McpsContract.js +113 -0
  19. package/lib/contracts/McpsContract.js.map +1 -0
  20. package/lib/contracts/index.js +3 -0
  21. package/lib/contracts/index.js.map +1 -0
  22. package/lib/dev.server.js +7 -0
  23. package/lib/dev.server.js.map +1 -0
  24. package/lib/entities/ChatRequestEntity.js +318 -0
  25. package/lib/entities/ChatRequestEntity.js.map +1 -0
  26. package/lib/entities/McpRequestEntity.js +271 -0
  27. package/lib/entities/McpRequestEntity.js.map +1 -0
  28. package/lib/entities/RequestLogEntity.js +177 -0
  29. package/lib/entities/RequestLogEntity.js.map +1 -0
  30. package/lib/entities/ResponseEntity.js +150 -0
  31. package/lib/entities/ResponseEntity.js.map +1 -0
  32. package/lib/entities/index.js +11 -0
  33. package/lib/entities/index.js.map +1 -0
  34. package/lib/entities/types.js +11 -0
  35. package/lib/entities/types.js.map +1 -0
  36. package/lib/index.js +3 -0
  37. package/lib/index.js.map +1 -0
  38. package/lib/mcps-cli.js +44 -0
  39. package/lib/mcps-cli.js.map +1 -0
  40. package/lib/providers/McpServerHandlerDef.js +40 -0
  41. package/lib/providers/McpServerHandlerDef.js.map +1 -0
  42. package/lib/providers/findMcpServerDef.js +26 -0
  43. package/lib/providers/findMcpServerDef.js.map +1 -0
  44. package/lib/providers/prometheus/def.js +24 -0
  45. package/lib/providers/prometheus/def.js.map +1 -0
  46. package/lib/providers/prometheus/index.js +2 -0
  47. package/lib/providers/prometheus/index.js.map +1 -0
  48. package/lib/providers/relay/def.js +32 -0
  49. package/lib/providers/relay/def.js.map +1 -0
  50. package/lib/providers/relay/index.js +2 -0
  51. package/lib/providers/relay/index.js.map +1 -0
  52. package/lib/providers/sql/def.js +31 -0
  53. package/lib/providers/sql/def.js.map +1 -0
  54. package/lib/providers/sql/index.js +2 -0
  55. package/lib/providers/sql/index.js.map +1 -0
  56. package/lib/providers/tencent-cls/def.js +44 -0
  57. package/lib/providers/tencent-cls/def.js.map +1 -0
  58. package/lib/providers/tencent-cls/index.js +2 -0
  59. package/lib/providers/tencent-cls/index.js.map +1 -0
  60. package/lib/scripts/bundle.js +90 -0
  61. package/lib/scripts/bundle.js.map +1 -0
  62. package/lib/server/api-routes.js +96 -0
  63. package/lib/server/api-routes.js.map +1 -0
  64. package/lib/server/audit.js +274 -0
  65. package/lib/server/audit.js.map +1 -0
  66. package/lib/server/chat-routes.js +82 -0
  67. package/lib/server/chat-routes.js.map +1 -0
  68. package/lib/server/config.js +223 -0
  69. package/lib/server/config.js.map +1 -0
  70. package/lib/server/db.js +97 -0
  71. package/lib/server/db.js.map +1 -0
  72. package/lib/server/index.js +2 -0
  73. package/lib/server/index.js.map +1 -0
  74. package/lib/server/mcp-handler.js +167 -0
  75. package/lib/server/mcp-handler.js.map +1 -0
  76. package/lib/server/mcp-routes.js +112 -0
  77. package/lib/server/mcp-routes.js.map +1 -0
  78. package/lib/server/mcps-router.js +119 -0
  79. package/lib/server/mcps-router.js.map +1 -0
  80. package/lib/server/schema.js +129 -0
  81. package/lib/server/schema.js.map +1 -0
  82. package/lib/server/server.js +166 -0
  83. package/lib/server/server.js.map +1 -0
  84. package/lib/web/ChatPage.js +827 -0
  85. package/lib/web/ChatPage.js.map +1 -0
  86. package/lib/web/McpInspectorPage.js +214 -0
  87. package/lib/web/McpInspectorPage.js.map +1 -0
  88. package/lib/web/ServersPage.js +93 -0
  89. package/lib/web/ServersPage.js.map +1 -0
  90. package/lib/web/main.js +541 -0
  91. package/lib/web/main.js.map +1 -0
  92. package/package.json +83 -0
  93. package/src/chat/agent.ts +240 -0
  94. package/src/chat/audit.ts +377 -0
  95. package/src/chat/converters.test.ts +325 -0
  96. package/src/chat/converters.ts +459 -0
  97. package/src/chat/handler.test.ts +137 -0
  98. package/src/chat/handler.ts +1233 -0
  99. package/src/chat/index.ts +16 -0
  100. package/src/chat/types.ts +72 -0
  101. package/src/contracts/AuditContract.ts +93 -0
  102. package/src/contracts/McpsContract.ts +141 -0
  103. package/src/contracts/index.ts +18 -0
  104. package/src/dev.server.ts +7 -0
  105. package/src/entities/ChatRequestEntity.ts +157 -0
  106. package/src/entities/McpRequestEntity.ts +149 -0
  107. package/src/entities/RequestLogEntity.ts +78 -0
  108. package/src/entities/ResponseEntity.ts +75 -0
  109. package/src/entities/index.ts +12 -0
  110. package/src/entities/types.ts +188 -0
  111. package/src/index.ts +1 -0
  112. package/src/mcps-cli.ts +59 -0
  113. package/src/providers/McpServerHandlerDef.ts +105 -0
  114. package/src/providers/findMcpServerDef.ts +31 -0
  115. package/src/providers/prometheus/def.ts +21 -0
  116. package/src/providers/prometheus/index.ts +1 -0
  117. package/src/providers/relay/def.ts +31 -0
  118. package/src/providers/relay/index.ts +1 -0
  119. package/src/providers/relay/relay.test.ts +47 -0
  120. package/src/providers/sql/def.ts +33 -0
  121. package/src/providers/sql/index.ts +1 -0
  122. package/src/providers/tencent-cls/def.ts +38 -0
  123. package/src/providers/tencent-cls/index.ts +1 -0
  124. package/src/scripts/bundle.ts +82 -0
  125. package/src/server/api-routes.ts +98 -0
  126. package/src/server/audit.ts +310 -0
  127. package/src/server/chat-routes.ts +95 -0
  128. package/src/server/config.test.ts +162 -0
  129. package/src/server/config.ts +198 -0
  130. package/src/server/db.ts +115 -0
  131. package/src/server/index.ts +1 -0
  132. package/src/server/mcp-handler.ts +209 -0
  133. package/src/server/mcp-routes.ts +133 -0
  134. package/src/server/mcps-router.ts +133 -0
  135. package/src/server/schema.ts +175 -0
  136. package/src/server/server.ts +163 -0
  137. package/src/web/ChatPage.tsx +1005 -0
  138. package/src/web/McpInspectorPage.tsx +254 -0
  139. package/src/web/ServersPage.tsx +139 -0
  140. package/src/web/main.tsx +600 -0
  141. package/src/web/styles.css +15 -0
@@ -0,0 +1,187 @@
1
+ /**
2
+ * Tool Loop Agent Handler
3
+ * Uses AI SDK with MCP tools for agentic chat
4
+ */ import { createOpenAICompatible } from '@ai-sdk/openai-compatible';
5
+ import { generateText, streamText, stepCountIs } from 'ai';
6
+ import consola from 'consola';
7
+ import { streamSSE } from 'hono/streaming';
8
+ const log = consola.withTag('agent');
9
+ /**
10
+ * Normalize base URL - strip trailing /v1 if present
11
+ */ function normalizeBaseUrl(url) {
12
+ return url.replace(/\/v1\/?$/, '');
13
+ }
14
+ /**
15
+ * Register agent routes on Hono app
16
+ */ export function registerAgentRoutes(app, options) {
17
+ const { resolveModelConfig, getMcpTools } = options;
18
+ /**
19
+ * POST /v1/agent/chat
20
+ * Agent chat with tool loop support
21
+ */ app.post('/v1/agent/chat', async (c)=>{
22
+ try {
23
+ const body = await c.req.json();
24
+ const { model: modelName, messages, mcpServers, maxSteps = 5, stream = false } = body;
25
+ // Resolve model config
26
+ const modelConfig = resolveModelConfig(modelName);
27
+ if (!modelConfig) {
28
+ return c.json({
29
+ error: {
30
+ message: `Model not found: ${modelName}`,
31
+ type: 'invalid_request_error',
32
+ code: 'model_not_found'
33
+ }
34
+ }, 404);
35
+ }
36
+ // Get API key
37
+ const apiKey = modelConfig.apiKey;
38
+ const baseUrl = normalizeBaseUrl(modelConfig.baseUrl || '');
39
+ if (!baseUrl) {
40
+ return c.json({
41
+ error: {
42
+ message: 'Model has no baseUrl configured',
43
+ type: 'invalid_request_error',
44
+ code: 'invalid_model_config'
45
+ }
46
+ }, 400);
47
+ }
48
+ // Create AI provider
49
+ const provider = createOpenAICompatible({
50
+ name: 'mcps-agent',
51
+ baseURL: `${baseUrl}/v1`,
52
+ apiKey: apiKey || 'not-needed'
53
+ });
54
+ const aiModel = provider.chatModel(modelName);
55
+ // Get MCP tools if available
56
+ let tools = {};
57
+ if (getMcpTools && mcpServers && mcpServers.length > 0) {
58
+ try {
59
+ tools = await getMcpTools(mcpServers);
60
+ log.info(`Loaded ${Object.keys(tools).length} tools from MCP servers: ${mcpServers.join(', ')}`);
61
+ } catch (err) {
62
+ log.warn('Failed to load MCP tools:', err);
63
+ }
64
+ }
65
+ // Convert messages
66
+ const aiMessages = messages.map((m)=>({
67
+ role: m.role,
68
+ content: m.content
69
+ }));
70
+ log.info(`→ POST /v1/agent/chat model=${modelName} messages=${messages.length} tools=${Object.keys(tools).length} maxSteps=${maxSteps}`);
71
+ if (stream) {
72
+ // Streaming response
73
+ return streamSSE(c, async (sseStream)=>{
74
+ try {
75
+ const result = streamText({
76
+ model: aiModel,
77
+ messages: aiMessages,
78
+ tools: Object.keys(tools).length > 0 ? tools : undefined,
79
+ stopWhen: stepCountIs(maxSteps),
80
+ onStepFinish: async (step)=>{
81
+ // Send step info
82
+ await sseStream.writeSSE({
83
+ data: JSON.stringify({
84
+ type: 'step',
85
+ text: step.text,
86
+ toolCalls: step.toolCalls?.map((tc)=>({
87
+ id: tc.toolCallId,
88
+ name: tc.toolName,
89
+ arguments: 'input' in tc ? tc.input : undefined
90
+ })),
91
+ toolResults: step.toolResults?.map((tr)=>({
92
+ id: tr.toolCallId,
93
+ name: tr.toolName,
94
+ result: 'output' in tr ? tr.output : undefined
95
+ }))
96
+ })
97
+ });
98
+ }
99
+ });
100
+ // Stream text deltas
101
+ for await (const part of result.textStream){
102
+ await sseStream.writeSSE({
103
+ data: JSON.stringify({
104
+ type: 'text',
105
+ content: part
106
+ })
107
+ });
108
+ }
109
+ // Get final result
110
+ const finalResult = await result;
111
+ const usage = await finalResult.usage;
112
+ const steps = await finalResult.steps;
113
+ // Send usage info
114
+ await sseStream.writeSSE({
115
+ data: JSON.stringify({
116
+ type: 'usage',
117
+ usage: {
118
+ promptTokens: usage?.inputTokens,
119
+ completionTokens: usage?.outputTokens,
120
+ totalTokens: (usage?.inputTokens || 0) + (usage?.outputTokens || 0)
121
+ }
122
+ })
123
+ });
124
+ // Send done
125
+ await sseStream.writeSSE({
126
+ data: '[DONE]'
127
+ });
128
+ log.info(`← 200 /v1/agent/chat model=${modelName} steps=${steps?.length || 1}`);
129
+ } catch (err) {
130
+ log.error('Agent streaming error:', err);
131
+ await sseStream.writeSSE({
132
+ data: JSON.stringify({
133
+ type: 'error',
134
+ error: err instanceof Error ? err.message : 'Streaming error'
135
+ })
136
+ });
137
+ }
138
+ });
139
+ }
140
+ // Non-streaming response
141
+ const result = await generateText({
142
+ model: aiModel,
143
+ messages: aiMessages,
144
+ tools: Object.keys(tools).length > 0 ? tools : undefined,
145
+ stopWhen: stepCountIs(maxSteps)
146
+ });
147
+ log.info(`← 200 /v1/agent/chat model=${modelName} steps=${result.steps?.length || 1} tokens=${result.usage?.totalTokens || 0}`);
148
+ // Format response
149
+ return c.json({
150
+ id: `agent-${Date.now()}`,
151
+ object: 'agent.chat.completion',
152
+ model: modelName,
153
+ content: result.text,
154
+ reasoning: result.reasoning,
155
+ steps: result.steps?.map((step)=>({
156
+ text: step.text,
157
+ toolCalls: step.toolCalls?.map((tc)=>({
158
+ id: tc.toolCallId,
159
+ name: tc.toolName,
160
+ arguments: 'input' in tc ? tc.input : undefined
161
+ })),
162
+ toolResults: step.toolResults?.map((tr)=>({
163
+ id: tr.toolCallId,
164
+ name: tr.toolName,
165
+ result: 'output' in tr ? tr.output : undefined
166
+ }))
167
+ })),
168
+ usage: {
169
+ prompt_tokens: result.usage?.inputTokens,
170
+ completion_tokens: result.usage?.outputTokens,
171
+ total_tokens: (result.usage?.inputTokens || 0) + (result.usage?.outputTokens || 0)
172
+ }
173
+ });
174
+ } catch (error) {
175
+ log.error('Agent error:', error);
176
+ return c.json({
177
+ error: {
178
+ message: error instanceof Error ? error.message : 'Internal server error',
179
+ type: 'api_error',
180
+ code: 'internal_error'
181
+ }
182
+ }, 500);
183
+ }
184
+ });
185
+ }
186
+
187
+ //# sourceMappingURL=agent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/chat/agent.ts"],"sourcesContent":["/**\n * Tool Loop Agent Handler\n * Uses AI SDK with MCP tools for agentic chat\n */\nimport { createOpenAICompatible } from '@ai-sdk/openai-compatible';\nimport { generateText, streamText, type Tool, stepCountIs } from 'ai';\nimport consola from 'consola';\nimport type { Hono } from 'hono';\nimport { streamSSE } from 'hono/streaming';\nimport type { ModelConfig } from '../server/schema';\n\nconst log = consola.withTag('agent');\n\nexport interface AgentHandlerOptions {\n\tresolveModelConfig: (modelName: string) => ModelConfig | null;\n\tgetMcpTools?: (servers?: string[]) => Promise<Record<string, Tool>>;\n}\n\nexport interface AgentRequest {\n\tmodel: string;\n\tmessages: Array<{\n\t\trole: 'user' | 'assistant' | 'system';\n\t\tcontent: string;\n\t}>;\n\ttools?: string[]; // Tool names to enable\n\tmcpServers?: string[]; // MCP server names to load tools from\n\tmaxSteps?: number; // Max tool loop iterations (default: 5)\n\tstream?: boolean;\n}\n\n/**\n * Normalize base URL - strip trailing /v1 if present\n */\nfunction normalizeBaseUrl(url: string): string {\n\treturn url.replace(/\\/v1\\/?$/, '');\n}\n\n/**\n * Register agent routes on Hono app\n */\nexport function registerAgentRoutes(app: Hono, options: AgentHandlerOptions) {\n\tconst { resolveModelConfig, getMcpTools } = options;\n\n\t/**\n\t * POST /v1/agent/chat\n\t * Agent chat with tool loop support\n\t */\n\tapp.post('/v1/agent/chat', async (c) => {\n\t\ttry {\n\t\t\tconst body = await c.req.json<AgentRequest>();\n\t\t\tconst { model: modelName, messages, mcpServers, maxSteps = 5, stream = false } = body;\n\n\t\t\t// Resolve model config\n\t\t\tconst modelConfig = resolveModelConfig(modelName);\n\t\t\tif (!modelConfig) {\n\t\t\t\treturn c.json(\n\t\t\t\t\t{\n\t\t\t\t\t\terror: {\n\t\t\t\t\t\t\tmessage: `Model not found: ${modelName}`,\n\t\t\t\t\t\t\ttype: 'invalid_request_error',\n\t\t\t\t\t\t\tcode: 'model_not_found',\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t404,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Get API key\n\t\t\tconst apiKey = modelConfig.apiKey;\n\t\t\tconst baseUrl = normalizeBaseUrl(modelConfig.baseUrl || '');\n\n\t\t\tif (!baseUrl) {\n\t\t\t\treturn c.json(\n\t\t\t\t\t{\n\t\t\t\t\t\terror: {\n\t\t\t\t\t\t\tmessage: 'Model has no baseUrl configured',\n\t\t\t\t\t\t\ttype: 'invalid_request_error',\n\t\t\t\t\t\t\tcode: 'invalid_model_config',\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\t400,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\t// Create AI provider\n\t\t\tconst provider = createOpenAICompatible({\n\t\t\t\tname: 'mcps-agent',\n\t\t\t\tbaseURL: `${baseUrl}/v1`,\n\t\t\t\tapiKey: apiKey || 'not-needed',\n\t\t\t});\n\n\t\t\tconst aiModel = provider.chatModel(modelName);\n\n\t\t\t// Get MCP tools if available\n\t\t\tlet tools: Record<string, Tool> = {};\n\t\t\tif (getMcpTools && mcpServers && mcpServers.length > 0) {\n\t\t\t\ttry {\n\t\t\t\t\ttools = await getMcpTools(mcpServers);\n\t\t\t\t\tlog.info(`Loaded ${Object.keys(tools).length} tools from MCP servers: ${mcpServers.join(', ')}`);\n\t\t\t\t} catch (err) {\n\t\t\t\t\tlog.warn('Failed to load MCP tools:', err);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Convert messages\n\t\t\tconst aiMessages = messages.map((m) => ({\n\t\t\t\trole: m.role as 'user' | 'assistant' | 'system',\n\t\t\t\tcontent: m.content,\n\t\t\t}));\n\n\t\t\tlog.info(\n\t\t\t\t`→ POST /v1/agent/chat model=${modelName} messages=${messages.length} tools=${Object.keys(tools).length} maxSteps=${maxSteps}`,\n\t\t\t);\n\n\t\t\tif (stream) {\n\t\t\t\t// Streaming response\n\t\t\t\treturn streamSSE(c, async (sseStream) => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst result = streamText({\n\t\t\t\t\t\t\tmodel: aiModel,\n\t\t\t\t\t\t\tmessages: aiMessages,\n\t\t\t\t\t\t\ttools: Object.keys(tools).length > 0 ? tools : undefined,\n\t\t\t\t\t\t\tstopWhen: stepCountIs(maxSteps),\n\t\t\t\t\t\t\tonStepFinish: async (step) => {\n\t\t\t\t\t\t\t\t// Send step info\n\t\t\t\t\t\t\t\tawait sseStream.writeSSE({\n\t\t\t\t\t\t\t\t\tdata: JSON.stringify({\n\t\t\t\t\t\t\t\t\t\ttype: 'step',\n\t\t\t\t\t\t\t\t\t\ttext: step.text,\n\t\t\t\t\t\t\t\t\t\ttoolCalls: step.toolCalls?.map((tc) => ({\n\t\t\t\t\t\t\t\t\t\t\tid: tc.toolCallId,\n\t\t\t\t\t\t\t\t\t\t\tname: tc.toolName,\n\t\t\t\t\t\t\t\t\t\t\targuments: 'input' in tc ? tc.input : undefined,\n\t\t\t\t\t\t\t\t\t\t})),\n\t\t\t\t\t\t\t\t\t\ttoolResults: step.toolResults?.map((tr) => ({\n\t\t\t\t\t\t\t\t\t\t\tid: tr.toolCallId,\n\t\t\t\t\t\t\t\t\t\t\tname: tr.toolName,\n\t\t\t\t\t\t\t\t\t\t\tresult: 'output' in tr ? tr.output : undefined,\n\t\t\t\t\t\t\t\t\t\t})),\n\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\t// Stream text deltas\n\t\t\t\t\t\tfor await (const part of result.textStream) {\n\t\t\t\t\t\t\tawait sseStream.writeSSE({\n\t\t\t\t\t\t\t\tdata: JSON.stringify({\n\t\t\t\t\t\t\t\t\ttype: 'text',\n\t\t\t\t\t\t\t\t\tcontent: part,\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Get final result\n\t\t\t\t\t\tconst finalResult = await result;\n\t\t\t\t\t\tconst usage = await finalResult.usage;\n\t\t\t\t\t\tconst steps = await finalResult.steps;\n\n\t\t\t\t\t\t// Send usage info\n\t\t\t\t\t\tawait sseStream.writeSSE({\n\t\t\t\t\t\t\tdata: JSON.stringify({\n\t\t\t\t\t\t\t\ttype: 'usage',\n\t\t\t\t\t\t\t\tusage: {\n\t\t\t\t\t\t\t\t\tpromptTokens: usage?.inputTokens,\n\t\t\t\t\t\t\t\t\tcompletionTokens: usage?.outputTokens,\n\t\t\t\t\t\t\t\t\ttotalTokens: (usage?.inputTokens || 0) + (usage?.outputTokens || 0),\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t});\n\n\t\t\t\t\t\t// Send done\n\t\t\t\t\t\tawait sseStream.writeSSE({ data: '[DONE]' });\n\n\t\t\t\t\t\tlog.info(`← 200 /v1/agent/chat model=${modelName} steps=${steps?.length || 1}`);\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\tlog.error('Agent streaming error:', err);\n\t\t\t\t\t\tawait sseStream.writeSSE({\n\t\t\t\t\t\t\tdata: JSON.stringify({\n\t\t\t\t\t\t\t\ttype: 'error',\n\t\t\t\t\t\t\t\terror: err instanceof Error ? err.message : 'Streaming error',\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// Non-streaming response\n\t\t\tconst result = await generateText({\n\t\t\t\tmodel: aiModel,\n\t\t\t\tmessages: aiMessages,\n\t\t\t\ttools: Object.keys(tools).length > 0 ? tools : undefined,\n\t\t\t\tstopWhen: stepCountIs(maxSteps),\n\t\t\t});\n\n\t\t\tlog.info(\n\t\t\t\t`← 200 /v1/agent/chat model=${modelName} steps=${result.steps?.length || 1} tokens=${result.usage?.totalTokens || 0}`,\n\t\t\t);\n\n\t\t\t// Format response\n\t\t\treturn c.json({\n\t\t\t\tid: `agent-${Date.now()}`,\n\t\t\t\tobject: 'agent.chat.completion',\n\t\t\t\tmodel: modelName,\n\t\t\t\tcontent: result.text,\n\t\t\t\treasoning: result.reasoning,\n\t\t\t\tsteps: result.steps?.map((step) => ({\n\t\t\t\t\ttext: step.text,\n\t\t\t\t\ttoolCalls: step.toolCalls?.map((tc) => ({\n\t\t\t\t\t\tid: tc.toolCallId,\n\t\t\t\t\t\tname: tc.toolName,\n\t\t\t\t\t\targuments: 'input' in tc ? tc.input : undefined,\n\t\t\t\t\t})),\n\t\t\t\t\ttoolResults: step.toolResults?.map((tr) => ({\n\t\t\t\t\t\tid: tr.toolCallId,\n\t\t\t\t\t\tname: tr.toolName,\n\t\t\t\t\t\tresult: 'output' in tr ? tr.output : undefined,\n\t\t\t\t\t})),\n\t\t\t\t})),\n\t\t\t\tusage: {\n\t\t\t\t\tprompt_tokens: result.usage?.inputTokens,\n\t\t\t\t\tcompletion_tokens: result.usage?.outputTokens,\n\t\t\t\t\ttotal_tokens: (result.usage?.inputTokens || 0) + (result.usage?.outputTokens || 0),\n\t\t\t\t},\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tlog.error('Agent error:', error);\n\t\t\treturn c.json(\n\t\t\t\t{\n\t\t\t\t\terror: {\n\t\t\t\t\t\tmessage: error instanceof Error ? error.message : 'Internal server error',\n\t\t\t\t\t\ttype: 'api_error',\n\t\t\t\t\t\tcode: 'internal_error',\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t500,\n\t\t\t);\n\t\t}\n\t});\n}\n"],"names":["createOpenAICompatible","generateText","streamText","stepCountIs","consola","streamSSE","log","withTag","normalizeBaseUrl","url","replace","registerAgentRoutes","app","options","resolveModelConfig","getMcpTools","post","c","body","req","json","model","modelName","messages","mcpServers","maxSteps","stream","modelConfig","error","message","type","code","apiKey","baseUrl","provider","name","baseURL","aiModel","chatModel","tools","length","info","Object","keys","join","err","warn","aiMessages","map","m","role","content","sseStream","result","undefined","stopWhen","onStepFinish","step","writeSSE","data","JSON","stringify","text","toolCalls","tc","id","toolCallId","toolName","arguments","input","toolResults","tr","output","part","textStream","finalResult","usage","steps","promptTokens","inputTokens","completionTokens","outputTokens","totalTokens","Error","Date","now","object","reasoning","prompt_tokens","completion_tokens","total_tokens"],"mappings":"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"}
@@ -0,0 +1,238 @@
1
+ /**
2
+ * Chat Request Audit Service
3
+ * Records all chat/LLM API requests for auditing and metering
4
+ */ import consola from 'consola';
5
+ const log = consola.withTag('chat-audit');
6
+ /**
7
+ * Re-export protocol and status constants for convenience
8
+ */ export const ChatProtocol = {
9
+ OPENAI: 'openai',
10
+ ANTHROPIC: 'anthropic',
11
+ GEMINI: 'gemini'
12
+ };
13
+ export const RequestStatus = {
14
+ PENDING: 'pending',
15
+ SUCCESS: 'success',
16
+ ERROR: 'error',
17
+ TIMEOUT: 'timeout'
18
+ };
19
+ /**
20
+ * In-memory audit store implementation
21
+ */ export class InMemoryChatAuditStore {
22
+ records = [];
23
+ maxSize;
24
+ constructor(maxSize = 10000){
25
+ this.maxSize = maxSize;
26
+ }
27
+ async save(record) {
28
+ this.records.unshift(record);
29
+ // Trim to max size
30
+ if (this.records.length > this.maxSize) {
31
+ this.records = this.records.slice(0, this.maxSize);
32
+ }
33
+ log.debug(`Saved audit record: ${record.requestId} model=${record.model} status=${record.status}`);
34
+ }
35
+ async query(options) {
36
+ let filtered = [
37
+ ...this.records
38
+ ];
39
+ if (options.model) {
40
+ filtered = filtered.filter((r)=>r.model === options.model);
41
+ }
42
+ if (options.provider) {
43
+ filtered = filtered.filter((r)=>r.provider === options.provider);
44
+ }
45
+ if (options.status) {
46
+ filtered = filtered.filter((r)=>r.status === options.status);
47
+ }
48
+ if (options.userId) {
49
+ filtered = filtered.filter((r)=>r.userId === options.userId);
50
+ }
51
+ if (options.orgId) {
52
+ filtered = filtered.filter((r)=>r.orgId === options.orgId);
53
+ }
54
+ if (options.from) {
55
+ const from = options.from;
56
+ filtered = filtered.filter((r)=>r.requestedAt >= from);
57
+ }
58
+ if (options.to) {
59
+ const to = options.to;
60
+ filtered = filtered.filter((r)=>r.requestedAt <= to);
61
+ }
62
+ const total = filtered.length;
63
+ const offset = options.offset || 0;
64
+ const limit = options.limit || 50;
65
+ return {
66
+ records: filtered.slice(offset, offset + limit),
67
+ total
68
+ };
69
+ }
70
+ async getStats(options) {
71
+ let filtered = [
72
+ ...this.records
73
+ ];
74
+ if (options.from) {
75
+ const from = options.from;
76
+ filtered = filtered.filter((r)=>r.requestedAt >= from);
77
+ }
78
+ if (options.to) {
79
+ const to = options.to;
80
+ filtered = filtered.filter((r)=>r.requestedAt <= to);
81
+ }
82
+ const totalRequests = filtered.length;
83
+ const successfulRequests = filtered.filter((r)=>r.status === RequestStatus.SUCCESS).length;
84
+ const failedRequests = filtered.filter((r)=>r.status === RequestStatus.ERROR).length;
85
+ const totalInputTokens = filtered.reduce((sum, r)=>sum + (r.inputTokens || 0), 0);
86
+ const totalOutputTokens = filtered.reduce((sum, r)=>sum + (r.outputTokens || 0), 0);
87
+ const durations = filtered.map((r)=>r.durationMs).filter((d)=>d != null);
88
+ const avgDurationMs = durations.length > 0 ? durations.reduce((a, b)=>a + b, 0) / durations.length : 0;
89
+ // Group by model
90
+ const modelMap = new Map();
91
+ for (const r of filtered){
92
+ const existing = modelMap.get(r.model) || {
93
+ count: 0,
94
+ tokens: 0
95
+ };
96
+ existing.count++;
97
+ existing.tokens += (r.inputTokens || 0) + (r.outputTokens || 0);
98
+ modelMap.set(r.model, existing);
99
+ }
100
+ const byModel = Array.from(modelMap.entries()).map(([model, data])=>({
101
+ model,
102
+ ...data
103
+ })).sort((a, b)=>b.count - a.count);
104
+ // Group by provider
105
+ const providerMap = new Map();
106
+ for (const r of filtered){
107
+ const provider = r.provider || 'unknown';
108
+ const existing = providerMap.get(provider) || {
109
+ count: 0,
110
+ tokens: 0
111
+ };
112
+ existing.count++;
113
+ existing.tokens += (r.inputTokens || 0) + (r.outputTokens || 0);
114
+ providerMap.set(provider, existing);
115
+ }
116
+ const byProvider = Array.from(providerMap.entries()).map(([provider, data])=>({
117
+ provider,
118
+ ...data
119
+ })).sort((a, b)=>b.count - a.count);
120
+ return {
121
+ totalRequests,
122
+ successfulRequests,
123
+ failedRequests,
124
+ totalInputTokens,
125
+ totalOutputTokens,
126
+ avgDurationMs,
127
+ byModel,
128
+ byProvider
129
+ };
130
+ }
131
+ }
132
+ // Global audit store instance
133
+ let auditStore = new InMemoryChatAuditStore();
134
+ /**
135
+ * Set the audit store implementation
136
+ */ export function setChatAuditStore(store) {
137
+ auditStore = store;
138
+ }
139
+ /**
140
+ * Get the current audit store
141
+ */ export function getChatAuditStore() {
142
+ return auditStore;
143
+ }
144
+ /**
145
+ * Generate a unique request ID
146
+ */ export function generateRequestId() {
147
+ return `chat-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
148
+ }
149
+ /**
150
+ * Create an audit context for tracking a request
151
+ */ export function createAuditContext(options) {
152
+ return new ChatAuditContext(options);
153
+ }
154
+ /**
155
+ * Audit context for tracking a single request lifecycle
156
+ */ export class ChatAuditContext {
157
+ record;
158
+ startTime;
159
+ firstTokenTime;
160
+ constructor(options){
161
+ this.startTime = Date.now();
162
+ this.record = {
163
+ requestId: generateRequestId(),
164
+ requestedAt: new Date(),
165
+ status: RequestStatus.PENDING,
166
+ method: options.method,
167
+ endpoint: options.endpoint,
168
+ inputProtocol: options.inputProtocol,
169
+ outputProtocol: options.outputProtocol,
170
+ model: options.model,
171
+ streaming: options.streaming,
172
+ clientIp: options.clientIp,
173
+ userAgent: options.userAgent,
174
+ userId: options.userId,
175
+ requestMeta: options.requestMeta
176
+ };
177
+ }
178
+ get requestId() {
179
+ return this.record.requestId;
180
+ }
181
+ /**
182
+ * Set the resolved model and provider info
183
+ */ setProvider(options) {
184
+ Object.assign(this.record, options);
185
+ }
186
+ /**
187
+ * Record first token received (for TTFT)
188
+ */ recordFirstToken() {
189
+ if (!this.firstTokenTime) {
190
+ this.firstTokenTime = Date.now();
191
+ this.record.ttftMs = this.firstTokenTime - this.startTime;
192
+ }
193
+ }
194
+ /**
195
+ * Record token usage
196
+ */ setTokenUsage(input, output) {
197
+ this.record.inputTokens = input;
198
+ this.record.outputTokens = output;
199
+ this.record.totalTokens = input + output;
200
+ }
201
+ /**
202
+ * Set response metadata
203
+ */ setResponseMeta(meta) {
204
+ this.record.responseMeta = meta;
205
+ }
206
+ /**
207
+ * Get current duration in ms
208
+ */ getDuration() {
209
+ return Date.now() - this.startTime;
210
+ }
211
+ /**
212
+ * Complete the request successfully
213
+ */ async complete(httpStatus = 200) {
214
+ this.record.status = RequestStatus.SUCCESS;
215
+ this.record.httpStatus = httpStatus;
216
+ this.record.completedAt = new Date();
217
+ this.record.durationMs = Date.now() - this.startTime;
218
+ await auditStore.save(this.record);
219
+ }
220
+ /**
221
+ * Complete the request with an error
222
+ */ async error(errorMessage, errorCode, httpStatus = 500) {
223
+ this.record.status = RequestStatus.ERROR;
224
+ this.record.httpStatus = httpStatus;
225
+ this.record.errorMessage = errorMessage;
226
+ this.record.errorCode = errorCode;
227
+ this.record.completedAt = new Date();
228
+ this.record.durationMs = Date.now() - this.startTime;
229
+ await auditStore.save(this.record);
230
+ }
231
+ }
232
+ /**
233
+ * Extract client IP from Hono context
234
+ */ export function extractClientIp(c) {
235
+ return c.req.header('x-forwarded-for')?.split(',')[0]?.trim() || c.req.header('x-real-ip') || c.req.header('cf-connecting-ip');
236
+ }
237
+
238
+ //# sourceMappingURL=audit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/chat/audit.ts"],"sourcesContent":["/**\n * Chat Request Audit Service\n * Records all chat/LLM API requests for auditing and metering\n */\nimport type { Context } from 'hono';\nimport consola from 'consola';\nimport type { ChatProtocolType, RequestStatus as RequestStatusType, ChatAuditStats } from '../entities/types';\n\nconst log = consola.withTag('chat-audit');\n\n/**\n * Re-export protocol and status constants for convenience\n */\nexport const ChatProtocol = {\n\tOPENAI: 'openai' as ChatProtocolType,\n\tANTHROPIC: 'anthropic' as ChatProtocolType,\n\tGEMINI: 'gemini' as ChatProtocolType,\n};\n\nexport const RequestStatus = {\n\tPENDING: 'pending' as RequestStatusType,\n\tSUCCESS: 'success' as RequestStatusType,\n\tERROR: 'error' as RequestStatusType,\n\tTIMEOUT: 'timeout' as RequestStatusType,\n};\n\n/**\n * Chat audit record (in-memory representation)\n */\nexport interface ChatAuditRecord {\n\trequestId: string;\n\trequestedAt: Date;\n\tcompletedAt?: Date;\n\tstatus: RequestStatusType;\n\tmethod: string;\n\tendpoint: string;\n\tinputProtocol: ChatProtocolType;\n\toutputProtocol: ChatProtocolType;\n\tmodel: string;\n\tresolvedModel?: string;\n\tprovider?: string;\n\tupstreamUrl?: string;\n\tstreaming: boolean;\n\tinputTokens?: number;\n\toutputTokens?: number;\n\ttotalTokens?: number;\n\tdurationMs?: number;\n\tttftMs?: number;\n\thttpStatus?: number;\n\terrorMessage?: string;\n\terrorCode?: string;\n\tclientIp?: string;\n\tuserAgent?: string;\n\tuserId?: string;\n\torgId?: string;\n\tapiKeyId?: string;\n\trequestMeta?: Record<string, unknown>;\n\tresponseMeta?: Record<string, unknown>;\n\tcost?: string;\n\tcurrency?: string;\n}\n\n/**\n * Audit store interface\n */\nexport interface ChatAuditStore {\n\t/**\n\t * Save a chat audit record\n\t */\n\tsave(record: ChatAuditRecord): Promise<void>;\n\n\t/**\n\t * Query audit records\n\t */\n\tquery(options: ChatAuditQueryOptions): Promise<{ records: ChatAuditRecord[]; total: number }>;\n\n\t/**\n\t * Get aggregate statistics\n\t */\n\tgetStats(options: { from?: Date; to?: Date }): Promise<import('../entities/types').ChatAuditStats>;\n}\n\nexport interface ChatAuditQueryOptions {\n\tlimit?: number;\n\toffset?: number;\n\tmodel?: string;\n\tprovider?: string;\n\tstatus?: RequestStatusType;\n\tfrom?: Date;\n\tto?: Date;\n\tuserId?: string;\n\torgId?: string;\n}\n\n// Re-export ChatAuditStats from entities\nexport type { ChatAuditStats } from '../entities/types';\n\n/**\n * In-memory audit store implementation\n */\nexport class InMemoryChatAuditStore implements ChatAuditStore {\n\tprivate records: ChatAuditRecord[] = [];\n\tprivate maxSize: number;\n\n\tconstructor(maxSize = 10000) {\n\t\tthis.maxSize = maxSize;\n\t}\n\n\tasync save(record: ChatAuditRecord): Promise<void> {\n\t\tthis.records.unshift(record);\n\n\t\t// Trim to max size\n\t\tif (this.records.length > this.maxSize) {\n\t\t\tthis.records = this.records.slice(0, this.maxSize);\n\t\t}\n\n\t\tlog.debug(`Saved audit record: ${record.requestId} model=${record.model} status=${record.status}`);\n\t}\n\n\tasync query(options: ChatAuditQueryOptions): Promise<{ records: ChatAuditRecord[]; total: number }> {\n\t\tlet filtered = [...this.records];\n\n\t\tif (options.model) {\n\t\t\tfiltered = filtered.filter((r) => r.model === options.model);\n\t\t}\n\t\tif (options.provider) {\n\t\t\tfiltered = filtered.filter((r) => r.provider === options.provider);\n\t\t}\n\t\tif (options.status) {\n\t\t\tfiltered = filtered.filter((r) => r.status === options.status);\n\t\t}\n\t\tif (options.userId) {\n\t\t\tfiltered = filtered.filter((r) => r.userId === options.userId);\n\t\t}\n\t\tif (options.orgId) {\n\t\t\tfiltered = filtered.filter((r) => r.orgId === options.orgId);\n\t\t}\n\t\tif (options.from) {\n\t\t\tconst from = options.from;\n\t\t\tfiltered = filtered.filter((r) => r.requestedAt >= from);\n\t\t}\n\t\tif (options.to) {\n\t\t\tconst to = options.to;\n\t\t\tfiltered = filtered.filter((r) => r.requestedAt <= to);\n\t\t}\n\n\t\tconst total = filtered.length;\n\t\tconst offset = options.offset || 0;\n\t\tconst limit = options.limit || 50;\n\n\t\treturn {\n\t\t\trecords: filtered.slice(offset, offset + limit),\n\t\t\ttotal,\n\t\t};\n\t}\n\n\tasync getStats(options: { from?: Date; to?: Date }): Promise<ChatAuditStats> {\n\t\tlet filtered = [...this.records];\n\n\t\tif (options.from) {\n\t\t\tconst from = options.from;\n\t\t\tfiltered = filtered.filter((r) => r.requestedAt >= from);\n\t\t}\n\t\tif (options.to) {\n\t\t\tconst to = options.to;\n\t\t\tfiltered = filtered.filter((r) => r.requestedAt <= to);\n\t\t}\n\n\t\tconst totalRequests = filtered.length;\n\t\tconst successfulRequests = filtered.filter((r) => r.status === RequestStatus.SUCCESS).length;\n\t\tconst failedRequests = filtered.filter((r) => r.status === RequestStatus.ERROR).length;\n\n\t\tconst totalInputTokens = filtered.reduce((sum, r) => sum + (r.inputTokens || 0), 0);\n\t\tconst totalOutputTokens = filtered.reduce((sum, r) => sum + (r.outputTokens || 0), 0);\n\n\t\tconst durations = filtered.map((r) => r.durationMs).filter((d): d is number => d != null);\n\t\tconst avgDurationMs = durations.length > 0 ? durations.reduce((a, b) => a + b, 0) / durations.length : 0;\n\n\t\t// Group by model\n\t\tconst modelMap = new Map<string, { count: number; tokens: number }>();\n\t\tfor (const r of filtered) {\n\t\t\tconst existing = modelMap.get(r.model) || { count: 0, tokens: 0 };\n\t\t\texisting.count++;\n\t\t\texisting.tokens += (r.inputTokens || 0) + (r.outputTokens || 0);\n\t\t\tmodelMap.set(r.model, existing);\n\t\t}\n\t\tconst byModel = Array.from(modelMap.entries())\n\t\t\t.map(([model, data]) => ({ model, ...data }))\n\t\t\t.sort((a, b) => b.count - a.count);\n\n\t\t// Group by provider\n\t\tconst providerMap = new Map<string, { count: number; tokens: number }>();\n\t\tfor (const r of filtered) {\n\t\t\tconst provider = r.provider || 'unknown';\n\t\t\tconst existing = providerMap.get(provider) || { count: 0, tokens: 0 };\n\t\t\texisting.count++;\n\t\t\texisting.tokens += (r.inputTokens || 0) + (r.outputTokens || 0);\n\t\t\tproviderMap.set(provider, existing);\n\t\t}\n\t\tconst byProvider = Array.from(providerMap.entries())\n\t\t\t.map(([provider, data]) => ({ provider, ...data }))\n\t\t\t.sort((a, b) => b.count - a.count);\n\n\t\treturn {\n\t\t\ttotalRequests,\n\t\t\tsuccessfulRequests,\n\t\t\tfailedRequests,\n\t\t\ttotalInputTokens,\n\t\t\ttotalOutputTokens,\n\t\t\tavgDurationMs,\n\t\t\tbyModel,\n\t\t\tbyProvider,\n\t\t};\n\t}\n}\n\n// Global audit store instance\nlet auditStore: ChatAuditStore = new InMemoryChatAuditStore();\n\n/**\n * Set the audit store implementation\n */\nexport function setChatAuditStore(store: ChatAuditStore) {\n\tauditStore = store;\n}\n\n/**\n * Get the current audit store\n */\nexport function getChatAuditStore(): ChatAuditStore {\n\treturn auditStore;\n}\n\n/**\n * Generate a unique request ID\n */\nexport function generateRequestId(): string {\n\treturn `chat-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;\n}\n\n/**\n * Create an audit context for tracking a request\n */\nexport function createAuditContext(options: {\n\tmethod: string;\n\tendpoint: string;\n\tmodel: string;\n\tinputProtocol: ChatProtocolType;\n\toutputProtocol: ChatProtocolType;\n\tstreaming: boolean;\n\tclientIp?: string;\n\tuserAgent?: string;\n\tuserId?: string;\n\trequestMeta?: Record<string, unknown>;\n}): ChatAuditContext {\n\treturn new ChatAuditContext(options);\n}\n\n/**\n * Audit context for tracking a single request lifecycle\n */\nexport class ChatAuditContext {\n\tprivate record: ChatAuditRecord;\n\tprivate startTime: number;\n\tprivate firstTokenTime?: number;\n\n\tconstructor(options: {\n\t\tmethod: string;\n\t\tendpoint: string;\n\t\tmodel: string;\n\t\tinputProtocol: ChatProtocolType;\n\t\toutputProtocol: ChatProtocolType;\n\t\tstreaming: boolean;\n\t\tclientIp?: string;\n\t\tuserAgent?: string;\n\t\tuserId?: string;\n\t\trequestMeta?: Record<string, unknown>;\n\t}) {\n\t\tthis.startTime = Date.now();\n\t\tthis.record = {\n\t\t\trequestId: generateRequestId(),\n\t\t\trequestedAt: new Date(),\n\t\t\tstatus: RequestStatus.PENDING,\n\t\t\tmethod: options.method,\n\t\t\tendpoint: options.endpoint,\n\t\t\tinputProtocol: options.inputProtocol,\n\t\t\toutputProtocol: options.outputProtocol,\n\t\t\tmodel: options.model,\n\t\t\tstreaming: options.streaming,\n\t\t\tclientIp: options.clientIp,\n\t\t\tuserAgent: options.userAgent,\n\t\t\tuserId: options.userId,\n\t\t\trequestMeta: options.requestMeta,\n\t\t};\n\t}\n\n\tget requestId(): string {\n\t\treturn this.record.requestId;\n\t}\n\n\t/**\n\t * Set the resolved model and provider info\n\t */\n\tsetProvider(options: { resolvedModel?: string; provider?: string; upstreamUrl?: string }) {\n\t\tObject.assign(this.record, options);\n\t}\n\n\t/**\n\t * Record first token received (for TTFT)\n\t */\n\trecordFirstToken() {\n\t\tif (!this.firstTokenTime) {\n\t\t\tthis.firstTokenTime = Date.now();\n\t\t\tthis.record.ttftMs = this.firstTokenTime - this.startTime;\n\t\t}\n\t}\n\n\t/**\n\t * Record token usage\n\t */\n\tsetTokenUsage(input: number, output: number) {\n\t\tthis.record.inputTokens = input;\n\t\tthis.record.outputTokens = output;\n\t\tthis.record.totalTokens = input + output;\n\t}\n\n\t/**\n\t * Set response metadata\n\t */\n\tsetResponseMeta(meta: Record<string, unknown>) {\n\t\tthis.record.responseMeta = meta;\n\t}\n\n\t/**\n\t * Get current duration in ms\n\t */\n\tgetDuration(): number {\n\t\treturn Date.now() - this.startTime;\n\t}\n\n\t/**\n\t * Complete the request successfully\n\t */\n\tasync complete(httpStatus: number = 200) {\n\t\tthis.record.status = RequestStatus.SUCCESS;\n\t\tthis.record.httpStatus = httpStatus;\n\t\tthis.record.completedAt = new Date();\n\t\tthis.record.durationMs = Date.now() - this.startTime;\n\n\t\tawait auditStore.save(this.record);\n\t}\n\n\t/**\n\t * Complete the request with an error\n\t */\n\tasync error(errorMessage: string, errorCode?: string, httpStatus: number = 500) {\n\t\tthis.record.status = RequestStatus.ERROR;\n\t\tthis.record.httpStatus = httpStatus;\n\t\tthis.record.errorMessage = errorMessage;\n\t\tthis.record.errorCode = errorCode;\n\t\tthis.record.completedAt = new Date();\n\t\tthis.record.durationMs = Date.now() - this.startTime;\n\n\t\tawait auditStore.save(this.record);\n\t}\n}\n\n/**\n * Extract client IP from Hono context\n */\nexport function extractClientIp(c: Context): string | undefined {\n\treturn (\n\t\tc.req.header('x-forwarded-for')?.split(',')[0]?.trim() ||\n\t\tc.req.header('x-real-ip') ||\n\t\tc.req.header('cf-connecting-ip')\n\t);\n}\n"],"names":["consola","log","withTag","ChatProtocol","OPENAI","ANTHROPIC","GEMINI","RequestStatus","PENDING","SUCCESS","ERROR","TIMEOUT","InMemoryChatAuditStore","records","maxSize","save","record","unshift","length","slice","debug","requestId","model","status","query","options","filtered","filter","r","provider","userId","orgId","from","requestedAt","to","total","offset","limit","getStats","totalRequests","successfulRequests","failedRequests","totalInputTokens","reduce","sum","inputTokens","totalOutputTokens","outputTokens","durations","map","durationMs","d","avgDurationMs","a","b","modelMap","Map","existing","get","count","tokens","set","byModel","Array","entries","data","sort","providerMap","byProvider","auditStore","setChatAuditStore","store","getChatAuditStore","generateRequestId","Date","now","Math","random","toString","substring","createAuditContext","ChatAuditContext","startTime","firstTokenTime","method","endpoint","inputProtocol","outputProtocol","streaming","clientIp","userAgent","requestMeta","setProvider","Object","assign","recordFirstToken","ttftMs","setTokenUsage","input","output","totalTokens","setResponseMeta","meta","responseMeta","getDuration","complete","httpStatus","completedAt","error","errorMessage","errorCode","extractClientIp","c","req","header","split","trim"],"mappings":"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"}