Compare commits

...

7 commits

Author SHA1 Message Date
Dominik Engelhardt
d87922c0eb
Fix Elixir LSP startup (#726)
Some checks are pending
deploy / deploy (push) Waiting to run
publish / publish (push) Waiting to run
2025-07-06 23:37:46 -04:00
Liang-Shih Lin
2446483df5
fix: Skip opencode upgrade if same version (#720) 2025-07-06 23:36:59 -04:00
GitHub Action
f4c453155d Update download stats 2025-07-06
Some checks are pending
deploy / deploy (push) Waiting to run
publish / publish (push) Waiting to run
2025-07-06 12:03:56 +00:00
Dax Raad
969ad80ed2 fix openrouter caching with anthropic, should be a lot cheaper
Some checks are pending
deploy / deploy (push) Waiting to run
publish / publish (push) Waiting to run
2025-07-05 11:39:54 -04:00
GitHub Action
af064b41d7 Update download stats 2025-07-05
Some checks are pending
deploy / deploy (push) Waiting to run
publish / publish (push) Waiting to run
2025-07-05 12:03:56 +00:00
Dax Raad
ea6bfef21a use full filepath
Some checks are pending
deploy / deploy (push) Waiting to run
publish / publish (push) Waiting to run
2025-07-04 17:58:03 -04:00
Jay V
107363b1d9 docs: fix show more in share page
Some checks failed
deploy / deploy (push) Has been cancelled
2025-07-04 17:57:12 -04:00
9 changed files with 105 additions and 130 deletions

View file

@ -8,3 +8,5 @@
| 2025-07-02 | 24,814 (+2,706) | 46,168 (+2,423) | 70,982 (+5,129) |
| 2025-07-03 | 27,834 (+3,020) | 49,955 (+3,787) | 77,789 (+6,807) |
| 2025-07-04 | 30,608 (+2,774) | 54,758 (+4,803) | 85,366 (+7,577) |
| 2025-07-05 | 32,524 (+1,916) | 58,371 (+3,613) | 90,895 (+5,529) |
| 2025-07-06 | 33,766 (+1,242) | 59,694 (+1,323) | 93,460 (+2,565) |

View file

@ -35,6 +35,15 @@ export const UpgradeCommand = {
}
prompts.log.info("Using method: " + method)
const target = args.target ?? (await Installation.latest())
if (Installation.VERSION === target) {
prompts.log.warn(
`opencode upgrade skipped: ${target} is already installed`,
)
prompts.outro("Done")
return
}
prompts.log.info(`From ${Installation.VERSION}${target}`)
const spinner = prompts.spinner()
spinner.start("Upgrading...")

View file

@ -1,6 +1,8 @@
import { App } from "../app/app"
import { Log } from "../util/log"
export namespace FileTime {
const log = Log.create({ service: "file.time" })
export const state = App.state("tool.filetimes", () => {
const read: {
[sessionID: string]: {
@ -13,6 +15,7 @@ export namespace FileTime {
})
export function read(sessionID: string, file: string) {
log.info("read", { sessionID, file })
const { read } = state()
read[sessionID] = read[sessionID] || {}
read[sessionID][file] = new Date()

View file

@ -66,6 +66,7 @@ export namespace LSPClient {
log.info("sending initialize", { id: serverID })
await withTimeout(
connection.sendRequest("initialize", {
rootUri: "file://" + app.path.cwd,
processId: server.process.pid,
workspaceFolders: [
{

View file

@ -17,6 +17,9 @@ export namespace ProviderTransform {
anthropic: {
cacheControl: { type: "ephemeral" },
},
openaiCompatible: {
cache_control: { type: "ephemeral" },
},
}
}
}

View file

@ -1,41 +1,42 @@
import path from "node:path"
import { App } from "../app/app"
import { Identifier } from "../id/id"
import { Storage } from "../storage/storage"
import { Log } from "../util/log"
import { Decimal } from "decimal.js"
import { z, ZodSchema } from "zod"
import {
generateText,
LoadAPIKeyError,
convertToCoreMessages,
streamText,
tool,
wrapLanguageModel,
type Tool as AITool,
type LanguageModelUsage,
type CoreMessage,
type UIMessage,
type ProviderMetadata,
wrapLanguageModel,
type Attachment,
} from "ai"
import { z, ZodSchema } from "zod"
import { Decimal } from "decimal.js"
import PROMPT_INITIALIZE from "../session/prompt/initialize.txt"
import { Share } from "../share/share"
import { Message } from "./message"
import { App } from "../app/app"
import { Bus } from "../bus"
import { Provider } from "../provider/provider"
import { MCP } from "../mcp"
import { NamedError } from "../util/error"
import type { Tool } from "../tool/tool"
import { SystemPrompt } from "./system"
import { Flag } from "../flag/flag"
import type { ModelsDev } from "../provider/models"
import { Installation } from "../installation"
import { Config } from "../config/config"
import { Flag } from "../flag/flag"
import { Identifier } from "../id/id"
import { Installation } from "../installation"
import { MCP } from "../mcp"
import { Provider } from "../provider/provider"
import { ProviderTransform } from "../provider/transform"
import type { ModelsDev } from "../provider/models"
import { Share } from "../share/share"
import { Snapshot } from "../snapshot"
import { Storage } from "../storage/storage"
import type { Tool } from "../tool/tool"
import { Log } from "../util/log"
import { NamedError } from "../util/error"
import { Message } from "./message"
import { SystemPrompt } from "./system"
import { FileTime } from "../file/time"
export namespace Session {
const log = Log.create({ service: "session" })
@ -367,10 +368,11 @@ export namespace Session {
const url = new URL(part.url)
switch (url.protocol) {
case "file:":
let content = Bun.file(path.join(app.path.cwd, url.pathname))
const filepath = path.join(app.path.cwd, url.pathname)
let file = Bun.file(filepath)
if (part.mediaType === "text/plain") {
let text = await content.text()
let text = await file.text()
const range = {
start: url.searchParams.get("start"),
end: url.searchParams.get("end"),
@ -381,6 +383,7 @@ export namespace Session {
const end = range.end ? parseInt(range.end) : lines.length
text = lines.slice(start, end).join("\n")
}
FileTime.read(input.sessionID, filepath)
return [
{
type: "text",
@ -403,9 +406,9 @@ export namespace Session {
type: "file",
url:
`data:${part.mediaType};base64,` +
Buffer.from(await content.bytes()).toString("base64url"),
Buffer.from(await file.bytes()).toString("base64url"),
mediaType: part.mediaType,
filename: path.basename(part.filename!),
filename: part.filename!,
},
]
}

View file

@ -92,7 +92,6 @@ func (m *editorComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// Now, insert the attachment at the position where the '@' was.
// The cursor is now at `atIndex` after the replacement.
filePath := msg.CompletionValue
fileName := filepath.Base(filePath)
extension := filepath.Ext(filePath)
mediaType := ""
switch extension {
@ -107,7 +106,7 @@ func (m *editorComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
attachment := &textarea.Attachment{
ID: uuid.NewString(),
Display: "@" + fileName,
Display: "@" + filePath,
URL: fmt.Sprintf("file://./%s", filePath),
Filename: filePath,
MediaType: mediaType,

View file

@ -243,6 +243,44 @@ function getStatusText(status: [Status, string?]): string {
}
}
function checkOverflow(getEl: () => HTMLElement | undefined, watch?: () => any) {
const [needsToggle, setNeedsToggle] = createSignal(false)
function measure() {
const el = getEl()
if (!el) return
setNeedsToggle(el.scrollHeight > el.clientHeight + 1)
}
onMount(() => {
let raf = 0
function probe() {
const el = getEl()
if (el && el.offsetParent !== null && el.getBoundingClientRect().height) {
measure()
}
else {
raf = requestAnimationFrame(probe)
}
}
raf = requestAnimationFrame(probe)
const ro = new ResizeObserver(measure)
const el = getEl()
if (el) ro.observe(el)
onCleanup(() => {
cancelAnimationFrame(raf)
ro.disconnect()
})
})
if (watch) createEffect(measure)
return needsToggle
}
function ProviderIcon(props: { provider: string; size?: number }) {
const size = props.size || 16
return (
@ -296,34 +334,11 @@ interface TextPartProps extends JSX.HTMLAttributes<HTMLDivElement> {
expand?: boolean
}
function TextPart(props: TextPartProps) {
const [local, rest] = splitProps(props, [
"text",
"expand",
])
const [expanded, setExpanded] = createSignal(false)
const [overflowed, setOverflowed] = createSignal(false)
let preEl: HTMLPreElement | undefined
function checkOverflow() {
if (preEl && !local.expand) {
setOverflowed(preEl.scrollHeight > preEl.clientHeight + 1)
}
}
onMount(() => {
checkOverflow()
window.addEventListener("resize", checkOverflow)
})
createEffect(() => {
local.text
local.expand
setTimeout(checkOverflow, 0)
})
onCleanup(() => {
window.removeEventListener("resize", checkOverflow)
})
const [local, rest] = splitProps(props, ["text", "expand"])
const [expanded, setExpanded] = createSignal(false)
const overflowed = checkOverflow(() => preEl, () => local.expand)
return (
<div
@ -331,7 +346,7 @@ function TextPart(props: TextPartProps) {
data-expanded={expanded() || local.expand === true}
{...rest}
>
<pre ref={(el) => (preEl = el)}>{local.text}</pre>
<pre ref={preEl}>{local.text}</pre>
{((!local.expand && overflowed()) || expanded()) && (
<button
type="button"
@ -349,31 +364,11 @@ interface ErrorPartProps extends JSX.HTMLAttributes<HTMLDivElement> {
expand?: boolean
}
function ErrorPart(props: ErrorPartProps) {
let preEl: HTMLDivElement | undefined
const [local, rest] = splitProps(props, ["expand", "children"])
const [expanded, setExpanded] = createSignal(false)
const [overflowed, setOverflowed] = createSignal(false)
let preEl: HTMLElement | undefined
function checkOverflow() {
if (preEl && !local.expand) {
setOverflowed(preEl.scrollHeight > preEl.clientHeight + 1)
}
}
onMount(() => {
checkOverflow()
window.addEventListener("resize", checkOverflow)
})
createEffect(() => {
local.children
local.expand
setTimeout(checkOverflow, 0)
})
onCleanup(() => {
window.removeEventListener("resize", checkOverflow)
})
const overflowed = checkOverflow(() => preEl, () => local.expand)
return (
<div
@ -381,7 +376,7 @@ function ErrorPart(props: ErrorPartProps) {
data-expanded={expanded() || local.expand === true}
{...rest}
>
<div data-section="content" ref={(el) => (preEl = el)}>
<div data-section="content" ref={preEl}>
{local.children}
</div>
{((!local.expand && overflowed()) || expanded()) && (
@ -403,31 +398,11 @@ interface MarkdownPartProps extends JSX.HTMLAttributes<HTMLDivElement> {
highlight?: boolean
}
function MarkdownPart(props: MarkdownPartProps) {
const [local, rest] = splitProps(props, ["text", "expand", "highlight"])
const [expanded, setExpanded] = createSignal(false)
const [overflowed, setOverflowed] = createSignal(false)
let divEl: HTMLDivElement | undefined
function checkOverflow() {
if (divEl && !local.expand) {
setOverflowed(divEl.scrollHeight > divEl.clientHeight + 1)
}
}
onMount(() => {
checkOverflow()
window.addEventListener("resize", checkOverflow)
})
createEffect(() => {
local.text
local.expand
setTimeout(checkOverflow, 0)
})
onCleanup(() => {
window.removeEventListener("resize", checkOverflow)
})
const [local, rest] = splitProps(props, ["text", "expand", "highlight"])
const [expanded, setExpanded] = createSignal(false)
const overflowed = checkOverflow(() => divEl, () => local.expand)
return (
<div
@ -469,36 +444,16 @@ function TerminalPart(props: TerminalPartProps) {
"desc",
"expand",
])
let preEl: HTMLDivElement | undefined
const [expanded, setExpanded] = createSignal(false)
const [overflowed, setOverflowed] = createSignal(false)
let preEl: HTMLElement | undefined
function checkOverflow() {
if (!preEl) return
const code = preEl.getElementsByTagName("code")[0]
if (code && !local.expand) {
setOverflowed(preEl.clientHeight < code.offsetHeight)
}
}
createEffect(() => {
local.command
local.result
local.error
local.expand
setTimeout(checkOverflow, 0)
})
onMount(() => {
checkOverflow()
window.addEventListener("resize", checkOverflow)
})
onCleanup(() => {
window.removeEventListener("resize", checkOverflow)
})
const overflowed = checkOverflow(
() => {
if (!preEl) return
return preEl.getElementsByTagName("pre")[0]
},
() => local.expand
)
return (
<div
@ -515,16 +470,16 @@ function TerminalPart(props: TerminalPartProps) {
<Switch>
<Match when={local.error}>
<CodeBlock
data-section="error"
ref={preEl}
lang="text"
ref={(el) => (preEl = el)}
data-section="error"
code={local.error || ""}
/>
</Match>
<Match when={local.result}>
<CodeBlock
ref={preEl}
lang="console"
ref={(el) => (preEl = el)}
code={local.result || ""}
/>
</Match>

View file

@ -253,7 +253,7 @@
line-height: 18px;
font-size: 0.875rem;
color: var(--sl-color-text-secondary);
max-width: var(--sm-tool-width);
max-width: var(--md-tool-width);
display: flex;
align-items: flex-start;