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