mirror of
https://gitea.toothfairyai.com/ToothFairyAI/tf_code.git
synced 2026-03-31 22:32:28 +00:00
feat: models
This commit is contained in:
2
bun.lock
2
bun.lock
@@ -381,7 +381,7 @@
|
||||
},
|
||||
"packages/tfcode": {
|
||||
"name": "@toothfairyai/tfcode",
|
||||
"version": "1.0.22",
|
||||
"version": "1.0.23",
|
||||
"bin": {
|
||||
"tfcode": "./bin/tfcode",
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"version": "1.0.22",
|
||||
"version": "1.0.23",
|
||||
"name": "@toothfairyai/tfcode",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Prompt, type PromptRef } from "@tui/component/prompt"
|
||||
import { createEffect, createMemo, Match, on, onMount, Show, Switch } from "solid-js"
|
||||
import { createEffect, createMemo, Match, on, onMount, Show, Switch, createSignal } from "solid-js"
|
||||
import { useTheme } from "@tui/context/theme"
|
||||
import { useKeybind } from "@tui/context/keybind"
|
||||
import { Logo } from "../component/logo"
|
||||
@@ -15,6 +15,10 @@ import { Installation } from "@/installation"
|
||||
import { useKV } from "../context/kv"
|
||||
import { useCommandDialog } from "../component/dialog-command"
|
||||
import { useLocal } from "../context/local"
|
||||
import { useSDK } from "../context/sdk"
|
||||
import semver from "semver"
|
||||
import { TextAttributes } from "@opentui/core"
|
||||
import { Flag } from "@/flag/flag"
|
||||
|
||||
// TODO: what is the best way to do this?
|
||||
let once = false
|
||||
@@ -26,6 +30,7 @@ export function Home() {
|
||||
const route = useRouteData("home")
|
||||
const promptRef = usePromptRef()
|
||||
const command = useCommandDialog()
|
||||
const sdk = useSDK()
|
||||
const mcp = createMemo(() => Object.keys(sync.data.mcp).length > 0)
|
||||
const mcpError = createMemo(() => {
|
||||
return Object.values(sync.data.mcp).some((x) => x.status === "failed")
|
||||
@@ -43,6 +48,19 @@ export function Home() {
|
||||
return !tipsHidden()
|
||||
})
|
||||
|
||||
const [updateAvailable, setUpdateAvailable] = createSignal<string | null>(null)
|
||||
const skippedVersion = createMemo(() => kv.get("skipped_version"))
|
||||
|
||||
onMount(() => {
|
||||
const unsub = sdk.event.on("installation.update-available", (evt) => {
|
||||
const version = evt.properties.version
|
||||
const skipped = skippedVersion()
|
||||
if (skipped && !semver.gt(version, skipped)) return
|
||||
setUpdateAvailable(version)
|
||||
})
|
||||
return unsub
|
||||
})
|
||||
|
||||
command.register(() => [
|
||||
{
|
||||
title: tipsHidden() ? "Show tips" : "Hide tips",
|
||||
@@ -132,6 +150,27 @@ export function Home() {
|
||||
<box flexGrow={1} minHeight={0} />
|
||||
<Toast />
|
||||
</box>
|
||||
<Show when={updateAvailable()}>
|
||||
<box
|
||||
paddingTop={1}
|
||||
paddingBottom={1}
|
||||
paddingLeft={2}
|
||||
paddingRight={2}
|
||||
flexDirection="row"
|
||||
flexShrink={0}
|
||||
gap={2}
|
||||
alignItems="center"
|
||||
>
|
||||
<box backgroundColor={theme.warning} paddingLeft={1} paddingRight={1} paddingTop={0} paddingBottom={0}>
|
||||
<text fg={theme.background} attributes={TextAttributes.BOLD}>
|
||||
!
|
||||
</text>
|
||||
</box>
|
||||
<text fg={theme.text}>Update available: v{updateAvailable()}</text>
|
||||
<box flexGrow={1} />
|
||||
<text fg={theme.textMuted}>/changelog</text>
|
||||
</box>
|
||||
</Show>
|
||||
<box paddingTop={1} paddingBottom={1} paddingLeft={2} paddingRight={2} flexDirection="row" flexShrink={0} gap={2}>
|
||||
<text fg={theme.textMuted}>{directory()}</text>
|
||||
<box gap={1} flexDirection="row" flexShrink={0}>
|
||||
|
||||
@@ -15,6 +15,7 @@ export async function upgrade() {
|
||||
}
|
||||
|
||||
if (Installation.VERSION === latest) return
|
||||
|
||||
if (config.autoupdate === false || Flag.OPENCODE_DISABLE_AUTOUPDATE) return
|
||||
|
||||
const kind = Installation.getReleaseType(Installation.VERSION, latest)
|
||||
|
||||
@@ -62,7 +62,7 @@ export namespace Installation {
|
||||
|
||||
export const VERSION = typeof OPENCODE_VERSION === "string" ? OPENCODE_VERSION : "local"
|
||||
export const CHANNEL = typeof OPENCODE_CHANNEL === "string" ? OPENCODE_CHANNEL : "local"
|
||||
export const USER_AGENT = `opencode/${CHANNEL}/${VERSION}/${Flag.OPENCODE_CLIENT}`
|
||||
export const USER_AGENT = `tfcode/${CHANNEL}/${VERSION}/${Flag.OPENCODE_CLIENT}`
|
||||
|
||||
export function isPreview() {
|
||||
return CHANNEL !== "latest"
|
||||
@@ -141,16 +141,16 @@ export namespace Installation {
|
||||
)
|
||||
|
||||
const getBrewFormula = Effect.fnUntraced(function* () {
|
||||
const tapFormula = yield* text(["brew", "list", "--formula", "anomalyco/tap/opencode"])
|
||||
if (tapFormula.includes("opencode")) return "anomalyco/tap/opencode"
|
||||
const coreFormula = yield* text(["brew", "list", "--formula", "opencode"])
|
||||
if (coreFormula.includes("opencode")) return "opencode"
|
||||
return "opencode"
|
||||
const tapFormula = yield* text(["brew", "list", "--formula", "toothfairyai/tap/tfcode"])
|
||||
if (tapFormula.includes("tfcode")) return "toothfairyai/tap/tfcode"
|
||||
const coreFormula = yield* text(["brew", "list", "--formula", "tfcode"])
|
||||
if (coreFormula.includes("tfcode")) return "tfcode"
|
||||
return "tfcode"
|
||||
})
|
||||
|
||||
const upgradeCurl = Effect.fnUntraced(
|
||||
function* (target: string) {
|
||||
const response = yield* httpOk.execute(HttpClientRequest.get("https://opencode.ai/install"))
|
||||
const response = yield* httpOk.execute(HttpClientRequest.get("https://toothfairyai.com/install"))
|
||||
const body = yield* response.text
|
||||
const bodyBytes = new TextEncoder().encode(body)
|
||||
const proc = ChildProcess.make("bash", [], {
|
||||
@@ -180,9 +180,9 @@ export namespace Installation {
|
||||
{ name: "yarn", command: () => text(["yarn", "global", "list"]) },
|
||||
{ name: "pnpm", command: () => text(["pnpm", "list", "-g", "--depth=0"]) },
|
||||
{ name: "bun", command: () => text(["bun", "pm", "ls", "-g"]) },
|
||||
{ name: "brew", command: () => text(["brew", "list", "--formula", "opencode"]) },
|
||||
{ name: "scoop", command: () => text(["scoop", "list", "opencode"]) },
|
||||
{ name: "choco", command: () => text(["choco", "list", "--limit-output", "opencode"]) },
|
||||
{ name: "brew", command: () => text(["brew", "list", "--formula", "tfcode"]) },
|
||||
{ name: "scoop", command: () => text(["scoop", "list", "tfcode"]) },
|
||||
{ name: "choco", command: () => text(["choco", "list", "--limit-output", "tfcode"]) },
|
||||
]
|
||||
|
||||
checks.sort((a, b) => {
|
||||
@@ -196,7 +196,9 @@ export namespace Installation {
|
||||
for (const check of checks) {
|
||||
const output = yield* check.command()
|
||||
const installedName =
|
||||
check.name === "brew" || check.name === "choco" || check.name === "scoop" ? "opencode" : "opencode-ai"
|
||||
check.name === "brew" || check.name === "choco" || check.name === "scoop"
|
||||
? "tfcode"
|
||||
: "@toothfairyai/tfcode"
|
||||
if (output.includes(installedName)) {
|
||||
return check.name
|
||||
}
|
||||
@@ -216,7 +218,7 @@ export namespace Installation {
|
||||
return info.formulae[0].versions.stable
|
||||
}
|
||||
const response = yield* httpOk.execute(
|
||||
HttpClientRequest.get("https://formulae.brew.sh/api/formula/opencode.json").pipe(
|
||||
HttpClientRequest.get("https://formulae.brew.sh/api/formula/tfcode.json").pipe(
|
||||
HttpClientRequest.acceptJson,
|
||||
),
|
||||
)
|
||||
@@ -230,7 +232,7 @@ export namespace Installation {
|
||||
const registry = reg.endsWith("/") ? reg.slice(0, -1) : reg
|
||||
const channel = CHANNEL
|
||||
const response = yield* httpOk.execute(
|
||||
HttpClientRequest.get(`${registry}/opencode-ai/${channel}`).pipe(HttpClientRequest.acceptJson),
|
||||
HttpClientRequest.get(`${registry}/@toothfairyai/tfcode/${channel}`).pipe(HttpClientRequest.acceptJson),
|
||||
)
|
||||
const data = yield* HttpClientResponse.schemaBodyJson(NpmPackage)(response)
|
||||
return data.version
|
||||
@@ -239,7 +241,7 @@ export namespace Installation {
|
||||
if (detectedMethod === "choco") {
|
||||
const response = yield* httpOk.execute(
|
||||
HttpClientRequest.get(
|
||||
"https://community.chocolatey.org/api/v2/Packages?$filter=Id%20eq%20%27opencode%27%20and%20IsLatestVersion&$select=Version",
|
||||
"https://community.chocolatey.org/api/v2/Packages?$filter=Id%20eq%20%27tfcode%27%20and%20IsLatestVersion&$select=Version",
|
||||
).pipe(HttpClientRequest.setHeaders({ Accept: "application/json;odata=verbose" })),
|
||||
)
|
||||
const data = yield* HttpClientResponse.schemaBodyJson(ChocoPackage)(response)
|
||||
@@ -249,7 +251,7 @@ export namespace Installation {
|
||||
if (detectedMethod === "scoop") {
|
||||
const response = yield* httpOk.execute(
|
||||
HttpClientRequest.get(
|
||||
"https://raw.githubusercontent.com/ScoopInstaller/Main/master/bucket/opencode.json",
|
||||
"https://raw.githubusercontent.com/ScoopInstaller/Main/master/bucket/tfcode.json",
|
||||
).pipe(HttpClientRequest.setHeaders({ Accept: "application/json" })),
|
||||
)
|
||||
const data = yield* HttpClientResponse.schemaBodyJson(ScoopManifest)(response)
|
||||
@@ -257,7 +259,7 @@ export namespace Installation {
|
||||
}
|
||||
|
||||
const response = yield* httpOk.execute(
|
||||
HttpClientRequest.get("https://api.github.com/repos/anomalyco/opencode/releases/latest").pipe(
|
||||
HttpClientRequest.get("https://api.github.com/repos/ToothFairyAI/tfcode/releases/latest").pipe(
|
||||
HttpClientRequest.acceptJson,
|
||||
),
|
||||
)
|
||||
@@ -272,24 +274,24 @@ export namespace Installation {
|
||||
result = yield* upgradeCurl(target)
|
||||
break
|
||||
case "npm":
|
||||
result = yield* run(["npm", "install", "-g", `opencode-ai@${target}`])
|
||||
result = yield* run(["npm", "install", "-g", `@toothfairyai/tfcode@${target}`])
|
||||
break
|
||||
case "pnpm":
|
||||
result = yield* run(["pnpm", "install", "-g", `opencode-ai@${target}`])
|
||||
result = yield* run(["pnpm", "install", "-g", `@toothfairyai/tfcode@${target}`])
|
||||
break
|
||||
case "bun":
|
||||
result = yield* run(["bun", "install", "-g", `opencode-ai@${target}`])
|
||||
result = yield* run(["bun", "install", "-g", `@toothfairyai/tfcode@${target}`])
|
||||
break
|
||||
case "brew": {
|
||||
const formula = yield* getBrewFormula()
|
||||
const env = { HOMEBREW_NO_AUTO_UPDATE: "1" }
|
||||
if (formula.includes("/")) {
|
||||
const tap = yield* run(["brew", "tap", "anomalyco/tap"], { env })
|
||||
const tap = yield* run(["brew", "tap", "toothfairyai/tap"], { env })
|
||||
if (tap.code !== 0) {
|
||||
result = tap
|
||||
break
|
||||
}
|
||||
const repo = yield* text(["brew", "--repo", "anomalyco/tap"])
|
||||
const repo = yield* text(["brew", "--repo", "toothfairyai/tap"])
|
||||
const dir = repo.trim()
|
||||
if (dir) {
|
||||
const pull = yield* run(["git", "pull", "--ff-only"], { cwd: dir, env })
|
||||
@@ -303,10 +305,10 @@ export namespace Installation {
|
||||
break
|
||||
}
|
||||
case "choco":
|
||||
result = yield* run(["choco", "upgrade", "opencode", `--version=${target}`, "-y"])
|
||||
result = yield* run(["choco", "upgrade", "tfcode", `--version=${target}`, "-y"])
|
||||
break
|
||||
case "scoop":
|
||||
result = yield* run(["scoop", "install", `opencode@${target}`])
|
||||
result = yield* run(["scoop", "install", `tfcode@${target}`])
|
||||
break
|
||||
default:
|
||||
return yield* new UpgradeFailedError({ stderr: `Unknown method: ${m}` })
|
||||
|
||||
@@ -7,7 +7,11 @@ import type { OpenAICompatibleChatPrompt } from "./openai-compatible-api-types"
|
||||
import { convertToBase64 } from "@ai-sdk/provider-utils"
|
||||
|
||||
function getOpenAIMetadata(message: { providerOptions?: SharedV2ProviderMetadata }) {
|
||||
return message?.providerOptions?.copilot ?? {}
|
||||
const opts = message?.providerOptions ?? {}
|
||||
return {
|
||||
...(opts.openaiCompatible as Record<string, unknown> | undefined),
|
||||
...(opts.copilot as Record<string, unknown> | undefined),
|
||||
}
|
||||
}
|
||||
|
||||
export function convertToOpenAICompatibleChatMessages(prompt: LanguageModelV2Prompt): OpenAICompatibleChatPrompt {
|
||||
@@ -82,7 +86,6 @@ export function convertToOpenAICompatibleChatMessages(prompt: LanguageModelV2Pro
|
||||
|
||||
for (const part of content) {
|
||||
const partMetadata = getOpenAIMetadata(part)
|
||||
// Check for reasoningOpaque on any part (may be attached to text/tool-call)
|
||||
const partOpaque = (part.providerOptions as { copilot?: { reasoningOpaque?: string } })?.copilot
|
||||
?.reasoningOpaque
|
||||
if (partOpaque && !reasoningOpaque) {
|
||||
@@ -113,15 +116,25 @@ export function convertToOpenAICompatibleChatMessages(prompt: LanguageModelV2Pro
|
||||
}
|
||||
}
|
||||
|
||||
messages.push({
|
||||
const msg: any = {
|
||||
role: "assistant",
|
||||
content: text || undefined,
|
||||
tool_calls: toolCalls.length > 0 ? toolCalls : undefined,
|
||||
reasoning_text: reasoningOpaque ? reasoningText : undefined,
|
||||
reasoning_opaque: reasoningOpaque,
|
||||
reasoning_content: reasoningText,
|
||||
...metadata,
|
||||
})
|
||||
}
|
||||
|
||||
if (metadata?.reasoning_content) {
|
||||
msg.reasoning_content = metadata.reasoning_content
|
||||
} else if (reasoningText) {
|
||||
msg.reasoning_content = reasoningText
|
||||
}
|
||||
|
||||
if (reasoningOpaque) {
|
||||
msg.reasoning_opaque = reasoningOpaque
|
||||
if (reasoningText) msg.reasoning_text = reasoningText
|
||||
}
|
||||
|
||||
messages.push(msg)
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user