@wenathlan/extension 1.1.55 → 1.1.57
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/README.md +4 -3
- package/dist/agentstream.d.ts +155 -0
- package/dist/agentstream.d.ts.map +1 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +945 -22
- package/dist/index.js.map +4 -4
- package/dist/llm.d.ts +238 -0
- package/dist/llm.d.ts.map +1 -0
- package/dist/mcpserver.d.ts +11 -4
- package/dist/mcpserver.d.ts.map +1 -1
- package/dist/memory.d.ts +122 -1
- package/dist/memory.d.ts.map +1 -1
- package/dist/modelroute.d.ts +43 -0
- package/dist/modelroute.d.ts.map +1 -0
- package/dist/policy.d.ts +57 -1
- package/dist/policy.d.ts.map +1 -1
- package/dist/promptlibrary.d.ts +33 -0
- package/dist/promptlibrary.d.ts.map +1 -0
- package/dist/protocol.d.ts +259 -0
- package/dist/protocol.d.ts.map +1 -1
- package/dist/types.d.ts +388 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/extension/dist/background.js +1604 -39
- package/extension/dist/background.js.map +4 -4
- package/extension/dist/manifest.json +1 -1
- package/extension/dist/pagebridge.js.map +1 -1
- package/extension/dist/popup.html +1 -1
- package/extension/dist/popup.js +14 -1
- package/extension/dist/popup.js.map +2 -2
- package/extension/dist/sidepanel.html +3 -1
- package/extension/dist/sidepanel.js +610 -5
- package/extension/dist/sidepanel.js.map +2 -2
- package/extension/manifest.json +1 -1
- package/package.json +1 -1
|
@@ -2510,6 +2510,224 @@ var sessionmemory = class {
|
|
|
2510
2510
|
async setstreamchannels(channels) {
|
|
2511
2511
|
return this.adapter.set("mcpchannels", channels);
|
|
2512
2512
|
}
|
|
2513
|
+
/** Returns every client event subscription with its kinds and filters, newest first. */
|
|
2514
|
+
async geteventsubscriptions() {
|
|
2515
|
+
return await this.adapter.get("mcpeventsubscriptions") ?? [];
|
|
2516
|
+
}
|
|
2517
|
+
/** Replaces the stored event subscription set after one subscribe, unsubscribe or delivery sweep. */
|
|
2518
|
+
async seteventsubscriptions(subscriptions) {
|
|
2519
|
+
return this.adapter.set("mcpeventsubscriptions", subscriptions);
|
|
2520
|
+
}
|
|
2521
|
+
/** Returns every page state resource watcher with its baseline, newest first. */
|
|
2522
|
+
async getresourcewatches() {
|
|
2523
|
+
return await this.adapter.get("mcpresourcewatches") ?? [];
|
|
2524
|
+
}
|
|
2525
|
+
/** Replaces the stored resource watcher set after one watch, unwatch or delta push. */
|
|
2526
|
+
async setresourcewatches(watches) {
|
|
2527
|
+
return this.adapter.set("mcpresourcewatches", watches);
|
|
2528
|
+
}
|
|
2529
|
+
/** Returns every sampling request with its provenance, newest first. */
|
|
2530
|
+
async getsamplingrequests() {
|
|
2531
|
+
return await this.adapter.get("mcpsampling") ?? [];
|
|
2532
|
+
}
|
|
2533
|
+
/** Replaces the stored sampling request set after one request or answer. */
|
|
2534
|
+
async setsamplingrequests(requests) {
|
|
2535
|
+
return this.adapter.set("mcpsampling", requests);
|
|
2536
|
+
}
|
|
2537
|
+
/** Returns every stored idempotency record for replay, newest first. */
|
|
2538
|
+
async getidempotencyrecords() {
|
|
2539
|
+
return await this.adapter.get("mcpidempotency") ?? [];
|
|
2540
|
+
}
|
|
2541
|
+
/** Replaces the stored idempotency record set after one store or expiry sweep. */
|
|
2542
|
+
async setidempotencyrecords(records) {
|
|
2543
|
+
return this.adapter.set("mcpidempotency", records);
|
|
2544
|
+
}
|
|
2545
|
+
/** Returns every per client rate limit counter with its window and budget. */
|
|
2546
|
+
async getcallratelimits() {
|
|
2547
|
+
return await this.adapter.get("mcpcallratelimits") ?? [];
|
|
2548
|
+
}
|
|
2549
|
+
/** Replaces the stored per client rate limit set after one configuration or counted call. */
|
|
2550
|
+
async setcallratelimits(limits) {
|
|
2551
|
+
return this.adapter.set("mcpcallratelimits", limits);
|
|
2552
|
+
}
|
|
2553
|
+
/** Returns every stored batch call with its per item outcomes, newest first. */
|
|
2554
|
+
async getbatchcalls() {
|
|
2555
|
+
return await this.adapter.get("mcpbatchcalls") ?? [];
|
|
2556
|
+
}
|
|
2557
|
+
/** Upserts one batch call by its id with the per item outcomes riding the record. */
|
|
2558
|
+
async setbatchcall(batch) {
|
|
2559
|
+
await this.adapter.set("mcpbatchcalls", [batch, ...(await this.getbatchcalls()).filter((candidate) => candidate.id !== batch.id)]);
|
|
2560
|
+
}
|
|
2561
|
+
/** Returns every call context of the call runtime, newest first. */
|
|
2562
|
+
async getcallcontexts() {
|
|
2563
|
+
return await this.adapter.get("mcpcallcontexts") ?? [];
|
|
2564
|
+
}
|
|
2565
|
+
/** Replaces the stored call context set after one begin, end or cancellation. */
|
|
2566
|
+
async setcallcontexts(contexts) {
|
|
2567
|
+
return this.adapter.set("mcpcallcontexts", contexts);
|
|
2568
|
+
}
|
|
2569
|
+
/** Returns every stored tool mock for client testing. */
|
|
2570
|
+
async gettoolmocks() {
|
|
2571
|
+
return await this.adapter.get("mcptoolmocks") ?? [];
|
|
2572
|
+
}
|
|
2573
|
+
/** Upserts one tool mock by its tool name or removes it when the canned result is absent. */
|
|
2574
|
+
async settoolmock(mock) {
|
|
2575
|
+
await this.adapter.set("mcptoolmocks", [mock, ...(await this.gettoolmocks()).filter((candidate) => candidate.tool !== mock.tool)]);
|
|
2576
|
+
}
|
|
2577
|
+
/** Removes one tool mock so its tool returns to the real gates. */
|
|
2578
|
+
async removetoolmock(tool) {
|
|
2579
|
+
await this.adapter.set("mcptoolmocks", (await this.gettoolmocks()).filter((candidate) => candidate.tool !== tool));
|
|
2580
|
+
}
|
|
2581
|
+
/** Stores one stream chunk of a progressive tool result under the recent chunk window of 25 records. */
|
|
2582
|
+
async addstreamchunk(chunk) {
|
|
2583
|
+
await this.adapter.set("mcpstreamchunks", [chunk, ...(await this.getstreamchunks()).slice(0, 24)]);
|
|
2584
|
+
}
|
|
2585
|
+
/** Returns the recent stream chunks of progressive tool results, newest first. */
|
|
2586
|
+
async getstreamchunks() {
|
|
2587
|
+
return await this.adapter.get("mcpstreamchunks") ?? [];
|
|
2588
|
+
}
|
|
2589
|
+
/** Replaces the recent stream chunk window after one streaming sweep. */
|
|
2590
|
+
async setstreamchunks(chunks) {
|
|
2591
|
+
return this.adapter.set("mcpstreamchunks", chunks);
|
|
2592
|
+
}
|
|
2593
|
+
/** Returns the audited tool call log under the requested filters: the client, the tool, the outcome, the time floor and the newest bound, all optional. */
|
|
2594
|
+
async getcalllog(filters) {
|
|
2595
|
+
let records = await this.listtoolcalls();
|
|
2596
|
+
if (filters?.clientid !== void 0) records = records.filter((record2) => record2.clientid === filters.clientid);
|
|
2597
|
+
if (filters?.tool !== void 0) records = records.filter((record2) => record2.tool === filters.tool);
|
|
2598
|
+
if (filters?.ok !== void 0) records = records.filter((record2) => record2.ok === filters.ok);
|
|
2599
|
+
if (filters?.since !== void 0) records = records.filter((record2) => record2.at >= (filters.since ?? 0));
|
|
2600
|
+
return filters?.limit !== void 0 ? records.slice(0, filters.limit) : records;
|
|
2601
|
+
}
|
|
2602
|
+
/** Stores one progress notice of a long tool call under the recent notice window of 25 records. */
|
|
2603
|
+
async addprogressnotice(notice) {
|
|
2604
|
+
await this.adapter.set("mcpprogressnotices", [notice, ...(await this.getprogressnotices()).slice(0, 24)]);
|
|
2605
|
+
}
|
|
2606
|
+
/** Returns the recent progress notices of long tool calls, newest first. */
|
|
2607
|
+
async getprogressnotices() {
|
|
2608
|
+
return await this.adapter.get("mcpprogressnotices") ?? [];
|
|
2609
|
+
}
|
|
2610
|
+
/** Returns the tool dry run toggle of the next call: true once the user armed the dry run in the panel. */
|
|
2611
|
+
async getdryruntoggle() {
|
|
2612
|
+
return await this.adapter.get("mcpdryruntoggle") === true;
|
|
2613
|
+
}
|
|
2614
|
+
/** Arms or disarms the tool dry run of the next call. */
|
|
2615
|
+
async setdryruntoggle(enabled) {
|
|
2616
|
+
return this.adapter.set("mcpdryruntoggle", enabled);
|
|
2617
|
+
}
|
|
2618
|
+
/** Returns every user configured provider config of the 1.1.57 llm integration; the api keys stay behind their storage id references, never inside these records. */
|
|
2619
|
+
async getproviders() {
|
|
2620
|
+
return await this.adapter.get("llmproviders") ?? [];
|
|
2621
|
+
}
|
|
2622
|
+
/** Replaces the stored provider config set after one save, test or removal. */
|
|
2623
|
+
async setproviders(providers) {
|
|
2624
|
+
return this.adapter.set("llmproviders", providers);
|
|
2625
|
+
}
|
|
2626
|
+
/** Returns the user configured local model endpoint of the browser reachable inference. */
|
|
2627
|
+
async getlocalmodel() {
|
|
2628
|
+
return this.adapter.get("llmlocalmodel");
|
|
2629
|
+
}
|
|
2630
|
+
/** Stores the local model endpoint config after one save or health check. */
|
|
2631
|
+
async setlocalmodel(config) {
|
|
2632
|
+
return this.adapter.set("llmlocalmodel", config);
|
|
2633
|
+
}
|
|
2634
|
+
/** Returns every model route entry of the routing table, newest update first. */
|
|
2635
|
+
async getmodelroutes() {
|
|
2636
|
+
return await this.adapter.get("llmmodelroutes") ?? [];
|
|
2637
|
+
}
|
|
2638
|
+
/** Replaces the stored routing table after one route edit. */
|
|
2639
|
+
async setmodelroutes(routes) {
|
|
2640
|
+
return this.adapter.set("llmmodelroutes", routes);
|
|
2641
|
+
}
|
|
2642
|
+
/** Appends one revision entry to the model route revision history so every routing change stays queryable for audit. */
|
|
2643
|
+
async addmodelrouterevision(route) {
|
|
2644
|
+
await this.adapter.set("llmmodelroutehistory", [route, ...await this.adapter.get("llmmodelroutehistory") ?? []].slice(0, 200));
|
|
2645
|
+
}
|
|
2646
|
+
/** Returns the model route revision history, newest first. */
|
|
2647
|
+
async getmodelroutehistory() {
|
|
2648
|
+
return await this.adapter.get("llmmodelroutehistory") ?? [];
|
|
2649
|
+
}
|
|
2650
|
+
/** Records one usage entry of a model call with its run and step ids; the newest call reads first and an absent retention keeps every record. */
|
|
2651
|
+
async addusagerecord(record2) {
|
|
2652
|
+
await this.adapter.set("llmusage", [record2, ...await this.adapter.get("llmusage") ?? []]);
|
|
2653
|
+
}
|
|
2654
|
+
/** Returns every stored usage record of model calls, newest first. */
|
|
2655
|
+
async getusagerecords() {
|
|
2656
|
+
return await this.adapter.get("llmusage") ?? [];
|
|
2657
|
+
}
|
|
2658
|
+
/** Returns the token and cost totals per period: the run, the step, the since floor and the until ceiling stay optional filters over the stored usage records. */
|
|
2659
|
+
async getusage(filter = {}) {
|
|
2660
|
+
const records = (await this.getusagerecords()).filter((record2) => (filter.runid === void 0 || record2.runid === filter.runid) && (filter.stepid === void 0 || record2.stepid === filter.stepid) && (filter.since === void 0 || record2.at >= filter.since) && (filter.until === void 0 || record2.at <= filter.until));
|
|
2661
|
+
return records.reduce((totals, record2) => ({ prompttokens: totals.prompttokens + record2.prompttokens, completiontokens: totals.completiontokens + record2.completiontokens, totaltokens: totals.totaltokens + record2.totaltokens, cost: totals.cost + record2.cost, calls: totals.calls + 1 }), { prompttokens: 0, completiontokens: 0, totaltokens: 0, cost: 0, calls: 0 });
|
|
2662
|
+
}
|
|
2663
|
+
/** Stores one model drafted plan for review and audit; newer drafts read first. */
|
|
2664
|
+
async addplandraft(draft) {
|
|
2665
|
+
await this.adapter.set("llmplandrafts", [draft, ...await this.adapter.get("llmplandrafts") ?? []]);
|
|
2666
|
+
}
|
|
2667
|
+
/** Replaces the stored draft set after one review decision. */
|
|
2668
|
+
async setplandrafts(drafts) {
|
|
2669
|
+
return this.adapter.set("llmplandrafts", drafts);
|
|
2670
|
+
}
|
|
2671
|
+
/** Returns every stored model drafted plan, newest first. */
|
|
2672
|
+
async getplandrafts() {
|
|
2673
|
+
return await this.adapter.get("llmplandrafts") ?? [];
|
|
2674
|
+
}
|
|
2675
|
+
/** Stores one replan record for the fresh review and the audit history; newer replans read first. */
|
|
2676
|
+
async addreplan(replan) {
|
|
2677
|
+
await this.adapter.set("llmreplans", [replan, ...await this.adapter.get("llmreplans") ?? []]);
|
|
2678
|
+
}
|
|
2679
|
+
/** Replaces the stored replan set after one fresh review decision. */
|
|
2680
|
+
async setreplans(replans) {
|
|
2681
|
+
return this.adapter.set("llmreplans", replans);
|
|
2682
|
+
}
|
|
2683
|
+
/** Returns every stored replan record, newest first. */
|
|
2684
|
+
async getreplans() {
|
|
2685
|
+
return await this.adapter.get("llmreplans") ?? [];
|
|
2686
|
+
}
|
|
2687
|
+
/** Stores one reflection note of an executed step under the recent note window of 100 records. */
|
|
2688
|
+
async addreflectnote(note) {
|
|
2689
|
+
await this.adapter.set("llmreflectnotes", [note, ...await this.adapter.get("llmreflectnotes") ?? []].slice(0, 100));
|
|
2690
|
+
}
|
|
2691
|
+
/** Returns the stored reflection notes, newest first. */
|
|
2692
|
+
async getreflectnotes() {
|
|
2693
|
+
return await this.adapter.get("llmreflectnotes") ?? [];
|
|
2694
|
+
}
|
|
2695
|
+
/** Replaces the stored prompt template library after one save or removal; every version with its change notes stays stored. */
|
|
2696
|
+
async setprompttemplates(templates) {
|
|
2697
|
+
return this.adapter.set("llmprompttemplates", templates);
|
|
2698
|
+
}
|
|
2699
|
+
/** Returns the stored prompt template library with every version, newest first. */
|
|
2700
|
+
async getprompttemplates() {
|
|
2701
|
+
return await this.adapter.get("llmprompttemplates") ?? [];
|
|
2702
|
+
}
|
|
2703
|
+
/** Returns the stored cost budget of the runs; the run scoped budget wins over the shared one when both exist. */
|
|
2704
|
+
async getcostbudget(runid) {
|
|
2705
|
+
const budgets = await this.adapter.get("llmcostbudgets") ?? [];
|
|
2706
|
+
return budgets.find((budget) => runid !== void 0 && budget.runid === runid) ?? budgets.find((budget) => budget.runid === void 0);
|
|
2707
|
+
}
|
|
2708
|
+
/** Stores one cost budget; a run scoped budget replaces the earlier budget of its run while the shared budget replaces the shared one. */
|
|
2709
|
+
async setcostbudget(budget) {
|
|
2710
|
+
const budgets = await this.adapter.get("llmcostbudgets") ?? [];
|
|
2711
|
+
const kept = budgets.filter((candidate) => candidate.runid !== budget.runid);
|
|
2712
|
+
await this.adapter.set("llmcostbudgets", [budget, ...kept]);
|
|
2713
|
+
}
|
|
2714
|
+
/** Returns the latest parsed natural language command with its intent badge payload. */
|
|
2715
|
+
async getcommandparse() {
|
|
2716
|
+
return this.adapter.get("llmcommandparse");
|
|
2717
|
+
}
|
|
2718
|
+
/** Stores the latest parsed natural language command. */
|
|
2719
|
+
async setcommandparse(parse) {
|
|
2720
|
+
return this.adapter.set("llmcommandparse", parse);
|
|
2721
|
+
}
|
|
2722
|
+
/** Returns the recent guard refusal notices of invalid or refused model output, newest first under a window of 50. */
|
|
2723
|
+
async getguardnotices() {
|
|
2724
|
+
return await this.adapter.get("llmguardnotices") ?? [];
|
|
2725
|
+
}
|
|
2726
|
+
/** Records one guard refusal notice for the panel; the verdict reason explains the parse failure and its retries. */
|
|
2727
|
+
async addguardnotice(output) {
|
|
2728
|
+
if (output.verdict === "valid") return;
|
|
2729
|
+
await this.adapter.set("llmguardnotices", [output, ...await this.getguardnotices()].slice(0, 50));
|
|
2730
|
+
}
|
|
2513
2731
|
};
|
|
2514
2732
|
function mediakindof(record2) {
|
|
2515
2733
|
if ("pages" in record2) return "pdf";
|
|
@@ -6770,8 +6988,8 @@ function validatetimelinegrammar(step, options) {
|
|
|
6770
6988
|
watchwindow = reviewed.window;
|
|
6771
6989
|
}
|
|
6772
6990
|
}
|
|
6773
|
-
const
|
|
6774
|
-
if (!
|
|
6991
|
+
const budgetcheck2 = debugwaitbudgetallowed(watchwindow, typeof options.wait === "number" ? options.wait : void 0);
|
|
6992
|
+
if (!budgetcheck2.allowed) return budgetcheck2;
|
|
6775
6993
|
if (options.level !== void 0 && !loglevels.includes(options.level)) return { allowed: false, reason: `The reviewed level floor must be one of ${loglevels.join(", ")}.` };
|
|
6776
6994
|
if (options.sources !== void 0) {
|
|
6777
6995
|
if (!Array.isArray(options.sources) || options.sources.length === 0 || !options.sources.every((source) => timelinesources.includes(source))) return { allowed: false, reason: `The reviewed source filters must be a non-empty list of the reviewed timeline sources: ${timelinesources.join(", ")}.` };
|
|
@@ -7347,8 +7565,8 @@ function validatecdpgrammar(step, options) {
|
|
|
7347
7565
|
const allowlist = cdpallowlistof(options.allowlist);
|
|
7348
7566
|
if (!allowlist || !allowlist.domains.every((domain) => options.domains.includes(domain))) return { allowed: false, reason: "The reviewed method allowlist must stay inside the enabled domains of the attach." };
|
|
7349
7567
|
}
|
|
7350
|
-
const
|
|
7351
|
-
if (!
|
|
7568
|
+
const budgetcheck2 = debugwaitbudgetallowed(typeof options.wait === "number" ? options.wait : void 0, void 0);
|
|
7569
|
+
if (!budgetcheck2.allowed) return budgetcheck2;
|
|
7352
7570
|
return { allowed: true };
|
|
7353
7571
|
}
|
|
7354
7572
|
if (kind === "detachcdp") return { allowed: true };
|
|
@@ -7372,8 +7590,8 @@ function validatecdpgrammar(step, options) {
|
|
|
7372
7590
|
}
|
|
7373
7591
|
}
|
|
7374
7592
|
if (watchwindow === void 0) return { allowed: false, reason: "The event watch needs a reviewed lifetime window before any domain event is observed." };
|
|
7375
|
-
const
|
|
7376
|
-
if (!
|
|
7593
|
+
const budgetcheck2 = debugwaitbudgetallowed(watchwindow, typeof options.wait === "number" ? options.wait : void 0);
|
|
7594
|
+
if (!budgetcheck2.allowed) return budgetcheck2;
|
|
7377
7595
|
return { allowed: true };
|
|
7378
7596
|
}
|
|
7379
7597
|
if (kind === "setbreakpoint") {
|
|
@@ -7410,8 +7628,8 @@ function validateprofilegrammar(step, options) {
|
|
|
7410
7628
|
if (flowspecof(options.flow) === void 0) return { allowed: false, reason: `The flow measurement needs a reviewed flow spec with its mark prefix, step window and metric list of the reviewed metric set: navigation, paint, lcp, fid, interaction, blocking.` };
|
|
7411
7629
|
const watch = options.watch && typeof options.watch === "object" && !Array.isArray(options.watch) ? options.watch : {};
|
|
7412
7630
|
if (typeof watch.window !== "number" || !Number.isFinite(watch.window) || watch.window < 0) return { allowed: false, reason: "The flow measurement needs a reviewed watch window of zero or more milliseconds." };
|
|
7413
|
-
const
|
|
7414
|
-
if (!
|
|
7631
|
+
const budgetcheck2 = debugwaitbudgetallowed(watch.window, typeof options.wait === "number" ? options.wait : void 0);
|
|
7632
|
+
if (!budgetcheck2.allowed) return budgetcheck2;
|
|
7415
7633
|
return { allowed: true };
|
|
7416
7634
|
}
|
|
7417
7635
|
if (kind === "heapshot") {
|
|
@@ -7428,16 +7646,16 @@ function validateprofilegrammar(step, options) {
|
|
|
7428
7646
|
if (kind === "profilecpu") {
|
|
7429
7647
|
const profile = options.profile && typeof options.profile === "object" && !Array.isArray(options.profile) ? options.profile : void 0;
|
|
7430
7648
|
if (!profile || typeof profile.duration !== "number" || !Number.isFinite(profile.duration) || profile.duration < 0) return { allowed: false, reason: "The cpu profile needs a reviewed duration of zero or more milliseconds." };
|
|
7431
|
-
const
|
|
7432
|
-
if (!
|
|
7649
|
+
const budgetcheck2 = debugwaitbudgetallowed(profile.duration, typeof options.wait === "number" ? options.wait : void 0);
|
|
7650
|
+
if (!budgetcheck2.allowed) return budgetcheck2;
|
|
7433
7651
|
return { allowed: true };
|
|
7434
7652
|
}
|
|
7435
7653
|
if (kind === "watchshifts") {
|
|
7436
7654
|
const watch = options.watch && typeof options.watch === "object" && !Array.isArray(options.watch) ? options.watch : {};
|
|
7437
7655
|
if (typeof watch.window !== "number" || !Number.isFinite(watch.window) || watch.window < 0) return { allowed: false, reason: "The layout shift watch needs a reviewed observation window of zero or more milliseconds; the window stays a user choice with no code ceiling." };
|
|
7438
7656
|
if (options.threshold !== void 0 && (typeof options.threshold !== "number" || !Number.isFinite(options.threshold) || options.threshold < 0)) return { allowed: false, reason: "The reviewed shift score threshold must be zero or a positive number." };
|
|
7439
|
-
const
|
|
7440
|
-
if (!
|
|
7657
|
+
const budgetcheck2 = debugwaitbudgetallowed(watch.window, typeof options.wait === "number" ? options.wait : void 0);
|
|
7658
|
+
if (!budgetcheck2.allowed) return budgetcheck2;
|
|
7441
7659
|
return { allowed: true };
|
|
7442
7660
|
}
|
|
7443
7661
|
if (kind === "traceload") {
|
|
@@ -7445,8 +7663,8 @@ function validateprofilegrammar(step, options) {
|
|
|
7445
7663
|
if (!trace || !Array.isArray(trace.categories) || trace.categories.length === 0 || !trace.categories.every((category) => typeof category === "string" && tracecategories.includes(category))) return { allowed: false, reason: `The trace record needs a non-empty reviewed category list of the reviewed category grammar: ${tracecategories.join(", ")}.` };
|
|
7446
7664
|
if (typeof trace.window !== "number" || !Number.isFinite(trace.window) || trace.window < 0) return { allowed: false, reason: "The trace record needs a reviewed window of zero or more milliseconds and stops at the reviewed window end." };
|
|
7447
7665
|
if (trace.exporttarget !== void 0 && trace.exporttarget !== "memory" && trace.exporttarget !== "download") return { allowed: false, reason: "The trace export target must be memory or download." };
|
|
7448
|
-
const
|
|
7449
|
-
if (!
|
|
7666
|
+
const budgetcheck2 = debugwaitbudgetallowed(trace.window, typeof options.wait === "number" ? options.wait : void 0);
|
|
7667
|
+
if (!budgetcheck2.allowed) return budgetcheck2;
|
|
7450
7668
|
return { allowed: true };
|
|
7451
7669
|
}
|
|
7452
7670
|
if (kind === "annotatetrace" || kind === "replaytrace") {
|
|
@@ -8417,6 +8635,90 @@ function approvaltimeoutvalid(timeout) {
|
|
|
8417
8635
|
function revocationgate() {
|
|
8418
8636
|
return { allowed: true };
|
|
8419
8637
|
}
|
|
8638
|
+
function subscriptiongrade(subscription) {
|
|
8639
|
+
if (!Array.isArray(subscription.kinds) || subscription.kinds.length === 0) return { allowed: false, reason: "An event subscription needs at least one protocol event kind." };
|
|
8640
|
+
if (subscription.kinds.includes("callstarted") && subscription.origin === void 0 && subscription.tool === void 0) return { allowed: false, reason: "An event subscription that mirrors the callstarted events of tools with side effects needs its origin or tool filter so it never widens what the session grants." };
|
|
8641
|
+
return { allowed: true };
|
|
8642
|
+
}
|
|
8643
|
+
function callratelimitvalid(limit) {
|
|
8644
|
+
if (limit === void 0) return { allowed: true };
|
|
8645
|
+
if (typeof limit.windowms !== "number" || !Number.isFinite(limit.windowms) || limit.windowms <= 0) return { allowed: false, reason: "The rate limit window must stay a positive user value with no code ceiling." };
|
|
8646
|
+
if (limit.budget !== void 0 && (typeof limit.budget !== "number" || !Number.isFinite(limit.budget) || limit.budget <= 0)) return { allowed: false, reason: "The rate limit budget must stay a positive user value with no code ceiling." };
|
|
8647
|
+
if (limit.clientid.trim() === "") return { allowed: false, reason: "A per client rate limit needs the client it counts." };
|
|
8648
|
+
return { allowed: true };
|
|
8649
|
+
}
|
|
8650
|
+
function batchgrade(input) {
|
|
8651
|
+
if (input.calls.length === 0) return { allowed: false, reason: "A batch call needs at least one ordered tool call." };
|
|
8652
|
+
const sensitive = input.calls.some((call) => call.risk === "sensitive");
|
|
8653
|
+
if (sensitive && !input.approved) return { allowed: false, reason: "The batch grades sensitive through its most sensitive member and runs only behind the approval gates." };
|
|
8654
|
+
return { allowed: true };
|
|
8655
|
+
}
|
|
8656
|
+
function mockusagevalid(mock) {
|
|
8657
|
+
if (mock.testcontext !== true) return { allowed: false, reason: `The ${mock.tool} mock stays outside a test context and is refused; tool mocks never answer real calls.` };
|
|
8658
|
+
if (mock.tool.trim() === "") return { allowed: false, reason: "A tool mock needs the namespaced tool it stands in for." };
|
|
8659
|
+
if (typeof mock.result.content !== "string") return { allowed: false, reason: "A tool mock needs its canned result content." };
|
|
8660
|
+
return { allowed: true };
|
|
8661
|
+
}
|
|
8662
|
+
function providervalid(config) {
|
|
8663
|
+
if (config.name.trim() === "") return { allowed: false, reason: "The provider config needs its name." };
|
|
8664
|
+
if (config.endpoint.trim() === "") return { allowed: false, reason: "The provider config needs the user configured endpoint url; no default endpoint ever applies." };
|
|
8665
|
+
let parsed;
|
|
8666
|
+
try {
|
|
8667
|
+
parsed = new URL(config.endpoint);
|
|
8668
|
+
} catch {
|
|
8669
|
+
return { allowed: false, reason: "The provider endpoint must be a well-formed url." };
|
|
8670
|
+
}
|
|
8671
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") return { allowed: false, reason: "The provider endpoint must speak http or https." };
|
|
8672
|
+
if (config.models.length === 0) return { allowed: false, reason: "The provider config needs at least one user configured model name." };
|
|
8673
|
+
if (config.models.some((model) => model.trim() === "")) return { allowed: false, reason: "Every provider model name must stay non-empty free text." };
|
|
8674
|
+
if (config.style !== "chatcompletions" && config.style !== "responses" && config.style !== "messages" && config.style !== "gemini") return { allowed: false, reason: "The provider protocol shape must be one of the four wire shapes the user picks." };
|
|
8675
|
+
if (config.authref !== void 0 && config.authref.storageid.trim() === "") return { allowed: false, reason: "The provider auth reference needs the storage id of the stored key; the key material never enters the config." };
|
|
8676
|
+
return { allowed: true };
|
|
8677
|
+
}
|
|
8678
|
+
function provideregressgrade(input) {
|
|
8679
|
+
const valid = providervalid(input.provider);
|
|
8680
|
+
if (!valid.allowed) return valid;
|
|
8681
|
+
return { allowed: true, reason: input.local ? "The model call stays on the local machine endpoint and grades as the local data egress preference." : "The model call leaves the browser for the user configured endpoint and grades as a data egress event with its endpoint, model and token counts in the audit trail." };
|
|
8682
|
+
}
|
|
8683
|
+
function egressconsentgate(input) {
|
|
8684
|
+
if (input.pagecontent !== void 0 && input.pagecontent.trim() !== "" && input.granted !== true) return { allowed: false, reason: "The model call carries page content the user has not granted, so the content stays in the browser and the call refuses." };
|
|
8685
|
+
return { allowed: true };
|
|
8686
|
+
}
|
|
8687
|
+
function plandraftreviewgate(draft) {
|
|
8688
|
+
if (draft.state !== "approved") return { allowed: false, reason: "The model drafted plan stays unreviewed; the human review approves the draft before any step executes." };
|
|
8689
|
+
if (draft.steps.length === 0) return { allowed: false, reason: "The model drafted plan carries no step, so nothing executes." };
|
|
8690
|
+
return { allowed: true };
|
|
8691
|
+
}
|
|
8692
|
+
function replanreviewgate(replan) {
|
|
8693
|
+
if (replan.state !== "approved") return { allowed: false, reason: "The replanned tail stays unreviewed; the fresh review approves the changed steps before any of them executes." };
|
|
8694
|
+
if (replan.tail.some((step) => step.freshreview !== true)) return { allowed: false, reason: "Every revised step of a replan must carry the fresh review marker." };
|
|
8695
|
+
return { allowed: true };
|
|
8696
|
+
}
|
|
8697
|
+
function costbudgetvalid(budget) {
|
|
8698
|
+
if (budget.maxtokens !== void 0 && (!Number.isFinite(budget.maxtokens) || budget.maxtokens <= 0)) return { allowed: false, reason: "The token ceiling of a cost budget must stay a positive user value." };
|
|
8699
|
+
if (budget.maxcost !== void 0 && (!Number.isFinite(budget.maxcost) || budget.maxcost <= 0)) return { allowed: false, reason: "The cost ceiling of a cost budget must stay a positive user value." };
|
|
8700
|
+
if (budget.maxcost !== void 0 && (budget.currency === void 0 || budget.currency.trim() === "")) return { allowed: false, reason: "The cost ceiling of a cost budget needs its currency unit." };
|
|
8701
|
+
if (budget.maxtokens === void 0 && budget.maxcost === void 0) return { allowed: false, reason: "The cost budget needs at least one ceiling the user configured; an absent budget stays the documented unbounded choice." };
|
|
8702
|
+
return { allowed: true };
|
|
8703
|
+
}
|
|
8704
|
+
function draftriskof(step) {
|
|
8705
|
+
try {
|
|
8706
|
+
return resolvedrisk({ id: step.id, kind: step.kind, ...step.target !== void 0 ? { target: step.target } : {}, ...step.value !== void 0 ? { value: step.value } : {}, summary: step.summary, risk: "sensitive" });
|
|
8707
|
+
} catch {
|
|
8708
|
+
return "sensitive";
|
|
8709
|
+
}
|
|
8710
|
+
}
|
|
8711
|
+
function planlint(draft, origin) {
|
|
8712
|
+
const findings = [];
|
|
8713
|
+
if (draft.goal.trim() === "") findings.push("The drafted plan carries no goal.");
|
|
8714
|
+
if (draft.steps.length === 0) findings.push("The drafted plan carries no step.");
|
|
8715
|
+
for (const step of draft.steps) {
|
|
8716
|
+
const mapped = { id: step.id, kind: step.kind, ...step.target !== void 0 ? { target: step.target } : {}, ...step.value !== void 0 ? { value: step.value } : {}, summary: step.summary, risk: draftriskof(step) };
|
|
8717
|
+
const verdict = validatestep(mapped, origin);
|
|
8718
|
+
if (!verdict.allowed) findings.push(`The drafted step ${step.id || "without id"} of kind ${step.kind || "unknown"} violates the action grammar: ${verdict.reason ?? "the step failed its grammar check."}`);
|
|
8719
|
+
}
|
|
8720
|
+
return findings;
|
|
8721
|
+
}
|
|
8420
8722
|
|
|
8421
8723
|
// progress.ts
|
|
8422
8724
|
function emptyprogress(planid, now) {
|
|
@@ -8596,7 +8898,7 @@ function recordtoolcall(progress, planid, stepid, entry, now) {
|
|
|
8596
8898
|
}
|
|
8597
8899
|
|
|
8598
8900
|
// version.ts
|
|
8599
|
-
var packageversion = "1.1.
|
|
8901
|
+
var packageversion = "1.1.57";
|
|
8600
8902
|
|
|
8601
8903
|
// types.ts
|
|
8602
8904
|
var protocolversion = packageversion;
|
|
@@ -8665,6 +8967,128 @@ function tlsstateof(tls) {
|
|
|
8665
8967
|
return { mode: tls.mode, certificaterequired: tls.mode === "required" || tls.certificatefingerprint !== void 0, verified: tls.verifiedat !== void 0 };
|
|
8666
8968
|
}
|
|
8667
8969
|
|
|
8970
|
+
// agentstream.ts
|
|
8971
|
+
var protocoleventkinds = ["callstarted", "callresult", "streamchunk", "progress", "resourcedelta", "sampling", "cancellation"];
|
|
8972
|
+
var readonlyeventkinds = ["callresult", "streamchunk", "progress", "resourcedelta", "sampling", "cancellation"];
|
|
8973
|
+
function subscriberegister(input) {
|
|
8974
|
+
if (input.clientid.trim() === "") return { reason: "The event subscription needs the paired client it belongs to." };
|
|
8975
|
+
const kinds = input.kinds === void 0 || input.kinds.length === 0 ? [...readonlyeventkinds] : [...new Set(input.kinds)];
|
|
8976
|
+
for (const kind of kinds) {
|
|
8977
|
+
if (!protocoleventkinds.includes(kind)) return { reason: `The event kind ${kind} is not a protocol event kind.` };
|
|
8978
|
+
}
|
|
8979
|
+
if (input.origin !== void 0 && input.origin.trim() === "") return { reason: "The origin filter of an event subscription must name an origin or stay absent." };
|
|
8980
|
+
if (input.tool !== void 0 && input.tool.trim() === "") return { reason: "The tool filter of an event subscription must name a tool or stay absent." };
|
|
8981
|
+
return { subscription: { id: input.id ?? randomid(), clientid: input.clientid, kinds, ...input.origin !== void 0 ? { origin: input.origin } : {}, ...input.tool !== void 0 ? { tool: input.tool } : {}, createdat: input.now } };
|
|
8982
|
+
}
|
|
8983
|
+
function unsubscriberegister(subscriptions, id, now) {
|
|
8984
|
+
return subscriptions.map((subscription) => subscription.id === id && subscription.canceledat === void 0 ? { ...subscription, canceledat: now } : subscription);
|
|
8985
|
+
}
|
|
8986
|
+
function notifyevent(input) {
|
|
8987
|
+
const deliveries = [];
|
|
8988
|
+
const subscriptions = input.subscriptions.map((subscription) => {
|
|
8989
|
+
if (subscription.canceledat !== void 0) return subscription;
|
|
8990
|
+
if (!subscription.kinds.includes(input.kind)) return subscription;
|
|
8991
|
+
if (subscription.origin !== void 0 && input.origin !== void 0 && subscription.origin !== input.origin) return subscription;
|
|
8992
|
+
if (subscription.tool !== void 0 && input.tool !== void 0 && subscription.tool !== input.tool) return subscription;
|
|
8993
|
+
deliveries.push({ subscriptionid: subscription.id, clientid: subscription.clientid, frame: { jsonrpc: "2.0", method: "events/notify", params: { subscriptionid: subscription.id, kind: input.kind, ...input.origin !== void 0 ? { origin: input.origin } : {}, ...input.tool !== void 0 ? { tool: input.tool } : {}, ...input.payload !== void 0 ? { payload: input.payload } : {}, at: input.now } } });
|
|
8994
|
+
return { ...subscription, lastdeliveredat: input.now };
|
|
8995
|
+
});
|
|
8996
|
+
return { deliveries, subscriptions };
|
|
8997
|
+
}
|
|
8998
|
+
function watchresource(input) {
|
|
8999
|
+
if (input.clientid.trim() === "") return { reason: "The resource watcher needs the paired client it belongs to." };
|
|
9000
|
+
if (input.resource.trim() === "") return { reason: "The resource watcher needs the page state resource it watches." };
|
|
9001
|
+
return { watch: { id: input.id ?? randomid(), clientid: input.clientid, resource: input.resource, baseline: input.state ?? {}, createdat: input.now } };
|
|
9002
|
+
}
|
|
9003
|
+
function unwatchresource(watches, id, now) {
|
|
9004
|
+
return watches.map((watch) => watch.id === id && watch.canceledat === void 0 ? { ...watch, canceledat: now } : watch);
|
|
9005
|
+
}
|
|
9006
|
+
function notifyresource(input) {
|
|
9007
|
+
const deliveries = [];
|
|
9008
|
+
const watches = input.watches.map((watch) => {
|
|
9009
|
+
if (watch.canceledat !== void 0 || watch.resource !== input.resource) return watch;
|
|
9010
|
+
const delta = {};
|
|
9011
|
+
for (const [key, value] of Object.entries(input.state)) {
|
|
9012
|
+
if (!(key in watch.baseline) || watch.baseline[key] !== value) delta[key] = value;
|
|
9013
|
+
}
|
|
9014
|
+
if (Object.keys(delta).length === 0) return watch;
|
|
9015
|
+
deliveries.push({ watchid: watch.id, clientid: watch.clientid, delta });
|
|
9016
|
+
return { ...watch, baseline: { ...input.state }, lastdeliveredat: input.now };
|
|
9017
|
+
});
|
|
9018
|
+
return { deliveries, watches };
|
|
9019
|
+
}
|
|
9020
|
+
function requestsampling(input) {
|
|
9021
|
+
if (input.clientid.trim() === "") return { reason: "The sampling callback needs the paired client it addresses." };
|
|
9022
|
+
if (input.prompt.trim() === "") return { reason: "The sampling callback needs its prompt." };
|
|
9023
|
+
if (input.capabilities?.sampling === false) return { reason: "The client declared no sampling capability and the callback is refused." };
|
|
9024
|
+
if (input.maxtokens !== void 0 && (!Number.isFinite(input.maxtokens) || input.maxtokens <= 0)) return { reason: "The granted maximum tokens of a sampling callback must stay a positive user value." };
|
|
9025
|
+
const granted = input.pagegrant === true;
|
|
9026
|
+
const pagecontent = granted ? input.pagecontent : void 0;
|
|
9027
|
+
const prompt = granted || input.pagecontent === void 0 ? input.prompt : `${input.prompt}
|
|
9028
|
+
The page content stays stripped because the user granted none.`;
|
|
9029
|
+
return { request: { id: input.id ?? randomid(), clientid: input.clientid, prompt, ...input.system !== void 0 ? { system: input.system } : {}, ...pagecontent !== void 0 ? { pagecontent } : {}, ...input.maxtokens !== void 0 ? { maxtokens: input.maxtokens } : {}, state: "pending", requestedat: input.now } };
|
|
9030
|
+
}
|
|
9031
|
+
function answersampling(input) {
|
|
9032
|
+
const match = input.requests.find((request2) => request2.id === input.id);
|
|
9033
|
+
if (match === void 0) return { requests: input.requests, reason: "The sampling answer names no stored request." };
|
|
9034
|
+
if (match.state !== "pending") return { requests: input.requests, reason: "The sampling request already closed its round trip." };
|
|
9035
|
+
const request = { ...match, state: input.refused === true ? "refused" : "answered", answeredat: input.now, ...input.refused !== true && input.answer !== void 0 ? { answer: input.answer } : {} };
|
|
9036
|
+
return { requests: input.requests.map((candidate) => candidate.id === input.id ? request : candidate), request };
|
|
9037
|
+
}
|
|
9038
|
+
function listprompts() {
|
|
9039
|
+
return [
|
|
9040
|
+
{ name: "runreview", description: "Renders the run review prompt that asks the user model to summarize the executed steps of the approved plan behind the consent gates.", arguments: [{ name: "objective", description: "The objective of the approved plan under review.", required: true }, { name: "steps", description: "The executed step summaries the review covers.", required: true }, { name: "tone", description: "The tone of the summary.", default: "plain" }], template: "Review the run of the objective {{objective}}. Summarize the executed steps: {{steps}}. Keep the tone {{tone}} and state every refusal the consent gates raised." },
|
|
9041
|
+
{ name: "pagesummary", description: "Renders the page summary prompt that condenses the observed page state of the session tab into the summary the client model asked for.", arguments: [{ name: "url", description: "The url of the observed page.", required: true }, { name: "observations", description: "The observed page state sections the summary condenses.", required: true }], template: "Summarize the page at {{url}} from the observations: {{observations}}. Name nothing the observations leave out." },
|
|
9042
|
+
{ name: "failuretriage", description: "Renders the failure triage prompt that classifies a failed tool call through the retry hints of the structured error it produced.", arguments: [{ name: "tool", description: "The namespaced tool that failed.", required: true }, { name: "error", description: "The structured error of the failed call.", required: true }], template: "Triage the failure of the {{tool}} tool: {{error}}. Classify it as retryable, a busy window or a consent refusal and propose the next reviewed step." }
|
|
9043
|
+
];
|
|
9044
|
+
}
|
|
9045
|
+
function renderprompt(prompt, args) {
|
|
9046
|
+
return prompt.template.replace(/\{\{\s*([a-z0-9]+)\s*\}\}/g, (whole, name) => {
|
|
9047
|
+
const value = args[name];
|
|
9048
|
+
if (value === void 0 || value === null) return whole;
|
|
9049
|
+
return typeof value === "string" ? value : JSON.stringify(value);
|
|
9050
|
+
});
|
|
9051
|
+
}
|
|
9052
|
+
function callprompt(input) {
|
|
9053
|
+
const prompt = listprompts().find((candidate) => candidate.name === input.name);
|
|
9054
|
+
if (prompt === void 0) return { reason: `The server exposes no prompt named ${input.name}.` };
|
|
9055
|
+
const args = input.args ?? {};
|
|
9056
|
+
const findings = [];
|
|
9057
|
+
const resolved = {};
|
|
9058
|
+
for (const argument of prompt.arguments) {
|
|
9059
|
+
const value = args[argument.name];
|
|
9060
|
+
if (value === void 0 || value === null || typeof value === "string" && value.trim() === "") {
|
|
9061
|
+
if (argument.default !== void 0) resolved[argument.name] = argument.default;
|
|
9062
|
+
else if (argument.required === true) findings.push(`The prompt argument ${argument.name} is required and stays empty.`);
|
|
9063
|
+
else resolved[argument.name] = "";
|
|
9064
|
+
} else {
|
|
9065
|
+
resolved[argument.name] = value;
|
|
9066
|
+
}
|
|
9067
|
+
}
|
|
9068
|
+
if (findings.length > 0) return { reason: findings.join(" ") };
|
|
9069
|
+
return { rendered: renderprompt(prompt, resolved), toolcall: { name: `prompts.${prompt.name}`, params: { prompt: prompt.name, arguments: resolved, rendered: renderprompt(prompt, resolved) } } };
|
|
9070
|
+
}
|
|
9071
|
+
function streamchunkof(input) {
|
|
9072
|
+
return { callid: input.callid, seq: input.seq, content: input.content, done: input.done === true, at: input.now };
|
|
9073
|
+
}
|
|
9074
|
+
function chunkcontent(input) {
|
|
9075
|
+
const size = input.size !== void 0 && Number.isFinite(input.size) && input.size > 0 ? Math.floor(input.size) : 80;
|
|
9076
|
+
const parts = [];
|
|
9077
|
+
for (let index = 0; index < input.content.length; index += size) parts.push(input.content.slice(index, index + size));
|
|
9078
|
+
const slices = parts.length > 0 ? parts : [""];
|
|
9079
|
+
return slices.map((content, index) => streamchunkof({ callid: input.callid, seq: index + 1, content, done: index === slices.length - 1, now: input.now + index }));
|
|
9080
|
+
}
|
|
9081
|
+
function notifyprogress(input) {
|
|
9082
|
+
return { callid: input.callid, ...input.percent !== void 0 ? { percent: input.percent } : {}, message: input.message, cancellable: input.cancellable !== false, at: input.now };
|
|
9083
|
+
}
|
|
9084
|
+
function canceltool(input) {
|
|
9085
|
+
const match = input.contexts.find((context2) => context2.callid === input.callid);
|
|
9086
|
+
if (match === void 0) return { contexts: input.contexts, reason: `The cancellation frame names no call context ${input.callid}.` };
|
|
9087
|
+
if (match.state !== "inflight") return { contexts: input.contexts, reason: `The call ${input.callid} already left the in flight state.` };
|
|
9088
|
+
const context = { ...match, state: "cancelled", endedat: input.now, ...input.partial !== void 0 ? { partial: input.partial } : {} };
|
|
9089
|
+
return { contexts: input.contexts.map((candidate) => candidate.callid === input.callid ? context : candidate), context };
|
|
9090
|
+
}
|
|
9091
|
+
|
|
8668
9092
|
// mcpserver.ts
|
|
8669
9093
|
var localhostbind = "127.0.0.1";
|
|
8670
9094
|
var defaultmcpport = 7436;
|
|
@@ -8707,7 +9131,10 @@ function servermethods() {
|
|
|
8707
9131
|
{ method: "ping", handler: "ping", description: "Answers keepalive frames with pong." },
|
|
8708
9132
|
{ method: "tools/list", handler: "listtools", description: "Returns every tool with its version and json schema inputs." },
|
|
8709
9133
|
{ method: "negotiate", handler: "negotiate", description: "Exchanges capability sets with the client." },
|
|
8710
|
-
{ method: "tools/call", handler: "dispatch", description: "Invokes one tool behind the consent gates." }
|
|
9134
|
+
{ method: "tools/call", handler: "dispatch", description: "Invokes one tool behind the consent gates." },
|
|
9135
|
+
{ method: "prompts/list", handler: "listprompts", description: "Lists the prompt defs the server exposes as callable tools." },
|
|
9136
|
+
{ method: "prompts/call", handler: "callprompt", description: "Renders one prompt and returns its arguments as a tool call." },
|
|
9137
|
+
{ method: "calls/cancel", handler: "cancel", description: "Aborts one in flight tool call and preserves its partial result." }
|
|
8711
9138
|
];
|
|
8712
9139
|
}
|
|
8713
9140
|
function servercapabilities(input) {
|
|
@@ -8792,6 +9219,22 @@ async function handleframe(input) {
|
|
|
8792
9219
|
const outcome = negotiate({ ...clientcaps !== void 0 ? { client: clientcaps } : {}, server });
|
|
8793
9220
|
return respond({ ...frame.id !== void 0 ? { id: frame.id } : {}, ...outcome.agreed ? { result: outcome.capabilities } : { error: rpcerrorof("params", outcome.mismatch ?? "The capability negotiation did not agree.") } });
|
|
8794
9221
|
}
|
|
9222
|
+
if (entry.handler === "listprompts") return respond({ ...frame.id !== void 0 ? { id: frame.id } : {}, result: { prompts: listprompts() } });
|
|
9223
|
+
if (entry.handler === "callprompt") {
|
|
9224
|
+
const name = typeof params?.name === "string" ? params.name : "";
|
|
9225
|
+
const args = params?.arguments && typeof params.arguments === "object" && !Array.isArray(params.arguments) ? params.arguments : void 0;
|
|
9226
|
+
const called = name === "" ? { reason: "The prompt call needs the prompt name." } : callprompt({ name, ...args !== void 0 ? { args } : {} });
|
|
9227
|
+
if (called.toolcall === void 0) return respond({ ...frame.id !== void 0 ? { id: frame.id } : {}, error: rpcerrorof("params", called.reason ?? "The prompt call did not render.") });
|
|
9228
|
+
return respond({ ...frame.id !== void 0 ? { id: frame.id } : {}, result: { toolcall: called.toolcall, rendered: called.rendered } });
|
|
9229
|
+
}
|
|
9230
|
+
if (entry.handler === "cancel") {
|
|
9231
|
+
const callid = typeof params?.callid === "string" ? params.callid : "";
|
|
9232
|
+
const reason = typeof params?.reason === "string" ? params.reason : void 0;
|
|
9233
|
+
if (callid.trim() === "") return respond({ ...frame.id !== void 0 ? { id: frame.id } : {}, error: rpcerrorof("params", "The cancellation frame needs the call id it aborts.") });
|
|
9234
|
+
const aborted = canceltool({ contexts: input.contexts ?? [], callid, ...reason !== void 0 ? { reason } : {}, now: input.now });
|
|
9235
|
+
if (aborted.context === void 0) return respond({ ...frame.id !== void 0 ? { id: frame.id } : {}, error: rpcerrorof("params", aborted.reason ?? "The cancellation frame named no in flight tool call.") });
|
|
9236
|
+
return respond({ ...frame.id !== void 0 ? { id: frame.id } : {}, result: { cancelled: true, callid, ...reason !== void 0 ? { reason } : {}, ...aborted.context.partial !== void 0 ? { partial: aborted.context.partial } : {} } });
|
|
9237
|
+
}
|
|
8795
9238
|
const dispatched = await dispatchtool({ ...params !== void 0 ? { params } : {}, client: input.client, catalog: input.catalog, ...input.session !== void 0 ? { session: input.session } : {}, ...input.plan !== void 0 ? { plan: input.plan } : {}, ...input.scopes !== void 0 ? { scopes: input.scopes } : {}, origin: input.origin, now: input.now, execute: input.execute });
|
|
8796
9239
|
return respond({ ...frame.id !== void 0 ? { id: frame.id } : {}, ...dispatched.error !== void 0 ? { error: dispatched.error } : { result: dispatched.result } });
|
|
8797
9240
|
}
|
|
@@ -8806,7 +9249,7 @@ function relayframe(input) {
|
|
|
8806
9249
|
return { ...input.bridge, connected: true, received: input.bridge.received + (input.direction === "inbound" ? 1 : 0), sent: input.bridge.sent + (input.direction === "outbound" ? 1 : 0), lastframeat: input.now };
|
|
8807
9250
|
}
|
|
8808
9251
|
function toolcallevent(input) {
|
|
8809
|
-
return { id: input.id, clientid: input.clientid, tool: input.tool, origin: input.origin, ok: input.ok, ...input.code !== void 0 ? { code: input.code } : {}, at: input.now };
|
|
9252
|
+
return { id: input.id, clientid: input.clientid, tool: input.tool, origin: input.origin, ok: input.ok, ...input.code !== void 0 ? { code: input.code } : {}, at: input.now, ...input.callid !== void 0 ? { callid: input.callid } : {}, ...input.idempotencykey !== void 0 && input.idempotencykey.trim() !== "" ? { idempotencykey: input.idempotencykey } : {}, ...input.dryrun === true ? { dryrun: true } : {}, ...input.mocked === true ? { mocked: true } : {}, ...input.batchid !== void 0 ? { batchid: input.batchid } : {}, ...input.replayed === true ? { replayed: true } : {} };
|
|
8810
9253
|
}
|
|
8811
9254
|
|
|
8812
9255
|
// httpstream.ts
|
|
@@ -10477,6 +10920,451 @@ function yamlscalarvalue(text2) {
|
|
|
10477
10920
|
return text2;
|
|
10478
10921
|
}
|
|
10479
10922
|
|
|
10923
|
+
// toolcalls.ts
|
|
10924
|
+
var defaultidempotencywindowms = 3e5;
|
|
10925
|
+
function applyratelimit(input) {
|
|
10926
|
+
const existing = input.limits.find((limit2) => limit2.clientid === input.clientid);
|
|
10927
|
+
if (existing === void 0) return { allowed: true, used: 0, limits: input.limits };
|
|
10928
|
+
const elapsed = input.now - existing.windowstartedat;
|
|
10929
|
+
const limit = elapsed >= existing.windowms ? { ...existing, windowstartedat: input.now, used: 0 } : existing;
|
|
10930
|
+
if (limit.budget !== void 0 && limit.used >= limit.budget) {
|
|
10931
|
+
const retryafter = Math.max(0, limit.windowms - (input.now - limit.windowstartedat));
|
|
10932
|
+
return { allowed: false, used: limit.used, budget: limit.budget, retryafter, limits: input.limits.map((candidate) => candidate.clientid === input.clientid ? limit : candidate) };
|
|
10933
|
+
}
|
|
10934
|
+
const counted = { ...limit, used: limit.used + 1 };
|
|
10935
|
+
return { allowed: true, used: counted.used, ...counted.budget !== void 0 ? { budget: counted.budget } : {}, limit: counted, limits: input.limits.map((candidate) => candidate.clientid === input.clientid ? counted : candidate) };
|
|
10936
|
+
}
|
|
10937
|
+
function structurederrorof(input) {
|
|
10938
|
+
return { code: input.code, message: input.message, retryhint: input.retryhint, ...input.retryafter !== void 0 ? { retryafter: input.retryafter } : {} };
|
|
10939
|
+
}
|
|
10940
|
+
function retryhintof(failure) {
|
|
10941
|
+
const text2 = `${failure.code ?? ""} ${failure.message ?? ""}`.toLowerCase();
|
|
10942
|
+
if (text2.includes("consent") || text2.includes("refus") || text2.includes("unpaired") || text2.includes("unapproved") || text2.includes("grant")) return "none";
|
|
10943
|
+
if (text2.includes("rate") || text2.includes("busy") || text2.includes("queue") || text2.includes("window")) return "wait";
|
|
10944
|
+
if (text2.includes("timeout") || text2.includes("timed out") || text2.includes("internal") || text2.includes("network")) return "retry";
|
|
10945
|
+
return "none";
|
|
10946
|
+
}
|
|
10947
|
+
function checkidempotency(input) {
|
|
10948
|
+
const match = input.records.find((record2) => record2.key === input.key);
|
|
10949
|
+
if (match === void 0) return { reason: "The idempotency key names no stored record." };
|
|
10950
|
+
if (match.clientid !== input.clientid) return { reason: "The idempotency key belongs to another client and never replays across clients." };
|
|
10951
|
+
if (input.now >= match.expiresat) return { reason: "The idempotency record expired past its window and the call runs again." };
|
|
10952
|
+
return { replay: match.result, record: match };
|
|
10953
|
+
}
|
|
10954
|
+
function recordidempotency(input) {
|
|
10955
|
+
const record2 = { key: input.key, clientid: input.clientid, tool: input.tool, result: input.result, createdat: input.now, expiresat: input.now + (input.window ?? defaultidempotencywindowms) };
|
|
10956
|
+
return [record2, ...input.records.filter((candidate) => !(candidate.key === input.key && candidate.clientid === input.clientid))];
|
|
10957
|
+
}
|
|
10958
|
+
function expireidempotency(records, now) {
|
|
10959
|
+
return records.filter((record2) => now < record2.expiresat);
|
|
10960
|
+
}
|
|
10961
|
+
async function runbatch(input) {
|
|
10962
|
+
const outcomes = [];
|
|
10963
|
+
for (let index = 0; index < input.calls.length; index += 1) {
|
|
10964
|
+
const call = input.calls[index];
|
|
10965
|
+
if (call === void 0) continue;
|
|
10966
|
+
const outcome = await input.execute(call, index);
|
|
10967
|
+
outcomes.push({ callid: call.id, tool: call.name, ok: outcome.ok, ...outcome.result !== void 0 ? { result: outcome.result } : {}, ...outcome.error !== void 0 ? { error: outcome.error } : {}, at: input.now + index });
|
|
10968
|
+
if (!outcome.ok && input.stoponerror) return { outcomes, stoppedat: call.id };
|
|
10969
|
+
}
|
|
10970
|
+
return { outcomes };
|
|
10971
|
+
}
|
|
10972
|
+
function dryruntool(input) {
|
|
10973
|
+
const findings = [];
|
|
10974
|
+
for (const required of input.tool.inputschema.required) {
|
|
10975
|
+
const value = input.params[required];
|
|
10976
|
+
if (value === void 0 || value === null || typeof value === "string" && value.trim() === "") findings.push(`The required argument ${required} of ${input.tool.name} stays empty.`);
|
|
10977
|
+
}
|
|
10978
|
+
for (const [name, property] of Object.entries(input.tool.inputschema.properties)) {
|
|
10979
|
+
const value = input.params[name];
|
|
10980
|
+
if (value === void 0 || value === null) continue;
|
|
10981
|
+
const expected = property.type;
|
|
10982
|
+
const actual = Array.isArray(value) ? "array" : typeof value;
|
|
10983
|
+
if (actual !== expected) findings.push(`The argument ${name} of ${input.tool.name} carries a ${actual} value where the schema asks a ${expected}.`);
|
|
10984
|
+
}
|
|
10985
|
+
const stepid = input.stepid !== void 0 ? input.stepid : typeof input.params.stepid === "string" ? input.params.stepid : void 0;
|
|
10986
|
+
const gate = tooldispatchgate({ client: input.client, tool: input.tool, session: input.session, plan: input.plan, origin: input.origin, ...stepid !== void 0 ? { stepid } : {}, now: input.now });
|
|
10987
|
+
if (!gate.allowed) findings.push(gate.reason ?? `The consent gates refused the ${input.tool.name} dry run.`);
|
|
10988
|
+
return { callid: input.callid ?? randomid(), tool: input.tool.name, argsvalid: findings.length === 0, consentok: gate.allowed, findings, executed: false, mutations: [], at: input.now };
|
|
10989
|
+
}
|
|
10990
|
+
function applymock(input) {
|
|
10991
|
+
const mock = input.mocks.find((candidate) => candidate.tool === input.tool);
|
|
10992
|
+
if (mock === void 0) return {};
|
|
10993
|
+
if (mock.testcontext !== true) return { reason: `The ${input.tool} mock stays outside a test context and is refused; mocks never answer real calls.` };
|
|
10994
|
+
return { result: mock.result };
|
|
10995
|
+
}
|
|
10996
|
+
function begincall(input) {
|
|
10997
|
+
return { callid: input.callid ?? randomid(), clientid: input.clientid, tool: input.tool, state: "inflight", startedat: input.now, ...input.idempotencykey !== void 0 && input.idempotencykey.trim() !== "" ? { idempotencykey: input.idempotencykey } : {}, ...input.dryrun === true ? { dryrun: true } : {}, ...input.batchid !== void 0 ? { batchid: input.batchid } : {}, chunks: 0 };
|
|
10998
|
+
}
|
|
10999
|
+
function endcall(input) {
|
|
11000
|
+
const match = input.contexts.find((context2) => context2.callid === input.callid);
|
|
11001
|
+
if (match === void 0) return { contexts: input.contexts, reason: `The call context ${input.callid} never opened.` };
|
|
11002
|
+
if (match.state !== "inflight") return { contexts: input.contexts, context: match };
|
|
11003
|
+
const context = { ...match, state: input.ok ? "done" : "failed", endedat: input.now, ...input.errorcode !== void 0 ? { errorcode: input.errorcode } : {}, ...input.partial !== void 0 ? { partial: input.partial } : {} };
|
|
11004
|
+
return { contexts: input.contexts.map((candidate) => candidate.callid === input.callid ? context : candidate), context };
|
|
11005
|
+
}
|
|
11006
|
+
|
|
11007
|
+
// llm.ts
|
|
11008
|
+
var defaultrefusalmarkers = ["i cannot", "i can't", "i'm unable", "refusal:", "cannot comply"];
|
|
11009
|
+
function buildrequest(input) {
|
|
11010
|
+
const headers = { "content-type": "application/json" };
|
|
11011
|
+
let url = input.provider.endpoint;
|
|
11012
|
+
const style = input.provider.style;
|
|
11013
|
+
if (style === "chatcompletions") {
|
|
11014
|
+
if (input.apikey !== void 0 && input.apikey.trim() !== "") headers.authorization = `Bearer ${input.apikey}`;
|
|
11015
|
+
const body2 = { model: input.model, messages: input.messages.map((message) => ({ role: message.role, content: message.content })), ...input.temperature !== void 0 ? { temperature: input.temperature } : {}, ...input.maxtokens !== void 0 ? { max_tokens: input.maxtokens } : {}, ...input.stream === true ? { stream: true } : {} };
|
|
11016
|
+
return { url, method: "POST", headers: { ...headers, ...input.provider.headers ?? {} }, body: JSON.stringify(body2) };
|
|
11017
|
+
}
|
|
11018
|
+
if (style === "responses") {
|
|
11019
|
+
if (input.apikey !== void 0 && input.apikey.trim() !== "") headers.authorization = `Bearer ${input.apikey}`;
|
|
11020
|
+
const system2 = input.messages.filter((message) => message.role === "system").map((message) => message.content).join("\n");
|
|
11021
|
+
const turns2 = input.messages.filter((message) => message.role !== "system").map((message) => ({ role: message.role === "assistant" ? "assistant" : "user", content: message.content }));
|
|
11022
|
+
const body2 = { model: input.model, input: turns2, ...system2.trim() !== "" ? { instructions: system2 } : {}, ...input.temperature !== void 0 ? { temperature: input.temperature } : {}, ...input.maxtokens !== void 0 ? { max_output_tokens: input.maxtokens } : {}, ...input.stream === true ? { stream: true } : {} };
|
|
11023
|
+
return { url, method: "POST", headers: { ...headers, ...input.provider.headers ?? {} }, body: JSON.stringify(body2) };
|
|
11024
|
+
}
|
|
11025
|
+
if (style === "messages") {
|
|
11026
|
+
if (input.apikey !== void 0 && input.apikey.trim() !== "") headers["x-api-key"] = input.apikey;
|
|
11027
|
+
const system2 = input.messages.filter((message) => message.role === "system").map((message) => message.content).join("\n");
|
|
11028
|
+
const turns2 = input.messages.filter((message) => message.role !== "system").map((message) => ({ role: message.role, content: message.content }));
|
|
11029
|
+
const body2 = { model: input.model, messages: turns2, ...system2.trim() !== "" ? { system: system2 } : {}, ...input.temperature !== void 0 ? { temperature: input.temperature } : {}, ...input.maxtokens !== void 0 ? { max_tokens: input.maxtokens } : {}, ...input.stream === true ? { stream: true } : {} };
|
|
11030
|
+
return { url, method: "POST", headers: { ...headers, ...input.provider.headers ?? {} }, body: JSON.stringify(body2) };
|
|
11031
|
+
}
|
|
11032
|
+
if (input.apikey !== void 0 && input.apikey.trim() !== "") url = `${url}${url.includes("?") ? "&" : "?"}key=${encodeURIComponent(input.apikey)}`;
|
|
11033
|
+
const system = input.messages.filter((message) => message.role === "system").map((message) => message.content).join("\n");
|
|
11034
|
+
const turns = input.messages.filter((message) => message.role !== "system").map((message) => ({ role: message.role === "assistant" ? "model" : "user", parts: [{ text: message.content }] }));
|
|
11035
|
+
const body = { contents: turns, ...system.trim() !== "" ? { systemInstruction: { parts: [{ text: system }] } } : {}, ...input.temperature !== void 0 ? { generationConfig: { temperature: input.temperature, ...input.maxtokens !== void 0 ? { maxOutputTokens: input.maxtokens } : {} } } : input.maxtokens !== void 0 ? { generationConfig: { maxOutputTokens: input.maxtokens } } : {} };
|
|
11036
|
+
return { url, method: "POST", headers: { ...headers, ...input.provider.headers ?? {} }, body: JSON.stringify(body) };
|
|
11037
|
+
}
|
|
11038
|
+
function numberof(value) {
|
|
11039
|
+
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
11040
|
+
}
|
|
11041
|
+
function parsecompletion(style, body) {
|
|
11042
|
+
let parsed;
|
|
11043
|
+
try {
|
|
11044
|
+
parsed = JSON.parse(body);
|
|
11045
|
+
} catch {
|
|
11046
|
+
return { reason: "The provider answer is not json." };
|
|
11047
|
+
}
|
|
11048
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return { reason: "The provider answer is not a json object." };
|
|
11049
|
+
const record2 = parsed;
|
|
11050
|
+
if (style === "chatcompletions") {
|
|
11051
|
+
const choice = Array.isArray(record2.choices) ? record2.choices[0] : void 0;
|
|
11052
|
+
const message = choice !== void 0 && choice.message !== void 0 && typeof choice.message === "object" ? choice.message : void 0;
|
|
11053
|
+
if (message === void 0 || typeof message.content !== "string") return { reason: "The chat completions answer carries no message content." };
|
|
11054
|
+
const usage2 = record2.usage !== void 0 && typeof record2.usage === "object" ? record2.usage : void 0;
|
|
11055
|
+
const prompttokens2 = usage2 !== void 0 ? numberof(usage2.prompt_tokens) : void 0;
|
|
11056
|
+
const completiontokens2 = usage2 !== void 0 ? numberof(usage2.completion_tokens) : void 0;
|
|
11057
|
+
const totaltokens2 = usage2 !== void 0 ? numberof(usage2.total_tokens) : void 0;
|
|
11058
|
+
return { text: message.content, ...prompttokens2 !== void 0 || completiontokens2 !== void 0 || totaltokens2 !== void 0 ? { usage: { prompttokens: prompttokens2 ?? 0, completiontokens: completiontokens2 ?? 0, totaltokens: totaltokens2 ?? (prompttokens2 ?? 0) + (completiontokens2 ?? 0) } } : {} };
|
|
11059
|
+
}
|
|
11060
|
+
if (style === "responses") {
|
|
11061
|
+
const direct = typeof record2.output_text === "string" ? record2.output_text : void 0;
|
|
11062
|
+
let text2 = direct;
|
|
11063
|
+
if (text2 === void 0 && Array.isArray(record2.output)) {
|
|
11064
|
+
const parts2 = [];
|
|
11065
|
+
for (const item of record2.output) {
|
|
11066
|
+
if (item && typeof item === "object" && Array.isArray(item.content)) {
|
|
11067
|
+
for (const part of item.content) {
|
|
11068
|
+
if (part && typeof part === "object" && part.type === "output_text" && typeof part.text === "string") parts2.push(part.text);
|
|
11069
|
+
}
|
|
11070
|
+
}
|
|
11071
|
+
}
|
|
11072
|
+
if (parts2.length > 0) text2 = parts2.join("");
|
|
11073
|
+
}
|
|
11074
|
+
if (text2 === void 0) return { reason: "The responses answer carries no output text." };
|
|
11075
|
+
const usage2 = record2.usage !== void 0 && typeof record2.usage === "object" ? record2.usage : void 0;
|
|
11076
|
+
const prompttokens2 = usage2 !== void 0 ? numberof(usage2.input_tokens) : void 0;
|
|
11077
|
+
const completiontokens2 = usage2 !== void 0 ? numberof(usage2.output_tokens) : void 0;
|
|
11078
|
+
const totaltokens2 = usage2 !== void 0 ? numberof(usage2.total_tokens) : void 0;
|
|
11079
|
+
return { text: text2, ...prompttokens2 !== void 0 || completiontokens2 !== void 0 || totaltokens2 !== void 0 ? { usage: { prompttokens: prompttokens2 ?? 0, completiontokens: completiontokens2 ?? 0, totaltokens: totaltokens2 ?? (prompttokens2 ?? 0) + (completiontokens2 ?? 0) } } : {} };
|
|
11080
|
+
}
|
|
11081
|
+
if (style === "messages") {
|
|
11082
|
+
const parts2 = [];
|
|
11083
|
+
if (Array.isArray(record2.content)) {
|
|
11084
|
+
for (const part of record2.content) {
|
|
11085
|
+
if (part && typeof part === "object" && part.type === "text" && typeof part.text === "string") parts2.push(part.text);
|
|
11086
|
+
}
|
|
11087
|
+
}
|
|
11088
|
+
if (parts2.length === 0) return { reason: "The messages answer carries no text block." };
|
|
11089
|
+
const usage2 = record2.usage !== void 0 && typeof record2.usage === "object" ? record2.usage : void 0;
|
|
11090
|
+
const prompttokens2 = usage2 !== void 0 ? numberof(usage2.input_tokens) : void 0;
|
|
11091
|
+
const completiontokens2 = usage2 !== void 0 ? numberof(usage2.output_tokens) : void 0;
|
|
11092
|
+
return { text: parts2.join(""), ...prompttokens2 !== void 0 || completiontokens2 !== void 0 ? { usage: { prompttokens: prompttokens2 ?? 0, completiontokens: completiontokens2 ?? 0, totaltokens: (prompttokens2 ?? 0) + (completiontokens2 ?? 0) } } : {} };
|
|
11093
|
+
}
|
|
11094
|
+
const candidate = Array.isArray(record2.candidates) ? record2.candidates[0] : void 0;
|
|
11095
|
+
const content = candidate !== void 0 && candidate.content !== void 0 && typeof candidate.content === "object" ? candidate.content.parts : void 0;
|
|
11096
|
+
const parts = [];
|
|
11097
|
+
if (Array.isArray(content)) {
|
|
11098
|
+
for (const part of content) {
|
|
11099
|
+
if (part && typeof part === "object" && typeof part.text === "string") parts.push(part.text);
|
|
11100
|
+
}
|
|
11101
|
+
}
|
|
11102
|
+
if (parts.length === 0) return { reason: "The gemini answer carries no candidate text." };
|
|
11103
|
+
const usage = record2.usageMetadata !== void 0 && typeof record2.usageMetadata === "object" ? record2.usageMetadata : void 0;
|
|
11104
|
+
const prompttokens = usage !== void 0 ? numberof(usage.promptTokenCount) : void 0;
|
|
11105
|
+
const completiontokens = usage !== void 0 ? numberof(usage.candidatesTokenCount) : void 0;
|
|
11106
|
+
const totaltokens = usage !== void 0 ? numberof(usage.totalTokenCount) : void 0;
|
|
11107
|
+
return { text: parts.join(""), ...prompttokens !== void 0 || completiontokens !== void 0 || totaltokens !== void 0 ? { usage: { prompttokens: prompttokens ?? 0, completiontokens: completiontokens ?? 0, totaltokens: totaltokens ?? (prompttokens ?? 0) + (completiontokens ?? 0) } } : {} };
|
|
11108
|
+
}
|
|
11109
|
+
function islocalorigin(url) {
|
|
11110
|
+
try {
|
|
11111
|
+
const host = new URL(url).hostname.toLowerCase();
|
|
11112
|
+
return host === "localhost" || host === "127.0.0.1" || host === "::1" || host === "[::1]" || host.endsWith(".localhost");
|
|
11113
|
+
} catch {
|
|
11114
|
+
return false;
|
|
11115
|
+
}
|
|
11116
|
+
}
|
|
11117
|
+
async function callmodel(input) {
|
|
11118
|
+
if (input.provider.endpoint.trim() === "") throw new Error("The provider needs the user configured endpoint url before any call leaves.");
|
|
11119
|
+
if (input.provider.authref !== void 0 && (input.apikey === void 0 || input.apikey.trim() === "")) throw new Error(`The provider ${input.provider.name} references the stored key ${input.provider.authref.name} and the call needs the resolved key material.`);
|
|
11120
|
+
const consent = egressconsentgate({ ...input.pagecontent !== void 0 ? { pagecontent: input.pagecontent } : {}, granted: input.pagegrant === true });
|
|
11121
|
+
if (!consent.allowed) throw new Error(consent.reason ?? "The page content stayed ungranted and the call refused.");
|
|
11122
|
+
const shaped = buildrequest({ provider: input.provider, model: input.model, messages: input.messages, ...input.apikey !== void 0 ? { apikey: input.apikey } : {}, ...input.temperature !== void 0 ? { temperature: input.temperature } : {}, ...input.maxtokens !== void 0 ? { maxtokens: input.maxtokens } : {}, ...input.stream === true ? { stream: true } : {} });
|
|
11123
|
+
const transport = await sendfetch({ request: { url: shaped.url, method: shaped.method, headers: shaped.headers, body: shaped.body }, ...input.options !== void 0 ? { options: input.options } : {}, transport: input.transport, ...input.sleep !== void 0 ? { sleep: input.sleep } : {}, ...input.now !== void 0 ? { now: input.now } : {} });
|
|
11124
|
+
const parsed = parsecompletion(input.provider.style, transport.body);
|
|
11125
|
+
if (parsed.text === void 0) throw new Error(parsed.reason ?? "The provider answer did not parse.");
|
|
11126
|
+
return { text: parsed.text, ...parsed.usage !== void 0 ? { usage: parsed.usage } : {}, request: shaped };
|
|
11127
|
+
}
|
|
11128
|
+
async function calllocal(input) {
|
|
11129
|
+
if (input.local.endpoint.trim() === "") throw new Error("The local model needs the user configured endpoint url before any call runs.");
|
|
11130
|
+
if (!islocalorigin(input.local.endpoint)) throw new Error("The local model endpoint must stay a local machine address; the call never leaves the machine.");
|
|
11131
|
+
const provider = { id: "local", name: "The local model endpoint", endpoint: input.local.endpoint, style: input.local.style, models: [input.local.model], status: "available", createdat: 0 };
|
|
11132
|
+
return callmodel({ provider, model: input.local.model, messages: input.messages, ...input.apikey !== void 0 ? { apikey: input.apikey } : {}, ...input.temperature !== void 0 ? { temperature: input.temperature } : {}, ...input.maxtokens !== void 0 ? { maxtokens: input.maxtokens } : {}, ...input.options !== void 0 ? { options: input.options } : {}, transport: input.transport, ...input.sleep !== void 0 ? { sleep: input.sleep } : {}, ...input.now !== void 0 ? { now: input.now } : {} });
|
|
11133
|
+
}
|
|
11134
|
+
var commandguard = { schema: { intent: { type: "string", required: true }, entities: { type: "array", required: true }, confidence: { type: "number", required: true } }, retries: 1 };
|
|
11135
|
+
function classifyintent(text2) {
|
|
11136
|
+
const words = text2.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
|
|
11137
|
+
if (words.length === 0) return { intent: "ask", confidence: 0 };
|
|
11138
|
+
const scores = { navigate: 0, extract: 0, fill: 0, monitor: 0, automate: 0, ask: 0 };
|
|
11139
|
+
const keywords = [
|
|
11140
|
+
["navigate", ["go", "open", "visit", "navigate", "browse", "url", "site", "page", "to"]],
|
|
11141
|
+
["extract", ["extract", "scrape", "collect", "read", "gather", "copy", "table", "data", "text"]],
|
|
11142
|
+
["fill", ["fill", "type", "enter", "form", "submit", "login", "sign", "checkout", "field"]],
|
|
11143
|
+
["monitor", ["watch", "monitor", "observe", "track", "alert", "notify", "poll", "changes"]],
|
|
11144
|
+
["automate", ["automate", "workflow", "repeat", "every", "schedule", "batch", "pipeline", "steps", "then"]],
|
|
11145
|
+
["ask", ["what", "who", "when", "where", "why", "how", "explain", "summarize", "ask", "question", "tell"]]
|
|
11146
|
+
];
|
|
11147
|
+
for (const [intent, list] of keywords) for (const word of list) if (words.includes(word)) scores[intent] += 1;
|
|
11148
|
+
let best = "ask";
|
|
11149
|
+
let bestscore = scores.ask;
|
|
11150
|
+
for (const [intent] of keywords) if (scores[intent] > bestscore) {
|
|
11151
|
+
best = intent;
|
|
11152
|
+
bestscore = scores[intent];
|
|
11153
|
+
}
|
|
11154
|
+
const total = Object.values(scores).reduce((sum, value) => sum + value, 0);
|
|
11155
|
+
const confidence = bestscore === 0 ? 0.1 : Math.min(1, Math.round((bestscore / total * 0.6 + Math.min(bestscore / 3, 1) * 0.4) * 100) / 100);
|
|
11156
|
+
return { intent: best, confidence };
|
|
11157
|
+
}
|
|
11158
|
+
async function parsecommand(input) {
|
|
11159
|
+
if (input.text.trim() === "") return { reason: "The command parse needs the natural language text." };
|
|
11160
|
+
const guard = input.guard ?? commandguard;
|
|
11161
|
+
const answer = await callmodel({ provider: input.provider, model: input.model, messages: [{ role: "system", content: "Parse the user command into json with the fields intent (one of navigate, extract, fill, monitor, automate, ask), entities (an array of { name, value } objects) and confidence (a number between 0 and 1). Answer with the json object only." }, { role: "user", content: input.text }], ...input.apikey !== void 0 ? { apikey: input.apikey } : {}, transport: input.transport, ...input.sleep !== void 0 ? { sleep: input.sleep } : {}, ...input.now !== void 0 ? { now: input.now } : {} });
|
|
11162
|
+
const output = guardoutput({ guard, attempts: [answer.text] });
|
|
11163
|
+
if (output.verdict !== "valid" || output.parsed === void 0) return { output, reason: output.reason ?? "The command answer failed its guard." };
|
|
11164
|
+
const parsed = output.parsed;
|
|
11165
|
+
if (typeof parsed.intent !== "string") return { output, reason: "The command answer carries no intent." };
|
|
11166
|
+
const intents = ["navigate", "extract", "fill", "monitor", "automate", "ask"];
|
|
11167
|
+
if (!intents.includes(parsed.intent)) return { output, reason: `The intent ${parsed.intent} is not one of the intent kinds.` };
|
|
11168
|
+
const entities = Array.isArray(parsed.entities) ? parsed.entities.filter((entity) => entity !== null && typeof entity === "object" && !Array.isArray(entity) && typeof entity.name === "string" && typeof entity.value === "string") : [];
|
|
11169
|
+
const confidence = typeof parsed.confidence === "number" && Number.isFinite(parsed.confidence) ? Math.min(1, Math.max(0, parsed.confidence)) : 0;
|
|
11170
|
+
return { parse: { text: input.text, intent: parsed.intent, entities, confidence, model: input.model, providerid: input.provider.id, parsedat: (input.now ?? Date.now)() }, output };
|
|
11171
|
+
}
|
|
11172
|
+
async function draftplan(input) {
|
|
11173
|
+
if (input.goal.trim() === "") return { reason: "The plan draft needs the goal." };
|
|
11174
|
+
const lessons = input.lessons ?? [];
|
|
11175
|
+
const answer = await callmodel({ provider: input.provider, model: input.model, messages: [{ role: "system", content: `Draft a browser agent plan as json with the fields goal (string), steps (an array of { kind, target, value, summary } objects using browser action kinds) and openquestions (an array of strings for what stays unclear).${lessons.length > 0 ? ` The running lessons of the earlier steps: ${lessons.join(" | ")}.` : ""} Answer with the json object only.` }, { role: "user", content: input.goal }], ...input.apikey !== void 0 ? { apikey: input.apikey } : {}, transport: input.transport, ...input.sleep !== void 0 ? { sleep: input.sleep } : {}, ...input.now !== void 0 ? { now: input.now } : {} });
|
|
11176
|
+
const guard = { schema: { goal: { type: "string", required: true }, steps: { type: "array", required: true }, openquestions: { type: "array" } }, retries: 1 };
|
|
11177
|
+
const output = guardoutput({ guard, attempts: [answer.text] });
|
|
11178
|
+
if (output.verdict !== "valid" || output.parsed === void 0) return { output, reason: output.reason ?? "The plan draft answer failed its guard." };
|
|
11179
|
+
const parsed = output.parsed;
|
|
11180
|
+
const rawsteps = Array.isArray(parsed.steps) ? parsed.steps : [];
|
|
11181
|
+
const steps = rawsteps.filter((step) => step !== null && typeof step === "object" && !Array.isArray(step)).map((step, index) => ({ id: `step${index + 1}`, kind: typeof step.kind === "string" ? step.kind : "", ...typeof step.target === "string" && step.target.trim() !== "" ? { target: step.target } : {}, ...typeof step.value === "string" && step.value.trim() !== "" ? { value: step.value } : {}, summary: typeof step.summary === "string" ? step.summary : "" }));
|
|
11182
|
+
const openquestions = Array.isArray(parsed.openquestions) ? parsed.openquestions.filter((question) => typeof question === "string") : [];
|
|
11183
|
+
const draft = { id: randomid(), goal: typeof parsed.goal === "string" && parsed.goal.trim() !== "" ? parsed.goal : input.goal, steps, openquestions, providerid: input.provider.id, model: input.model, state: "draft", lintfindings: [], createdat: (input.now ?? Date.now)() };
|
|
11184
|
+
draft.lintfindings = planlint(draft, input.origin ?? "");
|
|
11185
|
+
return { draft, output };
|
|
11186
|
+
}
|
|
11187
|
+
async function replannonfail(input) {
|
|
11188
|
+
if (input.reason.trim() === "") return { reason: "The replan needs the failure reason." };
|
|
11189
|
+
const completed = input.draft.steps.filter((step) => input.completedstepids.includes(step.id));
|
|
11190
|
+
const failed = input.draft.steps.filter((step) => input.failedstepids.includes(step.id));
|
|
11191
|
+
const lessons = input.lessons ?? [];
|
|
11192
|
+
const answer = await callmodel({ provider: input.provider, model: input.model, messages: [{ role: "system", content: `The plan ${input.draft.goal} failed at the steps ${failed.map((step) => step.summary).join("; ") || "unknown"} with the reason: ${input.reason}. The completed steps stay: ${completed.map((step) => step.summary).join("; ") || "none"}.${lessons.length > 0 ? ` The running lessons: ${lessons.join(" | ")}.` : ""} Draft the revised tail steps of the plan as json with the field steps (an array of { kind, target, value, summary } objects using browser action kinds). Answer with the json object only.` }, { role: "user", content: input.draft.goal }], ...input.apikey !== void 0 ? { apikey: input.apikey } : {}, transport: input.transport, ...input.sleep !== void 0 ? { sleep: input.sleep } : {}, ...input.now !== void 0 ? { now: input.now } : {} });
|
|
11193
|
+
const guard = { schema: { steps: { type: "array", required: true } }, retries: 1 };
|
|
11194
|
+
const output = guardoutput({ guard, attempts: [answer.text] });
|
|
11195
|
+
if (output.verdict !== "valid" || output.parsed === void 0) return { output, reason: output.reason ?? "The replan answer failed its guard." };
|
|
11196
|
+
const rawsteps = Array.isArray(output.parsed.steps) ? output.parsed.steps : [];
|
|
11197
|
+
const tail = rawsteps.filter((step) => step !== null && typeof step === "object" && !Array.isArray(step)).map((step, index) => ({ id: `tail${index + 1}`, kind: typeof step.kind === "string" ? step.kind : "", ...typeof step.target === "string" && step.target.trim() !== "" ? { target: step.target } : {}, ...typeof step.value === "string" && step.value.trim() !== "" ? { value: step.value } : {}, summary: typeof step.summary === "string" ? step.summary : "", freshreview: true }));
|
|
11198
|
+
const replan = { id: randomid(), draftid: input.draft.id, completedstepids: [...input.completedstepids], failedstepids: [...input.failedstepids], tail, reason: input.reason, providerid: input.provider.id, model: input.model, state: "pending", createdat: (input.now ?? Date.now)() };
|
|
11199
|
+
return { replan, output };
|
|
11200
|
+
}
|
|
11201
|
+
async function reflectstep(input) {
|
|
11202
|
+
if (input.outcome.trim() === "") return { reason: "The reflection needs the step outcome." };
|
|
11203
|
+
const lessons = input.lessons ?? [];
|
|
11204
|
+
const answer = await callmodel({ provider: input.provider, model: input.model, messages: [{ role: "system", content: `Reflect on the executed step ${input.stepid} of the run ${input.runid} with the outcome: ${input.outcome}.${lessons.length > 0 ? ` The running lessons of the earlier steps: ${lessons.join(" | ")}.` : ""} Answer as json with the fields outcome (string), lesson (string) and advice (string) for the next step. Answer with the json object only.` }, { role: "user", content: input.outcome }], ...input.apikey !== void 0 ? { apikey: input.apikey } : {}, transport: input.transport, ...input.sleep !== void 0 ? { sleep: input.sleep } : {}, ...input.now !== void 0 ? { now: input.now } : {} });
|
|
11205
|
+
const guard = { schema: { outcome: { type: "string", required: true }, lesson: { type: "string", required: true }, advice: { type: "string", required: true } }, retries: 1 };
|
|
11206
|
+
const output = guardoutput({ guard, attempts: [answer.text] });
|
|
11207
|
+
if (output.verdict !== "valid" || output.parsed === void 0) return { output, reason: output.reason ?? "The reflection answer failed its guard." };
|
|
11208
|
+
const parsed = output.parsed;
|
|
11209
|
+
if (typeof parsed.lesson !== "string" || typeof parsed.advice !== "string") return { output, reason: "The reflection answer carries no lesson or advice." };
|
|
11210
|
+
const note = { id: randomid(), runid: input.runid, stepid: input.stepid, outcome: typeof parsed.outcome === "string" ? parsed.outcome : input.outcome, lesson: parsed.lesson, advice: parsed.advice, providerid: input.provider.id, model: input.model, createdat: (input.now ?? Date.now)() };
|
|
11211
|
+
return { note, output };
|
|
11212
|
+
}
|
|
11213
|
+
function reflectionsummary(notes) {
|
|
11214
|
+
const latest = /* @__PURE__ */ new Map();
|
|
11215
|
+
for (const note of notes) latest.set(note.stepid, note);
|
|
11216
|
+
const lessons = [...latest.values()].sort((one, two) => one.createdat - two.createdat).map((note) => note.lesson);
|
|
11217
|
+
return lessons.length === 0 ? "" : lessons.join(" | ");
|
|
11218
|
+
}
|
|
11219
|
+
function stripguardrails(text2) {
|
|
11220
|
+
const fenced = text2.match(/```(?:[a-z]*)\s*\r?\n?([\s\S]*?)```/i);
|
|
11221
|
+
const candidate = fenced !== null ? fenced[1] ?? "" : text2;
|
|
11222
|
+
const start = candidate.indexOf("{");
|
|
11223
|
+
const end = candidate.lastIndexOf("}");
|
|
11224
|
+
if (start >= 0 && end > start) return candidate.slice(start, end + 1);
|
|
11225
|
+
const arraystart = candidate.indexOf("[");
|
|
11226
|
+
const arrayend = candidate.lastIndexOf("]");
|
|
11227
|
+
if (arraystart >= 0 && arrayend > arraystart) return candidate.slice(arraystart, arrayend + 1);
|
|
11228
|
+
return candidate.trim();
|
|
11229
|
+
}
|
|
11230
|
+
function parseoutput(input) {
|
|
11231
|
+
const raw = input.text;
|
|
11232
|
+
const stripped = stripguardrails(raw);
|
|
11233
|
+
const markers = input.guard.refusalmarkers ?? defaultrefusalmarkers;
|
|
11234
|
+
const lowered = stripped.toLowerCase();
|
|
11235
|
+
for (const marker of markers) if (marker.trim() !== "" && lowered.includes(marker.toLowerCase())) return { raw, verdict: "refused", reason: `The model answer carries the refusal marker ${marker}.`, attempts: 1 };
|
|
11236
|
+
let parsed;
|
|
11237
|
+
try {
|
|
11238
|
+
parsed = JSON.parse(stripped);
|
|
11239
|
+
} catch {
|
|
11240
|
+
return { raw, verdict: "invalid", reason: "The model answer is not json after the guardrail strip.", attempts: 1 };
|
|
11241
|
+
}
|
|
11242
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return { raw, verdict: "invalid", reason: "The model answer is not a json object.", attempts: 1 };
|
|
11243
|
+
const record2 = parsed;
|
|
11244
|
+
for (const [name, field] of Object.entries(input.guard.schema)) {
|
|
11245
|
+
const value = record2[name];
|
|
11246
|
+
if (value === void 0 || value === null) {
|
|
11247
|
+
if (field.required === true) return { raw, verdict: "invalid", reason: `The required field ${name} of the expected schema is missing.`, attempts: 1 };
|
|
11248
|
+
continue;
|
|
11249
|
+
}
|
|
11250
|
+
const actual = Array.isArray(value) ? "array" : typeof value;
|
|
11251
|
+
if (actual !== field.type) return { raw, verdict: "invalid", reason: `The field ${name} carries a ${actual} value where the schema asks a ${field.type}.`, attempts: 1 };
|
|
11252
|
+
}
|
|
11253
|
+
return { raw, parsed: record2, verdict: "valid", attempts: 1 };
|
|
11254
|
+
}
|
|
11255
|
+
function guardoutput(input) {
|
|
11256
|
+
const limit = Math.max(1, Math.floor(input.guard.retries) + 1);
|
|
11257
|
+
const attempts = input.attempts.slice(0, limit);
|
|
11258
|
+
let last;
|
|
11259
|
+
for (let index = 0; index < attempts.length; index += 1) {
|
|
11260
|
+
const output = parseoutput({ guard: input.guard, text: attempts[index] ?? "" });
|
|
11261
|
+
last = { ...output, attempts: index + 1 };
|
|
11262
|
+
if (output.verdict === "valid") return last;
|
|
11263
|
+
if (output.verdict === "refused") return { ...output, attempts: index + 1 };
|
|
11264
|
+
}
|
|
11265
|
+
const exhausted = last === void 0 ? { raw: "", verdict: "invalid", reason: "The model answer never arrived.", attempts: 0 } : { ...last, verdict: "invalid", reason: `${last.reason ?? "The model answer failed its guard."} Every retry attempt failed, so the guard refuses the output and nothing executes.` };
|
|
11266
|
+
return exhausted;
|
|
11267
|
+
}
|
|
11268
|
+
function toolbriefof(tool) {
|
|
11269
|
+
return { tool: tool.name, summary: `${tool.name}: ${tool.description.split(".")[0] ?? tool.description}.`, description: tool.description, risk: tool.risk, parameters: Object.entries(tool.inputschema.properties).map(([name, property]) => ({ name, type: property.type, description: property.description, required: property.required === true })) };
|
|
11270
|
+
}
|
|
11271
|
+
function rendertoolbriefs(tools) {
|
|
11272
|
+
const blocks = tools.map((tool) => {
|
|
11273
|
+
const brief = toolbriefof(tool);
|
|
11274
|
+
const parameters = brief.parameters.map((parameter) => ` - name: ${parameter.name}
|
|
11275
|
+
type: ${parameter.type}
|
|
11276
|
+
required: ${parameter.required ? "true" : "false"}
|
|
11277
|
+
description: ${parameter.description}`).join("\n");
|
|
11278
|
+
return ` - tool: ${brief.tool}
|
|
11279
|
+
summary: ${brief.summary}
|
|
11280
|
+
risk: ${brief.risk}
|
|
11281
|
+
parameters:
|
|
11282
|
+
${parameters}`;
|
|
11283
|
+
});
|
|
11284
|
+
return `tools:
|
|
11285
|
+
${blocks.join("\n")}
|
|
11286
|
+
consent: every tool with side effects executes only the approved plan step it names; a proposal without the approved step stays refused.`;
|
|
11287
|
+
}
|
|
11288
|
+
function usagetotals(records, filter = {}) {
|
|
11289
|
+
const kept = records.filter((record2) => (filter.runid === void 0 || record2.runid === filter.runid) && (filter.stepid === void 0 || record2.stepid === filter.stepid) && (filter.since === void 0 || record2.at >= filter.since) && (filter.until === void 0 || record2.at <= filter.until));
|
|
11290
|
+
return kept.reduce((totals, record2) => ({ prompttokens: totals.prompttokens + record2.prompttokens, completiontokens: totals.completiontokens + record2.completiontokens, totaltokens: totals.totaltokens + record2.totaltokens, cost: totals.cost + record2.cost, calls: totals.calls + 1 }), { prompttokens: 0, completiontokens: 0, totaltokens: 0, cost: 0, calls: 0 });
|
|
11291
|
+
}
|
|
11292
|
+
function budgetcheck(input) {
|
|
11293
|
+
if (input.budget === void 0) return { allowed: true, halted: false, asksuser: false };
|
|
11294
|
+
if (input.budget.maxtokens !== void 0 && Number.isFinite(input.budget.maxtokens) && input.totals.totaltokens >= input.budget.maxtokens) return { allowed: false, halted: true, asksuser: true, reason: `The run reached the user configured token ceiling of ${input.budget.maxtokens} and halts until the user answers.` };
|
|
11295
|
+
if (input.budget.maxcost !== void 0 && Number.isFinite(input.budget.maxcost) && input.totals.cost >= input.budget.maxcost) return { allowed: false, halted: true, asksuser: true, reason: `The run reached the user configured cost ceiling of ${input.budget.maxcost} and halts until the user answers.` };
|
|
11296
|
+
return { allowed: true, halted: false, asksuser: false };
|
|
11297
|
+
}
|
|
11298
|
+
|
|
11299
|
+
// modelroute.ts
|
|
11300
|
+
function routevalid(route) {
|
|
11301
|
+
if (route.kind.trim() === "") return { allowed: false, reason: "The model route needs its task kind." };
|
|
11302
|
+
if (route.providerid.trim() === "") return { allowed: false, reason: "The model route needs the provider it routes to." };
|
|
11303
|
+
if (route.model.trim() === "") return { allowed: false, reason: "The model route needs the model name it routes to." };
|
|
11304
|
+
const hasfallbackprovider = route.fallbackproviderid !== void 0 && route.fallbackproviderid.trim() !== "";
|
|
11305
|
+
const hasfallbackmodel = route.fallbackmodel !== void 0 && route.fallbackmodel.trim() !== "";
|
|
11306
|
+
if (hasfallbackprovider !== hasfallbackmodel) return { allowed: false, reason: "The fallback of a model route needs its provider and its model together." };
|
|
11307
|
+
return { allowed: true };
|
|
11308
|
+
}
|
|
11309
|
+
function routesfor(routes, kind) {
|
|
11310
|
+
return routes.filter((route) => route.kind === kind).sort((one, two) => two.revision - one.revision);
|
|
11311
|
+
}
|
|
11312
|
+
function resolveroute(input) {
|
|
11313
|
+
const candidates = routesfor(input.routes, input.kind);
|
|
11314
|
+
if (candidates.length === 0) return { reason: `No model route configures the task kind ${input.kind}; the user picks the provider and model pair.` };
|
|
11315
|
+
for (const route of candidates) {
|
|
11316
|
+
if (!routevalid(route).allowed) continue;
|
|
11317
|
+
const provider = input.providers.find((candidate) => candidate.id === route.providerid);
|
|
11318
|
+
if (provider === void 0) return { reason: `The route of ${input.kind} names the missing provider ${route.providerid}.` };
|
|
11319
|
+
if (provider.status === "unavailable") return { reason: `The provider ${provider.name} of the route of ${input.kind} stays marked unavailable from its last failure.` };
|
|
11320
|
+
if (!provider.models.includes(route.model)) return { reason: `The route of ${input.kind} names the model ${route.model} outside the model list of ${provider.name}.` };
|
|
11321
|
+
return { route, provider, model: route.model };
|
|
11322
|
+
}
|
|
11323
|
+
return { reason: `Every route of the task kind ${input.kind} failed its validation.` };
|
|
11324
|
+
}
|
|
11325
|
+
function markprovider(input) {
|
|
11326
|
+
return input.providers.map((provider) => provider.id === input.providerid ? { ...provider, status: input.available ? "available" : "unavailable", lastcheckedat: input.now } : provider);
|
|
11327
|
+
}
|
|
11328
|
+
function fallbackroute(input) {
|
|
11329
|
+
const candidates = routesfor(input.routes, input.kind);
|
|
11330
|
+
const primary = candidates.find((route) => routevalid(route).allowed);
|
|
11331
|
+
if (primary === void 0) return { reason: `No valid route configures the task kind ${input.kind}, so no fallback applies.` };
|
|
11332
|
+
if (primary.fallbackproviderid === void 0 || primary.fallbackmodel === void 0) return { reason: `The route of ${input.kind} carries no user configured fallback pair.` };
|
|
11333
|
+
const provider = input.providers.find((candidate) => candidate.id === primary.fallbackproviderid);
|
|
11334
|
+
if (provider === void 0) return { reason: `The fallback names the missing provider ${primary.fallbackproviderid}.` };
|
|
11335
|
+
if (provider.status === "unavailable") return { reason: `The fallback provider ${provider.name} stays marked unavailable from its last failure.` };
|
|
11336
|
+
if (!provider.models.includes(primary.fallbackmodel)) return { reason: `The fallback names the model ${primary.fallbackmodel} outside the model list of ${provider.name}.` };
|
|
11337
|
+
return { route: primary, provider, model: primary.fallbackmodel };
|
|
11338
|
+
}
|
|
11339
|
+
|
|
11340
|
+
// promptlibrary.ts
|
|
11341
|
+
function templatevariables(body) {
|
|
11342
|
+
const names = [];
|
|
11343
|
+
for (const match of body.matchAll(/\{\{\s*([a-z0-9]+)\s*\}\}/g)) {
|
|
11344
|
+
const name = match[1] ?? "";
|
|
11345
|
+
if (name !== "" && !names.includes(name)) names.push(name);
|
|
11346
|
+
}
|
|
11347
|
+
return names;
|
|
11348
|
+
}
|
|
11349
|
+
function savetemplate(input) {
|
|
11350
|
+
const existing = input.templates.filter((template) => template.name === input.name);
|
|
11351
|
+
const version = existing.length === 0 ? 1 : Math.max(...existing.map((template) => template.version)) + 1;
|
|
11352
|
+
const record2 = { id: randomid(), name: input.name, body: input.body, variables: templatevariables(input.body), version, ...input.notes !== void 0 && input.notes.trim() !== "" ? { notes: input.notes } : {}, createdat: input.now };
|
|
11353
|
+
return [record2, ...input.templates];
|
|
11354
|
+
}
|
|
11355
|
+
function latesttemplate(templates, name) {
|
|
11356
|
+
const versions = templates.filter((template) => template.name === name);
|
|
11357
|
+
return versions.length === 0 ? void 0 : versions.reduce((newest, template) => template.version > newest.version ? template : newest);
|
|
11358
|
+
}
|
|
11359
|
+
function searchtemplates(templates, query) {
|
|
11360
|
+
const term = query.trim().toLowerCase();
|
|
11361
|
+
const matches = term === "" ? templates : templates.filter((template) => template.name.toLowerCase().includes(term) || template.body.toLowerCase().includes(term) || (template.notes ?? "").toLowerCase().includes(term) || template.variables.some((variable) => variable.toLowerCase().includes(term)));
|
|
11362
|
+
return [...matches].sort((one, two) => two.version - one.version || two.createdat - one.createdat);
|
|
11363
|
+
}
|
|
11364
|
+
function removetemplate(templates, name) {
|
|
11365
|
+
return templates.filter((template) => template.name !== name);
|
|
11366
|
+
}
|
|
11367
|
+
|
|
10480
11368
|
// extension/pagesession.ts
|
|
10481
11369
|
function capturepagestate(sections) {
|
|
10482
11370
|
const wants = (section) => sections.includes(section);
|
|
@@ -15024,8 +15912,8 @@ async function executecdpstep(step, session, plan, tabid2, origin) {
|
|
|
15024
15912
|
const input = breakpointinputof(options.breakpoint);
|
|
15025
15913
|
if (!input) throw new Error("The breakpoint input is absent.");
|
|
15026
15914
|
const settings = await memory.getsettings();
|
|
15027
|
-
const
|
|
15028
|
-
if (!
|
|
15915
|
+
const budgetcheck2 = breakpointbudgetallowed(active.breakpoints.filter((spec) => spec.revertedat === void 0).length, breakpointceilingof(settings));
|
|
15916
|
+
if (!budgetcheck2.allowed) throw new Error(budgetcheck2.reason ?? "The breakpoint ceiling refused the registration.");
|
|
15029
15917
|
const output = await dispatchpagestep(step, tabid2, origin, plan) ?? { ok: false, summary: "The breakpoint registration returned no result." };
|
|
15030
15918
|
if (!output.ok) return output;
|
|
15031
15919
|
const registered = output.details?.breakpoint;
|
|
@@ -15667,8 +16555,8 @@ async function executenetcontrolstep(step, session, plan, tabid2, origin) {
|
|
|
15667
16555
|
const limits = await memory.getratelimits(Date.now());
|
|
15668
16556
|
const limit = limits.find((item) => item.origin === new URL(url).origin);
|
|
15669
16557
|
const wait = ratelimitwait(limit, Date.now());
|
|
15670
|
-
const
|
|
15671
|
-
if (!
|
|
16558
|
+
const budgetcheck2 = ratelimitbudgetallowed(wait, budget);
|
|
16559
|
+
if (!budgetcheck2.allowed) throw new Error(budgetcheck2.reason ?? "The rate limit wait exceeds the reviewed budget.");
|
|
15672
16560
|
if (wait > 0) {
|
|
15673
16561
|
await audit("control", `The rate limiter waits ${wait} milliseconds until the reset window of ${new URL(url).origin} passes before the submission.`, extra);
|
|
15674
16562
|
await new Promise((resolve) => setTimeout(resolve, wait));
|
|
@@ -16922,7 +17810,7 @@ async function handlerequest(message, sender) {
|
|
|
16922
17810
|
const livetab = session ? await chrome.tabs.get(session.tabid).catch(() => void 0) : void 0;
|
|
16923
17811
|
const waitprofile = session ? waitprofiles.find((record2) => record2.origin === session.origin) : void 0;
|
|
16924
17812
|
const livestate = { phase: livetab?.status === "loading" ? "loading" : "complete", ...navrecords[0] ? { finalurl: navrecords[0].finalurl, redirects: navrecords[0].chain } : {} };
|
|
16925
|
-
return { config: await memory.getconfig(), session, plan, progress: plan && progress?.planid === plan.id ? progress : void 0, diagnostic: await memory.getdiagnostic(), audit: await memory.getaudit(), capabilities: await refreshcapabilities(), outcomes: await memory.getoutcomes(), holds: heldkeysreport({ tabid: session?.tabid ?? 0, holds }), dialogs: await memory.getdialogs(), retries: await memory.getretries(), ...signals ? { signals: signalsreport({ signals }) } : { signals: signalsreport({}) }, banners: await memory.getbanners(), mutationevents: await memory.getmutationevents(), focusevents: await memory.getfocusevents(), diffs: await memory.getdiffs(), selectors: await memory.getselectors(), ...a11y ? { a11y } : {}, ...reader ? { reader } : {}, ...map ? { map } : {}, trail: trailreport({ ...session ? { sessionid: session.id } : {}, trail }), navrecords, ratestates, safeties, curated, waitprofiles, auths, navcontrol, navqueues, artifacts, navstate: livestate, ...waitprofile ? { waitprofile } : {}, offline: !navigator.onLine, tabs, windows, layouts: layoutreport({ layouts }), tabgroups, tabmetas, badges, snapshots, closedtabs, tabwatchevents, clones, tasktabgauge: taskgauge, ...controltab ? { controltab } : {}, tabreport: report, profiles, tickets, wizards: wizardreport({ ...session ? { sessionid: session.id } : {}, wizards, picks }), picks, errorreports, captchas, detections, ...codeentry !== void 0 ? { codeentry: true } : {}, datasets, imports, extractsessions, streams, exports, provenances, taskrules, sheetendpoints: sheetgrants, downloads, netlogs, clipconsents, clips, quarantines, cleanuprules, cleanupruns, capturecounters, inventory, mimefilters, scanhooks, captures: capturemetadata, capturepairs, capturepolicy: runsettings?.capturepolicy ?? "manual", media: mediarecords, imagebatches, recordingconsents, recordingactive: [...activerecordings.values()].map((active) => ({ id: active.record.id, kind: active.record.kind, scope: active.record.scope, startedat: active.record.startedat, stopat: active.stopat })), recordingwindow: runsettings?.recordingwindow, calls, endpoints, fetchconsents, apikeys, callretention: runsettings?.callretention, fetchesactive: activefetches.size, exchanges, channels, subscriptions, apimap, messages: messagecount, webrequestgrant: runsettings?.webrequestgrant === true, bodyretention: runsettings?.bodyretention, timelineretention: runsettings?.timelineretention, timeline, consoleconsents: await memory.getconsoleconsents(), rotationtargets: await memory.getrotationtargets(), levelsummaries: await memory.getlevelsummaries(), cdpsessions: await memory.getcdpsessions(), cdpcommands: await memory.getcdpcommands(), cdpeventrules: await memory.getcdpeventrules(), breakpoints: await memory.getbreakpoints(), pauses: await memory.getpauses(), watchexpressions: await memory.getwatchexpressions(), scriptoverrides: await memory.getscriptoverrides(), debuggergrants: await memory.getdebuggergrants(), pauseretention: runsettings?.pauseretention, breakpointceiling: runsettings?.breakpointceiling, cdpattached: [...activecdpsessions.values()].filter((active) => active.session.detachedat === void 0).length, profileretention: runsettings?.profileretention, traceceiling: runsettings?.traceceiling, profile: profilereport({ flows: await memory.getflowmetrics(), heaps: await memory.getheaprecords(), samples: await memory.getgrowsamples(), trends: await memory.gettrends(), profiles: await memory.getcpuprofiles(), shifts: await memory.getshiftentries(), traces: await memory.gettracerecords(), sourcemaps: await memory.getsourcemaps(), consents: await memory.getsourcemapconsents() }), profileactive: activememorytrackers.size + activeprofiletargets.size, profiletargets: [...activeprofiletargets.values()].flatMap((entry) => entry.targets), socketsactive: activesockets.size, emulation: emulationreport({ ...plan && await loademulationstate(plan.id) !== void 0 ? { state: await loademulationstate(plan.id) } : {}, devices: await memory.getdevicepresets(), networks: await memory.getnetworkpresets(), locations: await memory.getlocationpresets(), agents: await memory.getagentpresets(), blackbox: await memory.getblackboxrules(), permissions: await memory.getpermissionoverrides(), consents: await memory.getlocationconsents() }), emulatedlayers: plan ? layernames(await loademulationstate(plan.id)) : [], emulationretention: runsettings?.emulationretention, traffic, tokens, authflows, activerules: [...activerules.values()].reduce((total, ruleset) => total + ruleset.blocks.filter((rule) => rule.revertedat === void 0).length + ruleset.mocks.filter((rule) => rule.revertedat === void 0).length + ruleset.rewrites.filter((rule) => rule.revertedat === void 0).length + (ruleset.proxy !== void 0 && ruleset.proxy.revertedat === void 0 ? 1 : 0), 0), sessionmemory: sessionreport({ records: sessionrecords, events: await memory.getsessionevents(), folders: await memory.getsessionfolders(), diffs: await memory.getsessiondiffs(), ...autosnapshot !== void 0 ? { auto: autosnapshot.interval } : {}, ...crashed ? { crashed: true } : {} }), autosnapshotstate: autosnapshot, sessionretention: runsettings?.sessionretention, trigger: triggerlist({ rules: await memory.gettriggerules(), workflows: await memory.listworkflows(), queue: await memory.gettriggerqueue() }), triggerretention: runsettings?.triggerretention, workflow: workflowreport({ workflows: await memory.listworkflows(), runs: await memory.listworkflowruns(), templates: await memory.getsteptemplates(), ...newestworkflowrun !== void 0 ? { log: await memory.getrunlog(newestworkflowrun.id), scopes: await memory.getrunscopes(newestworkflowrun.id), provenance: await memory.getworkflowprovenance(newestworkflowrun.id), control: await memory.listcontroldecisions(newestworkflowrun.id) } : {} }), runlogretention: runsettings?.runlogretention, runhistoryretention: runsettings?.runhistoryretention, editor: editorstate({ versions: await memory.listworkflowversions(), diffs: await memory.listversiondiffs(), history: await memory.gethistory(), overrides: await memory.listsiteoverrides(), imports: (await memory.listworkflowimports()).map((entry) => ({ id: entry.id, workflowid: entry.record.id, name: entry.record.name, version: entry.record.version, steps: entry.record.steps.length, risk: entry.record.risk, importedat: entry.importedat, ...entry.filename !== void 0 ? { filename: entry.filename } : {} })), backgroundruns: await memory.getbackgroundruns(), watchdog: { ...runsettings?.watchdog !== void 0 ? { config: runsettings.watchdog } : {}, events: await memory.listwatchdogevents() } }), ...taskstate !== void 0 ? { taskstate } : {}, ...stitchprogress.size > 0 ? { stitchprogress: [...stitchprogress.values()] } : {}, mcp: await mcpstateof() };
|
|
17813
|
+
return { config: await memory.getconfig(), session, plan, progress: plan && progress?.planid === plan.id ? progress : void 0, diagnostic: await memory.getdiagnostic(), audit: await memory.getaudit(), capabilities: await refreshcapabilities(), outcomes: await memory.getoutcomes(), holds: heldkeysreport({ tabid: session?.tabid ?? 0, holds }), dialogs: await memory.getdialogs(), retries: await memory.getretries(), ...signals ? { signals: signalsreport({ signals }) } : { signals: signalsreport({}) }, banners: await memory.getbanners(), mutationevents: await memory.getmutationevents(), focusevents: await memory.getfocusevents(), diffs: await memory.getdiffs(), selectors: await memory.getselectors(), ...a11y ? { a11y } : {}, ...reader ? { reader } : {}, ...map ? { map } : {}, trail: trailreport({ ...session ? { sessionid: session.id } : {}, trail }), navrecords, ratestates, safeties, curated, waitprofiles, auths, navcontrol, navqueues, artifacts, navstate: livestate, ...waitprofile ? { waitprofile } : {}, offline: !navigator.onLine, tabs, windows, layouts: layoutreport({ layouts }), tabgroups, tabmetas, badges, snapshots, closedtabs, tabwatchevents, clones, tasktabgauge: taskgauge, ...controltab ? { controltab } : {}, tabreport: report, profiles, tickets, wizards: wizardreport({ ...session ? { sessionid: session.id } : {}, wizards, picks }), picks, errorreports, captchas, detections, ...codeentry !== void 0 ? { codeentry: true } : {}, datasets, imports, extractsessions, streams, exports, provenances, taskrules, sheetendpoints: sheetgrants, downloads, netlogs, clipconsents, clips, quarantines, cleanuprules, cleanupruns, capturecounters, inventory, mimefilters, scanhooks, captures: capturemetadata, capturepairs, capturepolicy: runsettings?.capturepolicy ?? "manual", media: mediarecords, imagebatches, recordingconsents, recordingactive: [...activerecordings.values()].map((active) => ({ id: active.record.id, kind: active.record.kind, scope: active.record.scope, startedat: active.record.startedat, stopat: active.stopat })), recordingwindow: runsettings?.recordingwindow, calls, endpoints, fetchconsents, apikeys, callretention: runsettings?.callretention, fetchesactive: activefetches.size, exchanges, channels, subscriptions, apimap, messages: messagecount, webrequestgrant: runsettings?.webrequestgrant === true, bodyretention: runsettings?.bodyretention, timelineretention: runsettings?.timelineretention, timeline, consoleconsents: await memory.getconsoleconsents(), rotationtargets: await memory.getrotationtargets(), levelsummaries: await memory.getlevelsummaries(), cdpsessions: await memory.getcdpsessions(), cdpcommands: await memory.getcdpcommands(), cdpeventrules: await memory.getcdpeventrules(), breakpoints: await memory.getbreakpoints(), pauses: await memory.getpauses(), watchexpressions: await memory.getwatchexpressions(), scriptoverrides: await memory.getscriptoverrides(), debuggergrants: await memory.getdebuggergrants(), pauseretention: runsettings?.pauseretention, breakpointceiling: runsettings?.breakpointceiling, cdpattached: [...activecdpsessions.values()].filter((active) => active.session.detachedat === void 0).length, profileretention: runsettings?.profileretention, traceceiling: runsettings?.traceceiling, profile: profilereport({ flows: await memory.getflowmetrics(), heaps: await memory.getheaprecords(), samples: await memory.getgrowsamples(), trends: await memory.gettrends(), profiles: await memory.getcpuprofiles(), shifts: await memory.getshiftentries(), traces: await memory.gettracerecords(), sourcemaps: await memory.getsourcemaps(), consents: await memory.getsourcemapconsents() }), profileactive: activememorytrackers.size + activeprofiletargets.size, profiletargets: [...activeprofiletargets.values()].flatMap((entry) => entry.targets), socketsactive: activesockets.size, emulation: emulationreport({ ...plan && await loademulationstate(plan.id) !== void 0 ? { state: await loademulationstate(plan.id) } : {}, devices: await memory.getdevicepresets(), networks: await memory.getnetworkpresets(), locations: await memory.getlocationpresets(), agents: await memory.getagentpresets(), blackbox: await memory.getblackboxrules(), permissions: await memory.getpermissionoverrides(), consents: await memory.getlocationconsents() }), emulatedlayers: plan ? layernames(await loademulationstate(plan.id)) : [], emulationretention: runsettings?.emulationretention, traffic, tokens, authflows, activerules: [...activerules.values()].reduce((total, ruleset) => total + ruleset.blocks.filter((rule) => rule.revertedat === void 0).length + ruleset.mocks.filter((rule) => rule.revertedat === void 0).length + ruleset.rewrites.filter((rule) => rule.revertedat === void 0).length + (ruleset.proxy !== void 0 && ruleset.proxy.revertedat === void 0 ? 1 : 0), 0), sessionmemory: sessionreport({ records: sessionrecords, events: await memory.getsessionevents(), folders: await memory.getsessionfolders(), diffs: await memory.getsessiondiffs(), ...autosnapshot !== void 0 ? { auto: autosnapshot.interval } : {}, ...crashed ? { crashed: true } : {} }), autosnapshotstate: autosnapshot, sessionretention: runsettings?.sessionretention, trigger: triggerlist({ rules: await memory.gettriggerules(), workflows: await memory.listworkflows(), queue: await memory.gettriggerqueue() }), triggerretention: runsettings?.triggerretention, workflow: workflowreport({ workflows: await memory.listworkflows(), runs: await memory.listworkflowruns(), templates: await memory.getsteptemplates(), ...newestworkflowrun !== void 0 ? { log: await memory.getrunlog(newestworkflowrun.id), scopes: await memory.getrunscopes(newestworkflowrun.id), provenance: await memory.getworkflowprovenance(newestworkflowrun.id), control: await memory.listcontroldecisions(newestworkflowrun.id) } : {} }), runlogretention: runsettings?.runlogretention, runhistoryretention: runsettings?.runhistoryretention, editor: editorstate({ versions: await memory.listworkflowversions(), diffs: await memory.listversiondiffs(), history: await memory.gethistory(), overrides: await memory.listsiteoverrides(), imports: (await memory.listworkflowimports()).map((entry) => ({ id: entry.id, workflowid: entry.record.id, name: entry.record.name, version: entry.record.version, steps: entry.record.steps.length, risk: entry.record.risk, importedat: entry.importedat, ...entry.filename !== void 0 ? { filename: entry.filename } : {} })), backgroundruns: await memory.getbackgroundruns(), watchdog: { ...runsettings?.watchdog !== void 0 ? { config: runsettings.watchdog } : {}, events: await memory.listwatchdogevents() } }), ...taskstate !== void 0 ? { taskstate } : {}, ...stitchprogress.size > 0 ? { stitchprogress: [...stitchprogress.values()] } : {}, mcp: await mcpstateof(), llm: await llmstateof() };
|
|
16926
17814
|
}
|
|
16927
17815
|
case "capabilities":
|
|
16928
17816
|
return refreshcapabilities();
|
|
@@ -18690,6 +19578,122 @@ async function handlerequest(message, sender) {
|
|
|
18690
19578
|
}
|
|
18691
19579
|
return mcpstateof();
|
|
18692
19580
|
}
|
|
19581
|
+
case "mcpsubscribe": {
|
|
19582
|
+
const inputsub = message;
|
|
19583
|
+
const clientid = inputsub.clientid?.trim() ?? "";
|
|
19584
|
+
if (clientid === "") throw new Error("The event subscription needs the paired client.");
|
|
19585
|
+
const now = Date.now();
|
|
19586
|
+
if (inputsub.unsubscribe === true) {
|
|
19587
|
+
const subscriptionid = inputsub.subscriptionid?.trim() ?? "";
|
|
19588
|
+
if (subscriptionid === "") throw new Error("The unsubscribe needs the subscription id.");
|
|
19589
|
+
await memory.seteventsubscriptions(unsubscriberegister(await memory.geteventsubscriptions(), subscriptionid, now));
|
|
19590
|
+
await audit("protocol", `The user cancelled the event subscription ${subscriptionid} of the client ${clientid}; the record stays for the audit trail.`, {});
|
|
19591
|
+
return mcpstateof();
|
|
19592
|
+
}
|
|
19593
|
+
const registered = subscriberegister({ clientid, ...Array.isArray(inputsub.kinds) ? { kinds: inputsub.kinds } : {}, ...inputsub.origin !== void 0 && inputsub.origin.trim() !== "" ? { origin: inputsub.origin.trim() } : {}, ...inputsub.tool !== void 0 && inputsub.tool.trim() !== "" ? { tool: inputsub.tool.trim() } : {}, now });
|
|
19594
|
+
if (registered.subscription === void 0) throw new Error(registered.reason ?? "The event subscription did not register.");
|
|
19595
|
+
const grade = subscriptiongrade(registered.subscription);
|
|
19596
|
+
if (!grade.allowed) throw new Error(grade.reason ?? "The event subscription failed its gate.");
|
|
19597
|
+
await memory.seteventsubscriptions([registered.subscription, ...await memory.geteventsubscriptions()]);
|
|
19598
|
+
await audit("protocol", `The user subscribed the client ${clientid} to the ${registered.subscription.kinds.join(", ")} event kinds${registered.subscription.origin !== void 0 ? ` of the origin ${registered.subscription.origin}` : ""}${registered.subscription.tool !== void 0 ? ` for the tool ${registered.subscription.tool}` : ""}.`, {});
|
|
19599
|
+
return mcpstateof();
|
|
19600
|
+
}
|
|
19601
|
+
case "mcpresource": {
|
|
19602
|
+
const inputwatch = message;
|
|
19603
|
+
const clientid = inputwatch.clientid?.trim() ?? "";
|
|
19604
|
+
if (clientid === "") throw new Error("The resource watcher needs the paired client.");
|
|
19605
|
+
const now = Date.now();
|
|
19606
|
+
if (inputwatch.unwatch === true) {
|
|
19607
|
+
const watchid = inputwatch.watchid?.trim() ?? "";
|
|
19608
|
+
if (watchid === "") throw new Error("The unwatch needs the watcher id.");
|
|
19609
|
+
await memory.setresourcewatches(unwatchresource(await memory.getresourcewatches(), watchid, now));
|
|
19610
|
+
await audit("protocol", `The user cancelled the resource watcher ${watchid} of the client ${clientid}; the record stays for the audit trail.`, {});
|
|
19611
|
+
return mcpstateof();
|
|
19612
|
+
}
|
|
19613
|
+
const watched = watchresource({ clientid, resource: inputwatch.resource?.trim() ?? "", ...inputwatch.state !== void 0 ? { state: inputwatch.state } : {}, now });
|
|
19614
|
+
if (watched.watch === void 0) throw new Error(watched.reason ?? "The resource watcher did not start.");
|
|
19615
|
+
await memory.setresourcewatches([watched.watch, ...await memory.getresourcewatches()]);
|
|
19616
|
+
await audit("protocol", `The user started the ${watched.watch.resource} resource watcher for the client ${clientid} with its page state baseline; the deltas compare against it.`, {});
|
|
19617
|
+
return mcpstateof();
|
|
19618
|
+
}
|
|
19619
|
+
case "mcpsampling": {
|
|
19620
|
+
const inputsample = message;
|
|
19621
|
+
const now = Date.now();
|
|
19622
|
+
if (inputsample.respond === true) {
|
|
19623
|
+
const samplingid = inputsample.samplingid?.trim() ?? "";
|
|
19624
|
+
if (samplingid === "") throw new Error("The sampling answer needs the request id.");
|
|
19625
|
+
const outcome = answersampling({ requests: await memory.getsamplingrequests(), id: samplingid, ...inputsample.answer !== void 0 ? { answer: inputsample.answer } : {}, ...inputsample.refused === true ? { refused: true } : {}, now });
|
|
19626
|
+
if (outcome.request === void 0) throw new Error(outcome.reason ?? "The sampling answer closed no request.");
|
|
19627
|
+
await memory.setsamplingrequests(outcome.requests);
|
|
19628
|
+
await audit("protocol", `The sampling callback ${samplingid} of the client ${outcome.request.clientid} closed its round trip with the client answer; the provenance times ride the record.`, {});
|
|
19629
|
+
return mcpstateof();
|
|
19630
|
+
}
|
|
19631
|
+
const clientid = inputsample.clientid?.trim() ?? "";
|
|
19632
|
+
if (clientid === "") throw new Error("The sampling callback needs the paired client it addresses.");
|
|
19633
|
+
const client = (await memory.getclients()).find((entry) => entry.id === clientid);
|
|
19634
|
+
const requested = requestsampling({ clientid, ...client?.capabilities !== void 0 ? { capabilities: client.capabilities } : {}, prompt: inputsample.prompt ?? "", ...inputsample.system !== void 0 ? { system: inputsample.system } : {}, ...inputsample.pagecontent !== void 0 ? { pagecontent: inputsample.pagecontent } : {}, pagegrant: inputsample.pagegrant === true, ...inputsample.maxtokens !== void 0 ? { maxtokens: inputsample.maxtokens } : {}, now });
|
|
19635
|
+
if (requested.request === void 0) throw new Error(requested.reason ?? "The sampling callback was refused.");
|
|
19636
|
+
await memory.setsamplingrequests([requested.request, ...await memory.getsamplingrequests()]);
|
|
19637
|
+
await audit("protocol", `The user sent one sampling callback to the client ${clientid}: the prompt leaves the browser${requested.request.pagecontent !== void 0 ? " with the page content the user granted" : " with the page content stripped because the user granted none"}, and the exact payload rides the panel.`, {});
|
|
19638
|
+
return mcpstateof();
|
|
19639
|
+
}
|
|
19640
|
+
case "mcpcancelcall": {
|
|
19641
|
+
const inputcancel = message;
|
|
19642
|
+
const callid = inputcancel.callid?.trim() ?? "";
|
|
19643
|
+
if (callid === "") throw new Error("The cancellation needs the call id.");
|
|
19644
|
+
const now = Date.now();
|
|
19645
|
+
const contexts = await memory.getcallcontexts();
|
|
19646
|
+
const chunks = (await memory.getstreamchunks()).filter((chunk) => chunk.callid === callid);
|
|
19647
|
+
const partial = chunks.length > 0 ? { content: chunks.map((chunk) => chunk.content).join(""), iserror: false } : void 0;
|
|
19648
|
+
const aborted = canceltool({ contexts, callid, ...inputcancel.reason !== void 0 ? { reason: inputcancel.reason } : {}, ...partial !== void 0 ? { partial } : {}, now });
|
|
19649
|
+
if (aborted.context === void 0) throw new Error(aborted.reason ?? "The cancellation named no in flight call.");
|
|
19650
|
+
await memory.setcallcontexts(aborted.contexts);
|
|
19651
|
+
await audit("protocol", `The user cancelled the in flight ${aborted.context.tool} call ${callid}${partial !== void 0 ? " with its partial result preserved" : ""}; the cooperative flag stops the page executor and the workflow engine keeps its own pause machinery.`, {});
|
|
19652
|
+
return mcpstateof();
|
|
19653
|
+
}
|
|
19654
|
+
case "mcpmock": {
|
|
19655
|
+
const inputmock = message;
|
|
19656
|
+
const toolname = inputmock.tool?.trim() ?? "";
|
|
19657
|
+
if (toolname === "") throw new Error("The tool mock needs the namespaced tool it stands in for.");
|
|
19658
|
+
const now = Date.now();
|
|
19659
|
+
if (inputmock.remove === true) {
|
|
19660
|
+
await memory.removetoolmock(toolname);
|
|
19661
|
+
await audit("protocol", `The user removed the ${toolname} tool mock; the tool returns to the real gates.`, {});
|
|
19662
|
+
return mcpstateof();
|
|
19663
|
+
}
|
|
19664
|
+
if (resolvetool(buildtoolcatalog(), toolname) === void 0) throw new Error(`The catalog holds no unambiguous tool named ${toolname}.`);
|
|
19665
|
+
const mock = { tool: toolname, result: { content: inputmock.content ?? `The ${toolname} mock answered from the test context.`, iserror: false }, testcontext: true, createdat: now };
|
|
19666
|
+
const valid = mockusagevalid(mock);
|
|
19667
|
+
if (!valid.allowed) throw new Error(valid.reason ?? "The tool mock failed its validation.");
|
|
19668
|
+
await memory.settoolmock(mock);
|
|
19669
|
+
await audit("protocol", `The user registered the ${toolname} tool mock of a test context; mocks answer with canned results and never touch the browser.`, {});
|
|
19670
|
+
return mcpstateof();
|
|
19671
|
+
}
|
|
19672
|
+
case "mcpratelimit": {
|
|
19673
|
+
const inputlimit = message;
|
|
19674
|
+
const clientid = inputlimit.clientid?.trim() ?? "";
|
|
19675
|
+
if (clientid === "") throw new Error("The per client rate limit needs the client it counts.");
|
|
19676
|
+
const now = Date.now();
|
|
19677
|
+
const limits = (await memory.getcallratelimits()).filter((limit2) => limit2.clientid !== clientid);
|
|
19678
|
+
if (inputlimit.remove === true) {
|
|
19679
|
+
await memory.setcallratelimits(limits);
|
|
19680
|
+
await audit("protocol", `The user removed the rate limit of the client ${clientid}; the client stays unbounded because no silent default applies.`, {});
|
|
19681
|
+
return mcpstateof();
|
|
19682
|
+
}
|
|
19683
|
+
const limit = { clientid, windowms: inputlimit.windowms ?? 6e4, ...inputlimit.budget !== void 0 ? { budget: inputlimit.budget } : {}, windowstartedat: now, used: 0 };
|
|
19684
|
+
const valid = callratelimitvalid(limit);
|
|
19685
|
+
if (!valid.allowed) throw new Error(valid.reason ?? "The rate limit failed its validation.");
|
|
19686
|
+
await memory.setcallratelimits([limit, ...limits]);
|
|
19687
|
+
await audit("protocol", `The user limited the client ${clientid} to ${limit.budget !== void 0 ? `${limit.budget} call${limit.budget === 1 ? "" : "s"} per ${limit.windowms} millisecond window` : `a ${limit.windowms} millisecond window with no call budget`}; every value stays the user choice.`, {});
|
|
19688
|
+
return mcpstateof();
|
|
19689
|
+
}
|
|
19690
|
+
case "mcpdryrun": {
|
|
19691
|
+
const inputdry = message;
|
|
19692
|
+
const enabled = inputdry.enabled === true;
|
|
19693
|
+
await memory.setdryruntoggle(enabled);
|
|
19694
|
+
await audit("protocol", `The user ${enabled ? "armed the tool dry run of the next call: it evaluates arguments and consent with no side effects" : "disarmed the tool dry run; the next call executes behind the gates"}.`, {});
|
|
19695
|
+
return mcpstateof();
|
|
19696
|
+
}
|
|
18693
19697
|
case "runtobreakpoint": {
|
|
18694
19698
|
const inputdebug = message;
|
|
18695
19699
|
const session = await memory.getsession();
|
|
@@ -18950,6 +19954,298 @@ async function handlerequest(message, sender) {
|
|
|
18950
19954
|
await audit("workflow", `The user removed the per site override ${removed.pattern} of the workflow ${removed.workflowid}.`, {});
|
|
18951
19955
|
return { removed: removed.id };
|
|
18952
19956
|
}
|
|
19957
|
+
case "llmstate": {
|
|
19958
|
+
return llmstateof();
|
|
19959
|
+
}
|
|
19960
|
+
case "llmproviders": {
|
|
19961
|
+
const inputprovider = message;
|
|
19962
|
+
const id = inputprovider.id?.trim() ?? "";
|
|
19963
|
+
const providers = await memory.getproviders();
|
|
19964
|
+
if (inputprovider.remove === true) {
|
|
19965
|
+
if (id === "") throw new Error("The provider removal needs the provider id.");
|
|
19966
|
+
await memory.setproviders(providers.filter((candidate) => candidate.id !== id));
|
|
19967
|
+
await audit("model", `The user removed the provider config ${id}; the stored key reference and its secret stay untouched in the credential store.`, {});
|
|
19968
|
+
return llmstateof();
|
|
19969
|
+
}
|
|
19970
|
+
const authrefname = inputprovider.authrefname?.trim() ?? "";
|
|
19971
|
+
const ref = authrefname !== "" ? (await memory.getapikeys()).find((entry) => entry.name === authrefname) : void 0;
|
|
19972
|
+
if (authrefname !== "" && ref === void 0) throw new Error(`No stored api key reference matches ${authrefname}; set the key from the review panel first.`);
|
|
19973
|
+
const style = inputprovider.style === "chatcompletions" || inputprovider.style === "responses" || inputprovider.style === "messages" || inputprovider.style === "gemini" ? inputprovider.style : void 0;
|
|
19974
|
+
const models = Array.isArray(inputprovider.models) ? inputprovider.models.map((model) => String(model).trim()).filter((model) => model !== "") : [];
|
|
19975
|
+
const storedprovider = providers.find((candidate) => candidate.id === id);
|
|
19976
|
+
const config = {
|
|
19977
|
+
id: id !== "" ? id : randomid(),
|
|
19978
|
+
name: inputprovider.name?.trim() ?? "",
|
|
19979
|
+
endpoint: inputprovider.endpoint?.trim() ?? "",
|
|
19980
|
+
...style !== void 0 ? { style } : { style: "chatcompletions" },
|
|
19981
|
+
models,
|
|
19982
|
+
...inputprovider.headers !== void 0 ? { headers: inputprovider.headers } : {},
|
|
19983
|
+
...ref !== void 0 ? { authref: { name: ref.name, origins: ref.origins, header: ref.header, storageid: ref.storageid, configuredat: ref.createdat } } : {},
|
|
19984
|
+
...inputprovider.costpermilliontokens !== void 0 && Number.isFinite(inputprovider.costpermilliontokens) ? { costpermilliontokens: inputprovider.costpermilliontokens } : {},
|
|
19985
|
+
...inputprovider.currency !== void 0 && inputprovider.currency.trim() !== "" ? { currency: inputprovider.currency.trim() } : {},
|
|
19986
|
+
status: storedprovider?.status ?? "available",
|
|
19987
|
+
...storedprovider?.lastcheckedat !== void 0 ? { lastcheckedat: storedprovider.lastcheckedat } : {},
|
|
19988
|
+
createdat: storedprovider?.createdat ?? Date.now()
|
|
19989
|
+
};
|
|
19990
|
+
const gate = providervalid(config);
|
|
19991
|
+
if (!gate.allowed) throw new Error(gate.reason ?? "The provider config failed its validation.");
|
|
19992
|
+
await memory.setproviders([config, ...providers.filter((candidate) => candidate.id !== config.id)]);
|
|
19993
|
+
await audit("model", `The user saved the provider config ${config.name} for the ${config.endpoint} endpoint with the ${config.style} protocol shape, ${config.models.length} model${config.models.length === 1 ? "" : "s"}${config.authref !== void 0 ? ` and the stored key reference ${config.authref.name} (the key material never enters the config)` : " and no key reference"}; every value stays the user choice.`, {});
|
|
19994
|
+
return llmstateof();
|
|
19995
|
+
}
|
|
19996
|
+
case "llmtestprovider": {
|
|
19997
|
+
const inputtest = message;
|
|
19998
|
+
const id = inputtest.id?.trim() ?? "";
|
|
19999
|
+
const provider = (await memory.getproviders()).find((candidate) => candidate.id === id);
|
|
20000
|
+
if (!provider) throw new Error(`No provider config matches ${id}.`);
|
|
20001
|
+
const now = Date.now();
|
|
20002
|
+
const providers = await memory.getproviders();
|
|
20003
|
+
try {
|
|
20004
|
+
const key = await providerkey(provider);
|
|
20005
|
+
const outcome = await callmodel({ provider, model: provider.models[0] ?? "", messages: [{ role: "user", content: "Answer with the single word ready." }], ...key !== void 0 ? { apikey: key } : {}, transport: llmtransport });
|
|
20006
|
+
await memory.setproviders(markprovider({ providers, providerid: provider.id, available: true, now }));
|
|
20007
|
+
await audit("model", `The test call of the provider ${provider.name} reached the ${provider.endpoint} endpoint with the ${provider.models[0] ?? ""} model and answered ${outcome.text.length} characters; the provider stays available.`, {});
|
|
20008
|
+
} catch (error) {
|
|
20009
|
+
await memory.setproviders(markprovider({ providers, providerid: provider.id, available: false, now }));
|
|
20010
|
+
await audit("model", `The test call of the provider ${provider.name} failed: ${error instanceof Error ? error.message : String(error)} The provider stays marked unavailable until its next success.`, {});
|
|
20011
|
+
throw error;
|
|
20012
|
+
}
|
|
20013
|
+
return llmstateof();
|
|
20014
|
+
}
|
|
20015
|
+
case "llmlocal": {
|
|
20016
|
+
const inputlocal = message;
|
|
20017
|
+
const stored = await memory.getlocalmodel();
|
|
20018
|
+
if (inputlocal.check === true) {
|
|
20019
|
+
const local = stored ?? { endpoint: "", model: "", style: "chatcompletions" };
|
|
20020
|
+
if (local.endpoint.trim() === "") throw new Error("The local model needs the user configured endpoint url before the health check runs.");
|
|
20021
|
+
const now = Date.now();
|
|
20022
|
+
try {
|
|
20023
|
+
const key = local.authref !== void 0 ? await memory.getsecret(local.authref.storageid) : void 0;
|
|
20024
|
+
const outcome = await calllocal({ local, messages: [{ role: "user", content: "Answer with the single word ready." }], ...key !== void 0 ? { apikey: key } : {}, transport: llmtransport });
|
|
20025
|
+
await memory.setlocalmodel({ ...local, health: { checkedat: now, ok: true, ...outcome.text !== "" ? { detail: `The endpoint answered ${outcome.text.length} characters.` } : {} } });
|
|
20026
|
+
await memory.addusagerecord({ id: randomid(), providerid: "local", endpoint: local.endpoint, model: local.model, prompttokens: outcome.usage?.prompttokens ?? 0, completiontokens: outcome.usage?.completiontokens ?? 0, totaltokens: outcome.usage?.totaltokens ?? 0, cost: 0, local: true, at: now });
|
|
20027
|
+
await audit("model", `The local model health check reached the ${local.endpoint} endpoint with the ${local.model} model and the endpoint stays healthy; the call never left the machine.`, {});
|
|
20028
|
+
} catch (error) {
|
|
20029
|
+
await memory.setlocalmodel({ ...local, health: { checkedat: now, ok: false, detail: error instanceof Error ? error.message : String(error) } });
|
|
20030
|
+
await audit("model", `The local model health check failed on the ${local.endpoint} endpoint: ${error instanceof Error ? error.message : String(error)}`, {});
|
|
20031
|
+
}
|
|
20032
|
+
return llmstateof();
|
|
20033
|
+
}
|
|
20034
|
+
const authrefname = inputlocal.authrefname?.trim() ?? "";
|
|
20035
|
+
const ref = authrefname !== "" ? (await memory.getapikeys()).find((entry) => entry.name === authrefname) : void 0;
|
|
20036
|
+
if (authrefname !== "" && ref === void 0) throw new Error(`No stored api key reference matches ${authrefname}; set the key from the review panel first.`);
|
|
20037
|
+
const style = inputlocal.style === "chatcompletions" || inputlocal.style === "responses" || inputlocal.style === "messages" || inputlocal.style === "gemini" ? inputlocal.style : stored?.style ?? "chatcompletions";
|
|
20038
|
+
const config = { endpoint: inputlocal.endpoint?.trim() ?? stored?.endpoint ?? "", model: inputlocal.model?.trim() ?? stored?.model ?? "", style, ...ref !== void 0 ? { authref: { name: ref.name, origins: ref.origins, header: ref.header, storageid: ref.storageid, configuredat: ref.createdat } } : {}, ...stored?.health !== void 0 ? { health: stored.health } : {} };
|
|
20039
|
+
if (config.endpoint.trim() === "") throw new Error("The local model needs the user configured endpoint url.");
|
|
20040
|
+
if (config.model.trim() === "") throw new Error("The local model needs the user configured model name.");
|
|
20041
|
+
if (!islocalorigin(config.endpoint)) throw new Error("The local model endpoint must stay a local machine address so no call leaves the machine.");
|
|
20042
|
+
await memory.setlocalmodel(config);
|
|
20043
|
+
await audit("model", `The user saved the local model endpoint ${config.endpoint} with the ${config.model} model and the ${config.style} protocol shape${config.authref !== void 0 ? ` and the stored key reference ${config.authref.name}` : " and no key reference"}.`, {});
|
|
20044
|
+
return llmstateof();
|
|
20045
|
+
}
|
|
20046
|
+
case "llmroutes": {
|
|
20047
|
+
const inputroute = message;
|
|
20048
|
+
const kind = inputroute.taskkind?.trim() ?? "";
|
|
20049
|
+
if (kind === "") throw new Error("The model route needs its task kind.");
|
|
20050
|
+
const routes = await memory.getmodelroutes();
|
|
20051
|
+
if (inputroute.remove === true) {
|
|
20052
|
+
await memory.setmodelroutes(routes.filter((candidate) => !(candidate.kind === kind)));
|
|
20053
|
+
await audit("model", `The user removed every route of the task kind ${kind}; the routing table holds no default route.`, {});
|
|
20054
|
+
return llmstateof();
|
|
20055
|
+
}
|
|
20056
|
+
const existing = routes.find((candidate) => candidate.kind === kind);
|
|
20057
|
+
const now = Date.now();
|
|
20058
|
+
const route = { id: existing?.id ?? randomid(), kind, providerid: inputroute.providerid?.trim() ?? "", model: inputroute.model?.trim() ?? "", ...inputroute.fallbackproviderid !== void 0 && inputroute.fallbackproviderid.trim() !== "" ? { fallbackproviderid: inputroute.fallbackproviderid.trim() } : {}, ...inputroute.fallbackmodel !== void 0 && inputroute.fallbackmodel.trim() !== "" ? { fallbackmodel: inputroute.fallbackmodel.trim() } : {}, revision: (existing?.revision ?? 0) + 1, updatedat: now };
|
|
20059
|
+
const gate = routevalid(route);
|
|
20060
|
+
if (!gate.allowed) throw new Error(gate.reason ?? "The model route failed its validation.");
|
|
20061
|
+
await memory.setmodelroutes([route, ...routes.filter((candidate) => candidate.kind !== kind)]);
|
|
20062
|
+
await memory.addmodelrouterevision(route);
|
|
20063
|
+
await audit("model", `The user routed the task kind ${kind} to the ${route.providerid} provider with the ${route.model} model${route.fallbackproviderid !== void 0 ? ` and the ${route.fallbackproviderid} fallback with the ${route.fallbackmodel} model` : " and no fallback"} at revision ${route.revision}.`, {});
|
|
20064
|
+
return llmstateof();
|
|
20065
|
+
}
|
|
20066
|
+
case "llmcommand": {
|
|
20067
|
+
const inputcommand = message;
|
|
20068
|
+
const text2 = inputcommand.text?.trim() ?? "";
|
|
20069
|
+
if (text2 === "") throw new Error("The command parse needs the natural language text.");
|
|
20070
|
+
const providers = await memory.getproviders();
|
|
20071
|
+
const routes = await memory.getmodelroutes();
|
|
20072
|
+
const resolved = resolveroute({ routes, providers, kind: "parsecommand" });
|
|
20073
|
+
if (resolved.provider === void 0 || resolved.model === void 0) {
|
|
20074
|
+
const local = await memory.getlocalmodel();
|
|
20075
|
+
if (local === void 0 || local.endpoint.trim() === "") {
|
|
20076
|
+
const fallback = classifyintent(text2);
|
|
20077
|
+
const parse = { text: text2, intent: fallback.intent, entities: [], confidence: fallback.confidence, parsedat: Date.now() };
|
|
20078
|
+
await memory.setcommandparse(parse);
|
|
20079
|
+
await audit("model", `The deterministic classifier mapped the command to the ${fallback.intent} intent at confidence ${fallback.confidence} because no model route and no local endpoint serve the parsecommand task kind.`, {});
|
|
20080
|
+
return llmstateof();
|
|
20081
|
+
}
|
|
20082
|
+
const key2 = local.authref !== void 0 ? await memory.getsecret(local.authref.storageid) : void 0;
|
|
20083
|
+
const now = Date.now();
|
|
20084
|
+
const outcome = await calllocal({ local, messages: [{ role: "system", content: "Parse the user command into json with the fields intent (one of navigate, extract, fill, monitor, automate, ask), entities (an array of { name, value } objects) and confidence (a number between 0 and 1). Answer with the json object only." }, { role: "user", content: text2 }], ...key2 !== void 0 ? { apikey: key2 } : {}, transport: llmtransport });
|
|
20085
|
+
const replay2 = await parsecommand({ provider: { id: "local", name: "The local model endpoint", endpoint: local.endpoint, style: local.style, models: [local.model], status: "available", createdat: now }, model: local.model, text: text2, transport: cannedtransport(outcome.text) });
|
|
20086
|
+
if (replay2.parse === void 0) {
|
|
20087
|
+
if (replay2.output !== void 0) await recordguardnotice("parsecommand", replay2.output);
|
|
20088
|
+
throw new Error(replay2.reason ?? "The command parse failed its guard.");
|
|
20089
|
+
}
|
|
20090
|
+
await memory.setcommandparse(replay2.parse);
|
|
20091
|
+
await memory.addusagerecord({ id: randomid(), providerid: "local", endpoint: local.endpoint, model: local.model, prompttokens: outcome.usage?.prompttokens ?? 0, completiontokens: outcome.usage?.completiontokens ?? 0, totaltokens: outcome.usage?.totaltokens ?? 0, cost: 0, local: true, at: now });
|
|
20092
|
+
await audit("model", `The local model parsed the command into the ${replay2.parse.intent} intent at confidence ${replay2.parse.confidence} with ${replay2.parse.entities.length} entit${replay2.parse.entities.length === 1 ? "y" : "ies"}; the call never left the machine.`, {});
|
|
20093
|
+
return llmstateof();
|
|
20094
|
+
}
|
|
20095
|
+
const raw = await callroutedmodel({ kind: "parsecommand", messages: [{ role: "system", content: "Parse the user command into json with the fields intent (one of navigate, extract, fill, monitor, automate, ask), entities (an array of { name, value } objects) and confidence (a number between 0 and 1). Answer with the json object only." }, { role: "user", content: text2 }] });
|
|
20096
|
+
const key = await providerkey(raw.provider);
|
|
20097
|
+
const replay = await parsecommand({ provider: raw.provider, model: raw.model, text: text2, ...key !== void 0 ? { apikey: key } : {}, transport: cannedtransport(raw.text) });
|
|
20098
|
+
if (replay.parse === void 0) {
|
|
20099
|
+
if (replay.output !== void 0) await recordguardnotice("parsecommand", replay.output);
|
|
20100
|
+
throw new Error(replay.reason ?? "The command parse failed its guard.");
|
|
20101
|
+
}
|
|
20102
|
+
await memory.setcommandparse(replay.parse);
|
|
20103
|
+
await audit("model", `The routed model parsed the command into the ${replay.parse.intent} intent at confidence ${replay.parse.confidence} with ${replay.parse.entities.length} entit${replay.parse.entities.length === 1 ? "y" : "ies"}.`, {});
|
|
20104
|
+
return llmstateof();
|
|
20105
|
+
}
|
|
20106
|
+
case "llmdraftplan": {
|
|
20107
|
+
const inputdraft = message;
|
|
20108
|
+
const goal = inputdraft.goal?.trim() ?? "";
|
|
20109
|
+
if (goal === "") throw new Error("The plan draft needs the goal.");
|
|
20110
|
+
const session = await memory.getsession();
|
|
20111
|
+
const origin = session?.origin ?? "";
|
|
20112
|
+
const raw = await callroutedmodel({ kind: "draftplan", messages: [{ role: "system", content: `Draft a browser agent plan as json with the fields goal (string), steps (an array of { kind, target, value, summary } objects using browser action kinds) and openquestions (an array of strings for what stays unclear). Answer with the json object only.` }, { role: "user", content: goal }] });
|
|
20113
|
+
const key = await providerkey(raw.provider);
|
|
20114
|
+
const replay = await draftplan({ provider: raw.provider, model: raw.model, goal, ...origin !== "" ? { origin } : {}, ...key !== void 0 ? { apikey: key } : {}, transport: cannedtransport(raw.text) });
|
|
20115
|
+
if (replay.draft === void 0) {
|
|
20116
|
+
if (replay.output !== void 0) await recordguardnotice("draftplan", replay.output);
|
|
20117
|
+
throw new Error(replay.reason ?? "The plan draft failed its guard.");
|
|
20118
|
+
}
|
|
20119
|
+
await memory.addplandraft(replay.draft);
|
|
20120
|
+
await audit("model", `The ${raw.provider.name} model drafted a ${replay.draft.steps.length} step plan for the goal ${goal}${replay.draft.lintfindings.length > 0 ? ` with ${replay.draft.lintfindings.length} grammar finding${replay.draft.lintfindings.length === 1 ? "" : "s"} the review must resolve` : " with a clean grammar check"}${replay.draft.openquestions.length > 0 ? ` and ${replay.draft.openquestions.length} open question${replay.draft.openquestions.length === 1 ? "" : "s"}` : ""}; the draft never executes until the human review approves it.`, {});
|
|
20121
|
+
return llmstateof();
|
|
20122
|
+
}
|
|
20123
|
+
case "llmdraftdecision": {
|
|
20124
|
+
const inputdecision = message;
|
|
20125
|
+
const drafts = await memory.getplandrafts();
|
|
20126
|
+
const draft = drafts.find((candidate) => candidate.id === (inputdecision.draftid ?? ""));
|
|
20127
|
+
if (!draft) throw new Error("No model drafted plan matches the decision.");
|
|
20128
|
+
if (inputdecision.approve !== true) {
|
|
20129
|
+
await memory.setplandrafts(drafts.map((candidate) => candidate.id === draft.id ? { ...candidate, state: "rejected" } : candidate));
|
|
20130
|
+
await audit("model", `The user rejected the model drafted plan ${draft.id} of the goal ${draft.goal}; the draft stays for the audit trail.`, {});
|
|
20131
|
+
return llmstateof();
|
|
20132
|
+
}
|
|
20133
|
+
const gate = plandraftreviewgate(draft);
|
|
20134
|
+
if (!gate.allowed) throw new Error(gate.reason ?? "The model drafted plan failed its review gate.");
|
|
20135
|
+
if (draft.lintfindings.length > 0) throw new Error(`The model drafted plan carries grammar violations the review must resolve first: ${draft.lintfindings.join(" ")}`);
|
|
20136
|
+
const steps = Array.isArray(inputdecision.steps) && inputdecision.steps.length > 0 ? inputdecision.steps.filter((step) => typeof step?.kind === "string" && step.kind.trim() !== "" && typeof step?.summary === "string" && step.summary.trim() !== "").map((step, index) => ({ id: typeof step.id === "string" && step.id.trim() !== "" ? step.id.trim() : `step${index + 1}`, kind: step.kind, ...typeof step.target === "string" && step.target.trim() !== "" ? { target: step.target } : {}, ...typeof step.value === "string" && step.value.trim() !== "" ? { value: step.value } : {}, summary: step.summary })) : draft.steps.map((step) => ({ id: step.id, kind: step.kind, ...step.target !== void 0 ? { target: step.target } : {}, ...step.value !== void 0 ? { value: step.value } : {}, summary: step.summary }));
|
|
20137
|
+
if (steps.length === 0) throw new Error("The model drafted plan carries no step to approve.");
|
|
20138
|
+
for (const step of steps) {
|
|
20139
|
+
try {
|
|
20140
|
+
actionrisk(step.kind);
|
|
20141
|
+
} catch {
|
|
20142
|
+
throw new Error(`The drafted step ${step.id} carries the unsupported kind ${step.kind}.`);
|
|
20143
|
+
}
|
|
20144
|
+
}
|
|
20145
|
+
const session = await memory.getsession();
|
|
20146
|
+
const plan = draftplanof(draft, steps, session?.origin ?? "");
|
|
20147
|
+
await memory.setplan(plan);
|
|
20148
|
+
await memory.setprogress(resetforplan(await memory.getprogress(), plan, Date.now()));
|
|
20149
|
+
await memory.setplandrafts(drafts.map((candidate) => candidate.id === draft.id ? { ...candidate, state: "approved" } : candidate));
|
|
20150
|
+
await audit("model", `The user approved the model drafted plan ${draft.id} of the goal ${draft.goal}; the ${plan.steps.length} drafted steps became a pending plan that still passes the same plan review every local plan passes before anything executes.`, { planid: plan.id, ...session ? { sessionid: session.id } : {} });
|
|
20151
|
+
return llmstateof();
|
|
20152
|
+
}
|
|
20153
|
+
case "llmreplan": {
|
|
20154
|
+
const inputreplan = message;
|
|
20155
|
+
const drafts = await memory.getplandrafts();
|
|
20156
|
+
const draft = inputreplan.draftid !== void 0 && inputreplan.draftid.trim() !== "" ? drafts.find((candidate) => candidate.id === inputreplan.draftid) : drafts.find((candidate) => candidate.state === "approved");
|
|
20157
|
+
if (!draft) throw new Error("No approved model drafted plan exists to replan.");
|
|
20158
|
+
const failedstepids = Array.isArray(inputreplan.failedstepids) ? inputreplan.failedstepids.filter((id) => typeof id === "string" && id.trim() !== "") : [];
|
|
20159
|
+
const reason = inputreplan.reason?.trim() ?? "";
|
|
20160
|
+
if (reason === "") throw new Error("The replan needs the failure reason.");
|
|
20161
|
+
const notes = await memory.getreflectnotes();
|
|
20162
|
+
const raw = await callroutedmodel({ kind: "replan", messages: [{ role: "system", content: `The plan ${draft.goal} failed with the reason: ${reason}. Draft the revised tail steps of the plan as json with the field steps (an array of { kind, target, value, summary } objects using browser action kinds). Answer with the json object only.` }, { role: "user", content: draft.goal }] });
|
|
20163
|
+
const completedstepids = draft.steps.filter((step) => !failedstepids.includes(step.id)).map((step) => step.id);
|
|
20164
|
+
const key = await providerkey(raw.provider);
|
|
20165
|
+
const replay = await replannonfail({ provider: raw.provider, model: raw.model, draft, completedstepids, failedstepids, reason, ...reflectionsummary(notes) !== "" ? { lessons: [reflectionsummary(notes)] } : {}, ...key !== void 0 ? { apikey: key } : {}, transport: cannedtransport(raw.text) });
|
|
20166
|
+
if (replay.replan === void 0) {
|
|
20167
|
+
if (replay.output !== void 0) await recordguardnotice("replan", replay.output);
|
|
20168
|
+
throw new Error(replay.reason ?? "The replan failed its guard.");
|
|
20169
|
+
}
|
|
20170
|
+
await memory.addreplan(replay.replan);
|
|
20171
|
+
await audit("model", `The ${raw.provider.name} model replanned the failed plan ${draft.id}: ${completedstepids.length} completed step${completedstepids.length === 1 ? "" : "s"} stay, the ${failedstepids.length} failed step${failedstepids.length === 1 ? "" : "s"} fall away and the ${replay.replan.tail.length} revised step${replay.replan.tail.length === 1 ? "" : "s"} carry the fresh review marker.`, {});
|
|
20172
|
+
return llmstateof();
|
|
20173
|
+
}
|
|
20174
|
+
case "llmreplandecision": {
|
|
20175
|
+
const inputdecision = message;
|
|
20176
|
+
const replans = await memory.getreplans();
|
|
20177
|
+
const replan = replans.find((candidate) => candidate.id === (inputdecision.replanid ?? ""));
|
|
20178
|
+
if (!replan) throw new Error("No replan record matches the decision.");
|
|
20179
|
+
if (inputdecision.approve !== true) {
|
|
20180
|
+
await memory.setreplans(replans.map((candidate) => candidate.id === replan.id ? { ...candidate, state: "rejected" } : candidate));
|
|
20181
|
+
await audit("model", `The user rejected the replan ${replan.id}; the record stays for the audit trail.`, {});
|
|
20182
|
+
return llmstateof();
|
|
20183
|
+
}
|
|
20184
|
+
const gate = replanreviewgate(replan);
|
|
20185
|
+
if (!gate.allowed) throw new Error(gate.reason ?? "The replan failed its fresh review gate.");
|
|
20186
|
+
const draft = (await memory.getplandrafts()).find((candidate) => candidate.id === replan.draftid);
|
|
20187
|
+
if (!draft) throw new Error("The replan names no stored draft.");
|
|
20188
|
+
const completed = draft.steps.filter((step) => replan.completedstepids.includes(step.id));
|
|
20189
|
+
const steps = [...completed.map((step) => ({ id: step.id, kind: step.kind, ...step.target !== void 0 ? { target: step.target } : {}, ...step.value !== void 0 ? { value: step.value } : {}, summary: step.summary })), ...replan.tail.map((step) => ({ id: step.id, kind: step.kind, ...step.target !== void 0 ? { target: step.target } : {}, ...step.value !== void 0 ? { value: step.value } : {}, summary: step.summary }))];
|
|
20190
|
+
const session = await memory.getsession();
|
|
20191
|
+
const plan = draftplanof(draft, steps, session?.origin ?? "");
|
|
20192
|
+
await memory.setplan(plan);
|
|
20193
|
+
await memory.setprogress(resetforplan(await memory.getprogress(), plan, Date.now()));
|
|
20194
|
+
await memory.setreplans(replans.map((candidate) => candidate.id === replan.id ? { ...candidate, state: "approved" } : candidate));
|
|
20195
|
+
await audit("model", `The user approved the fresh review of the replan ${replan.id}: ${completed.length} completed step${completed.length === 1 ? "" : "s"} stay and the ${replan.tail.length} revised step${replan.tail.length === 1 ? "" : "s"} became the changed tail of a pending plan that still passes the same plan review.`, { planid: plan.id, ...session ? { sessionid: session.id } : {} });
|
|
20196
|
+
return llmstateof();
|
|
20197
|
+
}
|
|
20198
|
+
case "llmreflect": {
|
|
20199
|
+
const inputreflect = message;
|
|
20200
|
+
const outcomes = await memory.getoutcomes();
|
|
20201
|
+
const outcome = inputreflect.stepid !== void 0 && inputreflect.stepid.trim() !== "" ? outcomes.find((entry) => entry.stepid === inputreflect.stepid) : outcomes[0];
|
|
20202
|
+
if (!outcome) throw new Error("No executed step outcome exists to reflect on.");
|
|
20203
|
+
const notes = await memory.getreflectnotes();
|
|
20204
|
+
const summary = reflectionsummary(notes.filter((note) => note.stepid !== outcome.stepid));
|
|
20205
|
+
const raw = await callroutedmodel({ kind: "reflect", stepid: outcome.stepid, messages: [{ role: "system", content: `Reflect on the executed step ${outcome.stepid} with the outcome: ${outcome.summary}. Answer as json with the fields outcome (string), lesson (string) and advice (string) for the next step. Answer with the json object only.` }, { role: "user", content: outcome.summary }] });
|
|
20206
|
+
const key = await providerkey(raw.provider);
|
|
20207
|
+
const replay = await reflectstep({ provider: raw.provider, model: raw.model, runid: inputreflect.runid?.trim() ?? "adhoc", stepid: outcome.stepid, outcome: outcome.summary, ...summary !== "" ? { lessons: [summary] } : {}, ...key !== void 0 ? { apikey: key } : {}, transport: cannedtransport(raw.text) });
|
|
20208
|
+
if (replay.note === void 0) {
|
|
20209
|
+
if (replay.output !== void 0) await recordguardnotice("reflect", replay.output);
|
|
20210
|
+
throw new Error(replay.reason ?? "The reflection failed its guard.");
|
|
20211
|
+
}
|
|
20212
|
+
await memory.addreflectnote(replay.note);
|
|
20213
|
+
await audit("model", `The ${raw.provider.name} model reflected on the step ${outcome.stepid}: the lesson learned rides the next prompt together with the running lessons of the earlier steps.`, {});
|
|
20214
|
+
return llmstateof();
|
|
20215
|
+
}
|
|
20216
|
+
case "llmbudget": {
|
|
20217
|
+
const inputbudget = message;
|
|
20218
|
+
if (inputbudget.remove === true) {
|
|
20219
|
+
await memory.setcostbudget({ maxtokens: Number.MAX_SAFE_INTEGER, configuredat: Date.now() });
|
|
20220
|
+
await audit("model", "The user removed the cost budget; the runs stay unbounded because no ceiling applies.", {});
|
|
20221
|
+
return llmstateof();
|
|
20222
|
+
}
|
|
20223
|
+
const budget = { ...inputbudget.runid !== void 0 && inputbudget.runid.trim() !== "" ? { runid: inputbudget.runid.trim() } : {}, ...inputbudget.maxtokens !== void 0 && Number.isFinite(inputbudget.maxtokens) ? { maxtokens: inputbudget.maxtokens } : {}, ...inputbudget.maxcost !== void 0 && Number.isFinite(inputbudget.maxcost) ? { maxcost: inputbudget.maxcost } : {}, ...inputbudget.currency !== void 0 && inputbudget.currency.trim() !== "" ? { currency: inputbudget.currency.trim() } : {}, configuredat: Date.now() };
|
|
20224
|
+
const gate = costbudgetvalid(budget);
|
|
20225
|
+
if (!gate.allowed) throw new Error(gate.reason ?? "The cost budget failed its validation.");
|
|
20226
|
+
await memory.setcostbudget(budget);
|
|
20227
|
+
await audit("model", `The user configured the cost budget${budget.runid !== void 0 ? ` of the run ${budget.runid}` : ""}${budget.maxtokens !== void 0 ? ` with a ${budget.maxtokens} token ceiling` : ""}${budget.maxcost !== void 0 ? `${budget.maxtokens !== void 0 ? " and" : " with"} a ${budget.maxcost} ${budget.currency ?? ""} cost ceiling` : ""}; a reached ceiling halts the run and asks the user.`, {});
|
|
20228
|
+
return llmstateof();
|
|
20229
|
+
}
|
|
20230
|
+
case "llmtemplate": {
|
|
20231
|
+
const inputtemplate = message;
|
|
20232
|
+
if (inputtemplate.remove === true) {
|
|
20233
|
+
const name2 = inputtemplate.name?.trim() ?? "";
|
|
20234
|
+
if (name2 === "") throw new Error("The template removal needs the template name.");
|
|
20235
|
+
await memory.setprompttemplates(removetemplate(await memory.getprompttemplates(), name2));
|
|
20236
|
+
await audit("model", `The user removed every version of the prompt template ${name2}; the change history leaves with the name.`, {});
|
|
20237
|
+
return llmstateof();
|
|
20238
|
+
}
|
|
20239
|
+
const name = inputtemplate.name?.trim() ?? "";
|
|
20240
|
+
const body = inputtemplate.body ?? "";
|
|
20241
|
+
if (name === "") throw new Error("The prompt template needs its name.");
|
|
20242
|
+
if (body.trim() === "") throw new Error("The prompt template needs its body.");
|
|
20243
|
+
const stored = savetemplate({ templates: await memory.getprompttemplates(), name, body, ...inputtemplate.notes !== void 0 && inputtemplate.notes.trim() !== "" ? { notes: inputtemplate.notes } : {}, now: Date.now() });
|
|
20244
|
+
await memory.setprompttemplates(stored);
|
|
20245
|
+
const latest = latesttemplate(stored, name);
|
|
20246
|
+
await audit("model", `The user saved the prompt template ${name} at version ${latest?.version ?? 1} with ${latest?.variables.length ?? 0} variable${(latest?.variables.length ?? 0) === 1 ? "" : "s"}${inputtemplate.notes !== void 0 ? ` and the change notes` : ""}; every earlier version stays stored.`, {});
|
|
20247
|
+
return llmstateof();
|
|
20248
|
+
}
|
|
18953
20249
|
default:
|
|
18954
20250
|
throw new Error("Unknown Devthink request.");
|
|
18955
20251
|
}
|
|
@@ -19056,6 +20352,82 @@ async function restorebackgroundruns() {
|
|
|
19056
20352
|
async function mcpconfigof() {
|
|
19057
20353
|
return await memory.getmcpconfig() ?? defaultmcpconfig();
|
|
19058
20354
|
}
|
|
20355
|
+
var llmtaskkinds = ["parsecommand", "classifyintent", "draftplan", "replan", "reflect", "summarize"];
|
|
20356
|
+
var llmtransport = async (url, init) => {
|
|
20357
|
+
const response = await fetch(url, { method: init.method, headers: init.headers, ...init.body !== void 0 ? { body: init.body } : {}, ...init.mode !== void 0 ? { mode: init.mode } : {}, redirect: init.redirect });
|
|
20358
|
+
return { status: response.status, headers: Object.fromEntries(response.headers.entries()), body: await response.text(), ...response.redirected ? { redirected: true } : {} };
|
|
20359
|
+
};
|
|
20360
|
+
function cannedtransport(text2) {
|
|
20361
|
+
return async () => ({ status: 200, headers: { "content-type": "application/json" }, body: JSON.stringify({ choices: [{ message: { role: "assistant", content: text2 } }] }) });
|
|
20362
|
+
}
|
|
20363
|
+
async function providerkey(provider) {
|
|
20364
|
+
if (provider.authref === void 0) return void 0;
|
|
20365
|
+
const secret = await memory.getsecret(provider.authref.storageid);
|
|
20366
|
+
if (secret === void 0) throw new Error(`The provider ${provider.name} references the stored key ${provider.authref.name} and its secret is missing; set it from the review panel first.`);
|
|
20367
|
+
return secret;
|
|
20368
|
+
}
|
|
20369
|
+
async function callroutedmodel(input) {
|
|
20370
|
+
const providers = await memory.getproviders();
|
|
20371
|
+
const routes = await memory.getmodelroutes();
|
|
20372
|
+
const primary = resolveroute({ routes, providers, kind: input.kind });
|
|
20373
|
+
const pair = primary.provider !== void 0 && primary.model !== void 0 ? primary : fallbackroute({ routes, providers, kind: input.kind });
|
|
20374
|
+
if (pair.provider === void 0 || pair.model === void 0) throw new Error(pair.reason ?? `No model route configures the task kind ${input.kind}; the user picks the provider and model pair.`);
|
|
20375
|
+
const budget = await memory.getcostbudget();
|
|
20376
|
+
if (budget !== void 0) {
|
|
20377
|
+
const verdict = budgetcheck({ budget, totals: usagetotals(await memory.getusagerecords()) });
|
|
20378
|
+
if (!verdict.allowed) {
|
|
20379
|
+
await audit("model", `The cost budget halted the ${input.kind} model call before it left: ${verdict.reason ?? "the ceiling is reached"} The user answers before anything else runs.`, {});
|
|
20380
|
+
throw new Error(verdict.reason ?? "The cost budget halted the run.");
|
|
20381
|
+
}
|
|
20382
|
+
}
|
|
20383
|
+
const attempt = async (provider, model) => {
|
|
20384
|
+
const egress = provideregressgrade({ provider, local: islocalorigin(provider.endpoint) });
|
|
20385
|
+
if (!egress.allowed) throw new Error(egress.reason ?? "The provider config failed its gate.");
|
|
20386
|
+
const key = await providerkey(provider);
|
|
20387
|
+
const at = Date.now();
|
|
20388
|
+
const outcome = await callmodel({ provider, model, messages: input.messages, ...key !== void 0 ? { apikey: key } : {}, transport: llmtransport });
|
|
20389
|
+
const usage = outcome.usage ?? { prompttokens: 0, completiontokens: 0, totaltokens: 0 };
|
|
20390
|
+
const cost = provider.costpermilliontokens !== void 0 && Number.isFinite(provider.costpermilliontokens) ? (usage.prompttokens + usage.completiontokens) / 1e6 * provider.costpermilliontokens : 0;
|
|
20391
|
+
await memory.addusagerecord({ id: randomid(), ...input.stepid !== void 0 ? { stepid: input.stepid } : {}, providerid: provider.id, endpoint: provider.endpoint, model, prompttokens: usage.prompttokens, completiontokens: usage.completiontokens, totaltokens: usage.totaltokens, cost, ...islocalorigin(provider.endpoint) ? { local: true } : {}, at });
|
|
20392
|
+
await audit("model", `The ${input.kind} model call reached the ${provider.endpoint} endpoint with the ${model} model and ${usage.totaltokens} total token${usage.totaltokens === 1 ? "" : "s"} (${usage.prompttokens} prompt and ${usage.completiontokens} completion)${cost > 0 ? ` at the recorded cost of ${cost.toFixed(4)}` : ""}; ${egress.reason ?? ""}`, {});
|
|
20393
|
+
return { text: outcome.text, provider, model, at };
|
|
20394
|
+
};
|
|
20395
|
+
try {
|
|
20396
|
+
return await attempt(pair.provider, pair.model);
|
|
20397
|
+
} catch (error) {
|
|
20398
|
+
const now = Date.now();
|
|
20399
|
+
const marked = markprovider({ providers, providerid: pair.provider.id, available: false, now });
|
|
20400
|
+
await memory.setproviders(marked);
|
|
20401
|
+
await audit("model", `The provider ${pair.provider.name} failed the ${input.kind} call and stays marked unavailable until its next success: ${error instanceof Error ? error.message : String(error)}`, {});
|
|
20402
|
+
const fallback = fallbackroute({ routes, providers: marked, kind: input.kind });
|
|
20403
|
+
if (fallback.provider === void 0 || fallback.model === void 0) throw error;
|
|
20404
|
+
const outcome = await attempt(fallback.provider, fallback.model);
|
|
20405
|
+
await memory.setproviders(markprovider({ providers: await memory.getproviders(), providerid: fallback.provider.id, available: true, now: Date.now() }));
|
|
20406
|
+
return outcome;
|
|
20407
|
+
}
|
|
20408
|
+
}
|
|
20409
|
+
async function recordguardnotice(kind, output) {
|
|
20410
|
+
if (output.verdict === "valid") return;
|
|
20411
|
+
await memory.addguardnotice(output);
|
|
20412
|
+
await audit("model", `The guardrails refused the ${kind} model output after ${output.attempts} attempt${output.attempts === 1 ? "" : "s"}: ${output.reason ?? "the output failed its guard"}`, {});
|
|
20413
|
+
}
|
|
20414
|
+
function draftplanof(draft, steps, origin) {
|
|
20415
|
+
const now = Date.now();
|
|
20416
|
+
const mapped = steps.map((step) => ({ id: step.id, kind: step.kind, ...step.target !== void 0 ? { target: step.target } : {}, ...step.value !== void 0 ? { value: step.value } : {}, summary: step.summary, risk: actionrisk(step.kind) }));
|
|
20417
|
+
return { id: randomid(), objective: draft.goal, origin, steps: mapped, createdat: now, expiresat: now + sessionduration, state: "pending" };
|
|
20418
|
+
}
|
|
20419
|
+
async function llmstateof() {
|
|
20420
|
+
const providers = await memory.getproviders();
|
|
20421
|
+
const routes = await memory.getmodelroutes();
|
|
20422
|
+
const budget = await memory.getcostbudget();
|
|
20423
|
+
const local = await memory.getlocalmodel();
|
|
20424
|
+
const parse = await memory.getcommandparse();
|
|
20425
|
+
const tasks = llmtaskkinds.map((kind) => {
|
|
20426
|
+
const resolved = resolveroute({ routes, providers, kind });
|
|
20427
|
+
return { kind, ...resolved.provider !== void 0 ? { provider: resolved.provider.name, model: resolved.model } : {} };
|
|
20428
|
+
});
|
|
20429
|
+
return { providers, ...local !== void 0 ? { local } : {}, routes, routehistory: (await memory.getmodelroutehistory()).slice(0, 10), drafts: (await memory.getplandrafts()).slice(0, 10), replans: (await memory.getreplans()).slice(0, 10), notes: (await memory.getreflectnotes()).slice(0, 10), ...budget !== void 0 ? { budget } : {}, usage: await memory.getusage(), templates: searchtemplates(await memory.getprompttemplates(), "").slice(0, 20), ...parse !== void 0 ? { parse } : {}, guardnotices: (await memory.getguardnotices()).slice(0, 10), tasks, toolbriefs: rendertoolbriefs((buildtoolcatalog().domains[0]?.tools ?? []).slice(0, 4)) };
|
|
20430
|
+
}
|
|
19059
20431
|
async function mcpstateof() {
|
|
19060
20432
|
const config = await mcpconfigof();
|
|
19061
20433
|
const state = await memory.getmcpstate();
|
|
@@ -19067,7 +20439,23 @@ async function mcpstateof() {
|
|
|
19067
20439
|
const channels = closeidlechannels({ channels: await memory.getstreamchannels(), now, ...config.httpstream?.idlewindowms !== void 0 ? { idlewindow: config.httpstream.idlewindowms } : {} });
|
|
19068
20440
|
await memory.setstreamchannels(channels);
|
|
19069
20441
|
const streamstatus = listremotestatus;
|
|
19070
|
-
|
|
20442
|
+
const watches = await pushresourcestate(now);
|
|
20443
|
+
const contexts = await memory.getcallcontexts();
|
|
20444
|
+
const dryruntoggle = await memory.getdryruntoggle();
|
|
20445
|
+
return { state: state?.state ?? "stopped", config, bind: binding.bind, port: binding.port, localhost: binding.localhost, clients, ...state?.bridge !== void 0 ? { bridge: state.bridge } : {}, calls: (await memory.listtoolcalls()).slice(0, 25), calllog: await memory.getcalllog({ limit: 25 }), catalog: listtools(buildtoolcatalog()), launches: (await memory.listbridgelaunches()).slice(0, 10), remote: streamstatus({ config, channels, clients, tokens, now }), pairing: (await memory.getpairingcodes()).filter((code) => code.usedat === void 0 && now < code.expiresat), allowlist: await memory.getallowlist(), tokens, identities: await memory.getclientidentities(), handshakes: (await memory.listauthhandshakes()).slice(0, 10), channels, approvals: await memory.listapprovals(), subscriptions: (await memory.geteventsubscriptions()).filter((subscription) => subscription.canceledat === void 0), watches: watches.filter((watch) => watch.canceledat === void 0), sampling: (await memory.getsamplingrequests()).slice(0, 10), limits: await memory.getcallratelimits(), contexts: contexts.slice(0, 25), inflight: contexts.filter((context) => context.state === "inflight"), batches: (await memory.getbatchcalls()).slice(0, 10), chunks: (await memory.getstreamchunks()).slice(0, 25), progressnotices: (await memory.getprogressnotices()).slice(0, 25), mocks: await memory.gettoolmocks(), dryruntoggle, idempotency: expireidempotency(await memory.getidempotencyrecords(), now) };
|
|
20446
|
+
}
|
|
20447
|
+
async function pushresourcestate(now) {
|
|
20448
|
+
const watches = await memory.getresourcewatches();
|
|
20449
|
+
const active = watches.filter((watch) => watch.canceledat === void 0);
|
|
20450
|
+
if (active.length === 0) return watches;
|
|
20451
|
+
const session = await memory.getsession();
|
|
20452
|
+
const state = { origin: session?.origin ?? "", mutations: (await memory.getmutationevents()).length, diffs: (await memory.getdiffs()).length };
|
|
20453
|
+
const pushed = notifyresource({ watches, resource: "page", state, now });
|
|
20454
|
+
if (pushed.deliveries.length > 0) {
|
|
20455
|
+
await memory.setresourcewatches(pushed.watches);
|
|
20456
|
+
await audit("protocol", `${pushed.deliveries.length} page state delta${pushed.deliveries.length === 1 ? "" : "s"} reached the resource watcher${pushed.deliveries.length === 1 ? "" : "s"} of ${[...new Set(pushed.deliveries.map((delivery) => delivery.clientid))].join(", ")}; the baselines absorbed the new page state.`, {});
|
|
20457
|
+
}
|
|
20458
|
+
return pushed.watches;
|
|
19071
20459
|
}
|
|
19072
20460
|
async function mcpmaintenance(now) {
|
|
19073
20461
|
const tokens = await memory.getsessiontokens();
|
|
@@ -19076,6 +20464,12 @@ async function mcpmaintenance(now) {
|
|
|
19076
20464
|
await memory.setsessiontokens(tokens.map((token) => expired.includes(token) ? { ...token, revokedat: now } : token));
|
|
19077
20465
|
await audit("protocol", `${expired.length} session token${expired.length === 1 ? "" : "s"} reached the user configured lifetime and the server refused ${expired.length === 1 ? "it" : "them"}; the records stay for the audit trail.`, {});
|
|
19078
20466
|
}
|
|
20467
|
+
const storedrecords = await memory.getidempotencyrecords();
|
|
20468
|
+
const liverecords = expireidempotency(storedrecords, now);
|
|
20469
|
+
if (liverecords.length !== storedrecords.length) {
|
|
20470
|
+
await memory.setidempotencyrecords(liverecords);
|
|
20471
|
+
await audit("protocol", `${storedrecords.length - liverecords.length} idempotency record${storedrecords.length - liverecords.length === 1 ? "" : "s"} expired past the user configured window and the key${storedrecords.length - liverecords.length === 1 ? " never replays" : "s never replay"}.`, {});
|
|
20472
|
+
}
|
|
19079
20473
|
const stored = await memory.listapprovals();
|
|
19080
20474
|
const approvals = expireapprovals(stored, now);
|
|
19081
20475
|
for (let index = 0; index < approvals.length; index += 1) {
|
|
@@ -19148,14 +20542,13 @@ async function trybridgelaunch(restart) {
|
|
|
19148
20542
|
function restartbridgeof(bridge) {
|
|
19149
20543
|
return { ...bridge, connected: true, restarts: bridge.restarts + 1, startedat: Date.now() };
|
|
19150
20544
|
}
|
|
20545
|
+
var mcpcancelledcalls = /* @__PURE__ */ new Set();
|
|
19151
20546
|
async function routemcpframe(client, frame, config, scopes) {
|
|
19152
20547
|
const session = await memory.getsession();
|
|
19153
20548
|
const plan = await memory.getplan();
|
|
19154
20549
|
const catalog = buildtoolcatalog();
|
|
19155
|
-
|
|
19156
|
-
const
|
|
19157
|
-
const callparams = frame.params ?? {};
|
|
19158
|
-
const response = await handleframe({ frame, client, catalog, config, ...session !== void 0 ? { session } : {}, ...plan !== void 0 ? { plan } : {}, ...scopes !== void 0 ? { scopes } : {}, origin: session?.origin ?? "", tabid: session?.tabid ?? 0, now: Date.now(), execute: gatedname !== void 0 ? (step) => raiseremoteapproval(client.id, gatedname, callparams, step) : executemcpstep });
|
|
20550
|
+
if (frame.method === "tools/call") return await routetoolcall(client, frame, config, scopes, session, plan, catalog);
|
|
20551
|
+
const response = await handleframe({ frame, client, catalog, config, ...session !== void 0 ? { session } : {}, ...plan !== void 0 ? { plan } : {}, ...scopes !== void 0 ? { scopes } : {}, origin: session?.origin ?? "", tabid: session?.tabid ?? 0, now: Date.now(), execute: executemcpstep });
|
|
19159
20552
|
const now = Date.now();
|
|
19160
20553
|
if (frame.method === "initialize") {
|
|
19161
20554
|
const clientinfo = frame.params?.clientinfo && typeof frame.params.clientinfo === "object" && !Array.isArray(frame.params.clientinfo) ? frame.params.clientinfo : void 0;
|
|
@@ -19181,19 +20574,187 @@ async function routemcpframe(client, frame, config, scopes) {
|
|
|
19181
20574
|
}
|
|
19182
20575
|
await audit("protocol", `The mcp client ${client.id} ${agreed ? "negotiated its capability set with the server and the negotiated floor stays stored on its record" : `failed the capability negotiation: ${response.error?.message ?? "the sets did not agree"}`}.`, {});
|
|
19183
20576
|
}
|
|
19184
|
-
if (frame.method === "tools/call") {
|
|
19185
|
-
const name = typeof frame.params?.name === "string" ? frame.params.name : "";
|
|
19186
|
-
const code = response.error?.code;
|
|
19187
|
-
const ok = response.error === void 0 && response.result?.iserror !== true;
|
|
19188
|
-
await memory.addtoolcall(toolcallevent({ id: randomid(), clientid: client.id, tool: name, origin: session?.origin ?? "", ok, now, ...code !== void 0 ? { code } : {} }));
|
|
19189
|
-
if (plan !== void 0) {
|
|
19190
|
-
const stepid = typeof frame.params?.stepid === "string" ? frame.params.stepid : "mcp";
|
|
19191
|
-
await memory.setprogress(recordtoolcall(await memory.getprogress(), plan.id, stepid, { clientid: client.id, tool: name, ok, ...code !== void 0 ? { code } : {} }, now));
|
|
19192
|
-
}
|
|
19193
|
-
await audit("tool", `The mcp client ${client.id} called the ${name} tool on ${session?.origin ?? "no origin"} and ${ok ? gatedname !== void 0 ? "its approval gate waits for the user decision" : "it ran behind the consent gates" : `it was refused${code !== void 0 ? ` with the ${code} error` : ""}`}; no payload rides the record.`, {});
|
|
19194
|
-
}
|
|
19195
20577
|
return response;
|
|
19196
20578
|
}
|
|
20579
|
+
async function routetoolcall(client, frame, config, scopes, session, plan, catalog) {
|
|
20580
|
+
const now = Date.now();
|
|
20581
|
+
const params = frame.params ?? {};
|
|
20582
|
+
const name = typeof params.name === "string" ? params.name : "";
|
|
20583
|
+
const origin = session?.origin ?? "";
|
|
20584
|
+
const answerframe = (result2, error) => ({ jsonrpc: "2.0", ...frame.id !== void 0 ? { id: frame.id } : error !== void 0 ? { id: null } : {}, ...error !== void 0 ? { error: { code: error.code, message: error.message, ...error.data !== void 0 ? { data: error.data } : {} } } : { result: result2 } });
|
|
20585
|
+
const refuse = async (code, message, data) => {
|
|
20586
|
+
await memory.addtoolcall(toolcallevent({ id: randomid(), clientid: client.id, tool: name, origin, ok: false, now: Date.now(), code }));
|
|
20587
|
+
await audit("tool", `The mcp client ${client.id} called the ${name} tool on ${origin || "no origin"} and was refused with the ${code} error; no payload rides the record.`, {});
|
|
20588
|
+
return answerframe(void 0, { code, message, ...data !== void 0 ? { data } : {} });
|
|
20589
|
+
};
|
|
20590
|
+
const counted = applyratelimit({ limits: await memory.getcallratelimits(), clientid: client.id, now });
|
|
20591
|
+
await memory.setcallratelimits(counted.limits);
|
|
20592
|
+
if (!counted.allowed) {
|
|
20593
|
+
const error = structurederrorof({ code: "ratelimited", message: `The client ${client.id} spent its user configured call budget of ${counted.budget ?? 0} inside the window.`, retryhint: "wait", ...counted.retryafter !== void 0 ? { retryafter: counted.retryafter } : {} });
|
|
20594
|
+
await audit("protocol", `The per client rate limit refused the ${name} call of the client ${client.id}: the budget of ${counted.budget ?? 0} calls is spent and the retry hint waits ${counted.retryafter ?? 0} milliseconds.`, {});
|
|
20595
|
+
return refuse("consentrefused", `${error.message} Retry: wait${error.retryafter !== void 0 ? ` after ${error.retryafter} milliseconds` : ""}.`, { retryhint: error.retryhint, ...error.retryafter !== void 0 ? { retryafter: error.retryafter } : {} });
|
|
20596
|
+
}
|
|
20597
|
+
const key = typeof params.idempotencykey === "string" && params.idempotencykey.trim() !== "" ? params.idempotencykey.trim() : void 0;
|
|
20598
|
+
if (key !== void 0) {
|
|
20599
|
+
const replay = checkidempotency({ records: await memory.getidempotencyrecords(), key, clientid: client.id, now });
|
|
20600
|
+
if (replay.replay !== void 0 && replay.record !== void 0) {
|
|
20601
|
+
await memory.addtoolcall(toolcallevent({ id: randomid(), clientid: client.id, tool: name, origin, ok: !replay.replay.iserror, now: Date.now(), idempotencykey: key, replayed: true }));
|
|
20602
|
+
await audit("tool", `The mcp client ${client.id} repeated the idempotency key of the ${name} call and the server replayed the stored result instead of executing; no payload rides the record.`, {});
|
|
20603
|
+
return answerframe({ ...replay.replay, replayed: true, idempotencykey: key, originalat: replay.record.createdat });
|
|
20604
|
+
}
|
|
20605
|
+
}
|
|
20606
|
+
const tool = name !== "" ? resolvetool(catalog, name) : void 0;
|
|
20607
|
+
const mock = tool !== void 0 ? applymock({ mocks: await memory.gettoolmocks(), tool: tool.name }) : void 0;
|
|
20608
|
+
if (mock?.result !== void 0) {
|
|
20609
|
+
await memory.addtoolcall(toolcallevent({ id: randomid(), clientid: client.id, tool: name, origin, ok: !mock.result.iserror, now: Date.now(), mocked: true }));
|
|
20610
|
+
await audit("tool", `A tool mock of a test context answered the ${name} call of the client ${client.id} with its canned result; the browser was never touched.`, {});
|
|
20611
|
+
return answerframe({ ...mock.result, mocked: true });
|
|
20612
|
+
}
|
|
20613
|
+
if (mock?.reason !== void 0) return await refuse("consentrefused", mock.reason);
|
|
20614
|
+
let dryrun = params.dryrun === true;
|
|
20615
|
+
if (!dryrun && await memory.getdryruntoggle()) {
|
|
20616
|
+
dryrun = true;
|
|
20617
|
+
await memory.setdryruntoggle(false);
|
|
20618
|
+
}
|
|
20619
|
+
if (dryrun && tool !== void 0) {
|
|
20620
|
+
const dry = dryruntool({ tool, params, client, ...session !== void 0 ? { session } : {}, ...plan !== void 0 ? { plan } : {}, origin, now });
|
|
20621
|
+
await memory.addtoolcall(toolcallevent({ id: randomid(), clientid: client.id, tool: name, origin, ok: dry.argsvalid && dry.consentok, now: Date.now(), dryrun: true }));
|
|
20622
|
+
await audit("tool", `The mcp client ${client.id} dry ran the ${name} tool: ${dry.findings.length} finding${dry.findings.length === 1 ? "" : "s"} and no page mutation because a dry run never executes anything.`, {});
|
|
20623
|
+
return answerframe({ content: `The ${name} dry run found ${dry.findings.length} issue${dry.findings.length === 1 ? "" : "s"}.`, payload: { dryrun: dry }, iserror: false });
|
|
20624
|
+
}
|
|
20625
|
+
const context = begincall({ clientid: client.id, tool: name, ...key !== void 0 ? { idempotencykey: key } : {}, now });
|
|
20626
|
+
await memory.setcallcontexts([context, ...await memory.getcallcontexts()]);
|
|
20627
|
+
const started = notifyevent({ subscriptions: await memory.geteventsubscriptions(), kind: "callstarted", ...origin !== "" ? { origin } : {}, ...name !== "" ? { tool: name } : {}, payload: { callid: context.callid, clientid: client.id }, now });
|
|
20628
|
+
await memory.seteventsubscriptions(started.subscriptions);
|
|
20629
|
+
const notice = notifyprogress({ callid: context.callid, percent: 10, message: `The ${name} call started behind the consent gates.`, now });
|
|
20630
|
+
await memory.addprogressnotice(notice);
|
|
20631
|
+
const progressed = notifyevent({ subscriptions: started.subscriptions, kind: "progress", ...origin !== "" ? { origin } : {}, ...name !== "" ? { tool: name } : {}, payload: { callid: notice.callid, percent: notice.percent, message: notice.message, cancellable: notice.cancellable }, now });
|
|
20632
|
+
await memory.seteventsubscriptions(progressed.subscriptions);
|
|
20633
|
+
const gatedname = client.transport === "http" && tool !== void 0 && tool.risk !== "read" ? tool.name : void 0;
|
|
20634
|
+
const execute = gatedname !== void 0 ? (step) => raiseremoteapproval(client.id, gatedname, params, step) : async (step) => {
|
|
20635
|
+
if (mcpcancelledcalls.has(context.callid)) throw new Error(`The ${name} call was cancelled before the page executor ran.`);
|
|
20636
|
+
const result2 = await executemcpstep(step);
|
|
20637
|
+
if (mcpcancelledcalls.has(context.callid)) throw new Error(`The ${name} call was cancelled in flight; the partial result stays preserved.`);
|
|
20638
|
+
return result2;
|
|
20639
|
+
};
|
|
20640
|
+
const response = await handleframe({ frame, client, catalog, config, ...session !== void 0 ? { session } : {}, ...plan !== void 0 ? { plan } : {}, ...scopes !== void 0 ? { scopes } : {}, origin, tabid: session?.tabid ?? 0, now: Date.now(), contexts: await memory.getcallcontexts(), execute });
|
|
20641
|
+
const after = Date.now();
|
|
20642
|
+
const ok = response.error === void 0 && (response.result?.iserror ?? true) !== true;
|
|
20643
|
+
const result = response.error === void 0 ? response.result : void 0;
|
|
20644
|
+
const closed = endcall({ contexts: await memory.getcallcontexts(), callid: context.callid, ok, ...response.error !== void 0 ? { errorcode: response.error.code } : {}, ...result !== void 0 ? { partial: result } : {}, now: after });
|
|
20645
|
+
if (closed.contexts !== void 0) await memory.setcallcontexts(closed.contexts);
|
|
20646
|
+
if (result !== void 0 && typeof result.content === "string" && result.content.length > 0) {
|
|
20647
|
+
const chunks = chunkcontent({ callid: context.callid, content: result.content, now: after });
|
|
20648
|
+
for (const chunk of chunks) await memory.addstreamchunk(chunk);
|
|
20649
|
+
const streamed = notifyevent({ subscriptions: await memory.geteventsubscriptions(), kind: "streamchunk", ...origin !== "" ? { origin } : {}, ...name !== "" ? { tool: name } : {}, payload: { callid: context.callid, chunks: chunks.length, done: true }, now: after });
|
|
20650
|
+
await memory.seteventsubscriptions(streamed.subscriptions);
|
|
20651
|
+
}
|
|
20652
|
+
const finished = notifyevent({ subscriptions: await memory.geteventsubscriptions(), kind: "callresult", ...origin !== "" ? { origin } : {}, ...name !== "" ? { tool: name } : {}, payload: { callid: context.callid, ok }, now: after });
|
|
20653
|
+
await memory.seteventsubscriptions(finished.subscriptions);
|
|
20654
|
+
if (key !== void 0 && result !== void 0 && !result.iserror) await memory.setidempotencyrecords(recordidempotency({ records: await memory.getidempotencyrecords(), key, clientid: client.id, tool: name, result, now: after }));
|
|
20655
|
+
await memory.addtoolcall(toolcallevent({ id: randomid(), clientid: client.id, tool: name, origin, ok, now: after, ...response.error !== void 0 ? { code: response.error.code } : {}, callid: context.callid, ...key !== void 0 ? { idempotencykey: key } : {} }));
|
|
20656
|
+
if (plan !== void 0) {
|
|
20657
|
+
const stepid = typeof params.stepid === "string" ? params.stepid : "mcp";
|
|
20658
|
+
await memory.setprogress(recordtoolcall(await memory.getprogress(), plan.id, stepid, { clientid: client.id, tool: name, ok, ...response.error !== void 0 ? { code: response.error.code } : {} }, after));
|
|
20659
|
+
}
|
|
20660
|
+
await audit("tool", `The mcp client ${client.id} called the ${name} tool on ${origin || "no origin"} and ${ok ? gatedname !== void 0 ? "its approval gate waits for the user decision" : `it ran behind the consent gates as the call ${context.callid}` : `it was refused${response.error !== void 0 ? ` with the ${response.error.code} error` : ""}`}${key !== void 0 ? " under its idempotency key" : ""}; no payload rides the record.`, {});
|
|
20661
|
+
return response;
|
|
20662
|
+
}
|
|
20663
|
+
async function routeprotocolframe(client, frame, config, scopes) {
|
|
20664
|
+
void config;
|
|
20665
|
+
void scopes;
|
|
20666
|
+
const params = frame.params ?? {};
|
|
20667
|
+
const now = Date.now();
|
|
20668
|
+
const answerframe = (result, error) => ({ jsonrpc: "2.0", ...frame.id !== void 0 ? { id: frame.id } : error !== void 0 ? { id: null } : {}, ...error !== void 0 ? { error: { code: error.code, message: error.message } } : { result } });
|
|
20669
|
+
if (frame.method === "events/subscribe") {
|
|
20670
|
+
const kinds = Array.isArray(params.kinds) ? params.kinds.filter((kind) => typeof kind === "string") : void 0;
|
|
20671
|
+
const registered = subscriberegister({ clientid: client.id, ...kinds !== void 0 ? { kinds } : {}, ...typeof params.origin === "string" && params.origin.trim() !== "" ? { origin: params.origin.trim() } : {}, ...typeof params.tool === "string" && params.tool.trim() !== "" ? { tool: params.tool.trim() } : {}, now });
|
|
20672
|
+
if (registered.subscription === void 0) return answerframe(void 0, { code: "params", message: registered.reason ?? "The event subscription did not register." });
|
|
20673
|
+
const grade = subscriptiongrade(registered.subscription);
|
|
20674
|
+
if (!grade.allowed) return answerframe(void 0, { code: "consentrefused", message: grade.reason ?? "The event subscription failed its gate." });
|
|
20675
|
+
await memory.seteventsubscriptions([registered.subscription, ...await memory.geteventsubscriptions()]);
|
|
20676
|
+
await audit("protocol", `The client ${client.id} subscribed to the ${registered.subscription.kinds.join(", ")} event kinds${registered.subscription.origin !== void 0 ? ` of the origin ${registered.subscription.origin}` : ""}${registered.subscription.tool !== void 0 ? ` for the tool ${registered.subscription.tool}` : ""}; the subscription grades read only.`, {});
|
|
20677
|
+
return answerframe({ subscriptionid: registered.subscription.id, kinds: registered.subscription.kinds });
|
|
20678
|
+
}
|
|
20679
|
+
if (frame.method === "events/unsubscribe") {
|
|
20680
|
+
const subscriptionid = typeof params.subscriptionid === "string" ? params.subscriptionid : "";
|
|
20681
|
+
await memory.seteventsubscriptions(unsubscriberegister(await memory.geteventsubscriptions(), subscriptionid, now));
|
|
20682
|
+
await audit("protocol", `The client ${client.id} cancelled its event subscription ${subscriptionid}; the record stays for the audit trail.`, {});
|
|
20683
|
+
return answerframe({ unsubscribed: true, subscriptionid });
|
|
20684
|
+
}
|
|
20685
|
+
if (frame.method === "resources/watch") {
|
|
20686
|
+
const resource = typeof params.resource === "string" ? params.resource.trim() : "";
|
|
20687
|
+
const state = params.state !== void 0 && typeof params.state === "object" && !Array.isArray(params.state) ? params.state : void 0;
|
|
20688
|
+
const watched = watchresource({ clientid: client.id, resource, ...state !== void 0 ? { state } : {}, now });
|
|
20689
|
+
if (watched.watch === void 0) return answerframe(void 0, { code: "params", message: watched.reason ?? "The resource watcher did not start." });
|
|
20690
|
+
await memory.setresourcewatches([watched.watch, ...await memory.getresourcewatches()]);
|
|
20691
|
+
await audit("protocol", `The client ${client.id} started the ${watched.watch.resource} resource watcher with its page state baseline; the deltas compare against it.`, {});
|
|
20692
|
+
return answerframe({ watchid: watched.watch.id, resource: watched.watch.resource });
|
|
20693
|
+
}
|
|
20694
|
+
if (frame.method === "resources/unwatch") {
|
|
20695
|
+
const watchid = typeof params.watchid === "string" ? params.watchid : "";
|
|
20696
|
+
await memory.setresourcewatches(unwatchresource(await memory.getresourcewatches(), watchid, now));
|
|
20697
|
+
await audit("protocol", `The client ${client.id} cancelled its resource watcher ${watchid}; the record stays for the audit trail.`, {});
|
|
20698
|
+
return answerframe({ unwatched: true, watchid });
|
|
20699
|
+
}
|
|
20700
|
+
if (frame.method === "sampling/answer") {
|
|
20701
|
+
const samplingid = typeof params.samplingid === "string" ? params.samplingid : "";
|
|
20702
|
+
const clientanswer = typeof params.answer === "string" ? params.answer : void 0;
|
|
20703
|
+
const outcome = answersampling({ requests: await memory.getsamplingrequests(), id: samplingid, ...clientanswer !== void 0 ? { answer: clientanswer } : {}, ...params.refused === true ? { refused: true } : {}, now });
|
|
20704
|
+
if (outcome.request === void 0) return answerframe(void 0, { code: "params", message: outcome.reason ?? "The sampling answer closed no request." });
|
|
20705
|
+
await memory.setsamplingrequests(outcome.requests);
|
|
20706
|
+
await audit("protocol", `The sampling callback ${samplingid} of the client ${client.id} closed its round trip with the client answer; the provenance times ride the record.`, {});
|
|
20707
|
+
return answerframe({ samplingid, state: outcome.request.state });
|
|
20708
|
+
}
|
|
20709
|
+
if (frame.method === "calls/cancel") {
|
|
20710
|
+
const callid = typeof params.callid === "string" ? params.callid : "";
|
|
20711
|
+
const reason = typeof params.reason === "string" ? params.reason : void 0;
|
|
20712
|
+
if (callid.trim() === "") return answerframe(void 0, { code: "params", message: "The cancellation frame needs the call id it aborts." });
|
|
20713
|
+
const chunks = (await memory.getstreamchunks()).filter((chunk) => chunk.callid === callid);
|
|
20714
|
+
const partial = chunks.length > 0 ? { content: chunks.map((chunk) => chunk.content).join(""), iserror: false } : void 0;
|
|
20715
|
+
const aborted = canceltool({ contexts: await memory.getcallcontexts(), callid, ...reason !== void 0 ? { reason } : {}, ...partial !== void 0 ? { partial } : {}, now });
|
|
20716
|
+
if (aborted.context === void 0) return answerframe(void 0, { code: "params", message: aborted.reason ?? "The cancellation frame named no in flight tool call." });
|
|
20717
|
+
await memory.setcallcontexts(aborted.contexts);
|
|
20718
|
+
mcpcancelledcalls.add(callid);
|
|
20719
|
+
const cancelled = notifyevent({ subscriptions: await memory.geteventsubscriptions(), kind: "cancellation", payload: { callid, reason: reason ?? "cancelled by the client" }, now });
|
|
20720
|
+
await memory.seteventsubscriptions(cancelled.subscriptions);
|
|
20721
|
+
await audit("protocol", `The client ${client.id} cancelled the in flight ${aborted.context.tool} call ${callid}${partial !== void 0 ? " with its partial result preserved" : ""}; the cooperative flag stops the page executor between chunks and the workflow engine keeps its own pause machinery.`, {});
|
|
20722
|
+
return answerframe({ cancelled: true, callid, ...aborted.context.partial !== void 0 ? { partial: aborted.context.partial } : {} });
|
|
20723
|
+
}
|
|
20724
|
+
if (frame.method === "calls/batch") {
|
|
20725
|
+
const members = (Array.isArray(params.calls) ? params.calls : []).filter((entry) => entry !== null && typeof entry === "object" && !Array.isArray(entry)).map((entry, index) => ({ id: typeof entry.id === "string" && entry.id.trim() !== "" ? entry.id.trim() : `member-${index + 1}`, name: typeof entry.name === "string" ? entry.name : "", params: entry.params !== void 0 && typeof entry.params === "object" && !Array.isArray(entry.params) ? entry.params : {} }));
|
|
20726
|
+
const stoponerror = params.stoponerror === true;
|
|
20727
|
+
const session = await memory.getsession();
|
|
20728
|
+
const plan = await memory.getplan();
|
|
20729
|
+
const catalog = buildtoolcatalog();
|
|
20730
|
+
const risks = members.map((member) => {
|
|
20731
|
+
const tool = resolvetool(catalog, member.name);
|
|
20732
|
+
return { risk: tool?.risk ?? "read" };
|
|
20733
|
+
});
|
|
20734
|
+
const approved = plan?.state === "approved" && members.every((member) => {
|
|
20735
|
+
const tool = resolvetool(catalog, member.name);
|
|
20736
|
+
return tool === void 0 || tool.risk === "read" || typeof member.params.stepid === "string" && plan.steps.some((step) => step.id === member.params.stepid);
|
|
20737
|
+
});
|
|
20738
|
+
const gate = batchgrade({ calls: risks, approved });
|
|
20739
|
+
const batch = { id: randomid(), clientid: client.id, calls: members, stoponerror, state: gate.allowed ? "running" : "stopped", createdat: now, outcomes: [] };
|
|
20740
|
+
await memory.setbatchcall(batch);
|
|
20741
|
+
if (!gate.allowed) {
|
|
20742
|
+
await audit("protocol", `The batch call of the client ${client.id} was refused: ${gate.reason ?? "the batch failed its grade."}`, {});
|
|
20743
|
+
return answerframe(void 0, { code: "consentrefused", message: gate.reason ?? "The batch call failed its grade." });
|
|
20744
|
+
}
|
|
20745
|
+
const outcome = await runbatch({ calls: members, stoponerror, now, execute: async (member) => {
|
|
20746
|
+
const response = await routemcpframe(client, { jsonrpc: "2.0", ...frame.id !== void 0 ? { id: frame.id } : {}, method: "tools/call", params: { ...member.params, name: member.name } }, config, scopes);
|
|
20747
|
+
const ok = response.error === void 0 && (response.result?.iserror ?? true) !== true;
|
|
20748
|
+
return { ok, ...response.result !== void 0 ? { result: response.result } : {}, ...response.error !== void 0 ? { error: structurederrorof({ code: response.error.code, message: response.error.message, retryhint: retryhintof({ code: response.error.code, message: response.error.message }) }) } : {} };
|
|
20749
|
+
} });
|
|
20750
|
+
const storedbatch = { ...batch, state: outcome.stoppedat !== void 0 ? "stopped" : "done", outcomes: outcome.outcomes, finishedat: Date.now() };
|
|
20751
|
+
await memory.setbatchcall(storedbatch);
|
|
20752
|
+
const ran = outcome.outcomes.filter((entry) => entry.ok).length;
|
|
20753
|
+
await audit("protocol", `The client ${client.id} ran the batch ${batch.id} of ${members.length} ordered call${members.length === 1 ? "" : "s"}${outcome.stoppedat !== void 0 ? ` and stopped at the member ${outcome.stoppedat} on its first error as the flag requested` : ""}; ${ran} of ${members.length} members ran behind the gates.`, {});
|
|
20754
|
+
return answerframe({ batchid: batch.id, state: storedbatch.state, stoponerror, outcomes: storedbatch.outcomes });
|
|
20755
|
+
}
|
|
20756
|
+
return void 0;
|
|
20757
|
+
}
|
|
19197
20758
|
async function processmcpframe(raw, clientid, transport, rawtoken, fingerprint) {
|
|
19198
20759
|
const state = await memory.getmcpstate();
|
|
19199
20760
|
if (state?.state !== "running") return { jsonrpc: "2.0", id: null, error: rpcerrorof("consentrefused", "The mcp server is not running and no frame is routed.") };
|
|
@@ -19236,7 +20797,11 @@ async function processmcpframe(raw, clientid, transport, rawtoken, fingerprint)
|
|
|
19236
20797
|
await audit("protocol", `A new mcp client ${client.id} connected on the ${transport} transport and waits for the pairing approval; unpaired clients never dispatch tools.`, {});
|
|
19237
20798
|
}
|
|
19238
20799
|
const previous = mcpclientchains.get(client.id) ?? Promise.resolve();
|
|
19239
|
-
const task = previous.catch(() => void 0).then(async () =>
|
|
20800
|
+
const task = previous.catch(() => void 0).then(async () => {
|
|
20801
|
+
const protocol = await routeprotocolframe(client, frame, config, scopes);
|
|
20802
|
+
if (protocol !== void 0) return protocol;
|
|
20803
|
+
return await routemcpframe(client, frame, config, scopes);
|
|
20804
|
+
});
|
|
19240
20805
|
mcpclientchains.set(client.id, task);
|
|
19241
20806
|
return task;
|
|
19242
20807
|
}
|