Luma public docs
English

Çalışma Zamanı

Tool Sistemi

Luma'da tool, contract ile implementasyonun ayrıldığı bir yapıdır: .luma dosyası tool'un imzasını, güvenlik sınıfını ve dayanıklılık politikasını tanımlar; gerçek kod host tarafındaki JS/TS module'ünde yaşar. Runtime aradaki her şeyi — doğrulama, izin, timeout, retry, cache, telemetry — üstlenir.

Tool contract'ı

luma
schema CalcResult {
  result: float
}

tool calculate(
  a: float @range(0, 1000000),
  b: float @range(0, 1000000),
  operation: enum["add", "multiply"] @desc("Yapılacak işlem")
) -> CalcResult {
  side_effects: "none"
  cache: true
}
ParçaKural
ParametrelerTipli olmak zorunda. Field annotation'ları kullanılabilir (@desc, @range, @default…). Bunlardan otomatik bir input schema üretilir.
Dönüş tipiTanımlı bir schema referansı olmalıdır — tagged by union dahil, aşağıya bak. Primitive veya inline tip kabul edilmez.
GövdeYalnızca yapılandırma anahtarları. Kod içermez — boş bırakılabilir.
Opsiyonel parametrelimit?: int @default(10) — eksikse doldurulur ve JSON Schema'da required olmaz.
Input schema otomatik üretilir

Parametrelerden __tool_<name>_input adında bir iç schema oluşturulur. Bu schema hem yerel input doğrulaması hem de modele gönderilen tool declaration'ının parameters alanı için kullanılır — yani parametre annotation'ların modele de görünür.

Tool seçenekleri

AnahtarDeğerlerVarsayılanAçıklama
side_effectsnone · read · writenoneTool'un yan etki sınıfı. tool_policy bunu temel alır.
cachetrue · falsefalseAynı input için sonucu process içinde saklar.
scopestring—İzin etiketi. Agent'ın tool_scopes listesinde olmalıdır.
credentialENV adı—Bu environment değişkeni yoksa tool hiç çalıştırılmaz.
errorschema adı—Handler'ın fırlattığı hatanın doğrulanacağı typed error schema'sı.
retriesint0Ek deneme sayısı (yalnızca retryable hatalarda).
retry_backoffnone · fixed · exponentialnoneDenemeler arası bekleme stratejisi.
retry_delayms100Temel bekleme süresi.
timeoutms—Tool'a özel timeout; agent'ın tool_timeout'unu ezer.
idempotenttrue · falsefalseAynı anahtarla yalnızca bir kez çalıştırma.
idempotency_keyinput alan yolu—idempotent: true ile zorunlu. Örn. request_id.
idempotency_ttl_msms300000Kaydın geçerlilik süresi.
needs <name>: <Type>bildirim—Host tarafından sağlanacak bağımlılık (DB bağlantısı, credential seti…). Tekrarlanabilir.

Tutarlılık kuralları

Kuralİhlal edilirse
Write tool cache edilemezWrite tool 'x' cannot use cache.
Write tool retry için idempotent olmalıA write tool 'x' must be idempotent and specify an idempotency_key to be retried.
idempotent anahtar isterIdempotent tool 'x' must specify an idempotency_key.
timeout: 0 geçersizTool 'x' timeout must be greater than zero.
needs kullanan tool cache edilemezCache cannot be used on a tool that uses needs: x (çalışma zamanında)

Direct tool çağrısı

Direct çağrı garantilidir: modelin kararına bağlı değildir, kaynak koda yazdığın yerde ve sırayla çalışır.

luma
agent Flow {
  tools: [text_stats, calculate]

  fn run(text: string) -> Answer {
    stats = tool text_stats({ text: text })

    if stats.words > 3 {
      bonus = tool calculate({ a: stats.words, b: 2, operation: "multiply" })
    }

    ask "İstatistikler: {stats}, bonus: {bonus}" -> Answer
  }
}
BiçimAnlamı
x = tool name({ a: 1, b: 2 })Object literal ile çağrı.
x = tool name(variable)Hazır bir nesne değişkeniyle çağrı.
x: Result = tool name({ … })Sonucu ayrıca tipe göre doğrula.
  • Tool, agent'ın tools listesinde olmalıdır; değilse Agent 'A' does not allow tool 'x' to be called directly.
  • Üst düzeyde de çağrılabilir: result = tool calculate({ a: 2, b: 3, operation: "add" })
  • Input ve output ayrı ayrı doğrulanır; hata TOOL_INPUT_INVALID / TOOL_OUTPUT_INVALID kodu taşır.
  • İki taraf @severity açısından simetrik değildir: output üzerindeki soft kural değeri şerhleyip koşuyu sürdürür, aynı kural input üzerinde yok sayılır ve sert kalır. Tool yan etkili koddur; sözleşmesini ihlal ettiği bilinen veriyle çağrılması, kuralın engellemek için var olduğu şeydir.

Native tool calling

Agent'ın tools listesi, tool'ları aynı zamanda modele görünür kılar. Model bir tool çağırmaya karar verirse runtime bunu çalıştırır ve sonucu geri gönderir.

luma
agent MultiToolWorker {
  model: GPT54Mini
  tools: [text_stats, calculate]
  tool_rate_limit: 2
  tool_concurrency: 2

  fn run() -> FinalResult {
    ask "Her iki tool'u da tam olarak bir kez çağır. text_stats'i text='Luma DSL' ile, calculate'ı a=20, b=22, operation='add' ile çağır. Tool sonuçlarını aldıktan sonra yalnızca geçerli JSON döndür." -> FinalResult
  }
}

Çok turlu akış

  1. istek   → messages + tools + tool_choice: "auto"  (veya zorunlu tool)
                  response_format gönderilmez
        ↓
  2. yanıt   → assistant mesajı + tool_calls[]
        ↓
  3. runtime → her tool call: policy → input doğrulama → çalıştırma → output doğrulama
                (tool_concurrency kadarı eşzamanlı)
        ↓
  4. mesajlar ← assistant(tool_calls) + her tool sonucu için bir tool mesajı
        ↓
  5. istek   → messages + tools + response_format: json_schema (strict)
        ↓
  6. yanıt   → nihai JSON → yerel schema doğrulaması
Tool'lar tanımlıyken ilk istek strict schema içermez

Model tool çağırabilsin diye ilk istekte response_format gönderilmez; yalnızca en az bir tool çağrısı gerçekleştikten sonra strict JSON Schema eklenir.

Pratik sonucu: tools tanımlı bir agent'ta model hiç tool çağırmadan doğrudan cevap verirse, o yanıt gateway tarafından format açısından zorlanmamış olur — yalnızca Luma'nın yerel doğrulaması devreye girer. Bu durumda retry vermek özellikle değerlidir.

Tek bir tool'u zorunlu kılma

Varsayılanda istek tool_choice: "auto" ile gider ve seçimi model yapar. Bir ask'in sonuna using tool <name> eklersen o tool zorunlu olur — argümanları yine model üretir, ama gateway'e çağrının mecburi olduğu bildirilir:

fn settle(text: string) -> Answer {
  ask "Hesabı kapat: {text}" -> Answer using tool calculate
}

Tool, agent'ın tools listesinde olmalıdır; değilse parser Agent 'A' does not allow the tool 'calculate' required by using tool. ile düşer. Her adapter'a kendi diliyle bildirilir — bkz. Provider eşlemesi. Bir adımın, argümanlarını model seçmeden kesin çalışmasını istiyorsan direct tool çağrısı hâlâ daha basit cevaptır.

Provider eşlemesi

Provider ailesiModel → tool çağrısıRuntime → sonuç
OpenAI-compatible · Azuretool_calls[]role: "tool" mesajı
Anthropictool_use bloğutool_result içeren user mesajı
GeminifunctionCall part'ıfunctionResponse part'ı

Bu dönüşüm adapter katmanında otomatik yapılır; .luma tarafında hiçbir şey değişmez.

Limitler

AyarKapsam
tool_rate_limit 5Bir ask akışındaki toplam native tool çağrısı. Her yeni ask'te (ve her retry denemesinde) sıfırlanır.
tool_concurrencyTek provider turunda eşzamanlı çalışacak tool sayısı.
tool_failure_limitBu kadar hatadan sonra circuit breaker açılır.

Implementasyon: tool registry

Bir tool'un .luma tarafında contract'ı, host tarafında handler'ı olmalıdır. Handler yoksa program başlamaz:

Error: 'calculate' is not registered in the tool registry.

Handler imzası

ts
export type ToolHandler = (
  input: any,                      // validated input
  signal?: AbortSignal,            // timeout / iptal sinyali
  needs?: Record<string, any>,     // contract'ta bildirilen bağımlılıklar
) => any | Promise<any>;
tstools.ts
// tools.ts
export const calculate = async (input, signal, needs) => {
  if (input.operation === "add")      return { result: input.a + input.b };
  if (input.operation === "multiply") return { result: input.a * input.b };
  throw new Error("Unsupported operation");
};

Typed hata fırlatma

luma
schema ToolError {
  code: string
  message: string
  retryable: bool
  details?: any
}

tool db_write(table: string, value: string) -> DbWriteResult {
  side_effects: "write"
  error: ToolError
  retries: 3
  retry_backoff: "exponential"
  idempotent: true
  idempotency_key: request_id
}
ts
import { LumaToolError } from "./src/tool-registry";

throw new LumaToolError({
  code: "DB_TIMEOUT",
  message: "Database timeout oldu",
  retryable: true,          // → retry döngüsü devam eder
  details: { table: input.table },
});
  • retryable: true → retries kadar yeniden denenir.
  • retryable: false → hem tool retry'ı hem LLM retry döngüsü anında durur.
  • error: schema'sı verilmişse fırlatılan hata o schema'ya göre doğrulanır; uymazsa TOOL_ERROR_INVALID.
  • Hata nesnesi try/catch değişkenine olduğu gibi aktarılır.

Tool module yükleme

Varsayılan handler'lar src/tool-registry.ts içindedir. Kendi module'ünü iki yolla ekleyebilirsin:

luma program.luma --tool-module ./tools.ts
jsonluma.config.json
{
  "tool_modules": [
    {
      "path": "./tools.ts",
      "version": "1.0.0",
      "integrity": "sha256-<base64-sha256>",
      "dependencies": ["node-fetch"],
      "dependency_root": "./vendor"
    }
  ]
}
Manifest alanıAnlamı
pathModule dosyası (program dizinine göre).
versionSemver metadata'sı. Aynı dosya farklı metadata ile ikinci kez yüklenirse hata.
integritysha256-<base64>. Dosya içeriği eşleşmezse yükleme reddedilir.
dependenciesModule'ün import/require edebileceği paket allowlist'i. Liste verilirse dinamik import() yasaklanır.
dependency_rootBağımlılıkların okunabileceği filesystem kökü (proje dışına çıkamaz).

Kısa biçim de geçerlidir: "tool_modules": ["./tools.ts"]

Dosya türüYükleme
.ts · .mtstypescript ile transpile edilip yüklenir (runtime dependency gerekir).
.js · .cjsCommonJS olarak yüklenir.
.mjs, ya da package.json'da "type": "module" olan .jsESM olarak yüklenir.

Export biçimi

Module şu anahtarlardan birini (sırayla aranır) veya doğrudan kendisini object olarak export etmelidir: lumaTools → tools → default → module'ün kendisi. Her değer bir fonksiyon olmalıdır.

ts
export const lumaTools = {
  calculate: (input) => ({ result: input.a + input.b }),
  text_stats: (input) => ({
    characters: input.text.length,
    words: input.text.trim().split(/\s+/).length,
  }),
};

Registry'de aynı isim iki kez tanımlanamaz: Tool registry conflict: calculate.

datasource / query / command (v0.4.0)

Luma tipli veritabanı contract'ını sahiplenir, sürücüyü değil. Üç bildirim — datasource, query, command — sınırı tarif eder; veritabanıyla asıl konuşan ~20 satırlık köprü ise bir tool modülü meselesidir, bilinen bir ad altında dışa açılır:

ts
export const datasources = {
  Warehouse: async (sql, params, signal) => (await pool.query(sql, params)).rows,
};

Bir DB sürücüsü npm paketidir; onu buranın dışında tutmak transpiler'ın sıfır bağımlılık sözünü koruyan şeydir. Köprü yoksa program hiçbir şey çalıştırmadan durur: datasource 'Warehouse' has no registered bridge.

Bildirimlerin kendisi, alan varsayılanları, satır doğrulama garantisi ve eksik transaction yüzeyinin gerekçesi Veri & Entegrasyon sayfasında.

Sandbox politikası

jsonluma.config.json
{
  "tool_module_policy": {
    "sandbox": "strict",
    "allow_builtins": ["crypto"],
    "allow_env": false,
    "allow_network": false,
    "max_memory_mb": 256,
    "max_concurrency": 2
  }
}
AyarDeğerlerVarsayılanEtkisi
sandboxnone · strictnonestrict: aşağıdaki yükleme-anı kontrollerini ve bir kaynak lint'ini açar. CommonJS/TS modülleri ayrıca açık global listesi olan bir VM context'inde koşar; ESM modülleri süreç içinde yüklenir.
allow_builtinsstring listesi[]İzin verilen Node builtin'leri. Listede olmayan require("fs") reddedilir.
allow_envboolfalsefalse ise metninde process ya da globalThis geçen modül reddedilir. Engel değil, lint — aşağıdaki uyarıya bak.
allow_networkboolfalsefalse ise metninde fetch, http, net, tls ya da WebSocket geçen modül reddedilir. Engel değil, lint.
max_memory_mbint ≥ 16—Kabul edilip kaydediliyor ama v0.6.0'dan beri uygulanmıyor — artık var olmayan child process'i sınırlıyordu.
max_concurrencyint ≥ 11Bir modüle eşzamanlı çağrı sayısı. Worker'a bağlıydı; bugünkü ele alınışı için transpiler notlarına bak.
strict bir sözleşme kontrolüdür, izolasyon sınırı değil

Başkasının modülüne strict ile güvenmeden önce bunu oku. v0.6.0'a kadar bir ESM modülü OS permission modeli altında ayrı bir süreçte koşuyordu; o runtime gitti ve izolasyon yeniden üretilemedi, bu yüzden taklit edilmek yerine kaldırıldı. strict'in bugünkü anlamı ikiye ayrılıyor:

Modül yüklenirken zorlanan, fail-closed: builtin allowlist — hesaplanmış specifier dahil, çünkü o da listede olmayan bir isme çözülüp reddediliyor; çözümlemenin proje dizinine hapsi; manifest bütünlüğü; ve CommonJS için globalleri gerçekleri değil açık bir liste olan VM context'i.

Modül metni üzerinde lint, o kadar: allow_env, allow_network ve dinamik-kod kontrolleri. Yazımları eşliyorlar. Hiçbirini adlandırmayan bir modül sürecin kendi ayrıcalıklarıyla çalışmaya devam eder — şu modül her kontrolden geçiyor ve süreç başlatıyor:

export const scan = () => ({ count: Bun.spawnSync(["id", "-u"]).stdout.toString().trim().length });

Bu ölçüldü ve lint'in ileride izolasyon sanılmaması için geçen bir testle kilitlendi. Bugünkü dürüst sözleşme: hangi tool modülünü yükleyeceğine sen karar verirsin, dil onu sınırlamaz. Yalnızca kendin çalıştıracağın kodu yükle. ROADMAP.md § U10'da izleniyor.

Policy yoksa sandbox da yok

Yukarıdaki tablodaki varsayılanlar bir tool_module_policy bloğunun içinde geçerlidir. Blok hiç yoksa hiçbiri geçerli değildir: modül sandbox: "none" ile çalışır, process, global ve ağ erişilebilir durumdadır. Policy'siz bir tool modülü çalıştırılarak doğrulandı — fetch tanımlı.

Yani allow_network: false, hiçbir şey yapmayınca ulaştığın durum değildir; policy bildirip anahtarı yazmayınca ulaştığın durumdur. Yazmadığın bir tool modülüne her zaman policy ver.

Strict modda her zaman yasak olanlar

  • eval(...) ve Function(...) — dinamik kod
  • child_process — process spawn
  • Proje dizini dışındaki module'lerin yüklenmesi
Strict altında ESM module'ü CommonJS'ten daha az denetlenir

v0.6.0'a kadar strict modda bir ESM module'ü işletim sisteminin permission modeli altında ayrı süreçte koşuyordu. O dal, dayandığı runtime ile birlikte kalktı; ESM module'ü artık diğer her import gibi süreç içinde yükleniyor.

Yüklenmeden önce ESM için de koşmaya devam edenler: kaynak lint'i, manifest bütünlüğü, bağımlılık allowlist'i ve builtin allowlist'i. Koşmayan: açık global listesine sahip VM context — o yalnızca CommonJS ve TypeScript module'leri için var. Yani strict altındaki bir ESM module'ü gerçek global'leri görür.

VM context'i istiyorsan module'ü CommonJS ya da TypeScript olarak yaz. İstemiyorsan, strict'in hiçbir yanı bir ESM module'ünü körlemesine yüklemeyi güvenli kılmaz — yukarıdaki sözleşmeye bak.

Güvenlik politikaları

tool_policy

DeğerDavranış
allow varsayılanKısıt yok.
deny_writeside_effects: "write" olan tool'lar reddedilir → TOOL_PERMISSION_DENIED.
approvalWrite tool'ları yalnızca LUMA_APPROVED_TOOLS içindeyse çalışır. Terminal interaktifse onay sorulur; değilse reddedilir.
export LUMA_APPROVED_TOOLS="db_write,send_email"

scope ve credential

luma
tool db_write(table: string, value: string) -> DbResult {
  side_effects: "write"
  scope: "db:write"
  credential: "DB_PASSWORD"
}

agent Writer {
  tools: [db_write]
  tool_scopes: ["db:write"]
  tool_policy: "approval"
}
  • credential ile bildirilen env değişkeni yoksa tool hiç çalıştırılmaz: Tool 'db_write' is missing credential: DB_PASSWORD.
  • scope tanımlıysa ve agent'ın tool_scopes listesinde yoksa: Tool 'db_write' scope access was denied: db:write.
  • tool_scopes hiç verilmezse scope kontrolü yapılmaz.

needs ve tool_contexts

needs, tool'un host tarafından sağlanacak bağımlılıklarını bildirir. Bunlar prompt'a veya modele asla girmez; doğrudan handler'ın üçüncü parametresine geçer.

luma
schema Database {
  user: string
  port: int
}

tool db_write(table: string) -> DbResult {
  needs db: Database
  side_effects: "write"
}
jsonluma.config.json
{
  "tool_contexts": {
    "db": {
      "user":     { "env": "DB_USER" },
      "password": { "env": "DB_PASSWORD" },
      "port":     { "env": "DB_PORT", "parse": "integer" }
    }
  }
}
parse değeriSonuç
string varsayılanHam metin.
jsonJSON.parse — geçersizse hata.
numberSonlu sayı olmalı.
integerTam sayı olmalı.
booleanYalnızca "true" / "false".

Agent tarafında tool_contexts: ["db"] ile hangi context'lerin kullanılabileceği daraltılır. Liste verilmezse tümü serbesttir; boş liste hiçbirine izin vermez. İzin dışı erişim TOOL_CONTEXT_INVALID üretir.

Not: file tabanlı context provider'ı henüz desteklenmiyor; secret'lar için env kullanılmalıdır.

Retry, idempotency, cache

Retry ve backoff

luma
tool http_get(url: string) -> HttpResult {
  error: ToolError
  retries: 3
  retry_backoff: "exponential"
  retry_delay: 100
  timeout: 3000
}
retry_backoffBekleme süreleri (retry_delay: 100)
none varsayılan0 ms — hemen tekrar dener.
fixed100, 100, 100 …
exponential100, 200, 400 …

Yalnızca retryable: true hatalar yeniden denenir. İptal sinyali geldiyse retry durur.

Idempotency

luma
tool db_write(request_id: string, value: string) -> DbResult {
  side_effects: "write"
  idempotent: true
  idempotency_key: request_id
  idempotency_ttl_ms: 300000
  retries: 3
  retry_backoff: "exponential"
}
  • idempotency_key yalnızca bir input alan yolu olabilir (request_id veya input.request_id); ifade yazılamaz.
  • Aynı anahtar + aynı input → handler bir kez çalışır, ikinci çağrı saklanan sonucu döndürür.
  • Aynı anahtar + farklı input → TOOL_IDEMPOTENCY_CONFLICT.
  • TTL dolduğunda kayıt düşer ve tool yeniden çalışır.

Kalıcı store

jsonluma.config.json
{
  "idempotency_store": {
    "backend": "file",
    "path": ".luma-idempotency.json",
    "encryption": "aes-256-gcm"
  }
}
export LUMA_IDEMPOTENCY_KEY="uzun-ve-guvenli-bir-key"

Varsayılan store process-local'dır ("memory"). File store; exclusive lock, ölü process lock kurtarma, versiyonlu zarf ve atomik rename kullanır. Bozuk dosyalar karantinaya alınıp yenisi oluşturulur. Süresi dolan kayıtlar başlangıçta temizlenir.

File store harici transactional backend değildir

Store aynı makinedeki process'ler için güvenlidir; dağıtık bir kurulumda (çok makineli worker havuzu) paylaşılan transactional bir backend sağlamaz. Production'da bu sınırı göz önünde bulundur.

Cache ve single-flight

luma
tool calculate(a: float, b: float, operation: enum["add", "multiply"]) -> CalcResult {
  side_effects: "none"
  cache: true
}
  • Cache anahtarı tool adı + input JSON'udur; process ömrü boyunca yaşar.
  • Aynı anahtar için eşzamanlı çağrılar tek bir çalıştırmayı paylaşır (single-flight); telemetry'de single_flight: true görünür.
  • Write tool'ları ve needs kullanan tool'lar cache edilemez.

Circuit breaker

luma
agent Resilient {
  tools: [http_get]
  tool_failure_limit: 3
}

Bir agent method çağrısı içinde tool hataları sayılır. Sayaç tool_failure_limit'e ulaştığında hata TOOL_CIRCUIT_OPEN koduna dönüştürülür ve retryable: false olur — böylece bozuk bir bağımlılığa karşı denemeler durur.

Tool çalıştırma sırası

Her tool çağrısı — direct veya native fark etmez — tam olarak şu boru hattından geçer:

   1  registry araması              TOOL_NOT_FOUND
   2  credential kontrolü           TOOL_PERMISSION_DENIED
   3  scope allowlist'i             TOOL_PERMISSION_DENIED
   4  tool_policy: deny_write       TOOL_PERMISSION_DENIED
   5  tool_policy: approval         TOOL_PERMISSION_DENIED
   6  input doğrulaması             TOOL_INPUT_INVALID
   7  needs çözümleme + tip kontrolü TOOL_CONTEXT_INVALID
   8  cache / single-flight araması
   9  idempotency araması           TOOL_IDEMPOTENCY_CONFLICT
  10  handler çalıştırma  (timeout + AbortSignal)
        └─ hata → retryable ise retry/backoff ile 10'a dön   TOOL_TIMEOUT
  11  output doğrulaması            TOOL_OUTPUT_INVALID
  12  cache/idempotency yazımı + telemetry
        └─ hata olduysa: failure sayacı++ → TOOL_CIRCUIT_OPEN
Timeout hesabı

Etkin timeout min(tool.timeout ?? agent.tool_timeout, agent.tool_timeout_max) olarak hesaplanır. Timeout dolduğunda handler'a verilen AbortSignal tetiklenir — uzun süren işleri handler tarafında bu sinyale bağlamak önemlidir.

TypeScript tip üretimi

Tool tipi olarak tagged union

Bir tagged by union tanımlı bir schema'dır, dolayısıyla tool'un parametre tipi ya da dönüş tipi olabilir. Girdi olarak etiketlenmiş hâlde gelir — discriminator'ı handler görmeden önce runtime yazar, yani input.doc.kind üzerinde dallanabilirsin. Çıktı olarak yön tersine döner: tag'i senin yerine kimse yazmaz ve çıplak member alanları döndüren handler reddedilir.

luma
schema Invoice {
  invoice_number: string
}

schema Receipt {
  reference: string
}

schema Document = Invoice | Receipt tagged by kind

tool classify(text: string) -> Document {
  side_effects: "none"
}
Error: [TOOL_OUTPUT_INVALID] classify.output.kind must identify a tagged union member.

Üretilen tipler bu zorunluluğu çalışma anında keşfedilmeye bırakmak yerine taşır — union, TypeScript'te discriminated union olur, yani kind'ı unutan handler derlenmez:

ts
export type Document =
  (Invoice & { kind: "Invoice" }) | (Receipt & { kind: "Receipt" });
luma luma/018_direct_tool.luma \
  --generate-tool-types tools.generated.d.ts

Bu bayrak verildiğinde program çalıştırılmaz; yalnızca tip dosyası üretilir.

tstools.generated.d.ts
// Generated by Luma. Do not edit manually.
export type ToolHandler<I, O, N = Record<string, never>> =
  (input: I, signal?: AbortSignal, needs?: N) => O | Promise<O>;

export interface CalcResult {
  result: number;
}

export interface CalculateInput {
  a: number;
  b: number;
  operation: "add" | "multiply";
}

export interface CalculateNeeds {}

export type CalculateHandler = ToolHandler<CalculateInput, CalcResult, CalculateNeeds>;

export interface GeneratedToolModule {
  calculate: CalculateHandler;
}
tstools.ts
import type { GeneratedToolModule } from "./tools.generated";

export const tools: GeneratedToolModule = {
  calculate: async (input, signal, needs) => ({
    result: input.operation === "add" ? input.a + input.b : input.a * input.b,
  }),
};

Üretilen dosya programdaki tüm schema'ları, her tool için <Name>Input / <Name>Needs / <Name>Handler tiplerini ve toplu GeneratedToolModule arayüzünü içerir. @desc açıklamaları JSDoc yorumu olarak taşınır.

✓ Tip güvenli
export const tools: GeneratedToolModule = {
  calculate: (input) => ({
    result: input.a + input.b,
  }),
};
✗ Tip kaçağı
export const tools = {
  calculate: (input: any) => ({
    // schema değişince derleme kırılmaz
    results: input.a + input.b,
  }),
};