feat(opencode): support custom agents and slash commands
Remove the hardcoded mode whitelist so user-defined agents (from opencode.json or global config) appear in the mode picker. Add a listProviderModes API (client → session → provider) so the draft agent form fetches dynamic modes instead of relying on the static manifest. Fix slash command dispatch to accept optional arguments. Closes #185
This commit is contained in:
@@ -444,6 +444,31 @@ export function useAgentFormState(options: UseAgentFormStateOptions = {}): UseAg
|
||||
|
||||
const availableModels = providerModelsQuery.data ?? null;
|
||||
|
||||
const providerModesQuery = useQuery({
|
||||
queryKey: ["providerModes", formState.serverId, formState.provider, debouncedCwd],
|
||||
enabled: Boolean(
|
||||
isVisible &&
|
||||
isTargetDaemonReady &&
|
||||
formState.serverId &&
|
||||
client &&
|
||||
isConnected &&
|
||||
providerDefinitionMap.has(formState.provider),
|
||||
),
|
||||
staleTime: 5 * 60 * 1000,
|
||||
queryFn: async () => {
|
||||
if (!client) {
|
||||
throw new Error("Host is not connected");
|
||||
}
|
||||
const payload = await client.listProviderModes(formState.provider, {
|
||||
cwd: debouncedCwd,
|
||||
});
|
||||
if (payload.error) {
|
||||
throw new Error(payload.error);
|
||||
}
|
||||
return payload.modes ?? [];
|
||||
},
|
||||
});
|
||||
|
||||
const allProviderModelQueries = useQueries({
|
||||
queries: providerDefinitions.map((def) => ({
|
||||
queryKey: ["providerModels", formState.serverId, def.id],
|
||||
@@ -722,7 +747,7 @@ export function useAgentFormState(options: UseAgentFormStateOptions = {}): UseAg
|
||||
]);
|
||||
|
||||
const agentDefinition = providerDefinitionMap.get(formState.provider);
|
||||
const modeOptions = agentDefinition?.modes ?? [];
|
||||
const modeOptions = providerModesQuery.data ?? agentDefinition?.modes ?? [];
|
||||
const effectiveModel = resolveEffectiveModel(availableModels, formState.model);
|
||||
const resolvedModelId = effectiveModel?.id ?? formState.model;
|
||||
const availableThinkingOptions = effectiveModel?.thinkingOptions ?? [];
|
||||
|
||||
@@ -38,6 +38,7 @@ import type {
|
||||
ArchiveWorkspaceResponseMessage,
|
||||
ListCommandsResponse,
|
||||
ListProviderModelsResponseMessage,
|
||||
ListProviderModesResponseMessage,
|
||||
ListAvailableProvidersResponse,
|
||||
ListTerminalsResponse,
|
||||
CreateTerminalResponse,
|
||||
@@ -216,6 +217,7 @@ type CreatePaseoWorktreePayload = Extract<
|
||||
type FileExplorerPayload = FileExplorerResponse["payload"];
|
||||
type FileDownloadTokenPayload = FileDownloadTokenResponse["payload"];
|
||||
type ListProviderModelsPayload = ListProviderModelsResponseMessage["payload"];
|
||||
type ListProviderModesPayload = ListProviderModesResponseMessage["payload"];
|
||||
type ListAvailableProvidersPayload = ListAvailableProvidersResponse["payload"];
|
||||
type ListCommandsPayload = ListCommandsResponse["payload"];
|
||||
type ListCommandsDraftConfig = Pick<
|
||||
@@ -2474,6 +2476,22 @@ export class DaemonClient {
|
||||
});
|
||||
}
|
||||
|
||||
async listProviderModes(
|
||||
provider: AgentProvider,
|
||||
options?: { cwd?: string; requestId?: string },
|
||||
): Promise<ListProviderModesPayload> {
|
||||
return this.sendCorrelatedSessionRequest({
|
||||
requestId: options?.requestId,
|
||||
message: {
|
||||
type: "list_provider_modes_request",
|
||||
provider,
|
||||
cwd: options?.cwd,
|
||||
},
|
||||
responseType: "list_provider_modes_response",
|
||||
timeout: 45000,
|
||||
});
|
||||
}
|
||||
|
||||
async listAvailableProviders(options?: {
|
||||
requestId?: string;
|
||||
}): Promise<ListAvailableProvidersPayload> {
|
||||
|
||||
@@ -414,6 +414,10 @@ export interface ListModelsOptions {
|
||||
cwd?: string;
|
||||
}
|
||||
|
||||
export interface ListModesOptions {
|
||||
cwd?: string;
|
||||
}
|
||||
|
||||
export interface AgentClient {
|
||||
readonly provider: AgentProvider;
|
||||
readonly capabilities: AgentCapabilityFlags;
|
||||
@@ -427,6 +431,7 @@ export interface AgentClient {
|
||||
launchContext?: AgentLaunchContext,
|
||||
): Promise<AgentSession>;
|
||||
listModels(options?: ListModelsOptions): Promise<AgentModelDefinition[]>;
|
||||
listModes?(options?: ListModesOptions): Promise<AgentMode[]>;
|
||||
listPersistedAgents?(options?: ListPersistedAgentsOptions): Promise<PersistedAgentDescriptor[]>;
|
||||
/**
|
||||
* Check if this provider is available (CLI binary is installed).
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import type {
|
||||
AgentClient,
|
||||
AgentMode,
|
||||
AgentModelDefinition,
|
||||
AgentProvider,
|
||||
ListModelsOptions,
|
||||
ListModesOptions,
|
||||
} from "./agent-sdk-types.js";
|
||||
import type { AgentProviderRuntimeSettingsMap } from "./provider-launch-config.js";
|
||||
import type { Logger } from "pino";
|
||||
@@ -24,6 +26,7 @@ export { AGENT_PROVIDER_DEFINITIONS, getAgentProviderDefinition };
|
||||
export interface ProviderDefinition extends AgentProviderDefinition {
|
||||
createClient: (logger: Logger) => AgentClient;
|
||||
fetchModels: (options?: ListModelsOptions) => Promise<AgentModelDefinition[]>;
|
||||
fetchModes: (options?: ListModesOptions) => Promise<AgentMode[]>;
|
||||
}
|
||||
|
||||
type BuildProviderRegistryOptions = {
|
||||
@@ -69,6 +72,10 @@ export function buildProviderRegistry(
|
||||
...definition,
|
||||
createClient: (providerLogger: Logger) => createClient(providerLogger, runtimeSettings),
|
||||
fetchModels: (listOptions?: ListModelsOptions) => modelClient.listModels(listOptions),
|
||||
fetchModes: (listOptions?: ListModesOptions) =>
|
||||
modelClient.listModes
|
||||
? modelClient.listModes(listOptions)
|
||||
: Promise.resolve(definition.modes),
|
||||
} satisfies ProviderDefinition,
|
||||
];
|
||||
}),
|
||||
|
||||
@@ -37,6 +37,58 @@ describe("opencode agent commands E2E", () => {
|
||||
}
|
||||
}, 60_000);
|
||||
|
||||
test("sendMessage executes a slash command without arguments", async () => {
|
||||
const agent = await ctx.client.createAgent({
|
||||
...getFullAccessConfig("opencode"),
|
||||
cwd: "/tmp",
|
||||
title: "OpenCode Slash Command No Args",
|
||||
});
|
||||
|
||||
expect(agent.id).toBeTruthy();
|
||||
expect(agent.provider).toBe("opencode");
|
||||
|
||||
await ctx.client.sendMessage(agent.id, "/help");
|
||||
const state = await ctx.client.waitForFinish(agent.id, 30_000);
|
||||
|
||||
expect(state.status).toBe("idle");
|
||||
expect(state.lastMessage).toContain("PASEO_SKILL_OK");
|
||||
}, 30_000);
|
||||
|
||||
test("sendMessage executes a slash command with arguments", async () => {
|
||||
const agent = await ctx.client.createAgent({
|
||||
...getFullAccessConfig("opencode"),
|
||||
cwd: "/tmp",
|
||||
title: "OpenCode Slash Command With Args",
|
||||
});
|
||||
|
||||
expect(agent.id).toBeTruthy();
|
||||
expect(agent.provider).toBe("opencode");
|
||||
|
||||
await ctx.client.sendMessage(agent.id, "/help some-topic");
|
||||
const state = await ctx.client.waitForFinish(agent.id, 30_000);
|
||||
|
||||
expect(state.status).toBe("idle");
|
||||
expect(state.lastMessage).toContain("PASEO_SKILL_OK");
|
||||
}, 30_000);
|
||||
|
||||
test("sendMessage keeps unknown slash input as plain prompt text", async () => {
|
||||
const agent = await ctx.client.createAgent({
|
||||
...getFullAccessConfig("opencode"),
|
||||
cwd: "/tmp",
|
||||
title: "OpenCode Slash Fallback",
|
||||
});
|
||||
|
||||
const token = `RAW_PROMPT_TOKEN_${Date.now()}`;
|
||||
await ctx.client.sendMessage(
|
||||
agent.id,
|
||||
`/not-a-real-command respond with exactly: ${token}`,
|
||||
);
|
||||
const state = await ctx.client.waitForFinish(agent.id, 30_000);
|
||||
|
||||
expect(state.status).toBe("idle");
|
||||
expect(state.lastMessage).toContain(token);
|
||||
}, 30_000);
|
||||
|
||||
test("returns error for non-existent agent", async () => {
|
||||
const result = await ctx.client.listCommands("non-existent-agent-id");
|
||||
|
||||
|
||||
@@ -35,4 +35,51 @@ describe("opencode agent commands contract (real)", () => {
|
||||
await session.close();
|
||||
}
|
||||
}, 60_000);
|
||||
|
||||
test("executes a slash command without arguments", async () => {
|
||||
expect(isCommandAvailable("opencode")).toBe(true);
|
||||
|
||||
const client = new OpenCodeAgentClient(pino({ level: "silent" }));
|
||||
const session = await client.createSession({
|
||||
provider: "opencode",
|
||||
cwd: process.cwd(),
|
||||
modeId: "default",
|
||||
});
|
||||
|
||||
try {
|
||||
const commands = await session.listCommands!();
|
||||
expect(commands.length).toBeGreaterThan(0);
|
||||
|
||||
// Pick the first available command and send it without arguments.
|
||||
const command = commands[0]!;
|
||||
const events: Array<{ type: string }> = [];
|
||||
session.subscribe((event) => events.push(event));
|
||||
|
||||
const { turnId } = await session.startTurn(`/${command.name}`);
|
||||
expect(turnId).toBeTruthy();
|
||||
|
||||
// Wait for the turn to complete or fail.
|
||||
await new Promise<void>((resolve) => {
|
||||
const unsub = session.subscribe((event) => {
|
||||
if (
|
||||
event.type === "turn_completed" ||
|
||||
event.type === "turn_failed" ||
|
||||
event.type === "turn_canceled"
|
||||
) {
|
||||
unsub();
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// The turn should not have failed with an "invalid_type" error on arguments.
|
||||
const failEvent = events.find((e) => e.type === "turn_failed");
|
||||
if (failEvent && "error" in failEvent) {
|
||||
expect(String(failEvent.error)).not.toContain("invalid_type");
|
||||
expect(String(failEvent.error)).not.toContain("expected string, received undefined");
|
||||
}
|
||||
} finally {
|
||||
await session.close();
|
||||
}
|
||||
}, 60_000);
|
||||
});
|
||||
|
||||
@@ -219,6 +219,42 @@ const hasOpenCode = isBinaryInstalled("opencode");
|
||||
rmSync(cwd, { recursive: true, force: true });
|
||||
}, 60_000);
|
||||
|
||||
test("custom agents defined in opencode.json appear in available modes", async () => {
|
||||
const cwd = tmpCwd();
|
||||
writeFileSync(
|
||||
path.join(cwd, "opencode.json"),
|
||||
JSON.stringify({
|
||||
agent: {
|
||||
"paseo-test-custom": {
|
||||
description: "Custom agent defined for Paseo integration test",
|
||||
mode: "primary",
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const client = new OpenCodeAgentClient(logger);
|
||||
const session = await client.createSession(buildConfig(cwd));
|
||||
|
||||
const modes = await session.getAvailableModes();
|
||||
|
||||
expect(modes.some((mode) => mode.id === "build")).toBe(true);
|
||||
expect(modes.some((mode) => mode.id === "plan")).toBe(true);
|
||||
|
||||
const custom = modes.find((mode) => mode.id === "paseo-test-custom");
|
||||
expect(custom).toBeDefined();
|
||||
expect(custom!.label).toBe("Paseo-test-custom");
|
||||
expect(custom!.description).toBe("Custom agent defined for Paseo integration test");
|
||||
|
||||
// System agents should not appear as selectable modes
|
||||
expect(modes.some((mode) => mode.id === "compaction")).toBe(false);
|
||||
expect(modes.some((mode) => mode.id === "summary")).toBe(false);
|
||||
expect(modes.some((mode) => mode.id === "title")).toBe(false);
|
||||
|
||||
await session.close();
|
||||
rmSync(cwd, { recursive: true, force: true });
|
||||
}, 60_000);
|
||||
|
||||
test("plan mode blocks edits while build mode can write files", async () => {
|
||||
const cwd = tmpCwd();
|
||||
const planFile = path.join(cwd, "plan-mode-output.txt");
|
||||
|
||||
@@ -26,6 +26,7 @@ import type {
|
||||
AgentTimelineItem,
|
||||
AgentUsage,
|
||||
ListModelsOptions,
|
||||
ListModesOptions,
|
||||
ListPersistedAgentsOptions,
|
||||
McpServerConfig,
|
||||
PersistedAgentDescriptor,
|
||||
@@ -63,8 +64,6 @@ const DEFAULT_MODES: AgentMode[] = [
|
||||
},
|
||||
];
|
||||
|
||||
const OPENCODE_MODE_IDS = new Set(DEFAULT_MODES.map((mode) => mode.id));
|
||||
|
||||
type OpenCodeAgentConfig = AgentSessionConfig & { provider: "opencode" };
|
||||
type OpenCodeMessageRole = "user" | "assistant";
|
||||
|
||||
@@ -563,6 +562,41 @@ export class OpenCodeAgentClient implements AgentClient {
|
||||
return models;
|
||||
}
|
||||
|
||||
async listModes(options?: ListModesOptions): Promise<AgentMode[]> {
|
||||
const { url } = await this.serverManager.ensureRunning();
|
||||
const directory = options?.cwd ?? process.cwd();
|
||||
const client = createOpencodeClient({ baseUrl: url, directory });
|
||||
|
||||
const timeoutPromise = new Promise<never>((_, reject) => {
|
||||
setTimeout(
|
||||
() => reject(new Error("OpenCode app.agents timed out after 10s")),
|
||||
10_000,
|
||||
);
|
||||
});
|
||||
|
||||
const response = await Promise.race([
|
||||
client.app.agents({ directory }),
|
||||
timeoutPromise,
|
||||
]);
|
||||
|
||||
if (response.error || !response.data) {
|
||||
return DEFAULT_MODES;
|
||||
}
|
||||
|
||||
const discovered = response.data
|
||||
.filter((agent) => agent.mode === "primary" && agent.hidden !== true)
|
||||
.map((agent) => ({
|
||||
id: agent.name,
|
||||
label: agent.name.charAt(0).toUpperCase() + agent.name.slice(1),
|
||||
description:
|
||||
typeof agent.description === "string" && agent.description.trim().length > 0
|
||||
? agent.description.trim()
|
||||
: DEFAULT_MODES.find((mode) => mode.id === agent.name)?.description,
|
||||
}));
|
||||
|
||||
return discovered.length > 0 ? sortOpenCodeModes(discovered) : DEFAULT_MODES;
|
||||
}
|
||||
|
||||
async listPersistedAgents(
|
||||
_options?: ListPersistedAgentsOptions,
|
||||
): Promise<PersistedAgentDescriptor[]> {
|
||||
@@ -1160,7 +1194,7 @@ class OpenCodeAgentSession implements AgentSession {
|
||||
sessionID: this.sessionId,
|
||||
directory: this.config.cwd,
|
||||
command: slashCommand.commandName,
|
||||
arguments: slashCommand.args,
|
||||
arguments: slashCommand.args ?? "",
|
||||
...(this.config.model ? { model: this.config.model } : {}),
|
||||
...(effectiveMode ? { agent: effectiveMode } : {}),
|
||||
...(effectiveVariant ? { variant: effectiveVariant } : {}),
|
||||
@@ -1432,7 +1466,6 @@ class OpenCodeAgentSession implements AgentSession {
|
||||
? []
|
||||
: response.data
|
||||
.filter((agent) => agent.mode === "primary" && agent.hidden !== true)
|
||||
.filter((agent) => OPENCODE_MODE_IDS.has(agent.name))
|
||||
.map((agent) => ({
|
||||
id: agent.name,
|
||||
label: agent.name.charAt(0).toUpperCase() + agent.name.slice(1),
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
import { describe, test, expect } from "vitest";
|
||||
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import path from "node:path";
|
||||
import pino from "pino";
|
||||
|
||||
import { OpenCodeAgentClient } from "../agent/providers/opencode-agent.js";
|
||||
import { createTestPaseoDaemon } from "../test-utils/paseo-daemon.js";
|
||||
import { DaemonClient } from "../test-utils/daemon-client.js";
|
||||
import { isProviderAvailable } from "./agent-configs.js";
|
||||
|
||||
function tmpCwd(): string {
|
||||
return mkdtempSync(path.join(tmpdir(), "daemon-real-opencode-custom-"));
|
||||
}
|
||||
|
||||
async function createHarness(): Promise<{
|
||||
client: DaemonClient;
|
||||
daemon: Awaited<ReturnType<typeof createTestPaseoDaemon>>;
|
||||
}> {
|
||||
const logger = pino({ level: "silent" });
|
||||
const daemon = await createTestPaseoDaemon({
|
||||
agentClients: { opencode: new OpenCodeAgentClient(logger) },
|
||||
logger,
|
||||
});
|
||||
const client = new DaemonClient({ url: `ws://127.0.0.1:${daemon.port}/ws` });
|
||||
await client.connect();
|
||||
await client.fetchAgents({ subscribe: { subscriptionId: "opencode-custom-agents" } });
|
||||
return { client, daemon };
|
||||
}
|
||||
|
||||
describe("daemon E2E (real opencode) - custom agent discovery", () => {
|
||||
test.runIf(isProviderAvailable("opencode"))(
|
||||
"custom agents defined in opencode.json appear in availableModes",
|
||||
async () => {
|
||||
const cwd = tmpCwd();
|
||||
writeFileSync(
|
||||
path.join(cwd, "opencode.json"),
|
||||
JSON.stringify({
|
||||
agent: {
|
||||
"paseo-e2e-custom": {
|
||||
description: "Custom agent for Paseo daemon E2E test",
|
||||
mode: "primary",
|
||||
},
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const { client, daemon } = await createHarness();
|
||||
|
||||
try {
|
||||
const agent = await client.createAgent({
|
||||
provider: "opencode",
|
||||
cwd,
|
||||
title: "OpenCode custom agent discovery",
|
||||
});
|
||||
|
||||
// Wait for modes to be populated (they arrive after session init)
|
||||
const snapshot = await client.waitForAgentUpsert(
|
||||
agent.id,
|
||||
(s) => s.availableModes.length > 0,
|
||||
30_000,
|
||||
);
|
||||
|
||||
expect(snapshot.availableModes.some((m) => m.id === "build")).toBe(true);
|
||||
expect(snapshot.availableModes.some((m) => m.id === "plan")).toBe(true);
|
||||
|
||||
const custom = snapshot.availableModes.find((m) => m.id === "paseo-e2e-custom");
|
||||
expect(custom).toBeDefined();
|
||||
expect(custom!.description).toBe("Custom agent for Paseo daemon E2E test");
|
||||
|
||||
// System agents should not leak through
|
||||
expect(snapshot.availableModes.some((m) => m.id === "compaction")).toBe(false);
|
||||
expect(snapshot.availableModes.some((m) => m.id === "summary")).toBe(false);
|
||||
expect(snapshot.availableModes.some((m) => m.id === "title")).toBe(false);
|
||||
} finally {
|
||||
await client.close().catch(() => undefined);
|
||||
await daemon.close();
|
||||
rmSync(cwd, { recursive: true, force: true });
|
||||
}
|
||||
},
|
||||
60_000,
|
||||
);
|
||||
});
|
||||
@@ -1661,6 +1661,10 @@ export class Session {
|
||||
await this.handleListProviderModelsRequest(msg);
|
||||
break;
|
||||
|
||||
case "list_provider_modes_request":
|
||||
await this.handleListProviderModesRequest(msg);
|
||||
break;
|
||||
|
||||
case "list_available_providers_request":
|
||||
await this.handleListAvailableProvidersRequest(msg);
|
||||
break;
|
||||
@@ -3042,6 +3046,41 @@ export class Session {
|
||||
}
|
||||
}
|
||||
|
||||
private async handleListProviderModesRequest(
|
||||
msg: Extract<SessionInboundMessage, { type: "list_provider_modes_request" }>,
|
||||
): Promise<void> {
|
||||
const fetchedAt = new Date().toISOString();
|
||||
try {
|
||||
const modes = await this.providerRegistry[msg.provider].fetchModes({
|
||||
cwd: msg.cwd ? expandTilde(msg.cwd) : undefined,
|
||||
});
|
||||
this.emit({
|
||||
type: "list_provider_modes_response",
|
||||
payload: {
|
||||
provider: msg.provider,
|
||||
modes,
|
||||
error: null,
|
||||
fetchedAt,
|
||||
requestId: msg.requestId,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
this.sessionLogger.error(
|
||||
{ err: error, provider: msg.provider },
|
||||
`Failed to list modes for ${msg.provider}`,
|
||||
);
|
||||
this.emit({
|
||||
type: "list_provider_modes_response",
|
||||
payload: {
|
||||
provider: msg.provider,
|
||||
error: (error as Error)?.message ?? String(error),
|
||||
fetchedAt,
|
||||
requestId: msg.requestId,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async handleListAvailableProvidersRequest(
|
||||
msg: Extract<SessionInboundMessage, { type: "list_available_providers_request" }>,
|
||||
): Promise<void> {
|
||||
|
||||
@@ -243,7 +243,7 @@ class FakeAgentSession implements AgentSession {
|
||||
private async resolveSlashCommandInput(
|
||||
prompt: AgentPromptInput,
|
||||
): Promise<{ commandName: string; args?: string } | null> {
|
||||
if (this.providerName !== "codex" || typeof prompt !== "string") {
|
||||
if ((this.providerName !== "codex" && this.providerName !== "opencode") || typeof prompt !== "string") {
|
||||
return null;
|
||||
}
|
||||
const parsed = this.parseSlashCommandInput(prompt);
|
||||
|
||||
@@ -728,6 +728,13 @@ export const ListProviderModelsRequestMessageSchema = z.object({
|
||||
requestId: z.string(),
|
||||
});
|
||||
|
||||
export const ListProviderModesRequestMessageSchema = z.object({
|
||||
type: z.literal("list_provider_modes_request"),
|
||||
provider: AgentProviderSchema,
|
||||
cwd: z.string().optional(),
|
||||
requestId: z.string(),
|
||||
});
|
||||
|
||||
export const ListAvailableProvidersRequestMessageSchema = z.object({
|
||||
type: z.literal("list_available_providers_request"),
|
||||
requestId: z.string(),
|
||||
@@ -1208,6 +1215,7 @@ export const SessionInboundMessageSchema = z.discriminatedUnion("type", [
|
||||
DictationStreamCancelMessageSchema,
|
||||
CreateAgentRequestMessageSchema,
|
||||
ListProviderModelsRequestMessageSchema,
|
||||
ListProviderModesRequestMessageSchema,
|
||||
ListAvailableProvidersRequestMessageSchema,
|
||||
ResumeAgentRequestMessageSchema,
|
||||
RefreshAgentRequestMessageSchema,
|
||||
@@ -2112,6 +2120,17 @@ export const ListProviderModelsResponseMessageSchema = z.object({
|
||||
}),
|
||||
});
|
||||
|
||||
export const ListProviderModesResponseMessageSchema = z.object({
|
||||
type: z.literal("list_provider_modes_response"),
|
||||
payload: z.object({
|
||||
provider: AgentProviderSchema,
|
||||
modes: z.array(AgentModeSchema).optional(),
|
||||
error: z.string().nullable().optional(),
|
||||
fetchedAt: z.string(),
|
||||
requestId: z.string(),
|
||||
}),
|
||||
});
|
||||
|
||||
const ProviderAvailabilitySchema = z.object({
|
||||
provider: AgentProviderSchema,
|
||||
available: z.boolean(),
|
||||
@@ -2317,6 +2336,7 @@ export const SessionOutboundMessageSchema = z.discriminatedUnion("type", [
|
||||
ProjectIconResponseSchema,
|
||||
FileDownloadTokenResponseSchema,
|
||||
ListProviderModelsResponseMessageSchema,
|
||||
ListProviderModesResponseMessageSchema,
|
||||
ListAvailableProvidersResponseSchema,
|
||||
ListCommandsResponseSchema,
|
||||
ListTerminalsResponseSchema,
|
||||
@@ -2386,6 +2406,9 @@ export type AgentDeletedMessage = z.infer<typeof AgentDeletedMessageSchema>;
|
||||
export type ListProviderModelsResponseMessage = z.infer<
|
||||
typeof ListProviderModelsResponseMessageSchema
|
||||
>;
|
||||
export type ListProviderModesResponseMessage = z.infer<
|
||||
typeof ListProviderModesResponseMessageSchema
|
||||
>;
|
||||
export type ListAvailableProvidersResponse = z.infer<typeof ListAvailableProvidersResponseSchema>;
|
||||
export type ChatCreateResponse = z.infer<typeof ChatCreateResponseSchema>;
|
||||
export type ChatListResponse = z.infer<typeof ChatListResponseSchema>;
|
||||
@@ -2425,6 +2448,9 @@ export type CreateAgentRequestMessage = z.infer<typeof CreateAgentRequestMessage
|
||||
export type ListProviderModelsRequestMessage = z.infer<
|
||||
typeof ListProviderModelsRequestMessageSchema
|
||||
>;
|
||||
export type ListProviderModesRequestMessage = z.infer<
|
||||
typeof ListProviderModesRequestMessageSchema
|
||||
>;
|
||||
export type ListAvailableProvidersRequestMessage = z.infer<
|
||||
typeof ListAvailableProvidersRequestMessageSchema
|
||||
>;
|
||||
|
||||
Reference in New Issue
Block a user