londai 0.1.0-alpha.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/LICENSE +21 -0
- package/dist/bin-create.d.ts +3 -0
- package/dist/bin-create.d.ts.map +1 -0
- package/dist/bin-create.js +3 -0
- package/dist/bin-create.js.map +1 -0
- package/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +3 -0
- package/dist/bin.js.map +1 -0
- package/dist/config-loader.d.ts +12 -0
- package/dist/config-loader.d.ts.map +1 -0
- package/dist/config-loader.js +22 -0
- package/dist/config-loader.js.map +1 -0
- package/dist/define-agent.d.ts +34 -0
- package/dist/define-agent.d.ts.map +1 -0
- package/dist/define-agent.js +38 -0
- package/dist/define-agent.js.map +1 -0
- package/dist/define-config.d.ts +27 -0
- package/dist/define-config.d.ts.map +1 -0
- package/dist/define-config.js +88 -0
- package/dist/define-config.js.map +1 -0
- package/dist/define-skill.d.ts +10 -0
- package/dist/define-skill.d.ts.map +1 -0
- package/dist/define-skill.js +45 -0
- package/dist/define-skill.js.map +1 -0
- package/dist/define-tool.d.ts +27 -0
- package/dist/define-tool.d.ts.map +1 -0
- package/dist/define-tool.js +44 -0
- package/dist/define-tool.js.map +1 -0
- package/dist/errors.d.ts +29 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +88 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +12 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +69 -0
- package/dist/logger.js.map +1 -0
- package/dist/londai.d.ts +15 -0
- package/dist/londai.d.ts.map +1 -0
- package/dist/londai.js +74 -0
- package/dist/londai.js.map +1 -0
- package/dist/registry.d.ts +57 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +130 -0
- package/dist/registry.js.map +1 -0
- package/dist/server/abort.d.ts +23 -0
- package/dist/server/abort.d.ts.map +1 -0
- package/dist/server/abort.js +35 -0
- package/dist/server/abort.js.map +1 -0
- package/dist/server/agent-builder.d.ts +49 -0
- package/dist/server/agent-builder.d.ts.map +1 -0
- package/dist/server/agent-builder.js +107 -0
- package/dist/server/agent-builder.js.map +1 -0
- package/dist/server/app.d.ts +21 -0
- package/dist/server/app.d.ts.map +1 -0
- package/dist/server/app.js +34 -0
- package/dist/server/app.js.map +1 -0
- package/dist/server/middleware/auth.d.ts +23 -0
- package/dist/server/middleware/auth.d.ts.map +1 -0
- package/dist/server/middleware/auth.js +102 -0
- package/dist/server/middleware/auth.js.map +1 -0
- package/dist/server/middleware/error.d.ts +10 -0
- package/dist/server/middleware/error.d.ts.map +1 -0
- package/dist/server/middleware/error.js +17 -0
- package/dist/server/middleware/error.js.map +1 -0
- package/dist/server/middleware/rate-limit.d.ts +18 -0
- package/dist/server/middleware/rate-limit.d.ts.map +1 -0
- package/dist/server/middleware/rate-limit.js +55 -0
- package/dist/server/middleware/rate-limit.js.map +1 -0
- package/dist/server/routes/approve.d.ts +11 -0
- package/dist/server/routes/approve.d.ts.map +1 -0
- package/dist/server/routes/approve.js +38 -0
- package/dist/server/routes/approve.js.map +1 -0
- package/dist/server/routes/chat-completion.d.ts +14 -0
- package/dist/server/routes/chat-completion.d.ts.map +1 -0
- package/dist/server/routes/chat-completion.js +70 -0
- package/dist/server/routes/chat-completion.js.map +1 -0
- package/dist/server/routes/chat-stream.d.ts +29 -0
- package/dist/server/routes/chat-stream.d.ts.map +1 -0
- package/dist/server/routes/chat-stream.js +190 -0
- package/dist/server/routes/chat-stream.js.map +1 -0
- package/dist/server/routes/common.d.ts +47 -0
- package/dist/server/routes/common.d.ts.map +1 -0
- package/dist/server/routes/common.js +143 -0
- package/dist/server/routes/common.js.map +1 -0
- package/dist/server/routes/session.d.ts +22 -0
- package/dist/server/routes/session.d.ts.map +1 -0
- package/dist/server/routes/session.js +228 -0
- package/dist/server/routes/session.js.map +1 -0
- package/dist/skills-loader.d.ts +46 -0
- package/dist/skills-loader.d.ts.map +1 -0
- package/dist/skills-loader.js +113 -0
- package/dist/skills-loader.js.map +1 -0
- package/dist/types.d.ts +128 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +9 -0
- package/dist/types.js.map +1 -0
- package/package.json +47 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build a per-request `Agent` from an {@link AgentSpec}.
|
|
3
|
+
*
|
|
4
|
+
* Londai is stateless-by-default (DESIGN §18): each request rebuilds the agent
|
|
5
|
+
* from the spec + conversation context. The agent's streamFn comes from the
|
|
6
|
+
* resolved `Models` registry (set by `@londai/providers`' side-effect import),
|
|
7
|
+
* or an explicit `streamFn` override.
|
|
8
|
+
*
|
|
9
|
+
* The agent's system prompt is composed at build time: base prompt (static or
|
|
10
|
+
* dynamic function result) + the skills `<available_skills>` block.
|
|
11
|
+
*
|
|
12
|
+
* Approval hook injection: when `approval` is enabled, a `beforeToolCall` hook
|
|
13
|
+
* is layered in front of any user-supplied hook: for tools whose `risk` meets
|
|
14
|
+
* the configured threshold, it emits an `approval_required` event (via a
|
|
15
|
+
* caller-supplied `emitApproval` callback) and awaits a decision from the
|
|
16
|
+
* registry's pending-approval machinery.
|
|
17
|
+
*/
|
|
18
|
+
import { Agent, } from "@londai/core";
|
|
19
|
+
import { registerPendingApproval, resolvePendingApproval } from "../registry.js";
|
|
20
|
+
import { formatSkillsForSystemPrompt } from "../skills-loader.js";
|
|
21
|
+
const RISK_RANK = { safe: 0, moderate: 1, destructive: 2 };
|
|
22
|
+
const DEFAULT_APPROVAL_TIMEOUT_MS = 5 * 60_000;
|
|
23
|
+
function shouldGate(risk, threshold) {
|
|
24
|
+
const rank = RISK_RANK[risk ?? "safe"] ?? 0;
|
|
25
|
+
return rank >= RISK_RANK[threshold];
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Build a per-request `Agent` from the spec.
|
|
29
|
+
*
|
|
30
|
+
* `models` resolves the model by `(spec.provider, spec.model)`. The agent's
|
|
31
|
+
* `streamFn` defaults via `setDefaultStreamFn` (installed by `@londai/providers`).
|
|
32
|
+
*/
|
|
33
|
+
export async function buildAgent(spec, ctx, models, approval) {
|
|
34
|
+
const model = models.getModel(spec.provider, spec.model);
|
|
35
|
+
if (!model) {
|
|
36
|
+
throw new Error(`londai: model "${spec.model}" not found for provider "${spec.provider}". Available: ${models
|
|
37
|
+
.getModels(spec.provider)
|
|
38
|
+
.map((m) => m.id)
|
|
39
|
+
.join(", ") || "(none)"}`);
|
|
40
|
+
}
|
|
41
|
+
// Compose system prompt: base (static or dynamic) + skills block.
|
|
42
|
+
const skillsBlock = formatSkillsForSystemPrompt(spec.skills);
|
|
43
|
+
const basePromptFn = spec.systemPrompt;
|
|
44
|
+
const systemPrompt = typeof basePromptFn === "function"
|
|
45
|
+
? await Promise.resolve(basePromptFn({ messages: ctx.messages ?? [], userId: ctx.userId })).then((base) => (skillsBlock ? `${base}${skillsBlock}` : base))
|
|
46
|
+
: skillsBlock
|
|
47
|
+
? `${basePromptFn}${skillsBlock}`
|
|
48
|
+
: basePromptFn;
|
|
49
|
+
// Layer approval hook before the user-supplied beforeToolCall.
|
|
50
|
+
let beforeToolCall = spec.hooks.beforeToolCall;
|
|
51
|
+
if (approval?.approvalConfig !== false && approval?.approvalConfig) {
|
|
52
|
+
const config = approval.approvalConfig;
|
|
53
|
+
const threshold = config.riskLevel ?? "destructive";
|
|
54
|
+
const timeoutMs = config.timeoutMs ?? DEFAULT_APPROVAL_TIMEOUT_MS;
|
|
55
|
+
const userHook = spec.hooks.beforeToolCall;
|
|
56
|
+
beforeToolCall = async (context, signal) => {
|
|
57
|
+
const userResult = await userHook?.(context, signal);
|
|
58
|
+
if (userResult?.block)
|
|
59
|
+
return userResult;
|
|
60
|
+
const tool = spec.tools.find((t) => t.name === context.toolCall.name);
|
|
61
|
+
if (!shouldGate(tool?.risk, threshold))
|
|
62
|
+
return userResult;
|
|
63
|
+
// Pause loop: await approval decision via registry.
|
|
64
|
+
const decision = await new Promise((resolve) => {
|
|
65
|
+
const pending = {
|
|
66
|
+
conversationId: ctx.conversationId,
|
|
67
|
+
callId: context.toolCall.id,
|
|
68
|
+
toolName: context.toolCall.name,
|
|
69
|
+
args: context.args,
|
|
70
|
+
resolve: (d) => resolve(d.approved),
|
|
71
|
+
timer: setTimeout(() => {
|
|
72
|
+
resolvePendingApproval(ctx.conversationId, { approved: false, reason: "approval timed out" });
|
|
73
|
+
}, timeoutMs),
|
|
74
|
+
};
|
|
75
|
+
registerPendingApproval(pending);
|
|
76
|
+
approval.emitApprovalRequired({
|
|
77
|
+
conversationId: ctx.conversationId,
|
|
78
|
+
callId: context.toolCall.id,
|
|
79
|
+
toolName: context.toolCall.name,
|
|
80
|
+
args: context.args,
|
|
81
|
+
timeoutMs,
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
if (!decision) {
|
|
85
|
+
return { block: true, reason: "Tool execution denied by operator" };
|
|
86
|
+
}
|
|
87
|
+
return undefined;
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
const agent = new Agent({
|
|
91
|
+
initialState: {
|
|
92
|
+
systemPrompt,
|
|
93
|
+
model: model,
|
|
94
|
+
thinkingLevel: spec.thinkingLevel ?? "off",
|
|
95
|
+
tools: spec.tools,
|
|
96
|
+
messages: ctx.messages ?? [],
|
|
97
|
+
},
|
|
98
|
+
beforeToolCall,
|
|
99
|
+
afterToolCall: spec.hooks.afterToolCall,
|
|
100
|
+
prepareNextTurnWithContext: spec.hooks.prepareNextTurn,
|
|
101
|
+
transformContext: spec.hooks.transformContext,
|
|
102
|
+
onPayload: spec.hooks.onPayload,
|
|
103
|
+
onResponse: spec.hooks.onResponse,
|
|
104
|
+
});
|
|
105
|
+
return agent;
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=agent-builder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-builder.js","sourceRoot":"","sources":["../../src/server/agent-builder.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal Hono app factory.
|
|
3
|
+
*
|
|
4
|
+
* Builds the Hono app with middleware (error → auth → rate-limit) and mounts the
|
|
5
|
+
* chat-stream / chat-completion / (optional) approve / session routes. User code
|
|
6
|
+
* never holds this app — `londai.run()` returns only a server handle (DESIGN §19).
|
|
7
|
+
*/
|
|
8
|
+
import { Hono } from "hono";
|
|
9
|
+
import type { AgentSpec, ApprovalConfig, LondaiRunOptions } from "../types.ts";
|
|
10
|
+
export type HonoContext = Hono<{
|
|
11
|
+
Variables: {
|
|
12
|
+
userId?: string;
|
|
13
|
+
};
|
|
14
|
+
}>;
|
|
15
|
+
export interface BuildAppOptions {
|
|
16
|
+
spec: AgentSpec;
|
|
17
|
+
approval?: ApprovalConfig | false;
|
|
18
|
+
onEvent?: LondaiRunOptions["onEvent"];
|
|
19
|
+
}
|
|
20
|
+
export declare function buildApp(opts: BuildAppOptions): HonoContext;
|
|
21
|
+
//# sourceMappingURL=app.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../src/server/app.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAS/E,MAAM,MAAM,WAAW,GAAG,IAAI,CAAC;IAAE,SAAS,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAAC,CAAC;AAEnE,MAAM,WAAW,eAAe;IAC/B,IAAI,EAAE,SAAS,CAAC;IAChB,QAAQ,CAAC,EAAE,cAAc,GAAG,KAAK,CAAC;IAClC,OAAO,CAAC,EAAE,gBAAgB,CAAC,SAAS,CAAC,CAAC;CACtC;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,eAAe,GAAG,WAAW,CAqB3D"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal Hono app factory.
|
|
3
|
+
*
|
|
4
|
+
* Builds the Hono app with middleware (error → auth → rate-limit) and mounts the
|
|
5
|
+
* chat-stream / chat-completion / (optional) approve / session routes. User code
|
|
6
|
+
* never holds this app — `londai.run()` returns only a server handle (DESIGN §19).
|
|
7
|
+
*/
|
|
8
|
+
import { Hono } from "hono";
|
|
9
|
+
import { getRateLimitConfig, getSessionRepo } from "../registry.js";
|
|
10
|
+
import { authMiddleware } from "./middleware/auth.js";
|
|
11
|
+
import { errorMiddleware } from "./middleware/error.js";
|
|
12
|
+
import { rateLimitMiddleware } from "./middleware/rate-limit.js";
|
|
13
|
+
import { registerApproveRoute } from "./routes/approve.js";
|
|
14
|
+
import { registerChatCompletionRoute } from "./routes/chat-completion.js";
|
|
15
|
+
import { registerChatStreamRoute } from "./routes/chat-stream.js";
|
|
16
|
+
import { registerSessionRoutes } from "./routes/session.js";
|
|
17
|
+
export function buildApp(opts) {
|
|
18
|
+
const app = new Hono();
|
|
19
|
+
app.onError(errorMiddleware);
|
|
20
|
+
app.use("*", authMiddleware());
|
|
21
|
+
app.use("*", rateLimitMiddleware(getRateLimitConfig()));
|
|
22
|
+
registerChatStreamRoute(app, { spec: opts.spec, approval: opts.approval, onEvent: opts.onEvent });
|
|
23
|
+
registerChatCompletionRoute(app, { spec: opts.spec, onEvent: opts.onEvent });
|
|
24
|
+
if (opts.approval !== false && opts.approval) {
|
|
25
|
+
registerApproveRoute(app);
|
|
26
|
+
}
|
|
27
|
+
// Mount session routes if a session repo is configured.
|
|
28
|
+
if (getSessionRepo()) {
|
|
29
|
+
registerSessionRoutes(app, { spec: opts.spec, approval: opts.approval, onEvent: opts.onEvent });
|
|
30
|
+
}
|
|
31
|
+
app.get("/health", (c) => c.json({ ok: true }));
|
|
32
|
+
return app;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=app.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app.js","sourceRoot":"","sources":["../../src/server/app.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAEpE,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,2BAA2B,EAAE,MAAM,6BAA6B,CAAC;AAC1E,OAAO,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAClE,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAU5D,MAAM,UAAU,QAAQ,CAAC,IAAqB;IAC7C,MAAM,GAAG,GAAG,IAAI,IAAI,EAAsC,CAAC;IAE3D,GAAG,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;IAC7B,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,cAAc,EAAE,CAAC,CAAC;IAC/B,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,mBAAmB,CAAC,kBAAkB,EAAE,CAAC,CAAC,CAAC;IAExD,uBAAuB,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;IAClG,2BAA2B,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;IAC7E,IAAI,IAAI,CAAC,QAAQ,KAAK,KAAK,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC9C,oBAAoB,CAAC,GAAG,CAAC,CAAC;IAC3B,CAAC;IAED,wDAAwD;IACxD,IAAI,cAAc,EAAE,EAAE,CAAC;QACtB,qBAAqB,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;IACjG,CAAC;IAED,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAEhD,OAAO,GAAG,CAAC;AACZ,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth middleware.
|
|
3
|
+
*
|
|
4
|
+
* Four modes from `AuthConfig`:
|
|
5
|
+
* - `false` → no auth (dev/internal). In `NODE_ENV=production` with `auth` unset,
|
|
6
|
+
* `londai.run()` rejects at boot; this middleware is only mounted when auth is
|
|
7
|
+
* truthy or explicitly `false`.
|
|
8
|
+
* - `{ type: "jwt", secret }` → verify HS256 Bearer token via `node:crypto`.
|
|
9
|
+
* - `{ type: "api-key", ... }` → header check, constant-time compare for literal validate.
|
|
10
|
+
* - `{ verify }` → custom hook returning userId | null | true.
|
|
11
|
+
*
|
|
12
|
+
* On success: `c.set("userId", string | undefined)`. On failure: throw a
|
|
13
|
+
* `FrameworkError("auth", ...)` that the error middleware maps to 401.
|
|
14
|
+
*/
|
|
15
|
+
import type { MiddlewareHandler } from "hono";
|
|
16
|
+
type Vars = {
|
|
17
|
+
userId?: string;
|
|
18
|
+
};
|
|
19
|
+
export declare function authMiddleware(): MiddlewareHandler<{
|
|
20
|
+
Variables: Vars;
|
|
21
|
+
}>;
|
|
22
|
+
export {};
|
|
23
|
+
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../../src/server/middleware/auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,KAAK,EAAW,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAKvD,KAAK,IAAI,GAAG;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AA0ChC,wBAAgB,cAAc,IAAI,iBAAiB,CAAC;IAAE,SAAS,EAAE,IAAI,CAAA;CAAE,CAAC,CAevE"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth middleware.
|
|
3
|
+
*
|
|
4
|
+
* Four modes from `AuthConfig`:
|
|
5
|
+
* - `false` → no auth (dev/internal). In `NODE_ENV=production` with `auth` unset,
|
|
6
|
+
* `londai.run()` rejects at boot; this middleware is only mounted when auth is
|
|
7
|
+
* truthy or explicitly `false`.
|
|
8
|
+
* - `{ type: "jwt", secret }` → verify HS256 Bearer token via `node:crypto`.
|
|
9
|
+
* - `{ type: "api-key", ... }` → header check, constant-time compare for literal validate.
|
|
10
|
+
* - `{ verify }` → custom hook returning userId | null | true.
|
|
11
|
+
*
|
|
12
|
+
* On success: `c.set("userId", string | undefined)`. On failure: throw a
|
|
13
|
+
* `FrameworkError("auth", ...)` that the error middleware maps to 401.
|
|
14
|
+
*/
|
|
15
|
+
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
16
|
+
import { FrameworkError } from "../../errors.js";
|
|
17
|
+
import { getAuthConfig } from "../../registry.js";
|
|
18
|
+
function readBearer(authHeader) {
|
|
19
|
+
if (!authHeader)
|
|
20
|
+
return undefined;
|
|
21
|
+
const match = /^Bearer\s+(.+)$/i.exec(authHeader.trim());
|
|
22
|
+
return match?.[1];
|
|
23
|
+
}
|
|
24
|
+
function timingSafeEqualString(a, b) {
|
|
25
|
+
const aBuf = Buffer.from(a);
|
|
26
|
+
const bBuf = Buffer.from(b);
|
|
27
|
+
if (aBuf.length !== bBuf.length)
|
|
28
|
+
return false;
|
|
29
|
+
return timingSafeEqual(aBuf, bBuf);
|
|
30
|
+
}
|
|
31
|
+
function verifyJwtHS256(token, secret) {
|
|
32
|
+
const parts = token.split(".");
|
|
33
|
+
if (parts.length !== 3)
|
|
34
|
+
throw new FrameworkError("auth", "Malformed JWT (expected 3 segments).");
|
|
35
|
+
const [headerB64, payloadB64, signatureB64] = parts;
|
|
36
|
+
const signingInput = `${headerB64}.${payloadB64}`;
|
|
37
|
+
const signature = Buffer.from(base64UrlDecode(signatureB64));
|
|
38
|
+
const expected = createHmac("sha256", secret).update(Buffer.from(signingInput)).digest();
|
|
39
|
+
if (signature.length !== expected.length || !timingSafeEqual(signature, expected)) {
|
|
40
|
+
throw new FrameworkError("auth", "Invalid JWT signature.");
|
|
41
|
+
}
|
|
42
|
+
const payload = JSON.parse(Buffer.from(base64UrlDecode(payloadB64)).toString("utf8"));
|
|
43
|
+
if (typeof payload.exp === "number" && Date.now() / 1000 >= payload.exp) {
|
|
44
|
+
throw new FrameworkError("auth", "JWT expired.");
|
|
45
|
+
}
|
|
46
|
+
const sub = payload.sub;
|
|
47
|
+
return typeof sub === "string" ? sub : "jwt-user";
|
|
48
|
+
}
|
|
49
|
+
function base64UrlDecode(input) {
|
|
50
|
+
const pad = input.length % 4 === 0 ? "" : "=".repeat(4 - (input.length % 4));
|
|
51
|
+
const normalized = input.replace(/-/g, "+").replace(/_/g, "/") + pad;
|
|
52
|
+
return Buffer.from(normalized, "base64");
|
|
53
|
+
}
|
|
54
|
+
export function authMiddleware() {
|
|
55
|
+
const config = getAuthConfig();
|
|
56
|
+
// No auth configured (undefined or explicit false) → noop.
|
|
57
|
+
if (config === undefined || config === false) {
|
|
58
|
+
return async (_c, next) => {
|
|
59
|
+
await next();
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
return async (c, next) => {
|
|
63
|
+
const userId = await authenticate(c.req.raw, config);
|
|
64
|
+
c.set("userId", userId ?? undefined);
|
|
65
|
+
await next();
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
async function authenticate(req, config) {
|
|
69
|
+
if ("verify" in config) {
|
|
70
|
+
const result = await config.verify(req);
|
|
71
|
+
if (result === null)
|
|
72
|
+
throw new FrameworkError("auth", "Authentication rejected by verify hook.");
|
|
73
|
+
return result === true ? undefined : result;
|
|
74
|
+
}
|
|
75
|
+
if (config.type === "jwt") {
|
|
76
|
+
const header = config.header ?? "authorization";
|
|
77
|
+
const token = readBearer(req.headers.get(header) ?? undefined);
|
|
78
|
+
if (!token)
|
|
79
|
+
throw new FrameworkError("auth", "Missing Bearer token.");
|
|
80
|
+
return verifyJwtHS256(token, config.secret);
|
|
81
|
+
}
|
|
82
|
+
if (config.type === "api-key") {
|
|
83
|
+
const header = config.header ?? "x-api-key";
|
|
84
|
+
const key = req.headers.get(header);
|
|
85
|
+
if (!key)
|
|
86
|
+
throw new FrameworkError("auth", "Missing API key.");
|
|
87
|
+
if (typeof config.validate === "string") {
|
|
88
|
+
if (!timingSafeEqualString(key, config.validate)) {
|
|
89
|
+
throw new FrameworkError("auth", "Invalid API key.");
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
const ok = await config.validate(key);
|
|
94
|
+
if (!ok)
|
|
95
|
+
throw new FrameworkError("auth", "Invalid API key.");
|
|
96
|
+
}
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
// Exhaustive fallback.
|
|
100
|
+
throw new FrameworkError("auth", "Unsupported auth configuration.");
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../../src/server/middleware/auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error middleware — convert thrown errors to JSON HTTP responses.
|
|
3
|
+
*
|
|
4
|
+
* `AgentError` (core), `FrameworkError`, and plain `Error` are mapped via
|
|
5
|
+
* `errorToStatus`. LLM-level failures (stopReason "error"/"aborted") never reach
|
|
6
|
+
* here — those are emitted as SSE `event: error` inside the chat-stream route.
|
|
7
|
+
*/
|
|
8
|
+
import type { ErrorHandler } from "hono";
|
|
9
|
+
export declare const errorMiddleware: ErrorHandler;
|
|
10
|
+
//# sourceMappingURL=error.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error.d.ts","sourceRoot":"","sources":["../../../src/server/middleware/error.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,MAAM,CAAC;AAGzC,eAAO,MAAM,eAAe,EAAE,YAO7B,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error middleware — convert thrown errors to JSON HTTP responses.
|
|
3
|
+
*
|
|
4
|
+
* `AgentError` (core), `FrameworkError`, and plain `Error` are mapped via
|
|
5
|
+
* `errorToStatus`. LLM-level failures (stopReason "error"/"aborted") never reach
|
|
6
|
+
* here — those are emitted as SSE `event: error` inside the chat-stream route.
|
|
7
|
+
*/
|
|
8
|
+
import { errorBody, errorToStatus } from "../../errors.js";
|
|
9
|
+
export const errorMiddleware = (err, c) => {
|
|
10
|
+
const status = errorToStatus(err);
|
|
11
|
+
const body = errorBody(err);
|
|
12
|
+
if (status >= 500) {
|
|
13
|
+
console.error("[londai] internal error:", err);
|
|
14
|
+
}
|
|
15
|
+
return c.json(body, status);
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=error.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error.js","sourceRoot":"","sources":["../../../src/server/middleware/error.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAE3D,MAAM,CAAC,MAAM,eAAe,GAAiB,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE;IACvD,MAAM,MAAM,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC;IAClC,MAAM,IAAI,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;IAC5B,IAAI,MAAM,IAAI,GAAG,EAAE,CAAC;QACnB,OAAO,CAAC,KAAK,CAAC,0BAA0B,EAAE,GAAG,CAAC,CAAC;IAChD,CAAC;IACD,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,MAAa,CAAC,CAAC;AACpC,CAAC,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rate-limit middleware.
|
|
3
|
+
*
|
|
4
|
+
* Simple in-memory token-bucket per client IP. Defaults: 60 requests per minute.
|
|
5
|
+
* A custom hook (`(req) => boolean`) bypasses the bucket entirely. Multi-instance
|
|
6
|
+
* fairness is a roadmap item (Redis-backed) — flagged in DESIGN §22.
|
|
7
|
+
*
|
|
8
|
+
* On rejection: throw `FrameworkError("forbidden")` → mapped to 429 by the error
|
|
9
|
+
* middleware? No: 429 is the conventional status. We expose it via a custom
|
|
10
|
+
* `errorToStatus` branch: code "rate_limited".
|
|
11
|
+
*
|
|
12
|
+
* For MVP simplicity we map to 429 directly here (return a Response) instead of
|
|
13
|
+
* routing through the error middleware.
|
|
14
|
+
*/
|
|
15
|
+
import type { MiddlewareHandler } from "hono";
|
|
16
|
+
import type { RateLimitConfig } from "../../types.ts";
|
|
17
|
+
export declare function rateLimitMiddleware(config: RateLimitConfig | undefined): MiddlewareHandler;
|
|
18
|
+
//# sourceMappingURL=rate-limit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../../../src/server/middleware/rate-limit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAC9C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAStD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,eAAe,GAAG,SAAS,GAAG,iBAAiB,CAkC1F"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rate-limit middleware.
|
|
3
|
+
*
|
|
4
|
+
* Simple in-memory token-bucket per client IP. Defaults: 60 requests per minute.
|
|
5
|
+
* A custom hook (`(req) => boolean`) bypasses the bucket entirely. Multi-instance
|
|
6
|
+
* fairness is a roadmap item (Redis-backed) — flagged in DESIGN §22.
|
|
7
|
+
*
|
|
8
|
+
* On rejection: throw `FrameworkError("forbidden")` → mapped to 429 by the error
|
|
9
|
+
* middleware? No: 429 is the conventional status. We expose it via a custom
|
|
10
|
+
* `errorToStatus` branch: code "rate_limited".
|
|
11
|
+
*
|
|
12
|
+
* For MVP simplicity we map to 429 directly here (return a Response) instead of
|
|
13
|
+
* routing through the error middleware.
|
|
14
|
+
*/
|
|
15
|
+
const buckets = new Map();
|
|
16
|
+
export function rateLimitMiddleware(config) {
|
|
17
|
+
if (!config) {
|
|
18
|
+
return async (_c, next) => {
|
|
19
|
+
await next();
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
if (typeof config === "function") {
|
|
23
|
+
return async (c, next) => {
|
|
24
|
+
const ok = await config(c.req.raw);
|
|
25
|
+
if (!ok) {
|
|
26
|
+
return c.json({ error: { code: "rate_limited", message: "Rate limit exceeded." } }, 429);
|
|
27
|
+
}
|
|
28
|
+
await next();
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
const { limit, windowMs } = config;
|
|
32
|
+
return async (c, next) => {
|
|
33
|
+
const ip = clientIp(c.req.raw);
|
|
34
|
+
const now = Date.now();
|
|
35
|
+
let bucket = buckets.get(ip);
|
|
36
|
+
if (!bucket || bucket.resetAt <= now) {
|
|
37
|
+
bucket = { count: 0, resetAt: now + windowMs };
|
|
38
|
+
buckets.set(ip, bucket);
|
|
39
|
+
}
|
|
40
|
+
bucket.count++;
|
|
41
|
+
if (bucket.count > limit) {
|
|
42
|
+
const retryAfter = Math.ceil((bucket.resetAt - now) / 1000);
|
|
43
|
+
c.header("Retry-After", String(retryAfter));
|
|
44
|
+
return c.json({ error: { code: "rate_limited", message: "Rate limit exceeded." } }, 429);
|
|
45
|
+
}
|
|
46
|
+
await next();
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function clientIp(req) {
|
|
50
|
+
const fwd = req.headers.get("x-forwarded-for");
|
|
51
|
+
if (fwd)
|
|
52
|
+
return fwd.split(",")[0].trim();
|
|
53
|
+
return req.headers.get("x-real-ip") ?? "unknown";
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=rate-limit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limit.js","sourceRoot":"","sources":["../../../src/server/middleware/rate-limit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAUH,MAAM,OAAO,GAAG,IAAI,GAAG,EAAkB,CAAC;AAE1C,MAAM,UAAU,mBAAmB,CAAC,MAAmC;IACtE,IAAI,CAAC,MAAM,EAAE,CAAC;QACb,OAAO,KAAK,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE;YACzB,MAAM,IAAI,EAAE,CAAC;QACd,CAAC,CAAC;IACH,CAAC;IAED,IAAI,OAAO,MAAM,KAAK,UAAU,EAAE,CAAC;QAClC,OAAO,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE;YACxB,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACnC,IAAI,CAAC,EAAE,EAAE,CAAC;gBACT,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,sBAAsB,EAAE,EAAE,EAAE,GAAG,CAAC,CAAC;YAC1F,CAAC;YACD,MAAM,IAAI,EAAE,CAAC;QACd,CAAC,CAAC;IACH,CAAC;IAED,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,MAAM,CAAC;IACnC,OAAO,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE;QACxB,MAAM,EAAE,GAAG,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC/B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,IAAI,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC7B,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,OAAO,IAAI,GAAG,EAAE,CAAC;YACtC,MAAM,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,GAAG,GAAG,QAAQ,EAAE,CAAC;YAC/C,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;QACzB,CAAC;QACD,MAAM,CAAC,KAAK,EAAE,CAAC;QACf,IAAI,MAAM,CAAC,KAAK,GAAG,KAAK,EAAE,CAAC;YAC1B,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC;YAC5D,CAAC,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC;YAC5C,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,sBAAsB,EAAE,EAAE,EAAE,GAAG,CAAC,CAAC;QAC1F,CAAC;QACD,MAAM,IAAI,EAAE,CAAC;IACd,CAAC,CAAC;AACH,CAAC;AAED,SAAS,QAAQ,CAAC,GAAY;IAC7B,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;IAC/C,IAAI,GAAG;QAAE,OAAO,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACzC,OAAO,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,SAAS,CAAC;AAClD,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `POST /approve` — opt-in approval endpoint (mounted only when `approval` is set).
|
|
3
|
+
*
|
|
4
|
+
* Body: `{ conversationId, callId, decision: "approve" | "deny", reason? }`.
|
|
5
|
+
* Resolves the pending `beforeToolCall` promise in `agent-builder`, which either
|
|
6
|
+
* unblocks the tool call or returns `{ block: true, reason }` so the loop encodes
|
|
7
|
+
* an error tool result.
|
|
8
|
+
*/
|
|
9
|
+
import type { HonoContext } from "../app.ts";
|
|
10
|
+
export declare function registerApproveRoute(app: HonoContext): void;
|
|
11
|
+
//# sourceMappingURL=approve.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approve.d.ts","sourceRoot":"","sources":["../../../src/server/routes/approve.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAE7C,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,WAAW,GAAG,IAAI,CA8B3D"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `POST /approve` — opt-in approval endpoint (mounted only when `approval` is set).
|
|
3
|
+
*
|
|
4
|
+
* Body: `{ conversationId, callId, decision: "approve" | "deny", reason? }`.
|
|
5
|
+
* Resolves the pending `beforeToolCall` promise in `agent-builder`, which either
|
|
6
|
+
* unblocks the tool call or returns `{ block: true, reason }` so the loop encodes
|
|
7
|
+
* an error tool result.
|
|
8
|
+
*/
|
|
9
|
+
import { FrameworkError } from "../../errors.js";
|
|
10
|
+
import { resolvePendingApproval } from "../../registry.js";
|
|
11
|
+
export function registerApproveRoute(app) {
|
|
12
|
+
app.post("/approve", async (c) => {
|
|
13
|
+
let body;
|
|
14
|
+
try {
|
|
15
|
+
body = await c.req.json();
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
throw new FrameworkError("validation", "Request body must be JSON.");
|
|
19
|
+
}
|
|
20
|
+
const { conversationId, callId, decision, reason } = (body ?? {});
|
|
21
|
+
if (typeof conversationId !== "string" || typeof callId !== "string") {
|
|
22
|
+
throw new FrameworkError("validation", "`conversationId` and `callId` are required strings.");
|
|
23
|
+
}
|
|
24
|
+
if (decision !== "approve" && decision !== "deny") {
|
|
25
|
+
throw new FrameworkError("validation", '`decision` must be "approve" or "deny".');
|
|
26
|
+
}
|
|
27
|
+
const reasonStr = typeof reason === "string" ? reason : undefined;
|
|
28
|
+
const resolved = resolvePendingApproval(conversationId, {
|
|
29
|
+
approved: decision === "approve",
|
|
30
|
+
reason: reasonStr,
|
|
31
|
+
});
|
|
32
|
+
if (!resolved) {
|
|
33
|
+
return c.json({ ok: false, message: "No pending approval for this conversation." }, 404);
|
|
34
|
+
}
|
|
35
|
+
return c.json({ ok: true, decision });
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=approve.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approve.js","sourceRoot":"","sources":["../../../src/server/routes/approve.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAG3D,MAAM,UAAU,oBAAoB,CAAC,GAAgB;IACpD,GAAG,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QAChC,IAAI,IAAa,CAAC;QAClB,IAAI,CAAC;YACJ,IAAI,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;QAC3B,CAAC;QAAC,MAAM,CAAC;YACR,MAAM,IAAI,cAAc,CAAC,YAAY,EAAE,4BAA4B,CAAC,CAAC;QACtE,CAAC;QACD,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,CAAC,IAAI,IAAI,EAAE,CAK/D,CAAC;QACF,IAAI,OAAO,cAAc,KAAK,QAAQ,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;YACtE,MAAM,IAAI,cAAc,CAAC,YAAY,EAAE,qDAAqD,CAAC,CAAC;QAC/F,CAAC;QACD,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,KAAK,MAAM,EAAE,CAAC;YACnD,MAAM,IAAI,cAAc,CAAC,YAAY,EAAE,yCAAyC,CAAC,CAAC;QACnF,CAAC;QACD,MAAM,SAAS,GAAG,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC;QAClE,MAAM,QAAQ,GAAG,sBAAsB,CAAC,cAAc,EAAE;YACvD,QAAQ,EAAE,QAAQ,KAAK,SAAS;YAChC,MAAM,EAAE,SAAS;SACjB,CAAC,CAAC;QACH,IAAI,CAAC,QAAQ,EAAE,CAAC;YACf,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,4CAA4C,EAAE,EAAE,GAAG,CAAC,CAAC;QAC1F,CAAC;QACD,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;IACvC,CAAC,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `POST /chat/completion` — non-streaming: await the final messages, return JSON.
|
|
3
|
+
*
|
|
4
|
+
* Same agent wiring as `/chat/stream` but consumes events synchronously and
|
|
5
|
+
* responds with `{ messages, stopReason, usage }` after `agent.prompt()` settles.
|
|
6
|
+
*/
|
|
7
|
+
import type { AgentSpec, LondaiRunOptions } from "../../types.ts";
|
|
8
|
+
import type { HonoContext } from "../app.ts";
|
|
9
|
+
export interface ChatCompletionDeps {
|
|
10
|
+
spec: AgentSpec;
|
|
11
|
+
onEvent?: LondaiRunOptions["onEvent"];
|
|
12
|
+
}
|
|
13
|
+
export declare function registerChatCompletionRoute(app: HonoContext, deps: ChatCompletionDeps): void;
|
|
14
|
+
//# sourceMappingURL=chat-completion.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-completion.d.ts","sourceRoot":"","sources":["../../../src/server/routes/chat-completion.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAGlE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAG7C,MAAM,WAAW,kBAAkB;IAClC,IAAI,EAAE,SAAS,CAAC;IAChB,OAAO,CAAC,EAAE,gBAAgB,CAAC,SAAS,CAAC,CAAC;CACtC;AAED,wBAAgB,2BAA2B,CAAC,GAAG,EAAE,WAAW,EAAE,IAAI,EAAE,kBAAkB,GAAG,IAAI,CAwE5F"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `POST /chat/completion` — non-streaming: await the final messages, return JSON.
|
|
3
|
+
*
|
|
4
|
+
* Same agent wiring as `/chat/stream` but consumes events synchronously and
|
|
5
|
+
* responds with `{ messages, stopReason, usage }` after `agent.prompt()` settles.
|
|
6
|
+
*/
|
|
7
|
+
import { requireModels } from "../../registry.js";
|
|
8
|
+
import { finishRun, trackRequest } from "../abort.js";
|
|
9
|
+
import { buildAgent } from "../agent-builder.js";
|
|
10
|
+
import { parseChatBody, persistNewMessages, resolveConversationContext } from "./common.js";
|
|
11
|
+
export function registerChatCompletionRoute(app, deps) {
|
|
12
|
+
const { spec } = deps;
|
|
13
|
+
app.post("/chat/completion", async (c) => {
|
|
14
|
+
const models = requireModels();
|
|
15
|
+
const body = await parseChatBody(c);
|
|
16
|
+
const sessionCtx = await resolveConversationContext(body.conversationId, c, {
|
|
17
|
+
explicitConversationId: body.conversationIdProvided,
|
|
18
|
+
});
|
|
19
|
+
const conversationId = sessionCtx?.conversationId ?? body.conversationId;
|
|
20
|
+
const sessionLoadedCount = sessionCtx?.loadedCount ?? 0;
|
|
21
|
+
const seedMessages = sessionCtx?.messages ?? body.messages;
|
|
22
|
+
const tracked = trackRequest(conversationId, { requestSignal: c.req.raw.signal });
|
|
23
|
+
if (!tracked) {
|
|
24
|
+
return c.json({ error: { code: "conflict", message: `A run is already active for conversation ${conversationId}.` } }, 409);
|
|
25
|
+
}
|
|
26
|
+
const { signal } = tracked;
|
|
27
|
+
const agent = await buildAgent(spec, {
|
|
28
|
+
conversationId,
|
|
29
|
+
messages: seedMessages,
|
|
30
|
+
userId: c.get("userId"),
|
|
31
|
+
signal,
|
|
32
|
+
}, models);
|
|
33
|
+
const collected = [];
|
|
34
|
+
const unsubscribe = agent.subscribe((event) => {
|
|
35
|
+
deps.onEvent?.(event);
|
|
36
|
+
if ((event.type === "message_end" || event.type === "turn_end") && event.type === "message_end") {
|
|
37
|
+
collected.push(event.message);
|
|
38
|
+
}
|
|
39
|
+
if (event.type === "agent_end") {
|
|
40
|
+
collected.push(...event.messages);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
try {
|
|
44
|
+
if (typeof body.message === "string") {
|
|
45
|
+
await agent.prompt(body.message);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
await agent.prompt(body.message);
|
|
49
|
+
}
|
|
50
|
+
// Persist new messages after prompt settles.
|
|
51
|
+
await persistNewMessages(conversationId, agent.state.messages, sessionLoadedCount);
|
|
52
|
+
}
|
|
53
|
+
finally {
|
|
54
|
+
unsubscribe();
|
|
55
|
+
finishRun(conversationId);
|
|
56
|
+
}
|
|
57
|
+
const finalMessages = agent.state.messages;
|
|
58
|
+
const lastAssistant = [...finalMessages].reverse().find((m) => m.role === "assistant");
|
|
59
|
+
const stopReason = lastAssistant && "stopReason" in lastAssistant ? lastAssistant.stopReason : "stop";
|
|
60
|
+
const usage = lastAssistant && "usage" in lastAssistant ? lastAssistant.usage : undefined;
|
|
61
|
+
return c.json({
|
|
62
|
+
conversationId,
|
|
63
|
+
messages: finalMessages,
|
|
64
|
+
collected,
|
|
65
|
+
stopReason,
|
|
66
|
+
usage,
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=chat-completion.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-completion.js","sourceRoot":"","sources":["../../../src/server/routes/chat-completion.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `POST /chat/stream` — SSE route mapping `AgentEvent` → wire events.
|
|
3
|
+
*
|
|
4
|
+
* Wire format:
|
|
5
|
+
* - `event: delta` data: "text chunk"
|
|
6
|
+
* - `event: tool_start` data: {callId, name, args}
|
|
7
|
+
* - `event: tool_end` data: {callId, isError, details?}
|
|
8
|
+
* - `event: approval_required` data: {conversationId, callId, toolName, args}
|
|
9
|
+
* - `event: error` data: {code, message}
|
|
10
|
+
* - `event: done` data: ""
|
|
11
|
+
*
|
|
12
|
+
* Abort: `c.req.raw.signal` is plowed into the agent via `trackRequest`. Client
|
|
13
|
+
* disconnect → `agent.abort()` → loop encodes stopReason "aborted" → we emit
|
|
14
|
+
* `event: error` (aborted) and close.
|
|
15
|
+
*
|
|
16
|
+
* Session integration (milestone 04):
|
|
17
|
+
* - If a `SessionRepo` is configured, loads conversation history and persists new
|
|
18
|
+
* messages after the prompt settles.
|
|
19
|
+
* - Sends `x-londai-conversation` header with the conversation id.
|
|
20
|
+
*/
|
|
21
|
+
import type { AgentSpec, ApprovalConfig, LondaiRunOptions } from "../../types.ts";
|
|
22
|
+
import type { HonoContext } from "../app.ts";
|
|
23
|
+
export interface ChatStreamDeps {
|
|
24
|
+
spec: AgentSpec;
|
|
25
|
+
approval?: ApprovalConfig | false;
|
|
26
|
+
onEvent?: LondaiRunOptions["onEvent"];
|
|
27
|
+
}
|
|
28
|
+
export declare function registerChatStreamRoute(app: HonoContext, deps: ChatStreamDeps): void;
|
|
29
|
+
//# sourceMappingURL=chat-stream.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat-stream.d.ts","sourceRoot":"","sources":["../../../src/server/routes/chat-stream.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAKH,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAGlF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAG7C,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,SAAS,CAAC;IAChB,QAAQ,CAAC,EAAE,cAAc,GAAG,KAAK,CAAC;IAClC,OAAO,CAAC,EAAE,gBAAgB,CAAC,SAAS,CAAC,CAAC;CACtC;AAED,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,WAAW,EAAE,IAAI,EAAE,cAAc,GAAG,IAAI,CAkIpF"}
|