Skip to content

Commit ea9f656

Browse files
grypezclaude
andcommitted
test(kernel-test-local): kernel-LMS integration tests
Add an integration test and e2e test that exercise the full kernel → LMS service → Ollama round-trip via a bundled vat. - lms-chat-vat: bundled vat that sends a single chat message and logs the response, used to verify the round-trip through the kernel - lms-chat.ts: shared test helper (runLmsChatKernelTest) - lms-chat.test.ts: uses a mock fetch — CI-safe, no network - lms-chat.e2e.test.ts: uses real fetch against local Ollama - agents.e2e.test.ts: json/repl agent e2e tests against local Ollama - test/suite.test.ts: pre-flight check that Ollama is running Lay out the package to match kernel-test: all vats, helpers, and test files live under src/; test/ holds only the Ollama pre-flight suite. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent e5f0f0e commit ea9f656

13 files changed

Lines changed: 186 additions & 11 deletions

packages/kernel-test-local/package.json

Lines changed: 9 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 src/vats",
17+
"clean": "rimraf --glob './*.tsbuildinfo' ./.eslintcache ./coverage ./.turbo ./logs './src/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",
@@ -29,7 +30,12 @@
2930
"test:dev:quiet": "yarn test:dev --reporter @ocap/repo-tools/vitest-reporters/silent"
3031
},
3132
"dependencies": {
33+
"@endo/eventual-send": "^1.3.4",
34+
"@metamask/kernel-node-runtime": "workspace:^",
35+
"@metamask/kernel-store": "workspace:^",
36+
"@metamask/kernel-utils": "workspace:^",
3237
"@metamask/logger": "workspace:^",
38+
"@metamask/ocap-kernel": "workspace:^",
3339
"@ocap/kernel-agents": "workspace:^",
3440
"@ocap/kernel-language-model-service": "workspace:^",
3541
"@ocap/repo-tools": "workspace:^"
@@ -39,6 +45,8 @@
3945
"@metamask/eslint-config": "^15.0.0",
4046
"@metamask/eslint-config-nodejs": "^15.0.0",
4147
"@metamask/eslint-config-typescript": "^15.0.0",
48+
"@metamask/kernel-cli": "workspace:^",
49+
"@metamask/kernel-shims": "workspace:^",
4250
"@ocap/kernel-agents-repl": "workspace:^",
4351
"@types/node": "^22.13.1",
4452
"@typescript-eslint/eslint-plugin": "^8.29.0",

packages/kernel-test-local/test/e2e/agents.test.ts renamed to packages/kernel-test-local/src/agents.e2e.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ import {
1919
vi,
2020
} from 'vitest';
2121

22-
import { DEFAULT_MODEL } from '../../src/constants.ts';
23-
import { filterTransports, randomLetter } from '../../src/utils.ts';
22+
import { DEFAULT_MODEL } from './constants.ts';
23+
import { filterTransports, randomLetter } from './utils.ts';
2424

2525
const logger = new Logger({
2626
tags: ['test'],

packages/kernel-test-local/src/constants.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,8 @@ export const TEST_MODELS = ['llama3.1:latest', 'gpt-oss:20b'];
1010
export const OLLAMA_API_BASE = 'http://localhost:11434';
1111
export const OLLAMA_TAGS_ENDPOINT = `${OLLAMA_API_BASE}/api/tags`;
1212

13-
// extract ignored logger tags from environment variable
14-
1513
/**
16-
* The tags to ignore for the local tests.
14+
* Logger tags to ignore, parsed from the LOGGER_IGNORE environment variable.
1715
*/
1816
export const IGNORE_TAGS =
1917
// eslint-disable-next-line n/no-process-env
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.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: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import '@metamask/kernel-shims/endoify-node';
2+
3+
import { makeOpenV1NodejsService } from '@ocap/kernel-language-model-service';
4+
import { makeMockOpenV1Fetch } from '@ocap/kernel-language-model-service/test-utils';
5+
import { describe, it } from 'vitest';
6+
7+
import { runLmsChatKernelTest } from './lms-chat.ts';
8+
9+
describe.sequential('lms-kernel', () => {
10+
// eslint-disable-next-line vitest/expect-expect
11+
it('sends a chat message through the kernel and receives a response', async () => {
12+
const { chat } = makeOpenV1NodejsService({
13+
endowments: { fetch: makeMockOpenV1Fetch(['Hello.']) },
14+
baseUrl: 'http://localhost:11434',
15+
});
16+
await runLmsChatKernelTest(chat);
17+
});
18+
});
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 './constants.ts';
22+
import { filterTransports } from './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+
};

packages/kernel-test-local/src/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export const filterTransports = (
2323
/**
2424
* Generate a random letter.
2525
*
26-
* @returns a random letter.
26+
* @returns A random letter.
2727
*/
2828
export function randomLetter(): string {
2929
return String.fromCharCode(Math.floor(Math.random() * 26) + 97);
File renamed without changes.
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-chat.test.ts and lms-chat.e2e.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+
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
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/test/e2e/suite.test.ts renamed to packages/kernel-test-local/test/suite.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
DEFAULT_MODEL,
1414
OLLAMA_API_BASE,
1515
OLLAMA_TAGS_ENDPOINT,
16-
} from '../../src/constants.ts';
16+
} from '../src/constants.ts';
1717

1818
describe.sequential('test suite', () => {
1919
beforeAll(() => {

0 commit comments

Comments
 (0)