@code-yeongyu/senpi-codemode 2026.10.6 → 2026.10.7
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/CHANGELOG.md +19 -0
- package/README.md +4 -0
- package/package.json +4 -4
- package/src/bridge/reserved.ts +10 -0
- package/src/bridges/eval-virtual-schemas.ts +79 -0
- package/src/bridges/handle-bridge.ts +72 -0
- package/src/bridges/reserved-dispatch.ts +20 -2
- package/src/bridges/schema-bridge.ts +4 -0
- package/src/completion/handle.ts +69 -0
- package/src/completion/tool-bridge.ts +31 -12
- package/src/extension/bridge-dispatch.ts +41 -0
- package/src/extension/bridge-tool-call.ts +5 -1
- package/src/extension/host-facts.ts +18 -0
- package/src/extension/removed-tool-hints.ts +15 -0
- package/src/extension/runtime-factory.ts +3 -0
- package/src/extension/session-manager-contract.ts +3 -0
- package/src/extension/session-manager.ts +17 -4
- package/src/extension/start-recovery.ts +111 -0
- package/src/handles/completion-handles.ts +202 -0
- package/src/handles/handle-args.ts +103 -0
- package/src/handles/handle-registry.ts +164 -0
- package/src/handles/wait.ts +214 -0
- package/src/handles/watch-queue.ts +102 -0
- package/src/index.ts +99 -94
- package/src/kernels/jl/handles.jl +124 -0
- package/src/kernels/jl/prelude.jl +9 -0
- package/src/kernels/jl/runner.jl +6 -1
- package/src/kernels/js/handles.js +93 -0
- package/src/kernels/js/kernel-tools-define.d.ts +6 -0
- package/src/kernels/js/kernel-tools-define.js +3 -1
- package/src/kernels/js/kernel-tools-registry.d.ts +2 -0
- package/src/kernels/js/kernel-tools-registry.js +18 -9
- package/src/kernels/js/kernel-tools-schema.js +6 -2
- package/src/kernels/js/worker-runtime.js +10 -1
- package/src/kernels/py/host-cell.ts +42 -0
- package/src/kernels/py/kernel-contract.ts +2 -0
- package/src/kernels/py/kernel.ts +48 -4
- package/src/kernels/py/prelude.py +140 -4
- package/src/kernels/rb/handles.rb +97 -0
- package/src/kernels/rb/paths.rb +37 -0
- package/src/kernels/rb/prelude.rb +15 -38
- package/src/kernels/rb/runner.rb +7 -7
- package/src/tool/cell-handler.ts +21 -18
- package/src/tool/cell-reserved-dispatch.ts +51 -0
- package/src/tool/eval-tool-options.ts +3 -0
- package/src/tool/run-eval-cell.ts +2 -0
- package/src/tool/types.ts +15 -0
package/CHANGELOG.md
CHANGED
|
@@ -12,6 +12,25 @@
|
|
|
12
12
|
|
|
13
13
|
### Removed
|
|
14
14
|
|
|
15
|
+
## [2026.10.7] - 2026-10-04
|
|
16
|
+
|
|
17
|
+
### Breaking Changes
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- JavaScript kernel tools: `tool.defined()` lists the defined kernel tools and `tool.undefine(name)` removes one; `tool(fn, { name })` registers a tool under an explicit name while keeping the function's argument order ([#2452](https://github.com/code-yeongyu/senpi/issues/2452)).
|
|
22
|
+
- In-cell `wait(handles, {timeout, mode})` barrier and `handle(node | ref | {pool_id})` rich views in all four kernels: `wait` returns values in input order (`all`), the first success (`any`) or every outcome (`settled`), times out with `eval_wait_timeout` without cancelling work, and pauses the run budget while parked; `handle(node).control` offers `status()`, `output()`, `send()`, `cancel()` and `wait()` fenced by owner, id and `run_epoch` through the host's `EvalHandleHost` capability (agent and workpool handles fail with `eval_wait_unavailable` on a host without it); `completion(prompt, {handle: true})` returns an opt-in completion handle bounded by its cell's hard deadline; `tool_schema("eval:helpers")` and `tool_schema("eval:wait")` document the surface and the removed-tool hint for `wait` points at them. The legacy `agent(..., {handle: true})` record, the eval description and the eval input schema are unchanged; the Python runner's dispatcher was renamed `_handle_message` so `handle()` is the helper ([#2687](https://github.com/code-yeongyu/senpi/pull/2687)).
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
|
|
28
|
+
- The Ruby kernel's memory notice names the largest globals again on Ruby 2.6 (the sizer used a Ruby 2.7 method) ([#2696](https://github.com/code-yeongyu/senpi/issues/2696)).
|
|
29
|
+
- Eval no longer fails with "codemode session manager is disposed" for the rest of a session after a session switch or fork that another extension cancelled: codemode only tears its kernels down when the session actually ends ([#1706](https://github.com/code-yeongyu/senpi/issues/1706)).
|
|
30
|
+
- A session whose codemode runtime failed to start is recovered by the next eval call (once, with one stderr line naming the failed start); if re-creation also fails, the call says "codemode runtime could not be re-created: <reason>" and how to bring eval back ([#1706](https://github.com/code-yeongyu/senpi/issues/1706)).
|
|
31
|
+
|
|
32
|
+
### Removed
|
|
33
|
+
|
|
15
34
|
## [2026.10.6] - 2026-10-04
|
|
16
35
|
|
|
17
36
|
### Breaking Changes
|
package/README.md
CHANGED
|
@@ -200,8 +200,12 @@ options object and asynchronous helpers are `await`-able.
|
|
|
200
200
|
| `env(key?, value?)` | Reads all kernel environment values, one value, or sets one value. Includes the session's `PI_*` values (see [Session environment](#session-environment)). |
|
|
201
201
|
| `tool.<name>(args)` | Invokes an active Senpi tool through the normal `pi.executeTool` pipeline and returns `{ text, images?, details?, hasError? }` in every kernel; image blocks arrive as `images[i] = { mimeType, dataBase64 }`. |
|
|
202
202
|
| `tool_schema(name?)` | Returns a tool's parameter schema without calling it; omit `name` to list tool names. |
|
|
203
|
+
| `tool(fn, metadata?)` (js) | Registers a named function as a kernel tool for in-process children. `metadata.name` registers it under that name instead of the function's; arguments are still passed in the function's parameter order. |
|
|
204
|
+
| `tool.defined()` / `tool.undefine(name)` (js) | List the kernel tools this kernel defines (sorted), and remove one (`true` if it existed). A descriptor taken before `undefine` can no longer be invoked. Both names are reserved: a kernel tool can't be registered as `defined` or `undefine` (`reserved_tool_name`), and a host tool with either name is shadowed in the `tool` namespace, so it can't be called from a JavaScript cell. |
|
|
203
205
|
| `completion(prompt, model?, system?, schema?)` | Requests a one-shot host completion; `schema` asks the host to parse structured output. |
|
|
204
206
|
| `agent(prompt, ...)` | Delegates to the configured active `taskTools.task` tool. Supports background handles and structured JSON results. |
|
|
207
|
+
| `wait(handles, timeout?, mode?)` | Blocks the cell until the given handles settle (agent handle records, `handle()` views, completion handles, closed workpools, or saved `{kind, id, run_epoch}` references). `mode` is `all` (values in input order; the first failed, cancelled, or lost handle raises), `any` (`{index, ref, value}` of the first success), or `settled` (one outcome per input slot). `timeout` is wall-clock seconds from entry; on expiry `eval_wait_timeout` is raised and nothing is cancelled. Rides the bridge-call path, so the run budget pauses while parked. Agent and workpool handles need the host's `EvalHandleHost` capability (`eval_wait_unavailable` without it); completion handles always work. Julia extends `Base.wait` for handle views (`wait(handle(node))`). Details: `tool_schema("eval:wait")`. |
|
|
208
|
+
| `handle(node \| ref \| {pool_id})` | Returns a rich view: the legacy record's fields plus a non-enumerable `control` (Python: attribute on a `dict` subclass; Ruby: singleton method) with `status()`, `output(format?, offset?, limit?)`, `send(message)` (agent handles only), `cancel()` (idempotent for that run epoch; never touches a successor run), and `wait(timeout?)`. Every control call is fenced by owner, id, and `run_epoch` inside the task owner (`eval_handle_stale`, `eval_handle_forbidden`). `completion(prompt, handle: true)` returns such a view for a host completion. The `agent(..., handle: true)` record itself is unchanged. Details: `tool_schema("eval:helpers")`. |
|
|
205
209
|
| `workpool(agent, name, mode?)` | Creates a thin adapter over the normal host `workpool` tool; exposes `pool_id`, `push(items)`, `close()`, `inspect()`, and `cancel()`. JS awaits creation and operations. |
|
|
206
210
|
| `output(ids, format?, offset?, limit?)` | Delegates transcript retrieval to the configured active `taskTools.output` tool. |
|
|
207
211
|
| `parallel(thunks)` | Runs thunks through the configured bounded pool while preserving input order. |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@code-yeongyu/senpi-codemode",
|
|
3
|
-
"version": "2026.10.
|
|
3
|
+
"version": "2026.10.7",
|
|
4
4
|
"description": "Source-only senpi extension package for codemode evaluation tools",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -32,14 +32,14 @@
|
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"@babel/parser": "8.0.4",
|
|
35
|
-
"@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@2026.10.
|
|
35
|
+
"@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@2026.10.7",
|
|
36
36
|
"typebox": "1.3.34"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
|
-
"@code-yeongyu/senpi": "2026.10.
|
|
39
|
+
"@code-yeongyu/senpi": "2026.10.7"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@code-yeongyu/senpi": "2026.10.
|
|
42
|
+
"@code-yeongyu/senpi": "2026.10.7"
|
|
43
43
|
},
|
|
44
44
|
"keywords": [
|
|
45
45
|
"senpi",
|
package/src/bridge/reserved.ts
CHANGED
|
@@ -5,6 +5,16 @@ export const RESERVED_AGENT_TOOL = "__agent__" as const;
|
|
|
5
5
|
export const RESERVED_OUTPUT_TOOL = "__output__" as const;
|
|
6
6
|
/** ADAPTATION: senpi resolves tool parameter schemas through a reserved kernel-side tool name. */
|
|
7
7
|
export const RESERVED_SCHEMA_TOOL = "__schema__" as const;
|
|
8
|
+
/** In-cell `wait(handles)` barrier over the host's EvalHandleHost subscription capability. */
|
|
9
|
+
export const RESERVED_WAIT_TOOL = "__wait__" as const;
|
|
10
|
+
/** `handle(node).control.status()`: one epoch-fenced snapshot through the host's `watch`. */
|
|
11
|
+
export const RESERVED_HANDLE_STATUS_TOOL = "__handle_status__" as const;
|
|
12
|
+
/** `handle(node).control.output(opts)`: the transcript of exactly `ref.run_epoch`. */
|
|
13
|
+
export const RESERVED_HANDLE_OUTPUT_TOOL = "__handle_output__" as const;
|
|
14
|
+
/** `handle(node).control.send(message)`: agent handles only, fenced inside the task owner. */
|
|
15
|
+
export const RESERVED_HANDLE_SEND_TOOL = "__handle_send__" as const;
|
|
16
|
+
/** `handle(node).control.cancel()`: idempotent for its epoch; a successor run is never touched. */
|
|
17
|
+
export const RESERVED_HANDLE_CANCEL_TOOL = "__handle_cancel__" as const;
|
|
8
18
|
/** Canonical oh-my-pi eval-timeout pause operation. */
|
|
9
19
|
export const TIMEOUT_PAUSE_OP = "timeout-pause" as const;
|
|
10
20
|
/** Canonical oh-my-pi eval-timeout resume operation. */
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { EvalSchemaResult } from "./schema-bridge.ts";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Virtual `tool_schema("eval:*")` entries: documentation a cell can read on demand. They are not tools,
|
|
5
|
+
* never execute anything, and never appear in the ordinary catalog listing, so the eval prompt and the
|
|
6
|
+
* host tool list stay byte-identical.
|
|
7
|
+
*/
|
|
8
|
+
const handleRefSchema = {
|
|
9
|
+
type: "object",
|
|
10
|
+
description:
|
|
11
|
+
"A saved handle reference. agent(..., {handle: true}) records, handle() views, completion handles and workpools ({pool_id}) are accepted directly.",
|
|
12
|
+
properties: {
|
|
13
|
+
kind: { enum: ["agent", "completion", "workpool"] },
|
|
14
|
+
id: { type: "string" },
|
|
15
|
+
run_epoch: {
|
|
16
|
+
type: "integer",
|
|
17
|
+
minimum: 0,
|
|
18
|
+
description:
|
|
19
|
+
"The run this reference is bound to; a resumed task gets a new epoch and the old reference fails with eval_handle_stale.",
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
required: ["kind", "id", "run_epoch"],
|
|
23
|
+
} as const;
|
|
24
|
+
|
|
25
|
+
const waitEntry: EvalSchemaResult = {
|
|
26
|
+
name: "eval:wait",
|
|
27
|
+
description: [
|
|
28
|
+
"wait(handles, {timeout?, mode?}) blocks the cell until the given handles settle (js: await wait(...); py/rb: wait(...); jl: wait(handle(node)) or wait([handle(a), handle(b)])).",
|
|
29
|
+
'mode "all" (default) returns the successful values in input order and raises the first failed, cancelled or lost handle\'s error; "any" returns {index, ref, value} of the first success or eval_wait_all_rejected; "settled" returns one {status, ref, value|error} outcome per input slot.',
|
|
30
|
+
"Duplicates keep their slots with one subscription. Empty handles: all/settled -> [], any -> eval_wait_empty.",
|
|
31
|
+
"timeout is wall-clock seconds from entry (finite, >= 0; 0 = one atomic check; omitted = no wait-specific deadline). On timeout the subscription closes and `eval_wait_timeout: wait() timed out after Ns; k/n handles settled; work was not cancelled` is raised. wait() never cancels work.",
|
|
32
|
+
"wait() rides the bridge-call path: the cell's run budget pauses while parked, the hard limit never pauses, and a long barrier detaches like any bridge-parked cell. Cancelling the cell closes its subscriptions at once. Task and workpool notifications are neither consumed nor suppressed.",
|
|
33
|
+
"A workpool must be closed first (eval_pool_open) and succeeds only when every key succeeds (eval_workpool_failed carries the keyed aggregate).",
|
|
34
|
+
"Agent and workpool handles need the host capability; without it wait() fails with eval_wait_unavailable and never polls task_output. Completion handles always work.",
|
|
35
|
+
].join("\n"),
|
|
36
|
+
parameters: {
|
|
37
|
+
type: "object",
|
|
38
|
+
properties: {
|
|
39
|
+
handles: { type: "array", items: handleRefSchema },
|
|
40
|
+
timeout: { type: "number", minimum: 0, description: "Wall-clock seconds from entry." },
|
|
41
|
+
mode: { enum: ["all", "any", "settled"], default: "all" },
|
|
42
|
+
},
|
|
43
|
+
required: ["handles"],
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const helpersEntry: EvalSchemaResult = {
|
|
48
|
+
name: "eval:helpers",
|
|
49
|
+
description: [
|
|
50
|
+
"Advanced cell helpers (all four languages; js helpers are awaited).",
|
|
51
|
+
"handle(node | ref | {pool_id}) returns a rich view: the legacy fields plus a non-enumerable `control` (py: attribute, rb: singleton method) with status(), output({format, offset, limit}), send(message) (agent handles only; others eval_handle_operation_unsupported), cancel() (idempotent for that run epoch; never cancels a successor run) and wait({timeout}).",
|
|
52
|
+
"control.status() goes through the host subscription; control.send/cancel/output are fenced by owner, id and run_epoch inside the task owner (stale -> eval_handle_stale, foreign -> eval_handle_forbidden); control.output() returns only that epoch's transcript. The plain output() helper and task tools are unchanged.",
|
|
53
|
+
"The legacy agent(..., {handle: true}) record is unchanged: .output stays a string and no enumerable key is added. A saved {kind, id, run_epoch} (or the record itself) can be rebound with handle(ref) in a later cell or after a kernel reset while the owner and run epoch are valid.",
|
|
54
|
+
"completion(prompt, {handle: true}) (py: handle=True) returns a control handle after preflight validation; provider failures surface in its outcome; its deadline derives from the creating cell's hard deadline. Completion handles are codemode-owned and need no host capability; once the session generation is dropped they fail with eval_handle_stale.",
|
|
55
|
+
"See tool_schema('eval:wait') for the barrier semantics.",
|
|
56
|
+
].join("\n"),
|
|
57
|
+
parameters: {
|
|
58
|
+
type: "object",
|
|
59
|
+
properties: {
|
|
60
|
+
wait: { description: "wait(handles, {timeout?, mode?}) -> values | {index, ref, value} | outcomes[]" },
|
|
61
|
+
handle: { description: "handle(node | ref | {pool_id}) -> view with .control" },
|
|
62
|
+
"control.status": { description: "() -> {ref, phase, host_status, revision}" },
|
|
63
|
+
"control.output": { description: "({format?, offset?, limit?}) -> {ref, text, offset, total, truncated}" },
|
|
64
|
+
"control.send": { description: "(message) -> snapshot; agent handles only" },
|
|
65
|
+
"control.cancel": { description: "() -> {ref, cancelled, phase}" },
|
|
66
|
+
"control.wait": { description: "({timeout?}) -> value" },
|
|
67
|
+
completion: { description: "completion(prompt, {handle: true}) -> control handle" },
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const VIRTUAL_ENTRIES: ReadonlyMap<string, EvalSchemaResult> = new Map([
|
|
73
|
+
[waitEntry.name, waitEntry],
|
|
74
|
+
[helpersEntry.name, helpersEntry],
|
|
75
|
+
]);
|
|
76
|
+
|
|
77
|
+
export function virtualEvalSchema(name: string): EvalSchemaResult | undefined {
|
|
78
|
+
return VIRTUAL_ENTRIES.get(name);
|
|
79
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { EvalHandleError, type EvalHandleHost } from "@code-yeongyu/senpi";
|
|
2
|
+
import {
|
|
3
|
+
RESERVED_HANDLE_CANCEL_TOOL,
|
|
4
|
+
RESERVED_HANDLE_OUTPUT_TOOL,
|
|
5
|
+
RESERVED_HANDLE_SEND_TOOL,
|
|
6
|
+
RESERVED_HANDLE_STATUS_TOOL,
|
|
7
|
+
RESERVED_WAIT_TOOL,
|
|
8
|
+
} from "../bridge/reserved.ts";
|
|
9
|
+
import { parseOutputArgs, parseRefArgs, parseSendArgs, parseWaitArgs } from "../handles/handle-args.ts";
|
|
10
|
+
import type { HandleBackend, HandleRegistry } from "../handles/handle-registry.ts";
|
|
11
|
+
import { waitForHandles } from "../handles/wait.ts";
|
|
12
|
+
|
|
13
|
+
export const HANDLE_TOOL_NAMES = [
|
|
14
|
+
RESERVED_WAIT_TOOL,
|
|
15
|
+
RESERVED_HANDLE_STATUS_TOOL,
|
|
16
|
+
RESERVED_HANDLE_OUTPUT_TOOL,
|
|
17
|
+
RESERVED_HANDLE_SEND_TOOL,
|
|
18
|
+
RESERVED_HANDLE_CANCEL_TOOL,
|
|
19
|
+
] as const;
|
|
20
|
+
|
|
21
|
+
export type HandleToolName = (typeof HANDLE_TOOL_NAMES)[number];
|
|
22
|
+
|
|
23
|
+
export interface HandleBridgeContext {
|
|
24
|
+
/** The session generation's registry; absent only for the pre-session baseline tool. */
|
|
25
|
+
readonly registry: HandleRegistry | undefined;
|
|
26
|
+
/** `ctx.evalHandleHost` at dispatch time; absent on a runtime without a provider. */
|
|
27
|
+
readonly host: EvalHandleHost | undefined;
|
|
28
|
+
/** The calling cell's signal: cancelling the cell closes its subscriptions immediately. */
|
|
29
|
+
readonly signal: AbortSignal | undefined;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function isHandleToolName(toolName: string): toolName is HandleToolName {
|
|
33
|
+
return HANDLE_TOOL_NAMES.some((name) => name === toolName);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Dispatches the five reserved handle tools; every path is fenced by the registry before any host call. */
|
|
37
|
+
export async function runHandleTool(toolName: HandleToolName, args: unknown, context: HandleBridgeContext) {
|
|
38
|
+
const registry = context.registry;
|
|
39
|
+
if (registry === undefined) {
|
|
40
|
+
throw new EvalHandleError(
|
|
41
|
+
"eval_wait_unavailable",
|
|
42
|
+
"wait()/handle() are not supported by this runtime: this session has no handle registry yet",
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
const backend: HandleBackend = { host: context.host, signal: context.signal };
|
|
46
|
+
switch (toolName) {
|
|
47
|
+
case RESERVED_WAIT_TOOL:
|
|
48
|
+
return await waitForHandles(parseWaitArgs(args), {
|
|
49
|
+
watch: (refs) => registry.watch(refs, backend),
|
|
50
|
+
result: (ref) => registry.result(ref, backend),
|
|
51
|
+
...(context.signal === undefined ? {} : { signal: context.signal }),
|
|
52
|
+
});
|
|
53
|
+
case RESERVED_HANDLE_STATUS_TOOL:
|
|
54
|
+
return await registry.status(parseRefArgs(args, "control.status()"), backend);
|
|
55
|
+
case RESERVED_HANDLE_OUTPUT_TOOL: {
|
|
56
|
+
const { ref, request } = parseOutputArgs(args);
|
|
57
|
+
return await registry.output(ref, request, backend);
|
|
58
|
+
}
|
|
59
|
+
case RESERVED_HANDLE_SEND_TOOL: {
|
|
60
|
+
const { ref, message } = parseSendArgs(args);
|
|
61
|
+
return await registry.send(ref, message, backend);
|
|
62
|
+
}
|
|
63
|
+
case RESERVED_HANDLE_CANCEL_TOOL:
|
|
64
|
+
return await registry.cancel(parseRefArgs(args, "control.cancel()"), backend);
|
|
65
|
+
default:
|
|
66
|
+
return assertNever(toolName);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function assertNever(value: never): never {
|
|
71
|
+
throw new TypeError(`Unsupported handle tool: ${String(value)}`);
|
|
72
|
+
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import type { AgentToolResult } from "@code-yeongyu/senpi";
|
|
1
|
+
import type { AgentToolResult, EvalHandleHost } from "@code-yeongyu/senpi";
|
|
2
2
|
import { RESERVED_AGENT_TOOL, RESERVED_OUTPUT_TOOL, RESERVED_SCHEMA_TOOL } from "../bridge/reserved.ts";
|
|
3
|
+
import type { HandleRegistry } from "../handles/handle-registry.ts";
|
|
3
4
|
import type { EvalStatusEvent, ExecuteTool } from "../tool/types.ts";
|
|
4
5
|
import { type AgentExecuteTool, runEvalAgent } from "./agent-bridge.ts";
|
|
6
|
+
import { isHandleToolName, runHandleTool } from "./handle-bridge.ts";
|
|
5
7
|
import { type MarshalledToolResult, type OutputExecuteTool, runEvalOutput } from "./output-bridge.ts";
|
|
6
8
|
import { type EvalSchemaToolInfo, runEvalSchema } from "./schema-bridge.ts";
|
|
7
9
|
|
|
@@ -15,6 +17,10 @@ export interface ReservedDispatchContext {
|
|
|
15
17
|
readonly signal: AbortSignal | undefined;
|
|
16
18
|
readonly emitStatus: (event: EvalStatusEvent) => void;
|
|
17
19
|
readonly marshalToolResult: (result: AgentToolResult<unknown>) => MarshalledToolResult;
|
|
20
|
+
/** The session generation's handle registry; `wait()`/`handle()` fail with `eval_wait_unavailable` without one. */
|
|
21
|
+
readonly handles?: HandleRegistry;
|
|
22
|
+
/** `ctx.evalHandleHost` at dispatch time; agent/workpool refs need it, completion refs do not. */
|
|
23
|
+
readonly evalHandleHost?: EvalHandleHost;
|
|
18
24
|
}
|
|
19
25
|
|
|
20
26
|
class SchemaUnavailableError extends Error {
|
|
@@ -26,10 +32,22 @@ class SchemaUnavailableError extends Error {
|
|
|
26
32
|
}
|
|
27
33
|
|
|
28
34
|
export function isReservedToolName(toolName: string): boolean {
|
|
29
|
-
return
|
|
35
|
+
return (
|
|
36
|
+
toolName === RESERVED_AGENT_TOOL ||
|
|
37
|
+
toolName === RESERVED_OUTPUT_TOOL ||
|
|
38
|
+
toolName === RESERVED_SCHEMA_TOOL ||
|
|
39
|
+
isHandleToolName(toolName)
|
|
40
|
+
);
|
|
30
41
|
}
|
|
31
42
|
|
|
32
43
|
export async function runReservedTool(toolName: string, context: ReservedDispatchContext): Promise<unknown> {
|
|
44
|
+
if (isHandleToolName(toolName)) {
|
|
45
|
+
return await runHandleTool(toolName, context.args, {
|
|
46
|
+
registry: context.handles,
|
|
47
|
+
host: context.evalHandleHost,
|
|
48
|
+
signal: context.signal,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
33
51
|
if (toolName === RESERVED_AGENT_TOOL) {
|
|
34
52
|
return await runEvalAgent(context.args, {
|
|
35
53
|
callId: context.callId,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { KernelPreludeContribution } from "@code-yeongyu/senpi";
|
|
2
2
|
import { type Static, Type } from "typebox";
|
|
3
3
|
import { Check, Errors } from "typebox/value";
|
|
4
|
+
import { virtualEvalSchema } from "./eval-virtual-schemas.ts";
|
|
4
5
|
|
|
5
6
|
const schemaArgsSchema = Type.Object(
|
|
6
7
|
{ name: Type.Optional(Type.String({ minLength: 1 })) },
|
|
@@ -46,6 +47,9 @@ export function runEvalSchema(args: unknown, options: RunEvalSchemaOptions): Eva
|
|
|
46
47
|
const tools = options.listTools();
|
|
47
48
|
if (parsed.name === undefined) return { tools: tools.map((tool) => tool.name) };
|
|
48
49
|
|
|
50
|
+
// Documentation-only entries; they are not tools and the listing above stays the ordinary catalog.
|
|
51
|
+
const virtual = virtualEvalSchema(parsed.name);
|
|
52
|
+
if (virtual !== undefined) return virtual;
|
|
49
53
|
const match = tools.find((tool) => tool.name === parsed.name);
|
|
50
54
|
if (match) return { name: match.name, description: match.description, parameters: match.parameters };
|
|
51
55
|
throw new SchemaUnknownToolError(parsed.name, nearestNames(parsed.name, tools));
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { ExtensionContext, HandleRef } from "@code-yeongyu/senpi";
|
|
2
|
+
import type { HandleRegistry } from "../handles/handle-registry.ts";
|
|
3
|
+
import type { CompletionRequest, CompletionResult } from "./handler.ts";
|
|
4
|
+
|
|
5
|
+
/** What `completion(prompt, {handle: true})` returns: a saved reference every prelude turns into a control view. */
|
|
6
|
+
export interface CompletionHandleRecord {
|
|
7
|
+
readonly kind: "completion";
|
|
8
|
+
readonly id: string;
|
|
9
|
+
readonly run_epoch: number;
|
|
10
|
+
readonly handle: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface StartCompletionHandleOptions {
|
|
14
|
+
readonly registry: HandleRegistry;
|
|
15
|
+
readonly request: CompletionRequest;
|
|
16
|
+
readonly complete: (request: CompletionRequest, ctx: ExtensionContext) => Promise<CompletionResult>;
|
|
17
|
+
readonly ctx: ExtensionContext;
|
|
18
|
+
/** The creating cell's hard deadline (absolute ms); the completion is aborted at it. */
|
|
19
|
+
readonly deadlineMs: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const COMPLETION_TIERS: ReadonlySet<string> = new Set(["smol", "default", "slow"]);
|
|
23
|
+
|
|
24
|
+
class CompletionHandleArgumentsError extends Error {
|
|
25
|
+
readonly name = "CompletionHandleArgumentsError";
|
|
26
|
+
readonly code = "eval_handle_invalid_arguments";
|
|
27
|
+
|
|
28
|
+
constructor(detail: string) {
|
|
29
|
+
super(`eval_handle_invalid_arguments: completion(prompt, {handle: true}) ${detail}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** True when the completion call opted into a handle (`{handle: true}` in its options). */
|
|
34
|
+
export function wantsCompletionHandle(opts: unknown): boolean {
|
|
35
|
+
return typeof opts === "object" && opts !== null && "handle" in opts && opts.handle === true;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Preflight-validates the request, then starts the completion in the session registry and returns its
|
|
40
|
+
* reference at once. Provider failures surface in the handle's outcome, never here.
|
|
41
|
+
*/
|
|
42
|
+
export function startCompletionHandle(options: StartCompletionHandleOptions): CompletionHandleRecord {
|
|
43
|
+
const request = withoutHandleOption(options.request);
|
|
44
|
+
if (request.prompt.trim().length === 0) throw new CompletionHandleArgumentsError("needs a non-empty prompt");
|
|
45
|
+
const tier = request.model ?? tierFromOpts(request.opts);
|
|
46
|
+
if (tier !== undefined && !COMPLETION_TIERS.has(tier)) {
|
|
47
|
+
throw new CompletionHandleArgumentsError(`got model tier "${tier}"; expected "smol", "default", or "slow"`);
|
|
48
|
+
}
|
|
49
|
+
const ref: HandleRef = options.registry.startCompletion({
|
|
50
|
+
deadlineMs: options.deadlineMs,
|
|
51
|
+
run: async (signal) => replyValue(await options.complete(request, { ...options.ctx, signal })),
|
|
52
|
+
});
|
|
53
|
+
return { kind: "completion", id: ref.id, run_epoch: ref.run_epoch, handle: `completion://${ref.id}` };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function replyValue(result: CompletionResult): unknown {
|
|
57
|
+
return "value" in result ? result.value : result.text;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function tierFromOpts(opts: unknown): string | undefined {
|
|
61
|
+
if (typeof opts !== "object" || opts === null || !("model" in opts)) return undefined;
|
|
62
|
+
return typeof opts.model === "string" ? opts.model : undefined;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function withoutHandleOption(request: CompletionRequest): CompletionRequest {
|
|
66
|
+
if (typeof request.opts !== "object" || request.opts === null || !("handle" in request.opts)) return request;
|
|
67
|
+
const { handle: _handle, ...opts } = request.opts;
|
|
68
|
+
return { ...request, opts };
|
|
69
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { ExtensionContext } from "@code-yeongyu/senpi";
|
|
2
2
|
import type { KernelToHostMessage } from "../bridge/protocol.ts";
|
|
3
|
+
import type { HandleRegistry } from "../handles/handle-registry.ts";
|
|
3
4
|
import type { EvalKernel } from "../tool/types.ts";
|
|
5
|
+
import { replyValue, startCompletionHandle, wantsCompletionHandle } from "./handle.ts";
|
|
4
6
|
import type { CompletionRequest, CompletionResult } from "./handler.ts";
|
|
5
7
|
|
|
6
8
|
export interface CompletionToolCallOptions {
|
|
@@ -9,20 +11,28 @@ export interface CompletionToolCallOptions {
|
|
|
9
11
|
readonly complete: (request: CompletionRequest, ctx: ExtensionContext) => Promise<CompletionResult>;
|
|
10
12
|
readonly ctx: ExtensionContext;
|
|
11
13
|
readonly isActive: () => boolean;
|
|
14
|
+
/** Needed only for `completion(prompt, {handle: true})`; without it that option is refused. */
|
|
15
|
+
readonly handles?: HandleRegistry;
|
|
16
|
+
/** The creating cell's hard deadline (absolute ms) bounding a completion handle. */
|
|
17
|
+
readonly hardDeadlineMs?: number;
|
|
12
18
|
}
|
|
13
19
|
|
|
14
20
|
export type CompletionToolCallSummary = { readonly ok: true } | { readonly ok: false; readonly error: string };
|
|
15
21
|
|
|
16
22
|
export async function handleCompletionToolCall(options: CompletionToolCallOptions): Promise<CompletionToolCallSummary> {
|
|
17
23
|
try {
|
|
18
|
-
const
|
|
24
|
+
const request = toCompletionRequest(options.message.args);
|
|
25
|
+
const value = wantsCompletionHandle(request.opts)
|
|
26
|
+
? startCompletionHandle({
|
|
27
|
+
registry: requireRegistry(options.handles),
|
|
28
|
+
request,
|
|
29
|
+
complete: options.complete,
|
|
30
|
+
ctx: options.ctx,
|
|
31
|
+
deadlineMs: options.hardDeadlineMs ?? Number.POSITIVE_INFINITY,
|
|
32
|
+
})
|
|
33
|
+
: replyValue(await options.complete(request, options.ctx));
|
|
19
34
|
if (!options.isActive()) return { ok: false, error: "completion() result ignored after eval finalization" };
|
|
20
|
-
options.kernel.deliverToolReply({
|
|
21
|
-
type: "tool-reply",
|
|
22
|
-
callId: options.message.callId,
|
|
23
|
-
ok: true,
|
|
24
|
-
value: completionReplyValue(result),
|
|
25
|
-
});
|
|
35
|
+
options.kernel.deliverToolReply({ type: "tool-reply", callId: options.message.callId, ok: true, value });
|
|
26
36
|
return { ok: true };
|
|
27
37
|
} catch (error) {
|
|
28
38
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -31,12 +41,25 @@ export async function handleCompletionToolCall(options: CompletionToolCallOption
|
|
|
31
41
|
type: "tool-reply",
|
|
32
42
|
callId: options.message.callId,
|
|
33
43
|
ok: false,
|
|
34
|
-
error: {
|
|
44
|
+
error: {
|
|
45
|
+
message,
|
|
46
|
+
...(error instanceof Error && "code" in error && typeof error.code === "string"
|
|
47
|
+
? { code: error.code }
|
|
48
|
+
: {}),
|
|
49
|
+
},
|
|
35
50
|
});
|
|
36
51
|
return { ok: false, error: message };
|
|
37
52
|
}
|
|
38
53
|
}
|
|
39
54
|
|
|
55
|
+
function requireRegistry(handles: HandleRegistry | undefined): HandleRegistry {
|
|
56
|
+
if (handles !== undefined) return handles;
|
|
57
|
+
throw Object.assign(
|
|
58
|
+
new Error("eval_wait_unavailable: completion handles are not available before the session starts"),
|
|
59
|
+
{ code: "eval_wait_unavailable" },
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
40
63
|
function toCompletionRequest(value: unknown): CompletionRequest {
|
|
41
64
|
if (typeof value === "object" && value !== null && "prompt" in value && typeof value.prompt === "string") {
|
|
42
65
|
return {
|
|
@@ -49,7 +72,3 @@ function toCompletionRequest(value: unknown): CompletionRequest {
|
|
|
49
72
|
}
|
|
50
73
|
throw new Error("completion() received invalid arguments");
|
|
51
74
|
}
|
|
52
|
-
|
|
53
|
-
function completionReplyValue(result: CompletionResult): unknown {
|
|
54
|
-
return "value" in result ? result.value : result.text;
|
|
55
|
-
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { ExtensionContext } from "@code-yeongyu/senpi";
|
|
2
|
+
import type { BridgeHttpCompletionRequest } from "../bridge/http-server.ts";
|
|
3
|
+
import { startCompletionHandle, wantsCompletionHandle } from "../completion/handle.ts";
|
|
4
|
+
import type { CompletionRequest } from "../completion/handler.ts";
|
|
5
|
+
import { resolveHardLimitSeconds } from "../config/settings.ts";
|
|
6
|
+
import type { CreateCodemodeSessionManagerOptions } from "./session-manager-contract.ts";
|
|
7
|
+
|
|
8
|
+
export interface BridgeDispatchContext {
|
|
9
|
+
/** The context of the cell that last reached the manager; the handle host and completion owner are read from it. */
|
|
10
|
+
readonly context: ExtensionContext | undefined;
|
|
11
|
+
readonly contextFor: (signal: AbortSignal) => ExtensionContext;
|
|
12
|
+
readonly requireContext: () => ExtensionContext;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* `completion(prompt, handle=True)` from a subprocess kernel: the handle lives in the session registry and is
|
|
17
|
+
* bounded by the hard limit measured from this request, the tightest bound the bridge knows for its cell.
|
|
18
|
+
*/
|
|
19
|
+
export async function dispatchBridgeCompletion(
|
|
20
|
+
options: CreateCodemodeSessionManagerOptions,
|
|
21
|
+
request: BridgeHttpCompletionRequest,
|
|
22
|
+
dispatch: BridgeDispatchContext,
|
|
23
|
+
): Promise<unknown> {
|
|
24
|
+
const completion: CompletionRequest = { prompt: request.prompt, opts: request.opts };
|
|
25
|
+
if (!wantsCompletionHandle(request.opts)) {
|
|
26
|
+
return await options.complete(completion, dispatch.contextFor(request.signal));
|
|
27
|
+
}
|
|
28
|
+
const registry = options.handles;
|
|
29
|
+
if (registry === undefined) {
|
|
30
|
+
throw Object.assign(new Error("eval_wait_unavailable: completion handles are not available in this session"), {
|
|
31
|
+
code: "eval_wait_unavailable",
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
return startCompletionHandle({
|
|
35
|
+
registry,
|
|
36
|
+
request: completion,
|
|
37
|
+
complete: options.complete,
|
|
38
|
+
ctx: dispatch.requireContext(),
|
|
39
|
+
deadlineMs: Date.now() + resolveHardLimitSeconds(options.settings) * 1_000,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { EvalHandleHost } from "@code-yeongyu/senpi";
|
|
1
2
|
import { isReservedToolName, runReservedTool } from "../bridges/reserved-dispatch.ts";
|
|
2
3
|
import { defaultCodemodeSettings } from "../config/settings.ts";
|
|
3
4
|
import { marshalToolResult } from "../tool/image.ts";
|
|
@@ -16,8 +17,9 @@ export interface BridgeToolCallRequest {
|
|
|
16
17
|
// and ordinary tool results are marshalled to { text, images, details, hasError } — the raw
|
|
17
18
|
// { content } shape left python cells unable to reach tool.read image blocks.
|
|
18
19
|
export async function routeBridgeToolCall(
|
|
19
|
-
options: Pick<CreateCodemodeSessionManagerOptions, "executeTool" | "listTools" | "settings">,
|
|
20
|
+
options: Pick<CreateCodemodeSessionManagerOptions, "executeTool" | "listTools" | "settings" | "handles">,
|
|
20
21
|
request: BridgeToolCallRequest,
|
|
22
|
+
evalHandleHost?: EvalHandleHost,
|
|
21
23
|
): Promise<unknown> {
|
|
22
24
|
if (!isReservedToolName(request.toolName)) {
|
|
23
25
|
return marshalToolResult(await options.executeTool(request.toolName, request.args, { signal: request.signal }));
|
|
@@ -33,5 +35,7 @@ export async function routeBridgeToolCall(
|
|
|
33
35
|
signal: request.signal,
|
|
34
36
|
emitStatus: () => {},
|
|
35
37
|
marshalToolResult,
|
|
38
|
+
...(options.handles === undefined ? {} : { handles: options.handles }),
|
|
39
|
+
...(evalHandleHost === undefined ? {} : { evalHandleHost }),
|
|
36
40
|
});
|
|
37
41
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as os from "node:os";
|
|
2
|
+
import { jsRuntimeLabel } from "./runtime-info.ts";
|
|
3
|
+
|
|
4
|
+
/** The one-line host description the eval prompt shows (platform, CPU, cores, JS runtime). */
|
|
5
|
+
export function hostLine(): string {
|
|
6
|
+
const cpu = os.cpus()[0]?.model?.trim();
|
|
7
|
+
return [`${os.platform()} ${os.arch()}`, cpu, `${os.availableParallelism()} cores`, jsRuntimeLabel()]
|
|
8
|
+
.filter((part): part is string => !!part)
|
|
9
|
+
.join(" \u00b7 ");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** The model id carried by a `model_select` event, when it has one. */
|
|
13
|
+
export function modelIdFrom(event: unknown): string | undefined {
|
|
14
|
+
if (typeof event !== "object" || event === null || !("model" in event)) return undefined;
|
|
15
|
+
const model = event.model;
|
|
16
|
+
if (typeof model !== "object" || model === null || !("id" in model)) return undefined;
|
|
17
|
+
return typeof model.id === "string" ? model.id : undefined;
|
|
18
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** Migration guidance for the tools eval replaced; the host returns it when a model calls them. */
|
|
2
|
+
export interface RemovedToolHintRegistrar {
|
|
3
|
+
registerRemovedToolHint(name: string, hint: string): void;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export const WAIT_REMOVED_TOOL_HINT =
|
|
7
|
+
"wait is not a tool; inside a cell use the wait(handles) helper (tool_schema('eval:wait')), or let detached cells notify.";
|
|
8
|
+
|
|
9
|
+
export function registerRemovedToolHints(pi: RemovedToolHintRegistrar): void {
|
|
10
|
+
pi.registerRemovedToolHint(
|
|
11
|
+
"exec",
|
|
12
|
+
'exec was removed; use eval({ language: "js", code }) instead. Long eval cells detach on their own and notify when complete.',
|
|
13
|
+
);
|
|
14
|
+
pi.registerRemovedToolHint("wait", WAIT_REMOVED_TOOL_HINT);
|
|
15
|
+
}
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
type ResolvedCodemodeSettings,
|
|
9
9
|
resolveEnabledLanguages,
|
|
10
10
|
} from "../config/settings.ts";
|
|
11
|
+
import type { HandleRegistry } from "../handles/handle-registry.ts";
|
|
11
12
|
import {
|
|
12
13
|
createInterpreterDetector,
|
|
13
14
|
getInterpreterAvailability,
|
|
@@ -54,6 +55,7 @@ export async function createRuntime(
|
|
|
54
55
|
event: unknown,
|
|
55
56
|
complete: (request: CompletionRequest, ctx: ExtensionContext) => Promise<CompletionResult>,
|
|
56
57
|
options: RuntimeFactoryOptions,
|
|
58
|
+
handles?: HandleRegistry,
|
|
57
59
|
): Promise<SessionRuntime> {
|
|
58
60
|
const loaded = await loadCodemodeSettings({ cwd: ctx.cwd });
|
|
59
61
|
const settings: ResolvedCodemodeSettings = {
|
|
@@ -81,6 +83,7 @@ export async function createRuntime(
|
|
|
81
83
|
executeTool,
|
|
82
84
|
listTools: () => pi.getAllTools(),
|
|
83
85
|
complete,
|
|
86
|
+
...(handles === undefined ? {} : { handles }),
|
|
84
87
|
});
|
|
85
88
|
return {
|
|
86
89
|
sessionId,
|
|
@@ -2,6 +2,7 @@ import type { ExtensionContext } from "@code-yeongyu/senpi";
|
|
|
2
2
|
import type { EvalSchemaToolInfo } from "../bridges/schema-bridge.ts";
|
|
3
3
|
import type { CompletionRequest, CompletionResult } from "../completion/handler.ts";
|
|
4
4
|
import type { CodemodeSettings } from "../config/settings.ts";
|
|
5
|
+
import type { HandleRegistry } from "../handles/handle-registry.ts";
|
|
5
6
|
import type { InterpreterAvailability } from "../interpreters/detect.ts";
|
|
6
7
|
import type { SessionEnvironment } from "../kernels/session-env.ts";
|
|
7
8
|
import type { EvalKernelManager, ExecuteTool } from "../tool/types.ts";
|
|
@@ -39,4 +40,6 @@ export interface CreateCodemodeSessionManagerOptions {
|
|
|
39
40
|
readonly executeTool: ExecuteTool;
|
|
40
41
|
readonly listTools?: () => readonly EvalSchemaToolInfo[];
|
|
41
42
|
readonly complete: (request: CompletionRequest, ctx: ExtensionContext) => Promise<CompletionResult>;
|
|
43
|
+
/** The session generation's handle registry; subprocess kernels reach `wait()`/`handle()` through the bridge. */
|
|
44
|
+
readonly handles?: HandleRegistry;
|
|
42
45
|
}
|
|
@@ -8,6 +8,7 @@ import { collectOrphanedChildren } from "../host-sdk.ts";
|
|
|
8
8
|
import { JavaScriptKernel } from "../kernels/js/context-manager.ts";
|
|
9
9
|
import type { KernelLifecycle } from "../kernels/shared/kernel-death.ts";
|
|
10
10
|
import type { EvalKernel, EvalLanguage } from "../tool/types.ts";
|
|
11
|
+
import { type BridgeDispatchContext, dispatchBridgeCompletion } from "./bridge-dispatch.ts";
|
|
11
12
|
import { type BridgeToolCallRequest, routeBridgeToolCall } from "./bridge-tool-call.ts";
|
|
12
13
|
import { parkWhenIdle } from "./idle-parking-kernel.ts";
|
|
13
14
|
import {
|
|
@@ -73,16 +74,23 @@ class DefaultCodemodeSessionManager implements CodemodeSessionManager {
|
|
|
73
74
|
}
|
|
74
75
|
|
|
75
76
|
async start(): Promise<void> {
|
|
77
|
+
const manager = this;
|
|
78
|
+
const dispatch: BridgeDispatchContext = {
|
|
79
|
+
get context() {
|
|
80
|
+
return manager.#context;
|
|
81
|
+
},
|
|
82
|
+
contextFor: (signal) => this.#contextFor(signal),
|
|
83
|
+
requireContext: () => this.#requireContext(),
|
|
84
|
+
};
|
|
76
85
|
this.#bridge = await startBridgeServer({
|
|
77
86
|
onCall: async (request) => await this.#call(request),
|
|
78
87
|
onEmit: async () => undefined,
|
|
79
|
-
onCompletion: async (request) =>
|
|
80
|
-
this.#options.complete({ prompt: request.prompt, opts: request.opts }, this.#contextFor(request.signal)),
|
|
88
|
+
onCompletion: async (request) => await dispatchBridgeCompletion(this.#options, request, dispatch),
|
|
81
89
|
});
|
|
82
90
|
}
|
|
83
91
|
|
|
84
92
|
async #call(request: BridgeToolCallRequest): Promise<unknown> {
|
|
85
|
-
return await routeBridgeToolCall(this.#options, request);
|
|
93
|
+
return await routeBridgeToolCall(this.#options, request, this.#context?.evalHandleHost);
|
|
86
94
|
}
|
|
87
95
|
|
|
88
96
|
async getKernel(language: EvalLanguage, onMessage: (message: KernelToHostMessage) => void): Promise<EvalKernel> {
|
|
@@ -280,9 +288,14 @@ class DefaultCodemodeSessionManager implements CodemodeSessionManager {
|
|
|
280
288
|
return names;
|
|
281
289
|
}
|
|
282
290
|
|
|
283
|
-
#
|
|
291
|
+
#requireContext(): ExtensionContext {
|
|
284
292
|
const ctx = this.#context;
|
|
285
293
|
if (!ctx) throw new CodemodeContextUnavailableError();
|
|
294
|
+
return ctx;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
#contextFor(signal: AbortSignal): ExtensionContext {
|
|
298
|
+
const ctx = this.#requireContext();
|
|
286
299
|
return { ...ctx, signal: ctx.signal ? AbortSignal.any([ctx.signal, signal]) : signal };
|
|
287
300
|
}
|
|
288
301
|
}
|