# جلسات المحادثة بمعرّفك الخاص

> اربط معرّفات محادثاتك بجلسات K-Agent مباشرة — طلب واحد لكل دور، وإعادة محاولة آمنة، وتحويلات لموظف، وردود تصلك عبر الويب هوك.

نظامك يسمّي كل محادثة بالفعل: تذكرة دعم، أو طلب، أو محادثة في تطبيقك. ومع `external_id` يصبح هذا الاسم **هو** جلسة K-Agent — بلا جدول ربط تحتفظ به. يبني هذا الدليل تكاملًا كاملًا على قاعدة واحدة من [عقد الجلسات](/docs/concepts/sessions/#عقد-معرّفات-الجلسات): ‏`POST /v1/sessions` يجلب الجلسة أو ينشئها ويجيب دائمًا عن `input`.

## طلب واحد لكل دور

مع كل رسالة يرسلها عميلك، نفّذ طلبًا واحدًا:

**JavaScript**

```js
const BASE = 'https://api.k-agent.kerneltics.com/v1';

/**
 * Sends one customer message to the agent and returns what to show them.
 * threadId: your conversation ID, e.g. "thread-58123"
 * messageId: your ID for this message — reused if you retry, so it is answered once
 */
export async function askAgent({ threadId, messageId, customer, text }) {
  const res = await fetch(`${BASE}/sessions`, {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.KAGENT_API_KEY}`,
      'Content-Type': 'application/json',
      'Idempotency-Key': `msg-${messageId}`,
    },
    body: JSON.stringify({
      agent: 'store-assistant',
      external_id: threadId,
      end_user: { external_id: customer.id, name: customer.firstName },
      input: text,
    }),
  });
  const body = await res.json();
  if (!res.ok) throw new Error(`${body.error.code}: ${body.error.message}`);

  if (!body.run) return { kind: 'with_team' };                          // human mode: your team has it
  if (['queued', 'in_progress'].includes(body.run.status)) return { kind: 'pending', runId: body.run.id };
  if (body.run.outcome === 'handed_off') return { kind: 'handed_off', text: body.run.output_text };
  return { kind: 'reply', text: body.run.output_text };
}
```

**Python**

```python
import os, requests

BASE = "https://api.k-agent.kerneltics.com/v1"

def ask_agent(thread_id: str, message_id: str, customer: dict, text: str) -> dict:
    """Sends one customer message to the agent and returns what to show them."""
    r = requests.post(
        f"{BASE}/sessions",
        headers={
            "Authorization": f"Bearer {os.environ['KAGENT_API_KEY']}",
            "Idempotency-Key": f"msg-{message_id}",  # a retry is answered once
        },
        json={
            "agent": "store-assistant",
            "external_id": thread_id,
            "end_user": {"external_id": customer["id"], "name": customer["first_name"]},
            "input": text,
        },
        timeout=120,
    )
    body = r.json()
    if not r.ok:
        raise RuntimeError(f"{body['error']['code']}: {body['error']['message']}")

    if body["run"] is None:
        return {"kind": "with_team"}  # human mode: your team has it
    if body["run"]["status"] in ("queued", "in_progress"):
        return {"kind": "pending", "run_id": body["run"]["id"]}
    if body["run"]["outcome"] == "handed_off":
        return {"kind": "handed_off", "text": body["run"]["output_text"]}
    return {"kind": "reply", "text": body["run"]["output_text"]}
```

**curl**

```bash
curl https://api.k-agent.kerneltics.com/v1/sessions \
  -H "Authorization: Bearer $KAGENT_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: msg-77120" \
  -d '{
    "agent": "store-assistant",
    "external_id": "thread-58123",
    "end_user": { "external_id": "cus_1042", "name": "فهد" },
    "input": "وين طلبي؟"
  }'
```

ما الذي يمنحك إياه هذا:

- **الرسالة الأولى تنشئ الجلسة** (`201`)؛ وكل رسالة بعدها تستأنفها (`200`). لا تحتاج أبدًا إلى البحث عن معرّف `sess_`.
- **إعادة المحاولة آمنة.** مفتاح `Idempotency-Key` مشتق من معرّف رسالتك يعني أن إعادة الطلب بعد انقطاع الشبكة تعيد النتيجة المحفوظة بدل إجابة ثانية. انظر [عدم التكرار](/docs/reference/idempotency/).
- **السجل محفوظ لك.** يرى الوكيل المحادثة حتى الآن (`history_limit` رسالة)، حتى بعد أيام.
- **التحويلات ظاهرة.** `outcome: "handed_off"` يعني أن فريقك أصبح مسؤولًا عن المحادثة. ومن بعدها تكون `session.mode` قيمتها `"human"` و`run` قيمتها `null`، ولا يُنتَج رد ذكاء اصطناعي حتى يُعاد التحويل إلى الوكيل أو تنتهي مدته.

## اختر معرّفات جيدة

- المعرّفات **فريدة في المشروع**، والمعرّف الواحد يتبع وكيلًا واحدًا. إذا عمل عدة وكلاء على السجلات نفسها فأضف بادئة: `support:thread-58123` و`sales:thread-58123`.
- المحارف المسموحة حروف لاتينية وأرقام و`. _ : -`، حتى 128 حرفًا، تبدأ بحرف أو رقم. ولا تبدأ بـ `sess_`.
- لا تستخدم رقم جوال أو بريدًا إلكترونيًا معرّفًا أبدًا. وإن كان هذا مفتاحك الطبيعي [فاشتقّ منه معرّفًا بالتجزئة على خادمك](/docs/concepts/sessions/#لا-بيانات-شخصية-في-المعرّفات).
- مرّر الشخص في `end_user`. عميل الجلسة لا يمكن تغييره لاحقًا، وذاكرة الوكيل للإجراءات السابقة تتبع العميل لا الجلسة.

## ردود تستغرق وقتًا أطول، أو تأتي من فريقك

بعض المحادثات تُدار أفضل دون إبقاء الطلب مفتوحًا: البريد، أو الرسائل النصية، أو أي قناة ترسل فيها الردود إلى العميل بنفسك. أرسل مع `background: true` واستقبل الردود عبر [الويب هوك](/docs/guides/webhooks/):

```bash
curl https://api.k-agent.kerneltics.com/v1/sessions/thread-58123/messages \
  -H "Authorization: Bearer $KAGENT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"input": "تمام، شكرًا!", "client_message_id": "77121", "background": true}'
```

يعيد الطلب `202` فورًا. اشترك بنقطة استقبال في `message.created`: يُطلق لردود الوكيل **و**لردود فريقك العامة من مكتب التحويل، مع `role` و`author`، فيتولى معالج واحد إيصال الاثنين:

```json
{
  "type": "message.created",
  "id": "evt_01k6rz8e0h2k4n6q8s0v2x4z6b",
  "created_at": 1791272100,
  "data": {
    "message": {
      "id": "msg_01k6rz8e0h2k4n6q8s0v2x4z6b",
      "object": "message",
      "session_id": "sess_01k6rz4p7h2c9m5x8w3t6v1qbg",
      "role": "assistant",
      "content": [{ "type": "text", "text": "العفو، يوصلك خلال 1 إلى 3 أيام عمل." }],
      "created_at": 1791272100
    }
  }
}
```

يحمل جسم الحدث معرّف الجلسة `sess_`. احتفظ به من طلبك الأول (الاستجابات تعيد المعرّفين دائمًا)، أو اجلب الجلسة لتقرأ `external_id`.

## القراءة والإغلاق والتنظيف

| المهمة | الطلب |
|---|---|
| عرض سجل المحادثة | `GET /v1/sessions/thread-58123/messages` |
| متابعة المحادثة مباشرة | `GET /v1/sessions/thread-58123/events` |
| إغلاق محادثة انتهت | `POST /v1/sessions/thread-58123/close` — رسالة جديدة تعيد فتحها |
| التحويل لموظف من جهتك | `POST /v1/sessions/thread-58123/handoff` مع `{"reason_type": "customer_requested", "summary": "…"}` |
| الحذف الكامل | `DELETE /v1/sessions/thread-58123` |

## أخطاء شائعة

| الرمز | الحل |
|---|---|
| `session_agent_mismatch` | المعرّف مرتبط بوكيل آخر؛ أضف بادئة لكل وكيل. |
| `session_end_user_mismatch` | بدأت المحادثة لعميل آخر؛ لا تُعِد استخدام معرّفات المحادثات بين العملاء. |
| `session_busy` / `session_queue_full` | وصلت الرسائل أسرع مما يجيب الوكيل؛ أعد المحاولة بعد `Retry-After`، أو استخدم `concurrency: "queue"`. |
| `external_id_invalid` | يحتوي المعرّف محارف خارج `A–Z a–z 0–9 . _ : -`، أو يبدأ بـ `sess_`. |
