@fugood/buttress-server 2.26.0-beta.10 → 2.26.0-beta.13
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 +44 -2
- package/config/sample.toml +1 -0
- package/lib/agent/device-auth.d.ts +3 -0
- package/lib/agent/device-tools.d.ts +5 -0
- package/lib/agent/types.d.ts +4 -2
- package/lib/{cli-DrbWX4ea.mjs → cli-CtBbHMYQ.mjs} +2 -2
- package/lib/{client-BCBBen9i.mjs → client-DzfRSFcJ.mjs} +1 -1
- package/lib/{config-lP89VahD.mjs → config-DbRjQnNp.mjs} +1 -1
- package/lib/functions/types.d.ts +1 -2
- package/lib/index.d.ts +2 -3
- package/lib/index.mjs +71 -61
- package/lib/rolldown-runtime-dTnj95Mm.mjs +2 -0
- package/lib/{tui-7B7x6A08.mjs → tui-DbQ0zW-C.mjs} +1 -1
- package/lib/types.d.ts +2 -1
- package/lib/utils/config.d.ts +1 -2
- package/lib/utils/workspaceState.d.ts +8 -0
- package/lib/wrapper-3PX6qE3t.mjs +7 -0
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -69,14 +69,14 @@ The `bricks` CLI is the tool that performs the binding and writes the local stat
|
|
|
69
69
|
### Bind a server to a workspace
|
|
70
70
|
|
|
71
71
|
```bash
|
|
72
|
-
# Pair the local
|
|
72
|
+
# Pair the local server and provision a workspace DevTools token using the current CLI profile
|
|
73
73
|
bricks buttress bind
|
|
74
74
|
|
|
75
75
|
# Override the auto-detected server id, give it a friendly name, or write to a custom state dir
|
|
76
76
|
bricks buttress bind --server-id buttress-mac-studio --name "Studio LLM" --state-dir /etc/buttress
|
|
77
77
|
|
|
78
78
|
# For headless/remote setups: emit state.json to stdout instead of writing to disk
|
|
79
|
-
bricks buttress bind --print > /etc/buttress/state.json
|
|
79
|
+
(umask 077; bricks buttress bind --print > /etc/buttress/state.json)
|
|
80
80
|
```
|
|
81
81
|
|
|
82
82
|
The state file (`~/.bricks-cli/buttress/state.json` by default, or `$BRICKS_BUTTRESS_STATE_DIR`) stores:
|
|
@@ -84,6 +84,15 @@ The state file (`~/.bricks-cli/buttress/state.json` by default, or `$BRICKS_BUTT
|
|
|
84
84
|
- `workspace.id` / `workspace.name` — which workspace this server belongs to
|
|
85
85
|
- `workspace.serverId` — the server's stable id (defaults to `buttress-<machineId>`)
|
|
86
86
|
- `workspace.issuerPublicKey` + `workspace.kid` — Ed25519 SPKI used to verify access tokens
|
|
87
|
+
- `devtools` — workspace-scoped DevTools JWT (`k: "da"`), workspace id and expiry,
|
|
88
|
+
provisioned automatically by `bind` for agent device tools
|
|
89
|
+
|
|
90
|
+
Normal bind/status output reports only token expiry, never the token. The CLI writes
|
|
91
|
+
state with mode `0600`; `--print` includes credentials and must be treated as a secret.
|
|
92
|
+
Rebinding provisions a fresh token (default lifetime: 30 days) and rotates the announce
|
|
93
|
+
key. Issuance failure stops binding before changing the remote announce key. Rebinding
|
|
94
|
+
or unbinding does not revoke previously issued DevTools JWTs; they remain valid until
|
|
95
|
+
expiry or issuer-key rotation.
|
|
87
96
|
|
|
88
97
|
**Restart `bricks-buttress` after binding** for the change to take effect — the state file is read once at startup.
|
|
89
98
|
|
|
@@ -867,6 +876,7 @@ model = "buttress/ggml-org/gpt-oss-20b-GGUF" # split on the FIRST slash: provid
|
|
|
867
876
|
# model = "anthropic/claude-sonnet-5" # any pi-supported provider; API key from env
|
|
868
877
|
system_prompt_file = "./prompts/ops.md" # or inline: system_prompt = "..."
|
|
869
878
|
tools = ["get_server_status", "restart_service"] # local function names (explicit; no wildcard)
|
|
879
|
+
local_devices = false # opt in to LAN scan + device DevTools tools
|
|
870
880
|
max_turns = 30 # assistant↔tool round-trips per run (default 30)
|
|
871
881
|
# max_tokens_per_run = 200000 # per-run token budget; unset = unlimited
|
|
872
882
|
# temperature = 0.2 # unrecognized keys pass through to generation
|
|
@@ -901,6 +911,38 @@ improvise around it. MCP tools get server-qualified names
|
|
|
901
911
|
(`mcp__github__create_issue`); MCP servers connect lazily on the first run and
|
|
902
912
|
fail closed unless marked `optional = true`.
|
|
903
913
|
|
|
914
|
+
**Local devices.** Set `local_devices = true` on an individual `[[agents]]` entry
|
|
915
|
+
(default `false`) to add just two built-in tools, independently of local functions/MCP:
|
|
916
|
+
|
|
917
|
+
- `devices_scan`: the same UDP + HTTP LAN discovery as `bricks devtools scan`,
|
|
918
|
+
including project previews found relative to the Buttress config directory.
|
|
919
|
+
- `devtools`: one action-based tool for screenshots (inline images), brick tree/query,
|
|
920
|
+
tap/key/text input, console capture, JavaScript evaluation, storage overview, and
|
|
921
|
+
raw CDP requests for other supported operations. Reuses the CLI CDP transport and
|
|
922
|
+
shared DevTools operations; no CLI subprocess or separate tool per device/action.
|
|
923
|
+
|
|
924
|
+
For example, scan with `{}` then call `devtools` with
|
|
925
|
+
`{ "action": "tree", "address": "192.168.1.42", "port": 19851 }`.
|
|
926
|
+
After `bricks buttress bind` and a restart, the workspace credential is injected
|
|
927
|
+
server-side automatically when the device advertises the same workspace and issuer.
|
|
928
|
+
The token is validated for signature, kind and expiry before use; inbound Buttress
|
|
929
|
+
JWTs (`k: "ba"`) are never forwarded. Older bindings without the credential, or expired
|
|
930
|
+
credentials, require rebinding and a restart. The server does not read the CLI login
|
|
931
|
+
or renew tokens itself. Local previews can still use advertised inspect credentials;
|
|
932
|
+
explicit `passcode` / `accessToken` arguments remain optional overrides. Calls open/close their own connection;
|
|
933
|
+
avoid parallel calls or other inspectors on the same device (one CDP client at a time).
|
|
934
|
+
|
|
935
|
+
**Security:** enabling this option grants network discovery and device control,
|
|
936
|
+
including arbitrary JavaScript evaluation on reachable authenticated devices. It is
|
|
937
|
+
not read-only or a network sandbox. Automatic authentication requires a **trusted LAN**:
|
|
938
|
+
workspace/issuer hints from device info are not cryptographic peer authentication.
|
|
939
|
+
The stored token is never inserted into model arguments and is redacted from text
|
|
940
|
+
results/errors before truncation. Explicit credentials supplied in arguments still
|
|
941
|
+
enter session transcripts, as do console output and device data; prefer automatic
|
|
942
|
+
authentication and only grant this to trusted agents/callers. Disable with `local_devices = false` and
|
|
943
|
+
restart Buttress. Aborting closes active CDP connections; discovery completes its
|
|
944
|
+
bounded scan before returning the abort result.
|
|
945
|
+
|
|
904
946
|
**Sessions.** Each run returns a `sessionId`; pass it back to continue the
|
|
905
947
|
conversation, or add `fork: true` to branch it into a fresh session. Sessions
|
|
906
948
|
are JSONL files under `sessions_dir`, scoped by agent name (renaming an agent
|
package/config/sample.toml
CHANGED
|
@@ -55,6 +55,7 @@ enabled = true
|
|
|
55
55
|
# model = "buttress/ggml-org/gpt-oss-20b-GGUF" # provider/model-id, split on the FIRST slash
|
|
56
56
|
# system_prompt = "You are an ops automation agent. Use the provided tools."
|
|
57
57
|
# tools = ["host-info"] # local function names (explicit; no wildcard)
|
|
58
|
+
# local_devices = false # opt in: devices_scan + devtools (inspection AND control)
|
|
58
59
|
# max_turns = 30
|
|
59
60
|
# [agents.mcp_servers.example]
|
|
60
61
|
# url = "https://example.com/mcp/"
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { WorkspaceState } from '../utils/workspaceState';
|
|
2
|
+
/** Select only the bound workspace's credential; never reuse an inbound Buttress (k=ba) JWT. */
|
|
3
|
+
export declare const resolveDeviceAccessToken: (address: string, port: number, state?: WorkspaceState, signal?: AbortSignal) => Promise<string | undefined>;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { AgentFunctionTool } from './tools';
|
|
2
|
+
import type { AgentDefinition } from './types';
|
|
3
|
+
import type { WorkspaceState } from '../utils/workspaceState';
|
|
4
|
+
/** Two tools regardless of device count; no remote tool-list expansion or persistent sockets. */
|
|
5
|
+
export declare const buildLocalDeviceTools: (agent: Pick<AgentDefinition, 'localDevices'>, configDir: string, workspaceState?: WorkspaceState) => AgentFunctionTool[];
|
package/lib/agent/types.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Agent feature types: config-defined agents that run pi-agent-core loops
|
|
3
|
-
* inside the buttress-server process, with local functions
|
|
4
|
-
*
|
|
3
|
+
* inside the buttress-server process, with local functions, MCP servers and
|
|
4
|
+
* opt-in device tools, plus config-scoped JSONL sessions.
|
|
5
5
|
*/
|
|
6
6
|
export type AgentMcpServerConfig = {
|
|
7
7
|
/** StreamableHTTP endpoint; mutually exclusive with `command`. */
|
|
@@ -27,6 +27,8 @@ export type AgentDefinition = {
|
|
|
27
27
|
systemPromptFile: string | null;
|
|
28
28
|
/** Local function names exposed as tools (explicit list, no wildcard). */
|
|
29
29
|
tools: string[];
|
|
30
|
+
/** Opt-in LAN discovery and device DevTools access (default false). */
|
|
31
|
+
localDevices: boolean;
|
|
30
32
|
mcpServers: Record<string, AgentMcpServerConfig>;
|
|
31
33
|
/** Assistant↔tool round-trips per run. */
|
|
32
34
|
maxTurns: number;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{a as e,i as t,n,r,t as i}from"./client-
|
|
2
|
+
import{a as e,i as t,n,r,t as i}from"./client-DzfRSFcJ.mjs";import a from"node:readline";const o=`\x1B[2m`,s=`\x1B[33m`,c=`\x1B[31m`,l=`\x1B[0m`,u=e=>{let t={name:null,configArg:null,url:null,token:null,sessionId:null,fork:!1,listSessions:!1,plain:!1};for(let n=0;n<e.length;n+=1){let r=e[n];r===`-h`||r===`--help`?(console.log(`
|
|
3
3
|
bricks-buttress agent — chat with a configured agent on a running server
|
|
4
4
|
|
|
5
5
|
Usage:
|
|
@@ -19,4 +19,4 @@ In the chat: /exit quits, /new starts a fresh session, Ctrl+C aborts the
|
|
|
19
19
|
current run (a second Ctrl+C quits).
|
|
20
20
|
`),process.exit(0)):r===`-c`||r===`--config`?t.configArg=e[++n]??null:r===`--url`?t.url=e[++n]??null:r===`--token`?t.token=e[++n]??null:r===`--session`?t.sessionId=e[++n]??null:r===`--fork`?(t.sessionId=e[++n]??null,t.fork=!0):r===`--sessions`?t.listSessions=!0:r===`--plain`?t.plain=!0:!r.startsWith(`-`)&&!t.name?t.name=r:(console.error(`Unknown argument: ${r}`),process.exit(1))}return t},d=e=>{console.error(`${c}${e}${l}`),process.exit(1)},f=async(t,n,r,i,a,u)=>{let d=i,f=!1,p=!1,m=()=>{f&&=(process.stdout.write(`${l}\n`),!1)},h=await e(t,n,{prompt:r,sessionId:i??void 0,fork:a},{signal:u,onFrame:({event:e,payload:t})=>{if(e===`session`){t.sessionId&&t.sessionId!==d&&(d=t.sessionId,i||console.log(`${o}session ${d}${l}`));return}if(e!==`agent`)return;let n=t?.event;if(t?.type===`message_update`&&n)n.type===`thinking_delta`?(f||=(process.stdout.write(`${o}`),!0),process.stdout.write(n.delta??``),p=!0):n.type===`text_delta`&&(m(),process.stdout.write(n.delta??``),p=!0);else if(t?.type===`tool_execution_start`){m(),p&&process.stdout.write(`
|
|
21
21
|
`);let e=JSON.stringify(t.args??{});console.log(`${s}⚙ ${t.toolName}${l}${o}(${e.length>160?`${e.slice(0,160)}…`:e})${l}`),p=!1}else t?.type===`tool_execution_end`?console.log(t.isError?`${c}✗ ${t.toolName} failed${l}`:`${o}✓ ${t.toolName}${l}`):t?.type===`tool_emit`&&console.log(`${o} ${t.toolName} → ${t.event}${l}`)}});if(m(),h.kind===`result`){let e=h.result;d=e.sessionId??d;let t=e.usage||{};return process.stdout.write(`
|
|
22
|
-
`),console.log(`${o}— ${e.stopReason} · ${t.totalTurns??`?`} turn(s) · ${t.input??0} in / ${t.output??0} out · session ${d}${l}`),{sessionId:d,ok:!0}}return h.kind===`aborted`?(console.log(`\n${s}(aborted)${l}`),{sessionId:h.sessionId??d,ok:!1}):h.kind===`error`?(console.error(`\n${c}Run failed: ${h.message}${l}`),{sessionId:h.sessionId??d,ok:!1}):(console.error(`\n${c}Connection lost mid-run.${l}`),{sessionId:h.sessionId??d,ok:!1})},p=async(e,t,n,r)=>{let s=n,u=r;console.log(`[1m${t}${l} @ ${e.baseUrl}`+(s?` ${o}(${u?`forking`:`continuing`} ${s})${l}`:``)),console.log(`${o}/exit to quit, /new for a fresh session, Ctrl+C aborts a running turn${l}`);let d=a.createInterface({input:process.stdin,output:process.stdout}),p=!1;d.on(`close`,()=>{p=!0});let m=e=>new Promise(t=>{if(p){t(null);return}d.question(e,t),d.once(`close`,()=>t(null))}),h=null;for(d.on(`SIGINT`,()=>{h?(h.abort(),h=null):(d.close(),process.exit(0))});;){let n=await m(`[36m> ${l}`);if(n==null)break;let r=n.trim();if(!r)continue;if(r===`/exit`||r===`/quit`)break;if(r===`/new`){s=null,u=!1,console.log(`${o}Started a fresh session.${l}`);continue}h=new AbortController;let a=s;try{let n=await f(e,t,r,s,u,h.signal);n.sessionId&&(s=n.sessionId),i(a,s)&&(u=!1)}catch(e){console.error(`${c}${e?.message||e}${l}`)}finally{h=null}}d.close()},m=async e=>{let i=u(e),{config:a,agentsConfig:s}=n(i.configArg),c=a.server.port||2080,f=(i.url||`http://127.0.0.1:${c}`).replace(/\/$/,``),m={baseUrl:f,token:i.token||(s?r(s.runtimeTokenFile):null)||process.env.BUTTRESS_AGENT_TOKEN||null},h=await t(m,`/agents`).catch(e=>d(`Cannot reach ${f}: ${e?.message||e} (is the server running?)`));if(!h.ok){let e=await h.text().catch(()=>``);d(`Server rejected the request (${h.status}): ${e.slice(0,300)}`)}let{agents:g}=await h.json();if(!i.name){console.log(`Configured agents on ${f}:`);for(let e of g)console.log(`- ${e}`);g.length===0&&console.log(`(none — add [[agents]] to the server config)`);return}if(g.includes(i.name)||d(`Unknown agent "${i.name}" (configured: ${g.join(`, `)||`none`})`),i.listSessions){let{sessions:e}=await(await t(m,`/agents/${encodeURIComponent(i.name)}/sessions`)).json();if(!e?.length){console.log(`No sessions yet.`);return}for(let t of e)console.log(`${t.sessionId} ${o}${t.updatedAt}${l}${t.parentSession?` ${o}(fork of ${t.parentSession})${l}`:``}`),t.preview&&console.log(` ${o}${t.preview}${l}`);return}if(process.stdin.isTTY&&process.stdout.isTTY&&!i.plain){let{runAgentTui:e}=await import(`./tui-
|
|
22
|
+
`),console.log(`${o}— ${e.stopReason} · ${t.totalTurns??`?`} turn(s) · ${t.input??0} in / ${t.output??0} out · session ${d}${l}`),{sessionId:d,ok:!0}}return h.kind===`aborted`?(console.log(`\n${s}(aborted)${l}`),{sessionId:h.sessionId??d,ok:!1}):h.kind===`error`?(console.error(`\n${c}Run failed: ${h.message}${l}`),{sessionId:h.sessionId??d,ok:!1}):(console.error(`\n${c}Connection lost mid-run.${l}`),{sessionId:h.sessionId??d,ok:!1})},p=async(e,t,n,r)=>{let s=n,u=r;console.log(`[1m${t}${l} @ ${e.baseUrl}`+(s?` ${o}(${u?`forking`:`continuing`} ${s})${l}`:``)),console.log(`${o}/exit to quit, /new for a fresh session, Ctrl+C aborts a running turn${l}`);let d=a.createInterface({input:process.stdin,output:process.stdout}),p=!1;d.on(`close`,()=>{p=!0});let m=e=>new Promise(t=>{if(p){t(null);return}d.question(e,t),d.once(`close`,()=>t(null))}),h=null;for(d.on(`SIGINT`,()=>{h?(h.abort(),h=null):(d.close(),process.exit(0))});;){let n=await m(`[36m> ${l}`);if(n==null)break;let r=n.trim();if(!r)continue;if(r===`/exit`||r===`/quit`)break;if(r===`/new`){s=null,u=!1,console.log(`${o}Started a fresh session.${l}`);continue}h=new AbortController;let a=s;try{let n=await f(e,t,r,s,u,h.signal);n.sessionId&&(s=n.sessionId),i(a,s)&&(u=!1)}catch(e){console.error(`${c}${e?.message||e}${l}`)}finally{h=null}}d.close()},m=async e=>{let i=u(e),{config:a,agentsConfig:s}=n(i.configArg),c=a.server.port||2080,f=(i.url||`http://127.0.0.1:${c}`).replace(/\/$/,``),m={baseUrl:f,token:i.token||(s?r(s.runtimeTokenFile):null)||process.env.BUTTRESS_AGENT_TOKEN||null},h=await t(m,`/agents`).catch(e=>d(`Cannot reach ${f}: ${e?.message||e} (is the server running?)`));if(!h.ok){let e=await h.text().catch(()=>``);d(`Server rejected the request (${h.status}): ${e.slice(0,300)}`)}let{agents:g}=await h.json();if(!i.name){console.log(`Configured agents on ${f}:`);for(let e of g)console.log(`- ${e}`);g.length===0&&console.log(`(none — add [[agents]] to the server config)`);return}if(g.includes(i.name)||d(`Unknown agent "${i.name}" (configured: ${g.join(`, `)||`none`})`),i.listSessions){let{sessions:e}=await(await t(m,`/agents/${encodeURIComponent(i.name)}/sessions`)).json();if(!e?.length){console.log(`No sessions yet.`);return}for(let t of e)console.log(`${t.sessionId} ${o}${t.updatedAt}${l}${t.parentSession?` ${o}(fork of ${t.parentSession})${l}`:``}`),t.preview&&console.log(` ${o}${t.preview}${l}`);return}if(process.stdin.isTTY&&process.stdout.isTTY&&!i.plain){let{runAgentTui:e}=await import(`./tui-DbQ0zW-C.mjs`);await e({connection:m,agentName:i.name,sessionId:i.sessionId,fork:i.fork});return}await p(m,i.name,i.sessionId,i.fork)};export{m as runAgentCommand};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{a as e,n as t}from"./config-
|
|
2
|
+
import{a as e,n as t}from"./config-DbRjQnNp.mjs";import n from"node:path";import r from"node:fs";import i from"@iarna/toml";const a=a=>{let o={},s=process.cwd();a&&(r.existsSync(a)?(o=i.parse(r.readFileSync(a,`utf8`)),s=n.dirname(n.resolve(a))):o=i.parse(a));let c=e(o);return{config:c,configDir:s,agentsConfig:t(c,{configDir:s})}},o=e=>{try{return r.readFileSync(e,`utf8`).trim()||null}catch{return null}},s=async(e,t,n)=>fetch(`${e.baseUrl}${t}`,{...n,headers:{"content-type":`application/json`,...e.token?{authorization:`Bearer ${e.token}`}:{},...n?.headers}});async function*c(e){let t=e.getReader(),n=new TextDecoder,r=``;try{for(;;){let{done:e,value:i}=await t.read();if(e)break;r+=n.decode(i,{stream:!0});let a=r.indexOf(`
|
|
3
3
|
|
|
4
4
|
`);for(;a!==-1;){let e=r.slice(0,a);r=r.slice(a+2),a=r.indexOf(`
|
|
5
5
|
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import e from"node:path";import t from"node:os";import n from"bytes";import r from"ms";import i from"node-machine-id";const a=(e={},t={})=>{let n=Array.isArray(e)?[...e]:{...e};return Object.entries(t||{}).forEach(([e,t])=>{t&&typeof t==`object`&&!Array.isArray(t)?n[e]=a(n[e]||{},t):n[e]=t}),n},o=e=>e&&typeof e==`object`?structuredClone(e):null,s=(e,t)=>{let n=o(e)||{},r=o(t)||{};return a(n,r)},c=(e,t)=>a(structuredClone(e.global),t||{}),l=(e,t,n,r)=>{if(e.generators.length>0){let i=e.generators.filter(e=>e?.type===n);if(i.length>0&&r){let a=i.find(e=>t.getModelIdentifier(n,e)===r);if(a)return c(e,a)}}return Object.keys(e.global).length>0?c(e,{}):null},u={udp:{port:8089,announcements:{enabled:!0,interval:5e3},requests:{enabled:!0,responseDelay:100}},http:{enabled:!0,path:`/buttress/info`,cors:!0}},d=e=>e?e===!0?{...u}:a(u,e):null,f=(e,t)=>{if(!e.generators||e.generators.length===0)return t.map(e=>({type:e}));let n=new Set;return e.generators.forEach(e=>{e.type&&n.add(e.type)}),n.size===0?t.map(e=>({type:e})):Array.from(n).map(e=>({type:e}))},p=(e,t,n)=>e===void 0?n:typeof e==`number`?e:t(e)??n,m=6e4,h=1024*1024*50,g=s=>{let c=i.machineIdSync(),l={server:{id:`buttress-${c}`,name:`Buttress Server (${c.slice(-8)})`,port:2080,temp_file_dir:e.join(t.tmpdir(),`.buttress`),session_timeout:m,max_body_size:h},autodiscover:!1},u=a(l,o(s)||{}),f=Array.isArray(u.generators)?u.generators:[],{server:g,generators:_,autodiscover:v,...y}=u;return{autodiscover:d(v),server:{id:g.id,name:g.name,port:g.port,log_level:g.log_level,temp_file_dir:g.temp_file_dir,max_body_size:p(g.max_body_size,n.parse,h),session_timeout:p(g.session_timeout,e=>r(e),m)},global:y,generators:f}},_=/^[A-Za-z0-9_-]{1,64}$/,v=n=>n===`~`||n.startsWith(`~/`)?e.join(t.homedir(),n.slice(1)):n;var y=class extends Error{constructor(e){super(`[Agents] ${e}`),this.name=`AgentsConfigError`}};const b=(e,t,n)=>{if(t==null)return n;if(t===0||t===!1)return null;if(typeof t==`number`){if(Number.isFinite(t)&&t>0)return t}else if(typeof t==`string`){let e;try{e=r(t)}catch{e=void 0}if(typeof e==`number`&&e>0)return e}throw new y(`agents_options.${e} must be a duration string (e.g. "30d") or a positive number of milliseconds; use 0 to disable it`)},x=(e,t)=>{if(t==null)return{};if(typeof t!=`object`||Array.isArray(t))throw new y(`agent "${e}": mcp_servers must be a table`);let n={};for(let[r,i]of Object.entries(t)){if(!_.test(r))throw new y(`agent "${e}": MCP server name "${r}" must match ${_}`);let t=!!(typeof i?.url==`string`&&i.url),a=!!(typeof i?.command==`string`&&i.command);if(t===a)throw new y(`agent "${e}": MCP server "${r}" needs exactly one of url / command`);n[r]={url:t?i.url:void 0,headers:i?.headers&&typeof i.headers==`object`?i.headers:void 0,command:a?i.command:void 0,args:Array.isArray(i?.args)?i.args.map(String):void 0,env:i?.env&&typeof i.env==`object`?i.env:void 0,optional:i?.optional===!0}}return n},S=(e,t,n)=>{let r=e?.name;if(typeof r!=`string`||!_.test(r))throw new y(`[[agents]] #${t+1}: name is required and must match ${_}`);let i=e?.model;if(typeof i!=`string`||!i.includes(`/`))throw new y(`agent "${r}": model must be "provider/model-id" (e.g. "buttress/ggml-org/gpt-oss-20b-GGUF")`);let a=i.indexOf(`/`),o=i.slice(0,a),s=i.slice(a+1);if(!o||!s)throw new y(`agent "${r}": invalid model reference "${i}"`);if(o===`buttress`&&!n.generators.filter(e=>e.type===`ggml-llm`||e.type===`mlx-llm`).some(e=>(e.model?.repo_id||e.model?.repository)===s))throw new y(`agent "${r}": model "${s}" does not match any configured [[generators]] ggml-llm/mlx-llm repo_id`);if(e?.system_prompt!=null&&e?.system_prompt_file!=null)throw new y(`agent "${r}": set system_prompt or system_prompt_file, not both`);let c=[];if(e?.tools!=null){if(!Array.isArray(e.tools))throw new y(`agent "${r}": tools must be an array of function names`);for(let t of e.tools){if(typeof t!=`string`||!_.test(t))throw new y(`agent "${r}": tool name "${t}" must match ${_} (local function names only)`);if(c.includes(t))throw new y(`agent "${r}": duplicate tool "${t}"`);c.push(t)}}let l=Number(e?.max_turns??30);if(!Number.isInteger(l)||l<1)throw new y(`agent "${r}": max_turns must be a positive integer`);let u=null;if(e?.max_tokens_per_run!=null&&(u=Number(e.max_tokens_per_run),!Number.isInteger(u)||u<1))throw new y(`agent "${r}": max_tokens_per_run must be a positive integer`);let{name:d,model:f,system_prompt:p,system_prompt_file:m,tools:h,
|
|
2
|
+
import e from"node:path";import t from"node:os";import n from"bytes";import r from"ms";import i from"node-machine-id";const a=(e={},t={})=>{let n=Array.isArray(e)?[...e]:{...e};return Object.entries(t||{}).forEach(([e,t])=>{t&&typeof t==`object`&&!Array.isArray(t)?n[e]=a(n[e]||{},t):n[e]=t}),n},o=e=>e&&typeof e==`object`?structuredClone(e):null,s=(e,t)=>{let n=o(e)||{},r=o(t)||{};return a(n,r)},c=(e,t)=>a(structuredClone(e.global),t||{}),l=(e,t,n,r)=>{if(e.generators.length>0){let i=e.generators.filter(e=>e?.type===n);if(i.length>0&&r){let a=i.find(e=>t.getModelIdentifier(n,e)===r);if(a)return c(e,a)}}return Object.keys(e.global).length>0?c(e,{}):null},u={udp:{port:8089,announcements:{enabled:!0,interval:5e3},requests:{enabled:!0,responseDelay:100}},http:{enabled:!0,path:`/buttress/info`,cors:!0}},d=e=>e?e===!0?{...u}:a(u,e):null,f=(e,t)=>{if(!e.generators||e.generators.length===0)return t.map(e=>({type:e}));let n=new Set;return e.generators.forEach(e=>{e.type&&n.add(e.type)}),n.size===0?t.map(e=>({type:e})):Array.from(n).map(e=>({type:e}))},p=(e,t,n)=>e===void 0?n:typeof e==`number`?e:t(e)??n,m=6e4,h=1024*1024*50,g=s=>{let c=i.machineIdSync(),l={server:{id:`buttress-${c}`,name:`Buttress Server (${c.slice(-8)})`,port:2080,temp_file_dir:e.join(t.tmpdir(),`.buttress`),session_timeout:m,max_body_size:h},autodiscover:!1},u=a(l,o(s)||{}),f=Array.isArray(u.generators)?u.generators:[],{server:g,generators:_,autodiscover:v,...y}=u;return{autodiscover:d(v),server:{id:g.id,name:g.name,port:g.port,log_level:g.log_level,temp_file_dir:g.temp_file_dir,max_body_size:p(g.max_body_size,n.parse,h),session_timeout:p(g.session_timeout,e=>r(e),m)},global:y,generators:f}},_=/^[A-Za-z0-9_-]{1,64}$/,v=n=>n===`~`||n.startsWith(`~/`)?e.join(t.homedir(),n.slice(1)):n;var y=class extends Error{constructor(e){super(`[Agents] ${e}`),this.name=`AgentsConfigError`}};const b=(e,t,n)=>{if(t==null)return n;if(t===0||t===!1)return null;if(typeof t==`number`){if(Number.isFinite(t)&&t>0)return t}else if(typeof t==`string`){let e;try{e=r(t)}catch{e=void 0}if(typeof e==`number`&&e>0)return e}throw new y(`agents_options.${e} must be a duration string (e.g. "30d") or a positive number of milliseconds; use 0 to disable it`)},x=(e,t)=>{if(t==null)return{};if(typeof t!=`object`||Array.isArray(t))throw new y(`agent "${e}": mcp_servers must be a table`);let n={};for(let[r,i]of Object.entries(t)){if(!_.test(r))throw new y(`agent "${e}": MCP server name "${r}" must match ${_}`);let t=!!(typeof i?.url==`string`&&i.url),a=!!(typeof i?.command==`string`&&i.command);if(t===a)throw new y(`agent "${e}": MCP server "${r}" needs exactly one of url / command`);n[r]={url:t?i.url:void 0,headers:i?.headers&&typeof i.headers==`object`?i.headers:void 0,command:a?i.command:void 0,args:Array.isArray(i?.args)?i.args.map(String):void 0,env:i?.env&&typeof i.env==`object`?i.env:void 0,optional:i?.optional===!0}}return n},S=(e,t,n)=>{let r=e?.name;if(typeof r!=`string`||!_.test(r))throw new y(`[[agents]] #${t+1}: name is required and must match ${_}`);let i=e?.model;if(typeof i!=`string`||!i.includes(`/`))throw new y(`agent "${r}": model must be "provider/model-id" (e.g. "buttress/ggml-org/gpt-oss-20b-GGUF")`);let a=i.indexOf(`/`),o=i.slice(0,a),s=i.slice(a+1);if(!o||!s)throw new y(`agent "${r}": invalid model reference "${i}"`);if(o===`buttress`&&!n.generators.filter(e=>e.type===`ggml-llm`||e.type===`mlx-llm`).some(e=>(e.model?.repo_id||e.model?.repository)===s))throw new y(`agent "${r}": model "${s}" does not match any configured [[generators]] ggml-llm/mlx-llm repo_id`);if(e?.system_prompt!=null&&e?.system_prompt_file!=null)throw new y(`agent "${r}": set system_prompt or system_prompt_file, not both`);let c=[];if(e?.tools!=null){if(!Array.isArray(e.tools))throw new y(`agent "${r}": tools must be an array of function names`);for(let t of e.tools){if(typeof t!=`string`||!_.test(t))throw new y(`agent "${r}": tool name "${t}" must match ${_} (local function names only)`);if(c.includes(t))throw new y(`agent "${r}": duplicate tool "${t}"`);c.push(t)}}if(e?.local_devices!==void 0&&typeof e.local_devices!=`boolean`)throw new y(`agent "${r}": local_devices must be a boolean`);let l=Number(e?.max_turns??30);if(!Number.isInteger(l)||l<1)throw new y(`agent "${r}": max_turns must be a positive integer`);let u=null;if(e?.max_tokens_per_run!=null&&(u=Number(e.max_tokens_per_run),!Number.isInteger(u)||u<1))throw new y(`agent "${r}": max_tokens_per_run must be a positive integer`);let{name:d,model:f,system_prompt:p,system_prompt_file:m,tools:h,local_devices:g=!1,mcp_servers:v,max_turns:b,max_tokens_per_run:S,thinking:C,...w}=e;return{name:r,provider:o,modelId:s,modelRef:i,systemPrompt:typeof p==`string`?p:null,systemPromptFile:typeof m==`string`?m:null,tools:c,localDevices:g,mcpServers:x(r,v),maxTurns:l,maxTokensPerRun:u,thinking:typeof C==`string`?C:void 0,generation:w}},C=(t,{configDir:n=process.cwd()}={})=>{let r=t.global||{},i=r.agents;if(i==null)return null;if(!Array.isArray(i))throw new y(`agents must be an array of tables ([[agents]])`);if(i.length===0)return null;let a=i.map((e,n)=>S(e,n,t)),o=new Set;for(let e of a){if(o.has(e.name))throw new y(`duplicate agent name "${e.name}"`);o.add(e.name)}let s=r.agents_options||{},c=typeof s.sessions_dir==`string`&&s.sessions_dir?s.sessions_dir:`./.buttress-agent/sessions`,l=v(c),u=e.isAbsolute(l)?l:e.resolve(n,l);for(let t of a)if(t.systemPromptFile){let r=v(t.systemPromptFile);t.systemPromptFile=e.isAbsolute(r)?r:e.resolve(n,r)}let d=Number(s.max_depth??2);if(!Number.isInteger(d)||d<1)throw new y(`agents_options.max_depth must be a positive integer`);let f=s.session_max_count,p=500;if(f===0||f===!1)p=null;else if(f!=null&&(p=Number(f),!Number.isInteger(p)||p<1))throw new y(`agents_options.session_max_count must be a positive integer`);return{agents:a,configDir:n,sessionsDir:u,runtimeTokenFile:e.join(e.dirname(u),`runtime-token`),sessionMaxAgeMs:b(`session_max_age`,s.session_max_age,2592e6),sessionMaxCount:p,maxDepth:d,allowUnauthenticated:s.allow_unauthenticated===!0}};export{g as a,o as i,C as n,l as o,s as r,f as s,y as t};
|
package/lib/functions/types.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { AgentsFunctionApi, AgentsService } from '../agent/types';
|
|
2
|
-
import type { Backend } from '../
|
|
3
|
-
import type { Config } from '../types';
|
|
2
|
+
import type { Backend, Config } from '../types';
|
|
4
3
|
import type { VerifiedIdentity } from '../utils/buttressAuth';
|
|
5
4
|
import type { WorkspaceState } from '../utils/workspaceState';
|
|
6
5
|
/**
|
package/lib/index.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { AnyElysia } from 'elysia';
|
|
2
|
-
import * as backendCore from '@fugood/buttress-backend-core';
|
|
3
2
|
import { AutodiscoverService } from './autodiscover';
|
|
4
|
-
import type { Config } from './types';
|
|
3
|
+
import type { Backend, Config } from './types';
|
|
5
4
|
import { compareVersions } from './utils/update';
|
|
6
5
|
import type { AgentsConfig, AgentsService } from './agent/types';
|
|
7
6
|
import type { FunctionsConfig, FunctionsService } from './functions';
|
|
@@ -14,7 +13,7 @@ export declare const checkForUpdates: () => Promise<string | null>;
|
|
|
14
13
|
export { compareVersions };
|
|
15
14
|
export declare const logUpdateMessage: (latestVersion: string) => void;
|
|
16
15
|
export declare const checkAndNotifyUpdates: () => Promise<void>;
|
|
17
|
-
export type Backend
|
|
16
|
+
export type { Backend } from './types';
|
|
18
17
|
export interface StartServerOptions {
|
|
19
18
|
backend?: Backend;
|
|
20
19
|
router?: AnyElysia;
|