@skybridge/test 0.0.0-dev.09043e8
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/LICENSE +21 -0
- package/README.md +93 -0
- package/dist/chat.d.ts +33 -0
- package/dist/chat.d.ts.map +1 -0
- package/dist/chat.js +111 -0
- package/dist/chat.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/model.d.ts +15 -0
- package/dist/model.d.ts.map +1 -0
- package/dist/model.js +43 -0
- package/dist/model.js.map +1 -0
- package/dist/session-registry.d.ts +16 -0
- package/dist/session-registry.d.ts.map +1 -0
- package/dist/session-registry.js +29 -0
- package/dist/session-registry.js.map +1 -0
- package/package.json +75 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Alpic
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# @skybridge/test
|
|
2
|
+
|
|
3
|
+
Evals for [Skybridge](https://skybridge.tech) MCP apps: they answer whether a
|
|
4
|
+
prompt reaches the right tool with the right arguments, which no unit test can
|
|
5
|
+
check.
|
|
6
|
+
|
|
7
|
+
**Beta.** The API is still settling, so it publishes under the `beta`
|
|
8
|
+
dist-tag and may change between releases.
|
|
9
|
+
|
|
10
|
+
Add the `evals` option to the Skybridge Vite plugin, then write scenarios with
|
|
11
|
+
vitest's own `it` and `expect`.
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import { expect, it } from "vitest";
|
|
15
|
+
import { start } from "@skybridge/test";
|
|
16
|
+
import type { AppType } from "../src/server.js";
|
|
17
|
+
|
|
18
|
+
it("finds products by category", async () => {
|
|
19
|
+
const chat = await start<AppType>();
|
|
20
|
+
await chat.send("I am looking for ski goggles");
|
|
21
|
+
|
|
22
|
+
expect.chat(chat).toHaveCalledToolWith("search-products", {
|
|
23
|
+
category: "goggles",
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Choosing a model
|
|
29
|
+
|
|
30
|
+
`model` is a `provider/model-id` string. Only the provider you name is
|
|
31
|
+
imported, so install just that one as a dev dependency, and the key is read
|
|
32
|
+
from its own environment variable.
|
|
33
|
+
|
|
34
|
+
| prefix | package | key |
|
|
35
|
+
| --- | --- | --- |
|
|
36
|
+
| `anthropic` | `@ai-sdk/anthropic` | `ANTHROPIC_API_KEY` |
|
|
37
|
+
| `openai` | `@ai-sdk/openai` | `OPENAI_API_KEY` |
|
|
38
|
+
| `mistral` | `@ai-sdk/mistral` | `MISTRAL_API_KEY` |
|
|
39
|
+
| `google` | `@ai-sdk/google` | `GOOGLE_GENERATIVE_AI_API_KEY` |
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
skybridge({
|
|
43
|
+
evals: {
|
|
44
|
+
model: "anthropic/claude-sonnet-4-5",
|
|
45
|
+
project: { cwd: ".", command: ["node", "dist/server.js"] },
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`command` has to start the server process directly, since wrappers such as
|
|
51
|
+
`pnpm run` do not forward the channel the runner uses to learn the port.
|
|
52
|
+
|
|
53
|
+
Scenarios wait on a real model, so raise vitest's `testTimeout` well above its
|
|
54
|
+
default: `test: { testTimeout: 120_000 }`.
|
|
55
|
+
|
|
56
|
+
Any other prefix is passed to the AI SDK untouched, so a string such as
|
|
57
|
+
`openai/gpt-5` also resolves through the Vercel AI Gateway if you have
|
|
58
|
+
`AI_GATEWAY_API_KEY` set and no provider package installed.
|
|
59
|
+
|
|
60
|
+
## Models the prefixes cannot reach
|
|
61
|
+
|
|
62
|
+
For a local runtime, a company gateway, or a model that needs middleware,
|
|
63
|
+
build the model yourself and register it from a vitest setup file. It takes
|
|
64
|
+
precedence over `model`, which can then be left out.
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
// eval-model.ts
|
|
68
|
+
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
|
|
69
|
+
import { defineEvalModel } from "@skybridge/test";
|
|
70
|
+
|
|
71
|
+
defineEvalModel(
|
|
72
|
+
createOpenAICompatible({ baseURL: "http://localhost:11434/v1" })("llama3.1"),
|
|
73
|
+
);
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
// vitest.config.ts
|
|
78
|
+
export default defineConfig({
|
|
79
|
+
plugins: [skybridge({ evals: { project: { ... } } })],
|
|
80
|
+
test: { setupFiles: ["./eval-model.ts"] },
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Tool calls the server rejected do not count towards a positive assertion, so a
|
|
85
|
+
scenario cannot pass on a call that errored. To assert an error on purpose, use
|
|
86
|
+
`toHaveFailedToolCall`.
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
expect.chat(chat).toHaveFailedToolCall("create-checkout");
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
See the [documentation](https://docs.skybridge.tech) for setup and the full
|
|
93
|
+
matcher list.
|
package/dist/chat.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ToolCall, ToolDefinition } from "@skybridge/vite-plugin/evals";
|
|
2
|
+
import { type LanguageModel } from "ai";
|
|
3
|
+
interface HostConfig {
|
|
4
|
+
model: LanguageModel;
|
|
5
|
+
temperature?: number;
|
|
6
|
+
systemPrompt?: string;
|
|
7
|
+
maxSteps?: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* One conversation against one MCP session. A scenario asserts on
|
|
11
|
+
* {@link Chat.toolCalls}, the sequence of calls the model made.
|
|
12
|
+
*/
|
|
13
|
+
export declare class Chat<App = unknown> {
|
|
14
|
+
/** Every call the model made, in order, as it crossed the wire. */
|
|
15
|
+
readonly toolCalls: ToolCall<App>[];
|
|
16
|
+
private readonly messages;
|
|
17
|
+
private readonly failures;
|
|
18
|
+
private readonly client;
|
|
19
|
+
private readonly transport;
|
|
20
|
+
private readonly host;
|
|
21
|
+
private tools;
|
|
22
|
+
private constructor();
|
|
23
|
+
static open<App>(serverUrl: string, host: HostConfig): Promise<Chat<App>>;
|
|
24
|
+
/** The tool definitions the model was looking at, for failure reports. */
|
|
25
|
+
get toolDefinitions(): ToolDefinition[];
|
|
26
|
+
/** Takes a turn and returns the model's answer. */
|
|
27
|
+
send(prompt: string): Promise<string>;
|
|
28
|
+
close(): Promise<void>;
|
|
29
|
+
private failureFor;
|
|
30
|
+
private toolSet;
|
|
31
|
+
}
|
|
32
|
+
export {};
|
|
33
|
+
//# sourceMappingURL=chat.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../src/chat.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC7E,OAAO,EAIL,KAAK,aAAa,EAGnB,MAAM,IAAI,CAAC;AAEZ,UAAU,UAAU;IAClB,KAAK,EAAE,aAAa,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAID;;;GAGG;AACH,qBAAa,IAAI,CAAC,GAAG,GAAG,OAAO;IAC7B,mEAAmE;IACnE,QAAQ,CAAC,SAAS,EAAE,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAM;IAEzC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAsB;IAC/C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA6B;IACtD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAgC;IAC1D,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAa;IAClC,OAAO,CAAC,KAAK,CAAwB;IAErC,OAAO;WAUM,IAAI,CAAC,GAAG,EACnB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,UAAU,GACf,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAerB,0EAA0E;IAC1E,IAAI,eAAe,IAAI,cAAc,EAAE,CAEtC;IAED,mDAAmD;IAC7C,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IA4BrC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAI5B,OAAO,CAAC,UAAU;IAalB,OAAO,CAAC,OAAO;CAmChB"}
|
package/dist/chat.js
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
2
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
3
|
+
import { dynamicTool, generateText, jsonSchema, stepCountIs, } from "ai";
|
|
4
|
+
const DEFAULT_MAX_STEPS = 8;
|
|
5
|
+
/**
|
|
6
|
+
* One conversation against one MCP session. A scenario asserts on
|
|
7
|
+
* {@link Chat.toolCalls}, the sequence of calls the model made.
|
|
8
|
+
*/
|
|
9
|
+
export class Chat {
|
|
10
|
+
/** Every call the model made, in order, as it crossed the wire. */
|
|
11
|
+
toolCalls = [];
|
|
12
|
+
messages = [];
|
|
13
|
+
failures = new Map();
|
|
14
|
+
client;
|
|
15
|
+
transport;
|
|
16
|
+
host;
|
|
17
|
+
tools = [];
|
|
18
|
+
constructor(client, transport, host) {
|
|
19
|
+
this.client = client;
|
|
20
|
+
this.transport = transport;
|
|
21
|
+
this.host = host;
|
|
22
|
+
}
|
|
23
|
+
static async open(serverUrl, host) {
|
|
24
|
+
const client = new Client({ name: "skybridge-eval", version: "0" });
|
|
25
|
+
const transport = new StreamableHTTPClientTransport(new URL(serverUrl));
|
|
26
|
+
await client.connect(transport);
|
|
27
|
+
const chat = new Chat(client, transport, host);
|
|
28
|
+
try {
|
|
29
|
+
const { tools } = await client.listTools();
|
|
30
|
+
chat.tools = tools;
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
await transport.close();
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
36
|
+
return chat;
|
|
37
|
+
}
|
|
38
|
+
/** The tool definitions the model was looking at, for failure reports. */
|
|
39
|
+
get toolDefinitions() {
|
|
40
|
+
return this.tools;
|
|
41
|
+
}
|
|
42
|
+
/** Takes a turn and returns the model's answer. */
|
|
43
|
+
async send(prompt) {
|
|
44
|
+
this.messages.push({ role: "user", content: prompt });
|
|
45
|
+
const result = await generateText({
|
|
46
|
+
model: this.host.model,
|
|
47
|
+
temperature: this.host.temperature ?? 0,
|
|
48
|
+
...(this.host.systemPrompt === undefined
|
|
49
|
+
? {}
|
|
50
|
+
: { system: this.host.systemPrompt }),
|
|
51
|
+
messages: this.messages,
|
|
52
|
+
tools: this.toolSet(),
|
|
53
|
+
stopWhen: stepCountIs(this.host.maxSteps ?? DEFAULT_MAX_STEPS),
|
|
54
|
+
});
|
|
55
|
+
for (const step of result.steps) {
|
|
56
|
+
for (const call of step.toolCalls) {
|
|
57
|
+
this.toolCalls.push({
|
|
58
|
+
name: call.toolName,
|
|
59
|
+
arguments: (call.input ?? {}),
|
|
60
|
+
...this.failureFor(call),
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
this.messages.push(...result.response.messages);
|
|
65
|
+
return result.text;
|
|
66
|
+
}
|
|
67
|
+
async close() {
|
|
68
|
+
await this.transport.close();
|
|
69
|
+
}
|
|
70
|
+
failureFor(call) {
|
|
71
|
+
const reported = this.failures.get(call.toolCallId);
|
|
72
|
+
if (reported !== undefined) {
|
|
73
|
+
return { failed: reported };
|
|
74
|
+
}
|
|
75
|
+
if (call.invalid === true) {
|
|
76
|
+
return { failed: "the model's arguments could not be parsed" };
|
|
77
|
+
}
|
|
78
|
+
return {};
|
|
79
|
+
}
|
|
80
|
+
toolSet() {
|
|
81
|
+
const entries = this.tools.map((definition) => {
|
|
82
|
+
const { $schema: _ignored, ...parameters } = definition.inputSchema;
|
|
83
|
+
return [
|
|
84
|
+
definition.name,
|
|
85
|
+
dynamicTool({
|
|
86
|
+
description: definition.description ?? "",
|
|
87
|
+
inputSchema: jsonSchema(parameters),
|
|
88
|
+
execute: async (input, { toolCallId }) => {
|
|
89
|
+
let result;
|
|
90
|
+
try {
|
|
91
|
+
result = await this.client.callTool({
|
|
92
|
+
name: definition.name,
|
|
93
|
+
arguments: (input ?? {}),
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
this.failures.set(toolCallId, `the call never reached the server: ${error instanceof Error ? error.message : String(error)}`);
|
|
98
|
+
throw error;
|
|
99
|
+
}
|
|
100
|
+
if (result.isError === true) {
|
|
101
|
+
this.failures.set(toolCallId, `the tool returned an error: ${JSON.stringify(result.content)}`);
|
|
102
|
+
}
|
|
103
|
+
return result.content ?? result;
|
|
104
|
+
},
|
|
105
|
+
}),
|
|
106
|
+
];
|
|
107
|
+
});
|
|
108
|
+
return Object.fromEntries(entries);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=chat.js.map
|
package/dist/chat.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat.js","sourceRoot":"","sources":["../src/chat.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,aAAa,EACb,YAAY,GACb,MAAM,8BAA8B,CAAC;AACtC,YAAY,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC"}
|
package/dist/model.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { LanguageModel } from "ai";
|
|
2
|
+
/**
|
|
3
|
+
* Registers a model instance from a setup file, taking precedence over the
|
|
4
|
+
* configured `model` string. The escape hatch for anything the provider
|
|
5
|
+
* prefixes cannot express, such as a local runtime or a company LLM gateway.
|
|
6
|
+
*/
|
|
7
|
+
export declare function defineEvalModel(model: LanguageModel): void;
|
|
8
|
+
/**
|
|
9
|
+
* Turns a `provider/model-id` string into a model instance, importing only the
|
|
10
|
+
* provider named. Each provider package reads its own conventional API key
|
|
11
|
+
* variable, so no key configuration is needed. An unrecognised prefix is
|
|
12
|
+
* returned untouched for the AI SDK's default provider to resolve.
|
|
13
|
+
*/
|
|
14
|
+
export declare function resolveModel(spec: string | undefined): Promise<LanguageModel>;
|
|
15
|
+
//# sourceMappingURL=model.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,IAAI,CAAC;AAaxC;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI,CAE1D;AAED;;;;;GAKG;AACH,wBAAsB,YAAY,CAChC,IAAI,EAAE,MAAM,GAAG,SAAS,GACvB,OAAO,CAAC,aAAa,CAAC,CAwBxB"}
|
package/dist/model.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const PROVIDERS = {
|
|
2
|
+
anthropic: () => import("@ai-sdk/anthropic").then((m) => m.anthropic),
|
|
3
|
+
google: () => import("@ai-sdk/google").then((m) => m.google),
|
|
4
|
+
mistral: () => import("@ai-sdk/mistral").then((m) => m.mistral),
|
|
5
|
+
openai: () => import("@ai-sdk/openai").then((m) => m.openai),
|
|
6
|
+
};
|
|
7
|
+
let registered;
|
|
8
|
+
/**
|
|
9
|
+
* Registers a model instance from a setup file, taking precedence over the
|
|
10
|
+
* configured `model` string. The escape hatch for anything the provider
|
|
11
|
+
* prefixes cannot express, such as a local runtime or a company LLM gateway.
|
|
12
|
+
*/
|
|
13
|
+
export function defineEvalModel(model) {
|
|
14
|
+
registered = model;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Turns a `provider/model-id` string into a model instance, importing only the
|
|
18
|
+
* provider named. Each provider package reads its own conventional API key
|
|
19
|
+
* variable, so no key configuration is needed. An unrecognised prefix is
|
|
20
|
+
* returned untouched for the AI SDK's default provider to resolve.
|
|
21
|
+
*/
|
|
22
|
+
export async function resolveModel(spec) {
|
|
23
|
+
if (registered !== undefined) {
|
|
24
|
+
return registered;
|
|
25
|
+
}
|
|
26
|
+
if (spec === undefined) {
|
|
27
|
+
throw new Error("No model configured. Set `model` on the evals plugin, or call defineEvalModel() from a setup file.");
|
|
28
|
+
}
|
|
29
|
+
const separator = spec.indexOf("/");
|
|
30
|
+
if (separator === -1) {
|
|
31
|
+
return spec;
|
|
32
|
+
}
|
|
33
|
+
const name = spec.slice(0, separator);
|
|
34
|
+
const load = PROVIDERS[name];
|
|
35
|
+
if (load === undefined) {
|
|
36
|
+
return spec;
|
|
37
|
+
}
|
|
38
|
+
const provider = await load().catch(() => {
|
|
39
|
+
throw new Error(`The model "${spec}" needs @ai-sdk/${name}@^2. Install it as a dev dependency.`);
|
|
40
|
+
});
|
|
41
|
+
return provider(spec.slice(separator + 1));
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=model.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model.js","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"AAIA,MAAM,SAAS,GAAmD;IAChE,SAAS,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;IACrE,MAAM,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;IAC5D,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;IAC/D,MAAM,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;CAC7D,CAAC;AAEF,IAAI,UAAqC,CAAC;AAE1C;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,KAAoB;IAClD,UAAU,GAAG,KAAK,CAAC;AACrB,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,IAAwB;IAExB,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;QAC7B,OAAO,UAAU,CAAC;IACpB,CAAC;IACD,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CACb,oGAAoG,CACrG,CAAC;IACJ,CAAC;IACD,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACpC,IAAI,SAAS,KAAK,CAAC,CAAC,EAAE,CAAC;QACrB,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;IACtC,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IAC7B,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,QAAQ,GAAG,MAAM,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE;QACvC,MAAM,IAAI,KAAK,CACb,cAAc,IAAI,mBAAmB,IAAI,sCAAsC,CAChF,CAAC;IACJ,CAAC,CAAC,CAAC;IACH,OAAO,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC;AAC7C,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Chat } from "./chat.js";
|
|
2
|
+
/**
|
|
3
|
+
* Opens a fresh MCP session and conversation against the server the plugin
|
|
4
|
+
* started. The session is closed when the current test finishes, so scenarios
|
|
5
|
+
* never leak sessions and never write teardown, and tests that run
|
|
6
|
+
* concurrently cannot close each other's sessions.
|
|
7
|
+
*
|
|
8
|
+
* The type parameter pins the assertions to the project's registry:
|
|
9
|
+
* `start<AppType>()` returns a `Chat<AppType>`, and `expect.chat` infers the
|
|
10
|
+
* tool names and argument shapes from it.
|
|
11
|
+
*/
|
|
12
|
+
export declare function start<App>(overrides?: {
|
|
13
|
+
systemPrompt?: string;
|
|
14
|
+
temperature?: number;
|
|
15
|
+
}): Promise<Chat<App>>;
|
|
16
|
+
//# sourceMappingURL=session-registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-registry.d.ts","sourceRoot":"","sources":["../src/session-registry.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAGjC;;;;;;;;;GASG;AACH,wBAAsB,KAAK,CAAC,GAAG,EAAE,SAAS,CAAC,EAAE;IAC3C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAgBrB"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { inject, onTestFinished } from "vitest";
|
|
2
|
+
import { Chat } from "./chat.js";
|
|
3
|
+
import { resolveModel } from "./model.js";
|
|
4
|
+
/**
|
|
5
|
+
* Opens a fresh MCP session and conversation against the server the plugin
|
|
6
|
+
* started. The session is closed when the current test finishes, so scenarios
|
|
7
|
+
* never leak sessions and never write teardown, and tests that run
|
|
8
|
+
* concurrently cannot close each other's sessions.
|
|
9
|
+
*
|
|
10
|
+
* The type parameter pins the assertions to the project's registry:
|
|
11
|
+
* `start<AppType>()` returns a `Chat<AppType>`, and `expect.chat` infers the
|
|
12
|
+
* tool names and argument shapes from it.
|
|
13
|
+
*/
|
|
14
|
+
export async function start(overrides) {
|
|
15
|
+
const config = inject("skybridgeEvals");
|
|
16
|
+
const url = inject("skybridgeEvalsUrl");
|
|
17
|
+
if (config === undefined || url === undefined) {
|
|
18
|
+
throw new Error("No eval server is running. Add `skybridge({ evals: {...} })` to the vitest config.");
|
|
19
|
+
}
|
|
20
|
+
const chat = await Chat.open(url, {
|
|
21
|
+
model: await resolveModel(config.model),
|
|
22
|
+
temperature: overrides?.temperature ?? config.temperature,
|
|
23
|
+
systemPrompt: overrides?.systemPrompt ?? config.systemPrompt,
|
|
24
|
+
maxSteps: config.maxSteps,
|
|
25
|
+
});
|
|
26
|
+
onTestFinished(() => chat.close());
|
|
27
|
+
return chat;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=session-registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-registry.js","sourceRoot":"","sources":["../src/session-registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAC;AAChD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,KAAK,CAAM,SAGhC;IACC,MAAM,MAAM,GAAG,MAAM,CAAC,gBAAgB,CAAC,CAAC;IACxC,MAAM,GAAG,GAAG,MAAM,CAAC,mBAAmB,CAAC,CAAC;IACxC,IAAI,MAAM,KAAK,SAAS,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;QAC9C,MAAM,IAAI,KAAK,CACb,oFAAoF,CACrF,CAAC;IACJ,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,CAAM,GAAG,EAAE;QACrC,KAAK,EAAE,MAAM,YAAY,CAAC,MAAM,CAAC,KAAK,CAAC;QACvC,WAAW,EAAE,SAAS,EAAE,WAAW,IAAI,MAAM,CAAC,WAAW;QACzD,YAAY,EAAE,SAAS,EAAE,YAAY,IAAI,MAAM,CAAC,YAAY;QAC5D,QAAQ,EAAE,MAAM,CAAC,QAAQ;KAC1B,CAAC,CAAC;IACH,cAAc,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;IACnC,OAAO,IAAI,CAAC;AACd,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@skybridge/test",
|
|
3
|
+
"version": "0.0.0-dev.09043e8",
|
|
4
|
+
"description": "Eval runner for Skybridge MCP apps: does a prompt reach the right tool with the right arguments",
|
|
5
|
+
"license": "ISC",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/alpic-ai/skybridge.git"
|
|
10
|
+
},
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"default": "./dist/index.js"
|
|
15
|
+
},
|
|
16
|
+
"./vitest-config": {
|
|
17
|
+
"types": "./dist/vitest-config.d.ts",
|
|
18
|
+
"default": "./dist/vitest-config.js"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
26
|
+
"ai": "^5.0.0",
|
|
27
|
+
"@skybridge/vite-plugin": "0.0.0-dev.09043e8"
|
|
28
|
+
},
|
|
29
|
+
"peerDependencies": {
|
|
30
|
+
"@ai-sdk/anthropic": "^2.0.0",
|
|
31
|
+
"@ai-sdk/google": "^2.0.0",
|
|
32
|
+
"@ai-sdk/mistral": "^2.0.0",
|
|
33
|
+
"@ai-sdk/openai": "^2.0.0",
|
|
34
|
+
"skybridge": "^2.0.0",
|
|
35
|
+
"vitest": "^4.1.0"
|
|
36
|
+
},
|
|
37
|
+
"peerDependenciesMeta": {
|
|
38
|
+
"@ai-sdk/anthropic": {
|
|
39
|
+
"optional": true
|
|
40
|
+
},
|
|
41
|
+
"@ai-sdk/google": {
|
|
42
|
+
"optional": true
|
|
43
|
+
},
|
|
44
|
+
"@ai-sdk/mistral": {
|
|
45
|
+
"optional": true
|
|
46
|
+
},
|
|
47
|
+
"@ai-sdk/openai": {
|
|
48
|
+
"optional": true
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@ai-sdk/anthropic": "^2.0.0",
|
|
53
|
+
"@ai-sdk/google": "^2.0.0",
|
|
54
|
+
"@ai-sdk/mistral": "^2.0.0",
|
|
55
|
+
"@ai-sdk/openai": "^2.0.0",
|
|
56
|
+
"@types/node": "^24.13.3",
|
|
57
|
+
"typescript": "^5.9.3",
|
|
58
|
+
"vitest": "^4.1.10",
|
|
59
|
+
"skybridge": "0.0.0-dev.09043e8"
|
|
60
|
+
},
|
|
61
|
+
"engines": {
|
|
62
|
+
"node": ">=22.23.1"
|
|
63
|
+
},
|
|
64
|
+
"publishConfig": {
|
|
65
|
+
"tag": "beta"
|
|
66
|
+
},
|
|
67
|
+
"scripts": {
|
|
68
|
+
"build": "tsc -p tsconfig.json",
|
|
69
|
+
"format": "biome check --write --error-on-warnings",
|
|
70
|
+
"test": "pnpm run test:format && pnpm run test:types && pnpm run test:unit",
|
|
71
|
+
"test:format": "biome ci",
|
|
72
|
+
"test:types": "tsc --noEmit -p tsconfig.test.json",
|
|
73
|
+
"test:unit": "vitest run"
|
|
74
|
+
}
|
|
75
|
+
}
|