-
-
Notifications
You must be signed in to change notification settings - Fork 423
Devsper Language Support #3401
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
rithulkamesh
wants to merge
10
commits into
LuaLS:master
from
devsper-com:feat/devsper-language-support
Closed
Devsper Language Support #3401
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
20a690f
feat(meta): add devsper type library for completions and hover docs
rithulkamesh 52b0f6e
fix(meta): fix WorkflowBuilder method syntax, HttpLib anchoring, unio…
rithulkamesh af4d5e6
feat(diag): add devsper schema validation diagnostic
rithulkamesh 13a992b
feat(meta): add workspace config template for .devsper projects
rithulkamesh 255d89e
fix(diag): harden schema diagnostic — nil spec, receiver name check, …
rithulkamesh 3b25304
feat(diag): add devsper depends_on cross-reference diagnostic
rithulkamesh 018dafb
feat(diag): add devsper model string hint diagnostic
rithulkamesh bb725d9
feat(vscode): add devsper VS Code extension with snippets and grammar
rithulkamesh 31541c2
chore: update changelog for devsper language support
rithulkamesh 5e993c7
fix(diag): fix depends_on array node type (tableexp) and hoist sorted…
rithulkamesh File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| { | ||
| "$schema": "https://raw.githubusercontent.com/sumneko/vscode-lua/master/setting/schema.json", | ||
| "runtime": { | ||
| "version": "Lua 5.4", | ||
| "extension": { ".devsper": "lua" } | ||
| }, | ||
| "workspace": { | ||
| "library": ["${3rd}/devsper/library"], | ||
| "checkThirdParty": false | ||
| }, | ||
| "diagnostics": { | ||
| "globals": ["devsper"] | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,122 @@ | ||
| ---@meta devsper | ||
|
|
||
| --- Result returned by devsper.exec(). | ||
| ---@class devsper.ExecResult | ||
| ---@field code integer Exit code of the process. | ||
| ---@field stdout string Standard output captured from the process. | ||
| ---@field stderr string Standard error captured from the process. | ||
|
|
||
| --- HTTP response returned by devsper.http methods. | ||
| ---@class devsper.HttpResponse | ||
| ---@field status integer HTTP status code (e.g. 200, 404). | ||
| ---@field body string Response body as a string. | ||
| ---@field headers table<string,string> Response headers keyed by header name. | ||
|
|
||
| ---@class devsper.HttpLib | ||
| ---Fetch a URL and return the response. | ||
| ---@field get fun(url: string, opts?: { headers?: table<string,string>, timeout?: integer }): devsper.HttpResponse | ||
| ---Perform an HTTP POST request. | ||
| ---@field post fun(url: string, body: string|table, opts?: { headers?: table<string,string>, timeout?: integer }): devsper.HttpResponse | ||
|
|
||
| --- Context object passed to tool run functions. | ||
| ---@class devsper.ToolCtx | ||
| ---@field workflow_id string ID of the parent workflow. | ||
| ---@field run_id string ID of the current run. | ||
| local ToolCtx = {} | ||
|
|
||
| --- Emit a structured log message from within a tool. | ||
| ---@param level "debug"|"info"|"warn"|"error" Log level. | ||
| ---@param msg string Log message text. | ||
| function ToolCtx:log(level, msg) end | ||
|
|
||
| --- Parameter specification for a tool parameter. | ||
| ---@class devsper.ParamSpec | ||
| ---@field type "string"|"number"|"boolean"|"object" JSON-schema-style type. | ||
| ---@field required boolean? Whether the parameter is required. Defaults to false. | ||
| ---@field default any? Default value when the parameter is omitted. | ||
|
|
||
| --- Configuration for the evolutionary/speculative execution engine. | ||
| ---@class devsper.EvolutionConfig | ||
| ---@field allow_mutations boolean? Allow the swarm to mutate task prompts at runtime. Defaults to false. | ||
| ---@field max_depth integer? Maximum recursion/mutation depth. Defaults to 3. | ||
| ---@field speculative boolean? Enable speculative (parallel branch) execution. Defaults to false. | ||
|
|
||
| --- Top-level configuration passed to devsper.workflow(). | ||
| ---@class devsper.WorkflowConfig | ||
| ---@field name string Human-readable workflow name (required). | ||
| ---@field model string? Default LLM model ID for tasks in this workflow. | ||
| ---@field workers integer? Number of parallel worker goroutines/processes. | ||
| ---@field bus "memory"|"redis"|"kafka"? Message bus backend. Defaults to "memory". | ||
| ---@field evolution devsper.EvolutionConfig? Optional evolutionary execution configuration. | ||
|
|
||
| --- Specification for a single workflow task. | ||
| ---@class devsper.TaskSpec | ||
| ---@field prompt string Prompt sent to the LLM for this task (required). | ||
| ---@field model string? Override the workflow-level model for this task. | ||
| ---@field can_mutate boolean? Allow this task's prompt to be mutated by the evolution engine. | ||
| ---@field depends_on? (string|"*")[] Task IDs that must complete first. Use "*" to depend on all other tasks. | ||
|
|
||
| --- Specification for a workflow plugin. | ||
| ---@class devsper.PluginSpec | ||
| ---@field source string Path or import specifier for the plugin module (required). | ||
|
|
||
| --- Specification for a workflow input parameter. | ||
| ---@class devsper.InputSpec | ||
| ---@field type "string"|"number"|"boolean"|"object"? Type of the input value. | ||
| ---@field required boolean? Whether this input must be supplied at run time. | ||
| ---@field default any? Default value used when the input is not supplied. | ||
|
|
||
| --- Specification passed to devsper.tool(). | ||
| ---@class devsper.ToolSpec | ||
| ---@field description string Human-readable description of the tool (required). | ||
| ---@field params table<string, devsper.ParamSpec>? Map of parameter name to its spec. | ||
| ---@field run fun(ctx: devsper.ToolCtx, args: table): any, string? Tool implementation (required). Returns a result and an optional error string. | ||
|
|
||
| --- Fluent builder returned by devsper.workflow(). | ||
| ---@class devsper.WorkflowBuilder | ||
| local WorkflowBuilder = {} | ||
|
|
||
| --- Register a task in the workflow. | ||
| ---@param id string Unique task identifier within the workflow. | ||
| ---@param spec devsper.TaskSpec Task configuration. | ||
| ---@return devsper.WorkflowBuilder Returns the builder for chaining. | ||
| function WorkflowBuilder:task(id, spec) end | ||
|
|
||
| --- Register a plugin in the workflow. | ||
| ---@param name string Plugin name used to reference it within tasks. | ||
| ---@param spec devsper.PluginSpec Plugin configuration. | ||
| ---@return devsper.WorkflowBuilder Returns the builder for chaining. | ||
| function WorkflowBuilder:plugin(name, spec) end | ||
|
|
||
| --- Declare an input parameter for the workflow. | ||
| ---@param name string Input parameter name. | ||
| ---@param spec devsper.InputSpec Input specification. | ||
| ---@return devsper.WorkflowBuilder Returns the builder for chaining. | ||
| function WorkflowBuilder:input(name, spec) end | ||
|
|
||
| --- The devsper global injected into every .devsper file. | ||
| ---@class devsper | ||
| ---@field http devsper.HttpLib HTTP client for use inside tools or workflows. | ||
| devsper = {} | ||
|
|
||
| --- Define and register a workflow. Returns a builder for adding tasks, plugins, and inputs. | ||
| ---@param config devsper.WorkflowConfig Top-level workflow configuration. | ||
| ---@return devsper.WorkflowBuilder | ||
| function devsper.workflow(config) end | ||
|
|
||
| --- Define and register a tool that the swarm can invoke. | ||
| ---@param name string Unique tool name. | ||
| ---@param spec devsper.ToolSpec Tool specification including description, params, and run function. | ||
| ---@return nil | ||
| function devsper.tool(name, spec) end | ||
|
|
||
| --- Execute a subprocess and return its result. | ||
| ---@param cmd string Executable to run. | ||
| ---@param args string[]? Arguments to pass to the executable. | ||
| ---@return devsper.ExecResult | ||
| function devsper.exec(cmd, args) end | ||
|
|
||
| --- Emit a structured log message at the workflow/global level. | ||
| ---@param level "debug"|"info"|"warn"|"error" Log level. | ||
| ---@param msg string Log message text. | ||
| function devsper.log(level, msg) end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| local files = require 'files' | ||
| local guide = require 'parser.guide' | ||
| local await = require 'await' | ||
|
|
||
| -- Returns the value node of a named field in a table AST node, or nil. | ||
| local function tableGet(tblNode, key) | ||
| if not tblNode or tblNode.type ~= 'table' then return nil end | ||
| for i = 1, #tblNode do | ||
| local child = tblNode[i] | ||
| if child and child.type == 'tablefield' and child.field and child.field[1] == key then | ||
| return child.value | ||
| end | ||
| end | ||
| return nil | ||
| end | ||
|
|
||
| -- Returns true if `node` is a getfield call like `<receiverName>.methodName(...)` | ||
| local function isLocalMethod(source, receiverName, methodName) | ||
| if not source or source.type ~= 'call' then return false end | ||
| local callee = source.node | ||
| if not callee or callee.type ~= 'getfield' then return false end | ||
| if not callee.field or callee.field[1] ~= methodName then return false end | ||
| local receiver = callee.node | ||
| if not receiver or receiver.type ~= 'getlocal' then return false end | ||
| return receiver[1] == receiverName | ||
| end | ||
|
|
||
| return function (uri, callback) | ||
| -- Only run on .devsper files | ||
| if not uri:match('%.devsper$') then return end | ||
|
|
||
| local state = files.getState(uri) | ||
| if not state then return end | ||
|
|
||
| -- Pass 1: collect all declared task IDs from wf.task(id, spec) calls | ||
| local declaredIds = {} | ||
|
|
||
| guide.eachSourceType(state.ast, 'call', function (source) | ||
| await.delay() | ||
| if not isLocalMethod(source, 'wf', 'task') then return end | ||
| local args = source.args | ||
| if not args then return end | ||
| local idNode = args[1] | ||
| if not idNode or idNode.type ~= 'string' then return end | ||
| local id = idNode[1] | ||
| if id then | ||
| declaredIds[id] = { start = idNode.start, finish = idNode.finish } | ||
| end | ||
| end) | ||
|
|
||
| -- Build sorted ID list once for error messages | ||
| local sortedIds = {} | ||
| for k in pairs(declaredIds) do | ||
| sortedIds[#sortedIds + 1] = k | ||
| end | ||
| table.sort(sortedIds) | ||
| local declaredIdsStr = table.concat(sortedIds, ', ') | ||
|
|
||
| -- Pass 2: check depends_on entries in each wf.task spec | ||
| guide.eachSourceType(state.ast, 'call', function (source) | ||
| await.delay() | ||
| if not isLocalMethod(source, 'wf', 'task') then return end | ||
| local args = source.args | ||
| if not args then return end | ||
| local spec = args[2] | ||
| if not spec or spec.type ~= 'table' then return end | ||
|
|
||
| local dependsOn = tableGet(spec, 'depends_on') | ||
| if not dependsOn or dependsOn.type ~= 'table' then return end | ||
|
|
||
| -- Array table elements are wrapped in tableexp nodes in the luals AST | ||
| for i = 1, #dependsOn do | ||
| local exp = dependsOn[i] | ||
| if exp and exp.type == 'tableexp' then | ||
| local child = exp.value | ||
| if child and child.type == 'string' then | ||
| local val = child[1] | ||
| if val and val ~= '*' and not declaredIds[val] then | ||
| callback { | ||
| start = child.start, | ||
| finish = child.finish, | ||
| message = ("devsper: unknown task id '%s' in depends_on — declared ids: %s"):format( | ||
| val, declaredIdsStr | ||
| ), | ||
| } | ||
| end | ||
| end | ||
| end | ||
| -- Non-string/non-literal children silently skipped | ||
| end | ||
| end) | ||
|
Comment on lines
+59
to
+91
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This section contains two significant issues:
|
||
| end | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,104 @@ | ||
| local files = require 'files' | ||
| local guide = require 'parser.guide' | ||
| local await = require 'await' | ||
|
|
||
| -- Returns the value node of a named field in a table AST node, or nil. | ||
| local function tableGet(tblNode, key) | ||
| if not tblNode or tblNode.type ~= 'table' then return nil end | ||
| for i = 1, #tblNode do | ||
| local child = tblNode[i] | ||
| if child and child.type == 'tablefield' and child.field and child.field[1] == key then | ||
| return child.value | ||
| end | ||
| end | ||
| return nil | ||
| end | ||
|
|
||
| -- Returns true if `node` is a getfield call like `<receiverName>.methodName(...)` | ||
| local function isLocalMethod(source, receiverName, methodName) | ||
| if not source or source.type ~= 'call' then return false end | ||
| local callee = source.node | ||
| if not callee or callee.type ~= 'getfield' then return false end | ||
| if not callee.field or callee.field[1] ~= methodName then return false end | ||
| local receiver = callee.node | ||
| if not receiver or receiver.type ~= 'getlocal' then return false end | ||
| return receiver[1] == receiverName | ||
| end | ||
|
|
||
| -- Returns true if `callNode` is `devsper.<methodName>(...)` | ||
| local function isDevsperGlobalMethod(callNode, methodName) | ||
| local callee = callNode.node | ||
| if not callee then return false end | ||
| if callee.type ~= 'getfield' then return false end | ||
| if not callee.field then return false end | ||
| if callee.field[1] ~= methodName then return false end | ||
| local receiver = callee.node | ||
| if not receiver then return false end | ||
| if receiver.type ~= 'getglobal' then return false end | ||
| return receiver[1] == 'devsper' | ||
| end | ||
|
|
||
| local KNOWN_PREFIXES = { | ||
| '^claude%-', | ||
| '^gpt%-', | ||
| '^o%d', -- o1, o3, etc. | ||
| '^gemini%-', | ||
| '^llama', | ||
| '^mistral', | ||
| '^phi', | ||
| '^qwen', | ||
| '^deepseek', | ||
| '^gemma', | ||
| '^falcon', | ||
| } | ||
|
|
||
| local KNOWN_EXACT = { auto = true } | ||
|
|
||
| local function isKnownModel(modelStr) | ||
| if KNOWN_EXACT[modelStr] then return true end | ||
| for _, pattern in ipairs(KNOWN_PREFIXES) do | ||
| if modelStr:match(pattern) then return true end | ||
| end | ||
| return false | ||
| end | ||
|
|
||
| local function checkModelValue(modelNode, callback) | ||
| if not modelNode then return end | ||
| if modelNode.type ~= 'string' then return end -- skip non-literal values | ||
| local modelStr = modelNode[1] | ||
| if not modelStr then return end | ||
| if not isKnownModel(modelStr) then | ||
| callback { | ||
| start = modelNode.start, | ||
| finish = modelNode.finish, | ||
| message = ("devsper: unrecognized model '%s' — verify this provider is configured"):format(modelStr), | ||
| } | ||
| end | ||
| end | ||
|
|
||
| return function (uri, callback) | ||
| -- Only run on .devsper files | ||
| if not uri:match('%.devsper$') then return end | ||
|
|
||
| local state = files.getState(uri) | ||
| if not state then return end | ||
|
|
||
| guide.eachSourceType(state.ast, 'call', function (source) | ||
| await.delay() | ||
|
|
||
| -- devsper.workflow(config) — check config.model | ||
| if isDevsperGlobalMethod(source, 'workflow') then | ||
| local config = source.args and source.args[1] | ||
| if config and config.type == 'table' then | ||
| checkModelValue(tableGet(config, 'model'), callback) | ||
| end | ||
|
|
||
| -- wf.task(id, spec) — check spec.model | ||
| elseif isLocalMethod(source, 'wf', 'task') then | ||
| local spec = source.args and source.args[2] | ||
| if spec and spec.type == 'table' then | ||
| checkModelValue(tableGet(spec, 'model'), callback) | ||
| end | ||
| end | ||
| end) | ||
| end |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The diagnostic performs two full traversals of all function calls in the AST using
guide.eachSourceType. This is inefficient. You can optimize this by performing a single traversal to collect both the declared IDs and the dependency lists that need validation, then performing the validation in a separate loop over the collected data.