@rong/agentscript 0.1.3 → 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 CHANGED
@@ -2,6 +2,20 @@
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
+
5
19
  ## 0.1.3 - 2026-05-08
6
20
 
7
21
  ### Added
package/README.md CHANGED
@@ -66,7 +66,7 @@ main agent FileSummarizer {
66
66
 
67
67
  generate({
68
68
  input: "Summarize the file for a busy teammate"
69
- limit: 1000
69
+ max_output: 1000
70
70
  }) -> {
71
71
  title string
72
72
  summary string
@@ -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
 
@@ -434,16 +434,19 @@ class Parser {
434
434
  const properties = [];
435
435
  let input;
436
436
  let attempts;
437
- let limit;
437
+ let maxOutput;
438
+ let temperature;
439
+ let think;
440
+ let strict;
438
441
  let debug;
439
442
  while (!this.check("}") && !this.isAtEnd()) {
440
443
  const propStart = this.peek().range.start;
441
444
  const key = this.consumeObjectKey();
442
445
  this.consume(":");
443
446
  let value;
444
- if (key === "limit") {
445
- const token = this.consumeKind("number", "Expected generate limit");
446
- limit = this.parseBudgetToken(token);
447
+ if (key === "max_output") {
448
+ const token = this.consumeKind("number", "Expected generate max_output");
449
+ maxOutput = this.parseBudgetToken(token);
447
450
  value = {
448
451
  kind: "NumberExpr",
449
452
  value: Number.parseFloat(token.value),
@@ -466,6 +469,15 @@ class Parser {
466
469
  else if (key === "attempts" && value.kind === "NumberExpr") {
467
470
  attempts = value;
468
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
+ }
469
481
  else if (key === "debug" && value.kind === "BooleanExpr") {
470
482
  debug = value;
471
483
  }
@@ -477,7 +489,10 @@ class Parser {
477
489
  properties,
478
490
  input,
479
491
  attempts,
480
- limit,
492
+ maxOutput,
493
+ temperature,
494
+ think,
495
+ strict,
481
496
  debug,
482
497
  range: { start, end: this.previous().range.end }
483
498
  };
@@ -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 response = await postJson(fetchImpl, `${baseUrl}/messages`, {
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
- }, timeoutMs, {
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
- body.options = { num_predict: maxTokens };
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: true,
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.budget) {
3
+ if (!request.maxOutput) {
4
4
  return undefined;
5
5
  }
6
- if (request.budget.unit === "k") {
7
- return Math.max(1, Math.floor(request.budget.amount * 1000));
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.budget.amount));
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();
@@ -14,7 +14,7 @@ export function buildContext(input) {
14
14
  instruction,
15
15
  instructionText,
16
16
  returnSchema,
17
- budget: input.budget,
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
- budget: budgetToJson(context.budget),
169
+ maxOutput: budgetToJson(context.maxOutput),
170
170
  finalUserMessage: context.finalUserMessage,
171
171
  };
172
172
  }
@@ -29,7 +29,7 @@ export class GenerateRuntime {
29
29
  instruction,
30
30
  returnShape: expr.returnShape,
31
31
  uses: context,
32
- budget: options.limit,
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
- budget: options.limit,
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
- budget: budgetToJson(options.limit),
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
- limit: expr.options.limit,
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
  }
@@ -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
- for (const key of Object.keys(value)) {
16
- if (!allowedFields.has(key)) {
17
- throw new RuntimeError(`LLM result contains unexpected field '${key}'`, range);
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 === "limit") {
399
- if (!expr.options.limit || expr.options.limit.amount <= 0) {
400
- this.error("INVALID_GENERATE_LIMIT", "generate limit must be a positive budget", property.value.range);
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") {