Flujo de Trabajo de Desarrollo de MMSP

Flujo de trabajo fijo para desarrollar el propio MMSP: agregar o actualizar soporte de modelos y cambiar sus páginas. Se usa cuando se solicita soportar un nuevo modelo o versión de protocolo en este repositorio, sincronizar llmsdk_docs, implementar un cliente proveedor, o cambiar el playground, el tracer o el sitio web. Incluye sincronización de documentación, captura de API en vivo, implementación pareada Python/TypeScript, pruebas e2e con ámbito de modelo y las reglas de UI para las tres páginas.

¿Qué es Flujo de Trabajo de Desarrollo de MMSP?

Este skill define un flujo de trabajo estricto y secuencial para añadir o actualizar el soporte de modelos de IA en el repositorio MMSP (Model-Message Stream Protocol). Su propósito es garantizar que cada nueva integración de modelo se base en documentación oficial, se valide con capturas de API en vivo y se implemente de manera consistente tanto en Python como en TypeScript. El flujo comienza con la sincronización de la documentación oficial del proveedor del modelo en `llmsdk_docs/`, seguido de la captura sin modificar de una interacción de streaming real de la API en `api_captures/`. Después, se implementan los clientes Python y TypeScript, que procesan el streaming de eventos delta para reconstruir elementos completos. El skill enfatiza reglas de seguridad para las credenciales, un enfoque de implementación mínima y fiel (fidelity) basado en pruebas de reproducción (replay probes), y un riguroso conjunto de pruebas e2e. Finalmente, actualiza la documentación de cambios (changelog) y las páginas de la interfaz de usuario (playground, tracer, sitio web). El flujo está diseñado para plataformas como Claude Code, Cursor o Codex, y modela un proceso de desarrollo altamente estructurado y repetible.

Compatible con✓Claude Code✓Codex CLI~Cursor✓Gemini CLI
npx skills add https://github.com/Prism-Shadow/model-message-stream-protocol/tree/HEAD/.agents/skills/mmsp-dev

Installed? Explore more Productividad y colaboración skills: steipete/gemini, steipete/gh-issues, steipete/skill-creator · View all 6 →

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

¿Qué hace Flujo de Trabajo de Desarrollo de MMSP?

Agregar o actualizar el soporte de modelos sigue las etapas a continuación, en orden. Cuando una etapa dice detenerse y preguntar, pausa y pregunta al usuario; no continúes hasta que el problema esté resuelto y nunca llenes el vacío tú mismo.

Mapa de directorios

llmsdk_docs/<model_version>/      Captura instantánea de la documentación oficial, una carpeta por generación de modelo (README.md + docs/)
api_captures/<protocol>/          Capturas de API sin procesar, ignoradas por git: cargas útiles de solicitudes + eventos de streaming
src_py/mmsp/<client_type>/        Cliente Python, una carpeta por tipo de cliente: <vendor>_official/ o <protocol>/
src_py/mmsp/auto_client.py        Crea el cliente para un tipo de cliente; la familia de un ID de modelo nombra su cliente oficial
src_ts/src/<client_type>/         Cliente TypeScript, refleja la carpeta de Python
src_ts/src/autoClient.ts          Enrutamiento TypeScript, refleja auto_client.py
src_py/tests/test_client.py       Pruebas e2e parametrizadas (AVAILABLE_MODELS limitados por entorno)
src_ts/tests/client.test.ts       Lo mismo para TypeScript
changelog/unreleased/             Donde van los cambios no publicados; renombrado a la versión en el lanzamiento
changelog/<version>/              Resumen del lanzamiento (README.md) más un archivo de detalle por entrada
changelog/README.md               El formato de entrada: bloque de metadatos, reglas del cuerpo, emparejamiento bilingüe
CHANGELOG.md                      Una línea breve por lanzamiento que enlaza a changelog/
src_py/mmsp/integration/          El playground, las páginas del tracer y el servidor MMSP, reflejado en src_ts/src/integration/
site/                             El sitio Astro en mmsp.penguin.ooo
.agents/skills/mmsp-dev/kill-ai-slop/   Guía y escáner de AI-slop integrados para el trabajo de UI a continuación
<name>.zh.md                      Contraparte en chino, requerida para cada archivo en changelog/

Etapa 1 — Sincronizar documentos oficiales en llmsdk_docs/

  • Las fuentes deben ser el sitio de documentación oficial del proveedor del modelo (por ejemplo, docs.anthropic.com, platform.openai.com, ai.google.dev). Nunca uses espejos de terceros, publicaciones de blog o memoria del modelo.
  • Guarda la captura instantánea en llmsdk_docs/<model_version>/ siguiendo las convenciones de carpeta existentes y lista la carpeta en llmsdk_docs/README.md. Ejecutar este flujo de trabajo es la solicitud explícita que la regla del repositorio contra la edición de llmsdk_docs/ solicita.
  • Cuando los documentos obtenidos difieran de una captura existente, los nuevos documentos oficiales ganan: actualiza los archivos antiguos en su lugar.
  • La captura debe ser lo suficientemente completa para implementar: esquemas de solicitud/respuesta, secuencia de eventos de streaming, salida de pensamiento, llamadas a herramientas, campos de uso y respuestas de error.
  • Detente y pregunta si la URL oficial es incierta o no se puede obtener una página. El usuario puede pegar el contenido manualmente.

Etapa 2 — Capturar un intercambio de API en vivo en api_captures/

  • Puerta: la variable de entorno de la clave API del proveedor debe estar configurada y ser utilizable. Usa las mismas variables de entorno y URLs base que src_py/tests/test_client.py (limitación de AVAILABLE_MODELS y _create_client). Detente y pregunta al usuario que proporcione la clave si falta; el flujo de trabajo no debe continuar sin ella.
  • Usando el SDK oficial del proveedor, o HTTP sin procesar exactamente como está documentado, ejecuta una solicitud de llamada a herramienta de streaming con pensamiento habilitado, luego envía el resultado de la herramienta de vuelta para que la captura también muestre cómo se reenvían los turnos del asistente.
  • Guarda el intercambio completo sin modificar en api_captures/<protocol>/ (ignorado por git), por ejemplo, round1.request.json más round1.stream.jsonl con cada evento de streaming sin procesar en orden. Nunca guardes credenciales.
  • Cuando algo en el intercambio no se serialice a JSON (cargas útiles binarias, objetos SDK que contengan valores no JSON), guarda su forma str() y analiza a partir de eso. Mantén el contenedor analizable: envuelve el texto en un objeto JSON para que el .jsonl mantenga un valor JSON por línea ({"unserializable_str": "<str(event)>"}, manteniendo el evento en orden de streaming), o pon todo el intercambio en un archivo .txt hermano cuando nada de él se serialice. Una captura str() sigue siendo el registro autorizado de lo que devolvió la API. Nunca descartes el evento, lo edites manualmente a JSON válido o recurras a la documentación porque la serialización falló.
  • Detente y pregunta ante cualquier error de API (clave no válida, cuota insuficiente, límite de velocidad). No simules la respuesta ni continúes solo con la documentación.
  • La captura es la referencia de implementación principal y supera a la documentación: donde no coincidan, implementa lo que la API realmente devolvió.
  • Luego, prueba la reproducción, porque la captura solo muestra lo que la API envía, no lo que necesita recibir. Reenvía el turno del asistente repetidamente, cada vez con un campo candidato eliminado (el id del elemento de razonamiento, su contenido, todo el elemento de razonamiento, cada campo que estuviste tentado a mantener) y registra qué eliminaciones la API aún acepta con el comportamiento del modelo intacto. Ese resultado, no la forma de la respuesta, decide lo que almacena la Etapa 3. Guarda los resultados de la prueba junto a la captura.

Etapa 3 — Implementar los clientes Python y TypeScript

  • Una carpeta por tipo de cliente, nombrada después de él: un cliente oficial por proveedor (openai_official/, anthropic_official/, google_official/, zai_official/, moonshot_official/, deepseek_official/, minimax_official/) habla la API propia del proveedor, conoce cada generación de los modelos del proveedor y lee la clave del proveedor del entorno; un cliente compatible por protocolo de red (openai_responses/, openai_chat/, openai_chat_vllm_adapter/, openai_embedding/, ant_messages/, google_genai/, mmsp/) habla ese protocolo para cualquier endpoint. La clase se nombra después de la carpeta (OpenAIOfficialClient, OpenaiChatClient), y el tipo de cliente es el nombre de la carpeta con guiones (openai-official, openai-chat).
    • Una nueva generación de modelos de un proveedor va al cliente oficial de ese proveedor. Diferencia el nuevo protocolo (captura + documentación) contra lo que el cliente envía hoy; donde una generación difiera, incluso por un solo nombre de clave, distingue las generaciones dentro del cliente por versión explícita ("4-6" in self._model, "gpt-6" in self._model), nunca por una subcadena simple como "claude" in model, y mantén las generaciones anteriores funcionando. Nunca elimines una generación a menos que el usuario lo indique explícitamente.
    • Un nuevo proveedor obtiene una nueva carpeta de cliente oficial, un nuevo tipo de cliente <vendor>-official en auto_client.py / autoClient.ts y una nueva fila en la tabla de familias de modelos allí: la familia es el prefijo con el que comienza cada ID de los modelos del proveedor (glm-, kimi-), comparado sin distinción de mayúsculas y minúsculas.
    • Un nuevo protocolo de red al que atienden varios proveedores obtiene una carpeta de cliente compatible y un tipo de cliente compatible; un cliente compatible no lleva peculiaridades de proveedor.
  • auto_client.py / autoClient.ts crean el cliente que nombra un client_type, a partir de la única tabla de tipos de cliente. Sin un client_type, la familia con la que comienza un ID de modelo nombra su cliente oficial; un ID de familia desconocida genera un error y pide una. El enrutamiento no tiene otra regla: sin coincidencia de subcadenas en los IDs de modelo, sin coincidencia de versión fuera de los clientes oficiales.
  • Una credencial va solo a donde fue dada. Este es el modelo de seguridad, y cada constructor de cliente lo sigue. Una clave pasada como api_key se usa tal cual. Un cliente que lee la clave genérica del entorno (OPENAI_API_KEY para openai_official y los clientes compatibles con el protocolo OpenAI, ANTHROPIC_API_KEY para anthropic_official y ant_messages, GEMINI_API_KEY para google_genai) la lee solo cuando el endpoint también proviene del entorno (OPENAI_BASE_URL / ANTHROPIC_BASE_URL / GEMINI_BASE_URL, o el valor predeterminado del proveedor): dado un base_url y sin api_key, genera un error en la construcción en lugar de enviar esa clave a otro endpoint. Eso es resolve_credentials / resolveCredentials en utils; llámalo, no lo reimplementes. Cualquier otro cliente oficial (deepseek_official, zai_official, moonshot_official, minimax_official, google_official) lee su propia variable (DEEPSEEK_API_KEY, ZAI_API_KEY, MOONSHOT_API_KEY, MINIMAX_API_KEY, GEMINI_API_KEY) sea cual sea la URL base que se le dé, y nunca la genérica; sin ella, genera un error <VAR> is required for <Client>.. Entrega cada credencial explícitamente al SDK del proveedor, incluyendo "ninguno" (authToken: null en TypeScript, un auth_token limpiado en Python), porque los SDKs llenan lo que no se les entrega de sus propias variables de entorno. Las pruebas sin conexión en tests/env-credentials.test.ts / tests/test_env_credentials.py construyen cada cliente bajo un entorno controlado y leen la credencial que contiene la instancia del SDK; agrega un nuevo cliente a ellas.
  • La reproducción debe ser mínima, no exhaustiva. Un turno de asistente reproducido debe ser aceptado por la API con el comportamiento del modelo intacto; no tiene que reproducir el elemento de red campo por campo. fidelity es un canal de excepción, no un espejo de la carga útil: un campo se gana un lugar allí solo cuando la prueba de la Etapa 2 muestra que la solicitud falla o el modelo se degrada sin él. Los IDs generados por el proveedor que la API regenera o ignora (un ID de elemento de razonamiento, un ID de elemento de salida) se quedan fuera. Los campos que la API exige van: la phase de GPT-5.5, la signature de pensamiento de Claude, el nombre exacto del campo de razonamiento que requiere un upstream estricto.
  • Nunca copies en fidelity lo que un campo universal ya contiene (el texto de pensamiento, el nombre o los argumentos analizados de una llamada a herramienta). Reconstruye el elemento de red a partir de los campos universales.
  • tool_call_id no es opcional: siempre captura el ID de llamada del proveedor y reprodúcelo. Cuando el formato de red lleva tanto un ID de elemento como un ID de llamada, el ID de llamada es el que correlaciona un resultado con su llamada.
  • Sigue al cliente de referencia; no rediseñes. openai_official/ es la forma para protocolos estilo Responses, openai_chat/ para Chat Completions: mismo orden de métodos, mismo flujo de control, mismos nombres. Un nuevo cliente debe leerse como una diferencia con respecto a su referencia, porque así es como se revisará.
  • Mantenlo legible de principio a fin. Un lector debe poder seguir una solicitud o respuesta directamente a través del archivo sin saltar entre definiciones, por lo que la inserción en línea es el valor predeterminado: lee los atributos del SDK directamente (model_output.delta, model_output.item.call_id) y mantén el acceso a campos, la aritmética de uso y el texto de error donde se usan. Extrae un ayudante solo para un bloque genuinamente grande y autocontenido (del tipo que enterraría el flujo principal si estuviera en línea, como obtener y decodificar una imagen), nunca para unas pocas líneas. Refleja los propios métodos privados del cliente de referencia (_convert_thinking_level_*, _convert_tool_choice) en lugar de inventar una capa junto a ellos; un shim que acepta tanto diccionarios como objetos SDK nunca es uno de ellos, porque los eventos están tipificados con los tipos propios del SDK.
  • Los clientes producen solo deltas; la clase base cierra los elementos. transform_model_output_to_uni_event / transformModelOutputToUniEvent convierte un evento de red en un UniEvent cuyos content_items son los elementos .delta que ese evento lleva, en orden de red, y nunca un elemento .done. _streaming_response_internal / _streamingResponseInternal construye la solicitud, abre el streaming y produce un evento por evento de red: un bucle simple, sin contabilidad de elementos, sin acumulador, sin análisis de argumentos, sin fusión de uso y sin stop sintetizado. usage_metadata / finish_reason viajan en eventos stop donde sea que la red los informe (Anthropic message_start y message_delta, los fragmentos de finalización y uso de Chat Completions, response.completed), fusionados campo por campo por la base; el evento stop de un cliente nunca termina el streaming público. Un evento de red sin nada universal es el evento delta vacío: sin elementos, uso nulo y motivo de finalización nulo. La clase base entrega cada delta a StreamItems (stream_items.py / streamItems.ts), que lo envía a medida que llega y cierra cada elemento con su elemento .done cuando comienza el siguiente elemento o termina el streaming del cliente; la base luego emite el único evento stop o genera EmptyResponseError. Los eventos que rompen el protocolo generan StreamProtocolError en todos los modos, y las pruebas e2e compartidas verifican el streaming de cada cliente con assert_stream_grammar / assertStreamGrammar.
  • Los deltas de un elemento son contiguos y comparten un fidelity.item_id. La salida del modelo es serial, por lo que un elemento se transmite a la vez y los eventos de fin de elemento del proveedor (content_block_stop, response.output_item.done, step.stop) se enumeran como no operaciones: una puerta de enlace que cierra sus elementos tarde o fuera de orden no cambia nada. Pasa el ID propio del proveedor, convertido a cadena, como fidelity.item_id: el index de Anthropic block, el item_id del evento Responses o item.id or item.call_id (None / undefined donde una puerta de enlace no envía ninguno), el index del paso de Interactions, el campo de red de Chat Completions (reasoning_content, reasoning, content, tool_calls), el tipo de parte de generateContent (function_call, thought, inline_thinking, inline_data, text); los vectores de incrustación no llevan ninguno. StreamItems lo elimina, por lo que nunca llega a consumidores, trazas o historiales.
  • StreamItems decide dónde termina un elemento; los clientes nunca lo hacen. Un delta pertenece al siguiente elemento cuando lleva otro item_id, es de otro tipo (un paso de pensamiento de Interactions que va texto, imagen, texto son tres elementos bajo un índice), o comienza un elemento por sí mismo: un tool_call.delta que lleva un nombre (cada proveedor lo envía una vez, en el primer delta de la llamada, por lo que las llamadas de Chat Completions no necesitan IDs propios), una imagen (inline_thinking, o inline_data con un tipo MIME image/*, mientras que el audio se transmite en fragmentos de un elemento), un vector de incrustación. De lo contrario, continúa el elemento que se está transmitiendo ahora: un delta sin un ID lo hace, y también los argumentos de una llamada, sea cual sea el ID que una puerta de enlace les ponga. La fidelidad enviada sola bajo el ID del elemento es de ese elemento, sea cual sea el tipo que lo lleve (un thought_signature de Interactions después de un pensamiento de imagen). La tabla por tipo en ese archivo (el campo creciente, los campos de encabezado del primer delta, cómo se unen los fragmentos) es el único código por tipo en el streaming: el elemento hecho es el primer delta del elemento con el campo creciente reemplazado por la unión de sus fragmentos; los campos de encabezado se toman del primer delta; los encabezados posteriores se ignoran.

Etapa 4 — Ejecutar las pruebas de integración compartidas

  • src_py/tests/test_client.py y src_ts/tests/client.test.ts son el único lugar para las pruebas de clientes. Una prueba añadida allí se ejecuta contra todos los clientes; no hay pruebas separadas por cliente.
  • AVAILABLE_MODELS en test_client.py enumera cada combinación (modelo, tipo de cliente) que se ha probado con una clave activa; el resto se salta con pytest.skip. AVAILABLE_MODELS se actualiza aquí, no en el mapa de directorios.
  • La prueba principal ejecuta una solicitud de streaming con pensamiento y dos llamadas a herramienta, envía un resultado de herramienta de vuelta para un segundo turno y verifica que la respuesta se lea como uni_items. assert_stream_grammar / assertStreamGrammar verifica cada streaming: que los elementos de contenido se abran y cierren, que los elementos .done aparezcan solo una vez, que el evento stop llegue exactamente una vez y que usage_metadata esté completo.
  • Las pruebas de credenciales (test_env_credentials.py) se ejecutan sin conexión en cada combinación de variable de entorno y construyen cada cliente bajo un entorno controlado para verificar que la instancia del SDK contenga la clave que esperamos; env-credentials.test.ts es su contraparte.
  • La prueba de inserción en tests/stream_items_test.py (test_stream_items) verifica internamente la división de deltas y la finalización de elementos. tests/test_vector_input.py y tests/test_image_input.py verifican los extremos de entrada. tests/auto_client_test.py prueba la tabla de tipos de cliente y sus enrutamientos. Los archivos de prueba de TypeScript reflejan la misma cobertura.

Etapa 5 — Cambiar las páginas de la interfaz de usuario (playground, tracer, sitio web)

  • src_py/mmsp/integration/ y src_ts/src/integration/ tienen la lógica para el playground interactivo, las páginas del tracer y el servidor proxy de MMSP que atiende al sitio Astro en site/.
  • Cuando se añada un modelo, la UI debe mostrarlo en el selector de modelos del playground, representar correctamente nuevos tipos de contenido (elementos de pensamiento, nuevas formas de datos en línea, diferentes esquemas de herramientas) y la página del tracer debe poder ingerir y mostrar los eventos del nuevo modelo. Los cambios en el lado Python (por ejemplo, nuevos formatos de serialización para la página del tracer) deben coincidir con los cambios en el lado TypeScript.
  • Antes de hacer cualquier cambio en la UI, ejecuta el escáner kill-ai-slop desde .agents/skills/mmsp-dev/kill-ai-slop/ sobre los archivos que estás a punto de cambiar. El escáner señala patrones de UI AI-slop (sombras de caja excesivas, bordes redondeados sospechosos, degradados donde no se necesitan, desenfoques decorativos, escalas de grises, animaciones de salto, etc.). Corrige lo que señale antes de continuar. Si el escáner no existe o falla, detente y pregunta.
  • Al enviar commits de cambios en la UI, ejecuta el escáner contra los archivos modificados de nuevo. Los cambios en la UI que introduzcan AI-slop son rechazados; este es un workflow libre de slop.

Etapa 6 — Documentación de cambios (changelog)

  • Cada entrada de changelog sigue el formato en changelog/README.md: bloque de metadatos (fecha, tipo, ámbito, etc.), luego el cuerpo en español e inglés (el repositorio es bilingüe e inglés-chino en la práctica, así que produce siempre tanto <name>.md como <name>.zh.md).
  • Los archivos van en changelog/unreleased/ hasta que se etiqueta un lanzamiento, momento en el que la carpeta completa se renombra a la versión.
  • CHANGELOG.md en la raíz debe actualizarse con una línea que enlace a la carpeta del changelog del lanzamiento.
  • El propio resumen del lanzamiento (changelog/<version>/README.md) enumera cada entrada, agrupada por tipo (añadido, cambiado, corregido, eliminado).

Reglas generales

  1. Nunca uses memoria del modelo en lugar de documentación oficial. Si surge una discrepancia, prevalece la documentación oficial.
  2. Las capturas de API tienen prioridad sobre la documentación. Si la API se comporta de manera diferente a los documentos, implementa el comportamiento real de la API.
  3. Ejecuta las pruebas e2e completas (AVAILABLE_MODELS completo o todo lo que se pueda probar) antes de marcar el trabajo como completo.
  4. Pareamiento Python/TypeScript: cada cambio en la lógica de Python debe tener una contraparte exacta en TypeScript. Si una característica es solo para un lado, documéntalo explícitamente.
  5. No hay cambios de UI AI-slop. Ejecuta el escáner. No lo omitas.
  6. Reproducción de reproducción, no de campo. Mantén fidelity mínima. Solo se reproducen los campos que la API exige absolutamente.
  7. La seguridad de las credenciales es estricta. Cada cliente lee solo su clave designada, o ninguna, y nunca envía una clave a un endpoint equivocado.
  8. Mantenlo legible de arriba a abajo. Sin saltos de definición, sin capas de shim, sin código por tipo en el streaming del cliente.
  9. El flujo de trabajo es vinculante. No reorganices etapas, no las omitas y nunca rellenes un "detente y pregunta" con tu propia suposición.

Este flujo de trabajo está diseñado para entornos de desarrollo de agentes de IA (Claude Code, Cursor, Codex) y aplica un control estricto sobre la calidad de la integración de modelos, la seguridad y la fidelidad de la implementación.

Skills relacionados