better-dsh 0.2.3-c → 0.2.3-d

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.
@@ -0,0 +1,33 @@
1
+ Run one Python cell on a session-persistent scripting pad. One `eval` call = one cell; the pad's state persists across calls and turns — variables, imports, and definitions from earlier cells stay alive.
2
+
3
+ Work incrementally: imports → define → test → use, each its own cell. Re-run setup only after `reset` or a crash.
4
+
5
+ ## Arguments
6
+
7
+ - `cell` (required): one Python program body. Top-level `await` works; top-level `return` is a SyntaxError — the cell runs in module scope (REPL semantics, not a function body).
8
+ - `description` (required): a short summary of what the cell does — shown as the call's title in the UI.
9
+ - `timeout` (optional): wall-clock budget in seconds; the cell is interrupted (then force-stopped) when exceeded. Omit for the runtime default.
10
+ - `reset` (optional): restart the pad EMPTY first — earlier state is discarded.
11
+
12
+ ## Direct calls vs cells
13
+
14
+ Payload-shaped work (one long read, one edit, one command) → direct tool call. Logic-shaped work (loops, conditions, fan-out, composing many tool results into one step) → an `eval` cell. Only what the cell prints or returns comes back — curate it.
15
+
16
+ Delegation: `agent` is the unified agent-spawn entry; `subagent` is its native alias — both delegate through the same runtime, so call either.
17
+
18
+ ## Calling tools from a cell
19
+
20
+ Every session tool is callable from a cell as `await tool.<name>(args)` with ONE positional argument: `args` is the tool's parameter object written as a Python dict literal, same field names (`true`/`false`/`null` become `True`/`False`/`None`). A failed call raises `ToolCallError` with `.toolName` naming the tool. Tool names that are not plain identifiers (e.g. contain hyphens) have no `tool.<name>` member — call those as direct tool calls.
21
+
22
+ ```python
23
+ print(await tool.read({"path": "docs/README.md", "limit": 5}))
24
+
25
+ r = await tool.bash({"command": "ls -la src/", "description": "List source directory"})
26
+ print(r["stdout"]["text"])
27
+
28
+ import asyncio
29
+ matches, files = await asyncio.gather(
30
+ tool.grep({"pattern": "TODO", "path": "src"}),
31
+ tool.glob({"pattern": "**/*.ts", "path": "src"}),
32
+ )
33
+ ```
@@ -69,7 +69,7 @@ const zh = {
69
69
  };
70
70
 
71
71
  //#endregion
72
- //#region \0dsh-css:/home/u1/workspaces/dashr/upstream/deepseek-harness/packages/better-dsh/better-dsh/src/failover/client/FailoverRow.module.css.mjs
72
+ //#region \0dsh-css:/home/u1/workspaces/dashr/dashr/src/failover/client/FailoverRow.module.css.mjs
73
73
  const css = "._440e1d7b_row {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 12px 0;\n}\n\n._9865b509_title {\n font-weight: 600;\n font-size: 14px;\n}\n\n._3cfec826_slots {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n._70954771_slot {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n._ba1fd839_slotLabel {\n min-width: 88px;\n flex-shrink: 0;\n font-size: 13px;\n color: var(--dsw-alias-text-secondary, #666);\n}\n\n._11c2662d_select {\n flex: 1;\n max-width: 280px;\n min-width: 0;\n}\n\n._b7c358f9_alert {\n color: var(--dsw-alias-danger, #c0392b);\n font-size: 12px;\n}\n";
74
74
  const tagId = "better-dsh/FailoverRow.module.css";
75
75
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
package/lib/index.d.ts CHANGED
@@ -1,9 +1,8 @@
1
- import { t as DASHRSdkSchema } from "./py-sdk-BCaOGYz7.js";
2
1
  import { Context, Service } from "@deepseek-ai/cordis";
3
2
  import z from "@deepseek-ai/schemastery";
4
3
  import { ToolDefinition, ToolExecutionInput, ToolRunContext, ToolRuntime } from "@deepseek-ai/dsh-tools";
5
4
  import { ContentBlock, HarnessError } from "@deepseek-ai/dsh-llm";
6
- import { ScopeKey, Scoped } from "@deepseek-ai/dsh-scope";
5
+ import { Scoped } from "@deepseek-ai/dsh-scope";
7
6
  import { Agent } from "@deepseek-ai/dsh-agent";
8
7
 
9
8
  //#region src/vendored/types.d.ts
@@ -616,10 +615,6 @@ declare const MASKED_TOOL_NAMES: ReadonlySet<string>;
616
615
  * bridges that read captured definitions.
617
616
  */
618
617
  declare const WIRE_MASKED_NAMES: ReadonlySet<string>;
619
- /** The `dashr:control-prompt` section order: the FIRST section in the 100–199 tool-guidance band, so the cell paradigm is taught before the Tool Catalog renders its signatures. */
620
- declare const CONTROL_SECTION_ORDER = 100;
621
- /** The `dashr:tool-catalog` section order: the 100–199 tool-guidance band's SDK position, matching upstream `tools:sdk`. */
622
- declare const SDK_SECTION_ORDER = 150;
623
618
  /**
624
619
  * The `dashr:escalation-guidance` CONTEXT order: sits inside the runtime-context snapshot
625
620
  * band between upstream `approval:policy` (115) and `subagent:delegation` (120) — the
@@ -721,17 +716,6 @@ interface RunCellBridgeOptions {
721
716
  * @returns the registry-ready definition.
722
717
  */
723
718
  declare function createRunCellTool(registry: ToolRuntime, options: RunCellBridgeOptions): ToolDefinition;
724
- /**
725
- * Collect one calling scope's bridge-declaration schemas through the
726
- * registry's public projection APIs: `schemas(scope)` for the model-facing
727
- * view (scoped tools join, restrictions apply — the wire mask is a
728
- * registry-level restriction installed at session-start, so every masked
729
- * name is ALREADY absent from this projection; no second name filter
730
- * exists to drift), `get(name, scope)` for the canonical output schema,
731
- * snapshotted so a live definition cannot mutate under the render. `eval`
732
- * itself is excluded — it is the transport, not a binding.
733
- */
734
- declare function collectSdkSchemas(registry: ToolRuntime, scope?: ScopeKey): DASHRSdkSchema[];
735
719
  declare function apply(ctx: Context, config: Config): void;
736
720
  declare const _default: {
737
721
  name: string;
@@ -740,4 +724,4 @@ declare const _default: {
740
724
  apply: typeof apply;
741
725
  };
742
726
  //#endregion
743
- export { CONTROL_SECTION_ORDER, Config, DASHRRunFailedError, DashrRuntime, ESCALATION_GUIDANCE_ORDER, EVAL_NAME, MASKED_TOOL_NAMES, ReplDispatchLog, ReplRuntime, RunCellBridgeOptions, type Config$1 as RuntimeConfig, SDK_SECTION_ORDER, WIRE_MASKED_NAMES, apply, collectSdkSchemas, createRunCellTool, _default as default, inject, name, resolveMaxParallelSubCalls };
727
+ export { Config, DASHRRunFailedError, DashrRuntime, ESCALATION_GUIDANCE_ORDER, EVAL_NAME, MASKED_TOOL_NAMES, ReplDispatchLog, ReplRuntime, RunCellBridgeOptions, type Config$1 as RuntimeConfig, WIRE_MASKED_NAMES, apply, createRunCellTool, _default as default, inject, name, resolveMaxParallelSubCalls };
package/lib/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { a as DUNDER_MEMBER, c as RESERVED_ERROR_MEMBERS, l as ReplRuntime, n as isFlatBindableName, o as PORTABLE_RESERVED_WORDS, r as renderReplBridgeInstructions, s as RESERVED_BINDING_GLOBALS } from "./py-sdk-CbgYiX8O.js";
1
+ import { a as RESERVED_ERROR_MEMBERS, i as RESERVED_BINDING_GLOBALS, n as DUNDER_MEMBER, o as ReplRuntime, r as PORTABLE_RESERVED_WORDS, t as isFlatBindableName } from "./py-sdk-Chvy92MB.js";
2
2
  import { s as resolveKernelEnv } from "./kernel-env-hxaihi9C.js";
3
3
  import { createRequire } from "node:module";
4
4
  import { Context } from "@deepseek-ai/cordis";
@@ -10961,8 +10961,6 @@ function installWebTrust(ctx, config) {
10961
10961
 
10962
10962
  //#endregion
10963
10963
  //#region src/index.ts
10964
- /** The control prompt text, loaded at module time from the sibling markdown file (editable without touching TS). */
10965
- const CONTROL_PROMPT_TEXT = readFileSync(new URL("../control-prompt.md", import.meta.url), "utf8");
10966
10964
  /** Cordis plugin name. */
10967
10965
  const name = "dashr-repl";
10968
10966
  /**
@@ -11037,10 +11035,6 @@ const TOOL_CALL_ERROR_CLASS = {
11037
11035
  name: "ToolCallError",
11038
11036
  memberNameProperty: "toolName"
11039
11037
  };
11040
- /** The `dashr:control-prompt` section order: the FIRST section in the 100–199 tool-guidance band, so the cell paradigm is taught before the Tool Catalog renders its signatures. */
11041
- const CONTROL_SECTION_ORDER = 100;
11042
- /** The `dashr:tool-catalog` section order: the 100–199 tool-guidance band's SDK position, matching upstream `tools:sdk`. */
11043
- const SDK_SECTION_ORDER = 150;
11044
11038
  /**
11045
11039
  * The `dashr:escalation-guidance` CONTEXT order: sits inside the runtime-context snapshot
11046
11040
  * band between upstream `approval:policy` (115) and `subagent:delegation` (120) — the
@@ -11049,12 +11043,12 @@ const SDK_SECTION_ORDER = 150;
11049
11043
  */
11050
11044
  const ESCALATION_GUIDANCE_ORDER = 116;
11051
11045
  /**
11052
- * The `eval` tool description the model sees: cell semantics — the
11053
- * persistent kernel — stated up front, unlike upstream's one-shot
11054
- * `PYTHON_FLAVOR` (blueprint §1.1: DASHR is channel ② state codification,
11055
- * and the model's Code-Interpreter prior matches THIS contract).
11046
+ * The `eval` tool's whole model-facing description, loaded at module time
11047
+ * from the sibling markdown file (editable without touching TS). It carries
11048
+ * ALL REPL guidance — cell semantics, the direct-vs-cell decision rule, the
11049
+ * delegation alias note, and the one-form bridge at the end.
11056
11050
  */
11057
- const EVAL_DESCRIPTION = "Execute one Python cell on a session-persistent scripting pad. Takes two required arguments: `cell`, one Python program body (top-level `await` works; top-level `return` is a SyntaxError — the cell runs in module scope; variables, imports, and definitions from earlier cells are still alive), and `description`, a short summary of what the cell does; optional `timeout` (seconds) bounds the wall-clock and optional `reset` restarts the pad empty. Call tools as `await tool.name(args)` functions per the declarations in the system prompt. Only what you print or return comes back — curate it.";
11051
+ const EVAL_DESCRIPTION = readFileSync(new URL("../eval-description.md", import.meta.url), "utf8");
11058
11052
  /** The `cell` parameter's model-facing description. */
11059
11053
  const EVAL_CELL_PARAM_DESCRIPTION = "The cell: one Python program body for the session-persistent scripting pad (top-level `await` works; top-level `return` is a SyntaxError — the cell runs in module scope).";
11060
11054
  /**
@@ -11517,36 +11511,8 @@ function createRunCellTool(registry, options) {
11517
11511
  });
11518
11512
  }
11519
11513
  /**
11520
- * Collect one calling scope's bridge-declaration schemas through the
11521
- * registry's public projection APIs: `schemas(scope)` for the model-facing
11522
- * view (scoped tools join, restrictions apply — the wire mask is a
11523
- * registry-level restriction installed at session-start, so every masked
11524
- * name is ALREADY absent from this projection; no second name filter
11525
- * exists to drift), `get(name, scope)` for the canonical output schema,
11526
- * snapshotted so a live definition cannot mutate under the render. `eval`
11527
- * itself is excluded — it is the transport, not a binding.
11528
- */
11529
- function collectSdkSchemas(registry, scope) {
11530
- const collected = [];
11531
- for (const schema of registry.schemas(scope)) {
11532
- if (schema.name === EVAL_NAME) continue;
11533
- const definition = registry.get(schema.name, scope);
11534
- if (definition === void 0) continue;
11535
- const output = snapshotJsonValue(definition.output.schema);
11536
- if (output === void 0) continue;
11537
- collected.push({
11538
- name: schema.name,
11539
- description: schema.description,
11540
- parameters: schema.parameters,
11541
- output
11542
- });
11543
- }
11544
- return collected;
11545
- }
11546
- /**
11547
11514
  * Declare the DASHR cell presentation for every agent this composition
11548
- * covers: the `eval` transport tool, the `dashr:tool-catalog` prompt
11549
- * section, the model-direct collapse guard, and the assembly filter that
11515
+ * covers: the `eval` transport tool, the model-direct collapse guard, and the assembly filter that
11550
11516
  * leaves `eval` the only contributed tool schema.
11551
11517
  *
11552
11518
  * Mount through a preset's standing scope (`agent.cordis.yml` include row);
@@ -11661,16 +11627,6 @@ function apply(ctx, config) {
11661
11627
  logger.warn(`dashr-repl: wire mask failed for agent ${agent.id}: ${error instanceof Error ? error.message : String(error)}`);
11662
11628
  }
11663
11629
  });
11664
- systemPrompt.section({
11665
- name: "dashr:control-prompt",
11666
- order: CONTROL_SECTION_ORDER,
11667
- text: CONTROL_PROMPT_TEXT
11668
- });
11669
- systemPrompt.section({
11670
- name: "dashr:tool-catalog",
11671
- order: SDK_SECTION_ORDER,
11672
- text: (context) => renderReplBridgeInstructions(collectSdkSchemas(registry, context.scope))
11673
- });
11674
11630
  systemPrompt.context({
11675
11631
  name: "dashr:escalation-guidance",
11676
11632
  order: ESCALATION_GUIDANCE_ORDER,
@@ -11692,4 +11648,4 @@ var src_default = {
11692
11648
  };
11693
11649
 
11694
11650
  //#endregion
11695
- export { CONTROL_SECTION_ORDER, Config, DASHRRunFailedError, DashrRuntime, ESCALATION_GUIDANCE_ORDER, EVAL_NAME, MASKED_TOOL_NAMES, ReplRuntime, SDK_SECTION_ORDER, WIRE_MASKED_NAMES, apply, collectSdkSchemas, createRunCellTool, src_default as default, inject, name, resolveMaxParallelSubCalls };
11651
+ export { Config, DASHRRunFailedError, DashrRuntime, ESCALATION_GUIDANCE_ORDER, EVAL_NAME, MASKED_TOOL_NAMES, ReplRuntime, WIRE_MASKED_NAMES, apply, createRunCellTool, src_default as default, inject, name, resolveMaxParallelSubCalls };
@@ -0,0 +1,178 @@
1
+ import { Context, Service } from "@deepseek-ai/cordis";
2
+
3
+ //#region src/vendored/repl-runtime.ts
4
+ /**
5
+ * Binding globals EVERY backend refuses because SOME backend owns the slot in
6
+ * the program's namespace: `console` (the worker's log capture), and
7
+ * `__dsh_main__`/`__builtins__`/`__name__` (the Python backend's bootstrap
8
+ * wrapper and seeded module globals; see upstream's Agent Note
9
+ * `2026-07-31-code-runtime-portable-identifier-seam.md` in the dsh monorepo),
10
+ * and `__debug__`. One shared set — rather than each backend refusing only its
11
+ * own slots — keeps the portability promise real: a namespace list valid on
12
+ * one backend is valid on all, so a caller cannot pick a name that works on
13
+ * the worker and collides on Python (or vice versa). `__name__` et al. ARE
14
+ * valid portable identifiers, so the identifier rule on
15
+ * `CodeBindingNamespace.global` never rejects them — hence this explicit set.
16
+ * (Error members differ: {@link DUNDER_MEMBER} refuses every dunder form
17
+ * wholesale; binding globals refuse only the names listed here.) `__debug__`
18
+ * is listed for a different reason than a collision: CPython compiles a bare
19
+ * `__debug__` reference to the constant `True` and rejects any assignment to
20
+ * the name at COMPILE time, so an injected global under that name is
21
+ * unreachable from the program — accepted by validation, unusable on the
22
+ * Python backend, which is exactly the split the shared set exists to prevent.
23
+ */
24
+ const RESERVED_BINDING_GLOBALS = new Set([
25
+ "console",
26
+ "__dsh_main__",
27
+ "__builtins__",
28
+ "__name__",
29
+ "__debug__"
30
+ ]);
31
+ /**
32
+ * `CodeBindingErrorClass.memberNameProperty` names EVERY backend refuses, as
33
+ * one shared contract so a request valid on one backend is valid on all. The
34
+ * JS `Error` exclusions (`name`, `message`, `stack`) and Python's
35
+ * exception-protocol members (`args`, `with_traceback`, `add_note`) are
36
+ * listed by name; dunder-form names (`__x__`, non-empty middle) are refused
37
+ * wholesale — several are constrained CPython descriptors whose `setattr`
38
+ * raises while constructing the rejection, and the exact set is an interpreter
39
+ * version detail. Any other non-empty own property name is accepted everywhere.
40
+ */
41
+ const RESERVED_ERROR_MEMBERS = new Set([
42
+ "name",
43
+ "message",
44
+ "stack",
45
+ "args",
46
+ "with_traceback",
47
+ "add_note"
48
+ ]);
49
+ /**
50
+ * Dunder form (`__x__`, non-empty middle): object-protocol slots in Python,
51
+ * refused as {@link RESERVED_ERROR_MEMBERS | error members} on every backend.
52
+ */
53
+ const DUNDER_MEMBER = /^__.+__$/;
54
+ /**
55
+ * Reserved words of every portable target language (ECMAScript ∪ Python),
56
+ * refused as {@link CodeBindingNamespace.global} / error-class names by all
57
+ * backends. Python is a portability target here even though only the
58
+ * TypeScript worker has a published backend. The portable-identifier contract
59
+ * promises a namespace list valid on one backend is valid on every backend; a
60
+ * per-language check would let `lambda` pass the TypeScript backend and fail
61
+ * the Python one. Extending the seam with a new language means widening this
62
+ * union (a breaking review of existing binding names, by design).
63
+ */
64
+ const PORTABLE_RESERVED_WORDS = new Set([
65
+ "await",
66
+ "break",
67
+ "case",
68
+ "catch",
69
+ "class",
70
+ "const",
71
+ "continue",
72
+ "debugger",
73
+ "default",
74
+ "delete",
75
+ "do",
76
+ "else",
77
+ "enum",
78
+ "export",
79
+ "extends",
80
+ "false",
81
+ "finally",
82
+ "for",
83
+ "function",
84
+ "if",
85
+ "import",
86
+ "in",
87
+ "instanceof",
88
+ "new",
89
+ "null",
90
+ "return",
91
+ "super",
92
+ "switch",
93
+ "this",
94
+ "throw",
95
+ "true",
96
+ "try",
97
+ "typeof",
98
+ "var",
99
+ "void",
100
+ "while",
101
+ "with",
102
+ "yield",
103
+ "let",
104
+ "static",
105
+ "implements",
106
+ "interface",
107
+ "package",
108
+ "private",
109
+ "protected",
110
+ "public",
111
+ "arguments",
112
+ "eval",
113
+ "False",
114
+ "None",
115
+ "True",
116
+ "and",
117
+ "as",
118
+ "assert",
119
+ "async",
120
+ "def",
121
+ "del",
122
+ "elif",
123
+ "except",
124
+ "from",
125
+ "global",
126
+ "is",
127
+ "lambda",
128
+ "nonlocal",
129
+ "not",
130
+ "or",
131
+ "pass",
132
+ "raise",
133
+ "match",
134
+ "type",
135
+ "_"
136
+ ]);
137
+ /**
138
+ * Registers one `ctx.replRuntime` implementation. Program, budget, abort, and substrate
139
+ * failures resolve in {@link CodeRunResult}; only Service Definition contract misuse rejects. Implementations bridge
140
+ * structured-cloneable bindings, materialize each declared namespace rejection
141
+ * class, treat programs as hostile peers — budget, interrupt, and substrate
142
+ * semantics apply per program and are unchanged by this seam's statefulness —
143
+ * share one persistent per-session user namespace across runs (state
144
+ * codification across runs IS the product; this replaces upstream's
145
+ * isolate-runs-from-one-another clause), and terminate and await in-flight
146
+ * runs during disposal.
147
+ */
148
+ var ReplRuntime = class extends Service {
149
+ constructor(ctx) {
150
+ super(ctx, "replRuntime");
151
+ }
152
+ };
153
+
154
+ //#endregion
155
+ //#region src/py-sdk.ts
156
+ /** The language-portable identifier subset the seam accepts as a binding global (mirrors the runtime's private rule). */
157
+ const PORTABLE_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
158
+ /**
159
+ * Whether a tool name can be bound as a `tool` member — the ONE
160
+ * policy the bridge's binding loop (src/index.ts) applies, so the
161
+ * bindings never promise a name the kernel does
162
+ * not bind. Strictly narrower than the runtime's validation: the
163
+ * language-portable identifier subset (`[A-Za-z_][A-Za-z0-9_]*`, ASCII — a
164
+ * non-ASCII XID name is legal CPython but not portable, so the runtime
165
+ * refuses it as a binding global), minus
166
+ * every portable reserved word (ECMAScript ∪ Python — `type` and `match`
167
+ * are legal Python but reserved on the seam), minus the seam's reserved
168
+ * binding globals (`console`, dunders), minus underscore-leading names
169
+ * (kernel-shim prefix plus the call-site hazards; not callable as taught
170
+ * flat globals). The runtime's `validateBindings` remains the authoritative
171
+ * backstop: everything this accepts, it accepts.
172
+ */
173
+ function isFlatBindableName(name) {
174
+ return PORTABLE_IDENTIFIER.test(name) && !name.startsWith("_") && !PORTABLE_RESERVED_WORDS.has(name) && !RESERVED_BINDING_GLOBALS.has(name);
175
+ }
176
+
177
+ //#endregion
178
+ export { RESERVED_ERROR_MEMBERS as a, RESERVED_BINDING_GLOBALS as i, DUNDER_MEMBER as n, ReplRuntime as o, PORTABLE_RESERVED_WORDS as r, isFlatBindableName as t };
package/lib/py-sdk.d.ts CHANGED
@@ -1,2 +1,19 @@
1
- import { a as renderReplBridgeInstructions, i as isFlatBindableName, n as REPL_BRIDGE_CATALOG_MODE, o as renderToolsSdkPy, r as ReplBridgeCatalogMode, t as DASHRSdkSchema } from "./py-sdk-BCaOGYz7.js";
2
- export { DASHRSdkSchema, REPL_BRIDGE_CATALOG_MODE, ReplBridgeCatalogMode, isFlatBindableName, renderReplBridgeInstructions, renderToolsSdkPy };
1
+ //#region src/py-sdk.d.ts
2
+ /**
3
+ * Whether a tool name can be bound as a `tool` member — the ONE
4
+ * policy the bridge's binding loop (src/index.ts) applies, so the
5
+ * bindings never promise a name the kernel does
6
+ * not bind. Strictly narrower than the runtime's validation: the
7
+ * language-portable identifier subset (`[A-Za-z_][A-Za-z0-9_]*`, ASCII — a
8
+ * non-ASCII XID name is legal CPython but not portable, so the runtime
9
+ * refuses it as a binding global), minus
10
+ * every portable reserved word (ECMAScript ∪ Python — `type` and `match`
11
+ * are legal Python but reserved on the seam), minus the seam's reserved
12
+ * binding globals (`console`, dunders), minus underscore-leading names
13
+ * (kernel-shim prefix plus the call-site hazards; not callable as taught
14
+ * flat globals). The runtime's `validateBindings` remains the authoritative
15
+ * backstop: everything this accepts, it accepts.
16
+ */
17
+ declare function isFlatBindableName(name: string): boolean;
18
+ //#endregion
19
+ export { isFlatBindableName };
package/lib/py-sdk.js CHANGED
@@ -1,3 +1,3 @@
1
- import { i as renderToolsSdkPy, n as isFlatBindableName, r as renderReplBridgeInstructions, t as REPL_BRIDGE_CATALOG_MODE } from "./py-sdk-CbgYiX8O.js";
1
+ import { t as isFlatBindableName } from "./py-sdk-Chvy92MB.js";
2
2
 
3
- export { REPL_BRIDGE_CATALOG_MODE, isFlatBindableName, renderReplBridgeInstructions, renderToolsSdkPy };
3
+ export { isFlatBindableName };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "better-dsh",
3
- "version": "0.2.3-c",
3
+ "version": "0.2.3-d",
4
4
  "type": "module",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -24,7 +24,7 @@
24
24
  "lib",
25
25
  "docs",
26
26
  "cordis.patch.yml",
27
- "control-prompt.md",
27
+ "eval-description.md",
28
28
  "scripts/kernel-provision.mjs"
29
29
  ],
30
30
  "scripts": {
package/control-prompt.md DELETED
@@ -1,37 +0,0 @@
1
- ## The DASHR REPL interface
2
-
3
- This agent has TWO ways to act:
4
-
5
- 1. **Direct tool calls** — call native tools (`read`/`write`/`edit`/`bash`/…) as ordinary function calls. Use these for payload-shaped work: one long read, one edit, one command.
6
- 2. **`eval` cells** — one `eval` call runs one Python program on a session-persistent scripting pad. Use it when you need logic: loops, conditions, fan-out, or composing many tool results into one step.
7
-
8
- `eval` takes two required arguments: `cell` (one Python program; top-level `await` works; top-level `return` is a SyntaxError — the cell runs in module scope; variables/imports/definitions from earlier cells are still alive) and `description` (a short summary).
9
-
10
- ## Tools inside a cell
11
-
12
- Inside a cell, every native tool is a member of the `tool` object, called as `await tool.name({...})` with ONE positional arguments object — `await tool.read({"file_path": "x"})`, never `tool.read(file_path="x")`. A failed call raises `ToolCallError`. Tool names that are not plain identifiers (non-identifier characters, e.g. hyphens) have no `tool.<name>` member — call those as direct tool calls. Delegation: `agent` is the unified agent-spawn entry; `subagent` is its native alias — both delegate through the same runtime, so call either.
13
-
14
- ```python
15
- # One step cell
16
- print(await tool.read({"file_path": "docs/README.md"}))
17
-
18
- # shell is another tool
19
- r = await tool.bash({"command": "ls -la src/", "description": "List source directory"})
20
- print(r["stdout"]["text"])
21
-
22
- # fan-out with gather
23
- import asyncio
24
- matches, files = await asyncio.gather(
25
- tool.grep({"pattern": "TODO", "path": "src"}),
26
- tool.glob({"pattern": "**/*.ts", "path": "src"}),
27
- )
28
-
29
- # variables persist across cells and turns
30
- cfg = await tool.read({"file_path": "config.yaml"}) # cfg stays alive in later cells
31
- ```
32
-
33
- ## Rules
34
-
35
- - Payload-shaped work (a long read, a big write, a single command) → direct tool call. Logic-shaped work (loops, conditions, composition) → an `eval` cell.
36
- - Only print or return what you need next; everything else stays in the scripting pad.
37
- - Variables persist across cells and turns, but they live in the pad's process: keep durable state in files.
@@ -1,125 +0,0 @@
1
- //#region src/py-sdk.d.ts
2
- /**
3
- * DASHR kernel SDK codegen — Python flavor, cell edition.
4
- *
5
- * The pure projection from one calling scope's visible tool schemas to the
6
- * Python SDK text the model programs against inside `eval` cells. The
7
- * type-rendering machinery is ported from `@deepseek-ai/dsh-tools`
8
- * `py-types.ts` (0.1.0-rc.6) per blueprint §7.4, deliberately slimmed to
9
- * DASHR's single-language, stateful surface:
10
- *
11
- * - The usage instructions are OURS, not upstream's: upstream promises a
12
- * one-shot program ("runs as the body of an async function", value-less
13
- * between calls), while a DASHR cell runs on a PERSISTENT kernel whose
14
- * variables, imports, and definitions survive across `eval` calls.
15
- * The prose here must never state the throwaway contract.
16
- * - No language table and no context-free renderer: DASHR renders Python
17
- * only, and object shapes always render through the named-`TypedDict`
18
- * path this module owns. (Upstream's exported `jsonSchemaToPy` degrades
19
- * every object to `dict[str, Any]`; reusing it would lose the shape.)
20
- * - The bare-identifier rule, `camelCase` derivation, class-name capping
21
- * and collision suffixing, `typing` import emission, and the
22
- * deterministic lexicographic member order are ported near-verbatim: the
23
- * parseability invariants (NFKC stability, reserved words, unprintable
24
- * escapes, bracket-nesting cap) exist because the emitted block is the
25
- * model's ONLY declaration of the tools, and a syntax error in it poisons
26
- * the whole mode.
27
- * - renders one `tool.<name>(args) -> Output` member per tool — no `Tools`
28
- * protocol and no `tools` singleton — matching the kernel's `tool`
29
- * object holder. Which names may render as members is decided by
30
- * {@link isFlatBindableName}, the one policy the renderer and the bridge
31
- * share.
32
- * @module dashr-repl/py-sdk
33
- */
34
- /**
35
- * One tool as the SDK renderer sees it: the model-facing schema
36
- * (name/description/parameters) plus the tool's canonical output schema.
37
- * The caller (the presentation plugin) excludes `eval` itself and reads
38
- * both through the tool registry's public projection APIs.
39
- */
40
- interface DASHRSdkSchema {
41
- /** Tool name as registered (may be exotic; the renderer routes non-bindable names to not-callable comments). */
42
- readonly name: string;
43
- /** Tool description, rendered as the method docstring. */
44
- readonly description: string;
45
- /** Validated JSON-Schema node for the arguments object. */
46
- readonly parameters: unknown;
47
- /** Validated JSON-Schema node for the canonical output value. */
48
- readonly output: unknown;
49
- }
50
- /**
51
- * Whether a tool name can be emitted AND bound as a `tool` member — the ONE
52
- * policy shared by this renderer and the bridge's binding
53
- * loop (src/index.ts), so the catalog never promises a name the kernel does
54
- * not bind. Strictly narrower than the runtime's validation: the
55
- * language-portable identifier subset (`[A-Za-z_][A-Za-z0-9_]*`, ASCII — a
56
- * non-ASCII XID name is legal CPython but not portable, so the runtime
57
- * refuses it as a binding global and the catalog must not teach it), minus
58
- * every portable reserved word (ECMAScript ∪ Python — `type` and `match`
59
- * are legal Python but reserved on the seam), minus the seam's reserved
60
- * binding globals (`console`, dunders), minus underscore-leading names
61
- * (kernel-shim prefix plus the call-site hazards; not callable as taught
62
- * flat globals). The runtime's `validateBindings` remains the authoritative
63
- * backstop: everything this accepts, it accepts.
64
- */
65
- declare function isFlatBindableName(name: string): boolean;
66
- /**
67
- * Render the `dashr:tool-catalog` prompt section body from the registry
68
- * schemas: the cell-flavored usage instructions above, the
69
- * `ToolCallError` declaration, one named `TypedDict` per tool argument or
70
- * output object (and per nested object), and one
71
- * `tool.<name>(args: XArgs) -> XOutput` member per visible tool — no `Tools`
72
- * protocol, no `tools` singleton, every tool is a member of the `tool`
73
- * object exactly as the kernel binds it — inside one fenced
74
- * ```python block. The `typing` import line lists exactly the symbols the
75
- * render used (and is omitted entirely when none are).
76
- *
77
- * Deterministic — tools are emitted in lexicographic name order and class
78
- * declarations precede the function that references them in that same
79
- * order (nested classes before the parent that references them), so an
80
- * unchanged tool set produces byte-identical text across assemblies.
81
- *
82
- * Non-bindable names (reserved, exotic, or underscore-leading) render as
83
- * comment lines: they are registered upstream but NOT callable from cells,
84
- * and the comment keeps the signature and description visible instead of
85
- * silently dropping the tool.
86
- * @param schemas - the calling scope's visible tools (caller excludes
87
- * `eval` and the masked delegation names).
88
- * @returns the complete section body.
89
- */
90
- declare function renderToolsSdkPy(schemas: readonly DASHRSdkSchema[]): string;
91
- /**
92
- * The `dashr:tool-catalog` section's presentation mode (design D4):
93
- * `'signatures'` renders one compact declaration line per visible tool
94
- * (the omp code-mode shape: `name(args: {…})` per line, argument and output
95
- * sketches abbreviated to depth 2 — the default, chosen for low-tier model
96
- * robustness); `'convention'` renders the one-sentence calling convention
97
- * alone (zero repetition, the A/B deployment experiment's other arm).
98
- * Both modes keep the output contract (each tool's canonical JSON output
99
- * shape) and the non-flat-name exception; neither presents a REPL binding
100
- * listing — the declaration lines themselves are the authoritative surface.
101
- */
102
- type ReplBridgeCatalogMode = 'signatures' | 'convention';
103
- /** The deployed presentation mode; flip to `'convention'` to ship arm A. */
104
- declare const REPL_BRIDGE_CATALOG_MODE: ReplBridgeCatalogMode;
105
- /**
106
- * Render the `dashr:tool-catalog` prompt section body as the REPL bridge
107
- * instructions (design D4/D5): the scripting-pad positioning and the
108
- * calling convention above, then — in the default `'signatures'` mode —
109
- * one compact declaration line per visible tool
110
- * (`tool.<name>(args: {…}) -> <output shape>`, omp code-mode shape with
111
- * the output contract kept), inside one fenced ```python block.
112
- * Non-bindable names (reserved, exotic, underscore-leading) are omitted
113
- * from the lines; the convention sentence above states that limit once.
114
- *
115
- * Deterministic — lines are emitted in lexicographic name order, so an
116
- * unchanged tool set produces byte-identical text across assemblies.
117
- * @param schemas - the calling scope's visible tools (the caller already
118
- * excluded the transport and the wire-masked names).
119
- * @param mode - override the deployed {@link REPL_BRIDGE_CATALOG_MODE}
120
- * (the two render states; tests exercise both).
121
- * @returns the complete section body.
122
- */
123
- declare function renderReplBridgeInstructions(schemas: readonly DASHRSdkSchema[], mode?: ReplBridgeCatalogMode): string;
124
- //#endregion
125
- export { renderReplBridgeInstructions as a, isFlatBindableName as i, REPL_BRIDGE_CATALOG_MODE as n, renderToolsSdkPy as o, ReplBridgeCatalogMode as r, DASHRSdkSchema as t };
@@ -1,691 +0,0 @@
1
- import { Context, Service } from "@deepseek-ai/cordis";
2
- import { assertSupportedJsonSchema } from "@deepseek-ai/dsh-tools";
3
-
4
- //#region src/vendored/repl-runtime.ts
5
- /**
6
- * Binding globals EVERY backend refuses because SOME backend owns the slot in
7
- * the program's namespace: `console` (the worker's log capture), and
8
- * `__dsh_main__`/`__builtins__`/`__name__` (the Python backend's bootstrap
9
- * wrapper and seeded module globals; see upstream's Agent Note
10
- * `2026-07-31-code-runtime-portable-identifier-seam.md` in the dsh monorepo),
11
- * and `__debug__`. One shared set — rather than each backend refusing only its
12
- * own slots — keeps the portability promise real: a namespace list valid on
13
- * one backend is valid on all, so a caller cannot pick a name that works on
14
- * the worker and collides on Python (or vice versa). `__name__` et al. ARE
15
- * valid portable identifiers, so the identifier rule on
16
- * `CodeBindingNamespace.global` never rejects them — hence this explicit set.
17
- * (Error members differ: {@link DUNDER_MEMBER} refuses every dunder form
18
- * wholesale; binding globals refuse only the names listed here.) `__debug__`
19
- * is listed for a different reason than a collision: CPython compiles a bare
20
- * `__debug__` reference to the constant `True` and rejects any assignment to
21
- * the name at COMPILE time, so an injected global under that name is
22
- * unreachable from the program — accepted by validation, unusable on the
23
- * Python backend, which is exactly the split the shared set exists to prevent.
24
- */
25
- const RESERVED_BINDING_GLOBALS = new Set([
26
- "console",
27
- "__dsh_main__",
28
- "__builtins__",
29
- "__name__",
30
- "__debug__"
31
- ]);
32
- /**
33
- * `CodeBindingErrorClass.memberNameProperty` names EVERY backend refuses, as
34
- * one shared contract so a request valid on one backend is valid on all. The
35
- * JS `Error` exclusions (`name`, `message`, `stack`) and Python's
36
- * exception-protocol members (`args`, `with_traceback`, `add_note`) are
37
- * listed by name; dunder-form names (`__x__`, non-empty middle) are refused
38
- * wholesale — several are constrained CPython descriptors whose `setattr`
39
- * raises while constructing the rejection, and the exact set is an interpreter
40
- * version detail. Any other non-empty own property name is accepted everywhere.
41
- */
42
- const RESERVED_ERROR_MEMBERS = new Set([
43
- "name",
44
- "message",
45
- "stack",
46
- "args",
47
- "with_traceback",
48
- "add_note"
49
- ]);
50
- /**
51
- * Dunder form (`__x__`, non-empty middle): object-protocol slots in Python,
52
- * refused as {@link RESERVED_ERROR_MEMBERS | error members} on every backend.
53
- */
54
- const DUNDER_MEMBER = /^__.+__$/;
55
- /**
56
- * Reserved words of every portable target language (ECMAScript ∪ Python),
57
- * refused as {@link CodeBindingNamespace.global} / error-class names by all
58
- * backends. Python is a portability target here even though only the
59
- * TypeScript worker has a published backend. The portable-identifier contract
60
- * promises a namespace list valid on one backend is valid on every backend; a
61
- * per-language check would let `lambda` pass the TypeScript backend and fail
62
- * the Python one. Extending the seam with a new language means widening this
63
- * union (a breaking review of existing binding names, by design).
64
- */
65
- const PORTABLE_RESERVED_WORDS = new Set([
66
- "await",
67
- "break",
68
- "case",
69
- "catch",
70
- "class",
71
- "const",
72
- "continue",
73
- "debugger",
74
- "default",
75
- "delete",
76
- "do",
77
- "else",
78
- "enum",
79
- "export",
80
- "extends",
81
- "false",
82
- "finally",
83
- "for",
84
- "function",
85
- "if",
86
- "import",
87
- "in",
88
- "instanceof",
89
- "new",
90
- "null",
91
- "return",
92
- "super",
93
- "switch",
94
- "this",
95
- "throw",
96
- "true",
97
- "try",
98
- "typeof",
99
- "var",
100
- "void",
101
- "while",
102
- "with",
103
- "yield",
104
- "let",
105
- "static",
106
- "implements",
107
- "interface",
108
- "package",
109
- "private",
110
- "protected",
111
- "public",
112
- "arguments",
113
- "eval",
114
- "False",
115
- "None",
116
- "True",
117
- "and",
118
- "as",
119
- "assert",
120
- "async",
121
- "def",
122
- "del",
123
- "elif",
124
- "except",
125
- "from",
126
- "global",
127
- "is",
128
- "lambda",
129
- "nonlocal",
130
- "not",
131
- "or",
132
- "pass",
133
- "raise",
134
- "match",
135
- "type",
136
- "_"
137
- ]);
138
- /**
139
- * Registers one `ctx.replRuntime` implementation. Program, budget, abort, and substrate
140
- * failures resolve in {@link CodeRunResult}; only Service Definition contract misuse rejects. Implementations bridge
141
- * structured-cloneable bindings, materialize each declared namespace rejection
142
- * class, treat programs as hostile peers — budget, interrupt, and substrate
143
- * semantics apply per program and are unchanged by this seam's statefulness —
144
- * share one persistent per-session user namespace across runs (state
145
- * codification across runs IS the product; this replaces upstream's
146
- * isolate-runs-from-one-another clause), and terminate and await in-flight
147
- * runs during disposal.
148
- */
149
- var ReplRuntime = class extends Service {
150
- constructor(ctx) {
151
- super(ctx, "replRuntime");
152
- }
153
- };
154
-
155
- //#endregion
156
- //#region src/py-sdk.ts
157
- /**
158
- * The reference grammar's `xid_start xid_continue*` — the set
159
- * `str.isidentifier()` accepts on a CPython whose Unicode tables match the
160
- * engine's. See {@link isBareIdentifier} for the version-skew stance.
161
- */
162
- const IDENTIFIER = /^[\p{XID_Start}_]\p{XID_Continue}*$/u;
163
- /**
164
- * Whether a name can be emitted as a bare Python identifier rather than
165
- * routed to the subscript/`dict[str, Any]` path. Two conditions, both
166
- * ported from upstream `py-types.ts`:
167
- *
168
- * 1. `IDENTIFIER` matches — Python identifiers are not ASCII (`路径` is a
169
- * legal field name), and rejecting such a name would degrade the whole
170
- * enclosing object, dropping every field's name, requiredness, and type.
171
- * 2. NFKC stability (`name.normalize('NFKC') === name`) — CPython normalizes
172
- * identifiers at compile time while JSON keys are compared as written, so
173
- * an unstable name (`field`) would be advertised under a spelling the
174
- * harness never accepts.
175
- *
176
- * Both conditions are evaluated against the ENGINE's tables; a CPython older
177
- * than the engine could reject a character this accepts (the dangerous
178
- * direction — the tokenizer refuses the character and the whole SDK block
179
- * goes down). DASHR targets the kernel venv shipped with the runtime
180
- * (`npm run kernel:venv`, Python 3.11), which tracks modern CPython; the
181
- * residual skew is accepted rather than carrying upstream's deferred
182
- * target-interpreter-version plumbing.
183
- */
184
- function isBareIdentifier(name) {
185
- return IDENTIFIER.test(name) && name.normalize("NFKC") === name;
186
- }
187
- /**
188
- * Python hard keywords: reserved everywhere, so a tool or field named
189
- * `class` or `lambda` is legal on the wire but not as a def name and not
190
- * as a class-syntax `TypedDict` field. Such FIELDS make the enclosing
191
- * object degrade to `dict[str, Any]`; tool NAME reserving additionally
192
- * follows the portable seam set (see {@link isFlatBindableName}), which is
193
- * narrower than Python alone. Soft keywords (`match`, `case`, `type`,
194
- * `_`) are deliberately absent from this FIELD set (each is special in
195
- * exactly one syntactic position); `__debug__` joins as a compile-time
196
- * assignment refusal. Ported near-verbatim from upstream.
197
- */
198
- const RESERVED = new Set([
199
- "False",
200
- "None",
201
- "True",
202
- "and",
203
- "as",
204
- "assert",
205
- "async",
206
- "await",
207
- "break",
208
- "class",
209
- "continue",
210
- "def",
211
- "del",
212
- "elif",
213
- "else",
214
- "except",
215
- "finally",
216
- "for",
217
- "from",
218
- "global",
219
- "if",
220
- "import",
221
- "in",
222
- "is",
223
- "lambda",
224
- "nonlocal",
225
- "not",
226
- "or",
227
- "pass",
228
- "raise",
229
- "return",
230
- "try",
231
- "while",
232
- "with",
233
- "yield",
234
- "__debug__"
235
- ]);
236
- /** The language-portable identifier subset the seam accepts as a binding global (mirrors the runtime's private rule). */
237
- const PORTABLE_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
238
- /** `typing` symbols this module may emit, in the deterministic import order. */
239
- const TYPING_ORDER = [
240
- "Any",
241
- "Literal",
242
- "NotRequired",
243
- "TypedDict"
244
- ];
245
- /** `indent`-deep line prefix (four spaces per level, PEP 8). */
246
- function pad(indent) {
247
- return " ".repeat(indent);
248
- }
249
- /**
250
- * The `Cc` code points with no printable form (C0, DEL, C1): CPython rejects
251
- * NUL anywhere in source and the rest are invisible, so one `\xNN` escape
252
- * form (covering U+0000–U+00FF exactly) keeps the SDK parseable and readable.
253
- * Ported from upstream.
254
- */
255
- const UNPRINTABLE = /[\u0000-\u0008\u000e-\u001f\u007f-\u009f]/g;
256
- /**
257
- * Unpaired surrogate code points, escaped as `\uNNNN`: Python source must be
258
- * UTF-8-encodable and a lone surrogate is not, so `compile()` raises for one
259
- * anywhere in the text. Reachable from a wire description carrying `"\ud800"`.
260
- */
261
- const LONE_SURROGATE = /[\ud800-\udfff]/gu;
262
- /**
263
- * The collapsed one-line `description` of a schema node, or `undefined` when
264
- * the node carries none that survives collapsing. Control characters left
265
- * after the whitespace collapse render as `\xNN` / `\uNNNN` escapes so the
266
- * emitted block stays valid Python.
267
- */
268
- function describe(schema) {
269
- const description = schema.description;
270
- if (typeof description !== "string") return void 0;
271
- const collapsed = description.replace(/\s+/g, " ").replace(UNPRINTABLE, (char) => `\\x${char.charCodeAt(0).toString(16).padStart(2, "0")}`).replace(LONE_SURROGATE, (char) => `\\u${char.charCodeAt(0).toString(16).padStart(4, "0")}`).trim();
272
- return collapsed.length === 0 ? void 0 : collapsed;
273
- }
274
- /**
275
- * CamelCase a name into a Python type identifier: non-identifier characters
276
- * and `_` split words, a head that cannot start an identifier takes a
277
- * `Tool` prefix, and the result is NFKC-normalized so what CPython compiles
278
- * is identical to what is emitted. Ported from upstream.
279
- */
280
- function camelCase(raw) {
281
- const joined = raw.split(/[^\p{XID_Continue}]+|_+/u).filter((part) => part.length > 0).map((part) => `${part.charAt(0).toUpperCase()}${part.slice(1)}`).join("").normalize("NFKC");
282
- return (/^\p{XID_Start}/u.test(joined) ? joined : `Tool${joined}`).normalize("NFKC");
283
- }
284
- /** Class-name base cap keeping each emitted name — and total text — linear in schema depth. */
285
- const MAX_CLASS_NAME_BASE = 120;
286
- /**
287
- * Deepest `list[…]` nesting emitted into one annotation before the item type
288
- * degrades to `Any`: CPython's tokenizer rejects more than 200 simultaneously
289
- * open brackets, and an array chain deeper than this would render an SDK block
290
- * that is not valid Python at all.
291
- */
292
- const MAX_LIST_NESTING = 180;
293
- /**
294
- * Cap a class-name base at {@link MAX_CLASS_NAME_BASE}. `slice` counts UTF-16
295
- * code units, so an astral character straddling the boundary would leave a
296
- * lone surrogate; drop it rather than emit it.
297
- */
298
- function capClassNameBase(base) {
299
- if (base.length <= MAX_CLASS_NAME_BASE) return base;
300
- const capped = base.slice(0, MAX_CLASS_NAME_BASE);
301
- return /[\uD800-\uDBFF]$/.test(capped) ? capped.slice(0, -1) : capped;
302
- }
303
- /**
304
- * Reserve a unique class name from a base, suffixing `2`, `3`, … on
305
- * collision; the per-base counter keeps a deep chain sharing one capped base
306
- * amortized O(1) instead of rescanning from `2`.
307
- */
308
- function allocateClassName(base, state) {
309
- const capped = capClassNameBase(base);
310
- let name = capped;
311
- if (state.usedClassNames.has(name)) {
312
- let n = state.nextClassCounter.get(capped) ?? 2;
313
- while (state.usedClassNames.has(`${capped}${n}`)) n++;
314
- name = `${capped}${n}`;
315
- state.nextClassCounter.set(capped, n + 1);
316
- }
317
- state.usedClassNames.add(name);
318
- return name;
319
- }
320
- /**
321
- * Append a child-name segment to a parent class-name base, capping at
322
- * propagation so each level stays O(1) and normalizing the join (Hangul jamo
323
- * composition at the seam) so the emitted name is the symbol CPython sees.
324
- */
325
- function childClassName(base, segment) {
326
- return capClassNameBase(`${base}${segment}`.normalize("NFKC"));
327
- }
328
- /**
329
- * Render one validated scalar as Python literal text. A beyond-safe-range
330
- * integer takes `BigInt` digits — Python integers are arbitrary-precision,
331
- * and `String`'s shortest-round-trip padding can name an integer no double
332
- * holds. `JSON.stringify` for strings is what keeps the literal parseable
333
- * (its escapes are Python escapes too; ES2019 well-formedness covers
334
- * surrogates).
335
- */
336
- function pyScalar(value) {
337
- if (value === true) return "True";
338
- if (value === false) return "False";
339
- if (typeof value === "string") return JSON.stringify(value);
340
- if (typeof value === "number" && Number.isInteger(value) && !Number.isSafeInteger(value)) return BigInt(value).toString();
341
- return String(value);
342
- }
343
- /** Render a validated scalar `const`/`enum` as `Literal[...]`, else the broad type. */
344
- function renderConstrainedScalar(node, broad, state) {
345
- if (node.const !== void 0) {
346
- state.typing.add("Literal");
347
- return `Literal[${pyScalar(node.const)}]`;
348
- }
349
- if (node.enum !== void 0) {
350
- state.typing.add("Literal");
351
- return `Literal[${node.enum.map(pyScalar).join(", ")}]`;
352
- }
353
- return broad;
354
- }
355
- /**
356
- * Map one JSON-Schema node to a Python type expression, threading `state`
357
- * for the `TypedDict` declarations and `typing` symbols a full render needs.
358
- * Iterative explicit-stack walk (ported): a hostile 5 000-deep schema must
359
- * not blow the host stack, and class declarations must precede the parent
360
- * that references them. An unsupported or malformed schema degrades to `Any`
361
- * without throwing — the stub is advisory prompt text, only required to
362
- * parse — exactly as upstream treats schemas trusted-after-validation.
363
- */
364
- function renderType(schema, className, state) {
365
- const newFrame = (schema$1, className$1, listDepth) => ({
366
- schema: schema$1,
367
- className: className$1,
368
- phase: "start",
369
- listDepth,
370
- children: [],
371
- childIndex: 0,
372
- childTypes: [],
373
- entries: []
374
- });
375
- try {
376
- assertSupportedJsonSchema(schema);
377
- const frames = [newFrame(schema, className, 0)];
378
- let result;
379
- const finish = (type) => {
380
- frames.pop();
381
- const parent = frames.at(-1);
382
- if (parent === void 0) result = type;
383
- else parent.childTypes.push(type);
384
- };
385
- while (frames.length > 0) {
386
- const frame = frames.at(-1);
387
- if (frame === void 0) break;
388
- if (frame.phase === "children") {
389
- if (frame.childIndex < frame.children.length) {
390
- const child = frame.children[frame.childIndex];
391
- if (child === void 0) throw new Error("dashr-repl: missing python render child");
392
- frame.childIndex++;
393
- frames.push(newFrame(child.schema, child.className, child.listDepth));
394
- continue;
395
- }
396
- if (frame.kind === "oneOf") {
397
- let union = "";
398
- for (const [index, childType] of frame.childTypes.entries()) union = index === 0 ? childType : `${union} | ${childType}`;
399
- finish(union);
400
- continue;
401
- }
402
- if (frame.kind === "array") {
403
- finish(`list[${frame.childTypes[0] ?? "Any"}]`);
404
- continue;
405
- }
406
- const node$1 = frame.node;
407
- const name = frame.allocated;
408
- if (node$1 === void 0 || name === void 0) throw new Error("dashr-repl: missing typeddict frame state");
409
- const required = new Set(node$1.required);
410
- const lines = [`class ${name}(TypedDict):`];
411
- for (let index = 0; index < frame.entries.length; index++) {
412
- const entry = frame.entries[index];
413
- const fieldType = frame.childTypes[index];
414
- if (entry === void 0 || fieldType === void 0) throw new Error("dashr-repl: missing typeddict field type");
415
- const [field, fieldSchema] = entry;
416
- const description = describe(fieldSchema);
417
- if (description !== void 0) lines.push(`${pad(1)}# ${description}`);
418
- if (required.has(field)) lines.push(`${pad(1)}${field}: ${fieldType}`);
419
- else {
420
- state.typing.add("NotRequired");
421
- lines.push(`${pad(1)}${field}: NotRequired[${fieldType}]`);
422
- }
423
- }
424
- if (node$1.additionalProperties !== false) lines.push(`${pad(1)}# Additional keys beyond those declared are allowed.`);
425
- if (lines.length === 1) lines.push(`${pad(1)}pass`);
426
- state.classes.push(lines.join("\n"));
427
- finish(name);
428
- continue;
429
- }
430
- frame.phase = "children";
431
- const node = frame.schema;
432
- if (node.oneOf !== void 0) {
433
- frame.kind = "oneOf";
434
- frame.children = node.oneOf.map((branch, index) => ({
435
- schema: branch,
436
- className: childClassName(frame.className, `${index + 1}`),
437
- listDepth: frame.listDepth
438
- }));
439
- continue;
440
- }
441
- if (node.type === void 0) {
442
- state.typing.add("Any");
443
- finish("Any");
444
- continue;
445
- }
446
- switch (node.type) {
447
- case "string":
448
- finish(renderConstrainedScalar(node, "str", state));
449
- break;
450
- case "number":
451
- finish(renderConstrainedScalar(node, "float", state));
452
- break;
453
- case "integer":
454
- finish(renderConstrainedScalar(node, "int", state));
455
- break;
456
- case "boolean":
457
- finish(renderConstrainedScalar(node, "bool", state));
458
- break;
459
- case "null":
460
- finish("None");
461
- break;
462
- case "array":
463
- if (node.items === void 0) {
464
- state.typing.add("Any");
465
- finish("list[Any]");
466
- break;
467
- }
468
- if (frame.listDepth >= MAX_LIST_NESTING) {
469
- state.typing.add("Any");
470
- finish("Any");
471
- break;
472
- }
473
- frame.kind = "array";
474
- frame.children = [{
475
- schema: node.items,
476
- className: frame.className,
477
- listDepth: frame.listDepth + 1
478
- }];
479
- break;
480
- case "object": {
481
- const entries = Object.entries(node.properties ?? {});
482
- if (!entries.every(([name]) => isBareIdentifier(name) && !RESERVED.has(name) && !(name.startsWith("__") && !name.endsWith("__")))) {
483
- state.typing.add("Any");
484
- finish("dict[str, Any]");
485
- break;
486
- }
487
- if (entries.length === 0 && node.additionalProperties !== false) {
488
- state.typing.add("Any");
489
- finish("dict[str, Any]");
490
- break;
491
- }
492
- frame.kind = "typeddict";
493
- frame.node = node;
494
- frame.allocated = allocateClassName(frame.className, state);
495
- state.typing.add("TypedDict");
496
- frame.entries = entries;
497
- frame.children = entries.map(([field, child]) => ({
498
- schema: child,
499
- className: childClassName(frame.allocated ?? "", camelCase(field)),
500
- listDepth: 1
501
- }));
502
- break;
503
- }
504
- default:
505
- state.typing.add("Any");
506
- finish("Any");
507
- }
508
- }
509
- return result ?? "Any";
510
- } catch {
511
- state.typing.add("Any");
512
- return "Any";
513
- }
514
- }
515
- /**
516
- * Whether a tool name can be emitted AND bound as a `tool` member — the ONE
517
- * policy shared by this renderer and the bridge's binding
518
- * loop (src/index.ts), so the catalog never promises a name the kernel does
519
- * not bind. Strictly narrower than the runtime's validation: the
520
- * language-portable identifier subset (`[A-Za-z_][A-Za-z0-9_]*`, ASCII — a
521
- * non-ASCII XID name is legal CPython but not portable, so the runtime
522
- * refuses it as a binding global and the catalog must not teach it), minus
523
- * every portable reserved word (ECMAScript ∪ Python — `type` and `match`
524
- * are legal Python but reserved on the seam), minus the seam's reserved
525
- * binding globals (`console`, dunders), minus underscore-leading names
526
- * (kernel-shim prefix plus the call-site hazards; not callable as taught
527
- * flat globals). The runtime's `validateBindings` remains the authoritative
528
- * backstop: everything this accepts, it accepts.
529
- */
530
- function isFlatBindableName(name) {
531
- return PORTABLE_IDENTIFIER.test(name) && !name.startsWith("_") && !PORTABLE_RESERVED_WORDS.has(name) && !RESERVED_BINDING_GLOBALS.has(name);
532
- }
533
- /**
534
- * The fixed model-facing usage contract rendered above the declarations —
535
- * DASHR's OWN text (do not copy upstream `py-types.ts` SDK_INSTRUCTIONS: it
536
- * promises one-shot program semantics, and our kernel is persistent).
537
- *
538
- * Every statement here must match what the `eval` transport actually
539
- * enforces: cell semantics (variables survive across calls), top-level
540
- * `await`/`return`, the completion-value contract (lossless JSON; explicit
541
- * `return None` → null; no `return` → no value), the flat binding set
542
- * (every tool function below plus `ToolCallError` — registry tools and
543
- * bridge tools alike are one flat surface), the static-stub caveat
544
- * for `TypedDict`s, and the sub-call concurrency contract implemented by
545
- * the transport's scheduler (submission-ordered starts; only
546
- * concurrency-safe tools overlap, up to the configured cap; exclusive
547
- * tools run alone as barriers).
548
- */
549
- const SDK_INSTRUCTIONS = `## Writing cells for eval
550
-
551
- \`eval\` takes two required arguments — \`cell\` (the Python program) and \`description\` (a short summary of what the cell does) — plus optional \`timeout\` (seconds, wall-clock budget) and \`reset\` (restart the kernel with an empty namespace). The cell runs on a PERSISTENT IPython kernel: variables, imports, and definitions created in any earlier \`eval\` call of this session are still alive in later ones (and in this one), so treat the kernel's namespace as your working memory. Top-level \`await\` works; a top-level \`return\` is a SyntaxError — the cell is module scope, exactly like a native IPython cell. At run time the bound names are \`ToolCallError\` and every tool function declared below. Everything else here is a STATIC STUB describing argument and return types — in particular the \`TypedDict\` classes do NOT exist at run time, so build arguments as plain \`dict\`/\`list\` JSON values: \`await tool.echo({"field": 1})\`, never \`EchoArgs(field=1)\`, which raises \`NameError\`. Inside a cell:
552
-
553
- - Call tools as \`await tool.name(args)\` — the members of the \`tool\` object declared below. Every call resolves to the tool's typed canonical JSON value (each function's return type below). Tool arguments must be lossless JSON.
554
- - A FAILED tool call raises \`ToolCallError\`, whose \`toolName\` identifies the failed tool and whose message is human-readable — wrap in \`try\`/\`except\` to handle and continue.
555
- - Independent calls may overlap under \`asyncio.gather\`: cells dispatch sub-calls in submission order, and only tools marked safe to run side by side actually overlap (bounded by a cap); any other tool runs alone, waiting for overlapping calls to drain first. Sequence dependent work with plain \`await\`.
556
- - Emit the answer with \`print(...)\`, or end the cell with a bare expression — its value becomes the cell's result, like a REPL. A value should be JSON-serializable — anything that isn't comes back as its repr text. A cell ending in a statement (or a \`None\` expression) yields no value. ONLY what you print and the final value come back — intermediate tool results never enter the conversation, so extract just what you need.
557
-
558
- The available tools:`;
559
- /**
560
- * Render the `dashr:tool-catalog` prompt section body from the registry
561
- * schemas: the cell-flavored usage instructions above, the
562
- * `ToolCallError` declaration, one named `TypedDict` per tool argument or
563
- * output object (and per nested object), and one
564
- * `tool.<name>(args: XArgs) -> XOutput` member per visible tool — no `Tools`
565
- * protocol, no `tools` singleton, every tool is a member of the `tool`
566
- * object exactly as the kernel binds it — inside one fenced
567
- * ```python block. The `typing` import line lists exactly the symbols the
568
- * render used (and is omitted entirely when none are).
569
- *
570
- * Deterministic — tools are emitted in lexicographic name order and class
571
- * declarations precede the function that references them in that same
572
- * order (nested classes before the parent that references them), so an
573
- * unchanged tool set produces byte-identical text across assemblies.
574
- *
575
- * Non-bindable names (reserved, exotic, or underscore-leading) render as
576
- * comment lines: they are registered upstream but NOT callable from cells,
577
- * and the comment keeps the signature and description visible instead of
578
- * silently dropping the tool.
579
- * @param schemas - the calling scope's visible tools (caller excludes
580
- * `eval` and the masked delegation names).
581
- * @returns the complete section body.
582
- */
583
- function renderToolsSdkPy(schemas) {
584
- const sorted = [...schemas].sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
585
- const state = {
586
- classes: [],
587
- usedClassNames: /* @__PURE__ */ new Set(),
588
- nextClassCounter: /* @__PURE__ */ new Map(),
589
- typing: /* @__PURE__ */ new Set()
590
- };
591
- const defs = [];
592
- for (const schema of sorted) {
593
- const argType = renderType(schema.parameters, `${camelCase(schema.name)}Args`, state);
594
- const outputType = renderType(schema.output, `${camelCase(schema.name)}Output`, state);
595
- if (isFlatBindableName(schema.name)) {
596
- const lines = [];
597
- const description = describe(schema);
598
- if (description !== void 0) lines.push(`# ${description}`);
599
- lines.push(`tool.${schema.name}(args: ${argType}) -> ${outputType}`);
600
- defs.push(lines.join("\n"));
601
- } else {
602
- defs.push(`# ${JSON.stringify(schema.name)}: registered but NOT callable from cells (its name is not a usable member name) — (args: ${argType}) -> ${outputType}`);
603
- const description = describe(schema);
604
- if (description !== void 0) defs.push(`# ${description}`);
605
- }
606
- }
607
- const imports = TYPING_ORDER.filter((symbol) => state.typing.has(symbol));
608
- return `${SDK_INSTRUCTIONS}\n\n\`\`\`python\n${`${imports.length > 0 ? `from typing import ${imports.join(", ")}\n\n` : ""}class ToolCallError(Exception):
609
- toolName: str\n\n${state.classes.length > 0 ? `${state.classes.join("\n\n")}\n\n` : ""}${defs.join("\n\n")}`}\n\`\`\``;
610
- }
611
- /** The deployed presentation mode; flip to `'convention'` to ship arm A. */
612
- const REPL_BRIDGE_CATALOG_MODE = "signatures";
613
- /** Abbreviation depth for the compact sketches: root, children, grandchildren; deeper degrades to `Any`. */
614
- const COMPACT_SKETCH_DEPTH = 2;
615
- /**
616
- * Render one JSON-Schema node as a compact Python-flavored shape sketch:
617
- * scalar names map to Python (`str`/`int`/`float`/`bool`), objects render
618
- * as the dict literal the call actually takes (`{'path': str, 'offset'?: int}` —
619
- * `?` marks optional keys), string enums render as quoted unions, arrays as
620
- * `list[…]`. Nodes deeper than {@link COMPACT_SKETCH_DEPTH}, and anything
621
- * malformed or unsupported, degrade to `Any` — the sketch is advisory
622
- * prompt text, mirroring the omp `tsType` simplification (depth-capped,
623
- * never throwing) rather than the full TypedDict codegen above.
624
- */
625
- function compactSketch(schema, depth) {
626
- if (schema === null || typeof schema !== "object" || depth > COMPACT_SKETCH_DEPTH) return "Any";
627
- const node = schema;
628
- if (Array.isArray(node.enum) && node.enum.length > 0 && node.enum.every((value) => typeof value === "string")) return node.enum.map((value) => `'${value.replaceAll("\\", "\\\\").replaceAll("'", "\\'")}'`).join(" | ");
629
- switch (node.type) {
630
- case "string": return "str";
631
- case "integer": return "int";
632
- case "number": return "float";
633
- case "boolean": return "bool";
634
- case "null": return "None";
635
- case "array": return `list[${compactSketch(node.items, depth + 1)}]`;
636
- case "object": {
637
- const properties = node.properties;
638
- if (properties === void 0) return "dict";
639
- const required = new Set(Array.isArray(node.required) ? node.required : []);
640
- const entries = Object.entries(properties).map(([key, child]) => {
641
- return `${`'${key.replaceAll("\\", "\\\\").replaceAll("'", "\\'")}'${required.has(key) ? "" : "?"}`}: ${compactSketch(child, depth + 1)}`;
642
- });
643
- return entries.length === 0 ? "dict" : `{${entries.join(", ")}}`;
644
- }
645
- default: return "Any";
646
- }
647
- }
648
- /**
649
- * The fixed model-facing convention rendered above any declaration lines —
650
- * the REPL scripting pad positioning (design D5): a session-persistent
651
- * environment (Python today) that is one working surface beside direct tool
652
- * calls, never a privileged entry point. The wording deliberately avoids
653
- * the word "kernel": the model's real environment is the runtime surface
654
- * presented to it, and importing a second name for it only blurs that.
655
- */
656
- const REPL_BRIDGE_INSTRUCTIONS = `## Calling tools from the scripting pad
657
-
658
- \`eval\` runs each cell on a session-persistent scripting pad (Python today; other languages are natural extensions): variables, imports, and definitions from earlier cells stay alive, top-level \`await\` works, and the pad is one working surface beside your direct tool calls — same tools, composed in code.
659
-
660
- Every tool this conversation declares is callable inside a cell as \`await tool.<name>(args)\` with ONE positional arguments object; the awaited value is that tool's canonical JSON output (the output shape declared with each signature) and a failed call raises \`ToolCallError\`, whose \`.toolName\` names the tool. Tool names that are not plain identifiers (non-identifier characters, e.g. hyphens) have no \`tool.<name>\` member — call those as direct tool calls. The declaration lines below ARE the live callable surface for this scope.`;
661
- /**
662
- * Render the `dashr:tool-catalog` prompt section body as the REPL bridge
663
- * instructions (design D4/D5): the scripting-pad positioning and the
664
- * calling convention above, then — in the default `'signatures'` mode —
665
- * one compact declaration line per visible tool
666
- * (`tool.<name>(args: {…}) -> <output shape>`, omp code-mode shape with
667
- * the output contract kept), inside one fenced ```python block.
668
- * Non-bindable names (reserved, exotic, underscore-leading) are omitted
669
- * from the lines; the convention sentence above states that limit once.
670
- *
671
- * Deterministic — lines are emitted in lexicographic name order, so an
672
- * unchanged tool set produces byte-identical text across assemblies.
673
- * @param schemas - the calling scope's visible tools (the caller already
674
- * excluded the transport and the wire-masked names).
675
- * @param mode - override the deployed {@link REPL_BRIDGE_CATALOG_MODE}
676
- * (the two render states; tests exercise both).
677
- * @returns the complete section body.
678
- */
679
- function renderReplBridgeInstructions(schemas, mode = REPL_BRIDGE_CATALOG_MODE) {
680
- if (mode === "convention") return REPL_BRIDGE_INSTRUCTIONS;
681
- const lines = [];
682
- for (const schema of [...schemas].sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0)) {
683
- if (!isFlatBindableName(schema.name)) continue;
684
- lines.push(`tool.${schema.name}(args: ${compactSketch(schema.parameters, 0)}) -> ${compactSketch(schema.output, 0)}`);
685
- }
686
- if (lines.length === 0) return REPL_BRIDGE_INSTRUCTIONS;
687
- return `${REPL_BRIDGE_INSTRUCTIONS}\n\nTool declarations (one line per tool; \`?\` marks optional keys, deeper structure is abbreviated):\n\n\`\`\`python\n${lines.join("\n")}\n\`\`\``;
688
- }
689
-
690
- //#endregion
691
- export { DUNDER_MEMBER as a, RESERVED_ERROR_MEMBERS as c, renderToolsSdkPy as i, ReplRuntime as l, isFlatBindableName as n, PORTABLE_RESERVED_WORDS as o, renderReplBridgeInstructions as r, RESERVED_BINDING_GLOBALS as s, REPL_BRIDGE_CATALOG_MODE as t };