@fugood/buttress-server 2.26.0-beta.3 → 2.26.0-beta.4

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.
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ import{a as e,n as t}from"./config-lP89VahD.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
+
4
+ `);for(;a!==-1;){let e=r.slice(0,a);r=r.slice(a+2),a=r.indexOf(`
5
+
6
+ `);let t=`message`,n=[];for(let r of e.split(`
7
+ `))r.startsWith(`event:`)?t=r.slice(6).trim():r.startsWith(`data:`)&&n.push(r.slice(5).trimStart());n.length>0&&(yield{event:t,data:n.join(`
8
+ `)})}}}finally{t.releaseLock()}}const l=async(e,t,n,{signal:r,onFrame:i})=>{let a=n.sessionId??null,o;try{o=await s(e,`/agents/${encodeURIComponent(t)}/run?stream=1`,{method:`POST`,body:JSON.stringify({prompt:n.prompt,...n.sessionId?{sessionId:n.sessionId}:{},...n.fork?{fork:!0}:{}}),signal:r})}catch(e){if(r?.aborted)return{kind:`aborted`,sessionId:a};throw e}if(!o.ok||!o.body){let e=await o.text().catch(()=>``);return{kind:`error`,message:`Run request failed (${o.status}): ${e.slice(0,400)}`,sessionId:a}}try{for await(let e of c(o.body)){let t;try{t=JSON.parse(e.data)}catch{continue}if(e.event===`session`&&t?.sessionId&&(a=t.sessionId),e.event===`result`)return i({event:e.event,payload:t}),{kind:`result`,result:t};if(e.event===`error`)return i({event:e.event,payload:t}),{kind:`error`,message:t?.message||`run failed`,sessionId:t?.sessionId??a};i({event:e.event,payload:t})}}catch(e){if(r?.aborted)return{kind:`aborted`,sessionId:a};throw e}return r?.aborted?{kind:`aborted`,sessionId:a}:{kind:`disconnected`,sessionId:a}};export{l as i,o as n,s as r,a as t};
@@ -0,0 +1,2 @@
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,mcp_servers:g,max_turns:v,max_tokens_per_run:b,thinking:S,...C}=e;return{name:r,provider:o,modelId:s,modelRef:i,systemPrompt:typeof p==`string`?p:null,systemPromptFile:typeof m==`string`?m:null,tools:c,mcpServers:x(r,g),maxTurns:l,maxTokensPerRun:u,thinking:typeof S==`string`?S:void 0,generation:C}},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};
@@ -23,6 +23,12 @@ export type CallContextOptions = {
23
23
  callId?: string;
24
24
  /** Routes `context.daemons`; absent when the daemon manager is not running. */
25
25
  daemons?: DaemonsHub;
26
+ /**
27
+ * Agent-invocation chain depth of this call (0 = a person/HTTP/MCP caller).
28
+ * Agent-invoked tool calls pass depth+1 so `context.agents.run` can cap
29
+ * function -> agent -> function -> agent recursion.
30
+ */
31
+ agentDepth?: number;
26
32
  };
27
33
  export type ExecuteOptions = CallContextOptions & {
28
34
  /** Aborts the call early (e.g. the HTTP client disconnected). */
@@ -44,5 +50,5 @@ export type FunctionCallHandle = {
44
50
  * + abort race); the daemon manager uses it directly for its long-lived
45
51
  * runs, where the context must outlive the initial invocation.
46
52
  */
47
- export declare const createCallContext: (fn: LoadedFunction, { runtime, functionsConfig, emit, callId, daemons }: CallContextOptions) => FunctionCallHandle;
53
+ export declare const createCallContext: (fn: LoadedFunction, { runtime, functionsConfig, emit, callId, daemons, agentDepth, }: CallContextOptions) => FunctionCallHandle;
48
54
  export declare const executeFunction: (fn: LoadedFunction, input: any, { signal, ...contextOptions }: ExecuteOptions) => Promise<any>;
@@ -19,7 +19,9 @@ export type CallOptions = {
19
19
  signal?: AbortSignal;
20
20
  callId?: string;
21
21
  /** Which API carried the call — recorded in the status history. */
22
- surface?: 'http' | 'sse' | 'mcp';
22
+ surface?: 'http' | 'sse' | 'mcp' | 'agent';
23
+ /** Agent-invocation chain depth (agent tool calls pass their run's depth). */
24
+ agentDepth?: number;
23
25
  };
24
26
  export type FunctionsService = {
25
27
  config: FunctionsConfig;
@@ -13,7 +13,7 @@
13
13
  export type FunctionCallRecord = {
14
14
  timestamp: string;
15
15
  name: string;
16
- surface: 'http' | 'sse' | 'mcp';
16
+ surface: 'http' | 'sse' | 'mcp' | 'agent';
17
17
  durationMs: number;
18
18
  success: boolean;
19
19
  error?: string;
@@ -1,3 +1,4 @@
1
+ import type { AgentsFunctionApi, AgentsService } from '../agent/types';
1
2
  import type { Backend } from '../index';
2
3
  import type { Config } from '../types';
3
4
  import type { VerifiedIdentity } from '../utils/buttressAuth';
@@ -210,6 +211,11 @@ export type FunctionContext = {
210
211
  /** Names of the daemons currently running. */
211
212
  list: () => string[];
212
213
  };
214
+ /**
215
+ * Configured agents (see `[[agents]]`). Every method throws when no agents
216
+ * are configured on this server.
217
+ */
218
+ agents: AgentsFunctionApi;
213
219
  fetch: typeof fetch;
214
220
  log: (...args: unknown[]) => void;
215
221
  emit: FunctionEmit;
@@ -283,6 +289,8 @@ export type FunctionRuntime = {
283
289
  config: Config;
284
290
  /** Carries the Data Bank binding; absent means `context.bank` is unconfigured. */
285
291
  workspaceState?: WorkspaceState;
292
+ /** Agents service; absent means `context.agents` throws. */
293
+ agents?: AgentsService;
286
294
  };
287
295
  /** Metadata an `_auth` file exports alongside its authorize handler. */
288
296
  export type AuthMeta = {
package/lib/index.d.ts CHANGED
@@ -3,10 +3,12 @@ import * as backendCore from '@fugood/buttress-backend-core';
3
3
  import { AutodiscoverService } from './autodiscover';
4
4
  import type { Config } from './types';
5
5
  import { compareVersions } from './utils/update';
6
+ import type { AgentsConfig, AgentsService } from './agent/types';
6
7
  import type { FunctionsConfig, FunctionsService } from './functions';
7
8
  export { startModelDownload } from '@fugood/buttress-backend-core';
8
9
  export { processConfig } from './utils/config';
9
10
  export { resolveFunctionsConfig } from './functions';
11
+ export { resolveAgentsConfig } from './agent/config';
10
12
  export type { FunctionsConfig, FunctionsService } from './functions';
11
13
  export declare const checkForUpdates: () => Promise<string | null>;
12
14
  export { compareVersions };
@@ -21,17 +23,21 @@ export interface StartServerOptions {
21
23
  enableAnthropicMessages?: boolean;
22
24
  /** Resolved `[functions]` config; null/omitted leaves the feature off. */
23
25
  functions?: FunctionsConfig | null;
26
+ /** Resolved `[[agents]]` config; null/omitted leaves the feature off. */
27
+ agents?: AgentsConfig | null;
24
28
  }
25
- export declare const createServer: ({ backend, router, config, enableOpenAICompat, enableAnthropicMessages, functions: functionsConfig, }: StartServerOptions) => Promise<{
29
+ export declare const createServer: ({ backend, router, config, enableOpenAICompat, enableAnthropicMessages, functions: functionsConfig, agents: agentsConfig, }: StartServerOptions) => Promise<{
26
30
  app: AnyElysia;
27
31
  config: Config;
28
32
  functions: FunctionsService | null;
33
+ agents: AgentsService | null;
29
34
  }>;
30
- export declare const startServer: ({ backend, router, config, enableOpenAICompat, enableAnthropicMessages, functions: functionsConfig, }: StartServerOptions) => Promise<{
35
+ export declare const startServer: ({ backend, router, config, enableOpenAICompat, enableAnthropicMessages, functions: functionsConfig, agents: agentsConfig, }: StartServerOptions) => Promise<{
31
36
  app: AnyElysia;
32
37
  port: number;
33
38
  openaiEnabled: boolean;
34
39
  anthropicMessagesEnabled: boolean;
35
40
  functions: FunctionsService | null;
41
+ agents: AgentsService | null;
36
42
  autoDiscover: AutodiscoverService | null;
37
43
  }>;