Ana içeriğe geç

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 etkiSahibi
Servis kaydıOtomatik olarak eklentinin fiber’ı
Olay dinleyicisiEklenti içindeki ctx.on(...)
Temizlik isteyen kaynakctx.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

  • apply metoduna verilen bağlamdan farklı bir bağlama kayıt yapmak. Servis, satırı kaldırıldıktan sonra yaşamaya devam eder.
  • Global setTimeout ile 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

  • apply ve inject adlandırılmış dışa aktarımlardır.
  • Servis, sabit bir provide adı olan Service alt 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.