diff --git a/detekt-baseline.xml b/detekt-baseline.xml index 9e84990..a2657ec 100644 --- a/detekt-baseline.xml +++ b/detekt-baseline.xml @@ -1,6 +1,6 @@ - + ClassNaming:GeneratedMetaCacheTest.kt$MetaCacheBadTypeBaz__GeneratedSchema ClassNaming:GeneratedMetaCacheTest.kt$MetaCacheCleanFoo__GeneratedSchema @@ -13,11 +13,11 @@ ClassNaming:GeneratedSchemaLookupTest.kt$HandGeneratedUser__GeneratedSchema ComplexCondition:ClaudeClient.kt$ClaudeClient$toolDefs.isNotEmpty() && mainSystemHint != null && mainSystemHint.segment == CacheSegment.SystemPrompt && consumeBreakpoint() CyclomaticComplexMethod:AgenticLoop.kt$internal suspend fun <IN> executeAgentic( agent: Agent<IN, *>, skill: Skill<*, *>, input: IN, /** * #3376 batch 5 — the per-invocation execution parameters (prompt override, resume/HITL state, * checkpoint callback, manifest-mismatch opt-out, attachments) bundled into one [RunRequest], * shared with [Agent.invokeSuspendForSession]. Defaults to a fresh invocation. */ request: agents_engine.core.RunRequest = agents_engine.core.RunRequest(), /** * #1739: optional AgentEvent emitter. When non-null, the loop streams * via `client.chatStream(...)`, surfaces `Token` / `ToolCallStarted` / * `ToolCallArgumentsDelta` events from chunks, and emits * `ToolCallFinished` after each tool executor runs. When null, the * loop uses `client.chat(...)` byte-for-byte as before — non-streaming * callers (`Agent.invoke`, `Agent.invokeSuspend`) pay no overhead. */ emitter: AgentEventEmitter? = null, runtimeContext: AgentRuntimeContext = AgentRuntimeContext.currentOrNew(), ): AgenticResult - CyclomaticComplexMethod:ClaudeClient.kt$ClaudeClient$@Suppress("UNCHECKED_CAST") private suspend fun dispatchSseEvent( event: String, dataJson: String, blocks: MutableMap<Int, BlockState>, collector: kotlinx.coroutines.flow.FlowCollector<LlmChunk>, onInputTokens: (Int) -> Unit, onCachedInputTokens: (Int) -> Unit, onOutputTokens: (Int) -> Unit, onMessageStop: suspend () -> Unit, ) + CyclomaticComplexMethod:ClaudeClient.kt$ClaudeClient$@Suppress("UNCHECKED_CAST") private suspend fun dispatchSseEvent( event: String, dataJson: String, blocks: MutableMap<Int, BlockState>, collector: FlowCollector<LlmChunk>, onInputTokens: (Int) -> Unit, onCachedInputTokens: (Int) -> Unit, onOutputTokens: (Int) -> Unit, onMessageStop: suspend () -> Unit, ) CyclomaticComplexMethod:ClaudeClient.kt$ClaudeClient$internal fun buildRequestJson( messages: List<LlmMessage>, stream: Boolean = false, jsonSchema: JsonSchema? = null, ): String CyclomaticComplexMethod:LenientJsonParser.kt$LenientJsonParser.Parser$private fun parseNumber(): Number CyclomaticComplexMethod:OllamaClient.kt$OllamaClient$override suspend fun chatStream(messages: List<LlmMessage>, jsonSchema: JsonSchema?): Flow<LlmChunk> - CyclomaticComplexMethod:OpenAiClient.kt$OpenAiClient$private suspend fun parseSseStream(stream: InputStream, collector: kotlinx.coroutines.flow.FlowCollector<LlmChunk>) + CyclomaticComplexMethod:OpenAiClient.kt$OpenAiClient$private suspend fun parseSseStream(stream: InputStream, collector: FlowCollector<LlmChunk>) DestructuringDeclarationWithTooManyEntries:AgentSessionIntegrationTest.kt$AgentSessionIntegrationTest$val (eventsA, outputA, eventsB, outputB) = coroutineScope { val sessionA = echoAgent.session("alpha") val sessionB = echoAgent.session("bravo") val a = async { sessionA.events.toList() } val b = async { sessionB.events.toList() } val outA = sessionA.await() val outB = sessionB.await() Quad(a.await(), outA, b.await(), outB) } EmptyDefaultConstructor:AllSpecsDeepIntegrationTest.kt$AllSpecsDeepIntegrationTest.Spec$() EmptyElseBlock:SnapshotResumeTest.kt$SnapshotResumeTest$if (firstSeenMessages == null) firstSeenMessages = msgs.toList() @@ -153,9 +153,7 @@ MaxLineLength:ClaudeClient.kt$ClaudeClient$"""{"type":"tool_result","tool_use_id":${id.toJsonString()},"content":${msg.content.toJsonString()}}""" MaxLineLength:ClaudeClient.kt$ClaudeClient$add("""{"name":${t.name.toJsonString()},"description":${t.description.toJsonString()},"input_schema":$schema}""") MaxLineLength:ClaudeClient.kt$ClaudeClient$blocks += """{"type":"tool_use","id":${id.toJsonString()},"name":${call.name.toJsonString()},"input":$argsJson}""" - MaxLineLength:ClaudeClient.kt$ClaudeClient$private suspend MaxLineLength:ClaudeClient.kt$ClaudeClient$return """{"model":${model.toJsonString()},"max_tokens":$maxTokens,"temperature":$effectiveTemperature$thinkingField$streamField$systemField,"messages":[${messageObjects.joinToString(",")}]$toolsField$toolChoiceField}""" - MaxLineLength:ClaudeClient.kt$ClaudeClient$val cacheControl = if (msg.cacheHint != null && consumeBreakpoint()) cacheControlJson(msg.cacheHint) else null MaxLineLength:ClaudeClientCancellationTest.kt$ClaudeClientCancellationTest$fun MaxLineLength:ClaudeClientCancellationTest.kt$ClaudeClientCancellationTest.SlowSseStream$add("event: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"chunk$i \"}}\n\n") MaxLineLength:ClaudeClientCancellationTest.kt$ClaudeClientCancellationTest.SlowSseStream$add("event: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"text\"}}\n\n") @@ -262,9 +260,7 @@ MaxLineLength:OpenAiClient.kt$OpenAiClient$""","response_format":{"type":"json_schema","json_schema":{"name":${schema.wireName().toJsonString()},"schema":${schema.schema},"strict":true}}""" MaxLineLength:OpenAiClient.kt$OpenAiClient$"""{"id":${id.toJsonString()},"type":"function","function":{"name":${call.name.toJsonString()},"arguments":${argsString.toJsonString()}}}""" MaxLineLength:OpenAiClient.kt$OpenAiClient$"""{"type":"function","function":{"name":${t.name.toJsonString()},"description":${t.description.toJsonString()},"parameters":$schema}}""" - MaxLineLength:OpenAiClient.kt$OpenAiClient$private suspend MaxLineLength:OpenAiClient.kt$OpenAiClient$return """{"model":${model.toJsonString()},"max_tokens":$maxTokens,"temperature":$temperature$additionalFields$cacheKeyField$streamField,"messages":[${messageObjects.joinToString(",")}]$effectiveToolsField$toolChoiceField$responseFormatField}""" - MaxLineLength:OpenAiClient.kt$OpenAiClient$throw LlmProviderException("$providerLabel returned an error: ${type ?: "unknown"}: ${message ?: "no message"}") MaxLineLength:OpenAiClientCancellationTest.kt$OpenAiClientCancellationTest$fun MaxLineLength:OpenAiClientCancellationTest.kt$OpenAiClientCancellationTest.SlowOpenAiSseStream$add("data: {\"id\":\"x\",\"choices\":[],\"usage\":{\"prompt_tokens\":3,\"completion_tokens\":12,\"total_tokens\":15}}\n\n") MaxLineLength:OpenAiClientCancellationTest.kt$OpenAiClientCancellationTest.SlowOpenAiSseStream$add("data: {\"id\":\"x\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"chunk$i \"},\"finish_reason\":null}]}\n\n") @@ -409,7 +405,6 @@ UnusedPrivateProperty:AgentsPipelineTest.kt$AgentsPipelineTest$val totalPipeline = pipelinePt1 then pipelinePt2 UnusedPrivateProperty:AgentsPipelineTest.kt$AgentsPipelineTest$val totalPipelineWithType: Pipeline<SomeSpecAsk, SomeProductionMachineryManagement> = pipelinePt1 then pipelinePt2 UnusedPrivateProperty:LoopAgenticIntegrationTest.kt$LoopAgenticIntegrationTest$var bestDraft = "" - UnusedPrivateProperty:MockTcpMcpServer.kt$MockTcpMcpServer$private val acceptThread = Thread({ acceptLoop() }, "MockTcpMcpServer-accept-${server.localPort}").apply { isDaemon = true start() } UseCheckOrError:LenientJsonParser.kt$LenientJsonParser.Parser$throw IllegalStateException( "LenientJsonParser: unexpected character '${s[pos]}' at pos $pos" ) UseCheckOrError:LenientJsonParser.kt$LenientJsonParser.Parser$throw IllegalStateException( "LenientJsonParser: zero-progress at pos $pos in array" ) UseCheckOrError:LenientJsonParser.kt$LenientJsonParser.Parser$throw IllegalStateException( "LenientJsonParser: zero-progress at pos $pos in object" ) diff --git a/src/main/kotlin/agents_engine/model/ClaudeClient.kt b/src/main/kotlin/agents_engine/model/ClaudeClient.kt index 91b84f8..ba24578 100644 --- a/src/main/kotlin/agents_engine/model/ClaudeClient.kt +++ b/src/main/kotlin/agents_engine/model/ClaudeClient.kt @@ -17,6 +17,7 @@ import kotlin.time.Duration.Companion.seconds import kotlin.time.toJavaDuration import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.FlowCollector import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOn @@ -167,7 +168,7 @@ open class ClaudeClient( val argsBuilder: StringBuilder, ) - private suspend fun parseSseStream(stream: InputStream, collector: kotlinx.coroutines.flow.FlowCollector) { + private suspend fun parseSseStream(stream: InputStream, collector: FlowCollector) { val blocks = mutableMapOf() var inputTokens: Int? = null var outputTokens: Int? = null @@ -229,7 +230,7 @@ open class ClaudeClient( event: String, dataJson: String, blocks: MutableMap, - collector: kotlinx.coroutines.flow.FlowCollector, + collector: FlowCollector, onInputTokens: (Int) -> Unit, onCachedInputTokens: (Int) -> Unit, onOutputTokens: (Int) -> Unit, @@ -408,7 +409,8 @@ open class ClaudeClient( // #2658 — when an assistant/user message carries a CacheHint // (typically segment=Conversation for rolling mode), attach // cache_control to the LAST content block on the wire. - val cacheControl = if (msg.cacheHint != null && consumeBreakpoint()) cacheControlJson(msg.cacheHint) else null + val cacheControl = + if (msg.cacheHint != null && consumeBreakpoint()) cacheControlJson(msg.cacheHint) else null when (msg.role) { "user" -> { val images = msg.images diff --git a/src/main/kotlin/agents_engine/model/OpenAiClient.kt b/src/main/kotlin/agents_engine/model/OpenAiClient.kt index 3f56ef4..4411cb0 100644 --- a/src/main/kotlin/agents_engine/model/OpenAiClient.kt +++ b/src/main/kotlin/agents_engine/model/OpenAiClient.kt @@ -17,6 +17,7 @@ import kotlin.time.Duration.Companion.seconds import kotlin.time.toJavaDuration import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.FlowCollector import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOn @@ -167,7 +168,7 @@ open class OpenAiClient( val argsBuilder: StringBuilder = StringBuilder(), ) - private suspend fun parseSseStream(stream: InputStream, collector: kotlinx.coroutines.flow.FlowCollector) { + private suspend fun parseSseStream(stream: InputStream, collector: FlowCollector) { // Keyed by `tool_calls[].index` within the choice. val toolStates = mutableMapOf() var usage: TokenUsage? = null @@ -377,7 +378,9 @@ open class OpenAiClient( (root["error"] as? Map<*, *>)?.let { err -> val type = err["type"] as? String val message = err["message"] as? String - throw LlmProviderException("$providerLabel returned an error: ${type ?: "unknown"}: ${message ?: "no message"}") + throw LlmProviderException( + "$providerLabel returned an error: ${type ?: "unknown"}: ${message ?: "no message"}" + ) } val tokenUsage = extractTokenUsage(root) diff --git a/src/test/kotlin/agents_engine/mcp/MockTcpMcpServer.kt b/src/test/kotlin/agents_engine/mcp/MockTcpMcpServer.kt index 5760bfd..e20c353 100644 --- a/src/test/kotlin/agents_engine/mcp/MockTcpMcpServer.kt +++ b/src/test/kotlin/agents_engine/mcp/MockTcpMcpServer.kt @@ -23,9 +23,13 @@ class MockTcpMcpServer internal constructor( private val workers = Executors.newCachedThreadPool { r -> Thread(r, "MockTcpMcpServer-${server.localPort}").apply { isDaemon = true } } - private val acceptThread = Thread({ acceptLoop() }, "MockTcpMcpServer-accept-${server.localPort}").apply { - isDaemon = true - start() + init { + // Accept loop runs on its own daemon thread; we don't retain a handle (stop() closes the + // ServerSocket, which unblocks accept()). + Thread({ acceptLoop() }, "MockTcpMcpServer-accept-${server.localPort}").apply { + isDaemon = true + start() + } } fun stop() {