mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-18 19:54:46 +00:00
refactor: centralize reply prompt envelope
This commit is contained in:
@@ -49,6 +49,7 @@ Docs: https://docs.openclaw.ai
|
||||
### Fixes
|
||||
|
||||
- OpenAI Codex: surface browser OAuth and device-code login failures instead of treating failed logins as empty successful auth results. Refs #80363.
|
||||
- CLI agents: carry runtime-only current-turn sender/reply context into CLI model prompts while keeping prompt-build hook input and transcript text clean.
|
||||
- fix(matrix): gate name-based allowlist resolution [AI]. (#79007) Thanks @pgondhi987.
|
||||
- Slack: include the bot's own root/parent message in new thread sessions so in-thread replies reach the agent with the parent text the user is responding to, instead of only `reply_to_id` metadata. Fixes #79338. Thanks @sxxtony.
|
||||
- Docker: keep image builds on the source pnpm workspace policy so pnpm 11 can prune production dependencies without a Docker-only workspace rewrite.
|
||||
|
||||
@@ -339,6 +339,55 @@ describe("shouldSkipLocalCliCredentialEpoch", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("prepends current-turn context after prompt-build hooks without changing hook or transcript prompt", async () => {
|
||||
const { dir, sessionFile } = createSessionFile();
|
||||
try {
|
||||
const hookRunner = {
|
||||
hasHooks: vi.fn((hookName: string) => hookName === "before_prompt_build"),
|
||||
runBeforePromptBuild: vi.fn(async () => ({
|
||||
prependContext: "trusted hook context",
|
||||
appendContext: "trusted hook tail",
|
||||
})),
|
||||
runBeforeAgentStart: vi.fn(),
|
||||
};
|
||||
mockGetGlobalHookRunner.mockReturnValue(hookRunner as never);
|
||||
|
||||
const context = await prepareCliRunContext({
|
||||
sessionId: "session-test",
|
||||
sessionKey: "agent:main:test",
|
||||
agentId: "main",
|
||||
trigger: "user",
|
||||
sessionFile,
|
||||
workspaceDir: dir,
|
||||
prompt: "latest ask",
|
||||
transcriptPrompt: "latest ask",
|
||||
currentTurnContext: {
|
||||
text: "Sender (untrusted metadata):\nsender_id=U123",
|
||||
promptJoiner: " ",
|
||||
},
|
||||
provider: "test-cli",
|
||||
model: "test-model",
|
||||
timeoutMs: 1_000,
|
||||
runId: "run-test-context",
|
||||
config: createCliBackendConfig(),
|
||||
});
|
||||
|
||||
expect(context.params.prompt).toBe(
|
||||
"Sender (untrusted metadata):\nsender_id=U123 trusted hook context\n\nlatest ask\n\ntrusted hook tail",
|
||||
);
|
||||
expect(context.params.transcriptPrompt).toBe("latest ask");
|
||||
expect(hookRunner.runBeforePromptBuild).toHaveBeenCalledTimes(1);
|
||||
const beforePromptBuildCalls = hookRunner.runBeforePromptBuild.mock.calls as unknown as Array<
|
||||
[unknown, unknown]
|
||||
>;
|
||||
expect(beforePromptBuildCalls[0]?.[0]).toMatchObject({
|
||||
prompt: "latest ask",
|
||||
});
|
||||
} finally {
|
||||
fs.rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("marks inter-session prompts after CLI prompt-build hook context is applied", async () => {
|
||||
const { dir, sessionFile } = createSessionFile();
|
||||
try {
|
||||
|
||||
@@ -39,6 +39,7 @@ import {
|
||||
import { resolvePromptBuildHookResult } from "../pi-embedded-runner/run/attempt.prompt-helpers.js";
|
||||
import { resolveAttemptPrependSystemContext } from "../pi-embedded-runner/run/attempt.prompt-helpers.js";
|
||||
import { composeSystemPromptWithHookContext } from "../pi-embedded-runner/run/attempt.thread-helpers.js";
|
||||
import { buildCurrentTurnPrompt } from "../pi-embedded-runner/run/runtime-context-prompt.js";
|
||||
import { applyPluginTextReplacements } from "../plugin-text-transforms.js";
|
||||
import { resolveSkillsPromptForRun } from "../skills.js";
|
||||
import { resolveSystemPromptOverride } from "../system-prompt-override.js";
|
||||
@@ -405,6 +406,10 @@ export async function prepareCliRunContext(
|
||||
} catch (error) {
|
||||
cliBackendLog.warn(`cli prompt-build hook preparation failed: ${String(error)}`);
|
||||
}
|
||||
preparedPrompt = buildCurrentTurnPrompt({
|
||||
context: params.currentTurnContext,
|
||||
prompt: preparedPrompt,
|
||||
});
|
||||
preparedPrompt = annotateInterSessionPromptText(preparedPrompt, params.inputProvenance);
|
||||
const allowRawTranscriptReseed =
|
||||
backendResolved.config.reseedFromRawTranscriptWhenUncompacted === true;
|
||||
|
||||
@@ -9,7 +9,10 @@ import type { OpenClawConfig } from "../../config/types.openclaw.js";
|
||||
import type { PromptImageOrderEntry } from "../../media/prompt-image-order.js";
|
||||
import type { InputProvenance } from "../../sessions/input-provenance.js";
|
||||
import type { ResolvedCliBackend } from "../cli-backends.js";
|
||||
import type { EmbeddedRunTrigger } from "../pi-embedded-runner/run/params.js";
|
||||
import type {
|
||||
CurrentTurnPromptContext,
|
||||
EmbeddedRunTrigger,
|
||||
} from "../pi-embedded-runner/run/params.js";
|
||||
import type { SkillSnapshot } from "../skills.js";
|
||||
import type { SilentReplyPromptMode } from "../system-prompt.types.js";
|
||||
|
||||
@@ -23,6 +26,8 @@ export type RunCliAgentParams = {
|
||||
config?: OpenClawConfig;
|
||||
prompt: string;
|
||||
transcriptPrompt?: string;
|
||||
/** Runtime-only current-turn context visible to the model but excluded from transcript text. */
|
||||
currentTurnContext?: CurrentTurnPromptContext;
|
||||
inputProvenance?: InputProvenance;
|
||||
provider: string;
|
||||
model?: string;
|
||||
|
||||
@@ -1558,6 +1558,7 @@ export async function runAgentTurnWithFallback(params: {
|
||||
config: runtimeConfig,
|
||||
prompt: params.commandBody,
|
||||
transcriptPrompt: params.transcriptCommandBody,
|
||||
currentTurnContext: params.followupRun.currentTurnContext,
|
||||
inputProvenance: params.followupRun.run.inputProvenance,
|
||||
provider: cliExecutionProvider,
|
||||
model,
|
||||
|
||||
@@ -4,7 +4,6 @@ import type { ExecToolDefaults } from "../../agents/bash-tools.js";
|
||||
import { resolveFastModeState } from "../../agents/fast-mode.js";
|
||||
import { resolveAgentHarnessPolicy } from "../../agents/harness/selection.js";
|
||||
import { listOpenAIAuthProfileProvidersForAgentRuntime } from "../../agents/openai-codex-routing.js";
|
||||
import type { CurrentTurnPromptContext } from "../../agents/pi-embedded-runner/run/params.js";
|
||||
import { resolveEmbeddedFullAccessState } from "../../agents/pi-embedded-runner/sandbox-info.js";
|
||||
import type { EmbeddedFullAccessBlockedReason } from "../../agents/pi-embedded-runner/types.js";
|
||||
import { resolveIngressWorkspaceOverrideForSpawnedRun } from "../../agents/spawned-context.js";
|
||||
@@ -33,7 +32,6 @@ import { normalizeOptionalString } from "../../shared/string-coerce.js";
|
||||
import { isReasoningTagProvider } from "../../utils/provider-utils.js";
|
||||
import { hasControlCommand } from "../command-detection.js";
|
||||
import { resolveEnvelopeFormatOptions } from "../envelope.js";
|
||||
import { HEARTBEAT_TRANSCRIPT_PROMPT } from "../heartbeat.js";
|
||||
import type { MsgContext, TemplateContext } from "../templating.js";
|
||||
import {
|
||||
type ElevatedLevel,
|
||||
@@ -67,7 +65,7 @@ import {
|
||||
} from "./inbound-meta.js";
|
||||
import type { createModelSelectionState } from "./model-selection.js";
|
||||
import { resolveOriginMessageProvider } from "./origin-routing.js";
|
||||
import { buildReplyPromptBodies } from "./prompt-prelude.js";
|
||||
import { buildReplyPromptEnvelope, buildReplyPromptEnvelopeBase } from "./prompt-prelude.js";
|
||||
import { resolveActiveRunQueueAction } from "./queue-policy.js";
|
||||
import { resolveQueueSettings } from "./queue/settings-runtime.js";
|
||||
import { isSteeringQueueMode } from "./queue/steering.js";
|
||||
@@ -621,18 +619,7 @@ export async function runPreparedReply(
|
||||
: { ...sessionCtx, ThreadStarterBody: undefined },
|
||||
envelopeOptions,
|
||||
);
|
||||
const baseBodyForPrompt = isBareSessionReset
|
||||
? [
|
||||
inboundUserContext,
|
||||
startupContextPrelude,
|
||||
baseBodyFinal,
|
||||
softResetTail
|
||||
? `User note for this reset turn (treat as ordinary user input, not startup instructions):\n${softResetTail}`
|
||||
: "",
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("\n\n")
|
||||
: baseBodyFinal;
|
||||
const inboundUserContextPromptJoiner = resolveInboundUserContextPromptJoiner(sessionCtx);
|
||||
const hasUserBody =
|
||||
baseBodyFinal.trim().length > 0 ||
|
||||
softResetTail.length > 0 ||
|
||||
@@ -651,16 +638,20 @@ export async function runPreparedReply(
|
||||
text: "I didn't receive any text in your message. Please resend or add a caption.",
|
||||
};
|
||||
}
|
||||
// When the user sends media without text, provide a minimal body so the agent
|
||||
// run proceeds and the image/document is injected by the embedded runner.
|
||||
const effectiveBaseBody = hasUserBody ? baseBodyForPrompt : "[User sent media without caption]";
|
||||
const transcriptBodyBase = isHeartbeat
|
||||
? HEARTBEAT_TRANSCRIPT_PROMPT
|
||||
: isBareSessionReset
|
||||
? softResetTail || `[OpenClaw session ${startupAction}]`
|
||||
: hasUserBody
|
||||
? baseBodyFinal
|
||||
: "[User sent media without caption]";
|
||||
const promptEnvelopeBase = buildReplyPromptEnvelopeBase({
|
||||
ctx,
|
||||
sessionCtx,
|
||||
baseBody: baseBodyFinal,
|
||||
hasUserBody,
|
||||
inboundUserContext,
|
||||
inboundUserContextPromptJoiner,
|
||||
isBareSessionReset,
|
||||
startupAction,
|
||||
startupContextPrelude,
|
||||
softResetTail,
|
||||
isHeartbeat,
|
||||
});
|
||||
const effectiveBaseBody = promptEnvelopeBase.effectiveBaseBody;
|
||||
let prefixedBodyBase = await applySessionHints({
|
||||
baseBody: effectiveBaseBody,
|
||||
abortedLastRun,
|
||||
@@ -701,6 +692,7 @@ export async function runPreparedReply(
|
||||
prefixedCommandBody: string;
|
||||
queuedBody: string;
|
||||
transcriptCommandBody: string;
|
||||
currentTurnContext?: typeof promptEnvelopeBase.currentTurnContext;
|
||||
}> => {
|
||||
if (!useFastReplyRuntime) {
|
||||
const eventsBlock = await drainFormattedSystemEvents({
|
||||
@@ -716,12 +708,19 @@ export async function runPreparedReply(
|
||||
}
|
||||
}
|
||||
}
|
||||
return buildReplyPromptBodies({
|
||||
return buildReplyPromptEnvelope({
|
||||
ctx,
|
||||
sessionCtx,
|
||||
effectiveBaseBody,
|
||||
baseBody: baseBodyFinal,
|
||||
prefixedBody: prefixedBodyCore,
|
||||
transcriptBody: transcriptBodyBase,
|
||||
hasUserBody,
|
||||
inboundUserContext,
|
||||
inboundUserContextPromptJoiner,
|
||||
isBareSessionReset,
|
||||
startupAction,
|
||||
startupContextPrelude,
|
||||
softResetTail,
|
||||
isHeartbeat,
|
||||
threadContextNote,
|
||||
systemEventBlocks: drainedSystemEventBlocks,
|
||||
});
|
||||
@@ -750,17 +749,8 @@ export async function runPreparedReply(
|
||||
sessionEntry = skillResult.sessionEntry ?? sessionEntry;
|
||||
currentSystemSent = skillResult.systemSent;
|
||||
const skillsSnapshot = skillResult.skillsSnapshot;
|
||||
let { prefixedCommandBody, queuedBody, transcriptCommandBody } = await traceRunPhase(
|
||||
"reply.build_prompt_bodies",
|
||||
() => rebuildPromptBodies(),
|
||||
);
|
||||
const currentTurnContext: CurrentTurnPromptContext | undefined =
|
||||
!isBareSessionReset && inboundUserContext.trim()
|
||||
? {
|
||||
text: inboundUserContext,
|
||||
promptJoiner: resolveInboundUserContextPromptJoiner(sessionCtx),
|
||||
}
|
||||
: undefined;
|
||||
let { prefixedCommandBody, queuedBody, transcriptCommandBody, currentTurnContext } =
|
||||
await traceRunPhase("reply.build_prompt_bodies", () => rebuildPromptBodies());
|
||||
if (!resolvedThinkLevel) {
|
||||
resolvedThinkLevel = await modelState.resolveDefaultThinkingLevel();
|
||||
}
|
||||
@@ -957,10 +947,8 @@ export async function runPreparedReply(
|
||||
isNewSession,
|
||||
});
|
||||
preparedSessionState = resolvePreparedSessionState();
|
||||
({ prefixedCommandBody, queuedBody, transcriptCommandBody } = await traceRunPhase(
|
||||
"reply.build_prompt_bodies",
|
||||
() => rebuildPromptBodies(),
|
||||
));
|
||||
({ prefixedCommandBody, queuedBody, transcriptCommandBody, currentTurnContext } =
|
||||
await traceRunPhase("reply.build_prompt_bodies", () => rebuildPromptBodies()));
|
||||
},
|
||||
resolveBusyState: resolveQueueBusyState,
|
||||
});
|
||||
|
||||
86
src/auto-reply/reply/prompt-prelude.test.ts
Normal file
86
src/auto-reply/reply/prompt-prelude.test.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { finalizeInboundContext } from "./inbound-context.js";
|
||||
import { buildReplyPromptEnvelope } from "./prompt-prelude.js";
|
||||
|
||||
describe("buildReplyPromptEnvelope", () => {
|
||||
it("keeps bare reset runtime context in the model prompt and out of transcript/current-turn context", () => {
|
||||
const sessionCtx = finalizeInboundContext({
|
||||
Body: "",
|
||||
BodyStripped: "",
|
||||
Provider: "telegram",
|
||||
ChatType: "direct",
|
||||
SenderId: "telegram-user-1",
|
||||
});
|
||||
|
||||
const envelope = buildReplyPromptEnvelope({
|
||||
ctx: sessionCtx,
|
||||
sessionCtx,
|
||||
baseBody: "A new session was started via /new or /reset.",
|
||||
hasUserBody: true,
|
||||
inboundUserContext: "Conversation info (untrusted metadata):\nsender_id=telegram-user-1",
|
||||
isBareSessionReset: true,
|
||||
startupAction: "reset",
|
||||
startupContextPrelude: "Startup context",
|
||||
});
|
||||
|
||||
expect(envelope.prefixedCommandBody).toContain("sender_id=telegram-user-1");
|
||||
expect(envelope.prefixedCommandBody).toContain("Startup context");
|
||||
expect(envelope.transcriptCommandBody).toBe("[OpenClaw session reset]");
|
||||
expect(envelope.currentTurnContext).toBeUndefined();
|
||||
});
|
||||
|
||||
it("keeps ordinary inbound context runtime-only while preserving transcript text", () => {
|
||||
const sessionCtx = finalizeInboundContext({
|
||||
Body: "what changed?",
|
||||
BodyStripped: "what changed?",
|
||||
Provider: "slack",
|
||||
ChatType: "group",
|
||||
});
|
||||
|
||||
const envelope = buildReplyPromptEnvelope({
|
||||
ctx: sessionCtx,
|
||||
sessionCtx,
|
||||
baseBody: "what changed?",
|
||||
prefixedBody: "what changed?",
|
||||
hasUserBody: true,
|
||||
inboundUserContext: "Current message:\nchat_id=C123",
|
||||
inboundUserContextPromptJoiner: " ",
|
||||
isBareSessionReset: false,
|
||||
startupAction: "new",
|
||||
});
|
||||
|
||||
expect(envelope.prefixedCommandBody).toBe("what changed?");
|
||||
expect(envelope.transcriptCommandBody).toBe("what changed?");
|
||||
expect(envelope.currentTurnContext).toEqual({
|
||||
text: "Current message:\nchat_id=C123",
|
||||
promptJoiner: " ",
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps soft reset user notes visible without leaking startup context into transcripts", () => {
|
||||
const sessionCtx = finalizeInboundContext({
|
||||
Body: "",
|
||||
BodyStripped: "",
|
||||
Provider: "slack",
|
||||
ChatType: "direct",
|
||||
});
|
||||
|
||||
const envelope = buildReplyPromptEnvelope({
|
||||
ctx: sessionCtx,
|
||||
sessionCtx,
|
||||
baseBody: "",
|
||||
hasUserBody: true,
|
||||
inboundUserContext: "Sender (untrusted metadata):\nsender_id=U123",
|
||||
isBareSessionReset: true,
|
||||
startupAction: "reset",
|
||||
startupContextPrelude: "Startup context",
|
||||
softResetTail: "re-read persona files",
|
||||
});
|
||||
|
||||
expect(envelope.prefixedCommandBody).toContain("Sender (untrusted metadata):");
|
||||
expect(envelope.prefixedCommandBody).toContain("Startup context");
|
||||
expect(envelope.prefixedCommandBody).toContain("re-read persona files");
|
||||
expect(envelope.transcriptCommandBody).toBe("re-read persona files");
|
||||
expect(envelope.transcriptCommandBody).not.toContain("Startup context");
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,6 @@
|
||||
import type { CurrentTurnPromptContext } from "../../agents/pi-embedded-runner/run/params.js";
|
||||
import { annotateInterSessionPromptText } from "../../sessions/input-provenance.js";
|
||||
import { HEARTBEAT_TRANSCRIPT_PROMPT } from "../heartbeat.js";
|
||||
import { buildInboundMediaNote } from "../media-note.js";
|
||||
import type { MsgContext, TemplateContext } from "../templating.js";
|
||||
import { appendUntrustedContext } from "./untrusted-context.js";
|
||||
@@ -10,7 +12,7 @@ export function buildReplyPromptBodies(params: {
|
||||
ctx: MsgContext;
|
||||
sessionCtx: TemplateContext;
|
||||
effectiveBaseBody: string;
|
||||
prefixedBody: string;
|
||||
prefixedBody?: string;
|
||||
transcriptBody?: string;
|
||||
threadContextNote?: string;
|
||||
systemEventBlocks?: string[];
|
||||
@@ -24,9 +26,10 @@ export function buildReplyPromptBodies(params: {
|
||||
const combinedEventsBlock = (params.systemEventBlocks ?? []).filter(Boolean).join("\n");
|
||||
const prependEvents = (body: string) =>
|
||||
combinedEventsBlock ? `${combinedEventsBlock}\n\n${body}` : body;
|
||||
const rawPrefixedBody = params.prefixedBody ?? params.effectiveBaseBody;
|
||||
const bodyWithEvents = prependEvents(params.effectiveBaseBody);
|
||||
const prefixedBodyWithEvents = appendUntrustedContext(
|
||||
prependEvents(params.prefixedBody),
|
||||
prependEvents(rawPrefixedBody),
|
||||
params.sessionCtx.UntrustedContext,
|
||||
);
|
||||
const prefixedBody = [params.threadContextNote, prefixedBodyWithEvents]
|
||||
@@ -59,3 +62,103 @@ export function buildReplyPromptBodies(params: {
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
export type ReplyPromptEnvelopeStartupAction = "new" | "reset";
|
||||
|
||||
export type ReplyPromptEnvelope = ReturnType<typeof buildReplyPromptBodies> & {
|
||||
/** Model-visible body before media, thread context, and inter-session annotation are applied. */
|
||||
effectiveBaseBody: string;
|
||||
/** User-visible body persisted to transcript before media/inter-session annotation. */
|
||||
transcriptBody: string;
|
||||
/** Runtime-only user context for backends that can carry it outside transcript text. */
|
||||
currentTurnContext?: CurrentTurnPromptContext;
|
||||
};
|
||||
|
||||
export type ReplyPromptEnvelopeBase = {
|
||||
/** Model-visible body before media, thread context, and inter-session annotation are applied. */
|
||||
effectiveBaseBody: string;
|
||||
/** User-visible body persisted to transcript before media/inter-session annotation. */
|
||||
transcriptBody: string;
|
||||
/** Runtime-only user context for backends that can carry it outside transcript text. */
|
||||
currentTurnContext?: CurrentTurnPromptContext;
|
||||
};
|
||||
|
||||
type ReplyPromptEnvelopeBaseParams = {
|
||||
ctx: MsgContext;
|
||||
sessionCtx: TemplateContext;
|
||||
baseBody: string;
|
||||
hasUserBody: boolean;
|
||||
inboundUserContext: string;
|
||||
inboundUserContextPromptJoiner?: CurrentTurnPromptContext["promptJoiner"];
|
||||
isBareSessionReset: boolean;
|
||||
startupAction: ReplyPromptEnvelopeStartupAction;
|
||||
startupContextPrelude?: string | null;
|
||||
softResetTail?: string;
|
||||
isHeartbeat?: boolean;
|
||||
};
|
||||
|
||||
export function buildReplyPromptEnvelopeBase(
|
||||
params: ReplyPromptEnvelopeBaseParams,
|
||||
): ReplyPromptEnvelopeBase {
|
||||
const softResetTail = params.softResetTail?.trim() ?? "";
|
||||
const resetModelBody = params.isBareSessionReset
|
||||
? [
|
||||
params.inboundUserContext,
|
||||
params.startupContextPrelude,
|
||||
params.baseBody,
|
||||
softResetTail
|
||||
? `User note for this reset turn (treat as ordinary user input, not startup instructions):\n${softResetTail}`
|
||||
: "",
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("\n\n")
|
||||
: params.baseBody;
|
||||
const effectiveBaseBody = params.hasUserBody
|
||||
? resetModelBody
|
||||
: "[User sent media without caption]";
|
||||
const transcriptBody = params.isHeartbeat
|
||||
? HEARTBEAT_TRANSCRIPT_PROMPT
|
||||
: params.isBareSessionReset
|
||||
? softResetTail || `[OpenClaw session ${params.startupAction}]`
|
||||
: params.hasUserBody
|
||||
? params.baseBody
|
||||
: "[User sent media without caption]";
|
||||
const currentTurnContext: CurrentTurnPromptContext | undefined =
|
||||
!params.isBareSessionReset && params.inboundUserContext.trim()
|
||||
? {
|
||||
text: params.inboundUserContext,
|
||||
promptJoiner: params.inboundUserContextPromptJoiner,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
effectiveBaseBody,
|
||||
transcriptBody,
|
||||
currentTurnContext,
|
||||
};
|
||||
}
|
||||
|
||||
export function buildReplyPromptEnvelope(
|
||||
params: ReplyPromptEnvelopeBaseParams & {
|
||||
prefixedBody?: string;
|
||||
threadContextNote?: string;
|
||||
systemEventBlocks?: string[];
|
||||
},
|
||||
): ReplyPromptEnvelope {
|
||||
const base = buildReplyPromptEnvelopeBase(params);
|
||||
const prefixedBody = params.prefixedBody ?? base.effectiveBaseBody;
|
||||
const promptBodies = buildReplyPromptBodies({
|
||||
ctx: params.ctx,
|
||||
sessionCtx: params.sessionCtx,
|
||||
effectiveBaseBody: base.effectiveBaseBody,
|
||||
prefixedBody,
|
||||
transcriptBody: base.transcriptBody,
|
||||
threadContextNote: params.threadContextNote,
|
||||
systemEventBlocks: params.systemEventBlocks,
|
||||
});
|
||||
|
||||
return {
|
||||
...promptBodies,
|
||||
...base,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user