privateer-agent 0.14.6 → 0.15.0
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 +5 -5
- package/bin/headless-flags.mjs +1 -1
- package/bin/privateer-launch.mjs +33 -0
- package/bin/privateer-subagent.mjs +13 -0
- package/extensions/privateer-codemode.ts +15 -0
- package/extensions/privateer-connect.ts +21 -18
- package/extensions/privateer-web.ts +1 -1
- package/package.json +8 -9
- package/patches/{@earendil-works+pi-ai+0.86.0.patch → @earendil-works+pi-ai+1.3.0.patch} +6 -7
- package/patches/{@earendil-works+pi-coding-agent+0.86.0.patch → @earendil-works+pi-coding-agent+1.3.0.patch} +275 -184
- package/patches/{pi-background-tasks+2.5.0.patch → pi-background-tasks+2.6.10.patch} +36 -21
- package/src/bridge/engineAdapter.ts +6 -0
- package/src/config/moat.ts +51 -12
- package/src/config/moatManifest.json +3 -3
- package/src/config/whatsNew.json +4 -4
- package/src/ext/permissionGate.ts +39 -2
- package/src/harbor/closeSession.ts +1 -1
- package/src/harbor/index.ts +55 -133
- package/src/mcp/catalog.ts +10 -9
- package/src/mcp/piMcp.ts +139 -0
- package/src/mcp/toolNames.ts +39 -140
- package/src/permissions/classify.ts +42 -4
- package/src/permissions/protected.ts +7 -0
- package/src/providers/account.ts +7 -2
- package/src/remote/mcpControl.ts +189 -66
- package/src/remote/relayClient.ts +1 -1
- package/src/routines/toolSelect.ts +4 -4
- package/src/harbor/buildLock.ts +0 -19
package/README.md
CHANGED
|
@@ -78,7 +78,7 @@ What Privateer adds is a *moat* of Pi extensions layered on top:
|
|
|
78
78
|
| `privateer-posture`, `privateer-tools` | live attestation shield + Privateer tool pack |
|
|
79
79
|
| `privateer-web` | `web_search`/`web_fetch` — your account's search once signed in, or your own provider (`rpiv-web-tools`: self-hosted SearXNG, Brave, Tavily…) |
|
|
80
80
|
| `rpiv-ask-user-question` | `ask_user_question` — a structured questionnaire the agent puts to you instead of guessing |
|
|
81
|
-
| `
|
|
81
|
+
| `privateer-codemode`, `pi-subagents` | codemode (how MCP tools are called — Pi's built-in MCP support) · bounded parallel sub-agents |
|
|
82
82
|
|
|
83
83
|
They're ordinary Pi extensions — inspect them, replace them, or build your own alongside.
|
|
84
84
|
**Extend by discovery:** drop an extension into `~/.privateer/agent/extensions/` (move the home
|
|
@@ -641,7 +641,7 @@ built-ins. Two kinds:
|
|
|
641
641
|
|
|
642
642
|
```
|
|
643
643
|
/connect # add, enable, disable, or remove connectors
|
|
644
|
-
/mcp #
|
|
644
|
+
/mcp # Pi's own status view — what connected; `/mcp login <name>` to sign in
|
|
645
645
|
```
|
|
646
646
|
|
|
647
647
|
`/connect` opens a picker over a curated catalog of 21 connectors — GitHub, Slack, Notion,
|
|
@@ -748,9 +748,9 @@ plan-mode deny is the one thing it doesn't talk around.
|
|
|
748
748
|
Everything below is a **Pi extension** loaded by discovery (see [Built on Pi](#built-on-pi)) —
|
|
749
749
|
drop your own into `~/.privateer/agent/extensions/` and it loads the same way, gated like the rest.
|
|
750
750
|
|
|
751
|
-
- **MCP servers** (
|
|
752
|
-
like the rest (local stdio, or remote HTTP with
|
|
753
|
-
`/connect`; see [Connectors — MCP](#connectors--mcp).
|
|
751
|
+
- **MCP servers** (Pi's built-in MCP support) — declare them and the model reaches their tools
|
|
752
|
+
through codemode, each call gated like the rest (local stdio, or remote streamable HTTP with
|
|
753
|
+
interactive OAuth). Add them with `/connect`; see [Connectors — MCP](#connectors--mcp).
|
|
754
754
|
- **Sub-agents** (`pi-subagents`) — delegate investigations to bounded parallel agents. Children
|
|
755
755
|
run as headless child processes that **inherit the moat**, so their actions hit the same
|
|
756
756
|
permission gate and their approvals surface on your phone.
|
package/bin/headless-flags.mjs
CHANGED
|
@@ -180,7 +180,7 @@ export function privateProblem(args) {
|
|
|
180
180
|
// Pi's own subcommands. Pi recognizes each only as args[0], so the launcher must hand
|
|
181
181
|
// them over with NOTHING in front — the normal launch prepends --model, -e and --skill,
|
|
182
182
|
// which is exactly how `privateer auth check` used to become a chat message.
|
|
183
|
-
export const PI_SUBCOMMANDS = ["auth", "install", "remove", "uninstall", "list", "config"];
|
|
183
|
+
export const PI_SUBCOMMANDS = ["auth", "install", "remove", "uninstall", "list", "config", "mcp"];
|
|
184
184
|
|
|
185
185
|
const AUTH_SUBCOMMANDS = ["check", "print-api-key", "print-bearer-token"];
|
|
186
186
|
|
package/bin/privateer-launch.mjs
CHANGED
|
@@ -690,6 +690,18 @@ else if (PI_SUBCOMMANDS.includes(sub)) {
|
|
|
690
690
|
configureCompileCache(PRIVATEER_HOME); // same Pi module graph as a launch — reuse its compiled code
|
|
691
691
|
ensurePatches(); // project `.privateer/` config dirs are a patch; -l scope needs them
|
|
692
692
|
process.env.PI_CODING_AGENT_DIR = AGENT_DIR;
|
|
693
|
+
// `mcp list` / `mcp login` read agent/mcp.json. A file an older release wrote for
|
|
694
|
+
// pi-mcp-adapter is rejected entry by entry, so upgrade it first — the same one-time
|
|
695
|
+
// step every session runs (src/remote/mcpControl.ts). Best-effort: a failure leaves
|
|
696
|
+
// Pi to report the file as it stands.
|
|
697
|
+
if (sub === "mcp") {
|
|
698
|
+
const mcpControl = pathToFileURL(path.join(REPO, "src", "remote", "mcpControl.ts")).href;
|
|
699
|
+
spawnSync(NODE_BIN, ["--import", "tsx", "--input-type=module", "-e", `(await import(${JSON.stringify(mcpControl)})).migrateMcpProjection()`], {
|
|
700
|
+
cwd: REPO,
|
|
701
|
+
stdio: "ignore",
|
|
702
|
+
env: process.env,
|
|
703
|
+
});
|
|
704
|
+
}
|
|
693
705
|
const nodeArgs = fs.existsSync(ENV_FILE) ? [`--env-file=${ENV_FILE}`] : [];
|
|
694
706
|
runToCompletion(NODE_BIN, [...nodeArgs, CLI, ...args]);
|
|
695
707
|
}
|
|
@@ -754,6 +766,11 @@ else {
|
|
|
754
766
|
process.env.PI_SUBAGENT_PI_BINARY = path.join(REPO, "bin", "privateer-subagent.mjs");
|
|
755
767
|
}
|
|
756
768
|
process.env.PRIVATEER_CHILD_EXTENSIONS = MOAT_PATHS.join(path.delimiter);
|
|
769
|
+
// A child also gets Pi's MCP support and tool_search, which it loads as built-ins:
|
|
770
|
+
// children run --no-extensions (bin/privateer-subagent.mjs), which turns built-ins off
|
|
771
|
+
// too, so a subagent of this terminal would otherwise lose the connectors its parent
|
|
772
|
+
// has. codemode reaches it as our own moat extension (privateer-codemode).
|
|
773
|
+
process.env.PRIVATEER_CHILD_BUILTINS = "mcp,tool-search";
|
|
757
774
|
// Suppress Pi's upstream update banner (our banner is the startup surface). Disables
|
|
758
775
|
// ONLY the version fetch — fd/rg can still download on first run.
|
|
759
776
|
if (!process.env.PI_SKIP_VERSION_CHECK) process.env.PI_SKIP_VERSION_CHECK = "1";
|
|
@@ -769,6 +786,22 @@ else {
|
|
|
769
786
|
if (s.quietStartup === undefined) { s.quietStartup = true; m = true; }
|
|
770
787
|
if (s.collapseChangelog === undefined) { s.collapseChangelog = true; m = true; }
|
|
771
788
|
if (s.lastChangelogVersion === undefined) { s.lastChangelogVersion = "9999.0.0"; m = true; }
|
|
789
|
+
// Pi 1.0 made fullscreen the default; our footer, splash and compaction patches are
|
|
790
|
+
// built for the regular scrollback UI. A user who picks fullscreen keeps it.
|
|
791
|
+
if (s.tuiMode === undefined) { s.tuiMode = "regular"; m = true; }
|
|
792
|
+
// Pi 1.3's `/share` defaults to "auto", which uploads to Earendil's Radius when the
|
|
793
|
+
// user is signed in there. A share leaves the machine only to the user's own gist.
|
|
794
|
+
if (s.sessionShareDestination === undefined) { s.sessionShareDestination = "gist"; m = true; }
|
|
795
|
+
// Pi 0.99 auto-loads built-in extensions. builtin:mcp and builtin:tool-search are
|
|
796
|
+
// ours to keep — Pi's MCP support IS our MCP support. builtin:codemode is replaced by
|
|
797
|
+
// extensions/privateer-codemode.ts (the same tool without the `models` global), and
|
|
798
|
+
// builtin:llama.cpp, a local-model integration, stays off until adopted on purpose.
|
|
799
|
+
// Each is added once — a user's own +/- entry for that built-in is left alone.
|
|
800
|
+
const ext = Array.isArray(s.extensions) ? s.extensions : [];
|
|
801
|
+
for (const name of ["builtin:codemode", "builtin:llama.cpp"]) {
|
|
802
|
+
if (!ext.some((e) => typeof e === "string" && e.replace(/^[+\-!]/, "") === name)) { ext.push(`-${name}`); m = true; }
|
|
803
|
+
}
|
|
804
|
+
if (ext.length) s.extensions = ext;
|
|
772
805
|
if (m) fs.writeFileSync(sp, JSON.stringify(s, null, 2) + "\n");
|
|
773
806
|
} catch { /* best-effort */ }
|
|
774
807
|
|
|
@@ -67,9 +67,22 @@ export function piCliPath(repoRoot = REPO) {
|
|
|
67
67
|
// Given the args pi-subagents built for the child, return the args to run the bundled
|
|
68
68
|
// cli.js with: `--no-extensions` + one `-e <path>` per moat extension, THEN the
|
|
69
69
|
// original args (so the injected flags precede the positional `Task:` prompt).
|
|
70
|
+
// Pi's built-in extensions the parent wants its children to keep (MCP, tool_search),
|
|
71
|
+
// by bare name. `--no-extensions` turns built-ins off too, so each is loaded back with
|
|
72
|
+
// `-e builtin:<name>`. A separate, comma-separated variable rather than entries in
|
|
73
|
+
// PRIVATEER_CHILD_EXTENSIONS: that one splits on path.delimiter, which is ":" on
|
|
74
|
+
// macOS and Linux — the very character in "builtin:mcp".
|
|
75
|
+
export function childBuiltins(env = process.env) {
|
|
76
|
+
return (env.PRIVATEER_CHILD_BUILTINS ?? "")
|
|
77
|
+
.split(",")
|
|
78
|
+
.map((n) => n.trim())
|
|
79
|
+
.filter((n) => /^[a-z0-9.-]+$/i.test(n));
|
|
80
|
+
}
|
|
81
|
+
|
|
70
82
|
export function buildChildArgs(originalArgs, repoRoot = REPO, env = process.env) {
|
|
71
83
|
const inject = ["--no-extensions"];
|
|
72
84
|
for (const p of moatExtensionPaths(repoRoot, env)) inject.push("-e", p);
|
|
85
|
+
for (const name of childBuiltins(env)) inject.push("-e", `builtin:${name}`);
|
|
73
86
|
return [...inject, ...originalArgs];
|
|
74
87
|
}
|
|
75
88
|
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// codemode — the model writes a short script that calls tools, and only the script's
|
|
2
|
+
// output comes back. It is how Pi's built-in MCP support reaches server tools by
|
|
3
|
+
// default, and it runs in a QuickJS sandbox with no file system, network or timers:
|
|
4
|
+
// every effect is a tool call, each one sent through the permission gate like a direct
|
|
5
|
+
// call (see classifyToolCall).
|
|
6
|
+
//
|
|
7
|
+
// Loaded here instead of as Pi's `builtin:codemode` (which the launcher turns off) for
|
|
8
|
+
// one option: `models: false`. Pi's copy gives scripts a `models` global that runs
|
|
9
|
+
// classifier and image models with the session's credentials — model calls the gate
|
|
10
|
+
// never sees, to whichever provider has a key, not necessarily the one this session was
|
|
11
|
+
// chosen for. Registering a tool named `codemode` replaces the built-in one, and Pi's
|
|
12
|
+
// MCP support activates it exactly as it would its own.
|
|
13
|
+
import { createCodemodeExtension } from "@earendil-works/pi-coding-agent";
|
|
14
|
+
|
|
15
|
+
export default createCodemodeExtension({ models: false });
|
|
@@ -2,10 +2,9 @@
|
|
|
2
2
|
* The Privateer `/connect` panel — add, toggle, and remove MCP connectors without
|
|
3
3
|
* leaving the terminal.
|
|
4
4
|
*
|
|
5
|
-
* WHY THIS EXISTS:
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* a server. So the terminal was the one surface where adding a connector meant
|
|
5
|
+
* WHY THIS EXISTS: Pi's `/mcp` is a *status and connection* surface — inspect,
|
|
6
|
+
* sign in, reconnect, toggle. It has no catalog and no form for typing in a server, so
|
|
7
|
+
* without this the terminal was the one surface where adding a connector meant
|
|
9
8
|
* hand-editing JSON, while the phone and the desktop both had a real editor.
|
|
10
9
|
*
|
|
11
10
|
* ONE CONFIG, THREE SURFACES: this is deliberately a thin UI over the SAME
|
|
@@ -16,8 +15,8 @@
|
|
|
16
15
|
* routine runs. Nothing about this panel is terminal-specific except the pixels.
|
|
17
16
|
*
|
|
18
17
|
* SECRETS: env values are typed into a masked field and written in PLAINTEXT to the
|
|
19
|
-
* config on this machine — correct, and the same thing the desktop does:
|
|
20
|
-
*
|
|
18
|
+
* config on this machine — correct, and the same thing the desktop does: Pi has to
|
|
19
|
+
* hand the real token to the server process. The masking is shoulder-surfing
|
|
21
20
|
* and screen-share hygiene, not a storage claim. (Over the relay it's different —
|
|
22
21
|
* there the value is sealed to the terminal's key; see mcpControl's header.)
|
|
23
22
|
*/
|
|
@@ -31,6 +30,7 @@ import {
|
|
|
31
30
|
} from "@earendil-works/pi-tui";
|
|
32
31
|
import {
|
|
33
32
|
makeMcpControl,
|
|
33
|
+
migrateMcpProjection,
|
|
34
34
|
type McpDraft,
|
|
35
35
|
type RemoteMcpServer,
|
|
36
36
|
} from "../src/remote/mcpControl.ts";
|
|
@@ -55,7 +55,7 @@ interface TuiLike {
|
|
|
55
55
|
|
|
56
56
|
// What the panel hands back to the command handler when it closes.
|
|
57
57
|
export interface ConnectResult {
|
|
58
|
-
// True when the config changed, so the handler knows to reload
|
|
58
|
+
// True when the config changed, so the handler knows to reload MCP.
|
|
59
59
|
changed: boolean;
|
|
60
60
|
message?: string;
|
|
61
61
|
}
|
|
@@ -183,8 +183,8 @@ function customSteps(existing?: RemoteMcpServer): Step[] {
|
|
|
183
183
|
key: "target",
|
|
184
184
|
prompt: hosted ? "Server URL" : "Launch command, or an https:// URL",
|
|
185
185
|
hint: hosted
|
|
186
|
-
? "e.g. https://mcp.example.com/
|
|
187
|
-
: "e.g. npx -y @modelcontextprotocol/server-memory · or https://mcp.example.com/
|
|
186
|
+
? "e.g. https://mcp.example.com/mcp — a hosted agent can't run a local command."
|
|
187
|
+
: "e.g. npx -y @modelcontextprotocol/server-memory · or https://mcp.example.com/mcp",
|
|
188
188
|
initial: target,
|
|
189
189
|
validate: hosted
|
|
190
190
|
? (v) =>
|
|
@@ -253,13 +253,12 @@ function buildCustomDraft(answers: Record<string, string>): McpDraft {
|
|
|
253
253
|
const hasHeaders = Object.keys(headers).length > 0;
|
|
254
254
|
if (hasHeaders) draft.headers = headers;
|
|
255
255
|
if (token) {
|
|
256
|
-
// A static token:
|
|
257
|
-
//
|
|
256
|
+
// A static token: mcpControl projects it into an Authorization header only when
|
|
257
|
+
// auth is "bearer", so this pairing is not optional.
|
|
258
258
|
draft.auth = "bearer";
|
|
259
259
|
draft.bearerToken = token;
|
|
260
260
|
} else if (hasHeaders) {
|
|
261
|
-
// Custom headers ARE the credential here
|
|
262
|
-
// OAuth once headers are configured, so claiming "oauth" would be a lie.
|
|
261
|
+
// Custom headers ARE the credential here, so claiming "oauth" would be a lie.
|
|
263
262
|
draft.auth = "none";
|
|
264
263
|
} else {
|
|
265
264
|
draft.auth = "oauth";
|
|
@@ -325,7 +324,7 @@ function pendingFromCatalog(e: CatalogEntry): Pending {
|
|
|
325
324
|
},
|
|
326
325
|
note:
|
|
327
326
|
e.needs === "oauth"
|
|
328
|
-
? `Authorize it in a browser on THIS machine: /mcp
|
|
327
|
+
? `Authorize it in a browser on THIS machine: /mcp login ${e.name}`
|
|
329
328
|
: e.localHttp
|
|
330
329
|
// Not a warning — a localHttp connector is correct the moment it saves. But
|
|
331
330
|
// it answers only while its host app is up, so a later failure means "the
|
|
@@ -507,7 +506,7 @@ class ConnectPanel extends Container {
|
|
|
507
506
|
private statusOf(s: RemoteMcpServer): string {
|
|
508
507
|
const t = this.theme;
|
|
509
508
|
if (s.transport === "http" && s.oauth) {
|
|
510
|
-
return t.fg("warning", `⚠ authorize: /mcp
|
|
509
|
+
return t.fg("warning", `⚠ authorize: /mcp login ${s.name}`);
|
|
511
510
|
}
|
|
512
511
|
if (s.envKeys.length > 0) {
|
|
513
512
|
const missing = s.envKeys.filter((k) => !s.secretsSet.includes(k));
|
|
@@ -880,6 +879,9 @@ export default function privateerConnect(pi: {
|
|
|
880
879
|
registerCommand?: (name: string, opts: unknown) => void;
|
|
881
880
|
}): void {
|
|
882
881
|
if (typeof pi.registerCommand !== "function") return;
|
|
882
|
+
// Before Pi's MCP support reads mcp.json at session_start: a file an older release
|
|
883
|
+
// wrote in pi-mcp-adapter's format would otherwise lose every HTTP connector in it.
|
|
884
|
+
migrateMcpProjection();
|
|
883
885
|
|
|
884
886
|
pi.registerCommand("connect", {
|
|
885
887
|
description: "Add, enable, or remove MCP connectors (GitHub, Notion, Linear, …)",
|
|
@@ -896,9 +898,10 @@ export default function privateerConnect(pi: {
|
|
|
896
898
|
);
|
|
897
899
|
|
|
898
900
|
if (!result?.changed) return;
|
|
899
|
-
// Reload so
|
|
900
|
-
//
|
|
901
|
-
//
|
|
901
|
+
// Reload so Pi's MCP support re-reads mcp.json in this session (its own docs:
|
|
902
|
+
// "Run /reload after adding, removing, or changing a server outside the
|
|
903
|
+
// session"). Without it the new connector only appears on the next launch,
|
|
904
|
+
// which reads as "it didn't work".
|
|
902
905
|
try {
|
|
903
906
|
await ctx.reload?.();
|
|
904
907
|
} catch {
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
//
|
|
34
34
|
// The specifiers are built rather than written as literals on purpose: it keeps tsc from
|
|
35
35
|
// pulling that package's own .ts sources into our typecheck, the same trick config/moat.ts
|
|
36
|
-
//
|
|
36
|
+
// used for pi-mcp-adapter.
|
|
37
37
|
import { guardedWebToolDefinitions } from "../src/tools/web.ts";
|
|
38
38
|
import { guardedCryptoToolDefinitions } from "../src/tools/crypto.ts";
|
|
39
39
|
import { readWebToolsConfig, resolveWebMode, type ProviderMetaLike } from "../src/tools/webMode.ts";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "privateer-agent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "Privacy-first terminal coding agent — bring your own model across 20 providers (Anthropic, OpenAI, OpenRouter, Google, local Ollama…). Safe-by-default permissions, MCP, sub-agents, workflows, and verifiable TEE inference. Built on the Pi toolkit.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -71,21 +71,20 @@
|
|
|
71
71
|
"node": ">=22.19.0"
|
|
72
72
|
},
|
|
73
73
|
"dependencies": {
|
|
74
|
-
"@earendil-works/pi-ai": "
|
|
75
|
-
"@earendil-works/pi-coding-agent": "
|
|
76
|
-
"@earendil-works/pi-tui": "
|
|
77
|
-
"@juicesharp/rpiv-ask-user-question": "2.
|
|
78
|
-
"@juicesharp/rpiv-web-tools": "2.
|
|
74
|
+
"@earendil-works/pi-ai": "1.3.0",
|
|
75
|
+
"@earendil-works/pi-coding-agent": "1.3.0",
|
|
76
|
+
"@earendil-works/pi-tui": "1.3.0",
|
|
77
|
+
"@juicesharp/rpiv-ask-user-question": "2.13.8",
|
|
78
|
+
"@juicesharp/rpiv-web-tools": "2.13.8",
|
|
79
79
|
"@noble/ciphers": "2.2.0",
|
|
80
80
|
"@noble/curves": "1.9.7",
|
|
81
81
|
"@noble/hashes": "1.8.0",
|
|
82
82
|
"@phala/dcap-qvl": "0.5.2",
|
|
83
83
|
"@zed-industries/agent-client-protocol": "0.4.5",
|
|
84
84
|
"patch-package": "8.0.1",
|
|
85
|
-
"pi-background-tasks": "2.
|
|
86
|
-
"pi-mcp-adapter": "2.34.0",
|
|
85
|
+
"pi-background-tasks": "2.6.10",
|
|
87
86
|
"pi-privacy": "0.14.1",
|
|
88
|
-
"pi-subagents": "0.
|
|
87
|
+
"pi-subagents": "0.78.0",
|
|
89
88
|
"picomatch": "4.0.5",
|
|
90
89
|
"privateer-speak": "0.8.0",
|
|
91
90
|
"privateer-workflow": "0.1.0",
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
diff --git a/node_modules/@earendil-works/pi-ai/dist/api/simple-options.js b/node_modules/@earendil-works/pi-ai/dist/api/simple-options.js
|
|
2
|
-
index
|
|
2
|
+
index 4e36f35..8a3f8a5 100644
|
|
3
3
|
--- a/node_modules/@earendil-works/pi-ai/dist/api/simple-options.js
|
|
4
4
|
+++ b/node_modules/@earendil-works/pi-ai/dist/api/simple-options.js
|
|
5
|
-
@@ -
|
|
5
|
+
@@ -2,11 +2,39 @@ import { clampThinkingLevel } from "../models.js";
|
|
6
6
|
import { estimateContextTokens } from "../utils/estimate.js";
|
|
7
7
|
const CONTEXT_SAFETY_TOKENS = 4096;
|
|
8
|
-
|
|
8
|
+
const MIN_MAX_TOKENS = 1;
|
|
9
9
|
+// Privateer patch: floor the answer budget at a usable answer, not at one token.
|
|
10
10
|
+//
|
|
11
11
|
+// Stock pi floored this at 1, so the moment the (chars/4) context estimate reached
|
|
@@ -36,11 +36,10 @@ index 216a45f..a1e3214 100644
|
|
|
36
36
|
+// Mirrors src/engine/contextBudget.ts, which is where this is specified and tested.
|
|
37
37
|
export function clampMaxTokensToContext(model, context, maxTokens) {
|
|
38
38
|
if (model.contextWindow <= 0)
|
|
39
|
-
|
|
40
|
-
+ return Math.max(1, maxTokens);
|
|
39
|
+
return Math.max(MIN_MAX_TOKENS, maxTokens);
|
|
41
40
|
const available = model.contextWindow - estimateContextTokens(context).tokens - CONTEXT_SAFETY_TOKENS;
|
|
42
41
|
- return Math.min(maxTokens, Math.max(MIN_MAX_TOKENS, available));
|
|
43
42
|
+ return Math.min(maxTokens, Math.max(MIN_ANSWER_TOKENS, available));
|
|
44
43
|
}
|
|
45
|
-
export function
|
|
46
|
-
const
|
|
44
|
+
export function resolveSamplingParams(model, thinkingLevel, requestParams) {
|
|
45
|
+
const effectiveThinkingLevel = clampThinkingLevel(model, thinkingLevel);
|