@listeningkit/treg 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "@listeningkit/treg",
3
+ "version": "0.1.0",
4
+ "description": "Production Convex component for treg.to — the OpenRouter for developer tools. Run, route, and pay for 2,600+ tools through one API and CLI.",
5
+ "type": "module",
6
+ "main": "./dist/client.js",
7
+ "types": "./dist/client.d.ts",
8
+ "files": [
9
+ "dist",
10
+ "convex.config.ts",
11
+ "schema.ts",
12
+ "treg.ts",
13
+ "client.ts",
14
+ "lib",
15
+ "README.md"
16
+ ],
17
+ "exports": {
18
+ "./package.json": "./package.json",
19
+ ".": {
20
+ "types": "./dist/client.d.ts",
21
+ "default": "./dist/client.js"
22
+ },
23
+ "./client": {
24
+ "types": "./dist/client.d.ts",
25
+ "default": "./dist/client.js"
26
+ },
27
+ "./convex.config": {
28
+ "types": "./dist/convex.config.d.ts",
29
+ "default": "./dist/convex.config.js"
30
+ },
31
+ "./convex.config.js": {
32
+ "types": "./dist/convex.config.d.ts",
33
+ "default": "./dist/convex.config.js"
34
+ }
35
+ },
36
+ "scripts": {
37
+ "build": "tsc --build ./tsconfig.build.json --force",
38
+ "build:clean": "rm -rf dist && npm run build",
39
+ "build:codegen": "convex codegen --component-dir ./",
40
+ "typecheck": "tsc --noEmit --skipLibCheck",
41
+ "prepare": "npm run build"
42
+ },
43
+ "peerDependencies": {
44
+ "convex": ">=1.45.0"
45
+ },
46
+ "devDependencies": {
47
+ "@types/node": "^22",
48
+ "convex": "^1.46.0",
49
+ "typescript": "^5.6.3"
50
+ },
51
+ "keywords": [
52
+ "convex",
53
+ "convex-component",
54
+ "treg",
55
+ "tools",
56
+ "openrouter-for-tools",
57
+ "api-proxy",
58
+ "developer-tools"
59
+ ],
60
+ "homepage": "https://github.com/superdesigndev/treg",
61
+ "repository": {
62
+ "type": "git",
63
+ "url": "git+https://github.com/superdesigndev/treg.git"
64
+ },
65
+ "license": "Apache-2.0"
66
+ }
package/schema.ts ADDED
@@ -0,0 +1,18 @@
1
+ import { defineSchema, defineTable } from "convex/server";
2
+ import { v } from "convex/values";
3
+
4
+ export default defineSchema({
5
+ // Spend receipts, one row per catalog call. Cost comes only from the
6
+ // X-Treg-Cost-Micro / X-Treg-Call-Id response headers, never from a
7
+ // provider body. Owner is a SHA-256 hash, never the raw identifier.
8
+ calls: defineTable({
9
+ callId: v.string(),
10
+ ownerHash: v.string(),
11
+ endpoint: v.string(),
12
+ costMicro: v.number(),
13
+ servedVia: v.optional(v.string()),
14
+ at: v.number(),
15
+ })
16
+ .index("by_owner", ["ownerHash"])
17
+ .index("by_call", ["callId"]),
18
+ });
package/treg.ts ADDED
@@ -0,0 +1,116 @@
1
+ import { ConvexError } from "convex/values";
2
+ import { v } from "convex/values";
3
+ import { action, internalMutation, query, env } from "./_generated/server.js";
4
+ import { internal } from "./_generated/api.js";
5
+ import { buildCallUrl, callFailureReason, TREG_DEFAULT_BASE_URL } from "./lib/treg.js";
6
+
7
+ /**
8
+ * Call any catalogued treg endpoint by id. Auth lives in the app: the
9
+ * caller passes its already-verified owner string (components have no
10
+ * ctx.auth), which is hashed before it leaves as the ledger tag. The token
11
+ * and base URL come only from the component's declared env, never from
12
+ * arguments; every call carries a spend ceiling (X-Treg-Route-Max-Cost
13
+ * refuses instead of overspending) and a fresh idempotency key so a retry
14
+ * is never billed twice. The upstream answer relays verbatim.
15
+ */
16
+ export const call = action({
17
+ args: {
18
+ owner: v.string(),
19
+ endpoint: v.string(),
20
+ params: v.optional(
21
+ v.record(v.string(), v.union(v.string(), v.number(), v.boolean())),
22
+ ),
23
+ maxCostUsd: v.optional(v.number()),
24
+ },
25
+ returns: v.any(),
26
+ handler: async (ctx, args): Promise<unknown> => {
27
+ const token = env.TREG_TOKEN;
28
+ if (!token) throw new ConvexError("Treg is not switched on yet.");
29
+ const baseUrl = env.TREG_BASE_URL ?? TREG_DEFAULT_BASE_URL;
30
+ const url = buildCallUrl(baseUrl, { endpoint: args.endpoint, params: args.params });
31
+ const ownerHash = await sha256Hex(args.owner);
32
+ const res = await fetch(url, {
33
+ headers: {
34
+ "X-Treg-Token": token,
35
+ "X-Treg-Route-Max-Cost": String(args.maxCostUsd ?? 0.05),
36
+ "Idempotency-Key": crypto.randomUUID(),
37
+ "X-Treg-Meta": `customer=${ownerHash}`,
38
+ },
39
+ signal: AbortSignal.timeout(55_000),
40
+ });
41
+ if (!res.ok) throw new ConvexError(callFailureReason(res.status));
42
+ const json = (await res.json()) as unknown;
43
+
44
+ // Record spend receipt in internal calls table if headers are present
45
+ const callId = res.headers.get("x-treg-call-id");
46
+ const costMicroStr = res.headers.get("x-treg-cost-micro");
47
+ const servedVia = res.headers.get("x-treg-served-via") ?? undefined;
48
+ if (callId && costMicroStr) {
49
+ const costMicro = parseInt(costMicroStr, 10);
50
+ if (!Number.isNaN(costMicro)) {
51
+ await ctx.runMutation(internal.treg.recordReceipt, {
52
+ callId,
53
+ ownerHash,
54
+ endpoint: args.endpoint,
55
+ costMicro,
56
+ servedVia,
57
+ at: Date.now(),
58
+ });
59
+ }
60
+ }
61
+
62
+ return json;
63
+ },
64
+ });
65
+
66
+ /**
67
+ * Record a spend receipt into the calls table.
68
+ */
69
+ export const recordReceipt = internalMutation({
70
+ args: {
71
+ callId: v.string(),
72
+ ownerHash: v.string(),
73
+ endpoint: v.string(),
74
+ costMicro: v.number(),
75
+ servedVia: v.optional(v.string()),
76
+ at: v.number(),
77
+ },
78
+ handler: async (ctx, args) => {
79
+ await ctx.db.insert("calls", args);
80
+ },
81
+ });
82
+
83
+ /**
84
+ * Read spend receipts for a specific owner hash.
85
+ */
86
+ export const getCalls = query({
87
+ args: {
88
+ ownerHash: v.string(),
89
+ limit: v.optional(v.number()),
90
+ },
91
+ returns: v.array(
92
+ v.object({
93
+ _id: v.id("calls"),
94
+ _creationTime: v.number(),
95
+ callId: v.string(),
96
+ ownerHash: v.string(),
97
+ endpoint: v.string(),
98
+ costMicro: v.number(),
99
+ servedVia: v.optional(v.string()),
100
+ at: v.number(),
101
+ }),
102
+ ),
103
+ handler: async (ctx, args) => {
104
+ const limit = args.limit ?? 50;
105
+ return await ctx.db
106
+ .query("calls")
107
+ .withIndex("by_owner", (q) => q.eq("ownerHash", args.ownerHash))
108
+ .order("desc")
109
+ .take(limit);
110
+ },
111
+ });
112
+
113
+ async function sha256Hex(value: string): Promise<string> {
114
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value));
115
+ return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("").slice(0, 16);
116
+ }