import type { MessengerAdapter, MessageContext } from '../../../core/types.js';
import type { DingTalkConfig } from './types.js';
/** Result of flattening a DingTalk richText payload. */
export interface RichTextFlatten {
    /** Joined + trimmed text from all text segments. */
    text: string;
    textPartCount: number;
    pictureCount: number;
    /** Which candidate location the segment array came from (audit). */
    source: string;
    listLen: number;
}
/**
 * Flatten a DingTalk `msgtype:'richText'` payload into plain text.
 *
 * The segment array has shipped in several shapes across SDK versions
 * and chat contexts. v1.2.104 confirmed from a real Stream-SDK 1:1
 * payload that the live shape is `msg.content.richText` AS AN ARRAY
 * DIRECTLY (segments like `{ text: "…" }`, no `type` field), NOT nested
 * under `.msgList`. We probe array-direct shapes first, then the older
 * `.msgList`-nested shapes as fallbacks.
 *
 * Pure + exported so the multi-shape probe is unit-testable without
 * standing up the whole adapter.
 */
export declare function flattenDingtalkRichText(msg: Record<string, unknown>): RichTextFlatten;
export declare const ACK_GLYPHS: {
    readonly emoji: {
        readonly received: "⏳";
        readonly working: "🤖";
        readonly done: "✅";
        readonly failed: "❌";
    };
    readonly kaomoji: {
        readonly received: "(´・ω・`)";
        readonly working: "(｡•̀ᴗ-)";
        readonly done: "(•̀ᴗ•́)";
        readonly failed: "(╥﹏╥)";
    };
};
/**
 * Batch 4 (#3) — pure account-resolution helper. Merge accounts dict +
 * top-level credentials into the set of (accountId → DingTalkConfig)
 * pairs the adapter should start. Disabled accounts are filtered out.
 * Each entry's policy / ack-reaction fields fall back to the top-level
 * values when the account-block doesn't override them.
 *
 * Single-account legacy configs (top-level credentials, no `accounts`)
 * resolve to `{'default': <cfg>}` so the adapter sees the same code
 * path regardless of multi-bot vs single-bot deployments.
 */
export declare function planDingTalkAccounts(cfg: DingTalkConfig): Map<string, DingTalkConfig>;
/**
 * Batch 1 (#4) — pure access-policy evaluator. Decoupled from the adapter
 * class so unit tests can hit it without the SDK. Returns:
 *  - allowed: should the message be dispatched to the agent?
 *  - pairedNewSender: when defined, the caller should persist this sender
 *    into allowFrom because the policy is `pairing` and this is the first
 *    inbound DM.
 */
export declare function evalAccessPolicy(cfg: Pick<DingTalkConfig, 'dmPolicy' | 'groupPolicy' | 'allowFrom' | 'groupAllowFrom'>, msg: {
    isGroup: boolean;
    senderStaffId: string;
}): {
    allowed: boolean;
    pairedNewSender?: string;
};
export declare class DingTalkAdapter implements MessengerAdapter {
    readonly name = "dingtalk";
    private clients;
    private threadOwner;
    private effectiveConfigs;
    private config;
    private messageHandler?;
    private seenMessages;
    start(): Promise<void>;
    /** Internal wrapper that defers to the pure {@link planDingTalkAccounts}
     *  helper exported from this module so unit tests can exercise account
     *  resolution without spinning up the SDK. */
    private planAccounts;
    stop(): Promise<void>;
    onMessage(handler: (ctx: MessageContext) => Promise<void>): void;
    sendMessage(threadId: string, text: string): Promise<void>;
    /** Batch 4 (#3) — pick the client that owns this thread, falling back
     *  to 'default' if we never saw the thread before (manual notify path). */
    private clientForThread;
    /** Returns true on duplicate; otherwise records and prunes. Same shape
     *  as the Feishu adapter's dedup helper. */
    private isDuplicate;
    /** Build the threadId we expose to the rest of agim. 1:1 routes by
     *  sender staffId; group chats route by openConversationId so any
     *  member triggers a shared session in that group. */
    private threadIdFor;
    private handleInbound;
    /** Image inbound: exchange downloadCode → CDN bytes → save under
     *  ~/.agim/media/dingtalk/<thread>/, then dispatch a Message whose
     *  text contains an `[图片附件：<path>]` marker. claude-code's Read
     *  tool already understands that marker — it'll fetch the file off
     *  disk and pass it to the multimodal model the user is talking to.
     *
     *  On download failure we still dispatch (with a `[图片附件下载失败]`
     *  marker) so the user's interaction isn't silently dropped. */
    private handleImageInbound;
    /** Voice inbound: download AMR (or whatever the CDN serves) → transcribe
     *  via the shared whisper provider → dispatch the transcript as the
     *  user's text. The saved source file path is appended so the agent can
     *  reference it (replay, archive, etc.) — same shape as the Telegram
     *  voice path so claude-code's prompts work uniformly.
     *
     *  Surfaces failures inline so the user gets feedback:
     *    - no downloadCode      → `[语音附件无下载凭据]`
     *    - download failed      → `[语音附件下载失败：…]`
     *    - no provider set      → `[语音附件未转写（未配置 OPENAI_API_KEY 或 IMHUB_WHISPERCPP_BIN）：path]`
     *    - transcribe threw     → `[语音转写失败（provider: reason）\n源文件：path]` */
    private handleVoiceInbound;
    /** Build a filesystem-safe sub-path under MEDIA_ROOT/dingtalk/.
     *  Uses senderStaffId for 1:1 and openConversationId for groups; both
     *  are alphanumeric+`=` from DingTalk so we sanitise the `=` (and any
     *  other path-unsafe chars) to `_` to avoid surprises on Windows ports
     *  or shell quoting. */
    private subPathFor;
    /** DingTalk msgIds are base64-shaped (`msgABC/DEF+xyz==`) and contain
     *  `/` + `+` + `=` — all reserved for path or shell quoting. Replace
     *  every non-[A-Za-z0-9_-] char with `_` so the resulting filename
     *  passes saveDingTalkMedia's safety check and is portable. */
    private safeFilenameStem;
    /** Batch 1 (#4) + Batch 4 (#3) — per-account policy gate. Uses the
     *  effective config for `accountId` so each bot can have its own
     *  dmPolicy / allowlist. */
    private passesAccessPolicy;
    /** Batch 1 (#7) + Batch 4 (#3) — per-account ack glyph. Reads the
     *  ackReaction mode from the account that owns the inbound thread. */
    private sendAckReaction;
    /** Shared text-dispatch path used by text / markdown msgtypes. */
    private dispatchText;
}
export { tryExtractLatLng, type MapUrlSource } from './link-coords.js';
export declare const dingtalkAdapter: DingTalkAdapter;
//# sourceMappingURL=dingtalk-adapter.d.ts.map