@ai-setting/roy-agent-cli 1.0.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 +126 -0
- package/dist/bin/roy.js +127297 -0
- package/dist/roy-agent-darwin-arm64/bin/roy.js +127297 -0
- package/dist/roy-agent-darwin-x64/bin/roy.js +127297 -0
- package/dist/roy-agent-linux-arm64/bin/roy.js +127297 -0
- package/dist/roy-agent-linux-x64/bin/roy.js +127297 -0
- package/dist/roy-agent-windows-x64/bin/roy.js +127297 -0
- package/package.json +91 -0
- package/src/bin/roy.ts +12 -0
- package/src/cli.ts +101 -0
- package/src/commands/act.ts +480 -0
- package/src/commands/commands-add.ts +110 -0
- package/src/commands/commands-dirs.ts +70 -0
- package/src/commands/commands-info.ts +90 -0
- package/src/commands/commands-list.ts +161 -0
- package/src/commands/commands-remove.ts +147 -0
- package/src/commands/commands.ts +55 -0
- package/src/commands/config/config-service.test.ts +449 -0
- package/src/commands/config/config-service.ts +312 -0
- package/src/commands/config/deep-merge.test.ts +168 -0
- package/src/commands/config/deep-merge.ts +63 -0
- package/src/commands/config/export.ts +97 -0
- package/src/commands/config/filter-history-e2e.test.ts +141 -0
- package/src/commands/config/import-preserve-refs.test.ts +212 -0
- package/src/commands/config/import.ts +119 -0
- package/src/commands/config/index.ts +35 -0
- package/src/commands/config/list.ts +281 -0
- package/src/commands/config/roy-config-e2e.test.ts +297 -0
- package/src/commands/config/types.ts +54 -0
- package/src/commands/debug/index.ts +38 -0
- package/src/commands/debug/log.test.ts +233 -0
- package/src/commands/debug/log.ts +123 -0
- package/src/commands/debug/span.test.ts +297 -0
- package/src/commands/debug/span.ts +211 -0
- package/src/commands/debug/trace.test.ts +254 -0
- package/src/commands/debug/trace.ts +140 -0
- package/src/commands/eventsource/add.ts +133 -0
- package/src/commands/eventsource/index.ts +48 -0
- package/src/commands/eventsource/list.ts +194 -0
- package/src/commands/eventsource/remove.ts +95 -0
- package/src/commands/eventsource/start.ts +103 -0
- package/src/commands/eventsource/status.ts +185 -0
- package/src/commands/eventsource/stop.ts +89 -0
- package/src/commands/index.ts +22 -0
- package/src/commands/input-handler.test.ts +76 -0
- package/src/commands/input-handler.ts +43 -0
- package/src/commands/interactive-esc.test.ts +254 -0
- package/src/commands/interactive.shutdown.test.ts +122 -0
- package/src/commands/interactive.test.ts +221 -0
- package/src/commands/interactive.ts +1015 -0
- package/src/commands/lsp/check.ts +92 -0
- package/src/commands/lsp/index.ts +32 -0
- package/src/commands/lsp/install.ts +126 -0
- package/src/commands/lsp/list.ts +64 -0
- package/src/commands/mcp/index.ts +27 -0
- package/src/commands/mcp/list.ts +116 -0
- package/src/commands/mcp/reload.ts +70 -0
- package/src/commands/mcp/tools.ts +121 -0
- package/src/commands/memory/extract-e2e.test.ts +388 -0
- package/src/commands/memory/index.ts +11 -0
- package/src/commands/memory/memory-simplified.test.ts +58 -0
- package/src/commands/memory/memory.ts +25 -0
- package/src/commands/memory/organize.ts +300 -0
- package/src/commands/memory/recall.test.ts +120 -0
- package/src/commands/memory/recall.ts +88 -0
- package/src/commands/memory/record-extract-handle-query.test.ts +385 -0
- package/src/commands/memory/record-prompt-component.test.ts +343 -0
- package/src/commands/memory/record.test.ts +92 -0
- package/src/commands/memory/record.ts +332 -0
- package/src/commands/plugin.test.ts +292 -0
- package/src/commands/plugin.ts +267 -0
- package/src/commands/sessions/active.ts +96 -0
- package/src/commands/sessions/add-message.ts +96 -0
- package/src/commands/sessions/checkpoints.ts +154 -0
- package/src/commands/sessions/compact.test.ts +215 -0
- package/src/commands/sessions/compact.ts +269 -0
- package/src/commands/sessions/delete.ts +236 -0
- package/src/commands/sessions/get.ts +165 -0
- package/src/commands/sessions/grep.ts +233 -0
- package/src/commands/sessions/index.ts +95 -0
- package/src/commands/sessions/list.ts +210 -0
- package/src/commands/sessions/messages.test.ts +333 -0
- package/src/commands/sessions/messages.ts +248 -0
- package/src/commands/sessions/mock.ts +194 -0
- package/src/commands/sessions/new.ts +82 -0
- package/src/commands/sessions/rename.ts +98 -0
- package/src/commands/shared/event-handler.ts +213 -0
- package/src/commands/shared/event-message-formatter.ts +295 -0
- package/src/commands/shared/index.ts +11 -0
- package/src/commands/shared/query-executor.test.ts +434 -0
- package/src/commands/shared/query-executor.ts +324 -0
- package/src/commands/shared/repl-engine.test.ts +354 -0
- package/src/commands/shared/session-manager.test.ts +212 -0
- package/src/commands/shared/session-manager.ts +114 -0
- package/src/commands/skills/get.ts +90 -0
- package/src/commands/skills/index.ts +39 -0
- package/src/commands/skills/list.ts +129 -0
- package/src/commands/skills/reload.ts +59 -0
- package/src/commands/skills/search.ts +132 -0
- package/src/commands/skills/show-config.ts +93 -0
- package/src/commands/tasks/complete.ts +92 -0
- package/src/commands/tasks/create.ts +118 -0
- package/src/commands/tasks/delete.ts +86 -0
- package/src/commands/tasks/get.ts +116 -0
- package/src/commands/tasks/index.ts +53 -0
- package/src/commands/tasks/list.ts +140 -0
- package/src/commands/tasks/operations.ts +120 -0
- package/src/commands/tasks/update.ts +122 -0
- package/src/commands/tools/exec-tool.ts +128 -0
- package/src/commands/tools/get.ts +114 -0
- package/src/commands/tools/index.ts +35 -0
- package/src/commands/tools/list.ts +107 -0
- package/src/commands/tools/shared/index.ts +7 -0
- package/src/commands/tools/shared/schema-helper.ts +111 -0
- package/src/commands/workflow/commands/add.ts +315 -0
- package/src/commands/workflow/commands/get.ts +193 -0
- package/src/commands/workflow/commands/list.ts +137 -0
- package/src/commands/workflow/commands/nodes.ts +528 -0
- package/src/commands/workflow/commands/remove.ts +94 -0
- package/src/commands/workflow/commands/run.ts +398 -0
- package/src/commands/workflow/commands/status.ts +147 -0
- package/src/commands/workflow/commands/stop.ts +91 -0
- package/src/commands/workflow/commands/update.ts +130 -0
- package/src/commands/workflow/commands/validate.ts +139 -0
- package/src/commands/workflow/commands/workflow-cli.test.ts +196 -0
- package/src/commands/workflow/index.ts +65 -0
- package/src/commands/workflow/renderers.ts +358 -0
- package/src/commands/workflow/validators/index.ts +8 -0
- package/src/commands/workflow/validators/node-validator-factory.ts +40 -0
- package/src/commands/workflow/validators/node-validator.ts +125 -0
- package/src/commands/workflow/validators/nodes/agent-node-validator.ts +58 -0
- package/src/commands/workflow/validators/nodes/condition-node-validator.ts +34 -0
- package/src/commands/workflow/validators/nodes/decorator-node-validator.ts +45 -0
- package/src/commands/workflow/validators/nodes/merge-node-validator.ts +46 -0
- package/src/commands/workflow/validators/nodes/skill-node-validator.ts +33 -0
- package/src/commands/workflow/validators/nodes/tool-node-validator.ts +54 -0
- package/src/commands/workflow/validators/nodes/workflow-node-validator.ts +33 -0
- package/src/commands/workflow/validators/types.ts +78 -0
- package/src/commands/workflow/validators/workflow-validator.test.ts +273 -0
- package/src/commands/workflow/validators/workflow-validator.ts +320 -0
- package/src/index.ts +19 -0
- package/src/plugin/apply.ts +103 -0
- package/src/plugin/discover.ts +219 -0
- package/src/plugin/index.ts +45 -0
- package/src/plugin/registry.ts +272 -0
- package/src/plugin/types.ts +165 -0
- package/src/services/context-handler.service.test.ts +501 -0
- package/src/services/context-handler.service.ts +372 -0
- package/src/services/environment.service.commands-prompt.test.ts +167 -0
- package/src/services/environment.service.ts +656 -0
- package/src/services/output.service.test.ts +92 -0
- package/src/services/output.service.ts +122 -0
- package/src/services/quiet-mode.service.test.ts +114 -0
- package/src/services/quiet-mode.service.ts +81 -0
- package/src/services/stream-output.service.test.ts +214 -0
- package/src/services/stream-output.service.ts +323 -0
- package/src/util/which.test.ts +101 -0
- package/src/util/which.ts +55 -0
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach } from "bun:test";
|
|
2
|
+
import { ContextHandlerService, type HandleQueryOptions } from "./context-handler.service";
|
|
3
|
+
import { ContextError, ErrorCodes, getTracerProvider, resetTracerProvider, type OTelTracer } from "@ai-setting/roy-agent-core";
|
|
4
|
+
import type { Environment, AgentContext } from "@ai-setting/roy-agent-core";
|
|
5
|
+
import type { SessionComponent, SessionCheckpoint } from "@ai-setting/roy-agent-core";
|
|
6
|
+
|
|
7
|
+
// Mock checkpoint
|
|
8
|
+
const mockCheckpoint: SessionCheckpoint = {
|
|
9
|
+
id: "cp_test123",
|
|
10
|
+
messageIndex: 10,
|
|
11
|
+
title: "Test Checkpoint",
|
|
12
|
+
summary: "Summary of conversation",
|
|
13
|
+
processKeyPoints: ["Point 1", "Point 2"],
|
|
14
|
+
currentState: "Current working state",
|
|
15
|
+
nextSteps: ["Next step 1", "Next step 2"],
|
|
16
|
+
userIntents: [],
|
|
17
|
+
messageCountBefore: 15,
|
|
18
|
+
createdAt: Date.now(),
|
|
19
|
+
type: "compact",
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
describe("ContextHandlerService", () => {
|
|
23
|
+
let mockEnv: Environment;
|
|
24
|
+
let mockSessionComponent: SessionComponent;
|
|
25
|
+
let contextHandler: ContextHandlerService;
|
|
26
|
+
let handleQueryMock: any;
|
|
27
|
+
let pushEnvEventMock: any;
|
|
28
|
+
let compactMock: any;
|
|
29
|
+
|
|
30
|
+
beforeEach(() => {
|
|
31
|
+
// 清除环境变量中的 trace context,避免继承外部上下文
|
|
32
|
+
delete process.env['TRACEPARENT'];
|
|
33
|
+
delete process.env['TRACE_ID'];
|
|
34
|
+
delete process.env['LOG_TRACE_REQUEST_ID'];
|
|
35
|
+
|
|
36
|
+
// 重置 tracer provider,确保干净的上下文
|
|
37
|
+
resetTracerProvider();
|
|
38
|
+
|
|
39
|
+
// Create fresh mocks
|
|
40
|
+
handleQueryMock = async () => { throw new Error("Not mocked"); };
|
|
41
|
+
pushEnvEventMock = () => {};
|
|
42
|
+
compactMock = async () => { throw new Error("Not mocked"); };
|
|
43
|
+
|
|
44
|
+
mockEnv = {
|
|
45
|
+
handle_query: handleQueryMock,
|
|
46
|
+
pushEnvEvent: pushEnvEventMock,
|
|
47
|
+
getComponent: () => undefined,
|
|
48
|
+
subscribe: () => () => {},
|
|
49
|
+
subscribeTo: () => () => {},
|
|
50
|
+
} as unknown as Environment;
|
|
51
|
+
|
|
52
|
+
// Mock generateCompactHint for two-phase compact
|
|
53
|
+
const generateCompactHintMock = async () => "Generated scenario hint for testing";
|
|
54
|
+
|
|
55
|
+
mockSessionComponent = {
|
|
56
|
+
compact: compactMock,
|
|
57
|
+
generateCompactHint: generateCompactHintMock,
|
|
58
|
+
get: async () => undefined,
|
|
59
|
+
addMessage: async () => undefined,
|
|
60
|
+
} as unknown as SessionComponent;
|
|
61
|
+
|
|
62
|
+
contextHandler = new ContextHandlerService(
|
|
63
|
+
mockEnv,
|
|
64
|
+
mockSessionComponent,
|
|
65
|
+
{ maxRetries: 1, autoCompact: true }
|
|
66
|
+
);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe("handleQueryWithContext", () => {
|
|
70
|
+
test("should return result when handle_query succeeds", async () => {
|
|
71
|
+
handleQueryMock = async () => "Query completed";
|
|
72
|
+
mockEnv.handle_query = handleQueryMock;
|
|
73
|
+
|
|
74
|
+
const result = await contextHandler.handleQueryWithContext(
|
|
75
|
+
"Test query",
|
|
76
|
+
{ sessionId: "session-123" }
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
expect(result).toBe("Query completed");
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("should throw non-ContextError as-is", async () => {
|
|
83
|
+
handleQueryMock = async () => { throw new Error("Some other error"); };
|
|
84
|
+
mockEnv.handle_query = handleQueryMock;
|
|
85
|
+
|
|
86
|
+
await expect(
|
|
87
|
+
contextHandler.handleQueryWithContext("Test query")
|
|
88
|
+
).rejects.toThrow("Some other error");
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("should compact and retry when ContextError is thrown", async () => {
|
|
92
|
+
let callCount = 0;
|
|
93
|
+
handleQueryMock = async () => {
|
|
94
|
+
callCount++;
|
|
95
|
+
if (callCount === 1) {
|
|
96
|
+
throw new ContextError(
|
|
97
|
+
"Context threshold exceeded",
|
|
98
|
+
ErrorCodes.CONTEXT_THRESHOLD_EXCEEDED,
|
|
99
|
+
"session-123",
|
|
100
|
+
{ promptTokens: 10000, completionTokens: 500, totalTokens: 10500 },
|
|
101
|
+
128000
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
return "Retry result";
|
|
105
|
+
};
|
|
106
|
+
mockEnv.handle_query = handleQueryMock;
|
|
107
|
+
|
|
108
|
+
compactMock = async () => ({
|
|
109
|
+
checkpoint: mockCheckpoint,
|
|
110
|
+
deletedMessageCount: 15,
|
|
111
|
+
remainingMessageCount: 5,
|
|
112
|
+
checkpointCount: 2,
|
|
113
|
+
});
|
|
114
|
+
mockSessionComponent.compact = compactMock;
|
|
115
|
+
|
|
116
|
+
const result = await contextHandler.handleQueryWithContext(
|
|
117
|
+
"Test query",
|
|
118
|
+
{ sessionId: "session-123" }
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
expect(result).toBe("Retry result");
|
|
122
|
+
expect(callCount).toBe(2);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("should publish events when compacting", async () => {
|
|
126
|
+
const events: any[] = [];
|
|
127
|
+
pushEnvEventMock = (event: any) => events.push(event);
|
|
128
|
+
mockEnv.pushEnvEvent = pushEnvEventMock;
|
|
129
|
+
|
|
130
|
+
let callCount = 0;
|
|
131
|
+
handleQueryMock = async () => {
|
|
132
|
+
callCount++;
|
|
133
|
+
if (callCount === 1) {
|
|
134
|
+
throw new ContextError(
|
|
135
|
+
"Context threshold exceeded",
|
|
136
|
+
ErrorCodes.CONTEXT_THRESHOLD_EXCEEDED,
|
|
137
|
+
"session-123"
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
return "Retry result";
|
|
141
|
+
};
|
|
142
|
+
mockEnv.handle_query = handleQueryMock;
|
|
143
|
+
|
|
144
|
+
compactMock = async () => ({
|
|
145
|
+
checkpoint: mockCheckpoint,
|
|
146
|
+
deletedMessageCount: 15,
|
|
147
|
+
remainingMessageCount: 5,
|
|
148
|
+
checkpointCount: 2,
|
|
149
|
+
});
|
|
150
|
+
mockSessionComponent.compact = compactMock;
|
|
151
|
+
|
|
152
|
+
await contextHandler.handleQueryWithContext(
|
|
153
|
+
"Test query",
|
|
154
|
+
{ sessionId: "session-123" }
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
// Verify events were published
|
|
158
|
+
expect(events.some(e => e.type === "context.threshold_exceeded")).toBe(true);
|
|
159
|
+
expect(events.some(e => e.type === "context.compacting")).toBe(true);
|
|
160
|
+
expect(events.some(e => e.type === "context.compacted")).toBe(true);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test("should throw after max retries exceeded", async () => {
|
|
164
|
+
handleQueryMock = async () => {
|
|
165
|
+
throw new ContextError(
|
|
166
|
+
"Context threshold exceeded",
|
|
167
|
+
ErrorCodes.CONTEXT_THRESHOLD_EXCEEDED,
|
|
168
|
+
"session-123"
|
|
169
|
+
);
|
|
170
|
+
};
|
|
171
|
+
mockEnv.handle_query = handleQueryMock;
|
|
172
|
+
|
|
173
|
+
compactMock = async () => ({
|
|
174
|
+
checkpoint: mockCheckpoint,
|
|
175
|
+
deletedMessageCount: 15,
|
|
176
|
+
remainingMessageCount: 5,
|
|
177
|
+
checkpointCount: 2,
|
|
178
|
+
});
|
|
179
|
+
mockSessionComponent.compact = compactMock;
|
|
180
|
+
|
|
181
|
+
let error: any;
|
|
182
|
+
try {
|
|
183
|
+
await contextHandler.handleQueryWithContext("Test query", { sessionId: "session-123" });
|
|
184
|
+
} catch (e) {
|
|
185
|
+
error = e;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
expect(error).toBeInstanceOf(ContextError);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test("should not compact when autoCompact is disabled", async () => {
|
|
192
|
+
contextHandler = new ContextHandlerService(
|
|
193
|
+
mockEnv,
|
|
194
|
+
mockSessionComponent,
|
|
195
|
+
{ maxRetries: 1, autoCompact: false }
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
handleQueryMock = async () => {
|
|
199
|
+
throw new ContextError(
|
|
200
|
+
"Context threshold exceeded",
|
|
201
|
+
ErrorCodes.CONTEXT_THRESHOLD_EXCEEDED,
|
|
202
|
+
"session-123"
|
|
203
|
+
);
|
|
204
|
+
};
|
|
205
|
+
mockEnv.handle_query = handleQueryMock;
|
|
206
|
+
|
|
207
|
+
let compactCalled = false;
|
|
208
|
+
compactMock = async () => {
|
|
209
|
+
compactCalled = true;
|
|
210
|
+
return {
|
|
211
|
+
checkpoint: mockCheckpoint,
|
|
212
|
+
deletedMessageCount: 15,
|
|
213
|
+
remainingMessageCount: 5,
|
|
214
|
+
checkpointCount: 2,
|
|
215
|
+
};
|
|
216
|
+
};
|
|
217
|
+
mockSessionComponent.compact = compactMock;
|
|
218
|
+
|
|
219
|
+
let error: any;
|
|
220
|
+
try {
|
|
221
|
+
await contextHandler.handleQueryWithContext("Test query");
|
|
222
|
+
} catch (e) {
|
|
223
|
+
error = e;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
expect(error).toBeInstanceOf(ContextError);
|
|
227
|
+
expect(compactCalled).toBe(false);
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
describe("compactSession", () => {
|
|
232
|
+
test("should return error when no sessionId provided", async () => {
|
|
233
|
+
const result = await contextHandler.compactSession(undefined);
|
|
234
|
+
|
|
235
|
+
expect(result.success).toBe(false);
|
|
236
|
+
expect(result.error).toBe("No session ID provided");
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
test("should compact session and return new query", async () => {
|
|
240
|
+
compactMock = async () => ({
|
|
241
|
+
checkpoint: mockCheckpoint,
|
|
242
|
+
deletedMessageCount: 15,
|
|
243
|
+
remainingMessageCount: 5,
|
|
244
|
+
checkpointCount: 2,
|
|
245
|
+
});
|
|
246
|
+
mockSessionComponent.compact = compactMock;
|
|
247
|
+
|
|
248
|
+
const result = await contextHandler.compactSession("session-123", "User's new query");
|
|
249
|
+
|
|
250
|
+
expect(result.success).toBe(true);
|
|
251
|
+
expect(result.checkpoint).toEqual(mockCheckpoint);
|
|
252
|
+
expect(result.newQuery).toContain("【会话历史摘要】");
|
|
253
|
+
expect(result.newQuery).toContain("Summary of conversation");
|
|
254
|
+
expect(result.newQuery).toContain("【已完成的工作】");
|
|
255
|
+
expect(result.newQuery).toContain("【用户新输入】");
|
|
256
|
+
expect(result.newQuery).toContain("User's new query");
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
test("should handle compact failure", async () => {
|
|
260
|
+
compactMock = async () => { throw new Error("Compact failed"); };
|
|
261
|
+
mockSessionComponent.compact = compactMock;
|
|
262
|
+
|
|
263
|
+
const result = await contextHandler.compactSession("session-123");
|
|
264
|
+
|
|
265
|
+
expect(result.success).toBe(false);
|
|
266
|
+
expect(result.error).toBe("Compact failed");
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
test("should construct query without original query", async () => {
|
|
270
|
+
compactMock = async () => ({
|
|
271
|
+
checkpoint: mockCheckpoint,
|
|
272
|
+
deletedMessageCount: 15,
|
|
273
|
+
remainingMessageCount: 5,
|
|
274
|
+
checkpointCount: 2,
|
|
275
|
+
});
|
|
276
|
+
mockSessionComponent.compact = compactMock;
|
|
277
|
+
|
|
278
|
+
const result = await contextHandler.compactSession("session-123");
|
|
279
|
+
|
|
280
|
+
expect(result.newQuery).not.toContain("【用户新输入】");
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
describe("Two-Phase Compact", () => {
|
|
285
|
+
test("should call generateCompactHint before compact in two-phase flow", async () => {
|
|
286
|
+
const events: any[] = [];
|
|
287
|
+
pushEnvEventMock = (event: any) => events.push(event);
|
|
288
|
+
mockEnv.pushEnvEvent = pushEnvEventMock;
|
|
289
|
+
|
|
290
|
+
let generateHintCalled = false;
|
|
291
|
+
let hintValue = "Scenario hint for task execution";
|
|
292
|
+
const generateCompactHintMock = async () => {
|
|
293
|
+
generateHintCalled = true;
|
|
294
|
+
return hintValue;
|
|
295
|
+
};
|
|
296
|
+
mockSessionComponent.generateCompactHint = generateCompactHintMock;
|
|
297
|
+
|
|
298
|
+
compactMock = async (sessionId: string, options: any) => {
|
|
299
|
+
// Verify scenarioHint was passed to compact
|
|
300
|
+
return {
|
|
301
|
+
checkpoint: {
|
|
302
|
+
...mockCheckpoint,
|
|
303
|
+
recentMessages: [
|
|
304
|
+
{ role: "user", content: "Test question" },
|
|
305
|
+
{ role: "assistant", content: "Test answer" },
|
|
306
|
+
],
|
|
307
|
+
},
|
|
308
|
+
deletedMessageCount: 15,
|
|
309
|
+
remainingMessageCount: 5,
|
|
310
|
+
checkpointCount: 2,
|
|
311
|
+
};
|
|
312
|
+
};
|
|
313
|
+
mockSessionComponent.compact = compactMock;
|
|
314
|
+
|
|
315
|
+
const result = await contextHandler.compactSessionWithTwoPhases("session-123", "Test query");
|
|
316
|
+
|
|
317
|
+
expect(result.success).toBe(true);
|
|
318
|
+
expect(generateHintCalled).toBe(true);
|
|
319
|
+
|
|
320
|
+
// Verify events were published
|
|
321
|
+
expect(events.some(e => e.type === "context.compacting")).toBe(true);
|
|
322
|
+
expect(events.some(e => e.type === "context.hint_generated")).toBe(true);
|
|
323
|
+
expect(events.some(e => e.type === "context.compacted")).toBe(true);
|
|
324
|
+
|
|
325
|
+
// Verify hint_generated event contains hint info
|
|
326
|
+
const hintEvent = events.find(e => e.type === "context.hint_generated");
|
|
327
|
+
expect(hintEvent?.payload?.hintLength).toBe(hintValue.length);
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
test("should continue with empty hint when generateCompactHint fails", async () => {
|
|
331
|
+
const events: any[] = [];
|
|
332
|
+
pushEnvEventMock = (event: any) => events.push(event);
|
|
333
|
+
mockEnv.pushEnvEvent = pushEnvEventMock;
|
|
334
|
+
|
|
335
|
+
// Make generateCompactHint fail
|
|
336
|
+
const generateCompactHintMock = async () => {
|
|
337
|
+
throw new Error("LLM not available");
|
|
338
|
+
};
|
|
339
|
+
mockSessionComponent.generateCompactHint = generateCompactHintMock;
|
|
340
|
+
|
|
341
|
+
compactMock = async () => ({
|
|
342
|
+
checkpoint: mockCheckpoint,
|
|
343
|
+
deletedMessageCount: 15,
|
|
344
|
+
remainingMessageCount: 5,
|
|
345
|
+
checkpointCount: 2,
|
|
346
|
+
});
|
|
347
|
+
mockSessionComponent.compact = compactMock;
|
|
348
|
+
|
|
349
|
+
const result = await contextHandler.compactSessionWithTwoPhases("session-123", "Test query");
|
|
350
|
+
|
|
351
|
+
expect(result.success).toBe(true);
|
|
352
|
+
|
|
353
|
+
// Verify hint_generated event was still published (with empty hint)
|
|
354
|
+
const hintEvent = events.find(e => e.type === "context.hint_generated");
|
|
355
|
+
expect(hintEvent?.payload?.hintLength).toBe(0);
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
test("should include scenarioHintUsed in compacted event", async () => {
|
|
359
|
+
const events: any[] = [];
|
|
360
|
+
pushEnvEventMock = (event: any) => events.push(event);
|
|
361
|
+
mockEnv.pushEnvEvent = pushEnvEventMock;
|
|
362
|
+
|
|
363
|
+
const generateCompactHintMock = async () => "Test scenario hint";
|
|
364
|
+
mockSessionComponent.generateCompactHint = generateCompactHintMock;
|
|
365
|
+
|
|
366
|
+
compactMock = async () => ({
|
|
367
|
+
checkpoint: mockCheckpoint,
|
|
368
|
+
deletedMessageCount: 15,
|
|
369
|
+
remainingMessageCount: 5,
|
|
370
|
+
checkpointCount: 2,
|
|
371
|
+
});
|
|
372
|
+
mockSessionComponent.compact = compactMock;
|
|
373
|
+
|
|
374
|
+
await contextHandler.compactSessionWithTwoPhases("session-123");
|
|
375
|
+
|
|
376
|
+
const compactedEvent = events.find(e => e.type === "context.compacted");
|
|
377
|
+
expect(compactedEvent?.payload?.scenarioHintUsed).toBe(true);
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
test("compactSession should call compactSessionWithTwoPhases", async () => {
|
|
381
|
+
let twoPhaseCalled = false;
|
|
382
|
+
const originalCompactSessionWithTwoPhases = (contextHandler as any).compactSessionWithTwoPhases.bind(contextHandler);
|
|
383
|
+
(contextHandler as any).compactSessionWithTwoPhases = async (sessionId?: string, originalQuery?: string) => {
|
|
384
|
+
twoPhaseCalled = true;
|
|
385
|
+
return originalCompactSessionWithTwoPhases(sessionId, originalQuery);
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
const generateCompactHintMock = async () => "Test hint";
|
|
389
|
+
mockSessionComponent.generateCompactHint = generateCompactHintMock;
|
|
390
|
+
mockSessionComponent.compact = async () => ({
|
|
391
|
+
checkpoint: mockCheckpoint,
|
|
392
|
+
deletedMessageCount: 15,
|
|
393
|
+
remainingMessageCount: 5,
|
|
394
|
+
checkpointCount: 2,
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
await contextHandler.compactSession("session-123");
|
|
398
|
+
|
|
399
|
+
expect(twoPhaseCalled).toBe(true);
|
|
400
|
+
});
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
describe("Trace Integration", () => {
|
|
404
|
+
test("should generate trace id when handleQueryWithContext is called", async () => {
|
|
405
|
+
// Get tracer from provider
|
|
406
|
+
const provider = getTracerProvider();
|
|
407
|
+
await provider.initialize();
|
|
408
|
+
const tracer = provider.getTracer("roy-tracer");
|
|
409
|
+
|
|
410
|
+
handleQueryMock = async () => "Success";
|
|
411
|
+
mockEnv.handle_query = handleQueryMock;
|
|
412
|
+
|
|
413
|
+
// 在调用期间捕获上下文
|
|
414
|
+
let capturedContext: any;
|
|
415
|
+
const originalHandleQuery = handleQueryMock;
|
|
416
|
+
handleQueryMock = async () => {
|
|
417
|
+
capturedContext = tracer.getCurrentContext();
|
|
418
|
+
return originalHandleQuery();
|
|
419
|
+
};
|
|
420
|
+
mockEnv.handle_query = handleQueryMock;
|
|
421
|
+
|
|
422
|
+
// Execute query
|
|
423
|
+
const result = await contextHandler.handleQueryWithContext(
|
|
424
|
+
"Test query",
|
|
425
|
+
{ sessionId: "session-123" }
|
|
426
|
+
);
|
|
427
|
+
|
|
428
|
+
expect(result).toBe("Success");
|
|
429
|
+
|
|
430
|
+
// Verify tracer created spans
|
|
431
|
+
expect(tracer).toBeDefined();
|
|
432
|
+
expect(tracer.name).toBe("roy-tracer");
|
|
433
|
+
|
|
434
|
+
// Verify context was captured during execution
|
|
435
|
+
expect(capturedContext).toBeDefined();
|
|
436
|
+
expect(capturedContext?.traceId).toBeDefined();
|
|
437
|
+
expect(typeof capturedContext?.traceId).toBe("string");
|
|
438
|
+
console.log("Generated trace ID:", capturedContext?.traceId);
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
test("should use provided trace id in context", async () => {
|
|
442
|
+
// Get tracer from provider
|
|
443
|
+
const provider = getTracerProvider();
|
|
444
|
+
await provider.initialize();
|
|
445
|
+
const tracer = provider.getTracer("roy-tracer");
|
|
446
|
+
|
|
447
|
+
// 在调用期间捕获上下文
|
|
448
|
+
let capturedContext: any;
|
|
449
|
+
handleQueryMock = async () => {
|
|
450
|
+
capturedContext = tracer.getCurrentContext();
|
|
451
|
+
return "Success with custom trace";
|
|
452
|
+
};
|
|
453
|
+
mockEnv.handle_query = handleQueryMock;
|
|
454
|
+
|
|
455
|
+
// Execute with custom trace id in metadata
|
|
456
|
+
const result = await contextHandler.handleQueryWithContext(
|
|
457
|
+
"Test query",
|
|
458
|
+
{
|
|
459
|
+
sessionId: "session-123",
|
|
460
|
+
metadata: {
|
|
461
|
+
traceId: "custom_trace_12345"
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
);
|
|
465
|
+
|
|
466
|
+
expect(result).toBe("Success with custom trace");
|
|
467
|
+
|
|
468
|
+
// Verify context was captured during execution
|
|
469
|
+
expect(capturedContext).toBeDefined();
|
|
470
|
+
expect(capturedContext?.traceId).toBeDefined();
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
test("should start span for query execution", async () => {
|
|
474
|
+
// Get tracer from provider
|
|
475
|
+
const provider = getTracerProvider();
|
|
476
|
+
await provider.initialize();
|
|
477
|
+
const tracer = provider.getTracer("roy-tracer");
|
|
478
|
+
|
|
479
|
+
// 在调用期间捕获上下文
|
|
480
|
+
let capturedContext: any;
|
|
481
|
+
handleQueryMock = async () => {
|
|
482
|
+
capturedContext = tracer.getCurrentContext();
|
|
483
|
+
return "Span test";
|
|
484
|
+
};
|
|
485
|
+
mockEnv.handle_query = handleQueryMock;
|
|
486
|
+
|
|
487
|
+
// Execute query
|
|
488
|
+
await contextHandler.handleQueryWithContext(
|
|
489
|
+
"Test query",
|
|
490
|
+
{ sessionId: "session-123" }
|
|
491
|
+
);
|
|
492
|
+
|
|
493
|
+
// Verify span was created during execution
|
|
494
|
+
expect(capturedContext).toBeDefined();
|
|
495
|
+
expect(capturedContext?.traceId).toBeDefined();
|
|
496
|
+
expect(capturedContext?.spanId).toBeDefined();
|
|
497
|
+
|
|
498
|
+
console.log(`Created span ${capturedContext?.spanId} for trace ${capturedContext?.traceId}`);
|
|
499
|
+
});
|
|
500
|
+
});
|
|
501
|
+
});
|