@frankieai/core 0.1.0 → 0.1.2
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/index.cjs +233 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +165 -1
- package/dist/index.d.ts +165 -1
- package/dist/index.js +229 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -21,17 +21,21 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
agentConfigSchema: () => agentConfigSchema,
|
|
24
|
+
apiCapabilitySchema: () => apiCapabilitySchema,
|
|
24
25
|
apiSourceSchema: () => apiSourceSchema,
|
|
25
26
|
authConfigSchema: () => authConfigSchema,
|
|
26
27
|
brandingConfigSchema: () => brandingConfigSchema,
|
|
27
28
|
defineFrankieConfig: () => defineFrankieConfig,
|
|
28
29
|
describeTool: () => describeTool,
|
|
29
30
|
evaluateToolPolicy: () => evaluateToolPolicy,
|
|
31
|
+
featuresConfigSchema: () => featuresConfigSchema,
|
|
30
32
|
frankieConfigSchema: () => frankieConfigSchema,
|
|
31
33
|
limitsConfigSchema: () => limitsConfigSchema,
|
|
32
34
|
mcpServerSchema: () => mcpServerSchema,
|
|
35
|
+
modelChoiceSchema: () => modelChoiceSchema,
|
|
33
36
|
modelConfigSchema: () => modelConfigSchema,
|
|
34
37
|
parseFrankieConfig: () => parseFrankieConfig,
|
|
38
|
+
planHostedInstall: () => planHostedInstall,
|
|
35
39
|
thinkingLevelSchema: () => thinkingLevelSchema,
|
|
36
40
|
toolPolicySchema: () => toolPolicySchema,
|
|
37
41
|
widgetThemeSchema: () => widgetThemeSchema
|
|
@@ -78,8 +82,42 @@ var modelConfigSchema = import_zod.z.object({
|
|
|
78
82
|
/** Forward the model's reasoning stream to the client as thinking deltas. */
|
|
79
83
|
streamReasoning: import_zod.z.boolean().default(false)
|
|
80
84
|
});
|
|
85
|
+
var modelChoiceSchema = import_zod.z.object({
|
|
86
|
+
/** Provider model id, e.g. "accounts/fireworks/models/kimi-k2p7-code". */
|
|
87
|
+
id: import_zod.z.string().min(1),
|
|
88
|
+
/** Shown in the picker, e.g. "Fast" or "Smart". */
|
|
89
|
+
label: import_zod.z.string().min(1),
|
|
90
|
+
/** Defaults to the workspace's default provider when omitted. */
|
|
91
|
+
provider: import_zod.z.string().optional()
|
|
92
|
+
});
|
|
93
|
+
var featuresConfigSchema = import_zod.z.object({
|
|
94
|
+
/** Let end users attach files. Images go to a vision model; docs are read as text. */
|
|
95
|
+
attachments: import_zod.z.object({
|
|
96
|
+
enabled: import_zod.z.boolean().default(false),
|
|
97
|
+
/** Accepted MIME patterns, e.g. "image/*", "text/*", "application/json". */
|
|
98
|
+
accept: import_zod.z.array(import_zod.z.string()).default(["image/*", "text/*", "application/json"]),
|
|
99
|
+
maxMb: import_zod.z.number().positive().max(25).default(10),
|
|
100
|
+
maxCount: import_zod.z.number().int().positive().max(10).default(4)
|
|
101
|
+
}).prefault({}),
|
|
102
|
+
/** Let end users choose the model, from a curated list you control. */
|
|
103
|
+
modelPicker: import_zod.z.object({
|
|
104
|
+
enabled: import_zod.z.boolean().default(false),
|
|
105
|
+
/** The models offered to end users. The workspace default is always allowed. */
|
|
106
|
+
models: import_zod.z.array(modelChoiceSchema).default([])
|
|
107
|
+
}).prefault({}),
|
|
108
|
+
/**
|
|
109
|
+
* Platform onboarding: give the agent built-in tools to create and list the
|
|
110
|
+
* signed-in user's own Frankie workspaces. Only for a workspace that *is* the
|
|
111
|
+
* Frankie onboarding assistant — a normal customer never turns this on.
|
|
112
|
+
*/
|
|
113
|
+
workspaceAdmin: import_zod.z.boolean().default(false)
|
|
114
|
+
}).prefault({});
|
|
81
115
|
var mcpServerSchema = import_zod.z.object({
|
|
82
116
|
name: import_zod.z.string().min(1).regex(/^[a-z0-9][a-z0-9_-]*$/i, "MCP server names must be alphanumeric with - or _"),
|
|
117
|
+
enabled: import_zod.z.boolean().default(true),
|
|
118
|
+
/** Explicit tool selection. Omitted keeps legacy all-tools behavior; [] disables all. */
|
|
119
|
+
selectedTools: import_zod.z.array(import_zod.z.string().min(1)).optional(),
|
|
120
|
+
discoveredTools: import_zod.z.array(import_zod.z.object({ name: import_zod.z.string(), description: import_zod.z.string().optional() })).default([]),
|
|
83
121
|
/** `http` covers streamable HTTP; `sse` is the legacy transport. */
|
|
84
122
|
transport: import_zod.z.enum(["http", "sse"]).default("http"),
|
|
85
123
|
url: import_zod.z.string().url(),
|
|
@@ -94,6 +132,12 @@ var mcpServerSchema = import_zod.z.object({
|
|
|
94
132
|
/** Extra claims from the user token to forward as `x-frankie-<claim>`. */
|
|
95
133
|
forwardUserClaims: import_zod.z.array(import_zod.z.string()).default([])
|
|
96
134
|
});
|
|
135
|
+
var apiCapabilitySchema = import_zod.z.object({
|
|
136
|
+
operationId: import_zod.z.string().min(1),
|
|
137
|
+
method: import_zod.z.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]),
|
|
138
|
+
path: import_zod.z.string().min(1),
|
|
139
|
+
summary: import_zod.z.string().optional()
|
|
140
|
+
});
|
|
97
141
|
var apiSourceSchema = import_zod.z.object({
|
|
98
142
|
name: import_zod.z.string().min(1).regex(/^[a-z0-9][a-z0-9_-]*$/i, "API source names must be alphanumeric with - or _"),
|
|
99
143
|
/** URL or local path to an OpenAPI 3.x document (JSON or YAML). */
|
|
@@ -115,6 +159,13 @@ var apiSourceSchema = import_zod.z.object({
|
|
|
115
159
|
* deliberately, per API.
|
|
116
160
|
*/
|
|
117
161
|
methods: import_zod.z.array(import_zod.z.enum(["GET", "POST", "PUT", "PATCH", "DELETE"])).default(["GET"]),
|
|
162
|
+
enabled: import_zod.z.boolean().default(true),
|
|
163
|
+
/** Explicit operation selection, independent of the discovered inventory. */
|
|
164
|
+
selectedTools: import_zod.z.array(import_zod.z.string().min(1)).optional(),
|
|
165
|
+
/** Whether these approved API tools are also served by the workspace MCP. */
|
|
166
|
+
exposeAsMcp: import_zod.z.boolean().default(true),
|
|
167
|
+
/** A read-only inventory derived from the source for the dashboard. */
|
|
168
|
+
capabilities: import_zod.z.array(apiCapabilitySchema).default([]),
|
|
118
169
|
/** Static headers, e.g. a service token. */
|
|
119
170
|
headers: import_zod.z.record(import_zod.z.string(), import_zod.z.string()).default({}),
|
|
120
171
|
/**
|
|
@@ -203,7 +254,9 @@ var frankieConfigSchema = import_zod.z.object({
|
|
|
203
254
|
mcpServers: import_zod.z.array(mcpServerSchema).default([]),
|
|
204
255
|
/** APIs turned into tools from their OpenAPI document. */
|
|
205
256
|
apis: import_zod.z.array(apiSourceSchema).default([]),
|
|
206
|
-
limits: limitsConfigSchema.prefault({})
|
|
257
|
+
limits: limitsConfigSchema.prefault({}),
|
|
258
|
+
/** Opt-in end-user chat capabilities (attachments, model picker). */
|
|
259
|
+
features: featuresConfigSchema
|
|
207
260
|
});
|
|
208
261
|
function parseFrankieConfig(input) {
|
|
209
262
|
return frankieConfigSchema.parse(input);
|
|
@@ -250,20 +303,199 @@ function humanise(name) {
|
|
|
250
303
|
function truncate(value, max) {
|
|
251
304
|
return value.length <= max ? value : `${value.slice(0, max - 1)}\u2026`;
|
|
252
305
|
}
|
|
306
|
+
|
|
307
|
+
// src/install.ts
|
|
308
|
+
function planHostedInstall(input) {
|
|
309
|
+
const { files, workspace } = input;
|
|
310
|
+
const layouts = Object.keys(files).filter((p) => /(?:^|\/)(?:src\/)?app\/layout\.tsx$/.test(p));
|
|
311
|
+
const layoutPath = layouts.find((p) => /apps\/(dashboard|web)\//.test(p)) ?? layouts[0];
|
|
312
|
+
if (!layoutPath) throw new Error("Automatic installation supports Next.js App Router. No app/layout.tsx was found.");
|
|
313
|
+
const appRoot = layoutPath.slice(0, -"layout.tsx".length);
|
|
314
|
+
const root = appRoot.replace(/(?:src\/)?app\/$/, "");
|
|
315
|
+
const pkgPath = root + "package.json";
|
|
316
|
+
const pkg = JSON.parse(files[pkgPath] ?? "{}");
|
|
317
|
+
if (!pkg.dependencies?.next) throw new Error("The selected app must have Next.js in dependencies.");
|
|
318
|
+
const out = /* @__PURE__ */ new Map();
|
|
319
|
+
const notes = [];
|
|
320
|
+
const existingMount = files[layoutPath].includes("FrankieWidgetMount");
|
|
321
|
+
const sourceRoot = appRoot.endsWith("src/app/") ? root + "src/" : root;
|
|
322
|
+
const firebase = Boolean(files[sourceRoot + "lib/firebase-admin.ts"]?.includes("getAdminAuth") && files[sourceRoot + "lib/session.ts"]?.includes("SESSION_COOKIE"));
|
|
323
|
+
const authFile = files[sourceRoot + "auth.ts"];
|
|
324
|
+
const authJs = Boolean(authFile?.includes("NextAuth") && /export\s+(?:const|let)\s*\{[^}]*\bauth\b/.test(authFile ?? ""));
|
|
325
|
+
const tokenPath = appRoot + "api/frankie/token/route.ts";
|
|
326
|
+
const needsAuthSetup = !workspace.allowAnonymous && !files[tokenPath] && !firebase && !authJs;
|
|
327
|
+
if (needsAuthSetup) notes.push("Connect getCurrentUser in the generated token endpoint to your server session. It rejects requests until configured.");
|
|
328
|
+
pkg.dependencies = { ...pkg.dependencies, "@frankieai/react": pkg.dependencies["@frankieai/react"] ?? "^0.1.4", ...!workspace.allowAnonymous ? { jose: pkg.dependencies.jose ?? "^6.0.11" } : {} };
|
|
329
|
+
out.set(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
330
|
+
const authProp = workspace.allowAnonymous ? "" : " getUserToken={getUserToken}";
|
|
331
|
+
const shared = `import { FrankieProvider, FrankieWidget, FrankieChatPage } from "@frankieai/react";
|
|
332
|
+
import "@frankieai/react/styles.css";
|
|
333
|
+
`;
|
|
334
|
+
const getToken = workspace.allowAnonymous ? "" : `
|
|
335
|
+
async function getUserToken() {
|
|
336
|
+
const response = await fetch("/api/frankie/token", { method: "POST", credentials: "same-origin" });
|
|
337
|
+
if (!response.ok) return undefined;
|
|
338
|
+
return ((await response.json()) as { token?: string }).token;
|
|
339
|
+
}
|
|
340
|
+
`;
|
|
341
|
+
let priorChat;
|
|
342
|
+
try {
|
|
343
|
+
const previous = JSON.parse(files[root + "frankie.install.json"] ?? "{}");
|
|
344
|
+
if (previous.workspaceId === workspace.id && ["/chat", "/frankie/chat"].includes(previous.chatPath)) priorChat = previous.chatPath;
|
|
345
|
+
} catch {
|
|
346
|
+
}
|
|
347
|
+
const chatPath = priorChat ?? (files[appRoot + "chat/page.tsx"] ? "/frankie/chat" : "/chat");
|
|
348
|
+
if (!priorChat && files[appRoot + chatPath.slice(1) + "/page.tsx"]) throw new Error("Both /chat and /frankie/chat already exist. Choose a chat route manually.");
|
|
349
|
+
out.set(root + "frankie.install.json", JSON.stringify({ workspaceId: workspace.id, chatPath }, null, 2) + "\n");
|
|
350
|
+
if (chatPath !== "/chat") notes.push("Existing /chat preserved. Frankie chat is available at /frankie/chat.");
|
|
351
|
+
const mount = `"use client";
|
|
352
|
+
import { usePathname } from "next/navigation";
|
|
353
|
+
${shared}${getToken}
|
|
354
|
+
export function FrankieWidgetMount() {
|
|
355
|
+
const pathname = usePathname();
|
|
356
|
+
const key = process.env.NEXT_PUBLIC_FRANKIE_KEY;
|
|
357
|
+
if (!key || pathname === ${JSON.stringify(chatPath)}) return null;
|
|
358
|
+
return <FrankieProvider publishableKey={key}${authProp}><FrankieWidget /></FrankieProvider>;
|
|
359
|
+
}
|
|
360
|
+
export function WorkspaceChatPage() {
|
|
361
|
+
const key = process.env.NEXT_PUBLIC_FRANKIE_KEY;
|
|
362
|
+
if (!key) return <p>Chat is not configured. Set NEXT_PUBLIC_FRANKIE_KEY in your deployment.</p>;
|
|
363
|
+
return <FrankieChatPage publishableKey={key}${authProp} />;
|
|
364
|
+
}
|
|
365
|
+
`;
|
|
366
|
+
const moduleName = existingMount ? "FrankieSetupChat" : "FrankieWidgetMount";
|
|
367
|
+
out.set(appRoot + "frankie/" + moduleName + ".tsx", mount);
|
|
368
|
+
if (!existingMount) {
|
|
369
|
+
const layout = files[layoutPath];
|
|
370
|
+
if (!layout.includes("</body>")) throw new Error("The layout has no closing body. This custom layout needs manual widget placement.");
|
|
371
|
+
const importLine = `import { FrankieWidgetMount } from "./frankie/FrankieWidgetMount";
|
|
372
|
+
`;
|
|
373
|
+
const withImport = /^["']use client["'];/.test(layout) ? layout.replace(/^(['"]use client['"];)/, "$1\n" + importLine) : importLine + layout;
|
|
374
|
+
out.set(layoutPath, withImport.replace("</body>", " <FrankieWidgetMount />\n</body>"));
|
|
375
|
+
}
|
|
376
|
+
const pageImport = chatPath === "/chat" ? "../frankie/" : "../../frankie/";
|
|
377
|
+
out.set(appRoot + chatPath.slice(1) + "/page.tsx", `import { WorkspaceChatPage } from "${pageImport}${moduleName}";
|
|
378
|
+
export default WorkspaceChatPage;
|
|
379
|
+
`);
|
|
380
|
+
if (!workspace.allowAnonymous && !files[tokenPath]) {
|
|
381
|
+
const lookup = firebase ? `import { cookies } from "next/headers";
|
|
382
|
+
import { getAdminAuth } from "@/lib/firebase-admin";
|
|
383
|
+
import { SESSION_COOKIE } from "@/lib/session";
|
|
384
|
+
async function getCurrentUser() {
|
|
385
|
+
const cookie = (await cookies()).get(SESSION_COOKIE)?.value;
|
|
386
|
+
if (!cookie) return null;
|
|
387
|
+
const user = await getAdminAuth().verifySessionCookie(cookie, true);
|
|
388
|
+
return { id: user.uid, name: user.name, email: user.email };
|
|
389
|
+
}
|
|
390
|
+
` : authJs ? `import { auth } from "@/auth";
|
|
391
|
+
async function getCurrentUser() {
|
|
392
|
+
const session = await auth();
|
|
393
|
+
const user = session?.user as { id?: string; name?: string | null; email?: string | null } | undefined;
|
|
394
|
+
return user?.id ? user : null;
|
|
395
|
+
}
|
|
396
|
+
` : `// Replace with your app's verified server session lookup.
|
|
397
|
+
async function getCurrentUser(): Promise<{ id: string; name?: string; email?: string } | null> { return null; }
|
|
398
|
+
`;
|
|
399
|
+
out.set(tokenPath, `import { SignJWT } from "jose";
|
|
400
|
+
import { NextResponse } from "next/server";
|
|
401
|
+
${lookup}
|
|
402
|
+
export async function POST() {
|
|
403
|
+
const secret = process.env.FRANKIE_JWT_SECRET;
|
|
404
|
+
if (!secret) return NextResponse.json({ error: "Chat secret not configured" }, { status: 503 });
|
|
405
|
+
try {
|
|
406
|
+
const user = await getCurrentUser();
|
|
407
|
+
if (!user?.id) return NextResponse.json({ error: "Sign in required" }, { status: 401 });
|
|
408
|
+
const token = await new SignJWT({ name: user.name, email: user.email })
|
|
409
|
+
.setProtectedHeader({ alg: "HS256" }).setSubject(user.id).setIssuedAt()
|
|
410
|
+
.setExpirationTime("5m").sign(new TextEncoder().encode(secret));
|
|
411
|
+
return NextResponse.json({ token }, { headers: { "Cache-Control": "no-store" } });
|
|
412
|
+
} catch { return NextResponse.json({ error: "Sign in required" }, { status: 401 }); }
|
|
413
|
+
}
|
|
414
|
+
`);
|
|
415
|
+
}
|
|
416
|
+
let doc;
|
|
417
|
+
for (const [path, content] of Object.entries(files)) {
|
|
418
|
+
if (!/(?:openapi|swagger)[^/]*\.json$/i.test(path)) continue;
|
|
419
|
+
try {
|
|
420
|
+
const parsed = JSON.parse(content);
|
|
421
|
+
if (parsed.openapi && parsed.paths) {
|
|
422
|
+
doc = parsed;
|
|
423
|
+
break;
|
|
424
|
+
}
|
|
425
|
+
} catch {
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
const paths = {};
|
|
429
|
+
if (!doc) for (const [file, content] of Object.entries(files)) {
|
|
430
|
+
if (/\.(test|spec)\.[tj]sx?$/.test(file)) continue;
|
|
431
|
+
const nextRoute = file.match(/(?:^|\/)app(\/.*)\/route\.[tj]s$/);
|
|
432
|
+
if (nextRoute) {
|
|
433
|
+
const path = nextRoute[1].replace(/\/\([^/]+\)/g, "").replace(/\[([^\]]+)\]/g, "{$1}");
|
|
434
|
+
if (path.includes("...") || /\/(?:auth|frankie)(?:\/|$)/.test(path)) continue;
|
|
435
|
+
for (const method of ["GET", "POST", "PUT", "PATCH", "DELETE"]) {
|
|
436
|
+
if (new RegExp("export\\s+(?:(?:async\\s+)?function\\s+" + method + "\\b|(?:const|let)\\s+" + method + "\\s*=)").test(content))
|
|
437
|
+
addInstallOperation(paths, method, path, file);
|
|
438
|
+
}
|
|
439
|
+
} else if (/\.[tj]s$/.test(file) && /(?:routes?|api)\//.test(file)) {
|
|
440
|
+
for (const match of content.matchAll(/\b(?:app|router|fastify)\.(get|post|put|patch|delete)(?:\s*<[^;]{1,1600}?>)?\s*\(\s*["']([^"']+)["']/g))
|
|
441
|
+
addInstallOperation(paths, match[1].toUpperCase(), match[2].replace(/:([A-Za-z0-9_]+)/g, "{$1}"), file);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
doc ??= { openapi: "3.0.3", info: { title: workspace.name + " API", version: "1.0.0", description: "Generated route inventory. Review prefixes, schemas and authorization before enabling tools." }, servers: [{ url: input.apiBaseUrl ?? "/" }], paths };
|
|
445
|
+
const specPath = root + "public/frankie.openapi.json";
|
|
446
|
+
const operationCount = Object.values(doc.paths).reduce((n, item) => n + Object.keys(item).filter((m) => /^(get|post|put|patch|delete)$/.test(m)).length, 0);
|
|
447
|
+
out.set(specPath, JSON.stringify(doc, null, 2) + "\n");
|
|
448
|
+
if (!operationCount) notes.push("No supported API routes detected. The OpenAPI draft has no operations; add the real schema before connecting tools.");
|
|
449
|
+
notes.push("Review generated schemas and route mount prefixes before enabling operations.");
|
|
450
|
+
const envPath = root + ".env.example";
|
|
451
|
+
const env = (files[envPath] ?? "").replace(/^NEXT_PUBLIC_FRANKIE_KEY=.*\n?/gm, "").replace(/^FRANKIE_JWT_SECRET=.*\n?/gm, "").trimEnd();
|
|
452
|
+
out.set(envPath, env + "\nNEXT_PUBLIC_FRANKIE_KEY=" + workspace.publishableKey + "\n" + (workspace.allowAnonymous ? "" : "# Server only: use the secret from this workspace. Never commit its value.\nFRANKIE_JWT_SECRET=\n"));
|
|
453
|
+
out.set(root + "FRANKIE_SETUP.md", `# Frankie setup
|
|
454
|
+
|
|
455
|
+
Workspace: ${workspace.name}
|
|
456
|
+
|
|
457
|
+
1. Add NEXT_PUBLIC_FRANKIE_KEY from .env.example to the frontend deployment.
|
|
458
|
+
${workspace.allowAnonymous ? "" : "2. Add FRANKIE_JWT_SECRET from this workspace to the server hosting /api/frankie/token. Never commit the value.\n"}3. Install dependencies, update the lockfile, review and deploy the PR.
|
|
459
|
+
4. Open ${chatPath}. The generated floating widget hides on this chat page.
|
|
460
|
+
5. Connect https://YOUR_APP/frankie.openapi.json in Frankie \u2192 Tools & MCP. Choose Direct OpenAPI or OpenAPI + hosted MCP and select allowed operations.
|
|
461
|
+
6. Forwarding sends the Frankie-signed JWT. Your API must verify it and enforce user/resource permissions. It is not automatically interchangeable with a Firebase or OAuth access token.
|
|
462
|
+
7. Review conversations in Workspace \u2192 Chat sessions.
|
|
463
|
+
|
|
464
|
+
## Remaining setup
|
|
465
|
+
|
|
466
|
+
${notes.map((n) => "- " + n).join("\n")}
|
|
467
|
+
`);
|
|
468
|
+
return { files: [...out].map(([path, content]) => ({ path, content })), root, chatPath, specPath, operationCount, needsAuthSetup, notes };
|
|
469
|
+
}
|
|
470
|
+
function addInstallOperation(paths, method, path, file) {
|
|
471
|
+
if (path.includes("*") || !path.startsWith("/")) return;
|
|
472
|
+
(paths[path] ??= {})[method.toLowerCase()] = {
|
|
473
|
+
operationId: (method.toLowerCase() + "_" + path).replace(/[^a-zA-Z0-9_]+/g, "_"),
|
|
474
|
+
summary: method + " " + path,
|
|
475
|
+
description: "Detected in " + file + ". Review schemas before enabling.",
|
|
476
|
+
parameters: [...path.matchAll(/\{([^}]+)\}/g)].map((m) => ({ name: m[1], in: "path", required: true, schema: { type: "string" } })),
|
|
477
|
+
responses: { "200": { description: "Successful response" } },
|
|
478
|
+
"x-frankie-review-required": true
|
|
479
|
+
};
|
|
480
|
+
}
|
|
253
481
|
// Annotate the CommonJS export names for ESM import in node:
|
|
254
482
|
0 && (module.exports = {
|
|
255
483
|
agentConfigSchema,
|
|
484
|
+
apiCapabilitySchema,
|
|
256
485
|
apiSourceSchema,
|
|
257
486
|
authConfigSchema,
|
|
258
487
|
brandingConfigSchema,
|
|
259
488
|
defineFrankieConfig,
|
|
260
489
|
describeTool,
|
|
261
490
|
evaluateToolPolicy,
|
|
491
|
+
featuresConfigSchema,
|
|
262
492
|
frankieConfigSchema,
|
|
263
493
|
limitsConfigSchema,
|
|
264
494
|
mcpServerSchema,
|
|
495
|
+
modelChoiceSchema,
|
|
265
496
|
modelConfigSchema,
|
|
266
497
|
parseFrankieConfig,
|
|
498
|
+
planHostedInstall,
|
|
267
499
|
thinkingLevelSchema,
|
|
268
500
|
toolPolicySchema,
|
|
269
501
|
widgetThemeSchema
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/config.ts","../src/tools.ts"],"sourcesContent":["export * from \"./config.js\";\nexport * from \"./protocol.js\";\nexport * from \"./tools.js\";\n","import { z } from \"zod\";\n\n/**\n * Tenant configuration. One config == one embedded agent. Everything a\n * customer needs to change to make Frankie theirs lives here, so the code\n * itself stays generic.\n */\n\nexport const widgetThemeSchema = z\n .object({\n primary: z.string().optional(),\n primaryForeground: z.string().optional(),\n background: z.string().optional(),\n foreground: z.string().optional(),\n muted: z.string().optional(),\n border: z.string().optional(),\n radius: z.string().optional(),\n fontFamily: z.string().optional(),\n mode: z.enum([\"light\", \"dark\", \"system\"]).default(\"system\"),\n })\n .prefault({});\n\nexport const thinkingLevelSchema = z.enum([\"minimal\", \"low\", \"medium\", \"high\", \"xhigh\", \"max\"]);\n\nexport const modelConfigSchema = z.object({\n /**\n * Any provider the model layer knows about. `fireworks` is the default;\n * `anthropic`, `openai`, `google`, `groq`, `together`, `openrouter` and\n * ~30 others resolve too, which is what makes the model a config choice.\n */\n provider: z.string().min(1).default(\"fireworks\"),\n /**\n * Model id within that provider. The default is Kimi K2.7 on Fireworks,\n * which serves a native Anthropic-Messages endpoint and a 262K window.\n */\n model: z.string().default(\"accounts/fireworks/models/kimi-k2p7-code\"),\n /** Env var holding the provider key. Never put the key in config itself. */\n apiKeyEnv: z.string().default(\"FIREWORKS_API_KEY\"),\n /** Override the provider's base URL (self-hosted gateways, tests). */\n baseUrl: z.string().url().optional(),\n maxTokens: z.number().int().positive().max(200_000).default(8192),\n temperature: z.number().min(0).max(2).default(0.6),\n /**\n * Reasoning depth. Lowest by default: an onboarding widget is latency-\n * sensitive and its questions rarely need deliberation.\n */\n thinkingLevel: thinkingLevelSchema.default(\"minimal\"),\n /** Forward the model's reasoning stream to the client as thinking deltas. */\n streamReasoning: z.boolean().default(false),\n});\n\nexport const mcpServerSchema = z.object({\n name: z\n .string()\n .min(1)\n .regex(/^[a-z0-9][a-z0-9_-]*$/i, \"MCP server names must be alphanumeric with - or _\"),\n /** `http` covers streamable HTTP; `sse` is the legacy transport. */\n transport: z.enum([\"http\", \"sse\"]).default(\"http\"),\n url: z.string().url(),\n /** Static headers, e.g. a service token for the company's own MCP server. */\n headers: z.record(z.string(), z.string()).default({}),\n /**\n * When set, the verified end-user JWT is forwarded to the MCP server in this\n * header so the company's tools can act as that user. Use with care: only\n * enable it for MCP servers you control.\n */\n forwardUserTokenAs: z.string().optional(),\n /** Extra claims from the user token to forward as `x-frankie-<claim>`. */\n forwardUserClaims: z.array(z.string()).default([]),\n});\n\n/**\n * An HTTP API described by an OpenAPI document, turned into tools directly.\n *\n * This is the \"no MCP server to write\" path: point Frankie at a spec, name the\n * operations the agent may use, and it builds the tools at startup.\n */\nexport const apiSourceSchema = z.object({\n name: z\n .string()\n .min(1)\n .regex(/^[a-z0-9][a-z0-9_-]*$/i, \"API source names must be alphanumeric with - or _\"),\n /** URL or local path to an OpenAPI 3.x document (JSON or YAML). */\n spec: z.string().min(1),\n /** Overrides the spec's own `servers[0].url`. Required if the spec has none. */\n baseUrl: z.string().url().optional(),\n /**\n * Operation ids the agent may call. Empty means \"every operation that passes\n * the other filters\" — fine for a small internal API, risky for a large one.\n */\n operations: z.array(z.string()).default([]),\n /**\n * Path globs to include, e.g. `/projects/**`. Empty means all paths.\n */\n includePaths: z.array(z.string()).default([]),\n /**\n * HTTP methods the agent may use. Read-only by default: an onboarding agent\n * wired to a whole API should not be able to DELETE on its first day. Widen\n * deliberately, per API.\n */\n methods: z.array(z.enum([\"GET\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\"])).default([\"GET\"]),\n /** Static headers, e.g. a service token. */\n headers: z.record(z.string(), z.string()).default({}),\n /**\n * When set, the verified end-user JWT is forwarded in this header, so your\n * API authorises the call exactly as it would any other request from them.\n */\n forwardUserTokenAs: z.string().optional(),\n /** Extra claims from the user token to forward as `x-frankie-<claim>`. */\n forwardUserClaims: z.array(z.string()).default([]),\n /** Per-call timeout. */\n timeoutMs: z.number().int().positive().default(30_000),\n /** Response bodies longer than this are truncated before the model sees them. */\n maxResponseChars: z.number().int().positive().default(8_000),\n});\n\nexport const toolPolicySchema = z.object({\n /**\n * Tool names the agent may call without asking. MCP tools are named\n * `mcp__<server>__<tool>` and generated API tools `api__<name>__<operation>`;\n * naming the prefix alone (`mcp__acme`, `api__acme`) allows all of them.\n */\n allow: z.array(z.string()).default([]),\n /** Explicit denials; evaluated before `allow`. */\n deny: z.array(z.string()).default([]),\n /**\n * Deny anything not in `allow`. On by default — an onboarding agent living\n * on a public marketing page should not have an open tool surface.\n */\n denyByDefault: z.boolean().default(true),\n});\n\nexport const agentConfigSchema = z.object({\n /** Prepended to the runtime's own operating instructions. */\n systemPrompt: z.string().default(\"\"),\n /** First message the widget shows before the user types anything. */\n greeting: z.string().default(\"Hi! I can walk you through getting set up. What are you trying to do?\"),\n suggestedPrompts: z.array(z.string()).max(6).default([]),\n maxTurns: z.number().int().positive().max(50).default(12),\n /** Hard stop on estimated spend per session. */\n maxBudgetUsd: z.number().positive().optional(),\n tools: toolPolicySchema.prefault({}),\n /** Show tool activity in the transcript. */\n showToolActivity: z.boolean().default(true),\n});\n\nexport const authConfigSchema = z.object({\n /**\n * Safe to ship in client bundles. Identifies the tenant; it authorises\n * nothing on its own — the origin allowlist and the user token do that.\n */\n publishableKey: z.string().min(8),\n /** Exact origins allowed to open sessions, e.g. `https://app.acme.com`. */\n allowedOrigins: z.array(z.string()).default([]),\n /** Let visitors chat without a host-signed token (marketing pages). */\n allowAnonymous: z.boolean().default(false),\n /** Env var holding the HS256 secret the host signs user tokens with. */\n jwtSecretEnv: z.string().default(\"FRANKIE_JWT_SECRET\"),\n /** Verify against a JWKS endpoint instead of a shared secret (RS/ES256). */\n jwksUrl: z.string().url().optional(),\n jwtIssuer: z.string().optional(),\n jwtAudience: z.string().optional(),\n /** How long an issued session token is good for. */\n sessionTtlSeconds: z.number().int().positive().default(60 * 60),\n /** Env var holding the secret used to sign Frankie's own session tokens. */\n sessionSecretEnv: z.string().default(\"FRANKIE_SESSION_SECRET\"),\n});\n\nexport const limitsConfigSchema = z.object({\n /** Messages per session per window. */\n messagesPerWindow: z.number().int().positive().default(30),\n windowSeconds: z.number().int().positive().default(15 * 60),\n /** Sessions a single IP may open per window. */\n sessionsPerIpPerWindow: z.number().int().positive().default(20),\n /** Longest a single turn may run before it is aborted. */\n turnTimeoutMs: z.number().int().positive().default(120_000),\n});\n\nexport const brandingConfigSchema = z.object({\n name: z.string().default(\"Assistant\"),\n tagline: z.string().optional(),\n avatarUrl: z.string().url().optional(),\n launcherLabel: z.string().default(\"Need a hand?\"),\n});\n\nexport const frankieConfigSchema = z.object({\n tenantId: z.string().min(1),\n branding: brandingConfigSchema.prefault({}),\n theme: widgetThemeSchema,\n model: modelConfigSchema.prefault({}),\n agent: agentConfigSchema.prefault({}),\n auth: authConfigSchema,\n mcpServers: z.array(mcpServerSchema).default([]),\n /** APIs turned into tools from their OpenAPI document. */\n apis: z.array(apiSourceSchema).default([]),\n limits: limitsConfigSchema.prefault({}),\n});\n\nexport type ThinkingLevel = z.infer<typeof thinkingLevelSchema>;\nexport type WidgetThemeConfig = z.infer<typeof widgetThemeSchema>;\nexport type ModelConfig = z.infer<typeof modelConfigSchema>;\nexport type McpServerConfig = z.infer<typeof mcpServerSchema>;\nexport type ApiSourceConfig = z.infer<typeof apiSourceSchema>;\nexport type ToolPolicy = z.infer<typeof toolPolicySchema>;\nexport type AgentConfig = z.infer<typeof agentConfigSchema>;\nexport type AuthConfig = z.infer<typeof authConfigSchema>;\nexport type LimitsConfig = z.infer<typeof limitsConfigSchema>;\nexport type BrandingConfig = z.infer<typeof brandingConfigSchema>;\nexport type FrankieConfig = z.infer<typeof frankieConfigSchema>;\nexport type FrankieConfigInput = z.input<typeof frankieConfigSchema>;\n\nexport function parseFrankieConfig(input: unknown): FrankieConfig {\n return frankieConfigSchema.parse(input);\n}\n\n/** Helper for `frankie.config.ts` files — gives editors full completion. */\nexport function defineFrankieConfig(config: FrankieConfigInput): FrankieConfigInput {\n return config;\n}\n","import type { ToolPolicy } from \"./config.js\";\n\nexport interface ToolDecision {\n allowed: boolean;\n reason: string;\n}\n\n/**\n * Decide whether the agent may run a tool.\n *\n * Patterns are matched exactly, or as a prefix when they end in `*`. MCP tools\n * arrive as `mcp__<server>__<tool>`, so listing `mcp__acme` (no trailing star)\n * also allows every tool on the `acme` server.\n */\nexport function evaluateToolPolicy(toolName: string, policy: ToolPolicy): ToolDecision {\n if (matchesAny(toolName, policy.deny)) {\n return { allowed: false, reason: `\"${toolName}\" is on the deny list for this agent.` };\n }\n if (matchesAny(toolName, policy.allow)) {\n return { allowed: true, reason: \"allowed by policy\" };\n }\n if (policy.denyByDefault) {\n return { allowed: false, reason: `\"${toolName}\" is not in this agent's allow list.` };\n }\n return { allowed: true, reason: \"no policy match; default allow\" };\n}\n\nfunction matchesAny(toolName: string, patterns: readonly string[]): boolean {\n return patterns.some((pattern) => matches(toolName, pattern));\n}\n\nfunction matches(toolName: string, pattern: string): boolean {\n if (pattern === \"*\") return true;\n if (pattern.endsWith(\"*\")) return toolName.startsWith(pattern.slice(0, -1));\n if (toolName === pattern) return true;\n // `mcp__acme` covers `mcp__acme__create_workspace`.\n return toolName.startsWith(`${pattern}__`);\n}\n\n/** A short human label for a tool call, for the \"Frankie is …\" line in the UI. */\nexport function describeTool(toolName: string, input: unknown): string {\n const mcp = /^mcp__([^_]+(?:_[^_]+)*)__(.+)$/.exec(toolName);\n if (mcp) {\n const [, server, tool] = mcp;\n return `${humanise(tool ?? toolName)} · ${server}`;\n }\n const inputRecord = typeof input === \"object\" && input !== null ? (input as Record<string, unknown>) : {};\n const hint = inputRecord[\"path\"] ?? inputRecord[\"url\"] ?? inputRecord[\"query\"] ?? inputRecord[\"command\"];\n return typeof hint === \"string\" ? `${humanise(toolName)}: ${truncate(hint, 60)}` : humanise(toolName);\n}\n\nfunction humanise(name: string): string {\n return name\n .replace(/[_-]+/g, \" \")\n .replace(/([a-z0-9])([A-Z])/g, \"$1 $2\")\n .replace(/\\b\\w/g, (c) => c.toUpperCase())\n .trim();\n}\n\nfunction truncate(value: string, max: number): string {\n return value.length <= max ? value : `${value.slice(0, max - 1)}…`;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/config.ts","../src/tools.ts","../src/install.ts"],"sourcesContent":["export * from \"./config.js\";\nexport * from \"./protocol.js\";\nexport * from \"./tools.js\";\n\nexport * from \"./install.js\";\n","import { z } from \"zod\";\n\n/**\n * Tenant configuration. One config == one embedded agent. Everything a\n * customer needs to change to make Frankie theirs lives here, so the code\n * itself stays generic.\n */\n\nexport const widgetThemeSchema = z\n .object({\n primary: z.string().optional(),\n primaryForeground: z.string().optional(),\n background: z.string().optional(),\n foreground: z.string().optional(),\n muted: z.string().optional(),\n border: z.string().optional(),\n radius: z.string().optional(),\n fontFamily: z.string().optional(),\n mode: z.enum([\"light\", \"dark\", \"system\"]).default(\"system\"),\n })\n .prefault({});\n\nexport const thinkingLevelSchema = z.enum([\"minimal\", \"low\", \"medium\", \"high\", \"xhigh\", \"max\"]);\n\nexport const modelConfigSchema = z.object({\n /**\n * Any provider the model layer knows about. `fireworks` is the default;\n * `anthropic`, `openai`, `google`, `groq`, `together`, `openrouter` and\n * ~30 others resolve too, which is what makes the model a config choice.\n */\n provider: z.string().min(1).default(\"fireworks\"),\n /**\n * Model id within that provider. The default is Kimi K2.7 on Fireworks,\n * which serves a native Anthropic-Messages endpoint and a 262K window.\n */\n model: z.string().default(\"accounts/fireworks/models/kimi-k2p7-code\"),\n /** Env var holding the provider key. Never put the key in config itself. */\n apiKeyEnv: z.string().default(\"FIREWORKS_API_KEY\"),\n /** Override the provider's base URL (self-hosted gateways, tests). */\n baseUrl: z.string().url().optional(),\n maxTokens: z.number().int().positive().max(200_000).default(8192),\n temperature: z.number().min(0).max(2).default(0.6),\n /**\n * Reasoning depth. Lowest by default: an onboarding widget is latency-\n * sensitive and its questions rarely need deliberation.\n */\n thinkingLevel: thinkingLevelSchema.default(\"minimal\"),\n /** Forward the model's reasoning stream to the client as thinking deltas. */\n streamReasoning: z.boolean().default(false),\n});\n\n/** One selectable model in the (optional) end-user model picker. */\nexport const modelChoiceSchema = z.object({\n /** Provider model id, e.g. \"accounts/fireworks/models/kimi-k2p7-code\". */\n id: z.string().min(1),\n /** Shown in the picker, e.g. \"Fast\" or \"Smart\". */\n label: z.string().min(1),\n /** Defaults to the workspace's default provider when omitted. */\n provider: z.string().optional(),\n});\n\n/**\n * Optional, opt-in chat capabilities the SDK consumer turns on per workspace.\n * Everything here is OFF by default: an embedded agent only gets a wider surface\n * when the customer deliberately enables it.\n */\nexport const featuresConfigSchema = z\n .object({\n /** Let end users attach files. Images go to a vision model; docs are read as text. */\n attachments: z\n .object({\n enabled: z.boolean().default(false),\n /** Accepted MIME patterns, e.g. \"image/*\", \"text/*\", \"application/json\". */\n accept: z.array(z.string()).default([\"image/*\", \"text/*\", \"application/json\"]),\n maxMb: z.number().positive().max(25).default(10),\n maxCount: z.number().int().positive().max(10).default(4),\n })\n .prefault({}),\n /** Let end users choose the model, from a curated list you control. */\n modelPicker: z\n .object({\n enabled: z.boolean().default(false),\n /** The models offered to end users. The workspace default is always allowed. */\n models: z.array(modelChoiceSchema).default([]),\n })\n .prefault({}),\n /**\n * Platform onboarding: give the agent built-in tools to create and list the\n * signed-in user's own Frankie workspaces. Only for a workspace that *is* the\n * Frankie onboarding assistant — a normal customer never turns this on.\n */\n workspaceAdmin: z.boolean().default(false),\n })\n .prefault({});\n\nexport const mcpServerSchema = z.object({\n name: z\n .string()\n .min(1)\n .regex(/^[a-z0-9][a-z0-9_-]*$/i, \"MCP server names must be alphanumeric with - or _\"),\n enabled: z.boolean().default(true),\n /** Explicit tool selection. Omitted keeps legacy all-tools behavior; [] disables all. */\n selectedTools: z.array(z.string().min(1)).optional(),\n discoveredTools: z.array(z.object({ name: z.string(), description: z.string().optional() })).default([]),\n /** `http` covers streamable HTTP; `sse` is the legacy transport. */\n transport: z.enum([\"http\", \"sse\"]).default(\"http\"),\n url: z.string().url(),\n /** Static headers, e.g. a service token for the company's own MCP server. */\n headers: z.record(z.string(), z.string()).default({}),\n /**\n * When set, the verified end-user JWT is forwarded to the MCP server in this\n * header so the company's tools can act as that user. Use with care: only\n * enable it for MCP servers you control.\n */\n forwardUserTokenAs: z.string().optional(),\n /** Extra claims from the user token to forward as `x-frankie-<claim>`. */\n forwardUserClaims: z.array(z.string()).default([]),\n});\n\n/**\n * An HTTP API described by an OpenAPI document, turned into tools directly.\n *\n * This is the \"no MCP server to write\" path: point Frankie at a spec, name the\n * operations the agent may use, and it builds the tools at startup.\n */\nexport const apiCapabilitySchema = z.object({\n operationId: z.string().min(1),\n method: z.enum([\"GET\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\"]),\n path: z.string().min(1),\n summary: z.string().optional(),\n});\n\nexport const apiSourceSchema = z.object({\n name: z\n .string()\n .min(1)\n .regex(/^[a-z0-9][a-z0-9_-]*$/i, \"API source names must be alphanumeric with - or _\"),\n /** URL or local path to an OpenAPI 3.x document (JSON or YAML). */\n spec: z.string().min(1),\n /** Overrides the spec's own `servers[0].url`. Required if the spec has none. */\n baseUrl: z.string().url().optional(),\n /**\n * Operation ids the agent may call. Empty means \"every operation that passes\n * the other filters\" — fine for a small internal API, risky for a large one.\n */\n operations: z.array(z.string()).default([]),\n /**\n * Path globs to include, e.g. `/projects/**`. Empty means all paths.\n */\n includePaths: z.array(z.string()).default([]),\n /**\n * HTTP methods the agent may use. Read-only by default: an onboarding agent\n * wired to a whole API should not be able to DELETE on its first day. Widen\n * deliberately, per API.\n */\n methods: z.array(z.enum([\"GET\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\"])).default([\"GET\"]),\n enabled: z.boolean().default(true),\n /** Explicit operation selection, independent of the discovered inventory. */\n selectedTools: z.array(z.string().min(1)).optional(),\n /** Whether these approved API tools are also served by the workspace MCP. */\n exposeAsMcp: z.boolean().default(true),\n /** A read-only inventory derived from the source for the dashboard. */\n capabilities: z.array(apiCapabilitySchema).default([]),\n /** Static headers, e.g. a service token. */\n headers: z.record(z.string(), z.string()).default({}),\n /**\n * When set, the verified end-user JWT is forwarded in this header, so your\n * API authorises the call exactly as it would any other request from them.\n */\n forwardUserTokenAs: z.string().optional(),\n /** Extra claims from the user token to forward as `x-frankie-<claim>`. */\n forwardUserClaims: z.array(z.string()).default([]),\n /** Per-call timeout. */\n timeoutMs: z.number().int().positive().default(30_000),\n /** Response bodies longer than this are truncated before the model sees them. */\n maxResponseChars: z.number().int().positive().default(8_000),\n});\n\nexport const toolPolicySchema = z.object({\n /**\n * Tool names the agent may call without asking. MCP tools are named\n * `mcp__<server>__<tool>` and generated API tools `api__<name>__<operation>`;\n * naming the prefix alone (`mcp__acme`, `api__acme`) allows all of them.\n */\n allow: z.array(z.string()).default([]),\n /** Explicit denials; evaluated before `allow`. */\n deny: z.array(z.string()).default([]),\n /**\n * Deny anything not in `allow`. On by default — an onboarding agent living\n * on a public marketing page should not have an open tool surface.\n */\n denyByDefault: z.boolean().default(true),\n});\n\nexport const agentConfigSchema = z.object({\n /** Prepended to the runtime's own operating instructions. */\n systemPrompt: z.string().default(\"\"),\n /** First message the widget shows before the user types anything. */\n greeting: z.string().default(\"Hi! I can walk you through getting set up. What are you trying to do?\"),\n suggestedPrompts: z.array(z.string()).max(6).default([]),\n maxTurns: z.number().int().positive().max(50).default(12),\n /** Hard stop on estimated spend per session. */\n maxBudgetUsd: z.number().positive().optional(),\n tools: toolPolicySchema.prefault({}),\n /** Show tool activity in the transcript. */\n showToolActivity: z.boolean().default(true),\n});\n\nexport const authConfigSchema = z.object({\n /**\n * Safe to ship in client bundles. Identifies the tenant; it authorises\n * nothing on its own — the origin allowlist and the user token do that.\n */\n publishableKey: z.string().min(8),\n /** Exact origins allowed to open sessions, e.g. `https://app.acme.com`. */\n allowedOrigins: z.array(z.string()).default([]),\n /** Let visitors chat without a host-signed token (marketing pages). */\n allowAnonymous: z.boolean().default(false),\n /** Env var holding the HS256 secret the host signs user tokens with. */\n jwtSecretEnv: z.string().default(\"FRANKIE_JWT_SECRET\"),\n /** Verify against a JWKS endpoint instead of a shared secret (RS/ES256). */\n jwksUrl: z.string().url().optional(),\n jwtIssuer: z.string().optional(),\n jwtAudience: z.string().optional(),\n /** How long an issued session token is good for. */\n sessionTtlSeconds: z.number().int().positive().default(60 * 60),\n /** Env var holding the secret used to sign Frankie's own session tokens. */\n sessionSecretEnv: z.string().default(\"FRANKIE_SESSION_SECRET\"),\n});\n\nexport const limitsConfigSchema = z.object({\n /** Messages per session per window. */\n messagesPerWindow: z.number().int().positive().default(30),\n windowSeconds: z.number().int().positive().default(15 * 60),\n /** Sessions a single IP may open per window. */\n sessionsPerIpPerWindow: z.number().int().positive().default(20),\n /** Longest a single turn may run before it is aborted. */\n turnTimeoutMs: z.number().int().positive().default(120_000),\n});\n\nexport const brandingConfigSchema = z.object({\n name: z.string().default(\"Assistant\"),\n tagline: z.string().optional(),\n avatarUrl: z.string().url().optional(),\n launcherLabel: z.string().default(\"Need a hand?\"),\n});\n\nexport const frankieConfigSchema = z.object({\n tenantId: z.string().min(1),\n branding: brandingConfigSchema.prefault({}),\n theme: widgetThemeSchema,\n model: modelConfigSchema.prefault({}),\n agent: agentConfigSchema.prefault({}),\n auth: authConfigSchema,\n mcpServers: z.array(mcpServerSchema).default([]),\n /** APIs turned into tools from their OpenAPI document. */\n apis: z.array(apiSourceSchema).default([]),\n limits: limitsConfigSchema.prefault({}),\n /** Opt-in end-user chat capabilities (attachments, model picker). */\n features: featuresConfigSchema,\n});\n\nexport type ThinkingLevel = z.infer<typeof thinkingLevelSchema>;\nexport type WidgetThemeConfig = z.infer<typeof widgetThemeSchema>;\nexport type ModelChoice = z.infer<typeof modelChoiceSchema>;\nexport type FeaturesConfig = z.infer<typeof featuresConfigSchema>;\nexport type ModelConfig = z.infer<typeof modelConfigSchema>;\nexport type McpServerConfig = z.infer<typeof mcpServerSchema>;\nexport type ApiCapability = z.infer<typeof apiCapabilitySchema>;\nexport type ApiSourceConfig = z.infer<typeof apiSourceSchema>;\nexport type ToolPolicy = z.infer<typeof toolPolicySchema>;\nexport type AgentConfig = z.infer<typeof agentConfigSchema>;\nexport type AuthConfig = z.infer<typeof authConfigSchema>;\nexport type LimitsConfig = z.infer<typeof limitsConfigSchema>;\nexport type BrandingConfig = z.infer<typeof brandingConfigSchema>;\nexport type FrankieConfig = z.infer<typeof frankieConfigSchema>;\nexport type FrankieConfigInput = z.input<typeof frankieConfigSchema>;\n\nexport function parseFrankieConfig(input: unknown): FrankieConfig {\n return frankieConfigSchema.parse(input);\n}\n\n/** Helper for `frankie.config.ts` files — gives editors full completion. */\nexport function defineFrankieConfig(config: FrankieConfigInput): FrankieConfigInput {\n return config;\n}\n","import type { ToolPolicy } from \"./config.js\";\n\nexport interface ToolDecision {\n allowed: boolean;\n reason: string;\n}\n\n/**\n * Decide whether the agent may run a tool.\n *\n * Patterns are matched exactly, or as a prefix when they end in `*`. MCP tools\n * arrive as `mcp__<server>__<tool>`, so listing `mcp__acme` (no trailing star)\n * also allows every tool on the `acme` server.\n */\nexport function evaluateToolPolicy(toolName: string, policy: ToolPolicy): ToolDecision {\n if (matchesAny(toolName, policy.deny)) {\n return { allowed: false, reason: `\"${toolName}\" is on the deny list for this agent.` };\n }\n if (matchesAny(toolName, policy.allow)) {\n return { allowed: true, reason: \"allowed by policy\" };\n }\n if (policy.denyByDefault) {\n return { allowed: false, reason: `\"${toolName}\" is not in this agent's allow list.` };\n }\n return { allowed: true, reason: \"no policy match; default allow\" };\n}\n\nfunction matchesAny(toolName: string, patterns: readonly string[]): boolean {\n return patterns.some((pattern) => matches(toolName, pattern));\n}\n\nfunction matches(toolName: string, pattern: string): boolean {\n if (pattern === \"*\") return true;\n if (pattern.endsWith(\"*\")) return toolName.startsWith(pattern.slice(0, -1));\n if (toolName === pattern) return true;\n // `mcp__acme` covers `mcp__acme__create_workspace`.\n return toolName.startsWith(`${pattern}__`);\n}\n\n/** A short human label for a tool call, for the \"Frankie is …\" line in the UI. */\nexport function describeTool(toolName: string, input: unknown): string {\n const mcp = /^mcp__([^_]+(?:_[^_]+)*)__(.+)$/.exec(toolName);\n if (mcp) {\n const [, server, tool] = mcp;\n return `${humanise(tool ?? toolName)} · ${server}`;\n }\n const inputRecord = typeof input === \"object\" && input !== null ? (input as Record<string, unknown>) : {};\n const hint = inputRecord[\"path\"] ?? inputRecord[\"url\"] ?? inputRecord[\"query\"] ?? inputRecord[\"command\"];\n return typeof hint === \"string\" ? `${humanise(toolName)}: ${truncate(hint, 60)}` : humanise(toolName);\n}\n\nfunction humanise(name: string): string {\n return name\n .replace(/[_-]+/g, \" \")\n .replace(/([a-z0-9])([A-Z])/g, \"$1 $2\")\n .replace(/\\b\\w/g, (c) => c.toUpperCase())\n .trim();\n}\n\nfunction truncate(value: string, max: number): string {\n return value.length <= max ? value : `${value.slice(0, max - 1)}…`;\n}\n","/** One installation plan for GitHub and the local CLI. Never includes credentials. */\nexport interface InstallProject {\n files: Record<string, string>;\n workspace: { id: string; name: string; publishableKey: string; allowAnonymous: boolean };\n apiBaseUrl?: string;\n}\nexport interface InstallPlan {\n files: Array<{ path: string; content: string }>; root: string; chatPath: string; specPath: string;\n operationCount: number; needsAuthSetup: boolean; notes: string[];\n}\ntype ApiDocument = { paths: Record<string, Record<string, unknown>>; [key: string]: unknown };\nexport function planHostedInstall(input: InstallProject): InstallPlan {\n const { files, workspace } = input;\n const layouts = Object.keys(files).filter((p) => /(?:^|\\/)(?:src\\/)?app\\/layout\\.tsx$/.test(p));\n const layoutPath = layouts.find((p) => /apps\\/(dashboard|web)\\//.test(p)) ?? layouts[0];\n if (!layoutPath) throw new Error(\"Automatic installation supports Next.js App Router. No app/layout.tsx was found.\");\n const appRoot = layoutPath.slice(0, -\"layout.tsx\".length);\n const root = appRoot.replace(/(?:src\\/)?app\\/$/, \"\");\n const pkgPath = root + \"package.json\";\n const pkg = JSON.parse(files[pkgPath] ?? \"{}\");\n if (!pkg.dependencies?.next) throw new Error(\"The selected app must have Next.js in dependencies.\");\n const out = new Map<string, string>();\n const notes: string[] = [];\n const existingMount = files[layoutPath]!.includes(\"FrankieWidgetMount\");\n const sourceRoot = appRoot.endsWith(\"src/app/\") ? root + \"src/\" : root;\n const firebase = Boolean(files[sourceRoot + \"lib/firebase-admin.ts\"]?.includes(\"getAdminAuth\") && files[sourceRoot + \"lib/session.ts\"]?.includes(\"SESSION_COOKIE\"));\n const authFile = files[sourceRoot + \"auth.ts\"];\n const authJs = Boolean(authFile?.includes(\"NextAuth\") && /export\\s+(?:const|let)\\s*\\{[^}]*\\bauth\\b/.test(authFile ?? \"\"));\n const tokenPath = appRoot + \"api/frankie/token/route.ts\";\n const needsAuthSetup = !workspace.allowAnonymous && !files[tokenPath] && !firebase && !authJs;\n if (needsAuthSetup) notes.push(\"Connect getCurrentUser in the generated token endpoint to your server session. It rejects requests until configured.\");\n pkg.dependencies = { ...pkg.dependencies, \"@frankieai/react\": pkg.dependencies[\"@frankieai/react\"] ?? \"^0.1.4\", ...(!workspace.allowAnonymous ? { jose: pkg.dependencies.jose ?? \"^6.0.11\" } : {}) };\n out.set(pkgPath, JSON.stringify(pkg, null, 2) + \"\\n\");\n const authProp = workspace.allowAnonymous ? \"\" : \" getUserToken={getUserToken}\";\n const shared = `import { FrankieProvider, FrankieWidget, FrankieChatPage } from \"@frankieai/react\";\\nimport \"@frankieai/react/styles.css\";\\n`;\n const getToken = workspace.allowAnonymous ? \"\" : `\nasync function getUserToken() {\n const response = await fetch(\"/api/frankie/token\", { method: \"POST\", credentials: \"same-origin\" });\n if (!response.ok) return undefined;\n return ((await response.json()) as { token?: string }).token;\n}\n`;\n let priorChat: string | undefined;\n try { const previous = JSON.parse(files[root + \"frankie.install.json\"] ?? \"{}\"); if (previous.workspaceId === workspace.id && [\"/chat\", \"/frankie/chat\"].includes(previous.chatPath)) priorChat = previous.chatPath; } catch {}\n const chatPath = priorChat ?? (files[appRoot + \"chat/page.tsx\"] ? \"/frankie/chat\" : \"/chat\");\n if (!priorChat && files[appRoot + chatPath.slice(1) + \"/page.tsx\"]) throw new Error(\"Both /chat and /frankie/chat already exist. Choose a chat route manually.\");\n out.set(root + \"frankie.install.json\", JSON.stringify({ workspaceId: workspace.id, chatPath }, null, 2) + \"\\n\");\n if (chatPath !== \"/chat\") notes.push(\"Existing /chat preserved. Frankie chat is available at /frankie/chat.\");\n const mount = `\"use client\";\\nimport { usePathname } from \"next/navigation\";\\n${shared}${getToken}\nexport function FrankieWidgetMount() {\n const pathname = usePathname();\n const key = process.env.NEXT_PUBLIC_FRANKIE_KEY;\n if (!key || pathname === ${JSON.stringify(chatPath)}) return null;\n return <FrankieProvider publishableKey={key}${authProp}><FrankieWidget /></FrankieProvider>;\n}\nexport function WorkspaceChatPage() {\n const key = process.env.NEXT_PUBLIC_FRANKIE_KEY;\n if (!key) return <p>Chat is not configured. Set NEXT_PUBLIC_FRANKIE_KEY in your deployment.</p>;\n return <FrankieChatPage publishableKey={key}${authProp} />;\n}\n`;\n const moduleName = existingMount ? \"FrankieSetupChat\" : \"FrankieWidgetMount\";\n out.set(appRoot + \"frankie/\" + moduleName + \".tsx\", mount);\n if (!existingMount) {\n const layout = files[layoutPath]!;\n if (!layout.includes(\"</body>\")) throw new Error(\"The layout has no closing body. This custom layout needs manual widget placement.\");\n const importLine = `import { FrankieWidgetMount } from \"./frankie/FrankieWidgetMount\";\\n`;\n const withImport = /^[\"']use client[\"'];/.test(layout) ? layout.replace(/^(['\"]use client['\"];)/, \"$1\\n\" + importLine) : importLine + layout;\n out.set(layoutPath, withImport.replace(\"</body>\", \" <FrankieWidgetMount />\\n</body>\"));\n }\n const pageImport = chatPath === \"/chat\" ? \"../frankie/\" : \"../../frankie/\";\n out.set(appRoot + chatPath.slice(1) + \"/page.tsx\", `import { WorkspaceChatPage } from \"${pageImport}${moduleName}\";\\nexport default WorkspaceChatPage;\\n`);\n if (!workspace.allowAnonymous && !files[tokenPath]) {\n const lookup = firebase\n ? `import { cookies } from \"next/headers\";\\nimport { getAdminAuth } from \"@/lib/firebase-admin\";\\nimport { SESSION_COOKIE } from \"@/lib/session\";\\nasync function getCurrentUser() {\\n const cookie = (await cookies()).get(SESSION_COOKIE)?.value;\\n if (!cookie) return null;\\n const user = await getAdminAuth().verifySessionCookie(cookie, true);\\n return { id: user.uid, name: user.name, email: user.email };\\n}\\n`\n : authJs ? `import { auth } from \"@/auth\";\\nasync function getCurrentUser() {\\n const session = await auth();\\n const user = session?.user as { id?: string; name?: string | null; email?: string | null } | undefined;\\n return user?.id ? user : null;\\n}\\n`\n : `// Replace with your app's verified server session lookup.\\nasync function getCurrentUser(): Promise<{ id: string; name?: string; email?: string } | null> { return null; }\\n`;\n out.set(tokenPath, `import { SignJWT } from \"jose\";\\nimport { NextResponse } from \"next/server\";\\n${lookup}\nexport async function POST() {\n const secret = process.env.FRANKIE_JWT_SECRET;\n if (!secret) return NextResponse.json({ error: \"Chat secret not configured\" }, { status: 503 });\n try {\n const user = await getCurrentUser();\n if (!user?.id) return NextResponse.json({ error: \"Sign in required\" }, { status: 401 });\n const token = await new SignJWT({ name: user.name, email: user.email })\n .setProtectedHeader({ alg: \"HS256\" }).setSubject(user.id).setIssuedAt()\n .setExpirationTime(\"5m\").sign(new TextEncoder().encode(secret));\n return NextResponse.json({ token }, { headers: { \"Cache-Control\": \"no-store\" } });\n } catch { return NextResponse.json({ error: \"Sign in required\" }, { status: 401 }); }\n}\n`);\n }\n let doc: ApiDocument | undefined;\n for (const [path, content] of Object.entries(files)) {\n if (!/(?:openapi|swagger)[^/]*\\.json$/i.test(path)) continue;\n try { const parsed = JSON.parse(content); if (parsed.openapi && parsed.paths) { doc = parsed; break; } } catch {}\n }\n const paths: Record<string, Record<string, unknown>> = {};\n if (!doc) for (const [file, content] of Object.entries(files)) {\n if (/\\.(test|spec)\\.[tj]sx?$/.test(file)) continue;\n const nextRoute = file.match(/(?:^|\\/)app(\\/.*)\\/route\\.[tj]s$/);\n if (nextRoute) {\n const path = nextRoute[1]!.replace(/\\/\\([^/]+\\)/g, \"\").replace(/\\[([^\\]]+)\\]/g, \"{$1}\");\n if (path.includes(\"...\") || /\\/(?:auth|frankie)(?:\\/|$)/.test(path)) continue;\n for (const method of [\"GET\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\"]) {\n if (new RegExp(\"export\\\\s+(?:(?:async\\\\s+)?function\\\\s+\" + method + \"\\\\b|(?:const|let)\\\\s+\" + method + \"\\\\s*=)\").test(content))\n addInstallOperation(paths, method, path, file);\n }\n } else if (/\\.[tj]s$/.test(file) && /(?:routes?|api)\\//.test(file)) {\n for (const match of content.matchAll(/\\b(?:app|router|fastify)\\.(get|post|put|patch|delete)(?:\\s*<[^;]{1,1600}?>)?\\s*\\(\\s*[\"']([^\"']+)[\"']/g))\n addInstallOperation(paths, match[1]!.toUpperCase(), match[2]!.replace(/:([A-Za-z0-9_]+)/g, \"{$1}\"), file);\n }\n }\n doc ??= { openapi: \"3.0.3\", info: { title: workspace.name + \" API\", version: \"1.0.0\", description: \"Generated route inventory. Review prefixes, schemas and authorization before enabling tools.\" }, servers: [{ url: input.apiBaseUrl ?? \"/\" }], paths };\n const specPath = root + \"public/frankie.openapi.json\";\n const operationCount = Object.values(doc.paths).reduce((n, item) => n + Object.keys(item).filter((m) => /^(get|post|put|patch|delete)$/.test(m)).length, 0);\n out.set(specPath, JSON.stringify(doc, null, 2) + \"\\n\");\n if (!operationCount) notes.push(\"No supported API routes detected. The OpenAPI draft has no operations; add the real schema before connecting tools.\");\n notes.push(\"Review generated schemas and route mount prefixes before enabling operations.\");\n const envPath = root + \".env.example\";\n const env = (files[envPath] ?? \"\").replace(/^NEXT_PUBLIC_FRANKIE_KEY=.*\\n?/gm, \"\").replace(/^FRANKIE_JWT_SECRET=.*\\n?/gm, \"\").trimEnd();\n out.set(envPath, env + \"\\nNEXT_PUBLIC_FRANKIE_KEY=\" + workspace.publishableKey + \"\\n\" + (workspace.allowAnonymous ? \"\" : \"# Server only: use the secret from this workspace. Never commit its value.\\nFRANKIE_JWT_SECRET=\\n\"));\n out.set(root + \"FRANKIE_SETUP.md\", `# Frankie setup\\n\\nWorkspace: ${workspace.name}\\n\\n1. Add NEXT_PUBLIC_FRANKIE_KEY from .env.example to the frontend deployment.\\n${workspace.allowAnonymous ? \"\" : \"2. Add FRANKIE_JWT_SECRET from this workspace to the server hosting /api/frankie/token. Never commit the value.\\n\"}3. Install dependencies, update the lockfile, review and deploy the PR.\\n4. Open ${chatPath}. The generated floating widget hides on this chat page.\\n5. Connect https://YOUR_APP/frankie.openapi.json in Frankie → Tools & MCP. Choose Direct OpenAPI or OpenAPI + hosted MCP and select allowed operations.\\n6. Forwarding sends the Frankie-signed JWT. Your API must verify it and enforce user/resource permissions. It is not automatically interchangeable with a Firebase or OAuth access token.\\n7. Review conversations in Workspace → Chat sessions.\\n\\n## Remaining setup\\n\\n${notes.map((n) => \"- \" + n).join(\"\\n\")}\\n`);\n return { files: [...out].map(([path, content]) => ({ path, content })), root, chatPath, specPath, operationCount, needsAuthSetup, notes };\n}\nfunction addInstallOperation(paths: Record<string, Record<string, unknown>>, method: string, path: string, file: string) {\n if (path.includes(\"*\") || !path.startsWith(\"/\")) return;\n (paths[path] ??= {})[method.toLowerCase()] = {\n operationId: (method.toLowerCase() + \"_\" + path).replace(/[^a-zA-Z0-9_]+/g, \"_\"),\n summary: method + \" \" + path, description: \"Detected in \" + file + \". Review schemas before enabling.\",\n parameters: [...path.matchAll(/\\{([^}]+)\\}/g)].map((m) => ({ name: m[1], in: \"path\", required: true, schema: { type: \"string\" } })),\n responses: { \"200\": { description: \"Successful response\" } }, \"x-frankie-review-required\": true,\n };\n}\n"],"mappings":"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","names":[]}
|