@herbertgao/pi-extensions 2026.9.13 → 2026.9.14
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/node_modules/@herbertgao/pi-bark/package.json +2 -2
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +4 -4
- package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +6 -0
- package/node_modules/@herbertgao/pi-subagents/package.json +4 -4
- package/node_modules/@herbertgao/resume-from/package.json +2 -2
- package/node_modules/@herbertgao/sol-pi/package.json +5 -5
- package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/extension.ts +2 -0
- package/node_modules/pi-mcp-adapter/CHANGELOG.md +26 -0
- package/node_modules/pi-mcp-adapter/README.md +39 -9
- package/node_modules/pi-mcp-adapter/cli.js +33 -22
- package/node_modules/pi-mcp-adapter/commands.ts +3 -3
- package/node_modules/pi-mcp-adapter/config.ts +9 -1
- package/node_modules/pi-mcp-adapter/dist/config.js +9 -1
- package/node_modules/pi-mcp-adapter/dist/config.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/jev-client.d.ts +1 -2
- package/node_modules/pi-mcp-adapter/dist/jev-client.js +59 -32
- package/node_modules/pi-mcp-adapter/dist/jev-client.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/jev-contracts.d.ts +1 -1
- package/node_modules/pi-mcp-adapter/dist/jev-key-store.d.ts +33 -4
- package/node_modules/pi-mcp-adapter/dist/jev-key-store.js +148 -28
- package/node_modules/pi-mcp-adapter/dist/jev-key-store.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/mcp-auth-flow.js +1 -2
- package/node_modules/pi-mcp-adapter/dist/mcp-auth-flow.js.map +1 -1
- package/node_modules/pi-mcp-adapter/dist/types.d.ts +7 -1
- package/node_modules/pi-mcp-adapter/dist/types.js.map +1 -1
- package/node_modules/pi-mcp-adapter/index.ts +51 -38
- package/node_modules/pi-mcp-adapter/jev-client.ts +58 -31
- package/node_modules/pi-mcp-adapter/jev-contracts.ts +2 -2
- package/node_modules/pi-mcp-adapter/jev-key-store.ts +144 -31
- package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +1 -2
- package/node_modules/pi-mcp-adapter/mcp-code.ts +6 -6
- package/node_modules/pi-mcp-adapter/package.json +5 -5
- package/node_modules/pi-mcp-adapter/semantic-search.ts +3 -3
- package/node_modules/pi-mcp-adapter/types.ts +7 -1
- package/node_modules/pi-multi-account/CHANGELOG.md +6 -0
- package/node_modules/pi-multi-account/README.md +6 -5
- package/node_modules/pi-multi-account/index.ts +1 -1
- package/node_modules/pi-multi-account/package.json +3 -3
- package/node_modules/pi-typesafe/dist/backends.d.ts +10 -0
- package/node_modules/pi-typesafe/dist/backends.js +26 -0
- package/node_modules/pi-typesafe/dist/client.d.ts +1 -1
- package/node_modules/pi-typesafe/dist/client.js +10 -6
- package/node_modules/pi-typesafe/dist/errors.d.ts +10 -2
- package/node_modules/pi-typesafe/dist/errors.js +28 -7
- package/node_modules/pi-typesafe/dist/extension.js +2 -1
- package/node_modules/pi-typesafe/package.json +4 -4
- package/node_modules/pi-web-access/CHANGELOG.md +22 -0
- package/node_modules/pi-web-access/README.md +21 -8
- package/node_modules/pi-web-access/baizhi.ts +124 -0
- package/node_modules/pi-web-access/crawl4ai.ts +2 -10
- package/node_modules/pi-web-access/curator-page.ts +3 -1
- package/node_modules/pi-web-access/curator-server.ts +1 -0
- package/node_modules/pi-web-access/dist/index.js +540 -220
- package/node_modules/pi-web-access/firecrawl.ts +2 -10
- package/node_modules/pi-web-access/gemini-search.ts +5 -10
- package/node_modules/pi-web-access/index.ts +134 -23
- package/node_modules/pi-web-access/package.json +5 -1
- package/node_modules/pi-web-access/storage.ts +1 -0
- package/node_modules/pi-web-access/tool-activation.ts +134 -0
- package/node_modules/pi-web-access/utils.ts +9 -1
- package/package.json +13 -13
|
@@ -56,6 +56,18 @@ const INIT_WAIT_TIMEOUT_MS = 30_000;
|
|
|
56
56
|
const INIT_FAILURE_MESSAGE_MAX_CHARS = 1_000;
|
|
57
57
|
const INIT_WAIT_TIMED_OUT: unique symbol = Symbol("init-wait-timed-out");
|
|
58
58
|
|
|
59
|
+
function hasEnabledServerWithoutValidMetadata(
|
|
60
|
+
config: McpConfig,
|
|
61
|
+
cache: MetadataCache | null,
|
|
62
|
+
directSpecs: readonly DirectToolSpec[] = [],
|
|
63
|
+
): boolean {
|
|
64
|
+
return Object.entries(config.mcpServers).some(([serverName, definition]) => {
|
|
65
|
+
if (isServerDisabled(definition) || directSpecs.some((spec) => spec.serverName === serverName)) return false;
|
|
66
|
+
const entry = cache?.servers[serverName];
|
|
67
|
+
return entry === undefined || !isServerCacheValid(entry, definition);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
59
71
|
export interface McpServerRegistration {
|
|
60
72
|
dispose(): Promise<void>;
|
|
61
73
|
}
|
|
@@ -320,17 +332,8 @@ function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
|
320
332
|
const envRaw = process.env.MCP_DIRECT_TOOLS;
|
|
321
333
|
const envDirectToolOverride = parseEnvDirectToolOverride(envRaw);
|
|
322
334
|
const namespaceEnvOverride = resolveNamespaceEnvOverride(envRaw, envDirectToolOverride);
|
|
323
|
-
const
|
|
324
|
-
|
|
325
|
-
const hasStartupServer = enabledEarlyServers.some(([, definition]) =>
|
|
326
|
-
definition.lifecycle === "eager" || definition.lifecycle === "keep-alive");
|
|
327
|
-
const hasUsableCachedMetadata = earlyCache !== null && enabledEarlyServers.every(([serverName, definition]) => {
|
|
328
|
-
const entry = earlyCache.servers[serverName];
|
|
329
|
-
return entry !== undefined && isServerCacheValid(entry, definition);
|
|
330
|
-
});
|
|
331
|
-
const hasColdEnvironmentDirectTools = envRaw !== undefined && envRaw !== "__none__"
|
|
332
|
-
&& getMissingConfiguredDirectToolServers(earlyConfig, earlyCache, envDirectToolOverride).length > 0;
|
|
333
|
-
const deferSessionRuntime = !hasStartupServer && hasUsableCachedMetadata && !hasColdEnvironmentDirectTools;
|
|
335
|
+
const hasStartupServer = Object.values(earlyConfig.mcpServers).some((definition) =>
|
|
336
|
+
!isServerDisabled(definition) && (definition.lifecycle === "eager" || definition.lifecycle === "keep-alive"));
|
|
334
337
|
const registeredDirectTools = new Map<string, string>();
|
|
335
338
|
const registeredDirectToolServers = new Map<string, string>();
|
|
336
339
|
const registeredDirectToolVersions = new Map<string, number>();
|
|
@@ -341,8 +344,6 @@ function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
|
341
344
|
const fallbackDeactivatedTools = new Set<string>();
|
|
342
345
|
// directTools: "search" — registered inactive, activated by mcp({ search }).
|
|
343
346
|
const lazyDirectTools = new Set<string>();
|
|
344
|
-
// The lazy tools a search has activated; every other lazy tool is held out
|
|
345
|
-
// of the active set. Per process: nothing here survives a restart.
|
|
346
347
|
const searchActivatedTools = new Set<string>();
|
|
347
348
|
const toolRenderOptions = resolveMcpToolRenderOptions(earlyConfig.settings);
|
|
348
349
|
const toolRenderShell = toolRenderOptions.resultRendering === "compact" ? "self" : "default";
|
|
@@ -657,29 +658,20 @@ function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
|
657
658
|
|
|
658
659
|
function getDeferredSessionSnapshot(cwd: string | undefined): {
|
|
659
660
|
config: McpConfig;
|
|
660
|
-
|
|
661
|
+
cache: MetadataCache | null;
|
|
661
662
|
enabledServerCount: number;
|
|
662
663
|
} | undefined {
|
|
663
664
|
const config = programmaticConfig
|
|
664
665
|
? resolveConfiguredClaudePluginMcp(cloneMcpConfig(sessionConfig), cwd ?? process.cwd())
|
|
665
666
|
: loadMcpConfig(earlyConfigPath, cwd);
|
|
666
667
|
const cache = loadMetadataCache();
|
|
667
|
-
const enabledServers = Object.
|
|
668
|
-
|
|
669
|
-
if (enabledServers.some(([, definition]) => definition.lifecycle === "eager" || definition.lifecycle === "keep-alive")) {
|
|
670
|
-
return undefined;
|
|
671
|
-
}
|
|
672
|
-
if (!cache || !enabledServers.every(([serverName, definition]) => {
|
|
673
|
-
const entry = cache.servers[serverName];
|
|
674
|
-
return entry !== undefined && isServerCacheValid(entry, definition);
|
|
675
|
-
})) return undefined;
|
|
668
|
+
const enabledServers = Object.values(config.mcpServers).filter((definition) => !isServerDisabled(definition));
|
|
669
|
+
if (enabledServers.some((definition) => definition.lifecycle === "eager" || definition.lifecycle === "keep-alive")) return undefined;
|
|
676
670
|
if (envRaw !== undefined && envRaw !== "__none__"
|
|
677
671
|
&& getMissingConfiguredDirectToolServers(config, cache, envDirectToolOverride).length > 0) return undefined;
|
|
678
|
-
|
|
679
|
-
config,
|
|
680
|
-
|
|
681
|
-
enabledServerCount: enabledServers.length,
|
|
682
|
-
};
|
|
672
|
+
if (config.settings?.deferWithMissingMetadata !== true
|
|
673
|
+
&& (cache === null || hasEnabledServerWithoutValidMetadata(config, cache))) return undefined;
|
|
674
|
+
return { config, cache, enabledServerCount: enabledServers.length };
|
|
683
675
|
}
|
|
684
676
|
|
|
685
677
|
function syncNamespaceTools(
|
|
@@ -742,8 +734,6 @@ function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
|
742
734
|
.flatMap(([, prompts]) => prompts));
|
|
743
735
|
}
|
|
744
736
|
|
|
745
|
-
registerPromptCommands(resolveCachedPrompts(earlyConfig));
|
|
746
|
-
|
|
747
737
|
const registerRuntimeServer = (name: string, definition: ServerEntry): McpServerRegistration => {
|
|
748
738
|
if (typeof name !== "string" || name.trim() === "") {
|
|
749
739
|
throw new Error("MCP server name must be a non-empty string");
|
|
@@ -1081,6 +1071,9 @@ function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
|
1081
1071
|
});
|
|
1082
1072
|
|
|
1083
1073
|
pi.on("session_start", async (_event, ctx) => {
|
|
1074
|
+
// Reset before any await so replacement sessions cannot inherit activation.
|
|
1075
|
+
searchActivatedTools.clear();
|
|
1076
|
+
holdLazyToolsInactive();
|
|
1084
1077
|
const generation = ++lifecycleGeneration;
|
|
1085
1078
|
largeDirectToolsAdvisoryDelivered = false;
|
|
1086
1079
|
const previousState = state;
|
|
@@ -1111,15 +1104,24 @@ function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
|
1111
1104
|
if (state) return;
|
|
1112
1105
|
|
|
1113
1106
|
if (!initPromise) {
|
|
1114
|
-
const deferredSnapshot =
|
|
1107
|
+
const deferredSnapshot = getDeferredSessionSnapshot(ctx.cwd);
|
|
1115
1108
|
if (deferredSnapshot) {
|
|
1116
|
-
|
|
1109
|
+
const { config, cache, enabledServerCount } = deferredSnapshot;
|
|
1110
|
+
const directResult = syncDirectTools(config, cache);
|
|
1111
|
+
syncProxyTool(config, cache, directResult.specs);
|
|
1112
|
+
syncNamespaceTools(config, cache, directResult.reservedDirectNames, directResult.activeDirectNames);
|
|
1113
|
+
// Pi cannot unregister commands. Wait until cwd is authoritative, and
|
|
1114
|
+
// under the opt-in wait for live metadata, before exposing prompts.
|
|
1115
|
+
if (config.settings?.deferWithMissingMetadata !== true) {
|
|
1116
|
+
registerPromptCommands(resolveCachedPrompts(config));
|
|
1117
|
+
}
|
|
1118
|
+
deliverLargeDirectToolsAdvisory(ctx, config, directResult.specs);
|
|
1117
1119
|
if (generation !== lifecycleGeneration || !owner.isActive() || currentOwner !== owner) return;
|
|
1118
|
-
const serverCount = Object.keys(
|
|
1120
|
+
const serverCount = Object.keys(config.mcpServers).length;
|
|
1119
1121
|
const formattedStatus = formatMcpFooterStatus(
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
serverCount -
|
|
1122
|
+
config,
|
|
1123
|
+
enabledServerCount,
|
|
1124
|
+
serverCount - enabledServerCount,
|
|
1123
1125
|
0,
|
|
1124
1126
|
);
|
|
1125
1127
|
const theme = ctx.ui?.theme;
|
|
@@ -1148,6 +1150,9 @@ function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
|
1148
1150
|
await initializationStarted;
|
|
1149
1151
|
});
|
|
1150
1152
|
|
|
1153
|
+
// Other extensions can reactivate registered tools after session_start.
|
|
1154
|
+
pi.on("before_agent_start", holdLazyToolsInactive);
|
|
1155
|
+
|
|
1151
1156
|
pi.on("session_tree", (_event, ctx) => {
|
|
1152
1157
|
const currentState = state;
|
|
1153
1158
|
const owner = currentOwner;
|
|
@@ -1589,6 +1594,12 @@ function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
|
1589
1594
|
details: { mode: "install", error: "programmatic_config" },
|
|
1590
1595
|
};
|
|
1591
1596
|
}
|
|
1597
|
+
if (targetState.config.settings?.allowInstall === false) {
|
|
1598
|
+
return {
|
|
1599
|
+
content: [{ type: "text" as const, text: "MCP install is disabled by configuration." }],
|
|
1600
|
+
details: { mode: "install", error: "install_disabled" },
|
|
1601
|
+
};
|
|
1602
|
+
}
|
|
1592
1603
|
if (target !== undefined && target !== "global" && target !== "project") {
|
|
1593
1604
|
return {
|
|
1594
1605
|
content: [{ type: "text" as const, text: "MCP install target must be 'global' or 'project'." }],
|
|
@@ -1787,7 +1798,7 @@ function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
|
1787
1798
|
describe: Type.Optional(Type.String({ description: "Tool name to describe (shows parameters)" })),
|
|
1788
1799
|
instructions: Type.Optional(Type.String({ description: "Server name to show that server's usage instructions" })),
|
|
1789
1800
|
search: Type.Optional(Type.String({ description: "Search tools by name/description" })),
|
|
1790
|
-
searchMode: Type.Optional(Type.String({ enum: ["lexical", "semantic"], description: "Search backend (default: lexical; semantic is available when a
|
|
1801
|
+
searchMode: Type.Optional(Type.String({ enum: ["lexical", "semantic"], description: "Search backend (default: lexical; semantic is available when a System One key is configured)" })),
|
|
1791
1802
|
regex: Type.Optional(Type.Boolean({ description: "Treat search as regex (default: substring match)" })),
|
|
1792
1803
|
includeSchemas: Type.Optional(Type.Boolean({ description: "Include parameter schemas in search results (default: true)" })),
|
|
1793
1804
|
limit: optionalNumber({ minimum: 1, description: "Maximum search results to return (default: 12)" }),
|
|
@@ -1941,6 +1952,7 @@ function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
|
1941
1952
|
}
|
|
1942
1953
|
if (params.search !== undefined) {
|
|
1943
1954
|
const result = await proxyModes.executeSearch(proxyState, params.search, params.regex, params.server, params.includeSchemas, params.limit, params.offset, params.searchMode, signal);
|
|
1955
|
+
assertRuntimeGuard(proxyGuard);
|
|
1944
1956
|
if (lazyDirectTools.size === 0) return result;
|
|
1945
1957
|
holdLazyToolsInactive();
|
|
1946
1958
|
const matches = (result.details as { matches?: Array<{ server: string; tool: string }> } | undefined)?.matches ?? [];
|
|
@@ -1978,7 +1990,8 @@ function installMcpAdapter(pi: ExtensionAPI, options: McpAdapterOptions) {
|
|
|
1978
1990
|
config.settings?.disableProxyTool !== true
|
|
1979
1991
|
|| directSpecs.length === 0
|
|
1980
1992
|
|| hasSearchModeSpecs
|
|
1981
|
-
|| missingConfiguredDirectToolServers.length > 0
|
|
1993
|
+
|| missingConfiguredDirectToolServers.length > 0
|
|
1994
|
+
|| hasEnabledServerWithoutValidMetadata(config, cache, directSpecs);
|
|
1982
1995
|
|
|
1983
1996
|
if (shouldRegisterProxyTool) {
|
|
1984
1997
|
const description = buildProxyDescription(config);
|
|
@@ -2,10 +2,9 @@ import { TypeSafeClient } from "@typesafe-ai/sdk";
|
|
|
2
2
|
import type { McpExtensionState } from "./state.ts";
|
|
3
3
|
import { isServerDisabled } from "./types.ts";
|
|
4
4
|
import { combineAbortSignals } from "./runtime-owner.ts";
|
|
5
|
-
import { resolveJevCredential,
|
|
5
|
+
import { JEV_SDK_PATH, resolveJevCredential, resolveJevEndpoint, type JevCredentialResolution, type ResolvedJevEndpoint } from "./jev-key-store.ts";
|
|
6
6
|
import type { JevAnswer, JevBudget, JevEvaluateInput, JevEvaluationData, JevEvaluationEnvelope, JevJson, JevQuestion, ResolvedJevSettings } from "./jev-contracts.ts";
|
|
7
7
|
|
|
8
|
-
export { TYPESAFE_API_ORIGIN };
|
|
9
8
|
export type { ResolvedJevSettings } from "./jev-contracts.ts";
|
|
10
9
|
|
|
11
10
|
const DEFAULTS: ResolvedJevSettings = {
|
|
@@ -18,18 +17,28 @@ const DEFAULTS: ResolvedJevSettings = {
|
|
|
18
17
|
const DANGEROUS_KEYS = new Set(["__proto__", "constructor", "prototype"]);
|
|
19
18
|
|
|
20
19
|
type JevSettingsInput = Partial<ResolvedJevSettings>;
|
|
21
|
-
type Host = { client: TypeSafeClient };
|
|
20
|
+
type Host = { endpoint: ResolvedJevEndpoint; client: TypeSafeClient };
|
|
21
|
+
type CredentialCacheEntry = { endpoint: string; credential: Extract<JevCredentialResolution, { status: "present" }> };
|
|
22
22
|
const hosts = new WeakMap<McpExtensionState, Host>();
|
|
23
|
-
const credentials = new WeakMap<McpExtensionState,
|
|
23
|
+
const credentials = new WeakMap<McpExtensionState, CredentialCacheEntry>();
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Cached per endpoint so a changed `SYSTEMONE_ENDPOINT` cannot reuse another provider's credential. Callers
|
|
27
|
+
* re-resolve the endpoint on every operation so a switched or newly invalid endpoint takes effect immediately.
|
|
28
|
+
*/
|
|
29
|
+
function resolveCredential(state: McpExtensionState, endpoint: ResolvedJevEndpoint): JevCredentialResolution {
|
|
26
30
|
const existing = credentials.get(state);
|
|
27
|
-
if (existing) return existing;
|
|
28
|
-
const credential = resolveJevCredential();
|
|
29
|
-
if (credential.status === "present") credentials.set(state, credential);
|
|
31
|
+
if (existing && existing.endpoint === endpoint.href) return existing.credential;
|
|
32
|
+
const credential = resolveJevCredential(process.env, endpoint);
|
|
33
|
+
if (credential.status === "present") credentials.set(state, { endpoint: endpoint.href, credential });
|
|
30
34
|
return credential;
|
|
31
35
|
}
|
|
32
36
|
|
|
37
|
+
function credentialForState(state: McpExtensionState): JevCredentialResolution {
|
|
38
|
+
const resolution = resolveJevEndpoint();
|
|
39
|
+
return resolution.status === "unavailable" ? resolution : resolveCredential(state, resolution.endpoint);
|
|
40
|
+
}
|
|
41
|
+
|
|
33
42
|
export function areJevSourcesAllowed(state: McpExtensionState, settings: ResolvedJevSettings, sources: Iterable<string>): boolean {
|
|
34
43
|
for (const source of sources) {
|
|
35
44
|
const server = state.config.mcpServers[source];
|
|
@@ -90,7 +99,7 @@ export function resolveSemanticJevSettings(
|
|
|
90
99
|
const configured = state.config.settings?.jev;
|
|
91
100
|
const settings = validateJevSettings(configured);
|
|
92
101
|
if (configured === false || configured?.semanticSearch === false) return settings;
|
|
93
|
-
const semanticSearch = settings.semanticSearch || (credentialResolver ? credentialResolver() :
|
|
102
|
+
const semanticSearch = settings.semanticSearch || (credentialResolver ? credentialResolver() : credentialForState(state)).status === "present";
|
|
94
103
|
const hasExplicitAllowlist = configured !== undefined && Object.hasOwn(configured, "allowedServers");
|
|
95
104
|
const allowedServers = hasExplicitAllowlist
|
|
96
105
|
? settings.allowedServers
|
|
@@ -219,55 +228,73 @@ function validateResponse(value: unknown, input: JevEvaluateInput): JevEvaluatio
|
|
|
219
228
|
return { answers: validated, model: response.model, usage: { inputTokens: safeNumber(usage.input_tokens, "input tokens", true), outputTokens: safeNumber(usage.output_tokens, "output tokens", true) } };
|
|
220
229
|
}
|
|
221
230
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
231
|
+
/**
|
|
232
|
+
* Pins every request to the configured endpoint: the SDK appends its own fixed path to the base URL, so this
|
|
233
|
+
* rewrites that path onto the configured one and then rejects anything that does not match the endpoint exactly.
|
|
234
|
+
* Redirects stay refused, so a provider cannot bounce a request carrying the API key somewhere else.
|
|
235
|
+
*/
|
|
236
|
+
function createPinnedEndpointFetch(endpoint: ResolvedJevEndpoint): (input: string, init?: RequestInit) => Promise<Response> {
|
|
237
|
+
return (input, init) => {
|
|
238
|
+
// A replacer function keeps `$&`, `$$`, and friends in the configured path literal.
|
|
239
|
+
const target = input.replace(JEV_SDK_PATH, () => endpoint.path);
|
|
240
|
+
const url = new URL(target);
|
|
241
|
+
if (url.origin !== endpoint.origin || url.pathname !== endpoint.path || url.search !== "" || url.hash !== "") {
|
|
242
|
+
throw new Error("Jev request endpoint rejected");
|
|
243
|
+
}
|
|
244
|
+
return fetch(target, { ...init, redirect: "error" });
|
|
245
|
+
};
|
|
226
246
|
}
|
|
227
247
|
function getHost(state: McpExtensionState, settings: ResolvedJevSettings): Host | JevEvaluationEnvelope {
|
|
248
|
+
const resolution = resolveJevEndpoint();
|
|
249
|
+
if (resolution.status === "unavailable") return { ok: false, error: { code: "endpoint_unavailable", message: resolution.message } };
|
|
250
|
+
const { endpoint } = resolution;
|
|
228
251
|
const existing = hosts.get(state);
|
|
229
|
-
if (existing) return existing;
|
|
230
|
-
const credential = resolveCredential(state);
|
|
231
|
-
if (credential.status === "missing") return { ok: false, error: { code: "credential_missing", message: "
|
|
252
|
+
if (existing && existing.endpoint.href === endpoint.href) return existing;
|
|
253
|
+
const credential = resolveCredential(state, endpoint);
|
|
254
|
+
if (credential.status === "missing") return { ok: false, error: { code: "credential_missing", message: "Jev API key is not configured." } };
|
|
232
255
|
if (credential.status === "unavailable") return { ok: false, error: { code: "credential_unavailable", message: credential.message } };
|
|
233
|
-
const host = { client: new TypeSafeClient({ apiKey: credential.apiKey, baseURL:
|
|
256
|
+
const host: Host = { endpoint, client: new TypeSafeClient({ apiKey: credential.apiKey, baseURL: endpoint.origin, defaultModel: settings.model, logLevel: "off", retry: { maxRetries: settings.maxRetries }, timeout: settings.requestTimeoutMs, defaultHeaders: {}, fetch: createPinnedEndpointFetch(endpoint) }) };
|
|
234
257
|
hosts.set(state, host);
|
|
235
258
|
return host;
|
|
236
259
|
}
|
|
237
260
|
function failure(error: unknown, signal: AbortSignal | undefined, timedOut: boolean): JevEvaluationEnvelope {
|
|
238
|
-
if (timedOut) return { ok: false, error: { code: "timeout", message: "
|
|
239
|
-
if (signal?.aborted || (error instanceof Error && error.name === "APIUserAbortError")) return { ok: false, error: { code: "aborted", message: "
|
|
261
|
+
if (timedOut) return { ok: false, error: { code: "timeout", message: "Jev evaluation timed out.", retryable: true } };
|
|
262
|
+
if (signal?.aborted || (error instanceof Error && error.name === "APIUserAbortError")) return { ok: false, error: { code: "aborted", message: "Jev evaluation was aborted." } };
|
|
263
|
+
// Provider and gateway bodies are never echoed; only the status is used to classify the failure.
|
|
240
264
|
const status = typeof error === "object" && error !== null && "status" in error ? (error as { status?: unknown }).status : undefined;
|
|
241
|
-
if (status === 401 || status === 403) return { ok: false, error: { code: "authentication_failed", message: "
|
|
242
|
-
if (status ===
|
|
243
|
-
|
|
244
|
-
if (
|
|
245
|
-
if (status ===
|
|
246
|
-
if (
|
|
247
|
-
return { ok: false, error: { code: "
|
|
265
|
+
if (status === 401 || status === 403) return { ok: false, error: { code: "authentication_failed", message: "Jev authentication failed." } };
|
|
266
|
+
if (status === 402) return { ok: false, error: { code: "payment_required", message: "Jev provider requires payment for this account." } };
|
|
267
|
+
// A configurable endpoint makes a wrong path a configuration error, not a malformed response.
|
|
268
|
+
if (status === 404 || status === 405 || status === 410) return { ok: false, error: { code: "endpoint_unavailable", message: "Jev endpoint was not found; check SYSTEMONE_ENDPOINT." } };
|
|
269
|
+
if (status === 408) return { ok: false, error: { code: "timeout", message: "Jev evaluation timed out.", retryable: true } };
|
|
270
|
+
if (status === 429) return { ok: false, error: { code: "rate_limited", message: "Jev rate limit exceeded.", retryable: true } };
|
|
271
|
+
if (typeof status === "number" && status >= 500) return { ok: false, error: { code: "service_unavailable", message: "Jev service is unavailable.", retryable: true } };
|
|
272
|
+
if (typeof status === "number" && status >= 400) return { ok: false, error: { code: "invalid_request", message: `Jev rejected the evaluation request (HTTP ${status}).` } };
|
|
273
|
+
if (error instanceof Error && (error.name === "APIConnectionError" || error.name === "APITimeoutError")) return { ok: false, error: { code: "service_unavailable", message: "Jev service is unavailable.", retryable: true } };
|
|
274
|
+
return { ok: false, error: { code: "invalid_response", message: "Jev returned an invalid response." } };
|
|
248
275
|
}
|
|
249
276
|
|
|
250
277
|
export async function evaluateJev(state: McpExtensionState, value: JevEvaluateInput, options: { purpose: "script" | "semantic-search"; signal?: AbortSignal; budget?: JevBudget; observedSources?: readonly string[] }): Promise<JevEvaluationEnvelope> {
|
|
251
278
|
let settings: ResolvedJevSettings;
|
|
252
279
|
let input: JevEvaluateInput;
|
|
253
280
|
try { settings = options.purpose === "semantic-search" ? resolveSemanticJevSettings(state) : validateJevSettings(state.config.settings?.jev); input = validateJevEvaluateInput(value, settings); }
|
|
254
|
-
catch { return { ok: false, error: { code: "invalid_request", message: "Invalid
|
|
255
|
-
if ((options.purpose === "script" && !settings.scriptEvaluation) || (options.purpose === "semantic-search" && !settings.semanticSearch)) return { ok: false, error: { code: "disabled", message: "
|
|
281
|
+
catch { return { ok: false, error: { code: "invalid_request", message: "Invalid Jev evaluation request or settings." } }; }
|
|
282
|
+
if ((options.purpose === "script" && !settings.scriptEvaluation) || (options.purpose === "semantic-search" && !settings.semanticSearch)) return { ok: false, error: { code: "disabled", message: "Jev evaluation is disabled." } };
|
|
256
283
|
const sources = new Set([...(input.sources ?? []), ...(options.observedSources ?? [])]);
|
|
257
284
|
if (!areJevSourcesAllowed(state, settings, sources)) return { ok: false, error: { code: "data_policy_denied", message: "Evaluation sources are not allowed by policy." } };
|
|
258
285
|
const bytes = Buffer.byteLength(JSON.stringify(input));
|
|
259
|
-
if (options.budget && !options.budget.consume(bytes)) return { ok: false, error: { code: "budget_exhausted", message: "
|
|
286
|
+
if (options.budget && !options.budget.consume(bytes)) return { ok: false, error: { code: "budget_exhausted", message: "Jev evaluation budget exhausted." } };
|
|
260
287
|
const resolved = getHost(state, settings);
|
|
261
288
|
if (!("client" in resolved)) return resolved;
|
|
262
289
|
const deadline = new AbortController();
|
|
263
290
|
let timedOut = false;
|
|
264
|
-
const timer = setTimeout(() => { timedOut = true; deadline.abort(new Error("
|
|
291
|
+
const timer = setTimeout(() => { timedOut = true; deadline.abort(new Error("Jev evaluation deadline exceeded")); }, settings.requestTimeoutMs);
|
|
265
292
|
const signal = combineAbortSignals(options.signal, state.owner.signal, deadline.signal);
|
|
266
293
|
try {
|
|
267
294
|
const request = { state: input.state, questions: input.questions, model: settings.model };
|
|
268
295
|
const raw = await resolved.client.systemOne(request as Parameters<TypeSafeClient["systemOne"]>[0], { ...(signal ? { signal } : {}), timeout: settings.requestTimeoutMs, retry: { maxRetries: settings.maxRetries } });
|
|
269
296
|
try { return { ok: true, data: validateResponse(raw, input) }; }
|
|
270
|
-
catch { return { ok: false, error: { code: "invalid_response", message: "
|
|
297
|
+
catch { return { ok: false, error: { code: "invalid_response", message: "Jev returned an invalid response." } }; }
|
|
271
298
|
} catch (error) { return failure(error, signal, timedOut); }
|
|
272
299
|
finally { clearTimeout(timer); }
|
|
273
300
|
}
|
|
@@ -25,8 +25,8 @@ export interface JevEvaluationData {
|
|
|
25
25
|
|
|
26
26
|
export type JevErrorCode =
|
|
27
27
|
| "disabled" | "invalid_request" | "data_policy_denied" | "budget_exhausted"
|
|
28
|
-
| "credential_missing" | "credential_unavailable" | "authentication_failed"
|
|
29
|
-
| "timeout" | "aborted" | "rate_limited" | "service_unavailable" | "invalid_response";
|
|
28
|
+
| "endpoint_unavailable" | "credential_missing" | "credential_unavailable" | "authentication_failed"
|
|
29
|
+
| "payment_required" | "timeout" | "aborted" | "rate_limited" | "service_unavailable" | "invalid_response";
|
|
30
30
|
|
|
31
31
|
export type JevEvaluationEnvelope =
|
|
32
32
|
| { ok: true; data: JevEvaluationData }
|
|
@@ -1,20 +1,44 @@
|
|
|
1
1
|
import { createSecureKeyringStore, type SecureKeyringStore } from "./secure-keyring.ts";
|
|
2
2
|
|
|
3
|
+
/** The Jev decisions endpoint used when `SYSTEMONE_ENDPOINT` is not set. */
|
|
4
|
+
export const JEV_DEFAULT_ENDPOINT = "https://api.typesafe.ai/v1/systemone";
|
|
5
|
+
const SYSTEMONE_ENDPOINT_ENV = "SYSTEMONE_ENDPOINT";
|
|
6
|
+
const SYSTEMONE_API_KEY_ENV = "SYSTEMONE_API_KEY";
|
|
7
|
+
/** TypeSafe-issued key: honored only for the default endpoint and never sent anywhere else. */
|
|
8
|
+
const LEGACY_TYPESAFE_API_KEY_ENV = "TYPESAFE_API_KEY";
|
|
9
|
+
/** Origin recorded in version 1 credential records. */
|
|
3
10
|
export const TYPESAFE_API_ORIGIN = "https://api.typesafe.ai";
|
|
4
|
-
|
|
11
|
+
/** Version 1 credential account, still read for the default endpoint. */
|
|
5
12
|
export const JEV_KEYRING_ACCOUNT = "typesafe@sha256(https://api.typesafe.ai)";
|
|
13
|
+
export const JEV_KEYRING_SERVICE = "pi-mcp-adapter.service-key";
|
|
14
|
+
|
|
15
|
+
const MAX_ENDPOINT_LENGTH = 512;
|
|
16
|
+
/** The path `@typesafe-ai/sdk` appends to whatever base URL it is given. */
|
|
17
|
+
export const JEV_SDK_PATH = "/v1/systemone";
|
|
18
|
+
|
|
19
|
+
export interface ResolvedJevEndpoint {
|
|
20
|
+
/** Canonical absolute URL sent to the provider, for example `https://opencode.ai/zen/v1/systemone`. */
|
|
21
|
+
readonly href: string;
|
|
22
|
+
/** URL origin, used as the SDK base URL. */
|
|
23
|
+
readonly origin: string;
|
|
24
|
+
/** URL path, rewritten onto the SDK's fixed path by the pinned fetch. */
|
|
25
|
+
readonly path: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type JevEndpointResolution =
|
|
29
|
+
| { status: "resolved"; source: "environment" | "default"; endpoint: ResolvedJevEndpoint }
|
|
30
|
+
| { status: "unavailable"; message: string };
|
|
6
31
|
|
|
7
32
|
interface StoredJevKey {
|
|
8
|
-
version:
|
|
9
|
-
|
|
10
|
-
origin: typeof TYPESAFE_API_ORIGIN;
|
|
33
|
+
version: 2;
|
|
34
|
+
endpoint: string;
|
|
11
35
|
apiKey: string;
|
|
12
36
|
}
|
|
13
37
|
|
|
14
38
|
export class JevCredentialStoreError extends Error {
|
|
15
39
|
readonly code = "JEV_CREDENTIAL_STORE_UNAVAILABLE";
|
|
16
40
|
constructor(readonly operation: "read" | "write" | "remove", cause: unknown) {
|
|
17
|
-
super(`
|
|
41
|
+
super(`Jev API key secure credential store unavailable during ${operation}. Configure or unlock the OS credential store and retry.`, { cause });
|
|
18
42
|
this.name = "JevCredentialStoreError";
|
|
19
43
|
}
|
|
20
44
|
}
|
|
@@ -26,54 +50,143 @@ export type JevCredentialResolution =
|
|
|
26
50
|
|
|
27
51
|
function validateApiKey(value: unknown): string {
|
|
28
52
|
if (typeof value !== "string" || value.trim().length === 0 || /[\u0000-\u001f\u007f]/.test(value)) {
|
|
29
|
-
throw new Error("
|
|
53
|
+
throw new Error("Jev API key must be a non-empty string without control characters");
|
|
30
54
|
}
|
|
31
55
|
return value;
|
|
32
56
|
}
|
|
33
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Constrains the operator-supplied endpoint to an absolute HTTPS URL without credentials, query, or fragment, so
|
|
60
|
+
* the pinned fetch can match it exactly and a stray value cannot redirect requests or smuggle a query string.
|
|
61
|
+
*/
|
|
62
|
+
function parseEndpoint(raw: string, label: string): ResolvedJevEndpoint {
|
|
63
|
+
if (typeof raw !== "string" || raw.trim().length === 0) throw new Error(`${label} must be a non-empty URL`);
|
|
64
|
+
if (raw.length > MAX_ENDPOINT_LENGTH) throw new Error(`${label} must be at most ${MAX_ENDPOINT_LENGTH} characters`);
|
|
65
|
+
if (/[\u0000-\u001f\u007f\s]/.test(raw)) throw new Error(`${label} must not contain whitespace or control characters`);
|
|
66
|
+
let url: URL;
|
|
67
|
+
try { url = new URL(raw); }
|
|
68
|
+
catch { throw new Error(`${label} must be an absolute URL`); }
|
|
69
|
+
if (url.protocol !== "https:") throw new Error(`${label} must use https`);
|
|
70
|
+
if (url.username !== "" || url.password !== "") throw new Error(`${label} must not embed credentials`);
|
|
71
|
+
if (url.search !== "") throw new Error(`${label} must not include a query string`);
|
|
72
|
+
if (url.hash !== "") throw new Error(`${label} must not include a fragment`);
|
|
73
|
+
if (url.pathname === "" || url.pathname === "/") throw new Error(`${label} must include a path such as ${JEV_SDK_PATH}`);
|
|
74
|
+
return { href: `${url.origin}${url.pathname}`, origin: url.origin, path: url.pathname };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Resolves the Jev endpoint. An invalid `SYSTEMONE_ENDPOINT` resolves to `unavailable` instead of silently falling
|
|
79
|
+
* back to the default: sending an operator's judgment payload to a provider they did not ask for would be a data leak.
|
|
80
|
+
*/
|
|
81
|
+
export function resolveJevEndpoint(env: NodeJS.ProcessEnv = process.env): JevEndpointResolution {
|
|
82
|
+
if (Object.hasOwn(env, SYSTEMONE_ENDPOINT_ENV)) {
|
|
83
|
+
try { return { status: "resolved", source: "environment", endpoint: parseEndpoint(env[SYSTEMONE_ENDPOINT_ENV] as string, SYSTEMONE_ENDPOINT_ENV) }; }
|
|
84
|
+
catch (error) { return { status: "unavailable", message: error instanceof Error ? error.message : `${SYSTEMONE_ENDPOINT_ENV} is invalid.` }; }
|
|
85
|
+
}
|
|
86
|
+
return { status: "resolved", source: "default", endpoint: parseEndpoint(JEV_DEFAULT_ENDPOINT, "the default Jev endpoint") };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Keyring account for an endpoint, so keys for different providers coexist instead of overwriting each other. */
|
|
90
|
+
export function jevKeyringAccount(endpoint: ResolvedJevEndpoint): string {
|
|
91
|
+
return `systemone@sha256(${endpoint.href})`;
|
|
92
|
+
}
|
|
93
|
+
|
|
34
94
|
function store(): SecureKeyringStore {
|
|
35
95
|
return createSecureKeyringStore(JEV_KEYRING_SERVICE);
|
|
36
96
|
}
|
|
37
97
|
|
|
38
|
-
function
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
98
|
+
function requireEndpoint(endpoint: ResolvedJevEndpoint | undefined): ResolvedJevEndpoint {
|
|
99
|
+
if (endpoint) return endpoint;
|
|
100
|
+
const resolution = resolveJevEndpoint();
|
|
101
|
+
if (resolution.status === "unavailable") throw new Error(resolution.message);
|
|
102
|
+
return resolution.endpoint;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Version 1 records were written before the endpoint was configurable, so they are accepted only for the default
|
|
107
|
+
* endpoint.
|
|
108
|
+
*/
|
|
109
|
+
function parseStoredKey(payload: string, endpoint: ResolvedJevEndpoint): string {
|
|
110
|
+
let value: unknown;
|
|
111
|
+
try { value = JSON.parse(payload); }
|
|
112
|
+
catch { throw new Error("invalid record"); }
|
|
113
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("invalid record");
|
|
114
|
+
const record = value as Record<string, unknown>;
|
|
115
|
+
const keys = Object.keys(record);
|
|
116
|
+
if (record.version === 2) {
|
|
117
|
+
if (keys.length !== 3 || !["version", "endpoint", "apiKey"].every(key => Object.hasOwn(record, key))) throw new Error("invalid record fields");
|
|
118
|
+
if (record.endpoint !== endpoint.href) throw new Error("invalid or mismatched record");
|
|
119
|
+
return validateApiKey(record.apiKey);
|
|
120
|
+
}
|
|
121
|
+
if (record.version === 1) {
|
|
48
122
|
if (keys.length !== 4 || !["version", "provider", "origin", "apiKey"].every(key => Object.hasOwn(record, key))) throw new Error("invalid record fields");
|
|
49
|
-
if (record.
|
|
123
|
+
if (record.provider !== "typesafe" || record.origin !== TYPESAFE_API_ORIGIN) throw new Error("invalid or mismatched record");
|
|
124
|
+
if (endpoint.href !== JEV_DEFAULT_ENDPOINT) throw new Error("invalid or mismatched record");
|
|
50
125
|
return validateApiKey(record.apiKey);
|
|
126
|
+
}
|
|
127
|
+
throw new Error("invalid record version");
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function readStoredKey(secretStore: SecureKeyringStore, endpoint: ResolvedJevEndpoint): string | undefined {
|
|
131
|
+
try {
|
|
132
|
+
let payload = secretStore.read(jevKeyringAccount(endpoint));
|
|
133
|
+
if (payload === undefined && endpoint.href === JEV_DEFAULT_ENDPOINT) payload = secretStore.read(JEV_KEYRING_ACCOUNT);
|
|
134
|
+
return payload === undefined ? undefined : parseStoredKey(payload, endpoint);
|
|
51
135
|
} catch (error) {
|
|
52
136
|
throw new JevCredentialStoreError("read", error);
|
|
53
137
|
}
|
|
54
138
|
}
|
|
55
139
|
|
|
56
|
-
export function resolveJevCredential(
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
140
|
+
export function resolveJevCredential(
|
|
141
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
142
|
+
endpoint?: ResolvedJevEndpoint,
|
|
143
|
+
secretStore: SecureKeyringStore = store(),
|
|
144
|
+
): JevCredentialResolution {
|
|
145
|
+
let target: ResolvedJevEndpoint;
|
|
146
|
+
if (endpoint) target = endpoint;
|
|
147
|
+
else {
|
|
148
|
+
const resolution = resolveJevEndpoint(env);
|
|
149
|
+
if (resolution.status === "unavailable") return resolution;
|
|
150
|
+
target = resolution.endpoint;
|
|
60
151
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
152
|
+
if (Object.hasOwn(env, SYSTEMONE_API_KEY_ENV)) {
|
|
153
|
+
try { return { status: "present", source: "environment", apiKey: validateApiKey(env[SYSTEMONE_API_KEY_ENV]) }; }
|
|
154
|
+
catch { return { status: "unavailable", message: `${SYSTEMONE_API_KEY_ENV} is present but invalid.` }; }
|
|
155
|
+
}
|
|
156
|
+
const legacySet = Object.hasOwn(env, LEGACY_TYPESAFE_API_KEY_ENV);
|
|
157
|
+
if (legacySet && target.href === JEV_DEFAULT_ENDPOINT) {
|
|
158
|
+
try { return { status: "present", source: "environment", apiKey: validateApiKey(env[LEGACY_TYPESAFE_API_KEY_ENV]) }; }
|
|
159
|
+
catch { return { status: "unavailable", message: `${LEGACY_TYPESAFE_API_KEY_ENV} is present but invalid.` }; }
|
|
160
|
+
}
|
|
161
|
+
let stored: string | undefined;
|
|
162
|
+
try { stored = readStoredKey(secretStore, target); }
|
|
163
|
+
catch (error) {
|
|
65
164
|
if (!(error instanceof JevCredentialStoreError)) throw error;
|
|
66
165
|
return { status: "unavailable", message: error.message };
|
|
67
166
|
}
|
|
167
|
+
if (stored !== undefined) return { status: "present", source: "keyring", apiKey: stored };
|
|
168
|
+
// A TypeSafe-issued credential is never sent to another endpoint, but it must not mask a credential stored for
|
|
169
|
+
// this endpoint either, so it is only reported once nothing for this endpoint resolves.
|
|
170
|
+
if (legacySet) {
|
|
171
|
+
return {
|
|
172
|
+
status: "unavailable",
|
|
173
|
+
message: `${LEGACY_TYPESAFE_API_KEY_ENV} is a TypeSafe credential and is not sent to ${target.href}; set ${SYSTEMONE_API_KEY_ENV} for that endpoint.`,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
return { status: "missing" };
|
|
68
177
|
}
|
|
69
178
|
|
|
70
|
-
export function saveJevApiKey(apiKey: string, secretStore: SecureKeyringStore = store()): void {
|
|
71
|
-
const
|
|
72
|
-
|
|
179
|
+
export function saveJevApiKey(apiKey: string, endpoint?: ResolvedJevEndpoint, secretStore: SecureKeyringStore = store()): void {
|
|
180
|
+
const target = requireEndpoint(endpoint);
|
|
181
|
+
const record: StoredJevKey = { version: 2, endpoint: target.href, apiKey: validateApiKey(apiKey) };
|
|
182
|
+
try { secretStore.write(jevKeyringAccount(target), JSON.stringify(record)); }
|
|
73
183
|
catch (error) { throw new JevCredentialStoreError("write", error); }
|
|
74
184
|
}
|
|
75
185
|
|
|
76
|
-
export function removeJevApiKey(secretStore: SecureKeyringStore = store()): void {
|
|
77
|
-
|
|
78
|
-
|
|
186
|
+
export function removeJevApiKey(endpoint?: ResolvedJevEndpoint, secretStore: SecureKeyringStore = store()): void {
|
|
187
|
+
const target = requireEndpoint(endpoint);
|
|
188
|
+
try {
|
|
189
|
+
secretStore.remove(jevKeyringAccount(target));
|
|
190
|
+
if (target.href === JEV_DEFAULT_ENDPOINT) secretStore.remove(JEV_KEYRING_ACCOUNT);
|
|
191
|
+
} catch (error) { throw new JevCredentialStoreError("remove", error); }
|
|
79
192
|
}
|
|
@@ -213,7 +213,7 @@ export async function runMcpScript(
|
|
|
213
213
|
let evaluationAttempts = 0;
|
|
214
214
|
let evaluationBytes = 0;
|
|
215
215
|
let evaluationTokensRemaining = jevSettings.maxEvaluationTokensPerScript;
|
|
216
|
-
const tokenBudgetExhausted = (): JevEvaluationEnvelope => ({ ok: false, error: { code: "budget_exhausted", message: "
|
|
216
|
+
const tokenBudgetExhausted = (): JevEvaluationEnvelope => ({ ok: false, error: { code: "budget_exhausted", message: "Jev evaluation token budget exhausted." } });
|
|
217
217
|
const chargeEvaluationTokens = (envelope: JevEvaluationEnvelope): JevEvaluationEnvelope => {
|
|
218
218
|
if (!envelope.ok) return envelope;
|
|
219
219
|
const used = envelope.data.usage.inputTokens + envelope.data.usage.outputTokens;
|
|
@@ -226,16 +226,16 @@ export async function runMcpScript(
|
|
|
226
226
|
};
|
|
227
227
|
const admitEvaluation = (input: unknown): JevEvaluationEnvelope | undefined => {
|
|
228
228
|
if (++evaluationAttempts > jevSettings.maxEvaluationsPerScript) {
|
|
229
|
-
return { ok: false, error: { code: "budget_exhausted", message: "
|
|
229
|
+
return { ok: false, error: { code: "budget_exhausted", message: "Jev evaluation count budget exhausted." } };
|
|
230
230
|
}
|
|
231
231
|
if (evaluationTokensRemaining === 0) return tokenBudgetExhausted();
|
|
232
232
|
let serialized: string | undefined;
|
|
233
233
|
try { serialized = JSON.stringify(input); }
|
|
234
|
-
catch { return { ok: false, error: { code: "invalid_request", message: "Invalid
|
|
235
|
-
if (serialized === undefined) return { ok: false, error: { code: "invalid_request", message: "Invalid
|
|
234
|
+
catch { return { ok: false, error: { code: "invalid_request", message: "Invalid Jev evaluation request." } }; }
|
|
235
|
+
if (serialized === undefined) return { ok: false, error: { code: "invalid_request", message: "Invalid Jev evaluation request." } };
|
|
236
236
|
const bytes = Buffer.byteLength(serialized, "utf8");
|
|
237
237
|
if (bytes > jevSettings.maxEvaluationBytesPerScript - evaluationBytes) {
|
|
238
|
-
return { ok: false, error: { code: "budget_exhausted", message: "
|
|
238
|
+
return { ok: false, error: { code: "budget_exhausted", message: "Jev evaluation byte budget exhausted." } };
|
|
239
239
|
}
|
|
240
240
|
evaluationBytes += bytes;
|
|
241
241
|
return undefined;
|
|
@@ -257,7 +257,7 @@ export async function runMcpScript(
|
|
|
257
257
|
envelope = chargeEvaluationTokens(envelope);
|
|
258
258
|
throwIfAborted(callSignal);
|
|
259
259
|
if (!reserveIntermediateBytes(JSON.stringify(envelope))) {
|
|
260
|
-
envelope = { ok: false, error: { code: "budget_exhausted", message: "
|
|
260
|
+
envelope = { ok: false, error: { code: "budget_exhausted", message: "Jev evaluation exceeds the remaining mcpScript intermediate transfer budget (16 MiB per script)." } };
|
|
261
261
|
}
|
|
262
262
|
calls[index] = envelope.ok
|
|
263
263
|
? {
|