Skip to content

Commit f1bc0c7

Browse files
committed
Adds deco llm binding
Signed-off-by: Marcos Candeia <marrcooos@gmail.com>
1 parent 29240e6 commit f1bc0c7

24 files changed

Lines changed: 605 additions & 302 deletions

File tree

bun.lock

Lines changed: 177 additions & 177 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

content-scraper/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"build": "bun run build:server"
1515
},
1616
"dependencies": {
17-
"@decocms/runtime": "^1.1.2",
17+
"@decocms/runtime": "^1.1.3",
1818
"@supabase/supabase-js": "^2.49.0",
1919
"zod": "^4.0.0"
2020
},

deco-llm/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.dev.vars

deco-llm/app.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"scopeName": "deco",
3+
"name": "llm",
4+
"connection": {
5+
"type": "HTTP",
6+
"url": "https://sites-deco-llm.decocache.com/mcp"
7+
},
8+
"description": "Deco LLM App Connection for LLM uses.",
9+
"icon": "https://assets.decocache.com/mcp/6e1418f7-c962-406b-aceb-137197902709/ai-gateway.png",
10+
"unlisted": false
11+
}

deco-llm/package.json

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{
2+
"name": "deco-llm",
3+
"version": "1.0.0",
4+
"description": "Deco LLM App Connection for LLM uses.",
5+
"private": true,
6+
"type": "module",
7+
"scripts": {
8+
"dev": "bun run --hot server/main.ts",
9+
"build:server": "NODE_ENV=production bun build server/main.ts --target=bun --outfile=dist/server/main.js",
10+
"build": "bun run build:server",
11+
"publish": "cat app.json | deco registry publish -w /shared/deco -y",
12+
"check": "tsc --noEmit"
13+
},
14+
"dependencies": {
15+
"@ai-sdk/provider": "^3.0.2",
16+
"@ai-sdk/provider-utils": "^4.0.4",
17+
"@decocms/bindings": "^1.0.7",
18+
"@decocms/runtime": "^1.1.3",
19+
"@openrouter/ai-sdk-provider": "^1.5.4",
20+
"@openrouter/sdk": "^0.3.11",
21+
"ai": "^6.0.3",
22+
"zod": "^4.0.0"
23+
},
24+
"devDependencies": {
25+
"@cloudflare/vite-plugin": "^1.13.4",
26+
"@cloudflare/workers-types": "^4.20251014.0",
27+
"@decocms/mcps-shared": "1.0.0",
28+
"@decocms/openrouter": "1.0.0",
29+
"@mastra/core": "^0.24.0",
30+
"@modelcontextprotocol/sdk": "1.25.1",
31+
"@types/mime-db": "^1.43.6",
32+
"deco-cli": "^0.28.0",
33+
"typescript": "^5.7.2",
34+
"vite": "7.2.0",
35+
"wrangler": "^4.28.0"
36+
},
37+
"engines": {
38+
"node": ">=22.0.0"
39+
}
40+
}

deco-llm/server/main.ts

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/**
2+
* OpenRouter MCP Server
3+
*
4+
* This MCP provides tools for interacting with OpenRouter's API,
5+
* including model discovery, comparison, and AI chat completions.
6+
*
7+
* OpenRouter offers a unified API for accessing hundreds of AI models
8+
* with built-in fallback mechanisms, cost optimization, and provider routing.
9+
*/
10+
import type { Registry } from "@decocms/mcps-shared/registry";
11+
import { serve } from "@decocms/mcps-shared/serve";
12+
import { tools } from "@decocms/openrouter/tools";
13+
import { BindingOf, type DefaultEnv, withRuntime } from "@decocms/runtime";
14+
import { z } from "zod";
15+
import { calculatePreAuthAmount, toMicrodollars } from "./usage";
16+
17+
export const StateSchema = z.object({
18+
WALLET: BindingOf("@deco/wallet"),
19+
});
20+
21+
/**
22+
* Environment type combining Deco bindings and Cloudflare Workers context
23+
*/
24+
export type Env = DefaultEnv<typeof StateSchema, Registry>;
25+
26+
interface OpenRouterUsageReport {
27+
providerMetadata: {
28+
openrouter: {
29+
usage: {
30+
cost: number;
31+
};
32+
};
33+
};
34+
}
35+
const isOpenRouterUsageReport = (
36+
usage: unknown | OpenRouterUsageReport,
37+
): usage is OpenRouterUsageReport => {
38+
return (
39+
typeof usage === "object" &&
40+
usage !== null &&
41+
"providerMetadata" in usage &&
42+
typeof usage.providerMetadata === "object" &&
43+
usage.providerMetadata !== null &&
44+
"openrouter" in usage.providerMetadata &&
45+
typeof usage.providerMetadata.openrouter === "object" &&
46+
usage.providerMetadata.openrouter !== null &&
47+
"usage" in usage.providerMetadata.openrouter &&
48+
typeof usage.providerMetadata.openrouter.usage === "object" &&
49+
usage.providerMetadata.openrouter.usage !== null &&
50+
"cost" in usage.providerMetadata.openrouter.usage
51+
);
52+
};
53+
54+
const runtime = withRuntime<
55+
DefaultEnv<typeof StateSchema, Registry>,
56+
typeof StateSchema,
57+
Registry
58+
>({
59+
tools: (env) => {
60+
return tools(env, {
61+
start: async (modelInfo, params) => {
62+
const amount = calculatePreAuthAmount(modelInfo, params);
63+
64+
const { id } =
65+
await env.MESH_REQUEST_CONTEXT.state.WALLET.PRE_AUTHORIZE_AMOUNT({
66+
amount,
67+
metadata: {
68+
modelId: modelInfo.id,
69+
params: params,
70+
},
71+
});
72+
return {
73+
end: async (usage) => {
74+
if (!isOpenRouterUsageReport(usage)) {
75+
throw new Error("Usage cost not found");
76+
}
77+
const vendorId = process.env.WALLET_VENDOR_ID ?? "deco";
78+
await env.MESH_REQUEST_CONTEXT.state.WALLET.COMMIT_PRE_AUTHORIZED_AMOUNT(
79+
{
80+
identifier: id,
81+
contractId:
82+
env.MESH_REQUEST_CONTEXT.connectionId ??
83+
env.MESH_REQUEST_CONTEXT.state.WALLET.value,
84+
vendorId,
85+
amount: toMicrodollars(
86+
usage.providerMetadata.openrouter.usage.cost,
87+
),
88+
},
89+
);
90+
},
91+
};
92+
},
93+
});
94+
},
95+
configuration: {
96+
state: StateSchema,
97+
scopes: [
98+
"WALLET::PRE_AUTHORIZE_AMOUNT",
99+
"WALLET::COMMIT_PRE_AUTHORIZED_AMOUNT",
100+
],
101+
},
102+
});
103+
104+
serve(runtime.fetch);

deco-llm/server/usage.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import type { LanguageModelInputSchema } from "@decocms/bindings/llm";
2+
import type { ModelInfo } from "@decocms/openrouter/types";
3+
import type { z } from "zod";
4+
5+
export interface GenerationContext {
6+
model: ModelInfo;
7+
}
8+
9+
const DEFAULT_MAX_COMPLETION_TOKENS = 1000000;
10+
11+
export const toMicrodollars = (amount: number): string => {
12+
return Math.round(amount * 1_000_000).toString();
13+
};
14+
/**
15+
*
16+
* @param model - The model to calculate the pre-auth amount for
17+
* @param params - The parameters for the language model
18+
* @returns The pre-auth amount in microdollars
19+
*/
20+
export const calculatePreAuthAmount = (
21+
model: ModelInfo,
22+
params: z.infer<typeof LanguageModelInputSchema>,
23+
): string => {
24+
const maxContextLength = Math.min(
25+
JSON.stringify({
26+
...params.callOptions.prompt,
27+
...params.callOptions.tools,
28+
}).length,
29+
model.context_length,
30+
);
31+
32+
const maxCompletionTokens =
33+
params.callOptions.maxOutputTokens ??
34+
model.top_provider?.max_completion_tokens ??
35+
DEFAULT_MAX_COMPLETION_TOKENS;
36+
37+
const constPerCompletionToken = parseFloat(model.pricing.completion);
38+
const constPerPromptToken = parseFloat(model.pricing.prompt);
39+
40+
const amountUsd =
41+
maxContextLength * constPerPromptToken +
42+
maxCompletionTokens * constPerCompletionToken;
43+
return toMicrodollars(amountUsd);
44+
};

deco-llm/tsconfig.json

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{
2+
"compilerOptions": {
3+
"target": "ES2022",
4+
"useDefineForClassFields": true,
5+
"lib": ["ES2023", "ES2024", "DOM", "DOM.Iterable"],
6+
"module": "ESNext",
7+
"skipLibCheck": true,
8+
9+
/* Bundler mode */
10+
"moduleResolution": "bundler",
11+
"allowImportingTsExtensions": true,
12+
"isolatedModules": true,
13+
"verbatimModuleSyntax": false,
14+
"moduleDetection": "force",
15+
"noEmit": true,
16+
"jsx": "react-jsx",
17+
"allowJs": true,
18+
19+
/* Linting */
20+
"strict": true,
21+
"noUnusedLocals": true,
22+
"noUnusedParameters": true,
23+
"noFallthroughCasesInSwitch": true,
24+
"noUncheckedSideEffectImports": true,
25+
26+
/* Path Aliases */
27+
"baseUrl": ".",
28+
"paths": {
29+
"shared/*": ["./shared/*"],
30+
"server/*": ["./server/*"],
31+
"worker/*": ["./worker/*"]
32+
},
33+
34+
/* Types */
35+
"types": ["@cloudflare/workers-types"]
36+
},
37+
"include": [
38+
"server",
39+
"shared",
40+
"vite.config.ts"
41+
]
42+
}

google-calendar/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"check": "tsc --noEmit"
1313
},
1414
"dependencies": {
15-
"@decocms/runtime": "^1.1.2",
15+
"@decocms/runtime": "^1.1.3",
1616
"zod": "^4.0.0"
1717
},
1818
"devDependencies": {

google-tag-manager/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"check": "tsc --noEmit"
1313
},
1414
"dependencies": {
15-
"@decocms/runtime": "^1.1.2",
15+
"@decocms/runtime": "^1.1.3",
1616
"zod": "^4.0.0"
1717
},
1818
"devDependencies": {

0 commit comments

Comments
 (0)