@ema.co/mcp-toolkit 0.2.0
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/LICENSE +21 -0
- package/README.md +321 -0
- package/config.example.yaml +32 -0
- package/dist/cli/index.js +333 -0
- package/dist/config.js +136 -0
- package/dist/emaClient.js +398 -0
- package/dist/index.js +109 -0
- package/dist/mcp/handlers-consolidated.js +851 -0
- package/dist/mcp/index.js +15 -0
- package/dist/mcp/prompts.js +1753 -0
- package/dist/mcp/resources.js +624 -0
- package/dist/mcp/server.js +4723 -0
- package/dist/mcp/tools-consolidated.js +590 -0
- package/dist/mcp/tools-legacy.js +736 -0
- package/dist/models.js +8 -0
- package/dist/scheduler.js +21 -0
- package/dist/sdk/client.js +788 -0
- package/dist/sdk/config.js +136 -0
- package/dist/sdk/contracts.js +429 -0
- package/dist/sdk/generation-schema.js +189 -0
- package/dist/sdk/index.js +39 -0
- package/dist/sdk/knowledge.js +2780 -0
- package/dist/sdk/models.js +8 -0
- package/dist/sdk/state.js +88 -0
- package/dist/sdk/sync-options.js +216 -0
- package/dist/sdk/sync.js +220 -0
- package/dist/sdk/validation-rules.js +355 -0
- package/dist/sdk/workflow-generator.js +291 -0
- package/dist/sdk/workflow-intent.js +1585 -0
- package/dist/state.js +88 -0
- package/dist/sync.js +416 -0
- package/dist/syncOptions.js +216 -0
- package/dist/ui.js +334 -0
- package/docs/advisor-comms-assistant-fixes.md +175 -0
- package/docs/api-contracts.md +216 -0
- package/docs/auto-builder-analysis.md +271 -0
- package/docs/data-architecture.md +166 -0
- package/docs/ema-auto-builder-guide.html +394 -0
- package/docs/ema-user-guide.md +1121 -0
- package/docs/mcp-tools-guide.md +149 -0
- package/docs/naming-conventions.md +218 -0
- package/docs/tool-consolidation-proposal.md +427 -0
- package/package.json +98 -0
- package/resources/templates/chat-ai/README.md +119 -0
- package/resources/templates/chat-ai/persona-config.json +111 -0
- package/resources/templates/dashboard-ai/README.md +156 -0
- package/resources/templates/dashboard-ai/persona-config.json +180 -0
- package/resources/templates/voice-ai/README.md +123 -0
- package/resources/templates/voice-ai/persona-config.json +74 -0
- package/resources/templates/voice-ai/workflow-prompt.md +120 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment Configuration
|
|
3
|
+
*
|
|
4
|
+
* Defines Ema API environments and service settings.
|
|
5
|
+
* Used by both MCP server and CLI/service modes.
|
|
6
|
+
*/
|
|
7
|
+
import fs from "node:fs";
|
|
8
|
+
import yaml from "js-yaml";
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
11
|
+
// Schema
|
|
12
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
13
|
+
const EnvironmentSchema = z.object({
|
|
14
|
+
name: z.string().min(1),
|
|
15
|
+
baseUrl: z.string().url(),
|
|
16
|
+
bearerTokenEnv: z.string().min(1),
|
|
17
|
+
isMaster: z.boolean().optional().default(false),
|
|
18
|
+
// Deprecated but accepted for backward compatibility
|
|
19
|
+
userId: z.string().optional(),
|
|
20
|
+
templateIdMap: z.record(z.string()).optional(),
|
|
21
|
+
});
|
|
22
|
+
const SchedulerSchema = z.object({
|
|
23
|
+
intervalSeconds: z.number().int().positive().optional(),
|
|
24
|
+
cron: z.string().min(1).optional(),
|
|
25
|
+
}).strict().refine((v) => v.cron || v.intervalSeconds, { message: "scheduler must set either intervalSeconds or cron" });
|
|
26
|
+
const ServiceSchema = z.object({
|
|
27
|
+
stateDbPath: z.string().min(1).optional().default("./ema-state.sqlite3"),
|
|
28
|
+
scheduler: SchedulerSchema.optional(),
|
|
29
|
+
}).strict();
|
|
30
|
+
// Legacy schema for backward compatibility
|
|
31
|
+
const LegacyRouteRuleSchema = z.object({
|
|
32
|
+
personaIds: z.array(z.string()).default([]),
|
|
33
|
+
targetEnvs: z.array(z.string()).default([]),
|
|
34
|
+
nameGlobs: z.array(z.string()).optional().default([]),
|
|
35
|
+
nameRegexes: z.array(z.string()).optional().default([]),
|
|
36
|
+
namePrefixes: z.array(z.string()).optional().default([]),
|
|
37
|
+
}).passthrough();
|
|
38
|
+
const AppConfigSchema = z.object({
|
|
39
|
+
environments: z.array(EnvironmentSchema).min(1),
|
|
40
|
+
service: ServiceSchema.optional(),
|
|
41
|
+
dryRun: z.boolean().optional().default(false),
|
|
42
|
+
verbose: z.boolean().optional().default(false),
|
|
43
|
+
// Legacy fields (kept for backward compatibility)
|
|
44
|
+
routing: z.array(LegacyRouteRuleSchema).optional(),
|
|
45
|
+
scheduler: SchedulerSchema.optional(),
|
|
46
|
+
stateDbPath: z.string().optional(),
|
|
47
|
+
eventSharedSecretEnv: z.string().optional(),
|
|
48
|
+
}).refine((cfg) => {
|
|
49
|
+
// Validate at most one master
|
|
50
|
+
const masters = cfg.environments.filter((e) => e.isMaster);
|
|
51
|
+
return masters.length <= 1;
|
|
52
|
+
}, { message: "At most one environment can be isMaster: true" });
|
|
53
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
54
|
+
// Loading functions
|
|
55
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
56
|
+
export function loadConfig(path) {
|
|
57
|
+
const raw = fs.readFileSync(path, "utf8");
|
|
58
|
+
const parsed = yaml.load(raw);
|
|
59
|
+
if (!parsed || typeof parsed !== "object") {
|
|
60
|
+
throw new Error("Invalid config YAML");
|
|
61
|
+
}
|
|
62
|
+
const res = AppConfigSchema.safeParse(parsed);
|
|
63
|
+
if (!res.success) {
|
|
64
|
+
const errors = res.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`);
|
|
65
|
+
throw new Error(`Invalid config: ${errors.join("; ")}`);
|
|
66
|
+
}
|
|
67
|
+
return res.data;
|
|
68
|
+
}
|
|
69
|
+
export function loadConfigOptional(path) {
|
|
70
|
+
try {
|
|
71
|
+
return loadConfig(path);
|
|
72
|
+
}
|
|
73
|
+
catch (e) {
|
|
74
|
+
if (e && typeof e === "object" && "code" in e && e.code === "ENOENT") {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
78
|
+
if (msg.includes("ENOENT"))
|
|
79
|
+
return null;
|
|
80
|
+
throw e;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
export function loadConfigFromJsonEnv() {
|
|
84
|
+
const raw = process.env.EMA_CONFIG_JSON ?? process.env.EMA_AGENT_SYNC_CONFIG_JSON;
|
|
85
|
+
if (!raw)
|
|
86
|
+
return null;
|
|
87
|
+
let parsed;
|
|
88
|
+
try {
|
|
89
|
+
parsed = JSON.parse(raw);
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
throw new Error("EMA_CONFIG_JSON is not valid JSON");
|
|
93
|
+
}
|
|
94
|
+
const res = validateConfig(parsed);
|
|
95
|
+
if (!res.ok) {
|
|
96
|
+
throw new Error(`Invalid EMA_CONFIG_JSON: ${res.errors.join("; ")}`);
|
|
97
|
+
}
|
|
98
|
+
return res.config;
|
|
99
|
+
}
|
|
100
|
+
export function validateConfig(input) {
|
|
101
|
+
const res = AppConfigSchema.safeParse(input);
|
|
102
|
+
if (!res.success) {
|
|
103
|
+
return { ok: false, errors: res.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`) };
|
|
104
|
+
}
|
|
105
|
+
return { ok: true, config: res.data };
|
|
106
|
+
}
|
|
107
|
+
export function configToYaml(cfg) {
|
|
108
|
+
return yaml.dump(cfg, { noRefs: true, sortKeys: true });
|
|
109
|
+
}
|
|
110
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
111
|
+
// Utilities
|
|
112
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
113
|
+
export function resolveBearerToken(envVarName) {
|
|
114
|
+
const v = process.env[envVarName];
|
|
115
|
+
if (!v)
|
|
116
|
+
throw new Error(`Missing bearer token env var: ${envVarName}`);
|
|
117
|
+
// Strip "Bearer " prefix if user included it
|
|
118
|
+
const trimmed = v.trim();
|
|
119
|
+
if (trimmed.toLowerCase().startsWith("bearer ")) {
|
|
120
|
+
return trimmed.slice(7).trim();
|
|
121
|
+
}
|
|
122
|
+
return trimmed;
|
|
123
|
+
}
|
|
124
|
+
export function assertEnvVarsPresent(cfg) {
|
|
125
|
+
for (const e of cfg.environments) {
|
|
126
|
+
if (!process.env[e.bearerTokenEnv]) {
|
|
127
|
+
throw new Error(`Missing required env var for ${e.name}: ${e.bearerTokenEnv}`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
export function getMasterEnv(cfg) {
|
|
132
|
+
return cfg.environments.find((e) => e.isMaster);
|
|
133
|
+
}
|
|
134
|
+
export function getEnvByName(cfg, name) {
|
|
135
|
+
return cfg.environments.find((e) => e.name === name);
|
|
136
|
+
}
|
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Contract Validation using Zod
|
|
3
|
+
*
|
|
4
|
+
* This module defines comprehensive Zod schemas for validating API responses at runtime.
|
|
5
|
+
* These schemas should match the OpenAPI spec (auto-generated from Pydantic).
|
|
6
|
+
*
|
|
7
|
+
* IMPORTANT: Keep these schemas in sync with the backend Pydantic models.
|
|
8
|
+
* Run `npm run check:contracts` to detect drift.
|
|
9
|
+
*
|
|
10
|
+
* Benefits:
|
|
11
|
+
* - Runtime type validation (catch malformed API responses)
|
|
12
|
+
* - Better error messages than TypeScript alone
|
|
13
|
+
* - Can generate TypeScript types with z.infer<>
|
|
14
|
+
*
|
|
15
|
+
* Usage:
|
|
16
|
+
* import { PersonaDTOSchema, validateResponse } from "./contracts";
|
|
17
|
+
*
|
|
18
|
+
* const result = validateResponse(PersonaDTOSchema, apiResponse);
|
|
19
|
+
* // result is typed as PersonaDTO
|
|
20
|
+
*/
|
|
21
|
+
import { z } from "zod";
|
|
22
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
23
|
+
// Base Types & Utilities
|
|
24
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
25
|
+
/** UUID string format - validates format but not actual UUID validity */
|
|
26
|
+
export const UUIDSchema = z.string().min(1);
|
|
27
|
+
/** ISO 8601 datetime string */
|
|
28
|
+
export const DateTimeSchema = z.string().refine((val) => !isNaN(Date.parse(val)), { message: "Invalid datetime format" });
|
|
29
|
+
/** Nullable wrapper for optional fields that can be explicitly null */
|
|
30
|
+
export const nullable = (schema) => schema.nullable().optional();
|
|
31
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
32
|
+
// Enums (match backend PersonaTriggerTypeEnum, PersonaStateEnum, etc.)
|
|
33
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
34
|
+
/** Persona trigger types - how the AI Employee is activated */
|
|
35
|
+
export const PersonaTriggerTypeSchema = z.enum([
|
|
36
|
+
"TRIGGER_TYPE_CHATBOT",
|
|
37
|
+
"TRIGGER_TYPE_WEBAPP",
|
|
38
|
+
"TRIGGER_TYPE_GCHAT",
|
|
39
|
+
"TRIGGER_TYPE_SLACK",
|
|
40
|
+
"TRIGGER_TYPE_MSTEAMS",
|
|
41
|
+
"TRIGGER_TYPE_EMAIL",
|
|
42
|
+
"TRIGGER_TYPE_VOICE",
|
|
43
|
+
"TRIGGER_TYPE_DOCUMENT",
|
|
44
|
+
"TRIGGER_TYPE_DASHBOARD",
|
|
45
|
+
"TRIGGER_TYPE_ORCHESTRATION",
|
|
46
|
+
"TRIGGER_TYPE_API",
|
|
47
|
+
]);
|
|
48
|
+
/** Persona state - current operational status */
|
|
49
|
+
export const PersonaStateSchema = z.enum([
|
|
50
|
+
"DISABLED_BY_USER",
|
|
51
|
+
"DISABLED_BY_SYSTEM",
|
|
52
|
+
"READY",
|
|
53
|
+
"DRAFT",
|
|
54
|
+
]);
|
|
55
|
+
/** Persona access level - who can use the AI Employee */
|
|
56
|
+
export const PersonaAccessLevelSchema = z.enum([
|
|
57
|
+
"PERSONA_ACCESS_LEVEL_PRIVATE",
|
|
58
|
+
"PERSONA_ACCESS_LEVEL_TEAM",
|
|
59
|
+
"PERSONA_ACCESS_LEVEL_PUBLIC",
|
|
60
|
+
]);
|
|
61
|
+
/** Conversation source - where the conversation originated */
|
|
62
|
+
export const ConversationSourceSchema = z.enum([
|
|
63
|
+
"webapp",
|
|
64
|
+
"chatbot",
|
|
65
|
+
"gchat",
|
|
66
|
+
"msteams",
|
|
67
|
+
"slack",
|
|
68
|
+
"email",
|
|
69
|
+
"voice",
|
|
70
|
+
]);
|
|
71
|
+
/** Message sentiment for feedback */
|
|
72
|
+
export const SentimentSchema = z.enum(["positive", "negative", "neutral"]);
|
|
73
|
+
/** Processing result status */
|
|
74
|
+
export const ProcessingResultSchema = z.enum([
|
|
75
|
+
"success",
|
|
76
|
+
"warning",
|
|
77
|
+
"error",
|
|
78
|
+
"in_progress",
|
|
79
|
+
"indexing",
|
|
80
|
+
]);
|
|
81
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
82
|
+
// Welcome Messages
|
|
83
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
84
|
+
export const WelcomeMessageSingleSchema = z.object({
|
|
85
|
+
display_text: z.string().optional(),
|
|
86
|
+
actual_prompt: z.string().optional(),
|
|
87
|
+
}).passthrough();
|
|
88
|
+
export const WelcomeMessageSectionSchema = z.object({
|
|
89
|
+
title: z.string().optional(),
|
|
90
|
+
messages: z.array(WelcomeMessageSingleSchema).optional(),
|
|
91
|
+
}).passthrough();
|
|
92
|
+
export const WelcomeMessageSchema = z.object({
|
|
93
|
+
sections: z.array(WelcomeMessageSectionSchema).optional(),
|
|
94
|
+
is_default: z.boolean().optional(),
|
|
95
|
+
}).passthrough();
|
|
96
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
97
|
+
// Persona (AI Employee)
|
|
98
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
99
|
+
/** AI Employee (Persona) data transfer object */
|
|
100
|
+
export const PersonaDTOSchema = z.object({
|
|
101
|
+
/** Unique identifier for the AI Employee */
|
|
102
|
+
id: z.string(),
|
|
103
|
+
/** ID of the template this persona was created from */
|
|
104
|
+
template_id: z.string().optional().nullable(),
|
|
105
|
+
templateId: z.string().optional().nullable(), // Legacy camelCase variant
|
|
106
|
+
/** ID of the workflow powering this AI Employee */
|
|
107
|
+
workflow_id: z.string().optional().nullable(),
|
|
108
|
+
/** Complete workflow definition */
|
|
109
|
+
workflow_def: z.record(z.unknown()).optional().nullable(),
|
|
110
|
+
/** Workflow interface defining inputs and outputs */
|
|
111
|
+
workflow_interface: z.record(z.unknown()).optional().nullable(),
|
|
112
|
+
/** Display name of the AI Employee */
|
|
113
|
+
name: z.string().optional(),
|
|
114
|
+
display_name: z.string().optional(),
|
|
115
|
+
/** Human-readable description */
|
|
116
|
+
description: z.string().optional().nullable(),
|
|
117
|
+
/** Protocol buffer configuration */
|
|
118
|
+
proto_config: z.record(z.unknown()).optional().nullable(),
|
|
119
|
+
/** Current state (READY, DRAFT, DISABLED_BY_USER, DISABLED_BY_SYSTEM) */
|
|
120
|
+
status: z.string().optional(),
|
|
121
|
+
computed_state: PersonaStateSchema.optional(),
|
|
122
|
+
/** Log of status changes and metadata */
|
|
123
|
+
status_log: z.record(z.unknown()).optional().nullable(),
|
|
124
|
+
/** Welcome messages displayed when starting a conversation */
|
|
125
|
+
welcome_messages: WelcomeMessageSchema.optional().nullable(),
|
|
126
|
+
/** Whether this persona supports projects */
|
|
127
|
+
has_project_template: z.boolean().optional(),
|
|
128
|
+
/** Access level controlling who can use this AI Employee */
|
|
129
|
+
access_level: z.string().optional(),
|
|
130
|
+
/** How this AI Employee is triggered */
|
|
131
|
+
trigger_type: z.string().optional(),
|
|
132
|
+
/** ID of the associated workflow dashboard */
|
|
133
|
+
workflow_dashboard_id: z.string().optional().nullable(),
|
|
134
|
+
/** Type of interaction supported */
|
|
135
|
+
persona_interaction_type: z.string().optional(),
|
|
136
|
+
/** Whether this AI Employee can be embedded */
|
|
137
|
+
embedding_enabled: z.boolean().optional(),
|
|
138
|
+
/** Whether user has enabled this persona */
|
|
139
|
+
enabled_by_user: z.boolean().optional(),
|
|
140
|
+
/** Creation timestamp */
|
|
141
|
+
created_at: DateTimeSchema.optional().nullable(),
|
|
142
|
+
/** Last update timestamp */
|
|
143
|
+
updated_at: DateTimeSchema.optional().nullable(),
|
|
144
|
+
}).passthrough();
|
|
145
|
+
/** Response from list personas endpoint */
|
|
146
|
+
export const GetPersonasForTenantResponseSchema = z.object({
|
|
147
|
+
personas: z.array(PersonaDTOSchema).optional(),
|
|
148
|
+
configs: z.array(PersonaDTOSchema).optional(), // Legacy field
|
|
149
|
+
}).passthrough();
|
|
150
|
+
/** Request to create an AI Employee */
|
|
151
|
+
export const CreatePersonaRequestSchema = z.object({
|
|
152
|
+
name: z.string(),
|
|
153
|
+
description: z.string().optional(),
|
|
154
|
+
template_id: z.string().optional(),
|
|
155
|
+
source_persona_id: z.string().optional(),
|
|
156
|
+
proto_config: z.record(z.unknown()).optional(),
|
|
157
|
+
welcome_messages: WelcomeMessageSchema.optional(),
|
|
158
|
+
trigger_type: z.string().optional(),
|
|
159
|
+
});
|
|
160
|
+
/** Response from create persona endpoint */
|
|
161
|
+
export const CreatePersonaResponseSchema = z.object({
|
|
162
|
+
persona_id: z.string().optional(),
|
|
163
|
+
id: z.string().optional(),
|
|
164
|
+
status: z.string().optional(),
|
|
165
|
+
}).passthrough();
|
|
166
|
+
/** Request to update an AI Employee */
|
|
167
|
+
export const UpdatePersonaRequestSchema = z.object({
|
|
168
|
+
persona_id: z.string(),
|
|
169
|
+
name: z.string().optional(),
|
|
170
|
+
description: z.string().optional(),
|
|
171
|
+
proto_config: z.record(z.unknown()).optional(),
|
|
172
|
+
welcome_messages: WelcomeMessageSchema.optional(),
|
|
173
|
+
workflow: z.record(z.unknown()).optional(),
|
|
174
|
+
embedding_enabled: z.boolean().optional(),
|
|
175
|
+
enabled_by_user: z.boolean().optional(),
|
|
176
|
+
status_log: z.record(z.unknown()).optional(),
|
|
177
|
+
});
|
|
178
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
179
|
+
// Actions (Agents in UI)
|
|
180
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
181
|
+
export const ActionParameterTypeSchema = z.object({
|
|
182
|
+
well_known_type: z.number().optional(),
|
|
183
|
+
wellKnownType: z.number().optional(),
|
|
184
|
+
is_list: z.boolean().optional(),
|
|
185
|
+
isList: z.boolean().optional(),
|
|
186
|
+
}).passthrough();
|
|
187
|
+
export const ActionParameterSchema = z.object({
|
|
188
|
+
name: z.string().optional(),
|
|
189
|
+
description: z.string().optional(),
|
|
190
|
+
type: ActionParameterTypeSchema.optional(),
|
|
191
|
+
required: z.boolean().optional(),
|
|
192
|
+
default_value: z.unknown().optional(),
|
|
193
|
+
}).passthrough();
|
|
194
|
+
export const ActionDTOSchema = z.object({
|
|
195
|
+
/** Unique identifier for the action */
|
|
196
|
+
id: z.string(),
|
|
197
|
+
/** Display name of the action (shown as "Agent" in UI) */
|
|
198
|
+
name: z.string().optional(),
|
|
199
|
+
/** Description of what the action does */
|
|
200
|
+
description: z.string().optional(),
|
|
201
|
+
/** Category or type of the action */
|
|
202
|
+
category: z.string().optional(),
|
|
203
|
+
/** Input parameters for the action */
|
|
204
|
+
inputs: z.array(ActionParameterSchema).optional(),
|
|
205
|
+
/** Output parameters from the action */
|
|
206
|
+
outputs: z.array(ActionParameterSchema).optional(),
|
|
207
|
+
/** Whether the action is enabled */
|
|
208
|
+
enabled: z.boolean().optional(),
|
|
209
|
+
/** Whether the action is deprecated */
|
|
210
|
+
deprecated: z.boolean().optional(),
|
|
211
|
+
/** Version of the action */
|
|
212
|
+
version: z.string().optional(),
|
|
213
|
+
/** Icon URL or identifier */
|
|
214
|
+
icon: z.string().optional(),
|
|
215
|
+
/** Tags for categorization */
|
|
216
|
+
tags: z.array(z.string()).optional(),
|
|
217
|
+
/** The workflow this action belongs to */
|
|
218
|
+
workflow_id: z.string().optional(),
|
|
219
|
+
/** Creation timestamp */
|
|
220
|
+
created_at: DateTimeSchema.optional(),
|
|
221
|
+
/** Last update timestamp */
|
|
222
|
+
updated_at: DateTimeSchema.optional(),
|
|
223
|
+
}).passthrough();
|
|
224
|
+
export const ListActionsResponseSchema = z.object({
|
|
225
|
+
actions: z.array(ActionDTOSchema).optional(),
|
|
226
|
+
}).passthrough();
|
|
227
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
228
|
+
// Conversations
|
|
229
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
230
|
+
export const ConversationDTOSchema = z.object({
|
|
231
|
+
id: z.string(),
|
|
232
|
+
display_name: z.string().optional().nullable(),
|
|
233
|
+
created_at: DateTimeSchema.optional().nullable(),
|
|
234
|
+
updated_at: DateTimeSchema.optional().nullable(),
|
|
235
|
+
is_blocked: z.boolean().optional().nullable(),
|
|
236
|
+
is_deleted: z.boolean().optional().nullable(),
|
|
237
|
+
persona_id: z.string().optional().nullable(),
|
|
238
|
+
persona_display_name: z.string().optional().nullable(),
|
|
239
|
+
persona_project_id: z.string().optional().nullable(),
|
|
240
|
+
persona_project_display_name: z.string().optional().nullable(),
|
|
241
|
+
status: z.string().optional().nullable(),
|
|
242
|
+
status_desc: z.string().optional().nullable(),
|
|
243
|
+
}).passthrough();
|
|
244
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
245
|
+
// Messages
|
|
246
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
247
|
+
export const SnippetDTOSchema = z.object({
|
|
248
|
+
id: z.string().optional(),
|
|
249
|
+
type: z.string().optional(),
|
|
250
|
+
content: z.record(z.unknown()).optional(),
|
|
251
|
+
context: z.string().optional(),
|
|
252
|
+
}).passthrough();
|
|
253
|
+
export const MessageDTOSchema = z.object({
|
|
254
|
+
id: z.string(),
|
|
255
|
+
content_payload: z.record(z.string()).optional().nullable(),
|
|
256
|
+
sender_id: z.string().optional().nullable(),
|
|
257
|
+
created_at: DateTimeSchema.optional().nullable(),
|
|
258
|
+
updated_at: DateTimeSchema.optional().nullable(),
|
|
259
|
+
sentiment: SentimentSchema.optional().nullable(),
|
|
260
|
+
feedback_content: z.string().optional().nullable(),
|
|
261
|
+
snippets: z.array(SnippetDTOSchema).optional().nullable(),
|
|
262
|
+
processing_status: z.string().optional().nullable(),
|
|
263
|
+
}).passthrough();
|
|
264
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
265
|
+
// Pagination
|
|
266
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
267
|
+
export const PaginationMetaSchema = z.object({
|
|
268
|
+
total: z.number(),
|
|
269
|
+
next_token: z.string().optional().nullable(),
|
|
270
|
+
prev_token: z.string().optional().nullable(),
|
|
271
|
+
});
|
|
272
|
+
export const PaginatedConversationsSchema = z.object({
|
|
273
|
+
conversations: z.array(ConversationDTOSchema),
|
|
274
|
+
meta: PaginationMetaSchema,
|
|
275
|
+
});
|
|
276
|
+
export const PaginatedMessagesSchema = z.object({
|
|
277
|
+
messages: z.array(MessageDTOSchema),
|
|
278
|
+
meta: PaginationMetaSchema,
|
|
279
|
+
});
|
|
280
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
281
|
+
// Projects
|
|
282
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
283
|
+
export const ProjectDTOSchema = z.object({
|
|
284
|
+
id: z.string(),
|
|
285
|
+
persona_id: z.string().optional(),
|
|
286
|
+
display_name: z.string().optional(),
|
|
287
|
+
description: z.string().optional().nullable(),
|
|
288
|
+
state: z.string().optional(),
|
|
289
|
+
created_at: DateTimeSchema.optional().nullable(),
|
|
290
|
+
updated_at: DateTimeSchema.optional().nullable(),
|
|
291
|
+
settings: z.record(z.unknown()).optional().nullable(),
|
|
292
|
+
}).passthrough();
|
|
293
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
294
|
+
// Templates
|
|
295
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
296
|
+
export const PersonaTemplateDTOSchema = z.object({
|
|
297
|
+
id: z.string(),
|
|
298
|
+
name: z.string().optional(),
|
|
299
|
+
description: z.string().optional().nullable(),
|
|
300
|
+
category: z.string().optional().nullable(),
|
|
301
|
+
agent_name: z.string().optional().nullable(),
|
|
302
|
+
trigger_type: z.string().optional().nullable(),
|
|
303
|
+
proto_config: z.record(z.unknown()).optional().nullable(),
|
|
304
|
+
workflow_def: z.record(z.unknown()).optional().nullable(),
|
|
305
|
+
has_project_template: z.boolean().optional(),
|
|
306
|
+
about_template: z.string().optional().nullable(),
|
|
307
|
+
is_system_template: z.boolean().optional().nullable(),
|
|
308
|
+
}).passthrough();
|
|
309
|
+
export const GetPersonaTemplatesResponseSchema = z.object({
|
|
310
|
+
templates: z.array(PersonaTemplateDTOSchema),
|
|
311
|
+
category_options: z.array(z.object({
|
|
312
|
+
value: z.string(),
|
|
313
|
+
name: z.string(),
|
|
314
|
+
})).optional(),
|
|
315
|
+
});
|
|
316
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
317
|
+
// Chat / Chatbot
|
|
318
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
319
|
+
export const ChatbotMessageSchema = z.object({
|
|
320
|
+
message_id: z.string().optional(),
|
|
321
|
+
message_type: z.number().optional(),
|
|
322
|
+
content: z.string().optional(),
|
|
323
|
+
role: z.string().optional(),
|
|
324
|
+
}).passthrough();
|
|
325
|
+
export const ChatbotResponseSchema = z.object({
|
|
326
|
+
conversation_id: z.string().optional(),
|
|
327
|
+
message: ChatbotMessageSchema.optional(),
|
|
328
|
+
success: z.boolean().optional(),
|
|
329
|
+
}).passthrough();
|
|
330
|
+
export const ChatbotSDKConfigResponseSchema = z.object({
|
|
331
|
+
persona_id: z.string().optional(),
|
|
332
|
+
theme: z.record(z.unknown()).optional(),
|
|
333
|
+
header: z.record(z.unknown()).optional(),
|
|
334
|
+
welcome_message: z.record(z.unknown()).optional(),
|
|
335
|
+
}).passthrough();
|
|
336
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
337
|
+
// Error Responses
|
|
338
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
339
|
+
export const ErrorDetailSchema = z.object({
|
|
340
|
+
field: z.string().optional().nullable(),
|
|
341
|
+
message: z.string(),
|
|
342
|
+
code: z.string().optional().nullable(),
|
|
343
|
+
});
|
|
344
|
+
export const ErrorResponseSchema = z.object({
|
|
345
|
+
error: z.string(),
|
|
346
|
+
message: z.string(),
|
|
347
|
+
details: z.array(ErrorDetailSchema).optional().nullable(),
|
|
348
|
+
request_id: z.string().optional().nullable(),
|
|
349
|
+
timestamp: DateTimeSchema.optional(),
|
|
350
|
+
path: z.string().optional().nullable(),
|
|
351
|
+
});
|
|
352
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
353
|
+
// Sync Metadata (MCP Toolkit internal)
|
|
354
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
355
|
+
export const SyncMetadataSchema = z.object({
|
|
356
|
+
master_env: z.string(),
|
|
357
|
+
master_id: z.string(),
|
|
358
|
+
synced_at: z.string(),
|
|
359
|
+
master_fingerprint: z.string().optional(),
|
|
360
|
+
sync_run_id: z.string().optional(),
|
|
361
|
+
});
|
|
362
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
363
|
+
// Validation Helpers
|
|
364
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
365
|
+
/**
|
|
366
|
+
* Validate an API response against a schema.
|
|
367
|
+
* Returns the parsed data if valid, throws ZodError if invalid.
|
|
368
|
+
*
|
|
369
|
+
* @example
|
|
370
|
+
* const persona = validateResponse(PersonaDTOSchema, apiResponse);
|
|
371
|
+
*/
|
|
372
|
+
export function validateResponse(schema, data) {
|
|
373
|
+
return schema.parse(data);
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Safely validate an API response.
|
|
377
|
+
* Returns { success: true, data } or { success: false, error }.
|
|
378
|
+
*
|
|
379
|
+
* @example
|
|
380
|
+
* const result = safeValidateResponse(PersonaDTOSchema, apiResponse);
|
|
381
|
+
* if (result.success) {
|
|
382
|
+
* console.log(result.data.name);
|
|
383
|
+
* } else {
|
|
384
|
+
* console.error(result.error.issues);
|
|
385
|
+
* }
|
|
386
|
+
*/
|
|
387
|
+
export function safeValidateResponse(schema, data) {
|
|
388
|
+
return schema.safeParse(data);
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Validate a list of items, returning only the valid ones.
|
|
392
|
+
* Invalid items are logged as warnings but don't cause the entire validation to fail.
|
|
393
|
+
*
|
|
394
|
+
* @example
|
|
395
|
+
* const personas = validateList(PersonaDTOSchema, apiResponse.personas, "personas");
|
|
396
|
+
*/
|
|
397
|
+
export function validateList(schema, items, context) {
|
|
398
|
+
const valid = [];
|
|
399
|
+
for (let i = 0; i < items.length; i++) {
|
|
400
|
+
const result = schema.safeParse(items[i]);
|
|
401
|
+
if (result.success) {
|
|
402
|
+
valid.push(result.data);
|
|
403
|
+
}
|
|
404
|
+
else {
|
|
405
|
+
console.warn(`[contracts] Invalid item at index ${i}${context ? ` in ${context}` : ""}:`, result.error.issues.map(issue => `${issue.path.join(".")}: ${issue.message}`).join(", "));
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
return valid;
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Type guard to check if a value matches a schema.
|
|
412
|
+
*
|
|
413
|
+
* @example
|
|
414
|
+
* if (isValid(PersonaDTOSchema, data)) {
|
|
415
|
+
* // data is typed as PersonaDTO here
|
|
416
|
+
* }
|
|
417
|
+
*/
|
|
418
|
+
export function isValid(schema, data) {
|
|
419
|
+
return schema.safeParse(data).success;
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Extract validation errors in a user-friendly format.
|
|
423
|
+
*/
|
|
424
|
+
export function formatValidationErrors(error) {
|
|
425
|
+
return error.issues.map(issue => {
|
|
426
|
+
const path = issue.path.join(".");
|
|
427
|
+
return path ? `${path}: ${issue.message}` : issue.message;
|
|
428
|
+
});
|
|
429
|
+
}
|