better-dsh 0.0.0 → 0.2.3
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/LICENSE +24 -0
- package/README.md +294 -4
- package/control-prompt.md +37 -0
- package/cordis.patch.yml +53 -0
- package/docs/00_adr/0001-bridge-tool-layer-not-service-layer.md +14 -0
- package/docs/00_adr/0002-masking-is-presentation-only.md +15 -0
- package/docs/10_plans/A2A-messaging-channel-test-archive.md +256 -0
- package/docs/10_plans/code-mode-vs-rlm-ipython-comparison.md +137 -0
- package/docs/10_plans/dashr-blueprint-review.md +201 -0
- package/docs/10_plans/dashr-blueprint.md +561 -0
- package/docs/10_plans/dashr-compaction-window-and-archive.md +307 -0
- package/docs/10_plans/dashr-profile-layer-feasibility.md +367 -0
- package/docs/10_plans/dashr-sandbox-escalation-semantics-gap.md +171 -0
- package/docs/10_plans/dashr-security-sandbox-analysis.md +187 -0
- package/docs/10_plans/dashr-surface-invariant-and-omp-imports.md +97 -0
- package/docs/10_plans/ipython-kernel-interactive-interface-test-report.md +152 -0
- package/docs/10_plans/kernel-refactoring/Dash-IPython-Control-Prompt-draft.md +146 -0
- package/docs/10_plans/kernel-refactoring/Dash-IPython-Control-Prompt-draft_v3.md +50 -0
- package/docs/10_plans/kernel-refactoring/Dash-IPython-Control-Prompt-draft_v4.md +79 -0
- package/docs/10_plans/kernel-refactoring/Dash-vs-PrimeAgent-systemprompt-toolcatalog-comparison.md +138 -0
- package/docs/10_plans/kernel-refactoring/RLM-system-prompt-injection-gap-report.md +161 -0
- package/docs/10_plans/kernel-refactoring/V0.1.5-development-plan.md +109 -0
- package/docs/10_plans/kernel-refactoring/actinoable-surface-to-llm-in-agent-runtime_dsh.md +50 -0
- package/docs/10_plans/kernel-refactoring/actinoable-surface-to-llm-in-agent-runtime_prime.md +113 -0
- package/docs/10_plans/recallable-compaction.md +147 -0
- package/docs/10_plans/spike-tag-repro.mjs +102 -0
- package/docs/10_plans/upstream-analysis.md +128 -0
- package/docs/50_test-reports/REPL-/345/267/245/345/205/267/350/260/203/347/224/250-/346/210/252/346/226/255/350/257/212/346/226/255.md +110 -0
- package/docs/50_test-reports/kernel-provisioning.md +44 -0
- package/docs/50_test-reports/repl-kernel-provisioning-test-report.md +87 -0
- package/docs/50_test-reports/upstream-dsh-0.1.2-alpha.5-local-test-report.md +81 -0
- package/docs/50_test-reports/upstream-dsh-0.1.2-alpha.5-report.md +93 -0
- package/docs/50_test-reports/v0.1.8-improved-/345/256/236/346/265/213/346/212/245/345/221/212.md +142 -0
- package/docs/50_test-reports/v0.1.8-/345/256/236/346/265/213/346/212/245/345/221/212.md +193 -0
- package/docs/50_test-reports/v0.1.8b-/345/256/236/346/265/213/346/212/245/345/221/212.md +96 -0
- package/docs/50_test-reports/v0.1.8c-/345/256/236/346/265/213/346/212/245/345/221/212.md +127 -0
- package/docs/50_test-reports/v0.1.8d-/345/256/236/346/265/213/346/212/245/345/221/212.md +150 -0
- package/docs/50_test-reports/v0.1.8d_artifacts/README.md +138 -0
- package/docs/50_test-reports/v0.1.8d_artifacts/code-mode-repl-only.observation.md +74 -0
- package/docs/50_test-reports/v0.1.8d_artifacts/dsh-session-session-4a293388-9ae1-474b-87a0-9e17bb556d94.jsonl +3890 -0
- package/docs/50_test-reports/v0.1.8d_artifacts/dsh-session-session-4a293388-9ae1-474b-87a0-9e17bb556d94.w-sample-0435.jsonl +544 -0
- package/docs/50_test-reports/v0.1.8d_artifacts/functions.json +592 -0
- package/docs/50_test-reports/v0.1.8d_artifacts/skills-catalog.snapshot.md +30 -0
- package/docs/50_test-reports/v0.1.8d_artifacts/tools-sdk.output-schemas.json +1236 -0
- package/docs/50_test-reports/v0.1.8d_artifacts/tools-sdk.python.txt +592 -0
- package/docs/50_test-reports/v0.1.8d_artifacts/tools-sdk.typescript.txt +516 -0
- package/docs/50_test-reports/v0.1.8d_artifacts/wire-vs-transcription.diff.md +54 -0
- package/docs/50_test-reports/v0.1.8e-/345/256/236/346/265/213/346/212/245/345/221/212.md +224 -0
- package/docs/50_test-reports/v0.1.9a-/345/256/236/346/265/213/346/212/245/345/221/212.md +168 -0
- package/docs/50_test-reports/v0.2.0b-/345/256/236/346/265/213/346/212/245/345/221/212.md +123 -0
- package/docs/50_test-reports/v0.2.0b_artifacts/f2probe/Cargo.lock +7 -0
- package/docs/50_test-reports/v0.2.0b_artifacts/f2probe/Cargo.toml +6 -0
- package/docs/50_test-reports/v0.2.0b_artifacts/f2probe/src/bin/messy.rs +8 -0
- package/docs/50_test-reports/v0.2.0b_artifacts/f2probe/src/main.rs +4 -0
- package/docs/50_test-reports/v0.2.0b_artifacts/hashline-probe.md +5 -0
- package/docs/50_test-reports/v0.2.0b_artifacts/slowprobe/Cargo.lock +7 -0
- package/docs/50_test-reports/v0.2.0b_artifacts/slowprobe/Cargo.toml +7 -0
- package/docs/50_test-reports/v0.2.0b_artifacts/slowprobe/build.rs +4 -0
- package/docs/50_test-reports/v0.2.0b_artifacts/slowprobe/src/main.rs +13 -0
- package/docs/50_test-reports/v0.2.1-/345/256/236/346/265/213/346/212/245/345/221/212.md +110 -0
- package/docs/50_test-reports/v0.2.1b-/345/256/236/346/265/213/346/212/245/345/221/212.md +86 -0
- package/docs/50_test-reports/v0.2.1c-/345/256/236/346/265/213/346/212/245/345/221/212.md +66 -0
- package/docs/50_test-reports/v0.2.1d-/345/256/236/346/265/213/346/212/245/345/221/212.md +67 -0
- package/docs/50_test-reports/v0.2.1e-P1-/345/256/236/346/265/213/346/212/245/345/221/212.md +136 -0
- package/docs/50_test-reports/v0.2.1ef-dev-audit-report.md +73 -0
- package/docs/50_test-reports/v0.2.1f-plugin-shipped-ui-patches/345/256/236/346/265/213/346/212/245/345/221/212.md +102 -0
- package/docs/60_exploration-and-research/cordis-research.md +350 -0
- package/docs/60_exploration-and-research/dsh-web-profile-package-map.md +186 -0
- package/docs/60_exploration-and-research/dsh-web-ui-slot-system-research.md +310 -0
- package/docs/60_exploration-and-research/dsh-webui-strip-boundary-research.md +300 -0
- package/docs/60_exploration-and-research/ios-chat-app-bridge-research.md +324 -0
- package/docs/60_exploration-and-research/web-frontend-composability-research.md +191 -0
- package/docs/REPL-/345/267/245/345/205/267/350/260/203/347/224/250-/346/210/252/346/226/255/350/257/212/346/226/255.md +110 -0
- package/docs/adr/0001-bridge-tool-layer-not-service-layer.md +14 -0
- package/docs/adr/0002-masking-is-presentation-only.md +15 -0
- package/docs/distro-blueprint.md +81 -0
- package/docs/dsh-webUI-with-rlm-mode.png +0 -0
- package/docs/plans/A2A-messaging-channel-test-archive.md +256 -0
- package/docs/plans/code-mode-vs-rlm-ipython-comparison.md +137 -0
- package/docs/plans/dashr-blueprint-review.md +201 -0
- package/docs/plans/dashr-blueprint.md +561 -0
- package/docs/plans/dashr-compaction-window-and-archive.md +307 -0
- package/docs/plans/dashr-profile-layer-feasibility.md +367 -0
- package/docs/plans/dashr-sandbox-escalation-semantics-gap.md +171 -0
- package/docs/plans/dashr-security-sandbox-analysis.md +187 -0
- package/docs/plans/dashr-surface-invariant-and-omp-imports.md +97 -0
- package/docs/plans/ipython-kernel-interactive-interface-test-report.md +152 -0
- package/docs/plans/kernel-refactoring/Dash-IPython-Control-Prompt-draft.md +146 -0
- package/docs/plans/kernel-refactoring/Dash-IPython-Control-Prompt-draft_v3.md +50 -0
- package/docs/plans/kernel-refactoring/Dash-IPython-Control-Prompt-draft_v4.md +79 -0
- package/docs/plans/kernel-refactoring/Dash-vs-PrimeAgent-systemprompt-toolcatalog-comparison.md +138 -0
- package/docs/plans/kernel-refactoring/RLM-system-prompt-injection-gap-report.md +161 -0
- package/docs/plans/kernel-refactoring/V0.1.5-development-plan.md +109 -0
- package/docs/plans/kernel-refactoring/actinoable-surface-to-llm-in-agent-runtime_dsh.md +50 -0
- package/docs/plans/kernel-refactoring/actinoable-surface-to-llm-in-agent-runtime_prime.md +113 -0
- package/docs/plans/recallable-compaction.md +147 -0
- package/docs/plans/spike-tag-repro.mjs +102 -0
- package/docs/plans/upstream-analysis.md +128 -0
- package/docs/repositioning-and-rebranding.md +102 -0
- package/docs/v0.1.8-improved-/345/256/236/346/265/213/346/212/245/345/221/212.md +142 -0
- package/docs/v0.1.8-/345/256/236/346/265/213/346/212/245/345/221/212.md +193 -0
- package/docs/v0.1.8b-/345/256/236/346/265/213/346/212/245/345/221/212.md +96 -0
- package/docs/v0.1.8c-/345/256/236/346/265/213/346/212/245/345/221/212.md +127 -0
- package/docs/v0.1.8d-/345/256/236/346/265/213/346/212/245/345/221/212.md +150 -0
- package/docs/v0.1.8d_artifacts/README.md +138 -0
- package/docs/v0.1.8d_artifacts/code-mode-repl-only.observation.md +74 -0
- package/docs/v0.1.8d_artifacts/dsh-session-session-4a293388-9ae1-474b-87a0-9e17bb556d94.jsonl +3890 -0
- package/docs/v0.1.8d_artifacts/dsh-session-session-4a293388-9ae1-474b-87a0-9e17bb556d94.w-sample-0435.jsonl +544 -0
- package/docs/v0.1.8d_artifacts/functions.json +592 -0
- package/docs/v0.1.8d_artifacts/skills-catalog.snapshot.md +30 -0
- package/docs/v0.1.8d_artifacts/tools-sdk.output-schemas.json +1236 -0
- package/docs/v0.1.8d_artifacts/tools-sdk.python.txt +592 -0
- package/docs/v0.1.8d_artifacts/tools-sdk.typescript.txt +516 -0
- package/docs/v0.1.8d_artifacts/wire-vs-transcription.diff.md +54 -0
- package/docs/v0.1.8e-/345/256/236/346/265/213/346/212/245/345/221/212.md +224 -0
- package/docs/v0.1.9a-/345/256/236/346/265/213/346/212/245/345/221/212.md +168 -0
- package/docs/v0.2.0b-/345/256/236/346/265/213/346/212/245/345/221/212.md +123 -0
- package/docs/v0.2.0b_artifacts/f2probe/Cargo.lock +7 -0
- package/docs/v0.2.0b_artifacts/f2probe/Cargo.toml +6 -0
- package/docs/v0.2.0b_artifacts/f2probe/src/bin/messy.rs +8 -0
- package/docs/v0.2.0b_artifacts/f2probe/src/main.rs +4 -0
- package/docs/v0.2.0b_artifacts/hashline-probe.md +5 -0
- package/docs/v0.2.0b_artifacts/slowprobe/Cargo.lock +7 -0
- package/docs/v0.2.0b_artifacts/slowprobe/Cargo.toml +7 -0
- package/docs/v0.2.0b_artifacts/slowprobe/build.rs +4 -0
- package/docs/v0.2.0b_artifacts/slowprobe/src/main.rs +13 -0
- package/docs/v0.2.1-/345/256/236/346/265/213/346/212/245/345/221/212.md +110 -0
- package/docs/v0.2.1b-/345/256/236/346/265/213/346/212/245/345/221/212.md +86 -0
- package/docs/v0.2.1c-/345/256/236/346/265/213/346/212/245/345/221/212.md +66 -0
- package/lib/client/index.js +473 -0
- package/lib/index.d.ts +736 -0
- package/lib/index.js +11518 -0
- package/lib/kernel-env-hxaihi9C.js +195 -0
- package/lib/kernel-env.d.ts +80 -0
- package/lib/kernel-env.js +3 -0
- package/lib/py-sdk-BCaOGYz7.d.ts +125 -0
- package/lib/py-sdk-CbgYiX8O.js +691 -0
- package/lib/py-sdk.d.ts +2 -0
- package/lib/py-sdk.js +3 -0
- package/package.json +325 -4
- package/scripts/kernel-provision.mjs +35 -0
- package/index.js +0 -3
|
@@ -0,0 +1,691 @@
|
|
|
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 };
|