@decocms/runtime 1.0.0-alpha.10 → 1.0.0-alpha.11

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/src/index.ts CHANGED
@@ -2,26 +2,13 @@
2
2
  import type { ExecutionContext } from "@cloudflare/workers-types";
3
3
  import { decodeJwt } from "jose";
4
4
  import type { z } from "zod";
5
- import {
6
- getReqToken,
7
- handleAuthCallback,
8
- handleLogout,
9
- StateParser,
10
- } from "./auth.ts";
11
- import {
12
- createContractBinding,
13
- createIntegrationBinding,
14
- workspaceClient,
15
- } from "./bindings.ts";
16
- import { DeconfigResource } from "./bindings/deconfig/index.ts";
17
- import { DECO_MCP_CLIENT_HEADER } from "./client.ts";
5
+ import { createContractBinding, createIntegrationBinding } from "./bindings.ts";
6
+ import { State } from "./state.ts";
18
7
  import {
19
8
  createMCPServer,
20
9
  type CreateMCPServerOptions,
21
10
  MCPServer,
22
- } from "./mastra.ts";
23
- import { MCPClient, type QueryResult } from "./mcp.ts";
24
- import { State } from "./state.ts";
11
+ } from "./tools.ts";
25
12
  import type { Binding, ContractBinding, MCPBinding } from "./wrangler.ts";
26
13
  export { proxyConnectionForId } from "./bindings.ts";
27
14
  export {
@@ -29,28 +16,15 @@ export {
29
16
  type CreateStubAPIOptions,
30
17
  type ToolBinder,
31
18
  } from "./mcp.ts";
32
- export interface WorkspaceDB {
33
- query: (params: {
34
- sql: string;
35
- params: string[];
36
- }) => Promise<{ result: QueryResult[] }>;
37
- }
38
19
 
39
20
  export interface DefaultEnv<TSchema extends z.ZodTypeAny = any> {
40
- DECO_REQUEST_CONTEXT: RequestContext<TSchema>;
41
- DECO_APP_NAME: string;
42
- DECO_APP_SLUG: string;
43
- DECO_APP_ENTRYPOINT: string;
44
- DECO_API_URL?: string;
45
- DECO_WORKSPACE: string;
46
- DECO_API_JWT_PUBLIC_KEY: string;
47
- DECO_APP_DEPLOYMENT_ID: string;
48
- DECO_BINDINGS: string;
49
- DECO_API_TOKEN: string;
50
- DECO_WORKSPACE_DB: WorkspaceDB & {
51
- forContext: (ctx: RequestContext) => WorkspaceDB;
52
- };
21
+ MESH_REQUEST_CONTEXT: RequestContext<TSchema>;
22
+ MESH_BINDINGS: string;
23
+ MESH_APP_DEPLOYMENT_ID: string;
53
24
  IS_LOCAL: boolean;
25
+ MESH_URL?: string;
26
+ MESH_RUNTIME_TOKEN?: string;
27
+ MESH_APP_NAME?: string;
54
28
  [key: string]: unknown;
55
29
  }
56
30
 
@@ -58,7 +32,7 @@ export interface BindingsObject {
58
32
  bindings?: Binding[];
59
33
  }
60
34
 
61
- export const WorkersMCPBindings = {
35
+ export const MCPBindings = {
62
36
  parse: (bindings?: string): Binding[] => {
63
37
  if (!bindings) return [];
64
38
  try {
@@ -104,14 +78,13 @@ export interface User {
104
78
 
105
79
  export interface RequestContext<TSchema extends z.ZodTypeAny = any> {
106
80
  state: z.infer<TSchema>;
107
- branch?: string;
108
81
  token: string;
109
- workspace: string;
82
+ meshUrl: string;
110
83
  ensureAuthenticated: (options?: {
111
84
  workspaceHint?: string;
112
85
  }) => User | undefined;
113
86
  callerApp?: string;
114
- integrationId?: string;
87
+ connectionId?: string;
115
88
  }
116
89
 
117
90
  // 2. Map binding type to its creator function
@@ -131,26 +104,12 @@ const creatorByType: CreatorByType = {
131
104
  const withDefaultBindings = ({
132
105
  env,
133
106
  server,
134
- ctx,
135
107
  url,
136
108
  }: {
137
109
  env: DefaultEnv;
138
110
  server: MCPServer<any, any>;
139
- ctx: RequestContext;
140
111
  url?: string;
141
112
  }) => {
142
- const client = workspaceClient(ctx, env.DECO_API_URL);
143
- const createWorkspaceDB = (ctx: RequestContext): WorkspaceDB => {
144
- const client = workspaceClient(ctx, env.DECO_API_URL);
145
- return {
146
- query: ({ sql, params }) => {
147
- return client.DATABASES_RUN_SQL({
148
- sql,
149
- params,
150
- });
151
- },
152
- };
153
- };
154
113
  env["SELF"] = new Proxy(
155
114
  {},
156
115
  {
@@ -169,15 +128,6 @@ const withDefaultBindings = ({
169
128
  },
170
129
  );
171
130
 
172
- const workspaceDbBinding = {
173
- ...createWorkspaceDB(ctx),
174
- forContext: createWorkspaceDB,
175
- };
176
-
177
- env["DECO_API"] = MCPClient;
178
- env["DECO_WORKSPACE_API"] = client;
179
- env["DECO_WORKSPACE_DB"] = workspaceDbBinding;
180
-
181
131
  env["IS_LOCAL"] =
182
132
  (url?.startsWith("http://localhost") ||
183
133
  url?.startsWith("http://127.0.0.1")) ??
@@ -194,13 +144,9 @@ export class UnauthorizedError extends Error {
194
144
  }
195
145
  }
196
146
 
197
- const AUTH_CALLBACK_ENDPOINT = "/oauth/callback";
198
- const AUTH_START_ENDPOINT = "/oauth/start";
199
- const AUTH_LOGOUT_ENDPOINT = "/oauth/logout";
200
- const AUTHENTICATED = (user?: unknown, workspace?: string) => () => {
147
+ const AUTHENTICATED = (user?: unknown) => () => {
201
148
  return {
202
149
  ...((user as User) ?? {}),
203
- workspace,
204
150
  } as User;
205
151
  };
206
152
 
@@ -208,66 +154,55 @@ export const withBindings = <TEnv>({
208
154
  env: _env,
209
155
  server,
210
156
  tokenOrContext,
211
- origin,
212
157
  url,
213
- branch,
214
158
  }: {
215
159
  env: TEnv;
216
160
  server: MCPServer<TEnv, any>;
217
161
  tokenOrContext?: string | RequestContext;
218
- origin?: string | null;
219
162
  url?: string;
220
- branch?: string | null;
221
163
  }): TEnv => {
222
- branch ??= undefined;
223
164
  const env = _env as DefaultEnv<any>;
224
165
 
225
- const apiUrl = env.DECO_API_URL ?? "https://api.decocms.com";
226
166
  let context;
227
167
  if (typeof tokenOrContext === "string") {
228
168
  const decoded = decodeJwt(tokenOrContext);
229
- const workspace = decoded.aud as string;
169
+ // Support both new JWT format (fields directly on payload) and legacy format (nested in metadata)
170
+ const metadata =
171
+ (decoded.metadata as {
172
+ state?: Record<string, unknown>;
173
+ meshUrl?: string;
174
+ connectionId?: string;
175
+ }) ?? {};
230
176
 
231
177
  context = {
232
- state: decoded.state as Record<string, unknown>,
178
+ state: decoded.state ?? metadata.state,
233
179
  token: tokenOrContext,
234
- integrationId: decoded.integrationId as string,
235
- workspace,
236
- ensureAuthenticated: AUTHENTICATED(decoded.user, workspace),
237
- branch,
180
+ meshUrl: (decoded.meshUrl as string) ?? metadata.meshUrl,
181
+ connectionId: (decoded.connectionId as string) ?? metadata.connectionId,
182
+ ensureAuthenticated: AUTHENTICATED(decoded.user ?? decoded.sub),
238
183
  } as RequestContext<any>;
239
184
  } else if (typeof tokenOrContext === "object") {
240
185
  context = tokenOrContext;
241
186
  const decoded = decodeJwt(tokenOrContext.token);
242
- const workspace = decoded.aud as string;
187
+ // Support both new JWT format (fields directly on payload) and legacy format (nested in metadata)
188
+ const metadata =
189
+ (decoded.metadata as {
190
+ state?: Record<string, unknown>;
191
+ meshUrl?: string;
192
+ connectionId?: string;
193
+ }) ?? {};
243
194
  const appName = decoded.appName as string | undefined;
244
195
  context.callerApp = appName;
245
- context.integrationId ??= decoded.integrationId as string;
246
- context.ensureAuthenticated = AUTHENTICATED(decoded.user, workspace);
196
+ context.connectionId ??=
197
+ (decoded.connectionId as string) ?? metadata.connectionId;
198
+ context.ensureAuthenticated = AUTHENTICATED(decoded.user ?? decoded.sub);
247
199
  } else {
248
- context = {
249
- state: undefined,
250
- token: env.DECO_API_TOKEN,
251
- workspace: env.DECO_WORKSPACE,
252
- branch,
253
- ensureAuthenticated: (options?: { workspaceHint?: string }) => {
254
- const workspaceHint = options?.workspaceHint ?? env.DECO_WORKSPACE;
255
- const authUri = new URL("/apps/oauth", apiUrl);
256
- authUri.searchParams.set("client_id", env.DECO_APP_NAME);
257
- authUri.searchParams.set(
258
- "redirect_uri",
259
- new URL(AUTH_CALLBACK_ENDPOINT, origin ?? env.DECO_APP_ENTRYPOINT)
260
- .href,
261
- );
262
- workspaceHint &&
263
- authUri.searchParams.set("workspace_hint", workspaceHint);
264
- throw new UnauthorizedError("Unauthorized", authUri);
265
- },
266
- };
200
+ // should not reach here
201
+ throw new Error("Invalid token or context");
267
202
  }
268
203
 
269
- env.DECO_REQUEST_CONTEXT = context;
270
- const bindings = WorkersMCPBindings.parse(env.DECO_BINDINGS);
204
+ env.MESH_REQUEST_CONTEXT = context;
205
+ const bindings = MCPBindings.parse(env.MESH_BINDINGS);
271
206
 
272
207
  for (const binding of bindings) {
273
208
  env[binding.name] = creatorByType[binding.type](binding as any, env);
@@ -276,7 +211,6 @@ export const withBindings = <TEnv>({
276
211
  withDefaultBindings({
277
212
  env,
278
213
  server,
279
- ctx: env.DECO_REQUEST_CONTEXT,
280
214
  url,
281
215
  });
282
216
 
@@ -293,21 +227,6 @@ export const withRuntime = <TEnv, TSchema extends z.ZodTypeAny = never>(
293
227
  ctx: ExecutionContext,
294
228
  ) => {
295
229
  const url = new URL(req.url);
296
- if (url.pathname === AUTH_CALLBACK_ENDPOINT) {
297
- return handleAuthCallback(req, {
298
- apiUrl: env.DECO_API_URL,
299
- appName: env.DECO_APP_NAME,
300
- });
301
- }
302
- if (url.pathname === AUTH_START_ENDPOINT) {
303
- env.DECO_REQUEST_CONTEXT.ensureAuthenticated();
304
- const redirectTo = new URL("/", url);
305
- const next = url.searchParams.get("next");
306
- return Response.redirect(next ?? redirectTo, 302);
307
- }
308
- if (url.pathname === AUTH_LOGOUT_ENDPOINT) {
309
- return handleLogout(req);
310
- }
311
230
  if (url.pathname === "/mcp") {
312
231
  return server.fetch(req, env, ctx);
313
232
  }
@@ -334,9 +253,6 @@ export const withRuntime = <TEnv, TSchema extends z.ZodTypeAny = never>(
334
253
  });
335
254
  }
336
255
 
337
- if (url.pathname.startsWith(DeconfigResource.WatchPathNameBase)) {
338
- return DeconfigResource.watchAPI(req, env);
339
- }
340
256
  return (
341
257
  userFns.fetch?.(req, env, ctx) ||
342
258
  new Response("Not found", { status: 404 })
@@ -348,41 +264,16 @@ export const withRuntime = <TEnv, TSchema extends z.ZodTypeAny = never>(
348
264
  env: TEnv & DefaultEnv<TSchema>,
349
265
  ctx: ExecutionContext,
350
266
  ) => {
351
- const referer = req.headers.get("referer");
352
- const isFetchRequest = req.headers.has(DECO_MCP_CLIENT_HEADER);
353
-
354
- try {
355
- const bindings = withBindings({
356
- env,
357
- server,
358
- branch:
359
- req.headers.get("x-deco-branch") ??
360
- new URL(req.url).searchParams.get("__b"),
361
- tokenOrContext: await getReqToken(req, env),
362
- origin:
363
- referer ?? req.headers.get("origin") ?? new URL(req.url).origin,
364
- url: req.url,
365
- });
366
- return await State.run(
367
- { req, env: bindings, ctx },
368
- async () => await fetcher(req, bindings, ctx),
369
- );
370
- } catch (error) {
371
- if (error instanceof UnauthorizedError) {
372
- if (!isFetchRequest) {
373
- const url = new URL(req.url);
374
- error.redirectTo.searchParams.set(
375
- "state",
376
- StateParser.stringify({
377
- next: url.searchParams.get("next") ?? referer ?? req.url,
378
- }),
379
- );
380
- return Response.redirect(error.redirectTo, 302);
381
- }
382
- return new Response(null, { status: 401 });
383
- }
384
- throw error;
385
- }
267
+ const bindings = withBindings({
268
+ env,
269
+ server,
270
+ tokenOrContext: req.headers.get("x-mesh-token") ?? undefined,
271
+ url: req.url,
272
+ });
273
+ return await State.run(
274
+ { req, env: bindings, ctx },
275
+ async () => await fetcher(req, bindings, ctx),
276
+ );
386
277
  },
387
278
  };
388
279
  };
package/src/mcp.ts CHANGED
@@ -1,165 +1,12 @@
1
1
  /* oxlint-disable no-explicit-any */
2
- import { z } from "zod";
3
- import type { MCPConnection } from "./connection.ts";
4
- import { createMCPClientProxy } from "./proxy.ts";
5
2
  import type { ToolBinder } from "@decocms/bindings";
6
-
7
- export interface FetchOptions extends RequestInit {
8
- path?: string;
9
- segments?: string[];
10
- }
11
-
12
- const Timings = z.object({
13
- sql_duration_ms: z.number().optional(),
14
- });
15
-
16
- const Meta = z.object({
17
- changed_db: z.boolean().optional(),
18
- changes: z.number().optional(),
19
- duration: z.number().optional(),
20
- last_row_id: z.number().optional(),
21
- rows_read: z.number().optional(),
22
- rows_written: z.number().optional(),
23
- served_by_primary: z.boolean().optional(),
24
- served_by_region: z
25
- .enum(["WNAM", "ENAM", "WEUR", "EEUR", "APAC", "OC"])
26
- .optional(),
27
- size_after: z.number().optional(),
28
- timings: Timings.optional(),
29
- });
30
-
31
- const QueryResult = z.object({
32
- meta: Meta.optional(),
33
- results: z.array(z.unknown()).optional(),
34
- success: z.boolean().optional(),
35
- });
36
-
37
- export type QueryResult = z.infer<typeof QueryResult>;
38
-
39
- const workspaceTools = [
40
- {
41
- name: "INTEGRATIONS_GET" as const,
42
- inputSchema: z.object({
43
- id: z.string(),
44
- }),
45
- outputSchema: z.object({
46
- connection: z.object({}),
47
- }),
48
- },
49
- {
50
- name: "DATABASES_RUN_SQL" as const,
51
- inputSchema: z.object({
52
- sql: z.string().describe("The SQL query to run"),
53
- params: z
54
- .array(z.string())
55
- .describe("The parameters to pass to the SQL query"),
56
- }),
57
- outputSchema: z.object({
58
- result: z.array(QueryResult),
59
- }),
60
- },
61
- ] satisfies ToolBinder<string, unknown, object>[];
62
-
63
- // Default fetcher instance with API_SERVER_URL and API_HEADERS
64
- const global = createMCPFetchStub<[]>({});
65
- export const MCPClient = new Proxy(
66
- {} as typeof global & {
67
- forWorkspace: (
68
- workspace: string,
69
- token?: string,
70
- decoCmsApiUrl?: string,
71
- ) => MCPClientFetchStub<typeof workspaceTools>;
72
- forConnection: <TDefinition extends readonly ToolBinder[]>(
73
- connection: MCPConnectionProvider,
74
- decoCmsApiUrl?: string,
75
- ) => MCPClientFetchStub<TDefinition>;
76
- },
77
- {
78
- get(_, name) {
79
- if (name === "toJSON") {
80
- return null;
81
- }
82
-
83
- if (name === "forWorkspace") {
84
- return (workspace: string, token?: string, decoCmsApiUrl?: string) =>
85
- createMCPFetchStub<[]>({
86
- workspace,
87
- token,
88
- decoCmsApiUrl,
89
- });
90
- }
91
- if (name === "forConnection") {
92
- return <TDefinition extends readonly ToolBinder[]>(
93
- connection: MCPConnectionProvider,
94
- decoCmsApiUrl?: string,
95
- ) =>
96
- createMCPFetchStub<TDefinition>({
97
- connection,
98
- decoCmsApiUrl,
99
- });
100
- }
101
- return global[name as keyof typeof global];
102
- },
103
- },
104
- );
3
+ export {
4
+ createMCPFetchStub,
5
+ isStreamableToolBinder,
6
+ MCPClient,
7
+ type CreateStubAPIOptions,
8
+ type MCPClientFetchStub,
9
+ type MCPClientStub,
10
+ } from "@decocms/bindings/client"; // Default fetcher instance with API_SERVER_URL and API_HEADERS
105
11
 
106
12
  export type { ToolBinder };
107
-
108
- export type MCPClientStub<TDefinition extends readonly ToolBinder[]> = {
109
- [K in TDefinition[number] as K["name"]]: K extends ToolBinder<
110
- string,
111
- infer TInput,
112
- infer TReturn
113
- >
114
- ? (params: TInput, init?: RequestInit) => Promise<TReturn>
115
- : never;
116
- };
117
-
118
- export type MCPClientFetchStub<TDefinition extends readonly ToolBinder[]> = {
119
- [K in TDefinition[number] as K["name"]]: K["streamable"] extends true
120
- ? K extends ToolBinder<string, infer TInput, any, true>
121
- ? (params: TInput, init?: RequestInit) => Promise<Response>
122
- : never
123
- : K extends ToolBinder<string, infer TInput, infer TReturn, any>
124
- ? (params: TInput, init?: RequestInit) => Promise<Awaited<TReturn>>
125
- : never;
126
- };
127
-
128
- export type MCPConnectionProvider = MCPConnection;
129
-
130
- export interface MCPClientRaw {
131
- callTool: (tool: string, args: unknown) => Promise<unknown>;
132
- listTools: () => Promise<
133
- {
134
- name: string;
135
- inputSchema: any;
136
- outputSchema?: any;
137
- description: string;
138
- }[]
139
- >;
140
- }
141
- export type JSONSchemaToZodConverter = (jsonSchema: any) => z.ZodTypeAny;
142
- export interface CreateStubAPIOptions {
143
- mcpPath?: string;
144
- decoCmsApiUrl?: string;
145
- workspace?: string;
146
- token?: string;
147
- connection?: MCPConnectionProvider;
148
- streamable?: Record<string, boolean>;
149
- debugId?: () => string;
150
- getErrorByStatusCode?: (
151
- statusCode: number,
152
- message?: string,
153
- traceId?: string,
154
- errorObject?: unknown,
155
- ) => Error;
156
- supportsToolName?: boolean;
157
- }
158
-
159
- export function createMCPFetchStub<TDefinition extends readonly ToolBinder[]>(
160
- options?: CreateStubAPIOptions,
161
- ): MCPClientFetchStub<TDefinition> {
162
- return createMCPClientProxy<MCPClientFetchStub<TDefinition>>({
163
- ...(options ?? {}),
164
- });
165
- }
package/src/proxy.ts CHANGED
@@ -1,17 +1,8 @@
1
1
  /* oxlint-disable no-explicit-any */
2
- import type { ToolExecutionContext as _ToolExecutionContext } from "@mastra/core";
3
2
  import { convertJsonSchemaToZod } from "zod-from-json-schema";
4
3
  import { MCPConnection } from "./connection.ts";
5
4
  import { createServerClient } from "./mcp-client.ts";
6
5
  import type { CreateStubAPIOptions } from "./mcp.ts";
7
- import { WELL_KNOWN_API_HOSTNAMES } from "./well-known.ts";
8
-
9
- const getWorkspace = (workspace?: string) => {
10
- if (workspace && workspace.length > 0 && !workspace.includes("/")) {
11
- return `/shared/${workspace}`;
12
- }
13
- return workspace ?? "";
14
- };
15
6
 
16
7
  const safeParse = (content: string) => {
17
8
  try {
@@ -33,36 +24,13 @@ const toolsMap = new Map<
33
24
  >
34
25
  >();
35
26
 
36
- /**
37
- * Determines if a given URL supports tool names in the path.
38
- * Our APIs (api.decocms.com, api.deco.chat, localhost) support /tool/${toolName} routing.
39
- * Third-party APIs typically don't support this pattern.
40
- */
41
- function supportsToolNameInPath(url: string): boolean {
42
- try {
43
- // Our main APIs that support /tool/${toolName} routing
44
- return WELL_KNOWN_API_HOSTNAMES.includes(new URL(url).hostname);
45
- } catch {
46
- return false;
47
- }
48
- }
49
-
50
27
  /**
51
28
  * The base fetcher used to fetch the MCP from API.
52
29
  */
53
30
  export function createMCPClientProxy<T extends Record<string, unknown>>(
54
- options?: CreateStubAPIOptions,
31
+ options: CreateStubAPIOptions,
55
32
  ): T {
56
- const mcpPath = options?.mcpPath ?? "/mcp";
57
-
58
- const connection: MCPConnection = options?.connection || {
59
- type: "HTTP",
60
- token: options?.token,
61
- url: new URL(
62
- `${getWorkspace(options?.workspace)}${mcpPath}`,
63
- options?.decoCmsApiUrl ?? `https://api.decocms.com`,
64
- ).href,
65
- };
33
+ const connection: MCPConnection = options.connection;
66
34
 
67
35
  return new Proxy<T>({} as T, {
68
36
  get(_, name) {
@@ -81,28 +49,9 @@ export function createMCPClientProxy<T extends Record<string, unknown>>(
81
49
  // Create a connection with the tool name in the URL path for better logging
82
50
  // Only modify connections that have a URL property (HTTP, SSE, Websocket)
83
51
  // Use automatic detection based on URL, with optional override
84
- let toolConnection = connection;
85
- const shouldAddToolName =
86
- options?.supportsToolName ??
87
- ("url" in connection &&
88
- typeof connection.url === "string" &&
89
- supportsToolNameInPath(connection.url));
90
-
91
- if (
92
- shouldAddToolName &&
93
- "url" in connection &&
94
- typeof connection.url === "string"
95
- ) {
96
- toolConnection = {
97
- ...connection,
98
- url: connection.url.endsWith("/")
99
- ? `${connection.url}tool/${String(name)}`
100
- : `${connection.url}/tool/${String(name)}`,
101
- };
102
- }
103
52
 
104
53
  const { client, callStreamableTool } = await createServerClient(
105
- { connection: toolConnection },
54
+ { connection },
106
55
  undefined,
107
56
  extraHeaders,
108
57
  );
package/src/state.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  import { AsyncLocalStorage } from "node:async_hooks";
2
- import { z } from "zod";
3
- import type { AppContext } from "./mastra.ts";
4
- import { createTool } from "./mastra.ts";
2
+ import { DefaultEnv } from "./index.ts";
3
+ import type { AppContext } from "./tools.ts";
5
4
 
6
5
  const asyncLocalStorage = new AsyncLocalStorage<AppContext | undefined>();
7
6
 
@@ -9,36 +8,9 @@ export const State = {
9
8
  getStore: () => {
10
9
  return asyncLocalStorage.getStore();
11
10
  },
12
- run: <TEnv, R, TArgs extends unknown[]>(
11
+ run: <TEnv extends DefaultEnv, R, TArgs extends unknown[]>(
13
12
  ctx: AppContext<TEnv>,
14
13
  f: (...args: TArgs) => R,
15
14
  ...args: TArgs
16
15
  ): R => asyncLocalStorage.run(ctx, f, ...args),
17
16
  };
18
-
19
- export interface ValidationPayload {
20
- state: unknown;
21
- }
22
-
23
- export const createStateValidationTool = (stateSchema?: z.ZodTypeAny) => {
24
- return createTool({
25
- id: "DECO_CHAT_STATE_VALIDATION",
26
- description: "Validate the state of the OAuth flow",
27
- inputSchema: z.object({
28
- state: z.unknown(),
29
- }),
30
- outputSchema: z.object({
31
- valid: z.boolean(),
32
- }),
33
- execute: (ctx) => {
34
- if (!stateSchema) {
35
- return Promise.resolve({ valid: true });
36
- }
37
- const parsed = stateSchema.safeParse(ctx.context.state);
38
- return Promise.resolve({
39
- valid: parsed.success,
40
- reason: parsed.error?.message,
41
- });
42
- },
43
- });
44
- };