nuabase 3.0.1 → 3.1.0

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.
@@ -293,21 +293,27 @@ function renderPrompt(cast, enforcement) {
293
293
  }
294
294
 
295
295
  function refusedCast(reason) {
296
- return { success: false, error: reason, reason, attempts: 0 };
296
+ return { success: false, error: reason, reason, attempts: 0, cancelled: false };
297
297
  }
298
298
  const RETRY_DELAYS_MS = [1e3, 2e3];
299
299
  const MAX_ATTEMPTS = RETRY_DELAYS_MS.length + 1;
300
- async function runCast(engine, request, logger) {
300
+ async function runCast(engine, request, logger, controls = {}) {
301
301
  const validate = validatorFor(request.schema);
302
302
  let plan = planAttempt(request, chooseEnforcement(engine, request.jsonSchema));
303
303
  let lastFailure = { error: "", prompt: plan.prompt };
304
304
  for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
305
- const outcome = await engine.attempt({
306
- prompt: plan.prompt.full,
307
- reusablePrefix: plan.prompt.reusablePrefix,
308
- maxTokens: request.maxTokens,
309
- enforcement: plan.enforcement
310
- });
305
+ if (controls.signal?.aborted) {
306
+ return cancelledCast(attempt - 1, attempt > 1 ? lastFailure.prompt : void 0);
307
+ }
308
+ const outcome = await engine.attempt(
309
+ {
310
+ prompt: plan.prompt.full,
311
+ reusablePrefix: plan.prompt.reusablePrefix,
312
+ maxTokens: request.maxTokens,
313
+ enforcement: plan.enforcement
314
+ },
315
+ controls
316
+ );
311
317
  const verdict = judgeOutcome(outcome, validate);
312
318
  if (verdict.kind === "accept") {
313
319
  return {
@@ -320,7 +326,17 @@ async function runCast(engine, request, logger) {
320
326
  };
321
327
  }
322
328
  if (verdict.kind === "give-up") {
323
- return { success: false, error: verdict.error, reason: verdict.error, attempts: attempt, prompt: plan.prompt };
329
+ return {
330
+ success: false,
331
+ error: verdict.error,
332
+ reason: verdict.error,
333
+ attempts: attempt,
334
+ cancelled: false,
335
+ prompt: plan.prompt
336
+ };
337
+ }
338
+ if (verdict.kind === "cancelled") {
339
+ return cancelledCast(attempt, plan.prompt);
324
340
  }
325
341
  lastFailure = { error: verdict.error, prompt: plan.prompt };
326
342
  if (verdict.kind === "retry-in-prompt") {
@@ -334,7 +350,7 @@ async function runCast(engine, request, logger) {
334
350
  if (attempt < MAX_ATTEMPTS) {
335
351
  const delayMs = RETRY_DELAYS_MS[attempt - 1];
336
352
  logger.warn("LLM call attempt failed; retrying", { attempt, delayMs, error: verdict.error });
337
- await delay(delayMs);
353
+ await delay(delayMs, controls.signal);
338
354
  }
339
355
  }
340
356
  return {
@@ -342,6 +358,7 @@ async function runCast(engine, request, logger) {
342
358
  error: `LLM call failed after ${MAX_ATTEMPTS} attempts. Last error: ${lastFailure.error}`,
343
359
  reason: lastFailure.error,
344
360
  attempts: MAX_ATTEMPTS,
361
+ cancelled: false,
345
362
  prompt: lastFailure.prompt
346
363
  };
347
364
  }
@@ -386,6 +403,8 @@ function verdictForFailure(failure) {
386
403
  return { kind: "retry-in-prompt", error: failure.message };
387
404
  case "fatal":
388
405
  return { kind: "give-up", error: failure.message };
406
+ case "cancelled":
407
+ return { kind: "cancelled" };
389
408
  }
390
409
  }
391
410
  function readAnswer(answer) {
@@ -421,8 +440,20 @@ function readStructuredAnswer(envelope) {
421
440
  function errorMessage(error) {
422
441
  return error instanceof Error ? error.message : String(error);
423
442
  }
424
- function delay(ms) {
425
- return new Promise((resolve) => setTimeout(resolve, ms));
443
+ function cancelledCast(attempts, prompt) {
444
+ const reason = "The call was cancelled.";
445
+ return { success: false, error: reason, reason, attempts, cancelled: true, prompt };
446
+ }
447
+ function delay(ms, signal) {
448
+ return new Promise((resolve) => {
449
+ const done = () => {
450
+ clearTimeout(timer);
451
+ signal?.removeEventListener("abort", done);
452
+ resolve();
453
+ };
454
+ const timer = setTimeout(done, ms);
455
+ signal?.addEventListener("abort", done, { once: true });
456
+ });
426
457
  }
427
458
 
428
459
  const DEFAULT_MAX_TOKENS = 4096;
@@ -445,7 +476,10 @@ class NuaLlmClient {
445
476
  jsonSchema,
446
477
  maxTokens: params.maxTokens ?? DEFAULT_MAX_TOKENS
447
478
  };
448
- return runCast(route.engine, request, this.engines.logger);
479
+ return runCast(route.engine, request, this.engines.logger, {
480
+ onOutput: params.onOutput,
481
+ signal: params.signal
482
+ });
449
483
  }
450
484
  async castArray(params) {
451
485
  const { primaryKey, outputName, schema } = params.output;
@@ -468,7 +502,10 @@ class NuaLlmClient {
468
502
  jsonSchema,
469
503
  maxTokens: params.maxTokens ?? DEFAULT_MAX_TOKENS
470
504
  };
471
- return runCast(route.engine, request, this.engines.logger);
505
+ return runCast(route.engine, request, this.engines.logger, {
506
+ onOutput: params.onOutput,
507
+ signal: params.signal
508
+ });
472
509
  }
473
510
  }
474
511
 
@@ -903,7 +940,8 @@ async function callProvider(call, logger) {
903
940
  const response = await fetch(request.url, {
904
941
  method,
905
942
  headers: request.headers ?? {},
906
- body: toFetchableBody(request.body)
943
+ body: toFetchableBody(request.body),
944
+ signal: call.signal
907
945
  });
908
946
  status = response.status;
909
947
  const reply = await call.parseResponse(response);
@@ -949,7 +987,7 @@ class HttpEngine {
949
987
  if (!providerConfigs[provider].strictSchemaModels?.has(model)) return null;
950
988
  return toStrictSchema(envelopeSchema);
951
989
  }
952
- async attempt(request) {
990
+ async attempt(request, { signal } = {}) {
953
991
  const { provider, model, apiKey } = this.target;
954
992
  const config = providerConfigs[provider];
955
993
  const responseSchema = request.enforcement.kind === "native" ? request.enforcement.schema : void 0;
@@ -970,11 +1008,15 @@ class HttpEngine {
970
1008
  ),
971
1009
  parseResponse: config.parseResponse,
972
1010
  responseLength: (reply) => reply.text.length,
973
- usage: (reply) => reply.usage
1011
+ usage: (reply) => reply.usage,
1012
+ signal
974
1013
  },
975
1014
  this.logger
976
1015
  );
977
1016
  if (call.kind === "failed") {
1017
+ if (signal?.aborted) {
1018
+ return { kind: "failed", failure: { kind: "cancelled", message: "The call was cancelled." } };
1019
+ }
978
1020
  return { kind: "failed", failure: failureForStatus(call.status, call.message, responseSchema !== void 0) };
979
1021
  }
980
1022
  const answer = responseSchema ? readEnvelope(call.reply.text) : { kind: "text", text: call.reply.text };
@@ -1077,7 +1119,9 @@ class Nua {
1077
1119
  const result = await this.client.castValue({
1078
1120
  model: options?.model,
1079
1121
  input: { prompt, data: options?.input ?? null },
1080
- output: { name: outputName, schema: outputSchema }
1122
+ output: { name: outputName, schema: outputSchema },
1123
+ onOutput: options?.onOutput,
1124
+ signal: options?.signal
1081
1125
  });
1082
1126
  return toDirectResult(result, Date.now() - startTime);
1083
1127
  }
@@ -1100,7 +1144,9 @@ class Nua {
1100
1144
  const result = await this.client.castArray({
1101
1145
  model: options.model,
1102
1146
  input: { prompt, data: options.input },
1103
- output
1147
+ output,
1148
+ onOutput: options.onOutput,
1149
+ signal: options.signal
1104
1150
  });
1105
1151
  return toDirectResult(result, Date.now() - startTime);
1106
1152
  }
@@ -1113,8 +1159,8 @@ function enginesFor(config) {
1113
1159
  }
1114
1160
  function toDirectResult(result, latencyMs) {
1115
1161
  if (!result.success) {
1116
- const { error, reason, attempts } = result;
1117
- return { success: false, error, reason, attempts, source: "direct", latencyMs };
1162
+ const { error, reason, attempts, cancelled } = result;
1163
+ return { success: false, error, reason, attempts, cancelled, source: "direct", latencyMs };
1118
1164
  }
1119
1165
  return {
1120
1166
  success: true,