1/**
2 * Defines prompts sent to AI language models.
3 *
4 * A prompt is an ordered list of messages. Messages can use roles such as
5 * system, user, assistant, and tool, and their content can be split into typed
6 * parts such as text, files, reasoning, tool calls, tool results, and approval
7 * messages. This module helps build prompts, combine them, and convert raw
8 * input or response parts into the shared prompt shape.
9 *
10 * @stability unstable
11 * @since 4.0.0
12 */
13import * as Arr from "../Array.ts"
14import * as Effect from "../Effect.ts"
15import * as Base64 from "../encoding/Base64.ts"
16import { dual } from "../Function.ts"
17import { type Pipeable, pipeArguments } from "../Pipeable.ts"
18import * as Predicate from "../Predicate.ts"
19import * as Schema from "../Schema.ts"
20import * as SchemaIssue from "../SchemaIssue.ts"
21import * as SchemaParser from "../SchemaParser.ts"
22import * as SchemaTransformation from "../SchemaTransformation.ts"
23import type * as Response from "./Response.ts"
24
25// =============================================================================
26// Options
27// =============================================================================
28
29/**
30 * Schema for provider-specific options that can be attached to content parts
31 * and messages.
32 *
33 * **Details**
34 *
35 * Provider-specific options are keyed by provider-specific names, and each
36 * value is JSON or `null`.
37 *
38 * @stability unstable
39 * @category options
40 * @since 4.0.0
41 */
42export const ProviderOptions: Schema.$Record<
43 Schema.String,
44 Schema.NullOr<Schema.Codec<Schema.Json>>
45> = Schema.Record(Schema.String, Schema.NullOr(Schema.Json))
46
47/**
48 * Type of provider-specific options that can be attached to prompt messages
49 * and content parts.
50 *
51 * @stability unstable
52 * @category options
53 * @since 4.0.0
54 */
55export type ProviderOptions = typeof ProviderOptions.Type
56
57// =============================================================================
58// Base Part
59// =============================================================================
60
61const PartTypeId = "~effect/ai/Prompt/Part" as const
62
63/**
64 * Type guard to check if a value is a Part.
65 *
66 * @stability unstable
67 * @category guards
68 * @since 4.0.0
69 */
70export const isPart = (u: unknown): u is Part => Predicate.hasProperty(u, PartTypeId)
71
72/**
73 * Union type representing all possible content parts within messages.
74 *
75 * **Details**
76 *
77 * Parts are the building blocks of message content, supporting text, files,
78 * reasoning, tool calls, tool results, tool approval responses, and tool
79 * approval requests.
80 *
81 * @stability unstable
82 * @category models
83 * @since 4.0.0
84 */
85export type Part =
86 | TextPart
87 | ReasoningPart
88 | FilePart
89 | ToolCallPart
90 | ToolResultPart
91 | ToolApprovalResponsePart
92 | ToolApprovalRequestPart
93
94/**
95 * Encoded representation of a Part.
96 *
97 * @stability unstable
98 * @category models
99 * @since 4.0.0
100 */
101export type PartEncoded =
102 | TextPartEncoded
103 | ReasoningPartEncoded
104 | FilePartEncoded
105 | ToolCallPartEncoded
106 | ToolResultPartEncoded
107 | ToolApprovalResponsePartEncoded
108 | ToolApprovalRequestPartEncoded
109
110/**
111 * Base interface for all content parts.
112 *
113 * **Details**
114 *
115 * It provides the common structure shared by all content parts, including the
116 * part type and provider options.
117 *
118 * @stability unstable
119 * @category models
120 * @since 4.0.0
121 */
122export interface BasePart<Type extends string, Options extends ProviderOptions> {
123 readonly [PartTypeId]: typeof PartTypeId
124 /**
125 * The type of this content part.
126 */
127 readonly type: Type
128 /**
129 * Provider-specific options for this part.
130 */
131 readonly options: Options
132}
133
134/**
135 * Base interface for encoded content parts.
136 *
137 * @stability unstable
138 * @category models
139 * @since 4.0.0
140 */
141export interface BasePartEncoded<Type extends string, Options extends ProviderOptions> {
142 /**
143 * The type of this content part.
144 */
145 readonly type: Type
146 /**
147 * Provider-specific options for this part.
148 */
149 readonly options?: Options | undefined
150}
151
152const BasePart = Schema.Struct({
153 [PartTypeId]: Schema.Literal(PartTypeId).pipe(
154 Schema.withDecodingDefaultKey(Effect.succeed(PartTypeId), { encodingStrategy: "omit" })
155 ),
156 options: ProviderOptions.pipe(Schema.withDecodingDefault(Effect.succeed({})))
157})
158
159/**
160 * Creates a new content part of the specified type.
161 *
162 * **Example** (Creating content parts)
163 *
164 * ```ts import.meta.vitest
165 * import { Prompt } from "effect/ai"
166 *
167 * const textPart = Prompt.makePart("text", {
168 * text: "Hello, world!"
169 * })
170 *
171 * const filePart = Prompt.makePart("file", {
172 * mediaType: "image/png",
173 * fileName: "screenshot.png",
174 * data: new Uint8Array([1, 2, 3])
175 * })
176 *
177 * const result = [textPart.type, filePart.type] // => ["text", "file"]
178 * ```
179 *
180 * @stability unstable
181 * @category constructors
182 * @since 4.0.0
183 */
184export const makePart = <const Type extends Part["type"]>(
185 /**
186 * The type of part to create.
187 */
188 type: Type,
189 /**
190 * Parameters specific to the part type being created.
191 */
192 params: Omit<Extract<Part, { type: Type }>, typeof PartTypeId | "type" | "options"> & {
193 /**
194 * Optional provider-specific options for this part.
195 */
196 readonly options?: Extract<Part, { type: Type }>["options"] | undefined
197 }
198): Extract<Part, { type: Type }> => (({
199 ...params,
200 [PartTypeId]: PartTypeId,
201 type,
202 options: params.options ?? {}
203}) as any)
204
205/**
206 * A utility type for specifying the parameters required to construct a
207 * specific part of a prompt.
208 *
209 * @stability unstable
210 * @category utility types
211 * @since 4.0.0
212 */
213export type PartConstructorParams<P extends Part> = Omit<P, typeof PartTypeId | "type" | "options"> & {
214 /**
215 * Optional provider-specific options for this part.
216 */
217 readonly options?: Part["options"] | undefined
218}
219
220// =============================================================================
221// Text Part
222// =============================================================================
223
224/**
225 * Content part representing plain text.
226 *
227 * **Details**
228 *
229 * Text parts are the basic content type used for textual information in
230 * messages.
231 *
232 * **Example** (Creating text parts)
233 *
234 * ```ts import.meta.vitest
235 * import { Prompt } from "effect/ai"
236 *
237 * const textPart: Prompt.TextPart = Prompt.makePart("text", {
238 * text: "Hello, how can I help you today?"
239 * })
240 * textPart.text // => "Hello, how can I help you today?"
241 * ```
242 *
243 * @stability unstable
244 * @category models
245 * @since 4.0.0
246 */
247export interface TextPart extends BasePart<"text", TextPartOptions> {
248 /**
249 * The text content.
250 */
251 readonly text: string
252}
253
254/**
255 * Encoded representation of text parts for serialization.
256 *
257 * @stability unstable
258 * @category models
259 * @since 4.0.0
260 */
261export interface TextPartEncoded extends BasePartEncoded<"text", TextPartOptions> {
262 /**
263 * The text content.
264 */
265 readonly text: string
266}
267
268/**
269 * Represents provider-specific options that can be associated with a
270 * `TextPart` through module augmentation.
271 *
272 * @stability unstable
273 * @category options
274 * @since 4.0.0
275 */
276export interface TextPartOptions extends ProviderOptions {}
277
278/**
279 * Schema for validation and encoding of text parts.
280 *
281 * @stability unstable
282 * @category schemas
283 * @since 4.0.0
284 */
285export const TextPart: Schema.Struct<
286 {
287 readonly type: Schema.Literal<"text">
288 readonly text: Schema.String
289 readonly "~effect/ai/Prompt/Part": Schema.withDecodingDefaultKey<Schema.Literal<"~effect/ai/Prompt/Part">>
290 readonly options: Schema.withDecodingDefault<
291 Schema.$Record<
292 Schema.String,
293 Schema.NullOr<Schema.Codec<Schema.Json>>
294 >
295 >
296 }
297> = Schema.Struct({
298 ...BasePart.fields,
299 type: Schema.Literal("text"),
300 text: Schema.String
301}).annotate({ identifier: "TextPart" })
302
303/**
304 * Constructs a new text part.
305 *
306 * @stability unstable
307 * @category constructors
308 * @since 4.0.0
309 */
310export const textPart = (params: PartConstructorParams<TextPart>): TextPart => makePart("text", params as any)
311
312// =============================================================================
313// Reasoning Part
314// =============================================================================
315
316/**
317 * Content part carrying reasoning text in an assistant message, such as a
318 * provider-supplied reasoning summary or explanation.
319 *
320 * **Example** (Creating reasoning parts)
321 *
322 * ```ts import.meta.vitest
323 * import { Prompt } from "effect/ai"
324 *
325 * const reasoningPart: Prompt.ReasoningPart = Prompt.makePart("reasoning", {
326 * text:
327 * "Summary: the response compares the requested options by price and availability."
328 * })
329 * reasoningPart.type // => "reasoning"
330 * ```
331 *
332 * @stability unstable
333 * @category models
334 * @since 4.0.0
335 */
336export interface ReasoningPart extends BasePart<"reasoning", ReasoningPartOptions> {
337 /**
338 * The reasoning or thought process text.
339 */
340 readonly text: string
341}
342
343/**
344 * Encoded representation of reasoning parts for serialization.
345 *
346 * @stability unstable
347 * @category models
348 * @since 4.0.0
349 */
350export interface ReasoningPartEncoded extends BasePartEncoded<"reasoning", ReasoningPartOptions> {
351 /**
352 * The reasoning or thought process text.
353 */
354 readonly text: string
355}
356
357/**
358 * Represents provider-specific options that can be associated with a
359 * `ReasoningPart` through module augmentation.
360 *
361 * @stability unstable
362 * @category options
363 * @since 4.0.0
364 */
365export interface ReasoningPartOptions extends ProviderOptions {}
366
367/**
368 * Schema for validation and encoding of reasoning parts.
369 *
370 * @stability unstable
371 * @category schemas
372 * @since 4.0.0
373 */
374export const ReasoningPart: Schema.Struct<{
375 readonly type: Schema.Literal<"reasoning">
376 readonly text: Schema.String
377 readonly "~effect/ai/Prompt/Part": Schema.withDecodingDefaultKey<Schema.Literal<"~effect/ai/Prompt/Part">>
378 readonly options: Schema.withDecodingDefault<
379 Schema.$Record<
380 Schema.String,
381 Schema.NullOr<Schema.Codec<Schema.Json>>
382 >
383 >
384}> = Schema.Struct({
385 ...BasePart.fields,
386 type: Schema.Literal("reasoning"),
387 text: Schema.String
388}).annotate({ identifier: "ReasoningPart" })
389
390/**
391 * Constructs a new reasoning part.
392 *
393 * @stability unstable
394 * @category constructors
395 * @since 4.0.0
396 */
397export const reasoningPart = (params: PartConstructorParams<ReasoningPart>): ReasoningPart =>
398 makePart("reasoning", params as any)
399
400// =============================================================================
401// File Part
402// =============================================================================
403
404/**
405 * Content part representing a file attachment.
406 *
407 * **Details**
408 *
409 * Files can be provided as base64 data strings, byte arrays, or URLs, and can
410 * represent images, documents, or other binary data.
411 *
412 * **Example** (Creating file parts)
413 *
414 * ```ts import.meta.vitest
415 * import { Prompt } from "effect/ai"
416 *
417 * const imagePart: Prompt.FilePart = Prompt.makePart("file", {
418 * mediaType: "image/jpeg",
419 * fileName: "photo.jpg",
420 * data: "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQ..."
421 * })
422 *
423 * const documentPart: Prompt.FilePart = Prompt.makePart("file", {
424 * mediaType: "application/pdf",
425 * fileName: "report.pdf",
426 * data: new Uint8Array([1, 2, 3])
427 * })
428 *
429 * const result = [imagePart.mediaType, documentPart.fileName] // => ["image/jpeg", "report.pdf"]
430 * ```
431 *
432 * @stability unstable
433 * @category models
434 * @since 4.0.0
435 */
436export interface FilePart extends BasePart<"file", FilePartOptions> {
437 /**
438 * MIME type of the file (e.g., "image/jpeg", "application/pdf").
439 */
440 readonly mediaType: string
441 /**
442 * Optional filename for the file.
443 */
444 readonly fileName?: string | undefined
445 /**
446 * File data as base64 string of data, a byte array, or a URL.
447 */
448 readonly data: string | Uint8Array | URL
449}
450
451/**
452 * Encoded representation of file parts for serialization.
453 *
454 * @stability unstable
455 * @category models
456 * @since 4.0.0
457 */
458export interface FilePartEncoded extends BasePartEncoded<"file", FilePartOptions> {
459 /**
460 * MIME type of the file (e.g., "image/jpeg", "application/pdf").
461 */
462 readonly mediaType: string
463 /**
464 * Optional filename for the file.
465 */
466 readonly fileName?: string | undefined
467 /**
468 * File data as base64 string of data, a byte array, or a URL.
469 */
470 readonly data: string | Uint8Array | URL
471}
472
473/**
474 * Represents provider-specific options that can be associated with a
475 * `FilePart` through module augmentation.
476 *
477 * @stability unstable
478 * @category options
479 * @since 4.0.0
480 */
481export interface FilePartOptions extends ProviderOptions {}
482
483/**
484 * Schema for validation and encoding of file parts.
485 *
486 * @stability unstable
487 * @category schemas
488 * @since 4.0.0
489 */
490export const FilePart: Schema.Struct<{
491 readonly type: Schema.Literal<"file">
492 readonly mediaType: Schema.String
493 readonly fileName: Schema.optional<Schema.String>
494 readonly data: Schema.Union<readonly [Schema.String, Schema.Uint8Array, Schema.URL]>
495 readonly "~effect/ai/Prompt/Part": Schema.withDecodingDefaultKey<Schema.Literal<"~effect/ai/Prompt/Part">>
496 readonly options: Schema.withDecodingDefault<
497 Schema.$Record<
498 Schema.String,
499 Schema.NullOr<Schema.Codec<Schema.Json>>
500 >
501 >
502}> = Schema.Struct({
503 ...BasePart.fields,
504 type: Schema.Literal("file"),
505 mediaType: Schema.String,
506 fileName: Schema.optional(Schema.String),
507 data: Schema.Union([Schema.String, Schema.Uint8Array, Schema.URL])
508}).annotate({ identifier: "FilePart" })
509
510/**
511 * Constructs a `FilePart` for prompt file attachments.
512 *
513 * **When to use**
514 *
515 * Use to create the file-attachment part of a prompt from typed file part
516 * parameters.
517 *
518 * @see {@link makePart} for the generic part constructor
519 *
520 * @stability unstable
521 * @category constructors
522 * @since 4.0.0
523 */
524export const filePart = (params: PartConstructorParams<FilePart>): FilePart => makePart("file", params as any)
525
526// =============================================================================
527// Tool Call Part
528// =============================================================================
529
530/**
531 * Content part representing a tool call request.
532 *
533 * **Example** (Creating tool call parts)
534 *
535 * ```ts import.meta.vitest
536 * import { Prompt } from "effect/ai"
537 *
538 * const toolCallPart: Prompt.ToolCallPart = Prompt.makePart("tool-call", {
539 * id: "call_123",
540 * name: "get_weather",
541 * params: { city: "San Francisco", units: "celsius" },
542 * providerExecuted: false
543 * })
544 * toolCallPart.name // => "get_weather"
545 * ```
546 *
547 * @stability unstable
548 * @category models
549 * @since 4.0.0
550 */
551export interface ToolCallPart extends BasePart<"tool-call", ToolCallPartOptions> {
552 /**
553 * Unique identifier for this tool call.
554 */
555 readonly id: string
556 /**
557 * Name of the tool to invoke.
558 */
559 readonly name: string
560 /**
561 * Parameters to pass to the tool.
562 */
563 readonly params: unknown
564 /**
565 * Whether the tool was executed by the provider (true) or framework (false).
566 */
567 readonly providerExecuted: boolean
568}
569
570/**
571 * Encoded representation of tool call parts for serialization.
572 *
573 * @stability unstable
574 * @category models
575 * @since 4.0.0
576 */
577export interface ToolCallPartEncoded extends BasePartEncoded<"tool-call", ToolCallPartOptions> {
578 /**
579 * Unique identifier for this tool call.
580 */
581 readonly id: string
582 /**
583 * Name of the tool to invoke.
584 */
585 readonly name: string
586 /**
587 * Parameters to pass to the tool.
588 */
589 readonly params: unknown
590 /**
591 * Whether the tool was executed by the provider (true) or framework (false).
592 */
593 readonly providerExecuted?: boolean | undefined
594}
595
596/**
597 * Represents provider-specific options that can be associated with a
598 * `ToolCallPart` through module augmentation.
599 *
600 * @stability unstable
601 * @category options
602 * @since 4.0.0
603 */
604export interface ToolCallPartOptions extends ProviderOptions {}
605
606/**
607 * Schema for validation and encoding of tool call parts.
608 *
609 * @stability unstable
610 * @category schemas
611 * @since 4.0.0
612 */
613export const ToolCallPart: Schema.Struct<{
614 readonly type: Schema.Literal<"tool-call">
615 readonly id: Schema.String
616 readonly name: Schema.String
617 readonly params: Schema.Unknown
618 readonly providerExecuted: Schema.withDecodingDefault<Schema.Boolean>
619 readonly "~effect/ai/Prompt/Part": Schema.withDecodingDefaultKey<Schema.Literal<"~effect/ai/Prompt/Part">>
620 readonly options: Schema.withDecodingDefault<
621 Schema.$Record<
622 Schema.String,
623 Schema.NullOr<Schema.Codec<Schema.Json>>
624 >
625 >
626}> = Schema.Struct({
627 ...BasePart.fields,
628 type: Schema.Literal("tool-call"),
629 id: Schema.String,
630 name: Schema.String,
631 params: Schema.Unknown,
632 providerExecuted: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false)))
633}).annotate({ identifier: "ToolCallPart" })
634
635/**
636 * Constructs a new tool call part.
637 *
638 * @stability unstable
639 * @category constructors
640 * @since 4.0.0
641 */
642export const toolCallPart = (params: PartConstructorParams<ToolCallPart>): ToolCallPart =>
643 makePart("tool-call", params as any)
644
645// =============================================================================
646// Tool Result Part
647// =============================================================================
648
649/**
650 * Content part representing the result of a tool call.
651 *
652 * **Example** (Creating tool result parts)
653 *
654 * ```ts import.meta.vitest
655 * import { Prompt } from "effect/ai"
656 *
657 * const toolResultPart: Prompt.ToolResultPart = Prompt.makePart("tool-result", {
658 * id: "call_123",
659 * name: "get_weather",
660 * isFailure: false,
661 * result: {
662 * temperature: 22,
663 * condition: "sunny",
664 * humidity: 65
665 * },
666 * providerExecuted: false
667 * })
668 * const result = [toolResultPart.name, toolResultPart.isFailure] // => ["get_weather", false]
669 * ```
670 *
671 * @stability unstable
672 * @category models
673 * @since 4.0.0
674 */
675export interface ToolResultPart extends BasePart<"tool-result", ToolResultPartOptions> {
676 /**
677 * Unique identifier matching the original tool call.
678 */
679 readonly id: string
680 /**
681 * Name of the tool that was executed.
682 */
683 readonly name: string
684 /**
685 * Whether or not the result of executing the tool call handler was an error.
686 */
687 readonly isFailure: boolean
688 /**
689 * The result returned by the tool execution.
690 */
691 readonly result: unknown
692 /**
693 * Whether the tool was executed by the provider (true) or framework (false).
694 */
695 readonly providerExecuted: boolean
696}
697
698/**
699 * Encoded representation of tool result parts for serialization.
700 *
701 * @stability unstable
702 * @category models
703 * @since 4.0.0
704 */
705export interface ToolResultPartEncoded extends BasePartEncoded<"tool-result", ToolResultPartOptions> {
706 /**
707 * Unique identifier matching the original tool call.
708 */
709 readonly id: string
710 /**
711 * Name of the tool that was executed.
712 */
713 readonly name: string
714 /**
715 * Whether or not the result of executing the tool call handler was an error.
716 */
717 readonly isFailure: boolean
718 /**
719 * The result returned by the tool execution.
720 */
721 readonly result: unknown
722 /**
723 * Whether the tool was executed by the provider (true) or framework (false).
724 */
725 readonly providerExecuted?: boolean | undefined
726}
727
728/**
729 * Represents provider-specific options that can be associated with a
730 * `ToolResultPart` through module augmentation.
731 *
732 * @stability unstable
733 * @category options
734 * @since 4.0.0
735 */
736export interface ToolResultPartOptions extends ProviderOptions {}
737
738/**
739 * Schema for validation and encoding of tool result parts.
740 *
741 * @stability unstable
742 * @category schemas
743 * @since 4.0.0
744 */
745export const ToolResultPart: Schema.Struct<{
746 readonly type: Schema.Literal<"tool-result">
747 readonly id: Schema.String
748 readonly name: Schema.String
749 readonly isFailure: Schema.Boolean
750 readonly result: Schema.Unknown
751 readonly providerExecuted: Schema.withDecodingDefault<Schema.Boolean>
752 readonly "~effect/ai/Prompt/Part": Schema.withDecodingDefaultKey<Schema.Literal<"~effect/ai/Prompt/Part">>
753 readonly options: Schema.withDecodingDefault<
754 Schema.$Record<
755 Schema.String,
756 Schema.NullOr<Schema.Codec<Schema.Json>>
757 >
758 >
759}> = Schema.Struct({
760 ...BasePart.fields,
761 type: Schema.Literal("tool-result"),
762 id: Schema.String,
763 name: Schema.String,
764 isFailure: Schema.Boolean,
765 result: Schema.Unknown,
766 providerExecuted: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false)))
767}).annotate({ identifier: "ToolResultPart" })
768
769/**
770 * Constructs a new tool result part.
771 *
772 * @stability unstable
773 * @category constructors
774 * @since 4.0.0
775 */
776export const toolResultPart = (params: PartConstructorParams<ToolResultPart>): ToolResultPart =>
777 makePart("tool-result", params as any)
778
779// =============================================================================
780// Tool Approval Response Part
781// =============================================================================
782
783/**
784 * Content part representing a user's response to a tool approval request.
785 *
786 * **When to use**
787 *
788 * Use when tool messages must approve or deny tool execution for tools with the
789 * `needsApproval` property set.
790 *
791 * **Example** (Creating tool approval responses)
792 *
793 * ```ts import.meta.vitest
794 * import { Prompt } from "effect/ai"
795 *
796 * const approvalResponse: Prompt.ToolApprovalResponsePart = Prompt.makePart(
797 * "tool-approval-response",
798 * {
799 * approvalId: "approval_123",
800 * approved: true
801 * }
802 * )
803 *
804 * const denialResponse: Prompt.ToolApprovalResponsePart = Prompt.makePart(
805 * "tool-approval-response",
806 * {
807 * approvalId: "approval_456",
808 * approved: false,
809 * reason: "Operation not allowed"
810 * }
811 * )
812 *
813 * const result = [approvalResponse.approved, denialResponse.approved] // => [true, false]
814 * ```
815 *
816 * @stability unstable
817 * @category models
818 * @since 4.0.0
819 */
820export interface ToolApprovalResponsePart extends BasePart<"tool-approval-response", ToolApprovalResponsePartOptions> {
821 /**
822 * References the original approval request.
823 */
824 readonly approvalId: string
825 /**
826 * User's decision to approve or deny the tool execution.
827 */
828 readonly approved: boolean
829 /**
830 * Optional justification for the decision.
831 */
832 readonly reason?: string | undefined
833}
834
835/**
836 * Encoded representation of tool approval response parts for serialization.
837 *
838 * @stability unstable
839 * @category models
840 * @since 4.0.0
841 */
842export interface ToolApprovalResponsePartEncoded
843 extends BasePartEncoded<"tool-approval-response", ToolApprovalResponsePartOptions>
844{
845 /**
846 * References the original approval request.
847 */
848 readonly approvalId: string
849 /**
850 * User's decision to approve or deny the tool execution.
851 */
852 readonly approved: boolean
853 /**
854 * Optional justification for the decision.
855 */
856 readonly reason?: string | undefined
857}
858
859/**
860 * Represents provider-specific options that can be associated with a
861 * `ToolApprovalResponsePart` through module augmentation.
862 *
863 * @stability unstable
864 * @category options
865 * @since 4.0.0
866 */
867export interface ToolApprovalResponsePartOptions extends ProviderOptions {}
868
869/**
870 * Schema for validation and encoding of tool approval response parts.
871 *
872 * @stability unstable
873 * @category schemas
874 * @since 4.0.0
875 */
876export const ToolApprovalResponsePart: Schema.Struct<{
877 readonly type: Schema.Literal<"tool-approval-response">
878 readonly approvalId: Schema.String
879 readonly approved: Schema.Boolean
880 readonly reason: Schema.optional<Schema.String>
881 readonly "~effect/ai/Prompt/Part": Schema.withDecodingDefaultKey<Schema.Literal<"~effect/ai/Prompt/Part">>
882 readonly options: Schema.withDecodingDefault<
883 Schema.$Record<
884 Schema.String,
885 Schema.NullOr<Schema.Codec<Schema.Json>>
886 >
887 >
888}> = Schema.Struct({
889 ...BasePart.fields,
890 type: Schema.Literal("tool-approval-response"),
891 approvalId: Schema.String,
892 approved: Schema.Boolean,
893 reason: Schema.optional(Schema.String)
894}).annotate({ identifier: "ToolApprovalResponsePart" })
895
896/**
897 * Constructs a new tool approval response part.
898 *
899 * @stability unstable
900 * @category constructors
901 * @since 4.0.0
902 */
903export const toolApprovalResponsePart = (
904 params: PartConstructorParams<ToolApprovalResponsePart>
905): ToolApprovalResponsePart => makePart("tool-approval-response", params as any)
906
907// =============================================================================
908// Tool Approval Request Part
909// =============================================================================
910
911/**
912 * Content part representing a tool approval request from the framework.
913 *
914 * **Details**
915 *
916 * Tool approval request parts are stored in assistant messages when a tool
917 * requires user approval before execution. The user responds with a
918 * `ToolApprovalResponsePart` in a tool message.
919 *
920 * **Example** (Creating tool approval requests)
921 *
922 * ```ts import.meta.vitest
923 * import { Prompt } from "effect/ai"
924 *
925 * const approvalRequest: Prompt.ToolApprovalRequestPart = Prompt.makePart(
926 * "tool-approval-request",
927 * {
928 * approvalId: "approval_123",
929 * toolCallId: "call_456"
930 * }
931 * )
932 * const result = [approvalRequest.approvalId, approvalRequest.toolCallId] // => ["approval_123", "call_456"]
933 * ```
934 *
935 * @stability unstable
936 * @category models
937 * @since 4.0.0
938 */
939export interface ToolApprovalRequestPart extends BasePart<"tool-approval-request", ToolApprovalRequestPartOptions> {
940 /**
941 * Unique identifier for this approval flow.
942 */
943 readonly approvalId: string
944 /**
945 * The tool call ID requiring approval.
946 */
947 readonly toolCallId: string
948}
949
950/**
951 * Encoded representation of tool approval request parts for serialization.
952 *
953 * @stability unstable
954 * @category models
955 * @since 4.0.0
956 */
957export interface ToolApprovalRequestPartEncoded
958 extends BasePartEncoded<"tool-approval-request", ToolApprovalRequestPartOptions>
959{
960 /**
961 * Unique identifier for this approval flow.
962 */
963 readonly approvalId: string
964 /**
965 * The tool call ID requiring approval.
966 */
967 readonly toolCallId: string
968}
969
970/**
971 * Represents provider-specific options that can be associated with a
972 * `ToolApprovalRequestPart` through module augmentation.
973 *
974 * @stability unstable
975 * @category options
976 * @since 4.0.0
977 */
978export interface ToolApprovalRequestPartOptions extends ProviderOptions {}
979
980/**
981 * Schema for validation and encoding of tool approval request parts.
982 *
983 * @stability unstable
984 * @category schemas
985 * @since 4.0.0
986 */
987export const ToolApprovalRequestPart: Schema.Struct<{
988 readonly type: Schema.Literal<"tool-approval-request">
989 readonly approvalId: Schema.String
990 readonly toolCallId: Schema.String
991 readonly "~effect/ai/Prompt/Part": Schema.withDecodingDefaultKey<Schema.Literal<"~effect/ai/Prompt/Part">>
992 readonly options: Schema.withDecodingDefault<
993 Schema.$Record<
994 Schema.String,
995 Schema.NullOr<Schema.Codec<Schema.Json>>
996 >
997 >
998}> = Schema.Struct({
999 ...BasePart.fields,
1000 type: Schema.Literal("tool-approval-request"),
1001 approvalId: Schema.String,
1002 toolCallId: Schema.String
1003}).annotate({ identifier: "ToolApprovalRequestPart" })
1004
1005/**
1006 * Constructs a new tool approval request part.
1007 *
1008 * @stability unstable
1009 * @category constructors
1010 * @since 4.0.0
1011 */
1012export const toolApprovalRequestPart = (
1013 params: PartConstructorParams<ToolApprovalRequestPart>
1014): ToolApprovalRequestPart => makePart("tool-approval-request", params as any)
1015
1016/**
1017 * Schema for validation and encoding of content parts.
1018 *
1019 * @stability unstable
1020 * @category schemas
1021 * @since 4.0.0
1022 */
1023export const Part: Schema.Union<
1024 readonly [
1025 typeof TextPart,
1026 typeof ReasoningPart,
1027 typeof FilePart,
1028 typeof ToolCallPart,
1029 typeof ToolResultPart,
1030 typeof ToolApprovalResponsePart,
1031 typeof ToolApprovalRequestPart
1032 ]
1033> = Schema.Union([
1034 TextPart,
1035 ReasoningPart,
1036 FilePart,
1037 ToolCallPart,
1038 ToolResultPart,
1039 ToolApprovalResponsePart,
1040 ToolApprovalRequestPart
1041])
1042
1043// =============================================================================
1044// Base Message
1045// =============================================================================
1046
1047const MessageTypeId = "~effect/ai/Prompt/Message" as const
1048
1049/**
1050 * Type guard to check if a value is a Message.
1051 *
1052 * @stability unstable
1053 * @category guards
1054 * @since 4.0.0
1055 */
1056export const isMessage = (u: unknown): u is Message => Predicate.hasProperty(u, MessageTypeId)
1057
1058/**
1059 * Base interface for all message types.
1060 *
1061 * **Details**
1062 *
1063 * It provides the common structure shared by all messages, including the role
1064 * and provider options.
1065 *
1066 * @stability unstable
1067 * @category models
1068 * @since 4.0.0
1069 */
1070export interface BaseMessage<Role extends string, Options extends ProviderOptions> {
1071 readonly [MessageTypeId]: typeof MessageTypeId
1072 /**
1073 * The role of the message participant.
1074 */
1075 readonly role: Role
1076 /**
1077 * Provider-specific options for this message.
1078 */
1079 readonly options: Options
1080}
1081
1082/**
1083 * Base interface for encoded message types.
1084 *
1085 * @stability unstable
1086 * @category models
1087 * @since 4.0.0
1088 */
1089export interface BaseMessageEncoded<Role extends string, Options extends ProviderOptions> {
1090 /**
1091 * The role of the message participant.
1092 */
1093 readonly role: Role
1094 /**
1095 * Provider-specific options for this message.
1096 */
1097 readonly options?: Options | undefined
1098}
1099
1100const BaseMessage = Schema.Struct({
1101 [MessageTypeId]: Schema.Literal(MessageTypeId).pipe(
1102 Schema.withDecodingDefaultKey(Effect.succeed(MessageTypeId), { encodingStrategy: "omit" })
1103 ),
1104 options: ProviderOptions.pipe(Schema.withDecodingDefault(Effect.succeed({})))
1105})
1106
1107/**
1108 * Creates a new message with the specified role.
1109 *
1110 * **Example** (Creating messages)
1111 *
1112 * ```ts import.meta.vitest
1113 * import { Prompt } from "effect/ai"
1114 *
1115 * const textPart = Prompt.makePart("text", {
1116 * text: "Hello, world!"
1117 * })
1118 *
1119 * const userMessage = Prompt.makeMessage("user", {
1120 * content: [textPart]
1121 * })
1122 * const result = [userMessage.role, userMessage.content.length] // => ["user", 1]
1123 * ```
1124 *
1125 * @stability unstable
1126 * @category constructors
1127 * @since 4.0.0
1128 */
1129export const makeMessage = <const Role extends Message["role"]>(
1130 role: Role,
1131 params: Omit<Extract<Message, { role: Role }>, typeof MessageTypeId | "role" | "options"> & {
1132 readonly options?: Extract<Message, { role: Role }>["options"] | undefined
1133 }
1134): Extract<Message, { role: Role }> => (({
1135 ...params,
1136 [MessageTypeId]: MessageTypeId,
1137 role,
1138 options: params.options ?? {}
1139}) as any)
1140
1141/**
1142 * A utility type for specifying the parameters required to construct a
1143 * specific message for a prompt.
1144 *
1145 * @stability unstable
1146 * @category utility types
1147 * @since 4.0.0
1148 */
1149export type MessageConstructorParams<M extends Message> = Omit<M, typeof MessageTypeId | "role" | "options"> & {
1150 /**
1151 * Optional provider-specific options for this message.
1152 */
1153 readonly options?: M["options"] | undefined
1154}
1155
1156/**
1157 * Schema that decodes a string into content containing a single `TextPart` and,
1158 * when encoding, emits the `text` value of the first part.
1159 *
1160 * @stability unstable
1161 * @category schemas
1162 * @since 4.0.0
1163 */
1164export const ContentFromString: Schema.decodeTo<
1165 Schema.NonEmptyArray<
1166 Schema.toType<
1167 Schema.Struct<{
1168 readonly type: Schema.Literal<"text">
1169 readonly text: Schema.String
1170 readonly "~effect/ai/Prompt/Part": Schema.withDecodingDefaultKey<Schema.Literal<"~effect/ai/Prompt/Part">>
1171 readonly options: Schema.withDecodingDefault<
1172 Schema.$Record<
1173 Schema.String,
1174 Schema.NullOr<Schema.Codec<Schema.Json>>
1175 >
1176 >
1177 }>
1178 >
1179 >,
1180 Schema.String
1181> = Schema.String.pipe(
1182 Schema.decodeTo(
1183 Schema.NonEmptyArray(Schema.toType(TextPart)),
1184 SchemaTransformation.transform({
1185 decode: (text) => Arr.of(makePart("text", { text })) as Arr.NonEmptyReadonlyArray<TextPart>,
1186 encode: (content) => content[0].text
1187 })
1188 )
1189)
1190
1191// =============================================================================
1192// System Message
1193// =============================================================================
1194
1195/**
1196 * Message representing system instructions or context.
1197 *
1198 * **Example** (Creating system messages)
1199 *
1200 * ```ts import.meta.vitest
1201 * import { Prompt } from "effect/ai"
1202 *
1203 * const systemMessage: Prompt.SystemMessage = Prompt.makeMessage("system", {
1204 * content: "You are a helpful assistant specialized in mathematics. " +
1205 * "Always show your work step by step."
1206 * })
1207 * systemMessage.role // => "system"
1208 * ```
1209 *
1210 * @stability unstable
1211 * @category models
1212 * @since 4.0.0
1213 */
1214export interface SystemMessage extends BaseMessage<"system", SystemMessageOptions> {
1215 /**
1216 * The system instruction or context as plain text.
1217 */
1218 readonly content: string
1219}
1220
1221/**
1222 * Encoded representation of system messages for serialization.
1223 *
1224 * @stability unstable
1225 * @category models
1226 * @since 4.0.0
1227 */
1228export interface SystemMessageEncoded extends BaseMessageEncoded<"system", SystemMessageOptions> {
1229 /**
1230 * The system instruction or context as plain text.
1231 */
1232 readonly content: string
1233}
1234
1235/**
1236 * Represents provider-specific options that can be associated with a
1237 * `SystemMessage` through module augmentation.
1238 *
1239 * @stability unstable
1240 * @category options
1241 * @since 4.0.0
1242 */
1243export interface SystemMessageOptions extends ProviderOptions {}
1244
1245/**
1246 * Schema for validation and encoding of system messages.
1247 *
1248 * @stability unstable
1249 * @category schemas
1250 * @since 4.0.0
1251 */
1252export const SystemMessage: Schema.Struct<{
1253 readonly role: Schema.Literal<"system">
1254 readonly content: Schema.String
1255 readonly "~effect/ai/Prompt/Message": Schema.withDecodingDefaultKey<Schema.Literal<"~effect/ai/Prompt/Message">>
1256 readonly options: Schema.withDecodingDefault<
1257 Schema.$Record<
1258 Schema.String,
1259 Schema.NullOr<Schema.Codec<Schema.Json>>
1260 >
1261 >
1262}> = Schema.Struct({
1263 ...BaseMessage.fields,
1264 role: Schema.Literal("system"),
1265 content: Schema.String
1266}).annotate({ identifier: "SystemMessage" })
1267
1268/**
1269 * Constructs a new system message.
1270 *
1271 * @stability unstable
1272 * @category constructors
1273 * @since 4.0.0
1274 */
1275export const systemMessage = (params: MessageConstructorParams<SystemMessage>): SystemMessage =>
1276 makeMessage("system", params)
1277
1278// =============================================================================
1279// User Message
1280// =============================================================================
1281
1282/**
1283 * Message representing user input or questions.
1284 *
1285 * **Example** (Creating user messages)
1286 *
1287 * ```ts import.meta.vitest
1288 * import { Prompt } from "effect/ai"
1289 *
1290 * const textUserMessage: Prompt.UserMessage = Prompt.makeMessage("user", {
1291 * content: [
1292 * Prompt.makePart("text", {
1293 * text: "Can you analyze this image for me?"
1294 * })
1295 * ]
1296 * })
1297 *
1298 * const multimodalUserMessage: Prompt.UserMessage = Prompt.makeMessage("user", {
1299 * content: [
1300 * Prompt.makePart("text", {
1301 * text: "What do you see in this image?"
1302 * }),
1303 * Prompt.makePart("file", {
1304 * mediaType: "image/jpeg",
1305 * fileName: "vacation.jpg",
1306 * data: "data:image/jpeg;base64,..."
1307 * })
1308 * ]
1309 * })
1310 *
1311 * const result = [textUserMessage.content.length, multimodalUserMessage.content.length] // => [1, 2]
1312 * ```
1313 *
1314 * @stability unstable
1315 * @category models
1316 * @since 4.0.0
1317 */
1318export interface UserMessage extends BaseMessage<"user", UserMessageOptions> {
1319 /**
1320 * Array of content parts that make up the user's message.
1321 */
1322 readonly content: ReadonlyArray<UserMessagePart>
1323}
1324
1325/**
1326 * Union type of content parts allowed in user messages.
1327 *
1328 * @stability unstable
1329 * @category models
1330 * @since 4.0.0
1331 */
1332export type UserMessagePart = TextPart | FilePart
1333
1334/**
1335 * Encoded representation of user messages for serialization.
1336 *
1337 * @stability unstable
1338 * @category models
1339 * @since 4.0.0
1340 */
1341export interface UserMessageEncoded extends BaseMessageEncoded<"user", UserMessageOptions> {
1342 /**
1343 * Array of content parts that make up the user's message.
1344 */
1345 readonly content: string | ReadonlyArray<UserMessagePartEncoded>
1346}
1347
1348/**
1349 * Union type of encoded content parts for user messages.
1350 *
1351 * @stability unstable
1352 * @category models
1353 * @since 4.0.0
1354 */
1355export type UserMessagePartEncoded = TextPartEncoded | FilePartEncoded
1356
1357/**
1358 * Schema for validation and encoding of user message content parts.
1359 *
1360 * @stability unstable
1361 * @category schemas
1362 * @since 4.0.0
1363 */
1364export const UserMessagePart: Schema.Union<readonly [typeof TextPart, typeof FilePart]> = Schema.Union([
1365 TextPart,
1366 FilePart
1367])
1368
1369/**
1370 * Represents provider-specific options that can be associated with a
1371 * `UserMessage` through module augmentation.
1372 *
1373 * @stability unstable
1374 * @category options
1375 * @since 4.0.0
1376 */
1377export interface UserMessageOptions extends ProviderOptions {}
1378
1379/**
1380 * Schema for validation and encoding of user messages.
1381 *
1382 * @stability unstable
1383 * @category schemas
1384 * @since 4.0.0
1385 */
1386export const UserMessage: Schema.Struct<{
1387 readonly role: Schema.Literal<"user">
1388 readonly content: Schema.Union<
1389 readonly [
1390 Schema.decodeTo<
1391 Schema.NonEmptyArray<
1392 Schema.toType<
1393 Schema.Struct<{
1394 readonly type: Schema.Literal<"text">
1395 readonly text: Schema.String
1396 readonly "~effect/ai/Prompt/Part": Schema.withDecodingDefaultKey<Schema.Literal<"~effect/ai/Prompt/Part">>
1397 readonly options: Schema.withDecodingDefault<
1398 Schema.$Record<
1399 Schema.String,
1400 Schema.NullOr<Schema.Codec<Schema.Json>>
1401 >
1402 >
1403 }>
1404 >
1405 >,
1406 Schema.String,
1407 never,
1408 never
1409 >,
1410 Schema.$Array<
1411 Schema.Union<
1412 readonly [
1413 Schema.Struct<{
1414 readonly type: Schema.Literal<"text">
1415 readonly text: Schema.String
1416 readonly "~effect/ai/Prompt/Part": Schema.withDecodingDefaultKey<Schema.Literal<"~effect/ai/Prompt/Part">>
1417 readonly options: Schema.withDecodingDefault<
1418 Schema.$Record<
1419 Schema.String,
1420 Schema.NullOr<Schema.Codec<Schema.Json>>
1421 >
1422 >
1423 }>,
1424 Schema.Struct<{
1425 readonly type: Schema.Literal<"file">
1426 readonly mediaType: Schema.String
1427 readonly fileName: Schema.optional<Schema.String>
1428 readonly data: Schema.Union<readonly [Schema.String, Schema.Uint8Array, Schema.URL]>
1429 readonly "~effect/ai/Prompt/Part": Schema.withDecodingDefaultKey<Schema.Literal<"~effect/ai/Prompt/Part">>
1430 readonly options: Schema.withDecodingDefault<
1431 Schema.$Record<
1432 Schema.String,
1433 Schema.NullOr<Schema.Codec<Schema.Json>>
1434 >
1435 >
1436 }>
1437 ]
1438 >
1439 >
1440 ]
1441 >
1442 readonly "~effect/ai/Prompt/Message": Schema.withDecodingDefaultKey<Schema.Literal<"~effect/ai/Prompt/Message">>
1443 readonly options: Schema.withDecodingDefault<
1444 Schema.$Record<
1445 Schema.String,
1446 Schema.NullOr<Schema.Codec<Schema.Json>>
1447 >
1448 >
1449}> = Schema.Struct({
1450 ...BaseMessage.fields,
1451 role: Schema.Literal("user"),
1452 content: Schema.Union([
1453 ContentFromString.check(
1454 Schema.makeFilter((content) => content.length === 1 && Object.keys(content[0].options).length === 0)
1455 ),
1456 Schema.Array(Schema.Union([TextPart, FilePart]))
1457 ])
1458}).annotate({ identifier: "UserMessage" })
1459
1460/**
1461 * Constructs a new user message.
1462 *
1463 * @stability unstable
1464 * @category constructors
1465 * @since 4.0.0
1466 */
1467export const userMessage = (params: MessageConstructorParams<UserMessage>): UserMessage => makeMessage("user", params)
1468
1469// =============================================================================
1470// Assistant Message
1471// =============================================================================
1472
1473/**
1474 * Message representing large language model assistant responses.
1475 *
1476 * **Example** (Creating assistant messages)
1477 *
1478 * ```ts import.meta.vitest
1479 * import { Prompt } from "effect/ai"
1480 *
1481 * const assistantMessage: Prompt.AssistantMessage = Prompt.makeMessage(
1482 * "assistant",
1483 * {
1484 * content: [
1485 * Prompt.makePart("text", {
1486 * text:
1487 * "I can check the current weather for San Francisco."
1488 * }),
1489 * Prompt.makePart("tool-call", {
1490 * id: "call_123",
1491 * name: "get_weather",
1492 * params: { city: "San Francisco" },
1493 * providerExecuted: false
1494 * }),
1495 * Prompt.makePart("tool-result", {
1496 * id: "call_123",
1497 * name: "get_weather",
1498 * isFailure: false,
1499 * result: {
1500 * temperature: 72,
1501 * condition: "sunny"
1502 * },
1503 * providerExecuted: true
1504 * }),
1505 * Prompt.makePart("text", {
1506 * text: "The weather in San Francisco is currently 72°F and sunny."
1507 * })
1508 * ]
1509 * }
1510 * )
1511 * assistantMessage.content.map((part) => part.type) // => ["text", "tool-call", "tool-result", "text"]
1512 * ```
1513 *
1514 * @stability unstable
1515 * @category models
1516 * @since 4.0.0
1517 */
1518export interface AssistantMessage extends BaseMessage<"assistant", AssistantMessageOptions> {
1519 /**
1520 * Array of content parts that make up the assistant's response.
1521 */
1522 readonly content: ReadonlyArray<AssistantMessagePart>
1523}
1524
1525/**
1526 * Union type of content parts allowed in assistant messages.
1527 *
1528 * @stability unstable
1529 * @category models
1530 * @since 4.0.0
1531 */
1532export type AssistantMessagePart =
1533 | TextPart
1534 | FilePart
1535 | ReasoningPart
1536 | ToolCallPart
1537 | ToolResultPart
1538 | ToolApprovalRequestPart
1539
1540/**
1541 * Encoded representation of assistant messages for serialization.
1542 *
1543 * @stability unstable
1544 * @category models
1545 * @since 4.0.0
1546 */
1547export interface AssistantMessageEncoded extends BaseMessageEncoded<"assistant", AssistantMessageOptions> {
1548 readonly content: string | ReadonlyArray<AssistantMessagePartEncoded>
1549}
1550
1551/**
1552 * Union type of encoded content parts for assistant messages.
1553 *
1554 * @stability unstable
1555 * @category models
1556 * @since 4.0.0
1557 */
1558export type AssistantMessagePartEncoded =
1559 | TextPartEncoded
1560 | FilePartEncoded
1561 | ReasoningPartEncoded
1562 | ToolCallPartEncoded
1563 | ToolResultPartEncoded
1564 | ToolApprovalRequestPartEncoded
1565
1566/**
1567 * Schema for validation and encoding of assistant message content parts.
1568 *
1569 * @stability unstable
1570 * @category schemas
1571 * @since 4.0.0
1572 */
1573export const AssistantMessagePart: Schema.Union<
1574 readonly [
1575 typeof TextPart,
1576 typeof FilePart,
1577 typeof ReasoningPart,
1578 typeof ToolCallPart,
1579 typeof ToolResultPart,
1580 typeof ToolApprovalRequestPart
1581 ]
1582> = Schema.Union([
1583 TextPart,
1584 FilePart,
1585 ReasoningPart,
1586 ToolCallPart,
1587 ToolResultPart,
1588 ToolApprovalRequestPart
1589])
1590
1591/**
1592 * Represents provider-specific options that can be associated with a
1593 * `AssistantMessage` through module augmentation.
1594 *
1595 * @stability unstable
1596 * @category options
1597 * @since 4.0.0
1598 */
1599export interface AssistantMessageOptions extends ProviderOptions {}
1600
1601/**
1602 * Schema for validation and encoding of assistant messages.
1603 *
1604 * **Details**
1605 *
1606 * Assistant content can be a string decoded through `ContentFromString` or an
1607 * array of text, file, reasoning, tool-call, tool-result, and
1608 * tool-approval-request parts.
1609 *
1610 * @stability unstable
1611 * @category schemas
1612 * @since 4.0.0
1613 */
1614export const AssistantMessage: Schema.Struct<{
1615 readonly role: Schema.Literal<"assistant">
1616 readonly content: Schema.Union<
1617 readonly [
1618 Schema.decodeTo<
1619 Schema.NonEmptyArray<
1620 Schema.toType<
1621 Schema.Struct<{
1622 readonly type: Schema.Literal<"text">
1623 readonly text: Schema.String
1624 readonly "~effect/ai/Prompt/Part": Schema.withDecodingDefaultKey<Schema.Literal<"~effect/ai/Prompt/Part">>
1625 readonly options: Schema.withDecodingDefault<
1626 Schema.$Record<
1627 Schema.String,
1628 Schema.NullOr<Schema.Codec<Schema.Json>>
1629 >
1630 >
1631 }>
1632 >
1633 >,
1634 Schema.String,
1635 never,
1636 never
1637 >,
1638 Schema.$Array<
1639 Schema.Union<
1640 readonly [
1641 typeof TextPart,
1642 typeof FilePart,
1643 typeof ReasoningPart,
1644 typeof ToolCallPart,
1645 typeof ToolResultPart,
1646 typeof ToolApprovalRequestPart
1647 ]
1648 >
1649 >
1650 ]
1651 >
1652 readonly "~effect/ai/Prompt/Message": Schema.withDecodingDefaultKey<Schema.Literal<"~effect/ai/Prompt/Message">>
1653 readonly options: Schema.withDecodingDefault<
1654 Schema.$Record<
1655 Schema.String,
1656 Schema.NullOr<Schema.Codec<Schema.Json>>
1657 >
1658 >
1659}> = Schema.Struct({
1660 ...BaseMessage.fields,
1661 role: Schema.Literal("assistant"),
1662 content: Schema.Union([
1663 ContentFromString.check(
1664 Schema.makeFilter((content) => content.length === 1 && Object.keys(content[0].options).length === 0)
1665 ),
1666 Schema.Array(Schema.Union([
1667 TextPart,
1668 FilePart,
1669 ReasoningPart,
1670 ToolCallPart,
1671 ToolResultPart,
1672 ToolApprovalRequestPart
1673 ]))
1674 ])
1675}).annotate({ identifier: "AssistantMessage" })
1676
1677/**
1678 * Constructs a new assistant message.
1679 *
1680 * **When to use**
1681 *
1682 * Use to add assistant-role prompt history or model responses.
1683 *
1684 * **Details**
1685 *
1686 * This is the role-specific wrapper around `makeMessage("assistant", params)`.
1687 *
1688 * @stability unstable
1689 * @category constructors
1690 * @since 4.0.0
1691 */
1692export const assistantMessage = (params: MessageConstructorParams<AssistantMessage>): AssistantMessage =>
1693 makeMessage("assistant", params)
1694
1695// =============================================================================
1696// Tool Message
1697// =============================================================================
1698
1699/**
1700 * Message carrying tool-side content, including tool execution results and
1701 * responses to tool approval requests.
1702 *
1703 * **Example** (Creating tool messages)
1704 *
1705 * ```ts import.meta.vitest
1706 * import { Prompt } from "effect/ai"
1707 *
1708 * const toolMessage: Prompt.ToolMessage = Prompt.makeMessage("tool", {
1709 * content: [
1710 * Prompt.makePart("tool-result", {
1711 * id: "call_123",
1712 * name: "search_web",
1713 * isFailure: false,
1714 * result: {
1715 * query: "TypeScript best practices",
1716 * results: [
1717 * { title: "TypeScript Handbook", url: "https://..." },
1718 * { title: "Effective TypeScript", url: "https://..." }
1719 * ]
1720 * },
1721 * providerExecuted: false
1722 * })
1723 * ]
1724 * })
1725 * const result = [toolMessage.role, toolMessage.content[0].type] // => ["tool", "tool-result"]
1726 * ```
1727 *
1728 * @stability unstable
1729 * @category models
1730 * @since 4.0.0
1731 */
1732export interface ToolMessage extends BaseMessage<"tool", ToolMessageOptions> {
1733 /**
1734 * Array of tool result parts.
1735 */
1736 readonly content: ReadonlyArray<ToolMessagePart>
1737}
1738
1739/**
1740 * Union type of content parts allowed in tool messages.
1741 *
1742 * @stability unstable
1743 * @category models
1744 * @since 4.0.0
1745 */
1746export type ToolMessagePart = ToolResultPart | ToolApprovalResponsePart
1747
1748/**
1749 * Encoded representation of tool messages for serialization.
1750 *
1751 * @stability unstable
1752 * @category models
1753 * @since 4.0.0
1754 */
1755export interface ToolMessageEncoded extends BaseMessageEncoded<"tool", ToolMessageOptions> {
1756 /**
1757 * Array of tool result parts.
1758 */
1759 readonly content: ReadonlyArray<ToolMessagePartEncoded>
1760}
1761
1762/**
1763 * Union type of encoded content parts for tool messages.
1764 *
1765 * @stability unstable
1766 * @category models
1767 * @since 4.0.0
1768 */
1769export type ToolMessagePartEncoded = ToolResultPartEncoded | ToolApprovalResponsePartEncoded
1770
1771/**
1772 * Schema for validation and encoding of tool message content parts.
1773 *
1774 * @stability unstable
1775 * @category schemas
1776 * @since 4.0.0
1777 */
1778export const ToolMessagePart: Schema.Union<
1779 readonly [typeof ToolResultPart, typeof ToolApprovalResponsePart]
1780> = Schema.Union([
1781 ToolResultPart,
1782 ToolApprovalResponsePart
1783])
1784
1785/**
1786 * Represents provider-specific options that can be associated with a
1787 * `ToolMessage` through module augmentation.
1788 *
1789 * @stability unstable
1790 * @category options
1791 * @since 4.0.0
1792 */
1793export interface ToolMessageOptions extends ProviderOptions {}
1794
1795/**
1796 * Schema for validation and encoding of tool messages.
1797 *
1798 * @stability unstable
1799 * @category schemas
1800 * @since 4.0.0
1801 */
1802export const ToolMessage: Schema.Struct<{
1803 readonly role: Schema.Literal<"tool">
1804 readonly content: Schema.$Array<
1805 Schema.Union<readonly [typeof ToolResultPart, typeof ToolApprovalResponsePart]>
1806 >
1807 readonly "~effect/ai/Prompt/Message": Schema.withDecodingDefaultKey<Schema.Literal<"~effect/ai/Prompt/Message">>
1808 readonly options: Schema.withDecodingDefault<
1809 Schema.$Record<
1810 Schema.String,
1811 Schema.NullOr<Schema.Codec<Schema.Json>>
1812 >
1813 >
1814}> = Schema.Struct({
1815 ...BaseMessage.fields,
1816 role: Schema.Literal("tool"),
1817 content: Schema.Array(Schema.Union([ToolResultPart, ToolApprovalResponsePart]))
1818}).annotate({ identifier: "ToolMessage" })
1819
1820/**
1821 * Constructs a new tool message.
1822 *
1823 * @stability unstable
1824 * @category constructors
1825 * @since 4.0.0
1826 */
1827export const toolMessage = (params: MessageConstructorParams<ToolMessage>): ToolMessage => makeMessage("tool", params)
1828
1829// =============================================================================
1830// Message
1831// =============================================================================
1832
1833/**
1834 * A type representing all possible message types in a conversation.
1835 *
1836 * @stability unstable
1837 * @category models
1838 * @since 4.0.0
1839 */
1840export type Message =
1841 | SystemMessage
1842 | UserMessage
1843 | AssistantMessage
1844 | ToolMessage
1845
1846/**
1847 * A type representing all possible encoded message types for serialization.
1848 *
1849 * @stability unstable
1850 * @category models
1851 * @since 4.0.0
1852 */
1853export type MessageEncoded =
1854 | SystemMessageEncoded
1855 | UserMessageEncoded
1856 | AssistantMessageEncoded
1857 | ToolMessageEncoded
1858
1859/**
1860 * Schema for validation and encoding of messages.
1861 *
1862 * @stability unstable
1863 * @category schemas
1864 * @since 4.0.0
1865 */
1866export const Message: Schema.Codec<Message, MessageEncoded> = Schema.Union([
1867 SystemMessage,
1868 UserMessage,
1869 AssistantMessage,
1870 ToolMessage
1871])
1872
1873// =============================================================================
1874// Prompt
1875// =============================================================================
1876
1877const TypeId = "~effect/ai/Prompt" as const
1878
1879/**
1880 * Type guard to check if a value is a Prompt.
1881 *
1882 * @stability unstable
1883 * @category guards
1884 * @since 4.0.0
1885 */
1886export const isPrompt = (u: unknown): u is Prompt => Predicate.hasProperty(u, TypeId)
1887
1888/**
1889 * A Prompt contains a sequence of messages that form the context of a
1890 * conversation with a large language model.
1891 *
1892 * @stability unstable
1893 * @category models
1894 * @since 4.0.0
1895 */
1896export interface Prompt extends Pipeable {
1897 readonly [TypeId]: typeof TypeId
1898 /**
1899 * Array of messages that make up the conversation.
1900 */
1901 readonly content: ReadonlyArray<Message>
1902}
1903
1904/**
1905 * Encoded representation of prompts for serialization.
1906 *
1907 * @stability unstable
1908 * @category models
1909 * @since 4.0.0
1910 */
1911export interface PromptEncoded {
1912 /**
1913 * Array of messages that make up the conversation.
1914 */
1915 readonly content: ReadonlyArray<MessageEncoded>
1916}
1917
1918const $Prompt = Schema.declare((u) => isPrompt(u), { identifier: "Prompt" })
1919
1920// TODO: is the type annotation necessary?
1921// TODO: shoudn't the name be `PromptFrom...`?
1922// TODO: is the explicit encoding necessary? maybe use the default JSON serializer?
1923/**
1924 * Schema for AI prompt instances.
1925 *
1926 * @stability unstable
1927 * @category schemas
1928 * @since 4.0.0
1929 */
1930export const Prompt: Schema.Codec<Prompt, PromptEncoded> = Schema.Struct({
1931 content: Schema.Array(Schema.toEncoded(Message))
1932}).pipe(
1933 Schema.decodeTo(
1934 $Prompt,
1935 SchemaTransformation.transformEffect({
1936 decode: (input, options) =>
1937 Effect.mapBothEager(
1938 SchemaParser.decodeEffect(Schema.Array(Message))(input.content),
1939 {
1940 onSuccess: makePrompt,
1941 onFailure: () =>
1942 new SchemaIssue.InvalidValue(
1943 { message: "Invalid Prompt messages" },
1944 input.content,
1945 options
1946 )
1947 }
1948 ),
1949 encode: (prompt, options) =>
1950 Effect.mapBothEager(
1951 SchemaParser.encodeEffect(Schema.Array(Message))(prompt.content),
1952 {
1953 onSuccess: (messages) => ({ content: messages }),
1954 onFailure: () =>
1955 new SchemaIssue.InvalidValue(
1956 { message: "Invalid Prompt messages" },
1957 prompt.content,
1958 options
1959 )
1960 }
1961 )
1962 })
1963 )
1964)
1965
1966/**
1967 * Raw input accepted by `make`: a string, an iterable of encoded messages, or
1968 * an existing `Prompt`.
1969 *
1970 * **Example** (Accepting raw prompt input)
1971 *
1972 * ```ts import.meta.vitest
1973 * import { Prompt } from "effect/ai"
1974 *
1975 * // String input - creates a user message
1976 * const stringInput: Prompt.RawInput = "Hello, world!"
1977 *
1978 * // Message array input
1979 * const messagesInput: Prompt.RawInput = [
1980 * { role: "system", content: "You are helpful." },
1981 * { role: "user", content: [{ type: "text", text: "Hi!" }] }
1982 * ]
1983 *
1984 * const promptInput: Prompt.RawInput = Prompt.empty
1985 *
1986 * const result = [typeof stringInput, Array.isArray(messagesInput), promptInput.content.length] // => ["string", true, 0]
1987 * ```
1988 *
1989 * @stability unstable
1990 * @category models
1991 * @since 4.0.0
1992 */
1993export type RawInput =
1994 | string
1995 | Iterable<MessageEncoded>
1996 | Prompt
1997
1998const Proto = {
1999 [TypeId]: TypeId,
2000 pipe() {
2001 return pipeArguments(this, arguments)
2002 }
2003}
2004
2005const makePrompt = (content: ReadonlyArray<Message>): Prompt =>
2006 Object.assign(Object.create(Proto), {
2007 content
2008 })
2009
2010const decodeMessagesSync = Schema.decodeSync(Schema.Array(Message))
2011
2012/**
2013 * An empty prompt with no messages.
2014 *
2015 * **Example** (Creating an empty prompt)
2016 *
2017 * ```ts import.meta.vitest
2018 * import { Prompt } from "effect/ai"
2019 *
2020 * const emptyPrompt = Prompt.empty
2021 * emptyPrompt.content // => []
2022 * ```
2023 *
2024 * @stability unstable
2025 * @category constructors
2026 * @since 4.0.0
2027 */
2028export const empty: Prompt = makePrompt([])
2029
2030/**
2031 * Creates a `Prompt` from an input.
2032 *
2033 * **Details**
2034 *
2035 * This is the primary constructor for creating prompts, supporting multiple
2036 * input formats for convenience and flexibility.
2037 *
2038 * **Example** (Creating prompts from inputs)
2039 *
2040 * ```ts import.meta.vitest
2041 * import { Prompt } from "effect/ai"
2042 *
2043 * // From string - creates a user message
2044 * const textPrompt = Prompt.make("Hello, how are you?")
2045 *
2046 * // From messages array
2047 * const structuredPrompt = Prompt.make([
2048 * { role: "system", content: "You are a helpful assistant." },
2049 * { role: "user", content: [{ type: "text", text: "Hi!" }] }
2050 * ])
2051 *
2052 * const copiedPrompt = Prompt.make(Prompt.empty)
2053 *
2054 * const result = [textPrompt.content[0].role, structuredPrompt.content.length, copiedPrompt.content.length] // => ["user", 2, 0]
2055 * ```
2056 *
2057 * @stability unstable
2058 * @category constructors
2059 * @since 4.0.0
2060 */
2061export const make = (input: RawInput): Prompt => {
2062 if (typeof input === "string") {
2063 const part = makePart("text", { text: input })
2064 const message = makeMessage("user", { content: [part] })
2065 return makePrompt([message])
2066 }
2067
2068 if (Predicate.isIterable(input)) {
2069 return makePrompt(decodeMessagesSync(Arr.fromIterable(input), {
2070 errors: "all"
2071 }))
2072 }
2073
2074 return input
2075}
2076
2077/**
2078 * Creates a Prompt from an array of messages.
2079 *
2080 * **Example** (Creating prompts from messages)
2081 *
2082 * ```ts import.meta.vitest
2083 * import { Prompt } from "effect/ai"
2084 *
2085 * const messages: ReadonlyArray<Prompt.Message> = [
2086 * Prompt.makeMessage("system", {
2087 * content: "You are a coding assistant."
2088 * }),
2089 * Prompt.makeMessage("user", {
2090 * content: [Prompt.makePart("text", { text: "Help me with TypeScript" })]
2091 * })
2092 * ]
2093 *
2094 * const prompt = Prompt.fromMessages(messages)
2095 * prompt.content.length // => 2
2096 * ```
2097 *
2098 * @stability unstable
2099 * @category constructors
2100 * @since 4.0.0
2101 */
2102export const fromMessages = (messages: ReadonlyArray<Message>): Prompt => makePrompt(messages)
2103
2104const mergeOptions = (left: ProviderOptions, right: ProviderOptions): ProviderOptions => {
2105 const result: Record<string, ProviderOptions[string]> = { ...left }
2106 for (const [provider, metadata] of Object.entries(right)) {
2107 const previous = result[provider]
2108 result[provider] = Predicate.isObject(previous) && Predicate.isObject(metadata)
2109 ? Object.assign({}, previous, metadata)
2110 : metadata
2111 }
2112 return result
2113}
2114
2115/**
2116 * Creates a `Prompt` from response parts by folding completed text and
2117 * reasoning streams into assistant parts, preserving provider metadata as
2118 * prompt options, placing tool calls and approval requests in an assistant
2119 * message, and placing non-preliminary tool results in a tool message using
2120 * their encoded results.
2121 *
2122 * **Example** (Creating prompts from response parts)
2123 *
2124 * ```ts import.meta.vitest
2125 * import { Prompt, Response } from "effect/ai"
2126 *
2127 * const responseParts: ReadonlyArray<Response.AnyPart> = [
2128 * Response.makePart("text", {
2129 * text: "Hello there!"
2130 * }),
2131 * Response.makePart("tool-call", {
2132 * id: "call_1",
2133 * name: "get_time",
2134 * params: {},
2135 * providerExecuted: false
2136 * }),
2137 * Response.makePart("tool-result", {
2138 * id: "call_1",
2139 * name: "get_time",
2140 * isFailure: false,
2141 * result: "10:30 AM",
2142 * encodedResult: "10:30 AM",
2143 * providerExecuted: false,
2144 * preliminary: false
2145 * })
2146 * ]
2147 *
2148 * const prompt = Prompt.fromResponseParts(responseParts)
2149 * // Creates an assistant message with the response content
2150 * prompt.content.map((message) => message.role) // => ["assistant", "tool"]
2151 * ```
2152 *
2153 * @stability unstable
2154 * @category constructors
2155 * @since 4.0.0
2156 */
2157export const fromResponseParts = (parts: ReadonlyArray<Response.AnyPart>): Prompt => {
2158 if (parts.length === 0) {
2159 return empty
2160 }
2161
2162 const assistantParts: Array<AssistantMessagePart> = []
2163 const toolParts: Array<ToolMessagePart> = []
2164
2165 const activeTextDeltas = new Map<string, { text: string; options: ProviderOptions }>()
2166 const activeReasoningDeltas = new Map<string, { text: string; options: ProviderOptions }>()
2167
2168 for (const part of parts) {
2169 switch (part.type) {
2170 // Text Parts
2171 case "text": {
2172 assistantParts.push(makePart("text", { text: part.text, options: part.metadata }))
2173 break
2174 }
2175
2176 // Text Parts (streaming)
2177 case "text-start": {
2178 activeTextDeltas.set(part.id, { text: "", options: part.metadata })
2179 break
2180 }
2181 case "text-delta": {
2182 if (activeTextDeltas.has(part.id)) {
2183 const active = activeTextDeltas.get(part.id)!
2184 active.text += part.delta
2185 active.options = mergeOptions(active.options, part.metadata)
2186 }
2187 break
2188 }
2189 case "text-end": {
2190 if (activeTextDeltas.has(part.id)) {
2191 const active = activeTextDeltas.get(part.id)!
2192 active.options = mergeOptions(active.options, part.metadata)
2193 assistantParts.push(makePart("text", active))
2194 }
2195 break
2196 }
2197
2198 // Reasoning Parts
2199 case "reasoning": {
2200 assistantParts.push(makePart("reasoning", { text: part.text, options: part.metadata }))
2201 break
2202 }
2203
2204 // Reasoning Parts (streaming)
2205 case "reasoning-start": {
2206 activeReasoningDeltas.set(part.id, { text: "", options: part.metadata })
2207 break
2208 }
2209 case "reasoning-delta": {
2210 if (activeReasoningDeltas.has(part.id)) {
2211 const active = activeReasoningDeltas.get(part.id)!
2212 active.text += part.delta
2213 active.options = mergeOptions(active.options, part.metadata)
2214 }
2215 break
2216 }
2217 case "reasoning-end": {
2218 if (activeReasoningDeltas.has(part.id)) {
2219 const active = activeReasoningDeltas.get(part.id)!
2220 active.options = mergeOptions(active.options, part.metadata)
2221 assistantParts.push(makePart("reasoning", active))
2222 }
2223 break
2224 }
2225
2226 // File Parts
2227 case "file": {
2228 assistantParts.push(makePart("file", {
2229 data: Base64.encode(part.data),
2230 mediaType: part.mediaType,
2231 options: part.metadata
2232 }))
2233 break
2234 }
2235
2236 // Tool Call Parts
2237 case "tool-call": {
2238 assistantParts.push(makePart("tool-call", {
2239 id: part.id,
2240 name: part.name,
2241 params: part.params,
2242 providerExecuted: part.providerExecuted ?? false,
2243 options: part.metadata
2244 }))
2245 break
2246 }
2247
2248 // Tool Result Parts (skip preliminary results)
2249 case "tool-result": {
2250 if (part.preliminary !== true) {
2251 const target = part.providerExecuted === true ? assistantParts : toolParts
2252 target.push(makePart("tool-result", {
2253 id: part.id,
2254 name: part.name,
2255 isFailure: part.isFailure,
2256 result: part.encodedResult,
2257 providerExecuted: part.providerExecuted ?? false,
2258 options: part.metadata
2259 }))
2260 }
2261 break
2262 }
2263
2264 // Tool Approval Request Parts
2265 case "tool-approval-request": {
2266 assistantParts.push(makePart("tool-approval-request", {
2267 approvalId: part.approvalId,
2268 toolCallId: part.toolCallId
2269 }))
2270 break
2271 }
2272 }
2273 }
2274
2275 if (assistantParts.length === 0 && toolParts.length === 0) {
2276 return empty
2277 }
2278
2279 const messages: Array<Message> = []
2280
2281 if (assistantParts.length > 0) {
2282 messages.push(makeMessage("assistant", { content: assistantParts }))
2283 }
2284
2285 if (toolParts.length > 0) {
2286 messages.push(makeMessage("tool", { content: toolParts }))
2287 }
2288
2289 return makePrompt(messages)
2290}
2291
2292// =============================================================================
2293// Merging Prompts
2294// =============================================================================
2295
2296/**
2297 * Concatenates a prompt with additional raw input by concatenating messages.
2298 *
2299 * **Details**
2300 *
2301 * The returned prompt contains all messages from the original prompt followed
2302 * by the provided raw input, preserving message order.
2303 *
2304 * **Example** (Concatenating prompts)
2305 *
2306 * ```ts import.meta.vitest
2307 * import { Prompt } from "effect/ai"
2308 *
2309 * const systemPrompt = Prompt.make([{
2310 * role: "system",
2311 * content: "You are a helpful assistant."
2312 * }])
2313 *
2314 * const merged = Prompt.concat(systemPrompt, "Hello, world!")
2315 * merged.content.map((message) => message.role) // => ["system", "user"]
2316 * ```
2317 *
2318 * @stability unstable
2319 * @category combinators
2320 * @since 4.0.0
2321 */
2322export const concat: {
2323 (input: RawInput): (self: Prompt) => Prompt
2324 (self: Prompt, input: RawInput): Prompt
2325} = dual(2, (self: Prompt, input: RawInput): Prompt => {
2326 const other = make(input)
2327 if (self.content.length === 0) {
2328 return other
2329 }
2330 if (other.content.length === 0) {
2331 return self
2332 }
2333 return fromMessages([...self.content, ...other.content])
2334})
2335
2336// =============================================================================
2337// Manipulating Prompts
2338// =============================================================================
2339
2340/**
2341 * Creates a new prompt from the specified prompt with the system message set
2342 * to the specified text content.
2343 *
2344 * **Gotchas**
2345 *
2346 * This method removes and replaces any previous system message from the
2347 * prompt.
2348 *
2349 * **Example** (Replacing system instructions)
2350 *
2351 * ```ts import.meta.vitest
2352 * import { Prompt } from "effect/ai"
2353 *
2354 * const systemPrompt = Prompt.make([{
2355 * role: "system",
2356 * content: "You are a helpful assistant."
2357 * }])
2358 *
2359 * const userPrompt = Prompt.make("Hello, world!")
2360 *
2361 * const prompt = Prompt.concat(systemPrompt, userPrompt)
2362 *
2363 * const replaced = Prompt.setSystem(
2364 * prompt,
2365 * "You are an expert in programming"
2366 * )
2367 * replaced.content[0].content // => "You are an expert in programming"
2368 * ```
2369 *
2370 * @stability unstable
2371 * @category combinators
2372 * @since 4.0.0
2373 */
2374export const setSystem: {
2375 (content: string): (self: Prompt) => Prompt
2376 (self: Prompt, content: string): Prompt
2377} = dual(2, (self: Prompt, content: string): Prompt => {
2378 const messages: Array<Message> = [makeMessage("system", { content })]
2379 for (const message of self.content) {
2380 if (message.role !== "system") {
2381 messages.push(message)
2382 }
2383 }
2384 return makePrompt(messages)
2385})
2386
2387/**
2388 * Creates a new prompt with a leading system message. If the prompt already has
2389 * a system message, the new message uses the provided content prepended to the
2390 * first existing system message's content; the original messages remain after
2391 * it.
2392 *
2393 * **Example** (Prepending system instructions)
2394 *
2395 * ```ts import.meta.vitest
2396 * import { Prompt } from "effect/ai"
2397 *
2398 * const systemPrompt = Prompt.make([{
2399 * role: "system",
2400 * content: "You are an expert in programming."
2401 * }])
2402 *
2403 * const userPrompt = Prompt.make("Hello, world!")
2404 *
2405 * const prompt = Prompt.concat(systemPrompt, userPrompt)
2406 *
2407 * const replaced = Prompt.prependSystem(
2408 * prompt,
2409 * "You are a helpful assistant. "
2410 * )
2411 * // result content: "You are a helpful assistant. You are an expert in programming."
2412 * replaced.content[0].content // => "You are a helpful assistant. You are an expert in programming."
2413 * ```
2414 *
2415 * @stability unstable
2416 * @category combinators
2417 * @since 4.0.0
2418 */
2419export const prependSystem: {
2420 (content: string): (self: Prompt) => Prompt
2421 (self: Prompt, content: string): Prompt
2422} = dual(2, (self: Prompt, content: string): Prompt => {
2423 let system: SystemMessage | undefined = undefined
2424 for (const message of self.content) {
2425 if (message.role === "system") {
2426 system = makeMessage("system", {
2427 content: content + message.content
2428 })
2429 break
2430 }
2431 }
2432 if (Predicate.isUndefined(system)) {
2433 system = makeMessage("system", { content })
2434 }
2435 return makePrompt([system, ...self.content])
2436})
2437
2438/**
2439 * Creates a new prompt with a leading system message. If the prompt already has
2440 * a system message, the new message uses the provided content appended to the
2441 * first existing system message's content; the original messages remain after
2442 * it.
2443 *
2444 * **Example** (Appending system instructions)
2445 *
2446 * ```ts import.meta.vitest
2447 * import { Prompt } from "effect/ai"
2448 *
2449 * const systemPrompt = Prompt.make([{
2450 * role: "system",
2451 * content: "You are an expert in programming."
2452 * }])
2453 *
2454 * const userPrompt = Prompt.make("Hello, world!")
2455 *
2456 * const prompt = Prompt.concat(systemPrompt, userPrompt)
2457 *
2458 * const replaced = Prompt.appendSystem(
2459 * prompt,
2460 * " You are a helpful assistant."
2461 * )
2462 * // result content: "You are an expert in programming. You are a helpful assistant."
2463 * replaced.content[0].content // => "You are an expert in programming. You are a helpful assistant."
2464 * ```
2465 *
2466 * @stability unstable
2467 * @category combinators
2468 * @since 4.0.0
2469 */
2470export const appendSystem: {
2471 (content: string): (self: Prompt) => Prompt
2472 (self: Prompt, content: string): Prompt
2473} = dual(2, (self: Prompt, content: string): Prompt => {
2474 let system: SystemMessage | undefined = undefined
2475 for (const message of self.content) {
2476 if (message.role === "system") {
2477 system = makeMessage("system", {
2478 content: message.content + content
2479 })
2480 break
2481 }
2482 }
2483 if (Predicate.isUndefined(system)) {
2484 system = makeMessage("system", { content })
2485 }
2486 return makePrompt([system, ...self.content])
2487})