@lingjingai/scriptctl 0.63.3 → 0.63.4
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 -1
- package/changes/0.63.4.md +5 -0
- package/dist/bin.js +73 -27
- package/dist/bin.js.map +2 -2
- package/dist/llm/router.js +77 -32
- package/dist/llm/router.js.map +1 -1
- package/package.json +1 -1
- package/skills/scriptctl/SKILL.md +4 -2
- package/skills/scriptctl/references/ingest-workflow.md +2 -2
package/README.md
CHANGED
|
@@ -136,7 +136,7 @@ MIT
|
|
|
136
136
|
|
|
137
137
|
### 指定视频理解服务
|
|
138
138
|
|
|
139
|
-
Gemini
|
|
139
|
+
Gemini 文本、结构化工具和视频请求先使用配置的主模型,再按 `gemini-3.8-flash → gemini-3.7-flash → gemini-3.6-flash` 尝试尚未调用的模型,全部失败后使用豆包。每个 Gemini 模型按 provider 的 `maxAttempts` 重试暂时性错误,豆包最多尝试 3 次;审核拒绝等不可重试错误直接进入下一模型,用户取消立即停止。切换 Gemini 模型保留服务地址、凭据和同一份视频;公共 Files API 上传重试耗尽时,由豆包读取本地视频。豆包使用 `ARK_API_KEY`,`ARK_MODEL` 可指定已开通的视频理解端点,默认 `ep-20260721150136-98dj9`。完整模型链耗尽后才汇总报错,调用和用量逐模型记录。直接选择豆包时只调用豆包:
|
|
140
140
|
|
|
141
141
|
```bash
|
|
142
142
|
# 豆包:默认读取 ARK_API_KEY;模型也可通过 ARK_MODEL 设置
|
package/dist/bin.js
CHANGED
|
@@ -7802,6 +7802,7 @@ var DOUBAO_FALLBACK = {
|
|
|
7802
7802
|
timeoutMs: 6e5,
|
|
7803
7803
|
maxAttempts: 3
|
|
7804
7804
|
};
|
|
7805
|
+
var GEMINI_FLASH_MODELS = ["gemini-3.8-flash", "gemini-3.7-flash", "gemini-3.6-flash"];
|
|
7805
7806
|
function normalizeBaseUrl(value) {
|
|
7806
7807
|
return value?.trim().replace(/\/+$/, "") ?? "anthropic-sdk-default";
|
|
7807
7808
|
}
|
|
@@ -7842,9 +7843,9 @@ function routedFailure(error, route) {
|
|
|
7842
7843
|
message: error.message
|
|
7843
7844
|
});
|
|
7844
7845
|
}
|
|
7845
|
-
function
|
|
7846
|
-
|
|
7847
|
-
const aggregate = aggregateFailures(failures, "
|
|
7846
|
+
function exhaustedFailure(error, failures) {
|
|
7847
|
+
if (failures.length === 1) return error;
|
|
7848
|
+
const aggregate = aggregateFailures(failures, "All configured model routes failed.");
|
|
7848
7849
|
return new LlmTransportError(error.code, aggregate.message, {
|
|
7849
7850
|
retryable: aggregate.retryable,
|
|
7850
7851
|
classification: classificationOf(aggregate),
|
|
@@ -7856,6 +7857,19 @@ function fallbackFailure(primary, error, route) {
|
|
|
7856
7857
|
function isGeminiRoute(route) {
|
|
7857
7858
|
return route.protocol === "google-gemini" || /(^|\/)gemini-/i.test(route.model);
|
|
7858
7859
|
}
|
|
7860
|
+
function geminiRoutes(primary) {
|
|
7861
|
+
const prefix = primary.info.model.slice(0, primary.info.model.lastIndexOf("/") + 1);
|
|
7862
|
+
const models = /* @__PURE__ */ new Set([primary.info.model, ...GEMINI_FLASH_MODELS.map((model) => prefix + model)]);
|
|
7863
|
+
return [...models].map((model) => {
|
|
7864
|
+
if (model === primary.info.model) return primary;
|
|
7865
|
+
const modelConfig = { ...primary.modelConfig, model };
|
|
7866
|
+
return {
|
|
7867
|
+
...primary,
|
|
7868
|
+
modelConfig,
|
|
7869
|
+
info: { ...primary.info, model, fingerprint: routeFingerprint(primary.info.task, primary.providerConfig, modelConfig) }
|
|
7870
|
+
};
|
|
7871
|
+
});
|
|
7872
|
+
}
|
|
7859
7873
|
function canFallback(error, signal) {
|
|
7860
7874
|
return !signal?.aborted && error instanceof LlmTransportError && error.code !== "cancelled";
|
|
7861
7875
|
}
|
|
@@ -8162,25 +8176,43 @@ var ModelRouter = class {
|
|
|
8162
8176
|
generate: async (request2) => {
|
|
8163
8177
|
request2.signal?.throwIfAborted();
|
|
8164
8178
|
if (request2.media.kind === "local-fallback") {
|
|
8165
|
-
return this.generateDoubaoVideo(task, request2, request2.media.primaryFailure);
|
|
8179
|
+
return this.generateDoubaoVideo(task, request2, [request2.media.primaryFailure]);
|
|
8166
8180
|
}
|
|
8167
|
-
|
|
8168
|
-
|
|
8169
|
-
|
|
8170
|
-
|
|
8171
|
-
|
|
8181
|
+
const routes = geminiRoutes(route);
|
|
8182
|
+
const failures = [];
|
|
8183
|
+
for (const [index, candidate] of routes.entries()) {
|
|
8184
|
+
request2.signal?.throwIfAborted();
|
|
8185
|
+
try {
|
|
8186
|
+
return await this.tracked(candidate.info, () => this.videoAdapter(candidate).generate({
|
|
8187
|
+
...request2,
|
|
8188
|
+
maxOutputTokens: spec2.maxOutputTokens
|
|
8189
|
+
}));
|
|
8190
|
+
} catch (error) {
|
|
8191
|
+
if (!canFallback(error, request2.signal)) throw error;
|
|
8192
|
+
const failure = routedFailure(error, candidate.info);
|
|
8193
|
+
failures.push(failure);
|
|
8194
|
+
const next = routes[index + 1];
|
|
8195
|
+
if (next) {
|
|
8196
|
+
process.stderr.write(`\u21AA\uFE0F ${candidate.info.model} \u8BF7\u6C42\u5931\u8D25\uFF0C\u5207\u6362\u5230 ${next.info.model} (${formatFailure(failure)})
|
|
8197
|
+
`);
|
|
8198
|
+
continue;
|
|
8199
|
+
}
|
|
8200
|
+
if (!this.doubaoAdapter() || !request2.localPath) throw exhaustedFailure(error, failures);
|
|
8201
|
+
return this.generateDoubaoVideo(task, request2, failures);
|
|
8202
|
+
}
|
|
8172
8203
|
}
|
|
8204
|
+
throw new Error("Gemini model routes cannot be empty.");
|
|
8173
8205
|
}
|
|
8174
8206
|
};
|
|
8175
8207
|
}
|
|
8176
|
-
async generateDoubaoVideo(task, request2,
|
|
8208
|
+
async generateDoubaoVideo(task, request2, failures) {
|
|
8177
8209
|
const adapter = this.doubaoAdapter();
|
|
8178
8210
|
if (!adapter) throw credentialError("doubao");
|
|
8179
8211
|
const localPath = request2.media.kind === "local-fallback" ? request2.media.path : request2.localPath;
|
|
8180
8212
|
if (!localPath) throw configRuntimeError("Doubao video fallback requires the local video.", ["localPath"], ["localPath: missing"]);
|
|
8181
8213
|
const localMime = request2.localMime ?? (request2.media.kind === "file-api" ? request2.media.file.mimeType : request2.media.mimeType);
|
|
8182
8214
|
const route = this.doubaoRoute(task);
|
|
8183
|
-
process.stderr.write(`\u21AA\uFE0F
|
|
8215
|
+
process.stderr.write(`\u21AA\uFE0F Gemini \u8DEF\u7531\u5DF2\u8017\u5C3D\uFF0C\u56DE\u9000\u5230\u8C46\u5305 ${route.model}
|
|
8184
8216
|
`);
|
|
8185
8217
|
try {
|
|
8186
8218
|
return await this.tracked(route, () => adapter.run({
|
|
@@ -8195,7 +8227,7 @@ var ModelRouter = class {
|
|
|
8195
8227
|
}));
|
|
8196
8228
|
} catch (error) {
|
|
8197
8229
|
if (!canFallback(error, request2.signal)) throw error;
|
|
8198
|
-
throw
|
|
8230
|
+
throw exhaustedFailure(error, [...failures, routedFailure(error, route)]);
|
|
8199
8231
|
}
|
|
8200
8232
|
}
|
|
8201
8233
|
// Resolve the default fallback or explicitly selected Doubao video.
|
|
@@ -8224,24 +8256,38 @@ var ModelRouter = class {
|
|
|
8224
8256
|
}
|
|
8225
8257
|
async withTextFallback(task, signal, generate) {
|
|
8226
8258
|
const route = this.resolve(task);
|
|
8227
|
-
const
|
|
8228
|
-
|
|
8229
|
-
|
|
8230
|
-
|
|
8231
|
-
|
|
8232
|
-
const
|
|
8233
|
-
if (!doubao) throw error;
|
|
8234
|
-
const primary = routedFailure(error, route.info);
|
|
8235
|
-
const fallback = this.doubaoRoute(task);
|
|
8236
|
-
process.stderr.write(`\u21AA\uFE0F ${route.info.model} \u8BF7\u6C42\u5931\u8D25\uFF0C\u56DE\u9000\u5230\u8C46\u5305 ${fallback.model} (${formatFailure(primary)})
|
|
8237
|
-
`);
|
|
8259
|
+
const gemini = isGeminiRoute(route.info);
|
|
8260
|
+
const routes = gemini ? geminiRoutes(route) : [route];
|
|
8261
|
+
const failures = [];
|
|
8262
|
+
for (const [index, candidate] of routes.entries()) {
|
|
8263
|
+
signal?.throwIfAborted();
|
|
8264
|
+
const adapter = this.adapter(candidate);
|
|
8238
8265
|
try {
|
|
8239
|
-
return await this.tracked(
|
|
8240
|
-
} catch (
|
|
8241
|
-
if (!canFallback(
|
|
8242
|
-
|
|
8266
|
+
return await this.tracked(candidate.info, () => generate(adapter));
|
|
8267
|
+
} catch (error) {
|
|
8268
|
+
if (!canFallback(error, signal) || !gemini && error.code !== "content_filtered") throw error;
|
|
8269
|
+
const failure = routedFailure(error, candidate.info);
|
|
8270
|
+
failures.push(failure);
|
|
8271
|
+
const next = routes[index + 1];
|
|
8272
|
+
if (next) {
|
|
8273
|
+
process.stderr.write(`\u21AA\uFE0F ${candidate.info.model} \u8BF7\u6C42\u5931\u8D25\uFF0C\u5207\u6362\u5230 ${next.info.model} (${formatFailure(failure)})
|
|
8274
|
+
`);
|
|
8275
|
+
continue;
|
|
8276
|
+
}
|
|
8277
|
+
const doubao = this.doubaoTextAdapter();
|
|
8278
|
+
if (!doubao) throw exhaustedFailure(error, failures);
|
|
8279
|
+
const fallback = this.doubaoRoute(task);
|
|
8280
|
+
process.stderr.write(`\u21AA\uFE0F \u4E3B\u6A21\u578B\u8DEF\u7531\u5DF2\u8017\u5C3D\uFF0C\u56DE\u9000\u5230\u8C46\u5305 ${fallback.model}
|
|
8281
|
+
`);
|
|
8282
|
+
try {
|
|
8283
|
+
return await this.tracked(fallback, () => generate(doubao));
|
|
8284
|
+
} catch (fallbackError) {
|
|
8285
|
+
if (!canFallback(fallbackError, signal)) throw fallbackError;
|
|
8286
|
+
throw exhaustedFailure(fallbackError, [...failures, routedFailure(fallbackError, fallback)]);
|
|
8287
|
+
}
|
|
8243
8288
|
}
|
|
8244
8289
|
}
|
|
8290
|
+
throw new Error("Text model routes cannot be empty.");
|
|
8245
8291
|
}
|
|
8246
8292
|
async generateText(task, request2) {
|
|
8247
8293
|
const spec2 = LLM_TASK_REGISTRY[task];
|