7527cf8848
- 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 зелёных
66 lines
6.0 KiB
Markdown
66 lines
6.0 KiB
Markdown
# Задача: медиа-тулы для тулсета 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://<glassesMediaDir>/<id>/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, что осталось непокрыто тестами.
|