Repository navigation
Expand file tree
/
Copy pathexecution-stack.ts
More file actions
135 lines (124 loc) · 6.54 KB
/
Copy pathexecution-stack.ts
File metadata and controls
135 lines (124 loc) · 6.54 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
// ---------------------------------------------------------------------------
// Cloud execution-stack seams.
//
// The shared `makeExecutionStack` (@executor-js/api/server) owns the body:
// makeScopedExecutor -> createExecutionEngine -> EngineDecorator.decorate.
// Used by the protected HTTP API (per-request) and the MCP session DO
// (per-session) so changes to the stack flow to both. Cloud supplies the five
// seam Layers it reads from; the only cloud-specific differences are the
// Cloudflare dynamic-worker code substrate and the usage-metering decorator.
//
// - DbProvider -> cloudDbProviderLayer: rebuilds the postgres-js fuma
// client per request off the request-scoped
// `DbService.db` (Hyperdrive forbids sharing an I/O
// handle across requests). The shared factory reads
// `db` without caching, preserving per-request rebuild.
// - PluginsProvider -> fresh per-request plugins with the Worker env's
// WorkOS credentials.
// - HostConfig -> `allowLocalNetwork` is config-driven (the
// `ALLOW_LOCAL_NETWORK` var; production leaves it unset
// -> `false`, the test workers set it `"true"`). It is
// an SSRF/private-network guard, so it MUST NOT key off
// a test flag. `webBaseUrl` is `VITE_PUBLIC_SITE_URL ??
// executor.sh`.
// - CodeExecutorProvider -> `makeDynamicWorkerExecutor({ loader: env.LOADER })`.
// - EngineDecorator -> the billing decorator that meters each execution
// to Autumn. BOTH cloud execution planes (the HTTP
// `/api/*` executor plane AND the MCP session DO) use
// the metered stack (`CloudMeteredExecutionStackLayer`,
// ../engine/execution-stack-metered.ts), since the MCP
// server is the primary execution surface. Billing
// still lives in the cloud app, not this neutral
// seams module; the decorator is composed on top.
// ---------------------------------------------------------------------------
import { env, waitUntil } from "cloudflare:workers";
import { Layer } from "effect";
import {
CodeExecutorProvider,
DbProvider,
HostConfig,
PluginsProvider,
collectTables,
} from "@executor-js/api/server";
import { makeDynamicWorkerExecutor } from "@executor-js/runtime-dynamic-worker";
import { type AnyPlugin } from "@executor-js/sdk";
import executorConfig from "../../executor.config";
import { cloudEnterpriseManagedRollout } from "../analytics/ema-rollout";
import { DbService } from "../db/db";
import { cloudDbProviderLayer } from "../db/fuma";
import { firstPartyOAuthClientsFor } from "./first-party-oauth-clients";
export { makeExecutionStack } from "@executor-js/api/server";
// The executor table set is fixed (plugin-independent), so the per-request
// DbProvider rebuilds the fuma client over the same schema.
export const CloudDbProvider = cloudDbProviderLayer(collectTables());
const cloudPluginFactory = executorConfig.plugins as (deps: {
readonly workosCredentials: {
readonly apiKey: string;
readonly clientId: string;
readonly apiUrl?: string;
};
}) => readonly AnyPlugin[];
// Fresh plugin instances per request, carrying the Worker env's WorkOS Vault
// credentials. Matches the old `createScopedExecutor`'s `orgPlugins()`.
export const CloudPluginsProvider: Layer.Layer<PluginsProvider> = Layer.succeed(PluginsProvider)({
plugins: () =>
cloudPluginFactory({
workosCredentials: {
apiKey: env.WORKOS_API_KEY,
clientId: env.WORKOS_CLIENT_ID,
apiUrl: env.WORKOS_API_URL,
},
}),
});
/**
* The path prefix the cloud mounts its typed API under. SINGLE SOURCE OF TRUTH:
* `app.ts` passes this as `ExecutorApp.make({ config: { mountPrefix } })`, and
* `make` derives the OAuth callback (`${webBaseUrl}${CLOUD_MOUNT_PREFIX}/oauth/callback`)
* from that same `mountPrefix`, so the redirect URI the host sends to providers
* always matches the route that actually serves the callback — no second knob.
*/
export const CLOUD_MOUNT_PREFIX = "/api" as const;
export const CloudHostConfig: Layer.Layer<HostConfig> = Layer.sync(HostConfig, () => ({
// SSRF / private-network egress guard. Config-driven, NOT a test flag:
// production leaves `ALLOW_LOCAL_NETWORK` unset so the guard stays ON (`false`);
// the e2e dev-server env opts in with `"true"` so in-scenario fixture
// servers on localhost are reachable. See `hosted-http-client.ts`.
allowLocalNetwork: env.ALLOW_LOCAL_NETWORK === "true",
webBaseUrl: env.VITE_PUBLIC_SITE_URL ?? "https://executor.sh",
oauthCallbackPath: `${CLOUD_MOUNT_PREFIX}/oauth/callback`,
// WorkOS Vault is cloud's credential storage implementation detail, not a
// user-selectable provider surface.
exposeCredentialProviders: false,
firstPartyOAuthClients: firstPartyOAuthClientsFor(env),
// Workers cancel request-scoped I/O once the response settles; the ambient
// `waitUntil` binds to the in-flight invocation (HTTP request or DO call),
// so stale tool-catalog rebuilds that outlive a read still converge.
waitUntil,
// Enterprise-managed authorization ships behind a PostHog flag. Cloud is the
// one host with a flag service, so cloud is the one host that installs a
// gate; everywhere else the seam stays empty and the profile is attempted as
// before. Gating happens at connect only — see the SDK contract.
enterpriseManagedRollout: cloudEnterpriseManagedRollout(),
}));
export const CloudCodeExecutorProvider: Layer.Layer<CodeExecutorProvider> = Layer.sync(
CodeExecutorProvider,
() => makeDynamicWorkerExecutor({ loader: env.LOADER }),
);
/**
* The four billing-free execution-stack seams (db / plugins / host-config /
* code-executor): everything `makeExecutionStack` reads EXCEPT the
* `EngineDecorator`. Both cloud planes compose this with the billing decorator
* via `CloudMeteredExecutionStackLayer` (../engine/execution-stack-metered.ts);
* exported so that overlay builds over the SAME four seams. There is no neutral
* no-op-decorator variant anymore: every cloud execution meters.
*/
export const CloudExecutionSeamsLayer: Layer.Layer<
DbProvider | PluginsProvider | HostConfig | CodeExecutorProvider,
never,
DbService
> = Layer.mergeAll(
CloudDbProvider,
CloudPluginsProvider,
CloudHostConfig,
CloudCodeExecutorProvider,
);