mirror of
https://gitea.toothfairyai.com/ToothFairyAI/tf_code.git
synced 2026-03-29 21:33:54 +00:00
feat: agents and skills
This commit is contained in:
parent
485cc7649e
commit
ff77a81141
Binary file not shown.
@ -50,8 +50,9 @@ def classify_tool(func: AgentFunction) -> ToolType:
|
|||||||
"""
|
"""
|
||||||
Classify a tool based on its properties.
|
Classify a tool based on its properties.
|
||||||
|
|
||||||
Currently the SDK exposes:
|
Types:
|
||||||
- agent_functions: API functions with request_type
|
- AGENT_SKILL: is_agent_skill=True
|
||||||
|
- API_FUNCTION: has request_type
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
func: AgentFunction from TF SDK
|
func: AgentFunction from TF SDK
|
||||||
@ -59,6 +60,10 @@ def classify_tool(func: AgentFunction) -> ToolType:
|
|||||||
Returns:
|
Returns:
|
||||||
ToolType enum value
|
ToolType enum value
|
||||||
"""
|
"""
|
||||||
|
# Agent skills have is_agent_skill=True
|
||||||
|
if getattr(func, 'is_agent_skill', None) is True:
|
||||||
|
return ToolType.AGENT_SKILL
|
||||||
|
|
||||||
# All agent_functions with request_type are API Functions
|
# All agent_functions with request_type are API Functions
|
||||||
if func.request_type:
|
if func.request_type:
|
||||||
return ToolType.API_FUNCTION
|
return ToolType.API_FUNCTION
|
||||||
@ -89,6 +94,10 @@ def parse_function(func: AgentFunction) -> SyncedTool:
|
|||||||
# or may use TF proxy
|
# or may use TF proxy
|
||||||
auth_via = "user_provided" if func.authorisation_type == "api_key" else "tf_proxy"
|
auth_via = "user_provided" if func.authorisation_type == "api_key" else "tf_proxy"
|
||||||
|
|
||||||
|
# Agent skills use skill script
|
||||||
|
if tool_type == ToolType.AGENT_SKILL:
|
||||||
|
auth_via = "tf_skill"
|
||||||
|
|
||||||
return SyncedTool(
|
return SyncedTool(
|
||||||
id=func.id,
|
id=func.id,
|
||||||
name=func.name,
|
name=func.name,
|
||||||
@ -98,6 +107,7 @@ def parse_function(func: AgentFunction) -> SyncedTool:
|
|||||||
url=func.url,
|
url=func.url,
|
||||||
authorisation_type=func.authorisation_type,
|
authorisation_type=func.authorisation_type,
|
||||||
auth_via=auth_via,
|
auth_via=auth_via,
|
||||||
|
is_agent_skill=tool_type == ToolType.AGENT_SKILL,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -126,7 +136,8 @@ def sync_tools(config: TFConfig) -> ToolSyncResult:
|
|||||||
Sync all tools from ToothFairyAI workspace using SDK.
|
Sync all tools from ToothFairyAI workspace using SDK.
|
||||||
|
|
||||||
Includes:
|
Includes:
|
||||||
- Agent Functions (API Functions)
|
- Agent Functions (API Functions with request_type)
|
||||||
|
- Agent Skills (functions with is_agent_skill=True)
|
||||||
- Coder Agents (agents with mode='coder')
|
- Coder Agents (agents with mode='coder')
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -138,7 +149,7 @@ def sync_tools(config: TFConfig) -> ToolSyncResult:
|
|||||||
try:
|
try:
|
||||||
client = config.get_client()
|
client = config.get_client()
|
||||||
|
|
||||||
# Sync agent functions
|
# Sync agent functions (includes API Functions and Agent Skills)
|
||||||
func_result = client.agent_functions.list()
|
func_result = client.agent_functions.list()
|
||||||
tools = [parse_function(f) for f in func_result.items]
|
tools = [parse_function(f) for f in func_result.items]
|
||||||
|
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import { DialogMcp } from "@tui/component/dialog-mcp"
|
|||||||
import { DialogStatus } from "@tui/component/dialog-status"
|
import { DialogStatus } from "@tui/component/dialog-status"
|
||||||
import { DialogThemeList } from "@tui/component/dialog-theme-list"
|
import { DialogThemeList } from "@tui/component/dialog-theme-list"
|
||||||
import { DialogHelp } from "./ui/dialog-help"
|
import { DialogHelp } from "./ui/dialog-help"
|
||||||
|
import { DialogChangelog } from "./ui/dialog-changelog"
|
||||||
import { CommandProvider, useCommandDialog } from "@tui/component/dialog-command"
|
import { CommandProvider, useCommandDialog } from "@tui/component/dialog-command"
|
||||||
import { DialogAgent } from "@tui/component/dialog-agent"
|
import { DialogAgent } from "@tui/component/dialog-agent"
|
||||||
import { DialogSessionList } from "@tui/component/dialog-session-list"
|
import { DialogSessionList } from "@tui/component/dialog-session-list"
|
||||||
@ -576,11 +577,22 @@ function App() {
|
|||||||
},
|
},
|
||||||
category: "System",
|
category: "System",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "Changelog",
|
||||||
|
value: "changelog.show",
|
||||||
|
slash: {
|
||||||
|
name: "changelog",
|
||||||
|
},
|
||||||
|
onSelect: () => {
|
||||||
|
dialog.replace(() => <DialogChangelog />)
|
||||||
|
},
|
||||||
|
category: "System",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: "Open docs",
|
title: "Open docs",
|
||||||
value: "docs.open",
|
value: "docs.open",
|
||||||
onSelect: () => {
|
onSelect: () => {
|
||||||
open("https://opencode.ai/docs").catch(() => {})
|
open("https://toothfairyai.com/developers/tfcode").catch(() => {})
|
||||||
dialog.clear()
|
dialog.clear()
|
||||||
},
|
},
|
||||||
category: "System",
|
category: "System",
|
||||||
|
|||||||
@ -15,12 +15,13 @@ import { Clipboard } from "@tui/util/clipboard"
|
|||||||
import { useToast } from "../ui/toast"
|
import { useToast } from "../ui/toast"
|
||||||
|
|
||||||
const PROVIDER_PRIORITY: Record<string, number> = {
|
const PROVIDER_PRIORITY: Record<string, number> = {
|
||||||
opencode: 0,
|
toothfairyai: 0,
|
||||||
"opencode-go": 1,
|
opencode: 1,
|
||||||
openai: 2,
|
"opencode-go": 2,
|
||||||
"github-copilot": 3,
|
openai: 3,
|
||||||
anthropic: 4,
|
"github-copilot": 4,
|
||||||
google: 5,
|
anthropic: 5,
|
||||||
|
google: 6,
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createDialogProviderOptions() {
|
export function createDialogProviderOptions() {
|
||||||
@ -36,6 +37,7 @@ export function createDialogProviderOptions() {
|
|||||||
title: provider.name,
|
title: provider.name,
|
||||||
value: provider.id,
|
value: provider.id,
|
||||||
description: {
|
description: {
|
||||||
|
toothfairyai: "(Recommended - ToothFairyAI)",
|
||||||
opencode: "(Recommended)",
|
opencode: "(Recommended)",
|
||||||
anthropic: "(API key)",
|
anthropic: "(API key)",
|
||||||
openai: "(ChatGPT Plus/Pro or API key)",
|
openai: "(ChatGPT Plus/Pro or API key)",
|
||||||
@ -237,6 +239,19 @@ function ApiMethod(props: ApiMethodProps) {
|
|||||||
placeholder="API key"
|
placeholder="API key"
|
||||||
description={
|
description={
|
||||||
{
|
{
|
||||||
|
toothfairyai: (
|
||||||
|
<box gap={1}>
|
||||||
|
<text fg={theme.textMuted}>
|
||||||
|
ToothFairyAI gives you access to AI coding models through your workspace credentials.
|
||||||
|
</text>
|
||||||
|
<text fg={theme.text}>
|
||||||
|
Set credentials via <span style={{ fg: theme.primary }}>tfcode setup</span> or environment variables
|
||||||
|
</text>
|
||||||
|
<text fg={theme.textMuted}>
|
||||||
|
TF_WORKSPACE_ID, TF_API_KEY, TF_REGION (dev/au/eu/us)
|
||||||
|
</text>
|
||||||
|
</box>
|
||||||
|
),
|
||||||
opencode: (
|
opencode: (
|
||||||
<box gap={1}>
|
<box gap={1}>
|
||||||
<text fg={theme.textMuted}>
|
<text fg={theme.textMuted}>
|
||||||
|
|||||||
@ -67,7 +67,7 @@ const TIPS = [
|
|||||||
"Press {highlight}Ctrl+X X{/highlight} or {highlight}/export{/highlight} to save the conversation as Markdown",
|
"Press {highlight}Ctrl+X X{/highlight} or {highlight}/export{/highlight} to save the conversation as Markdown",
|
||||||
"Press {highlight}Ctrl+X Y{/highlight} to copy the assistant's last message to clipboard",
|
"Press {highlight}Ctrl+X Y{/highlight} to copy the assistant's last message to clipboard",
|
||||||
"Press {highlight}Ctrl+P{/highlight} to see all available actions and commands",
|
"Press {highlight}Ctrl+P{/highlight} to see all available actions and commands",
|
||||||
"Run {highlight}/connect{/highlight} to add API keys for 75+ supported LLM providers",
|
"Run {highlight}/connect{/highlight} to configure your ToothFairyAI provider",
|
||||||
"The leader key is {highlight}Ctrl+X{/highlight}; combine with other keys for quick actions",
|
"The leader key is {highlight}Ctrl+X{/highlight}; combine with other keys for quick actions",
|
||||||
"Press {highlight}F2{/highlight} to quickly switch between recently used models",
|
"Press {highlight}F2{/highlight} to quickly switch between recently used models",
|
||||||
"Press {highlight}Ctrl+X B{/highlight} to show/hide the sidebar panel",
|
"Press {highlight}Ctrl+X B{/highlight} to show/hide the sidebar panel",
|
||||||
|
|||||||
74
packages/tfcode/src/cli/cmd/tui/ui/dialog-changelog.tsx
Normal file
74
packages/tfcode/src/cli/cmd/tui/ui/dialog-changelog.tsx
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import { TextAttributes } from "@opentui/core"
|
||||||
|
import { useTheme } from "@tui/context/theme"
|
||||||
|
import { useDialog } from "./dialog"
|
||||||
|
import { useKeyboard } from "@opentui/solid"
|
||||||
|
import { For } from "solid-js"
|
||||||
|
|
||||||
|
interface ChangelogEntry {
|
||||||
|
version: string
|
||||||
|
date: string
|
||||||
|
changes: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const CHANGELOG: ChangelogEntry[] = [
|
||||||
|
{
|
||||||
|
version: "1.0.0-beta",
|
||||||
|
date: "2026-03-24",
|
||||||
|
changes: [
|
||||||
|
"Initial tfcode release based on opencode",
|
||||||
|
"Custom TF CODE logo with teal branding",
|
||||||
|
"ToothFairyAI workspace integration",
|
||||||
|
"Sync tools from ToothFairyAI workspace",
|
||||||
|
"TF coder agents appear in /agents",
|
||||||
|
"Commands: tfcode sync, tfcode validate, tfcode tools list",
|
||||||
|
"Support for dev, au, eu, us regions",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export function DialogChangelog() {
|
||||||
|
const dialog = useDialog()
|
||||||
|
const { theme } = useTheme()
|
||||||
|
|
||||||
|
useKeyboard((evt) => {
|
||||||
|
if (evt.name === "return" || evt.name === "escape") {
|
||||||
|
dialog.clear()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<box paddingLeft={2} paddingRight={2} gap={1} flexDirection="column">
|
||||||
|
<box flexDirection="row" justifyContent="space-between">
|
||||||
|
<text attributes={TextAttributes.BOLD} fg={theme.text}>
|
||||||
|
tfcode Changelog
|
||||||
|
</text>
|
||||||
|
<text fg={theme.textMuted} onMouseUp={() => dialog.clear()}>
|
||||||
|
esc/enter
|
||||||
|
</text>
|
||||||
|
</box>
|
||||||
|
<box flexDirection="column" gap={1}>
|
||||||
|
<For each={CHANGELOG}>
|
||||||
|
{(release) => (
|
||||||
|
<box flexDirection="column" gap={0}>
|
||||||
|
<text fg={theme.primary} attributes={TextAttributes.BOLD}>
|
||||||
|
v{release.version} ({release.date})
|
||||||
|
</text>
|
||||||
|
<For each={release.changes}>
|
||||||
|
{(change) => (
|
||||||
|
<text fg={theme.textMuted}>
|
||||||
|
{" "}• {change}
|
||||||
|
</text>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</box>
|
||||||
|
)}
|
||||||
|
</For>
|
||||||
|
</box>
|
||||||
|
<box flexDirection="row" justifyContent="flex-end" paddingBottom={1}>
|
||||||
|
<box paddingLeft={3} paddingRight={3} backgroundColor={theme.primary} onMouseUp={() => dialog.clear()}>
|
||||||
|
<text fg={theme.selectedListItemText}>ok</text>
|
||||||
|
</box>
|
||||||
|
</box>
|
||||||
|
</box>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -15,6 +15,13 @@ export namespace ModelsDev {
|
|||||||
const log = Log.create({ service: "models.dev" })
|
const log = Log.create({ service: "models.dev" })
|
||||||
const filepath = path.join(Global.Path.cache, "models.json")
|
const filepath = path.join(Global.Path.cache, "models.json")
|
||||||
|
|
||||||
|
const REGION_URLS: Record<string, string> = {
|
||||||
|
dev: "https://ai.toothfairylab.link",
|
||||||
|
au: "https://ai.toothfairyai.com",
|
||||||
|
eu: "https://ai.eu.toothfairyai.com",
|
||||||
|
us: "https://ai.us.toothfairyai.com",
|
||||||
|
}
|
||||||
|
|
||||||
export const Model = z.object({
|
export const Model = z.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
@ -81,6 +88,21 @@ export namespace ModelsDev {
|
|||||||
|
|
||||||
export type Provider = z.infer<typeof Provider>
|
export type Provider = z.infer<typeof Provider>
|
||||||
|
|
||||||
|
interface TFModel {
|
||||||
|
name: string
|
||||||
|
provider: string
|
||||||
|
modelType: string
|
||||||
|
reasoner: boolean
|
||||||
|
supportsVision: boolean
|
||||||
|
toolCalling: boolean
|
||||||
|
maxTokens: number
|
||||||
|
deprecated: boolean
|
||||||
|
pricing?: {
|
||||||
|
inputPer1mTokens: number
|
||||||
|
outputPer1mTokens: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function url() {
|
function url() {
|
||||||
return Flag.OPENCODE_MODELS_URL || "https://models.dev"
|
return Flag.OPENCODE_MODELS_URL || "https://models.dev"
|
||||||
}
|
}
|
||||||
@ -100,7 +122,122 @@ export namespace ModelsDev {
|
|||||||
|
|
||||||
export async function get() {
|
export async function get() {
|
||||||
const result = await Data()
|
const result = await Data()
|
||||||
return result as Record<string, Provider>
|
const providers = result as Record<string, Provider>
|
||||||
|
|
||||||
|
// Try to fetch ToothFairyAI models dynamically
|
||||||
|
// First check env vars, then stored credentials
|
||||||
|
let tfApiKey = process.env.TF_API_KEY
|
||||||
|
let tfRegion = process.env.TF_REGION || "au"
|
||||||
|
|
||||||
|
// Try to load from stored credentials
|
||||||
|
if (!tfApiKey) {
|
||||||
|
try {
|
||||||
|
const credPath = path.join(Global.Path.data, ".tfcode", "credentials.json")
|
||||||
|
const credData = await Bun.file(credPath).json() as { api_key?: string; region?: string }
|
||||||
|
if (credData.api_key) {
|
||||||
|
tfApiKey = credData.api_key
|
||||||
|
tfRegion = credData.region || "au"
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const tfBaseUrl = REGION_URLS[tfRegion] || REGION_URLS.au
|
||||||
|
|
||||||
|
if (tfApiKey) {
|
||||||
|
try {
|
||||||
|
const tfResponse = await fetch(`${tfBaseUrl}/models_list`, {
|
||||||
|
headers: {
|
||||||
|
"x-api-key": tfApiKey,
|
||||||
|
},
|
||||||
|
signal: AbortSignal.timeout(10000),
|
||||||
|
})
|
||||||
|
|
||||||
|
if (tfResponse.ok) {
|
||||||
|
const tfData = await tfResponse.json() as { templates: Record<string, TFModel> }
|
||||||
|
const tfModels: Record<string, Model> = {}
|
||||||
|
|
||||||
|
for (const [key, model] of Object.entries(tfData.templates || {})) {
|
||||||
|
if (model.deprecated) continue
|
||||||
|
|
||||||
|
const modelId = key.startsWith("z/") ? key.slice(2) : key
|
||||||
|
|
||||||
|
tfModels[modelId] = {
|
||||||
|
id: modelId,
|
||||||
|
name: model.name,
|
||||||
|
family: model.provider,
|
||||||
|
release_date: new Date().toISOString().split("T")[0],
|
||||||
|
attachment: model.supportsVision || false,
|
||||||
|
reasoning: model.reasoner || false,
|
||||||
|
temperature: true,
|
||||||
|
tool_call: model.toolCalling || false,
|
||||||
|
options: {},
|
||||||
|
cost: {
|
||||||
|
input: model.pricing?.inputPer1mTokens || 0,
|
||||||
|
output: model.pricing?.outputPer1mTokens || 0,
|
||||||
|
},
|
||||||
|
limit: {
|
||||||
|
context: model.maxTokens ? model.maxTokens * 4 : 128000,
|
||||||
|
output: model.maxTokens || 16000,
|
||||||
|
},
|
||||||
|
modalities: {
|
||||||
|
input: model.supportsVision ? ["text", "image"] : ["text"],
|
||||||
|
output: ["text"],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
providers["toothfairyai"] = {
|
||||||
|
id: "toothfairyai",
|
||||||
|
name: "ToothFairyAI",
|
||||||
|
env: ["TF_API_KEY", "TF_WORKSPACE_ID"],
|
||||||
|
models: tfModels,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
log.error("Failed to fetch ToothFairyAI models", { error: e })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to static models if dynamic fetch failed
|
||||||
|
if (!providers["toothfairyai"] || Object.keys(providers["toothfairyai"].models).length === 0) {
|
||||||
|
providers["toothfairyai"] = {
|
||||||
|
id: "toothfairyai",
|
||||||
|
name: "ToothFairyAI",
|
||||||
|
env: ["TF_API_KEY", "TF_WORKSPACE_ID"],
|
||||||
|
models: {
|
||||||
|
"sorcerer": {
|
||||||
|
id: "sorcerer",
|
||||||
|
name: "TF Sorcerer",
|
||||||
|
family: "groq",
|
||||||
|
release_date: "2025-01-01",
|
||||||
|
attachment: true,
|
||||||
|
reasoning: false,
|
||||||
|
temperature: true,
|
||||||
|
tool_call: true,
|
||||||
|
options: {},
|
||||||
|
cost: { input: 0.62, output: 1.75 },
|
||||||
|
limit: { context: 128000, output: 16000 },
|
||||||
|
modalities: { input: ["text", "image"], output: ["text"] },
|
||||||
|
},
|
||||||
|
"mystica": {
|
||||||
|
id: "mystica",
|
||||||
|
name: "TF Mystica",
|
||||||
|
family: "fireworks",
|
||||||
|
release_date: "2025-01-01",
|
||||||
|
attachment: true,
|
||||||
|
reasoning: false,
|
||||||
|
temperature: true,
|
||||||
|
tool_call: true,
|
||||||
|
options: {},
|
||||||
|
cost: { input: 2.5, output: 7.5 },
|
||||||
|
limit: { context: 128000, output: 16000 },
|
||||||
|
modalities: { input: ["text", "image"], output: ["text"] },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return providers
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function refresh() {
|
export async function refresh() {
|
||||||
|
|||||||
@ -182,6 +182,29 @@ export namespace Provider {
|
|||||||
options: hasKey ? {} : { apiKey: "public" },
|
options: hasKey ? {} : { apiKey: "public" },
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async toothfairyai(input) {
|
||||||
|
const hasCredentials = await (async () => {
|
||||||
|
const env = Env.all()
|
||||||
|
if (env.TF_API_KEY) return true
|
||||||
|
if (await Auth.get(input.id)) return true
|
||||||
|
const config = await Config.get()
|
||||||
|
if (config.provider?.["toothfairyai"]?.options?.apiKey) return true
|
||||||
|
|
||||||
|
// Check stored credentials file
|
||||||
|
try {
|
||||||
|
const credPath = path.join(Global.Path.data, ".tfcode", "credentials.json")
|
||||||
|
const credData = await Bun.file(credPath).json() as { api_key?: string }
|
||||||
|
if (credData.api_key) return true
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
return false
|
||||||
|
})()
|
||||||
|
|
||||||
|
return {
|
||||||
|
autoload: hasCredentials,
|
||||||
|
options: hasCredentials ? {} : { apiKey: "setup-required" },
|
||||||
|
}
|
||||||
|
},
|
||||||
openai: async () => {
|
openai: async () => {
|
||||||
return {
|
return {
|
||||||
autoload: false,
|
autoload: false,
|
||||||
|
|||||||
@ -12,6 +12,7 @@ export const ProviderID = providerIdSchema.pipe(
|
|||||||
make: (id: string) => schema.makeUnsafe(id),
|
make: (id: string) => schema.makeUnsafe(id),
|
||||||
zod: z.string().pipe(z.custom<ProviderID>()),
|
zod: z.string().pipe(z.custom<ProviderID>()),
|
||||||
// Well-known providers
|
// Well-known providers
|
||||||
|
toothfairyai: schema.makeUnsafe("toothfairyai"),
|
||||||
opencode: schema.makeUnsafe("opencode"),
|
opencode: schema.makeUnsafe("opencode"),
|
||||||
anthropic: schema.makeUnsafe("anthropic"),
|
anthropic: schema.makeUnsafe("anthropic"),
|
||||||
openai: schema.makeUnsafe("openai"),
|
openai: schema.makeUnsafe("openai"),
|
||||||
|
|||||||
19
packages/tfcode/test-creds.ts
Normal file
19
packages/tfcode/test-creds.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { Global } from "./src/global"
|
||||||
|
import path from "path"
|
||||||
|
import { ModelsDev } from "./src/provider/models"
|
||||||
|
|
||||||
|
console.log("Global.Path.data:", Global.Path.data)
|
||||||
|
const credPath = path.join(Global.Path.data, ".tfcode", "credentials.json")
|
||||||
|
console.log("Credentials path:", credPath)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const credData = await Bun.file(credPath).json()
|
||||||
|
console.log("Credentials loaded:", { api_key: (credData as any).api_key?.slice(0,10)+"...", region: (credData as any).region })
|
||||||
|
} catch (e) {
|
||||||
|
console.log("Error loading credentials:", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
const providers = await ModelsDev.get()
|
||||||
|
console.log("ToothFairyAI provider found:", !!providers["toothfairyai"])
|
||||||
|
console.log("Models count:", Object.keys(providers["toothfairyai"]?.models || {}).length)
|
||||||
|
console.log("Sample models:", Object.keys(providers["toothfairyai"]?.models || {}).slice(0, 5))
|
||||||
Loading…
x
Reference in New Issue
Block a user