@telepat/ideon 0.1.29 → 0.1.31
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/dist/ideon.js
CHANGED
|
@@ -552,7 +552,8 @@ var baseT2ISettingsSchema = z2.preprocess(
|
|
|
552
552
|
z2.object({
|
|
553
553
|
modelId: z2.string().default(DEFAULT_LIMN_MODEL_ID),
|
|
554
554
|
replicateModelId: z2.string().optional(),
|
|
555
|
-
inputOverrides: z2.record(z2.string(), z2.unknown()).default({})
|
|
555
|
+
inputOverrides: z2.record(z2.string(), z2.unknown()).default({}),
|
|
556
|
+
maxAttempts: z2.number().int().min(1).max(10).default(4)
|
|
556
557
|
})
|
|
557
558
|
);
|
|
558
559
|
var notificationsSettingsSchema = z2.object({
|
|
@@ -562,6 +563,7 @@ var appSettingsSchema = z2.object({
|
|
|
562
563
|
model: z2.string().default("deepseek/deepseek-v4-pro"),
|
|
563
564
|
modelSettings: modelSettingsSchema.default(modelSettingsSchema.parse({})),
|
|
564
565
|
modelRequestTimeoutMs: z2.number().int().positive().default(9e4),
|
|
566
|
+
modelRequestMaxAttempts: z2.number().int().min(1).max(10).default(4),
|
|
565
567
|
t2i: baseT2ISettingsSchema.default(baseT2ISettingsSchema.parse({})),
|
|
566
568
|
notifications: notificationsSettingsSchema.default(notificationsSettingsSchema.parse({})),
|
|
567
569
|
contentTargets: z2.array(contentTargetSchema).min(1).refine((targets) => targets.filter((target) => target.role === "primary").length === 1, {
|
|
@@ -580,6 +582,7 @@ var envSettingsSchema = z2.object({
|
|
|
580
582
|
maxTokens: z2.number().int().positive().optional(),
|
|
581
583
|
topP: z2.number().min(0).max(1).optional(),
|
|
582
584
|
modelRequestTimeoutMs: z2.number().int().positive().optional(),
|
|
585
|
+
modelRequestMaxAttempts: z2.number().int().min(1).max(10).optional(),
|
|
583
586
|
notificationsEnabled: z2.boolean().optional(),
|
|
584
587
|
style: z2.enum(writingStyleValues).optional(),
|
|
585
588
|
intent: z2.enum(contentIntentValues).optional(),
|
|
@@ -624,6 +627,7 @@ function readEnvSettings(env = process.env) {
|
|
|
624
627
|
maxTokens: parseNumber(env.IDEON_MAX_TOKENS),
|
|
625
628
|
topP: parseNumber(env.IDEON_TOP_P),
|
|
626
629
|
modelRequestTimeoutMs: parseNumber(env.IDEON_MODEL_REQUEST_TIMEOUT_MS),
|
|
630
|
+
modelRequestMaxAttempts: parseNumber(env.IDEON_MODEL_REQUEST_MAX_ATTEMPTS),
|
|
627
631
|
notificationsEnabled: parseBoolean(env.IDEON_NOTIFICATIONS_ENABLED),
|
|
628
632
|
style: env.IDEON_STYLE,
|
|
629
633
|
intent: env.IDEON_INTENT,
|
|
@@ -1424,7 +1428,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
1424
1428
|
// package.json
|
|
1425
1429
|
var package_default = {
|
|
1426
1430
|
name: "@telepat/ideon",
|
|
1427
|
-
version: "0.1.
|
|
1431
|
+
version: "0.1.31",
|
|
1428
1432
|
description: "CLI for generating rich articles and images from ideas.",
|
|
1429
1433
|
type: "module",
|
|
1430
1434
|
repository: {
|
|
@@ -1461,6 +1465,7 @@ var package_default = {
|
|
|
1461
1465
|
test: "NODE_OPTIONS=--experimental-vm-modules jest",
|
|
1462
1466
|
"test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch",
|
|
1463
1467
|
"test:coverage": "NODE_OPTIONS=--experimental-vm-modules jest --coverage",
|
|
1468
|
+
"guides:sync": "node scripts/sync-writing-guides.mjs",
|
|
1464
1469
|
"pricing:refresh": "node scripts/refresh-openrouter-pricing.mjs",
|
|
1465
1470
|
"docs:start": "npm --prefix docs-site run start",
|
|
1466
1471
|
"docs:start:en": "npm --prefix docs-site run start -- --locale en",
|
|
@@ -1546,6 +1551,7 @@ async function resolveRunInput(input) {
|
|
|
1546
1551
|
...job?.settings ?? {},
|
|
1547
1552
|
...envSettings.model ? { model: envSettings.model } : {},
|
|
1548
1553
|
...envSettings.modelRequestTimeoutMs !== void 0 ? { modelRequestTimeoutMs: envSettings.modelRequestTimeoutMs } : {},
|
|
1554
|
+
...envSettings.modelRequestMaxAttempts !== void 0 ? { modelRequestMaxAttempts: envSettings.modelRequestMaxAttempts } : {},
|
|
1549
1555
|
...envSettings.notificationsEnabled !== void 0 ? {
|
|
1550
1556
|
notifications: {
|
|
1551
1557
|
...savedSettings.notifications,
|
|
@@ -3180,6 +3186,212 @@ function buildImagePromptMessages(plan, image, section) {
|
|
|
3180
3186
|
];
|
|
3181
3187
|
}
|
|
3182
3188
|
|
|
3189
|
+
// src/llm/retry.ts
|
|
3190
|
+
var DEFAULT_BASE_BACKOFF_MS = 1500;
|
|
3191
|
+
var DEFAULT_MAX_BACKOFF_MS = 6e4;
|
|
3192
|
+
var DEFAULT_JITTER_MS = 250;
|
|
3193
|
+
var RETRYABLE_STATUS_CODES = /* @__PURE__ */ new Set([408, 409, 425, 429]);
|
|
3194
|
+
var TRANSIENT_ERROR_PATTERN = /timeout|network|fetch|temporarily|aborted|ECONNRESET|ECONNREFUSED|ENOTFOUND|EAI_AGAIN|ETIMEDOUT|socket hang up/i;
|
|
3195
|
+
async function withRetry(op, opts) {
|
|
3196
|
+
const maxAttempts = Math.max(1, Math.floor(opts.maxAttempts));
|
|
3197
|
+
const baseBackoffMs = opts.baseBackoffMs ?? DEFAULT_BASE_BACKOFF_MS;
|
|
3198
|
+
const maxBackoffMs = opts.maxBackoffMs ?? DEFAULT_MAX_BACKOFF_MS;
|
|
3199
|
+
const jitterMs = opts.jitterMs ?? DEFAULT_JITTER_MS;
|
|
3200
|
+
const sleep = opts.sleep ?? defaultSleep;
|
|
3201
|
+
const randomFraction = opts.randomFraction ?? Math.random;
|
|
3202
|
+
let lastError;
|
|
3203
|
+
let lastClassification = null;
|
|
3204
|
+
let attemptsMade = 0;
|
|
3205
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
3206
|
+
attemptsMade = attempt;
|
|
3207
|
+
try {
|
|
3208
|
+
return await op(attempt);
|
|
3209
|
+
} catch (error) {
|
|
3210
|
+
lastError = error;
|
|
3211
|
+
const classification = classifyHttpError(error);
|
|
3212
|
+
lastClassification = classification;
|
|
3213
|
+
if (!classification.retryable || attempt >= maxAttempts) {
|
|
3214
|
+
break;
|
|
3215
|
+
}
|
|
3216
|
+
const delayMs = computeDelayMs({
|
|
3217
|
+
retryAfterMs: classification.retryAfterMs,
|
|
3218
|
+
attempt,
|
|
3219
|
+
baseBackoffMs,
|
|
3220
|
+
maxBackoffMs,
|
|
3221
|
+
jitterMs,
|
|
3222
|
+
randomFraction
|
|
3223
|
+
});
|
|
3224
|
+
opts.onRetry?.({
|
|
3225
|
+
attempt,
|
|
3226
|
+
delayMs,
|
|
3227
|
+
reason: classification.reason,
|
|
3228
|
+
statusCode: classification.statusCode
|
|
3229
|
+
});
|
|
3230
|
+
await sleep(delayMs);
|
|
3231
|
+
}
|
|
3232
|
+
}
|
|
3233
|
+
throw buildFinalError(opts.operationLabel, attemptsMade, lastError, lastClassification);
|
|
3234
|
+
}
|
|
3235
|
+
function computeDelayMs(input) {
|
|
3236
|
+
if (typeof input.retryAfterMs === "number" && input.retryAfterMs > 0) {
|
|
3237
|
+
return Math.min(input.maxBackoffMs, input.retryAfterMs);
|
|
3238
|
+
}
|
|
3239
|
+
const exponential = input.baseBackoffMs * 2 ** (input.attempt - 1);
|
|
3240
|
+
const capped = Math.min(input.maxBackoffMs, exponential);
|
|
3241
|
+
const jitter = input.jitterMs > 0 ? input.randomFraction() * input.jitterMs : 0;
|
|
3242
|
+
return Math.floor(capped + jitter);
|
|
3243
|
+
}
|
|
3244
|
+
function classifyHttpError(error) {
|
|
3245
|
+
if (!error) {
|
|
3246
|
+
return { retryable: true, reason: "Empty error value treated as transient." };
|
|
3247
|
+
}
|
|
3248
|
+
const message = errorMessage(error);
|
|
3249
|
+
const statusFromObject = extractStatusFromObject(error);
|
|
3250
|
+
const statusFromMessage = statusFromObject ?? extractStatusFromMessage(message);
|
|
3251
|
+
const retryAfterMs = extractRetryAfterMs(error, message);
|
|
3252
|
+
if (statusFromMessage !== void 0) {
|
|
3253
|
+
if (RETRYABLE_STATUS_CODES.has(statusFromMessage) || statusFromMessage >= 500) {
|
|
3254
|
+
return {
|
|
3255
|
+
retryable: true,
|
|
3256
|
+
statusCode: statusFromMessage,
|
|
3257
|
+
retryAfterMs,
|
|
3258
|
+
reason: `HTTP ${statusFromMessage}`
|
|
3259
|
+
};
|
|
3260
|
+
}
|
|
3261
|
+
return {
|
|
3262
|
+
retryable: false,
|
|
3263
|
+
statusCode: statusFromMessage,
|
|
3264
|
+
reason: `HTTP ${statusFromMessage}`
|
|
3265
|
+
};
|
|
3266
|
+
}
|
|
3267
|
+
if (TRANSIENT_ERROR_PATTERN.test(message)) {
|
|
3268
|
+
return {
|
|
3269
|
+
retryable: true,
|
|
3270
|
+
retryAfterMs,
|
|
3271
|
+
reason: "Transient network or timeout error."
|
|
3272
|
+
};
|
|
3273
|
+
}
|
|
3274
|
+
return {
|
|
3275
|
+
retryable: true,
|
|
3276
|
+
retryAfterMs,
|
|
3277
|
+
reason: "Unknown error treated as transient."
|
|
3278
|
+
};
|
|
3279
|
+
}
|
|
3280
|
+
function extractStatusFromObject(error) {
|
|
3281
|
+
if (typeof error !== "object" || error === null) {
|
|
3282
|
+
return void 0;
|
|
3283
|
+
}
|
|
3284
|
+
const record = error;
|
|
3285
|
+
const direct = numberFrom(record.status);
|
|
3286
|
+
if (direct !== void 0) {
|
|
3287
|
+
return direct;
|
|
3288
|
+
}
|
|
3289
|
+
const response = record.response;
|
|
3290
|
+
if (typeof response === "object" && response !== null) {
|
|
3291
|
+
const responseStatus = numberFrom(response.status);
|
|
3292
|
+
if (responseStatus !== void 0) {
|
|
3293
|
+
return responseStatus;
|
|
3294
|
+
}
|
|
3295
|
+
}
|
|
3296
|
+
return void 0;
|
|
3297
|
+
}
|
|
3298
|
+
function extractStatusFromMessage(message) {
|
|
3299
|
+
const match = message.match(/status\s+(\d{3})/i);
|
|
3300
|
+
if (!match) {
|
|
3301
|
+
return void 0;
|
|
3302
|
+
}
|
|
3303
|
+
const parsed = Number.parseInt(match[1], 10);
|
|
3304
|
+
return Number.isInteger(parsed) ? parsed : void 0;
|
|
3305
|
+
}
|
|
3306
|
+
function extractRetryAfterMs(error, message) {
|
|
3307
|
+
const headerSeconds = extractRetryAfterHeader(error);
|
|
3308
|
+
if (headerSeconds !== void 0) {
|
|
3309
|
+
return Math.round(headerSeconds * 1e3);
|
|
3310
|
+
}
|
|
3311
|
+
const bodyMatch = message.match(/"?retry_after"?\s*[:=]\s*(\d+(?:\.\d+)?)/i);
|
|
3312
|
+
if (bodyMatch) {
|
|
3313
|
+
const seconds = Number.parseFloat(bodyMatch[1]);
|
|
3314
|
+
if (Number.isFinite(seconds) && seconds > 0) {
|
|
3315
|
+
return Math.round(seconds * 1e3);
|
|
3316
|
+
}
|
|
3317
|
+
}
|
|
3318
|
+
return void 0;
|
|
3319
|
+
}
|
|
3320
|
+
function extractRetryAfterHeader(error) {
|
|
3321
|
+
if (typeof error !== "object" || error === null) {
|
|
3322
|
+
return void 0;
|
|
3323
|
+
}
|
|
3324
|
+
const response = error.response;
|
|
3325
|
+
if (typeof response !== "object" || response === null) {
|
|
3326
|
+
return void 0;
|
|
3327
|
+
}
|
|
3328
|
+
const headers = response.headers;
|
|
3329
|
+
if (!headers) {
|
|
3330
|
+
return void 0;
|
|
3331
|
+
}
|
|
3332
|
+
let rawValue;
|
|
3333
|
+
if (typeof headers === "object" && typeof headers.get === "function") {
|
|
3334
|
+
rawValue = headers.get("retry-after");
|
|
3335
|
+
} else if (typeof headers === "object") {
|
|
3336
|
+
const entries = headers;
|
|
3337
|
+
rawValue = entries["retry-after"] ?? entries["Retry-After"];
|
|
3338
|
+
}
|
|
3339
|
+
if (typeof rawValue !== "string" && typeof rawValue !== "number") {
|
|
3340
|
+
return void 0;
|
|
3341
|
+
}
|
|
3342
|
+
const stringValue = String(rawValue).trim();
|
|
3343
|
+
if (!stringValue) {
|
|
3344
|
+
return void 0;
|
|
3345
|
+
}
|
|
3346
|
+
const numeric = Number.parseFloat(stringValue);
|
|
3347
|
+
if (Number.isFinite(numeric) && numeric > 0) {
|
|
3348
|
+
return numeric;
|
|
3349
|
+
}
|
|
3350
|
+
const dateMs = Date.parse(stringValue);
|
|
3351
|
+
if (Number.isFinite(dateMs)) {
|
|
3352
|
+
const diffSeconds = (dateMs - Date.now()) / 1e3;
|
|
3353
|
+
return diffSeconds > 0 ? diffSeconds : void 0;
|
|
3354
|
+
}
|
|
3355
|
+
return void 0;
|
|
3356
|
+
}
|
|
3357
|
+
function numberFrom(value2) {
|
|
3358
|
+
if (typeof value2 === "number" && Number.isFinite(value2)) {
|
|
3359
|
+
return value2;
|
|
3360
|
+
}
|
|
3361
|
+
if (typeof value2 === "string") {
|
|
3362
|
+
const parsed = Number.parseFloat(value2);
|
|
3363
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
3364
|
+
}
|
|
3365
|
+
return void 0;
|
|
3366
|
+
}
|
|
3367
|
+
function errorMessage(error) {
|
|
3368
|
+
if (error instanceof Error) {
|
|
3369
|
+
return error.message;
|
|
3370
|
+
}
|
|
3371
|
+
if (typeof error === "string") {
|
|
3372
|
+
return error;
|
|
3373
|
+
}
|
|
3374
|
+
try {
|
|
3375
|
+
return JSON.stringify(error);
|
|
3376
|
+
} catch {
|
|
3377
|
+
return String(error);
|
|
3378
|
+
}
|
|
3379
|
+
}
|
|
3380
|
+
function buildFinalError(operationLabel, attempts, cause, classification) {
|
|
3381
|
+
const detail = errorMessage(cause) || classification?.reason || "unknown error";
|
|
3382
|
+
const message = `${operationLabel} failed after ${attempts} attempt${attempts === 1 ? "" : "s"}: ${detail}`;
|
|
3383
|
+
const wrapped = new Error(message, cause instanceof Error ? { cause } : void 0);
|
|
3384
|
+
if (!(cause instanceof Error) && cause !== void 0) {
|
|
3385
|
+
wrapped.cause = cause;
|
|
3386
|
+
}
|
|
3387
|
+
return wrapped;
|
|
3388
|
+
}
|
|
3389
|
+
function defaultSleep(ms) {
|
|
3390
|
+
return new Promise((resolve) => {
|
|
3391
|
+
setTimeout(resolve, ms);
|
|
3392
|
+
});
|
|
3393
|
+
}
|
|
3394
|
+
|
|
3183
3395
|
// src/pipeline/analytics.ts
|
|
3184
3396
|
var LLM_USD_PER_1K_TOKENS = {
|
|
3185
3397
|
// AUTO-GENERATED:OPENROUTER_PRICING_START
|
|
@@ -3432,8 +3644,26 @@ async function renderExpandedImages({
|
|
|
3432
3644
|
...replicateModelOverride && isReplicateModelIdForFamily(family, replicateModelOverride) ? { replicateModel: replicateModelOverride } : {}
|
|
3433
3645
|
};
|
|
3434
3646
|
const renderStartedAtMs = Date.now();
|
|
3647
|
+
let attemptsMade = 0;
|
|
3648
|
+
let retryCount = 0;
|
|
3649
|
+
let retryBackoffMs = 0;
|
|
3435
3650
|
try {
|
|
3436
|
-
const result = await
|
|
3651
|
+
const result = await withRetry(
|
|
3652
|
+
() => {
|
|
3653
|
+
attemptsMade += 1;
|
|
3654
|
+
return limn.generate(prompt.prompt, family, limnOptions);
|
|
3655
|
+
},
|
|
3656
|
+
{
|
|
3657
|
+
operationLabel: `Replicate ${prompt.kind} image (${prompt.id})`,
|
|
3658
|
+
maxAttempts: settings.t2i.maxAttempts,
|
|
3659
|
+
baseBackoffMs: 1500,
|
|
3660
|
+
maxBackoffMs: 6e4,
|
|
3661
|
+
onRetry({ delayMs }) {
|
|
3662
|
+
retryCount += 1;
|
|
3663
|
+
retryBackoffMs += delayMs;
|
|
3664
|
+
}
|
|
3665
|
+
}
|
|
3666
|
+
);
|
|
3437
3667
|
const ext = mimeTypeToExtension(result.mimeType);
|
|
3438
3668
|
const liveFileName = `${prompt.kind === "cover" ? "cover" : `inline-${prompt.anchorAfterSection}`}-${index + 1}.${ext}`;
|
|
3439
3669
|
const liveOutputPath = path6.join(assetDir, liveFileName);
|
|
@@ -3455,9 +3685,9 @@ async function renderExpandedImages({
|
|
|
3455
3685
|
kind: prompt.kind,
|
|
3456
3686
|
modelId: result.modelSlug,
|
|
3457
3687
|
durationMs: result.analytics.totalDurationMs,
|
|
3458
|
-
attempts:
|
|
3459
|
-
retries:
|
|
3460
|
-
retryBackoffMs
|
|
3688
|
+
attempts: attemptsMade,
|
|
3689
|
+
retries: retryCount,
|
|
3690
|
+
retryBackoffMs,
|
|
3461
3691
|
outputBytes: result.image.byteLength,
|
|
3462
3692
|
costUsd: result.analytics.totalEstimatedCostUsd,
|
|
3463
3693
|
costSource
|
|
@@ -3471,9 +3701,9 @@ async function renderExpandedImages({
|
|
|
3471
3701
|
startedAt: new Date(renderStartedAtMs).toISOString(),
|
|
3472
3702
|
endedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3473
3703
|
durationMs: result.analytics.totalDurationMs,
|
|
3474
|
-
attempts:
|
|
3475
|
-
retries:
|
|
3476
|
-
retryBackoffMs
|
|
3704
|
+
attempts: attemptsMade,
|
|
3705
|
+
retries: retryCount,
|
|
3706
|
+
retryBackoffMs,
|
|
3477
3707
|
status: "succeeded",
|
|
3478
3708
|
prompt: prompt.prompt,
|
|
3479
3709
|
input: {},
|
|
@@ -3490,9 +3720,9 @@ async function renderExpandedImages({
|
|
|
3490
3720
|
startedAt: new Date(renderStartedAtMs).toISOString(),
|
|
3491
3721
|
endedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3492
3722
|
durationMs,
|
|
3493
|
-
attempts: 1,
|
|
3494
|
-
retries:
|
|
3495
|
-
retryBackoffMs
|
|
3723
|
+
attempts: Math.max(attemptsMade, 1),
|
|
3724
|
+
retries: retryCount,
|
|
3725
|
+
retryBackoffMs,
|
|
3496
3726
|
status: "failed",
|
|
3497
3727
|
prompt: prompt.prompt,
|
|
3498
3728
|
input: {},
|
|
@@ -3552,8 +3782,9 @@ var OpenRouterClient = class {
|
|
|
3552
3782
|
}
|
|
3553
3783
|
async requestStructured(request) {
|
|
3554
3784
|
let aggregatedMetrics = null;
|
|
3785
|
+
const maxParseAttempts = Math.max(1, request.settings.modelRequestMaxAttempts);
|
|
3555
3786
|
try {
|
|
3556
|
-
for (let attempt = 0; attempt <
|
|
3787
|
+
for (let attempt = 0; attempt < maxParseAttempts; attempt += 1) {
|
|
3557
3788
|
const response = await this.sendCompletion({
|
|
3558
3789
|
messages: request.messages,
|
|
3559
3790
|
settings: request.settings,
|
|
@@ -3578,7 +3809,7 @@ var OpenRouterClient = class {
|
|
|
3578
3809
|
request.onMetrics?.(aggregatedMetrics);
|
|
3579
3810
|
return structured;
|
|
3580
3811
|
} catch (parseError) {
|
|
3581
|
-
if (attempt <
|
|
3812
|
+
if (attempt < maxParseAttempts - 1 && shouldRetryStructuredParseError(parseError)) {
|
|
3582
3813
|
const backoff = backoffMs(attempt);
|
|
3583
3814
|
aggregatedMetrics = recordParseRetryMetrics(aggregatedMetrics, backoff);
|
|
3584
3815
|
await wait(backoff);
|
|
@@ -3644,7 +3875,8 @@ var OpenRouterClient = class {
|
|
|
3644
3875
|
let attempts = 0;
|
|
3645
3876
|
let retries = 0;
|
|
3646
3877
|
let retryBackoffMs = 0;
|
|
3647
|
-
|
|
3878
|
+
const maxAttempts = Math.max(1, settings.modelRequestMaxAttempts);
|
|
3879
|
+
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
|
|
3648
3880
|
attempts = attempt + 1;
|
|
3649
3881
|
const attemptStartedAtMs = Date.now();
|
|
3650
3882
|
const controller = new AbortController();
|
|
@@ -3691,9 +3923,12 @@ var OpenRouterClient = class {
|
|
|
3691
3923
|
responseBodyRaw = rawBody;
|
|
3692
3924
|
const json = parseOpenRouterResponse(rawBody);
|
|
3693
3925
|
if (!response.ok) {
|
|
3694
|
-
const
|
|
3695
|
-
|
|
3696
|
-
|
|
3926
|
+
const providerMessage = json?.error?.message ?? `OpenRouter request failed with status ${response.status}`;
|
|
3927
|
+
const raw = json?.error?.metadata?.raw;
|
|
3928
|
+
const message = raw ? `${raw} (OpenRouter: ${providerMessage})` : providerMessage;
|
|
3929
|
+
if (shouldRetryStatus(response.status) && attempt < maxAttempts - 1) {
|
|
3930
|
+
const advisedMs = extractRetryAfterFromResponse(response, json, rawBody);
|
|
3931
|
+
const backoff = advisedMs !== null ? Math.min(MAX_RETRY_BACKOFF_MS, advisedMs) : backoffMs(attempt);
|
|
3697
3932
|
retries += 1;
|
|
3698
3933
|
retryBackoffMs += backoff;
|
|
3699
3934
|
onInteraction?.({
|
|
@@ -3719,7 +3954,7 @@ var OpenRouterClient = class {
|
|
|
3719
3954
|
throw new Error(message);
|
|
3720
3955
|
}
|
|
3721
3956
|
const content = json.choices?.[0]?.message?.content;
|
|
3722
|
-
if (!content && attempt <
|
|
3957
|
+
if (!content && attempt < maxAttempts - 1) {
|
|
3723
3958
|
const backoff = backoffMs(attempt);
|
|
3724
3959
|
retries += 1;
|
|
3725
3960
|
retryBackoffMs += backoff;
|
|
@@ -3795,7 +4030,7 @@ var OpenRouterClient = class {
|
|
|
3795
4030
|
responseBody: responseBodyRaw,
|
|
3796
4031
|
errorMessage: lastError.message
|
|
3797
4032
|
});
|
|
3798
|
-
if (attempt <
|
|
4033
|
+
if (attempt < maxAttempts - 1 && shouldRetryError(lastError)) {
|
|
3799
4034
|
const backoff = backoffMs(attempt);
|
|
3800
4035
|
retries += 1;
|
|
3801
4036
|
retryBackoffMs += backoff;
|
|
@@ -3922,6 +4157,61 @@ function normalizeClientError(error, timeoutMs) {
|
|
|
3922
4157
|
function backoffMs(attempt) {
|
|
3923
4158
|
return 500 * (attempt + 1);
|
|
3924
4159
|
}
|
|
4160
|
+
var MAX_RETRY_BACKOFF_MS = 6e4;
|
|
4161
|
+
function extractRetryAfterFromResponse(response, json, rawBody) {
|
|
4162
|
+
const headerValue = typeof response.headers?.get === "function" ? response.headers.get("retry-after") : null;
|
|
4163
|
+
if (headerValue) {
|
|
4164
|
+
const numeric = Number.parseFloat(headerValue);
|
|
4165
|
+
if (Number.isFinite(numeric) && numeric > 0) {
|
|
4166
|
+
return Math.round(numeric * 1e3);
|
|
4167
|
+
}
|
|
4168
|
+
const dateMs = Date.parse(headerValue);
|
|
4169
|
+
if (Number.isFinite(dateMs)) {
|
|
4170
|
+
const diff = dateMs - Date.now();
|
|
4171
|
+
if (diff > 0) {
|
|
4172
|
+
return diff;
|
|
4173
|
+
}
|
|
4174
|
+
}
|
|
4175
|
+
}
|
|
4176
|
+
const metadata = json?.error?.metadata;
|
|
4177
|
+
if (metadata) {
|
|
4178
|
+
const fromMetadata = metadata["retry_after"] ?? metadata["retryAfter"];
|
|
4179
|
+
const numeric = toFiniteNumber(fromMetadata);
|
|
4180
|
+
if (numeric !== null && numeric > 0) {
|
|
4181
|
+
return Math.round(numeric * 1e3);
|
|
4182
|
+
}
|
|
4183
|
+
const rawCandidate = metadata["raw"];
|
|
4184
|
+
if (typeof rawCandidate === "string") {
|
|
4185
|
+
const fromRaw = extractRetryAfterFromString(rawCandidate);
|
|
4186
|
+
if (fromRaw !== null) {
|
|
4187
|
+
return fromRaw;
|
|
4188
|
+
}
|
|
4189
|
+
}
|
|
4190
|
+
}
|
|
4191
|
+
const fromBody = extractRetryAfterFromString(rawBody);
|
|
4192
|
+
if (fromBody !== null) {
|
|
4193
|
+
return fromBody;
|
|
4194
|
+
}
|
|
4195
|
+
return null;
|
|
4196
|
+
}
|
|
4197
|
+
function extractRetryAfterFromString(value2) {
|
|
4198
|
+
const bodyMatch = value2.match(/\\?"retry_after\\?"\s*:\s*(\d+(?:\.\d+)?)/i);
|
|
4199
|
+
if (!bodyMatch) {
|
|
4200
|
+
return null;
|
|
4201
|
+
}
|
|
4202
|
+
const numeric = Number.parseFloat(bodyMatch[1]);
|
|
4203
|
+
return Number.isFinite(numeric) && numeric > 0 ? Math.round(numeric * 1e3) : null;
|
|
4204
|
+
}
|
|
4205
|
+
function toFiniteNumber(value2) {
|
|
4206
|
+
if (typeof value2 === "number" && Number.isFinite(value2)) {
|
|
4207
|
+
return value2;
|
|
4208
|
+
}
|
|
4209
|
+
if (typeof value2 === "string") {
|
|
4210
|
+
const parsed = Number.parseFloat(value2);
|
|
4211
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
4212
|
+
}
|
|
4213
|
+
return null;
|
|
4214
|
+
}
|
|
3925
4215
|
function aggregateLlmMetrics(total, next) {
|
|
3926
4216
|
if (!total) {
|
|
3927
4217
|
return { ...next };
|
|
@@ -4330,7 +4620,7 @@ async function runPipelineShell(input, options = {}) {
|
|
|
4330
4620
|
const llmInteractions = [];
|
|
4331
4621
|
const t2iInteractions = [];
|
|
4332
4622
|
let writeSession;
|
|
4333
|
-
const applyRetryUpdate = (stageId, retryIncrement,
|
|
4623
|
+
const applyRetryUpdate = (stageId, retryIncrement, errorMessage2) => {
|
|
4334
4624
|
if (retryIncrement <= 0) {
|
|
4335
4625
|
return;
|
|
4336
4626
|
}
|
|
@@ -4341,7 +4631,7 @@ async function runPipelineShell(input, options = {}) {
|
|
|
4341
4631
|
const existing = stageRetryState.get(stageId) ?? { retries: 0, lastError: null };
|
|
4342
4632
|
const next = {
|
|
4343
4633
|
retries: existing.retries + retryIncrement,
|
|
4344
|
-
lastError:
|
|
4634
|
+
lastError: errorMessage2 && errorMessage2.trim().length > 0 ? errorMessage2 : existing.lastError
|
|
4345
4635
|
};
|
|
4346
4636
|
stageRetryState.set(stageId, next);
|
|
4347
4637
|
stages[stageIndex] = {
|
|
@@ -7381,7 +7671,8 @@ function SettingsFlow({ initialSettings, initialSecrets, onDone }) {
|
|
|
7381
7671
|
t2i: {
|
|
7382
7672
|
modelId: item.value,
|
|
7383
7673
|
replicateModelId: current.t2i.replicateModelId && isReplicateModelIdForFamily(item.value, current.t2i.replicateModelId) ? current.t2i.replicateModelId : void 0,
|
|
7384
|
-
inputOverrides: {}
|
|
7674
|
+
inputOverrides: {},
|
|
7675
|
+
maxAttempts: current.t2i.maxAttempts
|
|
7385
7676
|
}
|
|
7386
7677
|
}));
|
|
7387
7678
|
setShowModelSelect(false);
|
|
@@ -9201,6 +9492,10 @@ function renderShell({
|
|
|
9201
9492
|
\`<code class="slug-text">\${escapeHtml(output.slug)}</code>\`,
|
|
9202
9493
|
\`<button class="copy-btn" data-copy-slug="\${escapeHtml(output.slug)}" type="button">Copy slug</button>\`,
|
|
9203
9494
|
'</div>',
|
|
9495
|
+
'<div class="slug-row">',
|
|
9496
|
+
\`<code class="slug-text">\${escapeHtml(currentGeneration.generationId)}</code>\`,
|
|
9497
|
+
\`<button class="copy-btn" data-copy-generation-id="\${escapeHtml(currentGeneration.generationId)}" type="button">Copy generation ID</button>\`,
|
|
9498
|
+
'</div>',
|
|
9204
9499
|
\`<div class="channel-meta">\${escapeHtml(output.contentTypeLabel)} \u2022 Variant \${output.index}</div>\`,
|
|
9205
9500
|
'</div>',
|
|
9206
9501
|
'</div>',
|
|
@@ -9312,6 +9607,12 @@ function renderShell({
|
|
|
9312
9607
|
const copyButton = target.closest('[data-copy-slug]');
|
|
9313
9608
|
if (copyButton instanceof HTMLElement && copyButton.dataset.copySlug) {
|
|
9314
9609
|
copySlug(copyButton, copyButton.dataset.copySlug);
|
|
9610
|
+
return;
|
|
9611
|
+
}
|
|
9612
|
+
|
|
9613
|
+
const copyGenerationIdButton = target.closest('[data-copy-generation-id]');
|
|
9614
|
+
if (copyGenerationIdButton instanceof HTMLElement && copyGenerationIdButton.dataset.copyGenerationId) {
|
|
9615
|
+
copySlug(copyGenerationIdButton, copyGenerationIdButton.dataset.copyGenerationId);
|
|
9315
9616
|
}
|
|
9316
9617
|
});
|
|
9317
9618
|
|
|
@@ -9697,13 +9998,13 @@ function PipelinePresenter({
|
|
|
9697
9998
|
prompt,
|
|
9698
9999
|
stages,
|
|
9699
10000
|
result,
|
|
9700
|
-
errorMessage
|
|
10001
|
+
errorMessage: errorMessage2
|
|
9701
10002
|
}) {
|
|
9702
10003
|
const visibleStages = getVisibleStages(stages);
|
|
9703
10004
|
const maxVisibleItems = computeMaxVisibleItemsPerStage({
|
|
9704
10005
|
terminalRows: process.stdout.rows,
|
|
9705
10006
|
visibleStageCount: visibleStages.length,
|
|
9706
|
-
hasError: Boolean(
|
|
10007
|
+
hasError: Boolean(errorMessage2),
|
|
9707
10008
|
hasResult: Boolean(result)
|
|
9708
10009
|
});
|
|
9709
10010
|
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", paddingX: 1, children: [
|
|
@@ -9719,7 +10020,7 @@ function PipelinePresenter({
|
|
|
9719
10020
|
},
|
|
9720
10021
|
stage.id
|
|
9721
10022
|
)) }),
|
|
9722
|
-
|
|
10023
|
+
errorMessage2 ? /* @__PURE__ */ jsx5(Box4, { marginTop: 1, borderStyle: "round", borderColor: "red", paddingX: 1, children: /* @__PURE__ */ jsx5(Text4, { color: "red", children: errorMessage2 }) }) : null,
|
|
9723
10024
|
result ? /* @__PURE__ */ jsx5(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx5(FinalSummary, { artifact: result.artifact, analytics: result.analytics }) }) : null
|
|
9724
10025
|
] });
|
|
9725
10026
|
}
|
|
@@ -10254,7 +10555,7 @@ function WriteApp({
|
|
|
10254
10555
|
() => createInitialStages()
|
|
10255
10556
|
);
|
|
10256
10557
|
const [result, setResult] = useState4(null);
|
|
10257
|
-
const [
|
|
10558
|
+
const [errorMessage2, setErrorMessage] = useState4(null);
|
|
10258
10559
|
useEffect3(() => {
|
|
10259
10560
|
let mounted = true;
|
|
10260
10561
|
void (async () => {
|
|
@@ -10307,7 +10608,7 @@ function WriteApp({
|
|
|
10307
10608
|
};
|
|
10308
10609
|
}, [dryRun, enrichLinks2, input, links, unlinks, maxLinks, maxImages, onError, runMode]);
|
|
10309
10610
|
useEffect3(() => {
|
|
10310
|
-
if (!result && !
|
|
10611
|
+
if (!result && !errorMessage2) {
|
|
10311
10612
|
return;
|
|
10312
10613
|
}
|
|
10313
10614
|
const exitTimer = setTimeout(() => {
|
|
@@ -10316,8 +10617,8 @@ function WriteApp({
|
|
|
10316
10617
|
return () => {
|
|
10317
10618
|
clearTimeout(exitTimer);
|
|
10318
10619
|
};
|
|
10319
|
-
}, [
|
|
10320
|
-
return /* @__PURE__ */ jsx7(PipelinePresenter, { prompt: input.idea, stages, result, errorMessage });
|
|
10620
|
+
}, [errorMessage2, exit, result]);
|
|
10621
|
+
return /* @__PURE__ */ jsx7(PipelinePresenter, { prompt: input.idea, stages, result, errorMessage: errorMessage2 });
|
|
10321
10622
|
}
|
|
10322
10623
|
async function runWriteCommand(options) {
|
|
10323
10624
|
const input = await resolveInputWithInteractiveIdeaFallback(options);
|