وكيل صُرَد عبر خادمك

لواجهتك الخاصة: تطبيق جوال، أو موقع مخصص، أو نظام داخلي. يدير صُرَد عقل الوكيل وصوته ومعرفته، وتتحكم أنت في التجربة كاملة.

قبل البدء

  • وكيل منشور في Studio، ومعرّفه (agent_id) ومعرّف مساحة العمل (workspace_id): تجدهما في Studio ← الدمج.
  • مفتاح API من Studio ← الدمج. يُحفظ في خادمك فقط.
  • نطاق واجهتك مسجل في «النطاقات المسموح لها» (مثل https://app.example.com). كل طلب من الواجهة يحمل هذا النطاق في ترويسة Origin، ويرفض صُرَد ما يخالفه.

1. رمز الجلسة (من خادمك)

POST/api/v1/workspaces/{workspace_id}/widget/session-tokens

الترويسة: Authorization: Bearer sk_…. يعيد token تمرره إلى واجهتك.

الحقلالنوعالوصف
agent_id*uuidالوكيل الذي ستحادثه الجلسة.
origin*urlنطاق واجهتك كما سجّلته. الرمز مربوط به ولا يعمل من غيره.
ttl_secondsintمدة صلاحية الرمز: من 60 إلى 3600 ثانية. الافتراضي 900.
agent_version_iduuidلتثبيت مراجعة بعينها. إن أُغفل تعمل الجلسة على المراجعة المنشورة.

2. فتح جلسة

POST/api/v1/widget/sessions

الترويستان: Authorization: Bearer <token> وOrigin. يعيد conversation.conversation_id الذي تستخدمه في بقية الطلبات.

الحقلالنوعالوصف
modetext | voiceطبيعة المحادثة. الافتراضي text.
conversation_idstringلاستئناف محادثة سابقة بعد انتهاء رمزها: افتح جلسة برمز جديد ومرّر المعرّف.
external_visitor_idstringمعرّفك أنت للزائر، يُحفظ مع المحادثة للربط مع نظامك.
كل رمز يفتح جلسة واحدة فقط (إعادة استخدامه تعيد 409 TOKEN_REPLAYED)، ثم يبقى صالحاً لأدوار هذه الجلسة حتى تنتهي مدته. وعند انتهائها (401) اطلب رمزاً جديداً وافتح جلسة بنفس conversation_id.

3. الأدوار النصية

POST/api/v1/widget/conversations/{conversation_id}/turns

الحقلالنوعالوصف
text*stringرسالة المستخدم.
modetext | voicevoice إن كانت واجهتك ستنطق الرد.
idempotency_keystringمفتاح فريد للدور. إعادة الطلب بنفس المفتاح لا تكرر التنفيذ ولا تدفع صوتاً مرتين.

أهم حقول الرد

الحقلالنوعالوصف
reply.displaystringنص الرد للعرض.
reply.spokenstringالنص كما نُطق (قد يختلف في النطق المضبوط للأسماء والأرقام).
audio.content_base64base64 WAVصوت الرد كاملاً، إن كان «شكل الرد» يشمل الصوت.
audio.voicestringالصوت الذي نطق فعلاً.
identity.voice_profile_idstringملف الصوت المطبق، للتحقق من ثبات صوت وكيلك.
turn_idstringمعرّف الدور، للربط مع سجلاتك.

4. الرسائل الصوتية

POST/api/v1/widget/conversations/{conversation_id}/voice/utterance

ترسل صوت المستخدم ملفاً (multipart/form-data، الحقل file)، فيحوله صُرَد إلى نص، ويرد الوكيل، ويعود الرد نصاً وصوتاً في طلب واحد. الأنسب: WAV أحادي 16kHz بترميز PCM16، وبحد أقصى 4MB.

الرد كالدور النصي، ومعه transcript.text: ما فهمه صُرَد من كلام المستخدم.

5. الأحداث الحية (اختياري)

GET/api/v1/widget/conversations/{conversation_id}/events

بث SSE لما يجري في الجلسة: session.ready، transcript.final، agent.responding، audio.first، audio.chunk، audio.completed، turn.completed، session.error. كل حدث بالشكل event: <type> ثم data: <json>، والتفاصيل في payload.

JavaScript
// قراءة الأحداث الحية بـ fetch (EventSource لا يرسل ترويسة Authorization)
const res = await fetch(API + '/api/v1/widget/conversations/' + conversationId + '/events', {
  headers: { Authorization: 'Bearer ' + token, Accept: 'text/event-stream' },
})
const reader = res.body.getReader()
const decoder = new TextDecoder()
let buffer = ''
for (;;) {
  const { value, done } = await reader.read()
  if (done) break
  buffer += decoder.decode(value, { stream: true })
  let end
  while ((end = buffer.indexOf('\n\n')) >= 0) {
    const frame = buffer.slice(0, end)
    buffer = buffer.slice(end + 2)
    const type = /^event: (.*)$/m.exec(frame)?.[1]
    const data = /^data: (.*)$/m.exec(frame)?.[1]
    if (type && data) onEvent(type, JSON.parse(data)) // data.payload يحمل التفاصيل
  }
}

6. الإيقاف والإنهاء

POST/api/v1/widget/conversations/{conversation_id}/cancel

يوقف الرد الجاري (مثلاً حين يقاطع المستخدم الوكيل)، ولا يُسلَّم بعده صوت قديم.

POST/api/v1/widget/conversations/{conversation_id}/close

ينهي الجلسة. تبقى المحادثة محفوظة في السجل.

7. سجل المحادثات

GET/api/v1/workspaces/{workspace_id}/conversations

GET/api/v1/workspaces/{workspace_id}/conversations/{conversation_id}

بمفتاح API يحمل نطاق agents.read. القائمة من الأحدث، وتقبل agent_id وbefore وlimit (حتى 100)، وتعيد next_before للصفحة التالية. وتُعرض المحادثات نفسها في Studio ← المحادثات.

أمثلة كاملة

curl
# 1) خادمك: رمز جلسة قصير العمر (المفتاح لا يغادر الخادم)
curl -X POST https://app.surad-ai.com/api/v1/workspaces/$WORKSPACE_ID/widget/session-tokens \
  -H "Authorization: Bearer $SURAD_API_KEY" -H "Content-Type: application/json" \
  -d '{"agent_id":"'$AGENT_ID'","origin":"https://app.example.com","ttl_seconds":900}'

# 2) واجهتك: افتح جلسة بالرمز ونطاقك
curl -X POST https://app.surad-ai.com/api/v1/widget/sessions \
  -H "Authorization: Bearer $TOKEN" -H "Origin: https://app.example.com" \
  -H "Content-Type: application/json" -d '{"mode":"text"}'

# 3) دور نصي
curl -X POST https://app.surad-ai.com/api/v1/widget/conversations/$CONVERSATION_ID/turns \
  -H "Authorization: Bearer $TOKEN" -H "Origin: https://app.example.com" \
  -H "Content-Type: application/json" -d '{"text":"هل عندكم توصيل اليوم؟","mode":"text"}'

# 4) رسالة صوتية (WAV)
curl -X POST https://app.surad-ai.com/api/v1/widget/conversations/$CONVERSATION_ID/voice/utterance \
  -H "Authorization: Bearer $TOKEN" -H "Origin: https://app.example.com" \
  -F "file=@question.wav;type=audio/wav"
Python
import base64, httpx

API = "https://app.surad-ai.com"
ORIGIN = "https://app.example.com"

def new_session(api_key: str, workspace_id: str, agent_id: str) -> tuple[str, str]:
    minted = httpx.post(
        f"{API}/api/v1/workspaces/{workspace_id}/widget/session-tokens",
        headers={"Authorization": f"Bearer {api_key}"},
        json={"agent_id": agent_id, "origin": ORIGIN, "ttl_seconds": 900},
    ).raise_for_status().json()
    token = minted["token"]
    opened = httpx.post(
        f"{API}/api/v1/widget/sessions",
        headers={"Authorization": f"Bearer {token}", "Origin": ORIGIN},
        json={"mode": "voice"},
    ).raise_for_status().json()
    return token, opened["conversation"]["conversation_id"]

def ask(token: str, conversation_id: str, text: str) -> dict:
    turn = httpx.post(
        f"{API}/api/v1/widget/conversations/{conversation_id}/turns",
        headers={"Authorization": f"Bearer {token}", "Origin": ORIGIN},
        json={"text": text, "mode": "voice"},
        timeout=60,
    ).raise_for_status().json()
    if turn.get("audio"):
        with open("reply.wav", "wb") as f:
            f.write(base64.b64decode(turn["audio"]["content_base64"]))
    return turn["reply"]  # {"display": "...", "spoken": "..."}

الأخطاء وحدود الاستخدام وصيغ الصوت في المرجع.