Search
Code3,144
import { readFile } from "https://esm.town/v/std/utils/index.ts";import { createAnthropic } from "npm:@ai-sdk/anthropic@1.2.12";import { convertToCoreMessages, } = await c.req.json(); const apiKey = Deno.env.get("ANTHROPIC_API_KEY"); if (await hasInsufficientCredits({ bearerToken })) { }); const anthropic = createAnthropic({ apiKey }); let tracedModel = anthropic(model); if (Deno.env.get("POSTHOG_PROJECT_API_KEY")) { const traceId = `townie_${rowid}_${Date.now()}`; // Wrap the Anthropic model with PostHog tracing tracedModel = withTracing(anthropic(model), phClient, { posthogDistinctId: user.id, posthogTraceId: traceId, // @ts-ignore lastMessage.content.at(-1).providerOptions = { anthropic: { cacheControl: { type: "ephemeral" } }, }; } output_tokens: result.usage.completionTokens, cache_read_tokens: result.providerMetadata.anthropic.cacheReadInputTokens, cache_write_tokens: result.providerMetadata.anthropic.cacheCreationInputTokens, }); output_tokens: result.usage.completionTokens, cache_read_tokens: result.providerMetadata.anthropic.cacheReadInputTokens, cache_write_tokens: result.providerMetadata.anthropic.cacheCreationInputTokens, }); },Townie is fully open-source and itself runs on Val Town. Pull requests welcome!To get Townie running in your Val Town account, click the **Remix** button and then add your ANTHROPIC_API_KEY. You can leave all the other environment variables blank.Authentication in Townie is handled via Val Town Oauth. However, we have not yet opened up our OAuth to anyone else, which currently makes it very awkward to use your own Townie. Here is a temporary workaround: </ul> <p> The application proxies requests to the Anthropic API and Val Town API, allowing Claude to view and edit your project files directly. </p> userId, type: "url", content: "https://www.anthropic.com/news/claude-3-5-sonnet", title: "New Claude 3.5 Sonnet Released", metadata: JSON.stringify({ url: "https://www.anthropic.com/news/claude-3-5-sonnet", source: "TechCrunch" }), createdAt: new Date(now - 4 * 60 * 60 * 1000).toISOString(), // 4 hours ago }, userId, type: 'url', content: 'https://www.anthropic.com/news/claude-3-5-sonnet', title: 'New Claude 3.5 Sonnet Released', metadata: JSON.stringify({ url: 'https://www.anthropic.com/news/claude-3-5-sonnet', source: 'TechCrunch' }), createdAt: new Date(Date.now() - 4 * 60 * 60 * 1000).toISOString(), // 4 hours ago },import Anthropic from "npm:@anthropic-ai/sdk@0.32.1";export default async function handler(req: Request): Promise<Response> { } const anthropic = new Anthropic({ apiKey: Deno.env.get("ANTHROPIC_API_KEY"), }); } console.log("Calling Anthropic API..."); const response = await anthropic.messages.create({ model: "claude-sonnet-4-20250514", max_tokens: 1000,## Features- **Multi-Model Comparison**: Compare responses from OpenAI, Anthropic, and Google models side-by-side- **Real-Time Streaming**: Watch responses stream in as they're generated- **Configurable Parameters**: Adjust temperature and max tokens per model- GPT-5 Mini### Anthropic- Claude 4.5 Opus- Claude 4.5 Sonnet|-----|-------------|| `OPENAI_API_KEY` | Your OpenAI API key || `ANTHROPIC_API_KEY` | Your Anthropic API key || `GOOGLE_GENERATIVE_AI_API_KEY` | Your Google AI API key | defaultColumns: [ { provider: "openai", model: "gpt-5.2" }, { provider: "anthropic", model: "claude-opus-4-5-20251101" }, { provider: "google", model: "gemini-3-pro-preview" }, ],import { streamSSE } from "npm:hono@4.4.8/streaming";import { createOpenAI } from "npm:@ai-sdk/openai@latest";import { createAnthropic } from "npm:@ai-sdk/anthropic@latest";import { createGoogleGenerativeAI } from "npm:@ai-sdk/google@latest";import { streamText } from "npm:ai@6.0.3"; defaultColumns: [ { provider: "openai", model: "gpt-5.2" }, { provider: "anthropic", model: "claude-opus-4-5-20251101" }, { provider: "google", model: "gemini-3-pro-preview" }, ],const providers = { openai: () => createOpenAI({ apiKey: Deno.env.get("OPENAI_API_KEY") || "" }), anthropic: () => createAnthropic({ apiKey: Deno.env.get("ANTHROPIC_API_KEY") || "" }), google: () => createGoogleGenerativeAI({ { id: "gpt-5-mini", name: "GPT-5 Mini", icon: "π«" }, ], anthropic: [ { id: "claude-opus-4-5-20251101", name: "Claude 4.5 Opus", icon: "πΌ" }, { id: "claude-sonnet-4-5-20250929", name: "Claude 4.5 Sonnet", icon: "π" }, `<svg viewBox="0 0 24 24" fill="currentColor" class="w-5 h-5"><path d="M22.282 9.821a5.985 5.985 0 0 0-.516-4.91 6.046 6.046 0 0 0-6.51-2.9A6.065 6.065 0 0 0 4.981 4.18a5.985 5.985 0 0 0-3.998 2.9 6.046 6.046 0 0 0 .743 7.097 5.98 5.98 0 0 0 .51 4.911 6.051 6.051 0 0 0 6.515 2.9A5.985 5.985 0 0 0 13.26 24a6.056 6.056 0 0 0 5.772-4.206 5.99 5.99 0 0 0 3.997-2.9 6.056 6.056 0 0 0-.747-7.073zM13.26 22.43a4.476 4.476 0 0 1-2.876-1.04l.141-.081 4.779-2.758a.795.795 0 0 0 .392-.681v-6.737l2.02 1.168a.071.071 0 0 1 .038.052v5.583a4.504 4.504 0 0 1-4.494 4.494zM3.6 18.304a4.47 4.47 0 0 1-.535-3.014l.142.085 4.783 2.759a.771.771 0 0 0 .78 0l5.843-3.369v2.332a.08.08 0 0 1-.033.062L9.74 19.95a4.5 4.5 0 0 1-6.14-1.646zM2.34 7.896a4.485 4.485 0 0 1 2.366-1.973V11.6a.766.766 0 0 0 .388.676l5.815 3.355-2.02 1.168a.076.076 0 0 1-.071 0l-4.83-2.786A4.504 4.504 0 0 1 2.34 7.872zm16.597 3.855l-5.833-3.387L15.119 7.2a.076.076 0 0 1 .071 0l4.83 2.791a4.494 4.494 0 0 1-.676 8.105v-5.678a.79.79 0 0 0-.407-.667zm2.01-3.023l-.141-.085-4.774-2.782a.776.776 0 0 0-.785 0L9.409 9.23V6.897a.066.066 0 0 1 .028-.061l4.83-2.787a4.5 4.5 0 0 1 6.68 4.66zm-12.64 4.135l-2.02-1.164a.08.08 0 0 1-.038-.057V6.075a4.5 4.5 0 0 1 7.375-3.453l-.142.08L8.704 5.46a.795.795 0 0 0-.393.681zm1.097-2.365l2.602-1.5 2.607 1.5v2.999l-2.597 1.5-2.607-1.5z"/></svg>`, }, anthropic: { name: "Anthropic", color: "#d4a574", gradient: "from-amber-400 via-orange-500 to-rose-500", } .accent-line.openai { background: linear-gradient(90deg, transparent, #10a37f, transparent); } .accent-line.anthropic { background: linear-gradient(90deg, transparent, #f97316, transparent); } .accent-line.google { background: linear-gradient(90deg, transparent, #6366f1, transparent); } > <option value="openai" \${defaultProvider === 'openai' ? 'selected' : ''}>OpenAI</option> <option value="anthropic" \${defaultProvider === 'anthropic' ? 'selected' : ''}>Anthropic</option> <option value="google" \${defaultProvider === 'google' ? 'selected' : ''}>Google</option> </select> // Update card class const columnEl = document.getElementById(\`column-\${id}\`); columnEl.classList.remove('openai', 'anthropic', 'google'); columnEl.classList.add(provider); // Update accent line const accentLine = columnEl.querySelector('.accent-line'); accentLine.classList.remove('openai', 'anthropic', 'google'); accentLine.classList.add(provider); function addColumn() { const providers = ['openai', 'anthropic', 'google']; const existingProviders = Array.from(columns.values()).map(c => c.provider); let newProvider = providers.find(p => !existingProviders.includes(p)) || providers[columns.size % 3]; aiModel = providers.openai()(model); break; case "anthropic": aiModel = providers.anthropic()(model); break; case "google": const body = await req.json(); const response = await fetch("https://api.anthropic.com/v1/messages", { method: "POST", headers: { "x-api-key": "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", "anthropic-version": "2023-06-01", }, body: JSON.stringify(body),## π€ AI-Generated ProjectThis entire project was created by **Claude (Anthropic)** using the **Val Town MCP (Model Context Protocol)** integration.### The Prompt