@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.
- package/LICENSE +176 -0
- package/dist/actions.d.ts +16 -0
- package/dist/actions.js +200 -0
- package/dist/adapter.d.ts +40 -0
- package/dist/adapter.js +31 -0
- package/dist/assembly.d.ts +75 -0
- package/dist/assembly.js +133 -0
- package/dist/audit-collector.d.ts +10 -0
- package/dist/audit-collector.js +139 -0
- package/dist/auth.d.ts +24 -0
- package/{src/auth.ts → dist/auth.js} +13 -19
- package/dist/authz-extension.d.ts +46 -0
- package/dist/authz-extension.js +184 -0
- package/dist/correlation.d.ts +26 -0
- package/dist/correlation.js +39 -0
- package/dist/default-director.d.ts +111 -0
- package/dist/default-director.js +228 -0
- package/dist/director.d.ts +6 -0
- package/dist/director.js +56 -0
- package/dist/errors.d.ts +18 -0
- package/dist/errors.js +83 -0
- package/dist/gates.d.ts +28 -0
- package/dist/gates.js +103 -0
- package/dist/harness.d.ts +147 -0
- package/dist/harness.js +1407 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.js +21 -0
- package/dist/manifest.d.ts +31 -0
- package/dist/manifest.js +44 -0
- package/dist/providers/anthropic.d.ts +37 -0
- package/dist/providers/anthropic.js +917 -0
- package/dist/providers/google-genai-files.d.ts +48 -0
- package/dist/providers/google-genai-files.js +205 -0
- package/dist/providers/google-genai.d.ts +5 -0
- package/dist/providers/google-genai.js +1205 -0
- package/dist/providers/index.d.ts +38 -0
- package/dist/providers/index.js +56 -0
- package/dist/providers/openai.d.ts +9 -0
- package/dist/providers/openai.js +903 -0
- package/dist/reactor.d.ts +50 -0
- package/dist/reactor.js +1233 -0
- package/dist/retry-policy.d.ts +31 -0
- package/{src/retry-policy.ts → dist/retry-policy.js} +41 -53
- package/dist/sse.d.ts +1 -0
- package/dist/sse.js +63 -0
- package/dist/state.d.ts +23 -0
- package/dist/state.js +100 -0
- package/dist/tool-name.d.ts +6 -0
- package/dist/tool-name.js +110 -0
- package/dist/transform.d.ts +11 -0
- package/dist/transform.js +132 -0
- package/dist/transforms/index.d.ts +2 -0
- package/dist/transforms/index.js +1 -0
- package/dist/transforms/size-cap.d.ts +12 -0
- package/dist/transforms/size-cap.js +80 -0
- package/dist/turns.d.ts +21 -0
- package/dist/turns.js +135 -0
- package/package.json +22 -6
- package/src/actions.ts +0 -245
- package/src/adapter.ts +0 -57
- package/src/assembly.test.ts +0 -728
- package/src/assembly.ts +0 -250
- package/src/audit-collector.test.ts +0 -332
- package/src/audit-collector.ts +0 -172
- package/src/auth.test.ts +0 -117
- package/src/authz-extension.test.ts +0 -269
- package/src/authz-extension.ts +0 -145
- package/src/correlation.ts +0 -61
- package/src/default-director.test.ts +0 -314
- package/src/default-director.ts +0 -344
- package/src/director.ts +0 -87
- package/src/errors.test.ts +0 -133
- package/src/errors.ts +0 -115
- package/src/gates.ts +0 -128
- package/src/harness.test.ts +0 -655
- package/src/harness.ts +0 -1571
- package/src/index.ts +0 -76
- package/src/providers/anthropic.test.ts +0 -771
- package/src/providers/anthropic.ts +0 -810
- package/src/providers/google-genai-files.ts +0 -289
- package/src/providers/google-genai.ts +0 -1518
- package/src/providers/openai.ts +0 -719
- package/src/providers/registry.ts +0 -33
- package/src/reactor.test.ts +0 -3660
- package/src/reactor.ts +0 -1058
- package/src/scheduler.test.ts +0 -41
- package/src/sse.test.ts +0 -133
- package/src/sse.ts +0 -76
- package/src/state.ts +0 -135
- package/src/transform.test.ts +0 -207
- package/src/transform.ts +0 -159
- package/src/transforms/index.ts +0 -2
- package/src/transforms/size-cap.test.ts +0 -172
- package/src/transforms/size-cap.ts +0 -110
- package/src/turns.ts +0 -54
- package/tsconfig.json +0 -4
- package/tsconfig.tsbuildinfo +0 -1
package/src/correlation.ts
DELETED
|
@@ -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
|
-
});
|