SDK reference
Every backing the platform exposes is a member of EggletContext, the object passed into the Egglet’s activate() hook. This page is the field-by-field reference. Each section names the surface, the manifest declaration it requires, the signature, and a short example.
Egglet code imports type definitions from @egg/sdk. Concrete implementations live in the host package and are not imported by Egglets directly. By convention authors bind the context to a parameter named ctx; the examples below follow that convention.
import { defineEgglet, type EggletContext } from "@egg/sdk";
export default defineEgglet({
activate(ctx: EggletContext) {
// ctx.log, ctx.ai, ctx.storage, ...
},
});
EggletContext.log
Tagged logger scoped to the Egglet’s id. Calls forward to the host console with a [egglet:<id>] prefix. No manifest declaration required. Use this instead of bare console.log so output is attributable.
log.debug(msg, ...args) / log.info(...) / log.warn(...) / log.error(...)
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
msg | unknown | yes | — | Primary message. Stringified for the console. |
...args | unknown[] | no | [] | Additional values appended to the log line. |
Returns: void.
Errors: none.
EggletContext.routes
Mount the Egglet’s top-level view at the path declared in manifest.route. The Egglet does not pick the path; the manifest does. The host calls the loader on first activation of the route and keeps the component mounted thereafter (the view participates in the keep-alive panel system; switching tabs does not unmount it). Two Egglets cannot mount the same route.
ctx.routes.mount(() => import("./MyEggletPage"));
routes.mount(loader)
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
loader | () => Promise<{ default?: ComponentType; Component?: ComponentType }> | yes | — | Dynamic-import callback returning a module that exports default or Component. At least one must be present. |
Returns: void.
Errors: throws if another Egglet has already mounted this route, or if the loader resolves to a module with neither a default nor a Component export.
EggletContext.tools
Register named tools the agent and other Egglets can call. Tool names are namespaced by convention: <egglet_id>.<verb>. Every name passed to register must appear in manifest.tools. Cross-Egglet calls require both external: true on the owning manifest and a matching entry in the caller’s permissions array.
ctx.tools.register<{ id: string }, { count: number }>(
"myEgglet.countItems",
async ({ id }) => {
const row = await ctx.storage.one<{ n: number }>(
"SELECT COUNT(*) AS n FROM {{items}} WHERE owner = ?",
[id],
);
return { count: row?.n ?? 0 };
},
);
tools.register<P, R>(name, handler)
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
name | string | yes | — | Fully-qualified tool name (e.g. "feed.summarize"). Must appear in manifest.tools. |
handler | (params: P) => Promise<R> \| R | yes | — | Sync or async function that produces the tool result. |
Returns: void.
Errors: throws if name is not declared in the manifest, or if another Egglet has already registered the same name.
tools.call<P, R>(name, params?)
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
name | string | yes | — | Fully-qualified tool name to invoke. |
params | P | no | undefined | Forwarded as the handler’s first argument. |
Returns: Promise<R> — whatever the handler returns.
Errors: throws if the tool is unknown. Cross-Egglet calls throw if the target tool is not external: true or the caller’s manifest does not list the tool in permissions.
tools.has(name)
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
name | string | yes | — | Fully-qualified tool name to test. |
Returns: boolean — true if a handler is registered. Permission checks are not applied here.
Errors: none.
EggletContext.storage
Read and write the Egglet’s tables. SQL strings reference tables by their logical name wrapped in double curly braces: {{items}}. The host substitutes the placeholder with the physical name from the manifest before sending the query. SQL referencing tables not declared by the manifest is rejected. Reads run against the local connection directly; writes route through the Gateway daemon so its update hooks fire and any sync push runs. From the Egglet’s perspective the calls look identical; the platform handles routing.
const rows = await ctx.storage.all<{ id: string; title: string }>(
"SELECT id, title FROM {{items}} WHERE is_read = 0 ORDER BY ts DESC LIMIT ?",
[50],
);
await ctx.storage.exec(
"UPDATE {{items}} SET is_read = 1 WHERE id IN (?, ?, ?)",
["a", "b", "c"],
);
storage.all<T>(sql, params?)
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
sql | string | yes | — | SELECT statement. Table names wrapped in {{ }}. |
params | unknown[] | no | [] | Positional bind values for ? placeholders. |
Returns: Promise<T[]> — all rows.
Errors: throws if the SQL references a table not in the manifest, or if SQLite reports an error.
storage.one<T>(sql, params?)
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
sql | string | yes | — | SELECT expected to return zero or one row. |
params | unknown[] | no | [] | Positional bind values. |
Returns: Promise<T | null> — the first row, or null when the result set is empty. If multiple rows match, only the first is returned.
Errors: same as all.
storage.exec(sql, params?)
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
sql | string | yes | — | INSERT / UPDATE / DELETE / CREATE statement. |
params | unknown[] | no | [] | Positional bind values. |
Returns: Promise<{ changes: number; lastInsertRowid: number }>.
Errors: throws if uses.gateway.writes is not declared in the manifest, if the SQL references undeclared tables, or if SQLite reports an error.
EggletContext.alwaysOn
Recurring work the platform drives. The Egglet supplies the handler; the platform supplies the cadence and survives a closed window when the work is daemon-driven. Requires uses.gateway.schedules: true.
const handle = ctx.alwaysOn.poller({
name: "refresh",
interval: 300,
handler: async () => { await refreshFeeds(); },
});
// later, to stop it manually:
handle.cancel();
alwaysOn.poller(config)
config: AlwaysOnPollerConfig
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
name | string | yes | — | Stable name scoped to the Egglet. Used as the schedule key for catch-up after a window close. |
interval | number (seconds) | yes | — | Time between handler calls. Minimum 1. |
handler | () => Promise<void> \| void | yes | — | Invoked every interval. Errors are logged and do not stop the poller. |
fireOnActivate | "always" \| "if-due" \| "never" | no | "if-due" | Whether the handler fires immediately on register. "if-due" fires only if the recorded last-fire is older than the interval. |
Returns: AlwaysOnHandle — { cancel(): void }.
Errors: throws if uses.gateway.schedules is not declared, or if a poller with the same name is already registered for this Egglet.
EggletContext.fetch
In-process outbound HTTP, distinct from the Gateway-driven headless path. Routes through the platform’s shared HTTP client. Requires uses.network.fetch: true.
const r = await ctx.fetch("https://api.example.com/v1/items", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ q: "today" }),
timeoutMs: 10_000,
});
if (r.ok) { const items = JSON.parse(r.body); }
fetch(url, init?)
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
url | string | yes | — | Absolute URL. Loopback / file URLs rejected. |
init.method | string | no | "GET" | HTTP method. |
init.headers | Record<string, string> | no | {} | Request headers. |
init.body | string | no | — | Request body. Stringify JSON yourself. |
init.timeoutMs | number | no | platform default | Per-request timeout. |
Returns: Promise<SafeFetchResponse> — { ok: boolean; status: number; statusText: string; headers: Record<string, string>; body: string }. Body is always a string; parse JSON yourself.
Errors: throws if uses.network.fetch is not declared, on network errors, or on timeout. Non-2xx responses are NOT thrown — check ok.
EggletContext.headless
Hidden-browser page fetch. Useful for SPAs, login-walled feeds, anything where ctx.fetch does not produce JS-rendered content. The Gateway spawns a fresh hidden browser per call. Requires uses.gateway.headless: true.
const result = await ctx.headless.fetch("https://example.com/feed", {
waitMs: 3_000,
evalJs: "document.title",
credentialSource: "example",
});
console.log(result.title, result.evalResult, result.html.length);
headless.fetch(url, opts?)
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
url | string | yes | — | Absolute URL. |
opts.waitMs | number | no | 0 | Time to wait after the page’s load event before reading state. Use for SPA hydration. |
opts.evalJs | string | no | — | JavaScript expression evaluated in the page context after waitMs elapses. The result lands in evalResult. |
opts.credentialSource | string | no | — | Credential name from manifest.credentials; cookies / auth headers are injected before navigation. |
Returns: Promise<HeadlessResult> — { title: string; html: string; evalResult?: unknown; url: string; status: number }.
Errors: throws if uses.gateway.headless is not declared, if credentialSource is unknown, or if the hidden browser cannot reach the URL within the timeout.
EggletContext.ai
All model-backed operations live here, grouped by modality: text (chat, embeddings, reranking), image, audio, and video. Every method routes through the Gateway daemon end-to-end: capability resolution, tier gating (per-day quotas based on the user’s plan), credential lookup, and the outbound HTTP call to whichever provider serves the slot all happen there. The browser shell never sees an API key.
A blocked tier returns a structured error the Egglet can surface to the user (e.g., “daily limit reached” or “not available on your plan”). Use EggletContext.tier to check availability before calling.
EggletContext.ai.text
Operations whose input or output is plain text against the user’s configured text-model slot. chat.complete and chat.stream require uses.llm in the manifest; the slot is picked by uses.llm.role (default agent-secondary) and uses.llm.capability (default text). chat.stream emits text deltas, tool-call deltas, usage events, and a final done event; the returned promise resolves with the same shape as complete once the stream closes.
const r = await ctx.ai.text.chat.complete({
system: "You are a concise summarizer.",
messages: [{ role: "user", content: "Summarize this in one line: ..." }],
maxTokens: 64,
});
const { embedding, dims, provider } = await ctx.ai.text.embed({ text: "hello world" });
const { scored } = await ctx.ai.text.rerank({
query: "cats",
docs: ["a cute kitten", "the weather is sunny", "a feline pet"],
topN: 2,
});
ai.text.chat.complete(req)
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
req.messages | { role: "user" \| "assistant"; content: string }[] | yes | — | Conversation, in order. Platform supplies provider-specific framing. |
req.system | string | no | — | System prompt. |
req.maxTokens | number | no | provider default | Cap on output tokens. |
Returns: Promise<ChatResponse> — { text, inputTokens, outputTokens, provider, model }.
Errors: throws if uses.llm is not declared, on tier block (402), tier limit (429), missing provider key (412), or upstream provider error (502).
ai.text.chat.stream(req, onEvent)
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
req | ChatRequest | yes | — | Same shape as complete. |
onEvent | (e: ChatStreamEvent) => void | yes | — | Called for every chunk. Event variants: text, toolCallStart, toolCallDelta, usage, done, error. |
Returns: Promise<ChatResponse> — resolves once the stream closes, with accumulated text and totals.
Errors: same gates as complete; an error event during streaming both fires through onEvent and rejects the promise.
ai.text.embed(req)
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
req.text | string | yes | — | Text to embed. |
req.provider | "google" \| "openai" | no | priority order | Provider hint. Default tries Google first, then OpenAI, based on which key is configured. |
Returns: Promise<{ embedding: number[]; provider: string; dims: number }>. Google returns 768d, OpenAI returns 1536d — always inspect dims, do not hard-code.
Errors: 412 PRECONDITION_FAILED when no embeddings key is configured; 502 on provider error.
ai.text.rerank(req)
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
req.query | string | yes | — | Query string. |
req.docs | string[] | yes | — | Candidate documents. Empty array is allowed and returns empty scored. |
req.topN | number | no | all | Return only the top N entries. |
req.variant | string | no | platform default | Reranker model variant id. |
Returns: Promise<{ scored: { index: number; score: number }[] }>. index refers to the original docs array. Higher score = more relevant.
Errors: 424 FAILED_DEPENDENCY when the reranker model is not installed (install via POST /x/ai/assets/rerank).
EggletContext.ai.image
generate calls the configured image model (Imagen by default) and writes the PNG to a daemon-known on-disk path. Pair with EggletContext.files to render or serve it. analyze sends the image to a vision-capable text model (Gemini) with a prompt; pass imagePath for files on disk or imageBase64 for in-memory bytes.
const img = await ctx.ai.image.generate({
prompt: "a green leaf on a white background, studio lighting",
aspectRatio: "1:1",
});
const url = ctx.files.urlFor(img.path);
const analysis = await ctx.ai.image.analyze({
imagePath: img.path,
prompt: "Describe the lighting and composition.",
});
ai.image.generate(req)
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
req.prompt | string | yes | — | Text prompt. Provider safety filters may reject; surfaces as a 502. |
req.aspectRatio | string | no | "1:1" | "16:9", "9:16", "1:1", "4:3", etc. Provider-dependent. |
Returns: Promise<{ path: string; sizeBytes: number }> — PNG saved into the daemon’s generated-images store. Use ctx.files.urlFor(path) to render.
Errors: 402 PAYMENT_REQUIRED tier block; 429 tier limit; 502 provider error (including safety-filter rejection); 500 on disk write failure.
ai.image.analyze(req)
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
req.imagePath | string | one of imagePath / imageBase64 | — | Daemon-readable on-disk path. Preferred when the image is on disk. |
req.imageBase64 | string | one of imagePath / imageBase64 | — | Base64-encoded bytes, no data: prefix. |
req.mimeType | string | no | "image/png" | Only consulted when imageBase64 is set. |
req.prompt | string | yes | — | Question or instruction. |
req.system | string | no | — | System prompt. |
Returns: Promise<{ text: string; model: string; provider: string }>.
Errors: 400 if neither imagePath nor imageBase64 is provided, or the path cannot be read; 402 / 429 tier gates; 502 provider error.
EggletContext.ai.audio
tts synthesizes speech. backend defaults to auto: prefer local Piper when installed, otherwise cloud. listVoices returns the catalog so you can render a picker. Two paths exist for speech-to-text: transcribe takes a file path or HTTPS URL and runs local Whisper (best for long-form on-disk audio); recognize takes base64-encoded inline bytes and routes to Google Speech-to-Text (best for short-form audio captured in the renderer). vad runs the local Silero voice-activity detector and returns speech intervals — useful for chunking long recordings before transcription.
Caveat: transcribeStatus currently reports best-effort placeholders; a real /api/ai/transcribe-ready endpoint will replace the stub.
const voices = await ctx.ai.audio.listVoices();
const audio = await ctx.ai.audio.tts({ text: "Hello there.", voice: voices[0].key });
const { transcript } = await ctx.ai.audio.transcribe("/path/to/recording.m4a");
const { speech } = await ctx.ai.audio.vad({ inputPath: "/path/to/recording.m4a" });
ai.audio.tts(req)
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
req.text | string | yes | — | Text to synthesize. Long inputs chunked internally. |
req.voice | string | no | platform default | Voice key from listVoices(). |
req.backend | "auto" \| "local" \| "cloud" | no | "auto" | Force local Piper or cloud. auto prefers local when installed. |
Returns: Promise<{ path: string; sizeBytes: number }> — on-disk audio file (typically MP3 or WAV).
Errors: 424 FAILED_DEPENDENCY if backend: "local" and Piper has no installed voice; 402/429 tier gates on cloud; 502 provider error.
ai.audio.transcribe(urlOrPath, modelPref?)
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
urlOrPath | string | yes | — | Local file path or http(s):// URL. URLs are downloaded into a temp file before transcription. |
modelPref | string | no | largest installed | Whisper model name to prefer (e.g. "medium.en"). |
Returns: Promise<{ transcript: string }>.
Errors: 412 PRECONDITION_FAILED if FFmpeg, Whisper, or any Whisper model is not installed; 502 BAD_GATEWAY on URL download failure; 500 on subprocess failure.
ai.audio.transcribeStatus()
No parameters.
Returns: Promise<{ ready: boolean; ffmpeg: boolean; whisper: boolean; model: boolean }>. See caveat above; values are placeholders until /api/ai/transcribe-ready ships.
Errors: none.
ai.audio.recognize(req)
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
req.audioBase64 | string | yes | — | Audio bytes, base64-encoded. |
req.encoding | string | no | provider default | "LINEAR16", "FLAC", "OGG_OPUS", etc. |
req.sampleRateHertz | number | no | provider default | e.g. 16000. |
req.languageCode | string | no | "en-US" | BCP-47 code. |
req.model | string | no | provider default | Provider-specific model id (e.g. Google "latest_short"). |
Returns: Promise<{ transcript: string }>.
Errors: 412 PRECONDITION_FAILED when no Speech-to-Text key is configured; 402/429 tier gates; 502 provider error.
ai.audio.vad(req)
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
req.inputPath | string | yes | — | Daemon-readable audio file path. |
req.threshold | number | no | 0.5 | Voice-probability threshold (0.0–1.0). |
req.minSpeechMs | number | no | 250 | Drop speech spans shorter than this. |
req.minSilenceMs | number | no | 100 | Treat silence shorter than this as part of the surrounding speech span. |
Returns: Promise<{ speech: { startMs: number; endMs: number }[]; durationMs: number }>.
Errors: 400 on invalid path; 424 FAILED_DEPENDENCY if the Silero VAD model is not installed (install via POST /x/ai/assets/vad).
ai.audio.listVoices()
No parameters.
Returns: Promise<Voice[]> — { key, displayName, backend: "local" | "cloud", installed }, local voices first.
Errors: none under normal operation.
EggletContext.ai.video
generate calls the configured video model (Grok-Imagine by default). It needs a public HTTPS seed image URL and accepts 5–15 second durations. The daemon polls the provider until completion (up to ~3 minutes) and returns the provider-hosted video URL. analyze sends the video to a multimodal text model with a prompt; pass videoPath for files on disk or videoBase64 for in-memory bytes. The model must support video input (Gemini today).
const video = await ctx.ai.video.generate({
prompt: "the leaf gently rustles in the breeze",
imageUrl: "https://...leaf.png",
durationSeconds: 5,
});
const description = await ctx.ai.video.analyze({
videoPath: "/path/to/clip.mp4",
prompt: "Summarize the action in one sentence.",
});
ai.video.generate(req)
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
req.prompt | string | yes | — | Action / scene description. |
req.imageUrl | string | yes | — | Public HTTPS seed image URL the provider can fetch. |
req.durationSeconds | number | no | 5 | 5–15 seconds (xAI limit). |
req.aspectRatio | string | no | provider default | e.g. "16:9". |
Returns: Promise<{ videoUrl: string; requestId: string }> — provider-hosted URL. Daemon polls up to ~3 minutes.
Errors: 412 PRECONDITION_FAILED when no xAI key is configured; 402/429 tier gates; 504 if generation does not finish within the timeout; 502 provider error.
ai.video.analyze(req)
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
req.videoPath | string | one of videoPath / videoBase64 | — | Daemon-readable on-disk path. Preferred when the video is on disk. |
req.videoBase64 | string | one of videoPath / videoBase64 | — | Base64-encoded bytes, no data: prefix. |
req.mimeType | string | no | "video/mp4" | Only consulted when videoBase64 is set. |
req.prompt | string | yes | — | Question or instruction. |
req.system | string | no | — | System prompt. |
Returns: Promise<{ text: string; model: string; provider: string }>.
Errors: 400 if neither videoPath nor videoBase64 is provided, or the path cannot be read; 402/429 tier gates; 502 provider error (the routed model must support video).
EggletContext.files
List, delete, and resolve URLs for files the daemon has written into its generated-media store (TTS output, generated images, captured video, etc.). The Egglet receives a path back from ctx.ai.*; pass that path to urlFor to get a URL it can embed. No manifest declaration required.
const images = await ctx.files.listGenerated("image");
for (const f of images) {
console.log(f.name, f.sizeBytes, ctx.files.urlFor(f.path));
}
files.listGenerated(kind)
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
kind | "audio" \| "image" \| "video" \| "document" | yes | — | Filter by media kind. |
Returns: Promise<GeneratedFile[]> — { id, path, name, fileType, mimeType, sizeBytes, createdAt }, newest first.
Errors: none under normal operation.
files.deleteGenerated(path)
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
path | string | yes | — | Path returned by an earlier listGenerated or ctx.ai.* call. |
Returns: Promise<void>.
Errors: 404 if the file is unknown to the daemon (no error if it’s already gone from disk).
files.urlFor(path)
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
path | string | yes | — | Daemon-known on-disk path. |
Returns: string — daemon HTTP URL that streams the file. Safe to drop into <img>, <audio>, <video>, or to pass to a download flow. Synchronous.
Errors: none.
EggletContext.artifacts
Long-lived domain artifacts: code, documents, diagrams. Distinct from files, which surfaces ephemeral generated media. Artifacts are durable, addressable by id, and round-trip through sync.
artifacts.list()
No parameters.
Returns: Promise<Artifact[]> — all artifacts owned by the user, newest first.
Errors: none under normal operation.
artifacts.get(id)
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
id | string | yes | — | Artifact id. |
Returns: Promise<Artifact | null> — null when not found.
Errors: none.
artifacts.create(req)
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
req.kind | string | yes | — | Artifact kind (e.g. "code", "doc", "diagram"). |
req.title | string | yes | — | Display title. |
req.content | string | yes | — | Artifact body. |
req.metadata | Record<string, unknown> | no | {} | Free-form domain metadata. |
Returns: Promise<Artifact> — full record with assigned id and timestamps.
Errors: 400 on missing required fields.
artifacts.update(id, patch)
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
id | string | yes | — | Artifact id. |
patch | Partial<Artifact> | yes | — | Fields to overwrite. Omitted fields are preserved. |
Returns: Promise<Artifact> — updated record.
Errors: 404 if the id is unknown.
artifacts.remove(id)
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
id | string | yes | — | Artifact id. |
Returns: Promise<void>.
Errors: 404 if the id is unknown.
EggletContext.tier
The user’s plan tier and per-capability availability/quota. Use this to gate AI calls in the UI and avoid showing buttons the user’s plan can’t honor. Capability strings are the same daemon-side gate names used by ctx.ai.* (text_fast, vision, imagen, veo, tts, stt, etc.).
if (await ctx.tier.isAvailable("imagen")) {
// safe to call ctx.ai.image.generate(...)
}
tier.getInfo()
No parameters.
Returns: Promise<TierInfo> — { tier: "free" | "plus" | "pro"; byokAllowed: boolean; capabilities: { capability: string; available: boolean; limit: number; used: number }[] }. limit: -1 means unlimited.
Errors: none under normal operation. Network failure surfaces as a rejected promise.
tier.isAvailable(capability)
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
capability | string | yes | — | Capability slug. |
Returns: Promise<boolean> — true if the user’s tier exposes this capability. Falls back to true on transport failure (fail-open).
Errors: none.
tier.isLimitReached(capability)
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
capability | string | yes | — | Capability slug. |
Returns: Promise<boolean> — true when the capability is unavailable OR the daily limit has been hit. Falls back to false on transport failure (fail-open).
Errors: none.
EggletContext.eggletIpc
Fire-and-forget messaging between Egglets. Use this for one-way signals (“new item arrived”, “follow-up needed”); use ctx.tools.call when you need a response. Both methods require uses.eggletIpc: true. Messages are dropped silently if the target has no subscribers; senders cannot tell.
// Sender
ctx.eggletIpc.send("people", { kind: "profileVisited", id: "alice-123" });
// Receiver
const handle = ctx.eggletIpc.subscribe<{ kind: string; id: string }>((msg) => {
console.log(`from ${msg.from}: ${msg.payload.kind}`);
});
eggletIpc.send<P>(targetEgglet, payload)
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
targetEgglet | string | yes | — | Recipient’s Egglet id. |
payload | P | yes | — | JSON-serializable payload. |
Returns: void.
Errors: throws if uses.eggletIpc is not declared. Does NOT throw when the target has no subscribers.
eggletIpc.subscribe<P>(handler)
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
handler | (msg: EggletIpcMessage<P>) => void \| Promise<void> | yes | — | Called for every message addressed to this Egglet. Receives { from, payload }. |
Returns: EggletIpcHandle — { cancel(): void } to stop receiving messages.
Errors: throws if uses.eggletIpc is not declared.
EggletContext.refs
Ref kinds are the cross-Egglet handle system: stable, serializable values like @e:alice-123 that can travel through tables, agent prompts, sync payloads, and other contexts. Each kind has exactly one owning Egglet, declared in manifest.refs. The owning Egglet calls register in activate() with a function that resolves an id to a RefView. Other Egglets call resolve to render or parse to extract refs from text.
ctx.refs.register("e", async (id) => {
const row = await ctx.storage.one<{ id: string; name: string }>(
"SELECT id, name FROM {{entities}} WHERE id = ?",
[id],
);
return row ? { kind: "e", id: row.id, label: row.name, route: `/people/${row.id}` } : null;
});
refs.register(kind, resolver)
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
kind | string | yes | — | Kind prefix (e.g. "e"). Must appear in manifest.refs. |
resolver | (id: string) => Promise<RefView | null> \| RefView \| null | yes | — | Returns { kind, id, label, icon?, route?, description? } or null when unresolved. |
Returns: void.
Errors: throws if kind is not declared in the manifest or another Egglet owns it.
refs.encode(kind, id)
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
kind | string | yes | — | Kind prefix. |
id | string | yes | — | Domain id. |
Returns: string — the serialized ref (e.g. "@e:alice-123"). Synchronous.
Errors: none.
refs.resolve(ref)
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
ref | string | yes | — | Serialized ref (e.g. "@e:alice-123"). |
Returns: Promise<RefView | null> — null when the kind has no registered resolver or the resolver reports unresolved.
Errors: none for malformed input (returns null).
refs.parse(text)
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
text | string | yes | — | Arbitrary text to scan for embedded refs. |
Returns: ParsedRef[] — each entry is { kind, id, start, end, raw }. Synchronous; does not call resolvers.
Errors: none.
EggletContext.credentials
Read access to user-authorized credentials. Egglets cannot create or revoke credentials; the user manages them in Settings. The source must appear in manifest.credentials.
const entry = await ctx.credentials.get("github");
if (!entry) { ctx.log.warn("no GitHub credential authorized"); return; }
const { token } = JSON.parse(entry.dataJson);
credentials.get(source)
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
source | string | yes | — | Credential source name (e.g. "github"). Must appear in manifest.credentials. |
Returns: Promise<CredentialEntry | null> — { credentialType: string; dataJson: string; expiresAt?: string }, or null when no credential is authorized. The Egglet parses dataJson.
Errors: throws if source is not declared in manifest.credentials.