opc-agent 2.1.0 → 3.0.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/README.md +603 -545
- package/dist/channels/voice.d.ts +59 -0
- package/dist/channels/voice.js +351 -1
- package/dist/cli.js +172 -1
- package/dist/core/agent.d.ts +4 -0
- package/dist/core/agent.js +35 -0
- package/dist/core/collaboration.d.ts +89 -0
- package/dist/core/collaboration.js +201 -0
- package/dist/deploy/index.d.ts +40 -0
- package/dist/deploy/index.js +261 -0
- package/dist/index.d.ts +7 -1
- package/dist/index.js +47 -3
- package/dist/mcp/servers/calculator-mcp.d.ts +3 -0
- package/dist/mcp/servers/calculator-mcp.js +65 -0
- package/dist/mcp/servers/crypto-mcp.d.ts +3 -0
- package/dist/mcp/servers/crypto-mcp.js +108 -0
- package/dist/mcp/servers/database-mcp.d.ts +3 -0
- package/dist/mcp/servers/database-mcp.js +73 -0
- package/dist/mcp/servers/datetime-mcp.d.ts +3 -0
- package/dist/mcp/servers/datetime-mcp.js +71 -0
- package/dist/mcp/servers/filesystem.d.ts +3 -0
- package/dist/mcp/servers/filesystem.js +101 -0
- package/dist/mcp/servers/github-mcp.d.ts +3 -0
- package/dist/mcp/servers/github-mcp.js +60 -0
- package/dist/mcp/servers/index.d.ts +21 -0
- package/dist/mcp/servers/index.js +50 -0
- package/dist/mcp/servers/json-mcp.d.ts +3 -0
- package/dist/mcp/servers/json-mcp.js +126 -0
- package/dist/mcp/servers/memory-mcp.d.ts +3 -0
- package/dist/mcp/servers/memory-mcp.js +60 -0
- package/dist/mcp/servers/regex-mcp.d.ts +3 -0
- package/dist/mcp/servers/regex-mcp.js +56 -0
- package/dist/mcp/servers/web-mcp.d.ts +3 -0
- package/dist/mcp/servers/web-mcp.js +51 -0
- package/dist/schema/oad.d.ts +292 -12
- package/dist/schema/oad.js +12 -1
- package/dist/security/guardrails.d.ts +50 -0
- package/dist/security/guardrails.js +197 -0
- package/dist/studio/server.d.ts +31 -1
- package/dist/studio/server.js +154 -3
- package/dist/studio-ui/index.html +1278 -662
- package/dist/tools/integrations/calendar.d.ts +3 -0
- package/dist/tools/integrations/calendar.js +73 -0
- package/dist/tools/integrations/code-exec.d.ts +3 -0
- package/dist/tools/integrations/code-exec.js +42 -0
- package/dist/tools/integrations/csv-analyzer.d.ts +3 -0
- package/dist/tools/integrations/csv-analyzer.js +142 -0
- package/dist/tools/integrations/database.d.ts +3 -0
- package/dist/tools/integrations/database.js +44 -0
- package/dist/tools/integrations/email-send.d.ts +3 -0
- package/dist/tools/integrations/email-send.js +104 -0
- package/dist/tools/integrations/git-tool.d.ts +3 -0
- package/dist/tools/integrations/git-tool.js +49 -0
- package/dist/tools/integrations/github-tool.d.ts +3 -0
- package/dist/tools/integrations/github-tool.js +77 -0
- package/dist/tools/integrations/image-gen.d.ts +3 -0
- package/dist/tools/integrations/image-gen.js +58 -0
- package/dist/tools/integrations/index.d.ts +30 -0
- package/dist/tools/integrations/index.js +107 -0
- package/dist/tools/integrations/jira.d.ts +3 -0
- package/dist/tools/integrations/jira.js +85 -0
- package/dist/tools/integrations/notion.d.ts +3 -0
- package/dist/tools/integrations/notion.js +71 -0
- package/dist/tools/integrations/npm-tool.d.ts +3 -0
- package/dist/tools/integrations/npm-tool.js +49 -0
- package/dist/tools/integrations/pdf-reader.d.ts +3 -0
- package/dist/tools/integrations/pdf-reader.js +91 -0
- package/dist/tools/integrations/slack.d.ts +3 -0
- package/dist/tools/integrations/slack.js +67 -0
- package/dist/tools/integrations/summarizer.d.ts +3 -0
- package/dist/tools/integrations/summarizer.js +49 -0
- package/dist/tools/integrations/translator.d.ts +3 -0
- package/dist/tools/integrations/translator.js +48 -0
- package/dist/tools/integrations/trello.d.ts +3 -0
- package/dist/tools/integrations/trello.js +60 -0
- package/dist/tools/integrations/vector-search.d.ts +3 -0
- package/dist/tools/integrations/vector-search.js +44 -0
- package/dist/tools/integrations/web-scraper.d.ts +3 -0
- package/dist/tools/integrations/web-scraper.js +48 -0
- package/dist/tools/integrations/web-search.d.ts +3 -0
- package/dist/tools/integrations/web-search.js +60 -0
- package/dist/tools/integrations/webhook.d.ts +3 -0
- package/dist/tools/integrations/webhook.js +39 -0
- package/dist/ui/components.d.ts +10 -0
- package/dist/ui/components.js +123 -0
- package/package.json +1 -1
- package/src/channels/voice.ts +365 -0
- package/src/cli.ts +176 -2
- package/src/core/agent.ts +38 -0
- package/src/core/collaboration.ts +275 -0
- package/src/deploy/index.ts +255 -0
- package/src/index.ts +21 -1
- package/src/mcp/servers/calculator-mcp.ts +65 -0
- package/src/mcp/servers/crypto-mcp.ts +73 -0
- package/src/mcp/servers/database-mcp.ts +72 -0
- package/src/mcp/servers/datetime-mcp.ts +69 -0
- package/src/mcp/servers/filesystem.ts +66 -0
- package/src/mcp/servers/github-mcp.ts +58 -0
- package/src/mcp/servers/index.ts +63 -0
- package/src/mcp/servers/json-mcp.ts +102 -0
- package/src/mcp/servers/memory-mcp.ts +56 -0
- package/src/mcp/servers/regex-mcp.ts +53 -0
- package/src/mcp/servers/web-mcp.ts +49 -0
- package/src/schema/oad.ts +13 -0
- package/src/security/guardrails.ts +248 -0
- package/src/studio/server.ts +166 -4
- package/src/studio-ui/index.html +1278 -662
- package/src/tools/integrations/calendar.ts +73 -0
- package/src/tools/integrations/code-exec.ts +39 -0
- package/src/tools/integrations/csv-analyzer.ts +92 -0
- package/src/tools/integrations/database.ts +44 -0
- package/src/tools/integrations/email-send.ts +76 -0
- package/src/tools/integrations/git-tool.ts +42 -0
- package/src/tools/integrations/github-tool.ts +76 -0
- package/src/tools/integrations/image-gen.ts +56 -0
- package/src/tools/integrations/index.ts +92 -0
- package/src/tools/integrations/jira.ts +83 -0
- package/src/tools/integrations/notion.ts +71 -0
- package/src/tools/integrations/npm-tool.ts +48 -0
- package/src/tools/integrations/pdf-reader.ts +58 -0
- package/src/tools/integrations/slack.ts +65 -0
- package/src/tools/integrations/summarizer.ts +49 -0
- package/src/tools/integrations/translator.ts +48 -0
- package/src/tools/integrations/trello.ts +60 -0
- package/src/tools/integrations/vector-search.ts +42 -0
- package/src/tools/integrations/web-scraper.ts +47 -0
- package/src/tools/integrations/web-search.ts +58 -0
- package/src/tools/integrations/webhook.ts +38 -0
- package/src/ui/components.ts +127 -0
- package/tests/brain-seed-extended.test.ts +490 -0
- package/tests/collaboration.test.ts +319 -0
- package/tests/deploy-and-dag.test.ts +196 -0
- package/tests/guardrails.test.ts +177 -0
- package/tests/integrations.test.ts +249 -0
- package/tests/mcp-servers.test.ts +260 -0
- package/tests/voice-enhanced.test.ts +169 -0
- package/dist/dtv/data.d.ts +0 -18
- package/dist/dtv/data.js +0 -25
- package/dist/dtv/trust.d.ts +0 -19
- package/dist/dtv/trust.js +0 -40
- package/dist/dtv/value.d.ts +0 -23
- package/dist/dtv/value.js +0 -38
- package/dist/marketplace/index.d.ts +0 -34
- package/dist/marketplace/index.js +0 -202
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Guardrails Module - v2.1.0
|
|
4
|
+
* Input/output guardrails for LLM safety: PII, toxicity, injection, compliance.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.GuardrailManager = void 0;
|
|
8
|
+
exports.createGuardrailsFromConfig = createGuardrailsFromConfig;
|
|
9
|
+
// ── Built-in Patterns ───────────────────────────────────────
|
|
10
|
+
const PII_PATTERNS = {
|
|
11
|
+
email: /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g,
|
|
12
|
+
phone: /(\+?1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}/g,
|
|
13
|
+
ssn: /\b\d{3}-\d{2}-\d{4}\b/g,
|
|
14
|
+
creditCard: /\b(?:\d{4}[-\s]?){3}\d{4}\b/g,
|
|
15
|
+
ipAddress: /\b(?:\d{1,3}\.){3}\d{1,3}\b/g,
|
|
16
|
+
};
|
|
17
|
+
const INJECTION_PATTERNS = [
|
|
18
|
+
/ignore\s+(all\s+)?previous\s+instructions/i,
|
|
19
|
+
/ignore\s+(all\s+)?above\s+instructions/i,
|
|
20
|
+
/system\s*prompt\s*:/i,
|
|
21
|
+
/you\s+are\s+now\s+(?:a|an|the)\s+/i,
|
|
22
|
+
/act\s+as\s+(?:a|an)\s+/i,
|
|
23
|
+
/pretend\s+(?:you(?:'re|\s+are)\s+)?/i,
|
|
24
|
+
/new\s+instruction[s]?\s*:/i,
|
|
25
|
+
/override\s+(?:your\s+)?(?:instructions|rules|guidelines)/i,
|
|
26
|
+
/jailbreak/i,
|
|
27
|
+
/DAN\s+mode/i,
|
|
28
|
+
];
|
|
29
|
+
const TOXICITY_KEYWORDS = [
|
|
30
|
+
'kill yourself', 'kys', 'go die', 'hate you',
|
|
31
|
+
'stupid idiot', 'worthless', 'piece of shit',
|
|
32
|
+
];
|
|
33
|
+
const COMPLIANCE_PATTERNS = [
|
|
34
|
+
{ pattern: /(?:you\s+should\s+)?(?:buy|sell|invest\s+in)\s+(?:stocks?|crypto|bitcoin)/i, label: 'financial advice' },
|
|
35
|
+
{ pattern: /(?:you\s+(?:have|probably\s+have)|diagnos(?:e|is))\s+(?:\w+\s+){0,3}(?:disease|syndrome|disorder|cancer)/i, label: 'medical diagnosis' },
|
|
36
|
+
{ pattern: /(?:legal(?:ly)?|sue|lawsuit|court)\s+(?:you\s+should|advice)/i, label: 'legal advice' },
|
|
37
|
+
];
|
|
38
|
+
// ── Rule Executors ──────────────────────────────────────────
|
|
39
|
+
function checkPII(text, action) {
|
|
40
|
+
const violations = [];
|
|
41
|
+
let redacted = text;
|
|
42
|
+
for (const [type, pattern] of Object.entries(PII_PATTERNS)) {
|
|
43
|
+
const cloned = new RegExp(pattern.source, pattern.flags);
|
|
44
|
+
const matches = text.match(cloned);
|
|
45
|
+
if (matches) {
|
|
46
|
+
violations.push({ rule: 'pii-detector', action, detail: `Found ${type}: ${matches.length} match(es)` });
|
|
47
|
+
redacted = redacted.replace(cloned, '[REDACTED]');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return { violations, redactedText: redacted };
|
|
51
|
+
}
|
|
52
|
+
function checkInjection(text) {
|
|
53
|
+
const violations = [];
|
|
54
|
+
for (const pattern of INJECTION_PATTERNS) {
|
|
55
|
+
if (pattern.test(text)) {
|
|
56
|
+
violations.push({ rule: 'prompt-injection', action: 'block', detail: `Matched pattern: ${pattern.source}` });
|
|
57
|
+
break; // one is enough
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return violations;
|
|
61
|
+
}
|
|
62
|
+
function checkToxicity(text, extraKeywords) {
|
|
63
|
+
const lower = text.toLowerCase();
|
|
64
|
+
const keywords = [...TOXICITY_KEYWORDS, ...(extraKeywords ?? [])];
|
|
65
|
+
for (const kw of keywords) {
|
|
66
|
+
if (lower.includes(kw.toLowerCase())) {
|
|
67
|
+
return [{ rule: 'toxicity', action: 'block', detail: `Matched keyword: "${kw}"` }];
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
function checkTopicRestriction(text, config) {
|
|
73
|
+
const denyTopics = config?.denyTopics ?? [];
|
|
74
|
+
const lower = text.toLowerCase();
|
|
75
|
+
for (const topic of denyTopics) {
|
|
76
|
+
if (lower.includes(topic.toLowerCase())) {
|
|
77
|
+
return [{ rule: 'topic-restrictor', action: 'block', detail: `Blocked topic: "${topic}"` }];
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return [];
|
|
81
|
+
}
|
|
82
|
+
function checkLength(text, config) {
|
|
83
|
+
const maxChars = config?.maxChars ?? 10000;
|
|
84
|
+
if (text.length > maxChars) {
|
|
85
|
+
return [{ rule: 'length-limit', action: 'warn', detail: `Response length ${text.length} exceeds max ${maxChars}` }];
|
|
86
|
+
}
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
89
|
+
function checkLanguage(text, config) {
|
|
90
|
+
const allowed = config?.allowedLanguages ?? [];
|
|
91
|
+
if (allowed.length === 0)
|
|
92
|
+
return [];
|
|
93
|
+
// Simple heuristic: check if text contains CJK characters
|
|
94
|
+
const hasCJK = /[\u4e00-\u9fff\u3040-\u309f\u30a0-\u30ff]/.test(text);
|
|
95
|
+
const hasLatin = /[a-zA-Z]{3,}/.test(text);
|
|
96
|
+
if (allowed.includes('en') && !allowed.includes('zh') && hasCJK && !hasLatin) {
|
|
97
|
+
return [{ rule: 'language-filter', action: 'block', detail: 'Non-allowed language detected' }];
|
|
98
|
+
}
|
|
99
|
+
if (allowed.includes('zh') && !allowed.includes('en') && hasLatin && !hasCJK) {
|
|
100
|
+
return [{ rule: 'language-filter', action: 'block', detail: 'Non-allowed language detected' }];
|
|
101
|
+
}
|
|
102
|
+
return [];
|
|
103
|
+
}
|
|
104
|
+
function checkCompliance(text) {
|
|
105
|
+
for (const { pattern, label } of COMPLIANCE_PATTERNS) {
|
|
106
|
+
if (pattern.test(text)) {
|
|
107
|
+
return [{ rule: 'compliance-filter', action: 'block', detail: `Potential ${label} detected` }];
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return [];
|
|
111
|
+
}
|
|
112
|
+
// ── Guardrail Manager ───────────────────────────────────────
|
|
113
|
+
class GuardrailManager {
|
|
114
|
+
config;
|
|
115
|
+
constructor(config) {
|
|
116
|
+
this.config = config;
|
|
117
|
+
}
|
|
118
|
+
async checkInput(message) {
|
|
119
|
+
return this.runRules(message, this.config.input ?? []);
|
|
120
|
+
}
|
|
121
|
+
async checkOutput(response) {
|
|
122
|
+
return this.runRules(response, this.config.output ?? []);
|
|
123
|
+
}
|
|
124
|
+
async runRules(text, rules) {
|
|
125
|
+
const allViolations = [];
|
|
126
|
+
let blocked = false;
|
|
127
|
+
let warned = false;
|
|
128
|
+
let redacted = false;
|
|
129
|
+
let redactedText = text;
|
|
130
|
+
let blockMessage = '';
|
|
131
|
+
for (const rule of rules) {
|
|
132
|
+
let violations = [];
|
|
133
|
+
switch (rule.name) {
|
|
134
|
+
case 'pii-detector': {
|
|
135
|
+
const result = checkPII(text, rule.action);
|
|
136
|
+
violations = result.violations;
|
|
137
|
+
if (violations.length > 0 && rule.action === 'redact') {
|
|
138
|
+
redacted = true;
|
|
139
|
+
redactedText = result.redactedText;
|
|
140
|
+
}
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
case 'prompt-injection':
|
|
144
|
+
violations = checkInjection(text);
|
|
145
|
+
break;
|
|
146
|
+
case 'toxicity':
|
|
147
|
+
violations = checkToxicity(text, rule.config?.keywords);
|
|
148
|
+
break;
|
|
149
|
+
case 'topic-restrictor':
|
|
150
|
+
violations = checkTopicRestriction(text, rule.config);
|
|
151
|
+
break;
|
|
152
|
+
case 'length-limit':
|
|
153
|
+
violations = checkLength(text, rule.config);
|
|
154
|
+
break;
|
|
155
|
+
case 'language-filter':
|
|
156
|
+
violations = checkLanguage(text, rule.config);
|
|
157
|
+
break;
|
|
158
|
+
case 'compliance-filter':
|
|
159
|
+
violations = checkCompliance(text);
|
|
160
|
+
break;
|
|
161
|
+
default:
|
|
162
|
+
// Unknown rule — skip
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
if (violations.length > 0) {
|
|
166
|
+
// Override action from rule config
|
|
167
|
+
violations = violations.map(v => ({ ...v, action: rule.action }));
|
|
168
|
+
allViolations.push(...violations);
|
|
169
|
+
if (rule.action === 'block') {
|
|
170
|
+
blocked = true;
|
|
171
|
+
blockMessage = `Message blocked by ${rule.name}: ${violations[0].detail}`;
|
|
172
|
+
}
|
|
173
|
+
else if (rule.action === 'warn') {
|
|
174
|
+
warned = true;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return {
|
|
179
|
+
passed: allViolations.length === 0,
|
|
180
|
+
blocked,
|
|
181
|
+
warned,
|
|
182
|
+
redacted,
|
|
183
|
+
message: blocked ? blockMessage : undefined,
|
|
184
|
+
redactedText: redacted ? redactedText : undefined,
|
|
185
|
+
violations: allViolations,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
exports.GuardrailManager = GuardrailManager;
|
|
190
|
+
// ── Factory from OAD config ─────────────────────────────────
|
|
191
|
+
function createGuardrailsFromConfig(config) {
|
|
192
|
+
return new GuardrailManager({
|
|
193
|
+
input: config.input,
|
|
194
|
+
output: config.output,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
//# sourceMappingURL=guardrails.js.map
|
package/dist/studio/server.d.ts
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
import { IncomingMessage, ServerResponse } from 'http';
|
|
2
2
|
import { Tracer } from '../telemetry';
|
|
3
|
+
export interface WorkflowNode {
|
|
4
|
+
id: string;
|
|
5
|
+
type: 'agent' | 'tool' | 'condition' | 'loop' | 'parallel' | 'input' | 'output';
|
|
6
|
+
name: string;
|
|
7
|
+
x: number;
|
|
8
|
+
y: number;
|
|
9
|
+
config: Record<string, any>;
|
|
10
|
+
}
|
|
11
|
+
export interface WorkflowEdge {
|
|
12
|
+
id: string;
|
|
13
|
+
from: string;
|
|
14
|
+
to: string;
|
|
15
|
+
fromPort: string;
|
|
16
|
+
toPort: string;
|
|
17
|
+
}
|
|
18
|
+
export interface WorkflowDefinition {
|
|
19
|
+
id: string;
|
|
20
|
+
name: string;
|
|
21
|
+
nodes: WorkflowNode[];
|
|
22
|
+
edges: WorkflowEdge[];
|
|
23
|
+
created: string;
|
|
24
|
+
updated: string;
|
|
25
|
+
}
|
|
3
26
|
interface StudioConfig {
|
|
4
27
|
port: number;
|
|
5
28
|
agentDir: string;
|
|
@@ -26,7 +49,13 @@ declare class StudioServer {
|
|
|
26
49
|
private getMemoryStats;
|
|
27
50
|
private getSkills;
|
|
28
51
|
private getTools;
|
|
29
|
-
private
|
|
52
|
+
private getWorkflowsDir;
|
|
53
|
+
private listWorkflows;
|
|
54
|
+
private getWorkflowById;
|
|
55
|
+
private saveWorkflow;
|
|
56
|
+
private deleteWorkflow;
|
|
57
|
+
private runWorkflow;
|
|
58
|
+
private topoSort;
|
|
30
59
|
private getJobs;
|
|
31
60
|
private getRecentLogs;
|
|
32
61
|
private getAnalytics;
|
|
@@ -57,6 +86,7 @@ declare class StudioServer {
|
|
|
57
86
|
private loadOAD;
|
|
58
87
|
private loadPackageJson;
|
|
59
88
|
serveStatic(req: IncomingMessage, res: ServerResponse, url: URL): void;
|
|
89
|
+
private handlePlaygroundChat;
|
|
60
90
|
private readBody;
|
|
61
91
|
}
|
|
62
92
|
export { StudioServer, StudioConfig };
|
package/dist/studio/server.js
CHANGED
|
@@ -107,6 +107,28 @@ class StudioServer {
|
|
|
107
107
|
const route = url.pathname.replace('/api/', '');
|
|
108
108
|
try {
|
|
109
109
|
let data;
|
|
110
|
+
// Dynamic workflow routes (parameterized)
|
|
111
|
+
if (route.match(/^workflows\/[^/]+\/run$/) && req.method === 'POST') {
|
|
112
|
+
const wfId = route.split('/')[1];
|
|
113
|
+
data = await this.runWorkflow(wfId);
|
|
114
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
115
|
+
res.end(JSON.stringify(data));
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (route.match(/^workflows\/[^/]+$/) && req.method === 'GET') {
|
|
119
|
+
const wfId = route.split('/')[1];
|
|
120
|
+
data = this.getWorkflowById(wfId);
|
|
121
|
+
res.writeHead(data.error ? 404 : 200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
122
|
+
res.end(JSON.stringify(data));
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (route.match(/^workflows\/[^/]+$/) && req.method === 'DELETE') {
|
|
126
|
+
const wfId = route.split('/')[1];
|
|
127
|
+
data = this.deleteWorkflow(wfId);
|
|
128
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' });
|
|
129
|
+
res.end(JSON.stringify(data));
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
110
132
|
switch (route) {
|
|
111
133
|
case 'modules':
|
|
112
134
|
data = await this.getModulesStatus();
|
|
@@ -139,7 +161,18 @@ class StudioServer {
|
|
|
139
161
|
data = await this.getTools();
|
|
140
162
|
break;
|
|
141
163
|
case 'workflows/list':
|
|
142
|
-
data =
|
|
164
|
+
data = this.listWorkflows();
|
|
165
|
+
break;
|
|
166
|
+
case 'workflows':
|
|
167
|
+
if (req.method === 'POST')
|
|
168
|
+
data = await this.saveWorkflow(req);
|
|
169
|
+
else if (req.method === 'GET')
|
|
170
|
+
data = this.listWorkflows();
|
|
171
|
+
else {
|
|
172
|
+
res.writeHead(405);
|
|
173
|
+
res.end();
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
143
176
|
break;
|
|
144
177
|
case 'jobs/list':
|
|
145
178
|
data = await this.getJobs();
|
|
@@ -207,6 +240,16 @@ class StudioServer {
|
|
|
207
240
|
case 'telemetry/metrics':
|
|
208
241
|
data = this.tracer ? this.tracer.getMetrics() : [];
|
|
209
242
|
break;
|
|
243
|
+
case 'playground/chat':
|
|
244
|
+
if (req.method === 'POST') {
|
|
245
|
+
return this.handlePlaygroundChat(req, res);
|
|
246
|
+
}
|
|
247
|
+
res.writeHead(405);
|
|
248
|
+
res.end();
|
|
249
|
+
return;
|
|
250
|
+
case 'playground/models':
|
|
251
|
+
data = { models: ['gpt-4o', 'gpt-4o-mini', 'claude-sonnet-4', 'claude-haiku', 'gemini-2.0-flash', 'deepseek-v3'] };
|
|
252
|
+
break;
|
|
210
253
|
default:
|
|
211
254
|
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
212
255
|
res.end(JSON.stringify({ error: 'Not found' }));
|
|
@@ -347,9 +390,96 @@ class StudioServer {
|
|
|
347
390
|
return { tools: [] };
|
|
348
391
|
}
|
|
349
392
|
}
|
|
350
|
-
|
|
393
|
+
getWorkflowsDir() {
|
|
394
|
+
const dir = (0, path_1.join)(this.config.agentDir, '.opc', 'workflows');
|
|
395
|
+
if (!(0, fs_1.existsSync)(dir))
|
|
396
|
+
(0, fs_1.mkdirSync)(dir, { recursive: true });
|
|
397
|
+
return dir;
|
|
398
|
+
}
|
|
399
|
+
listWorkflows() {
|
|
400
|
+
const dir = this.getWorkflowsDir();
|
|
401
|
+
const files = require('fs').readdirSync(dir).filter((f) => f.endsWith('.json'));
|
|
402
|
+
const workflows = files.map((f) => {
|
|
403
|
+
try {
|
|
404
|
+
return JSON.parse((0, fs_1.readFileSync)((0, path_1.join)(dir, f), 'utf-8'));
|
|
405
|
+
}
|
|
406
|
+
catch {
|
|
407
|
+
return null;
|
|
408
|
+
}
|
|
409
|
+
}).filter(Boolean);
|
|
410
|
+
// Also include OAD-defined workflows
|
|
351
411
|
const oad = this.loadOAD();
|
|
352
|
-
|
|
412
|
+
const oadWorkflows = (oad?.spec?.workflows || []).map((w, i) => ({
|
|
413
|
+
id: `oad-${i}`,
|
|
414
|
+
name: w.name || `Workflow ${i + 1}`,
|
|
415
|
+
nodes: [],
|
|
416
|
+
edges: [],
|
|
417
|
+
steps: w.steps,
|
|
418
|
+
source: 'oad',
|
|
419
|
+
}));
|
|
420
|
+
return { workflows: [...workflows, ...oadWorkflows] };
|
|
421
|
+
}
|
|
422
|
+
getWorkflowById(id) {
|
|
423
|
+
const filePath = (0, path_1.join)(this.getWorkflowsDir(), `${id}.json`);
|
|
424
|
+
if (!(0, fs_1.existsSync)(filePath))
|
|
425
|
+
return { error: 'Workflow not found' };
|
|
426
|
+
return JSON.parse((0, fs_1.readFileSync)(filePath, 'utf-8'));
|
|
427
|
+
}
|
|
428
|
+
async saveWorkflow(req) {
|
|
429
|
+
const body = await this.readBody(req);
|
|
430
|
+
const workflow = JSON.parse(body);
|
|
431
|
+
if (!workflow.id)
|
|
432
|
+
workflow.id = `wf-${Date.now()}`;
|
|
433
|
+
workflow.updated = new Date().toISOString();
|
|
434
|
+
if (!workflow.created)
|
|
435
|
+
workflow.created = workflow.updated;
|
|
436
|
+
const filePath = (0, path_1.join)(this.getWorkflowsDir(), `${workflow.id}.json`);
|
|
437
|
+
(0, fs_1.writeFileSync)(filePath, JSON.stringify(workflow, null, 2));
|
|
438
|
+
return { success: true, id: workflow.id };
|
|
439
|
+
}
|
|
440
|
+
deleteWorkflow(id) {
|
|
441
|
+
const filePath = (0, path_1.join)(this.getWorkflowsDir(), `${id}.json`);
|
|
442
|
+
if ((0, fs_1.existsSync)(filePath))
|
|
443
|
+
require('fs').unlinkSync(filePath);
|
|
444
|
+
return { success: true };
|
|
445
|
+
}
|
|
446
|
+
async runWorkflow(id) {
|
|
447
|
+
const wf = this.getWorkflowById(id);
|
|
448
|
+
if ('error' in wf)
|
|
449
|
+
return wf;
|
|
450
|
+
// Basic topological execution simulation
|
|
451
|
+
const results = {};
|
|
452
|
+
const sorted = this.topoSort(wf.nodes, wf.edges);
|
|
453
|
+
for (const node of sorted) {
|
|
454
|
+
results[node.id] = { type: node.type, name: node.name, status: 'completed', output: `[simulated output for ${node.name}]` };
|
|
455
|
+
}
|
|
456
|
+
return { workflowId: id, status: 'completed', results };
|
|
457
|
+
}
|
|
458
|
+
topoSort(nodes, edges) {
|
|
459
|
+
const nodeMap = new Map(nodes.map(n => [n.id, n]));
|
|
460
|
+
const inDegree = new Map();
|
|
461
|
+
const adj = new Map();
|
|
462
|
+
for (const n of nodes) {
|
|
463
|
+
inDegree.set(n.id, 0);
|
|
464
|
+
adj.set(n.id, []);
|
|
465
|
+
}
|
|
466
|
+
for (const e of edges) {
|
|
467
|
+
adj.get(e.from)?.push(e.to);
|
|
468
|
+
inDegree.set(e.to, (inDegree.get(e.to) || 0) + 1);
|
|
469
|
+
}
|
|
470
|
+
const queue = nodes.filter(n => (inDegree.get(n.id) || 0) === 0);
|
|
471
|
+
const result = [];
|
|
472
|
+
while (queue.length > 0) {
|
|
473
|
+
const node = queue.shift();
|
|
474
|
+
result.push(node);
|
|
475
|
+
for (const next of (adj.get(node.id) || [])) {
|
|
476
|
+
const d = (inDegree.get(next) || 1) - 1;
|
|
477
|
+
inDegree.set(next, d);
|
|
478
|
+
if (d === 0)
|
|
479
|
+
queue.push(nodeMap.get(next));
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
return result;
|
|
353
483
|
}
|
|
354
484
|
async getJobs() {
|
|
355
485
|
const oad = this.loadOAD();
|
|
@@ -612,6 +742,27 @@ class StudioServer {
|
|
|
612
742
|
res.writeHead(200, { 'Content-Type': contentType });
|
|
613
743
|
res.end(content);
|
|
614
744
|
}
|
|
745
|
+
async handlePlaygroundChat(req, res) {
|
|
746
|
+
const body = JSON.parse(await this.readBody(req));
|
|
747
|
+
const { messages = [], model = 'gpt-4o', temperature = 0.7, systemPrompt } = body;
|
|
748
|
+
res.writeHead(200, {
|
|
749
|
+
'Content-Type': 'text/event-stream',
|
|
750
|
+
'Cache-Control': 'no-cache',
|
|
751
|
+
'Connection': 'keep-alive',
|
|
752
|
+
'Access-Control-Allow-Origin': '*',
|
|
753
|
+
});
|
|
754
|
+
// Simulated streaming response for playground demo
|
|
755
|
+
const allMsgs = systemPrompt ? [{ role: 'system', content: systemPrompt }, ...messages] : messages;
|
|
756
|
+
const lastMsg = allMsgs[allMsgs.length - 1]?.content || '';
|
|
757
|
+
const response = `This is a playground demo response to: "${lastMsg}"\n\nModel: ${model}, Temperature: ${temperature}\nMessages in context: ${allMsgs.length}`;
|
|
758
|
+
const words = response.split(' ');
|
|
759
|
+
for (let i = 0; i < words.length; i++) {
|
|
760
|
+
const chunk = (i === 0 ? '' : ' ') + words[i];
|
|
761
|
+
res.write(`data: ${JSON.stringify({ content: chunk })}\n\n`);
|
|
762
|
+
}
|
|
763
|
+
res.write('data: [DONE]\n\n');
|
|
764
|
+
res.end();
|
|
765
|
+
}
|
|
615
766
|
readBody(req) {
|
|
616
767
|
return new Promise((resolve, reject) => {
|
|
617
768
|
let body = '';
|