refactor(src): split oversized modules

This commit is contained in:
Peter Steinberger
2026-01-14 01:08:15 +00:00
parent b2179de839
commit bcbfb357be
675 changed files with 91476 additions and 73453 deletions

View File

@@ -0,0 +1,490 @@
import fs from "node:fs/promises";
import os from "node:os";
import type { AgentMessage } from "@mariozechner/pi-agent-core";
import type { AssistantMessage } from "@mariozechner/pi-ai";
import { streamSimple } from "@mariozechner/pi-ai";
import {
createAgentSession,
SessionManager,
SettingsManager,
} from "@mariozechner/pi-coding-agent";
import { resolveHeartbeatPrompt } from "../../../auto-reply/heartbeat.js";
import { resolveChannelCapabilities } from "../../../config/channel-capabilities.js";
import { getMachineDisplayName } from "../../../infra/machine-name.js";
import { normalizeMessageChannel } from "../../../utils/message-channel.js";
import { isReasoningTagProvider } from "../../../utils/provider-utils.js";
import { resolveUserPath } from "../../../utils.js";
import { resolveClawdbotAgentDir } from "../../agent-paths.js";
import { resolveSessionAgentIds } from "../../agent-scope.js";
import { resolveModelAuthMode } from "../../model-auth.js";
import {
buildBootstrapContextFiles,
isCloudCodeAssistFormatError,
resolveBootstrapMaxChars,
validateAnthropicTurns,
validateGeminiTurns,
} from "../../pi-embedded-helpers.js";
import { subscribeEmbeddedPiSession } from "../../pi-embedded-subscribe.js";
import {
ensurePiCompactionReserveTokens,
resolveCompactionReserveTokensFloor,
} from "../../pi-settings.js";
import { createClawdbotCodingTools } from "../../pi-tools.js";
import { resolveSandboxContext } from "../../sandbox.js";
import { guardSessionManager } from "../../session-tool-result-guard-wrapper.js";
import { acquireSessionWriteLock } from "../../session-write-lock.js";
import {
applySkillEnvOverrides,
applySkillEnvOverridesFromSnapshot,
loadWorkspaceSkillEntries,
resolveSkillsPromptForRun,
} from "../../skills.js";
import {
filterBootstrapFilesForSession,
loadWorkspaceBootstrapFiles,
} from "../../workspace.js";
import { isAbortError } from "../abort.js";
import { buildEmbeddedExtensionPaths } from "../extensions.js";
import { applyExtraParamsToAgent } from "../extra-params.js";
import { logToolSchemasForGoogle, sanitizeSessionHistory } from "../google.js";
import {
getDmHistoryLimitFromSessionKey,
limitHistoryTurns,
} from "../history.js";
import { log } from "../logger.js";
import { buildModelAliasLines } from "../model.js";
import {
clearActiveEmbeddedRun,
type EmbeddedPiQueueHandle,
setActiveEmbeddedRun,
} from "../runs.js";
import { buildEmbeddedSandboxInfo } from "../sandbox-info.js";
import {
prewarmSessionFile,
trackSessionManagerAccess,
} from "../session-manager-cache.js";
import { prepareSessionManagerForRun } from "../session-manager-init.js";
import {
buildEmbeddedSystemPrompt,
createSystemPromptOverride,
} from "../system-prompt.js";
import { splitSdkTools } from "../tool-split.js";
import {
formatUserTime,
mapThinkingLevel,
resolveExecToolDefaults,
resolveUserTimezone,
} from "../utils.js";
import type {
EmbeddedRunAttemptParams,
EmbeddedRunAttemptResult,
} from "./types.js";
export async function runEmbeddedAttempt(
params: EmbeddedRunAttemptParams,
): Promise<EmbeddedRunAttemptResult> {
const resolvedWorkspace = resolveUserPath(params.workspaceDir);
const prevCwd = process.cwd();
const runAbortController = new AbortController();
log.debug(
`embedded run start: runId=${params.runId} sessionId=${params.sessionId} provider=${params.provider} model=${params.modelId} thinking=${params.thinkLevel} messageChannel=${params.messageChannel ?? params.messageProvider ?? "unknown"}`,
);
await fs.mkdir(resolvedWorkspace, { recursive: true });
const sandboxSessionKey = params.sessionKey?.trim() || params.sessionId;
const sandbox = await resolveSandboxContext({
config: params.config,
sessionKey: sandboxSessionKey,
workspaceDir: resolvedWorkspace,
});
const effectiveWorkspace = sandbox?.enabled
? sandbox.workspaceAccess === "rw"
? resolvedWorkspace
: sandbox.workspaceDir
: resolvedWorkspace;
await fs.mkdir(effectiveWorkspace, { recursive: true });
let restoreSkillEnv: (() => void) | undefined;
process.chdir(effectiveWorkspace);
try {
const shouldLoadSkillEntries =
!params.skillsSnapshot || !params.skillsSnapshot.resolvedSkills;
const skillEntries = shouldLoadSkillEntries
? loadWorkspaceSkillEntries(effectiveWorkspace)
: [];
restoreSkillEnv = params.skillsSnapshot
? applySkillEnvOverridesFromSnapshot({
snapshot: params.skillsSnapshot,
config: params.config,
})
: applySkillEnvOverrides({
skills: skillEntries ?? [],
config: params.config,
});
const skillsPrompt = resolveSkillsPromptForRun({
skillsSnapshot: params.skillsSnapshot,
entries: shouldLoadSkillEntries ? skillEntries : undefined,
config: params.config,
workspaceDir: effectiveWorkspace,
});
const bootstrapFiles = filterBootstrapFilesForSession(
await loadWorkspaceBootstrapFiles(effectiveWorkspace),
params.sessionKey ?? params.sessionId,
);
const sessionLabel = params.sessionKey ?? params.sessionId;
const contextFiles = buildBootstrapContextFiles(bootstrapFiles, {
maxChars: resolveBootstrapMaxChars(params.config),
warn: (message) => log.warn(`${message} (sessionKey=${sessionLabel})`),
});
const agentDir = params.agentDir ?? resolveClawdbotAgentDir();
const tools = createClawdbotCodingTools({
exec: {
...resolveExecToolDefaults(params.config),
elevated: params.bashElevated,
},
sandbox,
messageProvider: params.messageChannel ?? params.messageProvider,
agentAccountId: params.agentAccountId,
sessionKey: params.sessionKey ?? params.sessionId,
agentDir,
workspaceDir: effectiveWorkspace,
config: params.config,
abortSignal: runAbortController.signal,
modelProvider: params.model.provider,
modelId: params.modelId,
modelAuthMode: resolveModelAuthMode(params.model.provider, params.config),
currentChannelId: params.currentChannelId,
currentThreadTs: params.currentThreadTs,
replyToMode: params.replyToMode,
hasRepliedRef: params.hasRepliedRef,
});
logToolSchemasForGoogle({ tools, provider: params.provider });
const machineName = await getMachineDisplayName();
const runtimeChannel = normalizeMessageChannel(
params.messageChannel ?? params.messageProvider,
);
const runtimeCapabilities = runtimeChannel
? (resolveChannelCapabilities({
cfg: params.config,
channel: runtimeChannel,
accountId: params.agentAccountId,
}) ?? [])
: undefined;
const runtimeInfo = {
host: machineName,
os: `${os.type()} ${os.release()}`,
arch: os.arch(),
node: process.version,
model: `${params.provider}/${params.modelId}`,
channel: runtimeChannel,
capabilities: runtimeCapabilities,
};
const sandboxInfo = buildEmbeddedSandboxInfo(sandbox, params.bashElevated);
const reasoningTagHint = isReasoningTagProvider(params.provider);
const userTimezone = resolveUserTimezone(
params.config?.agents?.defaults?.userTimezone,
);
const userTime = formatUserTime(new Date(), userTimezone);
const { defaultAgentId, sessionAgentId } = resolveSessionAgentIds({
sessionKey: params.sessionKey,
config: params.config,
});
const isDefaultAgent = sessionAgentId === defaultAgentId;
const appendPrompt = buildEmbeddedSystemPrompt({
workspaceDir: effectiveWorkspace,
defaultThinkLevel: params.thinkLevel,
reasoningLevel: params.reasoningLevel ?? "off",
extraSystemPrompt: params.extraSystemPrompt,
ownerNumbers: params.ownerNumbers,
reasoningTagHint,
heartbeatPrompt: isDefaultAgent
? resolveHeartbeatPrompt(
params.config?.agents?.defaults?.heartbeat?.prompt,
)
: undefined,
skillsPrompt,
runtimeInfo,
sandboxInfo,
tools,
modelAliasLines: buildModelAliasLines(params.config),
userTimezone,
userTime,
contextFiles,
});
const systemPrompt = createSystemPromptOverride(appendPrompt);
const sessionLock = await acquireSessionWriteLock({
sessionFile: params.sessionFile,
});
let sessionManager: ReturnType<typeof guardSessionManager> | undefined;
let session:
| Awaited<ReturnType<typeof createAgentSession>>["session"]
| undefined;
try {
const hadSessionFile = await fs
.stat(params.sessionFile)
.then(() => true)
.catch(() => false);
await prewarmSessionFile(params.sessionFile);
sessionManager = guardSessionManager(
SessionManager.open(params.sessionFile),
);
trackSessionManagerAccess(params.sessionFile);
await prepareSessionManagerForRun({
sessionManager,
sessionFile: params.sessionFile,
hadSessionFile,
sessionId: params.sessionId,
cwd: effectiveWorkspace,
});
const settingsManager = SettingsManager.create(
effectiveWorkspace,
agentDir,
);
ensurePiCompactionReserveTokens({
settingsManager,
minReserveTokens: resolveCompactionReserveTokensFloor(params.config),
});
const additionalExtensionPaths = buildEmbeddedExtensionPaths({
cfg: params.config,
sessionManager,
provider: params.provider,
modelId: params.modelId,
model: params.model,
});
const { builtInTools, customTools } = splitSdkTools({
tools,
sandboxEnabled: !!sandbox?.enabled,
});
({ session } = await createAgentSession({
cwd: resolvedWorkspace,
agentDir,
authStorage: params.authStorage,
modelRegistry: params.modelRegistry,
model: params.model,
thinkingLevel: mapThinkingLevel(params.thinkLevel),
systemPrompt,
tools: builtInTools,
customTools,
sessionManager,
settingsManager,
skills: [],
contextFiles: [],
additionalExtensionPaths,
}));
if (!session) {
throw new Error("Embedded agent session missing");
}
const activeSession = session;
// Force a stable streamFn reference so vitest can reliably mock @mariozechner/pi-ai.
activeSession.agent.streamFn = streamSimple;
applyExtraParamsToAgent(
activeSession.agent,
params.config,
params.provider,
params.modelId,
params.thinkLevel,
);
try {
const prior = await sanitizeSessionHistory({
messages: activeSession.messages,
modelApi: params.model.api,
sessionManager,
sessionId: params.sessionId,
});
const validatedGemini = validateGeminiTurns(prior);
const validated = validateAnthropicTurns(validatedGemini);
const limited = limitHistoryTurns(
validated,
getDmHistoryLimitFromSessionKey(params.sessionKey, params.config),
);
if (limited.length > 0) {
activeSession.agent.replaceMessages(limited);
}
} catch (err) {
sessionManager.flushPendingToolResults?.();
activeSession.dispose();
throw err;
}
let aborted = Boolean(params.abortSignal?.aborted);
let timedOut = false;
const abortRun = (isTimeout = false) => {
aborted = true;
if (isTimeout) timedOut = true;
runAbortController.abort();
void activeSession.abort();
};
const subscription = subscribeEmbeddedPiSession({
session: activeSession,
runId: params.runId,
verboseLevel: params.verboseLevel,
reasoningMode: params.reasoningLevel ?? "off",
shouldEmitToolResult: params.shouldEmitToolResult,
onToolResult: params.onToolResult,
onReasoningStream: params.onReasoningStream,
onBlockReply: params.onBlockReply,
onBlockReplyFlush: params.onBlockReplyFlush,
blockReplyBreak: params.blockReplyBreak,
blockReplyChunking: params.blockReplyChunking,
onPartialReply: params.onPartialReply,
onAssistantMessageStart: params.onAssistantMessageStart,
onAgentEvent: params.onAgentEvent,
enforceFinalTag: params.enforceFinalTag,
});
const {
assistantTexts,
toolMetas,
unsubscribe,
waitForCompactionRetry,
getMessagingToolSentTexts,
getMessagingToolSentTargets,
didSendViaMessagingTool,
} = subscription;
const queueHandle: EmbeddedPiQueueHandle = {
queueMessage: async (text: string) => {
await activeSession.steer(text);
},
isStreaming: () => activeSession.isStreaming,
isCompacting: () => subscription.isCompacting(),
abort: abortRun,
};
setActiveEmbeddedRun(params.sessionId, queueHandle);
let abortWarnTimer: NodeJS.Timeout | undefined;
const abortTimer = setTimeout(
() => {
log.warn(
`embedded run timeout: runId=${params.runId} sessionId=${params.sessionId} timeoutMs=${params.timeoutMs}`,
);
abortRun(true);
if (!abortWarnTimer) {
abortWarnTimer = setTimeout(() => {
if (!activeSession.isStreaming) return;
log.warn(
`embedded run abort still streaming: runId=${params.runId} sessionId=${params.sessionId}`,
);
}, 10_000);
}
},
Math.max(1, params.timeoutMs),
);
let messagesSnapshot: AgentMessage[] = [];
let sessionIdUsed = activeSession.sessionId;
const onAbort = () => abortRun();
if (params.abortSignal) {
if (params.abortSignal.aborted) {
onAbort();
} else {
params.abortSignal.addEventListener("abort", onAbort, {
once: true,
});
}
}
let promptError: unknown = null;
try {
const promptStartedAt = Date.now();
log.debug(
`embedded run prompt start: runId=${params.runId} sessionId=${params.sessionId}`,
);
try {
await activeSession.prompt(params.prompt, { images: params.images });
} catch (err) {
promptError = err;
} finally {
log.debug(
`embedded run prompt end: runId=${params.runId} sessionId=${params.sessionId} durationMs=${Date.now() - promptStartedAt}`,
);
}
try {
await waitForCompactionRetry();
} catch (err) {
if (isAbortError(err)) {
if (!promptError) promptError = err;
} else {
throw err;
}
}
messagesSnapshot = activeSession.messages.slice();
sessionIdUsed = activeSession.sessionId;
} finally {
clearTimeout(abortTimer);
if (abortWarnTimer) clearTimeout(abortWarnTimer);
unsubscribe();
clearActiveEmbeddedRun(params.sessionId, queueHandle);
params.abortSignal?.removeEventListener?.("abort", onAbort);
}
const lastAssistant = messagesSnapshot
.slice()
.reverse()
.find((m) => (m as AgentMessage)?.role === "assistant") as
| AssistantMessage
| undefined;
const toolMetasNormalized = toolMetas
.filter(
(entry): entry is { toolName: string; meta?: string } =>
typeof entry.toolName === "string" &&
entry.toolName.trim().length > 0,
)
.map((entry) => ({ toolName: entry.toolName, meta: entry.meta }));
return {
aborted,
timedOut,
promptError,
sessionIdUsed,
messagesSnapshot,
assistantTexts,
toolMetas: toolMetasNormalized,
lastAssistant,
didSendViaMessagingTool: didSendViaMessagingTool(),
messagingToolSentTexts: getMessagingToolSentTexts(),
messagingToolSentTargets: getMessagingToolSentTargets(),
cloudCodeAssistFormatError: Boolean(
lastAssistant?.errorMessage &&
isCloudCodeAssistFormatError(lastAssistant.errorMessage),
),
};
} finally {
// Always tear down the session (and release the lock) before we leave this attempt.
sessionManager?.flushPendingToolResults?.();
session?.dispose();
await sessionLock.release();
}
} finally {
restoreSkillEnv?.();
process.chdir(prevCwd);
}
}

View File

@@ -0,0 +1,75 @@
import type { ImageContent } from "@mariozechner/pi-ai";
import type {
ReasoningLevel,
ThinkLevel,
VerboseLevel,
} from "../../../auto-reply/thinking.js";
import type { ClawdbotConfig } from "../../../config/config.js";
import type { enqueueCommand } from "../../../process/command-queue.js";
import type { ExecElevatedDefaults } from "../../bash-tools.js";
import type { BlockReplyChunking } from "../../pi-embedded-subscribe.js";
import type { SkillSnapshot } from "../../skills.js";
export type RunEmbeddedPiAgentParams = {
sessionId: string;
sessionKey?: string;
messageChannel?: string;
messageProvider?: string;
agentAccountId?: string;
/** Current channel ID for auto-threading (Slack). */
currentChannelId?: string;
/** Current thread timestamp for auto-threading (Slack). */
currentThreadTs?: string;
/** Reply-to mode for Slack auto-threading. */
replyToMode?: "off" | "first" | "all";
/** Mutable ref to track if a reply was sent (for "first" mode). */
hasRepliedRef?: { value: boolean };
sessionFile: string;
workspaceDir: string;
agentDir?: string;
config?: ClawdbotConfig;
skillsSnapshot?: SkillSnapshot;
prompt: string;
images?: ImageContent[];
provider?: string;
model?: string;
authProfileId?: string;
thinkLevel?: ThinkLevel;
verboseLevel?: VerboseLevel;
reasoningLevel?: ReasoningLevel;
bashElevated?: ExecElevatedDefaults;
timeoutMs: number;
runId: string;
abortSignal?: AbortSignal;
shouldEmitToolResult?: () => boolean;
onPartialReply?: (payload: {
text?: string;
mediaUrls?: string[];
}) => void | Promise<void>;
onAssistantMessageStart?: () => void | Promise<void>;
onBlockReply?: (payload: {
text?: string;
mediaUrls?: string[];
audioAsVoice?: boolean;
}) => void | Promise<void>;
onBlockReplyFlush?: () => void | Promise<void>;
blockReplyBreak?: "text_end" | "message_end";
blockReplyChunking?: BlockReplyChunking;
onReasoningStream?: (payload: {
text?: string;
mediaUrls?: string[];
}) => void | Promise<void>;
onToolResult?: (payload: {
text?: string;
mediaUrls?: string[];
}) => void | Promise<void>;
onAgentEvent?: (evt: {
stream: string;
data: Record<string, unknown>;
}) => void;
lane?: string;
enqueue?: typeof enqueueCommand;
extraSystemPrompt?: string;
ownerNumbers?: string[];
enforceFinalTag?: boolean;
};

View File

@@ -0,0 +1,147 @@
import type { AssistantMessage } from "@mariozechner/pi-ai";
import { parseReplyDirectives } from "../../../auto-reply/reply/reply-directives.js";
import type {
ReasoningLevel,
VerboseLevel,
} from "../../../auto-reply/thinking.js";
import {
isSilentReplyText,
SILENT_REPLY_TOKEN,
} from "../../../auto-reply/tokens.js";
import { formatToolAggregate } from "../../../auto-reply/tool-meta.js";
import type { ClawdbotConfig } from "../../../config/config.js";
import { formatAssistantErrorText } from "../../pi-embedded-helpers.js";
import {
extractAssistantText,
extractAssistantThinking,
formatReasoningMessage,
} from "../../pi-embedded-utils.js";
type ToolMetaEntry = { toolName: string; meta?: string };
export function buildEmbeddedRunPayloads(params: {
assistantTexts: string[];
toolMetas: ToolMetaEntry[];
lastAssistant: AssistantMessage | undefined;
config?: ClawdbotConfig;
sessionKey: string;
verboseLevel?: VerboseLevel;
reasoningLevel?: ReasoningLevel;
inlineToolResultsAllowed: boolean;
}): Array<{
text?: string;
mediaUrl?: string;
mediaUrls?: string[];
replyToId?: string;
isError?: boolean;
audioAsVoice?: boolean;
replyToTag?: boolean;
replyToCurrent?: boolean;
}> {
const replyItems: Array<{
text: string;
media?: string[];
isError?: boolean;
audioAsVoice?: boolean;
replyToId?: string;
replyToTag?: boolean;
replyToCurrent?: boolean;
}> = [];
const errorText = params.lastAssistant
? formatAssistantErrorText(params.lastAssistant, {
cfg: params.config,
sessionKey: params.sessionKey,
})
: undefined;
if (errorText) replyItems.push({ text: errorText, isError: true });
const inlineToolResults =
params.inlineToolResultsAllowed &&
params.verboseLevel === "on" &&
params.toolMetas.length > 0;
if (inlineToolResults) {
for (const { toolName, meta } of params.toolMetas) {
const agg = formatToolAggregate(toolName, meta ? [meta] : []);
const {
text: cleanedText,
mediaUrls,
audioAsVoice,
replyToId,
replyToTag,
replyToCurrent,
} = parseReplyDirectives(agg);
if (cleanedText) {
replyItems.push({
text: cleanedText,
media: mediaUrls,
audioAsVoice,
replyToId,
replyToTag,
replyToCurrent,
});
}
}
}
const reasoningText =
params.lastAssistant && params.reasoningLevel === "on"
? formatReasoningMessage(extractAssistantThinking(params.lastAssistant))
: "";
if (reasoningText) replyItems.push({ text: reasoningText });
const fallbackAnswerText = params.lastAssistant
? extractAssistantText(params.lastAssistant)
: "";
const answerTexts = params.assistantTexts.length
? params.assistantTexts
: fallbackAnswerText
? [fallbackAnswerText]
: [];
for (const text of answerTexts) {
const {
text: cleanedText,
mediaUrls,
audioAsVoice,
replyToId,
replyToTag,
replyToCurrent,
} = parseReplyDirectives(text);
if (
!cleanedText &&
(!mediaUrls || mediaUrls.length === 0) &&
!audioAsVoice
) {
continue;
}
replyItems.push({
text: cleanedText,
media: mediaUrls,
audioAsVoice,
replyToId,
replyToTag,
replyToCurrent,
});
}
const hasAudioAsVoiceTag = replyItems.some((item) => item.audioAsVoice);
return replyItems
.map((item) => ({
text: item.text?.trim() ? item.text.trim() : undefined,
mediaUrls: item.media?.length ? item.media : undefined,
mediaUrl: item.media?.[0],
isError: item.isError,
replyToId: item.replyToId,
replyToTag: item.replyToTag,
replyToCurrent: item.replyToCurrent,
audioAsVoice:
item.audioAsVoice || Boolean(hasAudioAsVoiceTag && item.media?.length),
}))
.filter((p) => {
if (!p.text && !p.mediaUrl && (!p.mediaUrls || p.mediaUrls.length === 0))
return false;
if (p.text && isSilentReplyText(p.text, SILENT_REPLY_TOKEN)) return false;
return true;
});
}

View File

@@ -0,0 +1,100 @@
import type { AgentMessage } from "@mariozechner/pi-agent-core";
import type {
Api,
AssistantMessage,
ImageContent,
Model,
} from "@mariozechner/pi-ai";
import type {
discoverAuthStorage,
discoverModels,
} from "@mariozechner/pi-coding-agent";
import type {
ReasoningLevel,
ThinkLevel,
VerboseLevel,
} from "../../../auto-reply/thinking.js";
import type { ClawdbotConfig } from "../../../config/config.js";
import type { ExecElevatedDefaults } from "../../bash-tools.js";
import type { MessagingToolSend } from "../../pi-embedded-messaging.js";
import type { BlockReplyChunking } from "../../pi-embedded-subscribe.js";
import type { SkillSnapshot } from "../../skills.js";
type AuthStorage = ReturnType<typeof discoverAuthStorage>;
type ModelRegistry = ReturnType<typeof discoverModels>;
export type EmbeddedRunAttemptParams = {
sessionId: string;
sessionKey?: string;
messageChannel?: string;
messageProvider?: string;
agentAccountId?: string;
currentChannelId?: string;
currentThreadTs?: string;
replyToMode?: "off" | "first" | "all";
hasRepliedRef?: { value: boolean };
sessionFile: string;
workspaceDir: string;
agentDir?: string;
config?: ClawdbotConfig;
skillsSnapshot?: SkillSnapshot;
prompt: string;
images?: ImageContent[];
provider: string;
modelId: string;
model: Model<Api>;
authStorage: AuthStorage;
modelRegistry: ModelRegistry;
thinkLevel: ThinkLevel;
verboseLevel?: VerboseLevel;
reasoningLevel?: ReasoningLevel;
bashElevated?: ExecElevatedDefaults;
timeoutMs: number;
runId: string;
abortSignal?: AbortSignal;
shouldEmitToolResult?: () => boolean;
onPartialReply?: (payload: {
text?: string;
mediaUrls?: string[];
}) => void | Promise<void>;
onAssistantMessageStart?: () => void | Promise<void>;
onBlockReply?: (payload: {
text?: string;
mediaUrls?: string[];
audioAsVoice?: boolean;
}) => void | Promise<void>;
onBlockReplyFlush?: () => void | Promise<void>;
blockReplyBreak?: "text_end" | "message_end";
blockReplyChunking?: BlockReplyChunking;
onReasoningStream?: (payload: {
text?: string;
mediaUrls?: string[];
}) => void | Promise<void>;
onToolResult?: (payload: {
text?: string;
mediaUrls?: string[];
}) => void | Promise<void>;
onAgentEvent?: (evt: {
stream: string;
data: Record<string, unknown>;
}) => void;
extraSystemPrompt?: string;
ownerNumbers?: string[];
enforceFinalTag?: boolean;
};
export type EmbeddedRunAttemptResult = {
aborted: boolean;
timedOut: boolean;
promptError: unknown;
sessionIdUsed: string;
messagesSnapshot: AgentMessage[];
assistantTexts: string[];
toolMetas: Array<{ toolName: string; meta?: string }>;
lastAssistant: AssistantMessage | undefined;
didSendViaMessagingTool: boolean;
messagingToolSentTexts: string[];
messagingToolSentTargets: MessagingToolSend[];
cloudCodeAssistFormatError: boolean;
};