dsh-neartoken 0.19.2 → 0.20.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/README.md +12 -3
- package/lib/client.js +121 -121
- package/lib/index.js +250 -47
- package/lib/types/host/images.d.ts +78 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -220,7 +220,7 @@ location / {
|
|
|
220
220
|
|
|
221
221
|
| 通道 | 判定 | 请求 | 响应 |
|
|
222
222
|
|---|---|---|---|
|
|
223
|
-
| `images` | 声明含 `image-generation`(gpt-image 系) | `POST /v1/images/
|
|
223
|
+
| `images` | 声明含 `image-generation`(gpt-image 系) | `POST /v1/images/tasks` 提交 → `GET /v1/images/tasks/<id>` 轮询 | 任务 `SUCCESS` 的 `data[0].url` / `b64_json`;站点回落同步时直接是 `data[0]` |
|
|
224
224
|
| `genai` | 声明含 `gemini`(nano-banana、`gemini-*-image`) | `POST /v1beta/models/<model>:generateContent` | `candidates[0].content.parts[].inlineData`(结构化) |
|
|
225
225
|
| `chat` | 其余(只有 `openai` 的,如 grok-imagine) | `POST /v1/chat/completions` | 回复文本里的 `data:image/...;base64,` 或图片链接 |
|
|
226
226
|
|
|
@@ -234,7 +234,7 @@ Gemini 系走原生 surface 而不是 openai 兼容层:同模型同提示词
|
|
|
234
234
|
|
|
235
235
|
`images` 传工作区里的图片路径,工具读成 data URL 后按通道发送:
|
|
236
236
|
|
|
237
|
-
- **images 通道**:作为 `image` 数组(data URL
|
|
237
|
+
- **images 通道**:作为 `image` 数组(data URL)随任务提交发给 `/v1/images/tasks`(站点 DTO 里没有可用的 `edits` 路由,参考图只能搭生成请求)
|
|
238
238
|
- **genai 通道**:作为 `inlineData` part 与提示词一起进 `contents[0].parts`
|
|
239
239
|
- **chat 通道**:作为 `image_url` content part 与提示词一起进 `messages`
|
|
240
240
|
|
|
@@ -251,13 +251,22 @@ Gemini 系走原生 surface 而不是 openai 兼容层:同模型同提示词
|
|
|
251
251
|
|
|
252
252
|
限制:一次只出一张图(`n` 未暴露);`mask` 局部重绘、`input_fidelity` 未暴露。
|
|
253
253
|
|
|
254
|
+
### 异步任务
|
|
255
|
+
|
|
256
|
+
`images` 通道的模型走站点的 **durable task**:`POST /v1/images/tasks` 立刻返回 `202 + task_id`,插件每 3 秒(退避到 5 秒)轮询 `GET /v1/images/tasks/<id>`,`SUCCESS` 后再取 `data[0].url` / `b64_json`。慢模型不再靠一条长 HTTP 撑时间 —— 连接断了,任务还在站点上跑。
|
|
257
|
+
|
|
258
|
+
- **轮询只重读,不重提交**:轮询撞上 5xx、网关故障或断网,按同一 `task_id` 再来一次;任务没有幂等键,重复提交会重复计费,所以提交没拿到任务就原样报给模型,由模型决定要不要重试。
|
|
259
|
+
- **取消只停轮询**:`exec.signal` 中止后本地不再查询,站点上的任务照常跑完并按站点规则计费。
|
|
260
|
+
- **失败带站点原话**:`FAILURE` 的 `fail_reason`(例如 `DataInspectionFailed: …`)直接进错误消息,不用去站点后台翻。
|
|
261
|
+
- **两种回落都能接**:站点没把该模型认领成任务时,同一端点直接回 `200 + data[]`;不认这个端点的老站点回 404,插件改用 `POST /v1/images/generations` 同步等一次。
|
|
262
|
+
|
|
254
263
|
### 超时
|
|
255
264
|
|
|
256
265
|
生图是几十秒到几分钟的活,所以有两条时间线,故意错开:
|
|
257
266
|
|
|
258
267
|
| 层 | 值 | 超时后模型看到什么 |
|
|
259
268
|
|---|---|---|
|
|
260
|
-
| 插件自己的预算 | `imageTimeoutMs`(默认 300
|
|
269
|
+
| 插件自己的预算 | `imageTimeoutMs`(默认 300 秒),**从提交到拿到图的总预算**(含轮询) | `the site did not return an image within 300s (imageTimeoutMs); … a submitted task keeps running on the site and is billed when it finishes …` |
|
|
261
270
|
| 工具调用截止时间 | `imageTimeoutMs + 30 秒`,声明给 harness 的 timeout policy | 通用的 `tool call timed out after …ms` |
|
|
262
271
|
|
|
263
272
|
自己那条早 30 秒触发,所以正常情况模型拿到的是**能指路**的那条(等了多久、该调哪个旋钮);harness 那条只兜住「工具无视 abort 继续跑」。预算耗尽和调用方取消是分开处理的(`AbortSignal.any` 里单独持有预算信号),取消原样向上传递,不会被写成超时。
|
package/lib/client.js
CHANGED
|
@@ -456,23 +456,23 @@ window.__ModuleLoader__.load({
|
|
|
456
456
|
document.head.appendChild(tag);
|
|
457
457
|
}
|
|
458
458
|
var group_tags_module_css_default = {
|
|
459
|
-
"toneIndigo": "FotuUq_toneIndigo",
|
|
460
459
|
"toneLime": "FotuUq_toneLime",
|
|
460
|
+
"tag": "FotuUq_tag",
|
|
461
|
+
"toneViolet": "FotuUq_toneViolet",
|
|
461
462
|
"toneRose": "FotuUq_toneRose",
|
|
462
463
|
"toneEmerald": "FotuUq_toneEmerald",
|
|
463
|
-
"
|
|
464
|
-
"toneFuchsia": "FotuUq_toneFuchsia",
|
|
464
|
+
"toneTeal": "FotuUq_toneTeal",
|
|
465
465
|
"toneOrange": "FotuUq_toneOrange",
|
|
466
|
-
"
|
|
467
|
-
"
|
|
468
|
-
"
|
|
466
|
+
"ratioBase": "FotuUq_ratioBase",
|
|
467
|
+
"toneCyan": "FotuUq_toneCyan",
|
|
468
|
+
"toneIndigo": "FotuUq_toneIndigo",
|
|
469
469
|
"toneAmber": "FotuUq_toneAmber",
|
|
470
|
-
"
|
|
470
|
+
"ratioChip": "FotuUq_ratioChip",
|
|
471
471
|
"ratioCheap": "FotuUq_ratioCheap",
|
|
472
|
-
"toneTeal": "FotuUq_toneTeal",
|
|
473
|
-
"toneCyan": "FotuUq_toneCyan",
|
|
474
472
|
"tonePink": "FotuUq_tonePink",
|
|
475
|
-
"
|
|
473
|
+
"toneSky": "FotuUq_toneSky",
|
|
474
|
+
"toneFuchsia": "FotuUq_toneFuchsia",
|
|
475
|
+
"ratioPricey": "FotuUq_ratioPricey"
|
|
476
476
|
};
|
|
477
477
|
//#endregion
|
|
478
478
|
//#region src/client/group-tags.tsx
|
|
@@ -527,39 +527,39 @@ window.__ModuleLoader__.load({
|
|
|
527
527
|
document.head.appendChild(tag);
|
|
528
528
|
}
|
|
529
529
|
var ModelGroupSelect_module_css_default = {
|
|
530
|
-
"
|
|
531
|
-
"
|
|
532
|
-
"
|
|
533
|
-
"modelName": "_2pt-bW_modelName",
|
|
534
|
-
"groupName": "_2pt-bW_groupName",
|
|
535
|
-
"searchRow": "_2pt-bW_searchRow",
|
|
536
|
-
"retry": "_2pt-bW_retry",
|
|
537
|
-
"empty": "_2pt-bW_empty",
|
|
538
|
-
"triggerLabel": "_2pt-bW_triggerLabel",
|
|
539
|
-
"status": "_2pt-bW_status",
|
|
530
|
+
"trigger": "_2pt-bW_trigger",
|
|
531
|
+
"menu": "_2pt-bW_menu",
|
|
532
|
+
"groupInline": "_2pt-bW_groupInline",
|
|
540
533
|
"chevron": "_2pt-bW_chevron",
|
|
541
534
|
"search": "_2pt-bW_search",
|
|
535
|
+
"retry": "_2pt-bW_retry",
|
|
536
|
+
"status": "_2pt-bW_status",
|
|
542
537
|
"warning": "_2pt-bW_warning",
|
|
543
|
-
"option": "_2pt-bW_option",
|
|
544
|
-
"cellValue": "_2pt-bW_cellValue",
|
|
545
|
-
"variantCount": "_2pt-bW_variantCount",
|
|
546
|
-
"cellChevron": "_2pt-bW_cellChevron",
|
|
547
538
|
"selected": "_2pt-bW_selected",
|
|
548
|
-
"triggerIcon": "_2pt-bW_triggerIcon",
|
|
549
|
-
"searchClear": "_2pt-bW_searchClear",
|
|
550
|
-
"cell": "_2pt-bW_cell",
|
|
551
|
-
"groupOptionLabel": "_2pt-bW_groupOptionLabel",
|
|
552
539
|
"optionTrailing": "_2pt-bW_optionTrailing",
|
|
553
|
-
"toast": "_2pt-bW_toast",
|
|
554
|
-
"groupInline": "_2pt-bW_groupInline",
|
|
555
540
|
"modelOption": "_2pt-bW_modelOption",
|
|
556
|
-
"
|
|
541
|
+
"variantCount": "_2pt-bW_variantCount",
|
|
542
|
+
"searchClear": "_2pt-bW_searchClear",
|
|
543
|
+
"toast": "_2pt-bW_toast",
|
|
544
|
+
"empty": "_2pt-bW_empty",
|
|
545
|
+
"optionActive": "_2pt-bW_optionActive",
|
|
546
|
+
"modelName": "_2pt-bW_modelName",
|
|
557
547
|
"optionCopy": "_2pt-bW_optionCopy",
|
|
558
|
-
"
|
|
559
|
-
"
|
|
560
|
-
"
|
|
548
|
+
"cellValue": "_2pt-bW_cellValue",
|
|
549
|
+
"cellLabel": "_2pt-bW_cellLabel",
|
|
550
|
+
"groups": "_2pt-bW_groups",
|
|
551
|
+
"searchRow": "_2pt-bW_searchRow",
|
|
552
|
+
"triggerLabel": "_2pt-bW_triggerLabel",
|
|
553
|
+
"cellChevron": "_2pt-bW_cellChevron",
|
|
554
|
+
"cell": "_2pt-bW_cell",
|
|
561
555
|
"root": "_2pt-bW_root",
|
|
562
|
-
"
|
|
556
|
+
"check": "_2pt-bW_check",
|
|
557
|
+
"groupName": "_2pt-bW_groupName",
|
|
558
|
+
"groupOptionLabel": "_2pt-bW_groupOptionLabel",
|
|
559
|
+
"triggerEffort": "_2pt-bW_triggerEffort",
|
|
560
|
+
"triggerIcon": "_2pt-bW_triggerIcon",
|
|
561
|
+
"option": "_2pt-bW_option",
|
|
562
|
+
"error": "_2pt-bW_error"
|
|
563
563
|
};
|
|
564
564
|
//#endregion
|
|
565
565
|
//#region src/client/ModelGroupSelect.tsx
|
|
@@ -1164,107 +1164,107 @@ window.__ModuleLoader__.load({
|
|
|
1164
1164
|
document.head.appendChild(tag);
|
|
1165
1165
|
}
|
|
1166
1166
|
var NearTokenSection_module_css_default = {
|
|
1167
|
-
"
|
|
1168
|
-
"
|
|
1169
|
-
"
|
|
1170
|
-
"
|
|
1171
|
-
"
|
|
1172
|
-
"
|
|
1173
|
-
"label": "_02HI2q_label",
|
|
1174
|
-
"tally": "_02HI2q_tally",
|
|
1175
|
-
"metaLabel": "_02HI2q_metaLabel",
|
|
1176
|
-
"badgeOn": "_02HI2q_badgeOn",
|
|
1177
|
-
"metaChecks": "_02HI2q_metaChecks",
|
|
1178
|
-
"metaCheck": "_02HI2q_metaCheck",
|
|
1179
|
-
"rowMeta": "_02HI2q_rowMeta",
|
|
1180
|
-
"rowRatio": "_02HI2q_rowRatio",
|
|
1181
|
-
"form": "_02HI2q_form",
|
|
1182
|
-
"cardHead": "_02HI2q_cardHead",
|
|
1183
|
-
"linkLine": "_02HI2q_linkLine",
|
|
1184
|
-
"metaBtn": "_02HI2q_metaBtn",
|
|
1185
|
-
"metaField": "_02HI2q_metaField",
|
|
1186
|
-
"search": "_02HI2q_search",
|
|
1187
|
-
"tipGroup": "_02HI2q_tipGroup",
|
|
1188
|
-
"topup": "_02HI2q_topup",
|
|
1189
|
-
"title": "_02HI2q_title",
|
|
1190
|
-
"cardToggle": "_02HI2q_cardToggle",
|
|
1191
|
-
"countPicked": "_02HI2q_countPicked",
|
|
1192
|
-
"factValue": "_02HI2q_factValue",
|
|
1193
|
-
"metaBtnOn": "_02HI2q_metaBtnOn",
|
|
1194
|
-
"select": "_02HI2q_select",
|
|
1195
|
-
"axisLabel": "_02HI2q_axisLabel",
|
|
1167
|
+
"fact": "_02HI2q_fact",
|
|
1168
|
+
"tipGroupName": "_02HI2q_tipGroupName",
|
|
1169
|
+
"chevron": "_02HI2q_chevron",
|
|
1170
|
+
"segmentOn": "_02HI2q_segmentOn",
|
|
1171
|
+
"axisRow": "_02HI2q_axisRow",
|
|
1172
|
+
"swatch": "_02HI2q_swatch",
|
|
1196
1173
|
"series3": "_02HI2q_series3",
|
|
1197
|
-
"
|
|
1198
|
-
"factLabel": "_02HI2q_factLabel",
|
|
1199
|
-
"linkRow": "_02HI2q_linkRow",
|
|
1200
|
-
"facts": "_02HI2q_facts",
|
|
1174
|
+
"voucherRow": "_02HI2q_voucherRow",
|
|
1201
1175
|
"link": "_02HI2q_link",
|
|
1176
|
+
"rowMeta": "_02HI2q_rowMeta",
|
|
1177
|
+
"gridLine": "_02HI2q_gridLine",
|
|
1178
|
+
"axis": "_02HI2q_axis",
|
|
1179
|
+
"list": "_02HI2q_list",
|
|
1202
1180
|
"topupChips": "_02HI2q_topupChips",
|
|
1203
|
-
"buttonSmall": "_02HI2q_buttonSmall",
|
|
1204
|
-
"rowName": "_02HI2q_rowName",
|
|
1205
|
-
"voucherRow": "_02HI2q_voucherRow",
|
|
1206
|
-
"tipRow": "_02HI2q_tipRow",
|
|
1207
|
-
"tipGroupName": "_02HI2q_tipGroupName",
|
|
1208
|
-
"badge": "_02HI2q_badge",
|
|
1209
|
-
"refresh": "_02HI2q_refresh",
|
|
1210
|
-
"metaIntro": "_02HI2q_metaIntro",
|
|
1211
|
-
"count": "_02HI2q_count",
|
|
1212
|
-
"metaInput": "_02HI2q_metaInput",
|
|
1213
1181
|
"topupInput": "_02HI2q_topupInput",
|
|
1182
|
+
"topupChipActive": "_02HI2q_topupChipActive",
|
|
1183
|
+
"count": "_02HI2q_count",
|
|
1184
|
+
"title": "_02HI2q_title",
|
|
1185
|
+
"countDot": "_02HI2q_countDot",
|
|
1214
1186
|
"chevronOpen": "_02HI2q_chevronOpen",
|
|
1215
|
-
"
|
|
1216
|
-
"
|
|
1217
|
-
"
|
|
1218
|
-
"
|
|
1219
|
-
"
|
|
1220
|
-
"
|
|
1187
|
+
"plotPanel": "_02HI2q_plotPanel",
|
|
1188
|
+
"legendItem": "_02HI2q_legendItem",
|
|
1189
|
+
"metaEditor": "_02HI2q_metaEditor",
|
|
1190
|
+
"code": "_02HI2q_code",
|
|
1191
|
+
"metaIntro": "_02HI2q_metaIntro",
|
|
1192
|
+
"voucherEmpty": "_02HI2q_voucherEmpty",
|
|
1193
|
+
"cardHead": "_02HI2q_cardHead",
|
|
1194
|
+
"buttonSmall": "_02HI2q_buttonSmall",
|
|
1195
|
+
"card": "_02HI2q_card",
|
|
1196
|
+
"legendName": "_02HI2q_legendName",
|
|
1197
|
+
"invite": "_02HI2q_invite",
|
|
1198
|
+
"button": "_02HI2q_button",
|
|
1199
|
+
"plot": "_02HI2q_plot",
|
|
1200
|
+
"factValue": "_02HI2q_factValue",
|
|
1201
|
+
"legend": "_02HI2q_legend",
|
|
1221
1202
|
"valueRow": "_02HI2q_valueRow",
|
|
1222
|
-
"
|
|
1203
|
+
"metaHead": "_02HI2q_metaHead",
|
|
1204
|
+
"cardToggle": "_02HI2q_cardToggle",
|
|
1205
|
+
"badgeOn": "_02HI2q_badgeOn",
|
|
1223
1206
|
"topupBar": "_02HI2q_topupBar",
|
|
1207
|
+
"buttonPrimary": "_02HI2q_buttonPrimary",
|
|
1208
|
+
"voucherFact": "_02HI2q_voucherFact",
|
|
1209
|
+
"metaBtnOn": "_02HI2q_metaBtnOn",
|
|
1224
1210
|
"intro": "_02HI2q_intro",
|
|
1225
|
-
"
|
|
1226
|
-
"
|
|
1227
|
-
"
|
|
1211
|
+
"linkLine": "_02HI2q_linkLine",
|
|
1212
|
+
"row": "_02HI2q_row",
|
|
1213
|
+
"select": "_02HI2q_select",
|
|
1214
|
+
"factLabel": "_02HI2q_factLabel",
|
|
1215
|
+
"countPicked": "_02HI2q_countPicked",
|
|
1216
|
+
"section": "_02HI2q_section",
|
|
1217
|
+
"tally": "_02HI2q_tally",
|
|
1218
|
+
"metaField": "_02HI2q_metaField",
|
|
1219
|
+
"chart": "_02HI2q_chart",
|
|
1220
|
+
"axisLabel": "_02HI2q_axisLabel",
|
|
1221
|
+
"series2": "_02HI2q_series2",
|
|
1228
1222
|
"topupChip": "_02HI2q_topupChip",
|
|
1223
|
+
"metaBtn": "_02HI2q_metaBtn",
|
|
1224
|
+
"metaTitle": "_02HI2q_metaTitle",
|
|
1225
|
+
"topup": "_02HI2q_topup",
|
|
1226
|
+
"metaLabel": "_02HI2q_metaLabel",
|
|
1227
|
+
"name": "_02HI2q_name",
|
|
1229
1228
|
"metaHint": "_02HI2q_metaHint",
|
|
1230
|
-
"
|
|
1231
|
-
"
|
|
1232
|
-
"
|
|
1233
|
-
"
|
|
1234
|
-
"
|
|
1235
|
-
"
|
|
1236
|
-
"metaEditor": "_02HI2q_metaEditor",
|
|
1237
|
-
"legendItem": "_02HI2q_legendItem",
|
|
1229
|
+
"tip": "_02HI2q_tip",
|
|
1230
|
+
"segmented": "_02HI2q_segmented",
|
|
1231
|
+
"segment": "_02HI2q_segment",
|
|
1232
|
+
"plotRow": "_02HI2q_plotRow",
|
|
1233
|
+
"series4": "_02HI2q_series4",
|
|
1234
|
+
"series5": "_02HI2q_series5",
|
|
1238
1235
|
"handle": "_02HI2q_handle",
|
|
1239
|
-
"swatch": "_02HI2q_swatch",
|
|
1240
1236
|
"series1": "_02HI2q_series1",
|
|
1241
|
-
"
|
|
1242
|
-
"
|
|
1243
|
-
"
|
|
1244
|
-
"
|
|
1245
|
-
"
|
|
1246
|
-
"
|
|
1237
|
+
"voucherGroup": "_02HI2q_voucherGroup",
|
|
1238
|
+
"metaActions": "_02HI2q_metaActions",
|
|
1239
|
+
"tipGroup": "_02HI2q_tipGroup",
|
|
1240
|
+
"yScale": "_02HI2q_yScale",
|
|
1241
|
+
"tipRow": "_02HI2q_tipRow",
|
|
1242
|
+
"codeRow": "_02HI2q_codeRow",
|
|
1243
|
+
"toolbar": "_02HI2q_toolbar",
|
|
1244
|
+
"label": "_02HI2q_label",
|
|
1245
|
+
"facts": "_02HI2q_facts",
|
|
1246
|
+
"tipTitle": "_02HI2q_tipTitle",
|
|
1247
|
+
"hit": "_02HI2q_hit",
|
|
1248
|
+
"refresh": "_02HI2q_refresh",
|
|
1249
|
+
"linkRow": "_02HI2q_linkRow",
|
|
1250
|
+
"notice": "_02HI2q_notice",
|
|
1251
|
+
"voucherList": "_02HI2q_voucherList",
|
|
1252
|
+
"form": "_02HI2q_form",
|
|
1253
|
+
"search": "_02HI2q_search",
|
|
1254
|
+
"rowName": "_02HI2q_rowName",
|
|
1255
|
+
"yTick": "_02HI2q_yTick",
|
|
1256
|
+
"legendCost": "_02HI2q_legendCost",
|
|
1257
|
+
"series0": "_02HI2q_series0",
|
|
1258
|
+
"actions": "_02HI2q_actions",
|
|
1259
|
+
"rowRatio": "_02HI2q_rowRatio",
|
|
1247
1260
|
"voucherAmount": "_02HI2q_voucherAmount",
|
|
1248
|
-
"
|
|
1261
|
+
"metaChecks": "_02HI2q_metaChecks",
|
|
1262
|
+
"autosave": "_02HI2q_autosave",
|
|
1249
1263
|
"gridBase": "_02HI2q_gridBase",
|
|
1250
|
-
"
|
|
1251
|
-
"
|
|
1252
|
-
"axis": "_02HI2q_axis",
|
|
1253
|
-
"legendCost": "_02HI2q_legendCost",
|
|
1254
|
-
"toolbar": "_02HI2q_toolbar",
|
|
1255
|
-
"topupChipActive": "_02HI2q_topupChipActive",
|
|
1256
|
-
"tip": "_02HI2q_tip",
|
|
1257
|
-
"button": "_02HI2q_button",
|
|
1258
|
-
"section": "_02HI2q_section",
|
|
1259
|
-
"chart": "_02HI2q_chart",
|
|
1260
|
-
"voucherGroup": "_02HI2q_voucherGroup",
|
|
1261
|
-
"code": "_02HI2q_code",
|
|
1262
|
-
"list": "_02HI2q_list",
|
|
1263
|
-
"series4": "_02HI2q_series4",
|
|
1264
|
+
"metaInput": "_02HI2q_metaInput",
|
|
1265
|
+
"metaCheck": "_02HI2q_metaCheck",
|
|
1264
1266
|
"hint": "_02HI2q_hint",
|
|
1265
|
-
"
|
|
1266
|
-
"segmented": "_02HI2q_segmented",
|
|
1267
|
-
"card": "_02HI2q_card"
|
|
1267
|
+
"badge": "_02HI2q_badge"
|
|
1268
1268
|
};
|
|
1269
1269
|
//#endregion
|
|
1270
1270
|
//#region src/client/NearTokenSection.tsx
|
package/lib/index.js
CHANGED
|
@@ -1325,6 +1325,28 @@ const IMAGE_NAME = /image|imagine|nano-banana|imagen|flux|seedream|dall-?e|midjo
|
|
|
1325
1325
|
* a picture model.
|
|
1326
1326
|
*/
|
|
1327
1327
|
const NON_IMAGE_NAME = /tts|transcri|\basr\b|speech|embed|rerank|veo|video|lyria|music|sora|whisper/i;
|
|
1328
|
+
/** The site's durable-task surface for picture models. */
|
|
1329
|
+
const TASKS_PATH = "/v1/images/tasks";
|
|
1330
|
+
/** The synchronous picture surface: a site without tasks, and a site that answers one here anyway. */
|
|
1331
|
+
const GENERATIONS_PATH = "/v1/images/generations";
|
|
1332
|
+
/** First poll interval, and the ceiling it backs off to (HANDOFF-new-api.md part 7). */
|
|
1333
|
+
const POLL_FIRST_MS = 3e3;
|
|
1334
|
+
const POLL_MAX_MS = 5e3;
|
|
1335
|
+
/** How much each poll adds before it reaches {@link POLL_MAX_MS}. */
|
|
1336
|
+
const POLL_STEP_MS = 1e3;
|
|
1337
|
+
/**
|
|
1338
|
+
* Statuses worth re-reading the same task for: a gateway's own failure says
|
|
1339
|
+
* nothing about the task, which is still running behind it.
|
|
1340
|
+
*/
|
|
1341
|
+
const TRANSIENT_STATUS = /* @__PURE__ */ new Set([
|
|
1342
|
+
408,
|
|
1343
|
+
425,
|
|
1344
|
+
429,
|
|
1345
|
+
500,
|
|
1346
|
+
502,
|
|
1347
|
+
503,
|
|
1348
|
+
504
|
|
1349
|
+
]);
|
|
1328
1350
|
/**
|
|
1329
1351
|
* Whether a catalog route is a picture generator.
|
|
1330
1352
|
* @param model - the bare model name.
|
|
@@ -1562,60 +1584,19 @@ var NearTokenImageClient = class {
|
|
|
1562
1584
|
timeoutMs
|
|
1563
1585
|
}).catch((error) => {
|
|
1564
1586
|
if (!budget.aborted) throw error;
|
|
1565
|
-
throw new Error(`the site did not return an image within ${String(Math.round(timeoutMs / 1e3))}s (imageTimeoutMs); ${request.model} can be this slow —
|
|
1587
|
+
throw new Error(`the site did not return an image within ${String(Math.round(timeoutMs / 1e3))}s (imageTimeoutMs); ${request.model} can be this slow — a submitted task keeps running on the site and is billed when it finishes, so raise imageTimeoutMs or enable a faster image model`, { cause: error });
|
|
1566
1588
|
});
|
|
1567
1589
|
}
|
|
1568
1590
|
/**
|
|
1569
|
-
* One
|
|
1591
|
+
* One generation under the time budget: the channel decides whether the
|
|
1592
|
+
* picture comes back in the answer or has to be waited for, and both shapes
|
|
1593
|
+
* end as one picture reference.
|
|
1570
1594
|
* @param request - the call, already resolved to a channel and group.
|
|
1571
1595
|
* @returns the encoded picture.
|
|
1572
1596
|
*/
|
|
1573
1597
|
async attempt(request) {
|
|
1574
1598
|
const pictures = request.images ?? [];
|
|
1575
|
-
const
|
|
1576
|
-
const response = await postJson(request.send, `${this.deps.baseURL()}${path}`, {
|
|
1577
|
-
headers: {
|
|
1578
|
-
...attributionHeaders(),
|
|
1579
|
-
...clientHeaders(),
|
|
1580
|
-
"content-type": "application/json",
|
|
1581
|
-
...request.channel === "genai" ? { "x-goog-api-key": request.key } : { authorization: `Bearer ${request.key}` },
|
|
1582
|
-
...request.route?.groupRef === void 0 ? {} : { "x-neartoken-group": request.route.groupRef }
|
|
1583
|
-
},
|
|
1584
|
-
json: JSON.stringify(request.channel === "images" ? {
|
|
1585
|
-
model: request.model,
|
|
1586
|
-
prompt: request.prompt,
|
|
1587
|
-
n: 1,
|
|
1588
|
-
...request.size === void 0 ? {} : { size: request.size },
|
|
1589
|
-
...request.quality === void 0 ? {} : { quality: request.quality },
|
|
1590
|
-
...request.background === void 0 ? {} : { background: request.background },
|
|
1591
|
-
...pictures.length === 0 ? {} : { image: pictures.map((picture) => `data:${picture.mediaType};base64,${picture.data}`) }
|
|
1592
|
-
} : request.channel === "genai" ? { contents: [{
|
|
1593
|
-
role: "user",
|
|
1594
|
-
parts: [{ text: request.prompt }, ...pictures.map((picture) => ({ inlineData: {
|
|
1595
|
-
mimeType: picture.mediaType,
|
|
1596
|
-
data: picture.data
|
|
1597
|
-
} }))]
|
|
1598
|
-
}] } : {
|
|
1599
|
-
model: request.model,
|
|
1600
|
-
messages: [{
|
|
1601
|
-
role: "user",
|
|
1602
|
-
content: pictures.length === 0 ? request.prompt : [{
|
|
1603
|
-
type: "text",
|
|
1604
|
-
text: request.prompt
|
|
1605
|
-
}, ...pictures.map((picture) => ({
|
|
1606
|
-
type: "image_url",
|
|
1607
|
-
image_url: { url: `data:${picture.mediaType};base64,${picture.data}` }
|
|
1608
|
-
}))]
|
|
1609
|
-
}],
|
|
1610
|
-
stream: false
|
|
1611
|
-
}),
|
|
1612
|
-
signal: request.signal
|
|
1613
|
-
});
|
|
1614
|
-
if (!response.ok) {
|
|
1615
|
-
const detail = (await response.text()).slice(0, 400);
|
|
1616
|
-
throw new Error(`the site refused ${request.model} with HTTP ${String(response.status)}: ${detail}`);
|
|
1617
|
-
}
|
|
1618
|
-
const reply = extractImageReply(await response.json());
|
|
1599
|
+
const reply = request.channel === "images" ? await this.imagesReply(request, pictures) : await this.chatReply(request, pictures);
|
|
1619
1600
|
if (reply === void 0) throw new Error(`${request.model} answered without an image; the model may not generate pictures on this site`);
|
|
1620
1601
|
const bytes = reply.kind === "base64" ? Uint8Array.from(Buffer.from(reply.data, "base64")) : await this.download(reply.url, request.signal, request.send);
|
|
1621
1602
|
const mediaType = sniffImageMediaType(bytes);
|
|
@@ -1627,6 +1608,173 @@ var NearTokenImageClient = class {
|
|
|
1627
1608
|
bytes
|
|
1628
1609
|
};
|
|
1629
1610
|
}
|
|
1611
|
+
/** The headers every picture call carries; only the credential header is the channel's own. */
|
|
1612
|
+
requestHeaders(request) {
|
|
1613
|
+
return {
|
|
1614
|
+
...attributionHeaders(),
|
|
1615
|
+
...clientHeaders(),
|
|
1616
|
+
"content-type": "application/json",
|
|
1617
|
+
...request.channel === "genai" ? { "x-goog-api-key": request.key } : { authorization: `Bearer ${request.key}` },
|
|
1618
|
+
...request.route?.groupRef === void 0 ? {} : { "x-neartoken-group": request.route.groupRef }
|
|
1619
|
+
};
|
|
1620
|
+
}
|
|
1621
|
+
/** The headers a poll carries: the same credential, no routing decision left to make. */
|
|
1622
|
+
pollHeaders(request) {
|
|
1623
|
+
return {
|
|
1624
|
+
...attributionHeaders(),
|
|
1625
|
+
...clientHeaders(),
|
|
1626
|
+
authorization: `Bearer ${request.key}`
|
|
1627
|
+
};
|
|
1628
|
+
}
|
|
1629
|
+
/**
|
|
1630
|
+
* The serialized body one channel sends.
|
|
1631
|
+
* @param request - the attempt.
|
|
1632
|
+
* @param pictures - reference pictures, already encoded.
|
|
1633
|
+
* @returns the JSON body.
|
|
1634
|
+
*/
|
|
1635
|
+
requestBody(request, pictures) {
|
|
1636
|
+
return JSON.stringify(request.channel === "images" ? {
|
|
1637
|
+
model: request.model,
|
|
1638
|
+
prompt: request.prompt,
|
|
1639
|
+
n: 1,
|
|
1640
|
+
...request.size === void 0 ? {} : { size: request.size },
|
|
1641
|
+
...request.quality === void 0 ? {} : { quality: request.quality },
|
|
1642
|
+
...request.background === void 0 ? {} : { background: request.background },
|
|
1643
|
+
...pictures.length === 0 ? {} : { image: pictures.map((picture) => `data:${picture.mediaType};base64,${picture.data}`) }
|
|
1644
|
+
} : request.channel === "genai" ? { contents: [{
|
|
1645
|
+
role: "user",
|
|
1646
|
+
parts: [{ text: request.prompt }, ...pictures.map((picture) => ({ inlineData: {
|
|
1647
|
+
mimeType: picture.mediaType,
|
|
1648
|
+
data: picture.data
|
|
1649
|
+
} }))]
|
|
1650
|
+
}] } : {
|
|
1651
|
+
model: request.model,
|
|
1652
|
+
messages: [{
|
|
1653
|
+
role: "user",
|
|
1654
|
+
content: pictures.length === 0 ? request.prompt : [{
|
|
1655
|
+
type: "text",
|
|
1656
|
+
text: request.prompt
|
|
1657
|
+
}, ...pictures.map((picture) => ({
|
|
1658
|
+
type: "image_url",
|
|
1659
|
+
image_url: { url: `data:${picture.mediaType};base64,${picture.data}` }
|
|
1660
|
+
}))]
|
|
1661
|
+
}],
|
|
1662
|
+
stream: false
|
|
1663
|
+
});
|
|
1664
|
+
}
|
|
1665
|
+
/**
|
|
1666
|
+
* One refusal, carrying the status and the site's own words.
|
|
1667
|
+
* @param request - the attempt that was refused.
|
|
1668
|
+
* @param response - the site's answer.
|
|
1669
|
+
* @returns the error to throw.
|
|
1670
|
+
*/
|
|
1671
|
+
async refusal(request, response) {
|
|
1672
|
+
const detail = (await response.text()).slice(0, 400);
|
|
1673
|
+
return /* @__PURE__ */ new Error(`the site refused ${request.model} with HTTP ${String(response.status)}: ${detail}`);
|
|
1674
|
+
}
|
|
1675
|
+
/**
|
|
1676
|
+
* The chat-shaped channels: one request that answers the picture.
|
|
1677
|
+
* @param request - the attempt.
|
|
1678
|
+
* @param pictures - reference pictures, already encoded.
|
|
1679
|
+
* @returns the picture reference, or undefined when the answer carries none.
|
|
1680
|
+
*/
|
|
1681
|
+
async chatReply(request, pictures) {
|
|
1682
|
+
const path = request.channel === "genai" ? `/v1beta/models/${request.model}:generateContent` : "/v1/chat/completions";
|
|
1683
|
+
const response = await postJson(request.send, `${this.deps.baseURL()}${path}`, {
|
|
1684
|
+
headers: this.requestHeaders(request),
|
|
1685
|
+
json: this.requestBody(request, pictures),
|
|
1686
|
+
signal: request.signal
|
|
1687
|
+
});
|
|
1688
|
+
if (!response.ok) throw await this.refusal(request, response);
|
|
1689
|
+
return extractImageReply(await response.json());
|
|
1690
|
+
}
|
|
1691
|
+
/**
|
|
1692
|
+
* The `images` channel: a durable task when the site runs one, the picture
|
|
1693
|
+
* itself when it does not.
|
|
1694
|
+
*
|
|
1695
|
+
* Which of the two an answer is, is the answer's own business — a task id
|
|
1696
|
+
* means polling, `data[]` means the picture is already here, and a 404 means
|
|
1697
|
+
* this site predates the task endpoint. Choosing by configuration would break
|
|
1698
|
+
* the moment the site ships or drops a task plugin.
|
|
1699
|
+
* @param request - the attempt.
|
|
1700
|
+
* @param pictures - reference pictures, already encoded.
|
|
1701
|
+
* @returns the picture reference the answer carries.
|
|
1702
|
+
*/
|
|
1703
|
+
async imagesReply(request, pictures) {
|
|
1704
|
+
const submitted = await postJson(request.send, `${this.deps.baseURL()}${TASKS_PATH}`, {
|
|
1705
|
+
headers: this.requestHeaders(request),
|
|
1706
|
+
json: this.requestBody(request, pictures),
|
|
1707
|
+
signal: request.signal
|
|
1708
|
+
});
|
|
1709
|
+
if (submitted.status === 404) {
|
|
1710
|
+
await submitted.body?.cancel().catch(() => {});
|
|
1711
|
+
return this.syncImagesReply(request, pictures);
|
|
1712
|
+
}
|
|
1713
|
+
if (!submitted.ok) throw await this.refusal(request, submitted);
|
|
1714
|
+
const body = await submitted.json();
|
|
1715
|
+
const taskId = taskIdOf(body);
|
|
1716
|
+
if (taskId === void 0) return extractImageReply(body);
|
|
1717
|
+
return this.settleTask(request, taskId);
|
|
1718
|
+
}
|
|
1719
|
+
/**
|
|
1720
|
+
* The synchronous images route, for a site that answers 404 on submit.
|
|
1721
|
+
* @param request - the attempt.
|
|
1722
|
+
* @param pictures - reference pictures, already encoded.
|
|
1723
|
+
* @returns the picture reference the answer carries.
|
|
1724
|
+
*/
|
|
1725
|
+
async syncImagesReply(request, pictures) {
|
|
1726
|
+
const response = await postJson(request.send, `${this.deps.baseURL()}${GENERATIONS_PATH}`, {
|
|
1727
|
+
headers: this.requestHeaders(request),
|
|
1728
|
+
json: this.requestBody(request, pictures),
|
|
1729
|
+
signal: request.signal
|
|
1730
|
+
});
|
|
1731
|
+
if (!response.ok) throw await this.refusal(request, response);
|
|
1732
|
+
return extractImageReply(await response.json());
|
|
1733
|
+
}
|
|
1734
|
+
/**
|
|
1735
|
+
* Poll one submitted task until the site reports a terminal state.
|
|
1736
|
+
*
|
|
1737
|
+
* Polling is the only retry: a transient gateway failure or a dropped
|
|
1738
|
+
* connection re-reads the same task, so a flaky link never submits — and
|
|
1739
|
+
* bills — a second generation. The caller's budget is the exit; once it
|
|
1740
|
+
* fires the task keeps running on the site and is billed when it finishes.
|
|
1741
|
+
* @param request - the attempt that submitted the task.
|
|
1742
|
+
* @param taskId - the id the submit answer carried.
|
|
1743
|
+
* @returns the picture reference the finished task carries.
|
|
1744
|
+
* @throws {Error} when the task fails, disappears, or the budget runs out.
|
|
1745
|
+
*/
|
|
1746
|
+
async settleTask(request, taskId) {
|
|
1747
|
+
const poll = this.deps.poll?.() ?? {
|
|
1748
|
+
firstMs: POLL_FIRST_MS,
|
|
1749
|
+
maxMs: POLL_MAX_MS
|
|
1750
|
+
};
|
|
1751
|
+
const url = `${this.deps.baseURL()}${TASKS_PATH}/${encodeURIComponent(taskId)}`;
|
|
1752
|
+
let waitMs = poll.firstMs;
|
|
1753
|
+
for (;;) {
|
|
1754
|
+
await waitFor(waitMs, request.signal);
|
|
1755
|
+
waitMs = Math.min(poll.maxMs, waitMs + POLL_STEP_MS);
|
|
1756
|
+
let response;
|
|
1757
|
+
try {
|
|
1758
|
+
response = await request.send(url, {
|
|
1759
|
+
headers: this.pollHeaders(request),
|
|
1760
|
+
signal: request.signal
|
|
1761
|
+
});
|
|
1762
|
+
} catch (error) {
|
|
1763
|
+
if (request.signal.aborted) throw error;
|
|
1764
|
+
continue;
|
|
1765
|
+
}
|
|
1766
|
+
if (response.status === 404) throw new Error(`the site no longer has task ${taskId} (HTTP 404): its result expired or the account changed; submit again`);
|
|
1767
|
+
if (!response.ok) {
|
|
1768
|
+
if (!TRANSIENT_STATUS.has(response.status) || request.signal.aborted) throw await this.refusal(request, response);
|
|
1769
|
+
await response.body?.cancel().catch(() => {});
|
|
1770
|
+
continue;
|
|
1771
|
+
}
|
|
1772
|
+
const body = await response.json();
|
|
1773
|
+
const status = taskStatusOf(body);
|
|
1774
|
+
if (status === "SUCCESS") return extractImageReply(body);
|
|
1775
|
+
if (status === "FAILURE") throw new Error(`the site reported task ${taskId} as FAILURE: ${taskFailureReason(body)}`);
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1630
1778
|
/**
|
|
1631
1779
|
* Fetch a generated picture from the short-lived URL some gateways answer with.
|
|
1632
1780
|
* @param url - the site's result URL.
|
|
@@ -1641,6 +1789,61 @@ var NearTokenImageClient = class {
|
|
|
1641
1789
|
return new Uint8Array(await response.arrayBuffer());
|
|
1642
1790
|
}
|
|
1643
1791
|
};
|
|
1792
|
+
/**
|
|
1793
|
+
* The task id an answer carries, when it carries one.
|
|
1794
|
+
* @param body - the parsed answer.
|
|
1795
|
+
* @returns the id, or undefined when this was not a task answer.
|
|
1796
|
+
*/
|
|
1797
|
+
function taskIdOf(body) {
|
|
1798
|
+
if (typeof body !== "object" || body === null) return void 0;
|
|
1799
|
+
const id = body.task_id;
|
|
1800
|
+
return typeof id === "string" && id.length > 0 ? id : void 0;
|
|
1801
|
+
}
|
|
1802
|
+
/**
|
|
1803
|
+
* The task state one answer carries, upper-cased.
|
|
1804
|
+
* @param body - the parsed answer.
|
|
1805
|
+
* @returns the state, or an empty string when the answer names none.
|
|
1806
|
+
*/
|
|
1807
|
+
function taskStatusOf(body) {
|
|
1808
|
+
if (typeof body !== "object" || body === null) return "";
|
|
1809
|
+
const status = body.status;
|
|
1810
|
+
return typeof status === "string" ? status.toUpperCase() : "";
|
|
1811
|
+
}
|
|
1812
|
+
/**
|
|
1813
|
+
* Why the site reported a task as failed.
|
|
1814
|
+
* @param body - the parsed task answer.
|
|
1815
|
+
* @returns the site's own reason, or a generic one when it gave none.
|
|
1816
|
+
*/
|
|
1817
|
+
function taskFailureReason(body) {
|
|
1818
|
+
const record = typeof body === "object" && body !== null ? body : {};
|
|
1819
|
+
const error = record.error;
|
|
1820
|
+
const detail = typeof error === "object" && error !== null ? error.message : void 0;
|
|
1821
|
+
return ((typeof record.fail_reason === "string" && record.fail_reason.length > 0 ? record.fail_reason : typeof detail === "string" && detail.length > 0 ? detail : void 0) ?? "image generation failed").slice(0, 400);
|
|
1822
|
+
}
|
|
1823
|
+
/**
|
|
1824
|
+
* Wait one poll interval, ending early when the generation is cancelled.
|
|
1825
|
+
* @param ms - how long to wait; the tests pass zero to poll without waiting.
|
|
1826
|
+
* @param signal - the generation's cancellation and time budget.
|
|
1827
|
+
*/
|
|
1828
|
+
async function waitFor(ms, signal) {
|
|
1829
|
+
if (signal.aborted) throw signal.reason instanceof Error ? signal.reason : /* @__PURE__ */ new Error("the image generation was cancelled");
|
|
1830
|
+
if (ms <= 0) return;
|
|
1831
|
+
await new Promise((resolve, reject) => {
|
|
1832
|
+
const done = () => {
|
|
1833
|
+
clearTimeout(timer);
|
|
1834
|
+
signal.removeEventListener("abort", cancel);
|
|
1835
|
+
};
|
|
1836
|
+
const cancel = () => {
|
|
1837
|
+
done();
|
|
1838
|
+
reject(signal.reason instanceof Error ? signal.reason : /* @__PURE__ */ new Error("the image generation was cancelled"));
|
|
1839
|
+
};
|
|
1840
|
+
const timer = setTimeout(() => {
|
|
1841
|
+
done();
|
|
1842
|
+
resolve();
|
|
1843
|
+
}, ms);
|
|
1844
|
+
signal.addEventListener("abort", cancel, { once: true });
|
|
1845
|
+
});
|
|
1846
|
+
}
|
|
1644
1847
|
//#endregion
|
|
1645
1848
|
//#region src/host/image-settings.ts
|
|
1646
1849
|
/** Most picture models one save may enable. */
|
|
@@ -1928,7 +2131,7 @@ async function writePicture(ctx, exec, requested, bytes) {
|
|
|
1928
2131
|
function registerGenerateImageTool(ctx, deps) {
|
|
1929
2132
|
return ctx.tools.register(defineTool({
|
|
1930
2133
|
name: "generate_image",
|
|
1931
|
-
description: "Generate one image from a text prompt with the configured image model (the gpt-image, nano-banana, and gemini image families) and return the image itself.
|
|
2134
|
+
description: "Generate one image from a text prompt with the configured image model (the gpt-image, nano-banana, and gemini image families) and return the image itself. A call takes 20-60 seconds, and a queued or slow model can take minutes. Write the prompt as a full description of the picture you want; quoted text that must appear in the image is rendered by the model, but long lines may smear. Pass images to edit or compose from existing files and say what to keep and what to change; the first reference picture also sets the output aspect ratio. The available models are the ones the operator enabled in Settings → NearToken → Image generation.",
|
|
1932
2135
|
parameters: {
|
|
1933
2136
|
prompt: {
|
|
1934
2137
|
type: "string",
|
|
@@ -3,11 +3,15 @@
|
|
|
3
3
|
* image models accept and the two result envelopes they answer with.
|
|
4
4
|
*
|
|
5
5
|
* The site lists picture generators next to chat models, and they do not share
|
|
6
|
-
* one API. A model the catalog marks `image-generation`
|
|
7
|
-
* `POST /v1/images/
|
|
8
|
-
* the
|
|
9
|
-
*
|
|
10
|
-
* a
|
|
6
|
+
* one API. A model the catalog marks `image-generation` is asked through the
|
|
7
|
+
* task endpoint (`POST /v1/images/tasks`): the site answers a durable task id at
|
|
8
|
+
* once and the picture arrives by polling, because a slow generator outlives
|
|
9
|
+
* any single HTTP response. The same endpoint answers the picture itself when
|
|
10
|
+
* nothing claims the model as a task, and a site too old to know it answers 404
|
|
11
|
+
* — the synchronous `POST /v1/images/generations` is then used once. The
|
|
12
|
+
* Gemini-shaped ones (nano-banana, `*-image`) answer a chat completion whose
|
|
13
|
+
* text embeds the picture. All paths end here as encoded bytes, which is what a
|
|
14
|
+
* tool result can carry.
|
|
11
15
|
*
|
|
12
16
|
* @module
|
|
13
17
|
*/
|
|
@@ -71,6 +75,14 @@ export interface NearTokenImageDeps {
|
|
|
71
75
|
routes: () => Promise<readonly NearTokenRoute[]>;
|
|
72
76
|
/** How long one generation may take before it is abandoned, in milliseconds. */
|
|
73
77
|
timeoutMs: () => number;
|
|
78
|
+
/**
|
|
79
|
+
* Poll cadence for a durable task: the first wait and the ceiling it backs
|
|
80
|
+
* off to. Defaults to 3 s and 5 s; a test passes zeros to poll immediately.
|
|
81
|
+
*/
|
|
82
|
+
poll?: () => {
|
|
83
|
+
readonly firstMs: number;
|
|
84
|
+
readonly maxMs: number;
|
|
85
|
+
};
|
|
74
86
|
/** HTTP carrier; defaults to the process's own fetch. */
|
|
75
87
|
fetch?: typeof fetch;
|
|
76
88
|
}
|
|
@@ -112,11 +124,71 @@ export declare class NearTokenImageClient {
|
|
|
112
124
|
readonly signal?: AbortSignal;
|
|
113
125
|
}): Promise<NearTokenGeneratedImage>;
|
|
114
126
|
/**
|
|
115
|
-
* One
|
|
127
|
+
* One generation under the time budget: the channel decides whether the
|
|
128
|
+
* picture comes back in the answer or has to be waited for, and both shapes
|
|
129
|
+
* end as one picture reference.
|
|
116
130
|
* @param request - the call, already resolved to a channel and group.
|
|
117
131
|
* @returns the encoded picture.
|
|
118
132
|
*/
|
|
119
133
|
private attempt;
|
|
134
|
+
/** The headers every picture call carries; only the credential header is the channel's own. */
|
|
135
|
+
private requestHeaders;
|
|
136
|
+
/** The headers a poll carries: the same credential, no routing decision left to make. */
|
|
137
|
+
private pollHeaders;
|
|
138
|
+
/**
|
|
139
|
+
* The serialized body one channel sends.
|
|
140
|
+
* @param request - the attempt.
|
|
141
|
+
* @param pictures - reference pictures, already encoded.
|
|
142
|
+
* @returns the JSON body.
|
|
143
|
+
*/
|
|
144
|
+
private requestBody;
|
|
145
|
+
/**
|
|
146
|
+
* One refusal, carrying the status and the site's own words.
|
|
147
|
+
* @param request - the attempt that was refused.
|
|
148
|
+
* @param response - the site's answer.
|
|
149
|
+
* @returns the error to throw.
|
|
150
|
+
*/
|
|
151
|
+
private refusal;
|
|
152
|
+
/**
|
|
153
|
+
* The chat-shaped channels: one request that answers the picture.
|
|
154
|
+
* @param request - the attempt.
|
|
155
|
+
* @param pictures - reference pictures, already encoded.
|
|
156
|
+
* @returns the picture reference, or undefined when the answer carries none.
|
|
157
|
+
*/
|
|
158
|
+
private chatReply;
|
|
159
|
+
/**
|
|
160
|
+
* The `images` channel: a durable task when the site runs one, the picture
|
|
161
|
+
* itself when it does not.
|
|
162
|
+
*
|
|
163
|
+
* Which of the two an answer is, is the answer's own business — a task id
|
|
164
|
+
* means polling, `data[]` means the picture is already here, and a 404 means
|
|
165
|
+
* this site predates the task endpoint. Choosing by configuration would break
|
|
166
|
+
* the moment the site ships or drops a task plugin.
|
|
167
|
+
* @param request - the attempt.
|
|
168
|
+
* @param pictures - reference pictures, already encoded.
|
|
169
|
+
* @returns the picture reference the answer carries.
|
|
170
|
+
*/
|
|
171
|
+
private imagesReply;
|
|
172
|
+
/**
|
|
173
|
+
* The synchronous images route, for a site that answers 404 on submit.
|
|
174
|
+
* @param request - the attempt.
|
|
175
|
+
* @param pictures - reference pictures, already encoded.
|
|
176
|
+
* @returns the picture reference the answer carries.
|
|
177
|
+
*/
|
|
178
|
+
private syncImagesReply;
|
|
179
|
+
/**
|
|
180
|
+
* Poll one submitted task until the site reports a terminal state.
|
|
181
|
+
*
|
|
182
|
+
* Polling is the only retry: a transient gateway failure or a dropped
|
|
183
|
+
* connection re-reads the same task, so a flaky link never submits — and
|
|
184
|
+
* bills — a second generation. The caller's budget is the exit; once it
|
|
185
|
+
* fires the task keeps running on the site and is billed when it finishes.
|
|
186
|
+
* @param request - the attempt that submitted the task.
|
|
187
|
+
* @param taskId - the id the submit answer carried.
|
|
188
|
+
* @returns the picture reference the finished task carries.
|
|
189
|
+
* @throws {Error} when the task fails, disappears, or the budget runs out.
|
|
190
|
+
*/
|
|
191
|
+
private settleTask;
|
|
120
192
|
/**
|
|
121
193
|
* Fetch a generated picture from the short-lived URL some gateways answer with.
|
|
122
194
|
* @param url - the site's result URL.
|