@acosmi/sdk-ts 1.0.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.
Files changed (35) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +247 -0
  3. package/dist/browser/index.js +3950 -0
  4. package/dist/browser/index.js.map +1 -0
  5. package/dist/index.js +3950 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/node/adapters/anthropic.cjs +257 -0
  8. package/dist/node/adapters/anthropic.cjs.map +1 -0
  9. package/dist/node/adapters/anthropic.d.cts +39 -0
  10. package/dist/node/adapters/anthropic.d.ts +39 -0
  11. package/dist/node/adapters/anthropic.js +254 -0
  12. package/dist/node/adapters/anthropic.js.map +1 -0
  13. package/dist/node/adapters/openai.cjs +472 -0
  14. package/dist/node/adapters/openai.cjs.map +1 -0
  15. package/dist/node/adapters/openai.d.cts +64 -0
  16. package/dist/node/adapters/openai.d.ts +64 -0
  17. package/dist/node/adapters/openai.js +465 -0
  18. package/dist/node/adapters/openai.js.map +1 -0
  19. package/dist/node/index-C3Z_84Bv.d.cts +992 -0
  20. package/dist/node/index-C3Z_84Bv.d.ts +992 -0
  21. package/dist/node/index-nvLKgCm9.d.cts +147 -0
  22. package/dist/node/index-nvLKgCm9.d.ts +147 -0
  23. package/dist/node/index.cjs +4024 -0
  24. package/dist/node/index.cjs.map +1 -0
  25. package/dist/node/index.d.cts +791 -0
  26. package/dist/node/index.d.ts +791 -0
  27. package/dist/node/index.js +3950 -0
  28. package/dist/node/index.js.map +1 -0
  29. package/dist/node/sanitize/index.cjs +248 -0
  30. package/dist/node/sanitize/index.cjs.map +1 -0
  31. package/dist/node/sanitize/index.d.cts +1 -0
  32. package/dist/node/sanitize/index.d.ts +1 -0
  33. package/dist/node/sanitize/index.js +217 -0
  34. package/dist/node/sanitize/index.js.map +1 -0
  35. package/package.json +84 -0
@@ -0,0 +1,248 @@
1
+ 'use strict';
2
+
3
+ // src/sanitize/types.ts
4
+ var BlockText = "text";
5
+ var BlockImage = "image";
6
+ var BlockVideo = "video";
7
+ var BlockDocument = "document";
8
+ var BlockSearchResult = "search_result";
9
+ var BlockThinking = "thinking";
10
+ var BlockRedactedThinking = "redacted_thinking";
11
+ var BlockToolUse = "tool_use";
12
+ var BlockToolResult = "tool_result";
13
+ var BlockToolReference = "tool_reference";
14
+ var BlockServerToolUse = "server_tool_use";
15
+ var BlockWebSearchToolResult = "web_search_tool_result";
16
+ var BlockCodeExecutionToolResult = "code_execution_tool_result";
17
+ var BlockMCPToolUse = "mcp_tool_use";
18
+ var BlockMCPToolResult = "mcp_tool_result";
19
+ var BlockContainerUpload = "container_upload";
20
+ var DeltaText = "text_delta";
21
+ var DeltaInputJSON = "input_json_delta";
22
+ var DeltaThinking = "thinking_delta";
23
+ var DeltaSignature = "signature_delta";
24
+ var DeltaCitations = "citations_delta";
25
+ var EphemeralMarkerField = "acosmi_ephemeral";
26
+
27
+ // src/sanitize/config.ts
28
+ var HistoryTooDeepError = class extends Error {
29
+ constructor() {
30
+ super("sanitize: messages history exceeds configured depth");
31
+ this.name = "HistoryTooDeepError";
32
+ }
33
+ };
34
+ var BlockDeniedError = class extends Error {
35
+ constructor() {
36
+ super("sanitize: block type permanently denied");
37
+ this.name = "BlockDeniedError";
38
+ }
39
+ };
40
+ var SizeError = class extends Error {
41
+ blockType;
42
+ actual;
43
+ limit;
44
+ constructor(blockType, actual, limit) {
45
+ super(`sanitize: ${blockType} base64 size ${actual} exceeds limit ${limit}`);
46
+ this.name = "SizeError";
47
+ this.blockType = blockType;
48
+ this.actual = actual;
49
+ this.limit = limit;
50
+ }
51
+ };
52
+ var ErrHistoryTooDeep = new HistoryTooDeepError();
53
+ var ErrBlockDenied = new BlockDeniedError();
54
+
55
+ // src/sanitize/history.ts
56
+ function dropBlocks(messages, pred) {
57
+ const droppedToolUseIDs = collectDroppedToolUseIDs(messages, pred);
58
+ const out = [];
59
+ for (const msg of messages) {
60
+ if (!isPlainObject(msg)) {
61
+ out.push(msg);
62
+ continue;
63
+ }
64
+ const content = msg["content"];
65
+ if (!Array.isArray(content)) {
66
+ out.push(msg);
67
+ continue;
68
+ }
69
+ const { kept, changed } = filterBlocks(content, pred, droppedToolUseIDs);
70
+ if (!changed) {
71
+ out.push(msg);
72
+ continue;
73
+ }
74
+ if (kept.length === 0) {
75
+ continue;
76
+ }
77
+ const newMsg = { ...msg };
78
+ newMsg["content"] = kept;
79
+ out.push(newMsg);
80
+ }
81
+ return out;
82
+ }
83
+ function collectDroppedToolUseIDs(messages, pred) {
84
+ const ids = /* @__PURE__ */ new Set();
85
+ for (const msg of messages) {
86
+ if (!isPlainObject(msg)) continue;
87
+ const content = msg["content"];
88
+ if (!Array.isArray(content)) continue;
89
+ for (const raw of content) {
90
+ if (!isPlainObject(raw)) continue;
91
+ if (!pred(raw)) continue;
92
+ const t = raw["type"];
93
+ if (typeof t !== "string") continue;
94
+ if (t === "tool_use" || t === "server_tool_use" || t === "mcp_tool_use") {
95
+ const id = raw["id"];
96
+ if (typeof id === "string" && id !== "") ids.add(id);
97
+ }
98
+ }
99
+ }
100
+ return ids;
101
+ }
102
+ function filterBlocks(content, pred, droppedToolUseIDs) {
103
+ const kept = [];
104
+ let changed = false;
105
+ for (const raw of content) {
106
+ if (!isPlainObject(raw)) {
107
+ kept.push(raw);
108
+ continue;
109
+ }
110
+ if (pred(raw)) {
111
+ changed = true;
112
+ continue;
113
+ }
114
+ if (droppedToolUseIDs.size > 0) {
115
+ const t = raw["type"];
116
+ if (t === "tool_result" || t === "mcp_tool_result") {
117
+ const id = raw["tool_use_id"];
118
+ if (typeof id === "string" && id !== "" && droppedToolUseIDs.has(id)) {
119
+ changed = true;
120
+ continue;
121
+ }
122
+ }
123
+ }
124
+ kept.push(raw);
125
+ }
126
+ return { kept, changed };
127
+ }
128
+ function stripEphemeral(messages) {
129
+ return dropBlocks(messages, (b) => {
130
+ const t = b["type"];
131
+ if (t === "thinking" || t === "redacted_thinking") {
132
+ return false;
133
+ }
134
+ const v = b[EphemeralMarkerField];
135
+ return v === true;
136
+ });
137
+ }
138
+ function isPlainObject(v) {
139
+ return typeof v === "object" && v !== null && !Array.isArray(v);
140
+ }
141
+
142
+ // src/sanitize/defensive.ts
143
+ function sanitize(messages, cfg) {
144
+ if ((cfg.maxMessagesTurns ?? 0) > 0 && messages.length > cfg.maxMessagesTurns) {
145
+ throw ErrHistoryTooDeep;
146
+ }
147
+ if ((cfg.maxImageBytes ?? 0) > 0 || (cfg.maxVideoBytes ?? 0) > 0 || (cfg.maxPDFBytes ?? 0) > 0) {
148
+ checkMediaSizes(messages, cfg);
149
+ }
150
+ if (cfg.permanentDenyBlocks && cfg.permanentDenyBlocks.length > 0) {
151
+ const denySet = /* @__PURE__ */ new Set();
152
+ for (const bt of cfg.permanentDenyBlocks) denySet.add(bt);
153
+ messages = dropBlocks(messages, (b) => {
154
+ const t = b["type"];
155
+ return typeof t === "string" && denySet.has(t);
156
+ });
157
+ }
158
+ return messages;
159
+ }
160
+ function checkMediaSizes(messages, cfg) {
161
+ for (const msg of messages) {
162
+ if (!isPlainObject2(msg)) continue;
163
+ const content = msg["content"];
164
+ if (!Array.isArray(content)) continue;
165
+ for (const raw of content) {
166
+ if (!isPlainObject2(raw)) continue;
167
+ const bt = raw["type"];
168
+ if (typeof bt !== "string") continue;
169
+ let limit = 0;
170
+ switch (bt) {
171
+ case "image":
172
+ limit = cfg.maxImageBytes ?? 0;
173
+ break;
174
+ case "video":
175
+ limit = cfg.maxVideoBytes ?? 0;
176
+ break;
177
+ case "document":
178
+ limit = cfg.maxPDFBytes ?? 0;
179
+ break;
180
+ default:
181
+ continue;
182
+ }
183
+ if (limit <= 0) continue;
184
+ const data = extractBase64Data(raw);
185
+ if (data === "") continue;
186
+ const actual = base64DecodedLen(data);
187
+ if (actual > limit) {
188
+ throw new SizeError(bt, actual, limit);
189
+ }
190
+ }
191
+ }
192
+ }
193
+ function extractBase64Data(block) {
194
+ const src = block["source"];
195
+ if (!isPlainObject2(src)) return "";
196
+ if (src["type"] !== "base64") return "";
197
+ const dataRaw = src["data"];
198
+ if (typeof dataRaw !== "string") return "";
199
+ let data = dataRaw;
200
+ const i = data.indexOf("base64,");
201
+ if (i >= 0) {
202
+ data = data.slice(i + "base64,".length);
203
+ }
204
+ return data;
205
+ }
206
+ function base64DecodedLen(b64) {
207
+ const n = b64.length;
208
+ let pad = 0;
209
+ if (n >= 1 && b64[n - 1] === "=") pad++;
210
+ if (n >= 2 && b64[n - 2] === "=") pad++;
211
+ return Math.floor(n * 3 / 4) - pad;
212
+ }
213
+ function isPlainObject2(v) {
214
+ return typeof v === "object" && v !== null && !Array.isArray(v);
215
+ }
216
+
217
+ exports.BlockCodeExecutionToolResult = BlockCodeExecutionToolResult;
218
+ exports.BlockContainerUpload = BlockContainerUpload;
219
+ exports.BlockDeniedError = BlockDeniedError;
220
+ exports.BlockDocument = BlockDocument;
221
+ exports.BlockImage = BlockImage;
222
+ exports.BlockMCPToolResult = BlockMCPToolResult;
223
+ exports.BlockMCPToolUse = BlockMCPToolUse;
224
+ exports.BlockRedactedThinking = BlockRedactedThinking;
225
+ exports.BlockSearchResult = BlockSearchResult;
226
+ exports.BlockServerToolUse = BlockServerToolUse;
227
+ exports.BlockText = BlockText;
228
+ exports.BlockThinking = BlockThinking;
229
+ exports.BlockToolReference = BlockToolReference;
230
+ exports.BlockToolResult = BlockToolResult;
231
+ exports.BlockToolUse = BlockToolUse;
232
+ exports.BlockVideo = BlockVideo;
233
+ exports.BlockWebSearchToolResult = BlockWebSearchToolResult;
234
+ exports.DeltaCitations = DeltaCitations;
235
+ exports.DeltaInputJSON = DeltaInputJSON;
236
+ exports.DeltaSignature = DeltaSignature;
237
+ exports.DeltaText = DeltaText;
238
+ exports.DeltaThinking = DeltaThinking;
239
+ exports.EphemeralMarkerField = EphemeralMarkerField;
240
+ exports.ErrBlockDenied = ErrBlockDenied;
241
+ exports.ErrHistoryTooDeep = ErrHistoryTooDeep;
242
+ exports.HistoryTooDeepError = HistoryTooDeepError;
243
+ exports.SizeError = SizeError;
244
+ exports.dropBlocks = dropBlocks;
245
+ exports.sanitize = sanitize;
246
+ exports.stripEphemeral = stripEphemeral;
247
+ //# sourceMappingURL=index.cjs.map
248
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/sanitize/types.ts","../../../src/sanitize/config.ts","../../../src/sanitize/history.ts","../../../src/sanitize/defensive.ts"],"names":["isPlainObject"],"mappings":"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","file":"index.cjs","sourcesContent":["// sanitize/types.ts — 端口自 acosmi-sdk-go/sanitize/types.go\n//\n// Provider 无关的 content-block 防御性处理。\n// 职责边界:\n// - SDK 层: 只做所有下游 provider 都不可能接受的底线剥除 + 早失败\n// - Gateway 层: 按 provider preset 精细剥除 (不在本包内)\n\n/** Anthropic content block 类型 (请求 + 响应 + ephemeral) */\nexport type BlockType =\n | 'text'\n | 'image'\n | 'video'\n | 'document'\n | 'search_result'\n | 'thinking'\n | 'redacted_thinking'\n | 'tool_use'\n | 'tool_result'\n | 'tool_reference'\n | 'server_tool_use'\n | 'web_search_tool_result'\n | 'code_execution_tool_result'\n | 'mcp_tool_use'\n | 'mcp_tool_result'\n | 'container_upload';\n\nexport const BlockText: BlockType = 'text';\nexport const BlockImage: BlockType = 'image';\nexport const BlockVideo: BlockType = 'video';\nexport const BlockDocument: BlockType = 'document';\nexport const BlockSearchResult: BlockType = 'search_result';\nexport const BlockThinking: BlockType = 'thinking';\nexport const BlockRedactedThinking: BlockType = 'redacted_thinking';\nexport const BlockToolUse: BlockType = 'tool_use';\nexport const BlockToolResult: BlockType = 'tool_result';\nexport const BlockToolReference: BlockType = 'tool_reference';\nexport const BlockServerToolUse: BlockType = 'server_tool_use';\nexport const BlockWebSearchToolResult: BlockType = 'web_search_tool_result';\nexport const BlockCodeExecutionToolResult: BlockType = 'code_execution_tool_result';\nexport const BlockMCPToolUse: BlockType = 'mcp_tool_use';\nexport const BlockMCPToolResult: BlockType = 'mcp_tool_result';\nexport const BlockContainerUpload: BlockType = 'container_upload';\n\n/** 流式响应 delta 类型 (与 BlockType 正交) */\nexport type DeltaType =\n | 'text_delta'\n | 'input_json_delta'\n | 'thinking_delta'\n | 'signature_delta'\n | 'citations_delta';\n\nexport const DeltaText: DeltaType = 'text_delta';\nexport const DeltaInputJSON: DeltaType = 'input_json_delta';\nexport const DeltaThinking: DeltaType = 'thinking_delta';\nexport const DeltaSignature: DeltaType = 'signature_delta';\nexport const DeltaCitations: DeltaType = 'citations_delta';\n\n/**\n * 网关在 block JSON 中注入的 in-band 标记字段名。\n * 存在且值为 true 时代表该 block 不应回传下一轮。\n *\n * 选择 in-band 标记而非独立 SSE 事件的理由:\n * - 零缓冲 / 零顺序依赖 / 零延迟\n * - history 剥离天然可做\n */\nexport const EphemeralMarkerField = 'acosmi_ephemeral';\n","// sanitize/config.ts — 端口自 acosmi-sdk-go/sanitize/config.go\n\nimport type { BlockType } from './types';\n\n/**\n * SDK 层防御性配置。只做所有下游 provider 都不可能接受的底线剥除;\n * 具体 provider 适配由网关承担。\n *\n * 零值 = 不校验 / 不剥除, 等价于未启用。\n */\nexport interface MinimalSanitizeConfig {\n /** 体积上限 (字节, 仅对 base64 内联媒体生效; URL 版交网关把关). 0 = 不校验. */\n maxImageBytes?: number;\n maxVideoBytes?: number;\n maxPDFBytes?: number;\n\n /** history 轮次硬上限 (防止内存爆炸 / 上行带宽). 0 = 不校验. 超限抛 ErrHistoryTooDeep. */\n maxMessagesTurns?: number;\n\n /** 公共黑名单 (所有 provider 均拒绝的 block 类型). 默认空. */\n permanentDenyBlocks?: BlockType[];\n}\n\nexport class HistoryTooDeepError extends Error {\n constructor() {\n super('sanitize: messages history exceeds configured depth');\n this.name = 'HistoryTooDeepError';\n }\n}\n\nexport class BlockDeniedError extends Error {\n constructor() {\n super('sanitize: block type permanently denied');\n this.name = 'BlockDeniedError';\n }\n}\n\n/** 体积超限错误, 携带实际/上限字节数以便上游展示。 */\nexport class SizeError extends Error {\n blockType: BlockType;\n actual: number;\n limit: number;\n\n constructor(blockType: BlockType, actual: number, limit: number) {\n super(`sanitize: ${blockType} base64 size ${actual} exceeds limit ${limit}`);\n this.name = 'SizeError';\n this.blockType = blockType;\n this.actual = actual;\n this.limit = limit;\n }\n}\n\n// 单例实例 — 与 Go 侧 var ErrHistoryTooDeep / ErrBlockDenied 等价的\"哨兵\"用法\n// 但 TS class 实例不能 const 哨兵化, 调用方应用 instanceof 判断。\nexport const ErrHistoryTooDeep = new HistoryTooDeepError();\nexport const ErrBlockDenied = new BlockDeniedError();\n","// sanitize/history.ts — 端口自 acosmi-sdk-go/sanitize/history.go\n\nimport { EphemeralMarkerField } from './types';\n\n/** 判定一个 block 是否应被剥除 */\nexport type BlockPredicate = (block: Record<string, unknown>) => boolean;\n\n/**\n * 按 pred 从 messages 中剥除 block, 并联动剥除对应的 tool_result (user 轮)。\n *\n * 收敛两步: 先扫收集 droppedToolUseIDs, 再整体剥; 这样顺序不影响正确性\n * (即使 tool_result 在 tool_use 之前出现也能捕获)。\n *\n * 不修改入参 messages 中任何 map 的 key, 只重构数组。\n * 未被修改的 message 保持原引用 (零拷贝优化)。\n */\nexport function dropBlocks(messages: unknown[], pred: BlockPredicate): unknown[] {\n const droppedToolUseIDs = collectDroppedToolUseIDs(messages, pred);\n\n const out: unknown[] = [];\n for (const msg of messages) {\n if (!isPlainObject(msg)) {\n out.push(msg);\n continue;\n }\n const content = msg['content'];\n if (!Array.isArray(content)) {\n out.push(msg);\n continue;\n }\n\n const { kept, changed } = filterBlocks(content, pred, droppedToolUseIDs);\n if (!changed) {\n out.push(msg);\n continue;\n }\n\n // content 空了的消息整条丢弃 (assistant 本轮全是 ephemeral 块;\n // 或 user 轮全是联动剥的 tool_result)。避免产生空消息让 provider 报错。\n if (kept.length === 0) {\n continue;\n }\n\n // 浅拷贝 msg, 只改 content, 避免污染调用方数据。\n const newMsg: Record<string, unknown> = { ...msg };\n newMsg['content'] = kept;\n out.push(newMsg);\n }\n return out;\n}\n\n/** 第一遍扫描, 仅收集\"本次被 pred 命中的 tool_use 类 block 的 id\", 以便联动剥 tool_result。 */\nfunction collectDroppedToolUseIDs(messages: unknown[], pred: BlockPredicate): Set<string> {\n const ids = new Set<string>();\n for (const msg of messages) {\n if (!isPlainObject(msg)) continue;\n const content = msg['content'];\n if (!Array.isArray(content)) continue;\n for (const raw of content) {\n if (!isPlainObject(raw)) continue;\n if (!pred(raw)) continue;\n const t = raw['type'];\n if (typeof t !== 'string') continue;\n if (t === 'tool_use' || t === 'server_tool_use' || t === 'mcp_tool_use') {\n const id = raw['id'];\n if (typeof id === 'string' && id !== '') ids.add(id);\n }\n }\n }\n return ids;\n}\n\n/**\n * 对单条消息的 content 数组剥除 pred 命中的 block, 以及\n * tool_use_id 在 droppedToolUseIDs 中的 tool_result。返回 (新数组, 是否变更)。\n */\nfunction filterBlocks(\n content: unknown[],\n pred: BlockPredicate,\n droppedToolUseIDs: Set<string>,\n): { kept: unknown[]; changed: boolean } {\n const kept: unknown[] = [];\n let changed = false;\n for (const raw of content) {\n if (!isPlainObject(raw)) {\n kept.push(raw);\n continue;\n }\n if (pred(raw)) {\n changed = true;\n continue;\n }\n // 联动剥 tool_result / mcp_tool_result (仅当 tool_use_id 命中)\n if (droppedToolUseIDs.size > 0) {\n const t = raw['type'];\n if (t === 'tool_result' || t === 'mcp_tool_result') {\n const id = raw['tool_use_id'];\n if (typeof id === 'string' && id !== '' && droppedToolUseIDs.has(id)) {\n changed = true;\n continue;\n }\n }\n }\n kept.push(raw);\n }\n return { kept, changed };\n}\n\n/**\n * 从 messages 中剥除带 acosmi_ephemeral:true 标记的 block, 以及对应的 tool_result。\n *\n * 硬豁免: thinking / redacted_thinking 块永不剥, 即使携带 ephemeral 标记。\n *\n * 理由: Anthropic extended thinking + tool_use 续轮场景下, 上游强制要求\n * assistant 历史中保留原始 thinking 块, 否则返回:\n * \"The content[].thinking in the thinking mode must be passed back to the API.\"\n *\n * 历史污染防御: 即使旧版网关 / 历史响应里 thinking 块带了 ephemeral=true,\n * 客户端在调用 stripEphemeral 时也不应剥除。网关侧 anthropic preset 已停止注入此标记,\n * 本豁免兜底历史会话与第三方调用方两类已污染场景。\n */\nexport function stripEphemeral(messages: unknown[]): unknown[] {\n return dropBlocks(messages, (b) => {\n const t = b['type'];\n if (t === 'thinking' || t === 'redacted_thinking') {\n return false; // 硬豁免, 不可被 ephemeral 标记覆盖\n }\n const v = b[EphemeralMarkerField];\n return v === true;\n });\n}\n\nfunction isPlainObject(v: unknown): v is Record<string, unknown> {\n return typeof v === 'object' && v !== null && !Array.isArray(v);\n}\n","// sanitize/defensive.ts — 端口自 acosmi-sdk-go/sanitize/defensive.go\n\nimport type { BlockType } from './types';\nimport type { MinimalSanitizeConfig } from './config';\nimport { ErrHistoryTooDeep, SizeError } from './config';\nimport { dropBlocks } from './history';\n\n/**\n * 对已经解析为 unknown[] 的 messages 做底线防御: 深度校验 +\n * 体积校验 + deny-list 剥除 + tool_use_id 联动剥除 tool_result。\n *\n * 入参形态: messages 的每个元素预期是 object (含 role / content); content 为 string (plain text)\n * 或 unknown[] (block 数组)。形态异常的元素原样透传, 不抛错。\n *\n * 通过返回新 messages (可能 block 数组被缩减); 早失败抛错 (调用方应放弃本次请求)。\n */\nexport function sanitize(messages: unknown[], cfg: MinimalSanitizeConfig): unknown[] {\n if ((cfg.maxMessagesTurns ?? 0) > 0 && messages.length > (cfg.maxMessagesTurns as number)) {\n throw ErrHistoryTooDeep;\n }\n\n // 体积校验: 先扫一遍, 任何违规直接早失败 (不修改 messages)\n if ((cfg.maxImageBytes ?? 0) > 0 || (cfg.maxVideoBytes ?? 0) > 0 || (cfg.maxPDFBytes ?? 0) > 0) {\n checkMediaSizes(messages, cfg);\n }\n\n // deny-list 剥除 + tool_use_id 联动\n if (cfg.permanentDenyBlocks && cfg.permanentDenyBlocks.length > 0) {\n const denySet = new Set<string>();\n for (const bt of cfg.permanentDenyBlocks) denySet.add(bt as string);\n messages = dropBlocks(messages, (b) => {\n const t = (b as Record<string, unknown>)['type'];\n return typeof t === 'string' && denySet.has(t);\n });\n }\n\n return messages;\n}\n\n/**\n * 遍历所有 block, 对 base64 内联 image/video/document 类型校验解码后字节数。\n * URL 版无法本地量体积, 跳过 (交网关把关)。违规直接抛 SizeError。\n */\nfunction checkMediaSizes(messages: unknown[], cfg: MinimalSanitizeConfig): void {\n for (const msg of messages) {\n if (!isPlainObject(msg)) continue;\n const content = msg['content'];\n if (!Array.isArray(content)) continue;\n for (const raw of content) {\n if (!isPlainObject(raw)) continue;\n const bt = raw['type'];\n if (typeof bt !== 'string') continue;\n\n let limit = 0;\n switch (bt as BlockType) {\n case 'image':\n limit = cfg.maxImageBytes ?? 0;\n break;\n case 'video':\n limit = cfg.maxVideoBytes ?? 0;\n break;\n case 'document':\n limit = cfg.maxPDFBytes ?? 0;\n break;\n default:\n continue;\n }\n if (limit <= 0) continue;\n\n const data = extractBase64Data(raw);\n if (data === '') continue; // URL 版或形态异常, 跳过\n const actual = base64DecodedLen(data);\n if (actual > limit) {\n throw new SizeError(bt as BlockType, actual, limit);\n }\n }\n }\n}\n\n/**\n * 从 Anthropic block 结构中抽 base64 data 字段。\n *\n * 形态:\n * image: {source:{type:\"base64\", data:\"...\"}}\n * video: {source:{type:\"base64\", data:\"...\"}}\n * document: {source:{type:\"base64\", data:\"...\"}}\n *\n * 若是 URL 版 (source.type=\"url\") 或缺字段, 返回 \"\"。\n */\nfunction extractBase64Data(block: Record<string, unknown>): string {\n const src = block['source'];\n if (!isPlainObject(src)) return '';\n if (src['type'] !== 'base64') return '';\n const dataRaw = src['data'];\n if (typeof dataRaw !== 'string') return '';\n let data: string = dataRaw;\n // 防御性: 某些上游把 \"data:image/jpeg;base64,...\" 整串塞进 data,\n // 虽非标准但兜底去掉前缀, 保证字节数估算正确。\n const i = data.indexOf('base64,');\n if (i >= 0) {\n data = data.slice(i + 'base64,'.length);\n }\n return data;\n}\n\n/** Go base64.StdEncoding.DecodedLen 等价: ceil(n*3/4) 减去 padding */\nfunction base64DecodedLen(b64: string): number {\n // Go 的 DecodedLen(n) = (n / 4) * 3 - paddingCount\n const n = b64.length;\n let pad = 0;\n if (n >= 1 && b64[n - 1] === '=') pad++;\n if (n >= 2 && b64[n - 2] === '=') pad++;\n return Math.floor((n * 3) / 4) - pad;\n}\n\nfunction isPlainObject(v: unknown): v is Record<string, unknown> {\n return typeof v === 'object' && v !== null && !Array.isArray(v);\n}\n"]}
@@ -0,0 +1 @@
1
+ export { B as BlockCodeExecutionToolResult, a as BlockContainerUpload, b as BlockDeniedError, c as BlockDocument, d as BlockImage, e as BlockMCPToolResult, f as BlockMCPToolUse, g as BlockPredicate, h as BlockRedactedThinking, j as BlockSearchResult, k as BlockServerToolUse, l as BlockText, m as BlockThinking, n as BlockToolReference, o as BlockToolResult, p as BlockToolUse, q as BlockType, r as BlockVideo, s as BlockWebSearchToolResult, D as DeltaCitations, t as DeltaInputJSON, u as DeltaSignature, v as DeltaText, w as DeltaThinking, x as DeltaType, E as EphemeralMarkerField, y as ErrBlockDenied, z as ErrHistoryTooDeep, H as HistoryTooDeepError, M as MinimalSanitizeConfig, S as SizeError, A as dropBlocks, C as sanitize, F as stripEphemeral } from '../index-nvLKgCm9.cjs';
@@ -0,0 +1 @@
1
+ export { B as BlockCodeExecutionToolResult, a as BlockContainerUpload, b as BlockDeniedError, c as BlockDocument, d as BlockImage, e as BlockMCPToolResult, f as BlockMCPToolUse, g as BlockPredicate, h as BlockRedactedThinking, j as BlockSearchResult, k as BlockServerToolUse, l as BlockText, m as BlockThinking, n as BlockToolReference, o as BlockToolResult, p as BlockToolUse, q as BlockType, r as BlockVideo, s as BlockWebSearchToolResult, D as DeltaCitations, t as DeltaInputJSON, u as DeltaSignature, v as DeltaText, w as DeltaThinking, x as DeltaType, E as EphemeralMarkerField, y as ErrBlockDenied, z as ErrHistoryTooDeep, H as HistoryTooDeepError, M as MinimalSanitizeConfig, S as SizeError, A as dropBlocks, C as sanitize, F as stripEphemeral } from '../index-nvLKgCm9.js';
@@ -0,0 +1,217 @@
1
+ // src/sanitize/types.ts
2
+ var BlockText = "text";
3
+ var BlockImage = "image";
4
+ var BlockVideo = "video";
5
+ var BlockDocument = "document";
6
+ var BlockSearchResult = "search_result";
7
+ var BlockThinking = "thinking";
8
+ var BlockRedactedThinking = "redacted_thinking";
9
+ var BlockToolUse = "tool_use";
10
+ var BlockToolResult = "tool_result";
11
+ var BlockToolReference = "tool_reference";
12
+ var BlockServerToolUse = "server_tool_use";
13
+ var BlockWebSearchToolResult = "web_search_tool_result";
14
+ var BlockCodeExecutionToolResult = "code_execution_tool_result";
15
+ var BlockMCPToolUse = "mcp_tool_use";
16
+ var BlockMCPToolResult = "mcp_tool_result";
17
+ var BlockContainerUpload = "container_upload";
18
+ var DeltaText = "text_delta";
19
+ var DeltaInputJSON = "input_json_delta";
20
+ var DeltaThinking = "thinking_delta";
21
+ var DeltaSignature = "signature_delta";
22
+ var DeltaCitations = "citations_delta";
23
+ var EphemeralMarkerField = "acosmi_ephemeral";
24
+
25
+ // src/sanitize/config.ts
26
+ var HistoryTooDeepError = class extends Error {
27
+ constructor() {
28
+ super("sanitize: messages history exceeds configured depth");
29
+ this.name = "HistoryTooDeepError";
30
+ }
31
+ };
32
+ var BlockDeniedError = class extends Error {
33
+ constructor() {
34
+ super("sanitize: block type permanently denied");
35
+ this.name = "BlockDeniedError";
36
+ }
37
+ };
38
+ var SizeError = class extends Error {
39
+ blockType;
40
+ actual;
41
+ limit;
42
+ constructor(blockType, actual, limit) {
43
+ super(`sanitize: ${blockType} base64 size ${actual} exceeds limit ${limit}`);
44
+ this.name = "SizeError";
45
+ this.blockType = blockType;
46
+ this.actual = actual;
47
+ this.limit = limit;
48
+ }
49
+ };
50
+ var ErrHistoryTooDeep = new HistoryTooDeepError();
51
+ var ErrBlockDenied = new BlockDeniedError();
52
+
53
+ // src/sanitize/history.ts
54
+ function dropBlocks(messages, pred) {
55
+ const droppedToolUseIDs = collectDroppedToolUseIDs(messages, pred);
56
+ const out = [];
57
+ for (const msg of messages) {
58
+ if (!isPlainObject(msg)) {
59
+ out.push(msg);
60
+ continue;
61
+ }
62
+ const content = msg["content"];
63
+ if (!Array.isArray(content)) {
64
+ out.push(msg);
65
+ continue;
66
+ }
67
+ const { kept, changed } = filterBlocks(content, pred, droppedToolUseIDs);
68
+ if (!changed) {
69
+ out.push(msg);
70
+ continue;
71
+ }
72
+ if (kept.length === 0) {
73
+ continue;
74
+ }
75
+ const newMsg = { ...msg };
76
+ newMsg["content"] = kept;
77
+ out.push(newMsg);
78
+ }
79
+ return out;
80
+ }
81
+ function collectDroppedToolUseIDs(messages, pred) {
82
+ const ids = /* @__PURE__ */ new Set();
83
+ for (const msg of messages) {
84
+ if (!isPlainObject(msg)) continue;
85
+ const content = msg["content"];
86
+ if (!Array.isArray(content)) continue;
87
+ for (const raw of content) {
88
+ if (!isPlainObject(raw)) continue;
89
+ if (!pred(raw)) continue;
90
+ const t = raw["type"];
91
+ if (typeof t !== "string") continue;
92
+ if (t === "tool_use" || t === "server_tool_use" || t === "mcp_tool_use") {
93
+ const id = raw["id"];
94
+ if (typeof id === "string" && id !== "") ids.add(id);
95
+ }
96
+ }
97
+ }
98
+ return ids;
99
+ }
100
+ function filterBlocks(content, pred, droppedToolUseIDs) {
101
+ const kept = [];
102
+ let changed = false;
103
+ for (const raw of content) {
104
+ if (!isPlainObject(raw)) {
105
+ kept.push(raw);
106
+ continue;
107
+ }
108
+ if (pred(raw)) {
109
+ changed = true;
110
+ continue;
111
+ }
112
+ if (droppedToolUseIDs.size > 0) {
113
+ const t = raw["type"];
114
+ if (t === "tool_result" || t === "mcp_tool_result") {
115
+ const id = raw["tool_use_id"];
116
+ if (typeof id === "string" && id !== "" && droppedToolUseIDs.has(id)) {
117
+ changed = true;
118
+ continue;
119
+ }
120
+ }
121
+ }
122
+ kept.push(raw);
123
+ }
124
+ return { kept, changed };
125
+ }
126
+ function stripEphemeral(messages) {
127
+ return dropBlocks(messages, (b) => {
128
+ const t = b["type"];
129
+ if (t === "thinking" || t === "redacted_thinking") {
130
+ return false;
131
+ }
132
+ const v = b[EphemeralMarkerField];
133
+ return v === true;
134
+ });
135
+ }
136
+ function isPlainObject(v) {
137
+ return typeof v === "object" && v !== null && !Array.isArray(v);
138
+ }
139
+
140
+ // src/sanitize/defensive.ts
141
+ function sanitize(messages, cfg) {
142
+ if ((cfg.maxMessagesTurns ?? 0) > 0 && messages.length > cfg.maxMessagesTurns) {
143
+ throw ErrHistoryTooDeep;
144
+ }
145
+ if ((cfg.maxImageBytes ?? 0) > 0 || (cfg.maxVideoBytes ?? 0) > 0 || (cfg.maxPDFBytes ?? 0) > 0) {
146
+ checkMediaSizes(messages, cfg);
147
+ }
148
+ if (cfg.permanentDenyBlocks && cfg.permanentDenyBlocks.length > 0) {
149
+ const denySet = /* @__PURE__ */ new Set();
150
+ for (const bt of cfg.permanentDenyBlocks) denySet.add(bt);
151
+ messages = dropBlocks(messages, (b) => {
152
+ const t = b["type"];
153
+ return typeof t === "string" && denySet.has(t);
154
+ });
155
+ }
156
+ return messages;
157
+ }
158
+ function checkMediaSizes(messages, cfg) {
159
+ for (const msg of messages) {
160
+ if (!isPlainObject2(msg)) continue;
161
+ const content = msg["content"];
162
+ if (!Array.isArray(content)) continue;
163
+ for (const raw of content) {
164
+ if (!isPlainObject2(raw)) continue;
165
+ const bt = raw["type"];
166
+ if (typeof bt !== "string") continue;
167
+ let limit = 0;
168
+ switch (bt) {
169
+ case "image":
170
+ limit = cfg.maxImageBytes ?? 0;
171
+ break;
172
+ case "video":
173
+ limit = cfg.maxVideoBytes ?? 0;
174
+ break;
175
+ case "document":
176
+ limit = cfg.maxPDFBytes ?? 0;
177
+ break;
178
+ default:
179
+ continue;
180
+ }
181
+ if (limit <= 0) continue;
182
+ const data = extractBase64Data(raw);
183
+ if (data === "") continue;
184
+ const actual = base64DecodedLen(data);
185
+ if (actual > limit) {
186
+ throw new SizeError(bt, actual, limit);
187
+ }
188
+ }
189
+ }
190
+ }
191
+ function extractBase64Data(block) {
192
+ const src = block["source"];
193
+ if (!isPlainObject2(src)) return "";
194
+ if (src["type"] !== "base64") return "";
195
+ const dataRaw = src["data"];
196
+ if (typeof dataRaw !== "string") return "";
197
+ let data = dataRaw;
198
+ const i = data.indexOf("base64,");
199
+ if (i >= 0) {
200
+ data = data.slice(i + "base64,".length);
201
+ }
202
+ return data;
203
+ }
204
+ function base64DecodedLen(b64) {
205
+ const n = b64.length;
206
+ let pad = 0;
207
+ if (n >= 1 && b64[n - 1] === "=") pad++;
208
+ if (n >= 2 && b64[n - 2] === "=") pad++;
209
+ return Math.floor(n * 3 / 4) - pad;
210
+ }
211
+ function isPlainObject2(v) {
212
+ return typeof v === "object" && v !== null && !Array.isArray(v);
213
+ }
214
+
215
+ export { BlockCodeExecutionToolResult, BlockContainerUpload, BlockDeniedError, BlockDocument, BlockImage, BlockMCPToolResult, BlockMCPToolUse, BlockRedactedThinking, BlockSearchResult, BlockServerToolUse, BlockText, BlockThinking, BlockToolReference, BlockToolResult, BlockToolUse, BlockVideo, BlockWebSearchToolResult, DeltaCitations, DeltaInputJSON, DeltaSignature, DeltaText, DeltaThinking, EphemeralMarkerField, ErrBlockDenied, ErrHistoryTooDeep, HistoryTooDeepError, SizeError, dropBlocks, sanitize, stripEphemeral };
216
+ //# sourceMappingURL=index.js.map
217
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/sanitize/types.ts","../../../src/sanitize/config.ts","../../../src/sanitize/history.ts","../../../src/sanitize/defensive.ts"],"names":["isPlainObject"],"mappings":"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","file":"index.js","sourcesContent":["// sanitize/types.ts — 端口自 acosmi-sdk-go/sanitize/types.go\n//\n// Provider 无关的 content-block 防御性处理。\n// 职责边界:\n// - SDK 层: 只做所有下游 provider 都不可能接受的底线剥除 + 早失败\n// - Gateway 层: 按 provider preset 精细剥除 (不在本包内)\n\n/** Anthropic content block 类型 (请求 + 响应 + ephemeral) */\nexport type BlockType =\n | 'text'\n | 'image'\n | 'video'\n | 'document'\n | 'search_result'\n | 'thinking'\n | 'redacted_thinking'\n | 'tool_use'\n | 'tool_result'\n | 'tool_reference'\n | 'server_tool_use'\n | 'web_search_tool_result'\n | 'code_execution_tool_result'\n | 'mcp_tool_use'\n | 'mcp_tool_result'\n | 'container_upload';\n\nexport const BlockText: BlockType = 'text';\nexport const BlockImage: BlockType = 'image';\nexport const BlockVideo: BlockType = 'video';\nexport const BlockDocument: BlockType = 'document';\nexport const BlockSearchResult: BlockType = 'search_result';\nexport const BlockThinking: BlockType = 'thinking';\nexport const BlockRedactedThinking: BlockType = 'redacted_thinking';\nexport const BlockToolUse: BlockType = 'tool_use';\nexport const BlockToolResult: BlockType = 'tool_result';\nexport const BlockToolReference: BlockType = 'tool_reference';\nexport const BlockServerToolUse: BlockType = 'server_tool_use';\nexport const BlockWebSearchToolResult: BlockType = 'web_search_tool_result';\nexport const BlockCodeExecutionToolResult: BlockType = 'code_execution_tool_result';\nexport const BlockMCPToolUse: BlockType = 'mcp_tool_use';\nexport const BlockMCPToolResult: BlockType = 'mcp_tool_result';\nexport const BlockContainerUpload: BlockType = 'container_upload';\n\n/** 流式响应 delta 类型 (与 BlockType 正交) */\nexport type DeltaType =\n | 'text_delta'\n | 'input_json_delta'\n | 'thinking_delta'\n | 'signature_delta'\n | 'citations_delta';\n\nexport const DeltaText: DeltaType = 'text_delta';\nexport const DeltaInputJSON: DeltaType = 'input_json_delta';\nexport const DeltaThinking: DeltaType = 'thinking_delta';\nexport const DeltaSignature: DeltaType = 'signature_delta';\nexport const DeltaCitations: DeltaType = 'citations_delta';\n\n/**\n * 网关在 block JSON 中注入的 in-band 标记字段名。\n * 存在且值为 true 时代表该 block 不应回传下一轮。\n *\n * 选择 in-band 标记而非独立 SSE 事件的理由:\n * - 零缓冲 / 零顺序依赖 / 零延迟\n * - history 剥离天然可做\n */\nexport const EphemeralMarkerField = 'acosmi_ephemeral';\n","// sanitize/config.ts — 端口自 acosmi-sdk-go/sanitize/config.go\n\nimport type { BlockType } from './types';\n\n/**\n * SDK 层防御性配置。只做所有下游 provider 都不可能接受的底线剥除;\n * 具体 provider 适配由网关承担。\n *\n * 零值 = 不校验 / 不剥除, 等价于未启用。\n */\nexport interface MinimalSanitizeConfig {\n /** 体积上限 (字节, 仅对 base64 内联媒体生效; URL 版交网关把关). 0 = 不校验. */\n maxImageBytes?: number;\n maxVideoBytes?: number;\n maxPDFBytes?: number;\n\n /** history 轮次硬上限 (防止内存爆炸 / 上行带宽). 0 = 不校验. 超限抛 ErrHistoryTooDeep. */\n maxMessagesTurns?: number;\n\n /** 公共黑名单 (所有 provider 均拒绝的 block 类型). 默认空. */\n permanentDenyBlocks?: BlockType[];\n}\n\nexport class HistoryTooDeepError extends Error {\n constructor() {\n super('sanitize: messages history exceeds configured depth');\n this.name = 'HistoryTooDeepError';\n }\n}\n\nexport class BlockDeniedError extends Error {\n constructor() {\n super('sanitize: block type permanently denied');\n this.name = 'BlockDeniedError';\n }\n}\n\n/** 体积超限错误, 携带实际/上限字节数以便上游展示。 */\nexport class SizeError extends Error {\n blockType: BlockType;\n actual: number;\n limit: number;\n\n constructor(blockType: BlockType, actual: number, limit: number) {\n super(`sanitize: ${blockType} base64 size ${actual} exceeds limit ${limit}`);\n this.name = 'SizeError';\n this.blockType = blockType;\n this.actual = actual;\n this.limit = limit;\n }\n}\n\n// 单例实例 — 与 Go 侧 var ErrHistoryTooDeep / ErrBlockDenied 等价的\"哨兵\"用法\n// 但 TS class 实例不能 const 哨兵化, 调用方应用 instanceof 判断。\nexport const ErrHistoryTooDeep = new HistoryTooDeepError();\nexport const ErrBlockDenied = new BlockDeniedError();\n","// sanitize/history.ts — 端口自 acosmi-sdk-go/sanitize/history.go\n\nimport { EphemeralMarkerField } from './types';\n\n/** 判定一个 block 是否应被剥除 */\nexport type BlockPredicate = (block: Record<string, unknown>) => boolean;\n\n/**\n * 按 pred 从 messages 中剥除 block, 并联动剥除对应的 tool_result (user 轮)。\n *\n * 收敛两步: 先扫收集 droppedToolUseIDs, 再整体剥; 这样顺序不影响正确性\n * (即使 tool_result 在 tool_use 之前出现也能捕获)。\n *\n * 不修改入参 messages 中任何 map 的 key, 只重构数组。\n * 未被修改的 message 保持原引用 (零拷贝优化)。\n */\nexport function dropBlocks(messages: unknown[], pred: BlockPredicate): unknown[] {\n const droppedToolUseIDs = collectDroppedToolUseIDs(messages, pred);\n\n const out: unknown[] = [];\n for (const msg of messages) {\n if (!isPlainObject(msg)) {\n out.push(msg);\n continue;\n }\n const content = msg['content'];\n if (!Array.isArray(content)) {\n out.push(msg);\n continue;\n }\n\n const { kept, changed } = filterBlocks(content, pred, droppedToolUseIDs);\n if (!changed) {\n out.push(msg);\n continue;\n }\n\n // content 空了的消息整条丢弃 (assistant 本轮全是 ephemeral 块;\n // 或 user 轮全是联动剥的 tool_result)。避免产生空消息让 provider 报错。\n if (kept.length === 0) {\n continue;\n }\n\n // 浅拷贝 msg, 只改 content, 避免污染调用方数据。\n const newMsg: Record<string, unknown> = { ...msg };\n newMsg['content'] = kept;\n out.push(newMsg);\n }\n return out;\n}\n\n/** 第一遍扫描, 仅收集\"本次被 pred 命中的 tool_use 类 block 的 id\", 以便联动剥 tool_result。 */\nfunction collectDroppedToolUseIDs(messages: unknown[], pred: BlockPredicate): Set<string> {\n const ids = new Set<string>();\n for (const msg of messages) {\n if (!isPlainObject(msg)) continue;\n const content = msg['content'];\n if (!Array.isArray(content)) continue;\n for (const raw of content) {\n if (!isPlainObject(raw)) continue;\n if (!pred(raw)) continue;\n const t = raw['type'];\n if (typeof t !== 'string') continue;\n if (t === 'tool_use' || t === 'server_tool_use' || t === 'mcp_tool_use') {\n const id = raw['id'];\n if (typeof id === 'string' && id !== '') ids.add(id);\n }\n }\n }\n return ids;\n}\n\n/**\n * 对单条消息的 content 数组剥除 pred 命中的 block, 以及\n * tool_use_id 在 droppedToolUseIDs 中的 tool_result。返回 (新数组, 是否变更)。\n */\nfunction filterBlocks(\n content: unknown[],\n pred: BlockPredicate,\n droppedToolUseIDs: Set<string>,\n): { kept: unknown[]; changed: boolean } {\n const kept: unknown[] = [];\n let changed = false;\n for (const raw of content) {\n if (!isPlainObject(raw)) {\n kept.push(raw);\n continue;\n }\n if (pred(raw)) {\n changed = true;\n continue;\n }\n // 联动剥 tool_result / mcp_tool_result (仅当 tool_use_id 命中)\n if (droppedToolUseIDs.size > 0) {\n const t = raw['type'];\n if (t === 'tool_result' || t === 'mcp_tool_result') {\n const id = raw['tool_use_id'];\n if (typeof id === 'string' && id !== '' && droppedToolUseIDs.has(id)) {\n changed = true;\n continue;\n }\n }\n }\n kept.push(raw);\n }\n return { kept, changed };\n}\n\n/**\n * 从 messages 中剥除带 acosmi_ephemeral:true 标记的 block, 以及对应的 tool_result。\n *\n * 硬豁免: thinking / redacted_thinking 块永不剥, 即使携带 ephemeral 标记。\n *\n * 理由: Anthropic extended thinking + tool_use 续轮场景下, 上游强制要求\n * assistant 历史中保留原始 thinking 块, 否则返回:\n * \"The content[].thinking in the thinking mode must be passed back to the API.\"\n *\n * 历史污染防御: 即使旧版网关 / 历史响应里 thinking 块带了 ephemeral=true,\n * 客户端在调用 stripEphemeral 时也不应剥除。网关侧 anthropic preset 已停止注入此标记,\n * 本豁免兜底历史会话与第三方调用方两类已污染场景。\n */\nexport function stripEphemeral(messages: unknown[]): unknown[] {\n return dropBlocks(messages, (b) => {\n const t = b['type'];\n if (t === 'thinking' || t === 'redacted_thinking') {\n return false; // 硬豁免, 不可被 ephemeral 标记覆盖\n }\n const v = b[EphemeralMarkerField];\n return v === true;\n });\n}\n\nfunction isPlainObject(v: unknown): v is Record<string, unknown> {\n return typeof v === 'object' && v !== null && !Array.isArray(v);\n}\n","// sanitize/defensive.ts — 端口自 acosmi-sdk-go/sanitize/defensive.go\n\nimport type { BlockType } from './types';\nimport type { MinimalSanitizeConfig } from './config';\nimport { ErrHistoryTooDeep, SizeError } from './config';\nimport { dropBlocks } from './history';\n\n/**\n * 对已经解析为 unknown[] 的 messages 做底线防御: 深度校验 +\n * 体积校验 + deny-list 剥除 + tool_use_id 联动剥除 tool_result。\n *\n * 入参形态: messages 的每个元素预期是 object (含 role / content); content 为 string (plain text)\n * 或 unknown[] (block 数组)。形态异常的元素原样透传, 不抛错。\n *\n * 通过返回新 messages (可能 block 数组被缩减); 早失败抛错 (调用方应放弃本次请求)。\n */\nexport function sanitize(messages: unknown[], cfg: MinimalSanitizeConfig): unknown[] {\n if ((cfg.maxMessagesTurns ?? 0) > 0 && messages.length > (cfg.maxMessagesTurns as number)) {\n throw ErrHistoryTooDeep;\n }\n\n // 体积校验: 先扫一遍, 任何违规直接早失败 (不修改 messages)\n if ((cfg.maxImageBytes ?? 0) > 0 || (cfg.maxVideoBytes ?? 0) > 0 || (cfg.maxPDFBytes ?? 0) > 0) {\n checkMediaSizes(messages, cfg);\n }\n\n // deny-list 剥除 + tool_use_id 联动\n if (cfg.permanentDenyBlocks && cfg.permanentDenyBlocks.length > 0) {\n const denySet = new Set<string>();\n for (const bt of cfg.permanentDenyBlocks) denySet.add(bt as string);\n messages = dropBlocks(messages, (b) => {\n const t = (b as Record<string, unknown>)['type'];\n return typeof t === 'string' && denySet.has(t);\n });\n }\n\n return messages;\n}\n\n/**\n * 遍历所有 block, 对 base64 内联 image/video/document 类型校验解码后字节数。\n * URL 版无法本地量体积, 跳过 (交网关把关)。违规直接抛 SizeError。\n */\nfunction checkMediaSizes(messages: unknown[], cfg: MinimalSanitizeConfig): void {\n for (const msg of messages) {\n if (!isPlainObject(msg)) continue;\n const content = msg['content'];\n if (!Array.isArray(content)) continue;\n for (const raw of content) {\n if (!isPlainObject(raw)) continue;\n const bt = raw['type'];\n if (typeof bt !== 'string') continue;\n\n let limit = 0;\n switch (bt as BlockType) {\n case 'image':\n limit = cfg.maxImageBytes ?? 0;\n break;\n case 'video':\n limit = cfg.maxVideoBytes ?? 0;\n break;\n case 'document':\n limit = cfg.maxPDFBytes ?? 0;\n break;\n default:\n continue;\n }\n if (limit <= 0) continue;\n\n const data = extractBase64Data(raw);\n if (data === '') continue; // URL 版或形态异常, 跳过\n const actual = base64DecodedLen(data);\n if (actual > limit) {\n throw new SizeError(bt as BlockType, actual, limit);\n }\n }\n }\n}\n\n/**\n * 从 Anthropic block 结构中抽 base64 data 字段。\n *\n * 形态:\n * image: {source:{type:\"base64\", data:\"...\"}}\n * video: {source:{type:\"base64\", data:\"...\"}}\n * document: {source:{type:\"base64\", data:\"...\"}}\n *\n * 若是 URL 版 (source.type=\"url\") 或缺字段, 返回 \"\"。\n */\nfunction extractBase64Data(block: Record<string, unknown>): string {\n const src = block['source'];\n if (!isPlainObject(src)) return '';\n if (src['type'] !== 'base64') return '';\n const dataRaw = src['data'];\n if (typeof dataRaw !== 'string') return '';\n let data: string = dataRaw;\n // 防御性: 某些上游把 \"data:image/jpeg;base64,...\" 整串塞进 data,\n // 虽非标准但兜底去掉前缀, 保证字节数估算正确。\n const i = data.indexOf('base64,');\n if (i >= 0) {\n data = data.slice(i + 'base64,'.length);\n }\n return data;\n}\n\n/** Go base64.StdEncoding.DecodedLen 等价: ceil(n*3/4) 减去 padding */\nfunction base64DecodedLen(b64: string): number {\n // Go 的 DecodedLen(n) = (n / 4) * 3 - paddingCount\n const n = b64.length;\n let pad = 0;\n if (n >= 1 && b64[n - 1] === '=') pad++;\n if (n >= 2 && b64[n - 2] === '=') pad++;\n return Math.floor((n * 3) / 4) - pad;\n}\n\nfunction isPlainObject(v: unknown): v is Record<string, unknown> {\n return typeof v === 'object' && v !== null && !Array.isArray(v);\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,84 @@
1
+ {
2
+ "name": "@acosmi/sdk-ts",
3
+ "version": "1.0.0",
4
+ "description": "Acosmi 模型网关 TypeScript SDK — 双格式 (Anthropic + OpenAI) 多端 (浏览器 / Node ≥18 / Deno / Bun)。Go SDK 全量端口, 1.0 稳定测试版。",
5
+ "type": "module",
6
+ "main": "./dist/node/index.cjs",
7
+ "module": "./dist/node/index.mjs",
8
+ "types": "./dist/node/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/node/index.d.ts",
12
+ "browser": "./dist/browser/index.mjs",
13
+ "deno": "./dist/index.mjs",
14
+ "node": {
15
+ "types": "./dist/node/index.d.ts",
16
+ "import": "./dist/node/index.mjs",
17
+ "require": "./dist/node/index.cjs"
18
+ },
19
+ "default": "./dist/index.mjs"
20
+ },
21
+ "./adapters/anthropic": {
22
+ "types": "./dist/node/adapters/anthropic.d.ts",
23
+ "import": "./dist/node/adapters/anthropic.mjs",
24
+ "require": "./dist/node/adapters/anthropic.cjs"
25
+ },
26
+ "./adapters/openai": {
27
+ "types": "./dist/node/adapters/openai.d.ts",
28
+ "import": "./dist/node/adapters/openai.mjs",
29
+ "require": "./dist/node/adapters/openai.cjs"
30
+ },
31
+ "./sanitize": {
32
+ "types": "./dist/node/sanitize/index.d.ts",
33
+ "import": "./dist/node/sanitize/index.mjs",
34
+ "require": "./dist/node/sanitize/index.cjs"
35
+ },
36
+ "./package.json": "./package.json"
37
+ },
38
+ "files": ["dist", "README.md", "LICENSE"],
39
+ "scripts": {
40
+ "build": "tsup",
41
+ "test": "vitest run",
42
+ "test:watch": "vitest",
43
+ "lint": "eslint src --ext .ts",
44
+ "format": "prettier --write \"src/**/*.{ts,json}\"",
45
+ "typecheck": "tsc --noEmit",
46
+ "prepublishOnly": "npm run typecheck && npm run lint && npm test && npm run build"
47
+ },
48
+ "engines": {
49
+ "node": ">=18"
50
+ },
51
+ "keywords": [
52
+ "acosmi",
53
+ "anthropic",
54
+ "openai",
55
+ "llm",
56
+ "sdk",
57
+ "claude",
58
+ "crabcode",
59
+ "crabclaw"
60
+ ],
61
+ "license": "MIT",
62
+ "author": "Acosmi",
63
+ "repository": {
64
+ "type": "git",
65
+ "url": "git+https://github.com/acosmi/sdk-ts.git"
66
+ },
67
+ "homepage": "https://github.com/acosmi/sdk-ts#readme",
68
+ "bugs": {
69
+ "url": "https://github.com/acosmi/sdk-ts/issues"
70
+ },
71
+ "publishConfig": {
72
+ "access": "public"
73
+ },
74
+ "devDependencies": {
75
+ "@types/node": "^20.0.0",
76
+ "@typescript-eslint/eslint-plugin": "^7.0.0",
77
+ "@typescript-eslint/parser": "^7.0.0",
78
+ "eslint": "^8.57.0",
79
+ "prettier": "^3.0.0",
80
+ "tsup": "^8.0.0",
81
+ "typescript": "^5.4.0",
82
+ "vitest": "^1.0.0"
83
+ }
84
+ }