@fugood/buttress-server 2.26.0-beta.16 → 2.26.0-beta.18
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 +185 -4
- package/config/function-samples/README.md +1 -0
- package/config/function-samples/classify-ticket.ts +73 -0
- package/config/sample.toml +39 -0
- package/lib/agent/codemode-worker.d.ts +1 -0
- package/lib/agent/codemode.d.ts +7 -0
- package/lib/agent/decisions.check.d.ts +1 -0
- package/lib/agent/decisions.d.ts +113 -0
- package/lib/agent/mcp.d.ts +2 -3
- package/lib/agent/models.d.ts +4 -2
- package/lib/agent/sessions.d.ts +21 -8
- package/lib/agent/types.d.ts +30 -0
- package/lib/{cli-CtBbHMYQ.mjs → cli-CIa2rDj7.mjs} +2 -2
- package/lib/{client-DzfRSFcJ.mjs → client-BVyIuSyr.mjs} +1 -1
- package/lib/codemode-BxD8KKWZ.mjs +112 -0
- package/lib/codemode-worker.mjs +3 -0
- package/lib/config-cxApm_PZ.mjs +2 -0
- package/lib/functions/templates.d.ts +1 -1
- package/lib/functions/types.d.ts +7 -1
- package/lib/index.d.ts +4 -2
- package/lib/index.mjs +148 -68
- package/lib/mcp-Cy5Jhrn5.mjs +5 -0
- package/lib/protocol-RJSV46H1.mjs +351 -0
- package/lib/quickjs.wasm +0 -0
- package/lib/routes/decision-shared.d.ts +28 -0
- package/lib/routes/generator-cache.d.ts +3 -1
- package/lib/routes/index.d.ts +1 -0
- package/lib/routes/llm-shared.d.ts +1 -0
- package/lib/routes/typesafe-compat.check.d.ts +1 -0
- package/lib/routes/typesafe-compat.d.ts +31 -0
- package/lib/services/ggml-decision.d.ts +80 -0
- package/lib/services/ggml-llm.d.ts +29 -1
- package/lib/services/index.d.ts +3 -0
- package/lib/{tui-DbQ0zW-C.mjs → tui-Bxeb1d7E.mjs} +1 -1
- package/lib/types.d.ts +25 -1
- package/lib/utils/cors.d.ts +1 -0
- package/lib/utils/decision-sharing.d.ts +37 -0
- package/package.json +10 -9
- package/public/status.html +104 -1
- package/lib/agent/session-fs.d.ts +0 -3
- package/lib/config-DbRjQnNp.mjs +0 -2
|
@@ -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;
|
package/lib/services/index.d.ts
CHANGED
|
@@ -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>;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{a as e,t}from"./client-
|
|
2
|
+
import{a as e,t}from"./client-BVyIuSyr.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
|
@@ -26,7 +26,25 @@ export type RuntimeConfig = {
|
|
|
26
26
|
huggingface_token?: string;
|
|
27
27
|
session_cache?: SessionCacheConfig;
|
|
28
28
|
} & Record<string, any>;
|
|
29
|
-
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
|
+
};
|
|
30
48
|
export type GeneratorConfig = {
|
|
31
49
|
type: GeneratorType;
|
|
32
50
|
} & Record<string, any>;
|
|
@@ -40,6 +58,12 @@ export type GlobalConfig = {
|
|
|
40
58
|
enabled?: boolean;
|
|
41
59
|
cors_allowed_origins?: string | string[];
|
|
42
60
|
};
|
|
61
|
+
typesafe_compat?: {
|
|
62
|
+
enabled?: boolean;
|
|
63
|
+
cors_allowed_origins?: string | string[];
|
|
64
|
+
/** Request `model` names (e.g. `jev-latest`) mapped to a decision generator id. */
|
|
65
|
+
models?: Record<string, string>;
|
|
66
|
+
};
|
|
43
67
|
functions?: {
|
|
44
68
|
enabled?: boolean;
|
|
45
69
|
/** Directory holding local function files, relative to the config file. */
|
package/lib/utils/cors.d.ts
CHANGED
|
@@ -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[];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fugood/buttress-server",
|
|
3
|
-
"version": "2.26.0-beta.
|
|
3
|
+
"version": "2.26.0-beta.18",
|
|
4
4
|
"main": "lib/index.mjs",
|
|
5
5
|
"types": "lib/index.d.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
],
|
|
20
20
|
"scripts": {
|
|
21
21
|
"typecheck": "tsc --noEmit",
|
|
22
|
-
"build": "tsdown -c rolldown.config.js --config-loader native && tsc --noCheck --emitDeclarationOnly && cp ../buttress-backend-core/src/backends/mlx-bridge.py lib/mlx-bridge.py",
|
|
22
|
+
"build": "tsdown -c rolldown.config.js --config-loader native && tsc --noCheck --emitDeclarationOnly && cp ../buttress-backend-core/src/backends/mlx-bridge.py lib/mlx-bridge.py && cp ../../node_modules/quickjs-wasi/quickjs.wasm lib/quickjs.wasm",
|
|
23
23
|
"build:dist": "$npm_execpath scripts/build-distribution.js",
|
|
24
24
|
"release": "$npm_execpath scripts/build-distribution.js",
|
|
25
25
|
"release-beta": "$npm_execpath scripts/build-distribution.js --beta",
|
|
@@ -37,12 +37,12 @@
|
|
|
37
37
|
],
|
|
38
38
|
"license": "MIT",
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@earendil-works/pi-agent-core": "^0.
|
|
41
|
-
"@earendil-works/pi-ai": "0.
|
|
42
|
-
"@earendil-works/pi-tui": "^0.
|
|
40
|
+
"@earendil-works/pi-agent-core": "^1.0.0",
|
|
41
|
+
"@earendil-works/pi-ai": "1.0.0",
|
|
42
|
+
"@earendil-works/pi-tui": "^1.0.0",
|
|
43
43
|
"@elysiajs/cors": "^1.1.1",
|
|
44
44
|
"@elysiajs/node": "^1.4.2",
|
|
45
|
-
"@fugood/llama.node": "^1.8.0-rc.
|
|
45
|
+
"@fugood/llama.node": "^1.8.0-rc.9",
|
|
46
46
|
"@fugood/whisper.node": "^1.2.0-rc.0",
|
|
47
47
|
"@huggingface/gguf": "^0.3.2",
|
|
48
48
|
"@iarna/toml": "^3.0.0",
|
|
@@ -69,10 +69,11 @@
|
|
|
69
69
|
"zod": "^3.25.76"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
|
-
"@fugood/
|
|
73
|
-
"@fugood/bricks-
|
|
72
|
+
"@fugood/agent-tool-runtime": "^2.26.0-beta.18",
|
|
73
|
+
"@fugood/bricks-cli": "^2.26.0-beta.18",
|
|
74
|
+
"@fugood/bricks-devtools-core": "^2.26.0-beta.18",
|
|
74
75
|
"tsdown": "^0.22.4",
|
|
75
76
|
"typescript": "^7.0.2"
|
|
76
77
|
},
|
|
77
|
-
"gitHead": "
|
|
78
|
+
"gitHead": "56a905825a9905fbcf48a3ee7dcc689759b7f1f4"
|
|
78
79
|
}
|
package/public/status.html
CHANGED
|
@@ -603,6 +603,27 @@
|
|
|
603
603
|
</div>
|
|
604
604
|
</div>
|
|
605
605
|
|
|
606
|
+
<!-- GGML Typed Decision Status (hidden unless active) -->
|
|
607
|
+
<div class="card" id="ggmlDecisionCard" style="display:none">
|
|
608
|
+
<div class="card-header">
|
|
609
|
+
<span class="card-title">Typed Decision Status</span>
|
|
610
|
+
<span class="badge badge-info" id="ggmlDecisionGeneratorCount">0 generators</span>
|
|
611
|
+
</div>
|
|
612
|
+
<div id="ggmlDecisionGenerators"></div>
|
|
613
|
+
<div class="section">
|
|
614
|
+
<div class="section-title collapsible" onclick="toggleSection(this)">Model Load History</div>
|
|
615
|
+
<div class="collapsible-content" id="ggmlDecisionModelHistory">
|
|
616
|
+
<div class="empty-state">No model load history</div>
|
|
617
|
+
</div>
|
|
618
|
+
</div>
|
|
619
|
+
<div class="section">
|
|
620
|
+
<div class="section-title collapsible" onclick="toggleSection(this)">Decision History</div>
|
|
621
|
+
<div class="collapsible-content" id="ggmlDecisionHistory">
|
|
622
|
+
<div class="empty-state">No decision history</div>
|
|
623
|
+
</div>
|
|
624
|
+
</div>
|
|
625
|
+
</div>
|
|
626
|
+
|
|
606
627
|
<!-- Local Functions (hidden unless enabled) -->
|
|
607
628
|
<div class="card" id="functionsCard" style="display:none">
|
|
608
629
|
<div class="card-header">
|
|
@@ -1049,6 +1070,54 @@
|
|
|
1049
1070
|
})
|
|
1050
1071
|
}
|
|
1051
1072
|
|
|
1073
|
+
function renderGgmlDecisionGenerators(generators) {
|
|
1074
|
+
const container = document.getElementById('ggmlDecisionGenerators')
|
|
1075
|
+
document.getElementById('ggmlDecisionGeneratorCount').textContent =
|
|
1076
|
+
`${generators.length} generator${generators.length !== 1 ? 's' : ''}`
|
|
1077
|
+
|
|
1078
|
+
withScrollPreserve(container, () => {
|
|
1079
|
+
if (generators.length === 0) {
|
|
1080
|
+
container.innerHTML = '<div class="empty-state">No typed decision generators loaded</div>'
|
|
1081
|
+
return
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
container.innerHTML = `
|
|
1085
|
+
<div class="table-wrapper">
|
|
1086
|
+
<div class="table-inner">
|
|
1087
|
+
<table>
|
|
1088
|
+
<thead>
|
|
1089
|
+
<tr>
|
|
1090
|
+
<th>ID</th>
|
|
1091
|
+
<th>Model</th>
|
|
1092
|
+
<th>Type</th>
|
|
1093
|
+
<th>Quant</th>
|
|
1094
|
+
<th>Variant</th>
|
|
1095
|
+
<th>GPU</th>
|
|
1096
|
+
<th>Model Size</th>
|
|
1097
|
+
<th>Context</th>
|
|
1098
|
+
</tr>
|
|
1099
|
+
</thead>
|
|
1100
|
+
<tbody>
|
|
1101
|
+
${generators.map(g => `
|
|
1102
|
+
<tr>
|
|
1103
|
+
<td class="mono">${escapeHtml(g.id?.slice(0, 8) || '-')}</td>
|
|
1104
|
+
<td>${formatModelName(g.repoId)}${g.mmprojFilename ? '<br><span style="font-size:11px;color:var(--text-muted)">+ ' + escapeHtml(g.mmprojFilename) + '</span>' : ''}</td>
|
|
1105
|
+
<td>${escapeHtml(g.decisionType || '-')}</td>
|
|
1106
|
+
<td>${escapeHtml(g.quantization || '-')}</td>
|
|
1107
|
+
<td>${escapeHtml(g.variant || '-')}</td>
|
|
1108
|
+
<td><span class="badge ${g.useGpu ? 'badge-success' : 'badge-info'}">${g.useGpu ? 'yes' : 'no'}</span></td>
|
|
1109
|
+
<td>${formatBytes(g.modelBytes)}</td>
|
|
1110
|
+
<td>${g.hasContext ? '<span class="badge badge-success">loaded</span>' : '-'}</td>
|
|
1111
|
+
</tr>
|
|
1112
|
+
`).join('')}
|
|
1113
|
+
</tbody>
|
|
1114
|
+
</table>
|
|
1115
|
+
</div>
|
|
1116
|
+
</div>
|
|
1117
|
+
`
|
|
1118
|
+
})
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1052
1121
|
// Escape HTML
|
|
1053
1122
|
function escapeHtml(str) {
|
|
1054
1123
|
if (str == null) return ''
|
|
@@ -1077,6 +1146,7 @@
|
|
|
1077
1146
|
const onnxStt = status.onnxStt || {}
|
|
1078
1147
|
const onnxTts = status.onnxTts || {}
|
|
1079
1148
|
const ggmlTts = status.ggmlTts || {}
|
|
1149
|
+
const ggmlDecision = status.ggmlDecision || {}
|
|
1080
1150
|
|
|
1081
1151
|
// LLM
|
|
1082
1152
|
renderLlmGenerators(llm.generators || [])
|
|
@@ -1288,6 +1358,39 @@
|
|
|
1288
1358
|
},
|
|
1289
1359
|
])
|
|
1290
1360
|
|
|
1361
|
+
// GGML typed decisions
|
|
1362
|
+
const hasGgmlDecision = (ggmlDecision.generators?.length > 0) ||
|
|
1363
|
+
(ggmlDecision.history?.modelLoads?.length > 0) ||
|
|
1364
|
+
(ggmlDecision.history?.decisions?.length > 0)
|
|
1365
|
+
document.getElementById('ggmlDecisionCard').style.display = hasGgmlDecision ? '' : 'none'
|
|
1366
|
+
renderGgmlDecisionGenerators(ggmlDecision.generators || [])
|
|
1367
|
+
|
|
1368
|
+
renderHistory('ggmlDecisionModelHistory', ggmlDecision.history?.modelLoads || [], [
|
|
1369
|
+
{ label: 'Time', render: i => `<span class="timestamp">${formatRelativeTime(i.timestamp)}</span>` },
|
|
1370
|
+
{ label: 'Model', render: i => formatModelName(i.repoId) },
|
|
1371
|
+
{ label: 'Type', render: i => escapeHtml(i.decisionType || '-') },
|
|
1372
|
+
{ label: 'Quant', render: i => escapeHtml(i.quantization || '-') },
|
|
1373
|
+
{ label: 'Variant', render: i => escapeHtml(i.variant || '-') },
|
|
1374
|
+
{ label: 'Duration', render: i => `${(i.durationMs / 1000).toFixed(1)}s` },
|
|
1375
|
+
{ label: 'Status', render: i => i.success ?
|
|
1376
|
+
'<span class="badge badge-success">Success</span>' :
|
|
1377
|
+
`<span class="badge badge-error">Failed: ${escapeHtml(i.error || 'Unknown')}</span>`
|
|
1378
|
+
},
|
|
1379
|
+
])
|
|
1380
|
+
|
|
1381
|
+
renderHistory('ggmlDecisionHistory', ggmlDecision.history?.decisions || [], [
|
|
1382
|
+
{ label: 'Time', render: i => `<span class="timestamp">${formatRelativeTime(i.timestamp)}</span>` },
|
|
1383
|
+
{ label: 'Model', render: i => formatModelName(i.repoId) },
|
|
1384
|
+
{ label: 'Questions', render: i => i.questionCount ?? '-' },
|
|
1385
|
+
{ label: 'Images', render: i => i.imageCount || '-' },
|
|
1386
|
+
{ label: 'Tokens', render: i => i.inputTokens ?? '-' },
|
|
1387
|
+
{ label: 'Duration', render: i => `${i.durationMs} ms` },
|
|
1388
|
+
{ label: 'Status', render: i => i.success ?
|
|
1389
|
+
'<span class="badge badge-success">Success</span>' :
|
|
1390
|
+
`<span class="badge badge-error">Failed: ${escapeHtml(i.error || 'Unknown')}</span>`
|
|
1391
|
+
},
|
|
1392
|
+
])
|
|
1393
|
+
|
|
1291
1394
|
renderFunctionsStatus(status.functions || {})
|
|
1292
1395
|
}
|
|
1293
1396
|
|
|
@@ -1447,7 +1550,7 @@
|
|
|
1447
1550
|
const response = await fetch('/buttress/status')
|
|
1448
1551
|
const data = await response.json()
|
|
1449
1552
|
// Direct JSON response (not tRPC wrapped)
|
|
1450
|
-
if (data.ggmlLlm || data.ggmlStt || data.mlxLlm || data.onnxStt || data.onnxTts || data.ggmlTts) {
|
|
1553
|
+
if (data.ggmlLlm || data.ggmlStt || data.mlxLlm || data.onnxStt || data.onnxTts || data.ggmlTts || data.ggmlDecision) {
|
|
1451
1554
|
updateUI(data)
|
|
1452
1555
|
setConnectionStatus('connected')
|
|
1453
1556
|
}
|
package/lib/config-DbRjQnNp.mjs
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
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)}}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};
|