Di pertengahan 2026, ada satu proyek yang mencuri perhatian para developer AI: DeepSeek Harness (DSH). Dengan tagline “Everything is a Plugin”, DSH membangun seluruh kemampuan sebuah agen AI — tools, adaptor LLM, akses file, hingga agent loop itu sendiri — sebagai plugin yang dipasang ke sebuah konteks bersama. Hasilnya: Anda bisa menambah kemampuan baru ke agen AI tanpa menyentuh inti sistem, cukup dengan menulis satu modul TypeScript. Di tutorial ini kita akan membangun plugin tool pertama Anda — sebuah tool cuaca yang bisa dipanggil langsung oleh model AI — mulai dari nol hingga berjalan di Web UI.

Kenapa ini relevan sekarang? DeepSeek Harness melesat menjadi salah satu repositori paling banyak dibintangi di GitHub dalam hitungan minggu, dengan ekosistem plugin yang tumbuh cepat (routing, sidebar, hingga marketplace). Kemampuan menulis plugin DSH adalah skill yang sedang dicari untuk membangun agen AI yang bisa dikustomisasi.

Apa Itu DeepSeek Harness dan Cordis?

DeepSeek Harness adalah runtime untuk agen AI yang dibangun di atas Cordis, sebuah framework plugin yang ringan. Dalam Cordis, setiap kemampuan didaftarkan sebagai service pada objek ctx (context), dan setiap plugin cukup mendeskripsikan apa yang ia sumbangkan. Pola ini membuat seluruh sistem menjadi modular: Anda bisa mengganti adaptor LLM, menambah tool, atau mengubah agent loop hanya lewat konfigurasi.

Konsep kuncinya sederhana: sebuah plugin adalah modul TypeScript yang mengekspor fungsi apply(ctx). Saat dimuat, Cordis memanggil apply dengan sebuah objek ctx — dan lewat objek inilah plugin mendaftarkan semua yang ia sumbangkan (tool, event listener, timer, dan lain-lain).

Prasyarat

Sebelum mulai, pastikan Anda punya:

  • Node.js 18+ (untuk fetch global dan runtime modern)
  • pnpm sebagai package manager (npm i -g pnpm)
  • Git untuk meng-clone repositori
  • Pemahaman dasar TypeScript/JavaScript (tidak perlu mahir)

Tidak butuh API key apa pun untuk tutorial ini — contoh berjalan sepenuhnya tanpa kunci.

Langkah 1: Clone Repositori dan Siapkan Direktori Kerja

Mulailah dengan meng-clone repositori DeepSeek Harness, lalu install dependensinya dengan pnpm. Setelah itu buat direktori kerja untuk plugin tutorial — folder scratch-plugin/ sudah di-gitignore, jadi apa pun yang Anda tulis tidak menyentuh version control.

bash
git clone https://github.com/deepseek-ai/deepseek-harness.git
cd deepseek-harness
pnpm install

# Direktori kerja untuk plugin tutorial (diabaikan Git)
mkdir -p scratch-plugin/src
Langkah 2: Membuat Plugin Pertama

Sebuah plugin minimal hanyalah modul TypeScript yang mengekspor fungsi apply dan (opsional) nama. Buat file scratch-plugin/src/my-plugin.ts dengan isi berikut:

typescript
import type { Context } from '@deepseek-ai/cordis'

export const name = 'hello-plugin'

export function apply(ctx: Context) {
  console.log('[hello-plugin] plugin berhasil dimuat!')
}

Perhatikan: tidak ada bootstrap framework di file ini. Plugin hanya mendeskripsikan apa yang ia sumbangkan; sisanya diurus oleh loader Cordis. Ekspor name bersifat opsional dan dipakai sebagai label saat diagnosa.

Langkah 3: Mendaftarkan Plugin ke cordis.yml

Agar plugin dimuat, kita perlu mendaftarkannya di file konfigurasi cordis.yml. Karena kita memuatnya sebagai overlay ke Web UI, kita memakai blok insert. Buat file scratch-plugin/cordis.yml (jalankan pwd dari root repositori untuk mendapat path absolut):

python
- insert:
    - id: hello
      name: '/absolute/path/ke/deepseek-harness/scratch-plugin/src/my-plugin.ts'

Path plugin harus absolut. File patch ini menyumbang konfigurasi tanpa mengubah direktori profil tempat loader meresolusi path modul.

Langkah 4: Menjalankan Web UI

Jalankan Web UI dengan overlay tersebut, lalu buka http://127.0.0.1:3080. Anda akan melihat log [hello-plugin] plugin berhasil dimuat! di terminal saat startup.

bash
pnpm dsh web --patch ./scratch-plugin/cordis.yml

Tip: Jika plugin Anda tidak mencetak apa pun saat baru ditambahkan, periksa dulu ejaan path di cordis.yml. Entry yang modulnya gagal di-resolve dilaporkan lewat logger Cordis — bukan membuat proses crash — dan pesannya bisa terlewat sebelum ada console exporter yang aktif.

Langkah 5: Mendaftarkan Tool yang Bisa Dipanggil Model AI

Ini bagian intinya. Dengan service tools, kita mendaftarkan tool — fungsi yang bisa dipanggil model AI saat menjawab pertanyaan. Kita memakai helper defineTool yang mengubah spesifikasi parameters menjadi JSON Schema (yang dilihat model), memvalidasi argumen dari model, lalu menjalankan execute.

Ganti isi scratch-plugin/src/my-plugin.ts dengan tool cuaca di bawah. Tool ini memanggil API publik Open-Meteo (gratis dan tanpa API key) dua kali: pertama geocoding nama kota menjadi koordinat, lalu mengambil suhu terkini.

typescript
import type { Context } from '@deepseek-ai/cordis'
import { defineTool } from '@deepseek-ai/dsh-tools'

export const name = 'cuaca-tool'
export const inject = ['tools']

async function geocode(kota: string) {
  const url = `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(kota)}&count=1&language=id`
  const res = await fetch(url)
  if (!res.ok) return null
  const data = (await res.json()) as {
    results?: { latitude: number; longitude: number; name: string; country: string }[]
  }
  return data.results?.[0] ?? null
}

function kodeCuaca(code: number): string {
  if (code === 0) return 'cerah'
  if (code <= 3) return 'berawan'
  if (code <= 48) return 'berkabut'
  if (code <= 67) return 'hujan'
  if (code <= 77) return 'salju'
  if (code <= 82) return 'hujan lebat'
  return 'badai'
}

export function apply(ctx: Context) {
  ctx.tools.register(defineTool({
    name: 'cuaca',
    description: 'Cari tahu suhu dan kondisi cuaca saat ini di sebuah kota.',
    parameters: {
      kota: { type: 'string', required: true, description: 'Nama kota, misalnya "Jakarta" atau "Bandung"' },
      satuan: { type: 'string', description: 'Satuan suhu: "celsius" (default) atau "fahrenheit"' },
    },
    output: {
      schema: {
        type: 'object',
        properties: {
          kota: { type: 'string' },
          suhu: { type: 'number' },
          satuan: { type: 'string' },
          kondisi: { type: 'string' },
        },
      },
      render: (_args, value) => [{
        type: 'text',
        text: `Cuaca di ${value.kota}: ${value.suhu}°${value.satuan === 'fahrenheit' ? 'F' : 'C'}, ${value.kondisi}`,
      }],
    },
    async execute(args, exec) {
      const satuan = args.satuan === 'fahrenheit' ? 'fahrenheit' : 'celsius'
      const lokasi = await geocode(args.kota)
      if (!lokasi) {
        return { kota: args.kota, suhu: 0, satuan, kondisi: 'kota tidak ditemukan' }
      }

      const url = `https://api.open-meteo.com/v1/forecast?latitude=${lokasi.latitude}&longitude=${lokasi.longitude}&current=temperature_2m,weather_code&temperature_unit=${satuan}`
      const res = await fetch(url, { signal: exec.signal })
      const data = (await res.json()) as {
        current: { temperature_2m: number; weather_code: number }
      }

      return {
        kota: lokasi.name,
        suhu: data.current.temperature_2m,
        satuan,
        kondisi: kodeCuaca(data.current.weather_code),
      }
    },
  }))
}

Mari kita bedah bagian-bagian pentingnya:

  • inject: ['tools'] membuat Cordis menahan plugin dalam status PENDING sampai registry tools tersedia — jadi ctx.tools dijamin siap di dalam apply.
  • parameters mendefinisikan argumen yang boleh diberikan model. required: true menandai kota wajib; satuan opsional (tanpa required).
  • output.schema mendeklarasikan nilai kanonik yang dikembalikan, sementara output.render mengubah nilai itu menjadi teks yang dibaca model.
  • execute(args, exec) adalah logika sebenarnya. Argumen sudah divalidasi otomatis, dan exec.signal bisa dipakai untuk membatalkan kerja yang sedang berjalan.

Penting: execute harus mengembalikan satu nilai JSON kanonik sesuai output.schema — jangan mengembalikan blok konten atau teks bebas. Untuk kegagalan infrastruktur, lempar exception (registry akan menandainya sebagai error); untuk hasil domain yang sah, kembalikan nilainya meskipun renderer menjelaskan kondisi kurang ideal.

Langkah 6: Uji Tool dari Chat di Web UI

Restart perintah pnpm dsh web --patch ./scratch-plugin/cordis.yml, lalu di Web UI tanyakan: “Gunakan tool cuaca untuk mengecek suhu di Jakarta.” Model akan memanggil tool cuaca dan menerima hasilnya sebagai teks, misalnya “Cuaca di Jakarta: 31°C, cerah.”

Yang menarik: Anda tidak perlu menulis integrasi tambahan. Registrasi tool adalah effect — saat plugin di-unload (misalnya saat hot reload), tool otomatis ter-unregister, dan skema tool mengalir otomatis ke perakitan system prompt.

Kesimpulan dan Langkah Berikutnya

Selamat — Anda baru saja membangun plugin tool pertama Anda untuk DeepSeek Harness. Anda telah belajar anatomi plugin Cordis, cara mendaftarkan tool dengan defineTool, cara menyusun komposisi di cordis.yml, dan cara mengujinya di Web UI.

Untuk melangkah lebih jauh, coba pelajari: layanan (service) untuk berbagi kemampuan antar-plugin lewat ctx, konfigurasi plugin agar tool Anda bisa diatur pengguna, event seperti tools/result untuk mengamati setiap pemanggilan tool, dan Code Mode di mana setiap tool terlihat sebagai await tools.cuaca(args). Selamat mengeksplorasi — dunia “everything is a plugin” baru saja terbuka.