1import * as Effect from "effect/Effect";
2import * as Binding from "../../Binding.ts";
3import { AWSEnvironment } from "../Environment.ts";
4import { isBindingHost } from "../Lambda/Function.ts";
5
6/**
7 * Shared scaffolding for AWS SageMaker Runtime HTTP bindings.
8 *
9 * NOT exported from `index.ts` — every `{Op}Http.ts` in this service is a
10 * thin `Layer.effect(Cap, makeEndpointInvocationHttpBinding({ … }))` over the
11 * builder below. Everything except the operation and the IAM action list is
12 * boilerplate: SageMaker Runtime is a pure data-plane API whose three
13 * invocation operations all take one or more endpoint names, inject
14 * `EndpointName` into the request, and authorize against the endpoint ARNs.
15 */
16
17const currentEnv = AWSEnvironment.current as unknown as Effect.Effect<{
18 accountId: string;
19 region: string;
20}>;
21
22/**
23 * Build the impl Effect for an endpoint-invocation operation
24 * (`InvokeEndpoint`, `InvokeEndpointAsync`,
25 * `InvokeEndpointWithResponseStream`). The binding accepts one or more
26 * endpoint names, grants `actions` on exactly those endpoint ARNs, and the
27 * runtime callable defaults `EndpointName` to the first bound endpoint (it
28 * may be overridden per call with any of the bound names).
29 */
30export const makeEndpointInvocationHttpBinding = <
31 I extends { EndpointName: string },
32 A,
33 E,
34 R,
35>(options: {
36 /** Fully-qualified binding tag, e.g. `AWS.SageMakerRuntime.InvokeEndpoint`. */
37 tag: string;
38 /** The distilled operation; `EndpointName` defaults to the first bound endpoint. */
39 operation: Effect.Effect<(input: I) => Effect.Effect<A, E>, never, R>;
40 /** IAM actions granted on the bound endpoint ARNs. */
41 actions: readonly string[];
42}) =>
43 Effect.gen(function* () {
44 const op = yield* options.operation;
45
46 return Effect.fn(function* (
47 endpoint: string,
48 ...additionalEndpoints: string[]
49 ) {
50 if (!globalThis.__ALCHEMY_RUNTIME__) {
51 const host = yield* Binding.Host;
52 if (isBindingHost(host)) {
53 const { accountId, region } = yield* currentEnv;
54 // Sort so the binding identity (SID + ARN list) is deterministic
55 // regardless of argument order.
56 const sorted = [
57 ...new Set([endpoint, ...additionalEndpoints]),
58 ].sort();
59 yield* host.bind`Allow(${host}, ${options.tag}(${sorted.join(",")}))`(
60 {
61 policyStatements: [
62 {
63 Effect: "Allow",
64 Action: [...options.actions],
65 Resource: sorted.map(
66 (name) =>
67 `arn:aws:sagemaker:${region}:${accountId}:endpoint/${name}`,
68 ),
69 },
70 ],
71 },
72 );
73 }
74 }
75 return Effect.fn(`${options.tag}(${endpoint})`)(function* (
76 request: Omit<I, "EndpointName"> & { EndpointName?: string },
77 ) {
78 return yield* op({
79 ...request,
80 EndpointName: request.EndpointName ?? endpoint,
81 } as unknown as I);
82 });
83 });
84 });