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.
- package/LICENSE +21 -0
- package/README.md +278 -0
- package/guidance/effect-first-development.md +1247 -0
- package/guidance/post__effect-and-the-near-inexpressible-majesty-of-layers.md +490 -0
- package/guidance/post__parse-dont-validate.md +109 -0
- package/guidance/progressive-disclosure-guidance.md +38 -0
- package/package.json +63 -0
- package/patterns/avoid-any.md +37 -0
- package/patterns/avoid-data-tagged-error.md +34 -0
- package/patterns/avoid-direct-json.md +51 -0
- package/patterns/avoid-direct-tag-checks.md +54 -0
- package/patterns/avoid-expect-in-if.md +52 -0
- package/patterns/avoid-mutable-state.md +70 -0
- package/patterns/avoid-native-fetch.md +61 -0
- package/patterns/avoid-node-imports.md +86 -0
- package/patterns/avoid-non-null-assertion.md +44 -0
- package/patterns/avoid-object-type.md +46 -0
- package/patterns/avoid-option-getorthrow.md +39 -0
- package/patterns/avoid-platform-coupling.md +43 -0
- package/patterns/avoid-process-env.md +43 -0
- package/patterns/avoid-react-hooks.md +73 -0
- package/patterns/avoid-schema-suffix.md +45 -0
- package/patterns/avoid-sync-fs.md +68 -0
- package/patterns/avoid-try-catch.md +47 -0
- package/patterns/avoid-ts-ignore.md +38 -0
- package/patterns/avoid-untagged-errors.md +67 -0
- package/patterns/avoid-yield-ref.md +46 -0
- package/patterns/casting-awareness.md +46 -0
- package/patterns/context-tag-extends.md +84 -0
- package/patterns/effect-catchall-default.md +61 -0
- package/patterns/effect-promise-vs-trypromise.md +47 -0
- package/patterns/effect-run-in-body.md +58 -0
- package/patterns/imperative-loops.md +76 -0
- package/patterns/prefer-arr-sort.md +52 -0
- package/patterns/prefer-duration-values.md +56 -0
- package/patterns/prefer-effect-fn.md +161 -0
- package/patterns/prefer-match-over-switch.md +48 -0
- package/patterns/prefer-option-over-null.md +56 -0
- package/patterns/prefer-redacted-config.md +70 -0
- package/patterns/prefer-schema-class.md +54 -0
- package/patterns/require-effect-concurrency.md +83 -0
- package/patterns/stream-large-files.md +63 -0
- package/patterns/throw-in-effect-gen.md +62 -0
- package/patterns/use-clock-service.md +45 -0
- package/patterns/use-command-executor-service.md +54 -0
- package/patterns/use-console-service.md +54 -0
- package/patterns/use-filesystem-service.md +59 -0
- package/patterns/use-http-client-service.md +77 -0
- package/patterns/use-path-service.md +53 -0
- package/patterns/use-random-service.md +45 -0
- package/patterns/use-temp-file-scoped.md +66 -0
- package/patterns/vm-in-wrong-file.md +51 -0
- package/patterns/yield-in-for-loop.md +61 -0
- package/skills/effect-ai-chat/SKILL.md +472 -0
- package/skills/effect-ai-language-model/SKILL.md +652 -0
- package/skills/effect-ai-prompt/SKILL.md +752 -0
- package/skills/effect-ai-provider/SKILL.md +668 -0
- package/skills/effect-ai-streaming/SKILL.md +418 -0
- package/skills/effect-ai-tool/SKILL.md +1132 -0
- package/skills/effect-atom-rpc/SKILL.md +488 -0
- package/skills/effect-atom-state/SKILL.md +640 -0
- package/skills/effect-batching/SKILL.md +614 -0
- package/skills/effect-cache/SKILL.md +570 -0
- package/skills/effect-cli/SKILL.md +523 -0
- package/skills/effect-command-executor/SKILL.md +675 -0
- package/skills/effect-concurrency-testing/SKILL.md +612 -0
- package/skills/effect-config/SKILL.md +580 -0
- package/skills/effect-context-witness/SKILL.md +274 -0
- package/skills/effect-domain-modeling/SKILL.md +1212 -0
- package/skills/effect-domain-predicates/SKILL.md +867 -0
- package/skills/effect-error-handling/SKILL.md +1581 -0
- package/skills/effect-fiber/SKILL.md +731 -0
- package/skills/effect-filesystem/SKILL.md +624 -0
- package/skills/effect-graph/SKILL.md +571 -0
- package/skills/effect-http-api/SKILL.md +1760 -0
- package/skills/effect-http-client/SKILL.md +989 -0
- package/skills/effect-http-server/SKILL.md +920 -0
- package/skills/effect-incremental-migration/SKILL.md +362 -0
- package/skills/effect-layer-design/SKILL.md +642 -0
- package/skills/effect-managed-runtime/SKILL.md +395 -0
- package/skills/effect-mcp-server/SKILL.md +608 -0
- package/skills/effect-observability/SKILL.md +719 -0
- package/skills/effect-optics/SKILL.md +554 -0
- package/skills/effect-parallelization/SKILL.md +668 -0
- package/skills/effect-path/SKILL.md +296 -0
- package/skills/effect-pattern-matching/SKILL.md +914 -0
- package/skills/effect-platform-abstraction/SKILL.md +1175 -0
- package/skills/effect-platform-layers/SKILL.md +514 -0
- package/skills/effect-pubsub-event-bus/SKILL.md +384 -0
- package/skills/effect-react-composition/SKILL.md +986 -0
- package/skills/effect-react-vm/SKILL.md +675 -0
- package/skills/effect-rpc-api/SKILL.md +624 -0
- package/skills/effect-rpc-client/SKILL.md +666 -0
- package/skills/effect-rpc-cluster/SKILL.md +1623 -0
- package/skills/effect-rpc-server/SKILL.md +767 -0
- package/skills/effect-scheduling/SKILL.md +124 -0
- package/skills/effect-schema-composition/SKILL.md +975 -0
- package/skills/effect-schema-v4/SKILL.md +691 -0
- package/skills/effect-scope/SKILL.md +682 -0
- package/skills/effect-service-implementation/SKILL.md +656 -0
- package/skills/effect-socket/SKILL.md +703 -0
- package/skills/effect-sql/SKILL.md +781 -0
- package/skills/effect-stream/SKILL.md +765 -0
- package/skills/effect-testing/SKILL.md +1331 -0
- package/skills/effect-typeclass-design/SKILL.md +161 -0
- package/skills/effect-wide-events/Article.md +66 -0
- package/skills/effect-wide-events/SKILL.md +95 -0
- package/skills/effect-workflow/SKILL.md +810 -0
- package/src/agent-policy.ts +22 -0
- package/src/enforcer.ts +104 -0
- package/src/frontmatter.ts +34 -0
- package/src/guidance.ts +66 -0
- package/src/index.ts +38 -0
- package/src/pattern-catalog.ts +115 -0
- package/src/pattern-matcher.ts +178 -0
- package/src/pattern.ts +97 -0
- package/src/skills.ts +29 -0
- 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
|