@langwatch/mcp-server 0.5.0 → 0.6.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.
Files changed (89) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/dist/{archive-scenario-GAE4XVFM.js → archive-scenario-YFD5THOR.js} +3 -3
  3. package/dist/archive-scenario-YFD5THOR.js.map +1 -0
  4. package/dist/chunk-5UOPNRXW.js +37 -0
  5. package/dist/chunk-5UOPNRXW.js.map +1 -0
  6. package/dist/{chunk-K2YFPOSD.js → chunk-6U4TCGFC.js} +2 -2
  7. package/dist/chunk-IX6QJKAD.js +22 -0
  8. package/dist/chunk-IX6QJKAD.js.map +1 -0
  9. package/dist/{chunk-JVWDWL3J.js → chunk-LLRQIF52.js} +3 -11
  10. package/dist/chunk-LLRQIF52.js.map +1 -0
  11. package/dist/create-evaluator-E5X5ZP3B.js +27 -0
  12. package/dist/create-evaluator-E5X5ZP3B.js.map +1 -0
  13. package/dist/create-prompt-7Z35MIL6.js +36 -0
  14. package/dist/create-prompt-7Z35MIL6.js.map +1 -0
  15. package/dist/{create-scenario-3YRZVDYF.js → create-scenario-DIMPJRPY.js} +3 -3
  16. package/dist/create-scenario-DIMPJRPY.js.map +1 -0
  17. package/dist/discover-evaluator-schema-H23XCLNE.js +1402 -0
  18. package/dist/discover-evaluator-schema-H23XCLNE.js.map +1 -0
  19. package/dist/{get-analytics-BAVXTAPB.js → get-analytics-4YJW4S5L.js} +2 -2
  20. package/dist/get-evaluator-WDEH2F7M.js +47 -0
  21. package/dist/get-evaluator-WDEH2F7M.js.map +1 -0
  22. package/dist/{get-prompt-LKCPT26O.js → get-prompt-F6PDVC76.js} +2 -5
  23. package/dist/get-prompt-F6PDVC76.js.map +1 -0
  24. package/dist/{get-scenario-3SCDW4Z6.js → get-scenario-H24ZYNT5.js} +3 -3
  25. package/dist/{get-trace-QFDWJ5D4.js → get-trace-27USKGO7.js} +2 -2
  26. package/dist/index.js +13310 -2410
  27. package/dist/index.js.map +1 -1
  28. package/dist/list-evaluators-KRGI72EH.js +34 -0
  29. package/dist/list-evaluators-KRGI72EH.js.map +1 -0
  30. package/dist/list-model-providers-A5YCFTPI.js +35 -0
  31. package/dist/list-model-providers-A5YCFTPI.js.map +1 -0
  32. package/dist/{list-prompts-UQPBCUYA.js → list-prompts-LKJSE7XN.js} +6 -7
  33. package/dist/list-prompts-LKJSE7XN.js.map +1 -0
  34. package/dist/{list-scenarios-573YOUKC.js → list-scenarios-ZK5CMGC4.js} +5 -5
  35. package/dist/list-scenarios-ZK5CMGC4.js.map +1 -0
  36. package/dist/{search-traces-RSMYCAN7.js → search-traces-SOKAAMAR.js} +2 -2
  37. package/dist/set-model-provider-7MGULZDH.js +33 -0
  38. package/dist/set-model-provider-7MGULZDH.js.map +1 -0
  39. package/dist/update-evaluator-A3XINFLJ.js +24 -0
  40. package/dist/update-evaluator-A3XINFLJ.js.map +1 -0
  41. package/dist/update-prompt-IW7X2UQM.js +22 -0
  42. package/dist/update-prompt-IW7X2UQM.js.map +1 -0
  43. package/dist/{update-scenario-SSGVOBJO.js → update-scenario-ZT7TOBFR.js} +3 -3
  44. package/dist/update-scenario-ZT7TOBFR.js.map +1 -0
  45. package/package.json +10 -10
  46. package/src/__tests__/all-tools.integration.test.ts +1337 -0
  47. package/src/__tests__/discover-evaluator-schema.unit.test.ts +89 -0
  48. package/src/__tests__/evaluator-tools.unit.test.ts +262 -0
  49. package/src/__tests__/integration.integration.test.ts +9 -34
  50. package/src/__tests__/langwatch-api.unit.test.ts +4 -32
  51. package/src/__tests__/model-provider-tools.unit.test.ts +190 -0
  52. package/src/__tests__/scenario-tools.integration.test.ts +5 -5
  53. package/src/__tests__/scenario-tools.unit.test.ts +2 -2
  54. package/src/__tests__/tools.unit.test.ts +59 -65
  55. package/src/index.ts +249 -88
  56. package/src/langwatch-api-evaluators.ts +70 -0
  57. package/src/langwatch-api-model-providers.ts +41 -0
  58. package/src/langwatch-api.ts +3 -28
  59. package/src/tools/archive-scenario.ts +1 -1
  60. package/src/tools/create-evaluator.ts +33 -0
  61. package/src/tools/create-prompt.ts +30 -5
  62. package/src/tools/create-scenario.ts +1 -1
  63. package/src/tools/discover-evaluator-schema.ts +143 -0
  64. package/src/tools/get-evaluator.ts +53 -0
  65. package/src/tools/get-prompt.ts +1 -4
  66. package/src/tools/list-evaluators.ts +37 -0
  67. package/src/tools/list-model-providers.ts +40 -0
  68. package/src/tools/list-prompts.ts +5 -6
  69. package/src/tools/list-scenarios.ts +3 -3
  70. package/src/tools/set-model-provider.ts +46 -0
  71. package/src/tools/update-evaluator.ts +30 -0
  72. package/src/tools/update-prompt.ts +9 -25
  73. package/src/tools/update-scenario.ts +1 -1
  74. package/dist/archive-scenario-GAE4XVFM.js.map +0 -1
  75. package/dist/chunk-JVWDWL3J.js.map +0 -1
  76. package/dist/create-prompt-P35POKBW.js +0 -22
  77. package/dist/create-prompt-P35POKBW.js.map +0 -1
  78. package/dist/create-scenario-3YRZVDYF.js.map +0 -1
  79. package/dist/get-prompt-LKCPT26O.js.map +0 -1
  80. package/dist/list-prompts-UQPBCUYA.js.map +0 -1
  81. package/dist/list-scenarios-573YOUKC.js.map +0 -1
  82. package/dist/update-prompt-G2Y5EBQY.js +0 -31
  83. package/dist/update-prompt-G2Y5EBQY.js.map +0 -1
  84. package/dist/update-scenario-SSGVOBJO.js.map +0 -1
  85. /package/dist/{chunk-K2YFPOSD.js.map → chunk-6U4TCGFC.js.map} +0 -0
  86. /package/dist/{get-analytics-BAVXTAPB.js.map → get-analytics-4YJW4S5L.js.map} +0 -0
  87. /package/dist/{get-scenario-3SCDW4Z6.js.map → get-scenario-H24ZYNT5.js.map} +0 -0
  88. /package/dist/{get-trace-QFDWJ5D4.js.map → get-trace-27USKGO7.js.map} +0 -0
  89. /package/dist/{search-traces-RSMYCAN7.js.map → search-traces-SOKAAMAR.js.map} +0 -0
@@ -0,0 +1,33 @@
1
+ import {
2
+ createEvaluator as apiCreateEvaluator,
3
+ getEvaluatorType,
4
+ } from "../langwatch-api-evaluators.js";
5
+
6
+ /**
7
+ * Handles the platform_create_evaluator MCP tool invocation.
8
+ *
9
+ * Creates a new evaluator in the LangWatch project and returns a
10
+ * confirmation with the created evaluator's details.
11
+ */
12
+ export async function handleCreateEvaluator(params: {
13
+ name: string;
14
+ config: Record<string, unknown>;
15
+ }): Promise<string> {
16
+ const result = await apiCreateEvaluator(params);
17
+
18
+ const evaluatorType = getEvaluatorType(result);
19
+
20
+ const lines: string[] = [];
21
+ lines.push("Evaluator created successfully!\n");
22
+ lines.push(`**ID**: ${result.id}`);
23
+ if (result.slug) lines.push(`**Slug**: ${result.slug}`);
24
+ lines.push(`**Name**: ${result.name}`);
25
+ if (evaluatorType) lines.push(`**Evaluator Type**: ${evaluatorType}`);
26
+ lines.push(`**Kind**: ${result.type}`);
27
+
28
+ if (Array.isArray(result.fields) && result.fields.length > 0) {
29
+ lines.push(`**Input Fields**: ${result.fields.map((f) => f.identifier).join(", ")}`);
30
+ }
31
+
32
+ return lines.join("\n");
33
+ }
@@ -1,7 +1,23 @@
1
1
  import { createPrompt as apiCreatePrompt } from "../langwatch-api.js";
2
2
 
3
+ const HANDLE_PATTERN = /^[a-z0-9_-]+(?:\/[a-z0-9_-]+)?$/;
4
+
3
5
  /**
4
- * Handles the create_prompt MCP tool invocation.
6
+ * Converts a human-readable name into a URL-friendly handle.
7
+ *
8
+ * Lowercases the input, replaces non-alphanumeric runs with hyphens,
9
+ * and strips leading/trailing hyphens. May return an empty string
10
+ * for inputs with no alphanumeric characters — callers must validate.
11
+ */
12
+ function toHandle(name: string): string {
13
+ return name
14
+ .toLowerCase()
15
+ .replace(/[^a-z0-9]+/g, "-")
16
+ .replace(/^-|-$/g, "");
17
+ }
18
+
19
+ /**
20
+ * Handles the platform_create_prompt MCP tool invocation.
5
21
  *
6
22
  * Creates a new prompt in the LangWatch project and returns a
7
23
  * confirmation with the created prompt's details.
@@ -11,17 +27,26 @@ export async function handleCreatePrompt(params: {
11
27
  handle?: string;
12
28
  messages: Array<{ role: string; content: string }>;
13
29
  model: string;
14
- modelProvider: string;
15
- description?: string;
16
30
  }): Promise<string> {
17
- const result = await apiCreatePrompt(params);
31
+ const handle = params.handle?.trim() || toHandle(params.name);
32
+ if (!handle || !HANDLE_PATTERN.test(handle)) {
33
+ throw new Error(
34
+ `Invalid prompt handle "${handle || ""}". Handle must match ${HANDLE_PATTERN}. Provide a valid \`handle\` explicitly.`
35
+ );
36
+ }
37
+
38
+ const result = await apiCreatePrompt({
39
+ handle,
40
+ messages: params.messages,
41
+ model: params.model,
42
+ });
18
43
 
19
44
  const lines: string[] = [];
20
45
  lines.push("Prompt created successfully!\n");
21
46
  if (result.id) lines.push(`**ID**: ${result.id}`);
22
47
  if (result.handle) lines.push(`**Handle**: ${result.handle}`);
23
48
  lines.push(`**Name**: ${result.name || params.name}`);
24
- lines.push(`**Model**: ${params.model} (${params.modelProvider})`);
49
+ lines.push(`**Model**: ${params.model}`);
25
50
  if (result.latestVersionNumber != null)
26
51
  lines.push(`**Version**: v${result.latestVersionNumber}`);
27
52
 
@@ -1,7 +1,7 @@
1
1
  import { createScenario as apiCreateScenario } from "../langwatch-api-scenarios.js";
2
2
 
3
3
  /**
4
- * Handles the create_scenario MCP tool invocation.
4
+ * Handles the platform_create_scenario MCP tool invocation.
5
5
  *
6
6
  * Creates a new scenario in the LangWatch project and returns a
7
7
  * confirmation with the created scenario's details.
@@ -0,0 +1,143 @@
1
+ import { AVAILABLE_EVALUATORS } from "../../../langevals/ts-integration/evaluators.generated.js";
2
+ import type { EvaluatorDefinition, EvaluatorTypes } from "../../../langevals/ts-integration/evaluators.generated.js";
3
+
4
+ /**
5
+ * Formats evaluator schema information for the discover_schema tool.
6
+ *
7
+ * Two levels of detail:
8
+ * - Overview (no evaluatorType): compact list of all evaluator types
9
+ * - Detail (with evaluatorType): full schema for one evaluator type
10
+ */
11
+ export function formatEvaluatorSchema(evaluatorType?: string): string {
12
+ if (evaluatorType) {
13
+ return formatEvaluatorDetail(evaluatorType);
14
+ }
15
+ return formatEvaluatorOverview();
16
+ }
17
+
18
+ /**
19
+ * Returns a compact overview of all available evaluator types.
20
+ * Shows type, name, category, and a one-line description.
21
+ */
22
+ function formatEvaluatorOverview(): string {
23
+ const lines: string[] = [];
24
+ lines.push("# Available Evaluator Types\n");
25
+
26
+ const byCategory = new Map<string, { type: string; name: string; description: string }[]>();
27
+
28
+ for (const [type, def] of Object.entries(AVAILABLE_EVALUATORS)) {
29
+ const evalDef = def as EvaluatorDefinition<EvaluatorTypes>;
30
+ const oneLine = extractFirstLine(evalDef.description);
31
+ const entry = { type, name: evalDef.name, description: oneLine };
32
+
33
+ const list = byCategory.get(evalDef.category) ?? [];
34
+ list.push(entry);
35
+ byCategory.set(evalDef.category, list);
36
+ }
37
+
38
+ for (const [category, entries] of byCategory) {
39
+ lines.push(`## ${category}\n`);
40
+ for (const entry of entries) {
41
+ lines.push(`- **${entry.type}** (${entry.name}): ${entry.description}`);
42
+ }
43
+ lines.push("");
44
+ }
45
+
46
+ lines.push(
47
+ "> Use `discover_schema({ category: 'evaluators', evaluatorType: '<type>' })` for full details on a specific evaluator type.",
48
+ );
49
+
50
+ return lines.join("\n");
51
+ }
52
+
53
+ /**
54
+ * Returns the full schema for a specific evaluator type.
55
+ * Includes settings with descriptions and defaults, required/optional fields, env vars, and result fields.
56
+ */
57
+ function formatEvaluatorDetail(evaluatorType: string): string {
58
+ const def = AVAILABLE_EVALUATORS[evaluatorType as EvaluatorTypes] as
59
+ | EvaluatorDefinition<EvaluatorTypes>
60
+ | undefined;
61
+
62
+ if (!def) {
63
+ return `Unknown evaluator type: "${evaluatorType}". Use \`discover_schema({ category: 'evaluators' })\` to see all available types.`;
64
+ }
65
+
66
+ const lines: string[] = [];
67
+ lines.push(`# ${def.name} (\`${evaluatorType}\`)\n`);
68
+ lines.push(`**Category**: ${def.category}`);
69
+ lines.push(`**Is Guardrail**: ${def.isGuardrail ? "Yes" : "No"}`);
70
+ if (def.docsUrl) {
71
+ lines.push(`**Docs**: ${def.docsUrl}`);
72
+ }
73
+ lines.push("");
74
+ lines.push(`## Description\n`);
75
+ lines.push(def.description.trim());
76
+
77
+ // Required and optional fields
78
+ lines.push("\n## Fields\n");
79
+ if (def.requiredFields.length > 0) {
80
+ lines.push(`**Required**: ${def.requiredFields.join(", ")}`);
81
+ } else {
82
+ lines.push("**Required**: none");
83
+ }
84
+ if (def.optionalFields.length > 0) {
85
+ lines.push(`**Optional**: ${def.optionalFields.join(", ")}`);
86
+ }
87
+
88
+ // Settings
89
+ const settingsEntries = Object.entries(def.settings);
90
+ if (settingsEntries.length > 0) {
91
+ lines.push("\n## Settings\n");
92
+ for (const [key, setting] of settingsEntries) {
93
+ const s = setting as { description?: string; default: unknown };
94
+ const defaultStr = JSON.stringify(s.default);
95
+ const desc = s.description ? ` - ${s.description}` : "";
96
+ lines.push(`- **${key}**${desc}`);
97
+ lines.push(` Default: \`${defaultStr}\``);
98
+ }
99
+ }
100
+
101
+ // Env vars
102
+ if (def.envVars.length > 0) {
103
+ lines.push("\n## Required Environment Variables\n");
104
+ for (const envVar of def.envVars) {
105
+ lines.push(`- \`${envVar}\``);
106
+ }
107
+ }
108
+
109
+ // Result fields
110
+ const resultEntries = Object.entries(def.result);
111
+ if (resultEntries.length > 0) {
112
+ lines.push("\n## Result Fields\n");
113
+ for (const [key, value] of resultEntries) {
114
+ const v = value as { description: string };
115
+ lines.push(`- **${key}**: ${v.description}`);
116
+ }
117
+ }
118
+
119
+ lines.push("\n## Usage Example\n");
120
+ lines.push("```json");
121
+ lines.push(JSON.stringify({
122
+ evaluatorType: evaluatorType,
123
+ settings: Object.fromEntries(
124
+ settingsEntries.map(([key, setting]) => [key, (setting as { default: unknown }).default]),
125
+ ),
126
+ }, null, 2));
127
+ lines.push("```");
128
+
129
+ return lines.join("\n");
130
+ }
131
+
132
+ /**
133
+ * Extracts the first meaningful line from a multi-line description.
134
+ */
135
+ function extractFirstLine(description: string): string {
136
+ const trimmed = description.trim();
137
+ const firstLine = trimmed.split("\n")[0]?.trim() ?? trimmed;
138
+ // Limit to a reasonable length
139
+ if (firstLine.length > 120) {
140
+ return firstLine.slice(0, 117) + "...";
141
+ }
142
+ return firstLine;
143
+ }
@@ -0,0 +1,53 @@
1
+ import {
2
+ getEvaluator as apiGetEvaluator,
3
+ getEvaluatorType,
4
+ } from "../langwatch-api-evaluators.js";
5
+
6
+ /**
7
+ * Handles the platform_get_evaluator MCP tool invocation.
8
+ *
9
+ * Retrieves a specific evaluator by ID or slug and formats it as
10
+ * AI-readable markdown.
11
+ */
12
+ export async function handleGetEvaluator(params: {
13
+ idOrSlug: string;
14
+ }): Promise<string> {
15
+ const evaluator = await apiGetEvaluator(params.idOrSlug);
16
+
17
+ const evaluatorType = getEvaluatorType(evaluator);
18
+
19
+ const lines: string[] = [];
20
+ lines.push(`# Evaluator: ${evaluator.name}\n`);
21
+ lines.push(`**ID**: ${evaluator.id}`);
22
+ if (evaluator.slug) lines.push(`**Slug**: ${evaluator.slug}`);
23
+ lines.push(`**Kind**: ${evaluator.type}`);
24
+ if (evaluatorType) lines.push(`**Evaluator Type**: ${evaluatorType}`);
25
+
26
+ if (evaluator.config) {
27
+ lines.push("\n## Config");
28
+ lines.push("```json");
29
+ lines.push(JSON.stringify(evaluator.config, null, 2));
30
+ lines.push("```");
31
+ }
32
+
33
+ if (Array.isArray(evaluator.fields) && evaluator.fields.length > 0) {
34
+ lines.push("\n## Input Fields");
35
+ for (const field of evaluator.fields) {
36
+ const opt = field.optional ? " (optional)" : "";
37
+ lines.push(`- **${field.identifier}** (${field.type})${opt}`);
38
+ }
39
+ }
40
+
41
+ if (Array.isArray(evaluator.outputFields) && evaluator.outputFields.length > 0) {
42
+ lines.push("\n## Output Fields");
43
+ for (const field of evaluator.outputFields) {
44
+ lines.push(`- **${field.identifier}** (${field.type})`);
45
+ }
46
+ }
47
+
48
+ if (evaluator.workflowName) {
49
+ lines.push(`\n**Workflow**: ${evaluator.workflowName}`);
50
+ }
51
+
52
+ return lines.join("\n");
53
+ }
@@ -1,7 +1,7 @@
1
1
  import { getPrompt as apiGetPrompt } from "../langwatch-api.js";
2
2
 
3
3
  /**
4
- * Handles the get_prompt MCP tool invocation.
4
+ * Handles the platform_get_prompt MCP tool invocation.
5
5
  *
6
6
  * Retrieves a specific prompt by ID or handle and formats it as
7
7
  * AI-readable markdown, including messages, model config, and version history.
@@ -19,15 +19,12 @@ export async function handleGetPrompt(params: {
19
19
 
20
20
  if (prompt.handle) lines.push(`**Handle**: ${prompt.handle}`);
21
21
  if (prompt.id) lines.push(`**ID**: ${prompt.id}`);
22
- if (prompt.description) lines.push(`**Description**: ${prompt.description}`);
23
22
  if (prompt.latestVersionNumber != null)
24
23
  lines.push(`**Latest Version**: v${prompt.latestVersionNumber}`);
25
24
 
26
25
  // Show model config
27
26
  const version = prompt.versions?.[0] ?? prompt;
28
27
  if (version.model) lines.push(`**Model**: ${version.model}`);
29
- if (version.modelProvider)
30
- lines.push(`**Provider**: ${version.modelProvider}`);
31
28
 
32
29
  // Show messages
33
30
  const messages = version.messages || prompt.prompt || [];
@@ -0,0 +1,37 @@
1
+ import {
2
+ listEvaluators as apiListEvaluators,
3
+ getEvaluatorType,
4
+ } from "../langwatch-api-evaluators.js";
5
+
6
+ /**
7
+ * Handles the platform_list_evaluators MCP tool invocation.
8
+ *
9
+ * Lists all evaluators in the LangWatch project, formatted as an
10
+ * AI-readable digest.
11
+ */
12
+ export async function handleListEvaluators(): Promise<string> {
13
+ const evaluators = await apiListEvaluators();
14
+
15
+ if (!Array.isArray(evaluators) || evaluators.length === 0) {
16
+ return "No evaluators found in this project.\n\n> Tip: Use `platform_create_evaluator` to create your first evaluator. Call `discover_schema({ category: 'evaluators' })` to see available evaluator types.";
17
+ }
18
+
19
+ const lines: string[] = [];
20
+ lines.push(`# Evaluators (${evaluators.length} total)\n`);
21
+
22
+ for (const e of evaluators) {
23
+ const evaluatorType = getEvaluatorType(e);
24
+ lines.push(`## ${e.name}`);
25
+ lines.push(`**ID**: ${e.id}`);
26
+ if (e.slug) lines.push(`**Slug**: ${e.slug}`);
27
+ if (evaluatorType) lines.push(`**Type**: ${evaluatorType}`);
28
+ lines.push(`**Kind**: ${e.type}`);
29
+ lines.push("");
30
+ }
31
+
32
+ lines.push(
33
+ "> Use `platform_get_evaluator` with the ID or slug to see full evaluator details.",
34
+ );
35
+
36
+ return lines.join("\n");
37
+ }
@@ -0,0 +1,40 @@
1
+ import { listModelProviders as apiListModelProviders } from "../langwatch-api-model-providers.js";
2
+
3
+ /**
4
+ * Handles the platform_list_model_providers MCP tool invocation.
5
+ *
6
+ * Lists all model providers for the project, showing provider name,
7
+ * enabled status, and which key fields are set (masked).
8
+ */
9
+ export async function handleListModelProviders(): Promise<string> {
10
+ const providers = await apiListModelProviders();
11
+
12
+ const entries = Object.entries(providers);
13
+ if (entries.length === 0) {
14
+ return "No model providers configured for this project.\n\n> Tip: Use `platform_set_model_provider` to configure an API key for a model provider.";
15
+ }
16
+
17
+ const lines: string[] = [];
18
+ lines.push(`# Model Providers (${entries.length} total)\n`);
19
+
20
+ for (const [key, provider] of entries) {
21
+ const status = provider.enabled ? "enabled" : "disabled";
22
+ lines.push(`## ${key}`);
23
+ lines.push(`**Status**: ${status}`);
24
+
25
+ if (provider.customKeys) {
26
+ const keyFields = Object.entries(provider.customKeys)
27
+ .map(([k, v]) => `${k}: ${v ? "set" : "not set"}`)
28
+ .join(", ");
29
+ lines.push(`**Keys**: ${keyFields}`);
30
+ }
31
+
32
+ if (provider.models && provider.models.length > 0) {
33
+ lines.push(`**Models**: ${provider.models.length} available`);
34
+ }
35
+
36
+ lines.push("");
37
+ }
38
+
39
+ return lines.join("\n");
40
+ }
@@ -1,7 +1,7 @@
1
1
  import { listPrompts as apiListPrompts } from "../langwatch-api.js";
2
2
 
3
3
  /**
4
- * Handles the list_prompts MCP tool invocation.
4
+ * Handles the platform_list_prompts MCP tool invocation.
5
5
  *
6
6
  * Lists all prompts in the LangWatch project, formatted as an
7
7
  * AI-readable markdown table.
@@ -15,20 +15,19 @@ export async function handleListPrompts(): Promise<string> {
15
15
 
16
16
  const lines: string[] = [];
17
17
  lines.push(`# Prompts (${prompts.length} total)\n`);
18
- lines.push("| Handle | Name | Latest Version | Description |");
19
- lines.push("|--------|------|----------------|-------------|");
18
+ lines.push("| Handle | Name | Latest Version |");
19
+ lines.push("|--------|------|----------------|");
20
20
 
21
21
  for (const p of prompts) {
22
22
  const handle = p.handle || p.id || "N/A";
23
23
  const name = p.name || "Untitled";
24
24
  const versionNum = p.latestVersionNumber ?? p.version;
25
25
  const version = versionNum != null ? `v${versionNum}` : "N/A";
26
- const desc = (p.description || "").slice(0, 60);
27
- lines.push(`| ${handle} | ${name} | ${version} | ${desc} |`);
26
+ lines.push(`| ${handle} | ${name} | ${version} |`);
28
27
  }
29
28
 
30
29
  lines.push(
31
- "\n> Use `get_prompt` with the handle or ID to see full prompt details."
30
+ "\n> Use `platform_get_prompt` with the handle or ID to see full prompt details."
32
31
  );
33
32
 
34
33
  return lines.join("\n");
@@ -1,7 +1,7 @@
1
1
  import { listScenarios as apiListScenarios } from "../langwatch-api-scenarios.js";
2
2
 
3
3
  /**
4
- * Handles the list_scenarios MCP tool invocation.
4
+ * Handles the platform_list_scenarios MCP tool invocation.
5
5
  *
6
6
  * Lists all scenarios in the LangWatch project, formatted as an
7
7
  * AI-readable digest or raw JSON.
@@ -16,7 +16,7 @@ export async function handleListScenarios(params: {
16
16
  }
17
17
 
18
18
  if (!Array.isArray(scenarios) || scenarios.length === 0) {
19
- return "No scenarios found in this project.\n\n> Tip: Use `create_scenario` to create your first scenario.";
19
+ return "No scenarios found in this project.\n\n> Tip: Use `platform_create_scenario` to create your first scenario.";
20
20
  }
21
21
 
22
22
  const lines: string[] = [];
@@ -40,7 +40,7 @@ export async function handleListScenarios(params: {
40
40
  }
41
41
 
42
42
  lines.push(
43
- "> Use `get_scenario` with the ID to see full scenario details.",
43
+ "> Use `platform_get_scenario` with the ID to see full scenario details.",
44
44
  );
45
45
 
46
46
  return lines.join("\n");
@@ -0,0 +1,46 @@
1
+ import { setModelProvider as apiSetModelProvider } from "../langwatch-api-model-providers.js";
2
+
3
+ /**
4
+ * Handles the platform_set_model_provider MCP tool invocation.
5
+ *
6
+ * Creates or updates a model provider configuration, including API keys.
7
+ * Returns confirmation with the updated provider status.
8
+ */
9
+ export async function handleSetModelProvider(params: {
10
+ provider: string;
11
+ enabled: boolean;
12
+ customKeys?: Record<string, unknown>;
13
+ defaultModel?: string;
14
+ }): Promise<string> {
15
+ const providers = await apiSetModelProvider(params);
16
+
17
+ const updated = providers[params.provider];
18
+
19
+ if (!updated) {
20
+ throw new Error(
21
+ `Model provider "${params.provider}" was not found in the response. The provider name may be invalid.`
22
+ );
23
+ }
24
+
25
+ const lines: string[] = [];
26
+ lines.push("Model provider updated successfully!\n");
27
+ lines.push(`**Provider**: ${params.provider}`);
28
+ lines.push(`**Status**: ${updated?.enabled ? "enabled" : "disabled"}`);
29
+
30
+ if (updated?.customKeys) {
31
+ const keyFields = Object.entries(updated.customKeys)
32
+ .map(([k, v]) => `${k}: ${v ? "set" : "not set"}`)
33
+ .join(", ");
34
+ lines.push(`**Keys**: ${keyFields}`);
35
+ }
36
+
37
+ if (params.defaultModel) {
38
+ // Show the normalized model name (with provider prefix)
39
+ const normalizedModel = params.defaultModel.includes("/")
40
+ ? params.defaultModel
41
+ : `${params.provider}/${params.defaultModel}`;
42
+ lines.push(`**Default Model**: ${normalizedModel}`);
43
+ }
44
+
45
+ return lines.join("\n");
46
+ }
@@ -0,0 +1,30 @@
1
+ import {
2
+ updateEvaluator as apiUpdateEvaluator,
3
+ getEvaluatorType,
4
+ } from "../langwatch-api-evaluators.js";
5
+
6
+ /**
7
+ * Handles the platform_update_evaluator MCP tool invocation.
8
+ *
9
+ * Updates an existing evaluator and returns a confirmation
10
+ * with the updated details.
11
+ */
12
+ export async function handleUpdateEvaluator(params: {
13
+ evaluatorId: string;
14
+ name?: string;
15
+ config?: Record<string, unknown>;
16
+ }): Promise<string> {
17
+ const { evaluatorId, ...data } = params;
18
+ const result = await apiUpdateEvaluator({ id: evaluatorId, ...data });
19
+
20
+ const evaluatorType = getEvaluatorType(result);
21
+
22
+ const lines: string[] = [];
23
+ lines.push("Evaluator updated successfully!\n");
24
+ lines.push(`**ID**: ${result.id}`);
25
+ if (result.slug) lines.push(`**Slug**: ${result.slug}`);
26
+ lines.push(`**Name**: ${result.name}`);
27
+ if (evaluatorType) lines.push(`**Evaluator Type**: ${evaluatorType}`);
28
+
29
+ return lines.join("\n");
30
+ }
@@ -1,44 +1,28 @@
1
- import {
2
- updatePrompt as apiUpdatePrompt,
3
- createPromptVersion as apiCreateVersion,
4
- } from "../langwatch-api.js";
5
- import type { PromptMutationResponse } from "../langwatch-api.js";
1
+ import { updatePrompt as apiUpdatePrompt } from "../langwatch-api.js";
6
2
 
7
3
  /**
8
- * Handles the update_prompt MCP tool invocation.
4
+ * Handles the platform_update_prompt MCP tool invocation.
9
5
  *
10
- * Updates an existing prompt or creates a new version, depending on the
11
- * `createVersion` flag. Returns a confirmation with the updated details.
6
+ * Updates an existing prompt via the PUT endpoint.
7
+ * Every update with a commitMessage creates a new version automatically.
12
8
  */
13
9
  export async function handleUpdatePrompt(params: {
14
10
  idOrHandle: string;
15
11
  messages?: Array<{ role: string; content: string }>;
16
12
  model?: string;
17
- modelProvider?: string;
18
- commitMessage?: string;
19
- createVersion?: boolean;
13
+ commitMessage: string;
20
14
  }): Promise<string> {
21
- const { idOrHandle, createVersion, ...data } = params;
15
+ const { idOrHandle, ...data } = params;
22
16
 
23
- let result: PromptMutationResponse;
24
- if (createVersion) {
25
- result = await apiCreateVersion(idOrHandle, data);
26
- } else {
27
- result = await apiUpdatePrompt(idOrHandle, data);
28
- }
17
+ const result = await apiUpdatePrompt(idOrHandle, data);
29
18
 
30
19
  const lines: string[] = [];
31
- lines.push(
32
- createVersion
33
- ? "New version created successfully!\n"
34
- : "Prompt updated successfully!\n"
35
- );
20
+ lines.push("Prompt updated successfully!\n");
36
21
  if (result.id) lines.push(`**ID**: ${result.id}`);
37
22
  if (result.handle) lines.push(`**Handle**: ${result.handle}`);
38
23
  if (result.latestVersionNumber != null)
39
24
  lines.push(`**Version**: v${result.latestVersionNumber}`);
40
- if (params.commitMessage)
41
- lines.push(`**Commit**: ${params.commitMessage}`);
25
+ lines.push(`**Commit**: ${params.commitMessage}`);
42
26
 
43
27
  return lines.join("\n");
44
28
  }
@@ -1,7 +1,7 @@
1
1
  import { updateScenario as apiUpdateScenario } from "../langwatch-api-scenarios.js";
2
2
 
3
3
  /**
4
- * Handles the update_scenario MCP tool invocation.
4
+ * Handles the platform_update_scenario MCP tool invocation.
5
5
  *
6
6
  * Updates an existing scenario and returns a confirmation
7
7
  * with the updated details.
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/tools/archive-scenario.ts"],"sourcesContent":["import { archiveScenario as apiArchiveScenario } from \"../langwatch-api-scenarios.js\";\n\n/**\n * Handles the archive_scenario MCP tool invocation.\n *\n * Archives (soft-deletes) a scenario and returns confirmation.\n */\nexport async function handleArchiveScenario(params: {\n scenarioId: string;\n}): Promise<string> {\n const result = await apiArchiveScenario(params.scenarioId);\n\n const lines: string[] = [];\n lines.push(\"Scenario archived successfully!\\n\");\n lines.push(`**ID**: ${result.id}`);\n lines.push(`**Status**: ${result.archived ? \"archived\" : \"active\"}`);\n\n return lines.join(\"\\n\");\n}\n"],"mappings":";;;;;;;AAOA,eAAsB,sBAAsB,QAExB;AAClB,QAAM,SAAS,MAAM,gBAAmB,OAAO,UAAU;AAEzD,QAAM,QAAkB,CAAC;AACzB,QAAM,KAAK,mCAAmC;AAC9C,QAAM,KAAK,WAAW,OAAO,EAAE,EAAE;AACjC,QAAM,KAAK,eAAe,OAAO,WAAW,aAAa,QAAQ,EAAE;AAEnE,SAAO,MAAM,KAAK,IAAI;AACxB;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/langwatch-api.ts"],"sourcesContent":["import { getConfig, requireApiKey } from \"./config.js\";\n\n// --- Response types ---\n\nexport interface TraceSearchResult {\n trace_id: string;\n formatted_trace?: string;\n input?: { value: string };\n output?: { value: string };\n timestamps?: { started_at?: string | number };\n metadata?: Record<string, unknown>;\n error?: Record<string, unknown>;\n}\n\nexport interface SearchTracesResponse {\n traces: TraceSearchResult[];\n pagination?: {\n totalHits?: number;\n scrollId?: string;\n };\n}\n\nexport interface TraceDetailResponse {\n trace_id: string;\n formatted_trace?: string;\n input?: { value: string };\n output?: { value: string };\n timestamps?: {\n started_at?: string | number;\n updated_at?: string | number;\n inserted_at?: string | number;\n };\n metadata?: {\n user_id?: string;\n thread_id?: string;\n customer_id?: string;\n labels?: string[];\n [key: string]: unknown;\n };\n error?: Record<string, unknown>;\n ascii_tree?: string;\n evaluations?: Array<{\n evaluator_id?: string;\n name?: string;\n score?: number;\n passed?: boolean;\n label?: string;\n }>;\n spans?: Array<{\n span_id: string;\n name?: string;\n type?: string;\n model?: string;\n input?: { value: string };\n output?: { value: string };\n timestamps?: { started_at?: number; finished_at?: number };\n metrics?: {\n completion_time_ms?: number;\n prompt_tokens?: number;\n completion_tokens?: number;\n tokens_estimated?: boolean;\n cost?: number;\n };\n }>;\n}\n\nexport interface AnalyticsBucket {\n date: string;\n [key: string]: unknown;\n}\n\nexport interface AnalyticsTimeseriesResponse {\n currentPeriod: AnalyticsBucket[];\n previousPeriod: AnalyticsBucket[];\n}\n\nexport interface PromptSummary {\n id?: string;\n handle?: string;\n name?: string;\n description?: string | null;\n latestVersionNumber?: number;\n version?: number;\n}\n\nexport interface PromptVersion {\n version?: number;\n commitMessage?: string;\n model?: string;\n modelProvider?: string;\n messages?: Array<{ role: string; content: string }>;\n}\n\nexport interface PromptDetailResponse extends PromptSummary {\n versions?: PromptVersion[];\n model?: string;\n modelProvider?: string;\n messages?: Array<{ role: string; content: string }>;\n prompt?: Array<{ role: string; content: string }>;\n}\n\nexport interface PromptMutationResponse {\n id?: string;\n handle?: string;\n name?: string;\n latestVersionNumber?: number;\n}\n\n// --- HTTP client ---\n\n/**\n * Sends an HTTP request to the LangWatch API.\n *\n * Builds the full URL from the configured endpoint, adds authentication,\n * and handles JSON serialization/deserialization.\n *\n * @throws Error with status code and response body when the response is not OK\n */\nexport async function makeRequest(\n method: \"GET\" | \"POST\" | \"PUT\" | \"PATCH\" | \"DELETE\",\n path: string,\n body?: unknown\n): Promise<unknown> {\n const url = getConfig().endpoint + path;\n const headers: Record<string, string> = {\n \"X-Auth-Token\": requireApiKey(),\n };\n\n if (body !== undefined) {\n headers[\"Content-Type\"] = \"application/json\";\n }\n\n const response = await fetch(url, {\n method,\n headers,\n ...(body !== undefined ? { body: JSON.stringify(body) } : {}),\n });\n\n if (!response.ok) {\n const responseBody = await response.text();\n throw new Error(\n `LangWatch API error ${response.status}: ${responseBody}`\n );\n }\n\n return response.json();\n}\n\n/** Searches traces with optional filters and pagination. */\nexport async function searchTraces(params: {\n query?: string;\n filters?: Record<string, string[]>;\n startDate: number;\n endDate: number;\n pageSize?: number;\n pageOffset?: number;\n scrollId?: string;\n format?: \"digest\" | \"json\";\n}): Promise<SearchTracesResponse> {\n const { format = \"digest\", ...rest } = params;\n return makeRequest(\"POST\", \"/api/traces/search\", {\n ...rest,\n format,\n }) as Promise<SearchTracesResponse>;\n}\n\n/** Retrieves a single trace by its ID. */\nexport async function getTraceById(\n traceId: string,\n format: \"digest\" | \"json\" = \"digest\"\n): Promise<TraceDetailResponse> {\n return makeRequest(\n \"GET\",\n `/api/traces/${encodeURIComponent(traceId)}?format=${format}`\n ) as Promise<TraceDetailResponse>;\n}\n\n/** Fetches analytics timeseries data for the given metrics and date range. */\nexport async function getAnalyticsTimeseries(params: {\n series: Array<{\n metric: string;\n aggregation: string;\n key?: string;\n subkey?: string;\n }>;\n startDate: number;\n endDate: number;\n timeZone?: string;\n groupBy?: string;\n groupByKey?: string;\n filters?: Record<string, string[]>;\n}): Promise<AnalyticsTimeseriesResponse> {\n return makeRequest(\n \"POST\",\n \"/api/analytics/timeseries\",\n params\n ) as Promise<AnalyticsTimeseriesResponse>;\n}\n\n/** Lists all prompts in the project. */\nexport async function listPrompts(): Promise<PromptSummary[]> {\n return makeRequest(\"GET\", \"/api/prompts\") as Promise<PromptSummary[]>;\n}\n\n/** Retrieves a single prompt by ID or handle. */\nexport async function getPrompt(\n idOrHandle: string,\n version?: number\n): Promise<PromptDetailResponse> {\n const query = version != null ? `?version=${version}` : \"\";\n return makeRequest(\n \"GET\",\n `/api/prompts/${encodeURIComponent(idOrHandle)}${query}`\n ) as Promise<PromptDetailResponse>;\n}\n\n/** Creates a new prompt. */\nexport async function createPrompt(data: {\n name: string;\n handle?: string;\n messages: Array<{ role: string; content: string }>;\n model: string;\n modelProvider: string;\n description?: string;\n}): Promise<PromptMutationResponse> {\n return makeRequest(\n \"POST\",\n \"/api/prompts\",\n data\n ) as Promise<PromptMutationResponse>;\n}\n\n/** Updates an existing prompt by ID or handle. */\nexport async function updatePrompt(\n idOrHandle: string,\n data: {\n messages?: Array<{ role: string; content: string }>;\n model?: string;\n modelProvider?: string;\n commitMessage?: string;\n }\n): Promise<PromptMutationResponse> {\n return makeRequest(\n \"POST\",\n `/api/prompts/${encodeURIComponent(idOrHandle)}`,\n data\n ) as Promise<PromptMutationResponse>;\n}\n\n/** Creates a new version of an existing prompt. */\nexport async function createPromptVersion(\n idOrHandle: string,\n data: {\n messages?: Array<{ role: string; content: string }>;\n model?: string;\n modelProvider?: string;\n commitMessage?: string;\n }\n): Promise<PromptMutationResponse> {\n return makeRequest(\n \"POST\",\n `/api/prompts/${encodeURIComponent(idOrHandle)}/versions`,\n data\n ) as Promise<PromptMutationResponse>;\n}\n\n"],"mappings":";;;;;;AAsHA,eAAsB,YACpB,QACA,MACA,MACkB;AAClB,QAAM,MAAM,UAAU,EAAE,WAAW;AACnC,QAAM,UAAkC;AAAA,IACtC,gBAAgB,cAAc;AAAA,EAChC;AAEA,MAAI,SAAS,QAAW;AACtB,YAAQ,cAAc,IAAI;AAAA,EAC5B;AAEA,QAAM,WAAW,MAAM,MAAM,KAAK;AAAA,IAChC;AAAA,IACA;AAAA,IACA,GAAI,SAAS,SAAY,EAAE,MAAM,KAAK,UAAU,IAAI,EAAE,IAAI,CAAC;AAAA,EAC7D,CAAC;AAED,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,eAAe,MAAM,SAAS,KAAK;AACzC,UAAM,IAAI;AAAA,MACR,uBAAuB,SAAS,MAAM,KAAK,YAAY;AAAA,IACzD;AAAA,EACF;AAEA,SAAO,SAAS,KAAK;AACvB;AAGA,eAAsB,aAAa,QASD;AAChC,QAAM,EAAE,SAAS,UAAU,GAAG,KAAK,IAAI;AACvC,SAAO,YAAY,QAAQ,sBAAsB;AAAA,IAC/C,GAAG;AAAA,IACH;AAAA,EACF,CAAC;AACH;AAGA,eAAsB,aACpB,SACA,SAA4B,UACE;AAC9B,SAAO;AAAA,IACL;AAAA,IACA,eAAe,mBAAmB,OAAO,CAAC,WAAW,MAAM;AAAA,EAC7D;AACF;AAGA,eAAsB,uBAAuB,QAaJ;AACvC,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAGA,eAAsB,cAAwC;AAC5D,SAAO,YAAY,OAAO,cAAc;AAC1C;AAGA,eAAsB,UACpB,YACA,SAC+B;AAC/B,QAAM,QAAQ,WAAW,OAAO,YAAY,OAAO,KAAK;AACxD,SAAO;AAAA,IACL;AAAA,IACA,gBAAgB,mBAAmB,UAAU,CAAC,GAAG,KAAK;AAAA,EACxD;AACF;AAGA,eAAsB,aAAa,MAOC;AAClC,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAGA,eAAsB,aACpB,YACA,MAMiC;AACjC,SAAO;AAAA,IACL;AAAA,IACA,gBAAgB,mBAAmB,UAAU,CAAC;AAAA,IAC9C;AAAA,EACF;AACF;AAGA,eAAsB,oBACpB,YACA,MAMiC;AACjC,SAAO;AAAA,IACL;AAAA,IACA,gBAAgB,mBAAmB,UAAU,CAAC;AAAA,IAC9C;AAAA,EACF;AACF;","names":[]}
@@ -1,22 +0,0 @@
1
- import {
2
- createPrompt
3
- } from "./chunk-JVWDWL3J.js";
4
- import "./chunk-AAQNA53E.js";
5
-
6
- // src/tools/create-prompt.ts
7
- async function handleCreatePrompt(params) {
8
- const result = await createPrompt(params);
9
- const lines = [];
10
- lines.push("Prompt created successfully!\n");
11
- if (result.id) lines.push(`**ID**: ${result.id}`);
12
- if (result.handle) lines.push(`**Handle**: ${result.handle}`);
13
- lines.push(`**Name**: ${result.name || params.name}`);
14
- lines.push(`**Model**: ${params.model} (${params.modelProvider})`);
15
- if (result.latestVersionNumber != null)
16
- lines.push(`**Version**: v${result.latestVersionNumber}`);
17
- return lines.join("\n");
18
- }
19
- export {
20
- handleCreatePrompt
21
- };
22
- //# sourceMappingURL=create-prompt-P35POKBW.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/tools/create-prompt.ts"],"sourcesContent":["import { createPrompt as apiCreatePrompt } from \"../langwatch-api.js\";\n\n/**\n * Handles the create_prompt MCP tool invocation.\n *\n * Creates a new prompt in the LangWatch project and returns a\n * confirmation with the created prompt's details.\n */\nexport async function handleCreatePrompt(params: {\n name: string;\n handle?: string;\n messages: Array<{ role: string; content: string }>;\n model: string;\n modelProvider: string;\n description?: string;\n}): Promise<string> {\n const result = await apiCreatePrompt(params);\n\n const lines: string[] = [];\n lines.push(\"Prompt created successfully!\\n\");\n if (result.id) lines.push(`**ID**: ${result.id}`);\n if (result.handle) lines.push(`**Handle**: ${result.handle}`);\n lines.push(`**Name**: ${result.name || params.name}`);\n lines.push(`**Model**: ${params.model} (${params.modelProvider})`);\n if (result.latestVersionNumber != null)\n lines.push(`**Version**: v${result.latestVersionNumber}`);\n\n return lines.join(\"\\n\");\n}\n"],"mappings":";;;;;;AAQA,eAAsB,mBAAmB,QAOrB;AAClB,QAAM,SAAS,MAAM,aAAgB,MAAM;AAE3C,QAAM,QAAkB,CAAC;AACzB,QAAM,KAAK,gCAAgC;AAC3C,MAAI,OAAO,GAAI,OAAM,KAAK,WAAW,OAAO,EAAE,EAAE;AAChD,MAAI,OAAO,OAAQ,OAAM,KAAK,eAAe,OAAO,MAAM,EAAE;AAC5D,QAAM,KAAK,aAAa,OAAO,QAAQ,OAAO,IAAI,EAAE;AACpD,QAAM,KAAK,cAAc,OAAO,KAAK,KAAK,OAAO,aAAa,GAAG;AACjE,MAAI,OAAO,uBAAuB;AAChC,UAAM,KAAK,iBAAiB,OAAO,mBAAmB,EAAE;AAE1D,SAAO,MAAM,KAAK,IAAI;AACxB;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/tools/create-scenario.ts"],"sourcesContent":["import { createScenario as apiCreateScenario } from \"../langwatch-api-scenarios.js\";\n\n/**\n * Handles the create_scenario MCP tool invocation.\n *\n * Creates a new scenario in the LangWatch project and returns a\n * confirmation with the created scenario's details.\n */\nexport async function handleCreateScenario(params: {\n name: string;\n situation: string;\n criteria?: string[];\n labels?: string[];\n}): Promise<string> {\n const result = await apiCreateScenario(params);\n\n const lines: string[] = [];\n lines.push(\"Scenario created successfully!\\n\");\n lines.push(`**ID**: ${result.id}`);\n lines.push(`**Name**: ${result.name}`);\n lines.push(`**Situation**: ${result.situation}`);\n if (Array.isArray(result.criteria) && result.criteria.length > 0) {\n lines.push(`**Criteria**: ${result.criteria.length} criteria`);\n }\n if (Array.isArray(result.labels) && result.labels.length > 0) {\n lines.push(`**Labels**: ${result.labels.join(\", \")}`);\n }\n\n return lines.join(\"\\n\");\n}\n"],"mappings":";;;;;;;AAQA,eAAsB,qBAAqB,QAKvB;AAClB,QAAM,SAAS,MAAM,eAAkB,MAAM;AAE7C,QAAM,QAAkB,CAAC;AACzB,QAAM,KAAK,kCAAkC;AAC7C,QAAM,KAAK,WAAW,OAAO,EAAE,EAAE;AACjC,QAAM,KAAK,aAAa,OAAO,IAAI,EAAE;AACrC,QAAM,KAAK,kBAAkB,OAAO,SAAS,EAAE;AAC/C,MAAI,MAAM,QAAQ,OAAO,QAAQ,KAAK,OAAO,SAAS,SAAS,GAAG;AAChE,UAAM,KAAK,iBAAiB,OAAO,SAAS,MAAM,WAAW;AAAA,EAC/D;AACA,MAAI,MAAM,QAAQ,OAAO,MAAM,KAAK,OAAO,OAAO,SAAS,GAAG;AAC5D,UAAM,KAAK,eAAe,OAAO,OAAO,KAAK,IAAI,CAAC,EAAE;AAAA,EACtD;AAEA,SAAO,MAAM,KAAK,IAAI;AACxB;","names":[]}