-
Notifications
You must be signed in to change notification settings - Fork 13
OpenCode v1.1.48 #118
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
OpenCode v1.1.48 #118
Changes from all commits
Commits
Show all changes
43 commits
Select commit
Hold shift + click to select a range
f51bd28
ci: increase ARM runner to 8 vCPUs for faster Tauri builds
thdxr 4a56491
fix(provider): exclude chat models from textVerbosity setting (#11363)
R44VC0RP 77fa8dd
refactor(app): refactored tests + added project tests (#11349)
neriousy 2c36cbb
refactor(provider): remove google-vertex-anthropic special case in ge…
MichaelYochpaz e7ff714
fix: handle redirected_statement treesitter node in bash permissions …
pschiel 9d3f320
test: add llm.test.ts (#11375)
rekram1-node e834a2e
docs: update agents options section to include color and top_p option…
IdrisGit 1a6461e
fix: ensure ask question tool isn't included when using acp (#11379)
rekram1-node aef0e58
chore(deps): bump ai-sdk packages (#11383)
rekram1-node 0c32afb
fix(provider): use snake_case for thinking param with OpenAI-compatib…
Chesars 252b2c4
chore: generate
opencode-agent[bot] 8512655
feat: make skills invokable as slash commands in the TUI
thdxr f1caf84
feat(build): respect OPENCODE_MODELS_URL env var (#11384)
bbartels 3542f3e
Revert "feat: make skills invokable as slash commands in the TUI"
thdxr 2f4374c
Merge remote dev and apply revert
thdxr d9f18e4
feat(opencode): add copilot specific provider to properly handle copi…
SteffenDE 644f0d4
chore: generate
opencode-agent[bot] 571f5b3
ci: schedule beta workflow hourly to automate sync runs
thdxr 73c4d36
ci: allow manual beta workflow trigger so users can release on demand…
thdxr d713026
ci: remove workflow restrictions to allow all PR triggers for broader…
thdxr b6bbb95
ci: remove pull-request write permissions from beta workflow
thdxr 95bf01a
fix: ensure the mistral ordering fixes also apply to devstral (#11412)
rekram1-node 90f39bf
core: prevent parallel test runs from contaminating environment varia…
thdxr 507f13a
ci: run tests automatically when code is pushed to dev branch
thdxr c0e71c4
fix: don't --follow by default for grep and other things using ripgre…
rekram1-node 81ac41e
feat: make skills invokable as slash commands in the TUI (#11390)
thdxr 46122d9
chore: generate
opencode-agent[bot] d005e70
core: ensure models configuration is not empty before loading
thdxr 8e5db30
ci: copy models fixture for e2e test consistency
thdxr 6ecd011
tui: allow specifying custom models file path via OPENCODE_MODELS_PATH
thdxr 6b97232
sync
thdxr 65c21f8
chore: generate
opencode-agent[bot] f834915
test: fix flaky test (#11427)
rekram1-node 511c7ab
test(app): session actions (#11386)
neriousy a552652
Revert "feat: Transitions, spacing, scroll fade, prompt area update (…
adamdotdevin 597ae57
release: v1.1.48
b0b442d
refactor: kilo compat for v1.1.48
catrielmuller 1a0b007
feat: merge opencode v1.1.48
catrielmuller e49b1ec
refactor: remove merge report
catrielmuller 1b9e8cf
refactor: use kilo gateway on go to session actions
catrielmuller e92365e
Merge branch 'dev' into catrielmuller/kilo-opencode-v1.1.48
catrielmuller 8dce77d
refactor: fix e2e test
catrielmuller 17e39c5
refactor: disable share e2e test
catrielmuller File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,271 @@ | ||
| import { expect, type Locator, type Page } from "@playwright/test" | ||
| import fs from "node:fs/promises" | ||
| import os from "node:os" | ||
| import path from "node:path" | ||
| import { execSync } from "node:child_process" | ||
| import { modKey, serverUrl } from "./utils" | ||
| import { | ||
| sessionItemSelector, | ||
| dropdownMenuTriggerSelector, | ||
| dropdownMenuContentSelector, | ||
| titlebarRightSelector, | ||
| popoverBodySelector, | ||
| listItemSelector, | ||
| listItemKeySelector, | ||
| listItemKeyStartsWithSelector, | ||
| } from "./selectors" | ||
| import type { createSdk } from "./utils" | ||
|
|
||
| export async function defocus(page: Page) { | ||
| await page.mouse.click(5, 5) | ||
| } | ||
|
|
||
| export async function openPalette(page: Page) { | ||
| await defocus(page) | ||
| await page.keyboard.press(`${modKey}+P`) | ||
|
|
||
| const dialog = page.getByRole("dialog") | ||
| await expect(dialog).toBeVisible() | ||
| await expect(dialog.getByRole("textbox").first()).toBeVisible() | ||
| return dialog | ||
| } | ||
|
|
||
| export async function closeDialog(page: Page, dialog: Locator) { | ||
| await page.keyboard.press("Escape") | ||
| const closed = await dialog | ||
| .waitFor({ state: "detached", timeout: 1500 }) | ||
| .then(() => true) | ||
| .catch(() => false) | ||
|
|
||
| if (closed) return | ||
|
|
||
| await page.keyboard.press("Escape") | ||
| const closedSecond = await dialog | ||
| .waitFor({ state: "detached", timeout: 1500 }) | ||
| .then(() => true) | ||
| .catch(() => false) | ||
|
|
||
| if (closedSecond) return | ||
|
|
||
| await page.locator('[data-component="dialog-overlay"]').click({ position: { x: 5, y: 5 } }) | ||
| await expect(dialog).toHaveCount(0) | ||
| } | ||
|
|
||
| export async function isSidebarClosed(page: Page) { | ||
| const main = page.locator("main") | ||
| const classes = (await main.getAttribute("class")) ?? "" | ||
| return classes.includes("xl:border-l") | ||
| } | ||
|
|
||
| export async function toggleSidebar(page: Page) { | ||
| await defocus(page) | ||
| await page.keyboard.press(`${modKey}+B`) | ||
| } | ||
|
|
||
| export async function openSidebar(page: Page) { | ||
| if (!(await isSidebarClosed(page))) return | ||
| await toggleSidebar(page) | ||
| await expect(page.locator("main")).not.toHaveClass(/xl:border-l/) | ||
| } | ||
|
|
||
| export async function closeSidebar(page: Page) { | ||
| if (await isSidebarClosed(page)) return | ||
| await toggleSidebar(page) | ||
| await expect(page.locator("main")).toHaveClass(/xl:border-l/) | ||
| } | ||
|
|
||
| export async function openSettings(page: Page) { | ||
| await defocus(page) | ||
|
|
||
| const dialog = page.getByRole("dialog") | ||
| await page.keyboard.press(`${modKey}+Comma`).catch(() => undefined) | ||
|
|
||
| const opened = await dialog | ||
| .waitFor({ state: "visible", timeout: 3000 }) | ||
| .then(() => true) | ||
| .catch(() => false) | ||
|
|
||
| if (opened) return dialog | ||
|
|
||
| await page.getByRole("button", { name: "Settings" }).first().click() | ||
| await expect(dialog).toBeVisible() | ||
| return dialog | ||
| } | ||
|
|
||
| export async function seedProjects(page: Page, input: { directory: string; extra?: string[] }) { | ||
| await page.addInitScript( | ||
| (args: { directory: string; serverUrl: string; extra: string[] }) => { | ||
| const key = "opencode.global.dat:server" | ||
| const raw = localStorage.getItem(key) | ||
| const parsed = (() => { | ||
| if (!raw) return undefined | ||
| try { | ||
| return JSON.parse(raw) as unknown | ||
| } catch { | ||
| return undefined | ||
| } | ||
| })() | ||
|
|
||
| const store = parsed && typeof parsed === "object" ? (parsed as Record<string, unknown>) : {} | ||
| const list = Array.isArray(store.list) ? store.list : [] | ||
| const lastProject = store.lastProject && typeof store.lastProject === "object" ? store.lastProject : {} | ||
| const projects = store.projects && typeof store.projects === "object" ? store.projects : {} | ||
| const nextProjects = { ...(projects as Record<string, unknown>) } | ||
|
|
||
| const add = (origin: string, directory: string) => { | ||
| const current = nextProjects[origin] | ||
| const items = Array.isArray(current) ? current : [] | ||
| const existing = items.filter( | ||
| (p): p is { worktree: string; expanded?: boolean } => | ||
| !!p && | ||
| typeof p === "object" && | ||
| "worktree" in p && | ||
| typeof (p as { worktree?: unknown }).worktree === "string", | ||
| ) | ||
|
|
||
| if (existing.some((p) => p.worktree === directory)) return | ||
| nextProjects[origin] = [{ worktree: directory, expanded: true }, ...existing] | ||
| } | ||
|
|
||
| const directories = [args.directory, ...args.extra] | ||
| for (const directory of directories) { | ||
| add("local", directory) | ||
| add(args.serverUrl, directory) | ||
| } | ||
|
|
||
| localStorage.setItem( | ||
| key, | ||
| JSON.stringify({ | ||
| list, | ||
| projects: nextProjects, | ||
| lastProject, | ||
| }), | ||
| ) | ||
| }, | ||
| { directory: input.directory, serverUrl, extra: input.extra ?? [] }, | ||
| ) | ||
| } | ||
|
|
||
| export async function createTestProject() { | ||
| const root = await fs.mkdtemp(path.join(os.tmpdir(), "opencode-e2e-project-")) | ||
|
|
||
| await fs.writeFile(path.join(root, "README.md"), "# e2e\n") | ||
|
|
||
| execSync("git init", { cwd: root, stdio: "ignore" }) | ||
| execSync("git add -A", { cwd: root, stdio: "ignore" }) | ||
| execSync('git -c user.name="e2e" -c user.email="e2e@example.com" commit -m "init" --allow-empty', { | ||
| cwd: root, | ||
| stdio: "ignore", | ||
| }) | ||
|
|
||
| return root | ||
| } | ||
|
|
||
| export async function cleanupTestProject(directory: string) { | ||
| await fs.rm(directory, { recursive: true, force: true }).catch(() => undefined) | ||
| } | ||
|
|
||
| export function sessionIDFromUrl(url: string) { | ||
| const match = /\/session\/([^/?#]+)/.exec(url) | ||
| return match?.[1] | ||
| } | ||
|
|
||
| export async function hoverSessionItem(page: Page, sessionID: string) { | ||
| const sessionEl = page.locator(sessionItemSelector(sessionID)).first() | ||
| await expect(sessionEl).toBeVisible() | ||
| await sessionEl.hover() | ||
| return sessionEl | ||
| } | ||
|
|
||
| export async function openSessionMoreMenu(page: Page, sessionID: string) { | ||
| const sessionEl = await hoverSessionItem(page, sessionID) | ||
|
|
||
| const menuTrigger = sessionEl.locator(dropdownMenuTriggerSelector).first() | ||
| await expect(menuTrigger).toBeVisible() | ||
| await menuTrigger.click() | ||
|
|
||
| const menu = page.locator(dropdownMenuContentSelector).first() | ||
| await expect(menu).toBeVisible() | ||
| return menu | ||
| } | ||
|
|
||
| export async function clickMenuItem(menu: Locator, itemName: string | RegExp, options?: { force?: boolean }) { | ||
| const item = menu.getByRole("menuitem").filter({ hasText: itemName }).first() | ||
| await expect(item).toBeVisible() | ||
| await item.click({ force: options?.force }) | ||
| } | ||
|
|
||
| export async function confirmDialog(page: Page, buttonName: string | RegExp) { | ||
| const dialog = page.getByRole("dialog").first() | ||
| await expect(dialog).toBeVisible() | ||
|
|
||
| const button = dialog.getByRole("button").filter({ hasText: buttonName }).first() | ||
| await expect(button).toBeVisible() | ||
| await button.click() | ||
| } | ||
|
|
||
| export async function openSharePopover(page: Page) { | ||
| const rightSection = page.locator(titlebarRightSelector) | ||
| const shareButton = rightSection.getByRole("button", { name: "Share" }).first() | ||
| await expect(shareButton).toBeVisible() | ||
|
|
||
| const popoverBody = page | ||
| .locator(popoverBodySelector) | ||
| .filter({ has: page.getByRole("button", { name: /^(Publish|Unpublish)$/ }) }) | ||
| .first() | ||
|
|
||
| const opened = await popoverBody | ||
| .isVisible() | ||
| .then((x) => x) | ||
| .catch(() => false) | ||
|
|
||
| if (!opened) { | ||
| await shareButton.click() | ||
| await expect(popoverBody).toBeVisible() | ||
| } | ||
| return { rightSection, popoverBody } | ||
| } | ||
|
|
||
| export async function clickPopoverButton(page: Page, buttonName: string | RegExp) { | ||
| const button = page.getByRole("button").filter({ hasText: buttonName }).first() | ||
| await expect(button).toBeVisible() | ||
| await button.click() | ||
| } | ||
|
|
||
| export async function clickListItem( | ||
| container: Locator | Page, | ||
| filter: string | RegExp | { key?: string; text?: string | RegExp; keyStartsWith?: string }, | ||
| ): Promise<Locator> { | ||
| let item: Locator | ||
|
|
||
| if (typeof filter === "string" || filter instanceof RegExp) { | ||
| item = container.locator(listItemSelector).filter({ hasText: filter }).first() | ||
| } else if (filter.keyStartsWith) { | ||
| item = container.locator(listItemKeyStartsWithSelector(filter.keyStartsWith)).first() | ||
| } else if (filter.key) { | ||
| item = container.locator(listItemKeySelector(filter.key)).first() | ||
| } else if (filter.text) { | ||
| item = container.locator(listItemSelector).filter({ hasText: filter.text }).first() | ||
| } else { | ||
| throw new Error("Invalid filter provided to clickListItem") | ||
| } | ||
|
|
||
| await expect(item).toBeVisible() | ||
| await item.click() | ||
| return item | ||
| } | ||
|
|
||
| export async function withSession<T>( | ||
| sdk: ReturnType<typeof createSdk>, | ||
| title: string, | ||
| callback: (session: { id: string; title: string }) => Promise<T>, | ||
| ): Promise<T> { | ||
| const session = await sdk.session.create({ title }).then((r) => r.data) | ||
| if (!session?.id) throw new Error("Session create did not return an id") | ||
|
|
||
| try { | ||
| return await callback(session) | ||
| } finally { | ||
| await sdk.session.delete({ sessionID: session.id }).catch(() => undefined) | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
WARNING: Hourly
schedulewill still create workflow runs even though the job is disabledon.scheduletriggers a workflow run every hour (0 * * * *) regardless ofjobs.sync.if: false. Even with the job skipped, this can clutter Actions history and may count toward workflow-run limits. Consider removing the schedule while the job is disabled, or making it less frequent until the sync is re-enabled.