import {
streamSimple,
type Api,
type AssistantMessageEvent,
type ImageContent,
type Message,
type Model,
type TextContent,
} from "@mariozechner/pi-ai"; import { SessionManager } from "@mariozechner/pi-coding-agent"; import type { GetReplyOptions } from "../auto-reply/get-reply-options.types.js"; import type { ReplyPayload } from "../auto-reply/reply-payload.js"; import type { ReasoningLevel, ThinkLevel } from "../auto-reply/thinking.js"; import {
resolveSessionFilePath,
resolveSessionFilePathOptions,
type SessionEntry,
} from "../config/sessions.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import { diagnosticLogger as diag } from "../logging/diagnostic.js"; import { prepareProviderRuntimeAuth } from "../plugins/provider-runtime.js"; import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js"; import { resolveAgentWorkspaceDir, resolveSessionAgentId } from "./agent-scope.js"; import { resolveSessionAuthProfileOverride } from "./auth-profiles/session-override.js"; import {
resolveImageSanitizationLimits,
type ImageSanitizationLimits,
} from "./image-sanitization.js"; import { getApiKeyForModel, requireApiKey } from "./model-auth.js"; import { ensureOpenClawModelsJson } from "./models-config.js"; import { EmbeddedBlockChunker, type BlockReplyChunking } from "./pi-embedded-block-chunker.js"; import { resolveModelWithRegistry } from "./pi-embedded-runner/model.js"; import { getActiveEmbeddedRunSnapshot } from "./pi-embedded-runner/runs.js"; import { streamWithPayloadPatch } from "./pi-embedded-runner/stream-payload-utils.js"; import { discoverAuthStorage, discoverModels } from "./pi-model-discovery.js"; import { registerProviderStreamForModel } from "./provider-stream.js"; import { stripToolResultDetails } from "./session-transcript-repair.js"; import { sanitizeImageBlocks } from "./tool-images.js";
function collectTextContent(content: Array<{ type?: string; text?: string }>): string { return content
.filter((part): part is { type: "text"; text: string } => part.type === "text")
.map((part) => part.text)
.join("");
}
function collectThinkingContent(content: Array<{ type?: string; thinking?: string }>): string { return content
.filter((part): part is { type: "thinking"; thinking: string } => part.type === "thinking")
.map((part) => part.thinking)
.join("");
}
function buildBtwSystemPrompt(): string { return [ "You are answering an ephemeral /btw side question about the current conversation.", "Use the conversation only as background context.", "Answer only the side question in the last user message.", "Do not continue, resume, or complete any unfinished task from the conversation.", "Do not emit tool calls, pseudo-tool calls, shell commands, file writes, patches, or code unless the side question explicitly asks for them.", "Do not say you will continue the main task after answering.", "If the question can be answered briefly, answer briefly.",
].join("\n");
}
function buildBtwQuestionPrompt(question: string, inFlightPrompt?: string): string { const lines = [ "Answer this side question only.", "Ignore any unfinished task in the conversation while answering it.",
]; const trimmedPrompt = inFlightPrompt?.trim(); if (trimmedPrompt) {
lines.push( "", "Current in-flight main task request for background context only:", "<in_flight_main_task>",
trimmedPrompt, "</in_flight_main_task>", "Do not continue or complete that task while answering the side question.",
);
}
lines.push("", "<btw_side_question>", question.trim(), "</btw_side_question>"); return lines.join("\n");
}
function normalizeBtwContentBlocks(content: unknown): unknown[] | undefined { if (Array.isArray(content)) { return content;
} if (content && typeof content === "object") { return [content];
} return undefined;
}
function isBtwTextBlock(block: unknown): block is TextContent { if (!block || typeof block !== "object") { returnfalse;
} const record = block as { type?: unknown; text?: unknown }; return normalizeLowercaseStringOrEmpty(record.type) === "text" && typeof record.text === "string";
}
function isBtwImageBlock(block: unknown): block is ImageContent { if (!block || typeof block !== "object") { returnfalse;
} const record = block as { type?: unknown; data?: unknown; mimeType?: unknown }; return (
normalizeLowercaseStringOrEmpty(record.type) === "image" && typeof record.data === "string" && typeof record.mimeType === "string"
);
}
// Use the provider's own stream fn so providers like Ollama (which build // `/api/chat` or `/v1/chat/completions` paths based on api mode) construct // URLs correctly. Without this, streamSimple hits the provider's baseUrl // directly and 404s on endpoints like Ollama Cloud (#68336). const providerStreamFn = registerProviderStreamForModel({
model: runtimeModel,
cfg: params.cfg,
agentDir: params.agentDir,
workspaceDir,
env: process.env,
});
const chunker =
params.opts?.onBlockReply && params.blockReplyChunking
? new EmbeddedBlockChunker(params.blockReplyChunking)
: undefined;
let emittedBlocks = 0;
let blockEmitChain: Promise<void> = Promise.resolve();
let answerText = "";
let reasoningText = "";
let assistantStarted = false;
let sawTextEvent = false;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.