Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a524fc545c | |||
| 4316edaf43 | |||
| d845924e8b | |||
| a29b322bdd | |||
| 9723ffa7a6 | |||
| f06cd88773 | |||
| 9af92b6914 | |||
| 8f64c4b312 | |||
| a32877e908 | |||
| 6465c9c44a |
@@ -152,6 +152,7 @@ if (!snapshot) {
|
||||
for (const pkg of ["opencode", "opencode-bin"]) {
|
||||
await $`rm -rf ./dist/aur-${pkg}`
|
||||
await $`git clone ssh://aur@aur.archlinux.org/${pkg}.git ./dist/aur-${pkg}`
|
||||
await $`cd ./dist/aur-${pkg} && git checkout master`
|
||||
await Bun.file(`./dist/aur-${pkg}/PKGBUILD`).write(pkgbuild.replace("${pkg}", pkg))
|
||||
await $`cd ./dist/aur-${pkg} && makepkg --printsrcinfo > .SRCINFO`
|
||||
await $`cd ./dist/aur-${pkg} && git add PKGBUILD .SRCINFO`
|
||||
|
||||
@@ -4,11 +4,12 @@ import path from "path"
|
||||
|
||||
export const AuthCopilot = lazy(async () => {
|
||||
const file = Bun.file(path.join(Global.Path.state, "plugin", "copilot.ts"))
|
||||
const exists = await file.exists()
|
||||
const response = fetch("https://raw.githubusercontent.com/sst/opencode-github-copilot/refs/heads/main/auth.ts")
|
||||
.then((x) => Bun.write(file, x))
|
||||
.catch(() => {})
|
||||
|
||||
if (!file.exists()) {
|
||||
if (!exists) {
|
||||
const worked = await response
|
||||
if (!worked) return
|
||||
}
|
||||
|
||||
@@ -31,9 +31,13 @@ export namespace ConfigHooks {
|
||||
}
|
||||
})
|
||||
|
||||
Bus.subscribe(Session.Event.Idle, async () => {
|
||||
Bus.subscribe(Session.Event.Idle, async (payload) => {
|
||||
const cfg = await Config.get()
|
||||
if (cfg.experimental?.hook?.session_completed) {
|
||||
const session = await Session.get(payload.properties.sessionID)
|
||||
// Only fire hook for top-level sessions (not subagent sessions)
|
||||
if (session.parentID) return
|
||||
|
||||
for (const item of cfg.experimental.hook.session_completed) {
|
||||
log.info("session_completed", {
|
||||
command: item.command,
|
||||
|
||||
@@ -13,22 +13,15 @@ export namespace ProviderTransform {
|
||||
anthropic: {
|
||||
cacheControl: { type: "ephemeral" },
|
||||
},
|
||||
openaiCompatible: {
|
||||
openrouter: {
|
||||
cache_control: { type: "ephemeral" },
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
if (providerID === "amazon-bedrock" || modelID.includes("anthropic")) {
|
||||
const system = msgs.filter((msg) => msg.role === "system").slice(0, 2)
|
||||
const final = msgs.filter((msg) => msg.role !== "system").slice(-2)
|
||||
|
||||
for (const msg of unique([...system, ...final])) {
|
||||
msg.providerOptions = {
|
||||
...msg.providerOptions,
|
||||
bedrock: {
|
||||
cachePoint: { type: "ephemeral" },
|
||||
},
|
||||
openaiCompatible: {
|
||||
cache_control: { type: "ephemeral" },
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,6 +325,7 @@ export namespace Session {
|
||||
providerID: z.string(),
|
||||
modelID: z.string(),
|
||||
mode: z.string().optional(),
|
||||
tools: z.record(z.boolean()).optional(),
|
||||
parts: z.array(
|
||||
z.discriminatedUnion("type", [
|
||||
MessageV2.TextPart.omit({
|
||||
@@ -618,6 +619,7 @@ export namespace Session {
|
||||
|
||||
for (const item of await Provider.tools(input.providerID)) {
|
||||
if (mode.tools[item.id] === false) continue
|
||||
if (input.tools?.[item.id] === false) continue
|
||||
if (session.parentID && item.id === "task") continue
|
||||
tools[item.id] = tool({
|
||||
id: item.id as any,
|
||||
|
||||
@@ -41,6 +41,10 @@ export const TaskTool = Tool.define({
|
||||
sessionID: session.id,
|
||||
modelID: msg.modelID,
|
||||
providerID: msg.providerID,
|
||||
tools: {
|
||||
todoread: false,
|
||||
todowrite: false,
|
||||
},
|
||||
parts: [
|
||||
{
|
||||
id: Identifier.ascending("part"),
|
||||
|
||||
@@ -91,6 +91,9 @@ func main() {
|
||||
stream := httpClient.Event.ListStreaming(ctx)
|
||||
for stream.Next() {
|
||||
evt := stream.Current().AsUnion()
|
||||
if _, ok := evt.(opencode.EventListResponseEventStorageWrite); ok {
|
||||
continue
|
||||
}
|
||||
program.Send(evt)
|
||||
}
|
||||
if err := stream.Err(); err != nil {
|
||||
|
||||
@@ -53,6 +53,8 @@ func (m *messagesComponent) Init() tea.Cmd {
|
||||
}
|
||||
|
||||
func (m *messagesComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
measure := util.Measure("messages.Update")
|
||||
defer measure("from", fmt.Sprintf("%T", msg))
|
||||
var cmds []tea.Cmd
|
||||
switch msg := msg.(type) {
|
||||
case tea.WindowSizeMsg:
|
||||
@@ -102,9 +104,7 @@ func (m *messagesComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
m.loading = false
|
||||
m.tail = m.viewport.AtBottom()
|
||||
m.viewport = msg.viewport
|
||||
if m.tail {
|
||||
m.viewport.GotoBottom()
|
||||
}
|
||||
m.header = msg.header
|
||||
if m.dirty {
|
||||
cmds = append(cmds, m.renderView())
|
||||
}
|
||||
@@ -120,12 +120,12 @@ func (m *messagesComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
|
||||
type renderCompleteMsg struct {
|
||||
viewport viewport.Model
|
||||
header string
|
||||
partCount int
|
||||
lineCount int
|
||||
}
|
||||
|
||||
func (m *messagesComponent) renderView() tea.Cmd {
|
||||
m.header = m.renderHeader()
|
||||
|
||||
if m.rendering {
|
||||
slog.Debug("pending render, skipping")
|
||||
@@ -138,8 +138,10 @@ func (m *messagesComponent) renderView() tea.Cmd {
|
||||
m.rendering = true
|
||||
|
||||
viewport := m.viewport
|
||||
tail := m.tail
|
||||
|
||||
return func() tea.Msg {
|
||||
header := m.renderHeader()
|
||||
measure := util.Measure("messages.renderView")
|
||||
defer measure()
|
||||
|
||||
@@ -402,8 +404,12 @@ func (m *messagesComponent) renderView() tea.Cmd {
|
||||
content := "\n" + strings.Join(blocks, "\n\n")
|
||||
viewport.SetHeight(m.height - lipgloss.Height(m.header))
|
||||
viewport.SetContent(content)
|
||||
if tail {
|
||||
viewport.GotoBottom()
|
||||
}
|
||||
|
||||
return renderCompleteMsg{
|
||||
header: header,
|
||||
viewport: viewport,
|
||||
partCount: partCount,
|
||||
lineCount: lineCount,
|
||||
|
||||
@@ -103,7 +103,7 @@ func (a appModel) Init() tea.Cmd {
|
||||
}
|
||||
|
||||
func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
measure := util.Measure("Update")
|
||||
measure := util.Measure("app.Update")
|
||||
defer measure("from", fmt.Sprintf("%T", msg))
|
||||
|
||||
var cmd tea.Cmd
|
||||
@@ -528,17 +528,17 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
}
|
||||
|
||||
func (a appModel) View() string {
|
||||
measure := util.Measure("app.View")
|
||||
defer measure()
|
||||
t := theme.CurrentTheme()
|
||||
|
||||
var mainLayout string
|
||||
|
||||
measure := util.Measure("app.View")
|
||||
if a.app.Session.ID == "" {
|
||||
mainLayout = a.home()
|
||||
} else {
|
||||
mainLayout = a.chat()
|
||||
}
|
||||
measure()
|
||||
mainLayout = styles.NewStyle().
|
||||
Background(t.Background()).
|
||||
Padding(0, 2).
|
||||
|
||||
@@ -43,17 +43,37 @@ func New(opts ...Option) (m Model) {
|
||||
opt(&m)
|
||||
}
|
||||
m.setInitialValues()
|
||||
m.memo = &Memo{}
|
||||
return m
|
||||
}
|
||||
|
||||
type Memo struct {
|
||||
dirty bool
|
||||
cache string
|
||||
}
|
||||
|
||||
func (m *Memo) View(render func() string) string {
|
||||
if m.dirty {
|
||||
// slog.Debug("memo dirty")
|
||||
m.cache = render()
|
||||
m.dirty = false
|
||||
return m.cache
|
||||
}
|
||||
// slog.Debug("memo cache")
|
||||
return m.cache
|
||||
}
|
||||
|
||||
func (m *Memo) Invalidate() {
|
||||
m.dirty = true
|
||||
}
|
||||
|
||||
// Model is the Bubble Tea model for this viewport element.
|
||||
type Model struct {
|
||||
memo *Memo
|
||||
width int
|
||||
height int
|
||||
KeyMap KeyMap
|
||||
|
||||
cache string
|
||||
|
||||
// Whether or not to wrap text. If false, it'll allow horizontal scrolling
|
||||
// instead.
|
||||
SoftWrap bool
|
||||
@@ -160,6 +180,7 @@ func (m Model) Height() int {
|
||||
// SetHeight sets the height of the viewport.
|
||||
func (m *Model) SetHeight(h int) {
|
||||
m.height = h
|
||||
m.memo.Invalidate()
|
||||
}
|
||||
|
||||
// Width returns the width of the viewport.
|
||||
@@ -170,6 +191,7 @@ func (m Model) Width() int {
|
||||
// SetWidth sets the width of the viewport.
|
||||
func (m *Model) SetWidth(w int) {
|
||||
m.width = w
|
||||
m.memo.Invalidate()
|
||||
}
|
||||
|
||||
// AtTop returns whether or not the viewport is at the very top position.
|
||||
@@ -220,7 +242,7 @@ func (m Model) HorizontalScrollPercent() float64 {
|
||||
func (m *Model) SetContent(s string) {
|
||||
s = strings.ReplaceAll(s, "\r\n", "\n") // normalize line endings
|
||||
m.SetContentLines(strings.Split(s, "\n"))
|
||||
m.render()
|
||||
m.memo.Invalidate()
|
||||
}
|
||||
|
||||
// SetContentLines allows to set the lines to be shown instead of the content.
|
||||
@@ -239,7 +261,7 @@ func (m *Model) SetContentLines(lines []string) {
|
||||
if m.YOffset > m.maxYOffset() {
|
||||
m.GotoBottom()
|
||||
}
|
||||
m.render()
|
||||
m.memo.Invalidate()
|
||||
}
|
||||
|
||||
// GetContent returns the entire content as a single string.
|
||||
@@ -442,12 +464,14 @@ func (m Model) setupGutter(lines []string) []string {
|
||||
}
|
||||
result[i] = strings.Join(line, "\n")
|
||||
}
|
||||
m.memo.Invalidate()
|
||||
return result
|
||||
}
|
||||
|
||||
// SetYOffset sets the Y offset.
|
||||
func (m *Model) SetYOffset(n int) {
|
||||
m.YOffset = clamp(n, 0, m.maxYOffset())
|
||||
m.memo.Invalidate()
|
||||
}
|
||||
|
||||
// SetXOffset sets the X offset.
|
||||
@@ -457,6 +481,7 @@ func (m *Model) SetXOffset(n int) {
|
||||
return
|
||||
}
|
||||
m.xOffset = clamp(n, 0, m.maxXOffset())
|
||||
m.memo.Invalidate()
|
||||
}
|
||||
|
||||
// EnsureVisible ensures that the given line and column are in the viewport.
|
||||
@@ -483,7 +508,7 @@ func (m *Model) ViewDown() {
|
||||
}
|
||||
|
||||
m.LineDown(m.Height())
|
||||
m.render()
|
||||
m.memo.Invalidate()
|
||||
}
|
||||
|
||||
// ViewUp moves the view up by one height of the viewport. Basically, "page up".
|
||||
@@ -493,7 +518,7 @@ func (m *Model) ViewUp() {
|
||||
}
|
||||
|
||||
m.LineUp(m.Height())
|
||||
m.render()
|
||||
m.memo.Invalidate()
|
||||
}
|
||||
|
||||
// HalfViewDown moves the view down by half the height of the viewport.
|
||||
@@ -503,7 +528,7 @@ func (m *Model) HalfViewDown() {
|
||||
}
|
||||
|
||||
m.LineDown(m.Height() / 2) //nolint:mnd
|
||||
m.render()
|
||||
m.memo.Invalidate()
|
||||
}
|
||||
|
||||
// HalfViewUp moves the view up by half the height of the viewport.
|
||||
@@ -513,7 +538,7 @@ func (m *Model) HalfViewUp() {
|
||||
}
|
||||
|
||||
m.LineUp(m.Height() / 2) //nolint:mnd
|
||||
m.render()
|
||||
m.memo.Invalidate()
|
||||
}
|
||||
|
||||
// LineDown moves the view down by the given number of lines.
|
||||
@@ -527,7 +552,7 @@ func (m *Model) LineDown(n int) {
|
||||
// the bottom.
|
||||
m.SetYOffset(m.YOffset + n)
|
||||
m.hiIdx = m.findNearedtMatch()
|
||||
m.render()
|
||||
m.memo.Invalidate()
|
||||
}
|
||||
|
||||
// LineUp moves the view down by the given number of lines. Returns the new
|
||||
@@ -541,7 +566,7 @@ func (m *Model) LineUp(n int) {
|
||||
// greater than the number of lines we are from the top.
|
||||
m.SetYOffset(m.YOffset - n)
|
||||
m.hiIdx = m.findNearedtMatch()
|
||||
m.render()
|
||||
m.memo.Invalidate()
|
||||
}
|
||||
|
||||
// TotalLineCount returns the total number of lines (both hidden and visible) within the viewport.
|
||||
@@ -562,7 +587,7 @@ func (m *Model) GotoTop() (lines []string) {
|
||||
|
||||
m.SetYOffset(0)
|
||||
m.hiIdx = m.findNearedtMatch()
|
||||
m.render()
|
||||
m.memo.Invalidate()
|
||||
return m.visibleLines()
|
||||
}
|
||||
|
||||
@@ -570,7 +595,7 @@ func (m *Model) GotoTop() (lines []string) {
|
||||
func (m *Model) GotoBottom() (lines []string) {
|
||||
m.SetYOffset(m.maxYOffset())
|
||||
m.hiIdx = m.findNearedtMatch()
|
||||
m.render()
|
||||
m.memo.Invalidate()
|
||||
return m.visibleLines()
|
||||
}
|
||||
|
||||
@@ -583,6 +608,7 @@ func (m *Model) SetHorizontalStep(n int) {
|
||||
}
|
||||
|
||||
m.horizontalStep = n
|
||||
m.memo.Invalidate()
|
||||
}
|
||||
|
||||
// MoveLeft moves the viewport to the left by the given number of columns.
|
||||
@@ -590,6 +616,7 @@ func (m *Model) MoveLeft(cols int) {
|
||||
m.xOffset -= cols
|
||||
if m.xOffset < 0 {
|
||||
m.xOffset = 0
|
||||
m.memo.Invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -606,6 +633,7 @@ func (m *Model) MoveRight(cols int) {
|
||||
// Resets lines indent to zero.
|
||||
func (m *Model) ResetIndent() {
|
||||
m.xOffset = 0
|
||||
m.memo.Invalidate()
|
||||
}
|
||||
|
||||
// SetHighlights sets ranges of characters to highlight.
|
||||
@@ -623,12 +651,14 @@ func (m *Model) SetHighlights(matches [][]int) {
|
||||
m.highlights = parseMatches(m.GetContent(), matches)
|
||||
m.hiIdx = m.findNearedtMatch()
|
||||
m.showHighlight()
|
||||
m.memo.Invalidate()
|
||||
}
|
||||
|
||||
// ClearHighlights clears previously set highlights.
|
||||
func (m *Model) ClearHighlights() {
|
||||
m.highlights = nil
|
||||
m.hiIdx = -1
|
||||
m.memo.Invalidate()
|
||||
}
|
||||
|
||||
func (m *Model) showHighlight() {
|
||||
@@ -637,6 +667,7 @@ func (m *Model) showHighlight() {
|
||||
}
|
||||
line, colstart, colend := m.highlights[m.hiIdx].coords()
|
||||
m.EnsureVisible(line, colstart, colend)
|
||||
m.memo.Invalidate()
|
||||
}
|
||||
|
||||
// HighlightNext highlights the next match.
|
||||
@@ -647,6 +678,7 @@ func (m *Model) HighlightNext() {
|
||||
|
||||
m.hiIdx = (m.hiIdx + 1) % len(m.highlights)
|
||||
m.showHighlight()
|
||||
m.memo.Invalidate()
|
||||
}
|
||||
|
||||
// HighlightPrevious highlights the previous match.
|
||||
@@ -657,6 +689,7 @@ func (m *Model) HighlightPrevious() {
|
||||
|
||||
m.hiIdx = (m.hiIdx - 1 + len(m.highlights)) % len(m.highlights)
|
||||
m.showHighlight()
|
||||
m.memo.Invalidate()
|
||||
}
|
||||
|
||||
func (m Model) findNearedtMatch() int {
|
||||
@@ -728,29 +761,30 @@ func (m Model) updateAsModel(msg tea.Msg) Model {
|
||||
|
||||
// View renders the viewport into a string.
|
||||
func (m *Model) render() {
|
||||
w, h := m.Width(), m.Height()
|
||||
if sw := m.Style.GetWidth(); sw != 0 {
|
||||
w = min(w, sw)
|
||||
}
|
||||
if sh := m.Style.GetHeight(); sh != 0 {
|
||||
h = min(h, sh)
|
||||
}
|
||||
contentWidth := w - m.Style.GetHorizontalFrameSize()
|
||||
contentHeight := h - m.Style.GetVerticalFrameSize()
|
||||
visible := m.visibleLines()
|
||||
contents := lipgloss.NewStyle().
|
||||
Width(contentWidth). // pad to width.
|
||||
Height(contentHeight). // pad to height.
|
||||
MaxHeight(contentHeight). // truncate height if taller.
|
||||
MaxWidth(contentWidth). // truncate width if wider.
|
||||
Render(strings.Join(visible, "\n"))
|
||||
m.cache = m.Style.
|
||||
UnsetWidth().UnsetHeight(). // Style size already applied in contents.
|
||||
Render(contents)
|
||||
}
|
||||
|
||||
func (m Model) View() string {
|
||||
return m.cache
|
||||
return m.memo.View(func() string {
|
||||
w, h := m.Width(), m.Height()
|
||||
if sw := m.Style.GetWidth(); sw != 0 {
|
||||
w = min(w, sw)
|
||||
}
|
||||
if sh := m.Style.GetHeight(); sh != 0 {
|
||||
h = min(h, sh)
|
||||
}
|
||||
contentWidth := w - m.Style.GetHorizontalFrameSize()
|
||||
contentHeight := h - m.Style.GetVerticalFrameSize()
|
||||
visible := m.visibleLines()
|
||||
contents := lipgloss.NewStyle().
|
||||
Width(contentWidth). // pad to width.
|
||||
Height(contentHeight). // pad to height.
|
||||
MaxHeight(contentHeight). // truncate height if taller.
|
||||
MaxWidth(contentWidth). // truncate width if wider.
|
||||
Render(strings.Join(visible, "\n"))
|
||||
return m.Style.
|
||||
UnsetWidth().UnsetHeight(). // Style size already applied in contents.
|
||||
Render(contents)
|
||||
})
|
||||
}
|
||||
|
||||
func clamp(v, low, high int) int {
|
||||
|
||||
Reference in New Issue
Block a user