@crewhaus/compaction-snip 0.1.1 → 0.1.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 (2) hide show
  1. package/package.json +5 -10
  2. package/src/index.test.ts +62 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crewhaus/compaction-snip",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
5
  "description": "Pure middle-message removal with tool-use/result orphan defense",
6
6
  "main": "src/index.ts",
@@ -17,8 +17,8 @@
17
17
  "license": "Apache-2.0",
18
18
  "author": {
19
19
  "name": "Max Meier",
20
- "email": "max@studiomax.io",
21
- "url": "https://studiomax.io"
20
+ "email": "max@crewhaus.ai",
21
+ "url": "https://crewhaus.ai"
22
22
  },
23
23
  "repository": {
24
24
  "type": "git",
@@ -30,12 +30,7 @@
30
30
  "url": "https://github.com/crewhaus/factory/issues"
31
31
  },
32
32
  "publishConfig": {
33
- "access": "restricted"
33
+ "access": "public"
34
34
  },
35
- "files": [
36
- "src",
37
- "README.md",
38
- "LICENSE",
39
- "NOTICE"
40
- ]
35
+ "files": ["src", "README.md", "LICENSE", "NOTICE"]
41
36
  }
package/src/index.test.ts CHANGED
@@ -156,4 +156,66 @@ describe("snip", () => {
156
156
  expect(useIds.has(id)).toBe(true);
157
157
  }
158
158
  });
159
+
160
+ test("tool_use in head with no matching result anywhere does not move the boundary", () => {
161
+ // index 1 holds a tool_use whose tool_result is absent from the entire
162
+ // conversation. The head-side defense must scan the whole array, find no
163
+ // result (findToolResultIndex → -1), and leave the snip boundary alone.
164
+ const messages: Anthropic.MessageParam[] = [
165
+ userMsg("u0"),
166
+ {
167
+ role: "assistant",
168
+ content: [{ type: "tool_use", id: "tu_orphan", name: "Bash", input: { cmd: "ls" } }],
169
+ },
170
+ userMsg("u2"),
171
+ asstMsg("a3"),
172
+ userMsg("u4"),
173
+ asstMsg("a5"),
174
+ userMsg("u6"),
175
+ asstMsg("a7"),
176
+ ];
177
+
178
+ const out = snip(messages, 2, 2);
179
+ // Naive cut [2..6) removes 4 messages; nothing pulls the boundary because
180
+ // the dangling tool_use has no result to rescue.
181
+ expect(out.length).toBe(5);
182
+ expect(out[2]).toEqual({
183
+ role: "assistant",
184
+ content: "[Context compacted: 4 messages removed]",
185
+ });
186
+ // The dangling tool_use stays in the head untouched (a tool_use without a
187
+ // result is API-legal; only orphan tool_results are rejected).
188
+ const flat = out.flatMap((m) => (typeof m.content === "string" ? [] : m.content));
189
+ expect(flat.some((b) => b.type === "tool_use" && b.id === "tu_orphan")).toBe(true);
190
+ });
191
+
192
+ test("pre-orphaned tool_result in tail with no use anywhere does not move the boundary", () => {
193
+ // index 6 holds a tool_result whose tool_use is absent from the entire
194
+ // conversation (the input was already orphaned). The tail-side defense must
195
+ // scan the whole array, find no use (findToolUseIndex → -1), and leave the
196
+ // snip boundary alone — input orphans are preserved, not "fixed".
197
+ const messages: Anthropic.MessageParam[] = [
198
+ userMsg("u0"),
199
+ asstMsg("a1"),
200
+ userMsg("u2"),
201
+ asstMsg("a3"),
202
+ userMsg("u4"),
203
+ asstMsg("a5"),
204
+ {
205
+ role: "user",
206
+ content: [{ type: "tool_result", tool_use_id: "tu_ghost", content: "stale" }],
207
+ },
208
+ asstMsg("a7"),
209
+ ];
210
+
211
+ const out = snip(messages, 2, 2);
212
+ // Naive cut [2..6) removes 4 messages; the pre-orphaned result stays in tail.
213
+ expect(out.length).toBe(5);
214
+ expect(out[2]).toEqual({
215
+ role: "assistant",
216
+ content: "[Context compacted: 4 messages removed]",
217
+ });
218
+ const flat = out.flatMap((m) => (typeof m.content === "string" ? [] : m.content));
219
+ expect(flat.some((b) => b.type === "tool_result" && b.tool_use_id === "tu_ghost")).toBe(true);
220
+ });
159
221
  });