@fugood/buttress-server 2.25.11 → 2.25.12
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 +115 -12
- package/config/sample.toml +15 -0
- package/lib/agent/decisions.check.d.ts +1 -0
- package/lib/agent/decisions.d.ts +113 -0
- package/lib/agent/models.d.ts +4 -2
- package/lib/agent/types.d.ts +14 -0
- package/lib/{cli-DMI3uIfZ.mjs → cli-CIa2rDj7.mjs} +2 -2
- package/lib/{client-DEUSDK5d.mjs → client-BVyIuSyr.mjs} +1 -1
- package/lib/codemode-CRowB9aL.mjs +112 -0
- package/lib/codemode-worker.mjs +2 -2
- package/lib/config-cxApm_PZ.mjs +2 -0
- package/lib/functions/templates.d.ts +1 -1
- package/lib/index.d.ts +4 -2
- package/lib/index.mjs +96 -80
- package/lib/mcp-DzknURyz.mjs +5 -0
- package/lib/mlx-bridge.py +47 -11
- package/lib/{protocol-RJSV46H1.mjs → protocol-WkPByalT.mjs} +129 -12
- package/lib/routes/decision-shared.d.ts +13 -1
- package/lib/routes/generator-cache.d.ts +3 -1
- package/lib/routes/index.d.ts +1 -0
- package/lib/routes/llm-shared.d.ts +8 -0
- package/lib/routes/openai-compat.d.ts +13 -2
- package/lib/routes/openai-decisions.check.d.ts +1 -0
- package/lib/routes/openai-decisions.d.ts +97 -0
- package/lib/routes/tts-shared.d.ts +5 -0
- package/lib/routes/typesafe-compat.check.d.ts +1 -0
- package/lib/routes/typesafe-compat.d.ts +30 -0
- package/lib/services/ggml-decision.d.ts +52 -5
- package/lib/services/ggml-llm.d.ts +34 -1
- package/lib/services/register-tts-result.d.ts +4 -0
- package/lib/{tui-Bl2ADqOw.mjs → tui-Bxeb1d7E.mjs} +1 -1
- package/lib/types.d.ts +14 -2
- package/lib/utils/SessionFileManager.d.ts +12 -4
- package/lib/utils/cors.d.ts +1 -0
- package/lib/utils/decision-sharing.d.ts +37 -0
- package/package.json +7 -7
- package/lib/codemode-BxD8KKWZ.mjs +0 -112
- package/lib/config-DtcwsKrn.mjs +0 -2
- package/lib/mcp-Cy5Jhrn5.mjs +0 -5
|
@@ -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 {};
|
|
@@ -25,6 +25,11 @@ export type SynthesizeBackendResult = {
|
|
|
25
25
|
* — callers that need the audio beyond the immediate request must copy it.
|
|
26
26
|
*/
|
|
27
27
|
cachedFile: string;
|
|
28
|
+
/**
|
|
29
|
+
* The cache did not keep this output: `cachedFile` is a per-synthesis temporary
|
|
30
|
+
* file that the caller owns and must delete.
|
|
31
|
+
*/
|
|
32
|
+
temporary?: boolean;
|
|
28
33
|
sampling_rate: number;
|
|
29
34
|
channels: number;
|
|
30
35
|
};
|
|
@@ -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 {};
|
|
@@ -1,33 +1,70 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import type { ReadableStream } from 'node:stream/web';
|
|
3
3
|
import type { DecisionResult, ServiceContext, EventStream, Expand } from '../types';
|
|
4
|
+
export declare const decideProperty: z.ZodObject<{
|
|
5
|
+
request: z.ZodObject<{
|
|
6
|
+
state: z.ZodOptional<z.ZodNullable<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
|
+
files: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
10
|
+
}, "strip", z.ZodTypeAny, {
|
|
11
|
+
state?: string | unknown[] | Record<string, unknown> | null | undefined;
|
|
12
|
+
questions: Record<string, Record<string, unknown>>;
|
|
13
|
+
images?: string[] | undefined;
|
|
14
|
+
files?: string[] | undefined;
|
|
15
|
+
}, {
|
|
16
|
+
state?: string | unknown[] | Record<string, unknown> | null | undefined;
|
|
17
|
+
questions: Record<string, Record<string, unknown>>;
|
|
18
|
+
images?: string[] | undefined;
|
|
19
|
+
files?: string[] | undefined;
|
|
20
|
+
}>;
|
|
21
|
+
}, "strip", z.ZodTypeAny, {
|
|
22
|
+
request: {
|
|
23
|
+
state?: string | unknown[] | Record<string, unknown> | null | undefined;
|
|
24
|
+
questions: Record<string, Record<string, unknown>>;
|
|
25
|
+
images?: string[] | undefined;
|
|
26
|
+
files?: string[] | undefined;
|
|
27
|
+
};
|
|
28
|
+
}, {
|
|
29
|
+
request: {
|
|
30
|
+
state?: string | unknown[] | Record<string, unknown> | null | undefined;
|
|
31
|
+
questions: Record<string, Record<string, unknown>>;
|
|
32
|
+
images?: string[] | undefined;
|
|
33
|
+
files?: string[] | undefined;
|
|
34
|
+
};
|
|
35
|
+
}>;
|
|
4
36
|
export declare const schemas: {
|
|
5
37
|
initContext: z.ZodTuple<[z.ZodString, z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>], null>;
|
|
6
38
|
decide: z.ZodTuple<[z.ZodString, z.ZodObject<{
|
|
7
39
|
request: z.ZodObject<{
|
|
8
|
-
state: z.ZodUnion<[z.ZodString, z.ZodArray<z.ZodUnknown, "many">, z.ZodRecord<z.ZodString, z.ZodUnknown>]
|
|
40
|
+
state: z.ZodOptional<z.ZodNullable<z.ZodUnion<[z.ZodString, z.ZodArray<z.ZodUnknown, "many">, z.ZodRecord<z.ZodString, z.ZodUnknown>]>>>;
|
|
9
41
|
questions: z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
10
42
|
images: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
43
|
+
files: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
11
44
|
}, "strip", z.ZodTypeAny, {
|
|
12
|
-
state
|
|
45
|
+
state?: string | unknown[] | Record<string, unknown> | null | undefined;
|
|
13
46
|
questions: Record<string, Record<string, unknown>>;
|
|
14
47
|
images?: string[] | undefined;
|
|
48
|
+
files?: string[] | undefined;
|
|
15
49
|
}, {
|
|
16
|
-
state
|
|
50
|
+
state?: string | unknown[] | Record<string, unknown> | null | undefined;
|
|
17
51
|
questions: Record<string, Record<string, unknown>>;
|
|
18
52
|
images?: string[] | undefined;
|
|
53
|
+
files?: string[] | undefined;
|
|
19
54
|
}>;
|
|
20
55
|
}, "strip", z.ZodTypeAny, {
|
|
21
56
|
request: {
|
|
22
|
-
state
|
|
57
|
+
state?: string | unknown[] | Record<string, unknown> | null | undefined;
|
|
23
58
|
questions: Record<string, Record<string, unknown>>;
|
|
24
59
|
images?: string[] | undefined;
|
|
60
|
+
files?: string[] | undefined;
|
|
25
61
|
};
|
|
26
62
|
}, {
|
|
27
63
|
request: {
|
|
28
|
-
state
|
|
64
|
+
state?: string | unknown[] | Record<string, unknown> | null | undefined;
|
|
29
65
|
questions: Record<string, Record<string, unknown>>;
|
|
30
66
|
images?: string[] | undefined;
|
|
67
|
+
files?: string[] | undefined;
|
|
31
68
|
};
|
|
32
69
|
}>], null>;
|
|
33
70
|
releaseContext: z.ZodTuple<[z.ZodString], null>;
|
|
@@ -40,5 +77,15 @@ export interface Service {
|
|
|
40
77
|
alreadyReleased?: boolean;
|
|
41
78
|
}>;
|
|
42
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* Clients upload local media (images, audio clips) first, like STT audio,
|
|
82
|
+
* and send their names; data URLs pass through.
|
|
83
|
+
*/
|
|
84
|
+
export declare const resolveDecisionMedia: (session: ServiceContext['session'], request: z.infer<typeof decideProperty>['request']) => {
|
|
85
|
+
state?: string | unknown[] | Record<string, unknown> | null | undefined;
|
|
86
|
+
questions: Record<string, Record<string, unknown>>;
|
|
87
|
+
images: string[] | undefined;
|
|
88
|
+
files: string[] | undefined;
|
|
89
|
+
};
|
|
43
90
|
declare const _default: Service;
|
|
44
91
|
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,42 @@ 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.ZodOptional<z.ZodNullable<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
|
+
files: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
18
|
+
}, "strip", z.ZodTypeAny, {
|
|
19
|
+
state?: string | unknown[] | Record<string, unknown> | null | undefined;
|
|
20
|
+
questions: Record<string, Record<string, unknown>>;
|
|
21
|
+
images?: string[] | undefined;
|
|
22
|
+
files?: string[] | undefined;
|
|
23
|
+
}, {
|
|
24
|
+
state?: string | unknown[] | Record<string, unknown> | null | undefined;
|
|
25
|
+
questions: Record<string, Record<string, unknown>>;
|
|
26
|
+
images?: string[] | undefined;
|
|
27
|
+
files?: string[] | undefined;
|
|
28
|
+
}>;
|
|
29
|
+
}, "strip", z.ZodTypeAny, {
|
|
30
|
+
request: {
|
|
31
|
+
state?: string | unknown[] | Record<string, unknown> | null | undefined;
|
|
32
|
+
questions: Record<string, Record<string, unknown>>;
|
|
33
|
+
images?: string[] | undefined;
|
|
34
|
+
files?: string[] | undefined;
|
|
35
|
+
};
|
|
36
|
+
}, {
|
|
37
|
+
request: {
|
|
38
|
+
state?: string | unknown[] | Record<string, unknown> | null | undefined;
|
|
39
|
+
questions: Record<string, Record<string, unknown>>;
|
|
40
|
+
images?: string[] | undefined;
|
|
41
|
+
files?: string[] | undefined;
|
|
42
|
+
};
|
|
43
|
+
}>], null>;
|
|
12
44
|
};
|
|
13
45
|
export interface Service extends LlmService {
|
|
14
46
|
embedding: (ctx: ServiceContext, ...args: z.infer<typeof schemas.embedding>) => Promise<any>;
|
|
47
|
+
decide: (ctx: ServiceContext, ...args: z.infer<typeof schemas.decide>) => Promise<DecisionResult>;
|
|
15
48
|
}
|
|
16
49
|
declare const _default: Service;
|
|
17
50
|
export default _default;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { SessionFileManager } from '../utils/SessionFileManager';
|
|
2
|
+
import type { SynthesizeBackendResult } from '../routes/tts-shared';
|
|
3
|
+
/** Register a backend synthesis result with the session and return its filename. */
|
|
4
|
+
export default function registerTtsResult(fileManager: SessionFileManager, result: SynthesizeBackendResult): string;
|
|
@@ -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
|
@@ -29,12 +29,20 @@ export type RuntimeConfig = {
|
|
|
29
29
|
export type GeneratorType = 'ggml-llm' | 'ggml-stt' | 'ggml-tts' | 'ggml-decision' | 'mlx-llm' | 'onnx-stt' | 'onnx-tts';
|
|
30
30
|
/** A TypeSafe `/v1/systemone` request, answered by a `ggml-decision` generator. */
|
|
31
31
|
export type DecisionRequest = {
|
|
32
|
-
/**
|
|
32
|
+
/**
|
|
33
|
+
* A string, or any JSON object/array (given to the model as JSON text).
|
|
34
|
+
* `null` asks about the media only (`lfm2-d1`, `lfm2-d1-omni`); it needs media.
|
|
35
|
+
*/
|
|
33
36
|
state: unknown;
|
|
34
37
|
/** Question id → `{ type: 'choice' | 'score' | 'noul', instructions, criteria }`. */
|
|
35
38
|
questions: Record<string, Record<string, any>>;
|
|
36
|
-
/**
|
|
39
|
+
/**
|
|
40
|
+
* Images, or audio clips for a model that takes audio, as local file paths
|
|
41
|
+
* or data URLs; needs a model with an mmproj configured.
|
|
42
|
+
*/
|
|
37
43
|
images?: string[];
|
|
44
|
+
/** Alias of `images`; both lists are used. */
|
|
45
|
+
files?: string[];
|
|
38
46
|
};
|
|
39
47
|
export type DecisionResult = {
|
|
40
48
|
model: string;
|
|
@@ -58,6 +66,10 @@ export type GlobalConfig = {
|
|
|
58
66
|
enabled?: boolean;
|
|
59
67
|
cors_allowed_origins?: string | string[];
|
|
60
68
|
};
|
|
69
|
+
typesafe_compat?: {
|
|
70
|
+
enabled?: boolean;
|
|
71
|
+
cors_allowed_origins?: string | string[];
|
|
72
|
+
};
|
|
61
73
|
functions?: {
|
|
62
74
|
enabled?: boolean;
|
|
63
75
|
/** Directory holding local function files, relative to the config file. */
|
|
@@ -1,16 +1,24 @@
|
|
|
1
1
|
import type { ReadableStream as NodeReadableStream } from 'node:stream/web';
|
|
2
|
+
type RegisterOptions = {
|
|
3
|
+
owned?: boolean;
|
|
4
|
+
ttl?: number;
|
|
5
|
+
group?: {
|
|
6
|
+
name: string;
|
|
7
|
+
limit: number;
|
|
8
|
+
};
|
|
9
|
+
};
|
|
2
10
|
type UploadData = Buffer | ArrayBuffer | ReadableStream | NodeReadableStream;
|
|
3
11
|
export declare class SessionFileManager {
|
|
4
12
|
sessionId: string;
|
|
5
13
|
private sessionDir;
|
|
6
14
|
private registry;
|
|
7
15
|
private uploadSeq;
|
|
16
|
+
private destroyed;
|
|
8
17
|
constructor(sessionId: string, tempDir: string);
|
|
9
|
-
register(filename: string, realPath: string, opts?:
|
|
10
|
-
owned?: boolean;
|
|
11
|
-
ttl?: number;
|
|
12
|
-
}): void;
|
|
18
|
+
register(filename: string, realPath: string, opts?: RegisterOptions): void;
|
|
13
19
|
resolve(filename: string): string | null;
|
|
20
|
+
private release;
|
|
21
|
+
private trimGroup;
|
|
14
22
|
storeUpload(originalName: string, data: UploadData): Promise<string>;
|
|
15
23
|
destroy(): Promise<void>;
|
|
16
24
|
}
|
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.25.
|
|
3
|
+
"version": "2.25.12",
|
|
4
4
|
"main": "lib/index.mjs",
|
|
5
5
|
"types": "lib/index.d.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -37,13 +37,13 @@
|
|
|
37
37
|
],
|
|
38
38
|
"license": "MIT",
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@earendil-works/pi-agent-core": "^1.
|
|
41
|
-
"@earendil-works/pi-ai": "1.
|
|
42
|
-
"@earendil-works/pi-tui": "^1.
|
|
40
|
+
"@earendil-works/pi-agent-core": "^1.1.0",
|
|
41
|
+
"@earendil-works/pi-ai": "1.1.0",
|
|
42
|
+
"@earendil-works/pi-tui": "^1.1.0",
|
|
43
43
|
"@elysiajs/cors": "^1.1.1",
|
|
44
44
|
"@elysiajs/node": "^1.4.2",
|
|
45
|
-
"@fugood/llama.node": "^1.8.0-rc.
|
|
46
|
-
"@fugood/whisper.node": "^1.2.0-rc.
|
|
45
|
+
"@fugood/llama.node": "^1.8.0-rc.10",
|
|
46
|
+
"@fugood/whisper.node": "^1.2.0-rc.1",
|
|
47
47
|
"@huggingface/gguf": "^0.3.2",
|
|
48
48
|
"@iarna/toml": "^3.0.0",
|
|
49
49
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
72
|
"@fugood/agent-tool-runtime": "^2.25.10",
|
|
73
|
-
"@fugood/bricks-cli": "^2.25.
|
|
73
|
+
"@fugood/bricks-cli": "^2.25.12",
|
|
74
74
|
"@fugood/bricks-devtools-core": "^2.25.5",
|
|
75
75
|
"tsdown": "^0.22.4",
|
|
76
76
|
"typescript": "^7.0.2"
|