@swifty.js/swifty 0.0.28 → 0.0.30

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 (37) hide show
  1. package/README.md +40 -38
  2. package/dist/{agent-3NON2EXE.js → agent-TCNIRVHD.js} +1 -1
  3. package/dist/anthropic-7RIVSEDF.js +4 -0
  4. package/dist/{checker-N5TIJEN5.js → checker-3LHDOATB.js} +1 -1
  5. package/dist/chunk-2IBMB3ZM.js +150 -0
  6. package/dist/chunk-AQ3A5CF4.js +5 -0
  7. package/dist/chunk-FA26MQ7K.js +257 -0
  8. package/dist/{chunk-JGSIQXEN.js → chunk-QFSCPD63.js} +1 -1
  9. package/dist/chunk-TKADLB2Q.js +4 -0
  10. package/dist/chunk-VA64DJNN.js +4 -0
  11. package/dist/lib/{agent-XYO2MXXK.js → agent-IRY7KR5D.js} +3 -3
  12. package/dist/lib/{anthropic-TGJGAP5R.js → anthropic-CL5IK7KN.js} +3 -3
  13. package/dist/lib/{checker-AXHPKVBY.js → checker-BS4MK3O6.js} +1 -1
  14. package/dist/lib/{chunk-2IVEVG5N.js → chunk-7QPDFSQE.js} +10 -17
  15. package/dist/lib/{chunk-P4F46PEF.js → chunk-I7OPU4K2.js} +48 -8
  16. package/dist/lib/{chunk-ALJAVRRX.js → chunk-IYPTOY3Y.js} +59 -19
  17. package/dist/lib/{chunk-DV6RV5WU.js → chunk-OOSNCCI7.js} +134 -258
  18. package/dist/lib/{chunk-FBSPZQGC.js → chunk-OQIQOU5S.js} +132 -73
  19. package/dist/lib/{chunk-IB5IQK2S.js → chunk-PIL7M52F.js} +2 -11
  20. package/dist/lib/{chunk-DJ6AILPN.js → chunk-RMB3J46W.js} +13 -16
  21. package/dist/lib/index.d.ts +387 -112
  22. package/dist/lib/index.js +1000 -1157
  23. package/dist/lib/{openai-5UCIIDPO.js → openai-LH4E7ZQS.js} +2 -2
  24. package/dist/lib/{tool-filter-R6HDDJHE.js → tool-filter-CS6W2GMU.js} +1 -1
  25. package/dist/main.js +186 -178
  26. package/dist/{openai-YLS2LUAI.js → openai-RZLCY55V.js} +15 -15
  27. package/dist/{server-GX72MJQF.js → server-IVIRQTO7.js} +17 -17
  28. package/dist/{tool-filter-VBP6WOGO.js → tool-filter-CG3KGQQW.js} +1 -1
  29. package/package.json +5 -4
  30. package/dist/anthropic-BJ5GN2VT.js +0 -4
  31. package/dist/chunk-D34FUVGU.js +0 -4
  32. package/dist/chunk-LZBOXJX2.js +0 -301
  33. package/dist/chunk-R63ASIIW.js +0 -407
  34. package/dist/chunk-WX3B64R4.js +0 -4
  35. package/dist/chunk-Y6SQB5FG.js +0 -4
  36. package/dist/glob.wasm +0 -0
  37. package/dist/lib/glob.wasm +0 -0
@@ -40,7 +40,7 @@ var ContextTooLongError = class extends LLMError {
40
40
  import { existsSync, readFileSync } from "fs";
41
41
  import { homedir } from "os";
42
42
  import { join } from "path";
43
- import { safeParse } from "@modelcontextprotocol/sdk/server/zod-compat.js";
43
+ import { getParseErrorMessage, safeParse } from "@modelcontextprotocol/sdk/server/zod-compat.js";
44
44
  import yaml from "js-yaml";
45
45
  import { z } from "zod";
46
46
  var log = createChildLogger({ module: "config" });
@@ -59,7 +59,22 @@ var ConfigError = class extends Error {
59
59
  this.name = "ConfigError";
60
60
  }
61
61
  };
62
- var ProviderConfigSchema = z.object({
62
+ function globalConfigPath() {
63
+ return join(homedir(), ".swifty", "config.yaml");
64
+ }
65
+ var THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
66
+ var ReasoningEffortSchema = z.enum(["none", "minimal", "low", "medium", "high", "xhigh", "max"]);
67
+ var AnthropicEffortSchema = z.enum(["low", "medium", "high", "xhigh", "max"]);
68
+ var ThinkingLevelMapSchema = z.strictObject({
69
+ off: z.literal("none").nullable().optional(),
70
+ minimal: ReasoningEffortSchema.nullable().optional(),
71
+ low: ReasoningEffortSchema.nullable().optional(),
72
+ medium: ReasoningEffortSchema.nullable().optional(),
73
+ high: ReasoningEffortSchema.nullable().optional(),
74
+ xhigh: ReasoningEffortSchema.nullable().optional(),
75
+ max: ReasoningEffortSchema.nullable().optional()
76
+ });
77
+ var ProviderConfigSchema = z.looseObject({
63
78
  name: z.string(),
64
79
  /**
65
80
  * enum: ["anthropic", "openai", "openai-compat"]
@@ -68,17 +83,98 @@ var ProviderConfigSchema = z.object({
68
83
  base_url: z.string(),
69
84
  model: z.string(),
70
85
  api_key: z.string().optional(),
71
- thinking: z.boolean().optional(),
86
+ thinking: z.enum(THINKING_LEVELS).optional(),
87
+ /** Explicit capability metadata, never inferred from model names. */
88
+ reasoning: z.boolean().optional(),
89
+ thinking_level_map: ThinkingLevelMapSchema.optional(),
90
+ /** Only Anthropic uses this mode; existing configurations use budgets. */
91
+ thinking_mode: z.enum(["budget", "adaptive"]).optional(),
72
92
  context_window: z.coerce.number().optional(),
93
+ /**
94
+ * The model's output ceiling (PI's `model.maxTokens`). Clamped to the
95
+ * context window; reasoning shares this ceiling instead of raising it.
96
+ */
73
97
  max_output_tokens: z.coerce.number().optional()
74
98
  });
75
- var DEFAULT_PROVIDER_THINKING = true;
99
+ var DEFAULT_THINKING_LEVEL = "high";
76
100
  var DEFAULT_CONTEXT_WINDOW = 1e6;
77
101
  var DEFAULT_MAX_OUTPUT_TOKENS = 128e3;
102
+ var THINKING_BUDGETS = {
103
+ minimal: 1024,
104
+ low: 2048,
105
+ medium: 8192,
106
+ high: 16384,
107
+ xhigh: 32768,
108
+ max: 65536
109
+ };
110
+ var MIN_THINKING_BUDGET_TOKENS = 1024;
111
+ var MIN_THINKING_ANSWER_TOKENS = 1024;
112
+ function isValidThinkingLevel(value) {
113
+ return THINKING_LEVELS.some((level) => level === value);
114
+ }
115
+ function getThinkingLevel(provider) {
116
+ return clampThinkingLevel(provider, provider.thinking ?? DEFAULT_THINKING_LEVEL);
117
+ }
118
+ function thinkingBudgetForLevel(level) {
119
+ return level === "off" ? 0 : THINKING_BUDGETS[level];
120
+ }
121
+ function toReasoningEffort(level, provider) {
122
+ if (provider?.reasoning === false) {
123
+ return null;
124
+ }
125
+ if (level === "off") {
126
+ return "none";
127
+ }
128
+ const mapped = provider?.thinking_level_map?.[level];
129
+ if (mapped !== void 0) {
130
+ return mapped;
131
+ }
132
+ if (provider?.protocol === "anthropic" && provider.thinking_mode === "adaptive") {
133
+ if (level === "minimal") {
134
+ return "low";
135
+ }
136
+ if (level === "xhigh") {
137
+ return "high";
138
+ }
139
+ }
140
+ return level;
141
+ }
142
+ function toAnthropicThinkingEffort(level, provider) {
143
+ const parsed = AnthropicEffortSchema.safeParse(toReasoningEffort(level, provider));
144
+ return parsed.success ? parsed.data : null;
145
+ }
146
+ function getSupportedThinkingLevels(provider) {
147
+ if (provider.reasoning === false || provider.protocol === "anthropic" && provider.thinking_mode !== "adaptive" && getMaxOutputTokens(provider) < MIN_THINKING_BUDGET_TOKENS + MIN_THINKING_ANSWER_TOKENS) {
148
+ return ["off"];
149
+ }
150
+ return THINKING_LEVELS.filter((level) => {
151
+ if (level === "off") {
152
+ return true;
153
+ }
154
+ if (provider.protocol === "anthropic" && provider.thinking_mode === "adaptive") {
155
+ return toAnthropicThinkingEffort(level, provider) !== null;
156
+ }
157
+ const effort = toReasoningEffort(level, provider);
158
+ return effort !== null && effort !== "none";
159
+ });
160
+ }
161
+ function clampThinkingLevel(provider, level) {
162
+ const supported = getSupportedThinkingLevels(provider);
163
+ let effective = "off";
164
+ for (const candidate of THINKING_LEVELS) {
165
+ if (supported.includes(candidate)) {
166
+ effective = candidate;
167
+ }
168
+ if (candidate === level) {
169
+ break;
170
+ }
171
+ }
172
+ return effective;
173
+ }
78
174
  function withProviderDefaults(provider) {
79
175
  return {
80
176
  ...provider,
81
- thinking: provider.thinking ?? DEFAULT_PROVIDER_THINKING,
177
+ thinking: getThinkingLevel(provider),
82
178
  context_window: getContextWindow(provider),
83
179
  max_output_tokens: getMaxOutputTokens(provider)
84
180
  };
@@ -87,7 +183,9 @@ function getContextWindow(provider) {
87
183
  return Number.isSafeInteger(provider.context_window) && (provider.context_window ?? 0) > 0 ? provider.context_window ?? DEFAULT_CONTEXT_WINDOW : DEFAULT_CONTEXT_WINDOW;
88
184
  }
89
185
  function getMaxOutputTokens(provider) {
90
- return Number.isSafeInteger(provider.max_output_tokens) && (provider.max_output_tokens ?? 0) > 0 ? provider.max_output_tokens ?? DEFAULT_MAX_OUTPUT_TOKENS : DEFAULT_MAX_OUTPUT_TOKENS;
186
+ const configured = provider.max_output_tokens;
187
+ const maxOutput = Number.isSafeInteger(configured) && (configured ?? 0) > 0 ? configured ?? DEFAULT_MAX_OUTPUT_TOKENS : DEFAULT_MAX_OUTPUT_TOKENS;
188
+ return Math.min(maxOutput, getContextWindow(provider));
91
189
  }
92
190
  function resolveAPIKey(p) {
93
191
  if (p.api_key) {
@@ -177,6 +275,10 @@ function loadSingleFile(path) {
177
275
  const parsed2 = safeParse(z.array(ProviderConfigSchema), raw.providers);
178
276
  if (parsed2.success) {
179
277
  providers = parsed2.data.map(withProviderDefaults);
278
+ } else {
279
+ throw new ConfigError(
280
+ `Invalid provider configuration in ${path}: ${getParseErrorMessage(parsed2.error)}`
281
+ );
180
282
  }
181
283
  }
182
284
  if ("permission_mode" in raw && typeof raw.permission_mode === "string") {
@@ -216,41 +318,6 @@ function loadSingleFile(path) {
216
318
  enable_fork: enableFork
217
319
  };
218
320
  }
219
- function mergeConfig(base, override) {
220
- if (override.providers.length > 0) {
221
- base.providers = override.providers;
222
- }
223
- if (override.permission_mode) {
224
- base.permission_mode = override.permission_mode;
225
- }
226
- if (override.mcp_servers.length > 0) {
227
- const mcpToIdx = /* @__PURE__ */ new Map();
228
- for (let i = 0; i < base.mcp_servers.length; i++) {
229
- const mcp = base.mcp_servers[i];
230
- mcpToIdx.set(mcp.name, i);
231
- }
232
- for (const s of override.mcp_servers) {
233
- const idx = mcpToIdx.get(s.name);
234
- if (idx !== void 0) {
235
- base.mcp_servers[idx] = s;
236
- } else {
237
- base.mcp_servers.push(s);
238
- mcpToIdx.set(s.name, base.mcp_servers.length - 1);
239
- }
240
- }
241
- }
242
- base.hooks = [...base.hooks, ...override.hooks];
243
- if (override.sandbox) {
244
- base.sandbox = { ...base.sandbox, ...override.sandbox };
245
- }
246
- if (override.enable_coordinator_mode) {
247
- base.enable_coordinator_mode = true;
248
- }
249
- if (override.enable_fork !== void 0) {
250
- base.enable_fork = override.enable_fork;
251
- }
252
- return base;
253
- }
254
321
  function validateProviders(config) {
255
322
  if (config.providers.length === 0) {
256
323
  throw new ConfigError("At least one provider MUST be configured.");
@@ -277,43 +344,24 @@ function validateProviders(config) {
277
344
  }
278
345
  function loadConfig(path, options = {}) {
279
346
  if (path) {
280
- const config = loadSingleFile(path);
281
- if (!options.allowEmptyProviders || config.providers.length > 0) {
282
- validateProviders(config);
283
- }
284
- return config;
285
- }
286
- const wd = process.cwd();
287
- const home = homedir();
288
- const candidates = [
289
- join(home, ".swifty", "config.yaml"),
290
- join(wd, ".swifty", "config.yaml"),
291
- join(wd, ".swifty", "config.local.yaml")
292
- ];
293
- let merged = null;
294
- for (const candidate of candidates) {
295
- if (!existsSync(candidate)) {
296
- continue;
297
- }
298
- const layer = loadSingleFile(candidate);
299
- if (!merged) {
300
- merged = layer;
301
- } else {
302
- merged = mergeConfig(merged, layer);
347
+ const config2 = loadSingleFile(path);
348
+ if (!options.allowEmptyProviders || config2.providers.length > 0) {
349
+ validateProviders(config2);
303
350
  }
351
+ return config2;
304
352
  }
305
- if (!merged) {
353
+ const candidate = globalConfigPath();
354
+ if (!existsSync(candidate)) {
306
355
  if (options.allowEmptyProviders) {
307
356
  return { providers: [], mcp_servers: [], hooks: [] };
308
357
  }
309
- throw new ConfigError(
310
- "No config file found, expected .swifty/config.y(a)ml under project or $HOME/.swifty/config.y(a)ml."
311
- );
358
+ throw new ConfigError(`No config file found, expected ${candidate}.`);
312
359
  }
313
- if (!options.allowEmptyProviders || merged.providers.length > 0) {
314
- validateProviders(merged);
360
+ const config = loadSingleFile(candidate);
361
+ if (!options.allowEmptyProviders || config.providers.length > 0) {
362
+ validateProviders(config);
315
363
  }
316
- return merged;
364
+ return config;
317
365
  }
318
366
 
319
367
  // src/conversation/pairing.ts
@@ -374,17 +422,28 @@ export {
374
422
  NetworkError,
375
423
  ContextTooLongError,
376
424
  ConfigError,
425
+ globalConfigPath,
426
+ THINKING_LEVELS,
377
427
  ProviderConfigSchema,
378
- DEFAULT_PROVIDER_THINKING,
428
+ DEFAULT_THINKING_LEVEL,
379
429
  DEFAULT_CONTEXT_WINDOW,
380
430
  DEFAULT_MAX_OUTPUT_TOKENS,
431
+ THINKING_BUDGETS,
432
+ MIN_THINKING_BUDGET_TOKENS,
433
+ MIN_THINKING_ANSWER_TOKENS,
434
+ isValidThinkingLevel,
435
+ getThinkingLevel,
436
+ thinkingBudgetForLevel,
437
+ toReasoningEffort,
438
+ toAnthropicThinkingEffort,
439
+ getSupportedThinkingLevels,
440
+ clampThinkingLevel,
381
441
  withProviderDefaults,
382
442
  getContextWindow,
383
443
  getMaxOutputTokens,
384
444
  resolveAPIKey,
385
445
  HookConfigSchema,
386
446
  forkEnabled,
387
- mergeConfig,
388
447
  loadConfig,
389
448
  INTERRUPTED_TOOL_RESULT,
390
449
  REJECTED_TOOL_RESULT,
@@ -101,8 +101,7 @@ function isToolResultContentBlock(value) {
101
101
  return normalizeToolResultContentBlock(value) !== null;
102
102
  }
103
103
  var SKIP_DIRS = /* @__PURE__ */ new Set([
104
- ".claude",
105
- // Claude Code
104
+ ".agents",
106
105
  ".git",
107
106
  // Git
108
107
  ".swifty",
@@ -113,20 +112,12 @@ var SKIP_DIRS = /* @__PURE__ */ new Set([
113
112
  // Python venv
114
113
  ".mypy_cache",
115
114
  // Python mypy
116
- ".tox",
117
- // Python tox
118
115
  "__pycache__",
119
116
  // Python
120
- "build",
121
- // C++
122
117
  "dist",
123
118
  // Webpack, Vite
124
- "node_modules",
119
+ "node_modules"
125
120
  // Node.js
126
- "vendor",
127
- // Go
128
- "venv"
129
- // Python venv
130
121
  ]);
131
122
 
132
123
  export {
@@ -88,11 +88,7 @@ var CONTENT_FIELDS = {
88
88
  Grep: "pattern",
89
89
  InstallSkill: "source"
90
90
  };
91
- var DEFAULT_DENY_WRITE = [
92
- ".swifty/config.yaml",
93
- ".swifty/permissions.local.yaml",
94
- ".swifty/skills/"
95
- ];
91
+ var DEFAULT_DENY_WRITE = [".swifty/permissions.yaml", ".agents/skills/"];
96
92
  function extractContent(toolName, args) {
97
93
  if (toolName === MCP_CALL_TOOL_NAME) {
98
94
  return mcpCallPermissionContent(strArg(args, "server", ""), strArg(args, "tool", ""));
@@ -218,12 +214,10 @@ function evaluateRules(rules, toolName, content) {
218
214
  var RuleEngine = class {
219
215
  userPath;
220
216
  projectPath;
221
- localPath;
222
217
  cache = /* @__PURE__ */ new Map();
223
218
  constructor(workDir) {
224
219
  this.userPath = join2(homedir(), ".swifty", "permissions.yaml");
225
220
  this.projectPath = join2(workDir, ".swifty", "permissions.yaml");
226
- this.localPath = join2(workDir, ".swifty", "permissions.local.yaml");
227
221
  }
228
222
  // Read a single rules file; skips disk I/O and parsing on cache hit.
229
223
  rulesFor(path) {
@@ -242,12 +236,12 @@ var RuleEngine = class {
242
236
  this.cache.set(path, { mtimeNs: st.mtimeNs, size: st.size, rules });
243
237
  return rules;
244
238
  }
245
- // Return the merged snapshot of all three rules files. Reuses the previous
239
+ // Return the merged snapshot of both rules files. Reuses the previous
246
240
  // parse result when files are unchanged; re-reads only on change, so edits
247
241
  // take effect on the next evaluation without redundant parsing. One snapshot
248
242
  // is taken per tool call and shared across sub-command checks.
249
243
  snapshot() {
250
- return [this.userPath, this.projectPath, this.localPath].flatMap((p) => this.rulesFor(p));
244
+ return [this.userPath, this.projectPath].flatMap((p) => this.rulesFor(p));
251
245
  }
252
246
  // Take a snapshot then adjudicate: reuses the previous parse result when
253
247
  // files are unchanged; a freshly written "allow always" rule takes effect
@@ -257,11 +251,11 @@ var RuleEngine = class {
257
251
  evaluate(toolName, content) {
258
252
  return evaluateRules(this.snapshot(), toolName, content);
259
253
  }
260
- // Persists a rule to the project-local YAML file in the `Tool(pattern)`
254
+ // Persists a rule to the project-level YAML file in the `Tool(pattern)`
261
255
  // format so "allow always" survives a restart.
262
- appendLocalRule(rule) {
263
- mkdirSync(dirname2(this.localPath), { recursive: true });
264
- const rules = loadRulesFile(this.localPath);
256
+ appendProjectRule(rule) {
257
+ mkdirSync(dirname2(this.projectPath), { recursive: true });
258
+ const rules = loadRulesFile(this.projectPath);
265
259
  const exists = rules.some(
266
260
  (r) => r.tool === rule.tool && r.pattern === rule.pattern && r.effect === rule.effect
267
261
  );
@@ -273,7 +267,7 @@ var RuleEngine = class {
273
267
  rule: `${r.tool}(${r.pattern})`,
274
268
  effect: r.effect
275
269
  }));
276
- writeFileSync(this.localPath, yaml.dump(entries), "utf-8");
270
+ writeFileSync(this.projectPath, yaml.dump(entries), "utf-8");
277
271
  }
278
272
  };
279
273
  function detectDangerous(command) {
@@ -334,7 +328,10 @@ var PermissionChecker = class _PermissionChecker {
334
328
  const rules = () => snapshot ??= this.ruleEngine.snapshot();
335
329
  const explicitEffect = evaluateRules(rules(), toolName, content);
336
330
  if (explicitEffect === "deny" || explicitEffect === "ask") {
337
- return { effect: explicitEffect, reason: `Permission rule: ${explicitEffect}` };
331
+ return {
332
+ effect: explicitEffect,
333
+ reason: `Permission rule: ${explicitEffect}`
334
+ };
338
335
  }
339
336
  if (this.mode === "plan" && (toolName === "WriteFile" || toolName === "EditFile")) {
340
337
  const path = strArg(args, "file_path", "");
@@ -432,7 +429,7 @@ var PermissionChecker = class _PermissionChecker {
432
429
  const words = content.trim().split(/\s+/).slice(0, 2);
433
430
  pattern = words.join(" ") + "*";
434
431
  }
435
- this.ruleEngine.appendLocalRule({
432
+ this.ruleEngine.appendProjectRule({
436
433
  tool: toolName,
437
434
  pattern,
438
435
  effect: "allow"