@parlor/chat 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +23 -2
- package/dist/events.d.ts +4 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +12 -0
- package/dist/events.js.map +1 -0
- package/dist/id-schemas.d.ts +5 -0
- package/dist/id-schemas.d.ts.map +1 -0
- package/dist/id-schemas.js +11 -0
- package/dist/id-schemas.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/items.d.ts +295 -0
- package/dist/items.d.ts.map +1 -0
- package/dist/items.js +113 -0
- package/dist/items.js.map +1 -0
- package/dist/limits.d.ts +4 -0
- package/dist/limits.d.ts.map +1 -0
- package/dist/limits.js +4 -0
- package/dist/limits.js.map +1 -0
- package/dist/model.d.ts +143 -0
- package/dist/model.d.ts.map +1 -0
- package/dist/model.js +2 -0
- package/dist/model.js.map +1 -0
- package/dist/open-turns.d.ts +19 -0
- package/dist/open-turns.d.ts.map +1 -0
- package/dist/open-turns.js +31 -0
- package/dist/open-turns.js.map +1 -0
- package/dist/protocol.d.ts +67 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +78 -0
- package/dist/protocol.js.map +1 -0
- package/dist/server/actions.d.ts +561 -0
- package/dist/server/actions.d.ts.map +1 -0
- package/dist/server/actions.js +422 -0
- package/dist/server/actions.js.map +1 -0
- package/dist/server/context.d.ts +24 -0
- package/dist/server/context.d.ts.map +1 -0
- package/dist/server/context.js +77 -0
- package/dist/server/context.js.map +1 -0
- package/dist/server/index.d.ts +35 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +129 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/live.d.ts +24 -0
- package/dist/server/live.d.ts.map +1 -0
- package/dist/server/live.js +130 -0
- package/dist/server/live.js.map +1 -0
- package/dist/server/migrations.d.ts +3 -0
- package/dist/server/migrations.d.ts.map +1 -0
- package/dist/server/migrations.js +96 -0
- package/dist/server/migrations.js.map +1 -0
- package/dist/server/party.d.ts +24 -0
- package/dist/server/party.d.ts.map +1 -0
- package/dist/server/party.js +38 -0
- package/dist/server/party.js.map +1 -0
- package/dist/server/routes.d.ts +17 -0
- package/dist/server/routes.d.ts.map +1 -0
- package/dist/server/routes.js +105 -0
- package/dist/server/routes.js.map +1 -0
- package/dist/server/rows.d.ts +49 -0
- package/dist/server/rows.d.ts.map +1 -0
- package/dist/server/rows.js +136 -0
- package/dist/server/rows.js.map +1 -0
- package/dist/server/store.d.ts +130 -0
- package/dist/server/store.d.ts.map +1 -0
- package/dist/server/store.js +539 -0
- package/dist/server/store.js.map +1 -0
- package/dist/tags.d.ts +7 -0
- package/dist/tags.d.ts.map +1 -0
- package/dist/tags.js +7 -0
- package/dist/tags.js.map +1 -0
- package/dist/testing/index.d.ts +46 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +96 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/view.d.ts +87 -0
- package/dist/view.d.ts.map +1 -0
- package/dist/view.js +236 -0
- package/dist/view.js.map +1 -0
- package/package.json +47 -3
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { userAudience } from "@parlor/agent-ui/server";
|
|
2
|
+
import { REVOCATION_HORIZON_MS } from "@parlor/server";
|
|
3
|
+
import { CHAT_TAG, chatActions, HOSTS_TAG } from "./actions.js";
|
|
4
|
+
import { inProcessRelay } from "./live.js";
|
|
5
|
+
import { CHAT_MIGRATIONS } from "./migrations.js";
|
|
6
|
+
import { chatRoutes } from "./routes.js";
|
|
7
|
+
import { sqlChatStore } from "./store.js";
|
|
8
|
+
export { CHAT_ACTIVITY_TAG, CHAT_TAG, chatActions, HOST_GRANTS_TAG, hostChatActions, HOSTS_TAG, userChatActions, } from "./actions.js";
|
|
9
|
+
export { TRANSCRIPT_BUDGET_BYTES, turnContext, } from "./context.js";
|
|
10
|
+
export { chatItemBodies, hostItemDraft } from "../items.js";
|
|
11
|
+
export { idSchema } from "../id-schemas.js";
|
|
12
|
+
export { inProcessRelay, MAX_OPEN_BYTES_PER_TURN, MAX_OPEN_TURNS, } from "./live.js";
|
|
13
|
+
export { CHAT_MIGRATIONS } from "./migrations.js";
|
|
14
|
+
export { chatAction, } from "./party.js";
|
|
15
|
+
export { chatRoutes, MAX_STREAM_BODY_BYTES } from "./routes.js";
|
|
16
|
+
export { sqlChatStore, } from "./store.js";
|
|
17
|
+
export const CHAT_PLUGIN = "chat";
|
|
18
|
+
/** Chat then serves nothing, so the client reads `useChat().status` as `unavailable`. */
|
|
19
|
+
export const UNAVAILABLE_WITHOUT_AUTH = "chat() is unavailable: no auth provider is configured, so every action runs without a signed-in user.";
|
|
20
|
+
export const AUTH_UNDEFINED = "auth is undefined. Check the environment variables your auth setup reads, or remove the auth option to run without sign-in.";
|
|
21
|
+
/** Outlives a 30 s network drop plus a 5 s reconnect. */
|
|
22
|
+
export const DEFAULT_LEASE_MS = 60_000;
|
|
23
|
+
export const DEFAULT_HEARTBEAT_MS = 30_000;
|
|
24
|
+
export const DEFAULT_MAX_QUEUED = 4;
|
|
25
|
+
/** A lease past the revocation memory would outlive the revocation of its grant. */
|
|
26
|
+
export const MAX_LEASE_MS = REVOCATION_HORIZON_MS;
|
|
27
|
+
function positiveInteger(name, value) {
|
|
28
|
+
if (!Number.isInteger(value) || value < 1) {
|
|
29
|
+
throw new Error(`chat(): ${name} must be a positive whole number, got ${value}.`);
|
|
30
|
+
}
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The inline agent's server half: the `parlor_chat_*` log, the user and host actions, the
|
|
35
|
+
* `chat` channel, and the host-only stream and heartbeat routes.
|
|
36
|
+
*/
|
|
37
|
+
export function chat(options = {}) {
|
|
38
|
+
return {
|
|
39
|
+
name: CHAT_PLUGIN,
|
|
40
|
+
migrations: CHAT_MIGRATIONS,
|
|
41
|
+
setup(host) {
|
|
42
|
+
const leaseMs = positiveInteger("leaseMs", options.leaseMs ?? DEFAULT_LEASE_MS);
|
|
43
|
+
const maxQueued = positiveInteger("maxQueued", options.maxQueued ?? DEFAULT_MAX_QUEUED);
|
|
44
|
+
if (leaseMs > MAX_LEASE_MS) {
|
|
45
|
+
throw new Error(`chat(): leaseMs ${leaseMs} is above ${MAX_LEASE_MS}, the longest a revoked computer's turn token can be kept out.`);
|
|
46
|
+
}
|
|
47
|
+
const { auth, database } = host;
|
|
48
|
+
if (!database)
|
|
49
|
+
throw new Error("chat() needs a database.");
|
|
50
|
+
if (!auth?.delegate) {
|
|
51
|
+
if (host.authMisconfigured)
|
|
52
|
+
throw new Error(AUTH_UNDEFINED);
|
|
53
|
+
console.warn(UNAVAILABLE_WITHOUT_AUTH);
|
|
54
|
+
return {};
|
|
55
|
+
}
|
|
56
|
+
const relay = (options.relay ?? inProcessRelay)();
|
|
57
|
+
const db = () => database.read();
|
|
58
|
+
const ownStream = (owner) => userAudience(owner.scope, owner.userId);
|
|
59
|
+
const transact = (owner, work) => database.transaction(async (tx) => {
|
|
60
|
+
const { result, threadId } = await work(tx);
|
|
61
|
+
if (threadId !== undefined)
|
|
62
|
+
await host.announce(ownStream(owner), [[CHAT_TAG, threadId]], tx);
|
|
63
|
+
return result;
|
|
64
|
+
});
|
|
65
|
+
const heartbeatMs = DEFAULT_HEARTBEAT_MS;
|
|
66
|
+
const reconcile = async (owner) => {
|
|
67
|
+
if (!(await sqlChatStore.hasLapsed(db(), owner)))
|
|
68
|
+
return;
|
|
69
|
+
await database.transaction(async (tx) => {
|
|
70
|
+
for (const change of await sqlChatStore.expireLapsed(tx, owner)) {
|
|
71
|
+
const tags = [
|
|
72
|
+
...change.threads.map((threadId) => [CHAT_TAG, threadId]),
|
|
73
|
+
...change.hosts.map((hostId) => [HOSTS_TAG, hostId]),
|
|
74
|
+
];
|
|
75
|
+
await host.announce(ownStream(change), tags, tx);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
};
|
|
79
|
+
const pruneHosts = async (owner, hostIds) => {
|
|
80
|
+
try {
|
|
81
|
+
await database.transaction((tx) => sqlChatStore.pruneHosts(tx, owner, hostIds));
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
console.warn("[parlor] chat: pruning revoked computers failed:", error);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
const sweep = setInterval(() => {
|
|
88
|
+
(host.ready?.() ?? Promise.resolve())
|
|
89
|
+
.then(() => reconcile())
|
|
90
|
+
.catch((error) => {
|
|
91
|
+
console.warn("[parlor] chat: sweeping lapsed turns failed:", error);
|
|
92
|
+
});
|
|
93
|
+
}, heartbeatMs);
|
|
94
|
+
sweep.unref();
|
|
95
|
+
const actions = chatActions({
|
|
96
|
+
enforcing: auth.enforcing,
|
|
97
|
+
transact,
|
|
98
|
+
reconcile,
|
|
99
|
+
pruneHosts,
|
|
100
|
+
auth,
|
|
101
|
+
store: sqlChatStore,
|
|
102
|
+
relay,
|
|
103
|
+
leaseMs,
|
|
104
|
+
heartbeatMs,
|
|
105
|
+
maxQueued,
|
|
106
|
+
instructions: options.instructions,
|
|
107
|
+
appName: process.env.PARLOR_APP_NAME ?? "this app",
|
|
108
|
+
appOrigin: () => process.env.PARLOR_ORIGIN ?? process.env.PARLOR_WEB_ORIGIN ?? "",
|
|
109
|
+
});
|
|
110
|
+
return {
|
|
111
|
+
actions,
|
|
112
|
+
routes: chatRoutes({
|
|
113
|
+
db,
|
|
114
|
+
store: sqlChatStore,
|
|
115
|
+
relay,
|
|
116
|
+
heartbeatMs,
|
|
117
|
+
reconcile,
|
|
118
|
+
announceHost: (owner, hostId) => host.announce(ownStream(owner), [[HOSTS_TAG, hostId]]),
|
|
119
|
+
}),
|
|
120
|
+
channel: relay.channel,
|
|
121
|
+
close: () => {
|
|
122
|
+
clearInterval(sweep);
|
|
123
|
+
relay.close();
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type ThreadId, type TurnId } from "@parlor/core";
|
|
2
|
+
import type { ServerChannel } from "@parlor/sync";
|
|
3
|
+
import type { ChatOwner, OpenTurn } from "../model.js";
|
|
4
|
+
import { type HostStreamBatch } from "../protocol.js";
|
|
5
|
+
import type { TurnHolder } from "./store.js";
|
|
6
|
+
export declare const MAX_OPEN_BYTES_PER_TURN: number;
|
|
7
|
+
export declare const MAX_OPEN_TURNS = 1024;
|
|
8
|
+
/**
|
|
9
|
+
* The live half of the chat: what is streaming now, carried to the `chat` channel of the
|
|
10
|
+
* owner's own streams. Nothing here is the truth; drop it and every client still
|
|
11
|
+
* converges on the log, only without token streaming. So it writes nothing and can be
|
|
12
|
+
* replaced by a relay that crosses instances.
|
|
13
|
+
*/
|
|
14
|
+
export interface LiveRelay {
|
|
15
|
+
readonly channel: ServerChannel;
|
|
16
|
+
/** Applies a batch to the open turn and pushes it; a turn over its byte budget stops streaming. */
|
|
17
|
+
accept(turn: TurnHolder, batch: HostStreamBatch): void;
|
|
18
|
+
settle(owner: ChatOwner, threadId: ThreadId, turnId: TurnId): void;
|
|
19
|
+
open(owner: ChatOwner, threadId: ThreadId): readonly OpenTurn[];
|
|
20
|
+
close(): void;
|
|
21
|
+
}
|
|
22
|
+
/** The default: one instance, in memory. */
|
|
23
|
+
export declare function inProcessRelay(): LiveRelay;
|
|
24
|
+
//# sourceMappingURL=live.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"live.d.ts","sourceRoot":"","sources":["../../src/server/live.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,QAAQ,EACb,KAAK,MAAM,EAGZ,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAqB,aAAa,EAAE,MAAM,cAAc,CAAC;AAErE,OAAO,KAAK,EAAE,SAAS,EAAY,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEjE,OAAO,EAAgC,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACpF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,eAAO,MAAM,uBAAuB,QAAa,CAAC;AAClD,eAAO,MAAM,cAAc,OAAO,CAAC;AAEnC;;;;;GAKG;AACH,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;IAChC,mGAAmG;IACnG,MAAM,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,eAAe,GAAG,IAAI,CAAC;IACvD,MAAM,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACnE,IAAI,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,GAAG,SAAS,QAAQ,EAAE,CAAC;IAChE,KAAK,IAAI,IAAI,CAAC;CACf;AAuBD,4CAA4C;AAC5C,wBAAgB,cAAc,IAAI,SAAS,CA8G1C"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { DEV_USER_ID, userOf, } from "@parlor/core";
|
|
2
|
+
import { applyFrame } from "../open-turns.js";
|
|
3
|
+
import { CHAT_CHANNEL } from "../protocol.js";
|
|
4
|
+
export const MAX_OPEN_BYTES_PER_TURN = 256 * 1024;
|
|
5
|
+
export const MAX_OPEN_TURNS = 1024;
|
|
6
|
+
const SETTLED_MEMORY = 4 * MAX_OPEN_TURNS;
|
|
7
|
+
const encoder = new TextEncoder();
|
|
8
|
+
function ownerKey(scope, userId) {
|
|
9
|
+
return `${scope}\u001f${userId ?? DEV_USER_ID}`;
|
|
10
|
+
}
|
|
11
|
+
function sizeOf(frame) {
|
|
12
|
+
return frame.type === "delta"
|
|
13
|
+
? encoder.encode(frame.text).length
|
|
14
|
+
: encoder.encode(JSON.stringify(frame.item)).length;
|
|
15
|
+
}
|
|
16
|
+
/** The default: one instance, in memory. */
|
|
17
|
+
export function inProcessRelay() {
|
|
18
|
+
const connections = new Map();
|
|
19
|
+
const turns = new Map();
|
|
20
|
+
const settled = new Set();
|
|
21
|
+
let closed = false;
|
|
22
|
+
const push = (owner, frame) => {
|
|
23
|
+
const audience = connections.get(ownerKey(owner.scope, owner.userId));
|
|
24
|
+
if (!audience || audience.size === 0)
|
|
25
|
+
return;
|
|
26
|
+
const data = JSON.stringify(frame);
|
|
27
|
+
for (const connection of audience) {
|
|
28
|
+
try {
|
|
29
|
+
connection.send(data);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
audience.delete(connection);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
const channel = {
|
|
37
|
+
event: CHAT_CHANNEL,
|
|
38
|
+
connect(connection) {
|
|
39
|
+
if (closed || connection.closed?.aborted)
|
|
40
|
+
return () => { };
|
|
41
|
+
const key = ownerKey(connection.scope, userOf(connection.principal));
|
|
42
|
+
let audience = connections.get(key);
|
|
43
|
+
if (!audience) {
|
|
44
|
+
audience = new Set();
|
|
45
|
+
connections.set(key, audience);
|
|
46
|
+
}
|
|
47
|
+
audience.add(connection);
|
|
48
|
+
const detach = () => {
|
|
49
|
+
const current = connections.get(key);
|
|
50
|
+
current?.delete(connection);
|
|
51
|
+
if (current?.size === 0)
|
|
52
|
+
connections.delete(key);
|
|
53
|
+
connection.closed?.removeEventListener("abort", detach);
|
|
54
|
+
};
|
|
55
|
+
connection.closed?.addEventListener("abort", detach, { once: true });
|
|
56
|
+
return detach;
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
const forget = (turnId) => {
|
|
60
|
+
turns.delete(turnId);
|
|
61
|
+
settled.add(turnId);
|
|
62
|
+
if (settled.size > SETTLED_MEMORY) {
|
|
63
|
+
const oldest = settled.values().next();
|
|
64
|
+
if (!oldest.done)
|
|
65
|
+
settled.delete(oldest.value);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
return {
|
|
69
|
+
channel,
|
|
70
|
+
accept(holder, batch) {
|
|
71
|
+
if (closed || settled.has(batch.turnId))
|
|
72
|
+
return;
|
|
73
|
+
let state = turns.get(batch.turnId);
|
|
74
|
+
if (!state) {
|
|
75
|
+
if (turns.size >= MAX_OPEN_TURNS) {
|
|
76
|
+
const oldest = turns.keys().next();
|
|
77
|
+
if (!oldest.done)
|
|
78
|
+
turns.delete(oldest.value);
|
|
79
|
+
}
|
|
80
|
+
state = {
|
|
81
|
+
owner: holder.owner,
|
|
82
|
+
threadId: holder.threadId,
|
|
83
|
+
items: [],
|
|
84
|
+
bytes: 0,
|
|
85
|
+
overBudget: false,
|
|
86
|
+
};
|
|
87
|
+
turns.set(batch.turnId, state);
|
|
88
|
+
}
|
|
89
|
+
for (const hostFrame of batch.frames) {
|
|
90
|
+
const frame = {
|
|
91
|
+
...hostFrame,
|
|
92
|
+
threadId: state.threadId,
|
|
93
|
+
turnId: batch.turnId,
|
|
94
|
+
};
|
|
95
|
+
const cost = sizeOf(frame);
|
|
96
|
+
if (state.overBudget || state.bytes + cost > MAX_OPEN_BYTES_PER_TURN) {
|
|
97
|
+
state.overBudget = true;
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
const applied = applyFrame(state.items, frame);
|
|
101
|
+
if (!applied)
|
|
102
|
+
continue;
|
|
103
|
+
state.items = applied.items;
|
|
104
|
+
state.bytes += cost;
|
|
105
|
+
push(state.owner, applied.frame);
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
settle(owner, threadId, turnId) {
|
|
109
|
+
forget(turnId);
|
|
110
|
+
push(owner, { type: "settled", threadId, turnId });
|
|
111
|
+
},
|
|
112
|
+
open(owner, threadId) {
|
|
113
|
+
const open = [];
|
|
114
|
+
for (const [turnId, state] of turns) {
|
|
115
|
+
if (state.threadId === threadId &&
|
|
116
|
+
state.owner.scope === owner.scope &&
|
|
117
|
+
state.owner.userId === owner.userId) {
|
|
118
|
+
open.push({ turnId, threadId, items: state.items });
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return open;
|
|
122
|
+
},
|
|
123
|
+
close() {
|
|
124
|
+
closed = true;
|
|
125
|
+
connections.clear();
|
|
126
|
+
turns.clear();
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=live.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"live.js","sourceRoot":"","sources":["../../src/server/live.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../../src/server/migrations.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD,eAAO,MAAM,eAAe,EAAE,SAAS,SAAS,EA8F/C,CAAC"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
export const CHAT_MIGRATIONS = [
|
|
2
|
+
{
|
|
3
|
+
id: "chat/0001_threads",
|
|
4
|
+
sql: `
|
|
5
|
+
create table parlor_chat_threads (
|
|
6
|
+
id text primary key,
|
|
7
|
+
scope text not null,
|
|
8
|
+
owner_id text not null,
|
|
9
|
+
title text not null default '',
|
|
10
|
+
host_id text,
|
|
11
|
+
agent_id text,
|
|
12
|
+
last_seq bigint not null default 0,
|
|
13
|
+
created_at timestamptz not null default now(),
|
|
14
|
+
updated_at timestamptz not null default now()
|
|
15
|
+
);
|
|
16
|
+
create index parlor_chat_threads_owner
|
|
17
|
+
on parlor_chat_threads (owner_id, scope, updated_at desc);
|
|
18
|
+
|
|
19
|
+
create table parlor_chat_items (
|
|
20
|
+
id text primary key,
|
|
21
|
+
thread_id text not null references parlor_chat_threads (id) on delete cascade,
|
|
22
|
+
turn_id text not null,
|
|
23
|
+
seq bigint not null,
|
|
24
|
+
author text not null check (author in ('user', 'agent', 'system')),
|
|
25
|
+
kind text not null,
|
|
26
|
+
body jsonb not null,
|
|
27
|
+
created_at timestamptz not null default now(),
|
|
28
|
+
unique (thread_id, seq)
|
|
29
|
+
);
|
|
30
|
+
create unique index parlor_chat_items_one_answer
|
|
31
|
+
on parlor_chat_items (thread_id, (body->>'askId')) where kind = 'answer';
|
|
32
|
+
|
|
33
|
+
create table parlor_chat_turns (
|
|
34
|
+
id text primary key,
|
|
35
|
+
thread_id text not null references parlor_chat_threads (id) on delete cascade,
|
|
36
|
+
scope text not null,
|
|
37
|
+
owner_id text not null,
|
|
38
|
+
host_id text not null,
|
|
39
|
+
agent_id text not null,
|
|
40
|
+
client_id text not null,
|
|
41
|
+
device_id text,
|
|
42
|
+
prompt_item_id text not null,
|
|
43
|
+
state text not null
|
|
44
|
+
check (state in ('queued', 'running', 'done', 'failed', 'cancelled')),
|
|
45
|
+
lease_until timestamptz,
|
|
46
|
+
outcome jsonb,
|
|
47
|
+
created_at timestamptz not null default now(),
|
|
48
|
+
finished_at timestamptz
|
|
49
|
+
);
|
|
50
|
+
create index parlor_chat_turns_queue
|
|
51
|
+
on parlor_chat_turns (host_id, state, created_at) where state = 'queued';
|
|
52
|
+
create unique index parlor_chat_turns_one_running
|
|
53
|
+
on parlor_chat_turns (thread_id) where state = 'running';
|
|
54
|
+
create index parlor_chat_turns_thread_state on parlor_chat_turns (thread_id, state);
|
|
55
|
+
create index parlor_chat_turns_lease
|
|
56
|
+
on parlor_chat_turns (lease_until) where state = 'running';
|
|
57
|
+
|
|
58
|
+
create index parlor_chat_items_signals
|
|
59
|
+
on parlor_chat_items (turn_id, seq) where kind in ('answer', 'cancel');
|
|
60
|
+
|
|
61
|
+
create table parlor_chat_hosts (
|
|
62
|
+
id text primary key,
|
|
63
|
+
scope text not null,
|
|
64
|
+
owner_id text not null,
|
|
65
|
+
grant_id text not null,
|
|
66
|
+
protocol integer not null,
|
|
67
|
+
name text not null,
|
|
68
|
+
platform text not null,
|
|
69
|
+
agents jsonb not null,
|
|
70
|
+
online_until timestamptz not null,
|
|
71
|
+
announced_online boolean not null
|
|
72
|
+
);
|
|
73
|
+
create index parlor_chat_hosts_owner on parlor_chat_hosts (owner_id, scope);
|
|
74
|
+
create index parlor_chat_hosts_lapse
|
|
75
|
+
on parlor_chat_hosts (online_until) where announced_online;`,
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
id: "chat/0002_answer_retry",
|
|
79
|
+
sql: `
|
|
80
|
+
drop index parlor_chat_items_one_answer;
|
|
81
|
+
create index parlor_chat_items_answers
|
|
82
|
+
on parlor_chat_items (thread_id, (body->>'askId')) where kind = 'answer';
|
|
83
|
+
create index parlor_chat_items_rejections
|
|
84
|
+
on parlor_chat_items (thread_id, (body->>'itemId'))
|
|
85
|
+
where kind = 'event' and body->>'event' = 'answer_rejected';`,
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
id: "chat/0003_host_devices",
|
|
89
|
+
sql: `alter table parlor_chat_hosts add column devices jsonb;`,
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
id: "chat/0004_host_presence",
|
|
93
|
+
sql: `alter table parlor_chat_hosts add column presence_due jsonb;`,
|
|
94
|
+
},
|
|
95
|
+
];
|
|
96
|
+
//# sourceMappingURL=migrations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrations.js","sourceRoot":"","sources":["../../src/server/migrations.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,eAAe,GAAyB;IACnD;QACE,EAAE,EAAE,mBAAmB;QACvB,GAAG,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oEAuE2D;KACjE;IACD;QACE,EAAE,EAAE,wBAAwB;QAC5B,GAAG,EAAE;;;;;;qEAM4D;KAClE;IACD;QACE,EAAE,EAAE,wBAAwB;QAC5B,GAAG,EAAE,yDAAyD;KAC/D;IACD;QACE,EAAE,EAAE,yBAAyB;QAC7B,GAAG,EAAE,8DAA8D;KACpE;CACF,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type ActionConfig, type ActionContext, type ActionDefinition, type DeviceId, type HostPrincipal, type Principal } from "@parlor/core";
|
|
2
|
+
import type { z } from "zod";
|
|
3
|
+
import type { ChatOwner } from "../model.js";
|
|
4
|
+
export type Party = "user" | "host";
|
|
5
|
+
export interface ChatUser extends ChatOwner {
|
|
6
|
+
readonly deviceId: DeviceId | undefined;
|
|
7
|
+
}
|
|
8
|
+
/** A host and the owner whose rows it may touch: its own user. */
|
|
9
|
+
export interface ChatHost extends ChatOwner {
|
|
10
|
+
readonly host: HostPrincipal;
|
|
11
|
+
}
|
|
12
|
+
export type PartyOf<P extends Party> = P extends "user" ? ChatUser : ChatHost;
|
|
13
|
+
export interface ChatActionConfig<P extends Party, I extends z.ZodType, O> extends Omit<ActionConfig<I, O>, "run" | "confirm" | "public"> {
|
|
14
|
+
run(input: z.output<I>, party: PartyOf<P>, ctx: ActionContext): Promise<O> | O;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* An action only `party` may call, checked once before `run`: a user action takes a
|
|
18
|
+
* signed-in user, or without an enforcing provider an anonymous caller as `DEV_USER_ID`;
|
|
19
|
+
* a host action takes only a host. Everything else is `forbidden`, so no body checks the
|
|
20
|
+
* principal itself.
|
|
21
|
+
*/
|
|
22
|
+
export declare function chatAction<P extends Party, I extends z.ZodType, O>(party: P, enforcing: boolean, config: ChatActionConfig<P, I, O>): ActionDefinition<I, O>;
|
|
23
|
+
export declare function partyOf<P extends Party>(party: P, principal: Principal, scope: string, enforcing: boolean): PartyOf<P>;
|
|
24
|
+
//# sourceMappingURL=party.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"party.d.ts","sourceRoot":"","sources":["../../src/server/party.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,gBAAgB,EAErB,KAAK,QAAQ,EAEb,KAAK,aAAa,EAClB,KAAK,SAAS,EACf,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAE7B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,MAAM,MAAM,KAAK,GAAG,MAAM,GAAG,MAAM,CAAC;AAEpC,MAAM,WAAW,QAAS,SAAQ,SAAS;IACzC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,GAAG,SAAS,CAAC;CACzC;AAED,kEAAkE;AAClE,MAAM,WAAW,QAAS,SAAQ,SAAS;IACzC,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;CAC9B;AAED,MAAM,MAAM,OAAO,CAAC,CAAC,SAAS,KAAK,IAAI,CAAC,SAAS,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAE9E,MAAM,WAAW,gBAAgB,CAAC,CAAC,SAAS,KAAK,EAAE,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,CAAC,CAAE,SAAQ,IAAI,CACrF,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,EAClB,KAAK,GAAG,SAAS,GAAG,QAAQ,CAC7B;IACC,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;CAChF;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,CAAC,SAAS,KAAK,EAAE,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,CAAC,EAChE,KAAK,EAAE,CAAC,EACR,SAAS,EAAE,OAAO,EAClB,MAAM,EAAE,gBAAgB,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAChC,gBAAgB,CAAC,CAAC,EAAE,CAAC,CAAC,CAQxB;AAgCD,wBAAgB,OAAO,CAAC,CAAC,SAAS,KAAK,EACrC,KAAK,EAAE,CAAC,EACR,SAAS,EAAE,SAAS,EACpB,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,OAAO,GACjB,OAAO,CAAC,CAAC,CAAC,CAEZ"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { ActionError, defineAction, DEV_USER_ID, } from "@parlor/core";
|
|
2
|
+
/**
|
|
3
|
+
* An action only `party` may call, checked once before `run`: a user action takes a
|
|
4
|
+
* signed-in user, or without an enforcing provider an anonymous caller as `DEV_USER_ID`;
|
|
5
|
+
* a host action takes only a host. Everything else is `forbidden`, so no body checks the
|
|
6
|
+
* principal itself.
|
|
7
|
+
*/
|
|
8
|
+
export function chatAction(party, enforcing, config) {
|
|
9
|
+
const { run, ...rest } = config;
|
|
10
|
+
return defineAction({
|
|
11
|
+
notifies: "user",
|
|
12
|
+
...rest,
|
|
13
|
+
run: (input, ctx) => run(input, partyOf(party, ctx.principal, ctx.scope, enforcing), ctx),
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
const resolvers = {
|
|
17
|
+
host: (principal, scope) => {
|
|
18
|
+
if (principal.kind !== "host") {
|
|
19
|
+
throw new ActionError("forbidden", "Only a connected computer can call this.");
|
|
20
|
+
}
|
|
21
|
+
return { scope, userId: principal.userId, host: principal };
|
|
22
|
+
},
|
|
23
|
+
user: (principal, scope, enforcing) => {
|
|
24
|
+
if (principal.kind === "user") {
|
|
25
|
+
return { scope, userId: principal.userId, deviceId: principal.deviceId };
|
|
26
|
+
}
|
|
27
|
+
if (principal.kind === "anonymous") {
|
|
28
|
+
if (enforcing)
|
|
29
|
+
throw new ActionError("unauthenticated", "Sign in first.");
|
|
30
|
+
return { scope, userId: DEV_USER_ID, deviceId: undefined };
|
|
31
|
+
}
|
|
32
|
+
throw new ActionError("forbidden", `A ${principal.kind} principal cannot use the chat.`);
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
export function partyOf(party, principal, scope, enforcing) {
|
|
36
|
+
return resolvers[party](principal, scope, enforcing);
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=party.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"party.js","sourceRoot":"","sources":["../../src/server/party.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EAIX,YAAY,EAEZ,WAAW,GAGZ,MAAM,cAAc,CAAC;AAyBtB;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CACxB,KAAQ,EACR,SAAkB,EAClB,MAAiC;IAEjC,MAAM,EAAE,GAAG,EAAE,GAAG,IAAI,EAAE,GAAG,MAAM,CAAC;IAChC,OAAO,YAAY,CAAO;QACxB,QAAQ,EAAE,MAAM;QAChB,GAAG,IAAI;QACP,GAAG,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE,CAClB,GAAG,CAAC,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,SAAS,EAAE,GAAG,CAAC,KAAK,EAAE,SAAS,CAAC,EAAE,GAAG,CAAC;KACvE,CAAC,CAAC;AACL,CAAC;AAUD,MAAM,SAAS,GAAmB;IAChC,IAAI,EAAE,CAAC,SAAS,EAAE,KAAK,EAAE,EAAE;QACzB,IAAI,SAAS,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YAC9B,MAAM,IAAI,WAAW,CAAC,WAAW,EAAE,0CAA0C,CAAC,CAAC;QACjF,CAAC;QACD,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;IAC9D,CAAC;IACD,IAAI,EAAE,CAAC,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE,EAAE;QACpC,IAAI,SAAS,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YAC9B,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,CAAC,MAAM,EAAE,QAAQ,EAAE,SAAS,CAAC,QAAQ,EAAE,CAAC;QAC3E,CAAC;QACD,IAAI,SAAS,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;YACnC,IAAI,SAAS;gBAAE,MAAM,IAAI,WAAW,CAAC,iBAAiB,EAAE,gBAAgB,CAAC,CAAC;YAC1E,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC;QAC7D,CAAC;QACD,MAAM,IAAI,WAAW,CACnB,WAAW,EACX,KAAK,SAAS,CAAC,IAAI,iCAAiC,CACrD,CAAC;IACJ,CAAC;CACF,CAAC;AAEF,MAAM,UAAU,OAAO,CACrB,KAAQ,EACR,SAAoB,EACpB,KAAa,EACb,SAAkB;IAElB,OAAO,SAAS,CAAC,KAAK,CAAC,CAAC,SAAS,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC;AACvD,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type HostId } from "@parlor/core";
|
|
2
|
+
import { type AnyPluginRoute } from "@parlor/server";
|
|
3
|
+
import type { ChatOwner } from "../model.js";
|
|
4
|
+
import type { LiveRelay } from "./live.js";
|
|
5
|
+
import type { ChatDb, ChatStore } from "./store.js";
|
|
6
|
+
export declare const MAX_STREAM_BODY_BYTES: number;
|
|
7
|
+
export interface ChatRouteDeps {
|
|
8
|
+
readonly db: () => ChatDb;
|
|
9
|
+
readonly store: ChatStore;
|
|
10
|
+
readonly relay: LiveRelay;
|
|
11
|
+
readonly heartbeatMs: number;
|
|
12
|
+
readonly reconcile: (owner: ChatOwner) => Promise<void>;
|
|
13
|
+
readonly announceHost: (owner: ChatOwner, hostId: HostId) => Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
/** Plain routes only a host principal reaches, by type: no browser session, no agent token. */
|
|
16
|
+
export declare function chatRoutes(deps: ChatRouteDeps): readonly AnyPluginRoute[];
|
|
17
|
+
//# sourceMappingURL=routes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/server/routes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,MAAM,EAAE,MAAM,cAAc,CAAC;AACxD,OAAO,EAAE,KAAK,cAAc,EAAgC,MAAM,gBAAgB,CAAC;AAInF,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAEpD,eAAO,MAAM,qBAAqB,QAAY,CAAC;AAG/C,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,EAAE,MAAM,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAC1B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACxD,QAAQ,CAAC,YAAY,EAAE,CAAC,KAAK,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5E;AAmDD,+FAA+F;AAC/F,wBAAgB,UAAU,CAAC,IAAI,EAAE,aAAa,GAAG,SAAS,cAAc,EAAE,CAmDzE"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { ActionError } from "@parlor/core";
|
|
2
|
+
import { pluginRoute, readBoundedText } from "@parlor/server";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { idSchema } from "../id-schemas.js";
|
|
5
|
+
export const MAX_STREAM_BODY_BYTES = 64 * 1024;
|
|
6
|
+
const MAX_FRAMES_PER_BATCH = 512;
|
|
7
|
+
const streamBatch = z.object({
|
|
8
|
+
turnId: idSchema("turn"),
|
|
9
|
+
frames: z
|
|
10
|
+
.array(z.discriminatedUnion("type", [
|
|
11
|
+
z.object({
|
|
12
|
+
type: z.literal("delta"),
|
|
13
|
+
itemId: idSchema("item"),
|
|
14
|
+
kind: z.enum(["reply", "reasoning"]),
|
|
15
|
+
offset: z.number().int().min(0),
|
|
16
|
+
text: z.string(),
|
|
17
|
+
}),
|
|
18
|
+
z.object({
|
|
19
|
+
type: z.literal("item"),
|
|
20
|
+
item: z.discriminatedUnion("kind", [
|
|
21
|
+
z.object({
|
|
22
|
+
id: idSchema("item"),
|
|
23
|
+
kind: z.literal("tool"),
|
|
24
|
+
title: z.string(),
|
|
25
|
+
status: z.enum(["pending", "running"]),
|
|
26
|
+
}),
|
|
27
|
+
z.object({
|
|
28
|
+
id: idSchema("item"),
|
|
29
|
+
kind: z.literal("plan"),
|
|
30
|
+
entries: z.array(z.object({
|
|
31
|
+
content: z.string(),
|
|
32
|
+
status: z.enum(["pending", "in_progress", "completed"]),
|
|
33
|
+
})),
|
|
34
|
+
}),
|
|
35
|
+
]),
|
|
36
|
+
}),
|
|
37
|
+
]))
|
|
38
|
+
.max(MAX_FRAMES_PER_BATCH),
|
|
39
|
+
});
|
|
40
|
+
const tooLarge = () => Response.json({
|
|
41
|
+
error: {
|
|
42
|
+
code: "invalid_input",
|
|
43
|
+
message: `A stream batch is at most ${MAX_STREAM_BODY_BYTES} bytes.`,
|
|
44
|
+
},
|
|
45
|
+
}, { status: 413 });
|
|
46
|
+
/** Plain routes only a host principal reaches, by type: no browser session, no agent token. */
|
|
47
|
+
export function chatRoutes(deps) {
|
|
48
|
+
return [
|
|
49
|
+
pluginRoute({
|
|
50
|
+
method: "POST",
|
|
51
|
+
path: "/stream",
|
|
52
|
+
access: ["host"],
|
|
53
|
+
handle: async (request, caller) => {
|
|
54
|
+
const text = await readBoundedText(request, MAX_STREAM_BODY_BYTES);
|
|
55
|
+
if (text === undefined)
|
|
56
|
+
return tooLarge();
|
|
57
|
+
const parsed = streamBatch.safeParse(parseJson(text));
|
|
58
|
+
if (!parsed.success) {
|
|
59
|
+
throw new ActionError("invalid_input", "Not a stream batch", {
|
|
60
|
+
issues: parsed.error.issues,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
const batch = parsed.data;
|
|
64
|
+
const holder = await deps.store.turnHolder(deps.db(), batch.turnId);
|
|
65
|
+
const { principal } = caller;
|
|
66
|
+
const owns = holder !== undefined &&
|
|
67
|
+
holder.hostId === principal.hostId &&
|
|
68
|
+
holder.owner.userId === principal.userId &&
|
|
69
|
+
holder.owner.scope === caller.scope;
|
|
70
|
+
if (!owns)
|
|
71
|
+
throw new ActionError("forbidden", "This turn is not yours.");
|
|
72
|
+
const { live, ...heldBy } = holder;
|
|
73
|
+
if (!live)
|
|
74
|
+
throw new ActionError("forbidden", "The turn is not running.");
|
|
75
|
+
deps.relay.accept(heldBy, batch);
|
|
76
|
+
return new Response(null, { status: 204 });
|
|
77
|
+
},
|
|
78
|
+
}),
|
|
79
|
+
pluginRoute({
|
|
80
|
+
method: "POST",
|
|
81
|
+
path: "/heartbeat",
|
|
82
|
+
access: ["host"],
|
|
83
|
+
handle: async (_request, caller) => {
|
|
84
|
+
const { principal } = caller;
|
|
85
|
+
const owner = { scope: caller.scope, userId: principal.userId };
|
|
86
|
+
const { returned } = await deps.store.heartbeat(deps.db(), owner, principal, 2 * deps.heartbeatMs);
|
|
87
|
+
if (returned)
|
|
88
|
+
await deps.announceHost(owner, principal.hostId);
|
|
89
|
+
await deps.reconcile(owner).catch((error) => {
|
|
90
|
+
console.warn("[parlor] chat: reconciling lapsed turns failed:", error);
|
|
91
|
+
});
|
|
92
|
+
return new Response(null, { status: 204 });
|
|
93
|
+
},
|
|
94
|
+
}),
|
|
95
|
+
];
|
|
96
|
+
}
|
|
97
|
+
function parseJson(text) {
|
|
98
|
+
try {
|
|
99
|
+
return JSON.parse(text);
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
throw new ActionError("invalid_input", "Request body must be JSON");
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=routes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes.js","sourceRoot":"","sources":["../../src/server/routes.ts"],"names":[],"mappings":"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"}
|