refactor: migrate messaging plugins to sdk

This commit is contained in:
Peter Steinberger
2026-01-18 08:32:19 +00:00
parent 9241e21114
commit c5e19f5c67
63 changed files with 4082 additions and 376 deletions

View File

@@ -1,97 +1,101 @@
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { beforeAll, describe, expect, it, vi } from "vitest";
import type { ClawdbotConfig } from "../config/config.js";
import { ensureClawdbotModelsJson } from "./models-config.js";
vi.mock("@mariozechner/pi-ai", async () => {
const actual = await vi.importActual<typeof import("@mariozechner/pi-ai")>("@mariozechner/pi-ai");
const buildAssistantMessage = (model: { api: string; provider: string; id: string }) => ({
role: "assistant" as const,
content: [{ type: "text" as const, text: "ok" }],
stopReason: "stop" as const,
api: model.api,
provider: model.provider,
model: model.id,
usage: {
input: 1,
output: 1,
cacheRead: 0,
cacheWrite: 0,
totalTokens: 2,
cost: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
total: 0,
},
},
timestamp: Date.now(),
});
const buildAssistantErrorMessage = (model: { api: string; provider: string; id: string }) => ({
role: "assistant" as const,
content: [] as const,
stopReason: "error" as const,
errorMessage: "boom",
api: model.api,
provider: model.provider,
model: model.id,
usage: {
const buildAssistantMessage = (model: { api: string; provider: string; id: string }) => ({
role: "assistant" as const,
content: [{ type: "text" as const, text: "ok" }],
stopReason: "stop" as const,
api: model.api,
provider: model.provider,
model: model.id,
usage: {
input: 1,
output: 1,
cacheRead: 0,
cacheWrite: 0,
totalTokens: 2,
cost: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
totalTokens: 0,
cost: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
total: 0,
},
total: 0,
},
timestamp: Date.now(),
});
return {
...actual,
complete: async (model: { api: string; provider: string; id: string }) => {
if (model.id === "mock-error") return buildAssistantErrorMessage(model);
return buildAssistantMessage(model);
},
completeSimple: async (model: { api: string; provider: string; id: string }) => {
if (model.id === "mock-error") return buildAssistantErrorMessage(model);
return buildAssistantMessage(model);
},
streamSimple: (model: { api: string; provider: string; id: string }) => {
const stream = new actual.AssistantMessageEventStream();
queueMicrotask(() => {
stream.push({
type: "done",
reason: "stop",
message:
model.id === "mock-error"
? buildAssistantErrorMessage(model)
: buildAssistantMessage(model),
});
stream.end();
});
return stream;
},
};
},
timestamp: Date.now(),
});
const buildAssistantErrorMessage = (model: { api: string; provider: string; id: string }) => ({
role: "assistant" as const,
content: [] as const,
stopReason: "error" as const,
errorMessage: "boom",
api: model.api,
provider: model.provider,
model: model.id,
usage: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
totalTokens: 0,
cost: {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
total: 0,
},
},
timestamp: Date.now(),
});
const mockPiAi = () => {
vi.doMock("@mariozechner/pi-ai", async () => {
const actual = await vi.importActual<typeof import("@mariozechner/pi-ai")>(
"@mariozechner/pi-ai",
);
return {
...actual,
complete: async (model: { api: string; provider: string; id: string }) => {
if (model.id === "mock-error") return buildAssistantErrorMessage(model);
return buildAssistantMessage(model);
},
completeSimple: async (model: { api: string; provider: string; id: string }) => {
if (model.id === "mock-error") return buildAssistantErrorMessage(model);
return buildAssistantMessage(model);
},
streamSimple: (model: { api: string; provider: string; id: string }) => {
const stream = new actual.AssistantMessageEventStream();
queueMicrotask(() => {
stream.push({
type: "done",
reason: "stop",
message:
model.id === "mock-error"
? buildAssistantErrorMessage(model)
: buildAssistantMessage(model),
});
stream.end();
});
return stream;
},
};
});
};
let runEmbeddedPiAgent: typeof import("./pi-embedded-runner.js").runEmbeddedPiAgent;
beforeEach(async () => {
beforeAll(async () => {
vi.useRealTimers();
vi.resetModules();
mockPiAi();
({ runEmbeddedPiAgent } = await import("./pi-embedded-runner.js"));
});
}, 20_000);
const makeOpenAiConfig = (modelIds: string[]) =>
({

View File

@@ -1,8 +1,17 @@
import { describe, expect, it } from "vitest";
import { beforeEach, describe, expect, it } from "vitest";
import { extractMessagingToolSend } from "./pi-embedded-subscribe.tools.js";
import { setActivePluginRegistry } from "../plugins/runtime.js";
import { createTestRegistry } from "../test-utils/channel-plugins.js";
import { telegramPlugin } from "../../extensions/telegram/src/channel.js";
describe("extractMessagingToolSend", () => {
beforeEach(() => {
setActivePluginRegistry(
createTestRegistry([{ pluginId: "telegram", plugin: telegramPlugin, source: "test" }]),
);
});
it("uses channel as provider for message tool", () => {
const result = extractMessagingToolSend("message", {
action: "send",

View File

@@ -1,18 +1,51 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import { createTestRegistry } from "../../test-utils/channel-plugins.js";
const callGatewayMock = vi.fn();
vi.mock("../../gateway/call.js", () => ({
callGateway: (opts: unknown) => callGatewayMock(opts),
}));
import { resolveAnnounceTarget } from "./sessions-announce-target.js";
const loadResolveAnnounceTarget = async () => await import("./sessions-announce-target.js");
const installRegistry = async () => {
const { setActivePluginRegistry } = await import("../../plugins/runtime.js");
setActivePluginRegistry(
createTestRegistry([
{
pluginId: "whatsapp",
source: "test",
plugin: {
id: "whatsapp",
meta: {
id: "whatsapp",
label: "WhatsApp",
selectionLabel: "WhatsApp",
docsPath: "/channels/whatsapp",
blurb: "WhatsApp test stub.",
preferSessionLookupForAnnounceTarget: true,
},
capabilities: { chatTypes: ["direct", "group"] },
config: {
listAccountIds: () => ["default"],
resolveAccount: () => ({}),
},
},
},
]),
);
};
describe("resolveAnnounceTarget", () => {
beforeEach(() => {
beforeEach(async () => {
callGatewayMock.mockReset();
vi.resetModules();
await installRegistry();
});
it("derives non-WhatsApp announce targets from the session key", async () => {
const { resolveAnnounceTarget } = await loadResolveAnnounceTarget();
const target = await resolveAnnounceTarget({
sessionKey: "agent:main:discord:group:dev",
displayKey: "agent:main:discord:group:dev",
@@ -22,6 +55,7 @@ describe("resolveAnnounceTarget", () => {
});
it("hydrates WhatsApp accountId from sessions.list when available", async () => {
const { resolveAnnounceTarget } = await loadResolveAnnounceTarget();
callGatewayMock.mockResolvedValueOnce({
sessions: [
{