create-nola-lang 0.1.13 → 0.1.15
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/README.md +8 -3
- package/dist/add.d.ts +3 -0
- package/dist/add.js +2 -1
- package/dist/checkout.js +1 -0
- package/dist/{chunk-LUSVIOAN.js → chunk-UTVGG44K.js} +124 -95
- package/dist/examples.d.ts +4 -2
- package/dist/examples.js +4 -2
- package/dist/flow.d.ts +20 -3
- package/dist/flow.js +49 -17
- package/dist/github.d.ts +1 -1
- package/dist/github.js +1 -1
- package/dist/index.js +1 -1
- package/dist/launch.d.ts +2 -2
- package/dist/launch.js +1 -1
- package/dist/main.js +1 -1
- package/dist/node-version.d.ts +3 -2
- package/dist/node-version.js +4 -3
- package/dist/package-manager.d.ts +5 -0
- package/dist/package-manager.js +7 -0
- package/dist/providers.d.ts +1 -1
- package/dist/providers.js +1 -1
- package/dist/registry.d.ts +21 -12
- package/dist/registry.js +27 -5
- package/dist/scaffold.d.ts +36 -16
- package/dist/scaffold.js +96 -116
- package/package.json +1 -1
- package/skills/nola/SKILL.md +72 -17
- package/skills/nola/references/config.md +35 -6
- package/skills/nola/references/patterns.md +129 -31
- package/skills/nola/references/pitfalls.md +318 -56
- package/skills/nola/references/syntax.md +345 -121
- package/templates/empty/package.json +5 -5
- package/templates/empty/src/main.tsi +8 -0
- package/templates/empty/src/main.ts +0 -5
- package/templates/feature-extraction/README.md +0 -20
- package/templates/feature-extraction/nola.config.ts +0 -14
- package/templates/feature-extraction/nola.replay.jsonl +0 -2
- package/templates/feature-extraction/package.json +0 -22
- package/templates/feature-extraction/src/main.tsi +0 -22
- package/templates/feature-extraction/tsconfig.json +0 -12
- package/templates/function-calling/README.md +0 -21
- package/templates/function-calling/nola.config.ts +0 -14
- package/templates/function-calling/nola.replay.jsonl +0 -1
- package/templates/function-calling/package.json +0 -22
- package/templates/function-calling/src/main.tsi +0 -12
- package/templates/function-calling/src/tickets.ts +0 -17
- package/templates/function-calling/tsconfig.json +0 -12
- package/templates/typescript-interop/README.md +0 -15
- package/templates/typescript-interop/nola.config.ts +0 -14
- package/templates/typescript-interop/nola.replay.jsonl +0 -1
- package/templates/typescript-interop/package.json +0 -22
- package/templates/typescript-interop/src/main.ts +0 -8
- package/templates/typescript-interop/src/person.tsi +0 -11
- package/templates/typescript-interop/tsconfig.json +0 -12
|
@@ -16,44 +16,96 @@ plain TS.
|
|
|
16
16
|
```tsi
|
|
17
17
|
// plain
|
|
18
18
|
infer function summarize(.text: string) {
|
|
19
|
-
return ask `a one-sentence summary
|
|
19
|
+
return ask `a one-sentence summary`: string;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
// exported
|
|
23
23
|
export infer function classify(.message: string) {
|
|
24
|
-
return ask `the category of the message
|
|
24
|
+
return ask `the category of the message`: string;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
//
|
|
28
|
-
export infer function triage
|
|
29
|
-
|
|
27
|
+
// a context statement: a bare template literal on its own line
|
|
28
|
+
export infer function triage(.ticket: string) {
|
|
29
|
+
`triage the ticket like a support lead`
|
|
30
|
+
return ask `the severity: low, medium or high`: "low" | "medium" | "high";
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
//
|
|
33
|
-
export infer function
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
// text and values — the value is spliced into your words, like a ${} hole
|
|
34
|
+
export infer function escalate(.ticket: string, oncall: string) {
|
|
35
|
+
`Page` oncall `when the ticket is an outage.`
|
|
36
|
+
const steps: string[] = [];
|
|
37
|
+
`Steps taken so far:` steps; // read at EACH ask: the loop sees the current list
|
|
38
|
+
while (steps.length < 3) steps.push(ask `the next step`: string);
|
|
39
|
+
return steps;
|
|
37
40
|
}
|
|
38
41
|
```
|
|
39
42
|
|
|
40
43
|
Rules:
|
|
41
44
|
|
|
42
|
-
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
- Context statements: any bare template literal statement in a scope body
|
|
46
|
+
(an infer body or the module body, at any depth of braces). Several are
|
|
47
|
+
legal. An ask sees the statements written BEFORE it, in its block or an
|
|
48
|
+
enclosing one — the `const .x` rule; each is read at each ask. Visibility
|
|
49
|
+
is lexical, like a `const`: a statement inside a block reaches only the
|
|
50
|
+
asks inside those braces (an ask after the block never sees it), and
|
|
51
|
+
statements never accumulate across loop passes — each ask sees every
|
|
52
|
+
statement visible at its position exactly once, rendered at that ask; only
|
|
53
|
+
the values change between passes. A value is a
|
|
54
|
+
name, a call or a bracketed literal (`[a, b]`, `{ a }`, `(a + b)`; a plain
|
|
55
|
+
number, string or `true` works too); a `[` or `(` group takes its whole
|
|
56
|
+
tail (`[a, b].filter(ok)` is one value). Parenthesize anything else: after
|
|
57
|
+
a value an operator or arrow is NOLA1020, and so is a value that ENDS with
|
|
58
|
+
a backtick (`` foo<string> `more` ``, `` new Foo `more` `` — write
|
|
59
|
+
`(foo<string>)`, `(new Foo)` or `new Foo()`). `await`, `ask` and `this`
|
|
60
|
+
cannot be values at all — assign the result to a local first
|
|
61
|
+
(`const user = this.user;`, then `` `Page` user ``): an item is a hoisted
|
|
62
|
+
function of its own, so a bare `this` is NOLA1020 and `this` in
|
|
63
|
+
parentheses or in a `${}` hole is a TypeScript error (TS2683); see "Values
|
|
64
|
+
in an instruction" for `await` and `ask`. A call intent is a value: a
|
|
65
|
+
sigil-less one (`` `Page` fn(`x`: T) ``) as it stands, a HINTED one
|
|
66
|
+
(`` fn`hint`(…) ``, an empty marker too) only in parentheses or a `${}`
|
|
67
|
+
hole — bare, its backtick reads as the next text part, so it is no call
|
|
68
|
+
intent (a typed template argument is then a syntax error at its colon, a
|
|
69
|
+
`` ..`x`: T `` one NOLA2010; plain arguments raise no error and render
|
|
70
|
+
the wrong text). An operator directly after the TEXT
|
|
71
|
+
makes no context statement at all: `` `Page ` + oncall `` is ordinary
|
|
72
|
+
string concatenation the model never sees — write `` `Page` oncall ``.
|
|
73
|
+
Values never sit side by side; whitespace between parts is kept as
|
|
74
|
+
written. LINES follow
|
|
75
|
+
JavaScript: a line that starts with a backtick, `[` or `(` continues the
|
|
76
|
+
statement; a line that starts with a NAME begins a new statement
|
|
77
|
+
(`` `analyze` `` ⏎ `foo(x)` is a statement and a call — put `foo(x)` at
|
|
78
|
+
the end of the text's line or in parentheses on its own line). Two
|
|
79
|
+
backtick-led lines in a row are ONE statement (the second line's
|
|
80
|
+
indentation stays in the text): end the first with `;` for two. The mirror
|
|
81
|
+
hazard: a CODE line starting with `[` or `(` right after a context
|
|
82
|
+
statement is taken as its value (its result lands in the prompt, and the
|
|
83
|
+
code runs at every ask) — end the statement with `;`. A backtick literal
|
|
84
|
+
between the name and the parameters (`` infer function f`…`(…) ``) is
|
|
85
|
+
reserved — NOLA1019.
|
|
86
|
+
- The MODULE body takes the same statements and `const .x` bindings — see
|
|
87
|
+
"The `ask` operator". A top-level module statement reaches the asks below
|
|
88
|
+
it and the infer functions DECLARED below it: an ask inside one renders the
|
|
89
|
+
module's `<context module="<file>">` block before the function's, whoever
|
|
90
|
+
calls it, once. A function's OWN VIEW of the module is the top-level
|
|
91
|
+
statements written above its declaration. A function declared ABOVE the
|
|
92
|
+
statement does not see it on its own (a detached `await fn()` sees only the
|
|
93
|
+
function's own view), but `ask fn()` from code that saw it — module code
|
|
94
|
+
below the statement, an infer function declared below it — renders the
|
|
95
|
+
block once with the caller's statements added. A statement inside a
|
|
96
|
+
module-level block reaches a function only through an `ask fn()` written
|
|
97
|
+
inside that block. Bindings are not carried.
|
|
98
|
+
- A context statement in a plain function, a callback, a static block or a
|
|
99
|
+
namespace body — or as the UNBRACED body of an `if`/`else`/loop/label — is
|
|
100
|
+
NOLA2017 (put braces around the body). In a file with no `ask` and no
|
|
101
|
+
infer function a lone text statement is left as written (plain JavaScript,
|
|
102
|
+
a no-op), but a statement of two or more parts makes the file use the
|
|
103
|
+
runtime and every context statement in it an item.
|
|
49
104
|
|
|
50
105
|
- Top-level function declarations only. `infer` on a method, arrow function,
|
|
51
|
-
or function expression is a
|
|
106
|
+
or function expression is a plain parse error (NOLA1001).
|
|
52
107
|
- `async infer function` is a parse error — an infer function is never `async`
|
|
53
108
|
in source; it is implicitly awaitable through `Intent`.
|
|
54
|
-
- The instruction marker is a template literal. `${expr}` holes interpolate
|
|
55
|
-
lexical values into the instruction; a `${.member}` hole makes the marker a
|
|
56
|
-
prompt TEMPLATE for the function's CONTEXT block (see "Prompt templates").
|
|
57
109
|
- `export default infer function` does NOT parse. Export by name
|
|
58
110
|
(`export infer function f(...)`) and let consumers import the name.
|
|
59
111
|
- `ask` is legal only DIRECTLY inside an infer function body or DIRECTLY in
|
|
@@ -68,7 +120,7 @@ Rules:
|
|
|
68
120
|
```tsi
|
|
69
121
|
export infer function enrich(.handle: string, fetchProfile: (h: string) => Promise<string>) {
|
|
70
122
|
const profile = await fetchProfile(handle); // ordinary promise
|
|
71
|
-
return ask `the person's job title from: ${profile}
|
|
123
|
+
return ask `the person's job title from: ${profile}`: string;
|
|
72
124
|
}
|
|
73
125
|
```
|
|
74
126
|
|
|
@@ -86,7 +138,7 @@ interface User {
|
|
|
86
138
|
}
|
|
87
139
|
|
|
88
140
|
export infer function getUser(.message: string): Intent<User> {
|
|
89
|
-
const user = ask `the user described in the message
|
|
141
|
+
const user = ask `the user described in the message`: User;
|
|
90
142
|
return user;
|
|
91
143
|
}
|
|
92
144
|
```
|
|
@@ -101,15 +153,16 @@ A parameter prefixed with ONE dot is a CONTEXT parameter: its name, type and
|
|
|
101
153
|
runtime VALUE are composed into the prompt of every `ask` in that invocation.
|
|
102
154
|
A plain parameter is an ordinary JS argument — its name and type reach the
|
|
103
155
|
LLM, its value does not. Mnemonic: one dot IN (`.name`); a template after
|
|
104
|
-
`ask` OUT — `` ..`prompt` `` spells the same request where `ask`
|
|
105
|
-
|
|
156
|
+
`ask` OUT — `` ..`prompt` `` spells the same request where neither `ask`
|
|
157
|
+
nor a call's argument list is in front of it. Writing `..name` on a
|
|
158
|
+
parameter is NOLA1013.
|
|
106
159
|
|
|
107
160
|
```tsi
|
|
108
161
|
export type Issue = { id: string; description: string };
|
|
109
162
|
|
|
110
163
|
// `issue` is visible to the LLM; `fallback` is a normal JS value only.
|
|
111
164
|
export infer function classifyIssue(.issue: Issue, fallback: string) {
|
|
112
|
-
const kind = ask `the kind of this issue
|
|
165
|
+
const kind = ask `the kind of this issue`: string;
|
|
113
166
|
return kind || fallback;
|
|
114
167
|
}
|
|
115
168
|
```
|
|
@@ -127,7 +180,7 @@ export infer function classifyIssue(.issue: Issue, fallback: string) {
|
|
|
127
180
|
|
|
128
181
|
```tsi
|
|
129
182
|
export infer function nextQuery(.question: string, .notes: string[]) {
|
|
130
|
-
return ask `the single best search query to advance the research
|
|
183
|
+
return ask `the single best search query to advance the research`: string;
|
|
131
184
|
}
|
|
132
185
|
```
|
|
133
186
|
- `const .x = …` / `let .x = …` is a contextual BINDING: context for every
|
|
@@ -137,27 +190,29 @@ export infer function nextQuery(.question: string, .notes: string[]) {
|
|
|
137
190
|
parameter (same `underivableContextType` policy), and travels to a callee
|
|
138
191
|
through `ask fn()`. At module level it gives the `<module>` scope its
|
|
139
192
|
CONTEXT block; it does NOT reach infer functions merely declared in the
|
|
140
|
-
file
|
|
193
|
+
file (a module context statement does, in the functions declared below
|
|
194
|
+
it — see "infer function"). `var .x` is NOLA1014; a pattern is NOLA1011.
|
|
141
195
|
|
|
142
196
|
```tsi
|
|
143
197
|
export infer function reply(.mail: string) {
|
|
144
198
|
const .tone = "brief, friendly";
|
|
145
199
|
const .customer: Customer = await loadCustomer(mail);
|
|
146
|
-
return ask `a reply to the mail
|
|
200
|
+
return ask `a reply to the mail`: string;
|
|
147
201
|
}
|
|
148
202
|
```
|
|
149
203
|
|
|
150
|
-
## Extractors — `` ask `instruction
|
|
204
|
+
## Extractors — `` ask `instruction`: T `` and `` ..`instruction`: T ``
|
|
151
205
|
|
|
152
206
|
An extractor is the request itself: instruction text in backticks plus an
|
|
153
|
-
optional type argument. Directly after `ask` (and after `ask with <name>`)
|
|
207
|
+
optional type argument. Directly after `ask` (and after `ask with <name>`),
|
|
208
|
+
and as a typed slot in a call's arguments (`` createTicket(`title`: string, 2) ``),
|
|
154
209
|
the backticks alone are the extractor; everywhere else it is written with two
|
|
155
210
|
leading dots so it cannot be mistaken for a string.
|
|
156
211
|
|
|
157
212
|
```tsi
|
|
158
213
|
export infer function parse(.doc: string) {
|
|
159
|
-
const id = ask `the ticket id
|
|
160
|
-
const count = ask `how many line items
|
|
214
|
+
const id = ask `the ticket id`: string; // typed
|
|
215
|
+
const count = ask `how many line items`: number;
|
|
161
216
|
const free = ask `think step by step about the document`; // untyped
|
|
162
217
|
return { id, count, free };
|
|
163
218
|
}
|
|
@@ -165,9 +220,7 @@ export infer function parse(.doc: string) {
|
|
|
165
220
|
|
|
166
221
|
- `${expr}` interpolation is legal inside the backticks and is evaluated at
|
|
167
222
|
intent-construction time. Strings splice as-is; anything else is
|
|
168
|
-
JSON-stringified. A hole
|
|
169
|
-
lexical value — it reads the extractor's prompt scope and turns the
|
|
170
|
-
backticks into a prompt template (see "Prompt templates"):
|
|
223
|
+
JSON-stringified. A hole cannot contain a Nola construct (NOLA2010):
|
|
171
224
|
|
|
172
225
|
```tsi
|
|
173
226
|
interface Person {
|
|
@@ -176,20 +229,51 @@ interface Person {
|
|
|
176
229
|
}
|
|
177
230
|
|
|
178
231
|
export infer function lookup(text: string) {
|
|
179
|
-
return ask `the person described in: ${text}
|
|
232
|
+
return ask `the person described in: ${text}`: Person;
|
|
180
233
|
}
|
|
181
234
|
```
|
|
182
235
|
|
|
183
|
-
- With no
|
|
236
|
+
- With no type, the extractor asks for free text: the wire schema is a plain
|
|
184
237
|
string and the static TS type is `any`. Give every extractor an explicit
|
|
185
|
-
|
|
186
|
-
-
|
|
187
|
-
|
|
188
|
-
``
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
238
|
+
type unless you deliberately want unconstrained prose.
|
|
239
|
+
- TWO SPELLINGS OF THE TYPE, one construct. `: T` — a colon after the
|
|
240
|
+
closing backtick on the same line, glued like an annotation or spaced
|
|
241
|
+
(`` `p`: T `` and `` `p` : T `` are the same ask) — is the taught form and
|
|
242
|
+
what every example writes. `<T>` — a type argument after the backtick — is
|
|
243
|
+
the same construct: `` ask `the ticket id`<string> ``, `` ..`x`<T> ``,
|
|
244
|
+
`` fn(`x`<T>) ``, `` ask `q`<Choice<C>> `` — same AST, same emit, same
|
|
245
|
+
definition, same ledger entries; legal everywhere `: T` is. Rules for the
|
|
246
|
+
colon: it may follow glued, spaced, or on the next line (a line break
|
|
247
|
+
alone never ends an ask); an ask directly
|
|
248
|
+
in a ternary's consequent is parenthesized, on one line or three,
|
|
249
|
+
`` cond ? (ask `p`: number) : fallback `` — bare it is NOLA1023, because
|
|
250
|
+
the colon after the ask is its type and `` cond ? ask `p` : fallback ``
|
|
251
|
+
would read `fallback` as that type (a call intent and the `<T>` form need
|
|
252
|
+
no parentheses, and the alternate position is free:
|
|
253
|
+
`` cond ? y : ask `p`: number ``); the
|
|
254
|
+
type starts on the colon's line (NOLA1018 otherwise); a type NAME followed by `.`
|
|
255
|
+
continues the type (`` `p`: User.withRetry(2) `` is the type
|
|
256
|
+
`User.withRetry` called with 2) — chain intent methods after a
|
|
257
|
+
parenthesized extractor (`` ask (..`p`: User).withRetry(2) ``), a
|
|
258
|
+
delimited type (`string[]`, `Array<User>`, `{…}`, `(User)`) or `<T>`;
|
|
259
|
+
`<` after a type name opens type arguments with either spelling.
|
|
260
|
+
- The `..` is implied in two places: directly after `ask` (the template is
|
|
261
|
+
the operand's first token) and in a call's SLOTS — a typed template that
|
|
262
|
+
starts an argument, or a property value / element of a plain object or
|
|
263
|
+
array literal written in the arguments: `` fn(`x`: T) ``,
|
|
264
|
+
`` api.save({ note: `x`: T }) ``, `` fn([`x`: T]) ``. Everywhere else it is
|
|
265
|
+
required: `` const i = ..`x`: T ``, `` fn(cond ? (..`x`: T) : y) ``,
|
|
266
|
+
`` fn(...[..`x`: T]) ``, `` new Foo(..`x`: T) ``,
|
|
267
|
+
`` ask (..`x`: T).withRetry(2) `` (parenthesized: the template is no longer
|
|
268
|
+
a first token — `` fn((`x`: T)) `` is a syntax error too). A `` `x`<T> ``
|
|
269
|
+
outside those places is NOLA2014; a `` `x`: T `` there is a plain syntax
|
|
270
|
+
error. An UNTYPED template in a slot is a plain string argument
|
|
271
|
+
(`` log(`done`) `` is an ordinary call), and in a slot `<` starts type
|
|
272
|
+
arguments only when `<…>` reads as such (`` fn(`a` < b) `` stays a
|
|
273
|
+
comparison). `` ask ..`x`: T `` and `` fn(..`x`: T) `` are still legal and
|
|
274
|
+
lower identically. The SPACE is mandatory after `ask`: `` ask`x` ``
|
|
275
|
+
(backtick glued to `ask`, or to the name after `ask with`) is NOLA1017 —
|
|
276
|
+
it reads as a tagged template.
|
|
193
277
|
- DECISION TYPES (intrinsic, no import): `Choice<{ billing: "Payments";
|
|
194
278
|
sales: null }>` (or `Choice<"a" | "b">`, 2–255 labels; number labels too,
|
|
195
279
|
alone or mixed — `Choice<1 | 2 | 3>` / `Choice<{ 1: "Low"; 2: "High" }>` /
|
|
@@ -205,12 +289,12 @@ export infer function lookup(text: string) {
|
|
|
205
289
|
at 0.5. RULE: adding criteria changes what the property evaluates to. A
|
|
206
290
|
type containing a decision type needs a DECISION model (`typesafe()`,
|
|
207
291
|
`mockProvider(replies, { decisions: true })` in tests) — on a chat model it
|
|
208
|
-
is NOLA3018 before the network.
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
-
|
|
292
|
+
is NOLA3018 before the network. There is NO `..choice` / `..scale` /
|
|
293
|
+
`..prob` sugar (retired 2026-09-23; an identifier after `..` is NOLA1005):
|
|
294
|
+
write the type long-hand — `` ask `Which team?`: Choice<{ billing:
|
|
295
|
+
"Payments"; sales: null }> ``, `` ask `How bad?`: Scale<["low", "high"]> ``,
|
|
296
|
+
`` ask `Urgent?`: Prob ``; malformed criteria are NOLA2015.
|
|
297
|
+
- The type accepts whatever resolves to a JSON shape: scalars, `Date`, arrays,
|
|
214
298
|
tuples, object literals, aliases/interfaces (same file, another file, a
|
|
215
299
|
package; `extends`, intersections, `Partial`/`Pick`/`Omit`, instantiated
|
|
216
300
|
generics), string-literal unions and string enums, object and nullable
|
|
@@ -230,7 +314,7 @@ export interface Conclusion {
|
|
|
230
314
|
(infer body or module body):
|
|
231
315
|
|
|
232
316
|
```tsi
|
|
233
|
-
export const nameIntent = ..`the user's full name
|
|
317
|
+
export const nameIntent = ..`the user's full name`: string; // legal, inert
|
|
234
318
|
```
|
|
235
319
|
|
|
236
320
|
## The `ask` operator
|
|
@@ -245,7 +329,7 @@ import { getUserById } from "./users.tsi";
|
|
|
245
329
|
type User = { name: string };
|
|
246
330
|
|
|
247
331
|
export infer function report(.text: string) {
|
|
248
|
-
const user = ask `the user named in the text
|
|
332
|
+
const user = ask `the user named in the text`: User; // extractor
|
|
249
333
|
const record = ask getUserById(user.name); // another infer function
|
|
250
334
|
return record;
|
|
251
335
|
}
|
|
@@ -255,8 +339,8 @@ export infer function report(.text: string) {
|
|
|
255
339
|
|
|
256
340
|
```tsi
|
|
257
341
|
export infer function summarize(.text: string) {
|
|
258
|
-
const draft = ask with fast `a rough summary
|
|
259
|
-
const final = ask with careful `a polished summary of: ${draft}
|
|
342
|
+
const draft = ask with fast `a rough summary`: string;
|
|
343
|
+
const final = ask with careful `a polished summary of: ${draft}`: string;
|
|
260
344
|
return final;
|
|
261
345
|
}
|
|
262
346
|
```
|
|
@@ -282,8 +366,9 @@ Three spellings:
|
|
|
282
366
|
declare function createTicket(title: string, priority: number): Promise<string>;
|
|
283
367
|
|
|
284
368
|
export infer function file(.request: string) {
|
|
285
|
-
// 1. sigil-less — a plain call whose arguments contain an extractor
|
|
286
|
-
|
|
369
|
+
// 1. sigil-less — a plain call whose arguments contain an extractor; the
|
|
370
|
+
// slot's `..` is implied (`..`a short ticket title`: string` still works)
|
|
371
|
+
const a = ask createTicket(`a short ticket title`: string, 2);
|
|
287
372
|
|
|
288
373
|
// 2. empty marker — identical lowering; the only spelling for a call
|
|
289
374
|
// intent whose arguments are all plain
|
|
@@ -291,8 +376,8 @@ export infer function file(.request: string) {
|
|
|
291
376
|
|
|
292
377
|
// 3. hint marker — the ONLY carrier of instruction text for the call
|
|
293
378
|
const c = ask createTicket`file the ticket the customer asked for`(
|
|
294
|
-
|
|
295
|
-
|
|
379
|
+
`a short ticket title`: string,
|
|
380
|
+
`priority 1-5, 1 is most urgent`: number,
|
|
296
381
|
);
|
|
297
382
|
|
|
298
383
|
return { a, b, c };
|
|
@@ -304,33 +389,36 @@ Detection rule for the sigil-less form — BOTH must hold:
|
|
|
304
389
|
- the callee is an `Identifier` or a `MemberExpression` (any nesting, computed
|
|
305
390
|
included), and
|
|
306
391
|
- at least one well-formed extractor appears in a slot position: a direct
|
|
307
|
-
argument, or nested at any depth inside plain object/array literals.
|
|
392
|
+
argument, or nested at any depth inside plain object/array literals. In
|
|
393
|
+
exactly those positions a typed template is an extractor without the `..`;
|
|
394
|
+
an untyped one is a plain string argument.
|
|
308
395
|
|
|
309
396
|
```tsi
|
|
310
397
|
declare const api: { save(order: { qty: number; note: string }): Promise<string> };
|
|
311
398
|
|
|
312
399
|
export infer function place(.request: string) {
|
|
313
400
|
// member callee + extractor nested in an object literal → call intent
|
|
314
|
-
return ask api.save({ qty: 1, note:
|
|
401
|
+
return ask api.save({ qty: 1, note: `a one-line note for the warehouse`: string });
|
|
315
402
|
}
|
|
316
403
|
```
|
|
317
404
|
|
|
318
|
-
These stay PLAIN calls (the extractor is just a value argument
|
|
319
|
-
inside a ternary, logical expression, spread element
|
|
320
|
-
a nested call (in `` outer(inner(
|
|
405
|
+
These stay PLAIN calls (the extractor is just a value argument, and needs its
|
|
406
|
+
`..` there): an extractor inside a ternary, logical expression, spread element
|
|
407
|
+
or template substitution; a nested call (in `` outer(inner(`x`: T)) `` the
|
|
408
|
+
INNER call is the intent and
|
|
321
409
|
`outer` receives an `Askable`); `new Foo(...)`, `super(...)`, `import(...)`,
|
|
322
410
|
optional calls (`fn?.(...)`, `a?.b(...)`); and exotic callees (`getFn()(...)`,
|
|
323
|
-
IIFEs) — use the
|
|
411
|
+
IIFEs) — use the hint form `` fn`hint`(…) `` if you want a call intent on one of those.
|
|
324
412
|
|
|
325
413
|
Parenthesizing an extractor does NOT opt out. To pass an intent as a plain
|
|
326
414
|
value, bind it to a variable first:
|
|
327
415
|
|
|
328
416
|
```tsi
|
|
329
|
-
const i = ..`a short title
|
|
417
|
+
const i = ..`a short title`: string;
|
|
330
418
|
helper(i); // plain call — helper receives the Askable
|
|
331
419
|
```
|
|
332
420
|
|
|
333
|
-
Every extractor used as a call-intent slot must carry an explicit
|
|
421
|
+
Every extractor used as a call-intent slot must carry an explicit type
|
|
334
422
|
(NOLA2004). The bare derive-all form `fn(..)` is reserved (NOLA1004).
|
|
335
423
|
|
|
336
424
|
### Result of a call intent — async callees are awaited
|
|
@@ -344,7 +432,7 @@ write `await ask fn(...)` — the extra `await` is a no-op.
|
|
|
344
432
|
declare function createTicket(title: string, priority: number): Promise<string>;
|
|
345
433
|
|
|
346
434
|
export infer function file(.request: string) {
|
|
347
|
-
const id = ask createTicket(
|
|
435
|
+
const id = ask createTicket(`a short ticket title`: string, 2); // id: string, not Promise<string>
|
|
348
436
|
return id;
|
|
349
437
|
}
|
|
350
438
|
```
|
|
@@ -359,62 +447,198 @@ Two consequences to keep in mind:
|
|
|
359
447
|
round trips only. Once the arguments are filled, the callee's own promise
|
|
360
448
|
runs to completion, the same as a plain `await fn()` in your code.
|
|
361
449
|
|
|
362
|
-
##
|
|
450
|
+
## Tool use
|
|
363
451
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
rule: a substitution hole whose expression starts with a single dot reads the
|
|
367
|
-
intent's prompt scope; every other hole is ordinary lexical JavaScript.
|
|
452
|
+
A tool-use ask offers the model functions it may call before it answers —
|
|
453
|
+
a bracketed list of function names between the task and the type:
|
|
368
454
|
|
|
369
455
|
```tsi
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
return
|
|
456
|
+
import { readFile, searchFiles } from "./tools.js";
|
|
457
|
+
|
|
458
|
+
export infer function find(.query: string) {
|
|
459
|
+
return ask `the files on disc D that belong to a US visa application` [searchFiles, readFile]: string[];
|
|
374
460
|
}
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
```tsi
|
|
464
|
+
import * as fileTools from "./tools.js";
|
|
465
|
+
import { dbTools } from "./db.js";
|
|
466
|
+
|
|
467
|
+
const report = ask `analyze this report` [fileTools, dbTools]: Report; // the model sees fileTools.search, dbTools.search, …
|
|
468
|
+
const files = ask `the matching files` [...fileTools]: string[]; // flat: search, readFile
|
|
469
|
+
```
|
|
375
470
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
471
|
+
Brackets OFFER, parentheses RUN: `` ask getUser(`id`: string) `` is a call
|
|
472
|
+
intent (getUser runs exactly once, the value is what it returned);
|
|
473
|
+
`` ask `the user's name` [getUser]: string `` offers getUser (the model
|
|
474
|
+
calls it zero or more times, the value is the model's answer). Reach for
|
|
475
|
+
the call when you want the function's own result, for the list when the
|
|
476
|
+
answer needs lookups first.
|
|
477
|
+
|
|
478
|
+
- Each provider call is a TURN: the model answers or requests tool calls;
|
|
479
|
+
Nola validates the arguments against the function's parameters, runs
|
|
480
|
+
the calls in the reply's order, renders each result (`fmt`: a string
|
|
481
|
+
verbatim, anything else JSON) and asks again with the transcript. An
|
|
482
|
+
unknown tool name or invalid arguments never reach the function — they
|
|
483
|
+
go back as that call's result. The answer then gets the usual validation
|
|
484
|
+
and single correction turn (not counted against the cap).
|
|
485
|
+
- Entries are VALUES, judged by the type checker: a name or dotted chain
|
|
486
|
+
names one function (`searchFiles`, `fs.readFile`) or a GROUP — a module
|
|
487
|
+
namespace, an object of functions, an array of them — whose functions
|
|
488
|
+
the model sees as `group.member`; `[...group]` offers them flat by their
|
|
489
|
+
own names; any other expression that is one function (`tools[0]`) is
|
|
490
|
+
named by the function. Group tools by what they are for: two groups
|
|
491
|
+
sharing a `search` never collide, a flat list with two does (NOLA2018).
|
|
492
|
+
The contract derives from each signature: one object over the
|
|
493
|
+
parameters, by name in order; optional/defaulted parameters optional; the
|
|
494
|
+
JSDoc summary is the description, `@param` tags describe the properties.
|
|
495
|
+
A member chain keeps its receiver (its object must be a plain chain —
|
|
496
|
+
a name, `this`, dotted / indexed / optional access; `api.client().search`
|
|
497
|
+
is NOLA2018, name the object first); a group's functions are called on
|
|
498
|
+
the group. One declared signature per tool: a union of functions, `any`,
|
|
499
|
+
`unknown`, a class, a group with nothing callable, a group reached
|
|
500
|
+
through an expression (name it or spread it): NOLA2018. An underivable
|
|
501
|
+
parameter (a rest parameter, a Map): NOLA2002 at the entry; a group
|
|
502
|
+
member it cannot use fails the group, naming the member. At run time a
|
|
503
|
+
list whose values do not match their types, an unnamed function or a
|
|
504
|
+
name collision is NOLA3022 before the network.
|
|
505
|
+
- The cap: `.withMaxTurns(n)` on the intent, else config `ask.maxTurns`
|
|
506
|
+
(10); past it NOLA3020, definitive, transcript attached. A throwing tool
|
|
507
|
+
is NOLA3021 with the error as `cause`; return a failure as a value when
|
|
508
|
+
the model should recover from it. `.withRetry(n)` re-runs the WHOLE
|
|
509
|
+
loop, tools included; `.withTimeout` bounds provider calls only.
|
|
510
|
+
- The model must support tool calls: `openai`, `anthropic`, `google`,
|
|
511
|
+
`model: "nola"`; `mockProvider(…, { tools: true })` with `toolCalls([{
|
|
512
|
+
name, args }])` values scripting tool turns; `replay()` of a recorded
|
|
513
|
+
ledger. Anything else is NOLA3019 before the network; `fallback` /
|
|
514
|
+
`roundRobin` skip members without support on a tool-use ask. A vendor
|
|
515
|
+
factory's `toolUse` option, beside `structuredOutputs`, names the
|
|
516
|
+
strategy (`"native"`, the default and the only one today) or marks a
|
|
517
|
+
host without tool calling (`toolUse: false` — unbranded, NOLA3019,
|
|
518
|
+
skipped by a group); a group mixing strategies is NOLA3003.
|
|
519
|
+
- Where the list is legal: directly after the template at the ask site
|
|
520
|
+
(`ask`, `ask with <name>`) and on the stored form (`` ..`p` [fn]: T ``);
|
|
521
|
+
the type follows the list (`]: T` — or `] : T` — or `]<T>`). Never in a call's
|
|
522
|
+
slots. A call-shaped entry (`` [getUser(`id`: string)] ``) is reserved
|
|
523
|
+
(NOLA1022); a literal or an empty list is NOLA1021. In a ternary's
|
|
524
|
+
consequent, parenthesize the typed ask (NOLA1023), as always.
|
|
525
|
+
- Receipts carry `turns` (each call's arguments, result or error,
|
|
526
|
+
duration); telemetry fires `onToolCallStart` / `onToolCallEnd`; the
|
|
527
|
+
terminal trace and `nola console` show the calls under the ask.
|
|
528
|
+
|
|
529
|
+
## The ask sequence
|
|
530
|
+
|
|
531
|
+
After the first text, texts and values alternate (spec 2026-10-12):
|
|
380
532
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
`(
|
|
533
|
+
```tsi
|
|
534
|
+
const owner = ask `the person responsible for` project `given this policy` policy: Person;
|
|
535
|
+
const user = ask `register the applicant` createUser(`user id`: string, `email`: string);
|
|
536
|
+
const files = ask `find` [searchFiles, readFile] `or, when stale,` [readDir] `and return paths`: string[];
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
- A bare value (a name, a member chain, a literal, `new X()`, a plain call,
|
|
540
|
+
a parenthesized expression) is DATA: the model reads an `<input name>`
|
|
541
|
+
block where you wrote it, named by what you wrote, with its type on the
|
|
542
|
+
wire; `${x}` inside a text stays your words. Prefer a hole for a literal.
|
|
543
|
+
- A call with a typed slot is the ACTION: the kind is function calling, the
|
|
544
|
+
call's result is the value, no `: T`. A `[list]` offers tools; several
|
|
545
|
+
lists union (the same entry in two lists is one tool), and the words
|
|
546
|
+
between them are guidance, nothing enforces "or".
|
|
547
|
+
- The kind follows the parts: texts and data → extract; one call → function
|
|
548
|
+
calling; one or more lists → tool use; a list beside a call is reserved
|
|
549
|
+
(NOLA2019); two calls, `: T` beside a call, text after a bare call
|
|
550
|
+
(`` ask f(`x`: T) `more` `` — put the words first), or a call with typed
|
|
551
|
+
slots inside a data value: NOLA2020.
|
|
552
|
+
- Two values never touch (`user [x]` is an index, `user (x)` a call); text
|
|
553
|
+
first; `: T` follows the last part (a backtick, `]` or the value) — glued,
|
|
554
|
+
spaced, or on its own line; `<T>` only after a text or a list. `this`,
|
|
555
|
+
`await`, `ask`, an unparenthesized function and a value ending in a
|
|
556
|
+
template are NOLA1020.
|
|
557
|
+
- An ask may span lines, one part per line: a line break alone never ends
|
|
558
|
+
it. After a text the next part may start on the next line, and the type
|
|
559
|
+
may stand on its own line; the ask ends at `;`, a closer, an operator, a
|
|
560
|
+
statement keyword (`const`, `return`, `type X`, `let x`, …) or its type.
|
|
561
|
+
After a VALUE a name on the next line is a new statement, as in JS. The
|
|
562
|
+
one trap: semicolon-free code after a call sequence that ends on a text —
|
|
563
|
+
`` …`done`\nnotify(files) `` takes `notify(files)` as a value; end the ask
|
|
564
|
+
with `;`.
|
|
565
|
+
A call's slot stays one text (a `..` sequence there is NOLA2019). A
|
|
566
|
+
sequence lowers to an instruction ARRAY of parts; a one-text ask lowers
|
|
567
|
+
as before.
|
|
568
|
+
|
|
569
|
+
## Values in an instruction: `.x` and `${x}`
|
|
570
|
+
|
|
571
|
+
Two spellings put a value in front of the model — pick by what the model
|
|
572
|
+
should treat it as.
|
|
573
|
+
|
|
574
|
+
```tsi
|
|
575
|
+
// data: an <input> block the system turn marks as "data, not instructions"
|
|
576
|
+
infer function triage(.ticket: string) {
|
|
577
|
+
`Answer with the order id only.`
|
|
578
|
+
return ask `order id`: string;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// your words: a value YOU control, spliced into the instruction where it stands
|
|
582
|
+
infer function triageFor(.ticket: string, team: string) {
|
|
583
|
+
`You answer for the ${team} team. Answer with the order id only.`
|
|
584
|
+
return ask `order id`: string;
|
|
585
|
+
}
|
|
384
586
|
```
|
|
385
587
|
|
|
386
|
-
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
the
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
a
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
588
|
+
- `.name` renders as `<input name="name">` after the instruction; a plain
|
|
589
|
+
parameter is never rendered. Both spellings on one value put it in the
|
|
590
|
+
prompt twice.
|
|
591
|
+
- Trust: interpolated text is INSTRUCTIONS the model follows. Interpolate
|
|
592
|
+
only what the developer controls (a team name, a count, a date); a
|
|
593
|
+
customer's message, a document, anything a user typed goes in `.name`,
|
|
594
|
+
where the system turn marks it as data.
|
|
595
|
+
- `${expr}` is ordinary TypeScript in EVERY instruction literal — a
|
|
596
|
+
context statement, an extractor prompt, a call hint. A string splices
|
|
597
|
+
verbatim; anything else is JSON (a `Date` as a quoted ISO string — write
|
|
598
|
+
`${d.toISOString()}` for bare text; `undefined` as `undefined`).
|
|
599
|
+
- When it is read: a context statement at EACH ask that sees it (a value
|
|
600
|
+
must be initialized by then — a `let` or `const` declared after the ask
|
|
601
|
+
is a ReferenceError at that ask); an extractor's prompt and a call hint when
|
|
602
|
+
the ask expression runs. A value after text in a context statement is the
|
|
603
|
+
same as a `${}` hole: `` `Page` oncall `…` `` ≡ `` `Page ${oncall} …` ``.
|
|
604
|
+
- A custom format is a function call in a hole (`${table(a, b)}`); a value
|
|
605
|
+
kept out of the prompt is a dotless parameter.
|
|
606
|
+
- Errors: a Nola construct (`..`, `ask`) in a context statement (a
|
|
607
|
+
parenthesized value, a hole) or in a call hint's hole is NOLA2010 — bare
|
|
608
|
+
right after a statement's text it is NOLA1020. A call intent is a legal
|
|
609
|
+
value in a context statement — it renders as its callee and arguments; a
|
|
610
|
+
HINTED one (`` fn`hint`(…) ``) must be parenthesized or written in a `${}`
|
|
611
|
+
hole (bare, the hint's backtick reads as the next text part: an extractor
|
|
612
|
+
argument is then NOLA2010). A bare `await` or `this` right after a statement's
|
|
613
|
+
text is NOLA1020; anywhere else in a value or hole it is a TypeScript
|
|
614
|
+
error (an item is read at each ask, outside the async body, in a function
|
|
615
|
+
of its own — TS1308 for `await`, TS2683 for `this`): compute the value
|
|
616
|
+
first, or assign `this` to a local. There is no
|
|
617
|
+
`${.member}` prompt scope: a dot cannot start a hole's expression (an
|
|
618
|
+
ordinary syntax error).
|
|
619
|
+
- Editor: completion, hover and TypeScript errors work inside `${…}`.
|
|
620
|
+
|
|
621
|
+
## The prompt
|
|
622
|
+
|
|
623
|
+
What reaches the model is the developer's text in four utility tags —
|
|
624
|
+
nothing else of Nola's is in the user turn:
|
|
625
|
+
|
|
626
|
+
- `<context function="f">` / `<context module="path">`: one scope — its
|
|
627
|
+
context statements (each starting on its own line), then one
|
|
628
|
+
`<input name="x">` per contextual param and `const .x` binding (a string
|
|
629
|
+
verbatim, anything else as JSON). Plain params are NOT rendered. Outermost
|
|
630
|
+
caller first, asking scope last.
|
|
631
|
+
- `<task>` last: the extractor's instruction; a call intent is
|
|
632
|
+
`<task call="fn">hint</task>` (self-closing without a hint).
|
|
633
|
+
- `<correction>` on the retry, after the rejected reply.
|
|
634
|
+
- The schema is NEVER in the prompt: it rides the provider's structured
|
|
635
|
+
output (`structuredOutputs: false` on a vendor factory puts it in the
|
|
636
|
+
system turn as `<schema>` for servers that cannot enforce it).
|
|
637
|
+
- The system turn is four constant sentences (`DEFAULT_SYSTEM`, exported
|
|
638
|
+
from `@nola-lang/providers`); `system.message` in the config REPLACES it.
|
|
639
|
+
- Every provider implements `infer(req)` over `req.intent` (the ask as
|
|
640
|
+
data) and renders it itself — `renderPrompt(req.intent)` is the default.
|
|
641
|
+
A model that still implements `complete(req)` is NOLA3003.
|
|
418
642
|
|
|
419
643
|
## Intent methods
|
|
420
644
|
|
|
@@ -422,10 +646,10 @@ Every intent (extractor, call intent, infer-function result) accepts:
|
|
|
422
646
|
|
|
423
647
|
```tsi
|
|
424
648
|
export infer function tuned(.text: string) {
|
|
425
|
-
const a = ask (..`the title
|
|
426
|
-
const b = ask (..`the body
|
|
427
|
-
const c = ask (..`a creative tagline
|
|
428
|
-
const d = ask (..`a one-paragraph summary
|
|
649
|
+
const a = ask (..`the title`: string).withRetry(2);
|
|
650
|
+
const b = ask (..`the body`: string).withModel("careful");
|
|
651
|
+
const c = ask (..`a creative tagline`: string).withParams({ temperature: 0.9, maxOutputTokens: 200 });
|
|
652
|
+
const d = ask (..`a one-paragraph summary`: string).withTimeout(10_000);
|
|
429
653
|
return { a, b, c, d };
|
|
430
654
|
}
|
|
431
655
|
```
|