1import * as Schema from "effect/Schema";
2import { TrimmedNonEmptyString } from "./baseSchemas.ts";
3import {
4 ApprovalRequestId,
5 EventId,
6 IsoDateTime,
7 ProviderItemId,
8 ThreadId,
9 TurnId,
10} from "./baseSchemas.ts";
11import {
12 getProviderAttachmentLimitError,
13 PROVIDER_SEND_TURN_MAX_INPUT_CHARS,
14 ChatAttachment,
15} from "./chatAttachment.ts";
16import { ModelSelection } from "./modelSelection.ts";
17import {
18 ProviderApprovalDecision,
19 ProviderApprovalPolicy,
20 ProviderInteractionMode,
21 ProviderRequestKind,
22 ProviderSandboxMode,
23 ProviderUserInputAnswers,
24 UserInputAttachments,
25 RuntimeMode,
26} from "./providerPolicy.ts";
27import { ProviderInstanceId, ProviderDriverKind } from "./providerInstance.ts";
28
29const ProviderSessionStatus = Schema.Literals([
30 "connecting",
31 "ready",
32 "running",
33 "error",
34 "closed",
35]);
36
37export const ProviderSession = Schema.Struct({
38 provider: ProviderDriverKind,
39 // Optional during the driver/instance migration. Once every producer
40 // populates it (post-slice-4), routing flips to instance-id-only and the
41 // legacy `provider` field is removed.
42 providerInstanceId: Schema.optional(ProviderInstanceId),
43 status: ProviderSessionStatus,
44 runtimeMode: RuntimeMode,
45 cwd: Schema.optional(TrimmedNonEmptyString),
46 model: Schema.optional(TrimmedNonEmptyString),
47 threadId: ThreadId,
48 resumeCursor: Schema.optional(Schema.Unknown),
49 activeTurnId: Schema.optional(TurnId),
50 createdAt: IsoDateTime,
51 updatedAt: IsoDateTime,
52 lastError: Schema.optional(TrimmedNonEmptyString),
53});
54export type ProviderSession = typeof ProviderSession.Type;
55
56export const ProviderSessionStartInput = Schema.Struct({
57 threadId: ThreadId,
58 provider: Schema.optional(ProviderDriverKind),
59 // See ProviderSession for the migration story.
60 providerInstanceId: Schema.optional(ProviderInstanceId),
61 cwd: Schema.optional(TrimmedNonEmptyString),
62 title: Schema.optional(TrimmedNonEmptyString),
63 modelSelection: Schema.optional(ModelSelection),
64 resumeCursor: Schema.optional(Schema.Unknown),
65 approvalPolicy: Schema.optional(ProviderApprovalPolicy),
66 sandboxMode: Schema.optional(ProviderSandboxMode),
67 runtimeMode: RuntimeMode,
68});
69export type ProviderSessionStartInput = typeof ProviderSessionStartInput.Type;
70
71export const ProviderSendTurnInput = Schema.Struct({
72 threadId: ThreadId,
73 /** Internal recovery signal. Allows an empty turn only for adapters that
74 explicitly support promptless continuation. */
75 continuation: Schema.optional(Schema.Boolean),
76 input: Schema.optional(
77 TrimmedNonEmptyString.check(Schema.isMaxLength(PROVIDER_SEND_TURN_MAX_INPUT_CHARS)),
78 ),
79 attachments: Schema.optional(
80 Schema.Array(ChatAttachment).check(
81 Schema.makeFilter((attachments) => getProviderAttachmentLimitError(attachments) ?? true),
82 ),
83 ),
84 modelSelection: Schema.optional(ModelSelection),
85 interactionMode: Schema.optional(ProviderInteractionMode),
86});
87export type ProviderSendTurnInput = typeof ProviderSendTurnInput.Type;
88
89export const ProviderTurnStartResult = Schema.Struct({
90 threadId: ThreadId,
91 turnId: TurnId,
92 resumeCursor: Schema.optional(Schema.Unknown),
93});
94export type ProviderTurnStartResult = typeof ProviderTurnStartResult.Type;
95
96export const ProviderInterruptTurnInput = Schema.Struct({
97 threadId: ThreadId,
98 turnId: Schema.optional(TurnId),
99});
100export type ProviderInterruptTurnInput = typeof ProviderInterruptTurnInput.Type;
101
102export const ProviderStopSessionInput = Schema.Struct({
103 threadId: ThreadId,
104});
105export type ProviderStopSessionInput = typeof ProviderStopSessionInput.Type;
106
107export const ProviderRespondToRequestInput = Schema.Struct({
108 threadId: ThreadId,
109 requestId: ApprovalRequestId,
110 decision: ProviderApprovalDecision,
111});
112export type ProviderRespondToRequestInput = typeof ProviderRespondToRequestInput.Type;
113
114export const ProviderRespondToUserInputInput = Schema.Struct({
115 threadId: ThreadId,
116 requestId: ApprovalRequestId,
117 answers: ProviderUserInputAnswers,
118 attachmentsByQuestionId: Schema.optional(UserInputAttachments),
119});
120export type ProviderRespondToUserInputInput = typeof ProviderRespondToUserInputInput.Type;
121
122export const ProviderUploadFeedbackInput = Schema.Struct({
123 threadId: ThreadId,
124 reason: Schema.optional(TrimmedNonEmptyString),
125});
126export type ProviderUploadFeedbackInput = typeof ProviderUploadFeedbackInput.Type;
127
128export const ProviderUploadFeedbackResult = Schema.Struct({
129 feedbackId: TrimmedNonEmptyString,
130});
131export type ProviderUploadFeedbackResult = typeof ProviderUploadFeedbackResult.Type;
132
133export class ProviderUploadFeedbackError extends Schema.TaggedError<ProviderUploadFeedbackError>()(
134 "ProviderUploadFeedbackError",
135 {
136 threadId: ThreadId,
137 cause: Schema.optional(Schema.Defect()),
138 },
139) {
140 override get message(): string {
141 return `Failed to upload feedback for thread ${this.threadId}.`;
142 }
143}
144
145const ProviderEventKind = Schema.Literals(["session", "notification", "request", "error"]);
146
147export const ProviderEvent = Schema.Struct({
148 id: EventId,
149 kind: ProviderEventKind,
150 provider: ProviderDriverKind,
151 // See ProviderSession for the migration story.
152 providerInstanceId: Schema.optional(ProviderInstanceId),
153 threadId: ThreadId,
154 createdAt: IsoDateTime,
155 method: TrimmedNonEmptyString,
156 message: Schema.optional(TrimmedNonEmptyString),
157 turnId: Schema.optional(TurnId),
158 itemId: Schema.optional(ProviderItemId),
159 requestId: Schema.optional(ApprovalRequestId),
160 requestKind: Schema.optional(ProviderRequestKind),
161 textDelta: Schema.optional(Schema.String),
162 payload: Schema.optional(Schema.Unknown),
163});
164export type ProviderEvent = typeof ProviderEvent.Type;