@relvo/mcp 0.0.0-stage → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,28 @@
1
+ import { serveStdio } from "@modelcontextprotocol/server/stdio";
2
+ import { configFromEnvironment, PlatformClient } from "../client/index.js";
3
+ import { createPlatformMcp } from "./server.js";
4
+ import { runtimeConfig } from "../client/local-profile.js";
5
+ export async function startPlatformMcp(version = "0.1.2", profile) {
6
+ let handle;
7
+ try {
8
+ const client = new PlatformClient(profile ? await runtimeConfig(process.env, "mcp", profile, true) : configFromEnvironment(process.env, "mcp"));
9
+ handle = serveStdio(() => createPlatformMcp(client, version, true, Boolean(profile)), {
10
+ onerror: () => {
11
+ process.stderr.write(JSON.stringify({ severity: "ERROR", message: "Relvo MCP protocol error." }) + "\n");
12
+ },
13
+ });
14
+ const shutdown = async () => {
15
+ await handle?.close();
16
+ process.exit(0);
17
+ };
18
+ process.on("SIGINT", shutdown);
19
+ process.on("SIGTERM", shutdown);
20
+ }
21
+ catch {
22
+ process.stderr.write(JSON.stringify({
23
+ severity: "ERROR",
24
+ message: "Relvo MCP could not start. Check RELVO_API_URL and RELVO_API_KEY.",
25
+ }) + "\n");
26
+ process.exitCode = 1;
27
+ }
28
+ }
@@ -0,0 +1,179 @@
1
+ import { McpServer } from "@modelcontextprotocol/server";
2
+ import { fitToolResultPage, executionReceiptContext, executionPageContext, hasRelvoEnvironment, relvoEnvironmentValue, platformToolInputSchema, platformCreateExecutionSchema, platformExecutionIdSchema, platformArtifactIdSchema, platformExecutionListQuerySchema, toolDiscoveryInputSchema, platformSkillSchema, toolResultSelectionSchema, platformConnectionCatalogQuerySchema } from "../client/index.js";
3
+ import { z } from "zod";
4
+ import { uploadAuthorizedFile } from "./local-upload.js";
5
+ import { platformMetaSearchSchema, platformMetaSchemasSchema, platformMetaConnectionsSchema, platformMetaExecuteSchema, platformMetaWorkbenchSchema, platformMetaBashSchema } from "../client/index.js";
6
+ import { boundedMcpResult, inspectJsonContext, mcpContextFits, MCP_RESULT_MAX_BYTES } from "./context.js";
7
+ import { RELVO_MCP_INSTRUCTIONS } from "./instructions.js";
8
+ const projectionOptions = { maximumBytes: Math.floor(MCP_RESULT_MAX_BYTES / 4), readWith: "platform_result" };
9
+ const jsonObject = z.record(z.string(), z.json());
10
+ const inspectInput = { ...toolResultSelectionSchema.omit({ fields: true }).shape,
11
+ path: toolResultSelectionSchema.shape.path.describe("Own JSON keys in the definition or guide, e.g. [\"inputSchema\",\"properties\"]. Follow entry paths for omitted subtrees.") };
12
+ const summary = (tool) => {
13
+ const { inputSchema: _schema, example: _example, models: _models, ...metadata } = tool;
14
+ return metadata;
15
+ };
16
+ export function createPlatformMcp(client, version = "0.1.2", localFilesystem = false, managedProfile = false) {
17
+ const relvoCredentials = managedProfile || hasRelvoEnvironment(process.env);
18
+ const uploadRoots = relvoEnvironmentValue(process.env, "RELVO_MCP_ALLOWED_UPLOAD_ROOTS") ?? (relvoCredentials ? undefined : process.env.NOIMOS_MCP_ALLOWED_UPLOAD_ROOTS);
19
+ const server = new McpServer({ name: "relvo", version }, {
20
+ instructions: RELVO_MCP_INSTRUCTIONS,
21
+ });
22
+ server.registerTool("platform_search_tools", { description: "Search up to four independent capabilities and open/continue a project tool session. Retain session.id for batches or remote processing. Free; omit private data from queries.",
23
+ inputSchema: platformMetaSearchSchema, annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: true, idempotentHint: false } }, input => respond(() => client.searchMetaTools(input), undefined, (value, fits) => {
24
+ const results = value.data.results.map(result => ({ ...result, tools: result.tools.slice(0, 0) }));
25
+ const response = () => ({ data: { ...value.data, results: results.map((result, index) => ({ ...result,
26
+ discovery: { ...result.discovery, returnedCount: result.tools.length, truncated: result.discovery.truncated || result.tools.length < value.data.results[index].tools.length } })) } });
27
+ for (let rank = 0; rank < Math.max(...value.data.results.map(result => result.tools.length)); rank++) {
28
+ for (const [index, result] of results.entries()) {
29
+ const tool = value.data.results[index].tools[rank];
30
+ if (!tool)
31
+ continue;
32
+ result.tools.push(summary(tool));
33
+ if (!fits(response()))
34
+ result.tools.pop();
35
+ }
36
+ }
37
+ return response();
38
+ }));
39
+ server.registerTool("platform_get_tool_schemas", { description: "Read selected schemas, examples and prices in a session. Inspect remaining names with platform_tool; large definitions provide exact paths/pages. Free.",
40
+ inputSchema: platformMetaSchemasSchema, annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false, idempotentHint: true } }, input => respond(() => client.getToolSchemas(input), undefined, (value, fits) => {
41
+ const data = { ...value.data, tools: [...value.data.tools], remaining: [...value.data.remaining] };
42
+ while (data.tools.length && !fits({ data })) {
43
+ const tool = data.tools.pop();
44
+ if (tool)
45
+ data.remaining.unshift(tool.name);
46
+ }
47
+ return { data };
48
+ }));
49
+ server.registerTool("platform_manage_connections", { description: "Check OAuth accounts required by a selected tool. Reuse the intended connected account; request console consent only for a missing/expired connection. Free read; no account changes.",
50
+ inputSchema: platformMetaConnectionsSchema, annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: true, idempotentHint: true } }, input => respond(() => client.manageConnections(input), undefined, (value, fits) => {
51
+ const data = { ...value.data, connections: [...value.data.connections] };
52
+ while (data.connections.length && !fits({ data }))
53
+ data.connections.pop();
54
+ return { data };
55
+ }));
56
+ server.registerTool("platform_multi_execute", { description: "Submit independent quoted calls within an accepted total cap. Save the complete request and per-item keys first. Read every admission even on error; wait for accepted IDs. Compact receipts use index to identify original calls. Dependencies require a later batch.",
57
+ inputSchema: platformMetaExecuteSchema, annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true, idempotentHint: true } }, async (input) => {
58
+ let allAccepted = false;
59
+ const result = await respond(async () => { const value = await client.multiExecute(input); allAccepted = value.data.allAccepted; return value; }, undefined, (value, fits) => {
60
+ const data = { ...value.data, results: value.data.results.map(({ tool: _tool, idempotencyKey: _key, ...receipt }) => receipt),
61
+ requestFieldsOmitted: ["tool", "idempotencyKey"],
62
+ instruction: "Match each index to the saved original request for its tool/key. Wait/read accepted IDs for status and charges. Recover unknown admission with the identical request and keys; never replace accepted work." };
63
+ if (fits({ data }))
64
+ return { data };
65
+ return { data: { ...data, results: data.results.map(({ index, admission, executionId, errorCode }) => ({ index, admission, executionId, errorCode })),
66
+ executionFieldsOmitted: ["status", "reservedMicros"] } };
67
+ });
68
+ return allAccepted ? result : { ...result, isError: true };
69
+ });
70
+ server.registerTool("platform_remote_workbench", { description: "Submit a quoted Python cell to this session's remote sandbox. Stage full prior outputs with sourceExecutionIds. Wait for success and a ready session before the next cell. Files/checkpointable variables persist; no credentials or authenticated tool proxy.",
71
+ inputSchema: platformMetaWorkbenchSchema, annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true, idempotentHint: true } }, input => respond(() => client.remoteWorkbench(input), "platform_remote_workbench", value => ({ data: { ...value.data,
72
+ execution: executionReceiptContext({ data: value.data.execution }, projectionOptions).data } })));
73
+ server.registerTool("platform_remote_bash", { description: "Submit a quoted remote Bash cell. Shares files with Python and retains its own checkpointed shell state. Wait for success and a ready session before another cell. Deployment controls network; never runs on the user's machine.",
74
+ inputSchema: platformMetaBashSchema, annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true, idempotentHint: true } }, input => respond(() => client.remoteBash(input), "platform_remote_bash", value => ({ data: { ...value.data,
75
+ execution: executionReceiptContext({ data: value.data.execution }, projectionOptions).data } })));
76
+ const respond = async (operation, untrustedTool, compact) => {
77
+ try {
78
+ return boundedMcpResult(await operation(), untrustedTool, compact);
79
+ }
80
+ catch (error) {
81
+ return { isError: true, content: [{ type: "text", text: error instanceof Error ? error.message : "Relvo request failed." }] };
82
+ }
83
+ };
84
+ if (localFilesystem)
85
+ server.registerTool("platform_upload_file", {
86
+ description: "Upload an authorized media file under configured RELVO_MCP_ALLOWED_UPLOAD_ROOTS, up to 12 MiB. A request to use that exact file authorizes upload. Never upload credentials/configs or broaden roots. Returns data.id for documented fileId inputs.",
87
+ inputSchema: z.object({ path: z.string().min(1).max(4096), confirmedByUser: z.literal(true).describe("True only when the user has authorized uploading this exact file.") }).strict(),
88
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true, idempotentHint: true },
89
+ }, ({ path }) => respond(() => uploadAuthorizedFile(client, path, uploadRoots)));
90
+ for (const [name, description, operation] of [
91
+ ["platform_project", "Read the developer organization and project bound to this API key.", () => client.project()],
92
+ ["platform_usage", "Read this developer project's current monthly requests and spend in USD.", () => client.usage()],
93
+ ]) {
94
+ server.registerTool(name, { description, inputSchema: z.object({}), annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false, idempotentHint: true } }, async () => {
95
+ return respond(async () => operation());
96
+ });
97
+ }
98
+ server.registerTool("platform_connections", {
99
+ description: "Page project OAuth IDs and account labels for a tool's connectionId. Follow nextOffset. No tokens or account changes. Empty scopes do not establish lack of access; reauth_required needs console consent.",
100
+ inputSchema: platformConnectionCatalogQuerySchema,
101
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false, idempotentHint: true },
102
+ }, ({ provider, offset, limit }) => respond(async () => {
103
+ const catalog = (await client.connections({ provider, offset, limit })).data;
104
+ return { data: catalog };
105
+ }, undefined, (value, fits) => {
106
+ const connections = [...value.data.connections];
107
+ const page = () => ({ data: { ...value.data, connections, nextOffset: offset + connections.length } });
108
+ while (connections.length && !fits(page()))
109
+ connections.pop();
110
+ if (!connections.length && value.data.connections.length)
111
+ throw new Error("This connection's metadata exceeds the context budget. Inspect it in the Relvo console.");
112
+ return page();
113
+ }));
114
+ server.registerTool("platform_tools", { description: "Find a capability or exact tool/model name; filter quotes, funding, unit, category or provider. summary:false with limit:1 can include its schema; reuse it, inspecting omitted definitions with platform_tool. Example quotes need actual-input requoting. Free. No match does not imply missing OAuth; provider does not pin execution.",
115
+ inputSchema: toolDiscoveryInputSchema, annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false, idempotentHint: true } }, input => respond(() => client.searchTools(input), undefined, (value, fits) => {
116
+ const tools = value.data.tools.map(summary);
117
+ const response = () => ({ data: { ...value.data, tools, discovery: { ...value.data.discovery, returnedCount: tools.length, truncated: true } } });
118
+ while (tools.length && !fits(response()))
119
+ tools.pop();
120
+ return response();
121
+ }));
122
+ server.registerTool("platform_quote", { description: "Quote exact tool arguments, optional provider and session without executing. Auto is default. Retain priceVersion, maximumChargeMicros and the unchanged selection for submission within the user's budget.",
123
+ inputSchema: platformToolInputSchema, annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false, idempotentHint: true } }, (input) => respond(() => client.quote(input)));
124
+ server.registerTool("platform_execute", { description: "Enqueue an authorized paid call using its accepted quote and a saved idempotencyKey. Auto is default. Wait on the returned ID. Retry uncertain admission with identical input/key to recover the same purchase.",
125
+ inputSchema: platformCreateExecutionSchema.extend({ idempotencyKey: z.string().regex(/^[A-Za-z0-9._:-]{8,128}$/) }),
126
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true, idempotentHint: true } }, ({ idempotencyKey, ...input }) => respond(async () => executionReceiptContext(await client.createExecution(input, idempotencyKey)), "platform_execute", value => executionReceiptContext(value, projectionOptions)));
127
+ server.registerTool("platform_execution", { description: "Read status, charges and a bounded result preview. Use platform_result for offloaded content. Pending zero charges are not final. needs_review requires operator reconciliation, not replacement work.",
128
+ inputSchema: z.object({ id: platformExecutionIdSchema }).strict(),
129
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: true, idempotentHint: true } }, ({ id }) => respond(() => client.executionContext(id), "platform_execution", value => executionReceiptContext(value, projectionOptions)));
130
+ server.registerTool("platform_result", { description: "Read saved results without regenerating. Select a path, page and optional row fields. Pages fit the response budget automatically; follow nextOffset. An offloaded single value needs a narrower path. Process bulk data in an available remote sandbox.",
131
+ inputSchema: z.object({ id: platformExecutionIdSchema, ...toolResultSelectionSchema.shape }).strict(),
132
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: true, idempotentHint: true } }, ({ id, ...selection }) => respond(() => client.readExecutionResult(id, selection), "platform_result", (value, fits) => {
133
+ const fitted = { data: { ...value.data, page: fitToolResultPage(value.data.page, page => fits({ data: { ...value.data, page } })) } };
134
+ return fits(fitted) ? fitted : executionPageContext(value, projectionOptions);
135
+ }));
136
+ server.registerTool("platform_cancel", { description: "Request cancellation of this execution's unstarted work. Already incurred provider costs may still be charged. Requires ownership of the execution or project ADMIN access.",
137
+ inputSchema: z.object({ id: platformExecutionIdSchema }).strict(),
138
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: true, idempotentHint: true } }, ({ id }) => respond(async () => executionReceiptContext(await client.cancelExecution(id)), "platform_cancel", value => executionReceiptContext(value, projectionOptions)));
139
+ server.registerTool("platform_artifact_url", { description: "Get a completed execution's private output download URL, including generated media and downloaded social videos, valid for at most 5 minutes. Use execution and artifact IDs from platform_execution. Treat the URL as temporary access to private data; share it only within the caller's authorized scope.",
140
+ inputSchema: z.object({ id: platformExecutionIdSchema, artifactId: platformArtifactIdSchema }).strict(),
141
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false, idempotentHint: true } }, ({ id, artifactId }) => respond(() => client.artifactDownload(id, artifactId)));
142
+ server.registerTool("platform_tool", { description: "Inspect one discovered tool's schema, example and price. Small definitions are complete; large ones return page.entries with exact paths. Read entries without value at their path; follow nextOffset for siblings. Free.",
143
+ inputSchema: z.object({ name: z.string().regex(/^[a-z][a-z0-9_]{1,63}$/).describe("Exact discovered tool name."), ...inspectInput }).strict(),
144
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false, idempotentHint: true } }, ({ name, ...selection }) => respond(async () => {
145
+ const value = await client.inspectTool(name);
146
+ return inspectJsonContext(name, jsonObject.parse(value.data), selection, mcpContextFits);
147
+ }));
148
+ server.registerTool("platform_executions", { description: "Recover prior project jobs, newest first. Follow nextBefore; summaries omit output. Inspect a known execution ID instead of purchasing replacement work. Free.",
149
+ inputSchema: platformExecutionListQuerySchema,
150
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false, idempotentHint: true } }, input => respond(() => client.executions(input), undefined, (value, fits) => {
151
+ const executions = [...value.data.executions];
152
+ const page = () => ({ data: { executions, nextBefore: executions.at(-1)?.id ?? value.data.nextBefore } });
153
+ while (executions.length && !fits(page()))
154
+ executions.pop();
155
+ if (!executions.length && value.data.executions.length)
156
+ throw new Error("Execution list metadata exceeds the context budget. Inspect a known execution ID.");
157
+ return page();
158
+ }));
159
+ server.registerTool("platform_wait", { description: "Wait for an existing job, up to 25 seconds per call. timedOut leaves it running: wait again on the same ID. Terminal failures include status/charges and isError. Free; never submits or cancels.",
160
+ inputSchema: z.object({ id: platformExecutionIdSchema.describe("Execution ID from platform_execute or platform_executions."),
161
+ timeoutSeconds: z.number().int().min(1).max(25).default(20).describe("Wait limit, 1–25 seconds; accepted work continues afterwards.") }).strict(),
162
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: true, idempotentHint: true } }, async ({ id, timeoutSeconds }, { mcpReq }) => {
163
+ let failed = false;
164
+ const result = await respond(async () => {
165
+ const outcome = await client.waitExecutionContext(id, timeoutSeconds * 1000, mcpReq.signal);
166
+ failed = !outcome.timedOut && outcome.data?.status !== "succeeded";
167
+ return outcome;
168
+ }, "platform_wait", value => ({ ...value, data: value.data ? executionReceiptContext({ data: value.data }, projectionOptions).data : null }));
169
+ return failed ? { ...result, isError: true } : result;
170
+ });
171
+ server.registerTool("platform_skills", { description: "List current first-party guide summaries. Inspect only a needed guide with platform_skill. Guides do not grant permissions or budget. Oversized full lists return summaries.",
172
+ inputSchema: z.object({ summary: z.boolean().default(true) }).strict(), annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false, idempotentHint: true } }, ({ summary }) => respond(async () => { const { data } = await client.skills(); return { data: { version: data.version, skills: summary ? data.skills.map(({ content, ...skill }) => skill) : data.skills } }; }, undefined, value => ({ data: { ...value.data, skills: value.data.skills.map(skill => ({ name: skill.name, description: skill.description, sha256: skill.sha256 })), offloaded: true, readWith: "platform_skill" } })));
173
+ server.registerTool("platform_skill", { description: "Read one needed first-party guide. Large guides return paths/pages like platform_tool. Current endpoint names come from discovery; guides do not authorize paid work.",
174
+ inputSchema: z.object({ name: platformSkillSchema.shape.name, ...inspectInput }).strict(), annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false, idempotentHint: true } }, ({ name, ...selection }) => respond(async () => {
175
+ const value = await client.skill(name);
176
+ return inspectJsonContext(name, jsonObject.parse(value.data), selection, mcpContextFits);
177
+ }));
178
+ return server;
179
+ }
@@ -0,0 +1,13 @@
1
+ import { z } from "zod";
2
+ export const amazonAdsRegionSchema = z.enum(["na", "eu", "fe"]);
3
+ export const amazonAdsProfileIdSchema = z.string().regex(/^[1-9][0-9]{0,18}$/)
4
+ .pipe(z.string().refine(value => BigInt(value) <= 9223372036854775807n));
5
+ export const amazonAdsProfileResourceIdSchema = z.string().regex(/^(?:na|eu|fe):[1-9][0-9]{0,18}$/)
6
+ .pipe(z.string().refine(value => BigInt(value.slice(3)) <= 9223372036854775807n));
7
+ export function amazonAdsProfileResourceId(region, profileId) {
8
+ return amazonAdsProfileResourceIdSchema.parse(`${region}:${profileId}`);
9
+ }
10
+ export function parseAmazonAdsProfileResourceId(input) {
11
+ const [region, profileId] = amazonAdsProfileResourceIdSchema.parse(input).split(":");
12
+ return z.object({ region: amazonAdsRegionSchema, profileId: amazonAdsProfileIdSchema }).parse({ region, profileId });
13
+ }
@@ -0,0 +1,30 @@
1
+ export async function readBoundedBody(message, maximumBytes) {
2
+ const reader = message.body?.getReader();
3
+ if (!reader)
4
+ return { status: "missing" };
5
+ const chunks = [];
6
+ let size = 0;
7
+ try {
8
+ for (;;) {
9
+ const { value, done } = await reader.read();
10
+ if (done)
11
+ break;
12
+ size += value.byteLength;
13
+ if (size > maximumBytes) {
14
+ await reader.cancel();
15
+ return { status: "too_large" };
16
+ }
17
+ chunks.push(value);
18
+ }
19
+ }
20
+ finally {
21
+ reader.releaseLock();
22
+ }
23
+ const bytes = new Uint8Array(size);
24
+ let offset = 0;
25
+ for (const chunk of chunks) {
26
+ bytes.set(chunk, offset);
27
+ offset += chunk.byteLength;
28
+ }
29
+ return { status: "ok", bytes };
30
+ }
@@ -0,0 +1,64 @@
1
+ import { z } from "zod";
2
+ export const oauth2ProviderSchema = z.enum(["google", "x", "github", "notion", "slack", "linear", "linkedin", "pinterest", "tiktok", "microsoft", "amazon"]);
3
+ export const AMAZON_OAUTH_AUTHORIZATION_URLS = { na: "https://www.amazon.com/ap/oa", eu: "https://eu.account.amazon.com/ap/oa", fe: "https://apac.account.amazon.com/ap/oa" };
4
+ export const oauth2TokenResponseSchema = z.object({
5
+ access_token: z.string().min(1),
6
+ refresh_token: z.string().min(1).optional(),
7
+ expires_in: z.number().positive().finite().optional(),
8
+ scope: z.string().optional(),
9
+ token_type: z.string().optional(),
10
+ error: z.never().optional(),
11
+ });
12
+ export const oauth2ExpiringTokenResponseSchema = oauth2TokenResponseSchema.extend({
13
+ expires_in: oauth2TokenResponseSchema.shape.expires_in.unwrap(),
14
+ });
15
+ export const oauth2ErrorResponseSchema = z.object({
16
+ error: z.string().min(1), error_description: z.string().optional(), log_id: z.string().optional(),
17
+ });
18
+ export const OAUTH2_PROVIDERS = {
19
+ google: { name: "Google", authorizeUrl: "https://accounts.google.com/o/oauth2/v2/auth", tokenUrl: "https://oauth2.googleapis.com/token", clientAuth: "body", encoding: "form", scopeSeparator: " ", pkce: true,
20
+ documentation: "https://developers.google.com/identity/protocols/oauth2/web-server" },
21
+ x: { name: "X", authorizeUrl: "https://x.com/i/oauth2/authorize", tokenUrl: "https://api.x.com/2/oauth2/token", clientAuth: "basic", encoding: "form", scopeSeparator: " ", pkce: true,
22
+ documentation: "https://docs.x.com/fundamentals/authentication/oauth-2-0/authorization-code" },
23
+ github: { name: "GitHub", authorizeUrl: "https://github.com/login/oauth/authorize", tokenUrl: "https://github.com/login/oauth/access_token", clientAuth: "body", encoding: "json", scopeSeparator: " ", pkce: true,
24
+ documentation: "https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps" },
25
+ notion: { name: "Notion", authorizeUrl: "https://api.notion.com/v1/oauth/authorize", tokenUrl: "https://api.notion.com/v1/oauth/token", clientAuth: "basic", encoding: "json", scopeSeparator: " ", pkce: false,
26
+ documentation: "https://developers.notion.com/guides/get-started/authorization" },
27
+ slack: { name: "Slack", authorizeUrl: "https://slack.com/oauth/v2/authorize", tokenUrl: "https://slack.com/api/oauth.v2.access", clientAuth: "basic", encoding: "form", scopeSeparator: ",", pkce: false,
28
+ documentation: "https://docs.slack.dev/authentication/installing-with-oauth/" },
29
+ linear: { name: "Linear", authorizeUrl: "https://linear.app/oauth/authorize", tokenUrl: "https://api.linear.app/oauth/token", clientAuth: "body", encoding: "form", scopeSeparator: ",", pkce: true,
30
+ documentation: "https://linear.app/developers/oauth-2-0-authentication" },
31
+ linkedin: { name: "LinkedIn", authorizeUrl: "https://www.linkedin.com/oauth/v2/authorization", tokenUrl: "https://www.linkedin.com/oauth/v2/accessToken", clientAuth: "body", encoding: "form", scopeSeparator: " ", pkce: false,
32
+ documentation: "https://learn.microsoft.com/en-us/linkedin/shared/authentication/authorization-code-flow" },
33
+ pinterest: { name: "Pinterest", authorizeUrl: "https://www.pinterest.com/oauth/", tokenUrl: "https://api.pinterest.com/v5/oauth/token", clientAuth: "basic", encoding: "form", scopeSeparator: ",", pkce: false,
34
+ documentation: "https://developers.pinterest.com/docs/getting-started/set-up-authentication-and-authorization/" },
35
+ tiktok: { name: "TikTok", authorizeUrl: "https://www.tiktok.com/v2/auth/authorize/", tokenUrl: "https://open.tiktokapis.com/v2/oauth/token/", clientAuth: "body", encoding: "form", scopeSeparator: ",", pkce: false,
36
+ documentation: "https://developers.tiktok.com/docs/en/oauth-user-access-token-management" },
37
+ microsoft: { name: "Microsoft Advertising", authorizeUrl: "https://login.microsoftonline.com/common/oauth2/v2.0/authorize", tokenUrl: "https://login.microsoftonline.com/common/oauth2/v2.0/token", clientAuth: "body", encoding: "form", scopeSeparator: " ", pkce: true,
38
+ documentation: "https://learn.microsoft.com/en-us/advertising/guides/authentication-oauth-consent?view=bingads-13" },
39
+ amazon: { name: "Amazon Advertising", authorizeUrl: AMAZON_OAUTH_AUTHORIZATION_URLS.na, tokenUrl: "https://api.amazon.com/auth/o2/token", clientAuth: "body", encoding: "form", scopeSeparator: " ", pkce: true,
40
+ documentation: "https://developer.amazon.com/docs/login-with-amazon/authorization-code-grant.html" },
41
+ };
42
+ export const OAUTH2_TOKEN_PROTOCOLS = {
43
+ ...OAUTH2_PROVIDERS,
44
+ wix: { tokenUrl: "https://www.wixapis.com/oauth2/token", clientAuth: "body", encoding: "json",
45
+ documentation: "https://dev.wix.com/docs/build-apps/develop-your-app/auth/authenticate-using-oauth" },
46
+ wordpress: { tokenUrl: "https://public-api.wordpress.com/oauth2/token", clientAuth: "body", encoding: "form",
47
+ documentation: "https://developer.wordpress.com/docs/api/oauth2/" },
48
+ snapchatMarketing: { tokenUrl: "https://accounts.snapchat.com/login/oauth2/access_token", clientAuth: "body", encoding: "form",
49
+ documentation: "https://developers.snap.com/marketing-api/Ads-API/authentication" },
50
+ hubspot: { tokenUrl: "https://api.hubapi.com/oauth/2026-03/token", clientAuth: "body", encoding: "form",
51
+ documentation: "https://developers.hubspot.com/docs/api-reference/legacy/authentication/oauth-tokens/v1/migration-guide" },
52
+ attio: { tokenUrl: "https://app.attio.com/oauth/token", clientAuth: "body", encoding: "form",
53
+ documentation: "https://docs.attio.com/docs/oauth/token" },
54
+ pipedrive: { tokenUrl: "https://oauth.pipedrive.com/oauth/token", clientAuth: "basic", encoding: "form",
55
+ documentation: "https://pipedrive.readme.io/docs/marketplace-oauth-authorization" },
56
+ };
57
+ export function isOAuth2AuthorizationUrl(provider, candidate) {
58
+ const actual = new URL(candidate);
59
+ const allowed = provider === "amazon" ? Object.values(AMAZON_OAUTH_AUTHORIZATION_URLS) : [OAUTH2_PROVIDERS[provider].authorizeUrl];
60
+ return !actual.username && !actual.password && !actual.hash && allowed.some(value => {
61
+ const expected = new URL(value);
62
+ return actual.origin === expected.origin && actual.pathname === expected.pathname;
63
+ });
64
+ }
@@ -0,0 +1,13 @@
1
+ export const PLATFORM_KEY_PREFIXES = { noimos: "nmp_", relvo: "rlv_", toolhub: "thk_" };
2
+ export const PLATFORM_API_KEY_PATTERN = /^(?:nmp|rlv|thk)_[A-Za-z0-9_-]{43}$/;
3
+ export const PLATFORM_CLIENT_HEADERS = { noimos: "x-noimos-client", relvo: "x-relvo-client", toolhub: "x-toolhub-client" };
4
+ export const PLATFORM_PRODUCT_NAMES = { noimos: "NoimosAI Platform", relvo: "Relvo" };
5
+ export function isRelvoProduct(product) {
6
+ return product === "relvo" || product === "toolhub";
7
+ }
8
+ export function platformKeyProduct(key) {
9
+ for (const product of Object.keys(PLATFORM_KEY_PREFIXES))
10
+ if (key.startsWith(PLATFORM_KEY_PREFIXES[product]))
11
+ return product === "toolhub" ? "relvo" : product;
12
+ return null;
13
+ }
@@ -0,0 +1,20 @@
1
+ import { z } from "zod";
2
+ import { TOOL_RESULT_CONTEXT_MAX_BYTES } from "./toolResultContext.js";
3
+ export const platformConnectionProviderSchema = z.enum(["x", "gsc", "ga", "google", "github", "notion", "slack", "linear", "linkedin", "pinterest", "tiktok", "microsoftAds", "amazonAds"]);
4
+ export function platformConnectionOAuthProvider(provider) {
5
+ return provider === "gsc" || provider === "ga" ? "google" : provider === "microsoftAds" ? "microsoft" : provider === "amazonAds" ? "amazon" : provider;
6
+ }
7
+ export const PLATFORM_CONNECTION_CATALOG_LIMITS = { maxRows: 20, responseBytes: 65_536, metadataBytes: 60_000, contextBytes: TOOL_RESULT_CONTEXT_MAX_BYTES };
8
+ export const PLATFORM_CONNECTION_PAGE_MAX = PLATFORM_CONNECTION_CATALOG_LIMITS.maxRows;
9
+ export const platformConnectionCatalogQuerySchema = z.object({
10
+ provider: platformConnectionProviderSchema.optional(),
11
+ offset: z.number().int().min(0).max(Number.MAX_SAFE_INTEGER).default(0),
12
+ limit: z.number().int().min(1).max(PLATFORM_CONNECTION_PAGE_MAX).default(10),
13
+ }).strict();
14
+ export const platformConnectionCatalogSchema = z.object({
15
+ connections: z.array(z.object({
16
+ id: z.string(), provider: platformConnectionProviderSchema, label: z.string(),
17
+ status: z.enum(["connected", "reauth_required"]), scopes: z.array(z.string()),
18
+ })).max(PLATFORM_CONNECTION_PAGE_MAX),
19
+ total: z.number().int().nonnegative(), nextOffset: z.number().int().nonnegative().nullable(),
20
+ });
@@ -0,0 +1,25 @@
1
+ import { z } from "zod";
2
+ import { developerKeyScopeSchema } from "./platformPublicExecution.js";
3
+ import { PLATFORM_API_KEY_PATTERN } from "./platformApiKey.js";
4
+ export const PLATFORM_LOCAL_SETUP_TTL_SECONDS = 600;
5
+ export const PLATFORM_LOCAL_SETUP_INTERVAL_SECONDS = 5;
6
+ export const platformLocalSetupClientSchema = z.enum(["claude", "codex", "agents", "terminal"]);
7
+ export const platformLocalSetupAuthorizationSchema = z.string().min(1).max(4096);
8
+ export const platformLocalSetupStartInputSchema = z.object({ client: platformLocalSetupClientSchema }).strict();
9
+ export const platformLocalSetupProofSchema = z.object({ authorizationRequest: platformLocalSetupAuthorizationSchema,
10
+ deviceCode: z.string().regex(/^[A-Za-z0-9_-]{43}$/) }).strict();
11
+ export const platformLocalSetupStartSchema = z.object({ authorizationRequest: platformLocalSetupAuthorizationSchema,
12
+ deviceCode: platformLocalSetupProofSchema.shape.deviceCode, userCode: z.string().regex(/^[A-Z2-9]{4}-[A-Z2-9]{4}$/),
13
+ verificationUri: z.url(), expiresAt: z.iso.datetime(), intervalSeconds: z.literal(PLATFORM_LOCAL_SETUP_INTERVAL_SECONDS) }).strict();
14
+ const grantMetadata = { apiKeyId: z.string(), developerOrganizationId: z.string(), developerProjectId: z.string(),
15
+ scopes: z.array(developerKeyScopeSchema).min(1).max(5), expiresAt: z.iso.datetime(), approvedExpiresAt: z.iso.datetime() };
16
+ export const platformLocalSetupAcknowledgedSchema = z.object({ status: z.literal("acknowledged"), ...grantMetadata }).strict();
17
+ export const platformLocalSetupPollSchema = z.discriminatedUnion("status", [
18
+ z.object({ status: z.literal("pending"), expiresAt: z.iso.datetime(), intervalSeconds: z.literal(PLATFORM_LOCAL_SETUP_INTERVAL_SECONDS) }).strict(),
19
+ z.object({ status: z.literal("approved"), apiKey: z.string().regex(PLATFORM_API_KEY_PATTERN), ...grantMetadata }).strict(),
20
+ platformLocalSetupAcknowledgedSchema,
21
+ ]);
22
+ export const platformLocalSetupMetadataSchema = z.object({ client: platformLocalSetupClientSchema,
23
+ expiresAt: z.iso.datetime(), status: z.enum(["pending", "approved", "acknowledged"]),
24
+ apiKeyId: z.string().optional(), developerOrganizationId: z.string().optional(), developerProjectId: z.string().optional(),
25
+ scopes: z.array(developerKeyScopeSchema).optional(), approvedExpiresAt: z.iso.datetime().optional() }).strict();
@@ -0,0 +1,60 @@
1
+ import { z } from "zod";
2
+ import { platformCatalogSchema, platformCreateExecutionSchema, platformExecutionIdSchema, platformExecutionStatusSchema, platformMicrosSchema, platformQuoteSchema, platformToolNameSchema, platformToolSessionIdSchema, platformOptionalToolSessionIdSchema, PLATFORM_SANDBOX_LIMITS, platformExecutionSchema, platformExecutionContextSchema, platformRequestIntentSchema } from "./platformPublicExecution.js";
3
+ import { platformConnectionCatalogSchema, platformConnectionProviderSchema } from "./platformConnectionCatalog.js";
4
+ import { toolDiscoveryInputSchema, toolDiscoveryResultSchema } from "./toolDiscovery.js";
5
+ export const PLATFORM_META_LIMITS = { queries: 4, schemas: 12, batch: 50, concurrency: 4, contextBytes: 24_000,
6
+ requestBytes: 1_048_576, requestTimeoutMs: 90_000, sessionLifetimeMs: 86_400_000, activeSessions: 100,
7
+ codeChars: PLATFORM_SANDBOX_LIMITS.code, commandChars: 16_000, sourceExecutions: PLATFORM_SANDBOX_LIMITS.sourceExecutions, timeoutMs: PLATFORM_SANDBOX_LIMITS.timeoutMs };
8
+ export const isSessionSandboxTool = (name) => name === "remote_workbench" || name === "remote_bash";
9
+ export const platformToolSessionSchema = z.object({ id: platformToolSessionIdSchema, expiresAt: z.iso.datetime(),
10
+ sandbox: z.enum(["new", "ready", "busy", "needs_review", "state_lost"]), activeExecutionId: platformExecutionIdSchema.nullable() });
11
+ const sessionInput = { sessionId: platformToolSessionIdSchema };
12
+ export const platformMetaSearchSchema = z.object({ sessionId: platformOptionalToolSessionIdSchema.describe("Continue a returned session. Omit or use an empty string to open a new session."),
13
+ intent: platformRequestIntentSchema,
14
+ queries: z.array(toolDiscoveryInputSchema).min(1).max(PLATFORM_META_LIMITS.queries)
15
+ .describe("Independent capability queries, one action per query. Use exact app names when known; omit private account data.") }).strict();
16
+ export const platformMetaSearchResultSchema = z.object({ session: platformToolSessionSchema,
17
+ results: z.array(z.object({ query: z.string().optional(), ...toolDiscoveryResultSchema.shape })),
18
+ nextSteps: z.array(z.enum(["inspect_schemas", "check_connections", "quote", "execute_independent", "process_results"])) });
19
+ export const platformMetaSchemasSchema = z.object({ ...sessionInput,
20
+ tools: z.array(platformToolNameSchema).min(1).max(PLATFORM_META_LIMITS.schemas).refine(names => new Set(names).size === names.length, "Tool names must be unique") }).strict();
21
+ export const platformMetaSchemasResultSchema = z.object({ session: platformToolSessionSchema,
22
+ tools: platformCatalogSchema.shape.tools, remaining: z.array(platformToolNameSchema) });
23
+ export const platformMetaConnectionsSchema = z.object({ ...sessionInput,
24
+ providers: z.array(platformConnectionProviderSchema).max(platformConnectionProviderSchema.options.length).default([])
25
+ .describe("OAuth providers to inspect. Empty means all supported providers. Data-provider API keys are managed by Relvo.") }).strict();
26
+ export const platformMetaConnectionsResultSchema = z.object({ session: platformToolSessionSchema,
27
+ developerOrganizationId: z.string(), developerProjectId: z.string(),
28
+ manageUrl: z.url(), connections: platformConnectionCatalogSchema.shape.connections,
29
+ total: z.number().int().nonnegative(), providers: z.array(z.object({ provider: platformConnectionProviderSchema,
30
+ status: z.enum(["connected", "reauth_required", "not_connected"]) })),
31
+ instruction: z.string() });
32
+ const item = platformCreateExecutionSchema.omit({ sessionId: true }).extend({ idempotencyKey: z.string().regex(/^[A-Za-z0-9._:-]{8,128}$/) }).strict();
33
+ export const platformMetaExecuteSchema = z.object({ ...sessionInput,
34
+ maximumChargeMicros: platformMicrosSchema.describe("Accepted total reservation ceiling across this batch, in addition to each accepted quote."),
35
+ tools: z.array(item).min(1).max(PLATFORM_META_LIMITS.batch)
36
+ .describe("Logically independent, quoted calls only. Dependent calls must wait for their inputs in a later batch.") }).strict().superRefine((value, ctx) => {
37
+ if (new Set(value.tools.map(tool => tool.idempotencyKey)).size !== value.tools.length)
38
+ ctx.addIssue({ code: "custom", path: ["tools"], message: "Each call needs its own retained idempotency key" });
39
+ if (value.tools.reduce((total, tool) => total + tool.maximumChargeMicros, 0) > value.maximumChargeMicros)
40
+ ctx.addIssue({ code: "custom", path: ["maximumChargeMicros"], message: "Batch exceeds the accepted reservation ceiling" });
41
+ });
42
+ export const platformMetaExecuteResultSchema = z.object({ session: platformToolSessionSchema,
43
+ allAccepted: z.boolean(),
44
+ results: z.array(z.object({ index: z.number().int().nonnegative(), tool: platformToolNameSchema, idempotencyKey: z.string(),
45
+ admission: z.enum(["accepted", "rejected", "unknown"]), executionId: platformExecutionIdSchema.optional(),
46
+ status: platformExecutionStatusSchema.optional(), reservedMicros: platformMicrosSchema.optional(),
47
+ errorCode: z.string().optional() })),
48
+ instruction: z.string() });
49
+ const remote = { ...sessionInput, timeoutMs: z.number().int().min(1000).max(PLATFORM_META_LIMITS.timeoutMs).default(30_000),
50
+ intent: platformRequestIntentSchema,
51
+ sourceExecutionIds: z.array(platformExecutionIdSchema).max(PLATFORM_META_LIMITS.sourceExecutions).default([])
52
+ .refine(ids => new Set(ids).size === ids.length, "Source execution IDs must be unique"),
53
+ quote: platformQuoteSchema,
54
+ idempotencyKey: item.shape.idempotencyKey };
55
+ export const platformMetaWorkbenchSchema = z.object({ ...remote,
56
+ code: z.string().min(1).max(PLATFORM_META_LIMITS.codeChars).describe("Python cell in this session's remote sandbox. Variables and files persist. Process prior outputs via sourceExecutionIds; no provider credentials or direct tool proxy are present.") }).strict();
57
+ export const platformMetaBashSchema = z.object({ ...remote,
58
+ command: z.string().min(1).max(PLATFORM_META_LIMITS.commandChars).describe("Bash command in this session's remote sandbox, never on the user's machine. Shares files with Python. No provider credentials are present.") }).strict();
59
+ export const platformMetaRemoteResultSchema = z.object({ session: platformToolSessionSchema,
60
+ execution: z.union([platformExecutionContextSchema, platformExecutionSchema]) });
@@ -0,0 +1,136 @@
1
+ import { z } from "zod";
2
+ import { fitToolResultPage, TOOL_RESULT_CONTEXT_MAX_BYTES, toolResultContextSchema, toolResultPageSchema } from "./toolResultContext.js";
3
+ export const PLATFORM_EXECUTION_MAX_STEPS = 20;
4
+ export const PLATFORM_EXECUTION_MAX_WAIT_SECONDS = 25;
5
+ export const PLATFORM_STEP_OUTPUT_MAX_BYTES = 1_048_576;
6
+ export const PLATFORM_EXECUTION_RESPONSE_MAX_BYTES = PLATFORM_EXECUTION_MAX_STEPS * PLATFORM_STEP_OUTPUT_MAX_BYTES + 65_536;
7
+ export const platformJsonObjectSchema = z.record(z.string(), z.json());
8
+ export const developerKeyScopeSchema = z.enum(["project:read", "usage:read", "tools:read", "tools:execute", "executions:read"]);
9
+ export const PLATFORM_INPUT_MAX_BYTES = 12 * 1024 * 1024;
10
+ export const PLATFORM_UPLOAD_MAX_BYTES = 16 * 1024 * 1024 + 4096;
11
+ export const platformInputFileIdSchema = z.string().regex(/^f_[a-f0-9]{64}$/);
12
+ export const platformInputMimeSchema = z.enum(["image/jpeg", "image/png", "image/webp", "audio/wav", "audio/mpeg", "video/mp4", "video/quicktime"]);
13
+ export const platformInputUploadSchema = z.object({ mimeType: platformInputMimeSchema, data: z.string().min(4).max(16 * 1024 * 1024) }).strict();
14
+ export const platformInputFileSchema = z.object({ id: platformInputFileIdSchema, mimeType: platformInputMimeSchema,
15
+ sizeBytes: z.number().int().positive().max(PLATFORM_INPUT_MAX_BYTES), sha256: z.string().regex(/^[a-f0-9]{64}$/), expiresAt: z.iso.datetime() });
16
+ export const platformMicrosSchema = z.number().int().min(0).max(1_000_000_000_000);
17
+ export const platformToolNameSchema = z.string().regex(/^[a-z][a-z0-9_]{1,63}$/);
18
+ export const platformExecutionIdSchema = z.string().regex(/^[A-Za-z0-9_-]{1,128}$/);
19
+ export const platformToolSessionIdSchema = z.uuid();
20
+ export const platformOptionalToolSessionIdSchema = z.union([platformToolSessionIdSchema, z.literal("")]).transform(value => value || undefined).optional();
21
+ export const PLATFORM_SANDBOX_LIMITS = { code: 64000, sourceExecutions: 10, timeoutMs: 60000, sourceBytes: 32 * 1024 * 1024 };
22
+ export const platformProviderChoiceSchema = z.string().trim().regex(/^(?:[a-z][a-z0-9_]{0,63})?$/)
23
+ .transform(value => value === "" || value === "auto" ? undefined : value).optional()
24
+ .describe("Omit, use auto, or use an empty string for automatic routing. A catalog provider ID strictly selects that compatible provider; no other provider is substituted.");
25
+ export const PLATFORM_REQUEST_INTENT_MAX_CHARS = 1000;
26
+ export const platformRequestIntentSchema = z.string().trim().max(PLATFORM_REQUEST_INTENT_MAX_CHARS).transform(value => value || undefined).optional()
27
+ .describe("Brief user goal without secrets or private content. Empty means omitted.");
28
+ export const platformExecutionStatusSchema = z.enum(["queued", "running", "waiting", "succeeded", "failed", "cancelled", "needs_review"]);
29
+ export const platformFundingSchema = z.enum(["relvo", "toolhub", "provider", "mixed"])
30
+ .transform(value => value === "toolhub" ? "relvo" : value);
31
+ const platformModelFundingSchema = z.enum(["relvo", "toolhub", "provider"])
32
+ .transform(value => value === "toolhub" ? "relvo" : value);
33
+ const workspaceArgumentNames = new Set(["workspaceid", "workspaceids", "teamid", "provideraccountid", "provideraccountids", "datasetid", "datasetids", "knowledgebaseid", "sessionid", "skillid"]);
34
+ const standaloneArgumentsSchema = z.record(z.string(), z.unknown()).pipe(platformJsonObjectSchema).superRefine((value, context) => {
35
+ for (const key of Object.keys(value))
36
+ if (workspaceArgumentNames.has(key.replaceAll("_", "").toLowerCase())) {
37
+ context.addIssue({ code: "custom", path: [key], message: "Workspace-owned data is unavailable on the standalone Platform." });
38
+ }
39
+ }).describe("Self-contained tool arguments: prompts, public URLs or public account identifiers. Web workspace identifiers are not supported.");
40
+ export const platformToolInputSchema = z.object({ tool: platformToolNameSchema.describe("Tool name from the standalone Platform catalog."), arguments: standaloneArgumentsSchema,
41
+ provider: platformProviderChoiceSchema,
42
+ intent: platformRequestIntentSchema,
43
+ sessionId: platformOptionalToolSessionIdSchema.describe("Relvo tool session returned by platform_search_tools. Omit or use an empty string for standalone executions.") }).strict();
44
+ export const platformCreateExecutionSchema = platformToolInputSchema.extend({
45
+ maximumChargeMicros: platformMicrosSchema,
46
+ priceVersion: z.string().min(1).max(100),
47
+ }).strict();
48
+ export const platformQuoteSchema = z.object({
49
+ tool: platformToolNameSchema, priceVersion: z.string(), currency: z.literal("usd"),
50
+ minimumChargeMicros: platformMicrosSchema, maximumChargeMicros: platformMicrosSchema,
51
+ expiresAt: z.string(),
52
+ funding: platformFundingSchema.optional(), externalBillingProviders: z.array(z.string()).optional(),
53
+ });
54
+ export const platformExecutionSchema = z.object({
55
+ id: platformExecutionIdSchema, tool: platformToolNameSchema, status: platformExecutionStatusSchema, cancelRequested: z.boolean(),
56
+ priceVersion: z.string(), currency: z.literal("usd"), reservedMicros: platformMicrosSchema, chargedMicros: platformMicrosSchema,
57
+ funding: platformFundingSchema.optional(), externalBillingProviders: z.array(z.string()).optional(),
58
+ output: platformJsonObjectSchema.nullable(), errorCode: z.string().nullable(),
59
+ createdAt: z.string(), updatedAt: z.string(), completedAt: z.string().nullable(),
60
+ });
61
+ export const platformExecutionContextSchema = platformExecutionSchema.extend({ resultContext: toolResultContextSchema.omit({ preview: true }) });
62
+ export const platformExecutionResultPageSchema = z.object({ executionId: platformExecutionIdSchema, page: toolResultPageSchema });
63
+ export const PLATFORM_EXECUTION_CONTEXT_BUDGET_ERROR = "platform_execution_context_budget_exceeded";
64
+ export class PlatformExecutionContextBudgetError extends Error {
65
+ executionId;
66
+ kind;
67
+ code = PLATFORM_EXECUTION_CONTEXT_BUDGET_ERROR;
68
+ constructor(executionId, kind = "execution") {
69
+ super(kind === "page"
70
+ ? `Execution ${executionId} selected result page metadata exceeds the context budget. Read a narrower path or use a smaller limit/maxChars; the full result remains retained.`
71
+ : `Execution ${executionId} metadata exceeds the context budget. Recover it by this ID or the retained idempotency key.`);
72
+ this.executionId = executionId;
73
+ this.kind = kind;
74
+ this.name = "PlatformExecutionContextBudgetError";
75
+ }
76
+ }
77
+ const executionContextBytes = (value) => new TextEncoder().encode(JSON.stringify(value)).byteLength;
78
+ export function platformExecutionReceiptContext(result, options) {
79
+ const maximumBytes = options.maximumBytes ?? TOOL_RESULT_CONTEXT_MAX_BYTES;
80
+ if (executionContextBytes(result) <= maximumBytes)
81
+ return result;
82
+ const existing = platformExecutionContextSchema.safeParse(result.data);
83
+ const compact = { data: platformExecutionContextSchema.parse({ ...result.data, output: null,
84
+ resultContext: { offloaded: true, totalBytes: existing.success ? existing.data.resultContext.totalBytes : executionContextBytes(result.data.output),
85
+ reference: { ...(existing.success ? existing.data.resultContext.reference : {}), executionId: result.data.id, readWith: options.readWith } },
86
+ }) };
87
+ if (executionContextBytes(compact) > maximumBytes)
88
+ throw new PlatformExecutionContextBudgetError(result.data.id);
89
+ return compact;
90
+ }
91
+ export function platformExecutionResultPageContext(result, options) {
92
+ const maximumBytes = options.maximumBytes ?? TOOL_RESULT_CONTEXT_MAX_BYTES;
93
+ if (executionContextBytes(result) <= maximumBytes)
94
+ return result;
95
+ const { executionId, page } = result.data;
96
+ const fitted = { data: { executionId, page: fitToolResultPage(page, candidate => executionContextBytes({ data: { executionId, page: candidate } }) <= maximumBytes) } };
97
+ if (executionContextBytes(fitted) <= maximumBytes)
98
+ return fitted;
99
+ const compact = { data: platformExecutionResultPageSchema.parse({ executionId, page: { ...page,
100
+ context: { ...page.context, offloaded: true, preview: null, reference: { ...page.context.reference,
101
+ executionId, readWith: options.readWith, path: page.path, offset: page.offset } },
102
+ } }) };
103
+ if (executionContextBytes(compact) > maximumBytes)
104
+ throw new PlatformExecutionContextBudgetError(executionId, "page");
105
+ return compact;
106
+ }
107
+ export const platformExecutionListQuerySchema = z.object({
108
+ limit: z.number().int().min(1).max(100).default(20).describe("Page size, 1–100."),
109
+ before: platformExecutionIdSchema.optional().describe("nextBefore from the previous page."),
110
+ status: platformExecutionStatusSchema.optional().describe("Only executions with this status."),
111
+ tool: platformToolNameSchema.optional().describe("Only executions of this catalog tool."),
112
+ }).strict();
113
+ export const platformExecutionSummarySchema = platformExecutionSchema.omit({ output: true });
114
+ export const platformExecutionListSchema = z.object({
115
+ executions: z.array(platformExecutionSummarySchema).max(100), nextBefore: z.string().nullable(),
116
+ });
117
+ export const platformArtifactIdSchema = z.string().regex(/^a_[A-Za-z0-9_-]{1,100}$/);
118
+ export const platformArtifactSchema = z.object({
119
+ id: platformArtifactIdSchema, mimeType: z.enum(["image/jpeg", "image/png", "image/webp", "image/svg+xml", "audio/wav", "audio/mpeg", "audio/flac", "audio/l16", "audio/mulaw", "audio/alaw", "video/mp4", "video/quicktime", "model/gltf-binary", "application/zip"]),
120
+ sizeBytes: z.number().int().positive().max(64 * 1024 * 1024), sha256: z.string().regex(/^[a-f0-9]{64}$/),
121
+ generation: z.string().regex(/^\d{1,30}$/), expiresAt: z.iso.datetime(),
122
+ });
123
+ export const platformArtifactDownloadSchema = z.object({ url: z.url().refine((value) => { const url = new URL(value); return url.protocol === "https:" && !url.username && !url.password; }), expiresAt: z.iso.datetime() });
124
+ export const platformCatalogToolSchema = z.object({
125
+ name: platformToolNameSchema, title: z.string().optional(), description: z.string(), category: z.enum(["research", "seo", "social", "generation", "enrichment", "finance", "news"]),
126
+ available: z.boolean(), inputSchema: platformJsonObjectSchema, example: platformJsonObjectSchema,
127
+ providers: z.array(z.object({ id: z.string(), name: z.string(), available: z.boolean(),
128
+ role: z.enum(["primary", "fallback", "alternative"]), condition: z.string().optional() })).optional(),
129
+ models: z.array(z.object({ id: z.string(), available: z.boolean(), example: platformJsonObjectSchema, priceDescription: z.string(), funding: platformModelFundingSchema.optional() })).optional(),
130
+ price: z.object({ minimumChargeMicros: platformMicrosSchema, unit: z.string(), unitPriceMicros: platformMicrosSchema.nullable(), unitPriceCeilingMicros: platformMicrosSchema.optional(), description: z.string(),
131
+ funding: platformFundingSchema.optional(), externalBillingProviders: z.array(z.string()).optional() }),
132
+ });
133
+ const platformCatalogHeader = { priceVersion: z.string(), currency: z.literal("usd"), executionEnabled: z.boolean() };
134
+ export const platformCatalogSchema = z.object({ ...platformCatalogHeader, tools: z.array(platformCatalogToolSchema) });
135
+ export const platformCatalogSummarySchema = z.object({ ...platformCatalogHeader, tools: z.array(platformCatalogToolSchema.omit({ inputSchema: true })) });
136
+ export const platformCatalogToolDetailSchema = z.object({ ...platformCatalogHeader, tool: platformCatalogToolSchema });