Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ nextflowPlugin {
className = 'nextflowio.plugin.JevPlugin'
description = 'Calibrated judgments from TypeSafe System One as Nextflow functions'
extensionPoints = [
'nextflowio.plugin.JevExtension'
'nextflowio.plugin.JevExtension',
'nextflowio.plugin.JevConfig'
]

registry {
Expand Down
46 changes: 44 additions & 2 deletions src/main/groovy/nextflowio/plugin/JevConfig.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,18 @@ package nextflowio.plugin
import java.time.Duration

import groovy.transform.CompileStatic
import nextflow.config.spec.ConfigOption
import nextflow.config.spec.ConfigScope
import nextflow.config.spec.ScopeName
import nextflow.exception.AbortOperationException
import nextflow.script.dsl.Description

/**
* Settings of the {@code jev} configuration scope, with environment fallback.
*
* <p>This class is the scope Nextflow reflects and the object the plugin reads. The no-arg
* constructor exists so the extension point can be instantiated; it does not resolve settings.
*
* <p>The provider comes from {@code jev.provider}, or failing that from the host of
* {@code jev.endpoint}, and decides the defaults and -- crucially -- which environment variable
* the credential falls back to. Only that provider's variable is ever read, so a TypeSafe key
Expand All @@ -35,24 +42,59 @@ import nextflow.exception.AbortOperationException
*
* @author Paolo Di Tommaso <paolo.ditommaso@gmail.com>
*/
@ScopeName('jev')
@Description('''
The `jev` scope controls how the nf-jev plugin calls a Jev decisions endpoint.
''')
@CompileStatic
class JevConfig {
class JevConfig implements ConfigScope {

static final JevProvider DEFAULT_PROVIDER = JevProvider.TYPESAFE
static final String DEFAULT_ENDPOINT = DEFAULT_PROVIDER.defaultEndpoint
static final String DEFAULT_MODEL = DEFAULT_PROVIDER.defaultModel
static final int DEFAULT_TIMEOUT_SECONDS = 30

@ConfigOption
@Description('''
The credential for the selected provider (default: the `TYPESAFE_API_KEY`
or `OPENROUTER_API_KEY` environment variable, matching the provider).
''')
private final String apiKey

@ConfigOption(types=[String])
@Description('''
The service that answers requests: `typesafe` (default) or `openrouter`.
Inferred from the host of `endpoint` when unset.
''')
final JevProvider provider

@ConfigOption
@Description('''
The decisions endpoint (default: the selected provider's endpoint).
''')
final String endpoint

@ConfigOption
@Description('''
The Jev model id (default: the selected provider's current model).
''')
final String model

@ConfigOption(types=[Integer])
@Description('''
The request timeout, in seconds (default: `30`).
''')
final Duration timeout

/** Where to keep cached responses; {@code null} -- the default -- disables caching entirely. */
@ConfigOption
@Description('''
Where to keep cached responses. Unset disables caching.
''')
final String cacheDir

/* required by extension point -- do not remove */
JevConfig() {}

JevConfig(Map opts, Map<String,String> env) {
final config = opts ?: Collections.emptyMap()
final sysEnv = env ?: Collections.<String,String>emptyMap()
Expand Down
30 changes: 30 additions & 0 deletions src/test/groovy/nextflowio/plugin/JevConfigTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ package nextflowio.plugin

import java.time.Duration

import groovy.json.JsonSlurper
import nextflow.config.spec.ConfigOption
import nextflow.exception.AbortOperationException
import spock.lang.Specification

Expand Down Expand Up @@ -152,4 +154,32 @@ class JevConfigTest extends Specification {
e.message.contains('jev.apiKey')
e.message.contains('TYPESAFE_API_KEY')
}

def 'should publish the jev scope in the generated plugin spec' () {
given:
def url = JevConfig.classLoader.getResource('META-INF/spec.json')
assert url != null : 'META-INF/spec.json is missing; JevConfig is not an extension point'
def spec = new JsonSlurper().parse(url)
def scope = spec.definitions.find { it.type == 'ConfigScope' && it.spec?.name == 'jev' }

expect:
scope != null
scope.spec.children.collect { it.spec.name } as Set ==
['provider', 'apiKey', 'model', 'endpoint', 'timeout', 'cacheDir'] as Set

and: 'the published types are the fields the plugin reads'
def types = scope.spec.children.collectEntries { [it.spec.name, it.spec.type] }
types.provider == 'JevProvider'
types.apiKey == 'String'
types.model == 'String'
types.endpoint == 'String'
types.timeout == 'Duration'
types.cacheDir == 'String'

and: 'users write a name and a number of seconds; the fields stay an enum and java.time.Duration'
JevConfig.getDeclaredField('provider').getAnnotation(ConfigOption).types() as List == [String]
JevConfig.getDeclaredField('timeout').type == Duration
JevConfig.getDeclaredField('timeout').getAnnotation(ConfigOption).types() as List == [Integer]
!scope.spec.children.any { it.spec.description?.contains('jev-1.13') }
}
}
Loading