Compare commits

...

2 Commits

Author SHA1 Message Date
Kit Langton efe9d91722 test: migrate provider lookup fixtures 2026-05-18 13:23:38 -04:00
Kit Langton a96b65f632 test: migrate custom provider fixtures 2026-05-18 13:23:35 -04:00
2 changed files with 152 additions and 227 deletions
+150 -227
View File
@@ -87,10 +87,6 @@ async function getModel(providerID: ProviderID, modelID: ModelID, ctx: InstanceC
return run(ctx, (provider) => provider.getModel(providerID, modelID)) return run(ctx, (provider) => provider.getModel(providerID, modelID))
} }
async function getLanguage(model: Provider.Model, ctx: InstanceContext) {
return run(ctx, (provider) => provider.getLanguage(model))
}
async function closest(providerID: ProviderID, query: string[], ctx: InstanceContext) { async function closest(providerID: ProviderID, query: string[], ctx: InstanceContext) {
return run(ctx, (provider) => provider.closest(providerID, query)) return run(ctx, (provider) => provider.closest(providerID, query))
} }
@@ -99,10 +95,6 @@ async function getSmallModel(providerID: ProviderID, ctx: InstanceContext) {
return run(ctx, (provider) => provider.getSmallModel(providerID)) return run(ctx, (provider) => provider.getSmallModel(providerID))
} }
async function defaultModel(ctx: InstanceContext) {
return run(ctx, (provider) => provider.defaultModel())
}
function paid(providers: Awaited<ReturnType<typeof list>>) { function paid(providers: Awaited<ReturnType<typeof list>>) {
const item = providers[ProviderID.make("opencode")] const item = providers[ProviderID.make("opencode")]
expect(item).toBeDefined() expect(item).toBeDefined()
@@ -228,81 +220,65 @@ it.instance(
}, },
) )
test("custom model alias via config", async () => { it.instance(
await using tmp = await tmpdir({ "custom model alias via config",
init: async (dir) => { Effect.gen(function* () {
await Bun.write( yield* setProcessEnv("ANTHROPIC_API_KEY", "test-api-key")
path.join(dir, "opencode.json"), const providers = yield* Provider.Service.use((provider) => provider.list())
JSON.stringify({ expect(providers[ProviderID.anthropic]).toBeDefined()
$schema: "https://opencode.ai/config.json", expect(providers[ProviderID.anthropic].models["my-alias"]).toBeDefined()
provider: { expect(providers[ProviderID.anthropic].models["my-alias"].name).toBe("My Custom Alias")
anthropic: { }),
models: { {
"my-alias": { config: {
id: "claude-sonnet-4-20250514", provider: {
name: "My Custom Alias", anthropic: {
}, models: {
}, "my-alias": {
id: "claude-sonnet-4-20250514",
name: "My Custom Alias",
}, },
}, },
}), },
) },
}, },
}) },
await withTestInstance({ )
directory: tmp.path,
fn: async (ctx) => {
await set(ctx, "ANTHROPIC_API_KEY", "test-api-key")
const providers = await list(ctx)
expect(providers[ProviderID.anthropic]).toBeDefined()
expect(providers[ProviderID.anthropic].models["my-alias"]).toBeDefined()
expect(providers[ProviderID.anthropic].models["my-alias"].name).toBe("My Custom Alias")
},
})
})
test("custom provider with npm package", async () => { it.instance(
await using tmp = await tmpdir({ "custom provider with npm package",
init: async (dir) => { Effect.gen(function* () {
await Bun.write( const providers = yield* Provider.Service.use((provider) => provider.list())
path.join(dir, "opencode.json"), expect(providers[ProviderID.make("custom-provider")]).toBeDefined()
JSON.stringify({ expect(providers[ProviderID.make("custom-provider")].name).toBe("Custom Provider")
$schema: "https://opencode.ai/config.json", expect(providers[ProviderID.make("custom-provider")].models["custom-model"]).toBeDefined()
provider: { }),
"custom-provider": { {
name: "Custom Provider", config: {
npm: "@ai-sdk/openai-compatible", provider: {
api: "https://api.custom.com/v1", "custom-provider": {
env: ["CUSTOM_API_KEY"], name: "Custom Provider",
models: { npm: "@ai-sdk/openai-compatible",
"custom-model": { api: "https://api.custom.com/v1",
name: "Custom Model", env: ["CUSTOM_API_KEY"],
tool_call: true, models: {
limit: { "custom-model": {
context: 128000, name: "Custom Model",
output: 4096, tool_call: true,
}, limit: {
}, context: 128000,
}, output: 4096,
options: {
apiKey: "custom-key",
}, },
}, },
}, },
}), options: {
) apiKey: "custom-key",
},
},
},
}, },
}) },
await withTestInstance({ )
directory: tmp.path,
fn: async (ctx) => {
const providers = await list(ctx)
expect(providers[ProviderID.make("custom-provider")]).toBeDefined()
expect(providers[ProviderID.make("custom-provider")].name).toBe("Custom Provider")
expect(providers[ProviderID.make("custom-provider")].models["custom-model"]).toBeDefined()
},
})
})
it.instance( it.instance(
"filters alpha provider models by default", "filters alpha provider models by default",
@@ -324,123 +300,95 @@ experimentalModels.instance(
{ config: alphaProviderConfig }, { config: alphaProviderConfig },
) )
test("custom DeepSeek openai-compatible model defaults interleaved reasoning field", async () => { it.instance(
await using tmp = await tmpdir({ "custom DeepSeek openai-compatible model defaults interleaved reasoning field",
init: async (dir) => { Effect.gen(function* () {
await Bun.write( const providers = yield* Provider.Service.use((provider) => provider.list())
path.join(dir, "opencode.json"), const provider = providers[ProviderID.make("custom-provider")]
JSON.stringify({ expect(provider.models["deepseek-r1"].capabilities.interleaved).toEqual({ field: "reasoning_content" })
$schema: "https://opencode.ai/config.json", expect(provider.models["deepseek-details"].capabilities.interleaved).toEqual({ field: "reasoning_details" })
provider: { expect(provider.models["custom-model"].capabilities.interleaved).toBe(false)
"custom-provider": { expect(providers[ProviderID.make("custom-anthropic-provider")].models["deepseek-r1"].capabilities.interleaved).toBe(
name: "Custom Provider", false,
npm: "@ai-sdk/openai-compatible", )
api: "https://api.custom.com/v1", }),
models: { {
"deepseek-r1": { config: {
name: "DeepSeek R1", provider: {
}, "custom-provider": {
"deepseek-details": { name: "Custom Provider",
name: "DeepSeek Details", npm: "@ai-sdk/openai-compatible",
interleaved: { field: "reasoning_details" }, api: "https://api.custom.com/v1",
}, models: {
"custom-model": { "deepseek-r1": {
name: "Custom Model", name: "DeepSeek R1",
},
},
options: {
apiKey: "custom-key",
},
}, },
"custom-anthropic-provider": { "deepseek-details": {
name: "Custom Anthropic Provider", name: "DeepSeek Details",
npm: "@ai-sdk/anthropic", interleaved: { field: "reasoning_details" },
api: "https://api.custom.com/v1", },
models: { "custom-model": {
"deepseek-r1": { name: "Custom Model",
name: "DeepSeek R1",
},
},
options: {
apiKey: "custom-key",
},
}, },
}, },
}), options: {
) apiKey: "custom-key",
}, },
}) },
await withTestInstance({ "custom-anthropic-provider": {
directory: tmp.path, name: "Custom Anthropic Provider",
fn: async (ctx) => { npm: "@ai-sdk/anthropic",
const providers = await list(ctx) api: "https://api.custom.com/v1",
const provider = providers[ProviderID.make("custom-provider")] models: {
expect(provider.models["deepseek-r1"].capabilities.interleaved).toEqual({ field: "reasoning_content" }) "deepseek-r1": {
expect(provider.models["deepseek-details"].capabilities.interleaved).toEqual({ field: "reasoning_details" }) name: "DeepSeek R1",
expect(provider.models["custom-model"].capabilities.interleaved).toBe(false)
expect(
providers[ProviderID.make("custom-anthropic-provider")].models["deepseek-r1"].capabilities.interleaved,
).toBe(false)
},
})
})
test("env variable takes precedence, config merges options", async () => {
await using tmp = await tmpdir({
init: async (dir) => {
await Bun.write(
path.join(dir, "opencode.json"),
JSON.stringify({
$schema: "https://opencode.ai/config.json",
provider: {
anthropic: {
options: {
timeout: 60000,
chunkTimeout: 15000,
},
}, },
}, },
}), options: {
) apiKey: "custom-key",
},
},
},
}, },
}) },
await withTestInstance({ )
directory: tmp.path,
fn: async (ctx) => {
await set(ctx, "ANTHROPIC_API_KEY", "env-api-key")
const providers = await list(ctx)
expect(providers[ProviderID.anthropic]).toBeDefined()
// Config options should be merged
expect(providers[ProviderID.anthropic].options.timeout).toBe(60000)
expect(providers[ProviderID.anthropic].options.chunkTimeout).toBe(15000)
},
})
})
test("getModel returns model for valid provider/model", async () => { it.instance(
await using tmp = await tmpdir({ "env variable takes precedence, config merges options",
init: async (dir) => { Effect.gen(function* () {
await Bun.write( yield* setProcessEnv("ANTHROPIC_API_KEY", "env-api-key")
path.join(dir, "opencode.json"), const providers = yield* Provider.Service.use((provider) => provider.list())
JSON.stringify({ expect(providers[ProviderID.anthropic]).toBeDefined()
$schema: "https://opencode.ai/config.json", // Config options should be merged
}), expect(providers[ProviderID.anthropic].options.timeout).toBe(60000)
) expect(providers[ProviderID.anthropic].options.chunkTimeout).toBe(15000)
}),
{
config: {
provider: {
anthropic: {
options: {
timeout: 60000,
chunkTimeout: 15000,
},
},
},
}, },
}) },
await withTestInstance({ )
directory: tmp.path,
fn: async (ctx) => { it.instance("getModel returns model for valid provider/model", () =>
await set(ctx, "ANTHROPIC_API_KEY", "test-api-key") Effect.gen(function* () {
const model = await getModel(ProviderID.anthropic, ModelID.make("claude-sonnet-4-20250514"), ctx) yield* setProcessEnv("ANTHROPIC_API_KEY", "test-api-key")
expect(model).toBeDefined() const provider = yield* Provider.Service
expect(String(model.providerID)).toBe("anthropic") const model = yield* provider.getModel(ProviderID.anthropic, ModelID.make("claude-sonnet-4-20250514"))
expect(String(model.id)).toBe("claude-sonnet-4-20250514") expect(model).toBeDefined()
const language = await getLanguage(model, ctx) expect(String(model.providerID)).toBe("anthropic")
expect(language).toBeDefined() expect(String(model.id)).toBe("claude-sonnet-4-20250514")
}, const language = yield* provider.getLanguage(model)
}) expect(language).toBeDefined()
}) }),
)
test("getModel throws ModelNotFoundError for invalid model", async () => { test("getModel throws ModelNotFoundError for invalid model", async () => {
await using tmp = await tmpdir({ await using tmp = await tmpdir({
@@ -493,50 +441,25 @@ test("parseModel handles model IDs with slashes", () => {
expect(String(result.modelID)).toBe("anthropic/claude-3-opus") expect(String(result.modelID)).toBe("anthropic/claude-3-opus")
}) })
test("defaultModel returns first available model when no config set", async () => { it.instance("defaultModel returns first available model when no config set", () =>
await using tmp = await tmpdir({ Effect.gen(function* () {
init: async (dir) => { yield* setProcessEnv("ANTHROPIC_API_KEY", "test-api-key")
await Bun.write( const model = yield* Provider.Service.use((provider) => provider.defaultModel())
path.join(dir, "opencode.json"), expect(model.providerID).toBeDefined()
JSON.stringify({ expect(model.modelID).toBeDefined()
$schema: "https://opencode.ai/config.json", }),
}), )
)
},
})
await withTestInstance({
directory: tmp.path,
fn: async (ctx) => {
await set(ctx, "ANTHROPIC_API_KEY", "test-api-key")
const model = await defaultModel(ctx)
expect(model.providerID).toBeDefined()
expect(model.modelID).toBeDefined()
},
})
})
test("defaultModel respects config model setting", async () => { it.instance(
await using tmp = await tmpdir({ "defaultModel respects config model setting",
init: async (dir) => { Effect.gen(function* () {
await Bun.write( yield* setProcessEnv("ANTHROPIC_API_KEY", "test-api-key")
path.join(dir, "opencode.json"), const model = yield* Provider.Service.use((provider) => provider.defaultModel())
JSON.stringify({ expect(String(model.providerID)).toBe("anthropic")
$schema: "https://opencode.ai/config.json", expect(String(model.modelID)).toBe("claude-sonnet-4-20250514")
model: "anthropic/claude-sonnet-4-20250514", }),
}), { config: { model: "anthropic/claude-sonnet-4-20250514" } },
) )
},
})
await withTestInstance({
directory: tmp.path,
fn: async (ctx) => {
await set(ctx, "ANTHROPIC_API_KEY", "test-api-key")
const model = await defaultModel(ctx)
expect(String(model.providerID)).toBe("anthropic")
expect(String(model.modelID)).toBe("claude-sonnet-4-20250514")
},
})
})
it.instance( it.instance(
"provider with baseURL from config", "provider with baseURL from config",
+2
View File
@@ -69,6 +69,8 @@ Repeated setup work, long sleeps/timeouts, serial integration tests, filesystem/
| HTTP listen PTY ticket tests restart the same listener topology twice | Folded directory-scoped ticket regression into the broader unsafe-ticket test | 7.051s | 6.170s | keep | Two targeted reruns passed after the change: 6.76s, 6.17s; still covers mint failure and successful same-directory upgrade. | | HTTP listen PTY ticket tests restart the same listener topology twice | Folded directory-scoped ticket regression into the broader unsafe-ticket test | 7.051s | 6.170s | keep | Two targeted reruns passed after the change: 6.76s, 6.17s; still covers mint failure and successful same-directory upgrade. |
| File watcher readiness can write before async native subscriptions are active | Retried short readiness writes and accepted symlink-realpath HEAD events | failed | 4.62s | keep | Three sequential focused watcher runs passed: 4.62s, 4.57s, 4.64s; full suite no longer failed in `watcher.test.ts`. | | File watcher readiness can write before async native subscriptions are active | Retried short readiness writes and accepted symlink-realpath HEAD events | failed | 4.62s | keep | Three sequential focused watcher runs passed: 4.62s, 4.57s, 4.64s; full suite no longer failed in `watcher.test.ts`. |
| First provider config/env/filtering block can use Effect-aware instance fixtures | Migrated six `tmpdir` + `withTestInstance` cases to `it.instance` | 6.06s | 6.07s | keep | Neutral timing, but removes manual config file writes and instance plumbing; use as the pattern for later provider slices. | | First provider config/env/filtering block can use Effect-aware instance fixtures | Migrated six `tmpdir` + `withTestInstance` cases to `it.instance` | 6.06s | 6.07s | keep | Neutral timing, but removes manual config file writes and instance plumbing; use as the pattern for later provider slices. |
| Custom provider/model config cases can use Effect-aware instance fixtures | Migrated three more config-heavy provider cases to `it.instance` | 6.07s | 6.12s | keep | Neutral timing within noise, but continues removing manual config file writes on top of the first provider fixture PR. |
| Provider env precedence and model lookup cases can use Effect-aware instance fixtures | Migrated four more provider lookup/default-model cases to `it.instance` | 6.12s | 6.36s | keep | Noisy 5-run median; kept as a small stacked cleanup slice but do not claim speedup from this migration. |
## Profiling Results ## Profiling Results