Dialect-agnostic LSP core for AgentScript. Provides all LSP features — dialects and parser are injected by the caller.
Follows the Volar dependency-injection pattern: this package contains no dialect or parser code. Instead, the caller (e.g., @agentscript/lsp-server or @agentscript/lsp-browser) passes them via LspConfig.
┌───────────────────────────────────────────┐
│ @agentscript/lsp (core, dialect-agnostic) │
│ setupServer(connection, config) │
│ Providers · Pipeline · Document Store │
└────────────────────┬──────────────────────┘
│ LspConfig { dialects, parser }
┌────────────┴────────────┐
▼ ▼
┌────────────────┐ ┌─────────────────┐
│ lsp-server │ │ lsp-browser │
│ (Node.js) │ │ (Web Worker) │
│ native parser │ │ WASM parser │
│ owns dialects │ │ owns dialects │
└────────────────┘ └─────────────────┘
All providers are fully implemented:
- Diagnostics — parse, lint, and compile errors
- Hover — type and documentation info
- Completion — field and namespace completions
- Go to Definition
- Find References
- Rename
- Document Symbols / Workspace Symbols
- Code Actions — quick fixes with suggestions
- Semantic Tokens — tree-sitter query-based highlighting
pnpm add @agentscript/lspYou typically won't use this package directly — use @agentscript/lsp-server (Node.js) or @agentscript/lsp-browser (web worker) instead.
The setupServer function accepts a connection and an LspConfig:
import { setupServer } from '@agentscript/lsp';
setupServer(connection, {
// Required
dialects: [myDialect, anotherDialect],
parser: { parse: (source) => treeSitterParser.parse(source) },
// Optional
defaultDialect: 'mydialect', // Falls back to first dialect
queryExecutor: { ... }, // For semantic token highlights
compile: (name) => compileHookOrUndefined,
enableCompletionProvider: true, // Default: true
enableSemanticTokens: true, // Default: true
onBeforeInitialize: async () => {}, // e.g., WASM parser init
});Documents can declare their dialect with a comment in the first 10 lines:
# @dialect: agentforce=2.2
The LSP resolves the dialect by matching the annotation name against the dialects array. If no annotation is found, defaultDialect (or the first dialect) is used.
Create a new package under dialect/ (e.g., dialect/mydialect/):
dialect/mydialect/
├── package.json
├── tsconfig.json
└── src/
├── index.ts # Exports DialectConfig
├── pkg-meta.ts # Auto-generated name + version
├── schema.ts # Block and field definitions
└── lint/
└── passes/ # Lint rules
package.json — include the prebuild script to auto-generate pkg-meta.ts:
{
"name": "@agentscript/mydialect-dialect",
"version": "0.1.0",
"scripts": {
"prebuild": "node ../../scripts/sync-pkg-meta.mjs",
"build": "tsc"
},
"dependencies": {
"@agentscript/language": "workspace:*"
}
}src/index.ts — export a DialectConfig:
import type { DialectConfig } from '@agentscript/language';
import { MySchemaInfo } from './schema.js';
import { myRules } from './lint/passes/index.js';
import { DIALECT_NAME, DIALECT_VERSION } from './pkg-meta.js';
export const mydialect: DialectConfig = {
name: DIALECT_NAME,
displayName: 'My Dialect',
description: 'A custom AgentScript dialect',
version: DIALECT_VERSION,
schemaInfo: MySchemaInfo,
createRules: myRules,
source: 'mydialect-lint',
};The DialectConfig interface (@agentscript/language):
interface DialectConfig {
readonly name: string; // Unique identifier (e.g., 'mydialect')
readonly displayName: string; // Human-readable name (e.g., 'My Dialect')
readonly description: string; // Short description for UI display
readonly version: string; // Semver from package.json
readonly schemaInfo: SchemaInfo; // Root schema, aliases, global scopes
readonly createRules: () => LintPass[]; // Factory for fresh lint passes
readonly source?: string; // Diagnostic source tag
}schemaInfodefines the blocks, fields, and types your dialect supports. See the existingagentscriptoragentforceschemas for reference.createRulesreturns a fresh array ofLintPassinstances for each analysis run (no shared state between runs).
Each consumer that needs the dialect imports it directly. There is no central registry — @agentscript/lsp is purely dialect-agnostic.
Add your dialect package as a dependency of each consumer that needs it (e.g., @agentscript/lsp-server, @agentscript/lsp-browser, @agentscript/ui):
pnpm add @agentscript/mydialect-dialect --filter @agentscript/lsp-server --workspaceThen import and add it to the dialects array in that consumer's entry point:
import { mydialect } from '@agentscript/mydialect-dialect';
const dialects: DialectConfig[] = [
agentforceDialect,
agentscriptDialect,
mydialect, // ← add here
];pnpm build
pnpm testEach consumer where the dialect was added will pick it up. Users select it with # @dialect: mydialect at the top of their document, or it can be set as the defaultDialect.
pnpm build # Build
pnpm test # Run tests
pnpm typecheck # Type-check
pnpm dev # Watch modeMIT