DeepSeek Harness: القدرات: الأدوار الثلاثة
آخر تحديث: 2026-08-31
نظام القدرات في Cordis هو أساس "كل شيء إضافة." يقسم الميزة إلى ثلاثة أدوار: من يُعرّف الواجهة، من يُنفّذها، ومن يستخدمها. هذا الانفصال يجعل استبدال التنفيذ سهلاً كتبديل بطاريات — أخرج القديمة، أدخل الجديدة، والنظام يعمل بشكل طبيعي.
📋 المتطلبات المسبقة: أكمل 19-service.md، تفهم صنف Service الأساسي
1. ما ستتعلمه
- Definition: تصريح الواجهات
- Provider: تنفيذ الواجهات
- Consumer: استخدام الواجهات
- مفهوم seam
- رسم تسجيل القدرات
- استبدال Provider = استبدال سلوك المنتج بالكامل
2. نظرة عامة على نظام القدرات
(1) لماذا ثلاثة أدوار
بدون نظام قدرات، الميزات مُرمّزة بثبات مباشرة:
// ❌ تنفيذ مُرمّز بثبات
class FileAnalyzer {
analyze(path: string) {
const stat = fs.statSync(path) // يمكن استخدام نظام الملفات المحلي فقط
return { size: stat.size, type: 'local' }
}
}
مع نظام قدرات، الميزات مُقسّمة لثلاث طبقات:
// ✅ فصل الأدوار الثلاثة
// 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
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 يُصرّح بواجهة القدرة — لا يحتوي على تنفيذ، فقط يصف "ماذا يمكن أن تفعل هذه القدرة":
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:
- قيود الأنواع: Provider يجب أن يُنفّذ جميع دوال الواجهة
- قيمة توثيقية: Definition هو توثيق استخدام القدرة
- قابلية الاستبدال: أي Provider جديد يلبي الواجهة يمكنه استبدال القديم
- فحص وقت الترجمة: TypeScript يضمن تناسق الواجهة
(4) اصطلاح
Definitions تُوضع عادة في ملف منفصل عن Providers:
capabilities/
├── file-stats/
│ ├── definition.ts ← Definition
│ ├── local.ts ← Provider (تنفيذ محلي)
│ └── sandbox.ts ← Provider (تنفيذ sandbox)
4. Provider: تنفيذ الواجهات
(1) تنفيذ قدرة
Provider يُنفّذ الواجهة المُصرّح بها بواسطة Definition:
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) يُسجّل التنفيذ للقدرة:
ctx.implement(FileStats, {
getSize: async (path) => { ... },
getType: async (path) => { ... },
// يجب تنفيذ جميع دوال الواجهة
})
إذا كانت دوال مفقودة، TypeScript يُبلغ عن خطأ وقت الترجمة.
(3) ▶ مثال 3
Provider محلي:
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 بعيد:
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:
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، لا على التنفيذ المُحدد:
// كود Consumer متطابق بغض النظر عما إذا كان Local أو Sandbox تحته
const size = await ctx['file-stats'].getSize(path)
(3) ▶ مثال 3
// عبر توسيع الأنواع
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 — هو المكان الذي يمكن فيه "فصل واستبدال" النظام:
graph LR
CON[Consumer] -->|يعتمد على| DEF[Definition<br/>seam]
DEF -->|يُنفّذ| PROV_A[Provider A<br/>تنفيذ محلي]
DEF -.->|استبدل بـ| PROV_B[Provider B<br/>تنفيذ sandbox]
(2) قيمة seam
بدون seam:
Consumer → Provider A (مُرمّز بثبات، لا يمكن الاستبدال)
مع seam:
Consumer → Definition (seam) → Provider A
(seam) → Provider B (استُبدل!)
seam يجعل النظام قابلاً للاستبدال عند كل نقطة قدرة.
(3) تحديد seams الجيدة
| المِعيار | seam جيدة | seam سيئة |
|---|---|---|
| مستوى التجريد | مناسب جداً | دقيق جداً أو خشن جداً |
| عدد التطبيقات | يمكن أن يكون متعدداً | واحد ممكن فقط |
| تكرار التغيير | التنفيذ قد يتغير | التنفيذ لا يتغير أبداً |
| اتجاه الاعتماد | Consumer يعتمد على الواجهة | Consumer يعتمد على التنفيذ |
(4) دقة seam
seam خشن: FileSystem (نظام الملفات بالكامل قابل للاستبدال)
seam متوسط: FileStats (إحصائيات الملفات قابلة للاستبدال)
seam دقيق: FileSize (استعلام حجم الملف قابل للاستبدال)
خشن جداً → تكلفة استبدال عالية؛ دقيق جداً → تفكك الواجهة. اختر دقة متوسطة.
7. رسم تسجيل القدرات
(1) تدفق التسجيل
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) تدفق الاستبدال
graph LR
OLD[Provider A<br/>نشط حالياً] -->|إلغاء تحميل| INACTIVE_A[غير نشط]
NEW[Provider B<br/>تسجيل جديد] -->|implement| ACTIVE_B[نشط حالياً]
ACTIVE_B --> CONSUMER[Consumer<br/>تبديل تلقائي]
(3) تعاون قدرات متعددة
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) القيمة الأساسية
هذه أقوى ميزة لنظام القدرات:
سيناريو: التبديل من التطوير المحلي لتنفيذ sandbox
1. ألغِ تحميل LocalFileStatsProvider
2. حمّل SandboxFileStatsProvider
3. جميع Consumers يستخدمون تلقائياً تنفيذ sandbox
4. كود Consumer: صفر تعديلات
(2) التبديل بالإعدادات
# تطوير محلي
plugins:
file-stats:
$insert: ./providers/local-file-stats
# بيئة sandbox (غيّر هذا السطر فقط)
plugins:
file-stats:
$replace: ./providers/sandbox-file-stats
(3) التبديل وقت التشغيل
// تبديل ديناميكي عبر $replace
ctx.on('config/updated', (config) => {
if (config.environment === 'sandbox') {
// الإطار يُعيد التحميل تلقائياً، يبدل لـ Sandbox Provider
}
})
(4) اختبار A/B
# المجموعة A: تنفيذ محلي
realms:
group-a:
plugins:
file-stats:
$insert: ./providers/local-file-stats
group-b:
plugins:
file-stats:
$insert: ./providers/sandbox-file-stats
❓ أسئلة شائعة
undefined.📖 ملخص
- ثلاثة أدوار القدرة: Definition (تصريح الواجهة)، Provider (تنفيذ الواجهة)، Consumer (استخدام الواجهة)
- seam هو الوصل بين Definition و Provider، مفتاح قابلية استبدال النظام
- بعد استبدال Provider، Consumers يستخدمون تلقائياً التنفيذ الجديد بدون أي تغيير كود
- seams الجيدة تختار دقة متوسطة مع تجريد مناسب
- نظام القدرات يناسب بيئات النشر المتعددة، اختبار 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 لترك وكلاء اثنين يستخدمان تطبيقات بحث مختلفة، مع التحقق من العزل.