Files
openclaw/extensions/telegram/src/sticker-cache.ts
Peter Steinberger 06f5f73e47 refactor: own model discovery by runtime lifecycle (#111173)
* refactor(agents): prepare model runtime catalogs

Build lifecycle-owned model and auth snapshots, carry prepared stores into hot agent paths, and serialize config/auth publication.

Credits @zeroaltitude's #90741 investigation and benchmark approach.

* refactor: finish lifecycle-owned model discovery

* fix: align prepared model catalog contracts

* test: align lifecycle catalog mocks

* test: fix prepared catalog type fixtures

* refactor: split prepared model runtime ownership

* fix: import prepared runtime replacement gate type

* test: split media runtime coverage

* test: preserve image auth fixture key types

* test: isolate lifecycle gate fixtures

* chore: keep release changelog owned

* refactor: finish prepared model catalog migration

* test: keep catalog review fixtures scanner-safe

* refactor: preserve lifecycle model runtime ownership

* fix: close prepared runtime lifecycle races

* fix: preserve compaction workspace fallback

* chore: document btw generation rebinding

* fix: preserve prepared generation boundaries

* fix: keep model-list discovery flag explicit

* fix: serialize standalone model runtime activation

* refactor: migrate subagent model catalog lookup

* refactor: clarify doctor catalog lookup seam

* chore: refresh plugin sdk api baseline

* test: migrate swarm catalog dependency

* refactor(telegram): rename runtime catalog seam

* refactor: extract model-aware tool context

* test(models): isolate lifecycle catalog fixtures

* refactor(agents): avoid btw parameter rebinding

* fix(net-policy): align root ipaddr dependency

* fix(build): keep net policy dependency bundled

* fix(deadcode): document net policy compile dependency
2026-07-19 05:30:54 -07:00

183 lines
5.3 KiB
TypeScript

// Telegram plugin module implements sticker cache behavior.
import { resolveApiKeyForProvider } from "openclaw/plugin-sdk/agent-runtime";
import type { ModelCatalogEntry } from "openclaw/plugin-sdk/agent-runtime";
import {
findModelInCatalog,
loadPreparedModelCatalog,
modelSupportsVision,
} from "openclaw/plugin-sdk/agent-runtime";
import { resolveAgentDir, resolveDefaultModelForAgent } from "openclaw/plugin-sdk/agent-runtime";
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
import { resolveAutoImageModel } from "openclaw/plugin-sdk/media-runtime";
import {
resolveAutoMediaKeyProviders,
resolveDefaultMediaModel,
} from "openclaw/plugin-sdk/media-runtime";
import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
import { getTelegramRuntime } from "./runtime.js";
export {
cacheSticker,
getAllCachedStickers,
getCachedSticker,
getCacheStats,
searchStickers,
type CachedSticker,
} from "./sticker-cache-store.js";
const STICKER_DESCRIPTION_PROMPT =
"Describe this sticker image in 1-2 sentences. Focus on what the sticker depicts (character, object, action, emotion). Be concise and objective.";
function isMinimaxVlmProvider(provider: string): boolean {
const normalized = normalizeLowercaseStringOrEmpty(provider);
return (
normalized === "minimax" ||
normalized === "minimax-cn" ||
normalized === "minimax-portal" ||
normalized === "minimax-portal-cn"
);
}
export interface DescribeStickerParams {
imagePath: string;
cfg: OpenClawConfig;
agentDir?: string;
agentId?: string;
}
/**
* Describe a sticker image using vision API.
* Auto-detects an available vision provider based on configured API keys.
* Returns null if no vision provider is available.
*/
export async function describeStickerImage(params: DescribeStickerParams): Promise<string | null> {
const { imagePath, cfg, agentDir, agentId } = params;
const defaultModel = resolveDefaultModelForAgent({ cfg, agentId });
let activeModel = undefined as { provider: string; model: string } | undefined;
let catalog: ModelCatalogEntry[] = [];
try {
catalog = await loadPreparedModelCatalog({
config: cfg,
...(agentId
? {
agentId,
agentDir: agentDir ?? resolveAgentDir(cfg, agentId),
}
: agentDir
? { agentDir }
: {}),
readOnly: true,
});
const entry = findModelInCatalog(catalog, defaultModel.provider, defaultModel.model);
const supportsVision = modelSupportsVision(entry);
if (supportsVision) {
const model = isMinimaxVlmProvider(defaultModel.provider)
? resolveDefaultMediaModel({
cfg,
providerId: defaultModel.provider,
capability: "image",
includeConfiguredImageModels: false,
})
: defaultModel.model;
if (model) {
activeModel = { provider: defaultModel.provider, model };
}
}
} catch {
// Ignore catalog failures; fall back to auto selection.
}
const hasProviderKey = async (provider: string) => {
try {
await resolveApiKeyForProvider({ provider, cfg, agentDir });
return true;
} catch {
return false;
}
};
const autoProviders = resolveAutoMediaKeyProviders({
cfg,
capability: "image",
});
const selectCatalogModel = (provider: string) => {
const entries = catalog.filter(
(entry) =>
normalizeLowercaseStringOrEmpty(entry.provider) ===
normalizeLowercaseStringOrEmpty(provider) && modelSupportsVision(entry),
);
if (entries.length === 0) {
return undefined;
}
const defaultId = resolveDefaultMediaModel({
cfg,
providerId: provider,
capability: "image",
includeConfiguredImageModels: !isMinimaxVlmProvider(provider),
});
const preferred = entries.find((entry) => entry.id === defaultId);
if (isMinimaxVlmProvider(provider)) {
return preferred;
}
return preferred ?? entries[0];
};
let resolved = null as { provider: string; model?: string } | null;
if (
activeModel &&
autoProviders.includes(activeModel.provider) &&
(await hasProviderKey(activeModel.provider))
) {
resolved = activeModel;
}
if (!resolved) {
for (const provider of autoProviders) {
if (!(await hasProviderKey(provider))) {
continue;
}
const entry = selectCatalogModel(provider);
if (entry) {
resolved = { provider, model: entry.id };
break;
}
}
}
if (!resolved) {
resolved = await resolveAutoImageModel({
cfg,
agentDir,
activeModel,
});
}
if (!resolved?.model) {
logVerbose("telegram: no vision provider available for sticker description");
return null;
}
const { provider, model } = resolved;
logVerbose(`telegram: describing sticker with ${provider}/${model}`);
try {
const result = await getTelegramRuntime().mediaUnderstanding.describeImageFileWithModel({
filePath: imagePath,
mime: "image/webp",
cfg,
agentDir,
provider,
model,
prompt: STICKER_DESCRIPTION_PROMPT,
maxTokens: 150,
timeoutMs: 30_000,
});
return result.text ?? null;
} catch (err) {
logVerbose(`telegram: failed to describe sticker: ${String(err)}`);
return null;
}
}