clanka 0.0.28 → 0.1.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 (73) hide show
  1. package/dist/Agent.d.ts +87 -40
  2. package/dist/Agent.d.ts.map +1 -1
  3. package/dist/Agent.js +154 -94
  4. package/dist/Agent.js.map +1 -1
  5. package/dist/AgentExecutor.d.ts +165 -0
  6. package/dist/AgentExecutor.d.ts.map +1 -0
  7. package/dist/AgentExecutor.js +121 -0
  8. package/dist/AgentExecutor.js.map +1 -0
  9. package/dist/AgentTools.d.ts +14 -132
  10. package/dist/AgentTools.d.ts.map +1 -1
  11. package/dist/AgentTools.js +10 -12
  12. package/dist/AgentTools.js.map +1 -1
  13. package/dist/OutputFormatter.d.ts.map +1 -1
  14. package/dist/OutputFormatter.js +18 -0
  15. package/dist/OutputFormatter.js.map +1 -1
  16. package/dist/WebToMarkdown.d.ts.map +1 -1
  17. package/dist/WebToMarkdown.js +1 -1
  18. package/dist/WebToMarkdown.js.map +1 -1
  19. package/dist/WebToMarkdown.test.d.ts +2 -0
  20. package/dist/WebToMarkdown.test.d.ts.map +1 -0
  21. package/dist/WebToMarkdown.test.js +37 -0
  22. package/dist/WebToMarkdown.test.js.map +1 -0
  23. package/dist/index.d.ts +4 -4
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +4 -4
  26. package/dist/index.js.map +1 -1
  27. package/package.json +3 -3
  28. package/src/Agent.ts +290 -215
  29. package/src/AgentExecutor.ts +245 -0
  30. package/src/AgentTools.ts +25 -22
  31. package/src/OutputFormatter.ts +18 -0
  32. package/src/WebToMarkdown.test.ts +65 -0
  33. package/src/WebToMarkdown.ts +1 -0
  34. package/src/index.ts +4 -4
  35. package/dist/Agent.test.d.ts +0 -2
  36. package/dist/Agent.test.d.ts.map +0 -1
  37. package/dist/Agent.test.js +0 -111
  38. package/dist/Agent.test.js.map +0 -1
  39. package/dist/AgentTools.test.d.ts +0 -2
  40. package/dist/AgentTools.test.d.ts.map +0 -1
  41. package/dist/AgentTools.test.js +0 -714
  42. package/dist/AgentTools.test.js.map +0 -1
  43. package/dist/DuckDuckGo.d.ts +0 -28
  44. package/dist/DuckDuckGo.d.ts.map +0 -1
  45. package/dist/DuckDuckGo.js +0 -131
  46. package/dist/DuckDuckGo.js.map +0 -1
  47. package/dist/Executor.d.ts +0 -20
  48. package/dist/Executor.d.ts.map +0 -1
  49. package/dist/Executor.js +0 -98
  50. package/dist/Executor.js.map +0 -1
  51. package/dist/GithubCopilot.d.ts +0 -11
  52. package/dist/GithubCopilot.d.ts.map +0 -1
  53. package/dist/GithubCopilot.js +0 -14
  54. package/dist/GithubCopilot.js.map +0 -1
  55. package/dist/GithubCopilotAuth.d.ts +0 -57
  56. package/dist/GithubCopilotAuth.d.ts.map +0 -1
  57. package/dist/GithubCopilotAuth.js +0 -218
  58. package/dist/GithubCopilotAuth.js.map +0 -1
  59. package/dist/GithubCopilotAuth.test.d.ts +0 -2
  60. package/dist/GithubCopilotAuth.test.d.ts.map +0 -1
  61. package/dist/GithubCopilotAuth.test.js +0 -267
  62. package/dist/GithubCopilotAuth.test.js.map +0 -1
  63. package/dist/MockSearch.d.ts +0 -12
  64. package/dist/MockSearch.d.ts.map +0 -1
  65. package/dist/MockSearch.js +0 -4
  66. package/dist/MockSearch.js.map +0 -1
  67. package/dist/ai.d.ts +0 -2
  68. package/dist/ai.d.ts.map +0 -1
  69. package/dist/ai.js +0 -29
  70. package/dist/ai.js.map +0 -1
  71. package/dist/examples/cli.mjs +0 -68514
  72. package/src/Agent.test.ts +0 -159
  73. package/src/Executor.ts +0 -151
package/src/Agent.ts CHANGED
@@ -3,17 +3,16 @@
3
3
  */
4
4
  import {
5
5
  Array,
6
- Deferred,
7
6
  Effect,
8
- FileSystem,
9
7
  identity,
10
8
  Layer,
9
+ MutableRef,
11
10
  Option,
12
- Path,
13
11
  pipe,
14
12
  Queue,
15
13
  Schema,
16
14
  Scope,
15
+ Semaphore,
17
16
  ServiceMap,
18
17
  Stream,
19
18
  } from "effect"
@@ -24,26 +23,62 @@ import {
24
23
  Tool,
25
24
  Toolkit,
26
25
  } from "effect/unstable/ai"
27
- import {
28
- AgentToolHandlers,
29
- AgentTools,
30
- CurrentDirectory,
31
- SubagentContext,
32
- TaskCompleteDeferred,
33
- } from "./AgentTools.ts"
34
- import { Executor } from "./Executor.ts"
35
- import { ToolkitRenderer } from "./ToolkitRenderer.ts"
36
26
  import { ModelName, ProviderName } from "effect/unstable/ai/Model"
37
27
  import { type StreamPart } from "effect/unstable/ai/Response"
38
- import type { ChildProcessSpawner } from "effect/unstable/process"
39
- import type { HttpClient } from "effect/unstable/http"
28
+ import * as AgentExecutor from "./AgentExecutor.ts"
29
+ import type { Path } from "effect/Path"
30
+ import type { ChildProcessSpawner } from "effect/unstable/process/ChildProcessSpawner"
31
+ import type { HttpClient } from "effect/unstable/http/HttpClient"
32
+ import type {
33
+ CurrentDirectory,
34
+ SubagentExecutor,
35
+ TaskCompleter,
36
+ } from "./AgentTools.ts"
37
+ import type { FileSystem } from "effect/FileSystem"
38
+
39
+ /**
40
+ * @since 1.0.0
41
+ * @category Models
42
+ */
43
+ export type TypeId = "~clanka/Agent"
44
+
45
+ /**
46
+ * @since 1.0.0
47
+ * @category Models
48
+ */
49
+ export const TypeId: TypeId = "~clanka/Agent"
40
50
 
41
51
  /**
42
52
  * @since 1.0.0
43
53
  * @category Models
44
54
  */
45
55
  export interface Agent {
46
- readonly output: Stream.Stream<Output, AgentFinished | AiError.AiError>
56
+ readonly [TypeId]: TypeId
57
+
58
+ /**
59
+ * Send a prompt to the agent and receive a stream of output.
60
+ */
61
+ send(options: {
62
+ /**
63
+ * The prompt to send to the agent.
64
+ */
65
+ readonly prompt: Prompt.RawInput
66
+ /**
67
+ * Provide additional system instructions, or a function that generates
68
+ * system instructions based on the tool instructions
69
+ */
70
+ readonly system?:
71
+ | string
72
+ | ((options: {
73
+ readonly toolInstructions: string
74
+ readonly agentsMd: string
75
+ }) => string)
76
+ | undefined
77
+ }): Effect.Effect<
78
+ Stream.Stream<Output, AgentFinished | AiError.AiError>,
79
+ never,
80
+ Scope.Scope | LanguageModel.LanguageModel | ProviderName | ModelName
81
+ >
47
82
 
48
83
  /**
49
84
  * Send a message to the agent to steer its behavior. This is useful for
@@ -57,89 +92,26 @@ export interface Agent {
57
92
  }
58
93
 
59
94
  /**
60
- * Start an agent in the given directory with the given prompt and tools.
61
- *
95
+ * @since 1.0.0
96
+ * @category Service
97
+ */
98
+ export const Agent = ServiceMap.Service<Agent>("clanka/Agent")
99
+
100
+ /**
62
101
  * @since 1.0.0
63
102
  * @category Constructors
64
103
  */
65
- export const make: <
66
- // oxlint-disable-next-line typescript/no-explicit-any
67
- Toolkit extends Toolkit.Any = never,
68
- SE = never,
69
- SR = never,
70
- >(options: {
71
- /** The working directory to run the agent in */
72
- readonly directory: string
73
- /** The prompt to use for the agent */
74
- readonly prompt: Prompt.RawInput
75
- /**
76
- * Provide additional system instructions, or a function that generates
77
- * system instructions based on the tool instructions
78
- */
79
- readonly system?:
80
- | string
81
- | ((options: {
82
- readonly toolInstructions: string
83
- readonly agentsMd: string
84
- }) => string)
85
- | undefined
86
- /** Additional tools to provide to the agent */
87
- readonly tools?: Toolkit | undefined
88
- /** Layer to use for subagents */
89
- readonly subagentModel?:
90
- | Layer.Layer<
91
- LanguageModel.LanguageModel | ProviderName | ModelName,
92
- SE,
93
- SR
94
- >
95
- | undefined
96
- }) => Effect.Effect<
104
+ export const make = Effect.gen(function* (): Effect.fn.Return<
97
105
  Agent,
98
106
  never,
99
- | Scope.Scope
100
- | FileSystem.FileSystem
101
- | Path.Path
102
- | Executor
103
- | LanguageModel.LanguageModel
104
- | ProviderName
105
- | ModelName
106
- | ToolkitRenderer
107
- | (Toolkit extends Toolkit.Toolkit<infer T>
108
- ? Tool.HandlersFor<T> | Tool.HandlerServices<T[keyof T]>
109
- : never)
110
- | Tool.HandlersFor<typeof AgentTools.tools>
111
- | SR
112
- > = Effect.fnUntraced(function* (options: {
113
- readonly directory: string
114
- readonly prompt: Prompt.RawInput
115
- readonly system?:
116
- | string
117
- | ((options: { readonly toolInstructions: string }) => string)
118
- | undefined
119
- readonly tools?: Toolkit.Toolkit<{}> | undefined
120
- readonly subagentModel?:
121
- | Layer.Layer<LanguageModel.LanguageModel | ProviderName | ModelName>
122
- | undefined
123
- }) {
124
- const fs = yield* FileSystem.FileSystem
125
- const pathService = yield* Path.Path
126
- const executor = yield* Executor
127
- const renderer = yield* ToolkitRenderer
128
- const generateSystem =
129
- typeof options.system === "function" ? options.system : defaultSystem
130
-
131
- const allTools = Toolkit.merge(AgentTools, options.tools ?? Toolkit.empty)
132
- const allToolsDts = renderer.render(allTools)
133
- const tools = yield* allTools
107
+ Scope.Scope | AgentExecutor.AgentExecutor
108
+ > {
109
+ const executor = yield* AgentExecutor.AgentExecutor
110
+
134
111
  const singleTool = yield* SingleTools.asEffect().pipe(
135
112
  Effect.provide(SingleToolHandlers),
136
113
  )
137
- const services = yield* Effect.services<
138
- | Tool.HandlerServices<{}>
139
- | LanguageModel.LanguageModel
140
- | ProviderName
141
- | ModelName
142
- >()
114
+ const toolsDts = yield* executor.toolsDts
143
115
 
144
116
  const pendingMessages = new Set<{
145
117
  readonly message: string
@@ -147,9 +119,10 @@ export const make: <
147
119
  }>()
148
120
 
149
121
  const agentsMd = yield* pipe(
150
- fs.readFileString(pathService.resolve(options.directory, "AGENTS.md")),
122
+ executor.agentsMd,
151
123
  Effect.map(
152
- (content) => `# AGENTS.md
124
+ Option.map(
125
+ (content) => `# AGENTS.md
153
126
 
154
127
  The following instructions are from ./AGENTS.md in the current directory.
155
128
  You do not need to read this file again.
@@ -159,8 +132,8 @@ You do not need to read this file again.
159
132
  <!-- AGENTS.md start -->
160
133
  ${content}
161
134
  <!-- AGENTS.md end -->`,
135
+ ),
162
136
  ),
163
- Effect.option,
164
137
  )
165
138
 
166
139
  let agentCounter = 0
@@ -168,27 +141,49 @@ ${content}
168
141
  const outputBuffer = new Map<number, Array<Output>>()
169
142
  let currentOutputAgent: number | null = null
170
143
 
171
- const spawn: (
172
- agentId: number,
173
- prompt: Prompt.Prompt,
174
- ) => Stream.Stream<
144
+ let history = MutableRef.make(Prompt.empty)
145
+
146
+ const spawn: (opts: {
147
+ readonly agentId: number
148
+ readonly prompt: Prompt.Prompt
149
+ readonly system?:
150
+ | string
151
+ | ((options: {
152
+ readonly toolInstructions: string
153
+ readonly agentsMd: string
154
+ }) => string)
155
+ | undefined
156
+ readonly disableHistory?: boolean | undefined
157
+ }) => Stream.Stream<
175
158
  Output,
176
159
  AgentFinished | AiError.AiError,
177
- LanguageModel.LanguageModel | ProviderName
178
- > = Effect.fnUntraced(function* (agentId, prompt) {
160
+ LanguageModel.LanguageModel | ProviderName | ModelName
161
+ > = Effect.fnUntraced(function* (opts) {
162
+ const agentId = opts.agentId
179
163
  const ai = yield* LanguageModel.LanguageModel
164
+ const subagentModel = yield* Effect.serviceOption(SubagentModel)
180
165
  const modelConfig = yield* AgentModelConfig
166
+ const services = yield* Effect.services<
167
+ LanguageModel.LanguageModel | ProviderName | ModelName
168
+ >()
169
+ let finalSummary = Option.none<string>()
170
+
181
171
  const singleToolMode = modelConfig.supportsNoTools !== true
182
- const deferred = yield* Deferred.make<string>()
183
172
  const output = yield* Queue.make<Output, AgentFinished | AiError.AiError>()
173
+ const prompt = opts.disableHistory ? MutableRef.make(Prompt.empty) : history
174
+
175
+ MutableRef.update(prompt, Prompt.concat(opts.prompt))
176
+
177
+ const generateSystem =
178
+ typeof opts.system === "function" ? opts.system : defaultSystem
184
179
 
185
- const toolInstructions = generateSystemTools(allToolsDts, !singleToolMode)
180
+ const toolInstructions = generateSystemTools(toolsDts, !singleToolMode)
186
181
  let system = generateSystem({
187
182
  toolInstructions,
188
183
  agentsMd: Option.getOrElse(agentsMd, () => ""),
189
184
  })
190
- if (typeof options.system === "string") {
191
- system += `\n${options.system}\n`
185
+ if (typeof opts.system === "string") {
186
+ system += `\n${opts.system}\n`
192
187
  }
193
188
 
194
189
  function maybeSend(options: {
@@ -197,10 +192,10 @@ ${content}
197
192
  readonly acquire?: boolean
198
193
  readonly release?: boolean
199
194
  }) {
200
- if (currentOutputAgent === null || currentOutputAgent === agentId) {
195
+ if (currentOutputAgent === null || currentOutputAgent === opts.agentId) {
201
196
  Queue.offerUnsafe(output, options.part)
202
197
  if (options.acquire) {
203
- currentOutputAgent = agentId
198
+ currentOutputAgent = opts.agentId
204
199
  }
205
200
  if (options.release) {
206
201
  currentOutputAgent = null
@@ -219,83 +214,88 @@ ${content}
219
214
  }
220
215
  return
221
216
  }
222
- let state = outputBuffer.get(agentId)
217
+ let state = outputBuffer.get(opts.agentId)
223
218
  if (!state) {
224
219
  state = []
225
- outputBuffer.set(agentId, state)
220
+ outputBuffer.set(opts.agentId, state)
226
221
  }
227
222
  state.push(options.part)
228
223
  return
229
224
  }
230
225
 
231
- const taskServices = SubagentContext.serviceMap({
232
- spawn: ({ prompt }) => {
226
+ const spawnSubagent = Effect.fnUntraced(
227
+ function* (prompt: string) {
233
228
  let id = agentCounter++
234
- const stream = spawn(
235
- id,
236
- Prompt.make(`You have been asked using the "delegate" function to complete the following task. Avoid using the "delegate" function yourself unless strictly necessary:
229
+ const stream = spawn({
230
+ agentId: id,
231
+ prompt:
232
+ Prompt.make(`You have been asked using the "delegate" function to complete the following task. Try to avoid using the "delegate" function yourself unless strictly necessary:
237
233
 
238
234
  ${prompt}`),
239
- )
240
- return Effect.gen(function* () {
241
- const provider = yield* ProviderName
242
- const model = yield* ModelName
243
- maybeSend({
244
- agentId,
245
- part: new SubagentStart({ id, prompt, model, provider }),
246
- release: true,
247
- })
248
- return yield* stream.pipe(
249
- Stream.runForEachArray((parts) => {
250
- for (const part of parts) {
251
- switch (part._tag) {
252
- case "SubagentStart":
253
- case "SubagentComplete":
254
- case "SubagentPart":
255
- Queue.offerUnsafe(output, part)
256
- break
235
+ system: opts.system,
236
+ disableHistory: true,
237
+ })
238
+ const provider = yield* ProviderName
239
+ const model = yield* ModelName
240
+ maybeSend({
241
+ agentId: opts.agentId,
242
+ part: new SubagentStart({ id, prompt, model, provider }),
243
+ release: true,
244
+ })
245
+ return yield* stream.pipe(
246
+ Stream.runForEachArray((parts) => {
247
+ for (const part of parts) {
248
+ switch (part._tag) {
249
+ case "AgentStart":
250
+ break
251
+ case "SubagentStart":
252
+ case "SubagentComplete":
253
+ case "SubagentPart":
254
+ Queue.offerUnsafe(output, part)
255
+ break
257
256
 
258
- default:
259
- Queue.offerUnsafe(output, new SubagentPart({ id, part }))
260
- break
261
- }
257
+ default:
258
+ Queue.offerUnsafe(output, new SubagentPart({ id, part }))
259
+ break
262
260
  }
263
- return Effect.void
264
- }),
265
- Effect.as(""),
266
- Effect.catchTag("AgentFinished", (finished) => {
267
- Queue.offerUnsafe(
268
- output,
269
- new SubagentComplete({ id, summary: finished.summary }),
270
- )
271
- return Effect.succeed(finished.summary)
272
- }),
273
- Effect.orDie,
274
- )
275
- }).pipe(
276
- options.subagentModel
277
- ? Effect.provide(Layer.orDie(options.subagentModel))
278
- : Effect.provideServices(services),
261
+ }
262
+ return Effect.void
263
+ }),
264
+ Effect.as(""),
265
+ Effect.catchTag("AgentFinished", (finished) => {
266
+ Queue.offerUnsafe(
267
+ output,
268
+ new SubagentComplete({ id, summary: finished.summary }),
269
+ )
270
+ return Effect.succeed(finished.summary)
271
+ }),
272
+ Effect.orDie,
279
273
  )
280
274
  },
281
- }).pipe(
282
- ServiceMap.add(CurrentDirectory, options.directory),
283
- ServiceMap.add(TaskCompleteDeferred, deferred),
275
+ Option.isSome(subagentModel)
276
+ ? Effect.provideServices(subagentModel.value)
277
+ : Effect.provideServices(services),
284
278
  )
285
279
 
286
280
  const executeScript = Effect.fnUntraced(function* (script: string) {
287
281
  maybeSend({ agentId, part: new ScriptEnd(), release: true })
288
282
  const output = yield* pipe(
289
- executor.execute({ tools, script }),
283
+ executor.execute({
284
+ script,
285
+ onSubagent: spawnSubagent,
286
+ onTaskComplete: (summary) =>
287
+ Effect.sync(() => {
288
+ finalSummary = Option.some(summary)
289
+ }),
290
+ }),
290
291
  Stream.mkString,
291
- Effect.provideServices(taskServices),
292
292
  )
293
293
  maybeSend({ agentId, part: new ScriptOutput({ output }) })
294
294
  return output
295
295
  })
296
296
 
297
297
  if (!modelConfig.systemPromptTransform) {
298
- prompt = Prompt.setSystem(prompt, system)
298
+ MutableRef.update(prompt, Prompt.setSystem(system))
299
299
  }
300
300
 
301
301
  let currentScript = ""
@@ -303,33 +303,40 @@ ${prompt}`),
303
303
  while (true) {
304
304
  if (!singleToolMode && currentScript.length > 0) {
305
305
  const result = yield* executeScript(currentScript)
306
- prompt = Prompt.concat(prompt, [
307
- {
308
- role: modelConfig.supportsAssistantPrefill ? "assistant" : "user",
309
- content: `Javascript output:\n\n${result}`,
310
- },
311
- ])
306
+ MutableRef.update(
307
+ prompt,
308
+ Prompt.concat([
309
+ {
310
+ role: modelConfig.supportsAssistantPrefill
311
+ ? "assistant"
312
+ : "user",
313
+ content: `Javascript output:\n\n${result}`,
314
+ },
315
+ ]),
316
+ )
312
317
  currentScript = ""
313
318
  }
314
319
 
315
- if (Deferred.isDoneUnsafe(deferred)) {
320
+ if (Option.isSome(finalSummary)) {
316
321
  yield* Queue.fail(
317
322
  output,
318
- new AgentFinished({ summary: yield* Deferred.await(deferred) }),
323
+ new AgentFinished({ summary: finalSummary.value }),
319
324
  )
320
325
  return
321
326
  }
322
327
 
323
328
  if (pendingMessages.size > 0) {
324
- prompt = Prompt.concat(
329
+ MutableRef.update(
325
330
  prompt,
326
- Array.Array.from(pendingMessages, ({ message, resume }) => {
327
- resume(Effect.void)
328
- return {
329
- role: "user",
330
- content: message,
331
- }
332
- }),
331
+ Prompt.concat(
332
+ Array.Array.from(pendingMessages, ({ message, resume }) => {
333
+ resume(Effect.void)
334
+ return {
335
+ role: "user",
336
+ content: message,
337
+ }
338
+ }),
339
+ ),
333
340
  )
334
341
  pendingMessages.clear()
335
342
  }
@@ -340,7 +347,9 @@ ${prompt}`),
340
347
  let hadReasoningDelta = false
341
348
  yield* pipe(
342
349
  ai.streamText(
343
- singleToolMode ? { prompt, toolkit: singleTool } : { prompt },
350
+ singleToolMode
351
+ ? { prompt: prompt.current, toolkit: singleTool }
352
+ : { prompt: prompt.current },
344
353
  ),
345
354
  Stream.takeUntil((part) => {
346
355
  if (
@@ -461,35 +470,45 @@ ${prompt}`),
461
470
  ? (effect) => modelConfig.systemPromptTransform!(system, effect)
462
471
  : identity,
463
472
  )
464
- prompt = Prompt.concat(prompt, Prompt.fromResponseParts(response))
473
+ MutableRef.update(
474
+ prompt,
475
+ Prompt.concat(Prompt.fromResponseParts(response)),
476
+ )
465
477
  currentScript = currentScript.trim()
466
478
  }
467
479
  }).pipe(
468
- Effect.provideServices(
469
- taskServices.pipe(
470
- ServiceMap.add(ScriptExecutor, (script) => {
471
- maybeSend({ agentId, part: new ScriptStart() })
472
- maybeSend({ agentId, part: new ScriptDelta({ delta: script }) })
473
- return executeScript(script)
474
- }),
475
- ),
476
- ),
477
- Effect.provideServices(services),
480
+ Effect.provideService(ScriptExecutor, (script) => {
481
+ maybeSend({ agentId, part: new ScriptStart() })
482
+ maybeSend({ agentId, part: new ScriptDelta({ delta: script }) })
483
+ return executeScript(script)
484
+ }),
478
485
  Effect.catchCause((cause) => Queue.failCause(output, cause)),
479
486
  Effect.forkScoped,
480
487
  )
481
488
 
489
+ yield* Queue.offer(
490
+ output,
491
+ new AgentStart({
492
+ id: opts.agentId,
493
+ prompt: opts.prompt,
494
+ provider: yield* ProviderName,
495
+ model: yield* ModelName,
496
+ }),
497
+ )
498
+
482
499
  return Stream.fromQueue(output)
483
500
  }, Stream.unwrap)
484
501
 
485
- const output = yield* spawn(agentCounter++, Prompt.make(options.prompt)).pipe(
486
- Stream.broadcast({
487
- capacity: "unbounded",
488
- }),
489
- )
502
+ const sendLock = Semaphore.makeUnsafe(1)
490
503
 
491
- return identity<Agent>({
492
- output,
504
+ return Agent.of({
505
+ [TypeId]: TypeId,
506
+ send: (options) =>
507
+ spawn({
508
+ agentId: agentCounter++,
509
+ prompt: Prompt.make(options.prompt),
510
+ system: options.system,
511
+ }).pipe(Stream.broadcast({ capacity: "unbounded" }), sendLock.withPermit),
493
512
  steer: (message) =>
494
513
  Effect.callback((resume) => {
495
514
  const entry = { message, resume }
@@ -497,8 +516,7 @@ ${prompt}`),
497
516
  return Effect.sync(() => pendingMessages.delete(entry))
498
517
  }),
499
518
  })
500
- // oxlint-disable-next-line typescript/no-explicit-any
501
- }) as any
519
+ })
502
520
 
503
521
  const defaultSystem = (options: {
504
522
  readonly toolInstructions: string
@@ -561,6 +579,7 @@ You have the following functions available to you:
561
579
  \`\`\`ts
562
580
  ${toolsDts}
563
581
 
582
+ /** The global Fetch API available for making HTTP requests. */
564
583
  declare const fetch: typeof globalThis.fetch
565
584
  \`\`\``
566
585
  }
@@ -578,6 +597,7 @@ You have the following functions available to you:
578
597
  \`\`\`ts
579
598
  ${toolsDts}
580
599
 
600
+ // The global Fetch API available for making HTTP requests.
581
601
  declare const fetch: typeof globalThis.fetch
582
602
  \`\`\``
583
603
  }
@@ -604,6 +624,59 @@ const SingleToolHandlers = SingleTools.toLayer({
604
624
  }),
605
625
  })
606
626
 
627
+ /**
628
+ * @since 1.0.0
629
+ * @category Layers
630
+ */
631
+ export const layer: Layer.Layer<Agent, never, AgentExecutor.AgentExecutor> =
632
+ Layer.effect(Agent, make)
633
+
634
+ /**
635
+ * Create an Agent layer that uses a local AgentExecutor.
636
+ *
637
+ * @since 1.0.0
638
+ * @category Layers
639
+ */
640
+ export const layerLocal = <Toolkit extends Toolkit.Any = never>(options: {
641
+ readonly directory: string
642
+ readonly tools?: Toolkit | undefined
643
+ }): Layer.Layer<
644
+ Agent,
645
+ never,
646
+ | FileSystem
647
+ | Path
648
+ | ChildProcessSpawner
649
+ | HttpClient
650
+ | Exclude<
651
+ Toolkit extends Toolkit.Toolkit<infer T>
652
+ ? Tool.HandlersFor<T> | Tool.HandlerServices<T[keyof T]>
653
+ : never,
654
+ CurrentDirectory | SubagentExecutor | TaskCompleter
655
+ >
656
+ > => layer.pipe(Layer.provide(AgentExecutor.layerLocal(options)))
657
+
658
+ /**
659
+ * @since 1.0.0
660
+ * @category Subagent model
661
+ */
662
+ export class SubagentModel extends ServiceMap.Service<
663
+ SubagentModel,
664
+ ServiceMap.ServiceMap<LanguageModel.LanguageModel | ProviderName | ModelName>
665
+ >()("clanka/Agent/SubagentModel") {}
666
+
667
+ /**
668
+ * @since 1.0.0
669
+ * @category Subagent model
670
+ */
671
+ export const layerSubagentModel = <E, R>(
672
+ layer: Layer.Layer<
673
+ LanguageModel.LanguageModel | ProviderName | ModelName,
674
+ E,
675
+ R
676
+ >,
677
+ ): Layer.Layer<SubagentModel, E, R> =>
678
+ Layer.effect(SubagentModel, Layer.build(layer))
679
+
607
680
  /**
608
681
  * @since 1.0.0
609
682
  * @category System prompts
@@ -623,19 +696,19 @@ export class AgentModelConfig extends ServiceMap.Reference<{
623
696
  }
624
697
 
625
698
  /**
626
- * A layer that provides most of the common services needed to run an agent.
627
- *
628
699
  * @since 1.0.0
629
- * @category Services
700
+ * @category Output
630
701
  */
631
- export const layerServices: Layer.Layer<
632
- Tool.HandlersFor<typeof AgentTools.tools> | Executor | ToolkitRenderer,
633
- never,
634
- | FileSystem.FileSystem
635
- | Path.Path
636
- | ChildProcessSpawner.ChildProcessSpawner
637
- | HttpClient.HttpClient
638
- > = Layer.mergeAll(AgentToolHandlers, Executor.layer, ToolkitRenderer.layer)
702
+ export class AgentStart extends Schema.TaggedClass<AgentStart>()("AgentStart", {
703
+ id: Schema.Number,
704
+ prompt: Prompt.Prompt,
705
+ provider: Schema.String,
706
+ model: Schema.String,
707
+ }) {
708
+ get modelAndProvider() {
709
+ return `${this.provider}/${this.model}`
710
+ }
711
+ }
639
712
 
640
713
  /**
641
714
  * @since 1.0.0
@@ -706,17 +779,6 @@ export class ScriptOutput extends Schema.TaggedClass<ScriptOutput>()(
706
779
  },
707
780
  ) {}
708
781
 
709
- /**
710
- * @since 1.0.0
711
- * @category Output
712
- */
713
- export class AgentFinished extends Schema.TaggedErrorClass<AgentFinished>()(
714
- "AgentFinished",
715
- {
716
- summary: Schema.String,
717
- },
718
- ) {}
719
-
720
782
  /**
721
783
  * @since 1.0.0
722
784
  * @category Output
@@ -783,6 +845,7 @@ export class SubagentPart extends Schema.TaggedClass<SubagentPart>()(
783
845
  * @category Output
784
846
  */
785
847
  export type Output =
848
+ | AgentStart
786
849
  | ContentPart
787
850
  | SubagentStart
788
851
  | SubagentComplete
@@ -793,6 +856,7 @@ export type Output =
793
856
  * @category Output
794
857
  */
795
858
  export const Output = Schema.Union([
859
+ AgentStart,
796
860
  ReasoningStart,
797
861
  ReasoningDelta,
798
862
  ReasoningEnd,
@@ -804,3 +868,14 @@ export const Output = Schema.Union([
804
868
  SubagentComplete,
805
869
  SubagentPart,
806
870
  ])
871
+
872
+ /**
873
+ * @since 1.0.0
874
+ * @category Output
875
+ */
876
+ export class AgentFinished extends Schema.TaggedErrorClass<AgentFinished>()(
877
+ "AgentFinished",
878
+ {
879
+ summary: Schema.String,
880
+ },
881
+ ) {}