-
Notifications
You must be signed in to change notification settings - Fork 0
Adds deco llm binding #107
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| .dev.vars |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| { | ||
| "scopeName": "deco", | ||
| "name": "llm", | ||
| "connection": { | ||
| "type": "HTTP", | ||
| "url": "https://sites-deco-llm.decocache.com/mcp" | ||
| }, | ||
| "description": "Deco LLM App Connection for LLM uses.", | ||
| "icon": "https://assets.decocache.com/mcp/6e1418f7-c962-406b-aceb-137197902709/ai-gateway.png", | ||
| "unlisted": false | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| { | ||
| "name": "deco-llm", | ||
| "version": "1.0.0", | ||
| "description": "Deco LLM App Connection for LLM uses.", | ||
| "private": true, | ||
| "type": "module", | ||
| "scripts": { | ||
| "dev": "bun run --hot server/main.ts", | ||
| "build:server": "NODE_ENV=production bun build server/main.ts --target=bun --outfile=dist/server/main.js", | ||
| "build": "bun run build:server", | ||
| "publish": "cat app.json | deco registry publish -w /shared/deco -y", | ||
| "check": "tsc --noEmit" | ||
| }, | ||
| "dependencies": { | ||
| "@ai-sdk/provider": "^3.0.2", | ||
| "@ai-sdk/provider-utils": "^4.0.4", | ||
| "@decocms/bindings": "^1.0.7", | ||
| "@decocms/runtime": "^1.1.3", | ||
| "@openrouter/ai-sdk-provider": "^1.5.4", | ||
| "@openrouter/sdk": "^0.3.11", | ||
| "ai": "^6.0.3", | ||
| "zod": "^4.0.0" | ||
| }, | ||
| "devDependencies": { | ||
| "@cloudflare/vite-plugin": "^1.13.4", | ||
| "@cloudflare/workers-types": "^4.20251014.0", | ||
| "@decocms/mcps-shared": "1.0.0", | ||
| "@decocms/openrouter": "1.0.0", | ||
| "@mastra/core": "^0.24.0", | ||
| "@modelcontextprotocol/sdk": "1.25.1", | ||
| "@types/mime-db": "^1.43.6", | ||
| "deco-cli": "^0.28.0", | ||
| "typescript": "^5.7.2", | ||
| "vite": "7.2.0", | ||
| "wrangler": "^4.28.0" | ||
| }, | ||
| "engines": { | ||
| "node": ">=22.0.0" | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,104 @@ | ||
| /** | ||
| * OpenRouter MCP Server | ||
| * | ||
| * This MCP provides tools for interacting with OpenRouter's API, | ||
| * including model discovery, comparison, and AI chat completions. | ||
| * | ||
| * OpenRouter offers a unified API for accessing hundreds of AI models | ||
| * with built-in fallback mechanisms, cost optimization, and provider routing. | ||
| */ | ||
| import type { Registry } from "@decocms/mcps-shared/registry"; | ||
| import { serve } from "@decocms/mcps-shared/serve"; | ||
| import { tools } from "@decocms/openrouter/tools"; | ||
| import { BindingOf, type DefaultEnv, withRuntime } from "@decocms/runtime"; | ||
| import { z } from "zod"; | ||
| import { calculatePreAuthAmount, toMicrodollars } from "./usage"; | ||
|
|
||
| export const StateSchema = z.object({ | ||
| WALLET: BindingOf("@deco/wallet"), | ||
| }); | ||
|
|
||
| /** | ||
| * Environment type combining Deco bindings and Cloudflare Workers context | ||
| */ | ||
| export type Env = DefaultEnv<typeof StateSchema, Registry>; | ||
|
|
||
| interface OpenRouterUsageReport { | ||
| providerMetadata: { | ||
| openrouter: { | ||
| usage: { | ||
| cost: number; | ||
| }; | ||
| }; | ||
| }; | ||
| } | ||
| const isOpenRouterUsageReport = ( | ||
| usage: unknown | OpenRouterUsageReport, | ||
| ): usage is OpenRouterUsageReport => { | ||
| return ( | ||
| typeof usage === "object" && | ||
| usage !== null && | ||
| "providerMetadata" in usage && | ||
| typeof usage.providerMetadata === "object" && | ||
| usage.providerMetadata !== null && | ||
| "openrouter" in usage.providerMetadata && | ||
| typeof usage.providerMetadata.openrouter === "object" && | ||
| usage.providerMetadata.openrouter !== null && | ||
| "usage" in usage.providerMetadata.openrouter && | ||
| typeof usage.providerMetadata.openrouter.usage === "object" && | ||
| usage.providerMetadata.openrouter.usage !== null && | ||
| "cost" in usage.providerMetadata.openrouter.usage | ||
| ); | ||
| }; | ||
|
|
||
| const runtime = withRuntime< | ||
| DefaultEnv<typeof StateSchema, Registry>, | ||
| typeof StateSchema, | ||
| Registry | ||
| >({ | ||
| tools: (env) => { | ||
| return tools(env, { | ||
| start: async (modelInfo, params) => { | ||
| const amount = calculatePreAuthAmount(modelInfo, params); | ||
|
|
||
| const { id } = | ||
| await env.MESH_REQUEST_CONTEXT.state.WALLET.PRE_AUTHORIZE_AMOUNT({ | ||
| amount, | ||
| metadata: { | ||
| modelId: modelInfo.id, | ||
| params: params, | ||
| }, | ||
| }); | ||
| return { | ||
| end: async (usage) => { | ||
| if (!isOpenRouterUsageReport(usage)) { | ||
| throw new Error("Usage cost not found"); | ||
| } | ||
| const vendorId = process.env.WALLET_VENDOR_ID ?? "deco"; | ||
| await env.MESH_REQUEST_CONTEXT.state.WALLET.COMMIT_PRE_AUTHORIZED_AMOUNT( | ||
| { | ||
| identifier: id, | ||
| contractId: | ||
| env.MESH_REQUEST_CONTEXT.connectionId ?? | ||
| env.MESH_REQUEST_CONTEXT.state.WALLET.value, | ||
| vendorId, | ||
| amount: toMicrodollars( | ||
| usage.providerMetadata.openrouter.usage.cost, | ||
| ), | ||
| }, | ||
| ); | ||
| }, | ||
| }; | ||
| }, | ||
| }); | ||
| }, | ||
| configuration: { | ||
| state: StateSchema, | ||
| scopes: [ | ||
| "WALLET::PRE_AUTHORIZE_AMOUNT", | ||
| "WALLET::COMMIT_PRE_AUTHORIZED_AMOUNT", | ||
| ], | ||
| }, | ||
| }); | ||
|
|
||
| serve(runtime.fetch); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| import type { LanguageModelInputSchema } from "@decocms/bindings/llm"; | ||
| import type { ModelInfo } from "@decocms/openrouter/types"; | ||
| import type { z } from "zod"; | ||
|
|
||
| export interface GenerationContext { | ||
| model: ModelInfo; | ||
| } | ||
|
|
||
| const DEFAULT_MAX_COMPLETION_TOKENS = 1000000; | ||
|
|
||
| export const toMicrodollars = (amount: number): string => { | ||
| return Math.round(amount * 1_000_000).toString(); | ||
| }; | ||
| /** | ||
| * | ||
| * @param model - The model to calculate the pre-auth amount for | ||
| * @param params - The parameters for the language model | ||
| * @returns The pre-auth amount in microdollars | ||
| */ | ||
| export const calculatePreAuthAmount = ( | ||
| model: ModelInfo, | ||
| params: z.infer<typeof LanguageModelInputSchema>, | ||
| ): string => { | ||
| const maxContextLength = Math.min( | ||
| JSON.stringify({ | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1: Unit mismatch: Prompt for AI agents |
||
| ...params.callOptions.prompt, | ||
| ...params.callOptions.tools, | ||
| }).length, | ||
| model.context_length, | ||
| ); | ||
|
|
||
| const maxCompletionTokens = | ||
| params.callOptions.maxOutputTokens ?? | ||
| model.top_provider?.max_completion_tokens ?? | ||
| DEFAULT_MAX_COMPLETION_TOKENS; | ||
|
|
||
| const constPerCompletionToken = parseFloat(model.pricing.completion); | ||
| const constPerPromptToken = parseFloat(model.pricing.prompt); | ||
|
|
||
| const amountUsd = | ||
| maxContextLength * constPerPromptToken + | ||
| maxCompletionTokens * constPerCompletionToken; | ||
| return toMicrodollars(amountUsd); | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| { | ||
| "compilerOptions": { | ||
| "target": "ES2022", | ||
| "useDefineForClassFields": true, | ||
| "lib": ["ES2023", "ES2024", "DOM", "DOM.Iterable"], | ||
| "module": "ESNext", | ||
| "skipLibCheck": true, | ||
|
|
||
| /* Bundler mode */ | ||
| "moduleResolution": "bundler", | ||
| "allowImportingTsExtensions": true, | ||
| "isolatedModules": true, | ||
| "verbatimModuleSyntax": false, | ||
| "moduleDetection": "force", | ||
| "noEmit": true, | ||
| "jsx": "react-jsx", | ||
| "allowJs": true, | ||
|
|
||
| /* Linting */ | ||
| "strict": true, | ||
| "noUnusedLocals": true, | ||
| "noUnusedParameters": true, | ||
| "noFallthroughCasesInSwitch": true, | ||
| "noUncheckedSideEffectImports": true, | ||
|
|
||
| /* Path Aliases */ | ||
| "baseUrl": ".", | ||
| "paths": { | ||
| "shared/*": ["./shared/*"], | ||
| "server/*": ["./server/*"], | ||
| "worker/*": ["./worker/*"] | ||
| }, | ||
|
|
||
| /* Types */ | ||
| "types": ["@cloudflare/workers-types"] | ||
| }, | ||
| "include": [ | ||
| "server", | ||
| "shared", | ||
| "vite.config.ts" | ||
| ] | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P2:
process.envis not available in Cloudflare Workers environment. TheWALLET_VENDOR_IDenvironment variable will always be undefined, causing the code to always fall back to"deco". Consider accessing environment variables through the Deco runtime'senvparameter instead.Prompt for AI agents