@graphorin/server 0.5.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/CHANGELOG.md +7 -0
- package/LICENSE +21 -0
- package/README.md +116 -0
- package/dist/app.d.ts +174 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +684 -0
- package/dist/app.js.map +1 -0
- package/dist/commentary/audit-bridge.d.ts +57 -0
- package/dist/commentary/audit-bridge.d.ts.map +1 -0
- package/dist/commentary/audit-bridge.js +92 -0
- package/dist/commentary/audit-bridge.js.map +1 -0
- package/dist/commentary/built-in-patterns.d.ts +24 -0
- package/dist/commentary/built-in-patterns.d.ts.map +1 -0
- package/dist/commentary/built-in-patterns.js +62 -0
- package/dist/commentary/built-in-patterns.js.map +1 -0
- package/dist/commentary/index.d.ts +5 -0
- package/dist/commentary/index.js +5 -0
- package/dist/commentary/sanitizer.d.ts +37 -0
- package/dist/commentary/sanitizer.d.ts.map +1 -0
- package/dist/commentary/sanitizer.js +182 -0
- package/dist/commentary/sanitizer.js.map +1 -0
- package/dist/commentary/types.d.ts +120 -0
- package/dist/commentary/types.d.ts.map +1 -0
- package/dist/config.d.ts +760 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +217 -0
- package/dist/config.js.map +1 -0
- package/dist/consolidator/daemon.d.ts +88 -0
- package/dist/consolidator/daemon.d.ts.map +1 -0
- package/dist/consolidator/daemon.js +54 -0
- package/dist/consolidator/daemon.js.map +1 -0
- package/dist/consolidator/index.d.ts +2 -0
- package/dist/consolidator/index.js +3 -0
- package/dist/errors/index.d.ts +104 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +131 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/health/checks.d.ts +120 -0
- package/dist/health/checks.d.ts.map +1 -0
- package/dist/health/checks.js +127 -0
- package/dist/health/checks.js.map +1 -0
- package/dist/health/index.d.ts +3 -0
- package/dist/health/index.js +4 -0
- package/dist/health/routes.d.ts +66 -0
- package/dist/health/routes.d.ts.map +1 -0
- package/dist/health/routes.js +96 -0
- package/dist/health/routes.js.map +1 -0
- package/dist/index.d.ts +88 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +86 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/context.d.ts +66 -0
- package/dist/internal/context.d.ts.map +1 -0
- package/dist/internal/ids.js +21 -0
- package/dist/internal/ids.js.map +1 -0
- package/dist/internal/json.js +46 -0
- package/dist/internal/json.js.map +1 -0
- package/dist/lifecycle/hooks.d.ts +57 -0
- package/dist/lifecycle/hooks.d.ts.map +1 -0
- package/dist/lifecycle/index.d.ts +2 -0
- package/dist/lifecycle/index.js +3 -0
- package/dist/lifecycle/pre-bind.d.ts +38 -0
- package/dist/lifecycle/pre-bind.d.ts.map +1 -0
- package/dist/lifecycle/pre-bind.js +62 -0
- package/dist/lifecycle/pre-bind.js.map +1 -0
- package/dist/metrics/catalog.d.ts +34 -0
- package/dist/metrics/catalog.d.ts.map +1 -0
- package/dist/metrics/catalog.js +61 -0
- package/dist/metrics/catalog.js.map +1 -0
- package/dist/metrics/index.d.ts +3 -0
- package/dist/metrics/index.js +4 -0
- package/dist/metrics/registry.d.ts +63 -0
- package/dist/metrics/registry.d.ts.map +1 -0
- package/dist/metrics/registry.js +222 -0
- package/dist/metrics/registry.js.map +1 -0
- package/dist/middleware/audit.d.ts +47 -0
- package/dist/middleware/audit.d.ts.map +1 -0
- package/dist/middleware/audit.js +71 -0
- package/dist/middleware/audit.js.map +1 -0
- package/dist/middleware/auth.d.ts +50 -0
- package/dist/middleware/auth.d.ts.map +1 -0
- package/dist/middleware/auth.js +164 -0
- package/dist/middleware/auth.js.map +1 -0
- package/dist/middleware/cors.d.ts +15 -0
- package/dist/middleware/cors.d.ts.map +1 -0
- package/dist/middleware/cors.js +45 -0
- package/dist/middleware/cors.js.map +1 -0
- package/dist/middleware/csrf.d.ts +15 -0
- package/dist/middleware/csrf.d.ts.map +1 -0
- package/dist/middleware/csrf.js +77 -0
- package/dist/middleware/csrf.js.map +1 -0
- package/dist/middleware/idempotency.d.ts +41 -0
- package/dist/middleware/idempotency.d.ts.map +1 -0
- package/dist/middleware/idempotency.js +198 -0
- package/dist/middleware/idempotency.js.map +1 -0
- package/dist/middleware/index.d.ts +9 -0
- package/dist/middleware/index.js +10 -0
- package/dist/middleware/rate-limit.d.ts +17 -0
- package/dist/middleware/rate-limit.d.ts.map +1 -0
- package/dist/middleware/rate-limit.js +47 -0
- package/dist/middleware/rate-limit.js.map +1 -0
- package/dist/middleware/request-state.d.ts +27 -0
- package/dist/middleware/request-state.d.ts.map +1 -0
- package/dist/middleware/request-state.js +42 -0
- package/dist/middleware/request-state.js.map +1 -0
- package/dist/middleware/scope.d.ts +24 -0
- package/dist/middleware/scope.d.ts.map +1 -0
- package/dist/middleware/scope.js +50 -0
- package/dist/middleware/scope.js.map +1 -0
- package/dist/registry/index.d.ts +135 -0
- package/dist/registry/index.d.ts.map +1 -0
- package/dist/registry/index.js +96 -0
- package/dist/registry/index.js.map +1 -0
- package/dist/replay/index.d.ts +2 -0
- package/dist/replay/index.js +3 -0
- package/dist/replay/routes.d.ts +46 -0
- package/dist/replay/routes.d.ts.map +1 -0
- package/dist/replay/routes.js +189 -0
- package/dist/replay/routes.js.map +1 -0
- package/dist/routes/agents.d.ts +41 -0
- package/dist/routes/agents.d.ts.map +1 -0
- package/dist/routes/agents.js +213 -0
- package/dist/routes/agents.js.map +1 -0
- package/dist/routes/audit.d.ts +57 -0
- package/dist/routes/audit.d.ts.map +1 -0
- package/dist/routes/audit.js +116 -0
- package/dist/routes/audit.js.map +1 -0
- package/dist/routes/auth.d.ts +26 -0
- package/dist/routes/auth.d.ts.map +1 -0
- package/dist/routes/auth.js +54 -0
- package/dist/routes/auth.js.map +1 -0
- package/dist/routes/health.d.ts +22 -0
- package/dist/routes/health.d.ts.map +1 -0
- package/dist/routes/health.js +33 -0
- package/dist/routes/health.js.map +1 -0
- package/dist/routes/index.d.ts +10 -0
- package/dist/routes/index.js +12 -0
- package/dist/routes/mcp.d.ts +32 -0
- package/dist/routes/mcp.d.ts.map +1 -0
- package/dist/routes/mcp.js +61 -0
- package/dist/routes/mcp.js.map +1 -0
- package/dist/routes/memory.d.ts +141 -0
- package/dist/routes/memory.d.ts.map +1 -0
- package/dist/routes/memory.js +102 -0
- package/dist/routes/memory.js.map +1 -0
- package/dist/routes/sessions.d.ts +54 -0
- package/dist/routes/sessions.d.ts.map +1 -0
- package/dist/routes/sessions.js +135 -0
- package/dist/routes/sessions.js.map +1 -0
- package/dist/routes/skills.d.ts +34 -0
- package/dist/routes/skills.d.ts.map +1 -0
- package/dist/routes/skills.js +54 -0
- package/dist/routes/skills.js.map +1 -0
- package/dist/routes/tokens.d.ts +25 -0
- package/dist/routes/tokens.d.ts.map +1 -0
- package/dist/routes/tokens.js +87 -0
- package/dist/routes/tokens.js.map +1 -0
- package/dist/routes/workflows.d.ts +27 -0
- package/dist/routes/workflows.d.ts.map +1 -0
- package/dist/routes/workflows.js +220 -0
- package/dist/routes/workflows.js.map +1 -0
- package/dist/runtime/run-state.d.ts +158 -0
- package/dist/runtime/run-state.d.ts.map +1 -0
- package/dist/runtime/run-state.js +182 -0
- package/dist/runtime/run-state.js.map +1 -0
- package/dist/sse/events.d.ts +44 -0
- package/dist/sse/events.d.ts.map +1 -0
- package/dist/sse/events.js +200 -0
- package/dist/sse/events.js.map +1 -0
- package/dist/sse/index.d.ts +2 -0
- package/dist/sse/index.js +3 -0
- package/dist/triggers/daemon.d.ts +74 -0
- package/dist/triggers/daemon.d.ts.map +1 -0
- package/dist/triggers/daemon.js +114 -0
- package/dist/triggers/daemon.js.map +1 -0
- package/dist/triggers/index.d.ts +3 -0
- package/dist/triggers/index.js +4 -0
- package/dist/triggers/routes.d.ts +21 -0
- package/dist/triggers/routes.d.ts.map +1 -0
- package/dist/triggers/routes.js +117 -0
- package/dist/triggers/routes.js.map +1 -0
- package/dist/ws/dispatcher.d.ts +169 -0
- package/dist/ws/dispatcher.d.ts.map +1 -0
- package/dist/ws/dispatcher.js +303 -0
- package/dist/ws/dispatcher.js.map +1 -0
- package/dist/ws/index.d.ts +6 -0
- package/dist/ws/index.js +7 -0
- package/dist/ws/replay-buffer.d.ts +47 -0
- package/dist/ws/replay-buffer.d.ts.map +1 -0
- package/dist/ws/replay-buffer.js +88 -0
- package/dist/ws/replay-buffer.js.map +1 -0
- package/dist/ws/subjects.d.ts +71 -0
- package/dist/ws/subjects.d.ts.map +1 -0
- package/dist/ws/subjects.js +112 -0
- package/dist/ws/subjects.js.map +1 -0
- package/dist/ws/ticket.d.ts +74 -0
- package/dist/ws/ticket.d.ts.map +1 -0
- package/dist/ws/ticket.js +112 -0
- package/dist/ws/ticket.js.map +1 -0
- package/dist/ws/upgrade.d.ts +63 -0
- package/dist/ws/upgrade.d.ts.map +1 -0
- package/dist/ws/upgrade.js +324 -0
- package/dist/ws/upgrade.js.map +1 -0
- package/package.json +156 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { AgentNotFoundError } from "../errors/index.js";
|
|
2
|
+
import { createScopeMiddleware } from "../middleware/scope.js";
|
|
3
|
+
import { newRequestId } from "../internal/ids.js";
|
|
4
|
+
import { Hono } from "hono";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
|
|
7
|
+
//#region src/routes/agents.ts
|
|
8
|
+
const RunBodySchema = z.object({
|
|
9
|
+
input: z.unknown().optional(),
|
|
10
|
+
sessionId: z.string().min(1).optional(),
|
|
11
|
+
userId: z.string().min(1).optional()
|
|
12
|
+
}).strict().default({});
|
|
13
|
+
/**
|
|
14
|
+
* @stable
|
|
15
|
+
*/
|
|
16
|
+
function createAgentRoutes(deps) {
|
|
17
|
+
const app = new Hono();
|
|
18
|
+
const newRunId = deps.newRunId ?? newRequestId;
|
|
19
|
+
app.get("/", createScopeMiddleware("agents:read"), (c) => c.json({ agents: deps.agents.list() }));
|
|
20
|
+
app.get("/:id", createScopeMiddleware((_path, params) => `agents:read:${params.id}`), (c) => {
|
|
21
|
+
const id = c.req.param("id");
|
|
22
|
+
const summary = deps.agents.describe(id);
|
|
23
|
+
if (summary === void 0) return c.json({
|
|
24
|
+
error: "agent-not-found",
|
|
25
|
+
message: `Agent '${id}' is not registered.`
|
|
26
|
+
}, 404);
|
|
27
|
+
return c.json(summary);
|
|
28
|
+
});
|
|
29
|
+
app.post("/:id/run", createScopeMiddleware((_path, params) => `agents:invoke:${params.id}`), async (c) => {
|
|
30
|
+
const id = c.req.param("id");
|
|
31
|
+
const agent = deps.agents.get(id);
|
|
32
|
+
if (agent === void 0) {
|
|
33
|
+
const err = new AgentNotFoundError(id);
|
|
34
|
+
return c.json({
|
|
35
|
+
error: err.kind,
|
|
36
|
+
message: err.message
|
|
37
|
+
}, 404);
|
|
38
|
+
}
|
|
39
|
+
const parsed = RunBodySchema.safeParse(await safelyParseJson(c));
|
|
40
|
+
if (!parsed.success) return c.json({
|
|
41
|
+
error: "config-invalid",
|
|
42
|
+
message: "Invalid run body.",
|
|
43
|
+
issues: parsed.error.issues.map((i) => ({
|
|
44
|
+
path: i.path,
|
|
45
|
+
message: i.message
|
|
46
|
+
}))
|
|
47
|
+
}, 400);
|
|
48
|
+
const runId = newRunId();
|
|
49
|
+
const sessionId = parsed.data.sessionId;
|
|
50
|
+
const userId = parsed.data.userId;
|
|
51
|
+
const tracker = deps.runs.start(runId, {
|
|
52
|
+
kind: "agent",
|
|
53
|
+
agentId: id,
|
|
54
|
+
...sessionId !== void 0 ? { sessionId } : {},
|
|
55
|
+
...userId !== void 0 ? { userId } : {}
|
|
56
|
+
});
|
|
57
|
+
try {
|
|
58
|
+
const callOptions = { signal: tracker.signal };
|
|
59
|
+
if (sessionId !== void 0) callOptions.sessionId = sessionId;
|
|
60
|
+
if (userId !== void 0) callOptions.userId = userId;
|
|
61
|
+
const result = await agent.run(parsed.data.input ?? "", callOptions);
|
|
62
|
+
deps.runs.complete(runId, "completed");
|
|
63
|
+
return c.json({
|
|
64
|
+
runId,
|
|
65
|
+
status: "completed",
|
|
66
|
+
result
|
|
67
|
+
}, 200);
|
|
68
|
+
} catch (err) {
|
|
69
|
+
const aborted = tracker.signal.aborted;
|
|
70
|
+
deps.runs.complete(runId, aborted ? "aborted" : "failed", err);
|
|
71
|
+
return c.json({
|
|
72
|
+
runId,
|
|
73
|
+
status: aborted ? "aborted" : "failed",
|
|
74
|
+
error: aborted ? "run-aborted" : "run-failed",
|
|
75
|
+
message: err instanceof Error ? err.message : String(err)
|
|
76
|
+
}, aborted ? 408 : 500);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
app.post("/:id/stream", createScopeMiddleware((_path, params) => `agents:invoke:${params.id}`), async (c) => {
|
|
80
|
+
const id = c.req.param("id");
|
|
81
|
+
if (!deps.agents.has(id)) {
|
|
82
|
+
const err = new AgentNotFoundError(id);
|
|
83
|
+
return c.json({
|
|
84
|
+
error: err.kind,
|
|
85
|
+
message: err.message
|
|
86
|
+
}, 404);
|
|
87
|
+
}
|
|
88
|
+
const runId = newRunId();
|
|
89
|
+
const parsed = RunBodySchema.safeParse(await safelyParseJson(c));
|
|
90
|
+
const sessionId = parsed.success ? parsed.data.sessionId : void 0;
|
|
91
|
+
const userId = parsed.success ? parsed.data.userId : void 0;
|
|
92
|
+
const input = parsed.success ? parsed.data.input ?? "" : "";
|
|
93
|
+
const tracker = deps.runs.start(runId, {
|
|
94
|
+
kind: "agent",
|
|
95
|
+
agentId: id,
|
|
96
|
+
...sessionId !== void 0 ? { sessionId } : {},
|
|
97
|
+
...userId !== void 0 ? { userId } : {}
|
|
98
|
+
});
|
|
99
|
+
const subject = `agent:${id}/runs/${runId}/events`;
|
|
100
|
+
const agent = deps.agents.get(id);
|
|
101
|
+
if (agent !== void 0) backgroundStreamAgent(agent, input, {
|
|
102
|
+
signal: tracker.signal,
|
|
103
|
+
...sessionId !== void 0 ? { sessionId } : {},
|
|
104
|
+
...userId !== void 0 ? { userId } : {},
|
|
105
|
+
runs: deps.runs,
|
|
106
|
+
runId,
|
|
107
|
+
subject,
|
|
108
|
+
...deps.dispatcher !== void 0 ? { dispatcher: deps.dispatcher } : {}
|
|
109
|
+
});
|
|
110
|
+
return c.json({
|
|
111
|
+
runId,
|
|
112
|
+
status: "running",
|
|
113
|
+
subscribe: { websocket: subject }
|
|
114
|
+
}, 202);
|
|
115
|
+
});
|
|
116
|
+
return app;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* IP-2: consume the agent's event stream in the background and emit
|
|
120
|
+
* every event onto the run subject. Falls back to `run(...)` (a single
|
|
121
|
+
* terminal frame) for registry entries without a `stream` surface.
|
|
122
|
+
*/
|
|
123
|
+
function backgroundStreamAgent(agent, input, opts) {
|
|
124
|
+
const emit = (type, payload) => {
|
|
125
|
+
opts.dispatcher?.emit(opts.subject, {
|
|
126
|
+
type,
|
|
127
|
+
payload
|
|
128
|
+
});
|
|
129
|
+
};
|
|
130
|
+
(async () => {
|
|
131
|
+
try {
|
|
132
|
+
const callOpts = {
|
|
133
|
+
signal: opts.signal,
|
|
134
|
+
...opts.sessionId !== void 0 ? { sessionId: opts.sessionId } : {},
|
|
135
|
+
...opts.userId !== void 0 ? { userId: opts.userId } : {}
|
|
136
|
+
};
|
|
137
|
+
if (typeof agent.stream === "function") for await (const ev of agent.stream(input, callOpts)) {
|
|
138
|
+
if (opts.signal.aborted) break;
|
|
139
|
+
emit(typeof ev.type === "string" ? ev.type : "agent.event", ev);
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
const output = await agent.run(input, callOpts);
|
|
143
|
+
emit("agent.end", {
|
|
144
|
+
runId: opts.runId,
|
|
145
|
+
output
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
opts.runs.complete(opts.runId, opts.signal.aborted ? "aborted" : "completed");
|
|
149
|
+
} catch (err) {
|
|
150
|
+
emit("agent.error", {
|
|
151
|
+
runId: opts.runId,
|
|
152
|
+
message: err instanceof Error ? err.message : String(err)
|
|
153
|
+
});
|
|
154
|
+
opts.runs.complete(opts.runId, opts.signal.aborted ? "aborted" : "failed", err);
|
|
155
|
+
}
|
|
156
|
+
})();
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Companion router for the `/runs/...` surface. Kept separate so the
|
|
160
|
+
* `createServer` factory can mount it under the top-level base path
|
|
161
|
+
* rather than under `/agents`.
|
|
162
|
+
*
|
|
163
|
+
* @stable
|
|
164
|
+
*/
|
|
165
|
+
function createRunRoutes(deps) {
|
|
166
|
+
const app = new Hono();
|
|
167
|
+
app.get("/:runId/state", createScopeMiddleware("agents:read"), (c) => {
|
|
168
|
+
const runId = c.req.param("runId");
|
|
169
|
+
const state = deps.runs.snapshot(runId);
|
|
170
|
+
if (state === void 0) return c.json({
|
|
171
|
+
error: "run-not-found",
|
|
172
|
+
message: `Run '${runId}' is not registered.`
|
|
173
|
+
}, 404);
|
|
174
|
+
return c.json(state);
|
|
175
|
+
});
|
|
176
|
+
app.post("/:runId/abort", createScopeMiddleware("agents:invoke"), (c) => {
|
|
177
|
+
const runId = c.req.param("runId");
|
|
178
|
+
if (!deps.runs.abort(runId)) return c.json({
|
|
179
|
+
error: "run-not-found",
|
|
180
|
+
message: `Run '${runId}' is not registered.`
|
|
181
|
+
}, 404);
|
|
182
|
+
return c.json({
|
|
183
|
+
runId,
|
|
184
|
+
status: "aborted"
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
app.post("/:runId/resume", createScopeMiddleware("agents:invoke"), async (c) => {
|
|
188
|
+
const runId = c.req.param("runId");
|
|
189
|
+
const state = deps.runs.snapshot(runId);
|
|
190
|
+
if (state === void 0) return c.json({
|
|
191
|
+
error: "run-not-found",
|
|
192
|
+
message: `Run '${runId}' is not registered.`
|
|
193
|
+
}, 404);
|
|
194
|
+
return c.json({
|
|
195
|
+
error: "resume-not-implemented",
|
|
196
|
+
runId,
|
|
197
|
+
status: state.status,
|
|
198
|
+
message: "Server-side HITL resume is not implemented yet. Resume library-side: agent.run(result.state, { directive }) — the suspended RunState is on the AgentResult."
|
|
199
|
+
}, 501);
|
|
200
|
+
});
|
|
201
|
+
return app;
|
|
202
|
+
}
|
|
203
|
+
async function safelyParseJson(c) {
|
|
204
|
+
try {
|
|
205
|
+
return await c.req.json();
|
|
206
|
+
} catch {
|
|
207
|
+
return {};
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
//#endregion
|
|
212
|
+
export { createAgentRoutes, createRunRoutes };
|
|
213
|
+
//# sourceMappingURL=agents.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agents.js","names":["callOptions: { signal?: AbortSignal; sessionId?: string; userId?: string }"],"sources":["../../src/routes/agents.ts"],"sourcesContent":["/**\n * Agent REST routes.\n *\n * GET /agents (scope `agents:read`)\n * GET /agents/:id (scope `agents:read`)\n * POST /agents/:id/run (idempotent; scope `agents:invoke[:id]`)\n * POST /agents/:id/stream (scope `agents:invoke[:id]`)\n * GET /runs/:runId/state (scope `agents:read`)\n * POST /runs/:runId/abort (scope `agents:invoke`)\n * POST /runs/:runId/resume (idempotent; scope `agents:invoke`)\n *\n * Agents are looked up via {@link AgentRegistry}; missing entries\n * surface a 404 with a typed error body.\n *\n * @packageDocumentation\n */\n\nimport type { Context } from 'hono';\nimport { Hono } from 'hono';\nimport { z } from 'zod';\nimport { AgentNotFoundError } from '../errors/index.js';\nimport type { ServerVariables } from '../internal/context.js';\nimport { newRequestId } from '../internal/ids.js';\nimport { createScopeMiddleware } from '../middleware/scope.js';\nimport type { AgentRegistry } from '../registry/index.js';\nimport type { RunStateTracker } from '../runtime/run-state.js';\n\n/**\n * @stable\n */\nexport interface AgentRoutesDeps {\n readonly agents: AgentRegistry;\n readonly runs: RunStateTracker;\n readonly newRunId?: () => string;\n /**\n * Streaming dispatcher (IP-2). When present, `POST /:id/stream`\n * actually runs the agent and emits every event onto the\n * `agent:<id>/runs/<runId>/events` subject.\n */\n readonly dispatcher?: import('../ws/dispatcher.js').WsDispatcher;\n}\n\nconst RunBodySchema = z\n .object({\n input: z.unknown().optional(),\n sessionId: z.string().min(1).optional(),\n userId: z.string().min(1).optional(),\n })\n .strict()\n .default({});\n\n/**\n * @stable\n */\nexport function createAgentRoutes(deps: AgentRoutesDeps): Hono<{ Variables: ServerVariables }> {\n const app = new Hono<{ Variables: ServerVariables }>();\n const newRunId = deps.newRunId ?? newRequestId;\n\n app.get('/', createScopeMiddleware('agents:read'), (c) => c.json({ agents: deps.agents.list() }));\n\n app.get(\n '/:id',\n createScopeMiddleware((_path, params) => `agents:read:${params.id}`),\n (c) => {\n const id = c.req.param('id');\n const summary = deps.agents.describe(id);\n if (summary === undefined) {\n return c.json(\n { error: 'agent-not-found', message: `Agent '${id}' is not registered.` },\n 404,\n );\n }\n return c.json(summary);\n },\n );\n\n app.post(\n '/:id/run',\n createScopeMiddleware((_path, params) => `agents:invoke:${params.id}`),\n async (c) => {\n const id = c.req.param('id');\n const agent = deps.agents.get(id);\n if (agent === undefined) {\n const err = new AgentNotFoundError(id);\n return c.json({ error: err.kind, message: err.message }, 404);\n }\n const parsed = RunBodySchema.safeParse(await safelyParseJson(c));\n if (!parsed.success) {\n return c.json(\n {\n error: 'config-invalid',\n message: 'Invalid run body.',\n issues: parsed.error.issues.map((i) => ({ path: i.path, message: i.message })),\n },\n 400,\n );\n }\n const runId = newRunId();\n const sessionId = parsed.data.sessionId;\n const userId = parsed.data.userId;\n const tracker = deps.runs.start(runId, {\n kind: 'agent',\n agentId: id,\n ...(sessionId !== undefined ? { sessionId } : {}),\n ...(userId !== undefined ? { userId } : {}),\n });\n try {\n const callOptions: { signal?: AbortSignal; sessionId?: string; userId?: string } = {\n signal: tracker.signal,\n };\n if (sessionId !== undefined) callOptions.sessionId = sessionId;\n if (userId !== undefined) callOptions.userId = userId;\n const result = await agent.run(parsed.data.input ?? '', callOptions);\n deps.runs.complete(runId, 'completed');\n return c.json({ runId, status: 'completed', result }, 200);\n } catch (err) {\n const aborted = tracker.signal.aborted;\n deps.runs.complete(runId, aborted ? 'aborted' : 'failed', err);\n return c.json(\n {\n runId,\n status: aborted ? 'aborted' : 'failed',\n error: aborted ? 'run-aborted' : 'run-failed',\n message: err instanceof Error ? err.message : String(err),\n },\n aborted ? 408 : 500,\n );\n }\n },\n );\n\n app.post(\n '/:id/stream',\n createScopeMiddleware((_path, params) => `agents:invoke:${params.id}`),\n async (c) => {\n const id = c.req.param('id');\n if (!deps.agents.has(id)) {\n const err = new AgentNotFoundError(id);\n return c.json({ error: err.kind, message: err.message }, 404);\n }\n const runId = newRunId();\n const parsed = RunBodySchema.safeParse(await safelyParseJson(c));\n const sessionId = parsed.success ? parsed.data.sessionId : undefined;\n const userId = parsed.success ? parsed.data.userId : undefined;\n const input = parsed.success ? (parsed.data.input ?? '') : '';\n // IP-2: actually run the agent. The old handler parsed the input\n // and threw it away — the run sat 'pending' forever while the 202\n // advertised subjects nothing would ever emit on.\n const tracker = deps.runs.start(runId, {\n kind: 'agent',\n agentId: id,\n ...(sessionId !== undefined ? { sessionId } : {}),\n ...(userId !== undefined ? { userId } : {}),\n });\n const subject = `agent:${id}/runs/${runId}/events`;\n const agent = deps.agents.get(id);\n if (agent !== undefined) {\n backgroundStreamAgent(agent, input, {\n signal: tracker.signal,\n ...(sessionId !== undefined ? { sessionId } : {}),\n ...(userId !== undefined ? { userId } : {}),\n runs: deps.runs,\n runId,\n subject,\n ...(deps.dispatcher !== undefined ? { dispatcher: deps.dispatcher } : {}),\n });\n }\n return c.json(\n {\n runId,\n status: 'running',\n subscribe: {\n // The SSE URL previously advertised here pointed at a path\n // that was never mounted — the WebSocket subject is the\n // delivery channel (IP-2).\n websocket: subject,\n },\n },\n 202,\n );\n },\n );\n\n return app;\n}\n\n/**\n * IP-2: consume the agent's event stream in the background and emit\n * every event onto the run subject. Falls back to `run(...)` (a single\n * terminal frame) for registry entries without a `stream` surface.\n */\nfunction backgroundStreamAgent(\n agent: ReturnType<AgentRegistry['get']> & object,\n input: unknown,\n opts: {\n readonly signal: AbortSignal;\n readonly sessionId?: string;\n readonly userId?: string;\n readonly runs: RunStateTracker;\n readonly runId: string;\n readonly subject: string;\n readonly dispatcher?: import('../ws/dispatcher.js').WsDispatcher;\n },\n): void {\n const emit = (type: string, payload: unknown): void => {\n opts.dispatcher?.emit(opts.subject, { type, payload });\n };\n void (async () => {\n try {\n const callOpts = {\n signal: opts.signal,\n ...(opts.sessionId !== undefined ? { sessionId: opts.sessionId } : {}),\n ...(opts.userId !== undefined ? { userId: opts.userId } : {}),\n };\n if (typeof agent.stream === 'function') {\n for await (const ev of agent.stream(input, callOpts)) {\n if (opts.signal.aborted) break;\n const type =\n typeof (ev as { type?: unknown }).type === 'string'\n ? (ev as { type: string }).type\n : 'agent.event';\n emit(type, ev);\n }\n } else {\n const output = await agent.run(input, callOpts);\n emit('agent.end', { runId: opts.runId, output });\n }\n opts.runs.complete(opts.runId, opts.signal.aborted ? 'aborted' : 'completed');\n } catch (err) {\n emit('agent.error', {\n runId: opts.runId,\n message: err instanceof Error ? err.message : String(err),\n });\n opts.runs.complete(opts.runId, opts.signal.aborted ? 'aborted' : 'failed', err);\n }\n })();\n}\n\n/**\n * Companion router for the `/runs/...` surface. Kept separate so the\n * `createServer` factory can mount it under the top-level base path\n * rather than under `/agents`.\n *\n * @stable\n */\nexport function createRunRoutes(deps: AgentRoutesDeps): Hono<{ Variables: ServerVariables }> {\n const app = new Hono<{ Variables: ServerVariables }>();\n\n app.get('/:runId/state', createScopeMiddleware('agents:read'), (c) => {\n const runId = c.req.param('runId');\n const state = deps.runs.snapshot(runId);\n if (state === undefined) {\n return c.json({ error: 'run-not-found', message: `Run '${runId}' is not registered.` }, 404);\n }\n return c.json(state);\n });\n\n app.post('/:runId/abort', createScopeMiddleware('agents:invoke'), (c) => {\n const runId = c.req.param('runId');\n const aborted = deps.runs.abort(runId);\n if (!aborted) {\n return c.json({ error: 'run-not-found', message: `Run '${runId}' is not registered.` }, 404);\n }\n return c.json({ runId, status: 'aborted' });\n });\n\n app.post('/:runId/resume', createScopeMiddleware('agents:invoke'), async (c) => {\n const runId = c.req.param('runId');\n const state = deps.runs.snapshot(runId);\n if (state === undefined) {\n return c.json({ error: 'run-not-found', message: `Run '${runId}' is not registered.` }, 404);\n }\n // IP-14: a 202 that persists nothing and resumes nothing is a lie\n // the client SDK was built on. Until the server can rehydrate the\n // RunState and re-enter the agent loop (the persisted-approvals\n // work — Wave 3), the honest answer is 501 with the working\n // library-side path: `agent.run(result.state, { directive })`\n // executes approved tools for real (AG-1/AG-9).\n return c.json(\n {\n error: 'resume-not-implemented',\n runId,\n status: state.status,\n message:\n 'Server-side HITL resume is not implemented yet. Resume library-side: ' +\n 'agent.run(result.state, { directive }) — the suspended RunState is on the AgentResult.',\n },\n 501,\n );\n });\n\n return app;\n}\n\nasync function safelyParseJson(c: Context<{ Variables: ServerVariables }>): Promise<unknown> {\n try {\n return await c.req.json();\n } catch {\n return {};\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { ServerVariables } from "../internal/context.js";
|
|
2
|
+
import { Hono } from "hono";
|
|
3
|
+
|
|
4
|
+
//#region src/routes/audit.d.ts
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @stable
|
|
8
|
+
*/
|
|
9
|
+
interface AuditApi {
|
|
10
|
+
list(opts: {
|
|
11
|
+
readonly limit?: number;
|
|
12
|
+
readonly fromSeq?: number;
|
|
13
|
+
/** Inclusive lower bound on the entry timestamp (epoch ms). */
|
|
14
|
+
readonly fromTs?: number;
|
|
15
|
+
/** Inclusive upper bound on the entry timestamp (epoch ms). */
|
|
16
|
+
readonly toTs?: number;
|
|
17
|
+
/** Restrict to entries whose `action` matches the supplied id. */
|
|
18
|
+
readonly action?: string;
|
|
19
|
+
}): Promise<ReadonlyArray<unknown>>;
|
|
20
|
+
export(opts: {
|
|
21
|
+
readonly fromSeq?: number;
|
|
22
|
+
readonly toSeq?: number;
|
|
23
|
+
readonly format?: 'jsonl' | 'csv';
|
|
24
|
+
}): Promise<{
|
|
25
|
+
readonly bytes: number;
|
|
26
|
+
readonly format?: 'jsonl' | 'csv';
|
|
27
|
+
}>;
|
|
28
|
+
/**
|
|
29
|
+
* Verify the chain integrity of every audit row in the inclusive
|
|
30
|
+
* range. Phase 14c surfaces this through `POST /v1/audit/verify`.
|
|
31
|
+
* Optional — operators that opt out of the audit chain should
|
|
32
|
+
* leave this method off.
|
|
33
|
+
*/
|
|
34
|
+
verify?(opts: {
|
|
35
|
+
readonly fromSeq?: number;
|
|
36
|
+
readonly toSeq?: number;
|
|
37
|
+
}): Promise<{
|
|
38
|
+
readonly ok: boolean;
|
|
39
|
+
readonly count?: number;
|
|
40
|
+
readonly brokenAt?: number;
|
|
41
|
+
}>;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* @stable
|
|
45
|
+
*/
|
|
46
|
+
interface AuditRoutesDeps {
|
|
47
|
+
readonly audit: AuditApi;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* @stable
|
|
51
|
+
*/
|
|
52
|
+
declare function createAuditRoutes(deps: AuditRoutesDeps): Hono<{
|
|
53
|
+
Variables: ServerVariables;
|
|
54
|
+
}>;
|
|
55
|
+
//#endregion
|
|
56
|
+
export { AuditApi, AuditRoutesDeps, createAuditRoutes };
|
|
57
|
+
//# sourceMappingURL=audit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit.d.ts","names":[],"sources":["../../src/routes/audit.ts"],"sourcesContent":[],"mappings":";;;;;;;;UAsBiB,QAAA;;;;;;;;;;MAUX,QAAQ;;;;;MAKR;;;;;;;;;;;;;MAUA;;;;;;;;;UAkCW,eAAA;kBACC;;;;;iBAMF,iBAAA,OAAwB,kBAAkB;aAAkB"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { createScopeMiddleware } from "../middleware/scope.js";
|
|
2
|
+
import { Hono } from "hono";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
|
|
5
|
+
//#region src/routes/audit.ts
|
|
6
|
+
const ISO_OR_EPOCH = z.union([z.coerce.number().int().nonnegative(), z.string().min(1)]);
|
|
7
|
+
const ListQuerySchema = z.object({
|
|
8
|
+
limit: z.coerce.number().int().positive().max(1e3).optional(),
|
|
9
|
+
fromSeq: z.coerce.number().int().nonnegative().optional(),
|
|
10
|
+
from: ISO_OR_EPOCH.optional(),
|
|
11
|
+
to: ISO_OR_EPOCH.optional(),
|
|
12
|
+
action: z.string().min(1).max(256).optional()
|
|
13
|
+
}).strict();
|
|
14
|
+
function parseTimestamp(input) {
|
|
15
|
+
if (input === void 0) return void 0;
|
|
16
|
+
if (typeof input === "number") return input;
|
|
17
|
+
const parsed = Date.parse(String(input));
|
|
18
|
+
return Number.isNaN(parsed) ? void 0 : parsed;
|
|
19
|
+
}
|
|
20
|
+
const ExportBodySchema = z.object({
|
|
21
|
+
fromSeq: z.number().int().nonnegative().optional(),
|
|
22
|
+
toSeq: z.number().int().nonnegative().optional(),
|
|
23
|
+
format: z.enum(["jsonl", "csv"]).optional()
|
|
24
|
+
}).strict().default({});
|
|
25
|
+
/**
|
|
26
|
+
* @stable
|
|
27
|
+
*/
|
|
28
|
+
function createAuditRoutes(deps) {
|
|
29
|
+
const app = new Hono();
|
|
30
|
+
app.get("/", createScopeMiddleware("audit:read"), async (c) => {
|
|
31
|
+
const parsed = ListQuerySchema.safeParse({
|
|
32
|
+
limit: c.req.query("limit"),
|
|
33
|
+
fromSeq: c.req.query("fromSeq"),
|
|
34
|
+
from: c.req.query("from"),
|
|
35
|
+
to: c.req.query("to"),
|
|
36
|
+
action: c.req.query("action")
|
|
37
|
+
});
|
|
38
|
+
if (!parsed.success) return c.json({
|
|
39
|
+
error: "config-invalid",
|
|
40
|
+
message: "Invalid audit query.",
|
|
41
|
+
issues: parsed.error.issues.map((i) => ({
|
|
42
|
+
path: i.path,
|
|
43
|
+
message: i.message
|
|
44
|
+
}))
|
|
45
|
+
}, 400);
|
|
46
|
+
const fromTs = parseTimestamp(parsed.data.from);
|
|
47
|
+
const toTs = parseTimestamp(parsed.data.to);
|
|
48
|
+
if (parsed.data.from !== void 0 && fromTs === void 0) return c.json({
|
|
49
|
+
error: "config-invalid",
|
|
50
|
+
message: `Invalid 'from' timestamp: '${String(parsed.data.from)}' is not parseable.`
|
|
51
|
+
}, 400);
|
|
52
|
+
if (parsed.data.to !== void 0 && toTs === void 0) return c.json({
|
|
53
|
+
error: "config-invalid",
|
|
54
|
+
message: `Invalid 'to' timestamp: '${String(parsed.data.to)}' is not parseable.`
|
|
55
|
+
}, 400);
|
|
56
|
+
const filters = {};
|
|
57
|
+
if (parsed.data.limit !== void 0) filters.limit = parsed.data.limit;
|
|
58
|
+
if (parsed.data.fromSeq !== void 0) filters.fromSeq = parsed.data.fromSeq;
|
|
59
|
+
if (fromTs !== void 0) filters.fromTs = fromTs;
|
|
60
|
+
if (toTs !== void 0) filters.toTs = toTs;
|
|
61
|
+
if (parsed.data.action !== void 0) filters.action = parsed.data.action;
|
|
62
|
+
const entries = await deps.audit.list(filters);
|
|
63
|
+
return c.json({ entries });
|
|
64
|
+
});
|
|
65
|
+
app.post("/verify", createScopeMiddleware("audit:verify"), async (c) => {
|
|
66
|
+
if (deps.audit.verify === void 0) return c.json({
|
|
67
|
+
error: "not-implemented",
|
|
68
|
+
message: "Audit chain verification is not enabled on this deployment."
|
|
69
|
+
}, 501);
|
|
70
|
+
const parsed = ExportBodySchema.safeParse(await safelyParseJson(c));
|
|
71
|
+
if (!parsed.success) return c.json({
|
|
72
|
+
error: "config-invalid",
|
|
73
|
+
message: "Invalid verify body.",
|
|
74
|
+
issues: parsed.error.issues.map((i) => ({
|
|
75
|
+
path: i.path,
|
|
76
|
+
message: i.message
|
|
77
|
+
}))
|
|
78
|
+
}, 400);
|
|
79
|
+
const opts = {};
|
|
80
|
+
if (parsed.data.fromSeq !== void 0) opts.fromSeq = parsed.data.fromSeq;
|
|
81
|
+
if (parsed.data.toSeq !== void 0) opts.toSeq = parsed.data.toSeq;
|
|
82
|
+
const result = await deps.audit.verify(opts);
|
|
83
|
+
return c.json({ verify: result });
|
|
84
|
+
});
|
|
85
|
+
app.post("/export", createScopeMiddleware("audit:export"), async (c) => {
|
|
86
|
+
const parsed = ExportBodySchema.safeParse(await safelyParseJson(c));
|
|
87
|
+
if (!parsed.success) return c.json({
|
|
88
|
+
error: "config-invalid",
|
|
89
|
+
message: "Invalid export body.",
|
|
90
|
+
issues: parsed.error.issues.map((i) => ({
|
|
91
|
+
path: i.path,
|
|
92
|
+
message: i.message
|
|
93
|
+
}))
|
|
94
|
+
}, 400);
|
|
95
|
+
const queryFormat = c.req.query("format");
|
|
96
|
+
const requestedFormat = parsed.data.format ?? (queryFormat === "csv" ? "csv" : queryFormat === "jsonl" ? "jsonl" : void 0);
|
|
97
|
+
const opts = {};
|
|
98
|
+
if (parsed.data.fromSeq !== void 0) opts.fromSeq = parsed.data.fromSeq;
|
|
99
|
+
if (parsed.data.toSeq !== void 0) opts.toSeq = parsed.data.toSeq;
|
|
100
|
+
if (requestedFormat !== void 0) opts.format = requestedFormat;
|
|
101
|
+
const result = await deps.audit.export(opts);
|
|
102
|
+
return c.json({ export: result });
|
|
103
|
+
});
|
|
104
|
+
return app;
|
|
105
|
+
}
|
|
106
|
+
async function safelyParseJson(c) {
|
|
107
|
+
try {
|
|
108
|
+
return await c.req.json();
|
|
109
|
+
} catch {
|
|
110
|
+
return {};
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
//#endregion
|
|
115
|
+
export { createAuditRoutes };
|
|
116
|
+
//# sourceMappingURL=audit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit.js","names":["filters: {\n limit?: number;\n fromSeq?: number;\n fromTs?: number;\n toTs?: number;\n action?: string;\n }","opts: { fromSeq?: number; toSeq?: number }","opts: { fromSeq?: number; toSeq?: number; format?: 'jsonl' | 'csv' }"],"sources":["../../src/routes/audit.ts"],"sourcesContent":["/**\n * Audit-log REST routes.\n *\n * GET /audit (scope `audit:read`)\n * POST /audit/export (scope `audit:export`)\n *\n * The handler delegates to a structurally-typed `AuditApi` so the\n * server code stays decoupled from any single audit backend.\n *\n * @packageDocumentation\n */\n\nimport type { Context } from 'hono';\nimport { Hono } from 'hono';\nimport { z } from 'zod';\n\nimport type { ServerVariables } from '../internal/context.js';\nimport { createScopeMiddleware } from '../middleware/scope.js';\n\n/**\n * @stable\n */\nexport interface AuditApi {\n list(opts: {\n readonly limit?: number;\n readonly fromSeq?: number;\n /** Inclusive lower bound on the entry timestamp (epoch ms). */\n readonly fromTs?: number;\n /** Inclusive upper bound on the entry timestamp (epoch ms). */\n readonly toTs?: number;\n /** Restrict to entries whose `action` matches the supplied id. */\n readonly action?: string;\n }): Promise<ReadonlyArray<unknown>>;\n export(opts: {\n readonly fromSeq?: number;\n readonly toSeq?: number;\n readonly format?: 'jsonl' | 'csv';\n }): Promise<{ readonly bytes: number; readonly format?: 'jsonl' | 'csv' }>;\n /**\n * Verify the chain integrity of every audit row in the inclusive\n * range. Phase 14c surfaces this through `POST /v1/audit/verify`.\n * Optional — operators that opt out of the audit chain should\n * leave this method off.\n */\n verify?(opts: {\n readonly fromSeq?: number;\n readonly toSeq?: number;\n }): Promise<{ readonly ok: boolean; readonly count?: number; readonly brokenAt?: number }>;\n}\n\nconst ISO_OR_EPOCH = z.union([z.coerce.number().int().nonnegative(), z.string().min(1)]);\n\nconst ListQuerySchema = z\n .object({\n limit: z.coerce.number().int().positive().max(1000).optional(),\n fromSeq: z.coerce.number().int().nonnegative().optional(),\n from: ISO_OR_EPOCH.optional(),\n to: ISO_OR_EPOCH.optional(),\n action: z.string().min(1).max(256).optional(),\n })\n .strict();\n\nfunction parseTimestamp(input: unknown): number | undefined {\n if (input === undefined) return undefined;\n if (typeof input === 'number') return input;\n const parsed = Date.parse(String(input));\n return Number.isNaN(parsed) ? undefined : parsed;\n}\n\nconst ExportBodySchema = z\n .object({\n fromSeq: z.number().int().nonnegative().optional(),\n toSeq: z.number().int().nonnegative().optional(),\n format: z.enum(['jsonl', 'csv']).optional(),\n })\n .strict()\n .default({});\n\n/**\n * @stable\n */\nexport interface AuditRoutesDeps {\n readonly audit: AuditApi;\n}\n\n/**\n * @stable\n */\nexport function createAuditRoutes(deps: AuditRoutesDeps): Hono<{ Variables: ServerVariables }> {\n const app = new Hono<{ Variables: ServerVariables }>();\n\n app.get('/', createScopeMiddleware('audit:read'), async (c) => {\n const parsed = ListQuerySchema.safeParse({\n limit: c.req.query('limit'),\n fromSeq: c.req.query('fromSeq'),\n from: c.req.query('from'),\n to: c.req.query('to'),\n action: c.req.query('action'),\n });\n if (!parsed.success) {\n return c.json(\n {\n error: 'config-invalid',\n message: 'Invalid audit query.',\n issues: parsed.error.issues.map((i) => ({ path: i.path, message: i.message })),\n },\n 400,\n );\n }\n const fromTs = parseTimestamp(parsed.data.from);\n const toTs = parseTimestamp(parsed.data.to);\n if (parsed.data.from !== undefined && fromTs === undefined) {\n return c.json(\n {\n error: 'config-invalid',\n message: `Invalid 'from' timestamp: '${String(parsed.data.from)}' is not parseable.`,\n },\n 400,\n );\n }\n if (parsed.data.to !== undefined && toTs === undefined) {\n return c.json(\n {\n error: 'config-invalid',\n message: `Invalid 'to' timestamp: '${String(parsed.data.to)}' is not parseable.`,\n },\n 400,\n );\n }\n const filters: {\n limit?: number;\n fromSeq?: number;\n fromTs?: number;\n toTs?: number;\n action?: string;\n } = {};\n if (parsed.data.limit !== undefined) filters.limit = parsed.data.limit;\n if (parsed.data.fromSeq !== undefined) filters.fromSeq = parsed.data.fromSeq;\n if (fromTs !== undefined) filters.fromTs = fromTs;\n if (toTs !== undefined) filters.toTs = toTs;\n if (parsed.data.action !== undefined) filters.action = parsed.data.action;\n const entries = await deps.audit.list(filters);\n return c.json({ entries });\n });\n\n app.post('/verify', createScopeMiddleware('audit:verify'), async (c) => {\n if (deps.audit.verify === undefined) {\n return c.json(\n {\n error: 'not-implemented',\n message: 'Audit chain verification is not enabled on this deployment.',\n },\n 501,\n );\n }\n const parsed = ExportBodySchema.safeParse(await safelyParseJson(c));\n if (!parsed.success) {\n return c.json(\n {\n error: 'config-invalid',\n message: 'Invalid verify body.',\n issues: parsed.error.issues.map((i) => ({ path: i.path, message: i.message })),\n },\n 400,\n );\n }\n const opts: { fromSeq?: number; toSeq?: number } = {};\n if (parsed.data.fromSeq !== undefined) opts.fromSeq = parsed.data.fromSeq;\n if (parsed.data.toSeq !== undefined) opts.toSeq = parsed.data.toSeq;\n const result = await deps.audit.verify(opts);\n return c.json({ verify: result });\n });\n\n app.post('/export', createScopeMiddleware('audit:export'), async (c) => {\n const parsed = ExportBodySchema.safeParse(await safelyParseJson(c));\n if (!parsed.success) {\n return c.json(\n {\n error: 'config-invalid',\n message: 'Invalid export body.',\n issues: parsed.error.issues.map((i) => ({ path: i.path, message: i.message })),\n },\n 400,\n );\n }\n const queryFormat = c.req.query('format');\n const requestedFormat =\n parsed.data.format ??\n (queryFormat === 'csv' ? 'csv' : queryFormat === 'jsonl' ? 'jsonl' : undefined);\n const opts: { fromSeq?: number; toSeq?: number; format?: 'jsonl' | 'csv' } = {};\n if (parsed.data.fromSeq !== undefined) opts.fromSeq = parsed.data.fromSeq;\n if (parsed.data.toSeq !== undefined) opts.toSeq = parsed.data.toSeq;\n if (requestedFormat !== undefined) opts.format = requestedFormat;\n const result = await deps.audit.export(opts);\n return c.json({ export: result });\n });\n\n return app;\n}\n\nasync function safelyParseJson(c: Context<{ Variables: ServerVariables }>): Promise<unknown> {\n try {\n return await c.req.json();\n } catch {\n return {};\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { ServerVariables } from "../internal/context.js";
|
|
2
|
+
import { WsTicketStore } from "../ws/ticket.js";
|
|
3
|
+
import { Hono } from "hono";
|
|
4
|
+
|
|
5
|
+
//#region src/routes/auth.d.ts
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Stable shape consumed by {@link createAuthRoutes}.
|
|
9
|
+
*
|
|
10
|
+
* @stable
|
|
11
|
+
*/
|
|
12
|
+
interface AuthRoutesDeps {
|
|
13
|
+
readonly tickets: WsTicketStore;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Build the auth router. The router is mounted at the same base path
|
|
17
|
+
* as the rest of the REST surface (defaults to `/v1`).
|
|
18
|
+
*
|
|
19
|
+
* @stable
|
|
20
|
+
*/
|
|
21
|
+
declare function createAuthRoutes(deps: AuthRoutesDeps): Hono<{
|
|
22
|
+
Variables: ServerVariables;
|
|
23
|
+
}>;
|
|
24
|
+
//#endregion
|
|
25
|
+
export { AuthRoutesDeps, createAuthRoutes };
|
|
26
|
+
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","names":[],"sources":["../../src/routes/auth.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;UA4BiB,cAAA;oBACG;;;;;;;;iBASJ,gBAAA,OAAuB,iBAAiB;aAAkB"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { createScopeMiddleware } from "../middleware/scope.js";
|
|
2
|
+
import { Hono } from "hono";
|
|
3
|
+
|
|
4
|
+
//#region src/routes/auth.ts
|
|
5
|
+
/**
|
|
6
|
+
* Auth-related REST routes that exist purely to support the WebSocket
|
|
7
|
+
* surface in `@graphorin/server/ws`.
|
|
8
|
+
*
|
|
9
|
+
* POST /session/ws-ticket (scope `agents:invoke`)
|
|
10
|
+
*
|
|
11
|
+
* Browser clients call the endpoint with a regular HTTP `Authorization:
|
|
12
|
+
* Bearer <token>` header to mint a single-use, short-lived ticket; the
|
|
13
|
+
* ticket value is then attached to the WebSocket upgrade request as a
|
|
14
|
+
* second `Sec-WebSocket-Protocol` token (`ticket.<value>`). The
|
|
15
|
+
* server-side WS upgrade handler (see `../ws/upgrade.ts`) consumes the
|
|
16
|
+
* ticket against the same in-memory store so the round-trip is
|
|
17
|
+
* race-free.
|
|
18
|
+
*
|
|
19
|
+
* @packageDocumentation
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Build the auth router. The router is mounted at the same base path
|
|
23
|
+
* as the rest of the REST surface (defaults to `/v1`).
|
|
24
|
+
*
|
|
25
|
+
* @stable
|
|
26
|
+
*/
|
|
27
|
+
function createAuthRoutes(deps) {
|
|
28
|
+
const app = new Hono();
|
|
29
|
+
app.post("/session/ws-ticket", createScopeMiddleware("agents:invoke"), (c) => {
|
|
30
|
+
const auth = c.get("state").auth;
|
|
31
|
+
const principal = auth.kind === "token" ? {
|
|
32
|
+
tokenId: auth.token.tokenId,
|
|
33
|
+
scopes: auth.grantedScopes
|
|
34
|
+
} : auth.kind === "anonymous" ? {
|
|
35
|
+
tokenId: "anonymous",
|
|
36
|
+
scopes: auth.grantedScopes
|
|
37
|
+
} : void 0;
|
|
38
|
+
if (principal === void 0) return c.json({
|
|
39
|
+
error: "auth-required",
|
|
40
|
+
message: "Bearer token required to mint a WS ticket."
|
|
41
|
+
}, 401);
|
|
42
|
+
const ticket = deps.tickets.issue(principal);
|
|
43
|
+
return c.json({
|
|
44
|
+
ticket: ticket.value,
|
|
45
|
+
expiresAt: ticket.expiresAt,
|
|
46
|
+
ttlMs: deps.tickets.ttlMs
|
|
47
|
+
}, 201);
|
|
48
|
+
});
|
|
49
|
+
return app;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
//#endregion
|
|
53
|
+
export { createAuthRoutes };
|
|
54
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","names":[],"sources":["../../src/routes/auth.ts"],"sourcesContent":["/**\n * Auth-related REST routes that exist purely to support the WebSocket\n * surface in `@graphorin/server/ws`.\n *\n * POST /session/ws-ticket (scope `agents:invoke`)\n *\n * Browser clients call the endpoint with a regular HTTP `Authorization:\n * Bearer <token>` header to mint a single-use, short-lived ticket; the\n * ticket value is then attached to the WebSocket upgrade request as a\n * second `Sec-WebSocket-Protocol` token (`ticket.<value>`). The\n * server-side WS upgrade handler (see `../ws/upgrade.ts`) consumes the\n * ticket against the same in-memory store so the round-trip is\n * race-free.\n *\n * @packageDocumentation\n */\n\nimport { Hono } from 'hono';\n\nimport type { ServerVariables } from '../internal/context.js';\nimport { createScopeMiddleware } from '../middleware/scope.js';\nimport type { WsTicketStore } from '../ws/ticket.js';\n\n/**\n * Stable shape consumed by {@link createAuthRoutes}.\n *\n * @stable\n */\nexport interface AuthRoutesDeps {\n readonly tickets: WsTicketStore;\n}\n\n/**\n * Build the auth router. The router is mounted at the same base path\n * as the rest of the REST surface (defaults to `/v1`).\n *\n * @stable\n */\nexport function createAuthRoutes(deps: AuthRoutesDeps): Hono<{ Variables: ServerVariables }> {\n const app = new Hono<{ Variables: ServerVariables }>();\n\n app.post('/session/ws-ticket', createScopeMiddleware('agents:invoke'), (c) => {\n const auth = c.get('state').auth;\n // IP-13: in the no-auth loopback mode (`auth.kind='none'`) there is no\n // bearer token, but the anonymous principal is fully authorized — mint a\n // ticket bound to a synthetic `anonymous` id so a browser client written\n // for token mode still completes the round-trip.\n const principal =\n auth.kind === 'token'\n ? { tokenId: auth.token.tokenId, scopes: auth.grantedScopes }\n : auth.kind === 'anonymous'\n ? { tokenId: 'anonymous', scopes: auth.grantedScopes }\n : undefined;\n if (principal === undefined) {\n return c.json(\n {\n error: 'auth-required',\n message: 'Bearer token required to mint a WS ticket.',\n },\n 401,\n );\n }\n const ticket = deps.tickets.issue(principal);\n return c.json(\n {\n ticket: ticket.value,\n expiresAt: ticket.expiresAt,\n ttlMs: deps.tickets.ttlMs,\n },\n 201,\n );\n });\n\n return app;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAsCA,SAAgB,iBAAiB,MAA4D;CAC3F,MAAM,MAAM,IAAI,MAAsC;AAEtD,KAAI,KAAK,sBAAsB,sBAAsB,gBAAgB,GAAG,MAAM;EAC5E,MAAM,OAAO,EAAE,IAAI,QAAQ,CAAC;EAK5B,MAAM,YACJ,KAAK,SAAS,UACV;GAAE,SAAS,KAAK,MAAM;GAAS,QAAQ,KAAK;GAAe,GAC3D,KAAK,SAAS,cACZ;GAAE,SAAS;GAAa,QAAQ,KAAK;GAAe,GACpD;AACR,MAAI,cAAc,OAChB,QAAO,EAAE,KACP;GACE,OAAO;GACP,SAAS;GACV,EACD,IACD;EAEH,MAAM,SAAS,KAAK,QAAQ,MAAM,UAAU;AAC5C,SAAO,EAAE,KACP;GACE,QAAQ,OAAO;GACf,WAAW,OAAO;GAClB,OAAO,KAAK,QAAQ;GACrB,EACD,IACD;GACD;AAEF,QAAO"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { ServerVariables } from "../internal/context.js";
|
|
2
|
+
import { Hono } from "hono";
|
|
3
|
+
|
|
4
|
+
//#region src/routes/health.d.ts
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @stable
|
|
8
|
+
*/
|
|
9
|
+
interface HealthRoutesDeps {
|
|
10
|
+
readonly version: string;
|
|
11
|
+
readonly startedAt: number;
|
|
12
|
+
readonly now?: () => number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* @stable
|
|
16
|
+
*/
|
|
17
|
+
declare function createHealthRoutes(deps: HealthRoutesDeps): Hono<{
|
|
18
|
+
Variables: ServerVariables;
|
|
19
|
+
}>;
|
|
20
|
+
//#endregion
|
|
21
|
+
export { HealthRoutesDeps, createHealthRoutes };
|
|
22
|
+
//# sourceMappingURL=health.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"health.d.ts","names":[],"sources":["../../src/routes/health.ts"],"sourcesContent":[],"mappings":";;;;;;;;UAqBiB,gBAAA;;;;;;;;iBASD,kBAAA,OAAyB,mBAAmB;aAAkB"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
|
|
3
|
+
//#region src/routes/health.ts
|
|
4
|
+
/**
|
|
5
|
+
* `GET /v1/health` — minimal health endpoint kept for backward
|
|
6
|
+
* compatibility with consumers that wired the Phase 14a route
|
|
7
|
+
* factory directly. The active server now mounts the extended
|
|
8
|
+
* Phase 14c routes from `@graphorin/server/health`. New code should
|
|
9
|
+
* use {@link import('../health/routes.js').createExtendedHealthRoutes}.
|
|
10
|
+
*
|
|
11
|
+
* @deprecated Phase 14c. Use `createExtendedHealthRoutes` from
|
|
12
|
+
* `@graphorin/server/health` instead. This export
|
|
13
|
+
* stays available so existing consumers do not break.
|
|
14
|
+
*
|
|
15
|
+
* @packageDocumentation
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* @stable
|
|
19
|
+
*/
|
|
20
|
+
function createHealthRoutes(deps) {
|
|
21
|
+
const app = new Hono();
|
|
22
|
+
const now = deps.now ?? Date.now;
|
|
23
|
+
app.get("/", (c) => c.json({
|
|
24
|
+
status: "ok",
|
|
25
|
+
version: deps.version,
|
|
26
|
+
uptimeSeconds: Math.max(0, Math.floor((now() - deps.startedAt) / 1e3))
|
|
27
|
+
}));
|
|
28
|
+
return app;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
//#endregion
|
|
32
|
+
export { createHealthRoutes };
|
|
33
|
+
//# sourceMappingURL=health.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"health.js","names":[],"sources":["../../src/routes/health.ts"],"sourcesContent":["/**\n * `GET /v1/health` — minimal health endpoint kept for backward\n * compatibility with consumers that wired the Phase 14a route\n * factory directly. The active server now mounts the extended\n * Phase 14c routes from `@graphorin/server/health`. New code should\n * use {@link import('../health/routes.js').createExtendedHealthRoutes}.\n *\n * @deprecated Phase 14c. Use `createExtendedHealthRoutes` from\n * `@graphorin/server/health` instead. This export\n * stays available so existing consumers do not break.\n *\n * @packageDocumentation\n */\n\nimport { Hono } from 'hono';\n\nimport type { ServerVariables } from '../internal/context.js';\n\n/**\n * @stable\n */\nexport interface HealthRoutesDeps {\n readonly version: string;\n readonly startedAt: number;\n readonly now?: () => number;\n}\n\n/**\n * @stable\n */\nexport function createHealthRoutes(deps: HealthRoutesDeps): Hono<{ Variables: ServerVariables }> {\n const app = new Hono<{ Variables: ServerVariables }>();\n const now = deps.now ?? Date.now;\n app.get('/', (c) =>\n c.json({\n status: 'ok' as const,\n version: deps.version,\n uptimeSeconds: Math.max(0, Math.floor((now() - deps.startedAt) / 1000)),\n }),\n );\n return app;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA8BA,SAAgB,mBAAmB,MAA8D;CAC/F,MAAM,MAAM,IAAI,MAAsC;CACtD,MAAM,MAAM,KAAK,OAAO,KAAK;AAC7B,KAAI,IAAI,MAAM,MACZ,EAAE,KAAK;EACL,QAAQ;EACR,SAAS,KAAK;EACd,eAAe,KAAK,IAAI,GAAG,KAAK,OAAO,KAAK,GAAG,KAAK,aAAa,IAAK,CAAC;EACxE,CAAC,CACH;AACD,QAAO"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { AgentRoutesDeps, createAgentRoutes, createRunRoutes } from "./agents.js";
|
|
2
|
+
import { AuditApi, AuditRoutesDeps, createAuditRoutes } from "./audit.js";
|
|
3
|
+
import { AuthRoutesDeps, createAuthRoutes } from "./auth.js";
|
|
4
|
+
import { HealthRoutesDeps, createHealthRoutes } from "./health.js";
|
|
5
|
+
import { McpApi, McpRoutesDeps, createMcpRoutes } from "./mcp.js";
|
|
6
|
+
import { MemoryApi, MemoryRoutesDeps, createMemoryRoutes } from "./memory.js";
|
|
7
|
+
import { SessionApi, SessionRoutesDeps, createSessionRoutes } from "./sessions.js";
|
|
8
|
+
import { SkillsApi, SkillsRoutesDeps, createSkillsRoutes } from "./skills.js";
|
|
9
|
+
import { TokensRoutesDeps, createTokensRoutes } from "./tokens.js";
|
|
10
|
+
import { WorkflowRoutesDeps, createWorkflowRoutes } from "./workflows.js";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createAgentRoutes, createRunRoutes } from "./agents.js";
|
|
2
|
+
import { createAuditRoutes } from "./audit.js";
|
|
3
|
+
import { createAuthRoutes } from "./auth.js";
|
|
4
|
+
import { createHealthRoutes } from "./health.js";
|
|
5
|
+
import { createMcpRoutes } from "./mcp.js";
|
|
6
|
+
import { createMemoryRoutes } from "./memory.js";
|
|
7
|
+
import { createSessionRoutes } from "./sessions.js";
|
|
8
|
+
import { createSkillsRoutes } from "./skills.js";
|
|
9
|
+
import { createTokensRoutes } from "./tokens.js";
|
|
10
|
+
import { createWorkflowRoutes } from "./workflows.js";
|
|
11
|
+
|
|
12
|
+
export { };
|