DeepSeek Harness: القدرات: الأدوار الثلاثة

آخر تحديث: 2026-08-31

نظام القدرات في Cordis هو أساس "كل شيء إضافة." يقسم الميزة إلى ثلاثة أدوار: من يُعرّف الواجهة، من يُنفّذها، ومن يستخدمها. هذا الانفصال يجعل استبدال التنفيذ سهلاً كتبديل بطاريات — أخرج القديمة، أدخل الجديدة، والنظام يعمل بشكل طبيعي.

💡 نصيحة: القيمة الأساسية لنموذج القدرات ذو الأدوار الثلاثة هي "القابلية للاستبدال" — استبدل Provider واحد واستبدل سلوك المنتج بالكامل، بينما Definition و Consumer يبقيان بدون تغيير. هذه قوة seam.

📋 المتطلبات المسبقة: أكمل 19-service.md، تفهم صنف Service الأساسي

1. ما ستتعلمه

نموذج الأدوار الثلاثة للقدرة


2. نظرة عامة على نظام القدرات

(1) لماذا ثلاثة أدوار

بدون نظام قدرات، الميزات مُرمّزة بثبات مباشرة:

TYPESCRIPT
// ❌ تنفيذ مُرمّز بثبات
class FileAnalyzer {
  analyze(path: string) {
    const stat = fs.statSync(path)  // يمكن استخدام نظام الملفات المحلي فقط
    return { size: stat.size, type: 'local' }
  }
}

مع نظام قدرات، الميزات مُقسّمة لثلاث طبقات:

TYPESCRIPT
// ✅ فصل الأدوار الثلاثة
// Definition: تصريح الواجهة
interface FileStats {
  getSize(path: string): Promise<number>
  getType(path: string): Promise<string>
}

// Provider: تنفيذ (محلي)
class LocalFileStats implements FileStats { ... }

// Provider: تنفيذ (sandbox بعيد)
class SandboxFileStats implements FileStats { ... }

// Consumer: استخدام (لا يهتم بالتنفيذ المُحدد)
class FileAnalyzer {
  constructor(private stats: FileStats) {}
  analyze(path: string) {
    const size = await this.stats.getSize(path)
    return { size }
  }
}

(2) ▶ مثال 2

100%
graph LR
    DEF[Definition<br/>تصريح الواجهة] --> PROV[Provider<br/>تنفيذ الواجهة]
    PROV --> CON[Consumer<br/>استخدام الواجهة]
    CON --> DEF

(3) قياس

الدور القياس المُقابل الواقعي
Definition معيار مقبس الطاقة مواصفة المقابس الوطنية
Provider تنفيذ المقبس مقبس الحائط
Consumer جهاز كهربائي تلفاز، ثلاجة

التلفاز لا يهتم من أي محطة طاقة يأتي الكهرباء — يهتم فقط أن المقبس يلبي المعيار. بنفس الطريقة، Consumer لا يهتم من هو Provider — يهتم فقط بالواجهة التي عرّفها Definition.


3. Definition: تصريح الواجهات

(1) تعريف قدرة

Definition يُصرّح بواجهة القدرة — لا يحتوي على تنفيذ، فقط يصف "ماذا يمكن أن تفعل هذه القدرة":

TYPESCRIPT
import { defineCapability } from '@deepseek-ai/cordis'

export const FileStats = defineCapability({
  name: 'file-stats',
  description: 'File statistics and metadata access',
  interface: {
    getSize(path: string): Promise<number>
    getType(path: string): Promise<string>
    exists(path: string): Promise<boolean>
    list(dir: string): Promise<string[]>
  }
})

(2) عناصر Definition

العنصر الوصف
name مُعرّف القدرة، فريد عالمياً
description وصف القدرة
interface تعريف واجهة TypeScript

(3) لماذا فصل التعريفات

فوائد فصل Definition عن Provider:

(4) اصطلاح

Definitions تُوضع عادة في ملف منفصل عن Providers:

TEXT 📖 للعرض فقط
capabilities/
├── file-stats/
│   ├── definition.ts    ← Definition
│   ├── local.ts         ← Provider (تنفيذ محلي)
│   └── sandbox.ts       ← Provider (تنفيذ sandbox)

4. Provider: تنفيذ الواجهات

(1) تنفيذ قدرة

Provider يُنفّذ الواجهة المُصرّح بها بواسطة Definition:

TYPESCRIPT
import { FileStats } from './definition'

export default class LocalFileStatsProvider extends Service {
  static inject = ['fs']

  constructor(ctx: Context) {
    super(ctx, 'file-stats')
    ctx.implement(FileStats, {
      async getSize(path: string) {
        const stat = await ctx.fs.stat(path)
        return stat.size
      },
      async getType(path: string) {
        const stat = await ctx.fs.stat(path)
        return stat.isDirectory ? 'directory' : 'file'
      },
      async exists(path: string) {
        try {
          await ctx.fs.stat(path)
          return true
        } catch {
          return false
        }
      },
      async list(dir: string) {
        const entries = await ctx.fs.readdir(dir)
        return entries.map(e => e.name)
      }
    })
  }
}

(2) ctx.implement()

ctx.implement(capability, implementation) يُسجّل التنفيذ للقدرة:

TYPESCRIPT
ctx.implement(FileStats, {
  getSize: async (path) => { ... },
  getType: async (path) => { ... },
  // يجب تنفيذ جميع دوال الواجهة
})

إذا كانت دوال مفقودة، TypeScript يُبلغ عن خطأ وقت الترجمة.

(3) ▶ مثال 3

Provider محلي:

TYPESCRIPT
export default class LocalFileStatsProvider extends Service {
  constructor(ctx: Context) {
    super(ctx, 'file-stats')
    ctx.implement(FileStats, {
      async getSize(path) {
        const stat = await ctx.fs.stat(path)
        return stat.size
      },
      async getType(path) {
        return (await ctx.fs.stat(path)).isDirectory ? 'directory' : 'file'
      },
      async exists(path) {
        try { await ctx.fs.stat(path); return true } catch { return false }
      },
      async list(dir) {
        return (await ctx.fs.readdir(dir)).map(e => e.name)
      }
    })
  }
}

Provider لـ Sandbox بعيد:

TYPESCRIPT
export default class SandboxFileStatsProvider extends Service {
  constructor(ctx: Context) {
    super(ctx, 'file-stats')
    ctx.implement(FileStats, {
      async getSize(path) {
        const resp = await fetch(`http://sandbox:8080/stat?path=${path}`)
        return (await resp.json()).size
      },
      async getType(path) {
        const resp = await fetch(`http://sandbox:8080/stat?path=${path}`)
        return (await resp.json()).type
      },
      async exists(path) {
        const resp = await fetch(`http://sandbox:8080/exists?path=${path}`)
        return (await resp.json()).exists
      },
      async list(dir) {
        const resp = await fetch(`http://sandbox:8080/ls?dir=${dir}`)
        return (await resp.json()).entries
      }
    })
  }
}

5. Consumer: استخدام الواجهات

(1) استهلاك قدرة

Consumer يصرّح بالاعتماديات عبر inject ويستخدم القدرة عبر ctx:

TYPESCRIPT
export const inject = ['file-stats']

export function apply(ctx: Context) {
  ctx.tools.register(defineTool({
    name: 'file_info',
    description: 'Get file information',
    parameters: {
      type: 'object',
      properties: {
        path: { type: 'string', description: 'File path' }
      },
      required: ['path']
    },
    async execute({ path }, ctx) {
      const size = await ctx['file-stats'].getSize(path)
      const type = await ctx['file-stats'].getType(path)
      return { path, size, type }
    }
  }))
}

(2) Consumer لا يعرف Provider

Consumer يعتمد فقط على الواجهة المُعرّفة بـ Definition، لا على التنفيذ المُحدد:

TYPESCRIPT
// كود Consumer متطابق بغض النظر عما إذا كان Local أو Sandbox تحته
const size = await ctx['file-stats'].getSize(path)

(3) ▶ مثال 3

TYPESCRIPT
// عبر توسيع الأنواع
declare module '@deepseek-ai/cordis' {
  interface Context {
    'file-stats': {
      getSize(path: string): Promise<number>
      getType(path: string): Promise<string>
      exists(path: string): Promise<boolean>
      list(dir: string): Promise<string[]>
    }
  }
}

6. مفهوم seam

(1) ما هو seam

seam هو نقطة الوصل بين Definition و Provider — هو المكان الذي يمكن فيه "فصل واستبدال" النظام:

100%
graph LR
    CON[Consumer] -->|يعتمد على| DEF[Definition<br/>seam]
    DEF -->|يُنفّذ| PROV_A[Provider A<br/>تنفيذ محلي]
    DEF -.->|استبدل بـ| PROV_B[Provider B<br/>تنفيذ sandbox]

(2) قيمة seam

TEXT 📖 للعرض فقط
بدون seam:
  Consumer → Provider A (مُرمّز بثبات، لا يمكن الاستبدال)

مع seam:
  Consumer → Definition (seam) → Provider A
                        (seam) → Provider B (استُبدل!)

seam يجعل النظام قابلاً للاستبدال عند كل نقطة قدرة.

(3) تحديد seams الجيدة

المِعيار seam جيدة seam سيئة
مستوى التجريد مناسب جداً دقيق جداً أو خشن جداً
عدد التطبيقات يمكن أن يكون متعدداً واحد ممكن فقط
تكرار التغيير التنفيذ قد يتغير التنفيذ لا يتغير أبداً
اتجاه الاعتماد Consumer يعتمد على الواجهة Consumer يعتمد على التنفيذ

(4) دقة seam

TEXT 📖 للعرض فقط
seam خشن: FileSystem (نظام الملفات بالكامل قابل للاستبدال)
seam متوسط: FileStats (إحصائيات الملفات قابلة للاستبدال)
seam دقيق: FileSize (استعلام حجم الملف قابل للاستبدال)

خشن جداً → تكلفة استبدال عالية؛ دقيق جداً → تفكك الواجهة. اختر دقة متوسطة.


7. رسم تسجيل القدرات

(1) تدفق التسجيل

100%
graph TB
    DEF[defineCapability<br/>تصريح الواجهة] --> REG[تسجيل Definition]
    REG --> PROV1[Provider A implement]
    REG --> PROV2[Provider B implement]
    PROV1 --> ACTIVE_A[نشط حالياً: A]
    PROV2 --> WAIT_B[بانتظار: B]
    ACTIVE_A --> CONSUMER[Consumer يستخدم]

(2) تدفق الاستبدال

100%
graph LR
    OLD[Provider A<br/>نشط حالياً] -->|إلغاء تحميل| INACTIVE_A[غير نشط]
    NEW[Provider B<br/>تسجيل جديد] -->|implement| ACTIVE_B[نشط حالياً]
    ACTIVE_B --> CONSUMER[Consumer<br/>تبديل تلقائي]

(3) تعاون قدرات متعددة

100%
graph TB
    FS_DEF[FileStats Definition] --> FS_PROV[FileStats Provider]
    DB_DEF[Database Definition] --> DB_PROV[Database Provider]
    
    FS_PROV --> TOOL[file_info Tool]
    DB_PROV --> TOOL
    TOOL --> AGENT[Agent]

8. استبدال Provider = استبدال سلوك المنتج بالكامل

(1) القيمة الأساسية

هذه أقوى ميزة لنظام القدرات:

TEXT 📖 للعرض فقط
سيناريو: التبديل من التطوير المحلي لتنفيذ sandbox

1. ألغِ تحميل LocalFileStatsProvider
2. حمّل SandboxFileStatsProvider
3. جميع Consumers يستخدمون تلقائياً تنفيذ sandbox
4. كود Consumer: صفر تعديلات

(2) التبديل بالإعدادات

YAML
# تطوير محلي
plugins:
  file-stats:
    $insert: ./providers/local-file-stats

# بيئة sandbox (غيّر هذا السطر فقط)
plugins:
  file-stats:
    $replace: ./providers/sandbox-file-stats

(3) التبديل وقت التشغيل

TYPESCRIPT
// تبديل ديناميكي عبر $replace
ctx.on('config/updated', (config) => {
  if (config.environment === 'sandbox') {
    // الإطار يُعيد التحميل تلقائياً، يبدل لـ Sandbox Provider
  }
})

(4) اختبار A/B

YAML
# المجموعة A: تنفيذ محلي
realms:
  group-a:
    plugins:
      file-stats:
        $insert: ./providers/local-file-stats
  
  group-b:
    plugins:
      file-stats:
        $insert: ./providers/sandbox-file-stats

❓ أسئلة شائعة

س هل يجب أن يكون Definition واجهة؟
ج نعم. Definition يصف واجهة بحتة (فقط تواقيع الدوال، بدون تنفيذ). إذا احتجت كوداً مشتركاً، ضعه في وحدة مساعدة منفصلة.
س هل يمكن لـ Definition واحد أن يكون له عدة Providers نشطين؟
ج ليس افتراضياً — التسجيلات اللاحقة تتجاوز السابقة. إذا احتجت التعايش، استخدم عزل realm.
س هل يوجد انقطاع عند استبدال Provider؟
ج وجيز. بين إلغاء تحميل Provider القديم وتحميل الجديد، القدرة غير متاحة مؤقتاً. Consumers يجب أن يتعاملوا مع حالات undefined.
س كيف أقرر إن كانت ميزة يجب تجريدها كقدرة؟
ج اسأل نفسك: هل قد يكون لهذه الميزة تطبيقات مختلفة مستقبلاً؟ إذا نعم، جرّدها؛ إذا بالتأكيد تطبيق واحد، استخدم Service مباشرة.
س ما الفرق بين seam و inject؟
ج inject هو آلية تصريح الاعتماديات ("أحتاج X")؛ seam هو تصميم قابلية الاستبدال ("X يمكن استبداله"). inject شرط مسبق لـ seam — Consumer يصرّح بالاعتماد عبر inject، seam يضمن أن الاعتمادية قابلة للاستبدال.
س هل نظام القدرات يستحق التعقيد المُضاف؟
ج للمشاريع البسيطة، ربما لا. لكن عندما يكون للمشروع بيئات نشر متعددة (محلي/sandbox/بعيد) أو يحتاج اختبار A/B، فوائد نظام القدرات تفوق تكاليفه بكثير.

📖 ملخص


📝 تمارين

1. ⭐ أساسي: عرّف قدرة TimeService (Definition) بدالة now(): number. نفّذ LocalTimeProvider، سجّله، واستدعه من إضافة Consumer.

2. ⭐⭐ متوسط: نفّذ MockTimeProvider (يُعيد طابعاً زمنياً ثابتاً)، استخدم $replace لتبديل Providers. تحقق أن نتائج استدعاء Consumer تتغير من وقت حقيقي لوقت ثابت بدون أي تعديل لكود Consumer.

3. ⭐⭐⭐ تحدٍ: صمّم قدرة SearchEngine، تُعرّف واجهة search(query: string): Promise<string[]>. نفّذ Providerين: LocalGrepProvider (يبحث بـ grep) و RemoteAPIProvider (يستدعي API بحث). استخدم إعدادات realm لترك وكلاء اثنين يستخدمان تطبيقات بحث مختلفة، مع التحقق من العزل.

Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%