import { type JSX, splitProps, createSignal, Show, For } from 'solid-js';
import { cn } from '../utils/cn';
import { Collapsible, CollapsibleTrigger, CollapsibleContent, type CollapsibleController } from '../ui/collapsible';
import { Button } from '../ui/button';
import { CheckCircle, ChevronDown, Loader2, Settings, XCircle } from 'lucide-solid';
import type { ToolPart } from './tool-types';
export type { ToolPart } from './tool-types';
export interface ToolProps {
toolPart: ToolPart;
defaultOpen?: boolean;
/** Gate the disclosure trigger (programmatic control still works). */
disabled?: boolean;
/** Receive the open controller once mounted (forwarded from the Collapsible),
* so a parent (the kai-tool facade) can drive/observe open state. */
controllerRef?: (api: CollapsibleController) => void;
class?: string;
}
function formatValue(value: unknown): string {
if (value === null) return 'null';
if (value === undefined) return 'undefined';
if (typeof value === 'string') return value;
if (typeof value === 'object') return JSON.stringify(value, null, 2);
return String(value);
}
function ToolStateIcon(props: { state: ToolPart['state'] }) {
return (
<>
>
);
}
// Status chips: a hue as text over a 15% translucent fill of the same hue
// (mirroring the inline-code chip). The TEXT color comes from a theme token
// (--color-tool-*) whose light value is darkened so it reaches WCAG AA (4.5:1)
// on the faint fill, while dark mode keeps a brighter hue for AA on the dark
// surface — both modes resolve via the token's `.dark` override. The FILL keeps
// a fixed bright hue so the chip's colored tint looks the same in both modes.
const STATE_TOKEN: Record = {
'input-streaming': 'var(--color-tool-blue)',
'input-available': 'var(--color-tool-amber)',
'output-available': 'var(--color-tool-green)',
'output-error': 'var(--color-tool-red)',
};
const STATE_FILL: Record = {
'input-streaming': 'hsl(217 91% 60%)', // blue
'input-available': 'hsl(38 92% 50%)', // amber
'output-available': 'hsl(142 71% 45%)', // green
'output-error': 'hsl(0 84% 60%)', // red
};
function stateChip(state: ToolPart['state']): JSX.CSSProperties {
return {
color: STATE_TOKEN[state],
background: `color-mix(in oklab, ${STATE_FILL[state]} 15%, transparent)`,
};
}
function ToolStateBadge(props: { state: ToolPart['state'] }) {
const baseClasses = 'px-2 py-1 rounded-full text-xs font-medium';
return (
<>
Processing
Ready
Completed
Error
>
);
}
function Tool(props: ToolProps) {
const [local, rest] = splitProps(props, ['toolPart', 'defaultOpen', 'disabled', 'controllerRef', 'class']);
// Drive the Collapsible UNCONTROLLED (seed from defaultOpen) and read its open
// state via the controller it hands up — that same controller is forwarded to
// the facade so wireDisclosure can drive show/hide/toggle.
const [api, setApi] = createSignal();
const isOpen = () => api()?.open() ?? (local.defaultOpen ?? false);
const state = () => local.toolPart.state;
const input = () => local.toolPart.input;
const output = () => local.toolPart.output;
return (