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.
Files changed (153) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/CONTRIBUTING.md +75 -75
  3. package/README.md +358 -235
  4. package/README.zh-CN.md +415 -415
  5. package/dist/core/dashboard.d.ts +35 -0
  6. package/dist/core/dashboard.js +157 -0
  7. package/dist/core/priority.d.ts +52 -0
  8. package/dist/core/priority.js +102 -0
  9. package/dist/core/streaming.d.ts +56 -0
  10. package/dist/core/streaming.js +160 -0
  11. package/dist/deploy/hermes.js +22 -22
  12. package/dist/deploy/openclaw.js +31 -31
  13. package/dist/index.d.ts +8 -0
  14. package/dist/index.js +12 -1
  15. package/dist/templates/code-reviewer.js +5 -5
  16. package/dist/templates/customer-service.js +2 -2
  17. package/dist/templates/data-analyst.js +5 -5
  18. package/dist/templates/knowledge-base.js +2 -2
  19. package/dist/templates/sales-assistant.js +4 -4
  20. package/dist/templates/teacher.js +6 -6
  21. package/dist/tools/gateway.d.ts +28 -0
  22. package/dist/tools/gateway.js +177 -0
  23. package/docs/.vitepress/config.ts +103 -103
  24. package/docs/api/cli.md +48 -48
  25. package/docs/api/oad-schema.md +64 -64
  26. package/docs/api/sdk.md +80 -80
  27. package/docs/guide/concepts.md +51 -51
  28. package/docs/guide/configuration.md +79 -79
  29. package/docs/guide/deployment.md +42 -42
  30. package/docs/guide/getting-started.md +44 -44
  31. package/docs/guide/templates.md +28 -28
  32. package/docs/guide/testing.md +84 -84
  33. package/docs/index.md +27 -27
  34. package/docs/zh/api/cli.md +54 -54
  35. package/docs/zh/api/oad-schema.md +87 -87
  36. package/docs/zh/api/sdk.md +102 -102
  37. package/docs/zh/guide/concepts.md +104 -104
  38. package/docs/zh/guide/configuration.md +135 -135
  39. package/docs/zh/guide/deployment.md +81 -81
  40. package/docs/zh/guide/getting-started.md +82 -82
  41. package/docs/zh/guide/templates.md +84 -84
  42. package/docs/zh/guide/testing.md +88 -88
  43. package/docs/zh/index.md +27 -27
  44. package/examples/customer-service-demo/README.md +90 -90
  45. package/examples/customer-service-demo/oad.yaml +107 -107
  46. package/package.json +50 -50
  47. package/src/analytics/index.ts +66 -66
  48. package/src/channels/discord.ts +192 -192
  49. package/src/channels/email.ts +177 -177
  50. package/src/channels/feishu.ts +236 -236
  51. package/src/channels/index.ts +15 -15
  52. package/src/channels/slack.ts +160 -160
  53. package/src/channels/telegram.ts +90 -90
  54. package/src/channels/voice.ts +106 -106
  55. package/src/channels/webhook.ts +199 -199
  56. package/src/channels/websocket.ts +87 -87
  57. package/src/channels/wechat.ts +149 -149
  58. package/src/cli.ts +1 -119
  59. package/src/core/a2a.ts +143 -143
  60. package/src/core/agent.ts +152 -152
  61. package/src/core/analytics-engine.ts +186 -186
  62. package/src/core/auth.ts +57 -57
  63. package/src/core/cache.ts +141 -141
  64. package/src/core/compose.ts +77 -77
  65. package/src/core/config.ts +14 -14
  66. package/src/core/dashboard.ts +219 -0
  67. package/src/core/errors.ts +148 -148
  68. package/src/core/hitl.ts +138 -138
  69. package/src/core/logger.ts +57 -57
  70. package/src/core/orchestrator.ts +215 -215
  71. package/src/core/performance.ts +187 -187
  72. package/src/core/priority.ts +140 -0
  73. package/src/core/rate-limiter.ts +128 -128
  74. package/src/core/room.ts +109 -109
  75. package/src/core/runtime.ts +152 -152
  76. package/src/core/sandbox.ts +101 -101
  77. package/src/core/security.ts +171 -171
  78. package/src/core/types.ts +68 -68
  79. package/src/core/versioning.ts +106 -106
  80. package/src/core/watch.ts +178 -178
  81. package/src/core/workflow.ts +235 -235
  82. package/src/deploy/hermes.ts +156 -156
  83. package/src/deploy/openclaw.ts +200 -200
  84. package/src/dtv/data.ts +29 -0
  85. package/src/dtv/trust.ts +43 -0
  86. package/src/dtv/value.ts +47 -0
  87. package/src/i18n/index.ts +216 -216
  88. package/src/index.ts +6 -4
  89. package/src/marketplace/index.ts +223 -0
  90. package/src/memory/deepbrain.ts +108 -108
  91. package/src/memory/index.ts +34 -34
  92. package/src/plugins/index.ts +208 -208
  93. package/src/schema/oad.ts +155 -154
  94. package/src/skills/base.ts +16 -16
  95. package/src/skills/document.ts +100 -100
  96. package/src/skills/http.ts +35 -35
  97. package/src/skills/index.ts +27 -27
  98. package/src/skills/scheduler.ts +80 -80
  99. package/src/skills/webhook-trigger.ts +59 -59
  100. package/src/templates/code-reviewer.ts +34 -30
  101. package/src/templates/customer-service.ts +80 -76
  102. package/src/templates/data-analyst.ts +70 -66
  103. package/src/templates/executive-assistant.ts +71 -71
  104. package/src/templates/financial-advisor.ts +60 -60
  105. package/src/templates/knowledge-base.ts +31 -27
  106. package/src/templates/legal-assistant.ts +71 -71
  107. package/src/templates/sales-assistant.ts +79 -75
  108. package/src/templates/teacher.ts +79 -75
  109. package/src/testing/index.ts +181 -181
  110. package/src/tools/calculator.ts +73 -73
  111. package/src/tools/datetime.ts +149 -149
  112. package/src/tools/json-transform.ts +187 -187
  113. package/src/tools/mcp.ts +76 -76
  114. package/src/tools/text-analysis.ts +116 -116
  115. package/templates/Dockerfile +15 -15
  116. package/templates/code-reviewer/README.md +27 -27
  117. package/templates/code-reviewer/oad.yaml +41 -41
  118. package/templates/customer-service/README.md +22 -22
  119. package/templates/customer-service/oad.yaml +36 -36
  120. package/templates/docker-compose.yml +21 -21
  121. package/templates/ecommerce-assistant/README.md +45 -45
  122. package/templates/ecommerce-assistant/oad.yaml +47 -47
  123. package/templates/knowledge-base/README.md +28 -28
  124. package/templates/knowledge-base/oad.yaml +38 -38
  125. package/templates/sales-assistant/README.md +26 -26
  126. package/templates/sales-assistant/oad.yaml +43 -43
  127. package/templates/tech-support/README.md +43 -43
  128. package/templates/tech-support/oad.yaml +45 -45
  129. package/tests/a2a.test.ts +66 -66
  130. package/tests/agent.test.ts +72 -72
  131. package/tests/analytics.test.ts +50 -50
  132. package/tests/channel.test.ts +39 -39
  133. package/tests/e2e.test.ts +134 -134
  134. package/tests/errors.test.ts +83 -83
  135. package/tests/hitl.test.ts +71 -71
  136. package/tests/i18n.test.ts +41 -41
  137. package/tests/mcp.test.ts +54 -54
  138. package/tests/oad.test.ts +68 -68
  139. package/tests/performance.test.ts +115 -115
  140. package/tests/plugin.test.ts +74 -74
  141. package/tests/room.test.ts +106 -106
  142. package/tests/runtime.test.ts +42 -42
  143. package/tests/sandbox.test.ts +46 -46
  144. package/tests/security.test.ts +60 -60
  145. package/tests/templates.test.ts +77 -77
  146. package/tests/v070.test.ts +76 -76
  147. package/tests/versioning.test.ts +75 -75
  148. package/tests/voice.test.ts +61 -61
  149. package/tests/webhook.test.ts +29 -29
  150. package/tests/workflow.test.ts +143 -143
  151. package/tsconfig.json +19 -19
  152. package/vitest.config.ts +9 -9
  153. 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
@@ -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');