mirror of
https://github.com/anomalyco/opencode.git
synced 2026-08-07 17:49:53 -04:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 536b99bbdc | |||
| dcae95e2bb |
@@ -178,6 +178,7 @@ export class LanguageModelCompatibility extends Schema.Class<LanguageModelCompat
|
||||
toolSchema: Schema.optional(LanguageModelToolSchemaCompatibility),
|
||||
reasoningField: Schema.optional(Schema.String),
|
||||
maxTokensField: Schema.optional(LanguageModelMaxTokensFieldCompatibility),
|
||||
requireFinishReason: Schema.optional(Schema.Boolean),
|
||||
}) {}
|
||||
|
||||
export namespace LanguageModelCompatibility {
|
||||
|
||||
@@ -102,7 +102,7 @@ describe("llm constructors", () => {
|
||||
const updated = LanguageModel.update(base, {
|
||||
route: responsesRoute,
|
||||
defaults: { generation: { maxTokens: 20 } },
|
||||
compatibility: { toolSchema: "gemini" },
|
||||
compatibility: { toolSchema: "gemini", requireFinishReason: false },
|
||||
})
|
||||
const updatedInput = LanguageModel.input(updated)
|
||||
|
||||
@@ -110,7 +110,7 @@ describe("llm constructors", () => {
|
||||
expect(String(updated.id)).toBe("fake-model")
|
||||
expect(updated.route).toBe(responsesRoute)
|
||||
expect(updated.defaults?.generation).toEqual({ maxTokens: 20 })
|
||||
expect(updated.compatibility).toEqual({ toolSchema: "gemini" })
|
||||
expect(updated.compatibility).toEqual({ toolSchema: "gemini", requireFinishReason: false })
|
||||
expect(updatedInput.defaults).toBe(updated.defaults)
|
||||
expect(updatedInput.compatibility).toBe(updated.compatibility)
|
||||
expect(String(updatedInput.provider)).toBe("fake")
|
||||
|
||||
@@ -57,7 +57,7 @@ for (const deviceScaleFactor of [1.25, 1.5]) {
|
||||
expect(edges.box.height).toBeCloseTo(geometry.outputHeight, 2)
|
||||
expect(geometry.borderColor).toBe("rgb(255, 0, 255)")
|
||||
expect(geometry.boxShadow).toBe("none")
|
||||
expect(geometry.clipMargin).toBe("1px")
|
||||
expect(geometry.clipMargin).toBe("0.5px")
|
||||
expect(edges.magenta.top).toBeGreaterThan(0.75)
|
||||
expect(edges.magenta.bottom).toBeGreaterThan(0.75)
|
||||
expect(edges.magenta.vertical).toBeGreaterThanOrEqual(2)
|
||||
@@ -117,40 +117,7 @@ test("keeps the patch card inside a fractionally short virtual row", async ({ pa
|
||||
expect(edges.luminance.top).toBeLessThan(245)
|
||||
expect(edges.luminance.bottom).toBeLessThan(245)
|
||||
expect(Math.abs(edges.luminance.bottom - edges.luminance.top)).toBeLessThan(10)
|
||||
expect(geometry.clipMargin).toBe("1px")
|
||||
})
|
||||
|
||||
test("keeps the shell outline inside a fractionally high virtual row", async ({ page }) => {
|
||||
const shellID = "prt_shell_top_outline"
|
||||
const timeline = await setupTimeline(page, {
|
||||
messages: [userMessage(), assistantMessage([shell(shellID, "completed", "shell output")])],
|
||||
settings: { newLayoutDesigns: true, shellToolPartsExpanded: true },
|
||||
reducedMotion: true,
|
||||
deviceScaleFactor: 1.25,
|
||||
})
|
||||
const part = page.locator(`[data-timeline-part-id="${shellID}"]`)
|
||||
const output = part.locator('[data-component="bash-output"]')
|
||||
const row = page.locator("[data-timeline-key]", { has: part })
|
||||
await expect(output).toBeVisible()
|
||||
await timeline.settle()
|
||||
|
||||
const overflow = await row.evaluate((element) => {
|
||||
const output = element.querySelector<HTMLElement>('[data-component="bash-output"]')
|
||||
if (!output) throw new Error("Shell output is unavailable")
|
||||
const offset = output.getBoundingClientRect().top - element.getBoundingClientRect().top + 0.99
|
||||
output.style.transform = `translateY(-${offset}px)`
|
||||
output.style.setProperty("--v2-border-border-base", "rgb(255, 0, 255)")
|
||||
output.style.setProperty("background", "rgb(0, 0, 0)", "important")
|
||||
return {
|
||||
top: element.getBoundingClientRect().top - output.getBoundingClientRect().top,
|
||||
clipMargin: getComputedStyle(element).overflowClipMargin,
|
||||
}
|
||||
})
|
||||
await timeline.settle()
|
||||
|
||||
expect(overflow.top).toBeCloseTo(0.99, 1)
|
||||
expect(overflow.clipMargin).toBe("1px")
|
||||
expect((await captureCardEdges(page, output)).magenta.top).toBeGreaterThan(0.75)
|
||||
expect(geometry.clipMargin).toBe("0.5px")
|
||||
})
|
||||
|
||||
test("allows paint rounding for every framed row but not fixed turn gaps", async ({ page }) => {
|
||||
@@ -186,7 +153,7 @@ test("allows paint rounding for every framed row but not fixed turn gaps", async
|
||||
clipMargin: getComputedStyle(element).overflowClipMargin,
|
||||
})),
|
||||
)
|
||||
expect(rows.filter((row) => row.tag !== "TurnGap").every((row) => row.clipMargin === "1px")).toBe(true)
|
||||
expect(rows.filter((row) => row.tag !== "TurnGap").every((row) => row.clipMargin === "0.5px")).toBe(true)
|
||||
expect(rows.filter((row) => row.tag === "TurnGap")).toEqual([{ tag: "TurnGap", clipMargin: "0px" }])
|
||||
})
|
||||
|
||||
|
||||
@@ -1299,8 +1299,8 @@ export function MessageTimeline(props: {
|
||||
width: "100%",
|
||||
height: `${item().size}px`,
|
||||
overflow: "clip",
|
||||
// Fractional virtual starts and measurements can otherwise clip a framed row's outer paint.
|
||||
"overflow-clip-margin": row()._tag === "TurnGap" ? undefined : "1px",
|
||||
// Rounded virtual measurements can otherwise clip a framed row's outer paint.
|
||||
"overflow-clip-margin": row()._tag === "TurnGap" ? undefined : "0.5px",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
|
||||
@@ -169,6 +169,8 @@ export type EventLogSynced = { type: "log.synced"; aggregateID: string; seq?: nu
|
||||
|
||||
export type ModelReasoningField = "reasoning" | "reasoning_content" | "reasoning_text" | (string & {})
|
||||
|
||||
export type ModelMaxTokensField = "max_completion_tokens" | "max_tokens"
|
||||
|
||||
export type ModelCapabilities = { tools: boolean; input: Array<string>; output: Array<string> }
|
||||
|
||||
export type ModelVariant = {
|
||||
@@ -1230,7 +1232,11 @@ export type SessionToolCalled = {
|
||||
|
||||
export type ToolContent1 = ToolTextContent | ToolFileContent1
|
||||
|
||||
export type ModelCompatibility = { reasoningField?: ModelReasoningField }
|
||||
export type ModelCompatibility = {
|
||||
reasoningField?: ModelReasoningField
|
||||
maxTokensField?: ModelMaxTokensField
|
||||
requireFinishReason?: boolean
|
||||
}
|
||||
|
||||
export type ModelCost = {
|
||||
tier?: { type: "context"; size: number }
|
||||
|
||||
@@ -237,7 +237,11 @@ describe("ConfigProviderPlugin.Plugin", () => {
|
||||
models: {
|
||||
chat: {
|
||||
name: "First",
|
||||
compatibility: { reasoningField: "vendor_reasoning" },
|
||||
compatibility: {
|
||||
reasoningField: "vendor_reasoning",
|
||||
maxTokensField: "max_completion_tokens",
|
||||
requireFinishReason: false,
|
||||
},
|
||||
capabilities: { tools: true, input: ["text"], output: ["text"] },
|
||||
disabled: true,
|
||||
limit: { context: 100, output: 50 },
|
||||
@@ -318,7 +322,11 @@ describe("ConfigProviderPlugin.Plugin", () => {
|
||||
expect(model.id).toBe(modelID)
|
||||
expect(model.modelID).toBe(Model.ID.make("api-chat"))
|
||||
expect(model.name).toBe("Last")
|
||||
expect(model.compatibility).toEqual({ reasoningField: "vendor_reasoning" })
|
||||
expect(model.compatibility).toEqual({
|
||||
reasoningField: "vendor_reasoning",
|
||||
maxTokensField: "max_completion_tokens",
|
||||
requireFinishReason: false,
|
||||
})
|
||||
expect(model.capabilities).toEqual({ tools: true, input: ["text"], output: ["text"] })
|
||||
expect(model.enabled).toBe(false)
|
||||
expect(model.limit).toEqual({ context: 100, output: 75 })
|
||||
|
||||
@@ -194,7 +194,11 @@ describe("ModelResolver", () => {
|
||||
Effect.gen(function* () {
|
||||
const resolved = yield* ModelResolver.fromCatalogModel(
|
||||
model(Provider.aisdk("@ai-sdk/openai-compatible"), {
|
||||
compatibility: { reasoningField: "vendor_reasoning" },
|
||||
compatibility: {
|
||||
reasoningField: "vendor_reasoning",
|
||||
maxTokensField: "max_completion_tokens",
|
||||
requireFinishReason: false,
|
||||
},
|
||||
settings: {
|
||||
apiKey: "settings-secret",
|
||||
baseURL: "https://compatible.example/v1",
|
||||
@@ -204,7 +208,8 @@ describe("ModelResolver", () => {
|
||||
body: {},
|
||||
}),
|
||||
)
|
||||
const request = LLM.request({ model: resolved, prompt: "Hello" })
|
||||
const request = LLM.request({ model: resolved, prompt: "Hello", generation: { maxTokens: 10 } })
|
||||
const prepared = yield* compileRequest(request)
|
||||
const headers = yield* resolved.route.auth.apply({
|
||||
request,
|
||||
method: "POST",
|
||||
@@ -216,6 +221,10 @@ describe("ModelResolver", () => {
|
||||
expect(headers.authorization).toBe("Bearer settings-secret")
|
||||
expect(resolved.route.id).toBe("openai-compatible-chat")
|
||||
expect(resolved.compatibility?.reasoningField).toBe("vendor_reasoning")
|
||||
expect(resolved.compatibility?.maxTokensField).toBe("max_completion_tokens")
|
||||
expect(resolved.compatibility?.requireFinishReason).toBe(false)
|
||||
expect(prepared.body).toMatchObject({ max_completion_tokens: 10 })
|
||||
expect(prepared.body).not.toHaveProperty("max_tokens")
|
||||
expect(resolved.route.endpoint.baseURL).toBe("https://compatible.example/v1")
|
||||
expect(resolved.route.defaults.http?.body).toEqual({})
|
||||
}),
|
||||
|
||||
@@ -47,9 +47,16 @@ export const ReasoningField: Schema.Codec<ReasoningField> = Schema.Union([
|
||||
Schema.String,
|
||||
]).annotate({ identifier: "Model.ReasoningField" })
|
||||
|
||||
export const MaxTokensField = Schema.Literals(["max_completion_tokens", "max_tokens"]).annotate({
|
||||
identifier: "Model.MaxTokensField",
|
||||
})
|
||||
export type MaxTokensField = typeof MaxTokensField.Type
|
||||
|
||||
export interface Compatibility extends Schema.Schema.Type<typeof Compatibility> {}
|
||||
export const Compatibility = Schema.Struct({
|
||||
reasoningField: ReasoningField.pipe(optional),
|
||||
maxTokensField: MaxTokensField.pipe(optional),
|
||||
requireFinishReason: Schema.Boolean.pipe(optional),
|
||||
}).annotate({ identifier: "Model.Compatibility" })
|
||||
|
||||
export interface Capabilities extends Schema.Schema.Type<typeof Capabilities> {}
|
||||
|
||||
@@ -30,3 +30,22 @@ describe("Model.ReasoningField", () => {
|
||||
expect(decode(field)).toBe(field)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Model.Compatibility", () => {
|
||||
test("decodes model compatibility overrides", () => {
|
||||
const decode = Schema.decodeUnknownSync(Model.Compatibility)
|
||||
|
||||
expect(decode({})).toEqual({})
|
||||
expect(
|
||||
decode({
|
||||
reasoningField: "vendor_reasoning",
|
||||
maxTokensField: "max_completion_tokens",
|
||||
requireFinishReason: false,
|
||||
}),
|
||||
).toEqual({
|
||||
reasoningField: "vendor_reasoning",
|
||||
maxTokensField: "max_completion_tokens",
|
||||
requireFinishReason: false,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useTerminalDimensions } from "@opentui/solid"
|
||||
import { createMemo, createResource, createSignal, For, Show } from "solid-js"
|
||||
import { renderUnicodeCompact } from "uqr"
|
||||
import { useClient } from "../context/client"
|
||||
import { Keymap } from "../context/keymap"
|
||||
import { useTheme } from "../context/theme"
|
||||
import { useDialog } from "../ui/dialog"
|
||||
import { errorMessage } from "../util/error"
|
||||
@@ -20,6 +21,22 @@ export function DialogPair(props: { credentials?: DialogPairCredentials }) {
|
||||
const [loadError, setLoadError] = createSignal<unknown>()
|
||||
const [showPassword, setShowPassword] = createSignal(false)
|
||||
const [passwordHover, setPasswordHover] = createSignal(false)
|
||||
const shortcuts = Keymap.useShortcuts()
|
||||
const togglePassword = () => {
|
||||
setShowPassword((current) => !current)
|
||||
}
|
||||
|
||||
Keymap.createLayer(() => ({
|
||||
mode: "modal",
|
||||
commands: [
|
||||
{
|
||||
id: "dialog.pair.toggle_password",
|
||||
title: showPassword() ? "Hide pairing password" : "Show pairing password",
|
||||
group: "Dialog",
|
||||
run: togglePassword,
|
||||
},
|
||||
],
|
||||
}))
|
||||
|
||||
dialog.setSize("large")
|
||||
dialog.setCentered(true)
|
||||
@@ -61,10 +78,14 @@ export function DialogPair(props: { credentials?: DialogPairCredentials }) {
|
||||
wrapMode="word"
|
||||
onMouseOver={() => setPasswordHover(true)}
|
||||
onMouseOut={() => setPasswordHover(false)}
|
||||
onMouseUp={() => setShowPassword((current) => !current)}
|
||||
onMouseUp={togglePassword}
|
||||
>
|
||||
{showPassword() ? value.password : "************"}
|
||||
</text>
|
||||
<text fg={theme.text.default} onMouseUp={togglePassword}>
|
||||
{shortcuts.get("dialog.pair.toggle_password")} {" "}
|
||||
<span style={{ fg: theme.text.subdued }}>{showPassword() ? "hide password" : "show password"}</span>
|
||||
</text>
|
||||
</box>
|
||||
<Show when={value.urls.some((url) => ["localhost", "127.0.0.1", "[::1]"].includes(new URL(url).hostname))}>
|
||||
<text fg={theme.text.subdued} wrapMode="word">
|
||||
|
||||
@@ -220,6 +220,7 @@ export const Definitions = {
|
||||
"dialog.move_session.new": keybind("ctrl+m", "New project copy"),
|
||||
"dialog.move_session.delete": keybind("ctrl+d", "Delete project copy"),
|
||||
"dialog.move_session.refresh": keybind("ctrl+r", "Refresh project copies"),
|
||||
"dialog.pair.toggle_password": keybind("space", "Show or hide pairing password"),
|
||||
"prompt.autocomplete.prev": keybind("up,ctrl+p", "Move to previous autocomplete item"),
|
||||
"prompt.autocomplete.next": keybind("down,ctrl+n", "Move to next autocomplete item"),
|
||||
"prompt.autocomplete.hide": keybind("escape", "Hide autocomplete"),
|
||||
|
||||
@@ -5,3 +5,7 @@ test("binds agent cycling only to shift+tab by default", () => {
|
||||
expect(TuiKeybind.Definitions.agent_cycle.default).toBe("shift+tab")
|
||||
expect(TuiKeybind.Definitions.agent_cycle_reverse.default).toBe("none")
|
||||
})
|
||||
|
||||
test("binds pairing password visibility to space by default", () => {
|
||||
expect(TuiKeybind.Definitions["dialog.pair.toggle_password"].default).toBe("space")
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user