@swifty.js/swifty 0.0.21 → 0.0.22

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 (34) hide show
  1. package/dist/{agent-ZCMBUWLZ.js → agent-5T7KNC7V.js} +1 -1
  2. package/dist/{anthropic-4ZVG7AMA.js → anthropic-P2R4GW6F.js} +1 -1
  3. package/dist/{chunk-Y5WGBAGP.js → chunk-HJIGA37J.js} +1 -1
  4. package/dist/{chunk-DDBH5AEN.js → chunk-L73IHJF4.js} +53 -53
  5. package/dist/{chunk-S6ZADC7C.js → chunk-NEAR6YPZ.js} +1 -1
  6. package/dist/{chunk-MUPVOATV.js → chunk-NLNH3IRT.js} +1 -1
  7. package/dist/{chunk-6E6UA7MT.js → chunk-YQQVB6VB.js} +9 -8
  8. package/dist/lib/agent-2AGRYN3R.js +9 -0
  9. package/dist/lib/anthropic-GFWP3ORB.js +22 -0
  10. package/dist/lib/bwrap-QRGPUP4J.js +6 -0
  11. package/dist/lib/checker-IIXJXQCB.js +19 -0
  12. package/dist/lib/chunk-6GOWRPYS.js +339 -0
  13. package/dist/lib/chunk-7URDLWQN.js +426 -0
  14. package/dist/lib/chunk-C7IHCJZ3.js +849 -0
  15. package/dist/lib/chunk-EJLGB2EJ.js +377 -0
  16. package/dist/lib/chunk-GHF2PSEW.js +8 -0
  17. package/dist/lib/chunk-GNBXECZN.js +243 -0
  18. package/dist/lib/chunk-HK2Z6WP4.js +223 -0
  19. package/dist/lib/chunk-LUEP4JMF.js +549 -0
  20. package/dist/lib/chunk-ORSYNBMM.js +38 -0
  21. package/dist/lib/chunk-RR2CZ6CY.js +598 -0
  22. package/dist/lib/chunk-UHVO63Y7.js +1246 -0
  23. package/dist/lib/chunk-UMFNTXKA.js +88 -0
  24. package/dist/lib/chunk-VUD72RTY.js +39 -0
  25. package/dist/lib/glob.wasm +0 -0
  26. package/dist/lib/index.d.ts +5350 -0
  27. package/dist/lib/index.js +12002 -0
  28. package/dist/lib/openai-HXRMPNB7.js +15 -0
  29. package/dist/lib/seatbelt-FT5IY73W.js +6 -0
  30. package/dist/lib/tool-filter-VF7TZRE5.js +19 -0
  31. package/dist/main.js +225 -225
  32. package/dist/{openai-HXD52MZB.js → openai-TVUUHRG7.js} +1 -1
  33. package/dist/{server-VMHWEO2Y.js → server-ZTLHJWEQ.js} +14 -14
  34. package/package.json +14 -2
@@ -0,0 +1,549 @@
1
+ import {
2
+ computeCompactThreshold
3
+ } from "./chunk-C7IHCJZ3.js";
4
+ import {
5
+ getContextWindow,
6
+ getMaxOutputTokens,
7
+ resolveAPIKey
8
+ } from "./chunk-6GOWRPYS.js";
9
+ import {
10
+ AuthenticationError,
11
+ ContextTooLongError,
12
+ LLMError,
13
+ NetworkError,
14
+ RateLimitError,
15
+ ensureToolPairing
16
+ } from "./chunk-UMFNTXKA.js";
17
+ import {
18
+ MCP_TOOL_PREFIX,
19
+ isMcpToolLike
20
+ } from "./chunk-GNBXECZN.js";
21
+ import {
22
+ DANGEROUSLY_JSON,
23
+ asErrorString,
24
+ asRecord,
25
+ asString,
26
+ contentToText,
27
+ createChildLogger,
28
+ isRecord
29
+ } from "./chunk-EJLGB2EJ.js";
30
+
31
+ // src/llm/anthropic.ts
32
+ import Anthropic from "@anthropic-ai/sdk";
33
+ import { safeParseAsync, z } from "zod";
34
+
35
+ // src/mcp/strategy.ts
36
+ var DEFAULT_EAGER_THRESHOLD_PERCENT = 10;
37
+ var CHARS_PER_TOKEN = 2.5;
38
+ var NATIVE_TOOL_USE_BETA = "advanced-tool-use-2025-11-20";
39
+ var OFFICIAL_HOSTS = /* @__PURE__ */ new Set(["api.anthropic.com"]);
40
+ var ENV_OVERRIDE = "SWIFTY_MCP_LOADING";
41
+ function isOfficialAnthropicEndpoint(baseUrl) {
42
+ if (!baseUrl) {
43
+ return true;
44
+ }
45
+ try {
46
+ return OFFICIAL_HOSTS.has(new URL(baseUrl).hostname.toLowerCase());
47
+ } catch {
48
+ return false;
49
+ }
50
+ }
51
+ function estimateSchemaTokens(schemaChars) {
52
+ return Math.floor(schemaChars / CHARS_PER_TOKEN);
53
+ }
54
+ function decideMode(baseUrl, contextWindow, mcpSchemaChars, thresholdPercent = DEFAULT_EAGER_THRESHOLD_PERCENT) {
55
+ const override = (process.env[ENV_OVERRIDE] ?? "").trim().toLowerCase();
56
+ if (override === "eager" || override === "native" || override === "dispatch") {
57
+ return override;
58
+ }
59
+ if (mcpSchemaChars <= 0) {
60
+ return "eager";
61
+ }
62
+ const budget = contextWindow * thresholdPercent / 100;
63
+ if (estimateSchemaTokens(mcpSchemaChars) < budget) {
64
+ return "eager";
65
+ }
66
+ return isOfficialAnthropicEndpoint(baseUrl) ? "native" : "dispatch";
67
+ }
68
+ function measureSchemaChars(registry) {
69
+ let total = 0;
70
+ for (const tool of registry.listTools()) {
71
+ if (!tool.name.startsWith(MCP_TOOL_PREFIX)) {
72
+ continue;
73
+ }
74
+ try {
75
+ total += JSON.stringify(tool.schema()).length;
76
+ } catch {
77
+ total += tool.name.length + (tool.description?.length ?? 0);
78
+ }
79
+ }
80
+ return total;
81
+ }
82
+ function applyMode(registry, mode) {
83
+ registry.mcpLoadingMode = mode;
84
+ const eager = mode === "eager";
85
+ for (const tool of registry.listTools()) {
86
+ if (isMcpToolLike(tool) && typeof tool.setDeferLoading === "function") {
87
+ tool.setDeferLoading(!eager);
88
+ }
89
+ }
90
+ registry.exposeToolSearch = !eager;
91
+ registry.exposeMcpCall = mode === "dispatch";
92
+ }
93
+ function decideAndApply(registry, baseUrl, contextWindow) {
94
+ const mode = decideMode(baseUrl, contextWindow, measureSchemaChars(registry));
95
+ applyMode(registry, mode);
96
+ return mode;
97
+ }
98
+
99
+ // src/llm/anthropic.ts
100
+ function markToolsForCache(tools) {
101
+ for (let i = tools.length - 1; i >= 0; i--) {
102
+ const t = tools[i];
103
+ if (t.defer_loading === true) {
104
+ continue;
105
+ }
106
+ t.cache_control = { type: "ephemeral" };
107
+ return;
108
+ }
109
+ }
110
+ function needsToolSearchBeta(toolSchemas) {
111
+ return toolSchemas.some((s) => s.defer_loading);
112
+ }
113
+ var log = createChildLogger({ module: "llm" });
114
+ var MODEL_FETCH_TIMEOUT_MS = 3e3;
115
+ var ModelContextWindowResSchema = z.object({
116
+ max_input_tokens: z.coerce.number()
117
+ });
118
+ async function fetchModelContextWindow(config) {
119
+ if (config.protocol !== "anthropic") {
120
+ return 0;
121
+ }
122
+ const apiKey = resolveAPIKey(config);
123
+ const base = config.base_url.replace(/\/+$/, "");
124
+ const url = `${base}/v1/models/${encodeURIComponent(config.model)}`;
125
+ const controller = new AbortController();
126
+ const timer = setTimeout(() => {
127
+ controller.abort();
128
+ }, MODEL_FETCH_TIMEOUT_MS);
129
+ try {
130
+ const res = await fetch(url, {
131
+ method: "GET",
132
+ headers: {
133
+ "anthropic-version": "2023-06-01",
134
+ ...apiKey ? { "x-api-key": apiKey } : {}
135
+ },
136
+ signal: controller.signal
137
+ });
138
+ if (!res.ok) {
139
+ return 0;
140
+ }
141
+ const body = await res.json();
142
+ const { success, error, data } = await safeParseAsync(ModelContextWindowResSchema, body);
143
+ if (!success) {
144
+ log.warn({ message: error.message }, "model context window schema validation failed");
145
+ return 0;
146
+ }
147
+ const maxInputTokens = data.max_input_tokens;
148
+ return Math.max(maxInputTokens, 0);
149
+ } catch (err) {
150
+ log.error({ err }, "failed to fetch model context window");
151
+ return 0;
152
+ } finally {
153
+ clearTimeout(timer);
154
+ }
155
+ }
156
+ function supportsAdaptiveThinking() {
157
+ return true;
158
+ }
159
+ function userBlocksFor(content) {
160
+ if (typeof content === "string") {
161
+ return [{ type: "text", text: content }];
162
+ }
163
+ return content;
164
+ }
165
+ function buildAnthropicMessages(messages) {
166
+ const result = [];
167
+ for (const m of messages) {
168
+ if (m.role === "assistant") {
169
+ const blocks = [];
170
+ if (m.thinkingBlocks) {
171
+ for (const tb of m.thinkingBlocks) {
172
+ blocks.push({
173
+ type: "thinking",
174
+ thinking: tb.thinking,
175
+ signature: tb.signature
176
+ });
177
+ }
178
+ }
179
+ const assistantText = typeof m.content === "string" ? m.content : contentToText(m.content);
180
+ if (assistantText) {
181
+ blocks.push({
182
+ type: "text",
183
+ text: assistantText
184
+ });
185
+ }
186
+ if (m.toolUses) {
187
+ for (const tu of m.toolUses) {
188
+ blocks.push({
189
+ type: "tool_use",
190
+ // tool use **request**
191
+ id: tu.toolUseId,
192
+ name: tu.toolName,
193
+ input: tu.arguments
194
+ });
195
+ }
196
+ }
197
+ if (blocks.length === 0) {
198
+ blocks.push({ type: "text", text: "" });
199
+ }
200
+ result.push({ role: "assistant", content: blocks });
201
+ } else if (m.toolResults && m.toolResults.length > 0) {
202
+ const blocks = [];
203
+ for (const tr of m.toolResults) {
204
+ blocks.push({
205
+ type: "tool_result",
206
+ // tool result
207
+ tool_use_id: tr.toolUseId,
208
+ is_error: tr.isError,
209
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
210
+ content: tr.content
211
+ });
212
+ }
213
+ result.push({ role: "user", content: blocks });
214
+ } else {
215
+ if (result.length === 0) {
216
+ result.push({
217
+ role: "user",
218
+ content: userBlocksFor(m.content)
219
+ });
220
+ continue;
221
+ }
222
+ let canMerge = false;
223
+ const prev = result[result.length - 1];
224
+ let content = prev.content;
225
+ if (prev.role === "user" && (typeof content === "string" || Array.isArray(content) && content.length > 0 && // content[0].type !== "tool_result"
226
+ (content[0].type === "text" || content[0].type === "image"))) {
227
+ canMerge = true;
228
+ }
229
+ if (canMerge) {
230
+ if (typeof content === "string") {
231
+ content = prev.content = content.trim().length > 0 ? [
232
+ {
233
+ type: "text",
234
+ text: content
235
+ }
236
+ ] : [];
237
+ }
238
+ content.push(...userBlocksFor(m.content));
239
+ } else {
240
+ result.push({
241
+ role: "user",
242
+ content: userBlocksFor(m.content)
243
+ });
244
+ }
245
+ }
246
+ }
247
+ return result;
248
+ }
249
+ var AnthropicClient = class {
250
+ client;
251
+ model;
252
+ /**
253
+ * Whether supports/enable thinking, default false
254
+ */
255
+ thinking;
256
+ systemPrompt;
257
+ maxOutputTokens;
258
+ /** Currently not used */
259
+ contextWindow;
260
+ constructor(config, systemPrompt) {
261
+ const apiKey = resolveAPIKey(config);
262
+ if (!apiKey) {
263
+ throw new AuthenticationError(
264
+ "Anthropic API key not found, set ANTHROPIC_API_KEY in .swifty/config.y(a)ml, or via ANTHROPIC_API_KEY env variable."
265
+ );
266
+ }
267
+ this.client = new Anthropic({
268
+ apiKey,
269
+ baseURL: config.base_url
270
+ });
271
+ this.model = config.model;
272
+ this.thinking = config.thinking ?? true;
273
+ this.systemPrompt = systemPrompt;
274
+ this.maxOutputTokens = getMaxOutputTokens(config);
275
+ this.contextWindow = getContextWindow(config);
276
+ }
277
+ setSystemPrompt(prompt) {
278
+ this.systemPrompt = prompt;
279
+ }
280
+ setMaxOutputTokens(maxTokens) {
281
+ this.maxOutputTokens = maxTokens;
282
+ }
283
+ async *stream(conversation, toolSchemas, abortSignal) {
284
+ const messages = buildAnthropicMessages(ensureToolPairing(conversation.getMessages()));
285
+ const sendToolSearchBeta = needsToolSearchBeta(toolSchemas);
286
+ const antToolSchemas = toolSchemas.map((s) => {
287
+ const inputSchema = s.input_schema;
288
+ const tool = {
289
+ name: s.name,
290
+ description: s.description,
291
+ input_schema: {
292
+ type: "object",
293
+ properties: inputSchema.properties,
294
+ required: inputSchema.required ?? []
295
+ }
296
+ };
297
+ if (s.defer_loading === true) {
298
+ tool.defer_loading = true;
299
+ }
300
+ return tool;
301
+ });
302
+ markToolsForCache(antToolSchemas);
303
+ markLastUserTailForCache(messages);
304
+ const params = {
305
+ model: this.model,
306
+ max_tokens: this.maxOutputTokens,
307
+ stream: true,
308
+ system: [
309
+ {
310
+ type: "text",
311
+ text: this.systemPrompt,
312
+ cache_control: {
313
+ type: "ephemeral"
314
+ // Prompt cache
315
+ }
316
+ }
317
+ ],
318
+ messages,
319
+ ...antToolSchemas.length > 0 ? { tools: antToolSchemas } : {}
320
+ };
321
+ if (this.thinking) {
322
+ if (supportsAdaptiveThinking()) {
323
+ params.thinking = {
324
+ type: "enabled",
325
+ budget_tokens: computeCompactThreshold(this.contextWindow, this.maxOutputTokens)
326
+ };
327
+ }
328
+ } else {
329
+ params.thinking = {
330
+ type: "enabled",
331
+ budget_tokens: computeCompactThreshold(this.contextWindow, this.maxOutputTokens)
332
+ };
333
+ }
334
+ let inputTokens = 0;
335
+ let outputTokens = 0;
336
+ let cacheReadInputTokens = 0;
337
+ let cacheCreationInputTokens = 0;
338
+ let stopReason = "end_turn";
339
+ let thinkingAccumulate = "";
340
+ let thinkingSignature = "";
341
+ let inThinking = false;
342
+ try {
343
+ const response = this.client.messages.stream(params, {
344
+ ...abortSignal ? { signal: abortSignal } : {},
345
+ // If any tool uses defer_loading this beta header is required, otherwise the server does not recognize the field.
346
+ // Only the official endpoint reaches this code path (see mcp/strategy).
347
+ ...sendToolSearchBeta ? { headers: { "anthropic-beta": NATIVE_TOOL_USE_BETA } } : {}
348
+ });
349
+ let currentToolName = "";
350
+ let currentToolId = "";
351
+ let jsonAccumulate = "";
352
+ for await (const event of response) {
353
+ switch (event.type) {
354
+ case "content_block_start": {
355
+ const block = event.content_block;
356
+ if (block.type === "thinking") {
357
+ inThinking = true;
358
+ thinkingAccumulate = "";
359
+ thinkingSignature = "";
360
+ } else if (block.type === "tool_use") {
361
+ currentToolId = block.id;
362
+ currentToolName = block.name;
363
+ jsonAccumulate = "";
364
+ yield {
365
+ type: "tool_call_start",
366
+ toolName: currentToolName,
367
+ toolId: currentToolId
368
+ };
369
+ }
370
+ break;
371
+ }
372
+ // end case "content_block_start"
373
+ case "content_block_delta": {
374
+ const delta = event.delta;
375
+ if (delta.type === "thinking_delta") {
376
+ thinkingAccumulate += delta.thinking;
377
+ yield {
378
+ type: "thinking_delta",
379
+ text: delta.thinking
380
+ };
381
+ } else if (delta.type === "signature_delta") {
382
+ thinkingSignature = delta.signature;
383
+ } else if (delta.type === "text_delta") {
384
+ yield {
385
+ type: "text_delta",
386
+ text: delta.text
387
+ };
388
+ } else if (delta.type === "input_json_delta") {
389
+ jsonAccumulate += delta.partial_json;
390
+ yield {
391
+ type: "tool_call_delta",
392
+ text: delta.partial_json
393
+ };
394
+ }
395
+ break;
396
+ }
397
+ // end case "content_block_delta"
398
+ case "content_block_stop": {
399
+ if (inThinking) {
400
+ yield {
401
+ type: "thinking_complete",
402
+ thinking: thinkingAccumulate,
403
+ signature: thinkingSignature
404
+ };
405
+ inThinking = false;
406
+ }
407
+ if (currentToolName) {
408
+ let args = {};
409
+ if (jsonAccumulate) {
410
+ try {
411
+ const parsed = JSON.parse(jsonAccumulate);
412
+ args = isRecord(parsed) ? asRecord(parsed) : { [DANGEROUSLY_JSON]: jsonAccumulate };
413
+ } catch (err) {
414
+ log.error({ err }, "llm operation failed");
415
+ args = {
416
+ [DANGEROUSLY_JSON]: jsonAccumulate
417
+ };
418
+ }
419
+ }
420
+ yield {
421
+ type: "tool_call_complete",
422
+ toolId: currentToolId,
423
+ toolName: currentToolName,
424
+ arguments: args
425
+ };
426
+ currentToolName = "";
427
+ currentToolId = "";
428
+ jsonAccumulate = "";
429
+ }
430
+ break;
431
+ }
432
+ // end case "content_block_stop"
433
+ case "message_delta": {
434
+ if (event.delta.stop_reason) {
435
+ stopReason = event.delta.stop_reason;
436
+ }
437
+ if (event.usage.output_tokens) {
438
+ outputTokens = event.usage.output_tokens;
439
+ if (event.usage.input_tokens) {
440
+ inputTokens = event.usage.input_tokens;
441
+ }
442
+ if (event.usage.cache_read_input_tokens) {
443
+ cacheReadInputTokens = event.usage.cache_read_input_tokens;
444
+ }
445
+ if (event.usage.cache_creation_input_tokens) {
446
+ cacheCreationInputTokens = event.usage.cache_creation_input_tokens;
447
+ }
448
+ }
449
+ break;
450
+ }
451
+ // end case "message_delta"
452
+ case "message_start": {
453
+ inputTokens = event.message.usage.input_tokens;
454
+ outputTokens = event.message.usage.output_tokens;
455
+ cacheReadInputTokens = event.message.usage.cache_read_input_tokens ?? 0;
456
+ cacheCreationInputTokens = event.message.usage.cache_creation_input_tokens ?? 0;
457
+ break;
458
+ }
459
+ }
460
+ }
461
+ yield {
462
+ type: "stream_end",
463
+ stopReason,
464
+ usage: {
465
+ inputTokens,
466
+ outputTokens,
467
+ cacheReadInputTokens,
468
+ cacheCreationInputTokens
469
+ }
470
+ };
471
+ } catch (err) {
472
+ log.error({ err }, "llm operation failed");
473
+ throw classifyAnthropicError(err);
474
+ }
475
+ }
476
+ };
477
+ function markLastUserTailForCache(messages) {
478
+ for (let i = messages.length - 1; i >= 0; i--) {
479
+ if (messages[i].role !== "user") {
480
+ continue;
481
+ }
482
+ let content = messages[i].content;
483
+ if (typeof content === "string" && content.length === 0 || Array.isArray(content) && content.length === 0) {
484
+ return;
485
+ }
486
+ if (typeof content === "string") {
487
+ content = messages[i].content = [
488
+ {
489
+ type: "text",
490
+ text: content
491
+ }
492
+ ];
493
+ }
494
+ let last = content[content.length - 1];
495
+ for (let j = content.length - 1; j >= 0; j--) {
496
+ if (content[j].type !== "image") {
497
+ last = content[j];
498
+ break;
499
+ }
500
+ }
501
+ Reflect.set(last, "cache_control", {
502
+ type: "ephemeral"
503
+ });
504
+ }
505
+ }
506
+ function classifyAnthropicError(err) {
507
+ if (err instanceof Anthropic.APIError) {
508
+ if (err.status === 413 /* PromptTooLong */ || /prompts?\s+too\s+long/i.test(err.message)) {
509
+ return new ContextTooLongError(`Prompt too long: ${err.message}`);
510
+ }
511
+ if (err.status === 401 /* InvalidAPIKey */) {
512
+ return new AuthenticationError(`Invalid API key: ${err.message}`);
513
+ }
514
+ if (err.status === 429 /* RateLimitError */) {
515
+ const retryAfter = asRecord(err.headers)["retry-after"];
516
+ let message = "Rate Limited";
517
+ if (retryAfter) {
518
+ const s = Number.parseInt(asString(retryAfter));
519
+ if (Number.isNaN(s)) {
520
+ message += ", please wait.";
521
+ }
522
+ message += `, retry after ${asString(s)}s.`;
523
+ } else {
524
+ message += ", please wait.";
525
+ }
526
+ return new RateLimitError(message, retryAfter ? asString(retryAfter) : void 0);
527
+ }
528
+ return new LLMError(`Anthropic API error (${asString(err.status)}): ${err.message}`);
529
+ }
530
+ return new NetworkError(`Network error: ${asErrorString(err)}`);
531
+ }
532
+
533
+ export {
534
+ DEFAULT_EAGER_THRESHOLD_PERCENT,
535
+ CHARS_PER_TOKEN,
536
+ NATIVE_TOOL_USE_BETA,
537
+ isOfficialAnthropicEndpoint,
538
+ estimateSchemaTokens,
539
+ decideMode,
540
+ measureSchemaChars,
541
+ applyMode,
542
+ decideAndApply,
543
+ markToolsForCache,
544
+ needsToolSearchBeta,
545
+ fetchModelContextWindow,
546
+ buildAnthropicMessages,
547
+ AnthropicClient,
548
+ markLastUserTailForCache
549
+ };
@@ -0,0 +1,38 @@
1
+ // src/sandbox/bwrap.ts
2
+ import { execSync } from "child_process";
3
+ var BwrapSandbox = class {
4
+ available() {
5
+ try {
6
+ execSync("which bwrap", { stdio: "ignore" });
7
+ return true;
8
+ } catch {
9
+ return false;
10
+ }
11
+ }
12
+ wrap(command, config) {
13
+ const args = [];
14
+ args.push("bwrap", "--unshare-user", "--unshare-pid");
15
+ args.push("--ro-bind", "/", "/");
16
+ for (const path of config.allowWrite) {
17
+ args.push("--bind", path, path);
18
+ }
19
+ for (const path of config.denyWrite) {
20
+ args.push("--ro-bind", path, path);
21
+ }
22
+ if (!config.networkEnabled) {
23
+ args.push("--unshare-net");
24
+ }
25
+ args.push("--proc", "/proc");
26
+ args.push("--", "bash", "-c", command);
27
+ return args.map((arg) => {
28
+ if (/[ \t\n"'\\$`!]/.test(arg)) {
29
+ return `'${arg.replace(/'/g, "'\\''")}'`;
30
+ }
31
+ return arg;
32
+ }).join(" ");
33
+ }
34
+ };
35
+
36
+ export {
37
+ BwrapSandbox
38
+ };