@rong/agentscript 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/INSTALL.md +92 -0
- package/LICENSE +21 -0
- package/README.md +246 -0
- package/dist/ast/constants.js +1 -0
- package/dist/ast/format.js +41 -0
- package/dist/ast/types.js +1 -0
- package/dist/bin/agentscript.js +234 -0
- package/dist/bin/input.js +19 -0
- package/dist/bin/repl.js +290 -0
- package/dist/index.js +26 -0
- package/dist/parser/errors.js +8 -0
- package/dist/parser/parser.js +661 -0
- package/dist/parser/tokenizer.js +246 -0
- package/dist/providers/llm/anthropic.js +36 -0
- package/dist/providers/llm/index.js +3 -0
- package/dist/providers/llm/ollama.js +19 -0
- package/dist/providers/llm/openai.js +31 -0
- package/dist/providers/llm/protocol.js +45 -0
- package/dist/providers/llm/shared.js +147 -0
- package/dist/providers/llm/types.js +1 -0
- package/dist/providers/llm/uri.js +24 -0
- package/dist/providers/memory/file.js +44 -0
- package/dist/providers/memory/host.js +66 -0
- package/dist/providers/memory/index.js +1 -0
- package/dist/providers/memory/shared.js +56 -0
- package/dist/providers/memory/sqlite.js +98 -0
- package/dist/providers/mock/index.js +32 -0
- package/dist/providers/tools/env.js +11 -0
- package/dist/providers/tools/file.js +99 -0
- package/dist/providers/tools/host.js +34 -0
- package/dist/providers/tools/http.js +40 -0
- package/dist/providers/tools/index.js +2 -0
- package/dist/providers/tools/scheme.js +16 -0
- package/dist/providers/tools/shared.js +92 -0
- package/dist/providers/tools/shell.js +80 -0
- package/dist/runtime/context.js +160 -0
- package/dist/runtime/errors.js +14 -0
- package/dist/runtime/evaluator.js +276 -0
- package/dist/runtime/generate.js +175 -0
- package/dist/runtime/guards.js +39 -0
- package/dist/runtime/input.js +38 -0
- package/dist/runtime/interpreter.js +314 -0
- package/dist/runtime/json.js +59 -0
- package/dist/runtime/loader.js +146 -0
- package/dist/runtime/scope.js +47 -0
- package/dist/runtime/shape.js +132 -0
- package/dist/runtime/trace.js +54 -0
- package/dist/runtime/truth.js +13 -0
- package/dist/runtime/types.js +1 -0
- package/dist/runtime/uri.js +10 -0
- package/dist/semantic/analyzer.js +519 -0
- package/dist/semantic/diagnostics.js +16 -0
- package/dist/utils/assert.js +3 -0
- package/docs/cn/context-engineering.md +389 -0
- package/docs/cn/language.md +478 -0
- package/docs/design-history/v0-design.md +365 -0
- package/docs/design-history/v0-implement.md +274 -0
- package/docs/design-history/v1-design.md +323 -0
- package/docs/design-history/v1-implement.md +267 -0
- package/docs/design-history/v2-design.md +387 -0
- package/docs/design-history/v2-implement.md +399 -0
- package/docs/en/context-engineering.md +332 -0
- package/docs/en/language.md +478 -0
- package/examples/changelog.as +29 -0
- package/examples/extract.as +29 -0
- package/examples/review.as +38 -0
- package/examples/summarize.as +28 -0
- package/examples/translate.as +33 -0
- package/package.json +59 -0
- package/tutorials/cli.as +22 -0
- package/tutorials/helloworld.as +14 -0
- package/tutorials/memory.as +19 -0
- package/tutorials/plan-execute.as +155 -0
- package/tutorials/react.as +98 -0
- package/tutorials/repl.as +31 -0
- package/tutorials/self-improve.as +60 -0
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
import { SHAPE_TYPE_NAMES } from "../ast/constants.js";
|
|
2
|
+
import { SemanticError } from "./diagnostics.js";
|
|
3
|
+
class SemanticScope {
|
|
4
|
+
parent;
|
|
5
|
+
bindings = new Map();
|
|
6
|
+
configs = new Set();
|
|
7
|
+
constructor(parent) {
|
|
8
|
+
this.parent = parent;
|
|
9
|
+
}
|
|
10
|
+
define(name, binding) {
|
|
11
|
+
if (this.bindings.has(name)) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
this.bindings.set(name, binding);
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
hasLocal(name) {
|
|
18
|
+
return this.bindings.has(name);
|
|
19
|
+
}
|
|
20
|
+
resolve(name) {
|
|
21
|
+
return this.bindings.get(name) ?? this.parent?.resolve(name);
|
|
22
|
+
}
|
|
23
|
+
defineConfig(name) {
|
|
24
|
+
this.configs.add(name);
|
|
25
|
+
}
|
|
26
|
+
hasConfig(name) {
|
|
27
|
+
return this.configs.has(name) || this.parent?.hasConfig(name) === true;
|
|
28
|
+
}
|
|
29
|
+
child() {
|
|
30
|
+
return new SemanticScope(this);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export function analyze(program) {
|
|
34
|
+
const analyzer = new Analyzer();
|
|
35
|
+
return analyzer.analyze(program);
|
|
36
|
+
}
|
|
37
|
+
export function assertSemanticallyValid(program) {
|
|
38
|
+
const result = analyze(program);
|
|
39
|
+
const errors = result.diagnostics.filter((diagnostic) => diagnostic.severity === "error");
|
|
40
|
+
if (errors.length > 0) {
|
|
41
|
+
throw new SemanticError(errors);
|
|
42
|
+
}
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
45
|
+
class Analyzer {
|
|
46
|
+
diagnostics = [];
|
|
47
|
+
agentDecls = new Map();
|
|
48
|
+
importBindings = new Map();
|
|
49
|
+
agents = new Map();
|
|
50
|
+
analyze(program) {
|
|
51
|
+
this.checkImports(program);
|
|
52
|
+
this.checkAgents(program);
|
|
53
|
+
return { diagnostics: this.diagnostics };
|
|
54
|
+
}
|
|
55
|
+
checkImports(program) {
|
|
56
|
+
for (const imported of program.imports) {
|
|
57
|
+
if (this.importBindings.has(imported.name)) {
|
|
58
|
+
this.error("DUPLICATE_IMPORT", `Duplicate import '${imported.name}'`, imported.range);
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
const kind = importResourceKindToBindingKind(imported.resourceKind);
|
|
62
|
+
this.importBindings.set(imported.name, { kind, range: imported.range });
|
|
63
|
+
if (imported.resourceKind === "agent") {
|
|
64
|
+
this.agents.set(imported.name, imported.range);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
checkAgents(program) {
|
|
69
|
+
let mainAgent;
|
|
70
|
+
const seenNames = new Set();
|
|
71
|
+
for (const agent of program.agents) {
|
|
72
|
+
if (seenNames.has(agent.name)) {
|
|
73
|
+
this.error("DUPLICATE_AGENT", `Duplicate agent '${agent.name}'`, agent.range);
|
|
74
|
+
}
|
|
75
|
+
seenNames.add(agent.name);
|
|
76
|
+
this.agentDecls.set(agent.name, agent);
|
|
77
|
+
this.agents.set(agent.name, agent.range);
|
|
78
|
+
if (agent.isMain) {
|
|
79
|
+
if (mainAgent) {
|
|
80
|
+
this.error("DUPLICATE_MAIN_AGENT", "Program can only have one main agent", agent.range);
|
|
81
|
+
}
|
|
82
|
+
mainAgent = agent;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (!mainAgent && program.agents.length > 1) {
|
|
86
|
+
this.error("MISSING_MAIN_AGENT", "Program with multiple agents must declare one main agent", program.range);
|
|
87
|
+
}
|
|
88
|
+
const entryAgent = mainAgent ?? (program.agents.length === 1 ? program.agents[0] : undefined);
|
|
89
|
+
if (entryAgent && !entryAgent.functions.some((fn) => fn.isMain)) {
|
|
90
|
+
this.error("MISSING_MAIN_FUNC", "Entry agent must declare one main func", entryAgent.range);
|
|
91
|
+
}
|
|
92
|
+
for (const agent of program.agents) {
|
|
93
|
+
this.checkAgent(agent);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
checkAgent(agent) {
|
|
97
|
+
const agentScope = new SemanticScope();
|
|
98
|
+
for (const [name, binding] of this.importBindings) {
|
|
99
|
+
agentScope.define(name, {
|
|
100
|
+
kind: binding.kind,
|
|
101
|
+
range: binding.range,
|
|
102
|
+
agentName: binding.kind === "agent" ? name : undefined,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
for (const [name, range] of this.agents) {
|
|
106
|
+
if (!agentScope.hasLocal(name)) {
|
|
107
|
+
agentScope.define(name, { kind: "agent", range, agentName: name });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
const functionNames = new Set();
|
|
111
|
+
let mainFunc;
|
|
112
|
+
for (const fn of agent.functions) {
|
|
113
|
+
if (functionNames.has(fn.name)) {
|
|
114
|
+
this.error("DUPLICATE_FUNCTION", `Duplicate function '${fn.name}' in agent '${agent.name}'`, fn.range);
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (fn.isMain) {
|
|
118
|
+
if (mainFunc) {
|
|
119
|
+
this.error("DUPLICATE_MAIN_FUNC", `Agent '${agent.name}' can only have one main func`, fn.range);
|
|
120
|
+
}
|
|
121
|
+
mainFunc = fn;
|
|
122
|
+
}
|
|
123
|
+
const existing = agentScope.resolve(fn.name);
|
|
124
|
+
if (existing && existing.kind !== "function") {
|
|
125
|
+
this.error("DUPLICATE_BINDING", `Function '${fn.name}' conflicts with an imported ${existing.kind}`, fn.range);
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
functionNames.add(fn.name);
|
|
129
|
+
agentScope.define(fn.name, functionBinding(agent.name, fn));
|
|
130
|
+
}
|
|
131
|
+
for (const config of agent.config) {
|
|
132
|
+
this.checkConfig(config, agentScope);
|
|
133
|
+
}
|
|
134
|
+
for (const fn of agent.functions) {
|
|
135
|
+
this.checkFunction(fn, agentScope);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
checkFunction(fn, agentScope) {
|
|
139
|
+
const scope = agentScope.child();
|
|
140
|
+
const params = new Set();
|
|
141
|
+
for (const [index, param] of fn.params.entries()) {
|
|
142
|
+
if (params.has(param.name)) {
|
|
143
|
+
this.error("DUPLICATE_PARAM", `Duplicate parameter '${param.name}' in function '${fn.name}'`, param.range);
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
params.add(param.name);
|
|
147
|
+
const existing = scope.resolve(param.name);
|
|
148
|
+
if (existing && isImportedBinding(existing.kind)) {
|
|
149
|
+
this.error("PARAM_SHADOWS_IMPORT", `Parameter '${param.name}' conflicts with an imported ${existing.kind}`, param.range);
|
|
150
|
+
}
|
|
151
|
+
scope.define(param.name, { kind: "param", range: param.range });
|
|
152
|
+
if (param.shape) {
|
|
153
|
+
if (!fn.isMain || index !== 0 || param.name !== "input") {
|
|
154
|
+
this.error("INVALID_PARAM_SHAPE", "V0 only supports shape declarations on the main func input parameter", param.range);
|
|
155
|
+
}
|
|
156
|
+
this.checkShapeObject(param.shape);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
for (const stmt of fn.body) {
|
|
160
|
+
this.checkStatement(stmt, scope);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
checkStatement(stmt, scope) {
|
|
164
|
+
switch (stmt.kind) {
|
|
165
|
+
case "ConfigStmt":
|
|
166
|
+
this.checkConfig(stmt, scope);
|
|
167
|
+
break;
|
|
168
|
+
case "UseStmt":
|
|
169
|
+
this.checkExpression(stmt.value, scope);
|
|
170
|
+
this.checkUseValue(stmt.value, scope);
|
|
171
|
+
if (stmt.budget && stmt.budget.amount <= 0) {
|
|
172
|
+
this.error("INVALID_BUDGET", "Budget amount must be greater than 0", stmt.range);
|
|
173
|
+
}
|
|
174
|
+
break;
|
|
175
|
+
case "AssignStmt":
|
|
176
|
+
this.checkAssignment(stmt, scope);
|
|
177
|
+
break;
|
|
178
|
+
case "ExprStmt":
|
|
179
|
+
this.checkExpression(stmt.expr, scope);
|
|
180
|
+
break;
|
|
181
|
+
case "IfStmt":
|
|
182
|
+
this.checkExpression(stmt.condition, scope);
|
|
183
|
+
this.checkBlock(stmt.thenBody, scope.child());
|
|
184
|
+
if (stmt.elseBody) {
|
|
185
|
+
this.checkBlock(stmt.elseBody, scope.child());
|
|
186
|
+
}
|
|
187
|
+
break;
|
|
188
|
+
case "ForInStmt": {
|
|
189
|
+
this.checkExpression(stmt.iterable, scope);
|
|
190
|
+
if (stmt.maxIterations <= 0) {
|
|
191
|
+
this.error("INVALID_ITERATION_LIMIT", "For iteration count must be greater than 0", stmt.range);
|
|
192
|
+
}
|
|
193
|
+
const child = scope.child();
|
|
194
|
+
child.define(stmt.itemName, { kind: "local", range: stmt.itemRange });
|
|
195
|
+
this.checkBlock(stmt.body, child);
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
case "LoopUntilStmt":
|
|
199
|
+
this.checkExpression(stmt.condition, scope);
|
|
200
|
+
this.checkBlock(stmt.body, scope.child());
|
|
201
|
+
break;
|
|
202
|
+
case "RepeatStmt":
|
|
203
|
+
this.checkRepeat(stmt, scope);
|
|
204
|
+
break;
|
|
205
|
+
case "ReturnStmt":
|
|
206
|
+
this.checkExpression(stmt.value, scope);
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
checkConfig(config, scope) {
|
|
211
|
+
scope.defineConfig(config.key);
|
|
212
|
+
switch (config.key) {
|
|
213
|
+
case "model": {
|
|
214
|
+
if (config.value.kind !== "IdentifierExpr") {
|
|
215
|
+
this.error("INVALID_CONFIG", "model must reference an imported llm name", config.value.range);
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
const binding = scope.resolve(config.value.name);
|
|
219
|
+
if (!binding) {
|
|
220
|
+
this.error("UNKNOWN_MODEL", `Unknown model import '${config.value.name}'`, config.value.range);
|
|
221
|
+
}
|
|
222
|
+
else if (binding.kind !== "llm") {
|
|
223
|
+
this.error("INVALID_MODEL", `model must reference an imported llm`, config.value.range);
|
|
224
|
+
}
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
case "role":
|
|
228
|
+
case "description":
|
|
229
|
+
if (config.value.kind !== "StringExpr") {
|
|
230
|
+
this.error("INVALID_CONFIG", `${config.key} must be a string`, config.value.range);
|
|
231
|
+
}
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
checkBlock(statements, scope) {
|
|
236
|
+
for (const stmt of statements) {
|
|
237
|
+
this.checkStatement(stmt, scope);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
checkRepeat(stmt, scope) {
|
|
241
|
+
this.checkBlock(stmt.body, scope.child());
|
|
242
|
+
}
|
|
243
|
+
checkAssignment(stmt, scope) {
|
|
244
|
+
this.checkExpression(stmt.value, scope);
|
|
245
|
+
if (stmt.target.kind === "IdentifierExpr") {
|
|
246
|
+
if (!scope.hasLocal(stmt.target.name)) {
|
|
247
|
+
scope.define(stmt.target.name, { kind: "local", range: stmt.target.range });
|
|
248
|
+
}
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
if (stmt.target.kind === "MemberExpr") {
|
|
252
|
+
this.checkExpression(stmt.target.object, scope);
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
this.error("INVALID_ASSIGNMENT_TARGET", "Assignment target must be an identifier or member expression", stmt.target.range);
|
|
256
|
+
}
|
|
257
|
+
checkExpression(expr, scope) {
|
|
258
|
+
switch (expr.kind) {
|
|
259
|
+
case "IdentifierExpr":
|
|
260
|
+
this.checkIdentifier(expr.name, expr.range, scope);
|
|
261
|
+
break;
|
|
262
|
+
case "StringExpr":
|
|
263
|
+
case "NumberExpr":
|
|
264
|
+
case "BooleanExpr":
|
|
265
|
+
case "NullExpr":
|
|
266
|
+
break;
|
|
267
|
+
case "ListExpr":
|
|
268
|
+
for (const item of expr.items) {
|
|
269
|
+
this.checkExpression(item, scope);
|
|
270
|
+
}
|
|
271
|
+
break;
|
|
272
|
+
case "ObjectExpr":
|
|
273
|
+
for (const property of expr.properties) {
|
|
274
|
+
this.checkExpression(property.value, scope);
|
|
275
|
+
}
|
|
276
|
+
break;
|
|
277
|
+
case "ShapeObjectExpr":
|
|
278
|
+
this.checkShapeObject(expr);
|
|
279
|
+
break;
|
|
280
|
+
case "MemberExpr":
|
|
281
|
+
this.checkMember(expr, scope);
|
|
282
|
+
break;
|
|
283
|
+
case "IndexExpr":
|
|
284
|
+
this.checkExpression(expr.object, scope);
|
|
285
|
+
this.checkExpression(expr.index, scope);
|
|
286
|
+
break;
|
|
287
|
+
case "UnaryExpr":
|
|
288
|
+
this.checkExpression(expr.value, scope);
|
|
289
|
+
break;
|
|
290
|
+
case "BinaryExpr":
|
|
291
|
+
this.checkExpression(expr.left, scope);
|
|
292
|
+
this.checkExpression(expr.right, scope);
|
|
293
|
+
break;
|
|
294
|
+
case "CallExpr":
|
|
295
|
+
this.checkCall(expr, scope);
|
|
296
|
+
break;
|
|
297
|
+
case "GenerateExpr":
|
|
298
|
+
this.checkGenerate(expr, scope);
|
|
299
|
+
break;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
checkIdentifier(name, range, scope) {
|
|
303
|
+
if (!scope.resolve(name)) {
|
|
304
|
+
this.error("UNKNOWN_IDENTIFIER", `Unknown identifier '${name}'`, range);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
checkUseValue(expr, scope) {
|
|
308
|
+
const root = this.rootIdentifier(expr);
|
|
309
|
+
if (!root) {
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
const binding = scope.resolve(root.name);
|
|
313
|
+
if (binding && NON_CONTEXT_BINDING_KINDS.has(binding.kind)) {
|
|
314
|
+
this.error("INVALID_USE_RESOURCE", `Resource '${root.name}' cannot be used as prompt context`, root.range);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
rootIdentifier(expr) {
|
|
318
|
+
switch (expr.kind) {
|
|
319
|
+
case "IdentifierExpr":
|
|
320
|
+
return { name: expr.name, range: expr.range };
|
|
321
|
+
case "MemberExpr":
|
|
322
|
+
return this.rootIdentifier(expr.object);
|
|
323
|
+
case "IndexExpr":
|
|
324
|
+
return this.rootIdentifier(expr.object);
|
|
325
|
+
default:
|
|
326
|
+
return undefined;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
checkMember(expr, scope) {
|
|
330
|
+
this.checkExpression(expr.object, scope);
|
|
331
|
+
}
|
|
332
|
+
checkCall(expr, scope) {
|
|
333
|
+
this.checkCallable(expr.callee, scope);
|
|
334
|
+
for (const arg of expr.args) {
|
|
335
|
+
this.checkExpression(arg, scope);
|
|
336
|
+
}
|
|
337
|
+
this.checkCallArity(expr, scope);
|
|
338
|
+
}
|
|
339
|
+
checkCallable(callee, scope) {
|
|
340
|
+
if (callee.kind === "IdentifierExpr") {
|
|
341
|
+
const binding = scope.resolve(callee.name);
|
|
342
|
+
if (!binding) {
|
|
343
|
+
this.error("UNKNOWN_FUNCTION", `Unknown function '${callee.name}'`, callee.range);
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
if (binding.kind !== "function" && binding.kind !== "agent") {
|
|
347
|
+
this.error("NOT_CALLABLE", `'${callee.name}' is not an agent function or agent`, callee.range);
|
|
348
|
+
}
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
if (callee.kind === "MemberExpr") {
|
|
352
|
+
this.checkExpression(callee.object, scope);
|
|
353
|
+
if (callee.object.kind === "IdentifierExpr") {
|
|
354
|
+
const binding = scope.resolve(callee.object.name);
|
|
355
|
+
if (binding?.kind === "memory" && !VALID_MEMORY_METHODS.has(callee.property)) {
|
|
356
|
+
this.error("UNKNOWN_MEMORY_METHOD", `Unknown memory method '${callee.object.name}.${callee.property}'`, callee.range);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
this.checkExpression(callee, scope);
|
|
362
|
+
this.error("NOT_CALLABLE", "Only agent functions and member calls can be called in V0", callee.range);
|
|
363
|
+
}
|
|
364
|
+
checkGenerate(expr, scope) {
|
|
365
|
+
if (expr.options.input) {
|
|
366
|
+
this.checkExpression(expr.options.input, scope);
|
|
367
|
+
}
|
|
368
|
+
this.checkGenerateInput(expr);
|
|
369
|
+
this.checkShapeObject(expr.returnShape);
|
|
370
|
+
this.checkGenerateConfig("model", expr, scope);
|
|
371
|
+
this.checkGenerateConfig("role", expr, scope);
|
|
372
|
+
this.checkGenerateConfig("description", expr, scope);
|
|
373
|
+
}
|
|
374
|
+
checkGenerateInput(expr) {
|
|
375
|
+
let hasInput = false;
|
|
376
|
+
const seen = new Set();
|
|
377
|
+
for (const property of expr.options.properties) {
|
|
378
|
+
if (seen.has(property.key)) {
|
|
379
|
+
this.error("DUPLICATE_GENERATE_OPTION", `Duplicate generate option '${property.key}'`, property.range);
|
|
380
|
+
}
|
|
381
|
+
seen.add(property.key);
|
|
382
|
+
if (property.key === "input") {
|
|
383
|
+
hasInput = true;
|
|
384
|
+
}
|
|
385
|
+
else if (property.key === "attempts") {
|
|
386
|
+
const isPositiveInteger = property.value.kind === "NumberExpr" &&
|
|
387
|
+
Number.isInteger(property.value.value) &&
|
|
388
|
+
property.value.value > 0;
|
|
389
|
+
if (!isPositiveInteger) {
|
|
390
|
+
this.error("INVALID_GENERATE_ATTEMPTS", "generate attempts must be a positive integer", property.value.range);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
else if (property.key === "limit") {
|
|
394
|
+
if (!expr.options.limit || expr.options.limit.amount <= 0) {
|
|
395
|
+
this.error("INVALID_GENERATE_LIMIT", "generate limit must be a positive budget", property.value.range);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
else if (property.key === "debug") {
|
|
399
|
+
if (property.value.kind !== "BooleanExpr") {
|
|
400
|
+
this.error("INVALID_GENERATE_DEBUG", "generate debug must be a boolean", property.value.range);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
else {
|
|
404
|
+
this.error("UNKNOWN_GENERATE_OPTION", `Unknown generate option '${property.key}'`, property.range);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
if (!hasInput) {
|
|
408
|
+
this.error("INVALID_GENERATE_INPUT", "generate object argument requires an input field", expr.options.range);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
checkGenerateConfig(key, expr, scope) {
|
|
412
|
+
if (!scope.hasConfig(key)) {
|
|
413
|
+
this.error("MISSING_GENERATE_CONFIG", `generate requires ${key} in the current scope`, expr.range);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
checkCallArity(expr, scope) {
|
|
417
|
+
if (expr.callee.kind === "IdentifierExpr") {
|
|
418
|
+
const binding = scope.resolve(expr.callee.name);
|
|
419
|
+
if (binding?.kind === "function") {
|
|
420
|
+
this.checkExpectedArity(binding, expr.args.length, expr.range);
|
|
421
|
+
}
|
|
422
|
+
else if (binding?.kind === "agent" && binding.agentName) {
|
|
423
|
+
const fn = this.findAgentMainFunction(binding.agentName);
|
|
424
|
+
if (!fn) {
|
|
425
|
+
this.error("UNKNOWN_FUNCTION", `Agent '${binding.agentName}' has no callable main func`, expr.callee.range);
|
|
426
|
+
}
|
|
427
|
+
else {
|
|
428
|
+
this.checkExpectedArity(functionBinding(binding.agentName, fn), expr.args.length, expr.range);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
if (expr.callee.kind === "MemberExpr" && expr.callee.object.kind === "IdentifierExpr") {
|
|
434
|
+
const binding = scope.resolve(expr.callee.object.name);
|
|
435
|
+
if (binding?.kind === "agent" && binding.agentName) {
|
|
436
|
+
const fn = this.findAgentFunction(binding.agentName, expr.callee.property);
|
|
437
|
+
if (!fn) {
|
|
438
|
+
this.error("UNKNOWN_FUNCTION", `Unknown function '${binding.agentName}.${expr.callee.property}'`, expr.callee.range);
|
|
439
|
+
}
|
|
440
|
+
else {
|
|
441
|
+
this.checkExpectedArity(functionBinding(binding.agentName, fn), expr.args.length, expr.range);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
else if (binding?.kind === "memory") {
|
|
445
|
+
const methodName = `${expr.callee.object.name}.${expr.callee.property}`;
|
|
446
|
+
if (VALID_MEMORY_METHODS.has(expr.callee.property)) {
|
|
447
|
+
if (expr.args.length !== 1) {
|
|
448
|
+
this.error("INVALID_ARGUMENT_COUNT", `Memory method '${methodName}' expects 1 argument(s), got ${expr.args.length}`, expr.range);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
else {
|
|
452
|
+
this.error("UNKNOWN_MEMORY_METHOD", `Unknown memory method '${methodName}'`, expr.callee.range);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
checkExpectedArity(binding, actual, range) {
|
|
458
|
+
if (binding.arity === undefined || binding.arity === actual) {
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
const displayName = binding.agentName && binding.functionName ? `${binding.agentName}.${binding.functionName}` : "function";
|
|
462
|
+
this.error("INVALID_ARGUMENT_COUNT", `Function '${displayName}' expects ${binding.arity} argument(s), got ${actual}`, range);
|
|
463
|
+
}
|
|
464
|
+
findAgentFunction(agentName, functionName) {
|
|
465
|
+
return this.agentDecls.get(agentName)?.functions.find((fn) => fn.name === functionName);
|
|
466
|
+
}
|
|
467
|
+
findAgentMainFunction(agentName) {
|
|
468
|
+
return this.agentDecls.get(agentName)?.functions.find((fn) => fn.isMain);
|
|
469
|
+
}
|
|
470
|
+
checkShapeObject(shape) {
|
|
471
|
+
const fields = new Set();
|
|
472
|
+
for (const field of shape.fields) {
|
|
473
|
+
if (fields.has(field.name)) {
|
|
474
|
+
this.error("DUPLICATE_SHAPE_FIELD", `Duplicate generate return field '${field.name}'`, field.range);
|
|
475
|
+
}
|
|
476
|
+
fields.add(field.name);
|
|
477
|
+
this.checkShapeType(field.type);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
checkShapeType(type) {
|
|
481
|
+
if (type.kind === "ListShapeType") {
|
|
482
|
+
this.checkListShapeType(type);
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
this.checkNamedShapeType(type);
|
|
486
|
+
}
|
|
487
|
+
checkListShapeType(type) {
|
|
488
|
+
this.checkShapeType(type.itemType);
|
|
489
|
+
}
|
|
490
|
+
checkNamedShapeType(type) {
|
|
491
|
+
if (!SHAPE_TYPE_NAMES.has(type.name)) {
|
|
492
|
+
this.error("UNKNOWN_SHAPE_TYPE", `Unsupported shape type '${type.name}'`, type.range);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
error(code, message, range) {
|
|
496
|
+
this.diagnostics.push({ severity: "error", code, message, range });
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
const IMPORTED_BINDING_KINDS = new Set(["tool", "llm", "file", "agent", "memory"]);
|
|
500
|
+
const NON_CONTEXT_BINDING_KINDS = new Set(["tool", "llm", "agent", "function", "memory"]);
|
|
501
|
+
const VALID_MEMORY_METHODS = new Set(["add", "query"]);
|
|
502
|
+
function functionBinding(agentName, fn) {
|
|
503
|
+
return {
|
|
504
|
+
kind: "function",
|
|
505
|
+
range: fn.range,
|
|
506
|
+
agentName,
|
|
507
|
+
functionName: fn.name,
|
|
508
|
+
arity: fn.params.length,
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
function isImportedBinding(kind) {
|
|
512
|
+
return IMPORTED_BINDING_KINDS.has(kind);
|
|
513
|
+
}
|
|
514
|
+
function importResourceKindToBindingKind(resourceKind) {
|
|
515
|
+
if (IMPORTED_BINDING_KINDS.has(resourceKind)) {
|
|
516
|
+
return resourceKind;
|
|
517
|
+
}
|
|
518
|
+
return "file";
|
|
519
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export class SemanticError extends Error {
|
|
2
|
+
diagnostics;
|
|
3
|
+
constructor(diagnostics) {
|
|
4
|
+
super(formatSemanticDiagnostics(diagnostics));
|
|
5
|
+
this.name = "SemanticError";
|
|
6
|
+
this.diagnostics = diagnostics;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export function formatSemanticDiagnostics(diagnostics) {
|
|
10
|
+
return diagnostics
|
|
11
|
+
.map((diagnostic) => {
|
|
12
|
+
const { line, column } = diagnostic.range.start;
|
|
13
|
+
return `${diagnostic.severity.toUpperCase()} ${diagnostic.code} at ${line}:${column}: ${diagnostic.message}`;
|
|
14
|
+
})
|
|
15
|
+
.join("\n");
|
|
16
|
+
}
|