@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.
Files changed (131) hide show
  1. package/dist/image-ast/index.browser.js +30 -6
  2. package/dist/image-ast/index.js +30 -6
  3. package/dist/pdf-ast/canvas.d.ts +1 -0
  4. package/dist/pdf-ast/index.js +26 -2
  5. package/dist/safe-bash/chunks/{chunk-LESPJ3OG.js → chunk-WNWIBBJZ.js} +15 -2
  6. package/dist/safe-bash/command-chunks/{chunk-S4J6MSLY.js → chunk-444OTXM7.js} +3 -3
  7. package/dist/safe-bash/command-chunks/{chunk-6AIOGLZO.js → chunk-CE6Z2ULX.js} +2 -2
  8. package/dist/safe-bash/command-chunks/{chunk-5VSGEE6J.js → chunk-F4IQCXZG.js} +5 -5
  9. package/dist/safe-bash/command-chunks/{chunk-F5MORDSU.js → chunk-FDNCPZQS.js} +32 -1
  10. package/dist/safe-bash/command-chunks/{chunk-SG3ZDZCX.js → chunk-FLMLM7Y4.js} +2 -2
  11. package/dist/safe-bash/command-chunks/{chunk-XRVNFK7Z.js → chunk-H2FQA4OU.js} +26 -2
  12. package/dist/safe-bash/command-chunks/{chunk-CT3FB722.js → chunk-J7UMKBRK.js} +1 -1
  13. package/dist/safe-bash/command-chunks/{chunk-KB2LQ4HG.js → chunk-KBBI4CSU.js} +2 -2
  14. package/dist/safe-bash/command-chunks/{chunk-QQOAK7JQ.js → chunk-KKJCKPTA.js} +1 -1
  15. package/dist/safe-bash/command-chunks/{chunk-7KS6PX4U.js → chunk-KSIGF4AG.js} +3 -3
  16. package/dist/safe-bash/command-chunks/{chunk-4HQ7WYSI.js → chunk-MED2BOOW.js} +30 -5
  17. package/dist/safe-bash/command-chunks/{chunk-JRNLDPLI.js → chunk-OZSWW23M.js} +1 -1
  18. package/dist/safe-bash/command-chunks/{chunk-DKQ5REXF.js → chunk-QDPRSPHQ.js} +1 -1
  19. package/dist/safe-bash/command-chunks/{chunk-WOPU2VWF.js → chunk-RID3B5RE.js} +1 -1
  20. package/dist/safe-bash/command-chunks/{chunk-DVZCHNB5.js → chunk-S4MLIYSN.js} +7 -7
  21. package/dist/safe-bash/command-chunks/{chunk-REYTQLLB.js → chunk-TT2DMDBY.js} +1 -1
  22. package/dist/safe-bash/command-chunks/{chunk-UGX6MXDD.js → chunk-UIIT4ULR.js} +3 -3
  23. package/dist/safe-bash/command-chunks/{chunk-MENC7RPR.js → chunk-WI35236J.js} +1 -1
  24. package/dist/safe-bash/command-chunks/{chunk-A6NFLWZI.js → chunk-WKPOGTZY.js} +22 -6
  25. package/dist/safe-bash/command-chunks/{chunk-3DOHQQJR.js → chunk-Y64PA6IA.js} +8 -8
  26. package/dist/safe-bash/command-chunks/{chunk-6Z7OS3TZ.js → chunk-ZP5KDHB4.js} +7 -7
  27. package/dist/safe-bash/commands/pandoc/index.d.ts +1 -1
  28. package/dist/safe-bash/commands/pandoc/index.js +5 -3
  29. package/dist/safe-bash/commands/python/index.browser.js +1 -1
  30. package/dist/safe-bash/commands/python/index.js +6 -0
  31. package/dist/safe-bash/commands/python/jspi.js +11 -2
  32. package/dist/safe-bash/core.browser.js +6 -4
  33. package/dist/safe-bash-command-csvkit/index.js +8 -8
  34. package/dist/safe-bash-command-ffmpeg/index.js +20 -2
  35. package/dist/safe-bash-command-imagemagick/index.js +2 -2
  36. package/dist/safe-bash-command-pandoc/command.js +3 -3
  37. package/dist/safe-bash-command-pandoc/index.js +3 -3
  38. package/dist/safe-bash-command-pdfimages/index.js +26 -2
  39. package/dist/safe-bash-command-pdfinfo/index.js +26 -2
  40. package/dist/safe-bash-command-pdftk/index.js +26 -2
  41. package/dist/safe-bash-command-pdftoppm/index.js +26 -2
  42. package/dist/safe-bash-command-pdftotext/index.js +26 -2
  43. package/dist/safe-bash-command-qpdf/index.js +26 -2
  44. package/dist/safe-bash-command-sips/index.js +2 -2
  45. package/dist/safe-bash-command-soffice/index.js +17 -17
  46. package/dist/safe-bash-command-ssconvert/core.js +8 -8
  47. package/dist/safe-bash-command-ssconvert/formats/csv.js +9 -9
  48. package/dist/safe-bash-command-ssconvert/formats/ods.js +8 -8
  49. package/dist/safe-bash-command-ssconvert/formats/spreadsheetml.js +1 -1
  50. package/dist/safe-bash-command-ssconvert/formats/xls.js +9 -8
  51. package/dist/safe-bash-command-ssconvert/formats/xlsx.js +8 -8
  52. package/dist/safe-bash-command-ssconvert/index.js +133 -29
  53. package/dist/safe-bash-command-wkhtmltopdf/index.js +26 -2
  54. package/dist/safe-bash-contracts/filesystem-descriptor.js +1 -1
  55. package/dist/safe-bash-contracts/filesystem-output.js +1 -1
  56. package/dist/safe-bash-mcp/index.js +4 -4
  57. package/dist/spreadsheet-engine/formulas/ast.d.ts +13 -0
  58. package/dist/spreadsheet-engine/formulas/conventions.js +1 -1
  59. package/dist/spreadsheet-engine/formulas/dependencies.js +2 -2
  60. package/dist/spreadsheet-engine/formulas/evaluator.js +2 -2
  61. package/dist/spreadsheet-engine/formulas/named-expressions.d.ts +2 -2
  62. package/dist/spreadsheet-engine/formulas/named-expressions.js +31 -5
  63. package/dist/spreadsheet-engine/formulas/parser.js +34 -0
  64. package/dist/spreadsheet-engine/formulas/rewriting.js +19 -1
  65. package/dist/spreadsheet-engine/formulas/serialization.js +6 -0
  66. package/dist/spreadsheet-format-xls/biff-write-formulas.js +2 -0
  67. package/dist/xlsx-ast/formula-semantics.js +2 -0
  68. package/package.json +2 -2
  69. package/third-party/pdf-ast/THIRD_PARTY_NOTICES.md +1 -1
  70. package/dist/safe-bash/chunks/browser-ECQX6GMK.js +0 -6766
  71. package/dist/safe-bash/chunks/bz2-4OP3OWW3.js +0 -8
  72. package/dist/safe-bash/chunks/chunk-22XPAAOI.js +0 -173
  73. package/dist/safe-bash/chunks/chunk-44WUNUPV.js +0 -649
  74. package/dist/safe-bash/chunks/chunk-4E3SGG37.js +0 -876
  75. package/dist/safe-bash/chunks/chunk-4QTSSQKP.js +0 -898
  76. package/dist/safe-bash/chunks/chunk-5UVGPVXP.js +0 -410
  77. package/dist/safe-bash/chunks/chunk-64OE6Q6X.js +0 -3487
  78. package/dist/safe-bash/chunks/chunk-6IIFUPN2.js +0 -1526
  79. package/dist/safe-bash/chunks/chunk-6XDKK63A.js +0 -38959
  80. package/dist/safe-bash/chunks/chunk-BENDLTQD.js +0 -7065
  81. package/dist/safe-bash/chunks/chunk-BMO5LWEX.js +0 -534
  82. package/dist/safe-bash/chunks/chunk-BPMCPTNP.js +0 -1920
  83. package/dist/safe-bash/chunks/chunk-BRJTAWP5.js +0 -2955
  84. package/dist/safe-bash/chunks/chunk-C3GEIN7Q.js +0 -20054
  85. package/dist/safe-bash/chunks/chunk-CEWCRI6H.js +0 -2813
  86. package/dist/safe-bash/chunks/chunk-CGL53DQQ.js +0 -37
  87. package/dist/safe-bash/chunks/chunk-CTYJJD4O.js +0 -869
  88. package/dist/safe-bash/chunks/chunk-CWSS4MQA.js +0 -7
  89. package/dist/safe-bash/chunks/chunk-DLIGMTCP.js +0 -3266
  90. package/dist/safe-bash/chunks/chunk-EMAJ7WUW.js +0 -1031
  91. package/dist/safe-bash/chunks/chunk-FDKIZ2IJ.js +0 -1950
  92. package/dist/safe-bash/chunks/chunk-FZWJ2A7V.js +0 -3093
  93. package/dist/safe-bash/chunks/chunk-G2VCIF2J.js +0 -22
  94. package/dist/safe-bash/chunks/chunk-GVKBUQDX.js +0 -2649
  95. package/dist/safe-bash/chunks/chunk-IO2OGW3E.js +0 -28051
  96. package/dist/safe-bash/chunks/chunk-ISCZG7WA.js +0 -2648
  97. package/dist/safe-bash/chunks/chunk-JEFBTRY7.js +0 -174814
  98. package/dist/safe-bash/chunks/chunk-KJHDD4A2.js +0 -759
  99. package/dist/safe-bash/chunks/chunk-LB6HVRXS.js +0 -857
  100. package/dist/safe-bash/chunks/chunk-LLWAJOZH.js +0 -15
  101. package/dist/safe-bash/chunks/chunk-LMUKYZIJ.js +0 -708
  102. package/dist/safe-bash/chunks/chunk-M5SCNKIK.js +0 -2012
  103. package/dist/safe-bash/chunks/chunk-MJPFHANT.js +0 -3350
  104. package/dist/safe-bash/chunks/chunk-MZQW2DOL.js +0 -82
  105. package/dist/safe-bash/chunks/chunk-NB2V7EOY.js +0 -1348
  106. package/dist/safe-bash/chunks/chunk-NJVGOIYN.js +0 -17
  107. package/dist/safe-bash/chunks/chunk-OLNZHDIQ.js +0 -5141
  108. package/dist/safe-bash/chunks/chunk-OUUVROTM.js +0 -38
  109. package/dist/safe-bash/chunks/chunk-PA6GYKBQ.js +0 -151
  110. package/dist/safe-bash/chunks/chunk-PEFHZ226.js +0 -6957
  111. package/dist/safe-bash/chunks/chunk-QBB5P5KW.js +0 -4839
  112. package/dist/safe-bash/chunks/chunk-QHB7HBLA.js +0 -2317
  113. package/dist/safe-bash/chunks/chunk-RSAWYAEH.js +0 -973
  114. package/dist/safe-bash/chunks/chunk-UWLYRWT4.js +0 -739
  115. package/dist/safe-bash/chunks/chunk-V463NBQH.js +0 -13
  116. package/dist/safe-bash/chunks/chunk-WPUALYUS.js +0 -843
  117. package/dist/safe-bash/chunks/chunk-XHKFSRGU.js +0 -5747
  118. package/dist/safe-bash/chunks/chunk-XHRCLZ3N.js +0 -13391
  119. package/dist/safe-bash/chunks/chunk-XPF5XHLY.js +0 -15
  120. package/dist/safe-bash/chunks/chunk-ZBFOWARG.js +0 -305
  121. package/dist/safe-bash/chunks/chunk-ZG7GWQ5P.js +0 -1859
  122. package/dist/safe-bash/chunks/model-store-EIY3ZQLZ.js +0 -10
  123. package/dist/safe-bash/chunks/pdf-renderer-XIFUTX34.js +0 -22
  124. package/dist/safe-bash/chunks/pptx-ZL6L7KP2-O425BFIU.js +0 -30658
  125. package/dist/safe-bash/chunks/src-HHEPGR7U.js +0 -18969
  126. package/dist/safe-bash/chunks/src-V4VCM5OX.js +0 -187
  127. package/dist/safe-bash/chunks/usage-6TGVA2OM.js +0 -35
  128. package/dist/safe-bash/chunks/xz-EGQGQUEI.js +0 -8
  129. package/dist/safe-bash/chunks/xz-K2E2WLM6.js +0 -9
  130. package/dist/safe-bash/chunks/zstd-6JBLP5EF.js +0 -8
  131. 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