create-nola-lang 0.1.14 → 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/dist/add.d.ts +3 -0
- package/dist/add.js +2 -1
- package/dist/checkout.js +1 -0
- package/dist/{chunk-CB26Z47Y.js → chunk-UTVGG44K.js} +73 -10
- package/dist/flow.d.ts +12 -1
- package/dist/flow.js +34 -5
- package/dist/index.js +1 -1
- package/dist/main.js +1 -1
- package/dist/package-manager.d.ts +5 -0
- package/dist/package-manager.js +7 -0
- package/dist/registry.d.ts +2 -2
- package/dist/registry.js +7 -0
- package/dist/scaffold.d.ts +17 -1
- package/dist/scaffold.js +35 -5
- package/package.json +1 -1
- package/skills/nola/SKILL.md +26 -3
- package/skills/nola/references/config.md +33 -4
- package/skills/nola/references/patterns.md +62 -0
- package/skills/nola/references/pitfalls.md +127 -13
- package/skills/nola/references/syntax.md +134 -9
package/dist/add.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Runtime } from "./package-manager.js";
|
|
1
2
|
import { type ProviderId } from "./providers.js";
|
|
2
3
|
export interface AddResult {
|
|
3
4
|
/** files written, e.g. ["nola.config.ts", "package.json"] */
|
|
@@ -17,6 +18,8 @@ export interface AddOptions {
|
|
|
17
18
|
* empty template's — the nola key step rewrites it afterwards (`applyTrial`).
|
|
18
19
|
*/
|
|
19
20
|
provider?: ProviderId;
|
|
21
|
+
/** "bun" also merges @nola-lang/bun into devDependencies; the bunfig lines are suggested by the flow's outro, never written */
|
|
22
|
+
runtime?: Runtime;
|
|
20
23
|
}
|
|
21
24
|
/**
|
|
22
25
|
* Retrofit an existing npm project: write nola.config.ts (unless present) and
|
package/dist/add.js
CHANGED
|
@@ -61,7 +61,8 @@ export async function addNola(targetDir, opts = {}) {
|
|
|
61
61
|
await writeFile(configPath, await readFile(vendor ? providerConfigUrl(vendor.id) : EMPTY_CONFIG_URL, "utf8"));
|
|
62
62
|
wrote.push("nola.config.ts");
|
|
63
63
|
}
|
|
64
|
-
|
|
64
|
+
const required = opts.runtime === "bun" ? [...REQUIRED, ["devDependencies", "@nola-lang/bun", null]] : REQUIRED;
|
|
65
|
+
for (const [section, name, fixedRange] of required) {
|
|
65
66
|
const existing = existingRange(name);
|
|
66
67
|
if (existing !== undefined) {
|
|
67
68
|
skipped.push(`${name} already present (${existing}) — left untouched`);
|
package/dist/checkout.js
CHANGED
|
@@ -52,6 +52,7 @@ async function checkoutRoot() {
|
|
|
52
52
|
var LINKS = [
|
|
53
53
|
["@nola-lang/runtime", "runtime"],
|
|
54
54
|
["@nola-lang/providers", "providers"],
|
|
55
|
+
["@nola-lang/bun", "bun"],
|
|
55
56
|
["nola-lang", "nola-lang"]
|
|
56
57
|
];
|
|
57
58
|
var LINK_CHECKOUT_ENV = "NOLA_LINK_CHECKOUT";
|
|
@@ -184,6 +185,13 @@ var TEMPLATES = [
|
|
|
184
185
|
featured: true,
|
|
185
186
|
entry: "src/main.tsi"
|
|
186
187
|
},
|
|
188
|
+
{
|
|
189
|
+
name: "tool-use",
|
|
190
|
+
label: "offer functions as tools: the model calls them as it needs before it answers",
|
|
191
|
+
source: "example",
|
|
192
|
+
featured: true,
|
|
193
|
+
entry: "src/main.tsi"
|
|
194
|
+
},
|
|
187
195
|
{
|
|
188
196
|
name: "agent-loop",
|
|
189
197
|
label: "an agent loop in one .tsi file: a top-level while with an ask, and a context statement read on every pass",
|
|
@@ -246,17 +254,34 @@ function envExample(envVar) {
|
|
|
246
254
|
${envVar}=
|
|
247
255
|
`;
|
|
248
256
|
}
|
|
257
|
+
var BUNFIG_TOML = 'preload = ["@nola-lang/bun/preload"]\n\n[test]\npreload = ["@nola-lang/bun/preload"]\n';
|
|
258
|
+
var BUN_ENGINE = ">=1.3.0";
|
|
259
|
+
function applyBunRuntime(content, opts) {
|
|
260
|
+
const pkg = JSON.parse(content);
|
|
261
|
+
const scripts = pkg.scripts ?? {};
|
|
262
|
+
scripts.start = `bun ${opts.entry}`;
|
|
263
|
+
pkg.scripts = scripts;
|
|
264
|
+
const dev = pkg.devDependencies ?? {};
|
|
265
|
+
dev["@nola-lang/bun"] = `^${opts.version}`;
|
|
266
|
+
pkg.devDependencies = dev;
|
|
267
|
+
const engines = pkg.engines ?? {};
|
|
268
|
+
engines.bun = BUN_ENGINE;
|
|
269
|
+
pkg.engines = engines;
|
|
270
|
+
return `${JSON.stringify(pkg, null, 2)}
|
|
271
|
+
`;
|
|
272
|
+
}
|
|
249
273
|
var GITIGNORE_URL = new URL("../templates/_gitignore", import.meta.url);
|
|
250
274
|
async function withRecommendedGitignore(files) {
|
|
251
275
|
if (!files.has(".gitignore")) files.set(".gitignore", await readFile3(GITIGNORE_URL, "utf8"));
|
|
252
276
|
return files;
|
|
253
277
|
}
|
|
254
|
-
function nextStepsComment(ide, template) {
|
|
278
|
+
function nextStepsComment(ide, template, runtime = "node") {
|
|
255
279
|
if (ide === "vscode") {
|
|
256
280
|
const breakpointIn = template === "empty" ? "on your first `ask` in this file" : templateByName(template)?.entry ? "on the `ask` line below" : template === "typescript-interop" ? "in src/person.tsi" : "in your .tsi file";
|
|
281
|
+
const runLine = runtime === "bun" ? "// 1. Run this file: bun start (F5 runs it too, under Node's loader \u2014 .vscode/launch.json is already set up)." : "// 1. Press F5 to run this file (.vscode/launch.json is already set up).";
|
|
257
282
|
return [
|
|
258
283
|
"// Next steps in VS Code:",
|
|
259
|
-
|
|
284
|
+
runLine,
|
|
260
285
|
`// 2. Set a breakpoint ${breakpointIn} and press F5 again to step through the ask.`,
|
|
261
286
|
'// 3. Install the recommended "Nola" extension when VS Code offers it \u2014 IntelliSense,',
|
|
262
287
|
"// go to definition and diagnostics inside .tsi files."
|
|
@@ -264,7 +289,7 @@ function nextStepsComment(ide, template) {
|
|
|
264
289
|
}
|
|
265
290
|
return [
|
|
266
291
|
"// Next steps:",
|
|
267
|
-
|
|
292
|
+
`// 1. Run this file: ${runtime === "bun" ? "bun start" : "npm start"}`,
|
|
268
293
|
"// 2. Set up your editor (VS Code extension, debugging): https://nola.sh/docs/start/editor-setup/"
|
|
269
294
|
].join("\n");
|
|
270
295
|
}
|
|
@@ -296,18 +321,24 @@ async function scaffold(targetDir, opts = {}) {
|
|
|
296
321
|
await prepareTarget(absRoot, opts.force ?? false);
|
|
297
322
|
const name = opts.name ?? basename(absRoot);
|
|
298
323
|
const provider = opts.provider ?? "none";
|
|
324
|
+
const runtime = opts.runtime ?? "node";
|
|
325
|
+
const entry = entryFile(template);
|
|
299
326
|
if (provider !== "none") {
|
|
300
327
|
for (const offline of OFFLINE_ONLY) files.delete(offline);
|
|
301
328
|
files.set("nola.config.ts", await readFile3(providerConfigUrl(provider), "utf8"));
|
|
302
329
|
}
|
|
303
330
|
const manifest = files.get("package.json");
|
|
304
331
|
if (manifest) files.set("package.json", rewriteExamplePackageJson(manifest, { name, version }));
|
|
332
|
+
if (runtime === "bun") {
|
|
333
|
+
files.set("bunfig.toml", BUNFIG_TOML);
|
|
334
|
+
const current = files.get("package.json");
|
|
335
|
+
if (current !== void 0) files.set("package.json", applyBunRuntime(current, { entry, version }));
|
|
336
|
+
}
|
|
305
337
|
await withRecommendedGitignore(files);
|
|
306
338
|
const envVar = vendorEnvVar(template, provider);
|
|
307
339
|
if (envVar !== void 0 && !files.has(".env.example")) files.set(".env.example", envExample(envVar));
|
|
308
|
-
const entry = entryFile(template);
|
|
309
340
|
const program = files.get(entry);
|
|
310
|
-
if (program !== void 0) files.set(entry, `${nextStepsComment(opts.ide ?? "none", template)}
|
|
341
|
+
if (program !== void 0) files.set(entry, `${nextStepsComment(opts.ide ?? "none", template, runtime)}
|
|
311
342
|
|
|
312
343
|
${program}`);
|
|
313
344
|
for (const [relPath, content] of files) {
|
|
@@ -368,7 +399,8 @@ async function addNola(targetDir, opts = {}) {
|
|
|
368
399
|
await writeFile3(configPath, await readFile4(vendor ? providerConfigUrl(vendor.id) : EMPTY_CONFIG_URL, "utf8"));
|
|
369
400
|
wrote.push("nola.config.ts");
|
|
370
401
|
}
|
|
371
|
-
|
|
402
|
+
const required = opts.runtime === "bun" ? [...REQUIRED, ["devDependencies", "@nola-lang/bun", null]] : REQUIRED;
|
|
403
|
+
for (const [section, name, fixedRange] of required) {
|
|
372
404
|
const existing = existingRange(name);
|
|
373
405
|
if (existing !== void 0) {
|
|
374
406
|
skipped.push(`${name} already present (${existing}) \u2014 left untouched`);
|
|
@@ -1558,6 +1590,12 @@ function detectPackageManager(userAgent = process.env.npm_config_user_agent) {
|
|
|
1558
1590
|
function packageManagerCommands(pm) {
|
|
1559
1591
|
return { install: `${pm} install`, start: `${pm} start`, run: `${pm} run` };
|
|
1560
1592
|
}
|
|
1593
|
+
function isRuntime(value) {
|
|
1594
|
+
return value === "node" || value === "bun";
|
|
1595
|
+
}
|
|
1596
|
+
function defaultRuntime(pm) {
|
|
1597
|
+
return pm === "bun" ? "bun" : "node";
|
|
1598
|
+
}
|
|
1561
1599
|
|
|
1562
1600
|
// src/flow.ts
|
|
1563
1601
|
var DEFAULT_DIR = "nola-app";
|
|
@@ -1674,11 +1712,18 @@ async function resolveProvider(input, prompter, template) {
|
|
|
1674
1712
|
return choice;
|
|
1675
1713
|
}
|
|
1676
1714
|
}
|
|
1715
|
+
function resolveRuntime(input) {
|
|
1716
|
+
if (input.runtime !== void 0) {
|
|
1717
|
+
if (!isRuntime(input.runtime)) throw new Error(`invalid --runtime "${input.runtime}" (valid: node, bun)`);
|
|
1718
|
+
return input.runtime;
|
|
1719
|
+
}
|
|
1720
|
+
return defaultRuntime(input.packageManager ?? "npm");
|
|
1721
|
+
}
|
|
1677
1722
|
async function resolveExtras(input, prompter, dir, template) {
|
|
1678
1723
|
const provider = await resolveProvider(input, prompter, template);
|
|
1679
1724
|
if (provider === null) return null;
|
|
1680
1725
|
const setup = await resolveSetup(input, prompter, dir);
|
|
1681
|
-
return setup === null ? null : { provider, ...setup };
|
|
1726
|
+
return setup === null ? null : { provider, ...setup, runtime: resolveRuntime(input) };
|
|
1682
1727
|
}
|
|
1683
1728
|
async function resolveScaffoldOptions(input, prompter) {
|
|
1684
1729
|
if (input.add && input.template) {
|
|
@@ -1689,6 +1734,9 @@ async function resolveScaffoldOptions(input, prompter) {
|
|
|
1689
1734
|
if (input.ide !== void 0 && input.ide !== "vscode" && input.ide !== "none") {
|
|
1690
1735
|
throw new Error(`invalid --ide "${input.ide}" (valid: vscode, none)`);
|
|
1691
1736
|
}
|
|
1737
|
+
if (input.runtime !== void 0 && !isRuntime(input.runtime)) {
|
|
1738
|
+
throw new Error(`invalid --runtime "${input.runtime}" (valid: node, bun)`);
|
|
1739
|
+
}
|
|
1692
1740
|
if (input.agents !== void 0) parseAgentsFlag(input.agents);
|
|
1693
1741
|
flaggedProvider(input);
|
|
1694
1742
|
if (input.add) {
|
|
@@ -1802,6 +1850,10 @@ var FLOW_OPTIONS = {
|
|
|
1802
1850
|
},
|
|
1803
1851
|
add: { type: "boolean", description: "add Nola to the existing project in [dir] instead of scaffolding" },
|
|
1804
1852
|
ide: { type: "string", description: "editor setup: vscode (the default) | none" },
|
|
1853
|
+
runtime: {
|
|
1854
|
+
type: "string",
|
|
1855
|
+
description: "runtime the project runs on: node | bun (default: bun when bun ran the scaffolder \u2014 bunfig.toml preload + `bun src/main.tsi`)"
|
|
1856
|
+
},
|
|
1805
1857
|
agents: {
|
|
1806
1858
|
type: "string",
|
|
1807
1859
|
description: `agent skill files: ${AGENT_IDS.join(",")} | all | none (default: claude,universal)`
|
|
@@ -1961,6 +2013,8 @@ async function runFlow(args, opts = {}) {
|
|
|
1961
2013
|
agents: args.agents,
|
|
1962
2014
|
provider: args.provider,
|
|
1963
2015
|
trial: args.trial,
|
|
2016
|
+
runtime: args.runtime,
|
|
2017
|
+
packageManager: pm,
|
|
1964
2018
|
keyPath,
|
|
1965
2019
|
signIn: signInNow,
|
|
1966
2020
|
cwd: opts.cwd,
|
|
@@ -1974,7 +2028,7 @@ async function runFlow(args, opts = {}) {
|
|
|
1974
2028
|
}
|
|
1975
2029
|
if (outcome.kind === "add") {
|
|
1976
2030
|
const grant = outcome.provider === "nola" ? await obtainTrial(prompter, opts, interactive) : null;
|
|
1977
|
-
const result = await addNola(outcome.dir, { provider: outcome.provider });
|
|
2031
|
+
const result = await addNola(outcome.dir, { provider: outcome.provider, runtime: outcome.runtime });
|
|
1978
2032
|
const trial = grant ? await applyTrial(outcome.dir, { apiKey: grant.apiKey, hasConfig: !result.wrote.includes("nola.config.ts") }) : { wrote: [], skipped: [] };
|
|
1979
2033
|
const ide = outcome.ide === "vscode" ? await writeVscodeSetup(outcome.dir) : { wrote: [], skipped: [] };
|
|
1980
2034
|
const ag = outcome.agents.length > 0 ? await writeAgentSkills(outcome.dir, outcome.agents) : { wrote: [], removed: [], skipped: [] };
|
|
@@ -1987,7 +2041,10 @@ async function runFlow(args, opts = {}) {
|
|
|
1987
2041
|
}
|
|
1988
2042
|
const lines = [
|
|
1989
2043
|
packageManagerCommands(pm).install,
|
|
1990
|
-
|
|
2044
|
+
...outcome.runtime === "bun" ? [
|
|
2045
|
+
'bunfig.toml: preload = ["@nola-lang/bun/preload"] (runs .tsi from source under Bun)',
|
|
2046
|
+
'optional start script: "start": "bun src/main.ts"'
|
|
2047
|
+
] : ['optional start script: "start": "nola run src/main.ts"'],
|
|
1991
2048
|
'tsconfig tip: directory-style include (e.g. ["src"]) lets the editor see .tsi files',
|
|
1992
2049
|
commented(CONSOLE_COMMAND, CONSOLE_NOTE)
|
|
1993
2050
|
];
|
|
@@ -2002,7 +2059,13 @@ Next steps:
|
|
|
2002
2059
|
try {
|
|
2003
2060
|
const grant = outcome.provider === "nola" ? await obtainTrial(prompter, opts, interactive) : null;
|
|
2004
2061
|
const provider = outcome.provider === "nola" ? grant ? "nola" : "none" : outcome.provider;
|
|
2005
|
-
const { files } = await scaffold(outcome.dir, {
|
|
2062
|
+
const { files } = await scaffold(outcome.dir, {
|
|
2063
|
+
template: outcome.template,
|
|
2064
|
+
force: outcome.force,
|
|
2065
|
+
provider,
|
|
2066
|
+
ide: outcome.ide,
|
|
2067
|
+
runtime: outcome.runtime
|
|
2068
|
+
});
|
|
2006
2069
|
let trialWrote = [];
|
|
2007
2070
|
if (grant) {
|
|
2008
2071
|
const trial = await applyTrial(outcome.dir, { apiKey: grant.apiKey, hasConfig: false });
|
package/dist/flow.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type AgentId } from "./agents.js";
|
|
2
2
|
import { type CommandSpec } from "./cli.js";
|
|
3
3
|
import { type Launcher } from "./launch.js";
|
|
4
|
-
import { type PackageManager } from "./package-manager.js";
|
|
4
|
+
import { type PackageManager, type Runtime } from "./package-manager.js";
|
|
5
5
|
import { type ProviderId } from "./providers.js";
|
|
6
6
|
export interface PrompterOption {
|
|
7
7
|
value: string;
|
|
@@ -45,6 +45,10 @@ export interface FlowInput {
|
|
|
45
45
|
provider?: string;
|
|
46
46
|
/** --trial / --no-trial: shorthand for --provider nola / --provider none */
|
|
47
47
|
trial?: boolean;
|
|
48
|
+
/** --runtime <node|bun>; undefined = bun when bun ran the scaffolder, else node */
|
|
49
|
+
runtime?: string;
|
|
50
|
+
/** the manager that invoked us — decides the default runtime */
|
|
51
|
+
packageManager?: PackageManager;
|
|
48
52
|
/** how the nola row is worded, decided by runFlow from ~/.nola (spec 2026-09-04-cli-sign-in-design.md §5); default trial */
|
|
49
53
|
keyPath?: KeyPath;
|
|
50
54
|
/**
|
|
@@ -75,12 +79,14 @@ export type FlowOutcome = {
|
|
|
75
79
|
provider: ProviderId;
|
|
76
80
|
ide: "vscode" | "none";
|
|
77
81
|
agents: AgentId[];
|
|
82
|
+
runtime: Runtime;
|
|
78
83
|
} | {
|
|
79
84
|
kind: "add";
|
|
80
85
|
dir: string;
|
|
81
86
|
provider: ProviderId;
|
|
82
87
|
ide: "vscode" | "none";
|
|
83
88
|
agents: AgentId[];
|
|
89
|
+
runtime: Runtime;
|
|
84
90
|
} | {
|
|
85
91
|
kind: "cancelled";
|
|
86
92
|
};
|
|
@@ -130,6 +136,7 @@ export interface RunFlowArgs {
|
|
|
130
136
|
agents?: string;
|
|
131
137
|
provider?: string;
|
|
132
138
|
trial?: boolean;
|
|
139
|
+
runtime?: string;
|
|
133
140
|
}
|
|
134
141
|
/**
|
|
135
142
|
* The flow's flags, declared ONCE for both bins (`npm create nola-lang` and
|
|
@@ -148,6 +155,10 @@ export declare const FLOW_OPTIONS: {
|
|
|
148
155
|
readonly type: "string";
|
|
149
156
|
readonly description: "editor setup: vscode (the default) | none";
|
|
150
157
|
};
|
|
158
|
+
readonly runtime: {
|
|
159
|
+
readonly type: "string";
|
|
160
|
+
readonly description: "runtime the project runs on: node | bun (default: bun when bun ran the scaffolder — bunfig.toml preload + `bun src/main.tsi`)";
|
|
161
|
+
};
|
|
151
162
|
readonly agents: {
|
|
152
163
|
readonly type: "string";
|
|
153
164
|
readonly description: `agent skill files: ${string} | all | none (default: claude,universal)`;
|
package/dist/flow.js
CHANGED
|
@@ -16,7 +16,7 @@ import { acquireKey, SignInRequiredError } from "./key.js";
|
|
|
16
16
|
import { realLauncher } from "./launch.js";
|
|
17
17
|
import { nodeVersionWarning } from "./node-version.js";
|
|
18
18
|
import { openBrowser } from "./open-url.js";
|
|
19
|
-
import { detectPackageManager, packageManagerCommands } from "./package-manager.js";
|
|
19
|
+
import { defaultRuntime, detectPackageManager, isRuntime, packageManagerCommands } from "./package-manager.js";
|
|
20
20
|
import { isProviderId, PROVIDERS, providerById, providerIds } from "./providers.js";
|
|
21
21
|
import { entryFile, exampleNames, featuredNames, templateByName, templateNames } from "./registry.js";
|
|
22
22
|
import { ownVersion, scaffold, vendorEnvVar } from "./scaffold.js";
|
|
@@ -230,13 +230,22 @@ async function resolveProvider(input, prompter, template) {
|
|
|
230
230
|
return choice;
|
|
231
231
|
}
|
|
232
232
|
}
|
|
233
|
+
/** `--runtime`, else the manager's default (bun → bun). Validated up front like the other flags. */
|
|
234
|
+
function resolveRuntime(input) {
|
|
235
|
+
if (input.runtime !== undefined) {
|
|
236
|
+
if (!isRuntime(input.runtime))
|
|
237
|
+
throw new Error(`invalid --runtime "${input.runtime}" (valid: node, bun)`);
|
|
238
|
+
return input.runtime;
|
|
239
|
+
}
|
|
240
|
+
return defaultRuntime(input.packageManager ?? "npm");
|
|
241
|
+
}
|
|
233
242
|
/** The provider and setup (editor + agents) questions, in that order, shared by every outcome site; null = cancelled. */
|
|
234
243
|
async function resolveExtras(input, prompter, dir, template) {
|
|
235
244
|
const provider = await resolveProvider(input, prompter, template);
|
|
236
245
|
if (provider === null)
|
|
237
246
|
return null;
|
|
238
247
|
const setup = await resolveSetup(input, prompter, dir);
|
|
239
|
-
return setup === null ? null : { provider, ...setup };
|
|
248
|
+
return setup === null ? null : { provider, ...setup, runtime: resolveRuntime(input) };
|
|
240
249
|
}
|
|
241
250
|
/** Args fill prompts; whatever is missing is asked (never asked non-interactively). */
|
|
242
251
|
export async function resolveScaffoldOptions(input, prompter) {
|
|
@@ -246,6 +255,9 @@ export async function resolveScaffoldOptions(input, prompter) {
|
|
|
246
255
|
if (input.ide !== undefined && input.ide !== "vscode" && input.ide !== "none") {
|
|
247
256
|
throw new Error(`invalid --ide "${input.ide}" (valid: vscode, none)`);
|
|
248
257
|
}
|
|
258
|
+
if (input.runtime !== undefined && !isRuntime(input.runtime)) {
|
|
259
|
+
throw new Error(`invalid --runtime "${input.runtime}" (valid: node, bun)`);
|
|
260
|
+
}
|
|
249
261
|
if (input.agents !== undefined)
|
|
250
262
|
parseAgentsFlag(input.agents); // throws on unknown ids
|
|
251
263
|
flaggedProvider(input); // throws on an unknown id or a contradiction
|
|
@@ -377,6 +389,10 @@ export const FLOW_OPTIONS = {
|
|
|
377
389
|
},
|
|
378
390
|
add: { type: "boolean", description: "add Nola to the existing project in [dir] instead of scaffolding" },
|
|
379
391
|
ide: { type: "string", description: "editor setup: vscode (the default) | none" },
|
|
392
|
+
runtime: {
|
|
393
|
+
type: "string",
|
|
394
|
+
description: "runtime the project runs on: node | bun (default: bun when bun ran the scaffolder — bunfig.toml preload + `bun src/main.tsi`)",
|
|
395
|
+
},
|
|
380
396
|
agents: {
|
|
381
397
|
type: "string",
|
|
382
398
|
description: `agent skill files: ${AGENT_IDS.join(",")} | all | none (default: claude,universal)`,
|
|
@@ -596,6 +612,8 @@ export async function runFlow(args, opts = {}) {
|
|
|
596
612
|
agents: args.agents,
|
|
597
613
|
provider: args.provider,
|
|
598
614
|
trial: args.trial,
|
|
615
|
+
runtime: args.runtime,
|
|
616
|
+
packageManager: pm,
|
|
599
617
|
keyPath,
|
|
600
618
|
signIn: signInNow,
|
|
601
619
|
cwd: opts.cwd,
|
|
@@ -609,7 +627,7 @@ export async function runFlow(args, opts = {}) {
|
|
|
609
627
|
}
|
|
610
628
|
if (outcome.kind === "add") {
|
|
611
629
|
const grant = outcome.provider === "nola" ? await obtainTrial(prompter, opts, interactive) : null;
|
|
612
|
-
const result = await addNola(outcome.dir, { provider: outcome.provider });
|
|
630
|
+
const result = await addNola(outcome.dir, { provider: outcome.provider, runtime: outcome.runtime });
|
|
613
631
|
const trial = grant
|
|
614
632
|
? await applyTrial(outcome.dir, { apiKey: grant.apiKey, hasConfig: !result.wrote.includes("nola.config.ts") })
|
|
615
633
|
: { wrote: [], skipped: [] };
|
|
@@ -629,7 +647,12 @@ export async function runFlow(args, opts = {}) {
|
|
|
629
647
|
}
|
|
630
648
|
const lines = [
|
|
631
649
|
packageManagerCommands(pm).install,
|
|
632
|
-
|
|
650
|
+
...(outcome.runtime === "bun"
|
|
651
|
+
? [
|
|
652
|
+
'bunfig.toml: preload = ["@nola-lang/bun/preload"] (runs .tsi from source under Bun)',
|
|
653
|
+
'optional start script: "start": "bun src/main.ts"',
|
|
654
|
+
]
|
|
655
|
+
: ['optional start script: "start": "nola run src/main.ts"']),
|
|
633
656
|
'tsconfig tip: directory-style include (e.g. ["src"]) lets the editor see .tsi files',
|
|
634
657
|
commented(CONSOLE_COMMAND, CONSOLE_NOTE),
|
|
635
658
|
];
|
|
@@ -644,7 +667,13 @@ export async function runFlow(args, opts = {}) {
|
|
|
644
667
|
const grant = outcome.provider === "nola" ? await obtainTrial(prompter, opts, interactive) : null;
|
|
645
668
|
// A nola choice without a key (declined sign-in, API failure) scaffolds the plain template — the note above says why.
|
|
646
669
|
const provider = outcome.provider === "nola" ? (grant ? "nola" : "none") : outcome.provider;
|
|
647
|
-
const { files } = await scaffold(outcome.dir, {
|
|
670
|
+
const { files } = await scaffold(outcome.dir, {
|
|
671
|
+
template: outcome.template,
|
|
672
|
+
force: outcome.force,
|
|
673
|
+
provider,
|
|
674
|
+
ide: outcome.ide,
|
|
675
|
+
runtime: outcome.runtime,
|
|
676
|
+
});
|
|
648
677
|
let trialWrote = [];
|
|
649
678
|
if (grant) {
|
|
650
679
|
const trial = await applyTrial(outcome.dir, { apiKey: grant.apiKey, hasConfig: false });
|
package/dist/index.js
CHANGED
package/dist/main.js
CHANGED
|
@@ -14,4 +14,9 @@ export interface PackageManagerCommands {
|
|
|
14
14
|
run: string;
|
|
15
15
|
}
|
|
16
16
|
export declare function packageManagerCommands(pm: PackageManager): PackageManagerCommands;
|
|
17
|
+
/** The runtime a scaffold targets: Node (the loader, `nola run`) or Bun (`@nola-lang/bun`'s preload, `bun src/main.tsi`). */
|
|
18
|
+
export type Runtime = "node" | "bun";
|
|
19
|
+
export declare function isRuntime(value: string): value is Runtime;
|
|
20
|
+
/** `bun create nola` produces a Bun project unless `--runtime node` says otherwise; every other manager scaffolds for Node. */
|
|
21
|
+
export declare function defaultRuntime(pm: PackageManager): Runtime;
|
|
17
22
|
//# sourceMappingURL=package-manager.d.ts.map
|
package/dist/package-manager.js
CHANGED
|
@@ -5,4 +5,11 @@ export function detectPackageManager(userAgent = process.env.npm_config_user_age
|
|
|
5
5
|
export function packageManagerCommands(pm) {
|
|
6
6
|
return { install: `${pm} install`, start: `${pm} start`, run: `${pm} run` };
|
|
7
7
|
}
|
|
8
|
+
export function isRuntime(value) {
|
|
9
|
+
return value === "node" || value === "bun";
|
|
10
|
+
}
|
|
11
|
+
/** `bun create nola` produces a Bun project unless `--runtime node` says otherwise; every other manager scaffolds for Node. */
|
|
12
|
+
export function defaultRuntime(pm) {
|
|
13
|
+
return pm === "bun" ? "bun" : "node";
|
|
14
|
+
}
|
|
8
15
|
//# sourceMappingURL=package-manager.js.map
|
package/dist/registry.d.ts
CHANGED
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
* chosen provider's config and the next-steps comment on top.
|
|
11
11
|
*
|
|
12
12
|
* The first menu leads (in this order) and is organized BY FEATURE:
|
|
13
|
-
* `feature-extraction` (the default)
|
|
14
|
-
* `.tsi` file with a top-level ask — the
|
|
13
|
+
* `feature-extraction` (the default), `function-calling` and `tool-use` are
|
|
14
|
+
* each one `.tsi` file with a top-level ask — the three things Nola does —
|
|
15
15
|
* `agent-loop` is the same shape around a top-level `while`,
|
|
16
16
|
* `typescript-interop` is the infer-function shape called from plain
|
|
17
17
|
* TypeScript, `triage-ticket` the one-file shape on a non-chat vendor, then
|
package/dist/registry.js
CHANGED
|
@@ -15,6 +15,13 @@ export const TEMPLATES = [
|
|
|
15
15
|
featured: true,
|
|
16
16
|
entry: "src/main.tsi",
|
|
17
17
|
},
|
|
18
|
+
{
|
|
19
|
+
name: "tool-use",
|
|
20
|
+
label: "offer functions as tools: the model calls them as it needs before it answers",
|
|
21
|
+
source: "example",
|
|
22
|
+
featured: true,
|
|
23
|
+
entry: "src/main.tsi",
|
|
24
|
+
},
|
|
18
25
|
{
|
|
19
26
|
name: "agent-loop",
|
|
20
27
|
label: "an agent loop in one .tsi file: a top-level while with an ask, and a context statement read on every pass",
|
package/dist/scaffold.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Runtime } from "./package-manager.js";
|
|
1
2
|
import { type ProviderId } from "./providers.js";
|
|
2
3
|
export interface ScaffoldResult {
|
|
3
4
|
root: string;
|
|
@@ -23,6 +24,12 @@ export interface ScaffoldOptions {
|
|
|
23
24
|
* files the flow writes make true); without one it stays editor-neutral.
|
|
24
25
|
*/
|
|
25
26
|
ide?: "vscode" | "none";
|
|
27
|
+
/**
|
|
28
|
+
* The runtime the project runs on; default "node". "bun" adds bunfig.toml with the
|
|
29
|
+
* @nola-lang/bun preload, a `bun <entry>` start script, the devDependency and
|
|
30
|
+
* engines.bun — build/check stay the Node CLI (spec 2026-10-14-bun-support).
|
|
31
|
+
*/
|
|
32
|
+
runtime?: Runtime;
|
|
26
33
|
}
|
|
27
34
|
/** The config a live provider choice writes over the template's own (`templates/_providers/<id>.config.ts`). */
|
|
28
35
|
export declare function providerConfigUrl(provider: Exclude<ProviderId, "none">): URL;
|
|
@@ -39,6 +46,15 @@ export declare function vendorEnvVar(template: string, provider: ProviderId): st
|
|
|
39
46
|
* `.gitignore` keeps trackable (`!.env.example`), so it can be committed.
|
|
40
47
|
*/
|
|
41
48
|
export declare function envExample(envVar: string): string;
|
|
49
|
+
/** The Bun project's bunfig.toml: the preload for `bun src/main.tsi` and for `bun test`. */
|
|
50
|
+
export declare const BUNFIG_TOML = "preload = [\"@nola-lang/bun/preload\"]\n\n[test]\npreload = [\"@nola-lang/bun/preload\"]\n";
|
|
51
|
+
/** The Bun floor @nola-lang/bun needs (process.getBuiltinModule). */
|
|
52
|
+
export declare const BUN_ENGINE = ">=1.3.0";
|
|
53
|
+
/** A Bun project's manifest additions on top of the template's: the start script, the devDependency, engines.bun. */
|
|
54
|
+
export declare function applyBunRuntime(content: string, opts: {
|
|
55
|
+
entry: string;
|
|
56
|
+
version: string;
|
|
57
|
+
}): string;
|
|
42
58
|
/**
|
|
43
59
|
* Add the recommended `.gitignore` to a template's files. Templates are
|
|
44
60
|
* copied as committed — examples from `examples/`, where the monorepo's root
|
|
@@ -53,7 +69,7 @@ export declare function withRecommendedGitignore(files: Map<string, string>): Pr
|
|
|
53
69
|
* the editor step's .vscode files, which are what make F5 and the extension
|
|
54
70
|
* prompt real.
|
|
55
71
|
*/
|
|
56
|
-
export declare function nextStepsComment(ide: "vscode" | "none", template: string): string;
|
|
72
|
+
export declare function nextStepsComment(ide: "vscode" | "none", template: string, runtime?: Runtime): string;
|
|
57
73
|
export declare function ownVersion(): Promise<string>;
|
|
58
74
|
/**
|
|
59
75
|
* Lay a template down into `targetDir`. One implementation serves both entry
|
package/dist/scaffold.js
CHANGED
|
@@ -29,6 +29,24 @@ export function vendorEnvVar(template, provider) {
|
|
|
29
29
|
export function envExample(envVar) {
|
|
30
30
|
return `# Copy to .env and fill in — \`nola run\` applies .env before evaluating nola.config.ts.\n${envVar}=\n`;
|
|
31
31
|
}
|
|
32
|
+
/** The Bun project's bunfig.toml: the preload for `bun src/main.tsi` and for `bun test`. */
|
|
33
|
+
export const BUNFIG_TOML = 'preload = ["@nola-lang/bun/preload"]\n\n[test]\npreload = ["@nola-lang/bun/preload"]\n';
|
|
34
|
+
/** The Bun floor @nola-lang/bun needs (process.getBuiltinModule). */
|
|
35
|
+
export const BUN_ENGINE = ">=1.3.0";
|
|
36
|
+
/** A Bun project's manifest additions on top of the template's: the start script, the devDependency, engines.bun. */
|
|
37
|
+
export function applyBunRuntime(content, opts) {
|
|
38
|
+
const pkg = JSON.parse(content);
|
|
39
|
+
const scripts = pkg.scripts ?? {};
|
|
40
|
+
scripts.start = `bun ${opts.entry}`;
|
|
41
|
+
pkg.scripts = scripts;
|
|
42
|
+
const dev = pkg.devDependencies ?? {};
|
|
43
|
+
dev["@nola-lang/bun"] = `^${opts.version}`;
|
|
44
|
+
pkg.devDependencies = dev;
|
|
45
|
+
const engines = pkg.engines ?? {};
|
|
46
|
+
engines.bun = BUN_ENGINE;
|
|
47
|
+
pkg.engines = engines;
|
|
48
|
+
return `${JSON.stringify(pkg, null, 2)}\n`;
|
|
49
|
+
}
|
|
32
50
|
/** The ONE recommended .gitignore — every scaffold gets it; no template keeps a copy of its own (_gitignore ships underscored: npm pack strips nested .gitignore files). */
|
|
33
51
|
const GITIGNORE_URL = new URL("../templates/_gitignore", import.meta.url);
|
|
34
52
|
/**
|
|
@@ -49,7 +67,7 @@ export async function withRecommendedGitignore(files) {
|
|
|
49
67
|
* the editor step's .vscode files, which are what make F5 and the extension
|
|
50
68
|
* prompt real.
|
|
51
69
|
*/
|
|
52
|
-
export function nextStepsComment(ide, template) {
|
|
70
|
+
export function nextStepsComment(ide, template, runtime = "node") {
|
|
53
71
|
if (ide === "vscode") {
|
|
54
72
|
// `empty`'s stub has no ask yet — the breakpoint goes on the one the user writes.
|
|
55
73
|
const breakpointIn = template === "empty"
|
|
@@ -59,9 +77,13 @@ export function nextStepsComment(ide, template) {
|
|
|
59
77
|
: template === "typescript-interop"
|
|
60
78
|
? "in src/person.tsi"
|
|
61
79
|
: "in your .tsi file";
|
|
80
|
+
// A Bun project still debugs under Node's loader: the .vscode files are valid whenever Node is present.
|
|
81
|
+
const runLine = runtime === "bun"
|
|
82
|
+
? "// 1. Run this file: bun start (F5 runs it too, under Node's loader — .vscode/launch.json is already set up)."
|
|
83
|
+
: "// 1. Press F5 to run this file (.vscode/launch.json is already set up).";
|
|
62
84
|
return [
|
|
63
85
|
"// Next steps in VS Code:",
|
|
64
|
-
|
|
86
|
+
runLine,
|
|
65
87
|
`// 2. Set a breakpoint ${breakpointIn} and press F5 again to step through the ask.`,
|
|
66
88
|
'// 3. Install the recommended "Nola" extension when VS Code offers it — IntelliSense,',
|
|
67
89
|
"// go to definition and diagnostics inside .tsi files.",
|
|
@@ -69,7 +91,7 @@ export function nextStepsComment(ide, template) {
|
|
|
69
91
|
}
|
|
70
92
|
return [
|
|
71
93
|
"// Next steps:",
|
|
72
|
-
|
|
94
|
+
`// 1. Run this file: ${runtime === "bun" ? "bun start" : "npm start"}`,
|
|
73
95
|
"// 2. Set up your editor (VS Code extension, debugging): https://nola.sh/docs/start/editor-setup/",
|
|
74
96
|
].join("\n");
|
|
75
97
|
}
|
|
@@ -124,6 +146,8 @@ export async function scaffold(targetDir, opts = {}) {
|
|
|
124
146
|
await prepareTarget(absRoot, opts.force ?? false);
|
|
125
147
|
const name = opts.name ?? basename(absRoot);
|
|
126
148
|
const provider = opts.provider ?? "none";
|
|
149
|
+
const runtime = opts.runtime ?? "node";
|
|
150
|
+
const entry = entryFile(template);
|
|
127
151
|
// A live provider's config replaces the template's own; the ledger it replayed goes with it.
|
|
128
152
|
if (provider !== "none") {
|
|
129
153
|
for (const offline of OFFLINE_ONLY)
|
|
@@ -133,15 +157,21 @@ export async function scaffold(targetDir, opts = {}) {
|
|
|
133
157
|
const manifest = files.get("package.json");
|
|
134
158
|
if (manifest)
|
|
135
159
|
files.set("package.json", rewriteExamplePackageJson(manifest, { name, version }));
|
|
160
|
+
// A Bun project (spec 2026-10-14-bun-support): the preload, a `bun <entry>` start script, the devDependency, engines.bun.
|
|
161
|
+
if (runtime === "bun") {
|
|
162
|
+
files.set("bunfig.toml", BUNFIG_TOML);
|
|
163
|
+
const current = files.get("package.json");
|
|
164
|
+
if (current !== undefined)
|
|
165
|
+
files.set("package.json", applyBunRuntime(current, { entry, version }));
|
|
166
|
+
}
|
|
136
167
|
await withRecommendedGitignore(files);
|
|
137
168
|
const envVar = vendorEnvVar(template, provider);
|
|
138
169
|
if (envVar !== undefined && !files.has(".env.example"))
|
|
139
170
|
files.set(".env.example", envExample(envVar));
|
|
140
171
|
// The entry file greets the user with the next steps — it is what VS Code opens.
|
|
141
|
-
const entry = entryFile(template);
|
|
142
172
|
const program = files.get(entry);
|
|
143
173
|
if (program !== undefined)
|
|
144
|
-
files.set(entry, `${nextStepsComment(opts.ide ?? "none", template)}\n\n${program}`);
|
|
174
|
+
files.set(entry, `${nextStepsComment(opts.ide ?? "none", template, runtime)}\n\n${program}`);
|
|
145
175
|
for (const [relPath, content] of files) {
|
|
146
176
|
const target = join(absRoot, relPath);
|
|
147
177
|
await mkdir(join(target, ".."), { recursive: true });
|
package/package.json
CHANGED
package/skills/nola/SKILL.md
CHANGED
|
@@ -45,8 +45,8 @@ infer function extractPerson(.text: string) {
|
|
|
45
45
|
one dot marks a value flowing INTO the model (`.name`); a template after
|
|
46
46
|
`ask` is a value coming OUT.
|
|
47
47
|
- `` ask `prompt`: T `` — an extractor: asks the LLM for a `T`. The colon
|
|
48
|
-
is
|
|
49
|
-
construct (write `: T` unless a page shows otherwise).
|
|
48
|
+
is on the closing backtick's line (glued or spaced); `` ask `prompt`<T> ``
|
|
49
|
+
is the same construct (write `: T` unless a page shows otherwise).
|
|
50
50
|
`${...}` interpolation works inside the backticks. The `..` is implied
|
|
51
51
|
directly after `ask` and in a call's argument list — a typed template
|
|
52
52
|
that starts an argument, or a value nested in a plain object/array
|
|
@@ -59,6 +59,24 @@ infer function extractPerson(.text: string) {
|
|
|
59
59
|
extractor — is a call intent (the LLM fills the extractor-shaped
|
|
60
60
|
arguments, then the function runs). Only `` fn`hint`(...) `` carries
|
|
61
61
|
instruction text.
|
|
62
|
+
- `` ask `use` admin `and` cfg.defaults `go`: T `` — an ASK SEQUENCE: a
|
|
63
|
+
bare value between texts is named data the model sees where it stands
|
|
64
|
+
(`${x}` stays your words); `` ask `words` fn(`slot`: T) `` is a call
|
|
65
|
+
intent with words; several `[lists]` union with your words between them;
|
|
66
|
+
the type glues to the last part. The kind follows the parts: texts and
|
|
67
|
+
data → extract, one call → function calling, lists → tool use; a list
|
|
68
|
+
beside a call is reserved (NOLA2019).
|
|
69
|
+
- `` ask `task` [searchFiles, fs.readFile]: T `` — a TOOL-USE ask: the
|
|
70
|
+
functions in brackets between the template and the type are tools the
|
|
71
|
+
model may call, as often as it needs, before it answers; Nola runs each
|
|
72
|
+
call, hands the result back and asks again, the typed answer ends the
|
|
73
|
+
loop. Parentheses RUN a function once (a call intent), brackets OFFER
|
|
74
|
+
functions. Entries are names or member chains (a call written there is
|
|
75
|
+
reserved, NOLA1022); the contract derives from the signature and JSDoc;
|
|
76
|
+
`.withMaxTurns(n)` / config `ask.maxTurns` (10) cap the turns (NOLA3020);
|
|
77
|
+
a tool that throws ends the ask (NOLA3021); the model must support tool
|
|
78
|
+
calls — openai/anthropic/google/"nola", or `mockProvider(…, { tools:
|
|
79
|
+
true })` (NOLA3019 otherwise).
|
|
62
80
|
- `ask with <modelName> <intent>` — routes one ask through a named model
|
|
63
81
|
from `nola.config.ts`. The name must be a static identifier. When the
|
|
64
82
|
platform serves inference (`model: "nola"`) any unconfigured
|
|
@@ -96,6 +114,11 @@ infer function extractPerson(.text: string) {
|
|
|
96
114
|
`import { f } from "./x.tsi"`. Imports of plain TS use NodeNext style:
|
|
97
115
|
`import { g } from "./y.js"` (the `.js` extension, even though the source
|
|
98
116
|
file is `.ts`).
|
|
117
|
+
- `.tsi` runs from source through the Node loader (`nola run`, Node ≥ 22.18)
|
|
118
|
+
or, on Bun, through `@nola-lang/bun`'s preload — `preload =
|
|
119
|
+
["@nola-lang/bun/preload"]` in `bunfig.toml`, then `bun src/main.tsi`;
|
|
120
|
+
`nola check` / `nola build` stay on Node (`references/config.md`, "Running
|
|
121
|
+
on Bun").
|
|
99
122
|
- Every `export type` / `export interface` in a `.tsi` is ALSO a runtime
|
|
100
123
|
value (`User.toJsonSchema()`, `User.validate(v)`, `User.parse(v)`,
|
|
101
124
|
`User["~standard"]` — Standard Schema AND Standard JSON Schema, so
|
|
@@ -124,5 +147,5 @@ Read these before non-trivial work:
|
|
|
124
147
|
|
|
125
148
|
- `references/syntax.md` — full grammar and semantics of every construct
|
|
126
149
|
- `references/patterns.md` — worked examples (extraction, call intents)
|
|
127
|
-
- `references/config.md` — `nola.config.ts`, providers, project layout
|
|
150
|
+
- `references/config.md` — `nola.config.ts`, providers, project layout, running on Bun
|
|
128
151
|
- `references/pitfalls.md` — common errors (NOLA codes) and their fixes
|
|
@@ -39,7 +39,9 @@ export default defineConfig({
|
|
|
39
39
|
```
|
|
40
40
|
|
|
41
41
|
`openai({ model })` requires an explicit model; optional fields are `apiKey`,
|
|
42
|
-
`apiKeyEnv` (default `"OPENAI_API_KEY"`), `baseUrl` and `fetch`.
|
|
42
|
+
`apiKeyEnv` (default `"OPENAI_API_KEY"`), `baseUrl` and `fetch`. With a custom
|
|
43
|
+
`fetch`, `apiKey`/`apiKeyEnv` are optional: the fetch authenticates, and without
|
|
44
|
+
a key no vendor auth header is sent (the same rule for every vendor factory).
|
|
43
45
|
|
|
44
46
|
`typesafe()` (typesafe.ai's Jev, reads `TYPESAFE_API_KEY`, model defaults to
|
|
45
47
|
`jev-latest`) is NOT a chat model: it is the DECISION model that answers the
|
|
@@ -232,11 +234,11 @@ the providers package:
|
|
|
232
234
|
"check": "nola check"
|
|
233
235
|
},
|
|
234
236
|
"dependencies": {
|
|
235
|
-
"@nola-lang/providers": "^0.1.
|
|
236
|
-
"@nola-lang/runtime": "^0.1.
|
|
237
|
+
"@nola-lang/providers": "^0.1.15",
|
|
238
|
+
"@nola-lang/runtime": "^0.1.15"
|
|
237
239
|
},
|
|
238
240
|
"devDependencies": {
|
|
239
|
-
"nola-lang": "^0.1.
|
|
241
|
+
"nola-lang": "^0.1.15",
|
|
240
242
|
"typescript": "^5.6.0"
|
|
241
243
|
},
|
|
242
244
|
"engines": { "node": ">=22.18" }
|
|
@@ -270,6 +272,33 @@ for you). Do not mix versions.
|
|
|
270
272
|
a bare `npx nola` outside a project resolves an unrelated npm package named
|
|
271
273
|
`nola`.)
|
|
272
274
|
|
|
275
|
+
### Running on Bun
|
|
276
|
+
|
|
277
|
+
`bun create nola` scaffolds a Bun project: `@nola-lang/bun` in devDependencies,
|
|
278
|
+
`bunfig.toml` with its preload, `start: "bun src/main.tsi"`. Any Node project
|
|
279
|
+
becomes one by adding the same three things:
|
|
280
|
+
|
|
281
|
+
```toml
|
|
282
|
+
# bunfig.toml
|
|
283
|
+
preload = ["@nola-lang/bun/preload"]
|
|
284
|
+
|
|
285
|
+
[test]
|
|
286
|
+
preload = ["@nola-lang/bun/preload"]
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
- `bun src/main.tsi` / `bun --watch src/main.tsi` run `.tsi` from source;
|
|
290
|
+
`bun test` files import `.tsi` modules. Bun ≥ 1.3.
|
|
291
|
+
- One-step bundles and executables: a build script with
|
|
292
|
+
`Bun.build({ entrypoints: ["src/main.tsi"], plugins: [nola()], target: "bun",
|
|
293
|
+
compile: { outfile: "my-app" } })` (`import { nola } from "@nola-lang/bun"`).
|
|
294
|
+
- `nola build` output runs under `bun` with nothing added
|
|
295
|
+
(`bun dist/src/main.tsi.js`, `bun build --compile dist/src/main.tsi.js`).
|
|
296
|
+
- `nola check`, `nola build`, `nola console` run on Node (`bun run check`
|
|
297
|
+
launches them there). `nola run` is the Node loader — under Bun it is
|
|
298
|
+
NOLA3015; use the preload. No source map rides a Bun plugin: stack-trace
|
|
299
|
+
lines through `.tsi` are right at module level and in plain functions, ONE
|
|
300
|
+
TOO HIGH inside an `infer function` body, and columns inside an `ask` drift.
|
|
301
|
+
|
|
273
302
|
## tsconfig.json
|
|
274
303
|
|
|
275
304
|
```json
|
|
@@ -258,6 +258,68 @@ call intent awaits a promise-returning callee itself (`ask` ≈ `await`), so
|
|
|
258
258
|
ask, `.withRetry(n)` on a call intent re-invokes it on failure — only use it
|
|
259
259
|
when the target is idempotent.
|
|
260
260
|
|
|
261
|
+
## The tool-use project: offer functions, let the model call them
|
|
262
|
+
|
|
263
|
+
The same one-file shape for the third feature (`npm create nola` scaffolds
|
|
264
|
+
it as `tool-use`): the top-level `ask` offers two plain-TypeScript functions
|
|
265
|
+
as tools. The model calls them as it needs — here it lists a folder, reads
|
|
266
|
+
a file to be sure, then answers — and the typed answer is what `ask` yields.
|
|
267
|
+
|
|
268
|
+
```ts
|
|
269
|
+
// src/tools.ts — plain TypeScript; the JSDoc is the contract the model sees
|
|
270
|
+
/**
|
|
271
|
+
* Lists the files under a directory on disc D.
|
|
272
|
+
* @param dir an absolute directory path, such as D:/docs
|
|
273
|
+
* @param pattern keeps only the paths that contain it
|
|
274
|
+
*/
|
|
275
|
+
export async function searchFiles(dir: string, pattern?: string): Promise<string[]> { /* … */ return []; }
|
|
276
|
+
|
|
277
|
+
/** Reads a file's text. */
|
|
278
|
+
export async function readFile(path: string): Promise<string> { /* … */ return ""; }
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
```tsi
|
|
282
|
+
// src/main.tsi — run with: nola run src/main.tsi
|
|
283
|
+
import { readFile, searchFiles } from "./tools.js";
|
|
284
|
+
|
|
285
|
+
const files = ask `the files on disc D that belong to a US visa application` [searchFiles, readFile]: string[];
|
|
286
|
+
|
|
287
|
+
console.log(JSON.stringify(files));
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
Parentheses run a function once with model-filled arguments (the
|
|
291
|
+
function-calling project); brackets offer functions the model may call zero
|
|
292
|
+
or more times. Cap the loop with `.withMaxTurns(n)` when a task should not
|
|
293
|
+
wander; return a failure as a value (`{ error: "no such file" }`) when the
|
|
294
|
+
model should recover from it, since a thrown error ends the ask.
|
|
295
|
+
|
|
296
|
+
## Data in place: a bare value in the instruction
|
|
297
|
+
|
|
298
|
+
When the model needs a value beside the words that explain it, write the
|
|
299
|
+
value bare between texts (an ask sequence): it reaches the model as a
|
|
300
|
+
named input block where you wrote it. A `${}` hole splices the value into
|
|
301
|
+
your words instead — pick by what the model should treat it as.
|
|
302
|
+
|
|
303
|
+
```tsi
|
|
304
|
+
// src/main.tsi
|
|
305
|
+
interface Person {
|
|
306
|
+
name: string;
|
|
307
|
+
}
|
|
308
|
+
declare const project: { id: string; lead: string };
|
|
309
|
+
declare const policy: string;
|
|
310
|
+
|
|
311
|
+
// data where it stands: <input name="project"> … </input>, then your next words
|
|
312
|
+
export const owner = ask `the person responsible for` project `given this policy` policy: Person;
|
|
313
|
+
|
|
314
|
+
// words: the id is spliced into the sentence
|
|
315
|
+
export const lead = ask `the lead of project ${project.id}`: string;
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
The same shape puts words on a call (`` ask `register the applicant` createUser(`user id`: string) ``)
|
|
319
|
+
and words between tool lists (`` ask `find` [searchFiles] `or, when stale,` [readDir] `done`: string[] ``).
|
|
320
|
+
The kind follows the parts, never the data: texts and data extract, a call
|
|
321
|
+
with a typed slot is function calling, lists are tool use.
|
|
322
|
+
|
|
261
323
|
## An agent loop with a live context statement
|
|
262
324
|
|
|
263
325
|
A context statement is read at EACH ask that sees it, so one placed before a
|
|
@@ -135,10 +135,11 @@ export const stored = ..`the user's full name`: string; // stored: dots
|
|
|
135
135
|
|
|
136
136
|
> expected a type after the extractor's `:` on the same line — write `…`: T.
|
|
137
137
|
|
|
138
|
-
The colon spelling `` ask `the ticket id`: string ``
|
|
139
|
-
the
|
|
140
|
-
|
|
141
|
-
having no
|
|
138
|
+
The colon spelling `` ask `the ticket id`: string `` takes the colon glued,
|
|
139
|
+
spaced or on the next line (`` `p` : string `` and `` `p`\n : string `` are the
|
|
140
|
+
same ask — a line break alone never ends an ask) and needs the type on the
|
|
141
|
+
colon's line. Two more traps come with it, both from the type having no
|
|
142
|
+
closing delimiter:
|
|
142
143
|
|
|
143
144
|
```tsi
|
|
144
145
|
interface User { name: string }
|
|
@@ -164,6 +165,12 @@ comparison). Chain intent methods after a parenthesized extractor
|
|
|
164
165
|
> parentheses, or assign it to a local first (`await`, `ask` and `this` cannot
|
|
165
166
|
> be values).
|
|
166
167
|
|
|
168
|
+
The same code, with ask-site wording, covers an ask sequence: `this`,
|
|
169
|
+
`await`, `ask`, `function` or `class` where a value would start after a
|
|
170
|
+
text, a value ending in a backtick (`` ask `p` new Foo `more` ``), and text
|
|
171
|
+
after the type (`` ask `p`: T `more` `` — the type ends the sequence; move
|
|
172
|
+
the text before `: T`).
|
|
173
|
+
|
|
167
174
|
```tsi
|
|
168
175
|
export infer function total(.items: string[], a: number, b: number) {
|
|
169
176
|
// WRONG — an operator after a value
|
|
@@ -345,6 +352,101 @@ A bare template with no type, `` createTicket(`a short ticket title`, 2) ``,
|
|
|
345
352
|
is not a slot at all: it is a plain string argument and the call stays an
|
|
346
353
|
ordinary call — no error, and no model request.
|
|
347
354
|
|
|
355
|
+
## NOLA1021 / NOLA1022 — a tool list holds functions
|
|
356
|
+
|
|
357
|
+
> a tool list holds functions — a name, a member, an expression that is one function, or a group (a module or an object of functions, an array of them), bare or spread; a literal cannot be one, and the list is not empty.
|
|
358
|
+
|
|
359
|
+
The brackets after an ask's template are a TOOL LIST (tool use). An entry is
|
|
360
|
+
a value: a name or dotted chain (one function, or a group — a module, an
|
|
361
|
+
object of functions, an array of them), a spread of a group, or an
|
|
362
|
+
expression that is one function. A literal (a string, a number, an arrow,
|
|
363
|
+
an object or array literal) or an empty list is NOLA1021, and a call written
|
|
364
|
+
in the list — describing the arguments in place — is reserved (NOLA1022):
|
|
365
|
+
|
|
366
|
+
```tsi
|
|
367
|
+
// not-checked — WRONG
|
|
368
|
+
const a = ask `find the files` [searchFiles, "readFile"]: string[]; // NOLA1021
|
|
369
|
+
const b = ask `get the user` [getUser(`the id`: string)]: User; // NOLA1022 — reserved
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
```tsi
|
|
373
|
+
// RIGHT
|
|
374
|
+
const a = ask `find the files` [searchFiles, fs.readFile]: string[];
|
|
375
|
+
const g = ask `find the files` [fileTools, ...moreTools]: string[]; // a group by name, a group flattened
|
|
376
|
+
const b = ask `get the user` [getUser]: User; // the arguments come from getUser's signature
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
## NOLA1023 — a typed ask in a ternary's consequent
|
|
380
|
+
|
|
381
|
+
> a typed extractor directly in a ternary's consequent — parenthesize it: `cond ? (ask `p`: T) : y`.
|
|
382
|
+
|
|
383
|
+
Two colons with different meanings would sit side by side, so the typed ask
|
|
384
|
+
goes in parentheses (an untyped ask, a call intent and the `<T>` form need
|
|
385
|
+
none; the alternate position is free):
|
|
386
|
+
|
|
387
|
+
```tsi
|
|
388
|
+
// not-checked — WRONG
|
|
389
|
+
const t = cond ? ask `the title`: string : "untitled";
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
```tsi
|
|
393
|
+
// RIGHT
|
|
394
|
+
const t = cond ? (ask `the title`: string) : "untitled";
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
## NOLA2018 — a tool list entry the compiler cannot use
|
|
398
|
+
|
|
399
|
+
An entry that is neither one function nor a group (a value, a type-only
|
|
400
|
+
import, a class, a union of function types — `const f = cond ? a : b` —
|
|
401
|
+
`any`), a function under a spread, a group reached through an expression
|
|
402
|
+
(`[groups[0]]` — name it or spread it), a method reached through a call
|
|
403
|
+
(`api.client().search` — name the object first), a group with nothing
|
|
404
|
+
callable, or
|
|
405
|
+
two tools that would share one name on the wire (`a.b` beside `a_b`; the
|
|
406
|
+
same function twice; a spread's `search` beside another). Name a function
|
|
407
|
+
or a group, keep the names distinct, offer colliding groups bare so their
|
|
408
|
+
names carry the prefix. A parameter the walk cannot derive — a rest
|
|
409
|
+
parameter, a `Map`, a generic parameter — is NOLA2002 at the entry; a group
|
|
410
|
+
member the walk cannot use fails the group, naming the member.
|
|
411
|
+
|
|
412
|
+
## NOLA2019 — a reserved shape of the ask sequence
|
|
413
|
+
|
|
414
|
+
Tools beside a call: `` ask `use` [searchFiles] `then` createUser(`id`: string) ``
|
|
415
|
+
is the kind where the loop runs and the call's slots are the answer —
|
|
416
|
+
designed, not built. Offer the function as a tool, or make the call its own
|
|
417
|
+
ask after the tool-use ask. Also a `..` sequence inside a call's slot
|
|
418
|
+
(`` fn(..`t` user: string) ``): a slot's instruction is one text.
|
|
419
|
+
|
|
420
|
+
## NOLA2020 — an ask sequence that cannot mean one thing
|
|
421
|
+
|
|
422
|
+
Two calls with typed slots (an ask makes one call — split the asks); a
|
|
423
|
+
`: T` beside a call (the call's result is the type — drop it); text after
|
|
424
|
+
a bare call (`` ask f(`x`: string) `more` `` — put the words first:
|
|
425
|
+
`` ask `more` f(`x`: string) ``); a call with typed slots inside a data
|
|
426
|
+
value (`` ask `use` (createUser(`id`: string)) `go`: T `` — write it bare
|
|
427
|
+
between texts, it is the action, not data).
|
|
428
|
+
|
|
429
|
+
## NOLA3019 – NOLA3022 — tool use at run time
|
|
430
|
+
|
|
431
|
+
- NOLA3019: the resolved model cannot request tool calls (a hand-written
|
|
432
|
+
`LanguageModel`, a mock without `{ tools: true }`, `typesafe()`, a vendor
|
|
433
|
+
model with `toolUse: false`). Route the
|
|
434
|
+
ask to `openai` / `anthropic` / `google` / `"nola"`, or brand the mock.
|
|
435
|
+
Refused before the network.
|
|
436
|
+
- NOLA3020: the model kept calling tools past the cap (`.withMaxTurns(n)`,
|
|
437
|
+
else `ask.maxTurns`, 10). Raise the cap, give it tools that answer in fewer
|
|
438
|
+
calls, or sharpen the task. Definitive; the transcript rides the error.
|
|
439
|
+
- NOLA3021: a tool threw. Fix the tool, or return the failure as a value
|
|
440
|
+
when the model should recover from it. Invalid arguments never reach the
|
|
441
|
+
function — they go back to the model as that call's result.
|
|
442
|
+
- NOLA3022: the list could not be built when the ask ran — a value that is
|
|
443
|
+
not what its type promised (a group member that is not a function, a
|
|
444
|
+
tuple of another length), a function with no name (an anonymous arrow in
|
|
445
|
+
an array), or two tools with one wire name. Before the network.
|
|
446
|
+
|
|
447
|
+
`.withRetry(n)` on a tool-use ask re-runs the WHOLE loop, tools included:
|
|
448
|
+
only with tools that are safe to repeat.
|
|
449
|
+
|
|
348
450
|
## NOLA3010 — bare `await` on a raw extract or call intent
|
|
349
451
|
|
|
350
452
|
> extract/call intents carry no construction scope — only `ask` supplies their
|
|
@@ -465,13 +567,19 @@ export infer function route(.ticket: string) {
|
|
|
465
567
|
}
|
|
466
568
|
```
|
|
467
569
|
|
|
468
|
-
## NOLA3015 —
|
|
570
|
+
## NOLA3015 — the Node loader under Bun or Deno
|
|
571
|
+
|
|
572
|
+
`nola run` and `node --import nola-lang/register` ARE Node's module-hooks API;
|
|
573
|
+
Bun and Deno do not run them. On Bun, run `.tsi` from source through
|
|
574
|
+
`@nola-lang/bun` instead: `preload = ["@nola-lang/bun/preload"]` in
|
|
575
|
+
`bunfig.toml`, then `bun src/main.tsi` (see config.md, "Running on Bun").
|
|
576
|
+
Deno cannot load `.tsi` at all — run on Node. Any package manager is fine
|
|
577
|
+
(`bun install`, `bun run start`: the `nola` bin is a Node script).
|
|
578
|
+
|
|
579
|
+
## NOLA3023 — Bun older than 1.3
|
|
469
580
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
the `nola` bin is a Node script), but `bun --bun`, `bun src/main.ts` and
|
|
473
|
-
`deno run` cannot load `.tsi`. Run on Node: `nola run` or
|
|
474
|
-
`node --import nola-lang/register src/main.ts`.
|
|
581
|
+
`@nola-lang/bun` needs `process.getBuiltinModule` (Bun ≥ 1.3). Run
|
|
582
|
+
`bun upgrade`.
|
|
475
583
|
|
|
476
584
|
## Other things that will not parse
|
|
477
585
|
|
|
@@ -481,6 +589,12 @@ the `nola` bin is a Node script), but `bun --bun`, `bun src/main.ts` and
|
|
|
481
589
|
- `infer` on a method, arrow function or function expression — top-level
|
|
482
590
|
function declarations only.
|
|
483
591
|
- `fn(..)` — the bare derive-all call form is reserved (NOLA1004).
|
|
484
|
-
- `` ask `p` :
|
|
485
|
-
|
|
486
|
-
|
|
592
|
+
- `` cond ? ask `p` : y `` — a colon on the extractor's line is its type,
|
|
593
|
+
spaced or glued, so the ask reads `y` as its type and the ternary loses its
|
|
594
|
+
alternate: NOLA1023. A typed ask standing bare as a consequent is the same
|
|
595
|
+
code — parenthesize it: `` cond ? (ask `p`: T) : y ``, on one line or
|
|
596
|
+
three: the formatted `` cond\n ? ask `p`\n : y `` reads `y` as the type
|
|
597
|
+
too, since a line break alone never ends an ask.
|
|
598
|
+
- `` fn(`x` [tools]: T) `` — a tool list is never a call slot; a bracket
|
|
599
|
+
there keeps its JavaScript meaning. The list goes on the ask site or the
|
|
600
|
+
stored `..` form only.
|
|
@@ -236,16 +236,22 @@ export infer function lookup(text: string) {
|
|
|
236
236
|
- With no type, the extractor asks for free text: the wire schema is a plain
|
|
237
237
|
string and the static TS type is `any`. Give every extractor an explicit
|
|
238
238
|
type unless you deliberately want unconstrained prose.
|
|
239
|
-
- TWO SPELLINGS OF THE TYPE, one construct. `: T` — a colon
|
|
240
|
-
closing backtick
|
|
241
|
-
|
|
242
|
-
|
|
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> ``,
|
|
243
244
|
`` fn(`x`<T>) ``, `` ask `q`<Choice<C>> `` — same AST, same emit, same
|
|
244
245
|
definition, same ledger entries; legal everywhere `: T` is. Rules for the
|
|
245
|
-
colon:
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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 `.`
|
|
249
255
|
continues the type (`` `p`: User.withRetry(2) `` is the type
|
|
250
256
|
`User.withRetry` called with 2) — chain intent methods after a
|
|
251
257
|
parenthesized extractor (`` ask (..`p`: User).withRetry(2) ``), a
|
|
@@ -256,7 +262,7 @@ export infer function lookup(text: string) {
|
|
|
256
262
|
starts an argument, or a property value / element of a plain object or
|
|
257
263
|
array literal written in the arguments: `` fn(`x`: T) ``,
|
|
258
264
|
`` api.save({ note: `x`: T }) ``, `` fn([`x`: T]) ``. Everywhere else it is
|
|
259
|
-
required: `` const i = ..`x`: T ``, `` fn(cond ? ..`x`: T : y) ``,
|
|
265
|
+
required: `` const i = ..`x`: T ``, `` fn(cond ? (..`x`: T) : y) ``,
|
|
260
266
|
`` fn(...[..`x`: T]) ``, `` new Foo(..`x`: T) ``,
|
|
261
267
|
`` ask (..`x`: T).withRetry(2) `` (parenthesized: the template is no longer
|
|
262
268
|
a first token — `` fn((`x`: T)) `` is a syntax error too). A `` `x`<T> ``
|
|
@@ -441,6 +447,125 @@ Two consequences to keep in mind:
|
|
|
441
447
|
round trips only. Once the arguments are filled, the callee's own promise
|
|
442
448
|
runs to completion, the same as a plain `await fn()` in your code.
|
|
443
449
|
|
|
450
|
+
## Tool use
|
|
451
|
+
|
|
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:
|
|
454
|
+
|
|
455
|
+
```tsi
|
|
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[];
|
|
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
|
+
```
|
|
470
|
+
|
|
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):
|
|
532
|
+
|
|
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
|
+
|
|
444
569
|
## Values in an instruction: `.x` and `${x}`
|
|
445
570
|
|
|
446
571
|
Two spellings put a value in front of the model — pick by what the model
|