@moikapy/lich 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/README.md +186 -0
- package/dist/chunk-P52U5M3L.js +3431 -0
- package/dist/chunk-P52U5M3L.js.map +1 -0
- package/dist/chunk-ZVK3MUPC.js +7 -0
- package/dist/chunk-ZVK3MUPC.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +409 -0
- package/dist/cli.js.map +1 -0
- package/dist/gateway-CWPVIU3W.js +752 -0
- package/dist/gateway-CWPVIU3W.js.map +1 -0
- package/dist/index.d.ts +542 -0
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -0
- package/dist/tui-V7ATLIKW.js +430 -0
- package/dist/tui-V7ATLIKW.js.map +1 -0
- package/docs/.vitepress/config.mts +55 -0
- package/docs/architecture/agent-loop.md +234 -0
- package/docs/architecture/extending.md +284 -0
- package/docs/architecture/overview.md +188 -0
- package/docs/architecture/plugins.md +91 -0
- package/docs/architecture/providers.md +273 -0
- package/docs/architecture/tools.md +180 -0
- package/docs/design/council/architecture-review.md +47 -0
- package/docs/design/council/security-review.md +39 -0
- package/docs/design/council/simplicity-review.md +45 -0
- package/docs/design/self-improvement-loop.md +166 -0
- package/docs/getting-started.md +133 -0
- package/docs/index.md +68 -0
- package/docs/user-guide/cli.md +182 -0
- package/docs/user-guide/gateway.md +168 -0
- package/docs/user-guide/library.md +181 -0
- package/docs/user-guide/plugins.md +120 -0
- package/docs/user-guide/tui.md +76 -0
- package/package.json +54 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/gateway/types.ts","../src/gateway/bus.ts","../src/gateway/discord.ts","../src/gateway/telegram.ts","../src/gateway/twitch.ts","../src/gateway/webhook.ts","../src/gateway/format.ts","../src/gateway/runner.ts"],"sourcesContent":["/**\n * Shared contracts for the messaging gateway: platform message shapes,\n * reply sinks, adapter lifecycle, and the small runtime helpers every\n * platform adapter shares (idle fallback, raw WebSocket, inbound dispatch).\n */\nimport type { Agent } from \"../agent/agent.js\";\nimport type { AgentConfig } from \"../agent/config.js\";\nimport { logger } from \"../util/log.js\";\n\nexport type PlatformName = \"webhook\" | \"telegram\" | \"discord\" | \"twitch\";\n\n/** Normalized inbound message from any supported platform. */\nexport interface PlatformMessage {\n platform: PlatformName;\n chat_id: string;\n user_id: string;\n text: string;\n}\n\n/** Outbound-only view used to deliver a reply to a conversation. */\nexport interface ReplySink {\n send(text: string): Promise<void>;\n}\n\n/** Lifecycle contract implemented by every platform adapter. */\nexport interface PlatformAdapter {\n readonly name: string;\n start(): Promise<void>;\n stop(): Promise<void>;\n}\n\n/** Handles one inbound message and resolves to the reply text. */\nexport type InboundHandler = (\n platform: string,\n chat_id: string,\n user_id: string,\n text: string,\n) => Promise<string | undefined>;\n\n/** Dependencies handed to every adapter factory. */\nexport interface AdapterParams {\n config: AgentConfig;\n handle_message: InboundHandler;\n get_agent: () => Agent;\n reply_router: (platform: string, chat_id: string) => ReplySink | undefined;\n}\n\n/** Minimal structural WebSocket surface shared by bun and node runtimes. */\nexport interface RawSocket {\n onopen: (() => void) | null;\n onmessage: ((event: { data: unknown }) => void) | null;\n onclose: (() => void) | null;\n onerror: ((event: unknown) => void) | null;\n send(data: string): void;\n close(): void;\n}\n\nconst ERROR_SNIPPET_CHARS = 300;\n\n/** Flattens a failure into a single-line, bounded, safe reply string. */\nexport function sanitize_agent_error(error: unknown): string {\n const raw = error instanceof Error ? error.message : String(error);\n const flat = raw.replace(/\\s+/g, \" \").trim();\n return `agent error: ${flat.slice(0, ERROR_SNIPPET_CHARS) || \"unknown\"}`;\n}\n\n/** Adapter that logs why it is idle once and otherwise does nothing. */\nexport function create_idle_adapter(name: string, reason: string): PlatformAdapter {\n logger.warn(`gateway ${name} adapter idle: ${reason}`);\n return {\n name,\n start: async () => undefined,\n stop: async () => undefined,\n };\n}\n\n/** Opens a WebSocket and resolves once connected; rejects on open error. */\nexport function open_socket(url: string): Promise<RawSocket> {\n return new Promise((resolve, reject) => {\n const ctor = (globalThis as { WebSocket?: new (url: string) => unknown }).WebSocket;\n if (ctor === undefined) {\n reject(new Error(\"runtime does not expose a WebSocket constructor\"));\n return;\n }\n const socket = new ctor(url) as unknown as RawSocket;\n socket.onopen = () => resolve(socket);\n socket.onerror = () => reject(new Error(`websocket connect failed: ${url}`));\n });\n}\n\n/** Runs one inbound message, converting failures into a safe reply string. */\nexport async function run_inbound_message(\n handle: InboundHandler,\n platform: string,\n chat_id: string,\n user_id: string,\n text: string,\n): Promise<string> {\n try {\n return (await handle(platform, chat_id, user_id, text)) ?? \"\";\n } catch (error) {\n logger.error(`gateway ${platform} message handling failed`, error);\n return sanitize_agent_error(error);\n }\n}","/**\n * GatewayBus: conversation-keyed runner over one shared Agent.\n *\n * Per-conversation history lives in a bounded Map; concurrent messages for\n * the same conversation are serialized through a promise chain so history\n * never interleaves. Agent failures become sanitized reply strings.\n */\nimport type { Agent } from \"../agent/agent.js\";\nimport type { AgentConfig } from \"../agent/config.js\";\nimport type { Message } from \"../providers/types.js\";\nimport { logger } from \"../util/log.js\";\nimport { sanitize_agent_error } from \"./types.js\";\n\nexport interface GatewayBusOptions {\n history_cap?: number;\n max_conversations?: number;\n}\n\nexport interface BusParams {\n config: AgentConfig;\n agent_factory: () => Agent;\n /** Subscribe to agent tool events for debug logging (creates the agent). */\n wire_tool_logging?: boolean;\n}\n\nconst DEFAULT_HISTORY_CAP = 40;\nconst DEFAULT_MAX_CONVERSATIONS = 200;\n\nexport class GatewayBus {\n private readonly config: AgentConfig;\n private readonly agent_factory: () => Agent;\n private agent: Agent | undefined;\n private readonly histories: Map<string, Message[]> = new Map();\n private readonly chains: Map<string, Promise<void>> = new Map();\n private readonly history_cap: number;\n private readonly max_conversations: number;\n private stop_logging: (() => void) | undefined;\n\n constructor(params: BusParams, options?: GatewayBusOptions) {\n this.config = params.config;\n this.agent_factory = params.agent_factory;\n this.history_cap = options?.history_cap ?? DEFAULT_HISTORY_CAP;\n this.max_conversations = options?.max_conversations ?? DEFAULT_MAX_CONVERSATIONS;\n if (params.wire_tool_logging === true) {\n this.wire_tool_logging();\n }\n }\n\n /** Serializes runs per conversation and resolves to the reply text. */\n async handle(platform: string, chat_id: string, user_id: string, text: string): Promise<string | undefined> {\n const key = conversation_key(platform, chat_id);\n const previous = this.chains.get(key) ?? Promise.resolve();\n const run = previous.then(() => this.run_once(key, platform, chat_id, user_id, text));\n this.chains.set(\n key,\n run.then(\n () => undefined,\n () => undefined,\n ),\n );\n return run;\n }\n\n /** Unsubscribes the debug tool logger (bus owns no other resources). */\n stop(): void {\n this.stop_logging?.();\n this.stop_logging = undefined;\n }\n\n private async run_once(\n key: string,\n platform: string,\n chat_id: string,\n user_id: string,\n text: string,\n ): Promise<string | undefined> {\n const input = text.startsWith(\"/start\") === true ? \"hello\" : text;\n const history = this.history_for(key);\n const agent = this.ensure_agent();\n try {\n const result = await agent.run({ input, history, label: `gw:${platform}:${chat_id}` });\n this.histories.set(key, cap_history(result.messages, this.history_cap));\n return final_reply_text(result.outcome.final?.content);\n } catch (error) {\n logger.error(`gateway bus run failed for ${key} (user ${user_id})`, error);\n return sanitize_agent_error(error);\n }\n }\n\n private ensure_agent(): Agent {\n if (this.agent === undefined) {\n this.agent = this.agent_factory();\n }\n return this.agent;\n }\n\n /** Oldest-first eviction keeps the conversation map bounded. */\n private history_for(key: string): Message[] {\n while (this.histories.size >= this.max_conversations && this.histories.has(key) === false) {\n const oldest = this.histories.keys().next();\n if (oldest.done === true) {\n break;\n }\n this.histories.delete(oldest.value);\n }\n return this.histories.get(key) ?? [];\n }\n\n /** Logs completed tool calls at debug level for gateway observability. */\n private wire_tool_logging(): void {\n const agent = this.agent_factory();\n this.agent = agent;\n this.stop_logging = agent.events.on((event) => {\n if (event.type === \"tool_call_end\") {\n logger.debug(`tool ${event.call.name} ${event.result.ok === true ? \"ok\" : \"failed\"}`);\n }\n });\n }\n}\n\nfunction conversation_key(platform: string, chat_id: string): string {\n return `${platform}:${chat_id}`;\n}\n\nfunction cap_history(messages: Message[], cap: number): Message[] {\n const overflow = messages.length - cap;\n if (overflow <= 0) {\n return messages;\n }\n return messages.slice(overflow);\n}\n\nfunction final_reply_text(content: string | undefined): string | undefined {\n return content === undefined || content.length === 0 ? undefined : content;\n}","/**\n * Discord adapter: gateway WebSocket (op-code switch, heartbeat, fresh\n * reconnect) plus REST replies. Token optional; absent token degrades to\n * an idle adapter. No resume support — reconnects are fresh by design.\n */\nimport { logger } from \"../util/log.js\";\nimport type { AdapterParams, PlatformAdapter, RawSocket } from \"./types.js\";\nimport { create_idle_adapter, open_socket, run_inbound_message } from \"./types.js\";\n\nconst DISCORD_API = \"https://discord.com/api/v10\";\nconst GATEWAY_URL = \"wss://gateway.discord.gg/?v=10&encoding=json\";\nconst INTENTS = 512 | 32768;\n/** Live heartbeat timers keyed by socket, cleared when the session ends. */\nconst heartbeat_timers = new WeakMap<RawSocket, ReturnType<typeof setInterval>>();\n\ntype DiscordPayload = {\n op?: number;\n t?: string;\n s?: number | null;\n d?: Record<string, unknown>;\n};\n\ninterface DiscordMessageData {\n content: string;\n channel_id: string;\n author_id: string;\n}\n\nexport function create_discord_adapter(params: AdapterParams): PlatformAdapter {\n const token = process.env.LICH_DISCORD_BOT_TOKEN;\n if (token === undefined || token.length === 0) {\n return create_idle_adapter(\"discord\", \"LICH_DISCORD_BOT_TOKEN not set\");\n }\n let running = false;\n let socket: RawSocket | undefined;\n return {\n name: \"discord\",\n start: async () => {\n running = true;\n void connect_loop(params, token, () => running, (opened) => {\n socket = opened;\n });\n },\n stop: async () => {\n running = false;\n socket?.close();\n socket = undefined;\n },\n };\n}\n\nasync function connect_loop(\n params: AdapterParams,\n token: string,\n keep_running: () => boolean,\n set_socket: (socket: RawSocket) => void,\n): Promise<void> {\n while (keep_running()) {\n try {\n const socket = await open_socket(GATEWAY_URL);\n set_socket(socket);\n await socket_session(socket, token, params);\n } catch (error) {\n logger.warn(\"gateway discord connection failed; reconnecting in 5s\", error);\n await new Promise((resolve) => setTimeout(resolve, 5000));\n }\n }\n}\n\nasync function socket_session(socket: RawSocket, token: string, params: AdapterParams): Promise<void> {\n let heartbeat: ReturnType<typeof setInterval> | undefined;\n const done = new Promise<void>((resolve) => {\n socket.onclose = () => resolve();\n });\n socket.onmessage = (event) => {\n const payload = parse_payload(event.data);\n if (payload === undefined) {\n return;\n }\n handle_discord_payload(socket, token, params, payload);\n };\n await done;\n heartbeat = heartbeat_timers.get(socket);\n if (heartbeat !== undefined) {\n clearInterval(heartbeat);\n heartbeat_timers.delete(socket);\n }\n}\n\nfunction handle_discord_payload(\n socket: RawSocket,\n token: string,\n params: AdapterParams,\n payload: DiscordPayload,\n): void {\n if (payload.op === 10 && is_hello(payload.d)) {\n socket.send(JSON.stringify({ op: 2, d: identify_body(token) }));\n schedule_heartbeat(socket, payload.d.heartbeat_interval);\n return;\n }\n if (payload.t === \"MESSAGE_CREATE\") {\n void on_message_create(params, payload.d);\n }\n}\n\nfunction schedule_heartbeat(\n socket: RawSocket,\n interval_ms: unknown,\n): void {\n const interval = typeof interval_ms === \"number\" ? interval_ms : 45000;\n const timer = setInterval(() => {\n socket.send(JSON.stringify({ op: 1, d: null }));\n }, Math.max(1000, interval - 1000));\n heartbeat_timers.set(socket, timer);\n}\n\nfunction identify_body(token: string): Record<string, unknown> {\n return { token, intents: INTENTS, properties: { os: \"linux\", browser: \"lich\", device: \"lich\" } };\n}\n\nasync function on_message_create(params: AdapterParams, data: Record<string, unknown> | undefined): Promise<void> {\n const message = normalize_message(data);\n if (message === undefined) {\n return;\n }\n const reply = await run_inbound_message(\n params.handle_message,\n \"discord\",\n message.channel_id,\n message.author_id,\n message.content,\n );\n await rest_send_message(message.channel_id, reply);\n}\n\nasync function rest_send_message(channel_id: string, text: string): Promise<void> {\n const token = process.env.LICH_DISCORD_BOT_TOKEN;\n if (token === undefined || channel_id === \"\") {\n return;\n }\n for (const chunk of split_chunks(text, 2000)) {\n const response = await fetch(`${DISCORD_API}/channels/${channel_id}/messages`, {\n method: \"POST\",\n headers: { authorization: `Bot ${token}`, \"content-type\": \"application/json\" },\n body: JSON.stringify({ content: chunk }),\n signal: AbortSignal.timeout(30_000),\n });\n if (response.ok === false) {\n logger.warn(`gateway discord sendMessage failed with http ${response.status}`);\n }\n }\n}\n\nfunction normalize_message(data: Record<string, unknown> | undefined): DiscordMessageData | undefined {\n if (data === undefined) {\n return undefined;\n }\n const author = data.author;\n if (typeof author !== \"object\" || author === null) {\n return undefined;\n }\n const info = author as { id?: unknown; bot?: unknown };\n if (info.bot === true) {\n return undefined;\n }\n const channel_id = typeof data.channel_id === \"string\" ? data.channel_id : \"\";\n const content = strip_mention(typeof data.content === \"string\" ? data.content : \"\");\n return { channel_id, content, author_id: typeof info.id === \"string\" ? info.id : \"\" };\n}\n\nfunction strip_mention(content: string): string {\n const bot_id = process.env.LICH_DISCORD_BOT_ID;\n if (bot_id === undefined) {\n return content.trim();\n }\n const mention = `<@${bot_id}>`;\n return content.startsWith(mention) === true ? content.slice(mention.length).trim() : content.trim();\n}\n\nfunction is_hello(d: Record<string, unknown> | undefined): d is { heartbeat_interval: number } {\n return d !== undefined && typeof d.heartbeat_interval === \"number\";\n}\n\nfunction parse_payload(data: unknown): DiscordPayload | undefined {\n if (typeof data !== \"string\") {\n return undefined;\n }\n try {\n return JSON.parse(data) as DiscordPayload;\n } catch {\n return undefined;\n }\n}\n\nfunction split_chunks(text: string, limit: number): string[] {\n const chunks: string[] = [];\n let rest = text;\n while (rest.length > limit) {\n chunks.push(rest.slice(0, limit));\n rest = rest.slice(limit);\n }\n if (rest.length > 0) {\n chunks.push(rest);\n }\n return chunks;\n}","/**\n * Telegram adapter: long-poll getUpdates over fetch and sendMessage\n * replies with 4096-char splitting. Missing token degrades to an idle\n * adapter instead of failing the gateway.\n */\nimport { sleep } from \"../util/sleep.js\";\nimport { logger } from \"../util/log.js\";\nimport type { AdapterParams, PlatformAdapter } from \"./types.js\";\nimport { create_idle_adapter, run_inbound_message } from \"./types.js\";\n\nexport const TELEGRAM_MAX_MESSAGE_CHARS = 4096;\n\ninterface TelegramUpdate {\n update_id?: number;\n message?: {\n text?: string;\n chat?: { id?: number | string };\n from?: { id?: number | string; is_bot?: boolean };\n };\n}\n\nexport const TELEGRAM_BACKOFF_MS = [2000, 4000, 8000, 16000, 30000] as const;\n\nexport function create_telegram_adapter(params: AdapterParams): PlatformAdapter {\n const token = process.env.LICH_TELEGRAM_BOT_TOKEN;\n if (token === undefined || token.length === 0) {\n return create_idle_adapter(\"telegram\", \"LICH_TELEGRAM_BOT_TOKEN not set\");\n }\n let running = false;\n return {\n name: \"telegram\",\n start: async () => {\n running = true;\n void poll_loop(params, token, () => running);\n },\n stop: async () => {\n running = false;\n },\n };\n}\n\nasync function poll_loop(params: AdapterParams, token: string, keep_running: () => boolean): Promise<void> {\n let offset = 0;\n let backoff_index = 0;\n while (keep_running()) {\n try {\n const updates = await fetch_updates(token, offset);\n backoff_index = 0;\n for (const update of updates) {\n offset = update.update_id !== undefined ? update.update_id + 1 : offset;\n void deliver_update(params, token, update);\n }\n } catch (error) {\n logger.warn(\"gateway telegram poll failed; backing off\", error);\n await sleep(TELEGRAM_BACKOFF_MS[backoff_index] ?? 30000);\n backoff_index = Math.min(backoff_index + 1, TELEGRAM_BACKOFF_MS.length - 1);\n }\n }\n}\n\nasync function fetch_updates(token: string, offset: number): Promise<TelegramUpdate[]> {\n const url = api_url(token, \"getUpdates\") + `?timeout=50&offset=${offset}`;\n const response = await fetch(url, { signal: AbortSignal.timeout(60_000) });\n if (response.ok === false) {\n throw new Error(`getUpdates http ${response.status}`);\n }\n const body = (await response.json()) as { result?: TelegramUpdate[] };\n return Array.isArray(body.result) === true ? body.result : [];\n}\n\nasync function deliver_update(params: AdapterParams, token: string, update: TelegramUpdate): Promise<void> {\n const message = update.message;\n if (message === undefined || message.from?.is_bot === true) {\n return;\n }\n const chat_id = message.chat?.id;\n if (chat_id === undefined) {\n return;\n }\n const text = message.text ?? \"media not supported yet\";\n const reply = await run_inbound_message(\n params.handle_message,\n \"telegram\",\n String(chat_id),\n String(message.from?.id ?? \"unknown\"),\n text,\n );\n await send_reply(token, String(chat_id), reply);\n}\n\nasync function send_reply(token: string, chat_id: string, text: string): Promise<void> {\n for (const chunk of split_text(text, TELEGRAM_MAX_MESSAGE_CHARS)) {\n await post_json(api_url(token, \"sendMessage\"), { chat_id, text: chunk });\n }\n}\n\nasync function post_json(url: string, payload: Record<string, unknown>): Promise<void> {\n const response = await fetch(url, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify(payload),\n signal: AbortSignal.timeout(30_000),\n });\n if (response.ok === false) {\n logger.warn(`gateway telegram sendMessage failed with http ${response.status}`);\n }\n}\n\nfunction api_url(token: string, method: string): string {\n return `https://api.telegram.org/bot${token}/${method}`;\n}\n\n/** Splits at the last newline/space inside each window; hard cut fallback. */\nexport function split_text(text: string, limit: number): string[] {\n const chunks: string[] = [];\n let rest = text;\n while (rest.length > limit) {\n const window = rest.slice(0, limit + 1);\n const newline = window.lastIndexOf(\"\\n\");\n const space = window.lastIndexOf(\" \");\n const cut = newline > 0 ? newline : space > 0 ? space : limit;\n chunks.push(rest.slice(0, cut));\n rest = rest.slice(cut).trimStart();\n }\n if (rest.length > 0) {\n chunks.push(rest);\n }\n return chunks;\n}","/**\n * Twitch adapter: IRC-over-WebSocket on irc-ws.chat.twitch.tv. Handles\n * CAP/JOIN setup, tag-prefixed PRIVMSG parsing, PING/PONG, and 512-char\n * message splitting. Absent config degrades to an idle adapter.\n */\nimport { logger } from \"../util/log.js\";\nimport type { AdapterParams, PlatformAdapter, RawSocket } from \"./types.js\";\nimport { create_idle_adapter, open_socket, run_inbound_message } from \"./types.js\";\n\nexport const TWITCH_IRC_URL = \"wss://irc-ws.chat.twitch.tv:443\";\nexport const TWITCH_MESSAGE_CAP = 512;\n\ninterface TwitchConfig {\n token: string;\n nick: string;\n channels: string[];\n}\n\ninterface ParsedLine {\n kind: \"ping\" | \"privmsg\" | \"other\";\n channel: string;\n user: string;\n text: string;\n}\n\nexport function create_twitch_adapter(params: AdapterParams): PlatformAdapter {\n const twitch = read_twitch_env();\n if (twitch === undefined) {\n return create_idle_adapter(\"twitch\", \"LICH_TWITCH_OAUTH_TOKEN / NICK not set\");\n }\n let running = false;\n let socket: RawSocket | undefined;\n return {\n name: \"twitch\",\n start: async () => {\n running = true;\n void irc_loop(params, twitch, () => running, (opened) => {\n socket = opened;\n });\n },\n stop: async () => {\n running = false;\n socket?.close();\n socket = undefined;\n },\n };\n}\n\nfunction read_twitch_env(): TwitchConfig | undefined {\n const raw_token = process.env.LICH_TWITCH_OAUTH_TOKEN;\n const nick = process.env.LICH_TWITCH_NICK;\n const channels_env = process.env.LICH_TWITCH_CHANNELS;\n if (raw_token === undefined || raw_token.length === 0 || nick === undefined || nick.length === 0) {\n return undefined;\n }\n const token = raw_token.startsWith(\"oauth:\") === true ? raw_token : `oauth:${raw_token}`;\n const channels = (channels_env ?? \"\")\n .split(\",\")\n .map((channel) => channel.trim().toLowerCase())\n .filter((channel) => channel.length > 0);\n if (channels.length === 0) {\n return undefined;\n }\n return { token, nick, channels };\n}\n\nasync function irc_loop(\n params: AdapterParams,\n twitch: TwitchConfig,\n keep_running: () => boolean,\n set_socket: (socket: RawSocket) => void,\n): Promise<void> {\n while (keep_running()) {\n try {\n const socket = await open_socket(TWITCH_IRC_URL);\n set_socket(socket);\n await irc_session(params, twitch, socket, keep_running);\n } catch (error) {\n logger.warn(\"gateway twitch connection failed; reconnecting in 5s\", error);\n await new Promise((resolve) => setTimeout(resolve, 5000));\n }\n }\n}\n\nasync function irc_session(\n params: AdapterParams,\n twitch: TwitchConfig,\n socket: RawSocket,\n keep_running: () => boolean,\n): Promise<void> {\n const closed = new Promise<void>((resolve) => {\n socket.onclose = () => resolve();\n });\n socket.onmessage = (event) => {\n for (const line of String(event.data).split(\"\\r\\n\")) {\n void handle_irc_line(params, twitch, socket, line);\n }\n };\n socket.send(\"CAP REQ :twitch.tv/tags twitch.tv/commands\");\n socket.send(`PASS ${twitch.token}`);\n socket.send(`NICK ${twitch.nick}`);\n for (const channel of twitch.channels) {\n socket.send(`JOIN #${channel}`);\n }\n await closed;\n}\n\nasync function handle_irc_line(\n params: AdapterParams,\n twitch: TwitchConfig,\n socket: RawSocket,\n line: string,\n): Promise<void> {\n if (line.length === 0) {\n return;\n }\n const parsed = parse_irc_line(line);\n if (parsed.kind === \"ping\") {\n socket.send(\"PONG :tmi.twitch.tv\");\n return;\n }\n if (parsed.kind !== \"privmsg\" || parsed.user === twitch.nick) {\n return;\n }\n const reply = await run_inbound_message(\n params.handle_message,\n \"twitch\",\n parsed.channel,\n parsed.user,\n parsed.text,\n );\n send_twitch_message(socket, parsed.channel, reply);\n}\n\nfunction send_twitch_message(socket: RawSocket, channel: string, text: string): void {\n for (const chunk of split_chunks(text, TWITCH_MESSAGE_CAP)) {\n socket.send(`PRIVMSG #${channel} :${chunk}`);\n }\n}\n\nexport function parse_irc_line(line: string): ParsedLine {\n if (line.startsWith(\"PING\") === true) {\n return { kind: \"ping\", channel: \"\", user: \"\", text: \"\" };\n }\n const privmsg = match_privmsg(line);\n if (privmsg === undefined) {\n return { kind: \"other\", channel: \"\", user: \"\", text: \"\" };\n }\n return privmsg;\n}\n\nfunction match_privmsg(line: string): ParsedLine | undefined {\n const without_tags = line.startsWith(\"@\") === true ? line.slice(line.indexOf(\" \") + 1) : line;\n const body = / PRIVMSG #([\\w]+) :/.exec(without_tags);\n if (body === null || body.index < 0) {\n return undefined;\n }\n const prefix = without_tags.slice(0, body.index);\n const ident = prefix.startsWith(\":\") === true ? prefix.slice(1) : prefix;\n const user = ident.slice(ident.lastIndexOf(\"!\") + 1).split(\"@\")[0] ?? \"\";\n const channel = body[1] ?? \"\";\n const text = without_tags.slice(body.index + body[0].length);\n return { kind: \"privmsg\", channel, user, text };\n}\n\nfunction split_chunks(text: string, limit: number): string[] {\n const chunks: string[] = [];\n let rest = text;\n while (rest.length > limit) {\n chunks.push(rest.slice(0, limit));\n rest = rest.slice(limit);\n }\n if (rest.length > 0) {\n chunks.push(rest);\n }\n return chunks;\n}","/**\n * Webhook adapter: the default zero-config platform. A plain node:http\n * server exposing POST /message (+ GET /health), optionally guarded by a\n * shared token via the x-lich-token header.\n */\nimport { createServer, type IncomingMessage, type Server, type ServerResponse } from \"node:http\";\nimport { logger } from \"../util/log.js\";\nimport { format_agent_reply } from \"./format.js\";\nimport type { AdapterParams, PlatformAdapter } from \"./types.js\";\n\nexport const DEFAULT_GATEWAY_PORT = 8089;\n\ninterface WebhookAdapterParams extends AdapterParams {\n port?: number;\n /** Test hook: reports the resolved bound port once listening. */\n on_listening?: (port: number) => void;\n}\n\ninterface IncomingPayload {\n platform?: unknown;\n chat_id?: unknown;\n user_id?: unknown;\n text?: unknown;\n}\n\nexport function create_webhook_adapter(params: WebhookAdapterParams): PlatformAdapter {\n const port = params.port ?? read_port_env() ?? DEFAULT_GATEWAY_PORT;\n const token = process.env.LICH_GATEWAY_TOKEN;\n let server: Server | undefined;\n\n return {\n name: \"webhook\",\n start: async () => {\n server = createServer((request, response) => {\n void dispatch_webhook(params, request, response, token);\n });\n await listen_on(server, port, (bound) => {\n params.on_listening?.(bound);\n });\n },\n stop: async () => {\n await close_server(server);\n server = undefined;\n },\n };\n}\n\nasync function dispatch_webhook(\n params: AdapterParams,\n request: IncomingMessage,\n response: ServerResponse,\n token: string | undefined,\n): Promise<void> {\n try {\n if (request.method === \"GET\" && request.url === \"/health\") {\n send_json(response, 200, { status: \"ok\" });\n return;\n }\n if (request.method !== \"POST\" || request.url !== \"/message\") {\n send_json(response, 404, { error: \"not found\" });\n return;\n }\n if (token !== undefined && request.headers[\"x-lich-token\"] !== token) {\n send_json(response, 401, { error: \"unauthorized\" });\n return;\n }\n await handle_message_post(params, request, response);\n } catch (error) {\n logger.error(\"gateway webhook request failed\", error);\n if (response.headersSent === false) {\n send_json(response, 500, { error: \"internal error\" });\n }\n }\n}\n\nasync function handle_message_post(\n params: AdapterParams,\n request: IncomingMessage,\n response: ServerResponse,\n): Promise<void> {\n const body = await read_body(request);\n const payload = parse_payload(body);\n const text = payload.text;\n if (text === undefined) {\n send_json(response, 400, { error: \"text is required\" });\n return;\n }\n const platform = payload.platform ?? \"webhook\";\n const chat_id = payload.chat_id ?? \"default\";\n const user_id = payload.user_id ?? \"anonymous\";\n const reply = await params.handle_message(String(platform), String(chat_id), String(user_id), String(text));\n respond_json_text(response, 200, format_agent_reply(reply ?? \"\", undefined, \"webhook\"));\n}\n\nfunction read_body(request: IncomingMessage): Promise<string> {\n return new Promise((resolve, reject) => {\n let body = \"\";\n request.on(\"data\", (chunk: Buffer) => {\n body += chunk.toString(\"utf8\");\n });\n request.on(\"end\", () => resolve(body));\n request.on(\"error\", reject);\n });\n}\n\nfunction parse_payload(body: string): IncomingPayload {\n if (body.trim().length === 0) {\n return {};\n }\n try {\n const parsed = JSON.parse(body) as unknown;\n if (typeof parsed !== \"object\" || parsed === null) {\n return {};\n }\n return parsed as IncomingPayload;\n } catch {\n return {};\n }\n}\n\nfunction send_json(response: ServerResponse, status: number, payload: Record<string, unknown>): void {\n respond_json_text(response, status, JSON.stringify(payload));\n}\n\nfunction respond_json_text(response: ServerResponse, status: number, json_text: string): void {\n response.statusCode = status;\n response.setHeader(\"content-type\", \"application/json\");\n response.end(json_text);\n}\n\nfunction listen_on(server: Server, port: number, on_listening: (port: number) => void): Promise<void> {\n return new Promise((resolve, reject) => {\n server.once(\"error\", reject);\n server.listen(port, \"0.0.0.0\", () => {\n const bound = (server.address() as { port?: number } | null)?.port ?? port;\n logger.info(`gateway webhook listening on :${bound}`);\n on_listening(bound);\n resolve();\n });\n });\n}\n\nasync function close_server(server: Server | undefined): Promise<void> {\n if (server === undefined) {\n return;\n }\n await new Promise<void>((resolve) => {\n server.close(() => resolve());\n });\n}\n\nfunction read_port_env(): number | undefined {\n const raw = process.env.LICH_GATEWAY_PORT;\n if (raw === undefined || raw.length === 0) {\n return undefined;\n }\n const port = Number(raw);\n return Number.isInteger(port) && port > 0 && port < 65536 ? port : undefined;\n}","/**\n * Gateway reply formatting: per-platform shaping and whitespace-aware\n * splitting for size-capped transports (telegram 4096, discord 2000, …).\n */\nimport type { Usage } from \"../providers/types.js\";\n\nconst USAGE_FOOTER_PREFIX = \"\\n\\n_tokens: \";\n\nexport function format_agent_reply(text: string, usage: Usage | undefined, platform: string): string {\n if (platform === \"webhook\") {\n return JSON.stringify({ reply: text, usage: usage ?? null });\n }\n const total_tokens = usage?.total_tokens ?? 0;\n if (total_tokens > 0) {\n return `${text}${USAGE_FOOTER_PREFIX}${total_tokens}_`;\n }\n return text;\n}\n\n/** Splits text into chunks of at most `limit` chars, preferring whitespace. */\nexport function split_text(text: string, limit: number): string[] {\n const chunks: string[] = [];\n let rest = text;\n while (rest.length > limit) {\n const cut = find_split_point(rest, limit);\n chunks.push(rest.slice(0, cut));\n rest = rest.slice(cut).trimStart();\n }\n if (rest.length > 0) {\n chunks.push(rest);\n }\n return chunks;\n}\n\n/** Last newline or space inside the window; falls back to a hard cut. */\nfunction find_split_point(text: string, limit: number): number {\n const window = text.slice(0, limit + 1);\n const newline = window.lastIndexOf(\"\\n\");\n if (newline > 0) {\n return newline;\n }\n const space = window.lastIndexOf(\" \");\n if (space > 0) {\n return space;\n }\n return limit;\n}","/**\n * Gateway runner: wires the shared Agent, the conversation bus, and the\n * requested platform adapters, then stays alive until a signal arrives.\n */\nimport { create_agent } from \"../agent/agent.js\";\nimport type { AgentConfig } from \"../agent/config.js\";\nimport { logger } from \"../util/log.js\";\nimport { GatewayBus } from \"./bus.js\";\nimport { create_discord_adapter } from \"./discord.js\";\nimport { create_telegram_adapter } from \"./telegram.js\";\nimport { create_twitch_adapter } from \"./twitch.js\";\nimport type { AdapterParams, PlatformAdapter } from \"./types.js\";\nimport { create_webhook_adapter } from \"./webhook.js\";\n\nexport async function run_gateway(config: AgentConfig, platforms: readonly string[]): Promise<number> {\n const valid: string[] = [];\n for (const platform of platforms) {\n if (is_known_platform(platform) === true) {\n valid.push(platform);\n } else {\n process.stderr.write(`lich: unknown gateway platform \"${platform}\" (skipping)\\n`);\n }\n }\n if (valid.length === 0) {\n process.stderr.write(\"lich: gateway needs at least one valid platform (webhook|telegram|discord|twitch)\\n\");\n return 1;\n }\n const bus = new GatewayBus({ config, agent_factory: () => create_agent(config), wire_tool_logging: true });\n const adapters = build_adapters(config, bus, valid);\n install_signal_handlers(bus, adapters);\n logger.info(`gateway starting: platforms=${valid.join(\",\")}, port=${process.env.LICH_GATEWAY_PORT ?? \"8089\"}, pid=${process.pid}`);\n await start_all_adapters(adapters);\n return await new Promise<number>(() => undefined);\n}\n\nfunction is_known_platform(platform: string): boolean {\n return platform === \"webhook\" || platform === \"telegram\" || platform === \"discord\" || platform === \"twitch\";\n}\n\nfunction build_adapters(config: AgentConfig, bus: GatewayBus, platforms: readonly string[]): PlatformAdapter[] {\n const params: AdapterParams = {\n config,\n handle_message: (platform, chat_id, user_id, text) => bus.handle(platform, chat_id, user_id, text),\n get_agent: () => {\n throw new Error(\"get_agent is reserved for future use\");\n },\n reply_router: () => undefined,\n };\n const adapters: PlatformAdapter[] = [];\n for (const platform of platforms) {\n const adapter = create_platform_adapter(params, platform);\n if (adapter !== undefined) {\n adapters.push(adapter);\n }\n }\n return adapters;\n}\n\nfunction create_platform_adapter(params: AdapterParams, platform: string): PlatformAdapter | undefined {\n switch (platform) {\n case \"webhook\":\n return create_webhook_adapter({ ...params, port: read_webhook_port() });\n case \"telegram\":\n return create_telegram_adapter(params);\n case \"discord\":\n return create_discord_adapter(params);\n case \"twitch\":\n return create_twitch_adapter(params);\n default:\n return undefined;\n }\n}\n\nfunction read_webhook_port(): number | undefined {\n const raw = process.env.LICH_GATEWAY_PORT;\n if (raw === undefined || raw.length === 0) {\n return undefined;\n }\n const port = Number(raw);\n return Number.isInteger(port) && port > 0 && port < 65536 ? port : undefined;\n}\n\n/** Adapter start failures log and are skipped; the rest keep running. */\nasync function start_all_adapters(adapters: readonly PlatformAdapter[]): Promise<void> {\n for (const adapter of adapters) {\n try {\n await adapter.start();\n logger.info(`gateway adapter started: ${adapter.name}`);\n } catch (error) {\n logger.error(`gateway adapter failed to start: ${adapter.name}`, error);\n }\n }\n}\n\nfunction install_signal_handlers(bus: GatewayBus, adapters: readonly PlatformAdapter[]): void {\n const shutdown = (): void => {\n for (const adapter of adapters) {\n void adapter\n .stop()\n .catch((error: unknown) => logger.warn(`gateway adapter stop failed: ${adapter.name}`, error));\n }\n bus.stop();\n process.exit(0);\n };\n process.once(\"SIGINT\", shutdown);\n process.once(\"SIGTERM\", shutdown);\n}"],"mappings":"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","names":["split_chunks","parse_payload"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,542 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Minimal JSON Schema types for tool parameter definitions.
|
|
5
|
+
*
|
|
6
|
+
* NOTE: property names here follow the JSON Schema wire format on purpose
|
|
7
|
+
* (properties, required, additionalProperties) because these objects are
|
|
8
|
+
* serialized verbatim into LLM provider requests. Do not snake_case them.
|
|
9
|
+
*/
|
|
10
|
+
interface JsonSchemaProperty {
|
|
11
|
+
type: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
enum?: string[];
|
|
14
|
+
items?: JsonSchemaProperty;
|
|
15
|
+
properties?: Record<string, JsonSchemaProperty>;
|
|
16
|
+
required?: string[];
|
|
17
|
+
default?: unknown;
|
|
18
|
+
}
|
|
19
|
+
interface JsonSchemaObject {
|
|
20
|
+
type: "object";
|
|
21
|
+
properties?: Record<string, JsonSchemaProperty>;
|
|
22
|
+
required?: string[];
|
|
23
|
+
additionalProperties?: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface ToolResult {
|
|
27
|
+
ok: boolean;
|
|
28
|
+
output: string;
|
|
29
|
+
error?: string;
|
|
30
|
+
}
|
|
31
|
+
interface ToolContext {
|
|
32
|
+
work_dir: string;
|
|
33
|
+
env: Record<string, string>;
|
|
34
|
+
signal?: AbortSignal;
|
|
35
|
+
}
|
|
36
|
+
interface Tool {
|
|
37
|
+
name: string;
|
|
38
|
+
description: string;
|
|
39
|
+
parameters: JsonSchemaObject;
|
|
40
|
+
execute(args: Record<string, unknown>, context: ToolContext): Promise<ToolResult>;
|
|
41
|
+
}
|
|
42
|
+
interface Toolset {
|
|
43
|
+
name: string;
|
|
44
|
+
tools: Tool[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Plugin surface: user-authored tools and lifecycle hooks loaded at startup.
|
|
49
|
+
*
|
|
50
|
+
* A plugin is a plain object exported from a TS/JS module. Hooks observe and
|
|
51
|
+
* (in the case of before_tool_call) veto tool executions; tools merge into the
|
|
52
|
+
* agent registry after builtin filtering.
|
|
53
|
+
*/
|
|
54
|
+
|
|
55
|
+
/** Runtime info handed to every hook call. */
|
|
56
|
+
interface HookContext {
|
|
57
|
+
work_dir: string;
|
|
58
|
+
}
|
|
59
|
+
/** Argument passed to before_tool_call hooks. */
|
|
60
|
+
interface BeforeToolCallInfo {
|
|
61
|
+
tool_name: string;
|
|
62
|
+
args: Record<string, unknown>;
|
|
63
|
+
}
|
|
64
|
+
/** Return value that vetoes a tool call; other hooks still run. */
|
|
65
|
+
interface BeforeToolCallResult {
|
|
66
|
+
block?: boolean;
|
|
67
|
+
reason?: string;
|
|
68
|
+
}
|
|
69
|
+
/** Argument passed to after_tool_call hooks. */
|
|
70
|
+
interface AfterToolCallInfo extends BeforeToolCallInfo {
|
|
71
|
+
result_summary: string;
|
|
72
|
+
}
|
|
73
|
+
interface RunEndInfo {
|
|
74
|
+
stopped_reason: string;
|
|
75
|
+
turns_used: number;
|
|
76
|
+
}
|
|
77
|
+
/** Optional lifecycle hooks a plugin may implement. All hooks are awaited. */
|
|
78
|
+
interface PluginHooks {
|
|
79
|
+
before_tool_call?(info: BeforeToolCallInfo, ctx: HookContext): Promise<BeforeToolCallResult | void> | BeforeToolCallResult | void;
|
|
80
|
+
after_tool_call?(info: AfterToolCallInfo, ctx: HookContext): Promise<void> | void;
|
|
81
|
+
on_run_start?(info: {
|
|
82
|
+
input_chars: number;
|
|
83
|
+
}, ctx: HookContext): Promise<void> | void;
|
|
84
|
+
on_run_end?(info: RunEndInfo, ctx: HookContext): Promise<void> | void;
|
|
85
|
+
}
|
|
86
|
+
/** A user plugin: required unique name, optional tools and hooks. */
|
|
87
|
+
interface Plugin {
|
|
88
|
+
name: string;
|
|
89
|
+
version?: string;
|
|
90
|
+
tools?: Tool[];
|
|
91
|
+
hooks?: PluginHooks;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** A successfully loaded plugin plus the entry path it came from. */
|
|
95
|
+
interface LoadedPlugin {
|
|
96
|
+
plugin: Plugin;
|
|
97
|
+
entry: string;
|
|
98
|
+
}
|
|
99
|
+
/** One failed entry: the specifier and why it failed. */
|
|
100
|
+
interface PluginLoadError {
|
|
101
|
+
entry: string;
|
|
102
|
+
error_message: string;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Load plugins from explicit entry paths (relative to `base_dir` or absolute).
|
|
106
|
+
* Broken imports, missing exports, and duplicate names become error entries;
|
|
107
|
+
* nothing is thrown for a bad plugin.
|
|
108
|
+
*/
|
|
109
|
+
declare function load_plugins(entries: readonly string[], base_dir: string): Promise<{
|
|
110
|
+
plugins: LoadedPlugin[];
|
|
111
|
+
errors: PluginLoadError[];
|
|
112
|
+
}>;
|
|
113
|
+
/** Joined one-liner per load error, for a single warn log. */
|
|
114
|
+
declare function plugin_errors_summary(errors: readonly PluginLoadError[]): string;
|
|
115
|
+
|
|
116
|
+
type FinishReason = "stop" | "tool_calls" | "length" | "error" | "unknown";
|
|
117
|
+
type ProviderErrorKind = "rate_limit" | "network" | "auth" | "overflow" | "bad_request" | "unknown";
|
|
118
|
+
interface ToolCall {
|
|
119
|
+
id: string;
|
|
120
|
+
name: string;
|
|
121
|
+
args: Record<string, unknown>;
|
|
122
|
+
}
|
|
123
|
+
interface SystemMessage {
|
|
124
|
+
role: "system";
|
|
125
|
+
content: string;
|
|
126
|
+
}
|
|
127
|
+
interface UserMessage {
|
|
128
|
+
role: "user";
|
|
129
|
+
content: string;
|
|
130
|
+
}
|
|
131
|
+
interface AssistantMessage {
|
|
132
|
+
role: "assistant";
|
|
133
|
+
content: string;
|
|
134
|
+
tool_calls?: ToolCall[];
|
|
135
|
+
}
|
|
136
|
+
interface ToolMessage {
|
|
137
|
+
role: "tool";
|
|
138
|
+
tool_call_id: string;
|
|
139
|
+
name: string;
|
|
140
|
+
content: string;
|
|
141
|
+
is_error?: boolean;
|
|
142
|
+
}
|
|
143
|
+
type Message = SystemMessage | UserMessage | AssistantMessage | ToolMessage;
|
|
144
|
+
interface ToolDefinition {
|
|
145
|
+
name: string;
|
|
146
|
+
description: string;
|
|
147
|
+
parameters: JsonSchemaObject;
|
|
148
|
+
}
|
|
149
|
+
interface Usage {
|
|
150
|
+
prompt_tokens: number;
|
|
151
|
+
completion_tokens: number;
|
|
152
|
+
total_tokens: number;
|
|
153
|
+
}
|
|
154
|
+
interface ChatResult {
|
|
155
|
+
message: AssistantMessage;
|
|
156
|
+
usage: Usage;
|
|
157
|
+
finish_reason: FinishReason;
|
|
158
|
+
model: string;
|
|
159
|
+
provider_name: string;
|
|
160
|
+
}
|
|
161
|
+
interface ChatOptions {
|
|
162
|
+
temperature?: number;
|
|
163
|
+
max_tokens?: number;
|
|
164
|
+
signal?: AbortSignal;
|
|
165
|
+
/** Ollama-only: request thinking mode for this call. */
|
|
166
|
+
think?: boolean;
|
|
167
|
+
}
|
|
168
|
+
interface ProviderConfig {
|
|
169
|
+
kind: "openai_compat" | "anthropic" | "ollama";
|
|
170
|
+
name: string;
|
|
171
|
+
model: string;
|
|
172
|
+
base_url?: string;
|
|
173
|
+
api_key?: string;
|
|
174
|
+
api_key_env?: string;
|
|
175
|
+
timeout_ms?: number;
|
|
176
|
+
/** Ollama-only: request thinking mode (adds think:true to /api/chat). */
|
|
177
|
+
think?: boolean;
|
|
178
|
+
/** Ollama-only: how long the model stays loaded (e.g. "10m"). */
|
|
179
|
+
keep_alive?: string;
|
|
180
|
+
/** Injectable fetch, mainly for tests. Defaults to global fetch. */
|
|
181
|
+
fetch_fn?: typeof fetch;
|
|
182
|
+
}
|
|
183
|
+
declare class ProviderError extends Error {
|
|
184
|
+
readonly kind: ProviderErrorKind;
|
|
185
|
+
readonly provider_name: string;
|
|
186
|
+
readonly status?: number;
|
|
187
|
+
readonly retry_after_ms?: number;
|
|
188
|
+
constructor(params: {
|
|
189
|
+
kind: ProviderErrorKind;
|
|
190
|
+
provider_name: string;
|
|
191
|
+
message: string;
|
|
192
|
+
status?: number;
|
|
193
|
+
retry_after_ms?: number;
|
|
194
|
+
cause?: unknown;
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Zod-validated agent configuration. Parsing applies defaults, computes the
|
|
200
|
+
* derived session_dir, deep-freezes the result, and syncs the logger level.
|
|
201
|
+
*/
|
|
202
|
+
|
|
203
|
+
declare const agent_config_schema: z.ZodEffects<z.ZodObject<{
|
|
204
|
+
system_prompt: z.ZodOptional<z.ZodString>;
|
|
205
|
+
max_turns: z.ZodDefault<z.ZodNumber>;
|
|
206
|
+
providers: z.ZodArray<z.ZodObject<{
|
|
207
|
+
kind: z.ZodEnum<["openai_compat", "anthropic", "ollama"]>;
|
|
208
|
+
name: z.ZodString;
|
|
209
|
+
model: z.ZodString;
|
|
210
|
+
base_url: z.ZodOptional<z.ZodString>;
|
|
211
|
+
api_key: z.ZodOptional<z.ZodString>;
|
|
212
|
+
api_key_env: z.ZodOptional<z.ZodString>;
|
|
213
|
+
timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
214
|
+
/** Injectable fetch, mainly for tests; passes through untouched. */
|
|
215
|
+
fetch_fn: z.ZodOptional<z.ZodType<typeof fetch, z.ZodTypeDef, typeof fetch>>;
|
|
216
|
+
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
|
|
217
|
+
kind: z.ZodEnum<["openai_compat", "anthropic", "ollama"]>;
|
|
218
|
+
name: z.ZodString;
|
|
219
|
+
model: z.ZodString;
|
|
220
|
+
base_url: z.ZodOptional<z.ZodString>;
|
|
221
|
+
api_key: z.ZodOptional<z.ZodString>;
|
|
222
|
+
api_key_env: z.ZodOptional<z.ZodString>;
|
|
223
|
+
timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
224
|
+
/** Injectable fetch, mainly for tests; passes through untouched. */
|
|
225
|
+
fetch_fn: z.ZodOptional<z.ZodType<typeof fetch, z.ZodTypeDef, typeof fetch>>;
|
|
226
|
+
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
|
|
227
|
+
kind: z.ZodEnum<["openai_compat", "anthropic", "ollama"]>;
|
|
228
|
+
name: z.ZodString;
|
|
229
|
+
model: z.ZodString;
|
|
230
|
+
base_url: z.ZodOptional<z.ZodString>;
|
|
231
|
+
api_key: z.ZodOptional<z.ZodString>;
|
|
232
|
+
api_key_env: z.ZodOptional<z.ZodString>;
|
|
233
|
+
timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
234
|
+
/** Injectable fetch, mainly for tests; passes through untouched. */
|
|
235
|
+
fetch_fn: z.ZodOptional<z.ZodType<typeof fetch, z.ZodTypeDef, typeof fetch>>;
|
|
236
|
+
}, z.ZodTypeAny, "passthrough">>, "many">;
|
|
237
|
+
work_dir: z.ZodOptional<z.ZodString>;
|
|
238
|
+
tools_enabled: z.ZodDefault<z.ZodUnion<[z.ZodLiteral<"all">, z.ZodArray<z.ZodString, "many">]>>;
|
|
239
|
+
temperature: z.ZodOptional<z.ZodNumber>;
|
|
240
|
+
max_tokens: z.ZodOptional<z.ZodNumber>;
|
|
241
|
+
context_budget_tokens: z.ZodDefault<z.ZodNumber>;
|
|
242
|
+
compress_threshold: z.ZodDefault<z.ZodNumber>;
|
|
243
|
+
session_dir: z.ZodOptional<z.ZodString>;
|
|
244
|
+
terminal_timeout_ms: z.ZodDefault<z.ZodNumber>;
|
|
245
|
+
/** Plugin entry module specifiers, relative to work_dir or absolute. */
|
|
246
|
+
plugins: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
247
|
+
log_level: z.ZodDefault<z.ZodEnum<["debug", "info", "warn", "error"]>>;
|
|
248
|
+
}, "strip", z.ZodTypeAny, {
|
|
249
|
+
plugins: string[];
|
|
250
|
+
max_turns: number;
|
|
251
|
+
providers: z.objectOutputType<{
|
|
252
|
+
kind: z.ZodEnum<["openai_compat", "anthropic", "ollama"]>;
|
|
253
|
+
name: z.ZodString;
|
|
254
|
+
model: z.ZodString;
|
|
255
|
+
base_url: z.ZodOptional<z.ZodString>;
|
|
256
|
+
api_key: z.ZodOptional<z.ZodString>;
|
|
257
|
+
api_key_env: z.ZodOptional<z.ZodString>;
|
|
258
|
+
timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
259
|
+
/** Injectable fetch, mainly for tests; passes through untouched. */
|
|
260
|
+
fetch_fn: z.ZodOptional<z.ZodType<typeof fetch, z.ZodTypeDef, typeof fetch>>;
|
|
261
|
+
}, z.ZodTypeAny, "passthrough">[];
|
|
262
|
+
tools_enabled: string[] | "all";
|
|
263
|
+
context_budget_tokens: number;
|
|
264
|
+
compress_threshold: number;
|
|
265
|
+
terminal_timeout_ms: number;
|
|
266
|
+
log_level: "debug" | "info" | "warn" | "error";
|
|
267
|
+
temperature?: number | undefined;
|
|
268
|
+
max_tokens?: number | undefined;
|
|
269
|
+
system_prompt?: string | undefined;
|
|
270
|
+
work_dir?: string | undefined;
|
|
271
|
+
session_dir?: string | undefined;
|
|
272
|
+
}, {
|
|
273
|
+
providers: z.objectInputType<{
|
|
274
|
+
kind: z.ZodEnum<["openai_compat", "anthropic", "ollama"]>;
|
|
275
|
+
name: z.ZodString;
|
|
276
|
+
model: z.ZodString;
|
|
277
|
+
base_url: z.ZodOptional<z.ZodString>;
|
|
278
|
+
api_key: z.ZodOptional<z.ZodString>;
|
|
279
|
+
api_key_env: z.ZodOptional<z.ZodString>;
|
|
280
|
+
timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
281
|
+
/** Injectable fetch, mainly for tests; passes through untouched. */
|
|
282
|
+
fetch_fn: z.ZodOptional<z.ZodType<typeof fetch, z.ZodTypeDef, typeof fetch>>;
|
|
283
|
+
}, z.ZodTypeAny, "passthrough">[];
|
|
284
|
+
plugins?: string[] | undefined;
|
|
285
|
+
temperature?: number | undefined;
|
|
286
|
+
max_tokens?: number | undefined;
|
|
287
|
+
system_prompt?: string | undefined;
|
|
288
|
+
max_turns?: number | undefined;
|
|
289
|
+
work_dir?: string | undefined;
|
|
290
|
+
tools_enabled?: string[] | "all" | undefined;
|
|
291
|
+
context_budget_tokens?: number | undefined;
|
|
292
|
+
compress_threshold?: number | undefined;
|
|
293
|
+
session_dir?: string | undefined;
|
|
294
|
+
terminal_timeout_ms?: number | undefined;
|
|
295
|
+
log_level?: "debug" | "info" | "warn" | "error" | undefined;
|
|
296
|
+
}>, {
|
|
297
|
+
work_dir: string;
|
|
298
|
+
providers: ProviderConfig[];
|
|
299
|
+
session_dir: string;
|
|
300
|
+
plugins: string[];
|
|
301
|
+
max_turns: number;
|
|
302
|
+
tools_enabled: string[] | "all";
|
|
303
|
+
context_budget_tokens: number;
|
|
304
|
+
compress_threshold: number;
|
|
305
|
+
terminal_timeout_ms: number;
|
|
306
|
+
log_level: "debug" | "info" | "warn" | "error";
|
|
307
|
+
temperature?: number | undefined;
|
|
308
|
+
max_tokens?: number | undefined;
|
|
309
|
+
system_prompt?: string | undefined;
|
|
310
|
+
}, {
|
|
311
|
+
providers: z.objectInputType<{
|
|
312
|
+
kind: z.ZodEnum<["openai_compat", "anthropic", "ollama"]>;
|
|
313
|
+
name: z.ZodString;
|
|
314
|
+
model: z.ZodString;
|
|
315
|
+
base_url: z.ZodOptional<z.ZodString>;
|
|
316
|
+
api_key: z.ZodOptional<z.ZodString>;
|
|
317
|
+
api_key_env: z.ZodOptional<z.ZodString>;
|
|
318
|
+
timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
319
|
+
/** Injectable fetch, mainly for tests; passes through untouched. */
|
|
320
|
+
fetch_fn: z.ZodOptional<z.ZodType<typeof fetch, z.ZodTypeDef, typeof fetch>>;
|
|
321
|
+
}, z.ZodTypeAny, "passthrough">[];
|
|
322
|
+
plugins?: string[] | undefined;
|
|
323
|
+
temperature?: number | undefined;
|
|
324
|
+
max_tokens?: number | undefined;
|
|
325
|
+
system_prompt?: string | undefined;
|
|
326
|
+
max_turns?: number | undefined;
|
|
327
|
+
work_dir?: string | undefined;
|
|
328
|
+
tools_enabled?: string[] | "all" | undefined;
|
|
329
|
+
context_budget_tokens?: number | undefined;
|
|
330
|
+
compress_threshold?: number | undefined;
|
|
331
|
+
session_dir?: string | undefined;
|
|
332
|
+
terminal_timeout_ms?: number | undefined;
|
|
333
|
+
log_level?: "debug" | "info" | "warn" | "error" | undefined;
|
|
334
|
+
}>;
|
|
335
|
+
type AgentConfig = z.infer<typeof agent_config_schema>;
|
|
336
|
+
declare function parse_agent_config(raw: unknown): AgentConfig;
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Typed event emitter for the agent loop.
|
|
340
|
+
*
|
|
341
|
+
* Handlers receive a discriminated AgentEvent union. emit() iterates a
|
|
342
|
+
* snapshot of the handler set so handlers may safely unsubscribe mid-emit,
|
|
343
|
+
* and a throwing handler never breaks the loop.
|
|
344
|
+
*/
|
|
345
|
+
|
|
346
|
+
interface AgentEvents {
|
|
347
|
+
turn_start: {
|
|
348
|
+
turn: number;
|
|
349
|
+
};
|
|
350
|
+
llm_start: {
|
|
351
|
+
turn: number;
|
|
352
|
+
};
|
|
353
|
+
llm_end: {
|
|
354
|
+
turn: number;
|
|
355
|
+
result: ChatResult;
|
|
356
|
+
};
|
|
357
|
+
tool_call_start: {
|
|
358
|
+
turn: number;
|
|
359
|
+
call: ToolCall;
|
|
360
|
+
};
|
|
361
|
+
tool_call_end: {
|
|
362
|
+
turn: number;
|
|
363
|
+
call: ToolCall;
|
|
364
|
+
result: ToolResult;
|
|
365
|
+
};
|
|
366
|
+
compress_start: {
|
|
367
|
+
estimated_tokens: number;
|
|
368
|
+
};
|
|
369
|
+
compress_end: {
|
|
370
|
+
summary_chars: number;
|
|
371
|
+
};
|
|
372
|
+
turn_end: {
|
|
373
|
+
turn: number;
|
|
374
|
+
};
|
|
375
|
+
final: {
|
|
376
|
+
message: AssistantMessage;
|
|
377
|
+
result: ChatResult;
|
|
378
|
+
};
|
|
379
|
+
budget_exhausted: {
|
|
380
|
+
turns_used: number;
|
|
381
|
+
};
|
|
382
|
+
error: {
|
|
383
|
+
error: unknown;
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
type AgentEvent = {
|
|
387
|
+
[K in keyof AgentEvents]: {
|
|
388
|
+
type: K;
|
|
389
|
+
} & AgentEvents[K];
|
|
390
|
+
}[keyof AgentEvents];
|
|
391
|
+
type AgentEventHandler = (event: AgentEvent) => void;
|
|
392
|
+
declare class AgentEmitter {
|
|
393
|
+
private readonly handlers;
|
|
394
|
+
on(handler: AgentEventHandler): () => void;
|
|
395
|
+
emit(event: AgentEvent): void;
|
|
396
|
+
clear(): void;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* The Think-Act-Observe agent loop.
|
|
401
|
+
*
|
|
402
|
+
* run_conversation drives a chat model, executes requested tools, feeds
|
|
403
|
+
* results back, and compresses history when the context budget demands it.
|
|
404
|
+
* It depends only on narrow structural interfaces (ChatFn, ToolRunner) so the
|
|
405
|
+
* loop never imports provider routers or the tool executor directly.
|
|
406
|
+
*/
|
|
407
|
+
|
|
408
|
+
interface LoopParams {
|
|
409
|
+
system_prompt?: string;
|
|
410
|
+
max_turns: number;
|
|
411
|
+
temperature?: number;
|
|
412
|
+
max_tokens?: number;
|
|
413
|
+
context_budget_tokens?: number;
|
|
414
|
+
compress_threshold?: number;
|
|
415
|
+
signal?: AbortSignal;
|
|
416
|
+
}
|
|
417
|
+
interface LoopOutcome {
|
|
418
|
+
messages: Message[];
|
|
419
|
+
final: AssistantMessage | undefined;
|
|
420
|
+
result: ChatResult | undefined;
|
|
421
|
+
turns_used: number;
|
|
422
|
+
stopped_reason: "final" | "budget" | "aborted";
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
interface AgentRunOptions {
|
|
426
|
+
input: string;
|
|
427
|
+
/** Prior conversation to continue (multi-turn callers: CLI chat, gateway, TUI). */
|
|
428
|
+
history?: readonly Message[];
|
|
429
|
+
signal?: AbortSignal;
|
|
430
|
+
label?: string;
|
|
431
|
+
}
|
|
432
|
+
interface AgentRunResult {
|
|
433
|
+
outcome: LoopOutcome;
|
|
434
|
+
/** Full transcript including prior history and the new exchange. */
|
|
435
|
+
messages: Message[];
|
|
436
|
+
usage_total: Usage;
|
|
437
|
+
session_path: string | undefined;
|
|
438
|
+
}
|
|
439
|
+
declare class Agent {
|
|
440
|
+
readonly events: AgentEmitter;
|
|
441
|
+
readonly config: AgentConfig;
|
|
442
|
+
private readonly router;
|
|
443
|
+
private readonly registry;
|
|
444
|
+
private readonly executor;
|
|
445
|
+
private readonly hook_runner;
|
|
446
|
+
constructor(config: AgentConfig, plugins?: readonly LoadedPlugin[]);
|
|
447
|
+
run(options: AgentRunOptions): Promise<AgentRunResult>;
|
|
448
|
+
private loop_deps;
|
|
449
|
+
/** Best-effort on_run_start fan-out; hook errors are logged, never fatal. */
|
|
450
|
+
private call_plugin_run_start;
|
|
451
|
+
/** Best-effort on_run_end fan-out; hook errors are logged, never fatal. */
|
|
452
|
+
private call_plugin_run_end;
|
|
453
|
+
/** Best-effort JSONL transcript: never fails the run, returns undefined path on error. */
|
|
454
|
+
private persist_session;
|
|
455
|
+
}
|
|
456
|
+
declare function create_agent(raw_config: unknown): Agent;
|
|
457
|
+
/** create_agent plus plugin loading: broken entries are warned and skipped. */
|
|
458
|
+
declare function create_agent_with_plugins(raw_config: unknown): Promise<Agent>;
|
|
459
|
+
declare function run_agent(raw_config: unknown, input: string, options?: {
|
|
460
|
+
signal?: AbortSignal;
|
|
461
|
+
label?: string;
|
|
462
|
+
}): Promise<AgentRunResult>;
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Name-keyed registry of tools and toolsets. Registration rejects duplicate
|
|
466
|
+
* tool names so conflicting builtins fail loudly at startup.
|
|
467
|
+
*/
|
|
468
|
+
declare class ToolRegistry {
|
|
469
|
+
private readonly tools;
|
|
470
|
+
register(tool: Tool): void;
|
|
471
|
+
register_toolset(toolset: Toolset): void;
|
|
472
|
+
get(name: string): Tool | undefined;
|
|
473
|
+
has(name: string): boolean;
|
|
474
|
+
list(): Tool[];
|
|
475
|
+
/** Map registered tools onto the provider-facing wire shape. */
|
|
476
|
+
definitions(): ToolDefinition[];
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
interface ExecutorDefaults {
|
|
480
|
+
work_dir?: string;
|
|
481
|
+
env?: Record<string, string>;
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Executes registry tools, never throws. Applies a per-call timeout, abort
|
|
485
|
+
* propagation (external signal and timeout both abort the tool's signal),
|
|
486
|
+
* error capture, and output clamping.
|
|
487
|
+
*/
|
|
488
|
+
declare class ToolExecutor {
|
|
489
|
+
private readonly registry;
|
|
490
|
+
private readonly defaults;
|
|
491
|
+
constructor(registry: ToolRegistry, defaults?: ExecutorDefaults);
|
|
492
|
+
execute(name: string, args: Record<string, unknown>, context?: ToolContext): Promise<ToolResult>;
|
|
493
|
+
private run_tool;
|
|
494
|
+
/** Render a result for a tool-role message: JSON on error, raw output otherwise. */
|
|
495
|
+
static format_result(result: ToolResult): string;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/** Register every builtin tool onto a fresh registry (idempotent per registry). */
|
|
499
|
+
declare function register_builtin_tools(registry: ToolRegistry, context?: ToolContext): void;
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* HookedToolRunner: wraps the ToolExecutor with plugin hooks.
|
|
503
|
+
*
|
|
504
|
+
* before_tool_call hooks run in registration order and may veto a call (first
|
|
505
|
+
* blocker wins; the wrapped executor is never called). Hook errors are warned
|
|
506
|
+
* and skipped, never fatal. after_tool_call hooks observe the result summary.
|
|
507
|
+
*/
|
|
508
|
+
|
|
509
|
+
/** Structural ToolRunner shape accepted from the wrapped executor. */
|
|
510
|
+
interface WrappedToolRunner {
|
|
511
|
+
execute(name: string, args: Record<string, unknown>, context?: ToolContext): Promise<ToolResult>;
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* All hook arrays are pre-flattened at construction so the per-call hot path
|
|
515
|
+
* does no concat; hooks always run in plugin registration order.
|
|
516
|
+
*/
|
|
517
|
+
declare class HookedToolRunner {
|
|
518
|
+
private readonly wrapped;
|
|
519
|
+
private readonly before_hooks;
|
|
520
|
+
private readonly after_hooks;
|
|
521
|
+
private readonly run_start_hooks;
|
|
522
|
+
private readonly run_end_hooks;
|
|
523
|
+
constructor(wrapped: WrappedToolRunner, hooks: readonly PluginHooks[]);
|
|
524
|
+
/** Run before hooks in order; the first {block: true} verdict wins. */
|
|
525
|
+
private run_before_hooks;
|
|
526
|
+
/** Fire-and-forget in spirit but awaited here so runs settle cleanly. */
|
|
527
|
+
private run_after_hooks;
|
|
528
|
+
execute(name: string, args: Record<string, unknown>, context?: ToolContext): Promise<ToolResult>;
|
|
529
|
+
/** Best-effort on_run_start fan-out used by Agent.run; never throws. */
|
|
530
|
+
call_run_start(info: {
|
|
531
|
+
input_chars: number;
|
|
532
|
+
}, ctx: HookContext): Promise<void>;
|
|
533
|
+
/** Best-effort on_run_end fan-out used by Agent.run; never throws. */
|
|
534
|
+
call_run_end(info: RunEndInfo, ctx: HookContext): Promise<void>;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Public surface of the lich agent harness. Pure re-exports only.
|
|
539
|
+
*/
|
|
540
|
+
declare const LICH_VERSION = "0.3.0";
|
|
541
|
+
|
|
542
|
+
export { type AfterToolCallInfo, Agent, type AgentConfig, AgentEmitter, type AgentEvent, type AgentEventHandler, type AgentEvents, type AgentRunOptions, type AgentRunResult, type BeforeToolCallInfo, type BeforeToolCallResult, type ChatOptions, type ChatResult, type HookContext, HookedToolRunner, LICH_VERSION, type LoadedPlugin, type LoopOutcome, type LoopParams, type Message, type Plugin, type PluginHooks, type PluginLoadError, type ProviderConfig, ProviderError, type RunEndInfo, type Tool, type ToolCall, type ToolContext, type ToolDefinition, ToolExecutor, ToolRegistry, type ToolResult, type Usage, create_agent, create_agent_with_plugins, load_plugins, parse_agent_config, plugin_errors_summary, register_builtin_tools, run_agent };
|