Skip to content

Commit e3cf0b3

Browse files
grypezclaude
andcommitted
test(kernel-test-local): CI-safe integration test and e2e test for LMS kernel round-trip
Add lms-chat-vat — a bundled vat that sends a single chat message and logs the response. Wire it through the kernel using two test variants sharing a common runLmsChatKernelTest helper: the integration test injects a mock fetch (no network, CI-safe, 30s timeout) and the e2e test uses real fetch against a local Ollama instance (60s timeout). Scope endoify-node lockdown setup to only the files that require it. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 999c17b commit e3cf0b3

8 files changed

Lines changed: 204 additions & 2 deletions

File tree

packages/kernel-test-local/package.json

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
},
1414
"type": "module",
1515
"scripts": {
16-
"clean": "rimraf --glob './*.tsbuildinfo' ./.eslintcache ./coverage ./.turbo ./logs",
16+
"build": "ocap bundle test/e2e/vats",
17+
"clean": "rimraf --glob './*.tsbuildinfo' ./.eslintcache ./coverage ./.turbo ./logs './test/e2e/vats/*.bundle'",
1718
"lint": "yarn lint:eslint && yarn lint:misc --check && yarn constraints && yarn lint:dependencies",
1819
"lint:dependencies": "depcheck --quiet",
1920
"lint:eslint": "eslint . --cache",
@@ -22,14 +23,20 @@
2223
"build:docs": "typedoc",
2324
"test": "vitest run --config vitest.config.ts",
2425
"test:e2e:local": "vitest run --config vitest.config.e2e.ts",
26+
"test:integration": "vitest run --config vitest.config.integration.ts",
2527
"test:clean": "yarn test --no-cache --coverage.clean",
2628
"test:dev": "yarn test --mode development",
2729
"test:verbose": "yarn test --reporter verbose",
2830
"test:watch": "vitest --config vitest.config.ts",
2931
"test:dev:quiet": "yarn test:dev --reporter @ocap/repo-tools/vitest-reporters/silent"
3032
},
3133
"dependencies": {
34+
"@endo/eventual-send": "^1.3.4",
35+
"@metamask/kernel-node-runtime": "workspace:^",
36+
"@metamask/kernel-store": "workspace:^",
37+
"@metamask/kernel-utils": "workspace:^",
3238
"@metamask/logger": "workspace:^",
39+
"@metamask/ocap-kernel": "workspace:^",
3340
"@ocap/kernel-agents": "workspace:^",
3441
"@ocap/kernel-language-model-service": "workspace:^",
3542
"@ocap/repo-tools": "workspace:^"
@@ -39,6 +46,8 @@
3946
"@metamask/eslint-config": "^15.0.0",
4047
"@metamask/eslint-config-nodejs": "^15.0.0",
4148
"@metamask/eslint-config-typescript": "^15.0.0",
49+
"@metamask/kernel-cli": "workspace:^",
50+
"@metamask/kernel-shims": "workspace:^",
4251
"@ocap/kernel-agents-repl": "workspace:^",
4352
"@types/node": "^22.13.1",
4453
"@typescript-eslint/eslint-plugin": "^8.29.0",
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import '@metamask/kernel-shims/endoify-node';
2+
3+
import { makeOpenV1NodejsService } from '@ocap/kernel-language-model-service';
4+
import { fetchMock } from '@ocap/repo-tools/test-utils/fetch-mock';
5+
import { afterAll, beforeAll, describe, it } from 'vitest';
6+
7+
import { runLmsChatKernelTest } from './lms-chat-kernel.ts';
8+
9+
describe.sequential('lms-kernel (e2e)', () => {
10+
beforeAll(() => {
11+
fetchMock.disableMocks();
12+
});
13+
14+
afterAll(() => {
15+
fetchMock.enableMocks();
16+
});
17+
18+
// eslint-disable-next-line vitest/expect-expect
19+
it(
20+
'sends a chat message through the kernel to Ollama and receives a response',
21+
{ timeout: 60_000 },
22+
async () => {
23+
const { chat } = makeOpenV1NodejsService({
24+
endowments: { fetch },
25+
baseUrl: 'http://localhost:11434',
26+
apiKey: 'test-api-key',
27+
});
28+
await runLmsChatKernelTest(chat);
29+
},
30+
);
31+
});
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { makeOpenV1NodejsService } from '@ocap/kernel-language-model-service';
2+
import { makeMockOpenV1Fetch } from '@ocap/kernel-language-model-service/test-utils';
3+
import { describe, it } from 'vitest';
4+
5+
import { runLmsChatKernelTest } from './lms-chat-kernel.ts';
6+
7+
describe.sequential('lms-kernel (integration)', () => {
8+
// eslint-disable-next-line vitest/expect-expect
9+
it('sends a chat message through the kernel and receives a response', async () => {
10+
const { chat } = makeOpenV1NodejsService({
11+
endowments: { fetch: makeMockOpenV1Fetch(['Hello.']) },
12+
baseUrl: 'http://localhost:11434',
13+
});
14+
await runLmsChatKernelTest(chat);
15+
});
16+
});
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { NodejsPlatformServices } from '@metamask/kernel-node-runtime';
2+
import { makeSQLKernelDatabase } from '@metamask/kernel-store/sqlite/nodejs';
3+
import { waitUntilQuiescent } from '@metamask/kernel-utils';
4+
import {
5+
Logger,
6+
makeArrayTransport,
7+
makeConsoleTransport,
8+
} from '@metamask/logger';
9+
import type { LogEntry } from '@metamask/logger';
10+
import { Kernel } from '@metamask/ocap-kernel';
11+
import type {
12+
ChatParams,
13+
ChatResult,
14+
} from '@ocap/kernel-language-model-service';
15+
import {
16+
LANGUAGE_MODEL_SERVICE_NAME,
17+
makeKernelLanguageModelService,
18+
} from '@ocap/kernel-language-model-service';
19+
import { expect } from 'vitest';
20+
21+
import { DEFAULT_MODEL } from '../../src/constants.ts';
22+
import { filterTransports } from '../../src/utils.ts';
23+
24+
const getBundleSpec = (name: string): string =>
25+
new URL(`./vats/${name}.bundle`, import.meta.url).toString();
26+
27+
export const runLmsChatKernelTest = async (
28+
chat: (params: ChatParams & { stream?: true & false }) => Promise<ChatResult>,
29+
): Promise<void> => {
30+
const kernelDatabase = await makeSQLKernelDatabase({
31+
dbFilename: ':memory:',
32+
});
33+
34+
const entries: LogEntry[] = [];
35+
const logger = new Logger({
36+
transports: [
37+
filterTransports(makeConsoleTransport(), makeArrayTransport(entries)),
38+
],
39+
});
40+
41+
const platformServices = new NodejsPlatformServices({
42+
logger: logger.subLogger({ tags: ['vat-worker-manager'] }),
43+
});
44+
45+
const kernel = await Kernel.make(platformServices, kernelDatabase, {
46+
resetStorage: true,
47+
logger,
48+
});
49+
50+
const { name, service } = makeKernelLanguageModelService(chat);
51+
kernel.registerKernelServiceObject(name, service);
52+
53+
await kernel.launchSubcluster({
54+
bootstrap: 'main',
55+
services: [LANGUAGE_MODEL_SERVICE_NAME],
56+
vats: {
57+
main: {
58+
bundleSpec: getBundleSpec('lms-chat-vat'),
59+
parameters: { model: DEFAULT_MODEL },
60+
},
61+
},
62+
});
63+
await waitUntilQuiescent(100);
64+
65+
const responseEntry = entries.find((entry) =>
66+
entry.message?.startsWith('lms-chat response:'),
67+
);
68+
expect(responseEntry).toBeDefined();
69+
expect(responseEntry?.message?.length).toBeGreaterThan(
70+
'lms-chat response: '.length,
71+
);
72+
expect(responseEntry?.message).toMatch(/^lms-chat response: [hH]ello[.!]?$/u);
73+
};
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import type { ERef } from '@endo/eventual-send';
2+
import { makeDefaultExo } from '@metamask/kernel-utils/exo';
3+
import type { Logger } from '@metamask/logger';
4+
import { makeChatClient } from '@ocap/kernel-language-model-service';
5+
import type { ChatService } from '@ocap/kernel-language-model-service';
6+
7+
/**
8+
* A vat that uses a kernel language model service to perform a chat completion
9+
* and logs the response. Used by lms-kernel.test.ts to verify the full
10+
* kernel → LMS service → Ollama round-trip.
11+
*
12+
* @param vatPowers - Vat powers, expected to include a logger.
13+
* @param parameters - Vat parameters.
14+
* @param parameters.model - The model to use for chat completion.
15+
* @returns A default Exo instance.
16+
*/
17+
18+
export function buildRootObject(
19+
vatPowers: Record<string, unknown>,
20+
{ model }: { model: string },
21+
) {
22+
const logger = vatPowers.logger as Logger;
23+
const tlog = (message: string): void => {
24+
logger.subLogger({ tags: ['test', 'lms-chat'] }).log(message);
25+
};
26+
27+
return makeDefaultExo('root', {
28+
async bootstrap(
29+
_roots: unknown,
30+
{ languageModelService }: { languageModelService: ERef<ChatService> },
31+
) {
32+
const client = makeChatClient(languageModelService, model);
33+
const result = await client.chat.completions.create({
34+
messages: [
35+
{ role: 'user', content: 'Reply with exactly one word: hello.' },
36+
],
37+
});
38+
tlog(`lms-chat response: ${result.choices[0]?.message.content ?? ''}`);
39+
},
40+
});
41+
}

packages/kernel-test-local/tsconfig.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,22 @@
88
},
99
"references": [
1010
{ "path": "../kernel-agents" },
11+
{ "path": "../kernel-shims" },
1112
{ "path": "../kernel-agents-repl" },
1213
{ "path": "../kernel-language-model-service" },
14+
{ "path": "../kernel-store" },
15+
{ "path": "../kernel-utils" },
1316
{ "path": "../logger" },
17+
{ "path": "../ocap-kernel" },
18+
{ "path": "../kernel-node-runtime" },
1419
{ "path": "../repo-tools" }
1520
],
1621
"include": [
1722
"../../vitest.config.ts",
1823
"./src",
1924
"./vitest.config.ts",
2025
"./vitest.config.e2e.ts",
26+
"./vitest.config.integration.ts",
2127
"./test/e2e"
2228
]
2329
}

packages/kernel-test-local/vitest.config.e2e.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ export default defineConfig((args) => {
1010
defineProject({
1111
test: {
1212
name: 'kernel-test-local-e2e',
13-
testTimeout: 30_000,
13+
testTimeout: 60_000,
1414
hookTimeout: 10_000,
1515
include: ['./test/e2e/**/*.test.ts'],
16+
exclude: ['./test/e2e/lms-chat-kernel.integration.test.ts'],
1617
},
1718
}),
1819
);
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { mergeConfig } from '@ocap/repo-tools/vitest-config';
2+
import { fileURLToPath } from 'node:url';
3+
import { defineConfig, defineProject } from 'vitest/config';
4+
5+
import defaultConfig from '../../vitest.config.ts';
6+
7+
export default defineConfig((args) => {
8+
return mergeConfig(
9+
args,
10+
defaultConfig,
11+
defineProject({
12+
test: {
13+
name: 'kernel-test-local-integration',
14+
testTimeout: 30_000,
15+
hookTimeout: 10_000,
16+
setupFiles: [
17+
fileURLToPath(
18+
import.meta.resolve('@metamask/kernel-shims/endoify-node'),
19+
),
20+
],
21+
include: ['./test/e2e/lms-chat-kernel.integration.test.ts'],
22+
},
23+
}),
24+
);
25+
});

0 commit comments

Comments
 (0)