@decocms/runtime 1.2.11 → 1.2.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decocms/runtime",
3
- "version": "1.2.11",
3
+ "version": "1.2.13",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "check": "tsc --noEmit",
@@ -9,7 +9,7 @@
9
9
  "dependencies": {
10
10
  "@cloudflare/workers-types": "^4.20250617.0",
11
11
  "@decocms/bindings": "^1.0.7",
12
- "@modelcontextprotocol/sdk": "1.26.0",
12
+ "@modelcontextprotocol/sdk": "1.27.1",
13
13
  "@ai-sdk/provider": "^3.0.0",
14
14
  "hono": "^4.10.7",
15
15
  "jose": "^6.0.11",
@@ -21,11 +21,16 @@
21
21
  "./client": "./src/client.ts",
22
22
  "./bindings": "./src/bindings/index.ts",
23
23
  "./asset-server": "./src/asset-server/index.ts",
24
- "./tools": "./src/tools.ts"
24
+ "./tools": "./src/tools.ts",
25
+ "./decopilot": "./src/decopilot.ts"
26
+ },
27
+ "peerDependencies": {
28
+ "ai": ">=6.0.0"
25
29
  },
26
30
  "devDependencies": {
27
31
  "@types/bun": "^1.3.5",
28
32
  "@types/mime-db": "^1.43.6",
33
+ "ai": "^6.0.116",
29
34
  "ts-json-schema-generator": "^2.4.0"
30
35
  },
31
36
  "engines": {
@@ -33,7 +38,7 @@
33
38
  },
34
39
  "repository": {
35
40
  "type": "git",
36
- "url": "git+https://github.com/decocms/mesh.git",
41
+ "url": "git+https://github.com/decocms/studio.git",
37
42
  "directory": "packages/runtime"
38
43
  },
39
44
  "publishConfig": {
package/src/bindings.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { CollectionBinding } from "@decocms/bindings/collections";
2
2
  import type { MCPConnection } from "./connection.ts";
3
+ import type { AgentBindingConfig, ResolvedAgentClient } from "./decopilot.ts";
3
4
  import type { RequestContext } from "./index.ts";
4
5
  import { type MCPClientFetchStub, MCPClient, type ToolBinder } from "./mcp.ts";
5
6
  import { z } from "zod";
@@ -29,18 +30,147 @@ export interface Binding<TType extends string = string> {
29
30
  export type BindingRegistry = Record<string, readonly ToolBinder[]>;
30
31
 
31
32
  /**
32
- * Function that returns Zod Schema
33
+ * Maps binding type names (e.g. "@deco/cms-admin") to their ToolBinder definitions.
34
+ * Populated by BindingOf() when a binding argument is provided.
35
+ * Consumed by injectBindingSchemas() to embed __binding in the JSON Schema
36
+ * without polluting the Zod schema (which becomes saved state → JWT).
37
+ */
38
+ const _bindingMetadata = new Map<string, readonly ToolBinder[]>();
39
+
40
+ /**
41
+ * Post-processes a JSON Schema to inject `__binding` metadata for binding fields.
42
+ * Call this on the output of `z.toJSONSchema(stateSchema)` before returning it
43
+ * from MCP_CONFIGURATION.
44
+ */
45
+ export function injectBindingSchemas(
46
+ jsonSchema: Record<string, unknown>,
47
+ ): Record<string, unknown> {
48
+ const properties = jsonSchema.properties as
49
+ | Record<string, Record<string, unknown>>
50
+ | undefined;
51
+ if (!properties) return jsonSchema;
52
+
53
+ for (const prop of Object.values(properties)) {
54
+ const innerProps = prop.properties as
55
+ | Record<string, Record<string, unknown>>
56
+ | undefined;
57
+ if (!innerProps?.__type?.const) continue;
58
+
59
+ const typeName = innerProps.__type.const as string;
60
+ const binding = _bindingMetadata.get(typeName);
61
+ if (!binding) continue;
62
+
63
+ const jsonBinding = binding.map((t) => ({
64
+ name: String(t.name),
65
+ ...(t.inputSchema && { inputSchema: z.toJSONSchema(t.inputSchema) }),
66
+ ...(t.outputSchema && { outputSchema: z.toJSONSchema(t.outputSchema) }),
67
+ }));
68
+
69
+ innerProps.__binding = { const: jsonBinding };
70
+ }
71
+
72
+ return jsonSchema;
73
+ }
74
+
75
+ /**
76
+ * Function that returns Zod Schema for a binding field.
77
+ *
78
+ * When `binding` is provided, the tool definitions are embedded as `__binding`
79
+ * in the JSON Schema so the Mesh UI can filter connections by tool capabilities.
80
+ *
81
+ * @example
82
+ * ```ts
83
+ * // Without inline binding (UI resolves via registry or builtin mapping)
84
+ * BindingOf<Bindings, "@deco/llm">("@deco/llm")
85
+ *
86
+ * // With inline binding (UI filters connections by tool name match)
87
+ * BindingOf<Bindings, "@deco/cms-admin">("@deco/cms-admin", DECO_CMS_ADMIN_BINDING)
88
+ * ```
33
89
  */
34
90
  export const BindingOf = <
35
91
  TRegistry extends BindingRegistry,
36
92
  TName extends (keyof TRegistry | "*") & z.util.Literal,
37
93
  >(
38
94
  name: TName,
95
+ binding?: TName extends keyof TRegistry
96
+ ? TRegistry[TName]
97
+ : readonly ToolBinder[],
39
98
  ) => {
40
- return z.object({
99
+ const schema = z.object({
41
100
  __type: z.literal(name).default(name as any),
42
101
  value: z.string(),
43
102
  });
103
+
104
+ if (binding) {
105
+ // Store binding metadata for JSON Schema injection (via injectBindingSchemas).
106
+ // We don't add __binding to the Zod schema itself because it would leak into
107
+ // the saved configuration_state → JWT token, causing 431 header-too-large errors.
108
+ _bindingMetadata.set(name as string, binding as readonly ToolBinder[]);
109
+ }
110
+
111
+ return schema;
112
+ };
113
+
114
+ // ============================================================================
115
+ // Agent Bindings
116
+ // ============================================================================
117
+
118
+ const AgentModelInfoSchema = z.object({
119
+ id: z.string(),
120
+ title: z.string(),
121
+ capabilities: z
122
+ .object({
123
+ vision: z.boolean().optional(),
124
+ text: z.boolean().optional(),
125
+ tools: z.boolean().optional(),
126
+ reasoning: z.boolean().optional(),
127
+ })
128
+ .passthrough()
129
+ .optional(),
130
+ provider: z.string().optional().nullable(),
131
+ limits: z
132
+ .object({
133
+ contextWindow: z.number().optional(),
134
+ maxOutputTokens: z.number().optional(),
135
+ })
136
+ .passthrough()
137
+ .optional(),
138
+ });
139
+
140
+ /**
141
+ * Zod schema for agent bindings in the StateSchema.
142
+ * Defines an AI agent with its model config, approval level, and temperature.
143
+ *
144
+ * @example
145
+ * ```ts
146
+ * const stateSchema = z.object({
147
+ * MY_AGENT: AgentOf(),
148
+ * });
149
+ *
150
+ * // In tools:
151
+ * const stream = await state.MY_AGENT.STREAM({ messages: [...] });
152
+ * for await (const message of stream) { ... }
153
+ * ```
154
+ */
155
+ export const AgentOf = () =>
156
+ z.object({
157
+ __type: z.literal("@deco/agent" as const).default("@deco/agent" as const),
158
+ value: z.string(),
159
+ id: z.string().optional(),
160
+ credentialId: z.string().optional(),
161
+ thinking: AgentModelInfoSchema.optional(),
162
+ coding: AgentModelInfoSchema.optional(),
163
+ fast: AgentModelInfoSchema.optional(),
164
+ toolApprovalLevel: z.enum(["auto", "readonly", "plan"]).default("readonly"),
165
+ temperature: z.number().default(0.5),
166
+ });
167
+
168
+ const isAgent = (v: unknown): v is AgentBindingConfig => {
169
+ return (
170
+ typeof v === "object" &&
171
+ v !== null &&
172
+ (v as { __type: string }).__type === "@deco/agent"
173
+ );
44
174
  };
45
175
 
46
176
  /**
@@ -73,21 +203,24 @@ export const BindingOf = <
73
203
  export type ResolvedBindings<
74
204
  T,
75
205
  TBindings extends BindingRegistry,
76
- > = T extends Binding<infer TType>
77
- ? TType extends keyof TBindings
78
- ? MCPClientFetchStub<TBindings[TType]> & { __type: TType; value: string }
79
- : MCPClientFetchStub<[]> & { __type: string; value: string }
80
- : T extends Array<infer U>
81
- ? Array<ResolvedBindings<U, TBindings>>
82
- : T extends object
83
- ? { [K in keyof T]: ResolvedBindings<T[K], TBindings> }
84
- : T;
206
+ > = T extends AgentBindingConfig
207
+ ? ResolvedAgentClient
208
+ : T extends Binding<infer TType>
209
+ ? TType extends keyof TBindings
210
+ ? MCPClientFetchStub<TBindings[TType]> & { __type: TType; value: string }
211
+ : MCPClientFetchStub<[]> & { __type: string; value: string }
212
+ : T extends Array<infer U>
213
+ ? Array<ResolvedBindings<U, TBindings>>
214
+ : T extends object
215
+ ? { [K in keyof T]: ResolvedBindings<T[K], TBindings> }
216
+ : T;
85
217
 
86
218
  export const isBinding = (v: unknown): v is Binding => {
87
219
  return (
88
220
  typeof v === "object" &&
89
221
  v !== null &&
90
222
  typeof (v as { __type: string }).__type === "string" &&
223
+ (v as { __type: string }).__type !== "@deco/agent" &&
91
224
  typeof (v as { value: string }).value === "string"
92
225
  );
93
226
  };
@@ -141,6 +274,24 @@ const mcpClientForConnectionId = (
141
274
  });
142
275
  };
143
276
 
277
+ const createAgentProxy = (
278
+ config: AgentBindingConfig,
279
+ ctx: ClientContext,
280
+ ): ResolvedAgentClient => {
281
+ const orgSlug = ctx.organizationSlug;
282
+ if (!orgSlug) {
283
+ throw new Error("organizationSlug is required for agent bindings");
284
+ }
285
+ const streamUrl = `${ctx.meshUrl}/api/${orgSlug}/decopilot/runtime/stream`;
286
+
287
+ return {
288
+ STREAM: async (params, opts) => {
289
+ const { streamAgent } = await import("./decopilot.ts");
290
+ return streamAgent(streamUrl, ctx.token, config, params, opts);
291
+ },
292
+ };
293
+ };
294
+
144
295
  const traverseAndReplace = (obj: unknown, ctx: ClientContext): unknown => {
145
296
  // Handle null/undefined
146
297
  if (obj === null || obj === undefined) {
@@ -154,7 +305,12 @@ const traverseAndReplace = (obj: unknown, ctx: ClientContext): unknown => {
154
305
 
155
306
  // Handle objects
156
307
  if (typeof obj === "object") {
157
- // Check if this is a binding
308
+ // Check if this is an agent binding (before connection binding check)
309
+ if (isAgent(obj)) {
310
+ return createAgentProxy(obj, ctx);
311
+ }
312
+
313
+ // Check if this is a connection binding
158
314
  if (isBinding(obj)) {
159
315
  return mcpClientForConnectionId(obj.value, ctx, obj.__type);
160
316
  }
@@ -0,0 +1,196 @@
1
+ import type { UIMessage } from "ai";
2
+
3
+ // ============================================================================
4
+ // Agent Model Types
5
+ // ============================================================================
6
+
7
+ export interface AgentModelInfo {
8
+ id: string;
9
+ title: string;
10
+ capabilities?: {
11
+ vision?: boolean;
12
+ text?: boolean;
13
+ tools?: boolean;
14
+ reasoning?: boolean;
15
+ };
16
+ provider?: string | null;
17
+ limits?: { contextWindow?: number; maxOutputTokens?: number };
18
+ }
19
+
20
+ // ============================================================================
21
+ // Agent Binding Config (lives in StateSchema)
22
+ // ============================================================================
23
+
24
+ export interface AgentBindingConfig {
25
+ __type: "@deco/agent";
26
+ /** Virtual MCP connection ID (binding pointer) */
27
+ value?: string;
28
+ /** Agent ID — may be absent when state only stores a binding reference */
29
+ id?: string;
30
+ credentialId?: string;
31
+ thinking?: AgentModelInfo;
32
+ coding?: AgentModelInfo;
33
+ fast?: AgentModelInfo;
34
+ toolApprovalLevel?: "auto" | "readonly" | "plan";
35
+ temperature?: number;
36
+ }
37
+
38
+ // ============================================================================
39
+ // STREAM() params — messages + optional overrides
40
+ // ============================================================================
41
+
42
+ export interface AgentStreamParams {
43
+ messages: Omit<UIMessage, "id">[];
44
+ credentialId?: string;
45
+ thinking?: AgentModelInfo;
46
+ coding?: AgentModelInfo;
47
+ fast?: AgentModelInfo;
48
+ toolApprovalLevel?: "auto" | "readonly" | "plan";
49
+ temperature?: number;
50
+ memory?: { windowSize: number; thread_id: string };
51
+ thread_id?: string;
52
+ }
53
+
54
+ // ============================================================================
55
+ // Resolved agent client — what env.MY_AGENT becomes after binding resolution
56
+ // ============================================================================
57
+
58
+ export interface ResolvedAgentClient {
59
+ STREAM: (
60
+ params: AgentStreamParams,
61
+ opts?: { signal?: AbortSignal },
62
+ ) => Promise<AsyncIterable<UIMessage> & ReadableStream<UIMessage>>;
63
+ }
64
+
65
+ // ============================================================================
66
+ // SSE → UIMessageChunk parsing (mirrors DefaultChatTransport.processResponseStream)
67
+ // ============================================================================
68
+
69
+ async function parseResponseStream(
70
+ body: ReadableStream<Uint8Array>,
71
+ ): Promise<ReadableStream> {
72
+ const { parseJsonEventStream, uiMessageChunkSchema } = await import("ai");
73
+ return parseJsonEventStream({
74
+ stream: body,
75
+ schema: uiMessageChunkSchema,
76
+ }).pipeThrough(
77
+ new TransformStream({
78
+ transform(result, controller) {
79
+ if (!result.success) {
80
+ throw result.error;
81
+ }
82
+ controller.enqueue(result.value);
83
+ },
84
+ }),
85
+ );
86
+ }
87
+
88
+ // ============================================================================
89
+ // Core stream function (used by binding proxy and standalone client)
90
+ // ============================================================================
91
+
92
+ export async function streamAgent(
93
+ streamUrl: string,
94
+ token: string,
95
+ config: AgentBindingConfig,
96
+ params: AgentStreamParams,
97
+ opts?: { signal?: AbortSignal },
98
+ ) {
99
+ const { readUIMessageStream } = await import("ai");
100
+
101
+ const agentId = config.value ?? config.id;
102
+ if (!agentId) {
103
+ throw new Error("Agent binding has no id or value — cannot resolve agent");
104
+ }
105
+
106
+ const credentialId = params.credentialId ?? config.credentialId;
107
+ const thinking = params.thinking ?? config.thinking;
108
+ const hasModels = credentialId && thinking?.id;
109
+
110
+ const request = {
111
+ messages: params.messages,
112
+ ...(hasModels
113
+ ? {
114
+ models: {
115
+ credentialId,
116
+ thinking,
117
+ ...((params.coding ?? config.coding)
118
+ ? { coding: params.coding ?? config.coding }
119
+ : {}),
120
+ ...((params.fast ?? config.fast)
121
+ ? { fast: params.fast ?? config.fast }
122
+ : {}),
123
+ },
124
+ }
125
+ : {}),
126
+ agent: { id: agentId },
127
+ temperature: params.temperature ?? config.temperature,
128
+ toolApprovalLevel: params.toolApprovalLevel ?? config.toolApprovalLevel,
129
+ ...(params.memory ? { memory: params.memory } : {}),
130
+ ...(params.thread_id ? { thread_id: params.thread_id } : {}),
131
+ };
132
+
133
+ const response = await fetch(streamUrl, {
134
+ method: "POST",
135
+ headers: {
136
+ "Content-Type": "application/json",
137
+ "x-mesh-token": token,
138
+ Authorization: `Bearer ${token}`,
139
+ },
140
+ body: JSON.stringify(request),
141
+ signal: opts?.signal,
142
+ });
143
+
144
+ if (!response.ok) {
145
+ const text = await response.text().catch(() => "");
146
+ let message = `HTTP ${response.status}`;
147
+ try {
148
+ const body = JSON.parse(text);
149
+ if (body?.error) message = body.error;
150
+ } catch {
151
+ if (text) message = text;
152
+ }
153
+ throw new Error(message);
154
+ }
155
+
156
+ if (!response.body) {
157
+ throw new Error("Empty response body from decopilot stream");
158
+ }
159
+
160
+ const chunkStream = await parseResponseStream(response.body);
161
+ return readUIMessageStream({ stream: chunkStream });
162
+ }
163
+
164
+ // ============================================================================
165
+ // Standalone client factory (for direct usage outside binding system)
166
+ // ============================================================================
167
+
168
+ export interface DecopilotClientOptions {
169
+ baseUrl: string;
170
+ orgSlug: string;
171
+ token: string;
172
+ }
173
+
174
+ export function createDecopilotClient(options: DecopilotClientOptions) {
175
+ const { baseUrl, orgSlug, token } = options;
176
+ const streamUrl = `${baseUrl}/${orgSlug}/decopilot/runtime/stream`;
177
+
178
+ return {
179
+ stream(
180
+ request: AgentStreamParams & { agent: { id: string } },
181
+ opts?: { signal?: AbortSignal },
182
+ ) {
183
+ const config: AgentBindingConfig = {
184
+ __type: "@deco/agent",
185
+ id: request.agent.id,
186
+ credentialId: request.credentialId ?? "",
187
+ thinking: request.thinking ?? { id: "", title: "" },
188
+ coding: request.coding,
189
+ fast: request.fast,
190
+ toolApprovalLevel: request.toolApprovalLevel,
191
+ temperature: request.temperature,
192
+ };
193
+ return streamAgent(streamUrl, token, config, request, opts);
194
+ },
195
+ };
196
+ }
package/src/index.ts CHANGED
@@ -28,9 +28,11 @@ export {
28
28
  type ResourceExecutionContext,
29
29
  type ResourceContents,
30
30
  type CreatedResource,
31
+ type WorkflowDefinition,
31
32
  } from "./tools.ts";
33
+ export { createWorkflow } from "./workflows.ts";
32
34
  import type { Binding } from "./wrangler.ts";
33
- export { proxyConnectionForId, BindingOf } from "./bindings.ts";
35
+ export { proxyConnectionForId, BindingOf, AgentOf } from "./bindings.ts";
34
36
  export { type CORSOptions, type CORSOrigin } from "./cors.ts";
35
37
  export {
36
38
  createMCPFetchStub,