<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:fh="http://purl.org/syndication/history/1.0"><channel><title>Candela | Blog</title><description/><link>https://www.candelahq.com/</link><language>en</language><fh:complete/><atom:link rel="self" href="https://www.candelahq.com/blog/rss.xml"/><item><title>Building an OpenCode Plugin from Scratch — Hooks, Tools, and the AI Event Loop</title><link>https://www.candelahq.com/blog/building-opencode-plugin/</link><guid isPermaLink="true">https://www.candelahq.com/blog/building-opencode-plugin/</guid><description>A technical walkthrough of OpenCode&apos;s plugin SDK. How hooks, tools, events, and system prompt injection actually work — with real code from an open-source plugin.</description><pubDate>Sun, 09 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;a href=&quot;https://opencode.ai&quot;&gt;OpenCode&lt;/a&gt; is an open-source AI coding agent that runs in your terminal. It has a plugin SDK that lets you hook into the AI lifecycle — intercept requests, inject context, define custom tools, and listen to session events.&lt;/p&gt;
&lt;p&gt;I built an OpenCode plugin for &lt;a href=&quot;https://github.com/candelahq/candela&quot;&gt;Candela&lt;/a&gt; (an LLM cost tracking proxy). This post walks through the SDK patterns I used, with real code from the &lt;a href=&quot;https://github.com/candelahq/opencode-candela&quot;&gt;open-source plugin&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-plugin-contract&quot;&gt;The Plugin Contract&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;An OpenCode plugin is a function that receives a context object and returns hooks:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; { Plugin } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;@opencode-ai/plugin&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export const &lt;/span&gt;&lt;span&gt;myPlugin&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Plugin&lt;/span&gt;&lt;span&gt; = async &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;{ &lt;/span&gt;&lt;span&gt;client&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;$&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;directory&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; =&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;// client — log messages, read/write config&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;// $ — Bun shell for subprocess execution&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;// directory — current working directory&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;return {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;// hooks go here&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code dir=&quot;auto&quot;&gt;PluginInput&lt;/code&gt; gives you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;client&lt;/code&gt; — OpenCode’s API client (logging, config read/write)&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;$&lt;/code&gt; — Bun shell for running commands&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;directory&lt;/code&gt; / &lt;code dir=&quot;auto&quot;&gt;worktree&lt;/code&gt; — filesystem context&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;serverUrl&lt;/code&gt; — OpenCode’s server URL&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What you return is a &lt;code dir=&quot;auto&quot;&gt;Hooks&lt;/code&gt; object. This is where the interesting stuff happens.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;hook-1-custom-tools&quot;&gt;Hook #1: Custom Tools&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;The most powerful hook. You define tools that the AI agent can call conversationally — the user says “how much have I spent today?” and the AI calls your tool to find out.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { tool } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;@opencode-ai/plugin&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;costSummary&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;tool&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;description: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Get a summary of LLM costs for the current session or time period&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;args: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;scope: &lt;/span&gt;&lt;span&gt;tool&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;schema&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;enum&lt;/span&gt;&lt;span&gt;([&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;session&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;1h&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;24h&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;7d&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;])&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;default&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;24h&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;describe&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Time scope for the cost query&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;model_filter: &lt;/span&gt;&lt;span&gt;tool&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;schema&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;optional&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;describe&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Filter results to a specific model&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;async &lt;/span&gt;&lt;span&gt;execute&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;args&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;context&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;// context gives you: sessionID, messageID, abort signal, directory&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;const&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;fetchCostData&lt;/span&gt;&lt;span&gt;(args&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;scope&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;return &lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;You&apos;ve spent &lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;totalCost&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; across &lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;requestCount&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; requests.&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Key details:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;tool.schema&lt;/code&gt; is built on Zod — you get type-safe argument parsing&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;.describe()&lt;/code&gt; annotations help the LLM choose the right arguments&lt;/li&gt;
&lt;li&gt;The return value is a string that goes back into the AI’s context&lt;/li&gt;
&lt;li&gt;&lt;code dir=&quot;auto&quot;&gt;context.abort&lt;/code&gt; gives you an &lt;code dir=&quot;auto&quot;&gt;AbortSignal&lt;/code&gt; for cancellation&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To register tools, return them from your plugin:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;tool: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;my_cost_tool: costSummary,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;my_config_tool: configTool,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;The AI discovers these tools automatically and can call them when relevant.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;hook-2-events&quot;&gt;Hook #2: Events&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;OpenCode emits lifecycle events. You listen with the &lt;code dir=&quot;auto&quot;&gt;event&lt;/code&gt; hook:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;event&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;{ &lt;/span&gt;&lt;span&gt;event&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (event&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;session.created&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;// A new chat session started&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;// Good for: capturing baseline metrics, resetting state&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (event&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;session.idle&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;// The AI finished responding, waiting for user input&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;// Good for: showing summaries, logging analytics&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (event&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;file.watcher.updated&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;// A file in the project changed&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;filePath&lt;/span&gt;&lt;span&gt; = &lt;/span&gt;&lt;span&gt;event&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;properties&lt;/span&gt;&lt;span&gt;?.&lt;/span&gt;&lt;span&gt;file&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (event&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;===&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;todo.updated&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;// A task/subtask was created or completed&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;const { &lt;/span&gt;&lt;span&gt;id&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;title&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;active&lt;/span&gt;&lt;span&gt; } = &lt;/span&gt;&lt;span&gt;event&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;properties&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;In the Candela plugin, I use &lt;code dir=&quot;auto&quot;&gt;session.created&lt;/code&gt; to capture a baseline of spend metrics, and &lt;code dir=&quot;auto&quot;&gt;session.idle&lt;/code&gt; to compute the delta — giving you exact per-session costs without any server-side session indexing.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;hook-3-system-prompt-injection&quot;&gt;Hook #3: System Prompt Injection&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;This is the hook that makes AI tools “cost-aware.” You can dynamically inject context into the system prompt:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;experimental.chat.system.transform&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;input&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;output&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;budget&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;getBudgetStatus&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;output&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;system&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;push&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;[Cost Context] Budget: &lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;budget&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;percentUsed&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;% used. &lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;Remaining: $&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;budget&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;remaining&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;. &lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;Resets in &lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;budget&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;resetLabel&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;);&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Now when the user asks “am I over budget?”, the AI already knows — it’s in the system prompt.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Design decision I learned the hard way:&lt;/strong&gt; Don’t inject on every message. System prompt tokens add up. In Candela, I only inject on the first prompt of each session when budget is healthy (&amp;#x3C;80%). When budget hits ≥80%, I inject on every prompt so the AI can proactively warn the user.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;hook-4-request-headers&quot;&gt;Hook #4: Request Headers&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;You can attach custom headers to every LLM API request:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;chat.headers&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;input&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;output&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (sessionId) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;output&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;headers&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;X-Session-Id&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; sessionId;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt; (activeTaskId) {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;output&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;headers&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;X-Task-Id&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; activeTaskId;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This is how Candela traces requests back to specific sessions and tasks — the proxy reads these headers and includes them in the cost log.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;hook-5-shell-environment&quot;&gt;Hook #5: Shell Environment&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Inject environment variables into every subprocess OpenCode runs:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;shell.env&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&lt;span&gt;input&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span&gt;output&lt;/span&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;output&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;env&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;CANDELA_PROXY_URL&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;http://localhost:8181&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;output&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;env&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;OPENAI_BASE_URL&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;http://localhost:8181/proxy/openai/v1&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;This means when the AI runs a test suite or a build script, those subprocesses automatically route through the proxy too. Transparent instrumentation.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;hook-6-tui-slash-commands&quot;&gt;Hook #6: TUI (Slash Commands)&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;OpenCode plugins can also export a TUI module for terminal UI features:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; { TuiPlugin } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;@opencode-ai/plugin/tui&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export const &lt;/span&gt;&lt;span&gt;tui&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;TuiPlugin&lt;/span&gt;&lt;span&gt; = async &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;api&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; =&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;  &lt;/span&gt;&lt;span&gt;api&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;command&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;register&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; =&gt;&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;title: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Candela: Cost Summary&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;value: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;candela.cost&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;slash: { name: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;cost&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;, aliases: [&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;spend&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;] },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;      &lt;/span&gt;&lt;span&gt;onSelect&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span&gt;async&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;=&gt;&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;fetchCosts&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;api&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;ui&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;toast&lt;/span&gt;&lt;span&gt;({&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;          &lt;/span&gt;&lt;/span&gt;&lt;span&gt;title: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;💰 Cost Summary&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;          &lt;/span&gt;&lt;/span&gt;&lt;span&gt;message: &lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;Today: $&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;today&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt; · This week: $&lt;/span&gt;&lt;span&gt;${&lt;/span&gt;&lt;span&gt;data&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;week&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;`&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;          &lt;/span&gt;&lt;/span&gt;&lt;span&gt;variant: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;info&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;});&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;])&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Slash commands show up when users type &lt;code dir=&quot;auto&quot;&gt;/&lt;/code&gt; in the chat. Toasts are non-blocking notifications.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Architecture note:&lt;/strong&gt; OpenCode separates server hooks and TUI hooks into different execution contexts. Your &lt;code dir=&quot;auto&quot;&gt;package.json&lt;/code&gt; exports both from the same package, but they run on different threads.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;putting-it-together&quot;&gt;Putting It Together&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;Here’s the minimal skeleton of a real plugin:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;src/index.ts&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; { Plugin } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;@opencode-ai/plugin&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;import&lt;/span&gt;&lt;span&gt; { tool } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;@opencode-ai/plugin&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export const &lt;/span&gt;&lt;span&gt;server&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Plugin&lt;/span&gt;&lt;span&gt; = async &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;{ &lt;/span&gt;&lt;span&gt;client&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; =&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;const &lt;/span&gt;&lt;span&gt;alive&lt;/span&gt;&lt;span&gt; = await &lt;/span&gt;&lt;span&gt;checkMyService&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;if &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;!&lt;/span&gt;&lt;span&gt;alive)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;await &lt;/span&gt;&lt;span&gt;client&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;app&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;log&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;body: {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;service: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;my-plugin&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;level: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;warn&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;message: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Service not running. Start it with: my-service start&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;return {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;tool: &lt;/span&gt;&lt;span&gt;alive&lt;/span&gt;&lt;span&gt; ? {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;my_tool: &lt;/span&gt;&lt;span&gt;tool&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;description: &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;Query my service&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;args: { query: &lt;/span&gt;&lt;span&gt;tool&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;schema&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;string&lt;/span&gt;&lt;span&gt;()&lt;/span&gt;&lt;span&gt; },&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;async &lt;/span&gt;&lt;span&gt;execute&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;args&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;          &lt;/span&gt;&lt;/span&gt;&lt;span&gt;return await &lt;/span&gt;&lt;span&gt;queryService&lt;/span&gt;&lt;span&gt;(args&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;query&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;        &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;} : {},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;    &lt;/span&gt;&lt;span&gt;event&lt;/span&gt;&lt;span&gt;: async &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;{ &lt;/span&gt;&lt;span&gt;event&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; =&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;if &lt;/span&gt;&lt;span&gt;(event&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; === &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;session.created&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;// setup&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;if &lt;/span&gt;&lt;span&gt;(event&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span&gt;type&lt;/span&gt;&lt;span&gt; === &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;session.idle&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;        &lt;/span&gt;&lt;span&gt;// teardown / summary&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;      &lt;/span&gt;&lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;    &lt;/span&gt;&lt;/span&gt;&lt;span&gt;},&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;&lt;span&gt;};&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;}&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;
&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;// Re-export TUI&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;export&lt;/span&gt;&lt;span&gt; { tui } &lt;/span&gt;&lt;span&gt;from&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;./tui.js&lt;/span&gt;&lt;span&gt;&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;Install with:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;opencode&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;plugin&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;my-plugin-name&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;div&gt;&lt;h2 id=&quot;what-id-do-differently&quot;&gt;What I’d Do Differently&lt;/h2&gt;&lt;/div&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Start with one tool, not seven.&lt;/strong&gt; I built 10 tools before any user tried the plugin. Consolidated to 7 after realizing the AI gets confused with too many similar options.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Test with fake timers.&lt;/strong&gt; Every time-dependent feature (cache TTL, session duration, budget resets) needs &lt;code dir=&quot;auto&quot;&gt;vi.useFakeTimers()&lt;/code&gt; in tests. I learned this after flaky CI runs.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Graceful degradation matters.&lt;/strong&gt; When the external service is down, the plugin should still load — just with reduced functionality. Returning &lt;code dir=&quot;auto&quot;&gt;undefined&lt;/code&gt; for hooks is fine.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div&gt;&lt;h2 id=&quot;resources&quot;&gt;Resources&lt;/h2&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://opencode.ai&quot;&gt;OpenCode&lt;/a&gt; — the AI coding agent&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.npmjs.com/package/@opencode-ai/plugin&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;@opencode-ai/plugin&lt;/code&gt;&lt;/a&gt; — the SDK&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/candelahq/opencode-candela&quot;&gt;opencode-candela&lt;/a&gt; — the full plugin source (MIT)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://candelahq.github.io/candela-docs/&quot;&gt;Candela docs&lt;/a&gt; — the observability platform&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Building an OpenCode plugin? I’d love to see what you make. Drop a link in the comments.&lt;/em&gt;&lt;/p&gt;
</content:encoded><category>ai</category><category>typescript</category><category>opensource</category><category>tutorial</category></item><item><title>The AI Cost Problem Nobody&apos;s Tracking</title><link>https://www.candelahq.com/blog/introducing-candela/</link><guid isPermaLink="true">https://www.candelahq.com/blog/introducing-candela/</guid><description>Every major AI coding tool hides your costs. Here&apos;s why that matters and what you can do about it.</description><pubDate>Sun, 09 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Open Claude Code. Open Cursor. Open Windsurf. Open Copilot.&lt;/p&gt;
&lt;p&gt;Now tell me: how much did your last coding session cost?&lt;/p&gt;
&lt;p&gt;You can’t. None of them show you.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-visibility-gap&quot;&gt;The Visibility Gap&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;AI coding tools have a strange relationship with money. They charge you — sometimes per-seat, sometimes per-token, sometimes through your API key — but they never show you what individual sessions, requests, or tasks actually cost.&lt;/p&gt;
&lt;p&gt;Here’s what the pricing pages tell you:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Input (per 1M tokens)&lt;/th&gt;
&lt;th&gt;Output (per 1M tokens)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Claude Sonnet 4&lt;/td&gt;
&lt;td&gt;$3.00&lt;/td&gt;
&lt;td&gt;$15.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPT-4.1&lt;/td&gt;
&lt;td&gt;$2.00&lt;/td&gt;
&lt;td&gt;$8.00&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemini 2.5 Pro&lt;/td&gt;
&lt;td&gt;$1.25&lt;/td&gt;
&lt;td&gt;$10.00&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Here’s what they don’t tell you: how many tokens a typical coding session actually uses. A file read is ~1K tokens. A grep across a codebase is ~5K. A 10-round debug loop with full context replay can hit 200K+. You’re multiplying these prices by numbers you can’t see.&lt;/p&gt;
&lt;p&gt;The result: monthly invoices that are either a relief or a surprise, with no way to understand what drove them.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;why-this-matters&quot;&gt;Why This Matters&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;For solo developers, it’s an annoyance. For teams, it’s a budget problem.&lt;/p&gt;
&lt;p&gt;Consider:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;No per-session attribution.&lt;/strong&gt; You can’t tell which task or feature drove a cost spike.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No per-user visibility.&lt;/strong&gt; Engineering managers see a total bill, not per-engineer spend.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No budget enforcement.&lt;/strong&gt; There’s no way to set a daily limit and have the tool respect it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No model efficiency data.&lt;/strong&gt; Are you using Opus for tasks where Haiku would suffice? You’ll never know.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cloud computing went through this exact phase. Early AWS bills were a mystery. Then tools like CloudWatch, Cost Explorer, and third-party observability platforms matured. Teams got visibility, set budgets, and optimized.&lt;/p&gt;
&lt;p&gt;LLM spend is where cloud spend was in 2010. The tools don’t exist yet.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;building-the-missing-layer&quot;&gt;Building the Missing Layer&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;That’s why we’re building &lt;a href=&quot;https://github.com/candelahq/candela&quot;&gt;Candela&lt;/a&gt; — an open-source LLM observability proxy.&lt;/p&gt;
&lt;p&gt;The idea is simple: Candela sits between your AI tool and your LLM provider. Every request passes through, gets logged with cost/latency/token data, and continues to its destination. No code changes, no SDK integration, no vendor lock-in.&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;AI Tool  →  Candela Proxy  →  LLM Provider&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;                 &lt;/span&gt;&lt;/span&gt;&lt;span&gt;↓&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;          &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Cost per request&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;          &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Token counts&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;          &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Cache hit rates&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;          &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Model breakdown&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;&lt;span&gt;          &lt;/span&gt;&lt;/span&gt;&lt;span&gt;Budget enforcement&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;It works with anything that speaks the OpenAI-compatible API format — which is most AI coding tools today.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;what-you-get&quot;&gt;What You Get&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Per-request cost tracking.&lt;/strong&gt; Every API call is logged with input tokens, output tokens, model, latency, and computed cost. Not estimated — computed from the actual token counts × the model’s pricing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Budget enforcement.&lt;/strong&gt; Set a daily spending limit. When you hit it, Candela can warn you or block requests. No more surprise invoices.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Model analytics.&lt;/strong&gt; See which models you’re using, how often, and what they cost. Spot the $15/day Claude Opus habit when Sonnet would work.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cache monitoring.&lt;/strong&gt; Prompt caching can cut costs 50-90% — but only if you know your hit rate. Candela tracks cache read/write tokens per request.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;the-opencode-plugin&quot;&gt;The OpenCode Plugin&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;We built a plugin that surfaces Candela data directly in &lt;a href=&quot;https://opencode.ai&quot;&gt;OpenCode&lt;/a&gt;:&lt;/p&gt;
&lt;div&gt;&lt;figure&gt;&lt;figcaption&gt;&lt;span&gt;&lt;/span&gt;&lt;/figcaption&gt;&lt;pre&gt;&lt;code&gt;&lt;div&gt;&lt;div&gt;&lt;span&gt;opencode&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;plugin&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;@candelahq/opencode&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;&lt;/div&gt;
&lt;p&gt;It gives you 7 AI tools (so you can ask “how much have I spent today?” in natural language) and 13 slash commands (&lt;code dir=&quot;auto&quot;&gt;/cost&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;/budget&lt;/code&gt;, &lt;code dir=&quot;auto&quot;&gt;/export&lt;/code&gt;, etc.).&lt;/p&gt;
&lt;p&gt;The plugin is open source: &lt;a href=&quot;https://www.npmjs.com/package/@candelahq/opencode&quot;&gt;&lt;code dir=&quot;auto&quot;&gt;@candelahq/opencode&lt;/code&gt;&lt;/a&gt; on npm.&lt;/p&gt;
&lt;div&gt;&lt;h2 id=&quot;get-involved&quot;&gt;Get Involved&lt;/h2&gt;&lt;/div&gt;
&lt;p&gt;If you’re using AI coding tools professionally, you should know what they cost. Candela is early, open source, and free.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;⭐ &lt;a href=&quot;https://github.com/candelahq/candela&quot;&gt;Star the repo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;📖 &lt;a href=&quot;https://candelahq.github.io/candela-docs/&quot;&gt;Read the docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;💬 Open an issue if you want support for your tool/workflow&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>ai</category><category>cost-tracking</category><category>opensource</category><category>devops</category></item></channel></rss>