mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-21 08:02:38 +00:00
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
191 lines
6.5 KiB
TypeScript
191 lines
6.5 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
||
|
||
import {
|
||
agentConfigToInputTransforms,
|
||
agentEditorRefusal,
|
||
flowLocalInputs,
|
||
inputTransformsToAgentConfig,
|
||
nonStaticBrainKeys,
|
||
summarizeAgentBrain,
|
||
toolInputOverrides,
|
||
transformValuedBrainKeys
|
||
} from './agentResourceUtils'
|
||
|
||
describe('transformValuedBrainKeys', () => {
|
||
// The tag is the whole test: `{type:'ai'}` carries no payload key to look for.
|
||
it('flags every transform variant, whatever it carries', () => {
|
||
expect(
|
||
transformValuedBrainKeys({
|
||
system_prompt: { type: 'ai' },
|
||
temperature: { type: 'javascript', expr: 'flow_input.t' },
|
||
max_iterations: { type: 'static', value: 3 }
|
||
})
|
||
).toEqual(['system_prompt', 'temperature', 'max_iterations'])
|
||
})
|
||
|
||
it('leaves the brain values that are legitimately objects', () => {
|
||
expect(
|
||
transformValuedBrainKeys({
|
||
output_schema: { type: 'object', properties: {} },
|
||
memory: { kind: 'auto', context_length: 20 },
|
||
provider: { kind: 'openai', model: 'gpt-4o' },
|
||
system_prompt: 'hi'
|
||
})
|
||
).toEqual([])
|
||
})
|
||
})
|
||
|
||
describe('agentEditorRefusal', () => {
|
||
it('opens an agent', () => {
|
||
expect(agentEditorRefusal('u/admin/agent', 'ai_agent')).toBeUndefined()
|
||
})
|
||
|
||
// A path with no deployed row answers with its own draft, and the generic resource editor writes
|
||
// no type: assuming `ai_agent` there would create that path as an agent on deploy.
|
||
it('refuses an unknown type rather than assuming one', () => {
|
||
expect(agentEditorRefusal('u/admin/db', undefined)).toContain('no deployed agent')
|
||
})
|
||
})
|
||
|
||
describe('summarizeAgentBrain', () => {
|
||
it('returns only set fields, in brain-key order, formatted', () => {
|
||
const rows = summarizeAgentBrain({
|
||
provider: { kind: 'openai', model: 'gpt-4o', resource: '$res:f/x/openai' } as any,
|
||
system_prompt: 'You are helpful',
|
||
temperature: 0.7,
|
||
streaming: true,
|
||
max_iterations: 10
|
||
})
|
||
expect(rows).toEqual([
|
||
{ label: 'Provider', value: 'openai · gpt-4o' },
|
||
{ label: 'System message', value: 'You are helpful' },
|
||
{ label: 'Stream the response', value: 'on' },
|
||
{ label: 'Temperature', value: '0.7' },
|
||
{ label: 'Max iterations', value: '10' }
|
||
])
|
||
})
|
||
|
||
it('skips empty/undefined fields', () => {
|
||
expect(summarizeAgentBrain({ system_prompt: '', provider: undefined as any })).toEqual([])
|
||
expect(summarizeAgentBrain(undefined)).toEqual([])
|
||
})
|
||
|
||
it('summarizes structured fields compactly', () => {
|
||
// memory is serialized with a `kind` tag (serde tag = "kind")
|
||
const rows = summarizeAgentBrain({
|
||
memory: { kind: 'auto', context_length: 20 } as any,
|
||
output_schema: { type: 'object' } as any
|
||
})
|
||
expect(rows).toEqual([
|
||
{ label: 'Memory', value: 'auto' },
|
||
{ label: 'Output schema', value: 'configured' }
|
||
])
|
||
})
|
||
})
|
||
|
||
describe('inputTransformsToAgentConfig', () => {
|
||
it('captures only static brain values and carries tools', () => {
|
||
const config = inputTransformsToAgentConfig(
|
||
{
|
||
provider: { type: 'static', value: { kind: 'openai' } },
|
||
system_prompt: { type: 'static', value: 'hi' },
|
||
temperature: { type: 'javascript', expr: 'flow_input.t' }, // non-static → dropped
|
||
max_iterations: { type: 'static', value: undefined }, // undefined → dropped
|
||
user_message: { type: 'static', value: 'hello' } // not a brain key → dropped
|
||
} as any,
|
||
[{ id: 't1' }] as any
|
||
)
|
||
expect(config).toEqual({
|
||
tools: [{ id: 't1' }],
|
||
provider: { kind: 'openai' },
|
||
system_prompt: 'hi'
|
||
})
|
||
})
|
||
|
||
it('defaults tools to []', () => {
|
||
expect(inputTransformsToAgentConfig({}, undefined)).toEqual({ tools: [] })
|
||
})
|
||
})
|
||
|
||
describe('agentConfigToInputTransforms', () => {
|
||
it('wraps brain values as static transforms and round-trips through the config', () => {
|
||
const config = { provider: { kind: 'openai' }, system_prompt: 'hi', tools: [{ id: 't1' }] }
|
||
const its = agentConfigToInputTransforms(config as any)
|
||
expect(its).toEqual({
|
||
provider: { type: 'static', value: { kind: 'openai' } },
|
||
system_prompt: { type: 'static', value: 'hi' }
|
||
})
|
||
const back = inputTransformsToAgentConfig(its, config.tools as any)
|
||
expect(back).toEqual(config)
|
||
})
|
||
})
|
||
|
||
describe('nonStaticBrainKeys', () => {
|
||
it('lists brain keys with a non-static transform, in brain-key order', () => {
|
||
expect(
|
||
nonStaticBrainKeys({
|
||
provider: { type: 'static', value: {} },
|
||
temperature: { type: 'connected' },
|
||
system_prompt: { type: 'javascript', expr: 'x' }
|
||
} as any)
|
||
).toEqual(['system_prompt', 'temperature'])
|
||
})
|
||
|
||
it('flags a non-static provider (the save-blocking case) but not a static one', () => {
|
||
// provider is required on the resource; a non-static one gets dropped and must block saving
|
||
expect(nonStaticBrainKeys({ provider: { type: 'javascript', expr: 'x' } } as any)).toContain(
|
||
'provider'
|
||
)
|
||
expect(nonStaticBrainKeys({ provider: { type: 'static', value: {} } } as any)).not.toContain(
|
||
'provider'
|
||
)
|
||
})
|
||
})
|
||
|
||
describe('flowLocalInputs', () => {
|
||
it('keeps the step’s own inputs, dropping brain transforms', () => {
|
||
expect(
|
||
flowLocalInputs({
|
||
provider: { type: 'static', value: {} },
|
||
user_message: { type: 'static', value: 'hi' },
|
||
user_attachments: { type: 'static', value: [] },
|
||
// The roster it narrows belongs to the agent, but which of it one flow may call does
|
||
// not: saving this into the resource would impose it on every flow linking the agent.
|
||
enabled_tools: { type: 'javascript', expr: 'flow_input.tools' }
|
||
} as any)
|
||
).toEqual({
|
||
user_message: { type: 'static', value: 'hi' },
|
||
user_attachments: { type: 'static', value: [] },
|
||
enabled_tools: { type: 'javascript', expr: 'flow_input.tools' }
|
||
})
|
||
})
|
||
|
||
it('handles undefined', () => {
|
||
expect(flowLocalInputs(undefined)).toEqual({})
|
||
})
|
||
})
|
||
|
||
describe('toolInputOverrides', () => {
|
||
const base = {
|
||
tenant: { type: 'javascript', expr: 'flow_input.tenant' },
|
||
query: { type: 'static', value: 'x' }
|
||
} as any
|
||
|
||
it('returns nothing when inputs equal the resource base (opening a tool is a no-op)', () => {
|
||
expect(toolInputOverrides(base, base)).toEqual({})
|
||
})
|
||
|
||
it('returns only the edited keys, so a revert to base drops back to nothing', () => {
|
||
const edited = { ...base, query: { type: 'static', value: 'y' } }
|
||
expect(toolInputOverrides(edited, base)).toEqual({ query: { type: 'static', value: 'y' } })
|
||
// reverting query back to the base value yields an empty override set again
|
||
expect(toolInputOverrides(base, base)).toEqual({})
|
||
})
|
||
|
||
it('includes keys absent from the base', () => {
|
||
expect(toolInputOverrides({ extra: { type: 'static', value: 1 } } as any, base)).toEqual({
|
||
extra: { type: 'static', value: 1 }
|
||
})
|
||
})
|
||
})
|