@decocms/bindings 1.3.1 → 1.3.3
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 +2 -1
- package/src/core/server-plugin.ts +8 -1
- package/src/index.ts +4 -0
- package/src/well-known/ai-gateway.ts +63 -0
- package/src/well-known/reports.ts +113 -0
- package/src/well-known/workflow.ts +22 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decocms/bindings",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"check": "tsc --noEmit",
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"./workflow": "./src/well-known/workflow.ts",
|
|
31
31
|
"./plugins": "./src/core/plugins.ts",
|
|
32
32
|
"./plugin-router": "./src/core/plugin-router.tsx",
|
|
33
|
+
"./ai-gateway": "./src/well-known/ai-gateway.ts",
|
|
33
34
|
"./server-plugin": "./src/core/server-plugin.ts"
|
|
34
35
|
},
|
|
35
36
|
"engines": {
|
|
@@ -40,7 +40,14 @@ export interface ServerPluginToolContext {
|
|
|
40
40
|
structuredContent?: unknown;
|
|
41
41
|
}>;
|
|
42
42
|
listTools: () => Promise<{
|
|
43
|
-
tools: Array<{
|
|
43
|
+
tools: Array<{
|
|
44
|
+
name: string;
|
|
45
|
+
title?: string;
|
|
46
|
+
description?: string;
|
|
47
|
+
inputSchema?: Record<string, unknown>;
|
|
48
|
+
outputSchema?: Record<string, unknown>;
|
|
49
|
+
annotations?: Record<string, unknown>;
|
|
50
|
+
}>;
|
|
44
51
|
}>;
|
|
45
52
|
close?: () => Promise<void>;
|
|
46
53
|
}>;
|
package/src/index.ts
CHANGED
|
@@ -111,7 +111,9 @@ export {
|
|
|
111
111
|
type ReportsBinding,
|
|
112
112
|
type ReportStatus,
|
|
113
113
|
type ReportLifecycleStatus,
|
|
114
|
+
type CriterionItem,
|
|
114
115
|
type MetricItem,
|
|
116
|
+
type RankedListRow,
|
|
115
117
|
type ReportSection,
|
|
116
118
|
type ReportSummary,
|
|
117
119
|
type Report,
|
|
@@ -127,4 +129,6 @@ export {
|
|
|
127
129
|
ReportSectionSchema,
|
|
128
130
|
ReportSummarySchema,
|
|
129
131
|
ReportSchema,
|
|
132
|
+
type SectionGroup,
|
|
133
|
+
groupSections,
|
|
130
134
|
} from "./well-known/reports";
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Gateway Billing Well-Known Binding
|
|
3
|
+
*
|
|
4
|
+
* Defines the interface for AI gateways that support billing management.
|
|
5
|
+
* Any MCP that implements this binding is recognized as a billing-capable
|
|
6
|
+
* AI gateway, regardless of its URL.
|
|
7
|
+
*
|
|
8
|
+
* Required tools:
|
|
9
|
+
* - GATEWAY_USAGE: Returns spending, usage, limits, and alert configuration
|
|
10
|
+
*
|
|
11
|
+
* Optional tools:
|
|
12
|
+
* - GATEWAY_SET_LIMIT: Configure spending limit / add credit
|
|
13
|
+
* - GATEWAY_SET_ALERT: Configure usage/balance alerts
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { z } from "zod";
|
|
17
|
+
import type { Binder } from "../core/binder";
|
|
18
|
+
|
|
19
|
+
export const GATEWAY_USAGE_INPUT = z.object({}).strict();
|
|
20
|
+
|
|
21
|
+
export const GATEWAY_USAGE_OUTPUT = z.object({
|
|
22
|
+
billing: z.object({
|
|
23
|
+
mode: z.enum(["prepaid", "postpaid"]),
|
|
24
|
+
limitPeriod: z.enum(["daily", "weekly", "monthly"]).nullable(),
|
|
25
|
+
}),
|
|
26
|
+
limit: z.object({
|
|
27
|
+
total: z.number().nullable(),
|
|
28
|
+
remaining: z.number().nullable(),
|
|
29
|
+
reset: z.string().nullable(),
|
|
30
|
+
}),
|
|
31
|
+
usage: z.object({
|
|
32
|
+
total: z.number(),
|
|
33
|
+
daily: z.number(),
|
|
34
|
+
weekly: z.number(),
|
|
35
|
+
monthly: z.number(),
|
|
36
|
+
}),
|
|
37
|
+
alert: z.object({
|
|
38
|
+
enabled: z.boolean(),
|
|
39
|
+
threshold_usd: z.number(),
|
|
40
|
+
email: z.string().nullable(),
|
|
41
|
+
}),
|
|
42
|
+
connectionId: z.string(),
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
export const AI_GATEWAY_BILLING_BINDING = [
|
|
46
|
+
{
|
|
47
|
+
name: "GATEWAY_USAGE" as const,
|
|
48
|
+
inputSchema: GATEWAY_USAGE_INPUT,
|
|
49
|
+
outputSchema: GATEWAY_USAGE_OUTPUT,
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: "GATEWAY_SET_LIMIT" as const,
|
|
53
|
+
inputSchema: z.object({}),
|
|
54
|
+
outputSchema: z.object({}),
|
|
55
|
+
opt: true,
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: "GATEWAY_SET_ALERT" as const,
|
|
59
|
+
inputSchema: z.object({}),
|
|
60
|
+
outputSchema: z.object({}),
|
|
61
|
+
opt: true,
|
|
62
|
+
},
|
|
63
|
+
] as const satisfies Binder;
|
|
@@ -48,6 +48,50 @@ export const MetricItemSchema = z.object({
|
|
|
48
48
|
});
|
|
49
49
|
export type MetricItem = z.infer<typeof MetricItemSchema>;
|
|
50
50
|
|
|
51
|
+
/**
|
|
52
|
+
* A single criterion item within a criteria section.
|
|
53
|
+
*/
|
|
54
|
+
export const CriterionItemSchema = z.object({
|
|
55
|
+
label: z.string().describe("Short name of the criterion"),
|
|
56
|
+
description: z.string().optional().describe("Longer explanation"),
|
|
57
|
+
status: ReportStatusSchema.optional().describe(
|
|
58
|
+
"Status of this individual criterion (passing/warning/failing/info)",
|
|
59
|
+
),
|
|
60
|
+
});
|
|
61
|
+
export type CriterionItem = z.infer<typeof CriterionItemSchema>;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* A single row within a ranked-list section.
|
|
65
|
+
*/
|
|
66
|
+
export const RankedListRowSchema = z.object({
|
|
67
|
+
position: z.number().describe("Current rank position"),
|
|
68
|
+
reference_position: z
|
|
69
|
+
.number()
|
|
70
|
+
.optional()
|
|
71
|
+
.describe(
|
|
72
|
+
"Previous rank position before reordering. Used to compute delta automatically (delta = reference_position - position).",
|
|
73
|
+
),
|
|
74
|
+
delta: z
|
|
75
|
+
.number()
|
|
76
|
+
.optional()
|
|
77
|
+
.describe(
|
|
78
|
+
"Explicit change in position. Ignored when reference_position is provided.",
|
|
79
|
+
),
|
|
80
|
+
label: z.string().describe("Item name"),
|
|
81
|
+
image: z.string().describe("URL of the item image"),
|
|
82
|
+
values: z
|
|
83
|
+
.array(z.union([z.string(), z.number()]))
|
|
84
|
+
.describe("Values matching columns"),
|
|
85
|
+
note: z
|
|
86
|
+
.union([
|
|
87
|
+
z.string(),
|
|
88
|
+
z.record(z.string(), z.union([z.string(), z.number()])),
|
|
89
|
+
])
|
|
90
|
+
.optional()
|
|
91
|
+
.describe("Inline annotation or structured key-value metrics"),
|
|
92
|
+
});
|
|
93
|
+
export type RankedListRow = z.infer<typeof RankedListRowSchema>;
|
|
94
|
+
|
|
51
95
|
/**
|
|
52
96
|
* Report sections -- polymorphic by type.
|
|
53
97
|
* Sections represent the main content blocks of a report.
|
|
@@ -70,6 +114,20 @@ export const ReportSectionSchema = z.discriminatedUnion("type", [
|
|
|
70
114
|
.array(z.array(z.union([z.string(), z.number(), z.null()])))
|
|
71
115
|
.describe("Table rows"),
|
|
72
116
|
}),
|
|
117
|
+
z.object({
|
|
118
|
+
type: z.literal("criteria"),
|
|
119
|
+
title: z.string().optional().describe("Section title"),
|
|
120
|
+
items: z.array(CriterionItemSchema).describe("List of criteria items"),
|
|
121
|
+
}),
|
|
122
|
+
z.object({
|
|
123
|
+
type: z.literal("note"),
|
|
124
|
+
content: z.string().describe("The note text"),
|
|
125
|
+
}),
|
|
126
|
+
z.object({
|
|
127
|
+
type: z.literal("ranked-list"),
|
|
128
|
+
title: z.string().optional().describe("Section title"),
|
|
129
|
+
rows: z.array(RankedListRowSchema).describe("Ranked items"),
|
|
130
|
+
}),
|
|
73
131
|
]);
|
|
74
132
|
export type ReportSection = z.infer<typeof ReportSectionSchema>;
|
|
75
133
|
|
|
@@ -88,6 +146,9 @@ export type ReportLifecycleStatus = z.infer<typeof ReportLifecycleStatusSchema>;
|
|
|
88
146
|
*/
|
|
89
147
|
export const ReportSummarySchema = z.object({
|
|
90
148
|
id: z.string().describe("Unique report identifier"),
|
|
149
|
+
collectionId: z
|
|
150
|
+
.string()
|
|
151
|
+
.describe("Collection identifier used to scope reports"),
|
|
91
152
|
title: z.string().describe("Report title"),
|
|
92
153
|
category: z
|
|
93
154
|
.string()
|
|
@@ -121,6 +182,58 @@ export const ReportSchema = ReportSummarySchema.extend({
|
|
|
121
182
|
});
|
|
122
183
|
export type Report = z.infer<typeof ReportSchema>;
|
|
123
184
|
|
|
185
|
+
// ============================================================================
|
|
186
|
+
// UI Helpers
|
|
187
|
+
// ============================================================================
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Groups adjacent criteria+metrics sections into side-by-side pairs for display.
|
|
191
|
+
* In a pair, criteria always goes left and metrics always goes right,
|
|
192
|
+
* regardless of their original order.
|
|
193
|
+
*/
|
|
194
|
+
|
|
195
|
+
type SingleGroup = { type: "single"; section: ReportSection; idx: number };
|
|
196
|
+
type SideBySideGroup = {
|
|
197
|
+
type: "side-by-side";
|
|
198
|
+
left: Extract<ReportSection, { type: "criteria" }>;
|
|
199
|
+
right: Extract<ReportSection, { type: "metrics" }>;
|
|
200
|
+
leftIdx: number;
|
|
201
|
+
rightIdx: number;
|
|
202
|
+
};
|
|
203
|
+
export type SectionGroup = SingleGroup | SideBySideGroup;
|
|
204
|
+
|
|
205
|
+
export function groupSections(sections: ReportSection[]): SectionGroup[] {
|
|
206
|
+
const groups: SectionGroup[] = [];
|
|
207
|
+
let i = 0;
|
|
208
|
+
while (i < sections.length) {
|
|
209
|
+
const current = sections[i]!;
|
|
210
|
+
const next = sections[i + 1];
|
|
211
|
+
const isPair =
|
|
212
|
+
(current.type === "criteria" && next?.type === "metrics") ||
|
|
213
|
+
(current.type === "metrics" && next?.type === "criteria");
|
|
214
|
+
|
|
215
|
+
if (isPair) {
|
|
216
|
+
const isCriteriaFirst = current.type === "criteria";
|
|
217
|
+
const criteria = isCriteriaFirst ? current : next!;
|
|
218
|
+
const metrics = isCriteriaFirst ? next! : current;
|
|
219
|
+
const criteriaIdx = isCriteriaFirst ? i : i + 1;
|
|
220
|
+
const metricsIdx = isCriteriaFirst ? i + 1 : i;
|
|
221
|
+
groups.push({
|
|
222
|
+
type: "side-by-side",
|
|
223
|
+
left: criteria as Extract<ReportSection, { type: "criteria" }>,
|
|
224
|
+
right: metrics as Extract<ReportSection, { type: "metrics" }>,
|
|
225
|
+
leftIdx: criteriaIdx,
|
|
226
|
+
rightIdx: metricsIdx,
|
|
227
|
+
});
|
|
228
|
+
i += 2;
|
|
229
|
+
} else {
|
|
230
|
+
groups.push({ type: "single", section: current, idx: i });
|
|
231
|
+
i += 1;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return groups;
|
|
235
|
+
}
|
|
236
|
+
|
|
124
237
|
// ============================================================================
|
|
125
238
|
// Tool Schemas
|
|
126
239
|
// ============================================================================
|
|
@@ -99,6 +99,7 @@ export type StepConfig = z.infer<typeof StepConfigSchema>;
|
|
|
99
99
|
* Data flow uses @ref syntax:
|
|
100
100
|
* - @input.field → workflow input
|
|
101
101
|
* - @stepName.field → output from a previous step
|
|
102
|
+
* - @ctx.execution_id → current workflow execution ID
|
|
102
103
|
*/
|
|
103
104
|
|
|
104
105
|
type JsonSchema = {
|
|
@@ -106,8 +107,8 @@ type JsonSchema = {
|
|
|
106
107
|
properties?: Record<string, unknown>;
|
|
107
108
|
required?: string[];
|
|
108
109
|
description?: string;
|
|
109
|
-
additionalProperties?: boolean
|
|
110
|
-
additionalItems?: boolean
|
|
110
|
+
additionalProperties?: boolean | Record<string, unknown>;
|
|
111
|
+
additionalItems?: boolean | Record<string, unknown>;
|
|
111
112
|
items?: JsonSchema;
|
|
112
113
|
};
|
|
113
114
|
const JsonSchemaSchema: z.ZodType<JsonSchema> = z.lazy(() =>
|
|
@@ -117,17 +118,33 @@ const JsonSchemaSchema: z.ZodType<JsonSchema> = z.lazy(() =>
|
|
|
117
118
|
properties: z.record(z.string(), z.unknown()).optional(),
|
|
118
119
|
required: z.array(z.string()).optional(),
|
|
119
120
|
description: z.string().optional(),
|
|
120
|
-
additionalProperties: z
|
|
121
|
-
|
|
121
|
+
additionalProperties: z
|
|
122
|
+
.union([z.boolean(), z.record(z.string(), z.unknown())])
|
|
123
|
+
.optional(),
|
|
124
|
+
additionalItems: z
|
|
125
|
+
.union([z.boolean(), z.record(z.string(), z.unknown())])
|
|
126
|
+
.optional(),
|
|
122
127
|
items: JsonSchemaSchema.optional(),
|
|
123
128
|
})
|
|
124
129
|
.passthrough(),
|
|
125
130
|
);
|
|
126
131
|
|
|
132
|
+
/**
|
|
133
|
+
* Step names that are reserved by the @ref system and cannot be used as step names.
|
|
134
|
+
* These are intercepted before step lookup in the ref resolver.
|
|
135
|
+
*/
|
|
136
|
+
export const RESERVED_STEP_NAMES = ["input", "item", "index", "ctx"] as const;
|
|
137
|
+
|
|
127
138
|
export const StepSchema = z.object({
|
|
128
139
|
name: z
|
|
129
140
|
.string()
|
|
130
141
|
.min(1)
|
|
142
|
+
.refine(
|
|
143
|
+
(name) => !(RESERVED_STEP_NAMES as readonly string[]).includes(name),
|
|
144
|
+
{
|
|
145
|
+
message: `Step name is reserved. Reserved names: ${RESERVED_STEP_NAMES.join(", ")}`,
|
|
146
|
+
},
|
|
147
|
+
)
|
|
131
148
|
.describe(
|
|
132
149
|
"Unique identifier for this step. Other steps reference its output as @name.field",
|
|
133
150
|
),
|
|
@@ -137,7 +154,7 @@ export const StepSchema = z.object({
|
|
|
137
154
|
.record(z.string(), z.unknown())
|
|
138
155
|
.optional()
|
|
139
156
|
.describe(
|
|
140
|
-
"Data passed to the action. Use @ref for dynamic values: @input.field (workflow input), @stepName.field (previous step output), @item/@index (loop context). Example: { 'userId': '@input.user_id', 'data': '@fetch.result' }",
|
|
157
|
+
"Data passed to the action. Use @ref for dynamic values: @input.field (workflow input), @stepName.field (previous step output), @item/@index (loop context), @ctx.execution_id (current execution ID). Example: { 'userId': '@input.user_id', 'data': '@fetch.result', 'executionId': '@ctx.execution_id' }",
|
|
141
158
|
),
|
|
142
159
|
outputSchema: JsonSchemaSchema.optional().describe(
|
|
143
160
|
"Optional JSON Schema describing the expected output of the step.",
|