auto-reply: support multi-text RPC outputs
This commit is contained in:
@@ -69,7 +69,7 @@ export const claudeSpec: AgentSpec = {
|
||||
const parsed = parseClaudeJson(rawStdout);
|
||||
const text = parsed?.text ?? rawStdout.trim();
|
||||
return {
|
||||
text: text?.trim(),
|
||||
texts: text ? [text.trim()] : undefined,
|
||||
meta: toMeta(parsed),
|
||||
};
|
||||
},
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { AgentMeta, AgentParseResult, AgentSpec } from "./types.js";
|
||||
|
||||
function parseCodexJson(raw: string): AgentParseResult {
|
||||
const lines = raw.split(/\n+/).filter((l) => l.trim().startsWith("{"));
|
||||
let text: string | undefined;
|
||||
const texts: string[] = [];
|
||||
let meta: AgentMeta | undefined;
|
||||
|
||||
for (const line of lines) {
|
||||
@@ -21,7 +21,7 @@ function parseCodexJson(raw: string): AgentParseResult {
|
||||
ev.item?.type === "agent_message" &&
|
||||
typeof ev.item.text === "string"
|
||||
) {
|
||||
text = ev.item.text;
|
||||
texts.push(ev.item.text);
|
||||
}
|
||||
if (
|
||||
ev.type === "turn.completed" &&
|
||||
@@ -50,7 +50,8 @@ function parseCodexJson(raw: string): AgentParseResult {
|
||||
}
|
||||
}
|
||||
|
||||
return { text: text?.trim(), meta };
|
||||
const finalTexts = texts.length ? texts.map((t) => t.trim()) : undefined;
|
||||
return { texts: finalTexts, meta };
|
||||
}
|
||||
|
||||
export const codexSpec: AgentSpec = {
|
||||
|
||||
@@ -10,7 +10,8 @@ export const GEMINI_IDENTITY_PREFIX =
|
||||
// keep parsing minimal and let MEDIA token stripping happen later in the pipeline.
|
||||
function parseGeminiOutput(raw: string): { text?: string; meta?: AgentMeta } {
|
||||
const trimmed = raw.trim();
|
||||
return { text: trimmed || undefined, meta: undefined };
|
||||
const text = trimmed || undefined;
|
||||
return { texts: text ? [text] : undefined, meta: undefined };
|
||||
}
|
||||
|
||||
export const geminiSpec: AgentSpec = {
|
||||
|
||||
@@ -55,7 +55,7 @@ export const opencodeSpec: AgentSpec = {
|
||||
const parsed = parseOpencodeJson(rawStdout);
|
||||
const text = parsed.text ?? rawStdout.trim();
|
||||
return {
|
||||
text: text?.trim(),
|
||||
texts: text ? [text.trim()] : undefined,
|
||||
meta: toMeta(parsed),
|
||||
};
|
||||
},
|
||||
|
||||
@@ -13,36 +13,50 @@ type PiAssistantMessage = {
|
||||
|
||||
function parsePiJson(raw: string): AgentParseResult {
|
||||
const lines = raw.split(/\n+/).filter((l) => l.trim().startsWith("{"));
|
||||
let lastMessage: PiAssistantMessage | undefined;
|
||||
|
||||
// Collect every assistant message we see; Tau in RPC mode can emit multiple
|
||||
// assistant payloads in one run (e.g., queued turns, heartbeats). We concatenate
|
||||
// all text blocks so users see everything instead of only the last message_end.
|
||||
const texts: string[] = [];
|
||||
let lastAssistant: PiAssistantMessage | undefined;
|
||||
|
||||
for (const line of lines) {
|
||||
try {
|
||||
const ev = JSON.parse(line) as {
|
||||
type?: string;
|
||||
message?: PiAssistantMessage;
|
||||
};
|
||||
// Pi emits a stream; we only care about the terminal assistant message_end.
|
||||
if (ev.type === "message_end" && ev.message?.role === "assistant") {
|
||||
lastMessage = ev.message;
|
||||
const msg = ev.message;
|
||||
if (msg?.role === "assistant" && Array.isArray(msg.content)) {
|
||||
const msgText = msg.content
|
||||
.filter((c) => c?.type === "text" && typeof c.text === "string")
|
||||
.map((c) => c.text)
|
||||
.join("\n")
|
||||
.trim();
|
||||
if (msgText) texts.push(msgText);
|
||||
// keep meta from the most recent assistant message
|
||||
lastAssistant = msg;
|
||||
}
|
||||
} catch {
|
||||
// ignore
|
||||
// ignore malformed lines
|
||||
}
|
||||
}
|
||||
const text =
|
||||
lastMessage?.content
|
||||
?.filter((c) => c?.type === "text" && typeof c.text === "string")
|
||||
.map((c) => c.text)
|
||||
.join("\n")
|
||||
?.trim() ?? undefined;
|
||||
const meta: AgentMeta | undefined = lastMessage
|
||||
? {
|
||||
model: lastMessage.model,
|
||||
provider: lastMessage.provider,
|
||||
stopReason: lastMessage.stopReason,
|
||||
usage: lastMessage.usage,
|
||||
}
|
||||
: undefined;
|
||||
return { text, meta };
|
||||
|
||||
// Combine all assistant text messages (ignore tool calls/partials). This keeps
|
||||
// multi-message replies intact while dropping non-text events.
|
||||
const text = texts.length ? texts.join("\n\n").trim() : undefined;
|
||||
|
||||
const meta: AgentMeta | undefined =
|
||||
text && lastAssistant
|
||||
? {
|
||||
model: lastAssistant.model,
|
||||
provider: lastAssistant.provider,
|
||||
stopReason: lastAssistant.stopReason,
|
||||
usage: lastAssistant.usage,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
return { texts, meta };
|
||||
}
|
||||
|
||||
export const piSpec: AgentSpec = {
|
||||
|
||||
@@ -16,7 +16,7 @@ export type AgentMeta = {
|
||||
};
|
||||
|
||||
export type AgentParseResult = {
|
||||
text?: string;
|
||||
texts?: string[];
|
||||
mediaUrls?: string[];
|
||||
meta?: AgentMeta;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user