dsh-modellix 0.2.1 → 0.3.0
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/CHANGELOG.md +15 -0
- package/README.md +9 -2
- package/README.zh-CN.md +9 -2
- package/docs/en-US/LOCAL_USAGE.md +11 -9
- package/docs/en-US/RELEASE_CHECKLIST.md +5 -5
- package/docs/en-US/USER_GUIDE.md +14 -5
- package/docs/zh-CN/LOCAL_USAGE.md +11 -9
- package/docs/zh-CN/RELEASE_CHECKLIST.md +5 -5
- package/docs/zh-CN/USER_GUIDE.md +14 -5
- package/lib/client.js +26 -4
- package/lib/client.js.map +1 -1
- package/lib/index.d.ts +8 -2
- package/lib/index.js +170 -25
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
package/lib/index.d.ts
CHANGED
|
@@ -7,7 +7,6 @@ import { z as z$1 } from "zod";
|
|
|
7
7
|
import { ToolDefinition } from "@deepseek-ai/dsh-tools";
|
|
8
8
|
import { Context } from "@deepseek-ai/cordis";
|
|
9
9
|
import { Agent } from "@deepseek-ai/dsh-agent";
|
|
10
|
-
import { ImageMediaType } from "@deepseek-ai/dsh-attachment";
|
|
11
10
|
|
|
12
11
|
//#region src/core/config.d.ts
|
|
13
12
|
declare const CURRENT_CONFIG_SCHEMA_VERSION: 1;
|
|
@@ -530,7 +529,7 @@ declare function extractAllowedSubmitUrl(document: Readonly<Record<string, unkno
|
|
|
530
529
|
interface PreparedMediaUpload {
|
|
531
530
|
readonly bytes: Uint8Array;
|
|
532
531
|
readonly filename: string;
|
|
533
|
-
readonly mimeType:
|
|
532
|
+
readonly mimeType: string;
|
|
534
533
|
}
|
|
535
534
|
interface MediaUploadRecord {
|
|
536
535
|
readonly fileId: string;
|
|
@@ -721,6 +720,7 @@ interface ReadPredictionInput {
|
|
|
721
720
|
readonly apiKey: string;
|
|
722
721
|
readonly maxAttempts?: number;
|
|
723
722
|
readonly signal?: AbortSignal;
|
|
723
|
+
readonly waitSeconds?: number;
|
|
724
724
|
}
|
|
725
725
|
declare class PredictionClient {
|
|
726
726
|
#private;
|
|
@@ -729,6 +729,10 @@ declare class PredictionClient {
|
|
|
729
729
|
submit(input: SubmitPredictionInput): Promise<PredictionTask>;
|
|
730
730
|
/** GET retries only transient read failures and is capped at five attempts. */
|
|
731
731
|
readTask(input: ReadPredictionInput): Promise<PredictionTask>;
|
|
732
|
+
/** Wait for one bounded server observation; does not submit or sleep between snapshots. */
|
|
733
|
+
waitTask(input: Omit<ReadPredictionInput, "waitSeconds"> & {
|
|
734
|
+
readonly timeoutSeconds?: number;
|
|
735
|
+
}): Promise<PredictionTask>;
|
|
732
736
|
}
|
|
733
737
|
declare function validateSubmitEndpoint(endpoint: string, modelSlug: string): URL;
|
|
734
738
|
declare function parsePredictionTask(payload: unknown, expectedTaskId?: string): PredictionTask | null;
|
|
@@ -897,6 +901,7 @@ interface CredentialSnapshot {
|
|
|
897
901
|
readonly credentialEpoch: number;
|
|
898
902
|
}
|
|
899
903
|
interface DesignHostControllerOptions {
|
|
904
|
+
readonly serializeObservation?: (operation: () => Promise<void>) => Promise<void>;
|
|
900
905
|
readonly storage: StoragePort;
|
|
901
906
|
readonly resolveCredential: () => Promise<CredentialSnapshot | undefined>;
|
|
902
907
|
readonly isCredentialEpochCurrent: (credentialEpoch: number) => boolean;
|
|
@@ -988,6 +993,7 @@ declare class DesignHostController {
|
|
|
988
993
|
constructor(options: DesignHostControllerOptions);
|
|
989
994
|
handle(endpoint: string, payload: unknown, signal?: AbortSignal): Promise<DesignSnapshotWire>;
|
|
990
995
|
pollRunning(signal?: AbortSignal): Promise<boolean>;
|
|
996
|
+
invalidateWaits(): void;
|
|
991
997
|
private read;
|
|
992
998
|
private refresh;
|
|
993
999
|
private selectModel;
|
package/lib/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import { getOrCreateAnonymousUserId } from "@deepseek-ai/dsh-anonymous-user-id";
|
|
|
4
4
|
import { credentialRef } from "@deepseek-ai/dsh-credentials";
|
|
5
5
|
import { SettingsConflictError } from "@deepseek-ai/dsh-settings";
|
|
6
6
|
import { lstat, open, realpath } from "node:fs/promises";
|
|
7
|
-
import { basename, isAbsolute, relative, resolve } from "node:path";
|
|
7
|
+
import { basename, extname, isAbsolute, relative, resolve } from "node:path";
|
|
8
8
|
import { WebError } from "@deepseek-ai/dsh-web";
|
|
9
9
|
import { createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
10
10
|
import { defineDomain } from "@deepseek-ai/dsh-storage-domain";
|
|
@@ -1547,6 +1547,10 @@ var MediaUploadClient = class {
|
|
|
1547
1547
|
throw new DesignError("SUBMIT_UNKNOWN", "The media upload outcome is unknown; do not retry automatically", { status: response.status });
|
|
1548
1548
|
}
|
|
1549
1549
|
if (!response.ok) {
|
|
1550
|
+
if (response.status === 429) {
|
|
1551
|
+
response.body?.cancel().catch(() => void 0);
|
|
1552
|
+
throw new DesignError("SUBMIT_REJECTED", "Upload quota exceeded. List existing files and explicitly delete only unneeded files before trying again; no automatic cleanup was performed", { status: 429 });
|
|
1553
|
+
}
|
|
1550
1554
|
const ambiguous = response.status === 408 || response.status === 409 || response.status === 425 || response.status === 429 || response.status >= 500;
|
|
1551
1555
|
response.body?.cancel().catch(() => void 0);
|
|
1552
1556
|
throw new DesignError(ambiguous ? "SUBMIT_UNKNOWN" : "SUBMIT_REJECTED", ambiguous ? "The media upload outcome is unknown; do not retry automatically" : `The media upload was rejected with HTTP ${String(response.status)}`, { status: response.status });
|
|
@@ -1591,12 +1595,19 @@ async function prepareMediaUploadFromPath(cwd, inputPath) {
|
|
|
1591
1595
|
try {
|
|
1592
1596
|
const opened = await handle.stat();
|
|
1593
1597
|
if (!opened.isFile() || opened.size !== before.size || opened.dev !== before.dev || opened.ino !== before.ino) throw new DesignError("INVALID_ARGUMENT", "Media file changed while it was being opened");
|
|
1594
|
-
|
|
1598
|
+
const bounded = Buffer.alloc(before.size + 1);
|
|
1599
|
+
let offset = 0;
|
|
1600
|
+
while (offset < bounded.length) {
|
|
1601
|
+
const { bytesRead } = await handle.read(bounded, offset, bounded.length - offset, null);
|
|
1602
|
+
if (bytesRead === 0) break;
|
|
1603
|
+
offset += bytesRead;
|
|
1604
|
+
}
|
|
1605
|
+
bytes = bounded.subarray(0, offset);
|
|
1595
1606
|
} finally {
|
|
1596
1607
|
await handle.close();
|
|
1597
1608
|
}
|
|
1598
1609
|
if (bytes.byteLength !== before.size) throw new DesignError("INVALID_ARGUMENT", "Media file changed while it was being read");
|
|
1599
|
-
const mimeType =
|
|
1610
|
+
const mimeType = detectMediaMimeType(bytes, resolved);
|
|
1600
1611
|
return {
|
|
1601
1612
|
bytes,
|
|
1602
1613
|
filename: safeFilename(basename(resolved), mimeType),
|
|
@@ -1611,7 +1622,74 @@ function within(root, candidate) {
|
|
|
1611
1622
|
const suffix = relative(root, candidate);
|
|
1612
1623
|
return suffix === "" || !suffix.startsWith("..") && !isAbsolute(suffix);
|
|
1613
1624
|
}
|
|
1614
|
-
|
|
1625
|
+
const MEDIA_EXTENSIONS = {
|
|
1626
|
+
jpg: "image/jpeg",
|
|
1627
|
+
jpeg: "image/jpeg",
|
|
1628
|
+
png: "image/png",
|
|
1629
|
+
webp: "image/webp",
|
|
1630
|
+
gif: "image/gif",
|
|
1631
|
+
bmp: "image/bmp",
|
|
1632
|
+
tif: "image/tiff",
|
|
1633
|
+
tiff: "image/tiff",
|
|
1634
|
+
heic: "image/heic",
|
|
1635
|
+
heif: "image/heif",
|
|
1636
|
+
mp4: "video/mp4",
|
|
1637
|
+
m4v: "video/x-m4v",
|
|
1638
|
+
webm: "video/webm",
|
|
1639
|
+
mov: "video/quicktime",
|
|
1640
|
+
mkv: "video/x-matroska",
|
|
1641
|
+
avi: "video/x-msvideo",
|
|
1642
|
+
mp3: "audio/mpeg",
|
|
1643
|
+
wav: "audio/wav",
|
|
1644
|
+
m4a: "audio/mp4",
|
|
1645
|
+
aac: "audio/aac",
|
|
1646
|
+
ogg: "audio/ogg",
|
|
1647
|
+
flac: "audio/flac",
|
|
1648
|
+
opus: "audio/opus",
|
|
1649
|
+
weba: "audio/webm"
|
|
1650
|
+
};
|
|
1651
|
+
function detectMediaMimeType(bytes, filename) {
|
|
1652
|
+
const extension = extname(filename).slice(1).toLowerCase();
|
|
1653
|
+
const expected = MEDIA_EXTENSIONS[extension];
|
|
1654
|
+
if (expected === void 0) throw new DesignError("INVALID_ARGUMENT", "Unsupported media file extension");
|
|
1655
|
+
const head = Buffer.from(bytes.subarray(0, 16)).toString("latin1");
|
|
1656
|
+
const riffType = head.startsWith("RIFF") ? head.slice(8, 12) : "";
|
|
1657
|
+
const iso = bytes.length >= 12 && head.slice(4, 8) === "ftyp";
|
|
1658
|
+
const ebml = head.startsWith("Eߣ");
|
|
1659
|
+
const audioFrame = bytes.length >= 2 && bytes[0] === 255 && ((bytes[1] ?? 0) & 224) === 224;
|
|
1660
|
+
const tiff = [
|
|
1661
|
+
73,
|
|
1662
|
+
73,
|
|
1663
|
+
42,
|
|
1664
|
+
0
|
|
1665
|
+
].every((byte, index) => bytes[index] === byte) || [
|
|
1666
|
+
77,
|
|
1667
|
+
77,
|
|
1668
|
+
0,
|
|
1669
|
+
42
|
|
1670
|
+
].every((byte, index) => bytes[index] === byte);
|
|
1671
|
+
if ({
|
|
1672
|
+
gif: /^GIF8[79]a/u.test(head),
|
|
1673
|
+
bmp: head.startsWith("BM"),
|
|
1674
|
+
tif: tiff,
|
|
1675
|
+
tiff,
|
|
1676
|
+
heic: iso && /^(?:heic|heix|hevc|hevx|mif1)$/u.test(head.slice(8, 12)),
|
|
1677
|
+
heif: iso && /^(?:heic|heix|hevc|hevx|mif1|msf1)$/u.test(head.slice(8, 12)),
|
|
1678
|
+
mp4: iso,
|
|
1679
|
+
m4v: iso,
|
|
1680
|
+
m4a: iso,
|
|
1681
|
+
mov: iso || head.slice(4, 8) === "moov",
|
|
1682
|
+
avi: riffType === "AVI ",
|
|
1683
|
+
wav: riffType === "WAVE",
|
|
1684
|
+
webm: ebml,
|
|
1685
|
+
mkv: ebml,
|
|
1686
|
+
weba: ebml,
|
|
1687
|
+
mp3: head.startsWith("ID3") || audioFrame,
|
|
1688
|
+
aac: audioFrame,
|
|
1689
|
+
ogg: head.startsWith("OggS"),
|
|
1690
|
+
opus: head.startsWith("OggS"),
|
|
1691
|
+
flac: head.startsWith("fLaC")
|
|
1692
|
+
}[extension] === true) return expected;
|
|
1615
1693
|
if (bytes.length >= 8 && bytes.slice(0, 8).every((value, index) => value === [
|
|
1616
1694
|
137,
|
|
1617
1695
|
80,
|
|
@@ -1621,14 +1699,18 @@ function detectImageMimeType(bytes) {
|
|
|
1621
1699
|
10,
|
|
1622
1700
|
26,
|
|
1623
1701
|
10
|
|
1624
|
-
][index])
|
|
1625
|
-
if (bytes.length >= 3 && bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255)
|
|
1702
|
+
][index]) && expected === "image/png") return expected;
|
|
1703
|
+
if (bytes.length >= 3 && bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255) {
|
|
1704
|
+
if (expected === "image/jpeg") return expected;
|
|
1705
|
+
}
|
|
1626
1706
|
const ascii = (start, end) => new TextDecoder("ascii").decode(bytes.slice(start, end));
|
|
1627
|
-
if (bytes.length >= 12 && ascii(0, 4) === "RIFF" && ascii(8, 12) === "WEBP")
|
|
1628
|
-
|
|
1707
|
+
if (bytes.length >= 12 && ascii(0, 4) === "RIFF" && ascii(8, 12) === "WEBP") {
|
|
1708
|
+
if (expected === "image/webp") return expected;
|
|
1709
|
+
}
|
|
1710
|
+
throw new DesignError("INVALID_ARGUMENT", "Unsupported media type or content does not match its extension");
|
|
1629
1711
|
}
|
|
1630
1712
|
function safeFilename(value, mimeType) {
|
|
1631
|
-
const extension =
|
|
1713
|
+
const extension = `.${Object.entries(MEDIA_EXTENSIONS).find(([, type]) => type === mimeType)?.[0] ?? "bin"}`;
|
|
1632
1714
|
const headerSafe = value.normalize("NFC").replaceAll(/["\\/]/gu, "_");
|
|
1633
1715
|
const cleaned = Array.from(headerSafe, (character) => {
|
|
1634
1716
|
const codePoint = character.codePointAt(0) ?? 0;
|
|
@@ -2510,10 +2592,18 @@ var PredictionClient = class {
|
|
|
2510
2592
|
const apiKey = validateApiKey(input.apiKey);
|
|
2511
2593
|
const maxAttempts = boundedAttempts(input.maxAttempts ?? 3);
|
|
2512
2594
|
const url = new URL(`/api/v1/tasks/${encodeURIComponent(taskId)}`, PREDICTION_ORIGIN);
|
|
2595
|
+
if (input.waitSeconds !== void 0) {
|
|
2596
|
+
if (!Number.isSafeInteger(input.waitSeconds) || input.waitSeconds < 1 || input.waitSeconds > 45) throw new DesignError("INVALID_ARGUMENT", "Wait duration must be an integer from 1 to 45 seconds");
|
|
2597
|
+
url.searchParams.set("wait", "true");
|
|
2598
|
+
url.searchParams.set("timeout", String(input.waitSeconds));
|
|
2599
|
+
}
|
|
2600
|
+
const totalTimeout = input.waitSeconds === void 0 ? this.#requestTimeoutMs : input.waitSeconds * 1e3 + 5e3;
|
|
2601
|
+
const overall = input.waitSeconds === void 0 ? void 0 : requestDeadline(input.signal, totalTimeout);
|
|
2513
2602
|
let lastFailure = null;
|
|
2514
2603
|
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
2515
2604
|
input.signal?.throwIfAborted();
|
|
2516
|
-
|
|
2605
|
+
overall?.signal.throwIfAborted();
|
|
2606
|
+
const deadline = overall ?? requestDeadline(input.signal, this.#requestTimeoutMs);
|
|
2517
2607
|
try {
|
|
2518
2608
|
const response = await this.#fetch(url, {
|
|
2519
2609
|
method: "GET",
|
|
@@ -2531,7 +2621,7 @@ var PredictionClient = class {
|
|
|
2531
2621
|
retryAfterMs: failure.retryAfterMs
|
|
2532
2622
|
});
|
|
2533
2623
|
lastFailure = failure;
|
|
2534
|
-
await this.#waitBeforeRetry(attempt, failure.retryAfterMs, input.signal);
|
|
2624
|
+
await this.#waitBeforeRetry(attempt, failure.retryAfterMs, overall?.signal ?? input.signal);
|
|
2535
2625
|
continue;
|
|
2536
2626
|
}
|
|
2537
2627
|
let payload;
|
|
@@ -2543,6 +2633,10 @@ var PredictionClient = class {
|
|
|
2543
2633
|
}
|
|
2544
2634
|
const task = parsePredictionTask(payload, taskId);
|
|
2545
2635
|
if (task === null) throw new DesignError("UNEXPECTED_RESPONSE", "Task status response does not contain a valid task");
|
|
2636
|
+
if (input.waitSeconds !== void 0 && (task.status === "queued" || task.status === "running")) {
|
|
2637
|
+
const wait = asRecord$3(asRecord$3(asRecord$3(payload)?.data)?.wait);
|
|
2638
|
+
if (typeof wait?.timed_out !== "boolean" || typeof wait.elapsed_ms !== "number") throw new DesignError("UNEXPECTED_RESPONSE", "The task API does not support server-side wait; keep the task ID and do not resubmit");
|
|
2639
|
+
}
|
|
2546
2640
|
this.#log({
|
|
2547
2641
|
level: "info",
|
|
2548
2642
|
event: "design.task.read",
|
|
@@ -2555,22 +2649,30 @@ var PredictionClient = class {
|
|
|
2555
2649
|
} catch (caught) {
|
|
2556
2650
|
if (input.signal?.aborted === true) throw caught;
|
|
2557
2651
|
if (deadline.timedOut()) {
|
|
2558
|
-
if (attempt === maxAttempts) throw new DesignError("TASK_READ_FAILED", "Task status request timed out within the retry bound", {
|
|
2652
|
+
if (attempt === maxAttempts || input.waitSeconds !== void 0) throw new DesignError("TASK_READ_FAILED", "Task status request timed out within the retry bound", {
|
|
2559
2653
|
cause: caught,
|
|
2560
2654
|
status: 408
|
|
2561
2655
|
});
|
|
2562
2656
|
lastFailure = caught;
|
|
2563
|
-
await this.#waitBeforeRetry(attempt, null, input.signal);
|
|
2657
|
+
await this.#waitBeforeRetry(attempt, null, overall?.signal ?? input.signal);
|
|
2564
2658
|
continue;
|
|
2565
2659
|
}
|
|
2566
2660
|
if (caught instanceof DesignError) throw caught;
|
|
2567
2661
|
if (isAbortError$1(caught) || attempt === maxAttempts) throw new DesignError("TASK_READ_FAILED", "Task status could not be read within the retry bound", { cause: caught });
|
|
2568
2662
|
lastFailure = caught;
|
|
2569
|
-
await this.#waitBeforeRetry(attempt, null, input.signal);
|
|
2663
|
+
await this.#waitBeforeRetry(attempt, null, overall?.signal ?? input.signal);
|
|
2570
2664
|
}
|
|
2571
2665
|
}
|
|
2572
2666
|
throw new DesignError("TASK_READ_FAILED", "Task status could not be read within the retry bound", { cause: lastFailure });
|
|
2573
2667
|
}
|
|
2668
|
+
/** Wait for one bounded server observation; does not submit or sleep between snapshots. */
|
|
2669
|
+
async waitTask(input) {
|
|
2670
|
+
return this.readTask({
|
|
2671
|
+
...input,
|
|
2672
|
+
waitSeconds: input.timeoutSeconds ?? 30,
|
|
2673
|
+
maxAttempts: input.maxAttempts ?? 1
|
|
2674
|
+
});
|
|
2675
|
+
}
|
|
2574
2676
|
async #waitBeforeRetry(attempt, retryAfter, signal) {
|
|
2575
2677
|
const delay = Math.min(MAX_INLINE_RETRY_DELAY_MS, Math.max(retryAfter ?? 0, 250 * 2 ** (attempt - 1)));
|
|
2576
2678
|
this.#log({
|
|
@@ -5480,6 +5582,11 @@ var DesignHostController = class {
|
|
|
5480
5582
|
#catalogNotice = null;
|
|
5481
5583
|
#catalogCredentialEpoch = null;
|
|
5482
5584
|
#pollCursor = 0;
|
|
5585
|
+
#pollInFlight;
|
|
5586
|
+
#pollAbort;
|
|
5587
|
+
#pollEpoch;
|
|
5588
|
+
#observationTail = Promise.resolve();
|
|
5589
|
+
#serializeObservation;
|
|
5483
5590
|
constructor(options) {
|
|
5484
5591
|
this.#repository = new DesignTaskRepository({ storage: options.storage });
|
|
5485
5592
|
this.#resolveCredential = options.resolveCredential;
|
|
@@ -5490,6 +5597,11 @@ var DesignHostController = class {
|
|
|
5490
5597
|
this.#rememberModel = options.rememberModel;
|
|
5491
5598
|
this.#fetch = options.fetch ?? globalThis.fetch;
|
|
5492
5599
|
this.#now = options.now ?? Date.now;
|
|
5600
|
+
this.#serializeObservation = options.serializeObservation ?? ((operation) => {
|
|
5601
|
+
const run = this.#observationTail.then(operation);
|
|
5602
|
+
this.#observationTail = run.catch(() => void 0);
|
|
5603
|
+
return run;
|
|
5604
|
+
});
|
|
5493
5605
|
}
|
|
5494
5606
|
async handle(endpoint, payload, signal) {
|
|
5495
5607
|
signal?.throwIfAborted();
|
|
@@ -5509,32 +5621,59 @@ var DesignHostController = class {
|
|
|
5509
5621
|
if (!this.#isEnabled()) return false;
|
|
5510
5622
|
const credential = await this.#resolveCredential();
|
|
5511
5623
|
if (credential === void 0) return false;
|
|
5624
|
+
if (this.#pollEpoch !== credential.credentialEpoch) this.invalidateWaits();
|
|
5625
|
+
if (this.#pollInFlight !== void 0) return this.#pollInFlight;
|
|
5626
|
+
const abort = new AbortController();
|
|
5627
|
+
this.#pollAbort = abort;
|
|
5628
|
+
this.#pollEpoch = credential.credentialEpoch;
|
|
5629
|
+
const combined = signal === void 0 ? abort.signal : AbortSignal.any([signal, abort.signal]);
|
|
5630
|
+
const run = this.#waitRunning(credential, combined);
|
|
5631
|
+
this.#pollInFlight = run;
|
|
5632
|
+
try {
|
|
5633
|
+
return await run;
|
|
5634
|
+
} finally {
|
|
5635
|
+
if (this.#pollInFlight === run) {
|
|
5636
|
+
this.#pollInFlight = void 0;
|
|
5637
|
+
this.#pollAbort = void 0;
|
|
5638
|
+
}
|
|
5639
|
+
}
|
|
5640
|
+
}
|
|
5641
|
+
invalidateWaits() {
|
|
5642
|
+
this.#pollAbort?.abort();
|
|
5643
|
+
this.#pollAbort = void 0;
|
|
5644
|
+
this.#pollInFlight = void 0;
|
|
5645
|
+
this.#pollEpoch = void 0;
|
|
5646
|
+
}
|
|
5647
|
+
async #waitRunning(credential, signal) {
|
|
5512
5648
|
const eligible = (await this.#repository.listTasks()).filter((task) => task.taskId !== null && task.credentialEpoch === credential.credentialEpoch && !task.pollBlocked && (task.state === "queued" || task.state === "running" || task.state === "unknown")).filter((task) => task.nextPollAt <= this.#now()).sort((left, right) => left.createdAt - right.createdAt || String(left.taskId).localeCompare(String(right.taskId)));
|
|
5513
5649
|
const start = eligible.length === 0 ? 0 : this.#pollCursor % eligible.length;
|
|
5514
5650
|
const batch = circularSlice(eligible, start, 5);
|
|
5515
5651
|
this.#pollCursor = eligible.length === 0 ? 0 : (start + batch.length) % eligible.length;
|
|
5516
|
-
|
|
5517
|
-
if (!this.#isCredentialEpochCurrent(credential.credentialEpoch))
|
|
5652
|
+
await Promise.all(batch.map(async (record) => {
|
|
5653
|
+
if (!this.#isCredentialEpochCurrent(credential.credentialEpoch)) return;
|
|
5518
5654
|
try {
|
|
5519
|
-
const task = await new PredictionClient({ fetch: this.#fetch }).
|
|
5655
|
+
const task = await new PredictionClient({ fetch: this.#fetch }).waitTask({
|
|
5520
5656
|
taskId: record.taskId,
|
|
5521
5657
|
apiKey: credential.value,
|
|
5522
5658
|
maxAttempts: 1,
|
|
5523
5659
|
...signal === void 0 ? {} : { signal }
|
|
5524
5660
|
});
|
|
5525
|
-
await this.#
|
|
5661
|
+
await this.#serializeObservation(async () => {
|
|
5662
|
+
if (!signal.aborted && this.#isCredentialEpochCurrent(credential.credentialEpoch)) await this.#repository.recordTaskObserved(task);
|
|
5663
|
+
});
|
|
5526
5664
|
} catch (error) {
|
|
5527
5665
|
if (signal?.aborted === true) throw error;
|
|
5528
5666
|
const failure = classifyPollFailure(error, record.pollAttempt + 1, this.#now());
|
|
5529
|
-
await this.#
|
|
5667
|
+
await this.#serializeObservation(async () => {
|
|
5668
|
+
if (!signal.aborted && this.#isCredentialEpochCurrent(credential.credentialEpoch)) await this.#repository.recordPollFailure(record.taskId, failure);
|
|
5669
|
+
});
|
|
5530
5670
|
await this.#markUnauthorized(error, credential.credentialEpoch);
|
|
5531
5671
|
}
|
|
5532
|
-
}
|
|
5672
|
+
}));
|
|
5533
5673
|
return (await this.#repository.listTasks()).some((task) => task.credentialEpoch === credential.credentialEpoch && !task.pollBlocked && (task.state === "queued" || task.state === "running" || task.state === "unknown"));
|
|
5534
5674
|
}
|
|
5535
5675
|
async read(input, signal) {
|
|
5536
5676
|
const session = this.#session(input.sessionId);
|
|
5537
|
-
if (this.#isEnabled()) await this.pollRunning(signal);
|
|
5538
5677
|
const models = await this.#loadModelsSafely(signal);
|
|
5539
5678
|
if (session.selectedModelId === null && models.length > 0) {
|
|
5540
5679
|
const modelId = chooseDefaultModel(models, this.#getLastModel());
|
|
@@ -6563,7 +6702,7 @@ function createSchemaTool(controller) {
|
|
|
6563
6702
|
function createUploadFileTool(client, resolveCredential) {
|
|
6564
6703
|
return defineTool({
|
|
6565
6704
|
name: MODELLIX_MEDIA_UPLOAD_FILE_TOOL,
|
|
6566
|
-
description: "Upload
|
|
6705
|
+
description: "Upload an image attachment from this conversation or a supported image, video, or audio file inside the session workspace. Maximum 16 MiB (16777216 bytes). Supply exactly one of attachment_id or path. Reuse returned URLs and never retry an unknown upload outcome automatically. On quota errors, explicitly select unneeded files for deletion; never clean up automatically.",
|
|
6567
6706
|
parameters: {
|
|
6568
6707
|
attachment_id: {
|
|
6569
6708
|
type: "string",
|
|
@@ -6571,7 +6710,7 @@ function createUploadFileTool(client, resolveCredential) {
|
|
|
6571
6710
|
},
|
|
6572
6711
|
path: {
|
|
6573
6712
|
type: "string",
|
|
6574
|
-
description: "Image path inside the current session workspace."
|
|
6713
|
+
description: "Image, video, or audio path inside the current session workspace."
|
|
6575
6714
|
}
|
|
6576
6715
|
},
|
|
6577
6716
|
output: {
|
|
@@ -7955,6 +8094,7 @@ var ModellixRuntime = class ModellixRuntime {
|
|
|
7955
8094
|
mutate: (operations, expectedRevision) => this.#ctx.settings.mutate(LLM_SETTINGS_NAMESPACE, operations, expectedRevision)
|
|
7956
8095
|
});
|
|
7957
8096
|
this.#design = new DesignHostController({
|
|
8097
|
+
serializeObservation: (operation) => this.enqueueDesignWrite(operation),
|
|
7958
8098
|
storage: designStorage,
|
|
7959
8099
|
resolveCredential: () => this.resolveUsableCredential(),
|
|
7960
8100
|
isCredentialEpochCurrent: (epoch) => epoch === this.#credential.credentialEpoch,
|
|
@@ -7974,6 +8114,7 @@ var ModellixRuntime = class ModellixRuntime {
|
|
|
7974
8114
|
this.#ctx.effect(() => this.#settings.watch((next, previous) => {
|
|
7975
8115
|
if (this.#closing) return void 0;
|
|
7976
8116
|
this.#config = next;
|
|
8117
|
+
if (next.credentialEpoch !== previous.credentialEpoch || !next.services.design.enabled) this.#design.invalidateWaits();
|
|
7977
8118
|
this.#catalog.invalidate();
|
|
7978
8119
|
if (next.services.design.enabled !== previous.services.design.enabled) {
|
|
7979
8120
|
this.syncDesignTools(next.services.design.enabled);
|
|
@@ -8031,6 +8172,7 @@ var ModellixRuntime = class ModellixRuntime {
|
|
|
8031
8172
|
return async () => {
|
|
8032
8173
|
this.#closing = true;
|
|
8033
8174
|
this.#lifecycleAbort.abort();
|
|
8175
|
+
this.#design.invalidateWaits();
|
|
8034
8176
|
this.#disposeDesignTools?.();
|
|
8035
8177
|
this.#disposeDesignTools = void 0;
|
|
8036
8178
|
if (this.#designPollTimer !== void 0) clearTimeout(this.#designPollTimer);
|
|
@@ -8689,7 +8831,10 @@ var ModellixRuntime = class ModellixRuntime {
|
|
|
8689
8831
|
handle: (endpoint, payload, signal) => {
|
|
8690
8832
|
const operationSignal = signal === void 0 ? this.#lifecycleAbort.signal : AbortSignal.any([signal, this.#lifecycleAbort.signal]);
|
|
8691
8833
|
if (designEndpointRequestsCredential(endpoint) && !this.credentialIsUsable()) this.requestCredentialRecovery();
|
|
8692
|
-
return this.enqueueDesignWrite(() => this.#design.handle(endpoint, payload, operationSignal))
|
|
8834
|
+
return this.enqueueDesignWrite(() => this.#design.handle(endpoint, payload, operationSignal)).then((snapshot) => {
|
|
8835
|
+
this.scheduleDesignPoll(0);
|
|
8836
|
+
return snapshot;
|
|
8837
|
+
});
|
|
8693
8838
|
}
|
|
8694
8839
|
});
|
|
8695
8840
|
return;
|
|
@@ -8731,7 +8876,7 @@ var ModellixRuntime = class ModellixRuntime {
|
|
|
8731
8876
|
if (this.#closing || this.#designPollTimer !== void 0) return;
|
|
8732
8877
|
this.#designPollTimer = setTimeout(() => {
|
|
8733
8878
|
this.#designPollTimer = void 0;
|
|
8734
|
-
this
|
|
8879
|
+
this.#design.pollRunning(this.#lifecycleAbort.signal).then((hasRunning) => {
|
|
8735
8880
|
if (hasRunning) this.scheduleDesignPoll(5e3);
|
|
8736
8881
|
}).catch(() => {
|
|
8737
8882
|
if (!this.#closing) this.scheduleDesignPoll(15e3);
|