@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.
Files changed (100) hide show
  1. package/README.md +116 -0
  2. package/dist/__tests__/agent-registry.test.d.ts +2 -0
  3. package/dist/__tests__/agent-registry.test.d.ts.map +1 -0
  4. package/dist/__tests__/agent-registry.test.js +429 -0
  5. package/dist/__tests__/agent-registry.test.js.map +1 -0
  6. package/dist/__tests__/conversation.test.d.ts +2 -0
  7. package/dist/__tests__/conversation.test.d.ts.map +1 -0
  8. package/dist/__tests__/conversation.test.js +127 -0
  9. package/dist/__tests__/conversation.test.js.map +1 -0
  10. package/dist/__tests__/messages.test.js +15 -2
  11. package/dist/__tests__/messages.test.js.map +1 -1
  12. package/dist/__tests__/middleware/compaction.test.d.ts +2 -0
  13. package/dist/__tests__/middleware/compaction.test.d.ts.map +1 -0
  14. package/dist/__tests__/middleware/compaction.test.js +163 -0
  15. package/dist/__tests__/middleware/compaction.test.js.map +1 -0
  16. package/dist/__tests__/middleware/hooks.test.d.ts +2 -0
  17. package/dist/__tests__/middleware/hooks.test.d.ts.map +1 -0
  18. package/dist/__tests__/middleware/hooks.test.js +97 -0
  19. package/dist/__tests__/middleware/hooks.test.js.map +1 -0
  20. package/dist/__tests__/middleware/persistence.test.d.ts +2 -0
  21. package/dist/__tests__/middleware/persistence.test.d.ts.map +1 -0
  22. package/dist/__tests__/middleware/persistence.test.js +70 -0
  23. package/dist/__tests__/middleware/persistence.test.js.map +1 -0
  24. package/dist/__tests__/middleware/retry.test.d.ts +2 -0
  25. package/dist/__tests__/middleware/retry.test.d.ts.map +1 -0
  26. package/dist/__tests__/middleware/retry.test.js +151 -0
  27. package/dist/__tests__/middleware/retry.test.js.map +1 -0
  28. package/dist/__tests__/middleware/turn-tracking.test.d.ts +2 -0
  29. package/dist/__tests__/middleware/turn-tracking.test.d.ts.map +1 -0
  30. package/dist/__tests__/middleware/turn-tracking.test.js +70 -0
  31. package/dist/__tests__/middleware/turn-tracking.test.js.map +1 -0
  32. package/dist/__tests__/runner.test.d.ts +2 -0
  33. package/dist/__tests__/runner.test.d.ts.map +1 -0
  34. package/dist/__tests__/runner.test.js +111 -0
  35. package/dist/__tests__/runner.test.js.map +1 -0
  36. package/dist/__tests__/stream.test.d.ts +2 -0
  37. package/dist/__tests__/stream.test.d.ts.map +1 -0
  38. package/dist/__tests__/stream.test.js +100 -0
  39. package/dist/__tests__/stream.test.js.map +1 -0
  40. package/dist/__tests__/ui-stream.test.js +105 -0
  41. package/dist/__tests__/ui-stream.test.js.map +1 -1
  42. package/dist/agent-registry.d.ts +90 -0
  43. package/dist/agent-registry.d.ts.map +1 -0
  44. package/dist/agent-registry.js +217 -0
  45. package/dist/agent-registry.js.map +1 -0
  46. package/dist/agent.d.ts +13 -0
  47. package/dist/agent.d.ts.map +1 -1
  48. package/dist/agent.js +186 -41
  49. package/dist/agent.js.map +1 -1
  50. package/dist/conversation.d.ts +46 -0
  51. package/dist/conversation.d.ts.map +1 -0
  52. package/dist/conversation.js +66 -0
  53. package/dist/conversation.js.map +1 -0
  54. package/dist/index.d.ts +9 -0
  55. package/dist/index.d.ts.map +1 -1
  56. package/dist/index.js +14 -0
  57. package/dist/index.js.map +1 -1
  58. package/dist/middleware/compaction.d.ts +20 -0
  59. package/dist/middleware/compaction.d.ts.map +1 -0
  60. package/dist/middleware/compaction.js +73 -0
  61. package/dist/middleware/compaction.js.map +1 -0
  62. package/dist/middleware/hooks.d.ts +10 -0
  63. package/dist/middleware/hooks.d.ts.map +1 -0
  64. package/dist/middleware/hooks.js +36 -0
  65. package/dist/middleware/hooks.js.map +1 -0
  66. package/dist/middleware/index.d.ts +6 -0
  67. package/dist/middleware/index.d.ts.map +1 -0
  68. package/dist/middleware/index.js +6 -0
  69. package/dist/middleware/index.js.map +1 -0
  70. package/dist/middleware/persistence.d.ts +12 -0
  71. package/dist/middleware/persistence.d.ts.map +1 -0
  72. package/dist/middleware/persistence.js +15 -0
  73. package/dist/middleware/persistence.js.map +1 -0
  74. package/dist/middleware/retry.d.ts +8 -0
  75. package/dist/middleware/retry.d.ts.map +1 -0
  76. package/dist/middleware/retry.js +70 -0
  77. package/dist/middleware/retry.js.map +1 -0
  78. package/dist/middleware/turn-tracking.d.ts +7 -0
  79. package/dist/middleware/turn-tracking.d.ts.map +1 -0
  80. package/dist/middleware/turn-tracking.js +25 -0
  81. package/dist/middleware/turn-tracking.js.map +1 -0
  82. package/dist/runner.d.ts +23 -0
  83. package/dist/runner.d.ts.map +1 -0
  84. package/dist/runner.js +18 -0
  85. package/dist/runner.js.map +1 -0
  86. package/dist/session.d.ts.map +1 -1
  87. package/dist/session.js +7 -40
  88. package/dist/session.js.map +1 -1
  89. package/dist/stream.d.ts +12 -0
  90. package/dist/stream.d.ts.map +1 -0
  91. package/dist/stream.js +38 -0
  92. package/dist/stream.js.map +1 -0
  93. package/dist/ui-stream.d.ts.map +1 -1
  94. package/dist/ui-stream.js +13 -1
  95. package/dist/ui-stream.js.map +1 -1
  96. package/dist/utils.d.ts +8 -0
  97. package/dist/utils.d.ts.map +1 -0
  98. package/dist/utils.js +40 -0
  99. package/dist/utils.js.map +1 -0
  100. 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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=turn-tracking.test.d.ts.map
@@ -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":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=runner.test.d.ts.map
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=stream.test.d.ts.map
@@ -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
  {