diff --git a/src/agents/utils.test.ts b/src/agents/utils.test.ts
index a37a8c710..5f3af762f 100644
--- a/src/agents/utils.test.ts
+++ b/src/agents/utils.test.ts
@@ -1,6 +1,6 @@
///
-import { describe, test, expect, beforeEach, afterEach, spyOn } from "bun:test"
+import { describe, test, expect, beforeEach, afterEach, spyOn, mock } from "bun:test"
import { createBuiltinAgents } from "./builtin-agents"
import type { AgentConfig } from "@opencode-ai/sdk"
import { clearSkillCache } from "../features/opencode-skill-loader/skill-content"
@@ -10,6 +10,18 @@ import * as shared from "../shared"
const TEST_DEFAULT_MODEL = "anthropic/claude-opus-4-6"
+beforeEach(() => {
+ mock.restore()
+ clearSkillCache()
+ connectedProvidersCache._resetMemCacheForTesting()
+})
+
+afterEach(() => {
+ clearSkillCache()
+ connectedProvidersCache._resetMemCacheForTesting()
+ mock.restore()
+})
+
describe("createBuiltinAgents with model overrides", () => {
test("Sisyphus with default model has thinking config when all models available", async () => {
// #given
diff --git a/src/shared/connected-providers-cache.test.ts b/src/shared/connected-providers-cache.test.ts
index 73c905d25..1f20255de 100644
--- a/src/shared/connected-providers-cache.test.ts
+++ b/src/shared/connected-providers-cache.test.ts
@@ -1,148 +1,171 @@
///
-import { beforeEach, afterEach, describe, expect, test } from "bun:test"
+import { describe, expect, test } from "bun:test"
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"
import { tmpdir } from "node:os"
import { join } from "node:path"
-import {
- createConnectedProvidersCacheStore,
- findProviderModelMetadata,
-} from "./connected-providers-cache"
+import { createConnectedProvidersCacheStore, findProviderModelMetadata } from "./connected-providers-cache"
-let fakeUserCacheRoot = ""
-let testCacheDir = ""
-let testCacheStore: ReturnType
+function createTestCacheContext() {
+ const fakeUserCacheRoot = mkdtempSync(join(tmpdir(), "connected-providers-user-cache-"))
+ const testCacheDir = join(fakeUserCacheRoot, "oh-my-opencode")
+ const testCacheStore = createConnectedProvidersCacheStore(() => testCacheDir)
+
+ return {
+ fakeUserCacheRoot,
+ testCacheDir,
+ testCacheStore,
+ }
+}
+
+function cleanupTestCacheContext(fakeUserCacheRoot: string): void {
+ if (existsSync(fakeUserCacheRoot)) {
+ rmSync(fakeUserCacheRoot, { recursive: true, force: true })
+ }
+}
describe("updateConnectedProvidersCache", () => {
- beforeEach(() => {
- fakeUserCacheRoot = mkdtempSync(join(tmpdir(), "connected-providers-user-cache-"))
- testCacheDir = join(fakeUserCacheRoot, "oh-my-opencode")
- testCacheStore = createConnectedProvidersCacheStore(() => testCacheDir)
- })
-
- afterEach(() => {
- if (existsSync(fakeUserCacheRoot)) {
- rmSync(fakeUserCacheRoot, { recursive: true, force: true })
- }
- fakeUserCacheRoot = ""
- testCacheDir = ""
- })
-
test("extracts models from provider.list().all response", async () => {
- //#given
- const mockClient = {
- provider: {
- list: async () => ({
- data: {
- connected: ["openai", "anthropic"],
- all: [
- {
- id: "openai",
- name: "OpenAI",
- env: [],
- models: {
- "gpt-5.3-codex": { id: "gpt-5.3-codex", name: "GPT-5.3 Codex" },
- "gpt-5.4": { id: "gpt-5.4", name: "GPT-5.4" },
+ const { testCacheStore, fakeUserCacheRoot } = createTestCacheContext()
+
+ try {
+ //#given
+ const mockClient = {
+ provider: {
+ list: async () => ({
+ data: {
+ connected: ["openai", "anthropic"],
+ all: [
+ {
+ id: "openai",
+ name: "OpenAI",
+ env: [],
+ models: {
+ "gpt-5.3-codex": { id: "gpt-5.3-codex", name: "GPT-5.3 Codex" },
+ "gpt-5.4": { id: "gpt-5.4", name: "GPT-5.4" },
+ },
},
- },
- {
- id: "anthropic",
- name: "Anthropic",
- env: [],
- models: {
- "claude-opus-4-6": { id: "claude-opus-4-6", name: "Claude Opus 4.6" },
- "claude-sonnet-4-6": { id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6" },
+ {
+ id: "anthropic",
+ name: "Anthropic",
+ env: [],
+ models: {
+ "claude-opus-4-6": { id: "claude-opus-4-6", name: "Claude Opus 4.6" },
+ "claude-sonnet-4-6": { id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6" },
+ },
},
- },
- ],
- },
- }),
- },
+ ],
+ },
+ }),
+ },
+ }
+
+ //#when
+ await testCacheStore.updateConnectedProvidersCache(mockClient)
+
+ //#then
+ const cache = testCacheStore.readProviderModelsCache()
+ expect(cache).not.toBeNull()
+ expect(cache!.connected).toEqual(["openai", "anthropic"])
+ expect(cache!.models).toEqual({
+ openai: [
+ { id: "gpt-5.3-codex", name: "GPT-5.3 Codex" },
+ { id: "gpt-5.4", name: "GPT-5.4" },
+ ],
+ anthropic: [
+ { id: "claude-opus-4-6", name: "Claude Opus 4.6" },
+ { id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6" },
+ ],
+ })
+ } finally {
+ cleanupTestCacheContext(fakeUserCacheRoot)
}
-
- //#when
- await testCacheStore.updateConnectedProvidersCache(mockClient)
-
- //#then
- const cache = testCacheStore.readProviderModelsCache()
- expect(cache).not.toBeNull()
- expect(cache!.connected).toEqual(["openai", "anthropic"])
- expect(cache!.models).toEqual({
- openai: [
- { id: "gpt-5.3-codex", name: "GPT-5.3 Codex" },
- { id: "gpt-5.4", name: "GPT-5.4" },
- ],
- anthropic: [
- { id: "claude-opus-4-6", name: "Claude Opus 4.6" },
- { id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6" },
- ],
- })
})
test("writes empty models when provider has no models", async () => {
- //#given
- const mockClient = {
- provider: {
- list: async () => ({
- data: {
- connected: ["empty-provider"],
- all: [
- {
- id: "empty-provider",
- name: "Empty",
- env: [],
- models: {},
- },
- ],
- },
- }),
- },
+ const { testCacheStore, fakeUserCacheRoot } = createTestCacheContext()
+
+ try {
+ //#given
+ const mockClient = {
+ provider: {
+ list: async () => ({
+ data: {
+ connected: ["empty-provider"],
+ all: [
+ {
+ id: "empty-provider",
+ name: "Empty",
+ env: [],
+ models: {},
+ },
+ ],
+ },
+ }),
+ },
+ }
+
+ //#when
+ await testCacheStore.updateConnectedProvidersCache(mockClient)
+
+ //#then
+ const cache = testCacheStore.readProviderModelsCache()
+ expect(cache).not.toBeNull()
+ expect(cache!.models).toEqual({})
+ } finally {
+ cleanupTestCacheContext(fakeUserCacheRoot)
}
-
- //#when
- await testCacheStore.updateConnectedProvidersCache(mockClient)
-
- //#then
- const cache = testCacheStore.readProviderModelsCache()
- expect(cache).not.toBeNull()
- expect(cache!.models).toEqual({})
})
test("writes empty models when all field is missing", async () => {
- //#given
- const mockClient = {
- provider: {
- list: async () => ({
- data: {
- connected: ["openai"],
- },
- }),
- },
+ const { testCacheStore, fakeUserCacheRoot } = createTestCacheContext()
+
+ try {
+ //#given
+ const mockClient = {
+ provider: {
+ list: async () => ({
+ data: {
+ connected: ["openai"],
+ },
+ }),
+ },
+ }
+
+ //#when
+ await testCacheStore.updateConnectedProvidersCache(mockClient)
+
+ //#then
+ const cache = testCacheStore.readProviderModelsCache()
+ expect(cache).not.toBeNull()
+ expect(cache!.models).toEqual({})
+ } finally {
+ cleanupTestCacheContext(fakeUserCacheRoot)
}
-
- //#when
- await testCacheStore.updateConnectedProvidersCache(mockClient)
-
- //#then
- const cache = testCacheStore.readProviderModelsCache()
- expect(cache).not.toBeNull()
- expect(cache!.models).toEqual({})
})
test("does nothing when client.provider.list is not available", async () => {
- //#given
- const mockClient = {}
+ const { testCacheStore, fakeUserCacheRoot } = createTestCacheContext()
- //#when
- await testCacheStore.updateConnectedProvidersCache(mockClient)
+ try {
+ //#given
+ const mockClient = {}
- //#then
- const cache = testCacheStore.readProviderModelsCache()
- expect(cache).toBeNull()
+ //#when
+ await testCacheStore.updateConnectedProvidersCache(mockClient)
+
+ //#then
+ const cache = testCacheStore.readProviderModelsCache()
+ expect(cache).toBeNull()
+ } finally {
+ cleanupTestCacheContext(fakeUserCacheRoot)
+ }
})
test("does not remove unrelated files in the cache directory", async () => {
+ const { testCacheStore, fakeUserCacheRoot } = createTestCacheContext()
+
//#given
const realCacheDir = join(fakeUserCacheRoot, "oh-my-opencode")
const sentinelPath = join(realCacheDir, "connected-providers-cache.test-sentinel.json")
@@ -179,88 +202,101 @@ describe("updateConnectedProvidersCache", () => {
if (existsSync(sentinelPath)) {
rmSync(sentinelPath, { force: true })
}
+ cleanupTestCacheContext(fakeUserCacheRoot)
}
})
test("findProviderModelMetadata returns rich cached metadata", async () => {
- //#given
- const mockClient = {
- provider: {
- list: async () => ({
- data: {
- connected: ["openai"],
- all: [
- {
- id: "openai",
- models: {
- "gpt-5.4": {
- id: "gpt-5.4",
- name: "GPT-5.4",
- temperature: false,
- variants: {
- low: {},
- high: {},
+ const { testCacheStore, fakeUserCacheRoot } = createTestCacheContext()
+
+ try {
+ //#given
+ const mockClient = {
+ provider: {
+ list: async () => ({
+ data: {
+ connected: ["openai"],
+ all: [
+ {
+ id: "openai",
+ models: {
+ "gpt-5.4": {
+ id: "gpt-5.4",
+ name: "GPT-5.4",
+ temperature: false,
+ variants: {
+ low: {},
+ high: {},
+ },
+ limit: { output: 128000 },
},
- limit: { output: 128000 },
},
},
- },
- ],
- },
- }),
- },
+ ],
+ },
+ }),
+ },
+ }
+
+ await testCacheStore.updateConnectedProvidersCache(mockClient)
+ const cache = testCacheStore.readProviderModelsCache()
+
+ //#when
+ const result = findProviderModelMetadata("openai", "gpt-5.4", cache)
+
+ //#then
+ expect(result).toEqual({
+ id: "gpt-5.4",
+ name: "GPT-5.4",
+ temperature: false,
+ variants: {
+ low: {},
+ high: {},
+ },
+ limit: { output: 128000 },
+ })
+ } finally {
+ cleanupTestCacheContext(fakeUserCacheRoot)
}
-
- await testCacheStore.updateConnectedProvidersCache(mockClient)
- const cache = testCacheStore.readProviderModelsCache()
-
- //#when
- const result = findProviderModelMetadata("openai", "gpt-5.4", cache)
-
- //#then
- expect(result).toEqual({
- id: "gpt-5.4",
- name: "GPT-5.4",
- temperature: false,
- variants: {
- low: {},
- high: {},
- },
- limit: { output: 128000 },
- })
})
test("keeps normalized fallback ids when raw metadata id is not a string", async () => {
- const mockClient = {
- provider: {
- list: async () => ({
- data: {
- connected: ["openai"],
- all: [
- {
- id: "openai",
- models: {
- "o3-mini": {
- id: 123,
- name: "o3-mini",
+ const { testCacheStore, fakeUserCacheRoot } = createTestCacheContext()
+
+ try {
+ const mockClient = {
+ provider: {
+ list: async () => ({
+ data: {
+ connected: ["openai"],
+ all: [
+ {
+ id: "openai",
+ models: {
+ "o3-mini": {
+ id: 123,
+ name: "o3-mini",
+ },
},
},
- },
- ],
- },
- }),
- },
+ ],
+ },
+ }),
+ },
+ }
+
+ await testCacheStore.updateConnectedProvidersCache(mockClient)
+ const cache = testCacheStore.readProviderModelsCache()
+
+ expect(cache?.models.openai).toEqual([
+ { id: "o3-mini", name: "o3-mini" },
+ ])
+ expect(findProviderModelMetadata("openai", "o3-mini", cache)).toEqual({
+ id: "o3-mini",
+ name: "o3-mini",
+ })
+ } finally {
+ cleanupTestCacheContext(fakeUserCacheRoot)
}
-
- await testCacheStore.updateConnectedProvidersCache(mockClient)
- const cache = testCacheStore.readProviderModelsCache()
-
- expect(cache?.models.openai).toEqual([
- { id: "o3-mini", name: "o3-mini" },
- ])
- expect(findProviderModelMetadata("openai", "o3-mini", cache)).toEqual({
- id: "o3-mini",
- name: "o3-mini",
- })
})
})
diff --git a/src/shared/legacy-plugin-warning.test.ts b/src/shared/legacy-plugin-warning.test.ts
index 47e8a39a9..48ea8194f 100644
--- a/src/shared/legacy-plugin-warning.test.ts
+++ b/src/shared/legacy-plugin-warning.test.ts
@@ -1,81 +1,110 @@
-import { afterEach, beforeEach, describe, expect, it } from "bun:test"
+import { describe, expect, it } from "bun:test"
import { mkdirSync, rmSync, writeFileSync } from "node:fs"
import { tmpdir } from "node:os"
import { join } from "node:path"
import { checkForLegacyPluginEntry } from "./legacy-plugin-warning"
+function createTestConfigDir(): string {
+ const testConfigDir = join(tmpdir(), `omo-legacy-check-${Date.now()}-${Math.random().toString(36).slice(2)}`)
+ mkdirSync(testConfigDir, { recursive: true })
+ return testConfigDir
+}
+
+function cleanupTestConfigDir(testConfigDir: string): void {
+ rmSync(testConfigDir, { recursive: true, force: true })
+}
+
describe("checkForLegacyPluginEntry", () => {
- let testConfigDir = ""
-
- beforeEach(() => {
- testConfigDir = join(tmpdir(), `omo-legacy-check-${Date.now()}-${Math.random().toString(36).slice(2)}`)
- mkdirSync(testConfigDir, { recursive: true })
- })
-
- afterEach(() => {
- rmSync(testConfigDir, { recursive: true, force: true })
- })
-
it("detects a bare legacy plugin entry", () => {
- // given
- writeFileSync(join(testConfigDir, "opencode.json"), JSON.stringify({ plugin: ["oh-my-opencode"] }, null, 2))
+ const testConfigDir = createTestConfigDir()
- // when
- const result = checkForLegacyPluginEntry(testConfigDir)
+ try {
+ // given
+ writeFileSync(join(testConfigDir, "opencode.json"), JSON.stringify({ plugin: ["oh-my-opencode"] }, null, 2))
- // then
- expect(result.hasLegacyEntry).toBe(true)
- expect(result.hasCanonicalEntry).toBe(false)
- expect(result.legacyEntries).toEqual(["oh-my-opencode"])
- expect(result.configPath).toBe(join(testConfigDir, "opencode.json"))
+ // when
+ const result = checkForLegacyPluginEntry(testConfigDir)
+
+ // then
+ expect(result.hasLegacyEntry).toBe(true)
+ expect(result.hasCanonicalEntry).toBe(false)
+ expect(result.legacyEntries).toEqual(["oh-my-opencode"])
+ expect(result.configPath).toBe(join(testConfigDir, "opencode.json"))
+ } finally {
+ cleanupTestConfigDir(testConfigDir)
+ }
})
it("detects a version-pinned legacy plugin entry", () => {
- // given
- writeFileSync(join(testConfigDir, "opencode.json"), JSON.stringify({ plugin: ["oh-my-opencode@3.10.0"] }, null, 2))
+ const testConfigDir = createTestConfigDir()
- // when
- const result = checkForLegacyPluginEntry(testConfigDir)
+ try {
+ // given
+ writeFileSync(join(testConfigDir, "opencode.json"), JSON.stringify({ plugin: ["oh-my-opencode@3.10.0"] }, null, 2))
- // then
- expect(result.hasLegacyEntry).toBe(true)
- expect(result.hasCanonicalEntry).toBe(false)
- expect(result.legacyEntries).toEqual(["oh-my-opencode@3.10.0"])
+ // when
+ const result = checkForLegacyPluginEntry(testConfigDir)
+
+ // then
+ expect(result.hasLegacyEntry).toBe(true)
+ expect(result.hasCanonicalEntry).toBe(false)
+ expect(result.legacyEntries).toEqual(["oh-my-opencode@3.10.0"])
+ } finally {
+ cleanupTestConfigDir(testConfigDir)
+ }
})
it("does not flag a canonical plugin entry", () => {
- // given
- writeFileSync(join(testConfigDir, "opencode.json"), JSON.stringify({ plugin: ["oh-my-openagent"] }, null, 2))
+ const testConfigDir = createTestConfigDir()
- // when
- const result = checkForLegacyPluginEntry(testConfigDir)
+ try {
+ // given
+ writeFileSync(join(testConfigDir, "opencode.json"), JSON.stringify({ plugin: ["oh-my-openagent"] }, null, 2))
- // then
- expect(result.hasLegacyEntry).toBe(false)
- expect(result.hasCanonicalEntry).toBe(true)
- expect(result.legacyEntries).toEqual([])
+ // when
+ const result = checkForLegacyPluginEntry(testConfigDir)
+
+ // then
+ expect(result.hasLegacyEntry).toBe(false)
+ expect(result.hasCanonicalEntry).toBe(true)
+ expect(result.legacyEntries).toEqual([])
+ } finally {
+ cleanupTestConfigDir(testConfigDir)
+ }
})
it("detects legacy entries in quoted jsonc config", () => {
- // given
- writeFileSync(join(testConfigDir, "opencode.jsonc"), '{\n "plugin": ["oh-my-opencode"]\n}\n')
+ const testConfigDir = createTestConfigDir()
- // when
- const result = checkForLegacyPluginEntry(testConfigDir)
+ try {
+ // given
+ writeFileSync(join(testConfigDir, "opencode.jsonc"), '{\n "plugin": ["oh-my-opencode"]\n}\n')
- // then
- expect(result.hasLegacyEntry).toBe(true)
- expect(result.legacyEntries).toEqual(["oh-my-opencode"])
+ // when
+ const result = checkForLegacyPluginEntry(testConfigDir)
+
+ // then
+ expect(result.hasLegacyEntry).toBe(true)
+ expect(result.legacyEntries).toEqual(["oh-my-opencode"])
+ } finally {
+ cleanupTestConfigDir(testConfigDir)
+ }
})
it("returns no warning data when config is missing", () => {
- // when
- const result = checkForLegacyPluginEntry(testConfigDir)
+ const testConfigDir = createTestConfigDir()
- // then
- expect(result.hasLegacyEntry).toBe(false)
- expect(result.hasCanonicalEntry).toBe(false)
- expect(result.legacyEntries).toEqual([])
- expect(result.configPath).toBeNull()
+ try {
+ // when
+ const result = checkForLegacyPluginEntry(testConfigDir)
+
+ // then
+ expect(result.hasLegacyEntry).toBe(false)
+ expect(result.hasCanonicalEntry).toBe(false)
+ expect(result.legacyEntries).toEqual([])
+ expect(result.configPath).toBeNull()
+ } finally {
+ cleanupTestConfigDir(testConfigDir)
+ }
})
})