@fugood/buttress-server 2.25.6 → 2.25.8
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 +26 -0
- package/config/sample.toml +1 -0
- package/lib/agent/device-tools.d.ts +4 -0
- package/lib/agent/session-fs.d.ts +3 -42
- package/lib/agent/sessions.d.ts +4 -2
- 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 +73 -66
- 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/wrapper-3PX6qE3t.mjs +7 -0
- package/package.json +6 -4
package/README.md
CHANGED
|
@@ -867,6 +867,7 @@ model = "buttress/ggml-org/gpt-oss-20b-GGUF" # split on the FIRST slash: provid
|
|
|
867
867
|
# model = "anthropic/claude-sonnet-5" # any pi-supported provider; API key from env
|
|
868
868
|
system_prompt_file = "./prompts/ops.md" # or inline: system_prompt = "..."
|
|
869
869
|
tools = ["get_server_status", "restart_service"] # local function names (explicit; no wildcard)
|
|
870
|
+
local_devices = false # opt in to LAN scan + device DevTools tools
|
|
870
871
|
max_turns = 30 # assistant↔tool round-trips per run (default 30)
|
|
871
872
|
# max_tokens_per_run = 200000 # per-run token budget; unset = unlimited
|
|
872
873
|
# temperature = 0.2 # unrecognized keys pass through to generation
|
|
@@ -901,6 +902,31 @@ improvise around it. MCP tools get server-qualified names
|
|
|
901
902
|
(`mcp__github__create_issue`); MCP servers connect lazily on the first run and
|
|
902
903
|
fail closed unless marked `optional = true`.
|
|
903
904
|
|
|
905
|
+
**Local devices.** Set `local_devices = true` on an individual `[[agents]]` entry
|
|
906
|
+
(default `false`) to add just two built-in tools, independently of local functions/MCP:
|
|
907
|
+
|
|
908
|
+
- `devices_scan`: the same UDP + HTTP LAN discovery as `bricks devtools scan`,
|
|
909
|
+
including project previews found relative to the Buttress config directory.
|
|
910
|
+
- `devtools`: one action-based tool for screenshots (inline images), brick tree/query,
|
|
911
|
+
tap/key/text input, console capture, JavaScript evaluation, storage overview, and
|
|
912
|
+
raw CDP requests for other supported operations. Reuses the CLI CDP transport and
|
|
913
|
+
shared DevTools operations; no CLI subprocess or separate tool per device/action.
|
|
914
|
+
|
|
915
|
+
For example, scan with `{}` then call `devtools` with
|
|
916
|
+
`{ "action": "tree", "address": "192.168.1.42", "port": 19851 }`.
|
|
917
|
+
Supply `passcode` or a workspace DevTools `accessToken` if required; otherwise the
|
|
918
|
+
transport uses the device's advertised Chrome Inspect credential. Buttress does **not**
|
|
919
|
+
read the host's CLI login or mint tokens. Calls open/close their own connection;
|
|
920
|
+
avoid parallel calls or other inspectors on the same device (one CDP client at a time).
|
|
921
|
+
|
|
922
|
+
**Security:** enabling this option grants network discovery and device control,
|
|
923
|
+
including arbitrary JavaScript evaluation on reachable authenticated devices. It is
|
|
924
|
+
not read-only or a network sandbox. Tool arguments/results (including credentials,
|
|
925
|
+
console output and device data) enter model context and persisted session transcripts;
|
|
926
|
+
only grant this to trusted agents/callers. Disable with `local_devices = false` and
|
|
927
|
+
restart Buttress. Aborting closes active CDP connections; discovery completes its
|
|
928
|
+
bounded scan before returning the abort result.
|
|
929
|
+
|
|
904
930
|
**Sessions.** Each run returns a `sessionId`; pass it back to continue the
|
|
905
931
|
conversation, or add `fork: true` to branch it into a fresh session. Sessions
|
|
906
932
|
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,4 @@
|
|
|
1
|
+
import type { AgentFunctionTool } from './tools';
|
|
2
|
+
import type { AgentDefinition } from './types';
|
|
3
|
+
/** Two tools regardless of device count; no remote tool-list expansion or persistent sockets. */
|
|
4
|
+
export declare const buildLocalDeviceTools: (agent: Pick<AgentDefinition, 'localDevices'>, configDir: string) => AgentFunctionTool[];
|
|
@@ -1,42 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* Minimal FileSystem implementation for the jsonl session repo, over node:fs.
|
|
5
|
-
*
|
|
6
|
-
* pi's NodeExecutionEnv classifies fs failures with `error instanceof Error`,
|
|
7
|
-
* which breaks in vm-realm hosts (jest runs test code in a vm context, so
|
|
8
|
-
* node-core errors come from another realm and ENOENT stops mapping to
|
|
9
|
-
* not_found). This implementation reads `error.code` structurally — and also
|
|
10
|
-
* skips pulling the exec-capable execution env into the server for what is
|
|
11
|
-
* purely file storage.
|
|
12
|
-
*/
|
|
13
|
-
type FileResult<T> = Promise<Result<T, FileError>>;
|
|
14
|
-
declare const toFileInfo: (target: string, stats: import('node:fs').Stats) => {
|
|
15
|
-
name: string;
|
|
16
|
-
path: string;
|
|
17
|
-
kind: "directory" | "file" | "symlink";
|
|
18
|
-
size: number;
|
|
19
|
-
mtimeMs: number;
|
|
20
|
-
};
|
|
21
|
-
/** The `Pick<FileSystem, …>` surface JsonlSessionRepo requires. */
|
|
22
|
-
export declare const createSessionFs: (rootDir: string) => {
|
|
23
|
-
absolutePath: (target: string) => FileResult<string>;
|
|
24
|
-
joinPath: (parts: string[]) => FileResult<string>;
|
|
25
|
-
readTextFile: (target: string) => FileResult<string>;
|
|
26
|
-
readTextLines: (target: string, options?: {
|
|
27
|
-
maxLines?: number;
|
|
28
|
-
}) => FileResult<string[]>;
|
|
29
|
-
writeFile: (target: string, content: string | Uint8Array) => FileResult<void>;
|
|
30
|
-
appendFile: (target: string, content: string | Uint8Array) => FileResult<void>;
|
|
31
|
-
renameFile: (source: string, destination: string) => FileResult<void>;
|
|
32
|
-
fileInfo: (target: string) => FileResult<ReturnType<typeof toFileInfo>>;
|
|
33
|
-
listDir: (target: string) => FileResult<ReturnType<typeof toFileInfo>[]>;
|
|
34
|
-
exists: (target: string) => FileResult<boolean>;
|
|
35
|
-
createDir: (target: string, options?: {
|
|
36
|
-
recursive?: boolean;
|
|
37
|
-
}) => FileResult<void>;
|
|
38
|
-
remove: (target: string, options?: {
|
|
39
|
-
recursive?: boolean;
|
|
40
|
-
}) => FileResult<void>;
|
|
41
|
-
};
|
|
42
|
-
export {};
|
|
1
|
+
import type { FileSystem } from '@earendil-works/pi-agent-core';
|
|
2
|
+
/** File storage for agent sessions that classifies errors structurally across vm realms. */
|
|
3
|
+
export declare const createSessionFs: (rootDir: string) => FileSystem;
|
package/lib/agent/sessions.d.ts
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
|
-
import { Session } from '@earendil-works/pi-agent-core';
|
|
2
|
-
import type { AgentMessage } from '@earendil-works/pi-agent-core';
|
|
1
|
+
import type { AgentMessage, Session } from '@earendil-works/pi-agent-core';
|
|
3
2
|
import type { AgentSessionSummary, AgentsConfig } from './types';
|
|
4
3
|
export type AgentSessionStore = {
|
|
5
4
|
create: (agentName: string) => Promise<Session<any>>;
|
|
6
5
|
/** Throws when the id is unknown within the agent's scope. */
|
|
7
6
|
open: (agentName: string, sessionId: string) => Promise<Session<any>>;
|
|
8
7
|
fork: (agentName: string, sessionId: string) => Promise<Session<any>>;
|
|
8
|
+
append: (session: Session<any>, message: AgentMessage) => Promise<string>;
|
|
9
|
+
close: (session: Session<any>) => Promise<void>;
|
|
9
10
|
list: (agentName: string, limit?: number) => Promise<AgentSessionSummary[]>;
|
|
10
11
|
/** Reconstruct the pi message history for continuing a session. */
|
|
11
12
|
messages: (session: Session<any>) => Promise<AgentMessage[]>;
|
|
12
13
|
/** Retention sweep across every configured agent scope. */
|
|
13
14
|
sweep: (agentNames: string[]) => Promise<number>;
|
|
15
|
+
dispose: () => Promise<void>;
|
|
14
16
|
};
|
|
15
17
|
export declare const createAgentSessionStore: (config: AgentsConfig) => AgentSessionStore;
|
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;
|