mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-19 11:18:37 +00:00
style: format PR 223 changes
This commit is contained in:
@@ -1,14 +1,17 @@
|
|||||||
import type { AssistantMessage } from "@mariozechner/pi-ai";
|
import type { AssistantMessage } from "@mariozechner/pi-ai";
|
||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
|
import type { ThinkLevel } from "../auto-reply/thinking.js";
|
||||||
import {
|
import {
|
||||||
isRateLimitAssistantError,
|
isRateLimitAssistantError,
|
||||||
pickFallbackThinkingLevel,
|
pickFallbackThinkingLevel,
|
||||||
} from "./pi-embedded-helpers.js";
|
} from "./pi-embedded-helpers.js";
|
||||||
import type { ThinkLevel } from "../auto-reply/thinking.js";
|
|
||||||
|
|
||||||
const asAssistant = (overrides: Partial<AssistantMessage>) =>
|
const asAssistant = (overrides: Partial<AssistantMessage>) =>
|
||||||
({ role: "assistant", stopReason: "error", ...overrides }) as AssistantMessage;
|
({
|
||||||
|
role: "assistant",
|
||||||
|
stopReason: "error",
|
||||||
|
...overrides,
|
||||||
|
}) as AssistantMessage;
|
||||||
|
|
||||||
describe("isRateLimitAssistantError", () => {
|
describe("isRateLimitAssistantError", () => {
|
||||||
it("detects 429 rate limit payloads", () => {
|
it("detects 429 rate limit payloads", () => {
|
||||||
@@ -57,8 +60,7 @@ describe("pickFallbackThinkingLevel", () => {
|
|||||||
it("skips already attempted levels", () => {
|
it("skips already attempted levels", () => {
|
||||||
const attempted = new Set<ThinkLevel>(["low", "medium"]);
|
const attempted = new Set<ThinkLevel>(["low", "medium"]);
|
||||||
const next = pickFallbackThinkingLevel({
|
const next = pickFallbackThinkingLevel({
|
||||||
message:
|
message: "Supported values are: 'medium', 'high', and 'xhigh'.",
|
||||||
"Supported values are: 'medium', 'high', and 'xhigh'.",
|
|
||||||
attempted,
|
attempted,
|
||||||
});
|
});
|
||||||
expect(next).toBe("high");
|
expect(next).toBe("high");
|
||||||
|
|||||||
@@ -6,7 +6,10 @@ import type {
|
|||||||
AgentToolResult,
|
AgentToolResult,
|
||||||
} from "@mariozechner/pi-agent-core";
|
} from "@mariozechner/pi-agent-core";
|
||||||
import type { AssistantMessage } from "@mariozechner/pi-ai";
|
import type { AssistantMessage } from "@mariozechner/pi-ai";
|
||||||
import { normalizeThinkLevel, type ThinkLevel } from "../auto-reply/thinking.js";
|
import {
|
||||||
|
normalizeThinkLevel,
|
||||||
|
type ThinkLevel,
|
||||||
|
} from "../auto-reply/thinking.js";
|
||||||
|
|
||||||
import { sanitizeContentBlocksImages } from "./tool-images.js";
|
import { sanitizeContentBlocksImages } from "./tool-images.js";
|
||||||
import type { WorkspaceBootstrapFile } from "./workspace.js";
|
import type { WorkspaceBootstrapFile } from "./workspace.js";
|
||||||
|
|||||||
@@ -340,319 +340,325 @@ export async function runEmbeddedPiAgent(params: {
|
|||||||
let restoreSkillEnv: (() => void) | undefined;
|
let restoreSkillEnv: (() => void) | undefined;
|
||||||
process.chdir(resolvedWorkspace);
|
process.chdir(resolvedWorkspace);
|
||||||
try {
|
try {
|
||||||
const shouldLoadSkillEntries =
|
const shouldLoadSkillEntries =
|
||||||
!params.skillsSnapshot || !params.skillsSnapshot.resolvedSkills;
|
!params.skillsSnapshot || !params.skillsSnapshot.resolvedSkills;
|
||||||
const skillEntries = shouldLoadSkillEntries
|
const skillEntries = shouldLoadSkillEntries
|
||||||
? loadWorkspaceSkillEntries(resolvedWorkspace)
|
? loadWorkspaceSkillEntries(resolvedWorkspace)
|
||||||
: [];
|
: [];
|
||||||
const skillsSnapshot =
|
const skillsSnapshot =
|
||||||
params.skillsSnapshot ??
|
params.skillsSnapshot ??
|
||||||
buildWorkspaceSkillSnapshot(resolvedWorkspace, {
|
buildWorkspaceSkillSnapshot(resolvedWorkspace, {
|
||||||
|
config: params.config,
|
||||||
|
entries: skillEntries,
|
||||||
|
});
|
||||||
|
const sandboxSessionKey =
|
||||||
|
params.sessionKey?.trim() || params.sessionId;
|
||||||
|
const sandbox = await resolveSandboxContext({
|
||||||
config: params.config,
|
config: params.config,
|
||||||
entries: skillEntries,
|
sessionKey: sandboxSessionKey,
|
||||||
});
|
|
||||||
const sandboxSessionKey = params.sessionKey?.trim() || params.sessionId;
|
|
||||||
const sandbox = await resolveSandboxContext({
|
|
||||||
config: params.config,
|
|
||||||
sessionKey: sandboxSessionKey,
|
|
||||||
workspaceDir: resolvedWorkspace,
|
|
||||||
});
|
|
||||||
restoreSkillEnv = params.skillsSnapshot
|
|
||||||
? applySkillEnvOverridesFromSnapshot({
|
|
||||||
snapshot: params.skillsSnapshot,
|
|
||||||
config: params.config,
|
|
||||||
})
|
|
||||||
: applySkillEnvOverrides({
|
|
||||||
skills: skillEntries ?? [],
|
|
||||||
config: params.config,
|
|
||||||
});
|
|
||||||
|
|
||||||
const bootstrapFiles =
|
|
||||||
await loadWorkspaceBootstrapFiles(resolvedWorkspace);
|
|
||||||
const contextFiles = buildBootstrapContextFiles(bootstrapFiles);
|
|
||||||
const promptSkills = resolvePromptSkills(skillsSnapshot, skillEntries);
|
|
||||||
// Tool schemas must be provider-compatible (OpenAI requires top-level `type: "object"`).
|
|
||||||
// `createClawdbotCodingTools()` normalizes schemas so the session can pass them through unchanged.
|
|
||||||
const tools = createClawdbotCodingTools({
|
|
||||||
bash: {
|
|
||||||
...params.config?.agent?.bash,
|
|
||||||
elevated: params.bashElevated,
|
|
||||||
},
|
|
||||||
sandbox,
|
|
||||||
surface: params.surface,
|
|
||||||
sessionKey: params.sessionKey ?? params.sessionId,
|
|
||||||
config: params.config,
|
|
||||||
});
|
|
||||||
const machineName = await getMachineDisplayName();
|
|
||||||
const runtimeInfo = {
|
|
||||||
host: machineName,
|
|
||||||
os: `${os.type()} ${os.release()}`,
|
|
||||||
arch: os.arch(),
|
|
||||||
node: process.version,
|
|
||||||
model: `${provider}/${modelId}`,
|
|
||||||
};
|
|
||||||
const sandboxInfo = buildEmbeddedSandboxInfo(sandbox);
|
|
||||||
const reasoningTagHint = provider === "ollama";
|
|
||||||
const systemPrompt = buildSystemPrompt({
|
|
||||||
appendPrompt: buildAgentSystemPromptAppend({
|
|
||||||
workspaceDir: resolvedWorkspace,
|
workspaceDir: resolvedWorkspace,
|
||||||
defaultThinkLevel: thinkLevel,
|
});
|
||||||
extraSystemPrompt: params.extraSystemPrompt,
|
restoreSkillEnv = params.skillsSnapshot
|
||||||
ownerNumbers: params.ownerNumbers,
|
? applySkillEnvOverridesFromSnapshot({
|
||||||
reasoningTagHint,
|
snapshot: params.skillsSnapshot,
|
||||||
runtimeInfo,
|
config: params.config,
|
||||||
sandboxInfo,
|
})
|
||||||
toolNames: tools.map((tool) => tool.name),
|
: applySkillEnvOverrides({
|
||||||
}),
|
skills: skillEntries ?? [],
|
||||||
contextFiles,
|
config: params.config,
|
||||||
skills: promptSkills,
|
});
|
||||||
cwd: resolvedWorkspace,
|
|
||||||
tools,
|
|
||||||
});
|
|
||||||
|
|
||||||
const sessionManager = SessionManager.open(params.sessionFile);
|
const bootstrapFiles =
|
||||||
const settingsManager = SettingsManager.create(
|
await loadWorkspaceBootstrapFiles(resolvedWorkspace);
|
||||||
resolvedWorkspace,
|
const contextFiles = buildBootstrapContextFiles(bootstrapFiles);
|
||||||
agentDir,
|
const promptSkills = resolvePromptSkills(
|
||||||
);
|
skillsSnapshot,
|
||||||
|
skillEntries,
|
||||||
const { session } = await createAgentSession({
|
|
||||||
cwd: resolvedWorkspace,
|
|
||||||
agentDir,
|
|
||||||
authStorage,
|
|
||||||
modelRegistry,
|
|
||||||
model,
|
|
||||||
thinkingLevel,
|
|
||||||
systemPrompt,
|
|
||||||
// Custom tool set: extra bash/process + read image sanitization.
|
|
||||||
tools,
|
|
||||||
sessionManager,
|
|
||||||
settingsManager,
|
|
||||||
skills: promptSkills,
|
|
||||||
contextFiles,
|
|
||||||
});
|
|
||||||
|
|
||||||
const prior = await sanitizeSessionMessagesImages(
|
|
||||||
session.messages,
|
|
||||||
"session:history",
|
|
||||||
);
|
|
||||||
if (prior.length > 0) {
|
|
||||||
session.agent.replaceMessages(prior);
|
|
||||||
}
|
|
||||||
let aborted = Boolean(params.abortSignal?.aborted);
|
|
||||||
const abortRun = () => {
|
|
||||||
aborted = true;
|
|
||||||
void session.abort();
|
|
||||||
};
|
|
||||||
const queueHandle: EmbeddedPiQueueHandle = {
|
|
||||||
queueMessage: async (text: string) => {
|
|
||||||
await session.steer(text);
|
|
||||||
},
|
|
||||||
isStreaming: () => session.isStreaming,
|
|
||||||
abort: abortRun,
|
|
||||||
};
|
|
||||||
ACTIVE_EMBEDDED_RUNS.set(params.sessionId, queueHandle);
|
|
||||||
|
|
||||||
const {
|
|
||||||
assistantTexts,
|
|
||||||
toolMetas,
|
|
||||||
unsubscribe,
|
|
||||||
waitForCompactionRetry,
|
|
||||||
} = subscribeEmbeddedPiSession({
|
|
||||||
session,
|
|
||||||
runId: params.runId,
|
|
||||||
verboseLevel: params.verboseLevel,
|
|
||||||
shouldEmitToolResult: params.shouldEmitToolResult,
|
|
||||||
onToolResult: params.onToolResult,
|
|
||||||
onBlockReply: params.onBlockReply,
|
|
||||||
blockReplyBreak: params.blockReplyBreak,
|
|
||||||
blockReplyChunking: params.blockReplyChunking,
|
|
||||||
onPartialReply: params.onPartialReply,
|
|
||||||
onAgentEvent: params.onAgentEvent,
|
|
||||||
enforceFinalTag: params.enforceFinalTag,
|
|
||||||
});
|
|
||||||
|
|
||||||
let abortWarnTimer: NodeJS.Timeout | undefined;
|
|
||||||
const abortTimer = setTimeout(
|
|
||||||
() => {
|
|
||||||
log.warn(
|
|
||||||
`embedded run timeout: runId=${params.runId} sessionId=${params.sessionId} timeoutMs=${params.timeoutMs}`,
|
|
||||||
);
|
|
||||||
abortRun();
|
|
||||||
if (!abortWarnTimer) {
|
|
||||||
abortWarnTimer = setTimeout(() => {
|
|
||||||
if (!session.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 = session.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}`,
|
|
||||||
);
|
);
|
||||||
|
// Tool schemas must be provider-compatible (OpenAI requires top-level `type: "object"`).
|
||||||
|
// `createClawdbotCodingTools()` normalizes schemas so the session can pass them through unchanged.
|
||||||
|
const tools = createClawdbotCodingTools({
|
||||||
|
bash: {
|
||||||
|
...params.config?.agent?.bash,
|
||||||
|
elevated: params.bashElevated,
|
||||||
|
},
|
||||||
|
sandbox,
|
||||||
|
surface: params.surface,
|
||||||
|
sessionKey: params.sessionKey ?? params.sessionId,
|
||||||
|
config: params.config,
|
||||||
|
});
|
||||||
|
const machineName = await getMachineDisplayName();
|
||||||
|
const runtimeInfo = {
|
||||||
|
host: machineName,
|
||||||
|
os: `${os.type()} ${os.release()}`,
|
||||||
|
arch: os.arch(),
|
||||||
|
node: process.version,
|
||||||
|
model: `${provider}/${modelId}`,
|
||||||
|
};
|
||||||
|
const sandboxInfo = buildEmbeddedSandboxInfo(sandbox);
|
||||||
|
const reasoningTagHint = provider === "ollama";
|
||||||
|
const systemPrompt = buildSystemPrompt({
|
||||||
|
appendPrompt: buildAgentSystemPromptAppend({
|
||||||
|
workspaceDir: resolvedWorkspace,
|
||||||
|
defaultThinkLevel: thinkLevel,
|
||||||
|
extraSystemPrompt: params.extraSystemPrompt,
|
||||||
|
ownerNumbers: params.ownerNumbers,
|
||||||
|
reasoningTagHint,
|
||||||
|
runtimeInfo,
|
||||||
|
sandboxInfo,
|
||||||
|
toolNames: tools.map((tool) => tool.name),
|
||||||
|
}),
|
||||||
|
contextFiles,
|
||||||
|
skills: promptSkills,
|
||||||
|
cwd: resolvedWorkspace,
|
||||||
|
tools,
|
||||||
|
});
|
||||||
|
|
||||||
|
const sessionManager = SessionManager.open(params.sessionFile);
|
||||||
|
const settingsManager = SettingsManager.create(
|
||||||
|
resolvedWorkspace,
|
||||||
|
agentDir,
|
||||||
|
);
|
||||||
|
|
||||||
|
const { session } = await createAgentSession({
|
||||||
|
cwd: resolvedWorkspace,
|
||||||
|
agentDir,
|
||||||
|
authStorage,
|
||||||
|
modelRegistry,
|
||||||
|
model,
|
||||||
|
thinkingLevel,
|
||||||
|
systemPrompt,
|
||||||
|
// Custom tool set: extra bash/process + read image sanitization.
|
||||||
|
tools,
|
||||||
|
sessionManager,
|
||||||
|
settingsManager,
|
||||||
|
skills: promptSkills,
|
||||||
|
contextFiles,
|
||||||
|
});
|
||||||
|
|
||||||
|
const prior = await sanitizeSessionMessagesImages(
|
||||||
|
session.messages,
|
||||||
|
"session:history",
|
||||||
|
);
|
||||||
|
if (prior.length > 0) {
|
||||||
|
session.agent.replaceMessages(prior);
|
||||||
|
}
|
||||||
|
let aborted = Boolean(params.abortSignal?.aborted);
|
||||||
|
const abortRun = () => {
|
||||||
|
aborted = true;
|
||||||
|
void session.abort();
|
||||||
|
};
|
||||||
|
const queueHandle: EmbeddedPiQueueHandle = {
|
||||||
|
queueMessage: async (text: string) => {
|
||||||
|
await session.steer(text);
|
||||||
|
},
|
||||||
|
isStreaming: () => session.isStreaming,
|
||||||
|
abort: abortRun,
|
||||||
|
};
|
||||||
|
ACTIVE_EMBEDDED_RUNS.set(params.sessionId, queueHandle);
|
||||||
|
|
||||||
|
const {
|
||||||
|
assistantTexts,
|
||||||
|
toolMetas,
|
||||||
|
unsubscribe,
|
||||||
|
waitForCompactionRetry,
|
||||||
|
} = subscribeEmbeddedPiSession({
|
||||||
|
session,
|
||||||
|
runId: params.runId,
|
||||||
|
verboseLevel: params.verboseLevel,
|
||||||
|
shouldEmitToolResult: params.shouldEmitToolResult,
|
||||||
|
onToolResult: params.onToolResult,
|
||||||
|
onBlockReply: params.onBlockReply,
|
||||||
|
blockReplyBreak: params.blockReplyBreak,
|
||||||
|
blockReplyChunking: params.blockReplyChunking,
|
||||||
|
onPartialReply: params.onPartialReply,
|
||||||
|
onAgentEvent: params.onAgentEvent,
|
||||||
|
enforceFinalTag: params.enforceFinalTag,
|
||||||
|
});
|
||||||
|
|
||||||
|
let abortWarnTimer: NodeJS.Timeout | undefined;
|
||||||
|
const abortTimer = setTimeout(
|
||||||
|
() => {
|
||||||
|
log.warn(
|
||||||
|
`embedded run timeout: runId=${params.runId} sessionId=${params.sessionId} timeoutMs=${params.timeoutMs}`,
|
||||||
|
);
|
||||||
|
abortRun();
|
||||||
|
if (!abortWarnTimer) {
|
||||||
|
abortWarnTimer = setTimeout(() => {
|
||||||
|
if (!session.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 = session.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 {
|
try {
|
||||||
await session.prompt(params.prompt);
|
const promptStartedAt = Date.now();
|
||||||
} catch (err) {
|
|
||||||
promptError = err;
|
|
||||||
} finally {
|
|
||||||
log.debug(
|
log.debug(
|
||||||
`embedded run prompt end: runId=${params.runId} sessionId=${params.sessionId} durationMs=${Date.now() - promptStartedAt}`,
|
`embedded run prompt start: runId=${params.runId} sessionId=${params.sessionId}`,
|
||||||
);
|
);
|
||||||
|
try {
|
||||||
|
await session.prompt(params.prompt);
|
||||||
|
} catch (err) {
|
||||||
|
promptError = err;
|
||||||
|
} finally {
|
||||||
|
log.debug(
|
||||||
|
`embedded run prompt end: runId=${params.runId} sessionId=${params.sessionId} durationMs=${Date.now() - promptStartedAt}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
await waitForCompactionRetry();
|
||||||
|
messagesSnapshot = session.messages.slice();
|
||||||
|
sessionIdUsed = session.sessionId;
|
||||||
|
} finally {
|
||||||
|
clearTimeout(abortTimer);
|
||||||
|
if (abortWarnTimer) {
|
||||||
|
clearTimeout(abortWarnTimer);
|
||||||
|
abortWarnTimer = undefined;
|
||||||
|
}
|
||||||
|
unsubscribe();
|
||||||
|
if (ACTIVE_EMBEDDED_RUNS.get(params.sessionId) === queueHandle) {
|
||||||
|
ACTIVE_EMBEDDED_RUNS.delete(params.sessionId);
|
||||||
|
notifyEmbeddedRunEnded(params.sessionId);
|
||||||
|
}
|
||||||
|
session.dispose();
|
||||||
|
params.abortSignal?.removeEventListener?.("abort", onAbort);
|
||||||
}
|
}
|
||||||
await waitForCompactionRetry();
|
if (promptError && !aborted) {
|
||||||
messagesSnapshot = session.messages.slice();
|
const fallbackThinking = pickFallbackThinkingLevel({
|
||||||
sessionIdUsed = session.sessionId;
|
message:
|
||||||
} finally {
|
promptError instanceof Error
|
||||||
clearTimeout(abortTimer);
|
? promptError.message
|
||||||
if (abortWarnTimer) {
|
: String(promptError),
|
||||||
clearTimeout(abortWarnTimer);
|
attempted: attemptedThinking,
|
||||||
abortWarnTimer = undefined;
|
});
|
||||||
|
if (fallbackThinking) {
|
||||||
|
log.warn(
|
||||||
|
`unsupported thinking level for ${provider}/${modelId}; retrying with ${fallbackThinking}`,
|
||||||
|
);
|
||||||
|
thinkLevel = fallbackThinking;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
throw promptError;
|
||||||
}
|
}
|
||||||
unsubscribe();
|
|
||||||
if (ACTIVE_EMBEDDED_RUNS.get(params.sessionId) === queueHandle) {
|
const lastAssistant = messagesSnapshot
|
||||||
ACTIVE_EMBEDDED_RUNS.delete(params.sessionId);
|
.slice()
|
||||||
notifyEmbeddedRunEnded(params.sessionId);
|
.reverse()
|
||||||
}
|
.find((m) => (m as AgentMessage)?.role === "assistant") as
|
||||||
session.dispose();
|
| AssistantMessage
|
||||||
params.abortSignal?.removeEventListener?.("abort", onAbort);
|
| undefined;
|
||||||
}
|
|
||||||
if (promptError && !aborted) {
|
|
||||||
const fallbackThinking = pickFallbackThinkingLevel({
|
const fallbackThinking = pickFallbackThinkingLevel({
|
||||||
message:
|
message: lastAssistant?.errorMessage,
|
||||||
promptError instanceof Error
|
|
||||||
? promptError.message
|
|
||||||
: String(promptError),
|
|
||||||
attempted: attemptedThinking,
|
attempted: attemptedThinking,
|
||||||
});
|
});
|
||||||
if (fallbackThinking) {
|
if (fallbackThinking && !aborted) {
|
||||||
log.warn(
|
log.warn(
|
||||||
`unsupported thinking level for ${provider}/${modelId}; retrying with ${fallbackThinking}`,
|
`unsupported thinking level for ${provider}/${modelId}; retrying with ${fallbackThinking}`,
|
||||||
);
|
);
|
||||||
thinkLevel = fallbackThinking;
|
thinkLevel = fallbackThinking;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
throw promptError;
|
|
||||||
}
|
|
||||||
|
|
||||||
const lastAssistant = messagesSnapshot
|
const fallbackConfigured =
|
||||||
.slice()
|
(params.config?.agent?.modelFallbacks?.length ?? 0) > 0;
|
||||||
.reverse()
|
if (fallbackConfigured && isRateLimitAssistantError(lastAssistant)) {
|
||||||
.find((m) => (m as AgentMessage)?.role === "assistant") as
|
const message =
|
||||||
| AssistantMessage
|
lastAssistant?.errorMessage?.trim() ||
|
||||||
| undefined;
|
(lastAssistant ? formatAssistantErrorText(lastAssistant) : "") ||
|
||||||
|
"LLM request rate limited.";
|
||||||
const fallbackThinking = pickFallbackThinkingLevel({
|
throw new Error(message);
|
||||||
message: lastAssistant?.errorMessage,
|
|
||||||
attempted: attemptedThinking,
|
|
||||||
});
|
|
||||||
if (fallbackThinking && !aborted) {
|
|
||||||
log.warn(
|
|
||||||
`unsupported thinking level for ${provider}/${modelId}; retrying with ${fallbackThinking}`,
|
|
||||||
);
|
|
||||||
thinkLevel = fallbackThinking;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const fallbackConfigured =
|
|
||||||
(params.config?.agent?.modelFallbacks?.length ?? 0) > 0;
|
|
||||||
if (fallbackConfigured && isRateLimitAssistantError(lastAssistant)) {
|
|
||||||
const message =
|
|
||||||
lastAssistant?.errorMessage?.trim() ||
|
|
||||||
(lastAssistant ? formatAssistantErrorText(lastAssistant) : "") ||
|
|
||||||
"LLM request rate limited.";
|
|
||||||
throw new Error(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
const usage = lastAssistant?.usage;
|
|
||||||
const agentMeta: EmbeddedPiAgentMeta = {
|
|
||||||
sessionId: sessionIdUsed,
|
|
||||||
provider: lastAssistant?.provider ?? provider,
|
|
||||||
model: lastAssistant?.model ?? model.id,
|
|
||||||
usage: usage
|
|
||||||
? {
|
|
||||||
input: usage.input,
|
|
||||||
output: usage.output,
|
|
||||||
cacheRead: usage.cacheRead,
|
|
||||||
cacheWrite: usage.cacheWrite,
|
|
||||||
total: usage.totalTokens,
|
|
||||||
}
|
|
||||||
: undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
const replyItems: Array<{ text: string; media?: string[] }> = [];
|
|
||||||
|
|
||||||
const errorText = lastAssistant
|
|
||||||
? formatAssistantErrorText(lastAssistant)
|
|
||||||
: undefined;
|
|
||||||
if (errorText) replyItems.push({ text: errorText });
|
|
||||||
|
|
||||||
const inlineToolResults =
|
|
||||||
params.verboseLevel === "on" &&
|
|
||||||
!params.onPartialReply &&
|
|
||||||
!params.onToolResult &&
|
|
||||||
toolMetas.length > 0;
|
|
||||||
if (inlineToolResults) {
|
|
||||||
for (const { toolName, meta } of toolMetas) {
|
|
||||||
const agg = formatToolAggregate(toolName, meta ? [meta] : []);
|
|
||||||
const { text: cleanedText, mediaUrls } = splitMediaFromOutput(agg);
|
|
||||||
if (cleanedText)
|
|
||||||
replyItems.push({ text: cleanedText, media: mediaUrls });
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
for (const text of assistantTexts.length
|
const usage = lastAssistant?.usage;
|
||||||
? assistantTexts
|
const agentMeta: EmbeddedPiAgentMeta = {
|
||||||
: lastAssistant
|
sessionId: sessionIdUsed,
|
||||||
? [extractAssistantText(lastAssistant)]
|
provider: lastAssistant?.provider ?? provider,
|
||||||
: []) {
|
model: lastAssistant?.model ?? model.id,
|
||||||
const { text: cleanedText, mediaUrls } = splitMediaFromOutput(text);
|
usage: usage
|
||||||
if (!cleanedText && (!mediaUrls || mediaUrls.length === 0)) continue;
|
? {
|
||||||
replyItems.push({ text: cleanedText, media: mediaUrls });
|
input: usage.input,
|
||||||
}
|
output: usage.output,
|
||||||
|
cacheRead: usage.cacheRead,
|
||||||
|
cacheWrite: usage.cacheWrite,
|
||||||
|
total: usage.totalTokens,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
const payloads = replyItems
|
const replyItems: Array<{ text: string; media?: string[] }> = [];
|
||||||
.map((item) => ({
|
|
||||||
text: item.text?.trim() ? item.text.trim() : undefined,
|
const errorText = lastAssistant
|
||||||
mediaUrls: item.media?.length ? item.media : undefined,
|
? formatAssistantErrorText(lastAssistant)
|
||||||
mediaUrl: item.media?.[0],
|
: undefined;
|
||||||
}))
|
if (errorText) replyItems.push({ text: errorText });
|
||||||
.filter(
|
|
||||||
(p) =>
|
const inlineToolResults =
|
||||||
p.text || p.mediaUrl || (p.mediaUrls && p.mediaUrls.length > 0),
|
params.verboseLevel === "on" &&
|
||||||
|
!params.onPartialReply &&
|
||||||
|
!params.onToolResult &&
|
||||||
|
toolMetas.length > 0;
|
||||||
|
if (inlineToolResults) {
|
||||||
|
for (const { toolName, meta } of toolMetas) {
|
||||||
|
const agg = formatToolAggregate(toolName, meta ? [meta] : []);
|
||||||
|
const { text: cleanedText, mediaUrls } =
|
||||||
|
splitMediaFromOutput(agg);
|
||||||
|
if (cleanedText)
|
||||||
|
replyItems.push({ text: cleanedText, media: mediaUrls });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const text of assistantTexts.length
|
||||||
|
? assistantTexts
|
||||||
|
: lastAssistant
|
||||||
|
? [extractAssistantText(lastAssistant)]
|
||||||
|
: []) {
|
||||||
|
const { text: cleanedText, mediaUrls } = splitMediaFromOutput(text);
|
||||||
|
if (!cleanedText && (!mediaUrls || mediaUrls.length === 0))
|
||||||
|
continue;
|
||||||
|
replyItems.push({ text: cleanedText, media: mediaUrls });
|
||||||
|
}
|
||||||
|
|
||||||
|
const payloads = replyItems
|
||||||
|
.map((item) => ({
|
||||||
|
text: item.text?.trim() ? item.text.trim() : undefined,
|
||||||
|
mediaUrls: item.media?.length ? item.media : undefined,
|
||||||
|
mediaUrl: item.media?.[0],
|
||||||
|
}))
|
||||||
|
.filter(
|
||||||
|
(p) =>
|
||||||
|
p.text || p.mediaUrl || (p.mediaUrls && p.mediaUrls.length > 0),
|
||||||
|
);
|
||||||
|
|
||||||
|
log.debug(
|
||||||
|
`embedded run done: runId=${params.runId} sessionId=${params.sessionId} durationMs=${Date.now() - started} aborted=${aborted}`,
|
||||||
);
|
);
|
||||||
|
return {
|
||||||
log.debug(
|
payloads: payloads.length ? payloads : undefined,
|
||||||
`embedded run done: runId=${params.runId} sessionId=${params.sessionId} durationMs=${Date.now() - started} aborted=${aborted}`,
|
meta: {
|
||||||
);
|
durationMs: Date.now() - started,
|
||||||
return {
|
agentMeta,
|
||||||
payloads: payloads.length ? payloads : undefined,
|
aborted,
|
||||||
meta: {
|
},
|
||||||
durationMs: Date.now() - started,
|
};
|
||||||
agentMeta,
|
|
||||||
aborted,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
} finally {
|
} finally {
|
||||||
restoreSkillEnv?.();
|
restoreSkillEnv?.();
|
||||||
process.chdir(prevCwd);
|
process.chdir(prevCwd);
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
|
import { getEnvApiKey } from "@mariozechner/pi-ai";
|
||||||
|
import { discoverAuthStorage } from "@mariozechner/pi-coding-agent";
|
||||||
|
import { resolveClawdbotAgentDir } from "../../agents/agent-paths.js";
|
||||||
import type { ClawdbotConfig } from "../../config/config.js";
|
import type { ClawdbotConfig } from "../../config/config.js";
|
||||||
|
import { resolveOAuthPath } from "../../config/paths.js";
|
||||||
import {
|
import {
|
||||||
type SessionEntry,
|
type SessionEntry,
|
||||||
type SessionScope,
|
type SessionScope,
|
||||||
@@ -12,10 +15,6 @@ import { resolveSendPolicy } from "../../sessions/send-policy.js";
|
|||||||
import { normalizeE164 } from "../../utils.js";
|
import { normalizeE164 } from "../../utils.js";
|
||||||
import { resolveHeartbeatSeconds } from "../../web/reconnect.js";
|
import { resolveHeartbeatSeconds } from "../../web/reconnect.js";
|
||||||
import { getWebAuthAgeMs, webAuthExists } from "../../web/session.js";
|
import { getWebAuthAgeMs, webAuthExists } from "../../web/session.js";
|
||||||
import { resolveClawdbotAgentDir } from "../../agents/agent-paths.js";
|
|
||||||
import { resolveOAuthPath } from "../../config/paths.js";
|
|
||||||
import { getEnvApiKey } from "@mariozechner/pi-ai";
|
|
||||||
import { discoverAuthStorage } from "@mariozechner/pi-coding-agent";
|
|
||||||
import {
|
import {
|
||||||
normalizeGroupActivation,
|
normalizeGroupActivation,
|
||||||
parseActivationCommand,
|
parseActivationCommand,
|
||||||
@@ -61,7 +60,8 @@ function hasOAuthCredentials(provider: string): boolean {
|
|||||||
if (!entry) return false;
|
if (!entry) return false;
|
||||||
const refresh =
|
const refresh =
|
||||||
entry.refresh ?? entry.refresh_token ?? entry.refreshToken ?? "";
|
entry.refresh ?? entry.refresh_token ?? entry.refreshToken ?? "";
|
||||||
const access = entry.access ?? entry.access_token ?? entry.accessToken ?? "";
|
const access =
|
||||||
|
entry.access ?? entry.access_token ?? entry.accessToken ?? "";
|
||||||
return Boolean(refresh.trim() && access.trim());
|
return Boolean(refresh.trim() && access.trim());
|
||||||
} catch {
|
} catch {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
Reference in New Issue
Block a user