@frankieai/server 0.2.6 → 0.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-EYR3BV7U.js → chunk-4XAOVSTL.js} +274 -27
- package/dist/chunk-4XAOVSTL.js.map +1 -0
- package/dist/{chunk-WEHJH4FL.js → chunk-TPVDQKD3.js} +508 -64
- package/dist/chunk-TPVDQKD3.js.map +1 -0
- package/dist/control-server.js +4 -4
- package/dist/control-server.js.map +1 -1
- package/dist/index.d.ts +306 -151
- package/dist/index.js +2 -2
- package/dist/server.js +8 -2
- package/dist/server.js.map +1 -1
- package/package.json +2 -2
- package/dist/chunk-EYR3BV7U.js.map +0 -1
- package/dist/chunk-WEHJH4FL.js.map +0 -1
package/dist/control-server.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
createControlApp
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-4XAOVSTL.js";
|
|
5
5
|
import {
|
|
6
6
|
createFrankieApp,
|
|
7
7
|
resolveStores
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-TPVDQKD3.js";
|
|
9
9
|
|
|
10
10
|
// src/control-server.ts
|
|
11
11
|
import { serve } from "@hono/node-server";
|
|
@@ -17,10 +17,10 @@ async function main() {
|
|
|
17
17
|
console.error("[frankie] Set FRANKIE_ADMIN_TOKEN to run the control plane.");
|
|
18
18
|
process.exit(1);
|
|
19
19
|
}
|
|
20
|
-
const { tenants: store, sessions, knowledge, describe } = await resolveStores();
|
|
20
|
+
const { tenants: store, sessions, knowledge, rateLimiter, turnSignals, describe } = await resolveStores();
|
|
21
21
|
const app = new Hono();
|
|
22
22
|
app.route("/", createControlApp({ store, sessions, knowledge }));
|
|
23
|
-
app.route("/", createFrankieApp({ tenants: store, sessions, knowledge }));
|
|
23
|
+
app.route("/", createFrankieApp({ tenants: store, sessions, knowledge, ...rateLimiter ? { rateLimiter } : {}, ...turnSignals ? { turnSignals } : {} }));
|
|
24
24
|
serve({ fetch: app.fetch, port, hostname: host }, () => {
|
|
25
25
|
console.log(
|
|
26
26
|
JSON.stringify({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/control-server.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * The control plane, as its own process: the admin API the dashboard and the\n * setup CLI talk to. It writes tenants into FRANKIE_TENANTS_DIR — the same\n * directory the data-plane server reads — so what you create here is served\n * there with no further wiring.\n *\n * FRANKIE_ADMIN_TOKEN=… FRANKIE_TENANTS_DIR=./.frankie/tenants \\\n * pnpm --filter @frankieai/server start:control\n */\nimport { serve } from \"@hono/node-server\";\nimport { Hono } from \"hono\";\nimport { createFrankieApp } from \"./app.js\";\nimport { createControlApp } from \"./control/app.js\";\nimport { resolveStores } from \"./control/resolve-store.js\";\n\nasync function main(): Promise<void> {\n const port = Number(process.env[\"PORT\"] ?? process.env[\"CONTROL_PORT\"] ?? 8081);\n const host = process.env[\"HOST\"] ?? \"0.0.0.0\";\n\n if (!process.env[\"FRANKIE_ADMIN_TOKEN\"]) {\n console.error(\"[frankie] Set FRANKIE_ADMIN_TOKEN to run the control plane.\");\n process.exit(1);\n }\n\n const { tenants: store, sessions, knowledge, describe } = await resolveStores();\n const app = new Hono();\n app.route(\"/\", createControlApp({ store, sessions, knowledge }));\n app.route(\"/\", createFrankieApp({ tenants: store, sessions, knowledge }));\n\n serve({ fetch: app.fetch, port, hostname: host }, () => {\n console.log(\n JSON.stringify({\n ts: new Date().toISOString(),\n level: \"info\",\n event: \"control_listening\",\n store: describe,\n url: `http://${host}:${port}`,\n }),\n );\n });\n}\n\nvoid main();\n"],"mappings":";;;;;;;;;;AAUA,SAAS,aAAa;AACtB,SAAS,YAAY;AAKrB,eAAe,OAAsB;AACnC,QAAM,OAAO,OAAO,QAAQ,IAAI,MAAM,KAAK,QAAQ,IAAI,cAAc,KAAK,IAAI;AAC9E,QAAM,OAAO,QAAQ,IAAI,MAAM,KAAK;AAEpC,MAAI,CAAC,QAAQ,IAAI,qBAAqB,GAAG;AACvC,YAAQ,MAAM,6DAA6D;AAC3E,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,EAAE,SAAS,OAAO,UAAU,WAAW,SAAS,IAAI,MAAM,cAAc;
|
|
1
|
+
{"version":3,"sources":["../src/control-server.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * The control plane, as its own process: the admin API the dashboard and the\n * setup CLI talk to. It writes tenants into FRANKIE_TENANTS_DIR — the same\n * directory the data-plane server reads — so what you create here is served\n * there with no further wiring.\n *\n * FRANKIE_ADMIN_TOKEN=… FRANKIE_TENANTS_DIR=./.frankie/tenants \\\n * pnpm --filter @frankieai/server start:control\n */\nimport { serve } from \"@hono/node-server\";\nimport { Hono } from \"hono\";\nimport { createFrankieApp } from \"./app.js\";\nimport { createControlApp } from \"./control/app.js\";\nimport { resolveStores } from \"./control/resolve-store.js\";\n\nasync function main(): Promise<void> {\n const port = Number(process.env[\"PORT\"] ?? process.env[\"CONTROL_PORT\"] ?? 8081);\n const host = process.env[\"HOST\"] ?? \"0.0.0.0\";\n\n if (!process.env[\"FRANKIE_ADMIN_TOKEN\"]) {\n console.error(\"[frankie] Set FRANKIE_ADMIN_TOKEN to run the control plane.\");\n process.exit(1);\n }\n\n const { tenants: store, sessions, knowledge, rateLimiter, turnSignals, describe } = await resolveStores();\n const app = new Hono();\n app.route(\"/\", createControlApp({ store, sessions, knowledge }));\n app.route(\"/\", createFrankieApp({ tenants: store, sessions, knowledge, ...(rateLimiter ? { rateLimiter } : {}), ...(turnSignals ? { turnSignals } : {}) }));\n\n serve({ fetch: app.fetch, port, hostname: host }, () => {\n console.log(\n JSON.stringify({\n ts: new Date().toISOString(),\n level: \"info\",\n event: \"control_listening\",\n store: describe,\n url: `http://${host}:${port}`,\n }),\n );\n });\n}\n\nvoid main();\n"],"mappings":";;;;;;;;;;AAUA,SAAS,aAAa;AACtB,SAAS,YAAY;AAKrB,eAAe,OAAsB;AACnC,QAAM,OAAO,OAAO,QAAQ,IAAI,MAAM,KAAK,QAAQ,IAAI,cAAc,KAAK,IAAI;AAC9E,QAAM,OAAO,QAAQ,IAAI,MAAM,KAAK;AAEpC,MAAI,CAAC,QAAQ,IAAI,qBAAqB,GAAG;AACvC,YAAQ,MAAM,6DAA6D;AAC3E,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,EAAE,SAAS,OAAO,UAAU,WAAW,aAAa,aAAa,SAAS,IAAI,MAAM,cAAc;AACxG,QAAM,MAAM,IAAI,KAAK;AACrB,MAAI,MAAM,KAAK,iBAAiB,EAAE,OAAO,UAAU,UAAU,CAAC,CAAC;AAC/D,MAAI,MAAM,KAAK,iBAAiB,EAAE,SAAS,OAAO,UAAU,WAAW,GAAI,cAAc,EAAE,YAAY,IAAI,CAAC,GAAI,GAAI,cAAc,EAAE,YAAY,IAAI,CAAC,EAAG,CAAC,CAAC;AAE1J,QAAM,EAAE,OAAO,IAAI,OAAO,MAAM,UAAU,KAAK,GAAG,MAAM;AACtD,YAAQ;AAAA,MACN,KAAK,UAAU;AAAA,QACb,KAAI,oBAAI,KAAK,GAAE,YAAY;AAAA,QAC3B,OAAO;AAAA,QACP,OAAO;AAAA,QACP,OAAO;AAAA,QACP,KAAK,UAAU,IAAI,IAAI,IAAI;AAAA,MAC7B,CAAC;AAAA,IACH;AAAA,EACF,CAAC;AACH;AAEA,KAAK,KAAK;","names":[]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Hono } from 'hono';
|
|
2
|
-
import { FrankieConfig, AuthConfig, WorkspaceSkill, HostToolRequest,
|
|
2
|
+
import { FrankieConfig, AuthConfig, HostToolResult, ActionPreview, WorkspaceSkill, HostToolRequest, ChatAttachment, FrankieServerEvent, McpServerConfig, ToolPolicy, ApiToolInventory, ApiSourceConfig } from '@frankieai/core';
|
|
3
3
|
export { matchPath } from '@frankieai/core';
|
|
4
4
|
import { AgentMessage, AgentTool } from '@earendil-works/pi-agent-core';
|
|
5
5
|
import { JWTPayload } from 'jose';
|
|
@@ -112,122 +112,6 @@ declare function safeEqual(a: string, b: string): boolean;
|
|
|
112
112
|
/** Stable pseudonymous id for an anonymous visitor. */
|
|
113
113
|
declare function anonymousUser(seed: string): VerifiedUser;
|
|
114
114
|
|
|
115
|
-
/** One recorded hand-off to the workspace's team. Stored on the conversation. */
|
|
116
|
-
interface EscalationRecord {
|
|
117
|
-
id: string;
|
|
118
|
-
at: number;
|
|
119
|
-
/** Why the agent escalated (its own words / the visitor's ask). */
|
|
120
|
-
reason: string;
|
|
121
|
-
/** A short recap of what the visitor needs and what was already tried. */
|
|
122
|
-
summary: string;
|
|
123
|
-
/** How to reach the visitor, when they provided it. */
|
|
124
|
-
email?: string;
|
|
125
|
-
name?: string;
|
|
126
|
-
}
|
|
127
|
-
/** What the tool hands back to the app: the record plus who/where it came from. */
|
|
128
|
-
interface EscalationEvent extends EscalationRecord {
|
|
129
|
-
tenantId: string;
|
|
130
|
-
conversationId: string;
|
|
131
|
-
userId: string;
|
|
132
|
-
}
|
|
133
|
-
type EscalationSink = (event: EscalationEvent) => Promise<void>;
|
|
134
|
-
|
|
135
|
-
interface ChatSession {
|
|
136
|
-
id: string;
|
|
137
|
-
tenantId: string;
|
|
138
|
-
user: VerifiedUser;
|
|
139
|
-
/** Raw host-signed token, forwarded only to tenant tools that explicitly opt in. */
|
|
140
|
-
userToken?: string;
|
|
141
|
-
origin?: string;
|
|
142
|
-
context: Record<string, string | number | boolean | null>;
|
|
143
|
-
/** Agent transcript used to continue the model conversation on the server. */
|
|
144
|
-
messages: AgentMessage[];
|
|
145
|
-
/** UI transcript kept separately so the exact rendered chat can sync across devices. */
|
|
146
|
-
clientMessages?: unknown[];
|
|
147
|
-
/** User-editable title, otherwise derived from the first user message. */
|
|
148
|
-
title?: string;
|
|
149
|
-
createdAt: number;
|
|
150
|
-
lastActiveAt: number;
|
|
151
|
-
messageCount: number;
|
|
152
|
-
costUsd: number;
|
|
153
|
-
/** True while a chat turn holds this session; see `busySince` for how long. */
|
|
154
|
-
busy: boolean;
|
|
155
|
-
/**
|
|
156
|
-
* When the running turn took the lock. A turn can only hold the session for
|
|
157
|
-
* as long as its timeout, so a lock older than that is one whose release
|
|
158
|
-
* never landed (the instance was frozen or restarted mid-turn) and is free
|
|
159
|
-
* for the taking — otherwise the session would answer 409 forever.
|
|
160
|
-
*/
|
|
161
|
-
busySince?: number;
|
|
162
|
-
/** Human hand-offs the agent made in this conversation (the escalation inbox). */
|
|
163
|
-
escalations?: EscalationRecord[];
|
|
164
|
-
}
|
|
165
|
-
interface SessionStore {
|
|
166
|
-
create(session: ChatSession): Promise<void>;
|
|
167
|
-
get(id: string): Promise<ChatSession | undefined>;
|
|
168
|
-
/** List one user's conversations for a tenant, newest first. */
|
|
169
|
-
listByUser(tenantId: string, userId: string): Promise<ChatSession[]>;
|
|
170
|
-
/** Workspace-owner history view. This is only exposed through the authenticated control plane. */
|
|
171
|
-
listByTenant(tenantId: string): Promise<ChatSession[]>;
|
|
172
|
-
/**
|
|
173
|
-
* Non-expired conversation count per tenant id, in one pass. Used by the
|
|
174
|
-
* workspace directory to show a workspace as "Live" once it has real chats —
|
|
175
|
-
* cheaper than a listByTenant call per workspace.
|
|
176
|
-
*/
|
|
177
|
-
countsByTenant(): Promise<Record<string, number>>;
|
|
178
|
-
update(id: string, patch: Partial<ChatSession>): Promise<void>;
|
|
179
|
-
delete(id: string): Promise<void>;
|
|
180
|
-
}
|
|
181
|
-
declare class MemorySessionStore implements SessionStore {
|
|
182
|
-
private readonly ttlMs;
|
|
183
|
-
private readonly sessions;
|
|
184
|
-
constructor(ttlMs?: number);
|
|
185
|
-
create(session: ChatSession): Promise<void>;
|
|
186
|
-
get(id: string): Promise<ChatSession | undefined>;
|
|
187
|
-
listByUser(tenantId: string, userId: string): Promise<ChatSession[]>;
|
|
188
|
-
listByTenant(tenantId: string): Promise<ChatSession[]>;
|
|
189
|
-
countsByTenant(): Promise<Record<string, number>>;
|
|
190
|
-
update(id: string, patch: Partial<ChatSession>): Promise<void>;
|
|
191
|
-
delete(id: string): Promise<void>;
|
|
192
|
-
private evictExpired;
|
|
193
|
-
}
|
|
194
|
-
declare function newSessionId(): string;
|
|
195
|
-
|
|
196
|
-
/** One indexed passage of a document, with its embedding vector. */
|
|
197
|
-
interface KnowledgeChunk {
|
|
198
|
-
id: string;
|
|
199
|
-
url: string;
|
|
200
|
-
title: string;
|
|
201
|
-
text: string;
|
|
202
|
-
embedding: number[];
|
|
203
|
-
}
|
|
204
|
-
/** A retrieval result: the passage and how close it was to the query. */
|
|
205
|
-
interface KnowledgeHit {
|
|
206
|
-
url: string;
|
|
207
|
-
title: string;
|
|
208
|
-
text: string;
|
|
209
|
-
score: number;
|
|
210
|
-
}
|
|
211
|
-
/** What the dashboard shows about a workspace's index. */
|
|
212
|
-
interface KnowledgeStats {
|
|
213
|
-
chunks: number;
|
|
214
|
-
pages: number;
|
|
215
|
-
sources: string[];
|
|
216
|
-
ingestedAt?: number;
|
|
217
|
-
}
|
|
218
|
-
interface KnowledgeStore {
|
|
219
|
-
/** Replace a workspace's whole index with a freshly ingested set (atomic swap). */
|
|
220
|
-
replace(tenantId: string, chunks: KnowledgeChunk[], meta: {
|
|
221
|
-
sources: string[];
|
|
222
|
-
pages: number;
|
|
223
|
-
ingestedAt: number;
|
|
224
|
-
}): Promise<void>;
|
|
225
|
-
/** Top-k passages by cosine similarity to the query vector. */
|
|
226
|
-
search(tenantId: string, queryVector: readonly number[], k: number): Promise<KnowledgeHit[]>;
|
|
227
|
-
stats(tenantId: string): Promise<KnowledgeStats>;
|
|
228
|
-
clear(tenantId: string): Promise<void>;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
115
|
declare class ModelResolutionError extends Error {
|
|
232
116
|
}
|
|
233
117
|
interface ResolvedModel {
|
|
@@ -319,34 +203,6 @@ declare class MemoryTenantStore implements TenantStore {
|
|
|
319
203
|
byPublishableKey(key: string): Promise<ResolvedConfig | null>;
|
|
320
204
|
}
|
|
321
205
|
|
|
322
|
-
interface CreateAppOptions {
|
|
323
|
-
/** A single tenant, resolved at startup — the self-hosted case. */
|
|
324
|
-
resolved?: ResolvedConfig;
|
|
325
|
-
/** Many tenants, each resolved by publishable key at request time — the SaaS case. */
|
|
326
|
-
tenants?: TenantStore | TenantRegistry;
|
|
327
|
-
sessions?: SessionStore;
|
|
328
|
-
/** Knowledge-base index for the docs-search tool; in-memory when omitted. */
|
|
329
|
-
knowledge?: KnowledgeStore;
|
|
330
|
-
rateLimiter?: RateLimiter;
|
|
331
|
-
logger?: (event: Record<string, unknown>) => void;
|
|
332
|
-
}
|
|
333
|
-
type AppEnv = {
|
|
334
|
-
Variables: {
|
|
335
|
-
tenant: TenantRuntime;
|
|
336
|
-
};
|
|
337
|
-
};
|
|
338
|
-
declare function createFrankieApp(options: CreateAppOptions): Hono<AppEnv>;
|
|
339
|
-
|
|
340
|
-
interface McpHostOptions {
|
|
341
|
-
tenants: TenantStore | TenantRegistry;
|
|
342
|
-
logger?: (event: Record<string, unknown>) => void;
|
|
343
|
-
/** Injected in tests so generated API tools hit a stub rather than the network. */
|
|
344
|
-
fetchImpl?: typeof fetch;
|
|
345
|
-
/** Shared with the chat API; each request counts against the tenant's message budget. */
|
|
346
|
-
rateLimiter?: RateLimiter;
|
|
347
|
-
}
|
|
348
|
-
declare function createMcpHostApp(options: McpHostOptions): Hono;
|
|
349
|
-
|
|
350
206
|
/**
|
|
351
207
|
* What the control plane stores per tenant. The model provider key is *not*
|
|
352
208
|
* here — in the hosted shape that key is Frankie's own, one shared key read
|
|
@@ -410,6 +266,13 @@ interface SuggestedThemeRecord {
|
|
|
410
266
|
repository: string;
|
|
411
267
|
inferredAt: string;
|
|
412
268
|
}
|
|
269
|
+
/** An uploaded file's text, kept so every re-index includes it. */
|
|
270
|
+
interface KnowledgeDocument {
|
|
271
|
+
id: string;
|
|
272
|
+
name: string;
|
|
273
|
+
text: string;
|
|
274
|
+
addedAt: string;
|
|
275
|
+
}
|
|
413
276
|
interface TenantRecord {
|
|
414
277
|
apiSpecSync?: Record<string, {
|
|
415
278
|
repository: string;
|
|
@@ -444,6 +307,12 @@ interface TenantRecord {
|
|
|
444
307
|
* open a separate scoped-install PR per agent.
|
|
445
308
|
*/
|
|
446
309
|
personaInstalls?: Record<string, RepositoryInstallRecord>;
|
|
310
|
+
/**
|
|
311
|
+
* Files the team uploaded for its agents to answer from (an FAQ, a guide),
|
|
312
|
+
* as extracted text. Indexed alongside the crawled pages; never sent to the
|
|
313
|
+
* dashboard in full.
|
|
314
|
+
*/
|
|
315
|
+
knowledgeDocuments?: KnowledgeDocument[];
|
|
447
316
|
/** Deployment provider connection, when the workspace has connected Vercel. */
|
|
448
317
|
vercel?: VercelLinkRecord;
|
|
449
318
|
createdAt: string;
|
|
@@ -513,6 +382,13 @@ interface PublicTenant {
|
|
|
513
382
|
repositoryInstall?: RepositoryInstallRecord;
|
|
514
383
|
/** A submitted or in-progress scoped-install PR per persona id (`config.personas`). */
|
|
515
384
|
personaInstalls?: Record<string, RepositoryInstallRecord>;
|
|
385
|
+
/** Uploaded files agents answer from: names and sizes only. */
|
|
386
|
+
knowledgeDocuments: Array<{
|
|
387
|
+
id: string;
|
|
388
|
+
name: string;
|
|
389
|
+
chars: number;
|
|
390
|
+
addedAt: string;
|
|
391
|
+
}>;
|
|
516
392
|
/** The connected Vercel installation and matched project — never its token. */
|
|
517
393
|
vercel?: PublicVercelLink;
|
|
518
394
|
/** The customer's brand theme for the widget, so the dashboard can edit it. */
|
|
@@ -592,6 +468,12 @@ interface ControlStore extends TenantStore {
|
|
|
592
468
|
list(ownerId?: string): Promise<TenantRecord[]>;
|
|
593
469
|
get(id: string): Promise<TenantRecord | null>;
|
|
594
470
|
put(record: TenantRecord): Promise<void>;
|
|
471
|
+
/**
|
|
472
|
+
* Store a brand-new tenant, never replacing one: false when the id (or its
|
|
473
|
+
* publishable key) is already taken. Creating a workspace must use this, so
|
|
474
|
+
* a colliding id can't overwrite someone else's workspace.
|
|
475
|
+
*/
|
|
476
|
+
insert(record: TenantRecord): Promise<boolean>;
|
|
595
477
|
/** Remove a tenant for good. A no-op if it does not exist. */
|
|
596
478
|
delete(id: string): Promise<void>;
|
|
597
479
|
/** All members of the org owned by `org`. */
|
|
@@ -651,6 +533,7 @@ declare class FileTenantStore implements ControlStore {
|
|
|
651
533
|
list(ownerId?: string): Promise<TenantRecord[]>;
|
|
652
534
|
get(id: string): Promise<TenantRecord | null>;
|
|
653
535
|
put(record: TenantRecord): Promise<void>;
|
|
536
|
+
insert(record: TenantRecord): Promise<boolean>;
|
|
654
537
|
compareAndSwap(previous: TenantRecord, next: TenantRecord): Promise<boolean>;
|
|
655
538
|
/**
|
|
656
539
|
* A record holds the workspace's signing and session secrets and source
|
|
@@ -718,11 +601,211 @@ declare class PostgresTenantStore implements ControlStore {
|
|
|
718
601
|
byPublishableKey(key: string): Promise<ResolvedConfig | null>;
|
|
719
602
|
list(ownerId?: string): Promise<TenantRecord[]>;
|
|
720
603
|
get(id: string): Promise<TenantRecord | null>;
|
|
604
|
+
insert(record: TenantRecord): Promise<boolean>;
|
|
721
605
|
put(record: TenantRecord): Promise<void>;
|
|
722
606
|
delete(id: string): Promise<void>;
|
|
723
607
|
compareAndSwap(previous: TenantRecord, next: TenantRecord): Promise<boolean>;
|
|
724
608
|
}
|
|
725
609
|
|
|
610
|
+
/**
|
|
611
|
+
* Something said about a running turn from a request that may have landed on a
|
|
612
|
+
* different server instance than the one running it: the user pressed Stop,
|
|
613
|
+
* or their browser finished a tool call the turn is waiting on.
|
|
614
|
+
*/
|
|
615
|
+
type TurnSignal = {
|
|
616
|
+
kind: "stop";
|
|
617
|
+
} | {
|
|
618
|
+
kind: "tool_result";
|
|
619
|
+
callId: string;
|
|
620
|
+
result: HostToolResult;
|
|
621
|
+
};
|
|
622
|
+
/**
|
|
623
|
+
* Carries turn signals between instances. A turn polls for its session's
|
|
624
|
+
* signals while it runs; a request that can't reach the turn locally sends one.
|
|
625
|
+
* Single-instance deployments don't need it: everything is local.
|
|
626
|
+
*/
|
|
627
|
+
interface TurnSignals {
|
|
628
|
+
send(sessionId: string, signal: TurnSignal): Promise<void>;
|
|
629
|
+
/** Signals for the session after `afterId`, oldest first. */
|
|
630
|
+
poll(sessionId: string, afterId: number): Promise<Array<{
|
|
631
|
+
id: number;
|
|
632
|
+
signal: TurnSignal;
|
|
633
|
+
}>>;
|
|
634
|
+
/** The newest signal id, so a turn starting now ignores everything before it. */
|
|
635
|
+
latestId(sessionId: string): Promise<number>;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
/** One recorded hand-off to the workspace's team. Stored on the conversation. */
|
|
639
|
+
interface EscalationRecord {
|
|
640
|
+
id: string;
|
|
641
|
+
at: number;
|
|
642
|
+
/** Why the agent escalated (its own words / the visitor's ask). */
|
|
643
|
+
reason: string;
|
|
644
|
+
/** A short recap of what the visitor needs and what was already tried. */
|
|
645
|
+
summary: string;
|
|
646
|
+
/** How to reach the visitor, when they provided it. */
|
|
647
|
+
email?: string;
|
|
648
|
+
name?: string;
|
|
649
|
+
}
|
|
650
|
+
/** What the tool hands back to the app: the record plus who/where it came from. */
|
|
651
|
+
interface EscalationEvent extends EscalationRecord {
|
|
652
|
+
tenantId: string;
|
|
653
|
+
conversationId: string;
|
|
654
|
+
userId: string;
|
|
655
|
+
}
|
|
656
|
+
type EscalationSink = (event: EscalationEvent) => Promise<void>;
|
|
657
|
+
|
|
658
|
+
interface ChatSession {
|
|
659
|
+
id: string;
|
|
660
|
+
tenantId: string;
|
|
661
|
+
user: VerifiedUser;
|
|
662
|
+
/** Raw host-signed token, forwarded only to tenant tools that explicitly opt in. */
|
|
663
|
+
userToken?: string;
|
|
664
|
+
origin?: string;
|
|
665
|
+
/**
|
|
666
|
+
* The publishable key the chat was opened with: the workspace's own key or
|
|
667
|
+
* one of its agents' keys. Lets the dashboard say which agent answered.
|
|
668
|
+
*/
|
|
669
|
+
agentKey?: string;
|
|
670
|
+
context: Record<string, string | number | boolean | null>;
|
|
671
|
+
/** Agent transcript used to continue the model conversation on the server. */
|
|
672
|
+
messages: AgentMessage[];
|
|
673
|
+
/** UI transcript kept separately so the exact rendered chat can sync across devices. */
|
|
674
|
+
clientMessages?: unknown[];
|
|
675
|
+
/** User-editable title, otherwise derived from the first user message. */
|
|
676
|
+
title?: string;
|
|
677
|
+
createdAt: number;
|
|
678
|
+
lastActiveAt: number;
|
|
679
|
+
messageCount: number;
|
|
680
|
+
costUsd: number;
|
|
681
|
+
/** True while a chat turn holds this session; see `busySince` for how long. */
|
|
682
|
+
busy: boolean;
|
|
683
|
+
/**
|
|
684
|
+
* When the running turn took the lock. A turn can only hold the session for
|
|
685
|
+
* as long as its timeout, so a lock older than that is one whose release
|
|
686
|
+
* never landed (the instance was frozen or restarted mid-turn) and is free
|
|
687
|
+
* for the taking — otherwise the session would answer 409 forever.
|
|
688
|
+
*/
|
|
689
|
+
busySince?: number;
|
|
690
|
+
/** When someone pressed Stop for a turn running on another instance; that turn then doesn't save its reply. */
|
|
691
|
+
stopRequestedAt?: number;
|
|
692
|
+
/** Human hand-offs the agent made in this conversation (the escalation inbox). */
|
|
693
|
+
escalations?: EscalationRecord[];
|
|
694
|
+
/** An action that changes data, paused until the user approves or cancels it. */
|
|
695
|
+
pendingAction?: PendingAction;
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* A write the agent asked for and the user hasn't answered yet. On Approve the
|
|
699
|
+
* server runs exactly this call, with these inputs, not whatever the model
|
|
700
|
+
* would ask for next time.
|
|
701
|
+
*/
|
|
702
|
+
interface PendingAction {
|
|
703
|
+
toolUseId: string;
|
|
704
|
+
name: string;
|
|
705
|
+
label: string;
|
|
706
|
+
args: unknown;
|
|
707
|
+
preview: ActionPreview;
|
|
708
|
+
createdAt: number;
|
|
709
|
+
}
|
|
710
|
+
interface SessionStore {
|
|
711
|
+
create(session: ChatSession): Promise<void>;
|
|
712
|
+
get(id: string): Promise<ChatSession | undefined>;
|
|
713
|
+
/** List one user's conversations for a tenant, newest first. */
|
|
714
|
+
listByUser(tenantId: string, userId: string): Promise<ChatSession[]>;
|
|
715
|
+
/** Workspace-owner history view. This is only exposed through the authenticated control plane. */
|
|
716
|
+
listByTenant(tenantId: string): Promise<ChatSession[]>;
|
|
717
|
+
/**
|
|
718
|
+
* Non-expired conversation count per tenant id, in one pass. Used by the
|
|
719
|
+
* workspace directory to show a workspace as "Live" once it has real chats —
|
|
720
|
+
* cheaper than a listByTenant call per workspace.
|
|
721
|
+
*/
|
|
722
|
+
countsByTenant(): Promise<Record<string, number>>;
|
|
723
|
+
update(id: string, patch: Partial<ChatSession>): Promise<void>;
|
|
724
|
+
delete(id: string): Promise<void>;
|
|
725
|
+
}
|
|
726
|
+
declare class MemorySessionStore implements SessionStore {
|
|
727
|
+
private readonly ttlMs;
|
|
728
|
+
private readonly sessions;
|
|
729
|
+
constructor(ttlMs?: number);
|
|
730
|
+
create(session: ChatSession): Promise<void>;
|
|
731
|
+
get(id: string): Promise<ChatSession | undefined>;
|
|
732
|
+
listByUser(tenantId: string, userId: string): Promise<ChatSession[]>;
|
|
733
|
+
listByTenant(tenantId: string): Promise<ChatSession[]>;
|
|
734
|
+
countsByTenant(): Promise<Record<string, number>>;
|
|
735
|
+
update(id: string, patch: Partial<ChatSession>): Promise<void>;
|
|
736
|
+
delete(id: string): Promise<void>;
|
|
737
|
+
private evictExpired;
|
|
738
|
+
}
|
|
739
|
+
declare function newSessionId(): string;
|
|
740
|
+
|
|
741
|
+
/** One indexed passage of a document, with its embedding vector. */
|
|
742
|
+
interface KnowledgeChunk {
|
|
743
|
+
id: string;
|
|
744
|
+
url: string;
|
|
745
|
+
title: string;
|
|
746
|
+
text: string;
|
|
747
|
+
embedding: number[];
|
|
748
|
+
}
|
|
749
|
+
/** A retrieval result: the passage and how close it was to the query. */
|
|
750
|
+
interface KnowledgeHit {
|
|
751
|
+
url: string;
|
|
752
|
+
title: string;
|
|
753
|
+
text: string;
|
|
754
|
+
score: number;
|
|
755
|
+
}
|
|
756
|
+
/** What the dashboard shows about a workspace's index. */
|
|
757
|
+
interface KnowledgeStats {
|
|
758
|
+
chunks: number;
|
|
759
|
+
pages: number;
|
|
760
|
+
sources: string[];
|
|
761
|
+
ingestedAt?: number;
|
|
762
|
+
}
|
|
763
|
+
interface KnowledgeStore {
|
|
764
|
+
/** Replace a workspace's whole index with a freshly ingested set (atomic swap). */
|
|
765
|
+
replace(tenantId: string, chunks: KnowledgeChunk[], meta: {
|
|
766
|
+
sources: string[];
|
|
767
|
+
pages: number;
|
|
768
|
+
ingestedAt: number;
|
|
769
|
+
}): Promise<void>;
|
|
770
|
+
/** Top-k passages by cosine similarity to the query vector. */
|
|
771
|
+
search(tenantId: string, queryVector: readonly number[], k: number): Promise<KnowledgeHit[]>;
|
|
772
|
+
stats(tenantId: string): Promise<KnowledgeStats>;
|
|
773
|
+
clear(tenantId: string): Promise<void>;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
interface CreateAppOptions {
|
|
777
|
+
/** A single tenant, resolved at startup — the self-hosted case. */
|
|
778
|
+
resolved?: ResolvedConfig;
|
|
779
|
+
/** Many tenants, each resolved by publishable key at request time — the SaaS case. */
|
|
780
|
+
tenants?: TenantStore | TenantRegistry;
|
|
781
|
+
sessions?: SessionStore;
|
|
782
|
+
/** Knowledge-base index for the docs-search tool; in-memory when omitted. */
|
|
783
|
+
knowledge?: KnowledgeStore;
|
|
784
|
+
rateLimiter?: RateLimiter;
|
|
785
|
+
/**
|
|
786
|
+
* Reaches a turn running on another instance: Stop, and browser tool
|
|
787
|
+
* results. Needed whenever more than one instance serves chat.
|
|
788
|
+
*/
|
|
789
|
+
turnSignals?: TurnSignals;
|
|
790
|
+
logger?: (event: Record<string, unknown>) => void;
|
|
791
|
+
}
|
|
792
|
+
type AppEnv = {
|
|
793
|
+
Variables: {
|
|
794
|
+
tenant: TenantRuntime;
|
|
795
|
+
};
|
|
796
|
+
};
|
|
797
|
+
declare function createFrankieApp(options: CreateAppOptions): Hono<AppEnv>;
|
|
798
|
+
|
|
799
|
+
interface McpHostOptions {
|
|
800
|
+
tenants: TenantStore | TenantRegistry;
|
|
801
|
+
logger?: (event: Record<string, unknown>) => void;
|
|
802
|
+
/** Injected in tests so generated API tools hit a stub rather than the network. */
|
|
803
|
+
fetchImpl?: typeof fetch;
|
|
804
|
+
/** Shared with the chat API; each request counts against the tenant's message budget. */
|
|
805
|
+
rateLimiter?: RateLimiter;
|
|
806
|
+
}
|
|
807
|
+
declare function createMcpHostApp(options: McpHostOptions): Hono;
|
|
808
|
+
|
|
726
809
|
interface ResolvedStore {
|
|
727
810
|
store: ControlStore;
|
|
728
811
|
describe: string;
|
|
@@ -731,6 +814,9 @@ interface ResolvedStores {
|
|
|
731
814
|
tenants: ControlStore;
|
|
732
815
|
sessions: SessionStore;
|
|
733
816
|
knowledge: KnowledgeStore;
|
|
817
|
+
/** Shared across instances with Postgres; undefined means in-memory (one instance). */
|
|
818
|
+
rateLimiter?: RateLimiter;
|
|
819
|
+
turnSignals?: TurnSignals;
|
|
734
820
|
describe: string;
|
|
735
821
|
}
|
|
736
822
|
/**
|
|
@@ -759,10 +845,21 @@ declare class PostgresSessionStore implements SessionStore {
|
|
|
759
845
|
private readonly db;
|
|
760
846
|
private readonly ttlMs;
|
|
761
847
|
private ready?;
|
|
848
|
+
private lastPurge;
|
|
762
849
|
constructor(options: {
|
|
763
850
|
db: Queryable;
|
|
764
851
|
ttlMs?: number;
|
|
765
852
|
});
|
|
853
|
+
/** Deletes every conversation idle past the TTL. Returns how many went. */
|
|
854
|
+
purgeExpired(): Promise<number>;
|
|
855
|
+
/**
|
|
856
|
+
* Purges at most hourly, piggybacking on the reads that scan the table
|
|
857
|
+
* anyway. Housekeeping must never fail the read it rides on: a slow DELETE
|
|
858
|
+
* hitting a statement timeout, or a role that can read but not delete,
|
|
859
|
+
* would otherwise turn a user's conversation list into an error. The
|
|
860
|
+
* expired rows are still filtered out of every read regardless.
|
|
861
|
+
*/
|
|
862
|
+
private purgeIfDue;
|
|
766
863
|
private ensureReady;
|
|
767
864
|
init(): Promise<void>;
|
|
768
865
|
create(session: ChatSession): Promise<void>;
|
|
@@ -796,10 +893,44 @@ interface SkillGenerationInput {
|
|
|
796
893
|
}
|
|
797
894
|
declare function generateSkill(resolved: ResolvedConfig, input: SkillGenerationInput): Promise<WorkspaceSkill>;
|
|
798
895
|
|
|
896
|
+
interface ConversationQuestion {
|
|
897
|
+
/** The full conversation log (see `conversationLog` in @frankieai/core), built server-side. */
|
|
898
|
+
log: string;
|
|
899
|
+
question: string;
|
|
900
|
+
/** Earlier questions and answers in this thread, oldest first. */
|
|
901
|
+
history: Array<{
|
|
902
|
+
role: "user" | "assistant";
|
|
903
|
+
text: string;
|
|
904
|
+
}>;
|
|
905
|
+
}
|
|
906
|
+
/** Answer one question about one conversation with the workspace's own model. */
|
|
907
|
+
declare function askAboutConversation(resolved: ResolvedConfig, input: ConversationQuestion): Promise<string>;
|
|
908
|
+
|
|
909
|
+
/** What a GitHub Actions OIDC token says about the run that minted it. */
|
|
910
|
+
interface GithubRunClaims {
|
|
911
|
+
/** "owner/name" of the repository the workflow ran in. */
|
|
912
|
+
repository: string;
|
|
913
|
+
/** e.g. "refs/heads/main". */
|
|
914
|
+
ref: string;
|
|
915
|
+
/** The commit the run is for. */
|
|
916
|
+
sha: string;
|
|
917
|
+
/** "push", "workflow_dispatch", … */
|
|
918
|
+
eventName: string;
|
|
919
|
+
}
|
|
920
|
+
/**
|
|
921
|
+
* Verify a token GitHub Actions minted for a workflow run (`id-token: write`).
|
|
922
|
+
* It proves which repository and commit the request comes from without any
|
|
923
|
+
* secret stored in the repository: GitHub signs it, we check the signature
|
|
924
|
+
* against GitHub's published keys, the issuer, the audience and its expiry.
|
|
925
|
+
*/
|
|
926
|
+
declare function verifyGithubRunToken(token: string): Promise<GithubRunClaims>;
|
|
927
|
+
|
|
799
928
|
interface ControlOptions {
|
|
800
929
|
/** Immutable GitHub IDs configured by the platform operator, never by a client. */
|
|
801
930
|
bootstrapSuperAdmins?: string[];
|
|
802
931
|
generateSkill?: typeof generateSkill;
|
|
932
|
+
askAboutConversation?: typeof askAboutConversation;
|
|
933
|
+
verifyGithubRunToken?: typeof verifyGithubRunToken;
|
|
803
934
|
store: ControlStore;
|
|
804
935
|
/** Durable session store shared with the data plane when Postgres is configured. */
|
|
805
936
|
sessions?: SessionStore;
|
|
@@ -888,6 +1019,18 @@ interface RunTurnParams {
|
|
|
888
1019
|
onWarning?: (message: string, detail: Record<string, unknown>) => void;
|
|
889
1020
|
/** Knowledge-base index + embedding config for the docs-search tool. */
|
|
890
1021
|
knowledge?: KnowledgeDeps;
|
|
1022
|
+
/**
|
|
1023
|
+
* Pause every action that changes data until the user approves it. Set when
|
|
1024
|
+
* the workspace asks for it and the chat can show the approval card.
|
|
1025
|
+
*/
|
|
1026
|
+
confirmWrites?: boolean;
|
|
1027
|
+
/** The user's answer to the action the previous turn paused. */
|
|
1028
|
+
decision?: {
|
|
1029
|
+
action: PendingAction;
|
|
1030
|
+
approved: boolean;
|
|
1031
|
+
};
|
|
1032
|
+
/** Called when this turn pauses an action for approval, so the caller can keep it. */
|
|
1033
|
+
onPendingAction?: (action: PendingAction) => void;
|
|
891
1034
|
}
|
|
892
1035
|
/**
|
|
893
1036
|
* Run one user turn and stream it back as widget-facing events.
|
|
@@ -907,6 +1050,8 @@ interface McpConnection {
|
|
|
907
1050
|
name: string;
|
|
908
1051
|
error: string;
|
|
909
1052
|
}>;
|
|
1053
|
+
/** Tools that may change data: every tool its server doesn't mark `readOnlyHint`. */
|
|
1054
|
+
writes: Set<string>;
|
|
910
1055
|
close(): Promise<void>;
|
|
911
1056
|
}
|
|
912
1057
|
interface ConnectMcpOptions {
|
|
@@ -953,6 +1098,11 @@ interface ApiToolSet {
|
|
|
953
1098
|
}>;
|
|
954
1099
|
/** One entry per source that built successfully, for chat-facing visibility. */
|
|
955
1100
|
apis: ApiToolInventory[];
|
|
1101
|
+
/** Tools that change data (any method but GET), by tool name, for the approval card. */
|
|
1102
|
+
writes: Map<string, {
|
|
1103
|
+
method: string;
|
|
1104
|
+
path: string;
|
|
1105
|
+
}>;
|
|
956
1106
|
}
|
|
957
1107
|
/**
|
|
958
1108
|
* Turn a tenant's OpenAPI document into agent tools.
|
|
@@ -997,6 +1147,14 @@ interface CollectedTools {
|
|
|
997
1147
|
}>;
|
|
998
1148
|
/** OpenAPI sources that built successfully this turn, for chat-facing visibility. */
|
|
999
1149
|
apis: ApiToolInventory[];
|
|
1150
|
+
/**
|
|
1151
|
+
* Tools that change data, by name, with the HTTP method and path when there
|
|
1152
|
+
* is one. These ask the user before they run (`agent.confirmWrites`).
|
|
1153
|
+
*/
|
|
1154
|
+
writes: Map<string, {
|
|
1155
|
+
method?: string;
|
|
1156
|
+
path?: string;
|
|
1157
|
+
}>;
|
|
1000
1158
|
close(): Promise<void>;
|
|
1001
1159
|
}
|
|
1002
1160
|
/**
|
|
@@ -1029,11 +1187,8 @@ interface ForwardingConfig {
|
|
|
1029
1187
|
*/
|
|
1030
1188
|
declare function buildForwardedHeaders(source: ForwardingConfig, session: ChatSession): Record<string, string>;
|
|
1031
1189
|
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
* a web page and must never be able to redirect the agent.
|
|
1036
|
-
*/
|
|
1037
|
-
declare function buildSystemPrompt(config: FrankieConfig, session: ChatSession, availableTools?: string[]): string;
|
|
1190
|
+
declare function buildSystemPrompt(config: FrankieConfig, session: ChatSession, availableTools?: string[], options?: {
|
|
1191
|
+
confirmWrites?: boolean;
|
|
1192
|
+
}): string;
|
|
1038
1193
|
|
|
1039
1194
|
export { API_TOOL_PREFIX, type ApiToolSet, AuthError, type BuildApiToolsOptions, type ChatSession, type CollectToolsOptions, type CollectedTools, ConfigError, type ConnectMcpOptions, type ControlOptions, type ControlStore, type CreateAppOptions, FileTenantStore, type ForwardingConfig, MCP_TOOL_PREFIX, type McpConnection, type McpHostOptions, MemoryRateLimiter, MemorySessionStore, MemoryTenantStore, ModelResolutionError, OpenApiError, PostgresSessionStore, PostgresTenantStore, type PublicTenant, type Queryable, type RateLimiter, type ResolvedConfig, type ResolvedModel, type ResolvedStore, type ResolvedStores, type RunTurnParams, type SessionStore, SingleTenantStore, type TenantRecord, TenantRegistry, type TenantRuntime, type TenantStore, TokenService, type VerifiedUser, anonymousUser, applyEnvOverrides, buildApiTools, buildForwardedHeaders, buildSystemPrompt, collectTools, connectMcpTools, createControlApp, createFrankieApp, createMcpHostApp, isOriginAllowed, loadConfigFromFile, loadResolvedConfig, loadSpec, modelHasApi, newSessionId, resolveModel, resolveSecrets, resolveStores, resolveTenantStore, runTurn, safeEqual, toPublicTenant };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createControlApp
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-4XAOVSTL.js";
|
|
4
4
|
import {
|
|
5
5
|
ConfigError,
|
|
6
6
|
applyEnvOverrides,
|
|
@@ -42,7 +42,7 @@ import {
|
|
|
42
42
|
runTurn,
|
|
43
43
|
safeEqual,
|
|
44
44
|
toPublicTenant
|
|
45
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-TPVDQKD3.js";
|
|
46
46
|
export {
|
|
47
47
|
API_TOOL_PREFIX,
|
|
48
48
|
AuthError,
|
package/dist/server.js
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
ModelResolutionError,
|
|
8
8
|
createFrankieApp,
|
|
9
9
|
resolveStores
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-TPVDQKD3.js";
|
|
11
11
|
|
|
12
12
|
// src/server.ts
|
|
13
13
|
import { serve } from "@hono/node-server";
|
|
@@ -21,7 +21,13 @@ async function main() {
|
|
|
21
21
|
if (multiTenant) {
|
|
22
22
|
const resolved = await resolveStores();
|
|
23
23
|
describe = `multi-tenant (${resolved.describe})`;
|
|
24
|
-
app = createFrankieApp({
|
|
24
|
+
app = createFrankieApp({
|
|
25
|
+
tenants: resolved.tenants,
|
|
26
|
+
sessions: resolved.sessions,
|
|
27
|
+
knowledge: resolved.knowledge,
|
|
28
|
+
...resolved.rateLimiter ? { rateLimiter: resolved.rateLimiter } : {},
|
|
29
|
+
...resolved.turnSignals ? { turnSignals: resolved.turnSignals } : {}
|
|
30
|
+
});
|
|
25
31
|
} else {
|
|
26
32
|
app = createFrankieApp({ resolved: await loadResolvedConfig() });
|
|
27
33
|
}
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { serve } from \"@hono/node-server\";\nimport { createFrankieApp } from \"./app.js\";\nimport { ModelResolutionError } from \"./agent/models.js\";\nimport { ConfigError, loadResolvedConfig } from \"./config.js\";\nimport { resolveStores } from \"./control/resolve-store.js\";\n\nasync function main(): Promise<void> {\n const port = Number(process.env[\"PORT\"] ?? 8080);\n const host = process.env[\"HOST\"] ?? \"0.0.0.0\";\n\n // Two shapes, one binary. With DATABASE_URL (or FRANKIE_TENANTS_DIR) set, this\n // serves every tenant the control plane wrote, resolved by publishable key —\n // the hosted, multi-tenant shape. Otherwise it loads one frankie.config.json,\n // the self-hosted, single-tenant shape.\n const multiTenant = Boolean(process.env[\"DATABASE_URL\"] || process.env[\"FRANKIE_TENANTS_DIR\"]);\n let app;\n let describe = \"single-tenant\";\n try {\n if (multiTenant) {\n const resolved = await resolveStores();\n describe = `multi-tenant (${resolved.describe})`;\n app = createFrankieApp({
|
|
1
|
+
{"version":3,"sources":["../src/server.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { serve } from \"@hono/node-server\";\nimport { createFrankieApp } from \"./app.js\";\nimport { ModelResolutionError } from \"./agent/models.js\";\nimport { ConfigError, loadResolvedConfig } from \"./config.js\";\nimport { resolveStores } from \"./control/resolve-store.js\";\n\nasync function main(): Promise<void> {\n const port = Number(process.env[\"PORT\"] ?? 8080);\n const host = process.env[\"HOST\"] ?? \"0.0.0.0\";\n\n // Two shapes, one binary. With DATABASE_URL (or FRANKIE_TENANTS_DIR) set, this\n // serves every tenant the control plane wrote, resolved by publishable key —\n // the hosted, multi-tenant shape. Otherwise it loads one frankie.config.json,\n // the self-hosted, single-tenant shape.\n const multiTenant = Boolean(process.env[\"DATABASE_URL\"] || process.env[\"FRANKIE_TENANTS_DIR\"]);\n let app;\n let describe = \"single-tenant\";\n try {\n if (multiTenant) {\n const resolved = await resolveStores();\n describe = `multi-tenant (${resolved.describe})`;\n app = createFrankieApp({\n tenants: resolved.tenants,\n sessions: resolved.sessions,\n knowledge: resolved.knowledge,\n ...(resolved.rateLimiter ? { rateLimiter: resolved.rateLimiter } : {}),\n ...(resolved.turnSignals ? { turnSignals: resolved.turnSignals } : {}),\n });\n } else {\n app = createFrankieApp({ resolved: await loadResolvedConfig() });\n }\n } catch (error) {\n if (error instanceof ConfigError || error instanceof ModelResolutionError) {\n console.error(`[frankie] ${error.message}`);\n process.exit(1);\n }\n throw error;\n }\n\n serve({ fetch: app.fetch, port, hostname: host }, () => {\n console.log(\n JSON.stringify({\n ts: new Date().toISOString(),\n level: \"info\",\n event: \"listening\",\n mode: describe,\n url: `http://${host}:${port}`,\n }),\n );\n });\n}\n\nvoid main();\n"],"mappings":";;;;;;;;;;;;AACA,SAAS,aAAa;AAMtB,eAAe,OAAsB;AACnC,QAAM,OAAO,OAAO,QAAQ,IAAI,MAAM,KAAK,IAAI;AAC/C,QAAM,OAAO,QAAQ,IAAI,MAAM,KAAK;AAMpC,QAAM,cAAc,QAAQ,QAAQ,IAAI,cAAc,KAAK,QAAQ,IAAI,qBAAqB,CAAC;AAC7F,MAAI;AACJ,MAAI,WAAW;AACf,MAAI;AACF,QAAI,aAAa;AACf,YAAM,WAAW,MAAM,cAAc;AACrC,iBAAW,iBAAiB,SAAS,QAAQ;AAC7C,YAAM,iBAAiB;AAAA,QACrB,SAAS,SAAS;AAAA,QAClB,UAAU,SAAS;AAAA,QACnB,WAAW,SAAS;AAAA,QACpB,GAAI,SAAS,cAAc,EAAE,aAAa,SAAS,YAAY,IAAI,CAAC;AAAA,QACpE,GAAI,SAAS,cAAc,EAAE,aAAa,SAAS,YAAY,IAAI,CAAC;AAAA,MACtE,CAAC;AAAA,IACH,OAAO;AACL,YAAM,iBAAiB,EAAE,UAAU,MAAM,mBAAmB,EAAE,CAAC;AAAA,IACjE;AAAA,EACF,SAAS,OAAO;AACd,QAAI,iBAAiB,eAAe,iBAAiB,sBAAsB;AACzE,cAAQ,MAAM,aAAa,MAAM,OAAO,EAAE;AAC1C,cAAQ,KAAK,CAAC;AAAA,IAChB;AACA,UAAM;AAAA,EACR;AAEA,QAAM,EAAE,OAAO,IAAI,OAAO,MAAM,UAAU,KAAK,GAAG,MAAM;AACtD,YAAQ;AAAA,MACN,KAAK,UAAU;AAAA,QACb,KAAI,oBAAI,KAAK,GAAE,YAAY;AAAA,QAC3B,OAAO;AAAA,QACP,OAAO;AAAA,QACP,MAAM;AAAA,QACN,KAAK,UAAU,IAAI,IAAI,IAAI;AAAA,MAC7B,CAAC;AAAA,IACH;AAAA,EACF,CAAC;AACH;AAEA,KAAK,KAAK;","names":[]}
|