feat: new integration

This commit is contained in:
Gab 2026-03-27 09:11:17 +11:00
parent 0f62ba8dd5
commit 8e05565e84
7 changed files with 192 additions and 7 deletions

View File

@ -25,6 +25,7 @@ class ToolType(str, Enum):
CODER_AGENT = "coder_agent"
DATABASE_SCRIPT = "database_script"
API_FUNCTION = "api_function"
PROMPT = "prompt"
class FunctionRequestType(str, Enum):

View File

@ -6,9 +6,10 @@ SDK Structure:
- agent_functions: API Functions (with request_type)
- connections: Provider connections (openai, anthropic, etc.)
- agents: TF workspace agents
- prompts: Prompt templates (with available_to_agents mapping)
"""
from typing import Any, Optional
from typing import Any, Optional, List
from pydantic import BaseModel
from toothfairyai.types import AgentFunction
@ -45,11 +46,23 @@ class SyncedTool(BaseModel):
llm_provider: Optional[str] = None
class SyncedPrompt(BaseModel):
"""A prompt template synced from ToothFairyAI workspace."""
id: str
label: str
interpolation_string: str
prompt_type: Optional[str] = None
available_to_agents: Optional[List[str]] = None
description: Optional[str] = None
class ToolSyncResult(BaseModel):
"""Result of tool sync operation."""
success: bool
tools: list[SyncedTool] = []
prompts: list[SyncedPrompt] = []
by_type: dict[str, int] = {}
error: Optional[str] = None
@ -149,6 +162,26 @@ def parse_agent(agent) -> SyncedTool:
)
def parse_prompt(prompt) -> SyncedPrompt:
"""
Parse Prompt from SDK into SyncedPrompt.
Args:
prompt: Prompt from TF SDK
Returns:
SyncedPrompt instance
"""
return SyncedPrompt(
id=prompt.id,
label=prompt.label,
interpolation_string=prompt.interpolation_string,
prompt_type=getattr(prompt, 'prompt_type', None),
available_to_agents=getattr(prompt, 'available_to_agents', None),
description=getattr(prompt, 'description', None),
)
def sync_tools(config: TFConfig) -> ToolSyncResult:
"""
Sync all tools from ToothFairyAI workspace using SDK.
@ -157,12 +190,13 @@ def sync_tools(config: TFConfig) -> ToolSyncResult:
- Agent Functions (API Functions with request_type)
- Agent Skills (functions with is_agent_skill=True)
- Coder Agents (agents with mode='coder')
- Prompts (prompt templates with available_to_agents mapping)
Args:
config: TFConfig instance
Returns:
ToolSyncResult with synced tools
ToolSyncResult with synced tools and prompts
"""
try:
client = config.get_client()
@ -180,14 +214,26 @@ def sync_tools(config: TFConfig) -> ToolSyncResult:
except Exception as e:
pass
# Sync prompts
prompts = []
try:
prompts_result = client.prompts.list()
prompts = [parse_prompt(p) for p in prompts_result.items]
except Exception as e:
pass
by_type = {}
for tool in tools:
type_name = tool.tool_type.value
by_type[type_name] = by_type.get(type_name, 0) + 1
if prompts:
by_type['prompt'] = len(prompts)
return ToolSyncResult(
success=True,
tools=tools,
prompts=prompts,
by_type=by_type,
)

View File

@ -306,6 +306,37 @@ export namespace Agent {
}
}
export interface TFPrompt {
id: string
label: string
interpolation_string: string
available_to_agents?: string[]
}
async function loadTFPrompts(): Promise<TFPrompt[]> {
const toolsPath = path.join(os.homedir(), ".tfcode", "tools.json")
try {
const content = await Bun.file(toolsPath).text()
const data = JSON.parse(content)
if (!data.success || !data.prompts) return []
return data.prompts as TFPrompt[]
} catch {
return []
}
}
export async function getPromptForAgent(agentId: string): Promise<TFPrompt | null> {
const prompts = await loadTFPrompts()
return prompts.find((p) => p.available_to_agents?.includes(agentId)) ?? null
}
export async function getPromptForAgentName(agentName: string): Promise<TFPrompt | null> {
const agents = await loadTFCoderAgents()
const agent = agents.find((a) => a.name === agentName)
if (!agent?.options?.tf_agent_id) return null
return getPromptForAgent(agent.options.tf_agent_id)
}
export async function list() {
const cfg = await Config.get()
const localAgents = await state()

View File

@ -14,7 +14,7 @@ const printError = (msg: string) => UI.error(msg)
const success = (msg: string) => UI.println(UI.Style.TEXT_SUCCESS_BOLD + msg + UI.Style.TEXT_NORMAL)
const info = (msg: string) => UI.println(UI.Style.TEXT_NORMAL + msg)
type ToolType = "mcp_server" | "agent_skill" | "database_script" | "api_function"
type ToolType = "mcp_server" | "agent_skill" | "database_script" | "api_function" | "coder_agent" | "prompt"
interface SyncedTool {
id: string
@ -28,11 +28,27 @@ interface SyncedTool {
url?: string
tools: string[]
auth_via: string
interpolation_string?: string
goals?: string
temperature?: number
max_tokens?: number
llm_base_model?: string
llm_provider?: string
}
interface SyncedPrompt {
id: string
label: string
interpolation_string: string
prompt_type?: string
available_to_agents?: string[]
description?: string
}
interface ToolSyncResult {
success: boolean
tools: SyncedTool[]
prompts: SyncedPrompt[]
by_type: Record<string, number>
error?: string
}
@ -101,12 +117,30 @@ try:
"request_type": tool.request_type.value if tool.request_type else None,
"url": tool.url,
"tools": tool.tools,
"auth_via": tool.auth_via
"auth_via": tool.auth_via,
"interpolation_string": tool.interpolation_string,
"goals": tool.goals,
"temperature": tool.temperature,
"max_tokens": tool.max_tokens,
"llm_base_model": tool.llm_base_model,
"llm_provider": tool.llm_provider
})
prompts_data = []
for prompt in result.prompts:
prompts_data.append({
"id": prompt.id,
"label": prompt.label,
"interpolation_string": prompt.interpolation_string,
"prompt_type": prompt.prompt_type,
"available_to_agents": prompt.available_to_agents,
"description": prompt.description
})
print(json.dumps({
"success": result.success,
"tools": tools_data,
"prompts": prompts_data,
"by_type": result.by_type,
"error": result.error
}))
@ -138,7 +172,13 @@ try:
"request_type": tool.request_type.value if tool.request_type else None,
"url": tool.url,
"tools": tool.tools,
"auth_via": tool.auth_via
"auth_via": tool.auth_via,
"interpolation_string": tool.interpolation_string,
"goals": tool.goals,
"temperature": tool.temperature,
"max_tokens": tool.max_tokens,
"llm_base_model": tool.llm_base_model,
"llm_provider": tool.llm_provider
})
print(json.dumps({
@ -353,6 +393,8 @@ const ToolsListCommand = cmd({
agent_skill: "Skill",
database_script: "DB",
api_function: "API",
coder_agent: "Coder Agent",
prompt: "Prompt",
}[tool.tool_type]
info(` ${tool.name}`)

View File

@ -13,6 +13,8 @@ import { useTerminalDimensions } from "@opentui/solid"
import { Locale } from "@/util/locale"
import type { PromptInfo } from "./history"
import { useFrecency } from "./frecency"
import { Agent } from "@/agent/agent"
import { useLocal } from "@tui/context/local"
function removeLineRange(input: string) {
const hashIndex = input.lastIndexOf("#")
@ -77,6 +79,7 @@ export function Autocomplete(props: {
}) {
const sdk = useSDK()
const sync = useSync()
const local = useLocal()
const command = useCommandDialog()
const { theme } = useTheme()
const dimensions = useTerminalDimensions()
@ -353,6 +356,42 @@ export function Autocomplete(props: {
)
})
const tfPrompts = createMemo(() => {
if (!store.visible || store.visible === "/") return []
const currentAgent = local.agent.current()
const agentId = currentAgent.options?.tf_agent_id as string | undefined
if (!agentId) return []
const options: AutocompleteOption[] = []
const width = props.anchor().width - 4
const prompts = sync.data.prompts || []
for (const prompt of prompts) {
const isAvailable =
!prompt.available_to_agents ||
prompt.available_to_agents.length === 0 ||
prompt.available_to_agents.includes(agentId)
if (isAvailable) {
options.push({
display: Locale.truncateMiddle("@" + prompt.label, width),
value: prompt.label,
description: "Prompt template",
onSelect: () => {
const cursor = props.input().logicalCursor
props.input().deleteRange(0, 0, cursor.row, cursor.col)
props.input().insertText(prompt.interpolation_string)
props.input().cursorOffset = Bun.stringWidth(prompt.interpolation_string)
},
})
}
}
return options
})
const commands = createMemo((): AutocompleteOption[] => {
const results: AutocompleteOption[] = [...command.slashes()]
@ -386,9 +425,12 @@ export function Autocomplete(props: {
const filesValue = files()
const agentsValue = agents()
const commandsValue = commands()
const promptsValue = tfPrompts()
const mixed: AutocompleteOption[] =
store.visible === "@" ? [...agentsValue, ...(filesValue || []), ...mcpResources()] : [...commandsValue]
store.visible === "@"
? [...agentsValue, ...promptsValue, ...(filesValue || []), ...mcpResources()]
: [...commandsValue]
const searchValue = search()

View File

@ -29,6 +29,8 @@ import { batch, onMount } from "solid-js"
import { Log } from "@/util/log"
import type { Path } from "@opencode-ai/sdk"
import type { Workspace } from "@opencode-ai/sdk/v2"
import path from "path"
import os from "os"
export const { use: useSync, provider: SyncProvider } = createSimpleContext({
name: "Sync",
@ -75,6 +77,12 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
vcs: VcsInfo | undefined
path: Path
workspaceList: Workspace[]
prompts: Array<{
id: string
label: string
interpolation_string: string
available_to_agents?: string[]
}>
}>({
provider_next: {
all: [],
@ -103,6 +111,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
vcs: undefined,
path: { state: "", config: "", worktree: "", directory: "" },
workspaceList: [],
prompts: [],
})
const sdk = useSDK()
@ -113,6 +122,19 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
setStore("workspaceList", reconcile(result.data))
}
async function loadTFPrompts() {
const toolsPath = path.join(os.homedir(), ".tfcode", "tools.json")
try {
const content = await Bun.file(toolsPath).text()
const data = JSON.parse(content)
if (data.success && data.prompts) {
setStore("prompts", reconcile(data.prompts))
}
} catch {
// File doesn't exist or is invalid, that's OK
}
}
sdk.event.listen((e) => {
const event = e.details
switch (event.type) {
@ -423,6 +445,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
sdk.client.vcs.get().then((x) => setStore("vcs", reconcile(x.data))),
sdk.client.path.get().then((x) => setStore("path", reconcile(x.data!))),
syncWorkspaces(),
loadTFPrompts(),
]).then(() => {
setStore("status", "complete")
})

View File

@ -70,7 +70,7 @@ export namespace LLM {
const system: string[] = []
// Build highlighted agent instructions for ToothFairyAI agents
const tfHighlightedInstructions = buildTFAgentInstructions(input.agent)
const tfHighlightedInstructions = await buildTFAgentInstructions(input.agent)
system.push(
[