@abassey/aid 0.1.0
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/dist/agents/index.cjs +741 -0
- package/dist/agents/index.d.cts +78 -0
- package/dist/agents/index.d.ts +78 -0
- package/dist/agents/index.js +741 -0
- package/dist/ai-AWJOUXFM.js +9 -0
- package/dist/ai-DOAYJKKI.cjs +9 -0
- package/dist/chunk-2TNYBUNK.js +124 -0
- package/dist/chunk-3LGKZRGY.cjs +124 -0
- package/dist/chunk-AUR2BBB5.cjs +1436 -0
- package/dist/chunk-IJLTRQF4.cjs +276 -0
- package/dist/chunk-JPD7UBAZ.js +58 -0
- package/dist/chunk-M4RQALTT.js +276 -0
- package/dist/chunk-NB65IHJE.cjs +58 -0
- package/dist/chunk-YNIEOBDF.js +1436 -0
- package/dist/client/index.cjs +18 -0
- package/dist/client/index.d.cts +8 -0
- package/dist/client/index.d.ts +8 -0
- package/dist/client/index.js +18 -0
- package/dist/errors-CUVTnseb.d.ts +13 -0
- package/dist/errors-CgCce4cK.d.cts +158 -0
- package/dist/errors-CgCce4cK.d.ts +158 -0
- package/dist/errors-zAPbTlpe.d.cts +13 -0
- package/dist/eval/index.cjs +308 -0
- package/dist/eval/index.d.cts +106 -0
- package/dist/eval/index.d.ts +106 -0
- package/dist/eval/index.js +308 -0
- package/dist/index.cjs +35 -0
- package/dist/index.d.cts +107 -0
- package/dist/index.d.ts +107 -0
- package/dist/index.js +35 -0
- package/dist/middleware/index.cjs +201 -0
- package/dist/middleware/index.d.cts +36 -0
- package/dist/middleware/index.d.ts +36 -0
- package/dist/middleware/index.js +201 -0
- package/dist/observability/index.cjs +147 -0
- package/dist/observability/index.d.cts +30 -0
- package/dist/observability/index.d.ts +30 -0
- package/dist/observability/index.js +147 -0
- package/dist/react/index.cjs +253 -0
- package/dist/react/index.d.cts +64 -0
- package/dist/react/index.d.ts +64 -0
- package/dist/react/index.js +253 -0
- package/dist/serve/index.cjs +545 -0
- package/dist/serve/index.d.cts +69 -0
- package/dist/serve/index.d.ts +69 -0
- package/dist/serve/index.js +545 -0
- package/dist/types-BJReASS-.d.cts +196 -0
- package/dist/types-BJReASS-.d.ts +196 -0
- package/dist/types-CguX3F16.d.cts +173 -0
- package/dist/types-CrFH-_qp.d.cts +68 -0
- package/dist/types-DvdzPmW0.d.ts +173 -0
- package/dist/types-qfE32ADy.d.ts +68 -0
- package/package.json +144 -0
|
@@ -0,0 +1,545 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ai
|
|
3
|
+
} from "../chunk-YNIEOBDF.js";
|
|
4
|
+
import {
|
|
5
|
+
AidError,
|
|
6
|
+
MODEL_MAP
|
|
7
|
+
} from "../chunk-2TNYBUNK.js";
|
|
8
|
+
|
|
9
|
+
// src/serve/index.ts
|
|
10
|
+
import http from "http";
|
|
11
|
+
|
|
12
|
+
// src/serve/router.ts
|
|
13
|
+
function matchRoute(routes, method, url) {
|
|
14
|
+
const path = (url.split("?")[0] ?? url).replace(/\/+$/, "") || "/";
|
|
15
|
+
const pathSegments = path.split("/").filter(Boolean);
|
|
16
|
+
const patternMatches = [];
|
|
17
|
+
for (const route of routes) {
|
|
18
|
+
const patternSegments = route.pattern.split("/").filter(Boolean);
|
|
19
|
+
if (patternSegments.length !== pathSegments.length) continue;
|
|
20
|
+
const params = {};
|
|
21
|
+
let isMatch = true;
|
|
22
|
+
for (let i = 0; i < patternSegments.length; i++) {
|
|
23
|
+
const seg = patternSegments[i];
|
|
24
|
+
if (seg.startsWith(":")) {
|
|
25
|
+
params[seg.slice(1)] = pathSegments[i];
|
|
26
|
+
} else if (seg !== pathSegments[i]) {
|
|
27
|
+
isMatch = false;
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (isMatch) {
|
|
32
|
+
patternMatches.push({ route, params });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (patternMatches.length === 0) {
|
|
36
|
+
return { matched: false, params: {}, status: 404 };
|
|
37
|
+
}
|
|
38
|
+
const methodMatch = patternMatches.find(
|
|
39
|
+
(m) => m.route.method === method
|
|
40
|
+
);
|
|
41
|
+
if (methodMatch) {
|
|
42
|
+
return {
|
|
43
|
+
matched: true,
|
|
44
|
+
route: methodMatch.route,
|
|
45
|
+
params: methodMatch.params
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
const allowedMethods = [
|
|
49
|
+
...new Set(patternMatches.map((m) => m.route.method))
|
|
50
|
+
];
|
|
51
|
+
return {
|
|
52
|
+
matched: false,
|
|
53
|
+
params: {},
|
|
54
|
+
status: 405,
|
|
55
|
+
allowedMethods
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// src/serve/sse.ts
|
|
60
|
+
var SSE_HEADERS = {
|
|
61
|
+
"Content-Type": "text/event-stream",
|
|
62
|
+
"Cache-Control": "no-cache",
|
|
63
|
+
"Connection": "keep-alive"
|
|
64
|
+
};
|
|
65
|
+
function sendEvent(res, data) {
|
|
66
|
+
res.write(`data: ${JSON.stringify(data)}
|
|
67
|
+
|
|
68
|
+
`);
|
|
69
|
+
}
|
|
70
|
+
async function writeStreamSSE(res, stream) {
|
|
71
|
+
res.writeHead(200, SSE_HEADERS);
|
|
72
|
+
let closed = false;
|
|
73
|
+
res.on("close", () => {
|
|
74
|
+
closed = true;
|
|
75
|
+
stream.abort();
|
|
76
|
+
});
|
|
77
|
+
try {
|
|
78
|
+
for await (const chunk of stream) {
|
|
79
|
+
if (closed) break;
|
|
80
|
+
if (chunk.done && chunk.response) {
|
|
81
|
+
sendEvent(res, {
|
|
82
|
+
type: "done",
|
|
83
|
+
text: chunk.response.text,
|
|
84
|
+
model: chunk.response.model,
|
|
85
|
+
tokens: chunk.response.tokens,
|
|
86
|
+
cost: chunk.response.cost,
|
|
87
|
+
latencyMs: chunk.response.latencyMs
|
|
88
|
+
});
|
|
89
|
+
} else {
|
|
90
|
+
sendEvent(res, { type: "text", text: chunk.delta });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
} catch (err) {
|
|
94
|
+
if (!closed) {
|
|
95
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
96
|
+
const code = err instanceof AidError ? err.code : "provider_error";
|
|
97
|
+
sendEvent(res, { type: "error", code, message });
|
|
98
|
+
}
|
|
99
|
+
} finally {
|
|
100
|
+
if (!closed) {
|
|
101
|
+
res.end();
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
async function writeAgentSSE(res, agent, task) {
|
|
106
|
+
res.writeHead(200, SSE_HEADERS);
|
|
107
|
+
let closed = false;
|
|
108
|
+
res.on("close", () => {
|
|
109
|
+
closed = true;
|
|
110
|
+
});
|
|
111
|
+
const send = (data) => {
|
|
112
|
+
if (!closed) sendEvent(res, data);
|
|
113
|
+
};
|
|
114
|
+
const onStepStart = (payload) => {
|
|
115
|
+
send({ type: "step:start", step: payload.step });
|
|
116
|
+
};
|
|
117
|
+
const onStepEnd = (payload) => {
|
|
118
|
+
send({ type: "step:end", step: payload.step });
|
|
119
|
+
};
|
|
120
|
+
const onToolCall = (payload) => {
|
|
121
|
+
send({ type: "tool:call", name: payload.name, args: payload.args });
|
|
122
|
+
};
|
|
123
|
+
const onToolResult = (payload) => {
|
|
124
|
+
send({ type: "tool:result", name: payload.name, result: payload.result });
|
|
125
|
+
};
|
|
126
|
+
const onMessage = (payload) => {
|
|
127
|
+
send({ type: "text", text: payload.content });
|
|
128
|
+
};
|
|
129
|
+
const onError = (payload) => {
|
|
130
|
+
send({ type: "error", code: payload.error.code, message: payload.error.message });
|
|
131
|
+
};
|
|
132
|
+
agent.on("step:start", onStepStart);
|
|
133
|
+
agent.on("step:end", onStepEnd);
|
|
134
|
+
agent.on("tool:call", onToolCall);
|
|
135
|
+
agent.on("tool:result", onToolResult);
|
|
136
|
+
agent.on("message", onMessage);
|
|
137
|
+
agent.on("error", onError);
|
|
138
|
+
const cleanup = () => {
|
|
139
|
+
agent.off("step:start", onStepStart);
|
|
140
|
+
agent.off("step:end", onStepEnd);
|
|
141
|
+
agent.off("tool:call", onToolCall);
|
|
142
|
+
agent.off("tool:result", onToolResult);
|
|
143
|
+
agent.off("message", onMessage);
|
|
144
|
+
agent.off("error", onError);
|
|
145
|
+
};
|
|
146
|
+
try {
|
|
147
|
+
const result = await agent.run(task);
|
|
148
|
+
if (!closed) {
|
|
149
|
+
sendEvent(res, {
|
|
150
|
+
type: "done",
|
|
151
|
+
text: result.text,
|
|
152
|
+
tokens: result.tokens,
|
|
153
|
+
cost: result.cost,
|
|
154
|
+
steps: result.steps.length
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
} catch (err) {
|
|
158
|
+
if (!closed) {
|
|
159
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
160
|
+
const code = err instanceof AidError ? err.code : "provider_error";
|
|
161
|
+
sendEvent(res, { type: "error", code, message });
|
|
162
|
+
}
|
|
163
|
+
} finally {
|
|
164
|
+
cleanup();
|
|
165
|
+
if (!closed) {
|
|
166
|
+
res.end();
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// src/serve/chat-store.ts
|
|
172
|
+
import { randomUUID } from "crypto";
|
|
173
|
+
var InMemoryChatStore = class {
|
|
174
|
+
sessions = /* @__PURE__ */ new Map();
|
|
175
|
+
maxSessions;
|
|
176
|
+
sessionTtl;
|
|
177
|
+
chatConfig;
|
|
178
|
+
constructor(options) {
|
|
179
|
+
this.maxSessions = options?.maxSessions ?? 1e3;
|
|
180
|
+
this.sessionTtl = options?.sessionTtl ?? 36e5;
|
|
181
|
+
this.chatConfig = {
|
|
182
|
+
system: options?.system,
|
|
183
|
+
model: options?.model,
|
|
184
|
+
maxHistory: options?.maxHistory
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
create(options) {
|
|
188
|
+
if (this.sessions.size >= this.maxSessions) {
|
|
189
|
+
this.evictOldest();
|
|
190
|
+
}
|
|
191
|
+
const system = options?.system ?? this.chatConfig.system;
|
|
192
|
+
const model = options?.model ?? this.chatConfig.model;
|
|
193
|
+
const conversation = ai.conversation({
|
|
194
|
+
system,
|
|
195
|
+
model,
|
|
196
|
+
maxHistory: this.chatConfig.maxHistory
|
|
197
|
+
});
|
|
198
|
+
const now = Date.now();
|
|
199
|
+
const session = {
|
|
200
|
+
id: randomUUID(),
|
|
201
|
+
conversation,
|
|
202
|
+
createdAt: now,
|
|
203
|
+
lastActiveAt: now,
|
|
204
|
+
model,
|
|
205
|
+
system
|
|
206
|
+
};
|
|
207
|
+
this.sessions.set(session.id, session);
|
|
208
|
+
return session;
|
|
209
|
+
}
|
|
210
|
+
get(id) {
|
|
211
|
+
const session = this.sessions.get(id);
|
|
212
|
+
if (!session) return void 0;
|
|
213
|
+
if (Date.now() - session.lastActiveAt > this.sessionTtl) {
|
|
214
|
+
this.sessions.delete(id);
|
|
215
|
+
return void 0;
|
|
216
|
+
}
|
|
217
|
+
return session;
|
|
218
|
+
}
|
|
219
|
+
delete(id) {
|
|
220
|
+
return this.sessions.delete(id);
|
|
221
|
+
}
|
|
222
|
+
list() {
|
|
223
|
+
return Array.from(this.sessions.values()).map((s) => ({
|
|
224
|
+
id: s.id,
|
|
225
|
+
createdAt: s.createdAt,
|
|
226
|
+
lastActiveAt: s.lastActiveAt
|
|
227
|
+
}));
|
|
228
|
+
}
|
|
229
|
+
evictOldest() {
|
|
230
|
+
let oldest;
|
|
231
|
+
for (const session of this.sessions.values()) {
|
|
232
|
+
if (!oldest || session.lastActiveAt < oldest.lastActiveAt) {
|
|
233
|
+
oldest = session;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
if (oldest) {
|
|
237
|
+
this.sessions.delete(oldest.id);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
// src/serve/handler.ts
|
|
243
|
+
var MAX_BODY_SIZE = 1048576;
|
|
244
|
+
function sendJson(res, status, data) {
|
|
245
|
+
const body = JSON.stringify(data);
|
|
246
|
+
res.writeHead(status, {
|
|
247
|
+
"Content-Type": "application/json",
|
|
248
|
+
"Content-Length": Buffer.byteLength(body)
|
|
249
|
+
});
|
|
250
|
+
res.end(body);
|
|
251
|
+
}
|
|
252
|
+
function sendError(res, status, code, message) {
|
|
253
|
+
sendJson(res, status, { error: { code, message } });
|
|
254
|
+
}
|
|
255
|
+
function errorToStatus(code) {
|
|
256
|
+
switch (code) {
|
|
257
|
+
case "invalid_request":
|
|
258
|
+
return 400;
|
|
259
|
+
case "auth_error":
|
|
260
|
+
return 401;
|
|
261
|
+
case "not_found":
|
|
262
|
+
return 404;
|
|
263
|
+
case "rate_limit":
|
|
264
|
+
return 429;
|
|
265
|
+
case "timeout":
|
|
266
|
+
return 504;
|
|
267
|
+
default:
|
|
268
|
+
return 500;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
async function readBody(req) {
|
|
272
|
+
return new Promise((resolve, reject) => {
|
|
273
|
+
const chunks = [];
|
|
274
|
+
let size = 0;
|
|
275
|
+
req.on("data", (chunk) => {
|
|
276
|
+
size += chunk.length;
|
|
277
|
+
if (size > MAX_BODY_SIZE) {
|
|
278
|
+
req.destroy();
|
|
279
|
+
reject(new AidError("invalid_request", "Request body too large"));
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
chunks.push(chunk);
|
|
283
|
+
});
|
|
284
|
+
req.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
|
|
285
|
+
req.on("error", reject);
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
function applyCors(req, res, cors) {
|
|
289
|
+
if (!cors) return false;
|
|
290
|
+
if (cors === true) {
|
|
291
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
292
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
|
|
293
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
294
|
+
} else {
|
|
295
|
+
const origin = req.headers.origin;
|
|
296
|
+
if (origin && cors.origins.includes(origin)) {
|
|
297
|
+
res.setHeader("Access-Control-Allow-Origin", origin);
|
|
298
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
|
|
299
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
if (req.method === "OPTIONS") {
|
|
303
|
+
res.writeHead(204);
|
|
304
|
+
res.end();
|
|
305
|
+
return true;
|
|
306
|
+
}
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
function createHandler(config) {
|
|
310
|
+
const routes = [];
|
|
311
|
+
const agents = config.agents ?? {};
|
|
312
|
+
const chatConfig = config.chat === true ? {} : config.chat ? config.chat : void 0;
|
|
313
|
+
const chatStore = chatConfig ? new InMemoryChatStore({
|
|
314
|
+
maxSessions: chatConfig.maxSessions,
|
|
315
|
+
sessionTtl: chatConfig.sessionTtl,
|
|
316
|
+
system: chatConfig.system,
|
|
317
|
+
model: chatConfig.model,
|
|
318
|
+
maxHistory: chatConfig.maxHistory
|
|
319
|
+
}) : void 0;
|
|
320
|
+
const handleAi = async (_req, res, _params, body) => {
|
|
321
|
+
const { prompt, model, system, temperature, maxTokens } = body;
|
|
322
|
+
if (!prompt || typeof prompt !== "string") {
|
|
323
|
+
sendError(res, 400, "invalid_request", "Missing required field: prompt");
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
const response = await ai(prompt, { model, system, temperature, maxTokens });
|
|
327
|
+
sendJson(res, 200, {
|
|
328
|
+
text: response.text,
|
|
329
|
+
model: response.model,
|
|
330
|
+
tokens: response.tokens,
|
|
331
|
+
cost: response.cost,
|
|
332
|
+
latencyMs: response.latencyMs
|
|
333
|
+
});
|
|
334
|
+
};
|
|
335
|
+
const handleAiStream = async (_req, res, _params, body) => {
|
|
336
|
+
const { prompt, model, system, temperature, maxTokens } = body;
|
|
337
|
+
if (!prompt || typeof prompt !== "string") {
|
|
338
|
+
sendError(res, 400, "invalid_request", "Missing required field: prompt");
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
const stream = ai.stream(prompt, { model, system, temperature, maxTokens });
|
|
342
|
+
await writeStreamSSE(res, stream);
|
|
343
|
+
};
|
|
344
|
+
const handleAgent = async (_req, res, params, body) => {
|
|
345
|
+
const agentName = params.name;
|
|
346
|
+
const agentInstance = agents[agentName];
|
|
347
|
+
if (!agentInstance) {
|
|
348
|
+
sendError(res, 404, "not_found", `Agent '${agentName}' not found`);
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
const { task } = body;
|
|
352
|
+
if (!task || typeof task !== "string") {
|
|
353
|
+
sendError(res, 400, "invalid_request", "Missing required field: task");
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
const result = await agentInstance.run(task);
|
|
357
|
+
sendJson(res, 200, {
|
|
358
|
+
text: result.text,
|
|
359
|
+
model: agentInstance.config.model ?? "default",
|
|
360
|
+
tokens: result.tokens,
|
|
361
|
+
cost: result.cost,
|
|
362
|
+
steps: result.steps.length
|
|
363
|
+
});
|
|
364
|
+
};
|
|
365
|
+
const handleAgentStream = async (_req, res, params, body) => {
|
|
366
|
+
const agentName = params.name;
|
|
367
|
+
const agentInstance = agents[agentName];
|
|
368
|
+
if (!agentInstance) {
|
|
369
|
+
sendError(res, 404, "not_found", `Agent '${agentName}' not found`);
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
const { task } = body;
|
|
373
|
+
if (!task || typeof task !== "string") {
|
|
374
|
+
sendError(res, 400, "invalid_request", "Missing required field: task");
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
await writeAgentSSE(res, agentInstance, task);
|
|
378
|
+
};
|
|
379
|
+
const handleChatCreate = async (_req, res, _params, body) => {
|
|
380
|
+
const { system, model } = body ?? {};
|
|
381
|
+
const session = chatStore.create({ system, model });
|
|
382
|
+
sendJson(res, 201, { sessionId: session.id });
|
|
383
|
+
};
|
|
384
|
+
const handleChatMessage = async (_req, res, params, body) => {
|
|
385
|
+
const session = chatStore.get(params.id);
|
|
386
|
+
if (!session) {
|
|
387
|
+
sendError(res, 404, "not_found", `Session '${params.id}' not found`);
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
const { message } = body;
|
|
391
|
+
if (!message || typeof message !== "string") {
|
|
392
|
+
sendError(res, 400, "invalid_request", "Missing required field: message");
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
session.lastActiveAt = Date.now();
|
|
396
|
+
const response = await session.conversation.say(message);
|
|
397
|
+
sendJson(res, 200, {
|
|
398
|
+
text: response.text,
|
|
399
|
+
model: response.model,
|
|
400
|
+
tokens: response.tokens,
|
|
401
|
+
cost: response.cost
|
|
402
|
+
});
|
|
403
|
+
};
|
|
404
|
+
const handleChatStream = async (_req, res, params, body) => {
|
|
405
|
+
const session = chatStore.get(params.id);
|
|
406
|
+
if (!session) {
|
|
407
|
+
sendError(res, 404, "not_found", `Session '${params.id}' not found`);
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
const { message } = body;
|
|
411
|
+
if (!message || typeof message !== "string") {
|
|
412
|
+
sendError(res, 400, "invalid_request", "Missing required field: message");
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
session.lastActiveAt = Date.now();
|
|
416
|
+
const saved = session.conversation.save();
|
|
417
|
+
const messages = [...saved.messages, { role: "user", content: message }];
|
|
418
|
+
const stream = ai.stream(message, {
|
|
419
|
+
system: saved.system || void 0,
|
|
420
|
+
model: session.model,
|
|
421
|
+
_messages: messages
|
|
422
|
+
});
|
|
423
|
+
await writeStreamSSE(res, stream);
|
|
424
|
+
};
|
|
425
|
+
const handleChatHistory = async (_req, res, params) => {
|
|
426
|
+
const session = chatStore.get(params.id);
|
|
427
|
+
if (!session) {
|
|
428
|
+
sendError(res, 404, "not_found", `Session '${params.id}' not found`);
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
const saved = session.conversation.save();
|
|
432
|
+
sendJson(res, 200, {
|
|
433
|
+
sessionId: session.id,
|
|
434
|
+
messages: saved.messages,
|
|
435
|
+
createdAt: session.createdAt,
|
|
436
|
+
lastActiveAt: session.lastActiveAt
|
|
437
|
+
});
|
|
438
|
+
};
|
|
439
|
+
const handleChatDelete = async (_req, res, params) => {
|
|
440
|
+
const deleted = chatStore.delete(params.id);
|
|
441
|
+
if (!deleted) {
|
|
442
|
+
sendError(res, 404, "not_found", `Session '${params.id}' not found`);
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
sendJson(res, 200, { deleted: true });
|
|
446
|
+
};
|
|
447
|
+
const handleHealth = async (_req, res) => {
|
|
448
|
+
sendJson(res, 200, { status: "ok" });
|
|
449
|
+
};
|
|
450
|
+
const handleModels = async (_req, res) => {
|
|
451
|
+
const models = Object.entries(MODEL_MAP).map(([alias, entry]) => ({
|
|
452
|
+
alias,
|
|
453
|
+
provider: entry.provider,
|
|
454
|
+
model: entry.model
|
|
455
|
+
}));
|
|
456
|
+
sendJson(res, 200, { models });
|
|
457
|
+
};
|
|
458
|
+
routes.push({ method: "GET", pattern: "/health", handler: handleHealth });
|
|
459
|
+
routes.push({ method: "GET", pattern: "/models", handler: handleModels });
|
|
460
|
+
if (config.ai) {
|
|
461
|
+
routes.push({ method: "POST", pattern: "/ai", handler: handleAi });
|
|
462
|
+
routes.push({ method: "POST", pattern: "/ai/stream", handler: handleAiStream });
|
|
463
|
+
}
|
|
464
|
+
if (config.agents && Object.keys(config.agents).length > 0) {
|
|
465
|
+
routes.push({ method: "POST", pattern: "/agents/:name", handler: handleAgent });
|
|
466
|
+
routes.push({ method: "POST", pattern: "/agents/:name/stream", handler: handleAgentStream });
|
|
467
|
+
}
|
|
468
|
+
if (chatStore) {
|
|
469
|
+
routes.push({ method: "POST", pattern: "/chat", handler: handleChatCreate });
|
|
470
|
+
routes.push({ method: "POST", pattern: "/chat/:id", handler: handleChatMessage });
|
|
471
|
+
routes.push({ method: "POST", pattern: "/chat/:id/stream", handler: handleChatStream });
|
|
472
|
+
routes.push({ method: "GET", pattern: "/chat/:id", handler: handleChatHistory });
|
|
473
|
+
routes.push({ method: "DELETE", pattern: "/chat/:id", handler: handleChatDelete });
|
|
474
|
+
}
|
|
475
|
+
return (req, res) => {
|
|
476
|
+
const handled = applyCors(req, res, config.cors);
|
|
477
|
+
if (handled) return;
|
|
478
|
+
const method = req.method ?? "GET";
|
|
479
|
+
const url = req.url ?? "/";
|
|
480
|
+
const match = matchRoute(routes, method, url);
|
|
481
|
+
if (!match.matched) {
|
|
482
|
+
if (match.status === 405 && match.allowedMethods) {
|
|
483
|
+
res.setHeader("Allow", match.allowedMethods.join(", "));
|
|
484
|
+
sendError(res, 405, "invalid_request", "Method not allowed");
|
|
485
|
+
} else {
|
|
486
|
+
sendError(res, 404, "not_found", "Not found");
|
|
487
|
+
}
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
const needsBody = ["POST", "PUT", "PATCH"].includes(method);
|
|
491
|
+
const dispatch = async () => {
|
|
492
|
+
let body = {};
|
|
493
|
+
if (needsBody) {
|
|
494
|
+
try {
|
|
495
|
+
const raw = await readBody(req);
|
|
496
|
+
body = raw.length > 0 ? JSON.parse(raw) : {};
|
|
497
|
+
} catch (err) {
|
|
498
|
+
if (err instanceof AidError) {
|
|
499
|
+
sendError(res, 400, err.code, err.message);
|
|
500
|
+
} else {
|
|
501
|
+
sendError(res, 400, "invalid_request", "Invalid JSON in request body");
|
|
502
|
+
}
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
await match.route.handler(req, res, match.params, body);
|
|
507
|
+
};
|
|
508
|
+
dispatch().catch((err) => {
|
|
509
|
+
if (err instanceof AidError) {
|
|
510
|
+
sendError(res, errorToStatus(err.code), err.code, err.message);
|
|
511
|
+
} else {
|
|
512
|
+
const message = err instanceof Error ? err.message : "Internal server error";
|
|
513
|
+
sendError(res, 500, "serve_error", message);
|
|
514
|
+
}
|
|
515
|
+
});
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// src/serve/index.ts
|
|
520
|
+
function serve(config = {}) {
|
|
521
|
+
const handler = createHandler(config);
|
|
522
|
+
const port = config.port ?? 3e3;
|
|
523
|
+
let server = null;
|
|
524
|
+
return {
|
|
525
|
+
handler,
|
|
526
|
+
async listen() {
|
|
527
|
+
server = http.createServer(handler);
|
|
528
|
+
return new Promise((resolve) => {
|
|
529
|
+
server.listen(port, resolve);
|
|
530
|
+
});
|
|
531
|
+
},
|
|
532
|
+
async close() {
|
|
533
|
+
if (!server) return;
|
|
534
|
+
return new Promise((resolve, reject) => {
|
|
535
|
+
server.close((err) => err ? reject(err) : resolve());
|
|
536
|
+
server = null;
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
export {
|
|
542
|
+
InMemoryChatStore,
|
|
543
|
+
createHandler,
|
|
544
|
+
serve
|
|
545
|
+
};
|