@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
package/README.md
CHANGED
|
@@ -162,6 +162,7 @@ Configuration is loaded from a TOML file passed via `--config` / `-c`. Every top
|
|
|
162
162
|
| `[autodiscover]` | LAN UDP / HTTP / mDNS discovery toggles |
|
|
163
163
|
| `[openai_compat]` | Enable `/oai-compat/v1/*` — see [Compatibility Endpoints](#compatibility-endpoints-experimental) |
|
|
164
164
|
| `[anthropic_messages]` | Enable `/anthropic-messages` — see [Compatibility Endpoints](#compatibility-endpoints-experimental)|
|
|
165
|
+
| `[typesafe_compat]` | Enable `/typesafe-compat/v1/*` — see [Compatibility Endpoints](#compatibility-endpoints-experimental) |
|
|
165
166
|
| `[functions]` | Enable local functions — see [Local Functions](#local-functions-experimental) |
|
|
166
167
|
| `[[agents]]` | Config-defined agents — see [Agents](#agents-experimental) |
|
|
167
168
|
| `[[generators]]` | Array of generator instances — one entry per loaded model |
|
|
@@ -496,17 +497,78 @@ vocoder_batch_size = 4096
|
|
|
496
497
|
### `ggml-decision` (typed decisions via `@fugood/llama.node`)
|
|
497
498
|
|
|
498
499
|
Loads a typed decision model (System One — Julia-1, Laya, Kev, Jevling, lev,
|
|
499
|
-
...) and answers `choice` / `score` / `noul`
|
|
500
|
-
calibrated probabilities: one forward pass per
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
functions through `context.buttress.decide`.
|
|
500
|
+
Clef, LiquidAI d1, pplx-decider, ...) and answers `choice` / `score` / `noul`
|
|
501
|
+
questions about a state with calibrated probabilities: one forward pass per
|
|
502
|
+
answer, no generated tokens. Clients reach it through the Typed Decision (GGML)
|
|
503
|
+
generator; local functions through `context.buttress.decide`.
|
|
504
504
|
|
|
505
505
|
The model is checked when its context loads: a GGUF with no decision metadata
|
|
506
506
|
(`<arch>.decision.type`) and no `system_one` template, or one whose decision
|
|
507
507
|
type this build cannot serve, fails the load with the reason. Requests on one
|
|
508
508
|
generator run one at a time.
|
|
509
509
|
|
|
510
|
+
**Requests** follow the TypeSafe `/v1/systemone` shape:
|
|
511
|
+
`{ state, questions, images?, files? }`.
|
|
512
|
+
|
|
513
|
+
- `state` is text, or a JSON object/array (given to the model as JSON text).
|
|
514
|
+
- `questions` maps an id to a `choice`, `score` or `noul` question. A `noul`
|
|
515
|
+
question takes `criteria: { true?, false? }`; `lfm2-d1-omni` also takes
|
|
516
|
+
`yes` / `no`.
|
|
517
|
+
- `images` carries the media: images, or audio clips for a model whose prompt
|
|
518
|
+
takes audio (`lfm2-d1-omni`). `files` is an alias; both lists are used. A
|
|
519
|
+
client session sends uploaded file names (resolved like STT audio) or data
|
|
520
|
+
URLs (`data:image/...`, `data:audio/...`); local functions send file paths
|
|
521
|
+
or data URLs; the HTTP endpoints take data URLs only. `lfm2-d1-omni` takes
|
|
522
|
+
images or one audio clip, not both.
|
|
523
|
+
- With media, `state` may be `null` (or empty) to ask about the media only.
|
|
524
|
+
Only `lfm2-d1` and `lfm2-d1-omni` accept that; other models reject it. Without
|
|
525
|
+
media a `state` is required.
|
|
526
|
+
|
|
527
|
+
The OpenAI-compatible `POST /oai-compat/v1/decisions` also takes `input_audio`
|
|
528
|
+
content parts (`{ type: 'input_audio', input_audio: { data, format } }`, base64
|
|
529
|
+
`data`, `format` `wav` or `mp3`). An input with only images or audio gets a
|
|
530
|
+
placeholder state (`The attached image.`), since every model type takes one.
|
|
531
|
+
|
|
532
|
+
**Sharing a model with `ggml-llm`** — a model that both generates and decides
|
|
533
|
+
(Jevling, for one) can be configured as both a `ggml-llm` and a
|
|
534
|
+
`ggml-decision`. By default each loads its own context. Set
|
|
535
|
+
`[generators.backend] share_llm_context = true` on the decision entry to run
|
|
536
|
+
its decisions on the LLM's context instead, when the two entries name the same
|
|
537
|
+
weights (same `repo_id`, `revision`, `filename`, `quantization`, `url` and
|
|
538
|
+
`local_path`): the model is then loaded once, with the LLM's config. This
|
|
539
|
+
applies to local functions, the TypeSafe-compatible endpoint and client
|
|
540
|
+
sessions (whose decision runtime options then do not apply).
|
|
541
|
+
|
|
542
|
+
Sharing trades throughput for memory. A decision takes one of the LLM's
|
|
543
|
+
`n_parallel` slots, and its forward passes run between the LLM's batches, so
|
|
544
|
+
concurrent completions stop generating while it runs (in a CPU test, a
|
|
545
|
+
128-token completion took twice as long beside six decisions; with a separate
|
|
546
|
+
decision context it did not slow down). Results are not affected: the slot's
|
|
547
|
+
sequence and prompt cache are cleared after each decision. Without
|
|
548
|
+
`kv_unified = true` on the LLM a slot holds `n_ctx / n_parallel` tokens, which
|
|
549
|
+
must fit the state plus the questions. The decision loads its own context
|
|
550
|
+
anyway when it sets `rank_pooling` (it needs an embedding context), when its
|
|
551
|
+
projector differs from the LLM's, or when the LLM is embedding or vocab-only.
|
|
552
|
+
The server logs at startup which decision models share.
|
|
553
|
+
|
|
554
|
+
```toml
|
|
555
|
+
[[generators]]
|
|
556
|
+
type = "ggml-llm"
|
|
557
|
+
[generators.model]
|
|
558
|
+
repo_id = "BricksDisplay/jevling-0.8b-v1-GGUF"
|
|
559
|
+
filename = "jevling-0.8b-v1-q8_0-embf16.gguf"
|
|
560
|
+
[generators.runtime]
|
|
561
|
+
n_ctx = 16384
|
|
562
|
+
|
|
563
|
+
[[generators]]
|
|
564
|
+
type = "ggml-decision"
|
|
565
|
+
[generators.backend]
|
|
566
|
+
share_llm_context = true
|
|
567
|
+
[generators.model]
|
|
568
|
+
repo_id = "BricksDisplay/jevling-0.8b-v1-GGUF"
|
|
569
|
+
filename = "jevling-0.8b-v1-q8_0-embf16.gguf"
|
|
570
|
+
```
|
|
571
|
+
|
|
510
572
|
**`[generators.backend]`** — same keys as `ggml-tts` (`variant`,
|
|
511
573
|
`variant_preference`, `gpu_memory_fraction`, `cpu_memory_fraction`).
|
|
512
574
|
|
|
@@ -515,7 +577,7 @@ generator run one at a time.
|
|
|
515
577
|
| Key | Type | Default | Notes |
|
|
516
578
|
| ------------------------- | -------- | -------------------------------- | ------------------------------------------------------------------ |
|
|
517
579
|
| `preferred_quantizations` | string[] | `["q8_0", "q4_k_m", <no-quant>]` | Default fallback chain |
|
|
518
|
-
| `mmproj_repo_id` | string | falls back to `repo_id` |
|
|
580
|
+
| `mmproj_repo_id` | string | falls back to `repo_id` | Projector repo, for models whose prompt takes images or audio |
|
|
519
581
|
| `mmproj_filename` | string | auto (`*mmproj*`) | Pin a specific projector artifact |
|
|
520
582
|
| `mmproj_url` | string | — | Direct projector URL (skips manifest lookup) |
|
|
521
583
|
| `mmproj_revision` | string | falls back to `revision` | |
|
|
@@ -525,8 +587,8 @@ generator run one at a time.
|
|
|
525
587
|
| `n_gpu_layers` | number | `99` | Forced to `0` when the GPU is unused |
|
|
526
588
|
|
|
527
589
|
Setting any `mmproj_*` key loads a projector; requests can then pass `images`
|
|
528
|
-
(file paths or data URLs). The projector is ignored,
|
|
529
|
-
model's prompt has no place for images.
|
|
590
|
+
(images or audio clips, as file paths or data URLs). The projector is ignored,
|
|
591
|
+
with a warning, when the model's prompt has no place for images or audio.
|
|
530
592
|
|
|
531
593
|
**Runtime extras** — under `[runtime]` for ggml-decision:
|
|
532
594
|
|
|
@@ -698,7 +760,7 @@ Examples:
|
|
|
698
760
|
|
|
699
761
|
## Compatibility Endpoints (Experimental)
|
|
700
762
|
|
|
701
|
-
The server can expose OpenAI- and
|
|
763
|
+
The server can expose OpenAI-, Anthropic- and TypeSafe-compatible HTTP endpoints in addition to the native RPC. Each endpoint is opt-in via the TOML config:
|
|
702
764
|
|
|
703
765
|
```toml
|
|
704
766
|
[openai_compat]
|
|
@@ -708,14 +770,19 @@ enabled = true
|
|
|
708
770
|
[anthropic_messages]
|
|
709
771
|
enabled = true
|
|
710
772
|
# cors_allowed_origins = ["http://localhost:3000"]
|
|
773
|
+
|
|
774
|
+
[typesafe_compat]
|
|
775
|
+
enabled = true
|
|
711
776
|
```
|
|
712
777
|
|
|
713
778
|
| Endpoint | Config flag |
|
|
714
779
|
| --------------------- | ------------------------------------ |
|
|
715
780
|
| `/oai-compat/v1/*` | `[openai_compat] enabled = true` |
|
|
716
781
|
| `/anthropic-messages` | `[anthropic_messages] enabled = true` |
|
|
782
|
+
| `/typesafe-compat/v1/*` | `[typesafe_compat] enabled = true` |
|
|
717
783
|
|
|
718
|
-
Completion requests may select only LLM models declared in `[[generators]]
|
|
784
|
+
Completion requests may select only LLM models declared in `[[generators]]`, and decision
|
|
785
|
+
requests only `ggml-decision` generators. Use each
|
|
719
786
|
endpoint's `GET /v1/models` response for valid model IDs; unknown IDs are rejected before the
|
|
720
787
|
server starts or downloads a generator.
|
|
721
788
|
|
|
@@ -779,7 +846,7 @@ export default async function ({ path }: { path: string }, context: ButtressFunc
|
|
|
779
846
|
| `buttress.detokenize({ model?, tokens })` | Convert token ids back into text with the same model. |
|
|
780
847
|
| `buttress.transcribe({ model?, filePath \| audioData, options? })` | Transcribe audio with this server's STT generator. |
|
|
781
848
|
| `buttress.synthesize({ model?, text, options? })` | Synthesize speech with this server's TTS generator (`onnx-tts` or `ggml-tts`); the WAV lands in `tempDir` → `{ path, sampling_rate, channels }`. |
|
|
782
|
-
| `buttress.decide({ model?, state, questions, images? })` | Answer typed `choice` / `score` / `noul` questions about `state` with this server's `ggml-decision` generator
|
|
849
|
+
| `buttress.decide({ model?, state, questions, images?, files? })` | Answer typed `choice` / `score` / `noul` questions about `state` with this server's `ggml-decision` generator → `{ model, answers, usage }`. `answers[id]` is typed from its question. `images` (alias `files`) are images or audio clips, as local paths or data URLs; with media, `state` may be `null` for `lfm2-d1` / `lfm2-d1-omni`. |
|
|
783
850
|
| `bank.list({ keyword?, meta?, ids? })` / `bank.get(id)` / `bank.update(properties, { dontNotify? })` / `bank.remove(id)` | Read/write the bound workspace's remote **Data Bank** (see below). To listen for changes, use `bank.subscribe` inside a daemon function. |
|
|
784
851
|
| `daemons.emit(name, event, data?)` / `daemons.list()` | Signal a running **daemon function**'s `onEvent` handlers (see below) / list running daemon names. |
|
|
785
852
|
| `emit(event, data)` | Progress event; delivered to SSE callers, ignored otherwise. |
|
|
@@ -999,9 +1066,45 @@ calls the agent's other tools (`await tools.<name>(args)`), so it can batch call
|
|
|
999
1066
|
and filter large results before they reach the context. MCP servers of such an
|
|
1000
1067
|
agent may use `exposure = "codemode"` (or `"deferred"`) to keep their tools out
|
|
1001
1068
|
of the model's tool list; scripts find them with `searchTools()`.
|
|
1002
|
-
`models.classify()` runs pi-ai classifier models with the agent's credentials
|
|
1069
|
+
`models.classify()` runs pi-ai classifier models with the agent's credentials,
|
|
1070
|
+
including this server's decision generators (`buttress/<ggml-decision id>`).
|
|
1003
1071
|
Small local models may write poor scripts — prefer it with capable models.
|
|
1004
1072
|
|
|
1073
|
+
**Decisions (System One).** `decision_model` gives an agent a typed-decision
|
|
1074
|
+
model: `buttress/<ggml-decision id>` (as `GET /typesafe-compat/v1/models` lists
|
|
1075
|
+
it), or a hosted classifier such as `typesafe/jev-latest` (`TYPESAFE_API_KEY`;
|
|
1076
|
+
the questions and the state are then sent to TypeSafe). Unset means no
|
|
1077
|
+
decisions; it never falls back to the chat model.
|
|
1078
|
+
|
|
1079
|
+
```toml
|
|
1080
|
+
[[agents]]
|
|
1081
|
+
name = "ops-assistant"
|
|
1082
|
+
model = "buttress/unsloth/Qwen3.5-4B-GGUF"
|
|
1083
|
+
tools = ["host-info"]
|
|
1084
|
+
decision_model = "buttress/BricksDisplay/jevling-e2b-v1-GGUF:jevling-e2b-v1-q8_0-embf16.gguf"
|
|
1085
|
+
decision_assistance = true # opt-in tool prefilter (default false)
|
|
1086
|
+
# decision_timeout_ms = 500 # prefilter deadline; past it the run keeps every tool
|
|
1087
|
+
```
|
|
1088
|
+
|
|
1089
|
+
- With a `decision_model`, the agent gets a `decision_evaluate` tool. The model
|
|
1090
|
+
calls it itself with a `state` and `choice` / `score` / `bool` questions, and
|
|
1091
|
+
gets calibrated probabilities back. The tool sees only that state, not the
|
|
1092
|
+
conversation. Its answers are evidence for the model, not permission.
|
|
1093
|
+
- With `decision_assistance = true` too, a tool prefilter runs before each run.
|
|
1094
|
+
It asks three yes/no questions about the request: does it need live facts
|
|
1095
|
+
about the system, does it ask for an action (or approve/retry one), and does
|
|
1096
|
+
it point at something not in the conversation. Only when all three are
|
|
1097
|
+
clearly no (each probability < 0.35) does the run go out **without tool
|
|
1098
|
+
definitions**: a shorter prompt for greetings, thanks, explanations or
|
|
1099
|
+
translations. Anything else, including a timeout or an error, keeps every
|
|
1100
|
+
tool. It sends the request, the nearest user/assistant text (≤ 6,000
|
|
1101
|
+
characters) and the tool names; never tool arguments, results or reasoning.
|
|
1102
|
+
The server logs each outcome (`tool prefilter pruned|kept …`).
|
|
1103
|
+
- The prefilter never adds or allows a tool; it only withholds them for one
|
|
1104
|
+
run. Small decision models judge it less reliably: jevling-0.8b pruned some
|
|
1105
|
+
live-fact questions that need a tool, jevling-e2b kept them (about 170 ms per
|
|
1106
|
+
run on a GPU). The first run after start may time out while the model loads.
|
|
1107
|
+
|
|
1005
1108
|
**Local devices.** Set `local_devices = true` on an individual `[[agents]]` entry
|
|
1006
1109
|
(default `false`) to add just two built-in tools, independently of local functions/MCP:
|
|
1007
1110
|
|
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
|
|
@@ -57,6 +64,8 @@ enabled = true
|
|
|
57
64
|
# tools = ["host-info"] # local function names (explicit; no wildcard)
|
|
58
65
|
# local_devices = false # opt in: devices_scan + devtools (inspection AND control)
|
|
59
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
|
|
60
69
|
# [agents.mcp_servers.example]
|
|
61
70
|
# url = "https://example.com/mcp/"
|
|
62
71
|
# optional = true
|
|
@@ -322,3 +331,9 @@ vocoder_batch_size = 4096
|
|
|
322
331
|
# # rank_pooling = true
|
|
323
332
|
# [generators.runtime]
|
|
324
333
|
# n_ctx = 4096
|
|
334
|
+
#
|
|
335
|
+
# A model that also generates (Jevling, for one) can be configured as both a
|
|
336
|
+
# ggml-llm and a ggml-decision. To load it once, add to the decision entry
|
|
337
|
+
# [generators.backend]
|
|
338
|
+
# share_llm_context = true
|
|
339
|
+
# (decisions then compete with completions for the LLM's slots; see README).
|
|
@@ -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>;
|
package/lib/agent/models.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type Model, type MutableModels } from '@earendil-works/pi-ai';
|
|
2
2
|
import type { Config } from '../types';
|
|
3
3
|
import type { AgentDefinition } from './types';
|
|
4
|
+
import { type DecideFn } from './decisions';
|
|
4
5
|
export declare const BUTTRESS_PROVIDER_ID = "buttress";
|
|
5
6
|
/**
|
|
6
7
|
* Loopback requests never leave the process: this host name only exists so the
|
|
@@ -13,8 +14,9 @@ export type LoopbackFetch = typeof fetch;
|
|
|
13
14
|
/**
|
|
14
15
|
* A `Models` collection with every pi built-in provider (env-var API keys)
|
|
15
16
|
* plus the `buttress` pseudo-provider whose models are the configured LLM
|
|
16
|
-
* generators, streamed over the in-process OpenAI-compat loopback
|
|
17
|
+
* generators, streamed over the in-process OpenAI-compat loopback, and the
|
|
18
|
+
* configured decision generators as classifier models, answered by `decide`.
|
|
17
19
|
*/
|
|
18
|
-
export declare const buildAgentModels: (config: Config, loopbackFetch: LoopbackFetch) => MutableModels;
|
|
20
|
+
export declare const buildAgentModels: (config: Config, loopbackFetch: LoopbackFetch, decide: DecideFn) => MutableModels;
|
|
19
21
|
/** Resolve an agent's model reference against the registry, with clear errors. */
|
|
20
22
|
export declare const resolveAgentModel: (models: MutableModels, agent: AgentDefinition) => Model<any>;
|
package/lib/agent/types.d.ts
CHANGED
|
@@ -51,6 +51,20 @@ export type AgentDefinition = {
|
|
|
51
51
|
/** Cumulative token budget per run; null = unlimited (default). */
|
|
52
52
|
maxTokensPerRun: number | null;
|
|
53
53
|
thinking?: 'off' | 'minimal' | 'low' | 'medium' | 'high';
|
|
54
|
+
/**
|
|
55
|
+
* Classifier model (`provider/model-id`, e.g. `buttress/<decision generator>`
|
|
56
|
+
* or `typesafe/jev-latest`) for the `decision_evaluate` tool and decision
|
|
57
|
+
* assistance; null = no decisions (never falls back to the chat model).
|
|
58
|
+
*/
|
|
59
|
+
decisionModel: {
|
|
60
|
+
provider: string;
|
|
61
|
+
modelId: string;
|
|
62
|
+
modelRef: string;
|
|
63
|
+
} | null;
|
|
64
|
+
/** Opt-in tool prefilter before each run (needs `decisionModel`; default false). */
|
|
65
|
+
decisionAssistance: boolean;
|
|
66
|
+
/** Prefilter deadline; past it the run keeps every tool. */
|
|
67
|
+
decisionTimeoutMs: number;
|
|
54
68
|
/** Generation passthrough (temperature, top_p, …). */
|
|
55
69
|
generation: Record<string, unknown>;
|
|
56
70
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{a as e,i as t,n,r,t as i}from"./client-
|
|
2
|
+
import{a as e,i as t,n,r,t as i}from"./client-BVyIuSyr.mjs";import a from"node:readline";const o=`\x1B[2m`,s=`\x1B[33m`,c=`\x1B[31m`,l=`\x1B[0m`,u=e=>{let t={name:null,configArg:null,url:null,token:null,sessionId:null,fork:!1,listSessions:!1,plain:!1};for(let n=0;n<e.length;n+=1){let r=e[n];r===`-h`||r===`--help`?(console.log(`
|
|
3
3
|
bricks-buttress agent — chat with a configured agent on a running server
|
|
4
4
|
|
|
5
5
|
Usage:
|
|
@@ -19,4 +19,4 @@ In the chat: /exit quits, /new starts a fresh session, Ctrl+C aborts the
|
|
|
19
19
|
current run (a second Ctrl+C quits).
|
|
20
20
|
`),process.exit(0)):r===`-c`||r===`--config`?t.configArg=e[++n]??null:r===`--url`?t.url=e[++n]??null:r===`--token`?t.token=e[++n]??null:r===`--session`?t.sessionId=e[++n]??null:r===`--fork`?(t.sessionId=e[++n]??null,t.fork=!0):r===`--sessions`?t.listSessions=!0:r===`--plain`?t.plain=!0:!r.startsWith(`-`)&&!t.name?t.name=r:(console.error(`Unknown argument: ${r}`),process.exit(1))}return t},d=e=>{console.error(`${c}${e}${l}`),process.exit(1)},f=async(t,n,r,i,a,u)=>{let d=i,f=!1,p=!1,m=()=>{f&&=(process.stdout.write(`${l}\n`),!1)},h=await e(t,n,{prompt:r,sessionId:i??void 0,fork:a},{signal:u,onFrame:({event:e,payload:t})=>{if(e===`session`){t.sessionId&&t.sessionId!==d&&(d=t.sessionId,i||console.log(`${o}session ${d}${l}`));return}if(e!==`agent`)return;let n=t?.event;if(t?.type===`message_update`&&n)n.type===`thinking_delta`?(f||=(process.stdout.write(`${o}`),!0),process.stdout.write(n.delta??``),p=!0):n.type===`text_delta`&&(m(),process.stdout.write(n.delta??``),p=!0);else if(t?.type===`tool_execution_start`){m(),p&&process.stdout.write(`
|
|
21
21
|
`);let e=JSON.stringify(t.args??{});console.log(`${s}⚙ ${t.toolName}${l}${o}(${e.length>160?`${e.slice(0,160)}…`:e})${l}`),p=!1}else t?.type===`tool_execution_end`?console.log(t.isError?`${c}✗ ${t.toolName} failed${l}`:`${o}✓ ${t.toolName}${l}`):t?.type===`tool_emit`&&console.log(`${o} ${t.toolName} → ${t.event}${l}`)}});if(m(),h.kind===`result`){let e=h.result;d=e.sessionId??d;let t=e.usage||{};return process.stdout.write(`
|
|
22
|
-
`),console.log(`${o}— ${e.stopReason} · ${t.totalTurns??`?`} turn(s) · ${t.input??0} in / ${t.output??0} out · session ${d}${l}`),{sessionId:d,ok:!0}}return h.kind===`aborted`?(console.log(`\n${s}(aborted)${l}`),{sessionId:h.sessionId??d,ok:!1}):h.kind===`error`?(console.error(`\n${c}Run failed: ${h.message}${l}`),{sessionId:h.sessionId??d,ok:!1}):(console.error(`\n${c}Connection lost mid-run.${l}`),{sessionId:h.sessionId??d,ok:!1})},p=async(e,t,n,r)=>{let s=n,u=r;console.log(`[1m${t}${l} @ ${e.baseUrl}`+(s?` ${o}(${u?`forking`:`continuing`} ${s})${l}`:``)),console.log(`${o}/exit to quit, /new for a fresh session, Ctrl+C aborts a running turn${l}`);let d=a.createInterface({input:process.stdin,output:process.stdout}),p=!1;d.on(`close`,()=>{p=!0});let m=e=>new Promise(t=>{if(p){t(null);return}d.question(e,t),d.once(`close`,()=>t(null))}),h=null;for(d.on(`SIGINT`,()=>{h?(h.abort(),h=null):(d.close(),process.exit(0))});;){let n=await m(`[36m> ${l}`);if(n==null)break;let r=n.trim();if(!r)continue;if(r===`/exit`||r===`/quit`)break;if(r===`/new`){s=null,u=!1,console.log(`${o}Started a fresh session.${l}`);continue}h=new AbortController;let a=s;try{let n=await f(e,t,r,s,u,h.signal);n.sessionId&&(s=n.sessionId),i(a,s)&&(u=!1)}catch(e){console.error(`${c}${e?.message||e}${l}`)}finally{h=null}}d.close()},m=async e=>{let i=u(e),{config:a,agentsConfig:s}=n(i.configArg),c=a.server.port||2080,f=(i.url||`http://127.0.0.1:${c}`).replace(/\/$/,``),m={baseUrl:f,token:i.token||(s?r(s.runtimeTokenFile):null)||process.env.BUTTRESS_AGENT_TOKEN||null},h=await t(m,`/agents`).catch(e=>d(`Cannot reach ${f}: ${e?.message||e} (is the server running?)`));if(!h.ok){let e=await h.text().catch(()=>``);d(`Server rejected the request (${h.status}): ${e.slice(0,300)}`)}let{agents:g}=await h.json();if(!i.name){console.log(`Configured agents on ${f}:`);for(let e of g)console.log(`- ${e}`);g.length===0&&console.log(`(none — add [[agents]] to the server config)`);return}if(g.includes(i.name)||d(`Unknown agent "${i.name}" (configured: ${g.join(`, `)||`none`})`),i.listSessions){let{sessions:e}=await(await t(m,`/agents/${encodeURIComponent(i.name)}/sessions`)).json();if(!e?.length){console.log(`No sessions yet.`);return}for(let t of e)console.log(`${t.sessionId} ${o}${t.updatedAt}${l}${t.parentSession?` ${o}(fork of ${t.parentSession})${l}`:``}`),t.preview&&console.log(` ${o}${t.preview}${l}`);return}if(process.stdin.isTTY&&process.stdout.isTTY&&!i.plain){let{runAgentTui:e}=await import(`./tui-
|
|
22
|
+
`),console.log(`${o}— ${e.stopReason} · ${t.totalTurns??`?`} turn(s) · ${t.input??0} in / ${t.output??0} out · session ${d}${l}`),{sessionId:d,ok:!0}}return h.kind===`aborted`?(console.log(`\n${s}(aborted)${l}`),{sessionId:h.sessionId??d,ok:!1}):h.kind===`error`?(console.error(`\n${c}Run failed: ${h.message}${l}`),{sessionId:h.sessionId??d,ok:!1}):(console.error(`\n${c}Connection lost mid-run.${l}`),{sessionId:h.sessionId??d,ok:!1})},p=async(e,t,n,r)=>{let s=n,u=r;console.log(`[1m${t}${l} @ ${e.baseUrl}`+(s?` ${o}(${u?`forking`:`continuing`} ${s})${l}`:``)),console.log(`${o}/exit to quit, /new for a fresh session, Ctrl+C aborts a running turn${l}`);let d=a.createInterface({input:process.stdin,output:process.stdout}),p=!1;d.on(`close`,()=>{p=!0});let m=e=>new Promise(t=>{if(p){t(null);return}d.question(e,t),d.once(`close`,()=>t(null))}),h=null;for(d.on(`SIGINT`,()=>{h?(h.abort(),h=null):(d.close(),process.exit(0))});;){let n=await m(`[36m> ${l}`);if(n==null)break;let r=n.trim();if(!r)continue;if(r===`/exit`||r===`/quit`)break;if(r===`/new`){s=null,u=!1,console.log(`${o}Started a fresh session.${l}`);continue}h=new AbortController;let a=s;try{let n=await f(e,t,r,s,u,h.signal);n.sessionId&&(s=n.sessionId),i(a,s)&&(u=!1)}catch(e){console.error(`${c}${e?.message||e}${l}`)}finally{h=null}}d.close()},m=async e=>{let i=u(e),{config:a,agentsConfig:s}=n(i.configArg),c=a.server.port||2080,f=(i.url||`http://127.0.0.1:${c}`).replace(/\/$/,``),m={baseUrl:f,token:i.token||(s?r(s.runtimeTokenFile):null)||process.env.BUTTRESS_AGENT_TOKEN||null},h=await t(m,`/agents`).catch(e=>d(`Cannot reach ${f}: ${e?.message||e} (is the server running?)`));if(!h.ok){let e=await h.text().catch(()=>``);d(`Server rejected the request (${h.status}): ${e.slice(0,300)}`)}let{agents:g}=await h.json();if(!i.name){console.log(`Configured agents on ${f}:`);for(let e of g)console.log(`- ${e}`);g.length===0&&console.log(`(none — add [[agents]] to the server config)`);return}if(g.includes(i.name)||d(`Unknown agent "${i.name}" (configured: ${g.join(`, `)||`none`})`),i.listSessions){let{sessions:e}=await(await t(m,`/agents/${encodeURIComponent(i.name)}/sessions`)).json();if(!e?.length){console.log(`No sessions yet.`);return}for(let t of e)console.log(`${t.sessionId} ${o}${t.updatedAt}${l}${t.parentSession?` ${o}(fork of ${t.parentSession})${l}`:``}`),t.preview&&console.log(` ${o}${t.preview}${l}`);return}if(process.stdin.isTTY&&process.stdout.isTTY&&!i.plain){let{runAgentTui:e}=await import(`./tui-Bxeb1d7E.mjs`);await e({connection:m,agentName:i.name,sessionId:i.sessionId,fork:i.fork});return}await p(m,i.name,i.sessionId,i.fork)};export{m as runAgentCommand};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{a as e,n as t}from"./config-
|
|
2
|
+
import{a as e,n as t}from"./config-cxApm_PZ.mjs";import n from"node:path";import r from"node:fs";import i from"@iarna/toml";const a=a=>{let o={},s=process.cwd();a&&(r.existsSync(a)?(o=i.parse(r.readFileSync(a,`utf8`)),s=n.dirname(n.resolve(a))):o=i.parse(a));let c=e(o);return{config:c,configDir:s,agentsConfig:t(c,{configDir:s})}},o=e=>{try{return r.readFileSync(e,`utf8`).trim()||null}catch{return null}},s=async(e,t,n)=>fetch(`${e.baseUrl}${t}`,{...n,headers:{"content-type":`application/json`,...e.token?{authorization:`Bearer ${e.token}`}:{},...n?.headers}});async function*c(e){let t=e.getReader(),n=new TextDecoder,r=``;try{for(;;){let{done:e,value:i}=await t.read();if(e)break;r+=n.decode(i,{stream:!0});let a=r.indexOf(`
|
|
3
3
|
|
|
4
4
|
`);for(;a!==-1;){let e=r.slice(0,a);r=r.slice(a+2),a=r.indexOf(`
|
|
5
5
|
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{n as e}from"./protocol-WkPByalT.mjs";import{createRequire as t}from"node:module";import{readFile as n}from"node:fs/promises";import{runToolCall as r}from"@earendil-works/pi-agent-core";import{Worker as i}from"node:worker_threads";function a(e){let t=``;for(let n of e){let e=t===``?/^[A-Za-z_$]$/.test(n):/^[A-Za-z0-9_$]$/.test(n);t+=e?n:`_`}return t===``?`_`:t}const o=/^[A-Za-z_$][A-Za-z0-9_$]*$/;function s(e,t={}){let n=e.inputSchema===void 0?`unknown`:m(e.inputSchema,{maxChars:t.inputMaxChars??16e3});return`${a(e.name)}(args: ${n}): Promise<${u(e.outputSchema)}>;`}function c(e,t={}){let n=`declare const tools: { ${s(e,t)} };`;return`${e.description?.trim()??``}\n\ncodemode tool declaration:\n\`\`\`ts\n${n}\n\`\`\``}function l(e){if(!f(e)||!f(e.properties))return;let{content:t,isError:n,_meta:r,structuredContent:i}=e.properties;if(!(!f(t)||t.type!==`array`||!f(t.items)||t.items.type!==`object`)&&!(!f(n)||n.type!==`boolean`||!f(r)||r.type!==`object`))return f(i)||typeof i==`boolean`?i:!0}function u(e){let t=l(e);if(t!==void 0){let e=m(t);return e===`unknown`?`CallToolResult`:`CallToolResult<${e}>`}return e===void 0?`unknown`:m(e)}function d(e){return o.test(e)?e:JSON.stringify(e)}function f(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function p(e){let t=[...new Set(e)];return t.includes(`unknown`)?`unknown`:t.length===0?`never`:t.join(` | `)}function m(e,t={}){let n=g(e,{root:e,resolving:new Set,expansions:0});return t.maxChars!==void 0&&n.length>t.maxChars?`unknown`:n}function h(e,t){if(e!==`#`&&!e.startsWith(`#/`))return;let n=t;for(let t of e.slice(2).split(`/`).filter(Boolean)){let e=decodeURIComponent(t).replaceAll(`~1`,`/`).replaceAll(`~0`,`~`);if(!f(n)||!(e in n))return;n=n[e]}return typeof n==`boolean`||f(n)?n:void 0}function g(e,t){if(e===!0)return`unknown`;if(e===!1)return`never`;if(!f(e))return`unknown`;if(typeof e.$ref==`string`){let n=e.$ref;if(t.resolving.has(n)||t.expansions>=32)return`unknown`;let r=h(n,t.root);if(r===void 0)return`unknown`;t.expansions++,t.resolving.add(n);try{return g(r,t)}finally{t.resolving.delete(n)}}if(`const`in e)return JSON.stringify(e.const)??`unknown`;if(Array.isArray(e.enum))return p(e.enum.map(e=>JSON.stringify(e)??`unknown`));let n=Array.isArray(e.anyOf)?e.anyOf:Array.isArray(e.oneOf)?e.oneOf:void 0;if(n)return p(n.map(e=>g(e,t)));if(Array.isArray(e.allOf)){let n=e.allOf.map(e=>g(e,t)).filter(e=>e!==`unknown`);return n.length===0?`unknown`:n.map(e=>e.includes(` | `)?`(${e})`:e).join(` & `)}let r=e.type;if(Array.isArray(r))return p(r.map(n=>g({...e,type:n},t)));switch(r){case`string`:return`string`;case`number`:case`integer`:return`number`;case`boolean`:return`boolean`;case`null`:return`null`;case`array`:return _(e,t);case`object`:return y(e,t);case void 0:return`properties`in e||`additionalProperties`in e||`required`in e?y(e,t):`items`in e||`prefixItems`in e?_(e,t):`unknown`;default:return`unknown`}}function _(e,t){if(e.items!==void 0&&!Array.isArray(e.items))return`Array<${g(e.items,t)}>`;let n=Array.isArray(e.prefixItems)?e.prefixItems:Array.isArray(e.items)?e.items:[];return n.length>0?`[${n.map(e=>g(e,t)).join(`, `)}]`:`unknown[]`}function v(e){return f(e)&&typeof e.description==`string`?e.description.trim():``}function y(e,t){let n=f(e.properties)?e.properties:{},r=new Set(Array.isArray(e.required)?e.required:[]),i=Object.keys(n).sort(),a=i.map(e=>{let i=r.has(e)?``:`?`;return`${d(e)}${i}: ${g(n[e],t)};`}),o=e.additionalProperties;if(o!==void 0&&o!==!1){let e=o===!0?`unknown`:g(o,t);a.push(`[key: string]: ${e};`)}else o===void 0&&i.length===0&&a.push(`[key: string]: unknown;`);if(a.length===0)return`{}`;if(!i.some(e=>v(n[e])))return`{ ${a.join(` `)} }`;let s=[`{`];i.forEach((e,t)=>{for(let t of v(n[e]).split(/\r?\n/))t.trim()&&s.push(` // ${t.trim()}`);s.push(` ${a[t].replaceAll(`
|
|
3
|
+
`,`
|
|
4
|
+
`)}`)});for(let e of a.slice(i.length))s.push(` ${e}`);return s.push(`}`),s.join(`
|
|
5
|
+
`)}const b=3e3,x={type:`string`};function S(e){return[`Globals:`,"- `text(value)`, `image(dataUrlOrImageBlock)`, `console.log(...)`, and top-level `return` add output; `exit()` ends the script.","- `store(key, value)` and `load(key)` keep small JSON values across codemode calls.","- `ALL_TOOLS`, `searchTools(query, { limit?, namespace? })`, `describeTool(name)`, `describeNamespace(name)`: find tools you were not given directly, such as MCP tools, and read their declarations.",...e?['- `models`: non-chat models with your credentials. `await models.getAvailableOfType("image" | "classifier")` lists them; `models.generateImages(model, { input: [{ type: "text", text }, ...{ type: "image", data, mimeType }] })` resolves to `{ output, stopReason, errorMessage? }` (show its image blocks with `image(block)`; can take minutes, so do not set a short timeout_ms); `models.classify(model, { state: {...}, questions: { id: { type: "choice" | "score" | "bool", instructions, criteria } } })` resolves to `{ answers, stopReason, errorMessage? }`. They do not throw on provider errors: check `stopReason`.']:[]].join(`
|
|
6
|
+
`)}function ee(e){return{name:e.name,description:e.description,inputSchema:e.parameters,outputSchema:e.outputSchema??x}}function C(e){let t=a(e.name);return`${t===e.name?`### \`${t}\``:`### \`${t}\` (\`${e.name}\`)`}\n${c(e).trim()}`}function w(e,t){let n=e.map(e=>[...e.entries].sort((e,t)=>e.cost-t.cost)),r=new Set,i=t,a=n.filter(e=>e.length>0);for(;a.length>0;)a=a.filter(e=>{let t=e[0];return t.cost>i?!1:(i-=t.cost,r.add(t.name),e.shift(),e.length>0)});return r}function T({tools:e,declaredNames:t=new Set,isDeferred:n=()=>!1,getNamespace:r=()=>void 0,models:i=!1,inlineBudget:a=b}){let o=['Run JavaScript that calls other tools. The input is raw JavaScript (not JSON, no code fence), run as an async function body in a QuickJS sandbox: top-level `await` and `return` work. No Node, file system, network, or timers.\n- `await tools.<name>({ ...args })` calls a tool by its name (characters outside [A-Za-z0-9_] become `_`), including most tools you can call directly (`ALL_TOOLS` is the exact list). It resolves to the tool\'s text output; MCP tools (`mcp__*`) resolve to their `CallToolResult` (`{ content, structuredContent?, isError? }`). A failed or blocked call rejects with an Error. Calls still running when the script ends are cancelled.\n- Use it to batch independent calls (`Promise.allSettled`), chain calls, or filter large output; only what the script outputs reaches you.\n- Optional first line: `// @options: {"max_output_tokens": 10000, "timeout_ms": 60000}`',S(i)],s=new Map;for(let i of e){if(t.has(i.name))continue;let e=r(i);if(!e||!n(i))continue;let a=s.get(e.name)??{...e,count:0};a.count+=1,s.set(e.name,a)}if(s.size>0){let e=[...s.values()].sort((e,t)=>e.name.localeCompare(t.name)).map(e=>{let t=e.description?.trim().split(`
|
|
7
|
+
`)[0];return`- \`${e.name}\` (${e.count} tool${e.count===1?``:`s`})${t?`: ${t}`:``}`});o.push(`Tool namespaces (find their tools with \`searchTools(query, { namespace })\` or \`describeNamespace(name)\`):\n${e.join(`
|
|
8
|
+
`)}`)}let c=e.filter(e=>!t.has(e.name)&&!n(e)).map(ee);if(c.length===0)return o.join(`
|
|
9
|
+
|
|
10
|
+
`);let u=new Map([[``,{namespace:void 0,entries:[]}]]);for(let t of c){let n=r(e.find(e=>e.name===t.name)),i=n?`ns:${n.name}`:``,a=u.get(i)??{namespace:n,entries:[]};u.set(i,a);let o=C(t);a.entries.push({name:t.name,section:o,cost:Math.ceil(o.length/4)})}let d=[...u.values()].sort((e,t)=>e.namespace===void 0?-1:t.namespace===void 0?1:e.namespace.name.localeCompare(t.namespace.name)),f=w(d,a);c.some(e=>f.has(e.name)&&l(e.outputSchema)!==void 0)&&o.push(`Shared MCP Types:
|
|
11
|
+
\`\`\`ts
|
|
12
|
+
type Role = "user" | "assistant";
|
|
13
|
+
type MetaObject = Record<string, unknown>;
|
|
14
|
+
type Annotations = {
|
|
15
|
+
audience?: Role[];
|
|
16
|
+
priority?: number;
|
|
17
|
+
lastModified?: string;
|
|
18
|
+
};
|
|
19
|
+
type Icon = {
|
|
20
|
+
src: string;
|
|
21
|
+
mimeType?: string;
|
|
22
|
+
sizes?: string[];
|
|
23
|
+
theme?: "light" | "dark";
|
|
24
|
+
};
|
|
25
|
+
type TextResourceContents = {
|
|
26
|
+
uri: string;
|
|
27
|
+
mimeType?: string;
|
|
28
|
+
_meta?: MetaObject;
|
|
29
|
+
text: string;
|
|
30
|
+
};
|
|
31
|
+
type BlobResourceContents = {
|
|
32
|
+
uri: string;
|
|
33
|
+
mimeType?: string;
|
|
34
|
+
_meta?: MetaObject;
|
|
35
|
+
blob: string;
|
|
36
|
+
};
|
|
37
|
+
type TextContent = {
|
|
38
|
+
type: "text";
|
|
39
|
+
text: string;
|
|
40
|
+
annotations?: Annotations;
|
|
41
|
+
_meta?: MetaObject;
|
|
42
|
+
};
|
|
43
|
+
type ImageContent = {
|
|
44
|
+
type: "image";
|
|
45
|
+
data: string;
|
|
46
|
+
mimeType: string;
|
|
47
|
+
annotations?: Annotations;
|
|
48
|
+
_meta?: MetaObject;
|
|
49
|
+
};
|
|
50
|
+
type AudioContent = {
|
|
51
|
+
type: "audio";
|
|
52
|
+
data: string;
|
|
53
|
+
mimeType: string;
|
|
54
|
+
annotations?: Annotations;
|
|
55
|
+
_meta?: MetaObject;
|
|
56
|
+
};
|
|
57
|
+
type ResourceLink = {
|
|
58
|
+
icons?: Icon[];
|
|
59
|
+
name: string;
|
|
60
|
+
title?: string;
|
|
61
|
+
uri: string;
|
|
62
|
+
description?: string;
|
|
63
|
+
mimeType?: string;
|
|
64
|
+
annotations?: Annotations;
|
|
65
|
+
size?: number;
|
|
66
|
+
_meta?: MetaObject;
|
|
67
|
+
type: "resource_link";
|
|
68
|
+
};
|
|
69
|
+
type EmbeddedResource = {
|
|
70
|
+
type: "resource";
|
|
71
|
+
resource: TextResourceContents | BlobResourceContents;
|
|
72
|
+
annotations?: Annotations;
|
|
73
|
+
_meta?: MetaObject;
|
|
74
|
+
};
|
|
75
|
+
type ContentBlock =
|
|
76
|
+
| TextContent
|
|
77
|
+
| ImageContent
|
|
78
|
+
| AudioContent
|
|
79
|
+
| ResourceLink
|
|
80
|
+
| EmbeddedResource;
|
|
81
|
+
type CallToolResult<TStructured = { [key: string]: unknown }> = {
|
|
82
|
+
_meta?: MetaObject;
|
|
83
|
+
content: ContentBlock[];
|
|
84
|
+
isError?: boolean;
|
|
85
|
+
structuredContent?: TStructured;
|
|
86
|
+
[key: string]: unknown;
|
|
87
|
+
};
|
|
88
|
+
\`\`\``);let p=[`Script-only tools:`];for(let{namespace:e,entries:t}of d){let n=t.filter(e=>f.has(e.name));if(e){let r=n.length===t.length?``:n.length===0?` (tools not listed)`:` (some tools not listed)`;p.push(`## ${e.name}${r}`)}for(let e of n)p.push(e.section)}return o.push(p.join(`
|
|
89
|
+
|
|
90
|
+
`)),o.join(`
|
|
91
|
+
|
|
92
|
+
`)}const E=new Map;function te(e){let r=e??t(import.meta.url).resolve(`quickjs-wasi/quickjs.wasm`),i=E.get(r);if(!i){let{WebAssembly:e}=globalThis;i=n(r).then(t=>e.compile(t)).catch(e=>{throw E.delete(r),e}),E.set(r,i)}return i}const ne=/^[A-Za-z_$][A-Za-z0-9_$]*$/,D=new Set([`tools`,`ALL_TOOLS`,`console`,`text`,`image`,`exit`,`globalThis`,`store`,`load`]);function O(e){return e instanceof Error?e.message:String(e)}function k(e){let t=Object.create(null);for(let[n,r]of Object.entries(e??{})){let e=JSON.stringify(r);e!==void 0&&(t[n]=e)}return t}var A=class extends Error{};function j(e,t){try{return JSON.parse(e)}catch{throw new A(`${t} is not valid JSON`)}}function M(e){let t=j(e,`store writes`);if(!Array.isArray(t))throw new A(`store writes are not an array`);let n={set:Object.create(null),delete:[]};for(let e of t){if(!Array.isArray(e)||typeof e[0]!=`string`||(e.length===2?typeof e[1]!=`string`:e.length!==1))throw new A(`store writes contain a malformed entry`);let[t,r]=e;r===void 0?n.delete.push(t):n.set[t]=j(r,`store value for ${JSON.stringify(t)}`)}return n}function re(e){let t=j(e,`script error`);if(typeof t!=`object`||!t)throw new A(`script error is not an object`);let{name:n,message:r,stack:i}=t;if(typeof r!=`string`||n!==void 0&&typeof n!=`string`||i!==void 0&&typeof i!=`string`)throw new A(`script error is malformed`);return{kind:`script`,name:n,message:r,stack:i}}function ie(){return new URL(import.meta.url.endsWith(`.ts`)?`./worker.ts`:`./worker.js`,import.meta.url)}var ae=class{promise;resolveResult;worker;interrupt=new SharedArrayBuffer(4);tools;globals;signal;timer;output=[];calls=[];pending=new Map;finished=!1;constructor(e){this.promise=new Promise(e=>{this.resolveResult=e}),this.tools=e.tools,this.globals=e.globals,this.signal=e.signal,Number.isFinite(e.timeoutMs)&&(this.timer=setTimeout(()=>{this.finish({kind:`timeout`,message:`Execution timed out after ${e.timeoutMs} ms`})},e.timeoutMs)),e.signal&&(e.signal.aborted?this.onAbort():e.signal.addEventListener(`abort`,this.onAbort,{once:!0})),e.wasm.then(t=>this.start(e,t),e=>{this.finish({kind:`sandbox`,message:`Failed to load QuickJS: ${O(e)}`})})}abort(e){return this.finish({kind:`aborted`,message:e}),this.promise}start(e,t){if(this.finished)return;let n={code:e.code,tools:[...e.tools.values()].map(e=>({name:e.name,jsName:a(e.name),description:e.description??``})),globals:[...e.globals.values()].map(e=>({name:e.name,spread:e.spread===!0})),wasm:t,memoryLimitBytes:e.memoryLimitBytes,store:e.store,interrupt:this.interrupt},r;try{r=new i(e.workerUrl,{workerData:n})}catch(e){this.finish({kind:`sandbox`,message:`Failed to start worker: ${O(e)}`});return}this.worker=r,r.on(`message`,e=>{try{this.handleMessage(e)}catch(e){this.finish({kind:`sandbox`,message:e instanceof A?`Sandbox bridge broken: ${e.message}. The script may have modified built-ins such as a prototype's toJSON.`:`Sandbox host failed: ${O(e)}`})}}),r.on(`error`,e=>{this.finish({kind:`sandbox`,name:e instanceof Error?e.name:void 0,message:O(e)})}),r.on(`exit`,e=>{this.finish({kind:`sandbox`,message:`Worker exited with code ${e} before the script settled`})})}onAbort=()=>{let e=this.signal?.reason;this.finish({kind:`aborted`,message:e instanceof Error?e.message:`Execution aborted`})};post(e){this.worker?.postMessage(e)}handleMessage(t){if(!this.finished){if(!e(t))throw new A(`unknown message from the worker`);switch(t.type){case`output`:this.output.push(t.item);break;case`call`:if(this.pending.has(t.id))throw new A(`duplicate call id ${t.id}`);this.handleCall(t);break;case`done`:this.handleDone(t);break;case`crash`:this.finish({kind:`sandbox`,message:t.message});break}}}handleDone(e){if(!e.ok){this.finish(re(e.error));return}let t=e.value===void 0?void 0:j(e.value,`return value`);this.finish(void 0,t,M(e.writes))}async handleCall(e){let{id:t,name:n}=e,r=e.target===`tool`,i=r?{name:n,status:`cancelled`,durationMs:0}:void 0;i&&this.calls.push(i);let a={record:i,startedAt:performance.now(),controller:new AbortController};this.pending.set(t,a);let o,s;try{let i=(r?this.tools:this.globals).get(n);if(!i)throw Error(`Unknown ${r?`tool`:`global`} "${n}"`);let c=e.args===void 0?void 0:JSON.parse(e.args),l=await i.execute(c,{signal:a.controller.signal});s={type:`result`,id:t,ok:!0,payload:l===void 0?void 0:JSON.stringify(l)},o=`ok`}catch(e){s={type:`result`,id:t,ok:!1,payload:O(e)},o=`error`}this.pending.delete(t)&&(i&&(i.status=o,i.durationMs=performance.now()-a.startedAt),this.post(s))}finish(e,t,n){if(this.finished)return;this.finished=!0,clearTimeout(this.timer),this.signal?.removeEventListener(`abort`,this.onAbort);let r=performance.now();for(let e of this.pending.values())e.record&&(e.record.durationMs=r-e.startedAt),e.controller.abort();this.pending.clear();let i=e?{ok:!1,error:e,output:this.output,calls:this.calls}:{ok:!0,value:t,output:this.output,calls:this.calls,storeWrites:n??{set:{},delete:[]}};if(!this.worker){this.resolveResult(i);return}Atomics.store(new Int32Array(this.interrupt),0,1),this.worker.terminate().catch(()=>void 0).then(()=>this.resolveResult(i))}},oe=class{toolsByName=new Map;globalsByName=new Map;timeoutMs;memoryLimitBytes;wasm;workerUrl;running=new Set;closed=!1;constructor(e={}){this.timeoutMs=e.timeoutMs??3e5,this.memoryLimitBytes=e.memoryLimitBytes,this.wasm=e.wasm,this.workerUrl=e.workerUrl??ie();for(let t of e.tools??[])this.registerTool(t);let t=new Set;for(let n of e.globals??[]){let e=n.name.split(`.`);if(e.length>2||!e.every(e=>ne.test(e))||D.has(e[0]))throw Error(`Invalid global name "${n.name}"`);if(this.globalsByName.has(n.name))throw Error(`Global "${n.name}" is already registered`);e.length===2&&t.add(e[0]),this.globalsByName.set(n.name,n)}for(let e of t)if(this.globalsByName.has(e))throw Error(`Global "${e}" conflicts with the namespace "${e}"`)}registerTool(e){if(this.toolsByName.has(e.name))throw Error(`Tool "${e.name}" is already registered`);this.toolsByName.set(e.name,e)}unregisterTool(e){return this.toolsByName.delete(e)}get tools(){return[...this.toolsByName.values()]}get globals(){return[...this.globalsByName.values()]}execute(e,t={}){if(this.closed)return Promise.reject(Error(`Sandbox is closed`));let n=new ae({code:e,tools:new Map(this.toolsByName),globals:this.globalsByName,timeoutMs:t.timeoutMs??this.timeoutMs,signal:t.signal,memoryLimitBytes:this.memoryLimitBytes,store:k(t.store),wasm:this.wasm===void 0?te():Promise.resolve(this.wasm),workerUrl:this.workerUrl});return this.running.add(n),n.promise.finally(()=>this.running.delete(n))}async close(){this.closed=!0,await Promise.all([...this.running].map(e=>e.abort(`Sandbox closed`)))}};const se=[`max_output_tokens`,`timeout_ms`],N="`max_output_tokens` and `timeout_ms`",ce=2147483647;String.raw`
|
|
93
|
+
start: options_source | plain_source
|
|
94
|
+
options_source: OPTIONS_LINE NEWLINE SOURCE
|
|
95
|
+
plain_source: SOURCE
|
|
96
|
+
|
|
97
|
+
OPTIONS_LINE: /[ \t]*\/\/ @options:[^\r\n]*/
|
|
98
|
+
NEWLINE: /\r?\n/
|
|
99
|
+
SOURCE: /[\s\S]+/
|
|
100
|
+
`;var P=class extends Error{constructor(e){super(e),this.name=`CodemodeSourceError`}};function le(e){return typeof e==`number`&&Number.isSafeInteger(e)&&e>=0}function ue(e){if(e===``)throw new P(`@options must be a JSON object with supported fields ${N}`);let t;try{t=JSON.parse(e)}catch(e){throw new P(`@options must be valid JSON with supported fields ${N}: ${e instanceof Error?e.message:String(e)}`)}if(typeof t!=`object`||!t||Array.isArray(t))throw new P(`@options must be a JSON object with supported fields ${N}`);let n=t;for(let e of Object.keys(n))if(!se.includes(e))throw new P(`@options only supports ${N}; got \`${e}\``);let r={},{max_output_tokens:i,timeout_ms:a}=n;if(i!==void 0){if(!le(i))throw new P("@options field `max_output_tokens` must be a non-negative safe integer");r.maxOutputTokens=i}if(a!==void 0){if(!le(a)||a===0||a>ce)throw new P(`@options field \`timeout_ms\` must be a positive integer up to ${ce}`);r.timeoutMs=a}return r}function de(e){if(e.trim()===``)throw new P('Expected JavaScript source text (non-empty). Provide JS only, optionally with a first line `// @options: {"max_output_tokens": 1000}`.');let t=e.indexOf(`
|
|
101
|
+
`),n=(t===-1?e:e.slice(0,t)).replace(/\r$/,``).trimStart();if(!n.startsWith(`// @options:`))return{code:e,options:{}};let r=t===-1?``:e.slice(t);if(r.trim()===``)throw new P(`The @options line must be followed by JavaScript source on subsequent lines`);return{code:r,options:ue(n.slice(12).trim())}}function F(e){return String(e||``).toLowerCase().match(/[\p{L}\p{N}]+/gu)||[]}function fe(e){try{return JSON.stringify(e||{}).toLowerCase()}catch{return``}}function pe(e,t,n){let r=e.name.toLowerCase(),i=String(e.label||``).toLowerCase(),a=n?.(e),o=String(e._mcpServer||a?.name||``).toLowerCase(),s=String([e.description,a?.description].filter(Boolean).join(` `)).toLowerCase(),c=F(`${r} ${i}`);return{tool:e,index:t,name:r,label:i,server:o,namespace:a?.name,description:s,schema:fe(e.parameters),namePhrase:c.join(` `),nameTerms:new Set(c),serverTerms:new Set(F(o))}}function me(e,t,n,r){let i=0;(e.name===t||e.label===t)&&(i+=100),n&&e.namePhrase.includes(n)&&(i+=24);for(let t of r)e.nameTerms.has(t)?i+=14:(e.name.includes(t)||e.label.includes(t))&&(i+=9),e.serverTerms.has(t)?i+=7:e.server.includes(t)&&(i+=4),e.description.includes(t)&&(i+=3),e.schema.includes(t)&&(i+=1);return i}function he(e,{getNamespace:t}={}){return e.map((e,n)=>pe(e,n,t))}function ge(e,{max:t=10}={}){return Number.isInteger(e)?Math.max(1,Math.min(e,t)):5}function _e(e,t,n,{namespace:r,maxLimit:i}={}){let a=String(t||``).trim().toLowerCase(),o=F(a);if(o.length===0)return[];let s=o.join(` `);return e.filter(e=>!r||e.namespace===r).map(e=>({entry:e,score:me(e,a,s,o)})).filter(({score:e})=>e>0).sort((e,t)=>t.score-e.score||e.entry.index-t.entry.index).slice(0,ge(n,{max:i})).map(({entry:e})=>e.tool)}const I=new Set([`chat`,`image`,`classifier`]),L=e=>typeof e==`object`&&!!e&&!Array.isArray(e),ve=(e,t)=>e.length>t?`${e.slice(0,t-3)}...`:e,R=e=>`${/^[aeiou]/.test(e)?`an`:`a`} ${e}`;function ye(e){return{read:async t=>{let n=await e(t);return n?{type:`api_key`,key:n}:void 0},list:async()=>[],modify:async()=>void 0,delete:async()=>{}}}function be({getApiKey:e}){let t;return async()=>{if(!t){let{builtinModels:n}=await import(`@earendil-works/pi-ai/providers/all`);t=n({credentials:ye(e)})}return t}}function xe({classifiers:e,images:t}={}){let n=async(n,r)=>{let i=n===`classifier`?e?(await e()).getModelsOfType(`classifier`):[]:n===`image`?t?.list()??[]:[];return r?i.filter(e=>e.provider===r):i};return{getModelsOfType:n,getAvailableOfType:async(t,r,i)=>t===`classifier`&&e?(await e()).getAvailableOfType(`classifier`,r,i):n(t,r),getModelOfType:async(e,t,r)=>(await n(e,t)).find(e=>e.id===r),classify:async(t,n,r)=>(await e()).classify(t,n,r),generateImages:(e,n,r)=>t.generateImages(e,n,r)}}function z(e){if(typeof e==`string`&&I.has(e))return e;throw Error(`Unknown model type ${JSON.stringify(e)}. Use "chat", "image", or "classifier".`)}function Se(e){if(e!=null){if(typeof e!=`string`)throw Error(`provider must be a string`);return e}}function B(e){let{headers:t,...n}=e;return n}function V(e){if(e==null)return String(e);if(Array.isArray(e))return e.length===0?`an empty array`:`an array`;if(typeof e==`object`){let t=Object.keys(e);return t.length===0?`{}`:`{ ${t.slice(0,6).join(`, `)}${t.length>6?`, ...`:``} }`}return typeof e==`string`?`a string`:`a ${typeof e}`}function Ce(e){let t=e=>Error(`models.classify() ${e}. Expected context: { state: { ... }, questions: { <id>: { type: "choice", instructions, criteria: { <label>: <meaning> } } | { type: "score", instructions, criteria: [<lowest level>, ..., <highest level>] } | { type: "bool", instructions, criteria: { true: <meaning>, false: <meaning> } } } }.`);if(!L(e))throw t(`expects a context object as its second argument, got ${V(e)}`);if(!L(e.state))throw t(`context.state must be an object, got ${V(e.state)}`);let{questions:n}=e;if(!L(n)||Object.keys(n).length===0)throw t(`context.questions must map question IDs to questions, got ${V(n)}`);let r=e=>e.length>0&&e.every(e=>typeof e==`string`);for(let[e,i]of Object.entries(n)){let n=`context.questions.${e}`;if(!L(i))throw t(`${n} must be a question object, got ${V(i)}`);if(typeof i.instructions!=`string`)throw t(`${n}.instructions must be a string`);let{criteria:a}=i;if(i.type===`choice`){if(!L(a)||!r(Object.values(a)))throw t(`${n} is a "choice" question, so criteria must map each label to its meaning`)}else if(i.type===`score`){if(!Array.isArray(a)||!r(a))throw t(`${n} is a "score" question, so criteria must list the levels as strings, lowest first`)}else if(i.type===`bool`){if(!L(a)||typeof a.true!=`string`||typeof a.false!=`string`)throw t(`${n} is a "bool" question, so criteria must be { true: string, false: string }`)}else throw t(`${n}.type must be "choice", "score", or "bool", got ${JSON.stringify(i.type)}`)}return e}function we(e){let t=e=>Error(`models.generateImages() ${e}. Expected context: { input: [{ type: "text", text: <prompt> }, ...optional { type: "image", data: <base64>, mimeType } references] }.`);if(!L(e))throw t(`expects a context object as its second argument, got ${V(e)}`);let{input:n}=e;if(!Array.isArray(n)||n.length===0)throw t(`context.input must be a non-empty array of blocks, got ${V(n)}`);return n.forEach((e,n)=>{if(!(L(e)&&e.type===`text`&&typeof e.text==`string`)&&!(L(e)&&e.type===`image`&&typeof e.data==`string`&&typeof e.mimeType==`string`))throw t(`context.input[${n}] must be a text or image block, got ${V(e)}`)}),e}function Te(e){let t=0,n=[];return async r=>{t>=e?await new Promise(e=>n.push(e)):t+=1;try{return await r()}finally{let e=n.shift();e?e():--t}}}function Ee(e,{toolCallId:t,track:n,publish:r,addUsage:i,addGeneratedImages:a}){let o=Te(4),s=0,c=async(a,c,[l,u],d,f)=>{let p=`List the ${c} models you can use with models.getAvailableOfType("${c}").`;if(!L(l)||typeof l.provider!=`string`||typeof l.id!=`string`){let e=l==null?` models.getModelOfType() returns undefined for an unknown provider or id.`:``;throw Error(`${a}() expects ${R(c)} model as its first argument, got ${V(l)}.${e} ${p}`)}let{provider:m,id:h}=l,g=await e.getModelOfType(c,m,h);if(!g){let t;for(let n of I)n!==c&&await e.getModelOfType(n,m,h)&&(t=n);throw Error(t?`"${m}/${h}" is ${R(t)} model, not ${R(c)} model. ${p}`:`Unknown ${c} model "${m}/${h}". ${p}`)}let _=d(u),v=n({id:`${t}/${a}/${++s}`,name:a,args:`${g.provider}/${g.id}`,status:`running`});r();let y=performance.now(),b=await o(()=>f(g,_));return v.durationMs=performance.now()-y,v.status=b.stopReason===`stop`?`ok`:b.stopReason===`aborted`?`cancelled`:`error`,b.errorMessage&&(v.error=ve(b.errorMessage,500)),b.usage&&(v.cost=b.usage.cost?.total,i(b.usage)),r(),b};return Object.entries({"models.getModelsOfType":async([t,n])=>(await e.getModelsOfType(z(t),Se(n))).map(B),"models.getAvailableOfType":async([t,n],{signal:r})=>(await e.getAvailableOfType(z(t),Se(n),{signal:r})).map(B),"models.getModelOfType":async t=>{let[n,r,i]=t;if(typeof r!=`string`||typeof i!=`string`)throw Error(`models.getModelOfType(type, provider, id) expects three strings, got (${t.map(V).join(`, `)}). The provider and the id are separate arguments.`);let a=await e.getModelOfType(z(n),r,i);return a===void 0?void 0:B(a)},"models.classify":(t,{signal:n})=>c(`models.classify`,`classifier`,t,Ce,(t,r)=>e.classify(t,r,{signal:n})),"models.generateImages":(t,{signal:n})=>c(`models.generateImages`,`image`,t,we,async(t,r)=>{let i=await e.generateImages(t,r,{signal:n});return a(i.output.filter(e=>e.type===`image`).length),i})}).map(([e,t])=>({name:e,spread:!0,execute:t}))}const De=256*1024*1024,Oe=5*6e4,ke=30*6e4,Ae=(e,t)=>e.length>t?`${e.slice(0,t-3)}...`:e;function je(e){if(e===void 0)return``;try{return Ae(JSON.stringify(e)??``,200)}catch{return``}}const Me=e=>(e?.content??[]).filter(e=>e.type===`text`).map(e=>e.text).join(`
|
|
102
|
+
`),Ne=e=>typeof e==`string`?e:JSON.stringify(e)??String(e);function Pe(e,t){if(!e)return structuredClone(t);for(let n of[`input`,`output`,`cacheRead`,`cacheWrite`,`totalTokens`])e[n]=(e[n]??0)+(t[n]??0);if(t.cost){e.cost??={};for(let[n,r]of Object.entries(t.cost))e.cost[n]=(e.cost[n]??0)+(r??0)}return e}function Fe(e){return e.length===0?`No tool calls were made.`:`Tool calls made before the failure (they are not undone): ${e.map(e=>`${e.name} (${e.status})`).join(`, `)}`}function Ie(e,t){return`${e.kind===`script`?e.stack??`${e.name??`Error`}: ${e.message}`:e.kind===`timeout`?`Script timed out: ${e.message}`:e.kind===`aborted`?`Script aborted: ${e.message}`:`Script sandbox failed: ${e.message}`}\n\n${Fe(t)}`}function Le(e,t){let n=Math.floor(t/2),r=t-n,i=e.length-n-r;return`${e.slice(0,n)}…${Math.ceil(i/4)} tokens truncated…${r>0?e.slice(-r):``}`}function Re(e,t){let n=Array.from({length:e.length}),r=e.map((e,t)=>({length:e,index:t})).sort((e,t)=>e.length-t.length),i=t;return r.forEach(({length:e,index:t},a)=>{n[t]=Math.min(e,Math.floor(i/(r.length-a))),i-=n[t]}),n}async function ze(e,t,n){let r=e.filter(e=>e.type===`text`).map(e=>e.text),i=r.join(`
|
|
103
|
+
`),a=t*4;if(r.length===0||i.length<=a)return{items:e};let o=Re(r.map(e=>e.length),a),s=r.every((e,t)=>e.length<=o[t]||o[t]>=400)?r.map((e,t)=>e.length>o[t]?Le(e,o[t]):e).join(`
|
|
104
|
+
`):Le(i,a),c=`Warning: truncated output (original token count: ${Math.ceil(i.length/4)})\nTotal output lines: ${i.split(`
|
|
105
|
+
`).length}\n\n${s}`,l;if(n)try{l=await n(i),l&&(c+=`\n\n[Full output: ${l} (read with offset/limit)]`)}catch(e){c+=`\n\n[Could not save the full output: ${e?.message||e}]`}return{items:[{type:`text`,text:c},...e.filter(e=>e.type===`image`)],...l?{fullOutputPath:l}:{}}}function Be(e,t){let{result:n}=t;if(e.outputSchema&&n.structuredContent!==void 0)return n.structuredContent;let r=Me(n);if(t.isError)throw Error(r||`Tool "${e.name}" failed`);return r}function Ve(e,t){let n=a(e),r=a(t),i=e=>e.includes(`__`)?e.slice(e.lastIndexOf(`__`)+2):void 0;return e===t||n===r||i(e)===t||i(n)===r}function He(e,t,n){let r=he(e,{getNamespace:n}),i=e=>({name:a(e.name),description:t.get(e.name)??``}),o=t=>e.map(n).find(e=>e&&Ve(e.name,t));return[{name:`searchTools`,spread:!0,execute:([e,t])=>{if(typeof e!=`string`)throw Error(`searchTools() expects a query string`);let n=t?.limit??8;if(typeof n!=`number`||!Number.isInteger(n)||n<=0)throw Error(`searchTools() limit must be a positive integer`);let a=t?.namespace;if(a!=null&&typeof a!=`string`)throw Error(`searchTools() namespace must be a string`);let s=a?o(a):void 0;return a&&!s?[]:_e(r,e,n,{namespace:s?.name,maxLimit:Math.max(n,8)}).map(i)}},{name:`describeTool`,spread:!0,execute:([n])=>{if(typeof n!=`string`)throw Error(`describeTool() expects a tool name`);let r=e.find(e=>e.name===n||a(e.name)===n);return r?t.get(r.name):void 0}},{name:`describeNamespace`,spread:!0,execute:([t])=>{if(typeof t!=`string`)throw Error(`describeNamespace() expects a namespace name`);let r=o(t);if(r)return{name:r.name,...r.description?{description:r.description}:{},...r.instructions?{instructions:r.instructions}:{},tools:e.filter(e=>n(e)?.name===r.name).map(e=>a(e.name))}}}]}async function Ue({toolCallId:e,code:t,signal:n,onUpdate:r,tools:i,executeTool:a,store:o={},models:s,getNamespace:l=()=>void 0,sandbox:u={},spillOutput:d}){let f=performance.now(),p;try{p=de(t)}catch(e){if(!(e instanceof P))throw e;return{content:[{type:`text`,text:`Script failed\nScript error:\n${e.message}`}],details:{calls:[]},isError:!0}}let{code:m,options:h}=p,g=[],_=0,v,y=0,b=()=>g.slice(-200).map(e=>({...e})),x=()=>r?.({content:[],details:{calls:b()}}),S=e=>(g.push(e),e),C=new Map(i.map(e=>[e.name,c(ee(e))])),w=i.map(t=>({name:t.name,description:C.get(t.name),execute:async(n={},{signal:r})=>{let i=S({id:`${e}/${++_}`,name:t.name,args:je(n),status:`running`});x();let o=performance.now(),s=await a(t,n,{id:i.id,parentToolCallId:e,signal:r});return i.durationMs=performance.now()-o,s.isError?(i.status=r.aborted?`cancelled`:`error`,i.error=Ae(Me(s.result)||`Tool "${t.name}" failed`,500)):i.status=`ok`,x(),Be(t,s)}})),T=Math.min(h.timeoutMs??u.defaultTimeoutMs??1/0,u.maxTimeoutMs??1/0),E=new oe({tools:w,globals:[...He(i,C,l),...s?Ee(s,{toolCallId:e,track:S,publish:x,addUsage:e=>{v=Pe(v,e)},addGeneratedImages:e=>{y+=e}}):[]],timeoutMs:T,memoryLimitBytes:u.memoryLimitBytes??268435456,...u.wasmPath?{wasm:te(u.wasmPath)}:{},...u.workerUrl?{workerUrl:u.workerUrl}:{}}),ne=u.limit??(e=>e()),D;try{D=await ne(()=>E.execute(m,{signal:n,store:o}))}finally{await E.close()}for(let e of g)e.status===`running`&&(e.status=`cancelled`);let O=D.output.map(e=>e.type===`text`?{type:`text`,text:e.text}:e);D.ok?D.value!==void 0&&O.push({type:`text`,text:Ne(D.value)}):O.push({type:`text`,text:`Script error:\n${Ie(D.error,g)}`}),y>0&&!O.some(e=>e.type===`image`)&&O.push({type:`text`,text:`Note: models.generateImages() returned ${y} image${y===1?``:`s`} that the script did not show. Show each image block of result.output with image(block).`});let k=await ze(O,h.maxOutputTokens??1e4,d),A=((performance.now()-f)/1e3).toFixed(1),j=`${D.ok?`Script completed`:`Script failed`}\nWall time ${A} seconds\nOutput:\n`,M=D.ok?D.storeWrites:null,re=M&&(Object.keys(M.set).length>0||M.delete.length>0);return{content:[{type:`text`,text:j},...k.items],details:{calls:b(),...g.length>200?{omittedCalls:g.length-200}:{},...re?{storeWrites:M}:{},...k.fullOutputPath?{fullOutputPath:k.fullOutputPath}:{}},...v?{usage:v}:{},...D.ok?{}:{isError:!0}}}const H=`codemode`,U=`codemodeStore`,W=e=>typeof e==`object`&&!!e&&!Array.isArray(e);function We(e,t){if(!(!W(t)||!W(t.set)||!Array.isArray(t.delete))){for(let n of t.delete)delete e[n];Object.assign(e,t.set)}}function Ge(e){let t=Object.create(null);for(let n of Array.isArray(e)?e:[]){if(W(n?.codemodeStore)){for(let e of Object.keys(t))delete t[e];Object.assign(t,n[U])}n?.role===`toolResult`&&n.toolName===`codemode`&&We(t,n.details?.storeWrites)}return t}function Ke(e,t){let n=Ge(t);Object.keys(n).length>0&&(e[U]=n)}var qe=class extends Error{constructor(e,t){super(e),this.name=`ParseError`,this.type=t.type,this.field=t.field,this.value=t.value,this.line=t.line}};function G(e){}function Je(e){if(typeof e==`function`)throw TypeError("`callbacks` must be an object, got a function instead. Did you mean `{onEvent: fn}`?");let{onEvent:t=G,onError:n=G,onRetry:r=G,onComment:i}=e,a=``,o=!0,s,c=``,l=``;function u(e){let t=o?e.replace(/^\xEF\xBB\xBF/,``):e,[n,r]=Ye(`${a}${t}`);for(let e of n)d(e);a=r,o=!1}function d(e){if(e===``){p();return}if(e.startsWith(`:`)){i&&i(e.slice(e.startsWith(`: `)?2:1));return}let t=e.indexOf(`:`);if(t!==-1){let n=e.slice(0,t),r=e[t+1]===` `?2:1;f(n,e.slice(t+r),e);return}f(e,``,e)}function f(e,t,i){switch(e){case`event`:l=t;break;case`data`:c=`${c}${t}
|
|
106
|
+
`;break;case`id`:s=t.includes(`\0`)?void 0:t;break;case`retry`:/^\d+$/.test(t)?r(parseInt(t,10)):n(new qe(`Invalid \`retry\` value: "${t}"`,{type:`invalid-retry`,value:t,line:i}));break;default:n(new qe(`Unknown field "${e.length>20?`${e.slice(0,20)}\u2026`:e}"`,{type:`unknown-field`,field:e,value:t,line:i}));break}}function p(){c.length>0&&t({id:s,event:l||void 0,data:c.endsWith(`
|
|
107
|
+
`)?c.slice(0,-1):c}),s=void 0,c=``,l=``}function m(e={}){a&&e.consume&&d(a),o=!0,s=void 0,c=``,l=``,a=``}return{feed:u,reset:m}}function Ye(e){let t=[],n=``,r=0;for(;r<e.length;){let i=e.indexOf(`\r`,r),a=e.indexOf(`
|
|
108
|
+
`,r),o=-1;if(i!==-1&&a!==-1?o=Math.min(i,a):i===-1?a!==-1&&(o=a):o=i===e.length-1?-1:i,o===-1){n=e.slice(r);break}else{let n=e.slice(r,o);t.push(n),r=o+1,e[r-1]===`\r`&&e[r]===`
|
|
109
|
+
`&&r++}}return[t,n]}const K=`ctor-images`,q=2e3;async function J(e,t,n){let r=await e(t,n),i=await r.text();if(!r.ok)throw Error(`HTTP ${r.status}: ${i.slice(0,q)}`);try{return JSON.parse(i)}catch{throw Error(`Provider returned invalid JSON: ${i.slice(0,q)}`)}}function Y(e){return{prompt:e.filter(e=>e.type===`text`).map(e=>e.text).join(`
|
|
110
|
+
`),images:e.filter(e=>e.type===`image`)}}const X=(e,t,n)=>{let r=e[t];if(!r)throw Error(n);return r};function Xe(e){let{prompt:t,images:n}=Y(e);return{contents:[{role:`user`,parts:[{text:t},...n.map(e=>({inlineData:{mimeType:e.mimeType,data:e.data}}))]}],generationConfig:{responseModalities:[`TEXT`,`IMAGE`]}}}function Ze(e){let t=e?.candidates?.[0]?.content?.parts||e?.parts||[],n=[];for(let e of t){e?.text&&n.push({type:`text`,text:e.text});let t=e?.inlineData||e?.inline_data;t?.data&&n.push({type:`image`,data:t.data,mimeType:t.mimeType||t.mime_type||`image/png`})}return n}function Qe(e,t,n){let r=encodeURIComponent(t);if(e===`bricks`){let e=X(n,`BRICKS_AI_GATEWAY_URL`,`BRICKS image generation needs a signed-in BRICKS account.`),t;try{t=JSON.parse(n.BRICKS_AI_GATEWAY_HEADERS||`{}`)}catch{throw Error(`BRICKS_AI_GATEWAY_HEADERS must be a JSON object of request headers.`)}return{url:`${e.replace(/\/+$/,``)}/v1/gemini/models/${r}:generateContent`,headers:t}}if(e===`google`){let e=n.GEMINI_API_KEY||n.GOOGLE_API_KEY;if(!e)throw Error(`Google image generation needs a Gemini API key.`);return{url:`https://generativelanguage.googleapis.com/v1beta/models/${r}:generateContent?key=${encodeURIComponent(e)}`,headers:{}}}let i=X(n,`GOOGLE_CLOUD_PROJECT`,`Vertex AI image generation needs a Google Cloud project.`),a=n.GOOGLE_CLOUD_LOCATION||`global`,o=a===`global`?`aiplatform.googleapis.com`:`${a}-aiplatform.googleapis.com`,s=n.GOOGLE_VERTEX_API_KEY?{"x-goog-api-key":n.GOOGLE_VERTEX_API_KEY}:n.GOOGLE_VERTEX_ACCESS_TOKEN?{Authorization:`Bearer ${n.GOOGLE_VERTEX_ACCESS_TOKEN}`}:null;if(!s)throw Error(`Vertex AI image generation needs an API key or an access token.`);return{url:`https://${o}/v1/projects/${encodeURIComponent(i)}/locations/${encodeURIComponent(a)}/publishers/google/models/${r}:generateContent`,headers:s}}async function Z(e,t,n,r,{fetch:i,signal:a}){let{url:o,headers:s}=Qe(e,t,r);return Ze(await J(i,o,{method:`POST`,headers:{...s,"Content-Type":`application/json`},body:JSON.stringify(Xe(n)),signal:a}))}async function Q(e,t,n){let r=[];for(let i of Array.isArray(t?.data)?t.data:[])if(i?.b64_json)r.push({type:`image`,data:i.b64_json,mimeType:`image/png`});else if(i?.url){let t=await e(i.url,{signal:n});if(!t.ok)throw Error(`Failed to download image URL: HTTP ${t.status}`);let a=t.headers.get(`content-type`)||`image/png`;r.push({type:`image`,data:Buffer.from(await t.arrayBuffer()).toString(`base64`),mimeType:a})}return r}async function $e(e,t,n,{fetch:r,signal:i}){let a=X(n,`OPENAI_API_KEY`,`OpenAI image generation needs an API key.`),o=(n.OPENAI_BASE_URL||`https://api.openai.com/v1`).replace(/\/$/,``),{prompt:s,images:c}=Y(t),l={model:e,prompt:s,n:1,size:`auto`,quality:`medium`,output_format:`png`};if(c.length===0)return Q(r,await J(r,`${o}/images/generations`,{method:`POST`,headers:{Authorization:`Bearer ${a}`,"Content-Type":`application/json`},body:JSON.stringify(l),signal:i}),i);let u=new FormData;for(let[e,t]of Object.entries(l))u.append(e,String(t));return c.forEach((e,t)=>{let n=e.mimeType.split(`/`)[1]||`png`;u.append(c.length>1?`image[]`:`image`,new Blob([Buffer.from(e.data,`base64`)],{type:e.mimeType}),`reference-${t+1}.${n}`)}),Q(r,await J(r,`${o}/images/edits`,{method:`POST`,headers:{Authorization:`Bearer ${a}`},body:u,signal:i}),i)}function et(e){let t=(e.OPENAI_CODEX_BASE_URL||`https://chatgpt.com/backend-api`).replace(/\/+$/,``);return t.endsWith(`/codex/responses`)?t:t.endsWith(`/codex`)?`${t}/responses`:`${t}/codex/responses`}function tt(e){try{let t=JSON.parse(Buffer.from(e.split(`.`)[1]||``,`base64url`).toString(`utf8`))?.[`https://api.openai.com/auth`]?.chatgpt_account_id;if(typeof t==`string`&&t)return t}catch{}throw Error(`The OpenAI Codex token does not include a ChatGPT account ID.`)}async function*nt(e){if(!e.body)return;let t=e.body.getReader(),n=new TextDecoder,r=[],i=Je({onEvent:({data:e})=>r.push(e)}),a=!1;try{for(;;){let{done:e,value:o}=await t.read(),s=n.decode(o,{stream:!e});i.feed(s),s&&(a=s.endsWith(`\r`)),e&&a&&i.feed(`
|
|
111
|
+
`);for(let e of r)if(e&&e!==`[DONE]`)try{yield JSON.parse(e)}catch{}if(r.length=0,e)break}}finally{await t.cancel().catch(()=>{}),t.releaseLock()}}async function rt(e,t,n,{fetch:r,signal:i}){let a=X(n,`OPENAI_CODEX_ACCESS_TOKEN`,`OpenAI Codex image generation needs a ChatGPT sign-in.`),{prompt:o,images:s}=Y(t),c=await r(et(n),{method:`POST`,headers:{Authorization:`Bearer ${a}`,"chatgpt-account-id":tt(a),originator:`pi`,"OpenAI-Beta":`responses=experimental`,Accept:`text/event-stream`,"Content-Type":`application/json`},body:JSON.stringify({model:e,store:!1,stream:!0,instructions:[`You are an image generation agent.`,`Use the image_generation tool for the user request.`,`Do not finish with text only when an image can be generated.`,s.length>0?`Use the attached image(s) as visual input and create one edited image.`:`Create one image.`].join(` `),input:[{role:`user`,content:[{type:`input_text`,text:o},...s.map(e=>({type:`input_image`,detail:`auto`,image_url:`data:${e.mimeType};base64,${e.data}`}))]}],tools:[{type:`image_generation`,output_format:`png`}],tool_choice:`auto`,parallel_tool_calls:!1,text:{verbosity:`low`}}),signal:i});if(!c.ok)throw Error(`HTTP ${c.status}: ${(await c.text()).slice(0,q)}`);let l=[];for await(let e of nt(c)){if(e?.type===`error`)throw Error(e.message||e.code||`Codex error`);if(e?.type===`response.failed`)throw Error(e.response?.error?.message||`OpenAI Codex response failed.`);let t=e?.item;if(t?.type===`message`)for(let e of Array.isArray(t.content)?t.content:[]){let t=e?.text||e?.content?.[0]?.text;typeof t==`string`&&t.trim()&&l.push({type:`text`,text:t})}t?.type===`image_generation_call`&&t.result&&l.push({type:`image`,data:t.result,mimeType:`image/png`})}return l}const it={bricks:(e,t,n,r)=>Z(`bricks`,e,t,n,r),google:(e,t,n,r)=>Z(`google`,e,t,n,r),"google-vertex":(e,t,n,r)=>Z(`google-vertex`,e,t,n,r),openai:$e,"openai-codex":rt};function at({listProviders:e,resolveEnv:t,fetch:n=globalThis.fetch}){return{list:()=>e().filter(({provider:e})=>it[e]).map(({provider:e,defaultModel:t})=>({type:`image`,provider:e,id:t,name:t,api:K,input:[`text`,`image`],output:[`text`,`image`]})),generateImages:async(e,r,{signal:i}={})=>{let a={api:K,provider:e.provider,model:e.id};try{let o=await t(e.provider),s=await it[e.provider](e.id,r.input,o,{fetch:n,signal:i}),c=s.some(e=>e.type===`image`);return{...a,output:s,stopReason:c?`stop`:`error`,...c?{}:{errorMessage:`The model returned no image.`},timestamp:Date.now()}}catch(e){return{...a,output:[],stopReason:i?.aborted?`aborted`:`error`,errorMessage:e instanceof Error?e.message:String(e),timestamp:Date.now()}}}}}const ot={type:`object`,properties:{code:{type:`string`,description:`Raw JavaScript source.`}},required:[`code`],additionalProperties:!1};function st(e){if(!e?._mcpNamespace)return;let t=e._mcpServerInstructions?.trim();return{name:e._mcpNamespace,description:e._mcpServerDescription??t?.split(`
|
|
112
|
+
`)[0],...t?{instructions:t}:{}}}const $=e=>!!e?._mcpServer&&e._mcpExposure!==`direct`;function ct(e,t,{id:n,parentToolCallId:i,signal:a,hooks:o,state:s}){let c={type:`toolCall`,id:n,name:e.name,arguments:t,parentToolCallId:i},l=s?.messages??[],u=[...l].reverse().find(e=>e.role===`assistant`);return r(c,{tools:[e],assistantMessage:u??{role:`assistant`,content:[c]},context:{systemPrompt:s?.systemPrompt??``,messages:l,tools:s?.tools??[e]},beforeToolCall:o?.beforeToolCall,afterToolCall:o?.afterToolCall,signal:a})}function lt(e){return{...e,execute:(t,n,r,i)=>e.execute(t,n,r,i&&(e=>i(e.details)))}}function ut({getTools:e,declaredNames:t,isDeferred:n=$,getNamespace:r=st,executeTool:i,getStore:a=()=>({}),models:o,sandbox:s,spillOutput:c,inlineBudget:l}){let u=()=>e().filter(e=>e.name!==H);return{name:H,label:`Codemode`,description:T({tools:u(),declaredNames:t,isDeferred:n,getNamespace:r,models:!!o,inlineBudget:l}),parameters:ot,execute:(e,t,n,l)=>Ue({toolCallId:e,code:t?.code??``,signal:n,onUpdate:l,tools:u(),executeTool:i,store:a(),models:o,getNamespace:r,sandbox:s,spillOutput:c})}}export{ot as CODEMODE_PARAMETERS,U as CODEMODE_STORE_SNAPSHOT_KEY,H as CODEMODE_TOOL_NAME,K as CTOR_IMAGE_API,b as DEFAULT_CODEMODE_INLINE_BUDGET,De as DEFAULT_CODEMODE_MEMORY_LIMIT_BYTES,Oe as DEFAULT_CODEMODE_TIMEOUT_MS,ke as MAX_CODEMODE_TIMEOUT_MS,Ke as annotateCodemodeStore,Ge as collectCodemodeStore,be as createClassifierModels,T as createCodemodeDescription,xe as createCodemodeModels,ut as createCodemodeTool,at as createImageModels,Te as createLimiter,Ue as executeCodemode,$ as isSearchOnlyMcpTool,st as mcpToolNamespace,ct as runNestedToolCall,lt as withDetailsUpdates};
|