@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
package/lib/ai-card.d.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DingTalk AI Card — the streaming reply (`ReplyHandle`).
|
|
3
|
+
*
|
|
4
|
+
* DingTalk streaming is modeled as: create card → update card with content
|
|
5
|
+
* (status 'update') → finalize card (status 'finished') → failure card
|
|
6
|
+
* (status 'failed'). This maps directly onto the reply contract:
|
|
7
|
+
* - `append(delta)` / `replace(message)` → update the card body
|
|
8
|
+
* - `finish()` → finalize the card
|
|
9
|
+
* - `fail(error)` → mark the card failed
|
|
10
|
+
*
|
|
11
|
+
* The handle does NOT throttle internally — ReplyRouter owns throttling via
|
|
12
|
+
* config `reply.updateIntervalMs`. It guards no-op updates (skip when the text
|
|
13
|
+
* is unchanged) and serializes card operations so concurrent router flushes
|
|
14
|
+
* cannot interleave. State is observable for tests: `status`, `cardId`,
|
|
15
|
+
* `text`, `error` and an `updates` record of applied operations.
|
|
16
|
+
*/
|
|
17
|
+
import type { ChannelLogger, ChannelTarget, OutboundMessage, ReplyHandle } from '@krischoichoi/channel-core';
|
|
18
|
+
import type { DingTalkUpstream } from './upstream.js';
|
|
19
|
+
export type DingTalkCardStatus = 'idle' | 'active' | 'finished' | 'failed';
|
|
20
|
+
/** One applied card operation (observable test surface). */
|
|
21
|
+
export interface DingTalkCardUpdate {
|
|
22
|
+
kind: 'created' | 'update' | 'finished' | 'failed';
|
|
23
|
+
/** Card text at the time of the operation. */
|
|
24
|
+
text?: string;
|
|
25
|
+
/** Clock timestamp (injectable). */
|
|
26
|
+
at: number;
|
|
27
|
+
/** Error detail when the operation itself failed. */
|
|
28
|
+
error?: string;
|
|
29
|
+
}
|
|
30
|
+
export interface DingTalkCardReplyOptions {
|
|
31
|
+
upstream: DingTalkUpstream;
|
|
32
|
+
/** Conversation the card is attached to. */
|
|
33
|
+
target: ChannelTarget;
|
|
34
|
+
logger: ChannelLogger;
|
|
35
|
+
/** Create the card on the first delta; otherwise only at `finish`. */
|
|
36
|
+
createOnFirstDelta: boolean;
|
|
37
|
+
/** Injectable clock (tests). */
|
|
38
|
+
now?: () => number;
|
|
39
|
+
}
|
|
40
|
+
export declare class DingTalkCardReply implements ReplyHandle {
|
|
41
|
+
private readonly options;
|
|
42
|
+
status: DingTalkCardStatus;
|
|
43
|
+
cardId?: string;
|
|
44
|
+
text: string;
|
|
45
|
+
/** Original failure value passed to `fail()`, if any. */
|
|
46
|
+
error?: unknown;
|
|
47
|
+
/** Ordered record of applied card operations. */
|
|
48
|
+
readonly updates: DingTalkCardUpdate[];
|
|
49
|
+
private readonly now;
|
|
50
|
+
private queue;
|
|
51
|
+
/** Card API/template failures fall back to one final sessionWebhook text reply. */
|
|
52
|
+
private cardUnavailable;
|
|
53
|
+
constructor(options: DingTalkCardReplyOptions);
|
|
54
|
+
append(delta: string): Promise<void>;
|
|
55
|
+
replace(message: OutboundMessage): Promise<void>;
|
|
56
|
+
finish(message?: OutboundMessage): Promise<void>;
|
|
57
|
+
fail(error: unknown): Promise<void>;
|
|
58
|
+
private get finalized();
|
|
59
|
+
private push;
|
|
60
|
+
private createCard;
|
|
61
|
+
private record;
|
|
62
|
+
private enqueue;
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=ai-card.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-card.d.ts","sourceRoot":"","sources":["../src/ai-card.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EACV,aAAa,EACb,aAAa,EACb,eAAe,EACf,WAAW,EACZ,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEtD,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,QAAQ,GAAG,UAAU,GAAG,QAAQ,CAAC;AAE3E,4DAA4D;AAC5D,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,SAAS,GAAG,QAAQ,GAAG,UAAU,GAAG,QAAQ,CAAC;IACnD,8CAA8C;IAC9C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,oCAAoC;IACpC,EAAE,EAAE,MAAM,CAAC;IACX,qDAAqD;IACrD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,4CAA4C;IAC5C,MAAM,EAAE,aAAa,CAAC;IACtB,MAAM,EAAE,aAAa,CAAC;IACtB,sEAAsE;IACtE,kBAAkB,EAAE,OAAO,CAAC;IAC5B,gCAAgC;IAChC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,qBAAa,iBAAkB,YAAW,WAAW;IAcvC,OAAO,CAAC,QAAQ,CAAC,OAAO;IAbpC,MAAM,EAAE,kBAAkB,CAAU;IACpC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,SAAM;IACV,yDAAyD;IACzD,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,iDAAiD;IACjD,QAAQ,CAAC,OAAO,EAAE,kBAAkB,EAAE,CAAM;IAE5C,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,KAAK,CAAoC;IACjD,mFAAmF;IACnF,OAAO,CAAC,eAAe,CAAS;gBAEH,OAAO,EAAE,wBAAwB;IAI9D,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IASpC,OAAO,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IAWhD,MAAM,CAAC,OAAO,CAAC,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IA+BhD,IAAI,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAoBnC,OAAO,KAAK,SAAS,GAEpB;YAEa,IAAI;YAaJ,UAAU;IAgBxB,OAAO,CAAC,MAAM;IASd,OAAO,CAAC,OAAO;CAQhB"}
|
package/lib/ai-card.js
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
export class DingTalkCardReply {
|
|
2
|
+
options;
|
|
3
|
+
status = 'idle';
|
|
4
|
+
cardId;
|
|
5
|
+
text = '';
|
|
6
|
+
/** Original failure value passed to `fail()`, if any. */
|
|
7
|
+
error;
|
|
8
|
+
/** Ordered record of applied card operations. */
|
|
9
|
+
updates = [];
|
|
10
|
+
now;
|
|
11
|
+
queue = Promise.resolve();
|
|
12
|
+
/** Card API/template failures fall back to one final sessionWebhook text reply. */
|
|
13
|
+
cardUnavailable = false;
|
|
14
|
+
constructor(options) {
|
|
15
|
+
this.options = options;
|
|
16
|
+
this.now = options.now ?? Date.now;
|
|
17
|
+
}
|
|
18
|
+
append(delta) {
|
|
19
|
+
return this.enqueue(async () => {
|
|
20
|
+
if (this.finalized)
|
|
21
|
+
return;
|
|
22
|
+
if (!delta)
|
|
23
|
+
return;
|
|
24
|
+
this.text += delta;
|
|
25
|
+
await this.push();
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
replace(message) {
|
|
29
|
+
return this.enqueue(async () => {
|
|
30
|
+
if (this.finalized)
|
|
31
|
+
return;
|
|
32
|
+
if (message.text === undefined)
|
|
33
|
+
return;
|
|
34
|
+
// No-op guard: skip the network round-trip when nothing changed.
|
|
35
|
+
if (message.text === this.text)
|
|
36
|
+
return;
|
|
37
|
+
this.text = message.text;
|
|
38
|
+
await this.push();
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
finish(message) {
|
|
42
|
+
return this.enqueue(async () => {
|
|
43
|
+
if (this.finalized)
|
|
44
|
+
return;
|
|
45
|
+
const finalText = message?.text;
|
|
46
|
+
let needsSync = false;
|
|
47
|
+
if (finalText !== undefined && finalText !== this.text) {
|
|
48
|
+
this.text = finalText;
|
|
49
|
+
needsSync = true;
|
|
50
|
+
}
|
|
51
|
+
if (!this.cardId) {
|
|
52
|
+
// Nothing was streamed eagerly: create the final card now, or a card
|
|
53
|
+
// with no content when there is nothing to show.
|
|
54
|
+
if (this.text)
|
|
55
|
+
await this.createCard();
|
|
56
|
+
}
|
|
57
|
+
else if (needsSync) {
|
|
58
|
+
await this.options.upstream.updateCard(this.cardId, this.text);
|
|
59
|
+
this.record('update', this.text);
|
|
60
|
+
}
|
|
61
|
+
if (this.cardUnavailable && this.text) {
|
|
62
|
+
await this.options.upstream.sendText(this.options.target, this.text);
|
|
63
|
+
this.record('update', this.text);
|
|
64
|
+
}
|
|
65
|
+
this.status = 'finished';
|
|
66
|
+
if (this.cardId) {
|
|
67
|
+
await this.options.upstream.finishCard(this.cardId, this.text);
|
|
68
|
+
this.record('finished', this.text);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
this.record('finished', undefined);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
fail(error) {
|
|
76
|
+
return this.enqueue(async () => {
|
|
77
|
+
if (this.finalized)
|
|
78
|
+
return;
|
|
79
|
+
this.error = error;
|
|
80
|
+
this.status = 'failed';
|
|
81
|
+
if (this.cardId) {
|
|
82
|
+
try {
|
|
83
|
+
await this.options.upstream.failCard(this.cardId, messageOf(error));
|
|
84
|
+
this.record('failed', this.text);
|
|
85
|
+
}
|
|
86
|
+
catch (secondary) {
|
|
87
|
+
// Marking the card failed must not mask the original error.
|
|
88
|
+
this.options.logger.error('[channel-dingtalk] failed to mark card failed', secondary);
|
|
89
|
+
this.record('failed', this.text, secondary);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
this.record('failed', this.text);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
get finalized() {
|
|
98
|
+
return this.status === 'finished' || this.status === 'failed';
|
|
99
|
+
}
|
|
100
|
+
async push() {
|
|
101
|
+
if (this.cardId) {
|
|
102
|
+
await this.options.upstream.updateCard(this.cardId, this.text);
|
|
103
|
+
this.record('update', this.text);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (!this.options.createOnFirstDelta)
|
|
107
|
+
return; // buffer until finish
|
|
108
|
+
await this.createCard();
|
|
109
|
+
if (this.cardUnavailable)
|
|
110
|
+
return;
|
|
111
|
+
await this.options.upstream.updateCard(this.cardId, this.text);
|
|
112
|
+
this.record('update', this.text);
|
|
113
|
+
}
|
|
114
|
+
async createCard() {
|
|
115
|
+
if (this.cardUnavailable)
|
|
116
|
+
return;
|
|
117
|
+
try {
|
|
118
|
+
const result = await this.options.upstream.createCard(this.options.target, this.text);
|
|
119
|
+
this.cardId = result.cardId;
|
|
120
|
+
this.status = 'active';
|
|
121
|
+
this.record('created', this.text);
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
// A card may be disabled for an otherwise valid bot. The session webhook
|
|
125
|
+
// can still deliver the completed answer, so defer that fallback to
|
|
126
|
+
// finish() rather than losing the whole Harness reply.
|
|
127
|
+
this.cardUnavailable = true;
|
|
128
|
+
this.options.logger.warn('[channel-dingtalk] AI Card unavailable; falling back to text', error);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
record(kind, text, error) {
|
|
132
|
+
this.updates.push({
|
|
133
|
+
kind,
|
|
134
|
+
text,
|
|
135
|
+
at: this.now(),
|
|
136
|
+
error: error !== undefined ? String(error) : undefined,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
enqueue(task) {
|
|
140
|
+
const next = this.queue.then(task, task);
|
|
141
|
+
this.queue = next.then(() => undefined, () => undefined);
|
|
142
|
+
return next;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
function messageOf(error) {
|
|
146
|
+
if (error instanceof Error)
|
|
147
|
+
return error.message;
|
|
148
|
+
return String(error);
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=ai-card.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-card.js","sourceRoot":"","sources":["../src/ai-card.ts"],"names":[],"mappings":"AAgDA,MAAM,OAAO,iBAAiB;IAcC;IAb7B,MAAM,GAAuB,MAAM,CAAC;IACpC,MAAM,CAAU;IAChB,IAAI,GAAG,EAAE,CAAC;IACV,yDAAyD;IACzD,KAAK,CAAW;IAChB,iDAAiD;IACxC,OAAO,GAAyB,EAAE,CAAC;IAE3B,GAAG,CAAe;IAC3B,KAAK,GAAkB,OAAO,CAAC,OAAO,EAAE,CAAC;IACjD,mFAAmF;IAC3E,eAAe,GAAG,KAAK,CAAC;IAEhC,YAA6B,OAAiC;QAAjC,YAAO,GAAP,OAAO,CAA0B;QAC5D,IAAI,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC;IACrC,CAAC;IAED,MAAM,CAAC,KAAa;QAClB,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE;YAC7B,IAAI,IAAI,CAAC,SAAS;gBAAE,OAAO;YAC3B,IAAI,CAAC,KAAK;gBAAE,OAAO;YACnB,IAAI,CAAC,IAAI,IAAI,KAAK,CAAC;YACnB,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QACpB,CAAC,CAAC,CAAC;IACL,CAAC;IAED,OAAO,CAAC,OAAwB;QAC9B,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE;YAC7B,IAAI,IAAI,CAAC,SAAS;gBAAE,OAAO;YAC3B,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS;gBAAE,OAAO;YACvC,iEAAiE;YACjE,IAAI,OAAO,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI;gBAAE,OAAO;YACvC,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;YACzB,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QACpB,CAAC,CAAC,CAAC;IACL,CAAC;IAED,MAAM,CAAC,OAAyB;QAC9B,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE;YAC7B,IAAI,IAAI,CAAC,SAAS;gBAAE,OAAO;YAC3B,MAAM,SAAS,GAAG,OAAO,EAAE,IAAI,CAAC;YAChC,IAAI,SAAS,GAAG,KAAK,CAAC;YACtB,IAAI,SAAS,KAAK,SAAS,IAAI,SAAS,KAAK,IAAI,CAAC,IAAI,EAAE,CAAC;gBACvD,IAAI,CAAC,IAAI,GAAG,SAAS,CAAC;gBACtB,SAAS,GAAG,IAAI,CAAC;YACnB,CAAC;YACD,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;gBACjB,qEAAqE;gBACrE,iDAAiD;gBACjD,IAAI,IAAI,CAAC,IAAI;oBAAE,MAAM,IAAI,CAAC,UAAU,EAAE,CAAC;YACzC,CAAC;iBAAM,IAAI,SAAS,EAAE,CAAC;gBACrB,MAAM,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;gBAC/D,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;YACnC,CAAC;YACD,IAAI,IAAI,CAAC,eAAe,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;gBACtC,MAAM,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;gBACrE,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;YACnC,CAAC;YACD,IAAI,CAAC,MAAM,GAAG,UAAU,CAAC;YACzB,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;gBAChB,MAAM,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;gBAC/D,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;YACrC,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;YACrC,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAED,IAAI,CAAC,KAAc;QACjB,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE;YAC7B,IAAI,IAAI,CAAC,SAAS;gBAAE,OAAO;YAC3B,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;YACnB,IAAI,CAAC,MAAM,GAAG,QAAQ,CAAC;YACvB,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;gBAChB,IAAI,CAAC;oBACH,MAAM,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,EAAE,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;oBACpE,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;gBACnC,CAAC;gBAAC,OAAO,SAAS,EAAE,CAAC;oBACnB,4DAA4D;oBAC5D,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,+CAA+C,EAAE,SAAS,CAAC,CAAC;oBACtF,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;gBAC9C,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;YACnC,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAED,IAAY,SAAS;QACnB,OAAO,IAAI,CAAC,MAAM,KAAK,UAAU,IAAI,IAAI,CAAC,MAAM,KAAK,QAAQ,CAAC;IAChE,CAAC;IAEO,KAAK,CAAC,IAAI;QAChB,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAChB,MAAM,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;YAC/D,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;YACjC,OAAO;QACT,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,kBAAkB;YAAE,OAAO,CAAC,sBAAsB;QACpE,MAAM,IAAI,CAAC,UAAU,EAAE,CAAC;QACxB,IAAI,IAAI,CAAC,eAAe;YAAE,OAAO;QACjC,MAAM,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,MAAO,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QAChE,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;IACnC,CAAC;IAEO,KAAK,CAAC,UAAU;QACtB,IAAI,IAAI,CAAC,eAAe;YAAE,OAAO;QACjC,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;YACtF,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;YAC5B,IAAI,CAAC,MAAM,GAAG,QAAQ,CAAC;YACvB,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;QACpC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,yEAAyE;YACzE,oEAAoE;YACpE,uDAAuD;YACvD,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC;YAC5B,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,8DAA8D,EAAE,KAAK,CAAC,CAAC;QAClG,CAAC;IACH,CAAC;IAEO,MAAM,CAAC,IAAgC,EAAE,IAAa,EAAE,KAAe;QAC7E,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;YAChB,IAAI;YACJ,IAAI;YACJ,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE;YACd,KAAK,EAAE,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS;SACvD,CAAC,CAAC;IACL,CAAC;IAEO,OAAO,CAAC,IAAyB;QACvC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QACzC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,IAAI,CACpB,GAAG,EAAE,CAAC,SAAS,EACf,GAAG,EAAE,CAAC,SAAS,CAChB,CAAC;QACF,OAAO,IAAI,CAAC;IACd,CAAC;CACF;AAED,SAAS,SAAS,CAAC,KAAc;IAC/B,IAAI,KAAK,YAAY,KAAK;QAAE,OAAO,KAAK,CAAC,OAAO,CAAC;IACjD,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;AACvB,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type AuthProviderSession, type PublicAuthStatus } from '@krischoichoi/channel-control';
|
|
2
|
+
export interface DingTalkDeviceAuthOptions {
|
|
3
|
+
baseUrl?: string;
|
|
4
|
+
source?: string;
|
|
5
|
+
fetcher?: typeof fetch;
|
|
6
|
+
}
|
|
7
|
+
export interface DingTalkDevicePollResult {
|
|
8
|
+
status: PublicAuthStatus;
|
|
9
|
+
credentials?: {
|
|
10
|
+
clientId: string;
|
|
11
|
+
clientSecret: string;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
/** Start DingTalk's device registration flow and return only browser-safe data. */
|
|
15
|
+
export declare function beginDingTalkDeviceAuth(options?: DingTalkDeviceAuthOptions): Promise<AuthProviderSession>;
|
|
16
|
+
/** Poll DingTalk registration; credentials are returned only to the host definition. */
|
|
17
|
+
export declare function pollDingTalkDeviceAuth(session: AuthProviderSession, options?: DingTalkDeviceAuthOptions): Promise<DingTalkDevicePollResult>;
|
|
18
|
+
//# sourceMappingURL=device-registration.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device-registration.d.ts","sourceRoot":"","sources":["../../src/auth/device-registration.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,mBAAmB,EAAE,KAAK,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AA8B9G,MAAM,WAAW,yBAAyB;IACxC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;CACxB;AAOD,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,gBAAgB,CAAC;IACzB,WAAW,CAAC,EAAE;QACZ,QAAQ,EAAE,MAAM,CAAC;QACjB,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC;CACH;AAKD,mFAAmF;AACnF,wBAAsB,uBAAuB,CAC3C,OAAO,GAAE,yBAA8B,GACtC,OAAO,CAAC,mBAAmB,CAAC,CA0B9B;AAED,wFAAwF;AACxF,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,mBAAmB,EAC5B,OAAO,GAAE,yBAA8B,GACtC,OAAO,CAAC,wBAAwB,CAAC,CAoDnC"}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { ControlError } from '@krischoichoi/channel-control';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
const RegistrationEnvelopeSchema = z.object({
|
|
4
|
+
errcode: z.union([z.number(), z.string()]).optional(),
|
|
5
|
+
errmsg: z.string().optional(),
|
|
6
|
+
}).passthrough();
|
|
7
|
+
const InitResponseSchema = RegistrationEnvelopeSchema.extend({
|
|
8
|
+
nonce: z.string().trim().min(1),
|
|
9
|
+
});
|
|
10
|
+
const BeginResponseSchema = RegistrationEnvelopeSchema.extend({
|
|
11
|
+
device_code: z.string().trim().min(1),
|
|
12
|
+
verification_uri_complete: z.string().trim().min(1),
|
|
13
|
+
expires_in: z.number().positive().optional(),
|
|
14
|
+
interval: z.number().positive().optional(),
|
|
15
|
+
});
|
|
16
|
+
const PollResponseSchema = RegistrationEnvelopeSchema.extend({
|
|
17
|
+
status: z.string().trim().min(1).optional(),
|
|
18
|
+
client_id: z.string().trim().min(1).optional(),
|
|
19
|
+
client_secret: z.string().trim().min(1).optional(),
|
|
20
|
+
fail_reason: z.string().optional(),
|
|
21
|
+
});
|
|
22
|
+
const DEFAULT_BASE_URL = 'https://oapi.dingtalk.com';
|
|
23
|
+
const DEFAULT_SOURCE = 'DING_DWS_CLAW';
|
|
24
|
+
/** Start DingTalk's device registration flow and return only browser-safe data. */
|
|
25
|
+
export async function beginDingTalkDeviceAuth(options = {}) {
|
|
26
|
+
const fetcher = options.fetcher ?? fetch;
|
|
27
|
+
const baseUrl = normalizeBaseUrl(options.baseUrl ?? DEFAULT_BASE_URL);
|
|
28
|
+
const source = options.source ?? DEFAULT_SOURCE;
|
|
29
|
+
const init = await postRegistration(fetcher, `${baseUrl}/app/registration/init`, { source }, InitResponseSchema);
|
|
30
|
+
const begin = await postRegistration(fetcher, `${baseUrl}/app/registration/begin`, { nonce: init.nonce }, BeginResponseSchema);
|
|
31
|
+
const expiresAt = Date.now() + (begin.expires_in ?? 7200) * 1000;
|
|
32
|
+
return {
|
|
33
|
+
provider: 'dingtalk-registration',
|
|
34
|
+
expiresAt,
|
|
35
|
+
pollingIntervalMs: Math.max(3000, (begin.interval ?? 3) * 1000),
|
|
36
|
+
qr: { kind: 'content', value: begin.verification_uri_complete, expiresAt },
|
|
37
|
+
prompt: { kind: 'confirm-on-phone', message: '请使用手机钉钉扫码并确认授权' },
|
|
38
|
+
deviceCode: begin.device_code,
|
|
39
|
+
providerState: { baseUrl, deviceCode: begin.device_code },
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/** Poll DingTalk registration; credentials are returned only to the host definition. */
|
|
43
|
+
export async function pollDingTalkDeviceAuth(session, options = {}) {
|
|
44
|
+
const providerState = parseProviderState(session.providerState);
|
|
45
|
+
const fetcher = options.fetcher ?? fetch;
|
|
46
|
+
const payload = await postRegistration(fetcher, `${providerState.baseUrl}/app/registration/poll`, { device_code: providerState.deviceCode }, PollResponseSchema);
|
|
47
|
+
const status = (payload.status ?? 'WAITING').toUpperCase();
|
|
48
|
+
if (status === 'WAITING') {
|
|
49
|
+
return {
|
|
50
|
+
status: {
|
|
51
|
+
state: 'pending',
|
|
52
|
+
phase: 'waiting-scan',
|
|
53
|
+
expiresAt: session.expiresAt,
|
|
54
|
+
detail: '等待钉钉扫码授权中',
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
if (status === 'SUCCESS') {
|
|
59
|
+
if (!payload.client_id || !payload.client_secret) {
|
|
60
|
+
throw new ControlError('CONTROL_ERROR', 'dingtalk authorization did not return complete credentials');
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
status: {
|
|
64
|
+
state: 'authenticated',
|
|
65
|
+
phase: 'authorized',
|
|
66
|
+
expiresAt: session.expiresAt,
|
|
67
|
+
detail: '钉钉扫码授权已完成,凭证已保存',
|
|
68
|
+
},
|
|
69
|
+
credentials: { clientId: payload.client_id, clientSecret: payload.client_secret },
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
if (status === 'FAIL' || status === 'EXPIRED') {
|
|
73
|
+
return {
|
|
74
|
+
status: {
|
|
75
|
+
state: 'expired',
|
|
76
|
+
phase: 'expired',
|
|
77
|
+
expiresAt: session.expiresAt,
|
|
78
|
+
detail: payload.fail_reason ?? payload.errmsg ?? '二维码已失效',
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
status: {
|
|
84
|
+
state: 'pending',
|
|
85
|
+
phase: 'waiting-scan',
|
|
86
|
+
expiresAt: session.expiresAt,
|
|
87
|
+
detail: `当前状态:${status}`,
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
async function postRegistration(fetcher, url, body, schema) {
|
|
92
|
+
let response;
|
|
93
|
+
try {
|
|
94
|
+
response = await fetcher(url, {
|
|
95
|
+
method: 'POST',
|
|
96
|
+
headers: { 'content-type': 'application/json' },
|
|
97
|
+
body: JSON.stringify(body),
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
throw new ControlError('CONTROL_ERROR', 'dingtalk authorization request failed', { cause: error });
|
|
102
|
+
}
|
|
103
|
+
let payload;
|
|
104
|
+
try {
|
|
105
|
+
payload = await response.json();
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
throw new ControlError('CONTROL_ERROR', 'dingtalk authorization returned invalid JSON', { cause: error });
|
|
109
|
+
}
|
|
110
|
+
const envelope = RegistrationEnvelopeSchema.safeParse(payload);
|
|
111
|
+
if (!envelope.success) {
|
|
112
|
+
throw new ControlError('CONTROL_ERROR', 'dingtalk authorization returned an invalid response');
|
|
113
|
+
}
|
|
114
|
+
const errcode = envelope.data.errcode;
|
|
115
|
+
if (!response.ok || (errcode !== undefined && String(errcode) !== '0')) {
|
|
116
|
+
throw new ControlError('CONTROL_ERROR', 'dingtalk authorization request failed');
|
|
117
|
+
}
|
|
118
|
+
const parsed = schema.safeParse(payload);
|
|
119
|
+
if (!parsed.success) {
|
|
120
|
+
throw new ControlError('CONTROL_ERROR', 'dingtalk authorization returned an invalid response');
|
|
121
|
+
}
|
|
122
|
+
return parsed.data;
|
|
123
|
+
}
|
|
124
|
+
function parseProviderState(value) {
|
|
125
|
+
const parsed = z.object({
|
|
126
|
+
baseUrl: z.string().url(),
|
|
127
|
+
deviceCode: z.string().trim().min(1),
|
|
128
|
+
}).safeParse(value);
|
|
129
|
+
if (!parsed.success) {
|
|
130
|
+
throw new ControlError('CONTROL_ERROR', 'dingtalk auth session is missing device code');
|
|
131
|
+
}
|
|
132
|
+
return parsed.data;
|
|
133
|
+
}
|
|
134
|
+
function normalizeBaseUrl(value) {
|
|
135
|
+
const trimmed = value.trim().replace(/\/$/, '');
|
|
136
|
+
try {
|
|
137
|
+
const parsed = new URL(trimmed);
|
|
138
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
139
|
+
throw new Error('unsupported URL protocol');
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
throw new ControlError('CONTROL_ERROR', 'dingtalk registration base URL is invalid', { cause: error });
|
|
144
|
+
}
|
|
145
|
+
return trimmed;
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=device-registration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device-registration.js","sourceRoot":"","sources":["../../src/auth/device-registration.ts"],"names":[],"mappings":"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"}
|
package/lib/config.d.ts
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DingTalk adapter configuration (Schemastery).
|
|
3
|
+
*
|
|
4
|
+
* Every deployment-tunable parameter is configurable here — no hardcoded
|
|
5
|
+
* deployment constants. In 'gateway' mode platform credentials live inside the
|
|
6
|
+
* self-hosted HTTP gateway; in 'sdk' mode the DingTalk AppKey (clientId) is
|
|
7
|
+
* configured here as `upstream.clientId` while the AppSecret is resolved
|
|
8
|
+
* through `ctx.credentials` from the reference `upstream.clientSecretRef`
|
|
9
|
+
* (default `DSH_CHANNEL_DINGTALK_MAIN_CLIENT_SECRET`) and injected into the
|
|
10
|
+
* adapter as `deps.clientSecret`. Only the reference name — never the secret
|
|
11
|
+
* value — lives in config; credentials are never logged and never written into
|
|
12
|
+
* fixtures.
|
|
13
|
+
*
|
|
14
|
+
* A deprecated optional `upstream.clientSecret` field is kept so legacy
|
|
15
|
+
* plaintext configs still parse. It is migration-only: apply() writes its value
|
|
16
|
+
* into the credentials seam a single time and deletes it (see index.ts). It is
|
|
17
|
+
* never written and never read to build the SDK client.
|
|
18
|
+
*/
|
|
19
|
+
import Schema from '@deepseek-ai/schemastery';
|
|
20
|
+
import type { Volatile } from '@deepseek-ai/cordis';
|
|
21
|
+
/** Credential reference name for the DingTalk AppSecret (SDK mode, default). */
|
|
22
|
+
export declare const DINGTALK_CLIENT_SECRET_REF = "DSH_CHANNEL_DINGTALK_MAIN_CLIENT_SECRET";
|
|
23
|
+
export interface DingTalkReconnectConfig {
|
|
24
|
+
enabled: boolean;
|
|
25
|
+
baseDelayMs: number;
|
|
26
|
+
maxDelayMs: number;
|
|
27
|
+
maxRetries: number;
|
|
28
|
+
}
|
|
29
|
+
export interface DingTalkDedupConfig {
|
|
30
|
+
enabled: boolean;
|
|
31
|
+
windowMs: number;
|
|
32
|
+
}
|
|
33
|
+
export interface DingTalkCardConfig {
|
|
34
|
+
/**
|
|
35
|
+
* Create the AI Card on the first streamed delta (eager preview). When
|
|
36
|
+
* `false`, deltas buffer locally and the card is only created at `finish`.
|
|
37
|
+
*/
|
|
38
|
+
createOnFirstDelta: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* OPT-IN Card Platform template used for Harness interactive question
|
|
41
|
+
* actions. UNSET by default on purpose: a card template only renders buttons
|
|
42
|
+
* when it is published in YOUR organization's Card Platform and exposes the
|
|
43
|
+
* `text`/`actions` variables configured below, so advertising button support
|
|
44
|
+
* from a built-in third-party template id made every `ask_user_question`
|
|
45
|
+
* fail with "无法在当前渠道展示问题,已取消。". Without it the adapter stays
|
|
46
|
+
* `interactiveActions: false` and questions use the numbered-text reply path
|
|
47
|
+
* (same as Weixin).
|
|
48
|
+
*/
|
|
49
|
+
interactiveTemplateId?: string;
|
|
50
|
+
/** Template variable carrying the card body text. */
|
|
51
|
+
interactiveTextParam: string;
|
|
52
|
+
/** Template variable carrying serialized button definitions. */
|
|
53
|
+
interactiveActionsParam: string;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Vendor AI Card template used by the STREAMING reply path (`ai-card.ts`:
|
|
57
|
+
* create instance → deliver → streaming update). It is unrelated to the
|
|
58
|
+
* opt-in interactive-actions template above and is never used to claim
|
|
59
|
+
* `interactiveActions`.
|
|
60
|
+
*/
|
|
61
|
+
export declare const DEFAULT_DINGTALK_INTERACTIVE_TEMPLATE_ID = "02fcf2f4-5e02-4a85-b672-46d1f715543e.schema";
|
|
62
|
+
/**
|
|
63
|
+
* Upstream driver selection.
|
|
64
|
+
*
|
|
65
|
+
* - `'sdk'` — inbound via the official `dingtalk-stream` SDK (WebSocket
|
|
66
|
+
* stream mode); outbound uses the message-scoped
|
|
67
|
+
* `sessionWebhook` and DingTalk's official AI Card OpenAPI.
|
|
68
|
+
* - `'gateway'` — inbound via the self-hosted HTTP gateway long-poll driver
|
|
69
|
+
* (legacy protocol-level integration; outbound unchanged).
|
|
70
|
+
*/
|
|
71
|
+
export interface DingTalkUpstreamConfig {
|
|
72
|
+
mode: 'sdk' | 'gateway';
|
|
73
|
+
/** DingTalk AppKey (clientId) for SDK mode. Not a secret — may live in config. */
|
|
74
|
+
clientId?: string;
|
|
75
|
+
/**
|
|
76
|
+
* Credential reference name for the DingTalk AppSecret (SDK mode). Only the
|
|
77
|
+
* reference — never the real secret — is stored here; the value is resolved
|
|
78
|
+
* via `ctx.credentials` at startup and injected as `deps.clientSecret`.
|
|
79
|
+
* Defaults to `DSH_CHANNEL_DINGTALK_MAIN_CLIENT_SECRET`.
|
|
80
|
+
*/
|
|
81
|
+
clientSecretRef?: string;
|
|
82
|
+
}
|
|
83
|
+
export interface DingTalkConfig {
|
|
84
|
+
enabled: boolean;
|
|
85
|
+
/** Account id within the dingtalk channel (defaults to 'main'). */
|
|
86
|
+
accountId: string;
|
|
87
|
+
/**
|
|
88
|
+
* Base URL of the self-hosted HTTP upstream (legacy gateway mode only).
|
|
89
|
+
* SDK mode ignores this value and calls DingTalk's official endpoints.
|
|
90
|
+
*/
|
|
91
|
+
baseUrl: string;
|
|
92
|
+
/** Per-request timeout. */
|
|
93
|
+
timeoutMs: number;
|
|
94
|
+
/** Long-poll hang time for one receive cycle (gateway mode only). */
|
|
95
|
+
longPollTimeoutMs: number;
|
|
96
|
+
reconnect: DingTalkReconnectConfig;
|
|
97
|
+
dedup: DingTalkDedupConfig;
|
|
98
|
+
card: DingTalkCardConfig;
|
|
99
|
+
/** Upstream driver selection (official SDK vs self-hosted gateway). */
|
|
100
|
+
upstream: DingTalkUpstreamConfig;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Activation-time config shape (Harness 0.2.0): every volatile field resolves
|
|
104
|
+
* to a `Volatile<T>` live handle instead of a plain value. `apply()` unwraps
|
|
105
|
+
* these once via `resolveVolatileConfig` into a plain `DingTalkConfig`.
|
|
106
|
+
*/
|
|
107
|
+
export interface DingTalkConfigLive {
|
|
108
|
+
enabled: Volatile<boolean>;
|
|
109
|
+
accountId: Volatile<string>;
|
|
110
|
+
baseUrl: Volatile<string>;
|
|
111
|
+
timeoutMs: Volatile<number>;
|
|
112
|
+
longPollTimeoutMs: Volatile<number>;
|
|
113
|
+
reconnect: Volatile<DingTalkReconnectConfig>;
|
|
114
|
+
dedup: Volatile<DingTalkDedupConfig>;
|
|
115
|
+
card: Volatile<DingTalkCardConfig>;
|
|
116
|
+
upstream: Volatile<DingTalkUpstreamConfig>;
|
|
117
|
+
}
|
|
118
|
+
export declare const Config: Schema<DingTalkConfigLive>;
|
|
119
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,MAAM,MAAM,0BAA0B,CAAC;AAC9C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAEpD,gFAAgF;AAChF,eAAO,MAAM,0BAA0B,4CAA4C,CAAC;AAEpF,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,OAAO,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,kBAAkB;IACjC;;;OAGG;IACH,kBAAkB,EAAE,OAAO,CAAC;IAC5B;;;;;;;;;OASG;IACH,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,qDAAqD;IACrD,oBAAoB,EAAE,MAAM,CAAC;IAC7B,gEAAgE;IAChE,uBAAuB,EAAE,MAAM,CAAC;CACjC;AAED;;;;;GAKG;AACH,eAAO,MAAM,wCAAwC,gDAAgD,CAAC;AAEtG;;;;;;;;GAQG;AACH,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,KAAK,GAAG,SAAS,CAAC;IACxB,kFAAkF;IAClF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;OAKG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,mEAAmE;IACnE,SAAS,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB,2BAA2B;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,qEAAqE;IACrE,iBAAiB,EAAE,MAAM,CAAC;IAC1B,SAAS,EAAE,uBAAuB,CAAC;IACnC,KAAK,EAAE,mBAAmB,CAAC;IAC3B,IAAI,EAAE,kBAAkB,CAAC;IACzB,uEAAuE;IACvE,QAAQ,EAAE,sBAAsB,CAAC;CAClC;AAED;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,OAAO,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;IAC3B,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC5B,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC1B,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC5B,iBAAiB,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IACpC,SAAS,EAAE,QAAQ,CAAC,uBAAuB,CAAC,CAAC;IAC7C,KAAK,EAAE,QAAQ,CAAC,mBAAmB,CAAC,CAAC;IACrC,IAAI,EAAE,QAAQ,CAAC,kBAAkB,CAAC,CAAC;IACnC,QAAQ,EAAE,QAAQ,CAAC,sBAAsB,CAAC,CAAC;CAC5C;AAMD,eAAO,MAAM,MAAM,EAyCF,MAAM,CAAC,kBAAkB,CAAC,CAAC"}
|
package/lib/config.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DingTalk adapter configuration (Schemastery).
|
|
3
|
+
*
|
|
4
|
+
* Every deployment-tunable parameter is configurable here — no hardcoded
|
|
5
|
+
* deployment constants. In 'gateway' mode platform credentials live inside the
|
|
6
|
+
* self-hosted HTTP gateway; in 'sdk' mode the DingTalk AppKey (clientId) is
|
|
7
|
+
* configured here as `upstream.clientId` while the AppSecret is resolved
|
|
8
|
+
* through `ctx.credentials` from the reference `upstream.clientSecretRef`
|
|
9
|
+
* (default `DSH_CHANNEL_DINGTALK_MAIN_CLIENT_SECRET`) and injected into the
|
|
10
|
+
* adapter as `deps.clientSecret`. Only the reference name — never the secret
|
|
11
|
+
* value — lives in config; credentials are never logged and never written into
|
|
12
|
+
* fixtures.
|
|
13
|
+
*
|
|
14
|
+
* A deprecated optional `upstream.clientSecret` field is kept so legacy
|
|
15
|
+
* plaintext configs still parse. It is migration-only: apply() writes its value
|
|
16
|
+
* into the credentials seam a single time and deletes it (see index.ts). It is
|
|
17
|
+
* never written and never read to build the SDK client.
|
|
18
|
+
*/
|
|
19
|
+
import Schema from '@deepseek-ai/schemastery';
|
|
20
|
+
/** Credential reference name for the DingTalk AppSecret (SDK mode, default). */
|
|
21
|
+
export const DINGTALK_CLIENT_SECRET_REF = 'DSH_CHANNEL_DINGTALK_MAIN_CLIENT_SECRET';
|
|
22
|
+
/**
|
|
23
|
+
* Vendor AI Card template used by the STREAMING reply path (`ai-card.ts`:
|
|
24
|
+
* create instance → deliver → streaming update). It is unrelated to the
|
|
25
|
+
* opt-in interactive-actions template above and is never used to claim
|
|
26
|
+
* `interactiveActions`.
|
|
27
|
+
*/
|
|
28
|
+
export const DEFAULT_DINGTALK_INTERACTIVE_TEMPLATE_ID = '02fcf2f4-5e02-4a85-b672-46d1f715543e.schema';
|
|
29
|
+
// The runtime schema is genuine Schemastery (validation happens at the Loader
|
|
30
|
+
// boundary); the annotation records the activation-time output shape. The cast
|
|
31
|
+
// is required because Schemastery's inferred ObjectT models per-field optionality
|
|
32
|
+
// more precisely than the hand-written `DingTalkConfigLive` alias.
|
|
33
|
+
export const Config = Schema.object({
|
|
34
|
+
// Harness 0.2.0 settings model: fields the control plane / settings page may
|
|
35
|
+
// write at runtime are declared `.volatile()` — they surface on the
|
|
36
|
+
// auto-generated settings form, `ctx.settings.update(entryId, patch)` accepts
|
|
37
|
+
// exactly these paths, and at activation they resolve to `Volatile<T>`
|
|
38
|
+
// handles (unwrapped by `resolveVolatileConfig` in definition.ts / apply()).
|
|
39
|
+
enabled: Schema.boolean().default(true).volatile(),
|
|
40
|
+
accountId: Schema.string().default('main').volatile(),
|
|
41
|
+
baseUrl: Schema.string().default('http://127.0.0.1:9100').volatile(),
|
|
42
|
+
timeoutMs: Schema.natural().default(30000).volatile(),
|
|
43
|
+
longPollTimeoutMs: Schema.natural().default(25000).volatile(),
|
|
44
|
+
reconnect: Schema.object({
|
|
45
|
+
enabled: Schema.boolean().default(true),
|
|
46
|
+
baseDelayMs: Schema.natural().default(1000),
|
|
47
|
+
maxDelayMs: Schema.natural().default(30000),
|
|
48
|
+
maxRetries: Schema.natural().default(10),
|
|
49
|
+
}).default({}).volatile(),
|
|
50
|
+
dedup: Schema.object({
|
|
51
|
+
enabled: Schema.boolean().default(true),
|
|
52
|
+
windowMs: Schema.natural().default(5000),
|
|
53
|
+
}).default({}).volatile(),
|
|
54
|
+
card: Schema.object({
|
|
55
|
+
createOnFirstDelta: Schema.boolean().default(true),
|
|
56
|
+
// Deliberately NO default: question buttons require a card template that is
|
|
57
|
+
// published in the operator's own Card Platform. A built-in third-party id
|
|
58
|
+
// made `interactiveActions` true everywhere and broke every question.
|
|
59
|
+
interactiveTemplateId: Schema.string(),
|
|
60
|
+
interactiveTextParam: Schema.string().default('text'),
|
|
61
|
+
interactiveActionsParam: Schema.string().default('actions'),
|
|
62
|
+
}).default({}).volatile(),
|
|
63
|
+
upstream: Schema.object({
|
|
64
|
+
mode: Schema.union(['sdk', 'gateway']).default('sdk'),
|
|
65
|
+
// The AppKey (clientId) is not a secret and may live in config.
|
|
66
|
+
clientId: Schema.string(),
|
|
67
|
+
// Credential reference name — never the secret value itself.
|
|
68
|
+
clientSecretRef: Schema.string().default(DINGTALK_CLIENT_SECRET_REF),
|
|
69
|
+
// DEPRECATED migration-only legacy plaintext field: kept so old configs
|
|
70
|
+
// still parse. apply() migrates its value to credentials a single time and
|
|
71
|
+
// deletes it. Never written, never read to build the SDK client.
|
|
72
|
+
clientSecret: Schema.string().hidden(),
|
|
73
|
+
}).default({}).volatile(),
|
|
74
|
+
});
|
|
75
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|