@frankieai/server 0.2.1 → 0.2.3
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-UZSHFMJN.js → chunk-37VMCQCI.js} +315 -26
- package/dist/chunk-37VMCQCI.js.map +1 -0
- package/dist/chunk-MKN5663J.js +3166 -0
- package/dist/chunk-MKN5663J.js.map +1 -0
- package/dist/chunk-NF6IJVES.js +75 -0
- package/dist/chunk-NF6IJVES.js.map +1 -0
- package/dist/control-server.js +7 -3
- package/dist/control-server.js.map +1 -1
- package/dist/index.d.ts +117 -8
- package/dist/index.js +12 -12
- package/dist/server.js +3 -3
- package/package.json +5 -2
- package/dist/chunk-UZSHFMJN.js.map +0 -1
- package/dist/chunk-WFENEYOV.js +0 -1266
- package/dist/chunk-WFENEYOV.js.map +0 -1
- package/dist/chunk-XCIM765D.js +0 -1551
- package/dist/chunk-XCIM765D.js.map +0 -1
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// src/config.ts
|
|
2
|
+
import { readFile } from "fs/promises";
|
|
3
|
+
import { resolve } from "path";
|
|
4
|
+
import { parseFrankieConfig } from "@frankieai/core";
|
|
5
|
+
var ConfigError = class extends Error {
|
|
6
|
+
};
|
|
7
|
+
async function loadConfigFromFile(path) {
|
|
8
|
+
const target = resolve(path ?? process.env["FRANKIE_CONFIG"] ?? "frankie.config.json");
|
|
9
|
+
let raw;
|
|
10
|
+
try {
|
|
11
|
+
raw = await readFile(target, "utf8");
|
|
12
|
+
} catch {
|
|
13
|
+
throw new ConfigError(
|
|
14
|
+
`Could not read Frankie config at ${target}. Set FRANKIE_CONFIG to point at your frankie.config.json.`
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
return parseFrankieConfig(applyEnvOverrides(JSON.parse(raw)));
|
|
19
|
+
} catch (error) {
|
|
20
|
+
throw new ConfigError(`Invalid Frankie config at ${target}: ${error.message}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function applyEnvOverrides(raw, env = process.env) {
|
|
24
|
+
const out = { ...raw };
|
|
25
|
+
const model = { ...isRecord(out["model"]) ? out["model"] : {} };
|
|
26
|
+
if (env["FRANKIE_MODEL_PROVIDER"]) model["provider"] = env["FRANKIE_MODEL_PROVIDER"];
|
|
27
|
+
if (env["FRANKIE_MODEL"]) model["model"] = env["FRANKIE_MODEL"];
|
|
28
|
+
if (env["FRANKIE_MODEL_BASE_URL"]) model["baseUrl"] = env["FRANKIE_MODEL_BASE_URL"];
|
|
29
|
+
if (Object.keys(model).length > 0) out["model"] = model;
|
|
30
|
+
const auth = { ...isRecord(out["auth"]) ? out["auth"] : {} };
|
|
31
|
+
if (env["FRANKIE_ALLOWED_ORIGINS"]) {
|
|
32
|
+
auth["allowedOrigins"] = env["FRANKIE_ALLOWED_ORIGINS"].split(",").map((o) => o.trim()).filter(Boolean);
|
|
33
|
+
}
|
|
34
|
+
if (env["FRANKIE_PUBLISHABLE_KEY"]) auth["publishableKey"] = env["FRANKIE_PUBLISHABLE_KEY"];
|
|
35
|
+
if (Object.keys(auth).length > 0) out["auth"] = auth;
|
|
36
|
+
return out;
|
|
37
|
+
}
|
|
38
|
+
function isRecord(value) {
|
|
39
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
40
|
+
}
|
|
41
|
+
function resolveSecrets(config, env = process.env) {
|
|
42
|
+
const missing = [];
|
|
43
|
+
const jwtSecret = env[config.auth.jwtSecretEnv];
|
|
44
|
+
if (!config.auth.jwksUrl && !jwtSecret) missing.push(config.auth.jwtSecretEnv);
|
|
45
|
+
const sessionSecret = env[config.auth.sessionSecretEnv];
|
|
46
|
+
if (!sessionSecret) missing.push(config.auth.sessionSecretEnv);
|
|
47
|
+
const providerApiKey = env[config.model.apiKeyEnv];
|
|
48
|
+
if (!providerApiKey) missing.push(config.model.apiKeyEnv);
|
|
49
|
+
if (missing.length) {
|
|
50
|
+
throw new ConfigError(`Missing required environment variables: ${missing.join(", ")}`);
|
|
51
|
+
}
|
|
52
|
+
if (sessionSecret.length < 32) {
|
|
53
|
+
throw new ConfigError(`${config.auth.sessionSecretEnv} must be at least 32 characters.`);
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
config,
|
|
57
|
+
secrets: {
|
|
58
|
+
jwtSecret,
|
|
59
|
+
sessionSecret,
|
|
60
|
+
providerApiKey
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
async function loadResolvedConfig(path) {
|
|
65
|
+
return resolveSecrets(await loadConfigFromFile(path));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export {
|
|
69
|
+
ConfigError,
|
|
70
|
+
loadConfigFromFile,
|
|
71
|
+
applyEnvOverrides,
|
|
72
|
+
resolveSecrets,
|
|
73
|
+
loadResolvedConfig
|
|
74
|
+
};
|
|
75
|
+
//# sourceMappingURL=chunk-NF6IJVES.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/config.ts"],"sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\nimport { parseFrankieConfig, type FrankieConfig } from \"@frankieai/core\";\n\n/**\n * Secrets never live in the config file — the file names the env var that\n * holds each one. `ResolvedConfig` is the config plus the resolved values.\n */\nexport interface ResolvedConfig {\n config: FrankieConfig;\n secrets: {\n /** HS256 secret the host signs end-user tokens with. Absent when using JWKS. */\n jwtSecret?: string;\n /** Signs Frankie's own session tokens. */\n sessionSecret: string;\n /** Key for the model provider named in `model.provider`. */\n providerApiKey: string;\n };\n}\n\nexport class ConfigError extends Error {}\n\nexport async function loadConfigFromFile(path?: string): Promise<FrankieConfig> {\n const target = resolve(path ?? process.env[\"FRANKIE_CONFIG\"] ?? \"frankie.config.json\");\n let raw: string;\n try {\n raw = await readFile(target, \"utf8\");\n } catch {\n throw new ConfigError(\n `Could not read Frankie config at ${target}. Set FRANKIE_CONFIG to point at your frankie.config.json.`,\n );\n }\n try {\n return parseFrankieConfig(applyEnvOverrides(JSON.parse(raw) as Record<string, unknown>));\n } catch (error) {\n throw new ConfigError(`Invalid Frankie config at ${target}: ${(error as Error).message}`);\n }\n}\n\n/**\n * A handful of fields have to move with the deployment rather than the config\n * file — the model and the origin allowlist are what an operator most often\n * wants to flip without a redeploy.\n */\nexport function applyEnvOverrides(\n raw: Record<string, unknown>,\n env: NodeJS.ProcessEnv = process.env,\n): Record<string, unknown> {\n const out = { ...raw };\n\n const model = { ...(isRecord(out[\"model\"]) ? out[\"model\"] : {}) };\n if (env[\"FRANKIE_MODEL_PROVIDER\"]) model[\"provider\"] = env[\"FRANKIE_MODEL_PROVIDER\"];\n if (env[\"FRANKIE_MODEL\"]) model[\"model\"] = env[\"FRANKIE_MODEL\"];\n if (env[\"FRANKIE_MODEL_BASE_URL\"]) model[\"baseUrl\"] = env[\"FRANKIE_MODEL_BASE_URL\"];\n if (Object.keys(model).length > 0) out[\"model\"] = model;\n\n const auth = { ...(isRecord(out[\"auth\"]) ? out[\"auth\"] : {}) };\n if (env[\"FRANKIE_ALLOWED_ORIGINS\"]) {\n auth[\"allowedOrigins\"] = env[\"FRANKIE_ALLOWED_ORIGINS\"].split(\",\").map((o) => o.trim()).filter(Boolean);\n }\n if (env[\"FRANKIE_PUBLISHABLE_KEY\"]) auth[\"publishableKey\"] = env[\"FRANKIE_PUBLISHABLE_KEY\"];\n if (Object.keys(auth).length > 0) out[\"auth\"] = auth;\n\n return out;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nexport function resolveSecrets(config: FrankieConfig, env: NodeJS.ProcessEnv = process.env): ResolvedConfig {\n const missing: string[] = [];\n\n const jwtSecret = env[config.auth.jwtSecretEnv];\n if (!config.auth.jwksUrl && !jwtSecret) missing.push(config.auth.jwtSecretEnv);\n\n const sessionSecret = env[config.auth.sessionSecretEnv];\n if (!sessionSecret) missing.push(config.auth.sessionSecretEnv);\n\n const providerApiKey = env[config.model.apiKeyEnv];\n if (!providerApiKey) missing.push(config.model.apiKeyEnv);\n\n if (missing.length) {\n throw new ConfigError(`Missing required environment variables: ${missing.join(\", \")}`);\n }\n\n if (sessionSecret!.length < 32) {\n throw new ConfigError(`${config.auth.sessionSecretEnv} must be at least 32 characters.`);\n }\n\n return {\n config,\n secrets: {\n jwtSecret,\n sessionSecret: sessionSecret!,\n providerApiKey: providerApiKey!,\n },\n };\n}\n\nexport async function loadResolvedConfig(path?: string): Promise<ResolvedConfig> {\n return resolveSecrets(await loadConfigFromFile(path));\n}\n"],"mappings":";AAAA,SAAS,gBAAgB;AACzB,SAAS,eAAe;AACxB,SAAS,0BAA8C;AAkBhD,IAAM,cAAN,cAA0B,MAAM;AAAC;AAExC,eAAsB,mBAAmB,MAAuC;AAC9E,QAAM,SAAS,QAAQ,QAAQ,QAAQ,IAAI,gBAAgB,KAAK,qBAAqB;AACrF,MAAI;AACJ,MAAI;AACF,UAAM,MAAM,SAAS,QAAQ,MAAM;AAAA,EACrC,QAAQ;AACN,UAAM,IAAI;AAAA,MACR,oCAAoC,MAAM;AAAA,IAC5C;AAAA,EACF;AACA,MAAI;AACF,WAAO,mBAAmB,kBAAkB,KAAK,MAAM,GAAG,CAA4B,CAAC;AAAA,EACzF,SAAS,OAAO;AACd,UAAM,IAAI,YAAY,6BAA6B,MAAM,KAAM,MAAgB,OAAO,EAAE;AAAA,EAC1F;AACF;AAOO,SAAS,kBACd,KACA,MAAyB,QAAQ,KACR;AACzB,QAAM,MAAM,EAAE,GAAG,IAAI;AAErB,QAAM,QAAQ,EAAE,GAAI,SAAS,IAAI,OAAO,CAAC,IAAI,IAAI,OAAO,IAAI,CAAC,EAAG;AAChE,MAAI,IAAI,wBAAwB,EAAG,OAAM,UAAU,IAAI,IAAI,wBAAwB;AACnF,MAAI,IAAI,eAAe,EAAG,OAAM,OAAO,IAAI,IAAI,eAAe;AAC9D,MAAI,IAAI,wBAAwB,EAAG,OAAM,SAAS,IAAI,IAAI,wBAAwB;AAClF,MAAI,OAAO,KAAK,KAAK,EAAE,SAAS,EAAG,KAAI,OAAO,IAAI;AAElD,QAAM,OAAO,EAAE,GAAI,SAAS,IAAI,MAAM,CAAC,IAAI,IAAI,MAAM,IAAI,CAAC,EAAG;AAC7D,MAAI,IAAI,yBAAyB,GAAG;AAClC,SAAK,gBAAgB,IAAI,IAAI,yBAAyB,EAAE,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,OAAO;AAAA,EACxG;AACA,MAAI,IAAI,yBAAyB,EAAG,MAAK,gBAAgB,IAAI,IAAI,yBAAyB;AAC1F,MAAI,OAAO,KAAK,IAAI,EAAE,SAAS,EAAG,KAAI,MAAM,IAAI;AAEhD,SAAO;AACT;AAEA,SAAS,SAAS,OAAkD;AAClE,SAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;AAEO,SAAS,eAAe,QAAuB,MAAyB,QAAQ,KAAqB;AAC1G,QAAM,UAAoB,CAAC;AAE3B,QAAM,YAAY,IAAI,OAAO,KAAK,YAAY;AAC9C,MAAI,CAAC,OAAO,KAAK,WAAW,CAAC,UAAW,SAAQ,KAAK,OAAO,KAAK,YAAY;AAE7E,QAAM,gBAAgB,IAAI,OAAO,KAAK,gBAAgB;AACtD,MAAI,CAAC,cAAe,SAAQ,KAAK,OAAO,KAAK,gBAAgB;AAE7D,QAAM,iBAAiB,IAAI,OAAO,MAAM,SAAS;AACjD,MAAI,CAAC,eAAgB,SAAQ,KAAK,OAAO,MAAM,SAAS;AAExD,MAAI,QAAQ,QAAQ;AAClB,UAAM,IAAI,YAAY,2CAA2C,QAAQ,KAAK,IAAI,CAAC,EAAE;AAAA,EACvF;AAEA,MAAI,cAAe,SAAS,IAAI;AAC9B,UAAM,IAAI,YAAY,GAAG,OAAO,KAAK,gBAAgB,kCAAkC;AAAA,EACzF;AAEA,SAAO;AAAA,IACL;AAAA,IACA,SAAS;AAAA,MACP;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;AAEA,eAAsB,mBAAmB,MAAwC;AAC/E,SAAO,eAAe,MAAM,mBAAmB,IAAI,CAAC;AACtD;","names":[]}
|
package/dist/control-server.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
createControlApp
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-37VMCQCI.js";
|
|
5
5
|
import {
|
|
6
|
+
createFrankieApp,
|
|
6
7
|
resolveStores
|
|
7
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-MKN5663J.js";
|
|
8
9
|
|
|
9
10
|
// src/control-server.ts
|
|
10
11
|
import { serve } from "@hono/node-server";
|
|
12
|
+
import { Hono } from "hono";
|
|
11
13
|
async function main() {
|
|
12
14
|
const port = Number(process.env["PORT"] ?? process.env["CONTROL_PORT"] ?? 8081);
|
|
13
15
|
const host = process.env["HOST"] ?? "0.0.0.0";
|
|
@@ -16,7 +18,9 @@ async function main() {
|
|
|
16
18
|
process.exit(1);
|
|
17
19
|
}
|
|
18
20
|
const { tenants: store, sessions, describe } = await resolveStores();
|
|
19
|
-
const app =
|
|
21
|
+
const app = new Hono();
|
|
22
|
+
app.route("/", createControlApp({ store, sessions }));
|
|
23
|
+
app.route("/", createFrankieApp({ tenants: store, sessions }));
|
|
20
24
|
serve({ fetch: app.fetch, port, hostname: host }, () => {
|
|
21
25
|
console.log(
|
|
22
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 { 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, describe } = await resolveStores();\n const app = createControlApp({ store, sessions });\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":"
|
|
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, describe } = await resolveStores();\n const app = new Hono();\n app.route(\"/\", createControlApp({ store, sessions }));\n app.route(\"/\", createFrankieApp({ tenants: store, sessions }));\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,SAAS,IAAI,MAAM,cAAc;AACnE,QAAM,MAAM,IAAI,KAAK;AACrB,MAAI,MAAM,KAAK,iBAAiB,EAAE,OAAO,SAAS,CAAC,CAAC;AACpD,MAAI,MAAM,KAAK,iBAAiB,EAAE,SAAS,OAAO,SAAS,CAAC,CAAC;AAE7D,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,6 @@
|
|
|
1
1
|
import { Hono } from 'hono';
|
|
2
|
-
import { FrankieConfig, AuthConfig, WorkspaceSkill, ChatAttachment, FrankieServerEvent, McpServerConfig, ToolPolicy, ApiSourceConfig } from '@frankieai/core';
|
|
2
|
+
import { FrankieConfig, AuthConfig, WorkspaceSkill, HostToolRequest, HostToolResult, ChatAttachment, FrankieServerEvent, McpServerConfig, ToolPolicy, ApiToolInventory, ApiSourceConfig } from '@frankieai/core';
|
|
3
|
+
export { matchPath } from '@frankieai/core';
|
|
3
4
|
import { AgentMessage, AgentTool } from '@earendil-works/pi-agent-core';
|
|
4
5
|
import { JWTPayload } from 'jose';
|
|
5
6
|
import { Models, Model, Api } from '@earendil-works/pi-ai';
|
|
@@ -138,6 +139,12 @@ interface SessionStore {
|
|
|
138
139
|
listByUser(tenantId: string, userId: string): Promise<ChatSession[]>;
|
|
139
140
|
/** Workspace-owner history view. This is only exposed through the authenticated control plane. */
|
|
140
141
|
listByTenant(tenantId: string): Promise<ChatSession[]>;
|
|
142
|
+
/**
|
|
143
|
+
* Non-expired conversation count per tenant id, in one pass. Used by the
|
|
144
|
+
* workspace directory to show a workspace as "Live" once it has real chats —
|
|
145
|
+
* cheaper than a listByTenant call per workspace.
|
|
146
|
+
*/
|
|
147
|
+
countsByTenant(): Promise<Record<string, number>>;
|
|
141
148
|
update(id: string, patch: Partial<ChatSession>): Promise<void>;
|
|
142
149
|
delete(id: string): Promise<void>;
|
|
143
150
|
}
|
|
@@ -149,6 +156,7 @@ declare class MemorySessionStore implements SessionStore {
|
|
|
149
156
|
get(id: string): Promise<ChatSession | undefined>;
|
|
150
157
|
listByUser(tenantId: string, userId: string): Promise<ChatSession[]>;
|
|
151
158
|
listByTenant(tenantId: string): Promise<ChatSession[]>;
|
|
159
|
+
countsByTenant(): Promise<Record<string, number>>;
|
|
152
160
|
update(id: string, patch: Partial<ChatSession>): Promise<void>;
|
|
153
161
|
delete(id: string): Promise<void>;
|
|
154
162
|
private evictExpired;
|
|
@@ -285,8 +293,35 @@ interface RepositoryInstallRecord {
|
|
|
285
293
|
pullRequestUrl?: string;
|
|
286
294
|
requiresJwtSecret: boolean;
|
|
287
295
|
failureMessage?: string;
|
|
296
|
+
/**
|
|
297
|
+
* Last-known rich readiness state, persisted from live GitHub inspection so the
|
|
298
|
+
* dashboard renders the current step instantly on reload and can fall back to
|
|
299
|
+
* it when GitHub is briefly unreachable (a stale token no longer wipes progress).
|
|
300
|
+
*/
|
|
301
|
+
step?: string;
|
|
302
|
+
pullRequestState?: "draft" | "open" | "closed" | "merged";
|
|
303
|
+
checkRollup?: "success" | "pending" | "failure" | "none";
|
|
304
|
+
checkedAt?: string;
|
|
288
305
|
updatedAt: string;
|
|
289
306
|
}
|
|
307
|
+
/**
|
|
308
|
+
* A Vercel integration installed for this workspace. The access token is a
|
|
309
|
+
* secret like `jwtSecret`: stored here, never returned by the public shape.
|
|
310
|
+
*/
|
|
311
|
+
interface VercelLinkRecord {
|
|
312
|
+
/** Token minted by the integration's OAuth exchange. Integration tokens don't expire. */
|
|
313
|
+
accessToken: string;
|
|
314
|
+
/** Vercel's id for this installation (`configurationId`), for support and uninstall. */
|
|
315
|
+
installationId: string;
|
|
316
|
+
/** Set when the integration was installed on a team rather than a personal account. */
|
|
317
|
+
teamId?: string;
|
|
318
|
+
/** The Vercel project linked to this workspace's repository, once matched. */
|
|
319
|
+
projectId?: string;
|
|
320
|
+
projectName?: string;
|
|
321
|
+
connectedAt: string;
|
|
322
|
+
}
|
|
323
|
+
/** `VercelLinkRecord` with the token stripped — what the dashboard sees. */
|
|
324
|
+
type PublicVercelLink = Omit<VercelLinkRecord, "accessToken">;
|
|
290
325
|
interface TenantRecord {
|
|
291
326
|
apiSpecSync?: Record<string, {
|
|
292
327
|
repository: string;
|
|
@@ -294,6 +329,11 @@ interface TenantRecord {
|
|
|
294
329
|
commitSha: string;
|
|
295
330
|
syncedAt: string;
|
|
296
331
|
}>;
|
|
332
|
+
/** Last live connectivity/auth check per API source, so the dashboard doesn't need to re-run it on every load. */
|
|
333
|
+
apiConnectivityChecks?: Record<string, {
|
|
334
|
+
checkedAt: string;
|
|
335
|
+
ok: boolean;
|
|
336
|
+
}>;
|
|
297
337
|
skillHistory?: FrankieConfig["skills"];
|
|
298
338
|
id: string;
|
|
299
339
|
/** Who owns this tenant — the dashboard user's stable id (e.g. "github:42"). */
|
|
@@ -305,12 +345,15 @@ interface TenantRecord {
|
|
|
305
345
|
locked?: boolean;
|
|
306
346
|
/** Durable repository-install state surfaced in the dashboard and setup chat. */
|
|
307
347
|
repositoryInstall?: RepositoryInstallRecord;
|
|
348
|
+
/** Deployment provider connection, when the workspace has connected Vercel. */
|
|
349
|
+
vercel?: VercelLinkRecord;
|
|
308
350
|
createdAt: string;
|
|
309
351
|
updatedAt: string;
|
|
310
352
|
}
|
|
311
353
|
/** A tenant record with its secrets stripped — safe to return from the API. */
|
|
312
354
|
interface PublicTenant {
|
|
313
355
|
apiSpecSync?: TenantRecord["apiSpecSync"];
|
|
356
|
+
apiConnectivityChecks?: TenantRecord["apiConnectivityChecks"];
|
|
314
357
|
skillHistory: FrankieConfig["skills"];
|
|
315
358
|
skills: TenantRecord["config"]["skills"];
|
|
316
359
|
id: string;
|
|
@@ -319,6 +362,8 @@ interface PublicTenant {
|
|
|
319
362
|
publishableKey: string;
|
|
320
363
|
allowedOrigins: string[];
|
|
321
364
|
allowAnonymous: boolean;
|
|
365
|
+
/** How the hosted MCP treats a caller's bearer: verify it as a JWT, or forward it opaquely. */
|
|
366
|
+
userCredential: "jwt" | "opaque";
|
|
322
367
|
connection: "knowledge" | "mcp" | "openapi";
|
|
323
368
|
mcpServers: string[];
|
|
324
369
|
apis: string[];
|
|
@@ -326,6 +371,8 @@ interface PublicTenant {
|
|
|
326
371
|
connections: Array<{
|
|
327
372
|
kind: "mcp" | "openapi";
|
|
328
373
|
name: string;
|
|
374
|
+
/** Friendly label for the dashboard; `name` remains the stable identifier. */
|
|
375
|
+
displayName?: string;
|
|
329
376
|
/** OpenAPI spec URL/path. */
|
|
330
377
|
spec?: string;
|
|
331
378
|
/** MCP server URL. */
|
|
@@ -337,6 +384,8 @@ interface PublicTenant {
|
|
|
337
384
|
method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
338
385
|
path: string;
|
|
339
386
|
summary?: string;
|
|
387
|
+
/** True for a write operation whose only auth is a static/shared credential, not the calling user's own identity. */
|
|
388
|
+
authGap: boolean;
|
|
340
389
|
}>;
|
|
341
390
|
discoveredTools?: Array<{
|
|
342
391
|
name: string;
|
|
@@ -344,7 +393,13 @@ interface PublicTenant {
|
|
|
344
393
|
}>;
|
|
345
394
|
selectedTools?: string[];
|
|
346
395
|
exposeAsMcp?: boolean;
|
|
396
|
+
/** Where the source's operations run: "server" (Frankie's server) or "client" (the user's browser). */
|
|
397
|
+
execution?: "server" | "client";
|
|
347
398
|
forwardsToken: boolean;
|
|
399
|
+
/** The header the user's credential is forwarded to your API as (empty when none). */
|
|
400
|
+
forwardTokenAs?: string;
|
|
401
|
+
/** Security scheme names with a credential configured (OpenAPI only). Never the values themselves. */
|
|
402
|
+
securedSchemes?: string[];
|
|
348
403
|
/** Whether this source is currently enabled for chat. */
|
|
349
404
|
enabled: boolean;
|
|
350
405
|
/** Configuration health, not a simulated runtime probe. */
|
|
@@ -354,6 +409,8 @@ interface PublicTenant {
|
|
|
354
409
|
locked: boolean;
|
|
355
410
|
/** A submitted or in-progress SDK installation for this workspace. */
|
|
356
411
|
repositoryInstall?: RepositoryInstallRecord;
|
|
412
|
+
/** The connected Vercel installation and matched project — never its token. */
|
|
413
|
+
vercel?: PublicVercelLink;
|
|
357
414
|
/** The customer's brand theme for the widget, so the dashboard can edit it. */
|
|
358
415
|
theme: TenantRecord["config"]["theme"];
|
|
359
416
|
/** Opt-in chat capabilities (attachments, model picker) for the dashboard to edit. */
|
|
@@ -559,13 +616,28 @@ declare class PostgresSessionStore implements SessionStore {
|
|
|
559
616
|
get(id: string): Promise<ChatSession | undefined>;
|
|
560
617
|
listByUser(tenantId: string, userId: string): Promise<ChatSession[]>;
|
|
561
618
|
listByTenant(tenantId: string): Promise<ChatSession[]>;
|
|
619
|
+
countsByTenant(): Promise<Record<string, number>>;
|
|
562
620
|
update(id: string, patch: Partial<ChatSession>): Promise<void>;
|
|
563
621
|
delete(id: string): Promise<void>;
|
|
564
622
|
}
|
|
565
623
|
|
|
566
624
|
interface SkillGenerationInput {
|
|
567
625
|
goal: string;
|
|
626
|
+
/** Approved tool identifiers this skill may use. Validated against on return. */
|
|
568
627
|
tools: string[];
|
|
628
|
+
/**
|
|
629
|
+
* What each tool in `tools` actually does (method/path/summary, or an MCP
|
|
630
|
+
* tool's own description) — see `toolDescriptions()` in @frankieai/core.
|
|
631
|
+
* Without this the model has nothing to write concrete steps from but a
|
|
632
|
+
* bare identifier like `api__product__listItems`. Optional so older
|
|
633
|
+
* callers (and tests) that only have bare ids still work; the resulting
|
|
634
|
+
* draft is just less specific.
|
|
635
|
+
*/
|
|
636
|
+
toolDetails?: Array<{
|
|
637
|
+
id: string;
|
|
638
|
+
description: string;
|
|
639
|
+
}>;
|
|
640
|
+
/** Present when refining an existing skill rather than starting fresh. */
|
|
569
641
|
instructions?: string;
|
|
570
642
|
}
|
|
571
643
|
declare function generateSkill(resolved: ResolvedConfig, input: SkillGenerationInput): Promise<WorkspaceSkill>;
|
|
@@ -579,6 +651,16 @@ interface ControlOptions {
|
|
|
579
651
|
sessions?: SessionStore;
|
|
580
652
|
/** Bearer token that gates the admin API. A stand-in for real user auth. */
|
|
581
653
|
adminToken?: string;
|
|
654
|
+
/**
|
|
655
|
+
* Optional shared secret for signed owner assertions. When set (here or via
|
|
656
|
+
* FRANKIE_CONTROL_ASSERTION_SECRET), the acting owner must be proven by a
|
|
657
|
+
* short-lived JWT the dashboard signs with the same secret — a plaintext
|
|
658
|
+
* `x-frankie-owner` header alone is refused. This closes the gap where the
|
|
659
|
+
* admin token by itself could select an arbitrary owner. Off by default, so
|
|
660
|
+
* existing deployments and unscoped admin-token callers are unaffected until
|
|
661
|
+
* the secret is configured on both the dashboard and the control plane.
|
|
662
|
+
*/
|
|
663
|
+
ownerAssertionSecret?: string;
|
|
582
664
|
logger?: (event: Record<string, unknown>) => void;
|
|
583
665
|
}
|
|
584
666
|
/**
|
|
@@ -591,6 +673,22 @@ interface ControlOptions {
|
|
|
591
673
|
*/
|
|
592
674
|
declare function createControlApp(options: ControlOptions): Hono;
|
|
593
675
|
|
|
676
|
+
/**
|
|
677
|
+
* The per-turn handle an API tool uses to run itself in the user's browser
|
|
678
|
+
* instead of on Frankie's server. `available` says whether a live widget
|
|
679
|
+
* channel is bound for this session — false over the hosted MCP endpoint or any
|
|
680
|
+
* headless caller, where a browser-scoped call is impossible.
|
|
681
|
+
*/
|
|
682
|
+
interface ClientToolBridge {
|
|
683
|
+
readonly available: boolean;
|
|
684
|
+
/**
|
|
685
|
+
* Ask the browser to perform one HTTP call and resolve with its outcome.
|
|
686
|
+
* Rejects if no channel is bound, the turn is aborted, or the browser does
|
|
687
|
+
* not answer before `timeoutMs`.
|
|
688
|
+
*/
|
|
689
|
+
execute(name: string, request: HostToolRequest, signal?: AbortSignal): Promise<HostToolResult>;
|
|
690
|
+
}
|
|
691
|
+
|
|
594
692
|
interface RunTurnParams {
|
|
595
693
|
config: FrankieConfig;
|
|
596
694
|
session: ChatSession;
|
|
@@ -604,6 +702,8 @@ interface RunTurnParams {
|
|
|
604
702
|
modelOverride?: Model<Api>;
|
|
605
703
|
/** Files attached to this turn, already validated against the feature flags. */
|
|
606
704
|
attachments?: ChatAttachment[];
|
|
705
|
+
/** Browser channel for API sources marked `execution: "client"`, when a widget is connected. */
|
|
706
|
+
clientBridge?: ClientToolBridge;
|
|
607
707
|
signal: AbortSignal;
|
|
608
708
|
/** Called with the full transcript once the turn settles, to persist it. */
|
|
609
709
|
onTranscript?: (messages: AgentMessage[]) => void;
|
|
@@ -656,6 +756,13 @@ interface BuildApiToolsOptions {
|
|
|
656
756
|
session: ChatSession;
|
|
657
757
|
policy: ToolPolicy;
|
|
658
758
|
fetchImpl?: typeof fetch;
|
|
759
|
+
/**
|
|
760
|
+
* Bridge to the user's browser for sources marked `execution: "client"`. When
|
|
761
|
+
* absent (headless callers, the hosted MCP endpoint), a client-mode operation
|
|
762
|
+
* has no way to run and reports that clearly instead of falling back to a
|
|
763
|
+
* server-side call the user's session would not authorize.
|
|
764
|
+
*/
|
|
765
|
+
clientBridge?: ClientToolBridge;
|
|
659
766
|
onWarning?: (message: string, detail: Record<string, unknown>) => void;
|
|
660
767
|
}
|
|
661
768
|
interface ApiToolSet {
|
|
@@ -664,6 +771,8 @@ interface ApiToolSet {
|
|
|
664
771
|
name: string;
|
|
665
772
|
error: string;
|
|
666
773
|
}>;
|
|
774
|
+
/** One entry per source that built successfully, for chat-facing visibility. */
|
|
775
|
+
apis: ApiToolInventory[];
|
|
667
776
|
}
|
|
668
777
|
/**
|
|
669
778
|
* Turn a tenant's OpenAPI document into agent tools.
|
|
@@ -680,14 +789,10 @@ interface OpenApiDoc {
|
|
|
680
789
|
}>;
|
|
681
790
|
paths?: Record<string, unknown>;
|
|
682
791
|
components?: Record<string, unknown>;
|
|
792
|
+
/** Document-wide default security requirement; each operation may override it. */
|
|
793
|
+
security?: unknown[];
|
|
683
794
|
}
|
|
684
795
|
declare function loadSpec(spec: string, fetchImpl?: typeof fetch): Promise<OpenApiDoc>;
|
|
685
|
-
/**
|
|
686
|
-
* `/projects/*` matches one path segment; `/projects/**` matches any depth and
|
|
687
|
-
* the base path itself, since a tenant writing that plainly means "this part of
|
|
688
|
-
* the API" rather than "everything under it but not it".
|
|
689
|
-
*/
|
|
690
|
-
declare function matchPath(glob: string, path: string): boolean;
|
|
691
796
|
|
|
692
797
|
interface CollectToolsOptions {
|
|
693
798
|
config: FrankieConfig;
|
|
@@ -695,6 +800,8 @@ interface CollectToolsOptions {
|
|
|
695
800
|
fetchImpl?: typeof fetch;
|
|
696
801
|
/** Base URL of the account API for the built-in workspaceAdmin tools. */
|
|
697
802
|
accountApiUrl?: string;
|
|
803
|
+
/** Browser channel for API sources marked `execution: "client"`. */
|
|
804
|
+
clientBridge?: ClientToolBridge;
|
|
698
805
|
onWarning?: (message: string, detail: Record<string, unknown>) => void;
|
|
699
806
|
}
|
|
700
807
|
interface CollectedTools {
|
|
@@ -704,6 +811,8 @@ interface CollectedTools {
|
|
|
704
811
|
name: string;
|
|
705
812
|
error: string;
|
|
706
813
|
}>;
|
|
814
|
+
/** OpenAPI sources that built successfully this turn, for chat-facing visibility. */
|
|
815
|
+
apis: ApiToolInventory[];
|
|
707
816
|
close(): Promise<void>;
|
|
708
817
|
}
|
|
709
818
|
/**
|
|
@@ -743,4 +852,4 @@ declare function buildForwardedHeaders(source: ForwardingConfig, session: ChatSe
|
|
|
743
852
|
*/
|
|
744
853
|
declare function buildSystemPrompt(config: FrankieConfig, session: ChatSession, availableTools?: string[]): string;
|
|
745
854
|
|
|
746
|
-
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,
|
|
855
|
+
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,21 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createControlApp
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-37VMCQCI.js";
|
|
4
4
|
import {
|
|
5
5
|
ConfigError,
|
|
6
|
-
MemoryTenantStore,
|
|
7
|
-
SingleTenantStore,
|
|
8
|
-
TenantRegistry,
|
|
9
6
|
applyEnvOverrides,
|
|
10
|
-
buildSystemPrompt,
|
|
11
|
-
collectTools,
|
|
12
|
-
createFrankieApp,
|
|
13
|
-
createMcpHostApp,
|
|
14
7
|
loadConfigFromFile,
|
|
15
8
|
loadResolvedConfig,
|
|
16
|
-
resolveSecrets
|
|
17
|
-
|
|
18
|
-
} from "./chunk-XCIM765D.js";
|
|
9
|
+
resolveSecrets
|
|
10
|
+
} from "./chunk-NF6IJVES.js";
|
|
19
11
|
import {
|
|
20
12
|
API_TOOL_PREFIX,
|
|
21
13
|
AuthError,
|
|
@@ -23,15 +15,22 @@ import {
|
|
|
23
15
|
MCP_TOOL_PREFIX,
|
|
24
16
|
MemoryRateLimiter,
|
|
25
17
|
MemorySessionStore,
|
|
18
|
+
MemoryTenantStore,
|
|
26
19
|
ModelResolutionError,
|
|
27
20
|
OpenApiError,
|
|
28
21
|
PostgresSessionStore,
|
|
29
22
|
PostgresTenantStore,
|
|
23
|
+
SingleTenantStore,
|
|
24
|
+
TenantRegistry,
|
|
30
25
|
TokenService,
|
|
31
26
|
anonymousUser,
|
|
32
27
|
buildApiTools,
|
|
33
28
|
buildForwardedHeaders,
|
|
29
|
+
buildSystemPrompt,
|
|
30
|
+
collectTools,
|
|
34
31
|
connectMcpTools,
|
|
32
|
+
createFrankieApp,
|
|
33
|
+
createMcpHostApp,
|
|
35
34
|
isOriginAllowed,
|
|
36
35
|
loadSpec,
|
|
37
36
|
matchPath,
|
|
@@ -40,9 +39,10 @@ import {
|
|
|
40
39
|
resolveModel,
|
|
41
40
|
resolveStores,
|
|
42
41
|
resolveTenantStore,
|
|
42
|
+
runTurn,
|
|
43
43
|
safeEqual,
|
|
44
44
|
toPublicTenant
|
|
45
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-MKN5663J.js";
|
|
46
46
|
export {
|
|
47
47
|
API_TOOL_PREFIX,
|
|
48
48
|
AuthError,
|
package/dist/server.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
ConfigError,
|
|
4
|
-
createFrankieApp,
|
|
5
4
|
loadResolvedConfig
|
|
6
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-NF6IJVES.js";
|
|
7
6
|
import {
|
|
8
7
|
ModelResolutionError,
|
|
8
|
+
createFrankieApp,
|
|
9
9
|
resolveStores
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-MKN5663J.js";
|
|
11
11
|
|
|
12
12
|
// src/server.ts
|
|
13
13
|
import { serve } from "@hono/node-server";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frankieai/server",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "Frankie cloud agent server — auth, sessions, MCP wiring and streaming for the embedded onboarding agent.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"yaml": "^2.9.0",
|
|
29
29
|
"zod": "^4.0.0",
|
|
30
30
|
"pg": "^8.13.0",
|
|
31
|
-
"@frankieai/core": "0.2.
|
|
31
|
+
"@frankieai/core": "0.2.3"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@types/node": "^22.10.0",
|
|
@@ -39,6 +39,9 @@
|
|
|
39
39
|
"pg-mem": "^3.0.5"
|
|
40
40
|
},
|
|
41
41
|
"module": "./dist/index.js",
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=20"
|
|
44
|
+
},
|
|
42
45
|
"scripts": {
|
|
43
46
|
"build": "tsup",
|
|
44
47
|
"dev": "tsup --watch",
|