मुख्य कंटेंट तक स्किप करें

Cordis प्लगइन लिखना

Cordis ब्रिज cordis.patch.yml में दी गई पंक्तियों का प्लगइन ट्री माउंट करता है। नई क्षमता जोड़ने के लिए Libre WebUI का स्रोत बदलने के बजाय Cordis प्लगइन लिखकर एक पंक्ति जोड़ें। यह पृष्ठ होस्ट की अपेक्षित परंपराएँ बताता है।

ट्री की संरचना समझने के लिए पहले Cordis ब्रिज और पंक्ति के फ़ील्ड के लिए Cordis कॉन्फ़िगरेशन पढ़ें।

प्लगइन के दो रूप

Cordis प्लगइन एक फ़ंक्शन या apply विधि वाला ऑब्जेक्ट होता है। Loader दोनों रूपों को स्वीकार करता है।

// Function form.
export function apply(ctx, config) {
// ...
}
// Object form, when the plugin also declares dependencies.
export const name = 'my-plugin';
export const inject = ['tools'];

export function apply(ctx, config) {
// ...
}

Loader मॉड्यूल नेमस्पेस पढ़ता है, इसलिए पंक्ति से लोड होने वाले प्लगइन को apply और inject नामित एक्सपोर्ट के रूप में देने चाहिए। डिफ़ॉल्ट एक्सपोर्ट भी काम करता है, लेकिन साथ आने वाले DSH प्लगइन नामित रूप इस्तेमाल करते हैं; एकरूपता के लिए वही चुनें।

निर्भरताएँ घोषित करना

inject ही पूरा निर्भरता तंत्र है। सभी नामित सेवाएँ मौजूद होने पर ही Cordis प्लगइन चलाता है। किसी सेवा को हटाकर फिर बहाल करने पर प्लगइन दोबारा चलता है।

export const inject = ['tools', 'systemPrompt'];

export function apply(ctx, config) {
// `ctx.tools` and `ctx.systemPrompt` are guaranteed present here.
}

दो परिणाम महत्वपूर्ण हैं:

  • सेवा का गायब होना त्रुटि नहीं है। पंक्ति बिना सूचना के अनिश्चित समय तक प्रतीक्षा करती है। इसीलिए GET /api/cordis/health एक बूलियन के बजाय हर सेवा की स्थिति देता है।
  • इंजेक्शन क्रम तय करता है। पंक्तियों को स्वयं क्रम में चलाने की ज़रूरत नहीं। साथ आने वाली संरचना में उनका क्रम लोडिंग का क्रम नहीं है।

वैकल्पिक निर्भरता के लिए apply के अंदर ctx.inject इस्तेमाल करें। सेवाएँ पहले से हों तो callback तुरंत चलता है और बाद में हर बार सेवाएँ उपलब्ध होने पर फिर चलता है:

export function apply(ctx) {
ctx.inject(['typert'], inner => {
inner.typert.lookups.register('session', {/* ... */});
});
}

निर्भरताएँ पहले से उपलब्ध हों तो ctx.inject समकालिक रूप से चलता है। इसलिए देर से माउंट किया गया प्लगइन भी अगले इवेंट चक्र की प्रतीक्षा किए बिना apply के दौरान पंजीकरण करता है।

सेवा उपलब्ध कराना

Service का उपवर्ग बनाएँ और super को सेवा का नाम दें। उपभोक्ता उसी नाम की प्रॉपर्टी पढ़ते हैं और पंजीकरण प्लगइन के फ़ाइबर की ज़िम्मेदारी होता है।

import { Service } from '@deepseek-ai/cordis';

export class WidgetRegistry extends Service {
static provide = 'widgets';

constructor(ctx) {
super(ctx, 'widgets');
this.widgets = new Map();
}

register(widget) {
// Return the disposer so the caller's fiber owns the entry.
return this.ctx.effect(() => {
this.widgets.set(widget.id, widget);
return () => this.widgets.delete(widget.id);
}, 'widgets.register()');
}
}

export default WidgetRegistry;

व्यवहार वाली सेवा के लिए ctx.reflect.provide(...) के बजाय Service उपवर्ग चुनें। यह नाम एक बार दर्ज करता है, टाइपयुक्त विधियाँ देता है और अपने फ़ाइबर के साथ स्वतः हट जाता है।

दुष्प्रभावों की ज़िम्मेदारी

हर दुष्प्रभाव का स्पष्ट स्वामी होना चाहिए। इसी नियम से ब्रिज की रोलबैक गारंटी मिलती है, और इसका उल्लंघन सबसे आसानी से होता है।

दुष्प्रभावज़िम्मेदार
सेवा पंजीकरणप्लगइन का फ़ाइबर, अपने आप
इवेंट लिसनरप्लगइन के अंदर ctx.on(...)
सफ़ाई वाला संसाधनctx.effect(() => disposer)
टाइमरctx.setTimeout / ctx.setInterval
टूल पंजीकरणctx.effect से सफ़ाई फ़ंक्शन लौटाएँ

ctx.effect ऐसा फ़ंक्शन लेता है जो सफ़ाई फ़ंक्शन लौटाता है, या ऐसा जनरेटर जो सफ़ाई फ़ंक्शन देता है:

ctx.effect(() => {
const registration = ctx.llm.registerAdapter(['my-route'], adapter);
return () => registration();
}, 'my-adapter.register');

लेबल सजावट नहीं, निदान के लिए है। सफ़ाई विफल होने पर उसी से संबंधित प्रभाव की पहचान होती है।

Cordis को अज्ञात संसाधन, जैसे सॉकेट, वर्कर या हैंडल अपनाने पर उसे स्वयं मुक्त करना होगा:

ctx.effect(() => {
const worker = startWorker();
return () => worker.terminate();
}, 'my-plugin.worker');

रोलबैक किन कारणों से टूटता है

  • apply को मिले संदर्भ से अलग संदर्भ में पंजीकरण करना। तब पंक्ति हटने पर भी सेवा रहती है।
  • वैश्विक setTimeout से टाइमर बनाना। वह प्रक्रिया को जीवित रखता है और कभी रद्द नहीं होता।
  • बाहरी इवेंट एमिटर की सदस्यता लेकर सफ़ाई में सदस्यता न हटाना।
  • मॉड्यूल-स्तरीय singleton में लिखना। सफ़ाई इसे वापस नहीं कर सकती और पंक्ति हटने के बाद मान रहता है। हर फ़ाइबर का अलग स्टेट रखें।

कॉन्फ़िगरेशन

प्लगइन की सेटिंग उसकी पंक्ति के config मैप में होती है। स्कीमा घोषित करें, ताकि टाइपिंग की गलती पर चुपचाप डिफ़ॉल्ट लेने के बजाय माउंट के समय त्रुटि मिले:

import z from '@deepseek-ai/schemastery';

export const Config = z.object({
route: z.string().required(),
maxRetries: z.number().default(2),
});

export function apply(ctx, config) {
// `config` is validated before this runs.
}

कंपोज़िशन दस्तावेज़ के मानों में !!js हो सकता है। अभिव्यक्ति संबंधित पंक्ति के फ़ाइबर में Loader संदर्भ के साथ चलती है। process.env और ctx.get(...) काम करते हैं, import.meta नहीं। name का मूल्यांकन कभी नहीं होता, इसलिए मॉड्यूल का नाम एक स्ट्रिंग लिटरल होना चाहिए।

पूरा उदाहरण

रिपॉज़िटरी में ब्रिज परीक्षणों के दो कार्यशील फ़िक्स्चर हैं। दोनों इतने छोटे हैं कि उन्हें कॉपी करके शुरू कर सकते हैं।

मॉडल अडैप्टर: scripts/fixtures/cordis/fake-adapter.mjs, ctx.llm पर प्रदाता रूट दर्ज करता है, प्रभाव से उस पंजीकरण का सफ़ाई फ़ंक्शन लौटाता है और हर अनुरोध को स्थिर पाठ देता है। प्रदाता का पूरा अनुबंध यही है: inject घोषित करें, पंजीकरण करें और उसका जीवनचक्र सँभालें।

जीवनचक्र जाँच: scripts/fixtures/cordis/lifecycle-probe.mjs सेवा उपलब्ध कराता है, इवेंट की सदस्यता लेता है और अपनी सफ़ाई दर्ज करता है। रोलबैक परीक्षण इससे पुष्टि करता है कि सेवा और सदस्यता दोनों वास्तव में समाप्त हुईं।

किसी भी उदाहरण को माउंट करने के लिए पंक्ति जोड़ें:

- id: my-adapter
name: './scripts/fixtures/cordis/fake-adapter.mjs'
config:
route: my-route

सापेक्ष मॉड्यूल नाम कंपोज़िशन फ़ाइल की निर्देशिका से हल होते हैं। साधारण पैकेज नाम बैकएंड पैकेज के माध्यम से हल होते हैं।

प्लगइन का परीक्षण

ब्रिज परीक्षणों में यह तरीका मिलता है:

  • scripts/test-cordis-bridge.mjs अस्थायी निर्देशिका में वास्तविक ट्री माउंट करता है, सेवा और अनुबंध जाँचता है, फिर पुष्टि करता है कि सफ़ाई ने सेवा और लिसनर हटा दिए।
  • scripts/test-cordis-bridge-http.mjs वास्तविक HTTP सर्वर से रूट और NDJSON स्ट्रीमिंग जाँचता है।

प्रदाता के बिना परीक्षण के लिए model.provider: none सेट करें और अपने प्लगइन के साथ इंजन की पंक्तियाँ माउंट करें। यदि llm चाहिए, तो ऐसा फ़िक्स्चर अडैप्टर माउंट करें जिसकी रूट कोई वास्तविक अडैप्टर उपयोग न करता हो। model.provider: none रखें ताकि होस्ट उसी रूट पर प्रतिस्पर्धी अडैप्टर न लगाए।

प्लगइन तभी सही है जब उसकी पंक्ति हटाने पर कुछ भी पीछे न रहे। क्रम जाँचें: पंजीकरण, निरीक्षण, सफ़ाई, फिर निरीक्षण।

जाँच सूची

  • apply और inject नामित एक्सपोर्ट हैं।
  • सेवा स्थिर provide नाम वाला Service उपवर्ग है।
  • हर पंजीकरण सफ़ाई फ़ंक्शन देता है और वह ctx.effect से लौटता है।
  • टाइमर वैश्विक फ़ंक्शन से नहीं, ctx से आते हैं।
  • बदलने वाला स्टेट इंस्टेंस में है, मॉड्यूल singleton में नहीं।
  • स्कीमा कॉन्फ़िगरेशन की जाँच करता है।
  • कंपोज़िशन या सेटिंग दस्तावेज़ में कोई रहस्य नहीं है।
  • परीक्षण पुष्टि करता है कि सफ़ाई के बाद कोई संसाधन नहीं बचता।