@nextclaw/extension-sdk 0.1.11 → 0.1.12-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -1
- package/dist/services/channel-typing-controller.service.d.ts +19 -0
- package/dist/services/channel-typing-controller.service.d.ts.map +1 -0
- package/dist/services/channel-typing-controller.service.js +40 -0
- package/dist/services/channel-typing-controller.service.js.map +1 -0
- package/dist/services/extension-channel-controller.service.d.ts +10 -1
- package/dist/services/extension-channel-controller.service.d.ts.map +1 -1
- package/dist/services/extension-channel-controller.service.js +10 -6
- package/dist/services/extension-channel-controller.service.js.map +1 -1
- package/dist/services/extension-channel.service.js +29 -0
- package/dist/services/extension-channel.service.js.map +1 -1
- package/dist/types/extension-sdk.types.d.ts +19 -2
- package/dist/types/extension-sdk.types.d.ts.map +1 -1
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { ChannelConfigGetRequest, ChannelConfigGetResponse, ChannelFileContent, ChannelImageContent, ChannelMessageContent, ChannelSubmittedAttachment, ChannelSubmittedMessage, ChannelTextContent, ExtensionCapabilities, ExtensionCapabilityHandler, ExtensionCapabilityPayload, ExtensionChannel, ExtensionChannelConfig, ExtensionChannels, ExtensionRequest, ExtensionRequestHandler, ExtensionRequestResponse, ExtensionTransportEnvelope, NextClawExtensionOptions, NextClawExtensionWebSocketLike } from "./types/extension-sdk.types.js";
|
|
1
|
+
import { ChannelCommandExecuteRequest, ChannelCommandExecuteResponse, ChannelCommandListRequest, ChannelCommandListResponse, ChannelCommandOption, ChannelCommandOptionType, ChannelCommandSpec, ChannelConfigGetRequest, ChannelConfigGetResponse, ChannelFileContent, ChannelImageContent, ChannelMessageContent, ChannelSubmittedAttachment, ChannelSubmittedMessage, ChannelTextContent, ExtensionCapabilities, ExtensionCapabilityHandler, ExtensionCapabilityPayload, ExtensionChannel, ExtensionChannelCommands, ExtensionChannelConfig, ExtensionChannels, ExtensionRequest, ExtensionRequestHandler, ExtensionRequestResponse, ExtensionTransportEnvelope, NextClawExtensionOptions, NextClawExtensionWebSocketLike } from "./types/extension-sdk.types.js";
|
|
2
2
|
import { NextClawExtension } from "./services/extension-client.service.js";
|
|
3
|
+
import { ChannelTypingController, ChannelTypingControllerOptions } from "./services/channel-typing-controller.service.js";
|
|
3
4
|
import { BusChannelCreateContext, BusChannelExtensionDefinition, BusChannelInboundMessage, BusChannelMessageBus, BusChannelRuntime, ChannelExtensionContext, ChannelExtensionDefinition, ChannelSubmittedMessageInput, ExtensionChannelAdapter, ExtensionChannelController, startBusChannelExtension, startChannelExtension, warnNcpEventError } from "./services/extension-channel-controller.service.js";
|
|
4
|
-
export { type BusChannelCreateContext, type BusChannelExtensionDefinition, type BusChannelInboundMessage, type BusChannelMessageBus, type BusChannelRuntime, type ChannelConfigGetRequest, type ChannelConfigGetResponse, type ChannelExtensionContext, type ChannelExtensionDefinition, type ChannelFileContent, type ChannelImageContent, type ChannelMessageContent, type ChannelSubmittedAttachment, type ChannelSubmittedMessage, type ChannelSubmittedMessageInput, type ChannelTextContent, type ExtensionCapabilities, type ExtensionCapabilityHandler, type ExtensionCapabilityPayload, type ExtensionChannel, type ExtensionChannelAdapter, type ExtensionChannelConfig, ExtensionChannelController, type ExtensionChannels, type ExtensionRequest, type ExtensionRequestHandler, type ExtensionRequestResponse, type ExtensionTransportEnvelope, NextClawExtension, type NextClawExtensionOptions, type NextClawExtensionWebSocketLike, startBusChannelExtension, startChannelExtension, warnNcpEventError };
|
|
5
|
+
export { type BusChannelCreateContext, type BusChannelExtensionDefinition, type BusChannelInboundMessage, type BusChannelMessageBus, type BusChannelRuntime, type ChannelCommandExecuteRequest, type ChannelCommandExecuteResponse, type ChannelCommandListRequest, type ChannelCommandListResponse, type ChannelCommandOption, type ChannelCommandOptionType, type ChannelCommandSpec, type ChannelConfigGetRequest, type ChannelConfigGetResponse, type ChannelExtensionContext, type ChannelExtensionDefinition, type ChannelFileContent, type ChannelImageContent, type ChannelMessageContent, type ChannelSubmittedAttachment, type ChannelSubmittedMessage, type ChannelSubmittedMessageInput, type ChannelTextContent, ChannelTypingController, type ChannelTypingControllerOptions, type ExtensionCapabilities, type ExtensionCapabilityHandler, type ExtensionCapabilityPayload, type ExtensionChannel, type ExtensionChannelAdapter, type ExtensionChannelCommands, type ExtensionChannelConfig, ExtensionChannelController, type ExtensionChannels, type ExtensionRequest, type ExtensionRequestHandler, type ExtensionRequestResponse, type ExtensionTransportEnvelope, NextClawExtension, type NextClawExtensionOptions, type NextClawExtensionWebSocketLike, startBusChannelExtension, startChannelExtension, warnNcpEventError };
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import { NextClawExtension } from "./services/extension-client.service.js";
|
|
2
|
+
import { ChannelTypingController } from "./services/channel-typing-controller.service.js";
|
|
2
3
|
import { ExtensionChannelController, startBusChannelExtension, startChannelExtension, warnNcpEventError } from "./services/extension-channel-controller.service.js";
|
|
3
|
-
export { ExtensionChannelController, NextClawExtension, startBusChannelExtension, startChannelExtension, warnNcpEventError };
|
|
4
|
+
export { ChannelTypingController, ExtensionChannelController, NextClawExtension, startBusChannelExtension, startChannelExtension, warnNcpEventError };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
//#region src/services/channel-typing-controller.service.d.ts
|
|
2
|
+
type ChannelTypingControllerOptions = {
|
|
3
|
+
heartbeatMs: number;
|
|
4
|
+
autoStopMs: number;
|
|
5
|
+
sendTyping: (targetId: string) => Promise<void> | void;
|
|
6
|
+
};
|
|
7
|
+
declare class ChannelTypingController {
|
|
8
|
+
private readonly heartbeatMs;
|
|
9
|
+
private readonly autoStopMs;
|
|
10
|
+
private readonly sendTyping;
|
|
11
|
+
private readonly tasks;
|
|
12
|
+
constructor(options: ChannelTypingControllerOptions);
|
|
13
|
+
start: (targetId: string) => void;
|
|
14
|
+
stop: (targetId: string) => void;
|
|
15
|
+
stopAll: () => void;
|
|
16
|
+
}
|
|
17
|
+
//#endregion
|
|
18
|
+
export { ChannelTypingController, ChannelTypingControllerOptions };
|
|
19
|
+
//# sourceMappingURL=channel-typing-controller.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channel-typing-controller.service.d.ts","names":[],"sources":["../../src/services/channel-typing-controller.service.ts"],"mappings":";KAAY,8BAAA;EACV,WAAA;EACA,UAAA;EACA,UAAA,GAAa,QAAA,aAAqB,OAAA;AAAA;AAAA,cAQvB,uBAAA;EAAA,iBACM,WAAA;EAAA,iBACA,UAAA;EAAA,iBACA,UAAA;EAAA,iBACA,KAAA;cAEL,OAAA,EAAS,8BAAA;EAMrB,KAAA,GAAS,QAAA;EAkBT,IAAA,GAAQ,QAAA;EAUR,OAAA;AAAA"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
//#region src/services/channel-typing-controller.service.ts
|
|
2
|
+
var ChannelTypingController = class {
|
|
3
|
+
heartbeatMs;
|
|
4
|
+
autoStopMs;
|
|
5
|
+
sendTyping;
|
|
6
|
+
tasks = /* @__PURE__ */ new Map();
|
|
7
|
+
constructor(options) {
|
|
8
|
+
this.heartbeatMs = Math.max(1e3, Math.floor(options.heartbeatMs));
|
|
9
|
+
this.autoStopMs = Math.max(this.heartbeatMs, Math.floor(options.autoStopMs));
|
|
10
|
+
this.sendTyping = options.sendTyping;
|
|
11
|
+
}
|
|
12
|
+
start = (targetId) => {
|
|
13
|
+
this.stop(targetId);
|
|
14
|
+
this.sendTyping(targetId);
|
|
15
|
+
const heartbeat = setInterval(() => {
|
|
16
|
+
this.sendTyping(targetId);
|
|
17
|
+
}, this.heartbeatMs);
|
|
18
|
+
const autoStop = setTimeout(() => {
|
|
19
|
+
this.stop(targetId);
|
|
20
|
+
}, this.autoStopMs);
|
|
21
|
+
this.tasks.set(targetId, {
|
|
22
|
+
heartbeat,
|
|
23
|
+
autoStop
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
stop = (targetId) => {
|
|
27
|
+
const task = this.tasks.get(targetId);
|
|
28
|
+
if (!task) return;
|
|
29
|
+
clearInterval(task.heartbeat);
|
|
30
|
+
clearTimeout(task.autoStop);
|
|
31
|
+
this.tasks.delete(targetId);
|
|
32
|
+
};
|
|
33
|
+
stopAll = () => {
|
|
34
|
+
for (const targetId of this.tasks.keys()) this.stop(targetId);
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
//#endregion
|
|
38
|
+
export { ChannelTypingController };
|
|
39
|
+
|
|
40
|
+
//# sourceMappingURL=channel-typing-controller.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"channel-typing-controller.service.js","names":[],"sources":["../../src/services/channel-typing-controller.service.ts"],"sourcesContent":["export type ChannelTypingControllerOptions = {\n heartbeatMs: number;\n autoStopMs: number;\n sendTyping: (targetId: string) => Promise<void> | void;\n};\n\ntype TypingTask = {\n heartbeat: NodeJS.Timeout;\n autoStop: NodeJS.Timeout;\n};\n\nexport class ChannelTypingController {\n private readonly heartbeatMs: number;\n private readonly autoStopMs: number;\n private readonly sendTyping: (targetId: string) => Promise<void> | void;\n private readonly tasks = new Map<string, TypingTask>();\n\n constructor(options: ChannelTypingControllerOptions) {\n this.heartbeatMs = Math.max(1000, Math.floor(options.heartbeatMs));\n this.autoStopMs = Math.max(this.heartbeatMs, Math.floor(options.autoStopMs));\n this.sendTyping = options.sendTyping;\n }\n\n start = (targetId: string): void => {\n this.stop(targetId);\n void this.sendTyping(targetId);\n\n const heartbeat = setInterval(() => {\n void this.sendTyping(targetId);\n }, this.heartbeatMs);\n\n const autoStop = setTimeout(() => {\n this.stop(targetId);\n }, this.autoStopMs);\n\n this.tasks.set(targetId, {\n heartbeat,\n autoStop,\n });\n };\n\n stop = (targetId: string): void => {\n const task = this.tasks.get(targetId);\n if (!task) {\n return;\n }\n clearInterval(task.heartbeat);\n clearTimeout(task.autoStop);\n this.tasks.delete(targetId);\n };\n\n stopAll = (): void => {\n for (const targetId of this.tasks.keys()) {\n this.stop(targetId);\n }\n };\n}\n"],"mappings":";AAWA,IAAa,0BAAb,MAAqC;CACnC;CACA;CACA;CACA,wBAAyB,IAAI,KAAyB;CAEtD,YAAY,SAAyC;AACnD,OAAK,cAAc,KAAK,IAAI,KAAM,KAAK,MAAM,QAAQ,YAAY,CAAC;AAClE,OAAK,aAAa,KAAK,IAAI,KAAK,aAAa,KAAK,MAAM,QAAQ,WAAW,CAAC;AAC5E,OAAK,aAAa,QAAQ;;CAG5B,SAAS,aAA2B;AAClC,OAAK,KAAK,SAAS;AACd,OAAK,WAAW,SAAS;EAE9B,MAAM,YAAY,kBAAkB;AAC7B,QAAK,WAAW,SAAS;KAC7B,KAAK,YAAY;EAEpB,MAAM,WAAW,iBAAiB;AAChC,QAAK,KAAK,SAAS;KAClB,KAAK,WAAW;AAEnB,OAAK,MAAM,IAAI,UAAU;GACvB;GACA;GACD,CAAC;;CAGJ,QAAQ,aAA2B;EACjC,MAAM,OAAO,KAAK,MAAM,IAAI,SAAS;AACrC,MAAI,CAAC,KACH;AAEF,gBAAc,KAAK,UAAU;AAC7B,eAAa,KAAK,SAAS;AAC3B,OAAK,MAAM,OAAO,SAAS;;CAG7B,gBAAsB;AACpB,OAAK,MAAM,YAAY,KAAK,MAAM,MAAM,CACtC,MAAK,KAAK,SAAS"}
|
|
@@ -32,6 +32,14 @@ type BusChannelMessageBus = {
|
|
|
32
32
|
type BusChannelRuntime = {
|
|
33
33
|
start: () => Promise<void>;
|
|
34
34
|
stop: () => Promise<void>;
|
|
35
|
+
handleControlMessage?: (message: {
|
|
36
|
+
channel: string;
|
|
37
|
+
chatId: string;
|
|
38
|
+
content: string;
|
|
39
|
+
replyTo?: string | null;
|
|
40
|
+
media: string[];
|
|
41
|
+
metadata: Record<string, unknown>;
|
|
42
|
+
}) => Promise<boolean>;
|
|
35
43
|
send: (message: {
|
|
36
44
|
channel: string;
|
|
37
45
|
chatId: string;
|
|
@@ -44,6 +52,7 @@ type BusChannelRuntime = {
|
|
|
44
52
|
type BusChannelCreateContext<TConfig, TBus = BusChannelMessageBus> = {
|
|
45
53
|
config: TConfig;
|
|
46
54
|
bus: TBus;
|
|
55
|
+
channel: ExtensionChannel;
|
|
47
56
|
};
|
|
48
57
|
type BusChannelExtensionDefinition<TConfig, TBus = BusChannelMessageBus> = {
|
|
49
58
|
channelId: string;
|
|
@@ -63,7 +72,7 @@ type ChannelExtensionContext = {
|
|
|
63
72
|
};
|
|
64
73
|
type ChannelExtensionDefinition<TConfig, TInbound> = {
|
|
65
74
|
channelId: string;
|
|
66
|
-
createAdapter: () => ExtensionChannelAdapter<TConfig, TInbound>;
|
|
75
|
+
createAdapter: (context: ChannelExtensionContext) => ExtensionChannelAdapter<TConfig, TInbound>;
|
|
67
76
|
mapInbound: ExtensionChannelInboundMapper<TInbound>;
|
|
68
77
|
createAuthCapability?: (context: ChannelExtensionContext) => object;
|
|
69
78
|
onNcpEventError?: (error: unknown, event: NcpEndpointEvent) => void | Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extension-channel-controller.service.d.ts","names":[],"sources":["../../src/services/extension-channel-controller.service.ts"],"mappings":";;;;KAgDY,uBAAA;EACV,SAAA,GAAY,MAAA,EAAQ,OAAA,KAAY,OAAA;EAChC,KAAA,QAAa,OAAA;EACb,IAAA,QAAY,OAAA;EACZ,SAAA,GAAY,OAAA,GAAU,OAAA,EAAS,QAAA,YAAoB,OAAA;EACnD,YAAA,GAAe,KAAA,EAAO,gBAAA,KAAqB,OAAA;EAC3C,gBAAA,GAAmB,MAAA;IACjB,EAAA;IACA,IAAA;IACA,SAAA;IACA,OAAA;IACA,KAAA;IACA,QAAA,GAAW,MAAA;EAAA,MACP,OAAA;AAAA;AAAA,KAGI,4BAAA,GAA+B,IAAA,CAAK,uBAAA;AAAA,KAEpC,wBAAA;EACV,OAAA;EACA,QAAA;EACA,MAAA;EACA,OAAA;EACA,WAAA,GAAc,0BAAA;EACd,QAAA,GAAW,MAAA;AAAA;AAAA,KAGD,oBAAA;EACV,cAAA,GAAiB,OAAA,EAAS,wBAAA,KAA6B,OAAA;AAAA;AAAA,KAG7C,iBAAA;EACV,KAAA,QAAa,OAAA;EACb,IAAA,QAAY,OAAA;EACZ,IAAA,GAAO,OAAA;IACL,OAAA;IACA,MAAA;IACA,OAAA;IACA,OAAA;IACA,KAAA;IACA,QAAA,EAAU,MAAA;EAAA,MACN,OAAA;AAAA;AAAA,KAGI,uBAAA,iBAAwC,oBAAA;EAClD,MAAA,EAAQ,OAAA;EACR,GAAA,EAAK,IAAA;AAAA;AAAA,
|
|
1
|
+
{"version":3,"file":"extension-channel-controller.service.d.ts","names":[],"sources":["../../src/services/extension-channel-controller.service.ts"],"mappings":"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"}
|
|
@@ -83,8 +83,9 @@ var ExtensionChannelController = class {
|
|
|
83
83
|
var BusChannelAdapter = class {
|
|
84
84
|
channel = null;
|
|
85
85
|
messageHandler = null;
|
|
86
|
-
constructor(definition) {
|
|
86
|
+
constructor(definition, extensionChannel) {
|
|
87
87
|
this.definition = definition;
|
|
88
|
+
this.extensionChannel = extensionChannel;
|
|
88
89
|
}
|
|
89
90
|
configure = async (config) => {
|
|
90
91
|
await this.stop();
|
|
@@ -92,7 +93,8 @@ var BusChannelAdapter = class {
|
|
|
92
93
|
config,
|
|
93
94
|
bus: { publishInbound: async (message) => {
|
|
94
95
|
await this.messageHandler?.(message);
|
|
95
|
-
} }
|
|
96
|
+
} },
|
|
97
|
+
channel: this.extensionChannel
|
|
96
98
|
});
|
|
97
99
|
};
|
|
98
100
|
start = async () => {
|
|
@@ -136,7 +138,7 @@ var BusChannelAdapter = class {
|
|
|
136
138
|
};
|
|
137
139
|
sendOutboundText = async (params) => {
|
|
138
140
|
const { accountId, media, metadata, replyTo, text, to } = params;
|
|
139
|
-
|
|
141
|
+
const outbound = {
|
|
140
142
|
channel: this.definition.channelId,
|
|
141
143
|
chatId: to,
|
|
142
144
|
content: text,
|
|
@@ -146,7 +148,9 @@ var BusChannelAdapter = class {
|
|
|
146
148
|
...metadata ?? {},
|
|
147
149
|
...accountId ? { accountId } : {}
|
|
148
150
|
}
|
|
149
|
-
}
|
|
151
|
+
};
|
|
152
|
+
if (await this.channel?.handleControlMessage?.(outbound)) return;
|
|
153
|
+
await this.channel?.send(outbound);
|
|
150
154
|
};
|
|
151
155
|
};
|
|
152
156
|
async function startChannelExtension(definition, options = {}) {
|
|
@@ -154,7 +158,7 @@ async function startChannelExtension(definition, options = {}) {
|
|
|
154
158
|
const channel = extension.channels.use(definition.channelId);
|
|
155
159
|
const controller = new ExtensionChannelController({
|
|
156
160
|
channel,
|
|
157
|
-
adapter: definition.createAdapter(),
|
|
161
|
+
adapter: definition.createAdapter({ channel }),
|
|
158
162
|
mapInbound: definition.mapInbound,
|
|
159
163
|
onNcpEventError: definition.onNcpEventError
|
|
160
164
|
});
|
|
@@ -172,7 +176,7 @@ async function startChannelExtension(definition, options = {}) {
|
|
|
172
176
|
async function startBusChannelExtension(definition, options = {}) {
|
|
173
177
|
await startChannelExtension({
|
|
174
178
|
channelId: definition.channelId,
|
|
175
|
-
createAdapter: () => new BusChannelAdapter(definition),
|
|
179
|
+
createAdapter: ({ channel }) => new BusChannelAdapter(definition, channel),
|
|
176
180
|
mapInbound: definition.mapInbound ?? toSubmittedTextMessage
|
|
177
181
|
}, options);
|
|
178
182
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extension-channel-controller.service.js","names":[],"sources":["../../src/services/extension-channel-controller.service.ts"],"sourcesContent":["import { NcpEventType, type NcpEndpointEvent, type NcpMessagePart } from \"@nextclaw/ncp\";\nimport type {\n ChannelSubmittedMessage,\n ChannelSubmittedAttachment,\n ExtensionChannel,\n NextClawExtensionOptions,\n} from \"../types/extension-sdk.types.js\";\nimport { NextClawExtension } from \"./extension-client.service.js\";\n\nfunction readRecord(value: unknown): Record<string, unknown> {\n return value && typeof value === \"object\" && !Array.isArray(value)\n ? value as Record<string, unknown>\n : {};\n}\n\nconst AGENT_ROUTE_KINDS = new Set([\"direct\", \"group\", \"channel\", \"thread\"]);\n\nfunction parseSessionDeliveryRoute(sessionId: string): {\n channel?: string;\n chatId?: string;\n accountId?: string;\n} {\n const parts = sessionId.trim().split(\":\");\n if (parts[0] !== \"agent\" || parts.length < 5) {\n return {};\n }\n const hasAccount = !AGENT_ROUTE_KINDS.has(parts[3]);\n const kind = parts[hasAccount ? 4 : 3];\n if (!AGENT_ROUTE_KINDS.has(kind ?? \"\")) {\n return {};\n }\n return {\n channel: parts[2],\n ...(hasAccount ? { accountId: parts[3] } : {}),\n chatId: parts.slice(hasAccount ? 5 : 4).join(\":\"),\n };\n}\n\nfunction readString(value: unknown): string | undefined {\n return typeof value === \"string\" && value.trim() ? value.trim() : undefined;\n}\n\nfunction renderMessageText(parts: readonly NcpMessagePart[]): string {\n return (parts as Array<{ type: string; text?: string }>).map((part) =>\n (part.type === \"text\" || part.type === \"rich-text\") && part.text ? part.text : \"\",\n ).filter(Boolean).join(\"\\n\\n\");\n}\n\nexport type ExtensionChannelAdapter<TConfig, TInbound> = {\n configure: (config: TConfig) => Promise<void>;\n start: () => Promise<void>;\n stop: () => Promise<void>;\n onMessage: (handler: (message: TInbound) => void | Promise<void>) => () => void;\n sendNcpEvent: (event: NcpEndpointEvent) => Promise<void>;\n sendOutboundText: (params: {\n to: string;\n text: string;\n accountId?: string | null;\n replyTo?: string | null;\n media?: string[];\n metadata?: Record<string, unknown>;\n }) => Promise<void>;\n};\n\nexport type ChannelSubmittedMessageInput = Omit<ChannelSubmittedMessage, \"channelId\">;\n\nexport type BusChannelInboundMessage = {\n channel: string;\n senderId: string;\n chatId: string;\n content: string;\n attachments?: ChannelSubmittedAttachment[];\n metadata?: Record<string, unknown>;\n};\n\nexport type BusChannelMessageBus = {\n publishInbound: (message: BusChannelInboundMessage) => Promise<void>;\n};\n\nexport type BusChannelRuntime = {\n start: () => Promise<void>;\n stop: () => Promise<void>;\n send: (message: {\n channel: string;\n chatId: string;\n content: string;\n replyTo?: string | null;\n media: string[];\n metadata: Record<string, unknown>;\n }) => Promise<void>;\n};\n\nexport type BusChannelCreateContext<TConfig, TBus = BusChannelMessageBus> = {\n config: TConfig;\n bus: TBus;\n};\n\nexport type BusChannelExtensionDefinition<TConfig, TBus = BusChannelMessageBus> = {\n channelId: string;\n createChannel: (context: BusChannelCreateContext<TConfig, TBus>) => BusChannelRuntime;\n mapInbound?: ExtensionChannelInboundMapper<BusChannelInboundMessage>;\n onChannelStartError?: (error: unknown) => void | Promise<void>;\n};\n\nexport type ExtensionChannelInboundMapper<TInbound> =\n (message: TInbound) => ChannelSubmittedMessageInput | Promise<ChannelSubmittedMessageInput>;\n\ntype ExtensionChannelControllerOptions<TConfig, TInbound> = {\n channel: ExtensionChannel;\n adapter: ExtensionChannelAdapter<TConfig, TInbound>;\n mapInbound: ExtensionChannelInboundMapper<TInbound>;\n onNcpEventError?: (error: unknown, event: NcpEndpointEvent) => void | Promise<void>;\n};\n\nexport type ChannelExtensionContext = {\n channel: ExtensionChannel;\n};\n\nexport type ChannelExtensionDefinition<TConfig, TInbound> = {\n channelId: string;\n createAdapter: () => ExtensionChannelAdapter<TConfig, TInbound>;\n mapInbound: ExtensionChannelInboundMapper<TInbound>;\n createAuthCapability?: (context: ChannelExtensionContext) => object;\n onNcpEventError?: (error: unknown, event: NcpEndpointEvent) => void | Promise<void>;\n};\n\nfunction defaultChannelEnabled(config: unknown): boolean {\n return !(\n config &&\n typeof config === \"object\" &&\n !Array.isArray(config) &&\n (config as { enabled?: unknown }).enabled === false\n );\n}\n\nexport class ExtensionChannelController<TConfig, TInbound> {\n private readonly cleanups: Array<() => void> = [];\n private started = false;\n\n constructor(\n private readonly options: ExtensionChannelControllerOptions<TConfig, TInbound>,\n ) {}\n\n start = async (): Promise<void> => {\n if (this.started) {\n return;\n }\n this.started = true;\n this.cleanups.push(this.options.adapter.onMessage(this.submitMessage));\n this.cleanups.push(this.options.channel.onNcpEvent(this.sendNcpEvent));\n this.cleanups.push(this.options.channel.config.onChange(async () => {\n await this.applyConfig();\n }));\n await this.applyConfig();\n };\n\n stop = async (): Promise<void> => {\n if (!this.started) {\n return;\n }\n this.started = false;\n this.drainCleanups();\n await this.options.adapter.stop();\n };\n\n sendOutboundText = async (params: {\n to: string;\n text: string;\n accountId?: string | null;\n replyTo?: string | null;\n media?: string[];\n metadata?: Record<string, unknown>;\n }): Promise<{ accepted: true }> => {\n await this.options.adapter.sendOutboundText(params);\n return { accepted: true };\n };\n\n private applyConfig = async (): Promise<void> => {\n const config = await this.options.channel.config.get<TConfig>();\n await this.options.adapter.configure(config);\n if (defaultChannelEnabled(config) === false) {\n await this.options.adapter.stop();\n return;\n }\n await this.options.adapter.start();\n };\n\n private submitMessage = async (message: TInbound): Promise<void> => {\n await this.options.channel.submitMessage(await this.options.mapInbound(message));\n };\n\n private sendNcpEvent = async (event: NcpEndpointEvent): Promise<void> => {\n try {\n await this.options.adapter.sendNcpEvent(event);\n } catch (error) {\n await this.options.onNcpEventError?.(error, event);\n }\n };\n\n private drainCleanups = (): void => {\n for (const cleanup of this.cleanups.splice(0).reverse()) {\n cleanup();\n }\n };\n}\n\nclass BusChannelAdapter<TConfig, TBus> implements ExtensionChannelAdapter<TConfig, BusChannelInboundMessage> {\n private channel: BusChannelRuntime | null = null;\n private messageHandler: ((message: BusChannelInboundMessage) => void | Promise<void>) | null = null;\n\n constructor(\n private readonly definition: BusChannelExtensionDefinition<TConfig, TBus>,\n ) {}\n\n configure = async (config: TConfig): Promise<void> => {\n await this.stop();\n this.channel = this.definition.createChannel({\n config,\n bus: {\n publishInbound: async (message: BusChannelInboundMessage) => {\n await this.messageHandler?.(message);\n },\n } as TBus,\n });\n };\n\n start = async (): Promise<void> => {\n const channel = this.channel;\n if (!channel) {\n return;\n }\n void channel.start().catch(async (error: unknown) => {\n await this.definition.onChannelStartError?.(error);\n });\n };\n\n stop = async (): Promise<void> => {\n const channel = this.channel;\n this.channel = null;\n await channel?.stop();\n };\n\n onMessage = (\n handler: (message: BusChannelInboundMessage) => void | Promise<void>,\n ): (() => void) => {\n this.messageHandler = handler;\n return () => {\n if (this.messageHandler === handler) {\n this.messageHandler = null;\n }\n };\n };\n\n sendNcpEvent = async (event: NcpEndpointEvent): Promise<void> => {\n if (event.type !== NcpEventType.MessageCompleted) {\n return;\n }\n const metadata = readRecord(event.payload.message.metadata);\n const sessionRoute = parseSessionDeliveryRoute(event.payload.message.sessionId);\n const eventChannel = readString(metadata.channel) ?? readString(metadata.channelId) ?? sessionRoute.channel;\n if (eventChannel !== this.definition.channelId) {\n return;\n }\n const chatId = readString(metadata.chatId) ?? readString(metadata.chat_id) ?? sessionRoute.chatId;\n const content = renderMessageText(event.payload.message.parts).trim();\n if (!chatId || !content) {\n return;\n }\n const replyTo = readString(metadata.message_id);\n await this.channel?.send({\n channel: this.definition.channelId,\n chatId,\n content,\n ...(replyTo ? { replyTo } : {}),\n media: [],\n metadata: {\n ...metadata,\n ...(sessionRoute.accountId && !metadata.accountId ? { accountId: sessionRoute.accountId } : {}),\n },\n });\n };\n\n sendOutboundText = async (params: {\n to: string;\n text: string;\n accountId?: string | null;\n replyTo?: string | null;\n media?: string[];\n metadata?: Record<string, unknown>;\n }): Promise<void> => {\n const { accountId, media, metadata, replyTo, text, to } = params;\n await this.channel?.send({\n channel: this.definition.channelId,\n chatId: to,\n content: text,\n ...(replyTo !== undefined ? { replyTo } : {}),\n media: media ?? [],\n metadata: {\n ...(metadata ?? {}),\n ...(accountId ? { accountId } : {}),\n },\n });\n };\n}\n\nexport async function startChannelExtension<TConfig, TInbound>(\n definition: ChannelExtensionDefinition<TConfig, TInbound>,\n options: NextClawExtensionOptions = {},\n): Promise<void> {\n const extension = new NextClawExtension(options);\n const channel = extension.channels.use(definition.channelId);\n const adapter = definition.createAdapter();\n const controller = new ExtensionChannelController({\n channel,\n adapter,\n mapInbound: definition.mapInbound,\n onNcpEventError: definition.onNcpEventError,\n });\n if (definition.createAuthCapability) {\n extension.capabilities.provide(\"channel.auth\", definition.createAuthCapability({ channel }));\n }\n extension.capabilities.provideHandler(\"channel.outbound.sendText\", async (payload) =>\n await controller.sendOutboundText({\n to: readRequiredPayloadString(payload.to, \"to\"),\n text: readRequiredPayloadString(payload.text, \"text\"),\n accountId: readOptionalPayloadString(payload.accountId),\n replyTo: readOptionalPayloadString(payload.replyTo),\n media: readStringArray(payload.media),\n metadata: readOptionalRecord(payload.metadata),\n }),\n );\n await controller.start();\n}\n\nexport async function startBusChannelExtension<TConfig, TBus = BusChannelMessageBus>(\n definition: BusChannelExtensionDefinition<TConfig, TBus>,\n options: NextClawExtensionOptions = {},\n): Promise<void> {\n await startChannelExtension(\n {\n channelId: definition.channelId,\n createAdapter: () => new BusChannelAdapter(definition),\n mapInbound: definition.mapInbound ?? toSubmittedTextMessage,\n },\n options,\n );\n}\n\nexport function warnNcpEventError(channelId: string): (error: unknown) => void {\n return (error) => {\n const message = error instanceof Error ? error.message : String(error);\n console.warn(`[${channelId}] failed to send NCP event: ${message}`);\n };\n}\n\nfunction readRequiredPayloadString(value: unknown, name: string): string {\n if (typeof value !== \"string\" || !value.trim()) {\n throw new Error(`${name} is required`);\n }\n return value.trim();\n}\n\nfunction readOptionalPayloadString(value: unknown): string | undefined {\n return typeof value === \"string\" && value.trim() ? value.trim() : undefined;\n}\n\nfunction readStringArray(value: unknown): string[] | undefined {\n if (!Array.isArray(value)) {\n return undefined;\n }\n return value.filter((item): item is string => typeof item === \"string\");\n}\n\nfunction readOptionalRecord(value: unknown): Record<string, unknown> | undefined {\n return value && typeof value === \"object\" && !Array.isArray(value)\n ? value as Record<string, unknown>\n : undefined;\n}\n\nfunction toSubmittedTextMessage(message: BusChannelInboundMessage): ChannelSubmittedMessageInput {\n return {\n conversationId: message.chatId,\n senderId: message.senderId,\n content: {\n type: \"text\",\n text: message.content,\n },\n ...(message.attachments ? { attachments: message.attachments } : {}),\n ...(message.metadata ? { metadata: message.metadata } : {}),\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"extension-channel-controller.service.js","names":[],"sources":["../../src/services/extension-channel-controller.service.ts"],"sourcesContent":["import { NcpEventType, type NcpEndpointEvent, type NcpMessagePart } from \"@nextclaw/ncp\";\nimport type {\n ChannelSubmittedMessage,\n ChannelSubmittedAttachment,\n ExtensionChannel,\n NextClawExtensionOptions,\n} from \"../types/extension-sdk.types.js\";\nimport { NextClawExtension } from \"./extension-client.service.js\";\n\nfunction readRecord(value: unknown): Record<string, unknown> {\n return value && typeof value === \"object\" && !Array.isArray(value)\n ? value as Record<string, unknown>\n : {};\n}\n\nconst AGENT_ROUTE_KINDS = new Set([\"direct\", \"group\", \"channel\", \"thread\"]);\n\nfunction parseSessionDeliveryRoute(sessionId: string): {\n channel?: string;\n chatId?: string;\n accountId?: string;\n} {\n const parts = sessionId.trim().split(\":\");\n if (parts[0] !== \"agent\" || parts.length < 5) {\n return {};\n }\n const hasAccount = !AGENT_ROUTE_KINDS.has(parts[3]);\n const kind = parts[hasAccount ? 4 : 3];\n if (!AGENT_ROUTE_KINDS.has(kind ?? \"\")) {\n return {};\n }\n return {\n channel: parts[2],\n ...(hasAccount ? { accountId: parts[3] } : {}),\n chatId: parts.slice(hasAccount ? 5 : 4).join(\":\"),\n };\n}\n\nfunction readString(value: unknown): string | undefined {\n return typeof value === \"string\" && value.trim() ? value.trim() : undefined;\n}\n\nfunction renderMessageText(parts: readonly NcpMessagePart[]): string {\n return (parts as Array<{ type: string; text?: string }>).map((part) =>\n (part.type === \"text\" || part.type === \"rich-text\") && part.text ? part.text : \"\",\n ).filter(Boolean).join(\"\\n\\n\");\n}\n\nexport type ExtensionChannelAdapter<TConfig, TInbound> = {\n configure: (config: TConfig) => Promise<void>;\n start: () => Promise<void>;\n stop: () => Promise<void>;\n onMessage: (handler: (message: TInbound) => void | Promise<void>) => () => void;\n sendNcpEvent: (event: NcpEndpointEvent) => Promise<void>;\n sendOutboundText: (params: {\n to: string;\n text: string;\n accountId?: string | null;\n replyTo?: string | null;\n media?: string[];\n metadata?: Record<string, unknown>;\n }) => Promise<void>;\n};\n\nexport type ChannelSubmittedMessageInput = Omit<ChannelSubmittedMessage, \"channelId\">;\n\nexport type BusChannelInboundMessage = {\n channel: string;\n senderId: string;\n chatId: string;\n content: string;\n attachments?: ChannelSubmittedAttachment[];\n metadata?: Record<string, unknown>;\n};\n\nexport type BusChannelMessageBus = {\n publishInbound: (message: BusChannelInboundMessage) => Promise<void>;\n};\n\nexport type BusChannelRuntime = {\n start: () => Promise<void>;\n stop: () => Promise<void>;\n handleControlMessage?: (message: {\n channel: string;\n chatId: string;\n content: string;\n replyTo?: string | null;\n media: string[];\n metadata: Record<string, unknown>;\n }) => Promise<boolean>;\n send: (message: {\n channel: string;\n chatId: string;\n content: string;\n replyTo?: string | null;\n media: string[];\n metadata: Record<string, unknown>;\n }) => Promise<void>;\n};\n\nexport type BusChannelCreateContext<TConfig, TBus = BusChannelMessageBus> = {\n config: TConfig;\n bus: TBus;\n channel: ExtensionChannel;\n};\n\nexport type BusChannelExtensionDefinition<TConfig, TBus = BusChannelMessageBus> = {\n channelId: string;\n createChannel: (context: BusChannelCreateContext<TConfig, TBus>) => BusChannelRuntime;\n mapInbound?: ExtensionChannelInboundMapper<BusChannelInboundMessage>;\n onChannelStartError?: (error: unknown) => void | Promise<void>;\n};\n\nexport type ExtensionChannelInboundMapper<TInbound> =\n (message: TInbound) => ChannelSubmittedMessageInput | Promise<ChannelSubmittedMessageInput>;\n\ntype ExtensionChannelControllerOptions<TConfig, TInbound> = {\n channel: ExtensionChannel;\n adapter: ExtensionChannelAdapter<TConfig, TInbound>;\n mapInbound: ExtensionChannelInboundMapper<TInbound>;\n onNcpEventError?: (error: unknown, event: NcpEndpointEvent) => void | Promise<void>;\n};\n\nexport type ChannelExtensionContext = {\n channel: ExtensionChannel;\n};\n\nexport type ChannelExtensionDefinition<TConfig, TInbound> = {\n channelId: string;\n createAdapter: (context: ChannelExtensionContext) => ExtensionChannelAdapter<TConfig, TInbound>;\n mapInbound: ExtensionChannelInboundMapper<TInbound>;\n createAuthCapability?: (context: ChannelExtensionContext) => object;\n onNcpEventError?: (error: unknown, event: NcpEndpointEvent) => void | Promise<void>;\n};\n\nfunction defaultChannelEnabled(config: unknown): boolean {\n return !(\n config &&\n typeof config === \"object\" &&\n !Array.isArray(config) &&\n (config as { enabled?: unknown }).enabled === false\n );\n}\n\nexport class ExtensionChannelController<TConfig, TInbound> {\n private readonly cleanups: Array<() => void> = [];\n private started = false;\n\n constructor(\n private readonly options: ExtensionChannelControllerOptions<TConfig, TInbound>,\n ) {}\n\n start = async (): Promise<void> => {\n if (this.started) {\n return;\n }\n this.started = true;\n this.cleanups.push(this.options.adapter.onMessage(this.submitMessage));\n this.cleanups.push(this.options.channel.onNcpEvent(this.sendNcpEvent));\n this.cleanups.push(this.options.channel.config.onChange(async () => {\n await this.applyConfig();\n }));\n await this.applyConfig();\n };\n\n stop = async (): Promise<void> => {\n if (!this.started) {\n return;\n }\n this.started = false;\n this.drainCleanups();\n await this.options.adapter.stop();\n };\n\n sendOutboundText = async (params: {\n to: string;\n text: string;\n accountId?: string | null;\n replyTo?: string | null;\n media?: string[];\n metadata?: Record<string, unknown>;\n }): Promise<{ accepted: true }> => {\n await this.options.adapter.sendOutboundText(params);\n return { accepted: true };\n };\n\n private applyConfig = async (): Promise<void> => {\n const config = await this.options.channel.config.get<TConfig>();\n await this.options.adapter.configure(config);\n if (defaultChannelEnabled(config) === false) {\n await this.options.adapter.stop();\n return;\n }\n await this.options.adapter.start();\n };\n\n private submitMessage = async (message: TInbound): Promise<void> => {\n await this.options.channel.submitMessage(await this.options.mapInbound(message));\n };\n\n private sendNcpEvent = async (event: NcpEndpointEvent): Promise<void> => {\n try {\n await this.options.adapter.sendNcpEvent(event);\n } catch (error) {\n await this.options.onNcpEventError?.(error, event);\n }\n };\n\n private drainCleanups = (): void => {\n for (const cleanup of this.cleanups.splice(0).reverse()) {\n cleanup();\n }\n };\n}\n\nclass BusChannelAdapter<TConfig, TBus> implements ExtensionChannelAdapter<TConfig, BusChannelInboundMessage> {\n private channel: BusChannelRuntime | null = null;\n private messageHandler: ((message: BusChannelInboundMessage) => void | Promise<void>) | null = null;\n\n constructor(\n private readonly definition: BusChannelExtensionDefinition<TConfig, TBus>,\n private readonly extensionChannel: ExtensionChannel,\n ) {}\n\n configure = async (config: TConfig): Promise<void> => {\n await this.stop();\n this.channel = this.definition.createChannel({\n config,\n bus: {\n publishInbound: async (message: BusChannelInboundMessage) => {\n await this.messageHandler?.(message);\n },\n } as TBus,\n channel: this.extensionChannel,\n });\n };\n\n start = async (): Promise<void> => {\n const channel = this.channel;\n if (!channel) {\n return;\n }\n void channel.start().catch(async (error: unknown) => {\n await this.definition.onChannelStartError?.(error);\n });\n };\n\n stop = async (): Promise<void> => {\n const channel = this.channel;\n this.channel = null;\n await channel?.stop();\n };\n\n onMessage = (\n handler: (message: BusChannelInboundMessage) => void | Promise<void>,\n ): (() => void) => {\n this.messageHandler = handler;\n return () => {\n if (this.messageHandler === handler) {\n this.messageHandler = null;\n }\n };\n };\n\n sendNcpEvent = async (event: NcpEndpointEvent): Promise<void> => {\n if (event.type !== NcpEventType.MessageCompleted) {\n return;\n }\n const metadata = readRecord(event.payload.message.metadata);\n const sessionRoute = parseSessionDeliveryRoute(event.payload.message.sessionId);\n const eventChannel = readString(metadata.channel) ?? readString(metadata.channelId) ?? sessionRoute.channel;\n if (eventChannel !== this.definition.channelId) {\n return;\n }\n const chatId = readString(metadata.chatId) ?? readString(metadata.chat_id) ?? sessionRoute.chatId;\n const content = renderMessageText(event.payload.message.parts).trim();\n if (!chatId || !content) {\n return;\n }\n const replyTo = readString(metadata.message_id);\n await this.channel?.send({\n channel: this.definition.channelId,\n chatId,\n content,\n ...(replyTo ? { replyTo } : {}),\n media: [],\n metadata: {\n ...metadata,\n ...(sessionRoute.accountId && !metadata.accountId ? { accountId: sessionRoute.accountId } : {}),\n },\n });\n };\n\n sendOutboundText = async (params: {\n to: string;\n text: string;\n accountId?: string | null;\n replyTo?: string | null;\n media?: string[];\n metadata?: Record<string, unknown>;\n }): Promise<void> => {\n const { accountId, media, metadata, replyTo, text, to } = params;\n const outbound = {\n channel: this.definition.channelId,\n chatId: to,\n content: text,\n ...(replyTo !== undefined ? { replyTo } : {}),\n media: media ?? [],\n metadata: {\n ...(metadata ?? {}),\n ...(accountId ? { accountId } : {}),\n },\n };\n if (await this.channel?.handleControlMessage?.(outbound)) {\n return;\n }\n await this.channel?.send(outbound);\n };\n}\n\nexport async function startChannelExtension<TConfig, TInbound>(\n definition: ChannelExtensionDefinition<TConfig, TInbound>,\n options: NextClawExtensionOptions = {},\n): Promise<void> {\n const extension = new NextClawExtension(options);\n const channel = extension.channels.use(definition.channelId);\n const adapter = definition.createAdapter({ channel });\n const controller = new ExtensionChannelController({\n channel,\n adapter,\n mapInbound: definition.mapInbound,\n onNcpEventError: definition.onNcpEventError,\n });\n if (definition.createAuthCapability) {\n extension.capabilities.provide(\"channel.auth\", definition.createAuthCapability({ channel }));\n }\n extension.capabilities.provideHandler(\"channel.outbound.sendText\", async (payload) =>\n await controller.sendOutboundText({\n to: readRequiredPayloadString(payload.to, \"to\"),\n text: readRequiredPayloadString(payload.text, \"text\"),\n accountId: readOptionalPayloadString(payload.accountId),\n replyTo: readOptionalPayloadString(payload.replyTo),\n media: readStringArray(payload.media),\n metadata: readOptionalRecord(payload.metadata),\n }),\n );\n await controller.start();\n}\n\nexport async function startBusChannelExtension<TConfig, TBus = BusChannelMessageBus>(\n definition: BusChannelExtensionDefinition<TConfig, TBus>,\n options: NextClawExtensionOptions = {},\n): Promise<void> {\n await startChannelExtension(\n {\n channelId: definition.channelId,\n createAdapter: ({ channel }) => new BusChannelAdapter(definition, channel),\n mapInbound: definition.mapInbound ?? toSubmittedTextMessage,\n },\n options,\n );\n}\n\nexport function warnNcpEventError(channelId: string): (error: unknown) => void {\n return (error) => {\n const message = error instanceof Error ? error.message : String(error);\n console.warn(`[${channelId}] failed to send NCP event: ${message}`);\n };\n}\n\nfunction readRequiredPayloadString(value: unknown, name: string): string {\n if (typeof value !== \"string\" || !value.trim()) {\n throw new Error(`${name} is required`);\n }\n return value.trim();\n}\n\nfunction readOptionalPayloadString(value: unknown): string | undefined {\n return typeof value === \"string\" && value.trim() ? value.trim() : undefined;\n}\n\nfunction readStringArray(value: unknown): string[] | undefined {\n if (!Array.isArray(value)) {\n return undefined;\n }\n return value.filter((item): item is string => typeof item === \"string\");\n}\n\nfunction readOptionalRecord(value: unknown): Record<string, unknown> | undefined {\n return value && typeof value === \"object\" && !Array.isArray(value)\n ? value as Record<string, unknown>\n : undefined;\n}\n\nfunction toSubmittedTextMessage(message: BusChannelInboundMessage): ChannelSubmittedMessageInput {\n return {\n conversationId: message.chatId,\n senderId: message.senderId,\n content: {\n type: \"text\",\n text: message.content,\n },\n ...(message.attachments ? { attachments: message.attachments } : {}),\n ...(message.metadata ? { metadata: message.metadata } : {}),\n };\n}\n"],"mappings":"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"}
|
|
@@ -14,13 +14,42 @@ var ChannelConfig = class {
|
|
|
14
14
|
this.get().then((config) => handler(config));
|
|
15
15
|
});
|
|
16
16
|
};
|
|
17
|
+
var ChannelCommands = class {
|
|
18
|
+
constructor(params) {
|
|
19
|
+
this.params = params;
|
|
20
|
+
}
|
|
21
|
+
list = async () => {
|
|
22
|
+
return (await this.params.transport.postIngress(getKeyId(ingressKeys.extension.channelCommandList), { channelId: this.params.channelId })).commands;
|
|
23
|
+
};
|
|
24
|
+
execute = async (input) => await this.params.transport.postIngress(getKeyId(ingressKeys.extension.channelCommandExecute), {
|
|
25
|
+
channelId: this.params.channelId,
|
|
26
|
+
commandName: input.commandName,
|
|
27
|
+
args: input.args ?? {},
|
|
28
|
+
conversationId: input.conversationId,
|
|
29
|
+
senderId: input.senderId,
|
|
30
|
+
metadata: input.metadata ?? {}
|
|
31
|
+
});
|
|
32
|
+
executeText = async (input) => {
|
|
33
|
+
const rawText = input.rawText.trim();
|
|
34
|
+
if (!rawText.startsWith("/")) return null;
|
|
35
|
+
return await this.params.transport.postIngress(getKeyId(ingressKeys.extension.channelCommandExecute), {
|
|
36
|
+
channelId: this.params.channelId,
|
|
37
|
+
rawText,
|
|
38
|
+
conversationId: input.conversationId,
|
|
39
|
+
senderId: input.senderId,
|
|
40
|
+
metadata: input.metadata ?? {}
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
};
|
|
17
44
|
var ExtensionChannelService = class {
|
|
18
45
|
id;
|
|
19
46
|
config;
|
|
47
|
+
commands;
|
|
20
48
|
constructor(params) {
|
|
21
49
|
this.params = params;
|
|
22
50
|
this.id = params.channelId;
|
|
23
51
|
this.config = new ChannelConfig(params);
|
|
52
|
+
this.commands = new ChannelCommands(params);
|
|
24
53
|
}
|
|
25
54
|
submitMessage = async (input) => {
|
|
26
55
|
await this.params.transport.postIngress(getKeyId(ingressKeys.extension.channelMessageSubmit), {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extension-channel.service.js","names":[],"sources":["../../src/services/extension-channel.service.ts"],"sourcesContent":["import {\n eventKeys,\n getKeyId,\n ingressKeys,\n type EventBus,\n type Unsubscribe,\n} from \"@nextclaw/shared\";\nimport type {\n ChannelConfigGetResponse,\n ChannelSubmittedMessage,\n ExtensionChannel,\n ExtensionChannelConfig,\n} from \"../types/extension-sdk.types.js\";\nimport type { ExtensionTransportService } from \"./extension-transport.service.js\";\n\nclass ChannelConfig implements ExtensionChannelConfig {\n constructor(\n private readonly params: {\n channelId: string;\n eventBus: EventBus;\n transport: ExtensionTransportService;\n },\n ) {}\n\n readonly get = async <TConfig = unknown>(): Promise<TConfig> => {\n const response = await this.params.transport.postIngress<ChannelConfigGetResponse<TConfig>>(\n getKeyId(ingressKeys.extension.channelConfigGet),\n { channelId: this.params.channelId },\n );\n return response.config;\n };\n\n readonly onChange = <TConfig = unknown>(\n handler: (config: TConfig) => void | Promise<void>,\n ): Unsubscribe =>\n this.params.eventBus.subscribeAll((event) => {\n if (event.type !== getKeyId(eventKeys.configUpdated)) {\n return;\n }\n const payload = event.payload as { path?: unknown };\n if (payload.path !== \"channels\" && payload.path !== `channels.${this.params.channelId}`) {\n return;\n }\n void this.get<TConfig>().then((config) => handler(config));\n });\n}\n\nexport class ExtensionChannelService implements ExtensionChannel {\n readonly id: string;\n readonly config: ExtensionChannelConfig;\n\n constructor(\n private readonly params: {\n channelId: string;\n eventBus: EventBus;\n transport: ExtensionTransportService;\n },\n ) {\n this.id = params.channelId;\n this.config = new ChannelConfig(params);\n }\n\n readonly submitMessage = async (\n input: Omit<ChannelSubmittedMessage, \"channelId\">,\n ): Promise<void> => {\n await this.params.transport.postIngress(getKeyId(ingressKeys.extension.channelMessageSubmit), {\n ...input,\n channelId: this.id,\n });\n };\n\n readonly onNcpEvent = (\n handler: Parameters<ExtensionChannel[\"onNcpEvent\"]>[0],\n ): Unsubscribe =>\n this.params.eventBus.on(eventKeys.ncpEvent, (event) => {\n void handler(event);\n });\n}\n"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"extension-channel.service.js","names":[],"sources":["../../src/services/extension-channel.service.ts"],"sourcesContent":["import {\n eventKeys,\n getKeyId,\n ingressKeys,\n type EventBus,\n type Unsubscribe,\n} from \"@nextclaw/shared\";\nimport type {\n ChannelCommandExecuteResponse,\n ChannelCommandListResponse,\n ChannelConfigGetResponse,\n ChannelSubmittedMessage,\n ExtensionChannel,\n ExtensionChannelCommands,\n ExtensionChannelConfig,\n} from \"../types/extension-sdk.types.js\";\nimport type { ExtensionTransportService } from \"./extension-transport.service.js\";\n\nclass ChannelConfig implements ExtensionChannelConfig {\n constructor(\n private readonly params: {\n channelId: string;\n eventBus: EventBus;\n transport: ExtensionTransportService;\n },\n ) {}\n\n readonly get = async <TConfig = unknown>(): Promise<TConfig> => {\n const response = await this.params.transport.postIngress<ChannelConfigGetResponse<TConfig>>(\n getKeyId(ingressKeys.extension.channelConfigGet),\n { channelId: this.params.channelId },\n );\n return response.config;\n };\n\n readonly onChange = <TConfig = unknown>(\n handler: (config: TConfig) => void | Promise<void>,\n ): Unsubscribe =>\n this.params.eventBus.subscribeAll((event) => {\n if (event.type !== getKeyId(eventKeys.configUpdated)) {\n return;\n }\n const payload = event.payload as { path?: unknown };\n if (payload.path !== \"channels\" && payload.path !== `channels.${this.params.channelId}`) {\n return;\n }\n void this.get<TConfig>().then((config) => handler(config));\n });\n}\n\nclass ChannelCommands implements ExtensionChannelCommands {\n constructor(\n private readonly params: {\n channelId: string;\n transport: ExtensionTransportService;\n },\n ) {}\n\n readonly list = async () => {\n const response = await this.params.transport.postIngress<ChannelCommandListResponse>(\n getKeyId(ingressKeys.extension.channelCommandList),\n { channelId: this.params.channelId },\n );\n return response.commands;\n };\n\n readonly execute = async (input: {\n commandName: string;\n args?: Record<string, unknown>;\n conversationId: string;\n senderId: string;\n metadata?: Record<string, unknown>;\n }): Promise<ChannelCommandExecuteResponse> =>\n await this.params.transport.postIngress<ChannelCommandExecuteResponse>(\n getKeyId(ingressKeys.extension.channelCommandExecute),\n {\n channelId: this.params.channelId,\n commandName: input.commandName,\n args: input.args ?? {},\n conversationId: input.conversationId,\n senderId: input.senderId,\n metadata: input.metadata ?? {},\n },\n );\n\n readonly executeText = async (input: {\n rawText: string;\n conversationId: string;\n senderId: string;\n metadata?: Record<string, unknown>;\n }): Promise<ChannelCommandExecuteResponse | null> => {\n const rawText = input.rawText.trim();\n if (!rawText.startsWith(\"/\")) {\n return null;\n }\n return await this.params.transport.postIngress<ChannelCommandExecuteResponse>(\n getKeyId(ingressKeys.extension.channelCommandExecute),\n {\n channelId: this.params.channelId,\n rawText,\n conversationId: input.conversationId,\n senderId: input.senderId,\n metadata: input.metadata ?? {},\n },\n );\n };\n}\n\nexport class ExtensionChannelService implements ExtensionChannel {\n readonly id: string;\n readonly config: ExtensionChannelConfig;\n readonly commands: ExtensionChannelCommands;\n\n constructor(\n private readonly params: {\n channelId: string;\n eventBus: EventBus;\n transport: ExtensionTransportService;\n },\n ) {\n this.id = params.channelId;\n this.config = new ChannelConfig(params);\n this.commands = new ChannelCommands(params);\n }\n\n readonly submitMessage = async (\n input: Omit<ChannelSubmittedMessage, \"channelId\">,\n ): Promise<void> => {\n await this.params.transport.postIngress(getKeyId(ingressKeys.extension.channelMessageSubmit), {\n ...input,\n channelId: this.id,\n });\n };\n\n readonly onNcpEvent = (\n handler: Parameters<ExtensionChannel[\"onNcpEvent\"]>[0],\n ): Unsubscribe =>\n this.params.eventBus.on(eventKeys.ncpEvent, (event) => {\n void handler(event);\n });\n}\n"],"mappings":";;AAkBA,IAAM,gBAAN,MAAsD;CACpD,YACE,QAKA;AALiB,OAAA,SAAA;;CAOnB,MAAe,YAAiD;AAK9D,UAJiB,MAAM,KAAK,OAAO,UAAU,YAC3C,SAAS,YAAY,UAAU,iBAAiB,EAChD,EAAE,WAAW,KAAK,OAAO,WAAW,CACrC,EACe;;CAGlB,YACE,YAEA,KAAK,OAAO,SAAS,cAAc,UAAU;AAC3C,MAAI,MAAM,SAAS,SAAS,UAAU,cAAc,CAClD;EAEF,MAAM,UAAU,MAAM;AACtB,MAAI,QAAQ,SAAS,cAAc,QAAQ,SAAS,YAAY,KAAK,OAAO,YAC1E;AAEG,OAAK,KAAc,CAAC,MAAM,WAAW,QAAQ,OAAO,CAAC;GAC1D;;AAGN,IAAM,kBAAN,MAA0D;CACxD,YACE,QAIA;AAJiB,OAAA,SAAA;;CAMnB,OAAgB,YAAY;AAK1B,UAJiB,MAAM,KAAK,OAAO,UAAU,YAC3C,SAAS,YAAY,UAAU,mBAAmB,EAClD,EAAE,WAAW,KAAK,OAAO,WAAW,CACrC,EACe;;CAGlB,UAAmB,OAAO,UAOxB,MAAM,KAAK,OAAO,UAAU,YAC1B,SAAS,YAAY,UAAU,sBAAsB,EACrD;EACE,WAAW,KAAK,OAAO;EACvB,aAAa,MAAM;EACnB,MAAM,MAAM,QAAQ,EAAE;EACtB,gBAAgB,MAAM;EACtB,UAAU,MAAM;EAChB,UAAU,MAAM,YAAY,EAAE;EAC/B,CACF;CAEH,cAAuB,OAAO,UAKuB;EACnD,MAAM,UAAU,MAAM,QAAQ,MAAM;AACpC,MAAI,CAAC,QAAQ,WAAW,IAAI,CAC1B,QAAO;AAET,SAAO,MAAM,KAAK,OAAO,UAAU,YACjC,SAAS,YAAY,UAAU,sBAAsB,EACrD;GACE,WAAW,KAAK,OAAO;GACvB;GACA,gBAAgB,MAAM;GACtB,UAAU,MAAM;GAChB,UAAU,MAAM,YAAY,EAAE;GAC/B,CACF;;;AAIL,IAAa,0BAAb,MAAiE;CAC/D;CACA;CACA;CAEA,YACE,QAKA;AALiB,OAAA,SAAA;AAMjB,OAAK,KAAK,OAAO;AACjB,OAAK,SAAS,IAAI,cAAc,OAAO;AACvC,OAAK,WAAW,IAAI,gBAAgB,OAAO;;CAG7C,gBAAyB,OACvB,UACkB;AAClB,QAAM,KAAK,OAAO,UAAU,YAAY,SAAS,YAAY,UAAU,qBAAqB,EAAE;GAC5F,GAAG;GACH,WAAW,KAAK;GACjB,CAAC;;CAGJ,cACE,YAEA,KAAK,OAAO,SAAS,GAAG,UAAU,WAAW,UAAU;AAChD,UAAQ,MAAM;GACnB"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ExtensionChannelConfigGetIngressPayload as ChannelConfigGetRequest, ExtensionChannelFileContent as ChannelFileContent, ExtensionChannelImageContent as ChannelImageContent, ExtensionChannelMessageContent as ChannelMessageContent, ExtensionChannelMessageSubmitIngressPayload, ExtensionChannelMessageSubmitIngressPayload as ChannelSubmittedMessage, ExtensionChannelSubmittedAttachment as ChannelSubmittedAttachment, ExtensionChannelTextContent as ChannelTextContent, ExtensionResponseIngressPayload as ExtensionRequestResponse, Unsubscribe } from "@nextclaw/shared";
|
|
1
|
+
import { ExtensionChannelCommandExecuteIngressPayload as ChannelCommandExecuteRequest, ExtensionChannelCommandExecuteResponse, ExtensionChannelCommandExecuteResponse as ChannelCommandExecuteResponse, ExtensionChannelCommandListIngressPayload as ChannelCommandListRequest, ExtensionChannelCommandListResponse as ChannelCommandListResponse, ExtensionChannelCommandOption as ChannelCommandOption, ExtensionChannelCommandOptionType as ChannelCommandOptionType, ExtensionChannelCommandSpec, ExtensionChannelCommandSpec as ChannelCommandSpec, ExtensionChannelConfigGetIngressPayload as ChannelConfigGetRequest, ExtensionChannelFileContent as ChannelFileContent, ExtensionChannelImageContent as ChannelImageContent, ExtensionChannelMessageContent as ChannelMessageContent, ExtensionChannelMessageSubmitIngressPayload, ExtensionChannelMessageSubmitIngressPayload as ChannelSubmittedMessage, ExtensionChannelSubmittedAttachment as ChannelSubmittedAttachment, ExtensionChannelTextContent as ChannelTextContent, ExtensionResponseIngressPayload as ExtensionRequestResponse, Unsubscribe } from "@nextclaw/shared";
|
|
2
2
|
import { NcpEndpointEvent } from "@nextclaw/ncp";
|
|
3
3
|
|
|
4
4
|
//#region src/types/extension-sdk.types.d.ts
|
|
@@ -38,11 +38,28 @@ type ExtensionChannelConfig = {
|
|
|
38
38
|
get: <TConfig = unknown>() => Promise<TConfig>;
|
|
39
39
|
onChange: <TConfig = unknown>(handler: (config: TConfig) => void | Promise<void>) => Unsubscribe;
|
|
40
40
|
};
|
|
41
|
+
type ExtensionChannelCommands = {
|
|
42
|
+
list: () => Promise<ExtensionChannelCommandSpec[]>;
|
|
43
|
+
execute: (input: {
|
|
44
|
+
commandName: string;
|
|
45
|
+
args?: Record<string, unknown>;
|
|
46
|
+
conversationId: string;
|
|
47
|
+
senderId: string;
|
|
48
|
+
metadata?: Record<string, unknown>;
|
|
49
|
+
}) => Promise<ExtensionChannelCommandExecuteResponse>;
|
|
50
|
+
executeText: (input: {
|
|
51
|
+
rawText: string;
|
|
52
|
+
conversationId: string;
|
|
53
|
+
senderId: string;
|
|
54
|
+
metadata?: Record<string, unknown>;
|
|
55
|
+
}) => Promise<ExtensionChannelCommandExecuteResponse | null>;
|
|
56
|
+
};
|
|
41
57
|
type ExtensionChannel = {
|
|
42
58
|
id: string;
|
|
43
59
|
submitMessage: (input: Omit<ExtensionChannelMessageSubmitIngressPayload, "channelId">) => Promise<void>;
|
|
44
60
|
onNcpEvent: (handler: (event: NcpEndpointEvent) => void | Promise<void>) => Unsubscribe;
|
|
45
61
|
config: ExtensionChannelConfig;
|
|
62
|
+
commands: ExtensionChannelCommands;
|
|
46
63
|
};
|
|
47
64
|
type ExtensionChannels = {
|
|
48
65
|
use: (channelId: string) => ExtensionChannel;
|
|
@@ -55,5 +72,5 @@ type ExtensionCapabilities = {
|
|
|
55
72
|
provideHandler: (kind: string, handler: ExtensionCapabilityHandler) => Unsubscribe;
|
|
56
73
|
};
|
|
57
74
|
//#endregion
|
|
58
|
-
export { type ChannelConfigGetRequest, ChannelConfigGetResponse, type ChannelFileContent, type ChannelImageContent, type ChannelMessageContent, type ChannelSubmittedAttachment, type ChannelSubmittedMessage, type ChannelTextContent, ExtensionCapabilities, ExtensionCapabilityHandler, ExtensionCapabilityPayload, ExtensionChannel, ExtensionChannelConfig, ExtensionChannels, ExtensionRequest, ExtensionRequestHandler, type ExtensionRequestResponse, ExtensionTransportEnvelope, NextClawExtensionOptions, NextClawExtensionWebSocketLike };
|
|
75
|
+
export { type ChannelCommandExecuteRequest, type ChannelCommandExecuteResponse, type ChannelCommandListRequest, type ChannelCommandListResponse, type ChannelCommandOption, type ChannelCommandOptionType, type ChannelCommandSpec, type ChannelConfigGetRequest, ChannelConfigGetResponse, type ChannelFileContent, type ChannelImageContent, type ChannelMessageContent, type ChannelSubmittedAttachment, type ChannelSubmittedMessage, type ChannelTextContent, ExtensionCapabilities, ExtensionCapabilityHandler, ExtensionCapabilityPayload, ExtensionChannel, ExtensionChannelCommands, ExtensionChannelConfig, ExtensionChannels, ExtensionRequest, ExtensionRequestHandler, type ExtensionRequestResponse, ExtensionTransportEnvelope, NextClawExtensionOptions, NextClawExtensionWebSocketLike };
|
|
59
76
|
//# sourceMappingURL=extension-sdk.types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extension-sdk.types.d.ts","names":[],"sources":["../../src/types/extension-sdk.types.ts"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"extension-sdk.types.d.ts","names":[],"sources":["../../src/types/extension-sdk.types.ts"],"mappings":";;;;KAyBY,wBAAA;EACV,QAAA;EACA,KAAA;EACA,WAAA;EACA,KAAA,UAAe,KAAA;EACf,gBAAA,IAAoB,GAAA,aAAgB,8BAAA;AAAA;AAAA,KAG1B,8BAAA;EACV,MAAA;EACA,SAAA,IAAa,KAAA;IAAS,IAAA;EAAA;EACtB,OAAA,IAAW,KAAA;EACX,OAAA;EACA,KAAA;AAAA;AAAA,KAGU,0BAAA;EACV,IAAA;EACA,WAAA;EACA,OAAA,EAAS,QAAA;EACT,SAAA;EACA,MAAA;AAAA;AAAA,KAGU,gBAAA;EACV,SAAA;EACA,WAAA;EACA,IAAA;EACA,OAAA,GAAU,MAAA;AAAA;AAAA,KAGA,wBAAA;EACV,MAAA,EAAQ,OAAA;AAAA;AAAA,KAGE,sBAAA;EACV,GAAA,2BAA8B,OAAA,CAAQ,OAAA;EACtC,QAAA,sBACE,OAAA,GAAU,MAAA,EAAQ,OAAA,YAAmB,OAAA,WAClC,WAAA;AAAA;AAAA,KAGK,wBAAA;EACV,IAAA,QAAY,OAAA,CAAQ,2BAAA;EACpB,OAAA,GAAU,KAAA;IACR,WAAA;IACA,IAAA,GAAO,MAAA;IACP,cAAA;IACA,QAAA;IACA,QAAA,GAAW,MAAA;EAAA,MACP,OAAA,CAAQ,sCAAA;EACd,WAAA,GAAc,KAAA;IACZ,OAAA;IACA,cAAA;IACA,QAAA;IACA,QAAA,GAAW,MAAA;EAAA,MACP,OAAA,CAAQ,sCAAA;AAAA;AAAA,KAGJ,gBAAA;EACV,EAAA;EACA,aAAA,GAAgB,KAAA,EAAO,IAAA,CAAK,2CAAA,mBAA8D,OAAA;EAC1F,UAAA,GAAa,OAAA,GAAU,KAAA,EAAO,gBAAA,YAA4B,OAAA,WAAkB,WAAA;EAC5E,MAAA,EAAQ,sBAAA;EACR,QAAA,EAAU,wBAAA;AAAA;AAAA,KAGA,iBAAA;EACV,GAAA,GAAM,SAAA,aAAsB,gBAAA;AAAA;AAAA,KAGlB,uBAAA,IAA2B,OAAA,EAAS,gBAAA,eAA+B,OAAA;AAAA,KAEnE,0BAAA,GAA6B,MAAA;AAAA,KAE7B,0BAAA,kBAA4C,0BAAA,GAA6B,0BAAA,KACnF,OAAA,EAAS,QAAA,EACT,OAAA,EAAS,gBAAA,eACI,OAAA;AAAA,KAEH,qBAAA;EACV,OAAA,GAAU,SAAA,UAAmB,UAAA,aAAuB,WAAA;EACpD,cAAA,GAAiB,IAAA,UAAc,OAAA,EAAS,0BAAA,KAA+B,WAAA;AAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nextclaw/extension-sdk",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.12-beta.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Backend SDK for NextClaw extension server processes.",
|
|
6
6
|
"type": "module",
|
|
@@ -35,8 +35,8 @@
|
|
|
35
35
|
"README.md"
|
|
36
36
|
],
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@nextclaw/
|
|
39
|
-
"@nextclaw/
|
|
38
|
+
"@nextclaw/ncp": "0.5.18-beta.1",
|
|
39
|
+
"@nextclaw/shared": "0.1.12-beta.1"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@types/node": "^20.17.6",
|