Showcase

Slack integration

Agent posts to Slack via @agentskit/tools.

integrationstools
Live preview
Source
'use client'import { useMemo } from 'react'import { useChat, ChatContainer, Message, InputBar } from '@agentskit/react'import '@/styles/agentskit-theme.css'import { createMockAdapter, initialAssistant, toolsFor } from './_shared/mock-adapter'import { ToolBadge } from './_shared/tool-badge'import { IntegrationCard } from '@/app/(home)/_components/hero-demo/widgets'const TURNS = [  {    toolCalls: [      {        name: 'slack_send',        args: { channel: '#product', text: 'launch update — agentskit v0.3' },        result: { ok: true, ts: '1714612812.001' },        durationMs: 600,      },    ],    text: 'Posted to #product. 1 reaction so far.',  },  {    toolCalls: [      {        name: 'github_create_issue',        args: { repo: 'agentskit/agentskit', title: 'docs: add showcase tags' },        result: { number: 142, url: 'github.com/agentskit/agentskit/issues/142' },        durationMs: 700,      },    ],    text: 'Filed issue #142. Linked to the docs label.',  },]export function SlackIntegration() {  const adapter = useMemo(() => createMockAdapter(TURNS), [])  const tools = useMemo(() => toolsFor(TURNS), [])  const chat = useChat({    adapter,    tools,    maxToolIterations: 1,    initialMessages: [      initialAssistant(        "I'm wired to Slack + GitHub via @agentskit/tools. Ask me to send a message or open an issue.",      ),    ],  })  return (    <div      data-ak-example      className="flex h-[480px] flex-col overflow-hidden rounded-lg border border-ak-border bg-ak-surface"    >      <ChatContainer className="flex-1 space-y-2 p-4">        {chat.messages          .filter(m => m.role !== 'tool')          .map(m => (            <div key={m.id} className="flex flex-col gap-1.5">              {m.toolCalls?.map(t => (                <div key={t.id} className="flex flex-col gap-2">                  <ToolBadge call={t} />                  {t.status === 'complete' && t.name === 'slack_send' && <IntegrationCard />}                </div>              ))}              {m.content ? <Message message={m} /> : null}            </div>          ))}      </ChatContainer>      <InputBar chat={chat} />    </div>  )}
import type { AdapterFactory, StreamChunk, ToolDefinition } from '@agentskit/core'export type ToolCallEmit = {  name: string  args?: Record<string, unknown>  result?: unknown  durationMs?: number}export type Turn = {  /** Text streamed as the assistant reply. */  text: string  /** Optional tool calls to emit before the text. The runtime executes each via   *  the matching tool stub exposed by `toolsFor(turns)`. */  toolCalls?: ToolCallEmit[]  /** Optional reasoning stream emitted before tool calls / text. */  reasoning?: string}export function createMockAdapter(turns: Turn[], cps = 80): AdapterFactory {  let idx = 0  return {    createSource: () => ({      stream: async function* (): AsyncIterableIterator<StreamChunk> {        const turn = turns[idx % turns.length]        idx += 1        if (turn.reasoning) {          for (const ch of turn.reasoning) {            await sleep(1000 / cps)            yield { type: 'reasoning', content: ch }          }        }        if (turn.toolCalls) {          for (const call of turn.toolCalls) {            yield {              type: 'tool_call',              toolCall: {                id: `call-${Math.random().toString(36).slice(2, 8)}`,                name: call.name,                args: JSON.stringify(call.args ?? {}),              },            }          }        }        for (const ch of turn.text) {          await sleep(1000 / cps)          yield { type: 'text', content: ch }        }        yield { type: 'done' }      },      abort() {},    }),    capabilities: { streaming: true, tools: true },  }}/** * Build a registry of tool stubs whose `execute()` resolves to the mocked * result declared for that tool name in `turns`. When the controller sees a * `tool_call` chunk from the mock adapter it looks up the name here, runs the * stub (with a simulated latency), and emits `tool_result`. */export function toolsFor(turns: Turn[]): ToolDefinition[] {  const byName = new Map<string, ToolCallEmit>()  for (const t of turns) {    for (const c of t.toolCalls ?? []) {      if (!byName.has(c.name)) byName.set(c.name, c)    }  }  return Array.from(byName.values()).map<ToolDefinition>((call) => ({    name: call.name,    description: `Mock ${call.name}`,    schema: {},    async execute() {      if (call.durationMs) await sleep(call.durationMs)      return JSON.stringify(call.result ?? { ok: true })    },  }))}function sleep(ms: number) {  return new Promise<void>((r) => setTimeout(r, ms))}export function initialAssistant(content: string) {  return {    id: 'init',    role: 'assistant' as const,    content,    status: 'complete' as const,    createdAt: new Date(),  }}
'use client'import type { ToolCall } from '@agentskit/core'/** * Compact tool-call badge that mirrors the home hero demo: * `✓ name({ args }) Nms` in a green pill when complete, blue with a spinner * while pending. Use inside <Message>{m.toolCalls?.map(...)}</Message>. */export function ToolBadge({ call }: { call: ToolCall }) {  const done = call.status === 'complete' || call.status === 'error'  const error = call.status === 'error'  const args = formatArgs(call.args)  return (    <div      data-ak-tool-badge      className={`inline-flex max-w-full items-start gap-2 rounded-md border px-2.5 py-1 font-mono text-xs ${        error          ? 'border-ak-red/30 bg-ak-red/5 text-ak-red'          : done          ? 'border-ak-green/30 bg-ak-green/5 text-ak-green'          : 'border-ak-blue/30 bg-ak-blue/5 text-ak-blue'      }`}    >      <span className="mt-0.5 shrink-0">        {done ? (error ? '✗' : '✓') : <Spinner />}      </span>      <span className="min-w-0 break-all">        {call.name}        {args ? `(${args})` : '()'}      </span>    </div>  )}function formatArgs(raw: Record<string, unknown> | string | undefined): string {  if (!raw) return ''  const parsed =    typeof raw === 'string'      ? (() => {          try {            return JSON.parse(raw)          } catch {            return raw          }        })()      : raw  if (!parsed || typeof parsed !== 'object') return String(parsed)  const entries = Object.entries(parsed)  if (entries.length === 0) return ''  return entries.map(([k, v]) => `${k}: ${stringify(v)}`).join(', ')}function stringify(value: unknown): string {  if (typeof value === 'string') return `"${value}"`  if (value === null) return 'null'  if (typeof value === 'object') return JSON.stringify(value)  return String(value)}function Spinner() {  return (    <span className="inline-block h-3 w-3 animate-spin rounded-full border-2 border-ak-blue border-t-transparent" />  )}
More examples
See all →