@poe-platform/safe-bash 0.1.755 → 0.1.757
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/image-ast/index.browser.js +30 -6
- package/dist/image-ast/index.js +30 -6
- package/dist/pdf-ast/canvas.d.ts +1 -0
- package/dist/pdf-ast/index.js +26 -2
- package/dist/safe-bash/chunks/{chunk-LESPJ3OG.js → chunk-WNWIBBJZ.js} +15 -2
- package/dist/safe-bash/command-chunks/{chunk-S4J6MSLY.js → chunk-444OTXM7.js} +3 -3
- package/dist/safe-bash/command-chunks/{chunk-6AIOGLZO.js → chunk-CE6Z2ULX.js} +2 -2
- package/dist/safe-bash/command-chunks/{chunk-5VSGEE6J.js → chunk-F4IQCXZG.js} +5 -5
- package/dist/safe-bash/command-chunks/{chunk-F5MORDSU.js → chunk-FDNCPZQS.js} +32 -1
- package/dist/safe-bash/command-chunks/{chunk-SG3ZDZCX.js → chunk-FLMLM7Y4.js} +2 -2
- package/dist/safe-bash/command-chunks/{chunk-XRVNFK7Z.js → chunk-H2FQA4OU.js} +26 -2
- package/dist/safe-bash/command-chunks/{chunk-CT3FB722.js → chunk-J7UMKBRK.js} +1 -1
- package/dist/safe-bash/command-chunks/{chunk-KB2LQ4HG.js → chunk-KBBI4CSU.js} +2 -2
- package/dist/safe-bash/command-chunks/{chunk-QQOAK7JQ.js → chunk-KKJCKPTA.js} +1 -1
- package/dist/safe-bash/command-chunks/{chunk-7KS6PX4U.js → chunk-KSIGF4AG.js} +3 -3
- package/dist/safe-bash/command-chunks/{chunk-4HQ7WYSI.js → chunk-MED2BOOW.js} +30 -5
- package/dist/safe-bash/command-chunks/{chunk-JRNLDPLI.js → chunk-OZSWW23M.js} +1 -1
- package/dist/safe-bash/command-chunks/{chunk-DKQ5REXF.js → chunk-QDPRSPHQ.js} +1 -1
- package/dist/safe-bash/command-chunks/{chunk-WOPU2VWF.js → chunk-RID3B5RE.js} +1 -1
- package/dist/safe-bash/command-chunks/{chunk-DVZCHNB5.js → chunk-S4MLIYSN.js} +7 -7
- package/dist/safe-bash/command-chunks/{chunk-REYTQLLB.js → chunk-TT2DMDBY.js} +1 -1
- package/dist/safe-bash/command-chunks/{chunk-UGX6MXDD.js → chunk-UIIT4ULR.js} +3 -3
- package/dist/safe-bash/command-chunks/{chunk-MENC7RPR.js → chunk-WI35236J.js} +1 -1
- package/dist/safe-bash/command-chunks/{chunk-A6NFLWZI.js → chunk-WKPOGTZY.js} +22 -6
- package/dist/safe-bash/command-chunks/{chunk-3DOHQQJR.js → chunk-Y64PA6IA.js} +8 -8
- package/dist/safe-bash/command-chunks/{chunk-6Z7OS3TZ.js → chunk-ZP5KDHB4.js} +7 -7
- package/dist/safe-bash/commands/pandoc/index.d.ts +1 -1
- package/dist/safe-bash/commands/pandoc/index.js +5 -3
- package/dist/safe-bash/commands/python/index.browser.js +1 -1
- package/dist/safe-bash/commands/python/index.js +6 -0
- package/dist/safe-bash/commands/python/jspi.js +11 -2
- package/dist/safe-bash/core.browser.js +6 -4
- package/dist/safe-bash-command-csvkit/index.js +8 -8
- package/dist/safe-bash-command-ffmpeg/index.js +20 -2
- package/dist/safe-bash-command-imagemagick/index.js +2 -2
- package/dist/safe-bash-command-pandoc/command.js +3 -3
- package/dist/safe-bash-command-pandoc/index.js +3 -3
- package/dist/safe-bash-command-pdfimages/index.js +26 -2
- package/dist/safe-bash-command-pdfinfo/index.js +26 -2
- package/dist/safe-bash-command-pdftk/index.js +26 -2
- package/dist/safe-bash-command-pdftoppm/index.js +26 -2
- package/dist/safe-bash-command-pdftotext/index.js +26 -2
- package/dist/safe-bash-command-qpdf/index.js +26 -2
- package/dist/safe-bash-command-sips/index.js +2 -2
- package/dist/safe-bash-command-soffice/index.js +17 -17
- package/dist/safe-bash-command-ssconvert/core.js +8 -8
- package/dist/safe-bash-command-ssconvert/formats/csv.js +9 -9
- package/dist/safe-bash-command-ssconvert/formats/ods.js +8 -8
- package/dist/safe-bash-command-ssconvert/formats/spreadsheetml.js +1 -1
- package/dist/safe-bash-command-ssconvert/formats/xls.js +9 -8
- package/dist/safe-bash-command-ssconvert/formats/xlsx.js +8 -8
- package/dist/safe-bash-command-ssconvert/index.js +133 -29
- package/dist/safe-bash-command-wkhtmltopdf/index.js +26 -2
- package/dist/safe-bash-contracts/filesystem-descriptor.js +1 -1
- package/dist/safe-bash-contracts/filesystem-output.js +1 -1
- package/dist/safe-bash-mcp/index.js +4 -4
- package/dist/spreadsheet-engine/formulas/ast.d.ts +13 -0
- package/dist/spreadsheet-engine/formulas/conventions.js +1 -1
- package/dist/spreadsheet-engine/formulas/dependencies.js +2 -2
- package/dist/spreadsheet-engine/formulas/evaluator.js +2 -2
- package/dist/spreadsheet-engine/formulas/named-expressions.d.ts +2 -2
- package/dist/spreadsheet-engine/formulas/named-expressions.js +31 -5
- package/dist/spreadsheet-engine/formulas/parser.js +34 -0
- package/dist/spreadsheet-engine/formulas/rewriting.js +19 -1
- package/dist/spreadsheet-engine/formulas/serialization.js +6 -0
- package/dist/spreadsheet-format-xls/biff-write-formulas.js +2 -0
- package/dist/xlsx-ast/formula-semantics.js +2 -0
- package/package.json +2 -2
- package/third-party/pdf-ast/THIRD_PARTY_NOTICES.md +1 -1
- package/dist/safe-bash/chunks/browser-ECQX6GMK.js +0 -6766
- package/dist/safe-bash/chunks/bz2-4OP3OWW3.js +0 -8
- package/dist/safe-bash/chunks/chunk-22XPAAOI.js +0 -173
- package/dist/safe-bash/chunks/chunk-44WUNUPV.js +0 -649
- package/dist/safe-bash/chunks/chunk-4E3SGG37.js +0 -876
- package/dist/safe-bash/chunks/chunk-4QTSSQKP.js +0 -898
- package/dist/safe-bash/chunks/chunk-5UVGPVXP.js +0 -410
- package/dist/safe-bash/chunks/chunk-64OE6Q6X.js +0 -3487
- package/dist/safe-bash/chunks/chunk-6IIFUPN2.js +0 -1526
- package/dist/safe-bash/chunks/chunk-6XDKK63A.js +0 -38959
- package/dist/safe-bash/chunks/chunk-BENDLTQD.js +0 -7065
- package/dist/safe-bash/chunks/chunk-BMO5LWEX.js +0 -534
- package/dist/safe-bash/chunks/chunk-BPMCPTNP.js +0 -1920
- package/dist/safe-bash/chunks/chunk-BRJTAWP5.js +0 -2955
- package/dist/safe-bash/chunks/chunk-C3GEIN7Q.js +0 -20054
- package/dist/safe-bash/chunks/chunk-CEWCRI6H.js +0 -2813
- package/dist/safe-bash/chunks/chunk-CGL53DQQ.js +0 -37
- package/dist/safe-bash/chunks/chunk-CTYJJD4O.js +0 -869
- package/dist/safe-bash/chunks/chunk-CWSS4MQA.js +0 -7
- package/dist/safe-bash/chunks/chunk-DLIGMTCP.js +0 -3266
- package/dist/safe-bash/chunks/chunk-EMAJ7WUW.js +0 -1031
- package/dist/safe-bash/chunks/chunk-FDKIZ2IJ.js +0 -1950
- package/dist/safe-bash/chunks/chunk-FZWJ2A7V.js +0 -3093
- package/dist/safe-bash/chunks/chunk-G2VCIF2J.js +0 -22
- package/dist/safe-bash/chunks/chunk-GVKBUQDX.js +0 -2649
- package/dist/safe-bash/chunks/chunk-IO2OGW3E.js +0 -28051
- package/dist/safe-bash/chunks/chunk-ISCZG7WA.js +0 -2648
- package/dist/safe-bash/chunks/chunk-JEFBTRY7.js +0 -174814
- package/dist/safe-bash/chunks/chunk-KJHDD4A2.js +0 -759
- package/dist/safe-bash/chunks/chunk-LB6HVRXS.js +0 -857
- package/dist/safe-bash/chunks/chunk-LLWAJOZH.js +0 -15
- package/dist/safe-bash/chunks/chunk-LMUKYZIJ.js +0 -708
- package/dist/safe-bash/chunks/chunk-M5SCNKIK.js +0 -2012
- package/dist/safe-bash/chunks/chunk-MJPFHANT.js +0 -3350
- package/dist/safe-bash/chunks/chunk-MZQW2DOL.js +0 -82
- package/dist/safe-bash/chunks/chunk-NB2V7EOY.js +0 -1348
- package/dist/safe-bash/chunks/chunk-NJVGOIYN.js +0 -17
- package/dist/safe-bash/chunks/chunk-OLNZHDIQ.js +0 -5141
- package/dist/safe-bash/chunks/chunk-OUUVROTM.js +0 -38
- package/dist/safe-bash/chunks/chunk-PA6GYKBQ.js +0 -151
- package/dist/safe-bash/chunks/chunk-PEFHZ226.js +0 -6957
- package/dist/safe-bash/chunks/chunk-QBB5P5KW.js +0 -4839
- package/dist/safe-bash/chunks/chunk-QHB7HBLA.js +0 -2317
- package/dist/safe-bash/chunks/chunk-RSAWYAEH.js +0 -973
- package/dist/safe-bash/chunks/chunk-UWLYRWT4.js +0 -739
- package/dist/safe-bash/chunks/chunk-V463NBQH.js +0 -13
- package/dist/safe-bash/chunks/chunk-WPUALYUS.js +0 -843
- package/dist/safe-bash/chunks/chunk-XHKFSRGU.js +0 -5747
- package/dist/safe-bash/chunks/chunk-XHRCLZ3N.js +0 -13391
- package/dist/safe-bash/chunks/chunk-XPF5XHLY.js +0 -15
- package/dist/safe-bash/chunks/chunk-ZBFOWARG.js +0 -305
- package/dist/safe-bash/chunks/chunk-ZG7GWQ5P.js +0 -1859
- package/dist/safe-bash/chunks/model-store-EIY3ZQLZ.js +0 -10
- package/dist/safe-bash/chunks/pdf-renderer-XIFUTX34.js +0 -22
- package/dist/safe-bash/chunks/pptx-ZL6L7KP2-O425BFIU.js +0 -30658
- package/dist/safe-bash/chunks/src-HHEPGR7U.js +0 -18969
- package/dist/safe-bash/chunks/src-V4VCM5OX.js +0 -187
- package/dist/safe-bash/chunks/usage-6TGVA2OM.js +0 -35
- package/dist/safe-bash/chunks/xz-EGQGQUEI.js +0 -8
- package/dist/safe-bash/chunks/xz-K2E2WLM6.js +0 -9
- package/dist/safe-bash/chunks/zstd-6JBLP5EF.js +0 -8
- package/dist/safe-bash/git_rust-C3QYOAZL.wasm +0 -0
|
@@ -1,3350 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
sha256
|
|
3
|
-
} from "./chunk-CTYJJD4O.js";
|
|
4
|
-
import {
|
|
5
|
-
PythonFailure,
|
|
6
|
-
PythonInvocationError,
|
|
7
|
-
isPythonFailureCategory,
|
|
8
|
-
parsePythonInvocation,
|
|
9
|
-
pythonDirectoryEntries,
|
|
10
|
-
pythonExecution,
|
|
11
|
-
pythonImportMetadata,
|
|
12
|
-
pythonRuntimeRelocation,
|
|
13
|
-
pythonStatProjection,
|
|
14
|
-
pythonTreeCleanup,
|
|
15
|
-
reportPythonFailure
|
|
16
|
-
} from "./chunk-BMO5LWEX.js";
|
|
17
|
-
import {
|
|
18
|
-
yield_exports
|
|
19
|
-
} from "./chunk-XPF5XHLY.js";
|
|
20
|
-
import {
|
|
21
|
-
dirname,
|
|
22
|
-
errors_exports,
|
|
23
|
-
output_exports,
|
|
24
|
-
resolvePath
|
|
25
|
-
} from "./chunk-OUUVROTM.js";
|
|
26
|
-
import {
|
|
27
|
-
command_exports,
|
|
28
|
-
io_exports
|
|
29
|
-
} from "./chunk-G2VCIF2J.js";
|
|
30
|
-
import {
|
|
31
|
-
path_exports
|
|
32
|
-
} from "./chunk-ISCZG7WA.js";
|
|
33
|
-
import {
|
|
34
|
-
sniffMimeType
|
|
35
|
-
} from "./chunk-ZBFOWARG.js";
|
|
36
|
-
import {
|
|
37
|
-
__reExport,
|
|
38
|
-
init_platform
|
|
39
|
-
} from "./chunk-ZG7GWQ5P.js";
|
|
40
|
-
|
|
41
|
-
// packages/safe-bash/src/commands/python/index.ts
|
|
42
|
-
init_platform();
|
|
43
|
-
import { PythonFileSystem, PythonStatTranslator } from "@poe-platform/safe-fs/core";
|
|
44
|
-
|
|
45
|
-
// packages/safe-bash/src/contracts/filesystem-descriptor.ts
|
|
46
|
-
var filesystem_descriptor_exports = {};
|
|
47
|
-
init_platform();
|
|
48
|
-
__reExport(filesystem_descriptor_exports, filesystem_descriptor_star);
|
|
49
|
-
import * as filesystem_descriptor_star from "../../safe-bash-contracts/filesystem-descriptor.js";
|
|
50
|
-
|
|
51
|
-
// packages/safe-bash/src/commands/python/reply.ts
|
|
52
|
-
init_platform();
|
|
53
|
-
function encodePythonReply(value, maxBytes) {
|
|
54
|
-
let used = 0;
|
|
55
|
-
const ancestors = /* @__PURE__ */ new Set();
|
|
56
|
-
const reserve = (bytes) => {
|
|
57
|
-
used += bytes;
|
|
58
|
-
if (used > maxBytes) throw new errors_exports.FsError("EFBIG", { syscall: "python reply" });
|
|
59
|
-
};
|
|
60
|
-
const string = (text2) => {
|
|
61
|
-
if (text2.length + 2 > maxBytes - used) throw new errors_exports.FsError("EFBIG", { syscall: "python reply" });
|
|
62
|
-
reserve(2);
|
|
63
|
-
for (let index = 0; index < text2.length; index++) {
|
|
64
|
-
const code = text2.charCodeAt(index);
|
|
65
|
-
if (code < 32) reserve([8, 9, 10, 12, 13].includes(code) ? 2 : 6);
|
|
66
|
-
else if (code === 34 || code === 92) reserve(2);
|
|
67
|
-
else if (code < 128) reserve(1);
|
|
68
|
-
else if (code < 2048) reserve(2);
|
|
69
|
-
else if (code >= 55296 && code <= 56319 && text2.charCodeAt(index + 1) >= 56320 && text2.charCodeAt(index + 1) <= 57343) {
|
|
70
|
-
reserve(4);
|
|
71
|
-
index++;
|
|
72
|
-
} else reserve(code >= 55296 && code <= 57343 ? 6 : 3);
|
|
73
|
-
}
|
|
74
|
-
return text2;
|
|
75
|
-
};
|
|
76
|
-
const own = (input, depth) => {
|
|
77
|
-
if (depth > 64) throw new errors_exports.FsError("EFBIG", { syscall: "python reply" });
|
|
78
|
-
if (input === null || input === void 0) {
|
|
79
|
-
reserve(4);
|
|
80
|
-
return null;
|
|
81
|
-
}
|
|
82
|
-
if (typeof input === "string") return string(input);
|
|
83
|
-
if (typeof input === "number") {
|
|
84
|
-
reserve(Number.isFinite(input) ? String(input).length : 4);
|
|
85
|
-
return input;
|
|
86
|
-
}
|
|
87
|
-
if (typeof input === "boolean") {
|
|
88
|
-
reserve(input ? 4 : 5);
|
|
89
|
-
return input;
|
|
90
|
-
}
|
|
91
|
-
if (typeof input !== "object") throw new TypeError("Invalid Python reply value");
|
|
92
|
-
if (ancestors.has(input)) throw new TypeError("Cyclic Python reply value");
|
|
93
|
-
ancestors.add(input);
|
|
94
|
-
try {
|
|
95
|
-
if (input instanceof Uint8Array || Array.isArray(input)) {
|
|
96
|
-
if (input.length > maxBytes - used) throw new errors_exports.FsError("EFBIG", { syscall: "python reply" });
|
|
97
|
-
reserve(2);
|
|
98
|
-
const output2 = [];
|
|
99
|
-
for (let index = 0; index < input.length; index++) {
|
|
100
|
-
if (index) reserve(1);
|
|
101
|
-
const descriptor = Object.getOwnPropertyDescriptor(input, String(index));
|
|
102
|
-
if (descriptor && !("value" in descriptor)) throw new TypeError("Accessor Python reply");
|
|
103
|
-
output2.push(own(descriptor?.value, depth + 1));
|
|
104
|
-
}
|
|
105
|
-
return output2;
|
|
106
|
-
}
|
|
107
|
-
reserve(2);
|
|
108
|
-
const output = /* @__PURE__ */ Object.create(null);
|
|
109
|
-
let count = 0;
|
|
110
|
-
let visited = 0;
|
|
111
|
-
for (const key in input) {
|
|
112
|
-
if (!Object.hasOwn(input, key)) continue;
|
|
113
|
-
if (++visited > maxBytes) throw new errors_exports.FsError("EFBIG", { syscall: "python reply" });
|
|
114
|
-
const descriptor = Object.getOwnPropertyDescriptor(input, key);
|
|
115
|
-
if (!("value" in descriptor)) throw new TypeError("Accessor Python reply");
|
|
116
|
-
if (descriptor.value === void 0 || typeof descriptor.value === "function") continue;
|
|
117
|
-
if (count++) reserve(1);
|
|
118
|
-
string(key);
|
|
119
|
-
reserve(1);
|
|
120
|
-
output[key] = own(descriptor.value, depth + 1);
|
|
121
|
-
}
|
|
122
|
-
return output;
|
|
123
|
-
} finally {
|
|
124
|
-
ancestors.delete(input);
|
|
125
|
-
}
|
|
126
|
-
};
|
|
127
|
-
const owned = own(value, 0);
|
|
128
|
-
return new TextEncoder().encode(JSON.stringify(owned));
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// packages/safe-bash/src/commands/python/installation.ts
|
|
132
|
-
init_platform();
|
|
133
|
-
var pythonInstallationHelp = `Usage: python -m pip install [-r FILE | --requirement FILE] PACKAGE ...
|
|
134
|
-
Installs compatible wheels and dependencies using the configured Pyodide installer.
|
|
135
|
-
PACKAGE is a package requirement or a canonical filesystem wheel path.
|
|
136
|
-
Supported options: -r, --requirement, -h, --help, --.
|
|
137
|
-
All other pip options and commands are unsupported, including --upgrade,
|
|
138
|
-
--force-reinstall, --no-index, --no-deps, --target, --user, --prefix, --root, --editable,
|
|
139
|
-
--index-url, --extra-index-url, --find-links, --trusted-host, --require-hashes,
|
|
140
|
-
--constraint, --dry-run, --quiet, --verbose and cache/build/configuration flags.
|
|
141
|
-
Configure transport and integrity through the Python package SDK configuration.
|
|
142
|
-
`;
|
|
143
|
-
function parsePythonInstallation(args) {
|
|
144
|
-
let pipArgs;
|
|
145
|
-
scan: for (let index = 0; index < args.length; index++) {
|
|
146
|
-
const option = args[index];
|
|
147
|
-
if (option === "--" || option === "-" || !option.startsWith("-") || option.startsWith("--")) return void 0;
|
|
148
|
-
for (let offset = 1; offset < option.length; offset++) {
|
|
149
|
-
const flag = option[offset];
|
|
150
|
-
if ("h?V".includes(flag)) return void 0;
|
|
151
|
-
if ("cmWX".includes(flag)) {
|
|
152
|
-
const operand = option.slice(offset + 1) || args[++index];
|
|
153
|
-
if (flag === "c") return void 0;
|
|
154
|
-
if (flag === "m") {
|
|
155
|
-
if (operand !== "pip") return void 0;
|
|
156
|
-
pipArgs = args.slice(index + 1);
|
|
157
|
-
break scan;
|
|
158
|
-
}
|
|
159
|
-
break;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
if (!pipArgs) return void 0;
|
|
164
|
-
const packages = [];
|
|
165
|
-
const requirements = [];
|
|
166
|
-
if (pipArgs.length === 0) return { packages, requirements, help: true };
|
|
167
|
-
if (pipArgs[0] === "--help" || pipArgs[0] === "-h") {
|
|
168
|
-
if (pipArgs.length > 1) throw new PythonInvocationError("unsupported pip option or argument after help: " + pipArgs[1]);
|
|
169
|
-
return { packages, requirements, help: true };
|
|
170
|
-
}
|
|
171
|
-
if (pipArgs[0] !== "install") throw new PythonInvocationError("unsupported pip command " + pipArgs[0]);
|
|
172
|
-
let operands = false;
|
|
173
|
-
let help = false;
|
|
174
|
-
for (let index = 1; index < pipArgs.length; index++) {
|
|
175
|
-
const option = pipArgs[index];
|
|
176
|
-
if (operands || !option.startsWith("-")) {
|
|
177
|
-
packages.push(option);
|
|
178
|
-
continue;
|
|
179
|
-
}
|
|
180
|
-
if (option === "--") {
|
|
181
|
-
operands = true;
|
|
182
|
-
continue;
|
|
183
|
-
}
|
|
184
|
-
if (option === "--help" || option === "-h") {
|
|
185
|
-
help = true;
|
|
186
|
-
continue;
|
|
187
|
-
}
|
|
188
|
-
if (option === "-r" || option === "--requirement" || option.startsWith("--requirement=") || option.startsWith("-r")) {
|
|
189
|
-
const path = option.startsWith("--requirement=") ? option.slice("--requirement=".length) : option !== "-r" && option.startsWith("-r") ? option.slice(2) : pipArgs[++index];
|
|
190
|
-
if (!path || path.startsWith("-")) throw new PythonInvocationError(option + " requires a path");
|
|
191
|
-
requirements.push(path);
|
|
192
|
-
continue;
|
|
193
|
-
}
|
|
194
|
-
throw new PythonInvocationError("unsupported pip option " + option);
|
|
195
|
-
}
|
|
196
|
-
if (!help && packages.length === 0 && requirements.length === 0) throw new PythonInvocationError("pip install requires at least one package or requirements file");
|
|
197
|
-
return { packages, requirements, help };
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// packages/safe-bash/src/commands/python/provisioning.ts
|
|
201
|
-
init_platform();
|
|
202
|
-
|
|
203
|
-
// packages/safe-bash/src/commands/python/manifest.ts
|
|
204
|
-
init_platform();
|
|
205
|
-
var PythonPackageConflictError = class extends Error {
|
|
206
|
-
code = "EPACKAGECONFLICT";
|
|
207
|
-
retryable = true;
|
|
208
|
-
constructor() {
|
|
209
|
-
super("Python package environment changed during installation; retry the command");
|
|
210
|
-
this.name = "PythonPackageConflictError";
|
|
211
|
-
}
|
|
212
|
-
};
|
|
213
|
-
function createPythonPackageManifestStore(options = {}) {
|
|
214
|
-
const maxBytes = options.maxBytes ?? 1048576;
|
|
215
|
-
if (!Number.isSafeInteger(maxBytes) || maxBytes < 1) throw new RangeError("Python manifest maxBytes must be a positive integer");
|
|
216
|
-
const manifests = /* @__PURE__ */ new Map();
|
|
217
|
-
let retainedBytes = 0;
|
|
218
|
-
let revision = 0;
|
|
219
|
-
let disposed = false;
|
|
220
|
-
const check = (scope, signal) => {
|
|
221
|
-
signal.throwIfAborted();
|
|
222
|
-
if (disposed) throw new Error("Python manifest store is disposed");
|
|
223
|
-
if (typeof scope !== "string" || !scope || scope.length > 1024) throw new TypeError("Invalid Python manifest scope");
|
|
224
|
-
};
|
|
225
|
-
return {
|
|
226
|
-
async get(scope, { signal }) {
|
|
227
|
-
check(scope, signal);
|
|
228
|
-
const value = manifests.get(scope);
|
|
229
|
-
return value ? { revision: value.revision, bytes: value.bytes.slice() } : void 0;
|
|
230
|
-
},
|
|
231
|
-
async compareAndSet(scope, expected, bytes, { signal }) {
|
|
232
|
-
check(scope, signal);
|
|
233
|
-
const current = manifests.get(scope);
|
|
234
|
-
if (current?.revision !== expected) return false;
|
|
235
|
-
if (!(bytes instanceof Uint8Array)) throw new TypeError("Python manifest must contain bytes");
|
|
236
|
-
const nextBytes = retainedBytes - (current?.bytes.length ?? 0) + bytes.length;
|
|
237
|
-
if (nextBytes > maxBytes || !current && manifests.size >= 1024 || revision >= Number.MAX_SAFE_INTEGER) {
|
|
238
|
-
throw new RangeError("Python manifest store budget exhausted");
|
|
239
|
-
}
|
|
240
|
-
manifests.set(scope, { revision: String(++revision), bytes: Uint8Array.from(bytes) });
|
|
241
|
-
retainedBytes = nextBytes;
|
|
242
|
-
return true;
|
|
243
|
-
},
|
|
244
|
-
dispose() {
|
|
245
|
-
disposed = true;
|
|
246
|
-
manifests.clear();
|
|
247
|
-
retainedBytes = 0;
|
|
248
|
-
}
|
|
249
|
-
};
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
// packages/safe-bash/src/commands/python/cache.ts
|
|
253
|
-
init_platform();
|
|
254
|
-
var pythonPackageRuntimeKey = "pyodide-314.0.6-cp314-emscripten-wasm32-v1";
|
|
255
|
-
function createPythonPackageCache(options = {}) {
|
|
256
|
-
const maxBytes = options.maxBytes ?? Infinity;
|
|
257
|
-
if (options.maxBytes !== void 0 && (!Number.isSafeInteger(maxBytes) || maxBytes < 1)) throw new RangeError("Python cache maxBytes must be a positive integer");
|
|
258
|
-
const values = /* @__PURE__ */ new Map();
|
|
259
|
-
const manifestKey = pythonPackageRuntimeKey + "-environment";
|
|
260
|
-
let retainedBytes = 0;
|
|
261
|
-
let disposed = false;
|
|
262
|
-
return {
|
|
263
|
-
async get(key) {
|
|
264
|
-
if (disposed) throw new Error("Python package cache is disposed");
|
|
265
|
-
return values.get(key)?.slice();
|
|
266
|
-
},
|
|
267
|
-
async set(key, bytes) {
|
|
268
|
-
if (disposed) throw new Error("Python package cache is disposed");
|
|
269
|
-
if (key === manifestKey && bytes.length > maxBytes) throw new RangeError("Python package manifest exceeds maxCacheBytes");
|
|
270
|
-
const previous = values.get(key);
|
|
271
|
-
if (previous) {
|
|
272
|
-
retainedBytes -= previous.length;
|
|
273
|
-
values.delete(key);
|
|
274
|
-
}
|
|
275
|
-
if (bytes.length > maxBytes) return;
|
|
276
|
-
while (values.size && retainedBytes + bytes.length > maxBytes) {
|
|
277
|
-
const oldest = [...values.keys()].find((entry) => entry !== manifestKey);
|
|
278
|
-
if (oldest === void 0) return;
|
|
279
|
-
retainedBytes -= values.get(oldest).length;
|
|
280
|
-
values.delete(oldest);
|
|
281
|
-
}
|
|
282
|
-
values.set(key, Uint8Array.from(bytes));
|
|
283
|
-
retainedBytes += bytes.length;
|
|
284
|
-
},
|
|
285
|
-
dispose() {
|
|
286
|
-
disposed = true;
|
|
287
|
-
values.clear();
|
|
288
|
-
retainedBytes = 0;
|
|
289
|
-
}
|
|
290
|
-
};
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
// packages/safe-bash/src/commands/python/provisioning.ts
|
|
294
|
-
var pythonDocumentPackages = Object.freeze([
|
|
295
|
-
"lxml==6.0.2",
|
|
296
|
-
"pillow==12.2.0",
|
|
297
|
-
"python-docx==1.2.0",
|
|
298
|
-
"openpyxl==3.1.5",
|
|
299
|
-
"XlsxWriter==3.2.9",
|
|
300
|
-
"pypdf==6.18.1",
|
|
301
|
-
"fpdf2==2.8.8",
|
|
302
|
-
"fonttools==4.65.0",
|
|
303
|
-
"defusedxml==0.7.1",
|
|
304
|
-
"et-xmlfile==2.0.0",
|
|
305
|
-
"typing-extensions==4.16.0"
|
|
306
|
-
]);
|
|
307
|
-
var encoder = new TextEncoder();
|
|
308
|
-
var decoder = new TextDecoder();
|
|
309
|
-
function digest(value) {
|
|
310
|
-
return Array.from(sha256(value), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
311
|
-
}
|
|
312
|
-
function failure(message, cause) {
|
|
313
|
-
return Object.assign(new Error(message, { cause }), { code: "EPACKAGE" });
|
|
314
|
-
}
|
|
315
|
-
function missing(error) {
|
|
316
|
-
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
317
|
-
}
|
|
318
|
-
function normalizeRequirement(value, cwd) {
|
|
319
|
-
const requirement = value.trim();
|
|
320
|
-
if (!requirement || requirement.startsWith("-")) throw failure(`Unsupported requirement: ${value}`);
|
|
321
|
-
if (requirement.endsWith(".whl") && !requirement.includes("://")) return "file://" + resolvePath(cwd, requirement).split("/").map(encodeURIComponent).join("/");
|
|
322
|
-
return requirement;
|
|
323
|
-
}
|
|
324
|
-
function checkSession(session) {
|
|
325
|
-
session.signal.throwIfAborted();
|
|
326
|
-
if (session.closed) throw failure("Python package session is closed");
|
|
327
|
-
}
|
|
328
|
-
function createPythonPackageEnvironment(options = {}) {
|
|
329
|
-
if (options.cache && options.cacheDirectory) throw new TypeError("Choose package cache or cacheDirectory, not both");
|
|
330
|
-
if (options.manifestStore && (typeof options.scope !== "string" || !options.scope.trim() || options.scope.length > 1024)) throw new TypeError("Shared Python manifests require an explicit nonempty scope");
|
|
331
|
-
if (options.scope !== void 0 && !options.manifestStore) throw new TypeError("Python scope requires a manifestStore");
|
|
332
|
-
const manifestKey = options.manifestStore ? pythonPackageRuntimeKey + "-environment-" + digest(encoder.encode(JSON.stringify(options.scope))) : pythonPackageRuntimeKey + "-environment";
|
|
333
|
-
if (options.profile !== void 0 && options.profile !== "documents") throw new TypeError("Unknown Python package profile");
|
|
334
|
-
const maxBytes = options.maxDownloadBytes ?? Infinity;
|
|
335
|
-
if (options.maxDownloadBytes !== void 0 && (!Number.isSafeInteger(maxBytes) || maxBytes < 1)) throw new RangeError("maxDownloadBytes must be a positive integer");
|
|
336
|
-
const maxManifestBytes = options.maxManifestBytes ?? Infinity;
|
|
337
|
-
const maxMetadataBytes = options.maxMetadataBytes ?? Infinity;
|
|
338
|
-
for (const name of ["maxManifestBytes", "maxMetadataBytes", "maxRequirementBytes"]) {
|
|
339
|
-
const value = options[name];
|
|
340
|
-
if (value !== void 0 && (!Number.isSafeInteger(value) || value < 1)) throw new RangeError(`${name} must be a positive integer`);
|
|
341
|
-
}
|
|
342
|
-
const maxCacheBytes = options.maxCacheBytes;
|
|
343
|
-
if (maxCacheBytes !== void 0 && (!Number.isSafeInteger(maxCacheBytes) || maxCacheBytes < 1)) throw new RangeError("maxCacheBytes must be a positive integer");
|
|
344
|
-
const defaultCache = createPythonPackageCache(maxCacheBytes === void 0 ? {} : { maxBytes: maxCacheBytes });
|
|
345
|
-
const sessions = /* @__PURE__ */ new Map();
|
|
346
|
-
const controller = new AbortController();
|
|
347
|
-
const pending = /* @__PURE__ */ new Set();
|
|
348
|
-
let disposed = false;
|
|
349
|
-
let disposing;
|
|
350
|
-
const admit = (operation) => (...args) => {
|
|
351
|
-
if (disposed) return Promise.reject(failure("Python package environment is disposed"));
|
|
352
|
-
const work = Promise.resolve().then(() => {
|
|
353
|
-
if (disposed) throw failure("Python package environment is disposed");
|
|
354
|
-
return operation(...args);
|
|
355
|
-
});
|
|
356
|
-
pending.add(work);
|
|
357
|
-
void work.then(() => {
|
|
358
|
-
pending.delete(work);
|
|
359
|
-
}, () => {
|
|
360
|
-
pending.delete(work);
|
|
361
|
-
});
|
|
362
|
-
return work;
|
|
363
|
-
};
|
|
364
|
-
let counter = 0;
|
|
365
|
-
let committing = Promise.resolve();
|
|
366
|
-
async function prepare(input) {
|
|
367
|
-
const invocation = new AbortController();
|
|
368
|
-
const signal = AbortSignal.any([input.signal, controller.signal, invocation.signal]);
|
|
369
|
-
(0, yield_exports.inheritYieldCheckpoint)(input.signal, signal);
|
|
370
|
-
const context = { ...input, signal };
|
|
371
|
-
context.signal.throwIfAborted();
|
|
372
|
-
const directory = options.cacheDirectory === void 0 ? void 0 : resolvePath(context.cwd, options.cacheDirectory, pythonPackageRuntimeKey);
|
|
373
|
-
const cache = options.cache ?? (directory === void 0 ? defaultCache : {
|
|
374
|
-
async get(key) {
|
|
375
|
-
try {
|
|
376
|
-
return await context.fs.readFile(resolvePath(directory, key), { signal: context.signal });
|
|
377
|
-
} catch (error) {
|
|
378
|
-
if (missing(error)) return void 0;
|
|
379
|
-
throw error;
|
|
380
|
-
}
|
|
381
|
-
},
|
|
382
|
-
async set(key, bytes) {
|
|
383
|
-
await context.fs.mkdir(directory, { recursive: true, signal: context.signal });
|
|
384
|
-
await context.fs.writeFile(resolvePath(directory, key), bytes, { signal: context.signal });
|
|
385
|
-
}
|
|
386
|
-
});
|
|
387
|
-
const manifestCache = options.cacheDirectory === void 0 ? defaultCache : cache;
|
|
388
|
-
let snapshot;
|
|
389
|
-
if (options.manifestStore) {
|
|
390
|
-
try {
|
|
391
|
-
snapshot = await options.manifestStore.get(manifestKey, context);
|
|
392
|
-
} catch (error) {
|
|
393
|
-
context.signal.throwIfAborted();
|
|
394
|
-
throw failure("Cannot read Python package environment manifest", error);
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
context.signal.throwIfAborted();
|
|
398
|
-
if (snapshot !== void 0 && (typeof snapshot !== "object" || snapshot === null || typeof snapshot.revision !== "string" || !snapshot.revision || snapshot.revision.length > 1024 || !(snapshot.bytes instanceof Uint8Array))) throw failure("Invalid Python package manifest snapshot");
|
|
399
|
-
const manifestRevision = snapshot?.revision;
|
|
400
|
-
const stored = options.manifestStore ? snapshot?.bytes : await manifestCache.get(manifestKey);
|
|
401
|
-
context.signal.throwIfAborted();
|
|
402
|
-
if (stored && stored.length > maxManifestBytes) throw failure("Python package manifest exceeds maxManifestBytes");
|
|
403
|
-
const manifest = stored === void 0 ? "" : decoder.decode(stored);
|
|
404
|
-
let previous;
|
|
405
|
-
try {
|
|
406
|
-
previous = stored === void 0 ? [] : JSON.parse(manifest);
|
|
407
|
-
} catch {
|
|
408
|
-
throw failure("Invalid Python package environment manifest");
|
|
409
|
-
}
|
|
410
|
-
if (!Array.isArray(previous) || previous.some((value) => typeof value !== "string")) throw failure("Invalid Python package environment manifest");
|
|
411
|
-
const requirements = [...previous, ...options.profile === "documents" ? pythonDocumentPackages : [], ...options.requirements ?? [], ...context.requirements ?? []].map((value) => normalizeRequirement(value, context.cwd));
|
|
412
|
-
for (const file of [...options.requirementFiles ?? [], ...context.requirementFiles ?? []]) {
|
|
413
|
-
const path = resolvePath(context.cwd, file);
|
|
414
|
-
let source;
|
|
415
|
-
try {
|
|
416
|
-
source = decoder.decode(await context.fs.readFile(path, { signal: context.signal, ...options.maxRequirementBytes === void 0 ? {} : { maxBytes: options.maxRequirementBytes } }));
|
|
417
|
-
} catch (error) {
|
|
418
|
-
context.signal.throwIfAborted();
|
|
419
|
-
throw failure(`Cannot read Python requirements ${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
420
|
-
}
|
|
421
|
-
for (const line of source.split("\n")) {
|
|
422
|
-
const comment = line.split("").findIndex((character, index) => character === "#" && (index === 0 || line[index - 1].trim() === ""));
|
|
423
|
-
const text2 = (comment < 0 ? line : line.slice(0, comment)).trim();
|
|
424
|
-
if (!text2) continue;
|
|
425
|
-
if (text2.endsWith("\\") || text2.startsWith("-")) throw failure(`Unsupported requirements option or continuation in ${path}: ${text2}`);
|
|
426
|
-
requirements.push(normalizeRequirement(text2, dirname(path)));
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
context.signal.throwIfAborted();
|
|
430
|
-
const session = String(++counter);
|
|
431
|
-
const unique = [...new Set(requirements)];
|
|
432
|
-
const aborted = () => {
|
|
433
|
-
const current = sessions.get(session);
|
|
434
|
-
if (current) {
|
|
435
|
-
current.closed = true;
|
|
436
|
-
current.opened.clear();
|
|
437
|
-
}
|
|
438
|
-
sessions.delete(session);
|
|
439
|
-
};
|
|
440
|
-
sessions.set(session, { ...context, cache, manifestCache, manifestRevision, controller: invocation, offline: context.offline ?? options.offline ?? false, requirements: unique, opened: /* @__PURE__ */ new Map(), opening: false, closed: false, manifest, aborted });
|
|
441
|
-
context.signal.addEventListener("abort", aborted, { once: true });
|
|
442
|
-
return { session, requirements: unique, offline: context.offline ?? options.offline ?? false };
|
|
443
|
-
}
|
|
444
|
-
async function dispatch(op, args, _context) {
|
|
445
|
-
_context.signal.throwIfAborted();
|
|
446
|
-
const session = sessions.get(String(args[0]));
|
|
447
|
-
if (!session) throw failure("Python package session is closed");
|
|
448
|
-
checkSession(session);
|
|
449
|
-
if (op === "package-commit") {
|
|
450
|
-
const pinned = args[1];
|
|
451
|
-
if (!Array.isArray(pinned) || pinned.some((value) => typeof value !== "string")) throw failure("Invalid installed package manifest");
|
|
452
|
-
const merged = [.../* @__PURE__ */ new Set([...session.requirements, ...pinned])];
|
|
453
|
-
const manifestBytes = encoder.encode(JSON.stringify(merged));
|
|
454
|
-
if (manifestBytes.length > maxManifestBytes) throw failure("Python package manifest exceeds maxManifestBytes");
|
|
455
|
-
const commit = committing.then(async () => {
|
|
456
|
-
checkSession(session);
|
|
457
|
-
if (options.manifestStore) {
|
|
458
|
-
const committed = await options.manifestStore.compareAndSet(manifestKey, session.manifestRevision, manifestBytes, { signal: session.signal });
|
|
459
|
-
checkSession(session);
|
|
460
|
-
if (typeof committed !== "boolean") throw failure("Invalid Python package manifest publication result");
|
|
461
|
-
if (!committed) throw new PythonPackageConflictError();
|
|
462
|
-
} else {
|
|
463
|
-
const current = await session.manifestCache.get(manifestKey);
|
|
464
|
-
checkSession(session);
|
|
465
|
-
if (current && current.length > maxManifestBytes) throw failure("Python package manifest exceeds maxManifestBytes");
|
|
466
|
-
if ((current === void 0 ? "" : decoder.decode(current)) !== session.manifest) throw new PythonPackageConflictError();
|
|
467
|
-
await session.manifestCache.set(manifestKey, manifestBytes);
|
|
468
|
-
}
|
|
469
|
-
checkSession(session);
|
|
470
|
-
});
|
|
471
|
-
committing = commit.catch(() => {
|
|
472
|
-
});
|
|
473
|
-
await commit;
|
|
474
|
-
options.onProgress?.({ phase: "installed" });
|
|
475
|
-
return null;
|
|
476
|
-
}
|
|
477
|
-
if (op === "package-read") {
|
|
478
|
-
const bytes = session.opened.get(String(args[1]));
|
|
479
|
-
const offset = args[2];
|
|
480
|
-
const length = args[3];
|
|
481
|
-
if (!bytes || !Number.isSafeInteger(offset) || !Number.isSafeInteger(length) || offset < 0 || length < 1 || length > 65536) throw failure("Invalid package chunk request");
|
|
482
|
-
return Array.from(bytes.subarray(offset, offset + length));
|
|
483
|
-
}
|
|
484
|
-
if (op === "package-close") {
|
|
485
|
-
session.opened.delete(String(args[1]));
|
|
486
|
-
return null;
|
|
487
|
-
}
|
|
488
|
-
if (op !== "package-open" || typeof args[1] !== "string") throw failure("Invalid package operation");
|
|
489
|
-
if (session.opening) throw failure("Package download already in progress");
|
|
490
|
-
session.opening = true;
|
|
491
|
-
session.opened.clear();
|
|
492
|
-
try {
|
|
493
|
-
const url = args[1];
|
|
494
|
-
const expected = args[2];
|
|
495
|
-
if (expected !== void 0 && expected !== null && (typeof expected !== "string" || expected.length !== 64 || Array.from(expected).some((c) => !"0123456789abcdef".includes(c)))) throw failure("Invalid SHA-256 package integrity value");
|
|
496
|
-
const address = pythonPackageRuntimeKey + "-url-" + digest(encoder.encode(url));
|
|
497
|
-
const canonicalWheel = url.startsWith("file:") || url.startsWith("emfs:");
|
|
498
|
-
const metadata = canonicalWheel ? void 0 : await session.cache.get(address);
|
|
499
|
-
checkSession(session);
|
|
500
|
-
let bytes;
|
|
501
|
-
let headers = [];
|
|
502
|
-
if (metadata) {
|
|
503
|
-
if (metadata.length > maxMetadataBytes) throw failure("Python package cache metadata exceeds maxMetadataBytes");
|
|
504
|
-
let record2;
|
|
505
|
-
try {
|
|
506
|
-
record2 = JSON.parse(decoder.decode(metadata));
|
|
507
|
-
} catch {
|
|
508
|
-
throw failure(`Invalid package cache metadata: ${url}`);
|
|
509
|
-
}
|
|
510
|
-
if (typeof record2 !== "object" || record2 === null || typeof record2.digest !== "string" || record2.digest.length !== 64 || Array.from(record2.digest).some((char) => !"0123456789abcdef".includes(char)) || !Array.isArray(record2.headers) || record2.headers.some((pair) => !Array.isArray(pair) || pair.length !== 2 || pair.some((value) => typeof value !== "string"))) throw failure(`Invalid package cache metadata: ${url}`);
|
|
511
|
-
bytes = await session.cache.get(pythonPackageRuntimeKey + "-sha256-" + record2.digest);
|
|
512
|
-
headers = record2.headers;
|
|
513
|
-
checkSession(session);
|
|
514
|
-
if (bytes && bytes.length > maxBytes) throw failure("Cached package exceeds maxDownloadBytes");
|
|
515
|
-
if (bytes) bytes = Uint8Array.from(bytes);
|
|
516
|
-
if (bytes && digest(bytes) !== record2.digest) throw failure(`Package cache integrity mismatch: ${url}`);
|
|
517
|
-
if (bytes) options.onProgress?.({ phase: "cached", url, bytes: bytes.length });
|
|
518
|
-
}
|
|
519
|
-
if (!bytes) {
|
|
520
|
-
if (canonicalWheel) {
|
|
521
|
-
const path = new URL(url);
|
|
522
|
-
if (path.host && path.host !== "localhost") throw failure("Local wheels must use the canonical filesystem");
|
|
523
|
-
try {
|
|
524
|
-
bytes = await session.fs.readFile(decodeURIComponent(path.pathname), { signal: session.signal, ...Number.isFinite(maxBytes) ? { maxBytes } : {} });
|
|
525
|
-
} catch (error) {
|
|
526
|
-
checkSession(session);
|
|
527
|
-
throw failure(`Cannot read canonical Python wheel ${path.pathname}: ${error instanceof Error ? error.message : String(error)}`);
|
|
528
|
-
}
|
|
529
|
-
} else {
|
|
530
|
-
if (session.offline) throw failure(`Offline package cache miss: ${url}`);
|
|
531
|
-
if (!options.transport || !options.authorize) throw failure("Python package download requires configured transport and authorization");
|
|
532
|
-
let current = new URL(url);
|
|
533
|
-
let redirectFrom;
|
|
534
|
-
for (let redirect = 0; ; redirect++) {
|
|
535
|
-
if (current.protocol !== "https:" && current.protocol !== "http:") throw failure(`Unsupported package transport protocol: ${current.protocol}`);
|
|
536
|
-
if (current.username || current.password) throw failure("Package URLs with credentials are unsupported");
|
|
537
|
-
let denyPrivate = false;
|
|
538
|
-
const allowed = await options.authorize({ url: current.href, method: "GET", attempt: 0, signal: session.signal, ...redirectFrom === void 0 ? {} : { redirectFrom }, requirePrivateNetworkDeny() {
|
|
539
|
-
denyPrivate = true;
|
|
540
|
-
} });
|
|
541
|
-
checkSession(session);
|
|
542
|
-
if (!allowed) throw failure(`Python package download authorization denied: ${current.href}`);
|
|
543
|
-
if (denyPrivate && !options.transport.supportsPrivateNetworkDeny) throw failure("Package transport cannot enforce private network denial");
|
|
544
|
-
checkSession(session);
|
|
545
|
-
const response = await options.transport({ url: current.href, method: "GET", headers: [["accept", "application/vnd.pypi.simple.v1+json, application/json;q=0.9, */*;q=0.1"]], signal: session.signal, ...denyPrivate ? { denyPrivateNetworks: true } : {} });
|
|
546
|
-
try {
|
|
547
|
-
checkSession(session);
|
|
548
|
-
if ([301, 302, 303, 307, 308].includes(response.status)) {
|
|
549
|
-
if (redirect >= 5) throw failure("Too many package redirects");
|
|
550
|
-
const location = response.headers.find(([key2]) => key2.toLowerCase() === "location")?.[1];
|
|
551
|
-
if (!location) throw failure("Package redirect has no location");
|
|
552
|
-
redirectFrom = current.href;
|
|
553
|
-
current = new URL(location, current);
|
|
554
|
-
continue;
|
|
555
|
-
}
|
|
556
|
-
if (response.status < 200 || response.status >= 300) throw failure(`Package download failed: HTTP ${response.status} ${current.href}`);
|
|
557
|
-
headers = response.headers;
|
|
558
|
-
const encoding = headers.find(([key2]) => key2.toLowerCase() === "content-encoding")?.[1].trim().toLowerCase();
|
|
559
|
-
const length = headers.find(([key2]) => key2.toLowerCase() === "content-length")?.[1];
|
|
560
|
-
const total = length === void 0 || encoding !== void 0 && encoding !== "identity" ? void 0 : Number(length);
|
|
561
|
-
if (total !== void 0 && Number.isFinite(total) && total > maxBytes) throw failure("Package download exceeds maxDownloadBytes");
|
|
562
|
-
const chunks = [];
|
|
563
|
-
let count = 0;
|
|
564
|
-
for await (const chunk of response.body) {
|
|
565
|
-
checkSession(session);
|
|
566
|
-
if (chunk.length === 0) continue;
|
|
567
|
-
count += chunk.length;
|
|
568
|
-
if (count > maxBytes) throw failure("Package download exceeds maxDownloadBytes");
|
|
569
|
-
chunks.push(Uint8Array.from(chunk));
|
|
570
|
-
options.onProgress?.({ phase: "download", url, bytes: count, ...typeof total === "number" && Number.isSafeInteger(total) ? { totalBytes: total } : {} });
|
|
571
|
-
}
|
|
572
|
-
bytes = new Uint8Array(count);
|
|
573
|
-
let offset = 0;
|
|
574
|
-
for (const chunk of chunks) {
|
|
575
|
-
bytes.set(chunk, offset);
|
|
576
|
-
offset += chunk.length;
|
|
577
|
-
}
|
|
578
|
-
break;
|
|
579
|
-
} finally {
|
|
580
|
-
await response.dispose();
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
checkSession(session);
|
|
585
|
-
if (expected && digest(bytes) !== expected) throw failure(`Package integrity mismatch: ${url}`);
|
|
586
|
-
const hash = digest(bytes);
|
|
587
|
-
const metadataBytes = encoder.encode(JSON.stringify({ digest: hash, headers }));
|
|
588
|
-
if (metadataBytes.length > maxMetadataBytes) throw failure("Python package cache metadata exceeds maxMetadataBytes");
|
|
589
|
-
await session.cache.set(pythonPackageRuntimeKey + "-sha256-" + hash, Uint8Array.from(bytes));
|
|
590
|
-
checkSession(session);
|
|
591
|
-
if (!canonicalWheel) await session.cache.set(address, metadataBytes);
|
|
592
|
-
checkSession(session);
|
|
593
|
-
}
|
|
594
|
-
checkSession(session);
|
|
595
|
-
if (expected && digest(bytes) !== expected) throw failure(`Package integrity mismatch: ${url}`);
|
|
596
|
-
const key = digest(bytes);
|
|
597
|
-
session.opened.set(key, bytes);
|
|
598
|
-
return { key, size: bytes.length, headers };
|
|
599
|
-
} finally {
|
|
600
|
-
session.opening = false;
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
return {
|
|
604
|
-
prepare: admit(prepare),
|
|
605
|
-
dispatch: admit(dispatch),
|
|
606
|
-
finish(start) {
|
|
607
|
-
const session = sessions.get(start.session);
|
|
608
|
-
if (session) {
|
|
609
|
-
session.closed = true;
|
|
610
|
-
session.opened.clear();
|
|
611
|
-
session.signal.removeEventListener("abort", session.aborted);
|
|
612
|
-
session.controller.abort(failure("Python package session is closed"));
|
|
613
|
-
}
|
|
614
|
-
sessions.delete(start.session);
|
|
615
|
-
},
|
|
616
|
-
dispose() {
|
|
617
|
-
if (!disposing) {
|
|
618
|
-
disposed = true;
|
|
619
|
-
controller.abort(failure("Python package environment is disposed"));
|
|
620
|
-
disposing = Promise.allSettled([...pending]).then(() => {
|
|
621
|
-
sessions.clear();
|
|
622
|
-
defaultCache.dispose();
|
|
623
|
-
});
|
|
624
|
-
}
|
|
625
|
-
return disposing;
|
|
626
|
-
}
|
|
627
|
-
};
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
// packages/safe-bash/src/commands/python/host-capabilities.ts
|
|
631
|
-
init_platform();
|
|
632
|
-
function pythonHostFailureCode(error) {
|
|
633
|
-
if (error instanceof RangeError) return "limit";
|
|
634
|
-
if (error instanceof Error && error.name === "TimeoutError") return "timeout";
|
|
635
|
-
return "service";
|
|
636
|
-
}
|
|
637
|
-
function createPythonHostBridge(capabilities, options) {
|
|
638
|
-
const maxMessageBytes = options.maxMessageBytes ?? Infinity;
|
|
639
|
-
const maxConcurrentCalls = options.maxConcurrentCalls ?? Infinity;
|
|
640
|
-
const maxStreams = options.maxStreams ?? Infinity;
|
|
641
|
-
const maxStreamBytes = options.maxStreamBytes ?? Infinity;
|
|
642
|
-
const maxMessageDepth = options.maxMessageDepth ?? Infinity;
|
|
643
|
-
for (const limit of [maxMessageBytes, maxMessageDepth, maxConcurrentCalls, maxStreams, maxStreamBytes]) {
|
|
644
|
-
if (limit !== Infinity && (!Number.isSafeInteger(limit) || limit < 1)) throw new RangeError("Invalid Python host capability limit");
|
|
645
|
-
}
|
|
646
|
-
const registry = new Map(Object.entries(capabilities));
|
|
647
|
-
const controller = new AbortController();
|
|
648
|
-
const signal = AbortSignal.any([options.signal, controller.signal]);
|
|
649
|
-
const pending = /* @__PURE__ */ new Set();
|
|
650
|
-
const streams = /* @__PURE__ */ new Map();
|
|
651
|
-
const jobs = /* @__PURE__ */ new Map();
|
|
652
|
-
const cleanupFailures = [];
|
|
653
|
-
const jobWork = /* @__PURE__ */ new Set();
|
|
654
|
-
let nextJob = 1;
|
|
655
|
-
let nextHandle = 1;
|
|
656
|
-
let retired = false;
|
|
657
|
-
let closing;
|
|
658
|
-
const data = (value) => {
|
|
659
|
-
const ancestors = /* @__PURE__ */ new Set();
|
|
660
|
-
let nodes = 0;
|
|
661
|
-
let estimatedBytes = 0;
|
|
662
|
-
const inspect = (item, depth) => {
|
|
663
|
-
if (++nodes > maxMessageBytes || depth > maxMessageDepth) throw new RangeError("Python host message limit exceeded");
|
|
664
|
-
if (item === null || typeof item === "boolean" || typeof item === "number" && Number.isFinite(item)) {
|
|
665
|
-
estimatedBytes += JSON.stringify(item).length;
|
|
666
|
-
if (estimatedBytes > maxMessageBytes) throw new RangeError("Python host message limit exceeded");
|
|
667
|
-
return;
|
|
668
|
-
}
|
|
669
|
-
if (typeof item === "string") {
|
|
670
|
-
if (item.length > maxMessageBytes) throw new RangeError("Python host message limit exceeded");
|
|
671
|
-
estimatedBytes += new TextEncoder().encode(JSON.stringify(item)).length;
|
|
672
|
-
if (estimatedBytes > maxMessageBytes) throw new RangeError("Python host message limit exceeded");
|
|
673
|
-
return;
|
|
674
|
-
}
|
|
675
|
-
if (typeof item !== "object" || item === null || !Array.isArray(item) && Object.getPrototypeOf(item) !== Object.prototype && Object.getPrototypeOf(item) !== null) throw new TypeError("Python host messages must contain data only");
|
|
676
|
-
if (Array.isArray(item) && item.length > maxMessageBytes) throw new RangeError("Python host message limit exceeded");
|
|
677
|
-
if (ancestors.has(item)) throw new TypeError("Python host messages must not contain cycles");
|
|
678
|
-
ancestors.add(item);
|
|
679
|
-
estimatedBytes += 2;
|
|
680
|
-
if (Object.getOwnPropertySymbols(item).length) throw new TypeError("Python host messages must contain data only");
|
|
681
|
-
for (const key in item) {
|
|
682
|
-
if (!Object.hasOwn(item, key)) continue;
|
|
683
|
-
estimatedBytes += 2;
|
|
684
|
-
if (estimatedBytes > maxMessageBytes) throw new RangeError("Python host message limit exceeded");
|
|
685
|
-
if (Array.isArray(item) && key === "length") continue;
|
|
686
|
-
if (typeof key !== "string") throw new TypeError("Python host messages must contain data only");
|
|
687
|
-
const descriptor = Object.getOwnPropertyDescriptor(item, key);
|
|
688
|
-
if (!("value" in descriptor)) throw new TypeError("Python host messages must contain data only");
|
|
689
|
-
if (!Array.isArray(item)) inspect(key, depth + 1);
|
|
690
|
-
inspect(descriptor.value, depth + 1);
|
|
691
|
-
}
|
|
692
|
-
ancestors.delete(item);
|
|
693
|
-
};
|
|
694
|
-
inspect(value, 0);
|
|
695
|
-
const encoded = JSON.stringify(value);
|
|
696
|
-
if (new TextEncoder().encode(encoded).length > maxMessageBytes) throw new RangeError("Python host message limit exceeded");
|
|
697
|
-
return JSON.parse(encoded);
|
|
698
|
-
};
|
|
699
|
-
const assertLive = () => {
|
|
700
|
-
if (retired) throw new Error("Python host capability retired");
|
|
701
|
-
signal.throwIfAborted();
|
|
702
|
-
};
|
|
703
|
-
const release = async (handle, cancel = true) => {
|
|
704
|
-
const stream = streams.get(handle);
|
|
705
|
-
if (!stream) throw new Error("Unknown Python host stream");
|
|
706
|
-
streams.delete(handle);
|
|
707
|
-
if (cancel) stream.controller.abort(new Error("Python host stream released"));
|
|
708
|
-
try {
|
|
709
|
-
await stream.iterator.return?.();
|
|
710
|
-
} catch (error) {
|
|
711
|
-
cleanupFailures.push(error);
|
|
712
|
-
throw error;
|
|
713
|
-
}
|
|
714
|
-
};
|
|
715
|
-
const pull = async (handle) => {
|
|
716
|
-
const stream = streams.get(handle);
|
|
717
|
-
if (!stream) throw new Error("Unknown Python host stream");
|
|
718
|
-
stream.busy = true;
|
|
719
|
-
try {
|
|
720
|
-
const result = await stream.iterator.next();
|
|
721
|
-
assertLive();
|
|
722
|
-
stream.controller.signal.throwIfAborted();
|
|
723
|
-
if (result.done) {
|
|
724
|
-
await release(handle, false);
|
|
725
|
-
return { done: true };
|
|
726
|
-
}
|
|
727
|
-
const chunkBytes = result.value instanceof Uint8Array ? result.value.length : new TextEncoder().encode(typeof result.value === "string" ? result.value : JSON.stringify(data(result.value))).length;
|
|
728
|
-
if (chunkBytes > maxStreamBytes - stream.bytes) throw new RangeError("Python host stream byte limit exceeded");
|
|
729
|
-
stream.bytes += chunkBytes;
|
|
730
|
-
const value = result.value instanceof Uint8Array ? (() => {
|
|
731
|
-
if (result.value.length > maxMessageBytes / 4) throw new RangeError("Python host message limit exceeded");
|
|
732
|
-
return { type: "bytes", bytes: Array.from(result.value) };
|
|
733
|
-
})() : typeof result.value === "string" ? { type: "text", text: result.value } : { type: "data", value: result.value };
|
|
734
|
-
return data({ done: false, value });
|
|
735
|
-
} catch (error) {
|
|
736
|
-
if (streams.has(handle)) await release(handle);
|
|
737
|
-
throw error;
|
|
738
|
-
} finally {
|
|
739
|
-
stream.busy = false;
|
|
740
|
-
}
|
|
741
|
-
};
|
|
742
|
-
const request = async (incoming) => {
|
|
743
|
-
assertLive();
|
|
744
|
-
if (pending.size >= maxConcurrentCalls) throw new Error("Python host call concurrency limit exceeded");
|
|
745
|
-
const payload = data(incoming);
|
|
746
|
-
if (!payload || Array.isArray(payload) || typeof payload !== "object" || payload.version !== 1) throw new TypeError("Unsupported Python host protocol");
|
|
747
|
-
const operation = Promise.resolve().then(async () => {
|
|
748
|
-
assertLive();
|
|
749
|
-
if (payload.operation === "poll" || payload.operation === "cancel") {
|
|
750
|
-
const handle = payload.handle;
|
|
751
|
-
if (typeof handle !== "number") throw new TypeError("Invalid Python host job");
|
|
752
|
-
const job = jobs.get(handle);
|
|
753
|
-
if (!job) throw new Error("Unknown Python host job");
|
|
754
|
-
if (payload.operation === "cancel") {
|
|
755
|
-
jobs.delete(handle);
|
|
756
|
-
job.controller.abort(new Error("Python guest cancelled host operation"));
|
|
757
|
-
return null;
|
|
758
|
-
}
|
|
759
|
-
if (job.result === void 0 && !job.failed) return { done: false };
|
|
760
|
-
jobs.delete(handle);
|
|
761
|
-
return job.failed ? { done: true, error: "Python host operation failed", errorCode: job.failed } : { done: true, value: job.result };
|
|
762
|
-
}
|
|
763
|
-
if (Math.max(jobWork.size, jobs.size) >= maxConcurrentCalls) throw new Error("Python host call concurrency limit exceeded");
|
|
764
|
-
if (payload.operation === "begin" || payload.operation === "begin-next") {
|
|
765
|
-
let work;
|
|
766
|
-
let child;
|
|
767
|
-
if (payload.operation === "begin-next") {
|
|
768
|
-
const stream = typeof payload.handle === "number" ? streams.get(payload.handle) : void 0;
|
|
769
|
-
if (!stream || stream.busy) throw new Error("Unknown or busy Python host stream");
|
|
770
|
-
child = stream.controller;
|
|
771
|
-
work = () => pull(payload.handle);
|
|
772
|
-
} else {
|
|
773
|
-
const capability2 = typeof payload.capability === "string" ? registry.get(payload.capability) : void 0;
|
|
774
|
-
if (!capability2?.call) throw new Error("Unknown Python host capability");
|
|
775
|
-
child = new AbortController();
|
|
776
|
-
work = (childSignal) => capability2.call(payload.value ?? null, { signal: childSignal });
|
|
777
|
-
}
|
|
778
|
-
const handle = nextJob++;
|
|
779
|
-
const job = { controller: child };
|
|
780
|
-
jobs.set(handle, job);
|
|
781
|
-
const task = Promise.resolve().then(() => {
|
|
782
|
-
const childSignal = AbortSignal.any([signal, child.signal]);
|
|
783
|
-
childSignal.throwIfAborted();
|
|
784
|
-
return work(childSignal);
|
|
785
|
-
}).then((value) => {
|
|
786
|
-
assertLive();
|
|
787
|
-
child.signal.throwIfAborted();
|
|
788
|
-
job.result = data(value);
|
|
789
|
-
}).catch((error) => {
|
|
790
|
-
job.failed = pythonHostFailureCode(error);
|
|
791
|
-
}).finally(() => {
|
|
792
|
-
jobWork.delete(task);
|
|
793
|
-
});
|
|
794
|
-
jobWork.add(task);
|
|
795
|
-
return handle;
|
|
796
|
-
}
|
|
797
|
-
if (payload.operation === "next" || payload.operation === "release") {
|
|
798
|
-
const handle = payload.handle;
|
|
799
|
-
if (typeof handle !== "number" || !Number.isSafeInteger(handle)) throw new TypeError("Invalid Python host stream handle");
|
|
800
|
-
const stream = streams.get(handle);
|
|
801
|
-
if (!stream) throw new Error("Unknown Python host stream");
|
|
802
|
-
if (stream.busy) throw new Error("Python host stream already pulling");
|
|
803
|
-
if (payload.operation === "release") {
|
|
804
|
-
await release(handle);
|
|
805
|
-
return null;
|
|
806
|
-
}
|
|
807
|
-
return pull(handle);
|
|
808
|
-
}
|
|
809
|
-
if (typeof payload.capability !== "string") throw new TypeError("Invalid Python host capability");
|
|
810
|
-
const capability = registry.get(payload.capability);
|
|
811
|
-
if (!capability) throw new Error("Unknown Python host capability");
|
|
812
|
-
if (payload.operation === "call" && capability.call) {
|
|
813
|
-
const result = await capability.call(payload.value ?? null, { signal });
|
|
814
|
-
assertLive();
|
|
815
|
-
return data(result);
|
|
816
|
-
}
|
|
817
|
-
if (payload.operation === "stream" && capability.stream) {
|
|
818
|
-
if (streams.size >= maxStreams) throw new Error("Python host stream limit exceeded");
|
|
819
|
-
const child = new AbortController();
|
|
820
|
-
const iterator = capability.stream(payload.value ?? null, { signal: AbortSignal.any([signal, child.signal]) })[Symbol.asyncIterator]();
|
|
821
|
-
const handle = nextHandle++;
|
|
822
|
-
streams.set(handle, { iterator, busy: false, controller: child, bytes: 0 });
|
|
823
|
-
return handle;
|
|
824
|
-
}
|
|
825
|
-
throw new Error("Unsupported Python host capability operation");
|
|
826
|
-
});
|
|
827
|
-
pending.add(operation);
|
|
828
|
-
try {
|
|
829
|
-
return await operation;
|
|
830
|
-
} finally {
|
|
831
|
-
pending.delete(operation);
|
|
832
|
-
}
|
|
833
|
-
};
|
|
834
|
-
const close = () => {
|
|
835
|
-
if (closing) return closing;
|
|
836
|
-
retired = true;
|
|
837
|
-
controller.abort(new Error("Python host capability retired"));
|
|
838
|
-
closing = (async () => {
|
|
839
|
-
for (const job of jobs.values()) job.controller.abort(controller.signal.reason);
|
|
840
|
-
await Promise.allSettled([...pending, ...jobWork]);
|
|
841
|
-
jobs.clear();
|
|
842
|
-
const results = await Promise.allSettled([...streams.keys()].map((handle) => release(handle)));
|
|
843
|
-
registry.clear();
|
|
844
|
-
for (const result of results) if (result.status === "rejected") throw result.reason;
|
|
845
|
-
if (cleanupFailures.length) throw cleanupFailures[0];
|
|
846
|
-
})();
|
|
847
|
-
return closing;
|
|
848
|
-
};
|
|
849
|
-
return { request, close };
|
|
850
|
-
}
|
|
851
|
-
|
|
852
|
-
// packages/safe-bash/src/commands/python/shell-capability.ts
|
|
853
|
-
init_platform();
|
|
854
|
-
var dispatching = /* @__PURE__ */ new WeakMap();
|
|
855
|
-
function pythonShellDispatchActive(scope) {
|
|
856
|
-
return scope !== void 0 && (dispatching.get(scope) ?? 0) > 0;
|
|
857
|
-
}
|
|
858
|
-
function createPythonShellCapability(context, options = {}) {
|
|
859
|
-
if (!context.invoke || !context.executionScope) throw new TypeError("Python shell capability requires a parent shell invocation");
|
|
860
|
-
const maxInputBytes = options.maxInputBytes ?? Infinity;
|
|
861
|
-
const maxOutputBytes = options.maxOutputBytes ?? Infinity;
|
|
862
|
-
const maxConcurrentCalls = options.maxConcurrentCalls ?? Infinity;
|
|
863
|
-
for (const limit of [maxInputBytes, maxOutputBytes, maxConcurrentCalls]) if (limit !== Infinity && (!Number.isSafeInteger(limit) || limit < 1)) throw new RangeError("Invalid Python shell limit");
|
|
864
|
-
const chunkBytes = options.maxStreamChunkBytes ?? 16384;
|
|
865
|
-
if (!Number.isSafeInteger(chunkBytes) || chunkBytes < 1) throw new RangeError("Invalid Python shell stream chunk limit");
|
|
866
|
-
const scope = context.executionScope;
|
|
867
|
-
const invoke = context.invoke;
|
|
868
|
-
let running = 0;
|
|
869
|
-
const execute = async (value, { signal }, publish) => {
|
|
870
|
-
signal.throwIfAborted();
|
|
871
|
-
if (running >= maxConcurrentCalls) throw new Error("Python nested shell concurrency limit exceeded");
|
|
872
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) throw new TypeError("Expected a Python shell request");
|
|
873
|
-
const request = value;
|
|
874
|
-
const requestedOutput = request.maxOutputBytes;
|
|
875
|
-
if (requestedOutput !== void 0 && (typeof requestedOutput !== "number" || requestedOutput !== Infinity && (!Number.isSafeInteger(requestedOutput) || requestedOutput < 0))) throw new RangeError("Invalid Python shell output limit");
|
|
876
|
-
const outputLimit = Math.min(maxOutputBytes, typeof requestedOutput === "number" ? requestedOutput : maxOutputBytes);
|
|
877
|
-
const timeoutMs = request.timeoutMs;
|
|
878
|
-
if (timeoutMs !== void 0 && (typeof timeoutMs !== "number" || !Number.isSafeInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 2147483647)) throw new RangeError("Invalid Python shell timeout");
|
|
879
|
-
const childSignal = typeof timeoutMs === "number" ? AbortSignal.any([signal, AbortSignal.timeout(timeoutMs)]) : signal;
|
|
880
|
-
const script = request.script;
|
|
881
|
-
const argv = request.argv;
|
|
882
|
-
if (typeof script === "string" === Array.isArray(argv)) throw new TypeError("Provide either literal argv or a script");
|
|
883
|
-
if (argv !== void 0 && (!Array.isArray(argv) || !argv.length || argv.some((item) => typeof item !== "string" || item.includes("\0")))) throw new TypeError("Invalid Python shell argv");
|
|
884
|
-
if (request.cwd !== void 0 && (typeof request.cwd !== "string" || request.cwd.includes("\0"))) throw new TypeError("Invalid Python shell cwd");
|
|
885
|
-
const env = request.env === void 0 ? { ...context.env } : {};
|
|
886
|
-
if (request.env !== void 0) {
|
|
887
|
-
if (!request.env || typeof request.env !== "object" || Array.isArray(request.env)) throw new TypeError("Invalid Python shell environment");
|
|
888
|
-
for (const [key, item] of Object.entries(request.env)) {
|
|
889
|
-
if (typeof item !== "string" || key.includes("=") || key.includes("\0") || item.includes("\0")) throw new TypeError("Invalid Python shell environment");
|
|
890
|
-
Object.defineProperty(env, key, { value: item, enumerable: true, configurable: true, writable: true });
|
|
891
|
-
}
|
|
892
|
-
}
|
|
893
|
-
const input = request.stdin ?? [];
|
|
894
|
-
if (typeof input !== "string" && (!Array.isArray(input) || input.some((byte) => typeof byte !== "number" || !Number.isInteger(byte) || byte < 0 || byte > 255))) throw new TypeError("Invalid Python shell stdin");
|
|
895
|
-
if (input.length > maxInputBytes) throw new RangeError("Python shell input limit exceeded");
|
|
896
|
-
const bytes = typeof input === "string" ? new TextEncoder().encode(input) : Uint8Array.from(input);
|
|
897
|
-
if (bytes.length > maxInputBytes) throw new RangeError("Python shell input limit exceeded");
|
|
898
|
-
const source = request.stdinMode === "inherit" && request.stdin === void 0 ? context.stdin : { async *[Symbol.asyncIterator]() {
|
|
899
|
-
yield bytes;
|
|
900
|
-
} };
|
|
901
|
-
const childInput = { async *[Symbol.asyncIterator]() {
|
|
902
|
-
let received = 0;
|
|
903
|
-
for await (const fragment of source) {
|
|
904
|
-
childSignal.throwIfAborted();
|
|
905
|
-
received += fragment.length;
|
|
906
|
-
if (received > maxInputBytes) throw new RangeError("Python shell input limit exceeded");
|
|
907
|
-
yield fragment;
|
|
908
|
-
}
|
|
909
|
-
} };
|
|
910
|
-
const stdout = [];
|
|
911
|
-
const stderr = [];
|
|
912
|
-
let captured = 0;
|
|
913
|
-
const sink = (target, type) => ({ async write(chunk) {
|
|
914
|
-
childSignal.throwIfAborted();
|
|
915
|
-
if (chunk.length > outputLimit - captured) throw new RangeError("Python shell output limit exceeded");
|
|
916
|
-
captured += chunk.length;
|
|
917
|
-
if (publish) {
|
|
918
|
-
for (let offset = 0; offset < chunk.length; offset += chunkBytes) {
|
|
919
|
-
childSignal.throwIfAborted();
|
|
920
|
-
await publish({ type, data: Array.from(chunk.subarray(offset, offset + chunkBytes)) });
|
|
921
|
-
}
|
|
922
|
-
} else for (const byte of chunk) target.push(byte);
|
|
923
|
-
} });
|
|
924
|
-
running++;
|
|
925
|
-
dispatching.set(scope, (dispatching.get(scope) ?? 0) + 1);
|
|
926
|
-
try {
|
|
927
|
-
const args = argv;
|
|
928
|
-
const result = await invoke(args ? args[0] : "sh", args ? args.slice(1) : ["-c", script], {
|
|
929
|
-
signal: childSignal,
|
|
930
|
-
cwd: request.cwd ?? context.cwd,
|
|
931
|
-
env,
|
|
932
|
-
replaceEnv: true,
|
|
933
|
-
stdin: childInput,
|
|
934
|
-
stdout: sink(stdout, "stdout"),
|
|
935
|
-
stderr: sink(stderr, "stderr"),
|
|
936
|
-
externalInvocation: true
|
|
937
|
-
});
|
|
938
|
-
childSignal.throwIfAborted();
|
|
939
|
-
return { stdout, stderr, exitCode: result.exitCode };
|
|
940
|
-
} catch (error) {
|
|
941
|
-
if (!publish && !signal.aborted && childSignal.aborted && childSignal.reason?.name === "TimeoutError") {
|
|
942
|
-
return { error: { code: "timeout", message: "Python shell deadline exceeded", stdout, stderr } };
|
|
943
|
-
}
|
|
944
|
-
throw error;
|
|
945
|
-
} finally {
|
|
946
|
-
running--;
|
|
947
|
-
const count = dispatching.get(scope) - 1;
|
|
948
|
-
if (count) dispatching.set(scope, count);
|
|
949
|
-
else dispatching.delete(scope);
|
|
950
|
-
}
|
|
951
|
-
};
|
|
952
|
-
return { call: execute, async *stream(value, context2) {
|
|
953
|
-
const controller = new AbortController();
|
|
954
|
-
const signal = AbortSignal.any([context2.signal, controller.signal]);
|
|
955
|
-
let queued;
|
|
956
|
-
let wake;
|
|
957
|
-
let ended = false;
|
|
958
|
-
let failure2;
|
|
959
|
-
let failed = false;
|
|
960
|
-
let publishing = Promise.resolve();
|
|
961
|
-
const publish = async (event) => {
|
|
962
|
-
const next = publishing.then(async () => {
|
|
963
|
-
signal.throwIfAborted();
|
|
964
|
-
await new Promise((resolve, reject) => {
|
|
965
|
-
const abort = () => {
|
|
966
|
-
signal.removeEventListener("abort", abort);
|
|
967
|
-
reject(signal.reason);
|
|
968
|
-
wake?.();
|
|
969
|
-
};
|
|
970
|
-
signal.addEventListener("abort", abort, { once: true });
|
|
971
|
-
queued = { event, accept() {
|
|
972
|
-
signal.removeEventListener("abort", abort);
|
|
973
|
-
resolve();
|
|
974
|
-
} };
|
|
975
|
-
wake?.();
|
|
976
|
-
if (signal.aborted) abort();
|
|
977
|
-
});
|
|
978
|
-
});
|
|
979
|
-
publishing = next.catch(() => void 0);
|
|
980
|
-
await next;
|
|
981
|
-
};
|
|
982
|
-
const child = execute(value, { signal }, publish).then(async (result) => {
|
|
983
|
-
await publish({ type: "exit", returncode: result.exitCode });
|
|
984
|
-
}).catch((error) => {
|
|
985
|
-
failure2 = error;
|
|
986
|
-
failed = true;
|
|
987
|
-
}).finally(() => {
|
|
988
|
-
ended = true;
|
|
989
|
-
wake?.();
|
|
990
|
-
});
|
|
991
|
-
try {
|
|
992
|
-
while (true) {
|
|
993
|
-
signal.throwIfAborted();
|
|
994
|
-
if (queued) {
|
|
995
|
-
const item = queued;
|
|
996
|
-
queued = void 0;
|
|
997
|
-
yield item.event;
|
|
998
|
-
item.accept();
|
|
999
|
-
} else if (ended) {
|
|
1000
|
-
if (failed) throw failure2;
|
|
1001
|
-
break;
|
|
1002
|
-
} else await new Promise((resolve) => {
|
|
1003
|
-
wake = resolve;
|
|
1004
|
-
});
|
|
1005
|
-
}
|
|
1006
|
-
} finally {
|
|
1007
|
-
controller.abort(new Error("Python shell stream closed"));
|
|
1008
|
-
await child;
|
|
1009
|
-
}
|
|
1010
|
-
} };
|
|
1011
|
-
}
|
|
1012
|
-
|
|
1013
|
-
// packages/safe-bash/src/commands/python/llm-capability.ts
|
|
1014
|
-
init_platform();
|
|
1015
|
-
function record(value) {
|
|
1016
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) throw new TypeError("Expected a structured Python LLM request");
|
|
1017
|
-
return value;
|
|
1018
|
-
}
|
|
1019
|
-
function createPythonLlmCapability(context, service, options = {}) {
|
|
1020
|
-
const chunkBytes = options.maxStreamChunkBytes ?? 16384;
|
|
1021
|
-
if (!Number.isSafeInteger(chunkBytes) || chunkBytes < 1) throw new RangeError("Invalid Python LLM stream chunk limit");
|
|
1022
|
-
const prepare = async (value, signal) => {
|
|
1023
|
-
const payload = record(value);
|
|
1024
|
-
for (const key of ["template", "conversation"]) {
|
|
1025
|
-
if (payload[key] !== void 0 && payload[key] !== null) throw new TypeError(`The shared LLM service does not yet support persisted ${key}`);
|
|
1026
|
-
}
|
|
1027
|
-
if (payload.parameters !== void 0 && Object.keys(record(payload.parameters)).length) throw new TypeError("Template parameters require shared-service template support");
|
|
1028
|
-
const timeout = payload.timeout;
|
|
1029
|
-
if (timeout !== void 0 && timeout !== null) {
|
|
1030
|
-
if (typeof timeout !== "number" || !Number.isFinite(timeout) || timeout <= 0 || timeout * 1e3 > 2147483647) throw new RangeError("Invalid LLM timeout");
|
|
1031
|
-
signal = AbortSignal.any([signal, AbortSignal.timeout(Math.max(1, Math.ceil(timeout * 1e3)))]);
|
|
1032
|
-
}
|
|
1033
|
-
signal.throwIfAborted();
|
|
1034
|
-
const limit = payload.max_response_bytes;
|
|
1035
|
-
if (limit !== void 0 && limit !== null && (typeof limit !== "number" || !Number.isSafeInteger(limit) || limit < 0)) throw new RangeError("Invalid LLM response limit");
|
|
1036
|
-
const attachments = [];
|
|
1037
|
-
const inputLimit = context.inputBudget?.maxBytes ?? Infinity;
|
|
1038
|
-
let inputBytes = 0;
|
|
1039
|
-
const checkInput = (size) => {
|
|
1040
|
-
if (size > inputLimit - inputBytes) throw new RangeError("LLM input byte limit exceeded");
|
|
1041
|
-
context.inputBudget?.check(inputBytes + size);
|
|
1042
|
-
};
|
|
1043
|
-
const inputs = payload.attachments ?? [];
|
|
1044
|
-
if (!Array.isArray(inputs)) throw new TypeError("Expected canonical LLM attachments");
|
|
1045
|
-
for (const value2 of inputs) {
|
|
1046
|
-
const attachment = record(value2);
|
|
1047
|
-
if (typeof attachment.path !== "string" || !attachment.path || attachment.path.includes("\0") || attachment.path.includes("://")) throw new TypeError("Attachment requires a canonical filesystem path");
|
|
1048
|
-
if (attachment.mimeType !== void 0 && typeof attachment.mimeType !== "string") throw new TypeError("Invalid attachment MIME type");
|
|
1049
|
-
const path = (0, path_exports.pathOf)(context, attachment.path);
|
|
1050
|
-
const stat = await context.fs.stat(path, { signal });
|
|
1051
|
-
checkInput(stat.size);
|
|
1052
|
-
const bytes = await context.fs.readFile(path, { signal, ...inputLimit === Infinity ? {} : { maxBytes: inputLimit - inputBytes } });
|
|
1053
|
-
signal.throwIfAborted();
|
|
1054
|
-
checkInput(bytes.byteLength);
|
|
1055
|
-
inputBytes += bytes.byteLength;
|
|
1056
|
-
attachments.push({ mimeType: attachment.mimeType ?? sniffMimeType(path, bytes), bytes: new Uint8Array(bytes) });
|
|
1057
|
-
}
|
|
1058
|
-
return {
|
|
1059
|
-
prompt: payload.prompt === void 0 ? "" : payload.prompt,
|
|
1060
|
-
...payload.model == null ? {} : { model: payload.model },
|
|
1061
|
-
...payload.system === void 0 ? {} : { system: payload.system },
|
|
1062
|
-
...payload.messages === void 0 ? {} : { messages: payload.messages },
|
|
1063
|
-
...payload.schema === void 0 ? {} : { schema: record(payload.schema) },
|
|
1064
|
-
options: record(payload.options ?? {}),
|
|
1065
|
-
attachments,
|
|
1066
|
-
signal,
|
|
1067
|
-
...limit == null ? {} : { maxOutputBytes: limit }
|
|
1068
|
-
};
|
|
1069
|
-
};
|
|
1070
|
-
const events = async function* (value, { signal }) {
|
|
1071
|
-
const request = await prepare(value, signal);
|
|
1072
|
-
for await (const event of service.stream(request)) {
|
|
1073
|
-
if (event.type === "bytes") {
|
|
1074
|
-
for (let offset = 0; offset < event.data.length; offset += chunkBytes) {
|
|
1075
|
-
request.signal.throwIfAborted();
|
|
1076
|
-
yield { type: "bytes", data: Array.from(event.data.subarray(offset, offset + chunkBytes)) };
|
|
1077
|
-
}
|
|
1078
|
-
} else yield event;
|
|
1079
|
-
}
|
|
1080
|
-
};
|
|
1081
|
-
return {
|
|
1082
|
-
async call(value, { signal }) {
|
|
1083
|
-
signal.throwIfAborted();
|
|
1084
|
-
const operation = record(value);
|
|
1085
|
-
const payload = record(operation.payload ?? {});
|
|
1086
|
-
if (operation.operation === "models") return service.models.map(({ provider, model }) => ({
|
|
1087
|
-
id: model.id,
|
|
1088
|
-
aliases: [...model.aliases ?? []],
|
|
1089
|
-
capabilities: [...model.capabilities ?? []],
|
|
1090
|
-
metadata: { provider: provider.name, attachmentTypes: [...model.attachmentTypes ?? []], outputType: model.outputType ?? "text/plain" }
|
|
1091
|
-
}));
|
|
1092
|
-
if (operation.operation === "embed") {
|
|
1093
|
-
return await service.embed({
|
|
1094
|
-
...payload.model == null ? {} : { model: payload.model },
|
|
1095
|
-
inputs: payload.inputs,
|
|
1096
|
-
options: record(payload.options ?? {}),
|
|
1097
|
-
signal
|
|
1098
|
-
});
|
|
1099
|
-
}
|
|
1100
|
-
if (operation.operation !== "complete") throw new TypeError("Unsupported Python LLM operation");
|
|
1101
|
-
const text2 = [], data = [];
|
|
1102
|
-
let response = {};
|
|
1103
|
-
for await (const value2 of events(payload, { signal })) {
|
|
1104
|
-
const event = record(value2);
|
|
1105
|
-
if (event.type === "text") text2.push(event.text);
|
|
1106
|
-
else if (event.type === "bytes") for (const byte of event.data) data.push(byte);
|
|
1107
|
-
else if (event.type === "response") response = { ...record(event.response) };
|
|
1108
|
-
}
|
|
1109
|
-
return { ...response, text: text2.join(""), data };
|
|
1110
|
-
},
|
|
1111
|
-
stream: events
|
|
1112
|
-
};
|
|
1113
|
-
}
|
|
1114
|
-
|
|
1115
|
-
// packages/safe-bash/src/commands/python/executor-pool.ts
|
|
1116
|
-
init_platform();
|
|
1117
|
-
function createPythonExecutorPool(options) {
|
|
1118
|
-
if (typeof options?.createExecutor !== "function") throw new TypeError("Python executor pool requires a factory");
|
|
1119
|
-
const factory = options.createExecutor;
|
|
1120
|
-
const capacity = options.maxConcurrentExecutors ?? Infinity;
|
|
1121
|
-
if (options.maxConcurrentExecutors !== void 0 && (!Number.isSafeInteger(capacity) || capacity < 1)) throw new RangeError("Invalid Python executor pool capacity");
|
|
1122
|
-
const entries = /* @__PURE__ */ new Set();
|
|
1123
|
-
let closed = false;
|
|
1124
|
-
let disposal;
|
|
1125
|
-
return {
|
|
1126
|
-
createExecutor() {
|
|
1127
|
-
if (closed) throw new PythonFailure("executor-unavailable");
|
|
1128
|
-
if (entries.size >= capacity) throw new PythonFailure("capacity");
|
|
1129
|
-
let acquired;
|
|
1130
|
-
const acquisition = new Promise((resolve) => {
|
|
1131
|
-
acquired = resolve;
|
|
1132
|
-
});
|
|
1133
|
-
let retirement;
|
|
1134
|
-
let execution;
|
|
1135
|
-
let started = false;
|
|
1136
|
-
const entry = {
|
|
1137
|
-
retire() {
|
|
1138
|
-
retirement ??= Promise.resolve().then(async () => {
|
|
1139
|
-
const executor2 = await acquisition;
|
|
1140
|
-
if (executor2 !== void 0) {
|
|
1141
|
-
const results = await Promise.allSettled([Promise.resolve().then(() => {
|
|
1142
|
-
const terminate = executor2?.terminate;
|
|
1143
|
-
if (typeof terminate !== "function") throw new PythonFailure("cleanup");
|
|
1144
|
-
return terminate.call(executor2);
|
|
1145
|
-
}), execution]);
|
|
1146
|
-
const failed = results.find((result) => result.status === "rejected");
|
|
1147
|
-
if (failed?.status === "rejected") throw failed.reason;
|
|
1148
|
-
}
|
|
1149
|
-
entries.delete(entry);
|
|
1150
|
-
});
|
|
1151
|
-
return retirement;
|
|
1152
|
-
}
|
|
1153
|
-
};
|
|
1154
|
-
entries.add(entry);
|
|
1155
|
-
let executor;
|
|
1156
|
-
try {
|
|
1157
|
-
executor = factory();
|
|
1158
|
-
} catch (error) {
|
|
1159
|
-
acquired(void 0);
|
|
1160
|
-
entries.delete(entry);
|
|
1161
|
-
throw error;
|
|
1162
|
-
}
|
|
1163
|
-
acquired(executor);
|
|
1164
|
-
try {
|
|
1165
|
-
if (closed || !executor || typeof executor.run !== "function" || typeof executor.terminate !== "function") throw new PythonFailure("executor-unavailable");
|
|
1166
|
-
} catch (error) {
|
|
1167
|
-
void entry.retire().catch(() => {
|
|
1168
|
-
});
|
|
1169
|
-
throw error;
|
|
1170
|
-
}
|
|
1171
|
-
return {
|
|
1172
|
-
async run(start) {
|
|
1173
|
-
if (started || closed || retirement) throw new PythonFailure("executor-unavailable");
|
|
1174
|
-
started = true;
|
|
1175
|
-
const running = Promise.resolve().then(() => {
|
|
1176
|
-
if (closed || retirement) throw new PythonFailure("executor-unavailable");
|
|
1177
|
-
return executor.run(start);
|
|
1178
|
-
});
|
|
1179
|
-
execution = running.then(() => {
|
|
1180
|
-
}, () => {
|
|
1181
|
-
});
|
|
1182
|
-
return running;
|
|
1183
|
-
},
|
|
1184
|
-
terminate: entry.retire
|
|
1185
|
-
};
|
|
1186
|
-
},
|
|
1187
|
-
inspect() {
|
|
1188
|
-
return { active: entries.size, capacity, closed };
|
|
1189
|
-
},
|
|
1190
|
-
dispose() {
|
|
1191
|
-
closed = true;
|
|
1192
|
-
disposal ??= Promise.allSettled([...entries].map((entry) => entry.retire())).then((results) => {
|
|
1193
|
-
const failed = results.find((result) => result.status === "rejected");
|
|
1194
|
-
if (failed?.status === "rejected") throw failed.reason;
|
|
1195
|
-
});
|
|
1196
|
-
return disposal;
|
|
1197
|
-
}
|
|
1198
|
-
};
|
|
1199
|
-
}
|
|
1200
|
-
|
|
1201
|
-
// packages/safe-bash/src/commands/python/jspi.ts
|
|
1202
|
-
init_platform();
|
|
1203
|
-
import { createPythonNativeSyscalls } from "@poe-platform/safe-fs/core";
|
|
1204
|
-
|
|
1205
|
-
// packages/safe-bash/src/commands/python/llm-module.ts
|
|
1206
|
-
init_platform();
|
|
1207
|
-
var pythonLlmModule = String.raw`
|
|
1208
|
-
"""Async Python workflows for the invocation-owned JavaScript LLM service."""
|
|
1209
|
-
from __future__ import annotations
|
|
1210
|
-
|
|
1211
|
-
import asyncio
|
|
1212
|
-
import copy
|
|
1213
|
-
import inspect
|
|
1214
|
-
import math
|
|
1215
|
-
from dataclasses import dataclass, field, replace
|
|
1216
|
-
from typing import Any, AsyncIterator, Callable, Mapping, Optional, Protocol, Sequence, Union
|
|
1217
|
-
|
|
1218
|
-
Option = Union[str, int, float, bool, None]
|
|
1219
|
-
|
|
1220
|
-
class LlmError(Exception):
|
|
1221
|
-
def __init__(self, code: str, message: str):
|
|
1222
|
-
super().__init__(message)
|
|
1223
|
-
self.code = code
|
|
1224
|
-
|
|
1225
|
-
class LimitError(LlmError):
|
|
1226
|
-
def __init__(self, message="LLM response byte limit exceeded"):
|
|
1227
|
-
super().__init__("limit", message)
|
|
1228
|
-
|
|
1229
|
-
class CapabilityError(LlmError):
|
|
1230
|
-
def __init__(self, message="This Python invocation has no LLM capability"):
|
|
1231
|
-
super().__init__("capability", message)
|
|
1232
|
-
|
|
1233
|
-
class Bridge(Protocol):
|
|
1234
|
-
async def call(self, operation: str, payload: Mapping[str, Any]) -> Any: ...
|
|
1235
|
-
def stream(self, payload: Mapping[str, Any]) -> AsyncIterator[Mapping[str, Any]]: ...
|
|
1236
|
-
|
|
1237
|
-
@dataclass(frozen=True)
|
|
1238
|
-
class Model:
|
|
1239
|
-
id: str
|
|
1240
|
-
aliases: tuple[str, ...] = ()
|
|
1241
|
-
capabilities: tuple[str, ...] = ()
|
|
1242
|
-
metadata: Mapping[str, Any] = field(default_factory=dict)
|
|
1243
|
-
|
|
1244
|
-
@dataclass(frozen=True)
|
|
1245
|
-
class Message:
|
|
1246
|
-
role: str
|
|
1247
|
-
content: str
|
|
1248
|
-
|
|
1249
|
-
@dataclass(frozen=True)
|
|
1250
|
-
class Attachment:
|
|
1251
|
-
path: str
|
|
1252
|
-
mime_type: Optional[str] = None
|
|
1253
|
-
|
|
1254
|
-
def payload(self):
|
|
1255
|
-
if not isinstance(self.path, str) or not self.path or "\0" in self.path:
|
|
1256
|
-
raise ValueError("Attachment requires an agent filesystem path")
|
|
1257
|
-
result = {"path": self.path}
|
|
1258
|
-
if self.mime_type is not None:
|
|
1259
|
-
result["mimeType"] = self.mime_type
|
|
1260
|
-
return result
|
|
1261
|
-
|
|
1262
|
-
def _options(values):
|
|
1263
|
-
result = dict(values)
|
|
1264
|
-
for key, value in result.items():
|
|
1265
|
-
if not isinstance(key, str) or not key:
|
|
1266
|
-
raise TypeError("Option names must be nonempty strings")
|
|
1267
|
-
if value is not None and type(value) not in (str, int, float, bool):
|
|
1268
|
-
raise TypeError("Model options must be strings, numbers, booleans or None")
|
|
1269
|
-
if type(value) is int and abs(value) > 9007199254740991:
|
|
1270
|
-
raise ValueError("Integer model options must fit the JavaScript safe integer range")
|
|
1271
|
-
if type(value) is float and not math.isfinite(value):
|
|
1272
|
-
raise ValueError("Model options must be finite")
|
|
1273
|
-
return result
|
|
1274
|
-
|
|
1275
|
-
@dataclass(frozen=True)
|
|
1276
|
-
class Request:
|
|
1277
|
-
prompt: str = ""
|
|
1278
|
-
model: Optional[str] = None
|
|
1279
|
-
system: Optional[str] = None
|
|
1280
|
-
messages: Sequence[Message] = ()
|
|
1281
|
-
attachments: Sequence[Attachment] = ()
|
|
1282
|
-
options: Mapping[str, Option] = field(default_factory=dict)
|
|
1283
|
-
schema: Optional[Mapping[str, Any]] = None
|
|
1284
|
-
template: Optional[str] = None
|
|
1285
|
-
parameters: Mapping[str, Any] = field(default_factory=dict)
|
|
1286
|
-
conversation: Optional[str] = None
|
|
1287
|
-
|
|
1288
|
-
def payload(self):
|
|
1289
|
-
if not isinstance(self.prompt, str):
|
|
1290
|
-
raise TypeError("Prompt must be a string")
|
|
1291
|
-
if self.model is not None and (not isinstance(self.model, str) or not self.model):
|
|
1292
|
-
raise TypeError("Model must be a nonempty identity or alias")
|
|
1293
|
-
if self.system is not None and not isinstance(self.system, str):
|
|
1294
|
-
raise TypeError("System prompt must be a string")
|
|
1295
|
-
messages = []
|
|
1296
|
-
for message in self.messages:
|
|
1297
|
-
if not isinstance(message, Message) or not isinstance(message.role, str) or not isinstance(message.content, str):
|
|
1298
|
-
raise TypeError("Messages must contain typed Message values")
|
|
1299
|
-
messages.append({"role": message.role, "content": message.content})
|
|
1300
|
-
result = {"prompt": self.prompt, "messages": messages,
|
|
1301
|
-
"attachments": [attachment.payload() for attachment in self.attachments],
|
|
1302
|
-
"options": _options(self.options)}
|
|
1303
|
-
for key in ("model", "system", "schema", "template", "conversation"):
|
|
1304
|
-
value = getattr(self, key)
|
|
1305
|
-
if value is not None:
|
|
1306
|
-
result[key] = copy.deepcopy(value)
|
|
1307
|
-
if self.parameters:
|
|
1308
|
-
result["parameters"] = copy.deepcopy(dict(self.parameters))
|
|
1309
|
-
return result
|
|
1310
|
-
|
|
1311
|
-
def _bytes(value):
|
|
1312
|
-
if value is None:
|
|
1313
|
-
return b""
|
|
1314
|
-
if isinstance(value, (bytes, bytearray, memoryview)):
|
|
1315
|
-
return bytes(value)
|
|
1316
|
-
if isinstance(value, (tuple, list)) and all(type(item) is int and 0 <= item <= 255 for item in value):
|
|
1317
|
-
return bytes(value)
|
|
1318
|
-
raise LlmError("protocol", "Invalid binary LLM result")
|
|
1319
|
-
|
|
1320
|
-
@dataclass(frozen=True)
|
|
1321
|
-
class Response:
|
|
1322
|
-
model: str
|
|
1323
|
-
text: str = ""
|
|
1324
|
-
data: bytes = b""
|
|
1325
|
-
usage: Mapping[str, Any] = field(default_factory=dict)
|
|
1326
|
-
conversation: Optional[str] = None
|
|
1327
|
-
metadata: Mapping[str, Any] = field(default_factory=dict)
|
|
1328
|
-
|
|
1329
|
-
@classmethod
|
|
1330
|
-
def from_payload(cls, value):
|
|
1331
|
-
if not isinstance(value, Mapping) or not isinstance(value.get("model"), str) or not isinstance(value.get("text", ""), str):
|
|
1332
|
-
raise LlmError("protocol", "Invalid complete LLM response")
|
|
1333
|
-
return cls(model=value["model"], text=value.get("text", ""), data=_bytes(value.get("data")),
|
|
1334
|
-
usage=copy.deepcopy(value.get("usage", {})), conversation=value.get("conversation"),
|
|
1335
|
-
metadata=copy.deepcopy(value.get("metadata", {})))
|
|
1336
|
-
|
|
1337
|
-
def json(self):
|
|
1338
|
-
import json
|
|
1339
|
-
return json.loads(self.text)
|
|
1340
|
-
|
|
1341
|
-
@dataclass(frozen=True)
|
|
1342
|
-
class Embeddings:
|
|
1343
|
-
model: str
|
|
1344
|
-
vectors: tuple[tuple[float, ...], ...]
|
|
1345
|
-
usage: Mapping[str, Any] = field(default_factory=dict)
|
|
1346
|
-
metadata: Mapping[str, Any] = field(default_factory=dict)
|
|
1347
|
-
|
|
1348
|
-
@dataclass(frozen=True)
|
|
1349
|
-
class Event:
|
|
1350
|
-
type: str
|
|
1351
|
-
text: str = ""
|
|
1352
|
-
data: bytes = b""
|
|
1353
|
-
response: Optional[Response] = None
|
|
1354
|
-
metadata: Mapping[str, Any] = field(default_factory=dict)
|
|
1355
|
-
|
|
1356
|
-
async def _transform(function, value):
|
|
1357
|
-
result = function(value) if function else value
|
|
1358
|
-
return await result if inspect.isawaitable(result) else result
|
|
1359
|
-
|
|
1360
|
-
_DEFAULT_LIMIT = object()
|
|
1361
|
-
|
|
1362
|
-
class Stream:
|
|
1363
|
-
"""Use async with to close the host iterator after an early break."""
|
|
1364
|
-
def __init__(self, client, request, timeout=None, max_response_bytes=_DEFAULT_LIMIT):
|
|
1365
|
-
self._client = client
|
|
1366
|
-
self._request = request
|
|
1367
|
-
self._iterator = None
|
|
1368
|
-
self._closed = False
|
|
1369
|
-
self._close_task = None
|
|
1370
|
-
self._pending = None
|
|
1371
|
-
self._lock = asyncio.Lock()
|
|
1372
|
-
self._bytes = 0
|
|
1373
|
-
self._limit = client._limit if max_response_bytes is _DEFAULT_LIMIT else _limit(max_response_bytes)
|
|
1374
|
-
self._timeout = client._timeout if timeout is None else _timeout(timeout)
|
|
1375
|
-
self._deadline = None
|
|
1376
|
-
self.response = None
|
|
1377
|
-
|
|
1378
|
-
async def __aenter__(self):
|
|
1379
|
-
self._client._check_open()
|
|
1380
|
-
return self
|
|
1381
|
-
|
|
1382
|
-
async def __aexit__(self, *exception):
|
|
1383
|
-
await self.aclose()
|
|
1384
|
-
|
|
1385
|
-
def __aiter__(self):
|
|
1386
|
-
return self
|
|
1387
|
-
|
|
1388
|
-
async def __anext__(self):
|
|
1389
|
-
async with self._lock:
|
|
1390
|
-
if self._closed:
|
|
1391
|
-
raise StopAsyncIteration
|
|
1392
|
-
self._client._check_open()
|
|
1393
|
-
try:
|
|
1394
|
-
if self._deadline is None and self._timeout is not None:
|
|
1395
|
-
self._deadline = asyncio.get_running_loop().time() + self._timeout
|
|
1396
|
-
async def read():
|
|
1397
|
-
if self._iterator is None:
|
|
1398
|
-
payload = await self._client._payload(self._request)
|
|
1399
|
-
payload["timeout"] = self._timeout
|
|
1400
|
-
payload["max_response_bytes"] = self._limit
|
|
1401
|
-
self._iterator = self._client._bridge.stream(payload).__aiter__()
|
|
1402
|
-
return await self._iterator.__anext__()
|
|
1403
|
-
remaining = None if self._deadline is None else max(0, self._deadline - asyncio.get_running_loop().time())
|
|
1404
|
-
self._pending = asyncio.ensure_future(read())
|
|
1405
|
-
value = await asyncio.wait_for(self._pending, remaining)
|
|
1406
|
-
kind = value.get("type")
|
|
1407
|
-
if kind == "text":
|
|
1408
|
-
if not isinstance(value.get("text"), str):
|
|
1409
|
-
raise LlmError("protocol", "Invalid text stream event")
|
|
1410
|
-
event = Event(kind, text=value["text"])
|
|
1411
|
-
elif kind == "bytes":
|
|
1412
|
-
event = Event(kind, data=_bytes(value.get("data")))
|
|
1413
|
-
elif kind == "response":
|
|
1414
|
-
self.response = Response.from_payload(value["response"])
|
|
1415
|
-
_check_size(self.response, self._limit)
|
|
1416
|
-
event = Event(kind, response=self.response)
|
|
1417
|
-
else:
|
|
1418
|
-
raise LlmError("protocol", "Unknown stream event")
|
|
1419
|
-
self._bytes += len(event.text.encode("utf-8")) + len(event.data)
|
|
1420
|
-
if self._limit is not None and self._bytes > self._limit:
|
|
1421
|
-
raise LimitError()
|
|
1422
|
-
return event
|
|
1423
|
-
except BaseException:
|
|
1424
|
-
await self.aclose()
|
|
1425
|
-
raise
|
|
1426
|
-
|
|
1427
|
-
async def aclose(self):
|
|
1428
|
-
if self._close_task is None:
|
|
1429
|
-
self._closed = True
|
|
1430
|
-
async def close():
|
|
1431
|
-
try:
|
|
1432
|
-
if self._pending is not None and not self._pending.done():
|
|
1433
|
-
self._pending.cancel()
|
|
1434
|
-
await asyncio.gather(self._pending, return_exceptions=True)
|
|
1435
|
-
if self._iterator is not None:
|
|
1436
|
-
await self._iterator.aclose()
|
|
1437
|
-
finally:
|
|
1438
|
-
self._client._streams.discard(self)
|
|
1439
|
-
self._close_task = asyncio.ensure_future(close())
|
|
1440
|
-
await asyncio.shield(self._close_task)
|
|
1441
|
-
|
|
1442
|
-
def _limit(value):
|
|
1443
|
-
if value is None or type(value) is float and value == math.inf:
|
|
1444
|
-
return None
|
|
1445
|
-
if type(value) is not int or value < 1:
|
|
1446
|
-
raise ValueError("max_response_bytes must be a positive integer")
|
|
1447
|
-
return value
|
|
1448
|
-
|
|
1449
|
-
def _timeout(value):
|
|
1450
|
-
if value is not None and (type(value) not in (int, float) or not math.isfinite(value) or value <= 0):
|
|
1451
|
-
raise ValueError("timeout must be a positive number of seconds")
|
|
1452
|
-
return value
|
|
1453
|
-
|
|
1454
|
-
def _check_size(response, limit):
|
|
1455
|
-
if limit is not None and len(response.text.encode("utf-8")) + len(response.data) > limit:
|
|
1456
|
-
raise LimitError()
|
|
1457
|
-
|
|
1458
|
-
class Client:
|
|
1459
|
-
def __init__(self, *, bridge=None, model=None, options=None, system=None,
|
|
1460
|
-
request_transform=None, response_transform=None,
|
|
1461
|
-
max_response_bytes=None, timeout=None):
|
|
1462
|
-
if bridge is None:
|
|
1463
|
-
try:
|
|
1464
|
-
from _poe_llm_capability import bridge
|
|
1465
|
-
except ImportError as error:
|
|
1466
|
-
raise CapabilityError() from error
|
|
1467
|
-
self._bridge = bridge
|
|
1468
|
-
self._defaults = {"model": model, "system": system, "options": _options(options or {})}
|
|
1469
|
-
self._request_transform = request_transform
|
|
1470
|
-
self._response_transform = response_transform
|
|
1471
|
-
self._limit = _limit(max_response_bytes)
|
|
1472
|
-
self._timeout = _timeout(timeout)
|
|
1473
|
-
self._streams = set()
|
|
1474
|
-
self._tasks = set()
|
|
1475
|
-
self._closed = False
|
|
1476
|
-
self._close_task = None
|
|
1477
|
-
|
|
1478
|
-
def _check_open(self):
|
|
1479
|
-
if self._closed:
|
|
1480
|
-
raise CapabilityError("LLM client has been closed")
|
|
1481
|
-
|
|
1482
|
-
async def __aenter__(self):
|
|
1483
|
-
self._check_open()
|
|
1484
|
-
return self
|
|
1485
|
-
|
|
1486
|
-
async def __aexit__(self, *exception):
|
|
1487
|
-
await self.aclose()
|
|
1488
|
-
|
|
1489
|
-
async def aclose(self):
|
|
1490
|
-
if self._close_task is None:
|
|
1491
|
-
self._closed = True
|
|
1492
|
-
current = asyncio.current_task()
|
|
1493
|
-
async def close():
|
|
1494
|
-
pending = [task for task in self._tasks if task is not current]
|
|
1495
|
-
for task in pending:
|
|
1496
|
-
task.cancel()
|
|
1497
|
-
await asyncio.gather(*pending, return_exceptions=True)
|
|
1498
|
-
results = await asyncio.gather(*(stream.aclose() for stream in tuple(self._streams)), return_exceptions=True)
|
|
1499
|
-
for result in results:
|
|
1500
|
-
if isinstance(result, BaseException):
|
|
1501
|
-
raise result
|
|
1502
|
-
self._close_task = asyncio.ensure_future(close())
|
|
1503
|
-
await asyncio.shield(self._close_task)
|
|
1504
|
-
|
|
1505
|
-
def with_defaults(self, **defaults):
|
|
1506
|
-
self._check_open()
|
|
1507
|
-
values = dict(self._defaults)
|
|
1508
|
-
values.update(defaults)
|
|
1509
|
-
values["options"] = {**self._defaults["options"], **defaults.get("options", {})}
|
|
1510
|
-
return Client(bridge=self._bridge, request_transform=self._request_transform,
|
|
1511
|
-
response_transform=self._response_transform, max_response_bytes=self._limit,
|
|
1512
|
-
timeout=self._timeout, **values)
|
|
1513
|
-
|
|
1514
|
-
def _request(self, prompt, values):
|
|
1515
|
-
if isinstance(prompt, Request):
|
|
1516
|
-
if values:
|
|
1517
|
-
raise TypeError("A Request cannot be combined with keyword fields")
|
|
1518
|
-
return replace(prompt, model=prompt.model if prompt.model is not None else self._defaults["model"],
|
|
1519
|
-
system=prompt.system if prompt.system is not None else self._defaults["system"],
|
|
1520
|
-
options={**self._defaults["options"], **prompt.options})
|
|
1521
|
-
fields = {**self._defaults, **values, "prompt": prompt}
|
|
1522
|
-
fields["options"] = {**self._defaults["options"], **values.get("options", {})}
|
|
1523
|
-
return Request(**fields)
|
|
1524
|
-
|
|
1525
|
-
async def _payload(self, request):
|
|
1526
|
-
self._check_open()
|
|
1527
|
-
transformed = await _transform(self._request_transform, request)
|
|
1528
|
-
self._check_open()
|
|
1529
|
-
if not isinstance(transformed, Request):
|
|
1530
|
-
raise TypeError("Request transform must return a Request")
|
|
1531
|
-
return transformed.payload()
|
|
1532
|
-
|
|
1533
|
-
async def _run(self, operation, timeout):
|
|
1534
|
-
self._check_open()
|
|
1535
|
-
task = asyncio.ensure_future(operation())
|
|
1536
|
-
self._tasks.add(task)
|
|
1537
|
-
try:
|
|
1538
|
-
return await asyncio.wait_for(task, timeout)
|
|
1539
|
-
finally:
|
|
1540
|
-
self._tasks.discard(task)
|
|
1541
|
-
|
|
1542
|
-
async def models(self):
|
|
1543
|
-
values = await self._run(lambda: self._bridge.call("models", {}), self._timeout)
|
|
1544
|
-
return tuple(Model(id=value["id"], aliases=tuple(value.get("aliases", ())),
|
|
1545
|
-
capabilities=tuple(value.get("capabilities", ())),
|
|
1546
|
-
metadata=copy.deepcopy(value.get("metadata", {}))) for value in values)
|
|
1547
|
-
|
|
1548
|
-
async def complete(self, prompt="", *, timeout=None, max_response_bytes=_DEFAULT_LIMIT, **values):
|
|
1549
|
-
limit = self._limit if max_response_bytes is _DEFAULT_LIMIT else _limit(max_response_bytes)
|
|
1550
|
-
timeout = self._timeout if timeout is None else _timeout(timeout)
|
|
1551
|
-
request = self._request(prompt, values)
|
|
1552
|
-
async def execute():
|
|
1553
|
-
payload = await self._payload(request)
|
|
1554
|
-
payload["timeout"] = timeout
|
|
1555
|
-
payload["max_response_bytes"] = limit
|
|
1556
|
-
response = Response.from_payload(await self._bridge.call("complete", payload))
|
|
1557
|
-
_check_size(response, limit)
|
|
1558
|
-
return await _transform(self._response_transform, response)
|
|
1559
|
-
return await self._run(execute, timeout)
|
|
1560
|
-
|
|
1561
|
-
def stream(self, prompt="", *, timeout=None, max_response_bytes=_DEFAULT_LIMIT, **values):
|
|
1562
|
-
self._check_open()
|
|
1563
|
-
stream = Stream(self, self._request(prompt, values), timeout, max_response_bytes)
|
|
1564
|
-
self._streams.add(stream)
|
|
1565
|
-
return stream
|
|
1566
|
-
|
|
1567
|
-
async def embed(self, inputs, *, model=None, options=None, timeout=None):
|
|
1568
|
-
if isinstance(inputs, str):
|
|
1569
|
-
raise TypeError("Embedding inputs must be an iterable of strings")
|
|
1570
|
-
inputs = tuple(inputs)
|
|
1571
|
-
if not all(isinstance(value, str) for value in inputs):
|
|
1572
|
-
raise TypeError("Embedding inputs must be an iterable of strings")
|
|
1573
|
-
payload = {"model": model if model is not None else self._defaults["model"], "inputs": list(inputs),
|
|
1574
|
-
"options": _options({**self._defaults["options"], **(options or {})})}
|
|
1575
|
-
payload["timeout"] = self._timeout if timeout is None else _timeout(timeout)
|
|
1576
|
-
result = await self._run(lambda: self._bridge.call("embed", payload), payload["timeout"])
|
|
1577
|
-
vectors = tuple(tuple(vector) for vector in result["vectors"])
|
|
1578
|
-
if len(vectors) != len(inputs) or any(type(value) not in (int, float) or not math.isfinite(value) for vector in vectors for value in vector):
|
|
1579
|
-
raise LlmError("protocol", "Invalid embedding result")
|
|
1580
|
-
if self._limit is not None and sum(len(vector) for vector in vectors) * 8 > self._limit:
|
|
1581
|
-
raise LimitError()
|
|
1582
|
-
return Embeddings(result["model"], vectors, copy.deepcopy(result.get("usage", {})), copy.deepcopy(result.get("metadata", {})))
|
|
1583
|
-
|
|
1584
|
-
def prompt(self, function, **defaults):
|
|
1585
|
-
async def invoke(*args, **kwargs):
|
|
1586
|
-
value = function(*args, **kwargs)
|
|
1587
|
-
value = await value if inspect.isawaitable(value) else value
|
|
1588
|
-
return await self.complete(value, **defaults)
|
|
1589
|
-
return invoke
|
|
1590
|
-
|
|
1591
|
-
def conversation(self, *, system=None, messages=(), conversation=None):
|
|
1592
|
-
return Conversation(self, system=system, messages=messages, conversation=conversation)
|
|
1593
|
-
|
|
1594
|
-
class Conversation:
|
|
1595
|
-
"""Serial turn orchestration; failed calls never append partial turns."""
|
|
1596
|
-
def __init__(self, client, *, system=None, messages=(), conversation=None):
|
|
1597
|
-
self.client = client
|
|
1598
|
-
self.system = system if system is not None else client._defaults["system"]
|
|
1599
|
-
self.messages = list(messages)
|
|
1600
|
-
self.id = conversation
|
|
1601
|
-
self._lock = asyncio.Lock()
|
|
1602
|
-
|
|
1603
|
-
async def complete(self, prompt, **values):
|
|
1604
|
-
async with self._lock:
|
|
1605
|
-
response = await self.client.complete(prompt, system=self.system, messages=tuple(self.messages),
|
|
1606
|
-
conversation=self.id, **values)
|
|
1607
|
-
if not isinstance(response, Response):
|
|
1608
|
-
raise TypeError("Conversation requires a Response-returning transform")
|
|
1609
|
-
self.messages.extend((Message("user", prompt), Message("assistant", response.text)))
|
|
1610
|
-
self.id = response.conversation or self.id
|
|
1611
|
-
return response
|
|
1612
|
-
`;
|
|
1613
|
-
function installPythonLlmModule(runtime) {
|
|
1614
|
-
runtime.globals.set("_safe_llm_source", pythonLlmModule);
|
|
1615
|
-
try {
|
|
1616
|
-
runtime.runPython(`
|
|
1617
|
-
import sys as _safe_llm_sys, importlib.machinery as _safe_llm_imports
|
|
1618
|
-
class _SafeLlmLoader:
|
|
1619
|
-
def __init__(self, source, spec):
|
|
1620
|
-
self._source = source
|
|
1621
|
-
self._spec = spec
|
|
1622
|
-
def find_spec(self, fullname, path=None, target=None):
|
|
1623
|
-
if fullname == 'poe_llm':
|
|
1624
|
-
return self._spec(fullname, self, origin='poe_llm.py')
|
|
1625
|
-
def create_module(self, spec):
|
|
1626
|
-
return None
|
|
1627
|
-
def exec_module(self, module):
|
|
1628
|
-
module.__file__ = 'poe_llm.py'
|
|
1629
|
-
exec(compile(self._source, 'poe_llm.py', 'exec'), module.__dict__)
|
|
1630
|
-
_safe_llm_sys.meta_path.insert(0, _SafeLlmLoader(_safe_llm_source, _safe_llm_imports.ModuleSpec))
|
|
1631
|
-
del _SafeLlmLoader, _safe_llm_sys, _safe_llm_imports
|
|
1632
|
-
`);
|
|
1633
|
-
} finally {
|
|
1634
|
-
runtime.globals.delete("_safe_llm_source");
|
|
1635
|
-
}
|
|
1636
|
-
}
|
|
1637
|
-
|
|
1638
|
-
// packages/safe-bash/src/commands/python/shell-module.ts
|
|
1639
|
-
init_platform();
|
|
1640
|
-
var pythonShellModule = String.raw`
|
|
1641
|
-
from __future__ import annotations
|
|
1642
|
-
import asyncio
|
|
1643
|
-
import math
|
|
1644
|
-
import inspect
|
|
1645
|
-
import subprocess
|
|
1646
|
-
from dataclasses import dataclass
|
|
1647
|
-
|
|
1648
|
-
class ShellError(RuntimeError):
|
|
1649
|
-
def __init__(self, code, message):
|
|
1650
|
-
super().__init__(message)
|
|
1651
|
-
self.code = code
|
|
1652
|
-
|
|
1653
|
-
def _bridge():
|
|
1654
|
-
try:
|
|
1655
|
-
from _poe_shell_capability import bridge
|
|
1656
|
-
return bridge
|
|
1657
|
-
except ImportError as error:
|
|
1658
|
-
raise ShellError('capability', 'This Python invocation has no shell capability') from error
|
|
1659
|
-
|
|
1660
|
-
def _bytes(value):
|
|
1661
|
-
if isinstance(value, (bytes, bytearray, memoryview)):
|
|
1662
|
-
return bytes(value)
|
|
1663
|
-
if isinstance(value, list) and all(type(item) is int and 0 <= item <= 255 for item in value):
|
|
1664
|
-
return bytes(value)
|
|
1665
|
-
raise ShellError('protocol', 'Invalid shell result bytes')
|
|
1666
|
-
|
|
1667
|
-
def _payload(args, *, shell=False, input=None, cwd=None, env=None, timeout=None,
|
|
1668
|
-
max_output_bytes=None, text=False, encoding=None, errors=None):
|
|
1669
|
-
if shell:
|
|
1670
|
-
if not isinstance(args, str):
|
|
1671
|
-
raise TypeError('shell=True requires an explicit Safe Bash script string')
|
|
1672
|
-
result = {'script': args}
|
|
1673
|
-
else:
|
|
1674
|
-
if isinstance(args, (str, bytes)):
|
|
1675
|
-
raise TypeError('Literal execution requires an argv sequence')
|
|
1676
|
-
args = list(args)
|
|
1677
|
-
if not args or any(not isinstance(value, str) or '\0' in value for value in args):
|
|
1678
|
-
raise TypeError('argv must contain nonempty command and literal strings without NUL')
|
|
1679
|
-
if not args[0]:
|
|
1680
|
-
raise ValueError('Command must not be empty')
|
|
1681
|
-
result = {'argv': args}
|
|
1682
|
-
if cwd is not None and (not isinstance(cwd, str) or '\0' in cwd):
|
|
1683
|
-
raise TypeError('cwd must be a canonical filesystem path')
|
|
1684
|
-
if env is not None and (not isinstance(env, dict) or any(not isinstance(key, str) or not isinstance(value, str) or '\0' in key + value or '=' in key for key, value in env.items())):
|
|
1685
|
-
raise TypeError('env must map strings to strings')
|
|
1686
|
-
if timeout is not None and (type(timeout) not in (int, float) or not math.isfinite(timeout) or timeout <= 0):
|
|
1687
|
-
raise ValueError('timeout must be positive and finite')
|
|
1688
|
-
if type(max_output_bytes) is float and max_output_bytes == math.inf:
|
|
1689
|
-
max_output_bytes = None
|
|
1690
|
-
if max_output_bytes is not None and (type(max_output_bytes) is not int or max_output_bytes < 0):
|
|
1691
|
-
raise ValueError('max_output_bytes must be a nonnegative integer')
|
|
1692
|
-
if input is not None:
|
|
1693
|
-
if text or encoding or errors:
|
|
1694
|
-
if not isinstance(input, str):
|
|
1695
|
-
raise TypeError('Text input must be a string')
|
|
1696
|
-
input = input.encode(encoding or 'utf-8', errors or 'strict')
|
|
1697
|
-
elif not isinstance(input, (bytes, bytearray, memoryview)):
|
|
1698
|
-
raise TypeError('Binary input must be bytes')
|
|
1699
|
-
result['input'] = list(input)
|
|
1700
|
-
result.update(cwd=cwd, env=env, timeout=timeout, max_output_bytes=max_output_bytes)
|
|
1701
|
-
return result
|
|
1702
|
-
|
|
1703
|
-
def _result(args, payload, value, *, check=False, text=False, encoding=None, errors=None, stdout=True, stderr=True):
|
|
1704
|
-
if not isinstance(value, dict):
|
|
1705
|
-
raise ShellError('protocol', 'Invalid shell result')
|
|
1706
|
-
failure = value.get('error')
|
|
1707
|
-
if failure:
|
|
1708
|
-
if failure.get('code') == 'timeout':
|
|
1709
|
-
raise subprocess.TimeoutExpired(args, payload['timeout'], output=_bytes(failure.get('stdout', [])), stderr=_bytes(failure.get('stderr', [])))
|
|
1710
|
-
raise ShellError(failure.get('code', 'shell'), failure.get('message', 'Shell execution failed'))
|
|
1711
|
-
code = value.get('returncode')
|
|
1712
|
-
if type(code) is not int:
|
|
1713
|
-
raise ShellError('protocol', 'Invalid shell return code')
|
|
1714
|
-
output, diagnostic = _bytes(value.get('stdout', [])), _bytes(value.get('stderr', []))
|
|
1715
|
-
if payload['max_output_bytes'] is not None and len(output) + len(diagnostic) > payload['max_output_bytes']:
|
|
1716
|
-
raise ShellError('limit', 'Shell captured output limit exceeded')
|
|
1717
|
-
if text or encoding or errors:
|
|
1718
|
-
output = output.decode(encoding or 'utf-8', errors or 'strict').replace('\r\n', '\n').replace('\r', '\n')
|
|
1719
|
-
diagnostic = diagnostic.decode(encoding or 'utf-8', errors or 'strict').replace('\r\n', '\n').replace('\r', '\n')
|
|
1720
|
-
result = subprocess.CompletedProcess(args, code, output if stdout else None, diagnostic if stderr else None)
|
|
1721
|
-
if check:
|
|
1722
|
-
result.check_returncode()
|
|
1723
|
-
return result
|
|
1724
|
-
|
|
1725
|
-
@dataclass(frozen=True)
|
|
1726
|
-
class Event:
|
|
1727
|
-
type: str
|
|
1728
|
-
data: bytes = b''
|
|
1729
|
-
returncode: int | None = None
|
|
1730
|
-
|
|
1731
|
-
class Stream:
|
|
1732
|
-
def __init__(self, client, payload):
|
|
1733
|
-
self.client, self.payload = client, payload
|
|
1734
|
-
self.iterator = None
|
|
1735
|
-
self.pending = None
|
|
1736
|
-
self.closed = False
|
|
1737
|
-
self.count = 0
|
|
1738
|
-
async def __aenter__(self):
|
|
1739
|
-
self.client._check()
|
|
1740
|
-
self.client.streams.add(self)
|
|
1741
|
-
self.iterator = self.client.bridge.stream(self.payload).__aiter__()
|
|
1742
|
-
return self
|
|
1743
|
-
async def __aexit__(self, *ignored):
|
|
1744
|
-
await self.aclose()
|
|
1745
|
-
def __aiter__(self):
|
|
1746
|
-
return self
|
|
1747
|
-
async def __anext__(self):
|
|
1748
|
-
if self.closed or self.iterator is None:
|
|
1749
|
-
raise StopAsyncIteration
|
|
1750
|
-
if self.pending is not None:
|
|
1751
|
-
raise ShellError('concurrent', 'Only one shell stream read is allowed')
|
|
1752
|
-
task = self.pending = asyncio.create_task(self.iterator.__anext__())
|
|
1753
|
-
try:
|
|
1754
|
-
value = await task
|
|
1755
|
-
if value.get('error'):
|
|
1756
|
-
error = value['error']
|
|
1757
|
-
raise ShellError(error.get('code', 'shell'), error.get('message', 'Shell stream failed'))
|
|
1758
|
-
data = _bytes(value.get('data', []))
|
|
1759
|
-
self.count += len(data)
|
|
1760
|
-
if self.payload['max_output_bytes'] is not None and self.count > self.payload['max_output_bytes']:
|
|
1761
|
-
raise ShellError('limit', 'Shell streamed output limit exceeded')
|
|
1762
|
-
return Event(value['type'], data, value.get('returncode'))
|
|
1763
|
-
except BaseException:
|
|
1764
|
-
self.pending = None
|
|
1765
|
-
await self.aclose()
|
|
1766
|
-
raise
|
|
1767
|
-
finally:
|
|
1768
|
-
if self.pending is task:
|
|
1769
|
-
self.pending = None
|
|
1770
|
-
async def aclose(self):
|
|
1771
|
-
if self.closed:
|
|
1772
|
-
return
|
|
1773
|
-
self.closed = True
|
|
1774
|
-
if self.pending:
|
|
1775
|
-
self.pending.cancel()
|
|
1776
|
-
await asyncio.gather(self.pending, return_exceptions=True)
|
|
1777
|
-
if self.iterator and hasattr(self.iterator, 'aclose'):
|
|
1778
|
-
await self.iterator.aclose()
|
|
1779
|
-
self.client.streams.discard(self)
|
|
1780
|
-
|
|
1781
|
-
class Client:
|
|
1782
|
-
def __init__(self, *, bridge=None):
|
|
1783
|
-
self.bridge = bridge if bridge is not None else _bridge()
|
|
1784
|
-
self.closed = False
|
|
1785
|
-
self.tasks = set()
|
|
1786
|
-
self.streams = set()
|
|
1787
|
-
def _check(self):
|
|
1788
|
-
if self.closed:
|
|
1789
|
-
raise ShellError('closed', 'Shell client is closed')
|
|
1790
|
-
async def __aenter__(self):
|
|
1791
|
-
self._check()
|
|
1792
|
-
return self
|
|
1793
|
-
async def __aexit__(self, *ignored):
|
|
1794
|
-
await self.aclose()
|
|
1795
|
-
async def aclose(self):
|
|
1796
|
-
if self.closed:
|
|
1797
|
-
return
|
|
1798
|
-
self.closed = True
|
|
1799
|
-
for task in self.tasks:
|
|
1800
|
-
task.cancel()
|
|
1801
|
-
await asyncio.gather(*self.tasks, return_exceptions=True)
|
|
1802
|
-
await asyncio.gather(*(stream.aclose() for stream in tuple(self.streams)))
|
|
1803
|
-
async def run(self, args, *, check=False, text=False, encoding=None, errors=None, **options):
|
|
1804
|
-
self._check()
|
|
1805
|
-
payload = _payload(args, text=text, encoding=encoding, errors=errors, **options)
|
|
1806
|
-
async def operation():
|
|
1807
|
-
value = getattr(self.bridge, 'async_call', self.bridge.call)('run', payload)
|
|
1808
|
-
if inspect.isawaitable(value):
|
|
1809
|
-
value = await value
|
|
1810
|
-
return _result(args, payload, value, check=check, text=text, encoding=encoding, errors=errors)
|
|
1811
|
-
task = asyncio.create_task(operation())
|
|
1812
|
-
self.tasks.add(task)
|
|
1813
|
-
try:
|
|
1814
|
-
return await task
|
|
1815
|
-
finally:
|
|
1816
|
-
self.tasks.discard(task)
|
|
1817
|
-
async def script(self, source, **options):
|
|
1818
|
-
return await self.run(source, shell=True, **options)
|
|
1819
|
-
def stream(self, args, **options):
|
|
1820
|
-
self._check()
|
|
1821
|
-
return Stream(self, _payload(args, **options))
|
|
1822
|
-
|
|
1823
|
-
def _subprocess_run(args, *, stdin=None, stdout=None, stderr=None, input=None,
|
|
1824
|
-
capture_output=False, shell=False, cwd=None, env=None, timeout=None,
|
|
1825
|
-
check=False, text=False, encoding=None, errors=None, universal_newlines=None, **unsupported):
|
|
1826
|
-
if unsupported:
|
|
1827
|
-
raise NotImplementedError('Unsupported subprocess options: ' + ', '.join(sorted(unsupported)))
|
|
1828
|
-
if stdin not in (None, subprocess.PIPE, subprocess.DEVNULL) or stdout not in (None, subprocess.PIPE, subprocess.DEVNULL) or stderr not in (None, subprocess.PIPE, subprocess.DEVNULL):
|
|
1829
|
-
raise NotImplementedError('Only inherited, PIPE and DEVNULL standard streams are supported')
|
|
1830
|
-
if input is not None and stdin is not None:
|
|
1831
|
-
raise ValueError('stdin and input cannot both be supplied')
|
|
1832
|
-
if capture_output:
|
|
1833
|
-
if stdout is not None or stderr is not None:
|
|
1834
|
-
raise ValueError('capture_output cannot be combined with stdout or stderr')
|
|
1835
|
-
stdout = stderr = subprocess.PIPE
|
|
1836
|
-
if universal_newlines is not None:
|
|
1837
|
-
if text and not universal_newlines:
|
|
1838
|
-
raise ValueError('text and universal_newlines disagree')
|
|
1839
|
-
text = text or universal_newlines
|
|
1840
|
-
payload = _payload(args, shell=shell, input=input, cwd=cwd, env=env, timeout=timeout, text=text, encoding=encoding, errors=errors)
|
|
1841
|
-
payload.update(stdin='empty' if stdin in (subprocess.PIPE, subprocess.DEVNULL) else 'inherit',
|
|
1842
|
-
stdout='capture' if stdout == subprocess.PIPE else 'discard' if stdout == subprocess.DEVNULL else 'inherit',
|
|
1843
|
-
stderr='capture' if stderr == subprocess.PIPE else 'discard' if stderr == subprocess.DEVNULL else 'inherit')
|
|
1844
|
-
value = _bridge().call('run', payload)
|
|
1845
|
-
return _result(args, payload, value, check=check, text=text, encoding=encoding, errors=errors,
|
|
1846
|
-
stdout=stdout == subprocess.PIPE, stderr=stderr == subprocess.PIPE)
|
|
1847
|
-
|
|
1848
|
-
def _check_output(args, **options):
|
|
1849
|
-
if 'stdout' in options:
|
|
1850
|
-
raise ValueError('stdout is not allowed with check_output')
|
|
1851
|
-
return _subprocess_run(args, stdout=subprocess.PIPE, check=True, **options).stdout
|
|
1852
|
-
|
|
1853
|
-
def _unsupported_popen(*args, **kwargs):
|
|
1854
|
-
raise NotImplementedError('Safe Bash does not expose OS processes; use run or check_output')
|
|
1855
|
-
`;
|
|
1856
|
-
function installPythonShellModule(runtime) {
|
|
1857
|
-
runtime.globals.set("_safe_shell_source", pythonShellModule);
|
|
1858
|
-
try {
|
|
1859
|
-
runtime.runPython(`
|
|
1860
|
-
import sys as _safe_shell_sys, importlib.machinery as _safe_shell_imports
|
|
1861
|
-
class _SafeShellLoader:
|
|
1862
|
-
def __init__(self, source, spec):
|
|
1863
|
-
self.source, self.spec = source, spec
|
|
1864
|
-
def find_spec(self, fullname, path=None, target=None):
|
|
1865
|
-
if fullname == 'poe_shell':
|
|
1866
|
-
return self.spec(fullname, self, origin='poe_shell.py')
|
|
1867
|
-
def create_module(self, spec):
|
|
1868
|
-
return None
|
|
1869
|
-
def exec_module(self, module):
|
|
1870
|
-
module.__file__ = 'poe_shell.py'
|
|
1871
|
-
exec(compile(self.source, 'poe_shell.py', 'exec'), module.__dict__)
|
|
1872
|
-
_safe_shell_sys.meta_path.insert(0, _SafeShellLoader(_safe_shell_source, _safe_shell_imports.ModuleSpec))
|
|
1873
|
-
import subprocess as _safe_subprocess
|
|
1874
|
-
def _safe_subprocess_getattr(name):
|
|
1875
|
-
if name not in ('run', 'check_output', 'Popen'):
|
|
1876
|
-
raise AttributeError(name)
|
|
1877
|
-
from poe_shell import _subprocess_run, _check_output, _unsupported_popen
|
|
1878
|
-
import subprocess
|
|
1879
|
-
subprocess.run = _subprocess_run
|
|
1880
|
-
subprocess.check_output = _check_output
|
|
1881
|
-
subprocess.Popen = _unsupported_popen
|
|
1882
|
-
return getattr(subprocess, name)
|
|
1883
|
-
del _safe_subprocess.run, _safe_subprocess.check_output, _safe_subprocess.Popen
|
|
1884
|
-
_safe_subprocess.__getattr__ = _safe_subprocess_getattr
|
|
1885
|
-
del _SafeShellLoader, _safe_shell_sys, _safe_shell_imports, _safe_subprocess
|
|
1886
|
-
`);
|
|
1887
|
-
} finally {
|
|
1888
|
-
runtime.globals.delete("_safe_shell_source");
|
|
1889
|
-
}
|
|
1890
|
-
}
|
|
1891
|
-
|
|
1892
|
-
// packages/safe-bash/src/commands/python/jspi-run-sync.ts
|
|
1893
|
-
init_platform();
|
|
1894
|
-
function installPythonJspiRunSync(runtime) {
|
|
1895
|
-
runtime.runPython(`
|
|
1896
|
-
import pyodide.ffi as _safe_jspi_ffi
|
|
1897
|
-
def _safe_jspi_run_sync(awaitable, _original=_safe_jspi_ffi.run_sync):
|
|
1898
|
-
async def capture():
|
|
1899
|
-
try:
|
|
1900
|
-
return True, await awaitable
|
|
1901
|
-
except BaseException as error:
|
|
1902
|
-
return False, error
|
|
1903
|
-
completed, value = _original(capture())
|
|
1904
|
-
if completed:
|
|
1905
|
-
return value
|
|
1906
|
-
raise value
|
|
1907
|
-
_safe_jspi_ffi.run_sync = _safe_jspi_run_sync
|
|
1908
|
-
del _safe_jspi_ffi, _safe_jspi_run_sync
|
|
1909
|
-
`);
|
|
1910
|
-
}
|
|
1911
|
-
|
|
1912
|
-
// packages/safe-bash/src/commands/python/library-adapter.ts
|
|
1913
|
-
init_platform();
|
|
1914
|
-
var pythonLibraryAdapter = String.raw`
|
|
1915
|
-
import sys as _safe_library_sys, types as _safe_library_types
|
|
1916
|
-
import safe_host as _safe_library_host
|
|
1917
|
-
|
|
1918
|
-
class _SafeLibraryBridge:
|
|
1919
|
-
def __init__(self, name, host):
|
|
1920
|
-
self.name, self.host = name, host
|
|
1921
|
-
def call(self, operation, payload):
|
|
1922
|
-
if self.name != 'shell' or operation != 'run':
|
|
1923
|
-
raise NotImplementedError('Unsupported synchronous library operation')
|
|
1924
|
-
request = self.shell_request(payload)
|
|
1925
|
-
try:
|
|
1926
|
-
result = self.host.call('shell', request)
|
|
1927
|
-
except self.host.HostError as error:
|
|
1928
|
-
return dict(error=dict(code=error.code, message=str(error), stdout=[], stderr=[]))
|
|
1929
|
-
if result.get('error'):
|
|
1930
|
-
return result
|
|
1931
|
-
for name in ('stdout', 'stderr'):
|
|
1932
|
-
if payload.get(name) == 'inherit':
|
|
1933
|
-
import sys
|
|
1934
|
-
getattr(sys, name).buffer.write(bytes(result[name]))
|
|
1935
|
-
return dict(returncode=result['exitCode'], stdout=result['stdout'], stderr=result['stderr'])
|
|
1936
|
-
async def async_call(self, operation, payload):
|
|
1937
|
-
if self.name != 'shell' or operation != 'run':
|
|
1938
|
-
raise NotImplementedError('Unsupported asynchronous shell operation')
|
|
1939
|
-
try:
|
|
1940
|
-
result = await self.wait('begin', capability='shell', value=self.shell_request(payload))
|
|
1941
|
-
except self.host.HostError as error:
|
|
1942
|
-
return dict(error=dict(code=error.code, message=str(error), stdout=[], stderr=[]))
|
|
1943
|
-
if result.get('error'):
|
|
1944
|
-
return result
|
|
1945
|
-
return dict(returncode=result['exitCode'], stdout=result['stdout'], stderr=result['stderr'])
|
|
1946
|
-
def shell_request(self, payload):
|
|
1947
|
-
request = {key: payload[key] for key in ('argv', 'script', 'cwd', 'env') if payload.get(key) is not None}
|
|
1948
|
-
if 'input' in payload:
|
|
1949
|
-
request['stdin'] = payload['input']
|
|
1950
|
-
elif payload.get('stdin') == 'inherit':
|
|
1951
|
-
request['stdinMode'] = 'inherit'
|
|
1952
|
-
if payload.get('max_output_bytes') is not None:
|
|
1953
|
-
request['maxOutputBytes'] = payload['max_output_bytes']
|
|
1954
|
-
if payload.get('timeout') is not None:
|
|
1955
|
-
request['timeoutMs'] = max(1, int(payload['timeout'] * 1000))
|
|
1956
|
-
return request
|
|
1957
|
-
def fail(self, error):
|
|
1958
|
-
code, message = error.get('code', 'service'), error.get('message', 'Host service failed')
|
|
1959
|
-
if self.name == 'llm':
|
|
1960
|
-
from poe_llm import LlmError, LimitError, CapabilityError
|
|
1961
|
-
if code == 'limit':
|
|
1962
|
-
raise LimitError(message)
|
|
1963
|
-
if code == 'timeout':
|
|
1964
|
-
import asyncio
|
|
1965
|
-
raise asyncio.TimeoutError(message)
|
|
1966
|
-
if code == 'capability':
|
|
1967
|
-
raise CapabilityError(message)
|
|
1968
|
-
raise LlmError(code, message)
|
|
1969
|
-
from poe_shell import ShellError
|
|
1970
|
-
raise ShellError(code, message)
|
|
1971
|
-
async def wait(self, operation, **fields):
|
|
1972
|
-
import asyncio
|
|
1973
|
-
handle = self.host._send(operation, **fields)
|
|
1974
|
-
delay = 0.001
|
|
1975
|
-
try:
|
|
1976
|
-
while True:
|
|
1977
|
-
result = self.host._send('poll', handle=handle)
|
|
1978
|
-
if result['done']:
|
|
1979
|
-
handle = None
|
|
1980
|
-
if result.get('error'):
|
|
1981
|
-
raise self.host.HostError(result['error'], result.get('errorCode', 'service'))
|
|
1982
|
-
return result['value']
|
|
1983
|
-
await asyncio.sleep(delay)
|
|
1984
|
-
delay = min(0.016, delay * 2)
|
|
1985
|
-
finally:
|
|
1986
|
-
if handle is not None:
|
|
1987
|
-
self.host._send('cancel', handle=handle)
|
|
1988
|
-
async def stream(self, payload):
|
|
1989
|
-
if self.name == 'shell':
|
|
1990
|
-
payload = self.shell_request(payload)
|
|
1991
|
-
handle = self.host._send('stream', capability=self.name, value=payload)
|
|
1992
|
-
cancelled = False
|
|
1993
|
-
try:
|
|
1994
|
-
while True:
|
|
1995
|
-
result = await self.wait('begin-next', handle=handle)
|
|
1996
|
-
if result['done']:
|
|
1997
|
-
handle = None
|
|
1998
|
-
break
|
|
1999
|
-
event = result['value']
|
|
2000
|
-
if event['type'] == 'bytes':
|
|
2001
|
-
yield dict(type='bytes', data=event['bytes'])
|
|
2002
|
-
elif event['type'] == 'text':
|
|
2003
|
-
yield dict(type='text', text=event['text'])
|
|
2004
|
-
else:
|
|
2005
|
-
value = event['value']
|
|
2006
|
-
if isinstance(value, dict) and value.get('error'):
|
|
2007
|
-
self.fail(value['error'])
|
|
2008
|
-
yield value
|
|
2009
|
-
except self.host.HostError as error:
|
|
2010
|
-
cancelled = True
|
|
2011
|
-
self.fail(dict(code=error.code, message=str(error)))
|
|
2012
|
-
except BaseException:
|
|
2013
|
-
cancelled = True
|
|
2014
|
-
raise
|
|
2015
|
-
finally:
|
|
2016
|
-
if handle is not None:
|
|
2017
|
-
try:
|
|
2018
|
-
self.host._send('release', handle=handle)
|
|
2019
|
-
except self.host.HostError:
|
|
2020
|
-
if not cancelled:
|
|
2021
|
-
raise
|
|
2022
|
-
|
|
2023
|
-
class _SafeAsyncLibraryBridge(_SafeLibraryBridge):
|
|
2024
|
-
async def call(self, operation, payload):
|
|
2025
|
-
try:
|
|
2026
|
-
value = await self.wait('begin', capability=self.name, value=dict(operation=operation, payload=payload))
|
|
2027
|
-
except self.host.HostError as error:
|
|
2028
|
-
self.fail(dict(code=error.code if error.code != 'service' else 'capability', message=str(error)))
|
|
2029
|
-
if isinstance(value, dict) and value.get('error'):
|
|
2030
|
-
self.fail(value['error'])
|
|
2031
|
-
return value
|
|
2032
|
-
|
|
2033
|
-
for _safe_library_name in ('llm', 'shell'):
|
|
2034
|
-
_safe_library_module = _safe_library_types.ModuleType('_poe_' + _safe_library_name + '_capability')
|
|
2035
|
-
_safe_library_type = _SafeAsyncLibraryBridge if _safe_library_name == 'llm' else _SafeLibraryBridge
|
|
2036
|
-
_safe_library_module.bridge = _safe_library_type(_safe_library_name, _safe_library_host)
|
|
2037
|
-
_safe_library_sys.modules[_safe_library_module.__name__] = _safe_library_module
|
|
2038
|
-
|
|
2039
|
-
del _safe_library_sys, _safe_library_types, _safe_library_host, _safe_library_name, _safe_library_module, _safe_library_type
|
|
2040
|
-
`;
|
|
2041
|
-
|
|
2042
|
-
// packages/safe-bash/src/commands/python/host-module.ts
|
|
2043
|
-
init_platform();
|
|
2044
|
-
var pythonHostModule = `
|
|
2045
|
-
import types as _safe_host_types
|
|
2046
|
-
_safe_host_module = _safe_host_types.ModuleType('safe_host')
|
|
2047
|
-
exec('''
|
|
2048
|
-
import json
|
|
2049
|
-
from _safe_native_fs import request as _request
|
|
2050
|
-
|
|
2051
|
-
class HostError(RuntimeError):
|
|
2052
|
-
def __init__(self, message, code='service'):
|
|
2053
|
-
super().__init__(message)
|
|
2054
|
-
self.code = code
|
|
2055
|
-
|
|
2056
|
-
def _send(operation, **fields):
|
|
2057
|
-
payload = dict(version=1, operation=operation, **fields)
|
|
2058
|
-
response = json.loads(_request(json.dumps(['host', payload], allow_nan=False)))
|
|
2059
|
-
if 'error' in response:
|
|
2060
|
-
raise HostError(response['error'], response.get('errorCode', 'service'))
|
|
2061
|
-
return response['value']
|
|
2062
|
-
|
|
2063
|
-
def call(capability, value=None):
|
|
2064
|
-
"""Call an explicitly granted host operation using JSPI suspension."""
|
|
2065
|
-
return _send('call', capability=capability, value=value)
|
|
2066
|
-
|
|
2067
|
-
class Stream:
|
|
2068
|
-
def __init__(self, capability, value=None):
|
|
2069
|
-
self._handle = _send('stream', capability=capability, value=value)
|
|
2070
|
-
def __iter__(self):
|
|
2071
|
-
return self
|
|
2072
|
-
def __next__(self):
|
|
2073
|
-
if self._handle is None:
|
|
2074
|
-
raise StopIteration
|
|
2075
|
-
try:
|
|
2076
|
-
item = _send('next', handle=self._handle)
|
|
2077
|
-
except BaseException:
|
|
2078
|
-
self.close()
|
|
2079
|
-
raise
|
|
2080
|
-
if item['done']:
|
|
2081
|
-
self._handle = None
|
|
2082
|
-
raise StopIteration
|
|
2083
|
-
event = item['value']
|
|
2084
|
-
if event['type'] == 'bytes':
|
|
2085
|
-
return bytes(event['bytes'])
|
|
2086
|
-
if event['type'] == 'text':
|
|
2087
|
-
return event['text']
|
|
2088
|
-
return event['value']
|
|
2089
|
-
def close(self):
|
|
2090
|
-
handle, self._handle = self._handle, None
|
|
2091
|
-
if handle is not None:
|
|
2092
|
-
_send('release', handle=handle)
|
|
2093
|
-
def __enter__(self):
|
|
2094
|
-
return self
|
|
2095
|
-
def __exit__(self, *_):
|
|
2096
|
-
self.close()
|
|
2097
|
-
|
|
2098
|
-
def stream(capability, value=None):
|
|
2099
|
-
return Stream(capability, value)
|
|
2100
|
-
|
|
2101
|
-
class CalledProcessError(HostError):
|
|
2102
|
-
def __init__(self, result):
|
|
2103
|
-
self.returncode, self.cmd = result.returncode, result.args
|
|
2104
|
-
self.output, self.stderr = result.stdout, result.stderr
|
|
2105
|
-
super().__init__('Safe Bash command exited with status ' + str(self.returncode))
|
|
2106
|
-
|
|
2107
|
-
class CompletedProcess:
|
|
2108
|
-
def __init__(self, args, result, text):
|
|
2109
|
-
self.args, self.returncode = args, result['exitCode']
|
|
2110
|
-
self.stdout, self.stderr = bytes(result['stdout']), bytes(result['stderr'])
|
|
2111
|
-
if text:
|
|
2112
|
-
self.stdout, self.stderr = self.stdout.decode('utf-8'), self.stderr.decode('utf-8')
|
|
2113
|
-
def check_returncode(self):
|
|
2114
|
-
if self.returncode:
|
|
2115
|
-
raise CalledProcessError(self)
|
|
2116
|
-
|
|
2117
|
-
def run(args, *, shell=False, input=None, cwd=None, env=None, text=False, check=False, timeout=None):
|
|
2118
|
-
"""Run a configured Safe Bash command; capture is bounded by the host."""
|
|
2119
|
-
if shell:
|
|
2120
|
-
if not isinstance(args, str):
|
|
2121
|
-
raise TypeError('script mode requires a string')
|
|
2122
|
-
request = dict(script=args)
|
|
2123
|
-
else:
|
|
2124
|
-
if isinstance(args, str):
|
|
2125
|
-
raise TypeError('literal execution requires an argv sequence')
|
|
2126
|
-
request = dict(argv=list(args))
|
|
2127
|
-
if input is not None:
|
|
2128
|
-
request['stdin'] = input if isinstance(input, str) else list(input)
|
|
2129
|
-
if cwd is not None:
|
|
2130
|
-
request['cwd'] = cwd
|
|
2131
|
-
if env is not None:
|
|
2132
|
-
request['env'] = env
|
|
2133
|
-
if timeout is not None:
|
|
2134
|
-
request['timeoutMs'] = max(1, int(timeout * 1000))
|
|
2135
|
-
value = call('shell', request)
|
|
2136
|
-
if value.get('error'):
|
|
2137
|
-
failure = value['error']
|
|
2138
|
-
if failure.get('code') == 'timeout':
|
|
2139
|
-
import subprocess
|
|
2140
|
-
raise subprocess.TimeoutExpired(args, timeout, output=bytes(failure['stdout']), stderr=bytes(failure['stderr']))
|
|
2141
|
-
raise HostError(failure.get('message', 'Shell service failed'), failure.get('code', 'service'))
|
|
2142
|
-
result = CompletedProcess(args, value, text)
|
|
2143
|
-
if check:
|
|
2144
|
-
result.check_returncode()
|
|
2145
|
-
return result
|
|
2146
|
-
|
|
2147
|
-
def check_output(args, **options):
|
|
2148
|
-
return run(args, check=True, **options).stdout
|
|
2149
|
-
''', _safe_host_module.__dict__)
|
|
2150
|
-
sys.modules['safe_host'] = _safe_host_module
|
|
2151
|
-
`;
|
|
2152
|
-
|
|
2153
|
-
// packages/safe-bash/src/commands/python/jspi-trampoline.ts
|
|
2154
|
-
init_platform();
|
|
2155
|
-
var pythonJspiSignatures = Object.freeze({
|
|
2156
|
-
__syscall_chdir: "ip",
|
|
2157
|
-
__syscall_chmod: "ipi",
|
|
2158
|
-
__syscall_dup: "ii",
|
|
2159
|
-
__syscall_faccessat: "iipii",
|
|
2160
|
-
__syscall_fcntl64: "iiip",
|
|
2161
|
-
__syscall_fdatasync: "ii",
|
|
2162
|
-
__syscall_fstat64: "iip",
|
|
2163
|
-
__syscall_ftruncate64: "iij",
|
|
2164
|
-
__syscall_getcwd: "ipp",
|
|
2165
|
-
__syscall_getdents64: "iipp",
|
|
2166
|
-
__syscall_ioctl: "iiip",
|
|
2167
|
-
__syscall_lstat64: "ipp",
|
|
2168
|
-
__syscall_mkdirat: "iipi",
|
|
2169
|
-
__syscall_newfstatat: "iippi",
|
|
2170
|
-
__syscall_openat: "iipip",
|
|
2171
|
-
__syscall_readlinkat: "iippp",
|
|
2172
|
-
__syscall_renameat: "iipip",
|
|
2173
|
-
__syscall_rmdir: "ip",
|
|
2174
|
-
__syscall_stat64: "ipp",
|
|
2175
|
-
__syscall_symlinkat: "ipip",
|
|
2176
|
-
__syscall_truncate64: "ipj",
|
|
2177
|
-
__syscall_unlinkat: "iipi",
|
|
2178
|
-
__syscall_utimensat: "iippi",
|
|
2179
|
-
fd_close: "ii",
|
|
2180
|
-
fd_fdstat_get: "iip",
|
|
2181
|
-
fd_pread: "iippjp",
|
|
2182
|
-
fd_pwrite: "iippjp",
|
|
2183
|
-
fd_read: "iippp",
|
|
2184
|
-
fd_seek: "iijip",
|
|
2185
|
-
fd_sync: "ii",
|
|
2186
|
-
fd_write: "iippp"
|
|
2187
|
-
});
|
|
2188
|
-
var unsigned = (value) => {
|
|
2189
|
-
const bytes = [];
|
|
2190
|
-
do {
|
|
2191
|
-
bytes.push(value & 127 | (value > 127 ? 128 : 0));
|
|
2192
|
-
value >>>= 7;
|
|
2193
|
-
} while (value);
|
|
2194
|
-
return bytes;
|
|
2195
|
-
};
|
|
2196
|
-
var vector = (entries) => [...unsigned(entries.length), ...entries.flat()];
|
|
2197
|
-
var text = (value) => {
|
|
2198
|
-
const bytes = new TextEncoder().encode(value);
|
|
2199
|
-
return [...unsigned(bytes.length), ...bytes];
|
|
2200
|
-
};
|
|
2201
|
-
var section = (type, bytes) => [type, ...unsigned(bytes.length), ...bytes];
|
|
2202
|
-
var signature = (parameters, result) => [96, ...vector(parameters.map((type) => [type])), ...result === void 0 ? [0] : [1, result]];
|
|
2203
|
-
function createPythonJspiTrampoline() {
|
|
2204
|
-
const entries = Object.entries(pythonJspiSignatures);
|
|
2205
|
-
const types = entries.flatMap(([, value]) => {
|
|
2206
|
-
const parameters = [...value.slice(1)].map((type) => type === "j" ? 126 : 127);
|
|
2207
|
-
return [signature(parameters, 127), signature(parameters, 111)];
|
|
2208
|
-
});
|
|
2209
|
-
types.push(signature([111], 127));
|
|
2210
|
-
const imports = entries.flatMap(([name], index) => [
|
|
2211
|
-
[...text("original"), ...text(name), 0, ...unsigned(index * 2)],
|
|
2212
|
-
[...text("request"), ...text(name), 0, ...unsigned(index * 2 + 1)],
|
|
2213
|
-
[...text("shutdown"), ...text(name), 0, ...unsigned(index * 2)]
|
|
2214
|
-
]);
|
|
2215
|
-
imports.push([...text("control"), ...text("syncify"), 1, 112, 0, 1]);
|
|
2216
|
-
imports.push([...text("control"), ...text("active"), 3, 127, 1]);
|
|
2217
|
-
const functions = entries.map(([, value], index) => {
|
|
2218
|
-
const argumentsBytes = [...value.slice(1)].flatMap((_type, parameter) => [32, ...unsigned(parameter)]);
|
|
2219
|
-
const code = [
|
|
2220
|
-
0,
|
|
2221
|
-
35,
|
|
2222
|
-
0,
|
|
2223
|
-
4,
|
|
2224
|
-
127,
|
|
2225
|
-
35,
|
|
2226
|
-
0,
|
|
2227
|
-
65,
|
|
2228
|
-
2,
|
|
2229
|
-
70,
|
|
2230
|
-
4,
|
|
2231
|
-
127,
|
|
2232
|
-
...argumentsBytes,
|
|
2233
|
-
16,
|
|
2234
|
-
...unsigned(index * 3 + 2),
|
|
2235
|
-
5,
|
|
2236
|
-
...argumentsBytes,
|
|
2237
|
-
16,
|
|
2238
|
-
...unsigned(index * 3 + 1),
|
|
2239
|
-
65,
|
|
2240
|
-
0,
|
|
2241
|
-
17,
|
|
2242
|
-
...unsigned(entries.length * 2),
|
|
2243
|
-
0,
|
|
2244
|
-
11,
|
|
2245
|
-
5,
|
|
2246
|
-
...argumentsBytes,
|
|
2247
|
-
16,
|
|
2248
|
-
...unsigned(index * 3),
|
|
2249
|
-
11,
|
|
2250
|
-
11
|
|
2251
|
-
];
|
|
2252
|
-
return [...unsigned(code.length), ...code];
|
|
2253
|
-
});
|
|
2254
|
-
return new Uint8Array([
|
|
2255
|
-
0,
|
|
2256
|
-
97,
|
|
2257
|
-
115,
|
|
2258
|
-
109,
|
|
2259
|
-
1,
|
|
2260
|
-
0,
|
|
2261
|
-
0,
|
|
2262
|
-
0,
|
|
2263
|
-
...section(1, vector(types)),
|
|
2264
|
-
...section(2, vector(imports)),
|
|
2265
|
-
...section(3, vector(entries.map((_entry, index) => unsigned(index * 2)))),
|
|
2266
|
-
...section(7, vector(entries.map(([name], index) => [...text(name), 0, ...unsigned(entries.length * 3 + index)]))),
|
|
2267
|
-
...section(10, vector(functions))
|
|
2268
|
-
]);
|
|
2269
|
-
}
|
|
2270
|
-
function createPythonJspiNativeCall() {
|
|
2271
|
-
const types = [
|
|
2272
|
-
signature([127], 127),
|
|
2273
|
-
signature([127]),
|
|
2274
|
-
signature([127], 111),
|
|
2275
|
-
signature([111], 127),
|
|
2276
|
-
signature([127, 127], 127),
|
|
2277
|
-
signature([], 127)
|
|
2278
|
-
];
|
|
2279
|
-
const imports = [
|
|
2280
|
-
["python", "utf8", 0],
|
|
2281
|
-
["python", "unicode", 0],
|
|
2282
|
-
["python", "free", 1],
|
|
2283
|
-
["python", "noMemory", 5],
|
|
2284
|
-
["request", "send", 2],
|
|
2285
|
-
["shutdown", "send", 0]
|
|
2286
|
-
];
|
|
2287
|
-
const code = [
|
|
2288
|
-
1,
|
|
2289
|
-
2,
|
|
2290
|
-
127,
|
|
2291
|
-
32,
|
|
2292
|
-
1,
|
|
2293
|
-
16,
|
|
2294
|
-
0,
|
|
2295
|
-
34,
|
|
2296
|
-
2,
|
|
2297
|
-
69,
|
|
2298
|
-
4,
|
|
2299
|
-
127,
|
|
2300
|
-
65,
|
|
2301
|
-
0,
|
|
2302
|
-
5,
|
|
2303
|
-
35,
|
|
2304
|
-
0,
|
|
2305
|
-
65,
|
|
2306
|
-
2,
|
|
2307
|
-
70,
|
|
2308
|
-
4,
|
|
2309
|
-
127,
|
|
2310
|
-
32,
|
|
2311
|
-
2,
|
|
2312
|
-
16,
|
|
2313
|
-
5,
|
|
2314
|
-
5,
|
|
2315
|
-
32,
|
|
2316
|
-
2,
|
|
2317
|
-
16,
|
|
2318
|
-
4,
|
|
2319
|
-
65,
|
|
2320
|
-
0,
|
|
2321
|
-
17,
|
|
2322
|
-
3,
|
|
2323
|
-
0,
|
|
2324
|
-
11,
|
|
2325
|
-
34,
|
|
2326
|
-
2,
|
|
2327
|
-
69,
|
|
2328
|
-
4,
|
|
2329
|
-
127,
|
|
2330
|
-
16,
|
|
2331
|
-
3,
|
|
2332
|
-
5,
|
|
2333
|
-
32,
|
|
2334
|
-
2,
|
|
2335
|
-
16,
|
|
2336
|
-
1,
|
|
2337
|
-
33,
|
|
2338
|
-
3,
|
|
2339
|
-
32,
|
|
2340
|
-
2,
|
|
2341
|
-
16,
|
|
2342
|
-
2,
|
|
2343
|
-
32,
|
|
2344
|
-
3,
|
|
2345
|
-
11,
|
|
2346
|
-
11,
|
|
2347
|
-
11
|
|
2348
|
-
];
|
|
2349
|
-
return new Uint8Array([
|
|
2350
|
-
0,
|
|
2351
|
-
97,
|
|
2352
|
-
115,
|
|
2353
|
-
109,
|
|
2354
|
-
1,
|
|
2355
|
-
0,
|
|
2356
|
-
0,
|
|
2357
|
-
0,
|
|
2358
|
-
...section(1, vector(types)),
|
|
2359
|
-
...section(2, vector([
|
|
2360
|
-
...imports.map(([namespace, name, type]) => [...text(namespace), ...text(name), 0, type]),
|
|
2361
|
-
[...text("control"), ...text("syncify"), 1, 112, 0, 1],
|
|
2362
|
-
[...text("control"), ...text("active"), 3, 127, 1]
|
|
2363
|
-
])),
|
|
2364
|
-
...section(3, vector([[4]])),
|
|
2365
|
-
...section(7, vector([[...text("call"), 0, 6]])),
|
|
2366
|
-
...section(10, vector([[...unsigned(code.length), ...code]]))
|
|
2367
|
-
]);
|
|
2368
|
-
}
|
|
2369
|
-
function createPythonJspiStatResult() {
|
|
2370
|
-
const types = [
|
|
2371
|
-
signature([127], 127),
|
|
2372
|
-
signature([127]),
|
|
2373
|
-
signature([127, 127], 127),
|
|
2374
|
-
signature([127, 127, 127]),
|
|
2375
|
-
signature([], 127)
|
|
2376
|
-
];
|
|
2377
|
-
const imports = [["create", 0], ["size", 0], ["item", 2], ["retain", 1], ["set", 3], ["invalid", 4]];
|
|
2378
|
-
const code = [
|
|
2379
|
-
1,
|
|
2380
|
-
3,
|
|
2381
|
-
127,
|
|
2382
|
-
32,
|
|
2383
|
-
1,
|
|
2384
|
-
16,
|
|
2385
|
-
1,
|
|
2386
|
-
35,
|
|
2387
|
-
0,
|
|
2388
|
-
71,
|
|
2389
|
-
4,
|
|
2390
|
-
64,
|
|
2391
|
-
16,
|
|
2392
|
-
5,
|
|
2393
|
-
15,
|
|
2394
|
-
11,
|
|
2395
|
-
32,
|
|
2396
|
-
0,
|
|
2397
|
-
16,
|
|
2398
|
-
0,
|
|
2399
|
-
34,
|
|
2400
|
-
2,
|
|
2401
|
-
69,
|
|
2402
|
-
4,
|
|
2403
|
-
127,
|
|
2404
|
-
65,
|
|
2405
|
-
0,
|
|
2406
|
-
5,
|
|
2407
|
-
2,
|
|
2408
|
-
64,
|
|
2409
|
-
3,
|
|
2410
|
-
64,
|
|
2411
|
-
32,
|
|
2412
|
-
3,
|
|
2413
|
-
35,
|
|
2414
|
-
0,
|
|
2415
|
-
79,
|
|
2416
|
-
13,
|
|
2417
|
-
1,
|
|
2418
|
-
32,
|
|
2419
|
-
1,
|
|
2420
|
-
32,
|
|
2421
|
-
3,
|
|
2422
|
-
16,
|
|
2423
|
-
2,
|
|
2424
|
-
34,
|
|
2425
|
-
4,
|
|
2426
|
-
16,
|
|
2427
|
-
3,
|
|
2428
|
-
32,
|
|
2429
|
-
2,
|
|
2430
|
-
32,
|
|
2431
|
-
3,
|
|
2432
|
-
32,
|
|
2433
|
-
4,
|
|
2434
|
-
16,
|
|
2435
|
-
4,
|
|
2436
|
-
32,
|
|
2437
|
-
3,
|
|
2438
|
-
65,
|
|
2439
|
-
1,
|
|
2440
|
-
106,
|
|
2441
|
-
33,
|
|
2442
|
-
3,
|
|
2443
|
-
12,
|
|
2444
|
-
0,
|
|
2445
|
-
11,
|
|
2446
|
-
11,
|
|
2447
|
-
32,
|
|
2448
|
-
2,
|
|
2449
|
-
11,
|
|
2450
|
-
11
|
|
2451
|
-
];
|
|
2452
|
-
return new Uint8Array([
|
|
2453
|
-
0,
|
|
2454
|
-
97,
|
|
2455
|
-
115,
|
|
2456
|
-
109,
|
|
2457
|
-
1,
|
|
2458
|
-
0,
|
|
2459
|
-
0,
|
|
2460
|
-
0,
|
|
2461
|
-
...section(1, vector(types)),
|
|
2462
|
-
...section(2, vector([
|
|
2463
|
-
...imports.map(([name, type]) => [...text("python"), ...text(name), 0, type]),
|
|
2464
|
-
[...text("python"), ...text("fields"), 3, 127, 0]
|
|
2465
|
-
])),
|
|
2466
|
-
...section(3, vector([[2]])),
|
|
2467
|
-
...section(7, vector([[...text("construct"), 0, 6]])),
|
|
2468
|
-
...section(10, vector([[...unsigned(code.length), ...code]]))
|
|
2469
|
-
]);
|
|
2470
|
-
}
|
|
2471
|
-
|
|
2472
|
-
// packages/safe-bash/src/commands/python/jspi-scheduler.ts
|
|
2473
|
-
init_platform();
|
|
2474
|
-
function createPythonJspiScheduler() {
|
|
2475
|
-
const queued = /* @__PURE__ */ new Map();
|
|
2476
|
-
const active = /* @__PURE__ */ new Set();
|
|
2477
|
-
const failures = [];
|
|
2478
|
-
let closed = false;
|
|
2479
|
-
let retirement;
|
|
2480
|
-
return {
|
|
2481
|
-
scheduleCallback(callback, delay = 0) {
|
|
2482
|
-
if (closed) {
|
|
2483
|
-
callback.destroy?.();
|
|
2484
|
-
throw new Error("Python scheduler is closed");
|
|
2485
|
-
}
|
|
2486
|
-
const timer = setTimeout(() => {
|
|
2487
|
-
queued.delete(timer);
|
|
2488
|
-
const work = Promise.resolve().then(() => callback()).then(() => {
|
|
2489
|
-
}, (error) => {
|
|
2490
|
-
failures.push(error);
|
|
2491
|
-
}).finally(() => {
|
|
2492
|
-
active.delete(work);
|
|
2493
|
-
});
|
|
2494
|
-
active.add(work);
|
|
2495
|
-
}, delay);
|
|
2496
|
-
queued.set(timer, callback);
|
|
2497
|
-
},
|
|
2498
|
-
close() {
|
|
2499
|
-
if (retirement) return retirement;
|
|
2500
|
-
if (!active.size) closed = true;
|
|
2501
|
-
retirement = Promise.resolve().then(async () => {
|
|
2502
|
-
while (active.size) await Promise.all(active);
|
|
2503
|
-
closed = true;
|
|
2504
|
-
for (const [timer, callback] of queued) {
|
|
2505
|
-
clearTimeout(timer);
|
|
2506
|
-
try {
|
|
2507
|
-
callback.destroy?.();
|
|
2508
|
-
} catch (error) {
|
|
2509
|
-
failures.push(error);
|
|
2510
|
-
}
|
|
2511
|
-
}
|
|
2512
|
-
queued.clear();
|
|
2513
|
-
if (failures.length === 1) throw failures[0];
|
|
2514
|
-
if (failures.length) throw new AggregateError(failures, "Python callback retirement failed");
|
|
2515
|
-
});
|
|
2516
|
-
return retirement;
|
|
2517
|
-
}
|
|
2518
|
-
};
|
|
2519
|
-
}
|
|
2520
|
-
|
|
2521
|
-
// packages/safe-bash/src/commands/python/jspi.ts
|
|
2522
|
-
function createPythonJspiExecutor(options) {
|
|
2523
|
-
const engine = globalThis.WebAssembly;
|
|
2524
|
-
const active = new engine.Global({ value: "i32", mutable: true }, 0);
|
|
2525
|
-
const syncify = new engine.Table({ element: "anyfunc", initial: 1 });
|
|
2526
|
-
const controller = new AbortController();
|
|
2527
|
-
const scheduler = createPythonJspiScheduler();
|
|
2528
|
-
let admitted = false;
|
|
2529
|
-
let running;
|
|
2530
|
-
let retirement;
|
|
2531
|
-
let schedulerCleanup;
|
|
2532
|
-
let cleanup;
|
|
2533
|
-
let runtime;
|
|
2534
|
-
let native;
|
|
2535
|
-
let instanceBound = false;
|
|
2536
|
-
let importsBound = false;
|
|
2537
|
-
let schedulerBound = false;
|
|
2538
|
-
let methodDefinition = 0;
|
|
2539
|
-
let methodPointer;
|
|
2540
|
-
let statPointer;
|
|
2541
|
-
const originals = {};
|
|
2542
|
-
const execute = async (start) => {
|
|
2543
|
-
const signal = AbortSignal.any([start.signal, controller.signal]);
|
|
2544
|
-
const configuration = parsePythonInvocation(start.invocation.args, start.invocation.env);
|
|
2545
|
-
let guestMask = 18;
|
|
2546
|
-
let qualified = false;
|
|
2547
|
-
let exitCode = 0;
|
|
2548
|
-
let finalized = 0;
|
|
2549
|
-
let acceptingHostCalls = true;
|
|
2550
|
-
let hostCleanupFailure;
|
|
2551
|
-
try {
|
|
2552
|
-
signal.throwIfAborted();
|
|
2553
|
-
if (start.packages?.requirements.length || start.installOnly) throw new PythonFailure("runtime-assets", { cause: new Error("JSPI runtime requires statically qualified packages") });
|
|
2554
|
-
runtime = await options.loadRuntime({
|
|
2555
|
-
jsglobals: /* @__PURE__ */ Object.create(null),
|
|
2556
|
-
args: configuration.startupArgs,
|
|
2557
|
-
env: configuration.env,
|
|
2558
|
-
bindImports(imports) {
|
|
2559
|
-
if (importsBound) throw new PythonFailure("runtime-abi");
|
|
2560
|
-
const namespaces = [...new Set(Object.values(imports))];
|
|
2561
|
-
for (const namespace of namespaces) {
|
|
2562
|
-
for (const [name, value] of Object.entries(namespace)) {
|
|
2563
|
-
if (typeof value !== "function") continue;
|
|
2564
|
-
if (name === "__syscall_umask_js") namespace[name] = (mask) => {
|
|
2565
|
-
const previous = guestMask;
|
|
2566
|
-
guestMask = mask & 511;
|
|
2567
|
-
return previous;
|
|
2568
|
-
};
|
|
2569
|
-
else if (name in pythonJspiSignatures) originals[name] = value;
|
|
2570
|
-
else if (name.startsWith("__syscall_") || name === "_maybe_connect_async") namespace[name] = () => -52;
|
|
2571
|
-
else if (name === "_emscripten_system") namespace[name] = (command) => command === 0 ? 0 : -52;
|
|
2572
|
-
}
|
|
2573
|
-
}
|
|
2574
|
-
if (!originals.fd_read || !originals.fd_write || !originals.__syscall_openat) throw new PythonFailure("runtime-abi");
|
|
2575
|
-
const original = Object.fromEntries(Object.keys(pythonJspiSignatures).map((name) => [name, originals[name] ?? (() => -52)]));
|
|
2576
|
-
const request = Object.fromEntries(Object.keys(pythonJspiSignatures).map((name) => [
|
|
2577
|
-
name,
|
|
2578
|
-
(...args) => native.invoke(name, args)
|
|
2579
|
-
]));
|
|
2580
|
-
const shutdown = Object.fromEntries(Object.entries(request).map(([name, dispatch]) => [name, new engine.Suspending(dispatch)]));
|
|
2581
|
-
const bridge = new engine.Instance(options.trampoline, { original, request, shutdown, control: { active, syncify } });
|
|
2582
|
-
for (const namespace of namespaces) for (const name of Object.keys(originals)) {
|
|
2583
|
-
if (typeof namespace[name] === "function") namespace[name] = bridge.exports[name];
|
|
2584
|
-
}
|
|
2585
|
-
importsBound = true;
|
|
2586
|
-
},
|
|
2587
|
-
bindInstance(instance) {
|
|
2588
|
-
if (instanceBound || !importsBound || typeof instance.exports.syscall_syncify !== "function") throw new PythonFailure("runtime-abi");
|
|
2589
|
-
syncify.set(0, instance.exports.syscall_syncify);
|
|
2590
|
-
instanceBound = true;
|
|
2591
|
-
},
|
|
2592
|
-
bindScheduler(api) {
|
|
2593
|
-
if (schedulerBound) throw new PythonFailure("runtime-abi");
|
|
2594
|
-
api.scheduleCallback = scheduler.scheduleCallback;
|
|
2595
|
-
schedulerBound = true;
|
|
2596
|
-
}
|
|
2597
|
-
});
|
|
2598
|
-
if (!importsBound || !instanceBound || !schedulerBound || runtime.version !== "314.0.6" || !runtime._module.jspiSupported) throw new PythonFailure("runtime-abi");
|
|
2599
|
-
qualified = true;
|
|
2600
|
-
signal.throwIfAborted();
|
|
2601
|
-
runtime.runPython("import sys, os, json, runpy, traceback, types, warnings, textwrap, io, struct, linecache, importlib.machinery, shutil, stat, pyodide.ffi");
|
|
2602
|
-
runtime.globals.set("_safe_runtime_mount", start.runtimeMount);
|
|
2603
|
-
const filesystem = runtime.FS;
|
|
2604
|
-
const bootstrap = filesystem.root;
|
|
2605
|
-
filesystem.root = null;
|
|
2606
|
-
filesystem.mount(filesystem.filesystems.MEMFS, {}, "/");
|
|
2607
|
-
filesystem.mkdir(start.runtimeMount);
|
|
2608
|
-
filesystem.mount({ mount: () => bootstrap }, {}, start.runtimeMount);
|
|
2609
|
-
runtime.runPython(pythonRuntimeRelocation);
|
|
2610
|
-
filesystem.currentPath = start.invocation.cwd;
|
|
2611
|
-
native = createPythonNativeSyscalls({
|
|
2612
|
-
getUmask: () => guestMask,
|
|
2613
|
-
runtime: runtime._module,
|
|
2614
|
-
cwd: start.invocation.cwd,
|
|
2615
|
-
runtimeMount: start.runtimeMount,
|
|
2616
|
-
maxTransferBytes: start.maxTransferBytes,
|
|
2617
|
-
signal,
|
|
2618
|
-
dispatch: start.dispatch,
|
|
2619
|
-
original: (name, args) => originals[name](...args)
|
|
2620
|
-
});
|
|
2621
|
-
const errno = JSON.parse(runtime.runPython("__import__('json').dumps({name:value for name,value in vars(__import__('errno')).items() if name.startswith('E') and isinstance(value,int)})"));
|
|
2622
|
-
const encode = async (operation) => {
|
|
2623
|
-
try {
|
|
2624
|
-
return JSON.stringify(await operation());
|
|
2625
|
-
} catch (error) {
|
|
2626
|
-
return JSON.stringify({ errno: signal.aborted ? errno.EINTR : error.errno ?? errno[error.code ?? ""] ?? errno.EIO });
|
|
2627
|
-
}
|
|
2628
|
-
};
|
|
2629
|
-
const absolute = (path) => path.startsWith("/") ? path : filesystem.cwd() + "/" + path;
|
|
2630
|
-
const bootstrapPath = (path) => path === start.runtimeMount || path.startsWith(start.runtimeMount + "/");
|
|
2631
|
-
const module = runtime._module;
|
|
2632
|
-
const send = async (pointer) => {
|
|
2633
|
-
let response = await encode(async () => {
|
|
2634
|
-
const payload = JSON.parse(module.UTF8ToString(pointer));
|
|
2635
|
-
if (payload[0] === "host") {
|
|
2636
|
-
try {
|
|
2637
|
-
if (!start.host || !acceptingHostCalls) throw new Error("Python host capability unavailable");
|
|
2638
|
-
return { value: await start.host.request(payload[1]) };
|
|
2639
|
-
} catch (error) {
|
|
2640
|
-
return { errorCode: pythonHostFailureCode(error), error: signal.aborted ? "Python host operation cancelled" : "Python host operation failed" };
|
|
2641
|
-
}
|
|
2642
|
-
}
|
|
2643
|
-
signal.throwIfAborted();
|
|
2644
|
-
const [operation, path, follow] = payload;
|
|
2645
|
-
if (operation === "stat") return native.metadata(path, follow);
|
|
2646
|
-
if (typeof path !== "string") throw Object.assign(new Error("Invalid native Python path"), { code: "EINVAL" });
|
|
2647
|
-
const target = absolute(path);
|
|
2648
|
-
if (operation === "directory") {
|
|
2649
|
-
const entries = bootstrapPath(target) ? filesystem.readdir(target).filter((name) => name !== "." && name !== "..") : (await start.dispatch({ op: "readdir", args: [target] })).map((entry) => entry.name);
|
|
2650
|
-
return { entries };
|
|
2651
|
-
}
|
|
2652
|
-
if (operation === "tree") {
|
|
2653
|
-
if (bootstrapPath(target) || !await start.dispatch({ op: "rmtreeSupported", args: [target] })) return { supported: false };
|
|
2654
|
-
try {
|
|
2655
|
-
await start.dispatch({ op: "rmtree", args: [target] });
|
|
2656
|
-
return { supported: true };
|
|
2657
|
-
} catch (error) {
|
|
2658
|
-
return { supported: true, errno: errno[error.code ?? ""] ?? errno.EIO };
|
|
2659
|
-
}
|
|
2660
|
-
}
|
|
2661
|
-
throw Object.assign(new Error("Invalid native Python operation"), { code: "EINVAL" });
|
|
2662
|
-
});
|
|
2663
|
-
if (response.length > 1048576) response = JSON.stringify({ errno: errno.EFBIG });
|
|
2664
|
-
let bytes = new TextEncoder().encode(response);
|
|
2665
|
-
if (bytes.length > 1048576) bytes = new TextEncoder().encode(JSON.stringify({ errno: errno.EFBIG }));
|
|
2666
|
-
const output = module._malloc(bytes.length + 1);
|
|
2667
|
-
if (!output) return 0;
|
|
2668
|
-
module.HEAPU8.set(bytes, output);
|
|
2669
|
-
module.HEAPU8[output + bytes.length] = 0;
|
|
2670
|
-
return output;
|
|
2671
|
-
};
|
|
2672
|
-
const nativeCall = new engine.Instance(options.nativeCall, {
|
|
2673
|
-
python: {
|
|
2674
|
-
utf8: module._PyUnicode_AsUTF8,
|
|
2675
|
-
unicode: module._PyUnicode_FromString,
|
|
2676
|
-
free: module._free,
|
|
2677
|
-
noMemory: module._PyErr_NoMemory
|
|
2678
|
-
},
|
|
2679
|
-
request: { send },
|
|
2680
|
-
shutdown: { send: new engine.Suspending(send) },
|
|
2681
|
-
control: { active, syncify }
|
|
2682
|
-
});
|
|
2683
|
-
methodPointer = module.addFunction(nativeCall.exports.call, "iii");
|
|
2684
|
-
methodDefinition = module._malloc(128);
|
|
2685
|
-
if (!methodDefinition) throw new PythonFailure("runtime-assets");
|
|
2686
|
-
module.HEAPU8.fill(0, methodDefinition, methodDefinition + 128);
|
|
2687
|
-
module.HEAPU8.set(new TextEncoder().encode("request\0_safe_native_fs\0"), methodDefinition + 16);
|
|
2688
|
-
module.HEAPU32.set([methodDefinition + 16, methodPointer, 8, 0], methodDefinition / 4);
|
|
2689
|
-
const extension = module._PyImport_AddModule(methodDefinition + 24);
|
|
2690
|
-
const callable = module._PyCFunction_NewEx(methodDefinition, 0, 0);
|
|
2691
|
-
if (!extension || !callable || module._PyModule_AddObject(extension, methodDefinition + 16, callable) < 0) throw new PythonFailure("runtime-abi");
|
|
2692
|
-
const fields = Number(runtime.runPython("os.stat_result.n_fields"));
|
|
2693
|
-
const statResult = new engine.Instance(options.statResult, { python: {
|
|
2694
|
-
fields: new engine.Global({ value: "i32" }, fields),
|
|
2695
|
-
create: module._PyStructSequence_New,
|
|
2696
|
-
size: module._PyTuple_Size,
|
|
2697
|
-
item: module._PyTuple_GetItem,
|
|
2698
|
-
retain: module._Py_IncRef,
|
|
2699
|
-
set: module._PyStructSequence_SetItem,
|
|
2700
|
-
invalid: module._PyErr_BadArgument
|
|
2701
|
-
} });
|
|
2702
|
-
statPointer = module.addFunction(statResult.exports.construct, "iii");
|
|
2703
|
-
module.HEAPU8.set(new TextEncoder().encode("stat_result\0os\0"), methodDefinition + 80);
|
|
2704
|
-
module.HEAPU32.set([methodDefinition + 80, statPointer, 8, 0], (methodDefinition + 64) / 4);
|
|
2705
|
-
const osModule = module._PyImport_AddModule(methodDefinition + 92);
|
|
2706
|
-
const statType = module._PyObject_GetAttrString(osModule, methodDefinition + 80);
|
|
2707
|
-
const constructStat = module._PyCFunction_NewEx(methodDefinition + 64, statType, 0);
|
|
2708
|
-
module._Py_DecRef(statType);
|
|
2709
|
-
if (!constructStat || module._PyModule_AddObject(extension, methodDefinition + 80, constructStat) < 0) throw new PythonFailure("runtime-abi");
|
|
2710
|
-
runtime.runPython(`
|
|
2711
|
-
from _safe_native_fs import request as _safe_native_request
|
|
2712
|
-
def _safe_stat_projection(path, follow):
|
|
2713
|
-
return _safe_native_request(json.dumps(['stat', path, follow]))
|
|
2714
|
-
def _safe_import_stat(path):
|
|
2715
|
-
return _safe_stat_projection(path, True)
|
|
2716
|
-
def _safe_directory_entries(path):
|
|
2717
|
-
return _safe_native_request(json.dumps(['directory', path]))
|
|
2718
|
-
def _safe_tree_cleanup(path):
|
|
2719
|
-
return _safe_native_request(json.dumps(['tree', path]))
|
|
2720
|
-
`);
|
|
2721
|
-
for (const script of [pythonImportMetadata, pythonDirectoryEntries, pythonStatProjection, pythonTreeCleanup]) runtime.runPython(script);
|
|
2722
|
-
runtime.runPython(`
|
|
2723
|
-
from _safe_native_fs import stat_result as _safe_construct_stat
|
|
2724
|
-
_safe_stat_fields = os.stat_result.n_fields
|
|
2725
|
-
_safe_stat_probe = os.stat_result(tuple(range(_safe_stat_fields)))
|
|
2726
|
-
_safe_stat_indexes = {name: getattr(_safe_stat_probe, name) for name in dir(_safe_stat_probe) if name.startswith('st_')}
|
|
2727
|
-
def _safe_native_stat_type(values, extras):
|
|
2728
|
-
fields = list(values) + [None] * (_safe_stat_fields - len(values))
|
|
2729
|
-
for name, value in extras.items():
|
|
2730
|
-
if name in _safe_stat_indexes:
|
|
2731
|
-
fields[_safe_stat_indexes[name]] = value
|
|
2732
|
-
return _safe_construct_stat(tuple(fields))
|
|
2733
|
-
_safe_stat_type = _safe_native_stat_type
|
|
2734
|
-
`);
|
|
2735
|
-
runtime.runPython(pythonHostModule);
|
|
2736
|
-
installPythonLlmModule(runtime);
|
|
2737
|
-
installPythonShellModule(runtime);
|
|
2738
|
-
installPythonJspiRunSync(runtime);
|
|
2739
|
-
runtime.runPython(pythonLibraryAdapter);
|
|
2740
|
-
runtime.globals.set("_safe_invocation_json", JSON.stringify(start.invocation));
|
|
2741
|
-
runtime.globals.set("_safe_execution_code", pythonExecution);
|
|
2742
|
-
runtime.globals.set("_safe_is_cancelled", () => signal.aborted);
|
|
2743
|
-
active.value = 1;
|
|
2744
|
-
start.onReady();
|
|
2745
|
-
exitCode = Number(await runtime.runPythonAsync(`
|
|
2746
|
-
try:
|
|
2747
|
-
exec(_safe_execution_code)
|
|
2748
|
-
except BaseException:
|
|
2749
|
-
if not _safe_is_cancelled():
|
|
2750
|
-
raise
|
|
2751
|
-
_safe_exit = 130
|
|
2752
|
-
_safe_exit
|
|
2753
|
-
`)) & 255;
|
|
2754
|
-
} finally {
|
|
2755
|
-
try {
|
|
2756
|
-
if (qualified) {
|
|
2757
|
-
acceptingHostCalls = false;
|
|
2758
|
-
try {
|
|
2759
|
-
try {
|
|
2760
|
-
await start.host?.close();
|
|
2761
|
-
} catch (error) {
|
|
2762
|
-
if (!signal.aborted) hostCleanupFailure = { reason: error };
|
|
2763
|
-
}
|
|
2764
|
-
} finally {
|
|
2765
|
-
try {
|
|
2766
|
-
await runtime.runPythonAsync(`
|
|
2767
|
-
import asyncio as _safe_asyncio
|
|
2768
|
-
async def _safe_quiesce_tasks():
|
|
2769
|
-
current = _safe_asyncio.current_task()
|
|
2770
|
-
while True:
|
|
2771
|
-
pending = [task for task in _safe_asyncio.all_tasks() if task is not current]
|
|
2772
|
-
if not pending:
|
|
2773
|
-
break
|
|
2774
|
-
for task in pending:
|
|
2775
|
-
task.cancel()
|
|
2776
|
-
await _safe_asyncio.gather(*pending, return_exceptions=True)
|
|
2777
|
-
loop = _safe_asyncio.get_running_loop()
|
|
2778
|
-
await loop.shutdown_asyncgens()
|
|
2779
|
-
await loop.shutdown_default_executor()
|
|
2780
|
-
await _safe_quiesce_tasks()
|
|
2781
|
-
`);
|
|
2782
|
-
} finally {
|
|
2783
|
-
try {
|
|
2784
|
-
await scheduler.close();
|
|
2785
|
-
} finally {
|
|
2786
|
-
active.value = native ? 2 : 0;
|
|
2787
|
-
finalized = Number(await engine.promising(runtime._module._Py_FinalizeEx)());
|
|
2788
|
-
}
|
|
2789
|
-
}
|
|
2790
|
-
}
|
|
2791
|
-
}
|
|
2792
|
-
} finally {
|
|
2793
|
-
active.value = 0;
|
|
2794
|
-
schedulerCleanup = scheduler.close();
|
|
2795
|
-
cleanup = schedulerCleanup.finally(() => native?.close());
|
|
2796
|
-
try {
|
|
2797
|
-
await cleanup;
|
|
2798
|
-
} finally {
|
|
2799
|
-
if (methodDefinition) runtime?._module._free(methodDefinition);
|
|
2800
|
-
if (methodPointer !== void 0) runtime?._module.removeFunction(methodPointer);
|
|
2801
|
-
if (statPointer !== void 0) runtime?._module.removeFunction(statPointer);
|
|
2802
|
-
runtime = void 0;
|
|
2803
|
-
native = void 0;
|
|
2804
|
-
syncify.set(0, null);
|
|
2805
|
-
for (const name of Object.keys(originals)) delete originals[name];
|
|
2806
|
-
}
|
|
2807
|
-
}
|
|
2808
|
-
}
|
|
2809
|
-
if (hostCleanupFailure) throw hostCleanupFailure.reason;
|
|
2810
|
-
signal.throwIfAborted();
|
|
2811
|
-
return finalized < 0 ? 120 : exitCode;
|
|
2812
|
-
};
|
|
2813
|
-
return {
|
|
2814
|
-
run(start) {
|
|
2815
|
-
if (admitted || retirement) return Promise.reject(new PythonFailure("executor-unavailable"));
|
|
2816
|
-
admitted = true;
|
|
2817
|
-
running = execute(start);
|
|
2818
|
-
return running;
|
|
2819
|
-
},
|
|
2820
|
-
terminate() {
|
|
2821
|
-
retirement ??= (async () => {
|
|
2822
|
-
controller.abort();
|
|
2823
|
-
await running?.catch(() => {
|
|
2824
|
-
});
|
|
2825
|
-
await cleanup?.catch(() => {
|
|
2826
|
-
});
|
|
2827
|
-
await schedulerCleanup;
|
|
2828
|
-
})();
|
|
2829
|
-
return retirement;
|
|
2830
|
-
}
|
|
2831
|
-
};
|
|
2832
|
-
}
|
|
2833
|
-
|
|
2834
|
-
// packages/safe-bash/src/commands/python/jspi-assets.ts
|
|
2835
|
-
init_platform();
|
|
2836
|
-
function createPythonJspiAssets(options) {
|
|
2837
|
-
const engine = globalThis.WebAssembly;
|
|
2838
|
-
const main = options.main;
|
|
2839
|
-
const registry = options.modules.map((entry) => ({ module: entry.module, bytes: Uint8Array.from(entry.bytes) }));
|
|
2840
|
-
if (![main, ...registry.map((entry) => entry.module)].every((module) => module instanceof engine.Module)) throw new TypeError("Expected precompiled Python Wasm modules");
|
|
2841
|
-
const stdlib = Uint8Array.from(options.stdlib);
|
|
2842
|
-
const responses = /* @__PURE__ */ new WeakMap();
|
|
2843
|
-
const approved = /* @__PURE__ */ new Set([main, ...registry.map((entry) => entry.module)]);
|
|
2844
|
-
const select = (source) => {
|
|
2845
|
-
const bytes = ArrayBuffer.isView(source) ? new Uint8Array(source.buffer, source.byteOffset, source.byteLength) : new Uint8Array(source);
|
|
2846
|
-
const entry = registry.find((candidate) => candidate.bytes.length === bytes.length && candidate.bytes.every((byte, index) => byte === bytes[index]));
|
|
2847
|
-
if (!entry) throw new engine.CompileError("Unapproved Python runtime Wasm bytes");
|
|
2848
|
-
return entry.module;
|
|
2849
|
-
};
|
|
2850
|
-
const WebAssembly = Object.create(engine);
|
|
2851
|
-
WebAssembly.Module = function(source) {
|
|
2852
|
-
return select(source);
|
|
2853
|
-
};
|
|
2854
|
-
Object.setPrototypeOf(WebAssembly.Module, engine.Module);
|
|
2855
|
-
WebAssembly.Module.prototype = engine.Module.prototype;
|
|
2856
|
-
WebAssembly.compile = async (source) => select(source);
|
|
2857
|
-
WebAssembly.compileStreaming = async () => {
|
|
2858
|
-
throw new engine.CompileError("Unapproved Python streaming compilation");
|
|
2859
|
-
};
|
|
2860
|
-
WebAssembly.instantiate = async (source, imports) => {
|
|
2861
|
-
if (source instanceof engine.Module) {
|
|
2862
|
-
if (!approved.has(source)) throw new engine.CompileError("Unapproved Python Wasm module");
|
|
2863
|
-
return new engine.Instance(source, imports);
|
|
2864
|
-
}
|
|
2865
|
-
const module = select(source);
|
|
2866
|
-
return { module, instance: new engine.Instance(module, imports) };
|
|
2867
|
-
};
|
|
2868
|
-
WebAssembly.instantiateStreaming = async (response, imports) => {
|
|
2869
|
-
const module = responses.get(await response);
|
|
2870
|
-
if (!module) throw new engine.CompileError("Unapproved Python Wasm response");
|
|
2871
|
-
return { module, instance: new engine.Instance(module, imports) };
|
|
2872
|
-
};
|
|
2873
|
-
const location = "https://safe-python.invalid/";
|
|
2874
|
-
return { WebAssembly, location, async fetch(url) {
|
|
2875
|
-
const target = new URL(url, location).href;
|
|
2876
|
-
if (target === location + "python_stdlib.zip") return new Response(stdlib);
|
|
2877
|
-
if (target === location + "pyodide.asm.wasm") {
|
|
2878
|
-
const response = new Response(null, { headers: { "Content-Type": "application/wasm" } });
|
|
2879
|
-
responses.set(response, main);
|
|
2880
|
-
return response;
|
|
2881
|
-
}
|
|
2882
|
-
throw new Error("Unapproved Python runtime asset");
|
|
2883
|
-
} };
|
|
2884
|
-
}
|
|
2885
|
-
|
|
2886
|
-
// packages/safe-bash/src/commands/python/index.ts
|
|
2887
|
-
var PythonInputChunkError = class extends RangeError {
|
|
2888
|
-
};
|
|
2889
|
-
function createPythonCommands(options) {
|
|
2890
|
-
if (!options || typeof options.createWorker === "function" === (typeof options.createExecutor === "function") || options.createWorker !== void 0 && typeof options.createWorker !== "function" || options.createExecutor !== void 0 && typeof options.createExecutor !== "function") throw new PythonFailure("executor-unavailable");
|
|
2891
|
-
if (options.createCapabilities && !options.createExecutor) throw new TypeError("Python host capabilities require an asynchronous executor");
|
|
2892
|
-
if (options.onDiagnostic !== void 0 && typeof options.onDiagnostic !== "function") throw new TypeError("Python onDiagnostic must be a function");
|
|
2893
|
-
if (options.environment && options.provisioning) throw new TypeError("A borrowed Python environment cannot be combined with provisioning options");
|
|
2894
|
-
const maxTransferBytes = options.maxTransferBytes ?? 65536;
|
|
2895
|
-
const maxOpenFiles = options.maxOpenFiles;
|
|
2896
|
-
const maxConcurrentWorkers = options.maxConcurrentWorkers ?? Infinity;
|
|
2897
|
-
const maxInputChunkBytes = options.maxInputChunkBytes ?? Infinity;
|
|
2898
|
-
if (options.maxConcurrentWorkers !== void 0 && (!Number.isSafeInteger(maxConcurrentWorkers) || maxConcurrentWorkers < 1)) throw new RangeError("Invalid Python worker concurrency limit");
|
|
2899
|
-
if (options.maxInputChunkBytes !== void 0 && (!Number.isSafeInteger(maxInputChunkBytes) || maxInputChunkBytes < 1)) throw new RangeError("Invalid Python input chunk limit");
|
|
2900
|
-
let activeWorkers = 0;
|
|
2901
|
-
for (const size of [maxTransferBytes, maxOpenFiles, options.maxDirectoryEntries]) if (size !== void 0 && (!Number.isSafeInteger(size) || size < 1)) throw new RangeError("Invalid Python resource limit");
|
|
2902
|
-
const runtimeMount = options.runtimeMount ?? "/.pyodide-runtime";
|
|
2903
|
-
if (!runtimeMount.startsWith("/") || runtimeMount === "/" || runtimeMount.slice(1).includes("/") || runtimeMount.includes("\0") || runtimeMount.split("/").some((part) => part === ".." || part === ".")) throw new TypeError("Python runtime mount must be an absolute top-level canonical path");
|
|
2904
|
-
const environment = options.environment ?? createPythonPackageEnvironment(options.provisioning);
|
|
2905
|
-
const execute = async (context) => {
|
|
2906
|
-
if (pythonShellDispatchActive(context.executionScope)) {
|
|
2907
|
-
await (0, io_exports.writeBytes)(context.stderr, new TextEncoder().encode("python: nested Python execution is unavailable while the parent interpreter is suspended\n"), context.signal);
|
|
2908
|
-
return { exitCode: 1 };
|
|
2909
|
-
}
|
|
2910
|
-
let installation;
|
|
2911
|
-
try {
|
|
2912
|
-
parsePythonInvocation(context.args, context.env);
|
|
2913
|
-
installation = parsePythonInstallation(context.args);
|
|
2914
|
-
} catch (error) {
|
|
2915
|
-
if (!(error instanceof PythonInvocationError)) throw error;
|
|
2916
|
-
await (0, io_exports.writeBytes)(context.stderr, new TextEncoder().encode("python: " + error.message + "\n"), context.signal);
|
|
2917
|
-
return { exitCode: 2 };
|
|
2918
|
-
}
|
|
2919
|
-
if (installation?.help) {
|
|
2920
|
-
await (0, io_exports.writeBytes)(context.stdout, new TextEncoder().encode(pythonInstallationHelp), context.signal);
|
|
2921
|
-
return { exitCode: 0 };
|
|
2922
|
-
}
|
|
2923
|
-
context.signal.throwIfAborted();
|
|
2924
|
-
if (activeWorkers >= maxConcurrentWorkers) {
|
|
2925
|
-
const failure2 = reportPythonFailure("capacity", void 0, options.onDiagnostic);
|
|
2926
|
-
await (0, io_exports.writeBytes)(context.stderr, new TextEncoder().encode("python: " + failure2.message + "\n"), context.signal);
|
|
2927
|
-
return { exitCode: 1 };
|
|
2928
|
-
}
|
|
2929
|
-
const controller = new AbortController();
|
|
2930
|
-
const signal = AbortSignal.any([context.signal, controller.signal]);
|
|
2931
|
-
(0, yield_exports.inheritYieldCheckpoint)(context.signal, signal);
|
|
2932
|
-
const fileContext = { ...context, descriptorCleanup: "caller" };
|
|
2933
|
-
const metadata = new PythonStatTranslator();
|
|
2934
|
-
const service = new PythonFileSystem(context.fs, {
|
|
2935
|
-
cwd: context.cwd,
|
|
2936
|
-
signal,
|
|
2937
|
-
maxTransferBytes,
|
|
2938
|
-
...maxOpenFiles === void 0 ? {} : { maxOpenFiles },
|
|
2939
|
-
...options.maxDirectoryEntries === void 0 ? {} : { maxDirectoryEntries: options.maxDirectoryEntries },
|
|
2940
|
-
open: (path, settings) => (0, filesystem_descriptor_exports.openCommandFile)(fileContext, path, settings)
|
|
2941
|
-
});
|
|
2942
|
-
let hostBridge;
|
|
2943
|
-
let stdoutOperation;
|
|
2944
|
-
let stderrOperation;
|
|
2945
|
-
let endpoint;
|
|
2946
|
-
let executor;
|
|
2947
|
-
let packages;
|
|
2948
|
-
let unsubscribe;
|
|
2949
|
-
let admitted = false;
|
|
2950
|
-
let closed = false;
|
|
2951
|
-
let closing;
|
|
2952
|
-
const pending = /* @__PURE__ */ new Set();
|
|
2953
|
-
const input = (0, io_exports.readBytes)(context.stdin, signal)[Symbol.asyncIterator]();
|
|
2954
|
-
let fragment;
|
|
2955
|
-
let offset = 0;
|
|
2956
|
-
let inputBytes = 0;
|
|
2957
|
-
let rejectRun;
|
|
2958
|
-
const close = () => {
|
|
2959
|
-
if (closing) return closing;
|
|
2960
|
-
let finish;
|
|
2961
|
-
let fail;
|
|
2962
|
-
closing = new Promise((resolve, reject) => {
|
|
2963
|
-
finish = resolve;
|
|
2964
|
-
fail = reject;
|
|
2965
|
-
});
|
|
2966
|
-
closed = true;
|
|
2967
|
-
controller.abort(new Error("Python invocation retired"));
|
|
2968
|
-
void (async () => {
|
|
2969
|
-
signal.removeEventListener("abort", aborted);
|
|
2970
|
-
const subscription = Promise.resolve().then(() => unsubscribe?.());
|
|
2971
|
-
const termination = Promise.resolve().then(() => executor ? executor.terminate() : endpoint?.terminate());
|
|
2972
|
-
const filesystemRetirement = termination.finally(() => service.close());
|
|
2973
|
-
const results = await Promise.allSettled([subscription, termination, filesystemRetirement, hostBridge?.close(), stdoutOperation?.close(), stderrOperation?.close(), ...pending]);
|
|
2974
|
-
await input.return?.(void 0);
|
|
2975
|
-
fragment = void 0;
|
|
2976
|
-
if (packages) environment.finish(packages);
|
|
2977
|
-
for (const result2 of results) if (result2.status === "rejected") throw result2.reason;
|
|
2978
|
-
})().then(() => {
|
|
2979
|
-
if (admitted) {
|
|
2980
|
-
activeWorkers--;
|
|
2981
|
-
admitted = false;
|
|
2982
|
-
}
|
|
2983
|
-
finish();
|
|
2984
|
-
}, (reason) => {
|
|
2985
|
-
fail(reportPythonFailure("cleanup", reason, options.onDiagnostic));
|
|
2986
|
-
});
|
|
2987
|
-
return closing;
|
|
2988
|
-
};
|
|
2989
|
-
const aborted = () => {
|
|
2990
|
-
rejectRun?.(signal.reason);
|
|
2991
|
-
};
|
|
2992
|
-
context.registerCleanup?.(close);
|
|
2993
|
-
let primary;
|
|
2994
|
-
let ready = false;
|
|
2995
|
-
const filesystemDiagnostics = /* @__PURE__ */ new Set();
|
|
2996
|
-
let result = 1;
|
|
2997
|
-
try {
|
|
2998
|
-
signal.throwIfAborted();
|
|
2999
|
-
activeWorkers++;
|
|
3000
|
-
admitted = true;
|
|
3001
|
-
const outputContext = { signal, ...context.registerCleanup ? { registerCleanup: context.registerCleanup } : {} };
|
|
3002
|
-
stdoutOperation = (0, output_exports.createOutputOperation)(outputContext, context.stdout);
|
|
3003
|
-
stderrOperation = (0, output_exports.createOutputOperation)(outputContext, context.stderr);
|
|
3004
|
-
const diagnoseFilesystemFailure = async (operation, error) => {
|
|
3005
|
-
if (typeof error !== "object" || error === null || !("code" in error) || error.code !== "ENOTSUP") return;
|
|
3006
|
-
const category = operation === "open" ? "filesystem-open" : operation === "read" ? "filesystem-read" : operation === "write" || operation === "ftruncate" ? "filesystem-write" : operation === "readdir" ? "filesystem-directory" : "filesystem-operation";
|
|
3007
|
-
if (!filesystemDiagnostics.has(category)) {
|
|
3008
|
-
filesystemDiagnostics.add(category);
|
|
3009
|
-
const failure2 = reportPythonFailure(category, error, options.onDiagnostic);
|
|
3010
|
-
await (0, io_exports.writeBytes)(stderrOperation.output, new TextEncoder().encode("python: " + failure2.message + "\n"), signal);
|
|
3011
|
-
}
|
|
3012
|
-
};
|
|
3013
|
-
const dispatch = async (request) => {
|
|
3014
|
-
if (request.op === "close") return service.dispatch(request);
|
|
3015
|
-
signal.throwIfAborted();
|
|
3016
|
-
if (request.op.startsWith("package-")) return environment.dispatch(request.op, request.args, { fs: context.fs, cwd: context.cwd, signal });
|
|
3017
|
-
if (request.op === "stdin") {
|
|
3018
|
-
const length = request.args[0];
|
|
3019
|
-
if (!Number.isSafeInteger(length) || length < 1 || length > maxTransferBytes) throw new RangeError("Invalid Python input request");
|
|
3020
|
-
let pulls = 0;
|
|
3021
|
-
while (!fragment || offset === fragment.length) {
|
|
3022
|
-
if (++pulls % 128 === 0) await (0, yield_exports.yieldTurn)(signal);
|
|
3023
|
-
const next = await input.next();
|
|
3024
|
-
if (next.done) return [];
|
|
3025
|
-
inputBytes += next.value.length;
|
|
3026
|
-
context.inputBudget?.check(inputBytes);
|
|
3027
|
-
if (next.value.length > maxInputChunkBytes) throw new PythonInputChunkError("Python input chunk exceeds maxInputChunkBytes");
|
|
3028
|
-
fragment = Uint8Array.from(next.value);
|
|
3029
|
-
offset = 0;
|
|
3030
|
-
}
|
|
3031
|
-
const end = Math.min(fragment.length, offset + length);
|
|
3032
|
-
const bytes = Array.from(fragment.subarray(offset, end));
|
|
3033
|
-
offset = end;
|
|
3034
|
-
return bytes;
|
|
3035
|
-
}
|
|
3036
|
-
if (request.op === "stdout" || request.op === "stderr") {
|
|
3037
|
-
const bytes = request.args[0];
|
|
3038
|
-
if (!Array.isArray(bytes) || bytes.length > maxTransferBytes || bytes.some((value2) => !Number.isInteger(value2) || value2 < 0 || value2 > 255)) throw new TypeError("Invalid Python output bytes");
|
|
3039
|
-
const operation = request.op === "stdout" ? stdoutOperation : stderrOperation;
|
|
3040
|
-
await (0, io_exports.writeBytes)(operation.output, Uint8Array.from(bytes), signal);
|
|
3041
|
-
return bytes.length;
|
|
3042
|
-
}
|
|
3043
|
-
const value = await service.dispatch(request);
|
|
3044
|
-
return request.op === "stat" || request.op === "lstat" || request.op === "fstat" ? metadata.translate(value) : value;
|
|
3045
|
-
};
|
|
3046
|
-
if (options.createCapabilities) {
|
|
3047
|
-
const capabilityInput = { async *[Symbol.asyncIterator]() {
|
|
3048
|
-
while (true) {
|
|
3049
|
-
const bytes = await dispatch({ op: "stdin", args: [maxTransferBytes] });
|
|
3050
|
-
if (!bytes.length) break;
|
|
3051
|
-
yield Uint8Array.from(bytes);
|
|
3052
|
-
}
|
|
3053
|
-
} };
|
|
3054
|
-
hostBridge = createPythonHostBridge(options.createCapabilities({ ...context, stdin: capabilityInput, signal }), { ...options.capabilityLimits, signal });
|
|
3055
|
-
}
|
|
3056
|
-
options.onProgress?.({ phase: "initializing", command: context.command });
|
|
3057
|
-
const preparation = environment.prepare({
|
|
3058
|
-
fs: context.fs,
|
|
3059
|
-
cwd: context.cwd,
|
|
3060
|
-
signal,
|
|
3061
|
-
requirements: [...options.packages ?? [], ...options.packageProfile ? pythonDocumentPackages : [], ...installation?.packages ?? []],
|
|
3062
|
-
requirementFiles: [...options.requirements ?? [], ...installation?.requirements ?? []]
|
|
3063
|
-
}).then((value) => {
|
|
3064
|
-
packages = value;
|
|
3065
|
-
});
|
|
3066
|
-
pending.add(preparation);
|
|
3067
|
-
try {
|
|
3068
|
-
await preparation;
|
|
3069
|
-
} finally {
|
|
3070
|
-
pending.delete(preparation);
|
|
3071
|
-
}
|
|
3072
|
-
signal.throwIfAborted();
|
|
3073
|
-
if (options.createExecutor) {
|
|
3074
|
-
try {
|
|
3075
|
-
executor = options.createExecutor();
|
|
3076
|
-
} catch (reason) {
|
|
3077
|
-
throw reportPythonFailure(reason instanceof PythonFailure ? reason.category : "startup", reason, options.onDiagnostic);
|
|
3078
|
-
}
|
|
3079
|
-
if (!executor || typeof executor.run !== "function" || typeof executor.terminate !== "function") throw reportPythonFailure("executor-unavailable", void 0, options.onDiagnostic);
|
|
3080
|
-
let requesting = false;
|
|
3081
|
-
let running = true;
|
|
3082
|
-
const start = {
|
|
3083
|
-
invocation: { command: context.command, args: [...context.args], cwd: context.cwd, env: { ...context.env } },
|
|
3084
|
-
...hostBridge ? { host: hostBridge } : {},
|
|
3085
|
-
runtimeMount,
|
|
3086
|
-
maxTransferBytes,
|
|
3087
|
-
signal,
|
|
3088
|
-
...packages ? { packages } : {},
|
|
3089
|
-
installOnly: !!installation,
|
|
3090
|
-
onReady() {
|
|
3091
|
-
if (closed || !running || signal.aborted || ready) return;
|
|
3092
|
-
ready = true;
|
|
3093
|
-
try {
|
|
3094
|
-
options.onProgress?.({ phase: "ready", command: context.command });
|
|
3095
|
-
} catch (reason) {
|
|
3096
|
-
running = false;
|
|
3097
|
-
rejectRun?.(reason);
|
|
3098
|
-
throw reason;
|
|
3099
|
-
}
|
|
3100
|
-
},
|
|
3101
|
-
async dispatch(request) {
|
|
3102
|
-
if (!request || typeof request.op !== "string" || !Array.isArray(request.args)) throw reportPythonFailure("transport-unavailable", void 0, options.onDiagnostic);
|
|
3103
|
-
const releasing = request.op === "close";
|
|
3104
|
-
if (!releasing) signal.throwIfAborted();
|
|
3105
|
-
if (closed && !releasing || !running || requesting) throw reportPythonFailure("transport-unavailable", void 0, options.onDiagnostic);
|
|
3106
|
-
requesting = true;
|
|
3107
|
-
const operation = Promise.resolve().then(() => dispatch(request)).catch(async (error) => {
|
|
3108
|
-
if (releasing) throw error;
|
|
3109
|
-
signal.throwIfAborted();
|
|
3110
|
-
if (error instanceof PythonInputChunkError) rejectRun?.(error);
|
|
3111
|
-
if (request.op.startsWith("package-")) throw Object.assign(reportPythonFailure("runtime-assets", error, options.onDiagnostic), { code: "EPACKAGE" });
|
|
3112
|
-
await diagnoseFilesystemFailure(request.op, error);
|
|
3113
|
-
throw error;
|
|
3114
|
-
});
|
|
3115
|
-
const tracked2 = operation.then(() => {
|
|
3116
|
-
}, () => {
|
|
3117
|
-
});
|
|
3118
|
-
pending.add(tracked2);
|
|
3119
|
-
try {
|
|
3120
|
-
return await operation;
|
|
3121
|
-
} finally {
|
|
3122
|
-
requesting = false;
|
|
3123
|
-
pending.delete(tracked2);
|
|
3124
|
-
}
|
|
3125
|
-
}
|
|
3126
|
-
};
|
|
3127
|
-
const execution = Promise.resolve().then(() => {
|
|
3128
|
-
signal.throwIfAborted();
|
|
3129
|
-
return executor.run(start);
|
|
3130
|
-
}).catch((reason) => {
|
|
3131
|
-
running = false;
|
|
3132
|
-
if (signal.aborted) throw signal.reason;
|
|
3133
|
-
throw reportPythonFailure(reason instanceof PythonFailure ? reason.category : ready ? "runtime" : "startup", reason, options.onDiagnostic);
|
|
3134
|
-
}).then((status) => {
|
|
3135
|
-
running = false;
|
|
3136
|
-
if (requesting) throw reportPythonFailure("transport-unavailable", void 0, options.onDiagnostic);
|
|
3137
|
-
try {
|
|
3138
|
-
return (0, command_exports.validateExitCode)(status);
|
|
3139
|
-
} catch (reason) {
|
|
3140
|
-
throw reportPythonFailure("transport-unavailable", reason, options.onDiagnostic);
|
|
3141
|
-
}
|
|
3142
|
-
});
|
|
3143
|
-
const tracked = execution.then(() => {
|
|
3144
|
-
}, () => {
|
|
3145
|
-
});
|
|
3146
|
-
pending.add(tracked);
|
|
3147
|
-
void tracked.then(() => pending.delete(tracked));
|
|
3148
|
-
result = await new Promise((resolve, reject) => {
|
|
3149
|
-
rejectRun = reject;
|
|
3150
|
-
signal.addEventListener("abort", aborted, { once: true });
|
|
3151
|
-
if (signal.aborted) aborted();
|
|
3152
|
-
execution.then(resolve, reject);
|
|
3153
|
-
});
|
|
3154
|
-
} else {
|
|
3155
|
-
let shared;
|
|
3156
|
-
try {
|
|
3157
|
-
shared = new SharedArrayBuffer(8 + maxTransferBytes * 6 + 65536);
|
|
3158
|
-
} catch (reason) {
|
|
3159
|
-
throw reportPythonFailure("transport-unavailable", reason, options.onDiagnostic);
|
|
3160
|
-
}
|
|
3161
|
-
const control = new Int32Array(shared, 0, 2);
|
|
3162
|
-
const payload = new Uint8Array(shared, 8);
|
|
3163
|
-
const reply = (value, status) => {
|
|
3164
|
-
if (closed) return;
|
|
3165
|
-
let bytes;
|
|
3166
|
-
try {
|
|
3167
|
-
bytes = encodePythonReply(value, payload.length);
|
|
3168
|
-
} catch (error) {
|
|
3169
|
-
if (typeof error !== "object" || error === null || !("code" in error) || error.code !== "EFBIG") throw error;
|
|
3170
|
-
bytes = encodePythonReply({ code: "EFBIG" }, payload.length);
|
|
3171
|
-
status = 2;
|
|
3172
|
-
}
|
|
3173
|
-
payload.set(bytes);
|
|
3174
|
-
try {
|
|
3175
|
-
Atomics.store(control, 1, bytes.length);
|
|
3176
|
-
Atomics.store(control, 0, status);
|
|
3177
|
-
Atomics.notify(control, 0);
|
|
3178
|
-
} catch (reason) {
|
|
3179
|
-
throw reportPythonFailure("transport-unavailable", reason, options.onDiagnostic);
|
|
3180
|
-
}
|
|
3181
|
-
};
|
|
3182
|
-
try {
|
|
3183
|
-
endpoint = options.createWorker();
|
|
3184
|
-
} catch (reason) {
|
|
3185
|
-
throw reportPythonFailure(reason instanceof PythonFailure ? reason.category : "startup", reason, options.onDiagnostic);
|
|
3186
|
-
}
|
|
3187
|
-
signal.throwIfAborted();
|
|
3188
|
-
result = await new Promise((resolve, reject) => {
|
|
3189
|
-
let settled = false;
|
|
3190
|
-
const fail = (reason) => {
|
|
3191
|
-
settled = true;
|
|
3192
|
-
reject(reason);
|
|
3193
|
-
};
|
|
3194
|
-
rejectRun = fail;
|
|
3195
|
-
signal.addEventListener("abort", aborted, { once: true });
|
|
3196
|
-
const transportFailure = (reason) => {
|
|
3197
|
-
fail(reportPythonFailure("transport-unavailable", reason, options.onDiagnostic));
|
|
3198
|
-
};
|
|
3199
|
-
try {
|
|
3200
|
-
unsubscribe = endpoint.subscribe((value) => {
|
|
3201
|
-
if (closed || settled) return;
|
|
3202
|
-
if (typeof value !== "object" || value === null) {
|
|
3203
|
-
transportFailure(new TypeError("Invalid Python worker message"));
|
|
3204
|
-
return;
|
|
3205
|
-
}
|
|
3206
|
-
const message = value;
|
|
3207
|
-
if (message.type === "ready") {
|
|
3208
|
-
ready = true;
|
|
3209
|
-
try {
|
|
3210
|
-
options.onProgress?.({ phase: "ready", command: context.command });
|
|
3211
|
-
} catch (error) {
|
|
3212
|
-
fail(error);
|
|
3213
|
-
}
|
|
3214
|
-
return;
|
|
3215
|
-
}
|
|
3216
|
-
if (message.type === "exit") {
|
|
3217
|
-
if (pending.size) {
|
|
3218
|
-
transportFailure(new TypeError("Python worker exited with an outstanding request"));
|
|
3219
|
-
return;
|
|
3220
|
-
}
|
|
3221
|
-
try {
|
|
3222
|
-
const status = (0, command_exports.validateExitCode)(message.exitCode);
|
|
3223
|
-
settled = true;
|
|
3224
|
-
resolve(status);
|
|
3225
|
-
} catch (error) {
|
|
3226
|
-
transportFailure(error);
|
|
3227
|
-
}
|
|
3228
|
-
return;
|
|
3229
|
-
}
|
|
3230
|
-
if (message.type === "error") {
|
|
3231
|
-
fail(reportPythonFailure(isPythonFailureCategory(message.category) ? message.category : ready ? "runtime" : "startup", message.message, options.onDiagnostic));
|
|
3232
|
-
return;
|
|
3233
|
-
}
|
|
3234
|
-
if (typeof message.op !== "string" || !Array.isArray(message.args) || pending.size) {
|
|
3235
|
-
transportFailure(new TypeError("Invalid concurrent Python worker request"));
|
|
3236
|
-
return;
|
|
3237
|
-
}
|
|
3238
|
-
const work = dispatch({ op: message.op, args: message.args }).then(
|
|
3239
|
-
(value2) => {
|
|
3240
|
-
pending.delete(work);
|
|
3241
|
-
reply(value2, 1);
|
|
3242
|
-
},
|
|
3243
|
-
async (error) => {
|
|
3244
|
-
if (signal.aborted) {
|
|
3245
|
-
pending.delete(work);
|
|
3246
|
-
fail(signal.reason);
|
|
3247
|
-
return;
|
|
3248
|
-
}
|
|
3249
|
-
if (typeof message.op === "string" && message.op.startsWith("package-")) {
|
|
3250
|
-
const failure2 = reportPythonFailure("runtime-assets", error, options.onDiagnostic);
|
|
3251
|
-
pending.delete(work);
|
|
3252
|
-
reply({ code: "EPACKAGE", message: failure2.message }, 2);
|
|
3253
|
-
return;
|
|
3254
|
-
}
|
|
3255
|
-
if (typeof error !== "object" || error === null || !("code" in error) || typeof error.code !== "string") {
|
|
3256
|
-
pending.delete(work);
|
|
3257
|
-
fail(error);
|
|
3258
|
-
return;
|
|
3259
|
-
}
|
|
3260
|
-
await diagnoseFilesystemFailure(message.op, error);
|
|
3261
|
-
pending.delete(work);
|
|
3262
|
-
reply({ code: error.code }, 2);
|
|
3263
|
-
}
|
|
3264
|
-
);
|
|
3265
|
-
pending.add(work);
|
|
3266
|
-
void work.catch((reason) => {
|
|
3267
|
-
pending.delete(work);
|
|
3268
|
-
fail(reason);
|
|
3269
|
-
});
|
|
3270
|
-
}, transportFailure);
|
|
3271
|
-
} catch (reason) {
|
|
3272
|
-
transportFailure(reason);
|
|
3273
|
-
}
|
|
3274
|
-
if (settled) return;
|
|
3275
|
-
const start = { type: "start", shared, invocation: { command: context.command, args: [...context.args], cwd: context.cwd, env: { ...context.env } }, runtimeMount, maxTransferBytes, ...packages ? { packages } : {}, installOnly: !!installation };
|
|
3276
|
-
signal.throwIfAborted();
|
|
3277
|
-
try {
|
|
3278
|
-
endpoint.postMessage(start);
|
|
3279
|
-
} catch (reason) {
|
|
3280
|
-
transportFailure(reason);
|
|
3281
|
-
}
|
|
3282
|
-
});
|
|
3283
|
-
}
|
|
3284
|
-
} catch (reason) {
|
|
3285
|
-
if (!signal.aborted && reason instanceof PythonInputChunkError) {
|
|
3286
|
-
try {
|
|
3287
|
-
await (0, io_exports.writeBytes)(stderrOperation.output, new TextEncoder().encode("python: " + reason.message + "\n"), signal);
|
|
3288
|
-
} catch (error) {
|
|
3289
|
-
primary = { reason: error };
|
|
3290
|
-
}
|
|
3291
|
-
} else if (!signal.aborted && typeof reason === "object" && reason !== null && "code" in reason && reason.code === "EPACKAGE") {
|
|
3292
|
-
primary = { reason: reportPythonFailure("runtime-assets", reason, options.onDiagnostic) };
|
|
3293
|
-
} else primary = { reason };
|
|
3294
|
-
}
|
|
3295
|
-
try {
|
|
3296
|
-
await close();
|
|
3297
|
-
} catch (reason) {
|
|
3298
|
-
primary ??= { reason };
|
|
3299
|
-
}
|
|
3300
|
-
try {
|
|
3301
|
-
options.onProgress?.({ phase: "finished", command: context.command });
|
|
3302
|
-
} catch (reason) {
|
|
3303
|
-
primary ??= { reason };
|
|
3304
|
-
}
|
|
3305
|
-
context.signal.throwIfAborted();
|
|
3306
|
-
if (primary) {
|
|
3307
|
-
if (!(primary.reason instanceof PythonFailure)) throw primary.reason;
|
|
3308
|
-
const failure2 = primary.reason;
|
|
3309
|
-
await (0, io_exports.writeBytes)(context.stderr, new TextEncoder().encode("python: " + failure2.message + "\n"), context.signal);
|
|
3310
|
-
return { exitCode: 1 };
|
|
3311
|
-
}
|
|
3312
|
-
return { exitCode: result };
|
|
3313
|
-
};
|
|
3314
|
-
return ["python", "python3"].map((name) => ({ name, description: "Python with an explicit interpreter executor and canonical filesystem", execute }));
|
|
3315
|
-
}
|
|
3316
|
-
function pythonCommands(options) {
|
|
3317
|
-
if (!options || typeof options.createWorker === "function" === (typeof options.createExecutor === "function")) throw new PythonFailure("executor-unavailable");
|
|
3318
|
-
if (options?.environment && options.provisioning) throw new TypeError("A borrowed Python environment cannot be combined with provisioning options");
|
|
3319
|
-
const { provisioning, ...configuration } = options;
|
|
3320
|
-
const environment = options.environment ?? createPythonPackageEnvironment(provisioning);
|
|
3321
|
-
const commands = createPythonCommands({ ...configuration, environment });
|
|
3322
|
-
return { name: "python-commands", ...options.environment ? {} : { dispose: environment.dispose }, setup(host) {
|
|
3323
|
-
if (!options.replace) {
|
|
3324
|
-
for (const command of commands) if (host.commands.has(command.name)) throw new Error(`Command already registered: ${command.name}`);
|
|
3325
|
-
}
|
|
3326
|
-
for (const command of commands) host.commands.register(command, { replace: options.replace ?? false });
|
|
3327
|
-
} };
|
|
3328
|
-
}
|
|
3329
|
-
|
|
3330
|
-
export {
|
|
3331
|
-
filesystem_descriptor_exports,
|
|
3332
|
-
PythonPackageConflictError,
|
|
3333
|
-
createPythonPackageManifestStore,
|
|
3334
|
-
createPythonPackageCache,
|
|
3335
|
-
pythonDocumentPackages,
|
|
3336
|
-
createPythonPackageEnvironment,
|
|
3337
|
-
pythonHostFailureCode,
|
|
3338
|
-
createPythonHostBridge,
|
|
3339
|
-
createPythonShellCapability,
|
|
3340
|
-
createPythonLlmCapability,
|
|
3341
|
-
createPythonExecutorPool,
|
|
3342
|
-
createPythonJspiTrampoline,
|
|
3343
|
-
createPythonJspiNativeCall,
|
|
3344
|
-
createPythonJspiStatResult,
|
|
3345
|
-
createPythonJspiExecutor,
|
|
3346
|
-
createPythonJspiAssets,
|
|
3347
|
-
createPythonCommands,
|
|
3348
|
-
pythonCommands
|
|
3349
|
-
};
|
|
3350
|
-
//# sourceMappingURL=chunk-MJPFHANT.js.map
|