@goondan/openharness-base 0.1.7 → 0.1.9
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/package.json +14 -9
- package/dist/extensions/compaction.d.ts +0 -12
- package/dist/extensions/compaction.d.ts.map +0 -1
- package/dist/extensions/compaction.js +0 -159
- package/dist/extensions/compaction.js.map +0 -1
- package/dist/extensions/context-message.d.ts +0 -9
- package/dist/extensions/context-message.d.ts.map +0 -1
- package/dist/extensions/context-message.js +0 -446
- package/dist/extensions/context-message.js.map +0 -1
- package/dist/extensions/index.d.ts +0 -13
- package/dist/extensions/index.d.ts.map +0 -1
- package/dist/extensions/index.js +0 -7
- package/dist/extensions/index.js.map +0 -1
- package/dist/extensions/logging.d.ts +0 -11
- package/dist/extensions/logging.d.ts.map +0 -1
- package/dist/extensions/logging.js +0 -140
- package/dist/extensions/logging.js.map +0 -1
- package/dist/extensions/message-window.d.ts +0 -7
- package/dist/extensions/message-window.d.ts.map +0 -1
- package/dist/extensions/message-window.js +0 -60
- package/dist/extensions/message-window.js.map +0 -1
- package/dist/extensions/required-tools-guard.d.ts +0 -9
- package/dist/extensions/required-tools-guard.d.ts.map +0 -1
- package/dist/extensions/required-tools-guard.js +0 -74
- package/dist/extensions/required-tools-guard.js.map +0 -1
- package/dist/extensions/tool-search.d.ts +0 -10
- package/dist/extensions/tool-search.d.ts.map +0 -1
- package/dist/extensions/tool-search.js +0 -198
- package/dist/extensions/tool-search.js.map +0 -1
- package/dist/harness.yaml +0 -503
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/manifests/base.d.ts +0 -8
- package/dist/manifests/base.d.ts.map +0 -1
- package/dist/manifests/base.js +0 -352
- package/dist/manifests/base.js.map +0 -1
- package/dist/manifests/index.d.ts +0 -3
- package/dist/manifests/index.d.ts.map +0 -1
- package/dist/manifests/index.js +0 -2
- package/dist/manifests/index.js.map +0 -1
- package/dist/tools/bash.d.ts +0 -8
- package/dist/tools/bash.d.ts.map +0 -1
- package/dist/tools/bash.js +0 -119
- package/dist/tools/bash.js.map +0 -1
- package/dist/tools/file-system.d.ts +0 -12
- package/dist/tools/file-system.d.ts.map +0 -1
- package/dist/tools/file-system.js +0 -117
- package/dist/tools/file-system.js.map +0 -1
- package/dist/tools/http-fetch.d.ts +0 -8
- package/dist/tools/http-fetch.d.ts.map +0 -1
- package/dist/tools/http-fetch.js +0 -149
- package/dist/tools/http-fetch.js.map +0 -1
- package/dist/tools/index.d.ts +0 -7
- package/dist/tools/index.d.ts.map +0 -1
- package/dist/tools/index.js +0 -7
- package/dist/tools/index.js.map +0 -1
- package/dist/tools/json-query.d.ts +0 -12
- package/dist/tools/json-query.d.ts.map +0 -1
- package/dist/tools/json-query.js +0 -176
- package/dist/tools/json-query.js.map +0 -1
- package/dist/tools/text-transform.d.ts +0 -16
- package/dist/tools/text-transform.d.ts.map +0 -1
- package/dist/tools/text-transform.js +0 -127
- package/dist/tools/text-transform.js.map +0 -1
- package/dist/tools/wait.d.ts +0 -6
- package/dist/tools/wait.d.ts.map +0 -1
- package/dist/tools/wait.js +0 -32
- package/dist/tools/wait.js.map +0 -1
- package/dist/types.d.ts +0 -4
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -6
- package/dist/types.js.map +0 -1
- package/dist/utils.d.ts +0 -17
- package/dist/utils.d.ts.map +0 -1
- package/dist/utils.js +0 -159
- package/dist/utils.js.map +0 -1
- package/src/__tests__/basic-system-prompt.test.ts +0 -186
- package/src/__tests__/compaction-summarize.test.ts +0 -282
- package/src/__tests__/logging.test.ts +0 -200
- package/src/__tests__/message-window.test.ts +0 -194
- package/src/__tests__/required-tools-guard.test.ts +0 -207
- package/src/__tests__/tool-search.test.ts +0 -187
- package/src/__tests__/tools.test.ts +0 -332
- package/src/extensions/basic-system-prompt.ts +0 -48
- package/src/extensions/compaction-summarize.ts +0 -104
- package/src/extensions/logging.ts +0 -42
- package/src/extensions/message-window.ts +0 -23
- package/src/extensions/required-tools-guard.ts +0 -24
- package/src/extensions/tool-search.ts +0 -38
- package/src/index.ts +0 -16
- package/src/tools/bash.ts +0 -38
- package/src/tools/file-system.ts +0 -83
- package/src/tools/http-fetch.ts +0 -64
- package/src/tools/json-query.ts +0 -71
- package/src/tools/text-transform.ts +0 -59
- package/src/tools/wait.ts +0 -46
- package/tsconfig.json +0 -8
- package/vitest.config.ts +0 -7
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from "vitest";
|
|
2
|
-
import { Logging } from "../extensions/logging.js";
|
|
3
|
-
import type { ExtensionApi, ConversationState } from "@goondan/openharness-types";
|
|
4
|
-
|
|
5
|
-
// ---------------------------------------------------------------------------
|
|
6
|
-
// Helpers
|
|
7
|
-
// ---------------------------------------------------------------------------
|
|
8
|
-
|
|
9
|
-
function makeMockConversationState(): ConversationState {
|
|
10
|
-
return {
|
|
11
|
-
messages: [],
|
|
12
|
-
events: [],
|
|
13
|
-
emit: vi.fn(),
|
|
14
|
-
restore: vi.fn(),
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function makeMockApi(conversation: ConversationState): {
|
|
19
|
-
api: ExtensionApi;
|
|
20
|
-
eventListeners: Map<string, Array<(payload: unknown) => void>>;
|
|
21
|
-
} {
|
|
22
|
-
const eventListeners = new Map<string, Array<(payload: unknown) => void>>();
|
|
23
|
-
|
|
24
|
-
const api: ExtensionApi = {
|
|
25
|
-
pipeline: {
|
|
26
|
-
register: vi.fn() as unknown as ExtensionApi["pipeline"]["register"],
|
|
27
|
-
},
|
|
28
|
-
tools: {
|
|
29
|
-
register: vi.fn(),
|
|
30
|
-
remove: vi.fn(),
|
|
31
|
-
list: vi.fn(() => []),
|
|
32
|
-
},
|
|
33
|
-
on: vi.fn((event: string, listener: (payload: unknown) => void) => {
|
|
34
|
-
if (!eventListeners.has(event)) {
|
|
35
|
-
eventListeners.set(event, []);
|
|
36
|
-
}
|
|
37
|
-
eventListeners.get(event)!.push(listener);
|
|
38
|
-
}),
|
|
39
|
-
conversation,
|
|
40
|
-
runtime: {
|
|
41
|
-
agent: {
|
|
42
|
-
name: "test-agent",
|
|
43
|
-
model: { provider: "openai", model: "gpt-4o" },
|
|
44
|
-
extensions: [],
|
|
45
|
-
tools: [],
|
|
46
|
-
},
|
|
47
|
-
agents: {},
|
|
48
|
-
connections: {},
|
|
49
|
-
},
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
return { api, eventListeners };
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function emit(
|
|
56
|
-
eventListeners: Map<string, Array<(payload: unknown) => void>>,
|
|
57
|
-
event: string,
|
|
58
|
-
payload: unknown,
|
|
59
|
-
) {
|
|
60
|
-
eventListeners.get(event)?.forEach((l) => l(payload));
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// ---------------------------------------------------------------------------
|
|
64
|
-
// Tests
|
|
65
|
-
// ---------------------------------------------------------------------------
|
|
66
|
-
|
|
67
|
-
describe("Logging", () => {
|
|
68
|
-
it("creates an Extension with name 'logging'", () => {
|
|
69
|
-
const ext = Logging();
|
|
70
|
-
expect(ext.name).toBe("logging");
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it("subscribes to all core events on register", () => {
|
|
74
|
-
const conversation = makeMockConversationState();
|
|
75
|
-
const { api } = makeMockApi(conversation);
|
|
76
|
-
|
|
77
|
-
const ext = Logging();
|
|
78
|
-
ext.register(api);
|
|
79
|
-
|
|
80
|
-
expect(api.on).toHaveBeenCalledWith("turn.start", expect.any(Function));
|
|
81
|
-
expect(api.on).toHaveBeenCalledWith("turn.done", expect.any(Function));
|
|
82
|
-
expect(api.on).toHaveBeenCalledWith("turn.error", expect.any(Function));
|
|
83
|
-
expect(api.on).toHaveBeenCalledWith("step.start", expect.any(Function));
|
|
84
|
-
expect(api.on).toHaveBeenCalledWith("step.done", expect.any(Function));
|
|
85
|
-
expect(api.on).toHaveBeenCalledWith("tool.start", expect.any(Function));
|
|
86
|
-
expect(api.on).toHaveBeenCalledWith("tool.done", expect.any(Function));
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it("logs turn.start event with custom logger", () => {
|
|
90
|
-
const conversation = makeMockConversationState();
|
|
91
|
-
const { api, eventListeners } = makeMockApi(conversation);
|
|
92
|
-
const logger = vi.fn();
|
|
93
|
-
|
|
94
|
-
const ext = Logging({ logger });
|
|
95
|
-
ext.register(api);
|
|
96
|
-
|
|
97
|
-
emit(eventListeners, "turn.start", { turnId: "t1" });
|
|
98
|
-
|
|
99
|
-
expect(logger).toHaveBeenCalledOnce();
|
|
100
|
-
expect(logger.mock.calls[0][0]).toContain("turn.start");
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
it("logs turn.done event", () => {
|
|
104
|
-
const conversation = makeMockConversationState();
|
|
105
|
-
const { api, eventListeners } = makeMockApi(conversation);
|
|
106
|
-
const logger = vi.fn();
|
|
107
|
-
|
|
108
|
-
const ext = Logging({ logger });
|
|
109
|
-
ext.register(api);
|
|
110
|
-
|
|
111
|
-
emit(eventListeners, "turn.done", { turnId: "t1", status: "completed" });
|
|
112
|
-
|
|
113
|
-
expect(logger).toHaveBeenCalledOnce();
|
|
114
|
-
expect(logger.mock.calls[0][0]).toContain("turn.done");
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
it("logs turn.error event", () => {
|
|
118
|
-
const conversation = makeMockConversationState();
|
|
119
|
-
const { api, eventListeners } = makeMockApi(conversation);
|
|
120
|
-
const logger = vi.fn();
|
|
121
|
-
|
|
122
|
-
const ext = Logging({ logger });
|
|
123
|
-
ext.register(api);
|
|
124
|
-
|
|
125
|
-
emit(eventListeners, "turn.error", { error: "oops" });
|
|
126
|
-
|
|
127
|
-
expect(logger).toHaveBeenCalledOnce();
|
|
128
|
-
expect(logger.mock.calls[0][0]).toContain("turn.error");
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
it("uses console.log as default logger", () => {
|
|
132
|
-
const conversation = makeMockConversationState();
|
|
133
|
-
const { api, eventListeners } = makeMockApi(conversation);
|
|
134
|
-
const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
135
|
-
|
|
136
|
-
const ext = Logging();
|
|
137
|
-
ext.register(api);
|
|
138
|
-
|
|
139
|
-
emit(eventListeners, "turn.start", { turnId: "t1" });
|
|
140
|
-
|
|
141
|
-
expect(consoleSpy).toHaveBeenCalledOnce();
|
|
142
|
-
consoleSpy.mockRestore();
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
it("logs step.start event with custom logger", () => {
|
|
146
|
-
const conversation = makeMockConversationState();
|
|
147
|
-
const { api, eventListeners } = makeMockApi(conversation);
|
|
148
|
-
const logger = vi.fn();
|
|
149
|
-
|
|
150
|
-
const ext = Logging({ logger });
|
|
151
|
-
ext.register(api);
|
|
152
|
-
|
|
153
|
-
emit(eventListeners, "step.start", { stepIndex: 0 });
|
|
154
|
-
|
|
155
|
-
expect(logger).toHaveBeenCalledOnce();
|
|
156
|
-
expect(logger.mock.calls[0][0]).toContain("step.start");
|
|
157
|
-
expect(logger.mock.calls[0][0]).toContain("stepIndex");
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
it("logs step.done event with custom logger", () => {
|
|
161
|
-
const conversation = makeMockConversationState();
|
|
162
|
-
const { api, eventListeners } = makeMockApi(conversation);
|
|
163
|
-
const logger = vi.fn();
|
|
164
|
-
|
|
165
|
-
const ext = Logging({ logger });
|
|
166
|
-
ext.register(api);
|
|
167
|
-
|
|
168
|
-
emit(eventListeners, "step.done", { stepIndex: 0, toolCallCount: 2 });
|
|
169
|
-
|
|
170
|
-
expect(logger).toHaveBeenCalledOnce();
|
|
171
|
-
expect(logger.mock.calls[0][0]).toContain("step.done");
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
it("logs tool.start and tool.done events with custom logger", () => {
|
|
175
|
-
const conversation = makeMockConversationState();
|
|
176
|
-
const { api, eventListeners } = makeMockApi(conversation);
|
|
177
|
-
const logger = vi.fn();
|
|
178
|
-
|
|
179
|
-
const ext = Logging({ logger });
|
|
180
|
-
ext.register(api);
|
|
181
|
-
|
|
182
|
-
emit(eventListeners, "tool.start", { toolName: "bash", toolCallId: "tc-1" });
|
|
183
|
-
emit(eventListeners, "tool.done", { toolName: "bash", toolCallId: "tc-1", result: { type: "text", text: "ok" } });
|
|
184
|
-
|
|
185
|
-
expect(logger).toHaveBeenCalledTimes(2);
|
|
186
|
-
expect(logger.mock.calls[0][0]).toContain("tool.start");
|
|
187
|
-
expect(logger.mock.calls[0][0]).toContain("bash");
|
|
188
|
-
expect(logger.mock.calls[1][0]).toContain("tool.done");
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
it("does NOT call pipeline.register (event-based, no middleware)", () => {
|
|
192
|
-
const conversation = makeMockConversationState();
|
|
193
|
-
const { api } = makeMockApi(conversation);
|
|
194
|
-
|
|
195
|
-
const ext = Logging();
|
|
196
|
-
ext.register(api);
|
|
197
|
-
|
|
198
|
-
expect(api.pipeline.register).not.toHaveBeenCalled();
|
|
199
|
-
});
|
|
200
|
-
});
|
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from "vitest";
|
|
2
|
-
import { MessageWindow } from "../extensions/message-window.js";
|
|
3
|
-
import type {
|
|
4
|
-
ExtensionApi,
|
|
5
|
-
StepMiddleware,
|
|
6
|
-
StepContext,
|
|
7
|
-
StepResult,
|
|
8
|
-
ConversationState,
|
|
9
|
-
Message,
|
|
10
|
-
MessageEvent,
|
|
11
|
-
} from "@goondan/openharness-types";
|
|
12
|
-
|
|
13
|
-
// ---------------------------------------------------------------------------
|
|
14
|
-
// Helpers
|
|
15
|
-
// ---------------------------------------------------------------------------
|
|
16
|
-
|
|
17
|
-
function makeMessages(count: number): Message[] {
|
|
18
|
-
return Array.from({ length: count }, (_, i) => ({
|
|
19
|
-
id: `msg-${i}`,
|
|
20
|
-
data: {
|
|
21
|
-
role: "user" as const,
|
|
22
|
-
content: `Message ${i}`,
|
|
23
|
-
},
|
|
24
|
-
}));
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function makeMockConversationState(
|
|
28
|
-
messages: Message[],
|
|
29
|
-
): ConversationState & { emitted: MessageEvent[] } {
|
|
30
|
-
const emitted: MessageEvent[] = [];
|
|
31
|
-
return {
|
|
32
|
-
messages,
|
|
33
|
-
events: [],
|
|
34
|
-
emitted,
|
|
35
|
-
emit: vi.fn((event: MessageEvent) => {
|
|
36
|
-
emitted.push(event);
|
|
37
|
-
}),
|
|
38
|
-
restore: vi.fn(),
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function makeMockApi(conversation: ConversationState): {
|
|
43
|
-
api: ExtensionApi;
|
|
44
|
-
registeredMiddleware: Array<{
|
|
45
|
-
level: string;
|
|
46
|
-
handler: StepMiddleware;
|
|
47
|
-
options?: { priority?: number };
|
|
48
|
-
}>;
|
|
49
|
-
} {
|
|
50
|
-
const registeredMiddleware: Array<{
|
|
51
|
-
level: string;
|
|
52
|
-
handler: StepMiddleware;
|
|
53
|
-
options?: { priority?: number };
|
|
54
|
-
}> = [];
|
|
55
|
-
|
|
56
|
-
const api: ExtensionApi = {
|
|
57
|
-
pipeline: {
|
|
58
|
-
register: vi.fn(
|
|
59
|
-
(level: string, handler: StepMiddleware, options?: { priority?: number }) => {
|
|
60
|
-
registeredMiddleware.push({ level, handler, options });
|
|
61
|
-
},
|
|
62
|
-
) as unknown as ExtensionApi["pipeline"]["register"],
|
|
63
|
-
},
|
|
64
|
-
tools: {
|
|
65
|
-
register: vi.fn(),
|
|
66
|
-
remove: vi.fn(),
|
|
67
|
-
list: vi.fn(() => []),
|
|
68
|
-
},
|
|
69
|
-
on: vi.fn(),
|
|
70
|
-
conversation,
|
|
71
|
-
runtime: {
|
|
72
|
-
agent: {
|
|
73
|
-
name: "test-agent",
|
|
74
|
-
model: { provider: "openai", model: "gpt-4o" },
|
|
75
|
-
extensions: [],
|
|
76
|
-
tools: [],
|
|
77
|
-
},
|
|
78
|
-
agents: {},
|
|
79
|
-
connections: {},
|
|
80
|
-
},
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
return { api, registeredMiddleware };
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function makeStepContext(conversation: ConversationState): StepContext {
|
|
87
|
-
return {
|
|
88
|
-
turnId: "turn-1",
|
|
89
|
-
agentName: "test-agent",
|
|
90
|
-
conversationId: "conv-1",
|
|
91
|
-
conversation,
|
|
92
|
-
stepNumber: 1,
|
|
93
|
-
abortSignal: new AbortController().signal,
|
|
94
|
-
input: {
|
|
95
|
-
name: "test-event",
|
|
96
|
-
content: [{ type: "text", text: "hello" }],
|
|
97
|
-
properties: {},
|
|
98
|
-
source: {
|
|
99
|
-
connector: "test-connector",
|
|
100
|
-
connectionName: "test",
|
|
101
|
-
receivedAt: new Date().toISOString(),
|
|
102
|
-
},
|
|
103
|
-
},
|
|
104
|
-
llm: { chat: vi.fn().mockResolvedValue({ text: "mock" }) },
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const stubStepResult: StepResult = {
|
|
109
|
-
toolCalls: [],
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
// ---------------------------------------------------------------------------
|
|
113
|
-
// Tests
|
|
114
|
-
// ---------------------------------------------------------------------------
|
|
115
|
-
|
|
116
|
-
describe("MessageWindow", () => {
|
|
117
|
-
it("creates an Extension with name 'message-window'", () => {
|
|
118
|
-
const ext = MessageWindow({ maxMessages: 5 });
|
|
119
|
-
expect(ext.name).toBe("message-window");
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
it("registers step middleware via api.pipeline.register", () => {
|
|
123
|
-
const conversation = makeMockConversationState([]);
|
|
124
|
-
const { api, registeredMiddleware } = makeMockApi(conversation);
|
|
125
|
-
|
|
126
|
-
const ext = MessageWindow({ maxMessages: 5 });
|
|
127
|
-
ext.register(api);
|
|
128
|
-
|
|
129
|
-
expect(api.pipeline.register).toHaveBeenCalledOnce();
|
|
130
|
-
expect(registeredMiddleware[0].level).toBe("step");
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
it("does NOT emit truncate when messages are within maxMessages", async () => {
|
|
134
|
-
const messages = makeMessages(3);
|
|
135
|
-
const conversation = makeMockConversationState(messages);
|
|
136
|
-
const { api, registeredMiddleware } = makeMockApi(conversation);
|
|
137
|
-
|
|
138
|
-
const ext = MessageWindow({ maxMessages: 5 });
|
|
139
|
-
ext.register(api);
|
|
140
|
-
|
|
141
|
-
const middleware = registeredMiddleware[0].handler;
|
|
142
|
-
const ctx = makeStepContext(conversation);
|
|
143
|
-
const next = vi.fn(async () => stubStepResult);
|
|
144
|
-
|
|
145
|
-
await middleware(ctx, next);
|
|
146
|
-
|
|
147
|
-
expect(conversation.emit).not.toHaveBeenCalled();
|
|
148
|
-
expect(next).toHaveBeenCalledOnce();
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
it("emits truncate when messages exceed maxMessages", async () => {
|
|
152
|
-
const messages = makeMessages(8);
|
|
153
|
-
const conversation = makeMockConversationState(messages);
|
|
154
|
-
const { api, registeredMiddleware } = makeMockApi(conversation);
|
|
155
|
-
|
|
156
|
-
const ext = MessageWindow({ maxMessages: 5 });
|
|
157
|
-
ext.register(api);
|
|
158
|
-
|
|
159
|
-
const middleware = registeredMiddleware[0].handler;
|
|
160
|
-
const ctx = makeStepContext(conversation);
|
|
161
|
-
const next = vi.fn(async () => stubStepResult);
|
|
162
|
-
|
|
163
|
-
await middleware(ctx, next);
|
|
164
|
-
|
|
165
|
-
expect(conversation.emit).toHaveBeenCalledOnce();
|
|
166
|
-
const emitted = (conversation as ReturnType<typeof makeMockConversationState>).emitted[0];
|
|
167
|
-
expect(emitted.type).toBe("truncate");
|
|
168
|
-
if (emitted.type === "truncate") {
|
|
169
|
-
expect(emitted.keepLast).toBe(5);
|
|
170
|
-
}
|
|
171
|
-
expect(next).toHaveBeenCalledOnce();
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
it("emits truncate with exact maxMessages count", async () => {
|
|
175
|
-
const messages = makeMessages(6);
|
|
176
|
-
const conversation = makeMockConversationState(messages);
|
|
177
|
-
const { api, registeredMiddleware } = makeMockApi(conversation);
|
|
178
|
-
|
|
179
|
-
const ext = MessageWindow({ maxMessages: 5 });
|
|
180
|
-
ext.register(api);
|
|
181
|
-
|
|
182
|
-
const middleware = registeredMiddleware[0].handler;
|
|
183
|
-
const ctx = makeStepContext(conversation);
|
|
184
|
-
const next = vi.fn(async () => stubStepResult);
|
|
185
|
-
|
|
186
|
-
await middleware(ctx, next);
|
|
187
|
-
|
|
188
|
-
expect(conversation.emit).toHaveBeenCalledOnce();
|
|
189
|
-
const emitted = (conversation as ReturnType<typeof makeMockConversationState>).emitted[0];
|
|
190
|
-
if (emitted.type === "truncate") {
|
|
191
|
-
expect(emitted.keepLast).toBe(5);
|
|
192
|
-
}
|
|
193
|
-
});
|
|
194
|
-
});
|
|
@@ -1,207 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from "vitest";
|
|
2
|
-
import { RequiredToolsGuard } from "../extensions/required-tools-guard.js";
|
|
3
|
-
import type {
|
|
4
|
-
ExtensionApi,
|
|
5
|
-
TurnMiddleware,
|
|
6
|
-
TurnContext,
|
|
7
|
-
TurnResult,
|
|
8
|
-
ConversationState,
|
|
9
|
-
ToolDefinition,
|
|
10
|
-
} from "@goondan/openharness-types";
|
|
11
|
-
|
|
12
|
-
// ---------------------------------------------------------------------------
|
|
13
|
-
// Helpers
|
|
14
|
-
// ---------------------------------------------------------------------------
|
|
15
|
-
|
|
16
|
-
function makeMockConversationState(): ConversationState {
|
|
17
|
-
return {
|
|
18
|
-
messages: [],
|
|
19
|
-
events: [],
|
|
20
|
-
emit: vi.fn(),
|
|
21
|
-
restore: vi.fn(),
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function makeMockApi(
|
|
26
|
-
conversation: ConversationState,
|
|
27
|
-
availableTools: ToolDefinition[] = [],
|
|
28
|
-
): {
|
|
29
|
-
api: ExtensionApi;
|
|
30
|
-
registeredMiddleware: Array<{
|
|
31
|
-
level: string;
|
|
32
|
-
handler: TurnMiddleware;
|
|
33
|
-
options?: { priority?: number };
|
|
34
|
-
}>;
|
|
35
|
-
} {
|
|
36
|
-
const registeredMiddleware: Array<{
|
|
37
|
-
level: string;
|
|
38
|
-
handler: TurnMiddleware;
|
|
39
|
-
options?: { priority?: number };
|
|
40
|
-
}> = [];
|
|
41
|
-
|
|
42
|
-
const api: ExtensionApi = {
|
|
43
|
-
pipeline: {
|
|
44
|
-
register: vi.fn(
|
|
45
|
-
(level: string, handler: TurnMiddleware, options?: { priority?: number }) => {
|
|
46
|
-
registeredMiddleware.push({ level, handler, options });
|
|
47
|
-
},
|
|
48
|
-
) as unknown as ExtensionApi["pipeline"]["register"],
|
|
49
|
-
},
|
|
50
|
-
tools: {
|
|
51
|
-
register: vi.fn(),
|
|
52
|
-
remove: vi.fn(),
|
|
53
|
-
list: vi.fn(() => availableTools as readonly ToolDefinition[]),
|
|
54
|
-
},
|
|
55
|
-
on: vi.fn(),
|
|
56
|
-
conversation,
|
|
57
|
-
runtime: {
|
|
58
|
-
agent: {
|
|
59
|
-
name: "test-agent",
|
|
60
|
-
model: { provider: "openai", model: "gpt-4o" },
|
|
61
|
-
extensions: [],
|
|
62
|
-
tools: [],
|
|
63
|
-
},
|
|
64
|
-
agents: {},
|
|
65
|
-
connections: {},
|
|
66
|
-
},
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
return { api, registeredMiddleware };
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function makeTurnContext(conversation: ConversationState): TurnContext {
|
|
73
|
-
return {
|
|
74
|
-
turnId: "turn-1",
|
|
75
|
-
agentName: "test-agent",
|
|
76
|
-
conversationId: "conv-1",
|
|
77
|
-
conversation,
|
|
78
|
-
abortSignal: new AbortController().signal,
|
|
79
|
-
input: {
|
|
80
|
-
name: "test-event",
|
|
81
|
-
content: [{ type: "text", text: "hello" }],
|
|
82
|
-
properties: {},
|
|
83
|
-
source: {
|
|
84
|
-
connector: "test-connector",
|
|
85
|
-
connectionName: "test",
|
|
86
|
-
receivedAt: new Date().toISOString(),
|
|
87
|
-
},
|
|
88
|
-
},
|
|
89
|
-
llm: { chat: vi.fn().mockResolvedValue({ text: "mock" }) },
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function makeDummyTool(name: string): ToolDefinition {
|
|
94
|
-
return {
|
|
95
|
-
name,
|
|
96
|
-
description: `Tool ${name}`,
|
|
97
|
-
parameters: { type: "object", properties: {} },
|
|
98
|
-
handler: async () => ({ type: "text", text: "ok" }),
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const stubTurnResult: TurnResult = {
|
|
103
|
-
turnId: "turn-1",
|
|
104
|
-
agentName: "test-agent",
|
|
105
|
-
conversationId: "conv-1",
|
|
106
|
-
status: "completed",
|
|
107
|
-
steps: [],
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
// ---------------------------------------------------------------------------
|
|
111
|
-
// Tests
|
|
112
|
-
// ---------------------------------------------------------------------------
|
|
113
|
-
|
|
114
|
-
describe("RequiredToolsGuard", () => {
|
|
115
|
-
it("creates an Extension with name 'required-tools-guard'", () => {
|
|
116
|
-
const ext = RequiredToolsGuard({ tools: ["my_tool"] });
|
|
117
|
-
expect(ext.name).toBe("required-tools-guard");
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
it("registers turn middleware via api.pipeline.register", () => {
|
|
121
|
-
const conversation = makeMockConversationState();
|
|
122
|
-
const { api, registeredMiddleware } = makeMockApi(conversation);
|
|
123
|
-
|
|
124
|
-
const ext = RequiredToolsGuard({ tools: ["my_tool"] });
|
|
125
|
-
ext.register(api);
|
|
126
|
-
|
|
127
|
-
expect(api.pipeline.register).toHaveBeenCalledOnce();
|
|
128
|
-
expect(registeredMiddleware[0].level).toBe("turn");
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
it("calls next() when all required tools are present", async () => {
|
|
132
|
-
const conversation = makeMockConversationState();
|
|
133
|
-
const tools = [makeDummyTool("tool_a"), makeDummyTool("tool_b")];
|
|
134
|
-
const { api, registeredMiddleware } = makeMockApi(conversation, tools);
|
|
135
|
-
|
|
136
|
-
const ext = RequiredToolsGuard({ tools: ["tool_a", "tool_b"] });
|
|
137
|
-
ext.register(api);
|
|
138
|
-
|
|
139
|
-
const middleware = registeredMiddleware[0].handler;
|
|
140
|
-
const ctx = makeTurnContext(conversation);
|
|
141
|
-
const next = vi.fn(async () => stubTurnResult);
|
|
142
|
-
|
|
143
|
-
const result = await middleware(ctx, next);
|
|
144
|
-
|
|
145
|
-
expect(next).toHaveBeenCalledOnce();
|
|
146
|
-
expect(result).toBe(stubTurnResult);
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
it("throws error when required tools are missing", async () => {
|
|
150
|
-
const conversation = makeMockConversationState();
|
|
151
|
-
const tools = [makeDummyTool("tool_a")];
|
|
152
|
-
const { api, registeredMiddleware } = makeMockApi(conversation, tools);
|
|
153
|
-
|
|
154
|
-
const ext = RequiredToolsGuard({ tools: ["tool_a", "tool_b"] });
|
|
155
|
-
ext.register(api);
|
|
156
|
-
|
|
157
|
-
const middleware = registeredMiddleware[0].handler;
|
|
158
|
-
const ctx = makeTurnContext(conversation);
|
|
159
|
-
const next = vi.fn(async () => stubTurnResult);
|
|
160
|
-
|
|
161
|
-
await expect(middleware(ctx, next)).rejects.toThrow("tool_b");
|
|
162
|
-
expect(next).not.toHaveBeenCalled();
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
it("throws when no tools are registered at all", async () => {
|
|
166
|
-
const conversation = makeMockConversationState();
|
|
167
|
-
const { api, registeredMiddleware } = makeMockApi(conversation, []);
|
|
168
|
-
|
|
169
|
-
const ext = RequiredToolsGuard({ tools: ["required_tool"] });
|
|
170
|
-
ext.register(api);
|
|
171
|
-
|
|
172
|
-
const middleware = registeredMiddleware[0].handler;
|
|
173
|
-
const ctx = makeTurnContext(conversation);
|
|
174
|
-
const next = vi.fn(async () => stubTurnResult);
|
|
175
|
-
|
|
176
|
-
await expect(middleware(ctx, next)).rejects.toThrow("required_tool");
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
it("passes when no tools are required", async () => {
|
|
180
|
-
const conversation = makeMockConversationState();
|
|
181
|
-
const { api, registeredMiddleware } = makeMockApi(conversation, []);
|
|
182
|
-
|
|
183
|
-
const ext = RequiredToolsGuard({ tools: [] });
|
|
184
|
-
ext.register(api);
|
|
185
|
-
|
|
186
|
-
const middleware = registeredMiddleware[0].handler;
|
|
187
|
-
const ctx = makeTurnContext(conversation);
|
|
188
|
-
const next = vi.fn(async () => stubTurnResult);
|
|
189
|
-
|
|
190
|
-
await expect(middleware(ctx, next)).resolves.toBe(stubTurnResult);
|
|
191
|
-
expect(next).toHaveBeenCalledOnce();
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
it("error message lists all missing tools", async () => {
|
|
195
|
-
const conversation = makeMockConversationState();
|
|
196
|
-
const { api, registeredMiddleware } = makeMockApi(conversation, []);
|
|
197
|
-
|
|
198
|
-
const ext = RequiredToolsGuard({ tools: ["tool_x", "tool_y", "tool_z"] });
|
|
199
|
-
ext.register(api);
|
|
200
|
-
|
|
201
|
-
const middleware = registeredMiddleware[0].handler;
|
|
202
|
-
const ctx = makeTurnContext(conversation);
|
|
203
|
-
const next = vi.fn(async () => stubTurnResult);
|
|
204
|
-
|
|
205
|
-
await expect(middleware(ctx, next)).rejects.toThrow(/tool_x.*tool_y.*tool_z/);
|
|
206
|
-
});
|
|
207
|
-
});
|