opc-agent 3.0.1 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/README.md +30 -24
  2. package/dist/channels/dingtalk.d.ts +17 -0
  3. package/dist/channels/dingtalk.js +38 -0
  4. package/dist/channels/googlechat.d.ts +14 -0
  5. package/dist/channels/googlechat.js +37 -0
  6. package/dist/channels/imessage.d.ts +13 -0
  7. package/dist/channels/imessage.js +28 -0
  8. package/dist/channels/irc.d.ts +20 -0
  9. package/dist/channels/irc.js +71 -0
  10. package/dist/channels/line.d.ts +14 -0
  11. package/dist/channels/line.js +28 -0
  12. package/dist/channels/matrix.d.ts +15 -0
  13. package/dist/channels/matrix.js +28 -0
  14. package/dist/channels/mattermost.d.ts +18 -0
  15. package/dist/channels/mattermost.js +49 -0
  16. package/dist/channels/msteams.d.ts +14 -0
  17. package/dist/channels/msteams.js +28 -0
  18. package/dist/channels/nostr.d.ts +14 -0
  19. package/dist/channels/nostr.js +28 -0
  20. package/dist/channels/qq.d.ts +15 -0
  21. package/dist/channels/qq.js +28 -0
  22. package/dist/channels/signal.d.ts +14 -0
  23. package/dist/channels/signal.js +28 -0
  24. package/dist/channels/sms.d.ts +15 -0
  25. package/dist/channels/sms.js +28 -0
  26. package/dist/channels/twitch.d.ts +17 -0
  27. package/dist/channels/twitch.js +59 -0
  28. package/dist/channels/voice-call.d.ts +27 -0
  29. package/dist/channels/voice-call.js +82 -0
  30. package/dist/channels/whatsapp.d.ts +14 -0
  31. package/dist/channels/whatsapp.js +28 -0
  32. package/dist/cli.js +36 -0
  33. package/dist/core/api-server.d.ts +25 -0
  34. package/dist/core/api-server.js +286 -0
  35. package/dist/core/audio.d.ts +50 -0
  36. package/dist/core/audio.js +68 -0
  37. package/dist/core/context-discovery.d.ts +16 -0
  38. package/dist/core/context-discovery.js +107 -0
  39. package/dist/core/context-refs.d.ts +29 -0
  40. package/dist/core/context-refs.js +162 -0
  41. package/dist/core/gateway.d.ts +53 -0
  42. package/dist/core/gateway.js +80 -0
  43. package/dist/core/heartbeat.d.ts +19 -0
  44. package/dist/core/heartbeat.js +50 -0
  45. package/dist/core/hooks.d.ts +28 -0
  46. package/dist/core/hooks.js +82 -0
  47. package/dist/core/ide-bridge.d.ts +53 -0
  48. package/dist/core/ide-bridge.js +97 -0
  49. package/dist/core/node-network.d.ts +23 -0
  50. package/dist/core/node-network.js +77 -0
  51. package/dist/core/profiles.d.ts +27 -0
  52. package/dist/core/profiles.js +131 -0
  53. package/dist/core/sandbox.d.ts +25 -0
  54. package/dist/core/sandbox.js +84 -1
  55. package/dist/core/session-manager.d.ts +33 -0
  56. package/dist/core/session-manager.js +157 -0
  57. package/dist/core/vision.d.ts +45 -0
  58. package/dist/core/vision.js +177 -0
  59. package/dist/index.d.ts +64 -1
  60. package/dist/index.js +86 -3
  61. package/dist/memory/context-compressor.d.ts +43 -0
  62. package/dist/memory/context-compressor.js +167 -0
  63. package/dist/memory/index.d.ts +4 -0
  64. package/dist/memory/index.js +5 -1
  65. package/dist/memory/user-profiler.d.ts +50 -0
  66. package/dist/memory/user-profiler.js +201 -0
  67. package/dist/schema/oad.d.ts +12 -12
  68. package/dist/security/approvals.d.ts +53 -0
  69. package/dist/security/approvals.js +115 -0
  70. package/dist/security/elevated.d.ts +41 -0
  71. package/dist/security/elevated.js +89 -0
  72. package/dist/security/index.d.ts +6 -0
  73. package/dist/security/index.js +7 -1
  74. package/dist/security/secrets.d.ts +34 -0
  75. package/dist/security/secrets.js +115 -0
  76. package/dist/tools/builtin/browser.d.ts +47 -0
  77. package/dist/tools/builtin/browser.js +284 -0
  78. package/dist/tools/builtin/home-assistant.d.ts +12 -0
  79. package/dist/tools/builtin/home-assistant.js +126 -0
  80. package/dist/tools/builtin/index.d.ts +6 -1
  81. package/dist/tools/builtin/index.js +18 -2
  82. package/dist/tools/builtin/rl-tools.d.ts +13 -0
  83. package/dist/tools/builtin/rl-tools.js +228 -0
  84. package/dist/tools/builtin/vision.d.ts +6 -0
  85. package/dist/tools/builtin/vision.js +61 -0
  86. package/package.json +2 -2
  87. package/src/channels/dingtalk.ts +46 -0
  88. package/src/channels/googlechat.ts +42 -0
  89. package/src/channels/imessage.ts +32 -0
  90. package/src/channels/irc.ts +82 -0
  91. package/src/channels/line.ts +33 -0
  92. package/src/channels/matrix.ts +34 -0
  93. package/src/channels/mattermost.ts +57 -0
  94. package/src/channels/msteams.ts +33 -0
  95. package/src/channels/nostr.ts +33 -0
  96. package/src/channels/qq.ts +34 -0
  97. package/src/channels/signal.ts +33 -0
  98. package/src/channels/sms.ts +34 -0
  99. package/src/channels/twitch.ts +65 -0
  100. package/src/channels/voice-call.ts +100 -0
  101. package/src/channels/whatsapp.ts +33 -0
  102. package/src/cli.ts +40 -0
  103. package/src/core/api-server.ts +277 -0
  104. package/src/core/audio.ts +98 -0
  105. package/src/core/context-discovery.ts +85 -0
  106. package/src/core/context-refs.ts +140 -0
  107. package/src/core/gateway.ts +106 -0
  108. package/src/core/heartbeat.ts +51 -0
  109. package/src/core/hooks.ts +105 -0
  110. package/src/core/ide-bridge.ts +133 -0
  111. package/src/core/node-network.ts +86 -0
  112. package/src/core/profiles.ts +122 -0
  113. package/src/core/sandbox.ts +100 -0
  114. package/src/core/session-manager.ts +137 -0
  115. package/src/core/vision.ts +180 -0
  116. package/src/index.ts +84 -1
  117. package/src/memory/context-compressor.ts +189 -0
  118. package/src/memory/index.ts +4 -0
  119. package/src/memory/user-profiler.ts +215 -0
  120. package/src/security/approvals.ts +143 -0
  121. package/src/security/elevated.ts +105 -0
  122. package/src/security/index.ts +6 -0
  123. package/src/security/secrets.ts +129 -0
  124. package/src/tools/builtin/browser.ts +299 -0
  125. package/src/tools/builtin/home-assistant.ts +116 -0
  126. package/src/tools/builtin/index.ts +9 -2
  127. package/src/tools/builtin/rl-tools.ts +243 -0
  128. package/src/tools/builtin/vision.ts +64 -0
  129. package/tests/api-server.test.ts +148 -0
  130. package/tests/approvals.test.ts +89 -0
  131. package/tests/audio.test.ts +40 -0
  132. package/tests/browser.test.ts +179 -0
  133. package/tests/builtin-tools.test.ts +83 -83
  134. package/tests/channels-extra.test.ts +45 -0
  135. package/tests/context-compressor.test.ts +172 -0
  136. package/tests/context-refs.test.ts +121 -0
  137. package/tests/elevated.test.ts +69 -0
  138. package/tests/gateway.test.ts +63 -71
  139. package/tests/home-assistant.test.ts +40 -0
  140. package/tests/hooks.test.ts +79 -0
  141. package/tests/ide-bridge.test.ts +38 -0
  142. package/tests/node-network.test.ts +74 -0
  143. package/tests/profiles.test.ts +61 -0
  144. package/tests/rl-tools.test.ts +93 -0
  145. package/tests/sandbox-manager.test.ts +46 -0
  146. package/tests/secrets.test.ts +107 -0
  147. package/tests/tools/builtin-extended.test.ts +138 -138
  148. package/tests/user-profiler.test.ts +169 -0
  149. package/tests/v090-features.test.ts +254 -0
  150. package/tests/vision.test.ts +61 -0
  151. package/tests/voice-call.test.ts +47 -0
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SignalChannel = void 0;
4
+ const index_1 = require("./index");
5
+ class SignalChannel extends index_1.BaseChannel {
6
+ type = 'signal';
7
+ config;
8
+ constructor(config = {}) {
9
+ super();
10
+ this.config = config;
11
+ }
12
+ async start() {
13
+ try {
14
+ require('signal-cli');
15
+ }
16
+ catch {
17
+ throw new Error('Install signal-cli to use the SignalChannel. Run: npm install signal-cli');
18
+ }
19
+ }
20
+ async stop() {
21
+ // cleanup
22
+ }
23
+ async send(chatId, text) {
24
+ throw new Error('SignalChannel: not yet connected. Call start() first.');
25
+ }
26
+ }
27
+ exports.SignalChannel = SignalChannel;
28
+ //# sourceMappingURL=signal.js.map
@@ -0,0 +1,15 @@
1
+ import { BaseChannel } from './index';
2
+ export interface SMSChannelConfig {
3
+ accountSid?: string;
4
+ authToken?: string;
5
+ fromNumber?: string;
6
+ }
7
+ export declare class SMSChannel extends BaseChannel {
8
+ readonly type = "sms";
9
+ private config;
10
+ constructor(config?: SMSChannelConfig);
11
+ start(): Promise<void>;
12
+ stop(): Promise<void>;
13
+ send(chatId: string, text: string): Promise<void>;
14
+ }
15
+ //# sourceMappingURL=sms.d.ts.map
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SMSChannel = void 0;
4
+ const index_1 = require("./index");
5
+ class SMSChannel extends index_1.BaseChannel {
6
+ type = 'sms';
7
+ config;
8
+ constructor(config = {}) {
9
+ super();
10
+ this.config = config;
11
+ }
12
+ async start() {
13
+ try {
14
+ require('twilio');
15
+ }
16
+ catch {
17
+ throw new Error('Install twilio to use the SMSChannel. Run: npm install twilio');
18
+ }
19
+ }
20
+ async stop() {
21
+ // cleanup
22
+ }
23
+ async send(chatId, text) {
24
+ throw new Error('SMSChannel: not yet connected. Call start() first.');
25
+ }
26
+ }
27
+ exports.SMSChannel = SMSChannel;
28
+ //# sourceMappingURL=sms.js.map
@@ -0,0 +1,17 @@
1
+ import { BaseChannel } from './index';
2
+ export interface TwitchChannelConfig {
3
+ username: string;
4
+ oauthToken: string;
5
+ channels: string[];
6
+ }
7
+ export declare class TwitchChannel extends BaseChannel {
8
+ readonly type = "twitch";
9
+ private config;
10
+ private client;
11
+ private running;
12
+ constructor(config: TwitchChannelConfig);
13
+ start(): Promise<void>;
14
+ stop(): Promise<void>;
15
+ send(channel: string, text: string): Promise<void>;
16
+ }
17
+ //# sourceMappingURL=twitch.d.ts.map
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TwitchChannel = void 0;
4
+ const index_1 = require("./index");
5
+ class TwitchChannel extends index_1.BaseChannel {
6
+ type = 'twitch';
7
+ config;
8
+ client = null;
9
+ running = false;
10
+ constructor(config) {
11
+ super();
12
+ if (!config.username || !config.oauthToken || !config.channels?.length) {
13
+ throw new Error('TwitchChannel requires username, oauthToken, and channels in config');
14
+ }
15
+ this.config = config;
16
+ }
17
+ async start() {
18
+ let tmi;
19
+ try {
20
+ tmi = require('tmi.js');
21
+ }
22
+ catch {
23
+ throw new Error('Install tmi.js to use the TwitchChannel. Run: npm install tmi.js');
24
+ }
25
+ this.client = new tmi.Client({
26
+ identity: { username: this.config.username, password: this.config.oauthToken },
27
+ channels: this.config.channels,
28
+ });
29
+ await this.client.connect();
30
+ this.running = true;
31
+ this.client.on('message', async (channel, tags, message, self) => {
32
+ if (self || !this.handler)
33
+ return;
34
+ const msg = {
35
+ id: tags.id || Date.now().toString(),
36
+ role: 'user',
37
+ content: message,
38
+ timestamp: Date.now(),
39
+ metadata: { channel, username: tags.username, tags },
40
+ };
41
+ await this.handler(msg);
42
+ });
43
+ }
44
+ async stop() {
45
+ if (this.client) {
46
+ await this.client.disconnect();
47
+ this.client = null;
48
+ }
49
+ this.running = false;
50
+ }
51
+ async send(channel, text) {
52
+ if (!this.running || !this.client) {
53
+ throw new Error('TwitchChannel: not started. Call start() first.');
54
+ }
55
+ await this.client.say(channel, text);
56
+ }
57
+ }
58
+ exports.TwitchChannel = TwitchChannel;
59
+ //# sourceMappingURL=twitch.js.map
@@ -0,0 +1,27 @@
1
+ import { EventEmitter } from 'events';
2
+ export interface VoiceCallConfig {
3
+ provider: 'twilio' | 'vonage' | 'webrtc' | 'sip';
4
+ credentials?: Record<string, string>;
5
+ sttProvider?: string;
6
+ ttsProvider?: string;
7
+ }
8
+ export declare class VoiceCallManager extends EventEmitter {
9
+ private config;
10
+ private calls;
11
+ private audioListeners;
12
+ constructor(config: VoiceCallConfig);
13
+ private ensureCredentials;
14
+ startCall(to: string): Promise<string>;
15
+ endCall(callId: string): Promise<void>;
16
+ onIncoming(callback: (callId: string, from: string) => void): void;
17
+ simulateIncoming(from: string): string;
18
+ sendAudio(callId: string, audio: Buffer): Promise<void>;
19
+ onAudio(callId: string, callback: (audio: Buffer) => void): void;
20
+ getCallStatus(callId: string): 'ringing' | 'active' | 'ended';
21
+ listActiveCalls(): Array<{
22
+ callId: string;
23
+ from: string;
24
+ startedAt: number;
25
+ }>;
26
+ }
27
+ //# sourceMappingURL=voice-call.d.ts.map
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.VoiceCallManager = void 0;
4
+ const events_1 = require("events");
5
+ const crypto_1 = require("crypto");
6
+ class VoiceCallManager extends events_1.EventEmitter {
7
+ config;
8
+ calls = new Map();
9
+ audioListeners = new Map();
10
+ constructor(config) {
11
+ super();
12
+ this.config = config;
13
+ }
14
+ ensureCredentials() {
15
+ if (!this.config.credentials || Object.keys(this.config.credentials).length === 0) {
16
+ throw new Error(`Voice call provider "${this.config.provider}" requires credentials. ` +
17
+ `Please configure credentials for ${this.config.provider} in your VoiceCallConfig.`);
18
+ }
19
+ }
20
+ async startCall(to) {
21
+ this.ensureCredentials();
22
+ const callId = (0, crypto_1.randomUUID)();
23
+ const call = {
24
+ callId,
25
+ from: 'self',
26
+ to,
27
+ status: 'ringing',
28
+ startedAt: Date.now(),
29
+ };
30
+ this.calls.set(callId, call);
31
+ // Simulate connection
32
+ setTimeout(() => {
33
+ const c = this.calls.get(callId);
34
+ if (c && c.status === 'ringing')
35
+ c.status = 'active';
36
+ }, 100);
37
+ return callId;
38
+ }
39
+ async endCall(callId) {
40
+ const call = this.calls.get(callId);
41
+ if (!call)
42
+ throw new Error(`Call ${callId} not found`);
43
+ call.status = 'ended';
44
+ this.audioListeners.delete(callId);
45
+ }
46
+ onIncoming(callback) {
47
+ this.on('incoming', callback);
48
+ }
49
+ simulateIncoming(from) {
50
+ const callId = (0, crypto_1.randomUUID)();
51
+ const call = { callId, from, to: 'self', status: 'ringing', startedAt: Date.now() };
52
+ this.calls.set(callId, call);
53
+ this.emit('incoming', callId, from);
54
+ return callId;
55
+ }
56
+ async sendAudio(callId, audio) {
57
+ const call = this.calls.get(callId);
58
+ if (!call)
59
+ throw new Error(`Call ${callId} not found`);
60
+ if (call.status !== 'active')
61
+ throw new Error(`Call ${callId} is not active`);
62
+ // Stub: would send audio to provider
63
+ }
64
+ onAudio(callId, callback) {
65
+ const listeners = this.audioListeners.get(callId) || [];
66
+ listeners.push(callback);
67
+ this.audioListeners.set(callId, listeners);
68
+ }
69
+ getCallStatus(callId) {
70
+ const call = this.calls.get(callId);
71
+ if (!call)
72
+ throw new Error(`Call ${callId} not found`);
73
+ return call.status;
74
+ }
75
+ listActiveCalls() {
76
+ return Array.from(this.calls.values())
77
+ .filter(c => c.status !== 'ended')
78
+ .map(({ callId, from, startedAt }) => ({ callId, from, startedAt }));
79
+ }
80
+ }
81
+ exports.VoiceCallManager = VoiceCallManager;
82
+ //# sourceMappingURL=voice-call.js.map
@@ -0,0 +1,14 @@
1
+ import { BaseChannel } from './index';
2
+ export interface WhatsAppChannelConfig {
3
+ phoneNumber?: string;
4
+ authDir?: string;
5
+ }
6
+ export declare class WhatsAppChannel extends BaseChannel {
7
+ readonly type = "whatsapp";
8
+ private config;
9
+ constructor(config?: WhatsAppChannelConfig);
10
+ start(): Promise<void>;
11
+ stop(): Promise<void>;
12
+ send(chatId: string, text: string): Promise<void>;
13
+ }
14
+ //# sourceMappingURL=whatsapp.d.ts.map
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WhatsAppChannel = void 0;
4
+ const index_1 = require("./index");
5
+ class WhatsAppChannel extends index_1.BaseChannel {
6
+ type = 'whatsapp';
7
+ config;
8
+ constructor(config = {}) {
9
+ super();
10
+ this.config = config;
11
+ }
12
+ async start() {
13
+ try {
14
+ require('@whiskeysockets/baileys');
15
+ }
16
+ catch {
17
+ throw new Error('Install @whiskeysockets/baileys to use the WhatsAppChannel. Run: npm install @whiskeysockets/baileys');
18
+ }
19
+ }
20
+ async stop() {
21
+ // cleanup
22
+ }
23
+ async send(chatId, text) {
24
+ throw new Error('WhatsAppChannel: not yet connected. Call start() first.');
25
+ }
26
+ }
27
+ exports.WhatsAppChannel = WhatsAppChannel;
28
+ //# sourceMappingURL=whatsapp.js.map
package/dist/cli.js CHANGED
@@ -800,6 +800,42 @@ program
800
800
  console.log(` ${color.dim('API:')} POST http://localhost:3000/api/chat`);
801
801
  console.log(`\n ${color.dim('Press Ctrl+C to stop.')}\n`);
802
802
  });
803
+ // ── Serve command (OpenAI-compatible API) ────────────────────
804
+ program
805
+ .command('serve')
806
+ .description('Start OpenAI-compatible API server')
807
+ .option('-f, --file <file>', 'OAD file', 'oad.yaml')
808
+ .option('-p, --port <port>', 'Port', '8080')
809
+ .option('-H, --host <host>', 'Host', '0.0.0.0')
810
+ .option('-k, --api-key <key>', 'API key for auth')
811
+ .action(async (opts) => {
812
+ loadDotEnv();
813
+ const { APIServer } = require('./core/api-server');
814
+ const runtime = new runtime_1.AgentRuntime();
815
+ await runtime.loadConfig(opts.file);
816
+ await runtime.initialize();
817
+ await runtime.start();
818
+ const agent = runtime.getAgent();
819
+ const server = new APIServer({
820
+ port: parseInt(opts.port) || 8080,
821
+ host: opts.host,
822
+ apiKey: opts.apiKey ?? process.env.OPC_API_KEY,
823
+ agent,
824
+ });
825
+ await server.start();
826
+ const name = agent?.name ?? 'unknown';
827
+ console.log(`\n${icon.rocket} OpenAI-compatible API server running`);
828
+ console.log(` Agent: ${color.bold(name)}`);
829
+ console.log(` URL: ${color.cyan(`http://${opts.host}:${opts.port}`)}`);
830
+ console.log(` Auth: ${opts.apiKey ? color.green('enabled') : color.yellow('disabled')}`);
831
+ console.log(`\n Endpoints:`);
832
+ console.log(` POST /v1/chat/completions`);
833
+ console.log(` GET /v1/models`);
834
+ console.log(` POST /v1/embeddings`);
835
+ console.log(` GET /health`);
836
+ console.log(` GET /v1/agent/status`);
837
+ console.log(`\n ${color.dim('Press Ctrl+C to stop.')}\n`);
838
+ });
803
839
  // ── Info command ─────────────────────────────────────────────
804
840
  program
805
841
  .command('info')
@@ -0,0 +1,25 @@
1
+ export interface APIServerConfig {
2
+ port: number;
3
+ host: string;
4
+ apiKey?: string;
5
+ agent: any;
6
+ }
7
+ export declare class APIServer {
8
+ private server;
9
+ private config;
10
+ constructor(config: Partial<APIServerConfig> & {
11
+ agent: any;
12
+ });
13
+ private authenticate;
14
+ start(): Promise<void>;
15
+ stop(): Promise<void>;
16
+ getPort(): number;
17
+ getHost(): string;
18
+ private handleRequest;
19
+ private handleModels;
20
+ private handleAgentStatus;
21
+ private handleChatCompletions;
22
+ private getAgentResponse;
23
+ private handleEmbeddings;
24
+ }
25
+ //# sourceMappingURL=api-server.d.ts.map
@@ -0,0 +1,286 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.APIServer = void 0;
37
+ const http = __importStar(require("http"));
38
+ const crypto_1 = require("crypto");
39
+ function jsonError(message, type, code) {
40
+ return { error: { message, type, code } };
41
+ }
42
+ function corsHeaders() {
43
+ return {
44
+ 'Access-Control-Allow-Origin': '*',
45
+ 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
46
+ 'Access-Control-Allow-Headers': 'Content-Type, Authorization',
47
+ };
48
+ }
49
+ function parseBody(req) {
50
+ return new Promise((resolve, reject) => {
51
+ const chunks = [];
52
+ req.on('data', (c) => chunks.push(c));
53
+ req.on('end', () => resolve(Buffer.concat(chunks).toString()));
54
+ req.on('error', reject);
55
+ });
56
+ }
57
+ function sendJSON(res, status, data) {
58
+ const body = JSON.stringify(data);
59
+ res.writeHead(status, { ...corsHeaders(), 'Content-Type': 'application/json' });
60
+ res.end(body);
61
+ }
62
+ class APIServer {
63
+ server = null;
64
+ config;
65
+ constructor(config) {
66
+ this.config = {
67
+ port: config.port ?? 8080,
68
+ host: config.host ?? '0.0.0.0',
69
+ apiKey: config.apiKey,
70
+ agent: config.agent,
71
+ };
72
+ }
73
+ authenticate(req) {
74
+ if (!this.config.apiKey)
75
+ return true;
76
+ const auth = req.headers['authorization'] ?? '';
77
+ return auth === `Bearer ${this.config.apiKey}`;
78
+ }
79
+ async start() {
80
+ return new Promise((resolve) => {
81
+ this.server = http.createServer((req, res) => this.handleRequest(req, res));
82
+ this.server.listen(this.config.port, this.config.host, () => resolve());
83
+ });
84
+ }
85
+ async stop() {
86
+ return new Promise((resolve) => {
87
+ if (this.server)
88
+ this.server.close(() => resolve());
89
+ else
90
+ resolve();
91
+ });
92
+ }
93
+ getPort() { return this.config.port; }
94
+ getHost() { return this.config.host; }
95
+ async handleRequest(req, res) {
96
+ const url = req.url ?? '/';
97
+ const method = req.method ?? 'GET';
98
+ // CORS preflight
99
+ if (method === 'OPTIONS') {
100
+ res.writeHead(204, corsHeaders());
101
+ res.end();
102
+ return;
103
+ }
104
+ // Health check (no auth)
105
+ if (method === 'GET' && url === '/health') {
106
+ sendJSON(res, 200, { status: 'ok', timestamp: new Date().toISOString() });
107
+ return;
108
+ }
109
+ // Auth check for all other endpoints
110
+ if (!this.authenticate(req)) {
111
+ sendJSON(res, 401, jsonError('Invalid API key', 'authentication_error', 401));
112
+ return;
113
+ }
114
+ try {
115
+ if (method === 'GET' && url === '/v1/models') {
116
+ return this.handleModels(res);
117
+ }
118
+ if (method === 'GET' && url === '/v1/agent/status') {
119
+ return this.handleAgentStatus(res);
120
+ }
121
+ if (method === 'POST' && url === '/v1/chat/completions') {
122
+ return await this.handleChatCompletions(req, res);
123
+ }
124
+ if (method === 'POST' && url === '/v1/embeddings') {
125
+ return await this.handleEmbeddings(req, res);
126
+ }
127
+ sendJSON(res, 404, jsonError('Not found', 'not_found', 404));
128
+ }
129
+ catch (err) {
130
+ const msg = err instanceof Error ? err.message : String(err);
131
+ sendJSON(res, 500, jsonError(msg, 'internal_error', 500));
132
+ }
133
+ }
134
+ handleModels(res) {
135
+ const agent = this.config.agent;
136
+ const modelId = agent?.config?.model ?? agent?.model ?? 'default';
137
+ sendJSON(res, 200, {
138
+ object: 'list',
139
+ data: [{ id: modelId, object: 'model', created: Math.floor(Date.now() / 1000), owned_by: 'opc-agent' }],
140
+ });
141
+ }
142
+ handleAgentStatus(res) {
143
+ const agent = this.config.agent;
144
+ sendJSON(res, 200, {
145
+ name: agent?.name ?? agent?.config?.name ?? 'unknown',
146
+ state: agent?.state ?? 'unknown',
147
+ uptime: process.uptime(),
148
+ });
149
+ }
150
+ async handleChatCompletions(req, res) {
151
+ let body;
152
+ try {
153
+ body = JSON.parse(await parseBody(req));
154
+ }
155
+ catch {
156
+ sendJSON(res, 400, jsonError('Invalid JSON body', 'invalid_request_error', 400));
157
+ return;
158
+ }
159
+ const { model, messages, temperature, max_tokens, stream, tools } = body;
160
+ if (!messages || !Array.isArray(messages)) {
161
+ sendJSON(res, 400, jsonError('messages is required and must be an array', 'invalid_request_error', 400));
162
+ return;
163
+ }
164
+ const agent = this.config.agent;
165
+ const requestId = `chatcmpl-${(0, crypto_1.randomUUID)().replace(/-/g, '').slice(0, 24)}`;
166
+ const modelId = model ?? agent?.config?.model ?? agent?.model ?? 'default';
167
+ if (stream) {
168
+ // SSE streaming
169
+ res.writeHead(200, {
170
+ ...corsHeaders(),
171
+ 'Content-Type': 'text/event-stream',
172
+ 'Cache-Control': 'no-cache',
173
+ 'Connection': 'keep-alive',
174
+ });
175
+ try {
176
+ const responseText = await this.getAgentResponse(agent, messages, { temperature, max_tokens, tools });
177
+ // Stream in chunks
178
+ const chunkSize = 10;
179
+ for (let i = 0; i < responseText.length; i += chunkSize) {
180
+ const delta = responseText.slice(i, i + chunkSize);
181
+ const chunk = {
182
+ id: requestId,
183
+ object: 'chat.completion.chunk',
184
+ created: Math.floor(Date.now() / 1000),
185
+ model: modelId,
186
+ choices: [{ index: 0, delta: { content: delta }, finish_reason: null }],
187
+ };
188
+ res.write(`data: ${JSON.stringify(chunk)}\n\n`);
189
+ }
190
+ // Final chunk
191
+ const finalChunk = {
192
+ id: requestId,
193
+ object: 'chat.completion.chunk',
194
+ created: Math.floor(Date.now() / 1000),
195
+ model: modelId,
196
+ choices: [{ index: 0, delta: {}, finish_reason: 'stop' }],
197
+ };
198
+ res.write(`data: ${JSON.stringify(finalChunk)}\n\n`);
199
+ res.write('data: [DONE]\n\n');
200
+ }
201
+ catch (err) {
202
+ const errMsg = err instanceof Error ? err.message : String(err);
203
+ res.write(`data: ${JSON.stringify({ error: { message: errMsg } })}\n\n`);
204
+ }
205
+ res.end();
206
+ }
207
+ else {
208
+ // Non-streaming
209
+ const responseText = await this.getAgentResponse(agent, messages, { temperature, max_tokens, tools });
210
+ sendJSON(res, 200, {
211
+ id: requestId,
212
+ object: 'chat.completion',
213
+ created: Math.floor(Date.now() / 1000),
214
+ model: modelId,
215
+ choices: [{
216
+ index: 0,
217
+ message: { role: 'assistant', content: responseText },
218
+ finish_reason: 'stop',
219
+ }],
220
+ usage: {
221
+ prompt_tokens: messages.reduce((a, m) => a + (m.content?.length ?? 0), 0),
222
+ completion_tokens: responseText.length,
223
+ total_tokens: messages.reduce((a, m) => a + (m.content?.length ?? 0), 0) + responseText.length,
224
+ },
225
+ });
226
+ }
227
+ }
228
+ async getAgentResponse(agent, messages, options) {
229
+ // Try various agent interfaces
230
+ if (agent?.chat) {
231
+ const lastUserMsg = [...messages].reverse().find((m) => m.role === 'user');
232
+ return await agent.chat(lastUserMsg?.content ?? '', options);
233
+ }
234
+ if (agent?.processMessage) {
235
+ const lastUserMsg = [...messages].reverse().find((m) => m.role === 'user');
236
+ const result = await agent.processMessage({ id: 'api', role: 'user', content: lastUserMsg?.content ?? '', timestamp: Date.now() });
237
+ return result?.response ?? result?.content ?? String(result);
238
+ }
239
+ if (agent?.provider?.chat) {
240
+ const formatted = messages.map((m) => ({ id: 'x', role: m.role, content: m.content, timestamp: Date.now() }));
241
+ return await agent.provider.chat(formatted, agent.systemPrompt ?? '');
242
+ }
243
+ return 'Agent does not support chat interface';
244
+ }
245
+ async handleEmbeddings(req, res) {
246
+ let body;
247
+ try {
248
+ body = JSON.parse(await parseBody(req));
249
+ }
250
+ catch {
251
+ sendJSON(res, 400, jsonError('Invalid JSON body', 'invalid_request_error', 400));
252
+ return;
253
+ }
254
+ const { input, model } = body;
255
+ if (!input) {
256
+ sendJSON(res, 400, jsonError('input is required', 'invalid_request_error', 400));
257
+ return;
258
+ }
259
+ const agent = this.config.agent;
260
+ const inputs = Array.isArray(input) ? input : [input];
261
+ try {
262
+ if (agent?.embed || agent?.provider?.embed) {
263
+ const embedFn = agent.embed?.bind(agent) ?? agent.provider.embed.bind(agent.provider);
264
+ const data = await Promise.all(inputs.map(async (text, i) => {
265
+ const embedding = await embedFn(text);
266
+ return { object: 'embedding', embedding, index: i };
267
+ }));
268
+ sendJSON(res, 200, {
269
+ object: 'list',
270
+ data,
271
+ model: model ?? 'default',
272
+ usage: { prompt_tokens: inputs.join('').length, total_tokens: inputs.join('').length },
273
+ });
274
+ }
275
+ else {
276
+ sendJSON(res, 501, jsonError('Embedding provider not configured', 'not_implemented', 501));
277
+ }
278
+ }
279
+ catch (err) {
280
+ const msg = err instanceof Error ? err.message : String(err);
281
+ sendJSON(res, 500, jsonError(msg, 'internal_error', 500));
282
+ }
283
+ }
284
+ }
285
+ exports.APIServer = APIServer;
286
+ //# sourceMappingURL=api-server.js.map