@nextclaw/extension-sdk 0.1.9 → 0.1.10

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.
@@ -94,4 +94,5 @@ declare function startChannelExtension<TConfig, TInbound>(definition: ChannelExt
94
94
  declare function startBusChannelExtension<TConfig, TBus = BusChannelMessageBus>(definition: BusChannelExtensionDefinition<TConfig, TBus>, options?: NextClawExtensionOptions): Promise<void>;
95
95
  declare function warnNcpEventError(channelId: string): (error: unknown) => void;
96
96
  //#endregion
97
- export { BusChannelCreateContext, BusChannelExtensionDefinition, BusChannelInboundMessage, BusChannelMessageBus, BusChannelRuntime, ChannelExtensionContext, ChannelExtensionDefinition, ChannelSubmittedMessageInput, ExtensionChannelAdapter, ExtensionChannelController, startBusChannelExtension, startChannelExtension, warnNcpEventError };
97
+ export { BusChannelCreateContext, BusChannelExtensionDefinition, BusChannelInboundMessage, BusChannelMessageBus, BusChannelRuntime, ChannelExtensionContext, ChannelExtensionDefinition, ChannelSubmittedMessageInput, ExtensionChannelAdapter, ExtensionChannelController, startBusChannelExtension, startChannelExtension, warnNcpEventError };
98
+ //# sourceMappingURL=extension-channel-controller.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extension-channel-controller.service.d.ts","names":[],"sources":["../../src/services/extension-channel-controller.service.ts"],"mappings":"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"}
@@ -210,3 +210,5 @@ function toSubmittedTextMessage(message) {
210
210
  }
211
211
  //#endregion
212
212
  export { ExtensionChannelController, startBusChannelExtension, startChannelExtension, warnNcpEventError };
213
+
214
+ //# sourceMappingURL=extension-channel-controller.service.js.map
@@ -0,0 +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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"}
@@ -34,3 +34,5 @@ var ExtensionChannelService = class {
34
34
  };
35
35
  //#endregion
36
36
  export { ExtensionChannelService };
37
+
38
+ //# sourceMappingURL=extension-channel.service.js.map
@@ -0,0 +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":";;AAeA,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,IAAa,0BAAb,MAAiE;CAC/D;CACA;CAEA,YACE,QAKA;AALiB,OAAA,SAAA;AAMjB,OAAK,KAAK,OAAO;AACjB,OAAK,SAAS,IAAI,cAAc,OAAO;;CAGzC,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"}
@@ -15,4 +15,5 @@ declare class NextClawExtension {
15
15
  private readonly toEventBusEnvelope;
16
16
  }
17
17
  //#endregion
18
- export { NextClawExtension };
18
+ export { NextClawExtension };
19
+ //# sourceMappingURL=extension-client.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extension-client.service.d.ts","names":[],"sources":["../../src/services/extension-client.service.ts"],"mappings":";;;;cA6Ia,iBAAA;EAAA,SACF,QAAA,EAAU,QAAA;EAAA,SACV,QAAA,EAAU,iBAAA;EAAA,SACV,YAAA,EAAc,qBAAA;EAAA,SACd,WAAA;EAAA,iBACQ,SAAA;EAAA,QACT,oBAAA;cAEI,OAAA,GAAS,wBAAA;EAAA,SAyBZ,KAAA;EAAA,SAKA,SAAA,GAAa,OAAA,EAAS,uBAAA;EAAA,iBAYd,kBAAA;AAAA"}
@@ -125,3 +125,5 @@ var NextClawExtension = class {
125
125
  };
126
126
  //#endregion
127
127
  export { NextClawExtension };
128
+
129
+ //# sourceMappingURL=extension-client.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extension-client.service.js","names":[],"sources":["../../src/services/extension-client.service.ts"],"sourcesContent":["import { EventBus } from \"@nextclaw/shared\";\nimport type {\n ExtensionCapabilities,\n ExtensionCapabilityHandler,\n ExtensionChannels,\n ExtensionRequest,\n ExtensionRequestHandler,\n ExtensionTransportEnvelope,\n NextClawExtensionOptions,\n} from \"../types/extension-sdk.types.js\";\nimport { ExtensionChannelService } from \"./extension-channel.service.js\";\nimport { ExtensionTransportService } from \"./extension-transport.service.js\";\n\nclass ExtensionChannelRegistry implements ExtensionChannels {\n private readonly channels = new Map<string, ExtensionChannelService>();\n\n constructor(\n private readonly params: {\n eventBus: EventBus;\n transport: ExtensionTransportService;\n },\n ) {}\n\n readonly use = (channelId: string): ExtensionChannelService => {\n const normalizedChannelId = channelId.trim();\n if (!normalizedChannelId) {\n throw new Error(\"channelId is required.\");\n }\n const existing = this.channels.get(normalizedChannelId);\n if (existing) {\n return existing;\n }\n const channel = new ExtensionChannelService({\n channelId: normalizedChannelId,\n eventBus: this.params.eventBus,\n transport: this.params.transport,\n });\n this.channels.set(normalizedChannelId, channel);\n return channel;\n };\n}\n\nfunction readRequest(payload: unknown): ExtensionRequest | null {\n if (!payload || typeof payload !== \"object\" || Array.isArray(payload)) {\n return null;\n }\n const record = payload as Record<string, unknown>;\n if (\n typeof record.requestId !== \"string\" ||\n typeof record.extensionId !== \"string\" ||\n typeof record.kind !== \"string\"\n ) {\n return null;\n }\n return {\n requestId: record.requestId,\n extensionId: record.extensionId,\n kind: record.kind,\n payload: record.payload && typeof record.payload === \"object\" && !Array.isArray(record.payload)\n ? record.payload as Record<string, unknown>\n : {},\n };\n}\n\nfunction normalizeName(value: string, name: string): string {\n const normalized = value.trim();\n if (!normalized) {\n throw new Error(`${name} is required.`);\n }\n return normalized;\n}\n\nasync function handleRequest(\n transport: ExtensionTransportService,\n request: ExtensionRequest,\n handler: ExtensionRequestHandler,\n): Promise<void> {\n try {\n const data = await handler(request);\n await transport.respondToRequest({\n requestId: request.requestId,\n ok: true,\n data,\n });\n } catch (error) {\n await transport.respondToRequest({\n requestId: request.requestId,\n ok: false,\n error: {\n message: error instanceof Error ? error.message : String(error),\n },\n });\n }\n}\n\nclass ExtensionCapabilityRegistry implements ExtensionCapabilities {\n constructor(\n private readonly params: {\n eventBus: EventBus;\n extensionId: string;\n transport: ExtensionTransportService;\n },\n ) {}\n\n readonly provide = (namespace: string, capability: object): (() => void) => {\n const normalizedNamespace = normalizeName(namespace, \"namespace\");\n const unsubscribeHandlers = this.readHandlers(capability).map(([name, handler]) =>\n this.provideHandler(`${normalizedNamespace}.${name}`, handler),\n );\n if (unsubscribeHandlers.length === 0) {\n throw new Error(`capability '${normalizedNamespace}' has no callable methods.`);\n }\n return () => {\n for (const unsubscribe of unsubscribeHandlers) {\n unsubscribe();\n }\n };\n };\n\n readonly provideHandler = (kind: string, handler: ExtensionCapabilityHandler): (() => void) => {\n const normalizedKind = normalizeName(kind, \"kind\");\n return this.params.eventBus.subscribeAll((event) => {\n if (event.type !== \"extension.request\") {\n return;\n }\n const request = readRequest(event.payload);\n if (!request || request.extensionId !== this.params.extensionId || request.kind !== normalizedKind) {\n return;\n }\n void handleRequest(this.params.transport, request, async (matchedRequest) =>\n await handler(matchedRequest.payload ?? {}, matchedRequest),\n );\n });\n };\n\n private readonly readHandlers = (capability: object): Array<[string, ExtensionCapabilityHandler]> =>\n Object.entries(capability).filter(\n (entry): entry is [string, ExtensionCapabilityHandler] => typeof entry[1] === \"function\",\n );\n}\n\nexport class NextClawExtension {\n readonly eventBus: EventBus;\n readonly channels: ExtensionChannels;\n readonly capabilities: ExtensionCapabilities;\n readonly extensionId: string;\n private readonly transport: ExtensionTransportService;\n private realtimeSubscription: { close: () => void } | null = null;\n\n constructor(options: NextClawExtensionOptions = {}) {\n this.transport = new ExtensionTransportService(options);\n this.extensionId = this.transport.extensionId;\n this.eventBus = new EventBus({\n onFirstSubscriber: () => {\n this.realtimeSubscription ??= this.transport.subscribe((event) => {\n this.eventBus.emitEnvelope(this.toEventBusEnvelope(event));\n });\n },\n onNoSubscribers: () => {\n this.realtimeSubscription?.close();\n this.realtimeSubscription = null;\n },\n });\n this.channels = new ExtensionChannelRegistry({\n eventBus: this.eventBus,\n transport: this.transport,\n });\n this.capabilities = new ExtensionCapabilityRegistry({\n eventBus: this.eventBus,\n extensionId: this.extensionId,\n transport: this.transport,\n });\n }\n\n readonly close = (): void => {\n this.realtimeSubscription?.close();\n this.realtimeSubscription = null;\n };\n\n readonly onRequest = (handler: ExtensionRequestHandler): (() => void) =>\n this.eventBus.subscribeAll((event) => {\n if (event.type !== \"extension.request\") {\n return;\n }\n const request = readRequest(event.payload);\n if (!request || request.extensionId !== this.extensionId) {\n return;\n }\n void handleRequest(this.transport, request, handler);\n });\n\n private readonly toEventBusEnvelope = (event: ExtensionTransportEnvelope): ExtensionTransportEnvelope => ({\n ...event,\n emittedAt: event.emittedAt ?? new Date().toISOString(),\n source: event.source ?? \"realtime\",\n });\n\n}\n"],"mappings":"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"}
@@ -84,3 +84,5 @@ var ExtensionTransportService = class {
84
84
  };
85
85
  //#endregion
86
86
  export { ExtensionTransportService };
87
+
88
+ //# sourceMappingURL=extension-transport.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extension-transport.service.js","names":[],"sources":["../../src/services/extension-transport.service.ts"],"sourcesContent":["import type {\n ExtensionRequestResponse,\n ExtensionTransportEnvelope,\n NextClawExtensionOptions,\n NextClawExtensionWebSocketLike,\n} from \"../types/extension-sdk.types.js\";\nimport { getKeyId, ingressKeys } from \"@nextclaw/shared\";\nimport { normalizeEndpoint, resolveWebSocketUrl } from \"../utils/extension-url.utils.js\";\n\ntype RealtimeHandler = (event: ExtensionTransportEnvelope) => void;\n\ntype RuntimeEnv = {\n NEXTCLAW_EXTENSION_ENDPOINT?: string;\n NEXTCLAW_EXTENSION_TOKEN?: string;\n NEXTCLAW_EXTENSION_ID?: string;\n};\n\ndeclare const process: { env: RuntimeEnv } | undefined;\n\nfunction readRuntimeEnv(): RuntimeEnv {\n return typeof process === \"undefined\" ? {} : process.env;\n}\n\nfunction requireRuntimeValue(value: string | undefined, name: string): string {\n const trimmed = value?.trim();\n if (!trimmed) {\n throw new Error(`${name} is required.`);\n }\n return trimmed;\n}\n\nexport class ExtensionTransportService {\n readonly token: string;\n readonly extensionId: string;\n private readonly endpoint: string;\n private readonly fetchImpl: typeof fetch;\n private readonly webSocketFactory?: (url: string) => NextClawExtensionWebSocketLike;\n\n constructor(options: NextClawExtensionOptions = {}) {\n const env = readRuntimeEnv();\n this.endpoint = normalizeEndpoint(\n options.endpoint ?? requireRuntimeValue(env.NEXTCLAW_EXTENSION_ENDPOINT, \"NEXTCLAW_EXTENSION_ENDPOINT\"),\n );\n this.token = options.token ?? requireRuntimeValue(env.NEXTCLAW_EXTENSION_TOKEN, \"NEXTCLAW_EXTENSION_TOKEN\");\n this.extensionId =\n options.extensionId ?? requireRuntimeValue(env.NEXTCLAW_EXTENSION_ID, \"NEXTCLAW_EXTENSION_ID\");\n this.fetchImpl = options.fetch ?? globalThis.fetch;\n this.webSocketFactory = options.webSocketFactory;\n if (typeof this.fetchImpl !== \"function\") {\n throw new Error(\"fetch is unavailable. Provide fetch when creating the extension.\");\n }\n }\n\n readonly postIngress = async <TResponse = unknown>(\n type: string,\n payload: unknown,\n ): Promise<TResponse> => {\n const envelope: ExtensionTransportEnvelope = {\n type,\n extensionId: this.extensionId,\n payload,\n emittedAt: new Date().toISOString(),\n source: \"extension-sdk\",\n };\n const response = await this.fetchImpl(`${this.endpoint}/webhook`, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/json\",\n authorization: `Bearer ${this.token}`,\n },\n body: JSON.stringify(envelope),\n });\n const body = (await response.json().catch(() => null)) as unknown;\n if (!response.ok) {\n throw new Error(this.readErrorMessage(body, `NextClaw ingress failed with ${response.status}`));\n }\n return this.readResponseData<TResponse>(body);\n };\n\n readonly respondToRequest = async (response: ExtensionRequestResponse): Promise<void> => {\n await this.postIngress(getKeyId(ingressKeys.extension.response), response);\n };\n\n readonly subscribe = (handler: RealtimeHandler): { close: () => void } => {\n const socket = this.createSocket(resolveWebSocketUrl(this.endpoint, \"/ws\"));\n socket.onmessage = (event) => {\n const envelope = this.parseEnvelope(event.data);\n if (envelope) {\n handler(envelope);\n }\n };\n return {\n close: () => socket.close(),\n };\n };\n\n private readonly createSocket = (url: string): NextClawExtensionWebSocketLike => {\n if (this.webSocketFactory) {\n return this.webSocketFactory(url);\n }\n if (typeof globalThis.WebSocket !== \"function\") {\n throw new Error(\"WebSocket is unavailable. Provide webSocketFactory when creating the extension.\");\n }\n return new globalThis.WebSocket(url) as unknown as NextClawExtensionWebSocketLike;\n };\n\n private readonly parseEnvelope = (value: unknown): ExtensionTransportEnvelope | null => {\n if (typeof value !== \"string\") {\n return null;\n }\n try {\n const parsed = JSON.parse(value) as ExtensionTransportEnvelope;\n return parsed && typeof parsed === \"object\" && typeof parsed.type === \"string\" ? parsed : null;\n } catch {\n return null;\n }\n };\n\n private readonly readErrorMessage = (body: unknown, fallback: string): string => {\n if (!body || typeof body !== \"object\") {\n return fallback;\n }\n const error = (body as { error?: { message?: unknown } }).error;\n return typeof error?.message === \"string\" && error.message.trim() ? error.message : fallback;\n };\n\n private readonly readResponseData = <TResponse>(body: unknown): TResponse => {\n if (body && typeof body === \"object\" && !Array.isArray(body)) {\n const record = body as { ok?: unknown; data?: unknown };\n if (record.ok === true && \"data\" in record) {\n return record.data as TResponse;\n }\n }\n return body as TResponse;\n };\n}\n"],"mappings":";;;AAmBA,SAAS,iBAA6B;AACpC,QAAO,OAAO,YAAY,cAAc,EAAE,GAAG,QAAQ;;AAGvD,SAAS,oBAAoB,OAA2B,MAAsB;CAC5E,MAAM,UAAU,OAAO,MAAM;AAC7B,KAAI,CAAC,QACH,OAAM,IAAI,MAAM,GAAG,KAAK,eAAe;AAEzC,QAAO;;AAGT,IAAa,4BAAb,MAAuC;CACrC;CACA;CACA;CACA;CACA;CAEA,YAAY,UAAoC,EAAE,EAAE;EAClD,MAAM,MAAM,gBAAgB;AAC5B,OAAK,WAAW,kBACd,QAAQ,YAAY,oBAAoB,IAAI,6BAA6B,8BAA8B,CACxG;AACD,OAAK,QAAQ,QAAQ,SAAS,oBAAoB,IAAI,0BAA0B,2BAA2B;AAC3G,OAAK,cACH,QAAQ,eAAe,oBAAoB,IAAI,uBAAuB,wBAAwB;AAChG,OAAK,YAAY,QAAQ,SAAS,WAAW;AAC7C,OAAK,mBAAmB,QAAQ;AAChC,MAAI,OAAO,KAAK,cAAc,WAC5B,OAAM,IAAI,MAAM,mEAAmE;;CAIvF,cAAuB,OACrB,MACA,YACuB;EACvB,MAAM,WAAuC;GAC3C;GACA,aAAa,KAAK;GAClB;GACA,4BAAW,IAAI,MAAM,EAAC,aAAa;GACnC,QAAQ;GACT;EACD,MAAM,WAAW,MAAM,KAAK,UAAU,GAAG,KAAK,SAAS,WAAW;GAChE,QAAQ;GACR,SAAS;IACP,gBAAgB;IAChB,eAAe,UAAU,KAAK;IAC/B;GACD,MAAM,KAAK,UAAU,SAAS;GAC/B,CAAC;EACF,MAAM,OAAQ,MAAM,SAAS,MAAM,CAAC,YAAY,KAAK;AACrD,MAAI,CAAC,SAAS,GACZ,OAAM,IAAI,MAAM,KAAK,iBAAiB,MAAM,gCAAgC,SAAS,SAAS,CAAC;AAEjG,SAAO,KAAK,iBAA4B,KAAK;;CAG/C,mBAA4B,OAAO,aAAsD;AACvF,QAAM,KAAK,YAAY,SAAS,YAAY,UAAU,SAAS,EAAE,SAAS;;CAG5E,aAAsB,YAAoD;EACxE,MAAM,SAAS,KAAK,aAAa,oBAAoB,KAAK,UAAU,MAAM,CAAC;AAC3E,SAAO,aAAa,UAAU;GAC5B,MAAM,WAAW,KAAK,cAAc,MAAM,KAAK;AAC/C,OAAI,SACF,SAAQ,SAAS;;AAGrB,SAAO,EACL,aAAa,OAAO,OAAO,EAC5B;;CAGH,gBAAiC,QAAgD;AAC/E,MAAI,KAAK,iBACP,QAAO,KAAK,iBAAiB,IAAI;AAEnC,MAAI,OAAO,WAAW,cAAc,WAClC,OAAM,IAAI,MAAM,kFAAkF;AAEpG,SAAO,IAAI,WAAW,UAAU,IAAI;;CAGtC,iBAAkC,UAAsD;AACtF,MAAI,OAAO,UAAU,SACnB,QAAO;AAET,MAAI;GACF,MAAM,SAAS,KAAK,MAAM,MAAM;AAChC,UAAO,UAAU,OAAO,WAAW,YAAY,OAAO,OAAO,SAAS,WAAW,SAAS;UACpF;AACN,UAAO;;;CAIX,oBAAqC,MAAe,aAA6B;AAC/E,MAAI,CAAC,QAAQ,OAAO,SAAS,SAC3B,QAAO;EAET,MAAM,QAAS,KAA2C;AAC1D,SAAO,OAAO,OAAO,YAAY,YAAY,MAAM,QAAQ,MAAM,GAAG,MAAM,UAAU;;CAGtF,oBAAgD,SAA6B;AAC3E,MAAI,QAAQ,OAAO,SAAS,YAAY,CAAC,MAAM,QAAQ,KAAK,EAAE;GAC5D,MAAM,SAAS;AACf,OAAI,OAAO,OAAO,QAAQ,UAAU,OAClC,QAAO,OAAO;;AAGlB,SAAO"}
@@ -55,4 +55,5 @@ type ExtensionCapabilities = {
55
55
  provideHandler: (kind: string, handler: ExtensionCapabilityHandler) => Unsubscribe;
56
56
  };
57
57
  //#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 };
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 };
59
+ //# sourceMappingURL=extension-sdk.types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extension-sdk.types.d.ts","names":[],"sources":["../../src/types/extension-sdk.types.ts"],"mappings":";;;;KAgBY,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,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;AAAA;AAAA,KAGE,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"}
@@ -11,3 +11,5 @@ function resolveWebSocketUrl(endpoint, path) {
11
11
  }
12
12
  //#endregion
13
13
  export { normalizeEndpoint, resolveWebSocketUrl };
14
+
15
+ //# sourceMappingURL=extension-url.utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extension-url.utils.js","names":[],"sources":["../../src/utils/extension-url.utils.ts"],"sourcesContent":["export function normalizeEndpoint(endpoint: string): string {\n const trimmed = endpoint.trim();\n if (!trimmed) {\n throw new Error(\"NextClaw extension endpoint is required.\");\n }\n return trimmed.replace(/\\/+$/, \"\");\n}\n\nexport function resolveWebSocketUrl(endpoint: string, path: string): string {\n const url = new URL(path, `${normalizeEndpoint(endpoint)}/`);\n url.protocol = url.protocol === \"https:\" ? \"wss:\" : \"ws:\";\n return url.toString();\n}\n"],"mappings":";AAAA,SAAgB,kBAAkB,UAA0B;CAC1D,MAAM,UAAU,SAAS,MAAM;AAC/B,KAAI,CAAC,QACH,OAAM,IAAI,MAAM,2CAA2C;AAE7D,QAAO,QAAQ,QAAQ,QAAQ,GAAG;;AAGpC,SAAgB,oBAAoB,UAAkB,MAAsB;CAC1E,MAAM,MAAM,IAAI,IAAI,MAAM,GAAG,kBAAkB,SAAS,CAAC,GAAG;AAC5D,KAAI,WAAW,IAAI,aAAa,WAAW,SAAS;AACpD,QAAO,IAAI,UAAU"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/extension-sdk",
3
- "version": "0.1.9",
3
+ "version": "0.1.10",
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/ncp": "0.5.15",
39
- "@nextclaw/shared": "0.1.9"
38
+ "@nextclaw/ncp": "0.5.16",
39
+ "@nextclaw/shared": "0.1.10"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@types/node": "^20.17.6",
@@ -44,7 +44,7 @@
44
44
  "vitest": "^4.1.2"
45
45
  },
46
46
  "scripts": {
47
- "build": "tsdown src/index.ts --dts --clean --target es2022 --no-fixedExtension --unbundle",
47
+ "build": "tsdown src/index.ts --dts.sourcemap --clean --target es2022 --no-fixedExtension --unbundle",
48
48
  "lint": "eslint src --max-warnings=0",
49
49
  "tsc": "tsc -p tsconfig.json",
50
50
  "test": "vitest run"