@shareai-lab/kode-sdk 2.7.1 → 2.7.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/dist/core/agent/breakpoint-manager.js +36 -0
- package/dist/core/agent/message-queue.js +57 -0
- package/dist/core/agent/permission-manager.js +32 -0
- package/dist/core/agent/todo-manager.js +91 -0
- package/dist/core/agent/tool-runner.js +45 -0
- package/dist/core/agent.js +2035 -0
- package/dist/core/config.js +2 -0
- package/dist/core/context-manager.js +241 -0
- package/dist/core/errors.js +49 -0
- package/dist/core/events.js +329 -0
- package/dist/core/file-pool.d.ts +2 -0
- package/dist/core/file-pool.js +125 -0
- package/dist/core/hooks.js +71 -0
- package/dist/core/permission-modes.js +61 -0
- package/dist/core/pool.js +301 -0
- package/dist/core/room.js +57 -0
- package/dist/core/scheduler.js +58 -0
- package/dist/core/skills/index.js +20 -0
- package/dist/core/skills/management-manager.js +557 -0
- package/dist/core/skills/manager.js +243 -0
- package/dist/core/skills/operation-queue.js +113 -0
- package/dist/core/skills/sandbox-file-manager.js +183 -0
- package/dist/core/skills/types.js +9 -0
- package/dist/core/skills/xml-generator.js +70 -0
- package/dist/core/template.js +35 -0
- package/dist/core/time-bridge.js +100 -0
- package/dist/core/todo.js +89 -0
- package/dist/core/types.js +3 -0
- package/dist/index.js +148 -60461
- package/dist/infra/db/postgres/postgres-store.js +1073 -0
- package/dist/infra/db/sqlite/sqlite-store.js +800 -0
- package/dist/infra/e2b/e2b-fs.js +128 -0
- package/dist/infra/e2b/e2b-sandbox.js +156 -0
- package/dist/infra/e2b/e2b-template.js +105 -0
- package/dist/infra/e2b/index.js +9 -0
- package/dist/infra/e2b/types.js +2 -0
- package/dist/infra/provider.js +67 -0
- package/dist/infra/providers/anthropic.js +308 -0
- package/dist/infra/providers/core/errors.js +353 -0
- package/dist/infra/providers/core/fork.js +418 -0
- package/dist/infra/providers/core/index.js +76 -0
- package/dist/infra/providers/core/logger.js +191 -0
- package/dist/infra/providers/core/retry.js +189 -0
- package/dist/infra/providers/core/usage.js +376 -0
- package/dist/infra/providers/gemini.js +493 -0
- package/dist/infra/providers/index.js +83 -0
- package/dist/infra/providers/openai.js +662 -0
- package/dist/infra/providers/types.js +20 -0
- package/dist/infra/providers/utils.js +400 -0
- package/dist/infra/sandbox-factory.js +30 -0
- package/dist/infra/sandbox.js +243 -0
- package/dist/infra/store/factory.js +80 -0
- package/dist/infra/store/index.js +26 -0
- package/dist/infra/store/json-store.js +606 -0
- package/dist/infra/store/types.js +2 -0
- package/dist/infra/store.js +29 -0
- package/dist/tools/bash_kill/index.js +35 -0
- package/dist/tools/bash_kill/prompt.js +14 -0
- package/dist/tools/bash_logs/index.js +40 -0
- package/dist/tools/bash_logs/prompt.js +14 -0
- package/dist/tools/bash_run/index.js +61 -0
- package/dist/tools/bash_run/prompt.js +18 -0
- package/dist/tools/builtin.js +26 -0
- package/dist/tools/define.js +214 -0
- package/dist/tools/fs_edit/index.js +62 -0
- package/dist/tools/fs_edit/prompt.js +15 -0
- package/dist/tools/fs_glob/index.js +40 -0
- package/dist/tools/fs_glob/prompt.js +15 -0
- package/dist/tools/fs_grep/index.js +66 -0
- package/dist/tools/fs_grep/prompt.js +16 -0
- package/dist/tools/fs_multi_edit/index.js +106 -0
- package/dist/tools/fs_multi_edit/prompt.js +16 -0
- package/dist/tools/fs_read/index.js +40 -0
- package/dist/tools/fs_read/prompt.js +16 -0
- package/dist/tools/fs_write/index.js +40 -0
- package/dist/tools/fs_write/prompt.js +15 -0
- package/dist/tools/index.js +61 -0
- package/dist/tools/mcp.js +185 -0
- package/dist/tools/registry.js +26 -0
- package/dist/tools/scripts.js +205 -0
- package/dist/tools/skills.js +115 -0
- package/dist/tools/task_run/index.js +58 -0
- package/dist/tools/task_run/prompt.js +25 -0
- package/dist/tools/todo_read/index.js +29 -0
- package/dist/tools/todo_read/prompt.js +18 -0
- package/dist/tools/todo_write/index.js +42 -0
- package/dist/tools/todo_write/prompt.js +23 -0
- package/dist/tools/tool.js +211 -0
- package/dist/tools/toolkit.js +98 -0
- package/dist/tools/type-inference.js +207 -0
- package/dist/utils/agent-id.js +28 -0
- package/dist/utils/logger.js +44 -0
- package/dist/utils/session-id.js +64 -0
- package/package.json +7 -38
- package/dist/index.js.map +0 -7
- package/dist/index.mjs +0 -60385
- package/dist/index.mjs.map +0 -7
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Shared utilities for provider implementations.
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.AUDIO_UNSUPPORTED_TEXT = exports.IMAGE_UNSUPPORTED_TEXT = exports.FILE_UNSUPPORTED_TEXT = void 0;
|
|
7
|
+
exports.resolveProxyUrl = resolveProxyUrl;
|
|
8
|
+
exports.getProxyDispatcher = getProxyDispatcher;
|
|
9
|
+
exports.withProxy = withProxy;
|
|
10
|
+
exports.normalizeBaseUrl = normalizeBaseUrl;
|
|
11
|
+
exports.normalizeOpenAIBaseUrl = normalizeOpenAIBaseUrl;
|
|
12
|
+
exports.normalizeAnthropicBaseUrl = normalizeAnthropicBaseUrl;
|
|
13
|
+
exports.normalizeGeminiBaseUrl = normalizeGeminiBaseUrl;
|
|
14
|
+
exports.getMessageBlocks = getMessageBlocks;
|
|
15
|
+
exports.markTransportIfDegraded = markTransportIfDegraded;
|
|
16
|
+
exports.joinTextBlocks = joinTextBlocks;
|
|
17
|
+
exports.formatToolResult = formatToolResult;
|
|
18
|
+
exports.safeJsonStringify = safeJsonStringify;
|
|
19
|
+
exports.concatTextWithReasoning = concatTextWithReasoning;
|
|
20
|
+
exports.joinReasoningBlocks = joinReasoningBlocks;
|
|
21
|
+
exports.normalizeThinkBlocks = normalizeThinkBlocks;
|
|
22
|
+
exports.splitThinkText = splitThinkText;
|
|
23
|
+
exports.extractReasoningDetails = extractReasoningDetails;
|
|
24
|
+
exports.buildGeminiImagePart = buildGeminiImagePart;
|
|
25
|
+
exports.buildGeminiFilePart = buildGeminiFilePart;
|
|
26
|
+
exports.sanitizeGeminiSchema = sanitizeGeminiSchema;
|
|
27
|
+
exports.hasAnthropicFileBlocks = hasAnthropicFileBlocks;
|
|
28
|
+
exports.mergeAnthropicBetaHeader = mergeAnthropicBetaHeader;
|
|
29
|
+
exports.normalizeAnthropicContent = normalizeAnthropicContent;
|
|
30
|
+
exports.normalizeAnthropicContentBlock = normalizeAnthropicContentBlock;
|
|
31
|
+
exports.normalizeAnthropicDelta = normalizeAnthropicDelta;
|
|
32
|
+
// =============================================================================
|
|
33
|
+
// Proxy Handling
|
|
34
|
+
// =============================================================================
|
|
35
|
+
const proxyAgents = new Map();
|
|
36
|
+
function resolveProxyUrl(explicit) {
|
|
37
|
+
if (explicit)
|
|
38
|
+
return explicit;
|
|
39
|
+
const flag = process.env.KODE_USE_ENV_PROXY;
|
|
40
|
+
if (!flag || ['0', 'false', 'no'].includes(flag.toLowerCase())) {
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
return (process.env.HTTPS_PROXY ||
|
|
44
|
+
process.env.https_proxy ||
|
|
45
|
+
process.env.HTTP_PROXY ||
|
|
46
|
+
process.env.http_proxy ||
|
|
47
|
+
process.env.ALL_PROXY ||
|
|
48
|
+
process.env.all_proxy);
|
|
49
|
+
}
|
|
50
|
+
function getProxyDispatcher(proxyUrl) {
|
|
51
|
+
const resolved = resolveProxyUrl(proxyUrl);
|
|
52
|
+
if (!resolved)
|
|
53
|
+
return undefined;
|
|
54
|
+
const cached = proxyAgents.get(resolved);
|
|
55
|
+
if (cached)
|
|
56
|
+
return cached;
|
|
57
|
+
let ProxyAgent;
|
|
58
|
+
try {
|
|
59
|
+
({ ProxyAgent } = require('undici'));
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
throw new Error(`Proxy support requires undici. Install it to use proxyUrl (${error?.message || error}).`);
|
|
63
|
+
}
|
|
64
|
+
const agent = new ProxyAgent(resolved);
|
|
65
|
+
proxyAgents.set(resolved, agent);
|
|
66
|
+
return agent;
|
|
67
|
+
}
|
|
68
|
+
function withProxy(init, dispatcher) {
|
|
69
|
+
if (!dispatcher)
|
|
70
|
+
return init;
|
|
71
|
+
return { ...init, dispatcher };
|
|
72
|
+
}
|
|
73
|
+
// =============================================================================
|
|
74
|
+
// URL Normalization
|
|
75
|
+
// =============================================================================
|
|
76
|
+
function normalizeBaseUrl(url) {
|
|
77
|
+
return url.replace(/\/+$/, '');
|
|
78
|
+
}
|
|
79
|
+
function normalizeOpenAIBaseUrl(url) {
|
|
80
|
+
let normalized = url.replace(/\/+$/, '');
|
|
81
|
+
// Auto-append /v1 if not present (for OpenAI-compatible APIs)
|
|
82
|
+
if (!normalized.endsWith('/v1')) {
|
|
83
|
+
normalized += '/v1';
|
|
84
|
+
}
|
|
85
|
+
return normalized;
|
|
86
|
+
}
|
|
87
|
+
function normalizeAnthropicBaseUrl(url) {
|
|
88
|
+
let normalized = url.replace(/\/+$/, '');
|
|
89
|
+
if (normalized.endsWith('/v1')) {
|
|
90
|
+
normalized = normalized.slice(0, -3);
|
|
91
|
+
}
|
|
92
|
+
return normalized;
|
|
93
|
+
}
|
|
94
|
+
function normalizeGeminiBaseUrl(url) {
|
|
95
|
+
let normalized = url.replace(/\/+$/, '');
|
|
96
|
+
// Auto-append /v1beta if no version path present
|
|
97
|
+
if (!normalized.endsWith('/v1beta') && !normalized.endsWith('/v1')) {
|
|
98
|
+
normalized += '/v1beta';
|
|
99
|
+
}
|
|
100
|
+
return normalized;
|
|
101
|
+
}
|
|
102
|
+
// =============================================================================
|
|
103
|
+
// Content Block Utilities
|
|
104
|
+
// =============================================================================
|
|
105
|
+
function getMessageBlocks(message) {
|
|
106
|
+
if (message.metadata?.transport === 'omit') {
|
|
107
|
+
return message.content;
|
|
108
|
+
}
|
|
109
|
+
return message.metadata?.content_blocks ?? message.content;
|
|
110
|
+
}
|
|
111
|
+
function markTransportIfDegraded(message, blocks) {
|
|
112
|
+
if (message.metadata?.transport === 'omit') {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (!message.metadata) {
|
|
116
|
+
message.metadata = { content_blocks: blocks, transport: 'text' };
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (!message.metadata.content_blocks) {
|
|
120
|
+
message.metadata.content_blocks = blocks;
|
|
121
|
+
}
|
|
122
|
+
message.metadata.transport = 'text';
|
|
123
|
+
}
|
|
124
|
+
// =============================================================================
|
|
125
|
+
// Text Formatting
|
|
126
|
+
// =============================================================================
|
|
127
|
+
function joinTextBlocks(blocks) {
|
|
128
|
+
return blocks
|
|
129
|
+
.filter((block) => block.type === 'text')
|
|
130
|
+
.map((block) => block.text)
|
|
131
|
+
.join('');
|
|
132
|
+
}
|
|
133
|
+
function formatToolResult(content) {
|
|
134
|
+
if (typeof content === 'string')
|
|
135
|
+
return content;
|
|
136
|
+
return safeJsonStringify(content);
|
|
137
|
+
}
|
|
138
|
+
function safeJsonStringify(value) {
|
|
139
|
+
try {
|
|
140
|
+
const json = JSON.stringify(value ?? {});
|
|
141
|
+
return json === undefined ? '{}' : json;
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
return '{}';
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
// =============================================================================
|
|
148
|
+
// Unsupported Content Messages
|
|
149
|
+
// =============================================================================
|
|
150
|
+
exports.FILE_UNSUPPORTED_TEXT = '[file unsupported] This model does not support PDF input. Please extract text or images first.';
|
|
151
|
+
exports.IMAGE_UNSUPPORTED_TEXT = '[image unsupported] This model does not support image URLs; please provide base64 data if supported.';
|
|
152
|
+
exports.AUDIO_UNSUPPORTED_TEXT = '[audio unsupported] This model does not support audio input; please provide a text transcript instead.';
|
|
153
|
+
// =============================================================================
|
|
154
|
+
// Reasoning/Thinking Utilities
|
|
155
|
+
// =============================================================================
|
|
156
|
+
function concatTextWithReasoning(blocks, reasoningTransport = 'text') {
|
|
157
|
+
let text = '';
|
|
158
|
+
for (const block of blocks) {
|
|
159
|
+
if (block.type === 'text') {
|
|
160
|
+
text += block.text;
|
|
161
|
+
}
|
|
162
|
+
else if (block.type === 'reasoning' && reasoningTransport === 'text') {
|
|
163
|
+
text += `<think>${block.reasoning}</think>`;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return text;
|
|
167
|
+
}
|
|
168
|
+
function joinReasoningBlocks(blocks) {
|
|
169
|
+
return blocks
|
|
170
|
+
.filter((block) => block.type === 'reasoning')
|
|
171
|
+
.map((block) => block.reasoning)
|
|
172
|
+
.join('\n');
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Parse <think> tags in text blocks and convert to reasoning blocks.
|
|
176
|
+
*/
|
|
177
|
+
function normalizeThinkBlocks(blocks, reasoningTransport = 'text') {
|
|
178
|
+
if (reasoningTransport !== 'text') {
|
|
179
|
+
return blocks;
|
|
180
|
+
}
|
|
181
|
+
const output = [];
|
|
182
|
+
for (const block of blocks) {
|
|
183
|
+
if (block.type !== 'text') {
|
|
184
|
+
output.push(block);
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
const parts = splitThinkText(block.text);
|
|
188
|
+
if (parts.length === 0) {
|
|
189
|
+
output.push(block);
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
output.push(...parts);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return output;
|
|
196
|
+
}
|
|
197
|
+
function splitThinkText(text) {
|
|
198
|
+
const blocks = [];
|
|
199
|
+
const regex = /<think>([\s\S]*?)<\/think>/g;
|
|
200
|
+
let match;
|
|
201
|
+
let cursor = 0;
|
|
202
|
+
let matched = false;
|
|
203
|
+
while ((match = regex.exec(text)) !== null) {
|
|
204
|
+
matched = true;
|
|
205
|
+
const before = text.slice(cursor, match.index);
|
|
206
|
+
if (before) {
|
|
207
|
+
blocks.push({ type: 'text', text: before });
|
|
208
|
+
}
|
|
209
|
+
const reasoning = match[1] || '';
|
|
210
|
+
blocks.push({ type: 'reasoning', reasoning });
|
|
211
|
+
cursor = match.index + match[0].length;
|
|
212
|
+
}
|
|
213
|
+
if (!matched) {
|
|
214
|
+
return [];
|
|
215
|
+
}
|
|
216
|
+
const after = text.slice(cursor);
|
|
217
|
+
if (after) {
|
|
218
|
+
blocks.push({ type: 'text', text: after });
|
|
219
|
+
}
|
|
220
|
+
return blocks;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Extract reasoning details from OpenAI response (for reasoning models).
|
|
224
|
+
*/
|
|
225
|
+
function extractReasoningDetails(message) {
|
|
226
|
+
const details = Array.isArray(message?.reasoning_details) ? message.reasoning_details : [];
|
|
227
|
+
const content = typeof message?.reasoning_content === 'string' ? message.reasoning_content : undefined;
|
|
228
|
+
const blocks = [];
|
|
229
|
+
for (const detail of details) {
|
|
230
|
+
if (typeof detail?.text === 'string') {
|
|
231
|
+
blocks.push({ type: 'reasoning', reasoning: detail.text });
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (content) {
|
|
235
|
+
blocks.push({ type: 'reasoning', reasoning: content });
|
|
236
|
+
}
|
|
237
|
+
return blocks;
|
|
238
|
+
}
|
|
239
|
+
// =============================================================================
|
|
240
|
+
// Gemini Helpers
|
|
241
|
+
// =============================================================================
|
|
242
|
+
function buildGeminiImagePart(block) {
|
|
243
|
+
if (block.file_id) {
|
|
244
|
+
return { file_data: { mime_type: block.mime_type, file_uri: block.file_id } };
|
|
245
|
+
}
|
|
246
|
+
if (block.url) {
|
|
247
|
+
if (block.url.startsWith('gs://')) {
|
|
248
|
+
return { file_data: { mime_type: block.mime_type, file_uri: block.url } };
|
|
249
|
+
}
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
if (block.base64 && block.mime_type) {
|
|
253
|
+
return { inline_data: { mime_type: block.mime_type, data: block.base64 } };
|
|
254
|
+
}
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
function buildGeminiFilePart(block) {
|
|
258
|
+
const mimeType = block.mime_type || 'application/pdf';
|
|
259
|
+
if (block.file_id) {
|
|
260
|
+
return { file_data: { mime_type: mimeType, file_uri: block.file_id } };
|
|
261
|
+
}
|
|
262
|
+
if (block.url) {
|
|
263
|
+
if (block.url.startsWith('gs://')) {
|
|
264
|
+
return { file_data: { mime_type: mimeType, file_uri: block.url } };
|
|
265
|
+
}
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
if (block.base64) {
|
|
269
|
+
return { inline_data: { mime_type: mimeType, data: block.base64 } };
|
|
270
|
+
}
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
273
|
+
function sanitizeGeminiSchema(schema) {
|
|
274
|
+
if (schema === null || schema === undefined)
|
|
275
|
+
return schema;
|
|
276
|
+
if (Array.isArray(schema))
|
|
277
|
+
return schema.map((item) => sanitizeGeminiSchema(item));
|
|
278
|
+
if (typeof schema !== 'object')
|
|
279
|
+
return schema;
|
|
280
|
+
const cleaned = {};
|
|
281
|
+
for (const [key, value] of Object.entries(schema)) {
|
|
282
|
+
if (key === 'additionalProperties' || key === '$schema' || key === '$defs' || key === 'definitions') {
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
cleaned[key] = sanitizeGeminiSchema(value);
|
|
286
|
+
}
|
|
287
|
+
return cleaned;
|
|
288
|
+
}
|
|
289
|
+
// =============================================================================
|
|
290
|
+
// Anthropic Helpers
|
|
291
|
+
// =============================================================================
|
|
292
|
+
function hasAnthropicFileBlocks(messages) {
|
|
293
|
+
for (const msg of messages) {
|
|
294
|
+
const blocks = getMessageBlocks(msg);
|
|
295
|
+
for (const block of blocks) {
|
|
296
|
+
if (block.type === 'file' && block.file_id) {
|
|
297
|
+
return true;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return false;
|
|
302
|
+
}
|
|
303
|
+
function mergeAnthropicBetaHeader(existing, entries) {
|
|
304
|
+
const set = new Set();
|
|
305
|
+
if (existing) {
|
|
306
|
+
for (const e of existing.split(',')) {
|
|
307
|
+
const trimmed = e.trim();
|
|
308
|
+
if (trimmed)
|
|
309
|
+
set.add(trimmed);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
for (const e of entries) {
|
|
313
|
+
if (e)
|
|
314
|
+
set.add(e);
|
|
315
|
+
}
|
|
316
|
+
return set.size > 0 ? Array.from(set).join(',') : undefined;
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Normalize Anthropic response content to internal format.
|
|
320
|
+
*/
|
|
321
|
+
function normalizeAnthropicContent(content, reasoningTransport) {
|
|
322
|
+
if (!Array.isArray(content))
|
|
323
|
+
return [];
|
|
324
|
+
const blocks = [];
|
|
325
|
+
for (const block of content) {
|
|
326
|
+
const normalized = normalizeAnthropicContentBlock(block, reasoningTransport);
|
|
327
|
+
if (normalized)
|
|
328
|
+
blocks.push(normalized);
|
|
329
|
+
}
|
|
330
|
+
return blocks;
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Normalize a single Anthropic content block.
|
|
334
|
+
* Handles thinking blocks with signature preservation.
|
|
335
|
+
*/
|
|
336
|
+
function normalizeAnthropicContentBlock(block, reasoningTransport) {
|
|
337
|
+
if (!block || typeof block !== 'object')
|
|
338
|
+
return null;
|
|
339
|
+
// Handle thinking blocks - preserve signature for conversation continuity
|
|
340
|
+
if (block.type === 'thinking') {
|
|
341
|
+
if (reasoningTransport === 'text') {
|
|
342
|
+
return { type: 'text', text: `<think>${block.thinking ?? ''}</think>` };
|
|
343
|
+
}
|
|
344
|
+
const result = { type: 'reasoning', reasoning: block.thinking ?? '' };
|
|
345
|
+
// Preserve signature for multi-turn conversations (critical for Claude 4+)
|
|
346
|
+
if (block.signature) {
|
|
347
|
+
result.meta = { signature: block.signature };
|
|
348
|
+
}
|
|
349
|
+
return result;
|
|
350
|
+
}
|
|
351
|
+
if (block.type === 'text') {
|
|
352
|
+
return { type: 'text', text: block.text ?? '' };
|
|
353
|
+
}
|
|
354
|
+
if (block.type === 'image' && block.source?.type === 'base64') {
|
|
355
|
+
return {
|
|
356
|
+
type: 'image',
|
|
357
|
+
base64: block.source.data,
|
|
358
|
+
mime_type: block.source.media_type,
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
if (block.type === 'document' && block.source?.type === 'file') {
|
|
362
|
+
return {
|
|
363
|
+
type: 'file',
|
|
364
|
+
file_id: block.source.file_id,
|
|
365
|
+
mime_type: block.source.media_type,
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
if (block.type === 'tool_use') {
|
|
369
|
+
return {
|
|
370
|
+
type: 'tool_use',
|
|
371
|
+
id: block.id,
|
|
372
|
+
name: block.name,
|
|
373
|
+
input: block.input ?? {},
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
if (block.type === 'tool_result') {
|
|
377
|
+
return {
|
|
378
|
+
type: 'tool_result',
|
|
379
|
+
tool_use_id: block.tool_use_id,
|
|
380
|
+
content: block.content,
|
|
381
|
+
is_error: block.is_error,
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
return null;
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Normalize Anthropic streaming delta.
|
|
388
|
+
*/
|
|
389
|
+
function normalizeAnthropicDelta(delta) {
|
|
390
|
+
if (!delta) {
|
|
391
|
+
return { type: 'text_delta', text: '' };
|
|
392
|
+
}
|
|
393
|
+
if (delta.type === 'thinking_delta') {
|
|
394
|
+
return { type: 'reasoning_delta', text: delta.thinking ?? '' };
|
|
395
|
+
}
|
|
396
|
+
if (delta.type === 'input_json_delta') {
|
|
397
|
+
return { type: 'input_json_delta', partial_json: delta.partial_json ?? '' };
|
|
398
|
+
}
|
|
399
|
+
return { type: 'text_delta', text: delta.text ?? '' };
|
|
400
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SandboxFactory = void 0;
|
|
4
|
+
const sandbox_1 = require("./sandbox");
|
|
5
|
+
const e2b_sandbox_1 = require("./e2b/e2b-sandbox");
|
|
6
|
+
class SandboxFactory {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.factories = new Map();
|
|
9
|
+
this.factories.set('local', (config) => new sandbox_1.LocalSandbox(config));
|
|
10
|
+
this.factories.set('e2b', (config) => new e2b_sandbox_1.E2BSandbox(config));
|
|
11
|
+
}
|
|
12
|
+
register(kind, factory) {
|
|
13
|
+
this.factories.set(kind, factory);
|
|
14
|
+
}
|
|
15
|
+
create(config) {
|
|
16
|
+
const factory = this.factories.get(config.kind);
|
|
17
|
+
if (!factory) {
|
|
18
|
+
throw new Error(`Sandbox factory not registered: ${config.kind}`);
|
|
19
|
+
}
|
|
20
|
+
return factory(config);
|
|
21
|
+
}
|
|
22
|
+
async createAsync(config) {
|
|
23
|
+
const sandbox = this.create(config);
|
|
24
|
+
if (config.kind === 'e2b' && sandbox instanceof e2b_sandbox_1.E2BSandbox) {
|
|
25
|
+
await sandbox.init();
|
|
26
|
+
}
|
|
27
|
+
return sandbox;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
exports.SandboxFactory = SandboxFactory;
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LocalSandbox = void 0;
|
|
4
|
+
// 危险命令模式 - 防止执行破坏性操作
|
|
5
|
+
const DANGEROUS_PATTERNS = [
|
|
6
|
+
/rm\s+-rf\s+\/($|\s)/, // rm -rf /
|
|
7
|
+
/sudo\s+/, // sudo 提权
|
|
8
|
+
/shutdown/, // 系统关机
|
|
9
|
+
/reboot/, // 系统重启
|
|
10
|
+
/mkfs\./, // 格式化文件系统
|
|
11
|
+
/dd\s+.*of=/, // dd 写入设备
|
|
12
|
+
/:\(\)\{\s*:\|\:&\s*\};:/, // fork bomb
|
|
13
|
+
/chmod\s+777\s+\//, // 修改根目录权限
|
|
14
|
+
/curl\s+.*\|\s*(bash|sh)/, // 管道执行远程脚本
|
|
15
|
+
/wget\s+.*\|\s*(bash|sh)/, // wget 执行远程脚本
|
|
16
|
+
/>\s*\/dev\/sda/, // 直接写入硬盘
|
|
17
|
+
/mkswap/, // 创建交换分区
|
|
18
|
+
/swapon/, // 启用交换分区
|
|
19
|
+
];
|
|
20
|
+
class LocalSandbox {
|
|
21
|
+
constructor(opts = {}) {
|
|
22
|
+
this.kind = 'local';
|
|
23
|
+
this.watchers = new Map();
|
|
24
|
+
const path = require('path');
|
|
25
|
+
this.workDir = path.resolve(opts.workDir || opts.baseDir || opts.pwd || process.cwd());
|
|
26
|
+
this.enforceBoundary = opts.enforceBoundary !== false;
|
|
27
|
+
this.allowPaths = (opts.allowPaths || []).map((p) => path.resolve(p));
|
|
28
|
+
this.watchEnabled = opts.watchFiles !== false; // default true
|
|
29
|
+
this.fs = new LocalFS(this.workDir, {
|
|
30
|
+
enforceBoundary: this.enforceBoundary,
|
|
31
|
+
allowPaths: this.allowPaths,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
async exec(cmd, opts) {
|
|
35
|
+
// 安全检查:阻止危险命令
|
|
36
|
+
for (const pattern of DANGEROUS_PATTERNS) {
|
|
37
|
+
if (pattern.test(cmd)) {
|
|
38
|
+
const error = `Dangerous command blocked for security: ${cmd.slice(0, 100)}`;
|
|
39
|
+
return {
|
|
40
|
+
code: 1,
|
|
41
|
+
stdout: '',
|
|
42
|
+
stderr: error,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const { exec } = require('child_process');
|
|
47
|
+
const util = require('util');
|
|
48
|
+
const execPromise = util.promisify(exec);
|
|
49
|
+
const timeout = opts?.timeoutMs || 120000;
|
|
50
|
+
try {
|
|
51
|
+
const { stdout, stderr } = await execPromise(cmd, {
|
|
52
|
+
cwd: this.workDir,
|
|
53
|
+
timeout,
|
|
54
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
55
|
+
});
|
|
56
|
+
return { code: 0, stdout: stdout || '', stderr: stderr || '' };
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
return {
|
|
60
|
+
code: error.code || 1,
|
|
61
|
+
stdout: error.stdout || '',
|
|
62
|
+
stderr: error.stderr || error.message || '',
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
static local(opts) {
|
|
67
|
+
return new LocalSandbox(opts);
|
|
68
|
+
}
|
|
69
|
+
async watchFiles(paths, listener) {
|
|
70
|
+
if (!this.watchEnabled) {
|
|
71
|
+
return `watch-disabled-${Date.now()}`;
|
|
72
|
+
}
|
|
73
|
+
const id = `watch-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
74
|
+
const fs = require('fs');
|
|
75
|
+
const watchers = [];
|
|
76
|
+
for (const path of paths) {
|
|
77
|
+
const resolved = this.fs.resolve(path);
|
|
78
|
+
if (!this.fs.isInside(resolved))
|
|
79
|
+
continue;
|
|
80
|
+
const watcher = fs.watch(resolved, async () => {
|
|
81
|
+
try {
|
|
82
|
+
const stat = await this.fs.stat(resolved);
|
|
83
|
+
listener({ path: resolved, mtimeMs: stat.mtimeMs });
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
listener({ path: resolved, mtimeMs: Date.now() });
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
watchers.push(watcher);
|
|
90
|
+
}
|
|
91
|
+
this.watchers.set(id, {
|
|
92
|
+
paths,
|
|
93
|
+
close: () => watchers.forEach((w) => w.close()),
|
|
94
|
+
});
|
|
95
|
+
return id;
|
|
96
|
+
}
|
|
97
|
+
unwatchFiles(id) {
|
|
98
|
+
const entry = this.watchers.get(id);
|
|
99
|
+
if (entry) {
|
|
100
|
+
entry.close();
|
|
101
|
+
this.watchers.delete(id);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
async dispose() {
|
|
105
|
+
for (const entry of this.watchers.values()) {
|
|
106
|
+
entry.close();
|
|
107
|
+
}
|
|
108
|
+
this.watchers.clear();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
exports.LocalSandbox = LocalSandbox;
|
|
112
|
+
class LocalFS {
|
|
113
|
+
constructor(workDir, options) {
|
|
114
|
+
this.workDir = workDir;
|
|
115
|
+
this.options = options;
|
|
116
|
+
}
|
|
117
|
+
resolve(p) {
|
|
118
|
+
const path = require('path');
|
|
119
|
+
if (path.isAbsolute(p))
|
|
120
|
+
return p;
|
|
121
|
+
return path.resolve(this.workDir, p);
|
|
122
|
+
}
|
|
123
|
+
isInside(p) {
|
|
124
|
+
const path = require('path');
|
|
125
|
+
const resolved = path.resolve(this.resolve(p)); // resolve 去除 ..
|
|
126
|
+
// 1. 检查是否在 workDir 内
|
|
127
|
+
const relativeToWork = path.relative(this.workDir, resolved);
|
|
128
|
+
if (!relativeToWork.startsWith('..') && !path.isAbsolute(relativeToWork)) {
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
// 2. 如果不强制边界检查,允许所有路径
|
|
132
|
+
if (!this.options.enforceBoundary)
|
|
133
|
+
return true;
|
|
134
|
+
// 3. 检查白名单(先 resolve 防止绕过)
|
|
135
|
+
return this.options.allowPaths.some((allowed) => {
|
|
136
|
+
const resolvedAllowed = path.resolve(allowed); // 先 resolve
|
|
137
|
+
const relative = path.relative(resolvedAllowed, resolved);
|
|
138
|
+
return !relative.startsWith('..') && !path.isAbsolute(relative);
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
async read(p) {
|
|
142
|
+
const fs = require('fs').promises;
|
|
143
|
+
const resolved = this.resolve(p);
|
|
144
|
+
if (!this.isInside(resolved)) {
|
|
145
|
+
throw new Error(`Path outside sandbox: ${p}`);
|
|
146
|
+
}
|
|
147
|
+
return await fs.readFile(resolved, 'utf-8');
|
|
148
|
+
}
|
|
149
|
+
async write(p, content) {
|
|
150
|
+
const fs = require('fs').promises;
|
|
151
|
+
const path = require('path');
|
|
152
|
+
const resolved = this.resolve(p);
|
|
153
|
+
if (!this.isInside(resolved)) {
|
|
154
|
+
throw new Error(`Path outside sandbox: ${p}`);
|
|
155
|
+
}
|
|
156
|
+
const dir = path.dirname(resolved);
|
|
157
|
+
await fs.mkdir(dir, { recursive: true });
|
|
158
|
+
await fs.writeFile(resolved, content, 'utf-8');
|
|
159
|
+
}
|
|
160
|
+
temp(name) {
|
|
161
|
+
const path = require('path');
|
|
162
|
+
const tempName = name || `temp-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
163
|
+
return path.relative(this.workDir, path.join(this.workDir, '.temp', tempName));
|
|
164
|
+
}
|
|
165
|
+
async stat(p) {
|
|
166
|
+
const fs = require('fs').promises;
|
|
167
|
+
const resolved = this.resolve(p);
|
|
168
|
+
if (!this.isInside(resolved)) {
|
|
169
|
+
throw new Error(`Path outside sandbox: ${p}`);
|
|
170
|
+
}
|
|
171
|
+
const stat = await fs.stat(resolved);
|
|
172
|
+
return { mtimeMs: stat.mtimeMs };
|
|
173
|
+
}
|
|
174
|
+
async glob(pattern, opts) {
|
|
175
|
+
const path = require('path');
|
|
176
|
+
const cwd = opts?.cwd ? this.resolve(opts.cwd) : this.workDir;
|
|
177
|
+
let matches;
|
|
178
|
+
try {
|
|
179
|
+
const fg = require('fast-glob');
|
|
180
|
+
matches = await fg(pattern, {
|
|
181
|
+
cwd,
|
|
182
|
+
dot: opts?.dot ?? false,
|
|
183
|
+
absolute: true,
|
|
184
|
+
ignore: opts?.ignore,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
matches = await this.manualGlob(pattern, { cwd, dot: opts?.dot ?? false });
|
|
189
|
+
}
|
|
190
|
+
const filtered = matches.filter((entry) => this.isInside(entry));
|
|
191
|
+
if (opts?.absolute) {
|
|
192
|
+
return filtered;
|
|
193
|
+
}
|
|
194
|
+
return filtered.map((entry) => path.relative(this.workDir, entry));
|
|
195
|
+
}
|
|
196
|
+
async manualGlob(pattern, opts) {
|
|
197
|
+
const fs = require('fs').promises;
|
|
198
|
+
const path = require('path');
|
|
199
|
+
const normalizedPattern = pattern.split(path.sep).join('/');
|
|
200
|
+
const results = [];
|
|
201
|
+
const walk = async (dir) => {
|
|
202
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
203
|
+
for (const entry of entries) {
|
|
204
|
+
if (!opts.dot && entry.name.startsWith('.'))
|
|
205
|
+
continue;
|
|
206
|
+
const full = path.join(dir, entry.name);
|
|
207
|
+
const rel = path.relative(opts.cwd, full).split(path.sep).join('/');
|
|
208
|
+
if (matchesGlob(normalizedPattern, rel)) {
|
|
209
|
+
results.push(full);
|
|
210
|
+
}
|
|
211
|
+
if (entry.isDirectory()) {
|
|
212
|
+
await walk(full);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
await walk(opts.cwd);
|
|
217
|
+
return results;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
function matchesGlob(pattern, target) {
|
|
221
|
+
const pSegs = pattern.split('/');
|
|
222
|
+
const tSegs = target.split('/');
|
|
223
|
+
return matchSegments(pSegs, tSegs);
|
|
224
|
+
}
|
|
225
|
+
function matchSegments(pattern, target) {
|
|
226
|
+
if (pattern.length === 0)
|
|
227
|
+
return target.length === 0;
|
|
228
|
+
const [head, ...rest] = pattern;
|
|
229
|
+
if (head === '**') {
|
|
230
|
+
return (matchSegments(rest, target) ||
|
|
231
|
+
(target.length > 0 && matchSegments(pattern, target.slice(1))));
|
|
232
|
+
}
|
|
233
|
+
if (target.length === 0)
|
|
234
|
+
return false;
|
|
235
|
+
if (!matchSegment(head, target[0]))
|
|
236
|
+
return false;
|
|
237
|
+
return matchSegments(rest, target.slice(1));
|
|
238
|
+
}
|
|
239
|
+
function matchSegment(pattern, target) {
|
|
240
|
+
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, '\\$&');
|
|
241
|
+
const regex = escaped.replace(/\*/g, '.*').replace(/\?/g, '.');
|
|
242
|
+
return new RegExp(`^${regex}$`).test(target);
|
|
243
|
+
}
|