@theokit/sdk 2.0.1 → 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.
Files changed (81) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/a2a/index.cjs +261 -174
  3. package/dist/a2a/index.cjs.map +1 -1
  4. package/dist/a2a/index.js +261 -174
  5. package/dist/a2a/index.js.map +1 -1
  6. package/dist/concurrency.cjs +86 -0
  7. package/dist/concurrency.cjs.map +1 -0
  8. package/dist/concurrency.d.cts +13 -0
  9. package/dist/concurrency.d.ts +13 -0
  10. package/dist/concurrency.js +83 -0
  11. package/dist/concurrency.js.map +1 -0
  12. package/dist/{cron-Bj8-Aq1O.d.ts → cron-Aksw2Hy4.d.ts} +10 -2
  13. package/dist/{cron-DFG9-W17.d.cts → cron-JSPSFczQ.d.cts} +10 -2
  14. package/dist/cron.cjs +244 -172
  15. package/dist/cron.cjs.map +1 -1
  16. package/dist/cron.d.cts +2 -2
  17. package/dist/cron.d.ts +2 -2
  18. package/dist/cron.js +244 -172
  19. package/dist/cron.js.map +1 -1
  20. package/dist/{errors-ChqOmFH1.d.cts → errors-Bcw_Pakm.d.ts} +24 -2
  21. package/dist/{errors-DV9e0rcp.d.ts → errors-Vhg6ZV4o.d.cts} +24 -2
  22. package/dist/errors.cjs +17 -11
  23. package/dist/errors.cjs.map +1 -1
  24. package/dist/errors.d.cts +2 -2
  25. package/dist/errors.d.ts +22 -0
  26. package/dist/errors.js +17 -12
  27. package/dist/errors.js.map +1 -1
  28. package/dist/eval.cjs +244 -172
  29. package/dist/eval.cjs.map +1 -1
  30. package/dist/eval.js +244 -172
  31. package/dist/eval.js.map +1 -1
  32. package/dist/index.cjs +262 -174
  33. package/dist/index.cjs.map +1 -1
  34. package/dist/index.d.cts +163 -121
  35. package/dist/index.d.ts +163 -121
  36. package/dist/index.js +262 -176
  37. package/dist/index.js.map +1 -1
  38. package/dist/internal/agent-loop/loop-types.d.ts +6 -0
  39. package/dist/internal/default-retriable.d.ts +1 -0
  40. package/dist/internal/persistence/index.cjs +75 -0
  41. package/dist/internal/persistence/index.cjs.map +1 -1
  42. package/dist/internal/persistence/index.d.cts +2 -0
  43. package/dist/internal/persistence/index.d.ts +2 -0
  44. package/dist/internal/persistence/index.js +74 -1
  45. package/dist/internal/persistence/index.js.map +1 -1
  46. package/dist/internal/persistence/sqlite-open.d.cts +47 -0
  47. package/dist/internal/persistence/sqlite-open.d.ts +47 -0
  48. package/dist/internal/providers/register-plugin-providers.d.ts +22 -0
  49. package/dist/internal/runtime/budget/budget-tracker.d.ts +8 -0
  50. package/dist/internal/runtime/concurrency/map-with-concurrency.d.ts +28 -0
  51. package/dist/internal/runtime/retry/with-retry.d.ts +40 -0
  52. package/dist/internal/security/index.cjs +1 -0
  53. package/dist/internal/security/index.cjs.map +1 -1
  54. package/dist/internal/security/index.js +1 -0
  55. package/dist/internal/security/index.js.map +1 -1
  56. package/dist/path-safety.cjs +15 -0
  57. package/dist/path-safety.cjs.map +1 -1
  58. package/dist/path-safety.d.cts +1 -1
  59. package/dist/path-safety.d.ts +1 -1
  60. package/dist/path-safety.js +15 -1
  61. package/dist/path-safety.js.map +1 -1
  62. package/dist/retry.cjs +85 -0
  63. package/dist/retry.cjs.map +1 -0
  64. package/dist/retry.d.cts +9 -0
  65. package/dist/retry.d.ts +9 -0
  66. package/dist/retry.js +83 -0
  67. package/dist/retry.js.map +1 -0
  68. package/dist/{run-DrwUpFxZ.d.cts → run-ekGKZlmg.d.cts} +20 -0
  69. package/dist/{run-DrwUpFxZ.d.ts → run-ekGKZlmg.d.ts} +20 -0
  70. package/dist/server/errors-envelope.cjs +14 -12
  71. package/dist/server/errors-envelope.cjs.map +1 -1
  72. package/dist/server/errors-envelope.js +14 -12
  73. package/dist/server/errors-envelope.js.map +1 -1
  74. package/dist/subscription/index.cjs.map +1 -1
  75. package/dist/subscription/index.js.map +1 -1
  76. package/dist/task-store.cjs.map +1 -1
  77. package/dist/task-store.js.map +1 -1
  78. package/dist/types/run.d.ts +20 -0
  79. package/dist/workflow.cjs.map +1 -1
  80. package/dist/workflow.js.map +1 -1
  81. package/package.json +21 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
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
+
13
+ ## 2.1.0
14
+
15
+ ### Minor Changes
16
+
17
+ - 7d53632: Custom LLM providers via the public Plugin protocol (plan `dev-friendly-custom-provider`).
18
+
19
+ - **Added** `defineProvider(profile, opts?)` — canonical factory (mirrors `defineTool`/`definePlugin`, Inviolable Rule 9) that wraps a data-only `ProviderProfile` into a `kind: "model-provider"` `Plugin`. Register any OpenAI-/Anthropic-compatible endpoint (Groq, Together, Fireworks, DeepInfra, a private gateway) with `Agent.create({ model: { id: "myprov/model" }, plugins: [defineProvider(profile)] })`, routed via the `provider/model` id prefix. Exported from `@theokit/sdk`.
20
+ - **Fixed** half-wired `kind: "model-provider"` plugin path: `PluginManager` aggregated provider profiles but nothing registered them, so `getProviderProfile`/`resolveProviderChain` never saw a plugin-contributed provider — a programmatic `model-provider` plugin was silently dropped. The local-agent run now registers plugin-contributed profiles before provider-chain resolution, so custom providers actually route.
21
+ - **Docs** new "Custom providers (`defineProvider`)" section in `docs.md` (field reference + `apiMode` table) and a worked `examples/custom-provider/`.
22
+
23
+ - 872c89e: M0 Foundation — expose already-existing internal primitives as public surfaces (plan `m0-foundation-expose-primitives`), so agent/code-assistant builders reuse battle-tested plumbing instead of re-implementing it.
24
+
25
+ - `isTransientError(err)` — public retryability predicate delegating to `TheokitAgentError.isRetryable` (T1.1).
26
+ - `safeFilenameForId(id, { maxLen })` — total id→filename helper via `@theokit/sdk/path-safety` (passthrough when safe, deterministic sha256 token otherwise); `sanitizeRunId` migrated to it (T2.1).
27
+ - `@theokit/sdk/concurrency` — public `createSemaphore` + new ordered, fail-fast `mapWithConcurrency`; two internal pooling clones deduplicated onto it (T3.1).
28
+ - `@theokit/sdk/retry` — generic `withRetry(fn, options)` (exponential backoff + full jitter, injectable sleep/rng, default `isRetryable = isTransientError`) (T4.1).
29
+ - `openSqliteResilient` (`@theokit/sdk/internal/persistence`, semver-exempt) — shared driver-load + WAL + corruption-recovery; both memory `index-db` copies deduplicated onto it (T5.1).
30
+
3
31
  ## 2.0.1
4
32
 
5
33
  ### Patch Changes
@@ -132,6 +132,24 @@ var init_agent_builder = __esm({
132
132
  }
133
133
  });
134
134
 
135
+ // src/internal/default-retriable.ts
136
+ function defaultRetriableForCode(code) {
137
+ switch (code) {
138
+ case "rate_limit":
139
+ case "timeout":
140
+ case "server_error":
141
+ case "network":
142
+ case "provider_unreachable":
143
+ return true;
144
+ default:
145
+ return false;
146
+ }
147
+ }
148
+ var init_default_retriable = __esm({
149
+ "src/internal/default-retriable.ts"() {
150
+ }
151
+ });
152
+
135
153
  // src/internal/security/redact.ts
136
154
  function readEnvOnce() {
137
155
  const raw = process.env.THEOKIT_REDACT_SECRETS;
@@ -282,7 +300,8 @@ __export(errors_exports, {
282
300
  UnsupportedBudgetOperationError: () => UnsupportedBudgetOperationError,
283
301
  UnsupportedRunOperationError: () => UnsupportedRunOperationError,
284
302
  UnsupportedTaskOperationError: () => UnsupportedTaskOperationError,
285
- coerceToKnownAgentRunErrorCode: () => coerceToKnownAgentRunErrorCode
303
+ coerceToKnownAgentRunErrorCode: () => coerceToKnownAgentRunErrorCode,
304
+ isTransientError: () => isTransientError
286
305
  });
287
306
  function coerceToKnownAgentRunErrorCode(code) {
288
307
  if (code !== void 0 && KNOWN_AGENT_RUN_ERROR_CODES.has(code)) {
@@ -314,21 +333,13 @@ function safeStringify(value) {
314
333
  return String(value);
315
334
  }
316
335
  }
317
- function defaultRetriableForCode(code) {
318
- switch (code) {
319
- case "rate_limit":
320
- case "timeout":
321
- case "server_error":
322
- case "network":
323
- case "provider_unreachable":
324
- return true;
325
- default:
326
- return false;
327
- }
336
+ function isTransientError(err) {
337
+ return err instanceof TheokitAgentError && err.isRetryable === true;
328
338
  }
329
339
  var KNOWN_AGENT_RUN_ERROR_CODES, TheokitAgentError, AuthenticationError, RateLimitError, ConfigurationError, IntegrationNotConnectedError, NetworkError, UnknownAgentError, AgentRunError, UnsupportedRunOperationError, CredentialPoolExhaustedError, MemoryAdapterError, InvalidTaskIdError, TaskNotFoundError, UnsupportedTaskOperationError, BudgetExceededError, AgentDisposedError, UnsupportedBudgetOperationError;
330
340
  var init_errors = __esm({
331
341
  "src/errors.ts"() {
342
+ init_default_retriable();
332
343
  init_redact();
333
344
  KNOWN_AGENT_RUN_ERROR_CODES = /* @__PURE__ */ new Set([
334
345
  "rate_limit",
@@ -1027,6 +1038,19 @@ function sanitizeIdentifier(input, options) {
1027
1038
  }
1028
1039
  return input.toLowerCase();
1029
1040
  }
1041
+ function safeFilenameForId(id, options) {
1042
+ if (id.length === 0) {
1043
+ throw new ConfigurationError("Filename id must be a non-empty string", {
1044
+ code: "invalid_filename_id"
1045
+ });
1046
+ }
1047
+ const maxLen = options?.maxLen;
1048
+ const lower = id.toLowerCase();
1049
+ if (lower.length <= maxLen && IDENTIFIER_PATTERN.test(lower)) {
1050
+ return lower;
1051
+ }
1052
+ return `h-${crypto.createHash("sha256").update(id).digest("hex").slice(0, 16)}`;
1053
+ }
1030
1054
  var PathTraversalError, IDENTIFIER_PATTERN;
1031
1055
  var init_path_guard = __esm({
1032
1056
  "src/internal/security/path-guard.ts"() {
@@ -2289,6 +2313,11 @@ function makeNotifier() {
2289
2313
  });
2290
2314
  return { promise, resolve: resolve3 };
2291
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
+ }
2292
2321
  var FixtureRunBase;
2293
2322
  var init_fixture_run_base = __esm({
2294
2323
  "src/internal/runtime/fixtures/fixture-run-base.ts"() {
@@ -2395,8 +2424,7 @@ var init_fixture_run_base = __esm({
2395
2424
  if (status === "error" && this.script.errorDetail !== void 0) {
2396
2425
  base.error = this.script.errorDetail;
2397
2426
  }
2398
- if (this.script.usage !== void 0) base.usage = this.script.usage;
2399
- if (this.script.cost !== void 0) base.cost = this.script.cost;
2427
+ applyScriptMetrics(base, this.script);
2400
2428
  return this.extendRunResult(applyExtraRunFields(base, this.script));
2401
2429
  }
2402
2430
  /** Subclasses override to attach runtime-specific fields (e.g. cloud git info). */
@@ -4243,10 +4271,7 @@ function sessionsDir(cwd) {
4243
4271
  return path.join(memoryDir(cwd), "sessions");
4244
4272
  }
4245
4273
  function sessionSummaryPath(cwd, runId) {
4246
- return path.join(sessionsDir(cwd), `${sanitizeRunId(runId)}.md`);
4247
- }
4248
- function sanitizeRunId(runId) {
4249
- return runId.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 128);
4274
+ return path.join(sessionsDir(cwd), `${safeFilenameForId(runId, { maxLen: 128 })}.md`);
4250
4275
  }
4251
4276
  function truncate(text) {
4252
4277
  if (text.length <= MAX_TURN_CHARS) return text;
@@ -4282,6 +4307,7 @@ var MAX_TURN_CHARS;
4282
4307
  var init_session_summary_writer = __esm({
4283
4308
  "src/internal/memory/storage/session-summary-writer.ts"() {
4284
4309
  init_atomic_write();
4310
+ init_path_guard();
4285
4311
  init_types();
4286
4312
  init_markdown_store();
4287
4313
  MAX_TURN_CHARS = 2e3;
@@ -7112,6 +7138,75 @@ var init_async_local_storage = __esm({
7112
7138
  }
7113
7139
  });
7114
7140
 
7141
+ // src/internal/runtime/concurrency/async-semaphore.ts
7142
+ function createSemaphore(permits) {
7143
+ if (!Number.isInteger(permits) || permits < 1) {
7144
+ throw new ConfigurationError(
7145
+ `async-semaphore: permits must be a positive integer, got ${permits}`,
7146
+ { code: "invalid_concurrency" }
7147
+ );
7148
+ }
7149
+ let active = 0;
7150
+ const queue = [];
7151
+ function tryGrant() {
7152
+ if (active < permits && queue.length > 0) {
7153
+ const resolve3 = queue.shift();
7154
+ if (resolve3 !== void 0) {
7155
+ active += 1;
7156
+ resolve3();
7157
+ }
7158
+ }
7159
+ }
7160
+ return {
7161
+ inFlight: () => active,
7162
+ pending: () => queue.length + active,
7163
+ async acquire() {
7164
+ await new Promise((resolve3) => {
7165
+ queue.push(resolve3);
7166
+ tryGrant();
7167
+ });
7168
+ let released = false;
7169
+ return () => {
7170
+ if (released) return;
7171
+ released = true;
7172
+ active -= 1;
7173
+ tryGrant();
7174
+ };
7175
+ }
7176
+ };
7177
+ }
7178
+ var init_async_semaphore = __esm({
7179
+ "src/internal/runtime/concurrency/async-semaphore.ts"() {
7180
+ init_errors();
7181
+ }
7182
+ });
7183
+
7184
+ // src/internal/runtime/concurrency/map-with-concurrency.ts
7185
+ async function mapWithConcurrency(items, concurrency, fn, options) {
7186
+ const semaphore = createSemaphore(concurrency);
7187
+ const signal = NEVER_ABORT;
7188
+ return Promise.all(
7189
+ items.map(async (item, index) => {
7190
+ const release = await semaphore.acquire();
7191
+ try {
7192
+ if (signal.aborted) {
7193
+ throw signal.reason instanceof Error ? signal.reason : new Error("mapWithConcurrency: aborted");
7194
+ }
7195
+ return await fn(item, index, signal);
7196
+ } finally {
7197
+ release();
7198
+ }
7199
+ })
7200
+ );
7201
+ }
7202
+ var NEVER_ABORT;
7203
+ var init_map_with_concurrency = __esm({
7204
+ "src/internal/runtime/concurrency/map-with-concurrency.ts"() {
7205
+ init_async_semaphore();
7206
+ NEVER_ABORT = new AbortController().signal;
7207
+ }
7208
+ });
7209
+
7115
7210
  // src/internal/tool-dispatch/repair-middleware.ts
7116
7211
  function repairToolCall(raw, registry) {
7117
7212
  const repairs = [];
@@ -7301,38 +7396,12 @@ var init_tool_executors = __esm({
7301
7396
  // src/internal/agent-loop/tool-dispatch.ts
7302
7397
  async function dispatchTools(inputs, tools, toolCalls, events) {
7303
7398
  const maxConcurrent = inputs.maxConcurrentTools ?? 4;
7304
- return boundedParallel(
7305
- maxConcurrent,
7399
+ return mapWithConcurrency(
7306
7400
  toolCalls,
7401
+ maxConcurrent,
7307
7402
  (call) => dispatchSingleCall(inputs, tools, call, events)
7308
7403
  );
7309
7404
  }
7310
- async function boundedParallel(max, items, fn) {
7311
- let running = 0;
7312
- const queue = [];
7313
- async function acquire() {
7314
- if (running < max) {
7315
- running++;
7316
- return;
7317
- }
7318
- await new Promise((resolve3) => queue.push(resolve3));
7319
- running++;
7320
- }
7321
- function release() {
7322
- running--;
7323
- if (queue.length > 0) queue.shift()();
7324
- }
7325
- return Promise.all(
7326
- items.map(async (item) => {
7327
- await acquire();
7328
- try {
7329
- return await fn(item);
7330
- } finally {
7331
- release();
7332
- }
7333
- })
7334
- );
7335
- }
7336
7405
  async function dispatchSingleCall(inputs, tools, call, events) {
7337
7406
  const { call: workingCall, repairs } = applyRepairAndExtractCall(tools, call);
7338
7407
  const callId = generateCallId();
@@ -7556,6 +7625,7 @@ var init_tool_dispatch = __esm({
7556
7625
  "src/internal/agent-loop/tool-dispatch.ts"() {
7557
7626
  init_ids();
7558
7627
  init_async_local_storage();
7628
+ init_map_with_concurrency();
7559
7629
  init_repair_middleware();
7560
7630
  init_tool_executors();
7561
7631
  }
@@ -7942,6 +8012,7 @@ async function runAgentLoop(inputs) {
7942
8012
  const ctx = await initLoopContext(inputs);
7943
8013
  ctxRef = ctx;
7944
8014
  const budget = inputs.budget ?? new IterationBudget({ maxIterations: inputs.maxIterations ?? 8 });
8015
+ let lastTurnDecision;
7945
8016
  while (budget.shouldContinue()) {
7946
8017
  if (inputs.budgetTracker !== void 0) {
7947
8018
  const decision2 = evaluateBudgetGate(inputs.budgetTracker);
@@ -7950,18 +8021,26 @@ async function runAgentLoop(inputs) {
7950
8021
  if (decision2.detail !== void 0) {
7951
8022
  ctx.error = { message: decision2.detail, code: decision2.reason ?? "budget" };
7952
8023
  }
8024
+ if (decision2.reason === "iteration_limit") {
8025
+ ctx.stoppedAtIterationLimit = true;
8026
+ }
7953
8027
  break;
7954
8028
  }
7955
8029
  }
7956
8030
  const usingGrace = budget.remaining <= 0 && !budget.graceCallUsed;
7957
8031
  if (usingGrace) budget.useGraceCall();
7958
8032
  const decision = await runIteration(inputs, ctx);
8033
+ lastTurnDecision = decision;
7959
8034
  if (decision === "done") break;
7960
8035
  if (decision === "error") {
7961
8036
  ctx.finalStatus = "error";
7962
8037
  break;
7963
8038
  }
7964
8039
  budget.consume();
8040
+ inputs.budgetTracker?.nextIteration?.();
8041
+ }
8042
+ if (lastTurnDecision === "continue" && budget.shouldContinue() === false) {
8043
+ ctx.stoppedAtIterationLimit = true;
7965
8044
  }
7966
8045
  if (budget.shouldContinue() === false && ctx.finalStatus === "finished" && ctx.finalText === "") {
7967
8046
  ctx.finalStatus = "error";
@@ -7992,7 +8071,8 @@ async function runAgentLoop(inputs) {
7992
8071
  conversation: ctx.conversation,
7993
8072
  ...usage !== void 0 ? { usage } : {},
7994
8073
  ...cost !== void 0 ? { cost } : {},
7995
- ...ctx.error !== void 0 ? { error: ctx.error } : {}
8074
+ ...ctx.error !== void 0 ? { error: ctx.error } : {},
8075
+ ...ctx.stoppedAtIterationLimit === true ? { stoppedAtIterationLimit: true } : {}
7996
8076
  };
7997
8077
  } finally {
7998
8078
  if (ctxRef !== void 0 && ctxRef.memoryProviderHandle !== void 0 && inputs.memoryProvider !== void 0) {
@@ -10827,6 +10907,21 @@ var init_client = __esm({
10827
10907
  }
10828
10908
  });
10829
10909
 
10910
+ // src/internal/providers/register-plugin-providers.ts
10911
+ function registerPluginProviderProfiles(entries) {
10912
+ let registered4 = 0;
10913
+ for (const entry of entries) {
10914
+ registerProvider(entry.profile);
10915
+ registered4 += 1;
10916
+ }
10917
+ return registered4;
10918
+ }
10919
+ var init_register_plugin_providers = __esm({
10920
+ "src/internal/providers/register-plugin-providers.ts"() {
10921
+ init_registry();
10922
+ }
10923
+ });
10924
+
10830
10925
  // src/internal/tool-registry/personality-filter.ts
10831
10926
  function applyPersonalityFilter(exposedTools, whitelist, opts) {
10832
10927
  if (whitelist === void 0) return exposedTools;
@@ -10907,12 +11002,33 @@ function createRealLocalRun(options) {
10907
11002
  registerRun(handle);
10908
11003
  return handle;
10909
11004
  }
10910
- function buildLoopInputs(options, runId, userText) {
11005
+ function resolveRunProvider(options) {
10911
11006
  registerBuiltins();
11007
+ const profiles = options.pluginManager?.aggregated.providerProfiles ?? [];
11008
+ const registered4 = registerPluginProviderProfiles(profiles);
11009
+ if (registered4 > 0 && !pluginProvidersAnnounced) {
11010
+ pluginProvidersAnnounced = true;
11011
+ const names = profiles.map((e) => e.profile.name).join(", ");
11012
+ process.stderr.write(
11013
+ `[theokit-sdk] registered ${registered4} plugin provider profile(s): ${names}
11014
+ `
11015
+ );
11016
+ }
10912
11017
  const parsedModel = parseModelId(options.model?.id);
10913
11018
  const inferredProvider = parsedModel.provider !== void 0 && getProviderProfile(parsedModel.provider) !== void 0 ? parsedModel.provider : void 0;
10914
11019
  const primary = options.agentOptions.providers?.routes?.[0]?.provider ?? inferredProvider ?? detectPrimaryProvider();
10915
11020
  const effectiveModelId = inferredProvider !== void 0 ? parsedModel.name : options.model?.id ?? "claude-sonnet-4-6";
11021
+ return { primary, effectiveModelId };
11022
+ }
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
+ }
11031
+ const { primary, effectiveModelId } = resolveRunProvider(options);
10916
11032
  const fallback = options.agentOptions.providers?.fallback;
10917
11033
  const apiKeys = options.agentOptions.providers?.apiKeys;
10918
11034
  const credentialPoolStrategy = options.agentOptions.providers?.credentialPoolStrategy;
@@ -10950,6 +11066,9 @@ function buildLoopInputs(options, runId, userText) {
10950
11066
  // D318 — forward SendOptions.signal to the agent loop so streamLlmTurn
10951
11067
  // can attach it to the LLM `fetch({ signal })` call.
10952
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 } : {},
10953
11072
  // D315-D317 — tool lifecycle hooks (cost tracking + audit + retry/alert)
10954
11073
  ...options.agentOptions.onToolStart !== void 0 ? { onToolStart: options.agentOptions.onToolStart } : {},
10955
11074
  ...options.agentOptions.onToolEnd !== void 0 ? { onToolEnd: options.agentOptions.onToolEnd } : {},
@@ -11006,19 +11125,22 @@ function buildMcpMap(options) {
11006
11125
  }
11007
11126
  return map;
11008
11127
  }
11009
- var RealLocalRun;
11128
+ var pluginProvidersAnnounced, RealLocalRun;
11010
11129
  var init_real_local_run = __esm({
11011
11130
  "src/internal/runtime/local-agent/real-local-run.ts"() {
11131
+ init_errors();
11012
11132
  init_loop();
11013
11133
  init_fallback_client();
11014
11134
  init_model_identifier();
11015
11135
  init_router();
11016
11136
  init_client();
11017
11137
  init_providers();
11138
+ init_register_plugin_providers();
11018
11139
  init_tracer();
11019
11140
  init_personality_filter();
11020
11141
  init_fixture_run_base();
11021
11142
  init_run_registry();
11143
+ pluginProvidersAnnounced = false;
11022
11144
  RealLocalRun = class extends FixtureRunBase {
11023
11145
  buildInputs;
11024
11146
  constructor(options, buildInputs) {
@@ -11094,6 +11216,7 @@ var init_real_local_run = __esm({
11094
11216
  if (output.result.length > 0) this.script.result = output.result;
11095
11217
  if (output.usage !== void 0) this.script.usage = output.usage;
11096
11218
  if (output.cost !== void 0) this.script.cost = output.cost;
11219
+ if (output.stoppedAtIterationLimit === true) this.script.stoppedAtIterationLimit = true;
11097
11220
  if (output.error !== void 0 && this.script.errorDetail === void 0) {
11098
11221
  this.script.errorDetail = {
11099
11222
  message: output.error.message,
@@ -11570,7 +11693,7 @@ async function embedTexts(input) {
11570
11693
  pending
11571
11694
  });
11572
11695
  }
11573
- await runBatches(input, pending, results);
11696
+ await embedInBoundedBatches(input, pending, results);
11574
11697
  return results.map((v) => v ?? new Array(dimension).fill(0));
11575
11698
  }
11576
11699
  function classifyEntry(args) {
@@ -11588,45 +11711,34 @@ function classifyEntry(args) {
11588
11711
  args.stats.cacheMisses += 1;
11589
11712
  args.pending.push({ index: args.index, text: args.text, key });
11590
11713
  }
11591
- async function runBatches(input, pending, results) {
11714
+ async function embedInBoundedBatches(input, pending, results) {
11592
11715
  const batches = [];
11593
11716
  for (let offset = 0; offset < pending.length; offset += MAX_BATCH) {
11594
11717
  batches.push(pending.slice(offset, offset + MAX_BATCH));
11595
11718
  }
11596
- let running = 0;
11597
- const queue = [];
11598
- await Promise.all(batches.map((batch) => processBatch(input, batch, results, acquire, release)));
11599
- async function acquire() {
11600
- if (running >= MAX_CONCURRENT_BATCHES) await new Promise((r) => queue.push(r));
11601
- running++;
11602
- }
11603
- function release() {
11604
- running--;
11605
- if (queue.length > 0) queue.shift()();
11606
- }
11719
+ await mapWithConcurrency(
11720
+ batches,
11721
+ MAX_CONCURRENT_BATCHES,
11722
+ (batch) => processBatch(input, batch, results)
11723
+ );
11607
11724
  }
11608
- async function processBatch(input, batch, results, acquire, release) {
11609
- await acquire();
11610
- try {
11611
- const vectors = await embedBatch({
11612
- apiKey: input.apiKey,
11613
- baseUrl: input.baseUrl,
11614
- embeddingsPath: input.embeddingsPath,
11615
- model: input.model,
11616
- inputs: batch.map((b) => b.text),
11617
- fetchImpl: input.fetchImpl,
11618
- stats: input.stats,
11619
- providerId: input.providerId
11620
- });
11621
- for (let j = 0; j < batch.length; j++) {
11622
- const slot = batch[j];
11623
- const vector = vectors[j];
11624
- if (slot === void 0 || vector === void 0) continue;
11625
- results[slot.index] = vector;
11626
- input.cache.set(slot.key, vector);
11627
- }
11628
- } finally {
11629
- release();
11725
+ async function processBatch(input, batch, results) {
11726
+ const vectors = await embedBatch({
11727
+ apiKey: input.apiKey,
11728
+ baseUrl: input.baseUrl,
11729
+ embeddingsPath: input.embeddingsPath,
11730
+ model: input.model,
11731
+ inputs: batch.map((b) => b.text),
11732
+ fetchImpl: input.fetchImpl,
11733
+ stats: input.stats,
11734
+ providerId: input.providerId
11735
+ });
11736
+ for (let j = 0; j < batch.length; j++) {
11737
+ const slot = batch[j];
11738
+ const vector = vectors[j];
11739
+ if (slot === void 0 || vector === void 0) continue;
11740
+ results[slot.index] = vector;
11741
+ input.cache.set(slot.key, vector);
11630
11742
  }
11631
11743
  }
11632
11744
  async function embedBatch(opts) {
@@ -11700,6 +11812,7 @@ var init_openai_compatible2 = __esm({
11700
11812
  "src/internal/memory/adapters/openai-compatible.ts"() {
11701
11813
  init_errors();
11702
11814
  init_openai_compatible();
11815
+ init_map_with_concurrency();
11703
11816
  init_embedding_cache();
11704
11817
  MAX_BATCH = 100;
11705
11818
  MAX_RETRIES = 2;
@@ -12184,6 +12297,61 @@ var init_sqlite_wal = __esm({
12184
12297
  warnedLabels = /* @__PURE__ */ new Set();
12185
12298
  }
12186
12299
  });
12300
+ async function openSqliteResilient(options) {
12301
+ await promises.mkdir(path.dirname(options.filePath), { recursive: true });
12302
+ try {
12303
+ return await openConcrete(options);
12304
+ } catch (cause) {
12305
+ if (options.recoverCorrupt !== false && isCorruptionError(cause)) {
12306
+ await renameAside(options.filePath, options.label ?? "sqlite");
12307
+ return await openConcrete(options);
12308
+ }
12309
+ throw cause;
12310
+ }
12311
+ }
12312
+ async function openConcrete(options) {
12313
+ const db = await loadDriver(options.filePath);
12314
+ applyWalWithFallback(db, options.label ?? "sqlite");
12315
+ await options.onOpen?.(db);
12316
+ return db;
12317
+ }
12318
+ async function loadDriver(filePath) {
12319
+ try {
12320
+ const mod = await import('better-sqlite3');
12321
+ const Ctor = mod.default ?? mod;
12322
+ if (typeof Ctor !== "function") {
12323
+ throw new Error(`better-sqlite3 export is not a constructor (got ${typeof Ctor})`);
12324
+ }
12325
+ return new Ctor(filePath);
12326
+ } catch (cause) {
12327
+ const message = cause instanceof Error ? cause.message : String(cause);
12328
+ throw new ConfigurationError(
12329
+ `Failed to load SQLite driver. Install \`better-sqlite3\` or run on Node 22.5+ for built-in \`node:sqlite\`. Cause: ${message}`,
12330
+ { code: "sqlite_driver_unavailable", cause }
12331
+ );
12332
+ }
12333
+ }
12334
+ function isCorruptionError(cause) {
12335
+ if (!(cause instanceof Error)) return false;
12336
+ const msg = cause.message.toLowerCase();
12337
+ return msg.includes("malformed") || msg.includes("not a database") || msg.includes("encrypted") || msg.includes("disk image is malformed");
12338
+ }
12339
+ async function renameAside(filePath, label) {
12340
+ const asidePath = `${filePath}.corrupt-${Date.now()}`;
12341
+ await promises.rename(filePath, asidePath).catch(() => void 0);
12342
+ await promises.rename(`${filePath}-wal`, `${asidePath}-wal`).catch(() => void 0);
12343
+ await promises.rename(`${filePath}-shm`, `${asidePath}-shm`).catch(() => void 0);
12344
+ process.stderr.write(
12345
+ `[theokit-sdk] ${label} database corrupt; renamed aside to ${asidePath} and rebuilt schema
12346
+ `
12347
+ );
12348
+ }
12349
+ var init_sqlite_open = __esm({
12350
+ "src/internal/persistence/sqlite-open.ts"() {
12351
+ init_errors();
12352
+ init_sqlite_wal();
12353
+ }
12354
+ });
12187
12355
 
12188
12356
  // src/internal/memory/index-schema.ts
12189
12357
  var SCHEMA_STATEMENTS, PRAGMA_STATEMENTS;
@@ -12231,60 +12399,22 @@ var init_index_schema = __esm({
12231
12399
  }
12232
12400
  });
12233
12401
  async function openMemoryDb(opts) {
12234
- await promises.mkdir(path.dirname(opts.filePath), { recursive: true });
12235
- try {
12236
- return await openConcrete(opts.filePath);
12237
- } catch (cause) {
12238
- if (opts.recoverCorrupt !== false && isCorruptionError(cause)) {
12239
- await renameAside(opts.filePath);
12240
- return await openConcrete(opts.filePath);
12402
+ return openSqliteResilient({
12403
+ filePath: opts.filePath,
12404
+ label: "memory-index",
12405
+ recoverCorrupt: opts.recoverCorrupt,
12406
+ onOpen: (db) => {
12407
+ for (const pragma of PRAGMA_STATEMENTS) db.exec(pragma);
12408
+ for (const stmt of SCHEMA_STATEMENTS) db.exec(stmt);
12241
12409
  }
12242
- throw cause;
12243
- }
12244
- }
12245
- async function openConcrete(filePath) {
12246
- const db = await loadDriver(filePath);
12247
- applyWalWithFallback(db, "memory-index");
12248
- for (const pragma of PRAGMA_STATEMENTS) db.exec(pragma);
12249
- for (const stmt of SCHEMA_STATEMENTS) db.exec(stmt);
12250
- return db;
12251
- }
12252
- async function loadDriver(filePath) {
12253
- try {
12254
- const mod = await import('better-sqlite3');
12255
- const Ctor = mod.default ?? mod;
12256
- const db = new Ctor(filePath);
12257
- return db;
12258
- } catch (cause) {
12259
- const message = cause instanceof Error ? cause.message : String(cause);
12260
- throw new ConfigurationError(
12261
- `Failed to load SQLite driver. Install \`better-sqlite3\` or run on Node 22.5+ for built-in \`node:sqlite\`. Cause: ${message}`,
12262
- { code: "sqlite_driver_unavailable", cause }
12263
- );
12264
- }
12265
- }
12266
- function isCorruptionError(cause) {
12267
- if (!(cause instanceof Error)) return false;
12268
- const msg = cause.message.toLowerCase();
12269
- return msg.includes("malformed") || msg.includes("not a database") || msg.includes("encrypted") || msg.includes("disk image is malformed");
12270
- }
12271
- async function renameAside(filePath) {
12272
- const asidePath = `${filePath}.corrupt-${Date.now()}`;
12273
- await promises.rename(filePath, asidePath).catch(() => void 0);
12274
- await promises.rename(`${filePath}-wal`, `${asidePath}-wal`).catch(() => void 0);
12275
- await promises.rename(`${filePath}-shm`, `${asidePath}-shm`).catch(() => void 0);
12276
- process.stderr.write(
12277
- `[theokit-sdk] memory index corrupt; renamed aside to ${asidePath} and rebuilt schema
12278
- `
12279
- );
12410
+ });
12280
12411
  }
12281
12412
  function defaultIndexPath(cwd) {
12282
12413
  return path.join(cwd, ".theokit", "memory", ".index", "memory.sqlite");
12283
12414
  }
12284
12415
  var init_index_db = __esm({
12285
12416
  "src/internal/memory/index-db.ts"() {
12286
- init_errors();
12287
- init_sqlite_wal();
12417
+ init_sqlite_open();
12288
12418
  init_index_schema();
12289
12419
  }
12290
12420
  });
@@ -14347,49 +14477,6 @@ var init_task = __esm({
14347
14477
  }
14348
14478
  });
14349
14479
 
14350
- // src/internal/runtime/concurrency/async-semaphore.ts
14351
- function createSemaphore(permits) {
14352
- if (!Number.isInteger(permits) || permits < 1) {
14353
- throw new ConfigurationError(
14354
- `async-semaphore: permits must be a positive integer, got ${permits}`,
14355
- { code: "invalid_concurrency" }
14356
- );
14357
- }
14358
- let active = 0;
14359
- const queue = [];
14360
- function tryGrant() {
14361
- if (active < permits && queue.length > 0) {
14362
- const resolve3 = queue.shift();
14363
- if (resolve3 !== void 0) {
14364
- active += 1;
14365
- resolve3();
14366
- }
14367
- }
14368
- }
14369
- return {
14370
- inFlight: () => active,
14371
- pending: () => queue.length + active,
14372
- async acquire() {
14373
- await new Promise((resolve3) => {
14374
- queue.push(resolve3);
14375
- tryGrant();
14376
- });
14377
- let released = false;
14378
- return () => {
14379
- if (released) return;
14380
- released = true;
14381
- active -= 1;
14382
- tryGrant();
14383
- };
14384
- }
14385
- };
14386
- }
14387
- var init_async_semaphore = __esm({
14388
- "src/internal/runtime/concurrency/async-semaphore.ts"() {
14389
- init_errors();
14390
- }
14391
- });
14392
-
14393
14480
  // src/internal/task/ring-buffer.ts
14394
14481
  var RingBuffer;
14395
14482
  var init_ring_buffer = __esm({