@theokit/sdk 2.3.0 → 2.4.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 (77) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/dist/a2a/index.cjs +103 -48
  3. package/dist/a2a/index.cjs.map +1 -1
  4. package/dist/a2a/index.js +104 -49
  5. package/dist/a2a/index.js.map +1 -1
  6. package/dist/compaction.cjs +78 -0
  7. package/dist/compaction.cjs.map +1 -0
  8. package/dist/compaction.d.cts +76 -0
  9. package/dist/compaction.d.ts +76 -0
  10. package/dist/compaction.js +70 -0
  11. package/dist/compaction.js.map +1 -0
  12. package/dist/{cron-B_H8rn-j.d.cts → cron-B656C3iq.d.cts} +8 -0
  13. package/dist/{cron-DX6HbHxd.d.ts → cron-CM2M9mhB.d.ts} +8 -0
  14. package/dist/cron.cjs +104 -57
  15. package/dist/cron.cjs.map +1 -1
  16. package/dist/cron.d.cts +1 -1
  17. package/dist/cron.d.ts +1 -1
  18. package/dist/cron.js +104 -57
  19. package/dist/cron.js.map +1 -1
  20. package/dist/eval.cjs +104 -57
  21. package/dist/eval.cjs.map +1 -1
  22. package/dist/eval.js +104 -57
  23. package/dist/eval.js.map +1 -1
  24. package/dist/index.cjs +104 -57
  25. package/dist/index.cjs.map +1 -1
  26. package/dist/index.d.cts +2 -2
  27. package/dist/index.d.ts +2 -2
  28. package/dist/index.js +104 -57
  29. package/dist/index.js.map +1 -1
  30. package/dist/internal/agent-loop/loop.d.ts +5 -0
  31. package/dist/internal/llm/model-capabilities.d.ts +40 -0
  32. package/dist/internal/llm/model-identifier.d.ts +9 -1
  33. package/dist/internal/llm/model-option.d.ts +38 -0
  34. package/dist/internal/runtime/compression/compression-attempt.d.ts +24 -0
  35. package/dist/internal/runtime/compression/compression-config.d.ts +33 -0
  36. package/dist/internal/runtime/compression/compression-decision.d.ts +10 -0
  37. package/dist/internal/runtime/compression/compression-helpers.d.ts +18 -0
  38. package/dist/internal/runtime/compression/compression-model-registry.d.ts +41 -0
  39. package/dist/internal/runtime/compression/compression-summarizer.d.ts +29 -0
  40. package/dist/internal/runtime/context/project-instructions.d.ts +66 -0
  41. package/dist/internal/runtime/context/replay-history.d.ts +43 -0
  42. package/dist/internal/runtime/hooks/hooks-frontmatter.d.ts +1 -1
  43. package/dist/internal/runtime/skills/discover-skills.d.ts +68 -0
  44. package/dist/internal/runtime/skills/skills-block.d.ts +18 -0
  45. package/dist/internal/runtime/skills/subagent-tool-scope.d.ts +25 -0
  46. package/dist/messages.cjs +24 -0
  47. package/dist/messages.cjs.map +1 -0
  48. package/dist/messages.d.cts +33 -0
  49. package/dist/messages.d.ts +33 -0
  50. package/dist/messages.js +20 -0
  51. package/dist/messages.js.map +1 -0
  52. package/dist/models.cjs +233 -0
  53. package/dist/models.cjs.map +1 -0
  54. package/dist/models.d.cts +16 -0
  55. package/dist/models.d.ts +16 -0
  56. package/dist/models.js +228 -0
  57. package/dist/models.js.map +1 -0
  58. package/dist/project.cjs +149 -0
  59. package/dist/project.cjs.map +1 -0
  60. package/dist/project.d.cts +14 -0
  61. package/dist/project.d.ts +14 -0
  62. package/dist/project.js +146 -0
  63. package/dist/project.js.map +1 -0
  64. package/dist/skills.cjs +282 -0
  65. package/dist/skills.cjs.map +1 -0
  66. package/dist/skills.d.cts +19 -0
  67. package/dist/skills.d.ts +19 -0
  68. package/dist/skills.js +279 -0
  69. package/dist/skills.js.map +1 -0
  70. package/dist/subagents.cjs +24 -0
  71. package/dist/subagents.cjs.map +1 -0
  72. package/dist/subagents.d.cts +14 -0
  73. package/dist/subagents.d.ts +14 -0
  74. package/dist/subagents.js +21 -0
  75. package/dist/subagents.js.map +1 -0
  76. package/dist/types/agent.d.ts +8 -0
  77. package/package.json +63 -3
@@ -0,0 +1,78 @@
1
+ 'use strict';
2
+
3
+ // src/errors.ts
4
+ var TheokitAgentError = class extends Error {
5
+ name = "TheokitAgentError";
6
+ isRetryable;
7
+ code;
8
+ protoErrorCode;
9
+ metadata;
10
+ constructor(message, options = {}) {
11
+ super(message, options.cause !== void 0 ? { cause: options.cause } : void 0);
12
+ this.isRetryable = options.isRetryable ?? false;
13
+ if (options.code !== void 0) this.code = options.code;
14
+ if (options.protoErrorCode !== void 0) this.protoErrorCode = options.protoErrorCode;
15
+ if (options.metadata !== void 0) this.metadata = options.metadata;
16
+ }
17
+ };
18
+
19
+ // src/internal/runtime/compression/compression-helpers.ts
20
+ function selectCompressionWindow(messages, preserveLast = 6) {
21
+ if (messages.length <= preserveLast) {
22
+ return { toCompress: [], toPreserve: [...messages] };
23
+ }
24
+ return {
25
+ toCompress: messages.slice(0, -preserveLast),
26
+ toPreserve: messages.slice(-preserveLast)
27
+ };
28
+ }
29
+
30
+ // src/compaction.ts
31
+ var CHECKPOINT_MARKER = "[[theokit:checkpoint]] ";
32
+ function isSystemPrompt(message) {
33
+ return message.role === "system" && !message.content.startsWith(CHECKPOINT_MARKER);
34
+ }
35
+ async function compactTranscript(messages, options = {}) {
36
+ const keepRecent = options.keepRecent ?? 6;
37
+ const systemPrompts = messages.filter(isSystemPrompt);
38
+ const rest = messages.filter((m) => !isSystemPrompt(m));
39
+ const { toCompress, toPreserve } = selectCompressionWindow(rest, keepRecent);
40
+ if (toCompress.length === 0) {
41
+ return [...messages];
42
+ }
43
+ if (options.summarize) {
44
+ const summary = await options.summarize(toCompress);
45
+ return [...systemPrompts, summary, ...toPreserve];
46
+ }
47
+ return [...systemPrompts, ...toPreserve];
48
+ }
49
+ function buildCheckpoint(label) {
50
+ return { role: "system", content: CHECKPOINT_MARKER + (label ?? "") };
51
+ }
52
+ function filterFromLatestCheckpoint(messages) {
53
+ for (let i = messages.length - 1; i >= 0; i -= 1) {
54
+ if (messages[i]?.content.startsWith(CHECKPOINT_MARKER)) {
55
+ return messages.slice(i + 1);
56
+ }
57
+ }
58
+ return [...messages];
59
+ }
60
+ function isContextOverflowError(err) {
61
+ return err instanceof TheokitAgentError && (err.code === "context_too_long" || err.metadata?.code === "context_too_long");
62
+ }
63
+ function estimateTokens(text) {
64
+ return Math.ceil(text.length / 4);
65
+ }
66
+ function shouldCompact(input) {
67
+ return input.estimated >= input.contextWindow - input.buffer;
68
+ }
69
+
70
+ exports.CHECKPOINT_MARKER = CHECKPOINT_MARKER;
71
+ exports.buildCheckpoint = buildCheckpoint;
72
+ exports.compactTranscript = compactTranscript;
73
+ exports.estimateTokens = estimateTokens;
74
+ exports.filterFromLatestCheckpoint = filterFromLatestCheckpoint;
75
+ exports.isContextOverflowError = isContextOverflowError;
76
+ exports.shouldCompact = shouldCompact;
77
+ //# sourceMappingURL=compaction.cjs.map
78
+ //# sourceMappingURL=compaction.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors.ts","../src/internal/runtime/compression/compression-helpers.ts","../src/compaction.ts"],"names":[],"mappings":"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","file":"compaction.cjs","sourcesContent":["import { defaultRetriableForCode } from \"./internal/default-retriable.js\";\nimport { redactSecrets } from \"./internal/security/redact.js\";\nimport type { RunOperation } from \"./types/run.js\";\n\n/**\n * Finite, machine-readable error codes for provider-originated errors\n * (ADR D66). Consumers can `switch (err.metadata?.code)` exhaustively\n * — adding a new variant is an explicit decision + test coverage.\n *\n * @public\n */\nexport type ErrorCode =\n | \"rate_limit\"\n | \"auth_failed\"\n | \"invalid_request\"\n | \"timeout\"\n | \"server_error\"\n | \"context_too_long\"\n | \"content_filtered\"\n | \"model_unavailable\"\n | \"network\"\n | \"quota_exceeded\"\n | \"unknown\";\n\n/**\n * Codes used by {@link AgentRunError} (Production-Readiness #3, ADR D311).\n *\n * Superset of {@link ErrorCode} extended with codes that do NOT originate\n * from a provider HTTP response:\n *\n * - `quota_exceeded` — billing limit hit (provider 402 or signalled error)\n * - `tool_runtime_error` — custom tool handler threw inside dispatch\n * - `aborted` — caller's `AbortSignal` fired (Phase 4)\n * - `invalid_model` — model id rejected by provider (400 \"model not found\")\n * - `safety_blocked` — provider safety filter blocked req or resp\n * - `provider_unreachable` — DNS/TCP/timeout/5xx at transport boundary\n *\n * The `& {}` tail keeps the literal-union ergonomics (autocomplete) while\n * accepting any string for forward compatibility with constructor calls\n * that pass arbitrary code values (legacy callers).\n *\n * @public\n */\n/**\n * T1.1 — closed literal union for `AgentRunError.code`. The previous\n * `(string & {})` escape hatch let arbitrary strings slip into the type\n * surface and defeated exhaustive `switch (code)` discrimination. This is\n * the canonical closed form. `AgentRunErrorCode` is re-aliased below for\n * source-level back-compat.\n *\n * Adding a new code: append the literal here AND audit every `switch (err.code)`\n * in callers. Type-checker enforces the audit via the `default: assertNever(code)`\n * convention.\n *\n * @public\n */\nexport type KnownAgentRunErrorCode =\n | ErrorCode\n | \"quota_exceeded\"\n | \"tool_runtime_error\"\n | \"aborted\"\n | \"invalid_model\"\n | \"safety_blocked\"\n | \"provider_unreachable\";\n\n/**\n * Back-compat alias of {@link KnownAgentRunErrorCode}. Pre-T1.1 callers that\n * imported `AgentRunErrorCode` keep working; new code SHOULD prefer\n * `KnownAgentRunErrorCode` to make the closed-union intent explicit.\n *\n * @public\n */\nexport type AgentRunErrorCode = KnownAgentRunErrorCode;\n\n/** Snapshot of every known code at runtime — used by the boundary coercer. */\nconst KNOWN_AGENT_RUN_ERROR_CODES = new Set<string>([\n \"rate_limit\",\n \"auth_failed\",\n \"invalid_request\",\n \"timeout\",\n \"server_error\",\n \"context_too_long\",\n \"content_filtered\",\n \"model_unavailable\",\n \"network\",\n \"unknown\",\n \"quota_exceeded\",\n \"tool_runtime_error\",\n \"aborted\",\n \"invalid_model\",\n \"safety_blocked\",\n \"provider_unreachable\",\n]);\n\n/**\n * T1.1 boundary helper — coerce an arbitrary string (typically arriving from\n * a downstream `RunErrorDetail.code` or a deserialized cloud response) into a\n * `KnownAgentRunErrorCode`. Unknown strings collapse to `\"unknown\"` so the\n * closed type contract holds without forcing every caller to switch.\n *\n * @internal\n */\nexport function coerceToKnownAgentRunErrorCode(code: string | undefined): KnownAgentRunErrorCode {\n if (code !== undefined && KNOWN_AGENT_RUN_ERROR_CODES.has(code)) {\n return code as KnownAgentRunErrorCode;\n }\n return \"unknown\";\n}\n\n/**\n * Structured context for errors that originated from a provider HTTP\n * call (ADR D65). Lets callers retry with the right backoff (`retryAfter`),\n * surface actionable diagnostics (`provider`, `endpoint`), and inspect the\n * raw response body when needed (`raw`, capped at ~2KB by the mapper).\n *\n * @public\n */\nexport interface ErrorMetadata {\n /** Provider canonical name (e.g., `\"anthropic\"`, `\"openai\"`, `\"openrouter\"`, `\"gemini\"`). */\n provider: string;\n /** HTTP endpoint that failed (e.g., `\"/v1/messages\"`, `\"/v1/chat/completions\"`). */\n endpoint: string;\n /** Machine-readable error code (finite enum). */\n code: ErrorCode;\n /** HTTP status code if applicable. */\n statusCode?: number;\n /** Seconds to wait before retry, per provider's `retry-after` header (numeric form only). */\n retryAfter?: number;\n /** Raw response body for debugging (truncated to ~2KB by the mapper). */\n raw?: unknown;\n}\n\n/**\n * Base class for all errors thrown by `@theokit/sdk`.\n *\n * Use `isRetryable` to drive retry/backoff logic. `code` and `protoErrorCode`\n * are populated for server-originated errors when available. `metadata`\n * (ADR D65) carries structured `{ provider, endpoint, code, ... }` when\n * the error originated from a provider HTTP call.\n *\n * @public\n */\nexport class TheokitAgentError extends Error {\n override readonly name: string = \"TheokitAgentError\";\n readonly isRetryable: boolean;\n readonly code?: string;\n readonly protoErrorCode?: string;\n readonly metadata?: ErrorMetadata;\n\n constructor(\n message: string,\n options: {\n isRetryable?: boolean;\n code?: string;\n protoErrorCode?: string;\n cause?: unknown;\n metadata?: ErrorMetadata;\n } = {},\n ) {\n super(message, options.cause !== undefined ? { cause: options.cause } : undefined);\n this.isRetryable = options.isRetryable ?? false;\n if (options.code !== undefined) this.code = options.code;\n if (options.protoErrorCode !== undefined) this.protoErrorCode = options.protoErrorCode;\n if (options.metadata !== undefined) this.metadata = options.metadata;\n }\n}\n\n/**\n * Invalid API key, not logged in, insufficient permissions.\n *\n * @public\n */\nexport class AuthenticationError extends TheokitAgentError {\n override readonly name: string = \"AuthenticationError\";\n\n constructor(\n message: string,\n options: { code?: string; cause?: unknown; metadata?: ErrorMetadata } = {},\n ) {\n super(message, { ...options, isRetryable: false });\n }\n}\n\n/**\n * Too many requests or usage limits exceeded.\n *\n * @public\n */\nexport class RateLimitError extends TheokitAgentError {\n override readonly name: string = \"RateLimitError\";\n\n constructor(\n message: string,\n options: { code?: string; cause?: unknown; metadata?: ErrorMetadata } = {},\n ) {\n super(message, { ...options, isRetryable: true });\n }\n}\n\n/**\n * Invalid model, bad request parameters, malformed options.\n *\n * @public\n */\nexport class ConfigurationError extends TheokitAgentError {\n override readonly name: string = \"ConfigurationError\";\n\n constructor(\n message: string,\n options: { code?: string; cause?: unknown; metadata?: ErrorMetadata } = {},\n ) {\n super(message, { ...options, isRetryable: false });\n }\n}\n\n/**\n * Thrown when creating a cloud agent for a repo whose SCM provider is not\n * connected. Use `helpUrl` to point the user at the right reconnect flow.\n *\n * @public\n */\nexport class IntegrationNotConnectedError extends ConfigurationError {\n override readonly name: string = \"IntegrationNotConnectedError\";\n readonly provider: string;\n readonly helpUrl: string;\n\n constructor(\n message: string,\n options: {\n provider: string;\n helpUrl: string;\n code?: string;\n cause?: unknown;\n metadata?: ErrorMetadata;\n },\n ) {\n super(message, options);\n this.provider = options.provider;\n this.helpUrl = options.helpUrl;\n }\n}\n\n/**\n * Service unavailable, timeout, transport-level failure.\n *\n * @public\n */\nexport class NetworkError extends TheokitAgentError {\n override readonly name: string = \"NetworkError\";\n\n constructor(\n message: string,\n options: { code?: string; cause?: unknown; metadata?: ErrorMetadata } = {},\n ) {\n super(message, { ...options, isRetryable: true });\n }\n}\n\n/**\n * Catch-all for unclassified server or runtime errors.\n *\n * @public\n */\nexport class UnknownAgentError extends TheokitAgentError {\n override readonly name: string = \"UnknownAgentError\";\n\n constructor(\n message: string,\n options: { code?: string; cause?: unknown; metadata?: ErrorMetadata } = {},\n ) {\n super(message, { ...options, isRetryable: false });\n }\n}\n\n/**\n * Thrown by `Agent.prompt` (and helpers that go through `run.wait()`) when\n * the option `{ throwOnError: true }` is set and the run terminates with\n * `status: 'error'`. Carries the structured `RunResult.error` fields so\n * callers can `catch` once and branch on `code` / `provider` instead of\n * unwrapping the run.\n *\n * Extends {@link TheokitAgentError} per ADR D65 — no new hierarchy.\n *\n * @example\n * try {\n * await Agent.prompt(msg, { apiKey, model, throwOnError: true });\n * } catch (err) {\n * if (err instanceof AgentRunError && err.code === 'auth_failed') {\n * // bad key\n * }\n * }\n *\n * @public\n */\nexport class AgentRunError extends TheokitAgentError {\n override readonly name: string = \"AgentRunError\";\n readonly provider?: string;\n readonly raw?: string;\n /** Provider's request id (`x-request-id` / `request-id` header). Useful for support tickets. */\n readonly requestId?: string;\n /** SDK conversation id this error was raised inside. */\n readonly conversationId?: string;\n\n constructor(\n message: string,\n options: {\n code: AgentRunErrorCode;\n provider?: string;\n raw?: string;\n requestId?: string;\n conversationId?: string;\n retriable?: boolean;\n cause?: unknown;\n metadata?: ErrorMetadata;\n },\n ) {\n super(message, {\n code: options.code,\n cause: options.cause,\n metadata: options.metadata,\n // D311: most AgentRunErrors are not retriable (auth, validation, abort).\n // Provider mappers (D314) override per-status — explicit `retriable` wins\n // over the implicit default when supplied.\n isRetryable: options.retriable ?? defaultRetriableForCode(options.code),\n });\n if (options.provider !== undefined) this.provider = options.provider;\n if (options.raw !== undefined) this.raw = options.raw;\n if (options.requestId !== undefined) this.requestId = options.requestId;\n if (options.conversationId !== undefined) this.conversationId = options.conversationId;\n }\n\n /**\n * Production-Readiness #3 (ADR D311): alias for `isRetryable` exposed as\n * `retriable` to match the handoff contract. Future v2 will deprecate\n * `isRetryable` in favor of this.\n */\n get retriable(): boolean {\n return this.isRetryable;\n }\n\n /**\n * D312: provider's `Retry-After` header in **milliseconds**. Mappers store\n * the header value (seconds) in `metadata.retryAfter`; this getter\n * multiplies by 1000 so the result composes with `Date.now()`/`setTimeout`.\n *\n * Returns `undefined` when no hint was provided. `0` is a legitimate value\n * — use `=== undefined` check rather than truthy check.\n */\n get retryAfterMs(): number | undefined {\n if (this.metadata?.retryAfter === undefined) return undefined;\n return this.metadata.retryAfter * 1000;\n }\n\n /**\n * D313 + T1.5: alias for `metadata.raw`. Provider response body for\n * debugging. T1.5 wraps the value in `redactSecrets` at the getter\n * boundary so secret-shaped substrings (`sk-...`, Bearer JWTs, etc.) are\n * stripped before reaching the caller. Available but NEVER serialized\n * into `.message` (anti-leak invariant).\n */\n get providerError(): unknown {\n const raw = this.metadata?.raw;\n if (raw === undefined) return undefined;\n if (typeof raw === \"string\") return redactSecrets(raw);\n // Non-string raw (object/buffer) — stringify then redact.\n try {\n return redactSecrets(JSON.stringify(raw));\n } catch {\n return redactSecrets(String(raw));\n }\n }\n\n /**\n * T1.5 — sanitized JSON form. `metadata.raw` is OMITTED by default; opt\n * in via `THEOKIT_DEBUG_RAW_ERRORS=1` to surface the (redacted) raw\n * payload for diagnostics. Every other field stays accessible.\n *\n * The single env-var gate is read each call so operators can toggle at\n * runtime without restarting the process.\n */\n toJSON(): Record<string, unknown> {\n const json: Record<string, unknown> = {\n name: this.name,\n message: this.message,\n isRetryable: this.isRetryable,\n };\n addOptionalFields(json, this);\n const safeMeta = sanitizeMetadata(this.metadata);\n if (safeMeta !== undefined) json.metadata = safeMeta;\n return json;\n }\n}\n\nfunction addOptionalFields(json: Record<string, unknown>, err: AgentRunError): void {\n if (err.code !== undefined) json.code = err.code;\n if (err.provider !== undefined) json.provider = err.provider;\n if (err.requestId !== undefined) json.requestId = err.requestId;\n if (err.conversationId !== undefined) json.conversationId = err.conversationId;\n if (err.raw !== undefined) json.raw = redactSecrets(err.raw);\n}\n\nfunction sanitizeMetadata(meta: ErrorMetadata | undefined): ErrorMetadata | undefined {\n if (meta === undefined) return undefined;\n const { raw, ...rest } = meta;\n const debugRaw = process.env.THEOKIT_DEBUG_RAW_ERRORS === \"1\";\n if (debugRaw && raw !== undefined) {\n const redactedRaw =\n typeof raw === \"string\" ? redactSecrets(raw) : redactSecrets(safeStringify(raw));\n return { ...rest, raw: redactedRaw } as ErrorMetadata;\n }\n return rest as ErrorMetadata;\n}\n\nfunction safeStringify(value: unknown): string {\n try {\n return JSON.stringify(value);\n } catch {\n return String(value);\n }\n}\n\n/**\n * Is this error transient (worth retrying)?\n *\n * Returns the SDK's own retryability verdict: every {@link TheokitAgentError}\n * subclass computes `isRetryable` at construction (rate-limit / network /\n * credential-pool-exhausted are retryable; auth / configuration / unsupported\n * are not), so this predicate is a single source of truth rather than a\n * re-derivation. Non-SDK errors return `false` conservatively — wrap a foreign\n * error in the appropriate SDK error first if you want it considered transient.\n * It never inspects `err.message`.\n *\n * @example\n * try {\n * await agent.send(message, { throwOnError: true });\n * } catch (err) {\n * if (isTransientError(err)) return retryWithBackoff();\n * throw err;\n * }\n *\n * @public\n */\nexport function isTransientError(err: unknown): boolean {\n return err instanceof TheokitAgentError && err.isRetryable === true;\n}\n\n/**\n * Thrown when a {@link Run} or agent operation is not available on the current\n * runtime. Check first with `run.supports(operation)`.\n *\n * Extends {@link TheokitAgentError} (so error-catching code that branches on\n * `instanceof TheokitAgentError` continues to work) but is never retryable —\n * an unsupported operation will not become supported on retry.\n *\n * @public\n */\nexport class UnsupportedRunOperationError extends TheokitAgentError {\n override readonly name: string = \"UnsupportedRunOperationError\";\n readonly operation: RunOperation;\n\n constructor(\n message: string,\n operation: RunOperation,\n options: { code?: string; cause?: unknown } = {},\n ) {\n super(message, {\n ...options,\n isRetryable: false,\n code: options.code ?? \"unsupported_run_operation\",\n });\n this.operation = operation;\n }\n}\n\n/**\n * Thrown when every credential in a per-provider pool is in cooldown\n * and no healthy key is available (ADR D133). The caller's\n * {@link import(\"./internal/llm/fallback-client.js\").FallbackLlmClient}\n * catches this and tries the next provider in the fallback chain.\n *\n * `metadata.nextRetryAt` (epoch ms) tells callers when the soonest\n * pool entry resumes — useful for manual retry scheduling.\n *\n * @public\n */\nexport class CredentialPoolExhaustedError extends TheokitAgentError {\n override readonly name: string = \"CredentialPoolExhaustedError\";\n readonly provider: string;\n readonly nextRetryAt: number | undefined;\n\n constructor(\n message: string,\n options: {\n provider: string;\n nextRetryAt?: number;\n code?: string;\n cause?: unknown;\n metadata?: ErrorMetadata;\n },\n ) {\n super(message, {\n ...options,\n isRetryable: true,\n code: options.code ?? \"credential_pool_exhausted\",\n });\n this.provider = options.provider;\n this.nextRetryAt = options.nextRetryAt;\n }\n}\n\n/**\n * Finite error codes specific to memory adapter operations (ADR D141).\n *\n * @public\n */\nexport type MemoryAdapterErrorCode =\n | \"auth_failed\"\n | \"rate_limited\"\n | \"not_found\"\n | \"network\"\n | \"invalid_input\"\n | \"unknown\";\n\n/**\n * Error raised by `@theokit-memory-*` adapters. Carries `adapterId`\n * so callers can branch on which provider failed (ADR D141).\n *\n * @public\n */\nexport class MemoryAdapterError extends TheokitAgentError {\n override readonly name: string = \"MemoryAdapterError\";\n readonly adapterId: string;\n\n constructor(\n message: string,\n options: {\n adapterId: string;\n code: MemoryAdapterErrorCode;\n cause?: unknown;\n metadata?: ErrorMetadata;\n },\n ) {\n super(message, {\n isRetryable: options.code === \"rate_limited\" || options.code === \"network\",\n code: options.code,\n ...(options.cause !== undefined ? { cause: options.cause } : {}),\n ...(options.metadata !== undefined ? { metadata: options.metadata } : {}),\n });\n this.adapterId = options.adapterId;\n }\n}\n\n/**\n * Thrown when a user-supplied task ID violates the grammar\n * `^[a-z0-9][a-z0-9_-]*$` (D368) OR starts with a reserved adapter\n * prefix (`wf-` / `b-` / `cron-`, EC-5).\n *\n * @public\n */\nexport class InvalidTaskIdError extends TheokitAgentError {\n override readonly name: string = \"InvalidTaskIdError\";\n readonly taskId: string;\n\n constructor(message: string, taskId: string, options: { cause?: unknown } = {}) {\n super(message, {\n ...options,\n isRetryable: false,\n code: \"invalid_task_id\",\n });\n this.taskId = taskId;\n }\n}\n\n/**\n * Thrown when `Task.subscribe(id)` is called for a task that has been\n * evicted, never submitted, or evicted after retention (D373).\n *\n * @public\n */\nexport class TaskNotFoundError extends TheokitAgentError {\n override readonly name: string = \"TaskNotFoundError\";\n readonly taskId: string;\n\n constructor(taskId: string, options: { cause?: unknown } = {}) {\n super(`Task not found: ${taskId}`, {\n ...options,\n isRetryable: false,\n code: \"task_not_found\",\n });\n this.taskId = taskId;\n }\n}\n\n/**\n * Thrown when `CloudAgent` is asked to wrap a task (D370). Cloud\n * task observability is deferred until Theo PaaS GA.\n *\n * @public\n */\nexport class UnsupportedTaskOperationError extends TheokitAgentError {\n override readonly name: string = \"UnsupportedTaskOperationError\";\n readonly operation: string;\n\n constructor(operation: string, options: { cause?: unknown } = {}) {\n super(\n `Task operation \"${operation}\" is not supported on CloudAgent (pre-release; see ADR D370)`,\n {\n ...options,\n isRetryable: false,\n code: \"task_op_unsupported\",\n },\n );\n this.operation = operation;\n }\n}\n\n/**\n * Thrown by `Budget` enforcement (ADR D386) when a `mode: \"block\"`\n * budget would be exceeded by the upcoming LLM call. Caller pega\n * tipado para retry-after-window-reset or surface to the user.\n *\n * @public\n */\nexport class BudgetExceededError extends TheokitAgentError {\n override readonly name: string = \"BudgetExceededError\";\n readonly budgetName: string;\n readonly window: import(\"./types/budget.js\").BudgetWindow;\n readonly spentUsd: number;\n readonly limitUsd: number;\n readonly mode: import(\"./types/budget.js\").BudgetMode;\n\n constructor(args: {\n budgetName: string;\n window: import(\"./types/budget.js\").BudgetWindow;\n spentUsd: number;\n limitUsd: number;\n mode: import(\"./types/budget.js\").BudgetMode;\n cause?: unknown;\n }) {\n super(\n `Budget \"${args.budgetName}\" exceeded for window ${args.window}: spent $${args.spentUsd.toFixed(4)} > limit $${args.limitUsd.toFixed(4)}`,\n {\n ...(args.cause !== undefined ? { cause: args.cause } : {}),\n isRetryable: false,\n code: \"budget_exceeded\",\n },\n );\n this.budgetName = args.budgetName;\n this.window = args.window;\n this.spentUsd = args.spentUsd;\n this.limitUsd = args.limitUsd;\n this.mode = args.mode;\n }\n}\n\n/**\n * Thrown when `CloudAgent.send({ budget })` is invoked (D388). Cloud\n * budget surface waits for Theo PaaS GA.\n *\n * @public\n */\n/**\n * T1.6 — Thrown when a consumer calls `agent.send()` or any method\n * on an agent that has already been `dispose()`d. Pre-T1.6 this was\n * a generic `new Error(\"Agent has been disposed\")` — consumers\n * couldn't catch it without string-matching the message.\n *\n * @public\n */\nexport class AgentDisposedError extends TheokitAgentError {\n override readonly name: string = \"AgentDisposedError\";\n readonly agentId: string;\n\n constructor(agentId: string) {\n super(`Agent \"${agentId}\" has been disposed. Create a new agent or use Agent.resume().`, {\n isRetryable: false,\n code: \"agent_disposed\",\n });\n this.agentId = agentId;\n }\n}\n\nexport class UnsupportedBudgetOperationError extends TheokitAgentError {\n override readonly name: string = \"UnsupportedBudgetOperationError\";\n readonly operation: string;\n\n constructor(operation: string, options: { cause?: unknown } = {}) {\n super(\n `Budget operation \"${operation}\" is not supported on CloudAgent (pre-release; see ADR D388)`,\n {\n ...options,\n isRetryable: false,\n code: \"budget_op_unsupported\",\n },\n );\n this.operation = operation;\n }\n}\n","/**\n * Compression helpers (T2.3, ADR D92).\n *\n * Scaffold for future compression LLM integration:\n * - `selectCompressionWindow` — splits messages into compress/preserve halves\n * - `assertCompressionReduced` — 10% reduction floor to detect \"compression placebo\"\n *\n * The compression LLM call itself is out of scope for this plan (requires\n * an auxiliary-model ADR). These helpers are used by `Agent.send` when a\n * future iteration adds compression.\n *\n * @internal\n */\n\nexport interface CompressionWindow<M> {\n toCompress: M[];\n toPreserve: M[];\n}\n\n/**\n * Split `messages` into the half to compress (older) and the half to\n * preserve verbatim (recent). When `messages.length <= preserveLast`,\n * everything is preserved.\n *\n * @internal\n */\nexport function selectCompressionWindow<M>(\n messages: readonly M[],\n preserveLast = 6,\n): CompressionWindow<M> {\n if (messages.length <= preserveLast) {\n return { toCompress: [], toPreserve: [...messages] };\n }\n return {\n toCompress: messages.slice(0, -preserveLast),\n toPreserve: messages.slice(-preserveLast),\n };\n}\n\nexport interface CompressionCheck {\n reduced: boolean;\n reductionPct: number;\n reason?: string;\n}\n\n/**\n * Check that compression actually reduced token count by at least `minPct`\n * (default 10%). Returns `{ reduced: false }` for spirals-in-formation\n * (compression LLM outputs that grow or barely shrink).\n *\n * @internal\n */\nexport function assertCompressionReduced(\n before: number,\n after: number,\n minPct = 10,\n): CompressionCheck {\n if (before <= 0) {\n return { reduced: false, reductionPct: 0, reason: \"before count was zero\" };\n }\n const reductionPct = ((before - after) / before) * 100;\n if (reductionPct >= minPct) {\n return { reduced: true, reductionPct };\n }\n return {\n reduced: false,\n reductionPct,\n reason: `compression reduced ${reductionPct.toFixed(1)}% (< ${minPct}% min). Spiral likely.`,\n };\n}\n","/**\n * Public compaction / context-management helpers (M2-1).\n *\n * Promotes the SDK's compaction capability to a public surface so consumers can\n * compact a transcript (keep-recent + optional summarize), mark/filter\n * conversation checkpoints, and detect context-overflow — without reaching into\n * `internal/`. `compactTranscript` REUSES the internal `selectCompressionWindow`\n * (no second algorithm); summarization is delegated to a caller-supplied callback\n * (which can wire the internal `compressConversationWindow`).\n *\n * Public from the `@theokit/sdk/compaction` sub-path. See `docs.md → Compaction`.\n */\n\nimport { TheokitAgentError } from \"./errors.js\";\nimport { selectCompressionWindow } from \"./internal/runtime/compression/compression-helpers.js\";\nimport type { CompressibleMessage } from \"./internal/runtime/compression/compression-summarizer.js\";\n\nexport type { CompressibleMessage };\n\n/**\n * Sentinel prefix marking a conversation checkpoint turn. A visible, structured,\n * prose-unlikely token (no invisible/control bytes — safe to persist and to read\n * in source). Only {@link buildCheckpoint} should produce content beginning with it.\n */\nexport const CHECKPOINT_MARKER = \"[[theokit:checkpoint]] \";\n\n/** True for a real system prompt — a `system` turn that is NOT a checkpoint marker. */\nfunction isSystemPrompt(message: CompressibleMessage): boolean {\n return message.role === \"system\" && !message.content.startsWith(CHECKPOINT_MARKER);\n}\n\n/** Options for {@link compactTranscript}. */\nexport interface CompactTranscriptOptions {\n /** Trailing turns preserved verbatim (default 6, matching the internal window). */\n keepRecent?: number;\n /** Summarize the older window into one turn; if omitted, the older window is dropped. */\n summarize?: (older: CompressibleMessage[]) => Promise<CompressibleMessage>;\n}\n\n/**\n * Compact a transcript: keep the last `keepRecent` turns verbatim, always preserve\n * leading system PROMPTS, and either summarize (via `summarize`) or drop the older\n * window. Checkpoint markers are NOT system prompts — they flow through the\n * keep-recent window as ordinary turns (a marker in the older window is\n * summarized/dropped, one in the recent window is kept). Reuses the internal\n * `selectCompressionWindow`. Never mutates the input.\n */\nexport async function compactTranscript(\n messages: CompressibleMessage[],\n options: CompactTranscriptOptions = {},\n): Promise<CompressibleMessage[]> {\n const keepRecent = options.keepRecent ?? 6;\n const systemPrompts = messages.filter(isSystemPrompt);\n const rest = messages.filter((m) => !isSystemPrompt(m));\n const { toCompress, toPreserve } = selectCompressionWindow(rest, keepRecent);\n if (toCompress.length === 0) {\n return [...messages];\n }\n if (options.summarize) {\n const summary = await options.summarize(toCompress);\n return [...systemPrompts, summary, ...toPreserve];\n }\n return [...systemPrompts, ...toPreserve];\n}\n\n/** Build a checkpoint marker turn (a `system` turn whose content starts with {@link CHECKPOINT_MARKER}). */\nexport function buildCheckpoint(label?: string): CompressibleMessage {\n return { role: \"system\", content: CHECKPOINT_MARKER + (label ?? \"\") };\n}\n\n/**\n * Return the turns AFTER the most recent checkpoint marker (all turns if none).\n * Scans backward for the latest marker. Never mutates the input.\n */\nexport function filterFromLatestCheckpoint(messages: CompressibleMessage[]): CompressibleMessage[] {\n for (let i = messages.length - 1; i >= 0; i -= 1) {\n if (messages[i]?.content.startsWith(CHECKPOINT_MARKER)) {\n return messages.slice(i + 1);\n }\n }\n return [...messages];\n}\n\n/**\n * True iff `err` is a {@link TheokitAgentError} (or subclass) reporting a\n * context-window-exceeded condition (the typed `context_too_long` code). Reads\n * both `code` (set by provider mappers) and `metadata.code` (the preferred field)\n * — never a brittle message regex.\n */\nexport function isContextOverflowError(err: unknown): boolean {\n return (\n err instanceof TheokitAgentError &&\n (err.code === \"context_too_long\" || err.metadata?.code === \"context_too_long\")\n );\n}\n\n/** Input to {@link shouldCompact}: an estimate, the model's window, and reserved headroom. */\nexport interface ShouldCompactInput {\n /** Estimated token count of the next request (e.g. from {@link estimateTokens}). */\n readonly estimated: number;\n /** The model's total context window, in tokens. */\n readonly contextWindow: number;\n /** Tokens to reserve as headroom (output + safety margin). */\n readonly buffer: number;\n}\n\n/**\n * Tokenizer-free token estimate via the conventional ~4-chars-per-token\n * heuristic: `ceil(text.length / 4)`. `\"\"` → 0; any non-empty text → ≥ 1.\n * A cheap PRE-CALL gate for {@link shouldCompact} — NOT exact tokenization\n * (a consumer needing exactness supplies their own tokenizer). Uses UTF-16\n * `.length` (code units), so multibyte text is approximate.\n */\nexport function estimateTokens(text: string): number {\n return Math.ceil(text.length / 4);\n}\n\n/**\n * Decide BEFORE sending whether to compact: `true` when the `estimated` token\n * count leaves less than `buffer` headroom in the `contextWindow`\n * (`estimated >= contextWindow - buffer`). A `buffer >= contextWindow`\n * (non-positive threshold) always returns `true`. Pure — the caller supplies\n * the window (e.g. from `resolveModelCapabilities`), keeping this decoupled\n * from the per-model catalog.\n */\nexport function shouldCompact(input: ShouldCompactInput): boolean {\n return input.estimated >= input.contextWindow - input.buffer;\n}\n"]}
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Public compaction / context-management helpers (M2-1).
3
+ *
4
+ * Promotes the SDK's compaction capability to a public surface so consumers can
5
+ * compact a transcript (keep-recent + optional summarize), mark/filter
6
+ * conversation checkpoints, and detect context-overflow — without reaching into
7
+ * `internal/`. `compactTranscript` REUSES the internal `selectCompressionWindow`
8
+ * (no second algorithm); summarization is delegated to a caller-supplied callback
9
+ * (which can wire the internal `compressConversationWindow`).
10
+ *
11
+ * Public from the `@theokit/sdk/compaction` sub-path. See `docs.md → Compaction`.
12
+ */
13
+ import type { CompressibleMessage } from "./internal/runtime/compression/compression-summarizer.js";
14
+ export type { CompressibleMessage };
15
+ /**
16
+ * Sentinel prefix marking a conversation checkpoint turn. A visible, structured,
17
+ * prose-unlikely token (no invisible/control bytes — safe to persist and to read
18
+ * in source). Only {@link buildCheckpoint} should produce content beginning with it.
19
+ */
20
+ export declare const CHECKPOINT_MARKER = "[[theokit:checkpoint]] ";
21
+ /** Options for {@link compactTranscript}. */
22
+ export interface CompactTranscriptOptions {
23
+ /** Trailing turns preserved verbatim (default 6, matching the internal window). */
24
+ keepRecent?: number;
25
+ /** Summarize the older window into one turn; if omitted, the older window is dropped. */
26
+ summarize?: (older: CompressibleMessage[]) => Promise<CompressibleMessage>;
27
+ }
28
+ /**
29
+ * Compact a transcript: keep the last `keepRecent` turns verbatim, always preserve
30
+ * leading system PROMPTS, and either summarize (via `summarize`) or drop the older
31
+ * window. Checkpoint markers are NOT system prompts — they flow through the
32
+ * keep-recent window as ordinary turns (a marker in the older window is
33
+ * summarized/dropped, one in the recent window is kept). Reuses the internal
34
+ * `selectCompressionWindow`. Never mutates the input.
35
+ */
36
+ export declare function compactTranscript(messages: CompressibleMessage[], options?: CompactTranscriptOptions): Promise<CompressibleMessage[]>;
37
+ /** Build a checkpoint marker turn (a `system` turn whose content starts with {@link CHECKPOINT_MARKER}). */
38
+ export declare function buildCheckpoint(label?: string): CompressibleMessage;
39
+ /**
40
+ * Return the turns AFTER the most recent checkpoint marker (all turns if none).
41
+ * Scans backward for the latest marker. Never mutates the input.
42
+ */
43
+ export declare function filterFromLatestCheckpoint(messages: CompressibleMessage[]): CompressibleMessage[];
44
+ /**
45
+ * True iff `err` is a {@link TheokitAgentError} (or subclass) reporting a
46
+ * context-window-exceeded condition (the typed `context_too_long` code). Reads
47
+ * both `code` (set by provider mappers) and `metadata.code` (the preferred field)
48
+ * — never a brittle message regex.
49
+ */
50
+ export declare function isContextOverflowError(err: unknown): boolean;
51
+ /** Input to {@link shouldCompact}: an estimate, the model's window, and reserved headroom. */
52
+ export interface ShouldCompactInput {
53
+ /** Estimated token count of the next request (e.g. from {@link estimateTokens}). */
54
+ readonly estimated: number;
55
+ /** The model's total context window, in tokens. */
56
+ readonly contextWindow: number;
57
+ /** Tokens to reserve as headroom (output + safety margin). */
58
+ readonly buffer: number;
59
+ }
60
+ /**
61
+ * Tokenizer-free token estimate via the conventional ~4-chars-per-token
62
+ * heuristic: `ceil(text.length / 4)`. `""` → 0; any non-empty text → ≥ 1.
63
+ * A cheap PRE-CALL gate for {@link shouldCompact} — NOT exact tokenization
64
+ * (a consumer needing exactness supplies their own tokenizer). Uses UTF-16
65
+ * `.length` (code units), so multibyte text is approximate.
66
+ */
67
+ export declare function estimateTokens(text: string): number;
68
+ /**
69
+ * Decide BEFORE sending whether to compact: `true` when the `estimated` token
70
+ * count leaves less than `buffer` headroom in the `contextWindow`
71
+ * (`estimated >= contextWindow - buffer`). A `buffer >= contextWindow`
72
+ * (non-positive threshold) always returns `true`. Pure — the caller supplies
73
+ * the window (e.g. from `resolveModelCapabilities`), keeping this decoupled
74
+ * from the per-model catalog.
75
+ */
76
+ export declare function shouldCompact(input: ShouldCompactInput): boolean;
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Public compaction / context-management helpers (M2-1).
3
+ *
4
+ * Promotes the SDK's compaction capability to a public surface so consumers can
5
+ * compact a transcript (keep-recent + optional summarize), mark/filter
6
+ * conversation checkpoints, and detect context-overflow — without reaching into
7
+ * `internal/`. `compactTranscript` REUSES the internal `selectCompressionWindow`
8
+ * (no second algorithm); summarization is delegated to a caller-supplied callback
9
+ * (which can wire the internal `compressConversationWindow`).
10
+ *
11
+ * Public from the `@theokit/sdk/compaction` sub-path. See `docs.md → Compaction`.
12
+ */
13
+ import type { CompressibleMessage } from "./internal/runtime/compression/compression-summarizer.js";
14
+ export type { CompressibleMessage };
15
+ /**
16
+ * Sentinel prefix marking a conversation checkpoint turn. A visible, structured,
17
+ * prose-unlikely token (no invisible/control bytes — safe to persist and to read
18
+ * in source). Only {@link buildCheckpoint} should produce content beginning with it.
19
+ */
20
+ export declare const CHECKPOINT_MARKER = "[[theokit:checkpoint]] ";
21
+ /** Options for {@link compactTranscript}. */
22
+ export interface CompactTranscriptOptions {
23
+ /** Trailing turns preserved verbatim (default 6, matching the internal window). */
24
+ keepRecent?: number;
25
+ /** Summarize the older window into one turn; if omitted, the older window is dropped. */
26
+ summarize?: (older: CompressibleMessage[]) => Promise<CompressibleMessage>;
27
+ }
28
+ /**
29
+ * Compact a transcript: keep the last `keepRecent` turns verbatim, always preserve
30
+ * leading system PROMPTS, and either summarize (via `summarize`) or drop the older
31
+ * window. Checkpoint markers are NOT system prompts — they flow through the
32
+ * keep-recent window as ordinary turns (a marker in the older window is
33
+ * summarized/dropped, one in the recent window is kept). Reuses the internal
34
+ * `selectCompressionWindow`. Never mutates the input.
35
+ */
36
+ export declare function compactTranscript(messages: CompressibleMessage[], options?: CompactTranscriptOptions): Promise<CompressibleMessage[]>;
37
+ /** Build a checkpoint marker turn (a `system` turn whose content starts with {@link CHECKPOINT_MARKER}). */
38
+ export declare function buildCheckpoint(label?: string): CompressibleMessage;
39
+ /**
40
+ * Return the turns AFTER the most recent checkpoint marker (all turns if none).
41
+ * Scans backward for the latest marker. Never mutates the input.
42
+ */
43
+ export declare function filterFromLatestCheckpoint(messages: CompressibleMessage[]): CompressibleMessage[];
44
+ /**
45
+ * True iff `err` is a {@link TheokitAgentError} (or subclass) reporting a
46
+ * context-window-exceeded condition (the typed `context_too_long` code). Reads
47
+ * both `code` (set by provider mappers) and `metadata.code` (the preferred field)
48
+ * — never a brittle message regex.
49
+ */
50
+ export declare function isContextOverflowError(err: unknown): boolean;
51
+ /** Input to {@link shouldCompact}: an estimate, the model's window, and reserved headroom. */
52
+ export interface ShouldCompactInput {
53
+ /** Estimated token count of the next request (e.g. from {@link estimateTokens}). */
54
+ readonly estimated: number;
55
+ /** The model's total context window, in tokens. */
56
+ readonly contextWindow: number;
57
+ /** Tokens to reserve as headroom (output + safety margin). */
58
+ readonly buffer: number;
59
+ }
60
+ /**
61
+ * Tokenizer-free token estimate via the conventional ~4-chars-per-token
62
+ * heuristic: `ceil(text.length / 4)`. `""` → 0; any non-empty text → ≥ 1.
63
+ * A cheap PRE-CALL gate for {@link shouldCompact} — NOT exact tokenization
64
+ * (a consumer needing exactness supplies their own tokenizer). Uses UTF-16
65
+ * `.length` (code units), so multibyte text is approximate.
66
+ */
67
+ export declare function estimateTokens(text: string): number;
68
+ /**
69
+ * Decide BEFORE sending whether to compact: `true` when the `estimated` token
70
+ * count leaves less than `buffer` headroom in the `contextWindow`
71
+ * (`estimated >= contextWindow - buffer`). A `buffer >= contextWindow`
72
+ * (non-positive threshold) always returns `true`. Pure — the caller supplies
73
+ * the window (e.g. from `resolveModelCapabilities`), keeping this decoupled
74
+ * from the per-model catalog.
75
+ */
76
+ export declare function shouldCompact(input: ShouldCompactInput): boolean;
@@ -0,0 +1,70 @@
1
+ // src/errors.ts
2
+ var TheokitAgentError = class extends Error {
3
+ name = "TheokitAgentError";
4
+ isRetryable;
5
+ code;
6
+ protoErrorCode;
7
+ metadata;
8
+ constructor(message, options = {}) {
9
+ super(message, options.cause !== void 0 ? { cause: options.cause } : void 0);
10
+ this.isRetryable = options.isRetryable ?? false;
11
+ if (options.code !== void 0) this.code = options.code;
12
+ if (options.protoErrorCode !== void 0) this.protoErrorCode = options.protoErrorCode;
13
+ if (options.metadata !== void 0) this.metadata = options.metadata;
14
+ }
15
+ };
16
+
17
+ // src/internal/runtime/compression/compression-helpers.ts
18
+ function selectCompressionWindow(messages, preserveLast = 6) {
19
+ if (messages.length <= preserveLast) {
20
+ return { toCompress: [], toPreserve: [...messages] };
21
+ }
22
+ return {
23
+ toCompress: messages.slice(0, -preserveLast),
24
+ toPreserve: messages.slice(-preserveLast)
25
+ };
26
+ }
27
+
28
+ // src/compaction.ts
29
+ var CHECKPOINT_MARKER = "[[theokit:checkpoint]] ";
30
+ function isSystemPrompt(message) {
31
+ return message.role === "system" && !message.content.startsWith(CHECKPOINT_MARKER);
32
+ }
33
+ async function compactTranscript(messages, options = {}) {
34
+ const keepRecent = options.keepRecent ?? 6;
35
+ const systemPrompts = messages.filter(isSystemPrompt);
36
+ const rest = messages.filter((m) => !isSystemPrompt(m));
37
+ const { toCompress, toPreserve } = selectCompressionWindow(rest, keepRecent);
38
+ if (toCompress.length === 0) {
39
+ return [...messages];
40
+ }
41
+ if (options.summarize) {
42
+ const summary = await options.summarize(toCompress);
43
+ return [...systemPrompts, summary, ...toPreserve];
44
+ }
45
+ return [...systemPrompts, ...toPreserve];
46
+ }
47
+ function buildCheckpoint(label) {
48
+ return { role: "system", content: CHECKPOINT_MARKER + (label ?? "") };
49
+ }
50
+ function filterFromLatestCheckpoint(messages) {
51
+ for (let i = messages.length - 1; i >= 0; i -= 1) {
52
+ if (messages[i]?.content.startsWith(CHECKPOINT_MARKER)) {
53
+ return messages.slice(i + 1);
54
+ }
55
+ }
56
+ return [...messages];
57
+ }
58
+ function isContextOverflowError(err) {
59
+ return err instanceof TheokitAgentError && (err.code === "context_too_long" || err.metadata?.code === "context_too_long");
60
+ }
61
+ function estimateTokens(text) {
62
+ return Math.ceil(text.length / 4);
63
+ }
64
+ function shouldCompact(input) {
65
+ return input.estimated >= input.contextWindow - input.buffer;
66
+ }
67
+
68
+ export { CHECKPOINT_MARKER, buildCheckpoint, compactTranscript, estimateTokens, filterFromLatestCheckpoint, isContextOverflowError, shouldCompact };
69
+ //# sourceMappingURL=compaction.js.map
70
+ //# sourceMappingURL=compaction.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors.ts","../src/internal/runtime/compression/compression-helpers.ts","../src/compaction.ts"],"names":[],"mappings":"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","file":"compaction.js","sourcesContent":["import { defaultRetriableForCode } from \"./internal/default-retriable.js\";\nimport { redactSecrets } from \"./internal/security/redact.js\";\nimport type { RunOperation } from \"./types/run.js\";\n\n/**\n * Finite, machine-readable error codes for provider-originated errors\n * (ADR D66). Consumers can `switch (err.metadata?.code)` exhaustively\n * — adding a new variant is an explicit decision + test coverage.\n *\n * @public\n */\nexport type ErrorCode =\n | \"rate_limit\"\n | \"auth_failed\"\n | \"invalid_request\"\n | \"timeout\"\n | \"server_error\"\n | \"context_too_long\"\n | \"content_filtered\"\n | \"model_unavailable\"\n | \"network\"\n | \"quota_exceeded\"\n | \"unknown\";\n\n/**\n * Codes used by {@link AgentRunError} (Production-Readiness #3, ADR D311).\n *\n * Superset of {@link ErrorCode} extended with codes that do NOT originate\n * from a provider HTTP response:\n *\n * - `quota_exceeded` — billing limit hit (provider 402 or signalled error)\n * - `tool_runtime_error` — custom tool handler threw inside dispatch\n * - `aborted` — caller's `AbortSignal` fired (Phase 4)\n * - `invalid_model` — model id rejected by provider (400 \"model not found\")\n * - `safety_blocked` — provider safety filter blocked req or resp\n * - `provider_unreachable` — DNS/TCP/timeout/5xx at transport boundary\n *\n * The `& {}` tail keeps the literal-union ergonomics (autocomplete) while\n * accepting any string for forward compatibility with constructor calls\n * that pass arbitrary code values (legacy callers).\n *\n * @public\n */\n/**\n * T1.1 — closed literal union for `AgentRunError.code`. The previous\n * `(string & {})` escape hatch let arbitrary strings slip into the type\n * surface and defeated exhaustive `switch (code)` discrimination. This is\n * the canonical closed form. `AgentRunErrorCode` is re-aliased below for\n * source-level back-compat.\n *\n * Adding a new code: append the literal here AND audit every `switch (err.code)`\n * in callers. Type-checker enforces the audit via the `default: assertNever(code)`\n * convention.\n *\n * @public\n */\nexport type KnownAgentRunErrorCode =\n | ErrorCode\n | \"quota_exceeded\"\n | \"tool_runtime_error\"\n | \"aborted\"\n | \"invalid_model\"\n | \"safety_blocked\"\n | \"provider_unreachable\";\n\n/**\n * Back-compat alias of {@link KnownAgentRunErrorCode}. Pre-T1.1 callers that\n * imported `AgentRunErrorCode` keep working; new code SHOULD prefer\n * `KnownAgentRunErrorCode` to make the closed-union intent explicit.\n *\n * @public\n */\nexport type AgentRunErrorCode = KnownAgentRunErrorCode;\n\n/** Snapshot of every known code at runtime — used by the boundary coercer. */\nconst KNOWN_AGENT_RUN_ERROR_CODES = new Set<string>([\n \"rate_limit\",\n \"auth_failed\",\n \"invalid_request\",\n \"timeout\",\n \"server_error\",\n \"context_too_long\",\n \"content_filtered\",\n \"model_unavailable\",\n \"network\",\n \"unknown\",\n \"quota_exceeded\",\n \"tool_runtime_error\",\n \"aborted\",\n \"invalid_model\",\n \"safety_blocked\",\n \"provider_unreachable\",\n]);\n\n/**\n * T1.1 boundary helper — coerce an arbitrary string (typically arriving from\n * a downstream `RunErrorDetail.code` or a deserialized cloud response) into a\n * `KnownAgentRunErrorCode`. Unknown strings collapse to `\"unknown\"` so the\n * closed type contract holds without forcing every caller to switch.\n *\n * @internal\n */\nexport function coerceToKnownAgentRunErrorCode(code: string | undefined): KnownAgentRunErrorCode {\n if (code !== undefined && KNOWN_AGENT_RUN_ERROR_CODES.has(code)) {\n return code as KnownAgentRunErrorCode;\n }\n return \"unknown\";\n}\n\n/**\n * Structured context for errors that originated from a provider HTTP\n * call (ADR D65). Lets callers retry with the right backoff (`retryAfter`),\n * surface actionable diagnostics (`provider`, `endpoint`), and inspect the\n * raw response body when needed (`raw`, capped at ~2KB by the mapper).\n *\n * @public\n */\nexport interface ErrorMetadata {\n /** Provider canonical name (e.g., `\"anthropic\"`, `\"openai\"`, `\"openrouter\"`, `\"gemini\"`). */\n provider: string;\n /** HTTP endpoint that failed (e.g., `\"/v1/messages\"`, `\"/v1/chat/completions\"`). */\n endpoint: string;\n /** Machine-readable error code (finite enum). */\n code: ErrorCode;\n /** HTTP status code if applicable. */\n statusCode?: number;\n /** Seconds to wait before retry, per provider's `retry-after` header (numeric form only). */\n retryAfter?: number;\n /** Raw response body for debugging (truncated to ~2KB by the mapper). */\n raw?: unknown;\n}\n\n/**\n * Base class for all errors thrown by `@theokit/sdk`.\n *\n * Use `isRetryable` to drive retry/backoff logic. `code` and `protoErrorCode`\n * are populated for server-originated errors when available. `metadata`\n * (ADR D65) carries structured `{ provider, endpoint, code, ... }` when\n * the error originated from a provider HTTP call.\n *\n * @public\n */\nexport class TheokitAgentError extends Error {\n override readonly name: string = \"TheokitAgentError\";\n readonly isRetryable: boolean;\n readonly code?: string;\n readonly protoErrorCode?: string;\n readonly metadata?: ErrorMetadata;\n\n constructor(\n message: string,\n options: {\n isRetryable?: boolean;\n code?: string;\n protoErrorCode?: string;\n cause?: unknown;\n metadata?: ErrorMetadata;\n } = {},\n ) {\n super(message, options.cause !== undefined ? { cause: options.cause } : undefined);\n this.isRetryable = options.isRetryable ?? false;\n if (options.code !== undefined) this.code = options.code;\n if (options.protoErrorCode !== undefined) this.protoErrorCode = options.protoErrorCode;\n if (options.metadata !== undefined) this.metadata = options.metadata;\n }\n}\n\n/**\n * Invalid API key, not logged in, insufficient permissions.\n *\n * @public\n */\nexport class AuthenticationError extends TheokitAgentError {\n override readonly name: string = \"AuthenticationError\";\n\n constructor(\n message: string,\n options: { code?: string; cause?: unknown; metadata?: ErrorMetadata } = {},\n ) {\n super(message, { ...options, isRetryable: false });\n }\n}\n\n/**\n * Too many requests or usage limits exceeded.\n *\n * @public\n */\nexport class RateLimitError extends TheokitAgentError {\n override readonly name: string = \"RateLimitError\";\n\n constructor(\n message: string,\n options: { code?: string; cause?: unknown; metadata?: ErrorMetadata } = {},\n ) {\n super(message, { ...options, isRetryable: true });\n }\n}\n\n/**\n * Invalid model, bad request parameters, malformed options.\n *\n * @public\n */\nexport class ConfigurationError extends TheokitAgentError {\n override readonly name: string = \"ConfigurationError\";\n\n constructor(\n message: string,\n options: { code?: string; cause?: unknown; metadata?: ErrorMetadata } = {},\n ) {\n super(message, { ...options, isRetryable: false });\n }\n}\n\n/**\n * Thrown when creating a cloud agent for a repo whose SCM provider is not\n * connected. Use `helpUrl` to point the user at the right reconnect flow.\n *\n * @public\n */\nexport class IntegrationNotConnectedError extends ConfigurationError {\n override readonly name: string = \"IntegrationNotConnectedError\";\n readonly provider: string;\n readonly helpUrl: string;\n\n constructor(\n message: string,\n options: {\n provider: string;\n helpUrl: string;\n code?: string;\n cause?: unknown;\n metadata?: ErrorMetadata;\n },\n ) {\n super(message, options);\n this.provider = options.provider;\n this.helpUrl = options.helpUrl;\n }\n}\n\n/**\n * Service unavailable, timeout, transport-level failure.\n *\n * @public\n */\nexport class NetworkError extends TheokitAgentError {\n override readonly name: string = \"NetworkError\";\n\n constructor(\n message: string,\n options: { code?: string; cause?: unknown; metadata?: ErrorMetadata } = {},\n ) {\n super(message, { ...options, isRetryable: true });\n }\n}\n\n/**\n * Catch-all for unclassified server or runtime errors.\n *\n * @public\n */\nexport class UnknownAgentError extends TheokitAgentError {\n override readonly name: string = \"UnknownAgentError\";\n\n constructor(\n message: string,\n options: { code?: string; cause?: unknown; metadata?: ErrorMetadata } = {},\n ) {\n super(message, { ...options, isRetryable: false });\n }\n}\n\n/**\n * Thrown by `Agent.prompt` (and helpers that go through `run.wait()`) when\n * the option `{ throwOnError: true }` is set and the run terminates with\n * `status: 'error'`. Carries the structured `RunResult.error` fields so\n * callers can `catch` once and branch on `code` / `provider` instead of\n * unwrapping the run.\n *\n * Extends {@link TheokitAgentError} per ADR D65 — no new hierarchy.\n *\n * @example\n * try {\n * await Agent.prompt(msg, { apiKey, model, throwOnError: true });\n * } catch (err) {\n * if (err instanceof AgentRunError && err.code === 'auth_failed') {\n * // bad key\n * }\n * }\n *\n * @public\n */\nexport class AgentRunError extends TheokitAgentError {\n override readonly name: string = \"AgentRunError\";\n readonly provider?: string;\n readonly raw?: string;\n /** Provider's request id (`x-request-id` / `request-id` header). Useful for support tickets. */\n readonly requestId?: string;\n /** SDK conversation id this error was raised inside. */\n readonly conversationId?: string;\n\n constructor(\n message: string,\n options: {\n code: AgentRunErrorCode;\n provider?: string;\n raw?: string;\n requestId?: string;\n conversationId?: string;\n retriable?: boolean;\n cause?: unknown;\n metadata?: ErrorMetadata;\n },\n ) {\n super(message, {\n code: options.code,\n cause: options.cause,\n metadata: options.metadata,\n // D311: most AgentRunErrors are not retriable (auth, validation, abort).\n // Provider mappers (D314) override per-status — explicit `retriable` wins\n // over the implicit default when supplied.\n isRetryable: options.retriable ?? defaultRetriableForCode(options.code),\n });\n if (options.provider !== undefined) this.provider = options.provider;\n if (options.raw !== undefined) this.raw = options.raw;\n if (options.requestId !== undefined) this.requestId = options.requestId;\n if (options.conversationId !== undefined) this.conversationId = options.conversationId;\n }\n\n /**\n * Production-Readiness #3 (ADR D311): alias for `isRetryable` exposed as\n * `retriable` to match the handoff contract. Future v2 will deprecate\n * `isRetryable` in favor of this.\n */\n get retriable(): boolean {\n return this.isRetryable;\n }\n\n /**\n * D312: provider's `Retry-After` header in **milliseconds**. Mappers store\n * the header value (seconds) in `metadata.retryAfter`; this getter\n * multiplies by 1000 so the result composes with `Date.now()`/`setTimeout`.\n *\n * Returns `undefined` when no hint was provided. `0` is a legitimate value\n * — use `=== undefined` check rather than truthy check.\n */\n get retryAfterMs(): number | undefined {\n if (this.metadata?.retryAfter === undefined) return undefined;\n return this.metadata.retryAfter * 1000;\n }\n\n /**\n * D313 + T1.5: alias for `metadata.raw`. Provider response body for\n * debugging. T1.5 wraps the value in `redactSecrets` at the getter\n * boundary so secret-shaped substrings (`sk-...`, Bearer JWTs, etc.) are\n * stripped before reaching the caller. Available but NEVER serialized\n * into `.message` (anti-leak invariant).\n */\n get providerError(): unknown {\n const raw = this.metadata?.raw;\n if (raw === undefined) return undefined;\n if (typeof raw === \"string\") return redactSecrets(raw);\n // Non-string raw (object/buffer) — stringify then redact.\n try {\n return redactSecrets(JSON.stringify(raw));\n } catch {\n return redactSecrets(String(raw));\n }\n }\n\n /**\n * T1.5 — sanitized JSON form. `metadata.raw` is OMITTED by default; opt\n * in via `THEOKIT_DEBUG_RAW_ERRORS=1` to surface the (redacted) raw\n * payload for diagnostics. Every other field stays accessible.\n *\n * The single env-var gate is read each call so operators can toggle at\n * runtime without restarting the process.\n */\n toJSON(): Record<string, unknown> {\n const json: Record<string, unknown> = {\n name: this.name,\n message: this.message,\n isRetryable: this.isRetryable,\n };\n addOptionalFields(json, this);\n const safeMeta = sanitizeMetadata(this.metadata);\n if (safeMeta !== undefined) json.metadata = safeMeta;\n return json;\n }\n}\n\nfunction addOptionalFields(json: Record<string, unknown>, err: AgentRunError): void {\n if (err.code !== undefined) json.code = err.code;\n if (err.provider !== undefined) json.provider = err.provider;\n if (err.requestId !== undefined) json.requestId = err.requestId;\n if (err.conversationId !== undefined) json.conversationId = err.conversationId;\n if (err.raw !== undefined) json.raw = redactSecrets(err.raw);\n}\n\nfunction sanitizeMetadata(meta: ErrorMetadata | undefined): ErrorMetadata | undefined {\n if (meta === undefined) return undefined;\n const { raw, ...rest } = meta;\n const debugRaw = process.env.THEOKIT_DEBUG_RAW_ERRORS === \"1\";\n if (debugRaw && raw !== undefined) {\n const redactedRaw =\n typeof raw === \"string\" ? redactSecrets(raw) : redactSecrets(safeStringify(raw));\n return { ...rest, raw: redactedRaw } as ErrorMetadata;\n }\n return rest as ErrorMetadata;\n}\n\nfunction safeStringify(value: unknown): string {\n try {\n return JSON.stringify(value);\n } catch {\n return String(value);\n }\n}\n\n/**\n * Is this error transient (worth retrying)?\n *\n * Returns the SDK's own retryability verdict: every {@link TheokitAgentError}\n * subclass computes `isRetryable` at construction (rate-limit / network /\n * credential-pool-exhausted are retryable; auth / configuration / unsupported\n * are not), so this predicate is a single source of truth rather than a\n * re-derivation. Non-SDK errors return `false` conservatively — wrap a foreign\n * error in the appropriate SDK error first if you want it considered transient.\n * It never inspects `err.message`.\n *\n * @example\n * try {\n * await agent.send(message, { throwOnError: true });\n * } catch (err) {\n * if (isTransientError(err)) return retryWithBackoff();\n * throw err;\n * }\n *\n * @public\n */\nexport function isTransientError(err: unknown): boolean {\n return err instanceof TheokitAgentError && err.isRetryable === true;\n}\n\n/**\n * Thrown when a {@link Run} or agent operation is not available on the current\n * runtime. Check first with `run.supports(operation)`.\n *\n * Extends {@link TheokitAgentError} (so error-catching code that branches on\n * `instanceof TheokitAgentError` continues to work) but is never retryable —\n * an unsupported operation will not become supported on retry.\n *\n * @public\n */\nexport class UnsupportedRunOperationError extends TheokitAgentError {\n override readonly name: string = \"UnsupportedRunOperationError\";\n readonly operation: RunOperation;\n\n constructor(\n message: string,\n operation: RunOperation,\n options: { code?: string; cause?: unknown } = {},\n ) {\n super(message, {\n ...options,\n isRetryable: false,\n code: options.code ?? \"unsupported_run_operation\",\n });\n this.operation = operation;\n }\n}\n\n/**\n * Thrown when every credential in a per-provider pool is in cooldown\n * and no healthy key is available (ADR D133). The caller's\n * {@link import(\"./internal/llm/fallback-client.js\").FallbackLlmClient}\n * catches this and tries the next provider in the fallback chain.\n *\n * `metadata.nextRetryAt` (epoch ms) tells callers when the soonest\n * pool entry resumes — useful for manual retry scheduling.\n *\n * @public\n */\nexport class CredentialPoolExhaustedError extends TheokitAgentError {\n override readonly name: string = \"CredentialPoolExhaustedError\";\n readonly provider: string;\n readonly nextRetryAt: number | undefined;\n\n constructor(\n message: string,\n options: {\n provider: string;\n nextRetryAt?: number;\n code?: string;\n cause?: unknown;\n metadata?: ErrorMetadata;\n },\n ) {\n super(message, {\n ...options,\n isRetryable: true,\n code: options.code ?? \"credential_pool_exhausted\",\n });\n this.provider = options.provider;\n this.nextRetryAt = options.nextRetryAt;\n }\n}\n\n/**\n * Finite error codes specific to memory adapter operations (ADR D141).\n *\n * @public\n */\nexport type MemoryAdapterErrorCode =\n | \"auth_failed\"\n | \"rate_limited\"\n | \"not_found\"\n | \"network\"\n | \"invalid_input\"\n | \"unknown\";\n\n/**\n * Error raised by `@theokit-memory-*` adapters. Carries `adapterId`\n * so callers can branch on which provider failed (ADR D141).\n *\n * @public\n */\nexport class MemoryAdapterError extends TheokitAgentError {\n override readonly name: string = \"MemoryAdapterError\";\n readonly adapterId: string;\n\n constructor(\n message: string,\n options: {\n adapterId: string;\n code: MemoryAdapterErrorCode;\n cause?: unknown;\n metadata?: ErrorMetadata;\n },\n ) {\n super(message, {\n isRetryable: options.code === \"rate_limited\" || options.code === \"network\",\n code: options.code,\n ...(options.cause !== undefined ? { cause: options.cause } : {}),\n ...(options.metadata !== undefined ? { metadata: options.metadata } : {}),\n });\n this.adapterId = options.adapterId;\n }\n}\n\n/**\n * Thrown when a user-supplied task ID violates the grammar\n * `^[a-z0-9][a-z0-9_-]*$` (D368) OR starts with a reserved adapter\n * prefix (`wf-` / `b-` / `cron-`, EC-5).\n *\n * @public\n */\nexport class InvalidTaskIdError extends TheokitAgentError {\n override readonly name: string = \"InvalidTaskIdError\";\n readonly taskId: string;\n\n constructor(message: string, taskId: string, options: { cause?: unknown } = {}) {\n super(message, {\n ...options,\n isRetryable: false,\n code: \"invalid_task_id\",\n });\n this.taskId = taskId;\n }\n}\n\n/**\n * Thrown when `Task.subscribe(id)` is called for a task that has been\n * evicted, never submitted, or evicted after retention (D373).\n *\n * @public\n */\nexport class TaskNotFoundError extends TheokitAgentError {\n override readonly name: string = \"TaskNotFoundError\";\n readonly taskId: string;\n\n constructor(taskId: string, options: { cause?: unknown } = {}) {\n super(`Task not found: ${taskId}`, {\n ...options,\n isRetryable: false,\n code: \"task_not_found\",\n });\n this.taskId = taskId;\n }\n}\n\n/**\n * Thrown when `CloudAgent` is asked to wrap a task (D370). Cloud\n * task observability is deferred until Theo PaaS GA.\n *\n * @public\n */\nexport class UnsupportedTaskOperationError extends TheokitAgentError {\n override readonly name: string = \"UnsupportedTaskOperationError\";\n readonly operation: string;\n\n constructor(operation: string, options: { cause?: unknown } = {}) {\n super(\n `Task operation \"${operation}\" is not supported on CloudAgent (pre-release; see ADR D370)`,\n {\n ...options,\n isRetryable: false,\n code: \"task_op_unsupported\",\n },\n );\n this.operation = operation;\n }\n}\n\n/**\n * Thrown by `Budget` enforcement (ADR D386) when a `mode: \"block\"`\n * budget would be exceeded by the upcoming LLM call. Caller pega\n * tipado para retry-after-window-reset or surface to the user.\n *\n * @public\n */\nexport class BudgetExceededError extends TheokitAgentError {\n override readonly name: string = \"BudgetExceededError\";\n readonly budgetName: string;\n readonly window: import(\"./types/budget.js\").BudgetWindow;\n readonly spentUsd: number;\n readonly limitUsd: number;\n readonly mode: import(\"./types/budget.js\").BudgetMode;\n\n constructor(args: {\n budgetName: string;\n window: import(\"./types/budget.js\").BudgetWindow;\n spentUsd: number;\n limitUsd: number;\n mode: import(\"./types/budget.js\").BudgetMode;\n cause?: unknown;\n }) {\n super(\n `Budget \"${args.budgetName}\" exceeded for window ${args.window}: spent $${args.spentUsd.toFixed(4)} > limit $${args.limitUsd.toFixed(4)}`,\n {\n ...(args.cause !== undefined ? { cause: args.cause } : {}),\n isRetryable: false,\n code: \"budget_exceeded\",\n },\n );\n this.budgetName = args.budgetName;\n this.window = args.window;\n this.spentUsd = args.spentUsd;\n this.limitUsd = args.limitUsd;\n this.mode = args.mode;\n }\n}\n\n/**\n * Thrown when `CloudAgent.send({ budget })` is invoked (D388). Cloud\n * budget surface waits for Theo PaaS GA.\n *\n * @public\n */\n/**\n * T1.6 — Thrown when a consumer calls `agent.send()` or any method\n * on an agent that has already been `dispose()`d. Pre-T1.6 this was\n * a generic `new Error(\"Agent has been disposed\")` — consumers\n * couldn't catch it without string-matching the message.\n *\n * @public\n */\nexport class AgentDisposedError extends TheokitAgentError {\n override readonly name: string = \"AgentDisposedError\";\n readonly agentId: string;\n\n constructor(agentId: string) {\n super(`Agent \"${agentId}\" has been disposed. Create a new agent or use Agent.resume().`, {\n isRetryable: false,\n code: \"agent_disposed\",\n });\n this.agentId = agentId;\n }\n}\n\nexport class UnsupportedBudgetOperationError extends TheokitAgentError {\n override readonly name: string = \"UnsupportedBudgetOperationError\";\n readonly operation: string;\n\n constructor(operation: string, options: { cause?: unknown } = {}) {\n super(\n `Budget operation \"${operation}\" is not supported on CloudAgent (pre-release; see ADR D388)`,\n {\n ...options,\n isRetryable: false,\n code: \"budget_op_unsupported\",\n },\n );\n this.operation = operation;\n }\n}\n","/**\n * Compression helpers (T2.3, ADR D92).\n *\n * Scaffold for future compression LLM integration:\n * - `selectCompressionWindow` — splits messages into compress/preserve halves\n * - `assertCompressionReduced` — 10% reduction floor to detect \"compression placebo\"\n *\n * The compression LLM call itself is out of scope for this plan (requires\n * an auxiliary-model ADR). These helpers are used by `Agent.send` when a\n * future iteration adds compression.\n *\n * @internal\n */\n\nexport interface CompressionWindow<M> {\n toCompress: M[];\n toPreserve: M[];\n}\n\n/**\n * Split `messages` into the half to compress (older) and the half to\n * preserve verbatim (recent). When `messages.length <= preserveLast`,\n * everything is preserved.\n *\n * @internal\n */\nexport function selectCompressionWindow<M>(\n messages: readonly M[],\n preserveLast = 6,\n): CompressionWindow<M> {\n if (messages.length <= preserveLast) {\n return { toCompress: [], toPreserve: [...messages] };\n }\n return {\n toCompress: messages.slice(0, -preserveLast),\n toPreserve: messages.slice(-preserveLast),\n };\n}\n\nexport interface CompressionCheck {\n reduced: boolean;\n reductionPct: number;\n reason?: string;\n}\n\n/**\n * Check that compression actually reduced token count by at least `minPct`\n * (default 10%). Returns `{ reduced: false }` for spirals-in-formation\n * (compression LLM outputs that grow or barely shrink).\n *\n * @internal\n */\nexport function assertCompressionReduced(\n before: number,\n after: number,\n minPct = 10,\n): CompressionCheck {\n if (before <= 0) {\n return { reduced: false, reductionPct: 0, reason: \"before count was zero\" };\n }\n const reductionPct = ((before - after) / before) * 100;\n if (reductionPct >= minPct) {\n return { reduced: true, reductionPct };\n }\n return {\n reduced: false,\n reductionPct,\n reason: `compression reduced ${reductionPct.toFixed(1)}% (< ${minPct}% min). Spiral likely.`,\n };\n}\n","/**\n * Public compaction / context-management helpers (M2-1).\n *\n * Promotes the SDK's compaction capability to a public surface so consumers can\n * compact a transcript (keep-recent + optional summarize), mark/filter\n * conversation checkpoints, and detect context-overflow — without reaching into\n * `internal/`. `compactTranscript` REUSES the internal `selectCompressionWindow`\n * (no second algorithm); summarization is delegated to a caller-supplied callback\n * (which can wire the internal `compressConversationWindow`).\n *\n * Public from the `@theokit/sdk/compaction` sub-path. See `docs.md → Compaction`.\n */\n\nimport { TheokitAgentError } from \"./errors.js\";\nimport { selectCompressionWindow } from \"./internal/runtime/compression/compression-helpers.js\";\nimport type { CompressibleMessage } from \"./internal/runtime/compression/compression-summarizer.js\";\n\nexport type { CompressibleMessage };\n\n/**\n * Sentinel prefix marking a conversation checkpoint turn. A visible, structured,\n * prose-unlikely token (no invisible/control bytes — safe to persist and to read\n * in source). Only {@link buildCheckpoint} should produce content beginning with it.\n */\nexport const CHECKPOINT_MARKER = \"[[theokit:checkpoint]] \";\n\n/** True for a real system prompt — a `system` turn that is NOT a checkpoint marker. */\nfunction isSystemPrompt(message: CompressibleMessage): boolean {\n return message.role === \"system\" && !message.content.startsWith(CHECKPOINT_MARKER);\n}\n\n/** Options for {@link compactTranscript}. */\nexport interface CompactTranscriptOptions {\n /** Trailing turns preserved verbatim (default 6, matching the internal window). */\n keepRecent?: number;\n /** Summarize the older window into one turn; if omitted, the older window is dropped. */\n summarize?: (older: CompressibleMessage[]) => Promise<CompressibleMessage>;\n}\n\n/**\n * Compact a transcript: keep the last `keepRecent` turns verbatim, always preserve\n * leading system PROMPTS, and either summarize (via `summarize`) or drop the older\n * window. Checkpoint markers are NOT system prompts — they flow through the\n * keep-recent window as ordinary turns (a marker in the older window is\n * summarized/dropped, one in the recent window is kept). Reuses the internal\n * `selectCompressionWindow`. Never mutates the input.\n */\nexport async function compactTranscript(\n messages: CompressibleMessage[],\n options: CompactTranscriptOptions = {},\n): Promise<CompressibleMessage[]> {\n const keepRecent = options.keepRecent ?? 6;\n const systemPrompts = messages.filter(isSystemPrompt);\n const rest = messages.filter((m) => !isSystemPrompt(m));\n const { toCompress, toPreserve } = selectCompressionWindow(rest, keepRecent);\n if (toCompress.length === 0) {\n return [...messages];\n }\n if (options.summarize) {\n const summary = await options.summarize(toCompress);\n return [...systemPrompts, summary, ...toPreserve];\n }\n return [...systemPrompts, ...toPreserve];\n}\n\n/** Build a checkpoint marker turn (a `system` turn whose content starts with {@link CHECKPOINT_MARKER}). */\nexport function buildCheckpoint(label?: string): CompressibleMessage {\n return { role: \"system\", content: CHECKPOINT_MARKER + (label ?? \"\") };\n}\n\n/**\n * Return the turns AFTER the most recent checkpoint marker (all turns if none).\n * Scans backward for the latest marker. Never mutates the input.\n */\nexport function filterFromLatestCheckpoint(messages: CompressibleMessage[]): CompressibleMessage[] {\n for (let i = messages.length - 1; i >= 0; i -= 1) {\n if (messages[i]?.content.startsWith(CHECKPOINT_MARKER)) {\n return messages.slice(i + 1);\n }\n }\n return [...messages];\n}\n\n/**\n * True iff `err` is a {@link TheokitAgentError} (or subclass) reporting a\n * context-window-exceeded condition (the typed `context_too_long` code). Reads\n * both `code` (set by provider mappers) and `metadata.code` (the preferred field)\n * — never a brittle message regex.\n */\nexport function isContextOverflowError(err: unknown): boolean {\n return (\n err instanceof TheokitAgentError &&\n (err.code === \"context_too_long\" || err.metadata?.code === \"context_too_long\")\n );\n}\n\n/** Input to {@link shouldCompact}: an estimate, the model's window, and reserved headroom. */\nexport interface ShouldCompactInput {\n /** Estimated token count of the next request (e.g. from {@link estimateTokens}). */\n readonly estimated: number;\n /** The model's total context window, in tokens. */\n readonly contextWindow: number;\n /** Tokens to reserve as headroom (output + safety margin). */\n readonly buffer: number;\n}\n\n/**\n * Tokenizer-free token estimate via the conventional ~4-chars-per-token\n * heuristic: `ceil(text.length / 4)`. `\"\"` → 0; any non-empty text → ≥ 1.\n * A cheap PRE-CALL gate for {@link shouldCompact} — NOT exact tokenization\n * (a consumer needing exactness supplies their own tokenizer). Uses UTF-16\n * `.length` (code units), so multibyte text is approximate.\n */\nexport function estimateTokens(text: string): number {\n return Math.ceil(text.length / 4);\n}\n\n/**\n * Decide BEFORE sending whether to compact: `true` when the `estimated` token\n * count leaves less than `buffer` headroom in the `contextWindow`\n * (`estimated >= contextWindow - buffer`). A `buffer >= contextWindow`\n * (non-positive threshold) always returns `true`. Pure — the caller supplies\n * the window (e.g. from `resolveModelCapabilities`), keeping this decoupled\n * from the per-model catalog.\n */\nexport function shouldCompact(input: ShouldCompactInput): boolean {\n return input.estimated >= input.contextWindow - input.buffer;\n}\n"]}
@@ -911,6 +911,14 @@ interface AgentDefinition {
911
911
  prompt: string;
912
912
  model?: ModelSelection | "inherit";
913
913
  mcpServers?: Array<string | Record<string, McpServerConfig>>;
914
+ /**
915
+ * Tool whitelist (M4-6). When set, the sub-agent may ONLY call tools whose
916
+ * canonical (post-repair, lowercase) name is in this list — any other tool
917
+ * call is vetoed at dispatch via the same `withToolWhitelist` enforcement
918
+ * forks use (NOT `PermissionEngine`). Absent/empty → unscoped (inherits the
919
+ * parent's full toolset). Apply with `withSubagentToolScope`.
920
+ */
921
+ tools?: string[];
914
922
  }
915
923
  /**
916
924
  * Public skill metadata exposed to the system-prompt resolver. Mirrors the
@@ -911,6 +911,14 @@ interface AgentDefinition {
911
911
  prompt: string;
912
912
  model?: ModelSelection | "inherit";
913
913
  mcpServers?: Array<string | Record<string, McpServerConfig>>;
914
+ /**
915
+ * Tool whitelist (M4-6). When set, the sub-agent may ONLY call tools whose
916
+ * canonical (post-repair, lowercase) name is in this list — any other tool
917
+ * call is vetoed at dispatch via the same `withToolWhitelist` enforcement
918
+ * forks use (NOT `PermissionEngine`). Absent/empty → unscoped (inherits the
919
+ * parent's full toolset). Apply with `withSubagentToolScope`.
920
+ */
921
+ tools?: string[];
914
922
  }
915
923
  /**
916
924
  * Public skill metadata exposed to the system-prompt resolver. Mirrors the