@yolk-sdk/agent 0.0.1-canary.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 (161) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +93 -0
  3. package/dist/client/index.d.mts +3 -0
  4. package/dist/client/index.mjs +3 -0
  5. package/dist/client/state.d.mts +99 -0
  6. package/dist/client/state.d.mts.map +1 -0
  7. package/dist/client/state.mjs +245 -0
  8. package/dist/client/state.mjs.map +1 -0
  9. package/dist/client/transport.d.mts +67 -0
  10. package/dist/client/transport.d.mts.map +1 -0
  11. package/dist/client/transport.mjs +219 -0
  12. package/dist/client/transport.mjs.map +1 -0
  13. package/dist/index.d.mts +1 -0
  14. package/dist/index.mjs +1 -0
  15. package/dist/loop/accumulator.d.mts +11 -0
  16. package/dist/loop/accumulator.d.mts.map +1 -0
  17. package/dist/loop/accumulator.mjs +40 -0
  18. package/dist/loop/accumulator.mjs.map +1 -0
  19. package/dist/loop/error.d.mts +36 -0
  20. package/dist/loop/error.d.mts.map +1 -0
  21. package/dist/loop/error.mjs +84 -0
  22. package/dist/loop/error.mjs.map +1 -0
  23. package/dist/loop/index.d.mts +9 -0
  24. package/dist/loop/index.mjs +9 -0
  25. package/dist/loop/llm-event.d.mts +44 -0
  26. package/dist/loop/llm-event.d.mts.map +1 -0
  27. package/dist/loop/llm-event.mjs +34 -0
  28. package/dist/loop/llm-event.mjs.map +1 -0
  29. package/dist/loop/run.d.mts +37 -0
  30. package/dist/loop/run.d.mts.map +1 -0
  31. package/dist/loop/run.mjs +624 -0
  32. package/dist/loop/run.mjs.map +1 -0
  33. package/dist/loop/services/context-transformer.d.mts +18 -0
  34. package/dist/loop/services/context-transformer.d.mts.map +1 -0
  35. package/dist/loop/services/context-transformer.mjs +12 -0
  36. package/dist/loop/services/context-transformer.mjs.map +1 -0
  37. package/dist/loop/services/llm-provider.d.mts +20 -0
  38. package/dist/loop/services/llm-provider.d.mts.map +1 -0
  39. package/dist/loop/services/llm-provider.mjs +7 -0
  40. package/dist/loop/services/llm-provider.mjs.map +1 -0
  41. package/dist/loop/services/loop-config.d.mts +17 -0
  42. package/dist/loop/services/loop-config.d.mts.map +1 -0
  43. package/dist/loop/services/loop-config.mjs +15 -0
  44. package/dist/loop/services/loop-config.mjs.map +1 -0
  45. package/dist/loop/services/tool-executor.d.mts +12 -0
  46. package/dist/loop/services/tool-executor.d.mts.map +1 -0
  47. package/dist/loop/services/tool-executor.mjs +7 -0
  48. package/dist/loop/services/tool-executor.mjs.map +1 -0
  49. package/dist/loop/testing/faux-provider.d.mts +31 -0
  50. package/dist/loop/testing/faux-provider.d.mts.map +1 -0
  51. package/dist/loop/testing/faux-provider.mjs +47 -0
  52. package/dist/loop/testing/faux-provider.mjs.map +1 -0
  53. package/dist/loop/testing/index.d.mts +3 -0
  54. package/dist/loop/testing/index.mjs +3 -0
  55. package/dist/loop/testing/test-tool-executor.d.mts +10 -0
  56. package/dist/loop/testing/test-tool-executor.d.mts.map +1 -0
  57. package/dist/loop/testing/test-tool-executor.mjs +21 -0
  58. package/dist/loop/testing/test-tool-executor.mjs.map +1 -0
  59. package/dist/protocol/capability.d.mts +20 -0
  60. package/dist/protocol/capability.d.mts.map +1 -0
  61. package/dist/protocol/capability.mjs +34 -0
  62. package/dist/protocol/capability.mjs.map +1 -0
  63. package/dist/protocol/content.d.mts +31 -0
  64. package/dist/protocol/content.d.mts.map +1 -0
  65. package/dist/protocol/content.mjs +52 -0
  66. package/dist/protocol/content.mjs.map +1 -0
  67. package/dist/protocol/event.d.mts +228 -0
  68. package/dist/protocol/event.d.mts.map +1 -0
  69. package/dist/protocol/event.mjs +217 -0
  70. package/dist/protocol/event.mjs.map +1 -0
  71. package/dist/protocol/index.d.mts +14 -0
  72. package/dist/protocol/index.d.mts.map +1 -0
  73. package/dist/protocol/index.mjs +9 -0
  74. package/dist/protocol/message.d.mts +53 -0
  75. package/dist/protocol/message.d.mts.map +1 -0
  76. package/dist/protocol/message.mjs +49 -0
  77. package/dist/protocol/message.mjs.map +1 -0
  78. package/dist/protocol/reasoning.d.mts +8 -0
  79. package/dist/protocol/reasoning.d.mts.map +1 -0
  80. package/dist/protocol/reasoning.mjs +13 -0
  81. package/dist/protocol/reasoning.mjs.map +1 -0
  82. package/dist/protocol/session.d.mts +39 -0
  83. package/dist/protocol/session.d.mts.map +1 -0
  84. package/dist/protocol/session.mjs +38 -0
  85. package/dist/protocol/session.mjs.map +1 -0
  86. package/dist/protocol/tool.d.mts +101 -0
  87. package/dist/protocol/tool.d.mts.map +1 -0
  88. package/dist/protocol/tool.mjs +102 -0
  89. package/dist/protocol/tool.mjs.map +1 -0
  90. package/dist/protocol/usage.d.mts +26 -0
  91. package/dist/protocol/usage.d.mts.map +1 -0
  92. package/dist/protocol/usage.mjs +40 -0
  93. package/dist/protocol/usage.mjs.map +1 -0
  94. package/dist/runtime/error.d.mts +29 -0
  95. package/dist/runtime/error.d.mts.map +1 -0
  96. package/dist/runtime/error.mjs +46 -0
  97. package/dist/runtime/error.mjs.map +1 -0
  98. package/dist/runtime/index.d.mts +9 -0
  99. package/dist/runtime/index.d.mts.map +1 -0
  100. package/dist/runtime/index.mjs +4 -0
  101. package/dist/runtime/run-runtime.d.mts +47 -0
  102. package/dist/runtime/run-runtime.d.mts.map +1 -0
  103. package/dist/runtime/run-runtime.mjs +112 -0
  104. package/dist/runtime/run-runtime.mjs.map +1 -0
  105. package/dist/runtime/session-event-store.d.mts +75 -0
  106. package/dist/runtime/session-event-store.d.mts.map +1 -0
  107. package/dist/runtime/session-event-store.mjs +124 -0
  108. package/dist/runtime/session-event-store.mjs.map +1 -0
  109. package/dist/tools/index.d.mts +4 -0
  110. package/dist/tools/index.mjs +4 -0
  111. package/dist/tools/question.d.mts +21 -0
  112. package/dist/tools/question.d.mts.map +1 -0
  113. package/dist/tools/question.mjs +41 -0
  114. package/dist/tools/question.mjs.map +1 -0
  115. package/dist/tools/registry.d.mts +61 -0
  116. package/dist/tools/registry.d.mts.map +1 -0
  117. package/dist/tools/registry.mjs +113 -0
  118. package/dist/tools/registry.mjs.map +1 -0
  119. package/dist/tools/task.d.mts +34 -0
  120. package/dist/tools/task.d.mts.map +1 -0
  121. package/dist/tools/task.mjs +81 -0
  122. package/dist/tools/task.mjs.map +1 -0
  123. package/package.json +86 -0
  124. package/src/client/README.md +23 -0
  125. package/src/client/index.ts +43 -0
  126. package/src/client/state.ts +380 -0
  127. package/src/client/transport.ts +517 -0
  128. package/src/index.ts +2 -0
  129. package/src/loop/README.md +23 -0
  130. package/src/loop/accumulator.ts +71 -0
  131. package/src/loop/error.ts +105 -0
  132. package/src/loop/index.ts +35 -0
  133. package/src/loop/llm-event.ts +52 -0
  134. package/src/loop/run.ts +1237 -0
  135. package/src/loop/services/context-transformer.ts +24 -0
  136. package/src/loop/services/llm-provider.ts +20 -0
  137. package/src/loop/services/loop-config.ts +20 -0
  138. package/src/loop/services/tool-executor.ts +11 -0
  139. package/src/loop/testing/faux-provider.ts +94 -0
  140. package/src/loop/testing/index.ts +3 -0
  141. package/src/loop/testing/test-tool-executor.ts +28 -0
  142. package/src/protocol/README.md +24 -0
  143. package/src/protocol/capability.ts +29 -0
  144. package/src/protocol/content.ts +76 -0
  145. package/src/protocol/event.ts +286 -0
  146. package/src/protocol/index.ts +109 -0
  147. package/src/protocol/message.ts +86 -0
  148. package/src/protocol/reasoning.ts +4 -0
  149. package/src/protocol/session.ts +47 -0
  150. package/src/protocol/tool.ts +154 -0
  151. package/src/protocol/usage.ts +48 -0
  152. package/src/runtime/README.md +44 -0
  153. package/src/runtime/error.ts +70 -0
  154. package/src/runtime/index.ts +43 -0
  155. package/src/runtime/run-runtime.ts +307 -0
  156. package/src/runtime/session-event-store.ts +254 -0
  157. package/src/tools/README.md +22 -0
  158. package/src/tools/index.ts +29 -0
  159. package/src/tools/question.ts +58 -0
  160. package/src/tools/registry.ts +228 -0
  161. package/src/tools/task.ts +132 -0
@@ -0,0 +1,307 @@
1
+ import { Effect, Ref, Stream } from 'effect'
2
+ import type {
3
+ AgentEvent,
4
+ AgentMessage,
5
+ AgentModelCapabilities,
6
+ AgentReasoningEffort,
7
+ HitlResponse,
8
+ ToolDef
9
+ } from '@yolk-sdk/agent/protocol'
10
+ import {
11
+ run,
12
+ type AgentLoopError,
13
+ type ContextTransformer,
14
+ type LLMProvider,
15
+ type LoopConfig,
16
+ type ToolExecutor
17
+ } from '@yolk-sdk/agent/loop'
18
+ import { runtimeErrorToAgentError } from './error.ts'
19
+ import {
20
+ HitlResponseAppended,
21
+ InputAppended,
22
+ replayRuntimeHitlResponses,
23
+ replayRuntimeSessionEvents,
24
+ RunAwaitingInput,
25
+ RunCompleted,
26
+ RunFailed,
27
+ RunStarted,
28
+ SessionEventStore,
29
+ type RuntimeSessionEventLog,
30
+ type SessionEventStoreApi,
31
+ type SessionRevision
32
+ } from './session-event-store.ts'
33
+ import type { RuntimeError } from './error.ts'
34
+
35
+ export type RuntimeTranscript = readonly [AgentMessage, ...Array<AgentMessage>]
36
+
37
+ export type RuntimeConfig = {
38
+ readonly systemPrompt: string
39
+ readonly tools: ReadonlyArray<ToolDef>
40
+ readonly hitlResponses?: ReadonlyArray<HitlResponse>
41
+ readonly model: string
42
+ readonly reasoningEffort?: AgentReasoningEffort
43
+ readonly capabilities?: AgentModelCapabilities
44
+ }
45
+
46
+ export type TranscriptRuntimeRequest = {
47
+ readonly _tag: 'Transcript'
48
+ readonly sessionId: string
49
+ readonly messages: RuntimeTranscript
50
+ }
51
+
52
+ export type AppendInputRuntimeRequest = {
53
+ readonly _tag: 'AppendInput'
54
+ readonly sessionId: string
55
+ readonly input: AgentMessage
56
+ readonly runId: string
57
+ readonly expectedRevision?: SessionRevision
58
+ }
59
+
60
+ export type AppendHitlResponseRuntimeRequest = {
61
+ readonly _tag: 'AppendHitlResponse'
62
+ readonly sessionId: string
63
+ readonly response: HitlResponse
64
+ readonly runId: string
65
+ readonly expectedRevision?: SessionRevision
66
+ }
67
+
68
+ export type RuntimeRequest =
69
+ | TranscriptRuntimeRequest
70
+ | AppendInputRuntimeRequest
71
+ | AppendHitlResponseRuntimeRequest
72
+
73
+ type LoopRequirements = ContextTransformer | LLMProvider | LoopConfig | ToolExecutor
74
+ type AppendRuntimeRequirements = LoopRequirements | SessionEventStore
75
+ type RuntimeRequirements = LoopRequirements | AppendRuntimeRequirements
76
+ type RuntimeErrorUnion = RuntimeError | AgentLoopError
77
+
78
+ const extractNewMessages = (event: AgentEvent) => (event._tag === 'AgentEnd' ? event.messages : [])
79
+
80
+ const runtimeRunConfig = (config: RuntimeConfig, messages: ReadonlyArray<AgentMessage>) => ({
81
+ messages,
82
+ systemPrompt: config.systemPrompt,
83
+ tools: config.tools,
84
+ hitlResponses: config.hitlResponses,
85
+ reasoningEffort: config.reasoningEffort,
86
+ capabilities: config.capabilities,
87
+ model: config.model
88
+ })
89
+
90
+ const runAndCollectMessages = (
91
+ config: RuntimeConfig,
92
+ messages: ReadonlyArray<AgentMessage>,
93
+ createdMessages: Ref.Ref<ReadonlyArray<AgentMessage>>
94
+ ) =>
95
+ run(runtimeRunConfig(config, messages)).pipe(
96
+ Stream.tap(event => {
97
+ const newMessages = extractNewMessages(event)
98
+
99
+ return newMessages.length === 0
100
+ ? Effect.void
101
+ : Ref.update(createdMessages, messages => [...messages, ...newMessages])
102
+ })
103
+ )
104
+
105
+ const makeTranscriptRuntimeStream = (request: TranscriptRuntimeRequest, config: RuntimeConfig) =>
106
+ Stream.unwrap(
107
+ Ref.make<ReadonlyArray<AgentMessage>>([]).pipe(
108
+ Effect.map(createdMessages =>
109
+ runAndCollectMessages(config, request.messages, createdMessages)
110
+ )
111
+ )
112
+ )
113
+
114
+ const emptyRuntimeSessionEventLog = (sessionId: string): RuntimeSessionEventLog => ({
115
+ sessionId,
116
+ revision: 0,
117
+ events: []
118
+ })
119
+
120
+ const loadAppendLogOrEmpty = (store: SessionEventStoreApi, sessionId: string) =>
121
+ store
122
+ .load(sessionId)
123
+ .pipe(
124
+ Effect.catchTag('SessionNotFoundError', () =>
125
+ Effect.succeed(emptyRuntimeSessionEventLog(sessionId))
126
+ )
127
+ )
128
+
129
+ const appendRunFailed = (
130
+ store: SessionEventStoreApi,
131
+ request: AppendInputRuntimeRequest | AppendHitlResponseRuntimeRequest,
132
+ revision: SessionRevision,
133
+ error: AgentLoopError
134
+ ) =>
135
+ store.append({
136
+ sessionId: request.sessionId,
137
+ expectedRevision: revision,
138
+ events: [RunFailed.make({ runId: request.runId, error: runtimeErrorToAgentError(error) })]
139
+ })
140
+
141
+ const makeAppendInputRuntimeStream = (request: AppendInputRuntimeRequest, config: RuntimeConfig) =>
142
+ Stream.unwrap(
143
+ Effect.gen(function* () {
144
+ const store = yield* SessionEventStore
145
+ const initialLog = yield* loadAppendLogOrEmpty(store, request.sessionId)
146
+ const startedLog = yield* store.append({
147
+ sessionId: request.sessionId,
148
+ expectedRevision: request.expectedRevision ?? initialLog.revision,
149
+ events: [
150
+ InputAppended.make({ message: request.input }),
151
+ RunStarted.make({ runId: request.runId })
152
+ ]
153
+ })
154
+ const messages = [...replayRuntimeSessionEvents(initialLog.events), request.input]
155
+ return run(runtimeRunConfig(config, messages)).pipe(
156
+ Stream.tap(event =>
157
+ event._tag === 'AgentEnd'
158
+ ? store
159
+ .append({
160
+ sessionId: request.sessionId,
161
+ expectedRevision: startedLog.revision,
162
+ events: [RunCompleted.make({ runId: request.runId, messages: event.messages })]
163
+ })
164
+ .pipe(Effect.asVoid)
165
+ : event._tag === 'AgentAwaitingInput'
166
+ ? store
167
+ .append({
168
+ sessionId: request.sessionId,
169
+ expectedRevision: startedLog.revision,
170
+ events: [
171
+ RunAwaitingInput.make({
172
+ runId: request.runId,
173
+ requests: event.requests,
174
+ messages: event.messages
175
+ })
176
+ ]
177
+ })
178
+ .pipe(Effect.asVoid)
179
+ : Effect.void
180
+ ),
181
+ Stream.catchTags({
182
+ AbortError: error =>
183
+ Stream.fromEffect(appendRunFailed(store, request, startedLog.revision, error)).pipe(
184
+ Stream.flatMap(() => Stream.fail(error))
185
+ ),
186
+ ContextTransformError: error =>
187
+ Stream.fromEffect(appendRunFailed(store, request, startedLog.revision, error)).pipe(
188
+ Stream.flatMap(() => Stream.fail(error))
189
+ ),
190
+ FauxExhaustedError: error =>
191
+ Stream.fromEffect(appendRunFailed(store, request, startedLog.revision, error)).pipe(
192
+ Stream.flatMap(() => Stream.fail(error))
193
+ ),
194
+ LLMError: error =>
195
+ Stream.fromEffect(appendRunFailed(store, request, startedLog.revision, error)).pipe(
196
+ Stream.flatMap(() => Stream.fail(error))
197
+ ),
198
+ ToolError: error =>
199
+ Stream.fromEffect(appendRunFailed(store, request, startedLog.revision, error)).pipe(
200
+ Stream.flatMap(() => Stream.fail(error))
201
+ )
202
+ })
203
+ )
204
+ })
205
+ )
206
+
207
+ const makeAppendHitlResponseRuntimeStream = (
208
+ request: AppendHitlResponseRuntimeRequest,
209
+ config: RuntimeConfig
210
+ ) =>
211
+ Stream.unwrap(
212
+ Effect.gen(function* () {
213
+ const store = yield* SessionEventStore
214
+ const initialLog = yield* loadAppendLogOrEmpty(store, request.sessionId)
215
+ const startedLog = yield* store.append({
216
+ sessionId: request.sessionId,
217
+ expectedRevision: request.expectedRevision ?? initialLog.revision,
218
+ events: [
219
+ HitlResponseAppended.make({ response: request.response }),
220
+ RunStarted.make({ runId: request.runId })
221
+ ]
222
+ })
223
+ const messages = replayRuntimeSessionEvents(initialLog.events)
224
+ const priorResponses = replayRuntimeHitlResponses(initialLog.events)
225
+ const hitlResponses = [...priorResponses, request.response]
226
+
227
+ return run(runtimeRunConfig({ ...config, hitlResponses }, messages)).pipe(
228
+ Stream.tap(event =>
229
+ event._tag === 'AgentEnd'
230
+ ? store
231
+ .append({
232
+ sessionId: request.sessionId,
233
+ expectedRevision: startedLog.revision,
234
+ events: [RunCompleted.make({ runId: request.runId, messages: event.messages })]
235
+ })
236
+ .pipe(Effect.asVoid)
237
+ : event._tag === 'AgentAwaitingInput'
238
+ ? store
239
+ .append({
240
+ sessionId: request.sessionId,
241
+ expectedRevision: startedLog.revision,
242
+ events: [
243
+ RunAwaitingInput.make({
244
+ runId: request.runId,
245
+ requests: event.requests,
246
+ messages: event.messages
247
+ })
248
+ ]
249
+ })
250
+ .pipe(Effect.asVoid)
251
+ : Effect.void
252
+ ),
253
+ Stream.catchTags({
254
+ AbortError: error =>
255
+ Stream.fromEffect(appendRunFailed(store, request, startedLog.revision, error)).pipe(
256
+ Stream.flatMap(() => Stream.fail(error))
257
+ ),
258
+ ContextTransformError: error =>
259
+ Stream.fromEffect(appendRunFailed(store, request, startedLog.revision, error)).pipe(
260
+ Stream.flatMap(() => Stream.fail(error))
261
+ ),
262
+ FauxExhaustedError: error =>
263
+ Stream.fromEffect(appendRunFailed(store, request, startedLog.revision, error)).pipe(
264
+ Stream.flatMap(() => Stream.fail(error))
265
+ ),
266
+ LLMError: error =>
267
+ Stream.fromEffect(appendRunFailed(store, request, startedLog.revision, error)).pipe(
268
+ Stream.flatMap(() => Stream.fail(error))
269
+ ),
270
+ ToolError: error =>
271
+ Stream.fromEffect(appendRunFailed(store, request, startedLog.revision, error)).pipe(
272
+ Stream.flatMap(() => Stream.fail(error))
273
+ )
274
+ })
275
+ )
276
+ })
277
+ )
278
+
279
+ export function runRuntime(
280
+ request: TranscriptRuntimeRequest,
281
+ config: RuntimeConfig
282
+ ): Stream.Stream<AgentEvent, AgentLoopError, LoopRequirements>
283
+ export function runRuntime(
284
+ request: AppendInputRuntimeRequest,
285
+ config: RuntimeConfig
286
+ ): Stream.Stream<AgentEvent, RuntimeErrorUnion, AppendRuntimeRequirements>
287
+ export function runRuntime(
288
+ request: AppendHitlResponseRuntimeRequest,
289
+ config: RuntimeConfig
290
+ ): Stream.Stream<AgentEvent, RuntimeErrorUnion, AppendRuntimeRequirements>
291
+ export function runRuntime(
292
+ request: RuntimeRequest,
293
+ config: RuntimeConfig
294
+ ): Stream.Stream<AgentEvent, RuntimeErrorUnion, RuntimeRequirements>
295
+ export function runRuntime(
296
+ request: RuntimeRequest,
297
+ config: RuntimeConfig
298
+ ): Stream.Stream<AgentEvent, RuntimeErrorUnion, RuntimeRequirements> {
299
+ switch (request._tag) {
300
+ case 'Transcript':
301
+ return makeTranscriptRuntimeStream(request, config)
302
+ case 'AppendInput':
303
+ return makeAppendInputRuntimeStream(request, config)
304
+ case 'AppendHitlResponse':
305
+ return makeAppendHitlResponseRuntimeStream(request, config)
306
+ }
307
+ }
@@ -0,0 +1,254 @@
1
+ import { Context, Effect, Layer, Option, Ref } from 'effect'
2
+ import * as Schema from 'effect/Schema'
3
+ import { AgentError, AgentMessage, HitlRequest, HitlResponse } from '@yolk-sdk/agent/protocol'
4
+ import { SessionConflictError, SessionNotFoundError } from './error.ts'
5
+ import type { SessionLoadError, SessionSaveError } from './error.ts'
6
+
7
+ export type SessionRevision = number
8
+
9
+ export class InputAppended extends Schema.TaggedClass<InputAppended>()('InputAppended', {
10
+ message: AgentMessage
11
+ }) {}
12
+
13
+ export class HitlResponseAppended extends Schema.TaggedClass<HitlResponseAppended>()(
14
+ 'HitlResponseAppended',
15
+ {
16
+ response: HitlResponse
17
+ }
18
+ ) {}
19
+
20
+ export class RunStarted extends Schema.TaggedClass<RunStarted>()('RunStarted', {
21
+ runId: Schema.String
22
+ }) {}
23
+
24
+ export class RunCompleted extends Schema.TaggedClass<RunCompleted>()('RunCompleted', {
25
+ runId: Schema.String,
26
+ messages: Schema.Array(AgentMessage)
27
+ }) {}
28
+
29
+ export class RunAwaitingInput extends Schema.TaggedClass<RunAwaitingInput>()('RunAwaitingInput', {
30
+ runId: Schema.String,
31
+ requests: Schema.NonEmptyArray(HitlRequest),
32
+ messages: Schema.Array(AgentMessage)
33
+ }) {}
34
+
35
+ export class RunFailed extends Schema.TaggedClass<RunFailed>()('RunFailed', {
36
+ runId: Schema.String,
37
+ error: AgentError
38
+ }) {}
39
+
40
+ export class RunInterrupted extends Schema.TaggedClass<RunInterrupted>()('RunInterrupted', {
41
+ runId: Schema.String
42
+ }) {}
43
+
44
+ export const RuntimeSessionEvent = Schema.Union([
45
+ InputAppended,
46
+ HitlResponseAppended,
47
+ RunStarted,
48
+ RunCompleted,
49
+ RunAwaitingInput,
50
+ RunFailed,
51
+ RunInterrupted
52
+ ])
53
+ export type RuntimeSessionEvent = typeof RuntimeSessionEvent.Type
54
+
55
+ export type StoredRuntimeSessionEvent = {
56
+ readonly id: string
57
+ readonly sessionId: string
58
+ readonly revision: SessionRevision
59
+ readonly event: RuntimeSessionEvent
60
+ }
61
+
62
+ export type RuntimeSessionEventLog = {
63
+ readonly sessionId: string
64
+ readonly revision: SessionRevision
65
+ readonly events: ReadonlyArray<StoredRuntimeSessionEvent>
66
+ }
67
+
68
+ export type IncompleteRuntimeRun = {
69
+ readonly runId: string
70
+ readonly startedRevision: SessionRevision
71
+ }
72
+
73
+ export type AppendRuntimeSessionEventsInput = {
74
+ readonly sessionId: string
75
+ readonly expectedRevision?: SessionRevision
76
+ readonly events: ReadonlyArray<RuntimeSessionEvent>
77
+ }
78
+
79
+ export type SessionEventStoreApi = {
80
+ readonly load: (
81
+ sessionId: string
82
+ ) => Effect.Effect<RuntimeSessionEventLog, SessionNotFoundError | SessionLoadError>
83
+ readonly append: (
84
+ input: AppendRuntimeSessionEventsInput
85
+ ) => Effect.Effect<RuntimeSessionEventLog, SessionSaveError | SessionConflictError>
86
+ }
87
+
88
+ export class SessionEventStore extends Context.Service<SessionEventStore, SessionEventStoreApi>()(
89
+ '@yolk-sdk/agent/runtime/SessionEventStore'
90
+ ) {}
91
+
92
+ export const replayRuntimeSessionEvents = (
93
+ events: ReadonlyArray<StoredRuntimeSessionEvent>
94
+ ): ReadonlyArray<AgentMessage> =>
95
+ events.flatMap(stored => {
96
+ switch (stored.event._tag) {
97
+ case 'InputAppended':
98
+ return [stored.event.message]
99
+ case 'RunCompleted':
100
+ case 'RunAwaitingInput':
101
+ return stored.event.messages
102
+ case 'HitlResponseAppended':
103
+ case 'RunFailed':
104
+ case 'RunInterrupted':
105
+ case 'RunStarted':
106
+ return []
107
+ }
108
+ })
109
+
110
+ export const replayRuntimeHitlResponses = (
111
+ events: ReadonlyArray<StoredRuntimeSessionEvent>
112
+ ): ReadonlyArray<HitlResponse> =>
113
+ events.flatMap(stored =>
114
+ stored.event._tag === 'HitlResponseAppended' ? [stored.event.response] : []
115
+ )
116
+
117
+ type IncompleteRunSearch =
118
+ | {
119
+ readonly _tag: 'Found'
120
+ readonly run: IncompleteRuntimeRun
121
+ }
122
+ | {
123
+ readonly _tag: 'Searching'
124
+ readonly terminalRunIds: ReadonlySet<string>
125
+ }
126
+
127
+ const terminalRunId = (event: RuntimeSessionEvent): Option.Option<string> => {
128
+ switch (event._tag) {
129
+ case 'RunCompleted':
130
+ case 'RunAwaitingInput':
131
+ case 'RunFailed':
132
+ case 'RunInterrupted':
133
+ return Option.some(event.runId)
134
+ case 'HitlResponseAppended':
135
+ case 'InputAppended':
136
+ case 'RunStarted':
137
+ return Option.none()
138
+ }
139
+ }
140
+
141
+ export const latestIncompleteRuntimeRun = (
142
+ events: ReadonlyArray<StoredRuntimeSessionEvent>
143
+ ): Option.Option<IncompleteRuntimeRun> => {
144
+ const search = events.reduceRight<IncompleteRunSearch>(
145
+ (state, stored) => {
146
+ if (state._tag === 'Found') {
147
+ return state
148
+ }
149
+
150
+ const terminal = terminalRunId(stored.event)
151
+
152
+ if (Option.isSome(terminal)) {
153
+ return {
154
+ _tag: 'Searching',
155
+ terminalRunIds: new Set([...state.terminalRunIds, terminal.value])
156
+ }
157
+ }
158
+
159
+ if (stored.event._tag === 'RunStarted' && !state.terminalRunIds.has(stored.event.runId)) {
160
+ return {
161
+ _tag: 'Found',
162
+ run: {
163
+ runId: stored.event.runId,
164
+ startedRevision: stored.revision
165
+ }
166
+ }
167
+ }
168
+
169
+ return state
170
+ },
171
+ { _tag: 'Searching', terminalRunIds: new Set() }
172
+ )
173
+
174
+ return search._tag === 'Found' ? Option.some(search.run) : Option.none()
175
+ }
176
+
177
+ const emptyLog = (sessionId: string): RuntimeSessionEventLog => ({
178
+ sessionId,
179
+ revision: 0,
180
+ events: []
181
+ })
182
+
183
+ const makeStoredRuntimeSessionEvents = (
184
+ sessionId: string,
185
+ currentRevision: SessionRevision,
186
+ events: ReadonlyArray<RuntimeSessionEvent>
187
+ ): ReadonlyArray<StoredRuntimeSessionEvent> =>
188
+ events.map((event, index) => {
189
+ const revision = currentRevision + index + 1
190
+
191
+ return {
192
+ id: `${sessionId}:${revision}`,
193
+ sessionId,
194
+ revision,
195
+ event
196
+ }
197
+ })
198
+
199
+ export const appendRuntimeSessionEventsToLog = (
200
+ current: RuntimeSessionEventLog,
201
+ input: AppendRuntimeSessionEventsInput
202
+ ): RuntimeSessionEventLog => {
203
+ const stored = makeStoredRuntimeSessionEvents(input.sessionId, current.revision, input.events)
204
+ const last = stored.at(-1)
205
+
206
+ return {
207
+ sessionId: input.sessionId,
208
+ revision: last?.revision ?? current.revision,
209
+ events: [...current.events, ...stored]
210
+ }
211
+ }
212
+
213
+ export const makeInMemorySessionEventStoreLayer = (
214
+ initial: ReadonlyArray<RuntimeSessionEventLog> = []
215
+ ) =>
216
+ Layer.effect(
217
+ SessionEventStore,
218
+ Effect.gen(function* () {
219
+ const logs = yield* Ref.make(new Map(initial.map(log => [log.sessionId, log])))
220
+
221
+ return SessionEventStore.of({
222
+ load: sessionId =>
223
+ Effect.gen(function* () {
224
+ const current = yield* Ref.get(logs)
225
+
226
+ return yield* Effect.fromNullishOr(current.get(sessionId)).pipe(
227
+ Effect.mapError(() => new SessionNotFoundError({ sessionId }))
228
+ )
229
+ }),
230
+ append: input =>
231
+ Effect.gen(function* () {
232
+ const current = yield* Ref.get(logs)
233
+ const currentLog = current.get(input.sessionId) ?? emptyLog(input.sessionId)
234
+
235
+ if (
236
+ input.expectedRevision !== undefined &&
237
+ input.expectedRevision !== currentLog.revision
238
+ ) {
239
+ return yield* Effect.fail(
240
+ new SessionConflictError({
241
+ sessionId: input.sessionId,
242
+ message: `Session revision conflict: expected ${input.expectedRevision}, got ${currentLog.revision}`
243
+ })
244
+ )
245
+ }
246
+
247
+ const nextLog = appendRuntimeSessionEventsToLog(currentLog, input)
248
+ yield* Ref.set(logs, new Map([...current, [input.sessionId, nextLog]]))
249
+
250
+ return nextLog
251
+ })
252
+ })
253
+ })
254
+ )
@@ -0,0 +1,22 @@
1
+ # @yolk-sdk/agent/tools
2
+
3
+ Generic host tool registration and resolution.
4
+
5
+ ## What it provides
6
+
7
+ - `ToolModule<Context>` and `ToolRegistration<Context>` types.
8
+ - Tool resolution from host modules and context.
9
+ - Duplicate tool name validation.
10
+ - Adapter from resolved tools to `@yolk-sdk/agent/loop` `ToolExecutor`.
11
+ - Package-owned `task` and `question` tool contracts.
12
+
13
+ ## Use it when
14
+
15
+ - A host app wants declarative tool modules with generic context.
16
+ - You need to filter/resolve tools before running the agent loop.
17
+
18
+ ## Boundaries
19
+
20
+ - No app tool catalogs.
21
+ - No provider SDKs.
22
+ - Tool access/approval is metadata; host apps enforce product policy.
@@ -0,0 +1,29 @@
1
+ export { EmptyToolParams, makeTool, makeToolExecutorLayer, resolveTools, ToolAccess, ToolRegistryError } from './registry.ts'
2
+ export {
3
+ makeQuestionToolDef,
4
+ makeQuestionToolModule,
5
+ makeQuestionToolRegistration,
6
+ questionToolName
7
+ } from './question.ts'
8
+ export {
9
+ formatTaskResult,
10
+ makeTaskToolDef,
11
+ makeTaskToolModule,
12
+ makeTaskToolRegistration,
13
+ taskToolName
14
+ } from './task.ts'
15
+ export type {
16
+ ResolvedToolSet,
17
+ SchemaToolExecutionInput,
18
+ ToolExecutionInput,
19
+ ToolMetadata,
20
+ ToolModule,
21
+ ToolRegistration
22
+ } from './registry.ts'
23
+ export type { QuestionExecutionInput, QuestionToolOptions } from './question.ts'
24
+ export type {
25
+ TaskExecutionInput,
26
+ TaskSubagentDefinition,
27
+ TaskToolOptions,
28
+ TaskToolParams
29
+ } from './task.ts'
@@ -0,0 +1,58 @@
1
+ import { Effect } from 'effect'
2
+ import { ToolError } from '@yolk-sdk/agent/loop'
3
+ import { QuestionToolParams, ToolResult, type ToolCall } from '@yolk-sdk/agent/protocol'
4
+ import { makeTool, type ToolModule, type ToolRegistration } from './registry.ts'
5
+
6
+ export const questionToolName = 'question'
7
+
8
+ export type QuestionExecutionInput<Context> = {
9
+ readonly call: ToolCall
10
+ readonly context: Context
11
+ readonly params: QuestionToolParams
12
+ }
13
+
14
+ export type QuestionToolOptions<Context> = {
15
+ readonly execute: (input: QuestionExecutionInput<Context>) => Effect.Effect<ToolResult, ToolError>
16
+ }
17
+
18
+ const questionToolError = (message: string, cause: ToolError['cause']) =>
19
+ new ToolError({
20
+ tool: questionToolName,
21
+ message,
22
+ cause
23
+ })
24
+
25
+ const questionToolDescription = [
26
+ 'Ask the user one or more structured questions and wait for their response.',
27
+ 'Use this when you need explicit user input before continuing.',
28
+ 'Each question has a stable id, prompt, optional options, and optional custom-answer support.',
29
+ 'Yolk returns user answers as structured tool output.'
30
+ ].join('\n\n')
31
+
32
+ export const makeQuestionToolRegistration = <Context>(
33
+ options: QuestionToolOptions<Context>
34
+ ): ToolRegistration<Context> =>
35
+ makeTool({
36
+ name: questionToolName,
37
+ description: questionToolDescription,
38
+ parameters: QuestionToolParams,
39
+ access: 'read',
40
+ invalidParamsMessage: error =>
41
+ `Invalid question arguments: ${error instanceof Error ? error.message : String(error)}`,
42
+ execute: ({ call, context, params }) =>
43
+ call.name === questionToolName
44
+ ? options.execute({ call, context, params })
45
+ : Effect.fail(questionToolError(`Tool is not configured: ${call.name}`, 'not_found'))
46
+ })
47
+
48
+ export const makeQuestionToolDef = () =>
49
+ makeQuestionToolRegistration({
50
+ execute: ({ call }) => Effect.succeed(ToolResult.make({ toolCallId: call.id, content: '' }))
51
+ }).def
52
+
53
+ export const makeQuestionToolModule = <Context>(
54
+ options: QuestionToolOptions<Context>
55
+ ): ToolModule<Context> => ({
56
+ id: 'question',
57
+ tools: [makeQuestionToolRegistration(options)]
58
+ })