mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-21 00:02:30 +00:00
fix: keep legacy auto memory without an id working after an untouched redeploy
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
091e1128a3
commit
ad15cb740b
@@ -85,7 +85,7 @@ pub enum Memory {
|
||||
Auto {
|
||||
#[serde(default)]
|
||||
context_length: usize,
|
||||
#[serde(default)]
|
||||
#[serde(default, deserialize_with = "deserialize_blank_as_none")]
|
||||
memory_id: Option<Uuid>,
|
||||
},
|
||||
/// Written before the step's `messages` input, which it is equivalent to.
|
||||
@@ -94,6 +94,19 @@ pub enum Memory {
|
||||
},
|
||||
}
|
||||
|
||||
// An editor form can leave `""` in a legacy baked id it never filled; it means no id rather than
|
||||
// failing every run of the step.
|
||||
fn deserialize_blank_as_none<'de, D: serde::Deserializer<'de>>(
|
||||
deserializer: D,
|
||||
) -> Result<Option<Uuid>, D::Error> {
|
||||
match <Option<String> as serde::Deserialize>::deserialize(deserializer)? {
|
||||
Some(id) if !id.trim().is_empty() => Uuid::parse_str(id.trim())
|
||||
.map(Some)
|
||||
.map_err(serde::de::Error::custom),
|
||||
_ => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_present_messages<'de, D: serde::Deserializer<'de>>(
|
||||
deserializer: D,
|
||||
) -> Result<Option<Option<Vec<OpenAIMessage>>>, D::Error> {
|
||||
|
||||
@@ -1978,6 +1978,18 @@ mod tests {
|
||||
None,
|
||||
Resolved::Window(baked, 4),
|
||||
),
|
||||
(
|
||||
"legacy auto with an empty baked id uses the run's",
|
||||
json!({ "memory": { "kind": "auto", "context_length": 4, "memory_id": "" } }),
|
||||
Some(run),
|
||||
Resolved::Window(run, 4),
|
||||
),
|
||||
(
|
||||
"legacy auto with an empty baked id and no run id is stateless",
|
||||
json!({ "memory": { "kind": "auto", "context_length": 4, "memory_id": " " } }),
|
||||
None,
|
||||
Resolved::Stateless { noted: true },
|
||||
),
|
||||
(
|
||||
"legacy auto without a length is off",
|
||||
json!({ "memory": { "kind": "auto", "memory_id": baked } }),
|
||||
|
||||
@@ -115,5 +115,10 @@ describe('memoryPropertyFor', () => {
|
||||
'auto'
|
||||
])
|
||||
expect(kinds({ kind: 'manual', messages: [] })).toEqual(['off', 'window', 'manual'])
|
||||
const autoVariant = (value: unknown) => memoryPropertyFor(property, value).oneOf.at(-1)
|
||||
expect(autoVariant({ kind: 'auto', context_length: 4 }).properties.memory_id).toBeUndefined()
|
||||
expect(
|
||||
autoVariant({ kind: 'auto', context_length: 4, memory_id: 'x' }).properties.memory_id
|
||||
).toBeDefined()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -213,8 +213,15 @@ export const LEGACY_MEMORY_VARIANTS: Record<string, any> = {
|
||||
* value holds it. Otherwise the property itself is returned, which callers compare by identity to
|
||||
* avoid rebuilding the step schema. */
|
||||
export function memoryPropertyFor(property: any, value: any): any {
|
||||
const legacy = value?.kind ? LEGACY_MEMORY_VARIANTS[value.kind] : undefined
|
||||
let legacy = value?.kind ? LEGACY_MEMORY_VARIANTS[value.kind] : undefined
|
||||
if (!legacy || !property?.oneOf) return property
|
||||
// The form fills an empty string field with `''` when it opens, so the baked id field is only
|
||||
// offered to a value saved with the key. Keyed on presence rather than content, or clearing the
|
||||
// id to retype it would remove the field mid-edit.
|
||||
if (value.kind === 'auto' && !('memory_id' in value)) {
|
||||
const { memory_id: _, ...properties } = legacy.properties
|
||||
legacy = { ...legacy, properties }
|
||||
}
|
||||
return { ...property, oneOf: [...property.oneOf, legacy] }
|
||||
}
|
||||
|
||||
|
||||
@@ -163,10 +163,10 @@ export function normalizeAgentHistory(
|
||||
if (!inputTransforms) return
|
||||
const memory = inputTransforms.memory
|
||||
if (
|
||||
chatInputEnabled &&
|
||||
memory?.type === 'static' &&
|
||||
memory.value?.kind === 'auto' &&
|
||||
memory.value.memory_id
|
||||
'memory_id' in memory.value &&
|
||||
(chatInputEnabled || !String(memory.value.memory_id ?? '').trim())
|
||||
) {
|
||||
const { memory_id: _, ...policy } = memory.value
|
||||
memory.value = policy
|
||||
|
||||
@@ -70,6 +70,14 @@ describe('normalizeAgentHistory', () => {
|
||||
expect(transforms.memory.value).toEqual({ kind: 'auto', context_length: 10 })
|
||||
})
|
||||
|
||||
it('drops an empty baked memory id, which names no memory', () => {
|
||||
const transforms = {
|
||||
memory: { type: 'static', value: { kind: 'auto', context_length: 10, memory_id: '' } }
|
||||
}
|
||||
normalizeAgentHistory(transforms, false)
|
||||
expect(transforms.memory.value).toEqual({ kind: 'auto', context_length: 10 })
|
||||
})
|
||||
|
||||
it('does not persist an empty static memory id or message list', () => {
|
||||
const transforms: Record<string, any> = {
|
||||
memory_id: { type: 'static', value: ' ' },
|
||||
|
||||
Reference in New Issue
Block a user