@rong/agentscript 0.1.3 → 0.1.5
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 +24 -0
- package/README.md +19 -13
- package/dist/parser/parser.js +34 -11
- package/dist/parser/tokenizer.js +2 -1
- 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 +2 -2
- package/dist/runtime/evaluator.js +8 -0
- package/dist/runtime/generate.js +22 -6
- package/dist/runtime/shape.js +6 -4
- package/dist/semantic/analyzer.js +20 -3
- package/docs/cn/context-engineering.md +50 -323
- package/docs/cn/final-expression-return.md +5 -5
- package/docs/cn/generate.md +476 -0
- package/docs/cn/language.md +73 -40
- package/docs/cn/use-as.md +225 -0
- package/docs/en/context-engineering.md +51 -269
- package/docs/en/final-expression-return.md +3 -3
- package/docs/en/generate.md +476 -0
- package/docs/en/language.md +73 -40
- package/docs/en/use-as.md +225 -0
- package/examples/changelog.as +2 -2
- package/examples/extract.as +2 -2
- package/examples/review.as +3 -3
- package/examples/summarize.as +2 -2
- package/examples/translate.as +2 -2
- package/package.json +1 -1
- package/tutorials/cli.as +1 -1
- package/tutorials/memory.as +4 -4
- package/tutorials/plan-execute.as +8 -8
- package/tutorials/react.as +9 -9
- package/tutorials/self-improve.as +8 -8
- package/docs/cn/role-label.md +0 -492
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to AgentScript will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## 0.1.5 - 2026-05-09
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- Switched context budgets from `use expr < budget` to `use expr max budget`.
|
|
10
|
+
- Switched loop and for-in iteration limits to `max` syntax.
|
|
11
|
+
- Restored `<` as an ordinary numeric comparison operator.
|
|
12
|
+
- Required comma separators between object literal fields.
|
|
13
|
+
- Updated current docs, examples, tutorials, fixtures, and tests for the new syntax.
|
|
14
|
+
|
|
15
|
+
## 0.1.4 - 2026-05-08
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- Added `max_output` as the explicit `generate` output budget.
|
|
20
|
+
- Added `temperature`, `think`, and `strict` generate options.
|
|
21
|
+
- Added provider request mapping for generate provider hints.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- Replaced generate `limit` usage with `max_output` in current docs, examples, tutorials, and fixtures.
|
|
26
|
+
- Expanded English and Chinese `generate` design docs with configuration semantics.
|
|
27
|
+
- Updated shape validation so `strict: true` disables coercion and rejects extra fields.
|
|
28
|
+
|
|
5
29
|
## 0.1.3 - 2026-05-08
|
|
6
30
|
|
|
7
31
|
### Added
|
package/README.md
CHANGED
|
@@ -6,8 +6,10 @@
|
|
|
6
6
|
> Zero runtime dependencies. TypeScript-powered.
|
|
7
7
|
|
|
8
8
|
```agentscript
|
|
9
|
-
use scratch.summary
|
|
10
|
-
generate({
|
|
9
|
+
use scratch.summary max 2k as observations
|
|
10
|
+
generate({
|
|
11
|
+
input: "Answer from observations"
|
|
12
|
+
}) -> {
|
|
11
13
|
ok boolean
|
|
12
14
|
text string
|
|
13
15
|
}
|
|
@@ -60,13 +62,15 @@ main agent FileSummarizer {
|
|
|
60
62
|
description "Read one local file and produce a useful structured summary."
|
|
61
63
|
|
|
62
64
|
main func(input { path string }) {
|
|
63
|
-
content = File.read({
|
|
65
|
+
content = File.read({
|
|
66
|
+
path: input.path
|
|
67
|
+
})
|
|
64
68
|
use input.path as source path
|
|
65
|
-
use content
|
|
69
|
+
use content max 8k as file content
|
|
66
70
|
|
|
67
71
|
generate({
|
|
68
|
-
input: "Summarize the file for a busy teammate"
|
|
69
|
-
|
|
72
|
+
input: "Summarize the file for a busy teammate",
|
|
73
|
+
max_output: 1000
|
|
70
74
|
}) -> {
|
|
71
75
|
title string
|
|
72
76
|
summary string
|
|
@@ -181,10 +185,10 @@ main agent ResearchAgent {
|
|
|
181
185
|
use input.question as user question
|
|
182
186
|
|
|
183
187
|
scratch = []
|
|
184
|
-
use scratch.summary
|
|
188
|
+
use scratch.summary max 2k as observations
|
|
185
189
|
|
|
186
190
|
done = false
|
|
187
|
-
loop until done
|
|
191
|
+
loop until done max 6 {
|
|
188
192
|
thought = reason(input.question, scratch)
|
|
189
193
|
obs = Search.search(thought.focus)
|
|
190
194
|
scratch.add(obs)
|
|
@@ -196,8 +200,10 @@ main agent ResearchAgent {
|
|
|
196
200
|
|
|
197
201
|
func answer(question, scratch) {
|
|
198
202
|
use question as user question
|
|
199
|
-
use scratch.summary
|
|
200
|
-
generate({
|
|
203
|
+
use scratch.summary max 2k as observations
|
|
204
|
+
generate({
|
|
205
|
+
input: "Answer using only the observations"
|
|
206
|
+
}) -> {
|
|
201
207
|
ok boolean
|
|
202
208
|
text string
|
|
203
209
|
error string
|
|
@@ -254,8 +260,8 @@ agentscript examples/review.as --quiet # value only, no trace
|
|
|
254
260
|
|
|
255
261
|
| Language | Links |
|
|
256
262
|
|----------|-------|
|
|
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) |
|
|
263
|
+
| 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/) |
|
|
264
|
+
| 中文 | [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
265
|
|
|
260
266
|
### Design principles
|
|
261
267
|
|
|
@@ -281,4 +287,4 @@ Zero runtime dependencies. Built with TypeScript.
|
|
|
281
287
|
|
|
282
288
|
## License
|
|
283
289
|
|
|
284
|
-
MIT
|
|
290
|
+
MIT
|
package/dist/parser/parser.js
CHANGED
|
@@ -175,7 +175,7 @@ class Parser {
|
|
|
175
175
|
parseUse() {
|
|
176
176
|
const start = this.consume("use").range.start;
|
|
177
177
|
const value = this.parseLogicalOr();
|
|
178
|
-
const budget = this.match("
|
|
178
|
+
const budget = this.match("max") ? this.parseBudget() : undefined;
|
|
179
179
|
const label = this.match("as") ? this.parseUseLabel() : undefined;
|
|
180
180
|
return {
|
|
181
181
|
kind: "UseStmt",
|
|
@@ -228,7 +228,7 @@ class Parser {
|
|
|
228
228
|
const item = this.consumeIdentifier("Expected for item name");
|
|
229
229
|
this.consume("in");
|
|
230
230
|
const iterable = this.parseLogicalOr();
|
|
231
|
-
this.consume("
|
|
231
|
+
this.consume("max");
|
|
232
232
|
const maxIterations = this.parsePositiveInteger("Expected for iteration count");
|
|
233
233
|
const body = this.parseBlock();
|
|
234
234
|
return {
|
|
@@ -245,7 +245,7 @@ class Parser {
|
|
|
245
245
|
const start = this.consume("loop").range.start;
|
|
246
246
|
this.consume("until");
|
|
247
247
|
const condition = this.parseLogicalOr();
|
|
248
|
-
this.consume("
|
|
248
|
+
this.consume("max");
|
|
249
249
|
const maxIterations = this.parsePositiveInteger("Expected loop iteration count");
|
|
250
250
|
const body = this.parseBlock();
|
|
251
251
|
return {
|
|
@@ -299,7 +299,10 @@ class Parser {
|
|
|
299
299
|
return this.parseBinaryExpression(() => this.parseEquality(), "and");
|
|
300
300
|
}
|
|
301
301
|
parseEquality() {
|
|
302
|
-
return this.parseBinaryExpression(() => this.
|
|
302
|
+
return this.parseBinaryExpression(() => this.parseComparison(), "==", "!=");
|
|
303
|
+
}
|
|
304
|
+
parseComparison() {
|
|
305
|
+
return this.parseBinaryExpression(() => this.parseUnary(), "<");
|
|
303
306
|
}
|
|
304
307
|
parseBinaryExpression(parseOperand, ...operators) {
|
|
305
308
|
let expr = parseOperand();
|
|
@@ -434,16 +437,19 @@ class Parser {
|
|
|
434
437
|
const properties = [];
|
|
435
438
|
let input;
|
|
436
439
|
let attempts;
|
|
437
|
-
let
|
|
440
|
+
let maxOutput;
|
|
441
|
+
let temperature;
|
|
442
|
+
let think;
|
|
443
|
+
let strict;
|
|
438
444
|
let debug;
|
|
439
445
|
while (!this.check("}") && !this.isAtEnd()) {
|
|
440
446
|
const propStart = this.peek().range.start;
|
|
441
447
|
const key = this.consumeObjectKey();
|
|
442
448
|
this.consume(":");
|
|
443
449
|
let value;
|
|
444
|
-
if (key === "
|
|
445
|
-
const token = this.consumeKind("number", "Expected generate
|
|
446
|
-
|
|
450
|
+
if (key === "max_output") {
|
|
451
|
+
const token = this.consumeKind("number", "Expected generate max_output");
|
|
452
|
+
maxOutput = this.parseBudgetToken(token);
|
|
447
453
|
value = {
|
|
448
454
|
kind: "NumberExpr",
|
|
449
455
|
value: Number.parseFloat(token.value),
|
|
@@ -466,10 +472,19 @@ class Parser {
|
|
|
466
472
|
else if (key === "attempts" && value.kind === "NumberExpr") {
|
|
467
473
|
attempts = value;
|
|
468
474
|
}
|
|
475
|
+
else if (key === "temperature" && value.kind === "NumberExpr") {
|
|
476
|
+
temperature = value;
|
|
477
|
+
}
|
|
478
|
+
else if (key === "think" && (value.kind === "BooleanExpr" || value.kind === "StringExpr")) {
|
|
479
|
+
think = value;
|
|
480
|
+
}
|
|
481
|
+
else if (key === "strict" && value.kind === "BooleanExpr") {
|
|
482
|
+
strict = value;
|
|
483
|
+
}
|
|
469
484
|
else if (key === "debug" && value.kind === "BooleanExpr") {
|
|
470
485
|
debug = value;
|
|
471
486
|
}
|
|
472
|
-
this.
|
|
487
|
+
this.consumePropertySeparator("}");
|
|
473
488
|
}
|
|
474
489
|
this.consume("}");
|
|
475
490
|
return {
|
|
@@ -477,7 +492,10 @@ class Parser {
|
|
|
477
492
|
properties,
|
|
478
493
|
input,
|
|
479
494
|
attempts,
|
|
480
|
-
|
|
495
|
+
maxOutput,
|
|
496
|
+
temperature,
|
|
497
|
+
think,
|
|
498
|
+
strict,
|
|
481
499
|
debug,
|
|
482
500
|
range: { start, end: this.previous().range.end }
|
|
483
501
|
};
|
|
@@ -496,7 +514,7 @@ class Parser {
|
|
|
496
514
|
value,
|
|
497
515
|
range: { start: propStart, end: value.range.end }
|
|
498
516
|
});
|
|
499
|
-
this.
|
|
517
|
+
this.consumePropertySeparator("}");
|
|
500
518
|
}
|
|
501
519
|
this.consume("}");
|
|
502
520
|
return {
|
|
@@ -505,6 +523,11 @@ class Parser {
|
|
|
505
523
|
range: { start, end: this.previous().range.end }
|
|
506
524
|
};
|
|
507
525
|
}
|
|
526
|
+
consumePropertySeparator(terminator) {
|
|
527
|
+
if (this.check(terminator))
|
|
528
|
+
return;
|
|
529
|
+
this.consume(",");
|
|
530
|
+
}
|
|
508
531
|
parseList() {
|
|
509
532
|
const start = this.consume("[").range.start;
|
|
510
533
|
const items = this.parseCommaSeparatedUntil("]", () => this.parseExpression());
|
package/dist/parser/tokenizer.js
CHANGED
|
@@ -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
|
}
|
|
@@ -166,7 +166,7 @@ export function builtContextToJson(context) {
|
|
|
166
166
|
})),
|
|
167
167
|
instruction: context.instruction,
|
|
168
168
|
returnSchema: context.returnSchema ?? null,
|
|
169
|
-
|
|
169
|
+
maxOutput: budgetToJson(context.maxOutput),
|
|
170
170
|
finalUserMessage: context.finalUserMessage,
|
|
171
171
|
};
|
|
172
172
|
}
|
|
@@ -97,8 +97,16 @@ export class Evaluator {
|
|
|
97
97
|
return this.valuesEqual(await this.evaluate(expr.left, scope), await this.evaluate(expr.right, scope));
|
|
98
98
|
case "!=":
|
|
99
99
|
return !this.valuesEqual(await this.evaluate(expr.left, scope), await this.evaluate(expr.right, scope));
|
|
100
|
+
case "<":
|
|
101
|
+
return this.evaluateLessThan(await this.evaluate(expr.left, scope), await this.evaluate(expr.right, scope), expr.range);
|
|
100
102
|
}
|
|
101
103
|
}
|
|
104
|
+
evaluateLessThan(left, right, range) {
|
|
105
|
+
if (typeof left !== "number" || typeof right !== "number") {
|
|
106
|
+
throw new RuntimeError("operator '<' requires number operands", range);
|
|
107
|
+
}
|
|
108
|
+
return left < right;
|
|
109
|
+
}
|
|
102
110
|
valuesEqual(left, right) {
|
|
103
111
|
return runtimeValuesEqual(left, right);
|
|
104
112
|
}
|
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
|
}
|
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) {
|
|
@@ -395,9 +395,26 @@ class Analyzer {
|
|
|
395
395
|
this.error("INVALID_GENERATE_ATTEMPTS", "generate attempts must be a positive integer", property.value.range);
|
|
396
396
|
}
|
|
397
397
|
}
|
|
398
|
-
else if (property.key === "
|
|
399
|
-
if (!expr.options.
|
|
400
|
-
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);
|
|
401
418
|
}
|
|
402
419
|
}
|
|
403
420
|
else if (property.key === "debug") {
|