@poe-platform/safe-bash 0.1.821 → 0.1.823
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/dist/safe-bash/chunks/{chunk-HNT2MR4W.js → chunk-363QOIDR.js} +2 -1
- package/dist/safe-bash/chunks/chunk-AGHV53VF.js +42 -0
- package/dist/safe-bash/chunks/{chunk-VF3Q2V3E.js → chunk-RAFD5ZAH.js} +1 -1
- package/dist/safe-bash/chunks/{chunk-67QE3JLU.js → chunk-TZRVK3NU.js} +1 -1
- package/dist/safe-bash/chunks/{chunk-IRJS54AA.js → chunk-VWLQDZFF.js} +20 -12
- package/dist/safe-bash/chunks/{chunk-NAETSXBW.js → chunk-ZJAXMB5M.js} +1 -1
- package/dist/safe-bash/command-chunks/{chunk-QX3WKBLI.js → chunk-36FV6BAG.js} +5 -3
- package/dist/safe-bash/command-chunks/{chunk-ZSHXZ3XG.js → chunk-3EAZRKI4.js} +7 -7
- package/dist/safe-bash/command-chunks/chunk-64HIOP7Q.js +993 -0
- package/dist/safe-bash/command-chunks/{chunk-WOT2YXBT.js → chunk-7ZQVMPSB.js} +213 -42
- package/dist/safe-bash/command-chunks/{chunk-GRFJKJFN.js → chunk-AVSINNZ6.js} +233 -3
- package/dist/safe-bash/command-chunks/{chunk-BZLLQWRK.js → chunk-E5CDHXFA.js} +2 -2
- package/dist/safe-bash/command-chunks/{chunk-RHXV2KG5.js → chunk-EO3RYGUZ.js} +1010 -58
- package/dist/safe-bash/command-chunks/{chunk-MCUDSE4H.js → chunk-EWECMOQ5.js} +1 -1
- package/dist/safe-bash/command-chunks/{chunk-RTUDDAAY.js → chunk-O3KFOFJM.js} +1 -1
- package/dist/safe-bash/command-chunks/{chunk-SLD5A7IL.js → chunk-OU5UEUZN.js} +1 -1
- package/dist/safe-bash/command-chunks/{chunk-RKAL5VFP.js → chunk-PLVFJ6FF.js} +255 -616
- package/dist/safe-bash/command-chunks/{chunk-CJIFJ45R.js → chunk-QBCEAYQT.js} +1 -1
- package/dist/safe-bash/command-chunks/{chunk-4Q3HP33O.js → chunk-RGKHTSUY.js} +146 -123
- package/dist/safe-bash/command-chunks/{chunk-I7MEH76L.js → chunk-UYNJF5QN.js} +1 -1
- package/dist/safe-bash/command-chunks/{chunk-IAHYG67O.js → chunk-V5ZBRM5R.js} +3 -3
- package/dist/safe-bash/command-chunks/{chunk-R4NPYXQX.js → chunk-WDTWJFPK.js} +320 -67
- package/dist/safe-bash/command-chunks/{chunk-GQV5RWMN.js → chunk-ZFXHSFTU.js} +1 -1
- package/dist/safe-bash/command-chunks/{prompt-tool-debug-ERJKK3XZ.js → prompt-tool-debug-3POFNRYP.js} +5 -5
- package/dist/safe-bash/commands/bc/index.browser.js +1 -1
- package/dist/safe-bash/commands/llm/index.browser.js +3 -1
- package/dist/safe-bash/commands/llm/index.js +1 -1
- package/dist/safe-bash/commands/python/executor.browser.js +12 -1
- package/dist/safe-bash/commands/python/executor.js +1 -1
- package/dist/safe-bash/commands/python/index.browser.js +23 -1
- package/dist/safe-bash/commands/python/index.js +1 -1
- package/dist/safe-bash/core.browser.js +2 -2
- package/dist/safe-bash/full-core.browser.js +30 -6
- package/dist/safe-bash/plugins/index.browser.js +3 -3
- package/dist/safe-bash/registry-entry.browser.js +2 -2
- package/dist/safe-bash/search.browser.js +1 -1
- package/dist/safe-bash/shell/runtime.d.ts +2 -2
- package/dist/safe-bash/shell/runtime.js +11 -2
- package/dist/safe-bash/shell/sync-extra-evaluators.js +12 -12
- package/dist/safe-bash/shell-entry.browser.js +2 -2
- package/dist/safe-bash-command-llm/collections.js +24 -65
- package/dist/safe-bash-command-llm/command.js +6 -6
- package/dist/safe-bash-command-llm/fragment-loaders.d.ts +3 -2
- package/dist/safe-bash-command-llm/index.d.ts +3 -2
- package/dist/safe-bash-command-llm/index.js +10 -8
- package/dist/safe-bash-command-llm/loader-provider.d.ts +25 -0
- package/dist/safe-bash-command-llm/openai-sse.js +2 -2
- package/dist/safe-bash-command-llm/openai.js +4 -4
- package/dist/safe-bash-command-llm/providers/index.js +4 -4
- package/dist/safe-bash-command-llm/providers/openai.js +4 -4
- package/dist/safe-bash-command-llm/python-unicode.d.ts +2 -0
- package/dist/safe-bash-command-llm/templates.d.ts +7 -2
- package/dist/safe-bash-command-llm/types.d.ts +7 -1
- package/dist/safe-bash-command-mcp/index.browser.js +3 -0
- package/dist/safe-bash-command-mcp/index.js +3 -0
- package/dist/safe-bash-command-patch/index.js +2 -2
- package/dist/safe-bash-command-patch/patch-publication.js +1 -1
- package/dist/safe-bash-command-patch/patch.js +2 -2
- package/dist/safe-bash-command-python/build-backend.d.ts +51 -0
- package/dist/safe-bash-command-python/build-dependencies.d.ts +12 -0
- package/dist/safe-bash-command-python/build-environment.d.ts +3 -0
- package/dist/safe-bash-command-python/build-wheel.d.ts +8 -0
- package/dist/safe-bash-command-python/executor.d.ts +12 -2
- package/dist/safe-bash-command-python/executor.js +26 -4
- package/dist/safe-bash-command-python/index.js +26 -4
- package/dist/safe-bash-command-python/jspi.js +2 -2
- package/dist/safe-bash-command-python/llm-assets-node.js +1 -1
- package/dist/safe-bash-command-python/llm-fragment-loader.d.ts +6 -0
- package/dist/safe-bash-command-python/llm-functions-loader.d.ts +5 -1
- package/dist/safe-bash-command-python/llm-loader-discovery.d.ts +13 -0
- package/dist/safe-bash-command-python/llm-package-manager.d.ts +7 -0
- package/dist/safe-bash-command-python/llm-template-loader.d.ts +6 -0
- package/dist/safe-bash-command-python/manifest.d.ts +13 -0
- package/dist/safe-bash-command-python/provisioning.d.ts +33 -5
- package/dist/safe-bash-command-python/provisioning.js +1 -1
- package/dist/safe-bash-command-python/source-environment.d.ts +9 -0
- package/dist/safe-bash-command-python/source-snapshot.d.ts +8 -0
- package/dist/safe-bash-command-python/worker.js +3 -185
- package/dist/safe-bash-csv-engine/index.js +1 -1
- package/dist/safe-bash-csv-engine/python-iso2022.d.ts +21 -0
- package/dist/safe-bash-csv-engine/python-utf7.d.ts +18 -0
- package/dist/safe-bash-csv-engine/text-decoder.d.ts +2 -0
- package/dist/safe-bash-csv-engine/text-decoder.js +7 -3
- package/dist/safe-bash-zip-engine/index.js +1 -1
- package/dist/safe-bash-zip-engine/zip/repair.js +1 -1
- package/dist/safe-bash-zip-engine/zip/sfx.js +1 -1
- package/dist/safe-bash-zip-engine/zip-format.d.ts +3 -1
- package/dist/safe-bash-zip-engine/zip-format.js +1 -1
- package/dist/safe-playwright-cloudflare/shell-browser-resource.d.ts +1 -1
- package/dist/safe-playwright-cloudflare/shell-browser-resource.js +35 -20
- package/package.json +3 -3
- package/dist/safe-bash/chunks/chunk-PSD2RSOS.js +0 -31
- package/dist/safe-bash/command-chunks/chunk-5EFNQ6FY.js +0 -458
|
@@ -1,17 +1,23 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createPythonPackageEnvironment,
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
createPythonPackageManifestStore,
|
|
4
|
+
openPythonPackageFile,
|
|
5
|
+
pythonDocumentPackages,
|
|
6
|
+
stagePythonPackage
|
|
7
|
+
} from "./chunk-WDTWJFPK.js";
|
|
5
8
|
import {
|
|
6
9
|
pythonShellDispatchActive
|
|
7
10
|
} from "./chunk-NHAG4DWL.js";
|
|
8
11
|
import {
|
|
9
12
|
PythonInvocationError,
|
|
10
13
|
parsePythonInvocation
|
|
11
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-64HIOP7Q.js";
|
|
12
15
|
import {
|
|
13
16
|
pythonLlmProvider
|
|
14
17
|
} from "./chunk-YYRVGXJH.js";
|
|
18
|
+
import {
|
|
19
|
+
createPythonExecutorPool
|
|
20
|
+
} from "./chunk-GT27M77E.js";
|
|
15
21
|
import {
|
|
16
22
|
PythonFailure,
|
|
17
23
|
isPythonFailureCategory,
|
|
@@ -107,45 +113,23 @@ function encodePythonReply(value, maxBytes) {
|
|
|
107
113
|
}
|
|
108
114
|
|
|
109
115
|
// packages/safe-bash-command-python/src/installation.ts
|
|
110
|
-
var pythonInstallationHelp = `Usage: python -m pip install [-r
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
--force-reinstall, --no-index, --no-deps, --target, --user, --prefix, --root, --editable,
|
|
116
|
-
--index-url, --extra-index-url, --find-links, --trusted-host, --require-hashes,
|
|
117
|
-
--constraint, --dry-run, --quiet, --verbose and cache/build/configuration flags.
|
|
118
|
-
Configure transport and integrity through the Python package SDK configuration.
|
|
116
|
+
var pythonInstallationHelp = `Usage: python -m pip install [-r/--requirement FILE] [OPTIONS] PACKAGE ...
|
|
117
|
+
python -m pip uninstall [-y/--yes] PACKAGE ...
|
|
118
|
+
Install options: --pre, -U/--upgrade, --force-reinstall, --no-cache-dir
|
|
119
|
+
Compatible wheels; configure transport with the Python package SDK.
|
|
120
|
+
Both accept -h/--help and --. Other pip operations are unsupported.
|
|
119
121
|
`;
|
|
120
|
-
function parsePythonInstallation(
|
|
121
|
-
|
|
122
|
-
scan: for (let index = 0; index < args.length; index++) {
|
|
123
|
-
const option = args[index];
|
|
124
|
-
if (option === "--" || option === "-" || !option.startsWith("-") || option.startsWith("--")) return void 0;
|
|
125
|
-
for (let offset = 1; offset < option.length; offset++) {
|
|
126
|
-
const flag = option[offset];
|
|
127
|
-
if ("h?V".includes(flag)) return void 0;
|
|
128
|
-
if ("cmWX".includes(flag)) {
|
|
129
|
-
const operand = option.slice(offset + 1) || args[++index];
|
|
130
|
-
if (flag === "c") return void 0;
|
|
131
|
-
if (flag === "m") {
|
|
132
|
-
if (operand !== "pip") return void 0;
|
|
133
|
-
pipArgs = args.slice(index + 1);
|
|
134
|
-
break scan;
|
|
135
|
-
}
|
|
136
|
-
break;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
if (!pipArgs) return void 0;
|
|
122
|
+
function parsePythonInstallation(pipArgs) {
|
|
123
|
+
const controls = {};
|
|
141
124
|
const packages = [];
|
|
142
125
|
const requirements = [];
|
|
143
|
-
if (pipArgs.length === 0
|
|
144
|
-
if (pipArgs[0] === "--help" || pipArgs[0] === "-h") {
|
|
126
|
+
if (pipArgs.length === 0 || pipArgs[0] === "--help" || pipArgs[0] === "-h") {
|
|
145
127
|
if (pipArgs.length > 1) throw new PythonInvocationError("unsupported pip option or argument after help: " + pipArgs[1]);
|
|
146
|
-
return { packages, requirements, help: true };
|
|
128
|
+
return { packages, requirements, controls, help: true };
|
|
147
129
|
}
|
|
148
|
-
|
|
130
|
+
const uninstall = pipArgs[0] === "uninstall";
|
|
131
|
+
let yes = false;
|
|
132
|
+
if (!uninstall && pipArgs[0] !== "install") throw new PythonInvocationError("unsupported pip command " + pipArgs[0]);
|
|
149
133
|
let operands = false;
|
|
150
134
|
let help = false;
|
|
151
135
|
for (let index = 1; index < pipArgs.length; index++) {
|
|
@@ -154,6 +138,14 @@ function parsePythonInstallation(args) {
|
|
|
154
138
|
packages.push(option);
|
|
155
139
|
continue;
|
|
156
140
|
}
|
|
141
|
+
if (!uninstall && (option === "--pre" || option === "--no-cache-dir")) {
|
|
142
|
+
controls[option === "--pre" ? "pre" : "noCache"] = true;
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
if (!uninstall && (option === "-U" || option === "--upgrade" || option === "--force-reinstall")) {
|
|
146
|
+
controls[option === "--force-reinstall" ? "forceReinstall" : "upgrade"] = true;
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
157
149
|
if (option === "--") {
|
|
158
150
|
operands = true;
|
|
159
151
|
continue;
|
|
@@ -162,7 +154,11 @@ function parsePythonInstallation(args) {
|
|
|
162
154
|
help = true;
|
|
163
155
|
continue;
|
|
164
156
|
}
|
|
165
|
-
if (option === "-
|
|
157
|
+
if (uninstall && (option === "-y" || option === "--yes")) {
|
|
158
|
+
yes = true;
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
if (!uninstall && (option === "-r" || option === "--requirement" || option.startsWith("--requirement=") || option.startsWith("-r"))) {
|
|
166
162
|
const path = option.startsWith("--requirement=") ? option.slice("--requirement=".length) : option !== "-r" && option.startsWith("-r") ? option.slice(2) : pipArgs[++index];
|
|
167
163
|
if (!path || path.startsWith("-")) throw new PythonInvocationError(option + " requires a path");
|
|
168
164
|
requirements.push(path);
|
|
@@ -170,18 +166,455 @@ function parsePythonInstallation(args) {
|
|
|
170
166
|
}
|
|
171
167
|
throw new PythonInvocationError("unsupported pip option " + option);
|
|
172
168
|
}
|
|
173
|
-
if (!help && packages.length === 0 && requirements.length === 0) throw new PythonInvocationError("pip
|
|
174
|
-
return { packages, requirements, help };
|
|
169
|
+
if (!help && packages.length === 0 && requirements.length === 0) throw new PythonInvocationError("pip " + pipArgs[0] + " requires at least one package or requirements file");
|
|
170
|
+
return { packages, requirements, help, controls, ...uninstall ? { uninstall, yes } : {} };
|
|
175
171
|
}
|
|
176
172
|
|
|
177
173
|
// packages/safe-bash-command-python/src/executor.ts
|
|
178
174
|
import { readBytes, writeBytes } from "../../safe-bash-contracts/io.js";
|
|
179
175
|
import { createOutputOperation } from "../../safe-bash-contracts/output.js";
|
|
180
|
-
import { inheritYieldCheckpoint, yieldTurn } from "../../safe-bash-contracts/yield.js";
|
|
176
|
+
import { inheritYieldCheckpoint, yieldTurn as yieldTurn2 } from "../../safe-bash-contracts/yield.js";
|
|
181
177
|
|
|
182
|
-
// packages/safe-bash-command-python/src/llm-
|
|
183
|
-
import { createLlmSpool, createLlmToolRegistry } from "../../safe-bash-command-llm/index.js";
|
|
178
|
+
// packages/safe-bash-command-python/src/llm-package-manager.ts
|
|
184
179
|
import { toByteSource } from "../../safe-bash-contracts/index.js";
|
|
180
|
+
function createPythonLlmPackageManager(options) {
|
|
181
|
+
if (!options.createExecutor || !options.environment || options.provisioning) throw new TypeError("LLM package management requires an asynchronous executor and a shared Python environment");
|
|
182
|
+
const planners = /* @__PURE__ */ new WeakMap();
|
|
183
|
+
const pool = createPythonExecutorPool({
|
|
184
|
+
createExecutor: options.createExecutor,
|
|
185
|
+
...options.maxConcurrentWorkers === void 0 || options.maxConcurrentWorkers === Infinity ? {} : { maxConcurrentExecutors: options.maxConcurrentWorkers }
|
|
186
|
+
});
|
|
187
|
+
const configuration = { ...options, createExecutor: pool.createExecutor, createCapabilities(context) {
|
|
188
|
+
const capabilities = options.createCapabilities?.(context) ?? {};
|
|
189
|
+
if (capabilities.llm_packages) throw new Error("Python package capability is reserved");
|
|
190
|
+
const capability = planners.get(context.args);
|
|
191
|
+
return capability ? { ...capabilities, llm_packages: capability } : capabilities;
|
|
192
|
+
} };
|
|
193
|
+
const installer = createPythonExecutorCommands(configuration)[0];
|
|
194
|
+
const { environment: ignoredEnvironment, packages: ignoredPackages, requirements: ignoredRequirements, packageProfile: ignoredProfile, ...planning } = configuration;
|
|
195
|
+
const command = createPythonExecutorCommands(planning)[0];
|
|
196
|
+
return async ({ context, args }) => {
|
|
197
|
+
if (!Array.isArray(args) || !["install", "uninstall"].includes(args[0]) || args.some((value) => typeof value !== "string")) throw new TypeError("Invalid LLM package command");
|
|
198
|
+
context.signal.throwIfAborted();
|
|
199
|
+
let planned;
|
|
200
|
+
const { registerCleanup: ignoredCleanup, argumentValues: ignoredArguments, ...isolated } = context;
|
|
201
|
+
const invocation = { ...isolated, command: "python", args: ["-c", pythonLlmPackageProgram, ...args], stdin: toByteSource("") };
|
|
202
|
+
planners.set(invocation.args, { async call(value) {
|
|
203
|
+
if (planned || !Array.isArray(value) || value.some((item) => typeof item !== "string") || value[0] !== "pip" || value[1] !== args[0]) throw new Error("Invalid native LLM package request");
|
|
204
|
+
planned = value.slice(1);
|
|
205
|
+
return null;
|
|
206
|
+
} });
|
|
207
|
+
let result;
|
|
208
|
+
try {
|
|
209
|
+
result = await command.execute(invocation);
|
|
210
|
+
} finally {
|
|
211
|
+
planners.delete(invocation.args);
|
|
212
|
+
}
|
|
213
|
+
if (result.exitCode || !planned) return result;
|
|
214
|
+
context.signal.throwIfAborted();
|
|
215
|
+
return installer.execute({ ...isolated, command: "python", args: ["-m", "pip", ...planned] });
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
var pythonLlmPackageProgram = String.raw`
|
|
219
|
+
import sys
|
|
220
|
+
import safe_host
|
|
221
|
+
import llm.cli
|
|
222
|
+
|
|
223
|
+
def _safe_package_request(module, run_name):
|
|
224
|
+
if module != 'pip' or run_name != '__main__':
|
|
225
|
+
raise RuntimeError('Unexpected LLM package entrypoint')
|
|
226
|
+
safe_host.call('llm_packages', list(sys.argv))
|
|
227
|
+
|
|
228
|
+
llm.cli.run_module = _safe_package_request
|
|
229
|
+
llm.cli.cli.main(args=sys.argv[1:], prog_name='llm')
|
|
230
|
+
`;
|
|
231
|
+
|
|
232
|
+
// packages/safe-bash-command-python/src/llm-plugin-setup.ts
|
|
233
|
+
var pythonLlmPluginSetup = /* @__PURE__ */ (() => String.raw`
|
|
234
|
+
def load_plugins(request):
|
|
235
|
+
import llm.plugins as manager
|
|
236
|
+
manager.load_plugins()
|
|
237
|
+
if request['plugins']:
|
|
238
|
+
original = (manager.DEFAULT_PLUGINS, manager.LLM_LOAD_PLUGINS, manager._loaded)
|
|
239
|
+
try:
|
|
240
|
+
manager.DEFAULT_PLUGINS = ()
|
|
241
|
+
manager.LLM_LOAD_PLUGINS = ','.join(request['plugins'])
|
|
242
|
+
manager._loaded = False
|
|
243
|
+
manager.load_plugins()
|
|
244
|
+
finally:
|
|
245
|
+
manager.DEFAULT_PLUGINS, manager.LLM_LOAD_PLUGINS, manager._loaded = original
|
|
246
|
+
`)();
|
|
247
|
+
|
|
248
|
+
// packages/safe-bash-command-python/src/llm-json-loader.ts
|
|
249
|
+
import { LlmLoaderLookupError } from "../../safe-bash-command-llm/index.js";
|
|
250
|
+
import { toByteSource as toByteSource2 } from "../../safe-bash-contracts/index.js";
|
|
251
|
+
function createPythonLlmJsonLoader(options, capabilityName, program, label) {
|
|
252
|
+
if (!options.createExecutor) throw new TypeError(`Python ${label} loading requires an asynchronous executor`);
|
|
253
|
+
if (options.plugins !== void 0 && (!Array.isArray(options.plugins) || options.plugins.some((name) => typeof name !== "string" || !name || name !== name.trim() || name.includes(",")))) throw new TypeError(`Python ${label} plugins must be explicit distribution names`);
|
|
254
|
+
const plugins = Object.freeze([...options.plugins ?? []]), capabilities = /* @__PURE__ */ new WeakMap();
|
|
255
|
+
const command = createPythonExecutorCommands({ ...options, createCapabilities(context) {
|
|
256
|
+
const provided = options.createCapabilities?.(context) ?? {};
|
|
257
|
+
if (provided[capabilityName]) throw new Error(`Python ${label} capability is reserved`);
|
|
258
|
+
const capability = capabilities.get(context.args);
|
|
259
|
+
if (!capability) throw new Error(`Unknown Python ${label} invocation`);
|
|
260
|
+
return { ...provided, [capabilityName]: capability };
|
|
261
|
+
} })[0];
|
|
262
|
+
return async (request, context, admitBytes) => {
|
|
263
|
+
if (!context) throw new TypeError(`Python ${label} loading requires caller context`);
|
|
264
|
+
const { maxBytes, signal } = context;
|
|
265
|
+
if (maxBytes !== Infinity && (!Number.isSafeInteger(maxBytes) || maxBytes < 0)) throw new RangeError(`Invalid Python ${label} byte limit`);
|
|
266
|
+
signal.throwIfAborted();
|
|
267
|
+
let size = 0, done = false, failure;
|
|
268
|
+
const chunks = [];
|
|
269
|
+
const invocation = { ...context, command: "python", args: ["-c", program], signal, env: context.env ?? {}, stdin: toByteSource2(""), stdout: context.stdout ?? { async write() {
|
|
270
|
+
} }, stderr: context.stderr ?? { async write() {
|
|
271
|
+
} } };
|
|
272
|
+
capabilities.set(invocation.args, { async call(input) {
|
|
273
|
+
try {
|
|
274
|
+
signal.throwIfAborted();
|
|
275
|
+
if (!input || typeof input !== "object" || Array.isArray(input) || done) throw new TypeError(`Invalid Python ${label} message`);
|
|
276
|
+
const message = input;
|
|
277
|
+
if (message.op === "request") return { ...request, plugins };
|
|
278
|
+
if (message.op === "missing" || message.op === "lookup" || message.op === "error") {
|
|
279
|
+
failure ??= message.op === "error" ? new Error(String(message.message)) : new LlmLoaderLookupError(String(message.message));
|
|
280
|
+
done = true;
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
if (message.op === "done") {
|
|
284
|
+
done = true;
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
if (message.op !== "text" || typeof message.text !== "string" || message.text.length > 8192) throw new TypeError(`Invalid Python ${label} text window`);
|
|
288
|
+
const bytes = new TextEncoder().encode(message.text).length;
|
|
289
|
+
if (bytes > 16384) throw new TypeError(`Invalid Python ${label} text window`);
|
|
290
|
+
if (bytes > maxBytes - size) throw new RangeError(`Python ${label} byte limit exceeded`);
|
|
291
|
+
admitBytes?.(bytes);
|
|
292
|
+
size += bytes;
|
|
293
|
+
if (bytes) chunks.push(message.text);
|
|
294
|
+
return null;
|
|
295
|
+
} catch (error) {
|
|
296
|
+
failure ??= error;
|
|
297
|
+
throw error;
|
|
298
|
+
}
|
|
299
|
+
} });
|
|
300
|
+
try {
|
|
301
|
+
const result = await command.execute(invocation);
|
|
302
|
+
signal.throwIfAborted();
|
|
303
|
+
if (failure) throw failure;
|
|
304
|
+
if (result.exitCode) throw new Error(`Python ${label} interpreter exited with status ${result.exitCode}`);
|
|
305
|
+
if (!done) throw new Error(`Python ${label} interpreter returned no result`);
|
|
306
|
+
return JSON.parse(chunks.join(""));
|
|
307
|
+
} finally {
|
|
308
|
+
capabilities.delete(invocation.args);
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// packages/safe-bash-command-python/src/llm-template-loader.ts
|
|
314
|
+
function createPythonLlmTemplateLoader(options, prefix) {
|
|
315
|
+
if (typeof prefix !== "string" || !prefix) throw new TypeError("Python template loader requires a prefix");
|
|
316
|
+
const load = createPythonLlmJsonLoader(options, "llm_templates", pythonLlmTemplateProgram, "template");
|
|
317
|
+
return async (value, signal, context) => {
|
|
318
|
+
if (!context) throw new TypeError("Python template loading requires caller context");
|
|
319
|
+
if (typeof value !== "string") throw new TypeError("Python template input must be a string");
|
|
320
|
+
const template = await load({ prefix, value }, { ...context, signal });
|
|
321
|
+
if (!template || typeof template !== "object" || Array.isArray(template) || typeof template.name !== "string") throw new TypeError("Invalid native Python template");
|
|
322
|
+
return template;
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
var pythonLlmTemplateProgram = /* @__PURE__ */ (() => String.raw`
|
|
326
|
+
import json, llm, safe_host
|
|
327
|
+
|
|
328
|
+
${pythonLlmPluginSetup}
|
|
329
|
+
|
|
330
|
+
def send(op, **fields):
|
|
331
|
+
return safe_host.call('llm_templates', dict(op=op, **fields))
|
|
332
|
+
|
|
333
|
+
def main():
|
|
334
|
+
request = send('request')
|
|
335
|
+
try:
|
|
336
|
+
load_plugins(request)
|
|
337
|
+
loaders = llm.get_template_loaders()
|
|
338
|
+
except Exception as error:
|
|
339
|
+
send('lookup', message=str(error))
|
|
340
|
+
return
|
|
341
|
+
if request['prefix'] not in loaders:
|
|
342
|
+
send('missing', message='Unknown template prefix: ' + request['prefix'])
|
|
343
|
+
return
|
|
344
|
+
template = loaders[request['prefix']](request['value'])
|
|
345
|
+
encoder = json.JSONEncoder(ensure_ascii=False, separators=(',', ':'))
|
|
346
|
+
for part in encoder.iterencode(template.model_dump(exclude_none=True)):
|
|
347
|
+
for offset in range(0, len(part), 4096): send('text', text=part[offset:offset + 4096])
|
|
348
|
+
send('done')
|
|
349
|
+
try:
|
|
350
|
+
main()
|
|
351
|
+
except Exception as error:
|
|
352
|
+
send('error', message=str(error))
|
|
353
|
+
`)();
|
|
354
|
+
|
|
355
|
+
// packages/safe-bash-command-python/src/llm-fragment-loader.ts
|
|
356
|
+
import { createLlmSpool, createLlmUrlSource, LlmLoaderLookupError as LlmLoaderLookupError2 } from "../../safe-bash-command-llm/index.js";
|
|
357
|
+
import { toByteSource as toByteSource3 } from "../../safe-bash-contracts/index.js";
|
|
358
|
+
function deferred() {
|
|
359
|
+
let resolve, reject;
|
|
360
|
+
const promise = new Promise((yes, no) => {
|
|
361
|
+
resolve = yes;
|
|
362
|
+
reject = no;
|
|
363
|
+
});
|
|
364
|
+
void promise.catch(() => {
|
|
365
|
+
});
|
|
366
|
+
return { promise, resolve, reject };
|
|
367
|
+
}
|
|
368
|
+
function createPythonLlmFragmentLoader(options, prefix) {
|
|
369
|
+
if (!options.createExecutor) throw new TypeError("Python fragment loading requires an asynchronous executor");
|
|
370
|
+
if (typeof prefix !== "string" || !prefix) throw new TypeError("Python fragment loader requires a prefix");
|
|
371
|
+
if (options.plugins !== void 0 && (!Array.isArray(options.plugins) || options.plugins.some((name) => typeof name !== "string" || !name || name !== name.trim() || name.includes(",")))) throw new TypeError("Python fragment plugins must be explicit distribution names");
|
|
372
|
+
const plugins = Object.freeze([...options.plugins ?? []]), capabilities = /* @__PURE__ */ new WeakMap();
|
|
373
|
+
const command = createPythonExecutorCommands({ ...options, createCapabilities(context) {
|
|
374
|
+
const provided = options.createCapabilities?.(context) ?? {};
|
|
375
|
+
if (provided.llm_fragments) throw new Error("Python fragment capability is reserved");
|
|
376
|
+
const capability = capabilities.get(context.args);
|
|
377
|
+
if (!capability) throw new Error("Unknown Python fragment invocation");
|
|
378
|
+
return { ...provided, llm_fragments: capability };
|
|
379
|
+
} })[0];
|
|
380
|
+
return async function* (value, context) {
|
|
381
|
+
if (typeof value !== "string") throw new TypeError("Python fragment input must be a string");
|
|
382
|
+
const { fs, cwd, maxBytes } = context;
|
|
383
|
+
if (maxBytes !== Infinity && (!Number.isSafeInteger(maxBytes) || maxBytes < 0)) throw new RangeError("Invalid Python fragment byte limit");
|
|
384
|
+
const controller = new AbortController(), signal = AbortSignal.any([context.signal, controller.signal]);
|
|
385
|
+
signal.throwIfAborted();
|
|
386
|
+
let ready = deferred(), resume;
|
|
387
|
+
let current, spool, closed = false, bytes = 0;
|
|
388
|
+
const sources = /* @__PURE__ */ new Set();
|
|
389
|
+
const admit = (size) => {
|
|
390
|
+
if (size > maxBytes - bytes) throw new RangeError("Python fragment byte limit exceeded");
|
|
391
|
+
bytes += size;
|
|
392
|
+
};
|
|
393
|
+
const fail = (error) => {
|
|
394
|
+
ready.reject(error);
|
|
395
|
+
resume?.resolve(false);
|
|
396
|
+
controller.abort(error);
|
|
397
|
+
};
|
|
398
|
+
const aborted = () => {
|
|
399
|
+
ready.reject(signal.reason);
|
|
400
|
+
resume?.resolve(false);
|
|
401
|
+
};
|
|
402
|
+
signal.addEventListener("abort", aborted, { once: true });
|
|
403
|
+
const retire = async (source) => {
|
|
404
|
+
try {
|
|
405
|
+
await source.dispose();
|
|
406
|
+
} finally {
|
|
407
|
+
sources.delete(source);
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
const dispatch = async (input) => {
|
|
411
|
+
signal.throwIfAborted();
|
|
412
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) throw new TypeError("Invalid Python fragment message");
|
|
413
|
+
const message = input;
|
|
414
|
+
if (message.op === "request") return { prefix, value, plugins };
|
|
415
|
+
if (message.op === "missing" || message.op === "lookup") throw new LlmLoaderLookupError2(String(message.message));
|
|
416
|
+
if (message.op === "error") throw new Error(String(message.message));
|
|
417
|
+
if (message.op === "begin") {
|
|
418
|
+
if (current || resume) throw new Error("Python fragment already active");
|
|
419
|
+
if (message.type !== "text" && message.type !== "attachment") throw new TypeError("Invalid Python fragment type");
|
|
420
|
+
if (message.type === "attachment" && (typeof message.mimeType !== "string" || typeof message.id !== "string")) throw new TypeError("Invalid Python fragment metadata");
|
|
421
|
+
let source;
|
|
422
|
+
if (message.url !== void 0) {
|
|
423
|
+
if (message.type !== "attachment" || typeof message.url !== "string" || !context.capabilities?.fetch) throw new TypeError("Attachment URL loading is not configured");
|
|
424
|
+
source = createLlmUrlSource({ url: message.url, fetch: context.capabilities.fetch, signal, maxBytes: maxBytes - bytes, admitBytes: admit });
|
|
425
|
+
} else {
|
|
426
|
+
const owned = await createLlmSpool(fs, cwd, signal, "input");
|
|
427
|
+
spool = owned;
|
|
428
|
+
source = { bytes: owned.replay(), dispose: () => owned.close() };
|
|
429
|
+
}
|
|
430
|
+
sources.add(source);
|
|
431
|
+
current = message.type === "text" ? { type: "text", source: { ...source, normalizeNewlines: false } } : { type: "attachment", source, mimeType: message.mimeType, id: message.id };
|
|
432
|
+
sources.delete(source);
|
|
433
|
+
sources.add(current.source);
|
|
434
|
+
return null;
|
|
435
|
+
}
|
|
436
|
+
if (!current) throw new Error("No active Python fragment");
|
|
437
|
+
if (message.op === "text" || message.op === "bytes") {
|
|
438
|
+
if (!spool) throw new Error("Remote Python fragments cannot accept inline bytes");
|
|
439
|
+
let chunk;
|
|
440
|
+
if (message.op === "text") {
|
|
441
|
+
if (current.type !== "text" || typeof message.text !== "string" || message.text.length > 8192) throw new TypeError("Invalid Python fragment text window");
|
|
442
|
+
chunk = new TextEncoder().encode(message.text);
|
|
443
|
+
if (chunk.length > 16384) throw new TypeError("Invalid Python fragment text window");
|
|
444
|
+
} else {
|
|
445
|
+
if (current.type !== "attachment" || !Array.isArray(message.bytes) || message.bytes.length > 4096 || message.bytes.some((byte) => typeof byte !== "number" || !Number.isInteger(byte) || byte < 0 || byte > 255)) throw new TypeError("Invalid Python fragment byte window");
|
|
446
|
+
chunk = Uint8Array.from(message.bytes);
|
|
447
|
+
}
|
|
448
|
+
admit(chunk.length);
|
|
449
|
+
await spool.write(chunk);
|
|
450
|
+
return null;
|
|
451
|
+
}
|
|
452
|
+
if (message.op === "end") {
|
|
453
|
+
const continuation = resume = deferred();
|
|
454
|
+
ready.resolve(current);
|
|
455
|
+
return continuation.promise;
|
|
456
|
+
}
|
|
457
|
+
throw new TypeError("Invalid Python fragment operation");
|
|
458
|
+
};
|
|
459
|
+
const invocation = { ...context, command: "python", args: ["-c", pythonLlmFragmentProgram], fs, cwd, signal, env: context.env ?? {}, stdin: toByteSource3(""), stdout: context.stdout ?? { async write() {
|
|
460
|
+
} }, stderr: context.stderr ?? { async write() {
|
|
461
|
+
} } };
|
|
462
|
+
capabilities.set(invocation.args, { async call(input) {
|
|
463
|
+
try {
|
|
464
|
+
return await dispatch(input);
|
|
465
|
+
} catch (error) {
|
|
466
|
+
fail(error);
|
|
467
|
+
throw error;
|
|
468
|
+
}
|
|
469
|
+
} });
|
|
470
|
+
const running = Promise.resolve().then(() => command.execute(invocation)).then((result) => {
|
|
471
|
+
if (closed) return;
|
|
472
|
+
if (result.exitCode) fail(new Error(`Python fragment interpreter exited with status ${result.exitCode}`));
|
|
473
|
+
else ready.resolve(void 0);
|
|
474
|
+
}, (error) => {
|
|
475
|
+
if (!closed) fail(error);
|
|
476
|
+
});
|
|
477
|
+
let closing;
|
|
478
|
+
const close = () => closing ??= (async () => {
|
|
479
|
+
closed = true;
|
|
480
|
+
resume?.resolve(false);
|
|
481
|
+
controller.abort(new Error("Python fragment session closed"));
|
|
482
|
+
await running;
|
|
483
|
+
capabilities.delete(invocation.args);
|
|
484
|
+
signal.removeEventListener("abort", aborted);
|
|
485
|
+
await Promise.all([...sources].map(retire));
|
|
486
|
+
})();
|
|
487
|
+
context.registerCleanup?.(close);
|
|
488
|
+
try {
|
|
489
|
+
while (true) {
|
|
490
|
+
const fragment = await ready.promise;
|
|
491
|
+
if (!fragment) break;
|
|
492
|
+
let advance = false;
|
|
493
|
+
try {
|
|
494
|
+
yield fragment;
|
|
495
|
+
advance = true;
|
|
496
|
+
} finally {
|
|
497
|
+
await retire(fragment.source);
|
|
498
|
+
current = void 0;
|
|
499
|
+
spool = void 0;
|
|
500
|
+
ready = deferred();
|
|
501
|
+
const continuation = resume;
|
|
502
|
+
resume = void 0;
|
|
503
|
+
continuation?.resolve(advance);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
} finally {
|
|
507
|
+
await close();
|
|
508
|
+
}
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
var pythonLlmFragmentProgram = /* @__PURE__ */ (() => String.raw`
|
|
512
|
+
import hashlib, llm, safe_host
|
|
513
|
+
from llm_safe_host import _attachment_type
|
|
514
|
+
|
|
515
|
+
${pythonLlmPluginSetup}
|
|
516
|
+
|
|
517
|
+
def send(op, **fields):
|
|
518
|
+
return safe_host.call('llm_fragments', dict(op=op, **fields))
|
|
519
|
+
|
|
520
|
+
def emit_attachment(value):
|
|
521
|
+
mime = _attachment_type(value)
|
|
522
|
+
if not value.content and value.path:
|
|
523
|
+
with open(value.path, 'rb') as source:
|
|
524
|
+
if value._id is None:
|
|
525
|
+
digest = hashlib.sha256()
|
|
526
|
+
for chunk in iter(lambda: source.read(4096), b''): digest.update(chunk)
|
|
527
|
+
value._id = digest.hexdigest()
|
|
528
|
+
source.seek(0)
|
|
529
|
+
send('begin', type='attachment', mimeType=mime, id=value.id())
|
|
530
|
+
for chunk in iter(lambda: source.read(4096), b''): send('bytes', bytes=list(chunk))
|
|
531
|
+
elif not value.content and value.url:
|
|
532
|
+
send('begin', type='attachment', mimeType=mime, id=value.id(), url=value.url)
|
|
533
|
+
else:
|
|
534
|
+
send('begin', type='attachment', mimeType=mime, id=value.id())
|
|
535
|
+
content = memoryview(value.content or b'')
|
|
536
|
+
for offset in range(0, len(content), 4096): send('bytes', bytes=list(content[offset:offset + 4096]))
|
|
537
|
+
|
|
538
|
+
def main():
|
|
539
|
+
request = send('request')
|
|
540
|
+
try:
|
|
541
|
+
load_plugins(request)
|
|
542
|
+
loaders = llm.get_fragment_loaders()
|
|
543
|
+
except Exception as error:
|
|
544
|
+
send('lookup', message=str(error))
|
|
545
|
+
return
|
|
546
|
+
if request['prefix'] not in loaders:
|
|
547
|
+
send('missing', message='Unknown fragment prefix: ' + request['prefix'])
|
|
548
|
+
return
|
|
549
|
+
result = loaders[request['prefix']](request['value'])
|
|
550
|
+
if not isinstance(result, list): result = [result]
|
|
551
|
+
for value in result:
|
|
552
|
+
if isinstance(value, llm.Attachment): emit_attachment(value)
|
|
553
|
+
elif isinstance(value, str):
|
|
554
|
+
send('begin', type='text')
|
|
555
|
+
for offset in range(0, len(value), 4096): send('text', text=value[offset:offset + 4096])
|
|
556
|
+
else: raise TypeError('Fragment loader must return text or attachments')
|
|
557
|
+
if not send('end'): return
|
|
558
|
+
try:
|
|
559
|
+
main()
|
|
560
|
+
except Exception as error:
|
|
561
|
+
send('error', message=str(error))
|
|
562
|
+
`)();
|
|
563
|
+
|
|
564
|
+
// packages/safe-bash-command-python/src/llm-loader-discovery.ts
|
|
565
|
+
function createPythonLlmLoaderProvider(options) {
|
|
566
|
+
const discover = createPythonLlmLoaderDiscovery(options), configured = { ...options, plugins: Object.freeze([...options.plugins ?? []]) };
|
|
567
|
+
return { discover, fragments: createPythonLlmFragmentLoader.bind(null, configured), templates: createPythonLlmTemplateLoader.bind(null, configured) };
|
|
568
|
+
}
|
|
569
|
+
function createPythonLlmLoaderDiscovery(options) {
|
|
570
|
+
const load = createPythonLlmJsonLoader(options, "llm_loaders", pythonLlmLoaderDiscoveryProgram, "loader discovery");
|
|
571
|
+
const configured = { ...options, plugins: Object.freeze([...options.plugins ?? []]) };
|
|
572
|
+
return async (context) => {
|
|
573
|
+
if (context.kind !== void 0 && context.kind !== "fragments" && context.kind !== "templates") throw new TypeError("Invalid native loader discovery kind");
|
|
574
|
+
const result = await load(context.kind === void 0 ? {} : { kind: context.kind }, context, context.admitBytes);
|
|
575
|
+
if (!result || typeof result !== "object" || Array.isArray(result)) throw new TypeError("Invalid native loader discovery");
|
|
576
|
+
function loaders(entries, create) {
|
|
577
|
+
if (!Array.isArray(entries)) throw new TypeError("Invalid native loader discovery");
|
|
578
|
+
const values = /* @__PURE__ */ new Map();
|
|
579
|
+
for (const entry of entries) {
|
|
580
|
+
if (!Array.isArray(entry) || entry.length !== 2 || typeof entry[0] !== "string" || !entry[0] || values.has(entry[0]) || entry[1] !== null && typeof entry[1] !== "string") throw new TypeError("Invalid native loader registration");
|
|
581
|
+
const loader = create(configured, entry[0]);
|
|
582
|
+
if (entry[1] !== null) Object.defineProperty(loader, "description", { value: entry[1], enumerable: true });
|
|
583
|
+
values.set(entry[0], loader);
|
|
584
|
+
}
|
|
585
|
+
return values;
|
|
586
|
+
}
|
|
587
|
+
const data = result;
|
|
588
|
+
return { fragmentLoaders: loaders(data.fragments, createPythonLlmFragmentLoader), templateLoaders: loaders(data.templates, createPythonLlmTemplateLoader) };
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
var pythonLlmLoaderDiscoveryProgram = /* @__PURE__ */ (() => String.raw`
|
|
592
|
+
import json, llm, safe_host
|
|
593
|
+
|
|
594
|
+
${pythonLlmPluginSetup}
|
|
595
|
+
|
|
596
|
+
def send(op, **fields):
|
|
597
|
+
return safe_host.call('llm_loaders', dict(op=op, **fields))
|
|
598
|
+
|
|
599
|
+
def main():
|
|
600
|
+
request = send('request')
|
|
601
|
+
load_plugins(request)
|
|
602
|
+
encoder = json.JSONEncoder(ensure_ascii=False, separators=(',', ':'))
|
|
603
|
+
kind = request.get('kind')
|
|
604
|
+
result = dict(fragments=[] if kind == 'templates' else [(prefix, loader.__doc__) for prefix, loader in llm.get_fragment_loaders().items()],
|
|
605
|
+
templates=[] if kind == 'fragments' else [(prefix, loader.__doc__) for prefix, loader in llm.get_template_loaders().items()])
|
|
606
|
+
for part in encoder.iterencode(result):
|
|
607
|
+
for offset in range(0, len(part), 4096): send('text', text=part[offset:offset + 4096])
|
|
608
|
+
send('done')
|
|
609
|
+
try:
|
|
610
|
+
main()
|
|
611
|
+
except Exception as error:
|
|
612
|
+
send('error', message=str(error))
|
|
613
|
+
`)();
|
|
614
|
+
|
|
615
|
+
// packages/safe-bash-command-python/src/llm-functions-loader.ts
|
|
616
|
+
import { createLlmSpool as createLlmSpool2, createLlmToolRegistry } from "../../safe-bash-command-llm/index.js";
|
|
617
|
+
import { toByteSource as toByteSource4 } from "../../safe-bash-contracts/index.js";
|
|
185
618
|
|
|
186
619
|
// packages/safe-bash-command-python/src/llm-functions-program.ts
|
|
187
620
|
var pythonLlmFunctionsProgram = /* @__PURE__ */ (() => String.raw`
|
|
@@ -248,6 +681,20 @@ async def main():
|
|
|
248
681
|
discovery = selection.get("discovery", False)
|
|
249
682
|
tools = []
|
|
250
683
|
try:
|
|
684
|
+
requested_plugins = selection.get("plugins", [])
|
|
685
|
+
if requested_plugins:
|
|
686
|
+
import llm.plugins as plugin_manager
|
|
687
|
+
plugin_manager.load_plugins()
|
|
688
|
+
original = (plugin_manager.DEFAULT_PLUGINS, plugin_manager.LLM_LOAD_PLUGINS, plugin_manager._loaded)
|
|
689
|
+
try:
|
|
690
|
+
# Reuse native explicit-distribution loading. The host's tooling
|
|
691
|
+
# registration guard and ambient-entrypoint prohibition stay active.
|
|
692
|
+
plugin_manager.DEFAULT_PLUGINS = ()
|
|
693
|
+
plugin_manager.LLM_LOAD_PLUGINS = ",".join(requested_plugins)
|
|
694
|
+
plugin_manager._loaded = False
|
|
695
|
+
plugin_manager.load_plugins()
|
|
696
|
+
finally:
|
|
697
|
+
plugin_manager.DEFAULT_PLUGINS, plugin_manager.LLM_LOAD_PLUGINS, plugin_manager._loaded = original
|
|
251
698
|
plugin_query = selection.get("pluginQuery")
|
|
252
699
|
if plugin_query is not None:
|
|
253
700
|
plugins = get_plugins(plugin_query["all"])
|
|
@@ -356,7 +803,7 @@ asyncio.run(main())
|
|
|
356
803
|
`)();
|
|
357
804
|
|
|
358
805
|
// packages/safe-bash-command-python/src/llm-functions-loader.ts
|
|
359
|
-
function
|
|
806
|
+
function deferred2() {
|
|
360
807
|
let resolve, reject;
|
|
361
808
|
const promise = new Promise((yes, no) => {
|
|
362
809
|
resolve = yes;
|
|
@@ -370,6 +817,8 @@ function record(value) {
|
|
|
370
817
|
}
|
|
371
818
|
function createPythonLlmToolLoader(options) {
|
|
372
819
|
if (!options.createExecutor) throw new TypeError("Python tool loading requires an asynchronous executor");
|
|
820
|
+
if (options.plugins !== void 0 && (!Array.isArray(options.plugins) || options.plugins.some((name) => typeof name !== "string" || !name || name !== name.trim() || name.includes(",")))) throw new TypeError("Python tool plugins must be explicit distribution names");
|
|
821
|
+
const pluginNames = Object.freeze([...options.plugins ?? []]);
|
|
373
822
|
const capabilitiesByArguments = /* @__PURE__ */ new WeakMap();
|
|
374
823
|
const command = createPythonExecutorCommands({ ...options, createCapabilities: (current) => {
|
|
375
824
|
const capabilities = options.createCapabilities?.(current) ?? {};
|
|
@@ -387,7 +836,7 @@ function createPythonLlmToolLoader(options) {
|
|
|
387
836
|
const controller = new AbortController();
|
|
388
837
|
const runtimeController = new AbortController();
|
|
389
838
|
const signal = AbortSignal.any([context.signal, controller.signal]);
|
|
390
|
-
const ready =
|
|
839
|
+
const ready = deferred2();
|
|
391
840
|
const tools = [];
|
|
392
841
|
const toolboxes = [];
|
|
393
842
|
let plugins;
|
|
@@ -395,7 +844,7 @@ function createPythonLlmToolLoader(options) {
|
|
|
395
844
|
const jobs = /* @__PURE__ */ new Map();
|
|
396
845
|
const queue = [];
|
|
397
846
|
let waiter;
|
|
398
|
-
let
|
|
847
|
+
let serial3 = 0, controls = 0, loaded = false, initializationFailed = false, closed = false, closing;
|
|
399
848
|
const encoder = new TextEncoder();
|
|
400
849
|
const fail = (error) => {
|
|
401
850
|
ready.reject(error);
|
|
@@ -411,7 +860,7 @@ function createPythonLlmToolLoader(options) {
|
|
|
411
860
|
const aborted = () => fail(signal.reason);
|
|
412
861
|
signal.addEventListener("abort", aborted, { once: true });
|
|
413
862
|
const spool = async () => {
|
|
414
|
-
const value = await
|
|
863
|
+
const value = await createLlmSpool2(context.fs, context.cwd, signal);
|
|
415
864
|
spools.add(value);
|
|
416
865
|
if (closed || signal.aborted) {
|
|
417
866
|
await value.close();
|
|
@@ -438,7 +887,7 @@ function createPythonLlmToolLoader(options) {
|
|
|
438
887
|
try {
|
|
439
888
|
output = await spool();
|
|
440
889
|
callContext.signal.throwIfAborted();
|
|
441
|
-
const result =
|
|
890
|
+
const result = deferred2(), id = ++serial3;
|
|
442
891
|
jobs.set(id, { result, output, attachments: [], files: /* @__PURE__ */ new Map(), bytes: 0, limit: Math.min(callContext.maxBytes, maxOutputBytes) });
|
|
443
892
|
const request = { id, ...fields };
|
|
444
893
|
if (waiter) {
|
|
@@ -464,7 +913,7 @@ function createPythonLlmToolLoader(options) {
|
|
|
464
913
|
const message = record(value);
|
|
465
914
|
if (message.op !== "next" && message.op !== "failed") signal.throwIfAborted();
|
|
466
915
|
if (message.op === "definitions") return [...definitions];
|
|
467
|
-
if (message.op === "selection") return { names: [...toolNames], discovery, ...pluginQuery ? { pluginQuery: { all: pluginQuery.all, hooks: [...pluginQuery.hooks] } } : {} };
|
|
916
|
+
if (message.op === "selection") return { names: [...toolNames], discovery, ...pluginNames.length ? { plugins: [...pluginNames] } : {}, ...pluginQuery ? { pluginQuery: { all: pluginQuery.all, hooks: [...pluginQuery.hooks] } } : {} };
|
|
468
917
|
if (message.op === "admit") {
|
|
469
918
|
if (typeof message.size !== "number" || !Number.isSafeInteger(message.size) || message.size < 0) throw new TypeError("Invalid Python definition byte count");
|
|
470
919
|
controls += message.size;
|
|
@@ -541,7 +990,7 @@ function createPythonLlmToolLoader(options) {
|
|
|
541
990
|
if (signal.aborted) return { cancel: true };
|
|
542
991
|
if (!loaded || waiter) throw new Error("Invalid Python tool request queue");
|
|
543
992
|
if (queue.length) return queue.shift();
|
|
544
|
-
waiter =
|
|
993
|
+
waiter = deferred2();
|
|
545
994
|
return waiter.promise;
|
|
546
995
|
}
|
|
547
996
|
if (typeof message.id !== "number") throw new TypeError("Invalid Python tool call identity");
|
|
@@ -604,7 +1053,7 @@ function createPythonLlmToolLoader(options) {
|
|
|
604
1053
|
}
|
|
605
1054
|
} };
|
|
606
1055
|
const { registerCleanup: ignoredParentCleanup, ...isolatedContext } = context;
|
|
607
|
-
const invocation = { ...isolatedContext, signal: runtimeController.signal, command: "python", args: ["-c", pythonLlmFunctionsProgram], stdin:
|
|
1056
|
+
const invocation = { ...isolatedContext, signal: runtimeController.signal, command: "python", args: ["-c", pythonLlmFunctionsProgram], stdin: toByteSource4("") };
|
|
608
1057
|
capabilitiesByArguments.set(invocation.args, capability);
|
|
609
1058
|
const running = Promise.resolve().then(() => command.execute(invocation)).then(
|
|
610
1059
|
(result) => {
|
|
@@ -644,6 +1093,352 @@ function createPythonLlmToolLoader(options) {
|
|
|
644
1093
|
};
|
|
645
1094
|
}
|
|
646
1095
|
|
|
1096
|
+
// packages/safe-bash-command-python/src/build-environment.ts
|
|
1097
|
+
function createPythonBuildEnvironment(options = {}) {
|
|
1098
|
+
const manifestStore = createPythonPackageManifestStore({ maxEntries: 1, ...options.maxManifestBytes === void 0 ? {} : { maxBytes: options.maxManifestBytes } });
|
|
1099
|
+
const isolated = { ...options, manifestStore, scope: "build" };
|
|
1100
|
+
delete isolated.requirements;
|
|
1101
|
+
delete isolated.requirementFiles;
|
|
1102
|
+
delete isolated.profile;
|
|
1103
|
+
const environment = createPythonPackageEnvironment(isolated);
|
|
1104
|
+
let disposing;
|
|
1105
|
+
return { ...environment, dispose() {
|
|
1106
|
+
return disposing ??= environment.dispose().finally(() => manifestStore.dispose());
|
|
1107
|
+
} };
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
// packages/safe-bash-command-python/src/build-dependencies.ts
|
|
1111
|
+
import { toByteSource as toByteSource6 } from "../../safe-bash-contracts/index.js";
|
|
1112
|
+
|
|
1113
|
+
// packages/safe-bash-command-python/src/build-backend.ts
|
|
1114
|
+
import { toByteSource as toByteSource5 } from "../../safe-bash-contracts/index.js";
|
|
1115
|
+
function createPythonBuildBackend(options) {
|
|
1116
|
+
if (!options.createExecutor || !options.environment || options.provisioning) throw new TypeError("Python build hooks require an asynchronous executor and explicit environment");
|
|
1117
|
+
const capabilities = /* @__PURE__ */ new WeakMap();
|
|
1118
|
+
const environment = options.environment;
|
|
1119
|
+
const command = createPythonExecutorCommands({ ...options, environment: { ...environment, async prepare(context) {
|
|
1120
|
+
return { ...await environment.prepare(context), bootstrap: true };
|
|
1121
|
+
} }, createCapabilities(context) {
|
|
1122
|
+
const provided = options.createCapabilities?.(context) ?? {};
|
|
1123
|
+
if (provided.python_build) throw new Error("Python build capability is reserved");
|
|
1124
|
+
const capability = capabilities.get(context.args);
|
|
1125
|
+
if (!capability) throw new Error("Unknown Python build invocation");
|
|
1126
|
+
return { ...provided, python_build: capability };
|
|
1127
|
+
} })[0];
|
|
1128
|
+
return async function run(input, context) {
|
|
1129
|
+
context.signal.throwIfAborted();
|
|
1130
|
+
const { maxBytes } = context;
|
|
1131
|
+
if (maxBytes !== Infinity && (!Number.isSafeInteger(maxBytes) || maxBytes < 0)) throw new RangeError("Invalid Python build metadata limit");
|
|
1132
|
+
if (!input || !["read_build_system", "check_build_requirements", "get_requires_for_build_wheel", "build_wheel"].includes(input.hook) || typeof input.source !== "string" || !input.source) throw new TypeError("Invalid Python build hook request");
|
|
1133
|
+
if (input.hook === "check_build_requirements") {
|
|
1134
|
+
if ([input.requirements, input.installed].some((values) => !Array.isArray(values) || values.some((value) => typeof value !== "string"))) throw new TypeError("Invalid Python build requirements request");
|
|
1135
|
+
} else if (input.hook === "read_build_system") {
|
|
1136
|
+
if (input.name !== void 0 && typeof input.name !== "string" || input.usePep517 !== void 0 && typeof input.usePep517 !== "boolean") throw new TypeError("Invalid Python build system request");
|
|
1137
|
+
} else {
|
|
1138
|
+
if (typeof input.backend !== "string" || !input.backend || input.backendPath !== void 0 && (!Array.isArray(input.backendPath) || input.backendPath.some((path) => typeof path !== "string")) || input.hook === "build_wheel" && (typeof input.wheelDirectory !== "string" || !input.wheelDirectory || input.metadataDirectory !== void 0 && typeof input.metadataDirectory !== "string")) throw new TypeError("Invalid Python build hook request");
|
|
1139
|
+
if (input.configSettings !== void 0 && (!input.configSettings || typeof input.configSettings !== "object" || Array.isArray(input.configSettings) || Object.values(input.configSettings).some((value) => typeof value !== "string" && (!Array.isArray(value) || value.some((item) => typeof item !== "string"))))) throw new TypeError("Invalid Python build configuration");
|
|
1140
|
+
}
|
|
1141
|
+
const request = structuredClone(input.hook === "read_build_system" ? { ...input, maxBytes: maxBytes === Infinity ? null : maxBytes } : input);
|
|
1142
|
+
let done = false, size = 0, failure;
|
|
1143
|
+
const chunks = [];
|
|
1144
|
+
const invocation = { ...context, command: "python", args: ["-c", pythonBuildBackendProgram], stdin: toByteSource5("") };
|
|
1145
|
+
capabilities.set(invocation.args, { async call(value) {
|
|
1146
|
+
try {
|
|
1147
|
+
context.signal.throwIfAborted();
|
|
1148
|
+
if (!value || typeof value !== "object" || Array.isArray(value) || done) throw new TypeError("Invalid Python build message");
|
|
1149
|
+
const message = value;
|
|
1150
|
+
if (message.op === "request") return request;
|
|
1151
|
+
if (message.op === "error") {
|
|
1152
|
+
if (typeof message.type !== "string" || typeof message.message !== "string") throw new TypeError("Invalid Python build failure");
|
|
1153
|
+
failure ??= Object.assign(new Error(message.message), { name: message.type });
|
|
1154
|
+
done = true;
|
|
1155
|
+
return null;
|
|
1156
|
+
}
|
|
1157
|
+
if (message.op === "done") {
|
|
1158
|
+
done = true;
|
|
1159
|
+
return null;
|
|
1160
|
+
}
|
|
1161
|
+
if (message.op !== "text" || typeof message.text !== "string" || message.text.length > 8192) throw new TypeError("Invalid Python build metadata window");
|
|
1162
|
+
const bytes = new TextEncoder().encode(message.text).length;
|
|
1163
|
+
if (bytes > 16384 || bytes > maxBytes - size) throw new RangeError("Python build metadata limit exceeded");
|
|
1164
|
+
size += bytes;
|
|
1165
|
+
if (bytes) chunks.push(message.text);
|
|
1166
|
+
return null;
|
|
1167
|
+
} catch (error) {
|
|
1168
|
+
failure ??= error;
|
|
1169
|
+
throw error;
|
|
1170
|
+
}
|
|
1171
|
+
} });
|
|
1172
|
+
try {
|
|
1173
|
+
const result = await command.execute(invocation);
|
|
1174
|
+
context.signal.throwIfAborted();
|
|
1175
|
+
if (failure) throw failure;
|
|
1176
|
+
if (result.exitCode) throw new Error(`Python build interpreter exited with status ${result.exitCode}`);
|
|
1177
|
+
if (!done) throw new Error("Python build interpreter returned no result");
|
|
1178
|
+
const value = JSON.parse(chunks.join(""));
|
|
1179
|
+
if (request.hook === "check_build_requirements") {
|
|
1180
|
+
const result2 = value;
|
|
1181
|
+
if (!result2 || !Array.isArray(result2.missing) || result2.missing.some((value2) => typeof value2 !== "string") || !Array.isArray(result2.conflicting) || result2.conflicting.some((pair) => !Array.isArray(pair) || pair.length !== 2 || pair.some((value2) => typeof value2 !== "string"))) throw new TypeError("Invalid Python build requirement status");
|
|
1182
|
+
} else if (request.hook === "read_build_system") {
|
|
1183
|
+
if (value !== null) {
|
|
1184
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new TypeError("Invalid Python build system");
|
|
1185
|
+
const details = value;
|
|
1186
|
+
if (typeof details.backend !== "string" || !details.backend || ["requires", "check", "backendPath"].some((key) => !Array.isArray(details[key]) || details[key].some((item) => typeof item !== "string"))) throw new TypeError("Invalid Python build system");
|
|
1187
|
+
}
|
|
1188
|
+
} else if (request.hook === "build_wheel") {
|
|
1189
|
+
if (typeof value !== "string" || !value.endsWith(".whl") || value.includes("/") || value.includes("\\") || value.includes("\0")) throw new TypeError("Invalid Python build wheel filename");
|
|
1190
|
+
} else if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) throw new TypeError("Invalid Python build requirements");
|
|
1191
|
+
return value;
|
|
1192
|
+
} finally {
|
|
1193
|
+
capabilities.delete(invocation.args);
|
|
1194
|
+
}
|
|
1195
|
+
};
|
|
1196
|
+
}
|
|
1197
|
+
var pythonBuildBackendProgram = /* @__PURE__ */ (() => String.raw`
|
|
1198
|
+
import importlib, json, os, sys, safe_host
|
|
1199
|
+
|
|
1200
|
+
def send(op, **fields):
|
|
1201
|
+
return safe_host.call('python_build', dict(op=op, **fields))
|
|
1202
|
+
|
|
1203
|
+
def parse_build_requirement(value):
|
|
1204
|
+
from urllib.parse import urlparse, urlunparse
|
|
1205
|
+
from micropip._vendored.packaging.src.packaging.requirements import Requirement, InvalidRequirement
|
|
1206
|
+
requirement = Requirement(value)
|
|
1207
|
+
if requirement.url:
|
|
1208
|
+
url = urlparse(requirement.url)
|
|
1209
|
+
if (url.scheme == 'file' and urlunparse(url) != requirement.url) or (url.scheme != 'file' and not (url.scheme and url.netloc)): raise InvalidRequirement(value)
|
|
1210
|
+
return requirement
|
|
1211
|
+
|
|
1212
|
+
def check_build_requirements(request):
|
|
1213
|
+
from importlib.metadata import distribution, PackageNotFoundError
|
|
1214
|
+
from micropip._vendored.packaging.src.packaging.utils import canonicalize_name
|
|
1215
|
+
from micropip._vendored.packaging.src.packaging.version import Version
|
|
1216
|
+
installed = {canonicalize_name(name) for name in request['installed']}
|
|
1217
|
+
conflicting, missing = set(), set()
|
|
1218
|
+
for value in request['requirements']:
|
|
1219
|
+
requirement = parse_build_requirement(value)
|
|
1220
|
+
if canonicalize_name(requirement.name) not in installed:
|
|
1221
|
+
missing.add(value)
|
|
1222
|
+
continue
|
|
1223
|
+
try: version = Version(distribution(requirement.name).version)
|
|
1224
|
+
except PackageNotFoundError:
|
|
1225
|
+
missing.add(value)
|
|
1226
|
+
continue
|
|
1227
|
+
if version not in requirement.specifier: conflicting.add((requirement.name + '==' + str(version), value))
|
|
1228
|
+
return dict(conflicting=sorted(conflicting), missing=sorted(missing))
|
|
1229
|
+
|
|
1230
|
+
def read_build_system(request):
|
|
1231
|
+
import tomllib
|
|
1232
|
+
from micropip._vendored.packaging.src.packaging.requirements import InvalidRequirement
|
|
1233
|
+
class InstallationError(Exception): pass
|
|
1234
|
+
source = request['source']
|
|
1235
|
+
filename = os.path.join(source, 'pyproject.toml')
|
|
1236
|
+
has_project, has_setup = os.path.isfile(filename), os.path.isfile(os.path.join(source, 'setup.py'))
|
|
1237
|
+
system = None
|
|
1238
|
+
if has_project:
|
|
1239
|
+
limit = request['maxBytes']
|
|
1240
|
+
with open(filename, 'rb') as stream: raw = stream.read() if limit is None else stream.read(limit + 1)
|
|
1241
|
+
if limit is not None and len(raw) > limit: raise ValueError('Python build configuration limit exceeded')
|
|
1242
|
+
system = tomllib.loads(raw.decode('utf-8')).get('build-system')
|
|
1243
|
+
selected = request.get('usePep517')
|
|
1244
|
+
if has_project and not has_setup:
|
|
1245
|
+
if selected is False: raise InstallationError('Disabling PEP 517 processing is invalid: project does not have a setup.py')
|
|
1246
|
+
selected = True
|
|
1247
|
+
elif system and 'build-backend' in system:
|
|
1248
|
+
if selected is False: raise InstallationError('Disabling PEP 517 processing is invalid: project specifies a build backend of {} in pyproject.toml'.format(system['build-backend']))
|
|
1249
|
+
selected = True
|
|
1250
|
+
elif selected is None: selected = has_project
|
|
1251
|
+
if not selected: return None
|
|
1252
|
+
defaults = ['setuptools>=40.8.0', 'wheel']
|
|
1253
|
+
if system is None: system = {'requires': defaults, 'build-backend': 'setuptools.build_meta:__legacy__'}
|
|
1254
|
+
def invalid(reason):
|
|
1255
|
+
raise InstallationError('{} has a pyproject.toml file that does not comply with PEP 518: {}'.format(request.get('name', source), reason))
|
|
1256
|
+
if 'requires' not in system: invalid("it has a 'build-system' table but not 'build-system.requires' which is mandatory in the table")
|
|
1257
|
+
requirements = system['requires']
|
|
1258
|
+
if not isinstance(requirements, list) or not all(isinstance(value, str) for value in requirements): invalid("'build-system.requires' is not a list of strings.")
|
|
1259
|
+
for value in requirements:
|
|
1260
|
+
try:
|
|
1261
|
+
parse_build_requirement(value)
|
|
1262
|
+
except InvalidRequirement: invalid("'build-system.requires' contains an invalid requirement: {!r}".format(value))
|
|
1263
|
+
backend = system.get('build-backend')
|
|
1264
|
+
return dict(requires=requirements, backend=backend if backend is not None else 'setuptools.build_meta:__legacy__', check=defaults if backend is None else [], backendPath=system.get('backend-path', []))
|
|
1265
|
+
|
|
1266
|
+
def main():
|
|
1267
|
+
request = send('request')
|
|
1268
|
+
if request['hook'] == 'read_build_system': return read_build_system(request)
|
|
1269
|
+
if request['hook'] == 'check_build_requirements': return check_build_requirements(request)
|
|
1270
|
+
source = os.path.realpath(request['source'])
|
|
1271
|
+
wheel_directory = os.path.abspath(request['wheelDirectory']) if request['hook'] == 'build_wheel' else None
|
|
1272
|
+
metadata_directory = os.path.abspath(request['metadataDirectory']) if request.get('metadataDirectory') is not None else None
|
|
1273
|
+
os.chdir(source)
|
|
1274
|
+
paths = []
|
|
1275
|
+
for entry in request.get('backendPath', []):
|
|
1276
|
+
if os.path.isabs(entry): raise ValueError('paths must be relative')
|
|
1277
|
+
path = os.path.realpath(os.path.join(source, entry))
|
|
1278
|
+
if os.path.commonpath([source, path]) != source: raise ValueError('paths must be inside source tree')
|
|
1279
|
+
paths.append(path)
|
|
1280
|
+
sys.path[:0] = paths
|
|
1281
|
+
module, _, attributes = request['backend'].partition(':')
|
|
1282
|
+
backend = importlib.import_module(module)
|
|
1283
|
+
if paths and not any(os.path.commonpath([os.path.realpath(backend.__file__), path]) == path for path in paths):
|
|
1284
|
+
raise ValueError('Backend was not loaded from backend-path')
|
|
1285
|
+
if attributes:
|
|
1286
|
+
for name in attributes.split('.'): backend = getattr(backend, name)
|
|
1287
|
+
settings = request.get('configSettings')
|
|
1288
|
+
if request['hook'] == 'get_requires_for_build_wheel':
|
|
1289
|
+
try: hook = backend.get_requires_for_build_wheel
|
|
1290
|
+
except AttributeError: result = []
|
|
1291
|
+
else: result = hook(settings)
|
|
1292
|
+
else:
|
|
1293
|
+
result = backend.build_wheel(wheel_directory, settings, metadata_directory)
|
|
1294
|
+
return result
|
|
1295
|
+
|
|
1296
|
+
try:
|
|
1297
|
+
result = main()
|
|
1298
|
+
encoder = json.JSONEncoder(ensure_ascii=False, separators=(',', ':'))
|
|
1299
|
+
for part in encoder.iterencode(result):
|
|
1300
|
+
for offset in range(0, len(part), 4096): send('text', text=part[offset:offset + 4096])
|
|
1301
|
+
send('done')
|
|
1302
|
+
except Exception as error:
|
|
1303
|
+
send('error', type=type(error).__name__, message=str(error))
|
|
1304
|
+
`)();
|
|
1305
|
+
|
|
1306
|
+
// packages/safe-bash-command-python/src/build-dependencies.ts
|
|
1307
|
+
function createPythonBuildDependencies(options) {
|
|
1308
|
+
const isolated = { ...options };
|
|
1309
|
+
for (const key of ["packages", "requirements", "packageProfile", "provisioning"]) Reflect.deleteProperty(isolated, key);
|
|
1310
|
+
const hook = createPythonBuildBackend(isolated), command = createPythonExecutorCommands(isolated)[0];
|
|
1311
|
+
return async (input, caller) => {
|
|
1312
|
+
const context = { ...caller };
|
|
1313
|
+
context.signal.throwIfAborted();
|
|
1314
|
+
const request = structuredClone(input), { source, buildSystem, configSettings } = request;
|
|
1315
|
+
if (typeof source !== "string" || !source || request.name !== void 0 && typeof request.name !== "string" || !buildSystem || typeof buildSystem.backend !== "string" || !buildSystem.backend || [buildSystem.requires, buildSystem.check, buildSystem.backendPath].some((values) => !Array.isArray(values) || values.some((value) => typeof value !== "string"))) throw new TypeError("Invalid Python build dependency request");
|
|
1316
|
+
if (configSettings !== void 0 && (!configSettings || typeof configSettings !== "object" || Array.isArray(configSettings) || Object.values(configSettings).some((value) => typeof value !== "string" && (!Array.isArray(value) || value.some((item) => typeof item !== "string"))))) throw new TypeError("Invalid Python build configuration");
|
|
1317
|
+
if (context.maxBytes !== Infinity && (!Number.isSafeInteger(context.maxBytes) || context.maxBytes < 0)) throw new RangeError("Invalid Python build metadata limit");
|
|
1318
|
+
const install = async (requirements2) => {
|
|
1319
|
+
if (!requirements2.length) return;
|
|
1320
|
+
const result = await command.execute({ ...context, command: "python", args: ["-m", "pip", "install", "--", ...requirements2], stdin: toByteSource6("") });
|
|
1321
|
+
context.signal.throwIfAborted();
|
|
1322
|
+
if (result.exitCode) throw new Error("Python build dependency installation exited with status " + result.exitCode);
|
|
1323
|
+
};
|
|
1324
|
+
const check = async (requirements2, against) => {
|
|
1325
|
+
if (!requirements2.length) return [];
|
|
1326
|
+
const receipt = await isolated.environment.prepare(context);
|
|
1327
|
+
let installed;
|
|
1328
|
+
try {
|
|
1329
|
+
if (!receipt.records && receipt.restore?.length) throw new Error("Python build dependency checks require an installed metadata manifest");
|
|
1330
|
+
installed = receipt.records?.map((record2) => record2[0]) ?? [];
|
|
1331
|
+
} finally {
|
|
1332
|
+
await isolated.environment.finish(receipt);
|
|
1333
|
+
}
|
|
1334
|
+
const status = await hook({ hook: "check_build_requirements", source, requirements: requirements2, installed }, context);
|
|
1335
|
+
if (status.conflicting.length) throw Object.assign(new Error("Some build dependencies for " + (request.name ?? source) + " conflict with " + against + ": " + [...status.conflicting].sort(([a, b], [c, d]) => a < c ? -1 : a > c ? 1 : b < d ? -1 : b > d ? 1 : 0).map(([installed2, wanted]) => installed2 + " is incompatible with " + wanted).join(", ") + "."), { name: "InstallationError" });
|
|
1336
|
+
return status.missing;
|
|
1337
|
+
};
|
|
1338
|
+
await install(buildSystem.requires);
|
|
1339
|
+
const missing = await check(buildSystem.check, "PEP 517/518 supported requirements");
|
|
1340
|
+
if (missing.length) await context.stderr.write(new TextEncoder().encode("Missing build requirements in pyproject.toml for " + (request.name ?? source) + ".\nThe project does not specify a build backend, and pip cannot fall back to setuptools without " + missing.map((value) => "'" + value + "'").join(" and ") + ".\n"));
|
|
1341
|
+
const requirements = await hook({ hook: "get_requires_for_build_wheel", source, backend: buildSystem.backend, backendPath: buildSystem.backendPath, ...configSettings === void 0 ? {} : { configSettings } }, context);
|
|
1342
|
+
await install(await check(requirements, "the backend dependencies"));
|
|
1343
|
+
};
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
// packages/safe-bash-command-python/src/source-snapshot.ts
|
|
1347
|
+
import { compareIdentity } from "@poe-platform/safe-fs/core";
|
|
1348
|
+
import { FsError as FsError2 } from "../../safe-bash-contracts/index.js";
|
|
1349
|
+
import { resolvePath } from "../../safe-bash-contracts/path.js";
|
|
1350
|
+
import { yieldTurn } from "../../safe-bash-contracts/yield.js";
|
|
1351
|
+
import { admitCopySource, admitCopyDestination, copyCheckedSource } from "../../safe-bash-io-engine/commands/copy-source.js";
|
|
1352
|
+
var serial = 0;
|
|
1353
|
+
async function createPythonSourceSnapshot(source, directory, context) {
|
|
1354
|
+
const { fs, signal } = context, settings = { signal };
|
|
1355
|
+
signal.throwIfAborted();
|
|
1356
|
+
if (!fs.iterateDirectory) throw new Error("Python source snapshots require lazy directory enumeration");
|
|
1357
|
+
const origin = await fs.realpath(resolvePath(context.cwd, source), settings);
|
|
1358
|
+
const parentPath = await fs.realpath(resolvePath(context.cwd, directory), settings);
|
|
1359
|
+
const capabilities = await fs.capabilitiesFor?.(parentPath, settings) ?? fs.capabilities;
|
|
1360
|
+
if (!fs.prepareDirectory || !fs.removeTreeConditional || !fs.confineExtraction || !capabilities.atomicTreeRemoval) throw new Error("Python source snapshots require confined writes, conditional directory creation and tree removal");
|
|
1361
|
+
const original = await fs.stat(origin, settings), parent = await fs.stat(parentPath, settings);
|
|
1362
|
+
if (original.type !== "directory" || parent.type !== "directory") throw new FsError2("ENOTDIR", { path: origin });
|
|
1363
|
+
let path = "", owned;
|
|
1364
|
+
for (let attempt = 0; attempt < 16; attempt++) {
|
|
1365
|
+
path = resolvePath(parentPath, ".python-source-" + ++serial);
|
|
1366
|
+
try {
|
|
1367
|
+
owned = await fs.prepareDirectory(path, { parent, expected: null, mode: 448, signal });
|
|
1368
|
+
break;
|
|
1369
|
+
} catch (error) {
|
|
1370
|
+
signal.throwIfAborted();
|
|
1371
|
+
if (!(error instanceof FsError2) || !["EEXIST", "EAGAIN"].includes(error.code)) throw error;
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
if (!owned) throw new Error("Python source snapshot names exhausted");
|
|
1375
|
+
let disposing;
|
|
1376
|
+
const snapshot = { path, dispose() {
|
|
1377
|
+
return disposing ??= (async () => {
|
|
1378
|
+
const current = await fs.lstat(path);
|
|
1379
|
+
if (compareIdentity(owned, current) !== "same") throw new Error("Python source snapshot identity changed");
|
|
1380
|
+
await fs.removeTreeConditional(path, { parent, expected: current });
|
|
1381
|
+
})();
|
|
1382
|
+
} };
|
|
1383
|
+
try {
|
|
1384
|
+
const destination = await fs.confineExtraction([path], settings);
|
|
1385
|
+
if (compareIdentity(owned, await fs.lstat(path, settings)) !== "same") throw new Error("Python source snapshot identity changed");
|
|
1386
|
+
let activeCleanup;
|
|
1387
|
+
context.registerCleanup?.(() => activeCleanup?.());
|
|
1388
|
+
const copyContext = { ...context, registerCleanup(cleanup) {
|
|
1389
|
+
activeCleanup = cleanup;
|
|
1390
|
+
}, fs: new Proxy(destination, { get(target, key) {
|
|
1391
|
+
const owner = key === "openReadFile" ? fs : target, value = Reflect.get(owner, key);
|
|
1392
|
+
return typeof value === "function" ? value.bind(owner) : value;
|
|
1393
|
+
} }) };
|
|
1394
|
+
const metadata = async (from, to, expected) => {
|
|
1395
|
+
const current = await fs.lstat(from, settings);
|
|
1396
|
+
if (compareIdentity(expected, current) !== "same") throw new Error("Python source identity changed while copying");
|
|
1397
|
+
if (destination.chmod) await destination.chmod(to, current.mode & 4095, settings);
|
|
1398
|
+
if (destination.utimes) await destination.utimes(to, current.atimeMs, current.mtimeMs, settings);
|
|
1399
|
+
};
|
|
1400
|
+
const copy = async (from, to, expected) => {
|
|
1401
|
+
signal.throwIfAborted();
|
|
1402
|
+
if (expected.type === "symlink") {
|
|
1403
|
+
if (!destination.symlink || !fs.readlink) throw new Error("Python source snapshots require symlink support");
|
|
1404
|
+
const target = await fs.readlink(from, settings);
|
|
1405
|
+
if (compareIdentity(expected, await fs.lstat(from, settings)) !== "same") throw new Error("Python source identity changed while copying");
|
|
1406
|
+
await destination.symlink(target, to, settings);
|
|
1407
|
+
return;
|
|
1408
|
+
}
|
|
1409
|
+
if (expected.type === "file") {
|
|
1410
|
+
await admitCopySource(context, from);
|
|
1411
|
+
await admitCopyDestination(copyContext, to, true);
|
|
1412
|
+
try {
|
|
1413
|
+
await copyCheckedSource(copyContext, from, to, expected, true);
|
|
1414
|
+
} finally {
|
|
1415
|
+
activeCleanup = void 0;
|
|
1416
|
+
}
|
|
1417
|
+
} else if (expected.type === "directory") {
|
|
1418
|
+
if (to !== path) await destination.mkdir(to, { mode: 448, signal });
|
|
1419
|
+
for await (const entry of fs.iterateDirectory(from, settings)) {
|
|
1420
|
+
await yieldTurn(signal);
|
|
1421
|
+
if (!entry.name || entry.name === "." || entry.name === ".." || entry.name.includes("/") || entry.name.includes("\0")) throw new Error("Invalid Python source directory entry");
|
|
1422
|
+
const child = resolvePath(from, entry.name);
|
|
1423
|
+
if (child === path || from === origin && [".tox", ".nox"].includes(entry.name)) continue;
|
|
1424
|
+
await copy(child, resolvePath(to, entry.name), await fs.lstat(child, settings));
|
|
1425
|
+
}
|
|
1426
|
+
} else throw new Error("Unsupported Python source entry: " + from);
|
|
1427
|
+
await metadata(from, to, expected);
|
|
1428
|
+
};
|
|
1429
|
+
await copy(origin, path, original);
|
|
1430
|
+
signal.throwIfAborted();
|
|
1431
|
+
return snapshot;
|
|
1432
|
+
} catch (error) {
|
|
1433
|
+
try {
|
|
1434
|
+
await snapshot.dispose();
|
|
1435
|
+
} catch (cleanup) {
|
|
1436
|
+
throw new AggregateError([error, cleanup], "Python source snapshot cleanup failed");
|
|
1437
|
+
}
|
|
1438
|
+
throw error;
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
|
|
647
1442
|
// packages/safe-bash-command-python/src/llm-manifest.ts
|
|
648
1443
|
var pythonLlmManifest = {
|
|
649
1444
|
"llm": "0.27.1",
|
|
@@ -1008,6 +1803,151 @@ del _install_safe_llm_import
|
|
|
1008
1803
|
}
|
|
1009
1804
|
}
|
|
1010
1805
|
|
|
1806
|
+
// packages/safe-bash-command-python/src/build-wheel.ts
|
|
1807
|
+
import { FsError as FsError3 } from "../../safe-bash-contracts/index.js";
|
|
1808
|
+
import { resolvePath as resolvePath2, basename } from "../../safe-bash-contracts/path.js";
|
|
1809
|
+
async function publishPythonBuildWheel(source, directory, maxBytes, context) {
|
|
1810
|
+
const { fs, signal } = context, settings = { signal };
|
|
1811
|
+
signal.throwIfAborted();
|
|
1812
|
+
if (maxBytes !== Infinity && (!Number.isSafeInteger(maxBytes) || maxBytes < 0)) throw new RangeError("Invalid Python wheel size limit");
|
|
1813
|
+
const filename = basename(source);
|
|
1814
|
+
if (!filename.endsWith(".whl") || filename.includes("\\") || filename.includes("\0")) throw new TypeError("Invalid Python build wheel filename");
|
|
1815
|
+
const root = await fs.realpath(resolvePath2(context.cwd, directory), settings);
|
|
1816
|
+
const caps = await fs.capabilitiesFor?.(root, settings) ?? fs.capabilities;
|
|
1817
|
+
if (!fs.confineExtraction || !fs.prepareDirectory || !caps.retainedStagingWrite || !caps.retainedStagingCleanup || !caps.retainedRead || !caps.atomicFileStaging) throw new Error("Python wheel publication requires confined retained atomic staging");
|
|
1818
|
+
const artifact = await openPythonPackageFile(context, resolvePath2(context.cwd, source), maxBytes);
|
|
1819
|
+
if (!artifact) throw new Error("Python wheel publication requires retained source reads");
|
|
1820
|
+
let reading;
|
|
1821
|
+
try {
|
|
1822
|
+
const confined = await fs.confineExtraction([root], settings);
|
|
1823
|
+
const destination = new Proxy(confined, { get(target, key) {
|
|
1824
|
+
const owner = key === "openReadFile" ? fs : target, value = Reflect.get(owner, key);
|
|
1825
|
+
return typeof value === "function" ? value.bind(owner) : value;
|
|
1826
|
+
} });
|
|
1827
|
+
const parent = await destination.stat(root, settings), path = resolvePath2(root, artifact.key);
|
|
1828
|
+
try {
|
|
1829
|
+
await destination.prepareDirectory(path, { parent, expected: null, mode: 448, signal });
|
|
1830
|
+
} catch (error) {
|
|
1831
|
+
if (!(error instanceof FsError3) || !["EEXIST", "EAGAIN"].includes(error.code)) throw error;
|
|
1832
|
+
}
|
|
1833
|
+
if ((await destination.lstat(path, settings)).type !== "directory") throw new Error("Python wheel publication directory changed");
|
|
1834
|
+
const output = resolvePath2(path, filename);
|
|
1835
|
+
const body = async function* () {
|
|
1836
|
+
for (let offset = 0; offset < artifact.size; ) {
|
|
1837
|
+
const bytes = await (reading = artifact.read(offset, 65536));
|
|
1838
|
+
offset += bytes.length;
|
|
1839
|
+
yield bytes;
|
|
1840
|
+
}
|
|
1841
|
+
};
|
|
1842
|
+
const published = await stagePythonPackage({ ...context, fs: destination }, path, body(), maxBytes, () => {
|
|
1843
|
+
}, (key) => {
|
|
1844
|
+
if (key !== artifact.key) throw new Error("Built Python wheel integrity mismatch");
|
|
1845
|
+
}, () => output);
|
|
1846
|
+
if (!published) throw new Error("Python wheel publication requires retained atomic staging");
|
|
1847
|
+
try {
|
|
1848
|
+
return { url: "file://" + output.split("/").map(encodeURIComponent).join("/"), digest: published.key, size: published.size };
|
|
1849
|
+
} finally {
|
|
1850
|
+
await published.close();
|
|
1851
|
+
}
|
|
1852
|
+
} finally {
|
|
1853
|
+
try {
|
|
1854
|
+
await reading?.catch(() => {
|
|
1855
|
+
});
|
|
1856
|
+
} finally {
|
|
1857
|
+
await artifact.close();
|
|
1858
|
+
}
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1862
|
+
// packages/safe-bash-command-python/src/source-environment.ts
|
|
1863
|
+
import { compareIdentity as compareIdentity2 } from "@poe-platform/safe-fs/core";
|
|
1864
|
+
import { FsError as FsError4, toByteSource as toByteSource7 } from "../../safe-bash-contracts/index.js";
|
|
1865
|
+
import { resolvePath as resolvePath3 } from "../../safe-bash-contracts/path.js";
|
|
1866
|
+
var serial2 = 0;
|
|
1867
|
+
function createPythonSourcePackageEnvironment(options, build) {
|
|
1868
|
+
if (options.prepareRequirements || !build.python.createExecutor || !build.directory) throw new TypeError("Source packages require an asynchronous build executor and caller storage");
|
|
1869
|
+
const prepareRequirement = async (requirement, context) => {
|
|
1870
|
+
const { fs, signal } = context, settings = { signal };
|
|
1871
|
+
let source = requirement;
|
|
1872
|
+
if (source.startsWith("file:")) {
|
|
1873
|
+
const url = new URL(source);
|
|
1874
|
+
if (url.host && url.host !== "localhost") return requirement;
|
|
1875
|
+
source = decodeURIComponent(url.pathname);
|
|
1876
|
+
} else if (source.includes("://")) return requirement;
|
|
1877
|
+
source = resolvePath3(context.cwd, source);
|
|
1878
|
+
try {
|
|
1879
|
+
if ((await fs.stat(source, settings)).type !== "directory") return requirement;
|
|
1880
|
+
} catch (error) {
|
|
1881
|
+
if (error instanceof FsError4 && error.code === "ENOENT") return requirement;
|
|
1882
|
+
throw error;
|
|
1883
|
+
}
|
|
1884
|
+
if (!context.stdout || !context.stderr || !context.env) throw new TypeError("Source package preparation requires command output and environment context");
|
|
1885
|
+
if (!fs.prepareDirectory || !fs.removeTreeConditional || !fs.confineExtraction) throw new Error("Source packages require conditional caller storage");
|
|
1886
|
+
const root = await fs.realpath(resolvePath3(context.cwd, build.directory), settings), parent = await fs.stat(root, settings);
|
|
1887
|
+
const caps = await fs.capabilitiesFor?.(root, settings) ?? fs.capabilities;
|
|
1888
|
+
if (!caps.atomicTreeRemoval) throw new Error("Source packages require conditional tree cleanup");
|
|
1889
|
+
let path = "", owned;
|
|
1890
|
+
for (let attempt = 0; attempt < 16; attempt++) {
|
|
1891
|
+
path = resolvePath3(root, ".python-build-" + ++serial2);
|
|
1892
|
+
try {
|
|
1893
|
+
owned = await fs.prepareDirectory(path, { parent, expected: null, mode: 448, signal });
|
|
1894
|
+
break;
|
|
1895
|
+
} catch (error) {
|
|
1896
|
+
if (!(error instanceof FsError4) || !["EEXIST", "EAGAIN"].includes(error.code)) throw error;
|
|
1897
|
+
signal.throwIfAborted();
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1900
|
+
if (!owned) throw new Error("Python build directory names exhausted");
|
|
1901
|
+
const environment = createPythonBuildEnvironment(options);
|
|
1902
|
+
let cleaning;
|
|
1903
|
+
const remove = async () => {
|
|
1904
|
+
const current = await fs.lstat(path);
|
|
1905
|
+
if (compareIdentity2(owned, current) !== "same") throw new Error("Python build directory identity changed");
|
|
1906
|
+
await fs.removeTreeConditional(path, { parent, expected: current });
|
|
1907
|
+
};
|
|
1908
|
+
const cleanup = () => cleaning ??= (async () => {
|
|
1909
|
+
try {
|
|
1910
|
+
await environment.dispose();
|
|
1911
|
+
} finally {
|
|
1912
|
+
await remove();
|
|
1913
|
+
}
|
|
1914
|
+
})();
|
|
1915
|
+
try {
|
|
1916
|
+
const confined = await fs.confineExtraction([path], settings);
|
|
1917
|
+
if (compareIdentity2(owned, await fs.lstat(path, settings)) !== "same") throw new Error("Python build directory identity changed");
|
|
1918
|
+
const staging = new Proxy(confined, { get(target, key) {
|
|
1919
|
+
const owner = ["stat", "lstat", "realpath", "openReadFile", "iterateDirectory", "readlink", "confineExtraction", "removeTreeConditional"].includes(String(key)) ? fs : target, value = Reflect.get(owner, key);
|
|
1920
|
+
return typeof value === "function" ? value.bind(owner) : value;
|
|
1921
|
+
} });
|
|
1922
|
+
const command = { ...context, env: context.env, stdout: context.stdout, stderr: context.stderr, command: "python", args: [], stdin: toByteSource7("") };
|
|
1923
|
+
const snapshot = await createPythonSourceSnapshot(source, path, { ...command, fs: staging });
|
|
1924
|
+
const wheelDirectory = resolvePath3(path, "wheels");
|
|
1925
|
+
await confined.mkdir(wheelDirectory, settings);
|
|
1926
|
+
const configuration = { ...build.python, environment }, hook = createPythonBuildBackend(configuration), hookContext = { ...command, maxBytes: options.maxMetadataBytes ?? Infinity };
|
|
1927
|
+
const buildSystem = await hook({ hook: "read_build_system", source: snapshot.path }, hookContext);
|
|
1928
|
+
if (!buildSystem) throw new Error("Legacy setup.py source installation is not yet available");
|
|
1929
|
+
await createPythonBuildDependencies(configuration)({ source: snapshot.path, buildSystem }, hookContext);
|
|
1930
|
+
const filename = await hook({ hook: "build_wheel", source: snapshot.path, backend: buildSystem.backend, backendPath: buildSystem.backendPath, wheelDirectory }, hookContext);
|
|
1931
|
+
const published = await publishPythonBuildWheel(resolvePath3(wheelDirectory, filename), root, options.maxDownloadBytes ?? Infinity, context);
|
|
1932
|
+
await cleanup();
|
|
1933
|
+
return published.url;
|
|
1934
|
+
} catch (error) {
|
|
1935
|
+
try {
|
|
1936
|
+
await cleanup();
|
|
1937
|
+
} catch (retirement) {
|
|
1938
|
+
if (retirement === error) throw error;
|
|
1939
|
+
throw new AggregateError([error, retirement], "Python source build cleanup failed");
|
|
1940
|
+
}
|
|
1941
|
+
throw error;
|
|
1942
|
+
}
|
|
1943
|
+
};
|
|
1944
|
+
return createPythonPackageEnvironment({ ...options, async prepareRequirements(requirements, context) {
|
|
1945
|
+
const resolved = [];
|
|
1946
|
+
for (const requirement of new Set(requirements)) resolved.push(await prepareRequirement(requirement, context));
|
|
1947
|
+
return resolved;
|
|
1948
|
+
} });
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1011
1951
|
// packages/safe-bash-command-python/src/executor.ts
|
|
1012
1952
|
var PythonInputChunkError = class extends RangeError {
|
|
1013
1953
|
};
|
|
@@ -1035,8 +1975,8 @@ function createPythonExecutorCommands(options) {
|
|
|
1035
1975
|
}
|
|
1036
1976
|
let installation;
|
|
1037
1977
|
try {
|
|
1038
|
-
parsePythonInvocation(context.args, context.env);
|
|
1039
|
-
installation = parsePythonInstallation(
|
|
1978
|
+
const invocation = parsePythonInvocation(context.args, context.env);
|
|
1979
|
+
installation = invocation.module?.name === "pip" ? parsePythonInstallation(invocation.module.args) : void 0;
|
|
1040
1980
|
} catch (error) {
|
|
1041
1981
|
if (!(error instanceof PythonInvocationError)) throw error;
|
|
1042
1982
|
await writeBytes(context.stderr, new TextEncoder().encode("python: " + error.message + "\n"), context.signal);
|
|
@@ -1099,7 +2039,7 @@ function createPythonExecutorCommands(options) {
|
|
|
1099
2039
|
const results = await Promise.allSettled([subscription, termination, filesystemRetirement, hostBridge?.close(), stdoutOperation?.close(), stderrOperation?.close(), ...pending]);
|
|
1100
2040
|
await input.return?.(void 0);
|
|
1101
2041
|
fragment = void 0;
|
|
1102
|
-
if (packages) environment.finish(packages);
|
|
2042
|
+
if (packages) results.push(...await Promise.allSettled([Promise.resolve().then(() => environment.finish(packages))]));
|
|
1103
2043
|
if (results[1]?.status === "fulfilled" && admitted) {
|
|
1104
2044
|
activeWorkers--;
|
|
1105
2045
|
admitted = false;
|
|
@@ -1149,7 +2089,7 @@ function createPythonExecutorCommands(options) {
|
|
|
1149
2089
|
if (!Number.isSafeInteger(length) || length < 1 || length > maxTransferBytes) throw new RangeError("Invalid Python input request");
|
|
1150
2090
|
let pulls = 0;
|
|
1151
2091
|
while (!fragment || offset === fragment.length) {
|
|
1152
|
-
if (++pulls % 128 === 0) await
|
|
2092
|
+
if (++pulls % 128 === 0) await yieldTurn2(signal);
|
|
1153
2093
|
const next = await input.next();
|
|
1154
2094
|
if (next.done) return [];
|
|
1155
2095
|
inputBytes += next.value.length;
|
|
@@ -1185,10 +2125,11 @@ function createPythonExecutorCommands(options) {
|
|
|
1185
2125
|
}
|
|
1186
2126
|
options.onProgress?.({ phase: "initializing", command: context.command });
|
|
1187
2127
|
const preparation = environment.prepare({
|
|
1188
|
-
|
|
1189
|
-
cwd: context.cwd,
|
|
2128
|
+
...context,
|
|
1190
2129
|
signal,
|
|
1191
|
-
|
|
2130
|
+
...installation?.controls,
|
|
2131
|
+
...installation?.uninstall ? { uninstall: { packages: installation.packages, yes: !!installation.yes } } : {},
|
|
2132
|
+
requirements: [...options.packages ?? [], ...options.packageProfile ? pythonDocumentPackages : [], ...installation?.uninstall ? [] : installation?.packages ?? []],
|
|
1192
2133
|
requirementFiles: [...options.requirements ?? [], ...installation?.requirements ?? []]
|
|
1193
2134
|
}).then((value) => {
|
|
1194
2135
|
packages = value;
|
|
@@ -1458,9 +2399,20 @@ function pythonExecutorCommands(options) {
|
|
|
1458
2399
|
}
|
|
1459
2400
|
|
|
1460
2401
|
export {
|
|
2402
|
+
createPythonLlmPackageManager,
|
|
2403
|
+
createPythonLlmTemplateLoader,
|
|
2404
|
+
createPythonLlmFragmentLoader,
|
|
2405
|
+
createPythonLlmLoaderProvider,
|
|
2406
|
+
createPythonLlmLoaderDiscovery,
|
|
1461
2407
|
createPythonLlmToolLoader,
|
|
2408
|
+
createPythonBuildEnvironment,
|
|
2409
|
+
createPythonBuildBackend,
|
|
2410
|
+
createPythonBuildDependencies,
|
|
2411
|
+
createPythonSourceSnapshot,
|
|
1462
2412
|
pythonLlmPackages,
|
|
1463
2413
|
installPythonLlmPackages,
|
|
2414
|
+
publishPythonBuildWheel,
|
|
2415
|
+
createPythonSourcePackageEnvironment,
|
|
1464
2416
|
createPythonExecutorCommands,
|
|
1465
2417
|
pythonExecutorCommands
|
|
1466
2418
|
};
|