mirror of
https://gitea.toothfairyai.com/ToothFairyAI/tf_code.git
synced 2026-04-15 13:14:35 +00:00
feat: agents and skills
This commit is contained in:
@@ -15,6 +15,13 @@ export namespace ModelsDev {
|
||||
const log = Log.create({ service: "models.dev" })
|
||||
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({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
@@ -81,6 +88,21 @@ export namespace ModelsDev {
|
||||
|
||||
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() {
|
||||
return Flag.OPENCODE_MODELS_URL || "https://models.dev"
|
||||
}
|
||||
@@ -100,7 +122,122 @@ export namespace ModelsDev {
|
||||
|
||||
export async function get() {
|
||||
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() {
|
||||
|
||||
@@ -182,6 +182,29 @@ export namespace Provider {
|
||||
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 () => {
|
||||
return {
|
||||
autoload: false,
|
||||
|
||||
@@ -12,6 +12,7 @@ export const ProviderID = providerIdSchema.pipe(
|
||||
make: (id: string) => schema.makeUnsafe(id),
|
||||
zod: z.string().pipe(z.custom<ProviderID>()),
|
||||
// Well-known providers
|
||||
toothfairyai: schema.makeUnsafe("toothfairyai"),
|
||||
opencode: schema.makeUnsafe("opencode"),
|
||||
anthropic: schema.makeUnsafe("anthropic"),
|
||||
openai: schema.makeUnsafe("openai"),
|
||||
|
||||
Reference in New Issue
Block a user