mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-29 08:12:56 +00:00
feat: app conversations through the memory_id query parameter (#12025)
* feat: app conversations through the memory_id query parameter, chat-mode flows included Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore: regenerate the mcp session instructions Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: type backendAsync's memoryId option in the eval diagnostics Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore: pin git-sync hub scripts to windmill-cli 1.849.0 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * feat: memoryId option on the sync backend call too Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore: bump the ui builder artifact for the memoryId option Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore: shared-utils 1.0.14 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * chore: cli takes shared-utils 1.0.14 for the memoryId option Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: pin the app conversation scoping and the memoryId signatures Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: assert the ownership refusal, not any refusal Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: match the memoryId option in the cli wmill.ts signature tests Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix: file an app's turns as the deployed flow's conversation, preview included Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
003a6a9fd6
commit
07d4f7fe61
+10
-11
@@ -1155,7 +1155,7 @@ async function dev(opts: DevOptions, appFolder?: string) {
|
||||
colors.cyan(`[WebSocket] Received: ${JSON.stringify(message)}`),
|
||||
);
|
||||
|
||||
const { type, reqId, runnable_id, v, jobId } = message;
|
||||
const { type, reqId, runnable_id, v, jobId, memoryId } = message;
|
||||
|
||||
// Helper to send response
|
||||
const respond = (responseType: string, result: any, error: boolean) => {
|
||||
@@ -1166,6 +1166,7 @@ async function dev(opts: DevOptions, appFolder?: string) {
|
||||
const runAndWaitForResult = async (
|
||||
runnableId: string,
|
||||
args: any,
|
||||
memoryId?: string,
|
||||
): Promise<{ uuid: string; result: any }> => {
|
||||
const runnables = await loadRunnables();
|
||||
const runnable = runnables[runnableId];
|
||||
@@ -1182,6 +1183,7 @@ async function dev(opts: DevOptions, appFolder?: string) {
|
||||
args,
|
||||
appTempRefs,
|
||||
previewModules(runnables, runnableId),
|
||||
memoryId,
|
||||
);
|
||||
log.info(colors.gray(`[backend] Job started: ${uuid}`));
|
||||
|
||||
@@ -1194,7 +1196,7 @@ async function dev(opts: DevOptions, appFolder?: string) {
|
||||
// Run a runnable synchronously and wait for result
|
||||
log.info(colors.blue(`[backend] Running runnable: ${runnable_id}`));
|
||||
try {
|
||||
const { result } = await runAndWaitForResult(runnable_id, v);
|
||||
const { result } = await runAndWaitForResult(runnable_id, v, memoryId);
|
||||
respond("backendRes", result, false);
|
||||
} catch (error: any) {
|
||||
log.error(colors.red(`[backend] Error: ${error.message}`));
|
||||
@@ -1230,6 +1232,7 @@ async function dev(opts: DevOptions, appFolder?: string) {
|
||||
v,
|
||||
appTempRefs,
|
||||
previewModules(runnables, runnable_id),
|
||||
memoryId,
|
||||
);
|
||||
log.info(colors.gray(`[backendAsync] Job started: ${uuid}`));
|
||||
|
||||
@@ -1713,8 +1716,8 @@ export function buildWmillTs(
|
||||
|
||||
/**
|
||||
* The inputs of an agent runnable, after the agent's memory (see the frontend's
|
||||
* `agentRunnableSchema`): `memory_id` when it keeps managed memory, `previous_messages` when it
|
||||
* is off, and neither for an older `manual` setting.
|
||||
* `agentRunnableSchema`): `previous_messages` when it keeps no memory. The conversation of one
|
||||
* that does is `backendAsync`'s `memoryId` option, not an input.
|
||||
*/
|
||||
function agentRunnableSchema(memory: any) {
|
||||
const managed =
|
||||
@@ -1723,13 +1726,7 @@ function agentRunnableSchema(memory: any) {
|
||||
Boolean(memory.context_length));
|
||||
const legacy = memory?.kind === "manual";
|
||||
const history: Record<string, any> = {};
|
||||
if (managed) {
|
||||
history.memory_id = {
|
||||
type: "string",
|
||||
description:
|
||||
"The conversation the message belongs to: reuse it to continue one, or omit it for a single run",
|
||||
};
|
||||
} else if (!legacy) {
|
||||
if (!managed && !legacy) {
|
||||
history.previous_messages = {
|
||||
type: "array",
|
||||
description:
|
||||
@@ -1922,10 +1919,12 @@ async function executeRunnable(
|
||||
args: any,
|
||||
tempScriptRefs?: Record<string, string>,
|
||||
modules?: Record<string, { content: string; language: string }>,
|
||||
memoryId?: string,
|
||||
): Promise<string> {
|
||||
const requestBody: any = {
|
||||
component: runnableId,
|
||||
args: args ?? {},
|
||||
...(memoryId ? { run_query_params: { memory_id: memoryId } } : {}),
|
||||
force_viewer_static_fields: {},
|
||||
force_viewer_one_of_fields: {},
|
||||
force_viewer_allow_user_resources: [],
|
||||
|
||||
@@ -115,8 +115,8 @@ export const backend = new Proxy(
|
||||
{},
|
||||
{
|
||||
get(_, runnable_id: string) {
|
||||
return (v: any) => {
|
||||
return doRequest('backend', { runnable_id, v })
|
||||
return (v: any, opts?: { memoryId?: string }) => {
|
||||
return doRequest('backend', { runnable_id, v, memoryId: opts?.memoryId })
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -125,8 +125,8 @@ export const backendAsync = new Proxy(
|
||||
{},
|
||||
{
|
||||
get(_, runnable_id: string) {
|
||||
return (v: any) => {
|
||||
return doRequest('backendAsync', { runnable_id, v })
|
||||
return (v: any, opts?: { memoryId?: string }) => {
|
||||
return doRequest('backendAsync', { runnable_id, v, memoryId: opts?.memoryId })
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
Generated
+2
-2
@@ -6882,7 +6882,7 @@ import * as wmill from './wmill'
|
||||
const chat = useWindmillChat({ wmill, runnable: 'chat' })
|
||||
\`\`\`
|
||||
|
||||
\`runnable\` is the runnable's key; each runnable keeps its own conversations. The runnable takes \`user_message\` and \`memory_id\`, the conversation id, which the chat fills in: the agent remembers earlier turns of a conversation, and a signed-in viewer's conversations are theirs alone. Called directly as \`backend.<key>({ user_message })\` without \`memory_id\`, it is a single run with no conversation, for a one-off question. The chat needs an agent with managed memory. An agent whose memory is off keeps none: its runnable takes \`previous_messages\` (chat messages) instead of \`memory_id\`, and the app keeps the history itself. The chat keeps its transcript in the app's own storage. \`chat.stop()\` stops following without cancelling the turn, and a message sent while it still runs is refused with \`TurnRunningError\`. Attachments are not supported this way.
|
||||
\`runnable\` is the runnable's key; each runnable keeps its own conversations. The runnable takes \`user_message\`; the chat passes the conversation id as \`backendAsync.<key>(args, { memoryId })\`: the agent remembers earlier turns of a conversation, and a signed-in viewer's conversations are theirs alone. Called directly as \`backend.<key>({ user_message })\`, it is a single run with no conversation, for a one-off question; \`backend.<key>(args, { memoryId })\` waits for the reply of a conversation turn. The chat needs an agent with managed memory. An agent whose memory is off keeps none: its runnable takes \`previous_messages\` (chat messages) instead, and the app keeps the history itself. The chat keeps its transcript in the app's own storage. \`chat.stop()\` stops following without cancelling the turn, and a message sent while it still runs is refused with \`TurnRunningError\`. Attachments are not supported this way.
|
||||
|
||||
## Backend runnables
|
||||
|
||||
@@ -6938,7 +6938,7 @@ Use only \`wmill\` functions the SDK actually exports; for an endpoint none of t
|
||||
|
||||
### Path runnables (script / flow / hubscript / agent)
|
||||
|
||||
When \`type\` is \`script\`, \`flow\`, \`hubscript\` or \`agent\`, the runnable just stores a \`path\` to an existing workspace or hub item — no inline code. The referenced item's input/output schema becomes the runnable's surface; an agent's is \`user_message\` and \`user_attachments\`, plus \`memory_id\` when the agent keeps managed memory or \`previous_messages\` when its memory is off.
|
||||
When \`type\` is \`script\`, \`flow\`, \`hubscript\` or \`agent\`, the runnable just stores a \`path\` to an existing workspace or hub item — no inline code. The referenced item's input/output schema becomes the runnable's surface; an agent's is \`user_message\` and \`user_attachments\`, plus \`previous_messages\` when its memory is off.
|
||||
|
||||
Before writing an inline runnable, look for a workspace script or flow that already does the job, or a Hub script (a prebuilt integration at \`hub/<version>/<app>/<name>\`) for a third-party service, and reference it instead of copying its logic.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user