mirror of
https://github.com/anomalyco/opencode.git
synced 2026-07-24 20:25:36 -04:00
fix(types): resolve type errors in usage, session, and TUI components
- Fix SQLite binding types in usage storage with proper any cast - Fix assistantMessage.time.start → .created in processor - Handle legacy string model format in prompt.ts - Extend SDK Agent type with fallback property for TUI - Extend SDK KeybindsConfig with model_fallback_toggle - Fix text/span styling for bold in prompt component Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -10,8 +10,7 @@ import {
|
||||
type ParentProps,
|
||||
} from "solid-js"
|
||||
import { useKeyboard } from "@opentui/solid"
|
||||
import { useKeybind } from "@tui/context/keybind"
|
||||
import type { KeybindsConfig } from "@opencode-ai/sdk/v2"
|
||||
import { useKeybind, type KeybindsConfig } from "@tui/context/keybind"
|
||||
|
||||
type Context = ReturnType<typeof init>
|
||||
const ctx = createContext<Context>()
|
||||
|
||||
@@ -1066,8 +1066,8 @@ export function Prompt(props: PromptProps) {
|
||||
</text>
|
||||
<text fg={theme.textMuted}>{local.model.parsed().provider}</text>
|
||||
<Show when={local.model.isFallbackActive()}>
|
||||
<text fg={theme.warning} style={{ bold: true }}>
|
||||
[FB]
|
||||
<text>
|
||||
<span style={{ fg: theme.warning, bold: true }}>[FB]</span>
|
||||
</text>
|
||||
</Show>
|
||||
<Show when={showVariant()}>
|
||||
|
||||
@@ -2,21 +2,26 @@ import { createMemo } from "solid-js"
|
||||
import { useSync } from "@tui/context/sync"
|
||||
import { Keybind } from "@/util/keybind"
|
||||
import { pipe, mapValues } from "remeda"
|
||||
import type { KeybindsConfig } from "@opencode-ai/sdk/v2"
|
||||
import type { KeybindsConfig as SDKKeybindsConfig } from "@opencode-ai/sdk/v2"
|
||||
import type { ParsedKey, Renderable } from "@opentui/core"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { useKeyboard, useRenderer } from "@opentui/solid"
|
||||
import { createSimpleContext } from "./helper"
|
||||
|
||||
// Extended keybinds type with new keybinds not yet in SDK
|
||||
export type KeybindsConfig = SDKKeybindsConfig & {
|
||||
model_fallback_toggle?: string
|
||||
}
|
||||
|
||||
export const { use: useKeybind, provider: KeybindProvider } = createSimpleContext({
|
||||
name: "Keybind",
|
||||
init: () => {
|
||||
const sync = useSync()
|
||||
const keybinds = createMemo(() => {
|
||||
return pipe(
|
||||
sync.data.config.keybinds ?? {},
|
||||
(sync.data.config.keybinds ?? {}) as KeybindsConfig,
|
||||
mapValues((value) => Keybind.parse(value)),
|
||||
)
|
||||
) as { [K in keyof KeybindsConfig]?: Keybind.Info[] }
|
||||
})
|
||||
const [store, setStore] = createStore({
|
||||
leader: false,
|
||||
|
||||
@@ -12,6 +12,12 @@ import { Provider } from "@/provider/provider"
|
||||
import { useArgs } from "./args"
|
||||
import { useSDK } from "./sdk"
|
||||
import { RGBA } from "@opentui/core"
|
||||
import type { Agent as SDKAgent } from "@opencode-ai/sdk/v2"
|
||||
|
||||
// Extended agent type with fallback model support (internal feature not yet in SDK)
|
||||
type AgentWithFallback = SDKAgent & {
|
||||
fallback?: { providerID: string; modelID: string }
|
||||
}
|
||||
|
||||
export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
||||
name: "Local",
|
||||
@@ -36,8 +42,8 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
||||
}
|
||||
|
||||
const agent = iife(() => {
|
||||
const agents = createMemo(() => {
|
||||
const list = sync.data?.agent
|
||||
const agents = createMemo((): AgentWithFallback[] => {
|
||||
const list = sync.data?.agent as AgentWithFallback[] | undefined
|
||||
if (!list || !Array.isArray(list)) return []
|
||||
return list
|
||||
.filter((x) => x.mode !== "subagent" && !x.hidden)
|
||||
@@ -83,6 +89,7 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
|
||||
permission: [],
|
||||
options: {},
|
||||
model: undefined as { providerID: string; modelID: string } | undefined,
|
||||
fallback: undefined as { providerID: string; modelID: string } | undefined,
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -138,7 +138,7 @@ export namespace ProviderTransform {
|
||||
? model.capabilities.interleaved.field
|
||||
: null
|
||||
|
||||
if (interleavedField === "reasoning_content" || interleavedField === "reasoning") {
|
||||
if (interleavedField === "reasoning_content" || interleavedField === "reasoning_details") {
|
||||
return msgs.map((msg) => {
|
||||
if (msg.role === "assistant" && Array.isArray(msg.content)) {
|
||||
const reasoningParts = msg.content.filter((part: any) => part.type === "reasoning")
|
||||
@@ -157,7 +157,7 @@ export namespace ProviderTransform {
|
||||
providerOptions: {
|
||||
...msg.providerOptions,
|
||||
openaiCompatible: {
|
||||
...(typeof existingOptions === "object" ? existingOptions : {}),
|
||||
...(existingOptions && typeof existingOptions === "object" ? existingOptions : {}),
|
||||
[interleavedField]: reasoningText,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -297,7 +297,7 @@ export namespace SessionProcessor {
|
||||
inputCost: usage.cost * (usage.tokens.input / (usage.tokens.input + usage.tokens.output + usage.tokens.reasoning || 1)),
|
||||
outputCost: usage.cost * ((usage.tokens.output + usage.tokens.reasoning) / (usage.tokens.input + usage.tokens.output + usage.tokens.reasoning || 1)),
|
||||
cacheCost: 0, // Cache cost already included in inputCost for Anthropic
|
||||
durationMs: Date.now() - (input.assistantMessage.time.start || Date.now()),
|
||||
durationMs: Date.now() - (input.assistantMessage.time.created || Date.now()),
|
||||
streaming: true,
|
||||
toolCalls: Object.keys(toolcalls).length || undefined,
|
||||
}).catch((e) => log.warn("failed to record usage", { error: String(e) }))
|
||||
|
||||
@@ -1573,8 +1573,11 @@ export namespace SessionPrompt {
|
||||
return await lastModel(input.sessionID)
|
||||
})()
|
||||
|
||||
// Handle both legacy string format ("provider/model") and object format
|
||||
const resolvedModel = typeof taskModel === "string" ? Provider.parseModel(taskModel) : taskModel
|
||||
|
||||
try {
|
||||
await Provider.getModel(taskModel.providerID, taskModel.modelID)
|
||||
await Provider.getModel(resolvedModel.providerID, resolvedModel.modelID)
|
||||
} catch (e) {
|
||||
if (Provider.ModelNotFoundError.isInstance(e)) {
|
||||
const { providerID, modelID, suggestions } = e.data
|
||||
@@ -1608,8 +1611,8 @@ export namespace SessionPrompt {
|
||||
description: command.description ?? "",
|
||||
command: input.command,
|
||||
model: {
|
||||
providerID: taskModel.providerID,
|
||||
modelID: taskModel.modelID,
|
||||
providerID: resolvedModel.providerID,
|
||||
modelID: resolvedModel.modelID,
|
||||
},
|
||||
// LIMITATION: Task tool currently only takes text prompt, not complex parts
|
||||
// This is intentional for simplicity; subagents get minimal context
|
||||
@@ -1623,7 +1626,7 @@ export namespace SessionPrompt {
|
||||
? input.model
|
||||
? Provider.parseModel(input.model)
|
||||
: await lastModel(input.sessionID)
|
||||
: taskModel
|
||||
: resolvedModel
|
||||
|
||||
await Plugin.trigger(
|
||||
"command.execute.before",
|
||||
|
||||
@@ -200,7 +200,8 @@ export function getSummary(query: UsageSummaryQuery = {}): UsageSummary {
|
||||
const { startTime, endTime } = getPeriodRange(query.period ?? "day", query.from, query.to)
|
||||
|
||||
const conditions: string[] = ["timestamp >= $startTime AND timestamp <= $endTime"]
|
||||
const params: Record<string, unknown> = { $startTime: startTime, $endTime: endTime }
|
||||
// biome-ignore lint/suspicious/noExplicitAny: SQLite bindings accept dynamic params
|
||||
const params: Record<string, any> = { $startTime: startTime, $endTime: endTime }
|
||||
|
||||
if (query.providerId) {
|
||||
conditions.push("provider_id = $providerId")
|
||||
|
||||
Reference in New Issue
Block a user