GNL 0.17: erken aşamada ve hızlı ilerliyor — API'ler sürümden sürüme değişebilirBize katıl

GNL
Dokümanlar menüsü
Core · Ücretsiz@gnldev/client

Sohbet arayüzü: client, useChat ve AG-UI

Dayanıklı bir ajanı kullanıcının önüne koymanın üç yolu: tipli bir REST/SSE istemcisi, Vercel AI SDK useChat uyumu ve bir AG-UI adaptörü — hiçbiri kendi UI kütüphanenizi yazmanızı istemez.

Ne işe yarar#

Sunucu her ajanı zaten REST ve SSE üzerinden yayınlıyor. Geriye istemci yarısı kalıyor ve burada tek bir doğru cevap yok: kimi uygulama tipli bir istemci ister, kimi zaten useChat koşar, kimi de mevcut bir AG-UI yüzeyine takılır. Üçü de aynı akışı okur.

Hepsinin paylaştığı ve baştan söylenmesi gereken bir mesele var. Dayanıklı bir tool, sonuç yerine iç sentinel dönebilir — çalıştırma onay için askıya alınmıştır ya da bir guard çağrıyı engellemiştir. Bunlar iç alanlar taşır ve bir tel sözleşmesi değildir; yani tarayıcıya asla olduğu gibi ulaşmamalıdır. Onları değiştiren maskeleme, canlı akış ile geçmiş yeniden kurulumunun paylaştığı tek bir yardımcıda durur, böylece ikisi birbirinden ayrışamaz.

Tipli istemci (@gnldev/client)#

GnlClient sunucunun uçlarını sarar: listAgents, run, resume (aynı runId artı approvals — askıdaki tool serbest bırakılır), async generator olarak stream, geri çağırmalarla streamTo, ayrıca journal zaman çizelgesi için listRuns ve getRun. Çerçeveden bağımsızdır; @gnldev/client/react kancaları ekler.

run, stream, resume
import { GnlClient } from '@gnldev/client';

const client = new GnlClient({
  baseUrl: 'http://localhost:3000/api',
  headers: { authorization: `Bearer ${token}` },
});

const { runId } = await client.run('support', { prompt: 'Where is my order?' });

for await (const ev of client.stream('support', { runId, prompt: 'and the refund?' })) {
  if (ev.event === 'text-delta') process.stdout.write(ev.data.text);
}

// an approval came back from the operator — same runId, the suspended tool is released
await client.resume('support', runId, { 'call-7': true });

React girişi useGnlAgent ile istemcinin kendi biriktiricisi üzerine kurulu bir useChat kancası sunar; mesaj durumu sizin için toplanır.

React kancaları
import { useChat, useGnlAgent } from '@gnldev/client/react';

const { messages, send, streaming } = useChat(client, 'support');

Vercel AI SDK useChat (@gnldev/chat-adapter)#

Arayüzünüz zaten AI SDK'nın useChat'ini (v5) koşuyorsa bu paket onun protokolünü konuşur. toUIMessageStreamResponse dayanıklı bir çalıştırmayı UI mesaj akışına çevirir. Uç için iki yol var: sohbet biçimini REST API'ye takmak ya da tek başına duran createChatRoute'u kullanmak.

REST API üzerinde (önerilen). surfaces içindeki chatSurface(), sohbet biçimini createRestApi'ye ekler. Çağıranın kim olduğuna, hangi organizasyonda olduğuna ve koşumun kime ait olduğuna API'nin auth'u karar verir — her REST rotasındaki kapıların aynısı. Yani ikinci bir kimlik çözücü yazmanız gerekmez. Geçerli kimlik bilgisi taşımayan istek, model çalışmadan reddedilir.

REST API üzerinde sohbet
import { createRestApi } from '@gnldev/server';
import { chatSurface } from '@gnldev/chat-adapter';
import { roleAuth } from '@gnldev/auth';
import type { CreateGnlConfig } from '@gnldev/durable';

declare const config: CreateGnlConfig;

export const api = createRestApi(config, {
  auth: roleAuth({ endUsers: { secret: process.env.GNL_END_USER_SECRET!, orgId: 'acme' } }),
  surfaces: [chatSurface()], // POST /agents/:name/chat — useChat({ api: '/agents/pay/chat' })
  cors: { origins: ['https://app.example.com'] }, // if the browser calls this API directly
});

Tek başına rota. createChatRoute'un kendi auth'u yoktur. İsteği sunucunuz doğrular (oturum çerezi, doğrulanmış token) ve çağıranın kim olduğunu rotaya identify: (req) => Principal | undefined ile söyler — @gnldev/agui, @gnldev/mcp ve @gnldev/server'ın aldığı fonksiyonun aynısı. Bir AuthProvider zaten böyle bir fonksiyondur: identify: (req) => auth.authenticate(req). Production'da identify verilmezse rota başlamayı reddeder; kullanıcı bazlı kimlik gerçekten yoksa bunu identify: () => undefined ile açıkça söylersiniz. 0.6'daki identity seçeneği artık kurulumda hata fırlatır.

tek başına bir sohbet ucu artı geçmiş
import { createChatRoute, toUIMessages } from '@gnldev/chat-adapter';
import type { Principal } from '@gnldev/auth';

// Your session lookup: who this request is, as YOUR server established it.
declare function userOf(req: Request): Promise<string | undefined>;

app.route('/api', createChatRoute({ gnl }, {
  identify: async (req): Promise<Principal | undefined> => {
    const id = await userOf(req);
    return id ? { kind: 'subject', id, roles: [] } : undefined;
  },
}));
// POST /api/agents/:name/chat — useChat({ api: '/api/agents/pay/chat' }) works unchanged.

// restore the conversation after a reload — rebuilt from the journal
const history = toUIMessages(await journal.readRun(runId));

Koşumun kime ait olduğunu principal belirler. subject o kullanıcının kendisidir ve yalnızca kendi koşumlarına ve thread'lerine ulaşır; operator kendi organizasyonundaki personeldir; application kullanıcılarından biri adına konuşur ve o kullanıcıyı gövdedeki resourceId ile adlandırır — bu alan yalnızca application için okunur, kimseyi adlandırmazsa cevap 400 olur. Organizasyon her zaman principal.orgId'dir, gövdeden okunmaz. identify hiçbir şey döndürmezse çağıran unknown olur ve hiçbir kullanıcının verisine ulaşamaz. Gönderilmiş ama reddedilmiş bir kimlik bilgisi (iptal edilmiş token, silinmiş kullanıcı) sağlayıcı bu reddi işaretliyorsa 401 alır, anonim sayılıp hizmet verilmez — @gnldev/auth-ee'nin sağlayıcısı işaretler, kendi identify'ınız markCredentialRejected(req) çağırır, ücretsiz roleAuth henüz işaretlemez.

toUIMessages diğer yarıdır: mesaj geçmişini journal'dan yeniden kurar, yani sayfa yenilenince konuşma kaybolmaz — döküm istemci durumundan değil, gerçekte ne olduğundan yeniden üretilir. Bir kesinti data-gnl-interrupt parçası olarak yüzeye çıkar; arayüz sonuç yerine bir onay istemi göstermesi gerektiğini böyle bilir.

AG-UI / CopilotKit (@gnldev/agui)#

AG-UI, CopilotKit'in ve birkaç başka ön yüzün konuştuğu protokoldür. createAguiRoute bir AG-UI HttpAgent'ının POST edebileceği tek bir uç yayınlar; pipeAguiStream ise alt seviyedeki parçadır ve çalıştırmanın akışını AG-UI olaylarına çevirir. Paket olay tiplerini kendisi tanımlar, hiçbir @ag-ui/* bağımlılığı almaz.

Kimlik, sohbet rotasındakiyle birebir aynı işler. Önerilen yol REST API üzerinde aguiSurface()'tir — createRestApi(config, { auth, surfaces: [aguiSurface()] }), /agents/:name/agui adresinde — çağıranın kim olduğuna API'nin auth'u karar verir. Tek başına createAguiRoute ise identify alır, production'da onsuz başlamaz ve açık vazgeçme olarak identify: () => undefined kabul eder.

bir AG-UI ucu
import { createAguiRoute } from '@gnldev/agui';
import { roleAuth } from '@gnldev/auth';

const auth = roleAuth({ endUsers: { secret: process.env.GNL_END_USER_SECRET!, orgId: 'acme' } })!;

const route = createAguiRoute(config, {
  identify: (req) => auth.authenticate(req), // an end user's signed token → that user, in acme
});
// POST /agents/:name/run → AG-UI SSE — an AG-UI HttpAgent can consume this directly

API#

classGnlClient

listAgents, run, resume, stream (async generator), streamTo (geri çağırmalar), listRuns, getRun. Seçenekler: baseUrl, headers, fetch (Node <18 ya da test mock'ları için).

fnuseGnlAgent

Tek bir ajan için GnlClient üzerine React kancası.

fnuseChat

İstemcinin biriktiricisi üzerine kurulu React sohbet kancası — mesaj durumu sizin için toplanır.

fntoUIMessageStreamResponse

Dayanıklı bir çalıştırmayı AI SDK useChat (v5) UI mesaj akışı yanıtına çevirir.

fntoUIMessages

Sohbet geçmişini journal'dan yeniden kurar; yenileme konuşmayı geri getirir.

fncreateChatRoute

useChat için tek başına duran bir Hono sohbet ucu. identify: (req) => Principal | undefined alır — production'da zorunlu.

fnchatSurface

useChat biçimini createRestApi'nin bir yüzeyi olarak ekler (surfaces: [chatSurface()]), /agents/:name/chat adresinde — çağıranın kim olduğuna API'nin auth'u karar verir.

fnmaskSentinelOutput

İç askı/engel sentinel'lerini istemciye güvenli bir şekille değiştirir; canlı akış ve geçmiş bunu paylaşır, böylece ayrışamazlar.

fncreateAguiRoute

Bir AG-UI HttpAgent'ının tüketebileceği tek POST ucu. identify alır — production'da zorunlu.

fnaguiSurface

AG-UI biçimini createRestApi'nin bir yüzeyi olarak ekler (surfaces: [aguiSurface()]), /agents/:name/agui adresinde.

fnpipeAguiStream

Bir çalıştırmayı AG-UI olayları olarak akıtır (pipeAgentStream'in AG-UI karşılığı).

İç sentinel'ler tarayıcıya ulaşmaz
Askıya alınmış bir tool __gnl_suspend sentinel'i, engellenmiş olan da kendi işaretini döner. İkisi de iç ayrıntı ve ham guard gerekçeleri taşır. maskSentinelOutput bunları { pending: 'approval' } / { blocked: true } ile değiştirir ve ham kesintiyi çağırana ayrıca verir; böylece arayüz onay istemini, içeriler sızmadan gösterebilir.
Tek olay eşlemesinin iki kopyası, bilerek
AG-UI adaptörü akış parçalarını, pipeAgentStream'in içinden kopyalanmış bir switch ile çevirir; import etmez. O dosya devam ettirilebilir akışın id sözleşmesini taşır ve süreç-öldürme ile exactly-once testleriyle sabitlenmiştir; oraya bir sink parametresi eklemek daha riskli olurdu. Kopya, iki şeklin adım adım aynı kalması için paralel testlerle korunuyor — ve burada yazıyor, çünkü okuyan kişi bunun bir kopya olduğunu bilmeyi hak eder.