@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,73 @@
|
|
|
1
|
+
// Triage a support ticket with this server's typed decision model: which team
|
|
2
|
+
// takes it, how urgent it is, and two yes/no flags — calibrated probabilities
|
|
3
|
+
// from one forward pass per answer, no text generation and no output parsing.
|
|
4
|
+
//
|
|
5
|
+
// Needs a ggml-decision generator in the server config, e.g.:
|
|
6
|
+
//
|
|
7
|
+
// [[generators]]
|
|
8
|
+
// type = "ggml-decision"
|
|
9
|
+
// [generators.model]
|
|
10
|
+
// repo_id = "ggml-org/Julia-1-GGUF"
|
|
11
|
+
// filename = "Julia-1-Q8_0.gguf"
|
|
12
|
+
//
|
|
13
|
+
// curl -X POST http://<host>:<port>/functions/classify-ticket \
|
|
14
|
+
// -H 'Content-Type: application/json' \
|
|
15
|
+
// -d '{"message": "I was charged twice and nobody replies. I want my money back today."}'
|
|
16
|
+
// # → { "result": { "route": "billing", "urgency": 1.53, "urgencyLabel": "Today", "angry": true, … } }
|
|
17
|
+
|
|
18
|
+
export const meta: ButtressFunctionMeta = {
|
|
19
|
+
description:
|
|
20
|
+
'Triage a support ticket: route it to a team, rate its urgency, and flag anger or a refund request',
|
|
21
|
+
parameters: {
|
|
22
|
+
type: 'object',
|
|
23
|
+
properties: {
|
|
24
|
+
message: { type: 'string', description: 'The customer message' },
|
|
25
|
+
model: { type: 'string', description: 'Optional decision model override' },
|
|
26
|
+
},
|
|
27
|
+
required: ['message'],
|
|
28
|
+
},
|
|
29
|
+
timeout: '2m',
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
type Input = { message: string; model?: string }
|
|
33
|
+
|
|
34
|
+
const URGENCY_LEVELS = ['Can wait', 'This week', 'Today', 'Right now']
|
|
35
|
+
|
|
36
|
+
export default async function ({ message, model }: Input, context: ButtressFunctionContext) {
|
|
37
|
+
const { answers, usage } = await context.buttress.decide({
|
|
38
|
+
model,
|
|
39
|
+
state: message,
|
|
40
|
+
questions: {
|
|
41
|
+
route: {
|
|
42
|
+
type: 'choice',
|
|
43
|
+
instructions: 'Which team should handle this?',
|
|
44
|
+
criteria: {
|
|
45
|
+
billing: 'Payments, charges, refunds',
|
|
46
|
+
shipping: 'Delivery and tracking',
|
|
47
|
+
technical: 'App or website problems',
|
|
48
|
+
account: 'Login and profile',
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
urgency: { type: 'score', instructions: 'How urgent is this?', criteria: URGENCY_LEVELS },
|
|
52
|
+
angry: { type: 'noul', instructions: 'The customer is angry' },
|
|
53
|
+
wants_refund: { type: 'noul', instructions: 'The customer asks for a refund' },
|
|
54
|
+
},
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
// `answers.route.choice` is typed as 'billing' | 'shipping' | 'technical' | 'account'
|
|
58
|
+
return {
|
|
59
|
+
route: answers.route.choice,
|
|
60
|
+
routeConfidence: answers.route.confidence,
|
|
61
|
+
// Expected level index (0 = "Can wait"), can fall between two levels
|
|
62
|
+
urgency: answers.urgency.score,
|
|
63
|
+
urgencyLabel: URGENCY_LEVELS[Math.round(answers.urgency.score)],
|
|
64
|
+
angry: answers.angry.noul >= 0.5,
|
|
65
|
+
wantsRefund: answers.wants_refund.noul >= 0.5,
|
|
66
|
+
probabilities: {
|
|
67
|
+
route: answers.route.probabilities,
|
|
68
|
+
angry: answers.angry.noul,
|
|
69
|
+
wantsRefund: answers.wants_refund.noul,
|
|
70
|
+
},
|
|
71
|
+
inputTokens: usage.input_tokens,
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Local functions can drive a configured agent (see [[agents]] in the server
|
|
2
|
+
// config): context.agents.run executes a full tool-using LLM loop server-side
|
|
3
|
+
// and returns the final answer plus the session id for follow-ups.
|
|
4
|
+
//
|
|
5
|
+
// The run shares this call's lifetime — the function's deadline (meta.timeout)
|
|
6
|
+
// and client disconnects abort it, including in-flight tool calls. Long agent
|
|
7
|
+
// work belongs in a daemon (no deadline) instead.
|
|
8
|
+
|
|
9
|
+
export const meta = {
|
|
10
|
+
description: 'Ask a configured agent to investigate something and return its conclusion.',
|
|
11
|
+
parameters: {
|
|
12
|
+
type: 'object',
|
|
13
|
+
properties: {
|
|
14
|
+
agent: { type: 'string', description: 'Configured agent name' },
|
|
15
|
+
question: { type: 'string' },
|
|
16
|
+
sessionId: { type: 'string', description: 'Continue an earlier session (optional)' },
|
|
17
|
+
},
|
|
18
|
+
required: ['agent', 'question'],
|
|
19
|
+
},
|
|
20
|
+
timeout: '10m',
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default async (
|
|
24
|
+
{ agent, question, sessionId }: { agent: string; question: string; sessionId?: string },
|
|
25
|
+
context: any,
|
|
26
|
+
) => {
|
|
27
|
+
const result = await context.agents.run(agent, {
|
|
28
|
+
prompt: question,
|
|
29
|
+
sessionId,
|
|
30
|
+
// Progress mirrors onto this call's SSE stream automatically; onEvent is
|
|
31
|
+
// available for custom handling of the raw pi events.
|
|
32
|
+
})
|
|
33
|
+
return {
|
|
34
|
+
conclusion: result.content,
|
|
35
|
+
sessionId: result.sessionId,
|
|
36
|
+
turns: result.usage.totalTurns,
|
|
37
|
+
stopReason: result.stopReason,
|
|
38
|
+
}
|
|
39
|
+
}
|
package/config/sample.toml
CHANGED
|
@@ -29,6 +29,13 @@ enabled = true
|
|
|
29
29
|
# cors_allowed_origins = ["http://localhost:3000", "https://example.com"]
|
|
30
30
|
# cors_allowed_origins = "*"
|
|
31
31
|
|
|
32
|
+
# TypeSafe-compatible System One API (`/typesafe-compat/v1/systemone`) for the
|
|
33
|
+
# TypeSafe SDKs: TYPESAFE_BASE_URL=http://<host>:<port>/typesafe-compat.
|
|
34
|
+
# Serves the decision generators below.
|
|
35
|
+
# [typesafe_compat]
|
|
36
|
+
# enabled = true
|
|
37
|
+
# cors_allowed_origins = ["http://localhost:3000"]
|
|
38
|
+
|
|
32
39
|
# Local functions (EXPERIMENTAL): .ts/.js files exposed as MCP tools and HTTP
|
|
33
40
|
# endpoints. Interfaces may change between releases.
|
|
34
41
|
# See the "Local Functions" section of README.md; ready-to-copy examples live
|
|
@@ -45,6 +52,31 @@ enabled = true
|
|
|
45
52
|
# api_base = "https://example.internal"
|
|
46
53
|
# api_keys = ["a-long-random-string"] # e.g. consumed by function-samples/_auth.ts
|
|
47
54
|
|
|
55
|
+
# Agents (EXPERIMENTAL): pi-based LLM loops running in this server, with local
|
|
56
|
+
# functions (and MCP servers) as tools and config-scoped sessions on disk.
|
|
57
|
+
# Local functions call them via context.agents.run(...); an interactive CLI
|
|
58
|
+
# (`bricks-buttress agent <name> -c <config>`) drives the same agents.
|
|
59
|
+
# See the "Agents" section of README.md.
|
|
60
|
+
# [[agents]]
|
|
61
|
+
# name = "ops-assistant"
|
|
62
|
+
# model = "buttress/ggml-org/gpt-oss-20b-GGUF" # provider/model-id, split on the FIRST slash
|
|
63
|
+
# system_prompt = "You are an ops automation agent. Use the provided tools."
|
|
64
|
+
# tools = ["host-info"] # local function names (explicit; no wildcard)
|
|
65
|
+
# local_devices = false # opt in: devices_scan + devtools (inspection AND control)
|
|
66
|
+
# max_turns = 30
|
|
67
|
+
# decision_model = "buttress/<ggml-decision id>" # adds the decision_evaluate tool
|
|
68
|
+
# decision_assistance = false # opt in: skip tool definitions on runs that need no tool
|
|
69
|
+
# [agents.mcp_servers.example]
|
|
70
|
+
# url = "https://example.com/mcp/"
|
|
71
|
+
# optional = true
|
|
72
|
+
|
|
73
|
+
# [agents_options]
|
|
74
|
+
# sessions_dir = "./.buttress-agent/sessions"
|
|
75
|
+
# session_max_age = "30d" # retention; 0 disables
|
|
76
|
+
# session_max_count = 500 # per agent; 0 disables
|
|
77
|
+
# max_depth = 2 # function -> agent -> function chain cap
|
|
78
|
+
# allow_unauthenticated = false # serve /agents on an UNBOUND server
|
|
79
|
+
|
|
48
80
|
[runtime]
|
|
49
81
|
cache_dir = "./.buttress-cache"
|
|
50
82
|
# huggingface_token = "hf_xx"
|
|
@@ -55,6 +87,12 @@ cache_type_k = "q8_0"
|
|
|
55
87
|
cache_type_v = "q8_0"
|
|
56
88
|
n_threads = 6
|
|
57
89
|
|
|
90
|
+
# Optional ggml-llm multi-GPU defaults (also supported under [generators.model]).
|
|
91
|
+
# split_mode = "layer" # none / layer (default) / row / tensor (experimental)
|
|
92
|
+
# tensor_split = [3, 1] # per-GPU proportions; also accepts "3,1"
|
|
93
|
+
# Tensor mode requires a compatible GPU backend and model architecture.
|
|
94
|
+
# TODO: Expose these Buttress-only options in the LLM (GGML) generator.
|
|
95
|
+
|
|
58
96
|
# Delay before releasing context when refCount reaches 0 (default: 10000ms)
|
|
59
97
|
# Set to 0 for immediate release
|
|
60
98
|
context_release_delay_ms = 30000
|
|
@@ -100,6 +138,7 @@ n_ctx = 12800 # Max: 131072
|
|
|
100
138
|
|
|
101
139
|
[[generators]]
|
|
102
140
|
type = "ggml-llm"
|
|
141
|
+
# enabled = false # keep this entry in the file but skip it at startup (default: true)
|
|
103
142
|
[generators.backend]
|
|
104
143
|
variant_preference = ["cuda", "vulkan", "snapdragon", "default"]
|
|
105
144
|
gpu_memory_fraction = 0.95
|
|
@@ -271,3 +310,31 @@ use_gpu = true
|
|
|
271
310
|
[generators.runtime]
|
|
272
311
|
n_ctx = 8192
|
|
273
312
|
vocoder_batch_size = 4096
|
|
313
|
+
|
|
314
|
+
# Typed decision generators
|
|
315
|
+
#
|
|
316
|
+
# A typed decision model (System One) answers `choice` / `score` / `noul`
|
|
317
|
+
# questions about a state with calibrated probabilities — one forward pass per
|
|
318
|
+
# answer, no generated tokens. Clients reach it through the Typed Decision
|
|
319
|
+
# (GGML) generator, and local functions through `context.buttress.decide`.
|
|
320
|
+
# Uncomment to enable:
|
|
321
|
+
#
|
|
322
|
+
# [[generators]]
|
|
323
|
+
# type = "ggml-decision"
|
|
324
|
+
# [generators.model]
|
|
325
|
+
# repo_id = "ggml-org/Julia-1-GGUF"
|
|
326
|
+
# filename = "Julia-1-Q8_0.gguf"
|
|
327
|
+
# download = true
|
|
328
|
+
# # Models whose prompt takes images also need a projector:
|
|
329
|
+
# # mmproj_repo_id = "..."
|
|
330
|
+
# # mmproj_filename = "mmproj-....gguf"
|
|
331
|
+
# # system_one models with a classification head (rank_head readout):
|
|
332
|
+
# # rank_pooling = true
|
|
333
|
+
# [generators.runtime]
|
|
334
|
+
# n_ctx = 4096
|
|
335
|
+
#
|
|
336
|
+
# A model that also generates (Jevling, for one) can be configured as both a
|
|
337
|
+
# ggml-llm and a ggml-decision. To load it once, add to the decision entry
|
|
338
|
+
# [generators.backend]
|
|
339
|
+
# share_llm_context = true
|
|
340
|
+
# (decisions then compete with completions for the LLM's slots; see README).
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `bricks-buttress agent` — chat client for configured agents.
|
|
3
|
+
*
|
|
4
|
+
* A thin remote client of the /agents endpoints on a RUNNING server: the agent
|
|
5
|
+
* loop, tools, and sessions all live server-side. Reads the config file only
|
|
6
|
+
* to find the server port and the local runtime token (written 0600 by the
|
|
7
|
+
* server at startup), so the same-host flow needs zero extra setup.
|
|
8
|
+
*
|
|
9
|
+
* On a real terminal the pi-tui chat UI runs (see tui.ts); pipes/scripts (or
|
|
10
|
+
* --plain) get a line-based streaming renderer with identical semantics.
|
|
11
|
+
*
|
|
12
|
+
* bricks-buttress agent list configured agents
|
|
13
|
+
* bricks-buttress agent <name> chat (new session)
|
|
14
|
+
* bricks-buttress agent <name> --session <id> continue a session
|
|
15
|
+
* bricks-buttress agent <name> --fork <id> fork then chat
|
|
16
|
+
* bricks-buttress agent <name> --sessions list sessions
|
|
17
|
+
* Common: -c/--config <path|toml>, --url <server>, --token <token>, --plain
|
|
18
|
+
*/
|
|
19
|
+
export declare const runAgentCommand: (argv: string[]) => Promise<void>;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared client plumbing for the `bricks-buttress agent` front-ends (line mode
|
|
3
|
+
* and the pi-tui chat): config/token discovery, authenticated requests, SSE
|
|
4
|
+
* parsing, and the streaming run call. UI-free on purpose.
|
|
5
|
+
*/
|
|
6
|
+
export type Connection = {
|
|
7
|
+
baseUrl: string;
|
|
8
|
+
token: string | null;
|
|
9
|
+
};
|
|
10
|
+
/** Same config-argument semantics as the server CLI: a path or inline TOML. */
|
|
11
|
+
export declare const loadClientConfig: (configArg: string | null) => {
|
|
12
|
+
config: import("../types").Config;
|
|
13
|
+
configDir: string;
|
|
14
|
+
agentsConfig: import("./types").AgentsConfig | null;
|
|
15
|
+
};
|
|
16
|
+
export declare const readRuntimeToken: (file: string) => string | null;
|
|
17
|
+
export declare const request: (connection: Connection, route: string, init?: RequestInit) => Promise<Response>;
|
|
18
|
+
/** Minimal text/event-stream reader: yields { event, data } frames. */
|
|
19
|
+
export declare function readSse(body: ReadableStream<Uint8Array>): AsyncGenerator<{
|
|
20
|
+
event: string;
|
|
21
|
+
data: string;
|
|
22
|
+
}, void, unknown>;
|
|
23
|
+
export type RunFrame = {
|
|
24
|
+
event: string;
|
|
25
|
+
payload: any;
|
|
26
|
+
};
|
|
27
|
+
export type RunOutcome = {
|
|
28
|
+
kind: 'result';
|
|
29
|
+
result: any;
|
|
30
|
+
} | {
|
|
31
|
+
kind: 'error';
|
|
32
|
+
message: string;
|
|
33
|
+
sessionId: string | null;
|
|
34
|
+
} | {
|
|
35
|
+
kind: 'aborted';
|
|
36
|
+
sessionId: string | null;
|
|
37
|
+
} | {
|
|
38
|
+
kind: 'disconnected';
|
|
39
|
+
sessionId: string | null;
|
|
40
|
+
};
|
|
41
|
+
export type StreamRunBody = {
|
|
42
|
+
prompt: string;
|
|
43
|
+
sessionId?: string;
|
|
44
|
+
fork?: boolean;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Whether a `--fork` chat has actually forked yet, i.e. whether the flag has
|
|
48
|
+
* been consumed and later prompts should continue rather than fork again.
|
|
49
|
+
*
|
|
50
|
+
* The server only forks once it accepts the run, and announces the new id
|
|
51
|
+
* (`session` event, or `result.sessionId` on an older server). A run that
|
|
52
|
+
* failed before that — a rejected token, an unknown agent, a dropped
|
|
53
|
+
* connection, an immediate Ctrl+C — leaves the client on the SOURCE session, so
|
|
54
|
+
* the flag has to survive: clearing it would make the user's retry append to
|
|
55
|
+
* the transcript `--fork` exists to keep untouched.
|
|
56
|
+
*/
|
|
57
|
+
export declare const forkTookEffect: (startedFrom: string | null, sessionId: string | null) => boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Run a prompt over the streaming endpoint, delivering every parsed frame to
|
|
60
|
+
* `onFrame` and returning the terminal outcome. Frame handler errors are the
|
|
61
|
+
* caller's problem — they propagate.
|
|
62
|
+
*/
|
|
63
|
+
export declare const streamRun: (connection: Connection, name: string, body: StreamRunBody, { signal, onFrame }: {
|
|
64
|
+
signal?: AbortSignal;
|
|
65
|
+
onFrame: (frame: RunFrame) => void;
|
|
66
|
+
}) => Promise<RunOutcome>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import '@earendil-works/pi-codemode/worker';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { MutableModels } from '@earendil-works/pi-ai';
|
|
2
|
+
export declare function createAgentCodemodeTool({ tools, declaredNames, getState, models, }: {
|
|
3
|
+
tools: any[];
|
|
4
|
+
declaredNames: Set<string>;
|
|
5
|
+
getState: () => any;
|
|
6
|
+
models: MutableModels;
|
|
7
|
+
}): Promise<any>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Config } from '../types';
|
|
2
|
+
import type { AgentsConfig } from './types';
|
|
3
|
+
export declare class AgentsConfigError extends Error {
|
|
4
|
+
constructor(message: string);
|
|
5
|
+
}
|
|
6
|
+
export type ResolveAgentsOptions = {
|
|
7
|
+
/** Directory relative paths resolve against — the `--config` file's directory. */
|
|
8
|
+
configDir?: string;
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Resolve `[[agents]]` + `[agents_options]` into the runtime shape. Returns
|
|
12
|
+
* null when no agents are configured. Invalid definitions throw — a wrong
|
|
13
|
+
* agent config should stop startup, not silently drop an agent.
|
|
14
|
+
*/
|
|
15
|
+
export declare const resolveAgentsConfig: (config: Config, { configDir }?: ResolveAgentsOptions) => AgentsConfig | null;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { AgentsFunctionApi, AgentsService } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* The `context.agents` surface local functions receive. Binds the caller's
|
|
4
|
+
* abort signal (function deadline / client disconnect cascades into the run),
|
|
5
|
+
* its emit stream, and its agent-invocation depth.
|
|
6
|
+
*/
|
|
7
|
+
export declare const buildAgentsFunctionApi: (service: AgentsService | null | undefined, { signal, emit, depth, }: {
|
|
8
|
+
signal: AbortSignal;
|
|
9
|
+
emit?: (event: string, data?: unknown) => void;
|
|
10
|
+
depth: number;
|
|
11
|
+
}) => AgentsFunctionApi;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* System One decisions for Buttress agents.
|
|
3
|
+
*
|
|
4
|
+
* - Buttress decision generators as pi-ai classifier models (`buttress/<id>`),
|
|
5
|
+
* answered in-process. Codemode `models.classify()` and the pieces below go
|
|
6
|
+
* through pi-ai, so a hosted classifier (`typesafe/jev-latest`) is
|
|
7
|
+
* interchangeable with a local one.
|
|
8
|
+
* - `decision_evaluate`: a tool the model calls itself with typed questions.
|
|
9
|
+
* - Tool prefilter (`decision_assistance`): before a run, three yes/no questions
|
|
10
|
+
* decide whether its request needs tools at all; a conversational request
|
|
11
|
+
* is sent without tool definitions, which shortens the prompt.
|
|
12
|
+
*
|
|
13
|
+
* A decision is evidence, never permission: it can only withhold tools for one
|
|
14
|
+
* run, never grant one, and any failure keeps the full tool set.
|
|
15
|
+
*/
|
|
16
|
+
import type { ClassifierModel, ClassifierResult, MutableModels, ProviderClassifier } from '@earendil-works/pi-ai';
|
|
17
|
+
import type { AgentMessage } from '@earendil-works/pi-agent-core';
|
|
18
|
+
import type { Config, DecisionRequest, DecisionResult } from '../types';
|
|
19
|
+
import type { AgentDefinition } from './types';
|
|
20
|
+
export declare const BUTTRESS_DECISION_API = "buttress-decision";
|
|
21
|
+
export type DecideFn = (request: DecisionRequest & {
|
|
22
|
+
model?: string;
|
|
23
|
+
}) => Promise<DecisionResult>;
|
|
24
|
+
/** One pi-ai classifier model per configured decision generator. */
|
|
25
|
+
export declare const buttressClassifierModels: (config: Config, provider: string, baseUrl: string) => ClassifierModel<string>[];
|
|
26
|
+
/**
|
|
27
|
+
* Answers classifier requests with this server's decision generators. Never
|
|
28
|
+
* rejects. A decision cannot be stopped once started, so a caller that gives
|
|
29
|
+
* up (timeout, abort) leaves it running; until it settles, new requests for
|
|
30
|
+
* that model fail fast instead of piling up behind it (a cold model load
|
|
31
|
+
* would otherwise start one more per agent run).
|
|
32
|
+
*/
|
|
33
|
+
export declare const createButtressClassifier: (decide: DecideFn) => ProviderClassifier;
|
|
34
|
+
/** The agent's decision model, or null when it has none. Throws on an unknown one. */
|
|
35
|
+
export declare const resolveDecisionModel: (models: MutableModels, agent: AgentDefinition) => ClassifierModel<any> | null;
|
|
36
|
+
export declare const DECISION_TOOL_NAME = "decision_evaluate";
|
|
37
|
+
/** Typed questions about a state the model supplies. Results never authorize actions. */
|
|
38
|
+
export declare const createDecisionEvaluateTool: (models: MutableModels, model: ClassifierModel<any>) => {
|
|
39
|
+
name: string;
|
|
40
|
+
label: string;
|
|
41
|
+
description: string;
|
|
42
|
+
parameters: {
|
|
43
|
+
type: string;
|
|
44
|
+
properties: {
|
|
45
|
+
state: {
|
|
46
|
+
type: string;
|
|
47
|
+
description: string;
|
|
48
|
+
};
|
|
49
|
+
questions: {
|
|
50
|
+
type: string;
|
|
51
|
+
description: string;
|
|
52
|
+
additionalProperties: {
|
|
53
|
+
type: string;
|
|
54
|
+
properties: {
|
|
55
|
+
type: {
|
|
56
|
+
type: string;
|
|
57
|
+
enum: string[];
|
|
58
|
+
};
|
|
59
|
+
instructions: {
|
|
60
|
+
type: string;
|
|
61
|
+
};
|
|
62
|
+
criteria: {};
|
|
63
|
+
};
|
|
64
|
+
required: string[];
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
required: string[];
|
|
69
|
+
};
|
|
70
|
+
execute: (_toolCallId: string, args: any, signal?: AbortSignal) => Promise<{
|
|
71
|
+
content: {
|
|
72
|
+
type: string;
|
|
73
|
+
text: string;
|
|
74
|
+
}[];
|
|
75
|
+
details: {};
|
|
76
|
+
}>;
|
|
77
|
+
};
|
|
78
|
+
/** Every probability must be below this to send a run without tools. */
|
|
79
|
+
export declare const PREFILTER_THRESHOLD = 0.35;
|
|
80
|
+
/**
|
|
81
|
+
* The prefilter's evidence: the request, the nearest user/assistant exchanges
|
|
82
|
+
* that fit, and only counts for what is left out. Tool arguments and results,
|
|
83
|
+
* reasoning and system messages are never sent.
|
|
84
|
+
*/
|
|
85
|
+
export declare const buildPrefilterState: (prompt: string, history: readonly AgentMessage[], toolNames: string[]) => {
|
|
86
|
+
latestRequest: string;
|
|
87
|
+
recentConversation: {
|
|
88
|
+
role: string;
|
|
89
|
+
text: string;
|
|
90
|
+
}[];
|
|
91
|
+
availableTools: string[];
|
|
92
|
+
omittedContext: {
|
|
93
|
+
olderConversation: number;
|
|
94
|
+
toolPayloads: number;
|
|
95
|
+
};
|
|
96
|
+
};
|
|
97
|
+
export type PrefilterOutcome = {
|
|
98
|
+
prune: boolean;
|
|
99
|
+
reason: 'conversational' | 'action-or-uncertain' | 'unavailable' | 'skipped';
|
|
100
|
+
detail?: string;
|
|
101
|
+
};
|
|
102
|
+
/** Prune only on clear "no" answers to every question; anything else keeps every tool. */
|
|
103
|
+
export declare const interpretPrefilter: (result: ClassifierResult) => PrefilterOutcome;
|
|
104
|
+
/** Decide whether this run's request can go out without tool definitions. */
|
|
105
|
+
export declare const runToolPrefilter: ({ models, model, prompt, history, toolNames, timeoutMs, signal, }: {
|
|
106
|
+
models: MutableModels;
|
|
107
|
+
model: ClassifierModel<any>;
|
|
108
|
+
prompt: string;
|
|
109
|
+
history: readonly AgentMessage[];
|
|
110
|
+
toolNames: string[];
|
|
111
|
+
timeoutMs: number;
|
|
112
|
+
signal?: AbortSignal;
|
|
113
|
+
}) => Promise<PrefilterOutcome>;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { WorkspaceState } from '../utils/workspaceState';
|
|
2
|
+
/** Select only the bound workspace's credential; never reuse an inbound Buttress (k=ba) JWT. */
|
|
3
|
+
export declare const resolveDeviceAccessToken: (address: string, port: number, state?: WorkspaceState, signal?: AbortSignal) => Promise<string | undefined>;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { AgentFunctionTool } from './tools';
|
|
2
|
+
import type { AgentDefinition } from './types';
|
|
3
|
+
import type { WorkspaceState } from '../utils/workspaceState';
|
|
4
|
+
/** Two tools regardless of device count; no remote tool-list expansion or persistent sockets. */
|
|
5
|
+
export declare const buildLocalDeviceTools: (agent: Pick<AgentDefinition, 'localDevices'>, configDir: string, workspaceState?: WorkspaceState) => AgentFunctionTool[];
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-process loopback for agent → buttress model traffic, plus the internal
|
|
3
|
+
* bearer token that authorizes it (and same-host CLIs) without a cloud-minted
|
|
4
|
+
* workspace JWT — a bound server only holds the issuer *public* key and
|
|
5
|
+
* cannot mint its own JWTs.
|
|
6
|
+
*/
|
|
7
|
+
export type ElysiaHandle = (request: Request) => Response | Promise<Response>;
|
|
8
|
+
export declare const generateInternalToken: () => string;
|
|
9
|
+
export declare const makeInternalTokenVerifier: (token: string) => (candidate: string | null | undefined) => boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Write the internal token where a same-host `bricks-buttress agent` CLI can
|
|
12
|
+
* read it (0600, next to the sessions dir — config-scoped like everything
|
|
13
|
+
* else about agents). Rewritten on every startup: the token is ephemeral.
|
|
14
|
+
*/
|
|
15
|
+
export declare const writeRuntimeTokenFile: (file: string, token: string) => void;
|
|
16
|
+
/**
|
|
17
|
+
* Fetch-shaped dispatcher into the local Elysia app. The request never touches
|
|
18
|
+
* a socket; the internal token replaces whatever Authorization the client
|
|
19
|
+
* library synthesized so the shared auth guard accepts it on bound servers.
|
|
20
|
+
*/
|
|
21
|
+
export declare const createLoopbackFetch: (getHandle: () => ElysiaHandle | null, token: string) => typeof fetch;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { AgentMcpServerConfig } from './types';
|
|
2
|
+
export type McpAgentTool = {
|
|
3
|
+
name: string;
|
|
4
|
+
label: string;
|
|
5
|
+
description: string;
|
|
6
|
+
parameters: Record<string, any>;
|
|
7
|
+
execute: (toolCallId: string, args: any, signal?: AbortSignal, onUpdate?: any) => Promise<any>;
|
|
8
|
+
_mcpServer: string;
|
|
9
|
+
_mcpToolName: string;
|
|
10
|
+
_mcpExposure: string;
|
|
11
|
+
};
|
|
12
|
+
export type McpManager = {
|
|
13
|
+
/**
|
|
14
|
+
* Tools for every configured server of an agent. Fail-closed: a server that
|
|
15
|
+
* won't connect rejects the whole call unless it is marked `optional`.
|
|
16
|
+
*/
|
|
17
|
+
toolsFor: (agentName: string, servers: Record<string, AgentMcpServerConfig>) => Promise<McpAgentTool[]>;
|
|
18
|
+
dispose: () => Promise<void>;
|
|
19
|
+
};
|
|
20
|
+
export declare const createMcpManager: ({ configDir }: {
|
|
21
|
+
configDir: string;
|
|
22
|
+
}) => McpManager;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type Model, type MutableModels } from '@earendil-works/pi-ai';
|
|
2
|
+
import type { Config } from '../types';
|
|
3
|
+
import type { AgentDefinition } from './types';
|
|
4
|
+
import { type DecideFn } from './decisions';
|
|
5
|
+
export declare const BUTTRESS_PROVIDER_ID = "buttress";
|
|
6
|
+
/**
|
|
7
|
+
* Loopback requests never leave the process: this host name only exists so the
|
|
8
|
+
* OpenAI client has a syntactically valid base URL to resolve paths against.
|
|
9
|
+
* The injected fetch dispatches the request straight into the Elysia app.
|
|
10
|
+
*/
|
|
11
|
+
export declare const LOOPBACK_BASE_URL = "http://buttress.internal/oai-compat/v1";
|
|
12
|
+
/** Fetch-shaped dispatcher into the local Elysia app (see loopback.ts). */
|
|
13
|
+
export type LoopbackFetch = typeof fetch;
|
|
14
|
+
/**
|
|
15
|
+
* A `Models` collection with every pi built-in provider (env-var API keys)
|
|
16
|
+
* plus the `buttress` pseudo-provider whose models are the configured LLM
|
|
17
|
+
* generators, streamed over the in-process OpenAI-compat loopback, and the
|
|
18
|
+
* configured decision generators as classifier models, answered by `decide`.
|
|
19
|
+
*/
|
|
20
|
+
export declare const buildAgentModels: (config: Config, loopbackFetch: LoopbackFetch, decide: DecideFn) => MutableModels;
|
|
21
|
+
/** Resolve an agent's model reference against the registry, with clear errors. */
|
|
22
|
+
export declare const resolveAgentModel: (models: MutableModels, agent: AgentDefinition) => Model<any>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Config } from '../types';
|
|
2
|
+
import type { FunctionsService } from '../functions';
|
|
3
|
+
import type { FunctionRuntime } from '../functions/types';
|
|
4
|
+
import { type ElysiaHandle } from './loopback';
|
|
5
|
+
import { type AgentsConfig, type AgentsService } from './types';
|
|
6
|
+
export type CreateAgentsServiceOptions = {
|
|
7
|
+
config: Config;
|
|
8
|
+
agentsConfig: AgentsConfig;
|
|
9
|
+
/** Server runtime handed to function tool calls; `agents` is added here. */
|
|
10
|
+
runtimeBase: Omit<FunctionRuntime, 'agents'>;
|
|
11
|
+
/** Late-bound: the Elysia app exists only after the service (circular wiring). */
|
|
12
|
+
getHandle: () => ElysiaHandle | null;
|
|
13
|
+
/** Late-bound for the same reason (functions service needs the agents service). */
|
|
14
|
+
getFunctions: () => FunctionsService | null;
|
|
15
|
+
};
|
|
16
|
+
export declare const createAgentsService: ({ config, agentsConfig, runtimeBase, getHandle, getFunctions, }: CreateAgentsServiceOptions) => AgentsService;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { AgentMessage } from '@earendil-works/pi-agent-core';
|
|
2
|
+
import type { AgentSessionSummary, AgentsConfig } from './types';
|
|
3
|
+
type SessionMeta = {
|
|
4
|
+
id: string;
|
|
5
|
+
createdAt: number;
|
|
6
|
+
parentSessionId?: string;
|
|
7
|
+
path: string;
|
|
8
|
+
modifiedAt: number;
|
|
9
|
+
/** Written by pi-agent-core's v4 repo; converted on the first append. */
|
|
10
|
+
legacy: boolean;
|
|
11
|
+
};
|
|
12
|
+
export type AgentSession = {
|
|
13
|
+
metadata: SessionMeta;
|
|
14
|
+
/** True once the file is in the current format with no torn tail. */
|
|
15
|
+
appendable: boolean;
|
|
16
|
+
};
|
|
17
|
+
export type AgentSessionStore = {
|
|
18
|
+
create: (agentName: string) => Promise<AgentSession>;
|
|
19
|
+
/** Throws when the id is unknown within the agent's scope. */
|
|
20
|
+
open: (agentName: string, sessionId: string) => Promise<AgentSession>;
|
|
21
|
+
fork: (agentName: string, sessionId: string) => Promise<AgentSession>;
|
|
22
|
+
append: (session: AgentSession, message: AgentMessage) => Promise<void>;
|
|
23
|
+
list: (agentName: string, limit?: number) => Promise<AgentSessionSummary[]>;
|
|
24
|
+
/** Reconstruct the pi message history for continuing a session. */
|
|
25
|
+
messages: (session: AgentSession) => Promise<AgentMessage[]>;
|
|
26
|
+
/** Retention sweep across every configured agent scope. */
|
|
27
|
+
sweep: (agentNames: string[]) => Promise<number>;
|
|
28
|
+
};
|
|
29
|
+
export declare const createAgentSessionStore: (config: AgentsConfig) => AgentSessionStore;
|
|
30
|
+
export {};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { FunctionsService } from '../functions';
|
|
2
|
+
import type { FunctionRuntime } from '../functions/types';
|
|
3
|
+
import type { AgentDefinition } from './types';
|
|
4
|
+
/**
|
|
5
|
+
* Local functions as agent tools. Each tool call funnels through the same
|
|
6
|
+
* executor as MCP/HTTP calls (lazy reload, per-call scratch dir, spawn
|
|
7
|
+
* tracking, the function's own deadline), and the run's abort signal chains
|
|
8
|
+
* in. Granting a function to an agent in config IS the authorization — no
|
|
9
|
+
* auth guard runs below the HTTP/MCP boundary.
|
|
10
|
+
*/
|
|
11
|
+
export type FunctionToolContext = {
|
|
12
|
+
functions: FunctionsService;
|
|
13
|
+
runtime: FunctionRuntime;
|
|
14
|
+
/** Agent-invocation depth for calls made BY this run's tools. */
|
|
15
|
+
depth: number;
|
|
16
|
+
/**
|
|
17
|
+
* Forwards `context.emit` progress as agent events. `toolCallId` tells calls of
|
|
18
|
+
* one tool apart: pi runs the calls of a turn in parallel.
|
|
19
|
+
*/
|
|
20
|
+
onToolEmit?: (toolName: string, event: string, data: unknown, toolCallId: string) => void;
|
|
21
|
+
};
|
|
22
|
+
export type AgentFunctionTool = {
|
|
23
|
+
name: string;
|
|
24
|
+
label: string;
|
|
25
|
+
description: string;
|
|
26
|
+
parameters: Record<string, any>;
|
|
27
|
+
execute: (toolCallId: string, args: any, signal?: AbortSignal) => Promise<any>;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Resolve the agent's configured tool list against the live registry.
|
|
31
|
+
* Fail-closed: a listed function that is missing (deleted, renamed, or the
|
|
32
|
+
* functions feature is off) rejects the run — a headless automation silently
|
|
33
|
+
* improvising around missing tools is worse than a loud failure.
|
|
34
|
+
*/
|
|
35
|
+
export declare const buildFunctionTools: (agent: AgentDefinition, { functions, runtime, depth, onToolEmit }: FunctionToolContext) => Promise<AgentFunctionTool[]>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pi-tui chat front-end for `bricks-buttress agent` — the interactive mode on
|
|
3
|
+
* a real terminal (line mode remains for pipes/scripts via --plain).
|
|
4
|
+
*
|
|
5
|
+
* Layout (main-screen mode, scrollback holds history):
|
|
6
|
+
* [chat log: user prompts, dim thinking, tool lines, markdown answers]
|
|
7
|
+
* [status line / spinner]
|
|
8
|
+
* [editor]
|
|
9
|
+
*/
|
|
10
|
+
import { type Connection } from './client';
|
|
11
|
+
export type AgentTuiOptions = {
|
|
12
|
+
connection: Connection;
|
|
13
|
+
agentName: string;
|
|
14
|
+
sessionId: string | null;
|
|
15
|
+
fork: boolean;
|
|
16
|
+
};
|
|
17
|
+
export declare const runAgentTui: ({ connection, agentName, sessionId: initialSessionId, fork: initialFork, }: AgentTuiOptions) => Promise<void>;
|