import { Effect } from "effect"; import { ConnectionName, IntegrationSlug, ToolAddress, isToolResult, type ConnectionRef, type Owner, type PluginCtx,} from "@executor-js/sdk";import { BindingError, type ClientResolver, type ConnectionCandidate } from "./bindings"; export interface AppsResolverPluginCtx { readonly connections: Pick<PluginCtx["connections"], "list" | "get">; readonly execute: PluginCtx["execute"];} const parseConnectionAddress = (address: string): ConnectionRef | null => { const parts = address.split("."); if (parts.length !== 4 || parts[0] !== "tools") return null; const [, integration, owner, name] = parts; if (!integration || !name) return null; if (owner !== "org" && owner !== "user") return null; return { owner: owner as Owner, integration: IntegrationSlug.make(integration), name: ConnectionName.make(name), };}; const toCandidate = (connection: { readonly address: unknown; readonly integration: unknown; readonly name?: unknown; readonly owner?: unknown;}): ConnectionCandidate => ({ address: String(connection.address), integration: String(connection.integration), ...(connection.name !== undefined ? { name: String(connection.name) } : {}), ...(connection.owner !== undefined ? { owner: String(connection.owner) } : {}),}); export const makePluginCtxAppsResolver = (input: { readonly ctx: AppsResolverPluginCtx;}): ClientResolver => ({ listConnections: ({ integration }) => input.ctx.connections.list({ integration: IntegrationSlug.make(integration) }).pipe( Effect.map((connections) => connections.map(toCandidate)), Effect.mapError( () => new BindingError({ role: integration, integration, message: `failed to list ${integration} connections`, }), ), ), resolveConnection: ({ connection }) => Effect.gen(function* () { const ref = parseConnectionAddress(connection); if (!ref) return null; const row = yield* input.ctx.connections.get(ref).pipe( Effect.mapError( () => new BindingError({ role: String(ref.integration), integration: String(ref.integration), requestedConnection: connection, message: `failed to resolve connection ${connection}`, }), ), ); return row ? toCandidate(row) : null; }), call: ({ integration, connection, path, args, invokeOptions }) => Effect.gen(function* () { const ref = parseConnectionAddress(connection); if (!ref) { return yield* new BindingError({ role: integration, integration, requestedConnection: connection, message: `invalid connection address ${connection}`, }); } if (String(ref.integration) !== integration) { return yield* new BindingError({ role: integration, integration, requestedConnection: connection, message: `connection "${connection}" belongs to integration "${ref.integration}", not "${integration}"`, }); } const tool = path.join("."); const address = ToolAddress.make(`${connection}.${tool}`); const payload = args[0] ?? {}; const result = yield* input.ctx.execute(address, payload, invokeOptions).pipe( Effect.mapError( () => new BindingError({ role: integration, integration, requestedConnection: connection, message: `failed to execute ${address}`, }), ), ); if (isToolResult(result)) { if (result.ok) return result.data; const { message } = result.error; return yield* new BindingError({ role: integration, integration, requestedConnection: connection, message, }); } return result; }),});
import { Effect } from "effect";
import {
ConnectionName,
IntegrationSlug,
ToolAddress,
isToolResult,
type ConnectionRef,
type Owner,
type PluginCtx,
} from "@executor-js/sdk";
import { BindingError, type ClientResolver, type ConnectionCandidate } from "./bindings";
export interface AppsResolverPluginCtx {
readonly connections: Pick<PluginCtx["connections"], "list" | "get">;
readonly execute: PluginCtx["execute"];
}
const parseConnectionAddress = (address: string): ConnectionRef | null => {
const parts = address.split(".");
if (parts.length !== 4 || parts[0] !== "tools") return null;
const [, integration, owner, name] = parts;
if (!integration || !name) return null;
if (owner !== "org" && owner !== "user") return null;
return {
owner: owner as Owner,
integration: IntegrationSlug.make(integration),
name: ConnectionName.make(name),
};
};
const toCandidate = (connection: {
readonly address: unknown;
readonly integration: unknown;
readonly name?: unknown;
readonly owner?: unknown;
}): ConnectionCandidate => ({
address: String(connection.address),
integration: String(connection.integration),
...(connection.name !== undefined ? { name: String(connection.name) } : {}),
...(connection.owner !== undefined ? { owner: String(connection.owner) } : {}),
});
export const makePluginCtxAppsResolver = (input: {
readonly ctx: AppsResolverPluginCtx;
}): ClientResolver => ({
listConnections: ({ integration }) =>
input.ctx.connections.list({ integration: IntegrationSlug.make(integration) }).pipe(
Effect.map((connections) => connections.map(toCandidate)),
Effect.mapError(
() =>
new BindingError({
role: integration,
integration,
message: `failed to list ${integration} connections`,
}),
),
),
resolveConnection: ({ connection }) =>
Effect.gen(function* () {
const ref = parseConnectionAddress(connection);
if (!ref) return null;
const row = yield* input.ctx.connections.get(ref).pipe(
Effect.mapError(
() =>
new BindingError({
role: String(ref.integration),
integration: String(ref.integration),
requestedConnection: connection,
message: `failed to resolve connection ${connection}`,
}),
),
);
return row ? toCandidate(row) : null;
}),
call: ({ integration, connection, path, args, invokeOptions }) =>
Effect.gen(function* () {
const ref = parseConnectionAddress(connection);
if (!ref) {
return yield* new BindingError({
role: integration,
integration,
requestedConnection: connection,
message: `invalid connection address ${connection}`,
});
}
if (String(ref.integration) !== integration) {
return yield* new BindingError({
role: integration,
integration,
requestedConnection: connection,
message: `connection "${connection}" belongs to integration "${ref.integration}", not "${integration}"`,
});
}
const tool = path.join(".");
const address = ToolAddress.make(`${connection}.${tool}`);
const payload = args[0] ?? {};
const result = yield* input.ctx.execute(address, payload, invokeOptions).pipe(
Effect.mapError(
() =>
new BindingError({
role: integration,
integration,
requestedConnection: connection,
message: `failed to execute ${address}`,
}),
),
);
if (isToolResult(result)) {
if (result.ok) return result.data;
const { message } = result.error;
return yield* new BindingError({
role: integration,
integration,
requestedConnection: connection,
message,
});
}
return result;
}),
});