fix(core): harden model selection edges (#30987)
This commit is contained in:
parent
d2204e0ff5
commit
3151e2246a
@ -199,6 +199,8 @@ export const layer = Layer.effect(
|
||||
}
|
||||
}),
|
||||
})
|
||||
const available = (model: ModelV2.Info) =>
|
||||
state.get().providers.get(model.providerID)?.provider.enabled !== false && model.enabled
|
||||
|
||||
yield* events.subscribe(PluginV2.Event.Added).pipe(
|
||||
// Plugin registries are location scoped even though the event bus is process scoped.
|
||||
@ -250,17 +252,17 @@ export const layer = Layer.effect(
|
||||
}),
|
||||
|
||||
available: Effect.fn("CatalogV2.model.available")(function* () {
|
||||
return (yield* result.model.all()).filter((model) => {
|
||||
const record = state.get().providers.get(model.providerID)
|
||||
return record?.provider.enabled !== false && model.enabled
|
||||
})
|
||||
return (yield* result.model.all()).filter(available)
|
||||
}),
|
||||
|
||||
default: Effect.fn("CatalogV2.model.default")(function* () {
|
||||
const defaultModel = state.get().defaultModel
|
||||
if (defaultModel) {
|
||||
const model = yield* result.model.get(defaultModel.providerID, defaultModel.modelID).pipe(Effect.option)
|
||||
if (Option.isSome(model) && model.value.enabled) return model
|
||||
const provider = state.get().providers.get(defaultModel.providerID)?.provider
|
||||
if (provider?.enabled !== false) {
|
||||
const model = yield* result.model.get(defaultModel.providerID, defaultModel.modelID).pipe(Effect.option)
|
||||
if (Option.isSome(model) && available(model.value)) return model
|
||||
}
|
||||
}
|
||||
|
||||
return pipe(
|
||||
|
||||
@ -88,7 +88,7 @@ export class NotFoundError extends Schema.TaggedErrorClass<NotFoundError>()("Ses
|
||||
export class OperationUnavailableError extends Schema.TaggedErrorClass<OperationUnavailableError>()(
|
||||
"Session.OperationUnavailableError",
|
||||
{
|
||||
operation: Schema.Literals(["move", "shell", "skill", "switchAgent", "switchModel", "compact", "wait"]),
|
||||
operation: Schema.Literals(["move", "shell", "skill", "switchAgent", "compact", "wait"]),
|
||||
},
|
||||
) {}
|
||||
|
||||
@ -386,13 +386,7 @@ export const layer = Layer.effect(
|
||||
return yield* new OperationUnavailableError({ operation: "switchAgent" })
|
||||
}),
|
||||
switchModel: Effect.fn("V2Session.switchModel")(function* (input) {
|
||||
const session = yield* result.get(input.sessionID)
|
||||
if (
|
||||
session.model?.providerID === input.model.providerID &&
|
||||
session.model.id === input.model.id &&
|
||||
(session.model.variant ?? "default") === (input.model.variant ?? "default")
|
||||
)
|
||||
return
|
||||
yield* result.get(input.sessionID)
|
||||
yield* events.publish(SessionEvent.ModelSwitched, {
|
||||
sessionID: input.sessionID,
|
||||
messageID: SessionMessage.ID.create(),
|
||||
|
||||
@ -281,6 +281,34 @@ describe("CatalogV2", () => {
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("ignores a configured default on a disabled provider", () =>
|
||||
Effect.gen(function* () {
|
||||
const catalog = yield* Catalog.Service
|
||||
const disabledProvider = ProviderV2.ID.make("disabled")
|
||||
const enabledProvider = ProviderV2.ID.make("enabled")
|
||||
const disabledModel = ModelV2.ID.make("configured")
|
||||
const fallbackModel = ModelV2.ID.make("fallback")
|
||||
const transform = yield* catalog.transform()
|
||||
|
||||
yield* transform((catalog) => {
|
||||
catalog.provider.update(disabledProvider, (provider) => {
|
||||
provider.enabled = false
|
||||
})
|
||||
catalog.model.update(disabledProvider, disabledModel, () => {})
|
||||
catalog.provider.update(enabledProvider, (provider) => {
|
||||
provider.enabled = { via: "custom", data: {} }
|
||||
})
|
||||
catalog.model.update(enabledProvider, fallbackModel, () => {})
|
||||
catalog.model.default.set(disabledProvider, disabledModel)
|
||||
})
|
||||
|
||||
expect(Option.getOrUndefined(yield* catalog.model.default())).toMatchObject({
|
||||
providerID: enabledProvider,
|
||||
id: fallbackModel,
|
||||
})
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("small model prefers small keyword candidates before cost scoring", () =>
|
||||
Effect.gen(function* () {
|
||||
const catalog = yield* Catalog.Service
|
||||
|
||||
@ -356,12 +356,23 @@ describe("SessionV2.create", () => {
|
||||
expect(
|
||||
Array.from(yield* session.events({ sessionID: created.id }).pipe(Stream.take(1), Stream.runCollect)),
|
||||
).toMatchObject([{ event: { type: "session.next.model.switched", data: { model } } }])
|
||||
}),
|
||||
)
|
||||
|
||||
it.effect("persists repeated switches as distinct durable Session events", () =>
|
||||
Effect.gen(function* () {
|
||||
const session = yield* SessionV2.Service
|
||||
const created = yield* session.create({ location })
|
||||
const model = ModelV2.Ref.make({ id: ModelV2.ID.make("sonnet"), providerID: ProviderV2.ID.anthropic })
|
||||
|
||||
yield* session.switchModel({ sessionID: created.id, model })
|
||||
yield* session.switchModel({ sessionID: created.id, model })
|
||||
|
||||
const { db } = yield* Database.Service
|
||||
expect(
|
||||
yield* db.select().from(EventTable).where(eq(EventTable.aggregate_id, created.id)).all().pipe(Effect.orDie),
|
||||
).toHaveLength(2)
|
||||
).toHaveLength(3)
|
||||
expect(yield* session.get(created.id)).toMatchObject({ model })
|
||||
}),
|
||||
)
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user