@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.
- package/LICENSE +176 -0
- package/dist/actions.d.ts +16 -0
- package/dist/actions.js +200 -0
- package/dist/adapter.d.ts +38 -0
- package/dist/adapter.js +31 -0
- package/dist/assembly.d.ts +68 -0
- package/dist/assembly.js +132 -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 +32 -0
- package/dist/authz-extension.js +100 -0
- package/dist/correlation.d.ts +25 -0
- package/dist/correlation.js +32 -0
- package/dist/default-director.d.ts +111 -0
- package/dist/default-director.js +199 -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 +27 -0
- package/dist/gates.js +80 -0
- package/dist/harness.d.ts +147 -0
- package/dist/harness.js +1319 -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 +33 -0
- package/dist/providers/anthropic.js +670 -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 +3 -0
- package/dist/providers/google-genai.js +1196 -0
- package/dist/providers/index.d.ts +38 -0
- package/dist/providers/index.js +56 -0
- package/dist/providers/openai.d.ts +3 -0
- package/dist/providers/openai.js +609 -0
- package/dist/reactor.d.ts +50 -0
- package/dist/reactor.js +920 -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 +117 -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 +21 -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/transform.ts
DELETED
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
// Cross-provider message transformation.
|
|
2
|
-
//
|
|
3
|
-
// When conversations cross provider boundaries the message history must be
|
|
4
|
-
// adapted: thinking blocks are stripped for foreign models, orphaned tool
|
|
5
|
-
// calls receive synthetic error results, and tool call IDs are normalized to
|
|
6
|
-
// a portable format.
|
|
7
|
-
//
|
|
8
|
-
// Transformation runs automatically when the target model differs from a
|
|
9
|
-
// message's originating model. The originating model is tracked per-message,
|
|
10
|
-
// not per-conversation.
|
|
11
|
-
|
|
12
|
-
import type { ConversationTurn, ContentBlock } from "@intx/types/runtime";
|
|
13
|
-
|
|
14
|
-
export type TransformOptions = {
|
|
15
|
-
targetModel: string;
|
|
16
|
-
// When true, keep thinking blocks for messages that originated from the
|
|
17
|
-
// same model. When false, strip all thinking blocks (cross-provider replay).
|
|
18
|
-
keepThinkingForSameModel?: boolean;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
export function transformMessages(
|
|
22
|
-
messages: ConversationTurn[],
|
|
23
|
-
options: TransformOptions,
|
|
24
|
-
): ConversationTurn[] {
|
|
25
|
-
const { targetModel, keepThinkingForSameModel = true } = options;
|
|
26
|
-
|
|
27
|
-
// First pass: strip thinking blocks and filter aborted assistant messages.
|
|
28
|
-
const filtered = messages
|
|
29
|
-
.map((msg): ConversationTurn | null => {
|
|
30
|
-
if (msg.role === "assistant") {
|
|
31
|
-
const isSameModel = msg.model === targetModel;
|
|
32
|
-
const keepThinking = keepThinkingForSameModel && isSameModel;
|
|
33
|
-
|
|
34
|
-
const filteredContent = msg.content.filter((block) => {
|
|
35
|
-
if (block.type === "thinking") {
|
|
36
|
-
return keepThinking;
|
|
37
|
-
}
|
|
38
|
-
return true;
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
// Filter out assistant messages that have no text or tool calls
|
|
42
|
-
// (aborted/error messages with only thinking blocks removed).
|
|
43
|
-
const hasUsableContent = filteredContent.some(
|
|
44
|
-
(b) => b.type === "text" || b.type === "tool_call",
|
|
45
|
-
);
|
|
46
|
-
if (!hasUsableContent && filteredContent.length === 0) {
|
|
47
|
-
return null;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return { ...msg, content: filteredContent };
|
|
51
|
-
}
|
|
52
|
-
return msg;
|
|
53
|
-
})
|
|
54
|
-
.filter((msg): msg is ConversationTurn => msg !== null);
|
|
55
|
-
|
|
56
|
-
// Second pass: inject synthetic tool results for orphaned tool calls.
|
|
57
|
-
return injectOrphanedToolResults(filtered);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function injectOrphanedToolResults(
|
|
61
|
-
messages: ConversationTurn[],
|
|
62
|
-
): ConversationTurn[] {
|
|
63
|
-
const result: ConversationTurn[] = [];
|
|
64
|
-
|
|
65
|
-
for (let i = 0; i < messages.length; i++) {
|
|
66
|
-
const msg = messages[i];
|
|
67
|
-
if (msg === undefined) continue;
|
|
68
|
-
result.push(msg);
|
|
69
|
-
|
|
70
|
-
if (msg.role !== "assistant") continue;
|
|
71
|
-
|
|
72
|
-
const toolCalls = msg.content.filter(
|
|
73
|
-
(b): b is Extract<ContentBlock, { type: "tool_call" }> =>
|
|
74
|
-
b.type === "tool_call",
|
|
75
|
-
);
|
|
76
|
-
|
|
77
|
-
if (toolCalls.length === 0) continue;
|
|
78
|
-
|
|
79
|
-
// Collect tool call IDs from this assistant message.
|
|
80
|
-
const calledIds = new Set(toolCalls.map((tc) => tc.id));
|
|
81
|
-
|
|
82
|
-
// Check the following messages for results that cover these calls.
|
|
83
|
-
const coveredIds = new Set<string>();
|
|
84
|
-
for (let j = i + 1; j < messages.length; j++) {
|
|
85
|
-
const next = messages[j];
|
|
86
|
-
if (next === undefined) break;
|
|
87
|
-
if (next.role !== "user") break;
|
|
88
|
-
|
|
89
|
-
for (const block of next.content) {
|
|
90
|
-
if (block.type === "tool_result") {
|
|
91
|
-
coveredIds.add(block.callId);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Find which tool calls have no corresponding result.
|
|
97
|
-
const orphanedIds = [...calledIds].filter((id) => !coveredIds.has(id));
|
|
98
|
-
|
|
99
|
-
if (orphanedIds.length === 0) continue;
|
|
100
|
-
|
|
101
|
-
// Inject a synthetic user message with error tool results for each orphan.
|
|
102
|
-
const syntheticBlocks: ContentBlock[] = orphanedIds.map((id) => ({
|
|
103
|
-
type: "tool_result" as const,
|
|
104
|
-
callId: id,
|
|
105
|
-
content: [
|
|
106
|
-
{
|
|
107
|
-
type: "text" as const,
|
|
108
|
-
text: "Tool execution was interrupted before completion.",
|
|
109
|
-
},
|
|
110
|
-
],
|
|
111
|
-
isError: true,
|
|
112
|
-
}));
|
|
113
|
-
|
|
114
|
-
result.push({
|
|
115
|
-
role: "user",
|
|
116
|
-
content: syntheticBlocks,
|
|
117
|
-
timestamp: Date.now(),
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
return result;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// ---------------------------------------------------------------------------
|
|
125
|
-
// Tool call ID normalization
|
|
126
|
-
//
|
|
127
|
-
// OpenAI Responses API generates 450+ character IDs with pipes. Anthropic
|
|
128
|
-
// has strict format requirements. IDs are normalized to a short portable
|
|
129
|
-
// format with a bidirectional map for round-trip fidelity.
|
|
130
|
-
// ---------------------------------------------------------------------------
|
|
131
|
-
|
|
132
|
-
const PORTABLE_ID_PREFIX = "tc_";
|
|
133
|
-
|
|
134
|
-
export type IDNormalizer = {
|
|
135
|
-
normalize(providerId: string): string;
|
|
136
|
-
resolve(portableId: string): string | undefined;
|
|
137
|
-
};
|
|
138
|
-
|
|
139
|
-
export function createIDNormalizer(): IDNormalizer {
|
|
140
|
-
const portableToProvider = new Map<string, string>();
|
|
141
|
-
const providerToPortable = new Map<string, string>();
|
|
142
|
-
let counter = 0;
|
|
143
|
-
|
|
144
|
-
return {
|
|
145
|
-
normalize(providerId: string): string {
|
|
146
|
-
const existing = providerToPortable.get(providerId);
|
|
147
|
-
if (existing !== undefined) return existing;
|
|
148
|
-
|
|
149
|
-
const portable = `${PORTABLE_ID_PREFIX}${(++counter).toString(36)}`;
|
|
150
|
-
providerToPortable.set(providerId, portable);
|
|
151
|
-
portableToProvider.set(portable, providerId);
|
|
152
|
-
return portable;
|
|
153
|
-
},
|
|
154
|
-
|
|
155
|
-
resolve(portableId: string): string | undefined {
|
|
156
|
-
return portableToProvider.get(portableId);
|
|
157
|
-
},
|
|
158
|
-
};
|
|
159
|
-
}
|
package/src/transforms/index.ts
DELETED
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect } from "bun:test";
|
|
2
|
-
|
|
3
|
-
import { createSizeCapTransform } from "./size-cap";
|
|
4
|
-
import type {
|
|
5
|
-
ContextStore,
|
|
6
|
-
ReactorState,
|
|
7
|
-
StrategyContext,
|
|
8
|
-
ToolCall,
|
|
9
|
-
ToolResult,
|
|
10
|
-
} from "@intx/types/runtime";
|
|
11
|
-
|
|
12
|
-
function emptyState(): ReactorState {
|
|
13
|
-
return {
|
|
14
|
-
turns: [],
|
|
15
|
-
activeForks: [],
|
|
16
|
-
pendingOperations: [],
|
|
17
|
-
activeGates: [],
|
|
18
|
-
tokenUsage: {
|
|
19
|
-
input: 0,
|
|
20
|
-
output: 0,
|
|
21
|
-
cacheRead: 0,
|
|
22
|
-
cacheWrite: 0,
|
|
23
|
-
thinking: 0,
|
|
24
|
-
},
|
|
25
|
-
lastCycleUsage: null,
|
|
26
|
-
lastCycleSource: null,
|
|
27
|
-
sessionId: "test-session",
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function emptyContext(): StrategyContext {
|
|
32
|
-
return { state: emptyState(), trigger: "test" };
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function recordingWriteBlob(): {
|
|
36
|
-
store: Pick<ContextStore, "writeBlob">;
|
|
37
|
-
calls: { key: string; bytes: Uint8Array; contentType?: string }[];
|
|
38
|
-
} {
|
|
39
|
-
const calls: { key: string; bytes: Uint8Array; contentType?: string }[] = [];
|
|
40
|
-
const store: Pick<ContextStore, "writeBlob"> = {
|
|
41
|
-
async writeBlob(key, bytes, contentType) {
|
|
42
|
-
calls.push({
|
|
43
|
-
key,
|
|
44
|
-
bytes,
|
|
45
|
-
...(contentType !== undefined ? { contentType } : {}),
|
|
46
|
-
});
|
|
47
|
-
},
|
|
48
|
-
};
|
|
49
|
-
return { store, calls };
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function call(id: string, name = "tool"): ToolCall {
|
|
53
|
-
return { id, name, arguments: {} };
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
describe("createSizeCapTransform", () => {
|
|
57
|
-
test("passes a within-cap result through unchanged with a within-cap record", async () => {
|
|
58
|
-
const { store, calls } = recordingWriteBlob();
|
|
59
|
-
const transform = createSizeCapTransform({
|
|
60
|
-
maxChars: 100,
|
|
61
|
-
contextStore: store,
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
const result: ToolResult = { callId: "c1", content: "hello world" };
|
|
65
|
-
const out = await transform.apply(
|
|
66
|
-
{ call: call("c1"), result },
|
|
67
|
-
emptyContext(),
|
|
68
|
-
);
|
|
69
|
-
|
|
70
|
-
expect(out.output).toBe(result);
|
|
71
|
-
expect(out.record.strategy).toBe("size-cap");
|
|
72
|
-
expect(out.record.reason).toBe("within-cap");
|
|
73
|
-
expect(out.record.decisions["callId"]).toBe("c1");
|
|
74
|
-
expect(out.record.decisions["length"]).toBe(11);
|
|
75
|
-
expect(out.blobs).toBeUndefined();
|
|
76
|
-
expect(calls).toHaveLength(0);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
test("oversize result writes a blob and produces a truncated marker", async () => {
|
|
80
|
-
const { store, calls } = recordingWriteBlob();
|
|
81
|
-
const transform = createSizeCapTransform({
|
|
82
|
-
maxChars: 10,
|
|
83
|
-
contextStore: store,
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
const full = "x".repeat(50);
|
|
87
|
-
const result: ToolResult = { callId: "callABC", content: full };
|
|
88
|
-
const out = await transform.apply(
|
|
89
|
-
{ call: call("callABC"), result },
|
|
90
|
-
emptyContext(),
|
|
91
|
-
);
|
|
92
|
-
|
|
93
|
-
expect(out.output).not.toBe(result);
|
|
94
|
-
const content = out.output.content;
|
|
95
|
-
if (typeof content !== "string") throw new Error("expected string content");
|
|
96
|
-
expect(content.startsWith("xxxxxxxxxx\n")).toBe(true);
|
|
97
|
-
expect(content).toContain("tool-output:///callABC");
|
|
98
|
-
expect(content).toContain("omitted 40 chars");
|
|
99
|
-
expect(out.record.strategy).toBe("size-cap");
|
|
100
|
-
expect(out.record.reason).toBe("exceeded-cap");
|
|
101
|
-
expect(out.record.decisions["callId"]).toBe("callABC");
|
|
102
|
-
expect(out.record.decisions["originalLength"]).toBe(50);
|
|
103
|
-
expect(out.record.decisions["kept"]).toBe(10);
|
|
104
|
-
expect(out.record.decisions["spillKey"]).toBe("callABC");
|
|
105
|
-
expect(out.record.decisions["spillURI"]).toBe("tool-output:///callABC");
|
|
106
|
-
|
|
107
|
-
// Blob is emitted for the reactor to persist.
|
|
108
|
-
expect(out.blobs).toBeDefined();
|
|
109
|
-
expect(out.blobs).toHaveLength(1);
|
|
110
|
-
expect(out.blobs?.[0]?.key).toBe("callABC");
|
|
111
|
-
expect(out.blobs?.[0]?.contentType).toBe("text/plain");
|
|
112
|
-
|
|
113
|
-
// The transform itself also wrote the blob through the supplied store
|
|
114
|
-
// (so reactors that consume blobs from the chain stay consistent with
|
|
115
|
-
// the transform's contract).
|
|
116
|
-
expect(calls).toHaveLength(1);
|
|
117
|
-
expect(calls[0]?.key).toBe("callABC");
|
|
118
|
-
expect(calls[0]?.contentType).toBe("text/plain");
|
|
119
|
-
expect(new TextDecoder().decode(calls[0]?.bytes)).toBe(full);
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
test("structured (non-string) content is JSON-stringified before length check", async () => {
|
|
123
|
-
const { store } = recordingWriteBlob();
|
|
124
|
-
const transform = createSizeCapTransform({
|
|
125
|
-
maxChars: 5,
|
|
126
|
-
contextStore: store,
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
const result: ToolResult = {
|
|
130
|
-
callId: "json-1",
|
|
131
|
-
content: { hello: "world", n: 42 },
|
|
132
|
-
};
|
|
133
|
-
const out = await transform.apply(
|
|
134
|
-
{ call: call("json-1"), result },
|
|
135
|
-
emptyContext(),
|
|
136
|
-
);
|
|
137
|
-
expect(out.record.reason).toBe("exceeded-cap");
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
test("preserves isError and detail on the transformed output", async () => {
|
|
141
|
-
const { store } = recordingWriteBlob();
|
|
142
|
-
const transform = createSizeCapTransform({
|
|
143
|
-
maxChars: 4,
|
|
144
|
-
contextStore: store,
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
const result: ToolResult = {
|
|
148
|
-
callId: "x1",
|
|
149
|
-
content: "abcdefghij",
|
|
150
|
-
isError: true,
|
|
151
|
-
detail: { stack: "..." },
|
|
152
|
-
};
|
|
153
|
-
const out = await transform.apply(
|
|
154
|
-
{ call: call("x1"), result },
|
|
155
|
-
emptyContext(),
|
|
156
|
-
);
|
|
157
|
-
|
|
158
|
-
expect(out.output.isError).toBe(true);
|
|
159
|
-
expect(out.output.detail).toEqual({ stack: "..." });
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
test("rejects non-positive maxChars at construction time", () => {
|
|
163
|
-
const { store } = recordingWriteBlob();
|
|
164
|
-
let thrown: Error | undefined;
|
|
165
|
-
try {
|
|
166
|
-
createSizeCapTransform({ maxChars: 0, contextStore: store });
|
|
167
|
-
} catch (cause) {
|
|
168
|
-
thrown = cause instanceof Error ? cause : new Error(String(cause));
|
|
169
|
-
}
|
|
170
|
-
expect(thrown?.message).toContain("positive finite");
|
|
171
|
-
});
|
|
172
|
-
});
|
|
@@ -1,110 +0,0 @@
|
|
|
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
|
-
|
|
12
|
-
import type {
|
|
13
|
-
ContextStore,
|
|
14
|
-
StrategyContext,
|
|
15
|
-
StrategyResult,
|
|
16
|
-
ToolResult,
|
|
17
|
-
ToolResultTransform,
|
|
18
|
-
} from "@intx/types/runtime";
|
|
19
|
-
|
|
20
|
-
const SIZE_CAP_VERSION = "1";
|
|
21
|
-
const SIZE_CAP_NAME = "size-cap";
|
|
22
|
-
|
|
23
|
-
export type SizeCapTransformOptions = {
|
|
24
|
-
maxChars: number;
|
|
25
|
-
contextStore: Pick<ContextStore, "writeBlob">;
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Create a `ToolResultTransform` that caps inline tool result content at
|
|
30
|
-
* `maxChars` characters. Oversized results are spilled to the context store
|
|
31
|
-
* via `writeBlob` and the inline content becomes a truncated marker
|
|
32
|
-
* referencing the spill by `tool-output:///{callId}` URI.
|
|
33
|
-
*/
|
|
34
|
-
export function createSizeCapTransform(
|
|
35
|
-
options: SizeCapTransformOptions,
|
|
36
|
-
): ToolResultTransform {
|
|
37
|
-
const { maxChars, contextStore } = options;
|
|
38
|
-
if (!Number.isFinite(maxChars) || maxChars <= 0) {
|
|
39
|
-
throw new Error(
|
|
40
|
-
`createSizeCapTransform: maxChars must be a positive finite number, got ${String(maxChars)}`,
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return {
|
|
45
|
-
name: SIZE_CAP_NAME,
|
|
46
|
-
version: SIZE_CAP_VERSION,
|
|
47
|
-
async apply(
|
|
48
|
-
input: { call: { id: string; name: string }; result: ToolResult },
|
|
49
|
-
_ctx: StrategyContext,
|
|
50
|
-
): Promise<StrategyResult<ToolResult>> {
|
|
51
|
-
const { call, result } = input;
|
|
52
|
-
const text =
|
|
53
|
-
typeof result.content === "string"
|
|
54
|
-
? result.content
|
|
55
|
-
: JSON.stringify(result.content);
|
|
56
|
-
|
|
57
|
-
if (text.length <= maxChars) {
|
|
58
|
-
return {
|
|
59
|
-
output: result,
|
|
60
|
-
record: {
|
|
61
|
-
strategy: SIZE_CAP_NAME,
|
|
62
|
-
version: SIZE_CAP_VERSION,
|
|
63
|
-
parameters: { maxChars },
|
|
64
|
-
reason: "within-cap",
|
|
65
|
-
decisions: { callId: call.id, length: text.length },
|
|
66
|
-
},
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const omitted = text.length - maxChars;
|
|
71
|
-
const kept = text.slice(0, maxChars);
|
|
72
|
-
const spillURI = `tool-output:///${call.id}`;
|
|
73
|
-
const marker =
|
|
74
|
-
`${kept}\n[Tool output truncated: omitted ${String(omitted)} chars. ` +
|
|
75
|
-
`Full output available at ${spillURI} -- use read_file with that URI to see the rest.]`;
|
|
76
|
-
|
|
77
|
-
const bytes = new TextEncoder().encode(text);
|
|
78
|
-
await contextStore.writeBlob(call.id, bytes, "text/plain");
|
|
79
|
-
|
|
80
|
-
const output: ToolResult = {
|
|
81
|
-
...result,
|
|
82
|
-
content: marker,
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
return {
|
|
86
|
-
output,
|
|
87
|
-
record: {
|
|
88
|
-
strategy: SIZE_CAP_NAME,
|
|
89
|
-
version: SIZE_CAP_VERSION,
|
|
90
|
-
parameters: { maxChars },
|
|
91
|
-
reason: "exceeded-cap",
|
|
92
|
-
decisions: {
|
|
93
|
-
callId: call.id,
|
|
94
|
-
originalLength: text.length,
|
|
95
|
-
kept: maxChars,
|
|
96
|
-
spillKey: call.id,
|
|
97
|
-
spillURI,
|
|
98
|
-
},
|
|
99
|
-
},
|
|
100
|
-
blobs: [
|
|
101
|
-
{
|
|
102
|
-
key: call.id,
|
|
103
|
-
bytes,
|
|
104
|
-
contentType: "text/plain",
|
|
105
|
-
},
|
|
106
|
-
],
|
|
107
|
-
};
|
|
108
|
-
},
|
|
109
|
-
};
|
|
110
|
-
}
|
package/src/turns.ts
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
ConversationTurn,
|
|
3
|
-
ContentBlock,
|
|
4
|
-
AssistantTurn,
|
|
5
|
-
InboundMessage,
|
|
6
|
-
ToolCall,
|
|
7
|
-
ToolResult,
|
|
8
|
-
} from "@intx/types/runtime";
|
|
9
|
-
|
|
10
|
-
export type { ConversationTurn, ContentBlock, AssistantTurn };
|
|
11
|
-
|
|
12
|
-
export type { ToolCall, ToolResult };
|
|
13
|
-
|
|
14
|
-
export function createInboundTurn(
|
|
15
|
-
message: InboundMessage,
|
|
16
|
-
): ConversationTurn | null {
|
|
17
|
-
const content = message.content ?? "";
|
|
18
|
-
if (content.length === 0) return null;
|
|
19
|
-
|
|
20
|
-
const { from, subject } = message.headers;
|
|
21
|
-
const envelope: string[] = [];
|
|
22
|
-
if (from.length > 0) envelope.push(`[From: ${from}]`);
|
|
23
|
-
if (subject !== undefined && subject.length > 0) {
|
|
24
|
-
envelope.push(`[Subject: ${subject}]`);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const text =
|
|
28
|
-
envelope.length > 0 ? `${envelope.join("\n")}\n\n${content}` : content;
|
|
29
|
-
return {
|
|
30
|
-
role: "user",
|
|
31
|
-
content: [{ type: "text", text }],
|
|
32
|
-
timestamp: Date.now(),
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function createToolResultTurn(results: ToolResult[]): ConversationTurn {
|
|
37
|
-
const blocks: ContentBlock[] = results.map((r) => {
|
|
38
|
-
const raw =
|
|
39
|
-
typeof r.content === "string" ? r.content : JSON.stringify(r.content);
|
|
40
|
-
const block: Extract<ContentBlock, { type: "tool_result" }> = {
|
|
41
|
-
type: "tool_result",
|
|
42
|
-
callId: r.callId,
|
|
43
|
-
content: [{ type: "text" as const, text: raw }],
|
|
44
|
-
};
|
|
45
|
-
if (r.detail !== undefined) {
|
|
46
|
-
block.detail = r.detail;
|
|
47
|
-
}
|
|
48
|
-
if (r.isError !== undefined) {
|
|
49
|
-
block.isError = r.isError;
|
|
50
|
-
}
|
|
51
|
-
return block;
|
|
52
|
-
});
|
|
53
|
-
return { role: "user", content: blocks, timestamp: Date.now() };
|
|
54
|
-
}
|
package/tsconfig.json
DELETED