Kütüphane
Standard Library
Standard library, luma/std/ altında Luma ile yazılmış ince pure fn sarmalayıcılarından oluşur. Hepsi deterministiktir; where kurallarında, @valid ifadelerinde ve agent method'larında serbestçe çağrılabilir. Tek istisna std/io'dur — o io fn'lerden oluşur ve where/@valid içinde kullanılamaz.
Kullanım ve alias kuralı
import math from "std/math" // math.abs(...) import "std/math" // abs(...) — no namespace
Agent method gövdesinde x = name.function(...) satırı belirsizdir: hem bir standard library çağrısı hem de bir uses dependency'sine yapılan çağrı olabilir. Parser bunu bir namespace adları kümesine karşı çözer; bu küme aşağıdaki on geleneksel alias artı gerçekte hangi alias ile import ettiysen o. Geri kalan her şey dependency çağrısı sayılır.
Üçü de ölçülmüş üç sonuç:
- İstediğin alias ile import edebilirsin.
import numbers from "std/numeric"ardındannumbers.round(3.7)çalışır — import çözülürken senin alias'ın kaydedilir. Aşağıdaki tablo bir kısıt değil, gelenektir. - Bir
usesdependency'si bu on addan biriyle adlandırılamaz.uses col: Helperve ardındanr = col.give()namespace çağrısı olarak ayrıştırılır, dependency'ye hiç ulaşmaz ve çalışma zamanında'col' variable is not defined yet.ile düşer — dependency'den hiç söz etmeyen bir mesaj. - İkisi de olmayan bir ad — bir yazım hatası ya da unuttuğun bir import — parse zamanında adıyla düşer:
'helper' is not defined as a dependency.
| Modül | Geleneksel alias | Tür |
|---|---|---|
std/math | math | pure |
std/string | str | pure |
std/date | date | pure |
std/collections | col | pure |
std/regex | regex | pure |
std/numeric | num | pure |
std/json | json | pure |
std/schema | schema | pure |
std/agent | agent | pure |
std/findings | findings | io |
std/io | io | io |
agent A {
uses helper: Helper
fn f() -> S {
r = helper.give()
return { v: r.v }
}
}agent A {
uses col: Helper
fn f() -> S {
r = col.give()
// Error: 'col' variable is
// not defined yet.
}
}std/math
Bu modül tamamen Luma ile yazılmıştır — luma/std/math.luma içinde kaynağını okuyabilirsin.
Mutlak değer.
math.abs(0 - 7) // 7
Listedeki sayıların toplamı. Boş liste için 0.
math.sum([1, 2, 3]) // 6
İki sayıdan küçüğü.
İki sayıdan büyüğü.
Değeri verilen aralığa sıkıştırır.
math.clamp(150, 0, 100) // 100
import math from "std/math"
schema Calculation {
values: float[1..10]
total: float
where math.abs(math.sum(values) - total) <= 0.01
}std/string
Küçük harfe çevirir.
Büyük harfe çevirir.
Yalnızca baştaki ve sondaki boşlukları siler. Ortadaki boşluklar korunur.
str.trim(" Ayşe Yılmaz ") // "Ayşe Yılmaz"Önek kontrolü.
Sonek kontrolü.
Tüm eşleşmeleri değiştirir. Düz metin araması yapar — regex değildir.
str.replace("a-b-c", "-", "/") // "a/b/c"UTF-16 birimi cinsinden uzunluk — astral bir karakter 2 sayılır. (Built-in length ile aynı işi yapar.)
import str from "std/string"
schema Text {
name: string @valid(x => str.startsWith(str.trim(x), "Ayşe"))
slug: string @valid(x => str.lower(x) == "luma-dsl")
description: string @valid(x => str.textLength(x) > 5)
}str.trim içerideki tekrarlı boşluklara dokunmaz. Normalize etmek için regex kullan:
import regex from "std/regex" normalized = regex.replace(raw, "\\s+", " ")
std/date
Kabul edilen biçimler: YYYY-MM-DD ve tam ISO 8601 date-time (Z veya ±HH:MM offset zorunlu). Takvimsel olarak geçersiz tarihler (2024-02-31) reddedilir. Tüm alan okuma işlemleri UTC üzerinden yapılır.
Ayrıştırılabilir ve gerçek bir tarih mi?
left < right. Taraflardan biri geçersizse hata değil false döner.
left > right. Geçersiz girdide false.
Aradaki tam gün farkı (kesirli kısım atılır). Geçersiz tarihte hata fırlatır.
date.diffDays("2024-01-01", "2024-01-06") // 5UTC yılı. Geçersiz tarihte hata.
UTC ayı, 1–12 (0 tabanlı değil). Geçersiz tarihte hata.
UTC ayın günü. Geçersiz tarihte hata.
Değerin ilk 10 karakterini döndürür — date-time'ı YYYY-MM-DD'ye indirir.
date.toDate("2024-01-02T10:20:30Z") // "2024-01-02"import date from "std/date"
schema Period {
start: string @fmt("date") @valid(x => date.isValid(x))
end: string @fmt("date")
days: int @valid(x => x > 0)
where date.isBefore(start, end)
where date.diffDays(start, end) == days
where date.year(start) >= 2024
}Karşılaştırma fonksiyonları (isBefore/isAfter) geçersiz girdide sessizce false döner; hesaplama fonksiyonları (diffDays, year, month, day, toDate) hata fırlatır. Nullable tarih alanlarında önce date.isValid ile koru.
std/collections
Tüm elemanlar true mu? Boş liste için true.
En az biri true mu? Boş liste için false.
Liste elemanı içeriyor mu?
String listesini birleştirir.
Tekrarları atar; sıra korunur.
İç içe listeleri tamamen düzleştirir.
col.flatten([["a"], ["b", "c"]]) // ["a", "b", "c"]
import col from "std/collections"
schema Collection {
tags: string[] @valid(x => col.contains(x, "vip"))
groups: string[][]
flags: bool[] @valid(x => col.any(x))
where col.contains(tags, "vip")
where contains(col.flatten(groups), "approved")
where col.join(tags, ",") == "vip,active"
}Built-in contains hem string'de alt-dize hem array'de eleman araması yapar ve import gerektirmez. col.contains ise yalnızca string listeleri için tiplenmiştir. Scalar string araması yapacaksan built-in olanı kullan:
name: string @valid(x => contains(x, "Ayşe")) // built-in tags: string[] @valid(x => col.contains(x, "vip")) // std/collections
Kurma ve sıralama (v0.4.0)
Değeri sona ekleyen yeni bir liste döner. Hiçbir std fonksiyonu girdisini değiştirmez.
İki listeyi yeni bir listede birleştirir.
Artan, kararlı sıralama. Karşılaştırılabilir eleman türleri string, int ve float; bir listenin sıralanamaz olmasının iki yolu farklı mesajlarla reddedilir: başka türden bir eleman col.sort supports string, int and float values., karşılaştırılabilir ama karışık türler ise col.sort values have mixed types. verir. Liste olmayan argüman col.sort expects a list. verir.
Kayıt listesini bir alana göre artan ve kararlı sıralar. Anahtar string alan adıdır, lambda değil — Luma'da lambda değeri yok. Alan her elemanda bulunmalı ve tek tipli olmalı.
by_amount = col.sortBy(items, "amount")
col.sortBy ile aynı sözleşme, azalan.
Map'in anahtarları, deterministik yineleme sırasıyla.
Dilimleme ve biçimlendirme (v0.4.0)
start'tan end'e kadar (hariç) elemanlar. Negatif sınırlar adıyla reddeder; start > end boş liste verir; uzunluğu aşan end kırpılır.
İlk count eleman. Uzunluğu aşan count tüm listeyi döner — hata değil. Negatif sayı adıyla reddeder.
İlk count elemandan sonrası; uzunluğu aşınca boş liste.
size boyutlu kovalara böler; son kova kısa kalabilir. size < 1 adıyla reddeder.
col.chunk([1, 2, 3, 4, 5], 2) // [[1, 2], [3, 4], [5]]
Elemanları indekse göre ikili listelerde eşler; uzun liste kısaya göre kesilir.
Ters sırada yeni bir liste.
İlk eşleşmenin indeksi, yoksa -1.
İlk eleman. Boş liste adıyla reddeder: col.first on an empty list.
Son eleman; boş liste sözleşmesi col.first ile aynı.
Gruplama ve toplama (v0.4.0)
Kayıtları string değerli bir alana göre kovalar. Anahtar sırası deterministiktir (alan değerine göre sıralı); string olmayan anahtar alanı adıyla reddeder.
buckets = col.groupBy(orders, "region") // map<Order[]>
Her ayrık alan değeri için ilk kayıt. Sonuç alan değerine göre sıralıdır, girdi sırasına göre değil — col.sortBy'a delege ediyor; yalnızca bir kova içinde hangi kaydın kazandığı girdi sırasını izler.
Sayısal listenin toplamı; boş liste için 0. Int listesi kabul eder.
field alanında value taşıyan kayıt sayısı; eşleşme yoksa 0.
Luma ile yazılmış collection fonksiyonları sınır sözleşmelerini parametre annotation'larıyla (@valid + @feedback) ifade eder; hataları bu yüzden doğrulama önekini taşır: Error: pure col.first.values: col.first on an empty list. Önekten sonraki mesaj metni testlerle byte byte sabitlenmiştir.
std/regex
Desenler JavaScript regex sözdizimini kullanır. Desen uzunluğu 1000 karakterle sınırlıdır; geçersiz desen hata fırlatır.
Desen metinde bulunuyor mu? (test semantiği — tam eşleşme için ^…$ yaz.)
Tüm eşleşmeleri değiştirir (global bayrak).
regex.replace("Ada 42 elma", "[0-9]+", "#") // "Ada # elma"İlk eşleşmeyi döndürür; yoksa null.
Eşleşme sayısı; yoksa 0.
Desen derlenebiliyor mu? Hata fırlatmaz.
import regex from "std/regex"
schema RegexExample {
code: string @valid(x => regex.matches(x, "^[A-Z]{3}-[0-9]+$"))
text: string
found: string | null
where regex.count(text, "a") == 2
where regex.find(text, "[0-9]+") == found
}@valid gövdesindeki && ve || birleşimleri string ve parantez farkındalığıyla bölünür; regex deseninin içindeki | karakteri ifadeyi bozmaz:
code: string @valid(x => regex.matches(x, "^(AA||BB)-[0-9]+$"))
std/numeric
En yakın tam sayıya yuvarlar. Sonlu olmayan değerde hata.
Aşağı yuvarlar.
Yukarı yuvarlar.
Sonlu bir sayı mı? NaN/Infinity için false.
Tam sayı mı?
|left − right| ≤ tolerance. Kayan nokta karşılaştırmaları için doğru araç budur.
num.approximatelyEqual(0.1 + 0.2, 0.3, 0.0001) // true
Değeri aralığa sıkıştırır.
import num from "std/numeric"
schema InvoiceItem {
quantity: float | null
unit_price: float | null
total: float | null
where quantity == null || unit_price == null || total == null || num.approximatelyEqual(quantity * unit_price, total, 0.01)
@feedback("quantity * unit_price toplamına uymuyor")
}where total == subtotal + tax gibi doğrudan eşitlikler kayan nokta hataları yüzünden sağlam modellerde bile başarısız olur ve gereksiz retry'a yol açar. num.approximatelyEqual(…, 0.01) kuruş hassasiyeti için uygun bir seçimdir.
std/json
JSON metnini değere çevirir. Geçersizse Invalid JSON text. hatası.
Kompakt JSON metni.
2 boşluk girintili JSON metni.
stringify ile aynı — okunabilirlik için ayrı isim.
Ayrıştırılabilir mi? Hata fırlatmaz.
Nokta ayraçlı yolda değer var mı? undefined değilse true.
json.has(data, "seller.name")
Yoldaki değer string ise döndürür; yoksa veya tipi farklıysa null.
Yoldaki değer sayı ise döndürür, aksi halde null.
Yoldaki değer boolean ise döndürür, aksi halde null.
json.parse'tan gelen değerin tipi any'dir ve any üzerinde doğrudan alan erişimi, alan yoksa hata fırlatır:
Error: Field 'd.isyeri' was not found.
Yani bariz yoldan koruyamazsın — if d.isyeri != null, karşılaştırma olmadan önce erişimin kendisinde patlar. Bu, xs[5] is out of bounds (length 2). ve m["missing"] is not present. ile aynı kural ve bilinçli: buradaki sessiz bir null akıp giderek doğrulanmış çıktının içine düşerdi, ki bu dilin var olma sebebi tam olarak bunu engellemek. Ayrıca alan adındaki yazım hatası "yok" diye okunmak yerine yakalanmış oluyor.
Şekil senin kontrolündeyse schema bildir ve işi doğrulamaya bırak. Değilse — heterojen payload'lar, bir ext.json kolonu, üçüncü taraf webhook — gezinme yüzeyi json.has artı json.get* ailesidir; get* fonksiyonları hem eksik hem yanlış tipli için null döndürdüğü için tek kontrol ikisini birden kapsar.
import json from "std/json"
schema JsonExample {
raw: string
data: any
payload: json<Payload>
where json.isValid(raw)
where json.has(data, "id")
where json.getString(data, "id") == "p1"
where json.getNumber(data, "count") == 2
}
agent JsonProducer {
fn create() -> JsonExample {
raw: string = "{\"id\":\"p1\",\"count\":2}"
data: any = json.parse(raw)
payload: Payload = json.parse(raw)
return { raw: raw, data: data, payload: payload }
}
}json.getString(v, "a.b") hem "yol yok" hem "değer string değil" durumunda null döndürür. Ayrımı önemsiyorsan önce json.has(v, "a.b") ile kontrol et.
std/schema
Schema'lar compile-time tanımlardır ve runtime'da değiştirilemez; bu modül onları yalnızca okur. Bir schema adı expression içinde geçtiğinde otomatik olarak schema referansına dönüşür.
Schema adı.
Schema seviyesi @desc metni.
Schema seviyesi @prompt metni. Modele otomatik gönderilmez; prompt'a sen eklersin.
Tam descriptor: { name, description, prompt, fields, rules, components }.
Alan descriptor'ları listesi (aşağıdaki şekil).
Tek alanın descriptor'ı; yoksa null.
Nokta ayraçlı yol var mı? Nested schema'lara ve array eleman tiplerine iner.
schema.hasField(InvoiceExtract, "seller.name") // true
where ve koşullu kuralların listesi: { kind: "where" | "conditional", expression, feedback }.
Composition parçaları. Tagged union üzerinde ise üye adlarını döndürür — v0.9.0'dan beri resmî ve testle sabit. İkisi de değilse []. İsim → referans köprüsü yoktur: string'ler üyeleri adlandırır, onlara çözülmez.
Birden çok schema'nın descriptor listesi. Tekrar eden schema hata verir.
Tek schema'nın JSON Schema karşılığı. $defs içermez.
Ad → JSON Schema eşlemesi.
Alan descriptor'ının şekli
{
"name": "name",
"type": "string",
"optional": false,
"description": null,
"title": "Ad Soyad",
"format": null,
"aliases": [],
"deprecated": null,
"examples": ["Ayşe Yılmaz"],
"constraints": { "minLength": 2, "maxLength": 40 }
}type alanı Luma sözdiziminde yazılır: "string", "int", "LineItem[]", "map<float>", "string | null", "enum[\"a\", \"b\"]". optional, alan ? ile işaretliyse veya @default taşıyorsa true olur. constraints yalnızca tanımlı olan kısıtları içerir.
import schema from "std/schema"
import json from "std/json"
agent SchemaInspector {
fn inspect() -> ReflectionResult {
catalog = schema.catalog([Invoice, Receipt])
field = schema.field(Invoice, "invoice_number")
json_schema = schema.toJsonSchema(Invoice)
return { name: schema.name(Invoice)
, description: schema.description(Invoice)
, field_name: field.name
, has_field: schema.hasField(Invoice, "invoice_number")
, catalog_count: length(catalog)
, rule_count: length(schema.rules(Invoice))
, json_description: json.getString(json_schema, "description") }
}
}Extraction talimatını @prompt içine yazıp schema.prompt(...) ile okumak, talimatın schema ile birlikte versiyonlanmasını sağlar — alan eklediğinde talimatı da aynı yerde güncellersin.
instruction = schema.prompt(InvoiceExtract)
result = ask "{instruction}\n\nMARKDOWN:\n{markdown}" -> InvoiceExtractstd/agent
Hata nesnelerini okumak ve prompt parçaları kurmak için deterministik yardımcılar. Bu modül ask, tool, retry veya memory'yi sarmalamaz — bunlar dilin kendi özellikleridir.
Hata kodunu okur (TOOL_TIMEOUT, MODEL_RATE_LIMIT…). Kod yoksa null.
Hata mesajını okur.
retryable bayrağı; yoksa false.
Yapılandırılmış detay nesnesi; yoksa null.
Typed tool-error nesnesi üretir.
agent.toolError("VALIDATION_FAILED", "Fatura doğrulanamadı", true, { field: "total" })"Başlık:\nmetin" biçiminde bölüm üretir.
Bölümleri çift satır sonu ile birleştirir.
import agent from "std/agent"
agent Helper {
tools: [db_write]
fn run(data: string) -> Result {
try {
written = tool db_write({ request_id: "r-1", value: data })
return { status: "ok", message: "" }
} catch error {
if agent.isRetryable(error) {
return { status: "temporary_error", message: agent.errorMessage(error) }
}
return { status: agent.errorCode(error), message: agent.errorMessage(error) }
}
}
}Prompt kurma
prompt = agent.compose([
agent.section("Talimat", "Yalnızca geçerli JSON döndür."),
agent.section("Kurallar", "Bulunmayan alanları null yap."),
agent.section("Veri", json.pretty(record))
])
result = ask "{prompt}" -> OutputTalimat:
Yalnızca geçerli JSON döndür.
Kurallar:
Bulunmayan alanları null yap.
Veri:
{ … }std/findings (v0.8.0)
Bir @severity("warn") kuralının değer üzerinde bıraktığı şerhleri okur. Soft kural koşuyu durdurmaz; bulduğu şey değeri üreten operasyona kaydedilir ve programın ona ulaşma yolu bu modüldür.
Bu değer için kaydedilmiş finding'ler; yoksa boş liste. Okumak onları okundu işaretler — koşu sonu uyarısını susturan şey budur.
Listeyi oluşturmadan sayısını verir. O da okuma sayılır.
Finding alanı | Tip | Ne tutar |
|---|---|---|
rule | string | Düşen kural; valid:<ifade> ya da where:<ifade>. Tavanın işaret kaydı findings.capped kullanır. |
path | string | Nerede düştüğü, ör. Maker.make.return.score. |
message | string | Kuralın düşerken vereceği metin. |
severity | string | Bugün "warn". |
attempt | int | Değerin kabul edildiği deneme; retry olmadıysa 1. |
import findings from "std/findings"
schema Scored {
score: int @valid(x => x >= 0) @severity("warn")
}
agent Auditor {
uses maker: Maker
fn audit() -> Audit {
shaded = maker.make(-7)
flaws = findings.of(shaded)
return { score: shaded.score, flaws: length(flaws), first_rule: flaws[0].rule }
}
}İki fonksiyon da koşu durumunu okur, yani io'durlar — @valid, where ve pure fn gövdeleri onları adıyla reddeder:
Error: findings is io and cannot be used inside @valid or where rules: findings.count
Asıl mesele bu red: olmasaydı bir kural kendi ihlallerini okuyabilir ve sonucu kuralların koşma sırasına bağlı hâle gelirdi.
Finding'ler operasyon başına, parallel over altında eleman başına yaşar. Hiç şerhlenmemiş bir değer boş liste döner — çağırmadan önce korunman gereken bir durum yok. Tavan ve okunmamış uyarısı dahil tam semantik Schema & Validation sayfasında.
std/http (v0.4.0)
std/http HTTP servisleriyle sıfır bağımlılıkla konuşur — runtime'ın yerleşik fetch'i üzerinde çalışır. Bunun bedeli çağrı başına bir alt süreç, kabaca 30–80 ms: expression evaluator senkron ve senkron bir fetch yok, o yüzden istek bir alt süreçte koşuyor. Kaba taneli çağrılar için sorun değil — sayfalanmış çekme, birkaç yüz istek — ama büyük bir kümenin her satırını zenginleştirmek için yanlış araç; orada süreç maliyeti baskın çıkar. ROADMAP.md § U5'te izleniyor. Her fonksiyon standart kütüphanenin dışa açtığı ilk şemayı döner:
schema HttpResponse {
status: int
body: string
headers: map<string>
}GET. timeout milisaniyedir ve zorunludur — varsayılan yok; sessiz sonsuz bekleme sessiz bir başarısızlıktır.
JSON gövdeli POST; content-type varsayılanı application/json.
JSON gövdeli PUT.
JSON gövdeli PATCH.
DELETE.
404 de 500 de normal döner — statünün ne anlama geldiğine karar vermek programın işidir. Fırlatan tek başarısızlık host'a hiç ulaşamamaktır: http.get could not reach '<url>': <sebep>.
import http from "std/http"
response = http.get("https://api.example/v1/orders", { "authorization": token }, 5000)
if response.status == 200 {
data = json.parse(response.body)
}Redaksiyon header'ın adına göre işler, değerinin ne kadar hassas olduğuna göre değil; bu yüzden onu genel bir sır filtresi saymayın. Adı api_key · apikey · api-key · password · secret · credential · authorization ile eşleşen (büyük/küçük harf duyarsız, alt dize) bir header'ın değerinin tamamı [REDACTED] ile değiştirilir — x-api-key bu kuralın kapsamındadır, authorization ise yalnızca bearer token'ını değil değerinin tamamını kaybeder. Ayrıca runtime'ın zaten bildiği her sır değeri (ortamdan ve model profillerinden çözülen api key'ler) geçtiği her yerde değiştirilir. Diğer her header trace'e düz metin gider — cookie, x-auth-token, x-session, bir imza header'ı. URL ve gövde hiç loglanmaz.
std/state (v0.4.0)
std/state artımlı çalışmanın temel taşıdır: tek bir JSON dosyasının desteklediği düz key→string deposu. Watermark zaten saklanmış bir string'dir — ayrı bir soyutlama yok.
Saklanan değer; anahtar hiç yazılmamışsa null — ilk koşu kendini ayırt edebilir.
Kalıcılaştırır ve değeri döner. Yazma atomiktir (geçici dosya + rename): yarıda kesilen koşu depoyu bozamaz.
Saklı tüm anahtarlar.
import state from "std/state"
cursor = state.read("orders_watermark") // ilk koşu: null
state.write("orders_watermark", latest)Deponun yolu --state-file <path> ile verilir ve varsayılan yoktur: bayrak verilmezse state.read/state.write adıyla reddeder (state.read needs a state file; pass --state-file <path>.). Sessiz bir varsayılan konum sessiz veri kaybı olurdu. State gerçek bir dosyadır ve --record/--replay'in parçası değildir.
Değerler her zaman string'dir; sayı ya da tarih saklayan program json/date ile açıkça dönüştürür. --state-file, state.keys() dahil herhangi bir state.* fonksiyonu çağrıldığında zorunludur — varsayılan konum yoktur, çünkü sessiz bir varsayılan, iki iş aynı makineyi paylaştığı ilk anda sessiz veri kaybıdır.
std/io
std/io fonksiyonları where, @valid ve pure fn içinde kullanılamaz. Yalnızca agent method'larında ve io fn'ler içinde çağrılabilir. Tüm yollar proje köküyle sınırlıdır. Derinlemesine kullanım için → IO & Multimodal.
io.readFiles, --max-rows ile sınırlıdır (varsayılan 200000); bu bir io tuhaflığı değil, satır üreten yollara global bir tavandır — aynı sınır ve aynı mesaj bir query sonucunu da yönetir. Aşım adıyla reddeder ve col.chunk'ı işaret eder: Result set exceeded the row limit (200000). Process in batches with col.chunk, or raise --max-rows. Bayraklar ve tavanlar'a bak.
Metin dosyasını UTF-8 olarak okur.
Metin dosyası yazar; ara klasörleri oluşturur. Yazılan dosyanın kök-göreli yolunu döndürür.
İkili dosyayı okur: { path, mime_type, base64, size }.
Birden çok dosyayı aynı yapıda okur. --max-rows ile tavanlıdır — aşağıdaki nota bak.
Doğal sıralı, @ ön ekli yol listesi. Desen yalnızca dosya adı glob'udur (*, ?); / içeremez.
pages = io.listFiles("@0213", "*.png", false)Dosya/klasör var mı?
Uzantıdan MIME tipi; bilinmiyorsa application/octet-stream.
io.readFile sonucunu data:<mime>;base64,… URL'ine çevirir.
Dosya listesini satır satır {{image_url:…}} / {{file_url:…}} marker'larına çevirir.
import io from "std/io"
agent InvoiceReader {
model: Vision
fn transcribe(folder: string) -> Transcript {
pages = io.listFiles("@" + folder, "*.png", false)
files = io.readFiles(pages)
markers = io.toMultimodalMarkers(files)
text = ask "Tüm sayfaları Markdown'a çevir: {markers}" -> Transcript
written = io.writeText("@" + folder + "/pre.md", text.markdown)
return text
}
}