@code-yeongyu/senpi 2026.9.15-2 → 2026.9.16

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.
Files changed (137) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/core/extensions/kernel-tools-context.d.ts +13 -0
  3. package/dist/core/extensions/kernel-tools-context.d.ts.map +1 -0
  4. package/dist/core/extensions/kernel-tools-context.js +3 -0
  5. package/dist/core/extensions/kernel-tools-context.js.map +1 -0
  6. package/dist/core/extensions/runner.d.ts +19 -0
  7. package/dist/core/extensions/runner.d.ts.map +1 -1
  8. package/dist/core/extensions/runner.js +103 -1
  9. package/dist/core/extensions/runner.js.map +1 -1
  10. package/dist/core/extensions/types.d.ts +21 -0
  11. package/dist/core/extensions/types.d.ts.map +1 -1
  12. package/dist/core/extensions/types.js.map +1 -1
  13. package/dist/core/settings-manager.d.ts +23 -0
  14. package/dist/core/settings-manager.d.ts.map +1 -1
  15. package/dist/core/settings-manager.js +41 -0
  16. package/dist/core/settings-manager.js.map +1 -1
  17. package/dist/core/tools/index.d.ts +1 -1
  18. package/dist/core/tools/index.d.ts.map +1 -1
  19. package/dist/core/tools/index.js +4 -3
  20. package/dist/core/tools/index.js.map +1 -1
  21. package/dist/core/tools/read.d.ts +3 -1
  22. package/dist/core/tools/read.d.ts.map +1 -1
  23. package/dist/core/tools/read.js +16 -2
  24. package/dist/core/tools/read.js.map +1 -1
  25. package/dist/index.d.ts +1 -0
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +1 -0
  28. package/dist/index.js.map +1 -1
  29. package/docs/extensions.md +3 -0
  30. package/docs/settings.md +12 -0
  31. package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +21 -0
  32. package/node_modules/@code-yeongyu/senpi-codemode/README.md +24 -0
  33. package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
  34. package/node_modules/@code-yeongyu/senpi-codemode/src/bridge/http-server.ts +7 -1
  35. package/node_modules/@code-yeongyu/senpi-codemode/src/bridge/kernel-tools-protocol.ts +85 -0
  36. package/node_modules/@code-yeongyu/senpi-codemode/src/bridge/protocol.ts +6 -0
  37. package/node_modules/@code-yeongyu/senpi-codemode/src/bridges/agent-bridge.ts +30 -11
  38. package/node_modules/@code-yeongyu/senpi-codemode/src/extension/session-manager.ts +11 -0
  39. package/node_modules/@code-yeongyu/senpi-codemode/src/index.ts +10 -0
  40. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/jl/prelude.jl +46 -0
  41. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/jl/runner.jl +1 -1
  42. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/context-manager.ts +29 -0
  43. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-contract.ts +12 -0
  44. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-context.d.ts +13 -0
  45. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-context.js +7 -0
  46. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-define.d.ts +10 -0
  47. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-define.js +12 -0
  48. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-errors.js +22 -0
  49. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-errors.ts +26 -0
  50. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-host.ts +134 -0
  51. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-naming.js +28 -0
  52. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-parse.d.ts +5 -0
  53. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-parse.js +255 -0
  54. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-pump.js +105 -0
  55. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-registry.d.ts +27 -0
  56. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-registry.js +127 -0
  57. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-schema.js +69 -0
  58. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/kernel-tools-types.ts +42 -0
  59. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/prelude.ts +2 -0
  60. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-core.js +26 -3
  61. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-runtime.js +20 -8
  62. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-slot.ts +6 -1
  63. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/worker-startup.ts +9 -7
  64. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/js/workpool.js +37 -0
  65. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/kernel-tools-unavailable.ts +5 -0
  66. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/py/kernel.ts +13 -0
  67. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/py/prelude.py +57 -3
  68. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/rb/prelude.rb +13 -2
  69. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/rb/workpool.rb +38 -0
  70. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/shared/subprocess-kernel.ts +13 -0
  71. package/node_modules/@code-yeongyu/senpi-codemode/src/kernels/shared/subprocess-queue.ts +2 -0
  72. package/node_modules/@code-yeongyu/senpi-codemode/src/prompt/eval-prompt-template.ts +4 -2
  73. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/cell-handler.ts +8 -1
  74. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/run-eval-cell.ts +49 -30
  75. package/node_modules/@code-yeongyu/senpi-codemode/src/tool/types.ts +2 -0
  76. package/node_modules/@earendil-works/pi-agent-core/dist/empty-assistant-recovery.d.ts.map +1 -1
  77. package/node_modules/@earendil-works/pi-agent-core/dist/empty-assistant-recovery.js +51 -12
  78. package/node_modules/@earendil-works/pi-agent-core/dist/empty-assistant-recovery.js.map +1 -1
  79. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/read.d.ts +3 -0
  80. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/read.d.ts.map +1 -1
  81. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/read.js +18 -3
  82. package/node_modules/@earendil-works/pi-agent-core/dist/harness/tools/read.js.map +1 -1
  83. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/brace-scanner.d.ts +4 -0
  84. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/brace-scanner.d.ts.map +1 -0
  85. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/brace-scanner.js +268 -0
  86. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/brace-scanner.js.map +1 -0
  87. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/header-protection.d.ts +18 -0
  88. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/header-protection.d.ts.map +1 -0
  89. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/header-protection.js +69 -0
  90. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/header-protection.js.map +1 -0
  91. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/index.d.ts +27 -0
  92. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/index.d.ts.map +1 -0
  93. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/index.js +118 -0
  94. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/index.js.map +1 -0
  95. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/lexical-context.d.ts +21 -0
  96. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/lexical-context.d.ts.map +1 -0
  97. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/lexical-context.js +24 -0
  98. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/lexical-context.js.map +1 -0
  99. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/lexical-spans.d.ts +12 -0
  100. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/lexical-spans.d.ts.map +1 -0
  101. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/lexical-spans.js +92 -0
  102. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/lexical-spans.js.map +1 -0
  103. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/types.d.ts +47 -0
  104. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/types.d.ts.map +1 -0
  105. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/types.js +8 -0
  106. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/read-folders/types.js.map +1 -0
  107. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/segmented-read-view.d.ts +52 -0
  108. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/segmented-read-view.d.ts.map +1 -0
  109. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/segmented-read-view.js +155 -0
  110. package/node_modules/@earendil-works/pi-agent-core/dist/harness/utils/segmented-read-view.js.map +1 -0
  111. package/node_modules/@earendil-works/pi-agent-core/dist/index.d.ts +2 -0
  112. package/node_modules/@earendil-works/pi-agent-core/dist/index.d.ts.map +1 -1
  113. package/node_modules/@earendil-works/pi-agent-core/dist/index.js +2 -0
  114. package/node_modules/@earendil-works/pi-agent-core/dist/index.js.map +1 -1
  115. package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
  116. package/node_modules/@earendil-works/pi-ai/dist/index.d.ts +1 -0
  117. package/node_modules/@earendil-works/pi-ai/dist/index.d.ts.map +1 -1
  118. package/node_modules/@earendil-works/pi-ai/dist/index.js +1 -0
  119. package/node_modules/@earendil-works/pi-ai/dist/index.js.map +1 -1
  120. package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
  121. package/node_modules/@earendil-works/pi-ai/dist/providers/data/alibaba-token-plan.json +1 -1
  122. package/node_modules/@earendil-works/pi-ai/dist/providers/data/amazon-bedrock.json +1 -1
  123. package/node_modules/@earendil-works/pi-ai/dist/providers/data/fireworks.json +1 -1
  124. package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
  125. package/node_modules/@earendil-works/pi-ai/dist/providers/data/qwen-token-plan.json +1 -1
  126. package/node_modules/@earendil-works/pi-ai/dist/utils/empty-response-errors.d.ts +15 -0
  127. package/node_modules/@earendil-works/pi-ai/dist/utils/empty-response-errors.d.ts.map +1 -0
  128. package/node_modules/@earendil-works/pi-ai/dist/utils/empty-response-errors.js +15 -0
  129. package/node_modules/@earendil-works/pi-ai/dist/utils/empty-response-errors.js.map +1 -0
  130. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.d.ts.map +1 -1
  131. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.js +10 -0
  132. package/node_modules/@earendil-works/pi-ai/dist/utils/retry.js.map +1 -1
  133. package/node_modules/@earendil-works/pi-ai/package.json +2 -2
  134. package/node_modules/@earendil-works/pi-pty/package.json +1 -1
  135. package/node_modules/@earendil-works/pi-telemetry/package.json +1 -1
  136. package/node_modules/@earendil-works/pi-tui/package.json +1 -1
  137. package/package.json +7 -7
@@ -0,0 +1,134 @@
1
+ import type { HostToKernelMessage, KernelToHostMessage } from "../../bridge/protocol.ts";
2
+ import { generateCorrelationId } from "../../bridge/protocol.ts";
3
+ import { RESERVED_AGENT_TOOL } from "../../bridge/reserved.ts";
4
+ import { kernelToolError } from "./kernel-tools-errors.ts";
5
+ import type { KernelToolsDescribeResult, KernelToolsInvokeRequest } from "./kernel-tools-types.ts";
6
+
7
+ type KernelToolReply = Extract<
8
+ KernelToHostMessage,
9
+ { type: "kernel-tool-describe-reply" } | { type: "kernel-tool-invoke-reply" }
10
+ >;
11
+
12
+ type Waiter = {
13
+ readonly resolve: (message: KernelToolReply) => void;
14
+ readonly reject: (error: Error) => void;
15
+ };
16
+
17
+ export class KernelToolHostPump {
18
+ readonly events = new EventTarget();
19
+ readonly #waiters = new Map<string, Waiter>();
20
+ readonly #post: (message: HostToKernelMessage) => void;
21
+ readonly #isOpen: () => boolean;
22
+
23
+ constructor(post: (message: HostToKernelMessage) => void, isOpen: () => boolean) {
24
+ this.#post = post;
25
+ this.#isOpen = isOpen;
26
+ }
27
+
28
+ consume(message: KernelToHostMessage): boolean {
29
+ if (message.type === "tool-call" && message.toolName === RESERVED_AGENT_TOOL) {
30
+ this.events.dispatchEvent(new Event("outerAwaitingAgent"));
31
+ }
32
+ if (message.type !== "kernel-tool-describe-reply" && message.type !== "kernel-tool-invoke-reply") return false;
33
+ const waiter = this.#waiters.get(message.requestId);
34
+ if (!waiter) return true;
35
+ this.#waiters.delete(message.requestId);
36
+ waiter.resolve(message);
37
+ return true;
38
+ }
39
+
40
+ rejectAll(error: Error): void {
41
+ for (const [requestId, waiter] of this.#waiters) {
42
+ this.#waiters.delete(requestId);
43
+ waiter.reject(error);
44
+ }
45
+ }
46
+
47
+ async describe(names: readonly string[]): Promise<KernelToolsDescribeResult> {
48
+ const reply = await this.#request({
49
+ type: "kernel-tool-describe",
50
+ requestId: generateCorrelationId(),
51
+ names: [...names],
52
+ });
53
+ if (reply.type !== "kernel-tool-describe-reply") {
54
+ throw kernelToolError("kernel_tool_failed", "unexpected kernel-tool describe reply");
55
+ }
56
+ if (!reply.ok) {
57
+ throw kernelToolError(codeOf(reply.error.code), reply.error.message);
58
+ }
59
+ return { results: reply.results as KernelToolsDescribeResult["results"] };
60
+ }
61
+
62
+ async invoke(request: KernelToolsInvokeRequest, signal?: AbortSignal): Promise<unknown> {
63
+ this.events.dispatchEvent(new Event("nestedInvoke"));
64
+ const reply = await this.#request(
65
+ {
66
+ type: "kernel-tool-invoke",
67
+ requestId: generateCorrelationId(),
68
+ name: request.name,
69
+ kernel_generation: request.kernel_generation,
70
+ definition_revision: request.definition_revision,
71
+ args: request.args,
72
+ call_id: request.call_id,
73
+ },
74
+ signal,
75
+ );
76
+ if (reply.type !== "kernel-tool-invoke-reply") {
77
+ throw kernelToolError("kernel_tool_failed", "unexpected kernel-tool invoke reply");
78
+ }
79
+ if (!reply.ok) throw kernelToolError(codeOf(reply.error.code), reply.error.message);
80
+ return reply.value;
81
+ }
82
+
83
+ #request(
84
+ message: Extract<HostToKernelMessage, { requestId: string }>,
85
+ signal?: AbortSignal,
86
+ ): Promise<KernelToolReply> {
87
+ if (!this.#isOpen()) throw kernelToolError("tools_unavailable", "JavaScript worker is not available");
88
+ return new Promise((resolve, reject) => {
89
+ const onAbort = (): void => {
90
+ if (!this.#waiters.delete(message.requestId)) return;
91
+ this.#post({ type: "kernel-tool-cancel", requestId: message.requestId });
92
+ reject(kernelToolError("kernel_tool_stale", "Kernel tool call cancelled"));
93
+ };
94
+ const cleanup = (): void => signal?.removeEventListener("abort", onAbort);
95
+ this.#waiters.set(message.requestId, {
96
+ resolve: (reply) => {
97
+ cleanup();
98
+ resolve(reply);
99
+ },
100
+ reject: (error) => {
101
+ cleanup();
102
+ reject(error);
103
+ },
104
+ });
105
+ if (signal?.aborted) {
106
+ onAbort();
107
+ return;
108
+ }
109
+ signal?.addEventListener("abort", onAbort, { once: true });
110
+ this.#post(message);
111
+ });
112
+ }
113
+ }
114
+
115
+ function codeOf(
116
+ code: string | undefined,
117
+ ):
118
+ | "kernel_tool_failed"
119
+ | "kernel_tool_stale"
120
+ | "kernel_tool_missing"
121
+ | "kernel_tool_recursion"
122
+ | "tools_unavailable"
123
+ | "invalid_tool_definition" {
124
+ if (
125
+ code === "kernel_tool_stale" ||
126
+ code === "kernel_tool_missing" ||
127
+ code === "kernel_tool_recursion" ||
128
+ code === "tools_unavailable" ||
129
+ code === "invalid_tool_definition"
130
+ ) {
131
+ return code;
132
+ }
133
+ return "kernel_tool_failed";
134
+ }
@@ -0,0 +1,28 @@
1
+ /** MCP name rules from coding-agent mcp/expose/naming.ts, applied explicitly for kernel tools. */
2
+ export const MCP_TOOL_NAME_MAX_LENGTH = 64;
3
+
4
+ export function sanitizeNamePart(name) {
5
+ return name.replace(/[^a-zA-Z0-9_-]/g, "_");
6
+ }
7
+
8
+ export function matcherKey(name) {
9
+ return name.replace(/-/g, "_");
10
+ }
11
+
12
+ export function ellipsizeMiddle(value, maxLength) {
13
+ if (value.length <= maxLength) return value;
14
+ if (maxLength <= 3) return value.slice(0, maxLength);
15
+ const marker = "...";
16
+ const remaining = maxLength - marker.length;
17
+ const prefixLength = Math.ceil(remaining / 2);
18
+ const suffixLength = Math.floor(remaining / 2);
19
+ return `${value.slice(0, prefixLength)}${marker}${value.slice(value.length - suffixLength)}`;
20
+ }
21
+
22
+ export function normalizeKernelToolName(name) {
23
+ return ellipsizeMiddle(sanitizeNamePart(name), MCP_TOOL_NAME_MAX_LENGTH);
24
+ }
25
+
26
+ export function kernelToolKey(name) {
27
+ return matcherKey(normalizeKernelToolName(name));
28
+ }
@@ -0,0 +1,5 @@
1
+ export function parseToolFunction(fn: unknown): {
2
+ readonly name: string;
3
+ readonly params: readonly string[];
4
+ readonly async: boolean;
5
+ };
@@ -0,0 +1,255 @@
1
+ import { kernelToolError } from "./kernel-tools-errors.js";
2
+
3
+ const IDENT = /^[\p{ID_Start}$_][\p{ID_Continue}$\u200c\u200d]*$/u;
4
+ const RESERVED = new Set(
5
+ "await break case catch class const continue debugger default delete do else enum export extends false finally for function if implements import in instanceof interface let new null package private protected public return static super switch this throw true try typeof var void while with yield".split(
6
+ " ",
7
+ ),
8
+ );
9
+
10
+ export function parseToolFunction(fn) {
11
+ if (typeof fn !== "function") fail();
12
+ const source = fn.toString();
13
+ if (source.includes("[native code]")) fail("tool() cannot wrap native functions");
14
+ return parseHead(source);
15
+ }
16
+
17
+ function parseHead(source) {
18
+ const pos = { i: 0, source };
19
+ skipTrivia(pos);
20
+ if (keyword(pos, "class")) fail();
21
+ let async = false;
22
+ if (keyword(pos, "async")) {
23
+ skipTrivia(pos);
24
+ if (!keyword(pos, "function")) fail();
25
+ async = true;
26
+ } else if (!keyword(pos, "function")) fail();
27
+ skipTrivia(pos);
28
+ if (peek(pos) === "*") fail();
29
+ const name = ident(pos);
30
+ if (!name) fail();
31
+ skipTrivia(pos);
32
+ if (peek(pos) !== "(") fail();
33
+ return { name, params: readParams(pos), async };
34
+ }
35
+
36
+ function readParams(pos) {
37
+ const parts = splitTopLevel(extractParenInner(pos));
38
+ if (parts.length === 1 && plainIdent(parts[0]) === "") return [];
39
+ return parts.map((part) => {
40
+ const name = plainIdent(part);
41
+ if (!name) fail("tool() requires simple identifier parameters");
42
+ return name;
43
+ });
44
+ }
45
+
46
+ function plainIdent(part) {
47
+ const pos = { i: 0, source: part };
48
+ skipTrivia(pos);
49
+ if (eof(pos)) return "";
50
+ const name = ident(pos);
51
+ if (!name) return null;
52
+ skipTrivia(pos);
53
+ return eof(pos) ? name : null;
54
+ }
55
+
56
+ function splitTopLevel(source) {
57
+ const parts = [];
58
+ let current = "";
59
+ const pos = { i: 0, source };
60
+ let paren = 0;
61
+ let square = 0;
62
+ let curly = 0;
63
+ while (!eof(pos)) {
64
+ const start = pos.i;
65
+ if (skipValue(pos)) {
66
+ current += source.slice(start, pos.i);
67
+ continue;
68
+ }
69
+ const ch = source[pos.i];
70
+ pos.i += 1;
71
+ if (ch === "(") paren += 1;
72
+ else if (ch === ")") paren -= 1;
73
+ else if (ch === "[") square += 1;
74
+ else if (ch === "]") square -= 1;
75
+ else if (ch === "{") curly += 1;
76
+ else if (ch === "}") curly -= 1;
77
+ if (ch === "," && paren === 0 && square === 0 && curly === 0) {
78
+ parts.push(current);
79
+ current = "";
80
+ } else current += ch;
81
+ }
82
+ parts.push(current);
83
+ return parts;
84
+ }
85
+
86
+ function extractParenInner(pos) {
87
+ const source = pos.source;
88
+ if (source[pos.i] !== "(") fail();
89
+ pos.i += 1;
90
+ const start = pos.i;
91
+ let paren = 1;
92
+ while (pos.i < source.length && paren > 0) {
93
+ const before = pos.i;
94
+ if (skipValue(pos)) continue;
95
+ if (pos.i !== before) continue;
96
+ const ch = source[pos.i];
97
+ pos.i += 1;
98
+ if (ch === "(") paren += 1;
99
+ else if (ch === ")") paren -= 1;
100
+ }
101
+ if (paren !== 0) fail();
102
+ return source.slice(start, pos.i - 1);
103
+ }
104
+
105
+ function skipValue(pos) {
106
+ const ch = peek(pos);
107
+ if (ch === "'" || ch === '"') {
108
+ pos.i = skipString(pos.source, pos.i);
109
+ return true;
110
+ }
111
+ if (ch === "`") {
112
+ pos.i = skipTemplate(pos.source, pos.i);
113
+ return true;
114
+ }
115
+ if (ch === "/" && pos.source[pos.i + 1] === "/") {
116
+ pos.i = skipLineComment(pos.source, pos.i);
117
+ return true;
118
+ }
119
+ if (ch === "/" && pos.source[pos.i + 1] === "*") {
120
+ pos.i = skipBlockComment(pos.source, pos.i);
121
+ return true;
122
+ }
123
+ return false;
124
+ }
125
+
126
+ function skipTrivia(pos) {
127
+ while (!eof(pos)) {
128
+ if (/\s/u.test(peek(pos))) {
129
+ pos.i += 1;
130
+ continue;
131
+ }
132
+ if (peek(pos) === "/" && pos.source[pos.i + 1] === "/") {
133
+ pos.i = skipLineComment(pos.source, pos.i);
134
+ continue;
135
+ }
136
+ if (peek(pos) === "/" && pos.source[pos.i + 1] === "*") {
137
+ pos.i = skipBlockComment(pos.source, pos.i);
138
+ continue;
139
+ }
140
+ return;
141
+ }
142
+ }
143
+
144
+ function keyword(pos, word) {
145
+ if (!pos.source.startsWith(word, pos.i)) return false;
146
+ const next = pos.source[pos.i + word.length];
147
+ if (next !== undefined && isContinue(next)) return false;
148
+ pos.i += word.length;
149
+ return true;
150
+ }
151
+
152
+ function ident(pos) {
153
+ if (eof(pos) || !isStart(peek(pos))) return null;
154
+ const start = pos.i;
155
+ pos.i += 1;
156
+ while (!eof(pos) && isContinue(peek(pos))) pos.i += 1;
157
+ const value = pos.source.slice(start, pos.i);
158
+ if (!IDENT.test(value) || RESERVED.has(value)) {
159
+ pos.i = start;
160
+ return null;
161
+ }
162
+ return value;
163
+ }
164
+
165
+ function peek(pos) {
166
+ return pos.source[pos.i];
167
+ }
168
+
169
+ function eof(pos) {
170
+ return pos.i >= pos.source.length;
171
+ }
172
+
173
+ function fail(message = "tool() requires a named function") {
174
+ throw kernelToolError("invalid_tool_definition", message);
175
+ }
176
+
177
+ function isStart(ch) {
178
+ return /[\p{ID_Start}$_]/u.test(ch);
179
+ }
180
+
181
+ function isContinue(ch) {
182
+ return /[\p{ID_Continue}$\u200c\u200d]/u.test(ch);
183
+ }
184
+
185
+ function skipLineComment(source, i) {
186
+ i += 2;
187
+ while (i < source.length && source[i] !== "\n" && source[i] !== "\r" && source[i] !== "\u2028" && source[i] !== "\u2029") i += 1;
188
+ return i;
189
+ }
190
+
191
+ function skipBlockComment(source, i) {
192
+ const end = source.indexOf("*/", i + 2);
193
+ if (end < 0) fail();
194
+ return end + 2;
195
+ }
196
+
197
+ function skipString(source, i) {
198
+ const quote = source[i];
199
+ i += 1;
200
+ while (i < source.length) {
201
+ if (source[i] === "\\") {
202
+ i += 2;
203
+ continue;
204
+ }
205
+ if (source[i] === quote) return i + 1;
206
+ i += 1;
207
+ }
208
+ fail();
209
+ }
210
+
211
+ function skipTemplate(source, i) {
212
+ i += 1;
213
+ while (i < source.length) {
214
+ if (source[i] === "\\") {
215
+ i += 2;
216
+ continue;
217
+ }
218
+ if (source[i] === "`") return i + 1;
219
+ if (source[i] === "$" && source[i + 1] === "{") {
220
+ i = skipTemplateExpr(source, i + 2);
221
+ continue;
222
+ }
223
+ i += 1;
224
+ }
225
+ fail();
226
+ }
227
+
228
+ function skipTemplateExpr(source, i) {
229
+ let curly = 1;
230
+ while (i < source.length && curly > 0) {
231
+ if (source[i] === "'" || source[i] === '"') {
232
+ i = skipString(source, i);
233
+ continue;
234
+ }
235
+ if (source[i] === "`") {
236
+ i = skipTemplate(source, i);
237
+ continue;
238
+ }
239
+ if (source[i] === "/" && source[i + 1] === "/") {
240
+ i = skipLineComment(source, i);
241
+ continue;
242
+ }
243
+ if (source[i] === "/" && source[i + 1] === "*") {
244
+ i = skipBlockComment(source, i);
245
+ continue;
246
+ }
247
+ if (source[i] === "{") curly += 1;
248
+ else if (source[i] === "}") {
249
+ curly -= 1;
250
+ if (curly === 0) return i + 1;
251
+ }
252
+ i += 1;
253
+ }
254
+ fail();
255
+ }
@@ -0,0 +1,105 @@
1
+ import { kernelToolCallContext } from "./kernel-tools-context.js";
2
+ import { kernelToolError } from "./kernel-tools-errors.js";
3
+
4
+ export function createKernelToolPump({ getRuntime, emit, nestedInvokes }) {
5
+ async function describe(message) {
6
+ try {
7
+ const runtime = getRuntime();
8
+ if (!runtime?.kernelTools) throw kernelToolError("tools_unavailable", "JS runtime not initialized");
9
+ const results = runtime.kernelTools.describe(message.names).results;
10
+ emit({ type: "kernel-tool-describe-reply", requestId: message.requestId, ok: true, results });
11
+ } catch (error) {
12
+ emit({ type: "kernel-tool-describe-reply", requestId: message.requestId, ok: false, error: pumpError(error) });
13
+ }
14
+ }
15
+
16
+ async function invoke(message) {
17
+ const pendingTools = new Map();
18
+ const controller = new AbortController();
19
+ nestedInvokes.set(message.requestId, { pendingTools, controller });
20
+ try {
21
+ const runtime = getRuntime();
22
+ if (!runtime?.kernelTools) throw kernelToolError("tools_unavailable", "JS runtime not initialized");
23
+ const value = await kernelToolCallContext.run(
24
+ { pendingTools, callId: message.call_id, generation: message.kernel_generation, signal: controller.signal },
25
+ () =>
26
+ runtime.kernelTools.invoke(
27
+ {
28
+ name: message.name,
29
+ kernel_generation: message.kernel_generation,
30
+ definition_revision: message.definition_revision,
31
+ args: message.args,
32
+ call_id: message.call_id,
33
+ },
34
+ controller.signal,
35
+ ),
36
+ );
37
+ emit({ type: "kernel-tool-invoke-reply", requestId: message.requestId, ok: true, value });
38
+ } catch (error) {
39
+ emit({ type: "kernel-tool-invoke-reply", requestId: message.requestId, ok: false, error: pumpError(error) });
40
+ } finally {
41
+ nestedInvokes.delete(message.requestId);
42
+ }
43
+ }
44
+
45
+ return {
46
+ handle(message) {
47
+ if (message.type === "kernel-tool-describe") {
48
+ void describe(message);
49
+ return true;
50
+ }
51
+ if (message.type === "kernel-tool-invoke") {
52
+ void invoke(message);
53
+ return true;
54
+ }
55
+ if (message.type === "kernel-tool-cancel") {
56
+ const nested = nestedInvokes.get(message.requestId);
57
+ if (nested) abortNested(nested, kernelToolError("kernel_tool_stale", "Kernel tool call cancelled"));
58
+ return true;
59
+ }
60
+ return false;
61
+ },
62
+ abortAll(error) {
63
+ for (const nested of nestedInvokes.values()) abortNested(nested, error);
64
+ },
65
+ settleToolReply(message) {
66
+ for (const nested of nestedInvokes.values()) {
67
+ const pending = nested.pendingTools.get(message.callId);
68
+ if (!pending) continue;
69
+ nested.pendingTools.delete(message.callId);
70
+ if (message.ok) pending.resolve(message.value);
71
+ else pending.reject(errorFrom(message.error));
72
+ return true;
73
+ }
74
+ return false;
75
+ },
76
+ };
77
+ }
78
+
79
+ function abortNested(nested, error) {
80
+ nested.controller.abort(error);
81
+ for (const [callId, pending] of nested.pendingTools) {
82
+ nested.pendingTools.delete(callId);
83
+ pending.reject(error);
84
+ }
85
+ }
86
+
87
+ function pumpError(error) {
88
+ if (error instanceof Error) {
89
+ return {
90
+ name: error.name,
91
+ message: error.message,
92
+ stack: error.stack,
93
+ ...(typeof error.code === "string" ? { code: error.code } : {}),
94
+ };
95
+ }
96
+ return { message: String(error) };
97
+ }
98
+
99
+ function errorFrom(error) {
100
+ const result = new Error(error.message);
101
+ if (error.name) result.name = error.name;
102
+ if (error.stack) result.stack = error.stack;
103
+ if (typeof error.code === "string") result.code = error.code;
104
+ return result;
105
+ }
@@ -0,0 +1,27 @@
1
+ export { createToolNamespace } from "./kernel-tools-define.js";
2
+
3
+ export type KernelToolRegistryOptions = {
4
+ readonly generation?: number;
5
+ readonly language?: "js" | "py" | "rb" | "jl";
6
+ readonly hostToolNames?: readonly string[] | (() => readonly string[]);
7
+ readonly foreignLanguageNames?: readonly string[] | (() => readonly string[]);
8
+ readonly reservedNames?: readonly string[];
9
+ };
10
+
11
+ export function createKernelToolRegistry(options?: KernelToolRegistryOptions): {
12
+ readonly generation: number;
13
+ define(fn: { readonly name: string }, metadata?: unknown): unknown;
14
+ describe(names: readonly string[]): unknown;
15
+ invoke(
16
+ request: {
17
+ readonly name: string;
18
+ readonly kernel_generation: number;
19
+ readonly definition_revision: number;
20
+ readonly args: unknown;
21
+ readonly call_id: string;
22
+ },
23
+ signal?: AbortSignal,
24
+ ): Promise<unknown>;
25
+ bumpGeneration(): number;
26
+ setCollisionNames(hostToolNames?: readonly string[], foreignLanguageNames?: readonly string[]): void;
27
+ };
@@ -0,0 +1,127 @@
1
+ import { createToolNamespace } from "./kernel-tools-define.js";
2
+ import { kernelToolError } from "./kernel-tools-errors.js";
3
+ import { kernelToolKey, MCP_TOOL_NAME_MAX_LENGTH, sanitizeNamePart } from "./kernel-tools-naming.js";
4
+ import { parseToolFunction } from "./kernel-tools-parse.js";
5
+ import { orderedArgs, resolveToolMetadata, validateInvokeArgs } from "./kernel-tools-schema.js";
6
+
7
+ export { createToolNamespace };
8
+
9
+ const DEFAULT_RESERVED = Object.freeze(["__agent__", "__output__", "__schema__"]);
10
+
11
+ function currentNames(source) {
12
+ return typeof source === "function" ? source() : (source ?? []);
13
+ }
14
+
15
+ function hasNameKey(source, key) {
16
+ return currentNames(source).some((name) => kernelToolKey(name) === key);
17
+ }
18
+
19
+ export function createKernelToolRegistry(options = {}) {
20
+ const language = options.language ?? "js";
21
+ let generation = options.generation ?? 1;
22
+ const reservedKeys = new Set((options.reservedNames ?? DEFAULT_RESERVED).map(kernelToolKey));
23
+ let hostSource = options.hostToolNames ?? [];
24
+ let foreignSource = options.foreignLanguageNames ?? [];
25
+ const entries = new Map();
26
+
27
+ function assertJs() {
28
+ if (language !== "js") throw kernelToolError("tools_unavailable", "Kernel tools are JavaScript-only");
29
+ }
30
+
31
+ function descriptorFor(entry) {
32
+ return {
33
+ name: entry.normalizedName,
34
+ description: entry.description,
35
+ input_schema: entry.input_schema,
36
+ language: "js",
37
+ kernel_generation: generation,
38
+ definition_revision: entry.revision,
39
+ };
40
+ }
41
+
42
+ return {
43
+ get generation() {
44
+ return generation;
45
+ },
46
+ define(fn, metadata) {
47
+ assertJs();
48
+ const parsed = parseToolFunction(fn);
49
+ const resolved = resolveToolMetadata(metadata, parsed.params);
50
+ if (sanitizeNamePart(parsed.name) !== parsed.name || parsed.name.length > MCP_TOOL_NAME_MAX_LENGTH) {
51
+ throw kernelToolError("invalid_tool_definition", "Kernel tool name must match MCP name grammar");
52
+ }
53
+ const normalizedName = parsed.name;
54
+ const key = kernelToolKey(parsed.name);
55
+ if (reservedKeys.has(key)) throw kernelToolError("reserved_tool_name", `Kernel tool name is reserved: ${parsed.name}`);
56
+ if (hasNameKey(hostSource, key) || hasNameKey(foreignSource, key)) {
57
+ throw kernelToolError("tool_name_collision", `Kernel tool name collides: ${parsed.name}`);
58
+ }
59
+ const existing = entries.get(key);
60
+ if (existing && existing.originalName !== parsed.name) {
61
+ throw kernelToolError("tool_name_collision", `Kernel tool name collides: ${parsed.name}`);
62
+ }
63
+ const entry = {
64
+ originalName: parsed.name,
65
+ normalizedName,
66
+ fn,
67
+ params: parsed.params,
68
+ description: resolved.description,
69
+ input_schema: resolved.input_schema,
70
+ revision: existing ? existing.revision + 1 : 1,
71
+ };
72
+ entries.set(key, entry);
73
+ return descriptorFor(entry);
74
+ },
75
+ describe(names) {
76
+ assertJs();
77
+ return {
78
+ results: names.map((name) => {
79
+ const entry = entries.get(kernelToolKey(name));
80
+ if (!entry) {
81
+ return {
82
+ name,
83
+ ok: false,
84
+ error: { code: "kernel_tool_missing", message: `Kernel tool is not defined: ${name}` },
85
+ };
86
+ }
87
+ return { name, ok: true, descriptor: descriptorFor(entry) };
88
+ }),
89
+ };
90
+ },
91
+ async invoke(request, signal) {
92
+ assertJs();
93
+ if (signal?.aborted) {
94
+ throw signal.reason ?? kernelToolError("kernel_tool_failed", "Kernel tool call aborted");
95
+ }
96
+ if (request.kernel_generation !== generation) {
97
+ throw kernelToolError("kernel_tool_stale", "Kernel tool descriptor generation is stale");
98
+ }
99
+ const entry = entries.get(kernelToolKey(request.name));
100
+ if (!entry) throw kernelToolError("kernel_tool_missing", `Kernel tool is not defined: ${request.name}`);
101
+ if (entry.revision !== request.definition_revision) {
102
+ throw kernelToolError("kernel_tool_stale", "Kernel tool descriptor revision is stale");
103
+ }
104
+ validateInvokeArgs(entry.input_schema, request.args);
105
+ try {
106
+ const value = await entry.fn(...orderedArgs(entry.params, request.args));
107
+ const live = entries.get(kernelToolKey(request.name));
108
+ if (request.kernel_generation !== generation || live !== entry) {
109
+ throw kernelToolError("kernel_tool_stale", "Kernel tool descriptor is stale");
110
+ }
111
+ return value;
112
+ } catch (error) {
113
+ if (error instanceof Error && typeof error.code === "string") throw error;
114
+ throw kernelToolError("kernel_tool_failed", error instanceof Error ? error.message : String(error));
115
+ }
116
+ },
117
+ bumpGeneration() {
118
+ generation += 1;
119
+ entries.clear();
120
+ return generation;
121
+ },
122
+ setCollisionNames(hostToolNames, foreignLanguageNames) {
123
+ hostSource = hostToolNames ?? [];
124
+ foreignSource = foreignLanguageNames ?? [];
125
+ },
126
+ };
127
+ }