یہ صفحہ چینی اصل سے ترجمہ کیا گیا ہے۔ اختلاف کی صورت میں اصل متن معتبر ہے۔

Confer — API تفصیلات

کلائنٹ ↔ سرور اور سرور ↔ A2A peer کے درمیان تمام API یہاں متعین ہیں۔

عام اصول

{
  "error": {
    "code": "invalid_request",
    "message": "Human-readable message",
    "details": { /* optional */ }
  }
}

توثیق

کلائنٹ API (صارف کا کلائنٹ اسے استعمال کرتا ہے)

توثیق

POST   /api/v1/auth/register
POST   /api/v1/auth/login
POST   /api/v1/auth/refresh
POST   /api/v1/auth/logout
POST   /api/v1/auth/password        # {current_password, new_password}
GET    /api/v1/auth/2fa             # {enabled, recovery_codes_left}
POST   /api/v1/auth/2fa/setup       # → {secret, otpauth_uri}
POST   /api/v1/auth/2fa/enable      # {code} → {recovery_codes}
POST   /api/v1/auth/2fa/disable     # {password, totp_code | recovery_code}
POST   /api/v1/auth/oauth/{provider}    # OAuth callback

POST /api/v1/auth/login درخواست:

{
  "username": "laowang",
  "password": "...",
  "device_id": "ios-abc123",
  "device_info": { "platform": "ios", "model": "iPhone 15", "os": "17.1" }
}

جواب:

{
  "access_token": "eyJ...",
  "refresh_token": "...",
  "expires_in": 900,
  "user": { /* User object */ }
}

دو مرحلہ سائن ان (TOTP)۔ فعال ہونے پر login درست پاس ورڈ کے بعد ایک totp_code (تصدیقی ایپ کے 6 ہندسے) یا recovery_code بھی مانگتا ہے؛ دونوں نہ ہوں تو 401 totp_required لوٹاتا ہے، جس سے کلائنٹ جانتا ہے کہ دوسرا مرحلہ دکھانا ہے، اور غلط کوڈ پر 401 invalid_second_factor۔ غلط پاس ورڈ پر ہمیشہ پہلے invalid_credentials آتا ہے، اس لیے یہ کبھی ظاہر نہیں ہوتا کہ اکاؤنٹ میں دو مرحلہ سائن ان فعال ہے۔ ہر کوڈ ایک ہی بار چلتا ہے (استعمال شدہ وقتی مرحلہ درج ہوتا ہے) اور ہر بازیابی کوڈ استعمال ہوتے ہی کاٹ دیا جاتا ہے؛ دونوں ایک ہی مشروط UPDATE سے خرچ ہوتے ہیں، اس لیے بیک وقت دو سائن ان ایک کوڈ دوبارہ نہیں چلا سکتے۔ setup کا راز AES-256-GCM سے خفیہ کر کے رکھا جاتا ہے اور جب تک enable ایپ کے کوڈ سے اس کی تصدیق نہ کرے، بے اثر رہتا ہے؛ enable 10 بازیابی کوڈ صرف اسی ایک بار لوٹاتا ہے اور صرف ان کے ہیش رکھے جاتے ہیں۔ disable کے لیے پاس ورڈ اور دوسرا عنصر درکار ہے۔ password کے لیے موجودہ پاس ورڈ درکار ہے اور یہ باقی تمام سیشن ختم کر دیتا ہے۔ یہ سب آڈٹ لاگ میں درج ہوتے ہیں اور ہر اکاؤنٹ کے لیے فی منٹ 10 تک محدود ہیں۔ enable کے لیے بھی password درکار ہے، تاکہ صرف چوری شدہ ٹوکن سے کسی کے اکاؤنٹ کے آگے حملہ آور کی تصدیقی ایپ نہ لگائی جا سکے؛ دو مرحلہ سائن ان فعال ہونے پر password کے لیے بھی دوسرا عنصر درکار ہے۔ لگاتار 5 غلط کوڈ اس اکاؤنٹ کی کوڈ کوششوں کو 15 منٹ کے لیے روک دیتے ہیں (429 too_many_attempts)، جو فی IP لاگ ان حد کے علاوہ ہے۔ بازیابی کوڈ سرور کلید کے ساتھ HMAC کی صورت میں رکھے جاتے ہیں۔ جس صارف کی تصدیقی ایپ اور بازیابی کوڈ دونوں گم ہو گئے ہوں، اس کے لیے منتظم POST /api/v1/admin/users/{id}/reset-2fa سے اسے بند کر سکتا ہے (آڈٹ ہوتا ہے، اور منتظم کے اپنے اکاؤنٹ پر کبھی نہیں)۔

GET    /api/v1/tokens              # [{id, name, prefix, created_at, last_used_at, expires_at}]
POST   /api/v1/tokens              # {name, expires_in_days?} → {token: {..., token}}
DELETE /api/v1/tokens/{id}

ذاتی API ٹوکن۔ ان اسکرپٹس اور ٹولز کے لیے جو مالک کے طور پر API کو کال کرتے ہیں: Authorization: Bearer cfr_…، وہی ہیڈر جو سیشن کا access token استعمال کرتا ہے۔ ٹوکن سادہ متن میں صرف بناتے وقت لوٹایا جاتا ہے؛ سرور اس کا SHA-256 اور پہچان کے لیے ایک سابقہ رکھتا ہے۔ 1 سے 365 دن کی اختیاری میعاد، فی صارف زیادہ سے زیادہ 20، اور مالک کے غیر فعال ہوتے ہی ٹوکن کام کرنا بند کر دیتا ہے۔ ٹوکن وہ کر سکتا ہے جو مالک چیٹ میں کرتا ہے، مگر یہ کارروائیاں صرف سائن ان سیشن قبول کرتی ہیں اور ٹوکن کو 403 session_required لوٹاتی ہیں: لاگ آؤٹ، پاس ورڈ اور دو مرحلہ سائن ان، ٹوکن کا انتظام، اجازت کی درخواستوں اور errand کارڈز پر فیصلہ، LLM کلیدیں بدلنا، اور تمام admin راستے۔ لیک ہونے والا ٹوکن مالک کے کام کر سکتا ہے، مگر اس کی جگہ فیصلہ نہیں کر سکتا اور اسے باہر بند نہیں کر سکتا۔ بنانا اور منسوخ کرنا آڈٹ ہوتا ہے۔

صارف اور Agent کی ترتیب

GET    /api/v1/users/me
PATCH  /api/v1/users/me
GET    /api/v1/users/me/keys          # signing keys: current / retiring / revoked
POST   /api/v1/users/me/keys/rotate   # session only
POST   /api/v1/users/me/keys/revoke   # {key_id}, session only
GET    /api/v1/agents/me
PATCH  /api/v1/agents/me
PUT    /api/v1/agents/me/policies
GET    /api/v1/agents/me/llm-keys      # ہر فراہم کنندہ ترتیب شدہ ہے یا نہیں (صرف بولین لوٹاتا ہے، کلید کبھی نہیں)
PUT    /api/v1/agents/me/llm-keys      # LLM کی API کلیدیں مشفّر کر کے رکھتا ہے
DELETE /api/v1/agents/me/llm-keys/{provider}
GET    /api/v1/agents/me/llm-keys/{provider}/models   # فراہم کنندہ سے براہِ راست پوچھتا ہے کہ اس کے پاس کون سے ماڈل ہیں

provider کی قدریں @confer/shared کی فراہم کنندہ فہرست سے آتی ہیں (packages/shared/src/llm/catalog.ts)، اور ساتھ ٹول سروس tavily۔ یہ فہرست گیٹ وے، agent-runtime اور کلائنٹ — تینوں پڑھتے ہیں: base URL، ماڈل فہرست کا راستہ اور طے شدہ ماڈل صرف اُسی ایک جگہ لکھے ہیں، سو نیا فراہم کنندہ شامل کرنے کا مطلب صرف فہرست بدلنا ہے۔

/models فراہم کنندہ کی اپنی ماڈل فہرست آگے بڑھا دیتا ہے؛ مقامی طور پر رکھی کوئی فہرست کبھی نہیں لوٹاتا:

{ "models": [{ "id": "gpt-4o" }] }
// خالی فہرست ہمیشہ اپنی وجہ ساتھ لاتی ہے؛ چاروں الگ ہیں اور ہر ایک کا علاج بھی الگ
{ "models": [], "error": "no_key" }        // اس فراہم کنندہ کی کلید ابھی ترتیب نہیں دی گئی
{ "models": [], "error": "unauthorized" }  // فراہم کنندہ نے کلید مسترد کر دی (401/403)
{ "models": [], "error": "unreachable" }   // فراہم کنندہ تک رسائی نہ ہوئی، یا اس نے کوئی اور خرابی لوٹائی
{ "models": [], "error": "unsupported" }   // یہ فراہم کنندہ ماڈل فہرست کا راستہ دیتا ہی نہیں

روابط / دوسرے Agent

GET    /api/v1/contacts                     # روابط کی فہرست۔ صفحہ بندی: ?limit=&offset=
POST   /api/v1/contacts                     # رابطہ شامل کریں
GET    /api/v1/contacts/{contact_id}        # ایک رابطے کی تفصیل (peer سمیت)
DELETE /api/v1/contacts/{contact_id}
PATCH  /api/v1/contacts/{contact_id}        # alias / tags / pinned / muted میں جزوی تبدیلی (جو خانے نہ بھیجے جائیں وہ مٹتے نہیں)

POST   /api/v1/contacts/lookup              # DID / ڈومین / صارف نام سے تلاش

POST /api/v1/contacts/lookup درخواست:

{
  "method": "domain",          // domain | did | username | qr_code | phone
  "value": "abc-industries.com"
}

GET /api/v1/contacts { contacts, total } لوٹاتا ہے۔ limit کا طے شدہ 50 اور زیادہ سے زیادہ 100، offset کا طے شدہ 0؛ ترتیب id (ULID) کے نزولی حساب سے ہے، یعنی تازہ ترین پہلے — ترتیب کا واحد اور یقینی ہونا ہی وہ چیز ہے جو offset کی کھڑکی کو سطریں چھوڑنے یا دہرانے سے روکتی ہے۔ total پوری گنتی ہے، اس صفحے کی نہیں، اور اسی سے کلائنٹ جانتا ہے کہ آخر آ گیا۔ جو limit/offset پڑھے نہ جا سکیں وہ خرابی دینے کے بجائے طے شدہ قدر لے لیتے ہیں۔

جواب: ملنے والے ممکنہ Agent کی فہرست۔ تلاش جن peer کو پاتی ہے انہیں peer_agents میں لکھ دیتی ہے اور ہر امیدوار کے ساتھ مقامی id (peer_id) بھی دیتی ہے — POST /api/v1/contacts اُسی id سے رابطہ شامل کرتا ہے۔ POST /contacts idempotent ہے: وہی peer دوبارہ شامل کرنے پر خرابی نہیں، پہلے سے موجود رابطہ (200) لوٹتا ہے۔

رابطہ شامل کرنا دراصل وصول کنندہ کی وہ رضامندی ہے کہ سامنے والا اس کے Agent کو خرچ کر سکے: صرف وہی peer میرے Agent سے جواب دلوا سکتا ہے (اور میرا LLM بجٹ خرچ کر سکتا ہے) جو رابطے کے طور پر جڑا ہو۔ غیر جڑے peer کے A2A پیغام منظوری کے انتظار میں کنکشن کی درخواست بن کر معلق رہتے ہیں؛ دیکھیں 03-protocol.md کا «کنکشن رضامندی کا دروازہ»۔

POST   /api/v1/contacts/{contact_id}/policies   # مستقل پالیسیاں طے کریں (مکمل تبدیلی، PUT جیسا مفہوم)

POST /contacts/{id}/policies کا body رن ٹائم صورت میں { default?: 'allow'|'ask_user'|'deny', rules?: [{ action, peer_did?, decision }] } ہوتا ہے اور پورا کا پورا peer_contacts.policy_overrides_json میں لکھا جاتا ہے۔ ادغام کا مفہوم: اندر آتی A2A درخواست پر فیصلہ کرتے وقت یہ فی رابطہ سبقت پالیسی Agent کی پالیسی کے اوپر چڑھتی ہے — contact.default موجود ہو تو وہ Agent کے طے شدہ کی جگہ لیتا ہے، اور contact.rules Agent کے قواعد سے پہلے آتے ہیں سو پہلے لگتے ہیں (کسی رابطے کا مخصوص قاعدہ Agent کے عمومی قاعدے پر بھاری پڑتا ہے)۔ خالی سبقت {} تطابقی ہے: فیصلہ بائٹ در بائٹ وہی رہتا ہے جو بغیر سبقت کے ہوتا۔

گفتگو

GET    /api/v1/conversations                       # میری گفتگوؤں کی فہرست (پہلے صفحے کے لیے)
POST   /api/v1/conversations                       # نئی گفتگو بنائیں
GET    /api/v1/conversations/{id}
PATCH  /api/v1/conversations/{id}
DELETE /api/v1/conversations/{id}

GET    /api/v1/conversations/{id}/messages         # صفحہ بندی: ?before=&limit=
POST   /api/v1/conversations/{id}/messages         # پیغام بھیجیں
GET    /api/v1/conversations/{id}/messages/{msg_id}/stream    # SSE سے LLM کا جواب رواں صورت میں لیں

POST   /api/v1/conversations/{id}/participants     # شریک شامل کریں
DELETE /api/v1/conversations/{id}/participants/{p_id}

POST   /api/v1/conversations/{id}/read             # پڑھا ہوا نشان زد کریں

GET بطور طے شدہ غیر محفوظ شدہ گفتگو اور ?archived=1 کے ساتھ محفوظ شدہ گفتگو دیتا ہے؛ ہر گفتگو میں unread_count ہوتا ہے، یعنی صارف کے آخری بار پڑھا ہوا نشان لگانے کے بعد سے (اگر کبھی نہ لگایا ہو تو شامل ہونے کے بعد سے) دوسروں کے لکھے پیغامات — اپنے پیغامات شمار نہیں ہوتے۔ PATCH { name?, archived? } لیتا ہے اور اسے صرف گفتگو بنانے والا استعمال کر سکتا ہے۔ POST .../read کال کرنے والے کا last_read_at موجودہ وقت پر کر دیتا ہے۔ کوئی رابطہ محفوظ شدہ گفتگو میں لکھے تو وہ محفوظ شدہ سے نکل کر سب سے اوپر آ جاتی ہے۔

GET /api/v1/search/messages?q=&limit= صارف جن تمام گفتگوؤں میں شامل ہے (محفوظ شدہ سمیت) ان کے پیغامات کے متن میں ذیلی سلسلہ تلاش کرتا ہے، نئے پہلے؛ q ‏1–200 حروف کا ہوتا ہے اور limit بطور طے شدہ 20، زیادہ سے زیادہ 50۔ یہ { results: [{ message_id, conversation_id, conversation_name, sender_type, content, created_at }] } لوٹاتا ہے۔ Postgres کی مکمل متن تلاش کے بجائے ذیلی سلسلہ مماثلت اس لیے، کہ اس کی بلٹ اِن ترتیبات چینی متن کو الفاظ میں تقسیم نہیں کرتیں۔

GET /api/v1/conversations/{id}/export صرف شرکاء کو پوری گفتگو لوٹاتا ہے، پرانے پیغامات پہلے اور زیادہ سے زیادہ 5000: { conversation, messages, truncated }؛ ہر پیغام میں sender_type، sender_did، دوسرے فریق کے ایجنٹ کا sender_name، content، content_json اور citations_json ہوتے ہیں۔ یہ صرف حقائق لوٹاتا ہے؛ Markdown کلائنٹ قاری کی زبان میں بناتا ہے (کون “آپ” ہیں اور کون “آپ کا ایجنٹ”)۔

POST /api/v1/conversations/{id}/messages درخواست:

{
  "content_type": "text",
  "content": "X100 کے رجسٹر 0x40 کے لیے کون سا فنکشن کوڈ چاہیے؟",
  "in_reply_to": null,
  "via": "web"
}

جواب:

{
  "id": "01HXKQ...",
  "delivery_status": "queued",
  "stream_url": "/api/v1/conversations/01HX.../messages/01HXK.../stream"
}

اجازتوں کا انتظام

GET    /api/v1/permissions/pending               # زیرِ التوا L2/L3 درخواستیں
POST   /api/v1/permissions/{id}/decide           # منظور / مسترد کریں
GET    /api/v1/permissions/history               # تاریخچہ

POST /api/v1/permissions/{id}/decide درخواست:

{
  "decision": "allow_always",       // allow_once | allow_always | deny | deny_always
  "scope": "peer_action"            // فیصلے کا دائرہ
}

زیرِ التوا درخواستوں میں جن کا action='connect' ہے وہ کنکشن کی درخواستیں ہیں (اجنبی peer کے پہلے رابطے پر A2A داخلی راستہ انہیں بناتا ہے)۔ منظوری (allow_*) اس peer کو peer_contacts میں لکھ کر کنکشن بنا دیتی ہے؛ انکار سے نہیں بنتا۔

جن کا action='ask' ہے وہ پہلے سے جڑے peer کے زیرِ التوا سوال ہیں — جب Agent کی پالیسی اُس سوال پر ask_user طے کرتی ہے تب A2A داخلی راستہ انہیں بناتا ہے (دیکھیں 03-protocol.md کا «معلق ان باکس (غیر حاضری میں جواب)»)۔ منظوری (allow_*) پر Agent اُس معلق سوال کا جواب دیتا ہے؛ انکار پر نہیں دیتا۔

allow_always / deny_always مستقل فیصلے ہیں۔ رکے ہوئے سوال پر یہ اس رابطے کی policy override کے شروع میں { action: 'ask', decision: 'allow' | 'deny' } قاعدہ لکھتے (یا بدلتے) ہیں، اس لیے اس peer کے بعد کے سوالوں کا جواب دیا جاتا ہے یا انکار کیا جاتا ہے، روکا نہیں جاتا؛ مالک رابطے کے policy پینل میں یہ قاعدہ دیکھ اور منسوخ کر سکتا ہے۔ کنکشن کی درخواست پر deny_always سے اس peer کی بعد کی کنکشن درخواستیں انباکس تک پہنچنے کے بجائے خاموشی سے رد ہو جاتی ہیں؛ DID کے ذریعے peer کو ہاتھ سے شامل کرنا اب بھی کام کرتا ہے۔

GET /pending ہر درخواست کے ساتھ ایک description دیتا ہے (کنکشن کی درخواست میں پہل کرنے والا اور اس کا پہلا پیغام؛ سوال میں پوچھنے والا اور سوال کا متن) تاکہ مالک فیصلہ کر سکے۔

منصوبہ یادداشت (Claude Code انضمام سے متعلق)

GET    /api/v1/projects/{project_id}/peers              # اس منصوبے میں جن peer کی یادداشت ہے (join سے name/did سمیت)  ✅ نافذ
POST   /api/v1/projects/{project_id}/peers              # peer کو منصوبے میں صراحتاً درج کریں   🔜 backlog
GET    /api/v1/projects/{project_id}/peers/{peer_id}/facts        # ✅ نافذ
PUT    /api/v1/projects/{project_id}/peers/{peer_id}/facts        # ✅ نافذ
GET    /api/v1/projects/{project_id}/peers/{peer_id}/decisions    # ✅ نافذ
PUT    /api/v1/projects/{project_id}/peers/{peer_id}/decisions    # ✅ نافذ

مفہوم سے متعلق نوٹ (v0.1):

علمی ذخیرہ (RAG)

GET    /api/v1/knowledge-bases                                  # میرے علمی ذخیروں کی فہرست
POST   /api/v1/knowledge-bases                                  # نیا بنائیں
PATCH  /api/v1/knowledge-bases/{kb_id}                          # نام/تفصیل بدلیں، اور بیرونی Agent کے لیے کھلا ہے یا نہیں
DELETE /api/v1/knowledge-bases/{kb_id}                          # اس کے تمام دستاویزات اور سمتیوں سمیت مٹا دیں

GET    /api/v1/knowledge-bases/{kb_id}/documents                # صفحہ بندی: ?limit=&offset=
POST   /api/v1/knowledge-bases/{kb_id}/documents                # multipart اپلوڈ، خانے کا نام file
DELETE /api/v1/knowledge-bases/{kb_id}/documents/{doc_id}
POST   /api/v1/knowledge-bases/{kb_id}/documents/{doc_id}/retry # دوبارہ فہرست بند کریں

POST /knowledge-bases کا body { name, description? } ہے (name 1–255 حروف)، جواب 201 + { knowledge_base }۔

PATCH /knowledge-bases/{kb_id} کا body { name?, description?, shared_with_peers? } ہے، جواب { knowledge_base }۔ shared_with_peers صرف یہیں بدلا جا سکتا ہے، بناتے وقت قبول نہیں ہوتا: ہر ذخیرہ «صرف اپنے لیے» جنم لیتا ہے، اور اسے باہر کھولنا ایک دوسرا، جان بوجھ کر کیا گیا کام ہے۔

shared_with_peers یہ طے کرتا ہے کہ اندر آیا A2A سوال اس ذخیرے میں تلاش کر سکتا ہے یا نہیں، اور طے شدہ false ہے۔ مالک جب ویب پر بات کرتا ہے تو اس پر اثر نہیں پڑتا — وہ ہمیشہ سب کچھ تلاش کر سکتا ہے۔ یہ سرحد تلاش کے دائرے پر پڑنی چاہیے، پرامپٹ میں نہیں: سامنے والے کا سوال اور مالک کی ہدایات ماڈل تک ایک ہی طرح کے متن کے طور پر پہنچتے ہیں، سو «Agent خود فیصلہ کر لے گا کہ کیا بتانا ہے» کوئی سرحد ہے ہی نہیں۔ اسی سبب اندر آیا A2A سوال کسی طویل مدتی یادداشت کو نہیں بلاتا — طویل مدتی یادداشت مالک کی اپنی گفتگو سے کشید کی گئی ہے، اور اس کا ایک اندراج بھی اس انسٹنس سے باہر جانے کے قابل نشان زد نہیں۔

GET /knowledge-bases { knowledge_bases } لوٹاتا ہے اور صفحوں میں نہیں بٹتا: ایک صارف کے ذخیرے ہاتھ سے بنتے ہیں، ان کی تعداد محدود ہے۔

GET /{kb_id}/documents { documents, total } لوٹاتا ہے۔ limit کا طے شدہ 50، زیادہ سے زیادہ 100؛ offset کا طے شدہ 0؛ ترتیب id (ULID) کے نزولی حساب سے، یعنی تازہ ترین پہلے — واحد اور یقینی ترتیب ہی offset کی کھڑکی کو سطریں چھوڑنے یا دہرانے سے روکتی ہے۔ total پوری گنتی ہے، اس صفحے کی نہیں۔ نہ پڑھے جا سکنے والے limit/offset طے شدہ قدر لے لیتے ہیں۔ اس حصے کی یہی ایک فہرست ہے جو بے حد بڑھتی ہے، کیونکہ اپلوڈ کی منزل علمی ذخیرہ ہی ہے۔

اپلوڈ multipart/form-data سے ہوتا ہے، فائل کے خانے کا نام ہمیشہ file رہتا ہے، اور ایک فائل کی حد 10 MB ہے (اس سے زیادہ پر 400 bad_request)۔ Content-Type فارم میں آیا ہو تو وہی، نہ ہو تو ایکسٹینشن سے اخذ۔ جواب 201 + { document }، اور تب تک status processing ہو چکا ہوتا ہے: ٹکڑے کرنا، سمتی نمائندگی اور Qdrant میں لکھنا — یہ سب جواب کے بعد غیر ہم وقت چلتے ہیں، اپلوڈ کا راستہ ان کا انتظار نہیں کرتا۔ سو کلائنٹ status بدلنے تک دستاویزات کی فہرست ٹٹولتا رہتا ہے۔

status کی قدریں:

قدر مفہوم
processing محفوظ ہو چکا، ٹکڑے/سمتیں بن رہی ہیں۔ اپلوڈ اور retry کے بعد کی ابتدائی حالت
ready تلاش کیا جا سکتا ہے۔ chunk_count اُس دستاویز کے ٹکڑوں کی تعداد ہے
failed فہرست بندی ناکام (تجزیہ، embedding کلید کی کمی، یا سمتی ذخیرے میں لکھنے کی ناکامی)

POST /{doc_id}/retry شے-ذخیرے سے اصل فائل لا کر دوبارہ فہرست بند کرتا ہے؛ پہلے اُس دستاویز کی موجود سمتیں مٹاتا ہے، سو ٹکڑے دُہرے نہیں ہوتے۔ اصل فائل باقی نہ رہی ہو (storage_key خالی) یا دستاویز اب بھی processing میں ہو تو 400 لوٹتا ہے۔ جواب { document }، status پھر سے processing اور chunk_count صفر۔

علمی ذخیرہ مٹانے پر اس کی تمام دستاویز سطریں اور Qdrant کی سمتیں سلسلہ وار مٹ جاتی ہیں؛ اکیلی دستاویز مٹانے پر اس کی سمتیں اور شے-ذخیرے کی اصل فائل بھی صاف ہوتی ہے۔ سمتوں یا اشیا کی صفائی ناکام ہو تب بھی ڈیٹابیس سے حذف نہیں رکتا — یتیم شے چھوڑنا اُس سطر سے بہتر ہے جو مٹ چکے مواد کی طرف اشارہ کرے۔

تمام راستے user.sub تک محدود ہیں: کسی اور کا ذخیرہ یا دستاویز کھولنے پر 404 ملتا ہے (403 نہیں، تاکہ اس کا وجود ہی ظاہر نہ ہو)۔

ریورس پراکسی کو 10 MB کا body گزرنے دینا ہو گا۔ infra/nginx.conf میں /api/ پر client_max_body_size 10m ہے؛ nginx کے طے شدہ 1 MB پر 1–10 MB کی فائلیں گیٹ وے تک پہنچتی ہی نہیں اور براؤزر کو nginx کا اپنا 413 صفحہ ملتا ہے۔

منسلکات

POST   /api/v1/attachments                       # multipart upload
GET    /api/v1/attachments/{id}/content          # فائل خود، اپ لوڈ کرنے والے کو یا اس کے پیغام کی گفتگو کے شرکاء کو

اپ لوڈ شروع میں کسی پیغام سے منسلک نہیں ہوتا؛ POST /conversations/{id}/messages اسے attachment_ids (زیادہ سے زیادہ 5) کے ذریعے منسلک کرتا ہے، اور صرف بھیجنے والے کے اپنے، ابھی نہ بھیجے گئے اپ لوڈ ہی منسلک ہو سکتے ہیں۔ بغیر متن کے صرف منسلکات والا پیغام بھی درست ہے۔ تصاویر ان کے بائٹس سے پہچانی جاتی ہیں (PNG / JPEG / GIF / WebP، ہر ایک زیادہ سے زیادہ 5 MB) اور ماڈل تک تصویری بلاک کے طور پر پہنچتی ہیں؛ PDF / DOCX / XLSX / متن اپ لوڈ پر پڑھے جاتے ہیں (زیادہ سے زیادہ 10 MB) اور ان کا متن ہر باری میں <attachment> کے طور پر پیغام کے ساتھ جاتا ہے۔ SVG کو متن سمجھا جاتا ہے اور کبھی تصویر کی طرح ان لائن نہیں دکھایا جاتا۔ content صرف پہچانی گئی تصویر کو ان لائن دیتا ہے؛ باقی سب application/octet-stream ڈاؤن لوڈ ہوتا ہے۔ فہرست کے ہر پیغام میں attachments ہوتا ہے۔ گفتگو حذف کرنے سے اس کے منسلکات بھی حذف ہو جاتے ہیں۔

استعمال

GET /api/v1/usage?month=YYYY-MM    # ایک مہینے میں ایجنٹ ٹرن کا ٹوکن استعمال؛ ڈیفالٹ موجودہ مہینہ

{ month, rows } لوٹاتا ہے۔ ہر قطار فراہم کنندہ × ماڈل × سامعین کا ایک گروپ ہے: turns، unreported (وہ ٹرن جن کا استعمال فراہم کنندہ نے نہیں بتایا)، failed، input_tokens، output_tokens، cache_read_tokens، cache_write_tokens۔ مہینے UTC کے مطابق کٹتے ہیں؛ اگر month کی شکل YYYY-MM نہ ہو تو 400 ملتا ہے۔ صرف کال کرنے والے کا اپنا ڈیٹا لوٹتا ہے۔

WebSocket

راستہ

WSS  /ws?token=<access_token>&device_id=<device_id>

ہینڈ شیک کی توثیق REST جیسی ہی ہے، «دستخط ٹھیک ہے تو جانے دو» نہیں: typ access ہونا چاہیے، sid کسی ایسے سیشن کی طرف ہونا چاہیے جو اب بھی موجود ہو، اور کھاتہ disabled نہ ہو۔ تینوں لازم ہیں — ان کے بغیر ممنوع کھاتے کو بس اتنا چاہیے کہ اس کا ٹوکن نہ بیتا ہو، اور وہ جڑتا رہ کر پیغام پاتا رہے گا، جبکہ پابندی خود (تمام سیشن مٹانا) اس راستے پر کچھ بھی منسوخ نہیں کرتی۔ پابندی اُس صارف کے پہلے سے کھلے socket بھی بند کرتی ہے: nginx /ws کو ایک دن کا proxy_read_timeout دیتا ہے، اور اگلا ہینڈ شیک روکنے سے پہلے سے جڑا کنکشن نہیں رکتا۔

ہینڈ شیک پر جانچا گیا ٹوکن socket کی ضمانت ہمیشہ کے لیے نہیں دیتا: access token کی مدت ختم ہوتے ہی سرور 4001 کوڈ سے کنکشن بند کر دیتا ہے، اور کلائنٹ دوبارہ جڑنے سے پہلے ٹوکن تازہ کرتا ہے۔ لاگ آؤٹ، یا refresh token کا دوبارہ استعمال پکڑا جانا، جس سیشن کو مٹاتا ہے اس کے تحت کھلے socket 1008 کوڈ سے بند کر دیتا ہے۔

پیغام کی صورت

تمام WS پیغام JSON ہیں اور ان میں type خانہ ہوتا ہے:

{ "type": "message.new", "data": { /* ... */ } }

کلائنٹ → سرور

ping                          // دھڑکن
subscribe.conversation        // کسی گفتگو کی رکنیت (سرور جانچتا ہے کہ آپ شریک ہیں)
unsubscribe.conversation
typing.start                  // صرف اُنہی گفتگوؤں پر اثر کرتا ہے جن کی رکنیت لی گئی ہو
typing.stop
read.ack                      // پڑھے جانے کی تصدیق

typing.* کی نشریات اُسی socket کی رکنیت کی فہرست کے مطابق ہوتی ہیں۔ جب رکنیت پر دروازہ ہو اور ٹائپنگ کے واقعات پر نہ ہو، تو کسی گفتگو کا id جان لینا ہی کافی ہے کہ اس میں «فلاں لکھ رہا ہے» ڈال دیا جائے — وہ بھی اپنے ہی صارف نام کے ساتھ۔

سرور → کلائنٹ

pong
message.new                   // نیا پیغام
message.updated               // محفوظ، ابھی بھیجا نہیں جاتا
message.deleted               // محفوظ، ابھی بھیجا نہیں جاتا
typing.update                 // کون لکھ رہا ہے
presence.update               // رابطے کا آنا جانا
permission.request            // اجازت کی درخواست جس پر صارف کو فیصلہ کرنا ہے
agent.status                  // میرا Agent کسی رابطے کو جواب دے رہا ہے: {conversation_id, state: thinking|tool|idle, tool?}؛ عبارت کلائنٹ بناتا ہے
conversation.updated          // کسی رابطے نے {conversation_id} میں لکھا (شاید نئی گفتگو)؛ کلائنٹ اپنی فہرست تازہ کرتا ہے

message.new مثال:

{
  "type": "message.new",
  "data": {
    "id": "01HXKQ...",
    "conversation_id": "01HX...",
    "sender_type": "peer_agent",
    "sender_id": "01HY...",
    "sender_did": "did:web:acme.com:agents:support",
    "content_type": "text",
    "content": "0x03، یعنی Read Holding Registers سے…",
    "citations": [
      {
        "source": "X100 مواصلاتی دستی v3.2",
        "page": 87,
        "url": "https://acme.com/manuals/x100-v3.2.pdf#page=87",
        "trust_level": "authoritative"
      }
    ],
    "language": "ur",
    "created_at": "2024-11-15T14:30:00Z"
  }
}

permission.request مثال:

{
  "type": "permission.request",
  "data": {
    "id": "01HXP...",
    "level": "L2",
    "action": "share_files",
    "scope": {
      "peer": "did:web:acme.com:agents:support",
      "paths": ["src/modbus/"],
      "exclude": [".env", "secrets/"]
    },
    "peer_name": "ABC Agent",
    "peer_did": "did:web:acme.com:agents:support",
    "requested_at": "2024-11-15T14:30:00Z"
  }
}

پے لوڈ میں description نہیں ہے، اور یہ جان بوجھ کر ہے۔ سرور نہیں جانتا کہ پڑھنے والا کس زبان میں پڑھتا ہے، سو وہ صرف ساختہ حقائق بھیجتا ہے (action + peer کی شناخت + محفوظ scope)، اور منظوری کے وقت نظر آنے والا جملہ کلائنٹ اپنی i18n سے بناتا ہے (packages/client/src/lib/permission-text.ts)۔ یہ معاہدہ @confer/shared کے permissionRequestEventSchema کا اپنا ہے: گیٹ وے بھیجنے سے پہلے اسی سے parse کرتا ہے، کلائنٹ پانے کے بعد اسی سے۔

GET /api/v1/permissions/pending کی ہر سطر بالکل اسی شکل کی ہے (ایک اضافی decision خانے کے ساتھ) اور اسی بنانے والے سے آتی ہے، سو ٹٹول کر ملی سطر اور socket سے آئی سطر بائٹ در بائٹ ایک جیسی ہوتی ہیں۔

SSE (LLM streaming)

GET  /api/v1/conversations/{id}/messages/{msg_id}/stream
Accept: text/event-stream

واقعات کی اقسام:

event: token
data: {"text":"0x03، "}

event: token
data: {"text":"0x03 "}

event: tool_call
data: {"tool":"agent_network.ask_peer","args":{...}}

event: tool_result
data: {"result":"..."}

event: citation
data: {"source":"X100 مواصلاتی دستی v3.2","page":87}

event: done
data: {"finish_reason":"stop","tokens_used":523}

A2A API (باہر کی طرف — Confer کے دوسرے انسٹنس اسے پکارتے ہیں)

تفصیل docs/03-protocol.md میں۔ یہاں صرف راستے گنائے گئے ہیں۔

ایک ہی سابقے کے نیچے دو بائنڈنگ ساتھ رہتی ہیں اور ایک ہی دروازوں کے سلسلے (a2a/inbound.ts) سے گزرتی ہیں؛ فرق صرف تار پر کی صورت کا ہے۔

A2A کی معیاری HTTP+JSON بائنڈنگ (راستے تفصیلات کی §11.3 سے بعینہٖ لیے گئے ہیں؛ Agent Card اسی کا اعلان کرتا ہے):

POST   /a2a/v1/message:send              # SendMessage → Task
GET    /a2a/v1/tasks/{id}                # GetTask
GET    /a2a/v1/tasks                     # ListTasks(کرسر سے صفحہ بندی)
POST   /a2a/v1/tasks/{id}:cancel         # CancelTask → TaskNotCancelable
POST   /a2a/v1/message:stream            # SendStreamingMessage → SSE: Task, then statusUpdate
GET|POST /a2a/v1/tasks/{id}:subscribe    # SubscribeToTask → SSE
POST   /a2a/v1/tasks/{id}/pushNotificationConfigs            # → TaskPushNotificationConfig
GET    /a2a/v1/tasks/{id}/pushNotificationConfigs            # → {configs, nextPageToken}
GET    /a2a/v1/tasks/{id}/pushNotificationConfigs/{configId}
DELETE /a2a/v1/tasks/{id}/pushNotificationConfigs/{configId} # idempotent
GET    /a2a/v1/extendedAgentCard         # غیر نافذ → UnsupportedOperation

اسٹریمنگ اور پش۔ message:stream انہی جانچوں اور دروازوں سے گزرتا ہے جن سے message:send، اور text/event-stream سے جواب دیتا ہے، ہر StreamResponse کے لیے ایک data: سطر: پہلے {task}، پھر کام ختم ہونے (یا کسی شخص کے انتظار میں AUTH_REQUIRED تک پہنچنے) پر ایک {statusUpdate}، پھر اسٹریم بند۔ اس راستے پر جواب بغیر جزوی آؤٹ پٹ کے ایک LLM باری ہے، اس لیے باریکی کام کے واقعات کی سطح پر ہے۔ :subscribe GET اور POST دونوں قبول کرتا ہے (proto میں GET، spec کی جدولوں میں POST) اور ختم شدہ کام پر UnsupportedOperation دیتا ہے۔ پش: peer کسی کام پر webhook درج کرتا ہے (یا message:send میں configuration.taskPushNotificationConfig دیتا ہے)، اور کام جس حالت میں بھی پہنچے وہ ایک بار {task} کی صورت میں POST ہوتی ہے، Authorization: {scheme} {credentials} کے ساتھ اور token X-A2A-Notification-Token میں۔ webhook لازماً https ہو اور عوامی پتے پر حل ہو (باہر جانے والے A2A والا وہی SSRF اصول، ترسیل پر دوبارہ جانچا جاتا ہے، ری ڈائریکٹ نہیں مانے جاتے)؛ اسناد مہر بند رکھی جاتی ہیں اور صرف scheme لوٹایا جاتا ہے۔ فی کام زیادہ سے زیادہ 5، جنہیں صرف درج کرنے والا peer دیکھ یا حذف کر سکتا ہے۔

Confer کی اپنی بولی (انسٹنسوں کے درمیان؛ /.well-known/agents.json سے دریافت ہوتی ہے):

POST   /a2a/v1/messages                  # بیرونی Agent کے پیغام لیتا ہے
GET    /a2a/v1/stream/{message_id}       # جواب رواں صورت میں کھینچتا ہے (SSE)
GET    /a2a/v1/agent-facts/{agent_did}   # عوامی AgentFacts

تمام A2A راستوں پر HTTP پیغام کے دستخط کی جانچ لازم ہے، سوائے agent-facts کے: /.well-known/agents.json کی طرح یہ اسی نمائش والی عوامی دریافت دستاویز ہے، اس لیے غیر عوامی یا معطل Agent ہمیشہ 404 دیتا ہے۔ اس کی capabilities میں صرف وہی صلاحیتیں ہوتی ہیں جو مالک نے NANDA شکل ({type, scope, languages}) میں محفوظ کی ہیں۔

.well-known endpoints

GET    /.well-known/did.json                # بنیادی DID دستاویز
GET    /.well-known/agents.json             # اس انسٹنس کے تمام عوامی Agent کی فہرست
GET    /.well-known/agent-card.json         # A2A معیاری Agent Card (صرف جب انسٹنس پر ایک ہی عوامی Agent ہو)
GET    /.well-known/openid-configuration    # آگے چل کر: OIDC مطابقت (v2)

A2A معیاری Agent Card (باہمی عمل کی دریافت پرت)

GET    /agents/{username}/agent-card.json   # اُس Agent کا A2A معیاری Card
GET    /.well-known/agent-card.json         # وہی، صرف جب اس انسٹنس پر ایک ہی عوامی Agent ہو

Linux Foundation کے Agent2Agent v1.0 کے AgentCard کے مطابق (خانے a2aproject/A2A کی specification/a2a.proto @ v1.0.1 سے، proto3 کے JSON نقشے میں، اسی لیے camelCase)۔ مقصد یہ ہے کہ A2A کا نظام اس انسٹنس کے Agent کو دریافت کر سکے — نام تو ملتے تھے مگر پروٹوکول نہیں: سامنے والے کی دریافت دستاویز /.well-known/agent-card.json پر ہوتی ہے، جبکہ اس انسٹنس پر صرف /.well-known/agents.json تھا۔

چند جان بوجھ کر کیے گئے فیصلے:

پیغام پرت (Task کا مفہوم)

POST /a2a/v1/message:send تفصیلات کا SendMessageRequest لیتا ہے اور Task لوٹاتا ہے۔ ایک task یعنی ایک اندر آیا سوال: اس کا id اُسی پیغام کا id ہے، contextId وہ گفتگو ہے جس میں وہ محفوظ ہوتا ہے، اور حالت آگے پیش آنے والی باتوں سے نکلتی ہے — اُسی حقیقت کا سایہ رکھنے کے لیے الگ tasks جدول نہیں بنایا جاتا۔

Confer کا غیر ہم وقت + رضامندی کے دروازے والا ڈھانچہ تفصیلات کی حالت مشین پر ٹھیک بیٹھتا ہے:

صورت حالت
Agent جواب دے رہا ہے TASK_STATE_WORKING
جواب مکمل TASK_STATE_COMPLETED
یہ باری شروع ہی نہیں ہو سکتی (ماڈل ترتیب نہیں، یا فراہم کنندہ نے خرابی دی) TASK_STATE_FAILED
ask_user پالیسی سے معلق، مالک کی منظوری کا انتظار TASK_STATE_AUTH_REQUIRED(تعطل کی حالت، آخری نہیں)
مالک نے انکار کر دیا TASK_STATE_REJECTED

دو جگہ لوٹانے کو task ہے ہی نہیں، کیونکہ سطر بنی ہی نہیں: اجنبی peer (جو معلق کنکشن درخواست بن کر رہتا ہے) اور پالیسی سے براہِ راست انکار۔ دونوں 403 PERMISSION_DENIED دیتے ہیں اور ErrorInfo.metadata.confer_status سے الگ پہچانے جاتے ہیں — ایسا task id گھڑ دینا جو اگلی پکار پر 404 دے، اس سے بدتر ہوتا۔

باقی رویہ تفصیلات سے نکتہ بہ نکتہ ملایا گیا ہے: خرابی کا جسم google.rpc.Status کی شکل کا ہے اور اس میں ErrorInfo.reason ہمیشہ ہوتا ہے (کئی A2A خرابیاں ایک ہی HTTP کوڈ بانٹتی ہیں، اور reason ہی واحد خانہ ہے جو انہیں الگ کرتا ہے)؛ جس کلائنٹ نے لازمی توسیع کا اعلان نہ کیا ہو اسے §3.3.4 کے مطابق ExtensionSupportRequiredError ملتا ہے، نہ کہ کچھ نہ سمجھانے والا 401؛ historyLength=0 کا مطلب ہے پورا خانہ ہی چھوڑ دینا، خالی صف بھیجنا نہیں؛ اور nextPageToken ہمیشہ موجود رہتا ہے، اگلا صفحہ نہ ہو تو خالی سٹرنگ کے طور پر۔

دو جان بوجھ کر کیے گئے انحراف، دونوں کوڈ کے تبصروں میں درج ہیں: روکنے والے message:send کے انتظار کی بالائی حد ہے (55 سیکنڈ، اس کے بعد اب بھی WORKING والا task لوٹا کر کلائنٹ کو ٹٹولنے دیا جاتا ہے) — §3.2.2 وقت سے نکلنے کا راستہ نہیں دیتی، جبکہ LLM کی ایک پکار کی کوئی بالائی حد نہیں؛ اور messageId سے idempotency (§3.3.1 کا ایک MAY) نہیں کی گئی، کیونکہ کرایہ دار محفوظ منفرد کلید کو مالک کا دائرہ چاہیے، اور پہلے پیغام کی تار صورت میں وہ ملتا نہیں۔

Webhooks (اختیاری، v1.5+)

بیرونی نظام واقعات کی رکنیت لے سکیں، اس کے لیے:

POST   /api/v1/webhooks
GET    /api/v1/webhooks
DELETE /api/v1/webhooks/{id}

معاون واقعات: message.new.peer، permission.granted، thread.archived۔

شرحِ حد کی پالیسی

راستہ حد
/api/v1/auth/login 10/منٹ فی IP
/api/v1/auth/register 3/گھنٹہ فی IP
/api/v1/conversations/*/messages POST 60/منٹ فی صارف
/a2a/v1/* 60/منٹ فی IP اور پاتھ، پورے /a2a/v1 پر 300/منٹ فی IP
A2A سوالات (دونوں بائنڈنگز) فی peer DID فی مالک 20/منٹ اور 300/دن؛ فی peer ڈومین 60/منٹ۔ حد سے زیادہ → 429 / RESOURCE_EXHAUSTED، Retry-After کے ساتھ
/api/v1/consult/{peer} POST 30/منٹ فی صارف
WSS فی صارف زیادہ سے زیادہ 10 بیک وقت کنکشن

حد پار ہونے پر جواب:

HTTP/1.1 429 Too Many Requests
Retry-After: 30
Content-Type: application/json

{ "error": { "code": "rate_limited", "message": "Too many requests" } }

مشورہ API (صارف کی پہل پر باہر جاتا A2A)

صارف (یا اس کی جانب سے MCP سرور) جو پہلے سے رابطہ ہو ایسے peer Agent سے اپنی پہل پر سوال کر سکے اور بعد میں غیر ہم وقت جواب لے سکے۔ دستخط اور ترسیل پوری کی پوری گیٹ وے کے اندر ہوتی ہے؛ نجی کلید گیٹ وے سے باہر نہیں جاتی۔

«گفتگو API» سے فرق: /api/v1/conversations + /api/v1/stream اپنے ہی مقامی LLM معاون سے بات کرنا ہے؛ /api/v1/consult وہ ہے جو A2A سے کسی اور کے Agent کو جاتا ہے۔

POST /api/v1/consult/:peerId

type='consult' والی گفتگو شروع یا جاری کرتا ہے (ہر peer کے لیے وہی ایک گفتگو دوبارہ کام آتی ہے)، اور message.type='question' پر دستخط کر کے اسے پہنچاتا ہے۔

// درخواست کا body (consultRequestSchema)
{ "question": "کلیدیں کیسے گھمائی جائیں؟", "code_context": "…اختیاری کوڈ…", "language": "ur" }
جواب مفہوم
201 { conversation_id, message_id, status: "sent" } دستخط شدہ اور پہنچا دیا گیا
502 { ..., status: "failed", error } ترسیل ناکام (peer آف لائن / راستہ نہیں / دستخط کا مسئلہ)
403 not_a_contact peer موجودہ صارف کا رابطہ نہیں ہے

GET /api/v1/consult/:conversationId/reply?after=:messageId&wait=:seconds

peer کے غیر ہم وقت جواب کے لیے طویل انتظار (جواب اندر آتے /a2a/v1/messages سے اپنے thread_id سمیت آتا ہے، اور گیٹ وے اسے اُسی دھاگے پر واپس ٹانک دیتا ہے)۔ wait کی حد 55 سیکنڈ۔

GET /api/v1/consult/:conversationId

اُس مشورہ دھاگے کی پوری پیغام تاریخ لوٹاتا ہے (زیادہ سے زیادہ 200)۔

معاہدہ: اندر آتا A2A صرف message.type==='question' پر مقامی agent کا خودکار جواب چلاتا ہے؛ answer/notification صرف محفوظ اور نشر ہوتے ہیں، تاکہ مشورے کا جواب لامتناہی سوال جواب نہ چھیڑ دے۔

← Confer پر واپس A2A · DID:web · RFC 9421 · NANDA