mcp-rubber-duck 1.7.0 → 1.9.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/CHANGELOG.md +15 -0
- package/README.md +274 -2
- package/audit-ci.json +2 -1
- package/dist/config/config.d.ts +2 -0
- package/dist/config/config.d.ts.map +1 -1
- package/dist/config/config.js +144 -1
- package/dist/config/config.js.map +1 -1
- package/dist/config/types.d.ts +1084 -2
- package/dist/config/types.d.ts.map +1 -1
- package/dist/config/types.js +59 -0
- package/dist/config/types.js.map +1 -1
- package/dist/guardrails/context.d.ts +10 -0
- package/dist/guardrails/context.d.ts.map +1 -0
- package/dist/guardrails/context.js +35 -0
- package/dist/guardrails/context.js.map +1 -0
- package/dist/guardrails/errors.d.ts +26 -0
- package/dist/guardrails/errors.d.ts.map +1 -0
- package/dist/guardrails/errors.js +42 -0
- package/dist/guardrails/errors.js.map +1 -0
- package/dist/guardrails/index.d.ts +6 -0
- package/dist/guardrails/index.d.ts.map +1 -0
- package/dist/guardrails/index.js +11 -0
- package/dist/guardrails/index.js.map +1 -0
- package/dist/guardrails/plugins/base-plugin.d.ts +35 -0
- package/dist/guardrails/plugins/base-plugin.d.ts.map +1 -0
- package/dist/guardrails/plugins/base-plugin.js +70 -0
- package/dist/guardrails/plugins/base-plugin.js.map +1 -0
- package/dist/guardrails/plugins/index.d.ts +6 -0
- package/dist/guardrails/plugins/index.d.ts.map +1 -0
- package/dist/guardrails/plugins/index.js +6 -0
- package/dist/guardrails/plugins/index.js.map +1 -0
- package/dist/guardrails/plugins/pattern-blocker.d.ts +27 -0
- package/dist/guardrails/plugins/pattern-blocker.d.ts.map +1 -0
- package/dist/guardrails/plugins/pattern-blocker.js +140 -0
- package/dist/guardrails/plugins/pattern-blocker.js.map +1 -0
- package/dist/guardrails/plugins/pii-redactor/detectors.d.ts +40 -0
- package/dist/guardrails/plugins/pii-redactor/detectors.d.ts.map +1 -0
- package/dist/guardrails/plugins/pii-redactor/detectors.js +134 -0
- package/dist/guardrails/plugins/pii-redactor/detectors.js.map +1 -0
- package/dist/guardrails/plugins/pii-redactor/index.d.ts +28 -0
- package/dist/guardrails/plugins/pii-redactor/index.d.ts.map +1 -0
- package/dist/guardrails/plugins/pii-redactor/index.js +157 -0
- package/dist/guardrails/plugins/pii-redactor/index.js.map +1 -0
- package/dist/guardrails/plugins/pii-redactor/pseudonymizer.d.ts +33 -0
- package/dist/guardrails/plugins/pii-redactor/pseudonymizer.d.ts.map +1 -0
- package/dist/guardrails/plugins/pii-redactor/pseudonymizer.js +70 -0
- package/dist/guardrails/plugins/pii-redactor/pseudonymizer.js.map +1 -0
- package/dist/guardrails/plugins/rate-limiter.d.ts +28 -0
- package/dist/guardrails/plugins/rate-limiter.d.ts.map +1 -0
- package/dist/guardrails/plugins/rate-limiter.js +91 -0
- package/dist/guardrails/plugins/rate-limiter.js.map +1 -0
- package/dist/guardrails/plugins/token-limiter.d.ts +30 -0
- package/dist/guardrails/plugins/token-limiter.d.ts.map +1 -0
- package/dist/guardrails/plugins/token-limiter.js +98 -0
- package/dist/guardrails/plugins/token-limiter.js.map +1 -0
- package/dist/guardrails/service.d.ts +38 -0
- package/dist/guardrails/service.d.ts.map +1 -0
- package/dist/guardrails/service.js +183 -0
- package/dist/guardrails/service.js.map +1 -0
- package/dist/guardrails/types.d.ts +96 -0
- package/dist/guardrails/types.d.ts.map +1 -0
- package/dist/guardrails/types.js +2 -0
- package/dist/guardrails/types.js.map +1 -0
- package/dist/prompts/architecture.d.ts +6 -0
- package/dist/prompts/architecture.d.ts.map +1 -0
- package/dist/prompts/architecture.js +103 -0
- package/dist/prompts/architecture.js.map +1 -0
- package/dist/prompts/assumptions.d.ts +6 -0
- package/dist/prompts/assumptions.d.ts.map +1 -0
- package/dist/prompts/assumptions.js +72 -0
- package/dist/prompts/assumptions.js.map +1 -0
- package/dist/prompts/blindspots.d.ts +6 -0
- package/dist/prompts/blindspots.d.ts.map +1 -0
- package/dist/prompts/blindspots.js +71 -0
- package/dist/prompts/blindspots.js.map +1 -0
- package/dist/prompts/diverge-converge.d.ts +6 -0
- package/dist/prompts/diverge-converge.d.ts.map +1 -0
- package/dist/prompts/diverge-converge.js +85 -0
- package/dist/prompts/diverge-converge.js.map +1 -0
- package/dist/prompts/index.d.ts +22 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +57 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/prompts/perspectives.d.ts +7 -0
- package/dist/prompts/perspectives.d.ts.map +1 -0
- package/dist/prompts/perspectives.js +65 -0
- package/dist/prompts/perspectives.js.map +1 -0
- package/dist/prompts/red-team.d.ts +6 -0
- package/dist/prompts/red-team.d.ts.map +1 -0
- package/dist/prompts/red-team.js +83 -0
- package/dist/prompts/red-team.js.map +1 -0
- package/dist/prompts/reframe.d.ts +6 -0
- package/dist/prompts/reframe.d.ts.map +1 -0
- package/dist/prompts/reframe.js +71 -0
- package/dist/prompts/reframe.js.map +1 -0
- package/dist/prompts/tradeoffs.d.ts +6 -0
- package/dist/prompts/tradeoffs.d.ts.map +1 -0
- package/dist/prompts/tradeoffs.js +87 -0
- package/dist/prompts/tradeoffs.js.map +1 -0
- package/dist/prompts/types.d.ts +14 -0
- package/dist/prompts/types.d.ts.map +1 -0
- package/dist/prompts/types.js +2 -0
- package/dist/prompts/types.js.map +1 -0
- package/dist/providers/duck-provider-enhanced.d.ts +2 -1
- package/dist/providers/duck-provider-enhanced.d.ts.map +1 -1
- package/dist/providers/duck-provider-enhanced.js +55 -6
- package/dist/providers/duck-provider-enhanced.js.map +1 -1
- package/dist/providers/enhanced-manager.d.ts +2 -1
- package/dist/providers/enhanced-manager.d.ts.map +1 -1
- package/dist/providers/enhanced-manager.js +3 -3
- package/dist/providers/enhanced-manager.js.map +1 -1
- package/dist/providers/manager.d.ts +3 -1
- package/dist/providers/manager.d.ts.map +1 -1
- package/dist/providers/manager.js +4 -2
- package/dist/providers/manager.js.map +1 -1
- package/dist/providers/provider.d.ts +3 -1
- package/dist/providers/provider.d.ts.map +1 -1
- package/dist/providers/provider.js +43 -3
- package/dist/providers/provider.js.map +1 -1
- package/dist/server.d.ts +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +48 -7
- package/dist/server.js.map +1 -1
- package/dist/services/function-bridge.d.ts +3 -1
- package/dist/services/function-bridge.d.ts.map +1 -1
- package/dist/services/function-bridge.js +40 -1
- package/dist/services/function-bridge.js.map +1 -1
- package/package.json +1 -1
- package/src/config/config.ts +187 -1
- package/src/config/types.ts +73 -0
- package/src/guardrails/context.ts +37 -0
- package/src/guardrails/errors.ts +46 -0
- package/src/guardrails/index.ts +20 -0
- package/src/guardrails/plugins/base-plugin.ts +103 -0
- package/src/guardrails/plugins/index.ts +5 -0
- package/src/guardrails/plugins/pattern-blocker.ts +190 -0
- package/src/guardrails/plugins/pii-redactor/detectors.ts +200 -0
- package/src/guardrails/plugins/pii-redactor/index.ts +203 -0
- package/src/guardrails/plugins/pii-redactor/pseudonymizer.ts +91 -0
- package/src/guardrails/plugins/rate-limiter.ts +142 -0
- package/src/guardrails/plugins/token-limiter.ts +155 -0
- package/src/guardrails/service.ts +209 -0
- package/src/guardrails/types.ts +120 -0
- package/src/prompts/architecture.ts +111 -0
- package/src/prompts/assumptions.ts +80 -0
- package/src/prompts/blindspots.ts +79 -0
- package/src/prompts/diverge-converge.ts +92 -0
- package/src/prompts/index.ts +63 -0
- package/src/prompts/perspectives.ts +73 -0
- package/src/prompts/red-team.ts +91 -0
- package/src/prompts/reframe.ts +78 -0
- package/src/prompts/tradeoffs.ts +95 -0
- package/src/prompts/types.ts +14 -0
- package/src/providers/duck-provider-enhanced.ts +76 -7
- package/src/providers/enhanced-manager.ts +5 -3
- package/src/providers/manager.ts +6 -3
- package/src/providers/provider.ts +57 -6
- package/src/server.ts +55 -6
- package/src/services/function-bridge.ts +53 -2
- package/tests/guardrails/config.test.ts +267 -0
- package/tests/guardrails/errors.test.ts +109 -0
- package/tests/guardrails/plugins/pattern-blocker.test.ts +309 -0
- package/tests/guardrails/plugins/pii-redactor.test.ts +1004 -0
- package/tests/guardrails/plugins/rate-limiter.test.ts +310 -0
- package/tests/guardrails/plugins/token-limiter.test.ts +216 -0
- package/tests/guardrails/service.test.ts +911 -0
- package/tests/mcp-bridge.test.ts +248 -0
- package/tests/prompts.test.ts +314 -0
- package/tests/providers.test.ts +739 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Prompt, GetPromptResult } from '@modelcontextprotocol/sdk/types.js';
|
|
2
|
+
import { PromptDefinition } from './types.js';
|
|
3
|
+
|
|
4
|
+
// Import all prompt definitions
|
|
5
|
+
import { perspectivesPrompt } from './perspectives.js';
|
|
6
|
+
import { assumptionsPrompt } from './assumptions.js';
|
|
7
|
+
import { blindspotsPrompt } from './blindspots.js';
|
|
8
|
+
import { tradeoffsPrompt } from './tradeoffs.js';
|
|
9
|
+
import { redTeamPrompt } from './red-team.js';
|
|
10
|
+
import { reframePrompt } from './reframe.js';
|
|
11
|
+
import { architecturePrompt } from './architecture.js';
|
|
12
|
+
import { divergeConvergePrompt } from './diverge-converge.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Registry of all available prompts.
|
|
16
|
+
*/
|
|
17
|
+
export const PROMPTS: Record<string, PromptDefinition> = {
|
|
18
|
+
perspectives: perspectivesPrompt,
|
|
19
|
+
assumptions: assumptionsPrompt,
|
|
20
|
+
blindspots: blindspotsPrompt,
|
|
21
|
+
tradeoffs: tradeoffsPrompt,
|
|
22
|
+
red_team: redTeamPrompt,
|
|
23
|
+
reframe: reframePrompt,
|
|
24
|
+
architecture: architecturePrompt,
|
|
25
|
+
diverge_converge: divergeConvergePrompt,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Get all available prompts (without buildMessages function).
|
|
30
|
+
* Used for prompts/list handler.
|
|
31
|
+
*/
|
|
32
|
+
export function getPrompts(): Prompt[] {
|
|
33
|
+
return Object.values(PROMPTS).map(({ buildMessages: _, ...prompt }) => prompt);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Get a specific prompt with generated messages.
|
|
38
|
+
* Used for prompts/get handler.
|
|
39
|
+
*
|
|
40
|
+
* @param name - The prompt name
|
|
41
|
+
* @param args - Arguments to pass to the prompt
|
|
42
|
+
* @returns GetPromptResult with description and messages
|
|
43
|
+
* @throws Error if prompt not found or required arguments missing
|
|
44
|
+
*/
|
|
45
|
+
export function getPrompt(name: string, args: Record<string, string>): GetPromptResult {
|
|
46
|
+
const prompt = PROMPTS[name];
|
|
47
|
+
|
|
48
|
+
if (!prompt) {
|
|
49
|
+
throw new Error(`Unknown prompt: ${name}`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
const messages = prompt.buildMessages(args);
|
|
54
|
+
return {
|
|
55
|
+
description: prompt.description,
|
|
56
|
+
messages,
|
|
57
|
+
};
|
|
58
|
+
} catch (error) {
|
|
59
|
+
// Re-throw with more context
|
|
60
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
61
|
+
throw new Error(`Failed to build prompt "${name}": ${errorMessage}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { PromptDefinition } from './types.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Multi-angle analysis prompt that assigns different analytical lenses
|
|
5
|
+
* to each LLM for comprehensive analysis.
|
|
6
|
+
*/
|
|
7
|
+
export const perspectivesPrompt: PromptDefinition = {
|
|
8
|
+
name: 'perspectives',
|
|
9
|
+
description:
|
|
10
|
+
'Analyze a problem from multiple perspectives. Each LLM adopts a different analytical lens (e.g., security, performance, UX) for comprehensive multi-angle analysis.',
|
|
11
|
+
arguments: [
|
|
12
|
+
{
|
|
13
|
+
name: 'problem',
|
|
14
|
+
description: 'The problem, design, or code to analyze',
|
|
15
|
+
required: true,
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: 'perspectives',
|
|
19
|
+
description:
|
|
20
|
+
'Comma-separated analytical lenses (e.g., "security, performance, UX, maintainability")',
|
|
21
|
+
required: true,
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
name: 'context',
|
|
25
|
+
description: 'Additional background or constraints',
|
|
26
|
+
required: false,
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
buildMessages: (args: Record<string, string>) => {
|
|
30
|
+
const { problem, perspectives, context } = args;
|
|
31
|
+
|
|
32
|
+
if (!problem) {
|
|
33
|
+
throw new Error('problem argument is required');
|
|
34
|
+
}
|
|
35
|
+
if (!perspectives) {
|
|
36
|
+
throw new Error('perspectives argument is required');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let messageText = `I need multi-perspective analysis of this problem:
|
|
40
|
+
|
|
41
|
+
**PROBLEM:**
|
|
42
|
+
${problem}
|
|
43
|
+
`;
|
|
44
|
+
|
|
45
|
+
if (context) {
|
|
46
|
+
messageText += `
|
|
47
|
+
**CONTEXT:**
|
|
48
|
+
${context}
|
|
49
|
+
`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
messageText += `
|
|
53
|
+
**PERSPECTIVES TO ANALYZE:**
|
|
54
|
+
${perspectives}
|
|
55
|
+
|
|
56
|
+
Please analyze from these perspectives, with each LLM adopting ONE lens. Each perspective should provide:
|
|
57
|
+
1. Specific observations from that viewpoint
|
|
58
|
+
2. Potential concerns or issues
|
|
59
|
+
3. Recommendations aligned with that perspective
|
|
60
|
+
|
|
61
|
+
Aim for productive disagreement - different lenses may have conflicting priorities, and that's valuable.`;
|
|
62
|
+
|
|
63
|
+
return [
|
|
64
|
+
{
|
|
65
|
+
role: 'user',
|
|
66
|
+
content: {
|
|
67
|
+
type: 'text',
|
|
68
|
+
text: messageText,
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
];
|
|
72
|
+
},
|
|
73
|
+
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { PromptDefinition } from './types.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Prompt for security and risk analysis from multiple attack angles.
|
|
5
|
+
*/
|
|
6
|
+
export const redTeamPrompt: PromptDefinition = {
|
|
7
|
+
name: 'red_team',
|
|
8
|
+
description:
|
|
9
|
+
'Conduct attack surface analysis from multiple angles. Each reviewer focuses on different risk dimensions (security, privacy, abuse, compliance).',
|
|
10
|
+
arguments: [
|
|
11
|
+
{
|
|
12
|
+
name: 'target',
|
|
13
|
+
description: 'The system, feature, code, or plan to red-team',
|
|
14
|
+
required: true,
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
name: 'threat_model',
|
|
18
|
+
description: 'Known threat actors, attack scenarios, or security context',
|
|
19
|
+
required: false,
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: 'dimensions',
|
|
23
|
+
description:
|
|
24
|
+
'Risk dimensions to focus on (e.g., "security, privacy, abuse, compliance, reputation")',
|
|
25
|
+
required: false,
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
buildMessages: (args: Record<string, string>) => {
|
|
29
|
+
const { target, threat_model, dimensions } = args;
|
|
30
|
+
|
|
31
|
+
if (!target) {
|
|
32
|
+
throw new Error('target argument is required');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let messageText = `Conduct a red-team analysis of this target:
|
|
36
|
+
|
|
37
|
+
**TARGET:**
|
|
38
|
+
${target}
|
|
39
|
+
`;
|
|
40
|
+
|
|
41
|
+
if (threat_model) {
|
|
42
|
+
messageText += `
|
|
43
|
+
**THREAT MODEL/CONTEXT:**
|
|
44
|
+
${threat_model}
|
|
45
|
+
`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (dimensions) {
|
|
49
|
+
messageText += `
|
|
50
|
+
**RISK DIMENSIONS TO FOCUS ON:**
|
|
51
|
+
${dimensions}
|
|
52
|
+
`;
|
|
53
|
+
} else {
|
|
54
|
+
messageText += `
|
|
55
|
+
**RISK DIMENSIONS:**
|
|
56
|
+
Consider security, privacy, abuse potential, compliance, and reputation risks.
|
|
57
|
+
`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
messageText += `
|
|
61
|
+
**YOUR TASK:**
|
|
62
|
+
Each reviewer should focus on a different attack vector or risk dimension:
|
|
63
|
+
|
|
64
|
+
1. **Identify vulnerabilities or abuse scenarios**
|
|
65
|
+
- How could this be exploited or misused?
|
|
66
|
+
- What could go wrong?
|
|
67
|
+
|
|
68
|
+
2. **Rate each finding**
|
|
69
|
+
- Severity: How bad if it happens?
|
|
70
|
+
- Likelihood: How likely is exploitation?
|
|
71
|
+
|
|
72
|
+
3. **Suggest mitigations**
|
|
73
|
+
- How can each risk be reduced or eliminated?
|
|
74
|
+
- What's the cost/benefit of the mitigation?
|
|
75
|
+
|
|
76
|
+
**PRIORITIZE:**
|
|
77
|
+
Identify the top 3 risks that need immediate attention. Explain why these are most critical.
|
|
78
|
+
|
|
79
|
+
Think adversarially - what would a malicious actor, careless user, or edge case do?`;
|
|
80
|
+
|
|
81
|
+
return [
|
|
82
|
+
{
|
|
83
|
+
role: 'user',
|
|
84
|
+
content: {
|
|
85
|
+
type: 'text',
|
|
86
|
+
text: messageText,
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
];
|
|
90
|
+
},
|
|
91
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { PromptDefinition } from './types.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Prompt for problem reframing at different abstraction levels and angles.
|
|
5
|
+
*/
|
|
6
|
+
export const reframePrompt: PromptDefinition = {
|
|
7
|
+
name: 'reframe',
|
|
8
|
+
description:
|
|
9
|
+
'Reframe a problem from multiple angles and abstraction levels. Helps break out of mental ruts by viewing the problem differently.',
|
|
10
|
+
arguments: [
|
|
11
|
+
{
|
|
12
|
+
name: 'problem',
|
|
13
|
+
description: 'The current problem statement or challenge',
|
|
14
|
+
required: true,
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
name: 'stuck_on',
|
|
18
|
+
description: 'What specifically you are stuck on or frustrated by',
|
|
19
|
+
required: false,
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
buildMessages: (args: Record<string, string>) => {
|
|
23
|
+
const { problem, stuck_on } = args;
|
|
24
|
+
|
|
25
|
+
if (!problem) {
|
|
26
|
+
throw new Error('problem argument is required');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let messageText = `Help me reframe this problem from multiple angles:
|
|
30
|
+
|
|
31
|
+
**CURRENT PROBLEM:**
|
|
32
|
+
${problem}
|
|
33
|
+
`;
|
|
34
|
+
|
|
35
|
+
if (stuck_on) {
|
|
36
|
+
messageText += `
|
|
37
|
+
**WHAT I'M STUCK ON:**
|
|
38
|
+
${stuck_on}
|
|
39
|
+
`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
messageText += `
|
|
43
|
+
**YOUR TASK:**
|
|
44
|
+
Provide three distinct reframings of this problem:
|
|
45
|
+
|
|
46
|
+
1. **HIGHER ABSTRACTION**
|
|
47
|
+
- What's the core human need or job-to-be-done here?
|
|
48
|
+
- What problem behind the problem are we really solving?
|
|
49
|
+
- How might we phrase this as a "How might we...?" question?
|
|
50
|
+
|
|
51
|
+
2. **INVERSION**
|
|
52
|
+
- What if we wanted this problem to occur? How would we cause it?
|
|
53
|
+
- What's the opposite of what we're trying to achieve?
|
|
54
|
+
- What would make this problem impossible to solve?
|
|
55
|
+
|
|
56
|
+
3. **SIMPLIFICATION**
|
|
57
|
+
- How would you explain this to a non-expert?
|
|
58
|
+
- What's the simplest version of this problem?
|
|
59
|
+
- What would a child ask about this?
|
|
60
|
+
|
|
61
|
+
For each reframing:
|
|
62
|
+
- State the new problem formulation clearly
|
|
63
|
+
- Explain what new solution directions this opens up
|
|
64
|
+
- Note what aspects of the original problem this highlights or de-emphasizes
|
|
65
|
+
|
|
66
|
+
The goal is to break out of the current mental frame and see new possibilities.`;
|
|
67
|
+
|
|
68
|
+
return [
|
|
69
|
+
{
|
|
70
|
+
role: 'user',
|
|
71
|
+
content: {
|
|
72
|
+
type: 'text',
|
|
73
|
+
text: messageText,
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
];
|
|
77
|
+
},
|
|
78
|
+
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { PromptDefinition } from './types.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Prompt for structured comparison of options with explicit criteria.
|
|
5
|
+
*/
|
|
6
|
+
export const tradeoffsPrompt: PromptDefinition = {
|
|
7
|
+
name: 'tradeoffs',
|
|
8
|
+
description:
|
|
9
|
+
'Compare options with explicit criteria and trade-off analysis. Provides structured evaluation to help make informed decisions.',
|
|
10
|
+
arguments: [
|
|
11
|
+
{
|
|
12
|
+
name: 'options',
|
|
13
|
+
description: 'The options to compare (comma-separated list or detailed descriptions)',
|
|
14
|
+
required: true,
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
name: 'criteria',
|
|
18
|
+
description: 'Evaluation criteria (comma-separated, e.g., "cost, complexity, performance")',
|
|
19
|
+
required: true,
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: 'context',
|
|
23
|
+
description: 'Decision context, constraints, or background',
|
|
24
|
+
required: false,
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name: 'weights',
|
|
28
|
+
description: 'Which criteria matter most (priority order or weights)',
|
|
29
|
+
required: false,
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
buildMessages: (args: Record<string, string>) => {
|
|
33
|
+
const { options, criteria, context, weights } = args;
|
|
34
|
+
|
|
35
|
+
if (!options) {
|
|
36
|
+
throw new Error('options argument is required');
|
|
37
|
+
}
|
|
38
|
+
if (!criteria) {
|
|
39
|
+
throw new Error('criteria argument is required');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let messageText = `Provide a structured trade-off analysis:
|
|
43
|
+
|
|
44
|
+
**OPTIONS TO COMPARE:**
|
|
45
|
+
${options}
|
|
46
|
+
|
|
47
|
+
**EVALUATION CRITERIA:**
|
|
48
|
+
${criteria}
|
|
49
|
+
`;
|
|
50
|
+
|
|
51
|
+
if (context) {
|
|
52
|
+
messageText += `
|
|
53
|
+
**CONTEXT/CONSTRAINTS:**
|
|
54
|
+
${context}
|
|
55
|
+
`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (weights) {
|
|
59
|
+
messageText += `
|
|
60
|
+
**PRIORITY CRITERIA:**
|
|
61
|
+
${weights}
|
|
62
|
+
`;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
messageText += `
|
|
66
|
+
**YOUR TASK:**
|
|
67
|
+
1. **Assess each option** against each criterion
|
|
68
|
+
- Use concrete ratings or descriptions, not vague terms
|
|
69
|
+
- Note specific strengths and weaknesses
|
|
70
|
+
|
|
71
|
+
2. **Identify trade-offs**
|
|
72
|
+
- Where do options excel vs struggle?
|
|
73
|
+
- What do you gain/lose with each choice?
|
|
74
|
+
|
|
75
|
+
3. **Highlight disagreements**
|
|
76
|
+
- Where might reasonable people disagree?
|
|
77
|
+
- What depends on assumptions or preferences?
|
|
78
|
+
|
|
79
|
+
4. **Recommendation**
|
|
80
|
+
- Given the stated priorities, which option fits best?
|
|
81
|
+
- Under what conditions might a different option be better?
|
|
82
|
+
|
|
83
|
+
Present this as a structured analysis that helps decision-making, not just a list of pros/cons.`;
|
|
84
|
+
|
|
85
|
+
return [
|
|
86
|
+
{
|
|
87
|
+
role: 'user',
|
|
88
|
+
content: {
|
|
89
|
+
type: 'text',
|
|
90
|
+
text: messageText,
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
];
|
|
94
|
+
},
|
|
95
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Prompt, PromptMessage } from '@modelcontextprotocol/sdk/types.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Extended Prompt definition that includes a buildMessages function
|
|
5
|
+
* to generate structured messages from user-provided arguments.
|
|
6
|
+
*/
|
|
7
|
+
export interface PromptDefinition extends Prompt {
|
|
8
|
+
/**
|
|
9
|
+
* Build the prompt messages from the provided arguments.
|
|
10
|
+
* @param args - Record of argument name to value
|
|
11
|
+
* @returns Array of PromptMessage objects
|
|
12
|
+
*/
|
|
13
|
+
buildMessages: (args: Record<string, string>) => PromptMessage[];
|
|
14
|
+
}
|
|
@@ -4,6 +4,8 @@ import { FunctionBridge } from '../services/function-bridge.js';
|
|
|
4
4
|
import { ConversationMessage } from '../config/types.js';
|
|
5
5
|
import { logger } from '../utils/logger.js';
|
|
6
6
|
import { SafeLogger } from '../utils/safe-logger.js';
|
|
7
|
+
import { GuardrailsService } from '../guardrails/service.js';
|
|
8
|
+
import { GuardrailBlockError } from '../guardrails/errors.js';
|
|
7
9
|
|
|
8
10
|
export interface EnhancedChatResponse extends ChatResponse {
|
|
9
11
|
pendingApprovals?: {
|
|
@@ -22,15 +24,43 @@ export class EnhancedDuckProvider extends DuckProvider {
|
|
|
22
24
|
nickname: string,
|
|
23
25
|
options: ProviderOptions,
|
|
24
26
|
functionBridge: FunctionBridge,
|
|
25
|
-
mcpEnabled: boolean = true
|
|
27
|
+
mcpEnabled: boolean = true,
|
|
28
|
+
guardrailsService?: GuardrailsService
|
|
26
29
|
) {
|
|
27
|
-
super(name, nickname, options);
|
|
30
|
+
super(name, nickname, options, guardrailsService);
|
|
28
31
|
this.functionBridge = functionBridge;
|
|
29
32
|
this.mcpEnabled = mcpEnabled;
|
|
30
33
|
}
|
|
31
34
|
|
|
32
35
|
async chat(options: ChatOptions): Promise<EnhancedChatResponse> {
|
|
33
36
|
try {
|
|
37
|
+
const modelToUse = options.model || this.options.model;
|
|
38
|
+
|
|
39
|
+
// Create guardrail context if service is enabled
|
|
40
|
+
const guardrailContext = this.guardrailsService?.isEnabled()
|
|
41
|
+
? this.guardrailsService.createContext({
|
|
42
|
+
provider: this.name,
|
|
43
|
+
model: modelToUse,
|
|
44
|
+
messages: options.messages,
|
|
45
|
+
prompt: options.messages[options.messages.length - 1]?.content,
|
|
46
|
+
})
|
|
47
|
+
: undefined;
|
|
48
|
+
|
|
49
|
+
// Execute pre_request guardrails
|
|
50
|
+
if (guardrailContext && this.guardrailsService?.isEnabled()) {
|
|
51
|
+
const preResult = await this.guardrailsService.execute('pre_request', guardrailContext);
|
|
52
|
+
if (preResult.action === 'block') {
|
|
53
|
+
throw new GuardrailBlockError(
|
|
54
|
+
preResult.blockedBy || 'unknown',
|
|
55
|
+
preResult.blockReason || 'Request blocked by guardrails'
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
// Update messages if modified by guardrails (e.g., PII redaction)
|
|
59
|
+
if (preResult.action === 'modify' && guardrailContext.messages.length > 0) {
|
|
60
|
+
options = { ...options, messages: guardrailContext.messages };
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
34
64
|
// If MCP is enabled, add function definitions
|
|
35
65
|
if (this.mcpEnabled) {
|
|
36
66
|
const functions = await this.functionBridge.getFunctionDefinitions();
|
|
@@ -43,7 +73,6 @@ export class EnhancedDuckProvider extends DuckProvider {
|
|
|
43
73
|
|
|
44
74
|
// Prepare messages for function calling
|
|
45
75
|
const messages = this.prepareMessages(options.messages, options.systemPrompt);
|
|
46
|
-
const modelToUse = options.model || this.options.model;
|
|
47
76
|
|
|
48
77
|
const baseParams: Partial<OpenAIChatParams> = {
|
|
49
78
|
model: modelToUse,
|
|
@@ -75,17 +104,52 @@ export class EnhancedDuckProvider extends DuckProvider {
|
|
|
75
104
|
|
|
76
105
|
// Check if the model wants to call functions
|
|
77
106
|
if (choice.message?.tool_calls && choice.message.tool_calls.length > 0) {
|
|
78
|
-
|
|
107
|
+
const toolResult = await this.handleToolCalls(
|
|
79
108
|
choice.message.tool_calls,
|
|
80
109
|
messages as OpenAIMessage[],
|
|
81
110
|
baseParams,
|
|
82
|
-
modelToUse
|
|
111
|
+
modelToUse,
|
|
112
|
+
guardrailContext
|
|
83
113
|
);
|
|
114
|
+
|
|
115
|
+
// Execute post_response guardrails on final result
|
|
116
|
+
if (guardrailContext && this.guardrailsService?.isEnabled()) {
|
|
117
|
+
guardrailContext.response = toolResult.content;
|
|
118
|
+
const postResult = await this.guardrailsService.execute('post_response', guardrailContext);
|
|
119
|
+
if (postResult.action === 'block') {
|
|
120
|
+
throw new GuardrailBlockError(
|
|
121
|
+
postResult.blockedBy || 'unknown',
|
|
122
|
+
postResult.blockReason || 'Response blocked by guardrails'
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
if (postResult.action === 'modify' && guardrailContext.response) {
|
|
126
|
+
toolResult.content = guardrailContext.response;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return toolResult;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
let content = choice.message?.content || '';
|
|
134
|
+
|
|
135
|
+
// Execute post_response guardrails
|
|
136
|
+
if (guardrailContext && this.guardrailsService?.isEnabled()) {
|
|
137
|
+
guardrailContext.response = content;
|
|
138
|
+
const postResult = await this.guardrailsService.execute('post_response', guardrailContext);
|
|
139
|
+
if (postResult.action === 'block') {
|
|
140
|
+
throw new GuardrailBlockError(
|
|
141
|
+
postResult.blockedBy || 'unknown',
|
|
142
|
+
postResult.blockReason || 'Response blocked by guardrails'
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
if (postResult.action === 'modify' && guardrailContext.response) {
|
|
146
|
+
content = guardrailContext.response;
|
|
147
|
+
}
|
|
84
148
|
}
|
|
85
149
|
|
|
86
150
|
// No tool calls, return regular response
|
|
87
151
|
return {
|
|
88
|
-
content
|
|
152
|
+
content,
|
|
89
153
|
usage: response.usage ? {
|
|
90
154
|
promptTokens: response.usage.prompt_tokens,
|
|
91
155
|
completionTokens: response.usage.completion_tokens,
|
|
@@ -96,6 +160,10 @@ export class EnhancedDuckProvider extends DuckProvider {
|
|
|
96
160
|
};
|
|
97
161
|
|
|
98
162
|
} catch (error: unknown) {
|
|
163
|
+
// Re-throw GuardrailBlockError as-is
|
|
164
|
+
if (error instanceof GuardrailBlockError) {
|
|
165
|
+
throw error;
|
|
166
|
+
}
|
|
99
167
|
logger.error(`Enhanced provider ${this.name} chat error:`, error);
|
|
100
168
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
101
169
|
throw new Error(`Duck ${this.nickname} couldn't respond: ${errorMessage}`);
|
|
@@ -106,7 +174,8 @@ export class EnhancedDuckProvider extends DuckProvider {
|
|
|
106
174
|
toolCalls: OpenAIToolCall[],
|
|
107
175
|
messages: OpenAIMessage[],
|
|
108
176
|
baseParams: Partial<OpenAIChatParams>,
|
|
109
|
-
modelToUse: string
|
|
177
|
+
modelToUse: string,
|
|
178
|
+
_guardrailContext?: import('../guardrails/types.js').GuardrailContext
|
|
110
179
|
): Promise<EnhancedChatResponse> {
|
|
111
180
|
const pendingApprovals: { id: string; message: string }[] = [];
|
|
112
181
|
const toolMessages: OpenAIMessage[] = [];
|
|
@@ -3,6 +3,7 @@ import { ProviderManager } from './manager.js';
|
|
|
3
3
|
import { ConfigManager } from '../config/config.js';
|
|
4
4
|
import { FunctionBridge } from '../services/function-bridge.js';
|
|
5
5
|
import { UsageService } from '../services/usage.js';
|
|
6
|
+
import { GuardrailsService } from '../guardrails/service.js';
|
|
6
7
|
import { DuckResponse } from '../config/types.js';
|
|
7
8
|
import { ChatOptions, MCPResult } from './types.js';
|
|
8
9
|
import { logger } from '../utils/logger.js';
|
|
@@ -12,8 +13,8 @@ export class EnhancedProviderManager extends ProviderManager {
|
|
|
12
13
|
private functionBridge?: FunctionBridge;
|
|
13
14
|
private mcpEnabled: boolean = false;
|
|
14
15
|
|
|
15
|
-
constructor(configManager: ConfigManager, functionBridge?: FunctionBridge, usageService?: UsageService) {
|
|
16
|
-
super(configManager, usageService);
|
|
16
|
+
constructor(configManager: ConfigManager, functionBridge?: FunctionBridge, usageService?: UsageService, guardrailsService?: GuardrailsService) {
|
|
17
|
+
super(configManager, usageService, guardrailsService);
|
|
17
18
|
this.functionBridge = functionBridge;
|
|
18
19
|
this.mcpEnabled = !!functionBridge &&
|
|
19
20
|
(configManager.getConfig().mcp_bridge?.enabled || false);
|
|
@@ -49,7 +50,8 @@ export class EnhancedProviderManager extends ProviderManager {
|
|
|
49
50
|
systemPrompt: providerConfig.system_prompt,
|
|
50
51
|
},
|
|
51
52
|
this.functionBridge,
|
|
52
|
-
this.mcpEnabled
|
|
53
|
+
this.mcpEnabled,
|
|
54
|
+
this.guardrailsService
|
|
53
55
|
);
|
|
54
56
|
|
|
55
57
|
this.enhancedProviders.set(name, enhancedProvider);
|
package/src/providers/manager.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { ConfigManager } from '../config/config.js';
|
|
|
3
3
|
import { ProviderHealth, DuckResponse } from '../config/types.js';
|
|
4
4
|
import { ChatOptions, ModelInfo } from './types.js';
|
|
5
5
|
import { UsageService } from '../services/usage.js';
|
|
6
|
+
import { GuardrailsService } from '../guardrails/service.js';
|
|
6
7
|
import { logger } from '../utils/logger.js';
|
|
7
8
|
import { getRandomDuckMessage } from '../utils/ascii-art.js';
|
|
8
9
|
|
|
@@ -11,11 +12,13 @@ export class ProviderManager {
|
|
|
11
12
|
private healthStatus: Map<string, ProviderHealth> = new Map();
|
|
12
13
|
protected configManager: ConfigManager;
|
|
13
14
|
protected usageService?: UsageService;
|
|
15
|
+
protected guardrailsService?: GuardrailsService;
|
|
14
16
|
private defaultProvider?: string;
|
|
15
17
|
|
|
16
|
-
constructor(configManager: ConfigManager, usageService?: UsageService) {
|
|
18
|
+
constructor(configManager: ConfigManager, usageService?: UsageService, guardrailsService?: GuardrailsService) {
|
|
17
19
|
this.configManager = configManager;
|
|
18
20
|
this.usageService = usageService;
|
|
21
|
+
this.guardrailsService = guardrailsService;
|
|
19
22
|
this.initializeProviders();
|
|
20
23
|
}
|
|
21
24
|
|
|
@@ -34,7 +37,7 @@ export class ProviderManager {
|
|
|
34
37
|
timeout: providerConfig.timeout,
|
|
35
38
|
maxRetries: providerConfig.max_retries,
|
|
36
39
|
systemPrompt: providerConfig.system_prompt,
|
|
37
|
-
});
|
|
40
|
+
}, this.guardrailsService);
|
|
38
41
|
|
|
39
42
|
this.providers.set(name, provider);
|
|
40
43
|
logger.info(`Initialized provider: ${name} (${providerConfig.nickname})`);
|
|
@@ -44,7 +47,7 @@ export class ProviderManager {
|
|
|
44
47
|
}
|
|
45
48
|
|
|
46
49
|
this.defaultProvider = config.default_provider;
|
|
47
|
-
|
|
50
|
+
|
|
48
51
|
if (this.providers.size === 0) {
|
|
49
52
|
throw new Error('No providers could be initialized');
|
|
50
53
|
}
|