@xandreed/plugin-agent-loop 0.3.0 → 0.4.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.
- package/README.md +1 -1
- package/dist/loop.d.ts +19 -2
- package/dist/loop.js +21 -7
- package/dist/plugin.adapter.js +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
package/dist/loop.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { LanguageModel, Prompt } from "@effect/ai";
|
|
1
|
+
import { AiError, LanguageModel, Prompt } from "@effect/ai";
|
|
2
2
|
import type { Tool, Toolkit } from "@effect/ai";
|
|
3
3
|
import { Effect, Option } from "effect";
|
|
4
4
|
import type { LoopEvent } from "@xandreed/core";
|
|
@@ -26,6 +26,23 @@ export interface CompactionPlan {
|
|
|
26
26
|
* `Effect.iterate` — the same discipline as foundry's forge loop.
|
|
27
27
|
*/
|
|
28
28
|
export interface RunLoopOptions<Tools extends Record<string, Tool.Any>, R> {
|
|
29
|
+
/** A host-validated first batch. Runs as step zero through the ordinary
|
|
30
|
+
* Effect AI decoder, instrumented toolkit and durable tail hooks, without
|
|
31
|
+
* another provider request. Provider usage belongs to the host planner. */
|
|
32
|
+
readonly initialStep?: ReadonlyArray<{
|
|
33
|
+
readonly name: keyof Tools & string;
|
|
34
|
+
readonly params: unknown;
|
|
35
|
+
}>;
|
|
36
|
+
/** Host selection at a real provider boundary. Never called for initialStep.
|
|
37
|
+
* The host may cache its choice for a run; tools and completion remain loop-owned. */
|
|
38
|
+
readonly prepareModel?: (input: {
|
|
39
|
+
readonly stepIndex: number;
|
|
40
|
+
readonly messages: ReadonlyArray<AgentMessage>;
|
|
41
|
+
readonly activeTools: ReadonlyArray<string>;
|
|
42
|
+
}) => Effect.Effect<{
|
|
43
|
+
readonly model: LanguageModel.Service;
|
|
44
|
+
readonly system: Prompt.Prompt;
|
|
45
|
+
}, AiError.AiError, R>;
|
|
29
46
|
readonly system: string | Prompt.Prompt;
|
|
30
47
|
/** Explicit opt-in: export prompt and result content on step spans.
|
|
31
48
|
* Hosts own consent, redaction and exporter retention. Disabled by default. */
|
|
@@ -190,4 +207,4 @@ export declare const runLoop: <Tools extends Record<string, Tool.Any>, R = never
|
|
|
190
207
|
})[];
|
|
191
208
|
outcome: "ok" | "partial";
|
|
192
209
|
reason: "completed" | "step-cap" | "degenerate-loop";
|
|
193
|
-
},
|
|
210
|
+
}, AiError.AiError, LanguageModel.LanguageModel | Exclude<R, import("effect/Tracer").ParentSpan> | Exclude<Tool.HandlersFor<Tools>, import("effect/Tracer").ParentSpan>>;
|
package/dist/loop.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { LanguageModel, Prompt } from "@effect/ai";
|
|
1
|
+
import { AiError, LanguageModel, Prompt } from "@effect/ai";
|
|
2
2
|
import { Cause, Effect, Exit, Match, Metric, Option, Ref, Stream } from "effect";
|
|
3
3
|
import { foldStreamParts } from "@xandreed/core";
|
|
4
4
|
import { CurrentAgentStep } from "@xandreed/core";
|
|
@@ -42,7 +42,7 @@ export const foldProgress = (state, sig) => {
|
|
|
42
42
|
yield* onEvent({ type: "turn_start", turnIndex: state.turnIndex });
|
|
43
43
|
const active = options.activeTools === void 0 ? Object.keys(toolkit.tools) : yield* options.activeTools().pipe(Effect.provide(eventContext)), selectedTools = Object.fromEntries(Object.entries(toolkit.tools).filter(([name]) => active.includes(name))), toolNames = Object.keys(selectedTools);
|
|
44
44
|
yield* Effect.annotateCurrentSpan({ "engine.tools.active": toolNames });
|
|
45
|
-
const
|
|
45
|
+
const prepared = state.turnIndex === 0 && options.initialStep !== void 0 ? Option.none() : options.prepareModel === void 0 ? Option.none() : Option.some(yield* options.prepareModel({ stepIndex: state.turnIndex, messages: state.messages, activeTools: toolNames }).pipe(Effect.provide(eventContext))), baseSystem = Option.match(prepared, { onNone: () => options.system, onSome: (value) => value.system }), instructions = typeof baseSystem === "string" ? Prompt.make([{ role: "system", content: baseSystem }]) : baseSystem, prompt = Prompt.merge(instructions, Prompt.make(toPromptMessages(state.messages)));
|
|
46
46
|
if (options.captureTraceContent === !0)
|
|
47
47
|
yield* Effect.annotateCurrentSpan({
|
|
48
48
|
"engine.step.input": JSON.stringify({ messages: prompt.content, activeTools: toolNames })
|
|
@@ -66,14 +66,14 @@ export const foldProgress = (state, sig) => {
|
|
|
66
66
|
})
|
|
67
67
|
},
|
|
68
68
|
concurrency: options.toolConcurrency ?? DEFAULT_TOOL_CONCURRENCY
|
|
69
|
-
}, settled = (streamingHealthy) => LanguageModel.generateText(callOptions).pipe(Effect.map((res) => ({
|
|
69
|
+
}, settled = (streamingHealthy) => (Option.isSome(prepared) ? prepared.value.model.generateText(callOptions) : LanguageModel.generateText(callOptions)).pipe(Effect.map((res) => ({
|
|
70
70
|
_tag: "ok",
|
|
71
71
|
content: res.content,
|
|
72
72
|
finishReason: res.finishReason,
|
|
73
73
|
usage: res.usage,
|
|
74
74
|
streamingHealthy
|
|
75
75
|
}))), streamed = Effect.gen(function* () {
|
|
76
|
-
const partSeen = yield* Ref.make(!1), folded = yield* foldStreamParts(LanguageModel.streamText(callOptions).pipe(Stream.tap(() => Ref.set(partSeen, !0))), (delta) => onEvent({
|
|
76
|
+
const partSeen = yield* Ref.make(!1), folded = yield* foldStreamParts((Option.isSome(prepared) ? prepared.value.model.streamText(callOptions) : LanguageModel.streamText(callOptions)).pipe(Stream.tap(() => Ref.set(partSeen, !0))), (delta) => onEvent({
|
|
77
77
|
type: "assistant_delta",
|
|
78
78
|
turnIndex: state.turnIndex,
|
|
79
79
|
channel: delta.channel,
|
|
@@ -91,7 +91,20 @@ export const foldProgress = (state, sig) => {
|
|
|
91
91
|
onSome: Effect.succeed,
|
|
92
92
|
onNone: () => Effect.logWarning("streaming failed before any part arrived \u2014 falling back to generateText for this run").pipe(Effect.zipRight(settled(!1)))
|
|
93
93
|
});
|
|
94
|
-
}),
|
|
94
|
+
}), planned = state.turnIndex === 0 && options.initialStep !== void 0, initial = Effect.gen(function* () {
|
|
95
|
+
const provider = yield* LanguageModel.make({
|
|
96
|
+
generateText: () => Effect.succeed([...(options.initialStep ?? []).map((call, index) => ({
|
|
97
|
+
type: "tool-call",
|
|
98
|
+
id: `planned:0:${index}`,
|
|
99
|
+
name: call.name,
|
|
100
|
+
params: call.params,
|
|
101
|
+
providerExecuted: !1
|
|
102
|
+
})), { type: "finish", reason: "tool-calls", usage: { inputTokens: void 0, outputTokens: void 0, totalTokens: void 0 } }]),
|
|
103
|
+
streamText: () => Stream.empty
|
|
104
|
+
});
|
|
105
|
+
yield* Effect.annotateCurrentSpan("engine.step.host_planned", !0);
|
|
106
|
+
return yield* settled(state.streamingHealthy).pipe(Effect.provideService(LanguageModel.LanguageModel, provider));
|
|
107
|
+
}), useStreaming = options.streaming === !0 && state.streamingHealthy, outcome = yield* (planned ? initial : useStreaming ? streamed : settled(state.streamingHealthy)).pipe(Effect.catchAll((err) => err?._tag === "MalformedOutput" ? Effect.succeed({ _tag: "malformed", err }) : Effect.fail(err)), Effect.locally(CurrentEmptyResponseTolerance, state.toolCalls > 0));
|
|
95
108
|
if (outcome._tag === "malformed") {
|
|
96
109
|
const streak = state.malformedStreak + 1;
|
|
97
110
|
if (streak >= MAX_MALFORMED)
|
|
@@ -121,10 +134,11 @@ This usually means you called a tool that doesn't exist or used the wrong argume
|
|
|
121
134
|
const content = withToolCallIds(outcome.content, state.turnIndex), usage = extractUsage(outcome.usage, content);
|
|
122
135
|
yield* Effect.annotateCurrentSpan({
|
|
123
136
|
"engine.step.finish_reason": outcome.finishReason,
|
|
124
|
-
"engine.step.
|
|
137
|
+
"engine.step.usage_available": !planned,
|
|
138
|
+
...!planned ? { "engine.step.usage": JSON.stringify(usage) } : {},
|
|
125
139
|
...options.captureTraceContent === !0 ? { "engine.step.output": JSON.stringify({ content, usage, finishReason: outcome.finishReason }) } : {}
|
|
126
140
|
});
|
|
127
|
-
const model = extractModel(content), tail = withUsageOnAssistant(responseToAgentMessages(content), usage, model), positions = yield* options.onTail?.(tail) ?? Effect.succeed([]), assistantAt = tail.findIndex((m) => m.role === "assistant"), assistantPosition = assistantAt >= 0 ? Option.fromNullable(positions[assistantAt]) : Option.none(), text = responseText(content), toolCalls = responseToolCalls(content);
|
|
141
|
+
const model = extractModel(content), tail = planned ? responseToAgentMessages(content) : withUsageOnAssistant(responseToAgentMessages(content), usage, model), positions = yield* options.onTail?.(tail) ?? Effect.succeed([]), assistantAt = tail.findIndex((m) => m.role === "assistant"), assistantPosition = assistantAt >= 0 ? Option.fromNullable(positions[assistantAt]) : Option.none(), text = responseText(content), toolCalls = responseToolCalls(content);
|
|
128
142
|
yield* onEvent({
|
|
129
143
|
type: "assistant_message",
|
|
130
144
|
turnIndex: state.turnIndex,
|
package/dist/plugin.adapter.js
CHANGED
|
@@ -5,7 +5,7 @@ import { runLoop } from "./loop.js";
|
|
|
5
5
|
const Config = Schema.Struct({ maxSteps: Schema.Int.pipe(Schema.between(1, 1000)), toolConcurrency: Schema.Int.pipe(Schema.between(1, 32)), streaming: Schema.Boolean });
|
|
6
6
|
export const agentLoopPlugin = definePlugin({
|
|
7
7
|
id: "@xandreed/plugin-agent-loop",
|
|
8
|
-
version: "0.
|
|
8
|
+
version: "0.4.0",
|
|
9
9
|
config: Config,
|
|
10
10
|
defaults: { maxSteps: 100, toolConcurrency: 1, streaming: !0 },
|
|
11
11
|
requires: [LanguageModel.LanguageModel, AgentTools, SessionStore, Memory, ContextManager],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xandreed/plugin-agent-loop",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"exports": {
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
}
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"@xandreed/core": "0.
|
|
13
|
+
"@xandreed/core": "0.4.0",
|
|
14
14
|
"@effect/ai": "0.35.0",
|
|
15
15
|
"effect": "3.21.4"
|
|
16
16
|
},
|