@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,37 @@
1
+ import { AgentLoopError } from "./error.mjs";
2
+ import { ContextTransformer } from "./services/context-transformer.mjs";
3
+ import { LLMProvider } from "./services/llm-provider.mjs";
4
+ import { LoopConfig } from "./services/loop-config.mjs";
5
+ import { ToolExecutor } from "./services/tool-executor.mjs";
6
+ import { Stream } from "effect";
7
+ import { AgentEvent, AgentMessage, AgentModelCapabilities, AgentReasoningEffort, AgentUsage, HitlResponse, ToolCall, ToolDef } from "@yolk-sdk/agent/protocol";
8
+
9
+ //#region src/loop/run.d.ts
10
+ type AgentLoopRunId = string;
11
+ type RunConfig = {
12
+ readonly messages: ReadonlyArray<AgentMessage>;
13
+ readonly systemPrompt: string;
14
+ readonly tools: ReadonlyArray<ToolDef>;
15
+ readonly hitlResponses?: ReadonlyArray<HitlResponse>;
16
+ readonly model: string;
17
+ readonly reasoningEffort?: AgentReasoningEffort;
18
+ readonly capabilities?: AgentModelCapabilities;
19
+ };
20
+ type ModelTurnConfig = RunConfig & {
21
+ readonly turn: number;
22
+ };
23
+ type ToolBatchConfig = {
24
+ readonly calls: ReadonlyArray<ToolCall>;
25
+ readonly tools?: ReadonlyArray<ToolDef>;
26
+ readonly hitlResponses?: ReadonlyArray<HitlResponse>;
27
+ readonly model?: string;
28
+ readonly createdMessages?: ReadonlyArray<AgentMessage>;
29
+ readonly turn?: number;
30
+ readonly usage?: AgentUsage;
31
+ };
32
+ declare const runModelTurn: (config: ModelTurnConfig) => Stream.Stream<AgentEvent, AgentLoopError, ContextTransformer | LLMProvider | LoopConfig>;
33
+ declare const runToolBatch: (config: ToolBatchConfig) => Stream.Stream<AgentEvent, AgentLoopError, LoopConfig | ToolExecutor>;
34
+ declare const run: (config: RunConfig) => Stream.Stream<AgentEvent, AgentLoopError, ContextTransformer | LLMProvider | LoopConfig | ToolExecutor>;
35
+ //#endregion
36
+ export { AgentLoopRunId, ModelTurnConfig, RunConfig, ToolBatchConfig, run, runModelTurn, runToolBatch };
37
+ //# sourceMappingURL=run.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.d.mts","names":[],"sources":["../../src/loop/run.ts"],"mappings":";;;;;;;;;KAqEY,cAAA;AAAA,KAEA,SAAA;EAAA,SACD,QAAA,EAAU,aAAA,CAAc,YAAA;EAAA,SACxB,YAAA;EAAA,SACA,KAAA,EAAO,aAAA,CAAc,OAAA;EAAA,SACrB,aAAA,GAAgB,aAAA,CAAc,YAAA;EAAA,SAC9B,KAAA;EAAA,SACA,eAAA,GAAkB,oBAAA;EAAA,SAClB,YAAA,GAAe,sBAAA;AAAA;AAAA,KAGd,eAAA,GAAkB,SAAS;EAAA,SAC5B,IAAI;AAAA;AAAA,KAGH,eAAA;EAAA,SACD,KAAA,EAAO,aAAA,CAAc,QAAA;EAAA,SACrB,KAAA,GAAQ,aAAA,CAAc,OAAA;EAAA,SACtB,aAAA,GAAgB,aAAA,CAAc,YAAA;EAAA,SAC9B,KAAA;EAAA,SACA,eAAA,GAAkB,aAAA,CAAc,YAAA;EAAA,SAChC,IAAA;EAAA,SACA,KAAA,GAAQ,UAAA;AAAA;AAAA,cAihCN,YAAA,GACX,MAAA,EAAQ,eAAA,KACP,MAAA,CAAO,MAAA,CAAO,UAAA,EAAY,cAAA,EAAgB,kBAAA,GAAqB,WAAA,GAAc,UAAA;AAAA,cAuBnE,YAAA,GACX,MAAA,EAAQ,eAAA,KACP,MAAA,CAAO,MAAA,CAAO,UAAA,EAAY,cAAA,EAAgB,UAAA,GAAa,YAAA;AAAA,cA4C7C,GAAA,GACX,MAAA,EAAQ,SAAA,KACP,MAAA,CAAO,MAAA,CACR,UAAA,EACA,cAAA,EACA,kBAAA,GAAqB,WAAA,GAAc,UAAA,GAAa,YAAA"}
@@ -0,0 +1,624 @@
1
+ import { accumulateAssistantMessage, collectToolCalls } from "./accumulator.mjs";
2
+ import { AbortError, LLMError, ToolError } from "./error.mjs";
3
+ import { ContextTransformer } from "./services/context-transformer.mjs";
4
+ import { LLMProvider } from "./services/llm-provider.mjs";
5
+ import { LoopConfig } from "./services/loop-config.mjs";
6
+ import { ToolExecutor } from "./services/tool-executor.mjs";
7
+ import { Clock, Effect, Ref, Stream } from "effect";
8
+ import * as Schema from "effect/Schema";
9
+ import { AgentAwaitingInput, AgentEnd, AgentRetry, AgentStart, AssistantMessageEvent, LLMReasoningDelta, LLMStreamEnd, LLMStreamStart, LLMTextDelta, ProviderToolResult, QuestionAnswered, QuestionCancelled, QuestionRequest, QuestionRequested, QuestionToolParams, SubagentCompleted, SubagentStarted, ToolApprovalDenied, ToolApprovalGranted, ToolApprovalRequest, ToolApprovalRequested, ToolExecutionCompleted, ToolExecutionError, ToolExecutionStarted, ToolInputDelta, ToolInputEnd, ToolInputStart, ToolResult, ToolResultMessage, TurnEnd, TurnStart, UsageUpdate, addAgentUsage, assistantHostToolCalls, contentParts, contentPreview, formatQuestionResponseContent, zeroAgentUsage } from "@yolk-sdk/agent/protocol";
10
+ //#region src/loop/run.ts
11
+ const questionToolName = "question";
12
+ const objectField = (input, key) => input !== null && typeof input === "object" ? Object.getOwnPropertyDescriptor(input, key)?.value : void 0;
13
+ const nonEmptyStringField = (input, key) => {
14
+ const value = objectField(input, key);
15
+ return typeof value === "string" && value.trim().length > 0 ? value : void 0;
16
+ };
17
+ const taskCallMetadata = (call) => {
18
+ if (call.name !== "task") return;
19
+ const subagentType = nonEmptyStringField(call.params, "subagent_type");
20
+ const description = nonEmptyStringField(call.params, "description");
21
+ if (subagentType === void 0 || description === void 0) return;
22
+ return {
23
+ subagentRunId: `subagent:${call.id}`,
24
+ subagentType,
25
+ description
26
+ };
27
+ };
28
+ const subagentStartedEvent = (input) => {
29
+ const metadata = taskCallMetadata(input.call);
30
+ return metadata === void 0 ? void 0 : SubagentStarted.make({
31
+ parentToolCallId: input.call.id,
32
+ subagentRunId: metadata.subagentRunId,
33
+ subagentType: metadata.subagentType,
34
+ description: metadata.description,
35
+ model: input.model,
36
+ createdAtMs: input.startedAtMs
37
+ });
38
+ };
39
+ const subagentCompletedEvent = (input) => {
40
+ const metadata = taskCallMetadata(input.call);
41
+ return metadata === void 0 ? void 0 : SubagentCompleted.make({
42
+ parentToolCallId: input.call.id,
43
+ subagentRunId: metadata.subagentRunId,
44
+ subagentType: metadata.subagentType,
45
+ description: metadata.description,
46
+ model: input.model,
47
+ status: input.result.isError === true ? "error" : "completed",
48
+ durationMs: Math.max(0, input.endedAtMs - input.startedAtMs),
49
+ summary: contentPreview(input.result.content),
50
+ createdAtMs: input.endedAtMs
51
+ });
52
+ };
53
+ const unsupportedInputError = (message) => new LLMError({
54
+ cause: "validation_error",
55
+ message,
56
+ retryable: false
57
+ });
58
+ const validateContent = (message, capabilities) => Effect.forEach(contentPartsFromMessage(message), (part) => {
59
+ switch (part._tag) {
60
+ case "Text": return capabilities.input.text ? Effect.void : Effect.fail(unsupportedInputError("Text input is not supported by this model"));
61
+ case "Image": return capabilities.input.image ? Effect.void : Effect.fail(unsupportedInputError("Image input is not supported by this model"));
62
+ case "Audio": return capabilities.input.audio ? Effect.void : Effect.fail(unsupportedInputError("Audio input is not supported by this model"));
63
+ }
64
+ });
65
+ const contentPartsFromMessage = (message) => {
66
+ switch (message._tag) {
67
+ case "User":
68
+ case "ToolResult": return contentParts(message.content);
69
+ case "Assistant": return message.parts.flatMap((part) => part._tag === "Text" ? contentParts(part.content) : []);
70
+ }
71
+ };
72
+ const validateCapabilities = (config, messages) => {
73
+ const capabilities = config.capabilities;
74
+ if (capabilities === void 0) return Effect.void;
75
+ if (!capabilities.tools && config.tools.length > 0) return Effect.fail(unsupportedInputError("Tools are not supported by this model"));
76
+ if (!capabilities.reasoning && config.reasoningEffort !== void 0) return Effect.fail(unsupportedInputError("Reasoning effort is not supported by this model"));
77
+ return Effect.forEach(messages, (message) => validateContent(message, capabilities)).pipe(Effect.asVoid);
78
+ };
79
+ const toLlmEvent = (event) => {
80
+ switch (event._tag) {
81
+ case "TextDelta": return [LLMTextDelta.make({ text: event.text })];
82
+ case "ReasoningDelta": return [LLMReasoningDelta.make({ text: event.text })];
83
+ case "ToolCall": return [ToolInputEnd.make({ call: event.call })];
84
+ case "ToolInputStart": return [ToolInputStart.make({
85
+ id: event.id,
86
+ name: event.name
87
+ })];
88
+ case "ToolInputDelta": return [ToolInputDelta.make({
89
+ id: event.id,
90
+ delta: event.delta
91
+ })];
92
+ case "ProviderToolResult": return [ProviderToolResult.make({
93
+ call: event.call,
94
+ result: event.result
95
+ })];
96
+ case "Usage": return [UsageUpdate.make({ usage: event.usage })];
97
+ case "Done": return [];
98
+ }
99
+ };
100
+ const isLlmEvent = (event) => {
101
+ switch (event._tag) {
102
+ case "TextDelta":
103
+ case "ReasoningDelta":
104
+ case "Done":
105
+ case "ToolCall":
106
+ case "ToolInputStart":
107
+ case "ToolInputDelta":
108
+ case "ProviderToolResult":
109
+ case "Usage": return true;
110
+ default: return false;
111
+ }
112
+ };
113
+ const retryDelayMs = (baseDelayMs, attempt) => Math.max(0, Math.floor(baseDelayMs * 2 ** Math.max(0, attempt - 1)));
114
+ const retryReason = (error) => error.cause;
115
+ const retrySleep = (delayMs) => delayMs === 0 ? Effect.void : Effect.sleep(`${delayMs} millis`);
116
+ const failAgentLoopError = (error) => Stream.fail(error);
117
+ const sleepStream = (delayMs) => Stream.fromEffect(retrySleep(delayMs)).pipe(Stream.flatMap(() => Stream.empty));
118
+ const withProviderRetries = (stream, loopConfig, makeStream, attempt) => Stream.unwrap(Ref.make(false).pipe(Effect.map((emittedProviderEvent) => stream.pipe(Stream.tap(() => Ref.set(emittedProviderEvent, true)), Stream.catchTags({
119
+ LLMError: (error) => Stream.unwrap(Ref.get(emittedProviderEvent).pipe(Effect.map((emitted) => {
120
+ if (emitted || !error.retryable || error.cause === "context_overflow" || attempt > loopConfig.maxRetries) return failAgentLoopError(error);
121
+ const delayMs = retryDelayMs(loopConfig.retryBaseDelayMs, attempt);
122
+ return Stream.make(AgentRetry.make({
123
+ attempt,
124
+ reason: retryReason(error),
125
+ delayMs,
126
+ message: error.message
127
+ })).pipe(Stream.concat(sleepStream(delayMs)), Stream.concat(withProviderRetries(makeStream(), loopConfig, makeStream, attempt + 1)));
128
+ }))),
129
+ AbortError: failAgentLoopError,
130
+ FauxExhaustedError: failAgentLoopError
131
+ })))));
132
+ const makeToolExecutionStream = (executor, call, model) => Stream.unwrap(Effect.gen(function* () {
133
+ const startedAtMs = yield* Clock.currentTimeMillis;
134
+ const started = subagentStartedEvent({
135
+ call,
136
+ model,
137
+ startedAtMs
138
+ });
139
+ const startEvents = started === void 0 ? [ToolExecutionStarted.make({
140
+ call,
141
+ createdAtMs: startedAtMs
142
+ })] : [ToolExecutionStarted.make({
143
+ call,
144
+ createdAtMs: startedAtMs
145
+ }), started];
146
+ return Stream.fromIterable(startEvents).pipe(Stream.concat(Stream.fromEffect(executor.execute(call).pipe(Effect.flatMap((result) => Clock.currentTimeMillis.pipe(Effect.map((endedAtMs) => {
147
+ const completed = subagentCompletedEvent({
148
+ call,
149
+ result,
150
+ model,
151
+ startedAtMs,
152
+ endedAtMs
153
+ });
154
+ const toolCompleted = ToolExecutionCompleted.make({
155
+ call,
156
+ result,
157
+ createdAtMs: endedAtMs
158
+ });
159
+ return completed === void 0 ? [toolCompleted] : [toolCompleted, completed];
160
+ }))))).pipe(Stream.flatMap(Stream.fromIterable), Stream.catchTag("ToolError", (error) => Stream.fromEffect(Clock.currentTimeMillis).pipe(Stream.flatMap((endedAtMs) => Stream.make(ToolExecutionError.make({
161
+ call,
162
+ message: error.message,
163
+ code: toolErrorCode(error),
164
+ createdAtMs: endedAtMs
165
+ }))), Stream.concat(Stream.fail(error)))))));
166
+ }));
167
+ const boundedToolConcurrency = (loopConfig) => Math.max(1, loopConfig.toolConcurrency);
168
+ const toolResultMessageFromResult = (result) => ToolResultMessage.make({
169
+ toolCallId: result.toolCallId,
170
+ content: result.content,
171
+ isError: result.isError,
172
+ structuredContent: result.structuredContent
173
+ });
174
+ const toolDefFor = (tools, call) => tools.find((tool) => tool.name === call.name);
175
+ const approvalRequired = (tools, call) => toolDefFor(tools, call)?.approval?.mode === "manual";
176
+ const approvalRequestId = (call) => `approval:${call.id}`;
177
+ const questionRequestId = (call) => `question:${call.id}`;
178
+ const matchesApproval = (response, call) => response.toolCallId === call.id || response.requestId === approvalRequestId(call);
179
+ const matchesQuestion = (response, call) => response.toolCallId === call.id || response.requestId === questionRequestId(call);
180
+ const approvalResponseFor = (responses, call) => responses.flatMap((response) => response._tag === "ToolApprovalResponse" && matchesApproval(response, call) ? [response] : [])[0];
181
+ const questionResponseFor = (responses, call) => responses.flatMap((response) => response._tag === "QuestionResponse" && matchesQuestion(response, call) ? [response] : [])[0];
182
+ const toolApprovalRequest = (tools, call) => ToolApprovalRequest.make({
183
+ requestId: approvalRequestId(call),
184
+ toolCallId: call.id,
185
+ call,
186
+ policy: toolDefFor(tools, call)?.approval
187
+ });
188
+ const deniedToolResult = (call, response) => {
189
+ const reason = response.reason ?? "Denied by user";
190
+ return ToolResult.make({
191
+ toolCallId: call.id,
192
+ content: `Tool call denied: ${reason}`,
193
+ isError: true,
194
+ structuredContent: {
195
+ type: "tool_approval_denied",
196
+ reason,
197
+ source: response.source
198
+ }
199
+ });
200
+ };
201
+ const questionToolResult = (response, questions) => {
202
+ return ToolResult.make({
203
+ toolCallId: response.toolCallId,
204
+ content: formatQuestionResponseContent(response, questions),
205
+ isError: response.outcome === "cancelled" ? true : void 0,
206
+ structuredContent: {
207
+ type: "question_response",
208
+ outcome: response.outcome,
209
+ answers: response.answers ?? [],
210
+ reason: response.reason,
211
+ source: response.source
212
+ }
213
+ });
214
+ };
215
+ const invalidQuestionToolResult = (call) => ToolResult.make({
216
+ toolCallId: call.id,
217
+ content: "Invalid question arguments.",
218
+ isError: true,
219
+ structuredContent: { type: "question_invalid" }
220
+ });
221
+ const prepareQuestionCall = (call, index, responses) => Effect.gen(function* () {
222
+ const decoded = yield* Schema.decodeUnknownEffect(QuestionToolParams)(call.params).pipe(Effect.result);
223
+ if (decoded._tag === "Failure") return {
224
+ _tag: "Result",
225
+ index,
226
+ call,
227
+ result: invalidQuestionToolResult(call),
228
+ events: []
229
+ };
230
+ const response = questionResponseFor(responses, call);
231
+ if (response !== void 0) return {
232
+ _tag: "Result",
233
+ index,
234
+ call,
235
+ result: questionToolResult(response, decoded.success.questions),
236
+ events: [response.outcome === "answered" ? QuestionAnswered.make({ response }) : QuestionCancelled.make({ response })]
237
+ };
238
+ const request = QuestionRequest.make({
239
+ requestId: questionRequestId(call),
240
+ toolCallId: call.id,
241
+ call,
242
+ questions: decoded.success.questions
243
+ });
244
+ return {
245
+ _tag: "Pending",
246
+ request,
247
+ events: [QuestionRequested.make({ request })]
248
+ };
249
+ });
250
+ const prepareApprovalCall = (tools, call, index, responses) => {
251
+ if (!approvalRequired(tools, call)) return {
252
+ _tag: "Execute",
253
+ index,
254
+ call,
255
+ events: []
256
+ };
257
+ const request = toolApprovalRequest(tools, call);
258
+ const response = approvalResponseFor(responses, call);
259
+ if (response === void 0) return {
260
+ _tag: "Pending",
261
+ request,
262
+ events: [ToolApprovalRequested.make({
263
+ call,
264
+ request
265
+ })]
266
+ };
267
+ if (response.decision === "denied") return {
268
+ _tag: "Result",
269
+ index,
270
+ call,
271
+ result: deniedToolResult(call, response),
272
+ events: [ToolApprovalDenied.make({
273
+ toolCallId: call.id,
274
+ reason: response.reason ?? "Denied by user",
275
+ response
276
+ })]
277
+ };
278
+ return {
279
+ _tag: "Execute",
280
+ index,
281
+ call,
282
+ events: [ToolApprovalGranted.make({
283
+ toolCallId: call.id,
284
+ response
285
+ })]
286
+ };
287
+ };
288
+ const prepareToolCall = (input) => input.call.name === questionToolName ? prepareQuestionCall(input.call, input.index, input.responses) : Effect.succeed(prepareApprovalCall(input.tools, input.call, input.index, input.responses));
289
+ const prepareToolBatch = (input) => Effect.gen(function* () {
290
+ const prepared = yield* Effect.forEach(input.calls, (call, index) => prepareToolCall({
291
+ tools: input.tools,
292
+ responses: input.responses,
293
+ call,
294
+ index
295
+ }));
296
+ return {
297
+ callsToExecute: prepared.flatMap((item) => item._tag === "Execute" ? [{
298
+ index: item.index,
299
+ call: item.call
300
+ }] : []),
301
+ resultMessages: prepared.flatMap((item) => item._tag === "Result" ? [{
302
+ index: item.index,
303
+ message: toolResultMessageFromResult(item.result)
304
+ }] : []),
305
+ resultEvents: syntheticToolCompletionEvents(prepared),
306
+ events: prepared.flatMap((item) => item._tag === "Pending" ? [] : item.events),
307
+ pendingRequests: prepared.flatMap((item) => item._tag === "Pending" ? [item.request] : []),
308
+ pendingEvents: prepared.flatMap((item) => item._tag === "Pending" ? item.events : [])
309
+ };
310
+ });
311
+ const orderedToolResultMessages = (results) => [...results].sort((left, right) => left.index - right.index).map((result) => result.message);
312
+ const syntheticToolCompletionEvents = (prepared) => prepared.flatMap((item) => item._tag === "Result" ? [ToolExecutionCompleted.make({
313
+ call: item.call,
314
+ result: item.result
315
+ })] : []);
316
+ const toolResultIds = (messages) => new Set(messages.flatMap((message) => message._tag === "ToolResult" ? [message.toolCallId] : []));
317
+ const pendingHostToolCalls = (messages) => {
318
+ const completed = toolResultIds(messages);
319
+ return messages.flatMap((message) => message._tag === "Assistant" ? assistantHostToolCalls(message).filter((call) => !completed.has(call.id)) : []);
320
+ };
321
+ const nonEmptyHitlRequests = (requests) => {
322
+ const first = requests[0];
323
+ return first === void 0 ? void 0 : [first, ...requests.slice(1)];
324
+ };
325
+ const parallelToolExecutionStream = (input) => Stream.mergeAll(input.calls.map(({ call, index }) => makeToolExecutionStream(input.executor, call, input.model).pipe(Stream.tap((event) => {
326
+ if (event._tag !== "ToolExecutionCompleted") return Effect.void;
327
+ return Ref.update(input.results, (results) => [...results, {
328
+ index,
329
+ message: toolResultMessageFromResult(event.result)
330
+ }]);
331
+ }))), { concurrency: boundedToolConcurrency(input.loopConfig) });
332
+ const toolErrorCode = (error) => {
333
+ switch (error.cause) {
334
+ case "validation":
335
+ case "invalid_input": return "validation_error";
336
+ case "timeout": return "tool_timeout";
337
+ case "permission":
338
+ case "denied": return "tool_denied";
339
+ case "execution":
340
+ case "not_found":
341
+ case "unavailable": return "tool_error";
342
+ }
343
+ };
344
+ const validateTurnCompletion = (events) => {
345
+ const doneEvents = events.filter((event) => event._tag === "Done");
346
+ const toolCalls = collectToolCalls(events);
347
+ const stopReason = toolCalls.length === 0 ? "stop" : "tool_use";
348
+ if (doneEvents.length !== 1) return Effect.fail(new LLMError({
349
+ cause: "invalid_response",
350
+ message: `Expected exactly one LLM done event, received ${doneEvents.length}`,
351
+ retryable: false
352
+ }));
353
+ const doneEvent = doneEvents[0];
354
+ if (doneEvent === void 0 || doneEvent.stopReason !== stopReason) return Effect.fail(new LLMError({
355
+ cause: "invalid_response",
356
+ message: `LLM done reason must be ${stopReason}`,
357
+ retryable: false
358
+ }));
359
+ return Effect.succeed({
360
+ toolCalls,
361
+ stopReason
362
+ });
363
+ };
364
+ const makeAfterLlmStream = (input, llmEventsRef) => Stream.unwrap(Effect.gen(function* () {
365
+ const llmEvents = yield* Ref.get(llmEventsRef);
366
+ const completion = yield* validateTurnCompletion(llmEvents);
367
+ const assistantMessage = accumulateAssistantMessage(llmEvents);
368
+ const turnEndEvents = [LLMStreamEnd.make({ turn: input.turn }), AssistantMessageEvent.make({ message: assistantMessage })];
369
+ yield* Ref.update(input.createdMessages, (messages) => [...messages, assistantMessage]);
370
+ if (completion.toolCalls.length === 0) {
371
+ const messages = yield* Ref.get(input.createdMessages);
372
+ const usage = yield* Ref.get(input.usage);
373
+ return Stream.fromIterable([
374
+ ...turnEndEvents,
375
+ TurnEnd.make({
376
+ turn: input.turn,
377
+ reason: completion.stopReason
378
+ }),
379
+ AgentEnd.make({
380
+ messages,
381
+ turns: input.turn,
382
+ usage
383
+ })
384
+ ]);
385
+ }
386
+ const toolResultMessages = yield* Ref.make([]);
387
+ const prepared = yield* prepareToolBatch({
388
+ tools: input.config.tools,
389
+ responses: input.config.hitlResponses ?? [],
390
+ calls: completion.toolCalls
391
+ });
392
+ if (prepared.resultMessages.length > 0) yield* Ref.update(toolResultMessages, (results) => [...results, ...prepared.resultMessages]);
393
+ if (prepared.pendingRequests.length > 0) {
394
+ const pendingRequests = nonEmptyHitlRequests(prepared.pendingRequests);
395
+ if (pendingRequests === void 0) return Stream.empty;
396
+ const readyResults = orderedToolResultMessages(yield* Ref.get(toolResultMessages));
397
+ if (readyResults.length > 0) yield* Ref.update(input.createdMessages, (messages) => [...messages, ...readyResults]);
398
+ const messages = yield* Ref.get(input.createdMessages);
399
+ const usage = yield* Ref.get(input.usage);
400
+ return Stream.fromIterable([
401
+ ...turnEndEvents,
402
+ ...prepared.events,
403
+ ...prepared.pendingEvents,
404
+ TurnEnd.make({
405
+ turn: input.turn,
406
+ reason: completion.stopReason
407
+ }),
408
+ AgentAwaitingInput.make({
409
+ requests: pendingRequests,
410
+ messages,
411
+ turns: input.turn,
412
+ usage
413
+ })
414
+ ]);
415
+ }
416
+ const toolExecutionStream = parallelToolExecutionStream({
417
+ calls: prepared.callsToExecute,
418
+ executor: input.executor,
419
+ loopConfig: input.loopConfig,
420
+ model: input.config.model,
421
+ results: toolResultMessages
422
+ });
423
+ const nextTurnStream = Stream.unwrap(Ref.get(toolResultMessages).pipe(Effect.flatMap((results) => {
424
+ const orderedResults = orderedToolResultMessages(results);
425
+ return Ref.update(input.createdMessages, (messages) => [...messages, ...orderedResults]).pipe(Effect.as(Stream.make(TurnEnd.make({
426
+ turn: input.turn,
427
+ reason: completion.stopReason
428
+ })).pipe(Stream.concat(makeTurnStream({
429
+ ...input,
430
+ currentMessages: [
431
+ ...input.currentMessages,
432
+ assistantMessage,
433
+ ...orderedResults
434
+ ],
435
+ turn: input.turn + 1
436
+ })))));
437
+ })));
438
+ return Stream.fromIterable(turnEndEvents).pipe(Stream.concat(Stream.fromIterable(prepared.events)), Stream.concat(toolExecutionStream), Stream.concat(nextTurnStream));
439
+ }));
440
+ const makeModelOnlyAfterLlmStream = (input, llmEventsRef) => Stream.unwrap(Effect.gen(function* () {
441
+ const llmEvents = yield* Ref.get(llmEventsRef);
442
+ const completion = yield* validateTurnCompletion(llmEvents);
443
+ const assistantMessage = accumulateAssistantMessage(llmEvents);
444
+ yield* Ref.update(input.createdMessages, (messages) => [...messages, assistantMessage]);
445
+ return Stream.fromIterable([
446
+ LLMStreamEnd.make({ turn: input.turn }),
447
+ AssistantMessageEvent.make({ message: assistantMessage }),
448
+ TurnEnd.make({
449
+ turn: input.turn,
450
+ reason: completion.stopReason
451
+ })
452
+ ]);
453
+ }));
454
+ const makeLlmStream = (input, llmEvents, result) => {
455
+ const makeStream = () => input.provider.stream({
456
+ messages: result.messages,
457
+ tools: input.config.tools,
458
+ model: input.config.model,
459
+ reasoningEffort: input.config.reasoningEffort,
460
+ systemPrompt: input.config.systemPrompt
461
+ });
462
+ return Stream.fromIterable(result.events).pipe(Stream.concat(withProviderRetries(makeStream(), input.loopConfig, makeStream, 1))).pipe(Stream.tap((event) => {
463
+ if (!isLlmEvent(event)) return Effect.void;
464
+ const appendEvent = Ref.update(llmEvents, (events) => [...events, event]);
465
+ if (event._tag !== "Usage") return appendEvent;
466
+ return Ref.update(input.usage, (usage) => addAgentUsage(usage, event.usage)).pipe(Effect.flatMap(() => appendEvent));
467
+ }), Stream.flatMap((event) => event._tag === "AgentRetry" ? Stream.make(event) : isLlmEvent(event) ? Stream.fromIterable(toLlmEvent(event)) : Stream.make(event)), Stream.concat(makeAfterLlmStream(input, llmEvents)));
468
+ };
469
+ const makeModelOnlyLlmStream = (input, llmEvents, result) => {
470
+ const makeStream = () => input.provider.stream({
471
+ messages: result.messages,
472
+ tools: input.config.tools,
473
+ model: input.config.model,
474
+ reasoningEffort: input.config.reasoningEffort,
475
+ systemPrompt: input.config.systemPrompt
476
+ });
477
+ return Stream.fromIterable(result.events).pipe(Stream.concat(withProviderRetries(makeStream(), input.loopConfig, makeStream, 1))).pipe(Stream.tap((event) => {
478
+ if (!isLlmEvent(event)) return Effect.void;
479
+ const appendEvent = Ref.update(llmEvents, (events) => [...events, event]);
480
+ if (event._tag !== "Usage") return appendEvent;
481
+ return Ref.update(input.usage, (usage) => addAgentUsage(usage, event.usage)).pipe(Effect.flatMap(() => appendEvent));
482
+ }), Stream.flatMap((event) => event._tag === "AgentRetry" ? Stream.make(event) : isLlmEvent(event) ? Stream.fromIterable(toLlmEvent(event)) : Stream.make(event)), Stream.concat(makeModelOnlyAfterLlmStream(input, llmEvents)));
483
+ };
484
+ const makeTurnStream = (input) => Stream.suspend(() => {
485
+ if (input.turn > input.loopConfig.maxTurns) return Stream.fail(new AbortError({ reason: "max_turns" }));
486
+ const llmStream = Stream.unwrap(Effect.gen(function* () {
487
+ const llmEvents = yield* Ref.make([]);
488
+ const result = yield* input.contextTransformer.transform(input.currentMessages);
489
+ yield* validateCapabilities(input.config, result.messages);
490
+ return makeLlmStream(input, llmEvents, result);
491
+ }));
492
+ return Stream.fromIterable([TurnStart.make({ turn: input.turn }), LLMStreamStart.make({ turn: input.turn })]).pipe(Stream.concat(llmStream));
493
+ });
494
+ const makePendingToolResumeStream = (input) => Stream.unwrap(Effect.gen(function* () {
495
+ const pendingCalls = pendingHostToolCalls(input.currentMessages);
496
+ if (pendingCalls.length === 0 || (input.config.hitlResponses ?? []).length === 0) return makeTurnStream(input);
497
+ const toolResultMessages = yield* Ref.make([]);
498
+ const prepared = yield* prepareToolBatch({
499
+ tools: input.config.tools,
500
+ responses: input.config.hitlResponses ?? [],
501
+ calls: pendingCalls
502
+ });
503
+ if (prepared.resultMessages.length > 0) yield* Ref.update(toolResultMessages, (results) => [...results, ...prepared.resultMessages]);
504
+ if (prepared.pendingRequests.length > 0) {
505
+ const pendingRequests = nonEmptyHitlRequests(prepared.pendingRequests);
506
+ if (pendingRequests === void 0) return Stream.empty;
507
+ const readyResults = orderedToolResultMessages(yield* Ref.get(toolResultMessages));
508
+ if (readyResults.length > 0) yield* Ref.update(input.createdMessages, (messages) => [...messages, ...readyResults]);
509
+ const messages = yield* Ref.get(input.createdMessages);
510
+ const usage = yield* Ref.get(input.usage);
511
+ return Stream.fromIterable([
512
+ ...prepared.events,
513
+ ...prepared.pendingEvents,
514
+ AgentAwaitingInput.make({
515
+ requests: pendingRequests,
516
+ messages,
517
+ turns: Math.max(0, input.turn - 1),
518
+ usage
519
+ })
520
+ ]);
521
+ }
522
+ const toolExecutionStream = parallelToolExecutionStream({
523
+ calls: prepared.callsToExecute,
524
+ executor: input.executor,
525
+ loopConfig: input.loopConfig,
526
+ model: input.config.model,
527
+ results: toolResultMessages
528
+ });
529
+ const nextTurnStream = Stream.unwrap(Ref.get(toolResultMessages).pipe(Effect.flatMap((results) => {
530
+ const orderedResults = orderedToolResultMessages(results);
531
+ return Ref.update(input.createdMessages, (messages) => [...messages, ...orderedResults]).pipe(Effect.as(makeTurnStream({
532
+ ...input,
533
+ currentMessages: [...input.currentMessages, ...orderedResults]
534
+ })));
535
+ })));
536
+ return Stream.fromIterable(prepared.events).pipe(Stream.concat(toolExecutionStream), Stream.concat(nextTurnStream));
537
+ }));
538
+ const unavailableToolExecutor = { execute: (call) => Effect.fail(new ToolError({
539
+ tool: call.name,
540
+ message: "Tool execution is not available in model turn step",
541
+ cause: "execution"
542
+ })) };
543
+ const makeModelOnlyTurnStream = (input) => Stream.suspend(() => {
544
+ if (input.turn > input.loopConfig.maxTurns) return Stream.fail(new AbortError({ reason: "max_turns" }));
545
+ const llmStream = Stream.unwrap(Effect.gen(function* () {
546
+ const llmEvents = yield* Ref.make([]);
547
+ const result = yield* input.contextTransformer.transform(input.currentMessages);
548
+ yield* validateCapabilities(input.config, result.messages);
549
+ return makeModelOnlyLlmStream(input, llmEvents, result);
550
+ }));
551
+ return Stream.fromIterable([TurnStart.make({ turn: input.turn }), LLMStreamStart.make({ turn: input.turn })]).pipe(Stream.concat(llmStream));
552
+ });
553
+ const runModelTurn = (config) => Stream.unwrap(Effect.gen(function* () {
554
+ const contextTransformer = yield* ContextTransformer;
555
+ const loopConfig = yield* LoopConfig;
556
+ const provider = yield* LLMProvider;
557
+ const createdMessages = yield* Ref.make([]);
558
+ const usage = yield* Ref.make(zeroAgentUsage);
559
+ return makeModelOnlyTurnStream({
560
+ config,
561
+ contextTransformer,
562
+ loopConfig,
563
+ provider,
564
+ executor: unavailableToolExecutor,
565
+ currentMessages: config.messages,
566
+ createdMessages,
567
+ usage,
568
+ turn: config.turn
569
+ });
570
+ }));
571
+ const runToolBatch = (config) => Stream.unwrap(Effect.gen(function* () {
572
+ const executor = yield* ToolExecutor;
573
+ const loopConfig = yield* LoopConfig;
574
+ const toolResultMessages = yield* Ref.make([]);
575
+ const prepared = yield* prepareToolBatch({
576
+ tools: config.tools ?? [],
577
+ responses: config.hitlResponses ?? [],
578
+ calls: config.calls
579
+ });
580
+ const hasPendingRequests = prepared.pendingRequests.length > 0;
581
+ const resultEvents = hasPendingRequests ? [] : prepared.resultEvents;
582
+ const pendingRequests = nonEmptyHitlRequests(prepared.pendingRequests);
583
+ const awaitingEvents = pendingRequests === void 0 ? [] : [AgentAwaitingInput.make({
584
+ requests: pendingRequests,
585
+ messages: config.createdMessages ?? [],
586
+ turns: config.turn ?? 0,
587
+ usage: config.usage ?? zeroAgentUsage
588
+ })];
589
+ return Stream.fromIterable([
590
+ ...prepared.events,
591
+ ...resultEvents,
592
+ ...prepared.pendingEvents,
593
+ ...awaitingEvents
594
+ ]).pipe(Stream.concat(parallelToolExecutionStream({
595
+ calls: hasPendingRequests ? [] : prepared.callsToExecute,
596
+ executor,
597
+ loopConfig,
598
+ model: config.model ?? "",
599
+ results: toolResultMessages
600
+ })));
601
+ }));
602
+ const run = (config) => Stream.unwrap(Effect.gen(function* () {
603
+ const contextTransformer = yield* ContextTransformer;
604
+ const loopConfig = yield* LoopConfig;
605
+ const provider = yield* LLMProvider;
606
+ const executor = yield* ToolExecutor;
607
+ const createdMessages = yield* Ref.make([]);
608
+ const usage = yield* Ref.make(zeroAgentUsage);
609
+ return Stream.make(AgentStart.make({})).pipe(Stream.concat(makePendingToolResumeStream({
610
+ config,
611
+ contextTransformer,
612
+ loopConfig,
613
+ provider,
614
+ executor,
615
+ currentMessages: config.messages,
616
+ createdMessages,
617
+ usage,
618
+ turn: 1
619
+ })));
620
+ }));
621
+ //#endregion
622
+ export { run, runModelTurn, runToolBatch };
623
+
624
+ //# sourceMappingURL=run.mjs.map