{"record":{"id":"07aa7367bd85e31d","repo":"windmill-labs/windmill","slug":"ai-agent-modules-providerless-map-id-id","errorCode":null,"errorMessage":"AI agent modules ${providerless.map((id) => `\"${id}\"`).join(', ')} need a provider input transform, or an \"agent\" path linking them to a saved agent","messagePattern":"AI agent modules (.+?)\"`\\)\\.join\\(', '\\)\\} need a provider input transform, or an \"agent\" path linking them to a saved agent","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"frontend/src/lib/components/copilot/chat/flow/editableFlowJson.ts","lineNumber":272,"sourceCode":"\tconst ids = collectAllFlowModuleIdsFromModules(parsedModules)\n\tif (ids.length !== new Set(ids).size) {\n\t\tthrow new Error('Duplicate module IDs found in flow')\n\t}\n\n\tconst reservedIds = ids.filter(\n\t\t(id) => id === SPECIAL_MODULE_IDS.PREPROCESSOR || id === SPECIAL_MODULE_IDS.FAILURE\n\t)\n\tif (reservedIds.length > 0) {\n\t\tthrow new Error(\n\t\t\t'Special modules must be provided via preprocessor_module and failure_module, not inside modules'\n\t\t)\n\t}\n\n\t// Not expressible in the schema: `provider` is required only when the step is standalone, and\n\t// making AiAgent a conditional union breaks the FlowModuleValue discriminated union it belongs to.\n\tconst providerless = collectProviderlessAgentIds(parsedModules)\n\tif (providerless.length > 0) {\n\t\tthrow new Error(\n\t\t\t`AI agent modules ${providerless\n\t\t\t\t.map((id) => `\"${id}\"`)\n\t\t\t\t.join(\n\t\t\t\t\t', '\n\t\t\t\t)} need a provider input transform, or an \"agent\" path linking them to a saved agent`\n\t\t)\n\t}\n\n\t// An agent tool's `summary` is the name the LLM sees; the worker rejects anything outside\n\t// `^[a-zA-Z0-9_]+$`, so a flow written with a spaced name saves but fails on every run.\n\tconst invalidToolNames = collectInvalidAgentToolNames(parsedModules)\n\tif (invalidToolNames.length > 0) {\n\t\tthrow new Error(\n\t\t\t`Invalid AI agent tool name(s): ${invalidToolNames\n\t\t\t\t.map(\n\t\t\t\t\t(t) =>\n\t\t\t\t\t\t`agent \"${t.agentId}\" tool \"${t.toolId}\" is named ${JSON.stringify(t.name)} - ${t.error}`\n\t\t\t\t)","sourceCodeStart":254,"sourceCodeEnd":290,"githubUrl":"https://github.com/windmill-labs/windmill/blob/e474e8803ce2ff5c2df09a58dab51d45f5c922ca/frontend/src/lib/components/copilot/chat/flow/editableFlowJson.ts#L254-L290","documentation":"validateFlowModules in the AI copilot's editable-flow validator rejects a flow whose AiAgent step has neither a `provider` input transform nor an `agent` path to a saved agent. This is thrown because the flow schema cannot express 'provider required only when standalone' (a conditional union would break the FlowModuleValue discriminated union), so the invariant is enforced imperatively after zod parsing. It guarantees every AI agent step the LLM wrote is actually runnable.","triggerScenarios":"Calling the flowTools/modules copilot tools with a flow JSON containing an aiagent module whose value has no `provider` input transform and no `agent` path (e.g. the model omitted provider when generating a standalone agent step).","commonSituations":"An LLM generates a flow with an AI agent step but forgets the provider field; a user hand-edits flow JSON and deletes the provider transform; a template is copied with the agent path stripped.","solutions":["Add a `provider` input transform to the aiagent module's value (e.g. {\"type\":\"aiagent\",\"provider\":{...}} with a valid provider config).","Or set an `agent` path on the module linking it to an existing saved agent in the workspace.","Check the listed module id(s) in the message and fix only those steps, then retry the tool call."],"exampleFix":"// before\n{\"id\":\"m1\",\"value\":{\"type\":\"aiagent\",\"tools\":[]}}\n// after\n{\"id\":\"m1\",\"value\":{\"type\":\"aiagent\",\"tools\":[],\"provider\":{\"openai\":{\"model\":\"gpt-4o\"}}}}","handlingStrategy":"validation","validationCode":"function agentModulesNeedProvider(modules) {\n  return (modules ?? []).filter(m =>\n    m?.value?.type === 'aiagent' && !m.value.provider && !m.value.agent\n  ).map(m => m.id)\n}\nconst bad = agentModulesNeedProvider(flow.modules)\nif (bad.length) throw new Error(`Agents missing provider/agent path: ${bad.join(', ')}`)","typeGuard":"function hasAgentConfig(m) {\n  return m?.value?.type === 'aiagent' &&\n    (m.value.provider != null || typeof m.value.agent === 'string' && m.value.agent.length > 0)\n}","tryCatchPattern":"try {\n  const editable = validateEditableFlowJson(rawFlow)\n  // use editable\n} catch (e) {\n  if (String(e.message).includes('need a provider input transform')) {\n    // surface message to the LLM so it rewrites the agent step with a provider or agent path\n  } else throw e\n}","preventionTips":["Always give standalone aiagent steps a provider config when generating flow JSON","Use an `agent` path referencing a saved agent when reusing workspace agents","Keep a flow-JSON template that includes a provider field on every aiagent step"],"tags":["validation","ai-agent","flow-json"],"backgroundTag":"missing-required-argument","analyzedSha":"e474e8803ce2ff5c2df09a58dab51d45f5c922ca","analyzedAt":"2026-09-03T12:38:19.024Z","contentChangedAt":"2026-09-03T12:38:19.024Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}