@vibedev-si/dsh-vibedev 0.2.5 → 0.2.7
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 +1 -0
- package/THIRD-PARTY-NOTICES.txt +1 -1
- package/lib/gateway/catalog.d.ts +7 -0
- package/lib/gateway/errors.d.ts +15 -0
- package/lib/index.js +134 -57
- package/lib/tasks/tracker.d.ts +45 -17
- package/lib/tools/common.d.ts +1 -1
- package/lib/video/plan.d.ts +5 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
- 模型在选择器里的「VibeDev」组;默认模型仍是 DeepSeek Harness 原来的,想用 VibeDev 的模型就在选择器里选。
|
|
48
48
|
- 没登录就用了 VibeDev 的模型,或登录已失效时,对话里会提示到侧边栏或设置里登录;余额不足时会提示并给出充值链接。这些提示和 DeepSeek 账号的登录、充值提示是分开的。
|
|
49
49
|
- 访问令牌过期时插件会自动续期并重发请求,不会重复输出。
|
|
50
|
+
- 网关到上游模型服务的连接中途断开(例如 `upstream_http2_stream_error`、上游响应流被中断)属于可恢复的传输故障,会和超时、5xx、限流一样按退避自动重试,不再直接判定成一次不可重试的失败;重试会重新发起该次请求,已经输出的内容不会续写。
|
|
50
51
|
|
|
51
52
|
## 网页搜索
|
|
52
53
|
|
package/THIRD-PARTY-NOTICES.txt
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
THIRD-PARTY NOTICES · @vibedev-si/dsh-vibedev 0.2.
|
|
1
|
+
THIRD-PARTY NOTICES · @vibedev-si/dsh-vibedev 0.2.7
|
|
2
2
|
|
|
3
3
|
This package bundles code from the projects below. Each is used under the licence that
|
|
4
4
|
follows its name; the licence texts are reproduced as the projects ship them.
|
package/lib/gateway/catalog.d.ts
CHANGED
|
@@ -71,6 +71,13 @@ export interface VideoCapabilities {
|
|
|
71
71
|
readonly minReferenceVideoSeconds?: number;
|
|
72
72
|
readonly maxReferenceVideoSeconds?: number;
|
|
73
73
|
readonly maxTotalReferenceVideoSeconds?: number;
|
|
74
|
+
/**
|
|
75
|
+
* Declared limits on a reference AUDIO's own measured length. They are read
|
|
76
|
+
* from the model, never assumed: a lane that declares nothing is not held to
|
|
77
|
+
* another lane's range (some providers take 1–15 s, others take minutes).
|
|
78
|
+
*/
|
|
79
|
+
readonly minReferenceAudioSeconds?: number;
|
|
80
|
+
readonly maxReferenceAudioSeconds?: number;
|
|
74
81
|
/** References must be relayed through the gateway media library. */
|
|
75
82
|
readonly gatewayRelayRequired?: boolean;
|
|
76
83
|
}
|
package/lib/gateway/errors.d.ts
CHANGED
|
@@ -19,6 +19,19 @@ export interface MediaErrorDetails {
|
|
|
19
19
|
readonly retryAfterMs?: number;
|
|
20
20
|
/** The gateway was full and created nothing (a busy 429/503). */
|
|
21
21
|
readonly busy?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* The gateway's structured submission state: `rejected` (the provider refused
|
|
24
|
+
* the request; nothing exists under the key), `unknown` (the gateway cannot
|
|
25
|
+
* tell whether the provider received it) or `accepted` (a task exists).
|
|
26
|
+
* Absent on the older protocol, where the status is all there is.
|
|
27
|
+
*/
|
|
28
|
+
readonly submissionState?: string;
|
|
29
|
+
/**
|
|
30
|
+
* What the gateway asks next: `none` (do not try again), `reconcile` (settle
|
|
31
|
+
* the same key's outcome, do not create a task) or `retry`. `retryable` alone
|
|
32
|
+
* only says recovery is still needed; it never means "create another task".
|
|
33
|
+
*/
|
|
34
|
+
readonly retryAction?: string;
|
|
22
35
|
/** On a task-limit refusal: tasks in progress and the account's limit. */
|
|
23
36
|
readonly active?: number;
|
|
24
37
|
readonly limit?: number;
|
|
@@ -48,6 +61,8 @@ export interface GatewayFailure {
|
|
|
48
61
|
readonly message?: string;
|
|
49
62
|
readonly field?: string;
|
|
50
63
|
readonly retryable?: boolean;
|
|
64
|
+
readonly submissionState?: string;
|
|
65
|
+
readonly retryAction?: string;
|
|
51
66
|
readonly rechargeUrl?: string;
|
|
52
67
|
readonly active?: number;
|
|
53
68
|
readonly limit?: number;
|
package/lib/index.js
CHANGED
|
@@ -297,11 +297,15 @@ function parseGatewayFailure(body) {
|
|
|
297
297
|
availableCny: amount$1(source.available_cny),
|
|
298
298
|
pendingCny: amount$1(source.pending_cny)
|
|
299
299
|
};
|
|
300
|
+
const submissionState = text$7(source.submission_state) ?? text$7(root.submission_state);
|
|
301
|
+
const retryAction = text$7(source.retry_action) ?? text$7(root.retry_action);
|
|
300
302
|
return {
|
|
301
303
|
...code === void 0 ? {} : { code },
|
|
302
304
|
...message === void 0 ? {} : { message },
|
|
303
305
|
...field === void 0 ? {} : { field },
|
|
304
306
|
...typeof source.retryable === "boolean" ? { retryable: source.retryable } : {},
|
|
307
|
+
...submissionState === void 0 ? {} : { submissionState: submissionState.toLowerCase() },
|
|
308
|
+
...retryAction === void 0 ? {} : { retryAction: retryAction.toLowerCase() },
|
|
305
309
|
...rechargeUrl === void 0 ? {} : { rechargeUrl },
|
|
306
310
|
...Object.fromEntries(Object.entries({
|
|
307
311
|
...numbers,
|
|
@@ -2051,6 +2055,7 @@ function classifyPiAiError(message) {
|
|
|
2051
2055
|
if (/\b5\d\d\b/.test(message)) return "SERVER";
|
|
2052
2056
|
if (/\btime(?:d)?\s*out\b|timeout/i.test(message)) return "TIMEOUT";
|
|
2053
2057
|
if (/stream ended (?:before|without)\b/i.test(message)) return "TRANSPORT";
|
|
2058
|
+
if (/\bhttp\/?2\b/i.test(message) || /upstream_(?:http2|stream)_\w*_?error/i.test(message) || /\bstream (?:was )?(?:interrupted|disconnected|reset)\b|\bstream failed\b/i.test(message)) return "TRANSPORT";
|
|
2054
2059
|
if (/\b(?:network|connection|socket|fetch)\b|\bECONN[A-Z]+\b/i.test(message) || /\b(?:other side closed|HTTP2 request did not get a response|WebSocket closed unexpectedly)\b/i.test(message) || /\bterminated\b|premature close/i.test(message)) return "TRANSPORT";
|
|
2055
2060
|
return "PI_AI_ERROR";
|
|
2056
2061
|
}
|
|
@@ -3344,6 +3349,8 @@ function parseVideoCapabilities(raw) {
|
|
|
3344
3349
|
["maxAssetBytes", "max_asset_bytes"],
|
|
3345
3350
|
["minReferenceVideoSeconds", "min_reference_video_duration_seconds"],
|
|
3346
3351
|
["maxReferenceVideoSeconds", "max_reference_video_duration_seconds"],
|
|
3352
|
+
["minReferenceAudioSeconds", "min_reference_audio_duration_seconds"],
|
|
3353
|
+
["maxReferenceAudioSeconds", "max_reference_audio_duration_seconds"],
|
|
3347
3354
|
["maxTotalReferenceVideoSeconds", "max_total_reference_video_duration_seconds"]
|
|
3348
3355
|
]) {
|
|
3349
3356
|
const value = positive(field(raw, snake));
|
|
@@ -4523,7 +4530,7 @@ function startTaskJob(runtime, exec, record) {
|
|
|
4523
4530
|
/**
|
|
4524
4531
|
* Submit a task and start its report. An unconfirmed submission is not an
|
|
4525
4532
|
* error here: the agent must not resubmit it, so it is reported like any
|
|
4526
|
-
* other task while the tracker
|
|
4533
|
+
* other task while the tracker settles that one submission under the same key.
|
|
4527
4534
|
* @param runtime - the media runtime.
|
|
4528
4535
|
* @param exec - the tool call.
|
|
4529
4536
|
* @param draft - the task to submit.
|
|
@@ -4547,7 +4554,7 @@ async function submitTask(runtime, exec, draft) {
|
|
|
4547
4554
|
};
|
|
4548
4555
|
}
|
|
4549
4556
|
/** The sentence about an unconfirmed submission. */
|
|
4550
|
-
const UNCONFIRMED_NOTE = "The gateway did not confirm the submission; dsh-vibedev keeps
|
|
4557
|
+
const UNCONFIRMED_NOTE = "The gateway did not confirm the submission; dsh-vibedev keeps settling that same submission under the same request key for a few minutes, so no second task can be created from it. Do not submit it again.";
|
|
4551
4558
|
/**
|
|
4552
4559
|
* Wait for a task in the tool call itself, for hosts without background jobs.
|
|
4553
4560
|
* @param runtime - the media runtime.
|
|
@@ -4933,6 +4940,7 @@ function describeVideoModel(model) {
|
|
|
4933
4940
|
const limits = [];
|
|
4934
4941
|
if (video.minReferenceVideoSeconds !== void 0 || video.maxReferenceVideoSeconds !== void 0) limits.push(`each reference video ${video.minReferenceVideoSeconds ?? 0}-${video.maxReferenceVideoSeconds ?? "∞"} s`);
|
|
4935
4942
|
if (video.maxTotalReferenceVideoSeconds !== void 0) limits.push(`reference videos at most ${video.maxTotalReferenceVideoSeconds} s in total`);
|
|
4943
|
+
if (video.minReferenceAudioSeconds !== void 0 || video.maxReferenceAudioSeconds !== void 0) limits.push(`each reference audio ${video.minReferenceAudioSeconds ?? 0}-${video.maxReferenceAudioSeconds ?? "∞"} s`);
|
|
4936
4944
|
if (video.combinations !== void 0) limits.push(`allowed combinations: ${video.combinations.map((c) => [
|
|
4937
4945
|
c.duration === void 0 ? "" : `${c.duration}s`,
|
|
4938
4946
|
c.ratio ?? "",
|
|
@@ -5090,12 +5098,15 @@ function referenceByteLimit(model, input) {
|
|
|
5090
5098
|
return Math.min(kindMax ?? Infinity, video.maxAssetBytes ?? Infinity, MEDIA_LIBRARY_MAX_BYTES);
|
|
5091
5099
|
}
|
|
5092
5100
|
/**
|
|
5093
|
-
* Check reference
|
|
5101
|
+
* Check the measured lengths of the reference media once the gateway has
|
|
5102
|
+
* measured them. A reference's OWN length is what is checked — never the
|
|
5103
|
+
* requested output duration — and only against the limits that model declares.
|
|
5094
5104
|
* @param model - the video model.
|
|
5095
5105
|
* @param videos - the uploaded reference videos, in order.
|
|
5106
|
+
* @param audios - the uploaded reference audios, in order.
|
|
5096
5107
|
* @throws {@link MediaError} when a clip or the total is outside the model's limits.
|
|
5097
5108
|
*/
|
|
5098
|
-
function checkReferenceDurations(model, videos) {
|
|
5109
|
+
function checkReferenceDurations(model, videos, audios = []) {
|
|
5099
5110
|
const video = model.video ?? {};
|
|
5100
5111
|
let totalMs = 0;
|
|
5101
5112
|
for (const [index, clip] of videos.entries()) {
|
|
@@ -5109,6 +5120,16 @@ function checkReferenceDurations(model, videos) {
|
|
|
5109
5120
|
totalMs += clip.durationMs;
|
|
5110
5121
|
}
|
|
5111
5122
|
if (video.maxTotalReferenceVideoSeconds !== void 0 && totalMs / 1e3 > video.maxTotalReferenceVideoSeconds) throw new MediaError(`The reference videos add up to ${(totalMs / 1e3).toFixed(2)} s; ${model.id} takes at most ${video.maxTotalReferenceVideoSeconds} s in total.`, "REFERENCE_TOTAL_TOO_LONG", { field: "reference_videos" });
|
|
5123
|
+
const audioLimits = video.minReferenceAudioSeconds !== void 0 || video.maxReferenceAudioSeconds !== void 0;
|
|
5124
|
+
for (const [index, clip] of audios.entries()) {
|
|
5125
|
+
if (clip.durationMs === void 0) {
|
|
5126
|
+
if (audioLimits) throw new MediaError(`The gateway could not measure the length of reference audio ${index + 1} ("${clip.source}"), so ${model.id}'s length limits cannot be checked. Export it as a standard MP3 or WAV and try again.`, "REFERENCE_DURATION_UNKNOWN", { field: "reference_audios" });
|
|
5127
|
+
continue;
|
|
5128
|
+
}
|
|
5129
|
+
const seconds = clip.durationMs / 1e3;
|
|
5130
|
+
if (video.minReferenceAudioSeconds !== void 0 && seconds < video.minReferenceAudioSeconds) throw new MediaError(`Reference audio ${index + 1} ("${clip.source}") is ${seconds.toFixed(2)} s; ${model.id} needs at least ${video.minReferenceAudioSeconds} s of reference audio.`, "REFERENCE_AUDIO_TOO_SHORT", { field: "reference_audios" });
|
|
5131
|
+
if (video.maxReferenceAudioSeconds !== void 0 && seconds > video.maxReferenceAudioSeconds) throw new MediaError(`Reference audio ${index + 1} ("${clip.source}") is ${seconds.toFixed(2)} s; ${model.id} takes at most ${video.maxReferenceAudioSeconds} s of reference audio per clip. Trim it first, or use a shorter excerpt.`, "REFERENCE_AUDIO_TOO_LONG", { field: "reference_audios" });
|
|
5132
|
+
}
|
|
5112
5133
|
}
|
|
5113
5134
|
/**
|
|
5114
5135
|
* The `/v1/videos` body for a plan whose inputs are uploaded. The gateway reads
|
|
@@ -5259,7 +5280,12 @@ async function prepareVideoTask(runtime, call, request, save) {
|
|
|
5259
5280
|
bytes: item.media.data.byteLength,
|
|
5260
5281
|
...assets[index]?.durationMs === void 0 ? {} : { durationMs: assets[index]?.durationMs }
|
|
5261
5282
|
}] : []);
|
|
5262
|
-
checkReferenceDurations(model, videos)
|
|
5283
|
+
checkReferenceDurations(model, videos, loaded.flatMap((item, index) => item.input === "referenceAudios" ? [{
|
|
5284
|
+
source: item.media.source,
|
|
5285
|
+
mime: item.media.mime,
|
|
5286
|
+
bytes: item.media.data.byteLength,
|
|
5287
|
+
...assets[index]?.durationMs === void 0 ? {} : { durationMs: assets[index]?.durationMs }
|
|
5288
|
+
}] : []));
|
|
5263
5289
|
const urls = {};
|
|
5264
5290
|
for (const [index, item] of loaded.entries()) {
|
|
5265
5291
|
const asset = assets[index];
|
|
@@ -5897,13 +5923,21 @@ function parseAudioTask(body) {
|
|
|
5897
5923
|
* background job, so a task keeps being followed after its job is stopped and
|
|
5898
5924
|
* after the host restarts.
|
|
5899
5925
|
*
|
|
5900
|
-
* Money rules
|
|
5901
|
-
*
|
|
5902
|
-
*
|
|
5903
|
-
* and
|
|
5904
|
-
*
|
|
5905
|
-
*
|
|
5906
|
-
*
|
|
5926
|
+
* Money rules, and the rule that one operation creates at most one task:
|
|
5927
|
+
*
|
|
5928
|
+
* - A submission is settled by the gateway's structured verdict
|
|
5929
|
+
* (`submission_state` and `retry_action`) first, and only by the HTTP status
|
|
5930
|
+
* when it sends none. `rejected`/`retry_action: none` is final: the reason is
|
|
5931
|
+
* saved and nothing is sent again — not by the tool, not by the background
|
|
5932
|
+
* loop, not after a restart. `retryable` on its own never authorizes creating
|
|
5933
|
+
* another task.
|
|
5934
|
+
* - An unknown outcome keeps the record and settles it under the SAME key and
|
|
5935
|
+
* the SAME body, which the gateway answers with the original task when there
|
|
5936
|
+
* is one. A new key is minted only for a new request the user asks for.
|
|
5937
|
+
* - Once a gateway task id is known the task is only polled; a failed poll
|
|
5938
|
+
* never submits anything.
|
|
5939
|
+
* - A refusal that left nothing under the key ends the record as failed with
|
|
5940
|
+
* the gateway's own reason, so the person sees why.
|
|
5907
5941
|
* @module dsh-vibedev/tasks/tracker
|
|
5908
5942
|
*/
|
|
5909
5943
|
const SPECS = {
|
|
@@ -5944,34 +5978,74 @@ const SUBMIT_TIMEOUT_MS = 3e5;
|
|
|
5944
5978
|
const SUBMISSION_AMBIGUOUS = "VIDEO_SUBMISSION_AMBIGUOUS";
|
|
5945
5979
|
/** The same key was sent with a different body: a plugin bug, never retried. */
|
|
5946
5980
|
const KEY_CONFLICT = /_IDEMPOTENCY_CONFLICT$/;
|
|
5981
|
+
/** Structured verdicts the gateway sends; an unknown word falls back to the older protocol. */
|
|
5982
|
+
const REJECTED_STATES = /* @__PURE__ */ new Set([
|
|
5983
|
+
"rejected",
|
|
5984
|
+
"refused",
|
|
5985
|
+
"denied"
|
|
5986
|
+
]);
|
|
5987
|
+
const RECONCILE_STATES = /* @__PURE__ */ new Set(["unknown", "accepted"]);
|
|
5988
|
+
/** `retry_action` values that keep the same key: a lookup of this submission, never a new one. */
|
|
5989
|
+
const SAME_KEY_ACTIONS = /* @__PURE__ */ new Set([
|
|
5990
|
+
"reconcile",
|
|
5991
|
+
"retry_same_key",
|
|
5992
|
+
"retry_same"
|
|
5993
|
+
]);
|
|
5947
5994
|
/**
|
|
5948
|
-
*
|
|
5949
|
-
*
|
|
5950
|
-
*
|
|
5995
|
+
* What a failed submission means.
|
|
5996
|
+
*
|
|
5997
|
+
* The two STOP rules are read before anything that would keep the operation
|
|
5998
|
+
* alive, so no verdict can talk the plugin into sending a refused request
|
|
5999
|
+
* again: `retry_action: none` (the provider refused it) and
|
|
6000
|
+
* `VIDEO_SUBMISSION_AMBIGUOUS` (the gateway's key answers this forever) end the
|
|
6001
|
+
* operation even when the same answer also says the outcome is unknown.
|
|
6002
|
+
*
|
|
6003
|
+
* After those, the gateway's structured verdict decides, and only when it
|
|
6004
|
+
* sends none do the HTTP status and the older codes. `retryable` says recovery
|
|
6005
|
+
* is still needed; it never authorizes creating another task.
|
|
5951
6006
|
* @param error - the failure.
|
|
5952
|
-
* @returns
|
|
6007
|
+
* @returns how it may be taken further.
|
|
5953
6008
|
*/
|
|
5954
|
-
function
|
|
5955
|
-
if (!(error instanceof MediaError)) return
|
|
5956
|
-
|
|
5957
|
-
if (
|
|
5958
|
-
|
|
5959
|
-
|
|
6009
|
+
function submissionRecovery(error) {
|
|
6010
|
+
if (!(error instanceof MediaError)) return "rejected";
|
|
6011
|
+
const { submissionState, retryAction, status, busy, retryable } = error.details;
|
|
6012
|
+
if (retryAction === "none") return "rejected";
|
|
6013
|
+
if (error.code === SUBMISSION_AMBIGUOUS || KEY_CONFLICT.test(error.code)) return "rejected";
|
|
6014
|
+
if (submissionState !== void 0 && REJECTED_STATES.has(submissionState)) return "rejected";
|
|
6015
|
+
if (retryAction !== void 0 && SAME_KEY_ACTIONS.has(retryAction)) return "reconcile";
|
|
6016
|
+
if (retryAction === "retry") return "wait";
|
|
6017
|
+
if (submissionState !== void 0 && RECONCILE_STATES.has(submissionState)) return "reconcile";
|
|
6018
|
+
if (NO_ANSWER_CODES.has(error.code)) return "reconcile";
|
|
6019
|
+
if (busy === true) return "wait";
|
|
6020
|
+
if (status !== void 0 && status >= 400 && status < 500) return status === 409 ? "reconcile" : "rejected";
|
|
6021
|
+
if (status !== void 0 && status >= 500) return retryable === false ? "rejected" : "reconcile";
|
|
6022
|
+
return "rejected";
|
|
5960
6023
|
}
|
|
5961
6024
|
/**
|
|
5962
|
-
* The record
|
|
6025
|
+
* The failure to record when a submission is refused for good.
|
|
6026
|
+
*
|
|
6027
|
+
* None of these tells the person to generate again: in the two cases where the
|
|
6028
|
+
* provider may already hold the request, a new generation would be a second
|
|
6029
|
+
* paid task, so the wording asks them to check the records first.
|
|
5963
6030
|
* @param error - the failure.
|
|
5964
|
-
* @returns the
|
|
6031
|
+
* @returns the code and message to keep with the task.
|
|
5965
6032
|
*/
|
|
5966
|
-
function
|
|
5967
|
-
if (!(error instanceof MediaError)) return
|
|
6033
|
+
function terminalFailure(error) {
|
|
6034
|
+
if (!(error instanceof MediaError)) return {
|
|
6035
|
+
code: "SUBMISSION_REJECTED",
|
|
6036
|
+
message: messageOf(error)
|
|
6037
|
+
};
|
|
5968
6038
|
if (error.code === SUBMISSION_AMBIGUOUS) return {
|
|
5969
6039
|
code: error.code,
|
|
5970
|
-
message: "The gateway could not confirm
|
|
6040
|
+
message: "The gateway could not confirm this submission with the provider, so it created no task and charged nothing, and this request key will answer the same way. If the provider did receive it, that task is not visible here. Check the task list and the account usage before generating the same thing again."
|
|
5971
6041
|
};
|
|
5972
6042
|
if (KEY_CONFLICT.test(error.code)) return {
|
|
5973
6043
|
code: error.code,
|
|
5974
|
-
message: "The gateway refused the request: its key was already used for a different request. Nothing was charged
|
|
6044
|
+
message: "The gateway refused the request: its key was already used for a different request. Nothing was charged. Check the task list for the earlier request before generating again."
|
|
6045
|
+
};
|
|
6046
|
+
return {
|
|
6047
|
+
code: error.code,
|
|
6048
|
+
message: error.message
|
|
5975
6049
|
};
|
|
5976
6050
|
}
|
|
5977
6051
|
function sleepFor(ms, signal) {
|
|
@@ -6084,8 +6158,14 @@ var TaskTracker = class {
|
|
|
6084
6158
|
await this.releaseClaim(draft.id);
|
|
6085
6159
|
throw error;
|
|
6086
6160
|
}
|
|
6087
|
-
const
|
|
6088
|
-
if (
|
|
6161
|
+
const recovery = submissionRecovery(error);
|
|
6162
|
+
if (recovery === "rejected") {
|
|
6163
|
+
const settled = terminalFailure(error);
|
|
6164
|
+
if (!unknown) {
|
|
6165
|
+
this.dropClaim(draft.id);
|
|
6166
|
+
await this.options.store.remove(draft.id);
|
|
6167
|
+
throw error;
|
|
6168
|
+
}
|
|
6089
6169
|
await this.finish(draft.id, {
|
|
6090
6170
|
status: "failed",
|
|
6091
6171
|
error: settled
|
|
@@ -6093,14 +6173,10 @@ var TaskTracker = class {
|
|
|
6093
6173
|
await this.releaseClaim(draft.id);
|
|
6094
6174
|
throw new MediaError(settled.message, settled.code, { retryable: false }, { cause: error });
|
|
6095
6175
|
}
|
|
6096
|
-
if (!
|
|
6097
|
-
|
|
6098
|
-
|
|
6099
|
-
|
|
6100
|
-
throw error;
|
|
6101
|
-
}
|
|
6102
|
-
lastError = error;
|
|
6103
|
-
break;
|
|
6176
|
+
if (!unknown && recovery === "wait") {
|
|
6177
|
+
this.dropClaim(draft.id);
|
|
6178
|
+
await this.options.store.remove(draft.id);
|
|
6179
|
+
throw error;
|
|
6104
6180
|
}
|
|
6105
6181
|
unknown = true;
|
|
6106
6182
|
lastError = error;
|
|
@@ -6128,7 +6204,7 @@ var TaskTracker = class {
|
|
|
6128
6204
|
}
|
|
6129
6205
|
}
|
|
6130
6206
|
this.followQuietly(draft.id);
|
|
6131
|
-
throw new MediaError(`The VibeDev gateway did not confirm the request (${messageOf(lastError)}). dsh-vibedev keeps
|
|
6207
|
+
throw new MediaError(`The VibeDev gateway did not confirm the request (${messageOf(lastError)}). dsh-vibedev keeps settling it under the same request key for a few minutes — that key can only ever answer with the one task it may have created — and saves the result if it runs. Check media_tasks for task ${draft.id}; do not submit the same request again.`, "SUBMISSION_UNCONFIRMED", { retryable: false });
|
|
6132
6208
|
}
|
|
6133
6209
|
/**
|
|
6134
6210
|
* Follow a recorded task until it ends or the tracker is disposed. A task
|
|
@@ -6294,24 +6370,25 @@ var TaskTracker = class {
|
|
|
6294
6370
|
await this.update(record.id, { progress: "waiting for a VibeDev sign-in" });
|
|
6295
6371
|
return 6e4;
|
|
6296
6372
|
}
|
|
6297
|
-
|
|
6298
|
-
|
|
6299
|
-
|
|
6300
|
-
|
|
6301
|
-
|
|
6302
|
-
|
|
6303
|
-
|
|
6304
|
-
|
|
6305
|
-
|
|
6306
|
-
|
|
6307
|
-
|
|
6308
|
-
|
|
6309
|
-
|
|
6310
|
-
|
|
6311
|
-
|
|
6312
|
-
}
|
|
6313
|
-
|
|
6314
|
-
|
|
6373
|
+
if (submitting) {
|
|
6374
|
+
if (submissionRecovery(error) === "rejected") {
|
|
6375
|
+
await this.finish(record.id, {
|
|
6376
|
+
status: "failed",
|
|
6377
|
+
error: terminalFailure(error)
|
|
6378
|
+
});
|
|
6379
|
+
return;
|
|
6380
|
+
}
|
|
6381
|
+
if (failures >= CONSECUTIVE_FAILURE_LIMIT) {
|
|
6382
|
+
await this.finish(record.id, {
|
|
6383
|
+
status: "failed",
|
|
6384
|
+
error: {
|
|
6385
|
+
code: code ?? "SUBMISSION_FAILED",
|
|
6386
|
+
message: `The submission could not be settled ${failures} times in a row: ${messageOf(error)}`
|
|
6387
|
+
}
|
|
6388
|
+
});
|
|
6389
|
+
return;
|
|
6390
|
+
}
|
|
6391
|
+
return Math.min(6e4, 5e3 * 2 ** Math.min(failures - 1, 4));
|
|
6315
6392
|
}
|
|
6316
6393
|
if (status === 402) {
|
|
6317
6394
|
await this.update(record.id, { progress: "paused: the VibeDev balance is used up; top up to receive the result" });
|
package/lib/tasks/tracker.d.ts
CHANGED
|
@@ -5,13 +5,21 @@
|
|
|
5
5
|
* background job, so a task keeps being followed after its job is stopped and
|
|
6
6
|
* after the host restarts.
|
|
7
7
|
*
|
|
8
|
-
* Money rules
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* and
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
8
|
+
* Money rules, and the rule that one operation creates at most one task:
|
|
9
|
+
*
|
|
10
|
+
* - A submission is settled by the gateway's structured verdict
|
|
11
|
+
* (`submission_state` and `retry_action`) first, and only by the HTTP status
|
|
12
|
+
* when it sends none. `rejected`/`retry_action: none` is final: the reason is
|
|
13
|
+
* saved and nothing is sent again — not by the tool, not by the background
|
|
14
|
+
* loop, not after a restart. `retryable` on its own never authorizes creating
|
|
15
|
+
* another task.
|
|
16
|
+
* - An unknown outcome keeps the record and settles it under the SAME key and
|
|
17
|
+
* the SAME body, which the gateway answers with the original task when there
|
|
18
|
+
* is one. A new key is minted only for a new request the user asks for.
|
|
19
|
+
* - Once a gateway task id is known the task is only polled; a failed poll
|
|
20
|
+
* never submits anything.
|
|
21
|
+
* - A refusal that left nothing under the key ends the record as failed with
|
|
22
|
+
* the gateway's own reason, so the person sees why.
|
|
15
23
|
* @module dsh-vibedev/tasks/tracker
|
|
16
24
|
*/
|
|
17
25
|
import type { GatewayHttp } from '../gateway/http.js';
|
|
@@ -27,13 +35,12 @@ export interface TrackerOptions {
|
|
|
27
35
|
readonly sleep?: (ms: number, signal: AbortSignal) => Promise<void>;
|
|
28
36
|
/** Give up on a task this long after it was created. Defaults to 3 hours. */
|
|
29
37
|
readonly maxAgeMs?: number;
|
|
30
|
-
/** An unconfirmed submission is resent until it is this old. Defaults to 5 minutes. */
|
|
31
|
-
readonly resendWindowMs?: number;
|
|
32
38
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
39
|
+
* How long an unconfirmed submission is reconciled under its own key before
|
|
40
|
+
* the task is given up. Defaults to 5 minutes: this settles what became of
|
|
41
|
+
* that one submission, it never creates another task.
|
|
35
42
|
*/
|
|
36
|
-
readonly
|
|
43
|
+
readonly resendWindowMs?: number;
|
|
37
44
|
/** Download attempts for a finished task before its links are reported instead. Defaults to 5. */
|
|
38
45
|
readonly downloadAttempts?: number;
|
|
39
46
|
/** The largest output downloaded. Defaults to 2 GiB. */
|
|
@@ -42,13 +49,34 @@ export interface TrackerOptions {
|
|
|
42
49
|
readonly claimStaleMs?: number;
|
|
43
50
|
}
|
|
44
51
|
/**
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* `
|
|
52
|
+
* How a failed submission may be taken further.
|
|
53
|
+
*
|
|
54
|
+
* - `rejected`: the request is refused for good (a parameter refusal, a
|
|
55
|
+
* provider-side rejection, a key conflict); nothing exists under the key and
|
|
56
|
+
* nothing may be created again for this operation.
|
|
57
|
+
* - `reconcile`: the outcome is unknown — the same key and the same body are
|
|
58
|
+
* sent again only to settle what became of that one submission. This never
|
|
59
|
+
* mints a new key and never asks the provider for a new task.
|
|
60
|
+
* - `wait`: nothing was created and the same request may be sent later (busy,
|
|
61
|
+
* rate limited, no balance); the key stays the same.
|
|
62
|
+
*/
|
|
63
|
+
export type SubmissionRecovery = 'rejected' | 'reconcile' | 'wait';
|
|
64
|
+
/**
|
|
65
|
+
* What a failed submission means.
|
|
66
|
+
*
|
|
67
|
+
* The two STOP rules are read before anything that would keep the operation
|
|
68
|
+
* alive, so no verdict can talk the plugin into sending a refused request
|
|
69
|
+
* again: `retry_action: none` (the provider refused it) and
|
|
70
|
+
* `VIDEO_SUBMISSION_AMBIGUOUS` (the gateway's key answers this forever) end the
|
|
71
|
+
* operation even when the same answer also says the outcome is unknown.
|
|
72
|
+
*
|
|
73
|
+
* After those, the gateway's structured verdict decides, and only when it
|
|
74
|
+
* sends none do the HTTP status and the older codes. `retryable` says recovery
|
|
75
|
+
* is still needed; it never authorizes creating another task.
|
|
48
76
|
* @param error - the failure.
|
|
49
|
-
* @returns
|
|
77
|
+
* @returns how it may be taken further.
|
|
50
78
|
*/
|
|
51
|
-
export declare function
|
|
79
|
+
export declare function submissionRecovery(error: unknown): SubmissionRecovery;
|
|
52
80
|
/**
|
|
53
81
|
* The live progress line of a running task.
|
|
54
82
|
* @param task - the gateway's view.
|
package/lib/tools/common.d.ts
CHANGED
|
@@ -58,7 +58,7 @@ export interface Submitted {
|
|
|
58
58
|
/**
|
|
59
59
|
* Submit a task and start its report. An unconfirmed submission is not an
|
|
60
60
|
* error here: the agent must not resubmit it, so it is reported like any
|
|
61
|
-
* other task while the tracker
|
|
61
|
+
* other task while the tracker settles that one submission under the same key.
|
|
62
62
|
* @param runtime - the media runtime.
|
|
63
63
|
* @param exec - the tool call.
|
|
64
64
|
* @param draft - the task to submit.
|
package/lib/video/plan.d.ts
CHANGED
|
@@ -78,12 +78,15 @@ export declare function checkReferenceFile(model: MediaModel, input: VideoInput,
|
|
|
78
78
|
*/
|
|
79
79
|
export declare function referenceByteLimit(model: MediaModel, input: VideoInput): number;
|
|
80
80
|
/**
|
|
81
|
-
* Check reference
|
|
81
|
+
* Check the measured lengths of the reference media once the gateway has
|
|
82
|
+
* measured them. A reference's OWN length is what is checked — never the
|
|
83
|
+
* requested output duration — and only against the limits that model declares.
|
|
82
84
|
* @param model - the video model.
|
|
83
85
|
* @param videos - the uploaded reference videos, in order.
|
|
86
|
+
* @param audios - the uploaded reference audios, in order.
|
|
84
87
|
* @throws {@link MediaError} when a clip or the total is outside the model's limits.
|
|
85
88
|
*/
|
|
86
|
-
export declare function checkReferenceDurations(model: MediaModel, videos: readonly ReferenceMedia[]): void;
|
|
89
|
+
export declare function checkReferenceDurations(model: MediaModel, videos: readonly ReferenceMedia[], audios?: readonly ReferenceMedia[]): void;
|
|
87
90
|
/**
|
|
88
91
|
* The `/v1/videos` body for a plan whose inputs are uploaded. The gateway reads
|
|
89
92
|
* one vocabulary and translates it per provider.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibedev-si/dsh-vibedev",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.7",
|
|
4
4
|
"description": "VibeDev 账号与模型:在 DeepSeek Harness 里登录 VibeDev,与 DeepSeek 账号并存,使用 VibeDev 网关的模型(Claude、GPT 等),并生成图片、视频、音乐和播客 · VibeDev account and models for DeepSeek Harness: sign in to VibeDev next to the DeepSeek account and use the VibeDev gateway's models (Claude, GPT and more), plus image, video, music and podcast generation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|