Cordis Eklentisi Yazma
Cordis köprüsü, satırları cordis.patch.yml içinde tanımlanan bir eklenti ağacını bağlar. Yetenek eklemek için Libre WebUI kaynak kodunu değiştirmek yerine bir Cordis eklentisi yazıp satır eklersiniz. Bu sayfa, hostun dayandığı kuralları açıklar.
Ağacın nasıl birleştiğini anlamak için önce Cordis Köprüsü, satır alanları için Cordis Yapılandırması sayfalarını okuyun.
İki eklenti biçimi
Cordis eklentisi bir işlev veya apply metodu bulunan bir nesnedir. Loader her iki biçimi de çözümler.
// 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 modül ad alanını okur. Bu nedenle satırdan yüklenen eklenti, apply ve inject için adlandırılmış dışa aktarımlar sunmalıdır. Varsayılan dışa aktarım da çalışır, ancak paketle gelen DSH eklentileri adlandırılmış biçimi kullandığından tutarlılık için bunu tercih edin.
Bağımlılıkları bildirme
Bağımlılık mekanizmasının tamamı inject üzerinden çalışır. Cordis, adı verilen tüm servisler bulunana kadar eklentiyi çalıştırmaz; bir servis kaldırılıp geri geldiğinde eklentiyi yeniden çalıştırır.
export const inject = ['tools', 'systemPrompt'];
export function apply(ctx, config) {
// `ctx.tools` and `ctx.systemPrompt` are guaranteed present here.
}
İki sonuç önemlidir:
- Eksik servis hata değildir. Satır sessizce süresiz bekler. Bu nedenle
GET /api/cordis/health, tek bir boolean yerine her servisin durumunu bildirir. - Sırayı enjeksiyon belirler. Satırları kendiniz sıralamazsınız; hazır bileşimdeki satır sırası yükleme sırası anlamına gelmez.
Yazım sırasında isteğe bağlı kabul edilen bir bağımlılık için apply içinde ctx.inject kullanın. Servisler zaten varsa callback hemen, daha sonra da servisler her ortaya çıktığında çalışır:
export function apply(ctx) {
ctx.inject(['typert'], inner => {
inner.typert.lookups.register('session', {/* ... */});
});
}
Bağımlılıklar zaten mevcutsa ctx.inject eşzamanlı çalışır. Dolayısıyla geç bağlanan bir eklenti de sonraki olay döngüsünü beklemeden apply sırasında kaydolur.
Servis yayımlama
Service sınıfını genişletin ve servis adını super çağrısına verin. Tüketiciler bu addaki özelliği okur; kayıt, eklentinin fiber’ına aittir.
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;
Davranışı olan servislerde ctx.reflect.provide(...) yerine bir Service alt sınıfını tercih edin. Adı bir kez kaydeder, türlendirilmiş metotlar sunar ve fiber ile birlikte otomatik kaldırılır.
Yan etkilerin sahipliği
Her yan etkinin bir sahibi olmalıdır. Köprünün geri alma garantisini sağlayan temel kural budur; en kolay ihlal edilen de budur.
| Yan etki | Sahibi |
|---|---|
| Servis kaydı | Otomatik olarak eklentinin fiber’ı |
| Olay dinleyicisi | Eklenti içindeki ctx.on(...) |
| Temizlik isteyen kaynak | ctx.effect(() => disposer) |
| Zamanlayıcı | ctx.setTimeout / ctx.setInterval |
| Araç kaydı | Temizleme işlevini ctx.effect üzerinden döndürün |
ctx.effect, temizleme işlevi döndüren bir işlev veya temizleme işlevleri üreten bir generator kabul eder:
ctx.effect(() => {
const registration = ctx.llm.registerAdapter(['my-route'], adapter);
return () => registration();
}, 'my-adapter.register');
Etiket süs değildir; temizleme başarısız olduğunda yan etkiyi tanımlayan tanılama adıdır.
Cordis’in bilmediği bir soket, worker veya tanıtıcı gibi kaynağı sahipleniyorsanız kendiniz serbest bırakmalısınız:
ctx.effect(() => {
const worker = startWorker();
return () => worker.terminate();
}, 'my-plugin.worker');
Geri almayı bozan işlemler
applymetoduna verilen bağlamdan farklı bir bağlama kayıt yapmak. Servis, satırı kaldırıldıktan sonra yaşamaya devam eder.- Global
setTimeoutile zamanlayıcı oluşturmak. Süreci canlı tutar ve iptal edilmez. - Harici bir yayıcıya abone olup temizleme işlevinde abonelikten çıkmamak.
- Modül düzeyindeki singleton’a yazmak. Temizleme bunu geri alamaz; satır kaldırılınca değer görünür kalır. Bunun yerine fiber başına durum kullanın.
Yapılandırma
Eklentinin ayarları, satırının config eşlemesidir. Yazım hatasının sessizce varsayılanı kullanmak yerine bağlanma sırasında hata vermesi için bir şema bildirin:
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.
}
Bileşim belgesindeki değerler !!js kullanabilir. İfade, sahibi olan satırın fiber’ında Loader bağlamına erişerek değerlendirilir: process.env ve ctx.get(...) çalışır, import.meta çalışmaz. name hiçbir zaman değerlendirilmez; modül belirticisi bir dize sabiti olmalıdır.
Tam örnek
Depoda köprü testlerinin kullandığı iki çalışan test düzeneği bulunur. İkisi de kopyalanarak kullanılabilecek kadar küçüktür.
Model adaptörü: scripts/fixtures/cordis/fake-adapter.mjs, ctx.llm üzerinde sağlayıcı rotası kaydeder, bir effect üzerinden kayıt temizleyicisini döndürür ve her isteği sabit metinle yanıtlar. Sağlayıcı sözleşmesinin tamamı budur: inject bildirin, kaydolun ve kaydın yaşam döngüsünü üstlenin.
Yaşam döngüsü sondası: scripts/fixtures/cordis/lifecycle-probe.mjs bir servis sunar, olaya abone olur ve kendi temizliğini kaydeder. Geri alma testi böylece hem servisin hem aboneliğin gerçekten ortadan kalktığını doğrular.
İkisinden birini bağlamak için satır ekleyin:
- id: my-adapter
name: './scripts/fixtures/cordis/fake-adapter.mjs'
config:
route: my-route
Göreli belirticiler bileşim dosyasının dizinine göre; yalın paket belirticileri backend paketi üzerinden çözümlenir.
Eklentiyi test etme
Köprü testleri şu yaklaşımı gösterir:
scripts/test-cordis-bridge.mjs, geçici dizinde gerçek ağaç kurar, servis erişimini ve sözleşmeyi sınar; temizliğin servisi kaldırıp dinleyiciyi serbest bıraktığını doğrular.scripts/test-cordis-bridge-http.mjs, NDJSON akışı dahil rotaları gerçek bir HTTP sunucusunda sınar.
Sağlayıcı olmadan test için model.provider: none ayarlayıp eklentiyi ve motor satırlarını bağlayın. llm gerektiren eklentide, gerçek adaptörün kullanmadığı bir rotaya test adaptörü bağlayın; hostun aynı rotaya rakip adaptör eklememesi için model.provider: none kullanın.
Bir eklenti, satırı kaldırıldığında geride hiçbir şey bırakmıyorsa doğrudur. Şu sırayı doğrulayın: kaydet, gözlemle, temizle, tekrar gözlemle.
Kontrol listesi
-
applyveinjectadlandırılmış dışa aktarımlardır. - Servis, sabit bir
provideadı olanServicealt sınıfıdır. - Her kayıt temizleyici döndürür; her temizleyici
ctx.effectüzerinden döndürülür. - Zamanlayıcılar global işlevlerden değil
ctxüzerinden gelir. - Değişken durum modül singleton’ında değil örnekte yaşar.
- Yapılandırma bir şemayla doğrulanır.
- Bileşim veya ayar belgesinde sır bulunmaz.
- Test, temizleme sonrasında geride kaynak kalmadığını doğrular.