@effect/sql-sqlite-react-native 4.0.0-beta.99 → 4.0.0-rc.109

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 (90) hide show
  1. package/AGENTS.md +395 -0
  2. package/CLAUDE.md +395 -0
  3. package/README.md +10 -3
  4. package/ai-docs/README.md +44 -0
  5. package/ai-docs/package.json +36 -0
  6. package/ai-docs/src/01_effect/01_basics/01_effect-gen.ts +30 -0
  7. package/ai-docs/src/01_effect/01_basics/02_effect-fn.ts +39 -0
  8. package/ai-docs/src/01_effect/01_basics/10_creating-effects.ts +74 -0
  9. package/ai-docs/src/01_effect/01_basics/index.md +5 -0
  10. package/ai-docs/src/01_effect/02_schema/10_schema-basics.ts +43 -0
  11. package/ai-docs/src/01_effect/02_schema/index.md +7 -0
  12. package/ai-docs/src/01_effect/03_services/01_service.ts +45 -0
  13. package/ai-docs/src/01_effect/03_services/10_reference.ts +10 -0
  14. package/ai-docs/src/01_effect/03_services/20_layer-composition.ts +70 -0
  15. package/ai-docs/src/01_effect/03_services/20_layer-unwrap.ts +66 -0
  16. package/ai-docs/src/01_effect/03_services/index.md +5 -0
  17. package/ai-docs/src/01_effect/04_errors/01_error-handling.ts +30 -0
  18. package/ai-docs/src/01_effect/04_errors/10_catch-tags.ts +24 -0
  19. package/ai-docs/src/01_effect/04_errors/20_reason-errors.ts +64 -0
  20. package/ai-docs/src/01_effect/04_errors/index.md +1 -0
  21. package/ai-docs/src/01_effect/05_resources/10_acquire-release.ts +105 -0
  22. package/ai-docs/src/01_effect/05_resources/20_layer-side-effects.ts +31 -0
  23. package/ai-docs/src/01_effect/05_resources/30_layer-map.ts +86 -0
  24. package/ai-docs/src/01_effect/05_resources/index.md +3 -0
  25. package/ai-docs/src/01_effect/06_running/10_run-main.ts +30 -0
  26. package/ai-docs/src/01_effect/06_running/20_layer-launch.ts +27 -0
  27. package/ai-docs/src/01_effect/06_running/index.md +1 -0
  28. package/ai-docs/src/01_effect/07_pubsub/10_pubsub.ts +56 -0
  29. package/ai-docs/src/01_effect/07_pubsub/index.md +3 -0
  30. package/ai-docs/src/03_stream/10_creating-streams.ts +103 -0
  31. package/ai-docs/src/03_stream/20_consuming-streams.ts +137 -0
  32. package/ai-docs/src/03_stream/30_encoding.ts +165 -0
  33. package/ai-docs/src/03_stream/index.md +4 -0
  34. package/ai-docs/src/04_integration/10_managed-runtime.ts +129 -0
  35. package/ai-docs/src/04_integration/index.md +5 -0
  36. package/ai-docs/src/05_batching/10_request-resolver.ts +89 -0
  37. package/ai-docs/src/05_batching/index.md +3 -0
  38. package/ai-docs/src/06_schedule/10_schedules.ts +110 -0
  39. package/ai-docs/src/06_schedule/index.md +3 -0
  40. package/ai-docs/src/07_datetime/10_creating-and-formatting.ts +30 -0
  41. package/ai-docs/src/07_datetime/20_time-zones.ts +44 -0
  42. package/ai-docs/src/07_datetime/index.md +5 -0
  43. package/ai-docs/src/08_observability/10_logging.ts +66 -0
  44. package/ai-docs/src/08_observability/20_otlp-tracing.ts +95 -0
  45. package/ai-docs/src/08_observability/index.md +7 -0
  46. package/ai-docs/src/09_testing/10_effect-tests.ts +55 -0
  47. package/ai-docs/src/09_testing/20_layer-tests.ts +138 -0
  48. package/ai-docs/src/09_testing/index.md +1 -0
  49. package/ai-docs/src/10_predicate/01_basics.ts +14 -0
  50. package/ai-docs/src/10_predicate/index.md +9 -0
  51. package/ai-docs/src/40_sql/10_basics.ts +175 -0
  52. package/ai-docs/src/40_sql/index.md +6 -0
  53. package/ai-docs/src/50_http-client/10_basics.ts +102 -0
  54. package/ai-docs/src/50_http-client/index.md +3 -0
  55. package/ai-docs/src/51_http-server/10_basics.ts +118 -0
  56. package/ai-docs/src/51_http-server/20_testing.ts +100 -0
  57. package/ai-docs/src/51_http-server/fixtures/api/Api.ts +14 -0
  58. package/ai-docs/src/51_http-server/fixtures/api/Authorization.ts +36 -0
  59. package/ai-docs/src/51_http-server/fixtures/api/System.ts +10 -0
  60. package/ai-docs/src/51_http-server/fixtures/api/Users.ts +107 -0
  61. package/ai-docs/src/51_http-server/fixtures/domain/User.ts +21 -0
  62. package/ai-docs/src/51_http-server/fixtures/domain/UserErrors.ts +22 -0
  63. package/ai-docs/src/51_http-server/fixtures/server/Authorization.ts +37 -0
  64. package/ai-docs/src/51_http-server/fixtures/server/Users/http.ts +80 -0
  65. package/ai-docs/src/51_http-server/fixtures/server/Users.ts +183 -0
  66. package/ai-docs/src/51_http-server/index.md +3 -0
  67. package/ai-docs/src/60_child-process/10_working-with-child-processes.ts +117 -0
  68. package/ai-docs/src/60_child-process/index.md +3 -0
  69. package/ai-docs/src/70_cli/10_basics.ts +160 -0
  70. package/ai-docs/src/70_cli/index.md +5 -0
  71. package/ai-docs/src/71_ai/10_language-model.ts +156 -0
  72. package/ai-docs/src/71_ai/20_tools.ts +226 -0
  73. package/ai-docs/src/71_ai/30_chat.ts +158 -0
  74. package/ai-docs/src/71_ai/fixtures/domain/LaunchPlan.ts +9 -0
  75. package/ai-docs/src/71_ai/index.md +5 -0
  76. package/ai-docs/src/80_cluster/10_entities.ts +97 -0
  77. package/ai-docs/src/80_cluster/index.md +4 -0
  78. package/ai-docs/src/index.md +10 -0
  79. package/ai-docs/tsconfig.json +24 -0
  80. package/dist/SqliteClient.d.ts +3 -3
  81. package/dist/SqliteClient.d.ts.map +1 -1
  82. package/dist/SqliteClient.js +23 -10
  83. package/dist/SqliteClient.js.map +1 -1
  84. package/dist/SqliteMigrator.d.ts +2 -2
  85. package/dist/SqliteMigrator.js +2 -2
  86. package/dist/SqliteMigrator.js.map +1 -1
  87. package/dist/index.js.map +1 -1
  88. package/package.json +11 -9
  89. package/src/SqliteClient.ts +26 -12
  90. package/src/SqliteMigrator.ts +2 -2
@@ -0,0 +1,156 @@
1
+ /**
2
+ * @title Using LanguageModel for text, objects, and streams
3
+ *
4
+ * Configure a provider once, then use `LanguageModel` for plain text
5
+ * generation, schema-validated object generation, and streaming responses.
6
+ */
7
+ import { AnthropicClient, AnthropicLanguageModel } from "@effect/ai-anthropic"
8
+ import { OpenAiClient, OpenAiLanguageModel } from "@effect/ai-openai"
9
+ import { Config, Context, Effect, ExecutionPlan, Layer, Schema, Stream } from "effect"
10
+ import { AiError, LanguageModel, Model, type Response } from "effect/unstable/ai"
11
+ import { FetchHttpClient } from "effect/unstable/http"
12
+ import { LaunchPlan } from "./fixtures/domain/LaunchPlan.ts"
13
+
14
+ // You can use Config to create ai clients
15
+ const AnthropicClientLayer = AnthropicClient.layerConfig({
16
+ apiKey: Config.redacted("ANTHROPIC_API_KEY")
17
+ }).pipe(
18
+ // Providers typically require an HttpClient, but you can choose which one to
19
+ // use.
20
+ Layer.provide(FetchHttpClient.layer)
21
+ )
22
+
23
+ const OpenAiClientLayer = OpenAiClient.layerConfig({
24
+ apiKey: Config.redacted("OPENAI_API_KEY")
25
+ }).pipe(
26
+ Layer.provide(FetchHttpClient.layer)
27
+ )
28
+
29
+ export class AiWriterError extends Schema.TaggedError<AiWriterError>()("AiWriterError", {
30
+ // AiErrorReason is a Schema, so we can include it directly in our custom
31
+ // error schema.
32
+ reason: AiError.AiErrorReason
33
+ }) {
34
+ static fromAiError(error: AiError.AiError) {
35
+ return new AiWriterError({
36
+ reason: error.reason
37
+ })
38
+ }
39
+ }
40
+
41
+ // You can use `ExecutionPlan` to define a strategy for trying multiple
42
+ // providers with different configurations. In this example, we try a cheaper
43
+ // OpenAI model first, then fall back to a more expensive Anthropic model if the
44
+ // first one fails.
45
+ const DraftPlan = ExecutionPlan.make(
46
+ {
47
+ provide: OpenAiLanguageModel.model("gpt-5.2"),
48
+ // Attempt to use the openai model up to 3 times before falling back to the
49
+ // anthropic model.
50
+ attempts: 3
51
+ },
52
+ {
53
+ provide: AnthropicLanguageModel.model("claude-opus-4-6"),
54
+ attempts: 2
55
+ }
56
+ )
57
+
58
+ export class AiWriter extends Context.Service<AiWriter, {
59
+ draftAnnouncement(product: string): Effect.Effect<{
60
+ readonly provider: string
61
+ readonly text: string
62
+ }, AiWriterError>
63
+ extractLaunchPlan(notes: string): Effect.Effect<LaunchPlan, AiWriterError>
64
+ streamReleaseHighlights(version: string): Stream.Stream<string, AiWriterError>
65
+ }>()("docs/AiWriter") {
66
+ static readonly layer = Layer.effect(
67
+ AiWriter,
68
+ Effect.gen(function*() {
69
+ // Calling `captureRequirements` on an `ExecutionPlan` will move the
70
+ // requirements of the plan (in this case the ai clients) into the Layer
71
+ // requirements.
72
+ const draftsModel = yield* DraftPlan.captureRequirements
73
+
74
+ // Use a different model for the launch plan extraction
75
+ const launchPlanModel = yield* OpenAiLanguageModel.model("gpt-4.1").captureRequirements
76
+
77
+ const draftAnnouncement = Effect.fn("AiWriter.draftAnnouncement")(
78
+ function*(product: string) {
79
+ const model = yield* LanguageModel.LanguageModel
80
+ const provider = yield* Model.ProviderName
81
+ const response = yield* model.generateText({
82
+ prompt: `Write a short launch announcement for ${product}. ` +
83
+ "Keep it concise and include one concrete user benefit."
84
+ })
85
+
86
+ // `LanguageModel.generateText` exposes convenience fields so you can
87
+ // inspect usage and finish reason without parsing content parts.
88
+ yield* Effect.logInfo(
89
+ `${provider} finished with ${response.finishReason}. outputTokens=${response.usage.outputTokens.total}`
90
+ )
91
+
92
+ return {
93
+ provider,
94
+ text: response.text
95
+ }
96
+ },
97
+ // To apply an `ExecutionPlan`, we use `Effect.withExecutionPlan`
98
+ Effect.withExecutionPlan(draftsModel),
99
+ // Map AiError into our custom error type
100
+ Effect.mapError((error) => AiWriterError.fromAiError(error))
101
+ )
102
+
103
+ const extractLaunchPlan = Effect.fn("AiWriter.extractLaunchPlan")(
104
+ function*(notes: string) {
105
+ const model = yield* LanguageModel.LanguageModel
106
+ const response = yield* model.generateObject({
107
+ objectName: "launch_plan",
108
+ prompt:
109
+ "Convert these notes into a launch plan object with audience, channels, launchDate, summary, and keyRisks:\n" +
110
+ notes,
111
+ // The generated object is validated and decoded through this schema.
112
+ schema: LaunchPlan
113
+ })
114
+
115
+ return response.value
116
+ },
117
+ // The .model(...) apis return a Layer that can be used with
118
+ // Effect.provide
119
+ Effect.provide(launchPlanModel),
120
+ // Map AiError into our custom error type
121
+ Effect.mapError((error) => AiWriterError.fromAiError(error))
122
+ )
123
+
124
+ const streamReleaseHighlights = (version: string) =>
125
+ LanguageModel.streamText({
126
+ prompt: `Write release highlights for version ${version} as a short bulleted list.`
127
+ }).pipe(
128
+ Stream.filter((part): part is Response.TextDeltaPart => part.type === "text-delta"),
129
+ Stream.map((part) => part.delta),
130
+ Stream.provide(launchPlanModel),
131
+ // Map AiError into our custom error type
132
+ Stream.mapError((error) => AiWriterError.fromAiError(error))
133
+ )
134
+
135
+ return AiWriter.of({
136
+ draftAnnouncement,
137
+ extractLaunchPlan,
138
+ streamReleaseHighlights
139
+ })
140
+ })
141
+ ).pipe(
142
+ // This Layer has requirements for both the OpenAI and Anthropic clients,
143
+ // since the ExecutionPlan includes models from both providers.
144
+ Layer.provide([OpenAiClientLayer, AnthropicClientLayer])
145
+ )
146
+ }
147
+
148
+ // We can now use `AiWriter` like any other Effect service.
149
+ export const program: Effect.Effect<
150
+ void,
151
+ AiWriterError,
152
+ AiWriter
153
+ > = Effect.gen(function*() {
154
+ const writer = yield* AiWriter
155
+ yield* writer.draftAnnouncement("Effect Cloud")
156
+ })
@@ -0,0 +1,226 @@
1
+ /**
2
+ * @title Defining and using AI tools
3
+ *
4
+ * Define tools with schemas, group them into toolkits, implement handlers,
5
+ * and pass them to `LanguageModel.generateText`.
6
+ */
7
+ import { OpenAiClient, OpenAiLanguageModel, OpenAiTool } from "@effect/ai-openai"
8
+ import { Config, Context, Effect, Layer, Schema } from "effect"
9
+ import { AiError, LanguageModel, Tool, Toolkit } from "effect/unstable/ai"
10
+ import { FetchHttpClient } from "effect/unstable/http"
11
+
12
+ // ---------------------------------------------------------------------------
13
+ // 1. Defining tools
14
+ // ---------------------------------------------------------------------------
15
+
16
+ const ProductId = Schema.String.pipe(Schema.brand("ProductId")).annotate({
17
+ description: "A unique identifier for a product, e.g. 'p-123'"
18
+ })
19
+
20
+ class Product extends Schema.Class<Product>("acme/domain/Product")({
21
+ id: ProductId,
22
+ name: Schema.String,
23
+ price: Schema.Finite
24
+ }) {}
25
+
26
+ // Each tool has a name, an optional description, a parameters schema that the
27
+ // model fills in, and a success schema for the handler result. The description
28
+ // is shown to the model to help it decide when to call the tool.
29
+ const SearchProducts = Tool.make("SearchProducts", {
30
+ description: "Search the product catalog by keyword",
31
+ parameters: Schema.Struct({
32
+ query: Schema.String.annotate({
33
+ // Add a description to individual parameters for even better model
34
+ // guidance.
35
+ description: "The search query, e.g. 'wireless headphones'"
36
+ }),
37
+ maxResults: Schema.Natural.pipe(Schema.withDecodingDefault(Effect.succeed(10))).annotate({
38
+ description: "The maximum number of results to return"
39
+ })
40
+ }),
41
+ success: Schema.Array(Product),
42
+ // The strategy used for handling errors returned from tool call handler
43
+ // execution.
44
+ //
45
+ // If set to `"error"` (the default), errors that occur during tool call handler
46
+ // execution will be returned in the error channel of the calling effect.
47
+ //
48
+ // If set to `"return"`, errors that occur during tool call handler execution
49
+ // will be captured and returned as part of the tool call result.
50
+ failureMode: "error"
51
+ })
52
+
53
+ const GetInventory = Tool.make("GetInventory", {
54
+ description: "Check current stock level for a product",
55
+ parameters: Schema.Struct({
56
+ productId: ProductId
57
+ }),
58
+ success: Schema.Struct({
59
+ productId: ProductId,
60
+ available: Schema.Natural
61
+ })
62
+ })
63
+
64
+ // ---------------------------------------------------------------------------
65
+ // 2. Grouping tools into a Toolkit
66
+ // ---------------------------------------------------------------------------
67
+
68
+ // `Toolkit.make` accepts any number of tools and produces a typed toolkit that
69
+ // knows the names and schemas of every tool it contains.
70
+ const ProductToolkit = Toolkit.make(SearchProducts, GetInventory)
71
+
72
+ // ---------------------------------------------------------------------------
73
+ // 3. Implementing handlers via toLayer
74
+ // ---------------------------------------------------------------------------
75
+
76
+ // `toLayer` returns a `Layer` that satisfies the handler requirements for every
77
+ // tool in the toolkit. Each handler receives the decoded parameters and returns
78
+ // an Effect producing the success type.
79
+ const ProductToolkitLayer = ProductToolkit.toLayer(Effect.gen(function*() {
80
+ yield* Effect.log("Initializing ProductToolkitLive")
81
+ // Here you could access other services or resources needed to implement the
82
+ // handlers, e.g. a database client or external API client.
83
+ //
84
+ // const client = yield* SomeDatabaseClient
85
+ return ProductToolkit.of({
86
+ SearchProducts: Effect.fn("ProductToolkit.SearchProducts")(function*({ query, maxResults }) {
87
+ return [
88
+ new Product({ id: ProductId.make("p-1"), name: `${query} widget`, price: 19.99 }),
89
+ new Product({ id: ProductId.make("p-2"), name: `${query} gadget`, price: 29.99 })
90
+ ].slice(0, maxResults)
91
+ }),
92
+ GetInventory: Effect.fn("ProductToolkit.GetInventory")(function*({ productId }) {
93
+ return { productId, available: 42 }
94
+ })
95
+ })
96
+ }))
97
+
98
+ // ---------------------------------------------------------------------------
99
+ // 4. Using tools with LanguageModel
100
+ // ---------------------------------------------------------------------------
101
+
102
+ // Provider setup (same pattern as the language-model example).
103
+ const OpenAiClientLayer = OpenAiClient.layerConfig({
104
+ apiKey: Config.redacted("OPENAI_API_KEY")
105
+ }).pipe(Layer.provide(FetchHttpClient.layer))
106
+
107
+ export class ProductAssistantError extends Schema.TaggedError<ProductAssistantError>()(
108
+ "ProductAssistantError",
109
+ { reason: AiError.AiErrorReason }
110
+ ) {}
111
+
112
+ // Wrap tool-enabled generation in a service
113
+ export class ProductAssistant extends Context.Service<ProductAssistant, {
114
+ answer(question: string): Effect.Effect<{
115
+ readonly text: string
116
+ readonly toolCallCount: number
117
+ }, ProductAssistantError>
118
+ }>()("docs/ProductAssistant") {
119
+ static readonly layer = Layer.effect(
120
+ ProductAssistant,
121
+ Effect.gen(function*() {
122
+ // Access the toolkit's handlers by yielding the toolkit definition.
123
+ const toolkit = yield* ProductToolkit
124
+
125
+ // Choose a model to use
126
+ const model = yield* OpenAiLanguageModel.model("gpt-5.2").captureRequirements
127
+
128
+ const answer = Effect.fn("ProductAssistant.answer")(
129
+ function*(question: string) {
130
+ // Pass the toolkit to `generateText`. The model can call any tool in
131
+ // the toolkit; the framework resolves parameters, invokes handlers,
132
+ // and feeds results back automatically.
133
+ const response = yield* LanguageModel.generateText({
134
+ prompt: question,
135
+ toolkit,
136
+ // You can set `toolChoice` to "required" to force the model to call
137
+ // a tool before responding with text.
138
+ //
139
+ // By default it is set to "auto"
140
+ toolChoice: "required"
141
+ })
142
+
143
+ // -------------------------------------------------------------------
144
+ // 5. Inspecting tool calls and results
145
+ // -------------------------------------------------------------------
146
+
147
+ // `response.toolCalls` lists every tool the model invoked, each with
148
+ // the tool name, a unique id, and the decoded parameters.
149
+ for (const call of response.toolCalls) {
150
+ yield* Effect.log(`Tool call: ${call.name} id=${call.id}`)
151
+ }
152
+
153
+ // `response.toolResults` lists the resolved results, each with the
154
+ // tool name, id, decoded result, and an `isFailure` flag.
155
+ for (const result of response.toolResults) {
156
+ yield* Effect.log(
157
+ `Tool result: ${result.name} id=${result.id} isFailure=${result.isFailure}`
158
+ )
159
+ }
160
+
161
+ return {
162
+ text: response.text,
163
+ toolCallCount: response.toolCalls.length
164
+ }
165
+ },
166
+ // Provide the chosen model to use
167
+ Effect.provide(model),
168
+ (_) => _,
169
+ // Map AI errors into our domain error type
170
+ Effect.catchTag(
171
+ "AiError",
172
+ (error) =>
173
+ Effect.fail(
174
+ new ProductAssistantError({
175
+ reason: error.reason
176
+ })
177
+ ),
178
+ // For unexpected errors, die with the original error
179
+ (e) => Effect.die(e)
180
+ )
181
+ )
182
+
183
+ return ProductAssistant.of({ answer })
184
+ })
185
+ ).pipe(
186
+ // The toolkit handler layer must be provided so the framework can invoke
187
+ // the tool handlers when the model makes tool calls.
188
+ Layer.provide(ProductToolkitLayer),
189
+ // Also provide the openai client required by OpenAiLanguageModel.model
190
+ Layer.provide(OpenAiClientLayer)
191
+ )
192
+ }
193
+
194
+ // ---------------------------------------------------------------------------
195
+ // 6. Provider-defined tools
196
+ // ---------------------------------------------------------------------------
197
+
198
+ // Some providers offer built-in tools (web search, code interpreter, etc.)
199
+ // that run server-side. Use `Tool.providerDefined` or the pre-built
200
+ // definitions from provider packages.
201
+
202
+ // OpenAI's web search tool is pre-defined in `@effect/ai-openai`. Calling it
203
+ // produces a tool instance that can be merged into any toolkit.
204
+ const webSearch = OpenAiTool.WebSearch({
205
+ search_context_size: "medium"
206
+ })
207
+
208
+ // Combine user-defined and provider-defined tools in a single toolkit.
209
+ const AssistantToolkit = Toolkit.make(SearchProducts, GetInventory, webSearch)
210
+
211
+ // Only user-defined tools that require handlers appear in `toLayer`. The
212
+ // provider-defined `WebSearch` is executed server-side by the provider.
213
+ export const AssistantToolkitLayer = AssistantToolkit.toLayer(Effect.gen(function*() {
214
+ yield* Effect.log("Initializing AssistantToolkitLive")
215
+ return AssistantToolkit.of({
216
+ SearchProducts: Effect.fn("AssistantToolkit.SearchProducts")(function*({ query, maxResults }) {
217
+ return [
218
+ new Product({ id: ProductId.make("p-1"), name: `${query} widget`, price: 19.99 }),
219
+ new Product({ id: ProductId.make("p-2"), name: `${query} gadget`, price: 29.99 })
220
+ ].slice(0, maxResults)
221
+ }),
222
+ GetInventory: Effect.fn("AssistantToolkit.GetInventory")(function*({ productId }) {
223
+ return { productId, available: 42 }
224
+ })
225
+ })
226
+ }))
@@ -0,0 +1,158 @@
1
+ /**
2
+ * @title Stateful chat sessions
3
+ *
4
+ * The AI `Chat` module maintains conversation history automatically. Build
5
+ * AI agents or chat assistants.
6
+ */
7
+ import { OpenAiClient, OpenAiLanguageModel } from "@effect/ai-openai"
8
+ import { Config, Context, DateTime, Effect, Layer, Ref, Schema } from "effect"
9
+ import { AiError, Chat, Prompt, Tool, Toolkit } from "effect/unstable/ai"
10
+ import { FetchHttpClient } from "effect/unstable/http"
11
+
12
+ // ---------------------------------------------------------------------------
13
+ // Provider setup
14
+ // ---------------------------------------------------------------------------
15
+
16
+ const OpenAiClientLayer = OpenAiClient.layerConfig({
17
+ apiKey: Config.redacted("OPENAI_API_KEY")
18
+ }).pipe(Layer.provide(FetchHttpClient.layer))
19
+
20
+ // ---------------------------------------------------------------------------
21
+ // Tools for the agentic loop
22
+ // ---------------------------------------------------------------------------
23
+
24
+ const Tools = Toolkit.make(Tool.make("getCurrentTime", {
25
+ description: "Get the current time in ISO format",
26
+ parameters: Schema.Struct({
27
+ id: Schema.String
28
+ }),
29
+ success: Schema.String
30
+ }))
31
+
32
+ const ToolsLayer = Tools.toLayer(Effect.gen(function*() {
33
+ yield* Effect.logDebug("Initializing tools...")
34
+ return Tools.of({
35
+ getCurrentTime: Effect.fn("Tools.getCurrentTime")(function*(_) {
36
+ const now = yield* DateTime.now
37
+ return DateTime.formatIso(now)
38
+ })
39
+ })
40
+ }))
41
+
42
+ // ---------------------------------------------------------------------------
43
+ // Service that wraps Chat for a domain use-case
44
+ // ---------------------------------------------------------------------------
45
+
46
+ export class AiAssistantError extends Schema.TaggedError<AiAssistantError>()("AiAssistantError", {
47
+ reason: AiError.AiErrorReason
48
+ }) {
49
+ static fromAiError(error: AiError.AiError) {
50
+ return new AiAssistantError({ reason: error.reason })
51
+ }
52
+ }
53
+
54
+ export class AiAssistant extends Context.Service<AiAssistant, {
55
+ // Send a message while maintaining conversation history across turns.
56
+ chat(message: string): Effect.Effect<string, AiAssistantError>
57
+ // Ask a question and use an agentic loop with tool calls to answer it.
58
+ agent(question: string): Effect.Effect<string, AiAssistantError>
59
+ }>()("acme/AiAssistant") {
60
+ static readonly layer = Layer.effect(
61
+ AiAssistant,
62
+ Effect.gen(function*() {
63
+ // Choose the model you want to use for the chat sessions.
64
+ const modelLayer = yield* OpenAiLanguageModel.model("gpt-5.2").captureRequirements
65
+
66
+ // ---------------------------------------------------------------------------
67
+ // 1. Chat.empty — basic multi-turn conversation
68
+ // ---------------------------------------------------------------------------
69
+
70
+ // Create a new chat session with `Chat.empty` or `Chat.fromPrompt`. The
71
+ // session maintains conversation history automatically, so you can focus on
72
+ // the current turn without having to manage context.
73
+ const newSession = yield* Chat.fromPrompt(Prompt.empty.pipe(
74
+ Prompt.setSystem("You are a helpful assistant that answers questions.")
75
+ ))
76
+
77
+ // You can also create a chat using a json export.
78
+ const json = yield* newSession.exportJson
79
+ const session = yield* Chat.fromJson(json)
80
+
81
+ const chat = Effect.fn("AiAssistant.chat")(
82
+ function*(message: string) {
83
+ // Create a new turn in the conversation by passing the user's message
84
+ // to `session.generateText`.
85
+ const response = yield* session.generateText({ prompt: message }).pipe(
86
+ // Provide the model layer to use.
87
+ // You could potentially use different models for different turns,
88
+ // or even switch models in the middle of a conversation.
89
+ Effect.provide(modelLayer)
90
+ )
91
+
92
+ // You can inspect the accumulated history at any point through the
93
+ // `history` ref on the chat instance.
94
+ const history = yield* Ref.get(session.history)
95
+ yield* Effect.logInfo(
96
+ `Conversation has ${history.content.length} messages`
97
+ )
98
+
99
+ return response.text
100
+ },
101
+ Effect.mapError((error) => AiAssistantError.fromAiError(error))
102
+ )
103
+
104
+ // ---------------------------------------------------------------------------
105
+ // 2. Create agentic loops with tools
106
+ // ---------------------------------------------------------------------------
107
+
108
+ const tools = yield* Tools
109
+ const agent = Effect.fn("AiAssistant.agent")(
110
+ function*(question: string) {
111
+ // We start the agent with a system prompt and the user question. The
112
+ // agent can then call tools in a loop until it decides to return a
113
+ // final answer.
114
+ const session = yield* Chat.fromPrompt([
115
+ { role: "system", content: "You are an assistant that can use tools to answer questions." },
116
+ { role: "user", content: question }
117
+ ])
118
+
119
+ while (true) {
120
+ const response = yield* session.generateText({
121
+ prompt: [], // No additional prompt — the model has full access to the conversation history
122
+ toolkit: tools // Provide the tools to the model
123
+ }).pipe(
124
+ // Provide the model layer to use.
125
+ // You could potentially use different models for different turns,
126
+ // or even switch models in the middle of a conversation.
127
+ Effect.provide(modelLayer)
128
+ )
129
+ if (response.toolCalls.length > 0) {
130
+ // If the model called any tools, execute them and the Chat module
131
+ // will automatically add the tool results to the conversation
132
+ // history before the next turn.
133
+ continue
134
+ }
135
+ // If there are no tool calls, the model has returned a final answer
136
+ // and we can exit the loop.
137
+ return response.text
138
+ }
139
+ },
140
+ // Remap AI errors to our domain-specific error type, but die on
141
+ // unexpected errors.
142
+ Effect.catchTag(
143
+ "AiError",
144
+ (error) => Effect.fail(AiAssistantError.fromAiError(error)),
145
+ (e) => Effect.die(e)
146
+ )
147
+ )
148
+
149
+ return AiAssistant.of({
150
+ chat,
151
+ agent
152
+ })
153
+ })
154
+ ).pipe(
155
+ // Provide the OpenAI client and tools layers to the AiAssistant service.
156
+ Layer.provide([OpenAiClientLayer, ToolsLayer])
157
+ )
158
+ }
@@ -0,0 +1,9 @@
1
+ import { Schema } from "effect"
2
+
3
+ export class LaunchPlan extends Schema.Class<LaunchPlan>("LaunchPlan")({
4
+ audience: Schema.Literals(["developers", "operators", "platform teams"]),
5
+ channels: Schema.Array(Schema.String),
6
+ launchDate: Schema.String,
7
+ summary: Schema.String,
8
+ keyRisks: Schema.Array(Schema.String)
9
+ }) {}
@@ -0,0 +1,5 @@
1
+ ## Working with AI modules
2
+
3
+ Effect's AI modules provide a provider-agnostic interface for language models.
4
+ You can generate text, decode structured objects with `Schema` and stream partial
5
+ responses.
@@ -0,0 +1,97 @@
1
+ /**
2
+ * @title Defining cluster entities
3
+ *
4
+ * Define distributed entity RPCs and run them in a cluster.
5
+ */
6
+ import { NodeClusterSocket, NodeRuntime } from "@effect/platform-node"
7
+ import { Effect, Layer, Ref, Schema } from "effect"
8
+ import { ClusterSchema, Entity, TestRunner } from "effect/unstable/cluster"
9
+ import { Rpc } from "effect/unstable/rpc"
10
+ import type { SqlClient } from "effect/unstable/sql"
11
+
12
+ export const Increment = Rpc.make("Increment", {
13
+ payload: { amount: Schema.Int },
14
+ success: Schema.Int
15
+ })
16
+
17
+ export const GetCount = Rpc.make("GetCount", {
18
+ success: Schema.Int
19
+ })
20
+ // If you want GetCount messages to be persisted, you can annotate the RPC
21
+ // schema with `ClusterSchema.Persisted`.
22
+ //
23
+ // By default, messages are volatile and only sent over a network.
24
+ .annotate(ClusterSchema.Persisted, true)
25
+
26
+ // `Entity.make` takes an array of Rpc definitions
27
+ export const Counter = Entity.make("Counter", [Increment, GetCount])
28
+
29
+ // Entity handlers can keep in-memory state while the entity is active.
30
+ // `maxIdleTime` controls passivation: if the entity is idle long enough, it is
31
+ // stopped and later recreated on demand.
32
+ export const CounterEntityLayer = Counter.toLayer(
33
+ Effect.gen(function*() {
34
+ const count = yield* Ref.make(0)
35
+
36
+ return Counter.of({
37
+ Increment: ({ payload }) => Ref.updateAndGet(count, (current) => current + payload.amount),
38
+ GetCount: () =>
39
+ Ref.get(count).pipe(
40
+ // Add Rpc.fork to allow the GetCount handler to run concurrently with
41
+ // Increment handlers.
42
+ //
43
+ // This opts-out of the default behavior where all handlers for a
44
+ // given entity run sequentially.
45
+ Rpc.fork
46
+ )
47
+ })
48
+ }),
49
+ { maxIdleTime: "5 minutes" }
50
+ )
51
+
52
+ // If you ever need to access an entity client, you can use the `client`
53
+ // property on the entity definition.
54
+ export const useCounter = Effect.gen(function*() {
55
+ const clientFor = yield* Counter.client
56
+ const counter = clientFor("counter-123")
57
+
58
+ const afterIncrement = yield* counter.Increment({ amount: 1 })
59
+ const currentCount = yield* counter.GetCount()
60
+
61
+ console.log(`Count after increment: ${afterIncrement}, current count: ${currentCount}`)
62
+ })
63
+
64
+ // `SingleRunner.layer` is useful for local development / tests where you still
65
+ // want the cluster entity runtime model.
66
+ declare const SqlClientLayer: Layer.Layer<SqlClient.SqlClient>
67
+
68
+ // Create the cluster layer using `NodeClusterSocket.layer`
69
+ const ClusterLayer = NodeClusterSocket.layer().pipe(
70
+ Layer.provide(SqlClientLayer)
71
+ )
72
+
73
+ // You can also use `TestRunner.layer` to run your entities in a single process,
74
+ // without any network communication and in-memory storage. This is useful for testing and
75
+ // development.
76
+ const ClusterLayerTest = TestRunner.layer
77
+
78
+ // Merge all your entity layers together and provide the cluster layer to run
79
+ // them in a cluster.
80
+ const EntitiesLayer = Layer.mergeAll(
81
+ CounterEntityLayer
82
+ )
83
+
84
+ const ProductionLayer = EntitiesLayer.pipe(
85
+ Layer.provide(ClusterLayer)
86
+ )
87
+
88
+ export const TestLayer = EntitiesLayer.pipe(
89
+ // For testing, we can use `Layer.provideMerge` to tests can access storage
90
+ // and other cluster services directly.
91
+ Layer.provideMerge(ClusterLayerTest)
92
+ )
93
+
94
+ // Finally, run your app with the entities layer.
95
+ Layer.launch(ProductionLayer).pipe(
96
+ NodeRuntime.runMain
97
+ )
@@ -0,0 +1,4 @@
1
+ ## Building distributed applications with cluster
2
+
3
+ The cluster modules let you model stateful services as entities and distribute
4
+ them across multiple machines.
@@ -0,0 +1,10 @@
1
+ # Effect library documentation
2
+
3
+ This documentation covers the Effect library and its related packages.
4
+
5
+ When you need to find information about Effect, use this documentation and the
6
+ Effect source code available in your environment. Avoid unrelated copies of
7
+ Effect or external documentation, as they may be outdated or incorrect.
8
+
9
+ **Note**: The examples in this documentation contain comments for illustration
10
+ purposes. In practice, you would not include these comments in your code.