opc-agent 0.7.0 → 0.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/dist/channels/email.d.ts +69 -0
- package/dist/channels/email.js +118 -0
- package/dist/channels/slack.d.ts +62 -0
- package/dist/channels/slack.js +107 -0
- package/dist/channels/wechat.d.ts +62 -0
- package/dist/channels/wechat.js +104 -0
- package/dist/cli.js +45 -17
- package/dist/core/analytics-engine.d.ts +51 -0
- package/dist/core/analytics-engine.js +186 -0
- package/dist/core/cache.d.ts +47 -0
- package/dist/core/cache.js +156 -0
- package/dist/core/compose.d.ts +35 -0
- package/dist/core/compose.js +49 -0
- package/dist/core/orchestrator.d.ts +68 -0
- package/dist/core/orchestrator.js +145 -0
- package/dist/core/rate-limiter.d.ts +47 -0
- package/dist/core/rate-limiter.js +92 -0
- package/dist/i18n/index.d.ts +6 -1
- package/dist/i18n/index.js +86 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +39 -1
- package/dist/templates/data-analyst.d.ts +53 -0
- package/dist/templates/data-analyst.js +70 -0
- package/dist/templates/teacher.d.ts +58 -0
- package/dist/templates/teacher.js +78 -0
- package/dist/testing/index.d.ts +37 -0
- package/dist/testing/index.js +176 -0
- package/dist/tools/calculator.d.ts +7 -0
- package/dist/tools/calculator.js +70 -0
- package/dist/tools/datetime.d.ts +7 -0
- package/dist/tools/datetime.js +159 -0
- package/dist/tools/json-transform.d.ts +7 -0
- package/dist/tools/json-transform.js +184 -0
- package/dist/tools/text-analysis.d.ts +8 -0
- package/dist/tools/text-analysis.js +113 -0
- package/docs/.vitepress/config.ts +92 -0
- package/docs/api/cli.md +48 -0
- package/docs/api/sdk.md +80 -0
- package/docs/guide/configuration.md +79 -0
- package/docs/guide/deployment.md +42 -0
- package/docs/guide/testing.md +84 -0
- package/docs/index.md +27 -0
- package/docs/zh/api/oad-schema.md +3 -0
- package/docs/zh/guide/concepts.md +28 -0
- package/docs/zh/guide/configuration.md +39 -0
- package/docs/zh/guide/deployment.md +3 -0
- package/docs/zh/guide/getting-started.md +58 -0
- package/docs/zh/guide/templates.md +22 -0
- package/docs/zh/guide/testing.md +18 -0
- package/docs/zh/index.md +27 -0
- package/package.json +7 -3
- package/src/channels/email.ts +177 -0
- package/src/channels/slack.ts +160 -0
- package/src/channels/wechat.ts +149 -0
- package/src/cli.ts +45 -19
- package/src/core/analytics-engine.ts +186 -0
- package/src/core/cache.ts +141 -0
- package/src/core/compose.ts +77 -0
- package/src/core/orchestrator.ts +215 -0
- package/src/core/rate-limiter.ts +128 -0
- package/src/i18n/index.ts +87 -1
- package/src/index.ts +28 -0
- package/src/templates/data-analyst.ts +70 -0
- package/src/templates/teacher.ts +79 -0
- package/src/testing/index.ts +181 -0
- package/src/tools/calculator.ts +73 -0
- package/src/tools/datetime.ts +149 -0
- package/src/tools/json-transform.ts +187 -0
- package/src/tools/text-analysis.ts +116 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
import type { AgentContext, Message } from './types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Multi-Agent Orchestrator — v0.8.0
|
|
6
|
+
* Routes messages to specialized sub-agents, supports parallel execution and handoffs.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export interface AgentNode {
|
|
10
|
+
id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
description: string;
|
|
13
|
+
/** Patterns or intents this agent handles */
|
|
14
|
+
routes: string[];
|
|
15
|
+
/** Function that processes a message and returns a response */
|
|
16
|
+
handler: (context: AgentContext, message: Message) => Promise<Message>;
|
|
17
|
+
/** Priority for routing conflicts (higher wins) */
|
|
18
|
+
priority?: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface OrchestratorWorkflow {
|
|
22
|
+
name: string;
|
|
23
|
+
description?: string;
|
|
24
|
+
/** Ordered list of agent IDs for sequential execution */
|
|
25
|
+
steps?: string[];
|
|
26
|
+
/** List of agent IDs for parallel execution */
|
|
27
|
+
parallel?: string[];
|
|
28
|
+
/** Router config: auto-route based on message content */
|
|
29
|
+
router?: {
|
|
30
|
+
agents: string[];
|
|
31
|
+
fallback?: string;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface HandoffRequest {
|
|
36
|
+
fromAgent: string;
|
|
37
|
+
toAgent: string;
|
|
38
|
+
context: AgentContext;
|
|
39
|
+
reason: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface OrchestratorConfig {
|
|
43
|
+
agents: AgentNode[];
|
|
44
|
+
workflows?: OrchestratorWorkflow[];
|
|
45
|
+
defaultWorkflow?: string;
|
|
46
|
+
maxParallel?: number;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export class Orchestrator extends EventEmitter {
|
|
50
|
+
private agents: Map<string, AgentNode> = new Map();
|
|
51
|
+
private workflows: Map<string, OrchestratorWorkflow> = new Map();
|
|
52
|
+
private defaultWorkflow?: string;
|
|
53
|
+
private maxParallel: number;
|
|
54
|
+
|
|
55
|
+
constructor(config: OrchestratorConfig) {
|
|
56
|
+
super();
|
|
57
|
+
this.maxParallel = config.maxParallel ?? 5;
|
|
58
|
+
this.defaultWorkflow = config.defaultWorkflow;
|
|
59
|
+
|
|
60
|
+
for (const agent of config.agents) {
|
|
61
|
+
this.agents.set(agent.id, agent);
|
|
62
|
+
}
|
|
63
|
+
for (const wf of config.workflows ?? []) {
|
|
64
|
+
this.workflows.set(wf.name, wf);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Register a new agent node */
|
|
69
|
+
registerAgent(agent: AgentNode): void {
|
|
70
|
+
this.agents.set(agent.id, agent);
|
|
71
|
+
this.emit('agent:registered', agent.id);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Unregister an agent */
|
|
75
|
+
unregisterAgent(id: string): void {
|
|
76
|
+
this.agents.delete(id);
|
|
77
|
+
this.emit('agent:unregistered', id);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Route a message to the best-matching agent */
|
|
81
|
+
route(message: Message): AgentNode | undefined {
|
|
82
|
+
const content = message.content.toLowerCase();
|
|
83
|
+
let bestMatch: AgentNode | undefined;
|
|
84
|
+
let bestPriority = -1;
|
|
85
|
+
|
|
86
|
+
for (const agent of this.agents.values()) {
|
|
87
|
+
for (const route of agent.routes) {
|
|
88
|
+
if (content.includes(route.toLowerCase()) || new RegExp(route, 'i').test(content)) {
|
|
89
|
+
const priority = agent.priority ?? 0;
|
|
90
|
+
if (priority > bestPriority) {
|
|
91
|
+
bestMatch = agent;
|
|
92
|
+
bestPriority = priority;
|
|
93
|
+
}
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return bestMatch;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Execute a single agent */
|
|
102
|
+
async executeAgent(agentId: string, context: AgentContext, message: Message): Promise<Message> {
|
|
103
|
+
const agent = this.agents.get(agentId);
|
|
104
|
+
if (!agent) throw new Error(`Agent not found: ${agentId}`);
|
|
105
|
+
this.emit('agent:execute', agentId, message);
|
|
106
|
+
const result = await agent.handler(context, message);
|
|
107
|
+
this.emit('agent:complete', agentId, result);
|
|
108
|
+
return result;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Run multiple agents in parallel */
|
|
112
|
+
async executeParallel(
|
|
113
|
+
agentIds: string[],
|
|
114
|
+
context: AgentContext,
|
|
115
|
+
message: Message
|
|
116
|
+
): Promise<Map<string, Message>> {
|
|
117
|
+
const results = new Map<string, Message>();
|
|
118
|
+
const batches: string[][] = [];
|
|
119
|
+
|
|
120
|
+
// Batch by maxParallel
|
|
121
|
+
for (let i = 0; i < agentIds.length; i += this.maxParallel) {
|
|
122
|
+
batches.push(agentIds.slice(i, i + this.maxParallel));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
for (const batch of batches) {
|
|
126
|
+
const promises = batch.map(async (id) => {
|
|
127
|
+
const result = await this.executeAgent(id, context, message);
|
|
128
|
+
results.set(id, result);
|
|
129
|
+
});
|
|
130
|
+
await Promise.all(promises);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return results;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Execute a named workflow */
|
|
137
|
+
async executeWorkflow(
|
|
138
|
+
workflowName: string,
|
|
139
|
+
context: AgentContext,
|
|
140
|
+
message: Message
|
|
141
|
+
): Promise<Message[]> {
|
|
142
|
+
const wf = this.workflows.get(workflowName);
|
|
143
|
+
if (!wf) throw new Error(`Workflow not found: ${workflowName}`);
|
|
144
|
+
|
|
145
|
+
const results: Message[] = [];
|
|
146
|
+
|
|
147
|
+
// Sequential steps
|
|
148
|
+
if (wf.steps) {
|
|
149
|
+
let currentMessage = message;
|
|
150
|
+
for (const agentId of wf.steps) {
|
|
151
|
+
const result = await this.executeAgent(agentId, context, currentMessage);
|
|
152
|
+
results.push(result);
|
|
153
|
+
currentMessage = result; // chain output → next input
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Parallel execution
|
|
158
|
+
if (wf.parallel) {
|
|
159
|
+
const parallelResults = await this.executeParallel(wf.parallel, context, message);
|
|
160
|
+
results.push(...parallelResults.values());
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Router-based
|
|
164
|
+
if (wf.router) {
|
|
165
|
+
const matched = this.route(message);
|
|
166
|
+
const targetId = matched && wf.router.agents.includes(matched.id)
|
|
167
|
+
? matched.id
|
|
168
|
+
: wf.router.fallback;
|
|
169
|
+
if (targetId) {
|
|
170
|
+
const result = await this.executeAgent(targetId, context, message);
|
|
171
|
+
results.push(result);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return results;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Hand off conversation from one agent to another */
|
|
179
|
+
async handoff(request: HandoffRequest): Promise<Message> {
|
|
180
|
+
this.emit('agent:handoff', request);
|
|
181
|
+
const { toAgent, context } = request;
|
|
182
|
+
const lastMessage = context.messages[context.messages.length - 1];
|
|
183
|
+
if (!lastMessage) throw new Error('No message in context for handoff');
|
|
184
|
+
return this.executeAgent(toAgent, context, lastMessage);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** Process an incoming message using the default workflow or routing */
|
|
188
|
+
async process(context: AgentContext, message: Message): Promise<Message[]> {
|
|
189
|
+
if (this.defaultWorkflow) {
|
|
190
|
+
return this.executeWorkflow(this.defaultWorkflow, context, message);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Fallback: route to single agent
|
|
194
|
+
const agent = this.route(message);
|
|
195
|
+
if (agent) {
|
|
196
|
+
const result = await this.executeAgent(agent.id, context, message);
|
|
197
|
+
return [result];
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return [{
|
|
201
|
+
id: `orch-${Date.now()}`,
|
|
202
|
+
role: 'assistant',
|
|
203
|
+
content: 'No agent available to handle this request.',
|
|
204
|
+
timestamp: Date.now(),
|
|
205
|
+
}];
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
getAgents(): AgentNode[] {
|
|
209
|
+
return Array.from(this.agents.values());
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
getWorkflows(): OrchestratorWorkflow[] {
|
|
213
|
+
return Array.from(this.workflows.values());
|
|
214
|
+
}
|
|
215
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rate Limiter - Per-user and per-provider rate limiting with queuing.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
interface RateLimitConfig {
|
|
6
|
+
maxRequests: number;
|
|
7
|
+
windowMs: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface RateLimitEntry {
|
|
11
|
+
timestamps: number[];
|
|
12
|
+
queue: Array<{ resolve: () => void; reject: (err: Error) => void }>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class RateLimiter {
|
|
16
|
+
private userLimits: Map<string, RateLimitEntry> = new Map();
|
|
17
|
+
private providerLimits: Map<string, RateLimitEntry> = new Map();
|
|
18
|
+
private userConfig: RateLimitConfig;
|
|
19
|
+
private providerConfig: RateLimitConfig;
|
|
20
|
+
private maxQueueSize: number;
|
|
21
|
+
|
|
22
|
+
constructor(opts?: {
|
|
23
|
+
userLimit?: RateLimitConfig;
|
|
24
|
+
providerLimit?: RateLimitConfig;
|
|
25
|
+
maxQueueSize?: number;
|
|
26
|
+
}) {
|
|
27
|
+
this.userConfig = opts?.userLimit ?? { maxRequests: 60, windowMs: 60_000 };
|
|
28
|
+
this.providerConfig = opts?.providerLimit ?? { maxRequests: 100, windowMs: 60_000 };
|
|
29
|
+
this.maxQueueSize = opts?.maxQueueSize ?? 50;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Check if a request is allowed. If not, queues it with backpressure.
|
|
34
|
+
* Returns a promise that resolves when the request can proceed.
|
|
35
|
+
*/
|
|
36
|
+
async acquire(userId: string, provider: string): Promise<void> {
|
|
37
|
+
await this.checkLimit(userId, this.userLimits, this.userConfig, 'user');
|
|
38
|
+
await this.checkLimit(provider, this.providerLimits, this.providerConfig, 'provider');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private async checkLimit(
|
|
42
|
+
key: string,
|
|
43
|
+
limits: Map<string, RateLimitEntry>,
|
|
44
|
+
config: RateLimitConfig,
|
|
45
|
+
type: string,
|
|
46
|
+
): Promise<void> {
|
|
47
|
+
let entry = limits.get(key);
|
|
48
|
+
if (!entry) {
|
|
49
|
+
entry = { timestamps: [], queue: [] };
|
|
50
|
+
limits.set(key, entry);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const now = Date.now();
|
|
54
|
+
// Prune old timestamps
|
|
55
|
+
entry.timestamps = entry.timestamps.filter(t => t > now - config.windowMs);
|
|
56
|
+
|
|
57
|
+
if (entry.timestamps.length < config.maxRequests) {
|
|
58
|
+
entry.timestamps.push(now);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Rate limited - queue with backpressure
|
|
63
|
+
if (entry.queue.length >= this.maxQueueSize) {
|
|
64
|
+
throw new Error(`Rate limit exceeded for ${type} "${key}" and queue is full (${this.maxQueueSize})`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return new Promise<void>((resolve, reject) => {
|
|
68
|
+
entry!.queue.push({ resolve, reject });
|
|
69
|
+
// Set timeout to process queue when window expires
|
|
70
|
+
const oldestTs = entry!.timestamps[0];
|
|
71
|
+
const waitMs = oldestTs + config.windowMs - now + 10;
|
|
72
|
+
setTimeout(() => {
|
|
73
|
+
this.processQueue(key, limits, config);
|
|
74
|
+
}, Math.max(waitMs, 100));
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private processQueue(
|
|
79
|
+
key: string,
|
|
80
|
+
limits: Map<string, RateLimitEntry>,
|
|
81
|
+
config: RateLimitConfig,
|
|
82
|
+
): void {
|
|
83
|
+
const entry = limits.get(key);
|
|
84
|
+
if (!entry || entry.queue.length === 0) return;
|
|
85
|
+
|
|
86
|
+
const now = Date.now();
|
|
87
|
+
entry.timestamps = entry.timestamps.filter(t => t > now - config.windowMs);
|
|
88
|
+
|
|
89
|
+
while (entry.queue.length > 0 && entry.timestamps.length < config.maxRequests) {
|
|
90
|
+
entry.timestamps.push(now);
|
|
91
|
+
const item = entry.queue.shift()!;
|
|
92
|
+
item.resolve();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Get current usage stats.
|
|
98
|
+
*/
|
|
99
|
+
getUsage(userId?: string, provider?: string): {
|
|
100
|
+
user?: { used: number; limit: number; windowMs: number };
|
|
101
|
+
provider?: { used: number; limit: number; windowMs: number };
|
|
102
|
+
} {
|
|
103
|
+
const now = Date.now();
|
|
104
|
+
const result: any = {};
|
|
105
|
+
|
|
106
|
+
if (userId) {
|
|
107
|
+
const entry = this.userLimits.get(userId);
|
|
108
|
+
const used = entry ? entry.timestamps.filter(t => t > now - this.userConfig.windowMs).length : 0;
|
|
109
|
+
result.user = { used, limit: this.userConfig.maxRequests, windowMs: this.userConfig.windowMs };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (provider) {
|
|
113
|
+
const entry = this.providerLimits.get(provider);
|
|
114
|
+
const used = entry ? entry.timestamps.filter(t => t > now - this.providerConfig.windowMs).length : 0;
|
|
115
|
+
result.provider = { used, limit: this.providerConfig.maxRequests, windowMs: this.providerConfig.windowMs };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return result;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Reset all limits.
|
|
123
|
+
*/
|
|
124
|
+
reset(): void {
|
|
125
|
+
this.userLimits.clear();
|
|
126
|
+
this.providerLimits.clear();
|
|
127
|
+
}
|
|
128
|
+
}
|
package/src/i18n/index.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Internationalization (i18n) support for OPC Agent.
|
|
3
|
+
* Supports English, Chinese, and Japanese.
|
|
3
4
|
*/
|
|
4
|
-
export type Locale = 'en' | 'zh-CN';
|
|
5
|
+
export type Locale = 'en' | 'zh-CN' | 'ja';
|
|
5
6
|
|
|
6
7
|
export interface I18nMessages {
|
|
7
8
|
[key: string]: string;
|
|
@@ -19,6 +20,7 @@ const messages: Record<Locale, I18nMessages> = {
|
|
|
19
20
|
'cli.init.success': 'Created agent project: {name}',
|
|
20
21
|
'cli.build.success': 'Build successful: {name} v{version}',
|
|
21
22
|
'cli.test.pass': 'All tests passed',
|
|
23
|
+
'cli.test.fail': '{count} test(s) failed',
|
|
22
24
|
'cli.stats.title': 'Agent Analytics',
|
|
23
25
|
'cli.stats.messages': 'Messages Processed',
|
|
24
26
|
'cli.stats.avgTime': 'Avg Response Time',
|
|
@@ -26,6 +28,24 @@ const messages: Record<Locale, I18nMessages> = {
|
|
|
26
28
|
'cli.stats.uptime': 'Uptime',
|
|
27
29
|
'plugin.loaded': 'Plugin "{name}" loaded',
|
|
28
30
|
'plugin.error': 'Plugin "{name}" failed: {error}',
|
|
31
|
+
'web.title': 'OPC Agent',
|
|
32
|
+
'web.chat.placeholder': 'Type a message...',
|
|
33
|
+
'web.chat.send': 'Send',
|
|
34
|
+
'web.nav.chat': 'Chat',
|
|
35
|
+
'web.nav.dashboard': 'Dashboard',
|
|
36
|
+
'web.nav.settings': 'Settings',
|
|
37
|
+
'web.dashboard.title': 'Dashboard',
|
|
38
|
+
'web.dashboard.messages': 'Messages',
|
|
39
|
+
'web.dashboard.tokens': 'Tokens Used',
|
|
40
|
+
'web.dashboard.errors': 'Errors',
|
|
41
|
+
'web.dashboard.avgLatency': 'Avg Latency',
|
|
42
|
+
'web.settings.title': 'Settings',
|
|
43
|
+
'web.settings.language': 'Language',
|
|
44
|
+
'web.settings.theme': 'Theme',
|
|
45
|
+
'web.settings.save': 'Save',
|
|
46
|
+
'cache.hit': 'Cache hit',
|
|
47
|
+
'cache.miss': 'Cache miss',
|
|
48
|
+
'rateLimit.exceeded': 'Rate limit exceeded. Please wait.',
|
|
29
49
|
},
|
|
30
50
|
'zh-CN': {
|
|
31
51
|
'agent.started': '智能体 "{name}" 启动成功',
|
|
@@ -38,6 +58,7 @@ const messages: Record<Locale, I18nMessages> = {
|
|
|
38
58
|
'cli.init.success': '已创建智能体项目: {name}',
|
|
39
59
|
'cli.build.success': '构建成功: {name} v{version}',
|
|
40
60
|
'cli.test.pass': '所有测试通过',
|
|
61
|
+
'cli.test.fail': '{count} 个测试失败',
|
|
41
62
|
'cli.stats.title': '智能体分析',
|
|
42
63
|
'cli.stats.messages': '已处理消息',
|
|
43
64
|
'cli.stats.avgTime': '平均响应时间',
|
|
@@ -45,6 +66,62 @@ const messages: Record<Locale, I18nMessages> = {
|
|
|
45
66
|
'cli.stats.uptime': '运行时间',
|
|
46
67
|
'plugin.loaded': '插件 "{name}" 已加载',
|
|
47
68
|
'plugin.error': '插件 "{name}" 失败: {error}',
|
|
69
|
+
'web.title': 'OPC 智能体',
|
|
70
|
+
'web.chat.placeholder': '输入消息...',
|
|
71
|
+
'web.chat.send': '发送',
|
|
72
|
+
'web.nav.chat': '对话',
|
|
73
|
+
'web.nav.dashboard': '仪表盘',
|
|
74
|
+
'web.nav.settings': '设置',
|
|
75
|
+
'web.dashboard.title': '仪表盘',
|
|
76
|
+
'web.dashboard.messages': '消息数',
|
|
77
|
+
'web.dashboard.tokens': '已用 Token',
|
|
78
|
+
'web.dashboard.errors': '错误数',
|
|
79
|
+
'web.dashboard.avgLatency': '平均延迟',
|
|
80
|
+
'web.settings.title': '设置',
|
|
81
|
+
'web.settings.language': '语言',
|
|
82
|
+
'web.settings.theme': '主题',
|
|
83
|
+
'web.settings.save': '保存',
|
|
84
|
+
'cache.hit': '缓存命中',
|
|
85
|
+
'cache.miss': '缓存未命中',
|
|
86
|
+
'rateLimit.exceeded': '请求过于频繁,请稍候。',
|
|
87
|
+
},
|
|
88
|
+
'ja': {
|
|
89
|
+
'agent.started': 'エージェント「{name}」が正常に起動しました',
|
|
90
|
+
'agent.stopped': 'エージェント「{name}」が停止しました',
|
|
91
|
+
'agent.error': 'エラーが発生しました: {error}',
|
|
92
|
+
'agent.greeting': 'こんにちは!何かお手伝いできますか?',
|
|
93
|
+
'agent.farewell': 'さようなら!良い一日を。',
|
|
94
|
+
'agent.notUnderstood': '申し訳ございません、理解できませんでした。別の言い方でお願いできますか?',
|
|
95
|
+
'agent.handoff': 'オペレーターにおつなぎします。',
|
|
96
|
+
'cli.init.success': 'エージェントプロジェクトを作成しました: {name}',
|
|
97
|
+
'cli.build.success': 'ビルド成功: {name} v{version}',
|
|
98
|
+
'cli.test.pass': '全テスト合格',
|
|
99
|
+
'cli.test.fail': '{count} 件のテストが失敗しました',
|
|
100
|
+
'cli.stats.title': 'エージェント分析',
|
|
101
|
+
'cli.stats.messages': '処理済みメッセージ',
|
|
102
|
+
'cli.stats.avgTime': '平均応答時間',
|
|
103
|
+
'cli.stats.errors': 'エラー数',
|
|
104
|
+
'cli.stats.uptime': '稼働時間',
|
|
105
|
+
'plugin.loaded': 'プラグイン「{name}」を読み込みました',
|
|
106
|
+
'plugin.error': 'プラグイン「{name}」でエラー: {error}',
|
|
107
|
+
'web.title': 'OPC エージェント',
|
|
108
|
+
'web.chat.placeholder': 'メッセージを入力...',
|
|
109
|
+
'web.chat.send': '送信',
|
|
110
|
+
'web.nav.chat': 'チャット',
|
|
111
|
+
'web.nav.dashboard': 'ダッシュボード',
|
|
112
|
+
'web.nav.settings': '設定',
|
|
113
|
+
'web.dashboard.title': 'ダッシュボード',
|
|
114
|
+
'web.dashboard.messages': 'メッセージ数',
|
|
115
|
+
'web.dashboard.tokens': '使用トークン',
|
|
116
|
+
'web.dashboard.errors': 'エラー数',
|
|
117
|
+
'web.dashboard.avgLatency': '平均レイテンシ',
|
|
118
|
+
'web.settings.title': '設定',
|
|
119
|
+
'web.settings.language': '言語',
|
|
120
|
+
'web.settings.theme': 'テーマ',
|
|
121
|
+
'web.settings.save': '保存',
|
|
122
|
+
'cache.hit': 'キャッシュヒット',
|
|
123
|
+
'cache.miss': 'キャッシュミス',
|
|
124
|
+
'rateLimit.exceeded': 'リクエスト制限を超えました。しばらくお待ちください。',
|
|
48
125
|
},
|
|
49
126
|
};
|
|
50
127
|
|
|
@@ -58,6 +135,14 @@ export function getLocale(): Locale {
|
|
|
58
135
|
return currentLocale;
|
|
59
136
|
}
|
|
60
137
|
|
|
138
|
+
export function getSupportedLocales(): { code: Locale; label: string }[] {
|
|
139
|
+
return [
|
|
140
|
+
{ code: 'en', label: 'English' },
|
|
141
|
+
{ code: 'zh-CN', label: '中文' },
|
|
142
|
+
{ code: 'ja', label: '日本語' },
|
|
143
|
+
];
|
|
144
|
+
}
|
|
145
|
+
|
|
61
146
|
export function t(key: string, params?: Record<string, string>): string {
|
|
62
147
|
let msg = messages[currentLocale]?.[key] ?? messages['en']?.[key] ?? key;
|
|
63
148
|
if (params) {
|
|
@@ -71,6 +156,7 @@ export function t(key: string, params?: Record<string, string>): string {
|
|
|
71
156
|
export function detectLocale(): Locale {
|
|
72
157
|
const env = process.env.LANG ?? process.env.LC_ALL ?? process.env.LANGUAGE ?? '';
|
|
73
158
|
if (env.startsWith('zh')) return 'zh-CN';
|
|
159
|
+
if (env.startsWith('ja')) return 'ja';
|
|
74
160
|
return 'en';
|
|
75
161
|
}
|
|
76
162
|
|
package/src/index.ts
CHANGED
|
@@ -58,6 +58,22 @@ export type { AgentManifest, PublishOptions, InstallOptions } from './marketplac
|
|
|
58
58
|
// v0.7.0 modules
|
|
59
59
|
export { createAuthMiddleware, getActiveSessions } from './core/auth';
|
|
60
60
|
export type { AuthConfig, AuthSession } from './core/auth';
|
|
61
|
+
// v0.8.0 modules
|
|
62
|
+
export { Orchestrator } from './core/orchestrator';
|
|
63
|
+
export type { AgentNode, OrchestratorWorkflow, OrchestratorConfig, HandoffRequest } from './core/orchestrator';
|
|
64
|
+
export { AgentPipeline, compose } from './core/compose';
|
|
65
|
+
export type { ComposableAgent, ComposeOptions } from './core/compose';
|
|
66
|
+
export { EmailChannel } from './channels/email';
|
|
67
|
+
export type { EmailChannelConfig, EmailMessage } from './channels/email';
|
|
68
|
+
export { SlackChannel } from './channels/slack';
|
|
69
|
+
export type { SlackChannelConfig, SlashCommandConfig, SlashCommandPayload } from './channels/slack';
|
|
70
|
+
export { WeChatChannel } from './channels/wechat';
|
|
71
|
+
export type { WeChatChannelConfig, WeChatMessage, TemplateMessageData } from './channels/wechat';
|
|
72
|
+
export { CalculatorTool } from './tools/calculator';
|
|
73
|
+
export { DateTimeTool } from './tools/datetime';
|
|
74
|
+
export { JsonTransformTool } from './tools/json-transform';
|
|
75
|
+
export { TextAnalysisTool } from './tools/text-analysis';
|
|
76
|
+
|
|
61
77
|
export { HttpSkill } from './skills/http';
|
|
62
78
|
export { WebhookTriggerSkill } from './skills/webhook-trigger';
|
|
63
79
|
export type { WebhookTarget } from './skills/webhook-trigger';
|
|
@@ -65,3 +81,15 @@ export { SchedulerSkill } from './skills/scheduler';
|
|
|
65
81
|
export type { ScheduledTask } from './skills/scheduler';
|
|
66
82
|
export { DocumentSkill } from './skills/document';
|
|
67
83
|
export type { DocumentChunk } from './skills/document';
|
|
84
|
+
|
|
85
|
+
// v0.9.0 modules
|
|
86
|
+
export { runTests, loadTestCases, formatReport } from './testing';
|
|
87
|
+
export type { TestCase, TestResult, TestReport } from './testing';
|
|
88
|
+
export { AnalyticsEngine } from './core/analytics-engine';
|
|
89
|
+
export type { AnalyticsEvent, AnalyticsStats } from './core/analytics-engine';
|
|
90
|
+
export { RateLimiter } from './core/rate-limiter';
|
|
91
|
+
export { LLMCache } from './core/cache';
|
|
92
|
+
export type { CacheConfig, CacheEntry } from './core/cache';
|
|
93
|
+
export { getSupportedLocales } from './i18n';
|
|
94
|
+
export { createDataAnalystConfig } from './templates/data-analyst';
|
|
95
|
+
export { createTeacherConfig } from './templates/teacher';
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { BaseSkill } from '../skills/base';
|
|
2
|
+
import type { AgentContext, Message, SkillResult } from '../core/types';
|
|
3
|
+
|
|
4
|
+
export class DataQuerySkill extends BaseSkill {
|
|
5
|
+
name = 'data-query';
|
|
6
|
+
description = 'Help users query and analyze data';
|
|
7
|
+
|
|
8
|
+
async execute(_context: AgentContext, message: Message): Promise<SkillResult> {
|
|
9
|
+
const lower = message.content.toLowerCase();
|
|
10
|
+
if (lower.includes('average') || lower.includes('mean') || lower.includes('sum') || lower.includes('count')) {
|
|
11
|
+
return this.match('I can help with that calculation. Could you share the dataset or describe the data source?', 0.8);
|
|
12
|
+
}
|
|
13
|
+
if (lower.includes('chart') || lower.includes('graph') || lower.includes('visualize') || lower.includes('plot')) {
|
|
14
|
+
return this.match('I can help create visualizations. What type of chart would you like? (bar, line, pie, scatter)', 0.85);
|
|
15
|
+
}
|
|
16
|
+
if (lower.includes('csv') || lower.includes('excel') || lower.includes('spreadsheet')) {
|
|
17
|
+
return this.match('I can analyze CSV/Excel data. Please share the file or paste the data.', 0.8);
|
|
18
|
+
}
|
|
19
|
+
return this.noMatch();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class InsightSkill extends BaseSkill {
|
|
24
|
+
name = 'insight-generator';
|
|
25
|
+
description = 'Generate insights from data patterns';
|
|
26
|
+
|
|
27
|
+
async execute(_context: AgentContext, message: Message): Promise<SkillResult> {
|
|
28
|
+
const lower = message.content.toLowerCase();
|
|
29
|
+
if (lower.includes('trend') || lower.includes('pattern') || lower.includes('insight') || lower.includes('anomaly')) {
|
|
30
|
+
return this.match('I\'ll analyze the data for trends and anomalies. Please provide the dataset or describe what you\'re looking for.', 0.8);
|
|
31
|
+
}
|
|
32
|
+
return this.noMatch();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const DATA_ANALYST_SYSTEM_PROMPT = `You are a professional data analyst assistant. Your goals:
|
|
37
|
+
1. Help users query, transform, and analyze data
|
|
38
|
+
2. Create clear visualizations and summaries
|
|
39
|
+
3. Identify trends, patterns, and anomalies
|
|
40
|
+
4. Explain findings in plain language
|
|
41
|
+
Be precise with numbers, always cite your data source, and suggest next steps for deeper analysis.`;
|
|
42
|
+
|
|
43
|
+
export function createDataAnalystConfig() {
|
|
44
|
+
return {
|
|
45
|
+
apiVersion: 'opc/v1' as const,
|
|
46
|
+
kind: 'Agent' as const,
|
|
47
|
+
metadata: {
|
|
48
|
+
name: 'data-analyst',
|
|
49
|
+
version: '1.0.0',
|
|
50
|
+
description: 'AI data analyst with data querying, visualization, and insight generation',
|
|
51
|
+
author: 'OPC Agent',
|
|
52
|
+
license: 'Apache-2.0',
|
|
53
|
+
},
|
|
54
|
+
spec: {
|
|
55
|
+
provider: { default: 'openai', allowed: ['openai', 'deepseek', 'qwen'] },
|
|
56
|
+
model: 'gpt-4o-mini',
|
|
57
|
+
systemPrompt: DATA_ANALYST_SYSTEM_PROMPT,
|
|
58
|
+
skills: [
|
|
59
|
+
{ name: 'data-query', description: 'Query and transform data' },
|
|
60
|
+
{ name: 'insight-generator', description: 'Generate data insights' },
|
|
61
|
+
],
|
|
62
|
+
channels: [{ type: 'web' as const, port: 3000 }],
|
|
63
|
+
memory: { shortTerm: true, longTerm: true },
|
|
64
|
+
dtv: {
|
|
65
|
+
trust: { level: 'sandbox' as const },
|
|
66
|
+
value: { metrics: ['queries_processed', 'insights_generated'] },
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { BaseSkill } from '../skills/base';
|
|
2
|
+
import type { AgentContext, Message, SkillResult } from '../core/types';
|
|
3
|
+
|
|
4
|
+
export class LessonPlanSkill extends BaseSkill {
|
|
5
|
+
name = 'lesson-plan';
|
|
6
|
+
description = 'Create and manage lesson plans';
|
|
7
|
+
|
|
8
|
+
async execute(_context: AgentContext, message: Message): Promise<SkillResult> {
|
|
9
|
+
const lower = message.content.toLowerCase();
|
|
10
|
+
if (lower.includes('lesson') || lower.includes('plan') || lower.includes('curriculum') || lower.includes('syllabus')) {
|
|
11
|
+
return this.match('I can help create a lesson plan. What subject, grade level, and learning objectives should I focus on?', 0.85);
|
|
12
|
+
}
|
|
13
|
+
return this.noMatch();
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class QuizSkill extends BaseSkill {
|
|
18
|
+
name = 'quiz-generator';
|
|
19
|
+
description = 'Generate quizzes and assessments';
|
|
20
|
+
|
|
21
|
+
async execute(_context: AgentContext, message: Message): Promise<SkillResult> {
|
|
22
|
+
const lower = message.content.toLowerCase();
|
|
23
|
+
if (lower.includes('quiz') || lower.includes('test') || lower.includes('assessment') || lower.includes('exam') || lower.includes('question')) {
|
|
24
|
+
return this.match('I\'ll create a quiz for you. What topic, difficulty level, and number of questions would you like?', 0.85);
|
|
25
|
+
}
|
|
26
|
+
return this.noMatch();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class ExplainSkill extends BaseSkill {
|
|
31
|
+
name = 'concept-explainer';
|
|
32
|
+
description = 'Explain concepts at appropriate level';
|
|
33
|
+
|
|
34
|
+
async execute(_context: AgentContext, message: Message): Promise<SkillResult> {
|
|
35
|
+
const lower = message.content.toLowerCase();
|
|
36
|
+
if (lower.includes('explain') || lower.includes('what is') || lower.includes('how does') || lower.includes('why')) {
|
|
37
|
+
return this.match('Let me explain that concept. What\'s your current level of understanding so I can tailor my explanation?', 0.75);
|
|
38
|
+
}
|
|
39
|
+
return this.noMatch();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const TEACHER_SYSTEM_PROMPT = `You are a patient and encouraging teacher assistant. Your goals:
|
|
44
|
+
1. Create engaging lesson plans tailored to student level
|
|
45
|
+
2. Generate quizzes and assessments with answer keys
|
|
46
|
+
3. Explain complex concepts using analogies and examples
|
|
47
|
+
4. Provide constructive feedback and encouragement
|
|
48
|
+
5. Adapt teaching style to different learning preferences
|
|
49
|
+
Be patient, use clear language, and always check for understanding. Use the Socratic method when appropriate.`;
|
|
50
|
+
|
|
51
|
+
export function createTeacherConfig() {
|
|
52
|
+
return {
|
|
53
|
+
apiVersion: 'opc/v1' as const,
|
|
54
|
+
kind: 'Agent' as const,
|
|
55
|
+
metadata: {
|
|
56
|
+
name: 'teacher',
|
|
57
|
+
version: '1.0.0',
|
|
58
|
+
description: 'AI teacher assistant with lesson planning, quiz generation, and concept explanation',
|
|
59
|
+
author: 'OPC Agent',
|
|
60
|
+
license: 'Apache-2.0',
|
|
61
|
+
},
|
|
62
|
+
spec: {
|
|
63
|
+
provider: { default: 'openai', allowed: ['openai', 'deepseek', 'qwen'] },
|
|
64
|
+
model: 'gpt-4o-mini',
|
|
65
|
+
systemPrompt: TEACHER_SYSTEM_PROMPT,
|
|
66
|
+
skills: [
|
|
67
|
+
{ name: 'lesson-plan', description: 'Create lesson plans' },
|
|
68
|
+
{ name: 'quiz-generator', description: 'Generate quizzes' },
|
|
69
|
+
{ name: 'concept-explainer', description: 'Explain concepts' },
|
|
70
|
+
],
|
|
71
|
+
channels: [{ type: 'web' as const, port: 3000 }],
|
|
72
|
+
memory: { shortTerm: true, longTerm: true },
|
|
73
|
+
dtv: {
|
|
74
|
+
trust: { level: 'sandbox' as const },
|
|
75
|
+
value: { metrics: ['lessons_created', 'quizzes_generated', 'concepts_explained'] },
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
}
|