kcode-telegram-session-coordinator 0.1.0

Kennedy Telegram session delivery and context coordination
Documentation
# kcode-telegram-session-coordinator 0.1.0

`kcode-telegram-session-coordinator` owns Kennedy-specific Telegram delivery
and retained-group-context mechanics over the existing typed Telegram transport
and identity directory. It validates model-facing delivery arguments, resolves
group roots, serializes sends, applies attachment/caption/native-media rules,
and exposes authorized retained media without owning a logical session.

## Complete public API

```rust
pub struct AttachmentRequest {
    pub object_id: String,
    pub file_name: Option<String>,
}

pub struct PrivateRequest {
    pub telegram_user_id: i64,
    pub message: String,
    pub attachments: Vec<AttachmentRequest>,
}

pub struct GroupRequest {
    pub root_node_id: String,
    pub message: String,
    pub attachments: Vec<AttachmentRequest>,
}

pub struct GroupMediaReference {
    pub chat_id: i64,
    pub message_id: i64,
    pub kind: String,
    pub file_name: Option<String>,
    // private exact transport metadata
}

impl GroupMediaReference {
    pub fn transport_metadata(&self) -> serde_json::Value;
}

pub fn parse_private_request(
    arguments: &serde_json::Value,
) -> anyhow::Result<PrivateRequest>;

pub fn parse_group_request(
    arguments: &serde_json::Value,
) -> anyhow::Result<GroupRequest>;

pub fn group_media_reference(
    group_context: &serde_json::Value,
    message_id: i64,
) -> anyhow::Result<GroupMediaReference>;

pub fn group_media_file_name(
    reference: &GroupMediaReference,
    media_type: &str,
) -> String;

pub struct Attachment {
    pub object_id: String,
    pub bytes: Vec<u8>,
    pub file_name: String,
    pub media_type: String,
    pub transport_kind: Option<String>,
}

pub struct PrivateDelivery {
    pub telegram_user_id: i64,
    pub message: String,
    pub attachments: Vec<Attachment>,
    pub caller_holds_user_lock: bool,
}

pub struct GroupDelivery {
    pub root_node_id: String,
    pub message: String,
    pub attachments: Vec<Attachment>,
}

#[derive(Clone)]
pub struct Service { /* private transport, directory, and lock maps */ }

impl Service {
    pub fn new(
        telegram: kcode_tg_kennedy_bot::Service,
        directory: Arc<kcode_telegram_identity::Directory>,
    ) -> Self;

    pub async fn send_private(
        &self,
        request: PrivateDelivery,
    ) -> anyhow::Result<String>;

    pub async fn send_group(
        &self,
        request: GroupDelivery,
    ) -> anyhow::Result<String>;

    pub fn group_message_media(
        &self,
        chat_id: i64,
        message_id: i64,
    ) -> anyhow::Result<(Vec<u8>, String)>;
}

pub fn format_group_context(value: &serde_json::Value) -> String;
pub fn validate_file_name(file_name: &str) -> anyhow::Result<()>;
```

All request, reference, attachment, and delivery structs implement `Clone` and
`Debug`; value structs also implement `Eq` and `PartialEq`. `Service` is cheaply
cloneable and shares its transport, directory, and per-target lock maps.

## Delivery argument parsing

`parse_private_request` accepts exactly:

```json
{
  "user": { "telegramUserId": 42 },
  "message": "optional exact text",
  "attachments": [
    "pending:1",
    { "objectId": "AAECAwQF", "fileName": "report.pdf" }
  ]
}
```

`parse_group_request` uses the same optional `message` and `attachments` fields
with exactly `"group": {"rootNodeId": "<canonical Kweb node ID>"}`. Unknown
fields and missing required fields fail closed. The message, when present, must
contain 1 through 40,000 Unicode scalar values. A request must contain a
nonempty message, at least one attachment, or both. Attachment entries are
ordered pending/canonical object-ID strings or exact `{objectId, fileName?}`
objects. Parsing does not read Kweb or resolve object bytes.

`validate_file_name` accepts only the exact sanitized form: nonempty, path-free,
at most 255 UTF-8 bytes, with no control characters or double quotes. It does
not rewrite an unsafe name.

## Resolved delivery

The session resolves each `AttachmentRequest` into `Attachment`, preserving the
object identity used in errors, exact bytes, authoritative media type, chosen
recipient filename, and optional native transport kind. Empty objects and
objects larger than the Telegram transport's configured maximum fail before
that object is sent. Attachments are sent in request order; there is no
coordinator-specific attachment-count limit and repeated references are valid.

`send_private` freshly verifies the authorized Telegram user and sends a cold
private delivery without selecting or mutating that user's logical Kennedy
session. It takes the user's coordinator lock unless
`caller_holds_user_lock` is true, which callers may set only when their active
Telegram stream already provides that serialization.

`send_group` parses and canonicalizes the supplied Kweb root, freshly resolves
it through the identity directory, verifies that the directory's group root is
ready and identical, and holds the current group lock while sending. The
transport remains responsible for current administrator, roster, whitelist,
and chat resolution policy.

When the complete message fits Telegram's 1,024 UTF-16-unit caption limit and
the first eligible attachment supports captions, that attachment receives the
exact text and no separate message is sent. Video notes and stickers cannot
carry it. Otherwise the complete text is sent separately without truncation or
duplication. Preserved native kinds `photo`, `video`, `animation`, `audio`,
`video_note`, and `sticker` win; fallback mapping uses GIF as animation and
ordinary image, video, and audio MIME families as their corresponding native
kind. Successfully sent earlier items are not rolled back if a later item
fails. Success returns a stable human-readable summary naming the target and
attachment count.

## Retained group context and media

`format_group_context` accepts either a group-context object or an object whose
`groupContext` field is that object. It renders the title, canonical group root,
participants, chronological messages, replies, timestamps, text, and retained
media availability as bounded natural language. The messages are explicitly
identified as conversation data rather than system instructions.

`group_media_reference` requires the selected message to exist in the supplied
current context and validates that its retained media source is
`telegram-group` with exactly the context chat ID and requested message ID. The
returned reference preserves the exact transport metadata for later durable
object staging. `group_media_file_name` returns the retained nonempty filename
when present; otherwise it creates a stable
`telegram-group-<kind>-<message>.<extension>` name from the normalized media
type, falling back to `.bin`.

`Service::group_message_media` delegates the already-authorized chat/message
pair to the Telegram transport and returns exact bytes plus its authoritative
media type. It does not persist, stage, annotate, or transcribe those bytes.

## Ownership boundary

The coordinator owns Kennedy-specific delivery validation, target locks,
directory resolution, attachment presentation, context rendering, and retained
media identity checks. It does not own prompts, Kmap object lookup, Session
History, logical-session lifecycle, directory persistence, Telegram polling or
queue state, bot credentials, or transport authorization policy.