@lingjingai/scriptctl 0.63.2 → 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.3.md +5 -0
- package/changes/0.63.4.md +5 -0
- package/dist/bin.js +166 -19
- package/dist/bin.js.map +3 -3
- package/dist/llm/router.d.ts +3 -2
- package/dist/llm/router.js +144 -22
- package/dist/llm/router.js.map +1 -1
- package/package.json +1 -1
- package/skills/scriptctl/SKILL.md +6 -0
- package/skills/scriptctl/references/ingest-workflow.md +4 -3
- package/dist/cli/register-batch.d.ts +0 -3
- package/dist/cli/register-batch.js +0 -28
- package/dist/cli/register-batch.js.map +0 -1
- package/dist/cli/register-edits.d.ts +0 -4
- package/dist/cli/register-edits.js +0 -234
- package/dist/cli/register-edits.js.map +0 -1
- package/dist/cli/register-query.d.ts +0 -4
- package/dist/cli/register-query.js +0 -95
- package/dist/cli/register-query.js.map +0 -1
- package/dist/infra/llm/doubao-video-model.d.ts +0 -56
- package/dist/infra/llm/doubao-video-model.js +0 -231
- package/dist/infra/llm/doubao-video-model.js.map +0 -1
package/README.md
CHANGED
|
@@ -136,7 +136,7 @@ MIT
|
|
|
136
136
|
|
|
137
137
|
### 指定视频理解服务
|
|
138
138
|
|
|
139
|
-
|
|
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
|
@@ -265,6 +265,23 @@ function contextOf(failure) {
|
|
|
265
265
|
...failure.details !== void 0 ? { details: failure.details } : {}
|
|
266
266
|
};
|
|
267
267
|
}
|
|
268
|
+
function aggregateFailures(failures, message2) {
|
|
269
|
+
if (failures.length === 0) {
|
|
270
|
+
return createOperationalFailure(
|
|
271
|
+
{ failure_type: "unknown", failure_code: "unknown" },
|
|
272
|
+
{ retryable: false, message: message2 }
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
const first = failures[0];
|
|
276
|
+
const sameClassification = failures.every(
|
|
277
|
+
(failure) => failure.failure_type === first.failure_type && failure.failure_code === first.failure_code
|
|
278
|
+
);
|
|
279
|
+
const classification = sameClassification ? classificationOf(first) : { failure_type: "provider", failure_code: "attempts_exhausted" };
|
|
280
|
+
return createOperationalFailure(classification, {
|
|
281
|
+
retryable: failures.some((failure) => failure.retryable),
|
|
282
|
+
message: message2
|
|
283
|
+
});
|
|
284
|
+
}
|
|
268
285
|
function formatFailure(failure) {
|
|
269
286
|
const route = failure.provider || failure.model ? `${failure.provider ?? "provider"}/${failure.model ?? "model"} ` : "";
|
|
270
287
|
const reason = failure.provider_reason ? ` provider_reason=${failure.provider_reason}` : "";
|
|
@@ -7785,6 +7802,7 @@ var DOUBAO_FALLBACK = {
|
|
|
7785
7802
|
timeoutMs: 6e5,
|
|
7786
7803
|
maxAttempts: 3
|
|
7787
7804
|
};
|
|
7805
|
+
var GEMINI_FLASH_MODELS = ["gemini-3.8-flash", "gemini-3.7-flash", "gemini-3.6-flash"];
|
|
7788
7806
|
function normalizeBaseUrl(value) {
|
|
7789
7807
|
return value?.trim().replace(/\/+$/, "") ?? "anthropic-sdk-default";
|
|
7790
7808
|
}
|
|
@@ -7816,6 +7834,45 @@ function credentialError(provider) {
|
|
|
7816
7834
|
data: { expected: ["credential environment variable referenced by the selected provider"], actual: [`provider: ${provider}`, "credential: missing"] }
|
|
7817
7835
|
});
|
|
7818
7836
|
}
|
|
7837
|
+
function routedFailure(error, route) {
|
|
7838
|
+
return createOperationalFailure(classificationOf(error.failure), {
|
|
7839
|
+
...contextOf(error.failure),
|
|
7840
|
+
provider: route.provider,
|
|
7841
|
+
model: route.model,
|
|
7842
|
+
retryable: error.failure.retryable,
|
|
7843
|
+
message: error.message
|
|
7844
|
+
});
|
|
7845
|
+
}
|
|
7846
|
+
function exhaustedFailure(error, failures) {
|
|
7847
|
+
if (failures.length === 1) return error;
|
|
7848
|
+
const aggregate = aggregateFailures(failures, "All configured model routes failed.");
|
|
7849
|
+
return new LlmTransportError(error.code, aggregate.message, {
|
|
7850
|
+
retryable: aggregate.retryable,
|
|
7851
|
+
classification: classificationOf(aggregate),
|
|
7852
|
+
context: contextOf(aggregate),
|
|
7853
|
+
modelFailures: failures,
|
|
7854
|
+
details: failures.map(formatFailure)
|
|
7855
|
+
});
|
|
7856
|
+
}
|
|
7857
|
+
function isGeminiRoute(route) {
|
|
7858
|
+
return route.protocol === "google-gemini" || /(^|\/)gemini-/i.test(route.model);
|
|
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
|
+
}
|
|
7873
|
+
function canFallback(error, signal) {
|
|
7874
|
+
return !signal?.aborted && error instanceof LlmTransportError && error.code !== "cancelled";
|
|
7875
|
+
}
|
|
7819
7876
|
var ModelRouter = class {
|
|
7820
7877
|
constructor(config, options = {}) {
|
|
7821
7878
|
this.config = config;
|
|
@@ -8096,15 +8153,84 @@ var ModelRouter = class {
|
|
|
8096
8153
|
}
|
|
8097
8154
|
const adapter = this.videoAdapter(route);
|
|
8098
8155
|
return {
|
|
8099
|
-
prepare: (localPath, mimeType, mode, signal) =>
|
|
8100
|
-
|
|
8101
|
-
|
|
8102
|
-
|
|
8103
|
-
|
|
8104
|
-
|
|
8156
|
+
prepare: async (localPath, mimeType, mode, signal) => {
|
|
8157
|
+
const started = Date.now();
|
|
8158
|
+
try {
|
|
8159
|
+
return await adapter.prepare(localPath, mimeType, mode, signal);
|
|
8160
|
+
} catch (error) {
|
|
8161
|
+
if (!canFallback(error, signal) || !this.doubaoAdapter()) throw error;
|
|
8162
|
+
this.recordEvent(
|
|
8163
|
+
route.info,
|
|
8164
|
+
"failed",
|
|
8165
|
+
new Date(started).toISOString(),
|
|
8166
|
+
(/* @__PURE__ */ new Date()).toISOString(),
|
|
8167
|
+
Date.now() - started,
|
|
8168
|
+
error.usage,
|
|
8169
|
+
error.attempts,
|
|
8170
|
+
error.code
|
|
8171
|
+
);
|
|
8172
|
+
return { kind: "local-fallback", path: localPath, mimeType, primaryFailure: routedFailure(error, route.info) };
|
|
8173
|
+
}
|
|
8174
|
+
},
|
|
8175
|
+
release: (media, signal) => media.kind === "local-fallback" ? Promise.resolve() : adapter.release(media, signal),
|
|
8176
|
+
generate: async (request2) => {
|
|
8177
|
+
request2.signal?.throwIfAborted();
|
|
8178
|
+
if (request2.media.kind === "local-fallback") {
|
|
8179
|
+
return this.generateDoubaoVideo(task, request2, [request2.media.primaryFailure]);
|
|
8180
|
+
}
|
|
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
|
+
}
|
|
8203
|
+
}
|
|
8204
|
+
throw new Error("Gemini model routes cannot be empty.");
|
|
8205
|
+
}
|
|
8105
8206
|
};
|
|
8106
8207
|
}
|
|
8107
|
-
|
|
8208
|
+
async generateDoubaoVideo(task, request2, failures) {
|
|
8209
|
+
const adapter = this.doubaoAdapter();
|
|
8210
|
+
if (!adapter) throw credentialError("doubao");
|
|
8211
|
+
const localPath = request2.media.kind === "local-fallback" ? request2.media.path : request2.localPath;
|
|
8212
|
+
if (!localPath) throw configRuntimeError("Doubao video fallback requires the local video.", ["localPath"], ["localPath: missing"]);
|
|
8213
|
+
const localMime = request2.localMime ?? (request2.media.kind === "file-api" ? request2.media.file.mimeType : request2.media.mimeType);
|
|
8214
|
+
const route = this.doubaoRoute(task);
|
|
8215
|
+
process.stderr.write(`\u21AA\uFE0F Gemini \u8DEF\u7531\u5DF2\u8017\u5C3D\uFF0C\u56DE\u9000\u5230\u8C46\u5305 ${route.model}
|
|
8216
|
+
`);
|
|
8217
|
+
try {
|
|
8218
|
+
return await this.tracked(route, () => adapter.run({
|
|
8219
|
+
localPath,
|
|
8220
|
+
localMime,
|
|
8221
|
+
user: request2.user,
|
|
8222
|
+
fps: request2.fps,
|
|
8223
|
+
maxOutputTokens: LLM_TASK_REGISTRY[task].maxOutputTokens,
|
|
8224
|
+
...request2.system ? { system: request2.system } : {},
|
|
8225
|
+
...request2.responseJsonSchema ? { responseJsonSchema: request2.responseJsonSchema } : {},
|
|
8226
|
+
...request2.signal ? { signal: request2.signal } : {}
|
|
8227
|
+
}));
|
|
8228
|
+
} catch (error) {
|
|
8229
|
+
if (!canFallback(error, request2.signal)) throw error;
|
|
8230
|
+
throw exhaustedFailure(error, [...failures, routedFailure(error, route)]);
|
|
8231
|
+
}
|
|
8232
|
+
}
|
|
8233
|
+
// Resolve the default fallback or explicitly selected Doubao video.
|
|
8108
8234
|
// Overridable in tests to inject a fake Ark client.
|
|
8109
8235
|
doubaoAdapter(route) {
|
|
8110
8236
|
return this.arkAdapter(route);
|
|
@@ -8128,29 +8254,50 @@ var ModelRouter = class {
|
|
|
8128
8254
|
doubaoModel() {
|
|
8129
8255
|
return (process.env[DOUBAO_FALLBACK.modelEnv] ?? "").trim() || DOUBAO_FALLBACK.model;
|
|
8130
8256
|
}
|
|
8131
|
-
async
|
|
8257
|
+
async withTextFallback(task, signal, generate) {
|
|
8132
8258
|
const route = this.resolve(task);
|
|
8133
|
-
const
|
|
8134
|
-
|
|
8135
|
-
|
|
8136
|
-
|
|
8137
|
-
|
|
8138
|
-
const
|
|
8139
|
-
|
|
8140
|
-
|
|
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);
|
|
8265
|
+
try {
|
|
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)})
|
|
8141
8274
|
`);
|
|
8142
|
-
|
|
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
|
+
}
|
|
8288
|
+
}
|
|
8143
8289
|
}
|
|
8290
|
+
throw new Error("Text model routes cannot be empty.");
|
|
8144
8291
|
}
|
|
8145
8292
|
async generateText(task, request2) {
|
|
8146
8293
|
const spec2 = LLM_TASK_REGISTRY[task];
|
|
8147
8294
|
if (spec2.mode !== "text") throw configRuntimeError("The task does not support text generation.", ["text task"], [`task: ${task}`]);
|
|
8148
|
-
return this.
|
|
8295
|
+
return this.withTextFallback(task, request2.signal, (adapter) => adapter.generateText({ ...request2, maxOutputTokens: spec2.maxOutputTokens }));
|
|
8149
8296
|
}
|
|
8150
8297
|
async generateTool(task, request2) {
|
|
8151
8298
|
const spec2 = LLM_TASK_REGISTRY[task];
|
|
8152
8299
|
if (spec2.mode !== "tool") throw configRuntimeError("The task does not support tool generation.", ["tool task"], [`task: ${task}`]);
|
|
8153
|
-
return this.
|
|
8300
|
+
return this.withTextFallback(task, request2.signal, (adapter) => adapter.generateTool({ ...request2, maxOutputTokens: spec2.maxOutputTokens }));
|
|
8154
8301
|
}
|
|
8155
8302
|
};
|
|
8156
8303
|
function createLlmExecutor(explicitConfigPath, options) {
|