@rong/agentscript 0.1.2 → 0.1.4
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 +27 -0
- package/README.md +14 -14
- package/dist/parser/parser.js +45 -5
- package/dist/providers/llm/anthropic.js +6 -2
- package/dist/providers/llm/ollama.js +9 -2
- package/dist/providers/llm/openai.js +15 -1
- package/dist/providers/llm/shared.js +4 -4
- package/dist/runtime/context.js +15 -5
- package/dist/runtime/evaluator.js +1 -0
- package/dist/runtime/generate.js +22 -6
- package/dist/runtime/interpreter.js +5 -2
- package/dist/runtime/scope.js +2 -2
- package/dist/runtime/shape.js +6 -4
- package/dist/semantic/analyzer.js +24 -3
- package/docs/cn/context-engineering.md +49 -322
- package/docs/cn/final-expression-return.md +19 -1
- package/docs/cn/generate.md +480 -0
- package/docs/cn/language.md +25 -2
- package/docs/cn/use-as.md +225 -0
- package/docs/en/context-engineering.md +50 -268
- package/docs/en/final-expression-return.md +19 -1
- package/docs/en/generate.md +480 -0
- package/docs/en/language.md +25 -2
- package/docs/en/use-as.md +225 -0
- package/examples/changelog.as +3 -3
- package/examples/extract.as +3 -3
- package/examples/review.as +4 -4
- package/examples/summarize.as +3 -3
- package/examples/translate.as +4 -4
- package/package.json +1 -1
- package/tutorials/cli.as +1 -1
- package/tutorials/plan-execute.as +4 -4
- package/tutorials/react.as +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to AgentScript will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## 0.1.4 - 2026-05-08
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added `max_output` as the explicit `generate` output budget.
|
|
10
|
+
- Added `temperature`, `think`, and `strict` generate options.
|
|
11
|
+
- Added provider request mapping for generate provider hints.
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- Replaced generate `limit` usage with `max_output` in current docs, examples, tutorials, and fixtures.
|
|
16
|
+
- Expanded English and Chinese `generate` design docs with configuration semantics.
|
|
17
|
+
- Updated shape validation so `strict: true` disables coercion and rejects extra fields.
|
|
18
|
+
|
|
19
|
+
## 0.1.3 - 2026-05-08
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- Added final expression return for functions.
|
|
24
|
+
- Added literal context labels with `use context as label`.
|
|
25
|
+
- Added agent role and description to prompt identity construction.
|
|
26
|
+
- Added trace and built context output for context labels.
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
|
|
30
|
+
- Updated README, examples, and language references for labeled context usage.
|
|
31
|
+
|
|
5
32
|
## 1.0.0 - 2026-05-07
|
|
6
33
|
|
|
7
34
|
### Added
|
package/README.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# AgentScript
|
|
2
2
|
|
|
3
3
|
> **Agent context as code.**
|
|
4
|
-
> `use` declares what the model can see.
|
|
4
|
+
> `use` declares what the model can see, with optional labels for prompt sections.
|
|
5
5
|
> `generate` defines the only LLM call site and optional output shape.
|
|
6
6
|
> Zero runtime dependencies. TypeScript-powered.
|
|
7
7
|
|
|
8
8
|
```agentscript
|
|
9
|
-
use scratch.summary < 2k
|
|
9
|
+
use scratch.summary < 2k as observations
|
|
10
10
|
generate({ input: "Answer from observations" }) -> {
|
|
11
11
|
ok boolean
|
|
12
12
|
text string
|
|
@@ -61,12 +61,12 @@ main agent FileSummarizer {
|
|
|
61
61
|
|
|
62
62
|
main func(input { path string }) {
|
|
63
63
|
content = File.read({ path: input.path })
|
|
64
|
-
use input.path
|
|
65
|
-
use content < 8k
|
|
64
|
+
use input.path as source path
|
|
65
|
+
use content < 8k as file content
|
|
66
66
|
|
|
67
67
|
generate({
|
|
68
68
|
input: "Summarize the file for a busy teammate"
|
|
69
|
-
|
|
69
|
+
max_output: 1000
|
|
70
70
|
}) -> {
|
|
71
71
|
title string
|
|
72
72
|
summary string
|
|
@@ -113,7 +113,7 @@ It is a small language for one thing:
|
|
|
113
113
|
|
|
114
114
|
> making LLM prompt context explicit, scoped, typed, traceable, and compilable.
|
|
115
115
|
|
|
116
|
-
It gives you two things that general-purpose languages don't: a first-class `use` keyword that declares *which* data enters the LLM prompt
|
|
116
|
+
It gives you two things that general-purpose languages don't: a first-class `use` keyword that declares *which* data enters the LLM prompt and what role it plays via `as label`, and a first-class `generate` expression that defines an LLM call with an optional output contract. Everything else — variables, functions, agents, imports, loops — exists to support this core workflow. Scopes enforce context boundaries naturally: what's `use`d in one function stays there; child scopes inherit but never leak upward. Functions can also return their final top-level expression directly, which keeps typical LLM workflows concise.
|
|
117
117
|
|
|
118
118
|
## How it works
|
|
119
119
|
|
|
@@ -178,10 +178,10 @@ main agent ResearchAgent {
|
|
|
178
178
|
main func(input {
|
|
179
179
|
question string
|
|
180
180
|
}) {
|
|
181
|
-
use input.question
|
|
181
|
+
use input.question as user question
|
|
182
182
|
|
|
183
183
|
scratch = []
|
|
184
|
-
use scratch.summary < 2k
|
|
184
|
+
use scratch.summary < 2k as observations
|
|
185
185
|
|
|
186
186
|
done = false
|
|
187
187
|
loop until done < 6 {
|
|
@@ -195,8 +195,8 @@ main agent ResearchAgent {
|
|
|
195
195
|
}
|
|
196
196
|
|
|
197
197
|
func answer(question, scratch) {
|
|
198
|
-
use question
|
|
199
|
-
use scratch.summary < 2k
|
|
198
|
+
use question as user question
|
|
199
|
+
use scratch.summary < 2k as observations
|
|
200
200
|
generate({ input: "Answer using only the observations" }) -> {
|
|
201
201
|
ok boolean
|
|
202
202
|
text string
|
|
@@ -208,7 +208,7 @@ main agent ResearchAgent {
|
|
|
208
208
|
|
|
209
209
|
## Key ideas
|
|
210
210
|
|
|
211
|
-
1. **`use` is explicit context** — nothing enters the LLM prompt unless `use`d
|
|
211
|
+
1. **`use` is explicit context** — nothing enters the LLM prompt unless `use`d; `as label` names the context section
|
|
212
212
|
2. **`generate` is the only LLM call site** — with a required input instruction and optional output shape
|
|
213
213
|
3. **Final expression return keeps flows concise** — a function returns its final top-level expression
|
|
214
214
|
4. **Scope is context boundary** — functions, agents, and blocks isolate prompt visibility
|
|
@@ -219,7 +219,7 @@ main agent ResearchAgent {
|
|
|
219
219
|
|
|
220
220
|
| | Python / TypeScript | AgentScript |
|
|
221
221
|
|---|---|---|
|
|
222
|
-
| Context management | Implicit (string concatenation, array append) | Explicit (`use` declaration) |
|
|
222
|
+
| Context management | Implicit (string concatenation, array append) | Explicit (`use` declaration, optional `as label`) |
|
|
223
223
|
| LLM call site | Anywhere in the code | One `generate` expression |
|
|
224
224
|
| Context isolation | Manual discipline | Scope-inherited, auto-isolated |
|
|
225
225
|
| Trace / audit | External tooling needed | Built-in, per-call |
|
|
@@ -254,8 +254,8 @@ agentscript examples/review.as --quiet # value only, no trace
|
|
|
254
254
|
|
|
255
255
|
| Language | Links |
|
|
256
256
|
|----------|-------|
|
|
257
|
-
| English | [Language Reference](docs/en/language.md) · [Context Engineering](docs/en/context-engineering.md) · [Design History](docs/design-history/) |
|
|
258
|
-
| 中文 | [README-CN](./README-CN.md) · [语言参考](docs/cn/language.md) · [Context Engineering](docs/cn/context-engineering.md) |
|
|
257
|
+
| English | [Language Reference](docs/en/language.md) · [Context Engineering](docs/en/context-engineering.md) · [`use ... as ...`](docs/en/use-as.md) · [`generate`](docs/en/generate.md) · [Design History](docs/design-history/) |
|
|
258
|
+
| 中文 | [README-CN](./README-CN.md) · [语言参考](docs/cn/language.md) · [Context Engineering](docs/cn/context-engineering.md) · [`use ... as ...`](docs/cn/use-as.md) · [`generate`](docs/cn/generate.md) |
|
|
259
259
|
|
|
260
260
|
### Design principles
|
|
261
261
|
|
package/dist/parser/parser.js
CHANGED
|
@@ -176,13 +176,26 @@ class Parser {
|
|
|
176
176
|
const start = this.consume("use").range.start;
|
|
177
177
|
const value = this.parseLogicalOr();
|
|
178
178
|
const budget = this.match("<") ? this.parseBudget() : undefined;
|
|
179
|
+
const label = this.match("as") ? this.parseUseLabel() : undefined;
|
|
179
180
|
return {
|
|
180
181
|
kind: "UseStmt",
|
|
181
182
|
value,
|
|
182
183
|
budget,
|
|
184
|
+
label,
|
|
183
185
|
range: { start, end: this.previous().range.end }
|
|
184
186
|
};
|
|
185
187
|
}
|
|
188
|
+
parseUseLabel() {
|
|
189
|
+
const tokens = [];
|
|
190
|
+
const line = this.previous().range.start.line;
|
|
191
|
+
while (!this.isAtEnd() && !this.check("}") && this.peek().range.start.line === line) {
|
|
192
|
+
tokens.push(this.advance());
|
|
193
|
+
}
|
|
194
|
+
if (tokens.length === 0) {
|
|
195
|
+
throw this.error("Expected context label after 'as'");
|
|
196
|
+
}
|
|
197
|
+
return stringifyLabelTokens(tokens);
|
|
198
|
+
}
|
|
186
199
|
parseConfigValue(key) {
|
|
187
200
|
if (key === "model") {
|
|
188
201
|
return this.parsePostfix();
|
|
@@ -421,16 +434,19 @@ class Parser {
|
|
|
421
434
|
const properties = [];
|
|
422
435
|
let input;
|
|
423
436
|
let attempts;
|
|
424
|
-
let
|
|
437
|
+
let maxOutput;
|
|
438
|
+
let temperature;
|
|
439
|
+
let think;
|
|
440
|
+
let strict;
|
|
425
441
|
let debug;
|
|
426
442
|
while (!this.check("}") && !this.isAtEnd()) {
|
|
427
443
|
const propStart = this.peek().range.start;
|
|
428
444
|
const key = this.consumeObjectKey();
|
|
429
445
|
this.consume(":");
|
|
430
446
|
let value;
|
|
431
|
-
if (key === "
|
|
432
|
-
const token = this.consumeKind("number", "Expected generate
|
|
433
|
-
|
|
447
|
+
if (key === "max_output") {
|
|
448
|
+
const token = this.consumeKind("number", "Expected generate max_output");
|
|
449
|
+
maxOutput = this.parseBudgetToken(token);
|
|
434
450
|
value = {
|
|
435
451
|
kind: "NumberExpr",
|
|
436
452
|
value: Number.parseFloat(token.value),
|
|
@@ -453,6 +469,15 @@ class Parser {
|
|
|
453
469
|
else if (key === "attempts" && value.kind === "NumberExpr") {
|
|
454
470
|
attempts = value;
|
|
455
471
|
}
|
|
472
|
+
else if (key === "temperature" && value.kind === "NumberExpr") {
|
|
473
|
+
temperature = value;
|
|
474
|
+
}
|
|
475
|
+
else if (key === "think" && (value.kind === "BooleanExpr" || value.kind === "StringExpr")) {
|
|
476
|
+
think = value;
|
|
477
|
+
}
|
|
478
|
+
else if (key === "strict" && value.kind === "BooleanExpr") {
|
|
479
|
+
strict = value;
|
|
480
|
+
}
|
|
456
481
|
else if (key === "debug" && value.kind === "BooleanExpr") {
|
|
457
482
|
debug = value;
|
|
458
483
|
}
|
|
@@ -464,7 +489,10 @@ class Parser {
|
|
|
464
489
|
properties,
|
|
465
490
|
input,
|
|
466
491
|
attempts,
|
|
467
|
-
|
|
492
|
+
maxOutput,
|
|
493
|
+
temperature,
|
|
494
|
+
think,
|
|
495
|
+
strict,
|
|
468
496
|
debug,
|
|
469
497
|
range: { start, end: this.previous().range.end }
|
|
470
498
|
};
|
|
@@ -656,3 +684,15 @@ class Parser {
|
|
|
656
684
|
function isImportResourceKind(value) {
|
|
657
685
|
return IMPORT_RESOURCE_KINDS.has(value);
|
|
658
686
|
}
|
|
687
|
+
function stringifyLabelTokens(tokens) {
|
|
688
|
+
let label = tokens[0]?.value ?? "";
|
|
689
|
+
for (let index = 1; index < tokens.length; index += 1) {
|
|
690
|
+
const previous = tokens[index - 1];
|
|
691
|
+
const current = tokens[index];
|
|
692
|
+
if (current.range.start.column > previous.range.end.column) {
|
|
693
|
+
label += " ";
|
|
694
|
+
}
|
|
695
|
+
label += current.value;
|
|
696
|
+
}
|
|
697
|
+
return label;
|
|
698
|
+
}
|
|
@@ -5,7 +5,7 @@ export async function callAnthropic(request, parsed, options, fetchImpl, timeout
|
|
|
5
5
|
if (!apiKey) {
|
|
6
6
|
throw new RuntimeError("ANTHROPIC_API_KEY is required for anthropic:// models");
|
|
7
7
|
}
|
|
8
|
-
const
|
|
8
|
+
const body = {
|
|
9
9
|
model: parsed.model,
|
|
10
10
|
max_tokens: budgetToTokenLimit(request) ?? 1024,
|
|
11
11
|
system: request.builtContext.system,
|
|
@@ -15,7 +15,11 @@ export async function callAnthropic(request, parsed, options, fetchImpl, timeout
|
|
|
15
15
|
content: request.builtContext.finalUserMessage,
|
|
16
16
|
},
|
|
17
17
|
],
|
|
18
|
-
}
|
|
18
|
+
};
|
|
19
|
+
if (request.temperature !== undefined) {
|
|
20
|
+
body.temperature = request.temperature;
|
|
21
|
+
}
|
|
22
|
+
const response = await postJson(fetchImpl, `${baseUrl}/messages`, body, timeoutMs, {
|
|
19
23
|
"x-api-key": apiKey,
|
|
20
24
|
"anthropic-version": "2023-06-01",
|
|
21
25
|
});
|
|
@@ -3,7 +3,7 @@ export async function callOllama(request, parsed, fetchImpl, timeoutMs, baseUrl)
|
|
|
3
3
|
const body = {
|
|
4
4
|
model: parsed.model,
|
|
5
5
|
stream: false,
|
|
6
|
-
think: false,
|
|
6
|
+
think: request.think ?? false,
|
|
7
7
|
messages: [
|
|
8
8
|
{ role: "system", content: request.builtContext.system },
|
|
9
9
|
{ role: "user", content: request.builtContext.finalUserMessage },
|
|
@@ -13,8 +13,15 @@ export async function callOllama(request, parsed, fetchImpl, timeoutMs, baseUrl)
|
|
|
13
13
|
body.format = request.builtContext.returnSchema;
|
|
14
14
|
}
|
|
15
15
|
const maxTokens = budgetToTokenLimit(request);
|
|
16
|
+
const options = {};
|
|
16
17
|
if (maxTokens) {
|
|
17
|
-
|
|
18
|
+
options.num_predict = maxTokens;
|
|
19
|
+
}
|
|
20
|
+
if (request.temperature !== undefined) {
|
|
21
|
+
options.temperature = request.temperature;
|
|
22
|
+
}
|
|
23
|
+
if (Object.keys(options).length > 0) {
|
|
24
|
+
body.options = options;
|
|
18
25
|
}
|
|
19
26
|
const response = await postJson(fetchImpl, `${baseUrl}/api/chat`, body, timeoutMs);
|
|
20
27
|
const text = readPath(response, ["message", "content"]);
|
|
@@ -17,11 +17,18 @@ export async function callOpenAI(request, parsed, options, fetchImpl, timeoutMs,
|
|
|
17
17
|
type: "json_schema",
|
|
18
18
|
json_schema: {
|
|
19
19
|
name: "agentscript_generate",
|
|
20
|
-
strict:
|
|
20
|
+
strict: request.strict,
|
|
21
21
|
schema: request.builtContext.returnSchema,
|
|
22
22
|
},
|
|
23
23
|
};
|
|
24
24
|
}
|
|
25
|
+
if (request.temperature !== undefined) {
|
|
26
|
+
body.temperature = request.temperature;
|
|
27
|
+
}
|
|
28
|
+
const reasoningEffort = openAIReasoningEffort(request.think);
|
|
29
|
+
if (reasoningEffort) {
|
|
30
|
+
body.reasoning_effort = reasoningEffort;
|
|
31
|
+
}
|
|
25
32
|
const maxTokens = budgetToTokenLimit(request);
|
|
26
33
|
if (maxTokens) {
|
|
27
34
|
body.max_completion_tokens = maxTokens;
|
|
@@ -32,3 +39,10 @@ export async function callOpenAI(request, parsed, options, fetchImpl, timeoutMs,
|
|
|
32
39
|
const text = readPath(response, ["choices", 0, "message", "content"]);
|
|
33
40
|
return request.returnShape ? parseJsonText(text) : text;
|
|
34
41
|
}
|
|
42
|
+
function openAIReasoningEffort(think) {
|
|
43
|
+
if (think === true)
|
|
44
|
+
return "medium";
|
|
45
|
+
if (think === "low" || think === "medium" || think === "high")
|
|
46
|
+
return think;
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { RuntimeError } from "../../runtime/errors.js";
|
|
2
2
|
export function budgetToTokenLimit(request) {
|
|
3
|
-
if (!request.
|
|
3
|
+
if (!request.maxOutput) {
|
|
4
4
|
return undefined;
|
|
5
5
|
}
|
|
6
|
-
if (request.
|
|
7
|
-
return Math.max(1, Math.floor(request.
|
|
6
|
+
if (request.maxOutput.unit === "k") {
|
|
7
|
+
return Math.max(1, Math.floor(request.maxOutput.amount * 1000));
|
|
8
8
|
}
|
|
9
|
-
return Math.max(1, Math.floor(request.
|
|
9
|
+
return Math.max(1, Math.floor(request.maxOutput.amount));
|
|
10
10
|
}
|
|
11
11
|
export async function postJson(fetchImpl, url, body, timeoutMs, headers = {}) {
|
|
12
12
|
const controller = new AbortController();
|
package/dist/runtime/context.js
CHANGED
|
@@ -14,7 +14,7 @@ export function buildContext(input) {
|
|
|
14
14
|
instruction,
|
|
15
15
|
instructionText,
|
|
16
16
|
returnSchema,
|
|
17
|
-
|
|
17
|
+
maxOutput: input.maxOutput,
|
|
18
18
|
finalUserMessage: buildFinalUserMessage(context, instructionText, returnSchema),
|
|
19
19
|
};
|
|
20
20
|
}
|
|
@@ -51,6 +51,7 @@ function buildContextItem(item, index) {
|
|
|
51
51
|
return {
|
|
52
52
|
index,
|
|
53
53
|
source: item.source,
|
|
54
|
+
label: item.label,
|
|
54
55
|
value: clipped.value,
|
|
55
56
|
text: clipped.text,
|
|
56
57
|
budget: item.budget,
|
|
@@ -60,8 +61,15 @@ function buildContextItem(item, index) {
|
|
|
60
61
|
};
|
|
61
62
|
}
|
|
62
63
|
function buildSystemPrompt(agentName, identity) {
|
|
63
|
-
const
|
|
64
|
+
const role = typeof identity.role === "string" ? identity.role : agentName;
|
|
65
|
+
const lines = [`You are ${role}.`];
|
|
66
|
+
if (typeof identity.description === "string") {
|
|
67
|
+
lines.push(identity.description);
|
|
68
|
+
}
|
|
64
69
|
for (const [key, value] of Object.entries(identity)) {
|
|
70
|
+
if (key === "role" || key === "description") {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
65
73
|
lines.push(`${key}: ${renderJson(value)}`);
|
|
66
74
|
}
|
|
67
75
|
return lines.join("\n");
|
|
@@ -71,8 +79,9 @@ function buildFinalUserMessage(context, instructionText, returnSchema) {
|
|
|
71
79
|
if (context.length > 0) {
|
|
72
80
|
sections.push("Context:");
|
|
73
81
|
for (const item of context) {
|
|
74
|
-
const label = item.
|
|
75
|
-
|
|
82
|
+
const label = item.label ?? String(item.index);
|
|
83
|
+
const source = item.source ? `source: ${item.source}\n` : "";
|
|
84
|
+
sections.push(`[${label}]\n${source}${item.text}`);
|
|
76
85
|
}
|
|
77
86
|
}
|
|
78
87
|
sections.push("Instruction:", instructionText);
|
|
@@ -147,6 +156,7 @@ export function builtContextToJson(context) {
|
|
|
147
156
|
context: context.context.map((item) => ({
|
|
148
157
|
index: item.index,
|
|
149
158
|
source: item.source ?? null,
|
|
159
|
+
label: item.label ?? null,
|
|
150
160
|
value: item.value,
|
|
151
161
|
text: item.text,
|
|
152
162
|
budget: budgetToJson(item.budget),
|
|
@@ -156,7 +166,7 @@ export function builtContextToJson(context) {
|
|
|
156
166
|
})),
|
|
157
167
|
instruction: context.instruction,
|
|
158
168
|
returnSchema: context.returnSchema ?? null,
|
|
159
|
-
|
|
169
|
+
maxOutput: budgetToJson(context.maxOutput),
|
|
160
170
|
finalUserMessage: context.finalUserMessage,
|
|
161
171
|
};
|
|
162
172
|
}
|
package/dist/runtime/generate.js
CHANGED
|
@@ -29,7 +29,7 @@ export class GenerateRuntime {
|
|
|
29
29
|
instruction,
|
|
30
30
|
returnShape: expr.returnShape,
|
|
31
31
|
uses: context,
|
|
32
|
-
|
|
32
|
+
maxOutput: options.maxOutput,
|
|
33
33
|
});
|
|
34
34
|
if (options.debug) {
|
|
35
35
|
writeDebugPrompt(agent.name, attempt, builtContext);
|
|
@@ -44,7 +44,11 @@ export class GenerateRuntime {
|
|
|
44
44
|
returnShape: expr.returnShape,
|
|
45
45
|
context,
|
|
46
46
|
builtContext,
|
|
47
|
-
|
|
47
|
+
maxOutput: options.maxOutput,
|
|
48
|
+
temperature: options.temperature,
|
|
49
|
+
think: options.think,
|
|
50
|
+
strict: options.strict,
|
|
51
|
+
debug: options.debug,
|
|
48
52
|
});
|
|
49
53
|
}
|
|
50
54
|
catch (error) {
|
|
@@ -58,18 +62,27 @@ export class GenerateRuntime {
|
|
|
58
62
|
continue;
|
|
59
63
|
}
|
|
60
64
|
try {
|
|
61
|
-
const result = expr.returnShape ? coerceValueToShape(rawResult, expr.returnShape) : rawResult;
|
|
65
|
+
const result = expr.returnShape && !options.strict ? coerceValueToShape(rawResult, expr.returnShape) : rawResult;
|
|
62
66
|
if (expr.returnShape) {
|
|
63
|
-
validateValueAgainstShape(result, expr.returnShape, expr.range);
|
|
67
|
+
validateValueAgainstShape(result, expr.returnShape, expr.range, { rejectExtraFields: options.strict });
|
|
64
68
|
}
|
|
65
69
|
this.trace.push({
|
|
66
70
|
kind: "generate",
|
|
67
71
|
data: {
|
|
68
72
|
instruction: sanitizeForJson(options.input),
|
|
73
|
+
config: {
|
|
74
|
+
maxOutput: budgetToJson(options.maxOutput),
|
|
75
|
+
attempts: options.attempts,
|
|
76
|
+
temperature: options.temperature ?? null,
|
|
77
|
+
think: options.think ?? false,
|
|
78
|
+
strict: options.strict,
|
|
79
|
+
debug: options.debug,
|
|
80
|
+
},
|
|
69
81
|
attempts: attempt,
|
|
70
|
-
|
|
82
|
+
maxOutput: budgetToJson(options.maxOutput),
|
|
71
83
|
debug: options.debug,
|
|
72
84
|
context: builtContextToJson(builtContext),
|
|
85
|
+
validation: expr.returnShape ? { ok: true, strict: options.strict } : null,
|
|
73
86
|
result: sanitizeForJson(result),
|
|
74
87
|
},
|
|
75
88
|
});
|
|
@@ -99,7 +112,10 @@ export class GenerateRuntime {
|
|
|
99
112
|
return {
|
|
100
113
|
input: await this.host.evaluate(expr.options.input, scope),
|
|
101
114
|
attempts,
|
|
102
|
-
|
|
115
|
+
maxOutput: expr.options.maxOutput,
|
|
116
|
+
temperature: expr.options.temperature?.value,
|
|
117
|
+
think: expr.options.think?.value,
|
|
118
|
+
strict: expr.options.strict?.value ?? false,
|
|
103
119
|
debug: expr.options.debug?.value ?? false,
|
|
104
120
|
};
|
|
105
121
|
}
|
|
@@ -209,8 +209,11 @@ class Interpreter {
|
|
|
209
209
|
return undefined;
|
|
210
210
|
case "UseStmt": {
|
|
211
211
|
const source = formatExpressionSource(stmt.value);
|
|
212
|
-
scope.addUse(stmt.value, source, stmt.budget);
|
|
213
|
-
this.trace.push({
|
|
212
|
+
scope.addUse(stmt.value, source, stmt.budget, stmt.label);
|
|
213
|
+
this.trace.push({
|
|
214
|
+
kind: "use",
|
|
215
|
+
data: { source, label: stmt.label ?? null, budget: budgetToJson(stmt.budget) },
|
|
216
|
+
});
|
|
214
217
|
return undefined;
|
|
215
218
|
}
|
|
216
219
|
case "AssignStmt": {
|
package/dist/runtime/scope.js
CHANGED
|
@@ -29,8 +29,8 @@ export class RuntimeScope {
|
|
|
29
29
|
}
|
|
30
30
|
this.define(name, value);
|
|
31
31
|
}
|
|
32
|
-
addUse(expr, source, budget) {
|
|
33
|
-
this.uses.push({ expr, source, budget, scope: this });
|
|
32
|
+
addUse(expr, source, budget, label) {
|
|
33
|
+
this.uses.push({ expr, source, budget, label, scope: this });
|
|
34
34
|
}
|
|
35
35
|
setConfig(name, value) {
|
|
36
36
|
this.config.set(name, value);
|
package/dist/runtime/shape.js
CHANGED
|
@@ -7,14 +7,16 @@ export function buildValueFromShape(shape) {
|
|
|
7
7
|
}
|
|
8
8
|
return result;
|
|
9
9
|
}
|
|
10
|
-
export function validateValueAgainstShape(value, shape, range) {
|
|
10
|
+
export function validateValueAgainstShape(value, shape, range, options = {}) {
|
|
11
11
|
if (!isObject(value)) {
|
|
12
12
|
throw new RuntimeError("LLM result must be an object matching the generate return shape", range);
|
|
13
13
|
}
|
|
14
14
|
const allowedFields = new Set(shape.fields.map((field) => field.name));
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
if (options.rejectExtraFields) {
|
|
16
|
+
for (const key of Object.keys(value)) {
|
|
17
|
+
if (!allowedFields.has(key)) {
|
|
18
|
+
throw new RuntimeError(`LLM result contains unexpected field '${key}'`, range);
|
|
19
|
+
}
|
|
18
20
|
}
|
|
19
21
|
}
|
|
20
22
|
for (const field of shape.fields) {
|
|
@@ -171,6 +171,9 @@ class Analyzer {
|
|
|
171
171
|
if (stmt.budget && stmt.budget.amount <= 0) {
|
|
172
172
|
this.error("INVALID_BUDGET", "Budget amount must be greater than 0", stmt.range);
|
|
173
173
|
}
|
|
174
|
+
if (stmt.label && RESERVED_CONTEXT_LABELS.has(stmt.label)) {
|
|
175
|
+
this.error("RESERVED_CONTEXT_LABEL", `Context label '${stmt.label}' is reserved`, stmt.range);
|
|
176
|
+
}
|
|
174
177
|
break;
|
|
175
178
|
case "AssignStmt":
|
|
176
179
|
this.checkAssignment(stmt, scope);
|
|
@@ -392,9 +395,26 @@ class Analyzer {
|
|
|
392
395
|
this.error("INVALID_GENERATE_ATTEMPTS", "generate attempts must be a positive integer", property.value.range);
|
|
393
396
|
}
|
|
394
397
|
}
|
|
395
|
-
else if (property.key === "
|
|
396
|
-
if (!expr.options.
|
|
397
|
-
this.error("
|
|
398
|
+
else if (property.key === "max_output") {
|
|
399
|
+
if (!expr.options.maxOutput || expr.options.maxOutput.amount <= 0) {
|
|
400
|
+
this.error("INVALID_GENERATE_MAX_OUTPUT", "generate max_output must be a positive budget", property.value.range);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
else if (property.key === "temperature") {
|
|
404
|
+
if (property.value.kind !== "NumberExpr") {
|
|
405
|
+
this.error("INVALID_GENERATE_TEMPERATURE", "generate temperature must be a number", property.value.range);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
else if (property.key === "think") {
|
|
409
|
+
const validThinkString = property.value.kind === "StringExpr" &&
|
|
410
|
+
["auto", "low", "medium", "high"].includes(property.value.value);
|
|
411
|
+
if (property.value.kind !== "BooleanExpr" && !validThinkString) {
|
|
412
|
+
this.error("INVALID_GENERATE_THINK", "generate think must be a boolean or one of auto, low, medium, high", property.value.range);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
else if (property.key === "strict") {
|
|
416
|
+
if (property.value.kind !== "BooleanExpr") {
|
|
417
|
+
this.error("INVALID_GENERATE_STRICT", "generate strict must be a boolean", property.value.range);
|
|
398
418
|
}
|
|
399
419
|
}
|
|
400
420
|
else if (property.key === "debug") {
|
|
@@ -501,6 +521,7 @@ class Analyzer {
|
|
|
501
521
|
const IMPORTED_BINDING_KINDS = new Set(["tool", "llm", "file", "agent", "memory"]);
|
|
502
522
|
const NON_CONTEXT_BINDING_KINDS = new Set(["tool", "llm", "agent", "function", "memory"]);
|
|
503
523
|
const VALID_MEMORY_METHODS = new Set(["add", "query"]);
|
|
524
|
+
const RESERVED_CONTEXT_LABELS = new Set(["system", "assistant", "tool", "developer"]);
|
|
504
525
|
function functionBinding(agentName, fn) {
|
|
505
526
|
return {
|
|
506
527
|
kind: "function",
|