نقطة
  • الرئيسية
  • الخدمات
  • من نحن
  • الكتابات
  • تسجيل الدخول
الكتابات/tutorial/2026/06
● Tutorial30 يونيو 2026·30 دقيقة

بناء وكيل ACP بلغة TypeScript: اربط أي محرّر بوكيل الذكاء الاصطناعي الخاص بك (2026)

تعلّم كيف تبني وكيل برمجة بالذكاء الاصطناعي يتحدّث بروتوكول Agent Client Protocol (ACP) بلغة TypeScript. ستنفّذ initialize والجلسات والبث المباشر وطلبات الإذن، ثم تربطه بمحرّر Zed أو Neovim أو أي عميل ACP.

Noqta Team
Noqta Team
Author
·EN · FR · AR

فعلها LSP لخوادم اللغة. ويفعلها ACP لوكلاء البرمجة بالذكاء الاصطناعي. بروتوكول Agent Client Protocol معيار مفتوح يقوم على JSON-RPC يتيح لأي محرّر التحدّث مع أي وكيل. في هذا الدرس ستبني وكيلك المتوافق مع ACP بلغة TypeScript وتربطه بمحرّر Zed.

ماذا ستتعلّم

بنهاية هذا الدرس ستكون قادراً على:

  • فهم بنية Agent Client Protocol (ACP) وسبب وجوده
  • إعداد مشروع وكيل ACP بلغة TypeScript من الصفر
  • تنفيذ واجهة Agent الأساسية: initialize وnewSession وprompt
  • بثّ المخرجات لحظياً إلى المحرّر عبر session updates
  • طلب إذن المستخدم قبل تشغيل أي أداة
  • ربط الوكيل بمحرّر Zed واختباره من البداية إلى النهاية

المتطلبات المسبقة

قبل البدء، تأكّد من توفّر التالي:

  • تثبيت Node.js 20+ (node --version)
  • معرفة بـ TypeScript (الواجهات، async/await، وحدات ES)
  • محرّر شيفرة — نستخدم Zed للاختبار النهائي، لكن VS Code أو Cursor مناسبان لكتابة الشيفرة
  • فهم أساسي لـ JSON-RPC ولتدفقات الإدخال/الإخراج القياسية
  • اختيارياً، مفتاح API من Anthropic إذا أردت أن يستدعي الوكيل نموذجاً حقيقياً

ما هو Agent Client Protocol؟

Agent Client Protocol (ACP) معيار مفتوح يحدّد كيف يتواصل محرّر الشيفرة (العميل) مع وكيل برمجة بالذكاء الاصطناعي (الوكيل). يقوم على JSON-RPC 2.0 ويعمل عبر stdio — فالوكيل عملية فرعية، وتتدفّق الرسائل على شكل JSON مفصول بأسطر عبر الإدخال والإخراج القياسيين.

تخيّله بمثابة بروتوكول خادم اللغة (LSP) لوكلاء الذكاء الاصطناعي. قبل LSP كان كل محرّر يحتاج تكاملاً مخصّصاً لكل لغة. وقبل ACP كان كل محرّر يحتاج تكاملاً مخصّصاً لكل وكيل. يحوّل ACP مشكلة التكامل من N×M إلى N+M: اكتب وكيلك مرّة واحدة، وسيعمل في Zed وNeovim وJetBrains وEmacs وأي عميل ACP آخر.

أين يقع ACP

يكمّل ACP معيارين آخرين قد تكون تعرفهما:

  • MCP (Model Context Protocol) يربط الوكلاء بـ الأدوات والبيانات.
  • A2A (Agent-to-Agent) يربط الوكلاء بـ وكلاء آخرين.
  • ACP يربط الوكلاء بـ المحرّر والإنسان في الحلقة.

الطرق الأساسية الثلاث

يجب أن يستجيب وكيل ACP لثلاثة طلبات كحدّ أدنى:

1. initialize   →  التفاوض على نسخة البروتوكول + القدرات
2. session/new  →  إنشاء جلسة محادثة
3. session/prompt →  معالجة دور المستخدم، البثّ، وإرجاع stop reason

أثناء دور المطالبة، يرسل الوكيل إشعارات session/update إلى العميل (نصّ مبثوث، أفكار، استدعاءات أدوات)، ويمكنه إرسال session/request_permission قبل أي إجراء حسّاس.


الخطوة 1: إعداد المشروع

أنشئ مشروعاً جديداً وثبّت حزمة SDK الرسمية بلغة TypeScript.

mkdir acp-hello-agent && cd acp-hello-agent
npm init -y
npm install @zed-industries/agent-client-protocol
npm install -D typescript tsx @types/node

أنشئ ملف tsconfig.json مهيّأً لوحدات ES الحديثة:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "outDir": "dist"
  },
  "include": ["src"]
}

اضبط "type": "module" في package.json ليعامل Node ملفاتك كوحدات ES:

{
  "name": "acp-hello-agent",
  "type": "module",
  "bin": { "acp-hello-agent": "dist/agent.js" },
  "scripts": {
    "dev": "tsx src/agent.ts",
    "build": "tsc"
  }
}

لماذا stdio مهمّ. يتواصل وكلاء ACP عبر الإدخال والإخراج القياسيين، وليس عبر الشبكة افتراضياً. هذا يعني أن console.log ممنوع في شيفرة الوكيل — فأي شيء تطبعه على stdout يصبح رسالة بروتوكول تالفة. استخدم console.error (stderr) لأغراض التتبّع بدلاً من ذلك.


الخطوة 2: فهم واجهة Agent

تكشف الحزمة واجهة Agent. هذا هو الشكل الذي ستنفّذه (مبسّطاً):

interface Agent {
  initialize(params: InitializeRequest): Promise<InitializeResponse>;
  newSession(params: NewSessionRequest): Promise<NewSessionResponse>;
  prompt(params: PromptRequest): Promise<PromptResponse>;
  cancel(params: CancelNotification): Promise<void>;
  // اختياري: loadSession, authenticate, setSessionMode, ...
}

تربط تنفيذك بالمحرّر عبر AgentSideConnection وتدفّق يُنشأ بـ ndJsonStream (JSON مفصول بأسطر). كائن الاتصال هو أيضاً وسيلتك لإرسال التحديثات إلى العميل.


الخطوة 3: تنفيذ initialize وnewSession

أنشئ src/agent.ts. ابدأ بطرق المصافحة. يتفاوض استدعاء initialize على نسخة البروتوكول ويعلن عمّا يستطيع وكيلك فعله.

import {
  Agent,
  AgentSideConnection,
  ndJsonStream,
  PROTOCOL_VERSION,
  type InitializeRequest,
  type InitializeResponse,
  type NewSessionRequest,
  type NewSessionResponse,
  type PromptRequest,
  type PromptResponse,
  type CancelNotification,
} from "@zed-industries/agent-client-protocol";
import { Readable, Writable } from "node:stream";
 
class HelloAgent implements Agent {
  // الاتصال يتيح لنا دفع session updates إلى العميل.
  constructor(private conn: AgentSideConnection) {}
 
  // نتتبّع الجلسات النشطة كي نتمكّن من إلغائها لاحقاً.
  private sessions = new Map<string, AbortController>();
 
  async initialize(params: InitializeRequest): Promise<InitializeResponse> {
    return {
      protocolVersion: PROTOCOL_VERSION,
      agentCapabilities: {
        // يمكننا إعادة تحميل الجلسات السابقة إلى الذاكرة.
        loadSession: false,
        // نعلن أنواع محتوى المطالبة التي نقبلها.
        promptCapabilities: { image: false, audio: false, embeddedContext: true },
      },
      authMethods: [], // لا حاجة لمصادقة في هذا العرض التوضيحي.
    };
  }
 
  async newSession(params: NewSessionRequest): Promise<NewSessionResponse> {
    // params.cwd يحمل جذر مساحة العمل التي فتحها المحرّر.
    const sessionId = `sess_${this.sessions.size + 1}`;
    this.sessions.set(sessionId, new AbortController());
    return { sessionId };
  }
 
  async cancel(params: CancelNotification): Promise<void> {
    this.sessions.get(params.sessionId)?.abort();
  }
 
  // طريقة prompt() تأتي تالياً.
  async prompt(params: PromptRequest): Promise<PromptResponse> {
    return { stopReason: "end_turn" };
  }
}

بعض النقاط الجديرة بالملاحظة:

  • تُصدّر الحزمة PROTOCOL_VERSION كي يُبلّغ وكيلك دوماً عن النسخة التي بُني عليها.
  • يخبر agentCapabilities المحرّر بما يجب تفعيله في واجهته — مثلاً، هل يعرض إرفاق الصور أم لا.
  • نخزّن AbortController لكل جلسة كي يتمكّن إشعار cancel من مقاطعة دور قيد التشغيل.

الخطوة 4: بثّ المخرجات عبر session updates

قلب الوكيل هو طريقة prompt. يرسل المحرّر رسالة المستخدم، ومهمّتك هي:

  1. قراءة مطالبة المستخدم من params.prompt (مصفوفة من كتل المحتوى).
  2. بثّ الردّ عبر إشعارات session/update.
  3. إرجاع stop reason عند انتهاء الدور.

استبدل طريقة prompt المؤقتة بصدى مبثوث يكتب الردّ كلمة كلمة:

async prompt(params: PromptRequest): Promise<PromptResponse> {
  const { sessionId } = params;
  const controller = this.sessions.get(sessionId);
 
  // استخراج نصّ المستخدم من كتل المحتوى.
  const userText = params.prompt
    .filter((block) => block.type === "text")
    .map((block) => block.text)
    .join(" ");
 
  const reply = `قلتَ: "${userText}". وهذا ردّ مبثوث.`;
 
  // بثّ الردّ جزءاً جزءاً.
  for (const word of reply.split(" ")) {
    if (controller?.signal.aborted) {
      return { stopReason: "cancelled" };
    }
 
    await this.conn.sessionUpdate({
      sessionId,
      update: {
        sessionUpdate: "agent_message_chunk",
        content: { type: "text", text: word + " " },
      },
    });
 
    // محاكاة زمن استجابة التوكنات لجعل البثّ مرئياً.
    await new Promise((r) => setTimeout(r, 60));
  }
 
  return { stopReason: "end_turn" };
}

تدعم حمولة sessionUpdate عدّة أنواع من التحديثات. أكثرها شيوعاً:

قيمة sessionUpdateالغرض
agent_message_chunkنصّ المساعد المرئي
agent_thought_chunkتفكير يُعرض في كتلة قابلة للطيّ
tool_callالإعلان عن أداة على وشك التشغيل
tool_call_updateتقدّم أو نتيجة أداة
planخطّة متعدّدة الخطوات ينوي الوكيل اتّباعها

قيم stop reason الصالحة هي: end_turn وmax_tokens وmax_turn_requests وrefusal وcancelled.


الخطوة 5: الاتصال عبر stdio

اربط الآن الوكيل بطبقة النقل. يعمل ACP بـ JSON مفصول بأسطر عبر stdio، لذا تحوّل process.stdout وprocess.stdin من Node إلى تدفّقات Web وتمرّرها إلى ndJsonStream.

أضف نقطة الدخول هذه في أسفل src/agent.ts:

function main() {
  // يكتب ACP على stdout ويقرأ من stdin.
  // تحويل تدفّقات Node إلى تدفّقات Web التي تتوقّعها الحزمة.
  const input = Readable.toWeb(process.stdin) as ReadableStream<Uint8Array>;
  const output = Writable.toWeb(process.stdout) as WritableStream<Uint8Array>;
 
  const stream = ndJsonStream(output, input);
 
  // تتلقّى الـ factory الاتصال وتُرجِع وكيلنا.
  new AgentSideConnection((conn) => new HelloAgent(conn), stream);
 
  // إبقاء العملية حيّة؛ الاتصال يقود كل شيء.
  process.stdin.resume();
}
 
main();

يأخذ مُنشئ AgentSideConnection دالة factory. تمنحك الحزمة كائن الاتصال، وتُرجِع أنت تنفيذ Agent الخاص بك. هذا الانقلاب هو ما يتيح لوكيلك دفع التحديثات عبر conn بينما يردّ على الطلبات الواردة.

تحقّق من أن العملية تبدأ دون انهيار:

npm run dev
# يُفترض أن تتوقّف منتظرةً stdin — وهذا صحيح. اضغط Ctrl+C.

الخطوة 6: طلب الإذن قبل التنفيذ

الوكلاء الحقيقيون يعدّلون الملفات ويشغّلون الأوامر. لدى ACP تدفّق مدمج لهذا: قبل أي إجراء حسّاس، أرسِل session/request_permission ودع المحرّر يعرض خياراً على المستخدم.

إليك دالة مساعدة يمكنك استدعاؤها داخل prompt، مثلاً قبل كتابة ملف:

private async confirm(
  sessionId: string,
  title: string,
): Promise<boolean> {
  const result = await this.conn.requestPermission({
    sessionId,
    toolCall: {
      toolCallId: `call_${Date.now()}`,
      title,
      kind: "edit",
      status: "pending",
    },
    options: [
      { optionId: "allow", name: "سماح", kind: "allow_once" },
      { optionId: "reject", name: "رفض", kind: "reject_once" },
    ],
  });
 
  // يُرجِع العميل الخيار الذي اختاره المستخدم.
  return result.outcome?.outcome === "selected" &&
    result.outcome.optionId === "allow";
}

بما أننا نستخدم طابعاً زمنياً بدلاً من قيمة عشوائية للمعرّف، يبقى المثال حتمياً وسهل التتبّع. في الإنتاج، أنشئ معرّفاً فريداً ثابتاً لكل استدعاء أداة. يتيح حقل kind — من بين read وedit وdelete وexecute وfetch وغيرها — للمحرّر عرض أيقونة وتحذير مناسبين.


الخطوة 7: ربط نموذج حقيقي (اختياري)

لجعل الوكيل مفيداً، استبدل منطق الصدى باستدعاء نموذج لغوي. ثبّت حزمة Anthropic SDK وابثّ التوكنات مباشرةً إلى sessionUpdate:

npm install @anthropic-ai/sdk
import Anthropic from "@anthropic-ai/sdk";
 
const client = new Anthropic(); // يقرأ ANTHROPIC_API_KEY من البيئة
 
async function streamModel(
  conn: AgentSideConnection,
  sessionId: string,
  userText: string,
) {
  const stream = client.messages.stream({
    model: "claude-opus-4-8",
    max_tokens: 1024,
    messages: [{ role: "user", content: userText }],
  });
 
  stream.on("text", async (delta) => {
    await conn.sessionUpdate({
      sessionId,
      update: {
        sessionUpdate: "agent_message_chunk",
        content: { type: "text", text: delta },
      },
    });
  });
 
  await stream.finalMessage();
}

استدعِ streamModel من داخل prompt، ثم أرجِع { stopReason: "end_turn" } عند انتهاء التدفّق. لا تكتب مفتاح API بشكل صريح في الشيفرة أبداً — احتفظ به في متغيّر البيئة ANTHROPIC_API_KEY.


الخطوة 8: الاتصال بـ Zed

ابنِ وكيلك، ثم سجّله في ملف settings.json الخاص بـ Zed تحت المفتاح agent_servers:

npm run build
{
  "agent_servers": {
    "Hello Agent": {
      "command": "node",
      "args": ["/المسار/المطلق/إلى/acp-hello-agent/dist/agent.js"],
      "env": {
        "ANTHROPIC_API_KEY": "sk-ant-..."
      }
    }
  }
}

افتح لوحة الوكلاء في Zed، اختر Hello Agent من القائمة، وأرسل رسالة. يُفترض أن ترى ردّك المبثوث يظهر توكناً توكناً — دليل على أن وكيلك المخصّص يتحدّث ACP بشكل صحيح.


اختبار تنفيذك

تحقّق من كل طبقة قبل إعلان النجاح:

  • العملية تبدأ: يتوقّف npm run dev عند stdin دون أخطاء.
  • المصافحة: يعرض Zed الوكيل في المنتقي (أي أن initialize نجح).
  • البثّ: تظهر الردود تدريجياً، لا دفعة واحدة.
  • الإلغاء: إيقاف دور أثناء البثّ يُرجِع cancelled ويوقف المخرجات.
  • الإذن: تُطلق الإجراءات الحسّاسة نافذة طلب في واجهة المحرّر.

لاختبار على مستوى النقل دون محرّر، مرّر رسالة JSON-RPC مصنوعة يدوياً إلى العملية:

echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":1,"clientCapabilities":{}}}' | npm run dev

يُفترض أن تعود سطر JSON واحد يحمل قدرات وكيلك.


استكشاف الأخطاء وإصلاحها

الوكيل لا يظهر أبداً في Zed. تأكّد أن المسار في args مطلق وأن npm run build أنتج dist/agent.js. راجع سجلّ Zed (zed: open log) بحثاً عن أخطاء التشغيل.

رسائل مشوّشة أو مكرّرة. على الأرجح استدعيت console.log في مكان ما. كل كتابة دخيلة على stdout تُفسد تدفّق JSON-RPC. حوّل كل التتبّع إلى console.error.

عدم تطابق نسخة initialize. أرجِع دائماً PROTOCOL_VERSION من الحزمة بدلاً من رقم مكتوب صراحةً، كي يبقى الوكيل والعميل متزامنَين مع تطوّر البروتوكول.

التدفّق لا ينتهي أبداً. تأكّد أن كل مسار في prompt يُرجِع PromptResponse مع stop reason — بما في ذلك فرعا الإلغاء والخطأ.


الخطوات التالية

  • أضف استدعاءات أدوات كي يقرأ الوكيل الملفات ويكتبها عبر fs/read_text_file وfs/write_text_file.
  • أصدِر تحديث plan كي يرى المستخدمون نوايا الوكيل متعدّدة الخطوات قبل تنفيذها.
  • انشر وكيلك في سجلّ ACP كي يثبّته المطوّرون الآخرون عبر كل العملاء.
  • استكشف الدروس ذات الصلة: بناء خادم MCP بلغة TypeScript، وClaude Agent SDK للغة TypeScript، وOpenAI Agents SDK في الإنتاج.

الخلاصة

بنيتَ وكيل ACP يعمل من الصفر: مصافحة، وجلسة، ومخرجات مبثوثة، وتدفّق إذن، ونموذج حقيقي خلفه — كل ذلك عبر stdio بسيط. ولأنه يتحدّث Agent Client Protocol، فإن البرنامج نفسه يعمل اليوم في Zed وفي كل عميل ACP مستقبلي دون أي تعديل. هذه القابلية للنقل هي جوهر الأمر: في مشهد 2026 حيث يتغيّر الوصول إلى النماذج تبعاً للأسعار وضوابط التصدير، فإن وكيلاً غير مقيّد بمحرّر واحد — ومحرّراً غير مقيّد بوكيل واحد — يمثّل ميزة استراتيجية. اكتب مرّة واحدة، واتصل في كل مكان.

● الوسوم
#acp#agent-client-protocol#typescript#ai-agents#zed#claude#json-rpc#2026#intermediate#30 دقيقة قراءة
● مشاركة
● هل لديك سؤال؟

تحدث مع وكيل نقطة بشأن هذا المقال.

Noqta Team
Noqta Team
Author · noqta
متابعة ↗

● اقرأ التالي

بناء وكيل ذكاء اصطناعي مستقل باستخدام Agentic RAG و Next.js
● Tutorial

بناء وكيل ذكاء اصطناعي مستقل باستخدام Agentic RAG و Next.js

11 فبراير 2026
بناء أنظمة وكلاء متعددة جاهزة للإنتاج باستخدام Agno في بايثون
● Tutorial

بناء أنظمة وكلاء متعددة جاهزة للإنتاج باستخدام Agno في بايثون

7 يونيو 2026
بناء وكلاء الذكاء الاصطناعي من الصفر باستخدام TypeScript: إتقان نمط ReAct مع Vercel AI SDK
● Tutorial

بناء وكلاء الذكاء الاصطناعي من الصفر باستخدام TypeScript: إتقان نمط ReAct مع Vercel AI SDK

12 فبراير 2026
نقطة
الشروط والأحكام · سياسة الخصوصية
الخدمات
  • أتمتة الذكاء الاصطناعي
  • وكلاء الذكاء الاصطناعي
  • أتمتة تجربة العملاء
  • Vibe Coding
  • إدارة المشاريع
  • ضمان الجودة
  • تطوير الويب
  • تكامل API
  • تطبيقات الأعمال
  • الصيانة
  • Low-Code/No-Code
الروابط
  • معلومات عنا
  • كيف نعمل؟
  • الأخبار
  • الدروس التعليمية
  • المدونة
  • تواصل معنا
  • الأسئلة الشائعة
  • الموارد
المناطق
  • السعودية
  • الإمارات
  • قطر
  • البحرين
  • عُمان
  • ليبيا
  • تونس
  • الجزائر
  • المغرب
الشركة
  • نقطة، تونس، الهاتف +216 24 309 128
© نقطة. جميع الحقوق محفوظة.