cc-libs/.plans/opencc-agent-proxy-GRILLED_PLAN.md

23 KiB

GRILLED_PLAN - cc-agent-proxy + programme Lua opencc

Compagnon de .plans/opencc-agent-proxy-plan.md. Chaque question a une recommandation par defaut et un bloc TODO: answer que tu remplis. Un prochain /grill-me lira ce fichier et le condensera en plan v2 pret a executer.

Convention :

  • Remplace TODO: answer par ta reponse (1-3 lignes suffisent).
  • Si tu choisis une option listee, ecris Option N + raison breve.
  • Si tu inventes une 5e option, ecris-la in extenso.
  • Si une question devient sans objet apres une reponse majeure, ecris N/A - voir Q<n>.
  • Laisse TODO: answer (optionnel) tel quel si pas d'avis.

Regles de base du PoC

  • Une turtle, un OpenCode, un proxy, une reponse utilisable. Rien de plus.
  • Contraintes explicites > design generique.
  • Pas de packaging tant que la boucle n'a pas tourne in-game.
  • Aucun secret commit.
  • La forme des reponses OpenCode est suspecte tant qu'on ne l'a pas vue en vrai.

A. Les 8 questions determinantes

A repondre en premier. Le reste devient souvent trivial une fois ces choix trances.

A1. Critere d'acceptation du PoC

Quand dit-on "c'est fini" ?

  • Option 1 - curl roundtrip : curl /ask retourne une vraie reponse OpenCode. Pas d'in-game.
  • Option 2 - CraftOS-PC roundtrip : opencc "salut" repond dans l'emulateur, en pointant sur le proxy local.
  • Option 3 - in-game ATM10 : opencc marche sur une vraie turtle apres config http.rules.
  • Option 4 - conversation in-game : Option 3 + verifier que la session persistante garde le contexte sur plusieurs prompts.

Recommandation : Option 4. C'est ce qui prouve que le pipeline tient bout en bout. 1-3 sont des jalons intermediaires. Premier prompt anodin (dis bonjour) avant prompts libres.

Pourquoi ca compte : decide si async, multi-turtle, logs riches, quotas, packaging et UX terminale sont hors scope.

TODO: answer

A2. Autorite et workspace d'OpenCode

Le proxy parle a un opencode serve qui a, potentiellement, acces complet a des fichiers et a un shell. Une turtle qui prompt = un acteur exterieur qui peut indirectement declencher des actions cote hote.

  • Option 1 - workspace dedie jetable : opencode serve lance dans un dir temporaire (/tmp/opencc-poc) sans secrets, sans repo perso.
  • Option 2 - workspace projet dedie : un dossier ~/opencc-workspace/ vide ou minimal, persistant entre runs.
  • Option 3 - repo perso : non, trop risque.
  • Option 4 - sandbox container : OpenCode dans Docker avec mount limite. Plus de cout pour le PoC.

Recommandation : Option 1. Workspace jetable, recree a chaque session de test. Documenter le dir exact dans .env.

Pourquoi ca compte : c'est la vraie frontiere de confiance, plus que le token Lua.

TODO: answer

A3. Cycle de vie de la session

  • Option 1 - session unique partagee : le proxy cree une session au boot, toutes les turtles tapent dedans. Conflits de memoire conversationnelle.
  • Option 2 - session par turtle, persistee cote turtle : la turtle stocke sessionId dans settings ; le proxy se contente de relayer.
  • Option 3 - session ephemere par prompt : chaque /ask cree+detruit. Pas de continuite. Plus simple, perd le contexte.
  • Option 4 - session par turtle, mappee cote proxy : table turtleId -> sessionId cote proxy. Necessite d'identifier la turtle.

Recommandation : Option 2. Le proxy reste sans etat ; la turtle envoie sessionId optionnel, le proxy en cree une si absent et le renvoie dans la reponse.

TODO: answer

A4. Sync vs async pour /ask

POST /session/:id/message peut bloquer longtemps. CC:Tweaked a des timeouts HTTP (~30s typiques, depend du serveur).

  • Option 1 - sync stricte : le proxy attend la fin et renvoie. Risque de timeout cote turtle.
  • Option 2 - async + polling : POST /ask -> jobId, GET /ask/:jobId pour poll. Plus robuste, plus complexe.
  • Option 3 - keepalive / chunked : inutile en HTTP CC standard.
  • Option 4 - sync now, async later : on commence en sync (prompts courts pour PoC), on documente la limite, on bascule async si on l'atteint. API /ask reste compatible.

Recommandation : Option 4.

TODO: answer

A5. Topologie de deploiement

Le plan source dit "deploiement HTTPS, RP, firewall, exposition publique geres separement par Guillaume".

  • Option 1 - tout sur ta machine perso : opencode serve + cc-agent-proxy en localhost, expose via tunnel (Cloudflare, ngrok).
  • Option 2 - VPS dedie : meme hote distant, HTTPS via Caddy/nginx.
  • Option 3 - homelab : Option 2 mais chez toi, derriere ton RP existant.
  • Option 4 - deux hotes separes : OpenCode sur GPU dedie, proxy ailleurs.

Conditionne : auth (A6), OPENCODE_BASE_URL, qui termine TLS.

Recommandation : Option 3 si tu as un homelab, sinon Option 1. Proxy + opencode sur le meme hote, loopback entre les deux, proxy expose en HTTPS.

TODO: answer

A6. Modele d'authentification turtle <-> proxy

  • Option 1 - token unique partage : un seul PROXY_TOKEN, meme valeur sur toutes les turtles via settings. Pas de revocation granulaire.
  • Option 2 - token par turtle : table de tokens cote proxy. Permet revocation par turtle.
  • Option 3 - HMAC signe : turtle envoie os.getComputerID() + HMAC. Plus complexe, peu de gain sans rotation.
  • Option 4 - allowlist IP : impossible, la turtle sort via le proxy HTTP du serveur Minecraft.

Recommandation : Option 1. Helper isTokenValid(token) pour permettre une table plus tard sans refactor.

TODO: answer

A7. Forme de la reponse renvoyee a la turtle

OpenCode retourne parts, tool calls, code blocks. La turtle a peu de RAM.

  • Option 1 - texte plat : { reply }. Tout ce qui n'est pas texte est jete.
  • Option 2 - texte + meta : { reply, model, tokens, sessionId }.
  • Option 3 - parts brutes : la turtle parse. Trop couteux.
  • Option 4 - texte plat + troncature : { reply, sessionId, truncated } avec MAX_REPLY_CHARS (ex. 4 KiB). Logs proxy gardent le complet.

Recommandation : Option 4.

TODO: answer

A8. Scope minimal v1 (in et out)

  • Option 1 - hello world : turtle envoie "ping", recoit "pong". Une seule route, sync, pas de session persistante.
  • Option 2 - conversation 1 turtle / 1 session : echange sur plusieurs prompts, sessionId persistante.
  • Option 3 - + outils OpenCode : agent peut lire/ecrire un fichier dans le workspace.
  • Option 4 - + multi-turtle : deux turtles en parallele sans collision.

Recommandation : Option 2.

Hors-scope explicite du PoC : async, multi-turtles, package ccpm, deploiement auto, Docker, quotas, UI terminale riche, streaming OpenCode (/event).

TODO: answer


B. Scope, contraintes de temps, cleanup

B1. Limite de temps et plan de bascule

Si la forme de l'API OpenCode bloque, qu'est-ce qu'on fait ?

Recommandation : une seule passe d'implementation. Si l'API OpenCode bloque, on arrete d'abstraire et on capture le contrat reel via curl avant de coder.

TODO: answer

B2. Rollback / cleanup post-PoC

Comment on nettoie une fois la demo faite ?

Recommandation : stop proxy, stop opencode serve, supprimer sessions de test, rotation du PROXY_TOKEN et du OPENCODE_PASSWORD utilises.

TODO: answer


C. OpenCode upstream : contrat et comportement

C1. Version OpenCode et Basic Auth verifies au premier run

OpenCode evolue. Le plan suppose Basic Auth active. A valider.

Recommandation : enregistrer opencode --version utilise lors de la validation ; pinger /global/health avec Basic Auth via curl avant de coder le client.

TODO: answer

C2. Spec exacte de POST /session/:id/message

Plan : { parts: [{ type: "text", text }], model? }. Marque comme inconnu.

Recommandation : curl http://localhost:4096/doc une fois OpenCode lance, capturer un sample reel de reponse, le commiter sanitize dans cc-agent-proxy/test/fixtures/.

TODO: answer

C3. Provider et modele LLM

OPENCODE_MODEL optionnel. Quel provider Guillaume utilise (anthropic, openai, ollama local) ?

Recommandation : laisser defaut OpenCode, ne passer model que si OPENCODE_MODEL est defini dans .env.

TODO: answer

C4. Timeout amont vers OpenCode

Recommandation : REQUEST_TIMEOUT_MS configurable, defaut 60s. Au dela -> 504 cote proxy.

TODO: answer

C5. Exposition des erreurs OpenCode au turtle

Recommandation : pas de stack trace ni secret. Renvoyer { error: "...", code: "..." } avec message public court ; detail dans les logs proxy uniquement.

TODO: answer

C6. SessionId auto-creation et recovery si stale

Deux comportements distincts :

  • sessionId absent dans la requete -> proxy en cree un (recommande oui).
  • sessionId fourni mais 404 amont -> ?
    • Option A : recreer silencieusement.
    • Option B : renvoyer { error: "session_expired" }, laisser la turtle decider (opencc --new).

Recommandation : Option B. Auto-recreation cacherait une perte de contexte deroutante.

TODO: answer

C7. Comportement quand OpenCode est down

  • 502/503 brutal ?
  • Retry interne avec backoff ?
  • /health reflete-t-il l'etat upstream ?

Recommandation : pas de retry auto, 502 + JSON { error: "upstream_down" }. /health inclut un champ opencode: "up"/"down".

TODO: answer

C8. Gestion des parts non-textuelles (tools, code blocks)

Recommandation : concatener les parts texte, ignorer les non-textuelles dans la reply, mais logger un compteur cote proxy (parts_text, parts_tool, parts_other).

TODO: answer


D. Proxy API shape

D1. Routes minimales du proxy

Plan : /health, /ask, /session.

  • /session obligatoire ou implicite via /ask ?
  • /health inclut upstream ?

Recommandation :

  • GET /health -> { proxy: "ok", opencode: "up"/"down" }.
  • POST /ask -> obligatoire, body { prompt, sessionId? }, response { sessionId, reply, truncated }.
  • POST /session -> garder, retourne { sessionId }. Pratique pour initialiser ou nommer une session ; ne casse pas le PoC si on ne l'expose pas a la turtle.

TODO: answer

D2. Codes HTTP stables pour la turtle

Recommandation :

  • 200 succes
  • 400 requete invalide (prompt vide, body malforme)
  • 401 token absent/invalide
  • 413 prompt trop long
  • 502 OpenCode down
  • 504 OpenCode timeout
  • 500 erreur proxy inattendue

TODO: answer

D3. Limites taille de requete

  • Max prompt length ?
  • Max body size HTTP ?

Recommandation : prompt max 8 KiB (MAX_PROMPT_CHARS env), body max 32 KiB cote Fastify. Pas de rate-limit dans le PoC.

TODO: answer

D4. Request IDs / correlation

Recommandation : requestId auto-genere cote proxy, present dans tous les logs, non expose dans la reponse JSON (juste dans header x-request-id pour debug optionnel).

TODO: answer


E. Stack Node / TypeScript

E1. Runtime et gestionnaire de paquets

Recommandation : Node 20+, pnpm. Sinon npm si tu prefers vanilla.

TODO: answer

E2. Strictness TypeScript

Recommandation : "strict": true + noUncheckedIndexedAccess. exactOptionalPropertyTypes evite (bruit pour peu de valeur en PoC).

TODO: answer

E3. Framework HTTP et validation

Plan : Fastify + Zod.

Recommandation : Fastify + Zod + fastify-type-provider-zod pour validation automatique des routes. Confirme ou propose autre.

TODO: answer

E4. Logging

Recommandation : Pino (default Fastify). LOG_LEVEL=info par defaut, debug en dev. Flag LOG_BODIES=true pour inclure prompt/reply ; off par defaut. Jamais logger token ni OPENCODE_PASSWORD. Logs : status, duree, sessionId (12 premiers chars), requestId, longueurs prompt/reply.

TODO: answer

E5. Process / lancement / deploiement

  • tsx en dev, node dist/index.js apres tsc en prod ?
  • Dockerfile ? systemd ? pm2 ?
  • Bind 0.0.0.0 ? Shutdown gracieux ?

Recommandation : tsx en dev, node dist en prod, pas de Docker, pas de systemd dans le repo (tu deploies a la main). 0.0.0.0 car expose derriere RP. Shutdown : app.close() sur SIGTERM, rien de plus.

TODO: answer

E6. Tests Node

Plan : vitest, fetch mocke, app.inject(). Pas d'objectif de couverture %.

Recommandation : couvrir /ask happy path, 401, OpenCode 5xx mock, prompt vide, prompt trop long, sessionId 404 amont. Fixture OpenCode reelle (cf C2) utilisee dans opencode.test.ts.

TODO: answer

E7. Variables d'env (en plus du tableau du plan)

A ajouter :

  • LOG_LEVEL (info/debug)
  • LOG_BODIES (true/false)
  • MAX_REPLY_CHARS (defaut 4096)
  • MAX_PROMPT_CHARS (defaut 8192)
  • REQUEST_TIMEOUT_MS (defaut 60000)
  • OPENCODE_WORKSPACE (info uniquement, documente le dir A2)

Recommandation : oui, ajouter les six. .env.example les liste, jamais de secret reel.

TODO: answer

E8. CORS, body parsing, etat en memoire

  • CORS : non, ce n'est pas un navigateur.
  • Body : JSON only.
  • Etat : stateless (cf A3 Option 2).

Recommandation : garder tel quel. Confirme ou contredit.

TODO: answer


F. Auth, secrets, exposition publique

F1. Generation du PROXY_TOKEN

Recommandation : genere a la main (openssl rand -hex 32), ecrit dans .env. Si absent au boot, le proxy refuse de demarrer (fail-fast).

TODO: answer

F2. HTTPS vs HTTP cote turtle

ATM10 accepte les deux si dans http.rules. Token nu en HTTP = leak.

Recommandation : HTTPS uniquement, meme en homelab, via le RP existant.

TODO: answer

F3. Saisie du token sur la turtle

Le secret ne doit jamais etre dans un fichier Lua commit.

Recommandation : settings set opencc.proxy_token <token> + settings.save() manuellement dans l'environnement de chaque turtle. Documenter dans le quickstart.

TODO: answer

F4. Token en query string ?

Recommandation : non, header uniquement. Les query strings se retrouvent dans les logs RP/proxy.

TODO: answer

F5. Headers acceptes : Bearer et X-Proxy-Token

Plan : les deux pour simplicite CC.

Recommandation : accepter Authorization: Bearer <token> ET X-Proxy-Token, documenter Bearer comme prefere. X-Proxy-Token reste seulement si CC pose souci avec Authorization (a tester).

TODO: answer

F6. Comparaison constant-time

Recommandation : crypto.timingSafeEqual apres normalisation (longueur egalisee) pour eviter le throw sur taille differente.

TODO: answer

F7. Rotation du token

Recommandation : hors scope PoC. Redemarrer proxy + reconfigurer turtles = acceptable.

TODO: answer

F8. Rate-limit et allowlist IP

Recommandation : ni l'un ni l'autre dans l'app. Si exposition publique le demande, c'est au RP / firewall de gerer (donc en dehors du repo).

TODO: answer

F9. Basic Auth OpenCode

Recommandation : credentials uniquement via env (OPENCODE_USERNAME, OPENCODE_PASSWORD), valides au boot, sinon fail-fast.

TODO: answer


G. Programme Lua opencc

G1. Emplacement et packaging

Plan : apis/libopencc.lua, programs/opencc.lua, tests/opencc.lua.

Recommandation : pas de packages/tos-agent/ccpm.json avant validation in-game (cf I3). Fichiers nus dans apis/ et programs/ pendant le PoC.

TODO: answer

G2. Source de config (settings vs fichier vs CLI)

Clefs settings :

  • opencc.proxy_url (URL complete HTTPS recommandee)
  • opencc.proxy_token
  • opencc.session_id (auto-remplie)
  • opencc.timeout (optionnel)

Recommandation : settings uniquement + flags CLI (--url, --token) qui overrident pour le debug. Si proxy_url ou proxy_token manque, message clair + exit non-zero. Pas de host/port separes.

TODO: answer

G3. Format CLI

  • opencc --help, --version
  • opencc "prompt" (argv) ou interactif via read() si pas d'arg
  • REPL multi-tour ?

Recommandation : pas de REPL. Un prompt par invocation. Le contexte vient de la session_id persistante.

TODO: answer

G4. Persistance et reset de la session

  • Persistance : settings.set('opencc.session_id', id); settings.save() ou fichier dedie ?
  • Reset : opencc --new ? --reset-session ?

Recommandation : settings (coherence G2) ; flag opencc --new qui efface opencc.session_id et cree une nouvelle session via le prochain /ask.

TODO: answer

G5. Affichage de la reponse

  • Print brut ?
  • Word-wrap manuel ?
  • Pagination ?
  • Print metadata (sessionId, model) ?

Recommandation : print + word-wrap simple sur largeur terminal. Pas de pagination. Pas de metadata sauf en mode --verbose (a ajouter plus tard).

TODO: answer

G6. Erreurs reseau et http.rules

http.post peut renvoyer nil (handshake ko, host bloque par http.rules) ou un response status != 200.

Recommandation :

  • nil -> "proxy injoignable. Verifie opencc.proxy_url et que l'hote est autorise dans http.rules".
  • 401 -> "token invalide".
  • 413 -> "prompt trop long".
  • 502/504 -> "agent indisponible / trop lent".
  • Autre -> message generique + status code.

TODO: answer

G7. Timeout HTTP CC

http.post n'a pas de parametre timeout standard. CC:Tweaked >= 1.97 a http.request avec timeout ; a verifier sur ATM10.

Recommandation : exposer opencc.timeout. Si CC l'accepte, on l'utilise ; sinon documenter la limite et compter sur A4 Option 4 pour la suite.

TODO: answer

G8. Pattern factory et JSON

Repo : apis/libccpm.lua est une factory avec opts.http injectable. Meme chose pour libopencc ? Encodage JSON via textutils.serialiseJSON ?

Recommandation : factory : local createOpencc = require('/apis/libopencc'); local opencc = createOpencc({ http = http, settings = settings, computerCraftTimeout = ... }). JSON via textutils.serialiseJSON / unserialiseJSON.

TODO: answer

G9. Trim / multiline / vide

Recommandation : trim final, refus si prompt vide apres trim. Multiline preserve.

TODO: answer

G10. Tests Lua

Plan : injection d'un faux http via libtest.

Recommandation : tests/opencc.lua avec libtest, faux http qui verifie URL, headers (Bearer + Content-Type), body JSON, parsing succes, parsing erreur. Hors-scope : E2E reel avec vrai proxy.

TODO: answer


H. Tests et validation

H1. Fixture OpenCode capturee

Recommandation : oui (cf C2). Sanitize, store dans cc-agent-proxy/test/fixtures/session-message.json. Sert de reference pour opencode.test.ts.

TODO: answer

H2. E2E manuel vs automatise

Recommandation : manuel pour le PoC. Documenter les commandes (cf H7).

TODO: answer

H3. CraftOS-PC headless avant l'in-game

Recommandation : oui si just craftos --headless peut faire http.post. Premier roundtrip en local avant ATM10.

TODO: answer

H4. Pre-requis ATM10 a documenter

A documenter :

  • http enable cote serveur.
  • Hote du proxy autorise dans http.rules (allow *.tondomaine.tld:443 ou IP).
  • DNS / IP joignable depuis le serveur Minecraft.
  • Token + URL definis dans les settings de la turtle.
  • Provider LLM authentifie cote OpenCode.

Recommandation : checklist dans docs/opencc-quickstart.md ecrite apres la premiere validation reelle.

TODO: answer

H5. Hooks just check et just test

pre-commit = just check test (Lua + luacheck). Faut-il y mettre les tests Node ?

Recommandation : non. Recipe separee just node-check (eslint+prettier) et just node-test (vitest) dans un sous-Justfile de cc-agent-proxy/. Eviter une dependance implicite Node pour les commits Lua.

TODO: answer

H6. CI

Repo : pas de CI publique declaree, juste hooks locaux.

Recommandation : pas de CI pour le PoC. Plus tard, eventuellement un workflow GH Actions node-test + lua-check.

TODO: answer

H7. Transcript de validation manuelle

Recommandation : apres premier roundtrip reussi, enregistrer les commandes exactes et outputs sanitize dans docs/opencc-quickstart.md, incluant un deuxieme /ask avec le meme sessionId pour prouver la persistance.

TODO: answer


I. Architecture / coherence repo

I1. Reuse de apis/net.lua

Recommandation : non. net.lua est modem/rednet. http.post direct cote Lua est le bon choix.

TODO: answer

I2. Reuse de apis/eventloop.lua

Recommandation : non. Le PoC est request/response synchrone. Re-evaluer si A4 passe en async (G1 post-PoC).

TODO: answer

I3. Packaging ccpm

Recommandation : post-PoC. Apres validation in-game, creer packages/tos-agent/ccpm.json :

  • files : apis/libopencc.lua, programs/opencc.lua
  • dependencies : tos-core
  • autostart : aucun
  • Ajouter dans packages/index.json.

Declencheur : PoC reussi + au moins une raison de reinstaller/partager.

TODO: answer

I4. ADR

Recommandation : ADR-0012 "external agent bridge via HTTPS proxy" ecrit apres validation. Justifie la presence d'un sous-dossier Node dans un repo Lua.

TODO: answer

I5. Conformite CLAUDE.md

  • Bump _VERSION sur chaque module Lua modifie.
  • --version / --help sur opencc.
  • Lua : 2-space indent, ;, local function.
  • Node : suit Prettier default.

Recommandation : appliquer tel quel.

TODO: answer


J. Post-PoC (evolutions, hors scope mais a ne pas peindre dans un coin)

Reponds seulement si avis fort.

J1. Mode async

Trigger : reponses LLM frequemment > timeout CC/proxy.

Recommandation : ajouter POST /ask?async=1 + GET /ask/:jobId/status. Backward-compatible. opencc apprend a poll.

TODO: answer (optionnel)

J2. SSE / WebSocket pour streaming

Pas de SSE en CC:Tweaked, mais http.websocket existe.

Recommandation : proxy WS dedie si streaming devient utile. Hors-scope PoC.

TODO: answer (optionnel)

J3. Multi-turtle isolation et identite

Trigger : > 1 turtle/utilisateur sur le meme proxy.

Recommandation : avec A3 Option 2 c'est deja gratuit niveau sessions. Pour quotas, ajouter turtleId (= os.getComputerID()) au body et a la table de tokens.

TODO: answer (optionnel)

J4. Outils OpenCode (lecture/ecriture fichiers)

Recommandation : sandbox stricte (mount lecture-seule + un dir scratch). Hors-scope PoC.

TODO: answer (optionnel)

J5. Replacement de http.post par apis/net.lua

Recommandation : probablement jamais. net.lua est routeur/rednet, pas sortie HTTP.

TODO: answer (optionnel)

J6. Observabilite (metrics, traces)

Recommandation : ignorer en PoC. Pino logs suffisent. Prometheus exporter plus tard si quotas.

TODO: answer (optionnel)


K. Inconnues a valider au premier run

Pas de reponse texte ; coche au fur et a mesure.

  • Version exacte d'OpenCode utilisee (opencode --version).
  • Format reel des parts retournees par POST /session/:id/message.
  • Comportement OpenCode si model est passe vs absent.
  • Basic Auth vraiment necessaire dans la config de Guillaume.
  • Timeout effectif http.post cote CC sur ATM10 (et si CC:Tweaked version supporte un parametre timeout).
  • Reaction OpenCode si on hammer une session morte (404 ? autre ?).
  • Limite de longueur d'un message accepte par OpenCode.
  • Cas ou le provider LLM est down : OpenCode renvoie quoi ?
  • CraftOS-PC headless peut-il faire http.post HTTPS vers le proxy en local ?

L. Quoi repondre en priorite

Si tu veux aller vite, reponds dans cet ordre. Le reste decoule.

  1. A1 - critere d'acceptation
  2. A2 - workspace OpenCode (frontiere de confiance)
  3. A3 - cycle session
  4. A4 - sync vs async
  5. A5 - topologie deploiement
  6. A6 - auth turtle <-> proxy
  7. A7 - forme de la reponse
  8. A8 - scope v1

Une fois ces 8 trances, on peut souvent ignorer les sections B-J pour le "go/no-go" et juste valider les recommandations par defaut. Les detailler n'apporte de la valeur que si tu veux devier d'une recommandation precise.