@frankieai/core 0.2.1 → 0.2.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 +151 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +25 -1
- package/dist/index.d.ts +25 -1
- package/dist/index.js +150 -22
- package/dist/index.js.map +1 -1
- package/package.json +4 -1
package/dist/index.cjs
CHANGED
|
@@ -39,6 +39,7 @@ __export(index_exports, {
|
|
|
39
39
|
featuresConfigSchema: () => featuresConfigSchema,
|
|
40
40
|
frankieConfigSchema: () => frankieConfigSchema,
|
|
41
41
|
limitsConfigSchema: () => limitsConfigSchema,
|
|
42
|
+
matchPath: () => matchPath,
|
|
42
43
|
mcpServerSchema: () => mcpServerSchema,
|
|
43
44
|
modelChoiceSchema: () => modelChoiceSchema,
|
|
44
45
|
modelConfigSchema: () => modelConfigSchema,
|
|
@@ -153,10 +154,16 @@ var modelConfigSchema = import_zod2.z.object({
|
|
|
153
154
|
*/
|
|
154
155
|
provider: import_zod2.z.string().min(1).default("fireworks"),
|
|
155
156
|
/**
|
|
156
|
-
* Model id within that provider. The default is Kimi
|
|
157
|
-
*
|
|
157
|
+
* Model id within that provider. The default is Kimi K3 on Fireworks, served
|
|
158
|
+
* over the OpenAI-completions API. Crucially for an embedded chat widget, that
|
|
159
|
+
* endpoint returns the model's reasoning in a separate `reasoning_content`
|
|
160
|
+
* field (surfaced as a thinking stream the client gates off), rather than
|
|
161
|
+
* inlining it in the visible text channel — so the model's private planning
|
|
162
|
+
* never leaks into the user-facing reply. The earlier Anthropic-Messages Kimi
|
|
163
|
+
* builds emitted that reasoning as plain text on Fireworks, which no marker or
|
|
164
|
+
* prompt reliably suppressed.
|
|
158
165
|
*/
|
|
159
|
-
model: import_zod2.z.string().default("accounts/fireworks/models/kimi-
|
|
166
|
+
model: import_zod2.z.string().default("accounts/fireworks/models/kimi-k3"),
|
|
160
167
|
/** Env var holding the provider key. Never put the key in config itself. */
|
|
161
168
|
apiKeyEnv: import_zod2.z.string().default("FIREWORKS_API_KEY"),
|
|
162
169
|
/** Override the provider's base URL (self-hosted gateways, tests). */
|
|
@@ -230,7 +237,16 @@ var apiCapabilitySchema = import_zod2.z.object({
|
|
|
230
237
|
operationId: import_zod2.z.string().min(1),
|
|
231
238
|
method: import_zod2.z.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]),
|
|
232
239
|
path: import_zod2.z.string().min(1),
|
|
233
|
-
summary: import_zod2.z.string().optional()
|
|
240
|
+
summary: import_zod2.z.string().optional(),
|
|
241
|
+
/**
|
|
242
|
+
* True when this is a write operation the spec requires auth for, but the
|
|
243
|
+
* source has no way to authorize it as the specific calling user — no
|
|
244
|
+
* forwarded user token or claim, only (at best) a static/shared credential.
|
|
245
|
+
* A configured `security` credential never clears this on its own: it
|
|
246
|
+
* authorizes every call the same way regardless of who is chatting, which
|
|
247
|
+
* is a backend gap to flag, not something a spec or credential change fixes.
|
|
248
|
+
*/
|
|
249
|
+
authGap: import_zod2.z.boolean().default(false)
|
|
234
250
|
});
|
|
235
251
|
var apiSourceSchema = import_zod2.z.object({
|
|
236
252
|
name: import_zod2.z.string().min(1).regex(/^[a-z0-9][a-z0-9_-]*$/i, "API source names must be alphanumeric with - or _"),
|
|
@@ -269,6 +285,17 @@ var apiSourceSchema = import_zod2.z.object({
|
|
|
269
285
|
forwardUserTokenAs: import_zod2.z.string().optional(),
|
|
270
286
|
/** Extra claims from the user token to forward as `x-frankie-<claim>`. */
|
|
271
287
|
forwardUserClaims: import_zod2.z.array(import_zod2.z.string()).default([]),
|
|
288
|
+
/**
|
|
289
|
+
* Credentials for the spec's own `components.securitySchemes`, keyed by
|
|
290
|
+
* scheme name. Frankie applies each per-operation `security` requirement at
|
|
291
|
+
* the location and with the prefix the scheme declares (an `apiKey` scheme
|
|
292
|
+
* goes to its named header/query/cookie; an `http` `bearer` or `oauth2`
|
|
293
|
+
* scheme becomes an `Authorization: Bearer` header; `http` `basic` takes a
|
|
294
|
+
* `user:pass` value and is base64-encoded automatically). Without this, a
|
|
295
|
+
* spec with per-operation auth falls back to the source's static `headers`
|
|
296
|
+
* for every operation, which is often wrong for at least some of them.
|
|
297
|
+
*/
|
|
298
|
+
security: import_zod2.z.record(import_zod2.z.string(), import_zod2.z.string()).default({}),
|
|
272
299
|
/** Per-call timeout. */
|
|
273
300
|
timeoutMs: import_zod2.z.number().int().positive().default(3e4),
|
|
274
301
|
/** Response bodies longer than this are truncated before the model sees them. */
|
|
@@ -354,8 +381,16 @@ var frankieConfigSchema = import_zod2.z.object({
|
|
|
354
381
|
/** Opt-in end-user chat capabilities (attachments, model picker). */
|
|
355
382
|
features: featuresConfigSchema
|
|
356
383
|
});
|
|
384
|
+
var LEAKY_MODEL_MIGRATION = {
|
|
385
|
+
"accounts/fireworks/models/kimi-k2p7-code": "accounts/fireworks/models/kimi-k3"
|
|
386
|
+
};
|
|
357
387
|
function parseFrankieConfig(input) {
|
|
358
|
-
|
|
388
|
+
const config = frankieConfigSchema.parse(input);
|
|
389
|
+
if (config.model.provider === "fireworks") {
|
|
390
|
+
const migrated = LEAKY_MODEL_MIGRATION[config.model.model];
|
|
391
|
+
if (migrated) config.model.model = migrated;
|
|
392
|
+
}
|
|
393
|
+
return config;
|
|
359
394
|
}
|
|
360
395
|
function defineFrankieConfig(config) {
|
|
361
396
|
return config;
|
|
@@ -500,20 +535,24 @@ export async function POST() {
|
|
|
500
535
|
} catch {
|
|
501
536
|
}
|
|
502
537
|
}
|
|
538
|
+
const middlewareMatchers = nextMiddlewareMatchers(files, sourceRoot, root);
|
|
503
539
|
const paths = {};
|
|
540
|
+
let withheld = 0;
|
|
504
541
|
for (const [file, content] of Object.entries(files)) {
|
|
505
542
|
if (/\.(test|spec)\.[tj]sx?$/.test(file)) continue;
|
|
506
543
|
const nextRoute = file.match(/(?:^|\/)app(\/.*)\/route\.[tj]s$/);
|
|
507
544
|
if (nextRoute) {
|
|
508
545
|
const path = nextRoute[1].replace(/\/\([^/]+\)/g, "").replace(/\[([^\]]+)\]/g, "{$1}");
|
|
509
546
|
if (path.includes("...") || /\/(?:auth|frankie)(?:\/|$)/.test(path)) continue;
|
|
547
|
+
const guarded = matchesAnyMatcher(middlewareMatchers, path);
|
|
510
548
|
for (const method of ["GET", "POST", "PUT", "PATCH", "DELETE"]) {
|
|
511
|
-
if (new RegExp("export\\s+(?:(?:async\\s+)?function\\s+" + method + "\\b|(?:const|let)\\s+" + method + "\\s*=)").test(content))
|
|
512
|
-
addInstallOperation(paths, method, path, file);
|
|
549
|
+
if (new RegExp("export\\s+(?:(?:async\\s+)?function\\s+" + method + "\\b|(?:const|let)\\s+" + method + "\\s*=)").test(content)) {
|
|
550
|
+
if (!addInstallOperation(paths, method, path, file, guarded)) withheld += 1;
|
|
551
|
+
}
|
|
513
552
|
}
|
|
514
553
|
} else if (/\.[tj]s$/.test(file) && /(?:routes?|api)\//.test(file)) {
|
|
515
|
-
for (const match of content.matchAll(/\b(?:app|router|fastify)\.(get|post|put|patch|delete)(?:\s*<[^;]{1,1600}?>)?\s*\(\s*["']([^"']+)["']
|
|
516
|
-
addInstallOperation(paths, match[1].toUpperCase(), match[2].replace(/:([A-Za-z0-9_]+)/g, "{$1}"), file);
|
|
554
|
+
for (const match of content.matchAll(/\b(?:app|router|fastify)\.(get|post|put|patch|delete)(?:\s*<[^;]{1,1600}?>)?\s*\(\s*["']([^"']+)["'](\s*,\s*[A-Za-z_$][\w$]*\s*,)?/g))
|
|
555
|
+
if (!addInstallOperation(paths, match[1].toUpperCase(), match[2].replace(/:([A-Za-z0-9_]+)/g, "{$1}"), file, Boolean(match[3]))) withheld += 1;
|
|
517
556
|
}
|
|
518
557
|
}
|
|
519
558
|
doc ??= { openapi: "3.0.3", info: { title: workspace.name + " API", version: "1.0.0", description: "Generated route inventory. Review schemas, authorization, and the deployed origin before enabling tools." }, paths };
|
|
@@ -522,6 +561,7 @@ export async function POST() {
|
|
|
522
561
|
const operationCount = Object.values(doc.paths).reduce((n, item) => n + Object.keys(item).filter((m) => /^(get|post|put|patch|delete)$/.test(m)).length, 0);
|
|
523
562
|
out.set(specPath, JSON.stringify(doc, null, 2) + "\n");
|
|
524
563
|
if (!operationCount) notes.push("No supported API routes detected. The OpenAPI draft has no operations; add the real schema before connecting tools.");
|
|
564
|
+
if (withheld) notes.push(`Withheld ${withheld} newly discovered write operation(s) with no detected authorization guard (an Express/Fastify middleware argument, or a Next.js middleware.ts matcher covering the route). Add them to the spec yourself once they're protected.`);
|
|
525
565
|
notes.push("Generated OpenAPI includes read and write routes. Review schemas before approving tools; new connections still default to GET-only access.");
|
|
526
566
|
notes.push("Verify the public spec URL is reachable without dashboard authentication before connecting it.");
|
|
527
567
|
notes.push("The token endpoint issues a signed user JWT, but your product API must validate it and enforce user/resource permissions before exposing tools.");
|
|
@@ -548,8 +588,10 @@ ${notes.map((n) => "- " + n).join("\n")}
|
|
|
548
588
|
assertNoCommittedFrankieKeys(planned);
|
|
549
589
|
return { files: planned, root, chatPath, specPath, operationCount, needsAuthSetup, notes };
|
|
550
590
|
}
|
|
551
|
-
|
|
552
|
-
|
|
591
|
+
var WRITE_METHODS = /* @__PURE__ */ new Set(["post", "put", "patch", "delete"]);
|
|
592
|
+
function addInstallOperation(paths, method, path, file, guarded) {
|
|
593
|
+
if (path.includes("*") || !path.startsWith("/") || /\/(?:auth|frankie)(?:\/|$)/.test(path)) return true;
|
|
594
|
+
if (WRITE_METHODS.has(method.toLowerCase()) && !guarded) return false;
|
|
553
595
|
(paths[path] ??= {})[method.toLowerCase()] = {
|
|
554
596
|
operationId: (method.toLowerCase() + "_" + path).replace(/[^a-zA-Z0-9_]+/g, "_"),
|
|
555
597
|
summary: method + " " + path,
|
|
@@ -558,6 +600,32 @@ function addInstallOperation(paths, method, path, file) {
|
|
|
558
600
|
responses: { "200": { description: "Successful response" } },
|
|
559
601
|
"x-frankie-review-required": true
|
|
560
602
|
};
|
|
603
|
+
return true;
|
|
604
|
+
}
|
|
605
|
+
function nextMiddlewareMatchers(files, sourceRoot, root) {
|
|
606
|
+
const candidates = [.../* @__PURE__ */ new Set([sourceRoot + "middleware.ts", sourceRoot + "middleware.js", root + "middleware.ts", root + "middleware.js"])];
|
|
607
|
+
for (const candidate of candidates) {
|
|
608
|
+
const content = files[candidate];
|
|
609
|
+
if (typeof content !== "string") continue;
|
|
610
|
+
const config = content.match(/export\s+const\s+config\s*=\s*\{([^}]*)\}/);
|
|
611
|
+
if (!config) continue;
|
|
612
|
+
const matcher = config[1].match(/matcher\s*:\s*(\[[^\]]*\]|["'`][^"'`]*["'`])/);
|
|
613
|
+
if (!matcher) return [];
|
|
614
|
+
const raw = matcher[1];
|
|
615
|
+
return raw.startsWith("[") ? [...raw.matchAll(/["'`]([^"'`]+)["'`]/g)].map((m) => m[1]) : [raw.slice(1, -1)];
|
|
616
|
+
}
|
|
617
|
+
return [];
|
|
618
|
+
}
|
|
619
|
+
function matchesAnyMatcher(matchers, path) {
|
|
620
|
+
return matchers.some((pattern) => {
|
|
621
|
+
if (/[()|]/.test(pattern)) return false;
|
|
622
|
+
const glob = pattern.replace(/:[A-Za-z0-9_]+\*/g, "**").replace(/:[A-Za-z0-9_]+\+?/g, "*");
|
|
623
|
+
const escaped = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
624
|
+
const trailingDeep = escaped.endsWith("/**");
|
|
625
|
+
const body = trailingDeep ? escaped.slice(0, -3) : escaped;
|
|
626
|
+
const regexBody = body.split("**").map((chunk) => chunk.replace(/\*/g, "[^/]*")).join(".*") + (trailingDeep ? "(?:/.*)?" : "");
|
|
627
|
+
return new RegExp(`^${regexBody}$`).test(path);
|
|
628
|
+
});
|
|
561
629
|
}
|
|
562
630
|
|
|
563
631
|
// src/sdk-updates.ts
|
|
@@ -584,20 +652,20 @@ jobs:
|
|
|
584
652
|
outputs:
|
|
585
653
|
base: $GH steps.base.outputs.sha }}
|
|
586
654
|
steps:
|
|
587
|
-
- uses: actions/checkout@
|
|
655
|
+
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09
|
|
588
656
|
with:
|
|
589
657
|
persist-credentials: false
|
|
590
658
|
- id: base
|
|
591
659
|
run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
|
|
592
|
-
- uses: actions/setup-node@
|
|
660
|
+
- uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444
|
|
593
661
|
with:
|
|
594
|
-
node-version:
|
|
662
|
+
node-version: 24
|
|
595
663
|
- name: Install pinned pnpm
|
|
596
664
|
run: npm install --global --ignore-scripts pnpm@10.33.0
|
|
597
665
|
- name: Prepare SDK and lockfile together
|
|
598
666
|
run: |
|
|
599
667
|
test -f pnpm-lock.yaml
|
|
600
|
-
pnpm --recursive --include-workspace-root update '@frankieai/react@latest' '@frankieai/core@latest' '@frankieai/cli@latest' '@frankieai/server@latest' --lockfile-only --ignore-scripts --ignore-pnpmfile
|
|
668
|
+
pnpm --recursive --include-workspace-root update '@frankieai/react@latest' '@frankieai/core@latest' '@frankieai/cli@latest' '@frankieai/server@latest' --lockfile-only --ignore-scripts --ignore-pnpmfile --config.manage-package-manager-versions=false --config.package-manager-strict-version=false
|
|
601
669
|
- name: Package allowlisted changes
|
|
602
670
|
run: |
|
|
603
671
|
node --input-type=module <<'NODE'
|
|
@@ -613,7 +681,7 @@ jobs:
|
|
|
613
681
|
mkdirSync('frankie-update-artifact', {recursive:true});
|
|
614
682
|
writeFileSync('frankie-update-artifact/changes.json', JSON.stringify(files));
|
|
615
683
|
NODE
|
|
616
|
-
- uses: actions/upload-artifact@
|
|
684
|
+
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
|
|
617
685
|
with:
|
|
618
686
|
name: frankie-sdk-update
|
|
619
687
|
path: frankie-update-artifact/changes.json
|
|
@@ -627,11 +695,11 @@ jobs:
|
|
|
627
695
|
pull-requests: write
|
|
628
696
|
steps:
|
|
629
697
|
# Separate runner: no checkout, repository hooks, install scripts, or builds.
|
|
630
|
-
- uses: actions/download-artifact@
|
|
698
|
+
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
|
|
631
699
|
with:
|
|
632
700
|
name: frankie-sdk-update
|
|
633
701
|
path: artifact
|
|
634
|
-
- uses: actions/github-script@
|
|
702
|
+
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd
|
|
635
703
|
env:
|
|
636
704
|
BASE_SHA: $GH needs.prepare.outputs.base }}
|
|
637
705
|
with:
|
|
@@ -648,6 +716,7 @@ jobs:
|
|
|
648
716
|
if (current.data.object.sha !== base) throw Error('Default branch changed. Run the updater again.');
|
|
649
717
|
const existing = await github.paginate(github.rest.pulls.list, {owner,repo,state:'open',per_page:100});
|
|
650
718
|
if (existing.some(pr => pr.head.repo?.full_name === owner + '/' + repo && pr.head.ref.startsWith('frankie/sdk-update-'))) { core.notice('An SDK update PR already exists; review it first.'); return; }
|
|
719
|
+
const canonical = value => Array.isArray(value) ? '[' + value.map(canonical).join(',') + ']' : value && typeof value === 'object' ? '{' + Object.keys(value).sort().map(key => JSON.stringify(key) + ':' + canonical(value[key])).join(',') + '}' : JSON.stringify(value);
|
|
651
720
|
const clean = value => {
|
|
652
721
|
const copy = JSON.parse(JSON.stringify(value));
|
|
653
722
|
for (const group of ['dependencies','devDependencies','optionalDependencies']) {
|
|
@@ -655,6 +724,7 @@ jobs:
|
|
|
655
724
|
}
|
|
656
725
|
return copy;
|
|
657
726
|
};
|
|
727
|
+
let sdkChanged = false;
|
|
658
728
|
const seen = new Set();
|
|
659
729
|
for (const file of files) {
|
|
660
730
|
if (!file || typeof file.path !== 'string' || typeof file.content !== 'string' || seen.has(file.path)) throw Error('Invalid update file');
|
|
@@ -665,14 +735,16 @@ jobs:
|
|
|
665
735
|
const original = await github.rest.repos.getContent({owner,repo,path:file.path,ref:base});
|
|
666
736
|
const before = JSON.parse(Buffer.from(original.data.content, 'base64').toString());
|
|
667
737
|
const after = JSON.parse(file.content);
|
|
668
|
-
if (
|
|
738
|
+
if (canonical(clean(before)) !== canonical(clean(after))) throw Error('Non-SDK manifest change in ' + file.path);
|
|
669
739
|
for (const group of ['dependencies','devDependencies','optionalDependencies']) {
|
|
670
740
|
const sdkKeys = value => Object.keys(value[group] || {}).filter(key => key.startsWith('@frankieai/')).sort();
|
|
671
741
|
if (JSON.stringify(sdkKeys(before)) !== JSON.stringify(sdkKeys(after))) throw Error('SDK dependency added or removed');
|
|
742
|
+
if (sdkKeys(after).some(key => before[group][key] !== after[group][key])) sdkChanged = true;
|
|
672
743
|
for (const [key, value] of Object.entries(after[group] || {})) if (key.startsWith('@frankieai/') && !/^[~^]?\d+\.\d+\.\d+$/.test(value)) throw Error('Only stable SDK releases are supported');
|
|
673
744
|
}
|
|
674
745
|
}
|
|
675
746
|
}
|
|
747
|
+
if (!sdkChanged) { core.notice('Frankie SDK versions are unchanged; no formatting-only update PR is needed.'); return; }
|
|
676
748
|
if (!seen.has('pnpm-lock.yaml')) throw Error('SDK update must include the lockfile');
|
|
677
749
|
const parent = await github.rest.git.getCommit({owner,repo,commit_sha:base});
|
|
678
750
|
const tree = await github.rest.git.createTree({owner,repo,base_tree:parent.data.tree.sha,tree:files.map(f => ({...f,mode:'100644',type:'blob'}))});
|
|
@@ -720,15 +792,71 @@ function validateApiDocument(value) {
|
|
|
720
792
|
function operationId(method, path, op) {
|
|
721
793
|
return typeof op.operationId === "string" ? op.operationId : `${method}_${path.replace(/[{}]/g, "").replace(/[^a-zA-Z0-9]+/g, "_").replace(/^_|_$/g, "")}`;
|
|
722
794
|
}
|
|
795
|
+
function resolvePointer(doc, ref) {
|
|
796
|
+
let current = doc;
|
|
797
|
+
for (const segment of ref.slice(2).split("/")) {
|
|
798
|
+
if (!current || typeof current !== "object") return void 0;
|
|
799
|
+
current = current[segment.replace(/~1/g, "/").replace(/~0/g, "~")];
|
|
800
|
+
}
|
|
801
|
+
return current;
|
|
802
|
+
}
|
|
803
|
+
function reachableDefinitions(doc, roots) {
|
|
804
|
+
const resolved = {};
|
|
805
|
+
const visit = (node) => {
|
|
806
|
+
if (Array.isArray(node)) {
|
|
807
|
+
for (const entry of node) visit(entry);
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
810
|
+
if (!node || typeof node !== "object") return;
|
|
811
|
+
for (const [key, value] of Object.entries(node)) {
|
|
812
|
+
if (key === "$ref" && typeof value === "string") {
|
|
813
|
+
if (value in resolved) continue;
|
|
814
|
+
const target = resolvePointer(doc, value);
|
|
815
|
+
resolved[value] = target ?? null;
|
|
816
|
+
visit(target);
|
|
817
|
+
continue;
|
|
818
|
+
}
|
|
819
|
+
visit(value);
|
|
820
|
+
}
|
|
821
|
+
};
|
|
822
|
+
for (const root of roots) visit(root);
|
|
823
|
+
return resolved;
|
|
824
|
+
}
|
|
723
825
|
function apiOperations(doc) {
|
|
724
826
|
const operations = /* @__PURE__ */ new Map();
|
|
725
|
-
const
|
|
726
|
-
for (const [path, item] of Object.entries(paths)) {
|
|
827
|
+
for (const [path, item] of Object.entries(doc.paths)) {
|
|
727
828
|
const parameters = Object.fromEntries(Object.entries(item).filter(([key]) => !apiMethods.includes(key)));
|
|
728
|
-
for (const method of apiMethods)
|
|
829
|
+
for (const method of apiMethods) {
|
|
830
|
+
const operation = item[method];
|
|
831
|
+
if (!operation) continue;
|
|
832
|
+
operations.set(method.toUpperCase() + " " + path, {
|
|
833
|
+
id: operationId(method, path, operation),
|
|
834
|
+
// Fingerprint what the operation's contract actually depends on: itself,
|
|
835
|
+
// the path item's shared fields, the definitions it reaches by `$ref`, and
|
|
836
|
+
// the document-level `security` that applies only where it declares none.
|
|
837
|
+
// Folding in the whole document instead meant one unrelated schema — or an
|
|
838
|
+
// `openapi` version bump — revoked every approval on the source.
|
|
839
|
+
// Document-level `servers` stays out deliberately: a refresh whose servers
|
|
840
|
+
// moved is already rejected unless the operator pinned an explicit baseUrl,
|
|
841
|
+
// and that override wins over the spec anyway (see agent/openapi.ts).
|
|
842
|
+
fingerprint: canonicalJson({
|
|
843
|
+
operation,
|
|
844
|
+
parameters,
|
|
845
|
+
security: operation["security"] === void 0 ? doc.security ?? null : null,
|
|
846
|
+
definitions: reachableDefinitions(doc, [operation, parameters])
|
|
847
|
+
})
|
|
848
|
+
});
|
|
849
|
+
}
|
|
729
850
|
}
|
|
730
851
|
return operations;
|
|
731
852
|
}
|
|
853
|
+
function matchPath(glob, path) {
|
|
854
|
+
const escaped = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
855
|
+
const trailingDeep = escaped.endsWith("/**");
|
|
856
|
+
const body = trailingDeep ? escaped.slice(0, -3) : escaped;
|
|
857
|
+
const pattern = body.split("**").map((chunk) => chunk.replace(/\*/g, "[^/]*")).join(".*") + (trailingDeep ? "(?:/.*)?" : "");
|
|
858
|
+
return new RegExp(`^${pattern}$`).test(path);
|
|
859
|
+
}
|
|
732
860
|
function compareApiDocuments(before, after) {
|
|
733
861
|
const old = apiOperations(before), next = apiOperations(after);
|
|
734
862
|
return {
|
|
@@ -759,6 +887,7 @@ function compareApiDocuments(before, after) {
|
|
|
759
887
|
featuresConfigSchema,
|
|
760
888
|
frankieConfigSchema,
|
|
761
889
|
limitsConfigSchema,
|
|
890
|
+
matchPath,
|
|
762
891
|
mcpServerSchema,
|
|
763
892
|
modelChoiceSchema,
|
|
764
893
|
modelConfigSchema,
|