@effect/ai 0.28.1 → 0.28.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/Prompt.ts CHANGED
@@ -51,9 +51,14 @@
51
51
  *
52
52
  * @since 1.0.0
53
53
  */
54
+ import * as Arbitrary from "effect/Arbitrary"
55
+ import * as Arr from "effect/Array"
54
56
  import { constFalse, dual } from "effect/Function"
57
+ import * as ParseResult from "effect/ParseResult"
58
+ import { type Pipeable, pipeArguments } from "effect/Pipeable"
55
59
  import * as Predicate from "effect/Predicate"
56
60
  import * as Schema from "effect/Schema"
61
+ import type * as AST from "effect/SchemaAST"
57
62
  import type * as Response from "./Response.js"
58
63
 
59
64
  const constEmptyObject = () => ({})
@@ -221,10 +226,10 @@ export const makePart = <const Type extends Part["type"]>(
221
226
  }
222
227
  ): Extract<Part, { type: Type }> =>
223
228
  (({
224
- ...params,
225
- [PartTypeId]: PartTypeId,
226
- type,
227
- options: params.options ?? {}
229
+ ...params,
230
+ [PartTypeId]: PartTypeId,
231
+ type,
232
+ options: params.options ?? {}
228
233
  }) as any)
229
234
 
230
235
  // =============================================================================
@@ -720,12 +725,28 @@ export const makeMessage = <const Role extends Message["role"]>(
720
725
  }
721
726
  ): Extract<Message, { role: Role }> =>
722
727
  (({
723
- ...params,
724
- [MessageTypeId]: MessageTypeId,
725
- role,
726
- options: params.options ?? {}
728
+ ...params,
729
+ [MessageTypeId]: MessageTypeId,
730
+ role,
731
+ options: params.options ?? {}
727
732
  }) as any)
728
733
 
734
+ /**
735
+ * Schema for decoding message content (i.e. an array containing a single
736
+ * `TextPart`) from a string.
737
+ *
738
+ * @since 1.0.0
739
+ * @category Schemas
740
+ */
741
+ export const MessageContentFromString: Schema.Schema<
742
+ Arr.NonEmptyReadonlyArray<TextPart>,
743
+ string
744
+ > = Schema.transform(Schema.String, Schema.NonEmptyArray(Schema.typeSchema(TextPart)), {
745
+ strict: true,
746
+ decode: (text) => Arr.of(makePart("text", { text })),
747
+ encode: (content) => content[0].text
748
+ })
749
+
729
750
  // =============================================================================
730
751
  // System Message
731
752
  // =============================================================================
@@ -851,7 +872,7 @@ export interface UserMessageEncoded extends BaseMessageEncoded<"user", UserMessa
851
872
  /**
852
873
  * Array of content parts that make up the user's message.
853
874
  */
854
- readonly content: ReadonlyArray<UserMessagePartEncoded>
875
+ readonly content: string | ReadonlyArray<UserMessagePartEncoded>
855
876
  }
856
877
 
857
878
  /**
@@ -879,7 +900,10 @@ export interface UserMessageOptions extends ProviderOptions {}
879
900
  */
880
901
  export const UserMessage: Schema.Schema<UserMessage, UserMessageEncoded> = Schema.Struct({
881
902
  role: Schema.Literal("user"),
882
- content: Schema.Array(Schema.Union(TextPart, FilePart)),
903
+ content: Schema.Union(
904
+ MessageContentFromString,
905
+ Schema.Array(Schema.Union(TextPart, FilePart))
906
+ ),
883
907
  options: Schema.optionalWith(ProviderOptions, { default: constEmptyObject })
884
908
  }).pipe(
885
909
  Schema.attachPropertySignature(MessageTypeId, MessageTypeId),
@@ -950,7 +974,7 @@ export type AssistantMessagePart =
950
974
  * @category Models
951
975
  */
952
976
  export interface AssistantMessageEncoded extends BaseMessageEncoded<"assistant", AssistantMessageOptions> {
953
- readonly content: ReadonlyArray<AssistantMessagePartEncoded>
977
+ readonly content: string | ReadonlyArray<AssistantMessagePartEncoded>
954
978
  }
955
979
 
956
980
  /**
@@ -983,7 +1007,10 @@ export interface AssistantMessageOptions extends ProviderOptions {}
983
1007
  */
984
1008
  export const AssistantMessage: Schema.Schema<AssistantMessage, AssistantMessageEncoded> = Schema.Struct({
985
1009
  role: Schema.Literal("assistant"),
986
- content: Schema.Array(Schema.Union(TextPart, FilePart, ReasoningPart, ToolCallPart, ToolResultPart)),
1010
+ content: Schema.Union(
1011
+ MessageContentFromString,
1012
+ Schema.Array(Schema.Union(TextPart, FilePart, ReasoningPart, ToolCallPart, ToolResultPart))
1013
+ ),
987
1014
  options: Schema.optionalWith(ProviderOptions, { default: constEmptyObject })
988
1015
  }).pipe(
989
1016
  Schema.attachPropertySignature(MessageTypeId, MessageTypeId),
@@ -1157,7 +1184,7 @@ export const isPrompt = (u: unknown): u is Prompt => Predicate.hasProperty(u, Ty
1157
1184
  * @since 1.0.0
1158
1185
  * @category Models
1159
1186
  */
1160
- export interface Prompt {
1187
+ export interface Prompt extends Pipeable {
1161
1188
  readonly [TypeId]: TypeId
1162
1189
  /**
1163
1190
  * Array of messages that make up the conversation.
@@ -1178,18 +1205,61 @@ export interface PromptEncoded {
1178
1205
  readonly content: ReadonlyArray<MessageEncoded>
1179
1206
  }
1180
1207
 
1208
+ /**
1209
+ * Describes a schema that represents a `Prompt` instance.
1210
+ *
1211
+ * @since 1.0.0
1212
+ * @category Schemas
1213
+ */
1214
+ export class PromptFromSelf extends Schema.declare(
1215
+ (u) => isPrompt(u),
1216
+ {
1217
+ identifier: "PromptFromSelf",
1218
+ description: "a Prompt instance",
1219
+ arbitrary: (): Arbitrary.LazyArbitrary<Prompt> => (fc) =>
1220
+ fc.array(
1221
+ Arbitrary.makeLazy(Message)(fc)
1222
+ ).map(makePrompt)
1223
+ }
1224
+ ) {}
1225
+
1181
1226
  /**
1182
1227
  * Schema for validation and encoding of prompts.
1183
1228
  *
1184
1229
  * @since 1.0.0
1185
1230
  * @category Schemas
1186
1231
  */
1187
- export const Prompt: Schema.Schema<Prompt, PromptEncoded> = Schema.Struct({
1188
- content: Schema.Array(Message)
1189
- }).pipe(
1190
- Schema.attachPropertySignature(TypeId, TypeId),
1191
- Schema.annotations({ identifier: "Prompt" })
1192
- )
1232
+ export const Prompt: Schema.Schema<Prompt, PromptEncoded> = Schema.transformOrFail(
1233
+ Schema.Struct({ content: Schema.Array(Schema.encodedSchema(Message)) }),
1234
+ PromptFromSelf,
1235
+ {
1236
+ strict: true,
1237
+ decode: (i, _, ast) => decodePrompt(i, ast),
1238
+ encode: (a, _, ast) => encodePrompt(a, ast)
1239
+ }
1240
+ ).annotations({ identifier: "Prompt" })
1241
+
1242
+ const decodeMessages = ParseResult.decodeEither(Schema.Array(Message))
1243
+ const encodeMessages = ParseResult.encodeEither(Schema.Array(Message))
1244
+
1245
+ const decodePrompt = (input: PromptEncoded, ast: AST.AST) =>
1246
+ ParseResult.mapBoth(decodeMessages(input.content), {
1247
+ onFailure: () => new ParseResult.Type(ast, input, `Unable to decode ${JSON.stringify(input)} into a Prompt`),
1248
+ onSuccess: makePrompt
1249
+ })
1250
+
1251
+ const encodePrompt = (input: Prompt, ast: AST.AST) =>
1252
+ ParseResult.mapBoth(encodeMessages(input.content), {
1253
+ onFailure: () => new ParseResult.Type(ast, input, `Failed to encode Prompt`),
1254
+ onSuccess: (messages) => ({ content: messages })
1255
+ })
1256
+
1257
+ // export const Prompt: Schema.Schema<Prompt, PromptEncoded> = Schema.Struct({
1258
+ // content: Schema.Array(Message)
1259
+ // }).pipe(
1260
+ // Schema.attachPropertySignature(TypeId, TypeId),
1261
+ // Schema.annotations({ identifier: "Prompt" })
1262
+ // )
1193
1263
 
1194
1264
  /**
1195
1265
  * Schema for parsing a Prompt from JSON strings.
@@ -1231,10 +1301,17 @@ export type RawInput =
1231
1301
  | Iterable<MessageEncoded>
1232
1302
  | Prompt
1233
1303
 
1234
- const makePrompt = (content: ReadonlyArray<Message>): Prompt => ({
1304
+ const Proto = {
1235
1305
  [TypeId]: TypeId,
1236
- content
1237
- })
1306
+ pipe() {
1307
+ return pipeArguments(this, arguments)
1308
+ }
1309
+ }
1310
+
1311
+ const makePrompt = (content: ReadonlyArray<Message>): Prompt =>
1312
+ Object.assign(Object.create(Proto), {
1313
+ content
1314
+ })
1238
1315
 
1239
1316
  const decodeMessagesSync = Schema.decodeSync(Schema.Array(Message))
1240
1317
 
@@ -1289,7 +1366,7 @@ export const make = (input: RawInput): Prompt => {
1289
1366
  }
1290
1367
 
1291
1368
  if (Predicate.isIterable(input)) {
1292
- return makePrompt(decodeMessagesSync(Array.from(input), {
1369
+ return makePrompt(decodeMessagesSync(Arr.fromIterable(input), {
1293
1370
  errors: "all"
1294
1371
  }))
1295
1372
  }
@@ -1395,14 +1472,15 @@ export const fromResponseParts = (parts: ReadonlyArray<Response.AnyPart>): Promp
1395
1472
  return empty
1396
1473
  }
1397
1474
 
1398
- const content: Array<AssistantMessagePart> = []
1475
+ const assistantParts: Array<AssistantMessagePart> = []
1476
+ const toolParts: Array<ToolMessagePart> = []
1399
1477
 
1400
1478
  const textDeltas: Array<string> = []
1401
1479
  function flushTextDeltas() {
1402
1480
  if (textDeltas.length > 0) {
1403
1481
  const text = textDeltas.join("")
1404
1482
  if (text.length > 0) {
1405
- content.push(makePart("text", { text }))
1483
+ assistantParts.push(makePart("text", { text }))
1406
1484
  }
1407
1485
  textDeltas.length = 0
1408
1486
  }
@@ -1413,7 +1491,7 @@ export const fromResponseParts = (parts: ReadonlyArray<Response.AnyPart>): Promp
1413
1491
  if (reasoningDeltas.length > 0) {
1414
1492
  const text = reasoningDeltas.join("")
1415
1493
  if (text.length > 0) {
1416
- content.push(makePart("reasoning", { text }))
1494
+ assistantParts.push(makePart("reasoning", { text }))
1417
1495
  }
1418
1496
  reasoningDeltas.length = 0
1419
1497
  }
@@ -1429,7 +1507,7 @@ export const fromResponseParts = (parts: ReadonlyArray<Response.AnyPart>): Promp
1429
1507
  switch (part.type) {
1430
1508
  case "text": {
1431
1509
  flushDeltas()
1432
- content.push(makePart("text", { text: part.text }))
1510
+ assistantParts.push(makePart("text", { text: part.text }))
1433
1511
  break
1434
1512
  }
1435
1513
  case "text-delta": {
@@ -1439,7 +1517,7 @@ export const fromResponseParts = (parts: ReadonlyArray<Response.AnyPart>): Promp
1439
1517
  }
1440
1518
  case "reasoning": {
1441
1519
  flushDeltas()
1442
- content.push(makePart("reasoning", { text: part.text }))
1520
+ assistantParts.push(makePart("reasoning", { text: part.text }))
1443
1521
  break
1444
1522
  }
1445
1523
  case "reasoning-delta": {
@@ -1449,7 +1527,7 @@ export const fromResponseParts = (parts: ReadonlyArray<Response.AnyPart>): Promp
1449
1527
  }
1450
1528
  case "tool-call": {
1451
1529
  flushDeltas()
1452
- content.push(makePart("tool-call", {
1530
+ assistantParts.push(makePart("tool-call", {
1453
1531
  id: part.id,
1454
1532
  name: part.providerName ?? part.name,
1455
1533
  params: part.params,
@@ -1459,7 +1537,7 @@ export const fromResponseParts = (parts: ReadonlyArray<Response.AnyPart>): Promp
1459
1537
  }
1460
1538
  case "tool-result": {
1461
1539
  flushDeltas()
1462
- content.push(makePart("tool-result", {
1540
+ toolParts.push(makePart("tool-result", {
1463
1541
  id: part.id,
1464
1542
  name: part.providerName ?? part.name,
1465
1543
  result: part.encodedResult
@@ -1472,9 +1550,18 @@ export const fromResponseParts = (parts: ReadonlyArray<Response.AnyPart>): Promp
1472
1550
 
1473
1551
  flushDeltas()
1474
1552
 
1475
- const message = makeMessage("assistant", { content })
1553
+ if (assistantParts.length === 0 && toolParts.length === 0) {
1554
+ return empty
1555
+ }
1476
1556
 
1477
- return makePrompt([message])
1557
+ const messages: Array<Message> = []
1558
+ if (assistantParts.length > 0) {
1559
+ messages.push(makeMessage("assistant", { content: assistantParts }))
1560
+ }
1561
+ if (toolParts.length > 0) {
1562
+ messages.push(makeMessage("tool", { content: toolParts }))
1563
+ }
1564
+ return makePrompt(messages)
1478
1565
  }
1479
1566
 
1480
1567
  // =============================================================================
@@ -1482,10 +1569,10 @@ export const fromResponseParts = (parts: ReadonlyArray<Response.AnyPart>): Promp
1482
1569
  // =============================================================================
1483
1570
 
1484
1571
  /**
1485
- * Merges two prompts by concatenating their messages.
1572
+ * Merges a prompt with additional raw input by concatenating messages.
1486
1573
  *
1487
- * Creates a new prompt containing all messages from both prompts, maintaining
1488
- * the order of messages within each prompt.
1574
+ * Creates a new prompt containing all messages from both the original prompt,
1575
+ * and the provided raw input, maintaining the order of messages.
1489
1576
  *
1490
1577
  * @example
1491
1578
  * ```ts
@@ -1496,9 +1583,7 @@ export const fromResponseParts = (parts: ReadonlyArray<Response.AnyPart>): Promp
1496
1583
  * content: "You are a helpful assistant."
1497
1584
  * }])
1498
1585
  *
1499
- * const userPrompt = Prompt.make("Hello, world!")
1500
- *
1501
- * const merged = Prompt.merge(systemPrompt, userPrompt)
1586
+ * const merged = Prompt.merge(systemPrompt, "Hello, world!")
1502
1587
  * ```
1503
1588
  *
1504
1589
  * @since 1.0.0
@@ -1510,10 +1595,10 @@ export const merge: {
1510
1595
  // =============================================================================
1511
1596
 
1512
1597
  /**
1513
- * Merges two prompts by concatenating their messages.
1598
+ * Merges a prompt with additional raw input by concatenating messages.
1514
1599
  *
1515
- * Creates a new prompt containing all messages from both prompts, maintaining
1516
- * the order of messages within each prompt.
1600
+ * Creates a new prompt containing all messages from both the original prompt,
1601
+ * and the provided raw input, maintaining the order of messages.
1517
1602
  *
1518
1603
  * @example
1519
1604
  * ```ts
@@ -1524,24 +1609,22 @@ export const merge: {
1524
1609
  * content: "You are a helpful assistant."
1525
1610
  * }])
1526
1611
  *
1527
- * const userPrompt = Prompt.make("Hello, world!")
1528
- *
1529
- * const merged = Prompt.merge(systemPrompt, userPrompt)
1612
+ * const merged = Prompt.merge(systemPrompt, "Hello, world!")
1530
1613
  * ```
1531
1614
  *
1532
1615
  * @since 1.0.0
1533
1616
  * @category Combinators
1534
1617
  */
1535
- (other: Prompt): (self: Prompt) => Prompt
1618
+ (input: RawInput): (self: Prompt) => Prompt
1536
1619
  // =============================================================================
1537
1620
  // Merging Prompts
1538
1621
  // =============================================================================
1539
1622
 
1540
1623
  /**
1541
- * Merges two prompts by concatenating their messages.
1624
+ * Merges a prompt with additional raw input by concatenating messages.
1542
1625
  *
1543
- * Creates a new prompt containing all messages from both prompts, maintaining
1544
- * the order of messages within each prompt.
1626
+ * Creates a new prompt containing all messages from both the original prompt,
1627
+ * and the provided raw input, maintaining the order of messages.
1545
1628
  *
1546
1629
  * @example
1547
1630
  * ```ts
@@ -1552,25 +1635,68 @@ export const merge: {
1552
1635
  * content: "You are a helpful assistant."
1553
1636
  * }])
1554
1637
  *
1555
- * const userPrompt = Prompt.make("Hello, world!")
1556
- *
1557
- * const merged = Prompt.merge(systemPrompt, userPrompt)
1638
+ * const merged = Prompt.merge(systemPrompt, "Hello, world!")
1558
1639
  * ```
1559
1640
  *
1560
1641
  * @since 1.0.0
1561
1642
  * @category Combinators
1562
1643
  */
1563
- (self: Prompt, other: Prompt): Prompt
1564
- } = dual<
1644
+ (self: Prompt, input: RawInput): Prompt
1645
+ } = dual(2, (self: Prompt, input: RawInput): Prompt => {
1646
+ const other = make(input)
1647
+ if (self.content.length === 0) {
1648
+ return other
1649
+ }
1650
+ if (other.content.length === 0) {
1651
+ return self
1652
+ }
1653
+ return fromMessages([...self.content, ...other.content])
1654
+ })
1655
+
1656
+ // =============================================================================
1657
+ // Manipulating Prompts
1658
+ // =============================================================================
1659
+
1660
+ /**
1661
+ * Creates a new prompt from the specified prompt with the system message set
1662
+ * to the specified text content.
1663
+ *
1664
+ * **NOTE**: This method will remove and replace any previous system message
1665
+ * from the prompt.
1666
+ *
1667
+ * @example
1668
+ * ```ts
1669
+ * import { Prompt } from "@effect/ai"
1670
+ *
1671
+ * const systemPrompt = Prompt.make([{
1672
+ * role: "system",
1673
+ * content: "You are a helpful assistant."
1674
+ * }])
1675
+ *
1676
+ * const userPrompt = Prompt.make("Hello, world!")
1677
+ *
1678
+ * const prompt = Prompt.merge(systemPrompt, userPrompt)
1679
+ *
1680
+ * const replaced = Prompt.setSystem(
1681
+ * prompt,
1682
+ * "You are an expert in programming"
1683
+ * )
1684
+ * ```
1685
+ *
1686
+ * @since 1.0.0
1687
+ * @category Combinators
1688
+ */
1689
+ export const setSystem: {
1565
1690
  // =============================================================================
1566
- // Merging Prompts
1691
+ // Manipulating Prompts
1567
1692
  // =============================================================================
1568
1693
 
1569
1694
  /**
1570
- * Merges two prompts by concatenating their messages.
1695
+ * Creates a new prompt from the specified prompt with the system message set
1696
+ * to the specified text content.
1571
1697
  *
1572
- * Creates a new prompt containing all messages from both prompts, maintaining
1573
- * the order of messages within each prompt.
1698
+ * **NOTE**: This method will remove and replace any previous system message
1699
+ * from the prompt.
1574
1700
  *
1575
1701
  * @example
1576
1702
  * ```ts
@@ -1583,22 +1709,203 @@ export const merge: {
1583
1709
  *
1584
1710
  * const userPrompt = Prompt.make("Hello, world!")
1585
1711
  *
1586
- * const merged = Prompt.merge(systemPrompt, userPrompt)
1712
+ * const prompt = Prompt.merge(systemPrompt, userPrompt)
1713
+ *
1714
+ * const replaced = Prompt.setSystem(
1715
+ * prompt,
1716
+ * "You are an expert in programming"
1717
+ * )
1587
1718
  * ```
1588
1719
  *
1589
1720
  * @since 1.0.0
1590
1721
  * @category Combinators
1591
1722
  */
1592
- (other: Prompt) => (self: Prompt) => Prompt,
1723
+ (content: string): (self: Prompt) => Prompt
1593
1724
  // =============================================================================
1594
- // Merging Prompts
1725
+ // Manipulating Prompts
1595
1726
  // =============================================================================
1596
1727
 
1597
1728
  /**
1598
- * Merges two prompts by concatenating their messages.
1729
+ * Creates a new prompt from the specified prompt with the system message set
1730
+ * to the specified text content.
1731
+ *
1732
+ * **NOTE**: This method will remove and replace any previous system message
1733
+ * from the prompt.
1734
+ *
1735
+ * @example
1736
+ * ```ts
1737
+ * import { Prompt } from "@effect/ai"
1738
+ *
1739
+ * const systemPrompt = Prompt.make([{
1740
+ * role: "system",
1741
+ * content: "You are a helpful assistant."
1742
+ * }])
1743
+ *
1744
+ * const userPrompt = Prompt.make("Hello, world!")
1745
+ *
1746
+ * const prompt = Prompt.merge(systemPrompt, userPrompt)
1747
+ *
1748
+ * const replaced = Prompt.setSystem(
1749
+ * prompt,
1750
+ * "You are an expert in programming"
1751
+ * )
1752
+ * ```
1753
+ *
1754
+ * @since 1.0.0
1755
+ * @category Combinators
1756
+ */
1757
+ (self: Prompt, content: string): Prompt
1758
+ } = dual(2, (self: Prompt, content: string): Prompt => {
1759
+ const messages: Array<Message> = [makeMessage("system", { content })]
1760
+ for (const message of self.content) {
1761
+ if (message.role !== "system") {
1762
+ messages.push(message)
1763
+ }
1764
+ }
1765
+ return makePrompt(messages)
1766
+ })
1767
+
1768
+ /**
1769
+ * Creates a new prompt from the specified prompt with the provided text content
1770
+ * prepended to the start of existing system message content.
1771
+ *
1772
+ * If no system message exists in the specified prompt, the provided content
1773
+ * will be used to create a system message.
1774
+ *
1775
+ * @example
1776
+ * ```ts
1777
+ * import { Prompt } from "@effect/ai"
1778
+ *
1779
+ * const systemPrompt = Prompt.make([{
1780
+ * role: "system",
1781
+ * content: "You are an expert in programming."
1782
+ * }])
1783
+ *
1784
+ * const userPrompt = Prompt.make("Hello, world!")
1785
+ *
1786
+ * const prompt = Prompt.merge(systemPrompt, userPrompt)
1787
+ *
1788
+ * const replaced = Prompt.prependSystem(
1789
+ * prompt,
1790
+ * "You are a helpful assistant. "
1791
+ * )
1792
+ * ```
1793
+ *
1794
+ * @since 1.0.0
1795
+ * @category Combinators
1796
+ */
1797
+ export const prependSystem: {
1798
+ /**
1799
+ * Creates a new prompt from the specified prompt with the provided text content
1800
+ * prepended to the start of existing system message content.
1801
+ *
1802
+ * If no system message exists in the specified prompt, the provided content
1803
+ * will be used to create a system message.
1804
+ *
1805
+ * @example
1806
+ * ```ts
1807
+ * import { Prompt } from "@effect/ai"
1808
+ *
1809
+ * const systemPrompt = Prompt.make([{
1810
+ * role: "system",
1811
+ * content: "You are an expert in programming."
1812
+ * }])
1813
+ *
1814
+ * const userPrompt = Prompt.make("Hello, world!")
1815
+ *
1816
+ * const prompt = Prompt.merge(systemPrompt, userPrompt)
1817
+ *
1818
+ * const replaced = Prompt.prependSystem(
1819
+ * prompt,
1820
+ * "You are a helpful assistant. "
1821
+ * )
1822
+ * ```
1823
+ *
1824
+ * @since 1.0.0
1825
+ * @category Combinators
1826
+ */
1827
+ (content: string): (self: Prompt) => Prompt
1828
+ /**
1829
+ * Creates a new prompt from the specified prompt with the provided text content
1830
+ * prepended to the start of existing system message content.
1831
+ *
1832
+ * If no system message exists in the specified prompt, the provided content
1833
+ * will be used to create a system message.
1834
+ *
1835
+ * @example
1836
+ * ```ts
1837
+ * import { Prompt } from "@effect/ai"
1838
+ *
1839
+ * const systemPrompt = Prompt.make([{
1840
+ * role: "system",
1841
+ * content: "You are an expert in programming."
1842
+ * }])
1843
+ *
1844
+ * const userPrompt = Prompt.make("Hello, world!")
1845
+ *
1846
+ * const prompt = Prompt.merge(systemPrompt, userPrompt)
1847
+ *
1848
+ * const replaced = Prompt.prependSystem(
1849
+ * prompt,
1850
+ * "You are a helpful assistant. "
1851
+ * )
1852
+ * ```
1853
+ *
1854
+ * @since 1.0.0
1855
+ * @category Combinators
1856
+ */
1857
+ (self: Prompt, content: string): Prompt
1858
+ } = dual(2, (self: Prompt, content: string): Prompt => {
1859
+ const messages: Array<Message> = []
1860
+ for (const message of self.content) {
1861
+ if (message.role === "system") {
1862
+ const system = makeMessage("system", {
1863
+ content: content + message.content
1864
+ })
1865
+ messages.push(system)
1866
+ } else {
1867
+ messages.push(message)
1868
+ }
1869
+ }
1870
+ return makePrompt(messages)
1871
+ })
1872
+
1873
+ /**
1874
+ * Creates a new prompt from the specified prompt with the provided text content
1875
+ * appended to the end of existing system message content.
1876
+ *
1877
+ * If no system message exists in the specified prompt, the provided content
1878
+ * will be used to create a system message.
1879
+ *
1880
+ * @example
1881
+ * ```ts
1882
+ * import { Prompt } from "@effect/ai"
1883
+ *
1884
+ * const systemPrompt = Prompt.make([{
1885
+ * role: "system",
1886
+ * content: "You are a helpful assistant."
1887
+ * }])
1888
+ *
1889
+ * const userPrompt = Prompt.make("Hello, world!")
1890
+ *
1891
+ * const prompt = Prompt.merge(systemPrompt, userPrompt)
1892
+ *
1893
+ * const replaced = Prompt.appendSystem(
1894
+ * prompt,
1895
+ * " You are an expert in programming."
1896
+ * )
1897
+ * ```
1898
+ *
1899
+ * @since 1.0.0
1900
+ * @category Combinators
1901
+ */
1902
+ export const appendSystem: {
1903
+ /**
1904
+ * Creates a new prompt from the specified prompt with the provided text content
1905
+ * appended to the end of existing system message content.
1599
1906
  *
1600
- * Creates a new prompt containing all messages from both prompts, maintaining
1601
- * the order of messages within each prompt.
1907
+ * If no system message exists in the specified prompt, the provided content
1908
+ * will be used to create a system message.
1602
1909
  *
1603
1910
  * @example
1604
1911
  * ```ts
@@ -1611,11 +1918,59 @@ export const merge: {
1611
1918
  *
1612
1919
  * const userPrompt = Prompt.make("Hello, world!")
1613
1920
  *
1614
- * const merged = Prompt.merge(systemPrompt, userPrompt)
1921
+ * const prompt = Prompt.merge(systemPrompt, userPrompt)
1922
+ *
1923
+ * const replaced = Prompt.appendSystem(
1924
+ * prompt,
1925
+ * " You are an expert in programming."
1926
+ * )
1927
+ * ```
1928
+ *
1929
+ * @since 1.0.0
1930
+ * @category Combinators
1931
+ */
1932
+ (content: string): (self: Prompt) => Prompt
1933
+ /**
1934
+ * Creates a new prompt from the specified prompt with the provided text content
1935
+ * appended to the end of existing system message content.
1936
+ *
1937
+ * If no system message exists in the specified prompt, the provided content
1938
+ * will be used to create a system message.
1939
+ *
1940
+ * @example
1941
+ * ```ts
1942
+ * import { Prompt } from "@effect/ai"
1943
+ *
1944
+ * const systemPrompt = Prompt.make([{
1945
+ * role: "system",
1946
+ * content: "You are a helpful assistant."
1947
+ * }])
1948
+ *
1949
+ * const userPrompt = Prompt.make("Hello, world!")
1950
+ *
1951
+ * const prompt = Prompt.merge(systemPrompt, userPrompt)
1952
+ *
1953
+ * const replaced = Prompt.appendSystem(
1954
+ * prompt,
1955
+ * " You are an expert in programming."
1956
+ * )
1615
1957
  * ```
1616
1958
  *
1617
1959
  * @since 1.0.0
1618
1960
  * @category Combinators
1619
1961
  */
1620
- (self: Prompt, other: Prompt) => Prompt
1621
- >(2, (self, other) => fromMessages([...self.content, ...other.content]))
1962
+ (self: Prompt, content: string): Prompt
1963
+ } = dual(2, (self: Prompt, content: string): Prompt => {
1964
+ const messages: Array<Message> = []
1965
+ for (const message of self.content) {
1966
+ if (message.role === "system") {
1967
+ const system = makeMessage("system", {
1968
+ content: message.content + content
1969
+ })
1970
+ messages.push(system)
1971
+ } else {
1972
+ messages.push(message)
1973
+ }
1974
+ }
1975
+ return makePrompt(messages)
1976
+ })