@halo-sdk/strategies 1.0.0 → 1.0.1

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/README.md CHANGED
@@ -1,6 +1,60 @@
1
1
  # @halo-sdk/strategies
2
2
 
3
- Pluggable strategies for Halo AI SDK.
3
+ Pluggable strategies for context management and tool-call repair in Halo AI SDK.
4
4
 
5
- - `TruncateStrategy` — context window truncation
6
- - `BasicRepair` — truncated JSON repair
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @halo-sdk/strategies
9
+ ```
10
+
11
+ Requires `@halo-sdk/core` as a peer dependency.
12
+
13
+ ## TruncateStrategy
14
+
15
+ Automatically truncates `MessageLog` when approaching the context window limit. Drops `discardable` messages first (e.g. skill bodies), then oldest messages.
16
+
17
+ ```ts
18
+ import { Halo } from "@halo-sdk/core";
19
+ import { TruncateStrategy } from "@halo-sdk/strategies";
20
+
21
+ const agent = halo.agent({
22
+ messages: [{ role: "system", content: "You are helpful." }],
23
+ context: new TruncateStrategy({ maxTokens: 102_400 }), // default: 80% of 128K
24
+ });
25
+ ```
26
+
27
+ **Important:** The strategy is read-only on `StablePrefix` — it only modifies `MessageLog`. This preserves prefix caching.
28
+
29
+ ## BasicRepair
30
+
31
+ Fixes truncated JSON in tool-call arguments. Counts unbalanced braces/brackets and closes them.
32
+
33
+ ```ts
34
+ import { BasicRepair } from "@halo-sdk/strategies";
35
+
36
+ const agent = halo.agent({
37
+ messages: [...],
38
+ repair: new BasicRepair(),
39
+ });
40
+ ```
41
+
42
+ When the model's tool-call JSON is cut off mid-generation, `BasicRepair` tries to salvage it rather than discarding the call.
43
+
44
+ ## Custom Strategies
45
+
46
+ Implement `ContextStrategy` or `RepairStrategy` from `@halo-sdk/core` for custom behavior:
47
+
48
+ ```ts
49
+ import type { ContextStrategy, ChatMessage } from "@halo-sdk/core";
50
+
51
+ class MyStrategy implements ContextStrategy {
52
+ prepare(prefix: ChatMessage[], history: ChatMessage[], ctxMax: number) {
53
+ return { history, modified: false, droppedCount: 0 };
54
+ }
55
+ }
56
+ ```
57
+
58
+ ## Documentation
59
+
60
+ See the [Halo SDK docs](https://halo-sdk.github.io/halo-ai/en/api-reference/context-strategy) for full API reference.
package/dist/index.cjs ADDED
@@ -0,0 +1,162 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ BasicRepair: () => BasicRepair,
24
+ TruncateStrategy: () => TruncateStrategy
25
+ });
26
+ module.exports = __toCommonJS(index_exports);
27
+
28
+ // src/truncate.ts
29
+ var TruncateStrategy = class {
30
+ _maxTokens;
31
+ constructor(opts) {
32
+ this._maxTokens = opts?.maxTokens ?? 102400;
33
+ }
34
+ prepare(prefix, history, _ctxMax) {
35
+ const histEstimate = history.reduce((sum, m) => sum + m.content.length, 0);
36
+ const prefixEstimate = prefix.reduce((sum, m) => sum + m.content.length, 0);
37
+ const budgetChars = (this._maxTokens - prefixEstimate / 4) * 4;
38
+ if (histEstimate <= budgetChars * 0.9) {
39
+ return { history, modified: false, droppedCount: 0 };
40
+ }
41
+ const budgetLimit = budgetChars * 0.9;
42
+ const kept = [];
43
+ let keptTokens = 0;
44
+ for (let i = history.length - 1; i >= 0; i--) {
45
+ const msg = history[i];
46
+ if (msg.discardable) continue;
47
+ const size = msg.content.length;
48
+ if (keptTokens + size > budgetLimit) break;
49
+ keptTokens += size;
50
+ kept.unshift(msg);
51
+ }
52
+ for (let i = history.length - 1; i >= 0; i--) {
53
+ const msg = history[i];
54
+ if (!msg.discardable) continue;
55
+ const size = msg.content.length;
56
+ if (keptTokens + size > budgetLimit) break;
57
+ keptTokens += size;
58
+ const insertIdx = kept.findIndex(
59
+ (m, idx) => idx === kept.length - 1 || history.indexOf(m) > history.indexOf(msg)
60
+ );
61
+ kept.splice(insertIdx === -1 ? kept.length : insertIdx, 0, msg);
62
+ }
63
+ const droppedCount = history.length - kept.length;
64
+ return {
65
+ history: kept,
66
+ modified: droppedCount > 0,
67
+ droppedCount
68
+ };
69
+ }
70
+ };
71
+
72
+ // src/basic-repair.ts
73
+ var BasicRepair = class {
74
+ repair(toolCalls, _rawContent) {
75
+ const repaired = [];
76
+ let fixed = 0;
77
+ for (const call of toolCalls) {
78
+ const args = call.function?.arguments ?? "";
79
+ const fix = tryFixTruncatedJson(args);
80
+ if (fix !== args) {
81
+ fixed++;
82
+ repaired.push({
83
+ ...call,
84
+ function: { ...call.function, arguments: fix }
85
+ });
86
+ } else {
87
+ repaired.push(call);
88
+ }
89
+ }
90
+ return {
91
+ toolCalls: repaired,
92
+ fixed,
93
+ suppressed: 0,
94
+ notes: fixed > 0 ? [`fixed ${fixed} truncated JSON argument(s)`] : []
95
+ };
96
+ }
97
+ };
98
+ function tryFixTruncatedJson(raw) {
99
+ if (!raw || !raw.trim()) return raw;
100
+ try {
101
+ JSON.parse(raw);
102
+ return raw;
103
+ } catch {
104
+ }
105
+ let braces = 0;
106
+ let brackets = 0;
107
+ let inString = false;
108
+ let escaped = false;
109
+ for (const ch of raw) {
110
+ if (escaped) {
111
+ escaped = false;
112
+ continue;
113
+ }
114
+ if (ch === "\\") {
115
+ escaped = true;
116
+ continue;
117
+ }
118
+ if (ch === '"') {
119
+ inString = !inString;
120
+ continue;
121
+ }
122
+ if (inString) continue;
123
+ if (ch === "{") braces++;
124
+ if (ch === "}") braces--;
125
+ if (ch === "[") brackets++;
126
+ if (ch === "]") brackets--;
127
+ }
128
+ if (inString) {
129
+ let fixed2 = raw;
130
+ if (braces > 0 || brackets > 0) fixed2 += '"';
131
+ while (braces > 0) {
132
+ fixed2 += "}";
133
+ braces--;
134
+ }
135
+ while (brackets > 0) {
136
+ fixed2 += "]";
137
+ brackets--;
138
+ }
139
+ return fixed2;
140
+ }
141
+ let fixed = raw;
142
+ while (braces > 0) {
143
+ fixed += "}";
144
+ braces--;
145
+ }
146
+ while (brackets > 0) {
147
+ fixed += "]";
148
+ brackets--;
149
+ }
150
+ try {
151
+ JSON.parse(fixed);
152
+ return fixed;
153
+ } catch {
154
+ return raw;
155
+ }
156
+ }
157
+ // Annotate the CommonJS export names for ESM import in node:
158
+ 0 && (module.exports = {
159
+ BasicRepair,
160
+ TruncateStrategy
161
+ });
162
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/truncate.ts","../src/basic-repair.ts"],"sourcesContent":["export { TruncateStrategy } from \"./truncate.js\";\nexport { BasicRepair } from \"./basic-repair.js\";\n","import type { ChatMessage, ContextStrategy } from \"@halo-sdk/core\";\n\nexport class TruncateStrategy implements ContextStrategy {\n private _maxTokens: number;\n\n constructor(opts?: { maxTokens?: number }) {\n // Default: 80% of a 128K context window.\n this._maxTokens = opts?.maxTokens ?? 102_400;\n }\n\n prepare(\n prefix: ChatMessage[],\n history: ChatMessage[],\n _ctxMax: number,\n ): {\n history: ChatMessage[];\n modified: boolean;\n summary?: string;\n droppedCount: number;\n } {\n // Prefix is read-only — only history may be truncated.\n const histEstimate = history.reduce((sum, m) => sum + m.content.length, 0);\n const prefixEstimate = prefix.reduce((sum, m) => sum + m.content.length, 0);\n const budgetChars = (this._maxTokens - prefixEstimate / 4) * 4;\n\n if (histEstimate <= budgetChars * 0.9) {\n return { history, modified: false, droppedCount: 0 };\n }\n\n // Prefer keeping non-discardable messages. Discardable messages\n // (e.g. loadSkill results) are the first to go when truncating.\n const budgetLimit = budgetChars * 0.9;\n\n // Pass 1: keep non-discardable, newest first.\n const kept: ChatMessage[] = [];\n let keptTokens = 0;\n for (let i = history.length - 1; i >= 0; i--) {\n const msg = history[i]!;\n if (msg.discardable) continue;\n const size = msg.content.length;\n if (keptTokens + size > budgetLimit) break;\n keptTokens += size;\n kept.unshift(msg);\n }\n\n // Pass 2: backfill discardable messages if budget remains.\n for (let i = history.length - 1; i >= 0; i--) {\n const msg = history[i]!;\n if (!msg.discardable) continue;\n const size = msg.content.length;\n if (keptTokens + size > budgetLimit) break;\n keptTokens += size;\n // Insert in chronological order.\n const insertIdx = kept.findIndex(\n (m, idx) => idx === kept.length - 1 || history.indexOf(m) > history.indexOf(msg),\n );\n kept.splice(insertIdx === -1 ? kept.length : insertIdx, 0, msg);\n }\n\n const droppedCount = history.length - kept.length;\n\n return {\n history: kept,\n modified: droppedCount > 0,\n droppedCount,\n };\n }\n}\n","import type { ToolCall, RepairStrategy, RepairResult } from \"@halo-sdk/core\";\n\nexport class BasicRepair implements RepairStrategy {\n repair(toolCalls: ToolCall[], _rawContent: string): RepairResult {\n const repaired: ToolCall[] = [];\n let fixed = 0;\n\n for (const call of toolCalls) {\n const args = call.function?.arguments ?? \"\";\n const fix = tryFixTruncatedJson(args);\n if (fix !== args) {\n fixed++;\n repaired.push({\n ...call,\n function: { ...call.function, arguments: fix },\n });\n } else {\n repaired.push(call);\n }\n }\n\n return {\n toolCalls: repaired,\n fixed,\n suppressed: 0,\n notes: fixed > 0 ? [`fixed ${fixed} truncated JSON argument(s)`] : [],\n };\n }\n}\n\nfunction tryFixTruncatedJson(raw: string): string {\n if (!raw || !raw.trim()) return raw;\n\n // Try direct parse first.\n try {\n JSON.parse(raw);\n return raw;\n } catch {\n /* needs fixing */\n }\n\n // Count unbalanced brackets and braces.\n let braces = 0;\n let brackets = 0;\n let inString = false;\n let escaped = false;\n\n for (const ch of raw) {\n if (escaped) {\n escaped = false;\n continue;\n }\n if (ch === \"\\\\\") {\n escaped = true;\n continue;\n }\n if (ch === '\"') {\n inString = !inString;\n continue;\n }\n if (inString) continue;\n if (ch === \"{\") braces++;\n if (ch === \"}\") braces--;\n if (ch === \"[\") brackets++;\n if (ch === \"]\") brackets--;\n }\n\n // Also fix unterminated string.\n if (inString) {\n let fixed = raw;\n // Close the string before closing brackets.\n if (braces > 0 || brackets > 0) fixed += '\"';\n while (braces > 0) {\n fixed += \"}\";\n braces--;\n }\n while (brackets > 0) {\n fixed += \"]\";\n brackets--;\n }\n return fixed;\n }\n\n let fixed = raw;\n while (braces > 0) {\n fixed += \"}\";\n braces--;\n }\n while (brackets > 0) {\n fixed += \"]\";\n brackets--;\n }\n\n // Verify the fix is valid JSON.\n try {\n JSON.parse(fixed);\n return fixed;\n } catch {\n return raw;\n }\n}\n"],"mappings":"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","names":["fixed"]}
package/dist/index.js CHANGED
@@ -1,3 +1,134 @@
1
- export { TruncateStrategy } from "./truncate.js";
2
- export { BasicRepair } from "./basic-repair.js";
1
+ // src/truncate.ts
2
+ var TruncateStrategy = class {
3
+ _maxTokens;
4
+ constructor(opts) {
5
+ this._maxTokens = opts?.maxTokens ?? 102400;
6
+ }
7
+ prepare(prefix, history, _ctxMax) {
8
+ const histEstimate = history.reduce((sum, m) => sum + m.content.length, 0);
9
+ const prefixEstimate = prefix.reduce((sum, m) => sum + m.content.length, 0);
10
+ const budgetChars = (this._maxTokens - prefixEstimate / 4) * 4;
11
+ if (histEstimate <= budgetChars * 0.9) {
12
+ return { history, modified: false, droppedCount: 0 };
13
+ }
14
+ const budgetLimit = budgetChars * 0.9;
15
+ const kept = [];
16
+ let keptTokens = 0;
17
+ for (let i = history.length - 1; i >= 0; i--) {
18
+ const msg = history[i];
19
+ if (msg.discardable) continue;
20
+ const size = msg.content.length;
21
+ if (keptTokens + size > budgetLimit) break;
22
+ keptTokens += size;
23
+ kept.unshift(msg);
24
+ }
25
+ for (let i = history.length - 1; i >= 0; i--) {
26
+ const msg = history[i];
27
+ if (!msg.discardable) continue;
28
+ const size = msg.content.length;
29
+ if (keptTokens + size > budgetLimit) break;
30
+ keptTokens += size;
31
+ const insertIdx = kept.findIndex(
32
+ (m, idx) => idx === kept.length - 1 || history.indexOf(m) > history.indexOf(msg)
33
+ );
34
+ kept.splice(insertIdx === -1 ? kept.length : insertIdx, 0, msg);
35
+ }
36
+ const droppedCount = history.length - kept.length;
37
+ return {
38
+ history: kept,
39
+ modified: droppedCount > 0,
40
+ droppedCount
41
+ };
42
+ }
43
+ };
44
+
45
+ // src/basic-repair.ts
46
+ var BasicRepair = class {
47
+ repair(toolCalls, _rawContent) {
48
+ const repaired = [];
49
+ let fixed = 0;
50
+ for (const call of toolCalls) {
51
+ const args = call.function?.arguments ?? "";
52
+ const fix = tryFixTruncatedJson(args);
53
+ if (fix !== args) {
54
+ fixed++;
55
+ repaired.push({
56
+ ...call,
57
+ function: { ...call.function, arguments: fix }
58
+ });
59
+ } else {
60
+ repaired.push(call);
61
+ }
62
+ }
63
+ return {
64
+ toolCalls: repaired,
65
+ fixed,
66
+ suppressed: 0,
67
+ notes: fixed > 0 ? [`fixed ${fixed} truncated JSON argument(s)`] : []
68
+ };
69
+ }
70
+ };
71
+ function tryFixTruncatedJson(raw) {
72
+ if (!raw || !raw.trim()) return raw;
73
+ try {
74
+ JSON.parse(raw);
75
+ return raw;
76
+ } catch {
77
+ }
78
+ let braces = 0;
79
+ let brackets = 0;
80
+ let inString = false;
81
+ let escaped = false;
82
+ for (const ch of raw) {
83
+ if (escaped) {
84
+ escaped = false;
85
+ continue;
86
+ }
87
+ if (ch === "\\") {
88
+ escaped = true;
89
+ continue;
90
+ }
91
+ if (ch === '"') {
92
+ inString = !inString;
93
+ continue;
94
+ }
95
+ if (inString) continue;
96
+ if (ch === "{") braces++;
97
+ if (ch === "}") braces--;
98
+ if (ch === "[") brackets++;
99
+ if (ch === "]") brackets--;
100
+ }
101
+ if (inString) {
102
+ let fixed2 = raw;
103
+ if (braces > 0 || brackets > 0) fixed2 += '"';
104
+ while (braces > 0) {
105
+ fixed2 += "}";
106
+ braces--;
107
+ }
108
+ while (brackets > 0) {
109
+ fixed2 += "]";
110
+ brackets--;
111
+ }
112
+ return fixed2;
113
+ }
114
+ let fixed = raw;
115
+ while (braces > 0) {
116
+ fixed += "}";
117
+ braces--;
118
+ }
119
+ while (brackets > 0) {
120
+ fixed += "]";
121
+ brackets--;
122
+ }
123
+ try {
124
+ JSON.parse(fixed);
125
+ return fixed;
126
+ } catch {
127
+ return raw;
128
+ }
129
+ }
130
+ export {
131
+ BasicRepair,
132
+ TruncateStrategy
133
+ };
3
134
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"sources":["../src/truncate.ts","../src/basic-repair.ts"],"sourcesContent":["import type { ChatMessage, ContextStrategy } from \"@halo-sdk/core\";\n\nexport class TruncateStrategy implements ContextStrategy {\n private _maxTokens: number;\n\n constructor(opts?: { maxTokens?: number }) {\n // Default: 80% of a 128K context window.\n this._maxTokens = opts?.maxTokens ?? 102_400;\n }\n\n prepare(\n prefix: ChatMessage[],\n history: ChatMessage[],\n _ctxMax: number,\n ): {\n history: ChatMessage[];\n modified: boolean;\n summary?: string;\n droppedCount: number;\n } {\n // Prefix is read-only — only history may be truncated.\n const histEstimate = history.reduce((sum, m) => sum + m.content.length, 0);\n const prefixEstimate = prefix.reduce((sum, m) => sum + m.content.length, 0);\n const budgetChars = (this._maxTokens - prefixEstimate / 4) * 4;\n\n if (histEstimate <= budgetChars * 0.9) {\n return { history, modified: false, droppedCount: 0 };\n }\n\n // Prefer keeping non-discardable messages. Discardable messages\n // (e.g. loadSkill results) are the first to go when truncating.\n const budgetLimit = budgetChars * 0.9;\n\n // Pass 1: keep non-discardable, newest first.\n const kept: ChatMessage[] = [];\n let keptTokens = 0;\n for (let i = history.length - 1; i >= 0; i--) {\n const msg = history[i]!;\n if (msg.discardable) continue;\n const size = msg.content.length;\n if (keptTokens + size > budgetLimit) break;\n keptTokens += size;\n kept.unshift(msg);\n }\n\n // Pass 2: backfill discardable messages if budget remains.\n for (let i = history.length - 1; i >= 0; i--) {\n const msg = history[i]!;\n if (!msg.discardable) continue;\n const size = msg.content.length;\n if (keptTokens + size > budgetLimit) break;\n keptTokens += size;\n // Insert in chronological order.\n const insertIdx = kept.findIndex(\n (m, idx) => idx === kept.length - 1 || history.indexOf(m) > history.indexOf(msg),\n );\n kept.splice(insertIdx === -1 ? kept.length : insertIdx, 0, msg);\n }\n\n const droppedCount = history.length - kept.length;\n\n return {\n history: kept,\n modified: droppedCount > 0,\n droppedCount,\n };\n }\n}\n","import type { ToolCall, RepairStrategy, RepairResult } from \"@halo-sdk/core\";\n\nexport class BasicRepair implements RepairStrategy {\n repair(toolCalls: ToolCall[], _rawContent: string): RepairResult {\n const repaired: ToolCall[] = [];\n let fixed = 0;\n\n for (const call of toolCalls) {\n const args = call.function?.arguments ?? \"\";\n const fix = tryFixTruncatedJson(args);\n if (fix !== args) {\n fixed++;\n repaired.push({\n ...call,\n function: { ...call.function, arguments: fix },\n });\n } else {\n repaired.push(call);\n }\n }\n\n return {\n toolCalls: repaired,\n fixed,\n suppressed: 0,\n notes: fixed > 0 ? [`fixed ${fixed} truncated JSON argument(s)`] : [],\n };\n }\n}\n\nfunction tryFixTruncatedJson(raw: string): string {\n if (!raw || !raw.trim()) return raw;\n\n // Try direct parse first.\n try {\n JSON.parse(raw);\n return raw;\n } catch {\n /* needs fixing */\n }\n\n // Count unbalanced brackets and braces.\n let braces = 0;\n let brackets = 0;\n let inString = false;\n let escaped = false;\n\n for (const ch of raw) {\n if (escaped) {\n escaped = false;\n continue;\n }\n if (ch === \"\\\\\") {\n escaped = true;\n continue;\n }\n if (ch === '\"') {\n inString = !inString;\n continue;\n }\n if (inString) continue;\n if (ch === \"{\") braces++;\n if (ch === \"}\") braces--;\n if (ch === \"[\") brackets++;\n if (ch === \"]\") brackets--;\n }\n\n // Also fix unterminated string.\n if (inString) {\n let fixed = raw;\n // Close the string before closing brackets.\n if (braces > 0 || brackets > 0) fixed += '\"';\n while (braces > 0) {\n fixed += \"}\";\n braces--;\n }\n while (brackets > 0) {\n fixed += \"]\";\n brackets--;\n }\n return fixed;\n }\n\n let fixed = raw;\n while (braces > 0) {\n fixed += \"}\";\n braces--;\n }\n while (brackets > 0) {\n fixed += \"]\";\n brackets--;\n }\n\n // Verify the fix is valid JSON.\n try {\n JSON.parse(fixed);\n return fixed;\n } catch {\n return raw;\n }\n}\n"],"mappings":"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","names":["fixed"]}
@@ -1 +1 @@
1
- {"version":3,"file":"truncate.d.ts","sourceRoot":"","sources":["../src/truncate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEnE,qBAAa,gBAAiB,YAAW,eAAe;IACtD,OAAO,CAAC,UAAU,CAAS;gBAEf,IAAI,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE;IAKzC,OAAO,CACL,MAAM,EAAE,WAAW,EAAE,EACrB,OAAO,EAAE,WAAW,EAAE,EACtB,OAAO,EAAE,MAAM,GACd;QACD,OAAO,EAAE,WAAW,EAAE,CAAC;QACvB,QAAQ,EAAE,OAAO,CAAC;QAClB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,YAAY,EAAE,MAAM,CAAC;KACtB;CA6BF"}
1
+ {"version":3,"file":"truncate.d.ts","sourceRoot":"","sources":["../src/truncate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEnE,qBAAa,gBAAiB,YAAW,eAAe;IACtD,OAAO,CAAC,UAAU,CAAS;gBAEf,IAAI,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE;IAKzC,OAAO,CACL,MAAM,EAAE,WAAW,EAAE,EACrB,OAAO,EAAE,WAAW,EAAE,EACtB,OAAO,EAAE,MAAM,GACd;QACD,OAAO,EAAE,WAAW,EAAE,CAAC;QACvB,QAAQ,EAAE,OAAO,CAAC;QAClB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,YAAY,EAAE,MAAM,CAAC;KACtB;CAgDF"}
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "@halo-sdk/strategies",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Pluggable strategies for Halo AI SDK — context window truncation and JSON repair",
5
- "license": "MIT",
6
5
  "keywords": [
7
6
  "ai",
8
- "llm",
9
7
  "context-window",
10
- "truncation",
11
- "json-repair"
8
+ "json-repair",
9
+ "llm",
10
+ "truncation"
12
11
  ],
12
+ "license": "MIT",
13
13
  "repository": {
14
14
  "type": "git",
15
15
  "url": "https://github.com/halo-sdk/halo-ai",
@@ -24,23 +24,26 @@
24
24
  "exports": {
25
25
  ".": {
26
26
  "types": "./dist/index.d.ts",
27
- "import": "./dist/index.js"
27
+ "import": "./dist/index.js",
28
+ "require": "./dist/index.cjs"
28
29
  }
29
30
  },
30
31
  "publishConfig": {
31
32
  "access": "public"
32
33
  },
33
- "peerDependencies": {
34
- "@halo-sdk/core": ">=1.0.0"
35
- },
36
34
  "devDependencies": {
37
35
  "typescript": "^5.8.0",
38
36
  "vitest": "^3.0.0",
39
- "@halo-sdk/core": "1.0.0"
37
+ "@halo-sdk/core": "1.0.1"
38
+ },
39
+ "peerDependencies": {
40
+ "@halo-sdk/core": ">=1.0.1"
40
41
  },
41
42
  "scripts": {
42
- "build": "tsc",
43
- "dev": "tsc --watch",
43
+ "build": "tsc --build --emitDeclarationOnly && tsup",
44
+ "dev": "tsup --watch",
45
+ "clean": "del-cli dist *.tsbuildinfo",
46
+ "publint": "publint",
44
47
  "test": "vitest run",
45
48
  "test:watch": "vitest"
46
49
  }
@@ -1,101 +0,0 @@
1
- export class BasicRepair {
2
- repair(toolCalls, _rawContent) {
3
- const repaired = [];
4
- let fixed = 0;
5
- for (const call of toolCalls) {
6
- const args = call.function?.arguments ?? "";
7
- const fix = tryFixTruncatedJson(args);
8
- if (fix !== args) {
9
- fixed++;
10
- repaired.push({
11
- ...call,
12
- function: { ...call.function, arguments: fix },
13
- });
14
- }
15
- else {
16
- repaired.push(call);
17
- }
18
- }
19
- return {
20
- toolCalls: repaired,
21
- fixed,
22
- suppressed: 0,
23
- notes: fixed > 0 ? [`fixed ${fixed} truncated JSON argument(s)`] : [],
24
- };
25
- }
26
- }
27
- function tryFixTruncatedJson(raw) {
28
- if (!raw || !raw.trim())
29
- return raw;
30
- // Try direct parse first.
31
- try {
32
- JSON.parse(raw);
33
- return raw;
34
- }
35
- catch {
36
- /* needs fixing */
37
- }
38
- // Count unbalanced brackets and braces.
39
- let braces = 0;
40
- let brackets = 0;
41
- let inString = false;
42
- let escaped = false;
43
- for (const ch of raw) {
44
- if (escaped) {
45
- escaped = false;
46
- continue;
47
- }
48
- if (ch === "\\") {
49
- escaped = true;
50
- continue;
51
- }
52
- if (ch === '"') {
53
- inString = !inString;
54
- continue;
55
- }
56
- if (inString)
57
- continue;
58
- if (ch === "{")
59
- braces++;
60
- if (ch === "}")
61
- braces--;
62
- if (ch === "[")
63
- brackets++;
64
- if (ch === "]")
65
- brackets--;
66
- }
67
- // Also fix unterminated string.
68
- if (inString) {
69
- let fixed = raw;
70
- // Close the string before closing brackets.
71
- if (braces > 0 || brackets > 0)
72
- fixed += '"';
73
- while (braces > 0) {
74
- fixed += "}";
75
- braces--;
76
- }
77
- while (brackets > 0) {
78
- fixed += "]";
79
- brackets--;
80
- }
81
- return fixed;
82
- }
83
- let fixed = raw;
84
- while (braces > 0) {
85
- fixed += "}";
86
- braces--;
87
- }
88
- while (brackets > 0) {
89
- fixed += "]";
90
- brackets--;
91
- }
92
- // Verify the fix is valid JSON.
93
- try {
94
- JSON.parse(fixed);
95
- return fixed;
96
- }
97
- catch {
98
- return raw;
99
- }
100
- }
101
- //# sourceMappingURL=basic-repair.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"basic-repair.js","sourceRoot":"","sources":["../src/basic-repair.ts"],"names":[],"mappings":"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"}
package/dist/truncate.js DELETED
@@ -1,34 +0,0 @@
1
- export class TruncateStrategy {
2
- _maxTokens;
3
- constructor(opts) {
4
- // Default: 80% of a 128K context window.
5
- this._maxTokens = opts?.maxTokens ?? 102_400;
6
- }
7
- prepare(prefix, history, _ctxMax) {
8
- // Prefix is read-only — only history may be truncated.
9
- const histEstimate = history.reduce((sum, m) => sum + m.content.length, 0);
10
- const prefixEstimate = prefix.reduce((sum, m) => sum + m.content.length, 0);
11
- const budgetChars = (this._maxTokens - prefixEstimate / 4) * 4;
12
- if (histEstimate <= budgetChars * 0.9) {
13
- return { history, modified: false, droppedCount: 0 };
14
- }
15
- // Keep the most recent messages that fit the remaining budget.
16
- let tailTokens = 0;
17
- const tail = [];
18
- for (let i = history.length - 1; i >= 0; i--) {
19
- const msg = history[i];
20
- const size = msg.content.length;
21
- if (tailTokens + size > budgetChars * 0.9)
22
- break;
23
- tailTokens += size;
24
- tail.unshift(msg);
25
- }
26
- const droppedCount = history.length - tail.length;
27
- return {
28
- history: tail,
29
- modified: droppedCount > 0,
30
- droppedCount,
31
- };
32
- }
33
- }
34
- //# sourceMappingURL=truncate.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"truncate.js","sourceRoot":"","sources":["../src/truncate.ts"],"names":[],"mappings":"AAEA,MAAM,OAAO,gBAAgB;IACnB,UAAU,CAAS;IAE3B,YAAY,IAA6B;QACvC,yCAAyC;QACzC,IAAI,CAAC,UAAU,GAAG,IAAI,EAAE,SAAS,IAAI,OAAO,CAAC;IAC/C,CAAC;IAED,OAAO,CACL,MAAqB,EACrB,OAAsB,EACtB,OAAe;QAOf,uDAAuD;QACvD,MAAM,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;QAC3E,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;QAC5E,MAAM,WAAW,GAAG,CAAC,IAAI,CAAC,UAAU,GAAG,cAAc,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC;QAE/D,IAAI,YAAY,IAAI,WAAW,GAAG,GAAG,EAAE,CAAC;YACtC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC,EAAE,CAAC;QACvD,CAAC;QAED,+DAA+D;QAC/D,IAAI,UAAU,GAAG,CAAC,CAAC;QACnB,MAAM,IAAI,GAAkB,EAAE,CAAC;QAC/B,KAAK,IAAI,CAAC,GAAG,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;YAC7C,MAAM,GAAG,GAAG,OAAO,CAAC,CAAC,CAAE,CAAC;YACxB,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC;YAChC,IAAI,UAAU,GAAG,IAAI,GAAG,WAAW,GAAG,GAAG;gBAAE,MAAM;YACjD,UAAU,IAAI,IAAI,CAAC;YACnB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACpB,CAAC;QAED,MAAM,YAAY,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QAElD,OAAO;YACL,OAAO,EAAE,IAAI;YACb,QAAQ,EAAE,YAAY,GAAG,CAAC;YAC1B,YAAY;SACb,CAAC;IACJ,CAAC;CACF"}