@swifty.js/swifty 0.0.28 → 0.0.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -68,11 +68,9 @@ pnpm dev
68
68
 
69
69
  ## Configuration
70
70
 
71
- Swifty reads YAML configuration files from multiple locations, merged in order:
71
+ Swifty reads a single global YAML configuration file:
72
72
 
73
- 1. ~/.swifty/config.yaml
74
- 2. .swifty/config.yaml (project root)
75
- 3. .swifty/config.local.yaml (project root, gitignored)
73
+ - ~/.swifty/config.yaml
76
74
 
77
75
  At least one provider must be configured. Example config.yaml:
78
76
 
@@ -81,8 +79,9 @@ providers:
81
79
  - name: anthropic
82
80
  protocol: anthropic
83
81
  base_url: https://api.anthropic.com
84
- model: claude-sonnet-4-20250514
82
+ model: claude-sonnet-4-6
85
83
  # api_key defaults to $ANTHROPIC_API_KEY
84
+ thinking: high # off | minimal | low | medium | high | xhigh | max
86
85
 
87
86
  permission_mode: default
88
87
 
@@ -111,16 +110,18 @@ enable_coordinator_mode: false
111
110
 
112
111
  Provider fields:
113
112
 
114
- | Field | Required | Description |
115
- | ----------------- | -------- | -------------------------------------------------------------------- |
116
- | name | yes | Display name for the provider |
117
- | protocol | yes | One of: anthropic, openai, openai-compat |
118
- | base_url | yes | API base URL |
119
- | model | yes | Model identifier |
120
- | api_key | no | API key (falls back to environment variable) |
121
- | thinking | no | Enable extended thinking (default: true) |
122
- | context_window | no | Context window in tokens (default: 1000000; no model-name inference) |
123
- | max_output_tokens | no | Maximum output tokens (default: 128000) |
113
+ | Field | Required | Description |
114
+ | ----------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
115
+ | name | yes | Display name for the provider |
116
+ | protocol | yes | One of: anthropic, openai, openai-compat |
117
+ | base_url | yes | API base URL |
118
+ | model | yes | Model identifier |
119
+ | api_key | no | API key (falls back to environment variable) |
120
+ | thinking | no | Thinking level: off, minimal, low, medium, high, xhigh, max. Defaults to `high` for `anthropic` and `off` for `openai`/`openai-compat` (reasoning parameters are only sent when configured). A legacy `true`/`false` is still accepted (`false` → off, `true` → the protocol default). |
121
+ | context_window | no | Context window in tokens (default: 1000000; no model-name inference) |
122
+ | max_output_tokens | no | Output cap for the model (default: 128000, never above `context_window`). Set this for models with a smaller output limit. |
123
+
124
+ The thinking level controls reasoning depth. For `anthropic` it maps to a thinking token budget (minimal 1024, low 2048, medium 8192, high 16384, xhigh 32768, max 65536); for `openai` and `openai-compat` it maps to the provider reasoning effort. The budget shares `max_output_tokens` and always leaves at least 1024 answer tokens, so lower `max_output_tokens` shrinks the thinking budget instead of disabling it (below a 2048-token cap no valid budget remains and thinking falls back to disabled). On `openai`/`openai-compat`, the effort string is passed through verbatim, and only levels supported by the model are accepted (`xhigh`/`max` are model-specific). Use `/thinking <level>` to change it at runtime (the change is applied to the active client and saved to `~/.swifty/config.yaml`), or `/thinking` to show the current level.
124
125
 
125
126
  API keys are resolved in this order: explicit api_key field, then environment variables (ANTHROPIC_API_KEY for anthropic, OPENAI_API_KEY for openai and openai-compat).
126
127
 
@@ -134,9 +135,9 @@ swifty
134
135
 
135
136
  Launches the terminal interface. If multiple providers are configured, a provider selection screen appears first.
136
137
 
137
- Use `/login` to configure and activate a provider from the TUI. When no provider is configured, the login form opens automatically. Name, protocol, base URL, API key, and model are required in the form. Use ↑↓ or Tab to move between fields, ←→ to select protocol or thinking, Enter to save, and Esc to cancel. Duplicate names receive numeric suffixes (`name2`, `name3`, …).
138
+ Use `/login` to configure and activate a provider from the TUI. When no provider is configured, the login form opens automatically. Name, protocol, base URL, API key, and model are required in the form. Use ↑↓ or Tab to move between fields, ←→ to select protocol or cycle the thinking level, Enter to save, and Esc to cancel. Changing the protocol also moves an untouched thinking level to that protocol's default. Duplicate names receive numeric suffixes (`name2`, `name3`, …).
138
139
 
139
- The form saves to the project's `.swifty/config.local.yaml`, retaining existing providers and other settings. Context window accepts integers from 1000 to 10000000; max output accepts integers from 1 to 1000000 and must not exceed the context window. Empty optional fields use the defaults above.
140
+ The form saves to `~/.swifty/config.yaml`, retaining existing providers and other settings. Context window accepts integers from 1000 to 10000000; max output accepts integers from 1 to 1000000 and must not exceed the context window. Empty optional fields use the defaults above.
140
141
 
141
142
  ### Print Mode (Non-Interactive)
142
143
 
@@ -161,26 +162,27 @@ Starts a Koa HTTP server and WebSocket bridge. The bundled React frontend is ser
161
162
 
162
163
  Inside the TUI, these commands are available:
163
164
 
164
- | Command | Description |
165
- | ----------------------- | ---------------------------------------------------------------------------------- |
166
- | /login | Configure, save, and activate an LLM provider |
167
- | /status | Show current session status (model, tokens, tools, sandbox, memories, skills, MCP) |
168
- | /permission mode <mode> | Change permission mode (default, acceptEdits, plan, bypassPermissions) |
169
- | /memory | List stored memories |
170
- | /memory clear | Clear all memories |
171
- | /skills | List available skills |
172
- | /skills reload | Hot-reload skills from disk |
173
- | /skill <name> [args] | Run a skill by name |
174
- | /plan | Enter plan mode (read-only investigation) |
175
- | /do | Exit plan mode and execute the approved plan |
176
- | /compact | Force conversation compaction |
177
- | /clear | Reset the session and clear the terminal |
178
- | /resume [id] | List or restore a previous session |
179
- | /rewind | Open checkpoint rewind dialog |
180
- | /sandbox [1/2/3] | Configure sandbox (1=on+auto, 2=on+manual, 3=off) |
181
- | /worktree | List git worktrees |
182
- | /mcp | Show MCP server status |
183
- | /quit | Exit the application |
165
+ | Command | Description |
166
+ | ----------------------- | ------------------------------------------------------------------------------------------------------------------------- |
167
+ | /login | Configure, save, and activate an LLM provider |
168
+ | /status | Show current session status (model, tokens, tools, sandbox, memories, skills, MCP) |
169
+ | /permission mode <mode> | Change permission mode (default, acceptEdits, plan, bypassPermissions) |
170
+ | /memory | List stored memories |
171
+ | /memory clear | Clear all memories |
172
+ | /skills | List available skills |
173
+ | /skills reload | Hot-reload skills from disk |
174
+ | /skill <name> [args] | Run a skill by name |
175
+ | /plan | Enter plan mode (read-only investigation) |
176
+ | /do | Exit plan mode and execute the approved plan |
177
+ | /compact | Force conversation compaction |
178
+ | /clear | Reset the session and clear the terminal |
179
+ | /resume [id] | List or restore a previous session |
180
+ | /rewind | Open checkpoint rewind dialog |
181
+ | /sandbox [1/2/3] | Configure sandbox (1=on+auto, 2=on+manual, 3=off) |
182
+ | /worktree | List git worktrees |
183
+ | /mcp | Show MCP server status |
184
+ | /thinking [level] | Show or set the thinking level (off, minimal, low, medium, high, xhigh, max); setting persists to `~/.swifty/config.yaml` |
185
+ | /quit | Exit the application |
184
186
 
185
187
  ### Keyboard Shortcuts
186
188
 
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire as __swiftyCreateRequire } from "node:module";
3
3
  const require = __swiftyCreateRequire(import.meta.url);
4
- import{r as a}from"./chunk-LZBOXJX2.js";import"./chunk-JGSIQXEN.js";import"./chunk-WX3B64R4.js";import"./chunk-QCKJLO6X.js";import"./chunk-OTVZGPHD.js";import"./chunk-E55KH72M.js";import"./chunk-X3UA5OZL.js";export{a as Agent};
4
+ import{s as a}from"./chunk-POHIQUFP.js";import"./chunk-QFSCPD63.js";import"./chunk-TMEX7RFA.js";import"./chunk-QCKJLO6X.js";import"./chunk-OTVZGPHD.js";import"./chunk-E55KH72M.js";import"./chunk-X3UA5OZL.js";export{a as Agent};
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire as __swiftyCreateRequire } from "node:module";
3
+ const require = __swiftyCreateRequire(import.meta.url);
4
+ import{a as D}from"./chunk-K5ZK27BX.js";import{a as q}from"./chunk-QFSCPD63.js";import{g as M}from"./chunk-C3WGLVVZ.js";import{A as z,m as N,n as R,q as O,r as L,u as F,v as I,w as H,x as G,y as W}from"./chunk-TMEX7RFA.js";import"./chunk-QCKJLO6X.js";import{D as P,I as S,J as B,K as j,L as U,M as d,N as K,R as $,g as C}from"./chunk-OTVZGPHD.js";import"./chunk-E55KH72M.js";import"./chunk-X3UA5OZL.js";var J=1024,V=1024;function Q(n){for(let t=n.length-1;t>=0;t--){let e=n[t];if(e.defer_loading!==!0){e.cache_control={type:"ephemeral"};return}}}function X(n){return n.some(t=>t.defer_loading)}var v=S({module:"llm"});var Z=3e3,ee=P.object({max_input_tokens:P.coerce.number()});async function he(n){if(n.protocol!=="anthropic")return 0;let t=L(n),o=`${n.base_url.replace(/\/+$/,"")}/v1/models/${encodeURIComponent(n.model)}`,s=new AbortController,r=setTimeout(()=>{s.abort()},Z);try{let c=await fetch(o,{method:"GET",headers:{"anthropic-version":"2023-06-01",...t?{"x-api-key":t}:{}},signal:s.signal});if(!c.ok)return 0;let k=await c.json(),{success:_,error:m,data:h}=await C(ee,k);if(!_)return v.warn({message:m.message},"model context window schema validation failed"),0;let g=h.max_input_tokens;return Math.max(g,0)}catch(c){return v.error({err:c},"failed to fetch model context window"),0}finally{clearTimeout(r)}}function T(n){return typeof n=="string"?[{type:"text",text:n}]:n.map(t=>{let e=q(t);if(!e||e.type==="tool_reference")throw new Error(`Unsupported user content block: ${$(t,"type","unknown")}`);return e})}function te(n){let t=[];for(let e of n)if(e.role==="assistant"){let o=[];if(e.thinkingBlocks)for(let r of e.thinkingBlocks)o.push({type:"thinking",thinking:r.thinking,signature:r.signature});let s=typeof e.content=="string"?e.content:B(e.content);if(s&&o.push({type:"text",text:s}),e.toolUses)for(let r of e.toolUses)o.push({type:"tool_use",id:r.toolUseId,name:r.toolName,input:r.arguments});o.length===0&&o.push({type:"text",text:""}),t.push({role:"assistant",content:o})}else if(e.toolResults&&e.toolResults.length>0){let o=[];for(let s of e.toolResults)o.push({type:"tool_result",tool_use_id:s.toolUseId,is_error:s.isError,content:s.contentBlocks?.length?s.contentBlocks:s.content});e.content.length>0&&o.push(...T(e.content)),t.push({role:"user",content:o})}else{if(t.length===0){t.push({role:"user",content:T(e.content)});continue}let o=!1,s=t[t.length-1],r=s.content;s.role==="user"&&(typeof r=="string"||Array.isArray(r)&&r.length>0&&(r[0].type==="text"||r[0].type==="image"))&&(o=!0),o?(typeof r=="string"&&(r=s.content=r.trim().length>0?[{type:"text",text:r}]:[]),r.push(...T(e.content))):t.push({role:"user",content:T(e.content)})}return t}var Y=class{client;model;thinkingLevel;systemPrompt;maxOutputTokens;constructor(t,e){let o=L(t);if(!o)throw new I("Anthropic API key not found, set ANTHROPIC_API_KEY in ~/.swifty/config.yaml, or via ANTHROPIC_API_KEY env variable.");this.client=new M({apiKey:o,baseURL:t.base_url}),this.model=t.model,this.thinkingLevel=N(t),this.systemPrompt=e,this.maxOutputTokens=O(t)}setSystemPrompt(t){this.systemPrompt=t}setMaxOutputTokens(t){this.maxOutputTokens=t}setThinkingLevel(t){this.thinkingLevel=t}getThinkingLevel(){return this.thinkingLevel}async*stream(t,e,o){let s=te(z(t.getMessages())),r=X(e),c=e.map(l=>{let p=l.input_schema,u={name:l.name,description:l.description,input_schema:{type:"object",properties:p.properties,required:p.required??[]}};return l.defer_loading===!0&&(u.defer_loading=!0),u});Q(c),ne(s);let k={model:this.model,max_tokens:this.maxOutputTokens,stream:!0,system:[{type:"text",text:this.systemPrompt,cache_control:{type:"ephemeral"}}],messages:s,...c.length>0?{tools:c}:{}},_=this.maxOutputTokens-V;k.thinking=this.thinkingLevel!=="off"&&_>=J?{type:"enabled",budget_tokens:Math.min(R(this.thinkingLevel),_)}:{type:"disabled"};let m=0,h=0,g=0,x=0,E="end_turn",b="",A="",w=!1;try{let l=this.client.messages.stream(k,{...o?{signal:o}:{},...r?{headers:{"anthropic-beta":D}}:{}}),p="",u="",f="";for await(let a of l)switch(a.type){case"content_block_start":{let i=a.content_block;i.type==="thinking"?(w=!0,b="",A=""):i.type==="tool_use"&&(u=i.id,p=i.name,f="",yield{type:"tool_call_start",toolName:p,toolId:u});break}case"content_block_delta":{let i=a.delta;i.type==="thinking_delta"?(b+=i.thinking,yield{type:"thinking_delta",text:i.thinking}):i.type==="signature_delta"?A+=i.signature:i.type==="text_delta"?yield{type:"text_delta",text:i.text}:i.type==="input_json_delta"&&(f+=i.partial_json,yield{type:"tool_call_delta",text:i.partial_json});break}case"content_block_stop":{if(w&&(yield{type:"thinking_complete",thinking:b,signature:A},w=!1),p){let i={};if(f)try{let y=JSON.parse(f);i=j(y)?U(y):{}}catch(y){v.error({err:y},"llm operation failed"),i={}}yield{type:"tool_call_complete",toolId:u,toolName:p,arguments:i},p="",u="",f=""}break}case"message_delta":{a.delta.stop_reason&&(E=a.delta.stop_reason),a.usage.output_tokens&&(h=a.usage.output_tokens,a.usage.input_tokens&&(m=a.usage.input_tokens),a.usage.cache_read_input_tokens&&(g=a.usage.cache_read_input_tokens),a.usage.cache_creation_input_tokens&&(x=a.usage.cache_creation_input_tokens));break}case"message_start":{m=a.message.usage.input_tokens,h=a.message.usage.output_tokens,g=a.message.usage.cache_read_input_tokens??0,x=a.message.usage.cache_creation_input_tokens??0;break}}yield{type:"stream_end",stopReason:E,usage:{inputTokens:m,outputTokens:h,cacheReadInputTokens:g,cacheCreationInputTokens:x}}}catch(l){throw v.error({err:l},"llm operation failed"),oe(l)}}};function ne(n){for(let t=n.length-1;t>=0;t--){if(n[t].role!=="user")continue;let e=n[t].content;if(typeof e=="string"&&e.length===0||Array.isArray(e)&&e.length===0)return;typeof e=="string"&&(e=n[t].content=[{type:"text",text:e}]);let o=e[e.length-1];for(let s=e.length-1;s>=0;s--)if(e[s].type!=="image"){o=e[s];break}Reflect.set(o,"cache_control",{type:"ephemeral"});return}}function oe(n){if(n instanceof M.APIError){if(n.status===413||/prompts?\s+too\s+long/i.test(n.message))return new W(`Prompt too long: ${n.message}`);if(n.status===401)return new I(`Invalid API key: ${n.message}`);if(n.status===429){let t=n.headers,e=t instanceof Headers?t.get("retry-after"):void 0,o="Rate Limited";if(e){let s=Number.parseInt(d(e));Number.isNaN(s)?o+=", please wait.":o+=`, retry after ${d(s)}s.`}else o+=", please wait.";return new H(o,e?d(e):void 0)}return new F(`Anthropic API error (${d(n.status)}): ${n.message}`)}return new G(`Network error: ${K(n)}`)}export{Y as AnthropicClient,te as buildAnthropicMessages,he as fetchModelContextWindow,ne as markLastUserTailForCache,Q as markToolsForCache,X as needsToolSearchBeta};