code block on chat

This commit is contained in:
Arjun 2026-04-03 15:25:48 +05:30
parent bc929b6c1b
commit 4fb153f5dc
7 changed files with 59 additions and 4 deletions

View file

@ -1972,6 +1972,21 @@ function App() {
break
}
case 'tool-output-stream':
{
if (!isActiveRun) return
setConversation(prev => prev.map(item => {
if (
isToolCall(item)
&& item.id === event.toolCallId
) {
return { ...item, streamingOutput: (item.streamingOutput ?? '') + event.output }
}
return item
}))
break
}
case 'tool-permission-request': {
if (!isActiveRun) return
const key = event.toolCall.toolCallId
@ -3842,7 +3857,16 @@ function App() {
/>
<ToolContent>
<ToolInput input={input} />
{output !== null ? (
{item.streamingOutput && item.status === 'running' ? (
<div className="space-y-2 p-4">
<h4 className="font-medium text-muted-foreground text-xs uppercase tracking-wide">
Live Output
</h4>
<pre className="max-h-80 overflow-auto rounded-md border bg-zinc-950 p-4 font-mono text-xs text-green-400 whitespace-pre-wrap">
{item.streamingOutput}
</pre>
</div>
) : output !== null ? (
<ToolOutput output={output} errorText={errorText} />
) : null}
</ToolContent>

View file

@ -23,6 +23,7 @@ export interface ToolCall {
name: string
input: ToolUIPart['input']
result?: ToolUIPart['output']
streamingOutput?: string
status: 'pending' | 'running' | 'completed' | 'error'
timestamp: number
}

View file

@ -161,6 +161,7 @@ export class AgentRuntime implements IAgentRuntime {
modelConfigRepo: this.modelConfigRepo,
signal,
abortRegistry: this.abortRegistry,
bus: this.bus,
})) {
eventCount++;
if (event.type !== "llm-stream-event") {
@ -822,6 +823,7 @@ export async function* streamAgent({
modelConfigRepo,
signal,
abortRegistry,
bus,
}: {
state: AgentState,
idGenerator: IMonotonicallyIncreasingIdGenerator;
@ -830,6 +832,7 @@ export async function* streamAgent({
modelConfigRepo: IModelConfigRepo;
signal: AbortSignal;
abortRegistry: IAbortRegistry;
bus: IBus;
}): AsyncGenerator<z.infer<typeof RunEvent>, void, unknown> {
const logger = new PrefixLogger(`run-${runId}-${state.agentName}`);
@ -942,6 +945,7 @@ export async function* streamAgent({
modelConfigRepo,
signal,
abortRegistry,
bus,
})) {
yield* processEvent({
...event,
@ -952,7 +956,7 @@ export async function* streamAgent({
result = subflowState.finalResponse();
}
} else {
result = await execTool(agent.tools![toolCall.toolName], toolCall.arguments, { runId, signal, abortRegistry });
result = await execTool(agent.tools![toolCall.toolName], toolCall.arguments, { runId, toolCallId, signal, abortRegistry, publish: (event) => bus.publish(event) });
}
const resultPayload = result === undefined ? null : result;
const resultMsg: z.infer<typeof ToolMessage> = {

View file

@ -847,6 +847,16 @@ export const BuiltinTools: z.infer<typeof BuiltinToolsSchema> = {
const { promise, process: proc } = executeCommandAbortable(command, {
cwd: workingDir,
signal: ctx.signal,
onData: ctx.publish ? (chunk: string) => {
ctx.publish({
runId: ctx.runId,
type: "tool-output-stream",
toolCallId: ctx.toolCallId,
toolName: "executeCommand",
output: chunk,
subflow: [],
});
} : undefined,
});
// Register process with abort registry for force-kill

View file

@ -143,6 +143,7 @@ export function executeCommandAbortable(
timeout?: number;
maxBuffer?: number;
signal?: AbortSignal;
onData?: (chunk: string) => void;
}
): { promise: Promise<AbortableCommandResult>; process: ChildProcess } {
// Check if already aborted before spawning
@ -176,16 +177,20 @@ export function executeCommandAbortable(
// Collect output
proc.stdout?.on('data', (chunk: Buffer) => {
const text = chunk.toString();
const maxBuffer = options?.maxBuffer || 1024 * 1024;
if (stdout.length < maxBuffer) {
stdout += chunk.toString();
stdout += text;
}
options?.onData?.(text);
});
proc.stderr?.on('data', (chunk: Buffer) => {
const text = chunk.toString();
const maxBuffer = options?.maxBuffer || 1024 * 1024;
if (stderr.length < maxBuffer) {
stderr += chunk.toString();
stderr += text;
}
options?.onData?.(text);
});
// Abort handler

View file

@ -1,4 +1,5 @@
import { ToolAttachment } from "@x/shared/dist/agent.js";
import { RunEvent } from "@x/shared/dist/runs.js";
import { z } from "zod";
import { BuiltinTools } from "./builtin-tools.js";
import { executeTool } from "../../mcp/mcp.js";
@ -9,8 +10,10 @@ import { IAbortRegistry } from "../../runs/abort-registry.js";
*/
export interface ToolContext {
runId: string;
toolCallId: string;
signal: AbortSignal;
abortRegistry: IAbortRegistry;
publish: (event: z.infer<typeof RunEvent>) => Promise<void>;
}
async function execMcpTool(agentTool: z.infer<typeof ToolAttachment> & { type: "mcp" }, input: Record<string, unknown>): Promise<unknown> {

View file

@ -81,6 +81,13 @@ export const RunErrorEvent = BaseRunEvent.extend({
error: z.string(),
});
export const ToolOutputStreamEvent = BaseRunEvent.extend({
type: z.literal("tool-output-stream"),
toolCallId: z.string(),
toolName: z.string(),
output: z.string(),
});
export const RunStoppedEvent = BaseRunEvent.extend({
type: z.literal("run-stopped"),
reason: z.enum(["user-requested", "force-stopped"]).optional(),
@ -95,6 +102,7 @@ export const RunEvent = z.union([
MessageEvent,
ToolInvocationEvent,
ToolResultEvent,
ToolOutputStreamEvent,
AskHumanRequestEvent,
AskHumanResponseEvent,
ToolPermissionRequestEvent,