Skip to content

Commit 082dbcd

Browse files
docs: update spectrum-ts documentation for v2.0.0 (#82)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 0224eae commit 082dbcd

11 files changed

Lines changed: 217 additions & 19 deletions

docs-src/spectrum-ts/content.mdx.vel

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { TypeTooltip } from "/snippets/type-tooltip.mdx";
88
{% set ci = symbol("ts:spectrum-ts#ContentInput") %}
99
{% set cb = symbol("ts:spectrum-ts#ContentBuilder") %}
1010

11-
Spectrum exposes a family of content builders — `text`, `attachment`, `voice`, `contact`, `richlink`, `poll`, `group`, `custom`, `reaction`, `reply`, `edit`, `typing`, `rename`, and `avatar` — plus a string shortcut that's equivalent to `text()`. Any API that takes a <TypeTooltip name="ContentInput" type={`{{ ci.signature }}`} /> accepts a plain string or a <TypeTooltip name="ContentBuilder" type={`{{ cb.signature }}`} />.
11+
Spectrum exposes a family of content builders — `text`, `streamText`, `attachment`, `voice`, `contact`, `richlink`, `poll`, `group`, `custom`, `reaction`, `reply`, `edit`, `typing`, `rename`, and `avatar` — plus a string shortcut that's equivalent to `text()`. Any API that takes a <TypeTooltip name="ContentInput" type={`{{ ci.signature }}`} /> accepts a plain string or a <TypeTooltip name="ContentBuilder" type={`{{ cb.signature }}`} />.
1212

1313
## Text
1414

@@ -21,6 +21,53 @@ await space.send(text("Hello, world."));
2121
await space.send("Hello, world.");
2222
```
2323

24+
## Streaming text
25+
26+
Send streaming LLM output as Spectrum content. The `streamText` builder wraps an async stream of text deltas so it can be sent like any other content item. On platforms that support it (iMessage in remote mode), the first chunk is sent immediately as a real message and then edited in place as more text arrives.
27+
28+
```ts
29+
import { streamText } from "spectrum-ts";
30+
```
31+
32+
`streamText` accepts whatever the popular LLM SDKs return — the Vercel AI SDK `streamText()` result, a raw `AsyncIterable` of chunks, or a `ReadableStream`:
33+
34+
<Tabs>
35+
<Tab title="Vercel AI SDK">
36+
```ts
37+
import { streamText } from "spectrum-ts";
38+
import { streamText as aiStreamText } from "ai";
39+
40+
const result = aiStreamText({ model, prompt: message.content.text });
41+
await space.send(streamText(result));
42+
```
43+
</Tab>
44+
<Tab title="AsyncIterable">
45+
```ts
46+
import { streamText } from "spectrum-ts";
47+
48+
async function* generate() {
49+
yield "Hello, ";
50+
yield "world!";
51+
}
52+
53+
await space.send(streamText(generate()));
54+
```
55+
</Tab>
56+
<Tab title="Custom extractor">
57+
```ts
58+
import { streamText } from "spectrum-ts";
59+
60+
await space.send(
61+
streamText(customStream, {
62+
extract: (chunk) => chunk.delta?.text ?? null,
63+
}),
64+
);
65+
```
66+
</Tab>
67+
</Tabs>
68+
69+
Platforms that cannot stream silently skip the send with a warning.
70+
2471
## Attachments
2572

2673
Pass a file path or a `Buffer`. MIME types are detected from the file extension; override with `options.mimeType` when you already have the bytes.

docs-src/spectrum-ts/custom-events-and-lifecycle.mdx.vel

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,18 @@ for await (const event of im.typing) {
3434

3535
Use the flat form on `app` when you want a merged feed across platforms; use the narrowed form when you only care about one.
3636

37+
### Fusor custom events
38+
39+
Fusor-backed providers can emit non-message events (presence, read receipts, delivery status) into typed event streams using `fusorEvent`. Inside a Fusor `messages` handler, yield a `fusorEvent(name, data)` alongside regular messages to push events into `app.<name>` streams:
40+
41+
```ts
42+
import { fusorEvent } from "spectrum-ts";
43+
44+
yield fusorEvent("presence", { userId: update.userId, online: true });
45+
```
46+
47+
Events emitted this way are available as `app.presence` (merged across providers) or `narrowedInstance.presence` (scoped to the emitting provider). The event name and data shape are fully typed based on the provider's event declarations.
48+
3749
## Lifecycle
3850

3951
### Graceful shutdown

docs-src/spectrum-ts/custom-platforms.mdx.vel

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,54 @@ const msg = await mine.getMessage(space, "msg-123");
175175
const thread = await mine.lookupThread("thread-456");
176176
```
177177

178+
## Fusor-backed providers
179+
180+
When your platform receives inbound messages through webhooks (rather than a persistent connection), use `fusor(...)` as the client in `lifecycle.createClient`. A Fusor client handles webhook signature verification and delivers parsed payloads to your `messages` handler:
181+
182+
```ts
183+
import { definePlatform, fusor, fusorEvent } from "spectrum-ts";
184+
import z from "zod";
185+
186+
export const myWebhookPlatform = definePlatform("my-webhook-platform", {
187+
config: z.object({
188+
webhookSecret: z.string(),
189+
}),
190+
191+
lifecycle: {
192+
createClient: async ({ config }) =>
193+
fusor("my-webhook-platform", (req) => {
194+
verifySignature(req.rawBody, req.headers, config.webhookSecret);
195+
return JSON.parse(new TextDecoder().decode(req.rawBody));
196+
}),
197+
},
198+
199+
messages: async function* ({ payload, config, respond }) {
200+
respond({ status: 200 });
201+
yield {
202+
id: payload.id,
203+
content: { type: "text", text: payload.text },
204+
sender: { id: payload.userId },
205+
space: { id: payload.chatId },
206+
timestamp: new Date(payload.ts),
207+
};
208+
},
209+
210+
send: async ({ space, content, config }) => {
211+
// dispatch outbound messages
212+
},
213+
214+
user: {
215+
resolve: async ({ input }) => ({ id: input.userID }),
216+
},
217+
218+
space: {
219+
resolve: async ({ input }) => ({ id: input.users[0].id }),
220+
},
221+
});
222+
```
223+
224+
The Fusor overload of `definePlatform` replaces the top-level `messages` async generator with a per-webhook-delivery handler that receives `{ payload, config, respond }`. Call `respond()` to set the HTTP response sent back to the webhook caller.
225+
178226
## Registering your platform
179227

180228
Exported platforms work like the built-ins — register with `.config()` and use narrowing for the typed surface:

docs-src/spectrum-ts/introduction.mdx.vel

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,22 @@ A user might message you in iMessage today, WhatsApp tomorrow, and your app next
2323

2424
With Spectrum, you run one agent server and add providers for the interfaces you want to support. Each provider connects a native interface to the same Spectrum API, so your agent can feel consistent everywhere.
2525

26-
Today, Spectrum supports iMessage, WhatsApp Business, and terminal development. The same model is built for more interfaces over time: Slack, Discord, websites, apps, phone calls, meetings, and hardware like HomePod.
26+
Today, Spectrum supports iMessage, WhatsApp Business, Telegram, and terminal development. The same model is built for more interfaces over time: Slack, Discord, websites, apps, phone calls, meetings, and hardware like HomePod.
2727

2828
## Supported interfaces today
2929

3030
Spectrum currently includes official providers for:
3131

32-
<CardGroup cols={3}>
32+
<CardGroup cols={2}>
3333
<Card title="iMessage" icon="comment" href="/spectrum-ts/providers/imessage">
3434
Run production iMessage agents through managed iMessage lines.
3535
</Card>
3636
<Card title="WhatsApp Business" icon="whatsapp" href="/spectrum-ts/providers/whatsapp-business">
3737
Connect to the official WhatsApp Business Cloud API.
3838
</Card>
39+
<Card title="Telegram" icon="paper-plane" href="/spectrum-ts/providers/telegram">
40+
Build bots on the Telegram Bot API with inbound webhooks through Fusor.
41+
</Card>
3942
<Card title="Terminal" icon="terminal" href="/spectrum-ts/providers/terminal">
4043
Build, test, and demo agents from your local terminal.
4144
</Card>

docs-src/spectrum-ts/messages.mdx.vel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ for await (const [space, message] of app.messages) {
126126
| `"reply"` | `content: Content`, `target: Message` — threaded reply wrapping inner content |
127127
| `"edit"` | `content: Content`, `target: Message` — rewrite of a previously-sent message |
128128
| `"typing"` | `state: "start" \| "stop"` — typing indicator signal |
129+
| `"streamText"` | `stream: () => AsyncIterable<string>` — streaming text content |
129130
| `"custom"` | `raw: unknown` — platform-specific structured data |
130131
</Accordion>
131132

docs-src/spectrum-ts/platform-narrowing.mdx.vel

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { TypeTooltip } from "/snippets/type-tooltip.mdx";
77

88
{% set pi = symbol("ts:spectrum-ts#PlatformInstance") %}
99

10-
Every platform provider exports a callable — `imessage`, `terminal`, `whatsappBusiness` — that **narrows** generic Spectrum types into platform-specific ones. The same function handles three different inputs.
10+
Every platform provider exports a callable — `imessage`, `terminal`, `whatsappBusiness`, `telegram` — that **narrows** generic Spectrum types into platform-specific ones. The same function handles three different inputs.
1111

1212
## Narrowing the app
1313

@@ -41,7 +41,7 @@ for await (const [space, message] of app.messages) {
4141
}
4242
```
4343

44-
Narrowing a space from the wrong platform throws at runtime. Always gate on `message.platform` (or a similar signal) first.
44+
Narrowing a space from the wrong platform logs a structured warning at runtime. Always gate on `message.platform` (or a similar signal) first to avoid unexpected behavior.
4545

4646
## Narrowing a message
4747

docs-src/spectrum-ts/providers.mdx.vel

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ Providers plug into Spectrum's type system and runtime. Each one exports a calla
1717
<Card title="WhatsApp Business" icon="whatsapp" href="/spectrum-ts/providers/whatsapp-business">
1818
Official WhatsApp Business Cloud API. Native reactions and replies, 1:1 conversations only.
1919
</Card>
20+
<Card title="Telegram" icon="paper-plane" href="/spectrum-ts/providers/telegram">
21+
Telegram Bot API with Fusor webhooks. Text, media, reactions, replies, typing, and edits.
22+
</Card>
2023
</CardGroup>
2124

2225
## Combining providers
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
---
2+
title: "Telegram"
3+
description: "Send and receive messages through the Telegram Bot API"
4+
---
5+
6+
```ts
7+
import { telegram } from "spectrum-ts/providers/telegram";
8+
```
9+
10+
The Telegram provider connects your agent to the Telegram Bot API. Inbound messages are delivered through Fusor webhooks; outbound messages use the Bot API directly. The provider supports text, media, reactions, replies, typing indicators, edits, and lazy media downloads.
11+
12+
## Config
13+
14+
```ts
15+
telegram.config({
16+
botToken: "your-bot-token",
17+
});
18+
```
19+
20+
| Option | Description |
21+
|---|---|
22+
| `botToken` | Bot token from [@BotFather](https://t.me/BotFather). |
23+
| `webhookSecret` | Optional secret token for verifying webhook payloads. |
24+
25+
## Example
26+
27+
```ts
28+
import { Spectrum } from "spectrum-ts";
29+
import { telegram } from "spectrum-ts/providers/telegram";
30+
31+
const app = await Spectrum({
32+
projectId: process.env.PROJECT_ID!,
33+
projectSecret: process.env.PROJECT_SECRET!,
34+
providers: [
35+
telegram.config({
36+
botToken: process.env.TELEGRAM_BOT_TOKEN!,
37+
}),
38+
],
39+
});
40+
41+
for await (const [space, message] of app.messages) {
42+
if (message.content.type === "text") {
43+
await space.send(`Echo: ${message.content.text}`);
44+
}
45+
}
46+
```
47+
48+
## Starting a conversation
49+
50+
Resolve a user by their Telegram user ID and open a space. You can also pass a `chatId` parameter to target a specific Telegram chat:
51+
52+
```ts
53+
const tg = telegram(app);
54+
const user = await tg.user("123456789");
55+
const space = await tg.space(user);
56+
57+
await space.send("Hello from Spectrum.");
58+
```
59+
60+
## Webhook registration
61+
62+
In cloud mode (when `projectId` and `projectSecret` are provided), the Telegram provider automatically registers its Fusor webhook on startup. In local or direct mode, you need to configure the webhook yourself through the Telegram Bot API.
63+
64+
## Supported features
65+
66+
| Feature | Support |
67+
|---|---|
68+
| Text messages | Send and receive |
69+
| Media (photos, documents, audio, video) | Send and receive |
70+
| Reactions | Send and receive |
71+
| Threaded replies | Send and receive |
72+
| Typing indicators | Send |
73+
| Message edits | Send and receive |
74+
| Custom Bot API calls | Via platform-specific actions |

docs.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@
4141
"spectrum-ts/providers",
4242
"spectrum-ts/providers/imessage",
4343
"spectrum-ts/providers/terminal",
44-
"spectrum-ts/providers/whatsapp-business"
44+
"spectrum-ts/providers/whatsapp-business",
45+
"spectrum-ts/providers/telegram"
4546
]
4647
},
4748
{

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
"eslint-plugin-format": "^2.0.1",
2929
"husky": "^9.1.7",
3030
"oxfmt": "^0.44.0",
31-
"spectrum-ts": "1.18.0",
31+
"spectrum-ts": "2.0.0",
3232
"tsx": "^4.21.0",
3333
"typescript": "^5.9.3"
3434
}

0 commit comments

Comments
 (0)