@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 +2 -1
- package/esm/mod.d.ts +1 -1
- package/esm/mod.js +1 -1
- package/esm/src/guardrails/error.d.ts +1 -1
- package/esm/src/guardrails/error.js +3 -1
- package/esm/src/guardrails/labels.d.ts +2 -2
- package/esm/src/guardrails/labels.js +8 -1
- package/esm/src/guardrails/mod.d.ts +1 -1
- package/esm/src/guardrails/mod.js +1 -1
- package/esm/src/guardrails/quota.d.ts +3 -1
- package/esm/src/guardrails/quota.js +6 -1
- package/esm/src/guardrails/theorem-error.d.ts +1 -1
- package/esm/src/guardrails/theorem-error.js +3 -1
- package/esm/src/host/reply.js +1 -0
- package/esm/src/kernel/engine/decision.js +1 -0
- package/esm/src/kernel/engine/runner/steps.js +1 -0
- package/esm/src/kernel/protocols/runtime.js +2 -2
- package/esm/src/kernel/schema.js +1 -1
- package/esm/src/observability/trace-catalog.js +2 -1
- package/esm/src/providers/gemini/interactions-protocol.js +2 -5
- package/esm/src/providers/google/api-error.d.ts +2 -0
- package/esm/src/providers/google/api-error.js +26 -18
- package/esm/src/providers/google/live/stream.js +1 -1
- package/esm/src/providers/shared/retry.d.ts +1 -0
- package/esm/src/providers/shared/retry.js +6 -7
- package/package.json +1 -1
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
|
|
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
|
|
24
|
+
/** The provider is down. */
|
|
23
25
|
'unavailable',
|
|
24
26
|
/** The provider answered with something that cannot be used. */
|
|
25
27
|
'bad_response',
|
package/esm/src/host/reply.js
CHANGED
|
@@ -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)) {
|
package/esm/src/kernel/schema.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
94
|
-
? '
|
|
95
|
-
: typeof
|
|
96
|
-
?
|
|
97
|
-
:
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
if (typeof
|
|
101
|
-
return new TheoremError(
|
|
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
|
|
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(
|
|
113
|
+
return new TheoremError(kindOfHttpStatus(response.status), `HTTP ${response.status}`);
|
|
111
114
|
}
|
|
112
115
|
const parsed = parseRecord(text);
|
|
113
|
-
|
|
114
|
-
|
|
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 {
|
|
@@ -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 {
|
|
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
|
-
|
|
33
|
-
|
|
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.
|
|
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",
|