@dyyz1993/create-agent 2.0.0 → 2.0.1
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 +1 -1
- package/src/lib/copy.ts +23 -1
- package/templates/agent/eslint.config.mjs +15 -2
- package/templates/agent/package.json +10 -2
- package/templates/agent/src/__tests__/hybrid-mode.test.ts +126 -0
- package/templates/agent/src/__tests__/server-config-security.test.ts +55 -0
- package/templates/agent/src/bun/index.ts +96 -41
- package/templates/agent/src/bun/three.d.ts +1 -0
- package/templates/agent/src/gateway/__tests__/ws-handler-token.test.ts +118 -0
- package/templates/agent/src/gateway/http-routes.ts +1 -1
- package/templates/agent/src/gateway/ws-handler.ts +84 -56
- package/templates/agent/src/mainview/__tests__/i18n/i18n.test.ts +1 -0
- package/templates/agent/src/mainview/__tests__/setup.ts +32 -29
- package/templates/agent/src/mainview/__tests__/theme-variables.test.ts +36 -0
- package/templates/agent/src/mainview/components/common/ErrorBoundary.tsx +1 -58
- package/templates/agent/src/mainview/components/common/LanguageSwitcher.tsx +1 -0
- package/templates/agent/src/mainview/components/common/ThemeToggle.tsx +1 -0
- package/templates/agent/src/mainview/components/feed/FeedPanel.tsx +232 -210
- package/templates/agent/src/mainview/components/todo/TodoPanel.tsx +149 -122
- package/templates/agent/src/mainview/lib/api-client.ts +182 -172
- package/templates/agent/src/server-config.ts +34 -32
- package/templates/agent/src/server.ts +43 -54
- package/templates/agent/src/shared/handlers/__tests__/chat-concurrency.test.ts +127 -0
- package/templates/agent/src/shared/handlers/__tests__/handler-isolation.test.ts +244 -0
- package/templates/agent/src/shared/handlers/bash.ts +65 -65
- package/templates/agent/src/shared/handlers/chat.ts +189 -155
- package/templates/agent/src/shared/handlers/debug.ts +8 -0
- package/templates/agent/src/shared/handlers/feed.ts +31 -32
- package/templates/agent/src/shared/handlers/index.ts +1 -0
- package/templates/agent/src/shared/handlers/rules.ts +31 -31
- package/templates/agent/src/shared/handlers/todo.ts +31 -31
- package/templates/agent/src/shared/http-routes.ts +250 -0
- package/templates/agent/src/shared/lib/__tests__/web-server.test.ts +190 -0
- package/templates/agent/src/shared/lib/logger.ts +117 -95
- package/templates/agent/src/shared/lib/web-server.ts +128 -0
- package/templates/agent/src/shared/modules/debug.ts +8 -0
- package/templates/agent/src/shared/rpc-schema.ts +16 -3
- package/templates/agent/test-upload.txt +0 -0
- package/templates/agent/tsconfig.ipc.json +10 -0
- package/templates/agent/tsconfig.json +10 -2
- package/templates/chat/eslint.config.mjs +15 -2
- package/templates/chat/package.json +10 -2
- package/templates/chat/src/__tests__/hybrid-mode.test.ts +125 -0
- package/templates/chat/src/__tests__/server-config-security.test.ts +55 -0
- package/templates/chat/src/__tests__/server-config.test.ts +46 -44
- package/templates/chat/src/bun/index.ts +94 -44
- package/templates/chat/src/bun/three.d.ts +1 -0
- package/templates/chat/src/gateway/http-routes.ts +1 -1
- package/templates/chat/src/gateway/ws-handler.ts +84 -56
- package/templates/chat/src/mainview/App.tsx +6 -0
- package/templates/chat/src/mainview/__tests__/i18n/i18n.test.ts +1 -0
- package/templates/chat/src/mainview/__tests__/theme-variables.test.ts +36 -0
- package/templates/chat/src/mainview/components/common/LanguageSwitcher.tsx +1 -0
- package/templates/chat/src/mainview/components/common/ThemeToggle.tsx +1 -0
- package/templates/chat/src/mainview/lib/api-client.ts +183 -156
- package/templates/chat/src/mainview/lib/rpc-cache.ts +84 -0
- package/templates/chat/src/mainview/main.tsx +4 -3
- package/templates/chat/src/server-config.ts +33 -31
- package/templates/chat/src/server.ts +43 -60
- package/templates/chat/src/shared/handlers/chat.ts +196 -162
- package/templates/chat/src/shared/handlers/debug.ts +8 -0
- package/templates/chat/src/shared/handlers/index.ts +1 -0
- package/templates/chat/src/shared/http-routes.ts +250 -0
- package/templates/chat/src/shared/lib/__tests__/logger.test.ts +92 -0
- package/templates/chat/src/shared/lib/logger.ts +108 -95
- package/templates/chat/src/shared/lib/web-server.ts +128 -0
- package/templates/chat/src/shared/modules/debug.ts +8 -0
- package/templates/chat/src/shared/rpc-schema.ts +3 -2
- package/templates/chat/test-upload.txt +0 -0
- package/templates/chat/tsconfig.ipc.json +10 -0
- package/templates/general/eslint.config.mjs +15 -2
- package/templates/general/package.json +10 -2
- package/templates/general/src/__tests__/hybrid-mode.test.ts +125 -0
- package/templates/general/src/__tests__/server-config-security.test.ts +55 -0
- package/templates/general/src/__tests__/server-config.test.ts +46 -44
- package/templates/general/src/bun/index.ts +94 -44
- package/templates/general/src/bun/three.d.ts +1 -0
- package/templates/general/src/gateway/http-routes.ts +1 -1
- package/templates/general/src/gateway/ws-handler.ts +84 -56
- package/templates/general/src/mainview/__tests__/i18n/i18n.test.ts +1 -0
- package/templates/general/src/mainview/__tests__/theme-variables.test.ts +36 -0
- package/templates/general/src/mainview/components/common/LanguageSwitcher.tsx +1 -0
- package/templates/general/src/mainview/components/common/ThemeToggle.tsx +1 -0
- package/templates/general/src/mainview/components/feed/FeedPanel.tsx +241 -219
- package/templates/general/src/mainview/components/layout/AppLayout.tsx +6 -0
- package/templates/general/src/mainview/lib/api-client.ts +183 -156
- package/templates/general/src/mainview/lib/rpc-cache.ts +84 -0
- package/templates/general/src/mainview/main.tsx +4 -3
- package/templates/general/src/server-config.ts +33 -31
- package/templates/general/src/server.ts +43 -60
- package/templates/general/src/shared/handlers/__tests__/chat.test.ts +165 -160
- package/templates/general/src/shared/handlers/chat.ts +189 -155
- package/templates/general/src/shared/handlers/debug.ts +8 -0
- package/templates/general/src/shared/handlers/feed.ts +38 -40
- package/templates/general/src/shared/handlers/index.ts +1 -0
- package/templates/general/src/shared/http-routes.ts +250 -0
- package/templates/general/src/shared/lib/__tests__/logger.test.ts +92 -0
- package/templates/general/src/shared/lib/logger.ts +117 -95
- package/templates/general/src/shared/lib/web-server.ts +128 -0
- package/templates/general/src/shared/modules/debug.ts +8 -0
- package/templates/general/src/shared/rpc-schema.ts +12 -2
- package/templates/general/test-upload.txt +0 -0
- package/templates/general/tsconfig.ipc.json +10 -0
- package/templates/shared/__tests__/cors-security.test.ts +165 -0
- package/templates/shared/__tests__/file-path-security.test.ts +260 -0
- package/templates/shared/__tests__/http-routes.test.ts +174 -0
- package/templates/shared/__tests__/logger.test.ts +186 -0
- package/templates/shared/components/ErrorBoundary.tsx +58 -0
- package/templates/shared/env.d.ts +11 -0
- package/templates/shared/http-routes.ts +230 -0
- package/templates/shared/logger.ts +129 -0
- package/templates/shared/test-upload.txt +0 -0
- package/templates/shared/vite-base.config.ts +92 -0
- package/templates/shared/vitest-base.config.ts +42 -0
- package/templates/agent/eslint-plugin-rpc/index.js +0 -50
- package/templates/agent/eslint-plugin-rpc/package.json +0 -6
- package/templates/agent/eslint-plugin-rpc/rules/module-file-naming.js +0 -99
- package/templates/agent/eslint-plugin-rpc/rules/no-bare-method.js +0 -78
- package/templates/agent/eslint-plugin-rpc/rules/no-direct-register.js +0 -65
- package/templates/agent/eslint-plugin-rpc/rules/no-hardcoded-strings.js +0 -69
- package/templates/agent/eslint-plugin-rpc/rules/require-api-client.js +0 -58
- package/templates/agent/eslint-plugin-rpc/rules/require-typed-register.js +0 -78
- package/templates/agent/eslint-plugin-rpc/rules/schema-merge-only.js +0 -55
- package/templates/chat/eslint-plugin-rpc/index.js +0 -50
- package/templates/chat/eslint-plugin-rpc/package.json +0 -6
- package/templates/chat/eslint-plugin-rpc/rules/module-file-naming.js +0 -99
- package/templates/chat/eslint-plugin-rpc/rules/no-bare-method.js +0 -78
- package/templates/chat/eslint-plugin-rpc/rules/no-direct-register.js +0 -65
- package/templates/chat/eslint-plugin-rpc/rules/no-hardcoded-strings.js +0 -69
- package/templates/chat/eslint-plugin-rpc/rules/require-api-client.js +0 -58
- package/templates/chat/eslint-plugin-rpc/rules/require-typed-register.js +0 -78
- package/templates/chat/eslint-plugin-rpc/rules/schema-merge-only.js +0 -55
- package/templates/general/eslint-plugin-rpc/index.js +0 -50
- package/templates/general/eslint-plugin-rpc/package.json +0 -6
- package/templates/general/eslint-plugin-rpc/rules/module-file-naming.js +0 -99
- package/templates/general/eslint-plugin-rpc/rules/no-bare-method.js +0 -78
- package/templates/general/eslint-plugin-rpc/rules/no-direct-register.js +0 -65
- package/templates/general/eslint-plugin-rpc/rules/no-hardcoded-strings.js +0 -69
- package/templates/general/eslint-plugin-rpc/rules/require-api-client.js +0 -58
- package/templates/general/eslint-plugin-rpc/rules/require-typed-register.js +0 -78
- package/templates/general/eslint-plugin-rpc/rules/schema-merge-only.js +0 -55
|
@@ -1,15 +1,7 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Web server entry point — HTTP file endpoints + WebSocket RPC gateway.
|
|
3
|
-
* Port auto-negotiation: tries config.port first, increments on EADDRINUSE.
|
|
4
|
-
* Writes actual port to .server-port for dev orchestration.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { createServer } from "http";
|
|
8
1
|
import { writeFileSync, unlinkSync, existsSync } from "fs";
|
|
9
2
|
import { join, resolve, basename } from "path";
|
|
10
3
|
import { config } from "./server-config";
|
|
11
|
-
import {
|
|
12
|
-
import { createWsHandler } from "./gateway/ws-handler";
|
|
4
|
+
import { createWebServer, getLocalIP } from "./shared/lib/web-server";
|
|
13
5
|
import { createLogger, configureLogDir } from "./shared/lib/logger";
|
|
14
6
|
import { registerPort, unregisterPort, formatRegistryForOutput } from "./shared/lib/port-registry";
|
|
15
7
|
import { discoverMethodNames } from "./shared/register-all-handlers";
|
|
@@ -22,68 +14,59 @@ const PROJECT_ROOT = resolve(import.meta.dir, "..");
|
|
|
22
14
|
const PROJECT_NAME = basename(PROJECT_ROOT);
|
|
23
15
|
|
|
24
16
|
function cleanupPortFile() {
|
|
25
|
-
|
|
26
|
-
|
|
17
|
+
try {
|
|
18
|
+
if (existsSync(PORT_FILE)) unlinkSync(PORT_FILE);
|
|
19
|
+
} catch {}
|
|
20
|
+
unregisterPort(PROJECT_ROOT);
|
|
27
21
|
}
|
|
28
22
|
|
|
29
23
|
function writePortFile(port: number) {
|
|
30
|
-
|
|
24
|
+
writeFileSync(PORT_FILE, String(port), "utf-8");
|
|
31
25
|
}
|
|
32
26
|
|
|
33
27
|
process.on("exit", cleanupPortFile);
|
|
34
|
-
process.on("SIGINT", () => {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
28
|
+
process.on("SIGINT", () => {
|
|
29
|
+
cleanupPortFile();
|
|
30
|
+
process.exit(0);
|
|
31
|
+
});
|
|
32
|
+
process.on("SIGTERM", () => {
|
|
33
|
+
cleanupPortFile();
|
|
34
|
+
process.exit(0);
|
|
35
|
+
});
|
|
39
36
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
37
|
+
async function start() {
|
|
38
|
+
const { port, close } = await createWebServer({
|
|
39
|
+
port: config.port,
|
|
40
|
+
authToken: config.authToken,
|
|
41
|
+
maxUploadSize: config.maxUploadSize,
|
|
42
|
+
corsOrigin: config.corsOrigin,
|
|
43
|
+
});
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
});
|
|
56
|
-
testServer.listen(port);
|
|
57
|
-
});
|
|
58
|
-
}
|
|
45
|
+
process.on("SIGINT", () => {
|
|
46
|
+
close();
|
|
47
|
+
cleanupPortFile();
|
|
48
|
+
process.exit(0);
|
|
49
|
+
});
|
|
50
|
+
process.on("SIGTERM", () => {
|
|
51
|
+
close();
|
|
52
|
+
cleanupPortFile();
|
|
53
|
+
process.exit(0);
|
|
54
|
+
});
|
|
59
55
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
try {
|
|
63
|
-
await checkPort(port);
|
|
64
|
-
return port;
|
|
65
|
-
} catch {
|
|
66
|
-
log.info(`Port ${port} in use, trying ${port + 1}...`);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
throw new Error(`No available port found after ${maxRetries} retries`);
|
|
70
|
-
}
|
|
56
|
+
writePortFile(port);
|
|
57
|
+
registerPort(PROJECT_ROOT, port, PROJECT_NAME);
|
|
71
58
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
log.info("File endpoints: GET /file/{path}, GET /info/{path}");
|
|
81
|
-
// eslint-disable-next-line no-console
|
|
82
|
-
console.log("\n" + formatRegistryForOutput() + "\n");
|
|
83
|
-
});
|
|
59
|
+
const localIp = getLocalIP();
|
|
60
|
+
log.info(`HTTP + WebSocket server running on http://localhost:${port}`);
|
|
61
|
+
log.info(`Local network access: http://${localIp}:${port}`);
|
|
62
|
+
log.info(`WebSocket: ws://localhost:${port}/ws (auth required)`);
|
|
63
|
+
log.info(`Available RPC methods: ${discoverMethodNames().join(", ")}`);
|
|
64
|
+
log.info("File endpoints: GET /file/{path}, GET /info/{path}");
|
|
65
|
+
// eslint-disable-next-line no-console
|
|
66
|
+
console.log("\n" + formatRegistryForOutput() + "\n");
|
|
84
67
|
}
|
|
85
68
|
|
|
86
69
|
start().catch((err) => {
|
|
87
|
-
|
|
88
|
-
|
|
70
|
+
log.error("Server failed to start", { error: err.message });
|
|
71
|
+
process.exit(1);
|
|
89
72
|
});
|
|
@@ -9,175 +9,180 @@ import { homedir } from "os";
|
|
|
9
9
|
type Handler = (params: unknown) => Promise<unknown>;
|
|
10
10
|
type EmittedEvent = { event: string; payload: unknown; filter: unknown };
|
|
11
11
|
|
|
12
|
-
const STORAGE_PATH = join(homedir(), ".pi-agent", "chat-history.json");
|
|
12
|
+
const STORAGE_PATH = join(homedir(), ".pi-agent", "chat-history-desktop.json");
|
|
13
13
|
|
|
14
14
|
function createMockServer() {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
15
|
+
const handlers = new Map<string, Handler>();
|
|
16
|
+
const events: EmittedEvent[] = [];
|
|
17
|
+
return {
|
|
18
|
+
register: (method: string, handler: Handler) => {
|
|
19
|
+
handlers.set(method, handler);
|
|
20
|
+
},
|
|
21
|
+
emitEvent: (event: string, payload: unknown, filter?: unknown) => {
|
|
22
|
+
events.push({ event, payload, filter });
|
|
23
|
+
},
|
|
24
|
+
get: (method: string) => handlers.get(method),
|
|
25
|
+
has: (method: string) => handlers.has(method),
|
|
26
|
+
getEvents: () => events,
|
|
27
|
+
clearEvents: () => {
|
|
28
|
+
events.length = 0;
|
|
29
|
+
},
|
|
30
|
+
};
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
async function clearHistory(): Promise<void> {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
try {
|
|
35
|
+
if (existsSync(STORAGE_PATH)) {
|
|
36
|
+
const { unlink } = await import("fs/promises");
|
|
37
|
+
await unlink(STORAGE_PATH);
|
|
38
|
+
}
|
|
39
|
+
} catch {}
|
|
38
40
|
}
|
|
39
41
|
|
|
40
42
|
async function writeHistory(messages: Record<string, unknown>[]): Promise<void> {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
const dir = dirname(STORAGE_PATH);
|
|
44
|
+
if (!existsSync(dir)) {
|
|
45
|
+
await mkdir(dir, { recursive: true });
|
|
46
|
+
}
|
|
47
|
+
await writeFile(STORAGE_PATH, JSON.stringify(messages, null, 2), "utf-8");
|
|
46
48
|
}
|
|
47
49
|
|
|
48
50
|
describe("chat handler", () => {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
51
|
+
let server: ReturnType<typeof createMockServer>;
|
|
52
|
+
|
|
53
|
+
beforeEach(async () => {
|
|
54
|
+
server = createMockServer();
|
|
55
|
+
register(server as unknown as RPCServer, { platform: "desktop" });
|
|
56
|
+
await clearHistory();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("registers chat.list", () => {
|
|
60
|
+
expect(server.has("chat.list")).toBe(true);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("registers chat.send", () => {
|
|
64
|
+
expect(server.has("chat.send")).toBe(true);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test("chat.list returns empty when no history", async () => {
|
|
68
|
+
const result = (await server.get("chat.list")!({})) as Record<string, unknown>;
|
|
69
|
+
expect(result.messages).toEqual([]);
|
|
70
|
+
expect(result.hasMore).toBe(false);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("chat.list with existing history returns messages", async () => {
|
|
74
|
+
const msgs = [
|
|
75
|
+
{ id: "1", role: "user", content: "hi", timestamp: 1000 },
|
|
76
|
+
{ id: "2", role: "assistant", content: "hello", timestamp: 2000 },
|
|
77
|
+
];
|
|
78
|
+
await writeHistory(msgs);
|
|
79
|
+
|
|
80
|
+
const result = (await server.get("chat.list")!({})) as Record<string, unknown>;
|
|
81
|
+
const messages = result.messages as Record<string, unknown>[];
|
|
82
|
+
expect(messages.length).toBe(2);
|
|
83
|
+
expect(result.hasMore).toBe(false);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test("chat.list respects limit param", async () => {
|
|
87
|
+
const msgs = Array.from({ length: 100 }, (_, i) => ({
|
|
88
|
+
id: `msg-${i}`,
|
|
89
|
+
role: i % 2 === 0 ? ("user" as const) : ("assistant" as const),
|
|
90
|
+
content: `message ${i}`,
|
|
91
|
+
timestamp: i * 1000,
|
|
92
|
+
}));
|
|
93
|
+
await writeHistory(msgs);
|
|
94
|
+
|
|
95
|
+
const result = (await server.get("chat.list")!({ limit: 10 })) as Record<string, unknown>;
|
|
96
|
+
const messages = result.messages as Record<string, unknown>[];
|
|
97
|
+
expect(messages.length).toBe(10);
|
|
98
|
+
expect(result.hasMore).toBe(true);
|
|
99
|
+
expect(messages[0].id).toBe("msg-90");
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("chat.list default limit is 50", async () => {
|
|
103
|
+
const msgs = Array.from({ length: 60 }, (_, i) => ({
|
|
104
|
+
id: `msg-${i}`,
|
|
105
|
+
role: "user" as const,
|
|
106
|
+
content: `message ${i}`,
|
|
107
|
+
timestamp: i * 1000,
|
|
108
|
+
}));
|
|
109
|
+
await writeHistory(msgs);
|
|
110
|
+
|
|
111
|
+
const result = (await server.get("chat.list")!({})) as Record<string, unknown>;
|
|
112
|
+
const messages = result.messages as Record<string, unknown>[];
|
|
113
|
+
expect(messages.length).toBe(50);
|
|
114
|
+
expect(result.hasMore).toBe(true);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("chat.send returns { ok: true }", async () => {
|
|
118
|
+
const result = (await server.get("chat.send")!({ content: "hello" })) as Record<
|
|
119
|
+
string,
|
|
120
|
+
unknown
|
|
121
|
+
>;
|
|
122
|
+
expect(result.ok).toBe(true);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("chat.send emits chat.message events for user and assistant", async () => {
|
|
126
|
+
await server.get("chat.send")!({ content: "hi" });
|
|
127
|
+
|
|
128
|
+
const events = server.getEvents().filter((e) => e.event === "chat.message");
|
|
129
|
+
expect(events.length).toBe(2);
|
|
130
|
+
|
|
131
|
+
const userEvent = events[0].payload as Record<string, unknown>;
|
|
132
|
+
expect(userEvent.role).toBe("user");
|
|
133
|
+
expect(userEvent.content).toBe("hi");
|
|
134
|
+
|
|
135
|
+
const assistantEvent = events[1].payload as Record<string, unknown>;
|
|
136
|
+
expect(assistantEvent.role).toBe("assistant");
|
|
137
|
+
expect(assistantEvent.content).toBeTruthy();
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test("chat.send stores messages in history", async () => {
|
|
141
|
+
await server.get("chat.send")!({ content: "hello world" });
|
|
142
|
+
|
|
143
|
+
const result = (await server.get("chat.list")!({})) as Record<string, unknown>;
|
|
144
|
+
const messages = result.messages as Record<string, unknown>[];
|
|
145
|
+
expect(messages.length).toBe(2);
|
|
146
|
+
expect(messages[0].role).toBe("user");
|
|
147
|
+
expect(messages[0].content).toBe("hello world");
|
|
148
|
+
expect(messages[1].role).toBe("assistant");
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test("chat.send message has correct shape", async () => {
|
|
152
|
+
await server.get("chat.send")!({ content: "test" });
|
|
153
|
+
|
|
154
|
+
const events = server.getEvents().filter((e) => e.event === "chat.message");
|
|
155
|
+
const userMsg = events[0].payload as Record<string, unknown>;
|
|
156
|
+
expect(userMsg).toHaveProperty("id");
|
|
157
|
+
expect(userMsg).toHaveProperty("role");
|
|
158
|
+
expect(userMsg).toHaveProperty("content");
|
|
159
|
+
expect(userMsg).toHaveProperty("timestamp");
|
|
160
|
+
expect(typeof userMsg.id).toBe("string");
|
|
161
|
+
expect(typeof userMsg.timestamp).toBe("number");
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test("chat.message events include role filter", async () => {
|
|
165
|
+
await server.get("chat.send")!({ content: "yo" });
|
|
166
|
+
|
|
167
|
+
const events = server.getEvents().filter((e) => e.event === "chat.message");
|
|
168
|
+
expect(events[0].filter).toEqual({ role: "user" });
|
|
169
|
+
expect(events[1].filter).toEqual({ role: "assistant" });
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
test("chat.send with greeting gets greeting reply", async () => {
|
|
173
|
+
await server.get("chat.send")!({ content: "hello" });
|
|
174
|
+
|
|
175
|
+
const events = server.getEvents().filter((e) => e.event === "chat.message");
|
|
176
|
+
const reply = events[1].payload as Record<string, unknown>;
|
|
177
|
+
expect(reply.content).toBeTruthy();
|
|
178
|
+
expect(reply.role).toBe("assistant");
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test("chat.send with math expression gets calculation reply", async () => {
|
|
182
|
+
await server.get("chat.send")!({ content: "12 * 8" });
|
|
183
|
+
|
|
184
|
+
const events = server.getEvents().filter((e) => e.event === "chat.message");
|
|
185
|
+
const reply = events[1].payload as Record<string, unknown>;
|
|
186
|
+
expect(reply.content).toContain("96");
|
|
187
|
+
});
|
|
183
188
|
});
|