@tangle-network/agent-app 0.60.196 → 0.60.198
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chat-routes/index.js +2 -1
- package/dist/chat-routes/index.js.map +1 -1
- package/dist/chunk-EGBNNTAL.js +474 -0
- package/dist/chunk-EGBNNTAL.js.map +1 -0
- package/dist/chunk-O7UX55EU.js +62 -0
- package/dist/chunk-O7UX55EU.js.map +1 -0
- package/dist/{chunk-L5JLOC2F.js → chunk-QDKY5TBG.js} +1 -59
- package/dist/chunk-QDKY5TBG.js.map +1 -0
- package/dist/sandbox/index.js +2 -1
- package/dist/tools/index.js +8 -6
- package/dist/workspace-apps/cloud-artifact-origin.d.ts +94 -0
- package/dist/workspace-apps/cloud-artifacts.d.ts +135 -0
- package/dist/workspace-apps/index.d.ts +4 -0
- package/dist/workspace-apps/index.js +55 -0
- package/dist/workspace-apps/index.js.map +1 -1
- package/dist/workspace-apps/react.d.ts +25 -0
- package/dist/workspace-apps/react.js +65 -0
- package/dist/workspace-apps/react.js.map +1 -0
- package/package.json +6 -1
- package/dist/chunk-L5JLOC2F.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tools/capability.ts"],"sourcesContent":["/**\n * Per-user capability token — the sandbox→app auth primitive behind the\n * `verifyToken` seam in {@link authenticateToolRequest}.\n *\n * An app-agent runs inside the sandbox and reaches the host app back over HTTP\n * (the app tools, the integration-invoke bridge). The route must act AS the\n * connecting user without trusting any model-supplied identity, so the turn\n * mints a short HMAC token bound to the user id and bakes it into the per-turn\n * MCP server header; the route verifies it to recover the user.\n *\n * `HMAC-SHA256(secret, \"user:<userId>\")`, base64url, with an app-chosen prefix.\n * The token encodes no scopes — the hub's policy engine authorizes per action.\n * Fail-closed: with no secret, no token is minted (the caller MUST omit the MCP\n * server rather than fake an authorized call). WebCrypto only — runs on\n * Workers, Node, and the browser with no Node `crypto` dependency.\n */\n\nimport {\n base64UrlDecodeText,\n base64UrlEncodeText,\n constantTimeEqual,\n hmacSha256Base64Url,\n} from '../crypto/web-token'\n\n/** Define options for creating and verifying capability tokens including secret and prefix */\nexport interface CapabilityTokenOptions {\n /** Shared HMAC secret. When absent, mint returns undefined / verify returns false. */\n secret?: string\n /** Token prefix (namespaces the credential; lets verify reject foreign tokens\n * cheaply). Default `cap_`. */\n prefix?: string\n}\n\n/** Mint a capability token for `userId`, or `undefined` when no secret is\n * configured (fail-closed — the caller omits the MCP server rather than fake it). */\nexport async function createCapabilityToken(userId: string, opts: CapabilityTokenOptions): Promise<string | undefined> {\n const secret = opts.secret?.trim()\n if (!secret) return undefined\n const prefix = opts.prefix ?? 'cap_'\n return `${prefix}${await sign(userId, secret)}`\n}\n\n/** Verify a capability token against `userId`. Returns false (never throws) for\n * an unconfigured secret, a wrong prefix, a malformed token, or a mismatch. */\nexport async function verifyCapabilityToken(userId: string, token: string, opts: CapabilityTokenOptions): Promise<boolean> {\n const secret = opts.secret?.trim()\n const prefix = opts.prefix ?? 'cap_'\n if (!secret || !token.startsWith(prefix)) return false\n const expected = `${prefix}${await sign(userId, secret)}`\n return constantTimeEqual(token, expected)\n}\n\n/** Define options for capability tokens that expire after a specified lifetime in milliseconds */\nexport interface ExpiringCapabilityTokenOptions extends CapabilityTokenOptions {\n /** Token lifetime. Expired tokens verify false regardless of signature. */\n expiresInMs: number\n /** Clock injection for tests; defaults to Date.now. */\n now?: () => number\n}\n\n/**\n * Mint an EXPIRING capability token: `<prefix><base64url(payload)>.<sig>` where\n * the payload carries `{ sub, exp, n }` (subject, epoch-ms expiry, random\n * nonce) and the signature is HMAC-SHA256 over the encoded payload. Use this\n * for user-initiated scoped channels (e.g. a per-sequence MCP endpoint) where\n * a captured token must not stay valid past its window; the bare\n * {@link createCapabilityToken} remains for turn-scoped tool bridges whose\n * mint+verify happen inside one request cycle. Fail-closed like the bare\n * variant: no secret → no token.\n */\nexport async function createExpiringCapabilityToken(subject: string, opts: ExpiringCapabilityTokenOptions): Promise<string | undefined> {\n const secret = opts.secret?.trim()\n if (!secret) return undefined\n if (!Number.isFinite(opts.expiresInMs) || opts.expiresInMs <= 0) throw new Error('expiresInMs must be a positive number')\n const prefix = opts.prefix ?? 'cap_'\n const now = opts.now ?? Date.now\n const payload = base64UrlEncodeText(JSON.stringify({ sub: subject, exp: now() + opts.expiresInMs, n: crypto.randomUUID() }))\n return `${prefix}${payload}.${await hmacSha256Base64Url(payload, secret)}`\n}\n\n/** Verify an expiring token against `subject`: prefix, payload integrity,\n * subject match, and expiry all checked; returns false (never throws) on any\n * failure including a malformed payload. */\nexport async function verifyExpiringCapabilityToken(subject: string, token: string, opts: CapabilityTokenOptions & { now?: () => number }): Promise<boolean> {\n const secret = opts.secret?.trim()\n const prefix = opts.prefix ?? 'cap_'\n if (!secret || !token.startsWith(prefix)) return false\n const body = token.slice(prefix.length)\n const dot = body.lastIndexOf('.')\n if (dot <= 0 || dot === body.length - 1) return false\n const payload = body.slice(0, dot)\n const sig = body.slice(dot + 1)\n if (!constantTimeEqual(sig, await hmacSha256Base64Url(payload, secret))) return false\n let parsed: { sub?: unknown; exp?: unknown }\n try {\n parsed = JSON.parse(base64UrlDecodeText(payload)) as { sub?: unknown; exp?: unknown }\n } catch {\n return false\n }\n if (parsed.sub !== subject) return false\n if (typeof parsed.exp !== 'number') return false\n const now = opts.now ?? Date.now\n return parsed.exp > now()\n}\n\nasync function sign(userId: string, secret: string): Promise<string> {\n return hmacSha256Base64Url(`user:${userId}`, secret)\n}\n"],"mappings":";;;;;;;;AAmCA,eAAsB,sBAAsB,QAAgB,MAA2D;AACrH,QAAM,SAAS,KAAK,QAAQ,KAAK;AACjC,MAAI,CAAC,OAAQ,QAAO;AACpB,QAAM,SAAS,KAAK,UAAU;AAC9B,SAAO,GAAG,MAAM,GAAG,MAAM,KAAK,QAAQ,MAAM,CAAC;AAC/C;AAIA,eAAsB,sBAAsB,QAAgB,OAAe,MAAgD;AACzH,QAAM,SAAS,KAAK,QAAQ,KAAK;AACjC,QAAM,SAAS,KAAK,UAAU;AAC9B,MAAI,CAAC,UAAU,CAAC,MAAM,WAAW,MAAM,EAAG,QAAO;AACjD,QAAM,WAAW,GAAG,MAAM,GAAG,MAAM,KAAK,QAAQ,MAAM,CAAC;AACvD,SAAO,kBAAkB,OAAO,QAAQ;AAC1C;AAoBA,eAAsB,8BAA8B,SAAiB,MAAmE;AACtI,QAAM,SAAS,KAAK,QAAQ,KAAK;AACjC,MAAI,CAAC,OAAQ,QAAO;AACpB,MAAI,CAAC,OAAO,SAAS,KAAK,WAAW,KAAK,KAAK,eAAe,EAAG,OAAM,IAAI,MAAM,uCAAuC;AACxH,QAAM,SAAS,KAAK,UAAU;AAC9B,QAAM,MAAM,KAAK,OAAO,KAAK;AAC7B,QAAM,UAAU,oBAAoB,KAAK,UAAU,EAAE,KAAK,SAAS,KAAK,IAAI,IAAI,KAAK,aAAa,GAAG,OAAO,WAAW,EAAE,CAAC,CAAC;AAC3H,SAAO,GAAG,MAAM,GAAG,OAAO,IAAI,MAAM,oBAAoB,SAAS,MAAM,CAAC;AAC1E;AAKA,eAAsB,8BAA8B,SAAiB,OAAe,MAAyE;AAC3J,QAAM,SAAS,KAAK,QAAQ,KAAK;AACjC,QAAM,SAAS,KAAK,UAAU;AAC9B,MAAI,CAAC,UAAU,CAAC,MAAM,WAAW,MAAM,EAAG,QAAO;AACjD,QAAM,OAAO,MAAM,MAAM,OAAO,MAAM;AACtC,QAAM,MAAM,KAAK,YAAY,GAAG;AAChC,MAAI,OAAO,KAAK,QAAQ,KAAK,SAAS,EAAG,QAAO;AAChD,QAAM,UAAU,KAAK,MAAM,GAAG,GAAG;AACjC,QAAM,MAAM,KAAK,MAAM,MAAM,CAAC;AAC9B,MAAI,CAAC,kBAAkB,KAAK,MAAM,oBAAoB,SAAS,MAAM,CAAC,EAAG,QAAO;AAChF,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,oBAAoB,OAAO,CAAC;AAAA,EAClD,QAAQ;AACN,WAAO;AAAA,EACT;AACA,MAAI,OAAO,QAAQ,QAAS,QAAO;AACnC,MAAI,OAAO,OAAO,QAAQ,SAAU,QAAO;AAC3C,QAAM,MAAM,KAAK,OAAO,KAAK;AAC7B,SAAO,OAAO,MAAM,IAAI;AAC1B;AAEA,eAAe,KAAK,QAAgB,QAAiC;AACnE,SAAO,oBAAoB,QAAQ,MAAM,IAAI,MAAM;AACrD;","names":[]}
|
|
@@ -5,12 +5,6 @@ import {
|
|
|
5
5
|
import {
|
|
6
6
|
assertRenderUiSchema
|
|
7
7
|
} from "./chunk-2RCPPOQ4.js";
|
|
8
|
-
import {
|
|
9
|
-
base64UrlDecodeText,
|
|
10
|
-
base64UrlEncodeText,
|
|
11
|
-
constantTimeEqual,
|
|
12
|
-
hmacSha256Base64Url
|
|
13
|
-
} from "./chunk-S5SRJJQG.js";
|
|
14
8
|
import {
|
|
15
9
|
buildAppToolOpenAITools,
|
|
16
10
|
findCustomTool,
|
|
@@ -20,54 +14,6 @@ import {
|
|
|
20
14
|
ToolInputError
|
|
21
15
|
} from "./chunk-E65YV7VV.js";
|
|
22
16
|
|
|
23
|
-
// src/tools/capability.ts
|
|
24
|
-
async function createCapabilityToken(userId, opts) {
|
|
25
|
-
const secret = opts.secret?.trim();
|
|
26
|
-
if (!secret) return void 0;
|
|
27
|
-
const prefix = opts.prefix ?? "cap_";
|
|
28
|
-
return `${prefix}${await sign(userId, secret)}`;
|
|
29
|
-
}
|
|
30
|
-
async function verifyCapabilityToken(userId, token, opts) {
|
|
31
|
-
const secret = opts.secret?.trim();
|
|
32
|
-
const prefix = opts.prefix ?? "cap_";
|
|
33
|
-
if (!secret || !token.startsWith(prefix)) return false;
|
|
34
|
-
const expected = `${prefix}${await sign(userId, secret)}`;
|
|
35
|
-
return constantTimeEqual(token, expected);
|
|
36
|
-
}
|
|
37
|
-
async function createExpiringCapabilityToken(subject, opts) {
|
|
38
|
-
const secret = opts.secret?.trim();
|
|
39
|
-
if (!secret) return void 0;
|
|
40
|
-
if (!Number.isFinite(opts.expiresInMs) || opts.expiresInMs <= 0) throw new Error("expiresInMs must be a positive number");
|
|
41
|
-
const prefix = opts.prefix ?? "cap_";
|
|
42
|
-
const now = opts.now ?? Date.now;
|
|
43
|
-
const payload = base64UrlEncodeText(JSON.stringify({ sub: subject, exp: now() + opts.expiresInMs, n: crypto.randomUUID() }));
|
|
44
|
-
return `${prefix}${payload}.${await hmacSha256Base64Url(payload, secret)}`;
|
|
45
|
-
}
|
|
46
|
-
async function verifyExpiringCapabilityToken(subject, token, opts) {
|
|
47
|
-
const secret = opts.secret?.trim();
|
|
48
|
-
const prefix = opts.prefix ?? "cap_";
|
|
49
|
-
if (!secret || !token.startsWith(prefix)) return false;
|
|
50
|
-
const body = token.slice(prefix.length);
|
|
51
|
-
const dot = body.lastIndexOf(".");
|
|
52
|
-
if (dot <= 0 || dot === body.length - 1) return false;
|
|
53
|
-
const payload = body.slice(0, dot);
|
|
54
|
-
const sig = body.slice(dot + 1);
|
|
55
|
-
if (!constantTimeEqual(sig, await hmacSha256Base64Url(payload, secret))) return false;
|
|
56
|
-
let parsed;
|
|
57
|
-
try {
|
|
58
|
-
parsed = JSON.parse(base64UrlDecodeText(payload));
|
|
59
|
-
} catch {
|
|
60
|
-
return false;
|
|
61
|
-
}
|
|
62
|
-
if (parsed.sub !== subject) return false;
|
|
63
|
-
if (typeof parsed.exp !== "number") return false;
|
|
64
|
-
const now = opts.now ?? Date.now;
|
|
65
|
-
return parsed.exp > now();
|
|
66
|
-
}
|
|
67
|
-
async function sign(userId, secret) {
|
|
68
|
-
return hmacSha256Base64Url(`user:${userId}`, secret);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
17
|
// src/tools/gating.ts
|
|
72
18
|
function resolveToolCapabilities(opts) {
|
|
73
19
|
const { taxonomy, capabilities, enabled } = opts;
|
|
@@ -259,10 +205,6 @@ async function handleAppToolRequest(request, opts) {
|
|
|
259
205
|
}
|
|
260
206
|
|
|
261
207
|
export {
|
|
262
|
-
createCapabilityToken,
|
|
263
|
-
verifyCapabilityToken,
|
|
264
|
-
createExpiringCapabilityToken,
|
|
265
|
-
verifyExpiringCapabilityToken,
|
|
266
208
|
resolveToolCapabilities,
|
|
267
209
|
restrictTaxonomy,
|
|
268
210
|
dispatchAppTool,
|
|
@@ -270,4 +212,4 @@ export {
|
|
|
270
212
|
createAppToolRuntimeExecutor,
|
|
271
213
|
handleAppToolRequest
|
|
272
214
|
};
|
|
273
|
-
//# sourceMappingURL=chunk-
|
|
215
|
+
//# sourceMappingURL=chunk-QDKY5TBG.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tools/gating.ts","../src/tools/dispatch.ts","../src/tools/runtime.ts","../src/tools/http.ts"],"sourcesContent":["/**\n * Capability gating — compose an agent session's tool surface from a\n * product-defined capability registry.\n *\n * Products that let users pick what an agent can do (a \"Studio\" agent with the\n * full build toolset vs a clean \"Assistant\" with none) all need the same\n * mechanics: a registry of named capabilities, each unlocking proposal types\n * and/or named tool groups, resolved against the product's\n * {@link AppToolTaxonomy} into the concrete tools to expose. The mechanics are\n * generic and live here; the capability VOCABULARY (ids, labels, which\n * proposal types, which tool groups) is the product's.\n */\n\nimport type { AppToolTaxonomy } from './types'\n\n/** One toggleable tool group in a product's capability registry. */\nexport interface ToolCapability {\n id: string\n label: string\n description: string\n /** Proposal types this capability unlocks (intersected with the taxonomy,\n * so a capability can never widen the product's proposal surface). */\n proposalTypes?: readonly string[]\n /** Unlocks every taxonomy proposal type beyond the base set — the domain's\n * specialized long tail (risk_assessment, vuln_report, …). */\n domainActions?: boolean\n /** Named product tool groups (e.g. 'sandbox', 'integrations') this\n * capability unlocks. The vocabulary is the product's; the resolver only\n * unions them. */\n toolGroups?: readonly string[]\n}\n\n/** Resolve options for determining tool capabilities based on taxonomy, capabilities, and enabled IDs */\nexport interface ResolveToolCapabilitiesOptions {\n taxonomy: AppToolTaxonomy\n /** The product's full capability registry. */\n capabilities: readonly ToolCapability[]\n /** Enabled capability ids. `undefined` means full access (legacy callers\n * that don't send a capability set); an explicit `[]` means a pure chat\n * agent with no tools. Unknown ids are ignored. */\n enabled: readonly string[] | undefined\n /** The shared base proposal types `domainActions` excludes. Defaults to\n * every type some capability names explicitly via `proposalTypes` — i.e.\n * \"domain actions\" are the taxonomy types no capability claims. */\n baseProposalTypes?: readonly string[]\n}\n\n/** Describe resolved capabilities including proposal types and product tool groups to expose */\nexport interface ResolvedToolCapabilities {\n /** Proposal types to keep — feed to {@link restrictTaxonomy}. */\n proposalTypes: string[]\n /** Product tool groups to expose (deduped union across enabled caps). */\n toolGroups: string[]\n}\n\n/**\n * Resolve an enabled capability-id set against a taxonomy into the concrete\n * tool surface. Fail-closed: only types present in the taxonomy survive, and\n * an empty `enabled` set yields no tools at all.\n */\nexport function resolveToolCapabilities(\n opts: ResolveToolCapabilitiesOptions,\n): ResolvedToolCapabilities {\n const { taxonomy, capabilities, enabled } = opts\n if (enabled === undefined) {\n return {\n proposalTypes: [...taxonomy.proposalTypes],\n toolGroups: [...new Set(capabilities.flatMap((c) => c.toolGroups ?? []))],\n }\n }\n const base = new Set(\n opts.baseProposalTypes ?? capabilities.flatMap((c) => c.proposalTypes ?? []),\n )\n const domainTypes = taxonomy.proposalTypes.filter((t) => !base.has(t))\n const byId = new Map(capabilities.map((c) => [c.id, c]))\n\n const proposalTypes = new Set<string>()\n const toolGroups = new Set<string>()\n for (const id of enabled) {\n const cap = byId.get(id)\n if (!cap) continue\n for (const t of cap.proposalTypes ?? []) {\n if (taxonomy.proposalTypes.includes(t)) proposalTypes.add(t)\n }\n if (cap.domainActions) for (const t of domainTypes) proposalTypes.add(t)\n for (const g of cap.toolGroups ?? []) toolGroups.add(g)\n }\n return { proposalTypes: [...proposalTypes], toolGroups: [...toolGroups] }\n}\n\n/**\n * Restrict a taxonomy to a subset of proposal types, intersecting the\n * regulated subset too — the regulated label survives restriction, so a\n * narrowed agent can never launder a regulated type into an unregulated one.\n */\nexport function restrictTaxonomy(\n taxonomy: AppToolTaxonomy,\n allowed: readonly string[],\n): AppToolTaxonomy {\n const allow = new Set(allowed)\n return {\n proposalTypes: taxonomy.proposalTypes.filter((t) => allow.has(t)),\n regulatedTypes: taxonomy.regulatedTypes.filter((t) => allow.has(t)),\n }\n}\n","import { ToolInputError } from './errors'\nimport { isAppToolName } from './openai'\nimport { findCustomTool, type AppToolDefinition } from './registry'\nimport { assertRenderUiSchema } from './render-ui-schema'\nimport type {\n AppToolContext,\n AppToolHandlers,\n AppToolOutcome,\n AppToolProducedEvent,\n AppToolTaxonomy,\n} from './types'\n\n/** Define options for dispatching tools including handlers, taxonomy, custom tools, and approval policies */\nexport interface DispatchOptions {\n handlers: AppToolHandlers\n taxonomy: AppToolTaxonomy\n /** Product-registered tools beyond the four built-ins. A called name that is\n * not a built-in is dispatched to the matching {@link AppToolDefinition.execute}\n * through this same validation/outcome path. */\n customTools?: readonly AppToolDefinition[]\n /** Per-call approval policy. When provided it OVERRIDES the static\n * `taxonomy.regulatedTypes` membership check, so products can gate by\n * cost threshold, environment, or first-use instead of always/never.\n * Fail-closed: a predicate that throws counts as \"approval required\". */\n needsApproval?: (type: string, args: { title: string; description: string | null }, ctx: AppToolContext) => boolean | Promise<boolean>\n /** Called at the real side-effect site for proposals (proposal_created) and\n * generated views (artifact) so a consumer's completion oracle credits\n * persisted state. Omit when produced state isn't tracked. */\n onProduced?: (event: AppToolProducedEvent) => void\n}\n\n/**\n * The ONE place an app-tool call is validated, dispatched to the product's\n * handler, and turned into an {@link AppToolOutcome} + produced events. Shared\n * by the HTTP route layer and the agent-runtime executor so both paths apply\n * identical validation and identical side effects. A {@link ToolInputError}\n * (bad input the agent can correct) and any other throw both become\n * `{ ok: false }` — a tool call never silently \"succeeds\" without its effect.\n */\nexport async function dispatchAppTool(\n toolName: string,\n rawArgs: Record<string, unknown>,\n ctx: AppToolContext,\n opts: DispatchOptions,\n): Promise<AppToolOutcome> {\n try {\n if (!isAppToolName(toolName)) {\n const custom = findCustomTool(toolName, opts.customTools)\n if (!custom) return { ok: false, code: 'unknown_tool', message: `${toolName} is not an app tool.` }\n // Custom tools own their own arg validation (their execute throws\n // ToolInputError for correctable input); the outer try/catch maps it.\n const result = await custom.execute(rawArgs, ctx)\n return { ok: true, result }\n }\n\n if (toolName === 'submit_proposal') {\n const type = String(rawArgs.type ?? '').trim()\n const title = String(rawArgs.title ?? '').trim()\n if (!type || !opts.taxonomy.proposalTypes.includes(type)) {\n return { ok: false, code: 'invalid_type', message: `type must be one of: ${opts.taxonomy.proposalTypes.join(', ')}.` }\n }\n if (!title) return { ok: false, code: 'missing_title', message: 'title is required.' }\n const description = rawArgs.description == null ? null : String(rawArgs.description)\n // Approval policy runs BEFORE the handler so the decision can gate the\n // side effect itself, not merely re-label it afterwards.\n let regulated = opts.taxonomy.regulatedTypes.includes(type)\n if (opts.needsApproval) {\n try {\n regulated = await opts.needsApproval(type, { title, description }, ctx)\n } catch {\n regulated = true // fail-closed: a broken policy means approval required\n }\n }\n const r = await opts.handlers.submitProposal({ type, title, description, regulated }, ctx)\n // Pass the handler's result through: products with immediate-execute\n // proposal types return status 'executed' plus their own fields\n // (e.g. datasetId) — the model must see what actually happened, not a\n // hard-coded \"queued for approval\".\n const { proposalId, deduped, status, ...extra } = r\n const effectiveStatus = status ?? 'queued_for_approval'\n opts.onProduced?.({\n type: 'proposal_created',\n proposalId,\n title,\n status: effectiveStatus === 'executed' ? 'executed' : 'pending',\n content: description ?? undefined,\n })\n return { ok: true, result: { ...extra, status: effectiveStatus, proposalId, deduped, regulated } }\n }\n\n if (toolName === 'schedule_followup') {\n const r = await opts.handlers.scheduleFollowup(\n { title: String(rawArgs.title ?? ''), dueDate: String(rawArgs.dueDate ?? ''), priority: rawArgs.priority as string | undefined },\n ctx,\n )\n return { ok: true, result: { followupId: r.id, dueDate: r.dueDate, deduped: r.deduped } }\n }\n\n if (toolName === 'render_ui') {\n assertRenderUiSchema(rawArgs.schema)\n const r = await opts.handlers.renderUi({ title: String(rawArgs.title ?? ''), schema: rawArgs.schema }, ctx)\n opts.onProduced?.({ type: 'artifact', path: r.path, content: r.content })\n return { ok: true, result: { path: r.path } }\n }\n\n // add_citation\n const r = await opts.handlers.addCitation(\n { path: String(rawArgs.path ?? ''), quote: String(rawArgs.quote ?? ''), label: rawArgs.label as string | undefined },\n ctx,\n )\n return { ok: true, result: { citationId: r.citationId, path: r.path } }\n } catch (err) {\n if (err instanceof ToolInputError) return { ok: false, code: err.code, message: err.message, status: err.status }\n return { ok: false, code: 'app_tool_error', message: err instanceof Error ? err.message : String(err), status: 500 }\n }\n}\n\n/** HTTP status for a failed outcome — the handler's `ToolInputError.status`\n * when present, else 400 for a validation reject. */\nexport function outcomeStatus(outcome: Extract<AppToolOutcome, { ok: false }>): number {\n return outcome.status ?? 400\n}\n","import { dispatchAppTool, type DispatchOptions } from './dispatch'\nimport type { AppToolContext, AppToolOutcome } from './types'\n\n/** Executes an app-tool call the model emits on the agent-runtime chat path.\n * Plug into `runChatThroughRuntime({ appToolExecutor })` (or any loop that\n * dispatches function tool_calls). */\nexport type AppToolRuntimeExecutor = (call: {\n toolName: string\n args: Record<string, unknown>\n}) => Promise<AppToolOutcome>\n\n/** Define options for executing runtime tasks with a trusted per-turn context */\nexport interface RuntimeExecutorOptions extends DispatchOptions {\n /** The trusted per-turn context — supplied directly (not from headers), since\n * the runtime path has no HTTP request. */\n ctx: AppToolContext\n}\n\n/**\n * Build the runtime executor for one turn. The agent-runtime backend must also\n * advertise the tools (`buildAppToolOpenAITools(taxonomy)` on the backend's\n * `tools`) for the model to call them; this executor fulfils each call against\n * the product's handlers and emits produced events via `opts.onProduced`.\n */\nexport function createAppToolRuntimeExecutor(opts: RuntimeExecutorOptions): AppToolRuntimeExecutor {\n return ({ toolName, args }) => dispatchAppTool(toolName, args, opts.ctx, opts)\n}\n","import { authenticateToolRequest, type ToolHeaderNames } from './auth'\nimport { dispatchAppTool, outcomeStatus, type DispatchOptions } from './dispatch'\nimport { buildAppToolOpenAITools, type AppToolName } from './openai'\nimport type { AppToolDefinition } from './registry'\nimport { createMcpToolHandler, type McpToolCallContent } from './mcp-rpc'\nimport type { AppToolOutcome } from './types'\n\n/** Define options for handling tool requests including tool identification and token verification */\nexport interface HandleToolRequestOptions extends DispatchOptions {\n /** Which app tool this route serves — a built-in name or a product-registered\n * {@link AppToolDefinition} (auto-added to `customTools` for dispatch). */\n tool: AppToolName | AppToolDefinition\n /** Verify the bearer capability token belongs to the header user. */\n verifyToken: (userId: string, bearer: string) => Promise<boolean>\n headerNames?: ToolHeaderNames\n /** Optional model-facing description used by the MCP tools/list response. */\n description?: string\n /** Product-specific values for the schedule_followup.priority schema. */\n priorityValues?: readonly string[]\n /** Optional success-message builder for a friendlier tool result. */\n message?: (result: unknown) => string\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nfunction isMcpRequest(request: Request, value: unknown): boolean {\n // A valid request declares jsonrpc. An incomplete envelope has no reliable\n // body marker, so use the Streamable HTTP Accept contract as the explicit\n // transport discriminator before falling back to direct tool arguments.\n const acceptsMcp = request.headers\n .get('accept')\n ?.split(',')\n .some((part) => part.trim().toLowerCase() === 'text/event-stream')\n return (\n isRecord(value)\n && (\n 'jsonrpc' in value\n || acceptsMcp === true\n || request.headers.has('mcp-session-id')\n || request.headers.has('mcp-protocol-version')\n )\n )\n}\n\nfunction toolName(tool: AppToolName | AppToolDefinition): string {\n return typeof tool === 'string' ? tool : tool.name\n}\n\nfunction dispatchOptions(opts: HandleToolRequestOptions): DispatchOptions {\n return {\n ...opts,\n customTools:\n typeof opts.tool === 'string' ? opts.customTools : [...(opts.customTools ?? []), opts.tool],\n }\n}\n\nfunction mcpToolManifest(opts: HandleToolRequestOptions): {\n name: string\n description: string\n inputSchema: Record<string, unknown>\n} {\n const name = toolName(opts.tool)\n const tools = buildAppToolOpenAITools(opts.taxonomy, {\n ...(opts.description && typeof opts.tool === 'string'\n ? { descriptions: { [opts.tool]: opts.description } }\n : {}),\n ...(opts.priorityValues ? { priorityValues: opts.priorityValues } : {}),\n ...(typeof opts.tool === 'string' ? {} : { customTools: [opts.tool] }),\n })\n const definition = tools.find((entry) => entry.function.name === name)\n if (!definition) {\n throw new Error(`No MCP manifest exists for app tool ${name}`)\n }\n return {\n name,\n description: definition.function.description,\n inputSchema: definition.function.parameters,\n }\n}\n\nfunction mcpToolResult(outcome: AppToolOutcome, opts: HandleToolRequestOptions): McpToolCallContent {\n if (!outcome.ok) {\n return {\n content: [{ type: 'text', text: `${outcome.code}: ${outcome.message}` }],\n isError: true,\n }\n }\n\n const value = outcome.result\n const payload = value !== null && typeof value === 'object' && !Array.isArray(value)\n ? { ok: true, ...value }\n : { ok: true, value }\n return {\n content: [{ type: 'text', text: JSON.stringify({\n ...payload,\n ...(opts.message ? { message: opts.message(value) } : {}),\n }) }],\n }\n}\n\nfunction createAppToolMcpHandler(\n authContext: Parameters<DispatchOptions['handlers']['submitProposal']>[1],\n opts: HandleToolRequestOptions,\n): (request: Request) => Promise<Response> {\n const manifest = mcpToolManifest(opts)\n return createMcpToolHandler({\n serverInfo: { name: 'agent-app-tool', version: '1' },\n tools: [{\n ...manifest,\n run: (args) => dispatchAppTool(manifest.name, args, authContext, dispatchOptions(opts)),\n }],\n buildEnv: () => ({}),\n formatResult: (result) => mcpToolResult(result as AppToolOutcome, opts),\n })\n}\n\n/**\n * Handle one app-tool HTTP request end to end — the sandbox MCP path. The\n * agent's per-turn HTTP MCP server POSTs here; this authenticates (header user\n * + capability token), reads the args (MCP-alias tolerant), dispatches to the\n * product handler, and returns a JSON Response. A product's route file becomes\n * a one-liner: `export const action = ({ request }) => handleAppToolRequest(request, cfg)`.\n */\nexport async function handleAppToolRequest(request: Request, opts: HandleToolRequestOptions): Promise<Response> {\n if (request.method !== 'POST') return Response.json({ error: 'Method not allowed' }, { status: 405 })\n\n const auth = await authenticateToolRequest(request, { verifyToken: opts.verifyToken, headerNames: opts.headerNames })\n if (!auth.ok) return auth.response\n\n let body: unknown\n try {\n body = await request.clone().json()\n } catch {\n return Response.json({ error: 'Invalid JSON' }, { status: 400 })\n }\n\n // The same route supports the direct app-tool HTTP shape used by the\n // OpenAI/runtime surfaces and stateless Streamable HTTP MCP JSON-RPC used by\n // OpenCode. Keeping both at this boundary prevents every consumer from\n // writing a second route just to translate protocol envelopes.\n if (isMcpRequest(request, body)) {\n return createAppToolMcpHandler(auth.ctx, opts)(request)\n }\n\n if (!isRecord(body)) return Response.json({ error: 'Invalid JSON' }, { status: 400 })\n const args = (body.args ?? body.arguments ?? body) as Record<string, unknown>\n\n // A custom tool passed as `tool` is registered for this dispatch so the route\n // file stays a one-liner (no separate customTools wiring needed).\n const toolName = typeof opts.tool === 'string' ? opts.tool : opts.tool.name\n const outcome = await dispatchAppTool(toolName, args, auth.ctx, dispatchOptions(opts))\n if (!outcome.ok) {\n return Response.json({ error: outcome.code, message: outcome.message }, { status: outcomeStatus(outcome) })\n }\n const payload = outcome.result as Record<string, unknown>\n return Response.json({ ok: true, ...payload, ...(opts.message ? { message: opts.message(outcome.result) } : {}) })\n}\n"],"mappings":"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","names":["toolName","r","toolName","toolName"]}
|
package/dist/sandbox/index.js
CHANGED
|
@@ -138,8 +138,9 @@ import {
|
|
|
138
138
|
} from "../chunk-4LBCTFBP.js";
|
|
139
139
|
import "../chunk-VBRUG4N5.js";
|
|
140
140
|
import "../chunk-LWSJK546.js";
|
|
141
|
-
import "../chunk-
|
|
141
|
+
import "../chunk-QDKY5TBG.js";
|
|
142
142
|
import "../chunk-6A7MYOUI.js";
|
|
143
|
+
import "../chunk-O7UX55EU.js";
|
|
143
144
|
import "../chunk-2RCPPOQ4.js";
|
|
144
145
|
import "../chunk-S5SRJJQG.js";
|
|
145
146
|
import "../chunk-IJAXOQJU.js";
|
package/dist/tools/index.js
CHANGED
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createAppToolRuntimeExecutor,
|
|
3
|
-
createCapabilityToken,
|
|
4
|
-
createExpiringCapabilityToken,
|
|
5
3
|
dispatchAppTool,
|
|
6
4
|
handleAppToolRequest,
|
|
7
5
|
outcomeStatus,
|
|
8
6
|
resolveToolCapabilities,
|
|
9
|
-
restrictTaxonomy
|
|
10
|
-
|
|
11
|
-
verifyExpiringCapabilityToken
|
|
12
|
-
} from "../chunk-L5JLOC2F.js";
|
|
7
|
+
restrictTaxonomy
|
|
8
|
+
} from "../chunk-QDKY5TBG.js";
|
|
13
9
|
import {
|
|
14
10
|
DEFAULT_APP_TOOL_PATHS,
|
|
15
11
|
DEFAULT_HEADER_NAMES,
|
|
@@ -22,6 +18,12 @@ import {
|
|
|
22
18
|
readToolArgs,
|
|
23
19
|
unresolvableSurfaceCredential
|
|
24
20
|
} from "../chunk-6A7MYOUI.js";
|
|
21
|
+
import {
|
|
22
|
+
createCapabilityToken,
|
|
23
|
+
createExpiringCapabilityToken,
|
|
24
|
+
verifyCapabilityToken,
|
|
25
|
+
verifyExpiringCapabilityToken
|
|
26
|
+
} from "../chunk-O7UX55EU.js";
|
|
25
27
|
import "../chunk-2RCPPOQ4.js";
|
|
26
28
|
import "../chunk-S5SRJJQG.js";
|
|
27
29
|
import {
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The artifact origin: the only place cloud artifact HTML and scripts run.
|
|
3
|
+
*
|
|
4
|
+
* Mount it on a host of its own on a different site from the product (for
|
|
5
|
+
* GTM, `gtm-artifacts.tangle.sh` against `gtm.tangle.tools`), so a page an
|
|
6
|
+
* agent wrote never shares cookies, storage or same-site requests with a
|
|
7
|
+
* signed-in product session. Every response also carries a CSP `sandbox`
|
|
8
|
+
* without `allow-same-origin`, so each document runs in an opaque origin and
|
|
9
|
+
* one artifact cannot read another's storage.
|
|
10
|
+
*
|
|
11
|
+
* A request names `/<artifactId>/<grant>/<token>/<path>`. The token is a
|
|
12
|
+
* short-lived capability minted by the product after it authorized the viewer;
|
|
13
|
+
* `resolve` returns the record only while that grant is still live, so a
|
|
14
|
+
* revoked share stops serving on its next request.
|
|
15
|
+
*
|
|
16
|
+
* Files are streamed from the object store. For a `worker` artifact, a request
|
|
17
|
+
* that matches no file runs `_worker.js` in a Worker Loader isolate with
|
|
18
|
+
* `globalOutbound: null` (no network), CPU and subrequest limits, and only the
|
|
19
|
+
* bindings `workerEnv` grants. Its response headers are replaced with this
|
|
20
|
+
* origin's, so the isolate cannot loosen them.
|
|
21
|
+
*/
|
|
22
|
+
import type { ObjectStore } from '../object-store';
|
|
23
|
+
import { type CloudArtifactGrant, type CloudArtifactRecord } from './cloud-artifacts';
|
|
24
|
+
/** Script and style CDNs a generated page may load. Everything else is local or inline. */
|
|
25
|
+
export declare const CLOUD_ARTIFACT_SCRIPT_HOSTS: readonly ['https://cdn.jsdelivr.net', 'https://cdnjs.cloudflare.com', 'https://unpkg.com'];
|
|
26
|
+
/** The iframe sandbox for an artifact frame; never add `allow-same-origin`. */
|
|
27
|
+
export declare const CLOUD_ARTIFACT_FRAME_SANDBOX = "allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox allow-modals allow-downloads";
|
|
28
|
+
/** The Content-Security-Policy every artifact response carries. */
|
|
29
|
+
export declare function cloudArtifactContentSecurityPolicy(input: {
|
|
30
|
+
origin: string;
|
|
31
|
+
frameAncestors: readonly string[];
|
|
32
|
+
}): string;
|
|
33
|
+
/** The code shape Worker Loader's `get` callback returns (Cloudflare `WorkerCode`). */
|
|
34
|
+
export interface CloudArtifactWorkerCode {
|
|
35
|
+
compatibilityDate: string;
|
|
36
|
+
compatibilityFlags?: string[];
|
|
37
|
+
mainModule: string;
|
|
38
|
+
modules: Record<string, string | {
|
|
39
|
+
js: string;
|
|
40
|
+
} | {
|
|
41
|
+
json: unknown;
|
|
42
|
+
} | {
|
|
43
|
+
text: string;
|
|
44
|
+
}>;
|
|
45
|
+
env?: Record<string, unknown>;
|
|
46
|
+
globalOutbound: null;
|
|
47
|
+
limits?: {
|
|
48
|
+
cpuMs?: number;
|
|
49
|
+
subRequests?: number;
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/** Structural `env.LOADER` (a `worker_loaders` binding), so workers-types stays out of the public types. */
|
|
53
|
+
export interface CloudArtifactWorkerLoader {
|
|
54
|
+
get(id: string, load: () => Promise<CloudArtifactWorkerCode>): {
|
|
55
|
+
getEntrypoint(name?: string | null, options?: {
|
|
56
|
+
limits?: {
|
|
57
|
+
cpuMs?: number;
|
|
58
|
+
subRequests?: number;
|
|
59
|
+
};
|
|
60
|
+
}): {
|
|
61
|
+
fetch(request: Request): Promise<Response>;
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export interface CloudArtifactOriginOptions {
|
|
66
|
+
/** This origin, such as `https://gtm-artifacts.tangle.sh`. Requests for another host get 404. */
|
|
67
|
+
origin: string;
|
|
68
|
+
/** Product origins allowed to frame an artifact (CSP `frame-ancestors`). */
|
|
69
|
+
frameAncestors: readonly string[];
|
|
70
|
+
/** The secret the product signs view links with. */
|
|
71
|
+
secret: string;
|
|
72
|
+
store: ObjectStore;
|
|
73
|
+
/**
|
|
74
|
+
* The artifact's current record for a verified link, or null when it no
|
|
75
|
+
* longer exists or the grant is no longer live (a revoked share).
|
|
76
|
+
*/
|
|
77
|
+
resolve(view: {
|
|
78
|
+
artifactId: string;
|
|
79
|
+
grant: CloudArtifactGrant;
|
|
80
|
+
}): Promise<CloudArtifactRecord | null>;
|
|
81
|
+
/** The `worker_loaders` binding. Without it a `worker` artifact serves only its files. */
|
|
82
|
+
loader?: CloudArtifactWorkerLoader;
|
|
83
|
+
/** Bindings granted to a `worker` artifact's isolate; nothing else reaches it. */
|
|
84
|
+
workerEnv?(record: CloudArtifactRecord, grant: CloudArtifactGrant): Record<string, unknown>;
|
|
85
|
+
workerLimits?: {
|
|
86
|
+
cpuMs: number;
|
|
87
|
+
subRequests: number;
|
|
88
|
+
};
|
|
89
|
+
/** Compatibility date for isolates. */
|
|
90
|
+
workerCompatibilityDate?: string;
|
|
91
|
+
now?: () => number;
|
|
92
|
+
}
|
|
93
|
+
/** Serve cloud artifacts on their own origin. */
|
|
94
|
+
export declare function createCloudArtifactOrigin(options: CloudArtifactOriginOptions): (request: Request) => Promise<Response>;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import type { ObjectStore } from '../object-store';
|
|
2
|
+
import type { WorkspaceRole } from '../teams/roles';
|
|
3
|
+
export type CloudArtifactKind = 'static' | 'worker' | 'openui';
|
|
4
|
+
/** One stored file of a version. Bytes live at {@link cloudArtifactObjectKey}. */
|
|
5
|
+
export interface CloudArtifactFile {
|
|
6
|
+
/** Relative to the bundle root, such as `index.html` or `images/hero.png`. */
|
|
7
|
+
path: string;
|
|
8
|
+
contentType: string;
|
|
9
|
+
size: number;
|
|
10
|
+
/** Hex SHA-256 of the bytes; identical files share one stored object. */
|
|
11
|
+
sha256: string;
|
|
12
|
+
}
|
|
13
|
+
/** An immutable snapshot. Its id changes when any path, type or byte changes. */
|
|
14
|
+
export interface CloudArtifactVersion {
|
|
15
|
+
id: string;
|
|
16
|
+
kind: CloudArtifactKind;
|
|
17
|
+
/** The file a link opens: HTML for `static` and `worker`, JSON for `openui`. */
|
|
18
|
+
entry: string;
|
|
19
|
+
files: CloudArtifactFile[];
|
|
20
|
+
createdAt: string;
|
|
21
|
+
}
|
|
22
|
+
/** What the origin needs from the product's record. Products persist more. */
|
|
23
|
+
export interface CloudArtifactRecord {
|
|
24
|
+
id: string;
|
|
25
|
+
workspaceId: string;
|
|
26
|
+
title: string;
|
|
27
|
+
version: CloudArtifactVersion;
|
|
28
|
+
}
|
|
29
|
+
/** Who a view link was minted for: a workspace member, or a public share. */
|
|
30
|
+
export type CloudArtifactGrant = {
|
|
31
|
+
type: 'member';
|
|
32
|
+
} | {
|
|
33
|
+
type: 'share';
|
|
34
|
+
shareId: string;
|
|
35
|
+
};
|
|
36
|
+
/** Opening an artifact needs `viewer`; creating or revoking a public link needs this role. */
|
|
37
|
+
export declare const CLOUD_ARTIFACT_SHARE_ROLE: WorkspaceRole;
|
|
38
|
+
/** The module a `worker` bundle runs, at the bundle root (the Cloudflare Pages name). */
|
|
39
|
+
export declare const CLOUD_ARTIFACT_WORKER_MODULE = "_worker.js";
|
|
40
|
+
export declare const CLOUD_ARTIFACT_MAX_FILES = 200;
|
|
41
|
+
export declare const CLOUD_ARTIFACT_MAX_FILE_BYTES: number;
|
|
42
|
+
export declare const CLOUD_ARTIFACT_MAX_BYTES: number;
|
|
43
|
+
/** Member view links are re-minted on every page load, so they can be short. */
|
|
44
|
+
export declare const CLOUD_ARTIFACT_VIEW_TTL_MS: number;
|
|
45
|
+
export type CloudArtifactErrorCode = 'invalid_path' | 'duplicate_path' | 'unsupported_type' | 'too_large' | 'too_many_files' | 'missing_entry' | 'invalid_entry';
|
|
46
|
+
export declare class CloudArtifactError extends Error {
|
|
47
|
+
readonly code: CloudArtifactErrorCode;
|
|
48
|
+
constructor(code: CloudArtifactErrorCode, message: string);
|
|
49
|
+
}
|
|
50
|
+
/** The served type for a path, or null when cloud artifacts do not serve it. */
|
|
51
|
+
export declare function cloudArtifactContentType(path: string): string | null;
|
|
52
|
+
/** A bundle path: relative, no `.` or `..` segments, no control or URL-special characters. */
|
|
53
|
+
export declare function isCloudArtifactPath(path: string): boolean;
|
|
54
|
+
export interface CloudArtifactInputFile {
|
|
55
|
+
path: string;
|
|
56
|
+
body: Uint8Array | string;
|
|
57
|
+
}
|
|
58
|
+
export interface PreparedCloudArtifact {
|
|
59
|
+
version: CloudArtifactVersion;
|
|
60
|
+
/** Bytes to store, by SHA-256. Carried files are already stored and absent here. */
|
|
61
|
+
bodies: ReadonlyMap<string, Uint8Array>;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Validate and hash a bundle. `carry` keeps files of an earlier version that
|
|
65
|
+
* the new source could not supply, such as images that existed only on a box
|
|
66
|
+
* that has since been recycled; their bytes are already stored.
|
|
67
|
+
*/
|
|
68
|
+
export declare function prepareCloudArtifact(input: {
|
|
69
|
+
files: readonly CloudArtifactInputFile[];
|
|
70
|
+
entry: string;
|
|
71
|
+
/** Set for a declarative OpenUI view; otherwise `_worker.js` makes it `worker`. */
|
|
72
|
+
openui?: boolean;
|
|
73
|
+
carry?: readonly CloudArtifactFile[];
|
|
74
|
+
createdAt?: string;
|
|
75
|
+
}): Promise<PreparedCloudArtifact>;
|
|
76
|
+
/** Content-addressed per workspace, so erasing a workspace deletes one prefix. */
|
|
77
|
+
export declare function cloudArtifactObjectKey(workspaceId: string, sha256Hex: string): string;
|
|
78
|
+
/** The prefix holding every stored byte of a workspace's artifacts. */
|
|
79
|
+
export declare function cloudArtifactWorkspacePrefix(workspaceId: string): string;
|
|
80
|
+
/** Store a prepared version's new bytes; objects already present are not rewritten. */
|
|
81
|
+
export declare function storeCloudArtifact(store: ObjectStore, workspaceId: string, prepared: PreparedCloudArtifact): Promise<CloudArtifactVersion>;
|
|
82
|
+
/** Resolve a document-relative reference to a bundle path, or null when it is not a local file. */
|
|
83
|
+
export declare function resolveCloudArtifactReference(reference: string, documentPath: string): string | null;
|
|
84
|
+
/** Local files an HTML, CSS or JavaScript document refers to, as bundle paths. */
|
|
85
|
+
export declare function cloudArtifactReferences(text: string, documentPath: string): string[];
|
|
86
|
+
/**
|
|
87
|
+
* Gather an entry and every local file it refers to, transitively, plus
|
|
88
|
+
* `_worker.js` and its imports when the root has one. `readMany` returns the
|
|
89
|
+
* bytes of the paths it found (paths are bundle-relative); missing files are
|
|
90
|
+
* reported, not fatal, so a page with one broken image still publishes.
|
|
91
|
+
*/
|
|
92
|
+
export declare function collectCloudArtifactBundle(input: {
|
|
93
|
+
entry: string;
|
|
94
|
+
readMany(paths: readonly string[]): Promise<ReadonlyMap<string, Uint8Array>>;
|
|
95
|
+
maxFiles?: number;
|
|
96
|
+
maxBytes?: number;
|
|
97
|
+
}): Promise<{
|
|
98
|
+
files: CloudArtifactInputFile[];
|
|
99
|
+
missing: string[];
|
|
100
|
+
}>;
|
|
101
|
+
/** A random 32-hex id for an artifact or a share. */
|
|
102
|
+
export declare function createCloudArtifactId(): string;
|
|
103
|
+
/**
|
|
104
|
+
* A short-lived URL on the artifact origin for one artifact and one grant.
|
|
105
|
+
* Every file of the bundle sits under the same prefix, so relative links work.
|
|
106
|
+
* The product mints it only after it has authorized the viewer.
|
|
107
|
+
*/
|
|
108
|
+
export declare function createCloudArtifactViewUrl(input: {
|
|
109
|
+
origin: string;
|
|
110
|
+
secret: string;
|
|
111
|
+
artifactId: string;
|
|
112
|
+
grant: CloudArtifactGrant;
|
|
113
|
+
path: string;
|
|
114
|
+
ttlMs?: number;
|
|
115
|
+
now?: () => number;
|
|
116
|
+
}): Promise<string>;
|
|
117
|
+
export interface CloudArtifactView {
|
|
118
|
+
artifactId: string;
|
|
119
|
+
grant: CloudArtifactGrant;
|
|
120
|
+
/** Decoded bundle path; empty when the URL names the root. */
|
|
121
|
+
path: string;
|
|
122
|
+
}
|
|
123
|
+
/** Read and verify a view URL on the artifact origin; null for anything forged, expired or malformed. */
|
|
124
|
+
export declare function readCloudArtifactViewUrl(url: URL, options: {
|
|
125
|
+
secret: string;
|
|
126
|
+
now?: () => number;
|
|
127
|
+
}): Promise<CloudArtifactView | null>;
|
|
128
|
+
/**
|
|
129
|
+
* The secret part of a public link: `<shareId>.<HMAC>`. It is derived, so the
|
|
130
|
+
* product stores only the share id and can show the link again; revoking the
|
|
131
|
+
* share row turns the link off at once.
|
|
132
|
+
*/
|
|
133
|
+
export declare function createCloudArtifactShareToken(shareId: string, secret: string): Promise<string>;
|
|
134
|
+
/** The share id a public link names, or null when its signature does not verify. */
|
|
135
|
+
export declare function readCloudArtifactShareToken(token: string, secret: string): Promise<string | null>;
|
|
@@ -72,3 +72,7 @@ export type WorkspaceAppBuilderInstructionsOptions = {
|
|
|
72
72
|
export declare function workspaceAppBuilderInstructions(options: WorkspaceAppBuilderInstructionsOptions): string[];
|
|
73
73
|
export { WorkspaceAppDataConflict, createWorkspaceAppDataClient, createWorkspaceAppDataHost, } from './data-bridge';
|
|
74
74
|
export type { WorkspaceAppDataClient, WorkspaceAppDataClientOptions, WorkspaceAppDataEntry, WorkspaceAppDataHost, WorkspaceAppDataHostOptions, } from './data-bridge';
|
|
75
|
+
export { CLOUD_ARTIFACT_MAX_BYTES, CLOUD_ARTIFACT_MAX_FILES, CLOUD_ARTIFACT_MAX_FILE_BYTES, CLOUD_ARTIFACT_SHARE_ROLE, CLOUD_ARTIFACT_VIEW_TTL_MS, CLOUD_ARTIFACT_WORKER_MODULE, CloudArtifactError, cloudArtifactContentType, cloudArtifactObjectKey, cloudArtifactReferences, cloudArtifactWorkspacePrefix, collectCloudArtifactBundle, createCloudArtifactId, createCloudArtifactShareToken, createCloudArtifactViewUrl, isCloudArtifactPath, prepareCloudArtifact, readCloudArtifactShareToken, readCloudArtifactViewUrl, resolveCloudArtifactReference, storeCloudArtifact, } from './cloud-artifacts';
|
|
76
|
+
export type { CloudArtifactErrorCode, CloudArtifactFile, CloudArtifactGrant, CloudArtifactInputFile, CloudArtifactKind, CloudArtifactRecord, CloudArtifactVersion, CloudArtifactView, PreparedCloudArtifact, } from './cloud-artifacts';
|
|
77
|
+
export { CLOUD_ARTIFACT_FRAME_SANDBOX, CLOUD_ARTIFACT_SCRIPT_HOSTS, cloudArtifactContentSecurityPolicy, createCloudArtifactOrigin, } from './cloud-artifact-origin';
|
|
78
|
+
export type { CloudArtifactOriginOptions, CloudArtifactWorkerCode, CloudArtifactWorkerLoader, } from './cloud-artifact-origin';
|
|
@@ -1,3 +1,33 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CLOUD_ARTIFACT_FRAME_SANDBOX,
|
|
3
|
+
CLOUD_ARTIFACT_MAX_BYTES,
|
|
4
|
+
CLOUD_ARTIFACT_MAX_FILES,
|
|
5
|
+
CLOUD_ARTIFACT_MAX_FILE_BYTES,
|
|
6
|
+
CLOUD_ARTIFACT_SCRIPT_HOSTS,
|
|
7
|
+
CLOUD_ARTIFACT_SHARE_ROLE,
|
|
8
|
+
CLOUD_ARTIFACT_VIEW_TTL_MS,
|
|
9
|
+
CLOUD_ARTIFACT_WORKER_MODULE,
|
|
10
|
+
CloudArtifactError,
|
|
11
|
+
cloudArtifactContentSecurityPolicy,
|
|
12
|
+
cloudArtifactContentType,
|
|
13
|
+
cloudArtifactObjectKey,
|
|
14
|
+
cloudArtifactReferences,
|
|
15
|
+
cloudArtifactWorkspacePrefix,
|
|
16
|
+
collectCloudArtifactBundle,
|
|
17
|
+
createCloudArtifactId,
|
|
18
|
+
createCloudArtifactOrigin,
|
|
19
|
+
createCloudArtifactShareToken,
|
|
20
|
+
createCloudArtifactViewUrl,
|
|
21
|
+
isCloudArtifactPath,
|
|
22
|
+
prepareCloudArtifact,
|
|
23
|
+
readCloudArtifactShareToken,
|
|
24
|
+
readCloudArtifactViewUrl,
|
|
25
|
+
resolveCloudArtifactReference,
|
|
26
|
+
storeCloudArtifact
|
|
27
|
+
} from "../chunk-EGBNNTAL.js";
|
|
28
|
+
import "../chunk-O7UX55EU.js";
|
|
29
|
+
import "../chunk-S5SRJJQG.js";
|
|
30
|
+
|
|
1
31
|
// src/workspace-apps/data-bridge.ts
|
|
2
32
|
var protocol = "tangle.workspace-app-data.v1";
|
|
3
33
|
var maxBytes = 65536;
|
|
@@ -310,11 +340,36 @@ function workspaceAppBuilderInstructions(options) {
|
|
|
310
340
|
];
|
|
311
341
|
}
|
|
312
342
|
export {
|
|
343
|
+
CLOUD_ARTIFACT_FRAME_SANDBOX,
|
|
344
|
+
CLOUD_ARTIFACT_MAX_BYTES,
|
|
345
|
+
CLOUD_ARTIFACT_MAX_FILES,
|
|
346
|
+
CLOUD_ARTIFACT_MAX_FILE_BYTES,
|
|
347
|
+
CLOUD_ARTIFACT_SCRIPT_HOSTS,
|
|
348
|
+
CLOUD_ARTIFACT_SHARE_ROLE,
|
|
349
|
+
CLOUD_ARTIFACT_VIEW_TTL_MS,
|
|
350
|
+
CLOUD_ARTIFACT_WORKER_MODULE,
|
|
351
|
+
CloudArtifactError,
|
|
313
352
|
WorkspaceAppDataConflict,
|
|
353
|
+
cloudArtifactContentSecurityPolicy,
|
|
354
|
+
cloudArtifactContentType,
|
|
355
|
+
cloudArtifactObjectKey,
|
|
356
|
+
cloudArtifactReferences,
|
|
357
|
+
cloudArtifactWorkspacePrefix,
|
|
358
|
+
collectCloudArtifactBundle,
|
|
314
359
|
confirmWorkspaceAppReady,
|
|
360
|
+
createCloudArtifactId,
|
|
361
|
+
createCloudArtifactOrigin,
|
|
362
|
+
createCloudArtifactShareToken,
|
|
363
|
+
createCloudArtifactViewUrl,
|
|
315
364
|
createWorkspaceAppDataClient,
|
|
316
365
|
createWorkspaceAppDataHost,
|
|
366
|
+
isCloudArtifactPath,
|
|
367
|
+
prepareCloudArtifact,
|
|
368
|
+
readCloudArtifactShareToken,
|
|
369
|
+
readCloudArtifactViewUrl,
|
|
317
370
|
refreshWorkspaceAppPreview,
|
|
371
|
+
resolveCloudArtifactReference,
|
|
372
|
+
storeCloudArtifact,
|
|
318
373
|
workspaceAppBuilderInstructions,
|
|
319
374
|
workspaceAppFromPreviewLink
|
|
320
375
|
};
|