@byok-sdk/client 0.1.0 → 0.2.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 +11 -2
- package/dist/adapters/claude/resolve-bin.d.ts +2 -2
- package/dist/adapters/codex/codex-adapter.d.ts +4 -3
- package/dist/adapters/codex/resolve-bin.d.ts +3 -4
- package/dist/adapters/index.d.ts +9 -0
- package/dist/adapters/index.js +1956 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/pi/events.d.ts +10 -37
- package/dist/adapters/pi/permission-mapping.d.ts +4 -20
- package/dist/adapters/pi/resolve-bin.d.ts +16 -22
- package/dist/adapters/pi/rpc-client.d.ts +1 -4
- package/dist/bin/byok-agent.js +39 -29
- package/dist/bin/byok-agent.js.map +1 -1
- package/dist/index.js +39 -29
- package/dist/index.js.map +1 -1
- package/dist/lifecycle/create-service-lifecycle.d.ts +2 -2
- package/package.json +11 -9
|
@@ -1,44 +1,17 @@
|
|
|
1
1
|
import type { AgentEvent } from '@byok-sdk/protocol';
|
|
2
2
|
import type { PiRpcMessage } from './rpc-client';
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
* `Running` forever after pi streamed its final answer; pi process alive,
|
|
6
|
-
* idle, no sockets, only stdio pipes): this mapper used to listen for a pi
|
|
7
|
-
* event type `agent_settled` to know a whole run had finished. That type
|
|
8
|
-
* does not exist anywhere in the real, installed
|
|
9
|
-
* `@earendil-works/pi-coding-agent@0.74.2` package — not in its bundled
|
|
10
|
-
* `docs/rpc.md`, not in `dist/modes/rpc/rpc-types.d.ts`, not in
|
|
11
|
-
* `pi-agent-core`'s own `AgentEvent` union (`dist/types.d.ts`), and not
|
|
12
|
-
* observed even once across live probes against real GLM traffic (raw JSONL
|
|
13
|
-
* frame capture). The real "whole run is done, pi is idle again" signal is
|
|
14
|
-
* `agent_end` — confirmed both by `pi-agent-core`'s own doc comment ("
|
|
15
|
-
* `agent_end` is the last event emitted for a run... The agent becomes idle
|
|
16
|
-
* only after those listeners finish") and empirically: a live run's frame
|
|
17
|
-
* sequence ended `...turn_end (tool call), ..., turn_end (final text),
|
|
18
|
-
* agent_end` with nothing further arriving even after an 8s idle-grace
|
|
19
|
-
* window. Because this switch had no `agent_end` case, it fell to
|
|
20
|
-
* `default: return undefined` — silently dropped — so `task-runner.ts`'s
|
|
21
|
-
* `pump()` loop never saw a `turn_end` `AgentEvent` and blocked forever on
|
|
22
|
-
* the next one. This repo's own fixture (`fake-pi.mjs`) masked the bug for
|
|
23
|
-
* the whole M0/M1 test suite by emitting a fictional `agent_settled` frame
|
|
24
|
-
* of its own alongside the real `agent_end` one — fixed alongside this
|
|
25
|
-
* change (see fake-pi.mjs's doc comment).
|
|
4
|
+
* Map pi 0.84.1 RPC frames into BYOK's runtime-neutral event contract.
|
|
26
5
|
*
|
|
27
|
-
* `
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* task — empirically confirmed: a single-tool-call prompt produced two real
|
|
34
|
-
* `turn_end` frames (one after the tool call, one after the final text) and
|
|
35
|
-
* exactly one `agent_end`.
|
|
6
|
+
* `agent_settled` is the only whole-task completion authority. `agent_end`
|
|
7
|
+
* ends one low-level agent run, but pi may still perform retry/compaction or
|
|
8
|
+
* consume queued work before it emits `agent_settled`. Pi's own `turn_end`
|
|
9
|
+
* is even narrower: a tool-using prompt can emit several of them. Mapping
|
|
10
|
+
* either earlier boundary to BYOK `turn_end` would acknowledge the task
|
|
11
|
+
* before the runtime is actually idle.
|
|
36
12
|
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* extension UI dialogs, which never reach here at all in production since
|
|
40
|
-
* `PiRpcClient` answers them itself before they'd ever be queued as an
|
|
41
|
-
* event; see rpc-client.ts).
|
|
13
|
+
* `message_update` is delta-only in this contract. The mapper forwards text
|
|
14
|
+
* deltas and never reads the removed cumulative `message`/`partial` fields.
|
|
42
15
|
*/
|
|
43
16
|
export declare function mapPiMessageToAgentEvent(msg: PiRpcMessage): AgentEvent | undefined;
|
|
44
17
|
/**
|
|
@@ -48,7 +21,7 @@ export declare function mapPiMessageToAgentEvent(msg: PiRpcMessage): AgentEvent
|
|
|
48
21
|
* (pi-adapter.ts) calls `PiRpcClient.recordUnmappedFrame` for any message
|
|
49
22
|
* type that maps to `undefined` AND isn't in this set — i.e. traffic nobody
|
|
50
23
|
* has ever told this adapter to expect. That distinction is what makes a
|
|
51
|
-
* regression like
|
|
24
|
+
* regression like a changed completion event
|
|
52
25
|
* self-diagnosing: a warning fires the first time the new/renamed settle
|
|
53
26
|
* event shows up, instead of the daemon just quietly hanging. `default`-only
|
|
54
27
|
* unknowns (a type not listed in the switch at all) are equally "not
|
|
@@ -8,7 +8,7 @@ export interface PiPermissionMapping {
|
|
|
8
8
|
}
|
|
9
9
|
/**
|
|
10
10
|
* Map an effective {@link PermissionPolicy} to `pi --mode rpc` CLI args,
|
|
11
|
-
* fail-closed
|
|
11
|
+
* fail-closed against the exact pi 0.84.1 CLI contract:
|
|
12
12
|
*
|
|
13
13
|
* - `auto` / `readonly` are expressible via `--tools` / `--no-tools`.
|
|
14
14
|
* - `confirm` and `plan` are NOT expressible: pi ships no built-in per-call
|
|
@@ -23,24 +23,8 @@ export interface PiPermissionMapping {
|
|
|
23
23
|
* Workspace confinement is NOT a pi flag — the caller spawns pi with
|
|
24
24
|
* `cwd: ctx.workspaceDir`, the daemon-created per-task directory.
|
|
25
25
|
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* pi CLI flag. Confirmed against real pi 0.74.2: `pi --mode rpc
|
|
30
|
-
* --exclude-tools bash` → `Error: Unknown option: --exclude-tools`, exit
|
|
31
|
-
* code 1, before any model call — and it's absent from both `pi --help`'s
|
|
32
|
-
* own listing and the CLI's actual arg parser (`dist/cli/args.js`), which
|
|
33
|
-
* recognizes only `--tools`/`-t`, `--no-tools`/`-nt`, `--no-builtin-tools`/
|
|
34
|
-
* `-nbt` for tool control. This crashed EVERY real pi invocation for any
|
|
35
|
-
* policy with a non-empty `denyTools`, unconditionally — never caught by
|
|
36
|
-
* this repo's own test suite for the exact same reason as the
|
|
37
|
-
* `--session-id` bug (`fake-pi.mjs` never validated argv; fixed alongside
|
|
38
|
-
* this change). pi has no "start from the default set, minus these" flag at
|
|
39
|
-
* all — `--tools` always REPLACES the active set wholesale — so `denyTools`
|
|
40
|
-
* is now resolved to an equivalent allowlist in-process: readonly mode
|
|
41
|
-
* intersects with `READONLY_TOOLS` (as before), any other mode starts from
|
|
42
|
-
* an explicit `allowTools` or pi's own `DEFAULT_ACTIVE_TOOLS`, and
|
|
43
|
-
* `denyTools` is subtracted from that resolved set before it's ever handed
|
|
44
|
-
* to pi as a single `--tools`/`--no-tools` pair of args.
|
|
26
|
+
* - `--tools` is an allowlist and `--exclude-tools` is a denylist. Passing
|
|
27
|
+
* both lets pi remain the authority for its active tool registry instead
|
|
28
|
+
* of duplicating pi's default tool list in this adapter.
|
|
45
29
|
*/
|
|
46
30
|
export declare function mapPermissionPolicyToPiArgs(policy: PermissionPolicy): PiPermissionMapping;
|
|
@@ -5,40 +5,34 @@
|
|
|
5
5
|
* `@mariozechner/pi` — the identifier this task was originally briefed with —
|
|
6
6
|
* is NOT the coding agent. On npm it resolves to an unrelated "CLI tool for
|
|
7
7
|
* managing vLLM deployments on GPU pods" (bin: `pi-pods`). The real coding
|
|
8
|
-
* agent was `@mariozechner/pi-coding-agent`, which is now
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* `latest` requires Node >=22.19 while this SDK's baseline is Node >=20;
|
|
13
|
-
* both versions were empirically confirmed to speak the identical RPC-mode
|
|
14
|
-
* frame shapes this adapter depends on.
|
|
8
|
+
* agent was `@mariozechner/pi-coding-agent`, which is now deprecated in
|
|
9
|
+
* favor of this package. `package.json` carries the exact supported version
|
|
10
|
+
* as a required dependency; pi is a core BYOK capability, not an optional
|
|
11
|
+
* enhancement or an unversioned global executable.
|
|
15
12
|
*/
|
|
16
13
|
export declare const PI_PACKAGE_NAME = "@earendil-works/pi-coding-agent";
|
|
17
14
|
export interface ResolvedBin {
|
|
18
15
|
command: string;
|
|
19
|
-
source: 'package' | '
|
|
16
|
+
source: 'package' | 'env';
|
|
20
17
|
}
|
|
21
18
|
/**
|
|
22
|
-
* Resolve the pi CLI executable
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* pi install still work even when the optionalDependency didn't install.
|
|
19
|
+
* Resolve the pi CLI executable from the required package installed alongside
|
|
20
|
+
* `@byok-sdk/client`. There is intentionally no automatic PATH fallback: a
|
|
21
|
+
* global `pi` would create a second, unversioned authority for this contract.
|
|
26
22
|
*
|
|
27
|
-
* `BYOK_PI_BIN` overrides
|
|
23
|
+
* `BYOK_PI_BIN` explicitly overrides the package when set: `PiAdapterOptions.resolveBin`
|
|
28
24
|
* is the injectable seam for in-process tests, but the `byok-agent` CLI bin
|
|
29
25
|
* only ever constructs `new PiAdapter()` with no options (see `createDaemon`),
|
|
30
26
|
* so an out-of-process substitution (e.g. examples/basic's e2e run swapping
|
|
31
|
-
* in the fake-pi fixture
|
|
27
|
+
* in the fake-pi fixture, or a single-file product injecting its required
|
|
28
|
+
* Node 22.19+ pi sidecar) has no other seam to use.
|
|
32
29
|
*
|
|
33
30
|
* Deliberately does NOT use `createRequire(...).resolve()`: this package is
|
|
34
31
|
* pure ESM with no `require` export condition (`exports["."]` only offers
|
|
35
|
-
* `import`), so CJS-style resolution fails
|
|
36
|
-
* `ERR_PACKAGE_PATH_NOT_EXPORTED
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* package root (identified by a `package.json` whose `name` matches), which
|
|
41
|
-
* is robust to `dist/` layout changes and to whether `./package.json` is
|
|
42
|
-
* ever exported.
|
|
32
|
+
* `import`), so CJS-style resolution fails with
|
|
33
|
+
* `ERR_PACKAGE_PATH_NOT_EXPORTED`. It also does NOT resolve the
|
|
34
|
+
* `./package.json` subpath directly (also not exported); instead it resolves
|
|
35
|
+
* the package's main entry via `import.meta.resolve` and walks upward to the
|
|
36
|
+
* package root identified by its manifest name.
|
|
43
37
|
*/
|
|
44
38
|
export declare function resolvePiBin(): ResolvedBin;
|
|
@@ -58,10 +58,7 @@ export declare class PiRpcClient {
|
|
|
58
58
|
* traffic. Logs once per distinct type (not per occurrence, so a
|
|
59
59
|
* repeating unmapped type can't spam stdout); the running tally is also
|
|
60
60
|
* folded into this client's exit-time error message (`buildExitError`) so
|
|
61
|
-
* a post-mortem on a failed/hung task has it without
|
|
62
|
-
* scraping. This is the exact mechanism that would have turned this
|
|
63
|
-
* task's root-cause hang (`agent_end` arriving with no mapping) into a
|
|
64
|
-
* one-line, immediate warning instead of a silent stall.
|
|
61
|
+
* a post-mortem on a failed/hung task has it without separate log scraping.
|
|
65
62
|
*/
|
|
66
63
|
recordUnmappedFrame(type: string): void;
|
|
67
64
|
/** Best-effort teardown. SIGTERM on POSIX; `taskkill /T /F` on Windows to also reap child processes pi itself spawned (e.g. bash). */
|
package/dist/bin/byok-agent.js
CHANGED
|
@@ -714,7 +714,7 @@ function readPackageJson(dir) {
|
|
|
714
714
|
function resolvePiBin() {
|
|
715
715
|
const override = process.env.BYOK_PI_BIN;
|
|
716
716
|
if (override) {
|
|
717
|
-
return { command: override, source: "
|
|
717
|
+
return { command: override, source: "env" };
|
|
718
718
|
}
|
|
719
719
|
try {
|
|
720
720
|
const mainEntryUrl = import.meta.resolve(PI_PACKAGE_NAME);
|
|
@@ -732,14 +732,19 @@ function resolvePiBin() {
|
|
|
732
732
|
if (parent === dir) break;
|
|
733
733
|
dir = parent;
|
|
734
734
|
}
|
|
735
|
-
} catch {
|
|
735
|
+
} catch (cause) {
|
|
736
|
+
throw new Error(
|
|
737
|
+
`Required ${PI_PACKAGE_NAME} could not be resolved; install @byok-sdk/client dependencies or set BYOK_PI_BIN to a Node 22.19+ pi sidecar`,
|
|
738
|
+
{ cause }
|
|
739
|
+
);
|
|
736
740
|
}
|
|
737
|
-
|
|
741
|
+
throw new Error(
|
|
742
|
+
`Required ${PI_PACKAGE_NAME} does not expose the pi CLI; reinstall the pinned dependency or set BYOK_PI_BIN to a Node 22.19+ pi sidecar`
|
|
743
|
+
);
|
|
738
744
|
}
|
|
739
745
|
|
|
740
746
|
// src/adapters/pi/permission-mapping.ts
|
|
741
747
|
var READONLY_TOOLS = ["read", "grep", "find", "ls"];
|
|
742
|
-
var DEFAULT_ACTIVE_TOOLS = ["read", "bash", "edit", "write"];
|
|
743
748
|
function mapPermissionPolicyToPiArgs(policy) {
|
|
744
749
|
if (policy.network === false) {
|
|
745
750
|
return {
|
|
@@ -758,22 +763,18 @@ function mapPermissionPolicyToPiArgs(policy) {
|
|
|
758
763
|
const denyTools = policy.denyTools ?? [];
|
|
759
764
|
if (policy.mode === "readonly") {
|
|
760
765
|
const base = policy.allowTools ? policy.allowTools.filter((tool) => READONLY_TOOLS.includes(tool)) : [...READONLY_TOOLS];
|
|
761
|
-
|
|
762
|
-
return {
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
const effective = subtractDenied(base, denyTools);
|
|
767
|
-
return { ok: true, args: effective.length === 0 ? ["--no-tools"] : ["--tools", effective.join(",")] };
|
|
766
|
+
if (base.length === 0) return { ok: true, args: ["--no-tools"] };
|
|
767
|
+
return {
|
|
768
|
+
ok: true,
|
|
769
|
+
args: ["--tools", base.join(","), ...denyTools.length > 0 ? ["--exclude-tools", denyTools.join(",")] : []]
|
|
770
|
+
};
|
|
768
771
|
}
|
|
772
|
+
const args = [];
|
|
769
773
|
if (policy.allowTools && policy.allowTools.length > 0) {
|
|
770
|
-
|
|
774
|
+
args.push("--tools", policy.allowTools.join(","));
|
|
771
775
|
}
|
|
772
|
-
|
|
773
|
-
}
|
|
774
|
-
function subtractDenied(tools, denyTools) {
|
|
775
|
-
const denied = new Set(denyTools);
|
|
776
|
-
return tools.filter((tool) => !denied.has(tool));
|
|
776
|
+
if (denyTools.length > 0) args.push("--exclude-tools", denyTools.join(","));
|
|
777
|
+
return { ok: true, args };
|
|
777
778
|
}
|
|
778
779
|
|
|
779
780
|
// src/adapters/pi/events.ts
|
|
@@ -798,7 +799,7 @@ function mapPiMessageToAgentEvent(msg) {
|
|
|
798
799
|
output: { result: msg.result, isError: msg.isError === true }
|
|
799
800
|
};
|
|
800
801
|
}
|
|
801
|
-
case "
|
|
802
|
+
case "agent_settled":
|
|
802
803
|
return { type: "turn_end" };
|
|
803
804
|
/**
|
|
804
805
|
* `artifact` is NOT a real pi RPC message — pi's own `write` tool only
|
|
@@ -834,16 +835,22 @@ function mapPiMessageToAgentEvent(msg) {
|
|
|
834
835
|
// `recordUnmappedFrame`) can tell "known, expected, silently ignored"
|
|
835
836
|
// apart from "genuinely never seen before" (falls to `default` below).
|
|
836
837
|
case "agent_start":
|
|
838
|
+
case "agent_end":
|
|
839
|
+
// one low-level run; `agent_settled` is BYOK completion
|
|
837
840
|
case "turn_start":
|
|
838
841
|
case "turn_end":
|
|
839
|
-
// pi's own per-LLM-turn boundary, not ours
|
|
842
|
+
// pi's own per-LLM-turn boundary, not ours
|
|
840
843
|
case "message_start":
|
|
841
844
|
case "message_end":
|
|
845
|
+
case "bash_execution_update":
|
|
842
846
|
case "tool_execution_update":
|
|
843
847
|
case "queue_update":
|
|
844
848
|
case "compaction_start":
|
|
845
849
|
case "compaction_end":
|
|
846
850
|
case "auto_retry_start":
|
|
851
|
+
case "summarization_retry_scheduled":
|
|
852
|
+
case "summarization_retry_attempt_start":
|
|
853
|
+
case "summarization_retry_finished":
|
|
847
854
|
case "session_info_changed":
|
|
848
855
|
case "thinking_level_changed":
|
|
849
856
|
return void 0;
|
|
@@ -853,15 +860,20 @@ function mapPiMessageToAgentEvent(msg) {
|
|
|
853
860
|
}
|
|
854
861
|
var ROUTINE_PI_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
855
862
|
"agent_start",
|
|
863
|
+
"agent_end",
|
|
856
864
|
"turn_start",
|
|
857
865
|
"turn_end",
|
|
858
866
|
"message_start",
|
|
859
867
|
"message_end",
|
|
868
|
+
"bash_execution_update",
|
|
860
869
|
"tool_execution_update",
|
|
861
870
|
"queue_update",
|
|
862
871
|
"compaction_start",
|
|
863
872
|
"compaction_end",
|
|
864
873
|
"auto_retry_start",
|
|
874
|
+
"summarization_retry_scheduled",
|
|
875
|
+
"summarization_retry_attempt_start",
|
|
876
|
+
"summarization_retry_finished",
|
|
865
877
|
"session_info_changed",
|
|
866
878
|
"thinking_level_changed"
|
|
867
879
|
]);
|
|
@@ -995,10 +1007,7 @@ var PiRpcClient = class {
|
|
|
995
1007
|
* traffic. Logs once per distinct type (not per occurrence, so a
|
|
996
1008
|
* repeating unmapped type can't spam stdout); the running tally is also
|
|
997
1009
|
* folded into this client's exit-time error message (`buildExitError`) so
|
|
998
|
-
* a post-mortem on a failed/hung task has it without
|
|
999
|
-
* scraping. This is the exact mechanism that would have turned this
|
|
1000
|
-
* task's root-cause hang (`agent_end` arriving with no mapping) into a
|
|
1001
|
-
* one-line, immediate warning instead of a silent stall.
|
|
1010
|
+
* a post-mortem on a failed/hung task has it without separate log scraping.
|
|
1002
1011
|
*/
|
|
1003
1012
|
recordUnmappedFrame(type) {
|
|
1004
1013
|
const next = (this.unmappedFrameCounts.get(type) ?? 0) + 1;
|
|
@@ -1139,8 +1148,8 @@ var PiAdapter = class {
|
|
|
1139
1148
|
options;
|
|
1140
1149
|
id = "pi";
|
|
1141
1150
|
async detect() {
|
|
1142
|
-
const bin = this.resolveBin();
|
|
1143
1151
|
try {
|
|
1152
|
+
const bin = this.resolveBin();
|
|
1144
1153
|
const { stdout, stderr } = await execFileAsync(bin.command, ["--version"], { timeout: DETECT_TIMEOUT_MS });
|
|
1145
1154
|
const version = stdout.trim() || stderr.trim();
|
|
1146
1155
|
const authPresent = KNOWN_PROVIDER_ENV_VARS.some((name) => process.env[name] !== void 0);
|
|
@@ -1317,7 +1326,7 @@ function mapPermissionPolicyToClaudeArgs(policy) {
|
|
|
1317
1326
|
}
|
|
1318
1327
|
if (policy.mode === "readonly") {
|
|
1319
1328
|
const base = policy.allowTools ? policy.allowTools.filter((tool) => READONLY_TOOLS2.includes(tool)) : [...READONLY_TOOLS2];
|
|
1320
|
-
const effective =
|
|
1329
|
+
const effective = subtractDenied(base, denyTools);
|
|
1321
1330
|
return { ok: true, args: ["--permission-mode", "default", "--tools", effective.join(",")] };
|
|
1322
1331
|
}
|
|
1323
1332
|
if (denyTools.length > 0) {
|
|
@@ -1334,7 +1343,7 @@ function mapPermissionPolicyToClaudeArgs(policy) {
|
|
|
1334
1343
|
}
|
|
1335
1344
|
return { ok: true, args };
|
|
1336
1345
|
}
|
|
1337
|
-
function
|
|
1346
|
+
function subtractDenied(tools, denyTools) {
|
|
1338
1347
|
const denied = new Set(denyTools);
|
|
1339
1348
|
return tools.filter((tool) => !denied.has(tool));
|
|
1340
1349
|
}
|
|
@@ -2274,9 +2283,10 @@ var CodexAdapter = class {
|
|
|
2274
2283
|
* Two independently-verified channel gotchas apply here, the "pi lesson"
|
|
2275
2284
|
* yet again:
|
|
2276
2285
|
* - `codex login status`'s human-readable "Logged in using ChatGPT"
|
|
2277
|
-
* message prints on STDERR, not stdout
|
|
2278
|
-
*
|
|
2279
|
-
*
|
|
2286
|
+
* message prints on STDERR, not stdout — both streams are checked
|
|
2287
|
+
* here for exactly that reason. pi's `--version` is the same class of
|
|
2288
|
+
* hazard from the other direction: its channel has moved between pi
|
|
2289
|
+
* releases (see ../pi/pi-adapter.ts), so neither stream is assumed.
|
|
2280
2290
|
* - The NOT-logged-in message/exit-code shape was deliberately never
|
|
2281
2291
|
* empirically tested: this machine has a real, live ChatGPT login, and
|
|
2282
2292
|
* running `codex logout` to observe the negative case would have
|