Pi için yapılandırma, özelleştirme, platform kurulumu ve API referansları.

Extensions

pi uzantılar oluşturabilir. Kullanım durumunuz için bir tane oluşturmasını isteyin.

Extensions pi'nin davranışını genişleten TypeScript modüllerdir. Yaşam döngüsü olaylarına abone olabilirler, LLM tarafından çağrılabilen özel araçları kaydedebilirler, komutlar ekleyebilirler ve daha fazlasını yapabilirler.

/reload için yerleştirme: Otomatik keşif için uzantıları ~/.pi/agent/extensions/ (genel) veya .pi/extensions/ (proje-yerel) içine yerleştirin. pi -e./path.ts'yi yalnızca hızlı testler için kullanın. Otomatik keşfedilen konumlardaki Extensions, /reload ile çalışırken yeniden yüklenebilir.

Temel yetenekler:

  • Özel araçlar - LLM'nin pi.registerTool() aracılığıyla arayabileceği araçları kaydedin
  • Olay müdahalesi - Araç çağrılarını engelleyin veya değiştirin, bağlam ekleyin, sıkıştırmayı özelleştirin
  • Kullanıcı etkileşimi - Kullanıcılara ctx.ui aracılığıyla bilgi verin (seç, onayla, gir, bildir)
  • Özel kullanıcı arayüzü bileşenleri - Karmaşık etkileşimler için ctx.ui.custom() aracılığıyla klavye girişine sahip tam TUI bileşenler
  • Özel komutlar - /mycommand gibi komutları pi.registerCommand() aracılığıyla kaydedin
  • Oturum kalıcılığı - pi.appendEntry() aracılığıyla yeniden başlatıldıktan sonra hayatta kalan mağaza durumu
  • Özel oluşturma - Araç çağrılarının/sonuçlarının ve mesajlarının TUI'de nasıl görüneceğini kontrol edin

Örnek kullanım durumları:

  • İzin kapıları (rm -rf, sudo vb.'den önce onaylayın)
  • Git kontrol noktası oluşturma (her fırsatta saklama, dalda geri yükleme)
  • Yol koruması (blok .env, node_modules/'ye yazar)
  • Özel sıkıştırma (konuşmayı kendi tarzınızda özetleyin)
  • Konuşma özetleri (bkz. summarize.ts örneği)
  • Etkileşimli araçlar (sorular, sihirbazlar, özel diyaloglar)
  • Durum bilgisi olan araçlar (yapılacaklar listeleri, bağlantı havuzları)
  • Harici entegrasyonlar (dosya izleyicileri, web kancaları, CI tetikleyicileri)
  • Beklerken oynanan oyunlar (bkz. snake.ts örneği)

Çalışan uygulamalar için examples/extensions/'e bakın.

İçindekiler

Hızlı Başlangıç

~/.pi/agent/extensions/my-extension.ts oluştur:

import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
import { Type } from "typebox";

export default function (pi: ExtensionAPI) {
  // React to events
  pi.on("session_start", async (_event, ctx) => {
    ctx.ui.notify("Extension loaded!", "info");
  });

  pi.on("tool_call", async (event, ctx) => {
    if (event.toolName === "bash" && event.input.command?.includes("rm -rf")) {
      const ok = await ctx.ui.confirm("Dangerous!", "Allow rm -rf?");
      if (!ok) return { block: true, reason: "Blocked by user" };
    }
  });

  // Register a custom tool
  pi.registerTool({
    name: "greet",
    label: "Greet",
    description: "Greet someone by name",
    parameters: Type.Object({
      name: Type.String({ description: "Name to greet" }),
    }),
    async execute(toolCallId, params, signal, onUpdate, ctx) {
      return {
        content: [{ type: "text", text: `Hello, ${params.name}!` }],
        details: {},
      };
    },
  });

  // Register a command
  pi.registerCommand("hello", {
    description: "Say hello",
    handler: async (args, ctx) => {
      ctx.ui.notify(`Hello ${args || "world"}!`, "info");
    },
  });
}

--extension (veya -e) bayrağıyla test edin:

pi -e ./my-extension.ts

Uzantı Konumları

Güvenlik: Extensions tüm sistem izinlerinizle çalıştırın ve isteğe bağlı kod çalıştırabilir. Yalnızca güvendiğiniz kaynaklardan yükleyin.

Extensions güvenilir konumlardan otomatik olarak keşfedilir. Proje yerel .pi/extensions girişleri yalnızca projeye güvenildikten sonra yüklenir.

Konum Kapsam
~/.pi/agent/extensions/*.ts Küresel (tüm projeler)
~/.pi/agent/extensions/*/index.ts Genel (alt dizin)
.pi/extensions/*.ts Proje-yerel
.pi/extensions/*/index.ts Proje-yerel (alt dizin)

settings.json aracılığıyla ek yollar:

{
  "packages": [
    "npm:@foo/bar@1.0.0",
    "git:github.com/user/repo@v1"
  ],
  "extensions": [
    "/path/to/local/extension.ts",
    "/path/to/local/extension/dir"
  ]
}

Uzantıları npm veya git aracılığıyla pi paketleri olarak paylaşmak için, bkz. packages.md.

Mevcut İçe Aktarmalar

Paket Amaç
@earendil-works/pi-coding-agent Uzantı türleri (ExtensionAPI, ExtensionContext, etkinlikler)
typebox Takım parametreleri için şema tanımları
@earendil-works/pi-ai Yapay zeka yardımcı programları (Google uyumlu numaralandırmalar için StringEnum)
@earendil-works/pi-tui TUI özel işleme için bileşenler

npm bağımlılıklar da işe yarar. Uzantınızın yanına (veya bir ana dizine) package.json ekleyin, npm install komutunu çalıştırın; node_modules/'den yapılan içe aktarmalar otomatik olarak çözümlenir.

pi install (npm veya git) ile kurulan dağıtılmış pi paketleri için çalışma zamanı depoları dependencies'de olmalıdır. Paket kurulumunda varsayılan olarak üretim kurulumları (npm install --omit=dev) kullanılır, dolayısıyla devDependencies çalışma zamanında kullanılamaz; npmCommand yapılandırıldığında git paketleri sarmalayıcılarla uyumluluk için düz install kullanır.

Node.js yerleşikler (node:fs, node:path, vb.) de mevcuttur.

Uzantı Yazma

Bir uzantı, ExtensionAPI alan bir varsayılan fabrika işlevini dışa aktarır. Fabrika senkron veya asenkron olabilir:

import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";

export default function (pi: ExtensionAPI) {
  // Subscribe to events
  pi.on("event_name", async (event, ctx) => {
    // ctx.ui for user interaction
    const ok = await ctx.ui.confirm("Title", "Are you sure?");
    ctx.ui.notify("Done!", "info");
    ctx.ui.setStatus("my-ext", "Processing...");  // Footer status
    ctx.ui.setWidget("my-ext", ["Line 1", "Line 2"]);  // Widget above editor (default)
  });

  // Register tools, commands, shortcuts, flags
  pi.registerTool({ ... });
  pi.registerCommand("name", { ... });
  pi.registerShortcut("ctrl+x", { ... });
  pi.registerFlag("my-flag", { ... });
}

Extensions jiti aracılığıyla yüklenir, dolayısıyla TypeScript derleme olmadan çalışır.

Fabrika Promise değerini döndürürse pi, başlatmaya devam etmeden önce bunu bekler. Bu, eşzamansız başlatmanın session_start'den önce, resources_discover'den önce ve pi.registerProvider() aracılığıyla kuyruğa alınan sağlayıcı kayıtları temizlenmeden önce tamamlandığı anlamına gelir.

Eşzamansız fabrika işlevleri

Uzaktan yapılandırmayı getirme veya kullanılabilir modelleri dinamik olarak keşfetme gibi tek seferlik başlatma işleri için eşzamansız fabrika kullanın.

import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";

export default async function (pi: ExtensionAPI) {
  const response = await fetch("http://localhost:1234/v1/models");
  const payload = (await response.json()) as {
    data: Array<{
      id: string;
      name?: string;
      context_window?: number;
      max_tokens?: number;
    }>;
  };

  pi.registerProvider("local-openai", {
    baseUrl: "http://localhost:1234/v1",
    apiKey: "$LOCAL_OPENAI_API_KEY",
    api: "openai-completions",
    models: payload.data.map((model) => ({
      id: model.id,
      name: model.name ?? model.id,
      reasoning: false,
      input: ["text"],
      cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
      contextWindow: model.context_window ?? 128000,
      maxTokens: model.max_tokens ?? 4096,
    })),
  });
}

Bu model, getirilen modellerin normal başlatma sırasında ve pi --list-models'ye kadar kullanılabilir olmasını sağlar.

Uzun ömürlü kaynaklar ve kapatma

Uzantı fabrikaları hiçbir zaman oturum başlatmayan çağrılarda çalışabilir. İşlemler, yuvalar, dosya izleyiciler veya zamanlayıcılar gibi arka plan kaynaklarını fabrikadan başlatmayın.

Arka plan kaynağının başlatılmasını session_start tarihine veya kaynağa ihtiyaç duyan komut/araç/olayına kadar erteleyin. Başlattığınız oturum kapsamlı kaynakları kapatmak için idempotent bir session_shutdown işleyici kaydedin.

Uzatma Stilleri

Tek dosya - küçük uzantılar için en basiti:

~/.pi/agent/extensions/
└── my-extension.ts

index.ts içeren dizin - çoklu dosya uzantıları için:

~/.pi/agent/extensions/
└── my-extension/
    ├── index.ts        # Entry point (exports default function)
    ├── tools.ts        # Helper module
    └── utils.ts        # Helper module

Bağımlılık içeren paket - npm paketlere ihtiyaç duyan uzantılar için:

~/.pi/agent/extensions/
└── my-extension/
    ├── package.json    # Declares dependencies and entry points
    ├── package-lock.json
    ├── node_modules/   # After npm install
    └── src/
        └── index.ts
// package.json
{
  "name": "my-extension",
  "dependencies": {
    "zod": "^3.0.0",
    "chalk": "^5.0.0"
  },
  "pi": {
    "extensions": ["./src/index.ts"]
  }
}

Uzantı dizininde npm install komutunu çalıştırın, ardından node_modules/'den içe aktarmalar otomatik olarak çalışır.

Olaylar

Yaşam Döngüsüne Genel Bakış

pi starts
  │
  ├─► project_trust (user/global and CLI extensions only, before project resources load)
  ├─► session_start { reason: "startup" }
  └─► resources_discover { reason: "startup" }
      │
      ▼
user sends prompt ─────────────────────────────────────────┐
  │                                                        │
  ├─► (extension commands checked first, bypass if found)  │
  ├─► input (can intercept, transform, or handle)          │
  ├─► (skill/template expansion if not handled)            │
  ├─► before_agent_start (can inject message, modify system prompt)
  ├─► agent_start                                          │
  ├─► message_start / message_update / message_end         │
  │                                                        │
  │   ┌─── turn (repeats while LLM calls tools) ───┐       │
  │   │                                            │       │
  │   ├─► turn_start                               │       │
  │   ├─► context (can modify messages)            │       │
  │   ├─► before_provider_headers (can mutate headers)     |
  │   ├─► before_provider_request (can inspect or replace payload)
  │   ├─► after_provider_response (status + headers, before stream consume)
  │   │                                            │       │
  │   │   LLM responds, may call tools:            │       │
  │   │     ├─► tool_execution_start               │       │
  │   │     ├─► tool_call (can block)              │       │
  │   │     ├─► tool_execution_update              │       │
  │   │     ├─► tool_result (can modify)           │       │
  │   │     └─► tool_execution_end                 │       │
  │   │                                            │       │
  │   └─► turn_end                                 │       │
  │                                                        │
  ├─► agent_end                                            │
  └─► agent_settled (no retry/compaction/follow-up left)   │
                                                           │
user sends another prompt ◄────────────────────────────────┘

/new (new session) or /resume (switch session)
  ├─► session_before_switch (can cancel)
  ├─► session_shutdown
  ├─► session_start { reason: "new" | "resume", previousSessionFile? }
  └─► resources_discover { reason: "startup" }

/fork or /clone
  ├─► session_before_fork (can cancel)
  ├─► session_shutdown
  ├─► session_start { reason: "fork", previousSessionFile }
  └─► resources_discover { reason: "startup" }

/name or pi.setSessionName()
  └─► session_info_changed

/compact or auto-compaction
  ├─► session_before_compact (can cancel or customize)
  └─► session_compact

/tree navigation
  ├─► session_before_tree (can cancel or customize)
  └─► session_tree

/model or Ctrl+P (model selection/cycling)
  ├─► thinking_level_select (if model change changes/clamps thinking level)
  └─► model_select

thinking level changes (settings, keybinding, pi.setThinkingLevel())
  └─► thinking_level_select

exit (Ctrl+C, Ctrl+D, SIGHUP, SIGTERM)
  └─► session_shutdown

Başlangıç ​​Etkinlikleri

proje_güveni

Pi, dinamik yapılandırmalara (.pi veya .agents/skills) sahip bir projeye güvenilip güvenilmeyeceğine karar vermeden önce tetiklenir. Başlangıç ​​sırasında ve oturum değişimi (örneğin /resume) mevcut süreçte güveni çözülmemiş bir cwd'ye girdiğinde çalışır. Yalnızca kullanıcı/global uzantılar ve CLI -e uzantılar katılır; proje yerel uzantıları, güven çözümlenene kadar yüklenmez.

pi.on("project_trust", async (event, ctx) => {
  // event.cwd - current working directory
  // ctx has a limited trust context: cwd, mode, hasUI, and select/confirm/input/notify UI helpers
  if (await ctx.ui.confirm("Trust project?", event.cwd)) {
    return { trusted: "yes", remember: true };
  }
  return { trusted: "undecided" };
});

Bir project_trust işleyicisi { trusted: "yes" | "no" | "undecided" } döndürmelidir. Kararın sahibi, "yes" veya "no" döndüren kullanıcı/global veya CLI uzantısıdır; ilk evet/hayır kararı kazanır ve yerleşik güven istemini bastırır. Evet/hayır kararına devam etmek için remember: true tuşlarını kullanın; aksi takdirde yalnızca mevcut süreç için geçerlidir. Daha sonraki işleyicilerin veya yerleşik güven akışının karar vermesine izin vermek için "undecided" değerini döndürün. İstemden önce ctx.hasUI seçeneğini işaretleyin. Hiçbir işleyici evet/hayır döndürmezse normal güven çözümlemesi devam eder: kaydedilen trust.json kararlar önce uygulanır, ardından defaultProjectTrust pi'nin varsayılan olarak sorup sormadığını, güvendiğini veya reddedip reddetmediğini kontrol eder.

Kaynak Etkinlikleri

kaynaklar_keşfet

Uzantıların ek beceri, bilgi istemi ve tema yollarına katkıda bulunabilmesi için session_start tarihinden sonra tetiklenir. Başlangıç ​​yolu reason: "startup" kullanır. Yeniden yükleme reason: "reload" kullanır.

pi.on("resources_discover", async (event, _ctx) => {
  // event.cwd - current working directory
  // event.reason - "startup" | "reload"
  return {
    skillPaths: ["/path/to/skills"],
    promptPaths: ["/path/to/prompts"],
    themePaths: ["/path/to/themes"],
  };
});

Oturum Etkinlikleri

Oturum depolama dahili bilgileri için Session Format ve SessionManager API'e bakın.

oturum_başlangıcı

Bir oturum başlatıldığında, yüklendiğinde veya yeniden yüklendiğinde tetiklenir.

pi.on("session_start", async (event, ctx) => {
  // event.reason - "startup" | "reload" | "new" | "resume" | "fork"
  // event.previousSessionFile - present for "new", "resume", and "fork"
  ctx.ui.notify(`Session: ${ctx.sessionManager.getSessionFile() ?? "ephemeral"}`, "info");
});

session_info_changed

Geçerli oturumun görünen adı /name, RPC veya pi.setSessionName() aracılığıyla ayarlandığında tetiklenir.

pi.on("session_info_changed", async (event, ctx) => {
  // event.name - current normalized name, or undefined if cleared
  ctx.ui.notify(`Session renamed: ${event.name ?? "(none)"}`, "info");
});

session_before_switch

Yeni bir oturum başlatmadan (/new) veya oturum değiştirmeden (/resume) önce tetiklendi.

pi.on("session_before_switch", async (event, ctx) => {
  // event.reason - "new" or "resume"
  // event.targetSessionFile - session we're switching to (only for "resume")

  if (event.reason === "new") {
    const ok = await ctx.ui.confirm("Clear?", "Delete all messages?");
    if (!ok) return { cancel: true };
  }
});

Başarılı bir geçiş veya yeni oturum eyleminden sonra pi, eski uzantı örneği için session_shutdown yayar, yeni oturum için uzantıları yeniden yükler ve yeniden bağlar, ardından reason: "new" | "resume" ve previousSessionFile ile session_start yayar. session_shutdown'de temizleme çalışması yapın, ardından session_start'de herhangi bir bellek içi durumu yeniden kurun.

session_before_fork

/fork ile çatallanırken veya /clone ile klonlanırken ateşlenir.

pi.on("session_before_fork", async (event, ctx) => {
  // event.entryId - ID of the selected entry
  // event.position - "before" for /fork, "at" for /clone
  return { cancel: true }; // Cancel fork/clone
  // OR
  return { skipConversationRestore: true }; // Reserved for future conversation restore control
});

Başarılı bir çatallanma veya klonlamanın ardından pi, eski uzantı örneği için session_shutdown yayar, yeni oturum için uzantıları yeniden yükler ve yeniden bağlar, ardından reason: "fork" ve previousSessionFile ile session_start yayar. session_shutdown'de temizleme çalışması yapın, ardından session_start'de herhangi bir bellek içi durumu yeniden kurun.

session_before_compact / session_compact

Sıkıştırma sırasında ateşlendi. Ayrıntılar için compaction.md'e bakın.

pi.on("session_before_compact", async (event, ctx) => {
  const { preparation, branchEntries, customInstructions, reason, willRetry, signal } = event;

  // reason - "manual" (/compact), "threshold", or "overflow"
  // willRetry - whether the aborted turn is retried after compaction (overflow recovery)

  // Cancel:
  return { cancel: true };

  // Custom summary:
  return {
    compaction: {
      summary: "...",
      firstKeptEntryId: preparation.firstKeptEntryId,
      tokensBefore: preparation.tokensBefore,
      // usage: summaryResponse.usage, // Optional; included in session totals
    }
  };
});

pi.on("session_compact", async (event, ctx) => {
  // event.compactionEntry - the saved compaction
  // event.fromExtension - whether extension provided it
  // event.reason - "manual" (/compact), "threshold", or "overflow"
  // event.willRetry - whether the aborted turn is retried after compaction (overflow recovery)
});

oturum_önceki_ağaç / oturum_ağaç

/tree navigasyonda tetiklendi. Ağaç gezinme kavramları için Sessions'ye bakın.

pi.on("session_before_tree", async (event, ctx) => {
  const { preparation, signal } = event;
  return { cancel: true };
  // OR provide custom summary:
  return {
    summary: {
      summary: "...",
      // usage: summaryResponse.usage, // Optional; included in session totals
      details: {},
    },
  };
});

pi.on("session_tree", async (event, ctx) => {
  // event.newLeafId, oldLeafId, summaryEntry, fromExtension
});

session_shutdown

Başlatılmış bir oturum çalışma zamanı kesilmeden önce tetiklenir. session_start veya diğer oturum kapsamlı kancalardan açılan kaynakları temizlemek için bunu kullanın.

pi.on("session_shutdown", async (event, ctx) => {
  // event.reason - "quit" | "reload" | "new" | "resume" | "fork"
  // event.targetSessionFile - destination session for session replacement flows
  // Cleanup, save state, etc.
});

Temsilci Etkinlikleri

before_agent_start

Kullanıcı istemi gönderdikten sonra, aracı döngüsünden önce tetiklenir. Bir mesaj enjekte edebilir ve/veya sistem istemini değiştirebilir.

pi.on("before_agent_start", async (event, ctx) => {
  // event.prompt - user's prompt text
  // event.images - attached images (if any)
  // event.systemPrompt - current chained system prompt for this handler
  //   (includes changes from earlier before_agent_start handlers)
  // event.systemPromptOptions - structured options used to build the system prompt
  //   .customPrompt - any custom system prompt (from --system-prompt, SYSTEM.md, or custom templates)
  //   .selectedTools - tools currently active in the prompt
  //   .toolSnippets - one-line descriptions for each tool
  //   .promptGuidelines - custom guideline bullets
  //   .appendSystemPrompt - text from --append-system-prompt flags
  //   .cwd - working directory
  //   .contextFiles - AGENTS.md files and other loaded context files
  //   .skills - loaded skills

  return {
    // Inject a persistent message (stored in session, sent to LLM)
    message: {
      customType: "my-extension",
      content: "Additional context for the LLM",
      display: true,
    },
    // Replace the system prompt for this turn (chained across extensions)
    systemPrompt: event.systemPrompt + "\n\nExtra instructions for this turn...",
  };
});

systemPromptOptions alanı, uzantılara, Pi'nin sistem istemini oluşturmak için kullandığı aynı yapılandırılmış verilere erişim sağlar. Bu, kaynakları yeniden keşfetmeden veya bayrakları yeniden ayrıştırmadan Pi'nin yüklediklerini (özel istemler, yönergeler, araç parçacıkları, context files, beceriler) incelemenizi sağlar. Uzantınızın, kullanıcı tarafından sağlanan yapılandırmayı korurken sistem isteminde derin ve bilinçli değişiklikler yapması gerektiğinde bunu kullanın.

İçerideki before_agent_start, event.systemPrompt ve ctx.getSystemPrompt()'nin her ikisi de mevcut işleyiciden itibaren zincirleme sistem istemini yansıtır. Daha sonra before_agent_start işleyiciler yine de onu yeniden değiştirebilir.

Agent_start / Agent_end / Agent_settled

agent_start düşük seviyeli bir ajan çalıştırması başladığında tetiklenir. agent_end bu çalıştırma sona erdiğinde tetiklenir, ancak Pi yine de otomatik olarak yeniden deneyebilir, otomatik olarak sıkıştırabilir ve yeniden deneyebilir veya sıradaki takip mesajlarıyla devam edebilir. Pi'nin otomatik olarak çalışmaya devam etmeyeceğini bilmesi gereken durum entegrasyonları için agent_settled kullanın.

pi.on("agent_start", async (_event, ctx) => {});

pi.on("agent_end", async (event, ctx) => {
  // event.messages - messages from this low-level run
});

pi.on("agent_settled", async (_event, ctx) => {
  // ctx.isIdle() is true here unless another extension started a new run.
});

dönüş_başlangıç ​​/ dönüş_son

Her turda ateşlenir (bir LLM yanıtı + araç çağrıları).

pi.on("turn_start", async (event, ctx) => {
  // event.turnIndex, event.timestamp
});

pi.on("turn_end", async (event, ctx) => {
  // event.turnIndex, event.message, event.toolResults
});

message_start / message_update / message_end

İleti yaşam döngüsü güncellemeleri nedeniyle tetiklendi.

  • message_start ve message_end kullanıcı, asistan ve araçSonuç mesajları için tetiklenir.
  • message_update asistan akış güncellemeleri için etkinleşir.
  • message_end işleyiciler, sonlandırılmış mesajı değiştirmek için { message } değerini döndürebilir. Değiştirme aynı role tutmalıdır.
pi.on("message_start", async (event, ctx) => {
  // event.message
});

pi.on("message_update", async (event, ctx) => {
  // event.message
  // event.assistantMessageEvent (token-by-token stream event)
});

pi.on("message_end", async (event, ctx) => {
  if (event.message.role !== "assistant") return;

  return {
    message: {
      ...event.message,
      usage: {
        ...event.message.usage,
        cost: {
          ...event.message.usage.cost,
          total: 0.123,
        },
      },
    },
  };
});

tool_execution_start / tool_execution_update / tool_execution_end

Araç yürütme yaşam döngüsü güncellemeleri nedeniyle tetiklendi.

Paralel takım modunda:

  • tool_execution_start ön kontrol aşamasında yardımcı kaynak sırasına göre yayılır
  • tool_execution_update olaylar araçlara karışabilir
  • tool_execution_end her takım sonlandırıldıktan sonra takım tamamlama sırasına göre yayınlanır
  • son toolResult mesaj etkinlikleri daha sonra yardımcı kaynak sırasına göre yayınlanmaya devam eder
pi.on("tool_execution_start", async (event, ctx) => {
  // event.toolCallId, event.toolName, event.args
});

pi.on("tool_execution_update", async (event, ctx) => {
  // event.toolCallId, event.toolName, event.args, event.partialResult
});

pi.on("tool_execution_end", async (event, ctx) => {
  // event.toolCallId, event.toolName, event.result, event.isError
});

bağlam

Her LLM çağrısından önce kovuldu. Mesajları tahribatsız olarak değiştirin. Mesaj türleri için Session Format'e bakın.

pi.on("context", async (event, ctx) => {
  // event.messages - deep copy, safe to modify
  const filtered = event.messages.filter(m => !shouldPrune(m));
  return { messages: filtered };
});

before_provider_headers

Giden HTTP üstbilgileri birleştirildikten sonra tetiklenir. İstek başlıklarını eklemek, geçersiz kılmak veya kaldırmak için bunu kullanın.

İşleyiciler yerinde event.headers mutasyona uğrar. Eklemek veya geçersiz kılmak için bir dizeye veya silmek için null'ye bir tuş ayarlayın.

pi.on("before_provider_headers", (event, ctx) => {
  // Add or override — e.g. a session id for gateway tracing/attribution
  event.headers["x-session-id"] = ctx.sessionManager.getSessionId();

  // Drop a tracking header pi adds for this call
  event.headers["X-OpenRouter-Title"] = null;
});

Sağlayıcı isteği başına bir kez çalıştırılır; Kancayı yeniden ateşlemek yerine aynı başlıkları yeniden kullanmayı dener.

before_provider_request

Sağlayıcıya özel veri oluşturulduktan sonra, istek gönderilmeden hemen önce tetiklenir. İşleyiciler uzantı yükleme sırasına göre çalışır. undefined değerini döndürmek yükün değişmemesini sağlar. Başka herhangi bir değerin döndürülmesi, daha sonraki işleyicilerin ve gerçek isteğin yükünün yerini alır.

Bu kanca, sağlayıcı düzeyindeki sistem talimatlarını yeniden yazabilir veya bunları tamamen kaldırabilir. Bu yük düzeyi değişiklikleri, son serileştirilmiş sağlayıcı yükü yerine Pi'nin sistem istem dizesini bildiren ctx.getSystemPrompt() tarafından yansıtılmaz.

pi.on("before_provider_request", (event, ctx) => {
  console.log(JSON.stringify(event.payload, null, 2));

  // Optional: replace payload
  // return { ...event.payload, temperature: 0 };
});

Bu esas olarak sağlayıcı serileştirmesinde ve önbellek davranışında hata ayıklamak için kullanışlıdır.

after_provider_response

Bir HTTP yanıtı alındıktan sonra ve akış gövdesi tüketilmeden önce tetiklenir. İşleyiciler uzantı yükleme sırasına göre çalışır.

pi.on("after_provider_response", (event, ctx) => {
  // event.status - HTTP status code
  // event.headers - normalized response headers
  if (event.status === 429) {
    console.log("rate limited", event.headers["retry-after"]);
  }
});

Üstbilginin kullanılabilirliği sağlayıcıya ve aktarıma bağlıdır. Providers soyut HTTP yanıtlarının başlıkları açığa çıkarmaması.

Modeli Etkinlikleri

model_select

Model /model komutu, model döngüsü (Ctrl+P) veya oturum geri yükleme yoluyla değiştiğinde tetiklenir.

pi.on("model_select", async (event, ctx) => {
  // event.model - newly selected model
  // event.previousModel - previous model (undefined if first selection)
  // event.source - "set" | "cycle" | "restore"

  const prev = event.previousModel
    ? `${event.previousModel.provider}/${event.previousModel.id}`
    : "none";
  const next = `${event.model.provider}/${event.model.id}`;

  ctx.ui.notify(`Model changed (${event.source}): ${prev} -> ${next}`, "info");
});

Kullanıcı arayüzü öğelerini (durum çubukları, alt bilgiler) güncellemek veya etkin model değiştiğinde modele özel başlatma gerçekleştirmek için bunu kullanın.

think_level_select

Düşünme düzeyi değiştiğinde kovulur. Bu yalnızca bildirim amaçlıdır; işleyici dönüş değerleri göz ardı edilir.

pi.on("thinking_level_select", async (event, ctx) => {
  // event.level - newly selected thinking level
  // event.previousLevel - previous thinking level

  ctx.ui.setStatus("thinking", `thinking: ${event.level}`);
});

pi.setThinkingLevel(), model değişiklikleri veya yerleşik düşünme düzeyi kontrolleri aktif düşünme düzeyini değiştirdiğinde uzantı kullanıcı arayüzünü güncellemek için bunu kullanın.

Araç Olayları

tool_call

tool_execution_start sonrasında, araç çalıştırılmadan önce tetiklendi. Engelleyebilir. Yazılı girişleri daraltmak ve almak için isToolCallEventType tuşunu kullanın.

tool_call çalıştırılmadan önce pi, önceden yayılan Ajan olaylarının AgentSession boyunca boşaltılmasını bitirmesini bekler. Bu, ctx.sessionManager'nin mevcut yardımcı araç çağırma mesajı aracılığıyla güncel olduğu anlamına gelir.

Varsayılan paralel takım yürütme modunda, aynı asistan mesajından gelen kardeş takım çağrılarının sırasıyla ön kontrolü yapılır ve ardından eş zamanlı olarak yürütülür. tool_call, ctx.sessionManager'deki aynı asistan mesajından kardeş aracı sonuçlarını göreceğiniz garanti edilmez.

event.input değiştirilebilir. Yürütmeden önce araç bağımsız değişkenlerini yamamak için onu yerinde değiştirin.

Davranış garantileri:

  • event.input'deki mutasyonlar gerçek takım uygulamasını etkiler
  • Daha sonra tool_call işleyiciler daha önceki işleyiciler tarafından yapılan mutasyonları görür
  • Mutasyonunuzdan sonra yeniden doğrulama yapılmaz
  • { block: true, reason?: string, terminate?: boolean } aracılığıyla tool_call kontrol engellemesinden değerleri döndür
  • terminate yalnızca engellenen çağrı için geçerlidir; aracı yalnızca gruptaki her kesin sonuç sona erdiğinde erken durur
import { isToolCallEventType } from "@earendil-works/pi-coding-agent";

pi.on("tool_call", async (event, ctx) => {
  // event.toolName - "bash", "read", "write", "edit", etc.
  // event.toolCallId
  // event.input - tool parameters (mutable)

  // Built-in tools: no type params needed
  if (isToolCallEventType("bash", event)) {
    // event.input is { command: string; timeout?: number }
    event.input.command = `source ~/.profile\n${event.input.command}`;

    if (event.input.command.includes("rm -rf")) {
      return { block: true, reason: "Dangerous command", terminate: true };
    }
  }

  if (isToolCallEventType("read", event)) {
    // event.input is { path: string; offset?: number; limit?: number }
    console.log(`Reading: ${event.input.path}`);
  }
});

Özel araç girişi yazma

Özel araçlar giriş türlerini dışa aktarmalıdır:

// my-extension.ts
export type MyToolInput = Static<typeof myToolSchema>;

Açık tür parametreleriyle isToolCallEventType kullanın:

import { isToolCallEventType } from "@earendil-works/pi-coding-agent";
import type { MyToolInput } from "my-extension";

pi.on("tool_call", (event) => {
  if (isToolCallEventType<"my_tool", MyToolInput>("my_tool", event)) {
    event.input.action;  // typed
  }
});

tool_result

Aracın yürütülmesi tamamlandıktan sonra ve tool_execution_end artı son araç sonuç mesajı olayları yayınlanmadan önce tetiklenir. Sonucu değiştirebilir.

Paralel takım modunda, tool_result ve tool_execution_end takım tamamlama sırasına göre karışabilir, son toolResult mesaj olayları ise daha sonra yardımcı kaynak sırasına göre yayınlanmaya devam eder.

tool_result ara katman yazılımı gibi işleyiciler zinciri:

  • İşleyiciler uzantı yükleme sırasına göre çalışır
  • Her işleyici, önceki işleyici değişikliklerinden sonraki en son sonucu görür
  • İşleyiciler kısmi yamaları döndürebilir (content, details, isError veya usage); atlanan alanlar mevcut değerlerini korur

İşleyicinin içindeki iç içe eşzamansız çalışma için ctx.signal kullanın. Bu, Esc'nin model çağrılarını, fetch() ve uzantı tarafından başlatılan diğer iptal etmeye duyarlı işlemleri iptal etmesine olanak tanır.

import { isBashToolResult } from "@earendil-works/pi-coding-agent";

pi.on("tool_result", async (event, ctx) => {
  // event.toolName, event.toolCallId, event.input
  // event.content, event.details, event.isError, event.usage

  if (isBashToolResult(event)) {
    // event.details is typed as BashToolDetails
  }

  const response = await fetch("https://example.com/summarize", {
    method: "POST",
    body: JSON.stringify({ content: event.content }),
    signal: ctx.signal,
  });

  // Modify result:
  return { content: [...], details: {...}, isError: false, usage: nestedModelUsage };
});

Kullanıcı Bash Etkinlikleri

kullanıcı_bash

Kullanıcı ! veya !! komutlarını çalıştırdığında tetiklenir. Araya girebilir.

import { createLocalBashOperations } from "@earendil-works/pi-coding-agent";

pi.on("user_bash", (event, ctx) => {
  // event.command - the bash command
  // event.excludeFromContext - true if !! prefix
  // event.cwd - working directory

  // Option 1: Provide custom operations (e.g., SSH)
  return { operations: remoteBashOps };

  // Option 2: Wrap pi's built-in local bash backend
  const local = createLocalBashOperations();
  return {
    operations: {
      exec(command, cwd, options) {
        return local.exec(`source ~/.profile\n${command}`, cwd, options);
      }
    }
  };

  // Option 3: Full replacement - return result directly
  return { result: { output: "...", exitCode: 0, cancelled: false, truncated: false } };
});

Giriş Olayları

giriş

Kullanıcı girişi alındığında, uzantı komutları kontrol edildikten sonra ancak beceri ve şablon genişletmeden önce tetiklenir. Etkinlik ham giriş metnini gördüğünden /skill:foo ve /template henüz genişletilmedi.

İşleme sırası:

  1. Önce uzantı komutları (/cmd) kontrol edilir - bulunursa işleyici çalışır ve giriş olayı atlanır
  2. input olay tetiklenir - müdahale edebilir, dönüştürebilir veya işleyebilir
  3. Eğer işlenmezse: beceri komutları (/skill:name) beceri içeriğine genişletildi
  4. İşlenmezse: prompt templates (/template) şablon içeriğine genişletildi
  5. Aracı işleme başlar (before_agent_start vb.)
pi.on("input", async (event, ctx) => {
  // event.text - raw input (before skill/template expansion)
  // event.images - attached images, if any
  // event.source - "interactive" (typed), "rpc" (API), or "extension" (via sendUserMessage)
  // event.streamingBehavior - "steer" | "followUp" | undefined
  //   undefined when idle, "steer" for mid-stream interrupts,
  //   "followUp" for messages queued until the agent finishes

  // Transform: rewrite input before expansion
  if (event.text.startsWith("?quick "))
    return { action: "transform", text: `Respond briefly: ${event.text.slice(7)}` };

  // Handle: respond without LLM (extension shows its own feedback)
  if (event.text === "ping") {
    ctx.ui.notify("pong", "info");
    return { action: "handled" };
  }

  // Route by source: skip processing for extension-injected messages
  if (event.source === "extension") return { action: "continue" };

  // Intercept skill commands before expansion
  if (event.text.startsWith("/skill:")) {
    // Could transform, block, or let pass through
  }

  return { action: "continue" };  // Default: pass through to expansion
});

Sonuçlar:

  • continue - değişmeden geçiş (işleyici hiçbir şey döndürmezse varsayılan)
  • transform - metni/resimleri değiştirin, ardından genişletmeye devam edin
  • handled - temsilciyi tamamen atla (bunu geri veren ilk işleyici kazanır)

Zinciri işleyiciler arasında dönüştürür. streamingBehavior bilinçli yönlendirme için input-transform.ts ve input-transform-streaming.ts'ye bakın.

Uzantı Bağlamı

Tüm işleyiciler ctx: ExtensionContext alır.

ctx.ui

Kullanıcı etkileşimi için kullanıcı arayüzü yöntemleri. Tüm ayrıntılar için Custom UI'e bakın.

ctx.mode

Geçerli çalışma modu: "tui", "rpc", "json" veya "print". custom(), bileşen fabrikaları, terminal girişi ve doğrudan TUI oluşturma gibi yalnızca terminal özelliklerini korumak için ctx.mode === "tui" kullanın.

ctx.hasUI

TUI ve RPC modlarında true. false baskı modunda (-p) ve JSON modunda. Hem TUI hem de çalışan diyalog yöntemlerini (select, confirm, input, editor) ve ateşle ve unut yöntemlerini (notify, setStatus, setWidget, setTitle, setEditorText) korumak için bunu kullanın. RPC modları. RPC modunda, TUI'ye özgü bazı yöntemler işlem gerektirmez veya varsayılanları döndürür (bkz. rpc.md).

ctx.cwd

Geçerli çalışma dizini.

Proje yerel yapılandırma yollarını oluştururken .pi sabit kodlama yerine CONFIG_DIR_NAME kullanın. Yeniden markalanan dağıtımlar farklı bir yapılandırma dizini adı kullanabilir.

import { CONFIG_DIR_NAME, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
import { join } from "node:path";

export default function (pi: ExtensionAPI) {
  pi.on("session_start", (_event, ctx) => {
    const projectConfigPath = join(ctx.cwd, CONFIG_DIR_NAME, "my-extension.json");
    // ...
  });
}

ctx.isProjectTrusted()

Geçerli oturum bağlamı için proje yerel güveninin etkin olup olmadığını döndürür. Bu, yalnızca küresel güven deposunda kaydedilen kararları değil, geçici güven kararlarını ve CLI güven geçersiz kılmalarını da içerir.

Yalnızca güvenilir projeler için dikkate alınması gereken proje yerel uzantı yapılandırmasını okumadan önce bunu kullanın.

ctx.sessionManager'ı

Oturum durumuna salt okunur erişim. Tam SessionManager API ve giriş türleri için Session Format'ye bakın.

tool_call için bu durum, işleyiciler çalıştırılmadan önce mevcut asistan mesajı aracılığıyla senkronize edilir. Paralel takım yürütme modunda, aynı asistan mesajından kardeş takım sonuçlarının dahil edilmesi hala garanti edilmez.

ctx.sessionManager.getEntries()             // All entries
ctx.sessionManager.getBranch()              // Current branch
ctx.sessionManager.buildContextEntries()    // Active branch entries with compaction applied
ctx.sessionManager.getLeafId()              // Current leaf entry ID

ctx.modelRegistry / ctx.model / ctx.thinkingLevel / ctx.scopedModels

Modellere, sağlayıcılara ve çözümlenmiş kimlik doğrulamaya erişim. ctx.modelRegistry.getProvider(id) etkili pi-ai sağlayıcısını döndürürken getProviderAuth(id), yüklü bir model gerektirmeden mevcut API key, başlıklarını, temel URL'sini ve sağlayıcı kapsamlı ortamını çözer. ctx.model aktif model, ctx.thinkingLevel ise mevcut etkili düşünme düzeyidir.

ctx.scopedModels geçerli oturumun kapsamındaki modellerin salt okunur listesidir — /scoped-models komutunun gösterdiği kümenin aynısıdır. Oturum başlangıcında --models CLI bayrağı ve enabledModels ayarından çözümlenir (provider/modelId veya çıplak modelId'de mini eşleşme ile mevcut katalogla eşleştirilir). Kapsam belirleme yapılandırılmadığında boştur; bu, mevcut her modelin kullanılabileceği anlamına gelir. Her giriş { model, thinkingLevel? }'dir; burada thinkingLevel yalnızca bir desen sabitlendiğinde ayarlanır (örn. anthropic/*:high). Kataloğun tamamını ctx.modelRegistry.getAvailable() aracılığıyla numaralandırmak yerine yerleşik modeli yansıtan bir model seçiciyi doldurmak için bunu kullanın.

ctx.sinyali

Geçerli temsilci iptal sinyali veya hiçbir temsilci dönüşü etkin olmadığında undefined.

Uzantı işleyicileri tarafından başlatılan, iptal etme özelliğine sahip iç içe geçmiş işler için bunu kullanın, örneğin:

  • fetch(..., { signal: ctx.signal })
  • kabul edilen model çağrıları signal
  • AbortSignal kabul eden dosya veya işlem yardımcıları

ctx.signal genellikle tool_call, tool_result, message_update ve turn_end gibi aktif dönüş etkinlikleri sırasında tanımlanır. Oturum olayları, uzantı komutları ve pi boştayken başlatılan kısayollar gibi boşta veya dönüşsüz bağlamlarda genellikle undefined olur.

pi.on("tool_result", async (event, ctx) => {
  const response = await fetch("https://example.com/api", {
    method: "POST",
    body: JSON.stringify(event),
    signal: ctx.signal,
  });

  const data = await response.json();
  return { details: data };
});

ctx.isIdle() / ctx.abort() / ctx.hasPendingMessages()

Akış yardımcılarını kontrol edin. Pi bir aracı çalıştırmayı, otomatik yeniden denemeyi, otomatik sıkıştırma yeniden denemesini veya sıraya alınmış devamı işlerken ctx.isIdle() yanlıştır.

ctx.shutdown()

Pi'nin zarif bir şekilde kapatılmasını talep edin.

  • Etkileşimli mod: Temsilci boşta kalana kadar ertelenir (sıradaki tüm yönlendirme ve takip mesajlarını işledikten sonra).
  • RPC modu: Bir sonraki boşta durumuna kadar ertelenir (mevcut komut yanıtını tamamladıktan sonra, sonraki komutu beklerken).
  • Baskı modu: İşlem yok. Tüm istemler işlendiğinde işlemden otomatik olarak çıkılır.

Çıkmadan önce tüm uzantılara session_shutdown olayını yayar. Tüm bağlamlarda kullanılabilir (olay işleyicileri, araçlar, komutlar, kısayollar).

pi.on("tool_call", (event, ctx) => {
  if (isFatal(event.input)) {
    ctx.shutdown();
  }
});

ctx.getContextUsage()

Etkin model için geçerli bağlam kullanımını döndürür. Mümkün olduğunda son yardımcı kullanımını kullanır ve ardından takip eden mesajlar için belirteçleri tahmin eder.

const usage = ctx.getContextUsage();
if (usage && usage.tokens > 100_000) {
  // ...
}

ctx.compact()

Tamamlanmayı beklemeden sıkıştırmayı tetikleyin. Takip işlemleri için onComplete ve onError tuşlarını kullanın.

ctx.compact({
  customInstructions: "Focus on recent changes",
  onComplete: (result) => {
    ctx.ui.notify("Compaction completed", "info");
  },
  onError: (error) => {
    ctx.ui.notify(`Compaction failed: ${error.message}`, "error");
  },
});

ctx.getSystemPrompt()

Pi'nin geçerli sistem istem dizesini döndürür.

  • before_agent_start sırasında bu, mevcut tur için şu ana kadar yapılan zincirleme sistem istemi değişikliklerini yansıtır.
  • Daha sonraki context mesaj mutasyonlarını içermez.
  • before_provider_request yük yeniden yazma işlemlerini içermez.
  • Daha sonra yüklenen uzantılar sizinkinden sonra çalışırsa, sonuçta gönderilenleri yine de değiştirebilirler.
pi.on("before_agent_start", (event, ctx) => {
  const prompt = ctx.getSystemPrompt();
  console.log(`System prompt length: ${prompt.length}`);
});

ExtensionCommandContext

Komut işleyicileri, oturum kontrol yöntemleriyle ExtensionContext'yi genişleten ExtensionCommandContext'yi alır. Bunlar yalnızca komutlarda mevcuttur çünkü olay işleyicilerinden çağrıldıklarında kilitlenebilirler.

ctx.getSystemPromptOptions()

Pi'nin şu anda sistem istemini oluşturmak için kullandığı temel girişleri döndürür.

const options = ctx.getSystemPromptOptions();
const contextPaths = options.contextFiles?.map((file) => file.path) ?? [];

Bu, before_agent_start event.systemPromptOptions ile aynı şekle ve değiştirilebilirliğe sahiptir: özel bilgi istemi, aktif araçlar, araç parçacıkları, bilgi istemi yönergeleri, eklenen sistem bilgi istemi metni, cwd, yüklü context files ve yüklü beceriler. Tam bağlam dosyası içeriği içerebilir; bu nedenle, onu hassas uzantı yerel verileri olarak değerlendirin ve komut listeleri, günlükler veya otomatik tamamlama meta verileri aracılığıyla açığa çıkarmaktan kaçının.

Bu, geçerli temel bilgi istemi girişlerini rapor eder. Tur başına before_agent_start zincirleme sistem istemi değişikliklerini, daha sonra context olay mesajı mutasyonlarını veya before_provider_request yük yeniden yazma işlemlerini içermez.

ctx.waitForIdle()

Otomatik yeniden denemeler, otomatik sıkıştırma yeniden denemeleri ve sıraya alınmış devamlar da dahil olmak üzere aracının tamamen yerleşmesini bekleyin:

pi.registerCommand("my-cmd", {
  handler: async (args, ctx) => {
    await ctx.waitForIdle();
    // Agent is now idle, safe to modify session
  },
});

ctx.newSession(seçenekler?)

Yeni bir oturum oluşturun:

const parentSession = ctx.sessionManager.getSessionFile();
const kickoff = "Continue in the replacement session";

const result = await ctx.newSession({
  parentSession,
  setup: async (sm) => {
    sm.appendMessage({
      role: "user",
      content: [{ type: "text", text: "Context from previous session..." }],
      timestamp: Date.now(),
    });
  },
  withSession: async (ctx) => {
    // Use only the replacement-session ctx here.
    await ctx.sendUserMessage(kickoff);
  },
});

if (result.cancelled) {
  // An extension cancelled the new session
}

Seçenekler:

  • parentSession: yeni oturum başlığına kaydedilecek ana oturum dosyası
  • setup: withSession çalıştırılmadan önce yeni oturumun SessionManager'sini değiştirin
  • withSession: geçiş sonrası çalışmayı yeni bir değişim oturumu bağlamına göre çalıştırın. Yakalanan eski pi / komut ctx'yi kullanmayın; bkz. Session replacement lifecycle and footguns.

ctx.fork(giriş kimliği, seçenekler?)

Belirli bir girişten çatallanarak yeni bir oturum dosyası oluşturulur:

const result = await ctx.fork("entry-id-123", {
  withSession: async (ctx) => {
    // Use only the replacement-session ctx here.
    ctx.ui.notify("Now in the forked session", "info");
  },
});
if (result.cancelled) {
  // An extension cancelled the fork
}

const cloneResult = await ctx.fork("entry-id-456", { position: "at" });
if (cloneResult.cancelled) {
  // An extension cancelled the clone
}

Seçenekler:

  • position: "before" (varsayılan) seçili kullanıcı mesajından önce çatallar, bu istemi düzenleyiciye geri yükler
  • position: "at" düzenleyici metnini geri yüklemeden seçilen girişteki etkin yolu kopyalar
  • withSession: geçiş sonrası çalışmayı yeni bir değişim oturumu bağlamına göre çalıştırın. Yakalanan eski pi / komut ctx'yi kullanmayın; bkz. Session replacement lifecycle and footguns.

ctx.navigateTree(hedefId, seçenekler?)

session tree'de farklı bir noktaya gidin:

const result = await ctx.navigateTree("entry-id-456", {
  summarize: true,
  customInstructions: "Focus on error handling changes",
  replaceInstructions: false, // true = replace default prompt entirely
  label: "review-checkpoint",
});

Seçenekler:

  • summarize: Terk edilen dalın özetinin oluşturulup oluşturulmayacağı
  • customInstructions: Özetleyici için özel talimatlar
  • replaceInstructions: Doğruysa, customInstructions, eklenmek yerine varsayılan istemin yerine geçer
  • label: Şube özet girişine eklenecek etiket (veya özetlemiyorsa hedef giriş)

ctx.switchSession(sessionPath, seçenekler?)

Farklı bir oturum dosyasına geçin:

const result = await ctx.switchSession("/path/to/session.jsonl", {
  withSession: async (ctx) => {
    await ctx.sendUserMessage("Resume work in the replacement session");
  },
});
if (result.cancelled) {
  // An extension cancelled the switch via session_before_switch
}

Seçenekler:

Kullanılabilir oturumları keşfetmek için statik SessionManager.list() veya SessionManager.listAll() yöntemlerini kullanın:

import { SessionManager } from "@earendil-works/pi-coding-agent";

pi.registerCommand("switch", {
  description: "Switch to another session",
  handler: async (args, ctx) => {
    const sessions = await SessionManager.list(ctx.cwd);
    if (sessions.length === 0) return;
    const choice = await ctx.ui.select(
      "Pick session:",
      sessions.map(s => s.file),
    );
    if (choice) {
      await ctx.switchSession(choice, {
        withSession: async (ctx) => {
          ctx.ui.notify("Switched session", "info");
        },
      });
    }
  },
});

Oturum değiştirme yaşam döngüsü ve temelleri

withSession yeni bir ReplacedSessionContext alır ve bu, değiştirme oturumuna bağlı async sendMessage() ve sendUserMessage() yardımcılarıyla ExtensionCommandContext'yi genişletir.

Yaşam döngüsü ve tüfekler:

  • withSession yalnızca eski oturum session_shutdown yayınlandıktan, eski çalışma zamanı bozulduktan, değiştirme oturumu geri döndükten ve yeni uzantı örneği zaten session_start aldıktan sonra çalışır.
  • Geri çağırma, yeni uzantı örneğinin içinde değil, orijinal kapanışta yürütülmeye devam eder. Bu, eski uzantı örneğinizin withSession başlamadan önce kapatma temizleme işlemini zaten çalıştırmış olabileceği anlamına gelir.
  • Yakalanan eski pi / eski komut ctx oturuma bağlı nesneler değiştirildikten sonra eskidir ve kullanılırsa atılır. Oturuma bağlı çalışma için yalnızca withSession'ye iletilen ctx'yi kullanın.
  • Daha önce çıkarılan ham nesneler hâlâ sizin sorumluluğunuzdadır. Örneğin, değiştirmeden önce const sm = ctx.sessionManager yakalarsanız, sm hâlâ eski SessionManager nesnesidir. Değiştirdikten sonra tekrar kullanmayın.
  • withSession'deki kod, session_shutdown işleyiciniz tarafından geçersiz kılınan herhangi bir durumun zaten kaybolduğunu varsaymalıdır. Dizeler, kimlikler ve serileştirilmiş yapılandırma gibi yalnızca kapanmadan temiz bir şekilde kurtulabilen düz verileri yakalayın.

Güvenli desen:

pi.registerCommand("handoff", {
  handler: async (_args, ctx) => {
    const kickoff = "Continue from the replacement session";
    await ctx.newSession({
      withSession: async (ctx) => {
        await ctx.sendUserMessage(kickoff);
      },
    });
  },
});

Güvenli olmayan model:

pi.registerCommand("handoff", {
  handler: async (_args, ctx) => {
    const oldSessionManager = ctx.sessionManager;
    await ctx.newSession({
      withSession: async (_ctx) => {
        // stale old objects: do not do this
        oldSessionManager.getSessionFile();
        pi.sendUserMessage("wrong");
      },
    });
  },
});

ctx.reload()

/reload ile aynı yeniden yükleme akışını çalıştırın.

pi.registerCommand("reload-runtime", {
  description: "Reload extensions, skills, prompts, themes, and context files",
  handler: async (_args, ctx) => {
    await ctx.reload();
    return;
  },
});

Önemli davranış:

  • await ctx.reload() geçerli uzatma çalışma zamanı için session_shutdown yayar
  • Daha sonra kaynakları yeniden yükler ve reason: "reload" ile session_start ve "reload" nedeni ile resources_discover yayar
  • Şu anda çalışan komut işleyicisi hala eski çağrı çerçevesinde devam ediyor
  • await ctx.reload()'den sonraki kod, yeniden yükleme öncesi sürümden itibaren hala çalışıyor
  • await ctx.reload() sonrasındaki kod, eski bellek içi uzantı durumunun hala geçerli olduğunu varsaymamalıdır
  • İşleyici geri döndükten sonra gelecekteki komutlar/olaylar/araç çağrıları yeni uzantı sürümünü kullanır

Tahmin edilebilir davranış için, yeniden yüklemeyi söz konusu işleyicinin terminali olarak değerlendirin (await ctx.reload(); return;).

Araçlar ExtensionContext ile çalışır, dolayısıyla ctx.reload()'yi doğrudan çağıramazlar. Yeniden yükleme giriş noktası olarak bir komut kullanın, ardından bu komutu takip eden kullanıcı mesajı olarak sıraya koyan bir aracı kullanıma açın.

LLM'nin yeniden yüklemeyi tetiklemek için çağırabileceği örnek araç:

import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
import { Type } from "typebox";

export default function (pi: ExtensionAPI) {
  pi.registerCommand("reload-runtime", {
    description: "Reload extensions, skills, prompts, themes, and context files",
    handler: async (_args, ctx) => {
      await ctx.reload();
      return;
    },
  });

  pi.registerTool({
    name: "reload_runtime",
    label: "Reload Runtime",
    description: "Reload extensions, skills, prompts, themes, and context files",
    parameters: Type.Object({}),
    async execute() {
      pi.sendUserMessage("/reload-runtime", { deliverAs: "followUp" });
      return {
        content: [{ type: "text", text: "Queued /reload-runtime as a follow-up command." }],
      };
    },
  });
}

UzantıAPI Yöntemler

pi.on(olay, işleyici)

Etkinliklere abone olun. Etkinlik türleri ve dönüş değerleri için Events'ye bakın.

pi.registerTool(tanım)

Yüksek Lisans tarafından çağrılabilen özel bir aracı kaydedin. Tüm ayrıntılar için Custom Tools'e bakın.

pi.registerTool() hem uzatma yüklemesi sırasında hem de başlatma sonrasında çalışır. Bunu session_start, komut işleyicileri veya diğer olay işleyicileri içinden çağırabilirsiniz. Yeni araçlar aynı oturumda hemen yenilenir, böylece pi.getAllTools()'de görünürler ve LLM tarafından /reload olmadan çağrılabilirler.

Çalışma zamanında araçları (dinamik olarak eklenen araçlar dahil) etkinleştirmek veya devre dışı bırakmak için pi.setActiveTools() tuşunu kullanın.

Available tools'de özel bir aracı tek satırlık bir girişe dahil etmek için promptSnippet tuşunu, araç etkinken varsayılan Guidelines bölümüne araca özel madde işaretleri eklemek için promptGuidelines tuşunu kullanın.

Önemli: promptGuidelines madde işaretleri, araç adı öneki olmadan Guidelines bölümüne düz olarak eklenir. Her kılavuz, atıfta bulunduğu araca bir isim vermelidir; "Bu aracı şu durumlarda kullan..." kaçının çünkü Yüksek Lisans "bu"nun hangi araç anlamına geldiğini söyleyemez. Bunun yerine "My_tool'u şu durumlarda kullan..." yazın.

Tam bir örnek için dynamic-tools.ts'e bakın.

import { Type } from "typebox";
import { StringEnum } from "@earendil-works/pi-ai";

pi.registerTool({
  name: "my_tool",
  label: "My Tool",
  description: "What this tool does",
  promptSnippet: "Summarize or transform text according to action",
  promptGuidelines: ["Use my_tool when the user asks to summarize previously generated text."],
  parameters: Type.Object({
    action: StringEnum(["list", "add"] as const),
    text: Type.Optional(Type.String()),
  }),
  prepareArguments(args) {
    // Optional compatibility shim. Runs before schema validation.
    // Return the current schema shape, for example to fold legacy fields
    // into the modern parameter object.
    return args;
  },

  async execute(toolCallId, params, signal, onUpdate, ctx) {
    // Stream progress
    onUpdate?.({ content: [{ type: "text", text: "Working..." }] });

    return {
      content: [{ type: "text", text: "Done" }],
      details: { result: "..." },
    };
  },

  // Optional: Custom rendering
  renderCall(args, theme, context) { ... },
  renderResult(result, options, theme, context) { ... },
});

pi.sendMessage(mesaj, seçenekler?)

Oturuma özel bir mesaj enjekte edin. Özel mesajlar LLM bağlamına katılır. LLM'ye gönderilmemesi gereken yalnızca TUI kalıcı içerik için, pi.registerEntryRenderer() ile pi.appendEntry() kullanın.

pi.sendMessage({
  customType: "my-extension",
  content: "Message text",
  display: true,
  details: { ... },
}, {
  triggerTurn: true,
  deliverAs: "steer",
});

Seçenekler:

  • deliverAs - Teslimat modu:
    • "steer" (varsayılan) - Akış sırasında mesajı sıraya koyar. Mevcut asistan sırası, bir sonraki LLM çağrısından önce, araç çağrılarını yürütmeyi tamamladıktan sonra teslim edilir.
    • "followUp" - Temsilcinin bitirmesini bekler. Yalnızca temsilcinin başka araç çağrısı kalmadığında teslim edilir.
    • "nextTurn" - Bir sonraki kullanıcı istemi için sıraya alındı. Hiçbir şeyi kesintiye uğratmaz veya tetiklemez.
  • triggerTurn: true - Temsilci boştaysa hemen bir LLM yanıtını tetikleyin. Yalnızca "steer" ve "followUp" modları için geçerlidir ("nextTurn" için dikkate alınmaz).

pi.sendUserMessage(içerik, seçenekler?)

Temsilciye bir kullanıcı mesajı gönderin. Özel mesajlar gönderen sendMessage()'den farklı olarak bu, sanki kullanıcı tarafından yazılmış gibi görünen gerçek bir kullanıcı mesajı gönderir. Her zaman bir dönüşü tetikler.

// Simple text message
pi.sendUserMessage("What is 2+2?");

// With content array (text + images)
pi.sendUserMessage([
  { type: "text", text: "Describe this image:" },
  { type: "image", source: { type: "base64", mediaType: "image/png", data: "..." } },
]);

// During streaming - must specify delivery mode
pi.sendUserMessage("Focus on error handling", { deliverAs: "steer" });
pi.sendUserMessage("And then summarize", { deliverAs: "followUp" });

Seçenekler:

  • deliverAs - Aracı akış halindeyken gereklidir:
    • "steer" - Geçerli asistan sırasının araç çağrılarını yürütmesi tamamlandıktan sonra mesajı teslim edilmek üzere sıraya koyar
    • "followUp" - Temsilcinin tüm araçları bitirmesini bekler

Akış yapılmadığında mesaj hemen gönderilir ve yeni bir dönüşü tetikler. deliverAs olmadan yayın yaparken hata verir.

Tam bir örnek için send-user-message.ts'e bakın.

pi.appendEntry(customType, veri?)

Uzantı verilerini kalıcı hale getirin. Özel girişler LLM bağlamına katılmaz. Etkileşimli modda, pi.registerEntryRenderer() ile eşleştirildiğinde sohbet metninin içinde de görüntü oluşturabilirler.

pi.appendEntry("my-state", { count: 42 });
pi.appendEntry("status-card", { title: "Indexed files", count: 17 });

// Restore on reload
pi.on("session_start", async (_event, ctx) => {
  for (const entry of ctx.sessionManager.getEntries()) {
    if (entry.type === "custom" && entry.customType === "my-state") {
      // Reconstruct from entry.data
    }
  }
});

pi.setSessionName(ad)

Oturumun görünen adını ayarlayın (ilk mesaj yerine oturum seçicide gösterilir).

pi.setSessionName("Refactor auth module");

pi.getSessionName()

Ayarlanmışsa geçerli oturum adını alın.

const name = pi.getSessionName();
if (name) {
  console.log(`Session: ${name}`);
}

pi.setLabel(girişKimliği, etiket)

Girişteki etiketi ayarlayın veya temizleyin. Etiketler, yer işareti koyma ve gezinme için kullanıcı tanımlı işaretçilerdir (/tree seçicide gösterilir).

// Set a label
pi.setLabel(entryId, "checkpoint-before-refactor");

// Clear a label
pi.setLabel(entryId, undefined);

// Read labels via sessionManager
const label = ctx.sessionManager.getLabel(entryId);

Etiketler oturumda kalır ve yeniden başlatmalarda hayatta kalır. Konuşma ağacındaki önemli noktaları (dönüşler, kontrol noktaları) işaretlemek için bunları kullanın.

pi.registerCommand(ad, seçenekler)

Bir komutu kaydedin.

Birden fazla uzantı aynı komut adını kaydederse, pi bunların hepsini tutar ve yükleme sırasına göre sayısal çağırma soneklerini atar, örneğin /review:1 ve /review:2.

pi.registerCommand("stats", {
  description: "Show session statistics",
  handler: async (args, ctx) => {
    const count = ctx.sessionManager.getEntries().length;
    ctx.ui.notify(`${count} entries`, "info");
  }
});

İsteğe bağlı: /command... için bağımsız değişkenin otomatik tamamlanmasını ekleyin:

import type { AutocompleteItem } from "@earendil-works/pi-tui";

pi.registerCommand("deploy", {
  description: "Deploy to an environment",
  getArgumentCompletions: (prefix: string): AutocompleteItem[] | null => {
    const envs = ["dev", "staging", "prod"];
    const items = envs.map((e) => ({ value: e, label: e }));
    const filtered = items.filter((i) => i.value.startsWith(prefix));
    return filtered.length > 0 ? filtered : null;
  },
  handler: async (args, ctx) => {
    ctx.ui.notify(`Deploying: ${args}`, "info");
  },
});

pi.getCommands()

Geçerli oturumda slash commands'yi prompt aracılığıyla çağırmaya uygun hale getirin. Uzatma komutlarını, prompt templates ve beceri komutlarını içerir. Liste RPC get_commands sıralamasıyla eşleşiyor: önce uzantılar, sonra şablonlar, ardından beceriler.

const commands = pi.getCommands();
const bySource = commands.filter((command) => command.source === "extension");
const userScoped = commands.filter((command) => command.sourceInfo.scope === "user");

Her giriş şu şekle sahiptir:

{
  name: string; // Invokable command name without the leading slash. May be suffixed like "review:1"
  description?: string;
  source: "extension" | "prompt" | "skill";
  sourceInfo: {
    path: string;
    source: string;
    scope: "user" | "project" | "temporary";
    origin: "package" | "top-level";
    baseDir?: string;
  };
}

Kurallı kaynak alanı olarak sourceInfo kullanın. Komut adlarından veya özel yol ayrıştırmasından sahiplik sonucunu çıkarmayın.

Yerleşik etkileşimli komutlar (/model ve /settings gibi) buraya dahil edilmemiştir. Yalnızca etkileşimli olarak ele alınırlar modundadır ve prompt aracılığıyla gönderilirse yürütülmez.

pi.registerMessageRenderer(customType, oluşturucu)

customType'nizle özel mesajlar için özel bir TUI oluşturucu kaydedin. Özel mesajlar pi.sendMessage() ile oluşturulur ve LLM bağlamına katılır. Bakınız Custom UI.

pi.registerMarkdownTransformer(transformer)

Normal kullanıcı metninde, yardımcı metinde ve düşünme bloklarında Markdown için bir dönüştürücü kaydedin. Transformatörler uzatma yükü sırasına göre çalışır ve her transformatör, önceki transformatörün döndürdüğü Markdown'yi alır. Zincir tamamlandıktan sonra Pi dönüştürülen içeriği yerleşik oluşturucuyla işler.

Transformatör Markdown dizesini ve aşağıdakileri içeren bir bağlamı alır:

  • messageType"user", "assistant" veya "assistant-thinking"
  • isStreamingtrue kısmi asistan güncellemeleri için; false kullanıcı, sonlandırılan asistan ve geri yüklenen mesajlar için
  • availableWidth — dönüştürülmüş Markdown içeriği için tam terminal sütunları mevcuttur

Dönüştürülen Markdown'yi döndür:

pi.registerMarkdownTransformer((markdown, { messageType, isStreaming }) => {
  if (isStreaming || messageType === "assistant-thinking") return markdown;
  return markdown.replaceAll("-->", "→");
});

Bir transformatör atarsa ​​Pi o ana kadar üretilen Markdown'yi korur ve bir sonraki transformatörle devam eder. Kanca yalnızca görüntülenir: orijinal mesaj, oturum ve model bağlamında değişmeden kalır. Yeni kullanıcı mesajları, asistan akış güncellemeleri, geri yüklenen oturum mesajları ve terminal genişliği değişiklikleri için çalışır, dolayısıyla transformatörlerin senkronize ve ucuz kalması gerekir.

pi.registerEntryRenderer(customType, oluşturucu)

customType'ınızla özel girişler için özel bir TUI oluşturucu kaydedin. Özel girişler pi.appendEntry() ile oluşturulur ve LLM bağlamına katılmaz.

import { Box, Text } from "@earendil-works/pi-tui";

pi.registerEntryRenderer("status-card", (entry, { expanded }, theme) => {
  const data = entry.data as { title: string; count: number };
  const box = new Box(1, 1, (text) => theme.bg("customMessageBg", text));
  box.addChild(new Text(`${theme.bold(data.title)}: ${data.count}`));
  if (expanded) {
    box.addChild(new Text(theme.fg("dim", JSON.stringify(data, null, 2))));
  }
  return box;
});

pi.appendEntry("status-card", { title: "Indexed files", count: 17 });

pi.registerShortcut(kısayol, seçenekler)

Bir klavye kısayolu kaydedin. Kısayol formatı ve yerleşik tuş atamaları için keybindings.md'ye bakın.

pi.registerShortcut("ctrl+shift+p", {
  description: "Toggle plan mode",
  handler: async (ctx) => {
    ctx.ui.notify("Toggled!");
  },
});

pi.registerFlag(ad, seçenekler)

Bir CLI bayrağı kaydedin.

pi.registerFlag("plan", {
  description: "Start in plan mode",
  type: "boolean",
  default: false,
});

// Check value
if (pi.getFlag("plan")) {
  // Plan mode enabled
}

pi.exec(komut, bağımsız değişkenler, seçenekler?)

Bir kabuk komutunu yürütün.

const result = await pi.exec("git", ["status"], { signal, timeout: 5000 });
// result.stdout, result.stderr, result.code, result.killed

pi.getActiveTools() / pi.getAllTools() / pi.setActiveTools(isimler)

Etkin araçları yönetin. Bu, hem yerleşik araçlar hem de dinamik olarak kayıtlı araçlar için işe yarar. pi.getActiveTools() etkin araç adlarını string[] olarak döndürür; pi.getAllTools() yapılandırılmış tüm araçlar için meta verileri döndürür.

const active = pi.getActiveTools(); // ["read", "bash", ...]
const all = pi.getAllTools();
// all = [{
//   name: "read",
//   description: "Read file contents...",
//   parameters: ...,
//   promptGuidelines: ["Use read to examine files instead of cat or sed."],
//   sourceInfo: { path: "<builtin:read>", source: "builtin", scope: "temporary", origin: "top-level" }
// }, ...]
const builtinTools = all.filter((t) => t.sourceInfo.source === "builtin");
const extensionTools = all.filter((t) => t.sourceInfo.source !== "builtin" && t.sourceInfo.source !== "sdk");
pi.setActiveTools([...new Set([...active, "my_custom_tool"])]); // Keep current tools and enable my_custom_tool
pi.setActiveTools(["read", "bash"]); // Switch to read-only

pi.getAllTools(), name, description, parameters, promptGuidelines ve sourceInfo'yi döndürür.

Tipik sourceInfo.source değerleri:

  • builtin yerleşik araçlar için
  • sdk createAgentSession({ customTools }) üzerinden geçirilen takımlar için
  • Uzantılar tarafından kaydedilen araçlar için uzantı kaynağı meta verileri

pi.setModel(model)

Geçerli modeli ayarlayın. Model için API key mevcut değilse false değerini döndürür. Özel modelleri yapılandırmak için models.md'e bakın.

const model = ctx.modelRegistry.find("anthropic", "claude-sonnet-4-5");
if (model) {
  const success = await pi.setModel(model);
  if (!success) {
    ctx.ui.notify("No API key for this model", "error");
  }
}

pi.getThinkingLevel() / pi.setThinkingLevel(seviye)

Düşünme seviyesini alın veya ayarlayın. Düzey, model yeteneklerine bağlıdır (akıl yürütmeyen modeller her zaman "kapalı"yı kullanır). Değişiklikler thinking_level_select yayar.

const current = pi.getThinkingLevel();  // "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max"
pi.setThinkingLevel("high");

pi.events

Uzantılar arasındaki iletişim için paylaşılan olay veri yolu:

pi.events.on("my:event", (data) => { ... });
pi.events.emit("my:event", { ... });

pi.registerProvider(ad, yapılandırma)

Bir model sağlayıcıyı dinamik olarak kaydedin veya geçersiz kılın. Proxy'ler, özel uç noktalar veya ekip çapında model yapılandırmaları için kullanışlıdır.

Uzatma fabrikası işlevi sırasında yapılan çağrılar kuyruğa alınır ve çalıştırıcı başlatıldıktan sonra uygulanır. Bundan sonra yapılan çağrılar (örneğin, kullanıcı kurulum akışını izleyen bir komut işleyicisinden) /reload gerektirmeden hemen etkili olur.

Dinamik sağlayıcılar refreshModels uygulayabilir. Pi model yenileme sırasında bunu çağırır, döndürülen listeyi sağlayıcı aracılığıyla eşzamanlı olarak yayınlar ve kanonik kimlik bilgisi/depolanan katalog/ağ/sinyal bağlamını iletir. Uzantı, katalog meta verilerinin üretim kontrollü context.publish({ persist: entry }) aracılığıyla sürdürülüp sürdürülmeyeceğine karar verir; llama.cpp gibi canlı sunucular, modelleri ısrar etmeden döndürebilir.

context.signal her zaman somut bir sinyaldir ve sağlayıcı geri aramalarının bunu G/Ç engellemeye iletmesi gerekir. Genel ModelRuntime.refresh() ve ModelRegistry.refresh() çağrıları isteğe bağlı bir sinyali kabul eder ve atlandığında sınırsızdır; Uzatmalar ve başvurular kendi son tarihlerini seçer. İptal, sağlayıcı sinyali görmezden gelse bile arayanın beklemesini durdurur, ancak temeldeki işi durdurmak için yine de işbirliği gereklidir.

Yerel sağlayıcı kimlik doğrulaması, filtreleme, yenileme veya akış davranışına ihtiyaç duyan Extensions, @earendil-works/pi-ai'den tam bir Provider kaydedebilir. Sağlayıcı kompozisyon tabanı haline gelir ve models.json geçersiz kılmalar hâlâ bunun üzerinde geçerlidir.

import { createProvider, openAICompletionsApi } from "@earendil-works/pi-ai";

const provider = createProvider({
  id: "local-server",
  name: "Local Server",
  baseUrl: "http://localhost:8080/v1",
  auth: {
    apiKey: {
      name: "Local server setup",
      async login(interaction) {
        return {
          type: "api_key",
          key: await interaction.prompt({ type: "secret", message: "API key" }),
        };
      },
      async resolve({ credential }) {
        return credential?.key
          ? { auth: { apiKey: credential.key }, source: "stored API key" }
          : undefined;
      },
    },
  },
  models: [],
  api: openAICompletionsApi(),
});

pi.registerProvider(provider);

// Register a new provider with custom models
pi.registerProvider("my-proxy", {
  name: "My Proxy",
  baseUrl: "https://proxy.example.com",
  apiKey: "$PROXY_API_KEY",  // env var reference
  api: "anthropic-messages",
  models: [
    {
      id: "claude-sonnet-4-20250514",
      name: "Claude 4 Sonnet (proxy)",
      reasoning: false,
      input: ["text", "image"],
      cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
      contextWindow: 200000,
      maxTokens: 16384
    }
  ]
});

// Register a live llama.cpp catalog without persisting discovered models
pi.registerProvider("llama.cpp", {
  baseUrl: "http://localhost:8080/v1",
  apiKey: "local",
  api: "openai-completions",
  async refreshModels({ signal }) {
    const response = await fetch("http://localhost:8080/v1/models", { signal });
    const { data } = await response.json();
    return data.map(({ id }) => ({
      id,
      name: id,
      reasoning: false,
      input: ["text"],
      cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
      contextWindow: 128000,
      maxTokens: 16384
    }));
  }
});

// Override baseUrl for an existing provider (keeps all models)
pi.registerProvider("anthropic", {
  baseUrl: "https://proxy.example.com"
});

// Register provider with OAuth support for /login
pi.registerProvider("corporate-ai", {
  baseUrl: "https://ai.corp.com",
  api: "openai-responses",
  models: [...],
  oauth: {
    name: "Corporate AI (SSO)",
    async login(callbacks) {
      // Custom OAuth flow
      callbacks.onAuth({ url: "https://sso.corp.com/..." });
      const code = await callbacks.onPrompt({ message: "Enter code:" });
      return { refresh: code, access: code, expires: Date.now() + 3600000 };
    },
    async refreshToken(credentials, signal) {
      signal.throwIfAborted();
      // Refresh logic
      return credentials;
    },
    getApiKey(credentials) {
      return credentials.access;
    }
  }
});

Nesne formu, yerel auth, getModels, refreshModels, filterModels, stream ve streamSimple davranışı da dahil olmak üzere tam bir pi-ai Provider'yi kabul eder.

Eski yapılandırma seçenekleri:

  • name - Sağlayıcının kullanıcı arayüzünde /login gibi görünen adı.
  • baseUrl - API uç nokta URL'si. Modelleri tanımlarken gereklidir.
  • apiKey - API key değişmez, ortam enterpolasyonu ($ENV_VAR veya ${ENV_VAR}) veya baştaki !command. Modelleri tanımlarken gereklidir (oauth sağlanmadığı sürece). $, ``apiKey - API key değişmez, ortam enterpolasyonu ($ENV_VARveya${ENV_VAR}) veya baştaki !command. Modelleri tanımlarken gereklidir (oauthsağlanmadığı sürece).$, 'den kaçar ve $!, komut yürütmeyi tetiklemeden değişmez bir !`'den kaçar.
  • api - API yazın: "anthropic-messages", "openai-completions", "openai-responses", vb.
  • headers - İsteklere eklenecek özel başlıklar.
  • authHeader - Doğruysa, otomatik olarak Authorization: Bearer başlığını ekler.
  • models - Model tanımları dizisi. Sağlanırsa, bu sağlayıcı için mevcut tüm modellerin yerine geçer. Model tanımları, söz konusu model için sağlayıcı uç noktasını geçersiz kılmak üzere baseUrl ayarını yapabilir.
  • refreshModels - Eşzamansız dinamik keşif geri araması. İade edilen modelleri, genişletme tarafından sağlanan modellerin yerini alır. context.stored kalıcı sağlayıcı anlık görüntüsünü içerir; nesil kontrollü context.publish({ persist: entry })'yi yalnızca güncellenmiş katalog verilerinin devam etmesi gerektiğinde kullanın. Bu anlık görüntüyü silmek için persist: null tuşlarını kullanın.
  • /login desteği için oauth - OAuth sağlayıcı yapılandırması. Sağlandığında, sağlayıcı oturum açma menüsünde görünür.
  • streamSimple - Standart olmayan API'ler için özel akış uygulaması.

Gelişmiş konular için custom-provider.md'e bakın: özel akış API'ler, OAuth ayrıntıları, model tanımı referansı.

pi.unregisterProvider(isim)

Daha önce kayıtlı bir sağlayıcıyı ve modellerini kaldırın. Sağlayıcı tarafından geçersiz kılınan yerleşik modeller geri yüklenir. Sağlayıcı kayıtlı değilse hiçbir etkisi yoktur.

registerProvider gibi, bu da ilk yükleme aşamasından sonra çağrıldığında hemen etkili olur, dolayısıyla /reload gerekli değildir.

pi.registerCommand("my-setup-teardown", {
  description: "Remove the custom proxy provider",
  handler: async (_args, _ctx) => {
    pi.unregisterProvider("my-proxy");
  },
});

Devlet Yönetimi

Extensions durumla birlikte, uygun dallanma desteği için bunu araç sonucu details'da saklamalıdır:

export default function (pi: ExtensionAPI) {
  let items: string[] = [];

  // Reconstruct state from session
  pi.on("session_start", async (_event, ctx) => {
    items = [];
    for (const entry of ctx.sessionManager.getBranch()) {
      if (entry.type === "message" && entry.message.role === "toolResult") {
        if (entry.message.toolName === "my_tool") {
          items = entry.message.details?.items ?? [];
        }
      }
    }
  });

  pi.registerTool({
    name: "my_tool",
    // ...
    async execute(toolCallId, params, signal, onUpdate, ctx) {
      items.push("new item");
      return {
        content: [{ type: "text", text: "Added" }],
        details: { items: [...items] },  // Store for reconstruction
      };
    },
  });
}

Özel Araçlar

LLM'nin pi.registerTool() aracılığıyla arayabileceği araçları kaydedin. Araçlar sistem isteminde görünür ve özel işleme sahip olabilir.

Varsayılan sistem istemindeki Available tools bölümünde kısa bir tek satırlık giriş için promptSnippet tuşunu kullanın. Atlanırsa özel araçlar bu bölümün dışında bırakılır.

Varsayılan sistem istemi Guidelines bölümüne araca özel madde işaretleri eklemek için promptGuidelines tuşunu kullanın. Bu madde işaretleri yalnızca araç etkinken dahil edilir (örneğin, pi.setActiveTools([...])'den sonra).

Önemli: promptGuidelines madde işaretleri, araç adı öneki veya gruplaması olmaksızın Guidelines bölümüne düz olarak eklenir. Her kılavuz, atıfta bulunduğu araca bir isim vermelidir; "Bu aracı şu durumlarda kullan..." kaçının çünkü Yüksek Lisans "bu"nun hangi araç anlamına geldiğini söyleyemez. Bunun yerine "My_tool'u şu durumlarda kullan..." yazın.

Not: Bazı modeller aptaldır ve takım yolu argümanlarında @ önekini içerir. Yerleşik araçlar, yolları çözümlemeden önce baştaki @ karakterini çıkarır. Özel aracınız bir yolu kabul ediyorsa baştaki @ karakterini de normalleştirin.

Özel aracınız dosyaları değiştiriyorsa, yerleşik edit ve write ile aynı dosya başına kuyruğa katılması için withFileMutationQueue() kullanın. Bu önemlidir çünkü araç çağrıları varsayılan olarak paralel olarak çalışır. Sıra olmadan, iki araç aynı eski dosya içeriğini okuyabilir, farklı güncellemeleri hesaplayabilir ve ardından hangisi en son yazılırsa diğerinin üzerine yazılabilir.

Örnek başarısızlık durumu: özel aracınız foo.ts'yi düzenlerken yerleşik edit aynı asistan turunda foo.ts'yi de değiştirir. Aracınız kuyruğa katılmıyorsa, her ikisi de orijinal foo.ts'yi okuyabilir, ayrı değişiklikler uygulayabilir ve bu değişikliklerden biri kaybolur.

Ham kullanıcı bağımsız değişkenini değil, gerçek hedef dosya yolunu withFileMutationQueue()'ye iletin. Bunu önce ctx.cwd'ye veya aracınızın çalışma dizinine göre mutlak bir yola çözümleyin. Mevcut dosyalar için yardımcı, realpath() aracılığıyla kanonikleştirir, böylece aynı dosya için sembolik bağlantı takma adları bir kuyruğu paylaşır. Yeni dosyalar için çözümlenen mutlak yola geri döner çünkü henüz realpath() için hiçbir şey yoktur.

Tüm mutasyon penceresini o hedef yol üzerinde sıraya alın. Bu, yalnızca son yazmayı değil, okuma-değiştirme-yazma mantığını da içerir.

import { withFileMutationQueue } from "@earendil-works/pi-coding-agent";
import { mkdir, readFile, writeFile } from "node:fs/promises";
import { dirname, resolve } from "node:path";

async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
  const absolutePath = resolve(ctx.cwd, params.path);

  return withFileMutationQueue(absolutePath, async () => {
    await mkdir(dirname(absolutePath), { recursive: true });
    const current = await readFile(absolutePath, "utf8");
    const next = current.replace(params.oldText, params.newText);
    await writeFile(absolutePath, next, "utf8");

    return {
      content: [{ type: "text", text: `Updated ${params.path}` }],
      details: {},
    };
  });
}

Araç Tanımı

import { Type } from "typebox";
import { StringEnum } from "@earendil-works/pi-ai";
import { Text } from "@earendil-works/pi-tui";

pi.registerTool({
  name: "my_tool",
  label: "My Tool",
  description: "What this tool does (shown to LLM)",
  promptSnippet: "List or add items in the project todo list",
  promptGuidelines: [
    "Use my_tool for todo planning instead of direct file edits when the user asks for a task list."
  ],
  parameters: Type.Object({
    action: StringEnum(["list", "add"] as const),  // Use StringEnum for Google compatibility
    text: Type.Optional(Type.String()),
  }),
  prepareArguments(args) {
    if (!args || typeof args !== "object") return args;
    const input = args as { action?: string; oldAction?: string };
    if (typeof input.oldAction === "string" && input.action === undefined) {
      return { ...input, action: input.oldAction };
    }
    return args;
  },

  async execute(toolCallId, params, signal, onUpdate, ctx) {
    // Check for cancellation
    if (signal?.aborted) {
      return { content: [{ type: "text", text: "Cancelled" }] };
    }

    // Stream progress updates
    onUpdate?.({
      content: [{ type: "text", text: "Working..." }],
      details: { progress: 50 },
    });

    // Run commands via pi.exec (captured from extension closure)
    const result = await pi.exec("some-command", [], { signal });

    // Return result
    return {
      content: [{ type: "text", text: "Done" }],  // Sent to LLM
      details: { data: result },                   // For rendering & state
      // usage: nestedModelResponse.usage,          // Optional nested LLM usage
      // Optional: stop after this tool batch when every finalized tool result
      // in the batch also returns terminate: true.
      terminate: true,
    };
  },

  // Optional: Custom rendering
  renderCall(args, theme, context) { ... },
  renderResult(result, options, theme, context) { ... },
});

Kullanım hesaplaması: Bir araç iç içe geçmiş LLM çağrıları yapıyorsa, bunların birleştirilmiş Usage değerini usage olarak döndürün. Pi bunu araç sonucunda sürdürür ve altbilgiye, /session ve RPC oturum toplamlarına ekler. tool_result işleyiciler bu değeri inceleyebilir veya değiştirebilir.

Sinyalleme hataları: Bir aracın yürütülmesini başarısız olarak işaretlemek için (sonuçta isError: true ayarlar ve bunu LLM'ye bildirir), execute'den bir hata atın. Bir değerin döndürülmesi, dönüş nesnesine hangi özellikleri dahil ettiğinize bakılmaksızın hiçbir zaman hata bayrağını ayarlamaz.

Erken sonlandırma: Geçerli araç grubundan sonra otomatik takip LLM çağrısının atlanması gerektiğini belirtmek için execute()'den terminate: true'ye dönün. Bu yalnızca o gruptaki her sonlandırılmış araç sonucu sona erdiğinde etkili olur. Aracının son yapılandırılmış çıktı aracı çağrısıyla sona erdiği minimal bir örnek için examples/extensions/structured-output.ts'e bakın.

// Correct: throw to signal an error
async execute(toolCallId, params) {
  if (!isValid(params.input)) {
    throw new Error(`Invalid input: ${params.input}`);
  }
  return { content: [{ type: "text", text: "OK" }], details: {} };
}

Önemli: Dize numaralandırmaları için @earendil-works/pi-ai'den StringEnum'ı kullanın. Type.Union/Type.Literal Google'ın API'si ile çalışmaz.

Argüman hazırlığı: prepareArguments(args) isteğe bağlıdır. Tanımlanmışsa şema doğrulamasından önce ve execute()'den önce çalışır. Pi, saklanan araç çağrısı argümanları artık geçerli şemayla eşleşmeyen eski bir oturumu sürdürdüğünde, kabul edilen eski bir giriş şeklini taklit etmek için bunu kullanın. parameters ile doğrulanmasını istediğiniz nesneyi döndürün. Genel şemayı sıkı tutun. Sırf eski sürdürülen oturumların çalışmaya devam etmesi için parameters'e kullanım dışı uyumluluk alanları eklemeyin.

Örnek: eski bir oturum, üst düzey oldText ve newText içeren bir edit araç çağrısı içerebilir, mevcut şema ise yalnızca edits: [{ oldText, newText }]'yi kabul eder.

pi.registerTool({
  name: "edit",
  label: "Edit",
  description: "Edit a single file using exact text replacement",
  parameters: Type.Object({
    path: Type.String(),
    edits: Type.Array(
      Type.Object({
        oldText: Type.String(),
        newText: Type.String(),
      }),
    ),
  }),
  prepareArguments(args) {
    if (!args || typeof args !== "object") return args;

    const input = args as {
      path?: string;
      edits?: Array<{ oldText: string; newText: string }>;
      oldText?: unknown;
      newText?: unknown;
    };

    if (typeof input.oldText !== "string" || typeof input.newText !== "string") {
      return args;
    }

    return {
      ...input,
      edits: [...(input.edits ?? []), { oldText: input.oldText, newText: input.newText }],
    };
  },
  async execute(toolCallId, params, signal, onUpdate, ctx) {
    // params now matches the current schema
    return {
      content: [{ type: "text", text: `Applying ${params.edits.length} edit block(s)` }],
      details: {},
    };
  },
});

Yerleşik Araçları Geçersiz Kılma

Extensions aynı ada sahip bir aracı kaydederek yerleşik araçları (read, bash, edit, write, grep, find, ls) geçersiz kılabilir. Etkileşimli mod bu durumda bir uyarı görüntüler.

# Extension's read tool replaces built-in read
pi -e ./tool-override.ts

Alternatif olarak, uzatma araçlarını etkin durumda tutarken herhangi bir yerleşik araç olmadan başlamak için --no-builtin-tools tuşunu kullanın:

# No built-in tools, only extension tools
pi --no-builtin-tools -e ./my-extension.ts

Günlüğe kaydetme ve erişim kontrolü ile read'yi geçersiz kılan tam bir örnek için examples/extensions/tool-override.ts'ye bakın.

Oluşturma: Yerleşik oluşturucunun devralınması yuva başına çözümlenir. Yürütme geçersiz kılma ve oluşturma geçersiz kılma bağımsızdır. Geçersiz kılma işleminizde renderCall atlanırsa yerleşik renderCall kullanılır. Geçersiz kılma işleminizde renderResult atlanırsa yerleşik renderResult kullanılır. Geçersiz kılma işleminizde her ikisi de atlanırsa yerleşik oluşturucu otomatik olarak kullanılır (sözdizimi vurgulama, farklar vb.). Bu, kullanıcı arayüzünü yeniden uygulamaya gerek kalmadan günlüğe kaydetme veya erişim kontrolü için yerleşik araçları sarmanıza olanak tanır.

Bilgi meta verileri: promptSnippet ve promptGuidelines yerleşik araçtan devralınmaz. Geçersiz kılma işleminizin bu istem talimatlarını tutması gerekiyorsa bunları geçersiz kılmada açıkça tanımlayın.

Uygulamanız, details türü de dahil olmak üzere sonuç şekliyle tam olarak eşleşmelidir. Kullanıcı arayüzü ve oturum mantığı, oluşturma ve durum takibi için bu şekillere bağlıdır.

Yerleşik araç uygulamaları:

Uzaktan Yürütme

Yerleşik araçlar, uzak sistemlere (SSH, kapsayıcılar vb.) yetki vermek için takılabilir işlemleri destekler:

import { createReadTool, createBashTool, type ReadOperations } from "@earendil-works/pi-coding-agent";

// Create tool with custom operations
const remoteRead = createReadTool(cwd, {
  operations: {
    readFile: (path) => sshExec(remote, `cat ${path}`),
    access: (path) => sshExec(remote, `test -r ${path}`).then(() => {}),
  }
});

// Register, checking flag at execution time
pi.registerTool({
  ...remoteRead,
  async execute(id, params, signal, onUpdate, _ctx) {
    const ssh = getSshConfig();
    if (ssh) {
      const tool = createReadTool(cwd, { operations: createRemoteOps(ssh) });
      return tool.execute(id, params, signal, onUpdate);
    }
    return localRead.execute(id, params, signal, onUpdate);
  },
});

İşlem arayüzleri: ReadOperations, WriteOperations, EditOperations, BashOperations, LsOperations, GrepOperations, FindOperations

user_bash için uzantılar, yerel süreç oluşturmayı, kabuk çözümlemesini ve süreç ağacı sonlandırmayı yeniden uygulamak yerine pi'nin yerel kabuk arka ucunu createLocalBashOperations() aracılığıyla yeniden kullanabilir.

bash aracı aynı zamanda yürütmeden önce komutu, cwd veya env'yi ayarlamak için bir ortaya çıkma kancasını da destekler:

import { createBashTool } from "@earendil-works/pi-coding-agent";

const bashTool = createBashTool(cwd, {
  spawnHook: ({ command, cwd, env }) => ({
    command: `source ~/.profile\n${command}`,
    cwd: `/mnt/sandbox${cwd}`,
    env: { ...env, CI: "1" },
  }),
});

createBashTool() mevcut oturumu PI_SESSION_ID, PI_SESSION_FILE, PI_PROVIDER, PI_MODEL ve PI_REASONING_LEVEL aracılığıyla komutlara maruz bırakır. Enjeksiyon spawnHook'dan önce gerçekleşir, dolayısıyla kancalar bu değerleri env'de alır ve yukarıdaki gibi mevcut ortama yayıldıklarında bunları korurlar. Bunları devre dışı bırakmak için exposeSessionEnvironment: false öğesini ayarlayın:

const bashTool = createBashTool(cwd, {
  exposeSessionEnvironment: false,
});

Değişken semantiği için Bash tool session environment'ye bakın. --ssh bayrağıyla tam bir SSH örneği için examples/extensions/ssh.ts'ye bakın.

Çıkış Kesilmesi

LLM bağlamının aşırı yüklenmesini önlemek için araçların çıktılarını kesmesi GEREKİR. Büyük çıktılar şunlara neden olabilir:

  • Bağlam taşması hataları (istemin çok uzun olması)
  • Sıkıştırma hataları
  • Düşük model performansı

Dahili sınır 50 KB (~10 bin jeton) ve 2000 satır'dır (hangisi önce gerçekleşirse). Dışa aktarılan kesme yardımcı programlarını kullanın:

import {
  truncateHead,      // Keep first N lines/bytes (good for file reads, search results)
  truncateTail,      // Keep last N lines/bytes (good for logs, command output)
  truncateLine,      // Truncate a single line to maxBytes with ellipsis
  formatSize,        // Human-readable size (e.g., "50KB", "1.5MB")
  DEFAULT_MAX_BYTES, // 50KB
  DEFAULT_MAX_LINES, // 2000
} from "@earendil-works/pi-coding-agent";

async execute(toolCallId, params, signal, onUpdate, ctx) {
  const output = await runCommand();

  // Apply truncation
  const truncation = truncateHead(output, {
    maxLines: DEFAULT_MAX_LINES,
    maxBytes: DEFAULT_MAX_BYTES,
  });

  let result = truncation.content;

  if (truncation.truncated) {
    // Write full output to temp file
    const tempFile = writeTempFile(output);

    // Inform the LLM where to find complete output
    result += `\n\n[Output truncated: ${truncation.outputLines} of ${truncation.totalLines} lines`;
    result += ` (${formatSize(truncation.outputBytes)} of ${formatSize(truncation.totalBytes)}).`;
    result += ` Full output saved to: ${tempFile}]`;
  }

  return { content: [{ type: "text", text: result }] };
}

Önemli noktalar:

  • Başlangıcın önemli olduğu içerik için truncateHead kullanın (arama sonuçları, dosya okumaları)
  • Sonunun önemli olduğu içerik için truncateTail kullanın (günlükler, komut çıkışı)
  • Çıktı kesildiğinde ve tam sürümün nerede bulunacağı konusunda daima LLM'yi bilgilendirin
  • Aracınızın açıklamasında kesme sınırlarını belgeleyin

rg (ripgrep) öğesinin uygun kesmeyle sarmalanmasının tam bir örneği için examples/extensions/truncated-tool.ts öğesine bakın.

Çoklu Araçlar

Bir uzantı birden fazla aracı paylaşılan duruma kaydedebilir:

export default function (pi: ExtensionAPI) {
  let connection = null;

  pi.registerTool({ name: "db_connect", ... });
  pi.registerTool({ name: "db_query", ... });
  pi.registerTool({ name: "db_close", ... });

  pi.on("session_shutdown", async () => {
    connection?.close();
  });
}

Özel İşleme

Araçlar, özel TUI ekranı için renderCall ve renderResult sağlayabilir. Bileşenin tamamı için tui.md'ye bakın API ve araç satırlarının nasıl oluşturulduğu için tool-execution.ts'ye bakın.

Varsayılan olarak araç çıktısı, dolguyu ve arka planı işleyen bir Box içine sarılır. Tanımlanmış bir renderCall veya renderResult, Component döndürmelidir. Bir yuva oluşturucu tanımlanmamışsa tool-execution.ts o yuva için geri dönüş oluşturmayı kullanır.

Varsayılan Box kullanmak yerine aracın ne zaman kendi kabuğunu oluşturması gerektiğini renderShell: "self" olarak ayarlayın. Bu, çerçeveleme veya arka plan davranışı üzerinde tam kontrole ihtiyaç duyan araçlar için kullanışlıdır; örneğin, araç yerleştikten sonra görsel olarak sabit kalması gereken büyük önizlemeler.

pi.registerTool({
  name: "my_tool",
  label: "My Tool",
  description: "Custom shell example",
  parameters: Type.Object({}),
  renderShell: "self",
  async execute() {
    return { content: [{ type: "text", text: "ok" }], details: undefined };
  },
  renderCall(args, theme, context) {
    return new Text(theme.fg("accent", "my custom shell"), 0, 0);
  },
});

renderCall ve renderResult'nin her biri aşağıdaki özelliklere sahip bir context nesnesi alır:

  • args - mevcut araç çağrısı argümanları
  • state - renderCall ve renderResult genelinde paylaşılan satır yerel durumu
  • lastComponent - varsa o yuva için önceden döndürülen bileşen
  • invalidate() - bu araç satırının yeniden oluşturulmasını talep edin
  • toolCallId, cwd, executionStarted, argsComplete, isPartial, expanded, showImages, isError

Yuvalar arası paylaşım durumu için context.state kullanın. Aynı bileşeni işlemeler arasında yeniden kullanmak ve değiştirmek istediğinizde, döndürülen bileşen örneğinde yuva yerel önbelleklerini tutun.

renderÇağrı

Araç çağrısını veya başlığını işler:

import { Text } from "@earendil-works/pi-tui";

renderCall(args, theme, context) {
  const text = (context.lastComponent as Text | undefined) ?? new Text("", 0, 0);
  let content = theme.fg("toolTitle", theme.bold("my_tool "));
  content += theme.fg("muted", args.action);
  if (args.text) {
    content += " " + theme.fg("dim", `"${args.text}"`);
  }
  text.setText(content);
  return text;
}

renderSonucu

Araç sonucunu veya çıktısını işler:

renderResult(result, { expanded, isPartial }, theme, context) {
  if (isPartial) {
    return new Text(theme.fg("warning", "Processing..."), 0, 0);
  }

  if (result.details?.error) {
    return new Text(theme.fg("error", `Error: ${result.details.error}`), 0, 0);
  }

  let text = theme.fg("success", "✓ Done");
  if (expanded && result.details?.items) {
    for (const item of result.details.items) {
      text += "\n  " + theme.fg("dim", item);
    }
  }
  return new Text(text, 0, 0);
}

Bir yuvanın kasıtlı olarak görünür içeriği yoksa, boş bir Container gibi boş bir Component döndürün.

Tuş Bağlama İpuçları

Etkin tuş bağlama yapılandırmasına uygun tuş bağlama ipuçlarını görüntülemek için keyHint() tuşunu kullanın:

import { keyHint } from "@earendil-works/pi-coding-agent";

renderResult(result, { expanded }, theme, context) {
  let text = theme.fg("success", "✓ Done");
  if (!expanded) {
    text += ` (${keyHint("app.tools.expand", "to expand")})`;
  }
  return new Text(text, 0, 0);
}

Mevcut işlevler:

  • keyHint(keybinding, description) - "app.tools.expand" veya "tui.select.confirm" gibi yapılandırılmış bir tuş bağlama kimliğini biçimlendirir
  • keyText(keybinding) - Bir tuş bağlama kimliği için ham yapılandırılmış anahtar metnini döndürür
  • rawKeyHint(key, description) - Ham anahtar dizesini biçimlendirin

Ad alanlı tuş bağlama kimliklerini kullanın:

  • Kodlama aracısı kimlikleri app.* ad alanını kullanır; örneğin app.tools.expand, app.editor.external, app.session.rename
  • Paylaşılan TUI kimlikleri tui.* ad alanını kullanır; örneğin tui.select.confirm, tui.select.cancel, tui.input.tab

Tuş bağlama kimlikleri ve varsayılanlarının kapsamlı listesi için bkz. keybindings.md. keybindings.json aynı ad alanı kimliklerini kullanır.

Özel düzenleyiciler ve ctx.ui.custom() bileşenleri, eklenen argüman olarak keybindings: KeybindingsManager'yi alır. getKeybindings() veya setKeybindings()'ı çağırmak yerine, enjekte edilen yöneticiyi doğrudan kullanmaları gerekir.

En İyi Uygulamalar

  • (0, 0) dolgusu ile Text kullanın. Varsayılan Kutu dolguyu işler.
  • Çok satırlı içerik için \n kullanın.
  • Akış ilerlemesi için isPartial tutamacını kullanın.
  • Talep üzerine ayrıntılar için expanded desteği.
  • Varsayılan görünümü kompakt tutun.
  • Bağımsız değişkenleri context.state'ye kopyalamak yerine renderResult'deki context.args'yi okuyun.
  • context.state işaretini yalnızca çağrı ve sonuç alanları arasında paylaşılması gereken veriler için kullanın.
  • Aynı bileşen örneği yerinde güncellenebildiğinde context.lastComponent'yi yeniden kullanın.
  • renderShell: "self" tuşunu yalnızca varsayılan kutulu kabuk engel teşkil ettiğinde kullanın. Kendi kendine kabuk modunda araç kendi çerçevelemesinden, dolgusundan ve arka planından sorumludur.

Geri çekilmek

Bir slot oluşturucu tanımlanmamışsa veya şunu atarsa:

  • renderCall: Araç adını gösterir
  • renderResult: content'den ham metni gösterir

Dinamik Takım Yükleme

Extensions yalnızca küçük bir başlangıç ​​ayarını aktif tutarken birçok aracı kaydedebilir. Bir araç daha sonra yürütme sırasında pi.setActiveTools() ile daha fazla araç ekleyebilir. Pi tamamen eklemeli değişiklikleri algılar, yeni mevcut takım adlarını o takım sonucuna kaydeder ve güncellenmiş aktif seti bir sonraki model talebinden önce uygular.

Bu her modelde işe yarar. Models yerel ertelenmiş yükleme desteği ile kararlı bilgi istemi önekini koruyun ve yeni tanımları araç sonuç konumuna yükleyin. Diğer modeller aşağıda açıklanan geri dönüşü kullanır.

Yaşam döngüsü:

  1. Her aracı pi.registerTool() ile kaydedin, böylece pi.getAllTools()'de görünecektir.
  2. search_tools gibi yükleyici araçlarını etkin tutun ve aranabilir araçları devre dışı bırakın.
  3. Yükleyicinin yürütülmesi sırasında pi.setActiveTools([...currentTools,...matchingTools])'ı arayın. Değişiklik ek nitelikte olmalıdır: aynı çağrıda halihazırda etkin olan araçları kaldırmayın.
  4. Pi yükleyicinin takım sonucuna hangi araçların eklendiğini kaydeder.
  5. Bir sonraki model yanıtından önce, Pi desteklendiğinde yerel ertelenmiş yüklemeyi veya aksi takdirde normal aktif araç listesini kullanarak eklenen tanımları ortaya çıkarır.

Sağlayıcıya özel araç referanslarını döndürmenize veya yükleyiciyi özel bir arama aracı olarak işaretlemenize gerek yoktur. Aktif takım değişimi sinyaldir. pi.setActiveTools()'ye aktarılan adların zaten kayıtlı olması gerekir; bilinmeyen isimler dikkate alınmaz.

Models yerel ertelenmiş yüklemeyle

  • Antropik
    • Models: Sonnet, Opus, Fable sürüm 4.5 veya daha yenisi (Haiku olmadan)
    • Yerel gösterim: Ertelenmiş tanımlarda defer_loading kullanılır; yükleme noktası tool_reference içeriğini kullanır.
  • Açık AI
    • Models: gpt-5.4 ve daha yeni aile
    • Yerel gösterim: Pi, yükleme noktasında tamamlanmış istemci tool_search_call ve tool_search_output öğelerini ekler.

Doğrulanmış bir özel model veya proxy için yerel işleme, anthropic-messages için compat.supportsToolReferences: true veya openai-responses ve openai-codex-responses için compat.supportsToolSearch: true ile etkinleştirilebilir. Uç nokta ve model ilgili yerel protokolü kabul etmediği sürece bunları devre dışı bırakın.

Geri çekilme davranışı

Diğer tüm modeller ve sağlayıcılar için dinamik aktivasyon hala çalışıyor: Pi bir sonraki istekte normal olarak mevcut aktif takım listesinin tamamını gönderir. Model, yeni etkinleştirilen araçları çağırabilir ancak bunların tanımlarını eklemek, sağlayıcının önbelleğe alınmış bilgi istemi önekini geçersiz kılabilir.

Pi aynı zamanda bu güvenli geri dönüşü, bir araç grubunu diğeriyle değiştirmek gibi, aktif küme tamamen eklemeli olmadığında da kullanır. Bu nedenle takım çıkarma işlemleri işe yarar, ancak ertelenmiş yüklemeyi kullanmazlar.

En iyi önbellek davranışı için, yükleyici aracını tüm oturum boyunca etkin tutun ve etkin kümeyi değiştirmek yerine araçlar ekleyin. Ayrıca bir aracı promptSnippet veya promptGuidelines ile etkinleştirmenin sistem istemini yeniden oluşturduğunu unutmayın; Bu sistem istemi değişikliği, sağlayıcı ertelenmiş şemaları desteklediğinde bile öneki geçersiz kılabilir. Geç yüklenen araçlar genellikle description araçlarına güvenmeli ve yalnızca etkin bilgi istemi meta verilerini çıkarmalıdır.

Arama aracı örneği

Aşağıdaki uzantı, aranabilir iki aracı kaydeder, bunları başlangıçtaki etkin kümeden kaldırır ve yükleyici olarak yalnızca search_tools'yi tutar. Örnek basit anahtar kelime eşlemeyi kullanıyor ancak arama uygulaması BM25'i, yerleştirmeleri, uzak kataloğu veya projeye özel yönlendirmeyi kullanabilir.

import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
import { Type } from "typebox";

const SEARCHABLE_TOOL_NAMES = new Set(["lookup_weather", "search_issues"]);

export default function (pi: ExtensionAPI) {
  pi.registerTool({
    name: "lookup_weather",
    label: "Lookup Weather",
    description: "Look up the current weather for a city",
    parameters: Type.Object({ city: Type.String() }),
    async execute(_toolCallId, params) {
      return {
        content: [{ type: "text", text: `Weather for ${params.city}: sunny` }],
        details: {},
      };
    },
  });

  pi.registerTool({
    name: "search_issues",
    label: "Search Issues",
    description: "Search project issues by keyword",
    parameters: Type.Object({ query: Type.String() }),
    async execute(_toolCallId, params) {
      return {
        content: [{ type: "text", text: `No open issues matching ${params.query}` }],
        details: {},
      };
    },
  });

  pi.registerTool({
    name: "search_tools",
    label: "Search Tools",
    description: "Search for and enable tools relevant to a task",
    promptSnippet: "Search for additional tools when the active tools cannot perform the task",
    promptGuidelines: [
      "Use search_tools when a task requires a capability that is not currently available.",
    ],
    parameters: Type.Object({
      query: Type.String({ description: "Capability or task to search for" }),
      limit: Type.Optional(Type.Integer({ minimum: 1, maximum: 10 })),
    }),
    async execute(_toolCallId, params) {
      const terms = params.query.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
      const matches = pi.getAllTools()
        .filter((tool) => SEARCHABLE_TOOL_NAMES.has(tool.name))
        .map((tool) => ({
          tool,
          score: terms.reduce(
            (score, term) =>
              score + (`${tool.name} ${tool.description}`.toLowerCase().includes(term) ? 1 : 0),
            0,
          ),
        }))
        .filter((match) => match.score > 0)
        .sort((a, b) => b.score - a.score)
        .slice(0, params.limit ?? 3)
        .map((match) => match.tool.name);

      if (matches.length === 0) {
        return {
          content: [{ type: "text", text: `No tools found for: ${params.query}` }],
          details: { matches: [] },
        };
      }

      const active = pi.getActiveTools();
      const added = matches.filter((name) => !active.includes(name));
      pi.setActiveTools([...new Set([...active, ...added])]);

      return {
        content: [{
          type: "text",
          text: added.length > 0
            ? `Loaded tools: ${added.join(", ")}`
            : `Matching tools already active: ${matches.join(", ")}`,
        }],
        details: { matches, added },
      };
    },
  });

  pi.on("session_start", () => {
    // Keep searchable tools registered but initially inactive. Preserve built-ins
    // and tools owned by other extensions, and keep the loader itself active.
    const initialTools = pi.getActiveTools().filter(
      (name) => !SEARCHABLE_TOOL_NAMES.has(name),
    );
    pi.setActiveTools([...new Set([...initialTools, "search_tools"])]);
  });
}

search_tools bir eşleşme eklediğinde model, hemen ardından gelen istek üzerine bu tanımı alır. Yerel özellikli bir modelde tanım, başlangıçtaki araç şeması öneki değiştirilmeden arama sonucundan sonra sabitlenir. Diğer modellerde aynı istek üzerine normal takım listesinde görünür.

Özel kullanıcı arayüzü

Extensions kullanıcılarla ctx.ui yöntemleri aracılığıyla etkileşim kurabilir ve mesajların/araçların nasıl oluşturulduğunu özelleştirebilir.

Özel bileşenler için, aşağıdakiler için kopyala-yapıştır kalıplarına sahip tui.md'e bakın:

  • Seçim diyalogları (SelectList)
  • İptal ile eşzamansız işlemler (BorderedLoader)
  • Ayarlar arasında geçiş yapar (AyarlarList)
  • Durum göstergeleri (setStatus)
  • Akış sırasında çalışma mesajı, görünürlük ve gösterge (setWorkingMessage, setWorkingVisible, setWorkingIndicator)
  • Düzenleyicinin üstünde/altında widget'lar (setWidget)
  • Yerleşik eğik çizgi/yol tamamlamanın üstüne yerleştirilmiş otomatik tamamlama sağlayıcıları (addAutocompleteProvider)
  • Özel altbilgiler (setFooter)

Diyaloglar

// Select from options
const choice = await ctx.ui.select("Pick one:", ["A", "B", "C"]);

// Confirm dialog
const ok = await ctx.ui.confirm("Delete?", "This cannot be undone");

// Text input
const name = await ctx.ui.input("Name:", "placeholder");

// Multi-line editor
const text = await ctx.ui.editor("Edit:", "prefilled text");

// Notification (non-blocking)
ctx.ui.notify("Done!", "info");  // "info" | "warning" | "error"

Geri Sayımlı Zamanlanmış Diyaloglar

İletişim kutuları, canlı geri sayım ekranıyla otomatik olarak kapatılan timeout seçeneğini destekler:

// Dialog shows "Title (5s)" → "Title (4s)" → ... → auto-dismisses at 0
const confirmed = await ctx.ui.confirm(
  "Timed Confirmation",
  "This dialog will auto-cancel in 5 seconds. Confirm?",
  { timeout: 5000 }
);

if (confirmed) {
  // User confirmed
} else {
  // User cancelled or timed out
}

Zaman aşımında döndürülen değerler:

  • select() undefined değerini döndürür
  • confirm() false değerini döndürür
  • input() undefined değerini döndürür

AbortSignal ile Manuel İşten Çıkarma

Daha fazla kontrol için (örneğin, zaman aşımını kullanıcı iptalinden ayırt etmek için) AbortSignal kullanın:

const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000);

const confirmed = await ctx.ui.confirm(
  "Timed Confirmation",
  "This dialog will auto-cancel in 5 seconds. Confirm?",
  { signal: controller.signal }
);

clearTimeout(timeoutId);

if (confirmed) {
  // User confirmed
} else if (controller.signal.aborted) {
  // Dialog timed out
} else {
  // User cancelled (pressed Escape or selected "No")
}

Tam örnekler için examples/extensions/timed-confirm.ts'e bakın.

Widget'lar, Durum ve Altbilgi

// Status in footer (persistent until cleared)
ctx.ui.setStatus("my-ext", "Processing...");
ctx.ui.setStatus("my-ext", undefined);  // Clear

// Working loader (shown during streaming)
ctx.ui.setWorkingMessage("Thinking deeply...");
ctx.ui.setWorkingMessage();  // Restore default
ctx.ui.setWorkingVisible(false);  // Hide the built-in working loader row entirely
ctx.ui.setWorkingVisible(true);   // Show the built-in working loader row

// Working indicator (shown during streaming)
ctx.ui.setWorkingIndicator({ frames: [ctx.ui.theme.fg("accent", "●")] });  // Static dot
ctx.ui.setWorkingIndicator({
  frames: [
    ctx.ui.theme.fg("dim", "·"),
    ctx.ui.theme.fg("muted", "•"),
    ctx.ui.theme.fg("accent", "●"),
    ctx.ui.theme.fg("muted", "•"),
  ],
  intervalMs: 120,
});
ctx.ui.setWorkingIndicator({ frames: [] });  // Hide indicator
ctx.ui.setWorkingIndicator();  // Restore default spinner

// Widget above editor (default)
ctx.ui.setWidget("my-widget", ["Line 1", "Line 2"]);
// Widget below editor
ctx.ui.setWidget("my-widget", ["Line 1", "Line 2"], { placement: "belowEditor" });
ctx.ui.setWidget("my-widget", (tui, theme) => new Text(theme.fg("accent", "Custom"), 0, 0));
ctx.ui.setWidget("my-widget", undefined);  // Clear

// Custom footer (replaces built-in footer entirely)
ctx.ui.setFooter((tui, theme) => ({
  render(width) { return [theme.fg("dim", "Custom footer")]; },
  invalidate() {},
}));
ctx.ui.setFooter(undefined);  // Restore built-in footer

// Terminal title
ctx.ui.setTitle("pi - my-project");

// Editor text
ctx.ui.setEditorText("Prefill text");
const current = ctx.ui.getEditorText();

// Paste into editor (triggers paste handling, including collapse for large content)
ctx.ui.pasteToEditor("pasted content");

// Stack custom autocomplete behavior on top of the built-in provider
ctx.ui.addAutocompleteProvider((current) => ({
  triggerCharacters: ["#"],
  async getSuggestions(lines, line, col, options) {
    const beforeCursor = (lines[line] ?? "").slice(0, col);
    const match = beforeCursor.match(/(?:^|[ \t])#([^\s#]*)$/);
    if (!match) {
      return current.getSuggestions(lines, line, col, options);
    }

    return {
      prefix: `#${match[1] ?? ""}`,
      items: [{ value: "#2983", label: "#2983", description: "Extension API for autocomplete" }],
    };
  },
  applyCompletion(lines, line, col, item, prefix) {
    return current.applyCompletion(lines, line, col, item, prefix);
  },
  shouldTriggerFileCompletion(lines, line, col) {
    return current.shouldTriggerFileCompletion?.(lines, line, col) ?? true;
  },
}));

// Tool output expansion
const wasExpanded = ctx.ui.getToolsExpanded();
ctx.ui.setToolsExpanded(true);
ctx.ui.setToolsExpanded(wasExpanded);

// Custom editor (vim mode, emacs mode, etc.)
ctx.ui.setEditorComponent((tui, theme, keybindings) => new VimEditor(tui, theme, keybindings));
const currentEditor = ctx.ui.getEditorComponent();
ctx.ui.setEditorComponent((tui, theme, keybindings) =>
  new WrappedEditor(tui, theme, keybindings, currentEditor?.(tui, theme, keybindings))
);
ctx.ui.setEditorComponent(undefined);  // Restore default editor

// Theme management (see themes.md for creating themes)
const themes = ctx.ui.getAllThemes();  // [{ name: "dark", path: "/..." | undefined }, ...]
const lightTheme = ctx.ui.getTheme("light");  // Load without switching
const result = ctx.ui.setTheme("light");  // Switch by name
if (!result.success) {
  ctx.ui.notify(`Failed: ${result.error}`, "error");
}
ctx.ui.setTheme(lightTheme!);  // Or switch by Theme object
ctx.ui.theme.fg("accent", "styled text");  // Access current theme

Özel çalışma göstergesi çerçeveleri kelimesi kelimesine işlenir. Renkleri istiyorsanız bunları çerçeve dizelerine kendiniz ekleyin, örneğin ctx.ui.theme.fg(...) ile.

Otomatik tamamlama Providers

Özel otomatik tamamlama mantığını yerleşik eğik çizgi komutu ve yol sağlayıcının üstüne yığmak için ctx.ui.addAutocompleteProvider() tuşunu kullanın. Özel otomatik tamamlama mantığını yerleşik eğik çizgi komutu ve yol sağlayıcının üstüne yığmak için ctx.ui.addAutocompleteProvider()tuşunu kullanın. gibi özel doğal tetikleyiciler içintriggerCharacters`'yi ayarlayın.

Tipik desen:

  • imleçten önceki metni inceleyin
  • Uzantıya özel söz diziminiz eşleştiğinde kendi önerilerinizi döndürün
  • aksi takdirde current.getSuggestions(...)'ye yetki verin
  • Özel ekleme davranışına ihtiyacınız olmadığı sürece applyCompletion(...) delegesini verin
pi.on("session_start", (_event, ctx) => {
  ctx.ui.addAutocompleteProvider((current) => ({
    triggerCharacters: ["#"],
    async getSuggestions(lines, cursorLine, cursorCol, options) {
      const line = lines[cursorLine] ?? "";
      const beforeCursor = line.slice(0, cursorCol);
      const match = beforeCursor.match(/(?:^|[ \t])#([^\s#]*)$/);
      if (!match) {
        return current.getSuggestions(lines, cursorLine, cursorCol, options);
      }

      return {
        prefix: `#${match[1] ?? ""}`,
        items: [
          { value: "#2983", label: "#2983", description: "Extension API for registering custom @ autocomplete providers" },
          { value: "#2753", label: "#2753", description: "Reload stale resource settings" },
        ],
      };
    },

    applyCompletion(lines, cursorLine, cursorCol, item, prefix) {
      return current.applyCompletion(lines, cursorLine, cursorCol, item, prefix);
    },

    shouldTriggerFileCompletion(lines, cursorLine, cursorCol) {
      return current.shouldTriggerFileCompletion?.(lines, cursorLine, cursorCol) ?? true;
    },
  }));
});

gh issue list ile en son açık GitHub sayılarını önceden yükleyen ve hızlı #... tamamlama için bunları yerel olarak filtreleyen eksiksiz bir örnek için github-issue-autocomplete.ts'ye bakın. GitHub CLI (gh) ve GitHub depo kontrolü gerektirir.

Özel Bileşenler

Karmaşık kullanıcı arayüzü için ctx.ui.custom() kullanın. Bu, done() çağrılana kadar düzenleyiciyi geçici olarak bileşeninizle değiştirir:

import { Text, Component } from "@earendil-works/pi-tui";

const result = await ctx.ui.custom<boolean>((tui, theme, keybindings, done) => {
  const text = new Text("Press Enter to confirm, Escape to cancel", 1, 1);

  text.onKey = (key) => {
    if (key === "return") done(true);
    if (key === "escape") done(false);
    return true;
  };

  return text;
});

if (result) {
  // User pressed Enter
}

Geri arama şunları alır:

  • tui - TUI örneği (ekran boyutları, odak yönetimi için)
  • theme - Stil için güncel tema
  • keybindings - Uygulama tuş bağlama yöneticisi (kısayolları kontrol etmek için)
  • done(value) - Bileşeni kapatmak ve değeri döndürmek için çağrı

API bileşeninin tamamı için tui.md'ye bakın.

Yer Paylaşımı Modu (Deneysel)

Ekranı temizlemeden bileşeni mevcut içeriğin üzerinde kayan bir model olarak oluşturmak için { overlay: true } iletin:

const result = await ctx.ui.custom<string | null>(
  (tui, theme, keybindings, done) => new MyOverlayComponent({ onClose: done }),
  { overlay: true }
);

Gelişmiş konumlandırma için (sabitlemeler, kenar boşlukları, yüzdeler, duyarlı görünürlük), overlayOptions'yi geçin. Odağı veya görünürlüğü programlı olarak kontrol etmek için onHandle tuşunu kullanın:

const result = await ctx.ui.custom<string | null>(
  (tui, theme, keybindings, done) => new MyOverlayComponent({ onClose: done }),
  {
    overlay: true,
    overlayOptions: { anchor: "top-right", width: "50%", margin: 2 },
    onHandle: (handle) => {
      handle.focus(); // focus this overlay and bring it to the visual front
      // handle.unfocus({ target: editorComponent }); // release input to a specific component
      // handle.setHidden(true/false); // toggle visibility
      // handle.hide(); // permanently remove
    }
  }
);

Odaklanmış görünür bir yer paylaşımı, geçici olarak yer paylaşımsız özel kullanıcı arayüzü kapatıldıktan sonra girişi geri alabilir. Kaplama görünür kalırken kasıtlı olarak başka bir bileşenin girişi tutmasını istiyorsanız handle.unfocus({ target })'ı arayın. { target: null } geçişi, başka bir bileşene odaklanmadan kaplamayı serbest bırakır.

OverlayOptions'nin tamamı için tui.md ve örnekler için OverlayHandle API ve overlay-qa-tests.ts'ye bakın.

Özel Düzenleyici

Ana giriş düzenleyicisini özel bir uygulamayla (vim modu, emacs modu vb.) değiştirin:

import { CustomEditor, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
import { matchesKey } from "@earendil-works/pi-tui";

class VimEditor extends CustomEditor {
  private mode: "normal" | "insert" = "insert";

  handleInput(data: string): void {
    if (matchesKey(data, "escape") && this.mode === "insert") {
      this.mode = "normal";
      return;
    }
    if (this.mode === "normal" && data === "i") {
      this.mode = "insert";
      return;
    }
    super.handleInput(data);  // App keybindings + text editing
  }
}

export default function (pi: ExtensionAPI) {
  pi.on("session_start", (_event, ctx) => {
    ctx.ui.setEditorComponent((tui, theme, keybindings) =>
      new VimEditor(tui, theme, keybindings)
    );
  });
}

Önemli noktalar:

  • Uygulama tuş atamalarını almak için CustomEditor (temel Editor değil) öğesini genişletin (iptal etmek için kaçış, ctrl+d, model değiştirme)
  • Kullanmadığınız anahtarlar için super.handleInput(data)'ı arayın
  • Fabrika uygulamadan tui, theme ve keybindings alır
  • Önceden yapılandırılmış özel düzenleyiciyi kaydırmak için setEditorComponent()'den önce ctx.ui.getEditorComponent() kullanın
  • Varsayılanı geri yüklemek için undefined iletin: ctx.ui.setEditorComponent(undefined)

Düzenleyicinin yerini almış olan başka bir uzantıyla kompozisyon oluşturmak için, kendi fabrikanızı ayarlamadan önce önceki fabrikayı yakalayın:

const previous = ctx.ui.getEditorComponent();
ctx.ui.setEditorComponent((tui, theme, keybindings) =>
  new MyEditor(tui, theme, keybindings, { base: previous?.(tui, theme, keybindings) })
);

Mod göstergeli tam bir örnek için tui.md Desen 7'ye bakın.

Mesaj ve Giriş Oluşturma

customType'ınızla mesajlar için özel bir oluşturucu kaydedin. Yüksek Lisans bağlamına katılması gereken içerik için mesaj oluşturucuları kullanın:

import { Text } from "@earendil-works/pi-tui";

pi.registerMessageRenderer("my-extension", (message, options, theme) => {
  const { expanded, outputPad } = options;
  let text = theme.fg("accent", `[${message.customType}] `);
  text += message.content;

  if (expanded && message.details) {
    text += "\n" + theme.fg("dim", JSON.stringify(message.details, null, 2));
  }

  return new Text(text, outputPad, 0);
});

Mesajlar pi.sendMessage() aracılığıyla gönderilir:

pi.sendMessage({
  customType: "my-extension",  // Matches registerMessageRenderer
  content: "Status update",
  display: true,               // Show in TUI
  details: { ... },            // Available in renderer
});

LLM'ye gönderilmemesi gereken yalnızca TUI içeriği için bunun yerine özel girişler oluşturun:

pi.registerEntryRenderer("my-card", (entry, options, theme) => {
  return new Text(theme.fg("accent", JSON.stringify(entry.data)));
});

pi.appendEntry("my-card", { status: "done" });

Tema Renkleri

Tüm oluşturma işlevleri bir theme nesnesi alır. Özel temalar ve tam renk paleti oluşturmak için themes.md'e bakın.

// Foreground colors
theme.fg("toolTitle", text)   // Tool names
theme.fg("accent", text)      // Highlights
theme.fg("success", text)     // Success (green)
theme.fg("error", text)       // Errors (red)
theme.fg("warning", text)     // Warnings (yellow)
theme.fg("muted", text)       // Secondary text
theme.fg("dim", text)         // Tertiary text

// Text styles
theme.bold(text)
theme.italic(text)
theme.strikethrough(text)

Özel araç oluşturucularda sözdizimi vurgulaması için:

import { highlightCode, getLanguageFromPath } from "@earendil-works/pi-coding-agent";

// Highlight code with explicit language
const highlighted = highlightCode("const x = 1;", "typescript", theme);

// Auto-detect language from file path
const lang = getLanguageFromPath("/path/to/file.rs");  // "rust"
const highlighted = highlightCode(code, lang, theme);

Hata İşleme

  • Uzantı hataları günlüğe kaydedilir, temsilci devam eder
  • tool_call hatalar aracı engeller (arızaya karşı korumalı)
  • Araç execute hataları atılarak bildirilmelidir; atılan hata yakalanır, LLM'ye isError: true ile bildirilir ve yürütme devam eder

Mod Davranışı

Mod ctx.mode ctx.hasUI Notlar
İnteraktif "tui" true Terminal oluşturma ile tam TUI
RPC (--mode rpc) "rpc" true JSON protokolü aracılığıyla diyaloglar ve bildirimler; custom(), undefined değerini döndürür. Bkz. rpc.md
JSON (--mode json) "json" false stdout'e olay akışı; Kullanıcı arayüzü yöntemleri işlem gerektirmez
Yazdır (-p) "print" false Extensions çalıştır ama istemde bulunamıyorum

TUI'ye özgü özelliklerden (custom(), bileşen fabrikaları, terminal girişi) önce ctx.mode === "tui" kullanın. Hem TUI hem de RPC modlarında çalışan diyalog ve bildirim yöntemlerinden önce ctx.hasUI'yi kullanın.

Örnekler Referans

Tüm örnekler examples/extensions/'dedir.

Örnek Tanım Tuş APIs
Aletler
hello.ts Minimum takım kaydı registerTool
question.ts Kullanıcı etkileşimli araç registerTool, ui.select
questionnaire.ts Çok adımlı sihirbaz aracı registerTool, ui.custom
todo.ts Kalıcılığa sahip durum bilgisi olan araç registerTool, appendEntry, renderResult, oturum etkinlikleri
dynamic-tools.ts Araçları başlatma sonrasında ve komutlar sırasında kaydedin registerTool, session_start, registerCommand
structured-output.ts terminate: true ile son yapılandırılmış çıktı aracı registerTool, araç sonuçlarının sonlandırılması
truncated-tool.ts Çıkış kesme örneği registerTool, truncateHead
tool-override.ts Yerleşik okuma aracını geçersiz kıl registerTool (yerleşik ile aynı ad)
Komutlar
pirate.ts Her turda sistem istemini değiştirin registerCommand, before_agent_start
summarize.ts Konuşma özeti komutu registerCommand, ui.custom
handoff.ts Sağlayıcılar arası model aktarımı registerCommand, ui.editor, ui.custom
qna.ts Özel kullanıcı arayüzü ile Soru-Cevap registerCommand, ui.custom, setEditorText
send-user-message.ts Kullanıcı mesajlarını enjekte etme registerCommand, sendUserMessage
reload-runtime.ts Yeniden yükleme komutu ve LLM aracı aktarımı registerCommand, ctx.reload(), sendUserMessage
shutdown-command.ts Zarif kapatma komutu registerCommand, shutdown()
Etkinlikler ve Kapılar
permission-gate.ts Tehlikeli komutları engelle on("tool_call"), ui.confirm
project-trust.ts Bir kullanıcı/global veya CLI uzantısından proje güvenine karar verin veya bu güveni erteleyin on("project_trust"), güven kullanıcı arayüzü, gerekli güven sonucu
protected-paths.ts Belirli yollara yazmayı engelle on("tool_call")
confirm-destructive.ts Oturum değişikliklerini onaylayın on("session_before_switch"), on("session_before_fork")
dirty-repo-guard.ts Kirli git deposu hakkında uyar on("session_before_*"), exec
input-transform.ts Kullanıcı girişini dönüştürün on("input")
input-transform-streaming.ts Akış uyumlu giriş dönüşümü on("input"), streamingBehavior
model-status.ts React model değişikliklerine on("model_select"), setStatus
provider-payload.ts Yükleri ve sağlayıcı yanıt başlıklarını inceleyin on("before_provider_request"), on("after_provider_response")
system-prompt-header.ts Sistem istemi bilgilerini görüntüle on("agent_start"), getSystemPrompt
claude-rules.ts Dosyalardan kuralları yükle on("session_start"), on("before_agent_start")
prompt-customizer.ts systemPromptOptions kullanarak bağlama duyarlı araç rehberliği ekleyin on("before_agent_start"), BuildSystemPromptOptions
file-trigger.ts Dosya izleyici mesajları tetikler sendMessage
Sıkıştırma ve Oturumlar
custom-compaction.ts Özel sıkıştırma özeti on("session_before_compact")
trigger-compact.ts Sıkıştırmayı manuel olarak tetikleyin compact()
git-checkpoint.ts Git dönüşlerde saklanma on("turn_start"), on("session_before_fork"), exec
git-merge-and-resolve.ts Çakışmaları getirme, birleştirme ve çözme on("agent_end"), exec, sendUserMessage
auto-commit-on-exit.ts Kapatmayı taahhüt et on("session_shutdown"), exec
Kullanıcı Arayüzü Bileşenleri
status-line.ts Altbilgi durum göstergesi setStatus, oturum etkinlikleri
working-indicator.ts Akış çalışma göstergesini özelleştirin setWorkingIndicator, registerCommand
github-issue-autocomplete.ts gh issue list'den en son açık sayıları önceden yükleyerek yerleşik otomatik tamamlamanın üstüne #1234 sayı tamamlamaları ekleyin addAutocompleteProvider, on("session_start"), exec
custom-footer.ts Alt bilgiyi tamamen değiştir registerCommand, setFooter
custom-header.ts Başlangıç ​​başlığını değiştir on("session_start"), setHeader
modal-editor.ts Vim tarzı modal düzenleyici setEditorComponent, CustomEditor
rainbow-editor.ts Özel düzenleyici stili setEditorComponent
widget-placement.ts Widget düzenleyicinin üstünde/altında setWidget
overlay-test.ts Kaplama bileşenleri ui.custom kaplama seçenekleriyle
overlay-qa-tests.ts Kapsamlı kaplama testleri ui.custom, tüm kaplama seçenekleri
notify.ts Basit bildirimler ui.notify
timed-confirm.ts Zaman aşımı olan diyaloglar ui.confirm zaman aşımı/sinyalli
mac-system-theme.ts Temayı otomatik değiştir setTheme, exec
Karmaşık Extensions
plan-mode/ Tam plan modu uygulaması Tüm etkinlik türleri, registerCommand, registerShortcut, registerFlag, setStatus, setWidget, sendMessage, setActiveTools
preset.ts Kaydedilebilir ön ayarlar (model, araçlar, düşünme) registerCommand, registerShortcut, registerFlag, setModel, setActiveTools, setThinkingLevel, appendEntry
tools.ts Araçların kullanıcı arayüzünü açma/kapatma registerCommand, setActiveTools, SettingsList, oturum etkinlikleri
Uzaktan Kumanda ve Korumalı Alan
ssh.ts SSH uzaktan yürütme registerFlag, on("user_bash"), on("before_agent_start"), araç işlemleri
interactive-shell.ts Kalıcı kabuk oturumu on("user_bash")
sandbox/ Korumalı alanda araç yürütme Takım işlemleri
gondolin/ Yerleşik araçları ve ! komutlarını Gondolin mikro sanal makineye yönlendirin Araç işlemleri, yerleşik araç geçersiz kılmaları, on("user_bash")
subagent/ Alt ajanları doğur registerTool, exec
Oyunlar
snake.ts Yılan oyunu registerCommand, ui.custom, klavye kullanımı
space-invaders.ts Uzay İstilacıları oyunu registerCommand, ui.custom
doom-overlay/ Yer paylaşımında kıyamet ui.custom kaplamalı
Providers
custom-provider-anthropic/ Özel Antropik proxy registerProvider
custom-provider-gitlab-duo/ GitLab Duo entegrasyonu registerProvider ile OAuth
Mesajlar ve İletişim
message-renderer.ts Özel mesaj oluşturma registerMessageRenderer, sendMessage
entry-renderer.ts TUI-yalnızca özel giriş oluşturma registerEntryRenderer, appendEntry
event-bus.ts Uzantılar arası olaylar pi.events
Oturum Meta Verileri
session-name.ts Seçici için oturumları adlandırın setSessionName, getSessionName
bookmark.ts /tree için yer imi girişleri setLabel
Çeşitli
inline-bash.ts Araç çağrılarında satır içi bash on("tool_call")
bash-spawn-hook.ts Yürütmeden önce bash komutunu, cwd'yi ve env'yi ayarlayın createBashTool, spawnHook
with-deps/ npm bağımlılıklara sahip uzantı package.json ile paket yapısı