|
|
|
@@ -89,6 +89,9 @@ export interface AppAIChatHelpers {
|
|
|
|
|
|
|
|
|
|
// ============= Utility =============
|
|
|
|
|
|
|
|
|
|
/** Maximum characters per file in get_files tool */
|
|
|
|
|
const BATCH_FILE_SIZE_LIMIT = 2500
|
|
|
|
|
|
|
|
|
|
/** Memoize a factory function - the factory is only called once, on first access */
|
|
|
|
|
const memo = <T>(factory: () => T): (() => T) => {
|
|
|
|
|
let cached: T | undefined
|
|
|
|
@@ -97,15 +100,6 @@ const memo = <T>(factory: () => T): (() => T) => {
|
|
|
|
|
|
|
|
|
|
// ============= Frontend File Tools =============
|
|
|
|
|
|
|
|
|
|
const getListFrontendFilesSchema = memo(() => z.object({}))
|
|
|
|
|
const getListFrontendFilesToolDef = memo(() =>
|
|
|
|
|
createToolDef(
|
|
|
|
|
getListFrontendFilesSchema(),
|
|
|
|
|
'list_frontend_files',
|
|
|
|
|
'List all frontend file paths in the raw app. Returns an array of file paths without content. Use this for overview, then get specific files.'
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const getGetFrontendFileSchema = memo(() =>
|
|
|
|
|
z.object({
|
|
|
|
|
path: z
|
|
|
|
@@ -121,15 +115,6 @@ const getGetFrontendFileToolDef = memo(() =>
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const getGetFrontendFilesSchema = memo(() => z.object({}))
|
|
|
|
|
const getGetFrontendFilesToolDef = memo(() =>
|
|
|
|
|
createToolDef(
|
|
|
|
|
getGetFrontendFilesSchema(),
|
|
|
|
|
'get_frontend_files',
|
|
|
|
|
'Get all frontend files in the raw app. Returns a record of file paths to their content. Use list_frontend_files + get_frontend_file for large apps.'
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const getSetFrontendFileSchema = memo(() =>
|
|
|
|
|
z.object({
|
|
|
|
|
path: z
|
|
|
|
@@ -163,15 +148,6 @@ const getDeleteFrontendFileToolDef = memo(() =>
|
|
|
|
|
|
|
|
|
|
// ============= Backend Runnable Tools =============
|
|
|
|
|
|
|
|
|
|
const getListBackendRunnablesSchema = memo(() => z.object({}))
|
|
|
|
|
const getListBackendRunnablesToolDef = memo(() =>
|
|
|
|
|
createToolDef(
|
|
|
|
|
getListBackendRunnablesSchema(),
|
|
|
|
|
'list_backend_runnables',
|
|
|
|
|
'List all backend runnable keys and names in the raw app. Returns an array without full content. Use this for overview, then get specific runnables.'
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const getGetBackendRunnableSchema = memo(() =>
|
|
|
|
|
z.object({
|
|
|
|
|
key: z.string().describe('The key of the backend runnable to get')
|
|
|
|
@@ -185,15 +161,6 @@ const getGetBackendRunnableToolDef = memo(() =>
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const getGetBackendRunnablesSchema = memo(() => z.object({}))
|
|
|
|
|
const getGetBackendRunnablesToolDef = memo(() =>
|
|
|
|
|
createToolDef(
|
|
|
|
|
getGetBackendRunnablesSchema(),
|
|
|
|
|
'get_backend_runnables',
|
|
|
|
|
'Get all backend runnables in the raw app. Returns a record of runnable keys to their configuration. Use list_backend_runnables + get_backend_runnable for large apps.'
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const getInlineScriptSchema = memo(() =>
|
|
|
|
|
z.object({
|
|
|
|
|
language: z.enum(['bun', 'python3']).describe('The language of the inline script'),
|
|
|
|
@@ -273,7 +240,7 @@ const getGetFilesToolDef = memo(() =>
|
|
|
|
|
createToolDef(
|
|
|
|
|
getGetFilesSchema(),
|
|
|
|
|
'get_files',
|
|
|
|
|
'Get all files in the raw app, including both frontend files and backend runnables as separate records.'
|
|
|
|
|
'Get an overview of all files in the app. Content may be truncated for large apps - use get_frontend_file or get_backend_runnable for full content of specific files.'
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
@@ -443,7 +410,7 @@ function formatLintResultResponse(message: string, lintResult: LintResult): stri
|
|
|
|
|
// ============= Tools Array =============
|
|
|
|
|
|
|
|
|
|
export const getAppTools = memo((): Tool<AppAIChatHelpers>[] => [
|
|
|
|
|
// Combined files tool
|
|
|
|
|
// Combined files tool (with per-file truncation for large apps)
|
|
|
|
|
{
|
|
|
|
|
def: getGetFilesToolDef(),
|
|
|
|
|
fn: async ({ helpers, toolId, toolCallbacks }) => {
|
|
|
|
@@ -451,10 +418,50 @@ export const getAppTools = memo((): Tool<AppAIChatHelpers>[] => [
|
|
|
|
|
const files = helpers.getFiles()
|
|
|
|
|
const frontendCount = Object.keys(files.frontend).length
|
|
|
|
|
const backendCount = Object.keys(files.backend).length
|
|
|
|
|
|
|
|
|
|
// Truncate each file individually to BATCH_FILE_SIZE_LIMIT
|
|
|
|
|
let anyTruncated = false
|
|
|
|
|
const truncatedFiles: AppFiles = {
|
|
|
|
|
frontend: {},
|
|
|
|
|
backend: {}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const [path, content] of Object.entries(files.frontend)) {
|
|
|
|
|
if (content.length > BATCH_FILE_SIZE_LIMIT) {
|
|
|
|
|
truncatedFiles.frontend[path] =
|
|
|
|
|
content.slice(0, BATCH_FILE_SIZE_LIMIT) + '\n... [TRUNCATED]'
|
|
|
|
|
anyTruncated = true
|
|
|
|
|
} else {
|
|
|
|
|
truncatedFiles.frontend[path] = content
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const [key, runnable] of Object.entries(files.backend)) {
|
|
|
|
|
const runnableCopy = { ...runnable }
|
|
|
|
|
if (runnableCopy.inlineScript?.content) {
|
|
|
|
|
const content = runnableCopy.inlineScript.content
|
|
|
|
|
if (content.length > BATCH_FILE_SIZE_LIMIT) {
|
|
|
|
|
runnableCopy.inlineScript = {
|
|
|
|
|
...runnableCopy.inlineScript,
|
|
|
|
|
content: content.slice(0, BATCH_FILE_SIZE_LIMIT) + '\n... [TRUNCATED]'
|
|
|
|
|
}
|
|
|
|
|
anyTruncated = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
truncatedFiles.backend[key] = runnableCopy
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
toolCallbacks.setToolStatus(toolId, {
|
|
|
|
|
content: `Retrieved ${frontendCount} frontend files and ${backendCount} backend runnables`
|
|
|
|
|
content: `Retrieved ${frontendCount} frontend files and ${backendCount} backend runnables${anyTruncated ? ' (some truncated)' : ''}`
|
|
|
|
|
})
|
|
|
|
|
return JSON.stringify(files, null, 2)
|
|
|
|
|
|
|
|
|
|
let result = JSON.stringify(truncatedFiles, null, 2)
|
|
|
|
|
if (anyTruncated) {
|
|
|
|
|
result +=
|
|
|
|
|
'\n\nNote: Some file contents were truncated. Use get_frontend_file(path) or get_backend_runnable(key) to get full content.'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
// Selected context tool
|
|
|
|
@@ -473,16 +480,7 @@ export const getAppTools = memo((): Tool<AppAIChatHelpers>[] => [
|
|
|
|
|
return JSON.stringify(context, null, 2)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
// Frontend tools - list
|
|
|
|
|
{
|
|
|
|
|
def: getListFrontendFilesToolDef(),
|
|
|
|
|
fn: async ({ helpers, toolId, toolCallbacks }) => {
|
|
|
|
|
toolCallbacks.setToolStatus(toolId, { content: 'Listing frontend files...' })
|
|
|
|
|
const paths = helpers.listFrontendFiles()
|
|
|
|
|
toolCallbacks.setToolStatus(toolId, { content: `Found ${paths.length} frontend files` })
|
|
|
|
|
return JSON.stringify(paths, null, 2)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
// Frontend tools
|
|
|
|
|
{
|
|
|
|
|
def: getGetFrontendFileToolDef(),
|
|
|
|
|
fn: async ({ args, helpers, toolId, toolCallbacks }) => {
|
|
|
|
@@ -502,16 +500,6 @@ export const getAppTools = memo((): Tool<AppAIChatHelpers>[] => [
|
|
|
|
|
return content
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
def: getGetFrontendFilesToolDef(),
|
|
|
|
|
fn: async ({ helpers, toolId, toolCallbacks }) => {
|
|
|
|
|
toolCallbacks.setToolStatus(toolId, { content: 'Getting frontend files...' })
|
|
|
|
|
const files = helpers.getFrontendFiles()
|
|
|
|
|
const fileCount = Object.keys(files).length
|
|
|
|
|
toolCallbacks.setToolStatus(toolId, { content: `Retrieved ${fileCount} frontend files` })
|
|
|
|
|
return JSON.stringify(files, null, 2)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
def: getSetFrontendFileToolDef(),
|
|
|
|
|
fn: async ({ args, helpers, toolId, toolCallbacks }) => {
|
|
|
|
@@ -548,16 +536,7 @@ export const getAppTools = memo((): Tool<AppAIChatHelpers>[] => [
|
|
|
|
|
return `Frontend file '${parsedArgs.path}' has been deleted successfully`
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
// Backend tools - list
|
|
|
|
|
{
|
|
|
|
|
def: getListBackendRunnablesToolDef(),
|
|
|
|
|
fn: async ({ helpers, toolId, toolCallbacks }) => {
|
|
|
|
|
toolCallbacks.setToolStatus(toolId, { content: 'Listing backend runnables...' })
|
|
|
|
|
const list = helpers.listBackendRunnables()
|
|
|
|
|
toolCallbacks.setToolStatus(toolId, { content: `Found ${list.length} backend runnables` })
|
|
|
|
|
return JSON.stringify(list, null, 2)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
// Backend tools
|
|
|
|
|
{
|
|
|
|
|
def: getGetBackendRunnableToolDef(),
|
|
|
|
|
fn: async ({ args, helpers, toolId, toolCallbacks }) => {
|
|
|
|
@@ -577,16 +556,6 @@ export const getAppTools = memo((): Tool<AppAIChatHelpers>[] => [
|
|
|
|
|
return JSON.stringify(runnable, null, 2)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
def: getGetBackendRunnablesToolDef(),
|
|
|
|
|
fn: async ({ helpers, toolId, toolCallbacks }) => {
|
|
|
|
|
toolCallbacks.setToolStatus(toolId, { content: 'Getting backend runnables...' })
|
|
|
|
|
const runnables = helpers.getBackendRunnables()
|
|
|
|
|
const count = Object.keys(runnables).length
|
|
|
|
|
toolCallbacks.setToolStatus(toolId, { content: `Retrieved ${count} backend runnables` })
|
|
|
|
|
return JSON.stringify(runnables, null, 2)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
def: getSetBackendRunnableToolDef(),
|
|
|
|
|
fn: async ({ args, helpers, toolId, toolCallbacks }) => {
|
|
|
|
@@ -707,15 +676,11 @@ For inline scripts, the code must have a \`main\` function as its entrypoint.
|
|
|
|
|
## Available Tools
|
|
|
|
|
|
|
|
|
|
### File Management
|
|
|
|
|
- \`get_files()\`: Get both frontend files and backend runnables (use for small apps or full overview)
|
|
|
|
|
- \`list_frontend_files()\`: List all frontend file paths without content (efficient for large apps)
|
|
|
|
|
- \`get_frontend_file(path)\`: Get content of a specific frontend file
|
|
|
|
|
- \`get_frontend_files()\`: Get all frontend files with content (use list + get for large apps)
|
|
|
|
|
- \`get_files()\`: Get an overview of all files (content may be truncated for large files)
|
|
|
|
|
- \`get_frontend_file(path)\`: Get full content of a specific frontend file
|
|
|
|
|
- \`set_frontend_file(path, content)\`: Create or update a frontend file. Returns lint diagnostics.
|
|
|
|
|
- \`delete_frontend_file(path)\`: Delete a frontend file
|
|
|
|
|
- \`list_backend_runnables()\`: List all backend runnable keys and names (efficient for large apps)
|
|
|
|
|
- \`get_backend_runnable(key)\`: Get full configuration of a specific backend runnable
|
|
|
|
|
- \`get_backend_runnables()\`: Get all backend runnables (use list + get for large apps)
|
|
|
|
|
- \`set_backend_runnable(key, name, type, ...)\`: Create or update a backend runnable. Returns lint diagnostics.
|
|
|
|
|
- \`delete_backend_runnable(key)\`: Delete a backend runnable
|
|
|
|
|
|
|
|
|
@@ -726,12 +691,6 @@ For inline scripts, the code must have a \`main\` function as its entrypoint.
|
|
|
|
|
- \`list_workspace_runnables(query, type?)\`: Search workspace scripts and flows
|
|
|
|
|
- \`search_hub_scripts(query)\`: Search hub scripts
|
|
|
|
|
|
|
|
|
|
### Best Practices
|
|
|
|
|
For large apps with many files or runnables:
|
|
|
|
|
1. Use \`list_frontend_files()\` or \`list_backend_runnables()\` first to get an overview
|
|
|
|
|
2. Then use \`get_frontend_file(path)\` or \`get_backend_runnable(key)\` to inspect specific items
|
|
|
|
|
3. This approach is more efficient and avoids overwhelming the context with too much content
|
|
|
|
|
|
|
|
|
|
## Backend Runnable Configuration
|
|
|
|
|
|
|
|
|
|
When creating a backend runnable with \`set_backend_runnable\`:
|
|
|
|
@@ -780,15 +739,6 @@ When creating a backend runnable with \`set_backend_runnable\`:
|
|
|
|
|
}
|
|
|
|
|
\`\`\`
|
|
|
|
|
|
|
|
|
|
## Instructions
|
|
|
|
|
|
|
|
|
|
Follow the user instructions carefully. When creating a new app:
|
|
|
|
|
1. First use \`get_files\` to see the current state (includes wmill.d.ts showing how to call backend functions)
|
|
|
|
|
2. Create frontend files using \`set_frontend_file\`. This returns lint diagnostics.
|
|
|
|
|
3. Create backend runnables using \`set_backend_runnable\`. This returns lint diagnostics.
|
|
|
|
|
4. Use \`lint()\` to check for errors at any time
|
|
|
|
|
5. Use \`list_workspace_runnables\` or \`search_hub_scripts\` to find existing scripts/flows to reuse
|
|
|
|
|
6. Always fix any lint errors before finishing
|
|
|
|
|
|
|
|
|
|
Windmill expects all backend runnable calls to use an object parameter structure. For example for:
|
|
|
|
|
\`\`\`typescript
|
|
|
|
@@ -808,7 +758,15 @@ await backend.myFunction()
|
|
|
|
|
|
|
|
|
|
When you are using the windmill-client, do not forget that as id for variables or resources, those are path that are of the form \'u/<user>/<name>\' or \'f/<folder>/<name>\'.
|
|
|
|
|
|
|
|
|
|
Explain what you're doing as you work. Show file contents before setting them when making significant changes.
|
|
|
|
|
## Instructions
|
|
|
|
|
|
|
|
|
|
1. Start with \`get_files()\` to get an overview of all frontend files and backend runnables (content may be truncated for large files)
|
|
|
|
|
2. Use \`get_frontend_file(path)\` or \`get_backend_runnable(key)\` to get full content of specific files when needed
|
|
|
|
|
3. Make changes using \`set_frontend_file\` and \`set_backend_runnable\`. These return lint diagnostics.
|
|
|
|
|
4. Use \`lint()\` at the end to check for and fix any remaining errors
|
|
|
|
|
|
|
|
|
|
When creating a new app, use \`list_workspace_runnables\` or \`search_hub_scripts\` to find existing scripts/flows to reuse.
|
|
|
|
|
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
if (customPrompt?.trim()) {
|
|
|
|
@@ -823,7 +781,6 @@ Explain what you're doing as you work. Show file contents before setting them wh
|
|
|
|
|
|
|
|
|
|
export function prepareAppUserMessage(
|
|
|
|
|
instructions: string,
|
|
|
|
|
files?: AppFiles,
|
|
|
|
|
selectedContext?: SelectedContext
|
|
|
|
|
): ChatCompletionUserMessageParam {
|
|
|
|
|
let content = ''
|
|
|
|
@@ -837,21 +794,6 @@ export function prepareAppUserMessage(
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (files) {
|
|
|
|
|
if (Object.keys(files.frontend).length > 0) {
|
|
|
|
|
content += `## CURRENT FRONTEND FILES:\n`
|
|
|
|
|
for (const [path, fileContent] of Object.entries(files.frontend)) {
|
|
|
|
|
content += `\n### ${path}\n\`\`\`\n${fileContent}\n\`\`\`\n`
|
|
|
|
|
}
|
|
|
|
|
content += '\n'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (Object.keys(files.backend).length > 0) {
|
|
|
|
|
content += `## CURRENT BACKEND RUNNABLES:\n`
|
|
|
|
|
content += '\`\`\`json\n' + JSON.stringify(files.backend, null, 2) + '\n\`\`\`\n\n'
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
content += `## INSTRUCTIONS:\n${instructions}`
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|