@fugood/buttress-server 2.26.0-beta.3 → 2.26.0-beta.30
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 +439 -10
- package/config/function-samples/README.md +1 -0
- package/config/function-samples/classify-ticket.ts +73 -0
- package/config/function-samples/run-agent.ts +39 -0
- package/config/sample.toml +67 -0
- package/lib/agent/cli.d.ts +19 -0
- package/lib/agent/client.d.ts +66 -0
- package/lib/agent/codemode-worker.d.ts +1 -0
- package/lib/agent/codemode.d.ts +7 -0
- package/lib/agent/config.d.ts +15 -0
- package/lib/agent/context.d.ts +11 -0
- package/lib/agent/decisions.check.d.ts +1 -0
- package/lib/agent/decisions.d.ts +113 -0
- package/lib/agent/device-auth.d.ts +3 -0
- package/lib/agent/device-tools.d.ts +5 -0
- package/lib/agent/loopback.d.ts +21 -0
- package/lib/agent/mcp.d.ts +22 -0
- package/lib/agent/models.d.ts +22 -0
- package/lib/agent/service.d.ts +16 -0
- package/lib/agent/sessions.d.ts +30 -0
- package/lib/agent/tools.d.ts +35 -0
- package/lib/agent/tui.d.ts +17 -0
- package/lib/agent/types.d.ts +155 -0
- package/lib/cli-KbjOekMI.mjs +22 -0
- package/lib/client-qqI-HCOj.mjs +8 -0
- package/lib/codemode-C7j8OJ4D.mjs +112 -0
- package/lib/codemode-worker.mjs +3 -0
- package/lib/command-QjZhpiuj.mjs +38 -0
- package/lib/config-DiYweJ2m.mjs +2 -0
- package/lib/functions/executor.d.ts +7 -1
- package/lib/functions/index.d.ts +3 -1
- package/lib/functions/status.d.ts +1 -1
- package/lib/functions/templates.d.ts +1 -1
- package/lib/functions/types.d.ts +15 -2
- package/lib/index.d.ts +12 -5
- package/lib/index.mjs +160 -93
- package/lib/mcp-DzknURyz.mjs +5 -0
- package/lib/mlx-bridge.py +717 -0
- package/lib/net-ChIaAlIH.mjs +46 -0
- package/lib/protocol-WkPByalT.mjs +468 -0
- package/lib/quickjs.wasm +0 -0
- package/lib/rolldown-runtime-dTnj95Mm.mjs +2 -0
- package/lib/routes/agents.d.ts +66 -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 +2 -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/status.d.ts +2 -1
- 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 +91 -0
- package/lib/services/ggml-llm.d.ts +34 -1
- package/lib/services/index.d.ts +3 -0
- package/lib/services/register-tts-result.d.ts +4 -0
- package/lib/tui-ClNupsCH.mjs +2 -0
- package/lib/types.d.ts +56 -2
- package/lib/ui/command.d.ts +2 -0
- package/lib/ui/host.d.ts +1 -0
- package/lib/ui/window-module.d.ts +6 -0
- package/lib/utils/SessionFileManager.d.ts +12 -4
- package/lib/utils/config.d.ts +3 -3
- package/lib/utils/cors.d.ts +1 -0
- package/lib/utils/decision-sharing.d.ts +37 -0
- package/lib/utils/downloadStatus.d.ts +57 -0
- package/lib/utils/hostFacts.d.ts +13 -0
- package/lib/utils/workspaceState.d.ts +8 -0
- package/lib/wrapper-3PX6qE3t.mjs +7 -0
- package/package.json +17 -9
- 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 {};
|
package/lib/routes/status.d.ts
CHANGED
|
@@ -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 {};
|
|
@@ -0,0 +1,91 @@
|
|
|
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.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
|
+
}>;
|
|
36
|
+
export declare const schemas: {
|
|
37
|
+
initContext: z.ZodTuple<[z.ZodString, z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>], null>;
|
|
38
|
+
decide: z.ZodTuple<[z.ZodString, z.ZodObject<{
|
|
39
|
+
request: z.ZodObject<{
|
|
40
|
+
state: z.ZodOptional<z.ZodNullable<z.ZodUnion<[z.ZodString, z.ZodArray<z.ZodUnknown, "many">, z.ZodRecord<z.ZodString, z.ZodUnknown>]>>>;
|
|
41
|
+
questions: z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
42
|
+
images: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
43
|
+
files: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
44
|
+
}, "strip", z.ZodTypeAny, {
|
|
45
|
+
state?: string | unknown[] | Record<string, unknown> | null | undefined;
|
|
46
|
+
questions: Record<string, Record<string, unknown>>;
|
|
47
|
+
images?: string[] | undefined;
|
|
48
|
+
files?: string[] | undefined;
|
|
49
|
+
}, {
|
|
50
|
+
state?: string | unknown[] | Record<string, unknown> | null | undefined;
|
|
51
|
+
questions: Record<string, Record<string, unknown>>;
|
|
52
|
+
images?: string[] | undefined;
|
|
53
|
+
files?: string[] | undefined;
|
|
54
|
+
}>;
|
|
55
|
+
}, "strip", z.ZodTypeAny, {
|
|
56
|
+
request: {
|
|
57
|
+
state?: string | unknown[] | Record<string, unknown> | null | undefined;
|
|
58
|
+
questions: Record<string, Record<string, unknown>>;
|
|
59
|
+
images?: string[] | undefined;
|
|
60
|
+
files?: string[] | undefined;
|
|
61
|
+
};
|
|
62
|
+
}, {
|
|
63
|
+
request: {
|
|
64
|
+
state?: string | unknown[] | Record<string, unknown> | null | undefined;
|
|
65
|
+
questions: Record<string, Record<string, unknown>>;
|
|
66
|
+
images?: string[] | undefined;
|
|
67
|
+
files?: string[] | undefined;
|
|
68
|
+
};
|
|
69
|
+
}>], null>;
|
|
70
|
+
releaseContext: z.ZodTuple<[z.ZodString], null>;
|
|
71
|
+
};
|
|
72
|
+
export interface Service {
|
|
73
|
+
initContext: (ctx: ServiceContext, ...args: z.infer<typeof schemas.initContext>) => ReadableStream<Expand<EventStream>>;
|
|
74
|
+
decide: (ctx: ServiceContext, ...args: z.infer<typeof schemas.decide>) => Promise<DecisionResult>;
|
|
75
|
+
releaseContext: (ctx: ServiceContext, ...args: z.infer<typeof schemas.releaseContext>) => Promise<{
|
|
76
|
+
released: boolean;
|
|
77
|
+
alreadyReleased?: boolean;
|
|
78
|
+
}>;
|
|
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
|
+
};
|
|
90
|
+
declare const _default: Service;
|
|
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;
|
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>;
|
|
@@ -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;
|
|
@@ -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
|
|
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,33 @@ 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
|
+
/**
|
|
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
|
+
*/
|
|
36
|
+
state: unknown;
|
|
37
|
+
/** Question id → `{ type: 'choice' | 'score' | 'noul', instructions, criteria }`. */
|
|
38
|
+
questions: Record<string, Record<string, any>>;
|
|
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
|
+
*/
|
|
43
|
+
images?: string[];
|
|
44
|
+
/** Alias of `images`; both lists are used. */
|
|
45
|
+
files?: string[];
|
|
46
|
+
};
|
|
47
|
+
export type DecisionResult = {
|
|
48
|
+
model: string;
|
|
49
|
+
/** Question id → the answer for its type (`choice`, `score` or `noul`). */
|
|
50
|
+
answers: Record<string, any>;
|
|
51
|
+
usage: {
|
|
52
|
+
input_tokens: number;
|
|
53
|
+
output_tokens: number;
|
|
54
|
+
};
|
|
55
|
+
};
|
|
29
56
|
export type GeneratorConfig = {
|
|
30
57
|
type: GeneratorType;
|
|
31
58
|
} & Record<string, any>;
|
|
@@ -39,6 +66,10 @@ export type GlobalConfig = {
|
|
|
39
66
|
enabled?: boolean;
|
|
40
67
|
cors_allowed_origins?: string | string[];
|
|
41
68
|
};
|
|
69
|
+
typesafe_compat?: {
|
|
70
|
+
enabled?: boolean;
|
|
71
|
+
cors_allowed_origins?: string | string[];
|
|
72
|
+
};
|
|
42
73
|
functions?: {
|
|
43
74
|
enabled?: boolean;
|
|
44
75
|
/** Directory holding local function files, relative to the config file. */
|
|
@@ -53,6 +84,10 @@ export type GlobalConfig = {
|
|
|
53
84
|
config?: Record<string, any>;
|
|
54
85
|
cors_allowed_origins?: string | string[];
|
|
55
86
|
};
|
|
87
|
+
/** `[[agents]]` tables; validated by `agent/config.ts` at startup. */
|
|
88
|
+
agents?: Record<string, any>[];
|
|
89
|
+
/** `[agents_options]`: sessions_dir, retention, max_depth, allow_unauthenticated. */
|
|
90
|
+
agents_options?: Record<string, any>;
|
|
56
91
|
} & Record<string, any>;
|
|
57
92
|
export type AutodiscoverConfig = {
|
|
58
93
|
udp: {
|
|
@@ -135,6 +170,11 @@ export type ServerInfo = {
|
|
|
135
170
|
enabled: boolean;
|
|
136
171
|
count: number;
|
|
137
172
|
};
|
|
173
|
+
/** Agent surface: a flag and a count, for the same ANNOUNCE-size reason. */
|
|
174
|
+
agents?: {
|
|
175
|
+
enabled: boolean;
|
|
176
|
+
count: number;
|
|
177
|
+
};
|
|
138
178
|
};
|
|
139
179
|
export type EventStream = {
|
|
140
180
|
event: string;
|
|
@@ -161,6 +201,20 @@ export type State = {
|
|
|
161
201
|
config: Config;
|
|
162
202
|
sessions: Map<string, Session>;
|
|
163
203
|
workspaceState: import('./utils/workspaceState').WorkspaceState;
|
|
204
|
+
/** Epoch ms when `createServer` began, reported on `/buttress/status`. */
|
|
205
|
+
startedAt: number;
|
|
206
|
+
/** The compatibility endpoints this process serves (env var or config flag). */
|
|
207
|
+
endpoints: {
|
|
208
|
+
openaiCompat: boolean;
|
|
209
|
+
anthropicMessages: boolean;
|
|
210
|
+
typesafeCompat: boolean;
|
|
211
|
+
};
|
|
212
|
+
/** Local functions; null when the feature is off or failed to start. */
|
|
213
|
+
functionsService: import('./functions').FunctionsService | null;
|
|
214
|
+
/** Config-defined agents; null when none are configured or they failed to start. */
|
|
215
|
+
agentsService: import('./agent/types').AgentsService | null;
|
|
216
|
+
/** This machine, read once at startup for `/buttress/status`. */
|
|
217
|
+
host: import('./utils/hostFacts').HostFacts;
|
|
164
218
|
};
|
|
165
219
|
type ButtressSingleton = SingletonBase & {
|
|
166
220
|
store: State;
|
package/lib/ui/host.d.ts
ADDED
|
@@ -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,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/config.d.ts
CHANGED
|
@@ -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;
|
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[];
|
|
@@ -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;
|