liminal 0.14.0 → 0.16.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 (204) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/Envelope.ts +48 -0
  3. package/{L.ts → L/L.ts} +7 -1
  4. package/L/Self.ts +5 -0
  5. package/L/append.ts +14 -0
  6. package/{assistant.ts → L/assistant.ts} +10 -9
  7. package/{assistantSchema.ts → L/assistantSchema.ts} +8 -7
  8. package/{assistantStream.ts → L/assistantStream.ts} +8 -11
  9. package/L/branch.ts +33 -0
  10. package/L/clear.ts +14 -0
  11. package/L/disable.ts +13 -0
  12. package/L/enable.ts +24 -0
  13. package/L/events.ts +11 -0
  14. package/L/fqn.ts +9 -0
  15. package/{handle.ts → L/handle.ts} +6 -5
  16. package/L/messages.ts +10 -0
  17. package/L/raw.ts +30 -0
  18. package/{sequence.ts → L/sequence.ts} +2 -2
  19. package/L/system.ts +14 -0
  20. package/L/thread.ts +28 -0
  21. package/L/to.ts +4 -0
  22. package/L/toolkit.ts +14 -0
  23. package/{user.test.ts → L/user.test.ts} +2 -2
  24. package/{user.ts → L/user.ts} +5 -4
  25. package/{userJson.ts → L/userJson.ts} +4 -4
  26. package/LEvent.ts +2 -2
  27. package/Thread.ts +69 -0
  28. package/dist/Envelope.d.ts +15 -0
  29. package/dist/Envelope.js +30 -0
  30. package/dist/Envelope.js.map +1 -0
  31. package/dist/{L.d.ts → L/L.d.ts} +7 -1
  32. package/dist/{L.js → L/L.js} +7 -1
  33. package/dist/L/L.js.map +1 -0
  34. package/dist/L/Self.d.ts +3 -0
  35. package/dist/L/Self.js +4 -0
  36. package/dist/L/Self.js.map +1 -0
  37. package/dist/{append.d.ts → L/append.d.ts} +3 -3
  38. package/dist/L/append.js +10 -0
  39. package/dist/L/append.js.map +1 -0
  40. package/dist/{assistant.d.ts → L/assistant.d.ts} +3 -3
  41. package/dist/{assistant.js → L/assistant.js} +8 -8
  42. package/dist/L/assistant.js.map +1 -0
  43. package/dist/{assistantSchema.d.ts → L/assistantSchema.d.ts} +4 -4
  44. package/dist/{assistantSchema.js → L/assistantSchema.js} +5 -5
  45. package/dist/L/assistantSchema.js.map +1 -0
  46. package/dist/{assistantStream.d.ts → L/assistantStream.d.ts} +3 -3
  47. package/dist/{assistantStream.js → L/assistantStream.js} +6 -6
  48. package/dist/L/assistantStream.js.map +1 -0
  49. package/dist/L/branch.d.ts +6 -0
  50. package/dist/L/branch.js +22 -0
  51. package/dist/L/branch.js.map +1 -0
  52. package/dist/{clear.d.ts → L/clear.d.ts} +3 -3
  53. package/dist/L/clear.js +12 -0
  54. package/dist/L/clear.js.map +1 -0
  55. package/dist/L/disable.d.ts +4 -0
  56. package/dist/L/disable.js +10 -0
  57. package/dist/L/disable.js.map +1 -0
  58. package/dist/L/enable.d.ts +5 -0
  59. package/dist/L/enable.js +13 -0
  60. package/dist/L/enable.js.map +1 -0
  61. package/dist/L/events.d.ts +5 -0
  62. package/dist/L/events.js +6 -0
  63. package/dist/L/events.js.map +1 -0
  64. package/dist/L/fqn.d.ts +3 -0
  65. package/dist/L/fqn.js +8 -0
  66. package/dist/L/fqn.js.map +1 -0
  67. package/dist/{handle.d.ts → L/handle.d.ts} +4 -4
  68. package/dist/{handle.js → L/handle.js} +3 -3
  69. package/dist/L/handle.js.map +1 -0
  70. package/dist/{messages.d.ts → L/messages.d.ts} +2 -2
  71. package/dist/L/messages.js +5 -0
  72. package/dist/L/messages.js.map +1 -0
  73. package/dist/L/raw.d.ts +3 -0
  74. package/dist/L/raw.js +18 -0
  75. package/dist/L/raw.js.map +1 -0
  76. package/dist/L/sequence.d.ts +2 -0
  77. package/dist/L/sequence.js.map +1 -0
  78. package/dist/L/system.d.ts +5 -0
  79. package/dist/L/system.js +12 -0
  80. package/dist/L/system.js.map +1 -0
  81. package/dist/L/thread.d.ts +6 -0
  82. package/dist/L/thread.js +17 -0
  83. package/dist/L/thread.js.map +1 -0
  84. package/dist/L/to.d.ts +3 -0
  85. package/dist/L/to.js +3 -0
  86. package/dist/L/to.js.map +1 -0
  87. package/dist/L/toolkit.d.ts +4 -0
  88. package/dist/L/toolkit.js +9 -0
  89. package/dist/L/toolkit.js.map +1 -0
  90. package/dist/L/user.d.ts +4 -0
  91. package/dist/{user.js → L/user.js} +2 -2
  92. package/dist/L/user.js.map +1 -0
  93. package/dist/L/user.test.js.map +1 -0
  94. package/dist/{userJson.d.ts → L/userJson.d.ts} +3 -3
  95. package/dist/{userJson.js → L/userJson.js} +2 -2
  96. package/dist/L/userJson.js.map +1 -0
  97. package/dist/LEvent.d.ts +2 -2
  98. package/dist/LEvent.js +2 -2
  99. package/dist/LEvent.js.map +1 -1
  100. package/dist/Thread.d.ts +55 -0
  101. package/dist/Thread.js +38 -0
  102. package/dist/Thread.js.map +1 -0
  103. package/dist/index.d.ts +3 -2
  104. package/dist/index.js +3 -2
  105. package/dist/index.js.map +1 -1
  106. package/dist/patterns/Debate.d.ts +1 -0
  107. package/dist/patterns/Debate.js +3 -0
  108. package/dist/patterns/Debate.js.map +1 -0
  109. package/dist/patterns/Model.d.ts +6 -0
  110. package/dist/patterns/Model.js +14 -0
  111. package/dist/patterns/Model.js.map +1 -0
  112. package/dist/patterns/Route.d.ts +3 -0
  113. package/dist/patterns/Route.js +14 -0
  114. package/dist/patterns/Route.js.map +1 -0
  115. package/dist/tsconfig.tsbuildinfo +1 -1
  116. package/dist/util/JsonValue.js.map +1 -1
  117. package/dist/util/NeverTool.d.ts +3 -0
  118. package/dist/util/NeverTool.js +3 -0
  119. package/dist/util/NeverTool.js.map +1 -0
  120. package/dist/util/Sequencer.d.ts +4 -0
  121. package/dist/util/Sequencer.js +2 -0
  122. package/dist/util/Sequencer.js.map +1 -0
  123. package/dist/util/Taggable.d.ts +0 -1
  124. package/dist/util/Taggable.js +0 -16
  125. package/dist/util/Taggable.js.map +1 -1
  126. package/dist/util/{fixRaw.js → normalizeRaw.js} +1 -1
  127. package/dist/util/normalizeRaw.js.map +1 -0
  128. package/dist/util/prefix.d.ts +1 -0
  129. package/dist/util/prefix.js +2 -0
  130. package/dist/util/prefix.js.map +1 -0
  131. package/index.ts +3 -2
  132. package/package.json +4 -2
  133. package/patterns/Debate.ts +2 -0
  134. package/patterns/Model.ts +29 -0
  135. package/patterns/{matchGist.ts → Route.ts} +7 -9
  136. package/util/JsonValue.ts +2 -2
  137. package/util/NeverTool.ts +10 -0
  138. package/util/Sequencer.ts +13 -0
  139. package/util/Taggable.ts +0 -26
  140. package/util/prefix.ts +1 -0
  141. package/Strand.ts +0 -44
  142. package/append.ts +0 -13
  143. package/branch.ts +0 -26
  144. package/clear.ts +0 -15
  145. package/disable.ts +0 -9
  146. package/dist/L.js.map +0 -1
  147. package/dist/Strand.d.ts +0 -31
  148. package/dist/Strand.js +0 -18
  149. package/dist/Strand.js.map +0 -1
  150. package/dist/append.js +0 -10
  151. package/dist/append.js.map +0 -1
  152. package/dist/assistant.js.map +0 -1
  153. package/dist/assistantSchema.js.map +0 -1
  154. package/dist/assistantStream.js.map +0 -1
  155. package/dist/branch.d.ts +0 -4
  156. package/dist/branch.js +0 -18
  157. package/dist/branch.js.map +0 -1
  158. package/dist/clear.js +0 -14
  159. package/dist/clear.js.map +0 -1
  160. package/dist/disable.d.ts +0 -5
  161. package/dist/disable.js +0 -7
  162. package/dist/disable.js.map +0 -1
  163. package/dist/enable.d.ts +0 -5
  164. package/dist/enable.js +0 -7
  165. package/dist/enable.js.map +0 -1
  166. package/dist/events.d.ts +0 -5
  167. package/dist/events.js +0 -6
  168. package/dist/events.js.map +0 -1
  169. package/dist/handle.js.map +0 -1
  170. package/dist/messages.js +0 -5
  171. package/dist/messages.js.map +0 -1
  172. package/dist/patterns/index.d.ts +0 -1
  173. package/dist/patterns/index.js +0 -2
  174. package/dist/patterns/index.js.map +0 -1
  175. package/dist/patterns/matchGist.d.ts +0 -3
  176. package/dist/patterns/matchGist.js +0 -16
  177. package/dist/patterns/matchGist.js.map +0 -1
  178. package/dist/sequence.d.ts +0 -2
  179. package/dist/sequence.js.map +0 -1
  180. package/dist/strand_.d.ts +0 -4
  181. package/dist/strand_.js +0 -8
  182. package/dist/strand_.js.map +0 -1
  183. package/dist/system.d.ts +0 -5
  184. package/dist/system.js +0 -12
  185. package/dist/system.js.map +0 -1
  186. package/dist/user.d.ts +0 -4
  187. package/dist/user.js.map +0 -1
  188. package/dist/user.test.js.map +0 -1
  189. package/dist/userJson.js.map +0 -1
  190. package/dist/util/Sequence.d.ts +0 -2
  191. package/dist/util/Sequence.js +0 -2
  192. package/dist/util/Sequence.js.map +0 -1
  193. package/dist/util/fixRaw.js.map +0 -1
  194. package/enable.ts +0 -11
  195. package/events.ts +0 -10
  196. package/messages.ts +0 -9
  197. package/patterns/index.ts +0 -1
  198. package/strand_.ts +0 -11
  199. package/system.ts +0 -12
  200. package/util/Sequence.ts +0 -11
  201. /package/dist/{sequence.js → L/sequence.js} +0 -0
  202. /package/dist/{user.test.d.ts → L/user.test.d.ts} +0 -0
  203. /package/dist/util/{fixRaw.d.ts → normalizeRaw.d.ts} +0 -0
  204. /package/util/{fixRaw.ts → normalizeRaw.ts} +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # liminal
2
2
 
3
+ ## 0.16.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 2e96076: Refactoring "strands" into "threads", which can provide a handle with which to operate on the thread context outside of the thread's sequencers arguments.
8
+
9
+ ## 0.15.0
10
+
11
+ ### Minor Changes
12
+
13
+ - 3b4ec1a: - Instead of providing `AiToolkit` to `L.enable`/`L.disable`, we now provide tools directly.
14
+ - Also includes the beginnings of a `coalesceModels` pattern.
15
+
3
16
  ## 0.14.0
4
17
 
5
18
  ### Minor Changes
package/Envelope.ts ADDED
@@ -0,0 +1,48 @@
1
+ import * as AiInput from "@effect/ai/AiInput"
2
+ import * as Effect from "effect/Effect"
3
+ import * as Option from "effect/Option"
4
+ import { append } from "./L/append.ts"
5
+ import { raw } from "./L/raw.ts"
6
+ import { Self } from "./L/Self.ts"
7
+ import type { Thread } from "./Thread.ts"
8
+ import { type Taggable } from "./util/Taggable.ts"
9
+
10
+ export interface MessageHeaders {
11
+ readonly to: Array<Thread>
12
+ readonly cc?: Array<Thread> | undefined
13
+ readonly bcc?: Array<Thread> | undefined
14
+ }
15
+
16
+ export interface EnvelopeMembers {
17
+ readonly headers: MessageHeaders
18
+ readonly cc: (...cc: Array<Thread>) => Envelope
19
+ readonly bcc: (...bcc: Array<Thread>) => Envelope
20
+ }
21
+
22
+ export interface Envelope extends Taggable<void, never, Thread>, EnvelopeMembers {}
23
+
24
+ // TODO: cc, bcc
25
+ export const Envelope = (headers: MessageHeaders): Envelope =>
26
+ Object.assign(
27
+ Effect.fnUntraced(function*(a0, ...aRest) {
28
+ const { state: { fqn } } = yield* Self
29
+ const name = Option.getOrElse(fqn, () => "anonymous-entity")
30
+ const text = yield* raw(a0, ...aRest)
31
+ if (!text) return
32
+ const { to, cc: _cc, bcc: _bcc } = headers
33
+ for (const recipient of to) {
34
+ yield* append(AiInput.UserMessage.make({
35
+ parts: [
36
+ AiInput.TextPart.make({
37
+ text: `[FROM: ${name}]\n${text}`,
38
+ }),
39
+ ],
40
+ })).pipe(Effect.provideService(Self, recipient))
41
+ }
42
+ }) satisfies Taggable<void, never, Thread>,
43
+ {
44
+ headers,
45
+ cc: (...cc) => Envelope({ ...headers, cc }),
46
+ bcc: (...bcc) => Envelope({ ...headers, bcc }),
47
+ } satisfies EnvelopeMembers,
48
+ )
package/{L.ts → L/L.ts} RENAMED
@@ -4,12 +4,18 @@ export * from "./assistantSchema.ts"
4
4
  export * from "./assistantStream.ts"
5
5
  export * from "./branch.ts"
6
6
  export * from "./clear.ts"
7
+ export * from "./disable.ts"
7
8
  export * from "./enable.ts"
8
9
  export * from "./events.ts"
10
+ export * from "./fqn.ts"
9
11
  export * from "./handle.ts"
10
12
  export * from "./messages.ts"
13
+ export * from "./raw.ts"
14
+ export * from "./Self.ts"
11
15
  export * from "./sequence.ts"
12
- export * from "./strand_.ts"
13
16
  export * from "./system.ts"
17
+ export * from "./thread.ts"
18
+ export * from "./to.ts"
19
+ export * from "./toolkit.ts"
14
20
  export * from "./user.ts"
15
21
  export * from "./userJson.ts"
package/L/Self.ts ADDED
@@ -0,0 +1,5 @@
1
+ import * as Context from "effect/Context"
2
+ import type { Thread } from "../Thread.ts"
3
+ import { prefix } from "../util/prefix.ts"
4
+
5
+ export const Self: Context.Tag<Thread, Thread> = Context.GenericTag<Thread>(prefix("Self"))
package/L/append.ts ADDED
@@ -0,0 +1,14 @@
1
+ import type { Message } from "@effect/ai/AiInput"
2
+ import * as Effect from "effect/Effect"
3
+ import { MessagesAppended } from "../LEvent.ts"
4
+ import type { Thread } from "../Thread.ts"
5
+ import { Self } from "./Self.ts"
6
+
7
+ /** Append messages to the thread. */
8
+ export const append: (
9
+ ...messages: Array<Message>
10
+ ) => Effect.Effect<void, never, Thread> = Effect.fnUntraced(function*(...messages) {
11
+ const { state, events } = yield* Self
12
+ state.messages.push(...messages)
13
+ yield* events.publish(MessagesAppended.make({ messages }))
14
+ })
@@ -1,28 +1,29 @@
1
1
  import type { AiError } from "@effect/ai/AiError"
2
2
  import { AssistantMessage, TextPart } from "@effect/ai/AiInput"
3
3
  import { AiLanguageModel } from "@effect/ai/AiLanguageModel"
4
- import * as AiToolkit from "@effect/ai/AiToolkit"
5
4
  import * as Effect from "effect/Effect"
6
5
  import * as Option from "effect/Option"
6
+ import type { Thread } from "../Thread.ts"
7
7
  import { append } from "./append.ts"
8
- import { Strand } from "./Strand.ts"
8
+ import { Self } from "./Self.ts"
9
+ import { toolkit } from "./toolkit.ts"
9
10
 
10
- /** Infer an assistant message and append it to the conversation. */
11
- export const assistant: Effect.Effect<string, AiError, AiLanguageModel | Strand> = Effect.gen(function*() {
11
+ /** Infer an assistant message and append it to the thread. */
12
+ export const assistant: Effect.Effect<string, AiError, AiLanguageModel | Thread> = Effect.gen(function*() {
12
13
  const model = yield* AiLanguageModel
13
- const { system, messages, tools } = yield* Strand
14
+ const { state: { system, messages: prompt } } = yield* Self
14
15
  let { text, results } = yield* model.generateText({
15
16
  system: Option.getOrUndefined(system),
16
- prompt: messages,
17
- toolkit: AiToolkit.merge(...tools) as never,
17
+ prompt,
18
+ toolkit,
18
19
  })
19
20
  // TODO: this shouldn't be necessary. Bug in Effect AI?
20
21
  if (!text.trim()) {
21
22
  text = results.values().next().value?.result as never as string
22
23
  }
23
24
  yield* append(
24
- new AssistantMessage({
25
- parts: [new TextPart({ text })],
25
+ AssistantMessage.make({
26
+ parts: [TextPart.make({ text })],
26
27
  }),
27
28
  )
28
29
  return text
@@ -5,21 +5,22 @@ import * as Effect from "effect/Effect"
5
5
  import * as Option from "effect/Option"
6
6
  import * as Schema from "effect/Schema"
7
7
  import * as SchemaAST from "effect/SchemaAST"
8
+ import type { Thread } from "../Thread.ts"
9
+ import { encodeJsonc, type JsonValue } from "../util/JsonValue.ts"
8
10
  import { append } from "./append.ts"
9
- import { Strand } from "./Strand.ts"
10
- import { encodeJsonc, type JsonValue } from "./util/JsonValue.ts"
11
+ import { Self } from "./Self.ts"
11
12
 
12
13
  /** Infer a structured assistant message and append its JSON representation to the conversation. */
13
14
  export const assistantSchema: {
14
15
  <F extends Record<string, Schema.Schema.AnyNoContext>>(
15
16
  fields: F,
16
- ): Effect.Effect<{ [K in keyof F]: Schema.Schema.Type<F[K]> }, AiError, AiLanguageModel | Strand>
17
+ ): Effect.Effect<{ [K in keyof F]: Schema.Schema.Type<F[K]> }, AiError, AiLanguageModel | Thread>
17
18
  <O, I extends JsonValue>(
18
19
  schema: Schema.Schema<O, I, never>,
19
- ): Effect.Effect<O, AiError, AiLanguageModel | Strand>
20
+ ): Effect.Effect<O, AiError, AiLanguageModel | Thread>
20
21
  } = Effect.fnUntraced(function*(schema) {
21
22
  const model = yield* AiLanguageModel
22
- const { system, messages } = yield* Strand
23
+ const { state: { system, messages } } = yield* Self
23
24
 
24
25
  const isSchema = Schema.isSchema(schema)
25
26
  const schema_ = isSchema ? schema : Schema.Struct(schema) as Schema.Schema.AnyNoContext
@@ -35,9 +36,9 @@ export const assistantSchema: {
35
36
  )
36
37
 
37
38
  yield* append(
38
- new AssistantMessage({
39
+ AssistantMessage.make({
39
40
  parts: [
40
- new TextPart({
41
+ TextPart.make({
41
42
  text: yield* encodeJsonc(schema_)(value),
42
43
  }),
43
44
  ],
@@ -1,25 +1,22 @@
1
1
  import type { AiError } from "@effect/ai/AiError"
2
2
  import { AiLanguageModel } from "@effect/ai/AiLanguageModel"
3
3
  import type { AiResponse } from "@effect/ai/AiResponse"
4
- import * as AiToolkit from "@effect/ai/AiToolkit"
5
4
  import * as Effect from "effect/Effect"
6
5
  import * as Option from "effect/Option"
7
6
  import * as Stream from "effect/Stream"
8
- import { Strand } from "./Strand"
7
+ import type { Thread } from "../Thread.ts"
8
+ import { Self } from "./Self.ts"
9
+ import { toolkit } from "./toolkit.ts"
9
10
 
10
- /** Get a streamed inference from the assistant without appending it to the strand's messages. */
11
- export const assistantStream: Stream.Stream<
12
- AiResponse,
13
- AiError,
14
- AiLanguageModel | Strand
15
- > = Stream.unwrap(
11
+ /** Get a stream of an assistant message (does not append the message to the thread). */
12
+ export const assistantStream: Stream.Stream<AiResponse, AiError, AiLanguageModel | Thread> = Stream.unwrap(
16
13
  Effect.gen(function*() {
17
14
  const model = yield* AiLanguageModel
18
- const { system, messages, tools } = yield* Strand
15
+ const { state: { system, messages: prompt } } = yield* Self
19
16
  return model.streamText({
20
17
  system: Option.getOrUndefined(system),
21
- prompt: messages,
22
- toolkit: AiToolkit.merge(...tools) as never,
18
+ prompt,
19
+ toolkit,
23
20
  })
24
21
  }),
25
22
  )
package/L/branch.ts ADDED
@@ -0,0 +1,33 @@
1
+ import * as Effect from "effect/Effect"
2
+ import { flow } from "effect/Function"
3
+ import * as Option from "effect/Option"
4
+ import * as PubSub from "effect/PubSub"
5
+ import type { LEvent } from "../LEvent.ts"
6
+ import { Thread, ThreadState } from "../Thread.ts"
7
+ import type { Sequencer } from "../util/Sequencer.ts"
8
+ import { Self } from "./Self.ts"
9
+ import { sequence } from "./sequence.ts"
10
+
11
+ export interface branch extends Sequencer<never, Thread>, Effect.Effect<Thread, never, Thread> {}
12
+
13
+ const branch_ = Effect.gen(function*() {
14
+ const parent = yield* Self
15
+ return Thread({
16
+ parent: Option.some(parent),
17
+ events: yield* PubSub.unbounded<LEvent>(),
18
+ state: ThreadState.make({
19
+ fqn: parent.state.fqn,
20
+ system: parent.state.system,
21
+ messages: [...parent.state.messages ?? []],
22
+ }),
23
+ tools: parent.tools.pipe(Option.map((v) => new Set(v))),
24
+ })
25
+ })
26
+
27
+ export const branch: branch = Object.assign(
28
+ flow(
29
+ sequence,
30
+ Effect.provideServiceEffect(Self, branch_),
31
+ ),
32
+ branch_,
33
+ )
package/L/clear.ts ADDED
@@ -0,0 +1,14 @@
1
+ import type { Message } from "@effect/ai/AiInput"
2
+ import * as Effect from "effect/Effect"
3
+ import { MessagesCleared } from "../LEvent.ts"
4
+ import type { Thread } from "../Thread.ts"
5
+ import { Self } from "./Self.ts"
6
+
7
+ /** Clear the thread of messages. */
8
+ export const clear: Effect.Effect<Array<Message>, never, Thread> = Effect.gen(function*() {
9
+ const { state, events } = yield* Self
10
+ const cleared = state.messages
11
+ state.messages = []
12
+ yield* events.publish(MessagesCleared.make({ cleared }))
13
+ return cleared
14
+ })
package/L/disable.ts ADDED
@@ -0,0 +1,13 @@
1
+ import * as AiTool from "@effect/ai/AiTool"
2
+ import * as Effect from "effect/Effect"
3
+ import * as Option from "effect/Option"
4
+ import type { Thread } from "../Thread.ts"
5
+ import type { NeverTool } from "../util/NeverTool.ts"
6
+ import { Self } from "./Self.ts"
7
+
8
+ export const disable = (tool: AiTool.Any): Effect.Effect<void, never, Thread> =>
9
+ Effect.map(Self, ({ tools }) => {
10
+ if (Option.isSome(tools)) {
11
+ tools.value.delete(tool as NeverTool)
12
+ }
13
+ })
package/L/enable.ts ADDED
@@ -0,0 +1,24 @@
1
+ import type { AiTool, AnyStructSchema, Handler } from "@effect/ai/AiTool"
2
+ import * as Effect from "effect/Effect"
3
+ import * as Option from "effect/Option"
4
+ import type { Schema } from "effect/Schema"
5
+ import type { Thread } from "../Thread.ts"
6
+ import type { NeverTool } from "../util/NeverTool.ts"
7
+ import { Self } from "./Self.ts"
8
+
9
+ export const enable = <
10
+ K extends string,
11
+ E extends Schema.All,
12
+ R,
13
+ >(
14
+ tool: AiTool<K, AnyStructSchema, Schema.Any, E, R>,
15
+ ): Effect.Effect<void, E, Handler<K> | Thread | R> =>
16
+ Effect.map(Self, (thread) => {
17
+ const tool_: NeverTool = tool as never
18
+ Option.match(thread.tools, {
19
+ onSome: (value) => value.add(tool_),
20
+ onNone: () => {
21
+ thread.tools = Option.some(new Set([tool_]))
22
+ },
23
+ })
24
+ })
package/L/events.ts ADDED
@@ -0,0 +1,11 @@
1
+ import * as Effect from "effect/Effect"
2
+ import * as Stream from "effect/Stream"
3
+ import type { LEvent } from "../LEvent.ts"
4
+ import type { Thread } from "../Thread.ts"
5
+ import { Self } from "./Self.ts"
6
+
7
+ /** A stream of thread events. */
8
+ export const events: Stream.Stream<LEvent, never, Thread> = Self.pipe(
9
+ Effect.map(({ events }) => Stream.fromPubSub(events)),
10
+ Stream.unwrap,
11
+ )
package/L/fqn.ts ADDED
@@ -0,0 +1,9 @@
1
+ import * as Effect from "effect/Effect"
2
+ import * as Option from "effect/Option"
3
+ import { type Thread, ThreadFqn } from "../Thread.ts"
4
+ import { Self } from "./Self.ts"
5
+
6
+ export const fqn = (id: string): Effect.Effect<void, never, Thread> =>
7
+ Effect.map(Self, (thread) => {
8
+ thread.state.fqn = Option.some(ThreadFqn.make(id))
9
+ })
@@ -2,15 +2,16 @@ import * as Effect from "effect/Effect"
2
2
  import type { RuntimeFiber } from "effect/Fiber"
3
3
  import * as Scope from "effect/Scope"
4
4
  import * as Stream from "effect/Stream"
5
- import type { LEvent } from "./LEvent.ts"
6
- import { Strand } from "./Strand.ts"
5
+ import type { LEvent } from "../LEvent.ts"
6
+ import type { Thread } from "../Thread.ts"
7
+ import { Self } from "./Self.ts"
7
8
 
8
- /** Attach an event handler to process the events of the current strand. */
9
+ /** Attach an event handler to process thread events. */
9
10
  export const listen: <A, E, R>(
10
11
  f: (event: LEvent) => Effect.Effect<A, E, R>,
11
- ) => Effect.Effect<RuntimeFiber<void, E>, never, Strand | R | Scope.Scope> = Effect.fnUntraced(function*(f) {
12
+ ) => Effect.Effect<RuntimeFiber<void, E>, never, Thread | R | Scope.Scope> = Effect.fnUntraced(function*(f) {
12
13
  const latch = yield* Effect.makeLatch(false)
13
- const { events } = yield* Strand
14
+ const { events } = yield* Self
14
15
  const dequeue = yield* events.subscribe
15
16
  const fiber = yield* latch.open.pipe(
16
17
  Effect.zipRight(
package/L/messages.ts ADDED
@@ -0,0 +1,10 @@
1
+ import type { Message } from "@effect/ai/AiInput"
2
+ import * as Effect from "effect/Effect"
3
+ import type { Thread } from "../Thread.ts"
4
+ import { Self } from "./Self.ts"
5
+
6
+ /** Get a copy of the current list of messages. */
7
+ export const messages: Effect.Effect<Array<Message>, never, Thread> = Effect.map(
8
+ Self,
9
+ ({ state: { messages: [...messages] } }) => messages,
10
+ )
package/L/raw.ts ADDED
@@ -0,0 +1,30 @@
1
+ import * as Effect from "effect/Effect"
2
+ import { normalizeRaw } from "../util/normalizeRaw.ts"
3
+ import type { TaggableArg0 } from "../util/Taggable.ts"
4
+
5
+ export const raw: <
6
+ A0 extends TaggableArg0 | Effect.Effect<string | undefined, any, any>,
7
+ L extends Array<unknown>,
8
+ >(
9
+ a0: A0,
10
+ ...aRest: L
11
+ ) => Effect.Effect<
12
+ string | (undefined extends A0 ? undefined : never)
13
+ > = Effect.fnUntraced(function*(a0, ...aRest) {
14
+ const a0_: TaggableArg0 = Effect.isEffect(a0)
15
+ ? yield* a0 as Effect.Effect<TaggableArg0>
16
+ : a0
17
+ if (!a0_) return undefined as never
18
+ const aRest_ = yield* Effect.all(
19
+ aRest.map((v) =>
20
+ Effect.isEffect(v)
21
+ ? v
22
+ : Effect.succeed(v)
23
+ ),
24
+ ) as never as Effect.Effect<Array<unknown>>
25
+ return typeof a0_ === "string"
26
+ ? aRest_.length === 0
27
+ ? a0_
28
+ : [a0_, ...aRest_].join("")
29
+ : normalizeRaw(a0_, aRest_)
30
+ })
@@ -1,7 +1,7 @@
1
1
  import * as Effect from "effect/Effect"
2
- import type { Sequence } from "./util/Sequence"
2
+ import type { Sequencer } from "../util/Sequencer.ts"
3
3
 
4
- export const sequence: Sequence = (...steps) =>
4
+ export const sequence: Sequencer = (...steps) =>
5
5
  Effect.all(steps, {
6
6
  concurrency: 1,
7
7
  }).pipe(
package/L/system.ts ADDED
@@ -0,0 +1,14 @@
1
+ import * as Effect from "effect/Effect"
2
+ import * as Option from "effect/Option"
3
+ import type { Thread } from "../Thread.ts"
4
+ import type { Taggable } from "../util/Taggable.ts"
5
+ import { raw } from "./raw.ts"
6
+ import { Self } from "./Self.ts"
7
+
8
+ /** Set the thread's system instruction. */
9
+ export const system: Taggable<Option.Option<string>, never, Thread> = Effect.fnUntraced(function*(a0, ...aRest) {
10
+ const { state } = yield* Self
11
+ const { system } = state
12
+ state.system = a0 ? Option.some(yield* raw(a0, ...aRest)) : Option.none()
13
+ return system
14
+ })
package/L/thread.ts ADDED
@@ -0,0 +1,28 @@
1
+ import * as Effect from "effect/Effect"
2
+ import { flow } from "effect/Function"
3
+ import * as Option from "effect/Option"
4
+ import * as PubSub from "effect/PubSub"
5
+ import type { LEvent } from "../LEvent.ts"
6
+ import { Thread, ThreadState } from "../Thread.ts"
7
+ import type { Sequencer } from "../util/Sequencer.ts"
8
+ import { Self } from "./Self.ts"
9
+ import { sequence } from "./sequence.ts"
10
+
11
+ export interface thread extends Sequencer<Thread>, Effect.Effect<Thread> {}
12
+
13
+ const thread_ = Effect.gen(function*() {
14
+ return Thread({
15
+ parent: yield* Effect.serviceOption(Self),
16
+ events: yield* PubSub.unbounded<LEvent>(),
17
+ state: ThreadState.default(),
18
+ tools: Option.none(),
19
+ })
20
+ })
21
+
22
+ export const thread: thread = Object.assign(
23
+ flow(
24
+ sequence,
25
+ Effect.provideServiceEffect(Self, thread_),
26
+ ),
27
+ thread_,
28
+ )
package/L/to.ts ADDED
@@ -0,0 +1,4 @@
1
+ import { Envelope } from "../Envelope.ts"
2
+ import type { Thread } from "../Thread.ts"
3
+
4
+ export const to = (...to: Array<Thread>): Envelope => Envelope({ to })
package/L/toolkit.ts ADDED
@@ -0,0 +1,14 @@
1
+ import * as AiToolkit from "@effect/ai/AiToolkit"
2
+ import * as Effect from "effect/Effect"
3
+ import * as Option from "effect/Option"
4
+ import type { NeverTool } from "../util/NeverTool.ts"
5
+ import { Self } from "./Self.ts"
6
+
7
+ export const toolkit = Self.pipe(
8
+ Effect.flatMap(({ tools }) =>
9
+ Option.match(tools, {
10
+ onSome: (tools) => AiToolkit.make(...tools),
11
+ onNone: () => AiToolkit.make<ReadonlyArray<NeverTool>>(),
12
+ })
13
+ ),
14
+ )
@@ -2,7 +2,7 @@ import { TextPart, UserMessage } from "@effect/ai/AiInput"
2
2
  import { expect, it } from "@effect/vitest"
3
3
  import * as Effect from "effect/Effect"
4
4
  import { messages } from "./messages.ts"
5
- import { strand } from "./strand_.ts"
5
+ import { thread } from "./thread.ts"
6
6
  import { user } from "./user.ts"
7
7
 
8
8
  it.effect("test success", () =>
@@ -26,5 +26,5 @@ it.effect("test success", () =>
26
26
  }),
27
27
  ])
28
28
  }).pipe(
29
- strand,
29
+ thread,
30
30
  ))
@@ -1,12 +1,13 @@
1
1
  import { TextPart, UserMessage } from "@effect/ai/AiInput"
2
2
  import * as Effect from "effect/Effect"
3
+ import type { Thread } from "../Thread.ts"
4
+ import type { Taggable } from "../util/Taggable.ts"
3
5
  import { append } from "./append.ts"
4
- import type { Strand } from "./Strand.ts"
5
- import { normalize, type Taggable } from "./util/Taggable.ts"
6
+ import { raw } from "./raw.ts"
6
7
 
7
8
  /** Append a user message to the conversation. */
8
- export const user: Taggable<void, never, Strand> = (a0, ...aRest) =>
9
- normalize(a0, aRest).pipe(
9
+ export const user: Taggable<void, never, Thread> = (a0, ...aRest) =>
10
+ raw(a0, ...aRest).pipe(
10
11
  Effect.flatMap((text) =>
11
12
  text
12
13
  ? append(
@@ -1,14 +1,14 @@
1
1
  import * as Effect from "effect/Effect"
2
2
  import type * as Schema from "effect/Schema"
3
- import type * as Strand from "./Strand.ts"
3
+ import type { Thread } from "../Thread.ts"
4
+ import { encodeJsonc, type JsonValue } from "../util/JsonValue.ts"
4
5
  import { user } from "./user.ts"
5
- import { encodeJsonc, type JsonValue } from "./util/JsonValue.ts"
6
6
 
7
7
  /** Stringify and append some JSON as a user message to the conversation. */
8
8
  export const userJson: <A, I extends JsonValue>(
9
9
  value: A,
10
10
  schema?: Schema.Schema<A, I>,
11
- ) => Effect.Effect<void, never, Strand.Strand> = Effect.fnUntraced(function*(value, schema) {
11
+ ) => Effect.Effect<void, never, Thread> = Effect.fnUntraced(function*(value, schema) {
12
12
  const encoded = schema ? encodeJsonc(schema)(value) : JSON.stringify(value, null, 2)
13
- return yield* user`\`\`\`json${schema ? "c" : ""}${"\n"}${encoded}${"\n"}\`\`\``
13
+ return yield* user`\`\`\`json${schema ? "c" : ""}\n${encoded}\n\`\`\``
14
14
  })
package/LEvent.ts CHANGED
@@ -3,12 +3,12 @@ import * as Schema from "effect/Schema"
3
3
 
4
4
  export class Messages extends Schema.Array(Message) {}
5
5
 
6
- /** An event in which one or more messages were added to the current strand's message list. */
6
+ /** An event in which one or more messages were added to the thread. */
7
7
  export class MessagesAppended extends Schema.TaggedClass<MessagesAppended>("MessagesAppended")("MessagesAppended", {
8
8
  messages: Messages,
9
9
  }) {}
10
10
 
11
- /** An event in which the current strand's message list is cleared. */
11
+ /** An event in which the thread is cleared of messages. */
12
12
  export class MessagesCleared extends Schema.TaggedClass<MessagesCleared>("MessagesCleared")("MessagesCleared", {
13
13
  cleared: Messages,
14
14
  }) {}
package/Thread.ts ADDED
@@ -0,0 +1,69 @@
1
+ import { Message } from "@effect/ai/AiInput"
2
+ import * as Effect from "effect/Effect"
3
+ import * as Option from "effect/Option"
4
+ import { type Pipeable, pipeArguments } from "effect/Pipeable"
5
+ import * as PubSub from "effect/PubSub"
6
+ import * as Schema from "effect/Schema"
7
+ import type { Mutable } from "effect/Types"
8
+ import { Self } from "./L/Self.ts"
9
+ import { sequence } from "./L/sequence.ts"
10
+ import type { LEvent } from "./LEvent.ts"
11
+ import type { NeverTool } from "./util/NeverTool.ts"
12
+ import { prefix } from "./util/prefix.ts"
13
+ import type { Sequencer } from "./util/Sequencer.ts"
14
+
15
+ export const ThreadFqnTypeId: unique symbol = Symbol.for(prefix("ThreadFqn"))
16
+ export const ThreadFqn = Schema.String.pipe(Schema.brand(ThreadFqnTypeId))
17
+ export type ThreadFqn = typeof ThreadFqn["Type"]
18
+
19
+ export class ThreadState extends Schema.Class<ThreadState>(prefix("ThreadState"))({
20
+ /** The key with which the thread is referenced by others. */
21
+ fqn: Schema.Option(ThreadFqn),
22
+ /** The system prompt to be passed along to the model. */
23
+ system: Schema.Option(Schema.String),
24
+ /** The messages based off of which the model infers the next message. */
25
+ messages: Schema.Array(Message).pipe(Schema.mutable),
26
+ }) {
27
+ static default = (): ThreadState =>
28
+ ThreadState.make({
29
+ fqn: Option.none(),
30
+ system: Option.none(),
31
+ messages: [],
32
+ })
33
+ }
34
+
35
+ export interface ThreadInit {
36
+ /** The parent thread. */
37
+ parent: Option.Option<Thread>
38
+ /** The pubsub with which thread-specific events are emitted. */
39
+ events: PubSub.PubSub<LEvent>
40
+ /** The state of the current thread. */
41
+ state: Mutable<ThreadState>
42
+ /** The tools to be made accessible to the model. */
43
+ tools: Option.Option<Set<NeverTool>>
44
+ }
45
+
46
+ export const ThreadTypeId: unique symbol = Symbol.for(prefix("Thread"))
47
+ export type ThreadTypeId = typeof ThreadTypeId
48
+
49
+ interface ThreadMembers extends ThreadInit, Pipeable {
50
+ readonly [ThreadTypeId]: ThreadTypeId
51
+ }
52
+
53
+ /** A conversation isolate. */
54
+ export interface Thread extends Sequencer<Thread>, ThreadMembers {}
55
+
56
+ export const Thread = (init: ThreadInit): Thread => {
57
+ const members = {
58
+ [ThreadTypeId]: ThreadTypeId,
59
+ ...init,
60
+ pipe() {
61
+ return pipeArguments(self, arguments)
62
+ },
63
+ } satisfies ThreadMembers
64
+ const self = Object.assign(
65
+ ((...args) => sequence(...args).pipe(Effect.provideService(Self, self))) satisfies Sequencer<Thread>,
66
+ members,
67
+ ) as Thread
68
+ return self
69
+ }
@@ -0,0 +1,15 @@
1
+ import type { Thread } from "./Thread.ts";
2
+ import { type Taggable } from "./util/Taggable.ts";
3
+ export interface MessageHeaders {
4
+ readonly to: Array<Thread>;
5
+ readonly cc?: Array<Thread> | undefined;
6
+ readonly bcc?: Array<Thread> | undefined;
7
+ }
8
+ export interface EnvelopeMembers {
9
+ readonly headers: MessageHeaders;
10
+ readonly cc: (...cc: Array<Thread>) => Envelope;
11
+ readonly bcc: (...bcc: Array<Thread>) => Envelope;
12
+ }
13
+ export interface Envelope extends Taggable<void, never, Thread>, EnvelopeMembers {
14
+ }
15
+ export declare const Envelope: (headers: MessageHeaders) => Envelope;