1import {
2 ModelSelection,
3 OrchestrationV2AppThread,
4 ProjectId,
5 ProviderInstanceId,
6 type RuntimeMode,
7} from "@t3tools/contracts";
8import * as Context from "effect/Context";
9import * as Effect from "effect/Effect";
10import * as Layer from "effect/Layer";
11import * as Option from "effect/Option";
12import * as Schema from "effect/Schema";
13
14import * as ProviderInstanceRegistry from "../provider/ProviderInstanceRegistry.ts";
15import * as ProviderAdapter from "@t3tools/provider-core/server/ProviderAdapter";
16import * as ProjectStore from "./ProjectStore.ts";
17
18/**
19 * ERRORS
20 */
21export class RuntimePolicyResolveError extends Schema.TaggedError<RuntimePolicyResolveError>()(
22 "RuntimePolicyResolveError",
23 {
24 projectId: ProjectId,
25 providerInstanceId: ProviderInstanceId,
26 cause: Schema.optional(Schema.Defect()),
27 },
28) {
29 override get message(): string {
30 return `Failed to resolve runtime policy for provider instance ${this.providerInstanceId} in project ${this.projectId}.`;
31 }
32}
33
34export const RuntimePolicyV2Error = Schema.Union([RuntimePolicyResolveError]);
35export type RuntimePolicyV2Error = typeof RuntimePolicyV2Error.Type;
36
37export const RuntimePolicyV2Override = Schema.Struct({
38 cwd: Schema.optional(Schema.String),
39 approvalPolicy: Schema.optional(Schema.Unknown),
40 sandboxPolicy: Schema.optional(Schema.Unknown),
41 reasoningEffort: Schema.optional(Schema.String),
42});
43export type RuntimePolicyV2Override = typeof RuntimePolicyV2Override.Type;
44
45/**
46 * SERVICE DEFINITION
47 */
48export interface RuntimePolicyV2Shape {
49 readonly resolve: (input: {
50 readonly thread: OrchestrationV2AppThread;
51 readonly modelSelection: ModelSelection;
52 }) => Effect.Effect<ProviderAdapter.ProviderAdapterV2RuntimePolicy, RuntimePolicyV2Error>;
53}
54
55export class RuntimePolicyV2 extends Context.Service<RuntimePolicyV2, RuntimePolicyV2Shape>()(
56 "t3/orchestration-v2/RuntimePolicy/RuntimePolicyV2",
57) {}
58
59/**
60 * IMPLEMENTATIONS
61 */
62export const layer: Layer.Layer<RuntimePolicyV2> = Layer.succeed(RuntimePolicyV2, {
63 resolve: (input) =>
64 Effect.succeed({
65 runtimeMode: input.thread.runtimeMode,
66 interactionMode: input.thread.interactionMode,
67 cwd: input.thread.worktreePath,
68 }),
69});
70
71/**
72 * The mode a provider runs a thread in. A mode the provider does not offer
73 * (a thread set before it stopped offering it, or a stale client) runs in
74 * Supervised rather than having T3 imitate it.
75 */
76function providerRuntimeMode(
77 runtimeMode: RuntimeMode,
78 supportedRuntimeModes: ReadonlyArray<RuntimeMode> | undefined,
79): RuntimeMode {
80 return supportedRuntimeModes === undefined ||
81 supportedRuntimeModes.length === 0 ||
82 supportedRuntimeModes.includes(runtimeMode)
83 ? runtimeMode
84 : "approval-required";
85}
86
87export const layerFromProjectStore: Layer.Layer<
88 RuntimePolicyV2,
89 never,
90 ProjectStore.ProjectStoreV2 | ProviderInstanceRegistry.ProviderInstanceRegistry
91> = Layer.effect(
92 RuntimePolicyV2,
93 Effect.gen(function* () {
94 const projects = yield* ProjectStore.ProjectStoreV2;
95 const providerInstances = yield* ProviderInstanceRegistry.ProviderInstanceRegistry;
96 return RuntimePolicyV2.of({
97 resolve: Effect.fn("RuntimePolicyV2.resolve")(function* (input) {
98 const instance = yield* providerInstances.getInstance(input.modelSelection.instanceId);
99 const supportedRuntimeModes =
100 instance === undefined
101 ? undefined
102 : (yield* instance.snapshot.getSnapshot).supportedRuntimeModes;
103 const cwd =
104 input.thread.worktreePath ??
105 (yield* projects.get(input.thread.projectId).pipe(
106 Effect.mapError(
107 (cause) =>
108 new RuntimePolicyResolveError({
109 projectId: input.thread.projectId,
110 providerInstanceId: input.modelSelection.instanceId,
111 cause,
112 }),
113 ),
114 Effect.flatMap(
115 Option.match({
116 onNone: () =>
117 Effect.fail(
118 new RuntimePolicyResolveError({
119 projectId: input.thread.projectId,
120 providerInstanceId: input.modelSelection.instanceId,
121 cause: "Project not found.",
122 }),
123 ),
124 onSome: (project) => Effect.succeed(project.workspaceRoot),
125 }),
126 ),
127 ));
128 return ProviderAdapter.ProviderAdapterV2RuntimePolicy.make({
129 runtimeMode: providerRuntimeMode(input.thread.runtimeMode, supportedRuntimeModes),
130 interactionMode: input.thread.interactionMode,
131 cwd,
132 });
133 }),
134 });
135 }),
136);
137
138export function layerWithOverride(
139 override: RuntimePolicyV2Override,
140): Layer.Layer<RuntimePolicyV2, never, RuntimePolicyV2> {
141 return Layer.effect(
142 RuntimePolicyV2,
143 Effect.gen(function* () {
144 const base = yield* RuntimePolicyV2;
145 return {
146 resolve: (input) =>
147 base.resolve(input).pipe(
148 Effect.map((policy) =>
149 ProviderAdapter.ProviderAdapterV2RuntimePolicy.make({
150 ...policy,
151 ...(override.cwd === undefined ? {} : { cwd: override.cwd }),
152 ...(override.approvalPolicy === undefined
153 ? {}
154 : { approvalPolicy: override.approvalPolicy }),
155 ...(override.sandboxPolicy === undefined
156 ? {}
157 : { sandboxPolicy: override.sandboxPolicy }),
158 ...(override.reasoningEffort === undefined
159 ? {}
160 : { reasoningEffort: override.reasoningEffort }),
161 }),
162 ),
163 ),
164 } satisfies RuntimePolicyV2Shape;
165 }),
166 );
167}