diff --git a/skills/pinme-api/SKILL.md b/skills/pinme-api/SKILL.md new file mode 100644 index 0000000..ff2b46c --- /dev/null +++ b/skills/pinme-api/SKILL.md @@ -0,0 +1,345 @@ +--- +name: pinme-api +description: 当用户的 PinMe 项目(Worker TypeScript)需要集成发送邮件(send_email)或调用大模型 API(chat/completions)时使用此技能。指导 AI 生成正确的 Worker TS 代码。 +--- + +# PinMe Worker API 集成 + +指导在 PinMe Worker(TypeScript)中调用 PinMe 平台的邮件发送和 LLM API。 + +## 环境变量 + +Worker 创建时自动注入以下环境变量,无需手动配置: + +```typescript +// backend/src/worker.ts +export interface Env { + DB: D1Database; + API_KEY: string; // 项目 API Key — 用于 send_email 和 chat/completions 认证 +} +``` + +> `API_KEY` 是 Worker 调用 PinMe 平台 API 的唯一凭证。 + +--- + +## API 1:发送邮件 + +**端点:** `POST https://pinme.dev/api/v4/send_email` +**认证:** `X-API-Key` header(使用 `env.API_KEY`) +**发件人:** 自动为 `{project_name}@pinme.dev` + +### 请求格式 + +```json +{ + "to": "user@example.com", + "subject": "Your verification code", + "html": "
Your code is 123456
" +} +``` + +| 字段 | 类型 | 必填 | 说明 | +|------|------|------|------| +| `to` | string | 是 | 收件人邮箱 | +| `subject` | string | 是 | 邮件主题 | +| `html` | string | 是 | HTML 正文 | + +### 响应格式 + +**成功 (200):** +```json +{ "code": 200, "msg": "ok", "data": { "ok": true } } +``` + +**错误:** + +| HTTP 状态码 | 含义 | data.error 示例 | +|-------------|------|-----------------| +| 401 | API Key 缺失或无效 | `"X-API-Key header is required"` / `"Invalid API key"` | +| 400 | 参数校验失败 | `"Invalid email address"` / `"Subject is required"` | +| 500 | 邮件服务异常 | `"Failed to send email"` | + +### Worker 示例代码 + +```typescript +async function sendEmail(env: Env, to: string, subject: string, html: string): Promise<{ ok: boolean; error?: string }> { + const resp = await fetch('https://pinme.dev/api/v4/send_email', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-API-Key': env.API_KEY, + }, + body: JSON.stringify({ to, subject, html }), + }); + + const result = await resp.json() as { code: number; msg: string; data?: { ok?: boolean; error?: string } }; + + if (resp.status !== 200 || result.code !== 200) { + return { ok: false, error: result.data?.error || result.msg || 'Unknown error' }; + } + return { ok: true }; +} + +// 在路由中使用 +async function handleSendVerification(request: Request, env: Env): PromiseYour code is ${code}
`); + + if (!result.ok) { + return json({ error: result.error }, 500); + } + return json({ ok: true }); +} +``` + +--- + +## API 2:LLM Chat Completions + +**端点:** `POST https://pinme.dev/api/v1/chat/completions?project_name={project_name}` +**认证:** `X-API-Key` header(使用 `env.API_KEY`) +**请求体:** OpenAI 兼容格式,原样透传给 LLM 服务 +**流式:** 支持 SSE(`stream: true`) + +### 请求格式 + +```json +{ + "model": "openai/gpt-4o-mini", + "messages": [ + { "role": "system", "content": "You are a helpful assistant." }, + { "role": "user", "content": "Hello!" } + ], + "stream": true +} +``` + +> `project_name` 从 Worker 的子域名解析,见下方示例。模型列表参考 [PinMe LLM 支持的模型](https://openrouter.ai/models)(OpenAI 兼容格式)。 + +### 响应格式 + +**非流式成功 (200):** +```json +{ + "id": "chatcmpl-...", + "choices": [{ "message": { "role": "assistant", "content": "Hello!" }, "finish_reason": "stop" }], + "usage": { "prompt_tokens": 10, "completion_tokens": 5, "total_tokens": 15 } +} +``` + +**流式成功 (200):** SSE 格式 +``` +data: {"choices":[{"delta":{"content":"Hello"}}]} +data: {"choices":[{"delta":{"content":" there"}}]} +data: [DONE] +``` + +**错误:** + +| HTTP 状态码 | 含义 | data.error 示例 | +|-------------|------|-----------------| +| 401 | API Key 缺失或无效 | `"X-API-Key header is required"` / `"Invalid API key or project name"` | +| 400 | project_name 缺失或 LLM 未配置 | `"project_name is required"` / `"LLM service not configured for this project"` | +| 413 | 请求体超过 1MB | `"Request body too large (max 1MB)"` | +| 502 | LLM 服务不可用 | `"LLM service unavailable"` | + +### Worker 示例代码 — 非流式 + +```typescript +// 获取 project_name:从 Worker 的子域名解析 +function getProjectName(request: Request): string { + const host = new URL(request.url).hostname; // e.g. "my-app-1a2b.pinme.pro" + return host.split('.')[0]; +} + +async function callLLM( + env: Env, + projectName: string, + messages: Array<{ role: string; content: string }>, + model = 'openai/gpt-4o-mini', +): Promise<{ content: string; error?: string }> { + const resp = await fetch( + `https://pinme.dev/api/v1/chat/completions?project_name=${projectName}`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-API-Key': env.API_KEY, + }, + body: JSON.stringify({ model, messages }), + }, + ); + + if (!resp.ok) { + const err = await resp.json() as { data?: { error?: string } }; + return { content: '', error: err.data?.error || `HTTP ${resp.status}` }; + } + + const data = await resp.json() as { choices: Array<{ message: { content: string } }> }; + return { content: data.choices[0]?.message?.content || '' }; +} + +// 在路由中使用 +async function handleChat(request: Request, env: Env): PromiseHi
' }, +); +if (emailResult.error) return json({ error: emailResult.error }, 500); + +// 调 LLM(非流式) +const llmResult = await callPinmeAPI<{ choices: Array<{ message: { content: string } }> }>( + `https://pinme.dev/api/v1/chat/completions?project_name=${projectName}`, env.API_KEY, + { model: 'openai/gpt-4o-mini', messages: [{ role: 'user', content: 'Hi' }] }, +); +if (llmResult.error) return json({ error: llmResult.error }, 502); +```