@krischoichoi/channel-dingtalk 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +139 -0
- package/lib/adapter.d.ts +123 -0
- package/lib/adapter.d.ts.map +1 -0
- package/lib/adapter.js +355 -0
- package/lib/adapter.js.map +1 -0
- package/lib/ai-card.d.ts +64 -0
- package/lib/ai-card.d.ts.map +1 -0
- package/lib/ai-card.js +150 -0
- package/lib/ai-card.js.map +1 -0
- package/lib/auth/device-registration.d.ts +18 -0
- package/lib/auth/device-registration.d.ts.map +1 -0
- package/lib/auth/device-registration.js +147 -0
- package/lib/auth/device-registration.js.map +1 -0
- package/lib/config.d.ts +119 -0
- package/lib/config.d.ts.map +1 -0
- package/lib/config.js +75 -0
- package/lib/config.js.map +1 -0
- package/lib/definition.d.ts +53 -0
- package/lib/definition.d.ts.map +1 -0
- package/lib/definition.js +227 -0
- package/lib/definition.js.map +1 -0
- package/lib/image-hydrator.d.ts +213 -0
- package/lib/image-hydrator.d.ts.map +1 -0
- package/lib/image-hydrator.js +244 -0
- package/lib/image-hydrator.js.map +1 -0
- package/lib/inbound.d.ts +38 -0
- package/lib/inbound.d.ts.map +1 -0
- package/lib/inbound.js +158 -0
- package/lib/inbound.js.map +1 -0
- package/lib/index.d.ts +49 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +123 -0
- package/lib/index.js.map +1 -0
- package/lib/manifest.d.ts +23 -0
- package/lib/manifest.d.ts.map +1 -0
- package/lib/manifest.js +34 -0
- package/lib/manifest.js.map +1 -0
- package/lib/mapper.d.ts +36 -0
- package/lib/mapper.d.ts.map +1 -0
- package/lib/mapper.js +175 -0
- package/lib/mapper.js.map +1 -0
- package/lib/media-mime.d.ts +14 -0
- package/lib/media-mime.d.ts.map +1 -0
- package/lib/media-mime.js +32 -0
- package/lib/media-mime.js.map +1 -0
- package/lib/official-upstream.d.ts +143 -0
- package/lib/official-upstream.d.ts.map +1 -0
- package/lib/official-upstream.js +425 -0
- package/lib/official-upstream.js.map +1 -0
- package/lib/openapi-port.d.ts +190 -0
- package/lib/openapi-port.d.ts.map +1 -0
- package/lib/openapi-port.js +2 -0
- package/lib/openapi-port.js.map +1 -0
- package/lib/outbound.d.ts +69 -0
- package/lib/outbound.d.ts.map +1 -0
- package/lib/outbound.js +148 -0
- package/lib/outbound.js.map +1 -0
- package/lib/stream-upstream.d.ts +77 -0
- package/lib/stream-upstream.d.ts.map +1 -0
- package/lib/stream-upstream.js +303 -0
- package/lib/stream-upstream.js.map +1 -0
- package/lib/transport.d.ts +37 -0
- package/lib/transport.d.ts.map +1 -0
- package/lib/transport.js +73 -0
- package/lib/transport.js.map +1 -0
- package/lib/upstream.d.ts +60 -0
- package/lib/upstream.d.ts.map +1 -0
- package/lib/upstream.js +61 -0
- package/lib/upstream.js.map +1 -0
- package/package.json +46 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { applyHydrationResult, SecureRemoteMediaFetcher, toIngressFailureCode, } from '@krischoichoi/channel-core';
|
|
2
|
+
/** Default hard byte cap for a downloaded image (20 MiB). */
|
|
3
|
+
export const IMAGE_MAX_BYTES = 20 * 1024 * 1024;
|
|
4
|
+
/** Default read-idle timeout (no body chunk for N ms -> fail). */
|
|
5
|
+
export const IMAGE_IDLE_TIMEOUT_MS = 15_000;
|
|
6
|
+
function classifyLocator(url) {
|
|
7
|
+
if (url === undefined || url === '')
|
|
8
|
+
return 'none';
|
|
9
|
+
return /^https?:\/\//i.test(url) ? 'http-url' : 'opaque';
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Identify the http(s)-fetchable image parts in a message content. Opaque
|
|
13
|
+
* (non-http) locators are surfaced separately so the ingestion policy can
|
|
14
|
+
* route them to `resourceRef`.
|
|
15
|
+
*/
|
|
16
|
+
export function findFetchableImages(parts) {
|
|
17
|
+
const out = [];
|
|
18
|
+
parts.forEach((part, index) => {
|
|
19
|
+
if (part.type !== 'image')
|
|
20
|
+
return;
|
|
21
|
+
const kind = classifyLocator(part.url);
|
|
22
|
+
if (kind === 'none')
|
|
23
|
+
return;
|
|
24
|
+
out.push({ kind, index, locator: part.url });
|
|
25
|
+
});
|
|
26
|
+
return out;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Hydrate image parts in place: mutate `parts` so every image with a genuine
|
|
30
|
+
* http(s) URL carries `localData` + `mimeType` (preferring the fetcher MIME
|
|
31
|
+
* type) on success, and `ingressFailure` on failure. Opaque locators are
|
|
32
|
+
* moved to `resourceRef` and left unresolved (OpenAPI milestone).
|
|
33
|
+
*
|
|
34
|
+
* This never throws and never blocks text delivery — any image failure only
|
|
35
|
+
* annotates the part.
|
|
36
|
+
*/
|
|
37
|
+
export async function hydrateImages(parts, options = {}) {
|
|
38
|
+
const fetcher = options.secureFetch ?? new SecureRemoteMediaFetcher();
|
|
39
|
+
const resolver = options.resolveMedia;
|
|
40
|
+
const maxBytes = options.maxBytes ?? IMAGE_MAX_BYTES;
|
|
41
|
+
const idleTimeoutMs = options.idleTimeoutMs ?? IMAGE_IDLE_TIMEOUT_MS;
|
|
42
|
+
for (const part of parts) {
|
|
43
|
+
if (part.type !== 'image')
|
|
44
|
+
continue;
|
|
45
|
+
// Opaque handle: either the mapper already placed it in resourceRef, or a
|
|
46
|
+
// non-http url needs moving there first. Resolve via the DingTalk OpenAPI
|
|
47
|
+
// port (official downloadCode flow) when a resolver is wired — never via a
|
|
48
|
+
// generic fetch.
|
|
49
|
+
let opaque = part.resourceRef;
|
|
50
|
+
if (!opaque) {
|
|
51
|
+
const kind = classifyLocator(part.url);
|
|
52
|
+
if (kind === 'none')
|
|
53
|
+
continue;
|
|
54
|
+
if (kind === 'opaque') {
|
|
55
|
+
opaque = part.url;
|
|
56
|
+
part.resourceRef = opaque;
|
|
57
|
+
delete part.url;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (opaque) {
|
|
61
|
+
if (!resolver)
|
|
62
|
+
continue;
|
|
63
|
+
try {
|
|
64
|
+
const resolved = await resolver.resolveMedia(opaque, {
|
|
65
|
+
signal: options.signal,
|
|
66
|
+
name: part.alt,
|
|
67
|
+
downloadCode: options.downloadContext?.downloadCode,
|
|
68
|
+
robotCode: options.downloadContext?.robotCode,
|
|
69
|
+
});
|
|
70
|
+
applyResolvedImage(part, resolved);
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
part.ingressFailure = toIngressFailureCode(error);
|
|
74
|
+
options.onFailure?.(error, part);
|
|
75
|
+
}
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
const url = part.url;
|
|
79
|
+
try {
|
|
80
|
+
const result = await fetcher.fetchBounded(url, {
|
|
81
|
+
maxBytes,
|
|
82
|
+
idleTimeoutMs,
|
|
83
|
+
signal: options.signal,
|
|
84
|
+
});
|
|
85
|
+
// Prefer the fetcher declared MIME type; fall back to any existing hint.
|
|
86
|
+
part.localData = result.data;
|
|
87
|
+
if (result.mimeType)
|
|
88
|
+
part.mimeType = result.mimeType;
|
|
89
|
+
if (part.size === undefined)
|
|
90
|
+
part.size = result.data.byteLength;
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
// Keep the locator and annotate the failure; never throw into the caller.
|
|
94
|
+
part.ingressFailure = toIngressFailureCode(error);
|
|
95
|
+
options.onFailure?.(error, part);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/** Default hard byte cap for a downloaded generic file (50 MiB). */
|
|
100
|
+
export const FILE_MAX_BYTES = 50 * 1024 * 1024;
|
|
101
|
+
/**
|
|
102
|
+
* Hydrate generic file parts in place:
|
|
103
|
+
* - http(s) url -> SecureRemoteMediaFetcher -> `localData` + `mimeType` + `size`
|
|
104
|
+
* - opaque mediaId -> move to `resourceRef` -> `resolveMedia` -> `localData`
|
|
105
|
+
* On failure the original locator is kept and a stable `ingressFailure` is
|
|
106
|
+
* stamped — this never throws and never blocks text delivery.
|
|
107
|
+
*/
|
|
108
|
+
export async function hydrateFiles(parts, options = {}) {
|
|
109
|
+
const fetcher = options.secureFetch ?? new SecureRemoteMediaFetcher();
|
|
110
|
+
const resolver = options.resolveMedia;
|
|
111
|
+
const maxBytes = options.maxBytes ?? FILE_MAX_BYTES;
|
|
112
|
+
const idleTimeoutMs = options.idleTimeoutMs ?? IMAGE_IDLE_TIMEOUT_MS;
|
|
113
|
+
for (const part of parts) {
|
|
114
|
+
if (part.type !== 'file')
|
|
115
|
+
continue;
|
|
116
|
+
const file = part;
|
|
117
|
+
// Opaque handle: either the mapper already placed it in resourceRef, or a
|
|
118
|
+
// non-http url needs moving there first. Resolve via the port (official
|
|
119
|
+
// downloadCode flow) when a resolver is wired — never via a generic fetch.
|
|
120
|
+
let opaque = file.resourceRef;
|
|
121
|
+
if (!opaque) {
|
|
122
|
+
const kind = classifyPartLocator(file.url);
|
|
123
|
+
if (kind === 'none')
|
|
124
|
+
continue;
|
|
125
|
+
if (kind === 'opaque') {
|
|
126
|
+
opaque = file.url;
|
|
127
|
+
file.resourceRef = opaque;
|
|
128
|
+
delete file.url;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (opaque) {
|
|
132
|
+
if (resolver) {
|
|
133
|
+
try {
|
|
134
|
+
const resolved = await resolver.resolveMedia(opaque, {
|
|
135
|
+
signal: options.signal,
|
|
136
|
+
name: file.name,
|
|
137
|
+
downloadCode: options.downloadContext?.downloadCode,
|
|
138
|
+
robotCode: options.downloadContext?.robotCode,
|
|
139
|
+
});
|
|
140
|
+
applyResolved(file, resolved);
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
file.ingressFailure = toIngressFailureCode(error);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
const url = file.url;
|
|
149
|
+
try {
|
|
150
|
+
const result = await fetcher.fetchBounded(url, { maxBytes, idleTimeoutMs, signal: options.signal });
|
|
151
|
+
file.localData = result.data;
|
|
152
|
+
if (result.mimeType)
|
|
153
|
+
file.mimeType = result.mimeType;
|
|
154
|
+
if (file.size === undefined)
|
|
155
|
+
file.size = result.data.byteLength;
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
file.ingressFailure = toIngressFailureCode(error);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
function classifyPartLocator(url) {
|
|
163
|
+
if (url === undefined || url === '')
|
|
164
|
+
return 'none';
|
|
165
|
+
return /^https?:\/\//i.test(url) ? 'http-url' : 'opaque';
|
|
166
|
+
}
|
|
167
|
+
/** Default hard byte cap for a downloaded audio/video resource (100 MiB). */
|
|
168
|
+
export const MEDIA_MAX_BYTES = 100 * 1024 * 1024;
|
|
169
|
+
/**
|
|
170
|
+
* Hydrate audio/video parts in place. Audio/video follow the
|
|
171
|
+
* exact same URL-or-opaque resolution as image/file:
|
|
172
|
+
* - http(s) url -> SecureRemoteMediaFetcher -> `localData` + `mimeType` + `size`
|
|
173
|
+
* - opaque handle -> move to `resourceRef` -> `resolveMedia` (official
|
|
174
|
+
* `messageFiles/download` seam) -> `localData`
|
|
175
|
+
* The apply step delegates to core `applyHydrationResult`, so success clears
|
|
176
|
+
* `ingressFailure`, failures write ONLY a stable `ingressFailure` (never
|
|
177
|
+
* fabricating bytes), and this never throws / never blocks text delivery.
|
|
178
|
+
*/
|
|
179
|
+
export async function hydrateMedia(parts, options = {}) {
|
|
180
|
+
const fetcher = options.secureFetch ?? new SecureRemoteMediaFetcher();
|
|
181
|
+
const resolver = options.resolveMedia;
|
|
182
|
+
const maxBytes = options.maxBytes ?? MEDIA_MAX_BYTES;
|
|
183
|
+
const idleTimeoutMs = options.idleTimeoutMs ?? IMAGE_IDLE_TIMEOUT_MS;
|
|
184
|
+
for (const part of parts) {
|
|
185
|
+
if (part.type !== 'audio' && part.type !== 'video')
|
|
186
|
+
continue;
|
|
187
|
+
const media = part;
|
|
188
|
+
// Opaque handle: either the mapper already placed it in resourceRef, or a
|
|
189
|
+
// non-http url needs moving there first. Resolve via the port (official
|
|
190
|
+
// downloadCode flow) when a resolver is wired — never via a generic fetch.
|
|
191
|
+
let opaque = media.resourceRef;
|
|
192
|
+
if (!opaque) {
|
|
193
|
+
const kind = classifyPartLocator(media.url);
|
|
194
|
+
if (kind === 'none')
|
|
195
|
+
continue;
|
|
196
|
+
if (kind === 'opaque') {
|
|
197
|
+
opaque = media.url;
|
|
198
|
+
media.resourceRef = opaque;
|
|
199
|
+
delete media.url;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (opaque) {
|
|
203
|
+
if (!resolver)
|
|
204
|
+
continue;
|
|
205
|
+
await applyHydrationResult(media, () => resolver.resolveMedia(opaque, {
|
|
206
|
+
signal: options.signal,
|
|
207
|
+
name: media.name,
|
|
208
|
+
downloadCode: options.downloadContext?.downloadCode,
|
|
209
|
+
robotCode: options.downloadContext?.robotCode,
|
|
210
|
+
}), { maxBytes, signal: options.signal });
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
const url = media.url;
|
|
214
|
+
await applyHydrationResult(media, async () => {
|
|
215
|
+
const result = await fetcher.fetchBounded(url, { maxBytes, idleTimeoutMs, signal: options.signal });
|
|
216
|
+
return { data: result.data, mimeType: result.mimeType };
|
|
217
|
+
}, { maxBytes, signal: options.signal });
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
/** Apply a successful resolution to a file part. */
|
|
221
|
+
function applyResolved(part, resolved) {
|
|
222
|
+
part.localData = resolved.data;
|
|
223
|
+
if (resolved.mimeType)
|
|
224
|
+
part.mimeType = resolved.mimeType;
|
|
225
|
+
if (resolved.size !== undefined)
|
|
226
|
+
part.size = resolved.size;
|
|
227
|
+
if (part.size === undefined)
|
|
228
|
+
part.size = resolved.data.byteLength;
|
|
229
|
+
}
|
|
230
|
+
/** Apply a successful resolution to an image part. */
|
|
231
|
+
function applyResolvedImage(part, resolved) {
|
|
232
|
+
part.localData = resolved.data;
|
|
233
|
+
if (resolved.mimeType)
|
|
234
|
+
part.mimeType = resolved.mimeType;
|
|
235
|
+
if (resolved.size !== undefined)
|
|
236
|
+
part.size = resolved.size;
|
|
237
|
+
if (part.size === undefined)
|
|
238
|
+
part.size = resolved.data.byteLength;
|
|
239
|
+
}
|
|
240
|
+
/** Convenience factory returning a real secure fetcher bound to global fetch. */
|
|
241
|
+
export function defaultSecureFetcher() {
|
|
242
|
+
return new SecureRemoteMediaFetcher();
|
|
243
|
+
}
|
|
244
|
+
//# sourceMappingURL=image-hydrator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-hydrator.js","sourceRoot":"","sources":["../src/image-hydrator.ts"],"names":[],"mappings":"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"}
|
package/lib/inbound.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inbound processing: dedup window + structured mapping + media hydration (image/file/audio/video) + emit.
|
|
3
|
+
*/
|
|
4
|
+
import type { ChannelAdapterContext } from '@krischoichoi/channel-core';
|
|
5
|
+
import { type DingTalkInboundMeta } from './mapper.js';
|
|
6
|
+
import { type RemoteMediaFetchLike } from './image-hydrator.js';
|
|
7
|
+
import type { MediaResolverLike } from './openapi-port.js';
|
|
8
|
+
export interface InboundProcessorOptions {
|
|
9
|
+
ctx: ChannelAdapterContext;
|
|
10
|
+
meta: DingTalkInboundMeta;
|
|
11
|
+
dedupEnabled: boolean;
|
|
12
|
+
dedupWindowMs: number;
|
|
13
|
+
/** Injectable clock (tests). */
|
|
14
|
+
now?: () => number;
|
|
15
|
+
/**
|
|
16
|
+
* Secure remote media fetcher used to hydrate inbound image URLs into
|
|
17
|
+
* `localData`. Injectable for offline tests; defaults to a real
|
|
18
|
+
* `SecureRemoteMediaFetcher` bound to the global fetch.
|
|
19
|
+
*/
|
|
20
|
+
secureFetch?: RemoteMediaFetchLike;
|
|
21
|
+
/**
|
|
22
|
+
* DingTalk OpenAPI media resolver used to turn opaque file mediaIds into
|
|
23
|
+
* trusted bytes during file ingress. Injectable for offline
|
|
24
|
+
* tests; when absent, opaque file handles are deferred to `resourceRef`.
|
|
25
|
+
*/
|
|
26
|
+
resolveMedia?: MediaResolverLike;
|
|
27
|
+
}
|
|
28
|
+
export declare class InboundProcessor {
|
|
29
|
+
private readonly options;
|
|
30
|
+
private readonly now;
|
|
31
|
+
/** dedup key -> last-seen timestamp, pruned on every handle. */
|
|
32
|
+
private readonly seen;
|
|
33
|
+
constructor(options: InboundProcessorOptions);
|
|
34
|
+
/** Process one raw payload from the upstream; dedup, hydrate, then emit. */
|
|
35
|
+
handle(raw: unknown): Promise<void>;
|
|
36
|
+
private prune;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=inbound.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inbound.d.ts","sourceRoot":"","sources":["../src/inbound.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,OAAO,KAAK,EAAE,qBAAqB,EAAgC,MAAM,4BAA4B,CAAC;AAEtG,OAAO,EAAwC,KAAK,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAwD7F,OAAO,EAA6C,KAAK,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3G,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAE3D,MAAM,WAAW,uBAAuB;IACtC,GAAG,EAAE,qBAAqB,CAAC;IAC3B,IAAI,EAAE,mBAAmB,CAAC;IAC1B,YAAY,EAAE,OAAO,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,gCAAgC;IAChC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB;;;;OAIG;IACH,WAAW,CAAC,EAAE,oBAAoB,CAAC;IACnC;;;;OAIG;IACH,YAAY,CAAC,EAAE,iBAAiB,CAAC;CAClC;AAED,qBAAa,gBAAgB;IAKf,OAAO,CAAC,QAAQ,CAAC,OAAO;IAJpC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,gEAAgE;IAChE,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA6B;gBAErB,OAAO,EAAE,uBAAuB;IAI7D,4EAA4E;IACtE,MAAM,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAkFzC,OAAO,CAAC,KAAK;CAKd"}
|
package/lib/inbound.js
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { dedupKey, mapInbound, mapInteraction } from './mapper.js';
|
|
3
|
+
const downloadContextSchema = z.object({
|
|
4
|
+
type: z.string().optional(),
|
|
5
|
+
picDownloadCode: z.string().min(1).optional(),
|
|
6
|
+
downloadCode: z.string().min(1).optional(),
|
|
7
|
+
robotCode: z.string().min(1).optional(),
|
|
8
|
+
});
|
|
9
|
+
/** Compact per-part summary for inbound message logs (debug diagnostics). */
|
|
10
|
+
function summarizeParts(parts) {
|
|
11
|
+
return parts.map((part) => {
|
|
12
|
+
switch (part.type) {
|
|
13
|
+
case 'text':
|
|
14
|
+
return { type: 'text', text: part.text.slice(0, 80) };
|
|
15
|
+
case 'image':
|
|
16
|
+
return {
|
|
17
|
+
type: 'image',
|
|
18
|
+
hasUrl: Boolean(part.url),
|
|
19
|
+
resourceRef: part.resourceRef ? '[redacted]' : undefined,
|
|
20
|
+
mimeType: part.mimeType,
|
|
21
|
+
localDataBytes: part.localData?.byteLength,
|
|
22
|
+
ingressFailure: part.ingressFailure,
|
|
23
|
+
};
|
|
24
|
+
case 'file':
|
|
25
|
+
return {
|
|
26
|
+
type: 'file',
|
|
27
|
+
name: part.name,
|
|
28
|
+
size: part.size,
|
|
29
|
+
mimeType: part.mimeType,
|
|
30
|
+
localDataBytes: part.localData?.byteLength,
|
|
31
|
+
ingressFailure: part.ingressFailure,
|
|
32
|
+
};
|
|
33
|
+
case 'audio':
|
|
34
|
+
return {
|
|
35
|
+
type: 'audio',
|
|
36
|
+
durationMs: part.durationMs,
|
|
37
|
+
mimeType: part.mimeType,
|
|
38
|
+
size: part.size,
|
|
39
|
+
localDataBytes: part.localData?.byteLength,
|
|
40
|
+
ingressFailure: part.ingressFailure,
|
|
41
|
+
};
|
|
42
|
+
case 'video':
|
|
43
|
+
return {
|
|
44
|
+
type: 'video',
|
|
45
|
+
durationMs: part.durationMs,
|
|
46
|
+
mimeType: part.mimeType,
|
|
47
|
+
size: part.size,
|
|
48
|
+
localDataBytes: part.localData?.byteLength,
|
|
49
|
+
ingressFailure: part.ingressFailure,
|
|
50
|
+
};
|
|
51
|
+
default:
|
|
52
|
+
return { type: part.type };
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
import { hydrateFiles, hydrateImages, hydrateMedia } from './image-hydrator.js';
|
|
57
|
+
export class InboundProcessor {
|
|
58
|
+
options;
|
|
59
|
+
now;
|
|
60
|
+
/** dedup key -> last-seen timestamp, pruned on every handle. */
|
|
61
|
+
seen = new Map();
|
|
62
|
+
constructor(options) {
|
|
63
|
+
this.options = options;
|
|
64
|
+
this.now = options.now ?? Date.now;
|
|
65
|
+
}
|
|
66
|
+
/** Process one raw payload from the upstream; dedup, hydrate, then emit. */
|
|
67
|
+
async handle(raw) {
|
|
68
|
+
const key = dedupKey(raw);
|
|
69
|
+
if (this.options.dedupEnabled) {
|
|
70
|
+
const now = this.now();
|
|
71
|
+
const last = this.seen.get(key);
|
|
72
|
+
if (last !== undefined && now - last < this.options.dedupWindowMs) {
|
|
73
|
+
this.options.ctx.logger.debug(`[channel-dingtalk] dropped duplicate message '${key}'`);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
this.seen.set(key, now);
|
|
77
|
+
this.prune(now);
|
|
78
|
+
}
|
|
79
|
+
const kind = z.object({ type: z.string().optional() }).passthrough().safeParse(raw);
|
|
80
|
+
if (kind.success && kind.data.type === 'interaction') {
|
|
81
|
+
const interaction = mapInteraction(raw, this.options.meta);
|
|
82
|
+
this.options.ctx.logger.info('[channel-dingtalk] inbound interaction', {
|
|
83
|
+
interactionId: interaction.interactionId,
|
|
84
|
+
conversationId: interaction.conversation.id,
|
|
85
|
+
senderId: interaction.sender.id,
|
|
86
|
+
action: interaction.action,
|
|
87
|
+
});
|
|
88
|
+
await this.options.ctx.emit(interaction);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const event = mapInbound(raw, this.options.meta);
|
|
92
|
+
// Per-message download context (official robot schema): the callback's
|
|
93
|
+
// downloadCode + robotCode are transient upstream state the official
|
|
94
|
+
// /v1.0/robot/messageFiles/download API needs. Read from the raw payload
|
|
95
|
+
// and passed to hydration — never persisted onto core parts.
|
|
96
|
+
const parsedDownloadContext = downloadContextSchema.safeParse(raw);
|
|
97
|
+
const rawValue = parsedDownloadContext.success ? parsedDownloadContext.data : undefined;
|
|
98
|
+
const downloadContext = {
|
|
99
|
+
downloadCode: rawValue?.picDownloadCode ?? rawValue?.downloadCode,
|
|
100
|
+
robotCode: rawValue?.robotCode,
|
|
101
|
+
};
|
|
102
|
+
// Hydrate image parts (never throws / never blocks text delivery). The
|
|
103
|
+
// adapter context signal lets the owning scope abort in-flight downloads.
|
|
104
|
+
await hydrateImages(event.message.content, {
|
|
105
|
+
secureFetch: this.options.secureFetch,
|
|
106
|
+
resolveMedia: this.options.resolveMedia,
|
|
107
|
+
downloadContext,
|
|
108
|
+
signal: this.options.ctx.signal,
|
|
109
|
+
onFailure: (error, part) => {
|
|
110
|
+
this.options.ctx.logger.warn('[channel-dingtalk] inbound image hydration failed', {
|
|
111
|
+
hasResourceRef: part.type === 'image' && Boolean(part.resourceRef),
|
|
112
|
+
hasUrl: part.type === 'image' && Boolean(part.url),
|
|
113
|
+
error: summarizeError(error),
|
|
114
|
+
});
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
// Hydrate generic file parts: genuine http(s) urls via the secure fetcher;
|
|
118
|
+
// opaque mediaIds via the DingTalk OpenAPI media resolver.
|
|
119
|
+
await hydrateFiles(event.message.content, {
|
|
120
|
+
secureFetch: this.options.secureFetch,
|
|
121
|
+
resolveMedia: this.options.resolveMedia,
|
|
122
|
+
downloadContext,
|
|
123
|
+
signal: this.options.ctx.signal,
|
|
124
|
+
});
|
|
125
|
+
// Hydrate audio/video parts: audio/video follow the exact
|
|
126
|
+
// same URL-or-opaque resolution as image/file — genuine http(s) urls via
|
|
127
|
+
// the secure fetcher, opaque downloadCodes via the official
|
|
128
|
+
// messageFiles/download seam (the connector oracle downloads voice/video
|
|
129
|
+
// bytes through the identical downloadCode -> downloadUrl -> raw bytes
|
|
130
|
+
// flow). Failures only annotate the part; text still delivers.
|
|
131
|
+
await hydrateMedia(event.message.content, {
|
|
132
|
+
secureFetch: this.options.secureFetch,
|
|
133
|
+
resolveMedia: this.options.resolveMedia,
|
|
134
|
+
downloadContext,
|
|
135
|
+
signal: this.options.ctx.signal,
|
|
136
|
+
});
|
|
137
|
+
// Inbound message log: expose hydration state without platform locators.
|
|
138
|
+
// Signed URLs and download codes must never enter logs at any level.
|
|
139
|
+
this.options.ctx.logger.info(`[channel-dingtalk] inbound message ${event.message.id} from ${event.sender.id} in ${event.conversation.id}`, {
|
|
140
|
+
msgtype: rawValue?.type,
|
|
141
|
+
parts: summarizeParts(event.message.content),
|
|
142
|
+
});
|
|
143
|
+
await this.options.ctx.emit(event);
|
|
144
|
+
}
|
|
145
|
+
prune(now) {
|
|
146
|
+
for (const [key, ts] of this.seen) {
|
|
147
|
+
if (now - ts >= this.options.dedupWindowMs)
|
|
148
|
+
this.seen.delete(key);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function summarizeError(error) {
|
|
153
|
+
if (!(error instanceof Error))
|
|
154
|
+
return { name: 'UnknownError' };
|
|
155
|
+
const code = 'code' in error && typeof error.code === 'string' ? error.code : undefined;
|
|
156
|
+
return { name: error.name, ...(code !== undefined ? { code } : {}) };
|
|
157
|
+
}
|
|
158
|
+
//# sourceMappingURL=inbound.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inbound.js","sourceRoot":"","sources":["../src/inbound.ts"],"names":[],"mappings":"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"}
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @krischoichoi/channel-dingtalk — DingTalk channel adapter for DeepSeek Harness.
|
|
3
|
+
*
|
|
4
|
+
* Maps the DingTalk platform to the stable Channel Contract. Two upstream
|
|
5
|
+
* drivers implement `DingTalkUpstream` behind `config.upstream.mode`:
|
|
6
|
+
* - 'sdk' — inbound via the official `dingtalk-stream` SDK (WebSocket
|
|
7
|
+
* stream mode); outbound via `sessionWebhook` and the AI Card OpenAPI.
|
|
8
|
+
* - 'gateway' — self-hosted HTTP gateway long-poll driver (legacy).
|
|
9
|
+
*
|
|
10
|
+
* Credential model: the DingTalk AppSecret never lives in config. Config only
|
|
11
|
+
* carries its credential reference (`upstream.clientSecretRef`, default
|
|
12
|
+
* `DSH_CHANNEL_DINGTALK_MAIN_CLIENT_SECRET`); the value is resolved via
|
|
13
|
+
* `ctx.credentials` and injected as `deps.clientSecret`. A legacy plaintext
|
|
14
|
+
* `upstream.clientSecret` is migrated to the credentials seam ONCE on startup
|
|
15
|
+
* and deleted. Streaming is `edit` (AI Card): create card → update with
|
|
16
|
+
* content → finalize → failure card. Auth is connection-state driven — the
|
|
17
|
+
* driver owns platform credentials (never logged).
|
|
18
|
+
*/
|
|
19
|
+
import { type Context } from '@deepseek-ai/cordis';
|
|
20
|
+
import { Config, DINGTALK_CLIENT_SECRET_REF } from './config.js';
|
|
21
|
+
import type { DingTalkConfig, DingTalkConfigLive } from './config.js';
|
|
22
|
+
import { type DingTalkAdapterDeps } from './adapter.js';
|
|
23
|
+
export declare const name = "channel-dingtalk";
|
|
24
|
+
export declare const inject: string[];
|
|
25
|
+
export { Config, DINGTALK_CLIENT_SECRET_REF };
|
|
26
|
+
export type { DingTalkConfig, DingTalkConfigLive } from './config.js';
|
|
27
|
+
export { createDingTalkDefinition } from './definition.js';
|
|
28
|
+
export { beginDingTalkDeviceAuth, pollDingTalkDeviceAuth, type DingTalkDeviceAuthOptions, type DingTalkDevicePollResult, } from './auth/device-registration.js';
|
|
29
|
+
export { DingTalkAdapter, type DingTalkAdapterDeps } from './adapter.js';
|
|
30
|
+
export { DingTalkCardReply, type DingTalkCardStatus, type DingTalkCardUpdate } from './ai-card.js';
|
|
31
|
+
export { InboundProcessor } from './inbound.js';
|
|
32
|
+
export { IMAGE_IDLE_TIMEOUT_MS, IMAGE_MAX_BYTES, FILE_MAX_BYTES, MEDIA_MAX_BYTES, defaultSecureFetcher, findFetchableImages, hydrateFiles, hydrateImages, hydrateMedia, type BinaryIngressFailureCode, type HydrateFilesOptions, type HydrateImagesOptions, type HydrateMediaOptions, type RemoteMediaFetchLike, type ResolvedImageLocator, } from './image-hydrator.js';
|
|
33
|
+
export { OutboundSender, type OutboundSenderOptions, } from './outbound.js';
|
|
34
|
+
export { HttpDingTalkUpstream, type CardCreateResult, type DingTalkUpstream } from './upstream.js';
|
|
35
|
+
export { DingTalkOfficialUpstream, DingTalkOpenApiPortImpl, OFFICIAL_BASIS, type DingTalkOfficialUpstreamOptions, } from './official-upstream.js';
|
|
36
|
+
export { type AccessTokenResult, type DingTalkOpenApiCredentials, type DingTalkOpenApiPort, type MediaResolverLike, type MediaSendInput, type MediaUploadInput, type MediaUploadResult, type OutboxCapabilities, type ProactiveTextInput, type ResolvedMedia, type RobotMessageSendResult, } from './openapi-port.js';
|
|
37
|
+
export { DingTalkStreamUpstream, ackRobotMessage, toCardInteractionRaw, toGatewayRaw, type DingTalkStreamClient, type DingTalkStreamMessage, type DingTalkStreamUpstreamOptions, } from './stream-upstream.js';
|
|
38
|
+
export { FetchTransport, type HttpTransport } from './transport.js';
|
|
39
|
+
export { mapInbound, toTextPayload, dedupKey, simpleHash, type DingTalkTextPayload, } from './mapper.js';
|
|
40
|
+
export { manifest, type DingTalkManifest } from './manifest.js';
|
|
41
|
+
/**
|
|
42
|
+
* Entry plugin apply. Accepts both the activation-time live shape (volatile
|
|
43
|
+
* fields resolve to `Volatile<T>` handles under Harness 0.2.0) and a plain
|
|
44
|
+
* snapshot (tests / programmatic mounting); plain fields pass through
|
|
45
|
+
* `resolveVolatileConfig` untouched. The shape is validated by Schemastery at
|
|
46
|
+
* the Loader boundary via the attached `Config` schema.
|
|
47
|
+
*/
|
|
48
|
+
export declare function apply(ctx: Context, rawConfig: DingTalkConfig | DingTalkConfigLive, deps?: DingTalkAdapterDeps): void;
|
|
49
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAInD,OAAO,EAAE,MAAM,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAC;AACjE,OAAO,KAAK,EAAE,cAAc,EAAE,kBAAkB,EAA0B,MAAM,aAAa,CAAC;AAC9F,OAAO,EAAmB,KAAK,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAGzE,eAAO,MAAM,IAAI,qBAAqB,CAAC;AACvC,eAAO,MAAM,MAAM,EAAE,MAAM,EAAgC,CAAC;AAE5D,OAAO,EAAE,MAAM,EAAE,0BAA0B,EAAE,CAAC;AAC9C,YAAY,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACtE,OAAO,EAAE,wBAAwB,EAAE,MAAM,iBAAiB,CAAC;AAC3D,OAAO,EACL,uBAAuB,EACvB,sBAAsB,EACtB,KAAK,yBAAyB,EAC9B,KAAK,wBAAwB,GAC9B,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAE,eAAe,EAAE,KAAK,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACzE,OAAO,EAAE,iBAAiB,EAAE,KAAK,kBAAkB,EAAE,KAAK,kBAAkB,EAAE,MAAM,cAAc,CAAC;AACnG,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EACL,qBAAqB,EACrB,eAAe,EACf,cAAc,EACd,eAAe,EACf,oBAAoB,EACpB,mBAAmB,EACnB,YAAY,EACZ,aAAa,EACb,YAAY,EACZ,KAAK,wBAAwB,EAC7B,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,GAC1B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,cAAc,EACd,KAAK,qBAAqB,GAC3B,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,oBAAoB,EAAE,KAAK,gBAAgB,EAAE,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACnG,OAAO,EACL,wBAAwB,EACxB,uBAAuB,EACvB,cAAc,EACd,KAAK,+BAA+B,GACrC,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,0BAA0B,EAC/B,KAAK,mBAAmB,EACxB,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,aAAa,EAClB,KAAK,sBAAsB,GAC5B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,sBAAsB,EACtB,eAAe,EACf,oBAAoB,EACpB,YAAY,EACZ,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC1B,KAAK,6BAA6B,GACnC,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,cAAc,EAAE,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACpE,OAAO,EACL,UAAU,EACV,aAAa,EACb,QAAQ,EACR,UAAU,EACV,KAAK,mBAAmB,GACzB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,QAAQ,EAAE,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAoBhE;;;;;;GAMG;AACH,wBAAgB,KAAK,CACnB,GAAG,EAAE,OAAO,EACZ,SAAS,EAAE,cAAc,GAAG,kBAAkB,EAC9C,IAAI,GAAE,mBAAwB,GAC7B,IAAI,CAoFN"}
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @krischoichoi/channel-dingtalk — DingTalk channel adapter for DeepSeek Harness.
|
|
3
|
+
*
|
|
4
|
+
* Maps the DingTalk platform to the stable Channel Contract. Two upstream
|
|
5
|
+
* drivers implement `DingTalkUpstream` behind `config.upstream.mode`:
|
|
6
|
+
* - 'sdk' — inbound via the official `dingtalk-stream` SDK (WebSocket
|
|
7
|
+
* stream mode); outbound via `sessionWebhook` and the AI Card OpenAPI.
|
|
8
|
+
* - 'gateway' — self-hosted HTTP gateway long-poll driver (legacy).
|
|
9
|
+
*
|
|
10
|
+
* Credential model: the DingTalk AppSecret never lives in config. Config only
|
|
11
|
+
* carries its credential reference (`upstream.clientSecretRef`, default
|
|
12
|
+
* `DSH_CHANNEL_DINGTALK_MAIN_CLIENT_SECRET`); the value is resolved via
|
|
13
|
+
* `ctx.credentials` and injected as `deps.clientSecret`. A legacy plaintext
|
|
14
|
+
* `upstream.clientSecret` is migrated to the credentials seam ONCE on startup
|
|
15
|
+
* and deleted. Streaming is `edit` (AI Card): create card → update with
|
|
16
|
+
* content → finalize → failure card. Auth is connection-state driven — the
|
|
17
|
+
* driver owns platform credentials (never logged).
|
|
18
|
+
*/
|
|
19
|
+
import {} from '@deepseek-ai/cordis';
|
|
20
|
+
import { credentialRef } from '@deepseek-ai/dsh-credentials';
|
|
21
|
+
import { mountChannelAdapter, resolveVolatileConfig, resolveVolatileValue } from '@krischoichoi/channel-core';
|
|
22
|
+
import { Config, DINGTALK_CLIENT_SECRET_REF } from './config.js';
|
|
23
|
+
import { DingTalkAdapter } from './adapter.js';
|
|
24
|
+
import { createDingTalkDefinition } from './definition.js';
|
|
25
|
+
export const name = 'channel-dingtalk';
|
|
26
|
+
export const inject = ['channels', 'credentials'];
|
|
27
|
+
export { Config, DINGTALK_CLIENT_SECRET_REF };
|
|
28
|
+
export { createDingTalkDefinition } from './definition.js';
|
|
29
|
+
export { beginDingTalkDeviceAuth, pollDingTalkDeviceAuth, } from './auth/device-registration.js';
|
|
30
|
+
export { DingTalkAdapter } from './adapter.js';
|
|
31
|
+
export { DingTalkCardReply } from './ai-card.js';
|
|
32
|
+
export { InboundProcessor } from './inbound.js';
|
|
33
|
+
export { IMAGE_IDLE_TIMEOUT_MS, IMAGE_MAX_BYTES, FILE_MAX_BYTES, MEDIA_MAX_BYTES, defaultSecureFetcher, findFetchableImages, hydrateFiles, hydrateImages, hydrateMedia, } from './image-hydrator.js';
|
|
34
|
+
export { OutboundSender, } from './outbound.js';
|
|
35
|
+
export { HttpDingTalkUpstream } from './upstream.js';
|
|
36
|
+
export { DingTalkOfficialUpstream, DingTalkOpenApiPortImpl, OFFICIAL_BASIS, } from './official-upstream.js';
|
|
37
|
+
export {} from './openapi-port.js';
|
|
38
|
+
export { DingTalkStreamUpstream, ackRobotMessage, toCardInteractionRaw, toGatewayRaw, } from './stream-upstream.js';
|
|
39
|
+
export { FetchTransport } from './transport.js';
|
|
40
|
+
export { mapInbound, toTextPayload, dedupKey, simpleHash, } from './mapper.js';
|
|
41
|
+
export { manifest } from './manifest.js';
|
|
42
|
+
/**
|
|
43
|
+
* Entry plugin apply. Accepts both the activation-time live shape (volatile
|
|
44
|
+
* fields resolve to `Volatile<T>` handles under Harness 0.2.0) and a plain
|
|
45
|
+
* snapshot (tests / programmatic mounting); plain fields pass through
|
|
46
|
+
* `resolveVolatileConfig` untouched. The shape is validated by Schemastery at
|
|
47
|
+
* the Loader boundary via the attached `Config` schema.
|
|
48
|
+
*/
|
|
49
|
+
export function apply(ctx, rawConfig, deps = {}) {
|
|
50
|
+
const config = resolveVolatileConfig(rawConfig);
|
|
51
|
+
// --- One-time legacy plaintext -> credential-reference migration (§52 Task 4).
|
|
52
|
+
// The value is written to the credentials seam exactly once, then the
|
|
53
|
+
// plaintext is deleted from the in-memory config. Never double-written.
|
|
54
|
+
const legacySecret = config.upstream.clientSecret;
|
|
55
|
+
const ref = config.upstream.clientSecretRef ?? DINGTALK_CLIENT_SECRET_REF;
|
|
56
|
+
if (typeof legacySecret === 'string' && legacySecret.length > 0) {
|
|
57
|
+
void ctx.credentials.set(credentialRef(ref), legacySecret);
|
|
58
|
+
// Strip the plaintext from BOTH the unwrapped snapshot and the caller's
|
|
59
|
+
// raw config so it is never read or written again. Schemastery 3.18
|
|
60
|
+
// freezes parsed sub-objects, so swap in a fresh shallow copy; the raw
|
|
61
|
+
// config top level may be frozen too (host-provided), hence the guard.
|
|
62
|
+
const stripped = { ...config.upstream, clientSecret: undefined };
|
|
63
|
+
config.upstream = stripped;
|
|
64
|
+
try {
|
|
65
|
+
rawConfig.upstream = stripped;
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// Frozen live config: the swap above already sanitized our snapshot.
|
|
69
|
+
}
|
|
70
|
+
ctx.logger('channel-dingtalk').info('migrated legacy dingtalk clientSecret to credentials');
|
|
71
|
+
}
|
|
72
|
+
const control = ctx.get('channelControl');
|
|
73
|
+
if (control) {
|
|
74
|
+
// The control plane owns lifecycle: register EVEN when disabled so the Web
|
|
75
|
+
// control plane can re-enable the channel later (doc §19/§20); runtime
|
|
76
|
+
// auto-starts.
|
|
77
|
+
const settings = ctx.get('settings');
|
|
78
|
+
control.definitions.register(createDingTalkDefinition({
|
|
79
|
+
config,
|
|
80
|
+
deps,
|
|
81
|
+
credentials: ctx.credentials,
|
|
82
|
+
persistSetup: (patch) => settings?.update('channels-dingtalk', patch) ?? Promise.resolve(),
|
|
83
|
+
persistEnabled: (enabled) => settings?.update('channels-dingtalk', { enabled }) ?? Promise.resolve(),
|
|
84
|
+
onAuthCompleted: control.runtime
|
|
85
|
+
? async () => {
|
|
86
|
+
try {
|
|
87
|
+
await control.runtime.restart('dingtalk', config.accountId);
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
// Provider authorization is complete even if adapter startup
|
|
91
|
+
// needs attention; keep the public auth session terminal and
|
|
92
|
+
// expose the startup failure through runtime status.
|
|
93
|
+
ctx.logger('channel-dingtalk').error('dingtalk credentials saved but runtime restart failed', error);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
: undefined,
|
|
97
|
+
}));
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
// --- Legacy standalone mount (channel-control absent). No directory/control
|
|
101
|
+
// surface exists to re-enable a disabled channel, so the config `enabled`
|
|
102
|
+
// gate still applies (doc §20).
|
|
103
|
+
if (!resolveVolatileValue(config.enabled))
|
|
104
|
+
return;
|
|
105
|
+
const credentialsCtx = ctx;
|
|
106
|
+
ctx.effect(async () => {
|
|
107
|
+
// SDK mode requires a resolved credential; gateway mode owns its own.
|
|
108
|
+
let clientSecret;
|
|
109
|
+
if (config.upstream.mode === 'sdk') {
|
|
110
|
+
const resolved = await credentialsCtx.credentials.resolve(credentialRef(ref));
|
|
111
|
+
clientSecret = resolved?.value;
|
|
112
|
+
if (!clientSecret) {
|
|
113
|
+
ctx.logger('channel-dingtalk').warn(`dingtalk upstream mode "sdk" credential "${ref}" is not configured; not mounting`);
|
|
114
|
+
return () => { };
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
mountChannelAdapter(ctx, new DingTalkAdapter(config, clientSecret ? { ...deps, clientSecret } : deps), (signal) => ctx.channels.createAdapterContext({ channelId: 'dingtalk', signal }));
|
|
118
|
+
// The mount owns the adapter lifecycle; this outer effect only scopes the
|
|
119
|
+
// async credential resolution, so its disposer is a no-op.
|
|
120
|
+
return () => { };
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=index.js.map
|