/** * One artifact-originated call, resolved into the code that actually runs. * * Two surfaces carry these calls, and they must agree exactly: * * - `execute-action`, when the artifact renders inside an MCP-Apps client; * - `POST /executions` with an `artifactId`, when it renders on the console's * own artifact page (there is no MCP client there, so the shell's host * adapter speaks HTTP instead). * * The same iframe, the same generated component and the same proxy sit behind * both. If only one of them constrained the grammar or applied the bindings, * the other would be a way around it — so the logic lives here and each surface * only formats the outcome in its own vocabulary. * * Resolution is: parse the code against the proxy's grammar, load the artifact, * rewrite the role into a connection, re-emit the call. The code that reaches * the engine is built here from a parsed path and a stored binding; the string * the iframe sent is never executed. */import { Effect } from "effect";import type { Artifact } from "@executor-js/sdk";import { resolveCallAddress } from "./artifact-bindings";import { formatToolCallCode, parseToolCallCode } from "./tool-call-code";export type ArtifactActionResolution = /** Run this. Built from the parsed path plus the artifact's bindings. */ | { readonly status: "ok"; readonly code: string } /** Not the shape the proxy emits. */ | { readonly status: "invalid_action_code" } /** The artifact is missing, or isn't this caller's. One outcome for both, so * the channel can't be used to probe for ids that exist. */ | { readonly status: "artifact_unavailable" } /** The call names an integration role the artifact has no connection for. */ | { readonly status: "binding_unresolved"; readonly role: string; readonly integration: string; readonly message: string; };/** * `loadArtifact` is the caller's OWN scoped read. Ownership is therefore * enforced by the same query that fetches the row — an id belonging to someone * else simply doesn't resolve — rather than by a separate check that could * drift from it. * * A missing `artifactId` is treated as an artifact with no bindings: a call * that already carries a full address runs as written (that is the * pre-binding-contract path), a short one has nothing to resolve against and is * refused. */export const resolveArtifactAction = (input: { readonly code: string; readonly artifactId: string | undefined; readonly loadArtifact: (id: string) => Effect.Effect<Artifact | null>;}): Effect.Effect<ArtifactActionResolution> => Effect.gen(function* () { const call = parseToolCallCode(input.code); if (!call) return { status: "invalid_action_code" } as const; const artifact = input.artifactId === undefined ? null : yield* input.loadArtifact(input.artifactId); if (input.artifactId !== undefined && !artifact) { return { status: "artifact_unavailable" } as const; } const resolved = resolveCallAddress({ path: call.path, role: call.role, bindings: artifact?.bindings ?? null, }); if (!resolved.ok) { return input.artifactId === undefined ? ({ status: "artifact_unavailable" } as const) : ({ status: "binding_unresolved", role: resolved.role, integration: resolved.integration, message: resolved.message, } as const); } return { status: "ok", code: formatToolCallCode(resolved.path, call.args) } as const; });
/**
* One artifact-originated call, resolved into the code that actually runs.
*
* Two surfaces carry these calls, and they must agree exactly:
*
* - `execute-action`, when the artifact renders inside an MCP-Apps client;
* - `POST /executions` with an `artifactId`, when it renders on the console's
* own artifact page (there is no MCP client there, so the shell's host
* adapter speaks HTTP instead).
*
* The same iframe, the same generated component and the same proxy sit behind
* both. If only one of them constrained the grammar or applied the bindings,
* the other would be a way around it — so the logic lives here and each surface
* only formats the outcome in its own vocabulary.
*
* Resolution is: parse the code against the proxy's grammar, load the artifact,
* rewrite the role into a connection, re-emit the call. The code that reaches
* the engine is built here from a parsed path and a stored binding; the string
* the iframe sent is never executed.
*/
import { Effect } from "effect";
import type { Artifact } from "@executor-js/sdk";
import { resolveCallAddress } from "./artifact-bindings";
import { formatToolCallCode, parseToolCallCode } from "./tool-call-code";
export type ArtifactActionResolution =
/** Run this. Built from the parsed path plus the artifact's bindings. */
| { readonly status: "ok"; readonly code: string }
/** Not the shape the proxy emits. */
| { readonly status: "invalid_action_code" }
/** The artifact is missing, or isn't this caller's. One outcome for both, so
* the channel can't be used to probe for ids that exist. */
| { readonly status: "artifact_unavailable" }
/** The call names an integration role the artifact has no connection for. */
| {
readonly status: "binding_unresolved";
readonly role: string;
readonly integration: string;
readonly message: string;
};
/**
* `loadArtifact` is the caller's OWN scoped read. Ownership is therefore
* enforced by the same query that fetches the row — an id belonging to someone
* else simply doesn't resolve — rather than by a separate check that could
* drift from it.
*
* A missing `artifactId` is treated as an artifact with no bindings: a call
* that already carries a full address runs as written (that is the
* pre-binding-contract path), a short one has nothing to resolve against and is
* refused.
*/
export const resolveArtifactAction = (input: {
readonly code: string;
readonly artifactId: string | undefined;
readonly loadArtifact: (id: string) => Effect.Effect<Artifact | null>;
}): Effect.Effect<ArtifactActionResolution> =>
Effect.gen(function* () {
const call = parseToolCallCode(input.code);
if (!call) return { status: "invalid_action_code" } as const;
const artifact =
input.artifactId === undefined ? null : yield* input.loadArtifact(input.artifactId);
if (input.artifactId !== undefined && !artifact) {
return { status: "artifact_unavailable" } as const;
}
const resolved = resolveCallAddress({
path: call.path,
role: call.role,
bindings: artifact?.bindings ?? null,
});
if (!resolved.ok) {
return input.artifactId === undefined
? ({ status: "artifact_unavailable" } as const)
: ({
status: "binding_unresolved",
role: resolved.role,
integration: resolved.integration,
message: resolved.message,
} as const);
}
return { status: "ok", code: formatToolCallCode(resolved.path, call.args) } as const;
});