Files
view-mate/TASK-media-tools.md
subochev 7527cf8848 LLM: интерфейс + две реализации (Qwen удалённо / Gemma 4 E2B локально) + переключатель
- LlmClient интерфейс; RemoteLlmClient (OkHttp, llm.binom.pw) и LocalLlmClient (LiteRT-LM, Gemma 4 E2B)
- LocalLlm: Gemma4E2B (SHA-256, GPU/CPU-бэкенды), buildLocalLlmPrompt (SYSTEM→systemInstruction, TOOL→user), GemmaModelDownloader (стриминг, прогресс, фолбэк источников), спекулятивное декодирование
- LlmPrefs (персистентность выбора), PhoneApp: фабрика по llmChoice, пересоздание ассистента, скачивание/отмена
- UI: GlassesInfoScreen — выбор сервер/локальная + прогресс; ChatScreen — индикатор активной модели
- Тесты: +12 (LlmLocalTest) — phone 154 зелёных
2026-08-23 02:52:22 +03:00

6.0 KiB
Raw Permalink Blame History

Задача: медиа-тулы для тулсета media (агент «Порфирий»)

Механика тулсетов/скиллов УЖЕ готова (AgentToolkit, Skills, Toolsets — см. TASK-agent-tools.md, закоммичено c5cbc89). Твоя задача — реализовать сам тулсет media с исполнителем на базе Jellyfin. Чистую механику НЕ трогать.

Контекст (что уже есть)

  • AgentToolkit (app-phone/.../agent/AgentToolkit.kt): ядро 5 тул + диспач + подмена. Регистрация тулсета: ToolsetRegistry.register(Toolset(...), executor).
  • Toolset/ToolSignature/ToolsetExecutor (Toolsets.kt): сигнатуры с префиксом media_, аргументы строками (query, id, seek_ms?).
  • PhoneApp.kt уже создаёт AgentToolkit(skills=..., toolsets=ToolsetRegistry(), showText=...) — тулсеты пустые, тулы не зарегистрированы.
  • JellyfinClient (lib-core/src/commonMain/kotlin/pw/binom/viewmate/core/media/JellyfinClient.kt):
    • suspend fun search(userId: String, query: String): List<JellyfinItem>
    • suspend fun item(userId: String, itemId: String): JellyfinItem?
    • suspend fun allItems(userId: String, types: List<String>): List<JellyfinItem>
    • fun posterUrl(itemId: String, maxWidth: Int): String
  • JellyfinItem (JellyfinModels.kt): Id, Name, Type, Overview, ImageTags, SeriesName, SeasonId, IndexNumber, ParentIndexNumber, RunTimeTicks.
  • В PhoneApp: jellyfin: JellyfinClient, chatDao, server.hub (broadcast), actions (воспроизведение), state.

Что сделать

1. Тулсет media (зарегистрировать в PhoneApp)

Тулы (имена БЕЗ префикса — префикс media_ добавляется механизмом):

  • search — поиск фильмов/сериалов по названию. Аргументы: query (обязательный), limit? (опциональный, дефолт 5).
    • Возврат: список строк «id: Название (Год) — Тип» (до limit), либо «ничего не найдено по запросу ...».
  • info — детали элемента по id. Аргументы: id (обязательный).
    • Возврат: Название, Тип, Год, Обзор (Overview, обрезать до ~300 символов), длительность (RunTimeTicks/10_000_000 сек), есть ли постер.
  • play — запуск воспроизведения на очках. Аргументы: id (обязательный), seek_ms? (опциональный).
    • Проверки: очки подключены (hub.connected > 0), видео скачано на очки (локальный путь file:////video.mkv существует на очках — через hub/каталог), звук готов на телефоне (audioReady).
    • Воспроизведение — через существующий механизм DetailsScreen (actions.watch(item.Id, audioIndex, localVideoPath, phoneAudioReady)) — вынеси/переиспользуй логику, не дублируй криво.
    • Возврат: «запущено: <Название>» или понятная ошибка с причиной («очки не подключены», «видео не скачано на очки — скачай вручную», «звук не готов»).

2. Юзер-ID для Jellyfin

Jellyfin требует userId. Посмотри, как DetailsScreen/CatalogScreen получают userId (вероятно app.jellyfin.users() первый или конфиг). Используй тот же механизм, закешируй.

3. Скилл media (файл skills/media.md)

Формат (как в Skills.kt):

---
name: media
description: поиск фильмов, детали, запуск/управление просмотром.
---
Текст скилла: объясни модели, что для работы с медиа вызвать enable_toolset|media, и опиши тулы search/info/play с аргументами.

4. Тесты (JVM, без Android — по возможности)

Механику тестировать НЕ надо (уже покрыта). Но исполнитель media_search можно покрыть, если JellyfinClient абстрагируется (интерфейс/fake). Если JellyfinClient — конкретный класс с Ktor, сделай минимальный интерфейс MediaSource (search/item) и обёртку — тогда executor тестируется на фейке. Не усложняй сверх меры: если это требует большого рефакторинга, опиши, что осталось непокрытым.

Ограничения

  • Код только Kotlin
  • НЕ трогать AgentToolkit/Skills/Toolsets (механика) — только регистрация тулсета и его исполнитель
  • НЕ ломать существующие экраны (DetailsScreen/CatalogScreen)
  • Все новые файлы — в app-phone (агент живёт на телефоне)
  • Сборка: ./gradlew :app-phone:assembleDebug — зелёная; ./gradlew :app-phone:testDebugUnitTest — зелёный (все старые тесты остаются зелёными)
  • НЕ коммитить

Проверка

  1. ./gradlew :app-phone:testDebugUnitTest — зелёные (старые + новые)
  2. ./gradlew :app-phone:assembleDebug — собирается
  3. Опиши в ответе: какие файлы создал/изменил, как зарегистрирован тулсет, как получается userId, что осталось непокрыто тестами.