@openharness/core 0.4.0 → 0.4.2
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 +116 -0
- package/dist/__tests__/agent-registry.test.d.ts +2 -0
- package/dist/__tests__/agent-registry.test.d.ts.map +1 -0
- package/dist/__tests__/agent-registry.test.js +429 -0
- package/dist/__tests__/agent-registry.test.js.map +1 -0
- package/dist/__tests__/conversation.test.d.ts +2 -0
- package/dist/__tests__/conversation.test.d.ts.map +1 -0
- package/dist/__tests__/conversation.test.js +127 -0
- package/dist/__tests__/conversation.test.js.map +1 -0
- package/dist/__tests__/messages.test.js +15 -2
- package/dist/__tests__/messages.test.js.map +1 -1
- package/dist/__tests__/middleware/compaction.test.d.ts +2 -0
- package/dist/__tests__/middleware/compaction.test.d.ts.map +1 -0
- package/dist/__tests__/middleware/compaction.test.js +163 -0
- package/dist/__tests__/middleware/compaction.test.js.map +1 -0
- package/dist/__tests__/middleware/hooks.test.d.ts +2 -0
- package/dist/__tests__/middleware/hooks.test.d.ts.map +1 -0
- package/dist/__tests__/middleware/hooks.test.js +97 -0
- package/dist/__tests__/middleware/hooks.test.js.map +1 -0
- package/dist/__tests__/middleware/persistence.test.d.ts +2 -0
- package/dist/__tests__/middleware/persistence.test.d.ts.map +1 -0
- package/dist/__tests__/middleware/persistence.test.js +70 -0
- package/dist/__tests__/middleware/persistence.test.js.map +1 -0
- package/dist/__tests__/middleware/retry.test.d.ts +2 -0
- package/dist/__tests__/middleware/retry.test.d.ts.map +1 -0
- package/dist/__tests__/middleware/retry.test.js +151 -0
- package/dist/__tests__/middleware/retry.test.js.map +1 -0
- package/dist/__tests__/middleware/turn-tracking.test.d.ts +2 -0
- package/dist/__tests__/middleware/turn-tracking.test.d.ts.map +1 -0
- package/dist/__tests__/middleware/turn-tracking.test.js +70 -0
- package/dist/__tests__/middleware/turn-tracking.test.js.map +1 -0
- package/dist/__tests__/runner.test.d.ts +2 -0
- package/dist/__tests__/runner.test.d.ts.map +1 -0
- package/dist/__tests__/runner.test.js +111 -0
- package/dist/__tests__/runner.test.js.map +1 -0
- package/dist/__tests__/stream.test.d.ts +2 -0
- package/dist/__tests__/stream.test.d.ts.map +1 -0
- package/dist/__tests__/stream.test.js +100 -0
- package/dist/__tests__/stream.test.js.map +1 -0
- package/dist/__tests__/ui-stream.test.js +105 -0
- package/dist/__tests__/ui-stream.test.js.map +1 -1
- package/dist/agent-registry.d.ts +90 -0
- package/dist/agent-registry.d.ts.map +1 -0
- package/dist/agent-registry.js +217 -0
- package/dist/agent-registry.js.map +1 -0
- package/dist/agent.d.ts +13 -0
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +186 -41
- package/dist/agent.js.map +1 -1
- package/dist/conversation.d.ts +46 -0
- package/dist/conversation.d.ts.map +1 -0
- package/dist/conversation.js +66 -0
- package/dist/conversation.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -1
- package/dist/middleware/compaction.d.ts +20 -0
- package/dist/middleware/compaction.d.ts.map +1 -0
- package/dist/middleware/compaction.js +73 -0
- package/dist/middleware/compaction.js.map +1 -0
- package/dist/middleware/hooks.d.ts +10 -0
- package/dist/middleware/hooks.d.ts.map +1 -0
- package/dist/middleware/hooks.js +36 -0
- package/dist/middleware/hooks.js.map +1 -0
- package/dist/middleware/index.d.ts +6 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +6 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/middleware/persistence.d.ts +12 -0
- package/dist/middleware/persistence.d.ts.map +1 -0
- package/dist/middleware/persistence.js +15 -0
- package/dist/middleware/persistence.js.map +1 -0
- package/dist/middleware/retry.d.ts +8 -0
- package/dist/middleware/retry.d.ts.map +1 -0
- package/dist/middleware/retry.js +70 -0
- package/dist/middleware/retry.js.map +1 -0
- package/dist/middleware/turn-tracking.d.ts +7 -0
- package/dist/middleware/turn-tracking.d.ts.map +1 -0
- package/dist/middleware/turn-tracking.js +25 -0
- package/dist/middleware/turn-tracking.js.map +1 -0
- package/dist/runner.d.ts +23 -0
- package/dist/runner.d.ts.map +1 -0
- package/dist/runner.js +18 -0
- package/dist/runner.js.map +1 -0
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +7 -40
- package/dist/session.js.map +1 -1
- package/dist/stream.d.ts +12 -0
- package/dist/stream.d.ts.map +1 -0
- package/dist/stream.js +38 -0
- package/dist/stream.js.map +1 -0
- package/dist/ui-stream.d.ts.map +1 -1
- package/dist/ui-stream.js +13 -1
- package/dist/ui-stream.js.map +1 -1
- package/dist/utils.d.ts +8 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +40 -0
- package/dist/utils.js.map +1 -0
- package/package.json +13 -2
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { withRetry } from "../../middleware/retry.js";
|
|
3
|
+
// ── Helpers ─────────────────────────────────────────────────────────
|
|
4
|
+
async function collect(gen) {
|
|
5
|
+
const result = [];
|
|
6
|
+
for await (const event of gen)
|
|
7
|
+
result.push(event);
|
|
8
|
+
return result;
|
|
9
|
+
}
|
|
10
|
+
const doneEvent = {
|
|
11
|
+
type: "done",
|
|
12
|
+
result: "complete",
|
|
13
|
+
messages: [],
|
|
14
|
+
totalUsage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 },
|
|
15
|
+
};
|
|
16
|
+
// ── Tests ───────────────────────────────────────────────────────────
|
|
17
|
+
describe("withRetry", () => {
|
|
18
|
+
it("passes through events on success", async () => {
|
|
19
|
+
const runner = async function* () {
|
|
20
|
+
yield { type: "text.delta", text: "hello" };
|
|
21
|
+
yield doneEvent;
|
|
22
|
+
};
|
|
23
|
+
const retried = withRetry({ maxRetries: 3, initialDelayMs: 1 })(runner);
|
|
24
|
+
const events = await collect(retried([], "test"));
|
|
25
|
+
expect(events).toHaveLength(2);
|
|
26
|
+
expect(events[0].type).toBe("text.delta");
|
|
27
|
+
expect(events[1].type).toBe("done");
|
|
28
|
+
});
|
|
29
|
+
it("retries on retryable error before content", async () => {
|
|
30
|
+
let callCount = 0;
|
|
31
|
+
const runner = async function* () {
|
|
32
|
+
callCount++;
|
|
33
|
+
if (callCount === 1) {
|
|
34
|
+
yield { type: "error", error: new Error("429 rate limit") };
|
|
35
|
+
yield {
|
|
36
|
+
type: "done",
|
|
37
|
+
result: "error",
|
|
38
|
+
messages: [],
|
|
39
|
+
totalUsage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
|
|
40
|
+
};
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
yield { type: "text.delta", text: "success" };
|
|
44
|
+
yield doneEvent;
|
|
45
|
+
};
|
|
46
|
+
const retried = withRetry({ maxRetries: 3, initialDelayMs: 1 })(runner);
|
|
47
|
+
const events = await collect(retried([], "test"));
|
|
48
|
+
expect(callCount).toBe(2);
|
|
49
|
+
const retryEvents = events.filter((e) => e.type === "retry");
|
|
50
|
+
expect(retryEvents).toHaveLength(1);
|
|
51
|
+
expect(retryEvents[0].attempt).toBe(0);
|
|
52
|
+
});
|
|
53
|
+
it("does not retry after content has been yielded", async () => {
|
|
54
|
+
let callCount = 0;
|
|
55
|
+
const runner = async function* () {
|
|
56
|
+
callCount++;
|
|
57
|
+
yield { type: "text.delta", text: "content" };
|
|
58
|
+
yield { type: "error", error: new Error("429 rate limit") };
|
|
59
|
+
yield doneEvent;
|
|
60
|
+
};
|
|
61
|
+
const retried = withRetry({ maxRetries: 3, initialDelayMs: 1 })(runner);
|
|
62
|
+
const events = await collect(retried([], "test"));
|
|
63
|
+
expect(callCount).toBe(1);
|
|
64
|
+
expect(events.filter((e) => e.type === "retry")).toHaveLength(0);
|
|
65
|
+
// Error should pass through
|
|
66
|
+
expect(events.find((e) => e.type === "error")).toBeDefined();
|
|
67
|
+
});
|
|
68
|
+
it("does not retry non-retryable errors", async () => {
|
|
69
|
+
let callCount = 0;
|
|
70
|
+
const runner = async function* () {
|
|
71
|
+
callCount++;
|
|
72
|
+
yield { type: "error", error: new Error("invalid input") };
|
|
73
|
+
yield {
|
|
74
|
+
type: "done",
|
|
75
|
+
result: "error",
|
|
76
|
+
messages: [],
|
|
77
|
+
totalUsage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
const retried = withRetry({ maxRetries: 3, initialDelayMs: 1 })(runner);
|
|
81
|
+
const events = await collect(retried([], "test"));
|
|
82
|
+
expect(callCount).toBe(1);
|
|
83
|
+
expect(events.filter((e) => e.type === "retry")).toHaveLength(0);
|
|
84
|
+
});
|
|
85
|
+
it("retries on thrown retryable errors", async () => {
|
|
86
|
+
let callCount = 0;
|
|
87
|
+
const runner = async function* () {
|
|
88
|
+
callCount++;
|
|
89
|
+
if (callCount === 1) {
|
|
90
|
+
throw new Error("503 service unavailable");
|
|
91
|
+
}
|
|
92
|
+
yield doneEvent;
|
|
93
|
+
};
|
|
94
|
+
const retried = withRetry({ maxRetries: 3, initialDelayMs: 1 })(runner);
|
|
95
|
+
const events = await collect(retried([], "test"));
|
|
96
|
+
expect(callCount).toBe(2);
|
|
97
|
+
expect(events.filter((e) => e.type === "retry")).toHaveLength(1);
|
|
98
|
+
});
|
|
99
|
+
it("throws non-retryable thrown errors", async () => {
|
|
100
|
+
const runner = async function* () {
|
|
101
|
+
throw new Error("bug in code");
|
|
102
|
+
};
|
|
103
|
+
const retried = withRetry({ maxRetries: 3, initialDelayMs: 1 })(runner);
|
|
104
|
+
await expect(collect(retried([], "test"))).rejects.toThrow("bug in code");
|
|
105
|
+
});
|
|
106
|
+
it("gives up after maxRetries", async () => {
|
|
107
|
+
let callCount = 0;
|
|
108
|
+
const runner = async function* () {
|
|
109
|
+
callCount++;
|
|
110
|
+
yield { type: "error", error: new Error("429 rate limit") };
|
|
111
|
+
yield {
|
|
112
|
+
type: "done",
|
|
113
|
+
result: "error",
|
|
114
|
+
messages: [],
|
|
115
|
+
totalUsage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
const retried = withRetry({ maxRetries: 2, initialDelayMs: 1 })(runner);
|
|
119
|
+
const events = await collect(retried([], "test"));
|
|
120
|
+
// 1 initial + 2 retries = 3 calls
|
|
121
|
+
expect(callCount).toBe(3);
|
|
122
|
+
// Last attempt's error should pass through (not retried)
|
|
123
|
+
const errorEvents = events.filter((e) => e.type === "error");
|
|
124
|
+
expect(errorEvents).toHaveLength(1);
|
|
125
|
+
});
|
|
126
|
+
it("supports custom isRetryable", async () => {
|
|
127
|
+
let callCount = 0;
|
|
128
|
+
const runner = async function* () {
|
|
129
|
+
callCount++;
|
|
130
|
+
if (callCount === 1) {
|
|
131
|
+
yield { type: "error", error: new Error("custom-retryable") };
|
|
132
|
+
yield {
|
|
133
|
+
type: "done",
|
|
134
|
+
result: "error",
|
|
135
|
+
messages: [],
|
|
136
|
+
totalUsage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
|
|
137
|
+
};
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
yield doneEvent;
|
|
141
|
+
};
|
|
142
|
+
const retried = withRetry({
|
|
143
|
+
maxRetries: 3,
|
|
144
|
+
initialDelayMs: 1,
|
|
145
|
+
isRetryable: (e) => e.message.includes("custom-retryable"),
|
|
146
|
+
})(runner);
|
|
147
|
+
await collect(retried([], "test"));
|
|
148
|
+
expect(callCount).toBe(2);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
//# sourceMappingURL=retry.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry.test.js","sourceRoot":"","sources":["../../../src/__tests__/middleware/retry.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"turn-tracking.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/middleware/turn-tracking.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { withTurnTracking } from "../../middleware/turn-tracking.js";
|
|
3
|
+
// ── Helpers ─────────────────────────────────────────────────────────
|
|
4
|
+
function createMockRunner(events) {
|
|
5
|
+
return async function* () {
|
|
6
|
+
for (const event of events)
|
|
7
|
+
yield event;
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
async function collect(gen) {
|
|
11
|
+
const result = [];
|
|
12
|
+
for await (const event of gen)
|
|
13
|
+
result.push(event);
|
|
14
|
+
return result;
|
|
15
|
+
}
|
|
16
|
+
const doneEvent = {
|
|
17
|
+
type: "done",
|
|
18
|
+
result: "complete",
|
|
19
|
+
messages: [],
|
|
20
|
+
totalUsage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 },
|
|
21
|
+
};
|
|
22
|
+
// ── Tests ───────────────────────────────────────────────────────────
|
|
23
|
+
describe("withTurnTracking", () => {
|
|
24
|
+
it("wraps events with turn.start and turn.done", async () => {
|
|
25
|
+
const runner = createMockRunner([
|
|
26
|
+
{ type: "text.delta", text: "hi" },
|
|
27
|
+
doneEvent,
|
|
28
|
+
]);
|
|
29
|
+
const tracked = withTurnTracking()(runner);
|
|
30
|
+
const events = await collect(tracked([], "test"));
|
|
31
|
+
expect(events[0]).toEqual({ type: "turn.start", turnNumber: 1 });
|
|
32
|
+
expect(events[events.length - 1]).toMatchObject({
|
|
33
|
+
type: "turn.done",
|
|
34
|
+
turnNumber: 1,
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
it("increments turn counter across calls", async () => {
|
|
38
|
+
const runner = createMockRunner([doneEvent]);
|
|
39
|
+
const tracked = withTurnTracking()(runner);
|
|
40
|
+
const events1 = await collect(tracked([], "first"));
|
|
41
|
+
const events2 = await collect(tracked([], "second"));
|
|
42
|
+
expect(events1[0]).toEqual({ type: "turn.start", turnNumber: 1 });
|
|
43
|
+
expect(events2[0]).toEqual({ type: "turn.start", turnNumber: 2 });
|
|
44
|
+
});
|
|
45
|
+
it("reports usage from done event in turn.done", async () => {
|
|
46
|
+
const runner = createMockRunner([doneEvent]);
|
|
47
|
+
const tracked = withTurnTracking()(runner);
|
|
48
|
+
const events = await collect(tracked([], "test"));
|
|
49
|
+
const turnDone = events.find((e) => e.type === "turn.done");
|
|
50
|
+
expect(turnDone.usage).toEqual({
|
|
51
|
+
inputTokens: 10,
|
|
52
|
+
outputTokens: 5,
|
|
53
|
+
totalTokens: 15,
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
it("passes through all inner events", async () => {
|
|
57
|
+
const innerEvents = [
|
|
58
|
+
{ type: "text.delta", text: "a" },
|
|
59
|
+
{ type: "text.done", text: "a" },
|
|
60
|
+
doneEvent,
|
|
61
|
+
];
|
|
62
|
+
const runner = createMockRunner(innerEvents);
|
|
63
|
+
const tracked = withTurnTracking()(runner);
|
|
64
|
+
const events = await collect(tracked([], "test"));
|
|
65
|
+
// turn.start + 3 inner events + turn.done
|
|
66
|
+
expect(events).toHaveLength(5);
|
|
67
|
+
expect(events.slice(1, 4)).toEqual(innerEvents);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
//# sourceMappingURL=turn-tracking.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"turn-tracking.test.js","sourceRoot":"","sources":["../../../src/__tests__/middleware/turn-tracking.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAG9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AAErE,uEAAuE;AAEvE,SAAS,gBAAgB,CAAC,MAAoB;IAC5C,OAAO,KAAK,SAAS,CAAC;QACpB,KAAK,MAAM,KAAK,IAAI,MAAM;YAAE,MAAM,KAAK,CAAC;IAC1C,CAAC,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,OAAO,CAAC,GAAwB;IAC7C,MAAM,MAAM,GAAU,EAAE,CAAC;IACzB,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG;QAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAClD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,SAAS,GAAe;IAC5B,IAAI,EAAE,MAAM;IACZ,MAAM,EAAE,UAAU;IAClB,QAAQ,EAAE,EAAE;IACZ,UAAU,EAAE,EAAE,WAAW,EAAE,EAAE,EAAE,YAAY,EAAE,CAAC,EAAE,WAAW,EAAE,EAAE,EAAE;CAClE,CAAC;AAEF,uEAAuE;AAEvE,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;IAChC,EAAE,CAAC,4CAA4C,EAAE,KAAK,IAAI,EAAE;QAC1D,MAAM,MAAM,GAAG,gBAAgB,CAAC;YAC9B,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,IAAI,EAAE;YAClC,SAAS;SACV,CAAC,CAAC;QACH,MAAM,OAAO,GAAG,gBAAgB,EAAE,CAAC,MAAM,CAAC,CAAC;QAC3C,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC;QAElD,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC,CAAC;QACjE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC;YAC9C,IAAI,EAAE,WAAW;YACjB,UAAU,EAAE,CAAC;SACd,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sCAAsC,EAAE,KAAK,IAAI,EAAE;QACpD,MAAM,MAAM,GAAG,gBAAgB,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;QAC7C,MAAM,OAAO,GAAG,gBAAgB,EAAE,CAAC,MAAM,CAAC,CAAC;QAE3C,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC;QACpD,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC,CAAC;QAErD,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC,CAAC;QAClE,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC,CAAC;IACpE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4CAA4C,EAAE,KAAK,IAAI,EAAE;QAC1D,MAAM,MAAM,GAAG,gBAAgB,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;QAC7C,MAAM,OAAO,GAAG,gBAAgB,EAAE,CAAC,MAAM,CAAC,CAAC;QAC3C,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC;QAElD,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC;QACjE,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC;YAC7B,WAAW,EAAE,EAAE;YACf,YAAY,EAAE,CAAC;YACf,WAAW,EAAE,EAAE;SAChB,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iCAAiC,EAAE,KAAK,IAAI,EAAE;QAC/C,MAAM,WAAW,GAAiB;YAChC,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,GAAG,EAAE;YACjC,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,GAAG,EAAE;YAChC,SAAS;SACV,CAAC;QACF,MAAM,MAAM,GAAG,gBAAgB,CAAC,WAAW,CAAC,CAAC;QAC7C,MAAM,OAAO,GAAG,gBAAgB,EAAE,CAAC,MAAM,CAAC,CAAC;QAC3C,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC;QAElD,0CAA0C;QAC1C,MAAM,CAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAC/B,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runner.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/runner.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
import { toRunner, pipe, apply } from "../runner.js";
|
|
3
|
+
// ── Helpers ─────────────────────────────────────────────────────────
|
|
4
|
+
function createMockRunner(events) {
|
|
5
|
+
return async function* () {
|
|
6
|
+
for (const event of events)
|
|
7
|
+
yield event;
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
async function collect(gen) {
|
|
11
|
+
const result = [];
|
|
12
|
+
for await (const event of gen)
|
|
13
|
+
result.push(event);
|
|
14
|
+
return result;
|
|
15
|
+
}
|
|
16
|
+
const doneEvent = {
|
|
17
|
+
type: "done",
|
|
18
|
+
result: "complete",
|
|
19
|
+
messages: [],
|
|
20
|
+
totalUsage: { inputTokens: 10, outputTokens: 5, totalTokens: 15 },
|
|
21
|
+
};
|
|
22
|
+
// ── Tests ───────────────────────────────────────────────────────────
|
|
23
|
+
describe("runner", () => {
|
|
24
|
+
describe("toRunner", () => {
|
|
25
|
+
it("wraps an Agent-like object into a Runner", async () => {
|
|
26
|
+
const mockAgent = {
|
|
27
|
+
run: vi.fn(async function* () {
|
|
28
|
+
yield { type: "text.delta", text: "hello" };
|
|
29
|
+
yield doneEvent;
|
|
30
|
+
}),
|
|
31
|
+
};
|
|
32
|
+
const runner = toRunner(mockAgent);
|
|
33
|
+
const events = await collect(runner([], "test"));
|
|
34
|
+
expect(events).toHaveLength(2);
|
|
35
|
+
expect(events[0]).toEqual({ type: "text.delta", text: "hello" });
|
|
36
|
+
expect(mockAgent.run).toHaveBeenCalledWith([], "test", undefined);
|
|
37
|
+
});
|
|
38
|
+
it("passes history, input, and options through", async () => {
|
|
39
|
+
const mockAgent = {
|
|
40
|
+
run: vi.fn(async function* () {
|
|
41
|
+
yield doneEvent;
|
|
42
|
+
}),
|
|
43
|
+
};
|
|
44
|
+
const history = [{ role: "user", content: "hi" }];
|
|
45
|
+
const signal = new AbortController().signal;
|
|
46
|
+
const runner = toRunner(mockAgent);
|
|
47
|
+
await collect(runner(history, "test", { signal }));
|
|
48
|
+
expect(mockAgent.run).toHaveBeenCalledWith(history, "test", { signal });
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
describe("pipe", () => {
|
|
52
|
+
it("returns identity when no middleware provided", async () => {
|
|
53
|
+
const runner = createMockRunner([doneEvent]);
|
|
54
|
+
const piped = pipe()(runner);
|
|
55
|
+
const events = await collect(piped([], "test"));
|
|
56
|
+
expect(events).toEqual([doneEvent]);
|
|
57
|
+
});
|
|
58
|
+
it("composes middleware outermost first", async () => {
|
|
59
|
+
const order = [];
|
|
60
|
+
const outer = (inner) => async function* (h, i, o) {
|
|
61
|
+
order.push("outer-before");
|
|
62
|
+
yield* inner(h, i, o);
|
|
63
|
+
order.push("outer-after");
|
|
64
|
+
};
|
|
65
|
+
const inner = (inner) => async function* (h, i, o) {
|
|
66
|
+
order.push("inner-before");
|
|
67
|
+
yield* inner(h, i, o);
|
|
68
|
+
order.push("inner-after");
|
|
69
|
+
};
|
|
70
|
+
const runner = createMockRunner([doneEvent]);
|
|
71
|
+
const piped = pipe(outer, inner)(runner);
|
|
72
|
+
await collect(piped([], "test"));
|
|
73
|
+
expect(order).toEqual([
|
|
74
|
+
"outer-before",
|
|
75
|
+
"inner-before",
|
|
76
|
+
"inner-after",
|
|
77
|
+
"outer-after",
|
|
78
|
+
]);
|
|
79
|
+
});
|
|
80
|
+
it("composes three middleware in correct order", async () => {
|
|
81
|
+
const tags = [];
|
|
82
|
+
const mw = (tag) => (inner) => async function* (h, i, o) {
|
|
83
|
+
tags.push(`${tag}-in`);
|
|
84
|
+
yield* inner(h, i, o);
|
|
85
|
+
tags.push(`${tag}-out`);
|
|
86
|
+
};
|
|
87
|
+
const runner = createMockRunner([doneEvent]);
|
|
88
|
+
const piped = pipe(mw("a"), mw("b"), mw("c"))(runner);
|
|
89
|
+
await collect(piped([], "test"));
|
|
90
|
+
expect(tags).toEqual(["a-in", "b-in", "c-in", "c-out", "b-out", "a-out"]);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
describe("apply", () => {
|
|
94
|
+
it("is shorthand for pipe(...mw)(runner)", async () => {
|
|
95
|
+
const addPrefix = (inner) => async function* (h, i, o) {
|
|
96
|
+
yield { type: "text.delta", text: "[prefix] " };
|
|
97
|
+
yield* inner(h, i, o);
|
|
98
|
+
};
|
|
99
|
+
const runner = createMockRunner([
|
|
100
|
+
{ type: "text.delta", text: "hello" },
|
|
101
|
+
doneEvent,
|
|
102
|
+
]);
|
|
103
|
+
const applied = apply(runner, addPrefix);
|
|
104
|
+
const events = await collect(applied([], "test"));
|
|
105
|
+
expect(events).toHaveLength(3);
|
|
106
|
+
expect(events[0]).toEqual({ type: "text.delta", text: "[prefix] " });
|
|
107
|
+
expect(events[1]).toEqual({ type: "text.delta", text: "hello" });
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
//# sourceMappingURL=runner.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runner.test.js","sourceRoot":"","sources":["../../src/__tests__/runner.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/stream.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { tap, filter, map, takeUntil } from "../stream.js";
|
|
3
|
+
// ── Helpers ─────────────────────────────────────────────────────────
|
|
4
|
+
async function collect(gen) {
|
|
5
|
+
const result = [];
|
|
6
|
+
for await (const event of gen)
|
|
7
|
+
result.push(event);
|
|
8
|
+
return result;
|
|
9
|
+
}
|
|
10
|
+
async function* fromArray(events) {
|
|
11
|
+
for (const event of events)
|
|
12
|
+
yield event;
|
|
13
|
+
}
|
|
14
|
+
const doneEvent = {
|
|
15
|
+
type: "done",
|
|
16
|
+
result: "complete",
|
|
17
|
+
messages: [],
|
|
18
|
+
totalUsage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
|
|
19
|
+
};
|
|
20
|
+
// ── Tests ───────────────────────────────────────────────────────────
|
|
21
|
+
describe("stream combinators", () => {
|
|
22
|
+
describe("tap", () => {
|
|
23
|
+
it("calls fn for each event without modifying the stream", async () => {
|
|
24
|
+
const seen = [];
|
|
25
|
+
const events = [
|
|
26
|
+
{ type: "text.delta", text: "a" },
|
|
27
|
+
{ type: "text.delta", text: "b" },
|
|
28
|
+
doneEvent,
|
|
29
|
+
];
|
|
30
|
+
const result = await collect(tap((e) => seen.push(e.type))(fromArray(events)));
|
|
31
|
+
expect(result).toEqual(events);
|
|
32
|
+
expect(seen).toEqual(["text.delta", "text.delta", "done"]);
|
|
33
|
+
});
|
|
34
|
+
it("returns all events unchanged", async () => {
|
|
35
|
+
const events = [
|
|
36
|
+
{ type: "step.start", stepNumber: 1 },
|
|
37
|
+
doneEvent,
|
|
38
|
+
];
|
|
39
|
+
const result = await collect(tap(() => { })(fromArray(events)));
|
|
40
|
+
expect(result).toEqual(events);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
describe("filter", () => {
|
|
44
|
+
it("drops events that don't match the predicate", async () => {
|
|
45
|
+
const events = [
|
|
46
|
+
{ type: "text.delta", text: "a" },
|
|
47
|
+
{ type: "reasoning.delta", text: "think" },
|
|
48
|
+
{ type: "text.delta", text: "b" },
|
|
49
|
+
doneEvent,
|
|
50
|
+
];
|
|
51
|
+
const result = await collect(filter((e) => e.type !== "reasoning.delta")(fromArray(events)));
|
|
52
|
+
expect(result).toHaveLength(3);
|
|
53
|
+
expect(result.map((e) => e.type)).toEqual(["text.delta", "text.delta", "done"]);
|
|
54
|
+
});
|
|
55
|
+
it("never filters done events", async () => {
|
|
56
|
+
const events = [
|
|
57
|
+
{ type: "text.delta", text: "a" },
|
|
58
|
+
doneEvent,
|
|
59
|
+
];
|
|
60
|
+
// Predicate rejects everything
|
|
61
|
+
const result = await collect(filter(() => false)(fromArray(events)));
|
|
62
|
+
expect(result).toHaveLength(1);
|
|
63
|
+
expect(result[0].type).toBe("done");
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
describe("map", () => {
|
|
67
|
+
it("transforms each event", async () => {
|
|
68
|
+
const events = [
|
|
69
|
+
{ type: "text.delta", text: "hello" },
|
|
70
|
+
doneEvent,
|
|
71
|
+
];
|
|
72
|
+
const result = await collect(map((e) => (e.type === "text.delta" ? { ...e, text: e.text.toUpperCase() } : e))(fromArray(events)));
|
|
73
|
+
expect(result[0]).toEqual({ type: "text.delta", text: "HELLO" });
|
|
74
|
+
expect(result[1]).toEqual(doneEvent);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
describe("takeUntil", () => {
|
|
78
|
+
it("stops after predicate matches (inclusive)", async () => {
|
|
79
|
+
const events = [
|
|
80
|
+
{ type: "text.delta", text: "a" },
|
|
81
|
+
{ type: "text.delta", text: "b" },
|
|
82
|
+
{ type: "text.done", text: "ab" },
|
|
83
|
+
{ type: "text.delta", text: "c" },
|
|
84
|
+
doneEvent,
|
|
85
|
+
];
|
|
86
|
+
const result = await collect(takeUntil((e) => e.type === "text.done")(fromArray(events)));
|
|
87
|
+
expect(result).toHaveLength(3);
|
|
88
|
+
expect(result[2]).toEqual({ type: "text.done", text: "ab" });
|
|
89
|
+
});
|
|
90
|
+
it("yields all events if predicate never matches", async () => {
|
|
91
|
+
const events = [
|
|
92
|
+
{ type: "text.delta", text: "a" },
|
|
93
|
+
doneEvent,
|
|
94
|
+
];
|
|
95
|
+
const result = await collect(takeUntil(() => false)(fromArray(events)));
|
|
96
|
+
expect(result).toEqual(events);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
//# sourceMappingURL=stream.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stream.test.js","sourceRoot":"","sources":["../../src/__tests__/stream.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -257,6 +257,111 @@ describe("sessionEventsToUIStream", () => {
|
|
|
257
257
|
expect(subError).toHaveLength(1);
|
|
258
258
|
expect(subError[0].data.error).toBe("Agent crashed");
|
|
259
259
|
});
|
|
260
|
+
it("emits data-oh:subagent.start for background task tool calls", async () => {
|
|
261
|
+
const chunks = await collectChunks([
|
|
262
|
+
{
|
|
263
|
+
type: "tool.start",
|
|
264
|
+
toolCallId: "tc-bg",
|
|
265
|
+
toolName: "task",
|
|
266
|
+
input: { agent: "researcher", prompt: "find info", background: true },
|
|
267
|
+
},
|
|
268
|
+
]);
|
|
269
|
+
const subStart = findChunks(chunks, "data-oh:subagent.start");
|
|
270
|
+
expect(subStart).toHaveLength(1);
|
|
271
|
+
expect(subStart[0].data).toEqual({
|
|
272
|
+
agentName: "researcher",
|
|
273
|
+
task: "find info",
|
|
274
|
+
path: ["researcher"],
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
it("suppresses data-oh:subagent.done for background task tool calls", async () => {
|
|
278
|
+
const chunks = await collectChunks([
|
|
279
|
+
{
|
|
280
|
+
type: "tool.start",
|
|
281
|
+
toolCallId: "tc-bg",
|
|
282
|
+
toolName: "task",
|
|
283
|
+
input: { agent: "researcher", prompt: "find info", background: true },
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
type: "tool.done",
|
|
287
|
+
toolCallId: "tc-bg",
|
|
288
|
+
toolName: "task",
|
|
289
|
+
output: '<background_spawn agent_id="bg-1">spawned</background_spawn>',
|
|
290
|
+
},
|
|
291
|
+
]);
|
|
292
|
+
const subDone = findChunks(chunks, "data-oh:subagent.done");
|
|
293
|
+
expect(subDone).toHaveLength(0);
|
|
294
|
+
});
|
|
295
|
+
it("still emits data-oh:subagent.done for foreground task tool calls", async () => {
|
|
296
|
+
const chunks = await collectChunks([
|
|
297
|
+
{
|
|
298
|
+
type: "tool.start",
|
|
299
|
+
toolCallId: "tc-fg",
|
|
300
|
+
toolName: "task",
|
|
301
|
+
input: { agent: "coder", prompt: "fix bug" },
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
type: "tool.done",
|
|
305
|
+
toolCallId: "tc-fg",
|
|
306
|
+
toolName: "task",
|
|
307
|
+
output: "<task_result>fixed</task_result>",
|
|
308
|
+
},
|
|
309
|
+
]);
|
|
310
|
+
const subDone = findChunks(chunks, "data-oh:subagent.done");
|
|
311
|
+
expect(subDone).toHaveLength(1);
|
|
312
|
+
});
|
|
313
|
+
it("emits data-oh:subagent.error for background task tool errors", async () => {
|
|
314
|
+
const chunks = await collectChunks([
|
|
315
|
+
{
|
|
316
|
+
type: "tool.start",
|
|
317
|
+
toolCallId: "tc-bg",
|
|
318
|
+
toolName: "task",
|
|
319
|
+
input: { agent: "researcher", prompt: "find info", background: true },
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
type: "tool.error",
|
|
323
|
+
toolCallId: "tc-bg",
|
|
324
|
+
toolName: "task",
|
|
325
|
+
error: "Spawn failed",
|
|
326
|
+
},
|
|
327
|
+
]);
|
|
328
|
+
const subError = findChunks(chunks, "data-oh:subagent.error");
|
|
329
|
+
expect(subError).toHaveLength(1);
|
|
330
|
+
expect(subError[0].data.error).toBe("Spawn failed");
|
|
331
|
+
});
|
|
332
|
+
it("handles mixed foreground and background task calls", async () => {
|
|
333
|
+
const chunks = await collectChunks([
|
|
334
|
+
{
|
|
335
|
+
type: "tool.start",
|
|
336
|
+
toolCallId: "tc-bg",
|
|
337
|
+
toolName: "task",
|
|
338
|
+
input: { agent: "researcher", prompt: "bg work", background: true },
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
type: "tool.start",
|
|
342
|
+
toolCallId: "tc-fg",
|
|
343
|
+
toolName: "task",
|
|
344
|
+
input: { agent: "coder", prompt: "fg work" },
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
type: "tool.done",
|
|
348
|
+
toolCallId: "tc-bg",
|
|
349
|
+
toolName: "task",
|
|
350
|
+
output: "spawned",
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
type: "tool.done",
|
|
354
|
+
toolCallId: "tc-fg",
|
|
355
|
+
toolName: "task",
|
|
356
|
+
output: "completed",
|
|
357
|
+
},
|
|
358
|
+
]);
|
|
359
|
+
const subStart = findChunks(chunks, "data-oh:subagent.start");
|
|
360
|
+
expect(subStart).toHaveLength(2);
|
|
361
|
+
const subDone = findChunks(chunks, "data-oh:subagent.done");
|
|
362
|
+
expect(subDone).toHaveLength(1);
|
|
363
|
+
expect(subDone[0].data.agentName).toBe("coder");
|
|
364
|
+
});
|
|
260
365
|
it("does not emit subagent events for regular tools", async () => {
|
|
261
366
|
const chunks = await collectChunks([
|
|
262
367
|
{
|