opencode-effect-enforcer 0.2.0

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.
Files changed (118) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +278 -0
  3. package/guidance/effect-first-development.md +1247 -0
  4. package/guidance/post__effect-and-the-near-inexpressible-majesty-of-layers.md +490 -0
  5. package/guidance/post__parse-dont-validate.md +109 -0
  6. package/guidance/progressive-disclosure-guidance.md +38 -0
  7. package/package.json +63 -0
  8. package/patterns/avoid-any.md +37 -0
  9. package/patterns/avoid-data-tagged-error.md +34 -0
  10. package/patterns/avoid-direct-json.md +51 -0
  11. package/patterns/avoid-direct-tag-checks.md +54 -0
  12. package/patterns/avoid-expect-in-if.md +52 -0
  13. package/patterns/avoid-mutable-state.md +70 -0
  14. package/patterns/avoid-native-fetch.md +61 -0
  15. package/patterns/avoid-node-imports.md +86 -0
  16. package/patterns/avoid-non-null-assertion.md +44 -0
  17. package/patterns/avoid-object-type.md +46 -0
  18. package/patterns/avoid-option-getorthrow.md +39 -0
  19. package/patterns/avoid-platform-coupling.md +43 -0
  20. package/patterns/avoid-process-env.md +43 -0
  21. package/patterns/avoid-react-hooks.md +73 -0
  22. package/patterns/avoid-schema-suffix.md +45 -0
  23. package/patterns/avoid-sync-fs.md +68 -0
  24. package/patterns/avoid-try-catch.md +47 -0
  25. package/patterns/avoid-ts-ignore.md +38 -0
  26. package/patterns/avoid-untagged-errors.md +67 -0
  27. package/patterns/avoid-yield-ref.md +46 -0
  28. package/patterns/casting-awareness.md +46 -0
  29. package/patterns/context-tag-extends.md +84 -0
  30. package/patterns/effect-catchall-default.md +61 -0
  31. package/patterns/effect-promise-vs-trypromise.md +47 -0
  32. package/patterns/effect-run-in-body.md +58 -0
  33. package/patterns/imperative-loops.md +76 -0
  34. package/patterns/prefer-arr-sort.md +52 -0
  35. package/patterns/prefer-duration-values.md +56 -0
  36. package/patterns/prefer-effect-fn.md +161 -0
  37. package/patterns/prefer-match-over-switch.md +48 -0
  38. package/patterns/prefer-option-over-null.md +56 -0
  39. package/patterns/prefer-redacted-config.md +70 -0
  40. package/patterns/prefer-schema-class.md +54 -0
  41. package/patterns/require-effect-concurrency.md +83 -0
  42. package/patterns/stream-large-files.md +63 -0
  43. package/patterns/throw-in-effect-gen.md +62 -0
  44. package/patterns/use-clock-service.md +45 -0
  45. package/patterns/use-command-executor-service.md +54 -0
  46. package/patterns/use-console-service.md +54 -0
  47. package/patterns/use-filesystem-service.md +59 -0
  48. package/patterns/use-http-client-service.md +77 -0
  49. package/patterns/use-path-service.md +53 -0
  50. package/patterns/use-random-service.md +45 -0
  51. package/patterns/use-temp-file-scoped.md +66 -0
  52. package/patterns/vm-in-wrong-file.md +51 -0
  53. package/patterns/yield-in-for-loop.md +61 -0
  54. package/skills/effect-ai-chat/SKILL.md +472 -0
  55. package/skills/effect-ai-language-model/SKILL.md +652 -0
  56. package/skills/effect-ai-prompt/SKILL.md +752 -0
  57. package/skills/effect-ai-provider/SKILL.md +668 -0
  58. package/skills/effect-ai-streaming/SKILL.md +418 -0
  59. package/skills/effect-ai-tool/SKILL.md +1132 -0
  60. package/skills/effect-atom-rpc/SKILL.md +488 -0
  61. package/skills/effect-atom-state/SKILL.md +640 -0
  62. package/skills/effect-batching/SKILL.md +614 -0
  63. package/skills/effect-cache/SKILL.md +570 -0
  64. package/skills/effect-cli/SKILL.md +523 -0
  65. package/skills/effect-command-executor/SKILL.md +675 -0
  66. package/skills/effect-concurrency-testing/SKILL.md +612 -0
  67. package/skills/effect-config/SKILL.md +580 -0
  68. package/skills/effect-context-witness/SKILL.md +274 -0
  69. package/skills/effect-domain-modeling/SKILL.md +1212 -0
  70. package/skills/effect-domain-predicates/SKILL.md +867 -0
  71. package/skills/effect-error-handling/SKILL.md +1581 -0
  72. package/skills/effect-fiber/SKILL.md +731 -0
  73. package/skills/effect-filesystem/SKILL.md +624 -0
  74. package/skills/effect-graph/SKILL.md +571 -0
  75. package/skills/effect-http-api/SKILL.md +1760 -0
  76. package/skills/effect-http-client/SKILL.md +989 -0
  77. package/skills/effect-http-server/SKILL.md +920 -0
  78. package/skills/effect-incremental-migration/SKILL.md +362 -0
  79. package/skills/effect-layer-design/SKILL.md +642 -0
  80. package/skills/effect-managed-runtime/SKILL.md +395 -0
  81. package/skills/effect-mcp-server/SKILL.md +608 -0
  82. package/skills/effect-observability/SKILL.md +719 -0
  83. package/skills/effect-optics/SKILL.md +554 -0
  84. package/skills/effect-parallelization/SKILL.md +668 -0
  85. package/skills/effect-path/SKILL.md +296 -0
  86. package/skills/effect-pattern-matching/SKILL.md +914 -0
  87. package/skills/effect-platform-abstraction/SKILL.md +1175 -0
  88. package/skills/effect-platform-layers/SKILL.md +514 -0
  89. package/skills/effect-pubsub-event-bus/SKILL.md +384 -0
  90. package/skills/effect-react-composition/SKILL.md +986 -0
  91. package/skills/effect-react-vm/SKILL.md +675 -0
  92. package/skills/effect-rpc-api/SKILL.md +624 -0
  93. package/skills/effect-rpc-client/SKILL.md +666 -0
  94. package/skills/effect-rpc-cluster/SKILL.md +1623 -0
  95. package/skills/effect-rpc-server/SKILL.md +767 -0
  96. package/skills/effect-scheduling/SKILL.md +124 -0
  97. package/skills/effect-schema-composition/SKILL.md +975 -0
  98. package/skills/effect-schema-v4/SKILL.md +691 -0
  99. package/skills/effect-scope/SKILL.md +682 -0
  100. package/skills/effect-service-implementation/SKILL.md +656 -0
  101. package/skills/effect-socket/SKILL.md +703 -0
  102. package/skills/effect-sql/SKILL.md +781 -0
  103. package/skills/effect-stream/SKILL.md +765 -0
  104. package/skills/effect-testing/SKILL.md +1331 -0
  105. package/skills/effect-typeclass-design/SKILL.md +161 -0
  106. package/skills/effect-wide-events/Article.md +66 -0
  107. package/skills/effect-wide-events/SKILL.md +95 -0
  108. package/skills/effect-workflow/SKILL.md +810 -0
  109. package/src/agent-policy.ts +22 -0
  110. package/src/enforcer.ts +104 -0
  111. package/src/frontmatter.ts +34 -0
  112. package/src/guidance.ts +66 -0
  113. package/src/index.ts +38 -0
  114. package/src/pattern-catalog.ts +115 -0
  115. package/src/pattern-matcher.ts +178 -0
  116. package/src/pattern.ts +97 -0
  117. package/src/skills.ts +29 -0
  118. package/src/write-projection.ts +66 -0
@@ -0,0 +1,752 @@
1
+ ---
2
+ name: effect-ai-prompt
3
+ description: Build prompts for Effect AI using messages, parts, and composition operators. Covers the complete Prompt API for constructing, merging, and manipulating conversations with language models.
4
+ ---
5
+
6
+ # Effect AI Prompt Construction
7
+
8
+ Master the Effect AI Prompt API for building type-safe conversations with language models.
9
+
10
+ ## Import Patterns
11
+
12
+ **CRITICAL**: Always use namespace imports:
13
+
14
+ ```typescript
15
+ import * as Prompt from 'effect/unstable/ai/Prompt';
16
+ import * as Response from 'effect/unstable/ai/Response';
17
+ import { pipe } from 'effect';
18
+ ```
19
+
20
+ ## When to Use This Skill
21
+
22
+ - Constructing messages for language model requests
23
+ - Building multi-turn conversation history
24
+ - Adding system instructions to prompts
25
+ - Integrating tool calls and results into conversations
26
+ - Converting streaming responses to prompt history
27
+ - Managing file/image attachments in messages
28
+ - Implementing custom chat interfaces
29
+
30
+ ## Conceptual Model
31
+
32
+ ```haskell
33
+ -- Message hierarchy
34
+ type Message = SystemMessage | UserMessage | AssistantMessage | ToolMessage
35
+ type Part =
36
+ | TextPart
37
+ | ReasoningPart
38
+ | FilePart
39
+ | ToolCallPart
40
+ | ToolResultPart
41
+ | ToolApprovalRequestPart
42
+ | ToolApprovalResponsePart
43
+
44
+ -- Composition
45
+ Prompt.make :: RawInput → Prompt
46
+ Prompt.concat :: (Prompt, RawInput) → Prompt
47
+ Prompt.setSystem :: (Prompt, String) → Prompt
48
+
49
+ -- History transformation
50
+ fromResponseParts :: ReadonlyArray<Response.Part> → Prompt
51
+ ```
52
+
53
+ The Effect v4 module also exports runtime schemas for every part and each role-specific part union: `TextPart`, `ReasoningPart`, `FilePart`, `ToolCallPart`, `ToolResultPart`, `ToolApprovalRequestPart`, `ToolApprovalResponsePart`, `UserMessagePart`, `AssistantMessagePart`, and `ToolMessagePart`. Use these schemas to decode unknown persisted or provider-adapter input instead of relying only on `isPart`.
54
+
55
+ ## Message Types
56
+
57
+ Each message has `role` and `content`. Content is an array of `Part` objects.
58
+
59
+ ### System Messages
60
+
61
+ ```typescript
62
+ import * as Prompt from 'effect/unstable/ai/Prompt';
63
+
64
+ // String content only
65
+ const system = Prompt.makeMessage('system', {
66
+ content: 'You are a helpful assistant specialized in mathematics.'
67
+ });
68
+
69
+ // Shorthand constructor
70
+ const systemShorthand = Prompt.systemMessage({
71
+ content: 'You are a helpful assistant specialized in mathematics.'
72
+ });
73
+
74
+ // System message with options
75
+ const systemWithOptions = Prompt.makeMessage('system', {
76
+ content: 'You are an expert coder.',
77
+ options: {
78
+ anthropic: { cache_control: { type: 'ephemeral' } }
79
+ }
80
+ });
81
+ ```
82
+
83
+ ### User Messages
84
+
85
+ ```typescript
86
+ // Text-only user message
87
+ const userText = Prompt.makeMessage("user", {
88
+ content: [
89
+ Prompt.makePart("text", { text: "What is 2+2?" })
90
+ ]
91
+ })
92
+
93
+ // Shorthand constructor
94
+ const userShorthand = Prompt.userMessage({
95
+ content: [
96
+ Prompt.makePart("text", { text: "What is 2+2?" })
97
+ ]
98
+ })
99
+
100
+ // Multimodal user message (text + file)
101
+ const userMultimodal = Prompt.makeMessage("user", {
102
+ content: [
103
+ Prompt.makePart("text", { text: "What's in this image?" }),
104
+ Prompt.makePart("file", {
105
+ mediaType: "image/jpeg",
106
+ fileName: "photo.jpg",
107
+ data: new Uint8Array([...])
108
+ })
109
+ ]
110
+ })
111
+ ```
112
+
113
+ ### Assistant Messages
114
+
115
+ ```typescript
116
+ // Assistant response with text and tool call
117
+ const assistant = Prompt.makeMessage('assistant', {
118
+ content: [
119
+ Prompt.makePart('reasoning', {
120
+ text: 'I need to check the weather using the tool.'
121
+ }),
122
+ Prompt.makePart('tool-call', {
123
+ id: 'call_123',
124
+ name: 'get_weather',
125
+ params: { city: 'Paris' },
126
+ providerExecuted: false
127
+ }),
128
+ Prompt.makePart('tool-approval-request', {
129
+ approvalId: 'approval_123',
130
+ toolCallId: 'call_123'
131
+ }),
132
+ Prompt.makePart('text', {
133
+ text: 'The weather in Paris is sunny, 22°C.'
134
+ })
135
+ ]
136
+ });
137
+
138
+ // Shorthand constructor
139
+ const assistantShorthand = Prompt.assistantMessage({
140
+ content: [
141
+ Prompt.makePart('text', {
142
+ text: 'The weather in Paris is sunny, 22°C.'
143
+ })
144
+ ]
145
+ });
146
+ ```
147
+
148
+ ### Tool Messages
149
+
150
+ ```typescript
151
+ // Tool execution results
152
+ const toolMessage = Prompt.makeMessage('tool', {
153
+ content: [
154
+ Prompt.makePart('tool-result', {
155
+ id: 'call_123',
156
+ name: 'get_weather',
157
+ isFailure: false,
158
+ result: { temperature: 22, condition: 'sunny' }
159
+ })
160
+ ]
161
+ });
162
+
163
+ // Shorthand constructor
164
+ const toolShorthand = Prompt.toolMessage({
165
+ content: [
166
+ Prompt.makePart('tool-result', {
167
+ id: 'call_123',
168
+ name: 'get_weather',
169
+ isFailure: false,
170
+ result: { temperature: 22, condition: 'sunny' }
171
+ })
172
+ ]
173
+ });
174
+ ```
175
+
176
+ ## Part Types
177
+
178
+ ### Text Part
179
+
180
+ ```typescript
181
+ const textPart = Prompt.makePart('text', {
182
+ text: 'Hello, world!'
183
+ });
184
+ ```
185
+
186
+ ### Reasoning Part
187
+
188
+ ```typescript
189
+ const reasoningPart = Prompt.makePart('reasoning', {
190
+ text: 'Let me think step by step...'
191
+ });
192
+ ```
193
+
194
+ ### File Part
195
+
196
+ ```typescript
197
+ // From URL
198
+ const fileFromUrl = Prompt.makePart('file', {
199
+ mediaType: 'image/png',
200
+ fileName: 'screenshot.png',
201
+ data: new URL('https://example.com/image.png')
202
+ });
203
+
204
+ // From bytes
205
+ const fileFromBytes = Prompt.makePart('file', {
206
+ mediaType: 'application/pdf',
207
+ fileName: 'report.pdf',
208
+ data: new Uint8Array([1, 2, 3])
209
+ });
210
+
211
+ // From base64
212
+ const fileFromBase64 = Prompt.makePart('file', {
213
+ mediaType: 'image/jpeg',
214
+ data: 'data:image/jpeg;base64,/9j/4AAQ...'
215
+ });
216
+ ```
217
+
218
+ For OpenAI file parts, strings are provider file IDs only when `OpenAiLanguageModel.Config.fileIdPrefixes` matches the string prefix (for example `file-`). Use `URL` or `Uint8Array` for inline content; the OpenAI adapter encodes bytes as base64 data.
219
+
220
+ ### Tool Call Part
221
+
222
+ ```typescript
223
+ const toolCall = Prompt.makePart('tool-call', {
224
+ id: 'call_abc123',
225
+ name: 'calculate',
226
+ params: { expression: '2 + 2' },
227
+ providerExecuted: false
228
+ });
229
+ ```
230
+
231
+ ### Tool Result Part
232
+
233
+ ```typescript
234
+ const toolResult = Prompt.makePart('tool-result', {
235
+ id: 'call_abc123',
236
+ name: 'calculate',
237
+ isFailure: false,
238
+ result: 4
239
+ });
240
+ ```
241
+
242
+ ### Tool Approval Parts
243
+
244
+ ```typescript
245
+ const approvalRequest = Prompt.makePart('tool-approval-request', {
246
+ approvalId: 'approval_abc123',
247
+ toolCallId: 'call_abc123'
248
+ });
249
+
250
+ const approvalResponse = Prompt.toolApprovalResponsePart({
251
+ approvalId: 'approval_abc123',
252
+ approved: true
253
+ });
254
+
255
+ const denialResponse = Prompt.toolApprovalResponsePart({
256
+ approvalId: 'approval_def456',
257
+ approved: false,
258
+ reason: 'Operation not allowed'
259
+ });
260
+ ```
261
+
262
+ Tool approval requests live in assistant messages. Approval responses live in tool messages and are collected on the next model call.
263
+
264
+ ## Prompt Construction
265
+
266
+ ### From String
267
+
268
+ ```typescript
269
+ // Creates a user message with text part
270
+ const prompt = Prompt.make('Hello, how are you?');
271
+ ```
272
+
273
+ ### From Encoded Messages
274
+
275
+ ```typescript
276
+ const prompt = Prompt.make([
277
+ { role: 'system', content: 'You are helpful.' },
278
+ { role: 'user', content: [{ type: 'text', text: 'Hi!' }] }
279
+ ]);
280
+ ```
281
+
282
+ `Prompt.RawInput` is exactly `string | Iterable<Prompt.MessageEncoded> | Prompt.Prompt`: strings become one user text message, iterables are decoded encoded messages, and existing prompts pass through unchanged.
283
+
284
+ ### From Existing Prompt
285
+
286
+ ```typescript
287
+ const copy = Prompt.make(existingPrompt);
288
+ ```
289
+
290
+ ### Empty Prompt
291
+
292
+ ```typescript
293
+ const empty = Prompt.empty;
294
+ ```
295
+
296
+ ### From Messages Array
297
+
298
+ ```typescript
299
+ // Using fromMessages constructor
300
+ const messages: ReadonlyArray<Prompt.Message> = [
301
+ Prompt.systemMessage({ content: 'You are an expert.' }),
302
+ Prompt.userMessage({
303
+ content: [Prompt.makePart('text', { text: 'Help me.' })]
304
+ })
305
+ ];
306
+
307
+ const prompt = Prompt.fromMessages(messages);
308
+
309
+ // Alternative: Using make with messages array
310
+ const prompt2 = Prompt.make([
311
+ Prompt.makeMessage('system', { content: 'You are an expert.' }),
312
+ Prompt.makeMessage('user', {
313
+ content: [Prompt.makePart('text', { text: 'Help me.' })]
314
+ })
315
+ ]);
316
+ ```
317
+
318
+ ## Prompt Composition
319
+
320
+ ### Merge Prompts
321
+
322
+ ```typescript
323
+ import { pipe } from 'effect';
324
+
325
+ const systemPrompt = Prompt.make([
326
+ {
327
+ role: 'system',
328
+ content: 'You are a coding assistant.'
329
+ }
330
+ ]);
331
+
332
+ const userPrompt = Prompt.make('Write a function');
333
+
334
+ // Data-last (pipeline)
335
+ const combined = pipe(systemPrompt, Prompt.concat(userPrompt));
336
+
337
+ // Data-first
338
+ const combined2 = Prompt.concat(systemPrompt, userPrompt);
339
+ ```
340
+
341
+ ### System Message Manipulation
342
+
343
+ ```typescript
344
+ import { pipe } from 'effect';
345
+
346
+ const prompt = Prompt.make([
347
+ { role: 'system', content: 'You are helpful.' },
348
+ { role: 'user', content: [{ type: 'text', text: 'Hi' }] }
349
+ ]);
350
+
351
+ // Replace system message
352
+ const replaced = pipe(
353
+ prompt,
354
+ Prompt.setSystem('You are an expert in TypeScript.')
355
+ );
356
+
357
+ // Prepend to system message
358
+ const prepended = pipe(prompt, Prompt.prependSystem('IMPORTANT: '));
359
+ // Result: "IMPORTANT: You are helpful."
360
+
361
+ // Append to system message
362
+ const appended = pipe(prompt, Prompt.appendSystem(' Be concise.'));
363
+ // Result: "You are helpful. Be concise."
364
+ ```
365
+
366
+ ## History Management
367
+
368
+ ### Convert AI Response to Prompt
369
+
370
+ ```typescript
371
+ import * as Response from 'effect/unstable/ai/Response';
372
+
373
+ const responseParts: ReadonlyArray<Response.AnyPart> = [
374
+ Response.makePart('text-start', { id: 'text_1' }),
375
+ Response.makePart('text-delta', { id: 'text_1', delta: 'Hello' }),
376
+ Response.makePart('text-delta', { id: 'text_1', delta: '!' }),
377
+ Response.makePart('text-end', { id: 'text_1' }),
378
+ Response.makePart('tool-call', {
379
+ id: 'call_1',
380
+ name: 'get_time',
381
+ params: {},
382
+ providerExecuted: false
383
+ }),
384
+ Response.makePart('tool-approval-request', {
385
+ approvalId: 'approval_1',
386
+ toolCallId: 'call_1'
387
+ }),
388
+ Response.makePart('tool-result', {
389
+ id: 'call_1',
390
+ name: 'get_time',
391
+ isFailure: false,
392
+ result: '10:30 AM',
393
+ encodedResult: '10:30 AM',
394
+ providerExecuted: false,
395
+ preliminary: false
396
+ })
397
+ ];
398
+
399
+ // Folds complete streaming text/reasoning and splits assistant/tool messages.
400
+ const historyPrompt = Prompt.fromResponseParts(responseParts);
401
+ ```
402
+
403
+ `Prompt.fromResponseParts` folds streaming text/reasoning only when the matching start/delta/end parts are present in the same input, places tool calls and approval requests in assistant messages, and skips preliminary tool results. For final tool results it always uses `encodedResult`: framework-executed results (`providerExecuted: false`) become tool messages, while provider-executed results (`providerExecuted: true`) remain in the assistant message with that flag preserved.
404
+
405
+ This distinction matters for hosted tools such as provider web search or code execution. Moving their results into a tool message changes the conversation shape expected by the provider.
406
+
407
+ ### Effect-Returning Prompt/Message Helpers
408
+
409
+ Keep pure prompt helpers pure. But when prompt or message transformation depends on services, model metadata, truncation policy, storage, or other runtime state, wrap the transformation in `Effect.fn(...)` so orchestrators can `yield*` it directly.
410
+
411
+ ```typescript
412
+ import { Effect } from 'effect';
413
+
414
+ export const toModelMessagesEffect = Effect.fn('Prompt.toModelMessages')(
415
+ function* (messages: ReadonlyArray<AppMessage>) {
416
+ const policy = yield* MessagePolicy.Service;
417
+ return convertMessages(messages, policy);
418
+ }
419
+ );
420
+ ```
421
+
422
+ This keeps prompt assembly inside the Effect graph instead of forcing Promise islands through session orchestration code.
423
+
424
+ ### Typical Chat Pattern
425
+
426
+ ```typescript
427
+ import { Effect } from 'effect';
428
+ import * as SubscriptionRef from 'effect/SubscriptionRef';
429
+ import * as LanguageModel from 'effect/unstable/ai/LanguageModel';
430
+
431
+ const chat = Effect.gen(function* () {
432
+ const history = yield* SubscriptionRef.make(Prompt.empty);
433
+
434
+ function* generateText(userInput: string) {
435
+ const currentHistory = yield* SubscriptionRef.get(history);
436
+ const prompt = pipe(currentHistory, Prompt.concat(userInput));
437
+
438
+ const response = yield* LanguageModel.generateText({ prompt });
439
+
440
+ // Update history with user input + response
441
+ const newHistory = pipe(
442
+ prompt,
443
+ Prompt.concat(Prompt.fromResponseParts(response.content))
444
+ );
445
+ yield* SubscriptionRef.set(history, newHistory);
446
+
447
+ return response;
448
+ }
449
+
450
+ return { generateText };
451
+ });
452
+ ```
453
+
454
+ ## Usage with LanguageModel
455
+
456
+ ### Generate Text
457
+
458
+ ```typescript
459
+ import * as LanguageModel from 'effect/unstable/ai/LanguageModel';
460
+ import { Effect } from 'effect';
461
+
462
+ const program = Effect.gen(function* () {
463
+ const prompt = Prompt.make([
464
+ { role: 'system', content: 'You are helpful.' },
465
+ { role: 'user', content: [{ type: 'text', text: 'Explain Effect' }] }
466
+ ]);
467
+
468
+ const response = yield* LanguageModel.generateText({ prompt });
469
+
470
+ return response.content;
471
+ });
472
+ ```
473
+
474
+ ### Stream Text
475
+
476
+ ```typescript
477
+ import * as LanguageModel from 'effect/unstable/ai/LanguageModel';
478
+ import { Effect, Stream } from 'effect';
479
+
480
+ const program = Effect.gen(function* () {
481
+ const prompt = Prompt.make('Write a story');
482
+
483
+ yield* LanguageModel.streamText({ prompt }).pipe(
484
+ Stream.runForEach((part) =>
485
+ part.type === 'text-delta'
486
+ ? Effect.sync(() => process.stdout.write(part.delta))
487
+ : Effect.void
488
+ )
489
+ );
490
+ });
491
+ ```
492
+
493
+ ### Generate Object
494
+
495
+ ```typescript
496
+ import * as LanguageModel from 'effect/unstable/ai/LanguageModel';
497
+ import { Effect, Schema } from 'effect';
498
+
499
+ const Contact = Schema.Struct({
500
+ name: Schema.String,
501
+ email: Schema.String,
502
+ phone: Schema.optional(Schema.String)
503
+ });
504
+
505
+ const program = Effect.gen(function* () {
506
+ const prompt = Prompt.make('Extract: John Doe, john@example.com, 555-1234');
507
+
508
+ const response = yield* LanguageModel.generateObject({
509
+ prompt,
510
+ schema: Contact
511
+ });
512
+
513
+ return response.value;
514
+ });
515
+ ```
516
+
517
+ ## Provider-Specific Options
518
+
519
+ ```typescript
520
+ // Augment options interfaces via module augmentation
521
+ declare module 'effect/unstable/ai/Prompt' {
522
+ interface TextPartOptions {
523
+ readonly anthropic?: {
524
+ readonly cache_control?: {
525
+ readonly type: 'ephemeral';
526
+ };
527
+ };
528
+ }
529
+ }
530
+
531
+ // Use in parts
532
+ const cachedPart = Prompt.makePart('text', {
533
+ text: 'Large document...',
534
+ options: {
535
+ anthropic: { cache_control: { type: 'ephemeral' } }
536
+ }
537
+ });
538
+
539
+ // Use in messages
540
+ const cachedMessage = Prompt.makeMessage('system', {
541
+ content: 'You are an expert.',
542
+ options: {
543
+ anthropic: { cache_control: { type: 'ephemeral' } }
544
+ }
545
+ });
546
+ ```
547
+
548
+ ## Serialization
549
+
550
+ ### Export/Import
551
+
552
+ ```typescript
553
+ import * as Schema from 'effect/Schema';
554
+ import { Effect } from 'effect';
555
+
556
+ // Prompt.Prompt is a Schema.Codec<Prompt, PromptEncoded>
557
+ const encode = Schema.encodeEffect(Prompt.Prompt);
558
+ const decode = Schema.decodeUnknownEffect(Prompt.Prompt);
559
+
560
+ const program = Effect.gen(function* () {
561
+ const prompt = Prompt.make('Hello');
562
+
563
+ // Export to structured data
564
+ const exported = yield* encode(prompt);
565
+
566
+ // Import from structured data
567
+ const imported = yield* decode(exported);
568
+
569
+ return imported;
570
+ });
571
+ ```
572
+
573
+ ### JSON Serialization
574
+
575
+ Use `Schema.fromJsonString` with `Prompt.Prompt` to create a JSON codec:
576
+
577
+ ```typescript
578
+ import * as Schema from 'effect/Schema';
579
+ import { Effect } from 'effect';
580
+
581
+ const PromptJson = Schema.fromJsonString(Prompt.Prompt);
582
+ const encodeJson = Schema.encodeEffect(PromptJson);
583
+ const decodeJson = Schema.decodeUnknownEffect(PromptJson);
584
+
585
+ const program = Effect.gen(function* () {
586
+ const prompt = Prompt.make([
587
+ { role: 'system', content: 'You are helpful.' },
588
+ { role: 'user', content: [{ type: 'text', text: 'Hi' }] }
589
+ ]);
590
+
591
+ // To JSON string
592
+ const json = yield* encodeJson(prompt);
593
+ yield* Effect.log(json); // string
594
+
595
+ // From JSON string
596
+ const restored = yield* decodeJson(json);
597
+
598
+ return restored;
599
+ });
600
+ ```
601
+
602
+ **Note**: There is no `Prompt.FromJson` in v4. Use `Schema.fromJsonString(Prompt.Prompt)` instead.
603
+
604
+ ## Common Patterns
605
+
606
+ ### Multi-turn Conversation
607
+
608
+ ```typescript
609
+ const conversation = Prompt.make([
610
+ { role: 'system', content: 'You are a math tutor.' },
611
+ { role: 'user', content: [{ type: 'text', text: 'What is 2+2?' }] },
612
+ { role: 'assistant', content: [{ type: 'text', text: '2+2 equals 4.' }] },
613
+ { role: 'user', content: [{ type: 'text', text: 'What about 3+3?' }] }
614
+ ]);
615
+ ```
616
+
617
+ ### Dynamic System Prompt
618
+
619
+ ```typescript
620
+ import { pipe } from 'effect';
621
+
622
+ function withSystemPrompt(content: string) {
623
+ return (prompt: Prompt.Prompt) => pipe(prompt, Prompt.setSystem(content));
624
+ }
625
+
626
+ const userPrompt = Prompt.make('Help me code');
627
+ const withContext = pipe(
628
+ userPrompt,
629
+ withSystemPrompt('You are an expert TypeScript developer.')
630
+ );
631
+ ```
632
+
633
+ ### Tool Interaction History
634
+
635
+ ```typescript
636
+ const toolInteraction = Prompt.make([
637
+ { role: 'user', content: [{ type: 'text', text: "What's the weather?" }] },
638
+ {
639
+ role: 'assistant',
640
+ content: [
641
+ {
642
+ type: 'tool-call',
643
+ id: '1',
644
+ name: 'weather',
645
+ params: {},
646
+ providerExecuted: false
647
+ }
648
+ ]
649
+ },
650
+ {
651
+ role: 'tool',
652
+ content: [
653
+ {
654
+ type: 'tool-result',
655
+ id: '1',
656
+ name: 'weather',
657
+ isFailure: false,
658
+ result: 'Sunny, 22°C'
659
+ }
660
+ ]
661
+ },
662
+ {
663
+ role: 'assistant',
664
+ content: [{ type: 'text', text: "It's sunny and 22°C." }]
665
+ }
666
+ ]);
667
+ ```
668
+
669
+ ## Type Guards
670
+
671
+ ```typescript
672
+ import * as Prompt from 'effect/unstable/ai/Prompt';
673
+
674
+ declare const value: unknown;
675
+
676
+ if (Prompt.isPrompt(value)) {
677
+ // value: Prompt.Prompt
678
+ value.content; // narrowed to Prompt.Prompt
679
+ }
680
+
681
+ if (Prompt.isMessage(value)) {
682
+ // value: Prompt.Message
683
+ value.role; // narrowed to Prompt.Message
684
+ }
685
+
686
+ if (Prompt.isPart(value)) {
687
+ // value: Prompt.Part
688
+ value.type; // narrowed to Prompt.Part
689
+ }
690
+ ```
691
+
692
+ ## Anti-Patterns
693
+
694
+ ```typescript
695
+ // DON'T: Manually construct messages without constructors
696
+ const bad = {
697
+ role: 'user',
698
+ content: [{ type: 'text', text: 'Hello' }]
699
+ } as Prompt.UserMessage;
700
+
701
+ // DO: Use constructors
702
+ const good = Prompt.makeMessage('user', {
703
+ content: [Prompt.makePart('text', { text: 'Hello' })]
704
+ });
705
+
706
+ // DO: Use shorthand constructors
707
+ const better = Prompt.userMessage({
708
+ content: [Prompt.makePart('text', { text: 'Hello' })]
709
+ });
710
+
711
+ // DON'T: Mutate prompt content
712
+ const prompt = Prompt.make('Hi');
713
+ prompt.content.push(someMessage); // Error: readonly
714
+
715
+ // DO: Use merge for composition
716
+ const extended = Prompt.concat(prompt, 'Additional message');
717
+
718
+ // DON'T: Manually filter response parts
719
+ const filtered = responseParts.filter((p) => p.type === 'text');
720
+
721
+ // DO: Use fromResponseParts (handles streaming deltas, tool results, etc.)
722
+ const historyPrompt = Prompt.fromResponseParts(responseParts);
723
+ ```
724
+
725
+ ### Decoding Unknown Parts
726
+
727
+ ```typescript
728
+ import * as Schema from 'effect/Schema';
729
+
730
+ const decodeAssistantPart = Schema.decodeUnknownEffect(
731
+ Prompt.AssistantMessagePart
732
+ );
733
+
734
+ const decoded = yield* decodeAssistantPart(unknownPart);
735
+ ```
736
+
737
+ Use `Prompt.Part` for the unrestricted part union or a role-specific schema when the destination message role is already known.
738
+
739
+ ## Related Skills
740
+
741
+ - effect-ai-language-model - Using prompts with LanguageModel service
742
+ - effect-ai-streaming - Converting streaming responses to prompt history
743
+ - effect-ai-tool - Integrating tool call/result messages
744
+
745
+ ## Quality Checklist
746
+
747
+ - [ ] Messages use `Prompt.makeMessage` or shorthand constructors
748
+ - [ ] Parts use `Prompt.makePart` constructors
749
+ - [ ] System messages managed with `setSystem`/`prependSystem`/`appendSystem`
750
+ - [ ] History updates use `Prompt.fromResponseParts`
751
+ - [ ] Prompt composition uses `Prompt.concat` (not mutation)
752
+ - [ ] Namespace imports for all Effect AI modules