@yak-io/trpc 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/LICENSE ADDED
@@ -0,0 +1,36 @@
1
+ Yak Proprietary License
2
+
3
+ Copyright (c) 2025 Yak. All rights reserved.
4
+
5
+ This software and associated documentation files (the "Software") are the
6
+ proprietary property of Yak and are protected by copyright law.
7
+
8
+ GRANT OF LICENSE:
9
+ Subject to the terms of this license and your valid subscription or agreement
10
+ with Yak, you are granted a limited, non-exclusive, non-transferable license
11
+ to use the Software solely for integrating the Yak chatbot widget into your
12
+ applications as intended and documented.
13
+
14
+ RESTRICTIONS:
15
+ You may NOT:
16
+ - Modify, adapt, alter, translate, or create derivative works of the Software
17
+ - Reverse engineer, disassemble, decompile, or otherwise attempt to derive
18
+ the source code of the Software
19
+ - Redistribute, sublicense, lease, rent, or lend the Software to third parties
20
+ - Remove or alter any proprietary notices, labels, or marks on the Software
21
+ - Use the Software for any purpose other than as expressly permitted herein
22
+
23
+ NO WARRANTY:
24
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
25
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
26
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL YAK
27
+ BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
28
+ CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
29
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30
+
31
+ TERMINATION:
32
+ This license is effective until terminated. Your rights under this license
33
+ will terminate automatically without notice if you fail to comply with any
34
+ of its terms.
35
+
36
+ For licensing inquiries, contact: support@yak.io
package/README.md ADDED
@@ -0,0 +1,103 @@
1
+ # @yak-io/trpc
2
+
3
+ Adapters that turn tRPC procedures into Yak tool definitions. This package plugs directly into `@yak-io/javascript` and works seamlessly with the Next.js helpers.
4
+
5
+ ## What you get
6
+
7
+ - `createTRPCToolExecutor` – a thin wrapper that turns procedure names into a tool executor function.
8
+ - `createTRPCToolAdapter` – a higher level helper that returns a full `ToolSource` (manifest + executor) that can be passed straight into `createYakHandler` or `createNextYakHandler`.
9
+ - `buildToolManifest` – introspection utility used by the adapter (exposed for custom pipelines).
10
+
11
+ ## Installation
12
+
13
+ ```bash
14
+ pnpm add @yak-io/trpc @yak-io/javascript
15
+ ```
16
+
17
+ You also need `@trpc/server` (v10+ or v11+) in your project.
18
+
19
+ ## Usage
20
+
21
+ ### 1. Build a tRPC adapter
22
+
23
+ By default, all procedures from your router are exposed as tools:
24
+
25
+ ```ts
26
+ import { createTRPCToolAdapter } from "@yak-io/trpc";
27
+ import { appRouter, createContext } from "@/server/trpc";
28
+
29
+ // All procedures are available
30
+ const trpcTools = createTRPCToolAdapter({
31
+ router: appRouter,
32
+ createContext: async ({ req }) => createContext({ req }),
33
+ });
34
+ ```
35
+
36
+ #### Restricting procedures
37
+
38
+ Use `allowedProcedures` to whitelist specific procedures:
39
+
40
+ ```ts
41
+ const trpcTools = createTRPCToolAdapter({
42
+ router: appRouter,
43
+ createContext: async ({ req }) => createContext({ req }),
44
+ allowedProcedures: ["orders.list", "orders.detail"],
45
+ });
46
+ ```
47
+
48
+ Use `disallowedProcedures` to block specific procedures while allowing the rest:
49
+
50
+ ```ts
51
+ const trpcTools = createTRPCToolAdapter({
52
+ router: appRouter,
53
+ createContext: async ({ req }) => createContext({ req }),
54
+ disallowedProcedures: ["admin.deleteUser", "billing.refund"],
55
+ });
56
+ ```
57
+
58
+ ### 2. Attach it to a Yak handler
59
+
60
+ ```ts
61
+ import { createYakHandler } from "@yak-io/javascript/server";
62
+
63
+ export const { GET, POST } = createYakHandler({
64
+ routes: () => Promise.resolve([{ path: "/" }]),
65
+ tools: trpcTools,
66
+ });
67
+ ```
68
+
69
+ If you are in a Next.js app, simply pass the adapter to `createNextYakHandler` instead.
70
+
71
+ ### 3. Executor only
72
+
73
+ If you already generate manifests elsewhere you can still use the lower level executor helper:
74
+
75
+ ```ts
76
+ import { createTRPCToolExecutor } from "@yak-io/trpc";
77
+
78
+ const executeTool = createTRPCToolExecutor({
79
+ router: appRouter,
80
+ createContext,
81
+ allowedProcedures: ["orders.list"],
82
+ });
83
+ ```
84
+
85
+ Then supply `getTools` + `executeTool` to any Yak handler.
86
+
87
+ ## Types
88
+
89
+ All tool/route/chat config types are re-exported from `@yak-io/javascript/server`, e.g.
90
+
91
+ ```ts
92
+ import type { ToolDefinition, ToolManifest, ToolExecutor } from "@yak-io/trpc";
93
+ ```
94
+
95
+ ## Security tips
96
+
97
+ - For production apps, consider using `allowedProcedures` to restrict to the minimal surface area needed by the assistant, or use `disallowedProcedures` to block sensitive operations.
98
+ - Build per-request contexts so each call is authorized with the user session from `req`.
99
+ - Combine the adapter with additional `ToolSource`s (e.g., GraphQL or REST) by passing arrays to `createYakHandler` / `createNextYakHandler`.
100
+
101
+ ## License
102
+
103
+ Proprietary - see LICENSE file
@@ -0,0 +1,63 @@
1
+ import type { AnyRouter } from "@trpc/server";
2
+ import type { ToolExecutor, ToolSource } from "@yak-io/javascript/server";
3
+ /**
4
+ * Generic request type that works with Next.js Request
5
+ */
6
+ export type GenericRequest = {
7
+ json(): Promise<unknown>;
8
+ };
9
+ /**
10
+ * Configuration for creating a tRPC tool executor
11
+ */
12
+ export type TRPCToolExecutorConfig<TRouter extends AnyRouter, TContext, TRequest extends GenericRequest = GenericRequest> = {
13
+ /** tRPC router instance */
14
+ router: TRouter;
15
+ /** Function to create tRPC context from request */
16
+ createContext: (opts: {
17
+ req: TRequest;
18
+ }) => Promise<TContext>;
19
+ /**
20
+ * List of allowed procedure paths, e.g. ["orders.list", "user.updateProfile"].
21
+ * If provided, only these procedures can be invoked as tools.
22
+ * If omitted, all procedures are allowed by default.
23
+ */
24
+ allowedProcedures?: string[];
25
+ /**
26
+ * List of disallowed procedure paths, e.g. ["admin.deleteUser", "billing.refund"].
27
+ * These procedures will be excluded even if they would otherwise be allowed.
28
+ * Applied after allowedProcedures filtering.
29
+ */
30
+ disallowedProcedures?: string[];
31
+ };
32
+ /**
33
+ * Creates a tool executor function that uses tRPC to execute procedures
34
+ *
35
+ * This adapter allows you to use tRPC procedures as chatbot tools with the
36
+ * Next.js handler from @yak-io/nextjs/server
37
+ *
38
+ * @example
39
+ * ```ts
40
+ * // app/api/yak/tools/route.ts
41
+ * import { createNextYakToolsHandler } from "@yak-io/nextjs/server";
42
+ * import { createTRPCToolExecutor } from "@yak-io/trpc";
43
+ * import { appRouter } from "@/server/trpc/router";
44
+ * import { createContext } from "@/server/trpc/context";
45
+ *
46
+ * export const POST = createNextYakToolsHandler({
47
+ * executeTool: createTRPCToolExecutor({
48
+ * router: appRouter,
49
+ * createContext,
50
+ * allowedProcedures: ["orders.list", "user.updateProfile"],
51
+ * }),
52
+ * });
53
+ * ```
54
+ */
55
+ export declare function createTRPCToolExecutor<TRouter extends AnyRouter, TContext, TRequest extends GenericRequest = GenericRequest>(config: TRPCToolExecutorConfig<TRouter, TContext, TRequest>): ToolExecutor;
56
+ export type TRPCToolAdapterConfig<TRouter extends AnyRouter, TContext, TRequest extends GenericRequest = GenericRequest> = TRPCToolExecutorConfig<TRouter, TContext, TRequest> & {
57
+ id?: string;
58
+ };
59
+ /**
60
+ * Create a core compatible tool source that bundles manifest + executor
61
+ */
62
+ export declare function createTRPCToolAdapter<TRouter extends AnyRouter, TContext, TRequest extends GenericRequest = GenericRequest>(config: TRPCToolAdapterConfig<TRouter, TContext, TRequest>): ToolSource;
63
+ //# sourceMappingURL=adapters.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapters.d.ts","sourceRoot":"","sources":["../src/adapters.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9C,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAG1E;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;CAC1B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,sBAAsB,CAAC,OAAO,SAAS,SAAS,EAAE,QAAQ,EAAE,QAAQ,SAAS,cAAc,GAAG,cAAc,IAAI;IAC1H,2BAA2B;IAC3B,MAAM,EAAE,OAAO,CAAC;IAChB,mDAAmD;IACnD,aAAa,EAAE,CAAC,IAAI,EAAE;QAAE,GAAG,EAAE,QAAQ,CAAA;KAAE,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC9D;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;CACjC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,SAAS,SAAS,EACzB,QAAQ,EACR,QAAQ,SAAS,cAAc,GAAG,cAAc,EAEhD,MAAM,EAAE,sBAAsB,CAAC,OAAO,EAAE,QAAQ,EAAE,QAAQ,CAAC,GAC1D,YAAY,CAyBd;AAED,MAAM,MAAM,qBAAqB,CAC/B,OAAO,SAAS,SAAS,EACzB,QAAQ,EACR,QAAQ,SAAS,cAAc,GAAG,cAAc,IAC9C,sBAAsB,CAAC,OAAO,EAAE,QAAQ,EAAE,QAAQ,CAAC,GAAG;IACxD,EAAE,CAAC,EAAE,MAAM,CAAC;CACb,CAAC;AAEF;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,SAAS,SAAS,EACzB,QAAQ,EACR,QAAQ,SAAS,cAAc,GAAG,cAAc,EAChD,MAAM,EAAE,qBAAqB,CAAC,OAAO,EAAE,QAAQ,EAAE,QAAQ,CAAC,GAAG,UAAU,CAYxE"}
@@ -0,0 +1,63 @@
1
+ import { createDynamicCaller } from "./caller.js";
2
+ import { buildToolManifest } from "./introspect.js";
3
+ /**
4
+ * Creates a tool executor function that uses tRPC to execute procedures
5
+ *
6
+ * This adapter allows you to use tRPC procedures as chatbot tools with the
7
+ * Next.js handler from @yak-io/nextjs/server
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * // app/api/yak/tools/route.ts
12
+ * import { createNextYakToolsHandler } from "@yak-io/nextjs/server";
13
+ * import { createTRPCToolExecutor } from "@yak-io/trpc";
14
+ * import { appRouter } from "@/server/trpc/router";
15
+ * import { createContext } from "@/server/trpc/context";
16
+ *
17
+ * export const POST = createNextYakToolsHandler({
18
+ * executeTool: createTRPCToolExecutor({
19
+ * router: appRouter,
20
+ * createContext,
21
+ * allowedProcedures: ["orders.list", "user.updateProfile"],
22
+ * }),
23
+ * });
24
+ * ```
25
+ */
26
+ export function createTRPCToolExecutor(config) {
27
+ const { router, createContext, allowedProcedures, disallowedProcedures } = config;
28
+ const allowedSet = allowedProcedures ? new Set(allowedProcedures) : null;
29
+ const disallowedSet = disallowedProcedures ? new Set(disallowedProcedures) : null;
30
+ return async (name, args, req) => {
31
+ // Check if procedure is allowed
32
+ // If allowedProcedures is set, procedure must be in the list
33
+ if (allowedSet && !allowedSet.has(name)) {
34
+ throw new Error(`Procedure '${name}' is not allowed`);
35
+ }
36
+ // If disallowedProcedures is set, procedure must not be in the list
37
+ if (disallowedSet && disallowedSet.has(name)) {
38
+ throw new Error(`Procedure '${name}' is not allowed`);
39
+ }
40
+ // Create tRPC context
41
+ const ctx = await createContext({ req: req });
42
+ // Create dynamic caller
43
+ const caller = createDynamicCaller(router, ctx);
44
+ // Invoke the procedure
45
+ return caller(name, args);
46
+ };
47
+ }
48
+ /**
49
+ * Create a core compatible tool source that bundles manifest + executor
50
+ */
51
+ export function createTRPCToolAdapter(config) {
52
+ return {
53
+ id: config.id ?? "trpc",
54
+ getTools: async () => {
55
+ const manifest = buildToolManifest(config.router, {
56
+ allowedProcedures: config.allowedProcedures,
57
+ disallowedProcedures: config.disallowedProcedures,
58
+ });
59
+ return manifest.tools;
60
+ },
61
+ executeTool: createTRPCToolExecutor(config),
62
+ };
63
+ }
@@ -0,0 +1,25 @@
1
+ import type { ZodType } from "zod";
2
+ /**
3
+ * Coerces input values based on Zod schema types.
4
+ * Converts ISO date strings to Date objects only for z.date() fields.
5
+ * Leaves z.string() fields unchanged even if they contain ISO date strings.
6
+ */
7
+ export declare function coerceInputToSchema(input: unknown, schema: ZodType | undefined): unknown;
8
+ /**
9
+ * Splits a procedure path like "orders.list" into ["orders", "list"]
10
+ */
11
+ export declare function splitProcedurePath(path: string): string[];
12
+ /**
13
+ * Creates a dynamic caller that can invoke procedures by path string.
14
+ * Drills into nested router structure to find the target procedure.
15
+ * Automatically coerces ISO date strings to Date objects for z.date() fields.
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * const caller = createDynamicCaller(appRouter, ctx);
20
+ * const result = await caller("orders.list", { limit: 10 });
21
+ * ```
22
+ */
23
+ export declare function createDynamicCaller(router: any, // eslint-disable-line @typescript-eslint/no-explicit-any
24
+ ctx: any): (name: string, input: unknown) => Promise<unknown>;
25
+ //# sourceMappingURL=caller.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"caller.d.ts","sourceRoot":"","sources":["../src/caller.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,KAAK,CAAC;AAuEnC;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,OAAO,EACd,MAAM,EAAE,OAAO,GAAG,SAAS,GAC1B,OAAO,CA4DT;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAEzD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,GAAG,EAAE,yDAAyD;AACtE,GAAG,EAAE,GAAG,GACP,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,CA+CpD"}
package/dist/caller.js ADDED
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Properties that must never be accessed dynamically to prevent prototype pollution
3
+ */
4
+ const FORBIDDEN_PROPS = new Set(["__proto__", "constructor", "prototype"]);
5
+ /**
6
+ * ISO 8601 date string pattern
7
+ * Matches: 2024-01-15T10:30:00.000Z, 2024-01-15T10:30:00Z, 2024-01-15T10:30:00+00:00
8
+ */
9
+ const ISO_DATE_REGEX = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?(?:Z|[+-]\d{2}:\d{2})$/;
10
+ /**
11
+ * Gets the Zod type name from a schema (supports both Zod 3 and Zod 4)
12
+ */
13
+ function getZodTypeName(schema) {
14
+ // Zod 4 uses schema.type or schema._def.type
15
+ // Zod 3 uses schema._def.typeName
16
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
17
+ const s = schema;
18
+ return s.type ?? s._def?.type ?? s._def?.typeName;
19
+ }
20
+ /**
21
+ * Gets the inner type from wrapper Zod types (optional, nullable, default, etc.)
22
+ */
23
+ function unwrapZodType(schema) {
24
+ const typeName = getZodTypeName(schema);
25
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
26
+ const def = schema._def;
27
+ if (typeName === "optional" ||
28
+ typeName === "ZodOptional" ||
29
+ typeName === "nullable" ||
30
+ typeName === "ZodNullable" ||
31
+ typeName === "default" ||
32
+ typeName === "ZodDefault") {
33
+ // Zod 4 uses def.innerType, Zod 3 uses def.innerType
34
+ const inner = def.innerType ?? def.unwrap?.();
35
+ if (inner)
36
+ return unwrapZodType(inner);
37
+ }
38
+ return schema;
39
+ }
40
+ /**
41
+ * Checks if a Zod schema represents a date type
42
+ */
43
+ function isZodDate(schema) {
44
+ const unwrapped = unwrapZodType(schema);
45
+ const typeName = getZodTypeName(unwrapped);
46
+ return typeName === "date" || typeName === "ZodDate";
47
+ }
48
+ /**
49
+ * Gets the shape of a Zod object schema (supports both Zod 3 and Zod 4)
50
+ */
51
+ function getObjectShape(schema) {
52
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
53
+ const def = schema._def;
54
+ // Zod 4: def.shape is an object
55
+ // Zod 3: def.shape() is a function
56
+ if (typeof def.shape === "function") {
57
+ return def.shape();
58
+ }
59
+ return def.shape;
60
+ }
61
+ /**
62
+ * Coerces input values based on Zod schema types.
63
+ * Converts ISO date strings to Date objects only for z.date() fields.
64
+ * Leaves z.string() fields unchanged even if they contain ISO date strings.
65
+ */
66
+ export function coerceInputToSchema(input, schema) {
67
+ if (input === null || input === undefined || !schema) {
68
+ return input;
69
+ }
70
+ const unwrapped = unwrapZodType(schema);
71
+ const typeName = getZodTypeName(unwrapped);
72
+ // Handle date coercion
73
+ if ((typeName === "date" || typeName === "ZodDate") && typeof input === "string") {
74
+ if (ISO_DATE_REGEX.test(input)) {
75
+ const date = new Date(input);
76
+ if (!Number.isNaN(date.getTime())) {
77
+ return date;
78
+ }
79
+ }
80
+ return input;
81
+ }
82
+ // Handle arrays
83
+ if ((typeName === "array" || typeName === "ZodArray") && Array.isArray(input)) {
84
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
85
+ const def = unwrapped._def;
86
+ const elementSchema = def.type ?? def.element;
87
+ return input.map((item) => coerceInputToSchema(item, elementSchema));
88
+ }
89
+ // Handle objects
90
+ if ((typeName === "object" || typeName === "ZodObject") && typeof input === "object") {
91
+ const shape = getObjectShape(unwrapped);
92
+ if (!shape)
93
+ return input;
94
+ const result = {};
95
+ for (const [key, value] of Object.entries(input)) {
96
+ const fieldSchema = shape[key];
97
+ result[key] = fieldSchema
98
+ ? coerceInputToSchema(value, fieldSchema)
99
+ : value;
100
+ }
101
+ return result;
102
+ }
103
+ // Handle unions (e.g., z.union([z.string(), z.date()]))
104
+ if ((typeName === "union" || typeName === "ZodUnion") && typeof input === "string") {
105
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
106
+ const def = unwrapped._def;
107
+ const options = def.options;
108
+ if (options) {
109
+ // If any option is a date and input looks like a date, coerce it
110
+ const hasDateOption = options.some((opt) => isZodDate(opt));
111
+ if (hasDateOption && ISO_DATE_REGEX.test(input)) {
112
+ const date = new Date(input);
113
+ if (!Number.isNaN(date.getTime())) {
114
+ return date;
115
+ }
116
+ }
117
+ }
118
+ }
119
+ return input;
120
+ }
121
+ /**
122
+ * Splits a procedure path like "orders.list" into ["orders", "list"]
123
+ */
124
+ export function splitProcedurePath(path) {
125
+ return path.split(".");
126
+ }
127
+ /**
128
+ * Creates a dynamic caller that can invoke procedures by path string.
129
+ * Drills into nested router structure to find the target procedure.
130
+ * Automatically coerces ISO date strings to Date objects for z.date() fields.
131
+ *
132
+ * @example
133
+ * ```ts
134
+ * const caller = createDynamicCaller(appRouter, ctx);
135
+ * const result = await caller("orders.list", { limit: 10 });
136
+ * ```
137
+ */
138
+ export function createDynamicCaller(router, // eslint-disable-line @typescript-eslint/no-explicit-any
139
+ ctx // eslint-disable-line @typescript-eslint/no-explicit-any
140
+ ) {
141
+ const caller = router.createCaller(ctx);
142
+ // Get procedure definitions for schema lookup
143
+ const procedures = router._def.procedures; // eslint-disable-line @typescript-eslint/no-explicit-any
144
+ return async (name, input) => {
145
+ const pathParts = splitProcedurePath(name);
146
+ let current = caller; // eslint-disable-line @typescript-eslint/no-explicit-any
147
+ // Drill down to the target procedure
148
+ for (let i = 0; i < pathParts.length - 1; i++) {
149
+ const part = pathParts[i];
150
+ if (!part) {
151
+ throw new Error(`Invalid procedure path: ${name}`);
152
+ }
153
+ // Prevent prototype pollution attacks
154
+ if (FORBIDDEN_PROPS.has(part)) {
155
+ throw new Error(`Invalid procedure path: ${name}`);
156
+ }
157
+ current = current[part];
158
+ if (!current) {
159
+ throw new Error(`Procedure not found: ${name}`);
160
+ }
161
+ }
162
+ // Call the final procedure
163
+ const procedureName = pathParts[pathParts.length - 1];
164
+ if (!procedureName) {
165
+ throw new Error(`Invalid procedure path: ${name}`);
166
+ }
167
+ // Prevent prototype pollution attacks
168
+ if (FORBIDDEN_PROPS.has(procedureName)) {
169
+ throw new Error(`Invalid procedure path: ${name}`);
170
+ }
171
+ const procedure = current[procedureName];
172
+ if (typeof procedure !== "function") {
173
+ throw new Error(`Procedure not found or not callable: ${name}`);
174
+ }
175
+ // Get input schema and coerce dates based on schema types
176
+ const procDef = procedures[name];
177
+ const inputSchema = procDef?._def?.inputs?.[0];
178
+ const coercedInput = coerceInputToSchema(input, inputSchema);
179
+ return procedure(coercedInput);
180
+ };
181
+ }
@@ -0,0 +1,47 @@
1
+ import type { AnyRouter } from "@trpc/server";
2
+ import type { RouteInfo } from "./types.js";
3
+ /**
4
+ * Configuration for the chat config handler
5
+ */
6
+ export type NextChatbotConfigHandlerConfig<TRouter extends AnyRouter> = {
7
+ /**
8
+ * The tRPC router to introspect for tools
9
+ */
10
+ router: TRouter;
11
+ /**
12
+ * List of allowed procedure paths for tools
13
+ */
14
+ allowedProcedures: string[];
15
+ /**
16
+ * Function to get route information
17
+ * This should scan your Next.js app directory and return route info
18
+ */
19
+ getRoutes: () => Promise<RouteInfo[]>;
20
+ };
21
+ /**
22
+ * Create a Next.js App Router handler that exposes combined chat configuration
23
+ *
24
+ * This handler combines both the tool manifest (tRPC procedures) and route manifest
25
+ * (Next.js routes) into a single endpoint for the chatbot to consume.
26
+ *
27
+ * @param config - Configuration object
28
+ * @returns A Next.js route handler function
29
+ *
30
+ * @example
31
+ * ```ts
32
+ * // app/api/chatbot/config/route.ts
33
+ * import { appRouter } from "@/server/trpc/router";
34
+ * import { createNextChatbotConfigHandler } from "@yak/trpc";
35
+ * import { scanRoutes } from "@yak/nextjs/server";
36
+ *
37
+ * export const GET = createNextChatbotConfigHandler({
38
+ * router: appRouter,
39
+ * allowedProcedures: ["orders.list", "user.updateProfile"],
40
+ * getRoutes: async () => {
41
+ * return scanRoutes("./src/app", false);
42
+ * },
43
+ * });
44
+ * ```
45
+ */
46
+ export declare function createNextChatbotConfigHandler<TRouter extends AnyRouter>(config: NextChatbotConfigHandlerConfig<TRouter>): (_req: Request) => Promise<Response>;
47
+ //# sourceMappingURL=createNextChatbotConfigHandler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createNextChatbotConfigHandler.d.ts","sourceRoot":"","sources":["../src/createNextChatbotConfigHandler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAc,SAAS,EAAE,MAAM,YAAY,CAAC;AAGxD;;GAEG;AACH,MAAM,MAAM,8BAA8B,CAAC,OAAO,SAAS,SAAS,IAAI;IACtE;;OAEG;IACH,MAAM,EAAE,OAAO,CAAC;IAEhB;;OAEG;IACH,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAE5B;;;OAGG;IACH,SAAS,EAAE,MAAM,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;CACvC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,8BAA8B,CAC5C,OAAO,SAAS,SAAS,EAEzB,MAAM,EAAE,8BAA8B,CAAC,OAAO,CAAC,IAG7C,MAAM,OAAO,KACZ,OAAO,CAAC,QAAQ,CAAC,CAwBrB"}
@@ -0,0 +1,49 @@
1
+ import { buildToolManifest } from "./introspect.js";
2
+ /**
3
+ * Create a Next.js App Router handler that exposes combined chat configuration
4
+ *
5
+ * This handler combines both the tool manifest (tRPC procedures) and route manifest
6
+ * (Next.js routes) into a single endpoint for the chatbot to consume.
7
+ *
8
+ * @param config - Configuration object
9
+ * @returns A Next.js route handler function
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * // app/api/chatbot/config/route.ts
14
+ * import { appRouter } from "@/server/trpc/router";
15
+ * import { createNextChatbotConfigHandler } from "@yak/trpc";
16
+ * import { scanRoutes } from "@yak/nextjs/server";
17
+ *
18
+ * export const GET = createNextChatbotConfigHandler({
19
+ * router: appRouter,
20
+ * allowedProcedures: ["orders.list", "user.updateProfile"],
21
+ * getRoutes: async () => {
22
+ * return scanRoutes("./src/app", false);
23
+ * },
24
+ * });
25
+ * ```
26
+ */
27
+ export function createNextChatbotConfigHandler(config) {
28
+ return async function handleConfig(_req) {
29
+ const toolManifest = buildToolManifest(config.router, {
30
+ allowedProcedures: config.allowedProcedures,
31
+ });
32
+ const routes = await config.getRoutes();
33
+ const routeManifest = {
34
+ routes,
35
+ generated_at: new Date().toISOString(),
36
+ };
37
+ const chatConfig = {
38
+ tools: toolManifest,
39
+ routes: routeManifest,
40
+ };
41
+ return new Response(JSON.stringify(chatConfig), {
42
+ status: 200,
43
+ headers: {
44
+ "Content-Type": "application/json",
45
+ "Cache-Control": "no-store",
46
+ },
47
+ });
48
+ };
49
+ }
@@ -0,0 +1,44 @@
1
+ import type { AnyRouter } from "@trpc/server";
2
+ /**
3
+ * Generic request type
4
+ */
5
+ export type GenericRequest = {
6
+ json(): Promise<unknown>;
7
+ };
8
+ /**
9
+ * Configuration for the tool manifest handler
10
+ */
11
+ export type NextChatbotToolManifestHandlerConfig<TRouter extends AnyRouter> = {
12
+ /**
13
+ * The tRPC router to introspect
14
+ */
15
+ router: TRouter;
16
+ /**
17
+ * List of allowed procedure paths, e.g. ["orders.list", "user.updateProfile"].
18
+ * Only these procedures will appear in the tool manifest.
19
+ */
20
+ allowedProcedures: string[];
21
+ };
22
+ /**
23
+ * Create a Next.js App Router handler that exposes a tool manifest
24
+ *
25
+ * This handler introspects your tRPC router at runtime and returns a JSON manifest
26
+ * of tools (procedures) available to the chatbot, including their input/output schemas.
27
+ *
28
+ * @param config - Configuration object
29
+ * @returns A Next.js route handler function
30
+ *
31
+ * @example
32
+ * ```ts
33
+ * // app/api/chatbot/tools/manifest/route.ts
34
+ * import { appRouter } from "@/server/trpc/router";
35
+ * import { createNextChatbotToolManifestHandler } from "@yak/trpc";
36
+ *
37
+ * export const GET = createNextChatbotToolManifestHandler({
38
+ * router: appRouter,
39
+ * allowedProcedures: ["orders.list", "user.updateProfile"],
40
+ * });
41
+ * ```
42
+ */
43
+ export declare function createNextChatbotToolManifestHandler<TRouter extends AnyRouter>(config: NextChatbotToolManifestHandlerConfig<TRouter>): (_req: GenericRequest) => Promise<Response>;
44
+ //# sourceMappingURL=createNextChatbotToolManifestHandler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createNextChatbotToolManifestHandler.d.ts","sourceRoot":"","sources":["../src/createNextChatbotToolManifestHandler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAI9C;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;CAC1B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,oCAAoC,CAAC,OAAO,SAAS,SAAS,IAAI;IAC5E;;OAEG;IACH,MAAM,EAAE,OAAO,CAAC;IAEhB;;;OAGG;IACH,iBAAiB,EAAE,MAAM,EAAE,CAAC;CAC7B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,oCAAoC,CAClD,OAAO,SAAS,SAAS,EAEzB,MAAM,EAAE,oCAAoC,CAAC,OAAO,CAAC,IAGnD,MAAM,cAAc,KACnB,OAAO,CAAC,QAAQ,CAAC,CAarB"}
@@ -0,0 +1,36 @@
1
+ import { buildToolManifest } from "./introspect.js";
2
+ /**
3
+ * Create a Next.js App Router handler that exposes a tool manifest
4
+ *
5
+ * This handler introspects your tRPC router at runtime and returns a JSON manifest
6
+ * of tools (procedures) available to the chatbot, including their input/output schemas.
7
+ *
8
+ * @param config - Configuration object
9
+ * @returns A Next.js route handler function
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * // app/api/chatbot/tools/manifest/route.ts
14
+ * import { appRouter } from "@/server/trpc/router";
15
+ * import { createNextChatbotToolManifestHandler } from "@yak/trpc";
16
+ *
17
+ * export const GET = createNextChatbotToolManifestHandler({
18
+ * router: appRouter,
19
+ * allowedProcedures: ["orders.list", "user.updateProfile"],
20
+ * });
21
+ * ```
22
+ */
23
+ export function createNextChatbotToolManifestHandler(config) {
24
+ return async function handleManifest(_req) {
25
+ const manifest = buildToolManifest(config.router, {
26
+ allowedProcedures: config.allowedProcedures,
27
+ });
28
+ return new Response(JSON.stringify(manifest), {
29
+ status: 200,
30
+ headers: {
31
+ "Content-Type": "application/json",
32
+ "Cache-Control": "no-store",
33
+ },
34
+ });
35
+ };
36
+ }
@@ -0,0 +1,50 @@
1
+ import type { AnyRouter } from "@trpc/server";
2
+ /**
3
+ * Generic request type that works with Next.js Request
4
+ */
5
+ export type GenericRequest = {
6
+ json(): Promise<unknown>;
7
+ };
8
+ /**
9
+ * Configuration for the Next.js chatbot tools handler
10
+ */
11
+ export type NextChatbotToolsHandlerConfig<TRouter extends AnyRouter, TContext, TRequest extends GenericRequest = GenericRequest> = {
12
+ /** tRPC router instance */
13
+ router: TRouter;
14
+ /** Function to create tRPC context from request */
15
+ createContext: (opts: {
16
+ req: TRequest;
17
+ }) => Promise<TContext>;
18
+ /**
19
+ * List of allowed procedure paths, e.g. ["orders.list", "user.updateProfile"].
20
+ * Only these procedures can be invoked as tools.
21
+ */
22
+ allowedProcedures: string[];
23
+ };
24
+ /**
25
+ * Creates a Next.js App Router-compatible POST handler for chatbot tool calls.
26
+ *
27
+ * This handler:
28
+ * 1. Parses the tool call payload from the request
29
+ * 2. Validates the procedure name is in the allowlist
30
+ * 3. Creates a tRPC caller with the request context
31
+ * 4. Dynamically invokes the requested procedure
32
+ * 5. Returns the result or error as JSON
33
+ *
34
+ * @example
35
+ * ```ts
36
+ * // app/api/chatbot/tools/route.ts
37
+ * import { NextRequest } from "next/server";
38
+ * import { appRouter } from "@/server/trpc/router";
39
+ * import { createContext } from "@/server/trpc/context";
40
+ * import { createNextChatbotToolsHandler } from "@yak/trpc";
41
+ *
42
+ * export const POST = createNextChatbotToolsHandler({
43
+ * router: appRouter,
44
+ * createContext,
45
+ * allowedProcedures: ["orders.list", "user.updateProfile"],
46
+ * });
47
+ * ```
48
+ */
49
+ export declare function createNextChatbotToolsHandler<TRouter extends AnyRouter, TContext, TRequest extends GenericRequest = GenericRequest>(config: NextChatbotToolsHandlerConfig<TRouter, TContext, TRequest>): (req: TRequest) => Promise<Response>;
50
+ //# sourceMappingURL=createNextChatbotToolsHandler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createNextChatbotToolsHandler.d.ts","sourceRoot":"","sources":["../src/createNextChatbotToolsHandler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAK9C;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;CAC1B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,6BAA6B,CAAC,OAAO,SAAS,SAAS,EAAE,QAAQ,EAAE,QAAQ,SAAS,cAAc,GAAG,cAAc,IAAI;IACjI,2BAA2B;IAC3B,MAAM,EAAE,OAAO,CAAC;IAChB,mDAAmD;IACnD,aAAa,EAAE,CAAC,IAAI,EAAE;QAAE,GAAG,EAAE,QAAQ,CAAA;KAAE,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC9D;;;OAGG;IACH,iBAAiB,EAAE,MAAM,EAAE,CAAC;CAC7B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,6BAA6B,CAC3C,OAAO,SAAS,SAAS,EACzB,QAAQ,EACR,QAAQ,SAAS,cAAc,GAAG,cAAc,EAEhD,MAAM,EAAE,6BAA6B,CAAC,OAAO,EAAE,QAAQ,EAAE,QAAQ,CAAC,GACjE,CAAC,GAAG,EAAE,QAAQ,KAAK,OAAO,CAAC,QAAQ,CAAC,CA8DtC"}
@@ -0,0 +1,126 @@
1
+ import { createDynamicCaller } from "./caller.js";
2
+ import { logger } from "./logger.js";
3
+ /**
4
+ * Creates a Next.js App Router-compatible POST handler for chatbot tool calls.
5
+ *
6
+ * This handler:
7
+ * 1. Parses the tool call payload from the request
8
+ * 2. Validates the procedure name is in the allowlist
9
+ * 3. Creates a tRPC caller with the request context
10
+ * 4. Dynamically invokes the requested procedure
11
+ * 5. Returns the result or error as JSON
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * // app/api/chatbot/tools/route.ts
16
+ * import { NextRequest } from "next/server";
17
+ * import { appRouter } from "@/server/trpc/router";
18
+ * import { createContext } from "@/server/trpc/context";
19
+ * import { createNextChatbotToolsHandler } from "@yak/trpc";
20
+ *
21
+ * export const POST = createNextChatbotToolsHandler({
22
+ * router: appRouter,
23
+ * createContext,
24
+ * allowedProcedures: ["orders.list", "user.updateProfile"],
25
+ * });
26
+ * ```
27
+ */
28
+ export function createNextChatbotToolsHandler(config) {
29
+ const { router, createContext, allowedProcedures } = config;
30
+ return async (req) => {
31
+ try {
32
+ // Parse request body
33
+ const body = await req.json();
34
+ // Validate payload structure
35
+ if (!isToolCallPayload(body)) {
36
+ logger.warn("Invalid tool call payload received");
37
+ return createErrorResponse("Invalid request payload", 400);
38
+ }
39
+ const { id, name, args } = body;
40
+ logger.debug(`Tool call received: ${name}`, { id, args });
41
+ // Check if procedure is allowed
42
+ if (!allowedProcedures.includes(name)) {
43
+ logger.warn(`Procedure not allowed: ${name}`);
44
+ return createToolErrorResponse(id, `Procedure '${name}' is not allowed`);
45
+ }
46
+ // Create tRPC context
47
+ const ctx = await createContext({ req });
48
+ // Create dynamic caller
49
+ const caller = createDynamicCaller(router, ctx);
50
+ // Invoke the procedure
51
+ const result = await caller(name, args);
52
+ logger.debug(`Tool call succeeded: ${name}`, { id });
53
+ // Return success response
54
+ const successResult = {
55
+ id,
56
+ ok: true,
57
+ result,
58
+ };
59
+ return new Response(JSON.stringify(successResult), {
60
+ status: 200,
61
+ headers: {
62
+ "Content-Type": "application/json",
63
+ },
64
+ });
65
+ }
66
+ catch (error) {
67
+ logger.error("Tool call failed", error);
68
+ // Extract error details
69
+ const body = await req.json().catch(() => ({}));
70
+ const id = body.id ?? "unknown";
71
+ const errorMessage = extractErrorMessage(error);
72
+ return createToolErrorResponse(id, errorMessage);
73
+ }
74
+ };
75
+ }
76
+ /**
77
+ * Type guard to validate tool call payload
78
+ */
79
+ function isToolCallPayload(payload) {
80
+ return (typeof payload === "object" &&
81
+ payload !== null &&
82
+ "id" in payload &&
83
+ typeof payload.id === "string" &&
84
+ "name" in payload &&
85
+ typeof payload.name === "string" &&
86
+ "args" in payload);
87
+ }
88
+ /**
89
+ * Extract a safe error message from an unknown error
90
+ */
91
+ function extractErrorMessage(error) {
92
+ if (error instanceof Error) {
93
+ return error.message;
94
+ }
95
+ if (typeof error === "string") {
96
+ return error;
97
+ }
98
+ return "An unknown error occurred";
99
+ }
100
+ /**
101
+ * Create a JSON response for tool call errors
102
+ */
103
+ function createToolErrorResponse(id, error) {
104
+ const errorResult = {
105
+ id,
106
+ ok: false,
107
+ error,
108
+ };
109
+ return new Response(JSON.stringify(errorResult), {
110
+ status: 200, // Return 200 with error payload so client can process it
111
+ headers: {
112
+ "Content-Type": "application/json",
113
+ },
114
+ });
115
+ }
116
+ /**
117
+ * Create a generic HTTP error response
118
+ */
119
+ function createErrorResponse(message, status) {
120
+ return new Response(JSON.stringify({ error: message }), {
121
+ status,
122
+ headers: {
123
+ "Content-Type": "application/json",
124
+ },
125
+ });
126
+ }
@@ -0,0 +1,6 @@
1
+ export { createTRPCToolExecutor, createTRPCToolAdapter } from "./adapters.js";
2
+ export type { TRPCToolExecutorConfig, TRPCToolAdapterConfig } from "./adapters.js";
3
+ export { buildToolManifest } from "./introspect.js";
4
+ export type { BuildToolManifestOptions } from "./introspect.js";
5
+ export type { ToolCallPayload, ToolCallResult, ToolDefinition, ToolManifest, RouteInfo, RouteManifest, ChatConfig, ToolExecutor, } from "@yak-io/javascript/server";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAC9E,YAAY,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAC;AAGnF,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,YAAY,EAAE,wBAAwB,EAAE,MAAM,iBAAiB,CAAC;AAGhE,YAAY,EACX,eAAe,EACf,cAAc,EACd,cAAc,EACd,YAAY,EACZ,SAAS,EACT,aAAa,EACb,UAAU,EACV,YAAY,GACZ,MAAM,2BAA2B,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ // tRPC adapters for core handlers
2
+ export { createTRPCToolExecutor, createTRPCToolAdapter } from "./adapters.js";
3
+ // Tool manifest builder
4
+ export { buildToolManifest } from "./introspect.js";
@@ -0,0 +1,44 @@
1
+ import type { AnyRouter } from "@trpc/server";
2
+ import type { ToolManifest } from "@yak-io/javascript/server";
3
+ /**
4
+ * Options for building a tool manifest
5
+ */
6
+ export type BuildToolManifestOptions = {
7
+ /**
8
+ * List of procedure paths to include (e.g., ["orders.list", "user.updateProfile"]).
9
+ * If omitted, all procedures are included by default.
10
+ */
11
+ allowedProcedures?: string[];
12
+ /**
13
+ * List of procedure paths to exclude (e.g., ["admin.deleteUser", "billing.refund"]).
14
+ * Applied after allowedProcedures filtering.
15
+ */
16
+ disallowedProcedures?: string[];
17
+ };
18
+ /**
19
+ * Build a tool manifest from a tRPC router by introspecting its procedures
20
+ *
21
+ * @param router - The tRPC router to introspect
22
+ * @param opts - Configuration options
23
+ * @param opts.allowedProcedures - List of procedure paths to include. If omitted, all procedures are included.
24
+ * @param opts.disallowedProcedures - List of procedure paths to exclude. Applied after allowedProcedures.
25
+ * @returns A tool manifest containing definitions for the filtered procedures
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * // Allow all procedures
30
+ * const manifest = buildToolManifest(appRouter);
31
+ *
32
+ * // Only specific procedures
33
+ * const manifest = buildToolManifest(appRouter, {
34
+ * allowedProcedures: ["orders.list", "user.updateProfile"]
35
+ * });
36
+ *
37
+ * // All except specific procedures
38
+ * const manifest = buildToolManifest(appRouter, {
39
+ * disallowedProcedures: ["admin.deleteUser", "billing.refund"]
40
+ * });
41
+ * ```
42
+ */
43
+ export declare function buildToolManifest(router: AnyRouter, opts?: BuildToolManifestOptions): ToolManifest;
44
+ //# sourceMappingURL=introspect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"introspect.d.ts","sourceRoot":"","sources":["../src/introspect.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAoB,MAAM,cAAc,CAAC;AAGhE,OAAO,KAAK,EAAkB,YAAY,EAAE,MAAM,2BAA2B,CAAC;AA4B9E;;GAEG;AACH,MAAM,MAAM,wBAAwB,GAAG;IACrC;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B;;;OAGG;IACH,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;CACjC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,SAAS,EACjB,IAAI,GAAE,wBAA6B,GAClC,YAAY,CAuCd"}
@@ -0,0 +1,79 @@
1
+ import { toJSONSchema } from "zod";
2
+ /**
3
+ * Extract runtime procedure information from a tRPC router
4
+ */
5
+ function getRuntimeProcedures(router) {
6
+ const procedures = router._def.procedures;
7
+ return Object.entries(procedures).map(([path, proc]) => {
8
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
9
+ const def = proc._def;
10
+ return {
11
+ path,
12
+ type: def.type,
13
+ inputSchema: def.inputs?.[0],
14
+ outputSchema: def.output ?? undefined,
15
+ };
16
+ });
17
+ }
18
+ /**
19
+ * Build a tool manifest from a tRPC router by introspecting its procedures
20
+ *
21
+ * @param router - The tRPC router to introspect
22
+ * @param opts - Configuration options
23
+ * @param opts.allowedProcedures - List of procedure paths to include. If omitted, all procedures are included.
24
+ * @param opts.disallowedProcedures - List of procedure paths to exclude. Applied after allowedProcedures.
25
+ * @returns A tool manifest containing definitions for the filtered procedures
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * // Allow all procedures
30
+ * const manifest = buildToolManifest(appRouter);
31
+ *
32
+ * // Only specific procedures
33
+ * const manifest = buildToolManifest(appRouter, {
34
+ * allowedProcedures: ["orders.list", "user.updateProfile"]
35
+ * });
36
+ *
37
+ * // All except specific procedures
38
+ * const manifest = buildToolManifest(appRouter, {
39
+ * disallowedProcedures: ["admin.deleteUser", "billing.refund"]
40
+ * });
41
+ * ```
42
+ */
43
+ export function buildToolManifest(router, opts = {}) {
44
+ const { allowedProcedures, disallowedProcedures } = opts;
45
+ const allowedSet = allowedProcedures ? new Set(allowedProcedures) : null;
46
+ const disallowedSet = disallowedProcedures ? new Set(disallowedProcedures) : null;
47
+ const runtimeProcedures = getRuntimeProcedures(router);
48
+ const tools = runtimeProcedures
49
+ .filter((p) => {
50
+ // If allowedProcedures is set, procedure must be in the list
51
+ if (allowedSet && !allowedSet.has(p.path)) {
52
+ return false;
53
+ }
54
+ // If disallowedProcedures is set, procedure must not be in the list
55
+ if (disallowedSet && disallowedSet.has(p.path)) {
56
+ return false;
57
+ }
58
+ return true;
59
+ })
60
+ .map((p) => {
61
+ const inputSchema = p.inputSchema
62
+ ? toJSONSchema(p.inputSchema, { unrepresentable: "any" })
63
+ : undefined;
64
+ const outputSchema = p.outputSchema
65
+ ? toJSONSchema(p.outputSchema, { unrepresentable: "any" })
66
+ : undefined;
67
+ return {
68
+ name: p.path,
69
+ displayName: p.path, // host app can override later if needed
70
+ description: `tRPC procedure ${p.path} (${p.type})`,
71
+ inputSchema,
72
+ outputSchema,
73
+ };
74
+ });
75
+ return {
76
+ tools,
77
+ generated_at: new Date().toISOString(),
78
+ };
79
+ }
@@ -0,0 +1,6 @@
1
+ export declare const logger: {
2
+ debug: (message: string, data?: unknown) => void;
3
+ warn: (message: string, data?: unknown) => void;
4
+ error: (message: string, error?: unknown) => void;
5
+ };
6
+ //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,MAAM;qBACA,MAAM,SAAS,OAAO,KAAG,IAAI;oBAM9B,MAAM,SAAS,OAAO,KAAG,IAAI;qBAI5B,MAAM,UAAU,OAAO,KAAG,IAAI;CAGhD,CAAC"}
package/dist/logger.js ADDED
@@ -0,0 +1,15 @@
1
+ /* eslint-disable no-console */
2
+ const isDev = process.env.NODE_ENV === "development";
3
+ export const logger = {
4
+ debug: (message, data) => {
5
+ if (isDev) {
6
+ console.log(`[yak/trpc] ${message}`, data ?? "");
7
+ }
8
+ },
9
+ warn: (message, data) => {
10
+ console.warn(`[yak/trpc] ${message}`, data ?? "");
11
+ },
12
+ error: (message, error) => {
13
+ console.error(`[yak/trpc] ${message}`, error);
14
+ },
15
+ };
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Generic JSON Schema type
3
+ */
4
+ export type JSONSchema = Record<string, unknown>;
5
+ /**
6
+ * Payload for a tool call request from the iframe
7
+ */
8
+ export type ToolCallPayload = {
9
+ id: string;
10
+ name: string;
11
+ args: unknown;
12
+ };
13
+ /**
14
+ * Successful tool call result
15
+ */
16
+ export type ToolCallSuccess = {
17
+ ok: true;
18
+ result: unknown;
19
+ };
20
+ /**
21
+ * Failed tool call result
22
+ */
23
+ export type ToolCallError = {
24
+ ok: false;
25
+ error: string;
26
+ };
27
+ /**
28
+ * Union type for tool call responses
29
+ */
30
+ export type ToolCallResult = ToolCallSuccess | ToolCallError;
31
+ /**
32
+ * Definition of a single tool (tRPC procedure) available to the LLM
33
+ */
34
+ export type ToolDefinition = {
35
+ /** Fully qualified procedure name, e.g. "orders.list" */
36
+ name: string;
37
+ /** Human friendly label */
38
+ displayName?: string;
39
+ /** Description shown to the LLM as a tool description */
40
+ description?: string;
41
+ /** Optional JSON Schema for input args */
42
+ inputSchema?: JSONSchema;
43
+ /** Optional JSON Schema for output shape (not required in v1) */
44
+ outputSchema?: JSONSchema;
45
+ };
46
+ /**
47
+ * A manifest of available tools exposed to the chatbot
48
+ */
49
+ export type ToolManifest = {
50
+ tools: ToolDefinition[];
51
+ generated_at: string;
52
+ };
53
+ /**
54
+ * Information about a Next.js route
55
+ */
56
+ export type RouteInfo = {
57
+ path: string;
58
+ /** Human-readable title extracted from page metadata */
59
+ title?: string;
60
+ /** Description extracted from page metadata */
61
+ description?: string;
62
+ };
63
+ /**
64
+ * A manifest of available routes in the Next.js application
65
+ */
66
+ export type RouteManifest = {
67
+ routes: RouteInfo[];
68
+ generated_at: string;
69
+ };
70
+ /**
71
+ * Combined configuration for the chatbot including tools and routes
72
+ */
73
+ export type ChatConfig = {
74
+ tools: ToolManifest;
75
+ routes: RouteManifest;
76
+ };
77
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEjD;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,CAAC;CACf,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG;IAC5B,EAAE,EAAE,IAAI,CAAC;IACT,MAAM,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,EAAE,EAAE,KAAK,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG,eAAe,GAAG,aAAa,CAAC;AAE7D;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,yDAAyD;IACzD,IAAI,EAAE,MAAM,CAAC;IAEb,2BAA2B;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,yDAAyD;IACzD,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,0CAA0C;IAC1C,WAAW,CAAC,EAAE,UAAU,CAAC;IAEzB,iEAAiE;IACjE,YAAY,CAAC,EAAE,UAAU,CAAC;CAC3B,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,wDAAwD;IACxD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+CAA+C;IAC/C,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG;IACvB,KAAK,EAAE,YAAY,CAAC;IACpB,MAAM,EAAE,aAAa,CAAC;CACvB,CAAC"}
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@yak-io/trpc",
3
+ "version": "0.1.0",
4
+ "description": "tRPC adapter for yak chatbot - enables tRPC procedures as chatbot tools",
5
+ "type": "module",
6
+ "license": "SEE LICENSE IN LICENSE",
7
+ "author": "Yak <support@yak.io>",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/9f-au/yak.git",
11
+ "directory": "packages/trpc"
12
+ },
13
+ "publishConfig": {
14
+ "access": "public",
15
+ "provenance": false
16
+ },
17
+ "keywords": [
18
+ "yak",
19
+ "chatbot",
20
+ "ai",
21
+ "trpc",
22
+ "tools"
23
+ ],
24
+ "engines": {
25
+ "node": ">=18"
26
+ },
27
+ "files": [
28
+ "dist",
29
+ "LICENSE"
30
+ ],
31
+ "sideEffects": false,
32
+ "exports": {
33
+ ".": {
34
+ "types": "./dist/index.d.ts",
35
+ "import": "./dist/index.js"
36
+ },
37
+ "./package.json": "./package.json"
38
+ },
39
+ "dependencies": {
40
+ "@yak-io/javascript": "0.1.0"
41
+ },
42
+ "peerDependencies": {
43
+ "@trpc/server": "^10.0.0 || ^11.0.0",
44
+ "zod": "^4.0.0"
45
+ },
46
+ "devDependencies": {
47
+ "@trpc/server": "^11.8.0",
48
+ "@types/node": "^24.10.2",
49
+ "typescript": "^5.3.0",
50
+ "zod": "^4.1.13",
51
+ "@repo/typescript-config": "0.0.0"
52
+ },
53
+ "scripts": {
54
+ "build": "tsc",
55
+ "check-types": "tsc --noEmit"
56
+ }
57
+ }