opc-agent 1.3.0 → 1.3.2
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 +6 -0
- package/CONTRIBUTING.md +75 -75
- package/README.md +358 -235
- package/README.zh-CN.md +415 -415
- package/dist/core/dashboard.d.ts +35 -0
- package/dist/core/dashboard.js +157 -0
- package/dist/core/priority.d.ts +52 -0
- package/dist/core/priority.js +102 -0
- package/dist/core/streaming.d.ts +56 -0
- package/dist/core/streaming.js +160 -0
- package/dist/deploy/hermes.js +22 -22
- package/dist/deploy/openclaw.js +31 -31
- package/dist/index.d.ts +8 -0
- package/dist/index.js +12 -1
- package/dist/templates/code-reviewer.js +5 -5
- package/dist/templates/customer-service.js +2 -2
- package/dist/templates/data-analyst.js +5 -5
- package/dist/templates/knowledge-base.js +2 -2
- package/dist/templates/sales-assistant.js +4 -4
- package/dist/templates/teacher.js +6 -6
- package/dist/tools/gateway.d.ts +28 -0
- package/dist/tools/gateway.js +177 -0
- package/docs/.vitepress/config.ts +103 -103
- package/docs/api/cli.md +48 -48
- package/docs/api/oad-schema.md +64 -64
- package/docs/api/sdk.md +80 -80
- package/docs/guide/concepts.md +51 -51
- package/docs/guide/configuration.md +79 -79
- package/docs/guide/deployment.md +42 -42
- package/docs/guide/getting-started.md +44 -44
- package/docs/guide/templates.md +28 -28
- package/docs/guide/testing.md +84 -84
- package/docs/index.md +27 -27
- package/docs/zh/api/cli.md +54 -54
- package/docs/zh/api/oad-schema.md +87 -87
- package/docs/zh/api/sdk.md +102 -102
- package/docs/zh/guide/concepts.md +104 -104
- package/docs/zh/guide/configuration.md +135 -135
- package/docs/zh/guide/deployment.md +81 -81
- package/docs/zh/guide/getting-started.md +82 -82
- package/docs/zh/guide/templates.md +84 -84
- package/docs/zh/guide/testing.md +88 -88
- package/docs/zh/index.md +27 -27
- package/examples/customer-service-demo/README.md +90 -90
- package/examples/customer-service-demo/oad.yaml +107 -107
- package/package.json +50 -50
- package/src/analytics/index.ts +66 -66
- package/src/channels/discord.ts +192 -192
- package/src/channels/email.ts +177 -177
- package/src/channels/feishu.ts +236 -236
- package/src/channels/index.ts +15 -15
- package/src/channels/slack.ts +160 -160
- package/src/channels/telegram.ts +90 -90
- package/src/channels/voice.ts +106 -106
- package/src/channels/webhook.ts +199 -199
- package/src/channels/websocket.ts +87 -87
- package/src/channels/wechat.ts +149 -149
- package/src/cli.ts +1 -119
- package/src/core/a2a.ts +143 -143
- package/src/core/agent.ts +152 -152
- package/src/core/analytics-engine.ts +186 -186
- package/src/core/auth.ts +57 -57
- package/src/core/cache.ts +141 -141
- package/src/core/compose.ts +77 -77
- package/src/core/config.ts +14 -14
- package/src/core/dashboard.ts +219 -0
- package/src/core/errors.ts +148 -148
- package/src/core/hitl.ts +138 -138
- package/src/core/logger.ts +57 -57
- package/src/core/orchestrator.ts +215 -215
- package/src/core/performance.ts +187 -187
- package/src/core/priority.ts +140 -0
- package/src/core/rate-limiter.ts +128 -128
- package/src/core/room.ts +109 -109
- package/src/core/runtime.ts +152 -152
- package/src/core/sandbox.ts +101 -101
- package/src/core/security.ts +171 -171
- package/src/core/types.ts +68 -68
- package/src/core/versioning.ts +106 -106
- package/src/core/watch.ts +178 -178
- package/src/core/workflow.ts +235 -235
- package/src/deploy/hermes.ts +156 -156
- package/src/deploy/openclaw.ts +200 -200
- package/src/dtv/data.ts +29 -0
- package/src/dtv/trust.ts +43 -0
- package/src/dtv/value.ts +47 -0
- package/src/i18n/index.ts +216 -216
- package/src/index.ts +6 -4
- package/src/marketplace/index.ts +223 -0
- package/src/memory/deepbrain.ts +108 -108
- package/src/memory/index.ts +34 -34
- package/src/plugins/index.ts +208 -208
- package/src/schema/oad.ts +155 -154
- package/src/skills/base.ts +16 -16
- package/src/skills/document.ts +100 -100
- package/src/skills/http.ts +35 -35
- package/src/skills/index.ts +27 -27
- package/src/skills/scheduler.ts +80 -80
- package/src/skills/webhook-trigger.ts +59 -59
- package/src/templates/code-reviewer.ts +34 -30
- package/src/templates/customer-service.ts +80 -76
- package/src/templates/data-analyst.ts +70 -66
- package/src/templates/executive-assistant.ts +71 -71
- package/src/templates/financial-advisor.ts +60 -60
- package/src/templates/knowledge-base.ts +31 -27
- package/src/templates/legal-assistant.ts +71 -71
- package/src/templates/sales-assistant.ts +79 -75
- package/src/templates/teacher.ts +79 -75
- package/src/testing/index.ts +181 -181
- package/src/tools/calculator.ts +73 -73
- package/src/tools/datetime.ts +149 -149
- package/src/tools/json-transform.ts +187 -187
- package/src/tools/mcp.ts +76 -76
- package/src/tools/text-analysis.ts +116 -116
- package/templates/Dockerfile +15 -15
- package/templates/code-reviewer/README.md +27 -27
- package/templates/code-reviewer/oad.yaml +41 -41
- package/templates/customer-service/README.md +22 -22
- package/templates/customer-service/oad.yaml +36 -36
- package/templates/docker-compose.yml +21 -21
- package/templates/ecommerce-assistant/README.md +45 -45
- package/templates/ecommerce-assistant/oad.yaml +47 -47
- package/templates/knowledge-base/README.md +28 -28
- package/templates/knowledge-base/oad.yaml +38 -38
- package/templates/sales-assistant/README.md +26 -26
- package/templates/sales-assistant/oad.yaml +43 -43
- package/templates/tech-support/README.md +43 -43
- package/templates/tech-support/oad.yaml +45 -45
- package/tests/a2a.test.ts +66 -66
- package/tests/agent.test.ts +72 -72
- package/tests/analytics.test.ts +50 -50
- package/tests/channel.test.ts +39 -39
- package/tests/e2e.test.ts +134 -134
- package/tests/errors.test.ts +83 -83
- package/tests/hitl.test.ts +71 -71
- package/tests/i18n.test.ts +41 -41
- package/tests/mcp.test.ts +54 -54
- package/tests/oad.test.ts +68 -68
- package/tests/performance.test.ts +115 -115
- package/tests/plugin.test.ts +74 -74
- package/tests/room.test.ts +106 -106
- package/tests/runtime.test.ts +42 -42
- package/tests/sandbox.test.ts +46 -46
- package/tests/security.test.ts +60 -60
- package/tests/templates.test.ts +77 -77
- package/tests/v070.test.ts +76 -76
- package/tests/versioning.test.ts +75 -75
- package/tests/voice.test.ts +61 -61
- package/tests/webhook.test.ts +29 -29
- package/tests/workflow.test.ts +143 -143
- package/tsconfig.json +19 -19
- package/vitest.config.ts +9 -9
- package/src/traces/index.ts +0 -132
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export interface DashboardConfig {
|
|
2
|
+
/** Enable the dashboard (default: false) */
|
|
3
|
+
enabled: boolean;
|
|
4
|
+
/** HTTP port (default: 4100) */
|
|
5
|
+
port?: number;
|
|
6
|
+
/** Bind address (default: 127.0.0.1 for security) */
|
|
7
|
+
host?: string;
|
|
8
|
+
/** Enable CORS (default: false) */
|
|
9
|
+
cors?: boolean;
|
|
10
|
+
}
|
|
11
|
+
interface SessionSummary {
|
|
12
|
+
id: string;
|
|
13
|
+
channel: string;
|
|
14
|
+
messages: number;
|
|
15
|
+
lastActive: number;
|
|
16
|
+
status: 'active' | 'idle' | 'closed';
|
|
17
|
+
}
|
|
18
|
+
export declare class Dashboard {
|
|
19
|
+
private app;
|
|
20
|
+
private server;
|
|
21
|
+
private config;
|
|
22
|
+
private startTime;
|
|
23
|
+
private stats;
|
|
24
|
+
constructor(config: DashboardConfig);
|
|
25
|
+
private setupRoutes;
|
|
26
|
+
private getState;
|
|
27
|
+
trackSession(session: SessionSummary): void;
|
|
28
|
+
trackToolCall(toolName: string): void;
|
|
29
|
+
trackChannel(name: string, connected: boolean, messages?: number): void;
|
|
30
|
+
start(): Promise<void>;
|
|
31
|
+
stop(): Promise<void>;
|
|
32
|
+
private renderHTML;
|
|
33
|
+
}
|
|
34
|
+
export {};
|
|
35
|
+
//# sourceMappingURL=dashboard.d.ts.map
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.Dashboard = void 0;
|
|
7
|
+
const express_1 = __importDefault(require("express"));
|
|
8
|
+
// ─── Dashboard Server ────────────────────────────────────────
|
|
9
|
+
class Dashboard {
|
|
10
|
+
app = (0, express_1.default)();
|
|
11
|
+
server = null;
|
|
12
|
+
config;
|
|
13
|
+
startTime = Date.now();
|
|
14
|
+
stats = {
|
|
15
|
+
sessions: new Map(),
|
|
16
|
+
toolInvocations: new Map(),
|
|
17
|
+
channelStats: new Map(),
|
|
18
|
+
};
|
|
19
|
+
constructor(config) {
|
|
20
|
+
this.config = {
|
|
21
|
+
enabled: config.enabled,
|
|
22
|
+
port: config.port ?? 4100,
|
|
23
|
+
host: config.host ?? '127.0.0.1',
|
|
24
|
+
cors: config.cors ?? false,
|
|
25
|
+
};
|
|
26
|
+
this.setupRoutes();
|
|
27
|
+
}
|
|
28
|
+
setupRoutes() {
|
|
29
|
+
if (this.config.cors) {
|
|
30
|
+
this.app.use((_req, res, next) => {
|
|
31
|
+
res.header('Access-Control-Allow-Origin', '*');
|
|
32
|
+
res.header('Access-Control-Allow-Headers', 'Content-Type');
|
|
33
|
+
next();
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
this.app.use(express_1.default.json());
|
|
37
|
+
// Health check
|
|
38
|
+
this.app.get('/api/health', (_req, res) => {
|
|
39
|
+
res.json({ status: 'ok', uptime: Date.now() - this.startTime });
|
|
40
|
+
});
|
|
41
|
+
// Overview state
|
|
42
|
+
this.app.get('/api/state', (_req, res) => {
|
|
43
|
+
res.json(this.getState());
|
|
44
|
+
});
|
|
45
|
+
// Sessions
|
|
46
|
+
this.app.get('/api/sessions', (_req, res) => {
|
|
47
|
+
res.json([...this.stats.sessions.values()]);
|
|
48
|
+
});
|
|
49
|
+
// Tools
|
|
50
|
+
this.app.get('/api/tools', (_req, res) => {
|
|
51
|
+
const tools = [];
|
|
52
|
+
for (const [name, stat] of this.stats.toolInvocations) {
|
|
53
|
+
tools.push({ name, type: 'builtin', enabled: true, invocations: stat.count, lastUsed: stat.lastUsed });
|
|
54
|
+
}
|
|
55
|
+
res.json(tools);
|
|
56
|
+
});
|
|
57
|
+
// Channels
|
|
58
|
+
this.app.get('/api/channels', (_req, res) => {
|
|
59
|
+
const channels = [];
|
|
60
|
+
for (const [name, stat] of this.stats.channelStats) {
|
|
61
|
+
channels.push({ name, type: name, connected: stat.connected, messageCount: stat.messages });
|
|
62
|
+
}
|
|
63
|
+
res.json(channels);
|
|
64
|
+
});
|
|
65
|
+
// Simple HTML dashboard
|
|
66
|
+
this.app.get('/', (_req, res) => {
|
|
67
|
+
res.send(this.renderHTML());
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
getState() {
|
|
71
|
+
return {
|
|
72
|
+
agent: { name: 'opc-agent', version: '1.3.0', status: 'running', uptime: Date.now() - this.startTime },
|
|
73
|
+
sessions: [...this.stats.sessions.values()],
|
|
74
|
+
tools: [...this.stats.toolInvocations.entries()].map(([name, s]) => ({
|
|
75
|
+
name, type: 'builtin', enabled: true, invocations: s.count, lastUsed: s.lastUsed,
|
|
76
|
+
})),
|
|
77
|
+
channels: [...this.stats.channelStats.entries()].map(([name, s]) => ({
|
|
78
|
+
name, type: name, connected: s.connected, messageCount: s.messages,
|
|
79
|
+
})),
|
|
80
|
+
memory: { provider: 'unknown', entries: 0 },
|
|
81
|
+
modelAuth: { providers: [] },
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
// ─── Event Tracking ──────────────────────────────────────
|
|
85
|
+
trackSession(session) {
|
|
86
|
+
this.stats.sessions.set(session.id, session);
|
|
87
|
+
}
|
|
88
|
+
trackToolCall(toolName) {
|
|
89
|
+
const existing = this.stats.toolInvocations.get(toolName) ?? { count: 0, lastUsed: 0 };
|
|
90
|
+
existing.count++;
|
|
91
|
+
existing.lastUsed = Date.now();
|
|
92
|
+
this.stats.toolInvocations.set(toolName, existing);
|
|
93
|
+
}
|
|
94
|
+
trackChannel(name, connected, messages) {
|
|
95
|
+
const existing = this.stats.channelStats.get(name) ?? { connected: false, messages: 0 };
|
|
96
|
+
existing.connected = connected;
|
|
97
|
+
if (messages !== undefined)
|
|
98
|
+
existing.messages = messages;
|
|
99
|
+
this.stats.channelStats.set(name, existing);
|
|
100
|
+
}
|
|
101
|
+
// ─── Lifecycle ───────────────────────────────────────────
|
|
102
|
+
async start() {
|
|
103
|
+
if (!this.config.enabled)
|
|
104
|
+
return;
|
|
105
|
+
return new Promise((resolve) => {
|
|
106
|
+
this.server = this.app.listen(this.config.port, this.config.host, () => {
|
|
107
|
+
console.log(`[dashboard] http://${this.config.host}:${this.config.port}`);
|
|
108
|
+
resolve();
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
async stop() {
|
|
113
|
+
return new Promise((resolve) => {
|
|
114
|
+
if (this.server)
|
|
115
|
+
this.server.close(() => resolve());
|
|
116
|
+
else
|
|
117
|
+
resolve();
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
renderHTML() {
|
|
121
|
+
return `<!DOCTYPE html>
|
|
122
|
+
<html><head><meta charset="utf-8"><title>OPC Agent Dashboard</title>
|
|
123
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
124
|
+
<style>
|
|
125
|
+
*{box-sizing:border-box;margin:0;padding:0}
|
|
126
|
+
body{font-family:system-ui,-apple-system,sans-serif;background:#0a0a0f;color:#e0e0e0;padding:24px}
|
|
127
|
+
h1{font-size:1.5rem;margin-bottom:20px;color:#7c9aff}
|
|
128
|
+
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:16px}
|
|
129
|
+
.card{background:#14141f;border:1px solid #2a2a3a;border-radius:12px;padding:20px}
|
|
130
|
+
.card h2{font-size:0.85rem;text-transform:uppercase;letter-spacing:1px;color:#888;margin-bottom:12px}
|
|
131
|
+
.stat{font-size:2rem;font-weight:700;color:#7c9aff}
|
|
132
|
+
.sub{font-size:0.8rem;color:#666;margin-top:4px}
|
|
133
|
+
#data{margin-top:20px;font-family:monospace;font-size:0.75rem;color:#555;white-space:pre-wrap}
|
|
134
|
+
</style></head><body>
|
|
135
|
+
<h1>⚡ OPC Agent Dashboard</h1>
|
|
136
|
+
<div class="grid">
|
|
137
|
+
<div class="card"><h2>Status</h2><div class="stat" id="status">Loading…</div><div class="sub" id="uptime"></div></div>
|
|
138
|
+
<div class="card"><h2>Sessions</h2><div class="stat" id="sessions">-</div></div>
|
|
139
|
+
<div class="card"><h2>Tools</h2><div class="stat" id="tools">-</div></div>
|
|
140
|
+
<div class="card"><h2>Channels</h2><div class="stat" id="channels">-</div></div>
|
|
141
|
+
</div>
|
|
142
|
+
<div id="data"></div>
|
|
143
|
+
<script>
|
|
144
|
+
async function poll(){try{const r=await fetch('/api/state');const d=await r.json();
|
|
145
|
+
document.getElementById('status').textContent=d.agent.status;
|
|
146
|
+
document.getElementById('uptime').textContent='Uptime: '+Math.floor(d.agent.uptime/1000)+'s';
|
|
147
|
+
document.getElementById('sessions').textContent=d.sessions.length;
|
|
148
|
+
document.getElementById('tools').textContent=d.tools.length;
|
|
149
|
+
document.getElementById('channels').textContent=d.channels.length;
|
|
150
|
+
document.getElementById('data').textContent=JSON.stringify(d,null,2);
|
|
151
|
+
}catch(e){document.getElementById('status').textContent='offline'}}
|
|
152
|
+
poll();setInterval(poll,5000);
|
|
153
|
+
</script></body></html>`;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
exports.Dashboard = Dashboard;
|
|
157
|
+
//# sourceMappingURL=dashboard.js.map
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export interface PriorityConfig {
|
|
2
|
+
/** Enable priority mode (default: false) */
|
|
3
|
+
enabled: boolean;
|
|
4
|
+
/** Provider-specific priority settings */
|
|
5
|
+
providers?: PriorityProviderConfig[];
|
|
6
|
+
/** Default priority tier */
|
|
7
|
+
defaultTier?: PriorityTier;
|
|
8
|
+
}
|
|
9
|
+
export type PriorityTier = 'standard' | 'fast' | 'batch';
|
|
10
|
+
export interface PriorityProviderConfig {
|
|
11
|
+
provider: string;
|
|
12
|
+
tier: PriorityTier;
|
|
13
|
+
/** Custom endpoint override for priority routing */
|
|
14
|
+
endpoint?: string;
|
|
15
|
+
/** Supported models for this tier */
|
|
16
|
+
models?: string[];
|
|
17
|
+
}
|
|
18
|
+
interface PriorityHeaders {
|
|
19
|
+
[key: string]: string;
|
|
20
|
+
}
|
|
21
|
+
export declare class PriorityRouter {
|
|
22
|
+
private config;
|
|
23
|
+
private runtimeTier;
|
|
24
|
+
constructor(config: PriorityConfig);
|
|
25
|
+
/** Toggle fast mode on/off at runtime */
|
|
26
|
+
toggle(): PriorityTier;
|
|
27
|
+
/** Set specific tier */
|
|
28
|
+
setTier(tier: PriorityTier): void;
|
|
29
|
+
/** Get current tier */
|
|
30
|
+
getTier(): PriorityTier;
|
|
31
|
+
/** Check if fast mode is active */
|
|
32
|
+
isFast(): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Get priority headers for a provider + model combination.
|
|
35
|
+
* Returns empty object if provider doesn't support priority or model isn't eligible.
|
|
36
|
+
*/
|
|
37
|
+
getHeaders(provider: string, model: string): PriorityHeaders;
|
|
38
|
+
/**
|
|
39
|
+
* Get effective endpoint for a provider, allowing priority-specific routing.
|
|
40
|
+
*/
|
|
41
|
+
getEndpoint(provider: string, defaultEndpoint: string): string;
|
|
42
|
+
private getEffectiveTier;
|
|
43
|
+
private isModelEligible;
|
|
44
|
+
/** Status summary for dashboard / CLI */
|
|
45
|
+
status(): {
|
|
46
|
+
tier: PriorityTier;
|
|
47
|
+
enabled: boolean;
|
|
48
|
+
providers: string[];
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
export {};
|
|
52
|
+
//# sourceMappingURL=priority.d.ts.map
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ─── Priority / Fast Mode ────────────────────────────────────
|
|
3
|
+
// Route requests through provider priority tiers for lower latency.
|
|
4
|
+
// Toggle via config or runtime command.
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.PriorityRouter = void 0;
|
|
7
|
+
// Known priority-capable providers and their routing
|
|
8
|
+
const PROVIDER_PRIORITY_MAP = {
|
|
9
|
+
openai: {
|
|
10
|
+
headerKey: 'X-OpenAI-Processing-Priority',
|
|
11
|
+
headerValue: { fast: 'priority', standard: 'auto', batch: 'batch' },
|
|
12
|
+
supportedModels: ['gpt-5', 'gpt-5.4', 'gpt-4.1', 'codex-*', 'o3-*', 'o4-mini*'],
|
|
13
|
+
},
|
|
14
|
+
anthropic: {
|
|
15
|
+
headerKey: 'anthropic-priority',
|
|
16
|
+
headerValue: { fast: 'high', standard: 'normal', batch: 'low' },
|
|
17
|
+
supportedModels: ['claude-opus-*', 'claude-sonnet-*', 'claude-4*'],
|
|
18
|
+
},
|
|
19
|
+
google: {
|
|
20
|
+
headerKey: 'X-Goog-Priority',
|
|
21
|
+
headerValue: { fast: 'high', standard: 'normal', batch: 'low' },
|
|
22
|
+
supportedModels: ['gemini-2.5-*', 'gemini-3-*'],
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
class PriorityRouter {
|
|
26
|
+
config;
|
|
27
|
+
runtimeTier;
|
|
28
|
+
constructor(config) {
|
|
29
|
+
this.config = config;
|
|
30
|
+
this.runtimeTier = config.defaultTier ?? 'standard';
|
|
31
|
+
}
|
|
32
|
+
/** Toggle fast mode on/off at runtime */
|
|
33
|
+
toggle() {
|
|
34
|
+
this.runtimeTier = this.runtimeTier === 'fast' ? 'standard' : 'fast';
|
|
35
|
+
return this.runtimeTier;
|
|
36
|
+
}
|
|
37
|
+
/** Set specific tier */
|
|
38
|
+
setTier(tier) {
|
|
39
|
+
this.runtimeTier = tier;
|
|
40
|
+
}
|
|
41
|
+
/** Get current tier */
|
|
42
|
+
getTier() {
|
|
43
|
+
return this.runtimeTier;
|
|
44
|
+
}
|
|
45
|
+
/** Check if fast mode is active */
|
|
46
|
+
isFast() {
|
|
47
|
+
return this.runtimeTier === 'fast';
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Get priority headers for a provider + model combination.
|
|
51
|
+
* Returns empty object if provider doesn't support priority or model isn't eligible.
|
|
52
|
+
*/
|
|
53
|
+
getHeaders(provider, model) {
|
|
54
|
+
if (!this.config.enabled)
|
|
55
|
+
return {};
|
|
56
|
+
const tier = this.getEffectiveTier(provider);
|
|
57
|
+
if (tier === 'standard')
|
|
58
|
+
return {};
|
|
59
|
+
const providerMap = PROVIDER_PRIORITY_MAP[provider.toLowerCase()];
|
|
60
|
+
if (!providerMap)
|
|
61
|
+
return {};
|
|
62
|
+
// Check model eligibility
|
|
63
|
+
if (!this.isModelEligible(providerMap.supportedModels, model))
|
|
64
|
+
return {};
|
|
65
|
+
return { [providerMap.headerKey]: providerMap.headerValue[tier] };
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Get effective endpoint for a provider, allowing priority-specific routing.
|
|
69
|
+
*/
|
|
70
|
+
getEndpoint(provider, defaultEndpoint) {
|
|
71
|
+
const providerConfig = this.config.providers?.find((p) => p.provider.toLowerCase() === provider.toLowerCase());
|
|
72
|
+
if (providerConfig?.endpoint && this.runtimeTier === 'fast') {
|
|
73
|
+
return providerConfig.endpoint;
|
|
74
|
+
}
|
|
75
|
+
return defaultEndpoint;
|
|
76
|
+
}
|
|
77
|
+
getEffectiveTier(provider) {
|
|
78
|
+
// Check provider-specific override first
|
|
79
|
+
const providerConfig = this.config.providers?.find((p) => p.provider.toLowerCase() === provider.toLowerCase());
|
|
80
|
+
if (providerConfig)
|
|
81
|
+
return providerConfig.tier;
|
|
82
|
+
return this.runtimeTier;
|
|
83
|
+
}
|
|
84
|
+
isModelEligible(patterns, model) {
|
|
85
|
+
return patterns.some((pattern) => {
|
|
86
|
+
if (pattern.endsWith('*')) {
|
|
87
|
+
return model.startsWith(pattern.slice(0, -1));
|
|
88
|
+
}
|
|
89
|
+
return model === pattern;
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
/** Status summary for dashboard / CLI */
|
|
93
|
+
status() {
|
|
94
|
+
return {
|
|
95
|
+
tier: this.runtimeTier,
|
|
96
|
+
enabled: this.config.enabled,
|
|
97
|
+
providers: Object.keys(PROVIDER_PRIORITY_MAP),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
exports.PriorityRouter = PriorityRouter;
|
|
102
|
+
//# sourceMappingURL=priority.js.map
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
export interface StreamChunk {
|
|
3
|
+
id: string;
|
|
4
|
+
type: 'text' | 'tool_call' | 'error' | 'done';
|
|
5
|
+
data: string;
|
|
6
|
+
timestamp: number;
|
|
7
|
+
metadata?: Record<string, unknown>;
|
|
8
|
+
}
|
|
9
|
+
export interface StreamOptions {
|
|
10
|
+
/** High-water mark for backpressure (default 64 chunks). */
|
|
11
|
+
highWaterMark?: number;
|
|
12
|
+
/** Heartbeat interval in ms to keep connection alive (default 15000). */
|
|
13
|
+
heartbeatInterval?: number;
|
|
14
|
+
}
|
|
15
|
+
export declare class StreamableResponse extends EventEmitter {
|
|
16
|
+
readonly id: string;
|
|
17
|
+
private chunks;
|
|
18
|
+
private ended;
|
|
19
|
+
private paused;
|
|
20
|
+
private buffer;
|
|
21
|
+
private highWaterMark;
|
|
22
|
+
constructor(id: string, options?: StreamOptions);
|
|
23
|
+
/** Push a chunk. Returns false if backpressure threshold reached. */
|
|
24
|
+
push(chunk: StreamChunk): boolean;
|
|
25
|
+
/** Resume after backpressure — flush buffered chunks. */
|
|
26
|
+
resume(): void;
|
|
27
|
+
end(): void;
|
|
28
|
+
getChunks(): StreamChunk[];
|
|
29
|
+
/** Collect all text chunks into a single string. */
|
|
30
|
+
getText(): string;
|
|
31
|
+
get isEnded(): boolean;
|
|
32
|
+
get isPaused(): boolean;
|
|
33
|
+
get length(): number;
|
|
34
|
+
}
|
|
35
|
+
export declare class StreamingManager {
|
|
36
|
+
private streams;
|
|
37
|
+
private counter;
|
|
38
|
+
/** Create a new stream. */
|
|
39
|
+
createStream(options?: StreamOptions): StreamableResponse;
|
|
40
|
+
/** Write a text chunk to a stream. */
|
|
41
|
+
writeChunk(streamId: string, data: string, metadata?: Record<string, unknown>): boolean;
|
|
42
|
+
/** End a stream. */
|
|
43
|
+
endStream(streamId: string): void;
|
|
44
|
+
/** Get an existing stream. */
|
|
45
|
+
getStream(streamId: string): StreamableResponse | undefined;
|
|
46
|
+
/** Format a chunk as an SSE event string. */
|
|
47
|
+
static formatSSE(chunk: StreamChunk): string;
|
|
48
|
+
/** Pipe a stream to an SSE-compatible HTTP response (Express-style). */
|
|
49
|
+
static pipeSSE(stream: StreamableResponse, res: {
|
|
50
|
+
write(data: string): boolean;
|
|
51
|
+
end(): void;
|
|
52
|
+
setHeader?(name: string, value: string): void;
|
|
53
|
+
}, options?: StreamOptions): void;
|
|
54
|
+
get activeCount(): number;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=streaming.d.ts.map
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.StreamingManager = exports.StreamableResponse = void 0;
|
|
4
|
+
const events_1 = require("events");
|
|
5
|
+
// ─── StreamableResponse ──────────────────────────────────────
|
|
6
|
+
class StreamableResponse extends events_1.EventEmitter {
|
|
7
|
+
id;
|
|
8
|
+
chunks = [];
|
|
9
|
+
ended = false;
|
|
10
|
+
paused = false;
|
|
11
|
+
buffer = [];
|
|
12
|
+
highWaterMark;
|
|
13
|
+
constructor(id, options) {
|
|
14
|
+
super();
|
|
15
|
+
this.id = id;
|
|
16
|
+
this.highWaterMark = options?.highWaterMark ?? 64;
|
|
17
|
+
}
|
|
18
|
+
/** Push a chunk. Returns false if backpressure threshold reached. */
|
|
19
|
+
push(chunk) {
|
|
20
|
+
if (this.ended)
|
|
21
|
+
return false;
|
|
22
|
+
this.chunks.push(chunk);
|
|
23
|
+
if (this.paused) {
|
|
24
|
+
this.buffer.push(chunk);
|
|
25
|
+
return this.buffer.length < this.highWaterMark;
|
|
26
|
+
}
|
|
27
|
+
this.emit('chunk', chunk);
|
|
28
|
+
if (this.chunks.length >= this.highWaterMark) {
|
|
29
|
+
this.paused = true;
|
|
30
|
+
this.emit('backpressure');
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
/** Resume after backpressure — flush buffered chunks. */
|
|
36
|
+
resume() {
|
|
37
|
+
if (!this.paused)
|
|
38
|
+
return;
|
|
39
|
+
this.paused = false;
|
|
40
|
+
const buffered = this.buffer.splice(0);
|
|
41
|
+
for (const chunk of buffered) {
|
|
42
|
+
this.emit('chunk', chunk);
|
|
43
|
+
}
|
|
44
|
+
this.emit('drain');
|
|
45
|
+
}
|
|
46
|
+
end() {
|
|
47
|
+
if (this.ended)
|
|
48
|
+
return;
|
|
49
|
+
this.ended = true;
|
|
50
|
+
if (this.paused)
|
|
51
|
+
this.resume();
|
|
52
|
+
this.emit('end');
|
|
53
|
+
}
|
|
54
|
+
getChunks() {
|
|
55
|
+
return [...this.chunks];
|
|
56
|
+
}
|
|
57
|
+
/** Collect all text chunks into a single string. */
|
|
58
|
+
getText() {
|
|
59
|
+
return this.chunks
|
|
60
|
+
.filter((c) => c.type === 'text')
|
|
61
|
+
.map((c) => c.data)
|
|
62
|
+
.join('');
|
|
63
|
+
}
|
|
64
|
+
get isEnded() {
|
|
65
|
+
return this.ended;
|
|
66
|
+
}
|
|
67
|
+
get isPaused() {
|
|
68
|
+
return this.paused;
|
|
69
|
+
}
|
|
70
|
+
get length() {
|
|
71
|
+
return this.chunks.length;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
exports.StreamableResponse = StreamableResponse;
|
|
75
|
+
// ─── StreamingManager ────────────────────────────────────────
|
|
76
|
+
class StreamingManager {
|
|
77
|
+
streams = new Map();
|
|
78
|
+
counter = 0;
|
|
79
|
+
/** Create a new stream. */
|
|
80
|
+
createStream(options) {
|
|
81
|
+
const id = `stream_${++this.counter}_${Date.now()}`;
|
|
82
|
+
const stream = new StreamableResponse(id, options);
|
|
83
|
+
this.streams.set(id, stream);
|
|
84
|
+
stream.on('end', () => {
|
|
85
|
+
// Keep ended streams for a bit for late consumers, then clean up
|
|
86
|
+
setTimeout(() => this.streams.delete(id), 30_000);
|
|
87
|
+
});
|
|
88
|
+
return stream;
|
|
89
|
+
}
|
|
90
|
+
/** Write a text chunk to a stream. */
|
|
91
|
+
writeChunk(streamId, data, metadata) {
|
|
92
|
+
const stream = this.streams.get(streamId);
|
|
93
|
+
if (!stream)
|
|
94
|
+
return false;
|
|
95
|
+
return stream.push({
|
|
96
|
+
id: `chunk_${stream.length}`,
|
|
97
|
+
type: 'text',
|
|
98
|
+
data,
|
|
99
|
+
timestamp: Date.now(),
|
|
100
|
+
metadata,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
/** End a stream. */
|
|
104
|
+
endStream(streamId) {
|
|
105
|
+
const stream = this.streams.get(streamId);
|
|
106
|
+
if (!stream)
|
|
107
|
+
return;
|
|
108
|
+
stream.push({
|
|
109
|
+
id: `chunk_${stream.length}`,
|
|
110
|
+
type: 'done',
|
|
111
|
+
data: '',
|
|
112
|
+
timestamp: Date.now(),
|
|
113
|
+
});
|
|
114
|
+
stream.end();
|
|
115
|
+
}
|
|
116
|
+
/** Get an existing stream. */
|
|
117
|
+
getStream(streamId) {
|
|
118
|
+
return this.streams.get(streamId);
|
|
119
|
+
}
|
|
120
|
+
/** Format a chunk as an SSE event string. */
|
|
121
|
+
static formatSSE(chunk) {
|
|
122
|
+
const lines = [];
|
|
123
|
+
lines.push(`event: ${chunk.type}`);
|
|
124
|
+
lines.push(`id: ${chunk.id}`);
|
|
125
|
+
const payload = JSON.stringify({ data: chunk.data, metadata: chunk.metadata });
|
|
126
|
+
lines.push(`data: ${payload}`);
|
|
127
|
+
lines.push('');
|
|
128
|
+
return lines.join('\n') + '\n';
|
|
129
|
+
}
|
|
130
|
+
/** Pipe a stream to an SSE-compatible HTTP response (Express-style). */
|
|
131
|
+
static pipeSSE(stream, res, options) {
|
|
132
|
+
res.setHeader?.('Content-Type', 'text/event-stream');
|
|
133
|
+
res.setHeader?.('Cache-Control', 'no-cache');
|
|
134
|
+
res.setHeader?.('Connection', 'keep-alive');
|
|
135
|
+
const heartbeatMs = options?.heartbeatInterval ?? 15_000;
|
|
136
|
+
const heartbeat = setInterval(() => {
|
|
137
|
+
res.write(': heartbeat\n\n');
|
|
138
|
+
}, heartbeatMs);
|
|
139
|
+
stream.on('chunk', (chunk) => {
|
|
140
|
+
const ok = res.write(StreamingManager.formatSSE(chunk));
|
|
141
|
+
if (!ok && stream.isPaused === false) {
|
|
142
|
+
// Downstream can't keep up — will resume on drain from stream
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
stream.on('end', () => {
|
|
146
|
+
clearInterval(heartbeat);
|
|
147
|
+
res.end();
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
get activeCount() {
|
|
151
|
+
let count = 0;
|
|
152
|
+
for (const s of this.streams.values()) {
|
|
153
|
+
if (!s.isEnded)
|
|
154
|
+
count++;
|
|
155
|
+
}
|
|
156
|
+
return count;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
exports.StreamingManager = StreamingManager;
|
|
160
|
+
//# sourceMappingURL=streaming.js.map
|
package/dist/deploy/hermes.js
CHANGED
|
@@ -113,32 +113,32 @@ function deployToHermes(options) {
|
|
|
113
113
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf-8');
|
|
114
114
|
files.push('settings.json');
|
|
115
115
|
// .env template
|
|
116
|
-
const envContent = `# Hermes Agent Environment
|
|
117
|
-
HERMES_CHARACTER=${oad.metadata.name}
|
|
118
|
-
HERMES_MODEL=${oad.spec.model}
|
|
119
|
-
HERMES_PROVIDER=${oad.spec.provider?.default ?? 'openai'}
|
|
120
|
-
# Add your API keys below:
|
|
121
|
-
# OPENAI_API_KEY=
|
|
122
|
-
# DEEPSEEK_API_KEY=
|
|
116
|
+
const envContent = `# Hermes Agent Environment
|
|
117
|
+
HERMES_CHARACTER=${oad.metadata.name}
|
|
118
|
+
HERMES_MODEL=${oad.spec.model}
|
|
119
|
+
HERMES_PROVIDER=${oad.spec.provider?.default ?? 'openai'}
|
|
120
|
+
# Add your API keys below:
|
|
121
|
+
# OPENAI_API_KEY=
|
|
122
|
+
# DEEPSEEK_API_KEY=
|
|
123
123
|
`;
|
|
124
124
|
fs.writeFileSync(path.join(outputDir, '.env.hermes'), envContent, 'utf-8');
|
|
125
125
|
files.push('.env.hermes');
|
|
126
126
|
// README
|
|
127
|
-
const readme = `# ${oad.metadata.name} - Hermes Agent
|
|
128
|
-
|
|
129
|
-
Converted from OAD format using \`opc deploy --target hermes\`.
|
|
130
|
-
|
|
131
|
-
## Usage
|
|
132
|
-
|
|
133
|
-
1. Copy \`character.json\` to your Hermes agents directory
|
|
134
|
-
2. Configure \`.env.hermes\` with your API keys
|
|
135
|
-
3. Start Hermes with this character
|
|
136
|
-
|
|
137
|
-
## Files
|
|
138
|
-
|
|
139
|
-
- \`character.json\` - Agent character definition
|
|
140
|
-
- \`settings.json\` - Runtime settings
|
|
141
|
-
- \`.env.hermes\` - Environment template
|
|
127
|
+
const readme = `# ${oad.metadata.name} - Hermes Agent
|
|
128
|
+
|
|
129
|
+
Converted from OAD format using \`opc deploy --target hermes\`.
|
|
130
|
+
|
|
131
|
+
## Usage
|
|
132
|
+
|
|
133
|
+
1. Copy \`character.json\` to your Hermes agents directory
|
|
134
|
+
2. Configure \`.env.hermes\` with your API keys
|
|
135
|
+
3. Start Hermes with this character
|
|
136
|
+
|
|
137
|
+
## Files
|
|
138
|
+
|
|
139
|
+
- \`character.json\` - Agent character definition
|
|
140
|
+
- \`settings.json\` - Runtime settings
|
|
141
|
+
- \`.env.hermes\` - Environment template
|
|
142
142
|
`;
|
|
143
143
|
fs.writeFileSync(path.join(outputDir, 'README.md'), readme, 'utf-8');
|
|
144
144
|
files.push('README.md');
|