conciv

Tool contract

The defineTool reference: fields, server execution, render, and typing.

defineTool describes one agent-callable capability. The definition is what the agent sees; the chained .server and .render attach behavior.

import {z} from 'zod'
import {defineTool} from '@conciv/extension'

const tool = defineTool({
  name: 'deploy_run',
  description: 'Deploy the current branch to an environment.',
  inputSchema: z.object({env: z.enum(['staging', 'prod'])}),
  approval: 'ask',
})
  .server(async (input, ctx, request) => ({ok: true}))
  .render((props) => <div>done</div>)

Definition fields

Prop

Type

.server(execute)

The node-side action. It receives three arguments:

.server(async (input, ctx, request) => { ... })
  • input is already inputSchema.parsed, so it is fully typed.
  • ctx is the context your extension's .server(...) factory returned (see below). unknown unless you annotate the handler parameter.
  • request is {sessionId: string; model: string | null} for the calling chat session.

The return value is the tool result. Return small structured data; the agent reads it verbatim.

Typed context

Tools that need shared server state (a db handle, a child process manager) declare a context type, and the extension's server factory must provide it. The RequiredContext of all tools is enforced at the defineExtension(...).server(...) boundary:

const inputSchema = z.object({by: z.number().default(1)})

type Ctx = {counter: {next: () => number}}

const bump = defineTool({
  name: 'bump',
  description: 'Increment the counter.',
  inputSchema,
}).server((input, ctx: Ctx) => ({value: ctx.counter.next()}))

export default defineExtension({name: 'counter', tools: [bump]}).server(() => {
  let value = 0
  return {context: {counter: {next: () => ++value}}}
})

.render(renderer)

Replaces the generic result card. The renderer is a Solid component receiving {part, result, ctx, durationMs}:

  • part is the tool call (id, name, arguments).
  • result is the tool result once it lands, undefined while running. Its content carries what your server returned, error is set on failure.
  • ctx is the widget view context (theme, expansion state).

Extension-level server

defineExtension(...).server(factory) runs once at engine boot. It receives {config, cwd, sessions, harness}: your parsed configSchema config, the project root, and session and harness handles. To add custom routes (SSE streams, uploads, anything a tool result cannot carry), return a hono app; it is mounted under your extension's API namespace (/api/ext/<name>).

import {Hono} from 'hono'

export default defineExtension({name: 'metrics', configSchema, tools: [...]}).server((server) => {
  const app = new Hono().get('/stream', (c) => openStream(c))
  const poller = startPolling(server.config.interval)
  return {context: {poller}, app, dispose: () => poller.stop()}
})

Return {context, app, dispose}: context feeds your tools, app is optional, dispose runs on shutdown.

Exports

Everything above comes from one package:

import {defineExtension, defineTool} from '@conciv/extension'
import type {ExtensionBuilder, ToolBuilder, ServerApi, ToolRequest} from '@conciv/extension'

Client-side building blocks (mountExtension, useSlot, useContext, the client API) are covered in Widget UI.

On this page