@theoremjs/agents 0.3.1 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -21,6 +21,7 @@
21
21
  <a href="https://jsr.io/@theoremjs/agents/score"><img src="https://jsr.io/badges/@theoremjs/agents/score" alt="JSR score"></a>
22
22
  <a href="https://www.npmjs.com/package/@theoremjs/agents"><img src="https://img.shields.io/npm/v/@theoremjs/agents?logo=npm&label=npm&color=cb3837" alt="npm"></a>
23
23
  <a href="https://github.com/masudl-hub/theoremai/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue" alt="MIT License"></a>
24
+ <a href="https://deepwiki.com/masudl-hub/theoremai"><img src="https://deepwiki.com/badge.svg" alt="Ask DeepWiki"></a>
24
25
  </p>
25
26
 
26
27
  <p align="center">
@@ -441,7 +442,7 @@ Named exports from the root barrel (same symbols hosts get from `@theoremjs/agen
441
442
  | Guardrail vocabulary | `AdvisoryLevel`, `TrustLevel`, `GuardrailStage`, `Severity`, `GuardrailHit`, `Verdict`, `GuardrailAction`, `GuardrailContext`, `GuardrailEvent`, `guardrailEventSchema`, `OutboundPayload`, `Provenance`, `ToolOrigin`, `ScanText`, `BlockedReplySpec`, `BlockedReplyOnBlock`, `ResolvedBlockedReply`, `UrlAllow`, `NameAllow`, `UrlDetector`, `ResolvedAllow`, `ProfileGuardrailsSpec`, `HostGuardrailsSpec`, `NetworkGuardrailSpec`, `QuotaGuardrailSpec`, `ResolvedGuardrailPolicy`, `SensitiveGroup`, `SensitiveSelection`, `SensitiveSwitches`, `GuardedToolText`, `TurnTaint`, `TaintGate`, `TaintGuardrailSpec`, `TurnDestinations`, `DestinationGate`, `DestinationVerdict`, `TRUST_LEVELS`, `GUARDRAIL_STAGES`, `SEVERITIES`, `TOOL_ORIGINS` |
442
443
  | Guardrail policy | `resolveGuardrailPolicy`, `BLOCKED_REPLY_ON_BLOCK`, `hitRules`, `EGRESS_RULES` |
443
444
  | Tool boundary | `guardToolResult`, `guardToolFailureText`, `inspectToolArguments`, `toolCallEvent`, `wrapToolData`, `isRemoteOrigin`, `composeToolText`, `checkTaintGate`, `recordTaint`, `isTainted`, `isSuspicious`, `checkDestinationGate`, `turnDestinations`, `addGivenDestinations`, `addHistoryDestinations`, `addRequestDestinations`, `addResultDestinations`, `DESTINATION_GATES`, `directives`, `advisoryLevel`, `DIRECTIVE_SIGNALS`, `Directive`, `DirectiveSignal`, `ADVISORY_LEVELS`, `TOOL_CLOSE`, `TOOL_ORIGINS`, `TAINT_GATES`, `InspectedToolArguments`, `textForScan`, `scanTextOf` |
444
- | Quota | `QuotaSlotStatus`, `clientIp`, `quotaExhausted`, `releaseSlot`, `resetSlots`, `skipQuota`, `takeSlot` |
445
+ | Quota | `QuotaSlotStatus`, `clientIp`, `quotaBusy`, `quotaExhausted`, `releaseSlot`, `resetSlots`, `skipQuota`, `takeSlot` |
445
446
  | Labels | `LABEL_KEYS`, `LabelKey`, `CLIENT_LABEL_KEYS`, `ClientLabelKey`, `LabelOverrides`, `LabelParams`, `labelDefault`, `labelText`, `overrideLabels`, `resetLabels` |
446
447
  | Sanitize | `sanitizeProjectId`, `sanitizeTurnRequest`, `sanitizeTurnRequestWithEvents`, `SanitizedTurnRequest`, `guardrailFromHits`, `guardrailFromVerdict`, `guardrailTurnEvent`, `projectGuardrailTurnEvent`, `hitFromSpan`, `projectGuardrailEvent` |
447
448
  | Canary / egress | `mintCanary`, `bindCanary`, `wrapUserData`, `scanTextForCanaryLeak`, `scanTextForPromptEcho`, `PROMPT_ECHO_WORDS`, `redactCanary`, `OMIT_CANARY`, `createCanaryStreamGate`, `eventHasCanary`, `createCanaryGateSession`, `filterCanaryGatedEvents`, `CanaryGateResult`, `CanaryGateSession`, `CanaryStreamGate`, `GivenUrls`, `createOutboundProgressiveGate`, `createProgressiveYieldGate`, `createLiveOutboundGateSession`, `processLiveOutboundBatch`, `finalizeLiveOutboundTurn`, `abortLiveOutboundTurn`, `LiveHeldOutput`, `LiveOutboundBatchResult`, `LiveOutboundGateSession`, `ProgressiveYieldGate`, `ProgressiveYieldGateOptions`, `ProgressiveYieldResult` |
package/esm/mod.d.ts CHANGED
@@ -19,7 +19,7 @@ export { ADVISORY_LEVELS, abortLiveOutboundTurn, addGivenDestinations, addHistor
19
19
  export type { DnsOverHttpsOptions, GuardedFetchOptions, ResolveHost, } from './src/guardrails/network.js';
20
20
  export { assertSafeUrl, dnsOverHttpsResolver, fetchGuarded, isLocalhostName, isPrivateOrLocalAddress, } from './src/guardrails/network.js';
21
21
  export type { QuotaSlotStatus } from './src/guardrails/quota.js';
22
- export { clientIp, quotaExhausted, releaseSlot, resetSlots, skipQuota, takeSlot, } from './src/guardrails/quota.js';
22
+ export { clientIp, quotaBusy, quotaExhausted, releaseSlot, resetSlots, skipQuota, takeSlot, } from './src/guardrails/quota.js';
23
23
  export type { SanitizedTurnRequest } from './src/guardrails/sanitize.js';
24
24
  export { sanitizeProjectId, sanitizeTurnRequest, sanitizeTurnRequestWithEvents, } from './src/guardrails/sanitize.js';
25
25
  export type { ProviderStateRebuilt } from './src/kernel/checkpoint.js';
package/esm/mod.js CHANGED
@@ -14,7 +14,7 @@ export { hitFromSpan, projectGuardrailEvent, } from './src/guardrails/hits.js';
14
14
  export { CLIENT_LABEL_KEYS, LABEL_KEYS, labelDefault, labelText, overrideLabels, resetLabels, } from './src/guardrails/labels.js';
15
15
  export { ADVISORY_LEVELS, abortLiveOutboundTurn, addGivenDestinations, addHistoryDestinations, addRequestDestinations, addResultDestinations, BLOCKED_REPLY_ON_BLOCK, bindCanary, checkDestinationGate, checkTaintGate, composeToolText, createCanaryGateSession, createCanaryStreamGate, createLiveOutboundGateSession, createOutboundProgressiveGate, createProgressiveYieldGate, DESTINATION_GATES, DIRECTIVE_SIGNALS, directives, EGRESS_RULES, eventHasCanary, filterCanaryGatedEvents, finalizeLiveOutboundTurn, GUARDRAIL_STAGES, guardToolFailureText, guardToolResult, hitRules, inspectToolArguments, isRemoteOrigin, isSuspicious, isTainted, mintCanary, OMIT_CANARY, processLiveOutboundBatch, recordTaint, redactCanary, resolveGuardrailPolicy, SEVERITIES, scanTextForCanaryLeak, scanTextOf, TAINT_GATES, TOOL_CLOSE, TOOL_ORIGINS, TRUST_LEVELS, textForScan, toolCallEvent, turnDestinations, wrapToolData, wrapUserData, } from './src/guardrails/mod.js';
16
16
  export { assertSafeUrl, dnsOverHttpsResolver, fetchGuarded, isLocalhostName, isPrivateOrLocalAddress, } from './src/guardrails/network.js';
17
- export { clientIp, quotaExhausted, releaseSlot, resetSlots, skipQuota, takeSlot, } from './src/guardrails/quota.js';
17
+ export { clientIp, quotaBusy, quotaExhausted, releaseSlot, resetSlots, skipQuota, takeSlot, } from './src/guardrails/quota.js';
18
18
  export { sanitizeProjectId, sanitizeTurnRequest, sanitizeTurnRequestWithEvents, } from './src/guardrails/sanitize.js';
19
19
  export { historyHash, providerCheckpointSchema, providerContinuationSchema, providerWarningSchema, } from './src/kernel/checkpoint.js';
20
20
  export { clearProfiles, compactHistory, getProfile, getProvider, getStructured, getTool, hasProfile, hasTool, invokeTool, invokeToolStep, listProfiles, listProtocols, listProviders, listTools, projectProfile, registerProfile, registerProfiles, registerProvider, registerStructured, registerTool, registerTools, requireTool, resetTools, resolveTurn, runDecision, runEmbedding, runSession, runTool, runToolStep, runTurn, } from './src/kernel/default-scope.js';
@@ -10,7 +10,7 @@ declare function isTimeoutError(err: unknown): boolean;
10
10
  declare function throwIfAborted(signal?: AbortSignal): void;
11
11
  /** Anything that is not a `TheoremError` or an abort escaped every boundary that names kinds: a THEOREM bug. */
12
12
  declare function errorKind(err: unknown): ErrorKind;
13
- /** The error kind for an HTTP status: 401, 402 and 403 are `auth`; 408, 504 and 524 `timeout`; 429 `rate_limit`; 500 and above `unavailable`; the rest `unsupported`. */
13
+ /** The error kind for an HTTP status: 401, 402 and 403 are `auth`; 408, 504 and 524 `timeout`; 429 `rate_limit`; 503 and 529 (overloaded) `busy`; 500 and above `unavailable`; the rest `unsupported`. */
14
14
  declare function kindOfHttpStatus(status: number): ErrorKind;
15
15
  /** The named reasons an error carries, one line each, in the profile's wording. */
16
16
  declare function errorCopyText(copy: ErrorCopies, labels?: LabelOverrides): string;
@@ -32,7 +32,7 @@ function errorKind(err) {
32
32
  return 'cancelled';
33
33
  return 'internal';
34
34
  }
35
- /** The error kind for an HTTP status: 401, 402 and 403 are `auth`; 408, 504 and 524 `timeout`; 429 `rate_limit`; 500 and above `unavailable`; the rest `unsupported`. */
35
+ /** The error kind for an HTTP status: 401, 402 and 403 are `auth`; 408, 504 and 524 `timeout`; 429 `rate_limit`; 503 and 529 (overloaded) `busy`; 500 and above `unavailable`; the rest `unsupported`. */
36
36
  function kindOfHttpStatus(status) {
37
37
  if (status === 401 || status === 402 || status === 403)
38
38
  return 'auth';
@@ -40,6 +40,8 @@ function kindOfHttpStatus(status) {
40
40
  return 'timeout';
41
41
  if (status === 429)
42
42
  return 'rate_limit';
43
+ if (status === 503 || status === 529)
44
+ return 'busy';
43
45
  if (status >= 500)
44
46
  return 'unavailable';
45
47
  return 'unsupported';
@@ -1,7 +1,7 @@
1
1
  /** Values that fill a label template's `{param}` placeholders. */
2
2
  export type LabelParams = Record<string, string | number>;
3
3
  /** Keys are stable API. */
4
- export declare const LABEL_KEYS: readonly ["provider.decision_failed", "provider.decision_unavailable", "provider.decision_model_missing", "provider.decision_ended", "provider.decision_images_off", "provider.decision_box_model", "provider.budget_exhausted", "provider.adapter_version", "provider.adapter_methods", "provider.adapter_schemas", "provider.fallback_slot", "provider.model_fallback_slot", "provider.host_model", "provider.turn_profile", "provider.model_binding_missing", "provider.session_closed", "provider.settled_call", "provider.session_unavailable", "provider.live_input", "provider.tool_cancel_missing", "provider.kernel_gate", "provider.event_invalid", "provider.response_identity", "provider.credential_resolution", "provider.credential_shape", "provider.stored_continuation", "provider.continuation_mismatch", "provider.continuation_contract", "provider.stateless_history", "provider.events_after_completion", "provider.duplicate_call", "provider.tool_cancel_live", "provider.complete_unavailable", "provider.parallel_tools", "provider.binding_identity", "provider.profile_type", "provider.event_type", "provider.feature", "provider.input_kind", "provider.output_kind", "provider.builtin", "provider.credential_missing", "provider.profile_configuration", "continue.instruction", "canary.bind_note", "user_data.note", "context.note", "taint.blocked", "taint.reason_steered", "taint.reason_tainted", "taint.destination_blocked", "taint.destination_confirm", "advisory.notice_elevated", "advisory.notice_high", "advisory.guidance", "attachments.too_many_files", "attachments.too_many_of_kind", "attachments.file_too_large", "attachments.turn_too_large", "attachments.not_accepted", "attachments.mime_not_allowed", "input.too_long", "attachments.limits_unconfigured", "quota.exhausted", "error.config", "error.request", "error.input", "error.action", "error.auth", "error.rate_limit", "error.unsupported", "error.unavailable", "error.bad_response", "error.network", "error.timeout", "error.safety", "error.blocked", "error.declined", "error.failed", "error.cancelled", "error.internal", "repair.default_guidance", "repair.items_guidance", "repair.prompt_header", "repair.prompt_intro", "repair.prompt_instructions", "repair.history_heading", "repair.section_previous_output", "repair.section_validator_rejection", "repair.section_repair_guidance", "repair.section_instructions", "compaction.request", "compaction.tool_call", "compaction.tool_result", "decision.box_image", "detect.blocked", "detect.call_blocked", "detect.output_blocked", "detect.shown_blocked", "detect.hint.ids", "detect.hint.financial", "detect.hint.network", "detect.hint.credentials", "detect.hint.injection", "detect.hint.tool_instructions", "detect.hint.canary_leak", "detect.hint.prompt_leak", "detect.hint.marker_leak", "detect.hint.ungiven_images", "detect.hint.ungiven_links", "detect.hint.tool_leak", "detect.hint.own", "egress.default_repair_guidance", "egress.refusal", "egress.refusal_cut", "egress.rejection", "egress.rejection_found", "egress.hint_unscannable", "egress.hint_provider_tool_leak", "thought.omitted_image", "thought.omitted_link", "thought.omitted_instructions", "session.abandon_gated", "session.tool_denied", "session.tool_aborted", "session.sign_in", "sign_in.link", "sign_in.pending", "sign_in.done", "sign_in.declined", "sign_in.expired", "sign_in.out_of_scope", "session.gate_expired", "session.provider_state_unowned", "session.conversation_unknown", "session.turn_ended", "session.gate_pending", "session.part_skipped", "live.session_ended", "voice.unsupported", "voice.permission", "voice.unavailable", "voice.failed", "voice.empty", "stop.cancelled", "stop.interrupted", "stop.length", "stop.filtered", "stop.provider_error", "stop.stream_incomplete", "stop.step_limit", "composer.model", "composer.effort", "tool.awaiting_user", "tool.completed_hidden", "tool.t2_loader_needs_snapshot", "tool.t2_loader_shape", "tool.t2_loader_output_invalid", "tool.execution_uncertain", "tool.input_invalid", "tool.input_invalid_after_mutate", "tool.output_invalid_after_mutate", "tool.handler_no_output", "tool.did_not_run", "tool.page_no_answer", "tool.page_timed_out", "tool.output_invalid", "tool.not_loaded_t2", "tool.wait_unknown", "tool.wait_cycle", "tool.not_visible", "tool.builtin_not_enabled", "tool.provider_native", "tool.not_registered", "tool.builtin_needs_snapshot", "tool.not_allowed", "tool.not_eligible", "tool.unsupported_type", "tool.agent_call_limit", "tool.agent_failed", "tool.activity_failed", "protocol.not_served", "protocol.profile_type", "protocol.options", "protocol.option_clash", "protocol.base_options", "protocol.id_clash", "protocol.ws_headers", "protocol.builtin_unwritten", "protocol.provider_missing", "protocol.connection", "protocol.part_schema", "protocol.part_invalid", "protocol.key_invalid", "protocol.no_key", "protocol.reply_shape", "protocol.endpoint_url", "protocol.endpoint_host", "protocol.http", "protocol.no_operation", "protocol.definition", "protocol.provider_id_clash", "protocol.history_needed"];
4
+ export declare const LABEL_KEYS: readonly ["provider.decision_failed", "provider.decision_unavailable", "provider.decision_model_missing", "provider.decision_ended", "provider.decision_images_off", "provider.decision_box_model", "provider.budget_exhausted", "provider.adapter_version", "provider.adapter_methods", "provider.adapter_schemas", "provider.fallback_slot", "provider.model_fallback_slot", "provider.host_model", "provider.turn_profile", "provider.model_binding_missing", "provider.session_closed", "provider.settled_call", "provider.session_unavailable", "provider.live_input", "provider.tool_cancel_missing", "provider.kernel_gate", "provider.event_invalid", "provider.response_identity", "provider.credential_resolution", "provider.credential_shape", "provider.stored_continuation", "provider.continuation_mismatch", "provider.continuation_contract", "provider.stateless_history", "provider.events_after_completion", "provider.duplicate_call", "provider.tool_cancel_live", "provider.complete_unavailable", "provider.parallel_tools", "provider.binding_identity", "provider.profile_type", "provider.event_type", "provider.feature", "provider.input_kind", "provider.output_kind", "provider.builtin", "provider.credential_missing", "provider.profile_configuration", "continue.instruction", "canary.bind_note", "user_data.note", "context.note", "taint.blocked", "taint.reason_steered", "taint.reason_tainted", "taint.destination_blocked", "taint.destination_confirm", "advisory.notice_elevated", "advisory.notice_high", "advisory.guidance", "attachments.too_many_files", "attachments.too_many_of_kind", "attachments.file_too_large", "attachments.turn_too_large", "attachments.not_accepted", "attachments.mime_not_allowed", "input.too_long", "attachments.limits_unconfigured", "quota.exhausted", "quota.busy", "error.config", "error.request", "error.input", "error.action", "error.auth", "error.rate_limit", "error.busy", "error.unsupported", "error.unavailable", "error.bad_response", "error.network", "error.timeout", "error.safety", "error.blocked", "error.declined", "error.failed", "error.cancelled", "error.internal", "repair.default_guidance", "repair.items_guidance", "repair.prompt_header", "repair.prompt_intro", "repair.prompt_instructions", "repair.history_heading", "repair.section_previous_output", "repair.section_validator_rejection", "repair.section_repair_guidance", "repair.section_instructions", "compaction.request", "compaction.tool_call", "compaction.tool_result", "decision.box_image", "detect.blocked", "detect.call_blocked", "detect.output_blocked", "detect.shown_blocked", "detect.hint.ids", "detect.hint.financial", "detect.hint.network", "detect.hint.credentials", "detect.hint.injection", "detect.hint.tool_instructions", "detect.hint.canary_leak", "detect.hint.prompt_leak", "detect.hint.marker_leak", "detect.hint.ungiven_images", "detect.hint.ungiven_links", "detect.hint.tool_leak", "detect.hint.own", "egress.default_repair_guidance", "egress.refusal", "egress.refusal_cut", "egress.rejection", "egress.rejection_found", "egress.hint_unscannable", "egress.hint_provider_tool_leak", "thought.omitted_image", "thought.omitted_link", "thought.omitted_instructions", "session.abandon_gated", "session.tool_denied", "session.tool_aborted", "session.sign_in", "sign_in.link", "sign_in.pending", "sign_in.done", "sign_in.declined", "sign_in.expired", "sign_in.out_of_scope", "session.gate_expired", "session.provider_state_unowned", "session.conversation_unknown", "session.turn_ended", "session.gate_pending", "session.part_skipped", "live.session_ended", "voice.unsupported", "voice.permission", "voice.unavailable", "voice.failed", "voice.empty", "stop.cancelled", "stop.interrupted", "stop.length", "stop.filtered", "stop.provider_error", "stop.stream_incomplete", "stop.step_limit", "composer.model", "composer.effort", "tool.awaiting_user", "tool.completed_hidden", "tool.t2_loader_needs_snapshot", "tool.t2_loader_shape", "tool.t2_loader_output_invalid", "tool.execution_uncertain", "tool.input_invalid", "tool.input_invalid_after_mutate", "tool.output_invalid_after_mutate", "tool.handler_no_output", "tool.did_not_run", "tool.page_no_answer", "tool.page_timed_out", "tool.output_invalid", "tool.not_loaded_t2", "tool.wait_unknown", "tool.wait_cycle", "tool.not_visible", "tool.builtin_not_enabled", "tool.provider_native", "tool.not_registered", "tool.builtin_needs_snapshot", "tool.not_allowed", "tool.not_eligible", "tool.unsupported_type", "tool.agent_call_limit", "tool.agent_failed", "tool.activity_failed", "protocol.not_served", "protocol.profile_type", "protocol.options", "protocol.option_clash", "protocol.base_options", "protocol.id_clash", "protocol.ws_headers", "protocol.builtin_unwritten", "protocol.provider_missing", "protocol.connection", "protocol.part_schema", "protocol.part_invalid", "protocol.key_invalid", "protocol.no_key", "protocol.reply_shape", "protocol.endpoint_url", "protocol.endpoint_host", "protocol.http", "protocol.no_operation", "protocol.definition", "protocol.provider_id_clash", "protocol.history_needed"];
5
5
  /** One of {@linkcode LABEL_KEYS}. */
6
6
  export type LabelKey = (typeof LABEL_KEYS)[number];
7
7
  /**
@@ -10,7 +10,7 @@ export type LabelKey = (typeof LABEL_KEYS)[number];
10
10
  * puts into history. Only these overrides travel to the browser; the rest
11
11
  * (repair prompts, canary, taint, egress) stay on the host.
12
12
  */
13
- export declare const CLIENT_LABEL_KEYS: readonly ["attachments.too_many_files", "attachments.too_many_of_kind", "attachments.file_too_large", "attachments.turn_too_large", "attachments.not_accepted", "attachments.mime_not_allowed", "input.too_long", "attachments.limits_unconfigured", "detect.blocked", "error.config", "error.request", "error.input", "error.action", "error.auth", "error.rate_limit", "error.unsupported", "error.unavailable", "error.bad_response", "error.network", "error.timeout", "error.safety", "error.blocked", "error.declined", "error.failed", "error.cancelled", "error.internal", "session.abandon_gated", "session.tool_denied", "session.sign_in", "session.gate_expired", "session.turn_ended", "session.gate_pending", "session.part_skipped", "live.session_ended", "tool.awaiting_user", "tool.completed_hidden", "tool.execution_uncertain", "voice.unsupported", "voice.permission", "voice.unavailable", "voice.failed", "voice.empty", "stop.cancelled", "stop.interrupted", "stop.length", "stop.filtered", "stop.provider_error", "stop.stream_incomplete", "stop.step_limit", "composer.model", "composer.effort"];
13
+ export declare const CLIENT_LABEL_KEYS: readonly ["attachments.too_many_files", "attachments.too_many_of_kind", "attachments.file_too_large", "attachments.turn_too_large", "attachments.not_accepted", "attachments.mime_not_allowed", "input.too_long", "attachments.limits_unconfigured", "detect.blocked", "error.config", "error.request", "error.input", "error.action", "error.auth", "error.rate_limit", "error.busy", "error.unsupported", "error.unavailable", "error.bad_response", "error.network", "error.timeout", "error.safety", "error.blocked", "error.declined", "error.failed", "error.cancelled", "error.internal", "session.abandon_gated", "session.tool_denied", "session.sign_in", "session.gate_expired", "session.turn_ended", "session.gate_pending", "session.part_skipped", "live.session_ended", "tool.awaiting_user", "tool.completed_hidden", "tool.execution_uncertain", "voice.unsupported", "voice.permission", "voice.unavailable", "voice.failed", "voice.empty", "stop.cancelled", "stop.interrupted", "stop.length", "stop.filtered", "stop.provider_error", "stop.stream_incomplete", "stop.step_limit", "composer.model", "composer.effort"];
14
14
  /** One of {@linkcode CLIENT_LABEL_KEYS}. */
15
15
  export type ClientLabelKey = (typeof CLIENT_LABEL_KEYS)[number];
16
16
  /** Host-supplied replacement templates, `{param}` placeholders included. */
@@ -67,12 +67,14 @@ export const LABEL_KEYS = [
67
67
  'input.too_long',
68
68
  'attachments.limits_unconfigured',
69
69
  'quota.exhausted',
70
+ 'quota.busy',
70
71
  'error.config',
71
72
  'error.request',
72
73
  'error.input',
73
74
  'error.action',
74
75
  'error.auth',
75
76
  'error.rate_limit',
77
+ 'error.busy',
76
78
  'error.unsupported',
77
79
  'error.unavailable',
78
80
  'error.bad_response',
@@ -229,6 +231,7 @@ export const CLIENT_LABEL_KEYS = [
229
231
  'error.action',
230
232
  'error.auth',
231
233
  'error.rate_limit',
234
+ 'error.busy',
232
235
  'error.unsupported',
233
236
  'error.unavailable',
234
237
  'error.bad_response',
@@ -382,12 +385,14 @@ const DEFAULTS = {
382
385
  'quota.exhausted': (params) => params.perDay === 1
383
386
  ? "You've reached today's limit of 1 message."
384
387
  : `You've reached today's limit of ${String(params.perDay)} messages.`,
388
+ 'quota.busy': 'Sorry, your last message is still being answered. Please wait for it to finish.',
385
389
  'error.config': 'Sorry, something went wrong.',
386
390
  'error.request': 'Sorry, something went wrong.',
387
391
  'error.input': "Sorry, that file can't be used here.",
388
392
  'error.action': "Sorry, that isn't available here.",
389
393
  'error.auth': "Sorry, the assistant can't connect at the moment.",
390
394
  'error.rate_limit': 'Sorry, the usage limit has been reached. Please try again later.',
395
+ 'error.busy': 'Sorry, the model is busy right now. Please try again in a moment.',
391
396
  'error.unsupported': "Sorry, that isn't something the assistant can do.",
392
397
  'error.unavailable': "Sorry, the model isn't available at the moment. Please try again shortly.",
393
398
  'error.bad_response': "Sorry, that reply didn't come through properly. Please try again.",
@@ -592,14 +597,16 @@ export const LABEL_NOTES = {
592
597
  'input.too_long': 'Shown to the user when one text a turn sends, such as the message or a text file, is longer than Theorem reads. Takes {maxChars}.',
593
598
  'attachments.limits_unconfigured': 'Shown to the user who sends a file to an agent whose file limits are not set.',
594
599
  'quota.exhausted': 'Shown to the user who has used up the daily message cap. Takes {perDay}.',
600
+ 'quota.busy': 'Shown to the user who sends a message while their last one is still being answered (`quotaBusy`).',
595
601
  'error.config': "Shown to the user when the turn fails because the agent's profile, a tool, or a schema is set up wrong. Takes {tool} when one step failed.",
596
602
  'error.request': 'Shown to the user when the turn fails because the host called Theorem wrongly. Takes {tool} when one step failed.',
597
603
  'error.input': 'Shown to the user when they sent something the agent does not accept. Takes {tool} when one step failed.',
598
604
  'error.action': 'Shown to the user when they asked for something the agent does not allow. Takes {tool} when one step failed.',
599
605
  'error.auth': 'Shown to the user when a model key is missing or rejected, or its account cannot be billed. Takes {tool} when one step failed.',
600
606
  'error.rate_limit': 'Shown to the user when the model provider reports too many requests, or a quota is used up. Takes {tool} when one step failed.',
607
+ 'error.busy': 'Shown to the user when the model provider is up but too busy for the call (HTTP 503 or 529). Takes {tool} when one step failed.',
601
608
  'error.unsupported': 'Shown to the user when the model or its route cannot serve the request. Takes {tool} when one step failed.',
602
- 'error.unavailable': 'Shown to the user when the model provider is down or overloaded. Takes {tool} when one step failed.',
609
+ 'error.unavailable': 'Shown to the user when the model provider is down. Takes {tool} when one step failed.',
603
610
  'error.bad_response': 'Shown to the user when the model provider answers with something that cannot be used. Takes {tool} when one step failed.',
604
611
  'error.network': 'Shown to the user when the request never reached the model provider. Takes {tool} when one step failed.',
605
612
  'error.timeout': 'Shown to the user when the model takes longer than the host allows. Takes {tool} when one step failed.',
@@ -36,7 +36,7 @@ export type { ProgressiveYieldGate, ProgressiveYieldGateOptions, ProgressiveYiel
36
36
  export { createOutboundProgressiveGate, createProgressiveYieldGate } from './progressive-yield.js';
37
37
  export { PROMPT_ECHO_WORDS, scanTextForPromptEcho } from './prompt-echo.js';
38
38
  export type { QuotaSlotStatus } from './quota.js';
39
- export { clientIp, quotaExhausted, releaseSlot, resetSlots, skipQuota, takeSlot, } from './quota.js';
39
+ export { clientIp, quotaBusy, quotaExhausted, releaseSlot, resetSlots, skipQuota, takeSlot, } from './quota.js';
40
40
  export type { GuardrailRule } from './rules.js';
41
41
  export { DETECT_RULES, detectRule, EGRESS_RULES, NETWORK_RULES, TOOL_RULES, } from './rules.js';
42
42
  export type { SanitizedTurnRequest } from './sanitize.js';
@@ -23,7 +23,7 @@ export { abortLiveOutboundTurn, createLiveOutboundGateSession, finalizeLiveOutbo
23
23
  export { resolveGuardrailPolicy } from './policy.js';
24
24
  export { createOutboundProgressiveGate, createProgressiveYieldGate } from './progressive-yield.js';
25
25
  export { PROMPT_ECHO_WORDS, scanTextForPromptEcho } from './prompt-echo.js';
26
- export { clientIp, quotaExhausted, releaseSlot, resetSlots, skipQuota, takeSlot, } from './quota.js';
26
+ export { clientIp, quotaBusy, quotaExhausted, releaseSlot, resetSlots, skipQuota, takeSlot, } from './quota.js';
27
27
  export { DETECT_RULES, detectRule, EGRESS_RULES, NETWORK_RULES, TOOL_RULES, } from './rules.js';
28
28
  export { sanitizeHistory, sanitizeProjectId, sanitizeTurnRequest, sanitizeTurnRequestWithEvents, } from './sanitize.js';
29
29
  export { sensitiveSpans } from './sensitive.js';
@@ -15,7 +15,9 @@ declare function takeSlot(profile: Profile, ip: string, now: number): QuotaSlotS
15
15
  declare function releaseSlot(profile: Profile, ip: string): void;
16
16
  /** `undefined` when the profile has no quota configured. */
17
17
  declare function quotaExhausted(profile: Profile): TheoremError | undefined;
18
+ /** A turn already holds this client's slot (`takeSlot` returned `busy`): worded by the labels' `quota.busy`. */
19
+ declare function quotaBusy(profile: Profile): TheoremError;
18
20
  /** Clears all process-local quota counters; intended for tests or host resets. */
19
21
  declare function resetSlots(): void;
20
22
  export type { QuotaSlotStatus };
21
- export { clientIp, quotaExhausted, releaseSlot, resetSlots, skipQuota, takeSlot };
23
+ export { clientIp, quotaBusy, quotaExhausted, releaseSlot, resetSlots, skipQuota, takeSlot };
@@ -73,8 +73,13 @@ function quotaExhausted(profile) {
73
73
  return new TheoremError('rate_limit', `${profile.id} used its daily quota of ${quota.perDay} turns`, // labels-exempt: internal diagnostic; the user reads the copy key
74
74
  { copy: { key: 'quota.exhausted', params: { perDay: quota.perDay } } });
75
75
  }
76
+ /** A turn already holds this client's slot (`takeSlot` returned `busy`): worded by the labels' `quota.busy`. */
77
+ function quotaBusy(profile) {
78
+ return new TheoremError('rate_limit', `${profile.id} is already running a turn for this client`, // labels-exempt: internal diagnostic; the user reads the copy key
79
+ { copy: { key: 'quota.busy' } });
80
+ }
76
81
  /** Clears all process-local quota counters; intended for tests or host resets. */
77
82
  function resetSlots() {
78
83
  slots.clear();
79
84
  }
80
- export { clientIp, quotaExhausted, releaseSlot, resetSlots, skipQuota, takeSlot };
85
+ export { clientIp, quotaBusy, quotaExhausted, releaseSlot, resetSlots, skipQuota, takeSlot };
@@ -5,7 +5,7 @@ export type { ErrorCopies, ErrorCopy };
5
5
  * the builder in code and traces (`errorKind`, `error.type`), the user through
6
6
  * the kind's wording (`error.<kind>` in the labels).
7
7
  */
8
- export declare const ERROR_KINDS: readonly ["config", "request", "input", "action", "auth", "rate_limit", "unsupported", "unavailable", "bad_response", "network", "timeout", "safety", "blocked", "declined", "failed", "cancelled", "internal"];
8
+ export declare const ERROR_KINDS: readonly ["config", "request", "input", "action", "auth", "rate_limit", "busy", "unsupported", "unavailable", "bad_response", "network", "timeout", "safety", "blocked", "declined", "failed", "cancelled", "internal"];
9
9
  /** Why an operation failed; the user's wording and the HTTP status follow from it. */
10
10
  export type ErrorKind = (typeof ERROR_KINDS)[number];
11
11
  /** Options for a `TheoremError`: the cause and, optionally, wording for the user. */
@@ -17,9 +17,11 @@ export const ERROR_KINDS = [
17
17
  'auth',
18
18
  /** Too many requests, or a quota used up. */
19
19
  'rate_limit',
20
+ /** The provider is up but too busy for the call; another key or a moment later may serve it. */
21
+ 'busy',
20
22
  /** The model or route cannot serve this request. */
21
23
  'unsupported',
22
- /** The provider is down or overloaded. */
24
+ /** The provider is down. */
23
25
  'unavailable',
24
26
  /** The provider answered with something that cannot be used. */
25
27
  'bad_response',
@@ -14,6 +14,7 @@ const STATUS_BY_KIND = {
14
14
  action: 403,
15
15
  auth: 401,
16
16
  rate_limit: HTTP_BUSY,
17
+ busy: 503,
17
18
  unsupported: 422,
18
19
  unavailable: 503,
19
20
  bad_response: 502,
@@ -370,6 +370,7 @@ function decisionProviderError(error) {
370
370
  auth: 'authentication',
371
371
  rate_limit: 'rate_limited',
372
372
  network: 'network',
373
+ busy: 'unavailable',
373
374
  unavailable: 'unavailable',
374
375
  timeout: 'timeout',
375
376
  cancelled: 'cancelled',
@@ -135,6 +135,7 @@ function releasesNow(event, control, state, buffer) {
135
135
  }
136
136
  // why: Every other kind is the provider's answer to the request itself (it refused a part, a key, a setting); the same call sent again gets the same answer.
137
137
  const PASSING_ERROR_KINDS = new Set([
138
+ 'busy',
138
139
  'unavailable',
139
140
  'bad_response',
140
141
  'network',
@@ -309,7 +309,7 @@ function isKind(error, kind) {
309
309
  }
310
310
  /**
311
311
  * Runs one call with the kernel's retries, all before the first event: once on the fallback slot
312
- * when the first key is out of quota, and once after an `auth` refusal with the minted token
312
+ * when the first key is out of quota or its model is busy, and once after an `auth` refusal with the minted token
313
313
  * dropped. A fallback starts clean: it may be another account, so it never sends the first key's
314
314
  * state.
315
315
  */
@@ -330,7 +330,7 @@ async function* withKeys(setup, run) {
330
330
  catch (error) {
331
331
  if (started)
332
332
  throw error;
333
- if (isKind(error, 'rate_limit') &&
333
+ if ((isKind(error, 'rate_limit') || isKind(error, 'busy')) &&
334
334
  fallback &&
335
335
  slot !== fallback &&
336
336
  holdsKey(setup, fallback)) {
@@ -513,7 +513,7 @@ export const PROFILE_FIELDS = withScopeAndPresence({
513
513
  'models.*.protocol': field('string', 'The API shape this model is spoken to in.'),
514
514
  'models.*.provider': field('string', 'The service that runs the model or routes requests to one, such as Gemini or OpenRouter. Its registration supplies the endpoint and authentication.'),
515
515
  'models.*.keySlot': field('KeySlot', "The key slot this model's calls are signed with, instead of the provider's. Use it to bill one model to a different key. Absent: the provider's primary slot."),
516
- 'models.*.fallbackKeySlot': field('KeySlot', "The key slot tried once after a rate limit on the primary slot, instead of the provider's fallback. Must differ from the primary slot. Absent: the provider's fallback slot, if it has one."),
516
+ 'models.*.fallbackKeySlot': field('KeySlot', "The key slot tried once after a rate limit or a busy model on the primary slot, instead of the provider's fallback. Must differ from the primary slot. Absent: the provider's fallback slot, if it has one."),
517
517
  'models.*.options': field('JsonObject', "Settings only this model's protocol has, such as a task type or vector size. Each is checked when the profile loads. Any left out keep the provider's default."),
518
518
  providerContinuation: field('ProviderContinuationPolicy', 'Who holds the conversation a saved provider state continues.'),
519
519
  'providerContinuation.history': field("'host' | 'stateless' | 'provider'", 'Who holds the conversation a checkpoint continues: the host passes history back, the provider keeps it and the checkpoint travels alone, or the profile is stateless and every request stands alone.', ['host', 'stateless', 'provider']),
@@ -96,7 +96,8 @@ const ERROR_KIND_OPTIONS = {
96
96
  },
97
97
  rate_limit: { label: 'Rate limit', doc: 'Too many requests, or a quota used up.' },
98
98
  unsupported: { label: 'Unsupported', doc: 'The model or route cannot serve this request.' },
99
- unavailable: { label: 'Unavailable', doc: 'The provider is down or overloaded.' },
99
+ busy: { label: 'Busy', doc: 'The provider is up but too busy for the call.' },
100
+ unavailable: { label: 'Unavailable', doc: 'The provider is down.' },
100
101
  bad_response: {
101
102
  label: 'Unusable response',
102
103
  doc: 'The provider answered with something that cannot be used.',
@@ -1,9 +1,8 @@
1
1
  import { z } from 'zod';
2
- import { kindOfHttpStatus, TheoremError } from '../../guardrails/error.js';
3
2
  import { asRecord } from '../../kernel/engine/record.js';
4
3
  import { jsonObjectSchema } from '../../kernel/json.js';
5
4
  import { defineProtocol, } from '../../kernel/protocols/mod.js';
6
- import { readGeminiApiError } from '../google/api-error.js';
5
+ import { readGeminiApiError, statedOrStatusError } from '../google/api-error.js';
7
6
  import { googleBuiltins } from '../google/builtins.js';
8
7
  import { thoughtSignature, toInteractionsBody, } from '../google/interactions/framing.js';
9
8
  import { finalizeStructured, foldBody, foldPayload, missingMediaError, newStreamFold, openStepEvents, } from '../google/interactions/stream.js';
@@ -68,10 +67,8 @@ export const geminiInteractions = defineProtocol({
68
67
  continuation: { version: 1, schema: state, keyOptions: ['store', 'persistViaInteractionId'] },
69
68
  reasoning: thoughtSignature,
70
69
  error(failure) {
71
- const kind = kindOfHttpStatus(failure.status);
72
70
  const record = asRecord(failure.body);
73
- const stated = record ? readGeminiApiError(record) : null;
74
- return new TheoremError(stated?.kind === 'auth' ? 'auth' : kind, stated?.message ?? `Gemini HTTP ${failure.status}`);
71
+ return statedOrStatusError(record ? readGeminiApiError(record) : null, failure.status, `Gemini HTTP ${failure.status}`);
75
72
  },
76
73
  create(context) {
77
74
  return {
@@ -6,3 +6,5 @@ import { TheoremError } from '../../guardrails/error.js';
6
6
  export declare function readGeminiApiError(record: Record<string, unknown>): TheoremError | null;
7
7
  /** The kind is the HTTP status's, unless the body names a refused key. */
8
8
  export declare function readNonOkError(response: Response): Promise<TheoremError>;
9
+ /** A non-OK reply's error: the body's own when it names a refused key, else the status's with the body's message. */
10
+ export declare function statedOrStatusError(stated: TheoremError | null, status: number, fallback: string): TheoremError;
@@ -72,13 +72,15 @@ const BLOCKED_CODES = new Set([
72
72
  * codes (`malformed_function_call`, `no_image`, …) and any code Google adds later
73
73
  * are `bad_response`.
74
74
  */
75
- function kindOfNamedCode(code) {
75
+ function namedCodeError(code, message) {
76
76
  if (code === 'cancelled')
77
- return 'cancelled';
77
+ return new TheoremError('cancelled', message);
78
78
  if (BLOCKED_CODES.has(code))
79
- return 'safety';
79
+ return new TheoremError('safety', message);
80
80
  const status = NAMED_CODE_STATUS[code];
81
- return status === undefined ? 'bad_response' : kindOfHttpStatus(status);
81
+ return status === undefined
82
+ ? new TheoremError('bad_response', message)
83
+ : new TheoremError(kindOfHttpStatus(status), message);
82
84
  }
83
85
  /**
84
86
  * Interactions (SSE `error` rows, non-OK bodies) and Live (error frames) both send
@@ -90,28 +92,34 @@ export function readGeminiApiError(record) {
90
92
  return null;
91
93
  }
92
94
  const { code, message, status, details } = error;
93
- const kind = refusesKey(details)
94
- ? 'auth'
95
- : typeof code === 'number' && code >= HTTP_STATUS_MIN && code <= HTTP_STATUS_MAX
96
- ? kindOfHttpStatus(code)
97
- : typeof code === 'string'
98
- ? kindOfNamedCode(code)
99
- : 'bad_response';
100
- if (typeof message !== 'string' || message.length === 0) {
101
- return new TheoremError(kind, 'Gemini returned an error.');
95
+ const text = typeof message !== 'string' || message.length === 0
96
+ ? 'Gemini returned an error.'
97
+ : typeof status === 'string'
98
+ ? `${status}: ${message}`
99
+ : message;
100
+ if (refusesKey(details))
101
+ return new TheoremError('auth', text);
102
+ if (typeof code === 'number' && code >= HTTP_STATUS_MIN && code <= HTTP_STATUS_MAX) {
103
+ return new TheoremError(kindOfHttpStatus(code), text);
102
104
  }
103
- return new TheoremError(kind, typeof status === 'string' ? `${status}: ${message}` : message);
105
+ return typeof code === 'string'
106
+ ? namedCodeError(code, text)
107
+ : new TheoremError('bad_response', text);
104
108
  }
105
109
  /** The kind is the HTTP status's, unless the body names a refused key. */
106
110
  export async function readNonOkError(response) {
107
- const kind = kindOfHttpStatus(response.status);
108
111
  const text = await response.text().catch(() => '');
109
112
  if (!text.trim()) {
110
- return new TheoremError(kind, `HTTP ${response.status}`);
113
+ return new TheoremError(kindOfHttpStatus(response.status), `HTTP ${response.status}`);
111
114
  }
112
115
  const parsed = parseRecord(text);
113
- const stated = parsed ? readGeminiApiError(parsed) : null;
114
- return new TheoremError(stated?.kind === 'auth' ? 'auth' : kind, stated?.message ?? `Gemini HTTP ${response.status}: ${text}`);
116
+ return statedOrStatusError(parsed ? readGeminiApiError(parsed) : null, response.status, `Gemini HTTP ${response.status}: ${text}`);
117
+ }
118
+ /** A non-OK reply's error: the body's own when it names a refused key, else the status's with the body's message. */
119
+ export function statedOrStatusError(stated, status, fallback) {
120
+ if (stated?.kind === 'auth')
121
+ return stated;
122
+ return new TheoremError(kindOfHttpStatus(status), stated?.message ?? fallback);
115
123
  }
116
124
  function parseRecord(text) {
117
125
  try {
@@ -8,7 +8,7 @@ const CLOSE_KINDS = {
8
8
  1007: 'unsupported',
9
9
  1008: 'unsupported',
10
10
  1011: 'unavailable',
11
- 1013: 'unavailable',
11
+ 1013: 'busy',
12
12
  };
13
13
  /**
14
14
  * Google refuses an over-quota Live key with code 1011 (`unavailable`) and a reason
@@ -4,6 +4,7 @@ export type Wait = (ms: number, signal?: AbortSignal | null) => Promise<void>;
4
4
  export declare const WAIT_BUDGET_MS = 5000;
5
5
  export declare const waitDefault: Wait;
6
6
  export declare function isTransientHttp(status: number): boolean;
7
+ /** A rejected send that never got a response: a network failure, by `networkError`'s rule. An abort, a timeout, or any other kind is not. */
7
8
  export declare function isTransientThrown(err: unknown): boolean;
8
9
  export declare function backoffMs(attempt: number): number;
9
10
  /** `retry-after-ms`, else `Retry-After` in seconds or as a date; undefined when absent or unreadable. */
@@ -1,12 +1,12 @@
1
- import { isAbortError, isTimeoutError } from '../../guardrails/error.js';
1
+ import { TheoremError } from '../../guardrails/error.js';
2
+ import { networkError } from './upstream-tap.js';
2
3
  const BACKOFF_MS = [1000, 2000, 4000, 8000];
3
4
  /** All the waiting one call may do between tries; a wait that would pass it sends the last answer back. */
4
5
  export const WAIT_BUDGET_MS = 5000;
5
6
  /** The most tries after the first: a `Retry-After: 0` costs no wait, so the budget alone never ends it. */
6
7
  const MAX_RETRIES = BACKOFF_MS.length;
7
8
  const MS_PER_SECOND = 1000;
8
- const TRANSIENT_STATUSES = new Set([408, 429, 500, 502, 503, 504]);
9
- const TRANSIENT_THROWN_RE = /name resolution|dns|econnreset|econnrefused|etimedout|connection (?:refused|reset)|error sending request|network|fetch failed|temporarily unavailable|socket|503|502|504/i;
9
+ const TRANSIENT_STATUSES = new Set([408, 429, 500, 502, 503, 504, 529]);
10
10
  export const waitDefault = (ms, signal) => new Promise((resolve, reject) => {
11
11
  if (signal?.aborted) {
12
12
  reject(abortReason(signal));
@@ -28,11 +28,10 @@ function abortReason(signal) {
28
28
  export function isTransientHttp(status) {
29
29
  return TRANSIENT_STATUSES.has(status);
30
30
  }
31
+ /** A rejected send that never got a response: a network failure, by `networkError`'s rule. An abort, a timeout, or any other kind is not. */
31
32
  export function isTransientThrown(err) {
32
- if (isAbortError(err) || isTimeoutError(err)) {
33
- return false;
34
- }
35
- return TRANSIENT_THROWN_RE.test(String(err));
33
+ const failure = networkError(err);
34
+ return failure instanceof TheoremError && failure.kind === 'network';
36
35
  }
37
36
  export function backoffMs(attempt) {
38
37
  return BACKOFF_MS[Math.min(attempt, BACKOFF_MS.length - 1)];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theoremjs/agents",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "A flat TypeScript agent kernel for typed profiles, deterministic turn execution, registered tools, provider adapters, guardrails, and host-injected traces.",
5
5
  "keywords": [
6
6
  "agent",