feat(channel): add kitchen sink channel fixture

This commit is contained in:
Vincent Koc 2026-04-28 16:33:52 -07:00
parent 932a938795
commit b659e8c02e
No known key found for this signature in database
5 changed files with 150 additions and 6 deletions

View File

@ -49,6 +49,26 @@ assert.equal(hookResult.route, "hook:before_tool_call");
assert.equal(hookResult.scenarioId, "image.generate");
assert.equal(hookResult.matchedKitchen, true);
const channel = findRegistration("registerChannel", "kitchen-sink-channel");
const channelAccount = channel.config.resolveAccount({}, "local");
assert.equal(channelAccount.configured, true);
assert.equal(channelAccount.enabled, true);
const channelDelivery = await channel.outbound.sendText({
cfg: {},
to: "kitchen demo",
text: "kitchen generate an image",
});
assert.equal(channelDelivery.channel, "kitchen-sink-channel");
assert.equal(channelDelivery.conversationId, "kitchen-demo");
assert.equal(channelDelivery.meta.scenarioId, "image.generate");
const channelRoute = await channel.messaging.resolveOutboundSessionRoute({
cfg: {},
agentId: "fixture-agent",
target: "kitchen demo",
});
assert.equal(channelRoute.sessionKey, "kitchen:fixture-agent:kitchen-demo");
assert.equal(channelRoute.peer.kind, "direct");
const imageProvider = findRegistration("registerImageGenerationProvider", "kitchen-sink-image");
assert.equal(imageProvider.defaultModel, "kitchen-sink-image-v1");

View File

@ -67,11 +67,12 @@ export const apiSurfaceProbeFailures = [];
function payloadFor(name) {
const id = name.replace(/^register/, "").replace(/[A-Z]/g, (letter, index) => (index === 0 ? "" : "-") + letter.toLowerCase()) || "probe";
const probeId = name === "registerChannel" ? "kitchen-sink-channel-probe" : "kitchen-sink-" + id;
return {
id: "kitchen-sink-" + id,
name: "kitchen-sink-" + id,
id: probeId,
name: probeId,
description: "Kitchen-sink no-op probe for " + name + ".",
command: "kitchen-sink-" + id,
command: probeId,
path: "/kitchen-sink/" + id,
method: "POST",
inputSchema: objectSchema(),

View File

@ -56,11 +56,12 @@ export const apiSurfaceProbeFailures = [];
function payloadFor(name) {
const id = name.replace(/^register/, "").replace(/[A-Z]/g, (letter, index) => (index === 0 ? "" : "-") + letter.toLowerCase()) || "probe";
const probeId = name === "registerChannel" ? "kitchen-sink-channel-probe" : "kitchen-sink-" + id;
return {
id: "kitchen-sink-" + id,
name: "kitchen-sink-" + id,
id: probeId,
name: probeId,
description: "Kitchen-sink no-op probe for " + name + ".",
command: "kitchen-sink-" + id,
command: probeId,
path: "/kitchen-sink/" + id,
method: "POST",
inputSchema: objectSchema(),

View File

@ -2,22 +2,27 @@ import {
DEFAULT_IMAGE_MODEL,
DEFAULT_MEDIA_MODEL,
DEFAULT_TEXT_MODEL,
CHANNEL_ACCOUNT_ID,
CHANNEL_ID,
IMAGE_PROVIDER_ID,
MEDIA_PROVIDER_ID,
PLUGIN_ID,
TEXT_PROVIDER_ID,
WEB_FETCH_PROVIDER_ID,
WEB_SEARCH_PROVIDER_ID,
createKitchenChannelDelivery,
createKitchenScenarioRuntime,
createKitchenSinkImageAsset,
createKitchenTextStream,
extractInteractiveText,
kitchenChannelAccount,
kitchenImageDescription,
kitchenPromptGuidance,
kitchenSearchSchema,
kitchenTextModelDefinition,
kitchenTextProviderConfig,
kitchenToolSchema,
normalizeKitchenTarget,
readPrompt,
readQuery,
readUrl,
@ -39,6 +44,7 @@ export function registerKitchenSinkRuntime(api, options = {}) {
optionalRegister(api, "registerInteractiveHandler", () =>
api.registerInteractiveHandler(buildKitchenInteractiveHandler(runtime)),
);
optionalRegister(api, "registerChannel", () => api.registerChannel(buildKitchenChannel()));
optionalRegister(api, "registerTool", () => api.registerTool(buildKitchenImageTool(runtime)));
optionalRegister(api, "registerTool", () => api.registerTool(buildKitchenTextTool(runtime)));
optionalRegister(api, "registerTool", () => api.registerTool(buildKitchenSearchTool()));
@ -147,6 +153,84 @@ function buildKitchenSearchTool() {
};
}
function buildKitchenChannel() {
return {
id: CHANNEL_ID,
meta: {
id: CHANNEL_ID,
label: "Kitchen Sink",
selectionLabel: "Kitchen Sink",
docsPath: "/plugins/kitchen-sink",
docsLabel: "Kitchen Sink",
blurb: "Credential-free channel fixture for deterministic Kitchen Sink conversations.",
aliases: ["kitchen", "kitchen-sink"],
exposure: { configured: true, setup: true, docs: true },
showConfigured: true,
showInSetup: true,
},
capabilities: {
chatTypes: ["direct", "group", "channel"],
media: true,
nativeCommands: true,
reply: true,
threads: true,
},
config: {
listAccountIds: () => [CHANNEL_ACCOUNT_ID],
defaultAccountId: () => CHANNEL_ACCOUNT_ID,
resolveAccount: (_cfg, accountId) => kitchenChannelAccount(accountId || CHANNEL_ACCOUNT_ID),
isEnabled: () => true,
isConfigured: () => true,
describeAccount: (account) => kitchenChannelAccount(account.accountId),
resolveDefaultTo: () => "kitchen",
},
status: {
defaultRuntime: kitchenChannelAccount(),
probeAccount: async ({ account }) => ({
ok: true,
accountId: account.accountId,
scenarioId: "channel.probe",
}),
buildAccountSnapshot: ({ account }) => kitchenChannelAccount(account.accountId),
},
outbound: {
deliveryMode: "direct",
textChunkLimit: 2000,
sendText: async (ctx) =>
createKitchenChannelDelivery({ kind: "text", text: ctx?.text, to: ctx?.to }),
sendMedia: async (ctx) =>
createKitchenChannelDelivery({ kind: "media", text: ctx?.mediaUrl || ctx?.text, to: ctx?.to }),
},
messaging: {
normalizeTarget: (raw) => normalizeKitchenTarget(raw),
parseExplicitTarget: ({ raw }) => ({
to: normalizeKitchenTarget(raw),
chatType: "direct",
}),
inferTargetChatType: () => "direct",
resolveOutboundSessionRoute: ({ agentId, target, threadId }) => {
const to = normalizeKitchenTarget(target);
return {
sessionKey: `kitchen:${agentId || "agent"}:${to}`,
baseSessionKey: `kitchen:${agentId || "agent"}:${to}`,
peer: { kind: "direct", id: to },
chatType: "direct",
from: CHANNEL_ACCOUNT_ID,
to,
threadId: threadId || undefined,
};
},
},
agentPrompt: {
messageToolHints: () => kitchenPromptGuidance(),
messageToolCapabilities: () => [
"Kitchen Sink channel accepts deterministic dry messages prefixed with kitchen.",
"Kitchen Sink channel can deliver text and media without external credentials.",
],
},
};
}
function buildKitchenImageProvider(runtime) {
return {
id: IMAGE_PROVIDER_ID,

View File

@ -4,6 +4,8 @@ export const MEDIA_PROVIDER_ID = "kitchen-sink-media";
export const TEXT_PROVIDER_ID = "kitchen-sink-llm";
export const WEB_SEARCH_PROVIDER_ID = "kitchen-sink-search";
export const WEB_FETCH_PROVIDER_ID = "kitchen-sink-fetch";
export const CHANNEL_ID = "kitchen-sink-channel";
export const CHANNEL_ACCOUNT_ID = "local";
export const DEFAULT_IMAGE_MODEL = "kitchen-sink-image-v1";
export const DEFAULT_MEDIA_MODEL = "kitchen-sink-vision-v1";
export const DEFAULT_TEXT_MODEL = "kitchen-sink-text-v1";
@ -121,6 +123,42 @@ export function kitchenPromptGuidance() {
];
}
export function createKitchenChannelDelivery({ kind = "text", text = "", to = "kitchen" }) {
const normalizedTo = normalizeKitchenTarget(to);
const id = `ks_channel_${stableHash(`${kind}:${normalizedTo}:${text}`).slice(0, 10)}`;
return {
channel: CHANNEL_ID,
messageId: id,
conversationId: normalizedTo,
channelId: normalizedTo,
timestamp: Date.now(),
meta: {
kitchenSink: true,
pluginId: PLUGIN_ID,
scenarioId: inferKitchenScenario({ text }),
kind,
},
};
}
export function kitchenChannelAccount(accountId = CHANNEL_ACCOUNT_ID) {
return {
accountId: accountId || CHANNEL_ACCOUNT_ID,
name: "Kitchen Sink Local",
enabled: true,
configured: true,
statusState: "fixture",
linked: true,
running: true,
connected: true,
mode: "local",
};
}
export function normalizeKitchenTarget(raw) {
return String(raw ?? "").replace(/^kitchen:/i, "").replace(/\s+/g, "-").trim() || "kitchen";
}
export async function runKitchenCommand(runtime, args) {
const phrase = String(args ?? "").trim();
if (/\b(image|picture|draw|generate)\b/i.test(phrase)) {