diff --git a/cli/src/guidance/skills.gen.ts b/cli/src/guidance/skills.gen.ts index 9601477fd9..3eb1c872cc 100644 --- a/cli/src/guidance/skills.gen.ts +++ b/cli/src/guidance/skills.gen.ts @@ -6165,12 +6165,12 @@ Use this guide when writing or modifying Windmill Workflow-as-Code (WAC) scripts WAC is authored as a Windmill script and deployed with the normal script workflow. It is not an OpenFlow YAML flow. Supported WAC authoring targets: -- TypeScript scripts that import from \`windmill-client\` +- Bun TypeScript scripts that import from \`windmill-client\` - Python 3 scripts that import from \`wmill\` ## File Shape -TypeScript: +Bun TypeScript: \`\`\`typescript import { @@ -6212,7 +6212,7 @@ async def main(x: str): Rules: - Do not call \`main\`. -- TypeScript should export the workflow entrypoint, preferably \`export const main = workflow(async (...) => { ... })\`. +- Bun TypeScript should export the workflow entrypoint, preferably \`export const main = workflow(async (...) => { ... })\`. - Python must use \`@workflow\` on an async top-level function, usually \`main\`. - Define task functions and \`taskScript\`/\`task_script\` or \`taskFlow\`/\`task_flow\` assignments at module top level with stable names. - Use the exact SDK names. Do not alias \`workflow\`, \`task\`, \`taskScript\`, \`taskFlow\`, \`step\`, \`sleep\`, \`waitForApproval\`, \`task_script\`, \`task_flow\`, or \`wait_for_approval\`; the WAC parser recognizes these names directly. diff --git a/frontend/src/lib/components/Editor.svelte b/frontend/src/lib/components/Editor.svelte index 8a5836183a..352e963080 100644 --- a/frontend/src/lib/components/Editor.svelte +++ b/frontend/src/lib/components/Editor.svelte @@ -129,6 +129,7 @@ useWebsockets?: boolean small?: boolean scriptLang: Preview['language'] | 'bunnative' | 'tsx' | 'jsx' | 'json' | undefined + workflowAsCode?: boolean disabled?: boolean lineNumbersMinChars?: number files?: Record | undefined @@ -163,6 +164,7 @@ useWebsockets = true, small = false, scriptLang, + workflowAsCode = false, disabled = false, lineNumbersMinChars = 3, files = {}, @@ -819,12 +821,13 @@ function addAutoCompletor( editor: meditor.IStandaloneCodeEditor, - scriptLang: ScriptLang | 'bunnative' | 'jsx' | 'tsx' | 'json' + scriptLang: ScriptLang | 'bunnative' | 'jsx' | 'tsx' | 'json', + workflowAsCode: boolean ) { if (autocompletor) { autocompletor.dispose() } - autocompletor = new Autocompletor(editor, scriptLang) + autocompletor = new Autocompletor(editor, scriptLang, { workflowAsCode }) } const outputChannel = { @@ -1875,13 +1878,14 @@ ;(!dbSchema || lang !== 'graphql') && untrack(() => disposeGaphqlService()) }) $effect(() => { + const currentWorkflowAsCode = workflowAsCode $copilotInfo.enabled && $codeCompletionSessionEnabled && Autocompletor.isProviderModelSupported($copilotInfo.codeCompletionModel) && initialized && editor && scriptLang && - untrack(() => editor && addAutoCompletor(editor, scriptLang)) + untrack(() => editor && addAutoCompletor(editor, scriptLang, currentWorkflowAsCode)) }) $effect(() => { $copilotInfo.enabled && initialized && editor && untrack(() => editor && addChatHandler(editor)) diff --git a/frontend/src/lib/components/ScriptBuilder.svelte b/frontend/src/lib/components/ScriptBuilder.svelte index 400e9ff3e2..ee6fe15738 100644 --- a/frontend/src/lib/components/ScriptBuilder.svelte +++ b/frontend/src/lib/components/ScriptBuilder.svelte @@ -2117,6 +2117,7 @@ bind:code={script.content} lang={script.language} kind={script.kind} + autoKind={script.auto_kind} {template} tag={script.tag} lastSavedCode={savedScript?.draft?.content} diff --git a/frontend/src/lib/components/ScriptEditor.svelte b/frontend/src/lib/components/ScriptEditor.svelte index bd3549e05a..16f835fcdd 100644 --- a/frontend/src/lib/components/ScriptEditor.svelte +++ b/frontend/src/lib/components/ScriptEditor.svelte @@ -116,6 +116,7 @@ path: string | undefined lang: Preview['language'] kind?: string | undefined + autoKind?: string | undefined template?: | 'pgsql' | 'mysql' @@ -161,6 +162,7 @@ path, lang, kind = undefined, + autoKind = undefined, template = 'script', tag, fixedOverflowWidgets = true, @@ -1234,7 +1236,16 @@ ) } - let isWac = $derived(code && lang ? isWorkflowAsCode(code, lang) : false) + const WAC_CONTEXT_LANGUAGES = ['python3', 'bun'] + let isWac = $derived( + template === 'wac_python' || + template === 'wac_typescript' || + autoKind === 'wac' || + (code && lang ? isWorkflowAsCode(code, lang) : false) + ) + let workflowAsCodeAiContext = $derived( + activeModuleTab === null && isWac && WAC_CONTEXT_LANGUAGES.includes(lang ?? '') + ) let showTabs = $derived(hasPreprocessor || isWac) $effect(() => { !hasPreprocessor && (selectedTab = 'main') @@ -1304,7 +1315,8 @@ path, lastSavedCode, lastDeployedCode, - diffMode + diffMode, + workflowAsCode: workflowAsCodeAiContext } untrack(() => { aiChatManager.scriptEditorOptions = options @@ -2075,6 +2087,7 @@ automaticLayout={true} {fixedOverflowWidgets} {args} + workflowAsCode={workflowAsCodeAiContext} {enablePreprocessorSnippet} preparedAssetsSqlQueries={preparedSqlQueries.current} customTag={tag} diff --git a/frontend/src/lib/components/copilot/autocomplete/Autocompletor.ts b/frontend/src/lib/components/copilot/autocomplete/Autocompletor.ts index 8f62956952..b2484ff909 100644 --- a/frontend/src/lib/components/copilot/autocomplete/Autocompletor.ts +++ b/frontend/src/lib/components/copilot/autocomplete/Autocompletor.ts @@ -66,6 +66,7 @@ export class Autocompletor { max: 10 }) #scriptLang: ScriptLang | 'bunnative' | 'jsx' | 'tsx' | 'json' + #workflowAsCode: boolean #abortController: AbortController = new AbortController() #completionDisposable: IDisposable #cursorDisposable: IDisposable @@ -76,7 +77,8 @@ export class Autocompletor { constructor( editor: meditor.IStandaloneCodeEditor, - scriptLang: ScriptLang | 'bunnative' | 'jsx' | 'tsx' | 'json' + scriptLang: ScriptLang | 'bunnative' | 'jsx' | 'tsx' | 'json', + options: { workflowAsCode?: boolean } = {} ) { setGlobalCSS( 'ai-chat-autocomplete', @@ -87,6 +89,7 @@ export class Autocompletor { ` ) this.#scriptLang = scriptLang + this.#workflowAsCode = options.workflowAsCode ?? false const deletionsCues = editor.createDecorationsCollection() @@ -520,7 +523,8 @@ export class Autocompletor { suffix, scriptLang: this.#scriptLang, markers: markersAtCursor, - libraries: librariesCompletions + libraries: librariesCompletions, + workflowAsCode: this.#workflowAsCode }, this.#abortController ) diff --git a/frontend/src/lib/components/copilot/autocomplete/request.ts b/frontend/src/lib/components/copilot/autocomplete/request.ts index 51fe0cf720..ef29ba9a19 100644 --- a/frontend/src/lib/components/copilot/autocomplete/request.ts +++ b/frontend/src/lib/components/copilot/autocomplete/request.ts @@ -20,6 +20,7 @@ export async function autocompleteRequest( scriptLang: ScriptLang | 'bunnative' | 'jsx' | 'tsx' | 'json' markers: editor.IMarker[] libraries: string + workflowAsCode?: boolean }, abortController: AbortController ) { @@ -38,7 +39,10 @@ export async function autocompleteRequest( 'You are a code completion assistant. You are given three important contexts (, , ) to help you complete the code.\n' ) contextLines += comment(commentSymbol, 'LANGUAGE CONTEXT:\n') - contextLines += comment(commentSymbol, getLangContext(context.scriptLang) + '\n') + contextLines += comment( + commentSymbol, + getLangContext(context.scriptLang, { workflowAsCode: context.workflowAsCode }) + '\n' + ) contextLines += comment(commentSymbol, 'DIAGNOSTICS:\n') contextLines += comment(commentSymbol, context.markers.map((m) => m.message).join('\n') + '\n') contextLines += comment(commentSymbol, 'LIBRARY METHODS:\n') diff --git a/frontend/src/lib/components/copilot/chat/AIChatManager.svelte.ts b/frontend/src/lib/components/copilot/chat/AIChatManager.svelte.ts index 86f8b6d7a1..0d3ca62fd0 100644 --- a/frontend/src/lib/components/copilot/chat/AIChatManager.svelte.ts +++ b/frontend/src/lib/components/copilot/chat/AIChatManager.svelte.ts @@ -256,6 +256,8 @@ class AIChatManager { options?: { closeScriptSettings?: boolean lang?: ScriptLang | 'bunnative' + isPreprocessor?: boolean + workflowAsCode?: boolean } ) { if (mode === AIMode.SCRIPT && !tryGetCurrentModel()) return @@ -265,8 +267,16 @@ class AIChatManager { const currentModel = getCurrentModel() const customPrompt = getCombinedCustomPrompt(mode) const lang = options?.lang ?? this.scriptEditorOptions?.lang ?? 'bun' + const workflowAsCode = + options?.workflowAsCode ?? + (options?.lang ? false : (this.scriptEditorOptions?.workflowAsCode ?? false)) const context = this.contextManager.getSelectedContext() - this.systemMessage = prepareScriptSystemMessage(currentModel, lang, {}, customPrompt) + this.systemMessage = prepareScriptSystemMessage( + currentModel, + lang, + { isPreprocessor: options?.isPreprocessor, workflowAsCode }, + customPrompt + ) this.systemMessage.content = this.systemMessage.content this.tools = [...prepareScriptTools(currentModel, lang, context)] this.helpers = { @@ -533,7 +543,9 @@ class AIChatManager { const systemMessage: ChatCompletionSystemMessageParam = { role: 'system', - content: prepareInlineChatSystemPrompt(lang) + content: prepareInlineChatSystemPrompt(lang, { + workflowAsCode: this.scriptEditorOptions?.workflowAsCode ?? false + }) } let reply = '' @@ -606,9 +618,15 @@ class AIChatManager { } = {} ) => { if (options.mode) { - this.changeMode(options.mode, undefined, { lang: options.lang }) + this.changeMode(options.mode, undefined, { + lang: options.lang, + isPreprocessor: options.isPreprocessor + }) } else { - this.changeMode(this.mode, undefined, { lang: options.lang }) + this.changeMode(this.mode, undefined, { + lang: options.lang, + isPreprocessor: options.isPreprocessor + }) } if (options.instructions) { this.instructions = options.instructions diff --git a/frontend/src/lib/components/copilot/chat/ContextManager.svelte.ts b/frontend/src/lib/components/copilot/chat/ContextManager.svelte.ts index 266a27954a..c07e473d54 100644 --- a/frontend/src/lib/components/copilot/chat/ContextManager.svelte.ts +++ b/frontend/src/lib/components/copilot/chat/ContextManager.svelte.ts @@ -18,6 +18,7 @@ export interface ScriptOptions { lastSavedCode?: string lastDeployedCode?: string diffMode: boolean + workflowAsCode?: boolean } export interface FlowOptions { diff --git a/frontend/src/lib/components/copilot/chat/script/core.ts b/frontend/src/lib/components/copilot/chat/script/core.ts index 6d391f4cbb..b236235527 100644 --- a/frontend/src/lib/components/copilot/chat/script/core.ts +++ b/frontend/src/lib/components/copilot/chat/script/core.ts @@ -27,7 +27,7 @@ import { getModelContextWindow } from '../../lib' import type { ReviewChangesOpts } from '../monaco-adapter' import { getCurrentModel } from '$lib/aiStore' import { getDbSchemas } from '$lib/components/apps/components/display/dbtable/metadata' -import { getScriptPrompt } from '$system_prompts' +import { getScriptPrompt, getWorkflowAsCodePrompt } from '$system_prompts' // Score threshold for npm packages search filtering const SCORE_THRESHOLD = 1000 @@ -104,11 +104,22 @@ export function getLangContext( lang: ScriptLang | 'bunnative' | 'jsx' | 'tsx' | 'json', { allowResourcesFetch = false, - isPreprocessor = false - }: { allowResourcesFetch?: boolean; isPreprocessor?: boolean; isFailure?: boolean } = {} + isPreprocessor = false, + workflowAsCode = false + }: { + allowResourcesFetch?: boolean + isPreprocessor?: boolean + isFailure?: boolean + workflowAsCode?: boolean + } = {} ): string { // Get base language context from centralized prompts - let context = getScriptPrompt(lang) + let context = workflowAsCode ? getWorkflowAsCodePrompt(lang) : getScriptPrompt(lang) + + // Fallback to the regular script prompt if WAC context is requested for an unsupported language. + if (!context) { + context = getScriptPrompt(lang) + } // Add tool usage instructions for applicable languages if (['python3', 'php', 'bun', 'deno', 'nativets', 'bunnative'].includes(lang)) { @@ -117,8 +128,8 @@ export function getLangContext( } } - // Note preprocessor function naming if applicable - if (isPreprocessor) { + // Note preprocessor function naming if applicable. WAC scripts are not preprocessors. + if (isPreprocessor && !workflowAsCode) { context += '\n\nThe main function for this script should be named `preprocessor` instead of `main`.' } @@ -277,8 +288,15 @@ export async function main() { \`\`\` ` -export function prepareInlineChatSystemPrompt(lang: ScriptLang | 'bunnative') { - return INLINE_CHAT_SYSTEM_PROMPT + getLangContext(lang, { allowResourcesFetch: true }) +export function prepareInlineChatSystemPrompt( + lang: ScriptLang | 'bunnative', + options: { workflowAsCode?: boolean } = {} +) { + return ( + INLINE_CHAT_SYSTEM_PROMPT + + '\n\n' + + getLangContext(lang, { allowResourcesFetch: true, workflowAsCode: options.workflowAsCode }) + ) } export const CHAT_USER_PROMPT = ` @@ -290,7 +308,7 @@ INSTRUCTIONS: export function prepareScriptSystemMessage( currentModel: AIProviderModel, language: ScriptLang | 'bunnative', - options: { isPreprocessor?: boolean; allowResourcesFetch?: boolean } = {}, + options: { isPreprocessor?: boolean; allowResourcesFetch?: boolean; workflowAsCode?: boolean } = {}, customPrompt?: string ): ChatCompletionSystemMessageParam { let content = buildChatSystemPrompt(currentModel) diff --git a/frontend/src/lib/components/copilot/chat/script/wacPrompt.test.ts b/frontend/src/lib/components/copilot/chat/script/wacPrompt.test.ts new file mode 100644 index 0000000000..c08fc354ae --- /dev/null +++ b/frontend/src/lib/components/copilot/chat/script/wacPrompt.test.ts @@ -0,0 +1,31 @@ +import { describe, expect, it } from 'vitest' +import { getScriptPrompt, getWorkflowAsCodePrompt } from '$system_prompts' + +describe('Workflow-as-Code prompt helpers', () => { + it('injects only the TypeScript WAC SDK for TypeScript runtimes', () => { + const prompt = getWorkflowAsCodePrompt('bun') + + expect(prompt).toContain('Windmill Workflow-as-Code Writing Guide') + expect(prompt).toContain('## TypeScript Workflow-as-Code API') + expect(prompt).not.toContain('## Python Workflow-as-Code API') + }) + + it('injects only the Python WAC SDK for Python runtimes', () => { + const prompt = getWorkflowAsCodePrompt('python3') + + expect(prompt).toContain('Windmill Workflow-as-Code Writing Guide') + expect(prompt).toContain('## Python Workflow-as-Code API') + expect(prompt).not.toContain('## TypeScript Workflow-as-Code API') + }) + + it('does not support non-Bun TypeScript runtimes as WAC targets', () => { + expect(getWorkflowAsCodePrompt('deno')).toBe('') + expect(getWorkflowAsCodePrompt('nativets')).toBe('') + expect(getWorkflowAsCodePrompt('bunnative')).toBe('') + }) + + it('does not change normal script prompts', () => { + expect(getWorkflowAsCodePrompt('go')).toBe('') + expect(getScriptPrompt('bun')).not.toContain('Windmill Workflow-as-Code Writing Guide') + }) +}) diff --git a/frontend/src/lib/components/flows/content/ScriptEditorDrawer.svelte b/frontend/src/lib/components/flows/content/ScriptEditorDrawer.svelte index ae9d75daeb..91e71796fb 100644 --- a/frontend/src/lib/components/flows/content/ScriptEditorDrawer.svelte +++ b/frontend/src/lib/components/flows/content/ScriptEditorDrawer.svelte @@ -207,6 +207,7 @@ noSyncFromGithub lang={script.language} path={script.path} + autoKind={script.auto_kind} tag={script.tag} fixedOverflowWidgets={false} bind:code={script.content} diff --git a/frontend/src/lib/components/graph/wacToFlow.test.ts b/frontend/src/lib/components/graph/wacToFlow.test.ts new file mode 100644 index 0000000000..796c7a4d9a --- /dev/null +++ b/frontend/src/lib/components/graph/wacToFlow.test.ts @@ -0,0 +1,19 @@ +import { describe, expect, it } from 'vitest' +import { isWorkflowAsCode } from './wacToFlow' + +const tsWac = ` +import { workflow, task } from "windmill-client" + +const process = task(async () => "ok") + +export const main = workflow(async () => await process()) +` + +describe('isWorkflowAsCode', () => { + it('detects WAC only for Bun TypeScript and Python', () => { + expect(isWorkflowAsCode(tsWac, 'bun')).toBe(true) + expect(isWorkflowAsCode(tsWac, 'deno')).toBe(false) + expect(isWorkflowAsCode(tsWac, 'nativets')).toBe(false) + expect(isWorkflowAsCode('@workflow\nasync def main():\n return None\n', 'python3')).toBe(true) + }) +}) diff --git a/frontend/src/lib/components/graph/wacToFlow.ts b/frontend/src/lib/components/graph/wacToFlow.ts index 6f2f3720e1..4da1736f8d 100644 --- a/frontend/src/lib/components/graph/wacToFlow.ts +++ b/frontend/src/lib/components/graph/wacToFlow.ts @@ -5,7 +5,7 @@ export function isWorkflowAsCode(code: string, language: string): boolean { if (language === 'python3') { return /^\s*@workflow\s*$/m.test(code) || /from\s+wmill\s+import.*workflow/.test(code) } - if (language === 'bun' || language === 'deno') { + if (language === 'bun') { return ( /workflow\s*\(/.test(code) && /task\s*\(/.test(code) && diff --git a/system_prompts/auto-generated/index.d.ts b/system_prompts/auto-generated/index.d.ts index 4f710293fe..2ff02ece06 100644 --- a/system_prompts/auto-generated/index.d.ts +++ b/system_prompts/auto-generated/index.d.ts @@ -2,4 +2,4 @@ export * from './prompts'; export declare function getScriptPrompt(language: string): string; export declare function getFlowPrompt(): string; export declare function getDatatableSdkReference(): string; -export declare function getWorkflowAsCodePrompt(): string; +export declare function getWorkflowAsCodePrompt(language?: string): string; diff --git a/system_prompts/auto-generated/index.ts b/system_prompts/auto-generated/index.ts index 4bf251445a..1d880184a7 100644 --- a/system_prompts/auto-generated/index.ts +++ b/system_prompts/auto-generated/index.ts @@ -10,6 +10,12 @@ const TS_SDK_LANGUAGES = ['bun', 'deno', 'nativets', 'bunnative']; // Languages that use the Python SDK const PY_SDK_LANGUAGES = ['python3']; +// Languages that use the TypeScript Workflow-as-Code SDK +const WAC_TS_SDK_LANGUAGES = ['bun']; + +// Languages that use the Python Workflow-as-Code SDK +const WAC_PY_SDK_LANGUAGES = PY_SDK_LANGUAGES; + // Helper to combine prompts for scripts export function getScriptPrompt(language: string): string { const langKey = `LANG_${language.toUpperCase()}` as keyof typeof prompts; @@ -47,10 +53,24 @@ export function getDatatableSdkReference(): string { } // Helper to combine prompts for Workflow-as-Code scripts -export function getWorkflowAsCodePrompt(): string { +export function getWorkflowAsCodePrompt(language?: string): string { + let sdkPrompt = ''; + + if (language == null) { + sdkPrompt = [ + prompts.WAC_SDK_TYPESCRIPT, + prompts.WAC_SDK_PYTHON + ].filter(Boolean).join('\n\n'); + } else if (WAC_TS_SDK_LANGUAGES.includes(language)) { + sdkPrompt = prompts.WAC_SDK_TYPESCRIPT; + } else if (WAC_PY_SDK_LANGUAGES.includes(language)) { + sdkPrompt = prompts.WAC_SDK_PYTHON; + } else { + return ''; + } + return [ prompts.WORKFLOW_AS_CODE_BASE, - prompts.WAC_SDK_TYPESCRIPT, - prompts.WAC_SDK_PYTHON + sdkPrompt ].filter(Boolean).join('\n\n'); } diff --git a/system_prompts/auto-generated/prompts.ts b/system_prompts/auto-generated/prompts.ts index e0f705fafd..aeb39bb6b7 100644 --- a/system_prompts/auto-generated/prompts.ts +++ b/system_prompts/auto-generated/prompts.ts @@ -332,12 +332,12 @@ Use this guide when writing or modifying Windmill Workflow-as-Code (WAC) scripts WAC is authored as a Windmill script and deployed with the normal script workflow. It is not an OpenFlow YAML flow. Supported WAC authoring targets: -- TypeScript scripts that import from \`windmill-client\` +- Bun TypeScript scripts that import from \`windmill-client\` - Python 3 scripts that import from \`wmill\` ## File Shape -TypeScript: +Bun TypeScript: \`\`\`typescript import { @@ -379,7 +379,7 @@ async def main(x: str): Rules: - Do not call \`main\`. -- TypeScript should export the workflow entrypoint, preferably \`export const main = workflow(async (...) => { ... })\`. +- Bun TypeScript should export the workflow entrypoint, preferably \`export const main = workflow(async (...) => { ... })\`. - Python must use \`@workflow\` on an async top-level function, usually \`main\`. - Define task functions and \`taskScript\`/\`task_script\` or \`taskFlow\`/\`task_flow\` assignments at module top level with stable names. - Use the exact SDK names. Do not alias \`workflow\`, \`task\`, \`taskScript\`, \`taskFlow\`, \`step\`, \`sleep\`, \`waitForApproval\`, \`task_script\`, \`task_flow\`, or \`wait_for_approval\`; the WAC parser recognizes these names directly. diff --git a/system_prompts/auto-generated/skills/write-workflow-as-code/SKILL.md b/system_prompts/auto-generated/skills/write-workflow-as-code/SKILL.md index 59ca475c1a..eccacd1867 100644 --- a/system_prompts/auto-generated/skills/write-workflow-as-code/SKILL.md +++ b/system_prompts/auto-generated/skills/write-workflow-as-code/SKILL.md @@ -48,12 +48,12 @@ Use this guide when writing or modifying Windmill Workflow-as-Code (WAC) scripts WAC is authored as a Windmill script and deployed with the normal script workflow. It is not an OpenFlow YAML flow. Supported WAC authoring targets: -- TypeScript scripts that import from `windmill-client` +- Bun TypeScript scripts that import from `windmill-client` - Python 3 scripts that import from `wmill` ## File Shape -TypeScript: +Bun TypeScript: ```typescript import { @@ -95,7 +95,7 @@ async def main(x: str): Rules: - Do not call `main`. -- TypeScript should export the workflow entrypoint, preferably `export const main = workflow(async (...) => { ... })`. +- Bun TypeScript should export the workflow entrypoint, preferably `export const main = workflow(async (...) => { ... })`. - Python must use `@workflow` on an async top-level function, usually `main`. - Define task functions and `taskScript`/`task_script` or `taskFlow`/`task_flow` assignments at module top level with stable names. - Use the exact SDK names. Do not alias `workflow`, `task`, `taskScript`, `taskFlow`, `step`, `sleep`, `waitForApproval`, `task_script`, `task_flow`, or `wait_for_approval`; the WAC parser recognizes these names directly. diff --git a/system_prompts/base/workflow-as-code.md b/system_prompts/base/workflow-as-code.md index 2745d00a18..78be861213 100644 --- a/system_prompts/base/workflow-as-code.md +++ b/system_prompts/base/workflow-as-code.md @@ -6,12 +6,12 @@ Use this guide when writing or modifying Windmill Workflow-as-Code (WAC) scripts WAC is authored as a Windmill script and deployed with the normal script workflow. It is not an OpenFlow YAML flow. Supported WAC authoring targets: -- TypeScript scripts that import from `windmill-client` +- Bun TypeScript scripts that import from `windmill-client` - Python 3 scripts that import from `wmill` ## File Shape -TypeScript: +Bun TypeScript: ```typescript import { @@ -53,7 +53,7 @@ async def main(x: str): Rules: - Do not call `main`. -- TypeScript should export the workflow entrypoint, preferably `export const main = workflow(async (...) => { ... })`. +- Bun TypeScript should export the workflow entrypoint, preferably `export const main = workflow(async (...) => { ... })`. - Python must use `@workflow` on an async top-level function, usually `main`. - Define task functions and `taskScript`/`task_script` or `taskFlow`/`task_flow` assignments at module top level with stable names. - Use the exact SDK names. Do not alias `workflow`, `task`, `taskScript`, `taskFlow`, `step`, `sleep`, `waitForApproval`, `task_script`, `task_flow`, or `wait_for_approval`; the WAC parser recognizes these names directly. diff --git a/system_prompts/generate.py b/system_prompts/generate.py index 9c9da74081..f291995b88 100644 --- a/system_prompts/generate.py +++ b/system_prompts/generate.py @@ -1967,6 +1967,12 @@ const TS_SDK_LANGUAGES = ['bun', 'deno', 'nativets', 'bunnative']; // Languages that use the Python SDK const PY_SDK_LANGUAGES = ['python3']; +// Languages that use the TypeScript Workflow-as-Code SDK +const WAC_TS_SDK_LANGUAGES = ['bun']; + +// Languages that use the Python Workflow-as-Code SDK +const WAC_PY_SDK_LANGUAGES = PY_SDK_LANGUAGES; + // Helper to combine prompts for scripts export function getScriptPrompt(language: string): string { const langKey = `LANG_${language.toUpperCase()}` as keyof typeof prompts; @@ -2004,16 +2010,38 @@ export function getDatatableSdkReference(): string { } // Helper to combine prompts for Workflow-as-Code scripts -export function getWorkflowAsCodePrompt(): string { +export function getWorkflowAsCodePrompt(language?: string): string { + let sdkPrompt = ''; + + if (language == null) { + sdkPrompt = [ + prompts.WAC_SDK_TYPESCRIPT, + prompts.WAC_SDK_PYTHON + ].filter(Boolean).join('\\n\\n'); + } else if (WAC_TS_SDK_LANGUAGES.includes(language)) { + sdkPrompt = prompts.WAC_SDK_TYPESCRIPT; + } else if (WAC_PY_SDK_LANGUAGES.includes(language)) { + sdkPrompt = prompts.WAC_SDK_PYTHON; + } else { + return ''; + } + return [ prompts.WORKFLOW_AS_CODE_BASE, - prompts.WAC_SDK_TYPESCRIPT, - prompts.WAC_SDK_PYTHON + sdkPrompt ].filter(Boolean).join('\\n\\n'); } """ (OUTPUT_GENERATED_DIR / "index.ts").write_text(index_content) + index_dts_content = """export * from './prompts'; +export declare function getScriptPrompt(language: string): string; +export declare function getFlowPrompt(): string; +export declare function getDatatableSdkReference(): string; +export declare function getWorkflowAsCodePrompt(language?: string): string; +""" + (OUTPUT_GENERATED_DIR / "index.d.ts").write_text(index_dts_content) + # Generate skill files for Claude Code CLI_GUIDANCE_DIR.mkdir(parents=True, exist_ok=True) skills = generate_skills(