@decocms/runtime 0.0.1-testing-beta.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.
Files changed (85) hide show
  1. package/config-schema.json +553 -0
  2. package/dist/admin.d.ts +5 -0
  3. package/dist/admin.js +21 -0
  4. package/dist/admin.js.map +1 -0
  5. package/dist/bindings/deconfig/index.d.ts +9 -0
  6. package/dist/bindings/deconfig/index.js +9 -0
  7. package/dist/bindings/deconfig/index.js.map +1 -0
  8. package/dist/bindings/index.d.ts +1053 -0
  9. package/dist/bindings/index.js +132 -0
  10. package/dist/bindings/index.js.map +1 -0
  11. package/dist/chunk-4XSQKJLU.js +105 -0
  12. package/dist/chunk-4XSQKJLU.js.map +1 -0
  13. package/dist/chunk-AOFOWQXY.js +27 -0
  14. package/dist/chunk-AOFOWQXY.js.map +1 -0
  15. package/dist/chunk-F6XZPFWM.js +127 -0
  16. package/dist/chunk-F6XZPFWM.js.map +1 -0
  17. package/dist/chunk-IB3KGSMB.js +150 -0
  18. package/dist/chunk-IB3KGSMB.js.map +1 -0
  19. package/dist/chunk-NKUMVYKI.js +128 -0
  20. package/dist/chunk-NKUMVYKI.js.map +1 -0
  21. package/dist/chunk-NMXOC7PT.js +763 -0
  22. package/dist/chunk-NMXOC7PT.js.map +1 -0
  23. package/dist/chunk-OSSKGDAG.js +395 -0
  24. package/dist/chunk-OSSKGDAG.js.map +1 -0
  25. package/dist/chunk-UHR3BLMF.js +92 -0
  26. package/dist/chunk-UHR3BLMF.js.map +1 -0
  27. package/dist/client.d.ts +28 -0
  28. package/dist/client.js +4 -0
  29. package/dist/client.js.map +1 -0
  30. package/dist/connection-DDtQYrea.d.ts +30 -0
  31. package/dist/drizzle.d.ts +47 -0
  32. package/dist/drizzle.js +121 -0
  33. package/dist/drizzle.js.map +1 -0
  34. package/dist/index-AKVjfH4b.d.ts +336 -0
  35. package/dist/index-kMsI0ELb.d.ts +530 -0
  36. package/dist/index.d.ts +8 -0
  37. package/dist/index.js +507 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/mastra.d.ts +8 -0
  40. package/dist/mastra.js +5 -0
  41. package/dist/mastra.js.map +1 -0
  42. package/dist/mcp-Bv7IAgWX.d.ts +109 -0
  43. package/dist/mcp-client.d.ts +236 -0
  44. package/dist/mcp-client.js +3 -0
  45. package/dist/mcp-client.js.map +1 -0
  46. package/dist/proxy.d.ts +10 -0
  47. package/dist/proxy.js +4 -0
  48. package/dist/proxy.js.map +1 -0
  49. package/dist/resources.d.ts +362 -0
  50. package/dist/resources.js +3 -0
  51. package/dist/resources.js.map +1 -0
  52. package/dist/views.d.ts +72 -0
  53. package/dist/views.js +3 -0
  54. package/dist/views.js.map +1 -0
  55. package/package.json +98 -0
  56. package/src/admin.ts +16 -0
  57. package/src/auth.ts +233 -0
  58. package/src/bindings/README.md +132 -0
  59. package/src/bindings/binder.ts +143 -0
  60. package/src/bindings/channels.ts +54 -0
  61. package/src/bindings/deconfig/helpers.ts +107 -0
  62. package/src/bindings/deconfig/index.ts +1 -0
  63. package/src/bindings/deconfig/resources.ts +659 -0
  64. package/src/bindings/deconfig/types.ts +106 -0
  65. package/src/bindings/index.ts +61 -0
  66. package/src/bindings/resources/bindings.ts +99 -0
  67. package/src/bindings/resources/helpers.ts +95 -0
  68. package/src/bindings/resources/schemas.ts +265 -0
  69. package/src/bindings/utils.ts +22 -0
  70. package/src/bindings/views.ts +14 -0
  71. package/src/bindings.ts +179 -0
  72. package/src/client.ts +201 -0
  73. package/src/connection.ts +53 -0
  74. package/src/drizzle.ts +201 -0
  75. package/src/http-client-transport.ts +66 -0
  76. package/src/index.ts +394 -0
  77. package/src/mastra.ts +666 -0
  78. package/src/mcp-client.ts +119 -0
  79. package/src/mcp.ts +171 -0
  80. package/src/proxy.ts +204 -0
  81. package/src/resources.ts +168 -0
  82. package/src/state.ts +44 -0
  83. package/src/views.ts +26 -0
  84. package/src/well-known.ts +20 -0
  85. package/src/wrangler.ts +146 -0
package/src/auth.ts ADDED
@@ -0,0 +1,233 @@
1
+ import { JWK, jwtVerify } from "jose";
2
+ import type { DefaultEnv } from "./index.ts";
3
+
4
+ const DECO_APP_AUTH_COOKIE_NAME = "deco_page_auth";
5
+ const MAX_COOKIE_SIZE = 4000; // Leave some buffer below the 4096 limit
6
+
7
+ export interface State {
8
+ next?: string;
9
+ }
10
+
11
+ export const StateParser = {
12
+ parse: (state: string) => {
13
+ return JSON.parse(decodeURIComponent(atob(state))) as State;
14
+ },
15
+ stringify: (state: State) => {
16
+ return btoa(encodeURIComponent(JSON.stringify(state)));
17
+ },
18
+ };
19
+
20
+ // Helper function to chunk a value into multiple cookies
21
+ const chunkValue = (value: string): string[] => {
22
+ if (value.length <= MAX_COOKIE_SIZE) {
23
+ return [value];
24
+ }
25
+
26
+ const chunks: string[] = [];
27
+ for (let i = 0; i < value.length; i += MAX_COOKIE_SIZE) {
28
+ chunks.push(value.slice(i, i + MAX_COOKIE_SIZE));
29
+ }
30
+ return chunks;
31
+ };
32
+
33
+ // Helper function to reassemble chunked cookies
34
+ const reassembleChunkedCookies = (
35
+ cookies: Record<string, string>,
36
+ baseName: string,
37
+ ): string | undefined => {
38
+ // First try the base cookie (non-chunked)
39
+ if (cookies[baseName]) {
40
+ return cookies[baseName];
41
+ }
42
+
43
+ // Try to reassemble from chunks
44
+ const chunks: string[] = [];
45
+ let index = 0;
46
+
47
+ while (true) {
48
+ const chunkName = `${baseName}_${index}`;
49
+ if (!cookies[chunkName]) {
50
+ break;
51
+ }
52
+ chunks.push(cookies[chunkName]);
53
+ index++;
54
+ }
55
+
56
+ return chunks.length > 0 ? chunks.join("") : undefined;
57
+ };
58
+
59
+ // Helper function to parse cookies from request
60
+ const parseCookies = (cookieHeader: string): Record<string, string> => {
61
+ const cookies: Record<string, string> = {};
62
+ if (!cookieHeader) return cookies;
63
+
64
+ cookieHeader.split(";").forEach((cookie) => {
65
+ const [name, ...rest] = cookie.trim().split("=");
66
+ if (name && rest.length > 0) {
67
+ cookies[name] = decodeURIComponent(rest.join("="));
68
+ }
69
+ });
70
+
71
+ return cookies;
72
+ };
73
+
74
+ const parseJWK = (jwk: string): JWK => JSON.parse(atob(jwk)) as JWK;
75
+
76
+ export const getReqToken = async (req: Request, env: DefaultEnv) => {
77
+ const token = () => {
78
+ // First try to get token from Authorization header
79
+ const authHeader = req.headers.get("Authorization");
80
+ if (authHeader) {
81
+ return authHeader.split(" ")[1];
82
+ }
83
+
84
+ // If not found, try to get from cookie
85
+ const cookieHeader = req.headers.get("Cookie");
86
+ if (cookieHeader) {
87
+ const cookies = parseCookies(cookieHeader);
88
+ return reassembleChunkedCookies(cookies, DECO_APP_AUTH_COOKIE_NAME);
89
+ }
90
+
91
+ return undefined;
92
+ };
93
+
94
+ const authToken = token();
95
+ if (!authToken) {
96
+ return undefined;
97
+ }
98
+
99
+ env.DECO_API_JWT_PUBLIC_KEY &&
100
+ (await jwtVerify(authToken, parseJWK(env.DECO_API_JWT_PUBLIC_KEY), {
101
+ issuer: "https://api.decocms.com",
102
+ algorithms: ["RS256"],
103
+ typ: "JWT",
104
+ }).catch((err) => {
105
+ console.error(
106
+ `[auth-token]: error validating: ${err} ${env.DECO_API_JWT_PUBLIC_KEY}`,
107
+ );
108
+ }));
109
+
110
+ return authToken;
111
+ };
112
+
113
+ export interface AuthCallbackOptions {
114
+ apiUrl?: string;
115
+ appName: string;
116
+ }
117
+
118
+ export const handleAuthCallback = async (
119
+ req: Request,
120
+ options: AuthCallbackOptions,
121
+ ): Promise<Response> => {
122
+ const url = new URL(req.url);
123
+ const code = url.searchParams.get("code");
124
+ const state = url.searchParams.get("state");
125
+
126
+ if (!code) {
127
+ return new Response("Missing authorization code", { status: 400 });
128
+ }
129
+
130
+ // Parse state to get the next URL
131
+ let next = "/";
132
+ if (state) {
133
+ try {
134
+ const parsedState = StateParser.parse(state);
135
+ next = parsedState.next || "/";
136
+ } catch {
137
+ // ignore parse errors
138
+ }
139
+ }
140
+
141
+ try {
142
+ // Exchange code for token
143
+ const apiUrl = options.apiUrl ?? "https://api.decocms.com";
144
+ const exchangeResponse = await fetch(`${apiUrl}/apps/code-exchange`, {
145
+ method: "POST",
146
+ headers: {
147
+ "Content-Type": "application/json",
148
+ },
149
+ body: JSON.stringify({
150
+ code,
151
+ client_id: options.appName,
152
+ }),
153
+ });
154
+
155
+ if (!exchangeResponse.ok) {
156
+ console.error(
157
+ "authentication failed",
158
+ code,
159
+ options.appName,
160
+ await exchangeResponse.text().catch((_) => ""),
161
+ );
162
+ return new Response("Authentication failed", { status: 401 });
163
+ }
164
+
165
+ const { access_token } = (await exchangeResponse.json()) as {
166
+ access_token: string;
167
+ };
168
+
169
+ if (!access_token) {
170
+ return new Response("No access token received", { status: 401 });
171
+ }
172
+
173
+ // Chunk the token if it's too large
174
+ const chunks = chunkValue(access_token);
175
+ const headers = new Headers();
176
+ headers.set("Location", next);
177
+
178
+ // Set cookies for each chunk
179
+ if (chunks.length === 1) {
180
+ // Single cookie for small tokens
181
+ headers.set(
182
+ "Set-Cookie",
183
+ `${DECO_APP_AUTH_COOKIE_NAME}=${access_token}; HttpOnly; SameSite=None; Secure; Path=/`,
184
+ );
185
+ } else {
186
+ // Multiple cookies for large tokens
187
+ chunks.forEach((chunk, index) => {
188
+ headers.append(
189
+ "Set-Cookie",
190
+ `${DECO_APP_AUTH_COOKIE_NAME}_${index}=${chunk}; HttpOnly; SameSite=None; Secure; Path=/`,
191
+ );
192
+ });
193
+ }
194
+
195
+ return new Response(null, {
196
+ status: 302,
197
+ headers,
198
+ });
199
+ } catch (err) {
200
+ return new Response(`Authentication failed ${err}`, { status: 500 });
201
+ }
202
+ };
203
+
204
+ const removeAuthCookie = (headers: Headers) => {
205
+ // Clear the base cookie
206
+ headers.append(
207
+ "Set-Cookie",
208
+ `${DECO_APP_AUTH_COOKIE_NAME}=; HttpOnly; SameSite=None; Secure; Path=/; Max-Age=0`,
209
+ );
210
+
211
+ // Clear all potential chunked cookies
212
+ // We'll try to clear up to 10 chunks (which would support tokens up to 40KB)
213
+ // This is a reasonable upper limit
214
+ for (let i = 0; i < 10; i++) {
215
+ headers.append(
216
+ "Set-Cookie",
217
+ `${DECO_APP_AUTH_COOKIE_NAME}_${i}=; HttpOnly; SameSite=None; Secure; Path=/; Max-Age=0`,
218
+ );
219
+ }
220
+ };
221
+
222
+ export const handleLogout = (req: Request) => {
223
+ const url = new URL(req.url);
224
+ const next = url.searchParams.get("next");
225
+ const redirectTo = new URL("/", url);
226
+ const headers = new Headers();
227
+ removeAuthCookie(headers);
228
+ headers.set("Location", next ?? redirectTo.href);
229
+ return new Response(null, {
230
+ status: 302,
231
+ headers,
232
+ });
233
+ };
@@ -0,0 +1,132 @@
1
+ # Bindings
2
+
3
+ Bindings are a core concept for defining and enforcing standardized interfaces
4
+ that MCPs (Model Context Protocols) can implement. They provide a type-safe,
5
+ declarative way to specify what methods and schemas an integration (MCP) must
6
+ expose to be compatible with certain parts of the system, similar to how
7
+ TypeScript interfaces work.
8
+
9
+ ## Purpose
10
+
11
+ - **Standardization:** Bindings define contracts (schemas and method names) that
12
+ MCPs must implement to be considered compatible with a given integration
13
+ point.
14
+ - **Type Safety:** Bindings leverage Zod schemas and TypeScript types to ensure
15
+ correct data structures and method signatures.
16
+ - **Extensibility:** You can define new bindings for any use case, not just the
17
+ built-in triggers.
18
+
19
+ ## How Bindings Work
20
+
21
+ 1. **Define a Binding:**\
22
+ A binding is a list of required tool definitions (name, input/output schema).
23
+ 2. **Implement the Binding:**\
24
+ An MCP "implements" a binding by exposing all required tools with the correct
25
+ names and schemas.
26
+ 3. **Check Implementation:**\
27
+ The system can check if an MCP or a set of tools implements a binding using
28
+ helper functions.
29
+ 4. **Typed Client:**\
30
+ You can create a type-safe client for interacting with an MCP that implements
31
+ a binding.
32
+
33
+ ## Example: Trigger Bindings
34
+
35
+ - **Input Binding (`ON_AGENT_INPUT`):**\
36
+ Used for MCPs that handle incoming events, such as webhooks.
37
+
38
+ These are defined in [`trigger.ts`](./trigger.ts) and exported for use.
39
+
40
+ ## API
41
+
42
+ ### binder.ts
43
+
44
+ - `bindingClient(binder)`\
45
+ Creates a binding client for a given binding definition.
46
+ - `.implements(connectionOrTools)` — Checks if a connection or tool list
47
+ implements the binding.
48
+ - `.forConnection(mcpConnection)` — Returns a type-safe client for calling the
49
+ bound tools.
50
+
51
+ - `TriggerInputBinding`\
52
+ Predefined binding for agent input triggers.
53
+
54
+ ### utils.ts
55
+
56
+ - `Binding(binder)`\
57
+ Utility for checking if a set of tools implements a binding (by name).
58
+
59
+ ## Usage Example
60
+
61
+ ```ts
62
+ import { TriggerInputBinding } from "./bindings/trigger.ts";
63
+
64
+ // Check if a connection implements the input trigger binding
65
+ const isImplemented = await TriggerInputBinding.implements(connection);
66
+
67
+ // Create a client for a connection that implements the binding
68
+ const triggerClient = TriggerInputBinding.forConnection(connection);
69
+ await triggerClient.ON_AGENT_INPUT({ payload: ..., callbacks: ... });
70
+ ```
71
+
72
+ ## Creating a New Binding
73
+
74
+ To create a new binding:
75
+
76
+ 1. **Create a new file** in the `/bindings` folder (e.g., `my-binding.ts`).
77
+ 2. **Define your binding** using the `Binder` type and Zod schemas for
78
+ input/output:
79
+
80
+ ```ts
81
+ import { z } from "zod";
82
+ import type { Binder } from "../index.ts";
83
+
84
+ const myInputSchema = z.object({ ... });
85
+ const myOutputSchema = z.object({ ... });
86
+
87
+ export const MY_BINDING_SCHEMA = [{
88
+ name: "MY_BINDING_METHOD" as const,
89
+ inputSchema: myInputSchema,
90
+ outputSchema: myOutputSchema,
91
+ }] as const satisfies Binder;
92
+ ```
93
+
94
+ 3. **Export your binding** in `index.ts`:
95
+
96
+ ```ts
97
+ export * from "./my-binding.ts";
98
+ ```
99
+
100
+ 4. **Use your binding** in the UI or backend as needed.
101
+
102
+ ## Using Bindings in the UI
103
+
104
+ Bindings are integrated into the UI to allow users to select integrations that
105
+ implement a specific binding. For example:
106
+
107
+ - The
108
+ [`BindingSelector`](../../../apps/web/src/components/toolsets/binding-selector.tsx)
109
+ component lets users pick an integration that implements a given binding. It
110
+ uses the `Binding` utility to check if an integration's tools match the
111
+ binding.
112
+ - The
113
+ [`WebhookTriggerForm`](../../../apps/web/src/components/triggers/webhookTriggerForm.tsx)
114
+ uses `BindingSelector` to let users select an integration for the webhook
115
+ trigger, passing the `TRIGGER_INPUT_BINDING_SCHEMA` as the required binding.
116
+
117
+ This pattern allows you to:
118
+
119
+ - Ask the user to select an integration that implements a specific binding
120
+ through the UI
121
+ - Ensure only compatible integrations are selectable
122
+
123
+ ## Extending Bindings
124
+
125
+ You can define your own bindings by specifying the required tool names and
126
+ schemas, then use the same pattern to check and interact with MCPs that
127
+ implement them.
128
+
129
+ ---
130
+
131
+ For more details, see the code in this folder and the UI components that consume
132
+ bindings.
@@ -0,0 +1,143 @@
1
+ /* oxlint-disable no-explicit-any */
2
+ import { z } from "zod";
3
+ import type { MCPConnection } from "../connection.ts";
4
+ import { createPrivateTool } from "../mastra.ts";
5
+ import {
6
+ createMCPFetchStub,
7
+ type MCPClientFetchStub,
8
+ type ToolBinder,
9
+ } from "../mcp.ts";
10
+ import { CHANNEL_BINDING_SCHEMA } from "./channels.ts";
11
+ import { VIEW_BINDING_SCHEMA } from "./views.ts";
12
+
13
+ // ToolLike is a simplified version of the Tool interface that matches what we need for bindings
14
+ export interface ToolLike<
15
+ TName extends string = string,
16
+ TInput = any,
17
+ TReturn extends object | null | boolean = object,
18
+ > {
19
+ name: TName;
20
+ description: string;
21
+ inputSchema: z.ZodType<TInput>;
22
+ outputSchema?: z.ZodType<TReturn>;
23
+ handler: (props: TInput) => Promise<TReturn> | TReturn;
24
+ }
25
+
26
+ export type Binder<
27
+ TDefinition extends readonly ToolBinder[] = readonly ToolBinder[],
28
+ > = TDefinition;
29
+
30
+ export type BinderImplementation<
31
+ TBinder extends Binder<any>,
32
+ TContext = any,
33
+ > = TBinder extends Binder<infer TDefinition>
34
+ ? {
35
+ [K in keyof TDefinition]: Omit<
36
+ ToolLike<
37
+ TDefinition[K]["name"],
38
+ z.infer<TDefinition[K]["inputSchema"]>,
39
+ TDefinition[K] extends { outputSchema: infer Schema }
40
+ ? Schema extends z.ZodType
41
+ ? z.infer<Schema>
42
+ : never
43
+ : never
44
+ >,
45
+ "name" | "inputSchema" | "outputSchema" | "handler"
46
+ > & {
47
+ handler: (
48
+ props: z.infer<TDefinition[K]["inputSchema"]>,
49
+ c?: TContext,
50
+ ) => ReturnType<
51
+ ToolLike<
52
+ TDefinition[K]["name"],
53
+ z.infer<TDefinition[K]["inputSchema"]>,
54
+ TDefinition[K] extends { outputSchema: infer Schema }
55
+ ? Schema extends z.ZodType
56
+ ? z.infer<Schema>
57
+ : never
58
+ : never
59
+ >["handler"]
60
+ >;
61
+ };
62
+ }
63
+ : never;
64
+
65
+ export const bindingClient = <TDefinition extends readonly ToolBinder[]>(
66
+ binder: TDefinition,
67
+ ) => {
68
+ return {
69
+ implements: (tools: ToolBinder[]) => {
70
+ return binder.every(
71
+ (tool) =>
72
+ tool.opt === true || (tools ?? []).some((t) => t.name === tool.name),
73
+ );
74
+ },
75
+ forConnection: (
76
+ mcpConnection: MCPConnection,
77
+ ): MCPClientFetchStub<TDefinition> => {
78
+ const stub = createMCPFetchStub<TDefinition>({
79
+ connection: mcpConnection,
80
+ });
81
+ return new Proxy<MCPClientFetchStub<TDefinition>>(
82
+ {} as MCPClientFetchStub<TDefinition>,
83
+ {
84
+ get(_, name) {
85
+ if (typeof name !== "string") {
86
+ throw new Error("Name must be a string");
87
+ }
88
+
89
+ return (args: Record<string, unknown>) => {
90
+ return (
91
+ stub[name as keyof MCPClientFetchStub<TDefinition>] as (
92
+ args: Record<string, unknown>,
93
+ ) => Promise<unknown>
94
+ )(args);
95
+ };
96
+ },
97
+ },
98
+ );
99
+ },
100
+ };
101
+ };
102
+
103
+ export type MCPBindingClient<T extends ReturnType<typeof bindingClient>> =
104
+ ReturnType<T["forConnection"]>;
105
+
106
+ export const ChannelBinding = bindingClient(CHANNEL_BINDING_SCHEMA);
107
+
108
+ export const ViewBinding = bindingClient(VIEW_BINDING_SCHEMA);
109
+
110
+ export type { Callbacks } from "./channels.ts";
111
+
112
+ export const impl = <TBinder extends Binder>(
113
+ schema: TBinder,
114
+ implementation: BinderImplementation<TBinder>,
115
+ createToolFn = createPrivateTool,
116
+ ) => {
117
+ const impl: ReturnType<typeof createToolFn>[] = [];
118
+ for (const key in schema) {
119
+ const toolSchema = schema[key];
120
+ const toolImplementation = implementation[key];
121
+
122
+ if (toolSchema.opt && !toolImplementation) {
123
+ continue;
124
+ }
125
+
126
+ if (!toolImplementation) {
127
+ throw new Error(`Implementation for ${key} is required`);
128
+ }
129
+
130
+ const { name, handler, ...toolLike }: ToolLike = {
131
+ ...toolSchema,
132
+ ...toolImplementation,
133
+ };
134
+ impl.push(
135
+ createToolFn({
136
+ ...toolLike,
137
+ id: name,
138
+ execute: ({ context }) => Promise.resolve(handler(context)),
139
+ }),
140
+ );
141
+ }
142
+ return impl;
143
+ };
@@ -0,0 +1,54 @@
1
+ import { z } from "zod";
2
+ import type { ToolBinder } from "../mcp.ts";
3
+
4
+ const callbacksSchema = z.object({
5
+ stream: z.string(),
6
+ generate: z.string(),
7
+ generateObject: z.string(),
8
+ });
9
+
10
+ const channelIdSchema = z.object({
11
+ workspace: z.string(),
12
+ discriminator: z.string(),
13
+ });
14
+
15
+ const channelBindingSchema = channelIdSchema.extend({
16
+ agentId: z.string(),
17
+ agentName: z.string(),
18
+ agentLink: z.string(),
19
+ });
20
+
21
+ const joinChannelSchema = channelBindingSchema.extend({
22
+ callbacks: callbacksSchema,
23
+ });
24
+
25
+ const listChannelsSchema = z.object({
26
+ channels: z.array(
27
+ z.object({
28
+ label: z.string(),
29
+ value: z.string(),
30
+ }),
31
+ ),
32
+ });
33
+
34
+ export type Callbacks = z.infer<typeof callbacksSchema>;
35
+ export type JoinedChannelPayload = z.infer<typeof joinChannelSchema>;
36
+ export type ListChannelsSchema = z.infer<typeof listChannelsSchema>;
37
+ export const CHANNEL_BINDING_SCHEMA = [
38
+ {
39
+ name: "DECO_CHAT_CHANNELS_JOIN" as const,
40
+ inputSchema: joinChannelSchema,
41
+ outputSchema: z.any(),
42
+ },
43
+ {
44
+ name: "DECO_CHAT_CHANNELS_LEAVE" as const,
45
+ inputSchema: channelIdSchema,
46
+ outputSchema: z.any(),
47
+ },
48
+ {
49
+ name: "DECO_CHAT_CHANNELS_LIST" as const,
50
+ inputSchema: z.any(),
51
+ outputSchema: listChannelsSchema,
52
+ opt: true,
53
+ },
54
+ ] as const satisfies readonly ToolBinder[];
@@ -0,0 +1,107 @@
1
+ // Helper functions for DeconfigResource
2
+
3
+ export const normalizeDirectory = (dir: string) => {
4
+ // Ensure directory starts with / and doesn't end with /
5
+ const normalized = dir.startsWith("/") ? dir : `/${dir}`;
6
+ return normalized.endsWith("/") ? normalized.slice(0, -1) : normalized;
7
+ };
8
+
9
+ export const ResourcePath = {
10
+ build: (directory: string, resourceId: string) => {
11
+ const normalizedDir = normalizeDirectory(directory);
12
+ return `${normalizedDir}/${resourceId}.json`;
13
+ },
14
+ extract: (path: string) => {
15
+ const match = path.match(/^(.+)\/(.+)\.json$/);
16
+ if (!match) {
17
+ throw new Error("Invalid resource path");
18
+ }
19
+ return { directory: match[1], resourceId: match[2] };
20
+ },
21
+ };
22
+
23
+ export const ResourceUri = {
24
+ build: (integrationId: string, resourceName: string, resourceId: string) => {
25
+ return `rsc://${integrationId}/${resourceName}/${resourceId}`;
26
+ },
27
+ unwind: (uri: string) => {
28
+ const match = uri.match(/^rsc:\/\/[^/]+\/([^/]+)\/(.+)$/);
29
+ if (!match) {
30
+ throw new Error("Invalid Resources 2.0 URI format");
31
+ }
32
+ return { resourceName: match[1], resourceId: match[2] };
33
+ },
34
+ };
35
+
36
+ export function getMetadataValue(metadata: unknown, key: string): unknown {
37
+ if (!metadata || typeof metadata !== "object") return undefined;
38
+ const metaObj = metadata as Record<string, unknown>;
39
+ if (key in metaObj) return metaObj[key];
40
+ const nested = metaObj.metadata;
41
+ if (nested && typeof nested === "object" && key in nested) {
42
+ return (nested as Record<string, unknown>)[key];
43
+ }
44
+ return undefined;
45
+ }
46
+
47
+ export function getMetadataString(
48
+ metadata: unknown,
49
+ key: string,
50
+ ): string | undefined {
51
+ const value = getMetadataValue(metadata, key);
52
+ return typeof value === "string" ? value : undefined;
53
+ }
54
+
55
+ export const toAsyncIterator = <T>(
56
+ emitter: EventSource,
57
+ eventType: string = "message",
58
+ ): AsyncIterable<T> => {
59
+ const queue: T[] = [];
60
+ let done = false;
61
+ let waitPromise: ((data?: T) => void) | null = null;
62
+
63
+ const triggerLoop = () => {
64
+ if (waitPromise) {
65
+ waitPromise();
66
+ waitPromise = null;
67
+ }
68
+ };
69
+
70
+ const messageHandler = (data: MessageEvent) => {
71
+ try {
72
+ queue.push(JSON.parse(data.data));
73
+ } catch {
74
+ // Silently ignore malformed data or optionally log error
75
+ return;
76
+ }
77
+ triggerLoop();
78
+ };
79
+
80
+ const errorHandler = () => {
81
+ done = true;
82
+ triggerLoop();
83
+ };
84
+
85
+ emitter.addEventListener(eventType, messageHandler);
86
+ emitter.addEventListener("error", errorHandler);
87
+
88
+ return {
89
+ async *[Symbol.asyncIterator]() {
90
+ try {
91
+ while (true) {
92
+ const value = queue.shift();
93
+ if (value) {
94
+ yield value;
95
+ } else {
96
+ if (done) return;
97
+ await new Promise((resolve) => (waitPromise = resolve));
98
+ }
99
+ }
100
+ } finally {
101
+ emitter.removeEventListener(eventType, messageHandler);
102
+ emitter.removeEventListener("error", errorHandler);
103
+ emitter.close();
104
+ }
105
+ },
106
+ };
107
+ };
@@ -0,0 +1 @@
1
+ export * from "./resources.ts";