@byok-sdk/client 0.1.1 → 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 +4 -4
- package/dist/adapters/claude/resolve-bin.d.ts +2 -2
- package/dist/adapters/codex/codex-adapter.d.ts +4 -3
- package/dist/adapters/index.js +79 -44
- package/dist/adapters/index.js.map +1 -1
- 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 +18 -14
- package/dist/adapters/pi/rpc-client.d.ts +1 -4
- package/dist/bin/byok-agent.js +155 -119
- package/dist/bin/byok-agent.js.map +1 -1
- package/dist/index.js +135 -100
- package/dist/index.js.map +1 -1
- package/dist/lifecycle/create-service-lifecycle.d.ts +2 -2
- package/package.json +6 -5
package/README.md
CHANGED
|
@@ -7,8 +7,9 @@ and exposes authenticated local diagnostics/control commands.
|
|
|
7
7
|
The package installs `byok-agent` and `byok-approval-mcp` binaries. Provider
|
|
8
8
|
credentials are not read by the dispatch plane; `@byok-sdk/keys` is separate.
|
|
9
9
|
|
|
10
|
-
Pi
|
|
11
|
-
|
|
10
|
+
Pi is a required exact npm dependency and runs as an external Node subprocess;
|
|
11
|
+
authenticate it with your own provider credentials. Claude Code and Codex
|
|
12
|
+
remain user-installed runtimes. The SDK never reads or packages provider
|
|
12
13
|
credentials. Hosts that only need runtime detection/composition can import the
|
|
13
14
|
transport-free adapter surface:
|
|
14
15
|
|
|
@@ -16,5 +17,4 @@ transport-free adapter surface:
|
|
|
16
17
|
import { PiAdapter, ClaudeAdapter, CodexAdapter } from '@byok-sdk/client/adapters';
|
|
17
18
|
```
|
|
18
19
|
|
|
19
|
-
MIT licensed. Node.js
|
|
20
|
-
with `node:sqlite` support.
|
|
20
|
+
MIT licensed. Node.js 22.19.0 or newer.
|
|
@@ -6,7 +6,7 @@ export interface ResolvedBin {
|
|
|
6
6
|
* Resolve the `claude` (Claude Code) CLI executable.
|
|
7
7
|
*
|
|
8
8
|
* Unlike pi (`../pi/resolve-bin.ts`), this package does NOT bundle a
|
|
9
|
-
* matched `claude` build as
|
|
9
|
+
* matched `claude` build as a dependency. Claude Code is the end
|
|
10
10
|
* user's own globally-installed, individually-authenticated CLI (`claude
|
|
11
11
|
* auth login`, tied to their Anthropic/claude.ai account) — there is
|
|
12
12
|
* nothing useful to vendor: a bundled copy could never carry the user's own
|
|
@@ -22,6 +22,6 @@ export interface ResolvedBin {
|
|
|
22
22
|
* `fake-claude.mjs` fixture ahead of a real claude install, exactly as pi's
|
|
23
23
|
* own override does), otherwise this falls back to the literal command name
|
|
24
24
|
* `claude`, resolved via the child process's own PATH lookup — there is no
|
|
25
|
-
*
|
|
25
|
+
* package-resolution tier in between.
|
|
26
26
|
*/
|
|
27
27
|
export declare function resolveClaudeBin(): ResolvedBin;
|
|
@@ -66,9 +66,10 @@ export declare class CodexAdapter implements RuntimeAdapter {
|
|
|
66
66
|
* Two independently-verified channel gotchas apply here, the "pi lesson"
|
|
67
67
|
* yet again:
|
|
68
68
|
* - `codex login status`'s human-readable "Logged in using ChatGPT"
|
|
69
|
-
* message prints on STDERR, not stdout
|
|
70
|
-
*
|
|
71
|
-
*
|
|
69
|
+
* message prints on STDERR, not stdout — both streams are checked
|
|
70
|
+
* here for exactly that reason. pi's `--version` is the same class of
|
|
71
|
+
* hazard from the other direction: its channel has moved between pi
|
|
72
|
+
* releases (see ../pi/pi-adapter.ts), so neither stream is assumed.
|
|
72
73
|
* - The NOT-logged-in message/exit-code shape was deliberately never
|
|
73
74
|
* empirically tested: this machine has a real, live ChatGPT login, and
|
|
74
75
|
* running `codex logout` to observe the negative case would have
|
package/dist/adapters/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { execFile, spawn, spawnSync } from 'child_process';
|
|
2
2
|
import { promisify } from 'util';
|
|
3
|
-
import { promises, realpathSync } from 'fs';
|
|
4
|
-
import
|
|
5
|
-
import path2 from 'path';
|
|
3
|
+
import { promises, existsSync, readFileSync, realpathSync } from 'fs';
|
|
4
|
+
import path3 from 'path';
|
|
6
5
|
import { fileURLToPath } from 'url';
|
|
6
|
+
import os from 'os';
|
|
7
7
|
import 'readline';
|
|
8
8
|
|
|
9
9
|
// src/adapters/pi/pi-adapter.ts
|
|
@@ -24,20 +24,50 @@ var SteerUnsupportedError = class extends Error {
|
|
|
24
24
|
this.runtimeId = runtimeId;
|
|
25
25
|
}
|
|
26
26
|
};
|
|
27
|
-
|
|
28
|
-
// src/adapters/pi/resolve-bin.ts
|
|
29
27
|
var PI_PACKAGE_NAME = "@earendil-works/pi-coding-agent";
|
|
28
|
+
function readPackageJson(dir) {
|
|
29
|
+
const candidate = path3.join(dir, "package.json");
|
|
30
|
+
if (!existsSync(candidate)) return void 0;
|
|
31
|
+
try {
|
|
32
|
+
return JSON.parse(readFileSync(candidate, "utf8"));
|
|
33
|
+
} catch {
|
|
34
|
+
return void 0;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
30
37
|
function resolvePiBin() {
|
|
31
38
|
const override = process.env.BYOK_PI_BIN;
|
|
32
39
|
if (override) {
|
|
33
|
-
return { command: override, source: "
|
|
40
|
+
return { command: override, source: "env" };
|
|
34
41
|
}
|
|
35
|
-
|
|
42
|
+
try {
|
|
43
|
+
const mainEntryUrl = import.meta.resolve(PI_PACKAGE_NAME);
|
|
44
|
+
let dir = path3.dirname(fileURLToPath(mainEntryUrl));
|
|
45
|
+
for (let depth = 0; depth < 6; depth++) {
|
|
46
|
+
const pkg = readPackageJson(dir);
|
|
47
|
+
if (pkg?.name === PI_PACKAGE_NAME) {
|
|
48
|
+
const binRel = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.pi;
|
|
49
|
+
if (binRel) {
|
|
50
|
+
return { command: path3.join(dir, binRel), source: "package" };
|
|
51
|
+
}
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
const parent = path3.dirname(dir);
|
|
55
|
+
if (parent === dir) break;
|
|
56
|
+
dir = parent;
|
|
57
|
+
}
|
|
58
|
+
} catch (cause) {
|
|
59
|
+
throw new Error(
|
|
60
|
+
`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`,
|
|
61
|
+
{ cause }
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
throw new Error(
|
|
65
|
+
`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`
|
|
66
|
+
);
|
|
36
67
|
}
|
|
37
68
|
|
|
38
69
|
// src/adapters/pi/permission-mapping.ts
|
|
39
70
|
var READONLY_TOOLS = ["read", "grep", "find", "ls"];
|
|
40
|
-
var DEFAULT_ACTIVE_TOOLS = ["read", "bash", "edit", "write"];
|
|
41
71
|
function mapPermissionPolicyToPiArgs(policy) {
|
|
42
72
|
if (policy.network === false) {
|
|
43
73
|
return {
|
|
@@ -56,22 +86,18 @@ function mapPermissionPolicyToPiArgs(policy) {
|
|
|
56
86
|
const denyTools = policy.denyTools ?? [];
|
|
57
87
|
if (policy.mode === "readonly") {
|
|
58
88
|
const base = policy.allowTools ? policy.allowTools.filter((tool) => READONLY_TOOLS.includes(tool)) : [...READONLY_TOOLS];
|
|
59
|
-
|
|
60
|
-
return {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const effective = subtractDenied(base, denyTools);
|
|
65
|
-
return { ok: true, args: effective.length === 0 ? ["--no-tools"] : ["--tools", effective.join(",")] };
|
|
89
|
+
if (base.length === 0) return { ok: true, args: ["--no-tools"] };
|
|
90
|
+
return {
|
|
91
|
+
ok: true,
|
|
92
|
+
args: ["--tools", base.join(","), ...denyTools.length > 0 ? ["--exclude-tools", denyTools.join(",")] : []]
|
|
93
|
+
};
|
|
66
94
|
}
|
|
95
|
+
const args = [];
|
|
67
96
|
if (policy.allowTools && policy.allowTools.length > 0) {
|
|
68
|
-
|
|
97
|
+
args.push("--tools", policy.allowTools.join(","));
|
|
69
98
|
}
|
|
70
|
-
|
|
71
|
-
}
|
|
72
|
-
function subtractDenied(tools, denyTools) {
|
|
73
|
-
const denied = new Set(denyTools);
|
|
74
|
-
return tools.filter((tool) => !denied.has(tool));
|
|
99
|
+
if (denyTools.length > 0) args.push("--exclude-tools", denyTools.join(","));
|
|
100
|
+
return { ok: true, args };
|
|
75
101
|
}
|
|
76
102
|
|
|
77
103
|
// src/adapters/pi/events.ts
|
|
@@ -96,7 +122,7 @@ function mapPiMessageToAgentEvent(msg) {
|
|
|
96
122
|
output: { result: msg.result, isError: msg.isError === true }
|
|
97
123
|
};
|
|
98
124
|
}
|
|
99
|
-
case "
|
|
125
|
+
case "agent_settled":
|
|
100
126
|
return { type: "turn_end" };
|
|
101
127
|
/**
|
|
102
128
|
* `artifact` is NOT a real pi RPC message — pi's own `write` tool only
|
|
@@ -132,16 +158,22 @@ function mapPiMessageToAgentEvent(msg) {
|
|
|
132
158
|
// `recordUnmappedFrame`) can tell "known, expected, silently ignored"
|
|
133
159
|
// apart from "genuinely never seen before" (falls to `default` below).
|
|
134
160
|
case "agent_start":
|
|
161
|
+
case "agent_end":
|
|
162
|
+
// one low-level run; `agent_settled` is BYOK completion
|
|
135
163
|
case "turn_start":
|
|
136
164
|
case "turn_end":
|
|
137
|
-
// pi's own per-LLM-turn boundary, not ours
|
|
165
|
+
// pi's own per-LLM-turn boundary, not ours
|
|
138
166
|
case "message_start":
|
|
139
167
|
case "message_end":
|
|
168
|
+
case "bash_execution_update":
|
|
140
169
|
case "tool_execution_update":
|
|
141
170
|
case "queue_update":
|
|
142
171
|
case "compaction_start":
|
|
143
172
|
case "compaction_end":
|
|
144
173
|
case "auto_retry_start":
|
|
174
|
+
case "summarization_retry_scheduled":
|
|
175
|
+
case "summarization_retry_attempt_start":
|
|
176
|
+
case "summarization_retry_finished":
|
|
145
177
|
case "session_info_changed":
|
|
146
178
|
case "thinking_level_changed":
|
|
147
179
|
return void 0;
|
|
@@ -151,15 +183,20 @@ function mapPiMessageToAgentEvent(msg) {
|
|
|
151
183
|
}
|
|
152
184
|
var ROUTINE_PI_EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
153
185
|
"agent_start",
|
|
186
|
+
"agent_end",
|
|
154
187
|
"turn_start",
|
|
155
188
|
"turn_end",
|
|
156
189
|
"message_start",
|
|
157
190
|
"message_end",
|
|
191
|
+
"bash_execution_update",
|
|
158
192
|
"tool_execution_update",
|
|
159
193
|
"queue_update",
|
|
160
194
|
"compaction_start",
|
|
161
195
|
"compaction_end",
|
|
162
196
|
"auto_retry_start",
|
|
197
|
+
"summarization_retry_scheduled",
|
|
198
|
+
"summarization_retry_attempt_start",
|
|
199
|
+
"summarization_retry_finished",
|
|
163
200
|
"session_info_changed",
|
|
164
201
|
"thinking_level_changed"
|
|
165
202
|
]);
|
|
@@ -293,10 +330,7 @@ var PiRpcClient = class {
|
|
|
293
330
|
* traffic. Logs once per distinct type (not per occurrence, so a
|
|
294
331
|
* repeating unmapped type can't spam stdout); the running tally is also
|
|
295
332
|
* folded into this client's exit-time error message (`buildExitError`) so
|
|
296
|
-
* a post-mortem on a failed/hung task has it without
|
|
297
|
-
* scraping. This is the exact mechanism that would have turned this
|
|
298
|
-
* task's root-cause hang (`agent_end` arriving with no mapping) into a
|
|
299
|
-
* one-line, immediate warning instead of a silent stall.
|
|
333
|
+
* a post-mortem on a failed/hung task has it without separate log scraping.
|
|
300
334
|
*/
|
|
301
335
|
recordUnmappedFrame(type) {
|
|
302
336
|
const next = (this.unmappedFrameCounts.get(type) ?? 0) + 1;
|
|
@@ -437,8 +471,8 @@ var PiAdapter = class {
|
|
|
437
471
|
options;
|
|
438
472
|
id = "pi";
|
|
439
473
|
async detect() {
|
|
440
|
-
const bin = this.resolveBin();
|
|
441
474
|
try {
|
|
475
|
+
const bin = this.resolveBin();
|
|
442
476
|
const { stdout, stderr } = await execFileAsync(bin.command, ["--version"], { timeout: DETECT_TIMEOUT_MS });
|
|
443
477
|
const version = stdout.trim() || stderr.trim();
|
|
444
478
|
const authPresent = KNOWN_PROVIDER_ENV_VARS.some((name) => process.env[name] !== void 0);
|
|
@@ -584,7 +618,7 @@ function resolveApprovalMcpBin() {
|
|
|
584
618
|
if (override) {
|
|
585
619
|
return { command: override, args: [], source: "env" };
|
|
586
620
|
}
|
|
587
|
-
const distBin =
|
|
621
|
+
const distBin = path3.join(path3.dirname(fileURLToPath(import.meta.url)), "bin", "byok-approval-mcp.js");
|
|
588
622
|
return { command: process.execPath, args: [distBin], source: "dist" };
|
|
589
623
|
}
|
|
590
624
|
|
|
@@ -615,7 +649,7 @@ function mapPermissionPolicyToClaudeArgs(policy) {
|
|
|
615
649
|
}
|
|
616
650
|
if (policy.mode === "readonly") {
|
|
617
651
|
const base = policy.allowTools ? policy.allowTools.filter((tool) => READONLY_TOOLS2.includes(tool)) : [...READONLY_TOOLS2];
|
|
618
|
-
const effective =
|
|
652
|
+
const effective = subtractDenied(base, denyTools);
|
|
619
653
|
return { ok: true, args: ["--permission-mode", "default", "--tools", effective.join(",")] };
|
|
620
654
|
}
|
|
621
655
|
if (denyTools.length > 0) {
|
|
@@ -632,7 +666,7 @@ function mapPermissionPolicyToClaudeArgs(policy) {
|
|
|
632
666
|
}
|
|
633
667
|
return { ok: true, args };
|
|
634
668
|
}
|
|
635
|
-
function
|
|
669
|
+
function subtractDenied(tools, denyTools) {
|
|
636
670
|
const denied = new Set(denyTools);
|
|
637
671
|
return tools.filter((tool) => !denied.has(tool));
|
|
638
672
|
}
|
|
@@ -664,7 +698,7 @@ var EXTENSION_CONTENT_TYPES = {
|
|
|
664
698
|
".yml": "application/yaml"
|
|
665
699
|
};
|
|
666
700
|
function guessContentType(filePath) {
|
|
667
|
-
const ext =
|
|
701
|
+
const ext = path3.extname(filePath).toLowerCase();
|
|
668
702
|
return EXTENSION_CONTENT_TYPES[ext] ?? "application/octet-stream";
|
|
669
703
|
}
|
|
670
704
|
function mapAssistant(msg, correlation) {
|
|
@@ -736,11 +770,11 @@ function tryBuildArtifactEvent(msg, workspaceDir) {
|
|
|
736
770
|
const filePath = toolUseResult && typeof toolUseResult.filePath === "string" ? toolUseResult.filePath : void 0;
|
|
737
771
|
if (!filePath) return void 0;
|
|
738
772
|
const realWorkspaceDir = tryRealpath(workspaceDir) ?? workspaceDir;
|
|
739
|
-
const fileDir =
|
|
773
|
+
const fileDir = path3.dirname(filePath);
|
|
740
774
|
const realFileDir = tryRealpath(fileDir) ?? fileDir;
|
|
741
|
-
const realFilePath =
|
|
742
|
-
const relative =
|
|
743
|
-
if (relative === "" || relative.startsWith("..") ||
|
|
775
|
+
const realFilePath = path3.join(realFileDir, path3.basename(filePath));
|
|
776
|
+
const relative = path3.relative(realWorkspaceDir, realFilePath);
|
|
777
|
+
if (relative === "" || relative.startsWith("..") || path3.isAbsolute(relative)) {
|
|
744
778
|
return void 0;
|
|
745
779
|
}
|
|
746
780
|
return { type: "artifact", name: relative, contentType: guessContentType(filePath) };
|
|
@@ -1039,10 +1073,10 @@ var ClaudeAdapter = class {
|
|
|
1039
1073
|
);
|
|
1040
1074
|
}
|
|
1041
1075
|
const approvalMcpBin = (this.options.resolveApprovalMcpBin ?? resolveApprovalMcpBin)();
|
|
1042
|
-
approvalMcpConfigDir = await promises.mkdtemp(
|
|
1076
|
+
approvalMcpConfigDir = await promises.mkdtemp(path3.join(os.tmpdir(), "byok-approval-mcp-"));
|
|
1043
1077
|
await promises.chmod(approvalMcpConfigDir, 448).catch(() => {
|
|
1044
1078
|
});
|
|
1045
|
-
const mcpConfigPath =
|
|
1079
|
+
const mcpConfigPath = path3.join(approvalMcpConfigDir, "mcp-config.json");
|
|
1046
1080
|
const mcpConfig = {
|
|
1047
1081
|
mcpServers: {
|
|
1048
1082
|
[APPROVAL_MCP_SERVER_NAME]: {
|
|
@@ -1389,8 +1423,8 @@ function extractArtifactEvents(changes, workspaceDir) {
|
|
|
1389
1423
|
const absolutePath = typeof change.path === "string" ? change.path : void 0;
|
|
1390
1424
|
const kind = typeof change.kind === "string" ? change.kind : void 0;
|
|
1391
1425
|
if (!absolutePath || kind === "delete") continue;
|
|
1392
|
-
const relative =
|
|
1393
|
-
if (relative.length === 0 || relative.startsWith("..") ||
|
|
1426
|
+
const relative = path3.relative(workspaceDir, absolutePath);
|
|
1427
|
+
if (relative.length === 0 || relative.startsWith("..") || path3.isAbsolute(relative)) continue;
|
|
1394
1428
|
events.push({ type: "artifact", name: relative, contentType: guessContentType2(relative) });
|
|
1395
1429
|
}
|
|
1396
1430
|
return events;
|
|
@@ -1411,7 +1445,7 @@ var CONTENT_TYPE_BY_EXTENSION = {
|
|
|
1411
1445
|
".csv": "text/csv"
|
|
1412
1446
|
};
|
|
1413
1447
|
function guessContentType2(relativePath) {
|
|
1414
|
-
return CONTENT_TYPE_BY_EXTENSION[
|
|
1448
|
+
return CONTENT_TYPE_BY_EXTENSION[path3.extname(relativePath).toLowerCase()] ?? "application/octet-stream";
|
|
1415
1449
|
}
|
|
1416
1450
|
function extractErrorMessage(rawError) {
|
|
1417
1451
|
if (typeof rawError === "string") return rawError;
|
|
@@ -1572,9 +1606,10 @@ var CodexAdapter = class {
|
|
|
1572
1606
|
* Two independently-verified channel gotchas apply here, the "pi lesson"
|
|
1573
1607
|
* yet again:
|
|
1574
1608
|
* - `codex login status`'s human-readable "Logged in using ChatGPT"
|
|
1575
|
-
* message prints on STDERR, not stdout
|
|
1576
|
-
*
|
|
1577
|
-
*
|
|
1609
|
+
* message prints on STDERR, not stdout — both streams are checked
|
|
1610
|
+
* here for exactly that reason. pi's `--version` is the same class of
|
|
1611
|
+
* hazard from the other direction: its channel has moved between pi
|
|
1612
|
+
* releases (see ../pi/pi-adapter.ts), so neither stream is assumed.
|
|
1578
1613
|
* - The NOT-logged-in message/exit-code shape was deliberately never
|
|
1579
1614
|
* empirically tested: this machine has a real, live ChatGPT login, and
|
|
1580
1615
|
* running `codex logout` to observe the negative case would have
|