@intx/inference 0.1.2 → 0.2.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.
Files changed (97) hide show
  1. package/LICENSE +176 -0
  2. package/dist/actions.d.ts +16 -0
  3. package/dist/actions.js +200 -0
  4. package/dist/adapter.d.ts +38 -0
  5. package/dist/adapter.js +31 -0
  6. package/dist/assembly.d.ts +68 -0
  7. package/dist/assembly.js +132 -0
  8. package/dist/audit-collector.d.ts +10 -0
  9. package/dist/audit-collector.js +139 -0
  10. package/dist/auth.d.ts +24 -0
  11. package/{src/auth.ts → dist/auth.js} +13 -19
  12. package/dist/authz-extension.d.ts +32 -0
  13. package/dist/authz-extension.js +100 -0
  14. package/dist/correlation.d.ts +25 -0
  15. package/dist/correlation.js +32 -0
  16. package/dist/default-director.d.ts +111 -0
  17. package/dist/default-director.js +199 -0
  18. package/dist/director.d.ts +6 -0
  19. package/dist/director.js +56 -0
  20. package/dist/errors.d.ts +18 -0
  21. package/dist/errors.js +83 -0
  22. package/dist/gates.d.ts +27 -0
  23. package/dist/gates.js +80 -0
  24. package/dist/harness.d.ts +147 -0
  25. package/dist/harness.js +1319 -0
  26. package/dist/index.d.ts +37 -0
  27. package/dist/index.js +21 -0
  28. package/dist/manifest.d.ts +31 -0
  29. package/dist/manifest.js +44 -0
  30. package/dist/providers/anthropic.d.ts +33 -0
  31. package/dist/providers/anthropic.js +670 -0
  32. package/dist/providers/google-genai-files.d.ts +48 -0
  33. package/dist/providers/google-genai-files.js +205 -0
  34. package/dist/providers/google-genai.d.ts +3 -0
  35. package/dist/providers/google-genai.js +1196 -0
  36. package/dist/providers/index.d.ts +38 -0
  37. package/dist/providers/index.js +56 -0
  38. package/dist/providers/openai.d.ts +3 -0
  39. package/dist/providers/openai.js +609 -0
  40. package/dist/reactor.d.ts +50 -0
  41. package/dist/reactor.js +920 -0
  42. package/dist/retry-policy.d.ts +31 -0
  43. package/{src/retry-policy.ts → dist/retry-policy.js} +41 -53
  44. package/dist/sse.d.ts +1 -0
  45. package/dist/sse.js +63 -0
  46. package/dist/state.d.ts +23 -0
  47. package/dist/state.js +100 -0
  48. package/dist/tool-name.d.ts +6 -0
  49. package/dist/tool-name.js +110 -0
  50. package/dist/transform.d.ts +11 -0
  51. package/dist/transform.js +117 -0
  52. package/dist/transforms/index.d.ts +2 -0
  53. package/dist/transforms/index.js +1 -0
  54. package/dist/transforms/size-cap.d.ts +12 -0
  55. package/dist/transforms/size-cap.js +80 -0
  56. package/dist/turns.d.ts +21 -0
  57. package/dist/turns.js +135 -0
  58. package/package.json +21 -6
  59. package/src/actions.ts +0 -245
  60. package/src/adapter.ts +0 -57
  61. package/src/assembly.test.ts +0 -728
  62. package/src/assembly.ts +0 -250
  63. package/src/audit-collector.test.ts +0 -332
  64. package/src/audit-collector.ts +0 -172
  65. package/src/auth.test.ts +0 -117
  66. package/src/authz-extension.test.ts +0 -269
  67. package/src/authz-extension.ts +0 -145
  68. package/src/correlation.ts +0 -61
  69. package/src/default-director.test.ts +0 -314
  70. package/src/default-director.ts +0 -344
  71. package/src/director.ts +0 -87
  72. package/src/errors.test.ts +0 -133
  73. package/src/errors.ts +0 -115
  74. package/src/gates.ts +0 -128
  75. package/src/harness.test.ts +0 -655
  76. package/src/harness.ts +0 -1571
  77. package/src/index.ts +0 -76
  78. package/src/providers/anthropic.test.ts +0 -771
  79. package/src/providers/anthropic.ts +0 -810
  80. package/src/providers/google-genai-files.ts +0 -289
  81. package/src/providers/google-genai.ts +0 -1518
  82. package/src/providers/openai.ts +0 -719
  83. package/src/providers/registry.ts +0 -33
  84. package/src/reactor.test.ts +0 -3660
  85. package/src/reactor.ts +0 -1058
  86. package/src/scheduler.test.ts +0 -41
  87. package/src/sse.test.ts +0 -133
  88. package/src/sse.ts +0 -76
  89. package/src/state.ts +0 -135
  90. package/src/transform.test.ts +0 -207
  91. package/src/transform.ts +0 -159
  92. package/src/transforms/index.ts +0 -2
  93. package/src/transforms/size-cap.test.ts +0 -172
  94. package/src/transforms/size-cap.ts +0 -110
  95. package/src/turns.ts +0 -54
  96. package/tsconfig.json +0 -4
  97. package/tsconfig.tsbuildinfo +0 -1
@@ -0,0 +1,199 @@
1
+ // Default conversational director — reference ReactorDirector implementation.
2
+ //
3
+ // Inbound-event → action map (see INFERENCE.md § Director Decision Function
4
+ // for the director contract and action-validation rules):
5
+ //
6
+ // message.received → infer
7
+ // inference.done (tools) → checkpoint + execute_tools
8
+ // tool.done → checkpoint + infer (re-infer with tool results)
9
+ // inference.done (no tools) → checkpoint + reply (connector sends the message)
10
+ // inference.error → checkpoint + reply (error message to user)
11
+ // abort → done
12
+ // reactor.gate.cleared → checkpoint + infer (resume after gate)
13
+ //
14
+ // The inference.done branch additionally runs the optional afterInferenceDone
15
+ // policy hook, whose continue/abort/halt decisions route independently of the
16
+ // event map above. See AfterInferenceDecision for that contract.
17
+ //
18
+ // The director never throws. Inference errors are surfaced to the user as a
19
+ // reply so the problem is visible, and the agent remains alive for retries.
20
+ import { getLogger } from "@intx/log";
21
+ const logger = getLogger(["interchange", "inference", "default-director"]);
22
+ function extractToolCalls(turn) {
23
+ const calls = [];
24
+ for (const block of turn.content) {
25
+ if (block.type === "tool_call") {
26
+ calls.push({
27
+ id: block.id,
28
+ name: block.name,
29
+ arguments: block.arguments,
30
+ });
31
+ }
32
+ }
33
+ return calls;
34
+ }
35
+ function extractTextContent(turn) {
36
+ // Both regular text and refusal blocks carry human-readable model
37
+ // output that the connector needs to surface — a refusal-only turn
38
+ // (OpenAI strict-mode policy decline) would otherwise route through
39
+ // the empty-response branch below and never reach the reply path,
40
+ // leaving the human waiting for an answer the model already
41
+ // declined to give. The structural "this was a refusal" signal is
42
+ // preserved at the persistence layer (event-collector emits a
43
+ // refusal turn-part); the reply path only needs the words.
44
+ const parts = [];
45
+ for (const block of turn.content) {
46
+ if (block.type === "text") {
47
+ parts.push(block.text);
48
+ }
49
+ else if (block.type === "refusal") {
50
+ parts.push(block.reason);
51
+ }
52
+ }
53
+ return parts.join("\n").trim();
54
+ }
55
+ const ERROR_PREAMBLE = {
56
+ credential_failure: "This agent could not complete your request due to a credential error",
57
+ quota_exhausted: "This agent could not complete your request because the API quota has been exhausted",
58
+ context_overflow: "This agent could not complete your request because the conversation exceeded the model's context limit",
59
+ retryable: "This agent encountered a temporary error communicating with the inference provider",
60
+ fatal: "This agent could not complete your request due to an unrecoverable inference error",
61
+ aborted: "This agent's inference request was aborted",
62
+ };
63
+ function formatInferenceError(error) {
64
+ const preamble = ERROR_PREAMBLE[error.category] ?? ERROR_PREAMBLE["fatal"];
65
+ const status = error.statusCode !== undefined ? ` [HTTP ${error.statusCode}]` : "";
66
+ return `${preamble}${status}: ${error.message}`;
67
+ }
68
+ export class DefaultDirector {
69
+ systemPrompt;
70
+ toolDefinitions;
71
+ policy;
72
+ // Track outstanding tool results so we only re-infer once per batch.
73
+ pendingToolResults = 0;
74
+ constructor(systemPrompt, toolDefinitions = [], policy = {}) {
75
+ this.systemPrompt = systemPrompt;
76
+ this.toolDefinitions = toolDefinitions;
77
+ this.policy = policy;
78
+ }
79
+ async decide(event, state, capabilities) {
80
+ switch (event.type) {
81
+ case "message.received": {
82
+ return capabilities.infer({
83
+ systemPrompt: this.systemPrompt,
84
+ tools: this.toolDefinitions,
85
+ });
86
+ }
87
+ case "inference.done": {
88
+ // The hook gates the entire inference.done branch (including
89
+ // tool extraction and the reactive-mode wait shortcut). An
90
+ // abort/halt from the policy drops any tool calls the model
91
+ // emitted in this turn; see AfterInferenceHook TSDoc for the
92
+ // implications.
93
+ if (this.policy.afterInferenceDone !== undefined) {
94
+ let decision;
95
+ try {
96
+ decision = await this.policy.afterInferenceDone(state, event.turn);
97
+ }
98
+ catch (cause) {
99
+ const message = cause instanceof Error ? cause.message : String(cause);
100
+ logger.error `afterInferenceDone policy threw: ${message}`;
101
+ decision = {
102
+ type: "abort",
103
+ reason: `afterInferenceDone policy threw: ${message}`,
104
+ };
105
+ }
106
+ if (decision.type === "abort") {
107
+ // A reply invites the next inbound message, but abort is
108
+ // terminal — the reactor rejects reply paired with done. The
109
+ // reason is therefore not surfaced on this path.
110
+ return [
111
+ capabilities.checkpoint("after-inference-abort"),
112
+ capabilities.done(),
113
+ ];
114
+ }
115
+ if (decision.type === "halt") {
116
+ // A reply already returns the reactor to waiting for the next
117
+ // inbound message, so no separate wait is needed (and the
118
+ // reactor rejects reply paired with wait).
119
+ return [
120
+ capabilities.checkpoint("after-inference-halt"),
121
+ capabilities.reply(decision.reason),
122
+ ];
123
+ }
124
+ // decision.type === "continue" — fall through.
125
+ }
126
+ const toolCalls = extractToolCalls(event.turn);
127
+ if (toolCalls.length > 0) {
128
+ this.pendingToolResults = toolCalls.length;
129
+ return [
130
+ capabilities.checkpoint("tool-execution"),
131
+ capabilities.executeTools(toolCalls, true),
132
+ ];
133
+ }
134
+ // No tool calls — the model is done reasoning for this turn.
135
+ if (this.policy.mode === "reactive") {
136
+ return [
137
+ capabilities.checkpoint("inference-done"),
138
+ capabilities.wait(),
139
+ ];
140
+ }
141
+ // Conversational agent: send reply via the connector.
142
+ const replyContent = extractTextContent(event.turn);
143
+ if (replyContent.length > 0) {
144
+ return [
145
+ capabilities.checkpoint("inference-done"),
146
+ capabilities.reply(replyContent),
147
+ ];
148
+ }
149
+ // Empty response (no text, no tool calls) — checkpoint and wait for
150
+ // the next inbound message. The reactor only shuts down on explicit
151
+ // stop (abort), never because the model produced an empty turn.
152
+ return [capabilities.checkpoint("inference-done"), capabilities.wait()];
153
+ }
154
+ case "tool.done": {
155
+ this.pendingToolResults--;
156
+ if (this.pendingToolResults > 0) {
157
+ return [];
158
+ }
159
+ if (this.policy.mode === "reactive") {
160
+ return [capabilities.checkpoint("tool-done"), capabilities.wait()];
161
+ }
162
+ // All tool results received — re-infer with complete context.
163
+ return [
164
+ capabilities.checkpoint("tool-done"),
165
+ capabilities.infer({
166
+ systemPrompt: this.systemPrompt,
167
+ tools: this.toolDefinitions,
168
+ }),
169
+ ];
170
+ }
171
+ case "inference.error": {
172
+ const statusDetail = event.error.statusCode !== undefined
173
+ ? ` [HTTP ${event.error.statusCode}]`
174
+ : "";
175
+ logger.error `Inference error in default director: ${event.error.message}${statusDetail} (category: ${event.error.category})`;
176
+ const userMessage = formatInferenceError(event.error);
177
+ return [
178
+ capabilities.checkpoint("inference-error"),
179
+ capabilities.reply(userMessage),
180
+ ];
181
+ }
182
+ case "reactor.gate.cleared": {
183
+ return [
184
+ capabilities.checkpoint("gate-cleared"),
185
+ capabilities.infer({
186
+ systemPrompt: this.systemPrompt,
187
+ tools: this.toolDefinitions,
188
+ }),
189
+ ];
190
+ }
191
+ case "abort": {
192
+ return capabilities.done();
193
+ }
194
+ }
195
+ }
196
+ }
197
+ export function createDefaultDirector(systemPrompt, toolDefinitions = [], policy = {}) {
198
+ return new DefaultDirector(systemPrompt, toolDefinitions, policy);
199
+ }
@@ -0,0 +1,6 @@
1
+ import type { ReactorCapabilities } from "@intx/types/runtime";
2
+ /**
3
+ * Builds a frozen capabilities object. The same instance is reused across
4
+ * calls since all methods are pure constructors.
5
+ */
6
+ export declare function createCapabilities(): ReactorCapabilities;
@@ -0,0 +1,56 @@
1
+ // Director interface types and capabilities factory.
2
+ //
3
+ // The capabilities object is passed to the director on every decision call.
4
+ // It provides a type-safe API for constructing reactor actions without
5
+ // requiring the director to import or construct action literals directly.
6
+ //
7
+ // (INFERENCE.md § Reactor Director › Core Director)
8
+ /**
9
+ * Builds a frozen capabilities object. The same instance is reused across
10
+ * calls since all methods are pure constructors.
11
+ */
12
+ export function createCapabilities() {
13
+ return {
14
+ infer(options) {
15
+ return {
16
+ type: "infer",
17
+ ...(options !== undefined ? { options } : {}),
18
+ };
19
+ },
20
+ executeTools(calls, parallel, addToHistory) {
21
+ return {
22
+ type: "execute_tools",
23
+ calls,
24
+ ...(parallel !== undefined ? { parallel } : {}),
25
+ ...(addToHistory !== undefined ? { addToHistory } : {}),
26
+ };
27
+ },
28
+ suspend(gate) {
29
+ return { type: "suspend", gate };
30
+ },
31
+ fork(mode, forkId) {
32
+ return { type: "fork", mode, forkId };
33
+ },
34
+ emit(eventType, data) {
35
+ return { type: "emit", eventType, data };
36
+ },
37
+ reply(content) {
38
+ return { type: "reply", content };
39
+ },
40
+ checkpoint(reason) {
41
+ return {
42
+ type: "checkpoint",
43
+ message: reason !== undefined ? `checkpoint: ${reason}` : "checkpoint",
44
+ };
45
+ },
46
+ compact(compactor, reason) {
47
+ return { type: "compact", compactor, reason };
48
+ },
49
+ wait() {
50
+ return { type: "wait" };
51
+ },
52
+ done() {
53
+ return { type: "done" };
54
+ },
55
+ };
56
+ }
@@ -0,0 +1,18 @@
1
+ import type { InferenceError } from "@intx/types/runtime";
2
+ export type { InferenceError };
3
+ export declare function classifyHTTPError(statusCode: number, message: string, raw?: unknown, retryAfterMs?: number): InferenceError;
4
+ export declare function classifyNetworkError(cause: unknown): InferenceError;
5
+ export declare function classifyAbortError(): InferenceError;
6
+ export declare function classifyTimeoutError(kind: "inactivity" | "total", thresholdMs: number): InferenceError;
7
+ /**
8
+ * The one throw type a response parser is permitted to raise. See the
9
+ * `ResponseParser` contract on `adapter.ts` for full semantics. `raw`
10
+ * carries the offending bytes or parsed object so operators can
11
+ * inspect what came over the wire.
12
+ */
13
+ export declare class ProtocolMismatchError extends Error {
14
+ readonly raw: unknown;
15
+ constructor(detail: string, raw?: unknown);
16
+ }
17
+ export declare function classifyProtocolMismatch(detail: string, raw?: unknown): InferenceError;
18
+ export declare function classifyStreamError(cause: unknown): InferenceError;
package/dist/errors.js ADDED
@@ -0,0 +1,83 @@
1
+ export function classifyHTTPError(statusCode, message, raw, retryAfterMs) {
2
+ if (statusCode === 401 || statusCode === 403) {
3
+ return { category: "credential_failure", message, statusCode, raw };
4
+ }
5
+ if (statusCode === 429) {
6
+ return {
7
+ category: "quota_exhausted",
8
+ message,
9
+ statusCode,
10
+ ...(retryAfterMs !== undefined ? { retryAfterMs } : {}),
11
+ raw,
12
+ };
13
+ }
14
+ if (statusCode === 400) {
15
+ // Context-overflow manifests as a 400 with a provider-specific message.
16
+ // Check for known patterns before falling through to fatal.
17
+ if (isContextOverflowMessage(message)) {
18
+ return { category: "context_overflow", message, statusCode, raw };
19
+ }
20
+ return { category: "fatal", message, statusCode, raw };
21
+ }
22
+ if (statusCode >= 500 && statusCode < 600) {
23
+ return { category: "retryable", message, statusCode, raw };
24
+ }
25
+ return { category: "fatal", message, statusCode, raw };
26
+ }
27
+ export function classifyNetworkError(cause) {
28
+ const message = cause instanceof Error ? cause.message : String(cause);
29
+ return { category: "retryable", message, raw: cause };
30
+ }
31
+ export function classifyAbortError() {
32
+ return { category: "aborted", message: "inference aborted" };
33
+ }
34
+ export function classifyTimeoutError(kind, thresholdMs) {
35
+ const message = kind === "inactivity"
36
+ ? `inference call exceeded inactivity timeout (${String(thresholdMs)} ms with no events from the provider)`
37
+ : `inference call exceeded total timeout (${String(thresholdMs)} ms wall-clock)`;
38
+ return { category: "timeout", message };
39
+ }
40
+ /**
41
+ * The one throw type a response parser is permitted to raise. See the
42
+ * `ResponseParser` contract on `adapter.ts` for full semantics. `raw`
43
+ * carries the offending bytes or parsed object so operators can
44
+ * inspect what came over the wire.
45
+ */
46
+ export class ProtocolMismatchError extends Error {
47
+ raw;
48
+ constructor(detail, raw) {
49
+ super(detail);
50
+ this.name = "ProtocolMismatchError";
51
+ this.raw = raw;
52
+ }
53
+ }
54
+ export function classifyProtocolMismatch(detail, raw) {
55
+ return {
56
+ category: "protocol_mismatch",
57
+ message: detail,
58
+ ...(raw !== undefined ? { raw } : {}),
59
+ };
60
+ }
61
+ export function classifyStreamError(cause) {
62
+ if (isAbortError(cause)) {
63
+ return classifyAbortError();
64
+ }
65
+ if (cause instanceof ProtocolMismatchError) {
66
+ return classifyProtocolMismatch(cause.message, cause.raw);
67
+ }
68
+ const message = cause instanceof Error ? cause.message : String(cause);
69
+ return { category: "retryable", message, raw: cause };
70
+ }
71
+ function isContextOverflowMessage(message) {
72
+ const lower = message.toLowerCase();
73
+ return (lower.includes("context_length_exceeded") ||
74
+ lower.includes("context length") ||
75
+ lower.includes("too many tokens") ||
76
+ lower.includes("maximum context") ||
77
+ lower.includes("input is too long"));
78
+ }
79
+ function isAbortError(value) {
80
+ return (value instanceof Error &&
81
+ (value.name === "AbortError" ||
82
+ value.message === "The user aborted a request."));
83
+ }
@@ -0,0 +1,27 @@
1
+ import type { GateType } from "@intx/types/runtime";
2
+ export type GateRecord = {
3
+ gateId: string;
4
+ type: GateType;
5
+ timeoutAt: number;
6
+ correlationId: string | undefined;
7
+ resolve: (reason: "resolved" | "timeout" | "shutdown") => void;
8
+ onCleared: (gateId: string, reason: "resolved" | "timeout" | "shutdown") => void;
9
+ timer: ReturnType<typeof setTimeout>;
10
+ };
11
+ export type GateSnapshot = {
12
+ gateId: string;
13
+ type: GateType;
14
+ timeoutAt: number;
15
+ };
16
+ /**
17
+ * Manages active gates. All gates must have a positive timeout.
18
+ */
19
+ export declare function createGateManager(): {
20
+ register: (gateId: string, type: GateType, timeoutMs: number, correlationId: string | undefined, onCleared: (gateId: string, reason: "resolved" | "timeout" | "shutdown") => void) => Promise<"resolved" | "timeout" | "shutdown">;
21
+ clear: (gateId: string) => boolean;
22
+ shutdown: () => void;
23
+ findByCorrelationId: (correlationId: string) => GateRecord | undefined;
24
+ snapshot: () => GateSnapshot[];
25
+ has: (gateId: string) => boolean;
26
+ };
27
+ export type GateManager = ReturnType<typeof createGateManager>;
package/dist/gates.js ADDED
@@ -0,0 +1,80 @@
1
+ // Gate management for the agent reactor.
2
+ //
3
+ // Gates block the reactor until an external condition resolves. Each gate has
4
+ // a type, an ID, and a mandatory timeout. The gate manager owns all active
5
+ // gates and exposes methods to register, clear, and time out gates.
6
+ //
7
+ // (INFERENCE.md § Gates, Gate Timeouts, Gate Behavior During Suspension)
8
+ /**
9
+ * Manages active gates. All gates must have a positive timeout.
10
+ */
11
+ export function createGateManager() {
12
+ const gates = new Map();
13
+ function register(gateId, type, timeoutMs, correlationId, onCleared) {
14
+ if (timeoutMs <= 0) {
15
+ throw new Error(`Gate "${gateId}" must have a positive timeout (got ${timeoutMs})`);
16
+ }
17
+ if (gates.has(gateId)) {
18
+ throw new Error(`Gate "${gateId}" is already registered`);
19
+ }
20
+ const timeoutAt = Date.now() + timeoutMs;
21
+ let resolveGate;
22
+ const promise = new Promise((resolve) => {
23
+ resolveGate = resolve;
24
+ });
25
+ const timer = setTimeout(() => {
26
+ if (gates.has(gateId)) {
27
+ gates.delete(gateId);
28
+ resolveGate("timeout");
29
+ onCleared(gateId, "timeout");
30
+ }
31
+ }, timeoutMs);
32
+ gates.set(gateId, {
33
+ gateId,
34
+ type,
35
+ timeoutAt,
36
+ correlationId,
37
+ resolve: resolveGate,
38
+ onCleared,
39
+ timer,
40
+ });
41
+ return promise;
42
+ }
43
+ function clear(gateId) {
44
+ const gate = gates.get(gateId);
45
+ if (gate === undefined)
46
+ return false;
47
+ clearTimeout(gate.timer);
48
+ gates.delete(gateId);
49
+ gate.resolve("resolved");
50
+ gate.onCleared(gateId, "resolved");
51
+ return true;
52
+ }
53
+ function shutdown() {
54
+ const entries = Array.from(gates.values());
55
+ gates.clear();
56
+ for (const gate of entries) {
57
+ clearTimeout(gate.timer);
58
+ gate.resolve("shutdown");
59
+ gate.onCleared(gate.gateId, "shutdown");
60
+ }
61
+ }
62
+ function findByCorrelationId(correlationId) {
63
+ for (const gate of gates.values()) {
64
+ if (gate.correlationId === correlationId)
65
+ return gate;
66
+ }
67
+ return undefined;
68
+ }
69
+ function snapshot() {
70
+ return Array.from(gates.values()).map((g) => ({
71
+ gateId: g.gateId,
72
+ type: g.type,
73
+ timeoutAt: g.timeoutAt,
74
+ }));
75
+ }
76
+ function has(gateId) {
77
+ return gates.has(gateId);
78
+ }
79
+ return { register, clear, shutdown, findByCorrelationId, snapshot, has };
80
+ }
@@ -0,0 +1,147 @@
1
+ import type { ConversationTurn, InferenceEvent, InferenceOptions, InferenceSource } from "@intx/types/runtime";
2
+ import type { AdapterRegistry } from "./adapter.js";
3
+ /**
4
+ * Default per-call inactivity timeout (ms). Two minutes is conservative
5
+ * for reasoning-heavy models that emit `inference.thinking.delta` tokens
6
+ * regularly when actually working — sustained silence past this means
7
+ * the provider stream has genuinely stalled, not that the model is
8
+ * thinking. Operators can tune via `InferenceOptions.inactivityTimeoutMs`.
9
+ */
10
+ export declare const DEFAULT_INACTIVITY_TIMEOUT_MS = 120000;
11
+ /**
12
+ * Default per-call total wall-clock cap (ms). Matches Anthropic's
13
+ * documented per-call recommendation and fits within typical CI
14
+ * timeouts. Operators can tune via `InferenceOptions.totalTimeoutMs`.
15
+ */
16
+ export declare const DEFAULT_TOTAL_TIMEOUT_MS = 600000;
17
+ export declare const HarnessId: unique symbol;
18
+ /**
19
+ * Runtime dependencies injected into `runInference`. Code-only — not part of
20
+ * any persisted schema. Test harnesses substitute `fetch` (and stamp the
21
+ * `[HarnessId]` tag for per-harness identity) so production `runInference`
22
+ * never reaches `globalThis.fetch`.
23
+ *
24
+ * `fetch` is intentionally typed as a plain function rather than
25
+ * `typeof globalThis.fetch` — the latter is augmented per-runtime (Bun adds
26
+ * `preconnect`; Node and the DOM lib do not) and `runInference` only ever
27
+ * invokes the call signature.
28
+ *
29
+ * The `[HarnessId]` tag is enumerable via `Object.getOwnPropertySymbols`
30
+ * (and `Reflect.ownKeys`, which is the superset). Do not pass `Dependencies`
31
+ * instances through reflective serializers or expose them across trust
32
+ * boundaries. (`JSON.stringify` is safe — it walks string keys only.)
33
+ */
34
+ export type Dependencies = {
35
+ readonly fetch: (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
36
+ /**
37
+ * Time-based scheduler used by the harness's per-call timeouts (see
38
+ * `InferenceOptions.inactivityTimeoutMs` / `totalTimeoutMs`). Production
39
+ * passes the default wrapper around `setTimeout` / `clearTimeout`; the
40
+ * deterministic test harness injects a scheduler that wraps its virtual
41
+ * clock so timeout tests fire at virtual-time-N without sleeping real
42
+ * wall-clock. Required — every caller must make an explicit choice
43
+ * between the production scheduler and a virtual one. Use
44
+ * `createDefaultScheduler()` for the production default.
45
+ */
46
+ readonly scheduler: Scheduler;
47
+ /**
48
+ * Registry resolving an inference source to its provider adapter.
49
+ * `runSingleAttempt` consults this on every call via `adapters.resolve`,
50
+ * so it is required — the caller makes an explicit choice of provider set.
51
+ * Construct it via `createDependencies(adapters)` (core) or, for the
52
+ * built-in set, `@intx/inference/providers`' `createDefaultDependencies()`.
53
+ */
54
+ readonly adapters: AdapterRegistry;
55
+ readonly [HarnessId]?: symbol;
56
+ };
57
+ /**
58
+ * Minimal scheduling abstraction. `setTimeout` returns a canceller; the
59
+ * canceller is idempotent (multiple calls are safe). The harness uses
60
+ * this for both the inactivity timer (which is re-armed on every event)
61
+ * and the total wall-clock cap. `now()` is a monotonic time source in
62
+ * the same `delayMs` units `setTimeout` accepts — deltas across two
63
+ * `now()` reads describe elapsed time the same way `setTimeout(...,
64
+ * delta)` would have measured it.
65
+ */
66
+ export type Scheduler = {
67
+ setTimeout(callback: () => void, delayMs: number): () => void;
68
+ now(): number;
69
+ };
70
+ export declare function createDefaultScheduler(): Scheduler;
71
+ /**
72
+ * Construct runtime dependencies for `runInference` from an explicit adapter
73
+ * registry, binding `fetch` to `globalThis.fetch` and `scheduler` to the
74
+ * production wrapper. The registry is required so the caller makes an explicit
75
+ * choice of provider set; `@intx/inference/providers`' zero-arg
76
+ * `createDefaultDependencies()` is the honest default that supplies the
77
+ * built-in registry.
78
+ *
79
+ * @param adapters - Registry resolving inference sources to provider adapters
80
+ * @returns Fully-populated dependencies
81
+ */
82
+ export declare function createDependencies(adapters: AdapterRegistry): Dependencies;
83
+ export type InferenceHarnessOptions = {
84
+ turns: ConversationTurn[];
85
+ source: InferenceSource;
86
+ inferenceOptions?: InferenceOptions;
87
+ signal?: AbortSignal;
88
+ nextSeq: () => number;
89
+ deps: Dependencies;
90
+ };
91
+ /**
92
+ * Run a single inference call with mechanical retry. Wraps
93
+ * `runSingleAttempt` and consults the configured `RetryPolicy` (or the
94
+ * default from `createDefaultRetryPolicy`) on every `inference.error`.
95
+ *
96
+ * Events from each attempt are buffered until the attempt terminates;
97
+ * the wrapper only flushes them to the caller once it knows whether
98
+ * the attempt resolved (`inference.done` or a policy-approved abort)
99
+ * or whether the attempt's events should be discarded in favour of a
100
+ * retry. The buffer-and-flush model is what guarantees the caller
101
+ * sees a single clean event stream — exactly one `inference.start`,
102
+ * no orphaned partial deltas, no leaked `inference.error`s from
103
+ * attempts the policy chose to retry. The cost is that no events
104
+ * reach the caller until the wrapper knows the attempt's terminal
105
+ * shape, even on a successful first attempt. That trade-off is the
106
+ * deliberate consequence of making "one clean stream" a hard contract
107
+ * rather than a best-effort one. Consumers that need token-by-token
108
+ * partials must pin a custom non-buffering wrapper — no streaming-
109
+ * partials emission API exists today.
110
+ *
111
+ * The buffer is per-call and bounded by the size of one attempt's
112
+ * event stream — no cross-call accumulation.
113
+ *
114
+ * Caller-visible seqs stay contiguous across retries. Each attempt
115
+ * runs against a private seq allocator; on flush the wrapper
116
+ * re-stamps the buffered events with seqs from the caller's
117
+ * `nextSeq`, so a retry that discards an attempt does not leave a
118
+ * gap in the consumer's seq stream.
119
+ *
120
+ * Between attempts the wrapper emits one `inference.retry` event with
121
+ * the failed attempt's number, the policy-chosen `delayMs`, and the
122
+ * classified error that triggered the retry. The `setTimeout` await
123
+ * is driven by `deps.scheduler`, so virtual-clock test harnesses
124
+ * advance retry delays without sleeping real wall-clock. The
125
+ * caller-supplied `signal` short-circuits the retry delay: aborting
126
+ * the signal mid-delay wakes the await immediately and the next
127
+ * `runSingleAttempt` invocation surfaces `inference.error` of
128
+ * category `aborted` from its entry-time signal check, which the
129
+ * default policy aborts on.
130
+ *
131
+ * Policy-failure handling: if the policy throws synchronously or its
132
+ * returned Promise rejects, the wrapper treats the failure as
133
+ * `{ kind: "abort" }` and surfaces the *original* `inference.error`
134
+ * to the caller. The policy's own exception is logged at `warn` so
135
+ * operators can see when a custom policy is failing under load, and
136
+ * dropped — the inference error is what the caller needs to act on,
137
+ * not the bug in the policy callback.
138
+ *
139
+ * Synchronous throws from `runSingleAttempt` (`ProtocolMismatchError`
140
+ * raised by the streaming parse or the finalization walk, etc.)
141
+ * propagate out of `runInference`. The current attempt's buffered
142
+ * events are discarded along with the throw — those represent
143
+ * protocol bugs the policy mechanism is not equipped to absorb, and
144
+ * the caller's `for await` rejects so the failure surfaces rather
145
+ * than being silently buffered.
146
+ */
147
+ export declare function runInference(opts: InferenceHarnessOptions): AsyncIterable<InferenceEvent>;