@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,80 @@
1
+ // Tool-result size-cap transform.
2
+ //
3
+ // When a tool result's content exceeds `maxChars`, the full bytes are spilled
4
+ // via `contextStore.writeBlob` and the inline result is replaced with the
5
+ // first `maxChars` characters plus a marker pointing at the spill via a
6
+ // `tool-output:///{callId}` URI. The agent's `read_file` tool resolves the
7
+ // URI through its `BlobReader` capability.
8
+ //
9
+ // Within-cap results pass through unchanged (no blob is written) but still
10
+ // produce a `TransformRecord` so the manifest captures every invocation.
11
+ const SIZE_CAP_VERSION = "1";
12
+ const SIZE_CAP_NAME = "size-cap";
13
+ /**
14
+ * Create a `ToolResultTransform` that caps inline tool result content at
15
+ * `maxChars` characters. Oversized results are spilled to the context store
16
+ * via `writeBlob` and the inline content becomes a truncated marker
17
+ * referencing the spill by `tool-output:///{callId}` URI.
18
+ */
19
+ export function createSizeCapTransform(options) {
20
+ const { maxChars, contextStore } = options;
21
+ if (!Number.isFinite(maxChars) || maxChars <= 0) {
22
+ throw new Error(`createSizeCapTransform: maxChars must be a positive finite number, got ${String(maxChars)}`);
23
+ }
24
+ return {
25
+ name: SIZE_CAP_NAME,
26
+ version: SIZE_CAP_VERSION,
27
+ async apply(input, _ctx) {
28
+ const { call, result } = input;
29
+ const text = typeof result.content === "string"
30
+ ? result.content
31
+ : JSON.stringify(result.content);
32
+ if (text.length <= maxChars) {
33
+ return {
34
+ output: result,
35
+ record: {
36
+ strategy: SIZE_CAP_NAME,
37
+ version: SIZE_CAP_VERSION,
38
+ parameters: { maxChars },
39
+ reason: "within-cap",
40
+ decisions: { callId: call.id, length: text.length },
41
+ },
42
+ };
43
+ }
44
+ const omitted = text.length - maxChars;
45
+ const kept = text.slice(0, maxChars);
46
+ const spillURI = `tool-output:///${call.id}`;
47
+ const marker = `${kept}\n[Tool output truncated: omitted ${String(omitted)} chars. ` +
48
+ `Full output available at ${spillURI} -- use read_file with that URI to see the rest.]`;
49
+ const bytes = new TextEncoder().encode(text);
50
+ await contextStore.writeBlob(call.id, bytes, "text/plain");
51
+ const output = {
52
+ ...result,
53
+ content: marker,
54
+ };
55
+ return {
56
+ output,
57
+ record: {
58
+ strategy: SIZE_CAP_NAME,
59
+ version: SIZE_CAP_VERSION,
60
+ parameters: { maxChars },
61
+ reason: "exceeded-cap",
62
+ decisions: {
63
+ callId: call.id,
64
+ originalLength: text.length,
65
+ kept: maxChars,
66
+ spillKey: call.id,
67
+ spillURI,
68
+ },
69
+ },
70
+ blobs: [
71
+ {
72
+ key: call.id,
73
+ bytes,
74
+ contentType: "text/plain",
75
+ },
76
+ ],
77
+ };
78
+ },
79
+ };
80
+ }
@@ -0,0 +1,21 @@
1
+ import type { ConversationTurn, ContentBlock, AssistantTurn, InboundMessage, ToolCall, ToolResult } from "@intx/types/runtime";
2
+ export type { ConversationTurn, ContentBlock, AssistantTurn };
3
+ export type { ToolCall, ToolResult };
4
+ export declare function createInboundTurn(message: InboundMessage): ConversationTurn | null;
5
+ /**
6
+ * Assert that a prompt's tool_call / tool_result blocks are structurally
7
+ * well-formed before it is sent to a provider.
8
+ *
9
+ * Throws if a tool_call id is emitted twice, if a tool_result references a
10
+ * callId with no preceding tool_call, or if two tool_result blocks answer the
11
+ * same callId. None of these are valid in a coherent tool conversation — a
12
+ * tool call has exactly one result. Catching it here surfaces the corruption
13
+ * as an internal error at the assembly boundary, with the offending id and
14
+ * turn index, instead of an opaque downstream provider rejection.
15
+ *
16
+ * This deliberately does NOT require every tool_call to have a result: an
17
+ * unanswered tool_call is left legitimately by an after-inference halt/abort
18
+ * and is repaired downstream for cross-provider replay.
19
+ */
20
+ export declare function assertWellFormedToolSequence(turns: ConversationTurn[]): void;
21
+ export declare function createToolResultTurn(results: ToolResult[]): ConversationTurn;
package/dist/turns.js ADDED
@@ -0,0 +1,135 @@
1
+ import { attachmentCategory, base64Encode } from "@intx/types";
2
+ import { getLogger } from "@intx/log";
3
+ const logger = getLogger(["interchange", "inference", "turns"]);
4
+ /**
5
+ * Map a received attachment to a model ContentBlock.
6
+ *
7
+ * The dispatch uses two policies that look unified but are not, and must
8
+ * stay distinct:
9
+ * - major-type dispatch for image/*, video/*, audio/* → the matching block;
10
+ * - allowlist-category dispatch for the document category
11
+ * (application/pdf, application/json, text/plain, text/csv,
12
+ * text/markdown) → DocumentBlock.
13
+ * Do NOT collapse this into "always dispatch by major type": that would
14
+ * route text/plain to a text block instead of DocumentBlock.
15
+ *
16
+ * This function is total — it never throws. The hub route allowlist only
17
+ * guards the local user-upload path; inbound attachments arrive from remote
18
+ * senders via fetchFull and are not subtype-filtered here (provider adapters
19
+ * are the contract layer that rejects unsupported media at marshal time, per
20
+ * INTERCHANGE message design). Any major-type media is therefore passed
21
+ * through to the adapter. A type that maps to no block at all (e.g. an
22
+ * archive) degrades to a visible text marker rather than throwing: throwing
23
+ * here would propagate into the reactor's ungoverned delivery path and let a
24
+ * single malformed remote attachment tear down the session.
25
+ */
26
+ function attachmentToContentBlock(att) {
27
+ const majorType = att.contentType.split("/")[0];
28
+ if (majorType === "image" ||
29
+ majorType === "video" ||
30
+ majorType === "audio" ||
31
+ attachmentCategory(att.contentType) === "document") {
32
+ const source = {
33
+ kind: "base64",
34
+ mimeType: att.contentType,
35
+ data: base64Encode(att.data),
36
+ };
37
+ if (majorType === "image")
38
+ return { type: "image", source };
39
+ if (majorType === "video")
40
+ return { type: "video", source };
41
+ if (majorType === "audio")
42
+ return { type: "audio", source };
43
+ return { type: "document", source };
44
+ }
45
+ logger.warn `Unsupported attachment content type ${att.contentType}; surfacing as a text marker`;
46
+ return {
47
+ type: "text",
48
+ text: `[Unsupported attachment: ${att.name} (${att.contentType})]`,
49
+ };
50
+ }
51
+ export function createInboundTurn(message) {
52
+ const content = message.content ?? "";
53
+ const attachments = message.attachments ?? [];
54
+ if (content.length === 0 && attachments.length === 0)
55
+ return null;
56
+ const blocks = [];
57
+ if (content.length > 0) {
58
+ const { from, subject } = message.headers;
59
+ const envelope = [];
60
+ if (from.length > 0)
61
+ envelope.push(`[From: ${from}]`);
62
+ if (subject !== undefined && subject.length > 0) {
63
+ envelope.push(`[Subject: ${subject}]`);
64
+ }
65
+ const text = envelope.length > 0 ? `${envelope.join("\n")}\n\n${content}` : content;
66
+ blocks.push({ type: "text", text });
67
+ }
68
+ for (const att of attachments) {
69
+ blocks.push(attachmentToContentBlock(att));
70
+ }
71
+ return {
72
+ role: "user",
73
+ content: blocks,
74
+ timestamp: Date.now(),
75
+ };
76
+ }
77
+ /**
78
+ * Assert that a prompt's tool_call / tool_result blocks are structurally
79
+ * well-formed before it is sent to a provider.
80
+ *
81
+ * Throws if a tool_call id is emitted twice, if a tool_result references a
82
+ * callId with no preceding tool_call, or if two tool_result blocks answer the
83
+ * same callId. None of these are valid in a coherent tool conversation — a
84
+ * tool call has exactly one result. Catching it here surfaces the corruption
85
+ * as an internal error at the assembly boundary, with the offending id and
86
+ * turn index, instead of an opaque downstream provider rejection.
87
+ *
88
+ * This deliberately does NOT require every tool_call to have a result: an
89
+ * unanswered tool_call is left legitimately by an after-inference halt/abort
90
+ * and is repaired downstream for cross-provider replay.
91
+ */
92
+ export function assertWellFormedToolSequence(turns) {
93
+ const calledIds = new Set();
94
+ const answeredIds = new Set();
95
+ for (let turnIndex = 0; turnIndex < turns.length; turnIndex++) {
96
+ const turn = turns[turnIndex];
97
+ if (turn === undefined)
98
+ continue;
99
+ for (const block of turn.content) {
100
+ if (block.type === "tool_call") {
101
+ if (calledIds.has(block.id)) {
102
+ throw new Error(`Malformed tool sequence: duplicate tool_call id ${JSON.stringify(block.id)} at turn ${String(turnIndex)}`);
103
+ }
104
+ calledIds.add(block.id);
105
+ }
106
+ else if (block.type === "tool_result") {
107
+ if (!calledIds.has(block.callId)) {
108
+ throw new Error(`Malformed tool sequence: tool_result for ${JSON.stringify(block.callId)} at turn ${String(turnIndex)} has no preceding tool_call`);
109
+ }
110
+ if (answeredIds.has(block.callId)) {
111
+ throw new Error(`Malformed tool sequence: duplicate tool_result for ${JSON.stringify(block.callId)} at turn ${String(turnIndex)}`);
112
+ }
113
+ answeredIds.add(block.callId);
114
+ }
115
+ }
116
+ }
117
+ }
118
+ export function createToolResultTurn(results) {
119
+ const blocks = results.map((r) => {
120
+ const raw = typeof r.content === "string" ? r.content : JSON.stringify(r.content);
121
+ const block = {
122
+ type: "tool_result",
123
+ callId: r.callId,
124
+ content: [{ type: "text", text: raw }],
125
+ };
126
+ if (r.detail !== undefined) {
127
+ block.detail = r.detail;
128
+ }
129
+ if (r.isError !== undefined) {
130
+ block.isError = r.isError;
131
+ }
132
+ return block;
133
+ });
134
+ return { role: "user", content: blocks, timestamp: Date.now() };
135
+ }
package/package.json CHANGED
@@ -1,20 +1,35 @@
1
1
  {
2
2
  "name": "@intx/inference",
3
- "version": "0.1.2",
3
+ "version": "0.2.2",
4
4
  "license": "LGPL-2.1-only",
5
5
  "type": "module",
6
6
  "exports": {
7
7
  ".": {
8
- "types": "./src/index.ts",
9
- "default": "./src/index.ts"
8
+ "intx-src": "./src/index.ts",
9
+ "types": "./dist/index.d.ts",
10
+ "default": "./dist/index.js"
11
+ },
12
+ "./providers": {
13
+ "intx-src": "./src/providers/index.ts",
14
+ "types": "./dist/providers/index.d.ts",
15
+ "default": "./dist/providers/index.js"
10
16
  }
11
17
  },
12
18
  "dependencies": {
13
- "@intx/log": "0.0.0",
14
- "@intx/types": "0.0.0",
19
+ "@intx/log": "0.2.2",
20
+ "@intx/types": "0.2.2",
15
21
  "arktype": "^2.1.29"
16
22
  },
17
23
  "devDependencies": {
18
- "@intx/mime": "0.0.0"
24
+ "@intx/mime": "0.2.2"
25
+ },
26
+ "files": [
27
+ "dist",
28
+ "README.md",
29
+ "LICENSE"
30
+ ],
31
+ "sideEffects": false,
32
+ "publishConfig": {
33
+ "access": "public"
19
34
  }
20
35
  }
package/src/actions.ts DELETED
@@ -1,245 +0,0 @@
1
- // Action types and validation for the agent reactor.
2
- //
3
- // The reactor validates the set of actions returned by the director before
4
- // executing any of them. Invalid combinations produce a reactor.error rather
5
- // than partial execution — the reactor does not guess intent.
6
- //
7
- // Validation rules (INFERENCE.md § Action Validation):
8
- // - At most one `infer` action.
9
- // - At most one `done` action.
10
- // - At most one `reply` action.
11
- // - `infer` + `done` together is invalid.
12
- // - `reply` + `infer` together is invalid.
13
- // - `reply` + `execute_tools` together is invalid.
14
- // - `reply` + `done` together is invalid.
15
- // - `reply` + `suspend` together is invalid.
16
- // - `wait` + `infer` together is invalid.
17
- // - `wait` + `execute_tools` together is invalid.
18
- // - `wait` + `suspend` together is invalid.
19
- // - `wait` + `reply` together is invalid.
20
- // - `wait` + `done` together is invalid.
21
- // - `suspend` cannot appear alongside `infer` or `execute_tools`.
22
- // - `fork` is composable — may appear alongside any other action.
23
- // - At most one `checkpoint` action; composable with any other action.
24
- // - At most one `wait` action.
25
- // - Multiple `execute_tools` are merged into a single parallel batch.
26
- // - `emit` is always valid and composable.
27
- // - At most one `compact` action; composable with `checkpoint`, `emit`, and
28
- // `fork`. Not composable with `infer`, `execute_tools`, `reply`, `suspend`,
29
- // `wait`, or `done`. Context-overflow recovery runs compaction in its own
30
- // cycle and re-infers on the next director invocation.
31
-
32
- import type { ReactorAction, ToolCall } from "@intx/types/runtime";
33
-
34
- export type ValidationResult =
35
- | { ok: true; normalized: ReactorAction[] }
36
- | { ok: false; error: string };
37
-
38
- /**
39
- * Validate and normalize a set of actions returned by the director.
40
- *
41
- * On success, returns the normalized action list with multiple `execute_tools`
42
- * collapsed into a single batched action. On failure, returns a diagnostic
43
- * error string.
44
- */
45
- export function validateActions(
46
- actions: ReactorAction | ReactorAction[],
47
- ): ValidationResult {
48
- const list = Array.isArray(actions) ? actions : [actions];
49
-
50
- // An empty action list means "no-op, keep waiting for the next event."
51
- // This is valid — the reactor loop continues to waitForEvent().
52
- if (list.length === 0) {
53
- return { ok: true, normalized: [] };
54
- }
55
-
56
- const inferActions = list.filter((a) => a.type === "infer");
57
- const doneActions = list.filter((a) => a.type === "done");
58
- const replyActions = list.filter((a) => a.type === "reply");
59
- const suspendActions = list.filter((a) => a.type === "suspend");
60
- const executeActions = list.filter(
61
- (a): a is Extract<ReactorAction, { type: "execute_tools" }> =>
62
- a.type === "execute_tools",
63
- );
64
- const waitActions = list.filter((a) => a.type === "wait");
65
- const forkActions = list.filter((a) => a.type === "fork");
66
- const emitActions = list.filter((a) => a.type === "emit");
67
- const checkpointActions = list.filter((a) => a.type === "checkpoint");
68
- const compactActions = list.filter((a) => a.type === "compact");
69
-
70
- if (checkpointActions.length > 1) {
71
- return {
72
- ok: false,
73
- error: "Multiple checkpoint actions are not allowed",
74
- };
75
- }
76
-
77
- if (inferActions.length > 1) {
78
- return { ok: false, error: "Multiple infer actions are not allowed" };
79
- }
80
-
81
- if (doneActions.length > 1) {
82
- return { ok: false, error: "Multiple done actions are not allowed" };
83
- }
84
-
85
- if (inferActions.length > 0 && doneActions.length > 0) {
86
- return { ok: false, error: "infer and done cannot appear together" };
87
- }
88
-
89
- if (replyActions.length > 1) {
90
- return { ok: false, error: "Multiple reply actions are not allowed" };
91
- }
92
-
93
- if (replyActions.length > 0 && inferActions.length > 0) {
94
- return { ok: false, error: "reply and infer cannot appear together" };
95
- }
96
-
97
- if (replyActions.length > 0 && executeActions.length > 0) {
98
- return {
99
- ok: false,
100
- error: "reply and execute_tools cannot appear together",
101
- };
102
- }
103
-
104
- if (replyActions.length > 0 && doneActions.length > 0) {
105
- return { ok: false, error: "reply and done cannot appear together" };
106
- }
107
-
108
- if (replyActions.length > 0 && suspendActions.length > 0) {
109
- return { ok: false, error: "reply and suspend cannot appear together" };
110
- }
111
-
112
- if (suspendActions.length > 0) {
113
- if (inferActions.length > 0) {
114
- return {
115
- ok: false,
116
- error: "suspend cannot appear alongside infer",
117
- };
118
- }
119
- if (executeActions.length > 0) {
120
- return {
121
- ok: false,
122
- error: "suspend cannot appear alongside execute_tools",
123
- };
124
- }
125
- if (suspendActions.length > 1) {
126
- return { ok: false, error: "Multiple suspend actions are not allowed" };
127
- }
128
- }
129
-
130
- if (waitActions.length > 1) {
131
- return { ok: false, error: "Multiple wait actions are not allowed" };
132
- }
133
-
134
- if (waitActions.length > 0) {
135
- if (inferActions.length > 0) {
136
- return { ok: false, error: "wait and infer cannot appear together" };
137
- }
138
- if (executeActions.length > 0) {
139
- return {
140
- ok: false,
141
- error: "wait and execute_tools cannot appear together",
142
- };
143
- }
144
- if (suspendActions.length > 0) {
145
- return { ok: false, error: "wait and suspend cannot appear together" };
146
- }
147
- if (replyActions.length > 0) {
148
- return { ok: false, error: "wait and reply cannot appear together" };
149
- }
150
- if (doneActions.length > 0) {
151
- return { ok: false, error: "wait and done cannot appear together" };
152
- }
153
- }
154
-
155
- if (compactActions.length > 1) {
156
- return { ok: false, error: "Multiple compact actions are not allowed" };
157
- }
158
-
159
- if (compactActions.length > 0) {
160
- if (inferActions.length > 0) {
161
- return { ok: false, error: "compact and infer cannot appear together" };
162
- }
163
- if (executeActions.length > 0) {
164
- return {
165
- ok: false,
166
- error: "compact and execute_tools cannot appear together",
167
- };
168
- }
169
- if (replyActions.length > 0) {
170
- return { ok: false, error: "compact and reply cannot appear together" };
171
- }
172
- if (suspendActions.length > 0) {
173
- return { ok: false, error: "compact and suspend cannot appear together" };
174
- }
175
- if (waitActions.length > 0) {
176
- return { ok: false, error: "compact and wait cannot appear together" };
177
- }
178
- if (doneActions.length > 0) {
179
- return { ok: false, error: "compact and done cannot appear together" };
180
- }
181
- }
182
-
183
- // Verify fork actions have unique IDs.
184
- const forkIds = forkActions.map(
185
- (a) => (a as Extract<ReactorAction, { type: "fork" }>).forkId,
186
- );
187
- const uniqueForkIds = new Set(forkIds);
188
- if (forkIds.length !== uniqueForkIds.size) {
189
- return { ok: false, error: "Duplicate fork IDs in action list" };
190
- }
191
-
192
- // Build normalized list: collapse execute_tools into one parallel batch.
193
- const normalized: ReactorAction[] = [];
194
-
195
- for (const a of checkpointActions) {
196
- normalized.push(a);
197
- }
198
-
199
- for (const a of emitActions) {
200
- normalized.push(a);
201
- }
202
-
203
- for (const a of forkActions) {
204
- normalized.push(a);
205
- }
206
-
207
- for (const a of compactActions) {
208
- normalized.push(a);
209
- }
210
-
211
- if (executeActions.length > 0) {
212
- const merged: ToolCall[] = executeActions.flatMap((a) => a.calls);
213
- const allAddToHistory = executeActions.every(
214
- (a) => a.addToHistory !== false,
215
- );
216
- normalized.push({
217
- type: "execute_tools",
218
- calls: merged,
219
- parallel: true,
220
- ...(!allAddToHistory ? { addToHistory: false } : {}),
221
- });
222
- }
223
-
224
- for (const a of replyActions) {
225
- normalized.push(a);
226
- }
227
-
228
- for (const a of inferActions) {
229
- normalized.push(a);
230
- }
231
-
232
- for (const a of suspendActions) {
233
- normalized.push(a);
234
- }
235
-
236
- for (const a of waitActions) {
237
- normalized.push(a);
238
- }
239
-
240
- for (const a of doneActions) {
241
- normalized.push(a);
242
- }
243
-
244
- return { ok: true, normalized };
245
- }
package/src/adapter.ts DELETED
@@ -1,57 +0,0 @@
1
- import type {
2
- ConversationTurn,
3
- InferenceEvent,
4
- InferenceOptions,
5
- } from "@intx/types/runtime";
6
-
7
- // The request shape the harness passes to fetch.
8
- export type BuiltRequest = {
9
- url: string;
10
- headers: Record<string, string>;
11
- body: string;
12
- };
13
-
14
- // A request builder takes the internal message format and produces a
15
- // provider-specific HTTP request. Pure function — no state, no side effects.
16
- export type RequestBuilder = (
17
- messages: ConversationTurn[],
18
- model: string,
19
- options: InferenceOptions,
20
- ) => BuiltRequest;
21
-
22
- // A response parser converts one SSE data payload string into zero or more
23
- // internal inference events. May close over per-request state (e.g., for
24
- // correlating content block indices with tool call IDs). Each adapter
25
- // instance is created per inference call, so state does not leak across
26
- // requests.
27
- //
28
- // The parser may return an empty array for events it doesn't care about
29
- // (e.g., Anthropic's ping events). It MAY throw `ProtocolMismatchError`
30
- // when the upstream chunk violates the provider's protocol (malformed
31
- // JSON, schema validation failure, out-of-order events); the harness's
32
- // stream-error catch converts that into an `inference.error` with
33
- // category `"protocol_mismatch"` via `classifyStreamError`. No other
34
- // throw type is permitted, and adapter-returned `inference.error` or
35
- // `inference.done` events are silently dropped — the harness owns
36
- // emission of those terminal types and the only path through which
37
- // adapter-detected failures can surface is `ProtocolMismatchError`.
38
- export type ResponseParser = (sseData: string) => InferenceEvent[];
39
-
40
- // An adapter pairs a request builder with a response parser. Registration
41
- // is a map keyed by provider identifier — no class hierarchy required.
42
-
43
- // Extracts a retry delay from provider-specific response headers on a 429.
44
- // Returns milliseconds to wait, or undefined if no retry info is available.
45
- export type RetryAfterExtractor = (headers: Headers) => number | undefined;
46
-
47
- // Extracts a pacing delay from response headers on ANY response (including
48
- // success). Checks remaining rate limit capacity and returns how long to
49
- // wait before the next request, or undefined if no pacing is needed.
50
- export type PacingExtractor = (headers: Headers) => number | undefined;
51
-
52
- export type ProviderAdapter = {
53
- buildRequest: RequestBuilder;
54
- parseResponse: ResponseParser;
55
- extractRetryAfterMs?: RetryAfterExtractor;
56
- extractPacingDelayMs?: PacingExtractor;
57
- };