@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.
- package/LICENSE +21 -0
- package/dist/index.mjs +15 -0
- package/dist/mcps-cli.mjs +174727 -0
- package/lib/chat/agent.js +187 -0
- package/lib/chat/agent.js.map +1 -0
- package/lib/chat/audit.js +238 -0
- package/lib/chat/audit.js.map +1 -0
- package/lib/chat/converters.js +467 -0
- package/lib/chat/converters.js.map +1 -0
- package/lib/chat/handler.js +1068 -0
- package/lib/chat/handler.js.map +1 -0
- package/lib/chat/index.js +12 -0
- package/lib/chat/index.js.map +1 -0
- package/lib/chat/types.js +35 -0
- package/lib/chat/types.js.map +1 -0
- package/lib/contracts/AuditContract.js +85 -0
- package/lib/contracts/AuditContract.js.map +1 -0
- package/lib/contracts/McpsContract.js +113 -0
- package/lib/contracts/McpsContract.js.map +1 -0
- package/lib/contracts/index.js +3 -0
- package/lib/contracts/index.js.map +1 -0
- package/lib/dev.server.js +7 -0
- package/lib/dev.server.js.map +1 -0
- package/lib/entities/ChatRequestEntity.js +318 -0
- package/lib/entities/ChatRequestEntity.js.map +1 -0
- package/lib/entities/McpRequestEntity.js +271 -0
- package/lib/entities/McpRequestEntity.js.map +1 -0
- package/lib/entities/RequestLogEntity.js +177 -0
- package/lib/entities/RequestLogEntity.js.map +1 -0
- package/lib/entities/ResponseEntity.js +150 -0
- package/lib/entities/ResponseEntity.js.map +1 -0
- package/lib/entities/index.js +11 -0
- package/lib/entities/index.js.map +1 -0
- package/lib/entities/types.js +11 -0
- package/lib/entities/types.js.map +1 -0
- package/lib/index.js +3 -0
- package/lib/index.js.map +1 -0
- package/lib/mcps-cli.js +44 -0
- package/lib/mcps-cli.js.map +1 -0
- package/lib/providers/McpServerHandlerDef.js +40 -0
- package/lib/providers/McpServerHandlerDef.js.map +1 -0
- package/lib/providers/findMcpServerDef.js +26 -0
- package/lib/providers/findMcpServerDef.js.map +1 -0
- package/lib/providers/prometheus/def.js +24 -0
- package/lib/providers/prometheus/def.js.map +1 -0
- package/lib/providers/prometheus/index.js +2 -0
- package/lib/providers/prometheus/index.js.map +1 -0
- package/lib/providers/relay/def.js +32 -0
- package/lib/providers/relay/def.js.map +1 -0
- package/lib/providers/relay/index.js +2 -0
- package/lib/providers/relay/index.js.map +1 -0
- package/lib/providers/sql/def.js +31 -0
- package/lib/providers/sql/def.js.map +1 -0
- package/lib/providers/sql/index.js +2 -0
- package/lib/providers/sql/index.js.map +1 -0
- package/lib/providers/tencent-cls/def.js +44 -0
- package/lib/providers/tencent-cls/def.js.map +1 -0
- package/lib/providers/tencent-cls/index.js +2 -0
- package/lib/providers/tencent-cls/index.js.map +1 -0
- package/lib/scripts/bundle.js +90 -0
- package/lib/scripts/bundle.js.map +1 -0
- package/lib/server/api-routes.js +96 -0
- package/lib/server/api-routes.js.map +1 -0
- package/lib/server/audit.js +274 -0
- package/lib/server/audit.js.map +1 -0
- package/lib/server/chat-routes.js +82 -0
- package/lib/server/chat-routes.js.map +1 -0
- package/lib/server/config.js +223 -0
- package/lib/server/config.js.map +1 -0
- package/lib/server/db.js +97 -0
- package/lib/server/db.js.map +1 -0
- package/lib/server/index.js +2 -0
- package/lib/server/index.js.map +1 -0
- package/lib/server/mcp-handler.js +167 -0
- package/lib/server/mcp-handler.js.map +1 -0
- package/lib/server/mcp-routes.js +112 -0
- package/lib/server/mcp-routes.js.map +1 -0
- package/lib/server/mcps-router.js +119 -0
- package/lib/server/mcps-router.js.map +1 -0
- package/lib/server/schema.js +129 -0
- package/lib/server/schema.js.map +1 -0
- package/lib/server/server.js +166 -0
- package/lib/server/server.js.map +1 -0
- package/lib/web/ChatPage.js +827 -0
- package/lib/web/ChatPage.js.map +1 -0
- package/lib/web/McpInspectorPage.js +214 -0
- package/lib/web/McpInspectorPage.js.map +1 -0
- package/lib/web/ServersPage.js +93 -0
- package/lib/web/ServersPage.js.map +1 -0
- package/lib/web/main.js +541 -0
- package/lib/web/main.js.map +1 -0
- package/package.json +83 -0
- package/src/chat/agent.ts +240 -0
- package/src/chat/audit.ts +377 -0
- package/src/chat/converters.test.ts +325 -0
- package/src/chat/converters.ts +459 -0
- package/src/chat/handler.test.ts +137 -0
- package/src/chat/handler.ts +1233 -0
- package/src/chat/index.ts +16 -0
- package/src/chat/types.ts +72 -0
- package/src/contracts/AuditContract.ts +93 -0
- package/src/contracts/McpsContract.ts +141 -0
- package/src/contracts/index.ts +18 -0
- package/src/dev.server.ts +7 -0
- package/src/entities/ChatRequestEntity.ts +157 -0
- package/src/entities/McpRequestEntity.ts +149 -0
- package/src/entities/RequestLogEntity.ts +78 -0
- package/src/entities/ResponseEntity.ts +75 -0
- package/src/entities/index.ts +12 -0
- package/src/entities/types.ts +188 -0
- package/src/index.ts +1 -0
- package/src/mcps-cli.ts +59 -0
- package/src/providers/McpServerHandlerDef.ts +105 -0
- package/src/providers/findMcpServerDef.ts +31 -0
- package/src/providers/prometheus/def.ts +21 -0
- package/src/providers/prometheus/index.ts +1 -0
- package/src/providers/relay/def.ts +31 -0
- package/src/providers/relay/index.ts +1 -0
- package/src/providers/relay/relay.test.ts +47 -0
- package/src/providers/sql/def.ts +33 -0
- package/src/providers/sql/index.ts +1 -0
- package/src/providers/tencent-cls/def.ts +38 -0
- package/src/providers/tencent-cls/index.ts +1 -0
- package/src/scripts/bundle.ts +82 -0
- package/src/server/api-routes.ts +98 -0
- package/src/server/audit.ts +310 -0
- package/src/server/chat-routes.ts +95 -0
- package/src/server/config.test.ts +162 -0
- package/src/server/config.ts +198 -0
- package/src/server/db.ts +115 -0
- package/src/server/index.ts +1 -0
- package/src/server/mcp-handler.ts +209 -0
- package/src/server/mcp-routes.ts +133 -0
- package/src/server/mcps-router.ts +133 -0
- package/src/server/schema.ts +175 -0
- package/src/server/server.ts +163 -0
- package/src/web/ChatPage.tsx +1005 -0
- package/src/web/McpInspectorPage.tsx +254 -0
- package/src/web/ServersPage.tsx +139 -0
- package/src/web/main.tsx +600 -0
- 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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"}
|