@ykurilov/agent-chat-core 0.59.0 → 0.61.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/CHANGELOG.md +30 -0
- package/dist/{chunk-QA5DVEXF.js → chunk-XQ5FJ2VT.js} +233 -73
- package/dist/chunk-XQ5FJ2VT.js.map +1 -0
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/storage-contract.js +1 -1
- package/dist/types/agent-run/tool-result-images-policy.d.ts +37 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/models/model-catalog.d.ts +15 -2
- package/dist/types/models/providers/tool-result-images.d.ts +8 -0
- package/package.json +1 -1
- package/dist/chunk-QA5DVEXF.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# @ykurilov/agent-chat-core
|
|
2
2
|
|
|
3
|
+
## 0.61.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- b3f42a3: Снимок страницы: не проверено — не значит «не умеет». `page_screenshot` и снимок в `person_link_view` даются любой
|
|
8
|
+
модели, которая видит картинки, кроме провайдеров из `blindToolResultImageProviders` профиля. Картинка никогда не уходит
|
|
9
|
+
модели base64-строкой. Картинку получают провайдеры с `toolResultImages: true` (встроенные помечены) и провайдеры из
|
|
10
|
+
`toolResultImageProviders` профиля; остальным ядро шлёт текст страницы. Провайдер отказал из-за картинки (код 400, 415 или 422, в тексте ошибки — про картинку) — тот же запрос сразу
|
|
11
|
+
уходит текстом; удался повтор — строка `[картинки инструментов]` в журнал, и до перезапуска службы эта пара модели и
|
|
12
|
+
провайдера получает текст. Повтор упал — пара не запоминается, дальше идёт ошибка повтора. 413 — текстом только этот
|
|
13
|
+
запрос. Переполнение контекста и прочие отказы идут дальше как есть. Ошибка внутри уже открытого потока (код 200) не
|
|
14
|
+
откатывается. Первый переход на текст один раз сбрасывает кэш модели. Firecrawl отдаёт
|
|
15
|
+
текст страницы вместе со снимком (тот же кредит).
|
|
16
|
+
|
|
17
|
+
Что сделать приложению:
|
|
18
|
+
|
|
19
|
+
- своя проверка «провайдер проверен — даём снимок» больше не нужна;
|
|
20
|
+
- запрещали снимок тем, что не вписывали провайдер в `toolResultImageProviders`, — теперь нужен
|
|
21
|
+
`blindToolResultImageProviders`, иначе снимок уйдёт;
|
|
22
|
+
- провайдер точно не видит картинку из инструмента — `blindToolResultImageProviders: ["id"]` в профиле модели;
|
|
23
|
+
- свой провайдер, передающий картинки инструментов частью `file` SDK, — `toolResultImages: true` в его описании;
|
|
24
|
+
- своя служба снимков может вернуть `page` (текст той же страницы); свой инструмент с картинкой — запасной текст
|
|
25
|
+
`toolImageFallbackText(текст)` из `@ykurilov/agent-chat-core`.
|
|
26
|
+
|
|
27
|
+
## 0.60.0
|
|
28
|
+
|
|
29
|
+
### Minor Changes
|
|
30
|
+
|
|
31
|
+
- 6e8723c: Снимок страницы в ответе инструмента уходит картинкой к Together и Fireworks (раньше — строкой текста, ход зависал); профиль DeepSeek 4.1 Flash видит снимки через DeepSeek, Together, Fireworks и OpenRouter. Сценарии не называют инструменты, которых нет у модели этого хода; правила просмотра ссылок — в описании инструмента.
|
|
32
|
+
|
|
3
33
|
## 0.59.0
|
|
4
34
|
|
|
5
35
|
### Minor Changes
|
|
@@ -749,6 +749,7 @@ function withRouteModels(models, providers) {
|
|
|
749
749
|
const pricing = model.pricing && { ...model.pricing };
|
|
750
750
|
const imageProviders = model.toolResultImageProviders && [...model.toolResultImageProviders];
|
|
751
751
|
const imagesByProvider = model.acceptsImagesByProvider && { ...model.acceptsImagesByProvider };
|
|
752
|
+
const blindProviders = model.blindToolResultImageProviders && [...model.blindToolResultImageProviders];
|
|
752
753
|
for (const route of routes) {
|
|
753
754
|
const source = route.servesModelsOf;
|
|
754
755
|
if (!model.providerModelIds[source]) continue;
|
|
@@ -756,13 +757,15 @@ function withRouteModels(models, providers) {
|
|
|
756
757
|
if (pricing?.[source] && !pricing[route.id]) pricing[route.id] = pricing[source];
|
|
757
758
|
if (imageProviders?.includes(source) && !imageProviders.includes(route.id)) imageProviders.push(route.id);
|
|
758
759
|
if (imagesByProvider && source in imagesByProvider && !(route.id in imagesByProvider)) imagesByProvider[route.id] = imagesByProvider[source];
|
|
760
|
+
if (blindProviders?.includes(source) && !blindProviders.includes(route.id) && !imageProviders?.includes(route.id)) blindProviders.push(route.id);
|
|
759
761
|
}
|
|
760
762
|
return {
|
|
761
763
|
...model,
|
|
762
764
|
providerModelIds,
|
|
763
765
|
...pricing && { pricing },
|
|
764
766
|
...imageProviders && { toolResultImageProviders: imageProviders },
|
|
765
|
-
...imagesByProvider && { acceptsImagesByProvider: imagesByProvider }
|
|
767
|
+
...imagesByProvider && { acceptsImagesByProvider: imagesByProvider },
|
|
768
|
+
...blindProviders && { blindToolResultImageProviders: blindProviders }
|
|
766
769
|
};
|
|
767
770
|
});
|
|
768
771
|
}
|
|
@@ -2341,6 +2344,151 @@ function createForgedBlockGuard(formats) {
|
|
|
2341
2344
|
};
|
|
2342
2345
|
}
|
|
2343
2346
|
|
|
2347
|
+
// src/agent-run/tool-result-images-policy.ts
|
|
2348
|
+
import { wrapLanguageModel as wrapLanguageModel3 } from "ai";
|
|
2349
|
+
|
|
2350
|
+
// src/provider-errors/error-fields.ts
|
|
2351
|
+
import { APICallError, RetryError } from "ai";
|
|
2352
|
+
function underlyingError(error) {
|
|
2353
|
+
return RetryError.isInstance(error) ? error.lastError : error;
|
|
2354
|
+
}
|
|
2355
|
+
function errorStatusCode(error) {
|
|
2356
|
+
if (APICallError.isInstance(error)) return error.statusCode;
|
|
2357
|
+
if (typeof error !== "object" || error === null) return void 0;
|
|
2358
|
+
const fields = error;
|
|
2359
|
+
const nested = typeof fields.error === "object" && fields.error !== null ? fields.error.code : void 0;
|
|
2360
|
+
for (const value of [fields.statusCode, fields.status, fields.code, nested]) {
|
|
2361
|
+
const code = asStatusCode(value);
|
|
2362
|
+
if (code !== void 0) return code;
|
|
2363
|
+
}
|
|
2364
|
+
return void 0;
|
|
2365
|
+
}
|
|
2366
|
+
function asStatusCode(value) {
|
|
2367
|
+
const code = typeof value === "number" ? value : typeof value === "string" && /^\s*\d{3}\s*$/.test(value) ? Number(value) : void 0;
|
|
2368
|
+
return code !== void 0 && Number.isInteger(code) && code >= 100 && code <= 599 ? code : void 0;
|
|
2369
|
+
}
|
|
2370
|
+
function errorText(error) {
|
|
2371
|
+
if (APICallError.isInstance(error)) return [error.responseBody, error.message].filter(Boolean).join("\n");
|
|
2372
|
+
if (error instanceof Error) return error.message;
|
|
2373
|
+
if (typeof error === "string") return error;
|
|
2374
|
+
try {
|
|
2375
|
+
return JSON.stringify(error) ?? "";
|
|
2376
|
+
} catch {
|
|
2377
|
+
return "";
|
|
2378
|
+
}
|
|
2379
|
+
}
|
|
2380
|
+
function errorHeaders(error) {
|
|
2381
|
+
return APICallError.isInstance(error) ? error.responseHeaders : void 0;
|
|
2382
|
+
}
|
|
2383
|
+
function errorName(error) {
|
|
2384
|
+
const name = typeof error === "object" && error !== null ? error.name : void 0;
|
|
2385
|
+
return typeof name === "string" && name !== "" ? name : void 0;
|
|
2386
|
+
}
|
|
2387
|
+
function requestedRetryMs(error, nowMs) {
|
|
2388
|
+
const headers = errorHeaders(error);
|
|
2389
|
+
if (!headers) return void 0;
|
|
2390
|
+
let requestedMs;
|
|
2391
|
+
const inMs = Number.parseFloat(headers["retry-after-ms"] ?? "");
|
|
2392
|
+
if (!Number.isNaN(inMs)) requestedMs = inMs;
|
|
2393
|
+
const retryAfter = headers["retry-after"];
|
|
2394
|
+
if (requestedMs === void 0 && retryAfter) {
|
|
2395
|
+
const inSeconds = Number.parseFloat(retryAfter);
|
|
2396
|
+
requestedMs = Number.isNaN(inSeconds) ? Date.parse(retryAfter) - nowMs : inSeconds * 1e3;
|
|
2397
|
+
}
|
|
2398
|
+
if (requestedMs === void 0 || Number.isNaN(requestedMs) || requestedMs < 0) return void 0;
|
|
2399
|
+
return requestedMs;
|
|
2400
|
+
}
|
|
2401
|
+
|
|
2402
|
+
// src/agent-run/tool-result-images-policy.ts
|
|
2403
|
+
var FALLBACK_NAMESPACE = "agentChat";
|
|
2404
|
+
var NO_IMAGE_NOTE = "[\u041A\u0430\u0440\u0442\u0438\u043D\u043A\u0430 \u043D\u0435 \u043F\u043E\u043A\u0430\u0437\u0430\u043D\u0430: \u044D\u0442\u0430 \u043C\u043E\u0434\u0435\u043B\u044C \u0443 \u044D\u0442\u043E\u0433\u043E \u043F\u0440\u043E\u0432\u0430\u0439\u0434\u0435\u0440\u0430 \u043D\u0435 \u043F\u043E\u043B\u0443\u0447\u0430\u0435\u0442 \u043A\u0430\u0440\u0442\u0438\u043D\u043A\u0438 \u0438\u0437 \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u043E\u0432.]";
|
|
2405
|
+
var REFUSAL_CODES = /* @__PURE__ */ new Set([400, 413, 415, 422]);
|
|
2406
|
+
var TOO_LARGE = 413;
|
|
2407
|
+
var IMAGE_REFUSAL = /image|vision|multi-?modal|content[ _-]?type|base64|unsupported content|media[ _-]?type|mime/i;
|
|
2408
|
+
var CONTEXT_OVERFLOW = /context (length|window)|maximum context|too many tokens|token limit|prompt is too long|reduce the length/i;
|
|
2409
|
+
function toolImageFallbackText(text) {
|
|
2410
|
+
return { type: "text", text, providerOptions: { [FALLBACK_NAMESPACE]: { toolImageFallback: true } } };
|
|
2411
|
+
}
|
|
2412
|
+
function modelMaySeeToolResultImages(profile, providerId) {
|
|
2413
|
+
return modelAcceptsImages(profile, providerId) && !profile.blindToolResultImageProviders?.includes(providerId);
|
|
2414
|
+
}
|
|
2415
|
+
var refusalsByCatalog = /* @__PURE__ */ new WeakMap();
|
|
2416
|
+
function refusalsOf(catalog) {
|
|
2417
|
+
let refusals = refusalsByCatalog.get(catalog);
|
|
2418
|
+
if (!refusals) {
|
|
2419
|
+
refusals = /* @__PURE__ */ new Set();
|
|
2420
|
+
refusalsByCatalog.set(catalog, refusals);
|
|
2421
|
+
}
|
|
2422
|
+
return refusals;
|
|
2423
|
+
}
|
|
2424
|
+
function withToolResultImagePolicy(model, context) {
|
|
2425
|
+
if (typeof model === "string") return model;
|
|
2426
|
+
const { profile, provider } = context;
|
|
2427
|
+
const pair = `${profile.id}@${provider.id}`;
|
|
2428
|
+
const refusals = refusalsOf(context.catalog);
|
|
2429
|
+
const carries = provider.toolResultImages === true || profile.toolResultImageProviders?.includes(provider.id) === true;
|
|
2430
|
+
const imagesAllowed = () => carries && modelMaySeeToolResultImages(profile, provider.id) && !refusals.has(pair);
|
|
2431
|
+
async function attempt(params, call) {
|
|
2432
|
+
if (!hasToolResultImages(params.prompt)) return call(params);
|
|
2433
|
+
if (!imagesAllowed()) return call({ ...params, prompt: withToolResultImages(params.prompt, false) });
|
|
2434
|
+
try {
|
|
2435
|
+
return await call({ ...params, prompt: withToolResultImages(params.prompt, true) });
|
|
2436
|
+
} catch (error) {
|
|
2437
|
+
const cause = underlyingError(error);
|
|
2438
|
+
const code = errorStatusCode(cause);
|
|
2439
|
+
if (params.abortSignal?.aborted || code === void 0 || !REFUSAL_CODES.has(code)) throw error;
|
|
2440
|
+
const text = errorText(cause);
|
|
2441
|
+
if (CONTEXT_OVERFLOW.test(text)) throw error;
|
|
2442
|
+
const aboutImage = code !== TOO_LARGE && IMAGE_REFUSAL.test(text);
|
|
2443
|
+
if (code !== TOO_LARGE && !aboutImage) throw error;
|
|
2444
|
+
const result = await call({ ...params, prompt: withToolResultImages(params.prompt, false) });
|
|
2445
|
+
if (aboutImage) refusals.add(pair);
|
|
2446
|
+
context.logError?.(
|
|
2447
|
+
aboutImage ? `[\u043A\u0430\u0440\u0442\u0438\u043D\u043A\u0438 \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u043E\u0432] ${provider.id} \u043E\u0442\u043A\u0430\u0437\u0430\u043B \u043C\u043E\u0434\u0435\u043B\u0438 ${profile.id} \u0432 \u043A\u0430\u0440\u0442\u0438\u043D\u043A\u0435 \u0438\u0437 \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442\u0430 \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u0430 (\u043A\u043E\u0434 ${code}): \u0437\u0430\u043F\u0440\u043E\u0441 \u043F\u043E\u0432\u0442\u043E\u0440\u0451\u043D \u0442\u0435\u043A\u0441\u0442\u043E\u043C \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u044B, \u0434\u043E \u043F\u0435\u0440\u0435\u0437\u0430\u043F\u0443\u0441\u043A\u0430 \u0441\u043B\u0443\u0436\u0431\u044B \u2014 \u0442\u043E\u043B\u044C\u043A\u043E \u0442\u0435\u043A\u0441\u0442` : `[\u043A\u0430\u0440\u0442\u0438\u043D\u043A\u0438 \u0438\u043D\u0441\u0442\u0440\u0443\u043C\u0435\u043D\u0442\u043E\u0432] ${provider.id}: \u0437\u0430\u043F\u0440\u043E\u0441 \u043C\u043E\u0434\u0435\u043B\u0438 ${profile.id} \u0441 \u043A\u0430\u0440\u0442\u0438\u043D\u043A\u043E\u0439 \u0441\u043B\u0438\u0448\u043A\u043E\u043C \u0432\u0435\u043B\u0438\u043A (\u043A\u043E\u0434 ${code}), \u043F\u043E\u0432\u0442\u043E\u0440\u0451\u043D \u0442\u0435\u043A\u0441\u0442\u043E\u043C \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u044B`
|
|
2448
|
+
);
|
|
2449
|
+
return result;
|
|
2450
|
+
}
|
|
2451
|
+
}
|
|
2452
|
+
return wrapLanguageModel3({
|
|
2453
|
+
model,
|
|
2454
|
+
middleware: {
|
|
2455
|
+
specificationVersion: "v4",
|
|
2456
|
+
wrapGenerate: ({ params, model: inner }) => attempt(params, (next) => inner.doGenerate(next)),
|
|
2457
|
+
wrapStream: ({ params, model: inner }) => attempt(params, (next) => inner.doStream(next))
|
|
2458
|
+
}
|
|
2459
|
+
});
|
|
2460
|
+
}
|
|
2461
|
+
function isImage(item) {
|
|
2462
|
+
return item.type === "file" && (item.mediaType === "image" || item.mediaType.startsWith("image/"));
|
|
2463
|
+
}
|
|
2464
|
+
function isFallback(item) {
|
|
2465
|
+
return item.type === "text" && item.providerOptions?.[FALLBACK_NAMESPACE]?.toolImageFallback === true;
|
|
2466
|
+
}
|
|
2467
|
+
function hasToolResultImages(prompt) {
|
|
2468
|
+
return prompt.some(
|
|
2469
|
+
(message) => message.role === "tool" && message.content.some((part) => part.type === "tool-result" && part.output.type === "content" && part.output.value.some((item) => isImage(item) || isFallback(item)))
|
|
2470
|
+
);
|
|
2471
|
+
}
|
|
2472
|
+
function withToolResultImages(prompt, images) {
|
|
2473
|
+
return prompt.map((message) => {
|
|
2474
|
+
if (message.role !== "tool") return message;
|
|
2475
|
+
return {
|
|
2476
|
+
...message,
|
|
2477
|
+
content: message.content.map((part) => {
|
|
2478
|
+
if (part.type !== "tool-result" || part.output.type !== "content") return part;
|
|
2479
|
+
return { ...part, output: { ...part.output, value: contentFor(part.output.value, images) } };
|
|
2480
|
+
})
|
|
2481
|
+
};
|
|
2482
|
+
});
|
|
2483
|
+
}
|
|
2484
|
+
function contentFor(value, images) {
|
|
2485
|
+
if (images) return value.filter((item) => !isFallback(item));
|
|
2486
|
+
const fallbacks = value.filter(isFallback).map((item) => ({ type: "text", text: item.text }));
|
|
2487
|
+
const kept = value.filter((item) => !isImage(item) && !isFallback(item));
|
|
2488
|
+
if (fallbacks.length > 0) return [...kept, ...fallbacks];
|
|
2489
|
+
return value.some(isImage) ? [...kept, { type: "text", text: NO_IMAGE_NOTE }] : kept;
|
|
2490
|
+
}
|
|
2491
|
+
|
|
2344
2492
|
// src/storage/chat-storage.ts
|
|
2345
2493
|
var RELEASED_ANSWER_ALIVE_AT = (/* @__PURE__ */ new Date(0)).toISOString();
|
|
2346
2494
|
function aliveAtByLocalClock(mark) {
|
|
@@ -2849,58 +2997,6 @@ function isEmptyTokenUsage(usage) {
|
|
|
2849
2997
|
return usage.inputTokens === 0 && usage.outputTokens === 0 && usage.reasoningTokens === 0 && (usage.cacheWriteTokens ?? 0) === 0;
|
|
2850
2998
|
}
|
|
2851
2999
|
|
|
2852
|
-
// src/provider-errors/error-fields.ts
|
|
2853
|
-
import { APICallError, RetryError } from "ai";
|
|
2854
|
-
function underlyingError(error) {
|
|
2855
|
-
return RetryError.isInstance(error) ? error.lastError : error;
|
|
2856
|
-
}
|
|
2857
|
-
function errorStatusCode(error) {
|
|
2858
|
-
if (APICallError.isInstance(error)) return error.statusCode;
|
|
2859
|
-
if (typeof error !== "object" || error === null) return void 0;
|
|
2860
|
-
const fields = error;
|
|
2861
|
-
const nested = typeof fields.error === "object" && fields.error !== null ? fields.error.code : void 0;
|
|
2862
|
-
for (const value of [fields.statusCode, fields.status, fields.code, nested]) {
|
|
2863
|
-
const code = asStatusCode(value);
|
|
2864
|
-
if (code !== void 0) return code;
|
|
2865
|
-
}
|
|
2866
|
-
return void 0;
|
|
2867
|
-
}
|
|
2868
|
-
function asStatusCode(value) {
|
|
2869
|
-
const code = typeof value === "number" ? value : typeof value === "string" && /^\s*\d{3}\s*$/.test(value) ? Number(value) : void 0;
|
|
2870
|
-
return code !== void 0 && Number.isInteger(code) && code >= 100 && code <= 599 ? code : void 0;
|
|
2871
|
-
}
|
|
2872
|
-
function errorText(error) {
|
|
2873
|
-
if (APICallError.isInstance(error)) return [error.responseBody, error.message].filter(Boolean).join("\n");
|
|
2874
|
-
if (error instanceof Error) return error.message;
|
|
2875
|
-
if (typeof error === "string") return error;
|
|
2876
|
-
try {
|
|
2877
|
-
return JSON.stringify(error) ?? "";
|
|
2878
|
-
} catch {
|
|
2879
|
-
return "";
|
|
2880
|
-
}
|
|
2881
|
-
}
|
|
2882
|
-
function errorHeaders(error) {
|
|
2883
|
-
return APICallError.isInstance(error) ? error.responseHeaders : void 0;
|
|
2884
|
-
}
|
|
2885
|
-
function errorName(error) {
|
|
2886
|
-
const name = typeof error === "object" && error !== null ? error.name : void 0;
|
|
2887
|
-
return typeof name === "string" && name !== "" ? name : void 0;
|
|
2888
|
-
}
|
|
2889
|
-
function requestedRetryMs(error, nowMs) {
|
|
2890
|
-
const headers = errorHeaders(error);
|
|
2891
|
-
if (!headers) return void 0;
|
|
2892
|
-
let requestedMs;
|
|
2893
|
-
const inMs = Number.parseFloat(headers["retry-after-ms"] ?? "");
|
|
2894
|
-
if (!Number.isNaN(inMs)) requestedMs = inMs;
|
|
2895
|
-
const retryAfter = headers["retry-after"];
|
|
2896
|
-
if (requestedMs === void 0 && retryAfter) {
|
|
2897
|
-
const inSeconds = Number.parseFloat(retryAfter);
|
|
2898
|
-
requestedMs = Number.isNaN(inSeconds) ? Date.parse(retryAfter) - nowMs : inSeconds * 1e3;
|
|
2899
|
-
}
|
|
2900
|
-
if (requestedMs === void 0 || Number.isNaN(requestedMs) || requestedMs < 0) return void 0;
|
|
2901
|
-
return requestedMs;
|
|
2902
|
-
}
|
|
2903
|
-
|
|
2904
3000
|
// src/provider-errors/error-signs.ts
|
|
2905
3001
|
var signs = (patterns) => new RegExp(patterns.join("|"), "i");
|
|
2906
3002
|
var OVERFLOW_SIGNS = [
|
|
@@ -3406,7 +3502,7 @@ function usageMinusJournaled(usage, journaled) {
|
|
|
3406
3502
|
}
|
|
3407
3503
|
|
|
3408
3504
|
// src/agent-run/build-model-request.ts
|
|
3409
|
-
import { convertToModelMessages, wrapLanguageModel as
|
|
3505
|
+
import { convertToModelMessages, wrapLanguageModel as wrapLanguageModel6 } from "ai";
|
|
3410
3506
|
|
|
3411
3507
|
// src/conversation/hanging-tool-calls.ts
|
|
3412
3508
|
var PRELIMINARY_OUTPUT_MAX_LENGTH = 2e3;
|
|
@@ -3626,7 +3722,7 @@ ${chunk.delta}` } : chunk];
|
|
|
3626
3722
|
}
|
|
3627
3723
|
|
|
3628
3724
|
// src/agent-run/observe-model-calls.ts
|
|
3629
|
-
import { wrapLanguageModel as
|
|
3725
|
+
import { wrapLanguageModel as wrapLanguageModel4 } from "ai";
|
|
3630
3726
|
function observeModelCalls(model, context) {
|
|
3631
3727
|
if (typeof model === "string") return model;
|
|
3632
3728
|
const now = context.now ?? Date.now;
|
|
@@ -3636,7 +3732,7 @@ function observeModelCalls(model, context) {
|
|
|
3636
3732
|
attempts.set(params.prompt, attempt);
|
|
3637
3733
|
return attempt;
|
|
3638
3734
|
};
|
|
3639
|
-
return
|
|
3735
|
+
return wrapLanguageModel4({
|
|
3640
3736
|
model,
|
|
3641
3737
|
middleware: {
|
|
3642
3738
|
specificationVersion: "v4",
|
|
@@ -4224,7 +4320,7 @@ function withoutUndelivered(history) {
|
|
|
4224
4320
|
}
|
|
4225
4321
|
|
|
4226
4322
|
// src/agent-run/garbled-tail.ts
|
|
4227
|
-
import { wrapLanguageModel as
|
|
4323
|
+
import { wrapLanguageModel as wrapLanguageModel5 } from "ai";
|
|
4228
4324
|
var SIGN_ORDER = ["think", "role", "restart", "tool"];
|
|
4229
4325
|
var TOKEN_SOURCE = String.raw`<\/?think>|<\|[^|\n<>]{1,40}\|>|<|[^|\n<>]{1,40}|>`;
|
|
4230
4326
|
var TEMPLATE_TOKEN = new RegExp(TOKEN_SOURCE, "g");
|
|
@@ -4396,7 +4492,7 @@ function createGarbledTailGuard() {
|
|
|
4396
4492
|
/** Модель попытки под присмотром: каждый запрос — новый шаг. */
|
|
4397
4493
|
wrap(model) {
|
|
4398
4494
|
if (typeof model === "string") return model;
|
|
4399
|
-
return
|
|
4495
|
+
return wrapLanguageModel5({
|
|
4400
4496
|
model,
|
|
4401
4497
|
middleware: {
|
|
4402
4498
|
specificationVersion: "v4",
|
|
@@ -4519,7 +4615,12 @@ function withoutCodeAndQuotes(text) {
|
|
|
4519
4615
|
async function buildModelRequest(input) {
|
|
4520
4616
|
const found = findModel(input.catalog, input.modelId, input.providerId);
|
|
4521
4617
|
const { profile, provider } = found;
|
|
4522
|
-
const base = resolveLanguageModel(found, input.env, input.fetch)
|
|
4618
|
+
const base = withToolResultImagePolicy(resolveLanguageModel(found, input.env, input.fetch), {
|
|
4619
|
+
catalog: input.catalog,
|
|
4620
|
+
profile,
|
|
4621
|
+
provider,
|
|
4622
|
+
...input.logError && { logError: input.logError }
|
|
4623
|
+
});
|
|
4523
4624
|
const observed = input.observe ? observeModelCalls(base, {
|
|
4524
4625
|
...input.observe,
|
|
4525
4626
|
chatId: input.chatId,
|
|
@@ -4624,7 +4725,7 @@ function withoutLoneSurrogatesDeep(value) {
|
|
|
4624
4725
|
}
|
|
4625
4726
|
function withRequestWatch(model, provider, watch) {
|
|
4626
4727
|
if (!watch || typeof model === "string") return model;
|
|
4627
|
-
return
|
|
4728
|
+
return wrapLanguageModel6({
|
|
4628
4729
|
model,
|
|
4629
4730
|
middleware: {
|
|
4630
4731
|
specificationVersion: "v4",
|
|
@@ -4644,7 +4745,7 @@ function withRequestWatch(model, provider, watch) {
|
|
|
4644
4745
|
});
|
|
4645
4746
|
}
|
|
4646
4747
|
function withTransientRetries(model, context) {
|
|
4647
|
-
return typeof model === "string" ? model :
|
|
4748
|
+
return typeof model === "string" ? model : wrapLanguageModel6({ model, middleware: createTransientRetryMiddleware(context) });
|
|
4648
4749
|
}
|
|
4649
4750
|
function withLastMessageMark(messages, options2) {
|
|
4650
4751
|
if (!options2 || messages.length === 0) return messages;
|
|
@@ -8218,6 +8319,7 @@ function createDeepSeekProvider(connectionOptions = {}) {
|
|
|
8218
8319
|
// незнакомые поля не шлёт: `samplingRequest` нет, `sampling.minP` модели сюда не уходит.
|
|
8219
8320
|
// Кончившиеся деньги DeepSeek описывает телом ответа; код бывает не только 402.
|
|
8220
8321
|
errorHints: [{ match: /insufficient balance/i, kind: "insufficient_funds" }],
|
|
8322
|
+
toolResultImages: true,
|
|
8221
8323
|
...connectionField(connection),
|
|
8222
8324
|
...silenceField("DeepSeek", connectionOptions),
|
|
8223
8325
|
...hiddenField("DeepSeek", connectionOptions)
|
|
@@ -8242,6 +8344,59 @@ function sdkReasoningRequest(level) {
|
|
|
8242
8344
|
}
|
|
8243
8345
|
}
|
|
8244
8346
|
|
|
8347
|
+
// src/models/providers/tool-result-images.ts
|
|
8348
|
+
function withToolResultImages2(given) {
|
|
8349
|
+
return (async (input, init) => {
|
|
8350
|
+
const fetchImplementation = given ?? globalThis.fetch;
|
|
8351
|
+
if (typeof init?.body !== "string" || !init.body.includes('"role":"tool"')) return fetchImplementation(input, init);
|
|
8352
|
+
let body;
|
|
8353
|
+
try {
|
|
8354
|
+
body = JSON.parse(init.body);
|
|
8355
|
+
} catch {
|
|
8356
|
+
return fetchImplementation(input, init);
|
|
8357
|
+
}
|
|
8358
|
+
let changed = false;
|
|
8359
|
+
for (const message of body.messages ?? []) {
|
|
8360
|
+
if (message.role !== "tool" || typeof message.content !== "string") continue;
|
|
8361
|
+
const parts = imageContent(message.content);
|
|
8362
|
+
if (!parts) continue;
|
|
8363
|
+
message.content = parts;
|
|
8364
|
+
changed = true;
|
|
8365
|
+
}
|
|
8366
|
+
return fetchImplementation(input, changed ? { ...init, body: JSON.stringify(body) } : init);
|
|
8367
|
+
});
|
|
8368
|
+
}
|
|
8369
|
+
function imageContent(text) {
|
|
8370
|
+
if (!text.startsWith("[")) return void 0;
|
|
8371
|
+
let value;
|
|
8372
|
+
try {
|
|
8373
|
+
value = JSON.parse(text);
|
|
8374
|
+
} catch {
|
|
8375
|
+
return void 0;
|
|
8376
|
+
}
|
|
8377
|
+
if (!Array.isArray(value)) return void 0;
|
|
8378
|
+
const parts = [];
|
|
8379
|
+
let images = 0;
|
|
8380
|
+
for (const part of value) {
|
|
8381
|
+
if (part?.type === "text" && typeof part.text === "string") {
|
|
8382
|
+
parts.push({ type: "text", text: part.text });
|
|
8383
|
+
continue;
|
|
8384
|
+
}
|
|
8385
|
+
const url = part?.type === "file" && typeof part.mediaType === "string" && part.mediaType.startsWith("image/") ? imageUrl(part.mediaType, part.data) : void 0;
|
|
8386
|
+
if (!url) return void 0;
|
|
8387
|
+
parts.push({ type: "image_url", image_url: { url } });
|
|
8388
|
+
images += 1;
|
|
8389
|
+
}
|
|
8390
|
+
return images > 0 ? parts : void 0;
|
|
8391
|
+
}
|
|
8392
|
+
function imageUrl(mediaType, data) {
|
|
8393
|
+
if (data?.type === "url" && typeof data.url === "string") return data.url;
|
|
8394
|
+
if (data?.type !== "data") return void 0;
|
|
8395
|
+
const raw = data.data;
|
|
8396
|
+
const base64 = typeof raw === "string" ? raw : raw && typeof raw === "object" ? Buffer.from(Object.keys(raw).sort((a, b) => Number(a) - Number(b)).map((key) => raw[key])).toString("base64") : void 0;
|
|
8397
|
+
return base64 === void 0 ? void 0 : `data:${mediaType === "image/jpg" ? "image/jpeg" : mediaType};base64,${base64}`;
|
|
8398
|
+
}
|
|
8399
|
+
|
|
8245
8400
|
// src/models/providers/session-header.ts
|
|
8246
8401
|
var SAFE_SESSION_ID = /^[A-Za-z0-9._:~-]{1,128}$/;
|
|
8247
8402
|
function sessionHeaderValue(chatId) {
|
|
@@ -8267,7 +8422,7 @@ function createFireworksProvider(connectionOptions = {}) {
|
|
|
8267
8422
|
id: "fireworks",
|
|
8268
8423
|
displayName: "Fireworks",
|
|
8269
8424
|
apiKeyEnv: "FIREWORKS_API_KEY",
|
|
8270
|
-
createLanguageModel: (providerModelId, apiKey, options2) => createFireworks({ apiKey, fetch: options2?.fetch, ...connection })(providerModelId),
|
|
8425
|
+
createLanguageModel: (providerModelId, apiKey, options2) => createFireworks({ apiKey, fetch: withToolResultImages2(options2?.fetch), ...connection })(providerModelId),
|
|
8271
8426
|
reasoningRequest: (level) => sdkReasoningRequest(level),
|
|
8272
8427
|
requestOptions: ({ chatId }) => ({
|
|
8273
8428
|
providerOptions: {
|
|
@@ -8275,6 +8430,7 @@ function createFireworksProvider(connectionOptions = {}) {
|
|
|
8275
8430
|
},
|
|
8276
8431
|
headers: { "x-session-affinity": sessionHeaderValue(chatId) }
|
|
8277
8432
|
}),
|
|
8433
|
+
toolResultImages: true,
|
|
8278
8434
|
...connectionField(connection),
|
|
8279
8435
|
...silenceField("Fireworks", connectionOptions),
|
|
8280
8436
|
...hiddenField("Fireworks", connectionOptions)
|
|
@@ -8284,7 +8440,7 @@ var fireworksProvider = createFireworksProvider();
|
|
|
8284
8440
|
|
|
8285
8441
|
// src/models/providers/openrouter.ts
|
|
8286
8442
|
import { createOpenRouter } from "@openrouter/ai-sdk-provider";
|
|
8287
|
-
import { wrapLanguageModel as
|
|
8443
|
+
import { wrapLanguageModel as wrapLanguageModel7 } from "ai";
|
|
8288
8444
|
|
|
8289
8445
|
// src/models/providers/openrouter-cache-mark-limit.ts
|
|
8290
8446
|
function openrouterCacheMarkLimit(providerModelId) {
|
|
@@ -8522,6 +8678,7 @@ function createOpenRouterProvider(options2 = {}) {
|
|
|
8522
8678
|
id,
|
|
8523
8679
|
displayName,
|
|
8524
8680
|
...id !== OPENROUTER_ID && { servesModelsOf: OPENROUTER_ID },
|
|
8681
|
+
toolResultImages: true,
|
|
8525
8682
|
...connectionField(connection),
|
|
8526
8683
|
...silenceField("OpenRouter", options2),
|
|
8527
8684
|
...hiddenField("OpenRouter", options2),
|
|
@@ -8534,7 +8691,7 @@ function createOpenRouterProvider(options2 = {}) {
|
|
|
8534
8691
|
createLanguageModel: (providerModelId, apiKey, requestOptions) => {
|
|
8535
8692
|
const markLimit = openrouterCacheMarkLimit(providerModelId);
|
|
8536
8693
|
const fetch = markLimit === void 0 ? requestOptions?.fetch : fetchWithCacheMarkLimit(requestOptions?.fetch, markLimit);
|
|
8537
|
-
return
|
|
8694
|
+
return wrapLanguageModel7({
|
|
8538
8695
|
model: createOpenRouter({ apiKey, fetch, ...connection }).chat(providerModelId, routingOptions),
|
|
8539
8696
|
middleware: [
|
|
8540
8697
|
openrouterRoutingMiddleware(routing),
|
|
@@ -8639,10 +8796,11 @@ function createTogetherProvider(connectionOptions = {}) {
|
|
|
8639
8796
|
id: "together",
|
|
8640
8797
|
displayName: "Together AI",
|
|
8641
8798
|
apiKeyEnv: "TOGETHER_API_KEY",
|
|
8642
|
-
createLanguageModel: (providerModelId, apiKey, options2) => createTogetherAI({ apiKey, fetch: options2?.fetch, ...connection })(providerModelId),
|
|
8799
|
+
createLanguageModel: (providerModelId, apiKey, options2) => createTogetherAI({ apiKey, fetch: withToolResultImages2(options2?.fetch), ...connection })(providerModelId),
|
|
8643
8800
|
reasoningRequest: (level) => sdkReasoningRequest(level),
|
|
8644
8801
|
samplingRequest: ({ minP }) => minP === void 0 ? {} : { togetherai: { min_p: minP } },
|
|
8645
8802
|
requestOptions: () => ({}),
|
|
8803
|
+
toolResultImages: true,
|
|
8646
8804
|
...connectionField(connection),
|
|
8647
8805
|
...silenceField("Together AI", connectionOptions),
|
|
8648
8806
|
...hiddenField("Together AI", connectionOptions)
|
|
@@ -8699,11 +8857,11 @@ var deepseek41FlashProfile = {
|
|
|
8699
8857
|
displayName: "DeepSeek 4.1 Flash",
|
|
8700
8858
|
contextWindowTokens: 1e6,
|
|
8701
8859
|
acceptsImages: true,
|
|
8702
|
-
//
|
|
8703
|
-
|
|
8704
|
-
//
|
|
8705
|
-
//
|
|
8706
|
-
toolResultImageProviders: ["deepseek", "openrouter"],
|
|
8860
|
+
// Картинку в результате инструмента видит у самого DeepSeek (проверено вживую 2026-10-05), у Together и Fireworks
|
|
8861
|
+
// (вживую 2026-10-15: картинка в ответе инструмента и в сообщении человека — цвет назван верно; запись каталога
|
|
8862
|
+
// Together «только текст» устарела) и через OpenRouter у всех хозяев, кроме запрещённых ниже: одни её не видят,
|
|
8863
|
+
// другие отвергают запрос (замер всех хозяев модели в OpenRouter 2026-10-06, specs/research-openrouter-tool-images.md).
|
|
8864
|
+
toolResultImageProviders: ["deepseek", "together", "fireworks", "openrouter"],
|
|
8707
8865
|
reasoning: { replayPastTurns: true, levels: ["off", "on"], defaultLevel: "on" },
|
|
8708
8866
|
// Запрет стоит всегда, а не только при картинке: запрос одинаков в каждом ходе, кэш и выбранный хозяин не теряются.
|
|
8709
8867
|
providerOptions: { openrouter: { provider: { ignore: ["deepinfra", "open-inference", "phala", "sail-research", "venice"] } } },
|
|
@@ -10436,7 +10594,7 @@ function isPlainObject3(value) {
|
|
|
10436
10594
|
}
|
|
10437
10595
|
|
|
10438
10596
|
// src/memory/memory-after-answer.ts
|
|
10439
|
-
import { wrapLanguageModel as
|
|
10597
|
+
import { wrapLanguageModel as wrapLanguageModel8 } from "ai";
|
|
10440
10598
|
var DRAIN_WAIT_MS = 5e3;
|
|
10441
10599
|
function createMemoryAfterAnswer({ memory, storage, catalog, env, logError, turnObserver }) {
|
|
10442
10600
|
const running = /* @__PURE__ */ new Set();
|
|
@@ -10481,7 +10639,7 @@ function createMemoryAfterAnswer({ memory, storage, catalog, env, logError, turn
|
|
|
10481
10639
|
};
|
|
10482
10640
|
}
|
|
10483
10641
|
function withRetries(model, context) {
|
|
10484
|
-
return typeof model === "string" ? model :
|
|
10642
|
+
return typeof model === "string" ? model : wrapLanguageModel8({ model, middleware: createTransientRetryMiddleware(context) });
|
|
10485
10643
|
}
|
|
10486
10644
|
|
|
10487
10645
|
// src/conversation/to-agent-chat-message.ts
|
|
@@ -14017,6 +14175,8 @@ export {
|
|
|
14017
14175
|
serviceBlockName,
|
|
14018
14176
|
checkServiceBlockFormats,
|
|
14019
14177
|
cutForgedBlocks,
|
|
14178
|
+
toolImageFallbackText,
|
|
14179
|
+
modelMaySeeToolResultImages,
|
|
14020
14180
|
RELEASED_ANSWER_ALIVE_AT,
|
|
14021
14181
|
aliveAtByLocalClock,
|
|
14022
14182
|
silenceBoundaryByLocalClock,
|
|
@@ -14116,4 +14276,4 @@ export {
|
|
|
14116
14276
|
InvalidWidgetAnswerError,
|
|
14117
14277
|
createChatService
|
|
14118
14278
|
};
|
|
14119
|
-
//# sourceMappingURL=chunk-
|
|
14279
|
+
//# sourceMappingURL=chunk-XQ5FJ2VT.js.map
|