@branch-fiction/extension-sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +18 -0
- package/dist/db/boolean-plugin.d.ts +10 -0
- package/dist/db/boolean-plugin.js +24 -0
- package/dist/db/boolean-plugin.js.map +1 -0
- package/dist/db/iframe.d.ts +15 -0
- package/dist/db/iframe.js +78 -0
- package/dist/db/iframe.js.map +1 -0
- package/dist/db/types.d.ts +247 -0
- package/dist/db/types.js +1 -0
- package/dist/dev-cli.d.ts +1 -0
- package/dist/dev-cli.js +297 -0
- package/dist/dev-cli.js.map +1 -0
- package/dist/dev.d.ts +46 -0
- package/dist/dev.js +2 -0
- package/dist/extension-host.bundle.js +122607 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/manifest-CQaa55kR.mjs +200 -0
- package/dist/manifest-CQaa55kR.mjs.map +1 -0
- package/dist/manifest.d.ts +97 -0
- package/dist/manifest.js +2 -0
- package/dist/models-catalog.d.ts +12 -0
- package/dist/models-catalog.js +58 -0
- package/dist/models-catalog.js.map +1 -0
- package/dist/pi-handle.d.ts +26 -0
- package/dist/pi-handle.js +108 -0
- package/dist/pi-handle.js.map +1 -0
- package/dist/sdk-source.d.ts +69 -0
- package/dist/sdk-source.js +205 -0
- package/dist/sdk-source.js.map +1 -0
- package/dist/server-BcwliPFy.mjs +752 -0
- package/dist/server-BcwliPFy.mjs.map +1 -0
- package/dist/types-ZCFYu2MY.d.mts +23 -0
- package/dist/vite.d.ts +53 -0
- package/dist/vite.js +83 -0
- package/dist/vite.js.map +1 -0
- package/package.json +94 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { a as isKnownSlot, i as Slot, n as SLOT_KEYS, r as SLOT_LABELS, t as ProviderAuthShape } from "./types-ZCFYu2MY.mjs";
|
|
2
|
+
import { ExtensionBookDataTable, ExtensionConfigField, ExtensionManifestV1, ExtensionPath, ExtensionProviderOption, ExtensionProviderRequirement, ExtensionProviderRequirementOptions, ExtensionProviderRequirementSlot, NET_ALLOWLIST_ENTRY_REGEX, defaultsFromManifest, defineManifest, hasMissingConfigFields, isOptionalRequirement, isUseSlotRequirement, optionExpectsUserURL, optionURL, requirementHasModel, validateManifest } from "./manifest.js";
|
|
3
|
+
import { ExtensionCtx, ExtensionHost, ExtensionProviderBinding, ExtensionSDK, WorkerSpawnHandle, WorkerSpawnOptions, isTaskAlreadyRunningError } from "./sdk-source.js";
|
|
4
|
+
export { type ExtensionBookDataTable, type ExtensionConfigField, type ExtensionCtx, type ExtensionHost, type ExtensionManifestV1, type ExtensionPath, type ExtensionProviderBinding, type ExtensionProviderOption, type ExtensionProviderRequirement, type ExtensionProviderRequirementOptions, type ExtensionProviderRequirementSlot, type ExtensionSDK, NET_ALLOWLIST_ENTRY_REGEX, type ProviderAuthShape, SLOT_KEYS, SLOT_LABELS, type Slot, type WorkerSpawnHandle, type WorkerSpawnOptions, defaultsFromManifest, defineManifest, hasMissingConfigFields, isKnownSlot, isOptionalRequirement, isTaskAlreadyRunningError, isUseSlotRequirement, optionExpectsUserURL, optionURL, requirementHasModel, validateManifest };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { a as isOptionalRequirement, c as optionURL, d as SLOT_KEYS, f as SLOT_LABELS, i as hasMissingConfigFields, l as requirementHasModel, n as defaultsFromManifest, o as isUseSlotRequirement, p as isKnownSlot, r as defineManifest, s as optionExpectsUserURL, t as NET_ALLOWLIST_ENTRY_REGEX, u as validateManifest } from "./manifest-CQaa55kR.mjs";
|
|
2
|
+
import { isTaskAlreadyRunningError } from "./sdk-source.js";
|
|
3
|
+
export { NET_ALLOWLIST_ENTRY_REGEX, SLOT_KEYS, SLOT_LABELS, defaultsFromManifest, defineManifest, hasMissingConfigFields, isKnownSlot, isOptionalRequirement, isTaskAlreadyRunningError, isUseSlotRequirement, optionExpectsUserURL, optionURL, requirementHasModel, validateManifest };
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
//#region src/types.ts
|
|
2
|
+
const SLOT_KEYS = ["piText", "piTextLight"];
|
|
3
|
+
const SLOT_LABELS = {
|
|
4
|
+
piText: "Text model",
|
|
5
|
+
piTextLight: "Light text model"
|
|
6
|
+
};
|
|
7
|
+
function isKnownSlot(s) {
|
|
8
|
+
return SLOT_KEYS.includes(s);
|
|
9
|
+
}
|
|
10
|
+
//#endregion
|
|
11
|
+
//#region src/manifest.ts
|
|
12
|
+
const EXTENSION_ID_REGEX = /^@[a-z0-9][a-z0-9-]*\/[a-z0-9][a-z0-9-]*$/;
|
|
13
|
+
const SQL_IDENT_REGEX = /^[a-z_][a-z0-9_]*$/;
|
|
14
|
+
const HOST_MANAGED_TABLES = new Set([
|
|
15
|
+
"books",
|
|
16
|
+
"chapters",
|
|
17
|
+
"chapter_paragraphs",
|
|
18
|
+
"book_entities",
|
|
19
|
+
"book_arcs",
|
|
20
|
+
"book_entity_hierarchies",
|
|
21
|
+
"chapter_scenes",
|
|
22
|
+
"chapter_scene_groups",
|
|
23
|
+
"chapter_relationships",
|
|
24
|
+
"chapter_entity_appellations",
|
|
25
|
+
"chapter_entity_attributes",
|
|
26
|
+
"book_categories",
|
|
27
|
+
"book_character_place_scores",
|
|
28
|
+
"book_styles",
|
|
29
|
+
"book_migrations",
|
|
30
|
+
"extension_seeds"
|
|
31
|
+
]);
|
|
32
|
+
function optionURL(opt) {
|
|
33
|
+
return "baseURL" in opt && opt.baseURL ? opt.baseURL : opt.fullURL;
|
|
34
|
+
}
|
|
35
|
+
function optionExpectsUserURL(opt) {
|
|
36
|
+
return "fullURL" in opt && !!opt.fullURL;
|
|
37
|
+
}
|
|
38
|
+
function isUseSlotRequirement(r) {
|
|
39
|
+
return "useSlot" in r;
|
|
40
|
+
}
|
|
41
|
+
function isOptionalRequirement(r) {
|
|
42
|
+
return !isUseSlotRequirement(r) && r.optional === true;
|
|
43
|
+
}
|
|
44
|
+
const NET_ALLOWLIST_ENTRY_REGEX = /^[a-z0-9.-]+(:\d+)?$/;
|
|
45
|
+
function defineManifest(m) {
|
|
46
|
+
return m;
|
|
47
|
+
}
|
|
48
|
+
function requirementHasModel(req) {
|
|
49
|
+
if (isUseSlotRequirement(req)) return true;
|
|
50
|
+
return req.options.length > 0 && !!req.options[0].model;
|
|
51
|
+
}
|
|
52
|
+
function defaultsFromManifest(manifest) {
|
|
53
|
+
const out = {};
|
|
54
|
+
for (const f of manifest.config ?? []) if (f.type === "boolean") {
|
|
55
|
+
if (f.default !== void 0) out[f.key] = f.default;
|
|
56
|
+
} else if (f.default !== void 0 && f.default !== "") out[f.key] = f.default;
|
|
57
|
+
return out;
|
|
58
|
+
}
|
|
59
|
+
function hasMissingConfigFields(manifest, config) {
|
|
60
|
+
for (const f of manifest.config ?? []) {
|
|
61
|
+
if (f.type === "boolean") continue;
|
|
62
|
+
if (!f.required) continue;
|
|
63
|
+
const v = config[f.key];
|
|
64
|
+
if (typeof v !== "string" || v.length === 0) return true;
|
|
65
|
+
}
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
function validateManifest(m) {
|
|
69
|
+
if (!m || typeof m !== "object") throw new Error("manifest.json must contain a JSON object");
|
|
70
|
+
if (m.manifestVersion !== "v1") throw new Error(`Unsupported manifestVersion: ${JSON.stringify(m.manifestVersion)} (expected "v1")`);
|
|
71
|
+
if (!m.id || typeof m.id !== "string" || !EXTENSION_ID_REGEX.test(m.id)) throw new Error(`Invalid extension id: ${m.id} (expected "@scope/name", lowercase kebab-case)`);
|
|
72
|
+
if (!m.name || typeof m.name !== "string") throw new Error("Extension manifest missing \"name\"");
|
|
73
|
+
if (!m.version || typeof m.version !== "string") throw new Error("Extension manifest missing \"version\"");
|
|
74
|
+
if (m.repository !== void 0) {
|
|
75
|
+
if (typeof m.repository !== "string") throw new Error(`Extension ${m.id}: "repository" must be a string`);
|
|
76
|
+
let host;
|
|
77
|
+
try {
|
|
78
|
+
host = new URL(m.repository).host;
|
|
79
|
+
} catch {
|
|
80
|
+
throw new Error(`Extension ${m.id}: "repository" must be a valid URL`);
|
|
81
|
+
}
|
|
82
|
+
if (host !== "github.com" && host !== "www.github.com") throw new Error(`Extension ${m.id}: "repository" must be a github.com URL`);
|
|
83
|
+
}
|
|
84
|
+
if (m.path) {
|
|
85
|
+
if (!m.path.entry) throw new Error(`Extension ${m.id}: path.entry is required`);
|
|
86
|
+
}
|
|
87
|
+
if (m.seed !== void 0) {
|
|
88
|
+
if (typeof m.seed !== "string" || m.seed.length === 0) throw new Error(`Extension ${m.id}: "seed" must be a non-empty string`);
|
|
89
|
+
const parts = m.seed.split("/");
|
|
90
|
+
if (m.seed.startsWith("/") || m.seed.includes("\\") || parts.includes("..")) throw new Error(`Extension ${m.id}: "seed" must be a relative path inside the extension (got ${JSON.stringify(m.seed)})`);
|
|
91
|
+
}
|
|
92
|
+
if (m.bookData !== void 0) {
|
|
93
|
+
if (!Array.isArray(m.bookData)) throw new Error(`Extension ${m.id}: "bookData" must be an array`);
|
|
94
|
+
const seenTables = /* @__PURE__ */ new Set();
|
|
95
|
+
for (let i = 0; i < m.bookData.length; i++) {
|
|
96
|
+
const entry = m.bookData[i];
|
|
97
|
+
const where = `Extension ${m.id}, bookData[${i}]`;
|
|
98
|
+
if (!entry || typeof entry !== "object") throw new Error(`${where}: must be an object`);
|
|
99
|
+
if (typeof entry.table !== "string" || !SQL_IDENT_REGEX.test(entry.table)) throw new Error(`${where}: "table" must be a lowercase snake_case identifier`);
|
|
100
|
+
if (HOST_MANAGED_TABLES.has(entry.table)) throw new Error(`${where}: table "${entry.table}" is host-managed`);
|
|
101
|
+
if (seenTables.has(entry.table)) throw new Error(`${where}: duplicate table "${entry.table}"`);
|
|
102
|
+
seenTables.add(entry.table);
|
|
103
|
+
if (typeof entry.bookIdColumn !== "string" || !SQL_IDENT_REGEX.test(entry.bookIdColumn)) throw new Error(`${where}: "bookIdColumn" must be a lowercase snake_case identifier`);
|
|
104
|
+
if (entry.assetColumns !== void 0) {
|
|
105
|
+
if (!Array.isArray(entry.assetColumns) || entry.assetColumns.some((c) => typeof c !== "string" || !SQL_IDENT_REGEX.test(c))) throw new Error(`${where}: "assetColumns" must be lowercase snake_case identifiers`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (m.providers) {
|
|
110
|
+
if (!Array.isArray(m.providers)) throw new Error(`Extension ${m.id}: providers must be an array`);
|
|
111
|
+
const seen = /* @__PURE__ */ new Set();
|
|
112
|
+
for (const r of m.providers) {
|
|
113
|
+
if (!r || typeof r !== "object" || !r.key) throw new Error(`Invalid provider entry in ${m.id}: ${JSON.stringify(r)}`);
|
|
114
|
+
if (seen.has(r.key)) throw new Error(`Duplicate provider key in ${m.id}: ${r.key}`);
|
|
115
|
+
seen.add(r.key);
|
|
116
|
+
const reqKey = r.key;
|
|
117
|
+
const raw = r;
|
|
118
|
+
const hasUseSlot = "useSlot" in raw;
|
|
119
|
+
const hasOptions = "options" in raw;
|
|
120
|
+
if (hasUseSlot && hasOptions) throw new Error(`Provider ${reqKey} in ${m.id}: cannot declare both "useSlot" and "options"`);
|
|
121
|
+
if (!hasUseSlot && !hasOptions) throw new Error(`Provider ${reqKey} in ${m.id}: must declare either "useSlot" or "options"`);
|
|
122
|
+
if (hasUseSlot) {
|
|
123
|
+
const slot = raw.useSlot;
|
|
124
|
+
if (typeof slot !== "string" || !isKnownSlot(slot)) throw new Error(`Provider ${reqKey} in ${m.id}: useSlot must be one of ${SLOT_KEYS.join(", ")} (got ${JSON.stringify(slot)})`);
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
const optionsReq = r;
|
|
128
|
+
if (!Array.isArray(optionsReq.options) || optionsReq.options.length === 0) throw new Error(`Provider ${r.key} in ${m.id} must declare a non-empty "options" array`);
|
|
129
|
+
if (optionsReq.optional !== void 0 && typeof optionsReq.optional !== "boolean") throw new Error(`Provider ${reqKey} in ${m.id}: "optional" must be a boolean`);
|
|
130
|
+
const seenOriginAuth = /* @__PURE__ */ new Set();
|
|
131
|
+
const firstHasModel = !!optionsReq.options[0]?.model;
|
|
132
|
+
for (let i = 0; i < optionsReq.options.length; i++) {
|
|
133
|
+
const opt = optionsReq.options[i];
|
|
134
|
+
const where = `Provider ${reqKey} in ${m.id}, option ${i}`;
|
|
135
|
+
if (!opt || typeof opt !== "object") throw new Error(`${where}: must be an object`);
|
|
136
|
+
const rawOpt = opt;
|
|
137
|
+
const hasBase = typeof rawOpt.baseURL === "string" && rawOpt.baseURL.length > 0;
|
|
138
|
+
const hasFull = typeof rawOpt.fullURL === "string" && rawOpt.fullURL.length > 0;
|
|
139
|
+
if (hasBase && hasFull) throw new Error(`${where}: cannot declare both "baseURL" and "fullURL"`);
|
|
140
|
+
if (!hasBase && !hasFull) throw new Error(`${where}: must declare either "baseURL" or "fullURL"`);
|
|
141
|
+
const urlString = hasBase ? rawOpt.baseURL : rawOpt.fullURL;
|
|
142
|
+
try {
|
|
143
|
+
new URL(urlString);
|
|
144
|
+
} catch {
|
|
145
|
+
throw new Error(`${where}: invalid ${hasBase ? "baseURL" : "fullURL"}: ${urlString}`);
|
|
146
|
+
}
|
|
147
|
+
if (!opt.auth || typeof opt.auth !== "object") throw new Error(`${where}: must declare an auth block`);
|
|
148
|
+
const validKinds = [
|
|
149
|
+
"none",
|
|
150
|
+
"bearer",
|
|
151
|
+
"header",
|
|
152
|
+
"queryParam",
|
|
153
|
+
"body"
|
|
154
|
+
];
|
|
155
|
+
if (!validKinds.includes(opt.auth.kind)) throw new Error(`${where}: auth.kind must be one of ${validKinds.join(", ")}`);
|
|
156
|
+
if (opt.auth.kind === "header" && !("header" in opt.auth && opt.auth.header)) throw new Error(`${where}: auth.kind="header" requires "header"`);
|
|
157
|
+
if (opt.auth.kind === "queryParam" && !("param" in opt.auth && opt.auth.param)) throw new Error(`${where}: auth.kind="queryParam" requires "param"`);
|
|
158
|
+
if (opt.auth.kind === "body" && !("field" in opt.auth && opt.auth.field)) throw new Error(`${where}: auth.kind="body" requires "field"`);
|
|
159
|
+
if (opt.model !== void 0 && (typeof opt.model !== "string" || !opt.model)) throw new Error(`${where}: model must be a non-empty string when provided`);
|
|
160
|
+
if (!!opt.model !== firstHasModel) throw new Error(`Provider ${reqKey} in ${m.id}: every option must declare "model" or none of them — mixed states aren't allowed`);
|
|
161
|
+
const originKey = (() => {
|
|
162
|
+
try {
|
|
163
|
+
return new URL(urlString).origin;
|
|
164
|
+
} catch {
|
|
165
|
+
return urlString;
|
|
166
|
+
}
|
|
167
|
+
})();
|
|
168
|
+
const dedupeKey = `${originKey}|${JSON.stringify(opt.auth)}`;
|
|
169
|
+
if (seenOriginAuth.has(dedupeKey)) throw new Error(`Provider ${reqKey} in ${m.id}: option ${i} duplicates an earlier (origin, auth) pair (${originKey})`);
|
|
170
|
+
seenOriginAuth.add(dedupeKey);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
if (m.net !== void 0) {
|
|
175
|
+
if (!Array.isArray(m.net)) throw new Error(`Extension ${m.id}: net must be an array of strings`);
|
|
176
|
+
const seenNet = /* @__PURE__ */ new Set();
|
|
177
|
+
for (let i = 0; i < m.net.length; i++) {
|
|
178
|
+
const entry = m.net[i];
|
|
179
|
+
const where = `Extension ${m.id}, net[${i}]`;
|
|
180
|
+
if (typeof entry !== "string" || entry.length === 0) throw new Error(`${where}: must be a non-empty string`);
|
|
181
|
+
if (entry !== entry.toLowerCase()) throw new Error(`${where}: must be lowercase (got ${JSON.stringify(entry)})`);
|
|
182
|
+
if (!NET_ALLOWLIST_ENTRY_REGEX.test(entry)) throw new Error(`${where}: must be a bare host or host:port — no scheme, path, or wildcards (got ${JSON.stringify(entry)})`);
|
|
183
|
+
if (seenNet.has(entry)) throw new Error(`${where}: duplicate entry ${JSON.stringify(entry)}`);
|
|
184
|
+
seenNet.add(entry);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (m.config) {
|
|
188
|
+
if (!Array.isArray(m.config)) throw new Error(`Extension ${m.id}: config must be an array`);
|
|
189
|
+
const seenKeys = /* @__PURE__ */ new Set();
|
|
190
|
+
for (const f of m.config) {
|
|
191
|
+
if (!f || typeof f !== "object" || !f.key) throw new Error(`Invalid config field in ${m.id}: ${JSON.stringify(f)}`);
|
|
192
|
+
if (seenKeys.has(f.key)) throw new Error(`Duplicate config key in ${m.id}: ${f.key}`);
|
|
193
|
+
seenKeys.add(f.key);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
//#endregion
|
|
198
|
+
export { isOptionalRequirement as a, optionURL as c, SLOT_KEYS as d, SLOT_LABELS as f, hasMissingConfigFields as i, requirementHasModel as l, defaultsFromManifest as n, isUseSlotRequirement as o, isKnownSlot as p, defineManifest as r, optionExpectsUserURL as s, NET_ALLOWLIST_ENTRY_REGEX as t, validateManifest as u };
|
|
199
|
+
|
|
200
|
+
//# sourceMappingURL=manifest-CQaa55kR.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest-CQaa55kR.mjs","names":[],"sources":["../src/types.ts","../src/manifest.ts"],"sourcesContent":["export type ProviderAuthShape =\n | { kind: 'none' }\n | { kind: 'bearer'; headerPrefix?: string }\n | { kind: 'header'; header: string }\n | { kind: 'queryParam'; param: string }\n | { kind: 'body'; field: string };\n\n// if you add a new slot, update all three\nexport type Slot = 'piText' | 'piTextLight';\n\nexport const SLOT_KEYS: readonly Slot[] = ['piText', 'piTextLight'];\n\nexport const SLOT_LABELS: Record<Slot, string> = {\n piText: 'Text model',\n piTextLight: 'Light text model'\n};\n\nexport function isKnownSlot(s: string): s is Slot {\n return (SLOT_KEYS as readonly string[]).includes(s);\n}\n","import { isKnownSlot, type ProviderAuthShape, SLOT_KEYS, type Slot } from './types';\n\nconst EXTENSION_ID_REGEX = /^@[a-z0-9][a-z0-9-]*\\/[a-z0-9][a-z0-9-]*$/;\nconst SQL_IDENT_REGEX = /^[a-z_][a-z0-9_]*$/;\n\n// Host-managed extension db tables; keep in sync with RESERVED_TABLES in src-tauri/src/extension_db.rs.\nconst HOST_MANAGED_TABLES = new Set([\n 'books',\n 'chapters',\n 'chapter_paragraphs',\n 'book_entities',\n 'book_arcs',\n 'book_entity_hierarchies',\n 'chapter_scenes',\n 'chapter_scene_groups',\n 'chapter_relationships',\n 'chapter_entity_appellations',\n 'chapter_entity_attributes',\n 'book_categories',\n 'book_character_place_scores',\n 'book_styles',\n 'book_migrations',\n 'extension_seeds'\n]);\n\nexport type ExtensionConfigField =\n | {\n key: string;\n label: string;\n type: 'text' | 'url';\n default?: string;\n required?: boolean;\n placeholder?: string;\n description?: string;\n }\n | {\n key: string;\n label: string;\n type: 'select';\n options: { value: string; label: string }[];\n default?: string;\n required?: boolean;\n description?: string;\n }\n | {\n key: string;\n label: string;\n type: 'boolean';\n default?: boolean;\n description?: string;\n };\n\ntype ExtensionProviderOptionCommon = {\n auth: ProviderAuthShape;\n model?: string;\n providerName?: string;\n // Markdown-with-links (a, strong, p, em) shown under the API key input.\n credentialHelp?: string;\n};\n\n// `baseURL`: fixed prefix declared by the extension (e.g. a public API origin).\n// `fullURL`: user-supplied endpoint — manifest value is a placeholder/example.\nexport type ExtensionProviderOption = ExtensionProviderOptionCommon &\n ({ baseURL: string; fullURL?: never } | { fullURL: string; baseURL?: never });\n\nexport function optionURL(opt: ExtensionProviderOption): string {\n return 'baseURL' in opt && opt.baseURL ? opt.baseURL : opt.fullURL!;\n}\n\nexport function optionExpectsUserURL(opt: ExtensionProviderOption): boolean {\n return 'fullURL' in opt && !!opt.fullURL;\n}\n\nexport type ExtensionProviderRequirementOptions = {\n key: string;\n role?: string;\n options: ExtensionProviderOption[];\n optional?: boolean;\n};\n\nexport type ExtensionProviderRequirementSlot = {\n key: string;\n role?: string;\n useSlot: Slot;\n};\n\nexport type ExtensionProviderRequirement =\n | ExtensionProviderRequirementOptions\n | ExtensionProviderRequirementSlot;\n\nexport function isUseSlotRequirement(\n r: ExtensionProviderRequirement\n): r is ExtensionProviderRequirementSlot {\n return 'useSlot' in r;\n}\n\nexport function isOptionalRequirement(r: ExtensionProviderRequirement): boolean {\n return !isUseSlotRequirement(r) && r.optional === true;\n}\n\n// Book-scoped non-personal table in single-book exports; assetColumns hold portable file:// URLs.\nexport type ExtensionBookDataTable = {\n table: string;\n bookIdColumn: string;\n assetColumns?: string[];\n};\n\nexport type ExtensionPath = {\n entry: string;\n worker?: string;\n // Path (relative to the extension root) to an icon asset.\n icon?: string;\n // Initial window sizing/launch options. Open-ended on purpose so future\n // launch hints (minSize, titleBarStyle, etc.) can be added here.\n window?: { width: number; height: number };\n // Whether this path can be launched via the phone-share flow.\n phoneCompatible?: boolean;\n};\n\nexport type ExtensionManifestV1 = {\n manifestVersion: 'v1';\n // \"@scope/name\", lowercase kebab-case.\n id: string;\n name: string;\n // Extension's own semver. Distinct from `manifestVersion`.\n version: string;\n author?: string;\n description?: string;\n // GitHub URL the update checker polls for newer releases. Required for bundled\n // extensions to be updatable (they have no install provenance); GitHub-\n // installed extensions fall back to the URL they were installed from.\n // Accepts github.com/owner/repo or github.com/owner/repo/tree/<ref>/<subdir>.\n repository?: string;\n path?: ExtensionPath;\n providers?: ExtensionProviderRequirement[];\n config?: ExtensionConfigField[];\n // SQLite file (relative to the extension root, optionally .gz) copied into the extension DB once.\n // Tracked by path, so rename to re-seed. Bundled extensions only.\n seed?: string;\n // Tables packed into single-book exports (never personal data); list FK parents before children.\n bookData?: ExtensionBookDataTable[];\n // Extra outbound hosts the worker is allowed to reach\n // (provider baseURLs do NOT belong here as those are reached through the local proxy)\n net?: string[];\n};\n\nexport const NET_ALLOWLIST_ENTRY_REGEX = /^[a-z0-9.-]+(:\\d+)?$/;\n\n// Identity helper for type-checked manifest authoring.\n// export default defineManifest({ ... });\nexport function defineManifest(m: ExtensionManifestV1): ExtensionManifestV1 {\n return m;\n}\n\nexport function requirementHasModel(req: ExtensionProviderRequirement): boolean {\n if (isUseSlotRequirement(req)) return true;\n return req.options.length > 0 && !!req.options[0].model;\n}\n\nexport function defaultsFromManifest(\n manifest: ExtensionManifestV1\n): Record<string, unknown> {\n const out: Record<string, unknown> = {};\n for (const f of manifest.config ?? []) {\n if (f.type === 'boolean') {\n if (f.default !== undefined) out[f.key] = f.default;\n } else if (f.default !== undefined && f.default !== '') {\n out[f.key] = f.default;\n }\n }\n return out;\n}\n\nexport function hasMissingConfigFields(\n manifest: ExtensionManifestV1,\n config: Record<string, unknown>\n): boolean {\n for (const f of manifest.config ?? []) {\n if (f.type === 'boolean') continue;\n if (!f.required) continue;\n const v = config[f.key];\n if (typeof v !== 'string' || v.length === 0) return true;\n }\n return false;\n}\n\nexport function validateManifest(m: ExtensionManifestV1): void {\n if (!m || typeof m !== 'object') {\n throw new Error('manifest.json must contain a JSON object');\n }\n if (m.manifestVersion !== 'v1') {\n throw new Error(\n `Unsupported manifestVersion: ${JSON.stringify(m.manifestVersion)} (expected \"v1\")`\n );\n }\n if (!m.id || typeof m.id !== 'string' || !EXTENSION_ID_REGEX.test(m.id)) {\n throw new Error(\n `Invalid extension id: ${m.id} (expected \"@scope/name\", lowercase kebab-case)`\n );\n }\n if (!m.name || typeof m.name !== 'string') {\n throw new Error('Extension manifest missing \"name\"');\n }\n if (!m.version || typeof m.version !== 'string') {\n throw new Error('Extension manifest missing \"version\"');\n }\n\n if (m.repository !== undefined) {\n if (typeof m.repository !== 'string') {\n throw new Error(`Extension ${m.id}: \"repository\" must be a string`);\n }\n let host: string;\n try {\n host = new URL(m.repository).host;\n } catch {\n throw new Error(`Extension ${m.id}: \"repository\" must be a valid URL`);\n }\n if (host !== 'github.com' && host !== 'www.github.com') {\n throw new Error(`Extension ${m.id}: \"repository\" must be a github.com URL`);\n }\n }\n\n if (m.path) {\n if (!m.path.entry) {\n throw new Error(`Extension ${m.id}: path.entry is required`);\n }\n }\n\n if (m.seed !== undefined) {\n if (typeof m.seed !== 'string' || m.seed.length === 0) {\n throw new Error(`Extension ${m.id}: \"seed\" must be a non-empty string`);\n }\n const parts = m.seed.split('/');\n if (m.seed.startsWith('/') || m.seed.includes('\\\\') || parts.includes('..')) {\n throw new Error(\n `Extension ${m.id}: \"seed\" must be a relative path inside the extension (got ${JSON.stringify(m.seed)})`\n );\n }\n }\n\n if (m.bookData !== undefined) {\n if (!Array.isArray(m.bookData)) {\n throw new Error(`Extension ${m.id}: \"bookData\" must be an array`);\n }\n const seenTables = new Set<string>();\n for (let i = 0; i < m.bookData.length; i++) {\n const entry = m.bookData[i];\n const where = `Extension ${m.id}, bookData[${i}]`;\n if (!entry || typeof entry !== 'object') {\n throw new Error(`${where}: must be an object`);\n }\n if (typeof entry.table !== 'string' || !SQL_IDENT_REGEX.test(entry.table)) {\n throw new Error(`${where}: \"table\" must be a lowercase snake_case identifier`);\n }\n if (HOST_MANAGED_TABLES.has(entry.table)) {\n throw new Error(`${where}: table \"${entry.table}\" is host-managed`);\n }\n if (seenTables.has(entry.table)) {\n throw new Error(`${where}: duplicate table \"${entry.table}\"`);\n }\n seenTables.add(entry.table);\n if (\n typeof entry.bookIdColumn !== 'string' ||\n !SQL_IDENT_REGEX.test(entry.bookIdColumn)\n ) {\n throw new Error(\n `${where}: \"bookIdColumn\" must be a lowercase snake_case identifier`\n );\n }\n if (entry.assetColumns !== undefined) {\n if (\n !Array.isArray(entry.assetColumns) ||\n entry.assetColumns.some(\n (c) => typeof c !== 'string' || !SQL_IDENT_REGEX.test(c)\n )\n ) {\n throw new Error(\n `${where}: \"assetColumns\" must be lowercase snake_case identifiers`\n );\n }\n }\n }\n }\n\n if (m.providers) {\n if (!Array.isArray(m.providers)) {\n throw new Error(`Extension ${m.id}: providers must be an array`);\n }\n const seen = new Set<string>();\n for (const r of m.providers) {\n if (!r || typeof r !== 'object' || !r.key) {\n throw new Error(`Invalid provider entry in ${m.id}: ${JSON.stringify(r)}`);\n }\n if (seen.has(r.key)) {\n throw new Error(`Duplicate provider key in ${m.id}: ${r.key}`);\n }\n seen.add(r.key);\n\n const reqKey = r.key;\n const raw = r as Record<string, unknown>;\n const hasUseSlot = 'useSlot' in raw;\n const hasOptions = 'options' in raw;\n if (hasUseSlot && hasOptions) {\n throw new Error(\n `Provider ${reqKey} in ${m.id}: cannot declare both \"useSlot\" and \"options\"`\n );\n }\n if (!hasUseSlot && !hasOptions) {\n throw new Error(\n `Provider ${reqKey} in ${m.id}: must declare either \"useSlot\" or \"options\"`\n );\n }\n\n if (hasUseSlot) {\n const slot = raw.useSlot;\n if (typeof slot !== 'string' || !isKnownSlot(slot)) {\n throw new Error(\n `Provider ${reqKey} in ${m.id}: useSlot must be one of ${SLOT_KEYS.join(', ')} (got ${JSON.stringify(slot)})`\n );\n }\n continue;\n }\n\n const optionsReq = r as ExtensionProviderRequirementOptions;\n if (!Array.isArray(optionsReq.options) || optionsReq.options.length === 0) {\n throw new Error(\n `Provider ${r.key} in ${m.id} must declare a non-empty \"options\" array`\n );\n }\n if (optionsReq.optional !== undefined && typeof optionsReq.optional !== 'boolean') {\n throw new Error(`Provider ${reqKey} in ${m.id}: \"optional\" must be a boolean`);\n }\n const seenOriginAuth = new Set<string>();\n const firstHasModel = !!optionsReq.options[0]?.model;\n for (let i = 0; i < optionsReq.options.length; i++) {\n const opt = optionsReq.options[i];\n const where = `Provider ${reqKey} in ${m.id}, option ${i}`;\n if (!opt || typeof opt !== 'object') {\n throw new Error(`${where}: must be an object`);\n }\n const rawOpt = opt as Record<string, unknown>;\n const hasBase = typeof rawOpt.baseURL === 'string' && rawOpt.baseURL.length > 0;\n const hasFull = typeof rawOpt.fullURL === 'string' && rawOpt.fullURL.length > 0;\n if (hasBase && hasFull) {\n throw new Error(`${where}: cannot declare both \"baseURL\" and \"fullURL\"`);\n }\n if (!hasBase && !hasFull) {\n throw new Error(`${where}: must declare either \"baseURL\" or \"fullURL\"`);\n }\n const urlString = (hasBase ? rawOpt.baseURL : rawOpt.fullURL) as string;\n try {\n new URL(urlString);\n } catch {\n throw new Error(\n `${where}: invalid ${hasBase ? 'baseURL' : 'fullURL'}: ${urlString}`\n );\n }\n if (!opt.auth || typeof opt.auth !== 'object') {\n throw new Error(`${where}: must declare an auth block`);\n }\n const validKinds = ['none', 'bearer', 'header', 'queryParam', 'body'] as const;\n if (!validKinds.includes(opt.auth.kind as (typeof validKinds)[number])) {\n throw new Error(`${where}: auth.kind must be one of ${validKinds.join(', ')}`);\n }\n if (opt.auth.kind === 'header' && !('header' in opt.auth && opt.auth.header)) {\n throw new Error(`${where}: auth.kind=\"header\" requires \"header\"`);\n }\n if (opt.auth.kind === 'queryParam' && !('param' in opt.auth && opt.auth.param)) {\n throw new Error(`${where}: auth.kind=\"queryParam\" requires \"param\"`);\n }\n if (opt.auth.kind === 'body' && !('field' in opt.auth && opt.auth.field)) {\n throw new Error(`${where}: auth.kind=\"body\" requires \"field\"`);\n }\n if (opt.model !== undefined && (typeof opt.model !== 'string' || !opt.model)) {\n throw new Error(`${where}: model must be a non-empty string when provided`);\n }\n if (!!opt.model !== firstHasModel) {\n throw new Error(\n `Provider ${reqKey} in ${m.id}: every option must declare \"model\" or none of them — mixed states aren't allowed`\n );\n }\n const originKey = (() => {\n try {\n return new URL(urlString).origin;\n } catch {\n return urlString;\n }\n })();\n const authKey = JSON.stringify(opt.auth);\n const dedupeKey = `${originKey}|${authKey}`;\n if (seenOriginAuth.has(dedupeKey)) {\n throw new Error(\n `Provider ${reqKey} in ${m.id}: option ${i} duplicates an earlier (origin, auth) pair (${originKey})`\n );\n }\n seenOriginAuth.add(dedupeKey);\n }\n }\n }\n\n if (m.net !== undefined) {\n if (!Array.isArray(m.net)) {\n throw new Error(`Extension ${m.id}: net must be an array of strings`);\n }\n const seenNet = new Set<string>();\n for (let i = 0; i < m.net.length; i++) {\n const entry = m.net[i];\n const where = `Extension ${m.id}, net[${i}]`;\n if (typeof entry !== 'string' || entry.length === 0) {\n throw new Error(`${where}: must be a non-empty string`);\n }\n if (entry !== entry.toLowerCase()) {\n throw new Error(`${where}: must be lowercase (got ${JSON.stringify(entry)})`);\n }\n if (!NET_ALLOWLIST_ENTRY_REGEX.test(entry)) {\n throw new Error(\n `${where}: must be a bare host or host:port — no scheme, path, or wildcards (got ${JSON.stringify(entry)})`\n );\n }\n if (seenNet.has(entry)) {\n throw new Error(`${where}: duplicate entry ${JSON.stringify(entry)}`);\n }\n seenNet.add(entry);\n }\n }\n\n if (m.config) {\n if (!Array.isArray(m.config)) {\n throw new Error(`Extension ${m.id}: config must be an array`);\n }\n const seenKeys = new Set<string>();\n for (const f of m.config) {\n if (!f || typeof f !== 'object' || !f.key) {\n throw new Error(`Invalid config field in ${m.id}: ${JSON.stringify(f)}`);\n }\n if (seenKeys.has(f.key)) {\n throw new Error(`Duplicate config key in ${m.id}: ${f.key}`);\n }\n seenKeys.add(f.key);\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { i as Slot, t as ProviderAuthShape } from "./types-ZCFYu2MY.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/manifest.d.ts
|
|
4
|
+
type ExtensionConfigField = {
|
|
5
|
+
key: string;
|
|
6
|
+
label: string;
|
|
7
|
+
type: 'text' | 'url';
|
|
8
|
+
default?: string;
|
|
9
|
+
required?: boolean;
|
|
10
|
+
placeholder?: string;
|
|
11
|
+
description?: string;
|
|
12
|
+
} | {
|
|
13
|
+
key: string;
|
|
14
|
+
label: string;
|
|
15
|
+
type: 'select';
|
|
16
|
+
options: {
|
|
17
|
+
value: string;
|
|
18
|
+
label: string;
|
|
19
|
+
}[];
|
|
20
|
+
default?: string;
|
|
21
|
+
required?: boolean;
|
|
22
|
+
description?: string;
|
|
23
|
+
} | {
|
|
24
|
+
key: string;
|
|
25
|
+
label: string;
|
|
26
|
+
type: 'boolean';
|
|
27
|
+
default?: boolean;
|
|
28
|
+
description?: string;
|
|
29
|
+
};
|
|
30
|
+
type ExtensionProviderOptionCommon = {
|
|
31
|
+
auth: ProviderAuthShape;
|
|
32
|
+
model?: string;
|
|
33
|
+
providerName?: string;
|
|
34
|
+
credentialHelp?: string;
|
|
35
|
+
};
|
|
36
|
+
type ExtensionProviderOption = ExtensionProviderOptionCommon & ({
|
|
37
|
+
baseURL: string;
|
|
38
|
+
fullURL?: never;
|
|
39
|
+
} | {
|
|
40
|
+
fullURL: string;
|
|
41
|
+
baseURL?: never;
|
|
42
|
+
});
|
|
43
|
+
declare function optionURL(opt: ExtensionProviderOption): string;
|
|
44
|
+
declare function optionExpectsUserURL(opt: ExtensionProviderOption): boolean;
|
|
45
|
+
type ExtensionProviderRequirementOptions = {
|
|
46
|
+
key: string;
|
|
47
|
+
role?: string;
|
|
48
|
+
options: ExtensionProviderOption[];
|
|
49
|
+
optional?: boolean;
|
|
50
|
+
};
|
|
51
|
+
type ExtensionProviderRequirementSlot = {
|
|
52
|
+
key: string;
|
|
53
|
+
role?: string;
|
|
54
|
+
useSlot: Slot;
|
|
55
|
+
};
|
|
56
|
+
type ExtensionProviderRequirement = ExtensionProviderRequirementOptions | ExtensionProviderRequirementSlot;
|
|
57
|
+
declare function isUseSlotRequirement(r: ExtensionProviderRequirement): r is ExtensionProviderRequirementSlot;
|
|
58
|
+
declare function isOptionalRequirement(r: ExtensionProviderRequirement): boolean;
|
|
59
|
+
type ExtensionBookDataTable = {
|
|
60
|
+
table: string;
|
|
61
|
+
bookIdColumn: string;
|
|
62
|
+
assetColumns?: string[];
|
|
63
|
+
};
|
|
64
|
+
type ExtensionPath = {
|
|
65
|
+
entry: string;
|
|
66
|
+
worker?: string;
|
|
67
|
+
icon?: string;
|
|
68
|
+
window?: {
|
|
69
|
+
width: number;
|
|
70
|
+
height: number;
|
|
71
|
+
};
|
|
72
|
+
phoneCompatible?: boolean;
|
|
73
|
+
};
|
|
74
|
+
type ExtensionManifestV1 = {
|
|
75
|
+
manifestVersion: 'v1';
|
|
76
|
+
id: string;
|
|
77
|
+
name: string;
|
|
78
|
+
version: string;
|
|
79
|
+
author?: string;
|
|
80
|
+
description?: string;
|
|
81
|
+
repository?: string;
|
|
82
|
+
path?: ExtensionPath;
|
|
83
|
+
providers?: ExtensionProviderRequirement[];
|
|
84
|
+
config?: ExtensionConfigField[];
|
|
85
|
+
seed?: string;
|
|
86
|
+
bookData?: ExtensionBookDataTable[];
|
|
87
|
+
net?: string[];
|
|
88
|
+
};
|
|
89
|
+
declare const NET_ALLOWLIST_ENTRY_REGEX: RegExp;
|
|
90
|
+
declare function defineManifest(m: ExtensionManifestV1): ExtensionManifestV1;
|
|
91
|
+
declare function requirementHasModel(req: ExtensionProviderRequirement): boolean;
|
|
92
|
+
declare function defaultsFromManifest(manifest: ExtensionManifestV1): Record<string, unknown>;
|
|
93
|
+
declare function hasMissingConfigFields(manifest: ExtensionManifestV1, config: Record<string, unknown>): boolean;
|
|
94
|
+
declare function validateManifest(m: ExtensionManifestV1): void;
|
|
95
|
+
//#endregion
|
|
96
|
+
export { ExtensionBookDataTable, ExtensionConfigField, ExtensionManifestV1, ExtensionPath, ExtensionProviderOption, ExtensionProviderRequirement, ExtensionProviderRequirementOptions, ExtensionProviderRequirementSlot, NET_ALLOWLIST_ENTRY_REGEX, defaultsFromManifest, defineManifest, hasMissingConfigFields, isOptionalRequirement, isUseSlotRequirement, optionExpectsUserURL, optionURL, requirementHasModel, validateManifest };
|
|
97
|
+
//# sourceMappingURL=manifest.d.ts.map
|
package/dist/manifest.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as isOptionalRequirement, c as optionURL, i as hasMissingConfigFields, l as requirementHasModel, n as defaultsFromManifest, o as isUseSlotRequirement, r as defineManifest, s as optionExpectsUserURL, t as NET_ALLOWLIST_ENTRY_REGEX, u as validateManifest } from "./manifest-CQaa55kR.mjs";
|
|
2
|
+
export { NET_ALLOWLIST_ENTRY_REGEX, defaultsFromManifest, defineManifest, hasMissingConfigFields, isOptionalRequirement, isUseSlotRequirement, optionExpectsUserURL, optionURL, requirementHasModel, validateManifest };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Api, Model } from "@earendil-works/pi-ai";
|
|
2
|
+
|
|
3
|
+
//#region src/models-catalog.d.ts
|
|
4
|
+
type ModelsCatalogJson = Record<string, Record<string, unknown>>;
|
|
5
|
+
declare function applyModelsCatalog(json: unknown): number;
|
|
6
|
+
declare function modelsCatalogVersion(): number;
|
|
7
|
+
declare function getCatalogModel(provider: string, modelId: string): Model<Api> | undefined;
|
|
8
|
+
declare function getCatalogModels(provider: string): Model<Api>[];
|
|
9
|
+
declare function getCatalogProviders(): string[];
|
|
10
|
+
//#endregion
|
|
11
|
+
export { ModelsCatalogJson, applyModelsCatalog, getCatalogModel, getCatalogModels, getCatalogProviders, modelsCatalogVersion };
|
|
12
|
+
//# sourceMappingURL=models-catalog.d.ts.map
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { getModel, getModels, getProviders } from "@earendil-works/pi-ai";
|
|
2
|
+
//#region src/models-catalog.ts
|
|
3
|
+
const GLOBAL_KEY = Symbol.for("branch-fiction.models-catalog");
|
|
4
|
+
function getCatalogState() {
|
|
5
|
+
return globalThis[GLOBAL_KEY];
|
|
6
|
+
}
|
|
7
|
+
function isValidModel(value) {
|
|
8
|
+
if (value === null || typeof value !== "object") return false;
|
|
9
|
+
const m = value;
|
|
10
|
+
return typeof m.id === "string" && typeof m.name === "string" && typeof m.api === "string" && typeof m.provider === "string" && typeof m.baseUrl === "string";
|
|
11
|
+
}
|
|
12
|
+
function applyModelsCatalog(json) {
|
|
13
|
+
if (json === null || typeof json !== "object" || Array.isArray(json)) throw new Error("models catalog must be an object keyed by provider");
|
|
14
|
+
const models = /* @__PURE__ */ new Map();
|
|
15
|
+
let count = 0;
|
|
16
|
+
for (const [provider, entries] of Object.entries(json)) {
|
|
17
|
+
if (entries === null || typeof entries !== "object") continue;
|
|
18
|
+
const providerModels = /* @__PURE__ */ new Map();
|
|
19
|
+
for (const [id, model] of Object.entries(entries)) if (isValidModel(model)) {
|
|
20
|
+
providerModels.set(id, model);
|
|
21
|
+
count++;
|
|
22
|
+
}
|
|
23
|
+
if (providerModels.size > 0) models.set(provider, providerModels);
|
|
24
|
+
}
|
|
25
|
+
if (count === 0) throw new Error("models catalog contained no valid models");
|
|
26
|
+
const prev = getCatalogState();
|
|
27
|
+
globalThis[GLOBAL_KEY] = {
|
|
28
|
+
models,
|
|
29
|
+
version: (prev?.version ?? 0) + 1
|
|
30
|
+
};
|
|
31
|
+
return count;
|
|
32
|
+
}
|
|
33
|
+
function modelsCatalogVersion() {
|
|
34
|
+
return getCatalogState()?.version ?? 0;
|
|
35
|
+
}
|
|
36
|
+
function getCatalogModel(provider, modelId) {
|
|
37
|
+
const overlay = getCatalogState()?.models.get(provider)?.get(modelId);
|
|
38
|
+
if (overlay) return overlay;
|
|
39
|
+
return getModel(provider, modelId);
|
|
40
|
+
}
|
|
41
|
+
function getCatalogModels(provider) {
|
|
42
|
+
const overlay = getCatalogState()?.models.get(provider);
|
|
43
|
+
const baked = getModels(provider);
|
|
44
|
+
if (!overlay) return baked;
|
|
45
|
+
const merged = Array.from(overlay.values());
|
|
46
|
+
for (const m of baked) if (!overlay.has(m.id)) merged.push(m);
|
|
47
|
+
return merged;
|
|
48
|
+
}
|
|
49
|
+
function getCatalogProviders() {
|
|
50
|
+
const state = getCatalogState();
|
|
51
|
+
const baked = getProviders();
|
|
52
|
+
if (!state) return baked;
|
|
53
|
+
return Array.from(new Set([...state.models.keys(), ...baked]));
|
|
54
|
+
}
|
|
55
|
+
//#endregion
|
|
56
|
+
export { applyModelsCatalog, getCatalogModel, getCatalogModels, getCatalogProviders, modelsCatalogVersion };
|
|
57
|
+
|
|
58
|
+
//# sourceMappingURL=models-catalog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"models-catalog.js","names":[],"sources":["../src/models-catalog.ts"],"sourcesContent":["// runtime overlay for pi-ai's baked-in model catalog; lookups prefer the overlay\n\nimport type { Api, KnownProvider, Model } from '@earendil-works/pi-ai';\nimport { getModel, getModels, getProviders } from '@earendil-works/pi-ai';\n\nexport type ModelsCatalogJson = Record<string, Record<string, unknown>>;\n\ntype CatalogState = {\n models: Map<string, Map<string, Model<Api>>>;\n version: number;\n};\n\n// Symbol.for so every copy of this module in the isolate shares one state\nconst GLOBAL_KEY = Symbol.for('branch-fiction.models-catalog');\n\ntype GlobalWithCatalog = { [GLOBAL_KEY]?: CatalogState };\n\nfunction getCatalogState(): CatalogState | undefined {\n return (globalThis as GlobalWithCatalog)[GLOBAL_KEY];\n}\n\nfunction isValidModel(value: unknown): value is Model<Api> {\n if (value === null || typeof value !== 'object') return false;\n const m = value as Record<string, unknown>;\n return (\n typeof m.id === 'string' &&\n typeof m.name === 'string' &&\n typeof m.api === 'string' &&\n typeof m.provider === 'string' &&\n typeof m.baseUrl === 'string'\n );\n}\n\n// throws if nothing valid remains, so callers keep the previous overlay\nexport function applyModelsCatalog(json: unknown): number {\n if (json === null || typeof json !== 'object' || Array.isArray(json)) {\n throw new Error('models catalog must be an object keyed by provider');\n }\n const models = new Map<string, Map<string, Model<Api>>>();\n let count = 0;\n for (const [provider, entries] of Object.entries(json as ModelsCatalogJson)) {\n if (entries === null || typeof entries !== 'object') continue;\n const providerModels = new Map<string, Model<Api>>();\n for (const [id, model] of Object.entries(entries)) {\n if (isValidModel(model)) {\n providerModels.set(id, model);\n count++;\n }\n }\n if (providerModels.size > 0) models.set(provider, providerModels);\n }\n if (count === 0) throw new Error('models catalog contained no valid models');\n const prev = getCatalogState();\n (globalThis as GlobalWithCatalog)[GLOBAL_KEY] = {\n models,\n version: (prev?.version ?? 0) + 1\n };\n return count;\n}\n\n// bumps per applied catalog; 0 means baked data only\nexport function modelsCatalogVersion(): number {\n return getCatalogState()?.version ?? 0;\n}\n\nexport function getCatalogModel(\n provider: string,\n modelId: string\n): Model<Api> | undefined {\n const overlay = getCatalogState()?.models.get(provider)?.get(modelId);\n if (overlay) return overlay;\n return getModel(provider as KnownProvider, modelId as never) as Model<Api> | undefined;\n}\n\n// overlay first, then baked-only leftovers so configured models keep resolving\nexport function getCatalogModels(provider: string): Model<Api>[] {\n const overlay = getCatalogState()?.models.get(provider);\n const baked = getModels(provider as KnownProvider) as Model<Api>[];\n if (!overlay) return baked;\n const merged = Array.from(overlay.values());\n for (const m of baked) {\n if (!overlay.has(m.id)) merged.push(m);\n }\n return merged;\n}\n\nexport function getCatalogProviders(): string[] {\n const state = getCatalogState();\n const baked = getProviders() as string[];\n if (!state) return baked;\n return Array.from(new Set([...state.models.keys(), ...baked]));\n}\n"],"mappings":";;AAaA,MAAM,aAAa,OAAO,IAAI,+BAA+B;AAI7D,SAAS,kBAA4C;CACnD,OAAQ,WAAiC;AAC3C;AAEA,SAAS,aAAa,OAAqC;CACzD,IAAI,UAAU,QAAQ,OAAO,UAAU,UAAU,OAAO;CACxD,MAAM,IAAI;CACV,OACE,OAAO,EAAE,OAAO,YAChB,OAAO,EAAE,SAAS,YAClB,OAAO,EAAE,QAAQ,YACjB,OAAO,EAAE,aAAa,YACtB,OAAO,EAAE,YAAY;AAEzB;AAGA,SAAgB,mBAAmB,MAAuB;CACxD,IAAI,SAAS,QAAQ,OAAO,SAAS,YAAY,MAAM,QAAQ,IAAI,GACjE,MAAM,IAAI,MAAM,oDAAoD;CAEtE,MAAM,yBAAS,IAAI,IAAqC;CACxD,IAAI,QAAQ;CACZ,KAAK,MAAM,CAAC,UAAU,YAAY,OAAO,QAAQ,IAAyB,GAAG;EAC3E,IAAI,YAAY,QAAQ,OAAO,YAAY,UAAU;EACrD,MAAM,iCAAiB,IAAI,IAAwB;EACnD,KAAK,MAAM,CAAC,IAAI,UAAU,OAAO,QAAQ,OAAO,GAC9C,IAAI,aAAa,KAAK,GAAG;GACvB,eAAe,IAAI,IAAI,KAAK;GAC5B;EACF;EAEF,IAAI,eAAe,OAAO,GAAG,OAAO,IAAI,UAAU,cAAc;CAClE;CACA,IAAI,UAAU,GAAG,MAAM,IAAI,MAAM,0CAA0C;CAC3E,MAAM,OAAO,gBAAgB;CAC7B,WAAkC,cAAc;EAC9C;EACA,UAAU,MAAM,WAAW,KAAK;CAClC;CACA,OAAO;AACT;AAGA,SAAgB,uBAA+B;CAC7C,OAAO,gBAAgB,CAAC,EAAE,WAAW;AACvC;AAEA,SAAgB,gBACd,UACA,SACwB;CACxB,MAAM,UAAU,gBAAgB,CAAC,EAAE,OAAO,IAAI,QAAQ,CAAC,EAAE,IAAI,OAAO;CACpE,IAAI,SAAS,OAAO;CACpB,OAAO,SAAS,UAA2B,OAAgB;AAC7D;AAGA,SAAgB,iBAAiB,UAAgC;CAC/D,MAAM,UAAU,gBAAgB,CAAC,EAAE,OAAO,IAAI,QAAQ;CACtD,MAAM,QAAQ,UAAU,QAAyB;CACjD,IAAI,CAAC,SAAS,OAAO;CACrB,MAAM,SAAS,MAAM,KAAK,QAAQ,OAAO,CAAC;CAC1C,KAAK,MAAM,KAAK,OACd,IAAI,CAAC,QAAQ,IAAI,EAAE,EAAE,GAAG,OAAO,KAAK,CAAC;CAEvC,OAAO;AACT;AAEA,SAAgB,sBAAgC;CAC9C,MAAM,QAAQ,gBAAgB;CAC9B,MAAM,QAAQ,aAAa;CAC3B,IAAI,CAAC,OAAO,OAAO;CACnB,OAAO,MAAM,KAAK,IAAI,IAAI,CAAC,GAAG,MAAM,OAAO,KAAK,GAAG,GAAG,KAAK,CAAC,CAAC;AAC/D"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Api, KnownProvider, Model, ThinkingLevel } from "@earendil-works/pi-ai";
|
|
2
|
+
|
|
3
|
+
//#region src/pi-handle.d.ts
|
|
4
|
+
type PiModelHandle = {
|
|
5
|
+
model: Model<Api>;
|
|
6
|
+
apiKey?: string;
|
|
7
|
+
reasoning?: ThinkingLevel;
|
|
8
|
+
};
|
|
9
|
+
type BuildPiModelParams = {
|
|
10
|
+
providerType: string;
|
|
11
|
+
apiKey: string | null;
|
|
12
|
+
baseUrl: string | null;
|
|
13
|
+
modelId: string;
|
|
14
|
+
reasoning: ThinkingLevel | null;
|
|
15
|
+
};
|
|
16
|
+
declare function resolvePiProvider(providerType: string): KnownProvider | undefined;
|
|
17
|
+
declare function buildPiModel({
|
|
18
|
+
providerType,
|
|
19
|
+
apiKey,
|
|
20
|
+
baseUrl,
|
|
21
|
+
modelId,
|
|
22
|
+
reasoning
|
|
23
|
+
}: BuildPiModelParams): PiModelHandle;
|
|
24
|
+
//#endregion
|
|
25
|
+
export { BuildPiModelParams, PiModelHandle, buildPiModel, resolvePiProvider };
|
|
26
|
+
//# sourceMappingURL=pi-handle.d.ts.map
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { getCatalogModel, getCatalogProviders } from "./models-catalog.js";
|
|
2
|
+
//#region src/pi-handle.ts
|
|
3
|
+
const PI_PROVIDER_ALIASES = {
|
|
4
|
+
google_gemini: "google",
|
|
5
|
+
vercel_ai_gateway: "vercel-ai-gateway"
|
|
6
|
+
};
|
|
7
|
+
function resolvePiProvider(providerType) {
|
|
8
|
+
const alias = PI_PROVIDER_ALIASES[providerType];
|
|
9
|
+
if (alias) return alias;
|
|
10
|
+
return getCatalogProviders().includes(providerType) ? providerType : void 0;
|
|
11
|
+
}
|
|
12
|
+
const XAI_OPENAI_RESPONSES_MODEL_IDS = new Set([
|
|
13
|
+
"grok-4",
|
|
14
|
+
"grok-4-fast",
|
|
15
|
+
"grok-4-fast-non-reasoning",
|
|
16
|
+
"grok-4.20-0309-non-reasoning",
|
|
17
|
+
"grok-4.20-0309-reasoning",
|
|
18
|
+
"grok-4.3"
|
|
19
|
+
]);
|
|
20
|
+
function buildPiModel({ providerType, apiKey, baseUrl, modelId, reasoning }) {
|
|
21
|
+
const reasoningOpt = reasoning ?? void 0;
|
|
22
|
+
const piProvider = resolvePiProvider(providerType);
|
|
23
|
+
if (piProvider) {
|
|
24
|
+
const base = getCatalogModel(piProvider, modelId);
|
|
25
|
+
if (!base) throw new Error(`Unknown model "${modelId}" for provider "${piProvider}" — refresh the model catalog or pick another model`);
|
|
26
|
+
let model = baseUrl ? {
|
|
27
|
+
...base,
|
|
28
|
+
baseUrl
|
|
29
|
+
} : base;
|
|
30
|
+
if (providerType === "xai" && XAI_OPENAI_RESPONSES_MODEL_IDS.has(modelId)) model = {
|
|
31
|
+
...model,
|
|
32
|
+
api: "openai-responses",
|
|
33
|
+
thinkingLevelMap: {
|
|
34
|
+
...model.thinkingLevelMap,
|
|
35
|
+
off: "none"
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
return {
|
|
39
|
+
model,
|
|
40
|
+
apiKey: apiKey ?? void 0,
|
|
41
|
+
reasoning: reasoningOpt
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
switch (providerType) {
|
|
45
|
+
case "openai_compatible":
|
|
46
|
+
if (!baseUrl) throw new Error("\"openai_compatible\" providers require a baseUrl");
|
|
47
|
+
return {
|
|
48
|
+
model: openAiCompatibleModel(providerType, modelId, baseUrl),
|
|
49
|
+
apiKey: apiKey ?? void 0,
|
|
50
|
+
reasoning: reasoningOpt
|
|
51
|
+
};
|
|
52
|
+
case "anthropic_compatible":
|
|
53
|
+
if (!baseUrl) throw new Error("\"anthropic_compatible\" providers require a baseUrl");
|
|
54
|
+
return {
|
|
55
|
+
model: anthropicCompatibleModel(providerType, modelId, baseUrl),
|
|
56
|
+
apiKey: apiKey ?? void 0,
|
|
57
|
+
reasoning: reasoningOpt
|
|
58
|
+
};
|
|
59
|
+
case "ollama": return {
|
|
60
|
+
model: openAiCompatibleModel("ollama", modelId, baseUrl ?? "http://localhost:11434/v1"),
|
|
61
|
+
apiKey: apiKey ?? "ollama",
|
|
62
|
+
reasoning: reasoningOpt
|
|
63
|
+
};
|
|
64
|
+
default: throw new Error(`Unsupported provider type for pi-ai: "${providerType}"`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function openAiCompatibleModel(provider, modelId, baseUrl) {
|
|
68
|
+
return {
|
|
69
|
+
id: modelId,
|
|
70
|
+
name: modelId,
|
|
71
|
+
api: "openai-completions",
|
|
72
|
+
provider,
|
|
73
|
+
baseUrl,
|
|
74
|
+
reasoning: false,
|
|
75
|
+
input: ["text"],
|
|
76
|
+
cost: {
|
|
77
|
+
input: 0,
|
|
78
|
+
output: 0,
|
|
79
|
+
cacheRead: 0,
|
|
80
|
+
cacheWrite: 0
|
|
81
|
+
},
|
|
82
|
+
contextWindow: 256e3,
|
|
83
|
+
maxTokens: 32e3
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function anthropicCompatibleModel(provider, modelId, baseUrl) {
|
|
87
|
+
return {
|
|
88
|
+
id: modelId,
|
|
89
|
+
name: modelId,
|
|
90
|
+
api: "anthropic-messages",
|
|
91
|
+
provider,
|
|
92
|
+
baseUrl,
|
|
93
|
+
reasoning: false,
|
|
94
|
+
input: ["text"],
|
|
95
|
+
cost: {
|
|
96
|
+
input: 0,
|
|
97
|
+
output: 0,
|
|
98
|
+
cacheRead: 0,
|
|
99
|
+
cacheWrite: 0
|
|
100
|
+
},
|
|
101
|
+
contextWindow: 256e3,
|
|
102
|
+
maxTokens: 32e3
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
//#endregion
|
|
106
|
+
export { buildPiModel, resolvePiProvider };
|
|
107
|
+
|
|
108
|
+
//# sourceMappingURL=pi-handle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pi-handle.js","names":[],"sources":["../src/pi-handle.ts"],"sourcesContent":["import {\n type Api,\n type KnownProvider,\n type Model,\n type ThinkingLevel\n} from '@earendil-works/pi-ai';\n\nimport { getCatalogModel, getCatalogProviders } from './models-catalog';\n\nexport type PiModelHandle = {\n model: Model<Api>;\n apiKey?: string;\n reasoning?: ThinkingLevel;\n};\n\nexport type BuildPiModelParams = {\n providerType: string;\n apiKey: string | null;\n baseUrl: string | null;\n modelId: string;\n reasoning: ThinkingLevel | null;\n};\n\nconst PI_PROVIDER_ALIASES: Record<string, KnownProvider> = {\n google_gemini: 'google',\n vercel_ai_gateway: 'vercel-ai-gateway'\n};\n\n// Resolved per call: the model catalog overlay can add providers at runtime.\nexport function resolvePiProvider(providerType: string): KnownProvider | undefined {\n const alias = PI_PROVIDER_ALIASES[providerType];\n if (alias) return alias;\n return getCatalogProviders().includes(providerType)\n ? (providerType as KnownProvider)\n : undefined;\n}\n\n// xai models run via openai-responses instead of pi-ai's standard, completions API:\n// 1. it's deprecated: https://docs.x.ai/developers/model-capabilities/text/comparison\n// 2. prompt caching is non-standard with completions API\n// we're also adding a custom thinkingLevelMap.\n// todo: upstream PR? https://github.com/earendil-works/pi/issues/4308\n// (this ended up being more complex than expected, needs a full transition over to responses API as well)\nconst XAI_OPENAI_RESPONSES_MODEL_IDS = new Set([\n 'grok-4',\n 'grok-4-fast',\n 'grok-4-fast-non-reasoning',\n 'grok-4.20-0309-non-reasoning',\n 'grok-4.20-0309-reasoning',\n 'grok-4.3'\n]);\n\nexport function buildPiModel({\n providerType,\n apiKey,\n baseUrl,\n modelId,\n reasoning\n}: BuildPiModelParams): PiModelHandle {\n const reasoningOpt = reasoning ?? undefined;\n const piProvider = resolvePiProvider(providerType);\n if (piProvider) {\n const base = getCatalogModel(piProvider, modelId);\n if (!base) {\n throw new Error(\n `Unknown model \"${modelId}\" for provider \"${piProvider}\" — refresh the model catalog or pick another model`\n );\n }\n let model: Model<Api> = baseUrl ? { ...base, baseUrl } : base;\n if (providerType === 'xai' && XAI_OPENAI_RESPONSES_MODEL_IDS.has(modelId)) {\n model = {\n ...model,\n api: 'openai-responses',\n thinkingLevelMap: { ...model.thinkingLevelMap, off: 'none' }\n };\n }\n return { model, apiKey: apiKey ?? undefined, reasoning: reasoningOpt };\n }\n\n switch (providerType) {\n case 'openai_compatible':\n if (!baseUrl) {\n throw new Error('\"openai_compatible\" providers require a baseUrl');\n }\n return {\n model: openAiCompatibleModel(providerType, modelId, baseUrl),\n apiKey: apiKey ?? undefined,\n reasoning: reasoningOpt\n };\n case 'anthropic_compatible':\n if (!baseUrl) {\n throw new Error('\"anthropic_compatible\" providers require a baseUrl');\n }\n return {\n model: anthropicCompatibleModel(providerType, modelId, baseUrl),\n apiKey: apiKey ?? undefined,\n reasoning: reasoningOpt\n };\n case 'ollama':\n return {\n model: openAiCompatibleModel(\n 'ollama',\n modelId,\n baseUrl ?? 'http://localhost:11434/v1'\n ),\n apiKey: apiKey ?? 'ollama',\n reasoning: reasoningOpt\n };\n default:\n throw new Error(`Unsupported provider type for pi-ai: \"${providerType}\"`);\n }\n}\n\nfunction openAiCompatibleModel(\n provider: string,\n modelId: string,\n baseUrl: string\n): Model<'openai-completions'> {\n return {\n id: modelId,\n name: modelId,\n api: 'openai-completions',\n provider,\n baseUrl,\n reasoning: false,\n input: ['text'],\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },\n contextWindow: 256000,\n maxTokens: 32000\n };\n}\n\nfunction anthropicCompatibleModel(\n provider: string,\n modelId: string,\n baseUrl: string\n): Model<'anthropic-messages'> {\n return {\n id: modelId,\n name: modelId,\n api: 'anthropic-messages',\n provider,\n baseUrl,\n reasoning: false,\n input: ['text'],\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },\n contextWindow: 256000,\n maxTokens: 32000\n };\n}\n"],"mappings":";;AAuBA,MAAM,sBAAqD;CACzD,eAAe;CACf,mBAAmB;AACrB;AAGA,SAAgB,kBAAkB,cAAiD;CACjF,MAAM,QAAQ,oBAAoB;CAClC,IAAI,OAAO,OAAO;CAClB,OAAO,oBAAoB,CAAC,CAAC,SAAS,YAAY,IAC7C,eACD,KAAA;AACN;AAQA,MAAM,iCAAiC,IAAI,IAAI;CAC7C;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AAED,SAAgB,aAAa,EAC3B,cACA,QACA,SACA,SACA,aACoC;CACpC,MAAM,eAAe,aAAa,KAAA;CAClC,MAAM,aAAa,kBAAkB,YAAY;CACjD,IAAI,YAAY;EACd,MAAM,OAAO,gBAAgB,YAAY,OAAO;EAChD,IAAI,CAAC,MACH,MAAM,IAAI,MACR,kBAAkB,QAAQ,kBAAkB,WAAW,oDACzD;EAEF,IAAI,QAAoB,UAAU;GAAE,GAAG;GAAM;EAAQ,IAAI;EACzD,IAAI,iBAAiB,SAAS,+BAA+B,IAAI,OAAO,GACtE,QAAQ;GACN,GAAG;GACH,KAAK;GACL,kBAAkB;IAAE,GAAG,MAAM;IAAkB,KAAK;GAAO;EAC7D;EAEF,OAAO;GAAE;GAAO,QAAQ,UAAU,KAAA;GAAW,WAAW;EAAa;CACvE;CAEA,QAAQ,cAAR;EACE,KAAK;GACH,IAAI,CAAC,SACH,MAAM,IAAI,MAAM,mDAAiD;GAEnE,OAAO;IACL,OAAO,sBAAsB,cAAc,SAAS,OAAO;IAC3D,QAAQ,UAAU,KAAA;IAClB,WAAW;GACb;EACF,KAAK;GACH,IAAI,CAAC,SACH,MAAM,IAAI,MAAM,sDAAoD;GAEtE,OAAO;IACL,OAAO,yBAAyB,cAAc,SAAS,OAAO;IAC9D,QAAQ,UAAU,KAAA;IAClB,WAAW;GACb;EACF,KAAK,UACH,OAAO;GACL,OAAO,sBACL,UACA,SACA,WAAW,2BACb;GACA,QAAQ,UAAU;GAClB,WAAW;EACb;EACF,SACE,MAAM,IAAI,MAAM,yCAAyC,aAAa,EAAE;CAC5E;AACF;AAEA,SAAS,sBACP,UACA,SACA,SAC6B;CAC7B,OAAO;EACL,IAAI;EACJ,MAAM;EACN,KAAK;EACL;EACA;EACA,WAAW;EACX,OAAO,CAAC,MAAM;EACd,MAAM;GAAE,OAAO;GAAG,QAAQ;GAAG,WAAW;GAAG,YAAY;EAAE;EACzD,eAAe;EACf,WAAW;CACb;AACF;AAEA,SAAS,yBACP,UACA,SACA,SAC6B;CAC7B,OAAO;EACL,IAAI;EACJ,MAAM;EACN,KAAK;EACL;EACA;EACA,WAAW;EACX,OAAO,CAAC,MAAM;EACd,MAAM;GAAE,OAAO;GAAG,QAAQ;GAAG,WAAW;GAAG,YAAY;EAAE;EACzD,eAAe;EACf,WAAW;CACb;AACF"}
|