feat: add /reasoning reasoning visibility
This commit is contained in:
@@ -13,7 +13,11 @@ import {
|
||||
type Skill,
|
||||
} from "@mariozechner/pi-coding-agent";
|
||||
import { resolveHeartbeatPrompt } from "../auto-reply/heartbeat.js";
|
||||
import type { ThinkLevel, VerboseLevel } from "../auto-reply/thinking.js";
|
||||
import type {
|
||||
ReasoningLevel,
|
||||
ThinkLevel,
|
||||
VerboseLevel,
|
||||
} from "../auto-reply/thinking.js";
|
||||
import { formatToolAggregate } from "../auto-reply/tool-meta.js";
|
||||
import type { ClawdbotConfig } from "../config/config.js";
|
||||
import { getMachineDisplayName } from "../infra/machine-name.js";
|
||||
@@ -53,7 +57,11 @@ import {
|
||||
type BlockReplyChunking,
|
||||
subscribeEmbeddedPiSession,
|
||||
} from "./pi-embedded-subscribe.js";
|
||||
import { extractAssistantText } from "./pi-embedded-utils.js";
|
||||
import {
|
||||
extractAssistantText,
|
||||
extractAssistantThinking,
|
||||
formatReasoningMarkdown,
|
||||
} from "./pi-embedded-utils.js";
|
||||
import { toToolDefinitions } from "./pi-tool-definition-adapter.js";
|
||||
import { createClawdbotCodingTools } from "./pi-tools.js";
|
||||
import { resolveSandboxContext } from "./sandbox.js";
|
||||
@@ -575,6 +583,7 @@ export async function runEmbeddedPiAgent(params: {
|
||||
authProfileId?: string;
|
||||
thinkLevel?: ThinkLevel;
|
||||
verboseLevel?: VerboseLevel;
|
||||
reasoningLevel?: ReasoningLevel;
|
||||
bashElevated?: BashElevatedDefaults;
|
||||
timeoutMs: number;
|
||||
runId: string;
|
||||
@@ -846,6 +855,7 @@ export async function runEmbeddedPiAgent(params: {
|
||||
session,
|
||||
runId: params.runId,
|
||||
verboseLevel: params.verboseLevel,
|
||||
includeReasoning: params.reasoningLevel === "on",
|
||||
shouldEmitToolResult: params.shouldEmitToolResult,
|
||||
onToolResult: params.onToolResult,
|
||||
onBlockReply: params.onBlockReply,
|
||||
@@ -1064,10 +1074,22 @@ export async function runEmbeddedPiAgent(params: {
|
||||
}
|
||||
}
|
||||
|
||||
const fallbackText = lastAssistant
|
||||
? (() => {
|
||||
const base = extractAssistantText(lastAssistant);
|
||||
if (params.reasoningLevel !== "on") return base;
|
||||
const thinking = extractAssistantThinking(lastAssistant);
|
||||
const formatted = thinking
|
||||
? formatReasoningMarkdown(thinking)
|
||||
: "";
|
||||
if (!formatted) return base;
|
||||
return base ? `${base}\n\n${formatted}` : formatted;
|
||||
})()
|
||||
: "";
|
||||
for (const text of assistantTexts.length
|
||||
? assistantTexts
|
||||
: lastAssistant
|
||||
? [extractAssistantText(lastAssistant)]
|
||||
: fallbackText
|
||||
? [fallbackText]
|
||||
: []) {
|
||||
const { text: cleanedText, mediaUrls } = splitMediaFromOutput(text);
|
||||
if (!cleanedText && (!mediaUrls || mediaUrls.length === 0))
|
||||
|
||||
@@ -10,6 +10,8 @@ import type { BlockReplyChunking } from "./pi-embedded-block-chunker.js";
|
||||
import { EmbeddedBlockChunker } from "./pi-embedded-block-chunker.js";
|
||||
import {
|
||||
extractAssistantText,
|
||||
extractAssistantThinking,
|
||||
formatReasoningMarkdown,
|
||||
inferToolMetaFromArgs,
|
||||
} from "./pi-embedded-utils.js";
|
||||
|
||||
@@ -85,6 +87,7 @@ export function subscribeEmbeddedPiSession(params: {
|
||||
session: AgentSession;
|
||||
runId: string;
|
||||
verboseLevel?: "off" | "on";
|
||||
includeReasoning?: boolean;
|
||||
shouldEmitToolResult?: () => boolean;
|
||||
onToolResult?: (payload: {
|
||||
text?: string;
|
||||
@@ -120,6 +123,7 @@ export function subscribeEmbeddedPiSession(params: {
|
||||
let pendingCompactionRetry = 0;
|
||||
let compactionRetryResolve: (() => void) | undefined;
|
||||
let compactionRetryPromise: Promise<void> | null = null;
|
||||
let lastReasoningSent: string | undefined;
|
||||
|
||||
const ensureCompactionPromise = () => {
|
||||
if (!compactionRetryPromise) {
|
||||
@@ -216,6 +220,24 @@ export function subscribeEmbeddedPiSession(params: {
|
||||
});
|
||||
};
|
||||
|
||||
const extractThinkingFromText = (text: string): string => {
|
||||
if (!text || !THINKING_TAG_RE.test(text)) return "";
|
||||
THINKING_TAG_RE.lastIndex = 0;
|
||||
let result = "";
|
||||
let lastIndex = 0;
|
||||
let inThinking = false;
|
||||
for (const match of text.matchAll(THINKING_TAG_RE)) {
|
||||
const idx = match.index ?? 0;
|
||||
if (inThinking) {
|
||||
result += text.slice(lastIndex, idx);
|
||||
}
|
||||
const tag = match[0].toLowerCase();
|
||||
inThinking = !tag.includes("/");
|
||||
lastIndex = idx + match[0].length;
|
||||
}
|
||||
return result.trim();
|
||||
};
|
||||
|
||||
const resetForCompactionRetry = () => {
|
||||
assistantTexts.length = 0;
|
||||
toolMetas.length = 0;
|
||||
@@ -244,6 +266,7 @@ export function subscribeEmbeddedPiSession(params: {
|
||||
blockChunker?.reset();
|
||||
lastStreamedAssistant = undefined;
|
||||
lastBlockReplyText = undefined;
|
||||
lastReasoningSent = undefined;
|
||||
assistantTextBaseline = assistantTexts.length;
|
||||
}
|
||||
}
|
||||
@@ -470,19 +493,26 @@ export function subscribeEmbeddedPiSession(params: {
|
||||
if (evt.type === "message_end") {
|
||||
const msg = (evt as AgentEvent & { message: AgentMessage }).message;
|
||||
if (msg?.role === "assistant") {
|
||||
const assistantMessage = msg as AssistantMessage;
|
||||
const rawText = extractAssistantText(assistantMessage);
|
||||
const cleaned = params.enforceFinalTag
|
||||
? stripThinkingSegments(
|
||||
stripUnpairedThinkingTags(
|
||||
extractAssistantText(msg as AssistantMessage),
|
||||
),
|
||||
)
|
||||
: stripThinkingSegments(
|
||||
extractAssistantText(msg as AssistantMessage),
|
||||
);
|
||||
const text =
|
||||
? stripThinkingSegments(stripUnpairedThinkingTags(rawText))
|
||||
: stripThinkingSegments(rawText);
|
||||
const baseText =
|
||||
params.enforceFinalTag && cleaned
|
||||
? (extractFinalText(cleaned)?.trim() ?? cleaned)
|
||||
: cleaned;
|
||||
const rawThinking = params.includeReasoning
|
||||
? extractAssistantThinking(assistantMessage) ||
|
||||
extractThinkingFromText(rawText)
|
||||
: "";
|
||||
const formattedReasoning = rawThinking
|
||||
? formatReasoningMarkdown(rawThinking)
|
||||
: "";
|
||||
const text =
|
||||
baseText && formattedReasoning
|
||||
? `${baseText}\n\n${formattedReasoning}`
|
||||
: baseText || formattedReasoning;
|
||||
|
||||
const addedDuringMessage =
|
||||
assistantTexts.length > assistantTextBaseline;
|
||||
@@ -516,6 +546,16 @@ export function subscribeEmbeddedPiSession(params: {
|
||||
}
|
||||
}
|
||||
}
|
||||
const onBlockReply = params.onBlockReply;
|
||||
const shouldEmitReasoningBlock =
|
||||
Boolean(formattedReasoning) &&
|
||||
Boolean(onBlockReply) &&
|
||||
formattedReasoning !== lastReasoningSent &&
|
||||
(blockReplyBreak === "text_end" || Boolean(blockChunker));
|
||||
if (shouldEmitReasoningBlock && formattedReasoning && onBlockReply) {
|
||||
lastReasoningSent = formattedReasoning;
|
||||
void onBlockReply({ text: formattedReasoning });
|
||||
}
|
||||
deltaBuffer = "";
|
||||
blockBuffer = "";
|
||||
blockChunker?.reset();
|
||||
|
||||
@@ -19,6 +19,32 @@ export function extractAssistantText(msg: AssistantMessage): string {
|
||||
return blocks.join("\n").trim();
|
||||
}
|
||||
|
||||
export function extractAssistantThinking(msg: AssistantMessage): string {
|
||||
if (!Array.isArray(msg.content)) return "";
|
||||
const blocks = msg.content
|
||||
.map((block) => {
|
||||
if (!block || typeof block !== "object") return "";
|
||||
const record = block as unknown as Record<string, unknown>;
|
||||
if (record.type === "thinking" && typeof record.thinking === "string") {
|
||||
return record.thinking.trim();
|
||||
}
|
||||
return "";
|
||||
})
|
||||
.filter(Boolean);
|
||||
return blocks.join("\n").trim();
|
||||
}
|
||||
|
||||
export function formatReasoningMarkdown(text: string): string {
|
||||
const trimmed = text.trim();
|
||||
if (!trimmed) return "";
|
||||
const lines = trimmed.split(/\r?\n/);
|
||||
const wrapped = lines
|
||||
.map((line) => line.trim())
|
||||
.map((line) => (line ? `_${line}_` : ""))
|
||||
.filter((line) => line.length > 0);
|
||||
return wrapped.length > 0 ? [`_Reasoning:_`, ...wrapped].join("\n") : "";
|
||||
}
|
||||
|
||||
export function inferToolMetaFromArgs(
|
||||
toolName: string,
|
||||
args: unknown,
|
||||
|
||||
Reference in New Issue
Block a user