@theokit/sdk 2.1.0 → 2.2.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - efe183e: M1 Reliable harness — make the agent loop's iteration ceiling real (plan `m1-reliable-harness`).
8
+
9
+ - **M1-1:** the agent loop now calls `BudgetTracker.nextIteration?.()` once per completed turn, and `nextIteration?()` is an optional member of the `BudgetTracker` interface. `createCounterBudgetTracker({ maxIterations: N })` now actually halts the loop after N turns (it was dead — nothing called it). Additive and backward-compatible: trackers that only gate on tokens/USD omit the method.
10
+ - **M1-2 (knob):** `SendOptions.maxIterations` lets a builder raise/lower the loop's default 8-turn cap per `agent.send` call. Validated at the boundary (positive integer; invalid throws `ConfigurationError`). Default of 8 preserved when unset.
11
+ - **M1-2 (truncation signal):** `RunResult.stoppedAtIterationLimit` is `true` when the loop stopped at its iteration ceiling with tool work still pending (silent truncation) vs a clean `done` finish. Lets a caller/continuation driver detect and recover.
12
+
3
13
  ## 2.1.0
4
14
 
5
15
  ### Minor Changes
@@ -2313,6 +2313,11 @@ function makeNotifier() {
2313
2313
  });
2314
2314
  return { promise, resolve: resolve3 };
2315
2315
  }
2316
+ function applyScriptMetrics(base, script) {
2317
+ if (script.usage !== void 0) base.usage = script.usage;
2318
+ if (script.cost !== void 0) base.cost = script.cost;
2319
+ if (script.stoppedAtIterationLimit === true) base.stoppedAtIterationLimit = true;
2320
+ }
2316
2321
  var FixtureRunBase;
2317
2322
  var init_fixture_run_base = __esm({
2318
2323
  "src/internal/runtime/fixtures/fixture-run-base.ts"() {
@@ -2419,8 +2424,7 @@ var init_fixture_run_base = __esm({
2419
2424
  if (status === "error" && this.script.errorDetail !== void 0) {
2420
2425
  base.error = this.script.errorDetail;
2421
2426
  }
2422
- if (this.script.usage !== void 0) base.usage = this.script.usage;
2423
- if (this.script.cost !== void 0) base.cost = this.script.cost;
2427
+ applyScriptMetrics(base, this.script);
2424
2428
  return this.extendRunResult(applyExtraRunFields(base, this.script));
2425
2429
  }
2426
2430
  /** Subclasses override to attach runtime-specific fields (e.g. cloud git info). */
@@ -8008,6 +8012,7 @@ async function runAgentLoop(inputs) {
8008
8012
  const ctx = await initLoopContext(inputs);
8009
8013
  ctxRef = ctx;
8010
8014
  const budget = inputs.budget ?? new IterationBudget({ maxIterations: inputs.maxIterations ?? 8 });
8015
+ let lastTurnDecision;
8011
8016
  while (budget.shouldContinue()) {
8012
8017
  if (inputs.budgetTracker !== void 0) {
8013
8018
  const decision2 = evaluateBudgetGate(inputs.budgetTracker);
@@ -8016,18 +8021,26 @@ async function runAgentLoop(inputs) {
8016
8021
  if (decision2.detail !== void 0) {
8017
8022
  ctx.error = { message: decision2.detail, code: decision2.reason ?? "budget" };
8018
8023
  }
8024
+ if (decision2.reason === "iteration_limit") {
8025
+ ctx.stoppedAtIterationLimit = true;
8026
+ }
8019
8027
  break;
8020
8028
  }
8021
8029
  }
8022
8030
  const usingGrace = budget.remaining <= 0 && !budget.graceCallUsed;
8023
8031
  if (usingGrace) budget.useGraceCall();
8024
8032
  const decision = await runIteration(inputs, ctx);
8033
+ lastTurnDecision = decision;
8025
8034
  if (decision === "done") break;
8026
8035
  if (decision === "error") {
8027
8036
  ctx.finalStatus = "error";
8028
8037
  break;
8029
8038
  }
8030
8039
  budget.consume();
8040
+ inputs.budgetTracker?.nextIteration?.();
8041
+ }
8042
+ if (lastTurnDecision === "continue" && budget.shouldContinue() === false) {
8043
+ ctx.stoppedAtIterationLimit = true;
8031
8044
  }
8032
8045
  if (budget.shouldContinue() === false && ctx.finalStatus === "finished" && ctx.finalText === "") {
8033
8046
  ctx.finalStatus = "error";
@@ -8058,7 +8071,8 @@ async function runAgentLoop(inputs) {
8058
8071
  conversation: ctx.conversation,
8059
8072
  ...usage !== void 0 ? { usage } : {},
8060
8073
  ...cost !== void 0 ? { cost } : {},
8061
- ...ctx.error !== void 0 ? { error: ctx.error } : {}
8074
+ ...ctx.error !== void 0 ? { error: ctx.error } : {},
8075
+ ...ctx.stoppedAtIterationLimit === true ? { stoppedAtIterationLimit: true } : {}
8062
8076
  };
8063
8077
  } finally {
8064
8078
  if (ctxRef !== void 0 && ctxRef.memoryProviderHandle !== void 0 && inputs.memoryProvider !== void 0) {
@@ -11007,6 +11021,13 @@ function resolveRunProvider(options) {
11007
11021
  return { primary, effectiveModelId };
11008
11022
  }
11009
11023
  function buildLoopInputs(options, runId, userText) {
11024
+ const maxIterations = options.sendOptions.maxIterations;
11025
+ if (maxIterations !== void 0 && (!Number.isInteger(maxIterations) || maxIterations < 1)) {
11026
+ throw new ConfigurationError(
11027
+ `SendOptions.maxIterations must be a positive integer, got ${maxIterations}`,
11028
+ { code: "invalid_max_iterations" }
11029
+ );
11030
+ }
11010
11031
  const { primary, effectiveModelId } = resolveRunProvider(options);
11011
11032
  const fallback = options.agentOptions.providers?.fallback;
11012
11033
  const apiKeys = options.agentOptions.providers?.apiKeys;
@@ -11045,6 +11066,9 @@ function buildLoopInputs(options, runId, userText) {
11045
11066
  // D318 — forward SendOptions.signal to the agent loop so streamLlmTurn
11046
11067
  // can attach it to the LLM `fetch({ signal })` call.
11047
11068
  ...options.sendOptions.signal !== void 0 ? { signal: options.sendOptions.signal } : {},
11069
+ // M1-2: per-send iteration ceiling (validated above). The loop reads
11070
+ // inputs.maxIterations (default 8 when unset).
11071
+ ...maxIterations !== void 0 ? { maxIterations } : {},
11048
11072
  // D315-D317 — tool lifecycle hooks (cost tracking + audit + retry/alert)
11049
11073
  ...options.agentOptions.onToolStart !== void 0 ? { onToolStart: options.agentOptions.onToolStart } : {},
11050
11074
  ...options.agentOptions.onToolEnd !== void 0 ? { onToolEnd: options.agentOptions.onToolEnd } : {},
@@ -11104,6 +11128,7 @@ function buildMcpMap(options) {
11104
11128
  var pluginProvidersAnnounced, RealLocalRun;
11105
11129
  var init_real_local_run = __esm({
11106
11130
  "src/internal/runtime/local-agent/real-local-run.ts"() {
11131
+ init_errors();
11107
11132
  init_loop();
11108
11133
  init_fallback_client();
11109
11134
  init_model_identifier();
@@ -11191,6 +11216,7 @@ var init_real_local_run = __esm({
11191
11216
  if (output.result.length > 0) this.script.result = output.result;
11192
11217
  if (output.usage !== void 0) this.script.usage = output.usage;
11193
11218
  if (output.cost !== void 0) this.script.cost = output.cost;
11219
+ if (output.stoppedAtIterationLimit === true) this.script.stoppedAtIterationLimit = true;
11194
11220
  if (output.error !== void 0 && this.script.errorDetail === void 0) {
11195
11221
  this.script.errorDetail = {
11196
11222
  message: output.error.message,