@toothfairyai/tfcode 1.0.23 → 1.0.25
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/bin/tfcode +0 -0
- package/package.json +30 -19
- package/python/tf_sync/__init__.py +21 -0
- package/python/tf_sync/__pycache__/__init__.cpython-313.pyc +0 -0
- package/python/tf_sync/__pycache__/agents.cpython-313.pyc +0 -0
- package/python/tf_sync/__pycache__/config.cpython-313.pyc +0 -0
- package/python/tf_sync/__pycache__/mcp.cpython-313.pyc +0 -0
- package/python/tf_sync/__pycache__/tools.cpython-313.pyc +0 -0
- package/python/tf_sync/agents.py +39 -0
- package/python/tf_sync/config.py +221 -0
- package/python/tf_sync/mcp.py +41 -0
- package/python/tf_sync/tools.py +282 -0
package/bin/tfcode
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,22 +1,33 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toothfairyai/tfcode",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.25",
|
|
4
4
|
"bin": {
|
|
5
|
-
|
|
6
|
-
},
|
|
7
|
-
"scripts": {
|
|
8
|
-
"postinstall": "node ./postinstall.mjs"
|
|
9
|
-
},
|
|
10
|
-
"license": "MIT",
|
|
11
|
-
"optionalDependencies": {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
|
|
21
|
-
"
|
|
22
|
-
|
|
5
|
+
"tfcode": "./bin/tfcode.js"
|
|
6
|
+
},
|
|
7
|
+
"scripts": {
|
|
8
|
+
"postinstall": "node ./postinstall.mjs"
|
|
9
|
+
},
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"optionalDependencies": {
|
|
12
|
+
"@toothfairyai/tfcode-linux-arm64": "1.0.25",
|
|
13
|
+
"@toothfairyai/tfcode-windows-x64": "1.0.25",
|
|
14
|
+
"@toothfairyai/tfcode-linux-x64-baseline-musl": "1.0.25",
|
|
15
|
+
"@toothfairyai/tfcode-darwin-x64-baseline": "1.0.25",
|
|
16
|
+
"@toothfairyai/tfcode-linux-x64-musl": "1.0.25",
|
|
17
|
+
"@toothfairyai/tfcode-windows-x64-baseline": "1.0.25",
|
|
18
|
+
"@toothfairyai/tfcode-linux-arm64-musl": "1.0.25",
|
|
19
|
+
"@toothfairyai/tfcode-windows-arm64": "1.0.25",
|
|
20
|
+
"@toothfairyai/tfcode-linux-x64": "1.0.25",
|
|
21
|
+
"@toothfairyai/tfcode-darwin-x64": "1.0.25",
|
|
22
|
+
"@toothfairyai/tfcode-linux-x64-baseline": "1.0.25",
|
|
23
|
+
"@toothfairyai/tfcode-darwin-arm64": "1.0.25"
|
|
24
|
+
},
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=18"
|
|
27
|
+
},
|
|
28
|
+
"homepage": "https://toothfairyai.com/developers/tfcode",
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "https://gitea.toothfairyai.com/ToothFairyAI/tf_code.git"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""
|
|
2
|
+
tf-sync: ToothFairyAI workspace sync layer for tfcode
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from tf_sync.agents import sync_agents
|
|
6
|
+
from tf_sync.mcp import sync_mcp_servers
|
|
7
|
+
from tf_sync.tools import sync_tools, ToolType
|
|
8
|
+
from tf_sync.config import TFConfig, load_config, validate_credentials, get_region_urls
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"sync_agents",
|
|
12
|
+
"sync_mcp_servers",
|
|
13
|
+
"sync_tools",
|
|
14
|
+
"ToolType",
|
|
15
|
+
"TFConfig",
|
|
16
|
+
"load_config",
|
|
17
|
+
"validate_credentials",
|
|
18
|
+
"get_region_urls",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
__version__ = "0.1.0"
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agent sync module for tfcode.
|
|
3
|
+
|
|
4
|
+
NOTE: This module is reserved for future implementation.
|
|
5
|
+
Currently, tfcode only syncs tools (MCP, Skills, Database, Functions).
|
|
6
|
+
Agent sync will be added in a later phase.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from pydantic import BaseModel
|
|
12
|
+
|
|
13
|
+
from tf_sync.config import TFConfig
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class AgentSyncResult(BaseModel):
|
|
17
|
+
"""Result of agent sync operation."""
|
|
18
|
+
|
|
19
|
+
success: bool
|
|
20
|
+
agents: list[dict[str, Any]] = []
|
|
21
|
+
error: str | None = None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def sync_agents(config: TFConfig) -> AgentSyncResult:
|
|
25
|
+
"""
|
|
26
|
+
Sync agents from ToothFairyAI workspace.
|
|
27
|
+
|
|
28
|
+
NOTE: Currently not implemented. Reserved for future use.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
config: TFConfig instance
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
AgentSyncResult (currently always returns not implemented)
|
|
35
|
+
"""
|
|
36
|
+
return AgentSyncResult(
|
|
37
|
+
success=False,
|
|
38
|
+
error="Agent sync not yet implemented. Use tools sync for now.",
|
|
39
|
+
)
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configuration management for tfcode ToothFairyAI integration.
|
|
3
|
+
Uses the official ToothFairyAI Python SDK for multi-region support.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
from enum import Enum
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
from pydantic import BaseModel, Field, SecretStr
|
|
11
|
+
from toothfairyai import ToothFairyClient
|
|
12
|
+
from toothfairyai.errors import ToothFairyError
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Region(str, Enum):
|
|
16
|
+
DEV = "dev"
|
|
17
|
+
AU = "au"
|
|
18
|
+
EU = "eu"
|
|
19
|
+
US = "us"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ToolType(str, Enum):
|
|
23
|
+
MCP_SERVER = "mcp_server"
|
|
24
|
+
AGENT_SKILL = "agent_skill"
|
|
25
|
+
CODER_AGENT = "coder_agent"
|
|
26
|
+
DATABASE_SCRIPT = "database_script"
|
|
27
|
+
API_FUNCTION = "api_function"
|
|
28
|
+
PROMPT = "prompt"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class FunctionRequestType(str, Enum):
|
|
32
|
+
GET = "get"
|
|
33
|
+
POST = "post"
|
|
34
|
+
PUT = "put"
|
|
35
|
+
DELETE = "delete"
|
|
36
|
+
PATCH = "patch"
|
|
37
|
+
CUSTOM = "custom"
|
|
38
|
+
GRAPHQL_QUERY = "graphql_query"
|
|
39
|
+
GRAPHQL_MUTATION = "graphql_mutation"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# Region-specific URL configurations
|
|
43
|
+
REGION_URLS = {
|
|
44
|
+
Region.DEV: {
|
|
45
|
+
"base_url": "https://api.toothfairylab.link",
|
|
46
|
+
"ai_url": "https://ai.toothfairylab.link",
|
|
47
|
+
"ai_stream_url": "https://ais.toothfairylab.link",
|
|
48
|
+
"mcp_url": "https://mcp.toothfairylab.link/sse",
|
|
49
|
+
"mcp_proxy_url": "https://mcp-proxy.toothfairylab.link",
|
|
50
|
+
},
|
|
51
|
+
Region.AU: {
|
|
52
|
+
"base_url": "https://api.toothfairyai.com",
|
|
53
|
+
"ai_url": "https://ai.toothfairyai.com",
|
|
54
|
+
"ai_stream_url": "https://ais.toothfairyai.com",
|
|
55
|
+
"mcp_url": "https://mcp.toothfairyai.com/sse",
|
|
56
|
+
"mcp_proxy_url": "https://mcp-proxy.toothfairyai.com",
|
|
57
|
+
},
|
|
58
|
+
Region.EU: {
|
|
59
|
+
"base_url": "https://api.eu.toothfairyai.com",
|
|
60
|
+
"ai_url": "https://ai.eu.toothfairyai.com",
|
|
61
|
+
"ai_stream_url": "https://ais.eu.toothfairyai.com",
|
|
62
|
+
"mcp_url": "https://mcp.eu.toothfairyai.com/sse",
|
|
63
|
+
"mcp_proxy_url": "https://mcp-proxy.eu.toothfairyai.com",
|
|
64
|
+
},
|
|
65
|
+
Region.US: {
|
|
66
|
+
"base_url": "https://api.us.toothfairyai.com",
|
|
67
|
+
"ai_url": "https://ai.us.toothfairyai.com",
|
|
68
|
+
"ai_stream_url": "https://ais.us.toothfairyai.com",
|
|
69
|
+
"mcp_url": "https://mcp.us.toothfairyai.com/sse",
|
|
70
|
+
"mcp_proxy_url": "https://mcp-proxy.us.toothfairyai.com",
|
|
71
|
+
},
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def get_region_urls(region: Region) -> dict[str, str]:
|
|
76
|
+
"""Get URLs for a specific region."""
|
|
77
|
+
return REGION_URLS.get(region, REGION_URLS[Region.AU])
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class TFConfig(BaseModel):
|
|
81
|
+
"""ToothFairyAI workspace configuration."""
|
|
82
|
+
|
|
83
|
+
workspace_id: str
|
|
84
|
+
api_key: SecretStr
|
|
85
|
+
region: Region = Region.AU
|
|
86
|
+
enabled: bool = True
|
|
87
|
+
|
|
88
|
+
sync_interval: int = Field(default=3600, ge=60)
|
|
89
|
+
mcp_proxy_timeout: int = Field(default=30000, ge=1000)
|
|
90
|
+
|
|
91
|
+
_client: Optional[ToothFairyClient] = None
|
|
92
|
+
|
|
93
|
+
def get_client(self) -> ToothFairyClient:
|
|
94
|
+
"""
|
|
95
|
+
Get or create a ToothFairyClient instance configured for this region.
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
ToothFairyClient configured with region-specific URLs
|
|
99
|
+
"""
|
|
100
|
+
if self._client is None:
|
|
101
|
+
urls = get_region_urls(self.region)
|
|
102
|
+
self._client = ToothFairyClient(
|
|
103
|
+
api_key=self.api_key.get_secret_value(),
|
|
104
|
+
workspace_id=self.workspace_id,
|
|
105
|
+
base_url=urls["base_url"],
|
|
106
|
+
ai_url=urls["ai_url"],
|
|
107
|
+
ai_stream_url=urls["ai_stream_url"],
|
|
108
|
+
)
|
|
109
|
+
return self._client
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def mcp_sse_url(self) -> str:
|
|
113
|
+
"""Get the MCP SSE endpoint URL for this region."""
|
|
114
|
+
return get_region_urls(self.region)["mcp_url"]
|
|
115
|
+
|
|
116
|
+
@property
|
|
117
|
+
def mcp_proxy_url(self) -> str:
|
|
118
|
+
"""Get the MCP proxy URL for this region."""
|
|
119
|
+
return get_region_urls(self.region)["mcp_proxy_url"]
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class CredentialValidationResult(BaseModel):
|
|
123
|
+
"""Result of credential validation."""
|
|
124
|
+
success: bool
|
|
125
|
+
workspace_id: Optional[str] = None
|
|
126
|
+
workspace_name: Optional[str] = None
|
|
127
|
+
error: Optional[str] = None
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def load_config(
|
|
131
|
+
workspace_id: Optional[str] = None,
|
|
132
|
+
api_key: Optional[str] = None,
|
|
133
|
+
region: Optional[Region] = None,
|
|
134
|
+
) -> TFConfig:
|
|
135
|
+
"""
|
|
136
|
+
Load ToothFairyAI configuration from environment or parameters.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
workspace_id: Workspace UUID (defaults to TF_WORKSPACE_ID env var)
|
|
140
|
+
api_key: API key (defaults to TF_API_KEY env var)
|
|
141
|
+
region: Region (defaults to TF_REGION env var or 'au')
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
TFConfig instance
|
|
145
|
+
|
|
146
|
+
Raises:
|
|
147
|
+
ValueError: If required configuration is missing
|
|
148
|
+
"""
|
|
149
|
+
ws_id = workspace_id or os.environ.get("TF_WORKSPACE_ID")
|
|
150
|
+
key = api_key or os.environ.get("TF_API_KEY")
|
|
151
|
+
|
|
152
|
+
# Parse region from env or use provided/default
|
|
153
|
+
region_str = os.environ.get("TF_REGION", "au")
|
|
154
|
+
reg = region or Region(region_str)
|
|
155
|
+
|
|
156
|
+
if not ws_id:
|
|
157
|
+
raise ValueError("TF_WORKSPACE_ID not set. Set environment variable or pass workspace_id.")
|
|
158
|
+
if not key:
|
|
159
|
+
raise ValueError("TF_API_KEY not set. Set environment variable or pass api_key.")
|
|
160
|
+
|
|
161
|
+
return TFConfig(
|
|
162
|
+
workspace_id=ws_id,
|
|
163
|
+
api_key=SecretStr(key),
|
|
164
|
+
region=reg,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def validate_credentials(config: TFConfig) -> CredentialValidationResult:
|
|
169
|
+
"""
|
|
170
|
+
Validate ToothFairyAI credentials using the SDK.
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
config: TFConfig instance
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
CredentialValidationResult indicating success or failure
|
|
177
|
+
"""
|
|
178
|
+
try:
|
|
179
|
+
client = config.get_client()
|
|
180
|
+
|
|
181
|
+
# Test connection by listing chats (lightweight operation)
|
|
182
|
+
if client.test_connection():
|
|
183
|
+
return CredentialValidationResult(
|
|
184
|
+
success=True,
|
|
185
|
+
workspace_id=config.workspace_id,
|
|
186
|
+
workspace_name="Connected",
|
|
187
|
+
)
|
|
188
|
+
else:
|
|
189
|
+
return CredentialValidationResult(
|
|
190
|
+
success=False,
|
|
191
|
+
error="Connection test failed. Check credentials and region.",
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
except ToothFairyError as e:
|
|
195
|
+
error_msg = str(e)
|
|
196
|
+
|
|
197
|
+
if "401" in error_msg or "Unauthorized" in error_msg:
|
|
198
|
+
return CredentialValidationResult(
|
|
199
|
+
success=False,
|
|
200
|
+
error="Invalid API key. Check TF_API_KEY environment variable.",
|
|
201
|
+
)
|
|
202
|
+
elif "403" in error_msg or "Forbidden" in error_msg:
|
|
203
|
+
return CredentialValidationResult(
|
|
204
|
+
success=False,
|
|
205
|
+
error="API access not allowed. Business or Enterprise subscription required.",
|
|
206
|
+
)
|
|
207
|
+
elif "404" in error_msg or "Not Found" in error_msg:
|
|
208
|
+
return CredentialValidationResult(
|
|
209
|
+
success=False,
|
|
210
|
+
error="Workspace not found. Check TF_WORKSPACE_ID environment variable.",
|
|
211
|
+
)
|
|
212
|
+
else:
|
|
213
|
+
return CredentialValidationResult(
|
|
214
|
+
success=False,
|
|
215
|
+
error=f"API error: {error_msg}",
|
|
216
|
+
)
|
|
217
|
+
except Exception as e:
|
|
218
|
+
return CredentialValidationResult(
|
|
219
|
+
success=False,
|
|
220
|
+
error=f"Unexpected error: {str(e)}",
|
|
221
|
+
)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCP server sync module for tfcode.
|
|
3
|
+
|
|
4
|
+
NOTE: MCP servers are not currently exposed via the ToothFairyAI SDK.
|
|
5
|
+
This module is reserved for future implementation when MCP server
|
|
6
|
+
discovery is added to the SDK.
|
|
7
|
+
|
|
8
|
+
For now, MCP servers should be configured manually via tfcode.json.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from pydantic import BaseModel
|
|
12
|
+
|
|
13
|
+
from tf_sync.config import TFConfig
|
|
14
|
+
from tf_sync.tools import SyncedTool, ToolType
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class MCPServerSyncResult(BaseModel):
|
|
18
|
+
"""Result of MCP server sync operation."""
|
|
19
|
+
|
|
20
|
+
success: bool
|
|
21
|
+
servers: list[SyncedTool] = []
|
|
22
|
+
error: str | None = None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def sync_mcp_servers(config: TFConfig) -> MCPServerSyncResult:
|
|
26
|
+
"""
|
|
27
|
+
Sync MCP servers from ToothFairyAI workspace.
|
|
28
|
+
|
|
29
|
+
NOTE: Currently not supported. MCP servers are not exposed via the SDK.
|
|
30
|
+
Configure MCP servers manually in tfcode.json instead.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
config: TFConfig instance
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
MCPServerSyncResult with error message
|
|
37
|
+
"""
|
|
38
|
+
return MCPServerSyncResult(
|
|
39
|
+
success=False,
|
|
40
|
+
error="MCP server sync not available via SDK. Configure MCP servers in tfcode.json.",
|
|
41
|
+
)
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tool sync module for tfcode.
|
|
3
|
+
Syncs tools from ToothFairyAI workspace using the official SDK.
|
|
4
|
+
|
|
5
|
+
SDK Structure:
|
|
6
|
+
- agent_functions: API Functions (with request_type)
|
|
7
|
+
- connections: Provider connections (openai, anthropic, etc.)
|
|
8
|
+
- agents: TF workspace agents
|
|
9
|
+
- prompts: Prompt templates (with available_to_agents mapping)
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from typing import Any, Optional, List
|
|
13
|
+
|
|
14
|
+
from pydantic import BaseModel
|
|
15
|
+
from toothfairyai.types import AgentFunction
|
|
16
|
+
|
|
17
|
+
from tf_sync.config import TFConfig, ToolType, FunctionRequestType
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class SyncedTool(BaseModel):
|
|
21
|
+
"""A tool synced from ToothFairyAI workspace."""
|
|
22
|
+
|
|
23
|
+
id: str
|
|
24
|
+
name: str
|
|
25
|
+
description: Optional[str] = None
|
|
26
|
+
tool_type: ToolType
|
|
27
|
+
|
|
28
|
+
is_mcp_server: bool = False
|
|
29
|
+
is_agent_skill: bool = False
|
|
30
|
+
is_database_script: bool = False
|
|
31
|
+
|
|
32
|
+
request_type: Optional[FunctionRequestType] = None
|
|
33
|
+
url: Optional[str] = None
|
|
34
|
+
tools: list[str] = []
|
|
35
|
+
|
|
36
|
+
authorisation_type: Optional[str] = None
|
|
37
|
+
|
|
38
|
+
auth_via: str = "tf_proxy"
|
|
39
|
+
|
|
40
|
+
# Coder agent specific fields for prompting/model configuration
|
|
41
|
+
interpolation_string: Optional[str] = None
|
|
42
|
+
goals: Optional[str] = None
|
|
43
|
+
temperature: Optional[float] = None
|
|
44
|
+
max_tokens: Optional[int] = None
|
|
45
|
+
llm_base_model: Optional[str] = None
|
|
46
|
+
llm_provider: Optional[str] = None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class SyncedPrompt(BaseModel):
|
|
50
|
+
"""A prompt template synced from ToothFairyAI workspace."""
|
|
51
|
+
|
|
52
|
+
id: str
|
|
53
|
+
label: str
|
|
54
|
+
interpolation_string: str
|
|
55
|
+
prompt_type: Optional[str] = None
|
|
56
|
+
available_to_agents: Optional[List[str]] = None
|
|
57
|
+
description: Optional[str] = None
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class ToolSyncResult(BaseModel):
|
|
61
|
+
"""Result of tool sync operation."""
|
|
62
|
+
|
|
63
|
+
success: bool
|
|
64
|
+
tools: list[SyncedTool] = []
|
|
65
|
+
prompts: list[SyncedPrompt] = []
|
|
66
|
+
by_type: dict[str, int] = {}
|
|
67
|
+
error: Optional[str] = None
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def classify_tool(func: AgentFunction) -> ToolType:
|
|
71
|
+
"""
|
|
72
|
+
Classify a tool based on its properties.
|
|
73
|
+
|
|
74
|
+
Types:
|
|
75
|
+
- AGENT_SKILL: is_agent_skill=True
|
|
76
|
+
- API_FUNCTION: has request_type
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
func: AgentFunction from TF SDK
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
ToolType enum value
|
|
83
|
+
"""
|
|
84
|
+
# Agent skills have is_agent_skill=True
|
|
85
|
+
if getattr(func, 'is_agent_skill', None) is True:
|
|
86
|
+
return ToolType.AGENT_SKILL
|
|
87
|
+
|
|
88
|
+
# All agent_functions with request_type are API Functions
|
|
89
|
+
if func.request_type:
|
|
90
|
+
return ToolType.API_FUNCTION
|
|
91
|
+
|
|
92
|
+
return ToolType.API_FUNCTION
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def parse_function(func: AgentFunction) -> SyncedTool:
|
|
96
|
+
"""
|
|
97
|
+
Parse AgentFunction from SDK into SyncedTool.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
func: AgentFunction from TF SDK
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
SyncedTool instance
|
|
104
|
+
"""
|
|
105
|
+
tool_type = classify_tool(func)
|
|
106
|
+
|
|
107
|
+
request_type_enum = None
|
|
108
|
+
if func.request_type:
|
|
109
|
+
try:
|
|
110
|
+
request_type_enum = FunctionRequestType(func.request_type)
|
|
111
|
+
except ValueError:
|
|
112
|
+
pass
|
|
113
|
+
|
|
114
|
+
# API Functions may have user-provided auth (authorisation_type)
|
|
115
|
+
# or may use TF proxy
|
|
116
|
+
auth_via = "user_provided" if func.authorisation_type == "api_key" else "tf_proxy"
|
|
117
|
+
|
|
118
|
+
# Agent skills use skill script
|
|
119
|
+
if tool_type == ToolType.AGENT_SKILL:
|
|
120
|
+
auth_via = "tf_skill"
|
|
121
|
+
|
|
122
|
+
return SyncedTool(
|
|
123
|
+
id=func.id,
|
|
124
|
+
name=func.name,
|
|
125
|
+
description=func.description,
|
|
126
|
+
tool_type=tool_type,
|
|
127
|
+
request_type=request_type_enum,
|
|
128
|
+
url=func.url,
|
|
129
|
+
authorisation_type=func.authorisation_type,
|
|
130
|
+
auth_via=auth_via,
|
|
131
|
+
is_agent_skill=tool_type == ToolType.AGENT_SKILL,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def parse_agent(agent) -> SyncedTool:
|
|
136
|
+
"""
|
|
137
|
+
Parse Agent from SDK into SyncedTool.
|
|
138
|
+
|
|
139
|
+
Coder agents (mode='coder') are CODER_AGENT type, not skills.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
agent: Agent from TF SDK
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
SyncedTool instance with full agent configuration
|
|
146
|
+
"""
|
|
147
|
+
return SyncedTool(
|
|
148
|
+
id=agent.id,
|
|
149
|
+
name=agent.label or f"agent_{agent.id[:8]}",
|
|
150
|
+
description=agent.description,
|
|
151
|
+
tool_type=ToolType.CODER_AGENT,
|
|
152
|
+
is_agent_skill=False,
|
|
153
|
+
auth_via="tf_agent",
|
|
154
|
+
# Agent prompting configuration
|
|
155
|
+
interpolation_string=getattr(agent, 'interpolation_string', None),
|
|
156
|
+
goals=getattr(agent, 'goals', None),
|
|
157
|
+
# Agent model configuration
|
|
158
|
+
temperature=getattr(agent, 'temperature', None),
|
|
159
|
+
max_tokens=getattr(agent, 'max_tokens', None),
|
|
160
|
+
llm_base_model=getattr(agent, 'llm_base_model', None),
|
|
161
|
+
llm_provider=getattr(agent, 'llm_provider', None),
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def parse_prompt(prompt) -> SyncedPrompt:
|
|
166
|
+
"""
|
|
167
|
+
Parse Prompt from SDK into SyncedPrompt.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
prompt: Prompt from TF SDK
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
SyncedPrompt instance
|
|
174
|
+
"""
|
|
175
|
+
return SyncedPrompt(
|
|
176
|
+
id=prompt.id,
|
|
177
|
+
label=prompt.label,
|
|
178
|
+
interpolation_string=prompt.interpolation_string,
|
|
179
|
+
prompt_type=getattr(prompt, 'prompt_type', None),
|
|
180
|
+
available_to_agents=getattr(prompt, 'available_to_agents', None),
|
|
181
|
+
description=getattr(prompt, 'description', None),
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def sync_tools(config: TFConfig) -> ToolSyncResult:
|
|
186
|
+
"""
|
|
187
|
+
Sync all tools from ToothFairyAI workspace using SDK.
|
|
188
|
+
|
|
189
|
+
Includes:
|
|
190
|
+
- Agent Functions (API Functions with request_type)
|
|
191
|
+
- Agent Skills (functions with is_agent_skill=True)
|
|
192
|
+
- Coder Agents (agents with mode='coder')
|
|
193
|
+
- Prompts (prompt templates with available_to_agents mapping)
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
config: TFConfig instance
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
ToolSyncResult with synced tools and prompts
|
|
200
|
+
"""
|
|
201
|
+
try:
|
|
202
|
+
client = config.get_client()
|
|
203
|
+
|
|
204
|
+
# Sync agent functions (API auto-paginates up to 5000)
|
|
205
|
+
func_result = client.agent_functions.list()
|
|
206
|
+
tools = [parse_function(f) for f in func_result.items]
|
|
207
|
+
|
|
208
|
+
# Sync coder agents (API auto-paginates up to 5000)
|
|
209
|
+
try:
|
|
210
|
+
agents_result = client.agents.list()
|
|
211
|
+
for agent in agents_result.items:
|
|
212
|
+
if getattr(agent, 'mode', None) == 'coder':
|
|
213
|
+
tools.append(parse_agent(agent))
|
|
214
|
+
except Exception:
|
|
215
|
+
pass
|
|
216
|
+
|
|
217
|
+
# Sync prompts (API auto-paginates up to 5000)
|
|
218
|
+
prompts = []
|
|
219
|
+
try:
|
|
220
|
+
prompts_result = client.prompts.list()
|
|
221
|
+
prompts = [parse_prompt(p) for p in prompts_result.items]
|
|
222
|
+
except Exception:
|
|
223
|
+
pass
|
|
224
|
+
|
|
225
|
+
by_type = {}
|
|
226
|
+
for tool in tools:
|
|
227
|
+
type_name = tool.tool_type.value
|
|
228
|
+
by_type[type_name] = by_type.get(type_name, 0) + 1
|
|
229
|
+
|
|
230
|
+
if prompts:
|
|
231
|
+
by_type['prompt'] = len(prompts)
|
|
232
|
+
|
|
233
|
+
return ToolSyncResult(
|
|
234
|
+
success=True,
|
|
235
|
+
tools=tools,
|
|
236
|
+
prompts=prompts,
|
|
237
|
+
by_type=by_type,
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
except Exception as e:
|
|
241
|
+
return ToolSyncResult(
|
|
242
|
+
success=False,
|
|
243
|
+
error=f"Sync failed: {str(e)}",
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def sync_tools_by_type(
|
|
248
|
+
config: TFConfig,
|
|
249
|
+
tool_types: Optional[list[ToolType]] = None,
|
|
250
|
+
) -> ToolSyncResult:
|
|
251
|
+
"""
|
|
252
|
+
Sync tools of specific types from ToothFairyAI workspace.
|
|
253
|
+
|
|
254
|
+
Args:
|
|
255
|
+
config: TFConfig instance
|
|
256
|
+
tool_types: List of ToolType to sync (None = all)
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
ToolSyncResult with filtered tools
|
|
260
|
+
"""
|
|
261
|
+
result = sync_tools(config)
|
|
262
|
+
|
|
263
|
+
if not result.success or not tool_types:
|
|
264
|
+
return result
|
|
265
|
+
|
|
266
|
+
filtered = [t for t in result.tools if t.tool_type in tool_types]
|
|
267
|
+
|
|
268
|
+
by_type = {}
|
|
269
|
+
for tool in filtered:
|
|
270
|
+
type_name = tool.tool_type.value
|
|
271
|
+
by_type[type_name] = by_type.get(type_name, 0) + 1
|
|
272
|
+
|
|
273
|
+
return ToolSyncResult(
|
|
274
|
+
success=True,
|
|
275
|
+
tools=filtered,
|
|
276
|
+
by_type=by_type,
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def sync_api_functions_only(config: TFConfig) -> ToolSyncResult:
|
|
281
|
+
"""Sync only API Functions (has requestType)."""
|
|
282
|
+
return sync_tools_by_type(config, [ToolType.API_FUNCTION])
|