Files
windmill/frontend/src/lib/components/flows/agentToolUtils.test.ts
T
a78beff743 feat: dynamic AI agent toolsets (#11050)
* feat: dynamic ai agent toolsets, and memory as a step input

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: address review round 1 on dynamic ai agent toolsets

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* refactor: tag enabled_tools and drop the memory step input

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: let an mcp server entry be named by the path the roster shows

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: keep $res: out of the tool names the enabled tools picker offers

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: name an mcp server by its bare path on the one side that can hold it

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: count the enabled tool names that matched nothing instead of logging them

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* refactor: narrow an agent's roster in one pass, by whole entries

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* test: pin that an mcp summary is rejected against a name that is not

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* chore: regenerate the copilot flow schema after the merge

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs: shorten the enabled tools list hint

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* refactor: take enabled_tools back to a plain list of tool names

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs: keep the enabled tools add-menu hint describing the unset field

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: name a websearch tool that carries no summary of its own

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: reserve the name web search is enabled by so no tool can share it

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* refactor: spell the reserved web search name with a hyphen

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* refactor: reserve __wm_web_search as the name web search is enabled by

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* chore: advance ee-repo-ref past the git sync ci check work

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs: shorten the enabled tools description the run form shows

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* chore: update ee-repo-ref to e4c1b794d6c5e6e390987341b2840587bbb40348

This commit updates the EE repository reference after PR #785 was merged in windmill-ee-private.

Previous ee-repo-ref: af668462f0f06b02a5f4e0c22e6156858487a518

New ee-repo-ref: e4c1b794d6c5e6e390987341b2840587bbb40348

Automated by sync-ee-ref workflow.

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
2026-09-15 11:34:30 +02:00

53 lines
2.3 KiB
TypeScript

import { describe, expect, it, vi } from 'vitest'
// `agentToolUtils` reaches the copilot bundle, and Monaco's CSS with it, through this one import.
// Only `createAiAgentTool` reads it, and nothing below does.
vi.mock('../aiProviderStorage', () => ({ loadStoredConfig: () => undefined }))
import { getToolNameError, toolEnabledName, WEBSEARCH_ENABLED_NAME } from './agentToolUtils'
/**
* The names this returns are the ones `enabled_tools` holds and `tool_enabled_name` in
* `ai_executor.rs` matches against, so the two have to agree: a name only one side produces
* silently drops the tool from every run that narrows.
*/
describe('toolEnabledName', () => {
it('names a flow module tool by the name the model is shown', () => {
expect(
toolEnabledName({ id: 'a', summary: 'get_user', value: { tool_type: 'flowmodule' } } as any)
).toBe('get_user')
})
it('names an MCP server by its bare path, never the summary two servers may share', () => {
// `$res:` and all is how the roster stores it, but a name carrying that prefix is resolved to
// the resource itself before the worker sees it, so the list can only hold the bare path.
expect(
toolEnabledName({
id: 'm',
summary: 'github',
value: { tool_type: 'mcp', resource_path: '$res:u/admin/gh' }
} as any)
).toBe('u/admin/gh')
})
it('names web search by a reserved name, whatever label it carries', () => {
// It reaches the model as a provider capability rather than a tool, so the editor's label is
// not a name: something else in the roster could carry it and be switched on with it.
expect(toolEnabledName({ id: 'w', value: { tool_type: 'websearch' } } as any)).toBe(
WEBSEARCH_ENABLED_NAME
)
expect(
toolEnabledName({ id: 'w', summary: 'Web Search', value: { tool_type: 'websearch' } } as any)
).toBe(WEBSEARCH_ENABLED_NAME)
})
it('reserves that name against every other kind', () => {
// A flow module tool cannot be called it, so enabling a tool never enables web search beside
// it. Nothing about the name itself stops that — it is an ordinary identifier — so the rule
// is `getToolNameError` refusing it, as `flow_module_tool_name` does on the worker.
expect(getToolNameError(WEBSEARCH_ENABLED_NAME)).toBe(
`'${WEBSEARCH_ENABLED_NAME}' is a reserved name`
)
})
})