mirror of
https://gitea.toothfairyai.com/ToothFairyAI/tf_code.git
synced 2026-03-29 21:33:54 +00:00
feat: new integration
This commit is contained in:
parent
0f62ba8dd5
commit
8e05565e84
@ -25,6 +25,7 @@ class ToolType(str, Enum):
|
|||||||
CODER_AGENT = "coder_agent"
|
CODER_AGENT = "coder_agent"
|
||||||
DATABASE_SCRIPT = "database_script"
|
DATABASE_SCRIPT = "database_script"
|
||||||
API_FUNCTION = "api_function"
|
API_FUNCTION = "api_function"
|
||||||
|
PROMPT = "prompt"
|
||||||
|
|
||||||
|
|
||||||
class FunctionRequestType(str, Enum):
|
class FunctionRequestType(str, Enum):
|
||||||
|
|||||||
@ -6,9 +6,10 @@ SDK Structure:
|
|||||||
- agent_functions: API Functions (with request_type)
|
- agent_functions: API Functions (with request_type)
|
||||||
- connections: Provider connections (openai, anthropic, etc.)
|
- connections: Provider connections (openai, anthropic, etc.)
|
||||||
- agents: TF workspace agents
|
- 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 pydantic import BaseModel
|
||||||
from toothfairyai.types import AgentFunction
|
from toothfairyai.types import AgentFunction
|
||||||
@ -45,11 +46,23 @@ class SyncedTool(BaseModel):
|
|||||||
llm_provider: Optional[str] = None
|
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):
|
class ToolSyncResult(BaseModel):
|
||||||
"""Result of tool sync operation."""
|
"""Result of tool sync operation."""
|
||||||
|
|
||||||
success: bool
|
success: bool
|
||||||
tools: list[SyncedTool] = []
|
tools: list[SyncedTool] = []
|
||||||
|
prompts: list[SyncedPrompt] = []
|
||||||
by_type: dict[str, int] = {}
|
by_type: dict[str, int] = {}
|
||||||
error: Optional[str] = None
|
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:
|
def sync_tools(config: TFConfig) -> ToolSyncResult:
|
||||||
"""
|
"""
|
||||||
Sync all tools from ToothFairyAI workspace using SDK.
|
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 Functions (API Functions with request_type)
|
||||||
- Agent Skills (functions with is_agent_skill=True)
|
- Agent Skills (functions with is_agent_skill=True)
|
||||||
- Coder Agents (agents with mode='coder')
|
- Coder Agents (agents with mode='coder')
|
||||||
|
- Prompts (prompt templates with available_to_agents mapping)
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
config: TFConfig instance
|
config: TFConfig instance
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
ToolSyncResult with synced tools
|
ToolSyncResult with synced tools and prompts
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
client = config.get_client()
|
client = config.get_client()
|
||||||
@ -180,14 +214,26 @@ def sync_tools(config: TFConfig) -> ToolSyncResult:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
pass
|
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 = {}
|
by_type = {}
|
||||||
for tool in tools:
|
for tool in tools:
|
||||||
type_name = tool.tool_type.value
|
type_name = tool.tool_type.value
|
||||||
by_type[type_name] = by_type.get(type_name, 0) + 1
|
by_type[type_name] = by_type.get(type_name, 0) + 1
|
||||||
|
|
||||||
|
if prompts:
|
||||||
|
by_type['prompt'] = len(prompts)
|
||||||
|
|
||||||
return ToolSyncResult(
|
return ToolSyncResult(
|
||||||
success=True,
|
success=True,
|
||||||
tools=tools,
|
tools=tools,
|
||||||
|
prompts=prompts,
|
||||||
by_type=by_type,
|
by_type=by_type,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -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() {
|
export async function list() {
|
||||||
const cfg = await Config.get()
|
const cfg = await Config.get()
|
||||||
const localAgents = await state()
|
const localAgents = await state()
|
||||||
|
|||||||
@ -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 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)
|
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 {
|
interface SyncedTool {
|
||||||
id: string
|
id: string
|
||||||
@ -28,11 +28,27 @@ interface SyncedTool {
|
|||||||
url?: string
|
url?: string
|
||||||
tools: string[]
|
tools: string[]
|
||||||
auth_via: 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 {
|
interface ToolSyncResult {
|
||||||
success: boolean
|
success: boolean
|
||||||
tools: SyncedTool[]
|
tools: SyncedTool[]
|
||||||
|
prompts: SyncedPrompt[]
|
||||||
by_type: Record<string, number>
|
by_type: Record<string, number>
|
||||||
error?: string
|
error?: string
|
||||||
}
|
}
|
||||||
@ -101,12 +117,30 @@ try:
|
|||||||
"request_type": tool.request_type.value if tool.request_type else None,
|
"request_type": tool.request_type.value if tool.request_type else None,
|
||||||
"url": tool.url,
|
"url": tool.url,
|
||||||
"tools": tool.tools,
|
"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({
|
print(json.dumps({
|
||||||
"success": result.success,
|
"success": result.success,
|
||||||
"tools": tools_data,
|
"tools": tools_data,
|
||||||
|
"prompts": prompts_data,
|
||||||
"by_type": result.by_type,
|
"by_type": result.by_type,
|
||||||
"error": result.error
|
"error": result.error
|
||||||
}))
|
}))
|
||||||
@ -138,7 +172,13 @@ try:
|
|||||||
"request_type": tool.request_type.value if tool.request_type else None,
|
"request_type": tool.request_type.value if tool.request_type else None,
|
||||||
"url": tool.url,
|
"url": tool.url,
|
||||||
"tools": tool.tools,
|
"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({
|
print(json.dumps({
|
||||||
@ -353,6 +393,8 @@ const ToolsListCommand = cmd({
|
|||||||
agent_skill: "Skill",
|
agent_skill: "Skill",
|
||||||
database_script: "DB",
|
database_script: "DB",
|
||||||
api_function: "API",
|
api_function: "API",
|
||||||
|
coder_agent: "Coder Agent",
|
||||||
|
prompt: "Prompt",
|
||||||
}[tool.tool_type]
|
}[tool.tool_type]
|
||||||
|
|
||||||
info(` ${tool.name}`)
|
info(` ${tool.name}`)
|
||||||
|
|||||||
@ -13,6 +13,8 @@ import { useTerminalDimensions } from "@opentui/solid"
|
|||||||
import { Locale } from "@/util/locale"
|
import { Locale } from "@/util/locale"
|
||||||
import type { PromptInfo } from "./history"
|
import type { PromptInfo } from "./history"
|
||||||
import { useFrecency } from "./frecency"
|
import { useFrecency } from "./frecency"
|
||||||
|
import { Agent } from "@/agent/agent"
|
||||||
|
import { useLocal } from "@tui/context/local"
|
||||||
|
|
||||||
function removeLineRange(input: string) {
|
function removeLineRange(input: string) {
|
||||||
const hashIndex = input.lastIndexOf("#")
|
const hashIndex = input.lastIndexOf("#")
|
||||||
@ -77,6 +79,7 @@ export function Autocomplete(props: {
|
|||||||
}) {
|
}) {
|
||||||
const sdk = useSDK()
|
const sdk = useSDK()
|
||||||
const sync = useSync()
|
const sync = useSync()
|
||||||
|
const local = useLocal()
|
||||||
const command = useCommandDialog()
|
const command = useCommandDialog()
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const dimensions = useTerminalDimensions()
|
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 commands = createMemo((): AutocompleteOption[] => {
|
||||||
const results: AutocompleteOption[] = [...command.slashes()]
|
const results: AutocompleteOption[] = [...command.slashes()]
|
||||||
|
|
||||||
@ -386,9 +425,12 @@ export function Autocomplete(props: {
|
|||||||
const filesValue = files()
|
const filesValue = files()
|
||||||
const agentsValue = agents()
|
const agentsValue = agents()
|
||||||
const commandsValue = commands()
|
const commandsValue = commands()
|
||||||
|
const promptsValue = tfPrompts()
|
||||||
|
|
||||||
const mixed: AutocompleteOption[] =
|
const mixed: AutocompleteOption[] =
|
||||||
store.visible === "@" ? [...agentsValue, ...(filesValue || []), ...mcpResources()] : [...commandsValue]
|
store.visible === "@"
|
||||||
|
? [...agentsValue, ...promptsValue, ...(filesValue || []), ...mcpResources()]
|
||||||
|
: [...commandsValue]
|
||||||
|
|
||||||
const searchValue = search()
|
const searchValue = search()
|
||||||
|
|
||||||
|
|||||||
@ -29,6 +29,8 @@ import { batch, onMount } from "solid-js"
|
|||||||
import { Log } from "@/util/log"
|
import { Log } from "@/util/log"
|
||||||
import type { Path } from "@opencode-ai/sdk"
|
import type { Path } from "@opencode-ai/sdk"
|
||||||
import type { Workspace } from "@opencode-ai/sdk/v2"
|
import type { Workspace } from "@opencode-ai/sdk/v2"
|
||||||
|
import path from "path"
|
||||||
|
import os from "os"
|
||||||
|
|
||||||
export const { use: useSync, provider: SyncProvider } = createSimpleContext({
|
export const { use: useSync, provider: SyncProvider } = createSimpleContext({
|
||||||
name: "Sync",
|
name: "Sync",
|
||||||
@ -75,6 +77,12 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
|
|||||||
vcs: VcsInfo | undefined
|
vcs: VcsInfo | undefined
|
||||||
path: Path
|
path: Path
|
||||||
workspaceList: Workspace[]
|
workspaceList: Workspace[]
|
||||||
|
prompts: Array<{
|
||||||
|
id: string
|
||||||
|
label: string
|
||||||
|
interpolation_string: string
|
||||||
|
available_to_agents?: string[]
|
||||||
|
}>
|
||||||
}>({
|
}>({
|
||||||
provider_next: {
|
provider_next: {
|
||||||
all: [],
|
all: [],
|
||||||
@ -103,6 +111,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
|
|||||||
vcs: undefined,
|
vcs: undefined,
|
||||||
path: { state: "", config: "", worktree: "", directory: "" },
|
path: { state: "", config: "", worktree: "", directory: "" },
|
||||||
workspaceList: [],
|
workspaceList: [],
|
||||||
|
prompts: [],
|
||||||
})
|
})
|
||||||
|
|
||||||
const sdk = useSDK()
|
const sdk = useSDK()
|
||||||
@ -113,6 +122,19 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
|
|||||||
setStore("workspaceList", reconcile(result.data))
|
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) => {
|
sdk.event.listen((e) => {
|
||||||
const event = e.details
|
const event = e.details
|
||||||
switch (event.type) {
|
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.vcs.get().then((x) => setStore("vcs", reconcile(x.data))),
|
||||||
sdk.client.path.get().then((x) => setStore("path", reconcile(x.data!))),
|
sdk.client.path.get().then((x) => setStore("path", reconcile(x.data!))),
|
||||||
syncWorkspaces(),
|
syncWorkspaces(),
|
||||||
|
loadTFPrompts(),
|
||||||
]).then(() => {
|
]).then(() => {
|
||||||
setStore("status", "complete")
|
setStore("status", "complete")
|
||||||
})
|
})
|
||||||
|
|||||||
@ -70,7 +70,7 @@ export namespace LLM {
|
|||||||
const system: string[] = []
|
const system: string[] = []
|
||||||
|
|
||||||
// Build highlighted agent instructions for ToothFairyAI agents
|
// Build highlighted agent instructions for ToothFairyAI agents
|
||||||
const tfHighlightedInstructions = buildTFAgentInstructions(input.agent)
|
const tfHighlightedInstructions = await buildTFAgentInstructions(input.agent)
|
||||||
|
|
||||||
system.push(
|
system.push(
|
||||||
[
|
[
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user