Pinme llm
/SKILLUse this skill when a PinMe project (Worker TypeScript) needs to call OpenRouter-backed LLM APIs, including models, chat/completions, streaming, or OpenRouter web search.
--- name: pinme-llm description: Use this skills when a PinMe project (Worker TypeScript) needs to call OpenRouter-backed LLM APIs, including models, chat/completions, streaming, or OpenRouter web search. Guides AI to generate correct Worker TS code. --- # PinMe Worker OpenRouter API Integration Guides how to call PinMe platform's OpenRouter proxy APIs in a PinMe Worker (TypeScript). Workers use the PinMe project API key; they never hold the real OpenRouter API key. ## Environment Variables The following environment variables are automatically injected when the Worker is created : no manual configuration needed: ``typescript // backend/src/worker.ts export interface Env { DB: D1Database; API_KEY: string; // Project API Key from create_worker PROJECT_NAME: string; // Actual project_name from create_worker; must match API_KEY BASE_URL?: string; // Optional override for PinMe API base URL, defaults to https://pinme.cloud } ` > API_KEY authenticates the Worker to PinMe. PROJECT_NAME is required for chat/completions and must belong to the same project as API_KEY. When BASE_URL is not set, use https://pinme.cloud. --- ## Models API **Endpoint:** GET {BASE_URL}/api/v1/models **Authentication:** X-API-Key header (using env.API_KEY) **Request Body:** none Use this when the Worker needs to list available OpenRouter models. The response body, status, and headers are passed through from OpenRouter /models. `typescript async function listModels(env: Env): Promise<unknown> { const baseUrl = env.BASE_URL ?? 'https://pinme.cloud'; const resp = await fetch(${baseUrl}/api/v1/models, { headers: { 'X-API-Key': env.API_KEY }, }); if (!resp.ok) { throw new Error(await extractPinmeOpenRouterError(resp)); } return await resp.json(); } ` --- ## Chat Completions API **Endpoint:** POST {BASEURL}/api/v1/chat/completions?projectname={project_name} **Authentication:** X-API-Key header (using env.API_KEY) **Request Body:** OpenRouter chat/completions format, passed through as-is after a 1MB size check **Streaming:** Supports SSE (stream: true) **Web Search:** Supports OpenRouter openrouter:web_search server tool via the tools array ### Request Format `json { "model": "openai/gpt-4o-mini", "messages": [ { "role": "system", "content": "You are a helpful assistant." }, { "role": "user", "content": "Hello!" } ], "stream": true } ` > Use env.PROJECT_NAME from create_worker; always URL-encode it in the query string. For available models, call GET /api/v1/models or refer to OpenRouter model IDs. ### OpenRouter Web Search PinMe does not provide a raw search endpoint. To search the web, pass OpenRouter's openrouter:web_search server tool to chat/completions; the model decides whether and when to search. Always set max_results and maxtotalresults to keep search volume and cost bounded. `typescript async function searchWithLLM(env: Env, query: string): Promise<string> { const baseUrl = env.BASE_URL ?? 'https://pinme.cloud'; const resp = await fetch( ${baseUrl}/api/v1/chat/completions?projectname=${encodeURIComponent(env.PROJECTNAME)}, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-API-Key': env.API_KEY, }, body: JSON.stringify({ model: 'openai/gpt-5.2', messages: [{ role: 'user', content: query }], tools: [ { type: 'openrouter:web_search', parameters: { engine: 'auto', max_results: 5, max_total_results: 10, }, }, ], }), }, ); if (!resp.ok) { throw new Error(await extractPinmeOpenRouterError(resp)); } const data = await resp.json() as { choices: Array<{ message?: { content?: string } }> }; return data.choices[0]?.message?.content ?? ''; } ` ### Response Format Successful requests return OpenRouter's raw response body. **Non-streaming Success (200):** `json { "id": "chatcmpl-...", "choices": [{ "message": { "role": "assistant", "content": "Hello!" }, "finish_reason": "stop" }], "usage": { "prompt_tokens": 10, "completion_tokens": 5, "total_tokens": 15 } } ` **Streaming Success (200):** SSE format ` data: {"choices":[{"delta":{"content":"Hello"}}]} data: {"choices":[{"delta":{"content":" there"}}]} data: [DONE] ` **Errors:** | HTTP Status | Meaning | data.error Example | |-------------|---------|-------------------| | 401 | API Key missing, invalid, or mismatched with project_name | "X-API-Key header is required" / "Invalid API key" / "Invalid API key or project name" | | 400 | project_name missing or OpenRouter key not configured | "project_name is required" / "LLM service not configured for this project" | | 403 | LLM balance insufficient or disabled | "Insufficient balance, please recharge to continue using LLM service"` | | 413 | Request body exce