opc-agent 3.0.0 → 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.
- package/README.md +30 -24
- package/dist/channels/dingtalk.d.ts +17 -0
- package/dist/channels/dingtalk.js +38 -0
- package/dist/channels/googlechat.d.ts +14 -0
- package/dist/channels/googlechat.js +37 -0
- package/dist/channels/imessage.d.ts +13 -0
- package/dist/channels/imessage.js +28 -0
- package/dist/channels/irc.d.ts +20 -0
- package/dist/channels/irc.js +71 -0
- package/dist/channels/line.d.ts +14 -0
- package/dist/channels/line.js +28 -0
- package/dist/channels/matrix.d.ts +15 -0
- package/dist/channels/matrix.js +28 -0
- package/dist/channels/mattermost.d.ts +18 -0
- package/dist/channels/mattermost.js +49 -0
- package/dist/channels/msteams.d.ts +14 -0
- package/dist/channels/msteams.js +28 -0
- package/dist/channels/nostr.d.ts +14 -0
- package/dist/channels/nostr.js +28 -0
- package/dist/channels/qq.d.ts +15 -0
- package/dist/channels/qq.js +28 -0
- package/dist/channels/signal.d.ts +14 -0
- package/dist/channels/signal.js +28 -0
- package/dist/channels/sms.d.ts +15 -0
- package/dist/channels/sms.js +28 -0
- package/dist/channels/twitch.d.ts +17 -0
- package/dist/channels/twitch.js +59 -0
- package/dist/channels/voice-call.d.ts +27 -0
- package/dist/channels/voice-call.js +82 -0
- package/dist/channels/whatsapp.d.ts +14 -0
- package/dist/channels/whatsapp.js +28 -0
- package/dist/cli.js +36 -0
- package/dist/core/api-server.d.ts +25 -0
- package/dist/core/api-server.js +286 -0
- package/dist/core/audio.d.ts +50 -0
- package/dist/core/audio.js +68 -0
- package/dist/core/context-discovery.d.ts +16 -0
- package/dist/core/context-discovery.js +107 -0
- package/dist/core/context-refs.d.ts +29 -0
- package/dist/core/context-refs.js +162 -0
- package/dist/core/gateway.d.ts +53 -0
- package/dist/core/gateway.js +80 -0
- package/dist/core/heartbeat.d.ts +19 -0
- package/dist/core/heartbeat.js +50 -0
- package/dist/core/hooks.d.ts +28 -0
- package/dist/core/hooks.js +82 -0
- package/dist/core/ide-bridge.d.ts +53 -0
- package/dist/core/ide-bridge.js +97 -0
- package/dist/core/node-network.d.ts +23 -0
- package/dist/core/node-network.js +77 -0
- package/dist/core/profiles.d.ts +27 -0
- package/dist/core/profiles.js +131 -0
- package/dist/core/sandbox.d.ts +25 -0
- package/dist/core/sandbox.js +84 -1
- package/dist/core/session-manager.d.ts +33 -0
- package/dist/core/session-manager.js +157 -0
- package/dist/core/vision.d.ts +45 -0
- package/dist/core/vision.js +177 -0
- package/dist/index.d.ts +64 -1
- package/dist/index.js +86 -3
- package/dist/memory/context-compressor.d.ts +43 -0
- package/dist/memory/context-compressor.js +167 -0
- package/dist/memory/index.d.ts +4 -0
- package/dist/memory/index.js +5 -1
- package/dist/memory/user-profiler.d.ts +50 -0
- package/dist/memory/user-profiler.js +201 -0
- package/dist/schema/oad.d.ts +12 -12
- package/dist/security/approvals.d.ts +53 -0
- package/dist/security/approvals.js +115 -0
- package/dist/security/elevated.d.ts +41 -0
- package/dist/security/elevated.js +89 -0
- package/dist/security/index.d.ts +6 -0
- package/dist/security/index.js +7 -1
- package/dist/security/secrets.d.ts +34 -0
- package/dist/security/secrets.js +115 -0
- package/dist/tools/builtin/browser.d.ts +47 -0
- package/dist/tools/builtin/browser.js +284 -0
- package/dist/tools/builtin/home-assistant.d.ts +12 -0
- package/dist/tools/builtin/home-assistant.js +126 -0
- package/dist/tools/builtin/index.d.ts +6 -1
- package/dist/tools/builtin/index.js +18 -2
- package/dist/tools/builtin/rl-tools.d.ts +13 -0
- package/dist/tools/builtin/rl-tools.js +228 -0
- package/dist/tools/builtin/vision.d.ts +6 -0
- package/dist/tools/builtin/vision.js +61 -0
- package/package.json +3 -3
- package/src/channels/dingtalk.ts +46 -0
- package/src/channels/googlechat.ts +42 -0
- package/src/channels/imessage.ts +32 -0
- package/src/channels/irc.ts +82 -0
- package/src/channels/line.ts +33 -0
- package/src/channels/matrix.ts +34 -0
- package/src/channels/mattermost.ts +57 -0
- package/src/channels/msteams.ts +33 -0
- package/src/channels/nostr.ts +33 -0
- package/src/channels/qq.ts +34 -0
- package/src/channels/signal.ts +33 -0
- package/src/channels/sms.ts +34 -0
- package/src/channels/twitch.ts +65 -0
- package/src/channels/voice-call.ts +100 -0
- package/src/channels/whatsapp.ts +33 -0
- package/src/cli.ts +40 -0
- package/src/core/api-server.ts +277 -0
- package/src/core/audio.ts +98 -0
- package/src/core/context-discovery.ts +85 -0
- package/src/core/context-refs.ts +140 -0
- package/src/core/gateway.ts +106 -0
- package/src/core/heartbeat.ts +51 -0
- package/src/core/hooks.ts +105 -0
- package/src/core/ide-bridge.ts +133 -0
- package/src/core/node-network.ts +86 -0
- package/src/core/profiles.ts +122 -0
- package/src/core/sandbox.ts +100 -0
- package/src/core/session-manager.ts +137 -0
- package/src/core/vision.ts +180 -0
- package/src/index.ts +84 -1
- package/src/memory/context-compressor.ts +189 -0
- package/src/memory/index.ts +4 -0
- package/src/memory/user-profiler.ts +215 -0
- package/src/security/approvals.ts +143 -0
- package/src/security/elevated.ts +105 -0
- package/src/security/index.ts +6 -0
- package/src/security/secrets.ts +129 -0
- package/src/tools/builtin/browser.ts +299 -0
- package/src/tools/builtin/home-assistant.ts +116 -0
- package/src/tools/builtin/index.ts +9 -2
- package/src/tools/builtin/rl-tools.ts +243 -0
- package/src/tools/builtin/vision.ts +64 -0
- package/tests/api-server.test.ts +148 -0
- package/tests/approvals.test.ts +89 -0
- package/tests/audio.test.ts +40 -0
- package/tests/browser.test.ts +179 -0
- package/tests/builtin-tools.test.ts +83 -83
- package/tests/channels-extra.test.ts +45 -0
- package/tests/context-compressor.test.ts +172 -0
- package/tests/context-refs.test.ts +121 -0
- package/tests/elevated.test.ts +69 -0
- package/tests/gateway.test.ts +63 -71
- package/tests/home-assistant.test.ts +40 -0
- package/tests/hooks.test.ts +79 -0
- package/tests/ide-bridge.test.ts +38 -0
- package/tests/node-network.test.ts +74 -0
- package/tests/profiles.test.ts +61 -0
- package/tests/rl-tools.test.ts +93 -0
- package/tests/sandbox-manager.test.ts +46 -0
- package/tests/secrets.test.ts +107 -0
- package/tests/tools/builtin-extended.test.ts +138 -138
- package/tests/user-profiler.test.ts +169 -0
- package/tests/v090-features.test.ts +254 -0
- package/tests/vision.test.ts +61 -0
- 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
|