@decocms/runtime 1.0.0-alpha.5 → 1.0.1

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/proxy.ts CHANGED
@@ -1,208 +1 @@
1
- /* oxlint-disable no-explicit-any */
2
- import type { ToolExecutionContext as _ToolExecutionContext } from "@mastra/core";
3
- import { convertJsonSchemaToZod } from "zod-from-json-schema";
4
- import { MCPConnection } from "./connection.ts";
5
- import { createServerClient } from "./mcp-client.ts";
6
- 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
-
16
- const safeParse = (content: string) => {
17
- try {
18
- return JSON.parse(content as string);
19
- } catch {
20
- return content;
21
- }
22
- };
23
-
24
- const toolsMap = new Map<
25
- string,
26
- Promise<
27
- Array<{
28
- name: string;
29
- inputSchema: any;
30
- outputSchema?: any;
31
- description: string;
32
- }>
33
- >
34
- >();
35
-
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
- /**
51
- * The base fetcher used to fetch the MCP from API.
52
- */
53
- export function createMCPClientProxy<T extends Record<string, unknown>>(
54
- options?: CreateStubAPIOptions,
55
- ): 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
- };
66
-
67
- return new Proxy<T>({} as T, {
68
- get(_, name) {
69
- if (name === "toJSON") {
70
- return null;
71
- }
72
- if (typeof name !== "string") {
73
- throw new Error("Name must be a string");
74
- }
75
- async function callToolFn(args: unknown) {
76
- const debugId = options?.debugId?.();
77
- const extraHeaders = debugId
78
- ? { "x-trace-debug-id": debugId }
79
- : undefined;
80
-
81
- // Create a connection with the tool name in the URL path for better logging
82
- // Only modify connections that have a URL property (HTTP, SSE, Websocket)
83
- // 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
-
104
- const { client, callStreamableTool } = await createServerClient(
105
- { connection: toolConnection },
106
- undefined,
107
- extraHeaders,
108
- );
109
-
110
- if (options?.streamable?.[String(name)]) {
111
- return callStreamableTool(String(name), args);
112
- }
113
-
114
- const { structuredContent, isError, content } = await client.callTool(
115
- {
116
- name: String(name),
117
- arguments: args as Record<string, unknown>,
118
- },
119
- undefined,
120
- {
121
- timeout: 3000000,
122
- },
123
- );
124
-
125
- if (isError) {
126
- // @ts-expect-error - content is not typed
127
- const maybeErrorMessage = content?.[0]?.text;
128
- const error =
129
- typeof maybeErrorMessage === "string"
130
- ? safeParse(maybeErrorMessage)
131
- : null;
132
-
133
- const throwableError =
134
- error?.code && typeof options?.getErrorByStatusCode === "function"
135
- ? options.getErrorByStatusCode(
136
- error.code,
137
- error.message,
138
- error.traceId,
139
- )
140
- : null;
141
-
142
- if (throwableError) {
143
- throw throwableError;
144
- }
145
-
146
- throw new Error(
147
- `Tool ${String(name)} returned an error: ${JSON.stringify(
148
- structuredContent ?? content,
149
- )}`,
150
- );
151
- }
152
- return structuredContent;
153
- }
154
-
155
- const listToolsFn = async () => {
156
- const { client } = await createServerClient({ connection });
157
- const { tools } = await client.listTools();
158
-
159
- return tools as {
160
- name: string;
161
- inputSchema: any;
162
- outputSchema?: any;
163
- description: string;
164
- }[];
165
- };
166
-
167
- async function listToolsOnce() {
168
- const conn = connection;
169
- const key = JSON.stringify(conn);
170
-
171
- try {
172
- if (!toolsMap.has(key)) {
173
- toolsMap.set(key, listToolsFn());
174
- }
175
-
176
- return await toolsMap.get(key)!;
177
- } catch (error) {
178
- console.error("Failed to list tools", error);
179
-
180
- toolsMap.delete(key);
181
- return;
182
- }
183
- }
184
- callToolFn.asTool = async () => {
185
- const tools = (await listToolsOnce()) ?? [];
186
- const tool = tools.find((t) => t.name === name);
187
- if (!tool) {
188
- throw new Error(`Tool ${name} not found`);
189
- }
190
-
191
- return {
192
- ...tool,
193
- id: tool.name,
194
- inputSchema: tool.inputSchema
195
- ? convertJsonSchemaToZod(tool.inputSchema)
196
- : undefined,
197
- outputSchema: tool.outputSchema
198
- ? convertJsonSchemaToZod(tool.outputSchema)
199
- : undefined,
200
- execute: (input: any) => {
201
- return callToolFn(input.context);
202
- },
203
- };
204
- };
205
- return callToolFn;
206
- },
207
- });
208
- }
1
+ export * from "@decocms/bindings/client";
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
- };