@capybara-hq/testing 0.0.0-stage → 0.1.0-rc.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/LICENSE +202 -0
- package/NOTICE +2 -0
- package/README.md +40 -2
- package/dist/containers.d.ts +102 -0
- package/dist/containers.js +314 -0
- package/dist/dual-replica.d.ts +142 -0
- package/dist/dual-replica.js +270 -0
- package/dist/extension-config.d.ts +9 -0
- package/dist/extension-config.js +16 -0
- package/dist/fake-channel.d.ts +162 -0
- package/dist/fake-channel.js +325 -0
- package/dist/fake-e2b.d.ts +104 -0
- package/dist/fake-e2b.js +500 -0
- package/dist/fake-git-server.d.ts +57 -0
- package/dist/fake-git-server.js +323 -0
- package/dist/fake-lark.d.ts +234 -0
- package/dist/fake-lark.js +837 -0
- package/dist/fake-marketplace.d.ts +25 -0
- package/dist/fake-marketplace.js +58 -0
- package/dist/fake-mcp-server.d.ts +134 -0
- package/dist/fake-mcp-server.js +308 -0
- package/dist/fake-model-server.d.ts +28 -0
- package/dist/fake-model-server.js +67 -0
- package/dist/fake-npm-registry.d.ts +59 -0
- package/dist/fake-npm-registry.js +174 -0
- package/dist/fake-oauth-server.d.ts +63 -0
- package/dist/fake-oauth-server.js +284 -0
- package/dist/fake-openapi-server.d.ts +50 -0
- package/dist/fake-openapi-server.js +278 -0
- package/dist/fake-slack.d.ts +258 -0
- package/dist/fake-slack.js +654 -0
- package/dist/fixtures.d.ts +49 -0
- package/dist/fixtures.js +92 -0
- package/dist/global-setup.d.ts +15 -0
- package/dist/global-setup.js +58 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +25 -0
- package/dist/kernel-helpers.d.ts +125 -0
- package/dist/kernel-helpers.js +336 -0
- package/dist/media-samples.d.ts +10 -0
- package/dist/media-samples.js +76 -0
- package/dist/model-conformance.d.ts +61 -0
- package/dist/model-conformance.js +287 -0
- package/dist/replica-runner.d.ts +1 -0
- package/dist/replica-runner.js +59 -0
- package/dist/sse.d.ts +20 -0
- package/dist/sse.js +87 -0
- package/fixtures/extensions/fake-bootstrap-a/.capybara-plugin/plugin.json +7 -0
- package/fixtures/extensions/fake-bootstrap-a/package.json +12 -0
- package/fixtures/extensions/fake-bootstrap-a/src/index.ts +62 -0
- package/fixtures/extensions/fake-bootstrap-b/.capybara-plugin/plugin.json +7 -0
- package/fixtures/extensions/fake-bootstrap-b/migrations/0001_init.sql +10 -0
- package/fixtures/extensions/fake-bootstrap-b/package.json +12 -0
- package/fixtures/extensions/fake-bootstrap-b/src/index.ts +123 -0
- package/fixtures/extensions/fake-connector/.capybara-plugin/plugin.json +7 -0
- package/fixtures/extensions/fake-connector/connectors/fake-demo.json +12 -0
- package/fixtures/extensions/fake-connector/package.json +13 -0
- package/fixtures/extensions/fake-connector/src/index.ts +477 -0
- package/fixtures/extensions/fake-dep-provider/.capybara-plugin/plugin.json +7 -0
- package/fixtures/extensions/fake-dep-provider/package.json +12 -0
- package/fixtures/extensions/fake-dep-provider/src/index.ts +19 -0
- package/fixtures/extensions/fake-dep-requirer/.capybara-plugin/plugin.json +8 -0
- package/fixtures/extensions/fake-dep-requirer/package.json +12 -0
- package/fixtures/extensions/fake-dep-requirer/src/index.ts +21 -0
- package/fixtures/extensions/fake-embed/.capybara-plugin/plugin.json +7 -0
- package/fixtures/extensions/fake-embed/package.json +12 -0
- package/fixtures/extensions/fake-embed/src/index.ts +207 -0
- package/fixtures/extensions/fake-im/.capybara-plugin/plugin.json +7 -0
- package/fixtures/extensions/fake-im/package.json +12 -0
- package/fixtures/extensions/fake-im/src/channel.ts +224 -0
- package/fixtures/extensions/fake-im/src/directory.ts +25 -0
- package/fixtures/extensions/fake-im/src/identity.ts +66 -0
- package/fixtures/extensions/fake-im/src/index.ts +91 -0
- package/fixtures/extensions/fake-object-store/.capybara-plugin/plugin.json +7 -0
- package/fixtures/extensions/fake-object-store/package.json +15 -0
- package/fixtures/extensions/fake-object-store/src/index.ts +210 -0
- package/fixtures/extensions/fake-openapi/.capybara-plugin/plugin.json +7 -0
- package/fixtures/extensions/fake-openapi/package.json +12 -0
- package/fixtures/extensions/fake-openapi/src/index.ts +258 -0
- package/fixtures/extensions/fake-storage-user/.capybara-plugin/plugin.json +7 -0
- package/fixtures/extensions/fake-storage-user/package.json +12 -0
- package/fixtures/extensions/fake-storage-user/src/index.ts +58 -0
- package/fixtures/extensions/fake-tools-ui/.capybara-plugin/plugin.json +7 -0
- package/fixtures/extensions/fake-tools-ui/connectors/fake-notes.json +11 -0
- package/fixtures/extensions/fake-tools-ui/migrations/0001_init.sql +7 -0
- package/fixtures/extensions/fake-tools-ui/package.json +12 -0
- package/fixtures/extensions/fake-tools-ui/skills/fake-notes/SKILL.md +7 -0
- package/fixtures/extensions/fake-tools-ui/src/index.ts +140 -0
- package/package.json +67 -3
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"displayName": { "zh": "笔记服务", "en": "Notes service" },
|
|
3
|
+
"description": "A connector template of the fake-tools-ui bundle (fake connector, kind fake)",
|
|
4
|
+
"template": {
|
|
5
|
+
"plugin": "fake-connector",
|
|
6
|
+
"connector": "fake",
|
|
7
|
+
"endpoint": { "url": "https://notes.example.com/mcp" },
|
|
8
|
+
"editableEndpoint": ["url"],
|
|
9
|
+
"auth": { "type": "bearer", "ownership": "org" }
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
-- Runs with search_path "plugin_fake_tools_ui, public": table names carry no schema prefix.
|
|
2
|
+
-- One item list per conversation (replaced as a whole on every write).
|
|
3
|
+
CREATE TABLE IF NOT EXISTS items (
|
|
4
|
+
conversation_id text PRIMARY KEY,
|
|
5
|
+
items jsonb NOT NULL,
|
|
6
|
+
updated_at timestamptz NOT NULL DEFAULT now()
|
|
7
|
+
);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@capybara-hq/test-plugin-fake-tools-ui",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"description": "Test fake: an extension tool with a conversation view, a migration, a skill and a connector template",
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"dependencies": {
|
|
9
|
+
"@capybara-hq/plugin-sdk": "^0.1.0-rc.0",
|
|
10
|
+
"zod": "^4.6.7"
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineExtension,
|
|
3
|
+
type ExtensionContext,
|
|
4
|
+
ToolError,
|
|
5
|
+
type ToolSpec,
|
|
6
|
+
type UiView,
|
|
7
|
+
} from "@capybara-hq/plugin-sdk";
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Test fake of a tool + UI extension: the kernel contracts an extension tool, a schema-rendered
|
|
12
|
+
* view, a migration and a full plugin bundle exercise, without the official todo plugin. Like todo
|
|
13
|
+
* it needs no object store (storage is `fake-storage-user`'s).
|
|
14
|
+
* - tool `write` (model-visible `fake-tools-ui__write`, default policy allow): replaces the
|
|
15
|
+
* conversation's item list in the extension's own table, then invalidates the view `tasks`;
|
|
16
|
+
* - view `tasks` (conversation tab): badge `<done>/<total>` and a checklist;
|
|
17
|
+
* - the plugin also ships the skill `fake-notes` and the connector template `fake-notes`
|
|
18
|
+
* (for the `fake-connector` extension).
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
type Db = ExtensionContext["db"];
|
|
22
|
+
|
|
23
|
+
const Item = z.object({
|
|
24
|
+
text: z.string().min(1),
|
|
25
|
+
status: z.enum(["pending", "in_progress", "completed"]),
|
|
26
|
+
});
|
|
27
|
+
const WriteInput = z.object({ items: z.array(Item).max(100) });
|
|
28
|
+
|
|
29
|
+
/** One item of the list. */
|
|
30
|
+
export type FakeItem = z.infer<typeof Item>;
|
|
31
|
+
|
|
32
|
+
const WRITE: ToolSpec = {
|
|
33
|
+
name: "write",
|
|
34
|
+
description:
|
|
35
|
+
"Replace the item list of the current conversation. Send every item each time; status is " +
|
|
36
|
+
"pending, in_progress or completed. Test fixture only.",
|
|
37
|
+
inputSchema: {
|
|
38
|
+
type: "object",
|
|
39
|
+
properties: {
|
|
40
|
+
items: {
|
|
41
|
+
type: "array",
|
|
42
|
+
maxItems: 100,
|
|
43
|
+
description: "The complete list, in order.",
|
|
44
|
+
items: {
|
|
45
|
+
type: "object",
|
|
46
|
+
properties: {
|
|
47
|
+
text: { type: "string", minLength: 1, description: "What the item is." },
|
|
48
|
+
status: {
|
|
49
|
+
type: "string",
|
|
50
|
+
enum: ["pending", "in_progress", "completed"],
|
|
51
|
+
description: "Progress of the item.",
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
required: ["text", "status"],
|
|
55
|
+
additionalProperties: false,
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
required: ["items"],
|
|
60
|
+
additionalProperties: false,
|
|
61
|
+
},
|
|
62
|
+
defaultPolicy: "allow",
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const render = (items: FakeItem[]): string =>
|
|
66
|
+
items.length === 0
|
|
67
|
+
? "The list is now empty."
|
|
68
|
+
: `List updated (${items.length} items):\n${items.map((i) => `[${i.status}] ${i.text}`).join("\n")}`;
|
|
69
|
+
|
|
70
|
+
async function writeItems(db: Db, conversationId: string, items: FakeItem[]): Promise<void> {
|
|
71
|
+
await db
|
|
72
|
+
.insertInto("items")
|
|
73
|
+
.values({ conversation_id: conversationId, items: JSON.stringify(items) })
|
|
74
|
+
.onConflict((oc) =>
|
|
75
|
+
oc.column("conversation_id").doUpdateSet((eb) => ({
|
|
76
|
+
items: eb.ref("excluded.items"),
|
|
77
|
+
updated_at: new Date(),
|
|
78
|
+
})),
|
|
79
|
+
)
|
|
80
|
+
.execute();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function readItems(db: Db, conversationId: string): Promise<FakeItem[]> {
|
|
84
|
+
const row = await db
|
|
85
|
+
.selectFrom("items")
|
|
86
|
+
.select("items")
|
|
87
|
+
.where("conversation_id", "=", conversationId)
|
|
88
|
+
.executeTakeFirst();
|
|
89
|
+
return row === undefined ? [] : (row.items as FakeItem[]);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** The `tasks` view of a list. */
|
|
93
|
+
export function tasksView(items: FakeItem[]): UiView {
|
|
94
|
+
const done = items.filter((i) => i.status === "completed").length;
|
|
95
|
+
return {
|
|
96
|
+
badge: `${done}/${items.length}`,
|
|
97
|
+
blocks: [{ type: "checklist", items: items.map((i) => ({ text: i.text, status: i.status })) }],
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export default defineExtension({
|
|
102
|
+
name: "fake-tools-ui",
|
|
103
|
+
version: "0.1.0",
|
|
104
|
+
sdk: "^0.1.0-rc.0",
|
|
105
|
+
provides: [],
|
|
106
|
+
migrations: "../migrations",
|
|
107
|
+
ui: [{ id: "tasks", title: { zh: "条目", en: "Items" }, place: "conversation", kind: "view" }],
|
|
108
|
+
permissions: { tools: ["write"], ui: { views: ["tasks"] } },
|
|
109
|
+
setup(ctx) {
|
|
110
|
+
ctx.tools.register(WRITE, async (input, tctx) => {
|
|
111
|
+
const parsed = WriteInput.safeParse(input);
|
|
112
|
+
if (!parsed.success) {
|
|
113
|
+
throw new ToolError(
|
|
114
|
+
"Invalid items: pass an array of { text, status } where status is pending, in_progress or completed.",
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
const items = parsed.data.items;
|
|
118
|
+
await writeItems(ctx.db, tctx.conversationId, items);
|
|
119
|
+
await ctx.ui
|
|
120
|
+
.invalidate({
|
|
121
|
+
orgId: tctx.orgId,
|
|
122
|
+
conversationId: tctx.conversationId,
|
|
123
|
+
contribution: "tasks",
|
|
124
|
+
})
|
|
125
|
+
.catch(() => {});
|
|
126
|
+
return {
|
|
127
|
+
content: [{ type: "text", text: render(items) }],
|
|
128
|
+
display: {
|
|
129
|
+
summary: {
|
|
130
|
+
zh: `更新条目(${items.length} 项)`,
|
|
131
|
+
en: `Updated items (${items.length})`,
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
});
|
|
136
|
+
ctx.ui.view("tasks", async ({ conversationId }) =>
|
|
137
|
+
tasksView(conversationId === undefined ? [] : await readItems(ctx.db, conversationId)),
|
|
138
|
+
);
|
|
139
|
+
},
|
|
140
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,70 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capybara-hq/testing",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0-rc.1",
|
|
4
|
+
"description": "Test helpers for Capybara: test containers, dual-replica harness and fake third-party servers",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/capybara-hq/core.git",
|
|
9
|
+
"directory": "packages/testing"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/capybara-hq/core/tree/main/packages/testing#readme",
|
|
12
|
+
"type": "module",
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=24"
|
|
15
|
+
},
|
|
16
|
+
"main": "./dist/index.js",
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"import": "./dist/index.js",
|
|
22
|
+
"default": "./dist/index.js"
|
|
23
|
+
},
|
|
24
|
+
"./global-setup": {
|
|
25
|
+
"types": "./dist/global-setup.d.ts",
|
|
26
|
+
"import": "./dist/global-setup.js",
|
|
27
|
+
"default": "./dist/global-setup.js"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"dist",
|
|
32
|
+
"fixtures",
|
|
33
|
+
"README.md",
|
|
34
|
+
"NOTICE"
|
|
35
|
+
],
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@aws-sdk/client-s3": "^3.1154.0",
|
|
38
|
+
"@aws-sdk/s3-presigned-post": "^3.1154.0",
|
|
39
|
+
"@aws-sdk/s3-request-presigner": "^3.1154.0",
|
|
40
|
+
"@modelcontextprotocol/sdk": "^1.32.1",
|
|
41
|
+
"@testcontainers/postgresql": "^12.2.1",
|
|
42
|
+
"ioredis": "^6.0.0",
|
|
43
|
+
"kysely": "^0.29.6",
|
|
44
|
+
"pg": "^8.23.1",
|
|
45
|
+
"protobufjs": "^7.5.4",
|
|
46
|
+
"testcontainers": "^12.2.1",
|
|
47
|
+
"tsx": "^4.23.15",
|
|
48
|
+
"ws": "^8.22.0",
|
|
49
|
+
"zod": "^4.6.7",
|
|
50
|
+
"@capybara-hq/kernel": "^0.1.0-rc.0"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@types/pg": "^8.23.1",
|
|
54
|
+
"@types/ws": "^8.18.2",
|
|
55
|
+
"@capybara-hq/plugin-sdk": "^0.1.0-rc.0"
|
|
56
|
+
},
|
|
57
|
+
"peerDependencies": {
|
|
58
|
+
"vitest": "^5.0.5",
|
|
59
|
+
"@capybara-hq/plugin-sdk": "^0.1.0-rc.0",
|
|
60
|
+
"@capybara-hq/plugin-tools": "^0.1.0-rc.1"
|
|
61
|
+
},
|
|
62
|
+
"peerDependenciesMeta": {
|
|
63
|
+
"@capybara-hq/plugin-tools": {
|
|
64
|
+
"optional": true
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
"scripts": {
|
|
68
|
+
"build": "tsc -b"
|
|
69
|
+
}
|
|
6
70
|
}
|