Repository navigation
feat(acp): show Qoder reasoning levels in the model picker #33212
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,6 +4,7 @@ import { | |
| resolveOfficialAcpRegistryIconUrl, | ||
| TextGenerationError, | ||
| type AcpRegistryOperationError, | ||
| type ProviderOptionDescriptor, | ||
| type ServerProvider, | ||
| type ServerProviderModel, | ||
| } from "@t3tools/contracts"; | ||
|
|
@@ -63,6 +64,8 @@ const decodeSettings = Schema.decodeSync(AcpRegistrySettings); | |
| // Cache only successful authenticated probes so sign-in changes remain visible. | ||
| const PROBE_SUCCESS_TTL_MS = 15 * 60 * 1_000; | ||
| const EMPTY_CAPABILITIES = createModelCapabilities({ optionDescriptors: [] }); | ||
| // Qoder advertises this id and changes the choice list with the model. | ||
| const PER_MODEL_REASONING_OPTION_ID = "reasoning_effort"; | ||
| const MAINTENANCE = makeManualOnlyProviderMaintenanceCapabilities({ | ||
| provider: DRIVER_KIND, | ||
| packageName: null, | ||
|
|
@@ -86,17 +89,60 @@ const makeUnsupportedTextGeneration = (): ProviderTextGeneration => { | |
| }; | ||
| }; | ||
|
|
||
| function isPerModelReasoningDescriptor(descriptor: ProviderOptionDescriptor): boolean { | ||
| return descriptor.id === PER_MODEL_REASONING_OPTION_ID; | ||
| } | ||
|
|
||
| function capabilitiesFromDescriptors( | ||
| descriptors: ReadonlyArray<ProviderOptionDescriptor>, | ||
| ): ServerProviderModel["capabilities"] { | ||
| return descriptors.length === 0 | ||
| ? EMPTY_CAPABILITIES | ||
| : createModelCapabilities({ optionDescriptors: descriptors }); | ||
| } | ||
|
|
||
| /** | ||
| * `reasoning_effort` belongs to the model that advertised it. Other options, such as session | ||
| * mode, stay shared. A later selection keeps a model's levels after the session moves on. | ||
| * Custom models were not in that advertisement, so they receive the shared options only. | ||
| */ | ||
| function descriptorsForDiscoveredModel( | ||
| modelId: string, | ||
| discovery: Pick<AcpRegistryLiveConfiguration, "currentModelId" | "configOptions">, | ||
| previousModels: ReadonlyArray<ServerProviderModel> | undefined, | ||
| ): ReadonlyArray<ProviderOptionDescriptor> { | ||
| const shared = discovery.configOptions; | ||
| if (discovery.currentModelId == null || modelId === discovery.currentModelId) { | ||
| return shared; | ||
| } | ||
| const sharedWithoutReasoning = shared.filter( | ||
| (descriptor) => !isPerModelReasoningDescriptor(descriptor), | ||
| ); | ||
| const kept = ( | ||
| previousModels?.find((model) => model.slug === modelId)?.capabilities?.optionDescriptors ?? [] | ||
| ).filter(isPerModelReasoningDescriptor); | ||
| return [...sharedWithoutReasoning, ...kept]; | ||
| } | ||
|
|
||
| function modelsFromDiscovery( | ||
| discovery: | ||
| | Pick<AcpRegistryLiveConfiguration, "models" | "currentModelId" | "configOptions"> | ||
| | undefined, | ||
| customModels: ReadonlyArray<string>, | ||
| previousModels?: ReadonlyArray<ServerProviderModel>, | ||
| ): ReadonlyArray<ServerProviderModel> { | ||
| const discovered = discovery?.models ?? []; | ||
| const capabilities = | ||
| discovery === undefined || discovery.configOptions.length === 0 | ||
| const customModelCapabilities = capabilitiesFromDescriptors( | ||
| (discovery?.configOptions ?? []).filter( | ||
| (descriptor) => !isPerModelReasoningDescriptor(descriptor), | ||
| ), | ||
| ); | ||
| const capabilitiesFor = (modelId: string) => | ||
| discovery === undefined | ||
| ? EMPTY_CAPABILITIES | ||
| : createModelCapabilities({ optionDescriptors: discovery.configOptions }); | ||
| : capabilitiesFromDescriptors( | ||
| descriptorsForDiscoveredModel(modelId, discovery, previousModels), | ||
| ); | ||
| const builtInModels: ReadonlyArray<ServerProviderModel> = | ||
| discovered.length === 0 | ||
| ? [ | ||
|
|
@@ -105,17 +151,17 @@ function modelsFromDiscovery( | |
| name: "Default", | ||
| isCustom: false, | ||
| isDefault: true, | ||
| capabilities, | ||
| capabilities: capabilitiesFor("default"), | ||
| }, | ||
| ] | ||
| : discovered.map((model) => ({ | ||
| slug: model.id, | ||
| name: model.name, | ||
| isCustom: false, | ||
| ...(model.id === discovery?.currentModelId ? { isDefault: true } : {}), | ||
| capabilities, | ||
| capabilities: capabilitiesFor(model.id), | ||
| })); | ||
| return providerModelsFromSettings(builtInModels, customModels, capabilities); | ||
| return providerModelsFromSettings(builtInModels, customModels, customModelCapabilities); | ||
| } | ||
|
|
||
| function acpRegistrySnapshotReadiness( | ||
|
|
@@ -258,6 +304,7 @@ export function applyAcpRegistryLiveConfiguration( | |
| provider: ServerProvider, | ||
| configuration: AcpRegistryLiveConfiguration, | ||
| customModels: ReadonlyArray<string>, | ||
| previousModels: ReadonlyArray<ServerProviderModel> = [], | ||
| ): ServerProvider { | ||
| const { message: _staleProbeMessage, ...snapshot } = provider; | ||
| // session/load may omit models (Kimi returns `{}`); keep the probed list | ||
|
|
@@ -281,7 +328,12 @@ export function applyAcpRegistryLiveConfiguration( | |
| models: | ||
| configuration.models.length === 0 && configuration.configOptions.length === 0 | ||
| ? provider.models | ||
| : modelsFromDiscovery(discovery, customModels), | ||
| : modelsFromDiscovery(discovery, customModels, [ | ||
| ...provider.models.filter((model) => | ||
| model.capabilities?.optionDescriptors?.some(isPerModelReasoningDescriptor), | ||
| ), | ||
|
Comment on lines
+332
to
+334
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Stale enrichment cache can override newer learned reasoning choices. For a non-current model, The live-configuration path does not update Choose the newer source explicitly when the two entries for the same slug disagree. A fix needs an ordering rule that does not return an older probe result over a newer learned one, or the reverse. Specify the rule before changing the order.
|
||
| ...previousModels, | ||
| ]), | ||
| }; | ||
| } | ||
|
|
||
|
|
@@ -550,6 +602,32 @@ export const AcpRegistryDriver: ProviderDriver<AcpRegistrySettings, AcpRegistryD | |
| environment, | ||
| processEnvironment, | ||
| }); | ||
| // Readiness checks replace the published snapshot before enrichment runs. | ||
| const learnedModels = yield* Ref.make<ReadonlyArray<ServerProviderModel>>([]); | ||
| const applyRememberedConfiguration = Effect.fn( | ||
| "AcpRegistryDriver.applyRememberedConfiguration", | ||
| )(function* ( | ||
| provider: ServerProvider, | ||
| configuration: Option.Option<AcpRegistryLiveConfiguration>, | ||
| ) { | ||
| const previousModels = yield* Ref.get(learnedModels); | ||
| const next = Option.match(configuration, { | ||
| onNone: () => provider, | ||
| onSome: (liveConfiguration) => | ||
| applyAcpRegistryLiveConfiguration( | ||
| provider, | ||
| liveConfiguration, | ||
| effectiveConfig.customModels, | ||
| previousModels, | ||
| ), | ||
| }); | ||
| yield* Ref.set(learnedModels, [ | ||
| ...new Map( | ||
| [...previousModels, ...next.models].map((model) => [model.slug, model]), | ||
| ).values(), | ||
| ]); | ||
| return next; | ||
| }); | ||
| const withLiveRuntimeState = Effect.fn("AcpRegistryDriver.withLiveRuntimeState")(function* ( | ||
| input: ServerProvider, | ||
| ) { | ||
|
|
@@ -568,18 +646,13 @@ export const AcpRegistryDriver: ProviderDriver<AcpRegistrySettings, AcpRegistryD | |
| configuration: runtimeCoordinator.value.getLiveConfiguration(instanceId), | ||
| authAction: runtimeCoordinator.value.getUrlAuthAction(instanceId), | ||
| }).pipe( | ||
| Effect.map(({ commands, configuration, authAction }) => { | ||
| Effect.flatMap(({ commands, configuration, authAction }) => { | ||
| const withCommands = applyAcpRegistryAvailableCommands(provider, commands); | ||
| const withConfiguration = Option.match(configuration, { | ||
| onNone: () => withCommands, | ||
| onSome: (liveConfiguration) => | ||
| applyAcpRegistryLiveConfiguration( | ||
| withCommands, | ||
| liveConfiguration, | ||
| effectiveConfig.customModels, | ||
| ), | ||
| }); | ||
| return applyAcpRegistryUrlAuthAction(withConfiguration, authAction); | ||
| return applyRememberedConfiguration(withCommands, configuration).pipe( | ||
| Effect.map((withConfiguration) => | ||
| applyAcpRegistryUrlAuthAction(withConfiguration, authAction), | ||
| ), | ||
| ); | ||
| }), | ||
| ); | ||
| }); | ||
|
|
@@ -707,12 +780,8 @@ export const AcpRegistryDriver: ProviderDriver<AcpRegistrySettings, AcpRegistryD | |
| liveSnapshotSemaphore.withPermit( | ||
| getSnapshot.pipe( | ||
| Effect.flatMap((current) => | ||
| publishSnapshot( | ||
| applyAcpRegistryLiveConfiguration( | ||
| current, | ||
| configuration, | ||
| effectiveConfig.customModels, | ||
| ), | ||
| applyRememberedConfiguration(current, Option.some(configuration)).pipe( | ||
| Effect.flatMap(publishSnapshot), | ||
| ), | ||
| ), | ||
| ), | ||
|
|
||
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.
Script executed:
Repository: pingdotgg/t3code
Length of output: 11069
Script executed:
Repository: pingdotgg/t3code
Length of output: 32914
Script executed:
Repository: pingdotgg/t3code
Length of output: 18017
Script executed:
Repository: pingdotgg/t3code
Length of output: 22991
Script executed:
Repository: pingdotgg/t3code
Length of output: 17409
Script executed:
Repository: pingdotgg/t3code
Length of output: 8068
Script executed:
Repository: pingdotgg/t3code
Length of output: 8007
Do not assign unknown reasoning levels to every model.
If discovery rejects the current model ID,
currentModelIdis null even when other models remain. The helper then assigns the current model’sreasoning_effortchoices to those models, so the picker can show unsupported levels. Preserve the default-model fallback while avoiding this assignment when the advertising model is unknown.