@intx/inference 0.1.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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 +40 -0
  5. package/dist/adapter.js +31 -0
  6. package/dist/assembly.d.ts +75 -0
  7. package/dist/assembly.js +133 -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 +46 -0
  13. package/dist/authz-extension.js +184 -0
  14. package/dist/correlation.d.ts +26 -0
  15. package/dist/correlation.js +39 -0
  16. package/dist/default-director.d.ts +111 -0
  17. package/dist/default-director.js +228 -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 +28 -0
  23. package/dist/gates.js +103 -0
  24. package/dist/harness.d.ts +147 -0
  25. package/dist/harness.js +1407 -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 +37 -0
  31. package/dist/providers/anthropic.js +917 -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 +5 -0
  35. package/dist/providers/google-genai.js +1205 -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 +9 -0
  39. package/dist/providers/openai.js +903 -0
  40. package/dist/reactor.d.ts +50 -0
  41. package/dist/reactor.js +1233 -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 +132 -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 +22 -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
@@ -1,61 +0,0 @@
1
- // Correlation registry and validator interface for the agent reactor.
2
- //
3
- // Correlation connects outbound async tool calls to inbound responses. The
4
- // reactor owns the matching; the director does not participate.
5
- //
6
- // (INFERENCE.md § Correlation)
7
-
8
- import type { InboundMessage, PendingOperation } from "@intx/types/runtime";
9
-
10
- /**
11
- * Validates whether an inbound message is an authentic response to a
12
- * registered pending operation. Consumers provide this at reactor construction
13
- * time to enforce sender identity and signature checks.
14
- */
15
- export interface CorrelationValidator {
16
- /**
17
- * Return true if `message` is a valid resolution for `pending`.
18
- * False causes the message to be delivered as a regular uncorrelated event.
19
- */
20
- validate(
21
- pending: PendingOperation,
22
- message: InboundMessage,
23
- ): Promise<boolean>;
24
- }
25
-
26
- /**
27
- * Tracks pending async operations. Each entry maps a correlation ID to the
28
- * operation metadata and the gate that is waiting for it.
29
- */
30
- export function createCorrelationRegistry() {
31
- const operations = new Map<string, PendingOperation>();
32
-
33
- function register(op: PendingOperation): void {
34
- if (operations.has(op.correlationId)) {
35
- throw new Error(
36
- `Correlation ID "${op.correlationId}" is already registered`,
37
- );
38
- }
39
- operations.set(op.correlationId, op);
40
- }
41
-
42
- function lookup(correlationId: string): PendingOperation | undefined {
43
- return operations.get(correlationId);
44
- }
45
-
46
- function remove(correlationId: string): boolean {
47
- return operations.delete(correlationId);
48
- }
49
-
50
- function all(): PendingOperation[] {
51
- return Array.from(operations.values());
52
- }
53
-
54
- function hasAny(): boolean {
55
- return operations.size > 0;
56
- }
57
-
58
- return { register, lookup, remove, all, hasAny };
59
- }
60
-
61
- export type CorrelationRegistry = ReturnType<typeof createCorrelationRegistry>;
@@ -1,314 +0,0 @@
1
- import { describe, test, expect } from "bun:test";
2
-
3
- import { createInboundMessage } from "@intx/mime";
4
- import type {
5
- AssistantTurn,
6
- InferenceError,
7
- LastCycleSource,
8
- PartialMessage,
9
- ReactorAction,
10
- ReactorInboundEvent,
11
- ReactorState,
12
- ToolResult,
13
- TokenUsage,
14
- } from "@intx/types/runtime";
15
-
16
- import { createDefaultDirector } from "./default-director";
17
- import type {
18
- AfterInferenceDecision,
19
- AfterInferenceHook,
20
- DefaultDirectorPolicy,
21
- } from "./default-director";
22
- import { createCapabilities } from "./director";
23
-
24
- // ---------------------------------------------------------------------------
25
- // Fixtures
26
- // ---------------------------------------------------------------------------
27
-
28
- const TEST_SOURCE: LastCycleSource = {
29
- sourceId: "anthropic:claude-test",
30
- provider: "anthropic",
31
- model: "claude-test",
32
- };
33
-
34
- const TEST_USAGE: TokenUsage = {
35
- input: 100,
36
- output: 50,
37
- cacheRead: 0,
38
- cacheWrite: 0,
39
- thinking: 0,
40
- };
41
-
42
- function makeState(overrides: Partial<ReactorState> = {}): ReactorState {
43
- return {
44
- sessionId: "test-session",
45
- turns: [],
46
- activeForks: [],
47
- pendingOperations: [],
48
- activeGates: [],
49
- tokenUsage: { ...TEST_USAGE },
50
- lastCycleUsage: { ...TEST_USAGE },
51
- lastCycleSource: { ...TEST_SOURCE },
52
- ...overrides,
53
- };
54
- }
55
-
56
- function makeAssistantTurn(text: string): AssistantTurn {
57
- return {
58
- role: "assistant",
59
- content: [{ type: "text", text }],
60
- model: "claude-test",
61
- timestamp: 1000,
62
- };
63
- }
64
-
65
- function makeAssistantTurnWithToolCall(
66
- callId: string,
67
- name: string,
68
- ): AssistantTurn {
69
- return {
70
- role: "assistant",
71
- content: [
72
- { type: "tool_call", id: callId, name, arguments: { q: "test" } },
73
- ],
74
- model: "claude-test",
75
- timestamp: 1000,
76
- };
77
- }
78
-
79
- function makeInferenceDoneEvent(turn: AssistantTurn): ReactorInboundEvent {
80
- return {
81
- type: "inference.done",
82
- turn,
83
- usage: TEST_USAGE,
84
- source: TEST_SOURCE,
85
- };
86
- }
87
-
88
- function makeInferenceErrorEvent(): ReactorInboundEvent {
89
- const error: InferenceError = {
90
- category: "fatal",
91
- message: "test error",
92
- };
93
- const partial: PartialMessage = { text: "" };
94
- return { type: "inference.error", error, partial };
95
- }
96
-
97
- async function decide(
98
- policy: DefaultDirectorPolicy,
99
- event: ReactorInboundEvent,
100
- state: ReactorState = makeState(),
101
- ): Promise<ReactorAction[]> {
102
- const director = createDefaultDirector("You are a test agent.", [], policy);
103
- const result = await director.decide(event, state, createCapabilities());
104
- return Array.isArray(result) ? result : [result];
105
- }
106
-
107
- // ---------------------------------------------------------------------------
108
- // Hook decisions
109
- // ---------------------------------------------------------------------------
110
-
111
- describe("DefaultDirector — afterInferenceDone hook", () => {
112
- test("continue: existing post-inference logic runs unchanged", async () => {
113
- let receivedState: ReactorState | undefined;
114
- let receivedTurn: AssistantTurn | undefined;
115
- const hook: AfterInferenceHook = (state, turn) => {
116
- receivedState = state;
117
- receivedTurn = turn;
118
- return { type: "continue" };
119
- };
120
- const turn = makeAssistantTurn("Hello from the model");
121
- const actions = await decide(
122
- { afterInferenceDone: hook },
123
- makeInferenceDoneEvent(turn),
124
- );
125
-
126
- // The hook saw the post-cycle state and the turn — verifies the
127
- // director plumbed both arguments through, not just called the hook.
128
- expect(receivedTurn).toEqual(turn);
129
- expect(receivedState?.lastCycleSource).toEqual(TEST_SOURCE);
130
- expect(receivedState?.lastCycleUsage).toEqual(TEST_USAGE);
131
-
132
- // Continue falls through to the existing reply path.
133
- expect(actions).toEqual([
134
- { type: "checkpoint", message: "checkpoint: inference-done" },
135
- { type: "reply", content: "Hello from the model" },
136
- ]);
137
- });
138
-
139
- test("abort: returns [checkpoint, reply, done] with the policy reason", async () => {
140
- const hook: AfterInferenceHook = () => ({
141
- type: "abort",
142
- reason: "budget exhausted",
143
- });
144
- const actions = await decide(
145
- { afterInferenceDone: hook },
146
- makeInferenceDoneEvent(makeAssistantTurn("ignored")),
147
- );
148
- expect(actions).toEqual([
149
- { type: "checkpoint", message: "checkpoint: after-inference-abort" },
150
- { type: "reply", content: "budget exhausted" },
151
- { type: "done" },
152
- ]);
153
- });
154
-
155
- test("halt: returns [checkpoint, reply, wait] with the policy reason", async () => {
156
- const hook: AfterInferenceHook = () => ({
157
- type: "halt",
158
- reason: "paused for top-up",
159
- });
160
- const actions = await decide(
161
- { afterInferenceDone: hook },
162
- makeInferenceDoneEvent(makeAssistantTurn("ignored")),
163
- );
164
- expect(actions).toEqual([
165
- { type: "checkpoint", message: "checkpoint: after-inference-halt" },
166
- { type: "reply", content: "paused for top-up" },
167
- { type: "wait" },
168
- ]);
169
- });
170
-
171
- test("hook not set: existing behavior preserved", async () => {
172
- const actions = await decide(
173
- {},
174
- makeInferenceDoneEvent(makeAssistantTurn("Hello")),
175
- );
176
- expect(actions).toEqual([
177
- { type: "checkpoint", message: "checkpoint: inference-done" },
178
- { type: "reply", content: "Hello" },
179
- ]);
180
- });
181
-
182
- test("hook throws: caught, routed to abort with synthesized reason", async () => {
183
- const hook: AfterInferenceHook = () => {
184
- throw new Error("policy died");
185
- };
186
- const actions = await decide(
187
- { afterInferenceDone: hook },
188
- makeInferenceDoneEvent(makeAssistantTurn("ignored")),
189
- );
190
- expect(actions).toEqual([
191
- { type: "checkpoint", message: "checkpoint: after-inference-abort" },
192
- {
193
- type: "reply",
194
- content: "afterInferenceDone policy threw: policy died",
195
- },
196
- { type: "done" },
197
- ]);
198
- });
199
-
200
- test("hook returning a Promise is awaited", async () => {
201
- const hook: AfterInferenceHook = async () => {
202
- await new Promise((resolve) => setTimeout(resolve, 5));
203
- const decision: AfterInferenceDecision = {
204
- type: "abort",
205
- reason: "async abort",
206
- };
207
- return decision;
208
- };
209
- const actions = await decide(
210
- { afterInferenceDone: hook },
211
- makeInferenceDoneEvent(makeAssistantTurn("ignored")),
212
- );
213
- expect(actions).toEqual([
214
- { type: "checkpoint", message: "checkpoint: after-inference-abort" },
215
- { type: "reply", content: "async abort" },
216
- { type: "done" },
217
- ]);
218
- });
219
-
220
- test("hook does NOT fire on inference.error", async () => {
221
- let hookFired = false;
222
- const hook: AfterInferenceHook = () => {
223
- hookFired = true;
224
- return { type: "abort", reason: "should not run" };
225
- };
226
- const actions = await decide(
227
- { afterInferenceDone: hook },
228
- makeInferenceErrorEvent(),
229
- );
230
- expect(hookFired).toBe(false);
231
- // The inference.error branch produces its own checkpoint + reply
232
- // shape; the hook is not in that path.
233
- expect(actions[0]).toEqual({
234
- type: "checkpoint",
235
- message: "checkpoint: inference-error",
236
- });
237
- });
238
-
239
- test("abort fires before tool calls execute (tool calls dropped)", async () => {
240
- const hook: AfterInferenceHook = () => ({
241
- type: "abort",
242
- reason: "stop now",
243
- });
244
- const turn = makeAssistantTurnWithToolCall("call_1", "search");
245
- const actions = await decide(
246
- { afterInferenceDone: hook },
247
- makeInferenceDoneEvent(turn),
248
- );
249
- // The model's tool call is on the turn, but the hook's abort
250
- // routes to done before execute_tools is reached. The TSDoc warns
251
- // policy authors about this; the test pins the behavior.
252
- expect(actions).toEqual([
253
- { type: "checkpoint", message: "checkpoint: after-inference-abort" },
254
- { type: "reply", content: "stop now" },
255
- { type: "done" },
256
- ]);
257
- });
258
- });
259
-
260
- // ---------------------------------------------------------------------------
261
- // Firing boundary: hook fires only on inference.done
262
- //
263
- // The "does NOT fire on inference.error" test above pins one negative
264
- // case; this block exhausts the rest of the ReactorInboundEvent union
265
- // so a future switch refactor (e.g. extracting a shared post-event
266
- // helper) can't quietly start invoking the hook on the wrong branch.
267
- // ---------------------------------------------------------------------------
268
-
269
- async function fireHook(event: ReactorInboundEvent): Promise<boolean> {
270
- let fired = false;
271
- const hook: AfterInferenceHook = () => {
272
- fired = true;
273
- return { type: "continue" };
274
- };
275
- await decide({ afterInferenceDone: hook }, event);
276
- return fired;
277
- }
278
-
279
- describe("DefaultDirector — afterInferenceDone firing boundary", () => {
280
- test("not fired on message.received", async () => {
281
- const event: ReactorInboundEvent = {
282
- type: "message.received",
283
- message: createInboundMessage({
284
- from: "test@example.com",
285
- to: "agent@example.com",
286
- content: "hi",
287
- }),
288
- };
289
- expect(await fireHook(event)).toBe(false);
290
- });
291
-
292
- test("not fired on tool.done", async () => {
293
- const result: ToolResult = { callId: "c1", content: "ok" };
294
- const event: ReactorInboundEvent = { type: "tool.done", result };
295
- expect(await fireHook(event)).toBe(false);
296
- });
297
-
298
- test("not fired on reactor.gate.cleared", async () => {
299
- const event: ReactorInboundEvent = {
300
- type: "reactor.gate.cleared",
301
- gateId: "g1",
302
- reason: "resolved",
303
- };
304
- expect(await fireHook(event)).toBe(false);
305
- });
306
-
307
- test("not fired on abort", async () => {
308
- const event: ReactorInboundEvent = {
309
- type: "abort",
310
- reason: "user_disconnect",
311
- };
312
- expect(await fireHook(event)).toBe(false);
313
- });
314
- });