pixelkiln 0.34.0 → 0.34.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/cli.js +108 -87
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +105 -76
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +89 -32
- package/dist/index.d.ts +89 -32
- package/dist/index.js +99 -74
- package/dist/index.js.map +1 -1
- package/docs/ARCHITECTURE.md +6 -0
- package/docs/CLI.md +1 -1
- package/docs/GETTING_STARTED.md +2 -2
- package/docs/LIBRARY.md +15 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -65,8 +65,8 @@ provenance and no long-lived npm token.
|
|
|
65
65
|
|
|
66
66
|
### Local human review
|
|
67
67
|
|
|
68
|
-
`pixelkiln pick` opens a local candidate sheet
|
|
69
|
-
small sprites
|
|
68
|
+
`pixelkiln pick` opens a local candidate sheet with native aspect ratios and
|
|
69
|
+
crisp small sprites. No model chooses artwork for you.
|
|
70
70
|
|
|
71
71
|

|
|
72
72
|
|
|
@@ -84,7 +84,7 @@ reviews from the page under that ceiling.
|
|
|
84
84
|
Hand edits live beside the art, not in place of the record. [`pixelkiln edit`](docs/CLI.md#edit)
|
|
85
85
|
opens a copy in your own editor; with `--edit` the gallery does the same, or opens
|
|
86
86
|
it in a pinned, hash-verified [Pixelorama](docs/CLI.md#tools) build right in the page
|
|
87
|
-
and saves it back
|
|
87
|
+
and saves it back with its layers kept, one file per member for frame and tile sets.
|
|
88
88
|
|
|
89
89
|

|
|
90
90
|
|
package/dist/cli.js
CHANGED
|
@@ -181,6 +181,54 @@ function specHash(spec, styleImageHashes, providerOptionIdentity = spec.provider
|
|
|
181
181
|
);
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
+
// src/http.ts
|
|
185
|
+
var MAX_RETRIES = 4;
|
|
186
|
+
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
187
|
+
var sleepFor = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
188
|
+
function shouldRetry(status) {
|
|
189
|
+
return status === 408 || status === 429 || status === 500 || status === 502 || status === 503 || status === 504;
|
|
190
|
+
}
|
|
191
|
+
function backoffMs(attempt) {
|
|
192
|
+
const base = Math.min(1e3 * 2 ** attempt, 16e3);
|
|
193
|
+
return base + Math.floor(Math.random() * 400);
|
|
194
|
+
}
|
|
195
|
+
function retryAfterMs(value, now = Date.now()) {
|
|
196
|
+
if (!value) return null;
|
|
197
|
+
const seconds = Number(value);
|
|
198
|
+
if (Number.isFinite(seconds) && seconds >= 0) return seconds * 1e3;
|
|
199
|
+
const date = Date.parse(value);
|
|
200
|
+
if (!Number.isFinite(date)) return null;
|
|
201
|
+
return Math.max(0, date - now);
|
|
202
|
+
}
|
|
203
|
+
function fetchWithRetry(request, opts = {}) {
|
|
204
|
+
const sleep3 = opts.sleep ?? sleepFor;
|
|
205
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
206
|
+
const send = request ?? ((input, init) => globalThis.fetch(input, init));
|
|
207
|
+
return async (input, init) => {
|
|
208
|
+
const { retries: perCall, ...rest } = init ?? {};
|
|
209
|
+
const retries = Math.max(0, perCall ?? opts.retries ?? MAX_RETRIES);
|
|
210
|
+
for (let attempt = 0; ; attempt++) {
|
|
211
|
+
const signal = rest.signal ?? AbortSignal.timeout(timeoutMs);
|
|
212
|
+
let res;
|
|
213
|
+
try {
|
|
214
|
+
res = await send(input, { ...rest, signal });
|
|
215
|
+
} catch (error) {
|
|
216
|
+
if (rest.signal?.aborted || attempt >= retries) throw error;
|
|
217
|
+
const waitMs2 = backoffMs(attempt);
|
|
218
|
+
opts.onRetry?.({ attempt, waitMs: waitMs2, error });
|
|
219
|
+
await sleep3(waitMs2);
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
if (res.ok || !shouldRetry(res.status) || attempt >= retries) return res;
|
|
223
|
+
const waitMs = retryAfterMs(res.headers.get("retry-after")) ?? backoffMs(attempt);
|
|
224
|
+
await res.body?.cancel().catch(() => {
|
|
225
|
+
});
|
|
226
|
+
opts.onRetry?.({ attempt, waitMs, status: res.status });
|
|
227
|
+
await sleep3(waitMs);
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
184
232
|
// src/png.ts
|
|
185
233
|
import { deflateSync, inflateSync } from "zlib";
|
|
186
234
|
var SIGNATURE = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]);
|
|
@@ -636,14 +684,15 @@ var BUILTIN_BINDINGS = /* @__PURE__ */ new Set([
|
|
|
636
684
|
"strength"
|
|
637
685
|
]);
|
|
638
686
|
var ComfyUIClient = class {
|
|
639
|
-
|
|
640
|
-
|
|
687
|
+
baseUrl;
|
|
688
|
+
request;
|
|
689
|
+
constructor(baseUrl = process.env.COMFYUI_BASE_URL ?? DEFAULT_BASE_URL, request, retry) {
|
|
641
690
|
this.baseUrl = normalizeBaseUrl(baseUrl);
|
|
691
|
+
this.request = fetchWithRetry(request, retry);
|
|
642
692
|
}
|
|
643
|
-
|
|
644
|
-
baseUrl;
|
|
693
|
+
/** A connectivity check should answer fast, so it does not retry. */
|
|
645
694
|
async checkConnection() {
|
|
646
|
-
const value = await this.json("system_stats");
|
|
695
|
+
const value = await this.json("system_stats", { retries: 0 });
|
|
647
696
|
if (!isObject(value)) throw new Error("ComfyUI returned invalid system stats");
|
|
648
697
|
}
|
|
649
698
|
async submit(workflow) {
|
|
@@ -1505,24 +1554,7 @@ import path3 from "path";
|
|
|
1505
1554
|
// src/client.ts
|
|
1506
1555
|
import { z } from "zod";
|
|
1507
1556
|
var BASE = process.env.PIXELLAB_API_BASE ?? "https://api.pixellab.ai/v2";
|
|
1508
|
-
var MAX_RETRIES = 4;
|
|
1509
1557
|
var MAX_DOWNLOAD_BYTES = 64 * 1024 * 1024;
|
|
1510
|
-
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
1511
|
-
function shouldRetry(status) {
|
|
1512
|
-
return status === 429 || status === 408 || status >= 500;
|
|
1513
|
-
}
|
|
1514
|
-
function backoffMs(attempt) {
|
|
1515
|
-
const base = Math.min(1e3 * 2 ** attempt, 16e3);
|
|
1516
|
-
return base + Math.floor(Math.random() * 400);
|
|
1517
|
-
}
|
|
1518
|
-
function retryAfterMs(value, now = Date.now()) {
|
|
1519
|
-
if (!value) return null;
|
|
1520
|
-
const seconds = Number(value);
|
|
1521
|
-
if (Number.isFinite(seconds) && seconds >= 0) return seconds * 1e3;
|
|
1522
|
-
const date = Date.parse(value);
|
|
1523
|
-
if (!Number.isFinite(date)) return null;
|
|
1524
|
-
return Math.max(0, date - now);
|
|
1525
|
-
}
|
|
1526
1558
|
var BalanceResponseSchema = z.object({
|
|
1527
1559
|
credits: z.object({ usd: z.number() }).passthrough(),
|
|
1528
1560
|
subscription: z.object({
|
|
@@ -1598,45 +1630,22 @@ var PixelLabError = class extends Error {
|
|
|
1598
1630
|
var PixelLabClient = class {
|
|
1599
1631
|
constructor(apiKey, timeoutMs = 12e4) {
|
|
1600
1632
|
this.apiKey = apiKey;
|
|
1601
|
-
this.timeoutMs = timeoutMs;
|
|
1602
1633
|
if (!apiKey) throw new Error("PIXELLAB_API_KEY is required");
|
|
1634
|
+
this.http = fetchWithRetry(void 0, { timeoutMs });
|
|
1603
1635
|
}
|
|
1604
1636
|
apiKey;
|
|
1605
|
-
|
|
1606
|
-
/**
|
|
1607
|
-
|
|
1608
|
-
* A 4xx other than 429 is a bad request and retrying it just wastes time.
|
|
1609
|
-
*
|
|
1610
|
-
* POSTs that create objects are included, which is a deliberate trade: the
|
|
1611
|
-
* failure mode of not retrying (a dropped asset in a 65-item run) is more
|
|
1612
|
-
* common than the failure mode of retrying (a duplicate object), and a
|
|
1613
|
-
* duplicate is visible and free to delete whereas a silent gap is neither.
|
|
1614
|
-
*/
|
|
1615
|
-
async request(path32, init, attempt = 0) {
|
|
1637
|
+
http;
|
|
1638
|
+
/** Retries transport failures, 429, and 5xx; see http.ts for the policy. */
|
|
1639
|
+
async request(path32, init) {
|
|
1616
1640
|
const auth = this.apiKey.startsWith("Bearer ") ? this.apiKey : `Bearer ${this.apiKey}`;
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
headers
|
|
1623
|
-
Authorization: auth,
|
|
1624
|
-
"Content-Type": "application/json",
|
|
1625
|
-
...init?.headers ?? {}
|
|
1626
|
-
}
|
|
1627
|
-
});
|
|
1628
|
-
} catch (err) {
|
|
1629
|
-
if (attempt < MAX_RETRIES) {
|
|
1630
|
-
await sleep(backoffMs(attempt));
|
|
1631
|
-
return this.request(path32, init, attempt + 1);
|
|
1641
|
+
const res = await this.http(`${BASE}${path32}`, {
|
|
1642
|
+
...init,
|
|
1643
|
+
headers: {
|
|
1644
|
+
Authorization: auth,
|
|
1645
|
+
"Content-Type": "application/json",
|
|
1646
|
+
...init?.headers ?? {}
|
|
1632
1647
|
}
|
|
1633
|
-
|
|
1634
|
-
}
|
|
1635
|
-
if (!res.ok && shouldRetry(res.status) && attempt < MAX_RETRIES) {
|
|
1636
|
-
const waitMs = retryAfterMs(res.headers.get("retry-after")) ?? backoffMs(attempt);
|
|
1637
|
-
await sleep(waitMs);
|
|
1638
|
-
return this.request(path32, init, attempt + 1);
|
|
1639
|
-
}
|
|
1648
|
+
});
|
|
1640
1649
|
const text = await res.text();
|
|
1641
1650
|
if (!res.ok) {
|
|
1642
1651
|
throw new PixelLabError(`${init?.method ?? "GET"} ${path32} \u2192 ${res.status}`, res.status, text);
|
|
@@ -1824,7 +1833,7 @@ var PixelLabClient = class {
|
|
|
1824
1833
|
}
|
|
1825
1834
|
/** Storage URLs are public; no auth header, and sending one can break the CDN request. */
|
|
1826
1835
|
async download(url) {
|
|
1827
|
-
const res = await
|
|
1836
|
+
const res = await this.http(url);
|
|
1828
1837
|
if (!res.ok) throw new PixelLabError(`download ${url} \u2192 ${res.status}`, res.status, "");
|
|
1829
1838
|
const declared = Number(res.headers.get("content-length"));
|
|
1830
1839
|
if (Number.isFinite(declared) && declared > MAX_DOWNLOAD_BYTES) {
|
|
@@ -2631,10 +2640,10 @@ function firstUrl(urls) {
|
|
|
2631
2640
|
var DEFAULT_BASE_URL2 = "https://api.retrodiffusion.ai/v1";
|
|
2632
2641
|
var SOURCE_PROTOCOL = "retrodiffusion:";
|
|
2633
2642
|
var RetroDiffusionClient = class {
|
|
2634
|
-
constructor(token, baseUrl = DEFAULT_BASE_URL2, request
|
|
2643
|
+
constructor(token, baseUrl = DEFAULT_BASE_URL2, request, retry) {
|
|
2635
2644
|
this.token = token;
|
|
2636
2645
|
this.baseUrl = baseUrl;
|
|
2637
|
-
this.request = request;
|
|
2646
|
+
this.request = fetchWithRetry(request, retry);
|
|
2638
2647
|
}
|
|
2639
2648
|
token;
|
|
2640
2649
|
baseUrl;
|
|
@@ -2669,6 +2678,12 @@ var RetroDiffusionClient = class {
|
|
|
2669
2678
|
}
|
|
2670
2679
|
return balance;
|
|
2671
2680
|
}
|
|
2681
|
+
/** Result URLs are signed; no token header, and the same retry policy as the API. */
|
|
2682
|
+
async download(url) {
|
|
2683
|
+
const response = await this.request(url);
|
|
2684
|
+
if (!response.ok) throw new Error(`Retro Diffusion download failed (${response.status})`);
|
|
2685
|
+
return Buffer.from(await response.arrayBuffer());
|
|
2686
|
+
}
|
|
2672
2687
|
async call(path32, init = {}) {
|
|
2673
2688
|
if (!this.token) throw new Error("RD_API_KEY is not set");
|
|
2674
2689
|
const response = await this.request(`${this.baseUrl}${path32}`, {
|
|
@@ -2864,9 +2879,7 @@ var RetroDiffusionProvider = class _RetroDiffusionProvider {
|
|
|
2864
2879
|
async downloadResolved(url) {
|
|
2865
2880
|
const data = /^data:[^;]+;base64,(.+)$/.exec(url)?.[1];
|
|
2866
2881
|
if (data) return Buffer.from(data, "base64");
|
|
2867
|
-
|
|
2868
|
-
if (!response.ok) throw new Error(`Retro Diffusion download failed (${response.status})`);
|
|
2869
|
-
return Buffer.from(await response.arrayBuffer());
|
|
2882
|
+
return this.client.download(url);
|
|
2870
2883
|
}
|
|
2871
2884
|
async balance() {
|
|
2872
2885
|
return { unit: "usd", remaining: await this.client.balance() };
|
|
@@ -2993,11 +3006,11 @@ var PROTECTED_PARAMETERS = /* @__PURE__ */ new Set([
|
|
|
2993
3006
|
"width"
|
|
2994
3007
|
]);
|
|
2995
3008
|
var ScenarioClient = class {
|
|
2996
|
-
constructor(apiKey, apiSecret, baseUrl = DEFAULT_BASE_URL3, request
|
|
3009
|
+
constructor(apiKey, apiSecret, baseUrl = DEFAULT_BASE_URL3, request, retry) {
|
|
2997
3010
|
this.apiKey = apiKey;
|
|
2998
3011
|
this.apiSecret = apiSecret;
|
|
2999
3012
|
this.baseUrl = baseUrl;
|
|
3000
|
-
this.request = request;
|
|
3013
|
+
this.request = fetchWithRetry(request, retry);
|
|
3001
3014
|
}
|
|
3002
3015
|
apiKey;
|
|
3003
3016
|
apiSecret;
|
|
@@ -3095,8 +3108,9 @@ var ScenarioClient = class {
|
|
|
3095
3108
|
}
|
|
3096
3109
|
return bytes;
|
|
3097
3110
|
}
|
|
3111
|
+
/** A connectivity check should answer fast, so it does not retry. */
|
|
3098
3112
|
async checkConnection() {
|
|
3099
|
-
await this.call("/models", {}, { pageSize: "1" });
|
|
3113
|
+
await this.call("/models", { retries: 0 }, { pageSize: "1" });
|
|
3100
3114
|
}
|
|
3101
3115
|
async call(pathname, init = {}, query = {}) {
|
|
3102
3116
|
if (!this.apiKey || !this.apiSecret) {
|
|
@@ -6628,7 +6642,7 @@ async function revertGeneration(provider, spec, lock, lockPath, opts) {
|
|
|
6628
6642
|
|
|
6629
6643
|
// src/pipeline/submit.ts
|
|
6630
6644
|
var IN_FLIGHT_POLL_MS = 5e3;
|
|
6631
|
-
var
|
|
6645
|
+
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
6632
6646
|
async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
6633
6647
|
const limits = provider.rateLimit?.() ?? DEFAULT_RATE_LIMIT;
|
|
6634
6648
|
const maxInFlight = opts.maxInFlight ?? limits.maxInFlight;
|
|
@@ -6683,7 +6697,7 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
6683
6697
|
`Timed out waiting for a generation slot` + (lastSlotError ? `; last poll error: ${lastSlotError}` : "")
|
|
6684
6698
|
);
|
|
6685
6699
|
}
|
|
6686
|
-
await
|
|
6700
|
+
await sleep(slotPollMs);
|
|
6687
6701
|
await pruneInFlight();
|
|
6688
6702
|
}
|
|
6689
6703
|
}
|
|
@@ -6691,7 +6705,7 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
6691
6705
|
const estimate = estimates.get(key);
|
|
6692
6706
|
await waitForSlot();
|
|
6693
6707
|
const since = Date.now() - lastSubmitAt;
|
|
6694
|
-
if (since < spacing) await
|
|
6708
|
+
if (since < spacing) await sleep(spacing - since);
|
|
6695
6709
|
await requireRevisionReady(spec, lock);
|
|
6696
6710
|
const previousEntry = lock.entries[key];
|
|
6697
6711
|
const resumesCheckpoint = Boolean(
|
|
@@ -6809,7 +6823,7 @@ async function submit(provider, loaded, items, lock, lockPath, opts = {}) {
|
|
|
6809
6823
|
}
|
|
6810
6824
|
|
|
6811
6825
|
// src/pipeline/poll.ts
|
|
6812
|
-
var
|
|
6826
|
+
var sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
6813
6827
|
async function poll(provider, lock, lockPath, opts = {}) {
|
|
6814
6828
|
const interval = opts.intervalMs ?? 5e3;
|
|
6815
6829
|
const timeout = opts.timeoutMs ?? 15 * 60 * 1e3;
|
|
@@ -6886,7 +6900,7 @@ async function poll(provider, lock, lockPath, opts = {}) {
|
|
|
6886
6900
|
}
|
|
6887
6901
|
}
|
|
6888
6902
|
await saveLock(lockPath, lock);
|
|
6889
|
-
if (pending().length > 0) await
|
|
6903
|
+
if (pending().length > 0) await sleep2(interval);
|
|
6890
6904
|
}
|
|
6891
6905
|
await saveLock(lockPath, lock);
|
|
6892
6906
|
return result;
|
|
@@ -7800,8 +7814,25 @@ document.getElementById('submit').onclick = async () => {
|
|
|
7800
7814
|
|
|
7801
7815
|
// src/pick/review-server.ts
|
|
7802
7816
|
import { createServer } from "http";
|
|
7803
|
-
import { spawn } from "child_process";
|
|
7804
7817
|
import { readFile as readFile11 } from "fs/promises";
|
|
7818
|
+
|
|
7819
|
+
// src/open.ts
|
|
7820
|
+
import { spawn } from "child_process";
|
|
7821
|
+
function openExternal(target, command = defaultOpenCommand()) {
|
|
7822
|
+
const [program, ...args] = command;
|
|
7823
|
+
const child = spawn(program, [...args, target], { stdio: "ignore", detached: true });
|
|
7824
|
+
child.on("error", () => {
|
|
7825
|
+
});
|
|
7826
|
+
child.unref();
|
|
7827
|
+
return command.join(" ");
|
|
7828
|
+
}
|
|
7829
|
+
function defaultOpenCommand(platform = process.platform) {
|
|
7830
|
+
if (platform === "darwin") return ["open"];
|
|
7831
|
+
if (platform === "win32") return ["cmd", "/c", "start", ""];
|
|
7832
|
+
return ["xdg-open"];
|
|
7833
|
+
}
|
|
7834
|
+
|
|
7835
|
+
// src/pick/review-server.ts
|
|
7805
7836
|
function serveReviewPage(opts) {
|
|
7806
7837
|
const log2 = opts.onProgress ?? (() => {
|
|
7807
7838
|
});
|
|
@@ -7873,10 +7904,9 @@ function serveReviewPage(opts) {
|
|
|
7873
7904
|
server.listen(opts.port ?? 0, "127.0.0.1", () => {
|
|
7874
7905
|
const address = server.address();
|
|
7875
7906
|
const port = typeof address === "object" && address ? address.port : opts.port;
|
|
7876
|
-
|
|
7877
|
-
|
|
7878
|
-
|
|
7879
|
-
}
|
|
7907
|
+
const url = `http://127.0.0.1:${port}/`;
|
|
7908
|
+
opts.onReady(url);
|
|
7909
|
+
if (opts.open !== false) openExternal(url);
|
|
7880
7910
|
});
|
|
7881
7911
|
});
|
|
7882
7912
|
}
|
|
@@ -8449,7 +8479,6 @@ async function applyManifestEdit(manifestPath, edit) {
|
|
|
8449
8479
|
}
|
|
8450
8480
|
|
|
8451
8481
|
// src/pipeline/hand-edit.ts
|
|
8452
|
-
import { spawn as spawn2 } from "child_process";
|
|
8453
8482
|
import { existsSync as existsSync18 } from "fs";
|
|
8454
8483
|
import { copyFile, mkdir as mkdir6, readFile as readFile15, rename as rename7, rm as rm8, writeFile as writeFile9 } from "fs/promises";
|
|
8455
8484
|
import path21 from "path";
|
|
@@ -8689,13 +8718,8 @@ async function replaceFile(file, bytes) {
|
|
|
8689
8718
|
}
|
|
8690
8719
|
}
|
|
8691
8720
|
function openInEditor(file, editor = process.env.PIXELKILN_EDITOR) {
|
|
8692
|
-
const command = editor?.trim() ? editor.trim().split(/\s+/) :
|
|
8693
|
-
|
|
8694
|
-
const child = spawn2(program, [...args, file], { stdio: "ignore", detached: true });
|
|
8695
|
-
child.on("error", () => {
|
|
8696
|
-
});
|
|
8697
|
-
child.unref();
|
|
8698
|
-
return command.join(" ");
|
|
8721
|
+
const command = editor?.trim() ? editor.trim().split(/\s+/) : defaultOpenCommand();
|
|
8722
|
+
return openExternal(file, command);
|
|
8699
8723
|
}
|
|
8700
8724
|
|
|
8701
8725
|
// src/gallery/edit.ts
|
|
@@ -9346,7 +9370,6 @@ async function buildWorkspaceGallerySnapshot(opts) {
|
|
|
9346
9370
|
}
|
|
9347
9371
|
|
|
9348
9372
|
// src/gallery/server.ts
|
|
9349
|
-
import { spawn as spawn3 } from "child_process";
|
|
9350
9373
|
import { randomBytes } from "crypto";
|
|
9351
9374
|
import { createServer as createServer2 } from "http";
|
|
9352
9375
|
import { readFile as readFile17 } from "fs/promises";
|
|
@@ -12126,9 +12149,7 @@ async function serveGallery(opts) {
|
|
|
12126
12149
|
});
|
|
12127
12150
|
});
|
|
12128
12151
|
opts.onReady?.(url);
|
|
12129
|
-
if (opts.open !== false
|
|
12130
|
-
spawn3("open", [url], { stdio: "ignore", detached: true }).unref();
|
|
12131
|
-
}
|
|
12152
|
+
if (opts.open !== false) openExternal(url);
|
|
12132
12153
|
return {
|
|
12133
12154
|
url,
|
|
12134
12155
|
session,
|