mirror of
https://gitea.toothfairyai.com/ToothFairyAI/tf_code.git
synced 2026-04-14 12:44:36 +00:00
feat: cherry picks
This commit is contained in:
@@ -115,7 +115,7 @@ export namespace ACP {
|
||||
sessionUpdate: "usage_update",
|
||||
used,
|
||||
size,
|
||||
cost: { amount: totalCost, currency: "USD" },
|
||||
cost: { amount: Math.round(totalCost), currency: "UoI" },
|
||||
},
|
||||
})
|
||||
.catch((error) => {
|
||||
|
||||
@@ -53,6 +53,7 @@ export const { use: useExit, provider: ExitProvider } = createSimpleContext({
|
||||
message: store,
|
||||
},
|
||||
)
|
||||
process.on("SIGHUP", () => exit())
|
||||
return exit
|
||||
},
|
||||
})
|
||||
|
||||
@@ -52,10 +52,8 @@ export function Header() {
|
||||
messages(),
|
||||
sumBy((x) => (x.role === "assistant" ? x.cost : 0)),
|
||||
)
|
||||
return new Intl.NumberFormat("en-US", {
|
||||
style: "currency",
|
||||
currency: "USD",
|
||||
}).format(total)
|
||||
const uoi = Math.round(total)
|
||||
return uoi.toLocaleString() + " UoI"
|
||||
})
|
||||
|
||||
const context = createMemo(() => {
|
||||
|
||||
@@ -42,10 +42,8 @@ export function Sidebar(props: { sessionID: string; overlay?: boolean }) {
|
||||
|
||||
const cost = createMemo(() => {
|
||||
const total = messages().reduce((sum, x) => sum + (x.role === "assistant" ? x.cost : 0), 0)
|
||||
return new Intl.NumberFormat("en-US", {
|
||||
style: "currency",
|
||||
currency: "USD",
|
||||
}).format(total)
|
||||
const uoi = Math.round(total)
|
||||
return uoi.toLocaleString() + " UoI"
|
||||
})
|
||||
|
||||
const context = createMemo(() => {
|
||||
|
||||
@@ -31,7 +31,6 @@ import { ModelID, ProviderID } from "@/provider/schema"
|
||||
import { Permission } from "@/permission"
|
||||
import { Global } from "@/global"
|
||||
import type { LanguageModelV2Usage } from "@ai-sdk/provider"
|
||||
import { iife } from "@/util/iife"
|
||||
|
||||
export namespace Session {
|
||||
const log = Log.create({ service: "session" })
|
||||
@@ -813,27 +812,11 @@ export namespace Session {
|
||||
0) as number,
|
||||
)
|
||||
|
||||
// OpenRouter provides inputTokens as the total count of input tokens (including cached).
|
||||
// AFAIK other providers (OpenRouter/OpenAI/Gemini etc.) do it the same way e.g. vercel/ai#8794 (comment)
|
||||
// Anthropic does it differently though - inputTokens doesn't include cached tokens.
|
||||
// It looks like OpenCode's cost calculation assumes all providers return inputTokens the same way Anthropic does (I'm guessing getUsage logic was originally implemented with anthropic), so it's causing incorrect cost calculation for OpenRouter and others.
|
||||
const excludesCachedTokens = !!(input.metadata?.["anthropic"] || input.metadata?.["bedrock"])
|
||||
const adjustedInputTokens = safe(
|
||||
excludesCachedTokens ? inputTokens : inputTokens - cacheReadInputTokens - cacheWriteInputTokens,
|
||||
)
|
||||
// AI SDK v6 normalized inputTokens to include cached tokens across all providers.
|
||||
// Always subtract cache tokens to get non-cached input for separate cost calculation.
|
||||
const adjustedInputTokens = safe(inputTokens - cacheReadInputTokens - cacheWriteInputTokens)
|
||||
|
||||
const total = iife(() => {
|
||||
// Anthropic doesn't provide total_tokens, also ai sdk will vastly undercount if we
|
||||
// don't compute from components
|
||||
if (
|
||||
input.model.api.npm === "@ai-sdk/anthropic" ||
|
||||
input.model.api.npm === "@ai-sdk/amazon-bedrock" ||
|
||||
input.model.api.npm === "@ai-sdk/google-vertex/anthropic"
|
||||
) {
|
||||
return adjustedInputTokens + outputTokens + cacheReadInputTokens + cacheWriteInputTokens
|
||||
}
|
||||
return input.usage.totalTokens
|
||||
})
|
||||
const total = input.usage.totalTokens
|
||||
|
||||
const tokens = {
|
||||
total,
|
||||
@@ -853,13 +836,11 @@ export namespace Session {
|
||||
return {
|
||||
cost: safe(
|
||||
new Decimal(0)
|
||||
.add(new Decimal(tokens.input).mul(costInfo?.input ?? 0).div(1_000_000))
|
||||
.add(new Decimal(tokens.output).mul(costInfo?.output ?? 0).div(1_000_000))
|
||||
.add(new Decimal(tokens.cache.read).mul(costInfo?.cache?.read ?? 0).div(1_000_000))
|
||||
.add(new Decimal(tokens.cache.write).mul(costInfo?.cache?.write ?? 0).div(1_000_000))
|
||||
// TODO: update models.dev to have better pricing model, for now:
|
||||
// charge reasoning tokens at the same rate as output tokens
|
||||
.add(new Decimal(tokens.reasoning).mul(costInfo?.output ?? 0).div(1_000_000))
|
||||
.add(new Decimal(tokens.input).mul(costInfo?.input ?? 0).div(10_000))
|
||||
.add(new Decimal(tokens.output).mul(costInfo?.output ?? 0).div(10_000))
|
||||
.add(new Decimal(tokens.cache.read).mul(costInfo?.cache?.read ?? 0).div(10_000))
|
||||
.add(new Decimal(tokens.cache.write).mul(costInfo?.cache?.write ?? 0).div(10_000))
|
||||
.add(new Decimal(tokens.reasoning).mul(costInfo?.output ?? 0).div(10_000))
|
||||
.toNumber(),
|
||||
),
|
||||
tokens,
|
||||
|
||||
@@ -200,7 +200,7 @@ export const ReadTool = Tool.define("read", {
|
||||
})
|
||||
const preview = raw.slice(0, 20).join("\n")
|
||||
|
||||
let output = [`<path>${filepath}</path>`, `<type>file</type>`, "<content>"].join("\n")
|
||||
let output = [`<path>${filepath}</path>`, `<type>file</type>`, "<content>" + "\n"].join("\n")
|
||||
output += content.join("\n")
|
||||
|
||||
const totalLines = lines
|
||||
|
||||
@@ -64,6 +64,7 @@ export const TaskTool = Tool.define("task", async (ctx) => {
|
||||
if (!agent) throw new Error(`Unknown agent type: ${params.subagent_type} is not a valid agent type`)
|
||||
|
||||
const hasTaskPermission = agent.permission.some((rule) => rule.permission === "task")
|
||||
const hasTodoWritePermission = agent.permission.some((rule) => rule.permission === "todowrite")
|
||||
|
||||
const session = await iife(async () => {
|
||||
if (params.task_id) {
|
||||
@@ -75,11 +76,15 @@ export const TaskTool = Tool.define("task", async (ctx) => {
|
||||
parentID: ctx.sessionID,
|
||||
title: params.description + ` (@${agent.name} subagent)`,
|
||||
permission: [
|
||||
{
|
||||
permission: "todowrite",
|
||||
pattern: "*",
|
||||
action: "deny",
|
||||
},
|
||||
...(hasTodoWritePermission
|
||||
? []
|
||||
: [
|
||||
{
|
||||
permission: "todowrite" as const,
|
||||
pattern: "*" as const,
|
||||
action: "deny" as const,
|
||||
},
|
||||
]),
|
||||
{
|
||||
permission: "todoread",
|
||||
pattern: "*",
|
||||
@@ -136,7 +141,7 @@ export const TaskTool = Tool.define("task", async (ctx) => {
|
||||
},
|
||||
agent: agent.name,
|
||||
tools: {
|
||||
todowrite: false,
|
||||
...(hasTodoWritePermission ? {} : { todowrite: false }),
|
||||
todoread: false,
|
||||
...(hasTaskPermission ? {} : { task: false }),
|
||||
...Object.fromEntries((config.experimental?.primary_tools ?? []).map((t) => [t, false])),
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Tool } from "./tool"
|
||||
import TurndownService from "turndown"
|
||||
import DESCRIPTION from "./webfetch.txt"
|
||||
import { abortAfterAny } from "../util/abort"
|
||||
import { iife } from "@/util/iife"
|
||||
|
||||
const MAX_RESPONSE_SIZE = 5 * 1024 * 1024 // 5MB
|
||||
const DEFAULT_TIMEOUT = 30 * 1000 // 30 seconds
|
||||
@@ -62,15 +63,18 @@ export const WebFetchTool = Tool.define("webfetch", {
|
||||
"Accept-Language": "en-US,en;q=0.9",
|
||||
}
|
||||
|
||||
const initial = await fetch(params.url, { signal, headers })
|
||||
const response = await iife(async () => {
|
||||
try {
|
||||
const initial = await fetch(params.url, { signal, headers })
|
||||
|
||||
// Retry with honest UA if blocked by Cloudflare bot detection (TLS fingerprint mismatch)
|
||||
const response =
|
||||
initial.status === 403 && initial.headers.get("cf-mitigated") === "challenge"
|
||||
? await fetch(params.url, { signal, headers: { ...headers, "User-Agent": "opencode" } })
|
||||
: initial
|
||||
|
||||
clearTimeout()
|
||||
// Retry with honest UA if blocked by Cloudflare bot detection (TLS fingerprint mismatch)
|
||||
return initial.status === 403 && initial.headers.get("cf-mitigated") === "challenge"
|
||||
? await fetch(params.url, { signal, headers: { ...headers, "User-Agent": "opencode" } })
|
||||
: initial
|
||||
} finally {
|
||||
clearTimeout()
|
||||
}
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Request failed with status code: ${response.status}`)
|
||||
|
||||
Reference in New Issue
Block a user