feat(cli): add --output-schema flag for structured agent output

This commit is contained in:
Mohamed Boudra
2026-02-12 18:04:39 +07:00
parent 018edef3bb
commit cb2e4aacfe
10 changed files with 344 additions and 21 deletions
@@ -169,6 +169,7 @@ export type CreateAgentRequestOptions = {
provider?: AgentProvider;
cwd?: string;
initialPrompt?: string;
outputSchema?: Record<string, unknown>;
images?: CreateAgentRequestMessage["images"];
git?: GitSetupOptions;
worktreeName?: string;
@@ -224,6 +225,7 @@ export type WaitForFinishResult = {
status: "idle" | "error" | "permission" | "timeout";
final: AgentSnapshotPayload | null;
error: string | null;
lastMessage: string | null;
};
type Waiter<T> = {
@@ -984,6 +986,7 @@ export class DaemonClient {
requestId,
config,
...(options.initialPrompt ? { initialPrompt: options.initialPrompt } : {}),
...(options.outputSchema ? { outputSchema: options.outputSchema } : {}),
...(options.images && options.images.length > 0
? { images: options.images }
: {}),
@@ -2260,6 +2263,7 @@ export class DaemonClient {
status: payload.status,
final: payload.final,
error: payload.error,
lastMessage: payload.lastMessage,
};
}
+16 -10
View File
@@ -63,6 +63,7 @@ import type {
AgentPermissionResponse,
AgentPromptContentBlock,
AgentPromptInput,
AgentRunOptions,
McpServerConfig,
AgentSessionConfig,
AgentStreamEvent,
@@ -683,7 +684,8 @@ export class Session {
*/
private startAgentStream(
agentId: string,
prompt: AgentPromptInput
prompt: AgentPromptInput,
runOptions?: AgentRunOptions
): { ok: true } | { ok: false; error: string } {
this.sessionLogger.info(
{ agentId },
@@ -692,7 +694,7 @@ export class Session {
let iterator: AsyncGenerator<AgentStreamEvent>;
try {
iterator = this.agentManager.streamAgent(agentId, prompt);
iterator = this.agentManager.streamAgent(agentId, prompt, runOptions);
} catch (error) {
this.handleAgentRunError(agentId, error, "Failed to start agent run");
const message =
@@ -2036,7 +2038,8 @@ export class Session {
agentId: string,
text: string,
messageId?: string,
images?: Array<{ data: string; mimeType: string }>
images?: Array<{ data: string; mimeType: string }>,
runOptions?: AgentRunOptions
): Promise<void> {
this.sessionLogger.info(
{ agentId, textPreview: text.substring(0, 50), imageCount: images?.length ?? 0 },
@@ -2076,7 +2079,7 @@ export class Session {
);
}
this.startAgentStream(agentId, prompt);
this.startAgentStream(agentId, prompt, runOptions);
}
/**
@@ -2124,7 +2127,7 @@ export class Session {
private async handleCreateAgentRequest(
msg: Extract<SessionInboundMessage, { type: "create_agent_request" }>
): Promise<void> {
const { config, worktreeName, requestId, initialPrompt, git, images, labels } = msg;
const { config, worktreeName, requestId, initialPrompt, outputSchema, git, images, labels } = msg;
this.sessionLogger.info(
{ cwd: config.cwd, provider: config.provider, worktreeName },
`Creating agent in ${config.cwd} (${config.provider})${
@@ -2163,7 +2166,8 @@ export class Session {
snapshot.id,
trimmedPrompt,
uuidv4(),
images
images,
outputSchema ? { outputSchema } : undefined
);
} catch (promptError) {
this.sessionLogger.error(
@@ -4832,7 +4836,7 @@ export class Session {
if (!resolved.ok) {
this.emit({
type: "wait_for_finish_response",
payload: { requestId, status: "error", final: null, error: resolved.error },
payload: { requestId, status: "error", final: null, error: resolved.error, lastMessage: null },
});
return;
}
@@ -4849,6 +4853,7 @@ export class Session {
status: "error",
final: null,
error: `Agent not found: ${agentId}`,
lastMessage: null,
},
});
return;
@@ -4862,7 +4867,7 @@ export class Session {
: "idle";
this.emit({
type: "wait_for_finish_response",
payload: { requestId, status, final, error: null },
payload: { requestId, status, final, error: null, lastMessage: null },
});
return;
}
@@ -4892,7 +4897,7 @@ export class Session {
this.emit({
type: "wait_for_finish_response",
payload: { requestId, status, final, error: null },
payload: { requestId, status, final, error: null, lastMessage: result.lastMessage },
});
} catch (error) {
const isAbort =
@@ -4910,6 +4915,7 @@ export class Session {
status: "error",
final,
error: message,
lastMessage: null,
},
});
return;
@@ -4921,7 +4927,7 @@ export class Session {
}
this.emit({
type: "wait_for_finish_response",
payload: { requestId, status: "timeout", final, error: null },
payload: { requestId, status: "timeout", final, error: null, lastMessage: null },
});
} finally {
clearTimeout(timeoutHandle);
+2
View File
@@ -548,6 +548,7 @@ export const CreateAgentRequestMessageSchema = z.object({
config: AgentSessionConfigSchema,
worktreeName: z.string().optional(),
initialPrompt: z.string().optional(),
outputSchema: z.record(z.unknown()).optional(),
images: z.array(z.object({
data: z.string(), // base64 encoded image
mimeType: z.string(), // e.g., "image/jpeg", "image/png"
@@ -1360,6 +1361,7 @@ export const WaitForFinishResponseMessageSchema = z.object({
status: z.enum(["idle", "error", "permission", "timeout"]),
final: AgentSnapshotPayloadSchema.nullable(),
error: z.string().nullable(),
lastMessage: z.string().nullable(),
}),
});