@fugood/buttress-server 2.26.0-beta.2 → 2.26.0-beta.21

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 (70) hide show
  1. package/README.md +415 -10
  2. package/config/function-samples/README.md +1 -0
  3. package/config/function-samples/classify-ticket.ts +73 -0
  4. package/config/function-samples/run-agent.ts +39 -0
  5. package/config/sample.toml +67 -0
  6. package/lib/agent/cli.d.ts +19 -0
  7. package/lib/agent/client.d.ts +66 -0
  8. package/lib/agent/codemode-worker.d.ts +1 -0
  9. package/lib/agent/codemode.d.ts +7 -0
  10. package/lib/agent/config.d.ts +15 -0
  11. package/lib/agent/context.d.ts +11 -0
  12. package/lib/agent/decisions.check.d.ts +1 -0
  13. package/lib/agent/decisions.d.ts +113 -0
  14. package/lib/agent/device-auth.d.ts +3 -0
  15. package/lib/agent/device-tools.d.ts +5 -0
  16. package/lib/agent/loopback.d.ts +21 -0
  17. package/lib/agent/mcp.d.ts +22 -0
  18. package/lib/agent/models.d.ts +22 -0
  19. package/lib/agent/service.d.ts +16 -0
  20. package/lib/agent/sessions.d.ts +30 -0
  21. package/lib/agent/tools.d.ts +35 -0
  22. package/lib/agent/tui.d.ts +17 -0
  23. package/lib/agent/types.d.ts +155 -0
  24. package/lib/cli-KbjOekMI.mjs +22 -0
  25. package/lib/client-qqI-HCOj.mjs +8 -0
  26. package/lib/codemode-Cva7OeQX.mjs +112 -0
  27. package/lib/codemode-worker.mjs +3 -0
  28. package/lib/command-nXCdWBlc.mjs +38 -0
  29. package/lib/config-DiYweJ2m.mjs +2 -0
  30. package/lib/functions/executor.d.ts +7 -1
  31. package/lib/functions/index.d.ts +3 -1
  32. package/lib/functions/status.d.ts +1 -1
  33. package/lib/functions/templates.d.ts +1 -1
  34. package/lib/functions/types.d.ts +15 -2
  35. package/lib/index.d.ts +12 -5
  36. package/lib/index.mjs +146 -93
  37. package/lib/mcp-DzknURyz.mjs +5 -0
  38. package/lib/mlx-bridge.py +707 -0
  39. package/lib/net-OvedjqrB.mjs +46 -0
  40. package/lib/protocol-WkPByalT.mjs +468 -0
  41. package/lib/quickjs.wasm +0 -0
  42. package/lib/rolldown-runtime-dTnj95Mm.mjs +2 -0
  43. package/lib/routes/agents.d.ts +66 -0
  44. package/lib/routes/decision-shared.d.ts +28 -0
  45. package/lib/routes/generator-cache.d.ts +3 -1
  46. package/lib/routes/index.d.ts +2 -0
  47. package/lib/routes/llm-shared.d.ts +8 -0
  48. package/lib/routes/openai-compat.d.ts +13 -2
  49. package/lib/routes/openai-decisions.check.d.ts +1 -0
  50. package/lib/routes/openai-decisions.d.ts +97 -0
  51. package/lib/routes/status.d.ts +2 -1
  52. package/lib/routes/typesafe-compat.check.d.ts +1 -0
  53. package/lib/routes/typesafe-compat.d.ts +30 -0
  54. package/lib/services/ggml-decision.d.ts +80 -0
  55. package/lib/services/ggml-llm.d.ts +29 -1
  56. package/lib/services/index.d.ts +3 -0
  57. package/lib/tui-ClNupsCH.mjs +2 -0
  58. package/lib/types.d.ts +48 -2
  59. package/lib/ui/command.d.ts +2 -0
  60. package/lib/ui/host.d.ts +1 -0
  61. package/lib/ui/window-module.d.ts +6 -0
  62. package/lib/utils/config.d.ts +3 -3
  63. package/lib/utils/cors.d.ts +1 -0
  64. package/lib/utils/decision-sharing.d.ts +37 -0
  65. package/lib/utils/downloadStatus.d.ts +57 -0
  66. package/lib/utils/hostFacts.d.ts +13 -0
  67. package/lib/utils/workspaceState.d.ts +8 -0
  68. package/lib/wrapper-3PX6qE3t.mjs +7 -0
  69. package/package.json +17 -9
  70. package/public/status.html +219 -1489
@@ -0,0 +1,97 @@
1
+ /**
2
+ * OpenAI Decisions API (`POST /v1/decisions`) <-> native decision requests.
3
+ *
4
+ * Spec: https://developers.openai.com/api/docs/guides/decisions
5
+ *
6
+ * The native request follows the TypeSafe `/v1/systemone` contract, so the
7
+ * question types map one to one: `predicate` -> `noul`, `choice` -> `choice`,
8
+ * `score` -> `score`. What differs is the shape: OpenAI sends an ordered array
9
+ * of optionally named questions with typed choice values and returns an
10
+ * ordered array of answers; native keys questions and options by string.
11
+ * The local models never refuse, so no `refusal` answer is produced.
12
+ */
13
+ import type { DecisionRequest, DecisionResult } from '../types';
14
+ type ChoiceValue = string | boolean;
15
+ type QuestionPlan = {
16
+ id: string;
17
+ name: string | null;
18
+ type: 'predicate';
19
+ } | {
20
+ id: string;
21
+ name: string | null;
22
+ type: 'choice';
23
+ options: {
24
+ key: string;
25
+ value: ChoiceValue;
26
+ }[];
27
+ } | {
28
+ id: string;
29
+ name: string | null;
30
+ type: 'score';
31
+ labels: string[];
32
+ };
33
+ export type DecisionPlan = {
34
+ questions: QuestionPlan[];
35
+ };
36
+ export declare class DecisionRequestError extends Error {
37
+ param: string | null;
38
+ constructor(param: string | null, message: string);
39
+ }
40
+ /**
41
+ * Validate an OpenAI decision request and convert it to the native one.
42
+ * Throws a DecisionRequestError naming the offending field.
43
+ */
44
+ export declare const toNativeDecisionRequest: (body: unknown) => {
45
+ model: string;
46
+ request: DecisionRequest;
47
+ plan: DecisionPlan;
48
+ };
49
+ /** Convert a native result to an OpenAI `Decision` object, answers in request order. */
50
+ export declare const toOpenAIDecision: (result: DecisionResult, plan: DecisionPlan, model: string) => {
51
+ model: string;
52
+ answers: ({
53
+ type: string;
54
+ name: string | null;
55
+ probability: number;
56
+ choice?: undefined;
57
+ score?: undefined;
58
+ probabilities?: undefined;
59
+ confidence?: undefined;
60
+ } | {
61
+ probability?: undefined;
62
+ type: string;
63
+ name: string | null;
64
+ choice: any;
65
+ probabilities: {
66
+ value: ChoiceValue;
67
+ probability: number;
68
+ }[];
69
+ confidence: number;
70
+ score?: undefined;
71
+ } | {
72
+ probability?: undefined;
73
+ choice?: undefined;
74
+ type: string;
75
+ name: string | null;
76
+ score: number;
77
+ probabilities: {
78
+ value: number;
79
+ label: string;
80
+ probability: number;
81
+ }[];
82
+ confidence: number;
83
+ })[];
84
+ usage: {
85
+ input_tokens: number;
86
+ input_tokens_details: {
87
+ cached_tokens: number;
88
+ cache_write_tokens: number;
89
+ };
90
+ output_tokens: number;
91
+ output_tokens_details: {
92
+ reasoning_tokens: number;
93
+ };
94
+ total_tokens: number;
95
+ };
96
+ };
97
+ export {};
@@ -1,4 +1,5 @@
1
+ import type { State } from '../types';
1
2
  declare const _default: import("elysia").default<any, import("elysia").SingletonBase & {
2
- store: import("../types").State;
3
+ store: State;
3
4
  }, any, any, any, any, any>;
4
5
  export default _default;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,30 @@
1
+ /**
2
+ * TypeSafe-compatible System One API (EXPERIMENTAL)
3
+ *
4
+ * Serves `/typesafe-compat/v1/systemone` and `/typesafe-compat/v1/models` in
5
+ * the shape of the hosted TypeSafe API (https://docs.typesafe.ai/api), so the
6
+ * TypeSafe SDKs reach this server's decision generators with
7
+ * `TYPESAFE_BASE_URL=http://<host>:<port>/typesafe-compat` — the same way
8
+ * they reach OpenRouter's System One API under its own path.
9
+ *
10
+ * Enable via TOML config: [typesafe_compat] enabled = true
11
+ *
12
+ * `model` selects a configured decision generator by its published id. An
13
+ * unknown model is rejected, never substituted: another model answers with
14
+ * different calibration, which the caller cannot detect.
15
+ */
16
+ type ValidationIssue = {
17
+ loc: (string | number)[];
18
+ msg: string;
19
+ type: string;
20
+ };
21
+ /**
22
+ * Structural checks with the hosted API's limits. llama.node validates the
23
+ * questions again and has the final word; these catch what it reports less
24
+ * precisely, with the field location the SDKs print.
25
+ */
26
+ export declare const validateSystemOneRequest: (body: any) => ValidationIssue[];
27
+ /** Published ids of the configured decision generators, in config order. */
28
+ export declare const decisionModelIds: (config: any) => string[];
29
+ export default function factory(): import("../types").ButtressApp;
30
+ export {};
@@ -0,0 +1,80 @@
1
+ import { z } from 'zod';
2
+ import type { ReadableStream } from 'node:stream/web';
3
+ import type { DecisionResult, ServiceContext, EventStream, Expand } from '../types';
4
+ export declare const decideProperty: z.ZodObject<{
5
+ request: z.ZodObject<{
6
+ state: z.ZodUnion<[z.ZodString, z.ZodArray<z.ZodUnknown, "many">, z.ZodRecord<z.ZodString, z.ZodUnknown>]>;
7
+ questions: z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodUnknown>>;
8
+ images: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
9
+ }, "strip", z.ZodTypeAny, {
10
+ state: string | unknown[] | Record<string, unknown>;
11
+ questions: Record<string, Record<string, unknown>>;
12
+ images?: string[] | undefined;
13
+ }, {
14
+ state: string | unknown[] | Record<string, unknown>;
15
+ questions: Record<string, Record<string, unknown>>;
16
+ images?: string[] | undefined;
17
+ }>;
18
+ }, "strip", z.ZodTypeAny, {
19
+ request: {
20
+ state: string | unknown[] | Record<string, unknown>;
21
+ questions: Record<string, Record<string, unknown>>;
22
+ images?: string[] | undefined;
23
+ };
24
+ }, {
25
+ request: {
26
+ state: string | unknown[] | Record<string, unknown>;
27
+ questions: Record<string, Record<string, unknown>>;
28
+ images?: string[] | undefined;
29
+ };
30
+ }>;
31
+ export declare const schemas: {
32
+ initContext: z.ZodTuple<[z.ZodString, z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>], null>;
33
+ decide: z.ZodTuple<[z.ZodString, z.ZodObject<{
34
+ request: z.ZodObject<{
35
+ state: z.ZodUnion<[z.ZodString, z.ZodArray<z.ZodUnknown, "many">, z.ZodRecord<z.ZodString, z.ZodUnknown>]>;
36
+ questions: z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodUnknown>>;
37
+ images: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
38
+ }, "strip", z.ZodTypeAny, {
39
+ state: string | unknown[] | Record<string, unknown>;
40
+ questions: Record<string, Record<string, unknown>>;
41
+ images?: string[] | undefined;
42
+ }, {
43
+ state: string | unknown[] | Record<string, unknown>;
44
+ questions: Record<string, Record<string, unknown>>;
45
+ images?: string[] | undefined;
46
+ }>;
47
+ }, "strip", z.ZodTypeAny, {
48
+ request: {
49
+ state: string | unknown[] | Record<string, unknown>;
50
+ questions: Record<string, Record<string, unknown>>;
51
+ images?: string[] | undefined;
52
+ };
53
+ }, {
54
+ request: {
55
+ state: string | unknown[] | Record<string, unknown>;
56
+ questions: Record<string, Record<string, unknown>>;
57
+ images?: string[] | undefined;
58
+ };
59
+ }>], null>;
60
+ releaseContext: z.ZodTuple<[z.ZodString], null>;
61
+ };
62
+ export interface Service {
63
+ initContext: (ctx: ServiceContext, ...args: z.infer<typeof schemas.initContext>) => ReadableStream<Expand<EventStream>>;
64
+ decide: (ctx: ServiceContext, ...args: z.infer<typeof schemas.decide>) => Promise<DecisionResult>;
65
+ releaseContext: (ctx: ServiceContext, ...args: z.infer<typeof schemas.releaseContext>) => Promise<{
66
+ released: boolean;
67
+ alreadyReleased?: boolean;
68
+ }>;
69
+ }
70
+ /**
71
+ * Clients upload local images first (like STT audio) and send their names;
72
+ * data URLs pass through.
73
+ */
74
+ export declare const resolveDecisionImages: (session: ServiceContext['session'], request: z.infer<typeof decideProperty>['request']) => {
75
+ state: string | unknown[] | Record<string, unknown>;
76
+ questions: Record<string, Record<string, unknown>>;
77
+ images: string[] | undefined;
78
+ };
79
+ declare const _default: Service;
80
+ export default _default;
@@ -1,6 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  import { type Service as LlmService } from './create-llm-service';
3
- import type { ServiceContext } from '../types';
3
+ import type { DecisionResult, ServiceContext } from '../types';
4
4
  export declare const schemas: {
5
5
  initContext: z.ZodTuple<[z.ZodString, z.ZodOptional<z.ZodAny>], null>;
6
6
  completion: z.ZodTuple<[z.ZodString, z.ZodOptional<z.ZodAny>], null>;
@@ -9,9 +9,37 @@ export declare const schemas: {
9
9
  applyChatTemplate: z.ZodTuple<[z.ZodString, z.ZodAny], null>;
10
10
  releaseContext: z.ZodTuple<[z.ZodString], null>;
11
11
  embedding: z.ZodTuple<[z.ZodString, z.ZodAny], null>;
12
+ decide: z.ZodTuple<[z.ZodString, z.ZodObject<{
13
+ request: z.ZodObject<{
14
+ state: z.ZodUnion<[z.ZodString, z.ZodArray<z.ZodUnknown, "many">, z.ZodRecord<z.ZodString, z.ZodUnknown>]>;
15
+ questions: z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodUnknown>>;
16
+ images: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
17
+ }, "strip", z.ZodTypeAny, {
18
+ state: string | unknown[] | Record<string, unknown>;
19
+ questions: Record<string, Record<string, unknown>>;
20
+ images?: string[] | undefined;
21
+ }, {
22
+ state: string | unknown[] | Record<string, unknown>;
23
+ questions: Record<string, Record<string, unknown>>;
24
+ images?: string[] | undefined;
25
+ }>;
26
+ }, "strip", z.ZodTypeAny, {
27
+ request: {
28
+ state: string | unknown[] | Record<string, unknown>;
29
+ questions: Record<string, Record<string, unknown>>;
30
+ images?: string[] | undefined;
31
+ };
32
+ }, {
33
+ request: {
34
+ state: string | unknown[] | Record<string, unknown>;
35
+ questions: Record<string, Record<string, unknown>>;
36
+ images?: string[] | undefined;
37
+ };
38
+ }>], null>;
12
39
  };
13
40
  export interface Service extends LlmService {
14
41
  embedding: (ctx: ServiceContext, ...args: z.infer<typeof schemas.embedding>) => Promise<any>;
42
+ decide: (ctx: ServiceContext, ...args: z.infer<typeof schemas.decide>) => Promise<DecisionResult>;
15
43
  }
16
44
  declare const _default: Service;
17
45
  export default _default;
@@ -3,6 +3,7 @@ import type { Service as CommonService } from './common';
3
3
  import type { Service as GgmlLlmService } from './ggml-llm';
4
4
  import type { Service as GgmlSttService } from './ggml-stt';
5
5
  import type { Service as GgmlTtsService } from './ggml-tts';
6
+ import type { Service as GgmlDecisionService } from './ggml-decision';
6
7
  import type { Service as MlxLlmService } from './mlx-llm';
7
8
  import type { Service as OnnxSttService } from './onnx-stt';
8
9
  import type { Service as OnnxTtsService } from './onnx-tts';
@@ -21,6 +22,7 @@ declare const services: {
21
22
  releaseContext: ({ backend, session }: ServiceContext, id: any, force: any) => Promise<any>;
22
23
  };
23
24
  ggmlTts: GgmlTtsService;
25
+ ggmlDecision: GgmlDecisionService;
24
26
  mlxLlm: MlxLlmService;
25
27
  onnxStt: OnnxSttService;
26
28
  onnxTts: OnnxTtsService;
@@ -35,6 +37,7 @@ export type Services = {
35
37
  ggmlLlm: ClientService<GgmlLlmService>;
36
38
  ggmlStt: ClientService<GgmlSttService>;
37
39
  ggmlTts: ClientService<GgmlTtsService>;
40
+ ggmlDecision: ClientService<GgmlDecisionService>;
38
41
  mlxLlm: ClientService<MlxLlmService>;
39
42
  onnxStt: ClientService<OnnxSttService>;
40
43
  onnxTts: ClientService<OnnxTtsService>;
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import{a as e,t}from"./client-qqI-HCOj.mjs";import{Container as n,Editor as r,Loader as i,Markdown as a,ProcessTerminal as o,Text as s,TuiMainScreen as c}from"@earendil-works/pi-tui";const l=e=>t=>`\x1b[${e}m${t}\x1b[0m`,u=l(`2`),d=l(`1`),f=l(`36`),p=l(`33`),m=l(`31`),h=l(`32`),g=l(`35`),_={heading:e=>d(f(e)),link:f,linkUrl:e=>u(f(e)),code:p,codeBlock:p,codeBlockBorder:u,quote:u,quoteBorder:u,hr:u,listBullet:f,bold:d,italic:l(`3`),strikethrough:l(`9`),underline:l(`4`)},v={borderColor:u,selectList:{selectedPrefix:f,selectedText:d,description:u,scrollInfo:u,noMatch:u}},y=(e,t)=>e.length>t?`${e.slice(0,t)}…`:e;var b=class{tui;chat;thinking=null;thinkingBuffer=``;answer=null;answerBuffer=``;constructor(e,t){this.tui=e,this.chat=t}touch(){this.tui.requestRender()}appendThinking(e){this.thinking||(this.thinking=new s(``,0,0),this.chat.addChild(this.thinking)),this.thinkingBuffer+=e,this.thinking.setText(u(this.thinkingBuffer.trimEnd())),this.touch()}appendAnswer(e){this.answer||(this.answer=new a(``,0,0,_),this.chat.addChild(this.answer)),this.answerBuffer+=e,this.answer.setText(this.answerBuffer),this.touch()}addLine(e){this.answer=null,this.answerBuffer=``,this.chat.addChild(new s(e,0,0)),this.touch()}};const x=async({connection:a,agentName:l,sessionId:_,fork:x})=>{let S=new o,C=new c(S);S.setTitle(`buttress agent · ${l}`);let w=new n,T=new s(``,0,0),E=new r(C,v);C.addChild(w),C.addChild(T),C.addChild(E),C.setFocus(E);let D=_,O=x,k=null,A=null,j=e=>{T.setText(e),C.requestRender()},M=()=>{A||(A=new i(C,f,u,`thinking…`),w.addChild(A),C.requestRender())},N=()=>{A&&(w.removeChild(A),A=null,C.requestRender())},P=`${d(l)} ${u(`@ ${a.baseUrl}`)}`+(D?` ${u(`(${O?`forking`:`continuing`} ${D})`)}`:``);w.addChild(new s(P,0,0)),j(u(`Enter to send · /new fresh session · /exit or Ctrl+C to quit`));let F=async()=>{C.stop(),await S.drainInput().catch(()=>{}),process.exit(0)};C.addInputListener(e=>{if(e===``)return k?(k.abort(),k=null,{consume:!0}):(F(),{consume:!0})});let I=async n=>{w.addChild(new s(`${f(`>`)} ${n}`,0,0));let r=new b(C,w),i=D;k=new AbortController,M(),j(u(`running · Ctrl+C aborts this turn`));let o;try{o=await e(a,l,{prompt:n,sessionId:D??void 0,fork:O},{signal:k.signal,onFrame:({event:e,payload:t})=>{if(e===`session`&&t?.sessionId){t.sessionId!==D&&(D=t.sessionId,w.addChild(new s(u(`session ${D}`),0,0)));return}if(e!==`agent`)return;N();let n=t?.event;if(t?.type===`message_update`&&n)n.type===`thinking_delta`?r.appendThinking(n.delta??``):n.type===`text_delta`&&r.appendAnswer(n.delta??``);else if(t?.type===`tool_execution_start`){let e=JSON.stringify(t.args??{});r.addLine(`${p(`⚙ ${t.toolName}`)}${u(`(${y(e,120)})`)}`)}else t?.type===`tool_execution_end`?r.addLine(t.isError?m(`✗ ${t.toolName} failed`):u(`✓ ${t.toolName}`)):t?.type===`tool_emit`&&r.addLine(u(` ${t.toolName} → ${t.event}`))}})}catch(e){o={kind:`error`,message:e?.message||String(e),sessionId:D}}if(N(),k=null,o.kind===`result`){let{result:e}=o;D=e.sessionId??D;let t=e.usage||{};w.addChild(new s(u(`— ${e.stopReason} · ${t.totalTurns??`?`} turn(s) · ${t.input??0} in / ${t.output??0} out · session ${D}`),0,0))}else o.kind===`aborted`?(D=o.sessionId??D,w.addChild(new s(p(`(aborted — session is continuable)`),0,0))):o.kind===`error`?(D=o.sessionId??D,w.addChild(new s(m(`Run failed: ${o.message}`),0,0))):w.addChild(new s(m(`Connection lost mid-run.`),0,0));t(i,D)&&(O=!1),j(u(`Enter to send · /new fresh session · /exit or Ctrl+C to quit`)),C.requestRender()};E.onSubmit=e=>{let t=e.trim();if(E.setText(``),t){if(k){j(p(`A turn is still running — Ctrl+C aborts it.`));return}if(t===`/exit`||t===`/quit`){F();return}if(t===`/new`){D=null,O=!1,w.addChild(new s(`${h(`•`)} ${u(`started a fresh session`)}`,0,0)),C.requestRender();return}if(t===`/session`){w.addChild(new s(u(D?`session ${D}`:`no session yet`),0,0)),C.requestRender();return}I(t)}},w.addChild(new s(u(g(`ready`)),0,0)),C.start(),await new Promise(()=>{})};export{x as runAgentTui};
package/lib/types.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { Elysia, SingletonBase } from 'elysia';
2
2
  import type { ReadableStream } from 'node:stream/web';
3
- import type { Backend } from './index';
3
+ import type * as backendCore from '@fugood/buttress-backend-core';
4
+ export type Backend = typeof backendCore;
4
5
  export type HumanReadableUnit = number | string;
5
6
  type NormalizeKeys = 'max_body_size' | 'session_timeout' | 'max_size_bytes';
6
7
  export type DeepNormalize<T> = T extends object ? {
@@ -25,7 +26,25 @@ export type RuntimeConfig = {
25
26
  huggingface_token?: string;
26
27
  session_cache?: SessionCacheConfig;
27
28
  } & Record<string, any>;
28
- export type GeneratorType = 'ggml-llm' | 'ggml-stt' | 'ggml-tts' | 'mlx-llm' | 'onnx-stt' | 'onnx-tts';
29
+ export type GeneratorType = 'ggml-llm' | 'ggml-stt' | 'ggml-tts' | 'ggml-decision' | 'mlx-llm' | 'onnx-stt' | 'onnx-tts';
30
+ /** A TypeSafe `/v1/systemone` request, answered by a `ggml-decision` generator. */
31
+ export type DecisionRequest = {
32
+ /** A string, or any JSON object/array (given to the model as JSON text). */
33
+ state: unknown;
34
+ /** Question id → `{ type: 'choice' | 'score' | 'noul', instructions, criteria }`. */
35
+ questions: Record<string, Record<string, any>>;
36
+ /** Local file paths or data URLs; needs a model with an mmproj configured. */
37
+ images?: string[];
38
+ };
39
+ export type DecisionResult = {
40
+ model: string;
41
+ /** Question id → the answer for its type (`choice`, `score` or `noul`). */
42
+ answers: Record<string, any>;
43
+ usage: {
44
+ input_tokens: number;
45
+ output_tokens: number;
46
+ };
47
+ };
29
48
  export type GeneratorConfig = {
30
49
  type: GeneratorType;
31
50
  } & Record<string, any>;
@@ -39,6 +58,10 @@ export type GlobalConfig = {
39
58
  enabled?: boolean;
40
59
  cors_allowed_origins?: string | string[];
41
60
  };
61
+ typesafe_compat?: {
62
+ enabled?: boolean;
63
+ cors_allowed_origins?: string | string[];
64
+ };
42
65
  functions?: {
43
66
  enabled?: boolean;
44
67
  /** Directory holding local function files, relative to the config file. */
@@ -53,6 +76,10 @@ export type GlobalConfig = {
53
76
  config?: Record<string, any>;
54
77
  cors_allowed_origins?: string | string[];
55
78
  };
79
+ /** `[[agents]]` tables; validated by `agent/config.ts` at startup. */
80
+ agents?: Record<string, any>[];
81
+ /** `[agents_options]`: sessions_dir, retention, max_depth, allow_unauthenticated. */
82
+ agents_options?: Record<string, any>;
56
83
  } & Record<string, any>;
57
84
  export type AutodiscoverConfig = {
58
85
  udp: {
@@ -135,6 +162,11 @@ export type ServerInfo = {
135
162
  enabled: boolean;
136
163
  count: number;
137
164
  };
165
+ /** Agent surface: a flag and a count, for the same ANNOUNCE-size reason. */
166
+ agents?: {
167
+ enabled: boolean;
168
+ count: number;
169
+ };
138
170
  };
139
171
  export type EventStream = {
140
172
  event: string;
@@ -161,6 +193,20 @@ export type State = {
161
193
  config: Config;
162
194
  sessions: Map<string, Session>;
163
195
  workspaceState: import('./utils/workspaceState').WorkspaceState;
196
+ /** Epoch ms when `createServer` began, reported on `/buttress/status`. */
197
+ startedAt: number;
198
+ /** The compatibility endpoints this process serves (env var or config flag). */
199
+ endpoints: {
200
+ openaiCompat: boolean;
201
+ anthropicMessages: boolean;
202
+ typesafeCompat: boolean;
203
+ };
204
+ /** Local functions; null when the feature is off or failed to start. */
205
+ functionsService: import('./functions').FunctionsService | null;
206
+ /** Config-defined agents; null when none are configured or they failed to start. */
207
+ agentsService: import('./agent/types').AgentsService | null;
208
+ /** This machine, read once at startup for `/buttress/status`. */
209
+ host: import('./utils/hostFacts').HostFacts;
164
210
  };
165
211
  type ButtressSingleton = SingletonBase & {
166
212
  store: State;
@@ -0,0 +1,2 @@
1
+ /** `bricks-buttress ui …`. Exits the process: 0 after the window closes, 1 on any failure. */
2
+ export declare const runUiCommand: (argv: string[]) => Promise<void>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,6 @@
1
+ import type { LaunchButtressUi } from '@fugood/buttress-ui/src/host';
2
+ export declare const UI_PACKAGE = "@fugood/buttress-ui";
3
+ export type WindowModule = {
4
+ launchButtressUi: LaunchButtressUi;
5
+ };
6
+ export declare const importWindowModule: () => Promise<WindowModule>;
@@ -1,5 +1,4 @@
1
- import type { Config, AutodiscoverConfig, HumanReadableConfig } from '../types';
2
- import type { Backend } from '../index';
1
+ import type { Backend, Config, AutodiscoverConfig, HumanReadableConfig } from '../types';
3
2
  export declare const deepMerge: (target?: Record<string, any>, source?: Record<string, any>) => Record<string, any>;
4
3
  export declare const normalizeConfigInput: (input: any) => null | Record<string, any>;
5
4
  export declare const mergeGeneratorConfig: (base: any, override: any) => Record<string, any>;
@@ -16,6 +15,7 @@ export declare const resolveSupportedGenerators: (config: Config, defaultTypes:
16
15
  }[];
17
16
  /**
18
17
  * Process raw user config (HumanReadableConfig) into Config.
19
- * Handles: device defaults, value normalization (bytes/ms), structure splitting.
18
+ * Handles: device defaults, value normalization (bytes/ms), structure splitting,
19
+ * and dropping generators with `enabled = false`.
20
20
  */
21
21
  export declare const processConfig: (input: HumanReadableConfig | null) => Config;
@@ -56,6 +56,7 @@ export type CorsPolicy = {
56
56
  export declare const buildCorsPolicies: (config: Config, enabled?: {
57
57
  openaiCompat?: boolean;
58
58
  anthropicMessages?: boolean;
59
+ typesafeCompat?: boolean;
59
60
  functions?: boolean;
60
61
  }) => CorsPolicy[];
61
62
  /**
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Decision models that share an LLM generator's context.
3
+ *
4
+ * A model that both generates and answers typed decisions (Jevling, for one)
5
+ * is often configured twice: as a `ggml-llm` and as a `ggml-decision`. With
6
+ * `[generators.backend] share_llm_context = true` on the decision entry, and
7
+ * both entries naming the same weights, decisions run on the LLM's context
8
+ * (`parallel.decide()` on a slot beside its completions) instead of loading
9
+ * the weights a second time. The LLM config always loads the model, so the
10
+ * result does not depend on which generator starts first.
11
+ *
12
+ * It is opt-in because it trades throughput for memory: a decision takes one
13
+ * of the LLM's `n_parallel` slots, and its forward passes run in the slot
14
+ * loop between two shared batches, so concurrent completions stop generating
15
+ * while it runs (a 128-token completion took 2x as long beside six decisions,
16
+ * unchanged with a separate decision context). Results are not affected: the
17
+ * slot's sequence and prompt cache are cleared after each decision.
18
+ *
19
+ * Sharing is skipped when the decision needs a context the LLM cannot give:
20
+ * rank pooling (an embedding context), or a different projector.
21
+ */
22
+ /**
23
+ * The configured `ggml-llm` generator whose context serves this configured
24
+ * decision generator, or null. For a client's request use
25
+ * `findSharedLlmForClient`: the opt-in must come from the server config.
26
+ */
27
+ export declare const findSharedLlmGenerator: (config: any, decisionConfig: any) => any | null;
28
+ /**
29
+ * The shared LLM for a decision generator a client starts over RPC, or null.
30
+ * Sharing needs a configured `ggml-decision` entry with the same weights that
31
+ * opted in; the client's own `share_llm_context` is ignored, since it would
32
+ * let a client put its decisions on the LLM's slots. The client's merged
33
+ * model (rank pooling, projector) still decides whether the LLM can serve it.
34
+ */
35
+ export declare const findSharedLlmForClient: (config: any, clientModel: any) => any | null;
36
+ /** Startup report: which decision generators share an LLM, and why others do not. */
37
+ export declare const describeDecisionSharing: (config: any) => string[];
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Model pre-downloads (`download = true`) for `/buttress/status`: the ones in
3
+ * flight plus a short newest-first history.
4
+ *
5
+ * The backends report progress as a 0–1 fraction only, so byte counts stay
6
+ * unknown (`receivedBytes` 0, `totalBytes` / `bytesPerSecond` null) and the
7
+ * fraction is exposed as `progress`. Downloads a session starts on demand go
8
+ * through backend-core's download manager, which tracks no metadata, so they
9
+ * are not listed here.
10
+ *
11
+ * A module-scoped singleton like the other status trackers: the CLI records
12
+ * into it and the status route snapshots it.
13
+ */
14
+ import type { GeneratorType } from '../types';
15
+ export type ActiveDownload = {
16
+ type: GeneratorType;
17
+ repoId: string;
18
+ filename: string | null;
19
+ receivedBytes: number;
20
+ totalBytes: number | null;
21
+ bytesPerSecond: number | null;
22
+ startedAt: string;
23
+ /** 0–1 as reported by the backend; null before the first report. */
24
+ progress: number | null;
25
+ };
26
+ export type FinishedDownload = {
27
+ type: GeneratorType;
28
+ repoId: string;
29
+ filename: string | null;
30
+ totalBytes: number | null;
31
+ durationMs: number;
32
+ finishedAt: string;
33
+ success: boolean;
34
+ error?: string;
35
+ };
36
+ export type DownloadsSnapshot = {
37
+ active: ActiveDownload[];
38
+ recent: FinishedDownload[];
39
+ };
40
+ /** Feeds one download's callbacks into the tracker. Calls after the first ending are no-ops. */
41
+ export type DownloadHandle = {
42
+ progress(fraction: number): void;
43
+ /** Replaces the repo named at `begin` with the one the backend resolved. */
44
+ setRepoId(repoId: string): void;
45
+ complete(): void;
46
+ fail(error: unknown): void;
47
+ /** Ends the entry without history: nothing was downloaded (already cached, or joined another download). */
48
+ discard(): void;
49
+ };
50
+ export declare const downloadStatus: {
51
+ begin(download: {
52
+ type: GeneratorType;
53
+ repoId: string;
54
+ filename?: string | null;
55
+ }): DownloadHandle;
56
+ snapshot(): DownloadsSnapshot;
57
+ };
@@ -0,0 +1,13 @@
1
+ /**
2
+ * This machine, as `/buttress/status` and the settings window describe it. Same shape as
3
+ * `HostInfo` in @fugood/buttress-ui, kept local so the emitted types do not need that package.
4
+ */
5
+ export type HostFacts = {
6
+ cpuModel: string;
7
+ cpuCores: number;
8
+ totalMemoryBytes: number;
9
+ /** Unified memory: the GPU shares `totalMemoryBytes`. */
10
+ appleSilicon: boolean;
11
+ gpuName?: string;
12
+ };
13
+ export declare const readHostFacts: () => HostFacts;
@@ -19,10 +19,18 @@ export interface BankBinding {
19
19
  keyName?: string;
20
20
  issuedAt?: string;
21
21
  }
22
+ export interface DevtoolsBinding {
23
+ token: string;
24
+ workspaceId: string;
25
+ expiresAt: string;
26
+ jti?: string;
27
+ }
22
28
  export interface WorkspaceState {
23
29
  workspace: WorkspaceBinding | null;
24
30
  serverKeyPair: ServerKeyPair | null;
25
31
  bank: BankBinding | null;
32
+ /** Optional for state files written before device credential provisioning. */
33
+ devtools?: DevtoolsBinding | null;
26
34
  }
27
35
  export declare const resolveStateDir: () => string;
28
36
  export declare const resolveStatePath: () => string;