@relvo/mcp 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -154
- package/bin/relvo-mcp.mjs +1 -1
- package/dist/client/agent-run.js +681 -0
- package/dist/client/browser.js +652 -0
- package/dist/client/evm-wallet.js +214 -0
- package/dist/client/index.js +3 -360
- package/dist/client/local-input.js +18 -11
- package/dist/client/local-profile.js +22 -110
- package/dist/client/local-setup.js +0 -1
- package/dist/client/tool-name-hints.js +48 -0
- package/dist/client/wallet-access.js +207 -0
- package/dist/mcp/agent-server.js +237 -0
- package/dist/mcp/context.js +3 -2
- package/dist/mcp/host-schema.js +47 -0
- package/dist/mcp/instructions.js +12 -2
- package/dist/mcp/local-upload.js +12 -4
- package/dist/mcp/operations.js +30 -0
- package/dist/mcp/runtime.js +25 -5
- package/dist/mcp/server.js +191 -37
- package/dist/mcp/version.js +1 -0
- package/dist/schema/accountRequestMethods.js +1 -0
- package/dist/schema/cronValidator.js +150 -0
- package/dist/schema/platformApiKey.js +5 -5
- package/dist/schema/platformBillingConstants.js +3 -0
- package/dist/schema/platformCapabilityFamilies.js +269 -0
- package/dist/schema/platformConnectionCatalog.js +20 -3
- package/dist/schema/platformConnectionPolicy.js +26 -0
- package/dist/schema/platformCustomTools.js +113 -0
- package/dist/schema/platformFunding.js +9 -0
- package/dist/schema/platformHttpConstraints.js +7 -0
- package/dist/schema/platformMcpTools.js +7 -0
- package/dist/schema/platformMetaTools.js +8 -6
- package/dist/schema/platformMonitorSchedule.js +123 -0
- package/dist/schema/platformMonitors.js +159 -0
- package/dist/schema/platformOAuth.js +7 -0
- package/dist/schema/platformPublicExecution.js +53 -15
- package/dist/schema/platformRoutes.js +61 -1
- package/dist/schema/platformSkills.js +1 -1
- package/dist/schema/platformTeamSecretName.js +3 -0
- package/dist/schema/platformTeamSkills.js +49 -0
- package/dist/schema/platformToolAccess.js +18 -0
- package/dist/schema/platformTriggers.js +152 -0
- package/dist/schema/platformWorkflows.js +100 -0
- package/dist/schema/scheduleTimeZone.js +15 -0
- package/dist/schema/toolDiscovery.js +20 -3
- package/dist/schema/toolInputRepair.js +110 -0
- package/dist/schema/toolResultContext.js +1 -1
- package/package.json +1 -1
- package/dist/schema/amazonAds.js +0 -13
- package/dist/schema/oauth2.js +0 -64
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { createECDH, createHash, createPrivateKey, sign } from "node:crypto";
|
|
2
|
+
const P = 2n ** 256n - 2n ** 32n - 977n;
|
|
3
|
+
const N = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141n;
|
|
4
|
+
const G = {
|
|
5
|
+
x: 0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798n,
|
|
6
|
+
y: 0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8n,
|
|
7
|
+
};
|
|
8
|
+
export function keccak256(data) {
|
|
9
|
+
return createHash("keccak-256").update(data).digest();
|
|
10
|
+
}
|
|
11
|
+
function mod(a, m = P) {
|
|
12
|
+
const r = a % m;
|
|
13
|
+
return r >= 0n ? r : r + m;
|
|
14
|
+
}
|
|
15
|
+
function modPow(base, exponent, m) {
|
|
16
|
+
let result = 1n, b = mod(base, m), e = exponent;
|
|
17
|
+
while (e > 0n) {
|
|
18
|
+
if (e & 1n)
|
|
19
|
+
result = (result * b) % m;
|
|
20
|
+
b = (b * b) % m;
|
|
21
|
+
e >>= 1n;
|
|
22
|
+
}
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
function invert(a, m) {
|
|
26
|
+
let [r0, r1, t0, t1] = [m, mod(a, m), 0n, 1n];
|
|
27
|
+
while (r1 !== 0n) {
|
|
28
|
+
const q = r0 / r1;
|
|
29
|
+
[r0, r1] = [r1, r0 - q * r1];
|
|
30
|
+
[t0, t1] = [t1, t0 - q * t1];
|
|
31
|
+
}
|
|
32
|
+
return mod(t0, m);
|
|
33
|
+
}
|
|
34
|
+
function addPoints(a, b) {
|
|
35
|
+
if (!a)
|
|
36
|
+
return b;
|
|
37
|
+
if (!b)
|
|
38
|
+
return a;
|
|
39
|
+
let slope;
|
|
40
|
+
if (a.x === b.x) {
|
|
41
|
+
if (mod(a.y + b.y) === 0n)
|
|
42
|
+
return null;
|
|
43
|
+
slope = mod(3n * a.x * a.x * invert(2n * a.y, P));
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
slope = mod((b.y - a.y) * invert(b.x - a.x, P));
|
|
47
|
+
}
|
|
48
|
+
const x = mod(slope * slope - a.x - b.x);
|
|
49
|
+
return { x, y: mod(slope * (a.x - x) - a.y) };
|
|
50
|
+
}
|
|
51
|
+
function multiply(point, scalar) {
|
|
52
|
+
let result = null, addend = point, k = scalar;
|
|
53
|
+
while (k > 0n) {
|
|
54
|
+
if (k & 1n)
|
|
55
|
+
result = addPoints(result, addend);
|
|
56
|
+
addend = addPoints(addend, addend);
|
|
57
|
+
k >>= 1n;
|
|
58
|
+
}
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
const toBytes32 = (value) => Buffer.from(value.toString(16).padStart(64, "0"), "hex");
|
|
62
|
+
const toBigInt = (bytes) => BigInt(`0x${Buffer.from(bytes).toString("hex") || "0"}`);
|
|
63
|
+
const hex = (bytes) => `0x${Buffer.from(bytes).toString("hex")}`;
|
|
64
|
+
function addressOf(publicKey) {
|
|
65
|
+
return checksumAddress(`0x${keccak256(Buffer.concat([toBytes32(publicKey.x), toBytes32(publicKey.y)])).subarray(12).toString("hex")}`);
|
|
66
|
+
}
|
|
67
|
+
export function checksumAddress(address) {
|
|
68
|
+
if (!/^0x[0-9a-fA-F]{40}$/.test(address))
|
|
69
|
+
throw new Error("Not an EVM address");
|
|
70
|
+
const lower = address.slice(2).toLowerCase();
|
|
71
|
+
const hash = keccak256(lower).toString("hex");
|
|
72
|
+
let out = "0x";
|
|
73
|
+
for (let i = 0; i < lower.length; i += 1)
|
|
74
|
+
out += parseInt(hash[i], 16) >= 8 ? lower[i].toUpperCase() : lower[i];
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
77
|
+
function recoverPoint(digest, r, s, recovery) {
|
|
78
|
+
if (r <= 0n || r >= N || s <= 0n || s >= N)
|
|
79
|
+
return null;
|
|
80
|
+
const ySquared = mod(r ** 3n + 7n);
|
|
81
|
+
let y = modPow(ySquared, (P + 1n) / 4n, P);
|
|
82
|
+
if (mod(y * y) !== ySquared)
|
|
83
|
+
return null;
|
|
84
|
+
if (Number(y & 1n) !== recovery)
|
|
85
|
+
y = P - y;
|
|
86
|
+
const rInverse = invert(r, N);
|
|
87
|
+
const u1 = mod(-toBigInt(digest) * rInverse, N), u2 = mod(s * rInverse, N);
|
|
88
|
+
return addPoints(multiply(G, u1), multiply({ x: r, y }, u2));
|
|
89
|
+
}
|
|
90
|
+
export function recoverAddress(digest, signature) {
|
|
91
|
+
if (digest.length !== 32 || !/^0x[0-9a-fA-F]{130}$/.test(signature))
|
|
92
|
+
throw new Error("Expected a 32-byte digest and a 65-byte signature");
|
|
93
|
+
const bytes = Buffer.from(signature.slice(2), "hex"), v = bytes[64];
|
|
94
|
+
if (v !== 27 && v !== 28)
|
|
95
|
+
throw new Error("Signature recovery id must be 27 or 28");
|
|
96
|
+
const point = recoverPoint(Buffer.from(digest), toBigInt(bytes.subarray(0, 32)), toBigInt(bytes.subarray(32, 64)), v - 27);
|
|
97
|
+
if (!point)
|
|
98
|
+
throw new Error("Signature does not recover a public key");
|
|
99
|
+
return addressOf(point);
|
|
100
|
+
}
|
|
101
|
+
export function hashPersonalMessage(message) {
|
|
102
|
+
return keccak256(personalMessageBytes(message));
|
|
103
|
+
}
|
|
104
|
+
function personalMessageBytes(message) {
|
|
105
|
+
const body = Buffer.from(message, "utf8");
|
|
106
|
+
return Buffer.concat([Buffer.from(`\x19Ethereum Signed Message:\n${body.length}`, "utf8"), body]);
|
|
107
|
+
}
|
|
108
|
+
const ATOMIC = new Set(["address", "uint256", "bytes32", "string"]);
|
|
109
|
+
const DOMAIN_FIELDS = [
|
|
110
|
+
{ name: "name", type: "string" }, { name: "version", type: "string" },
|
|
111
|
+
{ name: "chainId", type: "uint256" }, { name: "verifyingContract", type: "address" },
|
|
112
|
+
];
|
|
113
|
+
function dependencies(primary, types, found = new Set()) {
|
|
114
|
+
if (found.has(primary))
|
|
115
|
+
return found;
|
|
116
|
+
const fields = types[primary];
|
|
117
|
+
if (!fields)
|
|
118
|
+
throw new Error(`Unknown EIP-712 type ${primary}`);
|
|
119
|
+
found.add(primary);
|
|
120
|
+
for (const { type } of fields)
|
|
121
|
+
if (!ATOMIC.has(type))
|
|
122
|
+
dependencies(type, types, found);
|
|
123
|
+
return found;
|
|
124
|
+
}
|
|
125
|
+
export function encodeType(primary, types) {
|
|
126
|
+
const others = [...dependencies(primary, types)].filter((name) => name !== primary).sort();
|
|
127
|
+
return [primary, ...others].map((name) => `${name}(${types[name].map((field) => `${field.type} ${field.name}`).join(",")})`).join("");
|
|
128
|
+
}
|
|
129
|
+
export const typeHash = (primary, types) => keccak256(encodeType(primary, types));
|
|
130
|
+
function encodeAtomic(type, value) {
|
|
131
|
+
if (type === "string") {
|
|
132
|
+
if (typeof value !== "string")
|
|
133
|
+
throw new Error("EIP-712 string value must be a string");
|
|
134
|
+
return keccak256(Buffer.from(value, "utf8"));
|
|
135
|
+
}
|
|
136
|
+
if (type === "address") {
|
|
137
|
+
if (typeof value !== "string" || !/^0x[0-9a-fA-F]{40}$/.test(value))
|
|
138
|
+
throw new Error("EIP-712 address value is invalid");
|
|
139
|
+
return Buffer.concat([Buffer.alloc(12), Buffer.from(value.slice(2), "hex")]);
|
|
140
|
+
}
|
|
141
|
+
if (type === "bytes32") {
|
|
142
|
+
if (typeof value !== "string" || !/^0x[0-9a-fA-F]{64}$/.test(value))
|
|
143
|
+
throw new Error("EIP-712 bytes32 value is invalid");
|
|
144
|
+
return Buffer.from(value.slice(2), "hex");
|
|
145
|
+
}
|
|
146
|
+
if (typeof value === "object" || typeof value === "string" && !/^(?:0x[0-9a-fA-F]+|\d+)$/.test(value) || typeof value === "number" && !Number.isSafeInteger(value))
|
|
147
|
+
throw new Error("EIP-712 uint256 value is invalid");
|
|
148
|
+
const number = BigInt(value);
|
|
149
|
+
if (number < 0n || number >= 2n ** 256n)
|
|
150
|
+
throw new Error("EIP-712 uint256 value is out of range");
|
|
151
|
+
return toBytes32(number);
|
|
152
|
+
}
|
|
153
|
+
export function hashStruct(primary, types, data) {
|
|
154
|
+
const parts = [typeHash(primary, types)];
|
|
155
|
+
for (const { name, type } of types[primary] ?? []) {
|
|
156
|
+
const value = data[name];
|
|
157
|
+
if (value === undefined)
|
|
158
|
+
throw new Error(`EIP-712 value for ${name} is missing`);
|
|
159
|
+
if (ATOMIC.has(type))
|
|
160
|
+
parts.push(encodeAtomic(type, value));
|
|
161
|
+
else if (typeof value === "object")
|
|
162
|
+
parts.push(hashStruct(type, types, value));
|
|
163
|
+
else
|
|
164
|
+
throw new Error(`EIP-712 value for ${name} must be a ${type} object`);
|
|
165
|
+
}
|
|
166
|
+
return keccak256(Buffer.concat(parts));
|
|
167
|
+
}
|
|
168
|
+
export function domainSeparator(domain) {
|
|
169
|
+
const fields = DOMAIN_FIELDS.filter(({ name }) => domain[name] !== undefined);
|
|
170
|
+
const data = {};
|
|
171
|
+
for (const { name } of fields)
|
|
172
|
+
data[name] = domain[name];
|
|
173
|
+
return hashStruct("EIP712Domain", { EIP712Domain: fields }, data);
|
|
174
|
+
}
|
|
175
|
+
export function hashTypedData(domain, types, primaryType, message) {
|
|
176
|
+
if (Object.hasOwn(types, "EIP712Domain"))
|
|
177
|
+
throw new Error("Pass the domain separately; types must not define EIP712Domain");
|
|
178
|
+
return keccak256(Buffer.concat([Buffer.from("1901", "hex"), domainSeparator(domain), hashStruct(primaryType, types, message)]));
|
|
179
|
+
}
|
|
180
|
+
export function loadEvmWallet(privateKeyHex) {
|
|
181
|
+
if (!/^0x[0-9a-fA-F]{64}$/.test(privateKeyHex))
|
|
182
|
+
throw new Error("The wallet private key must be 0x followed by 64 hex characters.");
|
|
183
|
+
const d = Buffer.from(privateKeyHex.slice(2), "hex"), scalar = toBigInt(d);
|
|
184
|
+
if (scalar <= 0n || scalar >= N)
|
|
185
|
+
throw new Error("The wallet private key is out of range.");
|
|
186
|
+
const ecdh = createECDH("secp256k1");
|
|
187
|
+
ecdh.setPrivateKey(d);
|
|
188
|
+
const uncompressed = ecdh.getPublicKey();
|
|
189
|
+
const publicKey = { x: toBigInt(uncompressed.subarray(1, 33)), y: toBigInt(uncompressed.subarray(33, 65)) };
|
|
190
|
+
const address = addressOf(publicKey);
|
|
191
|
+
const key = createPrivateKey({ key: { kty: "EC", crv: "secp256k1", d: d.toString("base64url"),
|
|
192
|
+
x: toBytes32(publicKey.x).toString("base64url"), y: toBytes32(publicKey.y).toString("base64url") }, format: "jwk" });
|
|
193
|
+
const signPreimage = (preimage) => {
|
|
194
|
+
const raw = sign("keccak-256", preimage, { key, dsaEncoding: "ieee-p1363" });
|
|
195
|
+
const r = toBigInt(raw.subarray(0, 32)), rawS = toBigInt(raw.subarray(32));
|
|
196
|
+
const s = rawS > N >> 1n ? N - rawS : rawS;
|
|
197
|
+
const digest = keccak256(preimage), body = `${hex(toBytes32(r))}${toBytes32(s).toString("hex")}`;
|
|
198
|
+
for (const v of ["1b", "1c"]) {
|
|
199
|
+
const point = recoverPoint(digest, r, s, v === "1b" ? 0 : 1);
|
|
200
|
+
if (point && point.x === publicKey.x && point.y === publicKey.y)
|
|
201
|
+
return `${body}${v}`;
|
|
202
|
+
}
|
|
203
|
+
throw new Error("No recovery id reproduces the wallet address.");
|
|
204
|
+
};
|
|
205
|
+
return {
|
|
206
|
+
address,
|
|
207
|
+
signPersonalMessage: (message) => signPreimage(personalMessageBytes(message)),
|
|
208
|
+
signTypedData: (domain, types, primaryType, message) => {
|
|
209
|
+
if (Object.hasOwn(types, "EIP712Domain"))
|
|
210
|
+
throw new Error("Pass the domain separately; types must not define EIP712Domain");
|
|
211
|
+
return signPreimage(Buffer.concat([Buffer.from("1901", "hex"), domainSeparator(domain), hashStruct(primaryType, types, message)]));
|
|
212
|
+
},
|
|
213
|
+
};
|
|
214
|
+
}
|
package/dist/client/index.js
CHANGED
|
@@ -1,360 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export {
|
|
4
|
-
import { PLATFORM_CONNECTION_CATALOG_LIMITS, platformConnectionCatalogSchema, platformConnectionCatalogQuerySchema } from "../schema/platformConnectionCatalog.js";
|
|
5
|
-
export { PLATFORM_CONNECTION_CATALOG_LIMITS, platformConnectionProviderSchema, platformConnectionCatalogQuerySchema } from "../schema/platformConnectionCatalog.js";
|
|
6
|
-
import { TOOL_RESULT_CONTEXT_MAX_BYTES, toolResultSelectionSchema } from "../schema/toolResultContext.js";
|
|
7
|
-
import { executionReceiptContext } from "./execution-context.js";
|
|
8
|
-
export { executionReceiptContext, executionPageContext } from "./execution-context.js";
|
|
9
|
-
export { UNTRUSTED_CONTENT_RULE, wrapUntrusted } from "../schema/untrustedContent.js";
|
|
10
|
-
export { fitToolResultPage, TOOL_RESULT_CONTEXT_MAX_BYTES, TOOL_RESULT_PAGE_MAX_ITEMS, toolResultSelectionSchema } from "../schema/toolResultContext.js";
|
|
11
|
-
import { toolDiscoveryInputSchema, toolDiscoveryResultSchema, TOOL_DISCOVERY_REQUEST_TIMEOUT_MS } from "../schema/toolDiscovery.js";
|
|
12
|
-
export { toolDiscoveryInputSchema } from "../schema/toolDiscovery.js";
|
|
13
|
-
import { isRelvoProduct, PLATFORM_API_KEY_PATTERN, PLATFORM_CLIENT_HEADERS, PLATFORM_KEY_PREFIXES, PLATFORM_PRODUCT_NAMES, platformKeyProduct } from "../schema/platformApiKey.js";
|
|
14
|
-
import { readBoundedBody } from "../schema/boundedBody.js";
|
|
15
|
-
import { PLATFORM_META_ROUTES, PLATFORM_ROUTES, platformToolsPath as route } from "../schema/platformRoutes.js";
|
|
16
|
-
import { platformSkillsBundleSchema, platformSkillSchema } from "../schema/platformSkills.js";
|
|
17
|
-
export { platformSkillsBundleSchema, platformSkillSchema } from "../schema/platformSkills.js";
|
|
18
|
-
import { platformInputFileSchema, platformInputUploadSchema, PLATFORM_UPLOAD_MAX_BYTES } from "../schema/platformPublicExecution.js";
|
|
19
|
-
export { platformInputFileSchema, platformInputMimeSchema, PLATFORM_INPUT_MAX_BYTES } from "../schema/platformPublicExecution.js";
|
|
20
|
-
import { PLATFORM_EXECUTION_MAX_WAIT_SECONDS, PLATFORM_EXECUTION_RESPONSE_MAX_BYTES, platformCatalogSchema, platformQuoteSchema, platformExecutionSchema, platformExecutionContextSchema, platformExecutionResultPageSchema, platformExecutionIdSchema, platformCreateExecutionSchema, platformToolInputSchema, platformToolNameSchema, platformArtifactIdSchema, platformArtifactDownloadSchema, platformExecutionListQuerySchema, platformExecutionListSchema } from "../schema/platformPublicExecution.js";
|
|
21
|
-
import { PLATFORM_EXECUTION_CONTEXT_BUDGET_ERROR, PlatformExecutionContextBudgetError, platformExecutionResultPageContext } from "../schema/platformPublicExecution.js";
|
|
22
|
-
import { PLATFORM_META_LIMITS, platformMetaSearchSchema, platformMetaSearchResultSchema, platformMetaSchemasSchema, platformMetaSchemasResultSchema, platformMetaConnectionsSchema, platformMetaConnectionsResultSchema, platformMetaExecuteSchema, platformMetaExecuteResultSchema, platformMetaWorkbenchSchema, platformMetaBashSchema, platformMetaRemoteResultSchema } from "../schema/platformMetaTools.js";
|
|
23
|
-
export { PLATFORM_META_LIMITS, platformMetaSearchSchema, platformMetaSchemasSchema, platformMetaConnectionsSchema, platformMetaExecuteSchema, platformMetaWorkbenchSchema, platformMetaBashSchema } from "../schema/platformMetaTools.js";
|
|
24
|
-
export { platformCreateExecutionSchema, platformToolInputSchema, platformExecutionSchema, platformExecutionIdSchema, platformExecutionContextSchema, platformExecutionResultPageSchema, platformArtifactIdSchema, platformArtifactSchema, platformExecutionListQuerySchema } from "../schema/platformPublicExecution.js";
|
|
25
|
-
export const projectResultSchema = z.object({ data: z.object({ developerOrganizationId: z.string(), developerProjectId: z.string(), name: z.string() }), requestId: z.string() });
|
|
26
|
-
export const usageResultSchema = z.object({ data: z.object({ month: z.string(), requests: z.number().int().nonnegative(), amountCents: z.number().nonnegative(), currency: z.literal("usd") }), requestId: z.string() });
|
|
27
|
-
export const executionResultSchema = z.object({ data: platformExecutionSchema });
|
|
28
|
-
const executionContextResultSchema = z.object({ data: platformExecutionContextSchema });
|
|
29
|
-
const catalogResultSchema = z.object({ data: platformCatalogSchema });
|
|
30
|
-
export const platformToolDiscoverySchema = z.object({
|
|
31
|
-
query: z.string().trim().min(1).max(200).optional().describe("Substring of a tool's name, title, description, provider names or model IDs, ignoring case, whitespace, hyphens and underscores."),
|
|
32
|
-
category: platformCatalogSchema.shape.tools.element.shape.category.optional(),
|
|
33
|
-
provider: z.string().regex(/^[a-z][a-z0-9_]{1,63}$/).optional().describe("Filter discovery by a provider identifier from catalog metadata, such as anyapi or tikhub. Does not pin execution routing."),
|
|
34
|
-
availableOnly: z.boolean().optional().describe("Only tools with a configured provider; check executionEnabled separately."),
|
|
35
|
-
summary: z.boolean().optional().describe("Omit input schemas and examples; inspect a selected tool for those details."),
|
|
36
|
-
}).strict();
|
|
37
|
-
const quoteResultSchema = z.object({ data: platformQuoteSchema });
|
|
38
|
-
const idempotencySchema = z.string().regex(/^[A-Za-z0-9._:-]{8,128}$/);
|
|
39
|
-
function normalizeToolSearch(value) { return value.toLowerCase().replace(/[\s_-]+/g, ""); }
|
|
40
|
-
const publicErrors = {
|
|
41
|
-
platform_quote_changed: "The priceVersion is stale or the maximum charge is below the current quote. This request was not admitted. Request a new quote for the same arguments/provider, copy its priceVersion and use an authorized maximum covering it. Keep the same idempotency key.",
|
|
42
|
-
platform_idempotency_key_used_for_another_request: "This idempotency key was already used for another request. Keep the original body to retrieve its result.",
|
|
43
|
-
platform_executions_disabled: "Paid executions are paused.",
|
|
44
|
-
platform_provider_not_configured: "This tool's provider is unavailable.",
|
|
45
|
-
platform_provider_not_supported: "This provider does not support the selected tool and arguments. Choose Auto or a compatible provider from the catalog.",
|
|
46
|
-
platform_insufficient_balance_or_spending_limit_reached: "The prepaid balance or monthly spending limit is insufficient.",
|
|
47
|
-
platform_request_rate_exceeded: "The API key's request limit was reached. Wait before retrying.",
|
|
48
|
-
platform_api_key_suspended: "This API key is suspended for ignoring request limits. Wait for the suspension to end.",
|
|
49
|
-
platform_organization_suspended: "This organization's API access is suspended for ignoring request limits. Wait for the suspension to end.",
|
|
50
|
-
platform_authentication_unavailable: "Account verification is temporarily unavailable. Retry the same request shortly; the key does not need to change.",
|
|
51
|
-
platform_api_key_scope_required: "The API key is missing the required scope.",
|
|
52
|
-
platform_artifact_unavailable: "This output file is unavailable or its retention period has ended.",
|
|
53
|
-
platform_meta_tools_unavailable: "Session meta tools are unavailable on this API deployment.",
|
|
54
|
-
platform_tool_session_required: "Start a tool session with searchMetaTools and retain its session ID.",
|
|
55
|
-
platform_tool_session_unavailable: "This session is expired or unavailable to this project key. Preserve any uncertain purchases for reconciliation.",
|
|
56
|
-
platform_tool_session_limit_reached: "This key has reached its active tool session limit. Reuse an existing session or wait for expiry.",
|
|
57
|
-
platform_tool_session_busy: "This sandbox session has an active execution. Wait on that existing execution; needs_review requires reconciliation.",
|
|
58
|
-
platform_tool_session_state_lost: "This sandbox session has no reusable checkpoint. Preserve uncertain purchases and do not claim earlier state survives.",
|
|
59
|
-
[PLATFORM_EXECUTION_CONTEXT_BUDGET_ERROR]: "Execution metadata exceeds the context budget. Retain the execution ID to recover the raw result or select a narrower path.",
|
|
60
|
-
platform_invalid_tool_request: "The request does not match the quote/execution contract. Check tool, arguments, provider, sessionId, priceVersion and maximumChargeMicros.",
|
|
61
|
-
platform_invalid_tool_arguments: "The arguments do not match this tool's input schema. Inspect the tool's schema and quote again.",
|
|
62
|
-
platform_tool_unavailable: "This tool is not in this project's catalog. Search the catalog again for an available tool.",
|
|
63
|
-
platform_idempotency_key_required: "Paid submissions need an idempotency key of 8-128 letters, digits, '.', '_', ':' or '-'. Retain it for retries.",
|
|
64
|
-
platform_invalid_media_input: "A media input is invalid for this tool. Check its type, size and duration against the tool's schema.",
|
|
65
|
-
platform_media_type_mismatch: "The file content does not match its declared media type.",
|
|
66
|
-
platform_image_reference_required: "This tool needs a reference image as documented in its schema.",
|
|
67
|
-
platform_audio_duration_exceeds_quote: "The audio is longer than the quoted duration. Quote again with the actual duration.",
|
|
68
|
-
platform_input_file_unavailable: "The uploaded input file is unavailable or expired. Upload it again and use the new file ID.",
|
|
69
|
-
platform_upload_quota_exceeded: "This project's daily input upload allowance is used up. Wait before uploading more files.",
|
|
70
|
-
platform_media_source_unavailable: "A referenced media source could not be read. Use a public URL, a current project file or a project artifact.",
|
|
71
|
-
platform_model_tariff_unavailable: "This model has no current price and cannot be quoted. Choose another model.",
|
|
72
|
-
platform_connection_not_configured: "This tool needs a project account connection. Connect the account in the console, then retry.",
|
|
73
|
-
platform_connection_reauthorize: "The connected account must be authorized again in the console before this tool can use it.",
|
|
74
|
-
platform_connection_scope_required: "The connected account lacks a permission this operation needs. Reconnect it with that permission in the console.",
|
|
75
|
-
platform_invalid_json: "The request body is not valid JSON.",
|
|
76
|
-
platform_body_too_large: "The request body is too large.",
|
|
77
|
-
};
|
|
78
|
-
export class PlatformApiError extends Error {
|
|
79
|
-
status;
|
|
80
|
-
code;
|
|
81
|
-
constructor(status, code, productName = PLATFORM_PRODUCT_NAMES.relvo) {
|
|
82
|
-
super(`${productName} API request failed (${status}). ${code && Object.hasOwn(publicErrors, code) ? publicErrors[code] : "Check the developer key, project access and limits in the console."}`);
|
|
83
|
-
this.status = status;
|
|
84
|
-
this.code = code;
|
|
85
|
-
this.name = "PlatformApiError";
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
function contextBudgetFailure(error, id, kind) {
|
|
89
|
-
if (error instanceof PlatformApiError && error.code === PLATFORM_EXECUTION_CONTEXT_BUDGET_ERROR)
|
|
90
|
-
throw new PlatformExecutionContextBudgetError(id, kind);
|
|
91
|
-
throw error;
|
|
92
|
-
}
|
|
93
|
-
async function responseJson(response, maximumBytes, productName) {
|
|
94
|
-
const body = await readBoundedBody(response, maximumBytes);
|
|
95
|
-
if (body.status === "missing")
|
|
96
|
-
throw new Error(`The ${productName} API returned an empty response.`);
|
|
97
|
-
if (body.status === "too_large")
|
|
98
|
-
throw new Error(`The ${productName} API response is too large.`);
|
|
99
|
-
try {
|
|
100
|
-
return JSON.parse(new TextDecoder().decode(body.bytes));
|
|
101
|
-
}
|
|
102
|
-
catch {
|
|
103
|
-
throw new Error(`The ${productName} API returned an invalid response.`);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
export function relvoEnvironmentValue(env, name) {
|
|
107
|
-
const legacy = name.replace(/^RELVO_/, "TOOLHUB_"), value = env[name], previous = env[legacy];
|
|
108
|
-
if (value && previous && value !== previous)
|
|
109
|
-
throw new Error(`Conflicting ${name} and ${legacy}. No credentials were selected.`);
|
|
110
|
-
return value === undefined ? previous : value;
|
|
111
|
-
}
|
|
112
|
-
export function hasRelvoEnvironment(env) {
|
|
113
|
-
return ["RELVO_API_URL", "RELVO_API_KEY", "TOOLHUB_API_URL", "TOOLHUB_API_KEY"].some(name => env[name] !== undefined);
|
|
114
|
-
}
|
|
115
|
-
export function configFromEnvironment(env, source) {
|
|
116
|
-
if (hasRelvoEnvironment(env)) {
|
|
117
|
-
const apiUrl = relvoEnvironmentValue(env, "RELVO_API_URL"), apiKey = relvoEnvironmentValue(env, "RELVO_API_KEY");
|
|
118
|
-
if (!apiUrl || !apiKey)
|
|
119
|
-
throw new Error("Set RELVO_API_URL and RELVO_API_KEY from the Relvo console.");
|
|
120
|
-
return { apiUrl, apiKey, source, product: "relvo", local: relvoEnvironmentValue(env, "RELVO_ENV") === "local" };
|
|
121
|
-
}
|
|
122
|
-
if (!env.NOIMOS_PLATFORM_API_URL || !env.NOIMOS_PLATFORM_API_KEY) {
|
|
123
|
-
throw new Error(env.NOIMOS_PLATFORM_API_URL !== undefined || env.NOIMOS_PLATFORM_API_KEY !== undefined
|
|
124
|
-
? "Set NOIMOS_PLATFORM_API_URL and NOIMOS_PLATFORM_API_KEY from the developer console."
|
|
125
|
-
: "Set RELVO_API_URL and RELVO_API_KEY from the Relvo console.");
|
|
126
|
-
}
|
|
127
|
-
return { apiUrl: env.NOIMOS_PLATFORM_API_URL, apiKey: env.NOIMOS_PLATFORM_API_KEY, source, product: "noimos", local: env.NOIMOS_PLATFORM_ENV === "local" };
|
|
128
|
-
}
|
|
129
|
-
export class PlatformClient {
|
|
130
|
-
config;
|
|
131
|
-
transport;
|
|
132
|
-
baseUrl;
|
|
133
|
-
productName;
|
|
134
|
-
holdsReads;
|
|
135
|
-
constructor(config, transport = (input, init) => fetch(input, init)) {
|
|
136
|
-
this.config = config;
|
|
137
|
-
this.transport = transport;
|
|
138
|
-
const product = config.product ?? platformKeyProduct(config.apiKey);
|
|
139
|
-
this.productName = PLATFORM_PRODUCT_NAMES[product === null || isRelvoProduct(product) ? "relvo" : "noimos"];
|
|
140
|
-
this.holdsReads = product !== null && isRelvoProduct(product);
|
|
141
|
-
const url = new URL(config.apiUrl);
|
|
142
|
-
if (url.username || url.password || url.search || url.hash || (url.protocol !== "https:" && !(config.local && url.protocol === "http:" && ["127.0.0.1", "localhost", "[::1]"].includes(url.hostname))))
|
|
143
|
-
throw new Error(`The ${this.productName} API URL must use HTTPS. Explicit local mode permits loopback HTTP.`);
|
|
144
|
-
if (!PLATFORM_API_KEY_PATTERN.test(config.apiKey) || config.product && platformKeyProduct(config.apiKey) !== config.product)
|
|
145
|
-
throw new Error(`A dedicated ${PLATFORM_KEY_PREFIXES[config.product ?? "relvo"]} developer API key is required.`);
|
|
146
|
-
this.baseUrl = url.href.replace(/\/$/, "");
|
|
147
|
-
}
|
|
148
|
-
async request(path, schema, options) {
|
|
149
|
-
const body = options?.body === undefined ? undefined : JSON.stringify(options.body);
|
|
150
|
-
const upload = path === PLATFORM_ROUTES.upload, meta = PLATFORM_META_ROUTES.includes(path);
|
|
151
|
-
if (body && new TextEncoder().encode(body).byteLength > (upload ? PLATFORM_UPLOAD_MAX_BYTES : meta ? PLATFORM_META_LIMITS.requestBytes : 65536))
|
|
152
|
-
throw new Error(upload ? "The upload exceeds 12 MiB." : meta ? "The meta tool request exceeds 1 MiB." : "The tool request exceeds 64 KiB.");
|
|
153
|
-
let response;
|
|
154
|
-
try {
|
|
155
|
-
options?.signal?.throwIfAborted();
|
|
156
|
-
const timeout = AbortSignal.timeout(options?.timeoutMs ?? 20000);
|
|
157
|
-
response = await this.transport(`${this.baseUrl}${path}`, { method: options?.method ?? "GET", redirect: "error", signal: options?.signal ? AbortSignal.any([timeout, options.signal]) : timeout,
|
|
158
|
-
headers: { authorization: `Bearer ${this.config.apiKey}`, [PLATFORM_CLIENT_HEADERS[this.config.product ?? platformKeyProduct(this.config.apiKey) ?? "relvo"]]: this.config.source, "idempotency-key": options?.idempotencyKey ?? crypto.randomUUID(),
|
|
159
|
-
...(body === undefined ? {} : { "content-type": "application/json" }), ...(options?.prefer ? { prefer: options.prefer } : {}) }, ...(body === undefined ? {} : { body }) });
|
|
160
|
-
}
|
|
161
|
-
catch {
|
|
162
|
-
options?.signal?.throwIfAborted();
|
|
163
|
-
throw new Error(`The ${this.productName} API could not be reached. If submitting an execution, retry with the same body and idempotency key.`);
|
|
164
|
-
}
|
|
165
|
-
const maximumBytes = options?.maximumResponseBytes ?? (meta ? PLATFORM_META_LIMITS.contextBytes : PLATFORM_EXECUTION_RESPONSE_MAX_BYTES);
|
|
166
|
-
const payload = await responseJson(response, maximumBytes, this.productName).catch(() => {
|
|
167
|
-
options?.signal?.throwIfAborted();
|
|
168
|
-
if (!response.ok)
|
|
169
|
-
throw new PlatformApiError(response.status, null, this.productName);
|
|
170
|
-
throw new Error(`The ${this.productName} API returned an invalid response. If submitting an execution, retain the same body and idempotency key.`);
|
|
171
|
-
});
|
|
172
|
-
if (!response.ok) {
|
|
173
|
-
const parsed = z.object({ error: z.object({ message: z.string() }) }).safeParse(payload);
|
|
174
|
-
const code = parsed.success && Object.hasOwn(publicErrors, parsed.data.error.message) ? parsed.data.error.message : null;
|
|
175
|
-
throw new PlatformApiError(response.status, code, this.productName);
|
|
176
|
-
}
|
|
177
|
-
const parsed = schema.safeParse(payload);
|
|
178
|
-
if (!parsed.success)
|
|
179
|
-
throw new Error(`The ${this.productName} API returned an unexpected response. If submitting an execution, retain the same body and idempotency key.`);
|
|
180
|
-
return parsed.data;
|
|
181
|
-
}
|
|
182
|
-
project() { return this.request(route(PLATFORM_ROUTES.project), projectResultSchema); }
|
|
183
|
-
usage() { return this.request(route(PLATFORM_ROUTES.usage), usageResultSchema); }
|
|
184
|
-
connections(input = {}) {
|
|
185
|
-
const { provider, offset, limit } = platformConnectionCatalogQuerySchema.parse(input);
|
|
186
|
-
const query = new URLSearchParams({ offset: String(offset), limit: String(limit) });
|
|
187
|
-
if (provider)
|
|
188
|
-
query.set("provider", provider);
|
|
189
|
-
return this.request(`${route(PLATFORM_ROUTES.connections)}?${query}`, z.object({ data: platformConnectionCatalogSchema }), { maximumResponseBytes: PLATFORM_CONNECTION_CATALOG_LIMITS.responseBytes });
|
|
190
|
-
}
|
|
191
|
-
tools() { return this.request(route(PLATFORM_ROUTES.tools), catalogResultSchema); }
|
|
192
|
-
realtimeModels() { return this.request("/tools/realtime/models", z.object({ data: realtimeVoiceModelsSchema })); }
|
|
193
|
-
createRealtimeSession(input, signal) {
|
|
194
|
-
return this.request("/tools/realtime/sessions", z.object({ data: realtimeVoiceGrantSchema }), { method: "POST", body: realtimeVoiceConfigSchema.parse(input), signal });
|
|
195
|
-
}
|
|
196
|
-
async connectVoice(input, options = {}) {
|
|
197
|
-
const { data } = await this.createRealtimeSession(input, options.signal);
|
|
198
|
-
return connectRealtimeVoice(this.baseUrl, data, options);
|
|
199
|
-
}
|
|
200
|
-
async audioTools() {
|
|
201
|
-
const { data } = await this.tools();
|
|
202
|
-
return { data: { ...data, tools: data.tools.filter(tool => /(?:audio|speech|voice|music|sound)/.test(tool.name) || tool.name.startsWith("elevenlabs_")) } };
|
|
203
|
-
}
|
|
204
|
-
generateSpeech(input, idempotencyKey) { return this.createExecution({ ...input, tool: "generate_speech" }, idempotencyKey); }
|
|
205
|
-
transcribeAudio(input, idempotencyKey) { return this.createExecution({ ...input, tool: "transcribe_audio" }, idempotencyKey); }
|
|
206
|
-
translateAudio(input, idempotencyKey) { return this.createExecution({ ...input, tool: "translate_audio" }, idempotencyKey); }
|
|
207
|
-
generateAudioResponse(input, idempotencyKey) { return this.createExecution({ ...input, tool: "generate_audio_response" }, idempotencyKey); }
|
|
208
|
-
skills() { return this.request(PLATFORM_ROUTES.skills, z.object({ data: platformSkillsBundleSchema }), { maximumResponseBytes: 1048640 }); }
|
|
209
|
-
async skill(name) {
|
|
210
|
-
platformSkillSchema.shape.name.parse(name);
|
|
211
|
-
const { data } = await this.skills();
|
|
212
|
-
const skill = data.skills.find((entry) => entry.name === name);
|
|
213
|
-
if (!skill)
|
|
214
|
-
throw new Error(`The requested ${this.productName} guide is unavailable. Use platform_skills to discover current guides.`);
|
|
215
|
-
return { data: { version: data.version, ...skill } };
|
|
216
|
-
}
|
|
217
|
-
uploadInput(input) {
|
|
218
|
-
return this.request(PLATFORM_ROUTES.upload, z.object({ data: platformInputFileSchema }), { method: "POST", body: platformInputUploadSchema.parse(input) });
|
|
219
|
-
}
|
|
220
|
-
async discoverTools(options = {}) {
|
|
221
|
-
const { query, category, provider, availableOnly, summary } = platformToolDiscoverySchema.parse(options);
|
|
222
|
-
const { data } = await this.tools();
|
|
223
|
-
const search = query && normalizeToolSearch(query);
|
|
224
|
-
const tools = data.tools.filter((tool) => (!search || normalizeToolSearch(`${tool.name} ${tool.title ?? ""} ${tool.description} ${(tool.providers ?? []).map(row => row.name).join(" ")} ${(tool.models ?? []).map(row => row.id).join(" ")}`).includes(search)) &&
|
|
225
|
-
(!category || tool.category === category) && (!provider || tool.providers?.some(row => row.id === provider)) && (!availableOnly || tool.available));
|
|
226
|
-
return { data: { ...data, tools: summary ? tools.map(({ inputSchema, example, ...tool }) => tool) : tools } };
|
|
227
|
-
}
|
|
228
|
-
searchTools(options = {}) {
|
|
229
|
-
return this.request(PLATFORM_ROUTES.discover, z.object({ data: toolDiscoveryResultSchema }), { method: "POST", body: toolDiscoveryInputSchema.parse(options), timeoutMs: TOOL_DISCOVERY_REQUEST_TIMEOUT_MS });
|
|
230
|
-
}
|
|
231
|
-
searchMetaTools(input) { return this.request(PLATFORM_ROUTES.metaSearch, z.object({ data: platformMetaSearchResultSchema }), { method: "POST", body: platformMetaSearchSchema.parse(input), timeoutMs: PLATFORM_META_LIMITS.requestTimeoutMs }); }
|
|
232
|
-
getToolSchemas(input) { return this.request(PLATFORM_ROUTES.metaSchemas, z.object({ data: platformMetaSchemasResultSchema }), { method: "POST", body: platformMetaSchemasSchema.parse(input), timeoutMs: PLATFORM_META_LIMITS.requestTimeoutMs }); }
|
|
233
|
-
manageConnections(input) { return this.request(PLATFORM_ROUTES.metaConnections, z.object({ data: platformMetaConnectionsResultSchema }), { method: "POST", body: platformMetaConnectionsSchema.parse(input) }); }
|
|
234
|
-
multiExecute(input) { return this.request(PLATFORM_ROUTES.metaExecute, z.object({ data: platformMetaExecuteResultSchema }), { method: "POST", body: platformMetaExecuteSchema.parse(input), timeoutMs: PLATFORM_META_LIMITS.requestTimeoutMs }); }
|
|
235
|
-
remoteWorkbench(input) { return this.request(PLATFORM_ROUTES.metaWorkbench, z.object({ data: platformMetaRemoteResultSchema }), { method: "POST", body: platformMetaWorkbenchSchema.parse(input), timeoutMs: PLATFORM_META_LIMITS.requestTimeoutMs }); }
|
|
236
|
-
remoteBash(input) { return this.request(PLATFORM_ROUTES.metaBash, z.object({ data: platformMetaRemoteResultSchema }), { method: "POST", body: platformMetaBashSchema.parse(input), timeoutMs: PLATFORM_META_LIMITS.requestTimeoutMs }); }
|
|
237
|
-
inspectTool(name) {
|
|
238
|
-
platformToolNameSchema.parse(name);
|
|
239
|
-
const schema = platformCatalogSchema.shape.tools.element.extend(platformCatalogSchema.omit({ tools: true }).shape);
|
|
240
|
-
return this.request(route(PLATFORM_ROUTES.definition, { name: encodeURIComponent(name) }), z.object({ data: schema }));
|
|
241
|
-
}
|
|
242
|
-
async tool(name) {
|
|
243
|
-
try {
|
|
244
|
-
return await this.inspectTool(name);
|
|
245
|
-
}
|
|
246
|
-
catch (error) {
|
|
247
|
-
if (error instanceof PlatformApiError && error.code === "platform_tool_unavailable")
|
|
248
|
-
throw new Error(`Tool is unavailable in the ${this.productName} catalog. Run tools to discover supported operations.`);
|
|
249
|
-
throw error;
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
executions(input = {}) {
|
|
253
|
-
const parsed = platformExecutionListQuerySchema.parse(input);
|
|
254
|
-
const query = new URLSearchParams({ limit: String(parsed.limit) });
|
|
255
|
-
if (parsed.before)
|
|
256
|
-
query.set("before", parsed.before);
|
|
257
|
-
if (parsed.status)
|
|
258
|
-
query.set("status", parsed.status);
|
|
259
|
-
if (parsed.tool)
|
|
260
|
-
query.set("tool", parsed.tool);
|
|
261
|
-
return this.request(`${route(PLATFORM_ROUTES.executions)}?${query}`, z.object({ data: platformExecutionListSchema }));
|
|
262
|
-
}
|
|
263
|
-
quote(input) {
|
|
264
|
-
const parsed = platformToolInputSchema.safeParse(input);
|
|
265
|
-
if (!parsed.success)
|
|
266
|
-
throw new Error("Provide a tool name, a JSON object of arguments and an optional valid provider identifier.");
|
|
267
|
-
return this.request(route(PLATFORM_ROUTES.quote), quoteResultSchema, { method: "POST", body: parsed.data }).then(value => {
|
|
268
|
-
const receivedAt = Date.now(), expiresAt = Date.parse(value.data.expiresAt);
|
|
269
|
-
return { ...value, timing: { receivedAtUtc: new Date(receivedAt).toISOString(),
|
|
270
|
-
expiresInSeconds: Number.isFinite(expiresAt) ? Math.max(0, Math.floor((expiresAt - receivedAt) / 1000)) : null } };
|
|
271
|
-
});
|
|
272
|
-
}
|
|
273
|
-
createExecution(input, idempotencyKey) {
|
|
274
|
-
const parsed = platformCreateExecutionSchema.safeParse(input);
|
|
275
|
-
if (!parsed.success)
|
|
276
|
-
throw new Error("Provide a tool, arguments, the quote's priceVersion and maximumChargeMicros, and an optional valid provider identifier.");
|
|
277
|
-
if (!idempotencySchema.safeParse(idempotencyKey).success)
|
|
278
|
-
throw new Error("Keep an idempotency key of 8–128 letters, digits, periods, underscores, colons or hyphens for this execution.");
|
|
279
|
-
return this.request(route(PLATFORM_ROUTES.tool, { tool: parsed.data.tool }), executionResultSchema, { method: "POST", body: parsed.data, idempotencyKey });
|
|
280
|
-
}
|
|
281
|
-
execution(id, signal, waitMs = 0) {
|
|
282
|
-
if (!platformExecutionIdSchema.safeParse(id).success)
|
|
283
|
-
throw new Error("Provide a valid execution ID.");
|
|
284
|
-
return this.request(route(PLATFORM_ROUTES.execution, { id }), executionResultSchema, { signal, ...this.heldRead(waitMs) });
|
|
285
|
-
}
|
|
286
|
-
executionContext(id, signal, waitMs = 0) {
|
|
287
|
-
platformExecutionIdSchema.parse(id);
|
|
288
|
-
return this.request(route(PLATFORM_ROUTES.executionContext, { id }), executionContextResultSchema, { signal, maximumResponseBytes: TOOL_RESULT_CONTEXT_MAX_BYTES + 65536, ...this.heldRead(waitMs) })
|
|
289
|
-
.then(result => executionReceiptContext(result)).catch(error => contextBudgetFailure(error, id, "execution"));
|
|
290
|
-
}
|
|
291
|
-
heldRead(waitMs) {
|
|
292
|
-
const seconds = this.holdsReads ? Math.min(PLATFORM_EXECUTION_MAX_WAIT_SECONDS, Math.floor(waitMs / 1000)) : 0;
|
|
293
|
-
return seconds > 0 ? { prefer: `wait=${seconds}`, timeoutMs: seconds * 1000 + 20000 } : {};
|
|
294
|
-
}
|
|
295
|
-
readExecutionResult(id, selection = {}) {
|
|
296
|
-
platformExecutionIdSchema.parse(id);
|
|
297
|
-
const body = toolResultSelectionSchema.parse(selection);
|
|
298
|
-
return this.request(route(PLATFORM_ROUTES.executionResult, { id }), z.object({ data: platformExecutionResultPageSchema }), { method: "POST", body, maximumResponseBytes: TOOL_RESULT_CONTEXT_MAX_BYTES + 65536 })
|
|
299
|
-
.then(result => platformExecutionResultPageContext(result, { readWith: "readExecutionResult" }))
|
|
300
|
-
.catch(error => contextBudgetFailure(error, id, "page"));
|
|
301
|
-
}
|
|
302
|
-
waitExecution(id, timeoutMs = 20000, signal) {
|
|
303
|
-
return this.waitForExecution(id, timeoutMs, signal, (executionId, abort, waitMs) => this.execution(executionId, abort, waitMs));
|
|
304
|
-
}
|
|
305
|
-
waitExecutionContext(id, timeoutMs = 20000, signal) {
|
|
306
|
-
return this.waitForExecution(id, timeoutMs, signal, (executionId, abort, waitMs) => this.executionContext(executionId, abort, waitMs));
|
|
307
|
-
}
|
|
308
|
-
async waitForExecution(id, timeoutMs, signal, read) {
|
|
309
|
-
platformExecutionIdSchema.parse(id);
|
|
310
|
-
if (!Number.isInteger(timeoutMs) || timeoutMs < 1000 || timeoutMs > 900000)
|
|
311
|
-
throw new Error("Wait must be between 1 and 900 seconds.");
|
|
312
|
-
const controller = new AbortController();
|
|
313
|
-
const timeout = new Error("Execution wait timed out; use the same execution ID to continue reading.");
|
|
314
|
-
const timer = setTimeout(() => controller.abort(timeout), timeoutMs);
|
|
315
|
-
const abort = () => controller.abort(signal?.reason);
|
|
316
|
-
signal?.addEventListener("abort", abort, { once: true });
|
|
317
|
-
if (signal?.aborted)
|
|
318
|
-
abort();
|
|
319
|
-
let latest = null;
|
|
320
|
-
const until = Date.now() + timeoutMs;
|
|
321
|
-
try {
|
|
322
|
-
for (;;) {
|
|
323
|
-
controller.signal.throwIfAborted();
|
|
324
|
-
const asked = Date.now();
|
|
325
|
-
latest = (await read(id, controller.signal, Math.max(0, until - asked - 1000))).data;
|
|
326
|
-
if (!["queued", "running", "waiting"].includes(latest.status))
|
|
327
|
-
return { data: latest, id, timedOut: false };
|
|
328
|
-
const pauseMs = this.holdsReads ? Math.max(0, 5000 - (Date.now() - asked)) : 5000;
|
|
329
|
-
if (pauseMs > 0)
|
|
330
|
-
await new Promise((resolve, reject) => {
|
|
331
|
-
const done = () => { controller.signal.removeEventListener("abort", cancelled); resolve(); };
|
|
332
|
-
const pause = setTimeout(done, pauseMs);
|
|
333
|
-
const cancelled = () => { clearTimeout(pause); controller.signal.removeEventListener("abort", cancelled); reject(controller.signal.reason); };
|
|
334
|
-
controller.signal.addEventListener("abort", cancelled, { once: true });
|
|
335
|
-
if (controller.signal.aborted)
|
|
336
|
-
cancelled();
|
|
337
|
-
});
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
catch (error) {
|
|
341
|
-
if (error !== timeout)
|
|
342
|
-
throw error;
|
|
343
|
-
return { data: latest, id, timedOut: true };
|
|
344
|
-
}
|
|
345
|
-
finally {
|
|
346
|
-
clearTimeout(timer);
|
|
347
|
-
signal?.removeEventListener("abort", abort);
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
cancelExecution(id) {
|
|
351
|
-
if (!platformExecutionIdSchema.safeParse(id).success)
|
|
352
|
-
throw new Error("Provide a valid execution ID.");
|
|
353
|
-
return this.request(route(PLATFORM_ROUTES.executionCancel, { id }), executionResultSchema, { method: "POST" });
|
|
354
|
-
}
|
|
355
|
-
artifactDownload(id, artifactId) {
|
|
356
|
-
if (!platformExecutionIdSchema.safeParse(id).success || !platformArtifactIdSchema.safeParse(artifactId).success)
|
|
357
|
-
throw new Error("Provide valid execution and artifact IDs.");
|
|
358
|
-
return this.request(route(PLATFORM_ROUTES.artifactDownload, { id, artifact: artifactId }), z.object({ data: platformArtifactDownloadSchema }));
|
|
359
|
-
}
|
|
360
|
-
}
|
|
1
|
+
export * from "./browser.js";
|
|
2
|
+
export { loadEvmWallet } from "./evm-wallet.js";
|
|
3
|
+
export { walletSignIn, walletTopUp, walletAutoTopUp, walletAutoTopUpFromEnvironment, WalletAccessError } from "./wallet-access.js";
|
|
@@ -1,28 +1,35 @@
|
|
|
1
1
|
import { open } from "node:fs/promises";
|
|
2
2
|
import { constants } from "node:fs";
|
|
3
3
|
import { extname } from "node:path";
|
|
4
|
-
import { platformInputMimeSchema, PLATFORM_INPUT_MAX_BYTES } from "./index.js";
|
|
5
|
-
const extensions = { ".mp3": "audio/mpeg", ".wav": "audio/wav", ".mp4": "video/mp4", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".png": "image/png", ".webp": "image/webp"
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
import { platformInputMimeSchema, platformInputFileMimeSchema, PLATFORM_INPUT_MAX_BYTES } from "./index.js";
|
|
5
|
+
const extensions = { ".mp3": "audio/mpeg", ".wav": "audio/wav", ".mp4": "video/mp4", ".mov": "video/quicktime", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".png": "image/png", ".webp": "image/webp",
|
|
6
|
+
".gif": "image/gif", ".bmp": "image/bmp", ".pdf": "application/pdf", ".csv": "text/csv", ".tsv": "text/tab-separated-values", ".txt": "text/plain", ".json": "application/json", ".xml": "application/xml",
|
|
7
|
+
".zip": "application/zip", ".gz": "application/gzip", ".glb": "model/gltf-binary", ".gltf": "model/gltf+json", ".srt": "application/x-subrip", ".vtt": "text/vtt", ".bin": "application/octet-stream" };
|
|
8
|
+
export async function uploadLocalInput(client, path, options = {}) {
|
|
9
|
+
const mimeType = platformInputFileMimeSchema.safeParse(options.mimeType ?? extensions[extname(path).toLowerCase()]);
|
|
8
10
|
if (!mimeType.success)
|
|
9
|
-
throw new Error("Supported input files
|
|
11
|
+
throw new Error("Supported input files include images, audio, video, documents, archives and models; other extensions require an explicit MIME type.");
|
|
12
|
+
const range = options.byteRange;
|
|
13
|
+
if (range && (!Number.isSafeInteger(range.start) || !Number.isSafeInteger(range.end) || range.start < 0 || range.end < range.start || range.end - range.start >= PLATFORM_INPUT_MAX_BYTES))
|
|
14
|
+
throw new Error("Input byte range must be nonnegative and at most 12 MiB.");
|
|
15
|
+
const purpose = options.purpose ?? (range || !platformInputMimeSchema.safeParse(mimeType.data).success ? "file" : undefined);
|
|
10
16
|
let data;
|
|
11
17
|
try {
|
|
12
18
|
const file = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
13
19
|
try {
|
|
14
20
|
const stat = await file.stat();
|
|
15
|
-
if (!stat.isFile() || stat.size < 1 || stat.size > PLATFORM_INPUT_MAX_BYTES)
|
|
21
|
+
if (!stat.isFile() || stat.size < 1 || (range ? range.end >= stat.size : stat.size > PLATFORM_INPUT_MAX_BYTES))
|
|
16
22
|
throw new Error("Invalid file");
|
|
17
|
-
const
|
|
23
|
+
const size = range ? range.end - range.start + 1 : stat.size;
|
|
24
|
+
const buffer = Buffer.alloc(size + (range ? 0 : 1));
|
|
18
25
|
let offset = 0;
|
|
19
26
|
while (offset < buffer.length) {
|
|
20
|
-
const { bytesRead } = await file.read(buffer, offset, buffer.length - offset, null);
|
|
27
|
+
const { bytesRead } = await file.read(buffer, offset, buffer.length - offset, range ? range.start + offset : null);
|
|
21
28
|
if (!bytesRead)
|
|
22
29
|
break;
|
|
23
30
|
offset += bytesRead;
|
|
24
31
|
}
|
|
25
|
-
if (offset !==
|
|
32
|
+
if (offset !== size)
|
|
26
33
|
throw new Error("File changed");
|
|
27
34
|
data = buffer.subarray(0, offset).toString("base64");
|
|
28
35
|
}
|
|
@@ -31,7 +38,7 @@ export async function uploadLocalInput(client, path) {
|
|
|
31
38
|
}
|
|
32
39
|
}
|
|
33
40
|
catch {
|
|
34
|
-
throw new Error("Input must be an existing regular file, at most 12 MiB. Symlinks are not accepted.");
|
|
41
|
+
throw new Error("Input must be an existing regular file or a selected chunk, at most 12 MiB. Symlinks are not accepted.");
|
|
35
42
|
}
|
|
36
|
-
return client.uploadInput({ mimeType: mimeType.data, data });
|
|
43
|
+
return client.uploadInput({ mimeType: mimeType.data, data, ...(purpose ? { purpose } : {}) });
|
|
37
44
|
}
|