Gravar o manipulador de ação
Depois de criar uma ação na interface do usuário do Adobe LLM Apps, os metadados são armazenados na API LLM Apps — mas ainda não há código. Este guia mostra como escrever a função de manipulador que é executada quando uma plataforma do LLM (como ChatGPT ou Claude) invoca sua ação.
Para obter detalhes sobre o layout do projeto, desenvolvimento local e testes, consulte Desenvolvimento.
Contrato do desenvolvedor
Você escreve somente manipuladores. Todo o resto — nome da ação, descrição, esquema de entrada, anotações, visibilidade do widget, permissões, CSP — reside na interface do usuário do LLM Apps e é entregue ao tempo de execução automaticamente no momento da implantação. Nunca edita manualmente os metadados no repositório e nunca registra uma ferramenta no código.
actions/<name>/index.jsactions.json (instantâneo de metadados)Introdução
Seu repositório vinculado precisa da estrutura do projeto antes que você possa gravar manipuladores. Clonar a placa-padrão de aplicativos Adobe LLM para começar com um ponto de partida vazio.
Enviar o conteúdo para o repositório que você vinculou durante a criação do aplicativo (por exemplo, your-org/your-repo).
Quando o código estiver em vigor, execute:
npm install
Isso instala todas as dependências, incluindo @adobe/llm-apps-runtime — o tempo de execução que lida com a comunicação do protocolo MCP, a descoberta de ações e o roteamento de solicitações. Você não interage diretamente com o tempo de execução; ele é consumido por entry.js no momento da compilação.
.claude/skills/llm-apps-action-author/. Ele pode criar andaimes de novas ações, gerar arquivos de teste, validar formas de manipulador e orientá-lo pelo contrato do manipulador — tudo a partir do seu editor. Para usá-lo, peça a Claude para "adicionar uma ação chamada search-products" e ela seguirá automaticamente as convenções de projeto corretas.Contrato do manipulador
Um manipulador é um único arquivo em actions/<name>/index.js que exporta uma função assíncrona:
module.exports = async (args) => {
return {
content: [{ type: 'text', text: 'response for the LLM' }],
structuredContent: { /* data for the widget */ }
}
}
A função recebe os argumentos de entrada da ação como um objeto simples — esses são os parâmetros definidos na caixa de diálogo Criar ação. O servidor os valida em relação ao esquema de entrada antes que o manipulador seja chamado.
content (obrigatório)
Uma matriz de partes de conteúdo enviadas para os hosts LLM e somente texto. É o que a plataforma LLM lê para formular sua resposta.
content: [
{ type: 'text', text: 'Found 5 products matching category "bagged-coffee".' }
]
Sempre retornar content — é o fallback universal para qualquer host.
structuredContent
Um objeto do JavaScript simples enviado para o widget. Estes dados têm custo zero de token — são consumidos pelo bloco de widget EDS para renderizar uma interface do usuário avançada como um carrossel de produtos ou um mapa.
structuredContent: {
products: [
{ name: 'Product A', category: 'bagged-coffee', imageUrl: '...' },
{ name: 'Product B', category: 'bagged-coffee', imageUrl: '...' }
],
total: 2,
category: 'bagged-coffee'
}
A estrutura depende de você — ela deve corresponder ao que o bloco de widget EDS espera por meio de bridge.toolResult.
structuredContent deve ser um objeto simples, não uma matriz simples._meta (opcional)
Metadados adicionais enviados junto com o resultado. A tecla openai/widgetDescription informa à plataforma LLM como apresentar o widget:
_meta: {
'openai/widgetDescription': 'The widget displays a scrollable product carousel. '
+ 'Do NOT repeat the product list. Instead, highlight one or two recommendations.'
}
Exemplo: manipulador Pesquisar produtos
Este é um exemplo de manipulador search-products. Ele aceita um filtro category opcional e um query de texto livre, pesquisa um catálogo de produtos e retorna um resumo de texto para o LLM e dados estruturados para o carrossel de widgets.
// actions/search-products/index.js
const PRODUCTS = [
{
name: 'Product A',
description: 'A short description of Product A.',
category: 'bagged-coffee',
sub_category: 'dark-roast',
image_url: 'https://www.example.com/products/product-a/hero.jpg',
url: 'https://www.example.com/products/product-a',
productId: 'PROD-001',
rating: 4.7,
reviewCount: 58
},
// ... more products
];
const WIDGET_DESCRIPTION = 'The widget displays a scrollable product carousel '
+ 'with images, star ratings, and review counts. Do NOT repeat the product list.';
module.exports = async ({ category = '', query = '' } = {}) => {
let results = PRODUCTS;
if (category) {
const categoryLower = category.toLowerCase();
results = results.filter((p) =>
p.category.toLowerCase().includes(categoryLower)
|| p.sub_category.toLowerCase().includes(categoryLower)
);
}
if (query) {
const queryLower = query.toLowerCase();
results = results.filter((p) =>
p.name.toLowerCase().includes(queryLower)
|| p.description.toLowerCase().includes(queryLower)
);
}
const products = results.map((p) => ({
productId: p.productId,
name: p.name,
shortDescription: p.description,
category: p.category,
rating: p.rating,
reviewCount: p.reviewCount,
imageUrl: p.image_url,
productUrl: p.url,
}));
if (products.length === 0) {
return {
content: [{ type: 'text', text: `No products found for "${category}".` }],
structuredContent: { products: [], total: 0, category: null },
_meta: { 'openai/widgetDescription': WIDGET_DESCRIPTION }
};
}
return {
content: [
{ type: 'text', text: `Found ${products.length} product(s) in "${category}".` }
],
structuredContent: { products, total: products.length, category },
_meta: { 'openai/widgetDescription': WIDGET_DESCRIPTION }
};
};
O que acontece em tempo de execução:
- Um usuário pergunta à plataforma LLM “Mostre-me seus produtos para café.”
- A plataforma LLM corresponde à intenção de Pesquisar Produtos e extrai
category. - O servidor MCP chama seu manipulador com
{ category: 'bagged-coffee' }. - Seu manipulador filtra o catálogo e retorna
content(resumo de texto do LLM) +structuredContent(matriz de produto do widget). - A plataforma LLM mostra a resposta de texto e transmite os dados estruturados para o dispositivo EDS, que renderiza um carrossel de produtos.
E se o manipulador estiver ausente?
Se você definiu uma ação na interface do usuário, mas ainda não criou o arquivo do manipulador, a ação ainda será registrada no momento da implantação. As chamadas usarão um manipulador de stub padrão que retorna um conteúdo vazio até que você adicione o código real. Isso significa que é possível definir todas as ações na interface primeiro e implementá-las de forma incremental.