@intentface/latch-server 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,238 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import { createLatchHandler } from "./handler.js";
3
+ /** Mirror of oauth.ts's payload half: `<csrf>.<b64url(JSON)>`. */
4
+ function encodeState(payload) {
5
+ const s = JSON.stringify(payload);
6
+ let bin = "";
7
+ for (const ch of new TextEncoder().encode(s))
8
+ bin += String.fromCharCode(ch);
9
+ const b64 = btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
10
+ return `deadbeef.${b64}`;
11
+ }
12
+ function build(opts) {
13
+ const start = vi.fn(async (_p, _o) => ({
14
+ status: "redirect",
15
+ authorizationUrl: "https://idp.example/authorize",
16
+ }));
17
+ const finish = vi.fn(async (_p, _params) => ({
18
+ status: "authorized",
19
+ }));
20
+ const oauth = { isAuthorized: async () => false, start, finish, provider: () => ({}) };
21
+ const conns = {
22
+ oauth: (name) => (name === "loredex" ? oauth : undefined),
23
+ status: async () => [],
24
+ testTools: async () => [],
25
+ };
26
+ const resolveContext = vi.fn(opts?.resolveContext ?? (() => ({ orgId: "org_a", userId: "u1" })));
27
+ const handler = createLatchHandler({
28
+ runtime: {},
29
+ connections: conns,
30
+ basePath: "/api/latch",
31
+ resolveContext,
32
+ reconstructPrincipal: opts?.reconstructPrincipal,
33
+ oauthFlowMaxAgeMs: opts?.oauthFlowMaxAgeMs,
34
+ });
35
+ return { handler, start, finish, resolveContext };
36
+ }
37
+ const authorize = (q = "") => new Request(`https://app.example/api/latch/connections/loredex/authorize${q}`);
38
+ const callback = (state, code = "abc") => new Request(`https://app.example/api/latch/connections/loredex/callback?code=${code}&state=${encodeURIComponent(state)}`);
39
+ describe("authorize", () => {
40
+ it("pins the resolved principal (+ iat) into the flow state", async () => {
41
+ const { handler, start } = build();
42
+ const res = await handler(authorize("?returnTo=/chat/c1"));
43
+ expect(res?.status).toBe(302);
44
+ const state = start.mock.calls[0][1]?.state;
45
+ expect(state.returnTo).toBe("/chat/c1");
46
+ const pin = state.__latch;
47
+ expect(pin.p).toEqual({ orgId: "org_a", userId: "u1" });
48
+ expect(typeof pin.iat).toBe("number");
49
+ expect(Math.abs(Date.now() - pin.iat)).toBeLessThan(5_000);
50
+ });
51
+ });
52
+ describe("callback with a pinned principal", () => {
53
+ const pinnedState = (iat = Date.now()) => encodeState({
54
+ returnTo: "/chat/c1",
55
+ __latch: { p: { orgId: "org_pinned", userId: "u_pinned" }, iat },
56
+ });
57
+ it("rebuilds identity from the pin — the session is never consulted", async () => {
58
+ const { handler, finish, resolveContext } = build({
59
+ // A live session would resolve to a DIFFERENT principal (the incident
60
+ // shape: personal tenant). It must not be asked at all.
61
+ resolveContext: () => {
62
+ throw new Error("resolveContext must not run on a pinned callback");
63
+ },
64
+ });
65
+ const res = await handler(callback(pinnedState()));
66
+ expect(res?.status).toBe(302);
67
+ expect(res?.headers.get("location")).toContain("connected=loredex");
68
+ expect(resolveContext).not.toHaveBeenCalled();
69
+ // finish — and therefore the token write — ran under the PINNED principal.
70
+ expect(finish.mock.calls[0][0]).toEqual({ orgId: "org_pinned", userId: "u_pinned" });
71
+ });
72
+ it("routes the pin through reconstructPrincipal and 401s on null", async () => {
73
+ const seen = [];
74
+ const { handler, finish } = build({
75
+ reconstructPrincipal: (identity) => {
76
+ seen.push(identity);
77
+ return null; // e.g. the user is no longer an org member
78
+ },
79
+ });
80
+ const res = await handler(callback(pinnedState()));
81
+ expect(res?.status).toBe(401);
82
+ expect(seen).toEqual([{ orgId: "org_pinned", userId: "u_pinned" }]);
83
+ expect(finish).not.toHaveBeenCalled();
84
+ });
85
+ it("rejects a stale pin with ?error=flow_expired", async () => {
86
+ const { handler, finish } = build({ oauthFlowMaxAgeMs: 60_000 });
87
+ const res = await handler(callback(pinnedState(Date.now() - 120_000)));
88
+ expect(res?.status).toBe(302);
89
+ const loc = res?.headers.get("location") ?? "";
90
+ expect(loc).toContain("error=flow_expired");
91
+ expect(loc.startsWith("/chat/c1")).toBe(true);
92
+ expect(finish).not.toHaveBeenCalled();
93
+ });
94
+ });
95
+ describe("callback without a pin", () => {
96
+ it.each([
97
+ ["no state param", "https://app.example/api/latch/connections/loredex/callback?code=abc"],
98
+ ["malformed state", "https://app.example/api/latch/connections/loredex/callback?code=abc&state=not-a-state"],
99
+ ])("%s is rejected as expired and never reaches the session", async (_label, href) => {
100
+ const { handler, finish, resolveContext } = build({
101
+ resolveContext: () => {
102
+ throw new Error("resolveContext must not run on any callback");
103
+ },
104
+ });
105
+ const res = await handler(new Request(href));
106
+ expect(res?.status).toBe(302);
107
+ const loc = res?.headers.get("location") ?? "";
108
+ expect(loc.startsWith("/")).toBe(true);
109
+ expect(loc).toContain("error=flow_expired");
110
+ expect(resolveContext).not.toHaveBeenCalled();
111
+ expect(finish).not.toHaveBeenCalled();
112
+ });
113
+ it("is rejected as expired — the session is never a stand-in for a flow", async () => {
114
+ const { handler, finish, resolveContext } = build({
115
+ resolveContext: () => {
116
+ throw new Error("resolveContext must not run on any callback");
117
+ },
118
+ });
119
+ const res = await handler(callback(encodeState({ returnTo: "/x" })));
120
+ expect(res?.status).toBe(302);
121
+ const loc = res?.headers.get("location") ?? "";
122
+ expect(loc.startsWith("/x")).toBe(true);
123
+ expect(loc).toContain("error=flow_expired");
124
+ expect(resolveContext).not.toHaveBeenCalled();
125
+ expect(finish).not.toHaveBeenCalled();
126
+ });
127
+ });
128
+ function buildRoutes(opts) {
129
+ const schedule = vi.fn(async (input) => ({
130
+ id: "sch_1",
131
+ ...input,
132
+ }));
133
+ const runScheduleNow = vi.fn(async (_i) => opts?.runNow ?? true);
134
+ const unschedule = vi.fn(async (_i) => { });
135
+ const listSchedules = vi.fn(async () => opts?.schedules ?? []);
136
+ const listRuns = vi.fn(async () => [
137
+ {
138
+ id: "run_1",
139
+ chatId: "chat_1",
140
+ agent: "briefer",
141
+ status: "awaiting_input",
142
+ identity: { orgId: "org_a", userId: "u1", plan: "enterprise" },
143
+ costTotal: 0.19,
144
+ startedAt: 1,
145
+ },
146
+ ]);
147
+ const handler = createLatchHandler({
148
+ runtime: { schedule, runScheduleNow, unschedule, listSchedules, listRuns },
149
+ connections: { oauth: () => undefined, status: async () => [], testTools: async () => [] },
150
+ basePath: "/api/latch",
151
+ resolveContext: () => ({ orgId: "org_a", userId: "u1" }),
152
+ });
153
+ return { handler, schedule, runScheduleNow, unschedule, listSchedules };
154
+ }
155
+ const post = (path, body) => new Request(`https://app.example/api/latch/${path}`, {
156
+ method: "POST",
157
+ ...(body === undefined ? {} : { body: JSON.stringify(body) }),
158
+ });
159
+ const get = (path) => new Request(`https://app.example/api/latch/${path}`);
160
+ describe("POST /schedules", () => {
161
+ it("forwards delivery and onParked to runtime.schedule", async () => {
162
+ const { handler, schedule } = buildRoutes();
163
+ const res = await handler(post("schedules", {
164
+ agent: "briefer",
165
+ cron: "0 8 * * *",
166
+ timezone: "Europe/Helsinki",
167
+ prompt: "morning brief",
168
+ delivery: { kind: "slack", appId: "A1", channel: "C1" },
169
+ onParked: "fire",
170
+ }));
171
+ expect(res?.status).toBe(200);
172
+ const passed = schedule.mock.calls[0][0];
173
+ expect(passed.delivery).toEqual({ kind: "slack", appId: "A1", channel: "C1" });
174
+ expect(passed.onParked).toBe("fire");
175
+ expect(passed.timezone).toBe("Europe/Helsinki");
176
+ // Echoed back, so a caller can see what actually stuck.
177
+ expect((await res.json()).schedule.delivery).toEqual({
178
+ kind: "slack",
179
+ appId: "A1",
180
+ channel: "C1",
181
+ });
182
+ });
183
+ it("omits both when unset — no invented defaults", async () => {
184
+ const { handler, schedule } = buildRoutes();
185
+ await handler(post("schedules", { agent: "briefer", cron: null, prompt: "brief" }));
186
+ const passed = schedule.mock.calls[0][0];
187
+ expect(passed.delivery).toBeUndefined();
188
+ expect(passed.onParked).toBeUndefined();
189
+ });
190
+ it("400s an unknown onParked instead of silently defaulting it", async () => {
191
+ const { handler, schedule } = buildRoutes();
192
+ const res = await handler(post("schedules", { agent: "briefer", cron: null, prompt: "b", onParked: "skipp" }));
193
+ expect(res?.status).toBe(400);
194
+ expect(schedule).not.toHaveBeenCalled();
195
+ });
196
+ });
197
+ describe("GET /schedules/:id and POST /schedules/:id/run-now", () => {
198
+ it("returns one of the caller's schedules", async () => {
199
+ const { handler } = buildRoutes({
200
+ schedules: [{ id: "sch_1", agent: "briefer", prompt: "a" }],
201
+ });
202
+ const res = await handler(get("schedules/sch_1"));
203
+ expect(res?.status).toBe(200);
204
+ expect((await res.json()).schedule.id).toBe("sch_1");
205
+ });
206
+ it("404s an id that isn't in the caller's own list (another tenant's included)", async () => {
207
+ const { handler } = buildRoutes({ schedules: [] });
208
+ expect((await handler(get("schedules/sch_other")))?.status).toBe(404);
209
+ });
210
+ it("arms run-now through the runtime, and 404s a spent one-shot", async () => {
211
+ const { handler, runScheduleNow } = buildRoutes();
212
+ expect((await handler(post("schedules/sch_1/run-now")))?.status).toBe(200);
213
+ expect(runScheduleNow.mock.calls[0][0]).toEqual({
214
+ principal: { orgId: "org_a", userId: "u1" },
215
+ id: "sch_1",
216
+ });
217
+ const spent = buildRoutes({ runNow: false });
218
+ expect((await spent.handler(post("schedules/sch_1/run-now")))?.status).toBe(404);
219
+ });
220
+ it("still deletes on DELETE /schedules/:id", async () => {
221
+ const { handler, unschedule } = buildRoutes();
222
+ const res = await handler(new Request("https://app.example/api/latch/schedules/sch_1", { method: "DELETE" }));
223
+ expect(res?.status).toBe(200);
224
+ expect(unschedule).toHaveBeenCalled();
225
+ });
226
+ });
227
+ describe("GET /runs", () => {
228
+ it("strips the identity blob — the principal is server state, not a payload", async () => {
229
+ const { handler } = buildRoutes();
230
+ const res = await handler(get("runs"));
231
+ const { runs } = (await res.json());
232
+ expect(runs).toHaveLength(1);
233
+ expect("identity" in runs[0]).toBe(false);
234
+ // Everything a cost/usage UI actually needs still comes through.
235
+ expect(runs[0]).toMatchObject({ id: "run_1", chatId: "chat_1", costTotal: 0.19 });
236
+ });
237
+ });
238
+ //# sourceMappingURL=handler.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handler.test.js","sourceRoot":"","sources":["../src/handler.test.ts"],"names":[],"mappings":"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"}
package/dist/hono.d.ts ADDED
@@ -0,0 +1,20 @@
1
+ import { Hono } from "hono";
2
+ import type { LatchHandlerConfig } from "./handler.js";
3
+ import type { LatchApp, LatchAppConfig } from "./app.js";
4
+ /**
5
+ * A mountable Hono app over the framework-agnostic `createLatchHandler`. Mount
6
+ * it under any prefix with your own middleware, or run it standalone. Identical
7
+ * routes/behavior to the bare handler — this is just the Hono adapter, kept in a
8
+ * subpath export (`@intentface/latch-server/hono`) so `hono` stays an optional
9
+ * peer dependency for hosts that don't use it (TanStack Start, Next, etc.).
10
+ */
11
+ export type LatchServerConfig<P> = LatchHandlerConfig<P>;
12
+ export declare function createLatchHono<P>(config: LatchServerConfig<P>): Hono;
13
+ /**
14
+ * `createLatchApp` + a mounted Hono app in one call: the whole backend, ready
15
+ * to `serve({ fetch: app.fetch })` or `app.route("/api/latch", …)` into yours.
16
+ */
17
+ export declare function createLatchHonoApp<P, RC = unknown>(config: LatchAppConfig<P, RC>): Promise<LatchApp<P> & {
18
+ app: Hono;
19
+ }>;
20
+ //# sourceMappingURL=hono.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hono.d.ts","sourceRoot":"","sources":["../src/hono.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAEvD,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAEzD;;;;;;GAMG;AACH,MAAM,MAAM,iBAAiB,CAAC,CAAC,IAAI,kBAAkB,CAAC,CAAC,CAAC,CAAC;AAEzD,wBAAgB,eAAe,CAAC,CAAC,EAAE,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAAG,IAAI,CAGrE;AAQD;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,CAAC,EAAE,EAAE,GAAG,OAAO,EACtD,MAAM,EAAE,cAAc,CAAC,CAAC,EAAE,EAAE,CAAC,GAC5B,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG;IAAE,GAAG,EAAE,IAAI,CAAA;CAAE,CAAC,CAGtC"}
package/dist/hono.js ADDED
@@ -0,0 +1,21 @@
1
+ import { Hono } from "hono";
2
+ import { createLatchHandler } from "./handler.js";
3
+ import { createLatchApp } from "./app.js";
4
+ export function createLatchHono(config) {
5
+ const handler = createLatchHandler(config);
6
+ return honoOver(handler);
7
+ }
8
+ function honoOver(handler) {
9
+ const app = new Hono();
10
+ app.all("*", async (c) => (await handler(c.req.raw)) ?? c.notFound());
11
+ return app;
12
+ }
13
+ /**
14
+ * `createLatchApp` + a mounted Hono app in one call: the whole backend, ready
15
+ * to `serve({ fetch: app.fetch })` or `app.route("/api/latch", …)` into yours.
16
+ */
17
+ export async function createLatchHonoApp(config) {
18
+ const latch = await createLatchApp(config);
19
+ return { ...latch, app: honoOver(latch.handler) };
20
+ }
21
+ //# sourceMappingURL=hono.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hono.js","sourceRoot":"","sources":["../src/hono.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAElD,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAY1C,MAAM,UAAU,eAAe,CAAI,MAA4B;IAC7D,MAAM,OAAO,GAAG,kBAAkB,CAAC,MAAM,CAAC,CAAC;IAC3C,OAAO,QAAQ,CAAC,OAAO,CAAC,CAAC;AAC3B,CAAC;AAED,SAAS,QAAQ,CAAC,OAAwD;IACxE,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;IACvB,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC;IACtE,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,MAA6B;IAE7B,MAAM,KAAK,GAAG,MAAM,cAAc,CAAQ,MAAM,CAAC,CAAC;IAClD,OAAO,EAAE,GAAG,KAAK,EAAE,GAAG,EAAE,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;AACpD,CAAC"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * @intentface/latch-server
3
+ *
4
+ * The mountable Latch HTTP surface — one framework-agnostic Web handler that
5
+ * exposes the whole runtime (chat, approvals, chat/run lists, history) and the
6
+ * generic MCP connection OAuth. Mount `createLatchHandler({ runtime, connections,
7
+ * resolveContext })` at a single route in any Web-standard host (TanStack Start,
8
+ * Next, Bun, Deno, serverless). For Hono, see `@intentface/latch-server/hono`.
9
+ *
10
+ * The routes are a convenience over the real contract — the Runtime operations —
11
+ * which you can still call directly from your own routes / non-HTTP triggers.
12
+ *
13
+ * `createLatchApp({ storage, agents, resolveContext })` is the composition root
14
+ * with every default wired (durability, cron, model catalog, harness tools) —
15
+ * the shortest path from nothing to a running agent backend.
16
+ */
17
+ export { createLatchHandler } from "./handler.js";
18
+ export type { LatchHandlerConfig, LatchHandler } from "./handler.js";
19
+ export { createCronerEvaluator } from "./cron.js";
20
+ export { createLatchApp } from "./app.js";
21
+ export type { LatchAppConfig, LatchApp } from "./app.js";
22
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAClD,YAAY,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACrE,OAAO,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC1C,YAAY,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,20 @@
1
+ /**
2
+ * @intentface/latch-server
3
+ *
4
+ * The mountable Latch HTTP surface — one framework-agnostic Web handler that
5
+ * exposes the whole runtime (chat, approvals, chat/run lists, history) and the
6
+ * generic MCP connection OAuth. Mount `createLatchHandler({ runtime, connections,
7
+ * resolveContext })` at a single route in any Web-standard host (TanStack Start,
8
+ * Next, Bun, Deno, serverless). For Hono, see `@intentface/latch-server/hono`.
9
+ *
10
+ * The routes are a convenience over the real contract — the Runtime operations —
11
+ * which you can still call directly from your own routes / non-HTTP triggers.
12
+ *
13
+ * `createLatchApp({ storage, agents, resolveContext })` is the composition root
14
+ * with every default wired (durability, cron, model catalog, harness tools) —
15
+ * the shortest path from nothing to a running agent backend.
16
+ */
17
+ export { createLatchHandler } from "./handler.js";
18
+ export { createCronerEvaluator } from "./cron.js";
19
+ export { createLatchApp } from "./app.js";
20
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAElD,OAAO,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC"}
@@ -0,0 +1,18 @@
1
+ import { Hono } from "hono";
2
+ import type { Principal, Runtime } from "@intentface/latch-core";
3
+ /**
4
+ * A mountable Hono app exposing the runtime over HTTP.
5
+ *
6
+ * Default route: `POST /agents/:agent/:id` — prompt the agent in conversation
7
+ * `:id` (a client-chosen key, tenant-namespaced here), returning a streaming
8
+ * UIMessage Response. Mount it under any prefix + your own middleware (embed),
9
+ * or run it standalone. Auth is BYO via `resolveContext` — the core never
10
+ * authenticates; everything below is scoped by the returned `Principal`.
11
+ */
12
+ export interface LatchServerConfig {
13
+ runtime: Runtime;
14
+ /** Map a request to a Principal (your existing auth). */
15
+ resolveContext: (request: Request) => Principal | Promise<Principal>;
16
+ }
17
+ export declare function createLatchHono(config: LatchServerConfig): Hono;
18
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,KAAK,EAGV,SAAS,EACT,OAAO,EACR,MAAM,wBAAwB,CAAC;AAEhC;;;;;;;;GAQG;AACH,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,OAAO,CAAC;IACjB,yDAAyD;IACzD,cAAc,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;CACtE;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI,CAwC/D"}
package/dist/server.js ADDED
@@ -0,0 +1,61 @@
1
+ import { Hono } from "hono";
2
+ export function createLatchHono(config) {
3
+ const app = new Hono();
4
+ app.post("/agents/:agent/:id", async (c) => {
5
+ const principal = await config.resolveContext(c.req.raw);
6
+ const agent = c.req.param("agent");
7
+ const rawId = c.req.param("id");
8
+ // Tenant-namespaced chat key: org_a's "ticket-1" ≠ org_b's "ticket-1".
9
+ const chatId = `${principal.orgId}:${rawId}`;
10
+ const body = (await c.req
11
+ .json()
12
+ .catch(() => ({})));
13
+ const message = toUserMessage(body);
14
+ return config.runtime.handleChat({
15
+ agent,
16
+ chatId,
17
+ principal,
18
+ message,
19
+ request: c.req.raw,
20
+ });
21
+ });
22
+ // HITL: submit tool-approval decisions and continue the turn (streams the
23
+ // continuation, same as a chat turn). Body: `{ decisions: [{approvalId, approved, reason?}] }`.
24
+ app.post("/agents/:agent/:id/approvals", async (c) => {
25
+ const principal = await config.resolveContext(c.req.raw);
26
+ const chatId = `${principal.orgId}:${c.req.param("id")}`;
27
+ const body = (await c.req.json().catch(() => ({})));
28
+ const decisions = Array.isArray(body.decisions) ? body.decisions : [];
29
+ return config.runtime.applyApproval({
30
+ chatId,
31
+ principal,
32
+ decisions,
33
+ request: c.req.raw,
34
+ });
35
+ });
36
+ return app;
37
+ }
38
+ /** Accept `{ message }`, `{ text }`, or a `useChat`-style `{ messages }` (last). */
39
+ function toUserMessage(body) {
40
+ const explicit = body.message;
41
+ if (explicit?.parts)
42
+ return explicit;
43
+ let text = "";
44
+ if (typeof body.text === "string") {
45
+ text = body.text;
46
+ }
47
+ else if (Array.isArray(body.messages)) {
48
+ const last = body.messages[body.messages.length - 1];
49
+ text = (last?.parts ?? [])
50
+ .filter((p) => p.type === "text")
51
+ .map((p) => p.text)
52
+ .join("");
53
+ }
54
+ return {
55
+ id: crypto.randomUUID(),
56
+ role: "user",
57
+ parts: [{ type: "text", text }],
58
+ metadata: { visibility: "user", createdAt: Date.now() },
59
+ };
60
+ }
61
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "@intentface/latch-server",
3
+ "version": "0.9.1",
4
+ "description": "The mountable Latch HTTP surface — one framework-agnostic Web handler (chat, approvals, lists, MCP connection OAuth). Hono adapter in the /hono subpath.",
5
+ "keywords": [
6
+ "ai",
7
+ "ai-sdk",
8
+ "agents",
9
+ "llm",
10
+ "http",
11
+ "hono",
12
+ "webhooks"
13
+ ],
14
+ "license": "MIT",
15
+ "type": "module",
16
+ "main": "./dist/index.js",
17
+ "module": "./dist/index.js",
18
+ "types": "./dist/index.d.ts",
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/index.d.ts",
22
+ "import": "./dist/index.js"
23
+ },
24
+ "./hono": {
25
+ "types": "./dist/hono.d.ts",
26
+ "import": "./dist/hono.js"
27
+ }
28
+ },
29
+ "files": [
30
+ "dist"
31
+ ],
32
+ "sideEffects": false,
33
+ "publishConfig": {
34
+ "access": "public",
35
+ "registry": "https://registry.npmjs.org"
36
+ },
37
+ "dependencies": {
38
+ "croner": "^10.0.1",
39
+ "@intentface/latch-core": "^0.9.1",
40
+ "@intentface/latch-mcp": "^0.9.1"
41
+ },
42
+ "peerDependencies": {
43
+ "ai": ">=7.0.0-beta.178",
44
+ "hono": ">=4"
45
+ },
46
+ "peerDependenciesMeta": {
47
+ "hono": {
48
+ "optional": true
49
+ }
50
+ },
51
+ "devDependencies": {
52
+ "ai": "7.0.0-beta.178",
53
+ "hono": "^4.6.0",
54
+ "typescript": "^5.7.0",
55
+ "vitest": "^4"
56
+ },
57
+ "repository": {
58
+ "type": "git",
59
+ "url": "git+https://github.com/Intentface/intentface-latch.git",
60
+ "directory": "packages/server"
61
+ },
62
+ "scripts": {
63
+ "build": "tsc -p tsconfig.json",
64
+ "typecheck": "tsc -p tsconfig.json --noEmit",
65
+ "test": "vitest run"
66
+ }
67
+ }