| 1 | import { |
| 2 | CommandId, |
| 3 | type ModelSelection, |
| 4 | type OrchestrationV2ThreadProjection, |
| 5 | type ProviderInteractionMode, |
| 6 | type RuntimeMode, |
| 7 | ThreadId, |
| 8 | } from "@t3tools/contracts"; |
| 9 | import * as Context from "effect/Context"; |
| 10 | import * as Effect from "effect/Effect"; |
| 11 | import * as Layer from "effect/Layer"; |
| 12 | import * as Schema from "effect/Schema"; |
| 13 | |
| 14 | import * as ThreadManagement from "./ThreadManagementService.ts"; |
| 15 | |
| 16 | export class ThreadLifecycleError extends Schema.TaggedError<ThreadLifecycleError>()( |
| 17 | "ThreadLifecycleError", |
| 18 | { |
| 19 | operation: Schema.Literals([ |
| 20 | "archive", |
| 21 | "unarchive", |
| 22 | "delete", |
| 23 | "update-metadata", |
| 24 | "set-runtime-mode", |
| 25 | "set-interaction-mode", |
| 26 | "set-model-selection", |
| 27 | ]), |
| 28 | threadId: ThreadId, |
| 29 | cause: Schema.Defect(), |
| 30 | }, |
| 31 | ) { |
| 32 | override get message(): string { |
| 33 | return `Thread lifecycle operation '${this.operation}' failed for ${this.threadId}.`; |
| 34 | } |
| 35 | } |
| 36 | |
| 37 | export class ThreadLifecycleService extends Context.Service< |
| 38 | ThreadLifecycleService, |
| 39 | { |
| 40 | readonly archive: (input: { |
| 41 | readonly commandId: CommandId; |
| 42 | readonly threadId: ThreadId; |
| 43 | }) => Effect.Effect<Pick<OrchestrationV2ThreadProjection, "thread">, ThreadLifecycleError>; |
| 44 | readonly unarchive: (input: { |
| 45 | readonly commandId: CommandId; |
| 46 | readonly threadId: ThreadId; |
| 47 | }) => Effect.Effect<Pick<OrchestrationV2ThreadProjection, "thread">, ThreadLifecycleError>; |
| 48 | readonly delete: (input: { |
| 49 | readonly commandId: CommandId; |
| 50 | readonly threadId: ThreadId; |
| 51 | }) => Effect.Effect<Pick<OrchestrationV2ThreadProjection, "thread">, ThreadLifecycleError>; |
| 52 | readonly updateMetadata: (input: { |
| 53 | readonly commandId: CommandId; |
| 54 | readonly threadId: ThreadId; |
| 55 | readonly title?: string; |
| 56 | readonly branch?: string | null; |
| 57 | readonly worktreePath?: string | null; |
| 58 | }) => Effect.Effect<Pick<OrchestrationV2ThreadProjection, "thread">, ThreadLifecycleError>; |
| 59 | readonly setRuntimeMode: (input: { |
| 60 | readonly commandId: CommandId; |
| 61 | readonly threadId: ThreadId; |
| 62 | readonly runtimeMode: RuntimeMode; |
| 63 | }) => Effect.Effect<Pick<OrchestrationV2ThreadProjection, "thread">, ThreadLifecycleError>; |
| 64 | readonly setInteractionMode: (input: { |
| 65 | readonly commandId: CommandId; |
| 66 | readonly threadId: ThreadId; |
| 67 | readonly interactionMode: ProviderInteractionMode; |
| 68 | }) => Effect.Effect<Pick<OrchestrationV2ThreadProjection, "thread">, ThreadLifecycleError>; |
| 69 | readonly setModelSelection: (input: { |
| 70 | readonly commandId: CommandId; |
| 71 | readonly threadId: ThreadId; |
| 72 | readonly modelSelection: ModelSelection; |
| 73 | }) => Effect.Effect<Pick<OrchestrationV2ThreadProjection, "thread">, ThreadLifecycleError>; |
| 74 | } |
| 75 | >()("t3/orchestration-v2/ThreadLifecycleService") {} |
| 76 | |
| 77 | const make = Effect.gen(function* () { |
| 78 | const threads = yield* ThreadManagement.ThreadManagementService; |
| 79 | |
| 80 | const dispatch = <Operation extends ThreadLifecycleError["operation"]>( |
| 81 | operation: Operation, |
| 82 | threadId: ThreadId, |
| 83 | command: Parameters<ThreadManagement.ThreadManagementService["Service"]["dispatch"]>[0], |
| 84 | ) => |
| 85 | threads.dispatch(command).pipe( |
| 86 | Effect.andThen(threads.getThreadRecords(threadId, [])), |
| 87 | Effect.mapError((cause) => new ThreadLifecycleError({ operation, threadId, cause })), |
| 88 | ); |
| 89 | |
| 90 | return ThreadLifecycleService.of({ |
| 91 | archive: (input) => |
| 92 | dispatch("archive", input.threadId, { |
| 93 | type: "thread.archive", |
| 94 | commandId: input.commandId, |
| 95 | threadId: input.threadId, |
| 96 | }), |
| 97 | unarchive: (input) => |
| 98 | dispatch("unarchive", input.threadId, { |
| 99 | type: "thread.unarchive", |
| 100 | commandId: input.commandId, |
| 101 | threadId: input.threadId, |
| 102 | }), |
| 103 | delete: (input) => |
| 104 | dispatch("delete", input.threadId, { |
| 105 | type: "thread.delete", |
| 106 | commandId: input.commandId, |
| 107 | threadId: input.threadId, |
| 108 | }), |
| 109 | updateMetadata: (input) => |
| 110 | dispatch("update-metadata", input.threadId, { |
| 111 | type: "thread.metadata.update", |
| 112 | commandId: input.commandId, |
| 113 | threadId: input.threadId, |
| 114 | ...(input.title === undefined ? {} : { title: input.title }), |
| 115 | ...(input.branch === undefined ? {} : { branch: input.branch }), |
| 116 | ...(input.worktreePath === undefined ? {} : { worktreePath: input.worktreePath }), |
| 117 | }), |
| 118 | setRuntimeMode: (input) => |
| 119 | dispatch("set-runtime-mode", input.threadId, { |
| 120 | type: "thread.runtime-mode.set", |
| 121 | commandId: input.commandId, |
| 122 | threadId: input.threadId, |
| 123 | runtimeMode: input.runtimeMode, |
| 124 | }), |
| 125 | setInteractionMode: (input) => |
| 126 | dispatch("set-interaction-mode", input.threadId, { |
| 127 | type: "thread.interaction-mode.set", |
| 128 | commandId: input.commandId, |
| 129 | threadId: input.threadId, |
| 130 | interactionMode: input.interactionMode, |
| 131 | }), |
| 132 | setModelSelection: (input) => |
| 133 | dispatch("set-model-selection", input.threadId, { |
| 134 | type: "thread.model-selection.set", |
| 135 | commandId: input.commandId, |
| 136 | threadId: input.threadId, |
| 137 | modelSelection: input.modelSelection, |
| 138 | }), |
| 139 | }); |
| 140 | }); |
| 141 | |
| 142 | export const layer = Layer.effect(ThreadLifecycleService, make); |