@supernovae-st/nika 0.120.2 → 0.120.3
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 +48 -2
- package/dist/bin/nika.js +40 -14
- package/dist/index.cjs +635 -90
- package/dist/index.d.cts +133 -2
- package/dist/index.d.ts +133 -2
- package/dist/index.js +635 -90
- package/docs/http-api.md +24 -0
- package/docs/testing.md +37 -0
- package/openapi.json +340 -2
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -346,8 +346,8 @@ node -p "require('@supernovae-st/nika/package.json').version"
|
|
|
346
346
|
./node_modules/.bin/nika --version
|
|
347
347
|
```
|
|
348
348
|
|
|
349
|
-
This repository's source train is **0.120.
|
|
350
|
-
public engine tag `v0.120.
|
|
349
|
+
This repository's source train is **0.120.3** (`package.json`), lockstep with
|
|
350
|
+
public engine tag `v0.120.3` (`578352a31`). A source train is not a published
|
|
351
351
|
npm version until the release workflow publishes it.
|
|
352
352
|
|
|
353
353
|
This package metadata subpath is exported for CommonJS, ESM build tools and CI
|
|
@@ -383,6 +383,52 @@ No destination means preview only. `nika.yaml` is the project control plane
|
|
|
383
383
|
and asks for French; the hand-written file above is enough if it keeps
|
|
384
384
|
`outputs.greeting` and `model: mock/echo`.
|
|
385
385
|
|
|
386
|
+
## Compile a candidate without running it
|
|
387
|
+
|
|
388
|
+
`compile()` requires an engine that advertises the `compile` capability. The
|
|
389
|
+
released 0.120.3 engine supports native compilation and its Serve advertises
|
|
390
|
+
HTTP compilation (the route landed in engine commit `4334e58b`, first published
|
|
391
|
+
in v0.120.3); a 0.120.2 or older Serve predates that route and is refused
|
|
392
|
+
without fallback. This SDK method first publishes with 0.120.3. This foundation resolves
|
|
393
|
+
exact embedded skeleton names (including `hello`) and edits existing constants;
|
|
394
|
+
unsupported intent remains `incomplete`.
|
|
395
|
+
|
|
396
|
+
```ts
|
|
397
|
+
const candidate = await nika.compile({
|
|
398
|
+
intent: 'classify-and-route',
|
|
399
|
+
answers: { 'const.request': 'An outage affects support customers.' },
|
|
400
|
+
}, { timeoutMs: 15_000 });
|
|
401
|
+
|
|
402
|
+
// The outcome is source and review data. Incomplete/refused are also outcomes.
|
|
403
|
+
console.log(candidate.status, candidate.questions, candidate.diagnostics);
|
|
404
|
+
if (candidate.ready && candidate.candidate !== null) {
|
|
405
|
+
const edited = await nika.compile({
|
|
406
|
+
workflow: candidate.candidate,
|
|
407
|
+
change: { set_constant: { name: 'request', value: 'One customer is affected.' } },
|
|
408
|
+
});
|
|
409
|
+
console.log(edited.candidate);
|
|
410
|
+
}
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
Edits also accept a string such as `Set const.request to "One customer"`.
|
|
414
|
+
Answers and structured values are strict JSON values, preserving numbers,
|
|
415
|
+
booleans, null, strings, arrays and objects. The native adapter uses the engine's
|
|
416
|
+
CLI; the HTTP adapter sends authenticated `POST /v1/compile`. An unavailable
|
|
417
|
+
remote capability raises `NikaCompatibilityError` without local fallback.
|
|
418
|
+
|
|
419
|
+
`candidate` is `.nika` source, distinct from the path accepted by `run()`.
|
|
420
|
+
The caller reviews and materializes it before calling `run(path)`, which performs
|
|
421
|
+
normal admission. `requested_boundary` and the source-only `check_preview` grant
|
|
422
|
+
no execution authority. Compile creates no Run, job, approval or Proof, and the
|
|
423
|
+
SDK writes no persistent candidate file. `signal`/`timeoutMs` stop only the
|
|
424
|
+
compile request. The common outcome has no `exitCode` or `written` field.
|
|
425
|
+
|
|
426
|
+
Compile accepts standard `AbortSignal`s, including `AbortSignal.any` composites.
|
|
427
|
+
It rejects direct signal interface overrides and proxies before starting work.
|
|
428
|
+
Composite sources must retain their standard interfaces: Node may read their
|
|
429
|
+
public fields while inspecting or subscribing to a composite, so the SDK cannot
|
|
430
|
+
validate a modified hidden source graph without invoking those fields.
|
|
431
|
+
|
|
386
432
|
## Verify a local trace
|
|
387
433
|
|
|
388
434
|
Local terminal results carry an engine-issued receipt when tracing is enabled.
|
package/dist/bin/nika.js
CHANGED
|
@@ -150,29 +150,41 @@ function captureEngine(bin, args, options) {
|
|
|
150
150
|
shell: false,
|
|
151
151
|
stdio: ["ignore", "pipe", "pipe"]
|
|
152
152
|
});
|
|
153
|
-
|
|
154
|
-
|
|
153
|
+
const stdout = [];
|
|
154
|
+
const stderr = [];
|
|
155
|
+
const bytes = { stdout: 0, stderr: 0 };
|
|
155
156
|
let overflow = false;
|
|
156
157
|
let spawnError;
|
|
158
|
+
let closed = false;
|
|
159
|
+
let killTimer;
|
|
160
|
+
const stop = () => {
|
|
161
|
+
child.kill("SIGTERM");
|
|
162
|
+
if (options.killGraceMs === void 0) return;
|
|
163
|
+
killTimer ??= setTimeout(() => {
|
|
164
|
+
if (!closed) child.kill("SIGKILL");
|
|
165
|
+
}, options.killGraceMs);
|
|
166
|
+
killTimer.unref();
|
|
167
|
+
};
|
|
157
168
|
const append = (stream, chunk) => {
|
|
158
169
|
if (overflow) return;
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
if (Buffer.byteLength(stdout) > options.bufferBytes || Buffer.byteLength(stderr) > options.bufferBytes) {
|
|
170
|
+
bytes[stream] += chunk.byteLength;
|
|
171
|
+
if (bytes[stream] > options.bufferBytes) {
|
|
162
172
|
overflow = true;
|
|
163
|
-
|
|
173
|
+
stop();
|
|
174
|
+
return;
|
|
164
175
|
}
|
|
176
|
+
(stream === "stdout" ? stdout : stderr).push(chunk);
|
|
165
177
|
};
|
|
166
|
-
child.stdout.setEncoding("utf8");
|
|
167
|
-
child.stderr.setEncoding("utf8");
|
|
168
178
|
child.stdout.on("data", (chunk) => append("stdout", chunk));
|
|
169
179
|
child.stderr.on("data", (chunk) => append("stderr", chunk));
|
|
170
|
-
const abort = () =>
|
|
180
|
+
const abort = () => stop();
|
|
171
181
|
options.signal?.addEventListener("abort", abort, { once: true });
|
|
172
182
|
child.once("error", (cause) => {
|
|
173
183
|
spawnError = cause;
|
|
174
184
|
});
|
|
175
|
-
child.once("close", (code) => {
|
|
185
|
+
child.once("close", (code, exitSignal) => {
|
|
186
|
+
closed = true;
|
|
187
|
+
if (killTimer) clearTimeout(killTimer);
|
|
176
188
|
options.signal?.removeEventListener("abort", abort);
|
|
177
189
|
if (options.signal?.aborted) {
|
|
178
190
|
reject(new NikaTransportError(options.transport, `${options.label} aborted by caller`));
|
|
@@ -188,7 +200,19 @@ function captureEngine(bin, args, options) {
|
|
|
188
200
|
`${options.label} exceeded ${options.bufferBytes} bytes`
|
|
189
201
|
));
|
|
190
202
|
} else {
|
|
191
|
-
|
|
203
|
+
let decoded;
|
|
204
|
+
try {
|
|
205
|
+
decoded = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(Buffer.concat(stdout, bytes.stdout));
|
|
206
|
+
} catch {
|
|
207
|
+
reject(new NikaProtocolError(options.transport, `${options.label} stdout was not valid UTF-8`));
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
resolve({
|
|
211
|
+
exitCode: code ?? 3,
|
|
212
|
+
stdout: decoded,
|
|
213
|
+
stderr: Buffer.concat(stderr, bytes.stderr).toString("utf8"),
|
|
214
|
+
exitSignal
|
|
215
|
+
});
|
|
192
216
|
}
|
|
193
217
|
});
|
|
194
218
|
});
|
|
@@ -252,9 +276,9 @@ function incompatible(transport, message) {
|
|
|
252
276
|
|
|
253
277
|
// src/lib/binary/verify.ts
|
|
254
278
|
var IDENTITY_BUFFER_BYTES = 16 * 1024;
|
|
255
|
-
async function verifyNikaEngine(engine) {
|
|
279
|
+
async function verifyNikaEngine(engine, options = {}) {
|
|
256
280
|
const expectedVersion = engine.packageRoot ? await verifyManagedPayload(engine) : void 0;
|
|
257
|
-
const identity = await probeIdentity(engine.bin);
|
|
281
|
+
const identity = await probeIdentity(engine.bin, options);
|
|
258
282
|
if (expectedVersion !== void 0 && identity.engineVersion !== expectedVersion) {
|
|
259
283
|
throw incompatible2(
|
|
260
284
|
`Packaged engine version ${identity.engineVersion} does not match payload version ${expectedVersion}`
|
|
@@ -293,13 +317,15 @@ async function verifyManagedPayload(engine) {
|
|
|
293
317
|
}
|
|
294
318
|
return manifest.version;
|
|
295
319
|
}
|
|
296
|
-
async function probeIdentity(bin) {
|
|
320
|
+
async function probeIdentity(bin, options) {
|
|
297
321
|
const notAnEngine = `is ${bin} a nika engine? (run "${bin} --sdk-identity" by hand)`;
|
|
298
322
|
const captured = await captureEngine(bin, ["--sdk-identity"], {
|
|
323
|
+
...options,
|
|
299
324
|
bufferBytes: IDENTITY_BUFFER_BYTES,
|
|
300
325
|
transport: "native-process",
|
|
301
326
|
label: "Engine identity probe"
|
|
302
327
|
}).catch((cause) => {
|
|
328
|
+
if (options.signal?.aborted) throw cause;
|
|
303
329
|
throw incompatible2(
|
|
304
330
|
`Engine identity probe of ${bin} failed`,
|
|
305
331
|
cause
|