@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.
Files changed (70) hide show
  1. package/README.md +139 -0
  2. package/lib/adapter.d.ts +123 -0
  3. package/lib/adapter.d.ts.map +1 -0
  4. package/lib/adapter.js +355 -0
  5. package/lib/adapter.js.map +1 -0
  6. package/lib/ai-card.d.ts +64 -0
  7. package/lib/ai-card.d.ts.map +1 -0
  8. package/lib/ai-card.js +150 -0
  9. package/lib/ai-card.js.map +1 -0
  10. package/lib/auth/device-registration.d.ts +18 -0
  11. package/lib/auth/device-registration.d.ts.map +1 -0
  12. package/lib/auth/device-registration.js +147 -0
  13. package/lib/auth/device-registration.js.map +1 -0
  14. package/lib/config.d.ts +119 -0
  15. package/lib/config.d.ts.map +1 -0
  16. package/lib/config.js +75 -0
  17. package/lib/config.js.map +1 -0
  18. package/lib/definition.d.ts +53 -0
  19. package/lib/definition.d.ts.map +1 -0
  20. package/lib/definition.js +227 -0
  21. package/lib/definition.js.map +1 -0
  22. package/lib/image-hydrator.d.ts +213 -0
  23. package/lib/image-hydrator.d.ts.map +1 -0
  24. package/lib/image-hydrator.js +244 -0
  25. package/lib/image-hydrator.js.map +1 -0
  26. package/lib/inbound.d.ts +38 -0
  27. package/lib/inbound.d.ts.map +1 -0
  28. package/lib/inbound.js +158 -0
  29. package/lib/inbound.js.map +1 -0
  30. package/lib/index.d.ts +49 -0
  31. package/lib/index.d.ts.map +1 -0
  32. package/lib/index.js +123 -0
  33. package/lib/index.js.map +1 -0
  34. package/lib/manifest.d.ts +23 -0
  35. package/lib/manifest.d.ts.map +1 -0
  36. package/lib/manifest.js +34 -0
  37. package/lib/manifest.js.map +1 -0
  38. package/lib/mapper.d.ts +36 -0
  39. package/lib/mapper.d.ts.map +1 -0
  40. package/lib/mapper.js +175 -0
  41. package/lib/mapper.js.map +1 -0
  42. package/lib/media-mime.d.ts +14 -0
  43. package/lib/media-mime.d.ts.map +1 -0
  44. package/lib/media-mime.js +32 -0
  45. package/lib/media-mime.js.map +1 -0
  46. package/lib/official-upstream.d.ts +143 -0
  47. package/lib/official-upstream.d.ts.map +1 -0
  48. package/lib/official-upstream.js +425 -0
  49. package/lib/official-upstream.js.map +1 -0
  50. package/lib/openapi-port.d.ts +190 -0
  51. package/lib/openapi-port.d.ts.map +1 -0
  52. package/lib/openapi-port.js +2 -0
  53. package/lib/openapi-port.js.map +1 -0
  54. package/lib/outbound.d.ts +69 -0
  55. package/lib/outbound.d.ts.map +1 -0
  56. package/lib/outbound.js +148 -0
  57. package/lib/outbound.js.map +1 -0
  58. package/lib/stream-upstream.d.ts +77 -0
  59. package/lib/stream-upstream.d.ts.map +1 -0
  60. package/lib/stream-upstream.js +303 -0
  61. package/lib/stream-upstream.js.map +1 -0
  62. package/lib/transport.d.ts +37 -0
  63. package/lib/transport.d.ts.map +1 -0
  64. package/lib/transport.js +73 -0
  65. package/lib/transport.js.map +1 -0
  66. package/lib/upstream.d.ts +60 -0
  67. package/lib/upstream.d.ts.map +1 -0
  68. package/lib/upstream.js +61 -0
  69. package/lib/upstream.js.map +1 -0
  70. package/package.json +46 -0
@@ -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":"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"}
@@ -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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"}
@@ -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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"}