Prism-Shadow/model-message-stream-protocol

Fluxo de trabalho fixo para desenvolver o próprio MMSP — adicionar ou atualizar suporte a modelos e alterar suas páginas. Use quando solicitado a suportar um novo modelo ou versão de protocolo neste repositório, sincronizar llmsdk_docs, implementar um cliente provedor, ou alterar o playground, o tracer ou o site. Abrange sincronização de documentação, captura de API ao vivo, implementação pareada Python/TypeScript, testes e2e com escopo de modelo e as regras de UI para as três páginas.

O que é model-message-stream-protocol?

Esta skill define um fluxo de trabalho de desenvolvimento completo e fixo para o projeto MMSP (Model Message Stream Protocol). Ela é projetada para orientar um desenvolvedor ou agente de IA em cada etapa necessária para adicionar ou atualizar o suporte a um modelo de linguagem grande ao protocolo, abrangendo desde a sincronização da documentação oficial do provedor até a implementação de clientes Python e TypeScript, testes de integração e atualizações de interface de usuário. A skill suporta os principais provedores e suas APIs (como Anthropic, OpenAI, Google Gemini) e garante que o novo código siga estritamente a arquitetura existente, utilizando clientes oficiais por fornecedor ou clientes compatíveis por protocolo de rede. Ela também prescreve a captura de exemplos reais de API como referência primária de implementação, superando a documentação estática, e inclui estágios obrigatórios de revisão onde o desenvolvedor deve parar e consultar o usuário antes de prosseguir.

Funciona com✓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 Produtividade e Colaboração skills: steipete/gemini, steipete/gh-issues, steipete/skill-creator · View all 6 →

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

Fluxo de Trabalho de Desenvolvimento do MMSP

Adicionar ou atualizar o suporte a um modelo segue os estágios abaixo, em ordem. Onde um estágio disser pare e pergunte, pare e pergunte ao usuário; não continue até que o problema seja resolvido e nunca preencha a lacuna você mesmo.

Mapa de diretórios

llmsdk_docs/<versão_do_modelo>/      Snapshot da documentação oficial, uma pasta por geração de modelo (README.md + docs/)
api_captures/<protocolo>/          Capturas de API brutas ignoradas pelo Git: payloads de requisição + eventos de stream
src_py/mmsp/<tipo_cliente>/        Cliente Python, uma pasta por tipo de cliente: <fornecedor>_official/ ou <protocolo>/
src_py/mmsp/auto_client.py        Cria o cliente que um tipo_cliente nomeia; a família de um id de modelo nomeia seu cliente oficial
src_ts/src/<tipo_cliente>/         Cliente TypeScript, espelha a pasta Python
src_ts/src/autoClient.ts          Roteamento TypeScript, espelha auto_client.py
src_py/tests/test_client.py       Testes e2e parametrizados (protegidos por variável de ambiente AVAILABLE_MODELS)
src_ts/tests/client.test.ts       O mesmo para TypeScript
changelog/unreleased/              Onde as alterações não lançadas vão; renomeado para a versão no lançamento
changelog/<versão>/                Resumo do lançamento (README.md) mais um arquivo de detalhe por entrada
changelog/README.md               O formato da entrada: bloco de metadados, regras do corpo, pareamento bilíngue
CHANGELOG.md                      Uma linha breve por lançamento ligando a changelog/
src_py/mmsp/integration/          O playground, as páginas do tracer e o servidor MMSP, espelhado em src_ts/src/integration/
site/                             O site Astro em mmsp.penguin.ooo
.agents/skills/mmsp-dev/kill-ai-slop/   Guia de AI-slop e scanner embutidos para o trabalho de UI abaixo
<nome>.zh.md                      Contraparte em Chinês, obrigatório para todo arquivo em changelog/

Estágio 1 — Sincronizar documentação oficial em llmsdk_docs/

  • As fontes devem ser o site de documentação oficial do fornecedor do modelo (ex.: docs.anthropic.com, platform.openai.com, ai.google.dev). Nunca use mirrors de terceiros, postagens de blog ou memória de modelo.
  • Salve o snapshot em llmsdk_docs/<versão_do_modelo>/ seguindo as convenções de pasta existentes e liste a pasta em llmsdk_docs/README.md. Executar este fluxo de trabalho é a solicitação explícita que a regra do repositório contra editar llmsdk_docs/ pede.
  • Quando a documentação buscada diferir de um snapshot existente, a nova documentação oficial vence: atualize os arquivos antigos no lugar.
  • O snapshot deve ser completo o suficiente para implementar a partir dele: esquemas de requisição/resposta, sequência de eventos de stream, saída de pensamento, chamada de ferramenta, campos de uso e respostas de erro.
  • Pare e pergunte se a URL oficial for incerta ou se uma página não puder ser buscada. O usuário pode colar o conteúdo manualmente.

Estágio 2 — Capturar uma troca de API ao vivo em api_captures/

  • Portão: a variável de ambiente da chave de API do provedor deve estar definida e utilizável. Use as mesmas variáveis de ambiente e URLs base que src_py/tests/test_client.py (proteção AVAILABLE_MODELS e _create_client). Pare e pergunte ao usuário para fornecer a chave se ela estiver faltando; o fluxo de trabalho não deve continuar sem ela.
  • Usando o SDK oficial do provedor, ou HTTP puro exatamente como documentado, execute uma requisição de chamada de ferramenta em stream com pensamento habilitado, então envie o resultado da ferramenta de volta para que a captura também mostre como as rodadas do assistente são reenviadas.
  • Salve a troca completa sem modificações em api_captures/<protocolo>/ (ignorada pelo git), ex.: round1.request.json mais round1.stream.jsonl com cada evento de stream bruto em ordem. Nunca salve credenciais.
  • Quando algo na troca não serializar para JSON (payloads binários, objetos SDK contendo valores não JSON), salve sua forma str() e analise a partir disso. Mantenha o contêiner analisável: envolva o texto em um objeto JSON para que o .jsonl permaneça um valor JSON por linha ({"unserializable_str": "<str(evento)>"}, mantendo o evento em ordem de stream), ou coloque toda a troca em um arquivo .txt irmão quando nada sobre ela serializar. Uma captura str() ainda é o registro autoritativo do que a API retornou. Nunca descarte o evento, edite-o manualmente para JSON válido ou recorra à documentação porque a serialização falhou.
  • Pare e pergunte em qualquer erro de API (chave inválida, cota insuficiente, limite de taxa). Não simule a resposta ou continue apenas com a documentação.
  • A captura é a referência primária de implementação e supera a documentação: onde discordarem, implemente o que a API realmente retornou.
  • Em seguida, investigue a repetição, porque a captura só mostra o que a API envia, não o que ela precisa de volta. Reenvie a rodada do assistente repetidamente, cada vez com um campo candidato removido — o id do item de raciocínio, seu conteúdo, todo o item de raciocínio, cada campo que você foi tentado a manter — e registre quais remoções a API ainda aceita com o comportamento do modelo intacto. Esse resultado, não a forma da resposta, decide o que o Estágio 3 armazena. Salve os resultados da investigação ao lado da captura.

Estágio 3 — Implementar os clientes Python e TypeScript

  • Uma pasta por tipo de cliente, nomeada após ele: um cliente oficial por fornecedor (openai_official/, anthropic_official/, google_official/, zai_official/, moonshot_official/, deepseek_official/, minimax_official/) fala a própria API do fornecedor, conhece todas as gerações dos modelos do fornecedor e lê a chave do fornecedor do ambiente; um cliente compatível por protocolo de rede (openai_responses/, openai_chat/, openai_chat_vllm_adapter/, openai_embedding/, ant_messages/, google_genai/, mmsp/) fala esse protocolo para qualquer endpoint. A classe é nomeada após a pasta (OpenAIOfficialClient, OpenaiChatClient), e o tipo de cliente é o nome da pasta com hífens (openai-official, openai-chat).
    • Uma nova geração de modelos de um fornecedor vai para o cliente oficial do fornecedor. Diferencie o novo protocolo (captura + documentação) do que o cliente envia hoje; onde uma geração difere, mesmo que por um único nome de chave, diferencie as gerações dentro do cliente por versão explícita ("4-6" in self._model, "gpt-6" in self._model), nunca por uma substring simples como "claude" in model, e mantenha as gerações mais antigas funcionando. Nunca descarte uma geração a menos que o usuário instrua explicitamente.
    • Um novo fornecedor recebe uma nova pasta de cliente oficial, um novo tipo de cliente <fornecedor>-official em auto_client.py / autoClient.ts e uma nova linha na tabela de famílias de modelos lá: a família é o prefixo com o qual todo id dos modelos do fornecedor começa (glm-, kimi-), correspondido sem distinção de maiúsculas/minúsculas.
    • Um novo protocolo de rede que vários fornecedores atendem recebe uma pasta de cliente compatível e tipo de cliente compatível; um cliente compatível não carrega peculiaridades de nenhum fornecedor.
  • auto_client.py / autoClient.ts criam o cliente que um client_type nomeia, a partir da única tabela de tipos de cliente. Sem um client_type, a família com a qual um id de modelo começa nomeia seu cliente oficial; um id de uma família desconhecida levanta e pede por uma. O roteamento não tem outra regra: sem correspondência de substring em ids de modelo, sem correspondência de versão fora dos clientes oficiais.
  • Uma credencial vai apenas onde foi fornecida. Este é o modelo de segurança, e todo construtor de cliente o segue. Uma chave passada como api_key é usada como está. Um cliente que lê a chave genérica do ambiente (OPENAI_API_KEY para openai_official e os clientes compatíveis com o protocolo OpenAI, ANTHROPIC_API_KEY para anthropic_official e ant_messages, GEMINI_API_KEY para google_genai) a lê apenas quando o endpoint também vem do ambiente (OPENAI_BASE_URL / ANTHROPIC_BASE_URL / GEMINI_BASE_URL, ou o padrão do próprio provedor): dado um base_url e nenhum api_key, ele levanta na construção em vez de enviar essa chave para outro endpoint. Isso é resolve_credentials / resolveCredentials em utils; chame-o, não o reimplemente. Qualquer outro cliente oficial (deepseek_official, zai_official, moonshot_official, minimax_official, google_official) lê sua própria variável (DEEPSEEK_API_KEY, ZAI_API_KEY, MOONSHOT_API_KEY, MINIMAX_API_KEY, GEMINI_API_KEY) qualquer que seja a URL base fornecida, e nunca a genérica; sem uma delas, levanta <VAR> is required for <Client>.. Entregue toda credencial ao SDK do fornecedor explicitamente, incluindo "nenhuma" (authToken: null em TypeScript, um auth_token limpo em Python), porque os SDKs preenchem o que não recebem de suas próprias variáveis de ambiente. Os testes offline em tests/env-credentials.test.ts / tests/test_env_credentials.py constroem cada cliente sob um ambiente controlado e leem a credencial que a instância do SDK mantém; adicione um novo cliente a eles.
  • A repetição deve ser mínima, não exaustiva. Uma rodada do assistente repetida tem que ser aceita pela API com o comportamento do modelo intacto; ela não tem que reproduzir o campo do item de rede campo por campo. fidelity é um canal de exceção, não um espelho do payload: um campo ganha um lugar lá apenas quando a investigação do Estágio 2 mostra que a requisição falha ou o modelo degrada sem ele. Ids gerados pelo provedor que a API regenera ou ignora — um id de item de raciocínio, um id de item de saída — ficam de fora. Campos que a API exige vão: phase do GPT-5.5, signature do pensamento do Claude, o nome exato do campo de raciocínio que um upstream estrito requer.
  • Nunca copie para fidelity o que um campo universal já carrega — o texto de pensamento, o nome ou argumentos analisados de uma chamada de ferramenta. Reconstrua o item de rede a partir dos campos universais.
  • tool_call_id não é opcional: sempre capture o id de chamada do provedor e repita-o. Onde o formato de rede carrega tanto um id de item quanto um id de chamada, o id de chamada é aquele que correlaciona um resultado à sua chamada.
  • Siga o cliente de referência; não redesenhe. openai_official/ é a forma para protocolos estilo Responses, openai_chat/ para Chat Completions: mesma ordem de métodos, mesmo fluxo de controle, mesmos nomes. Um novo cliente deve ser lido como uma diferença contra sua referência, porque é assim que será revisado.
  • Mantenha legível de cima a baixo. Um leitor deve seguir uma requisição ou resposta diretamente através do arquivo sem pular entre definições, então a inlining é o padrão: leia os atributos do SDK diretamente (model_output.delta, model_output.item.call_id) e mantenha acesso a campos, aritmética de uso e texto de erro onde são usados. Extraia um auxiliar apenas para um bloco genuinamente grande e autocontido — o tipo que enterraria o fluxo principal se fosse inlinado, como buscar e decodificar uma imagem — nunca para algumas linhas. Espelhe os métodos privados do cliente de referência (_convert_thinking_level_*, _convert_tool_choice) em vez de inventar uma camada ao lado deles; uma adaptação que aceita tanto dicts quanto objetos SDK nunca é uma delas, porque os eventos são tipados com os próprios tipos do SDK.
  • Os clientes geram apenas deltas; a classe base fecha os itens. transform_model_output_to_uni_event / transformModelOutputToUniEvent transforma um evento de rede em um UniEvent cujos content_items são os itens .delta que aquele evento carrega, em ordem de rede, e nunca um item .done. _streaming_response_internal / _streamingResponseInternal constrói a requisição, abre o stream e gera um evento por evento de rede: um loop simples, sem contabilidade de itens, sem acumulador, sem análise de argumentos, sem mesclagem de uso e sem síntese de parada. usage_metadata / finish_reason andam em eventos stop onde quer que o wire os reporte — message_start e message_delta do Anthropic, os chunks de finalização e uso do Chat Completions, response.completed — mesclados campo por campo pela base; um evento stop de um cliente nunca termina o stream público. Um evento de rede sem nada universal é o evento delta vazio: sem itens, uso e motivo de finalização nulos. A classe base entrega cada delta para StreamItems (stream_items.py / streamItems.ts), que o envia conforme chega e fecha cada item com seu item .done quando o próximo item começa ou o stream do cliente termina; a base então emite o único evento stop ou levanta EmptyResponseError. Eventos que quebram o protocolo levantam StreamProtocolError em todos os modos, e os testes e2e compartilhados verificam o stream de cada cliente com assert_stream_grammar / assertStreamGrammar.
  • Os deltas de um item são contíguos e compartilham um fidelity.item_id. A saída do modelo é serial, então um item transmite por vez e os eventos de fim de item do provedor (content_block_stop, response.output_item.done, step.stop) são listados como no-ops: um gateway que fecha seus itens tarde ou fora de ordem não muda nada. Passe o próprio id do provedor, como string, como fidelity.item_id: o index do bloco do Anthropic, o item_id do evento Responses ou item.id ou item.call_id (None / undefined onde um gateway não envia nenhum), o index do passo das Interações, o campo de rede do Chat Completions (reasoning_content, reasoning, content, tool_calls), o tipo de parte do generateContent (function_call, thought, inline_thinking, inline_data, text); vetores de embedding não carregam nenhum. StreamItems o remove, então ele nunca alcança consumidores, rastros ou históricos.
  • StreamItems decide onde um item termina; os clientes nunca fazem isso. Um delta pertence ao próximo item quando ele carrega outro item_id, é de outro tipo (um passo de pensamento das Interações indo texto, imagem, texto são três itens sob um índice) ou começa um item por si só: um tool_call.delta carregando um nome (todo provedor o envia uma vez, no primeiro delta da chamada, então chamadas do Chat Completions não precisam de ids próprios), uma imagem (inline_thinking ou inline_data com um tipo MIME image/*, enquanto o áudio transmite em chunks de um item), um vetor de embedding. Caso contrário, ele continua o item que está transmitindo agora: um delta sem id faz isso, e os argumentos de uma chamada também, qualquer que seja o id que um gateway coloque neles. Fidelidade enviada sozinha sob o id do item é daquele item, qualquer que seja o tipo que a carregue (um thought_signature das Interações após um pensamento de imagem). A tabela por tipo naquele arquivo — o campo crescente, os campos de cabeçalho do primeiro delta, como os chunks se juntam — é o único código por tipo no stream: o item feito é o primeiro delta do item com o campo crescente substituído pela junção de

Habilidades Relacionadas