packages feed

praxo-core 0.4.0 → 0.5.0

raw patch · 12 files changed

+390/−22 lines, 12 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

+ Praxo.Delegation: SubagentRequest :: String -> String -> String -> String -> SubagentRequest
+ Praxo.Delegation: SubagentResult :: String -> String -> String -> SubagentResult
+ Praxo.Delegation: SubagentTask :: String -> String -> String -> String -> String -> String -> String -> SubagentTask
+ Praxo.Delegation: [subagentAgentId] :: SubagentRequest -> String
+ Praxo.Delegation: [subagentAgent] :: SubagentTask -> String
+ Praxo.Delegation: [subagentMode] :: SubagentRequest -> String
+ Praxo.Delegation: [subagentOutput] :: SubagentResult -> String
+ Praxo.Delegation: [subagentParentSessionId] :: SubagentRequest -> String
+ Praxo.Delegation: [subagentParentSession] :: SubagentTask -> String
+ Praxo.Delegation: [subagentPrompt] :: SubagentRequest -> String
+ Praxo.Delegation: [subagentResultAgentId] :: SubagentResult -> String
+ Praxo.Delegation: [subagentStatus] :: SubagentTask -> String
+ Praxo.Delegation: [subagentTaskError] :: SubagentTask -> String
+ Praxo.Delegation: [subagentTaskId] :: SubagentResult -> String
+ Praxo.Delegation: [subagentTaskKey] :: SubagentTask -> String
+ Praxo.Delegation: [subagentTaskMode] :: SubagentTask -> String
+ Praxo.Delegation: [subagentTaskOutput] :: SubagentTask -> String
+ Praxo.Delegation: data SubagentRequest
+ Praxo.Delegation: data SubagentResult
+ Praxo.Delegation: data SubagentTask
+ Praxo.Delegation: instance GHC.Classes.Eq Praxo.Delegation.SubagentRequest
+ Praxo.Delegation: instance GHC.Classes.Eq Praxo.Delegation.SubagentResult
+ Praxo.Delegation: instance GHC.Classes.Eq Praxo.Delegation.SubagentTask
+ Praxo.Delegation: instance GHC.Show.Show Praxo.Delegation.SubagentRequest
+ Praxo.Delegation: instance GHC.Show.Show Praxo.Delegation.SubagentResult
+ Praxo.Delegation: instance GHC.Show.Show Praxo.Delegation.SubagentTask
+ Praxo.Delegation: subagentRun :: SubagentRunner -> SubagentRequest -> IO (Either AgentError SubagentTask)
+ Praxo.Delegation: type SubagentRunner = SubagentRequest -> IO Either AgentError SubagentResult
+ Praxo.Otel: OtelRequest :: String -> String -> [(String, String)] -> String -> OtelRequest
+ Praxo.Otel: OtelResponse :: Int -> String -> OtelResponse
+ Praxo.Otel: OtelSink :: String -> String -> [String] -> IO Integer -> OtelTransport -> OtelSink
+ Praxo.Otel: OtelTransport :: (OtelRequest -> IO (Either AgentError OtelResponse)) -> OtelTransport
+ Praxo.Otel: [otelAllowedDataKeys] :: OtelSink -> [String]
+ Praxo.Otel: [otelBody] :: OtelRequest -> String
+ Praxo.Otel: [otelClock] :: OtelSink -> IO Integer
+ Praxo.Otel: [otelEndpoint] :: OtelSink -> String
+ Praxo.Otel: [otelHeaders] :: OtelRequest -> [(String, String)]
+ Praxo.Otel: [otelMethod] :: OtelRequest -> String
+ Praxo.Otel: [otelResponseBody] :: OtelResponse -> String
+ Praxo.Otel: [otelServiceName] :: OtelSink -> String
+ Praxo.Otel: [otelStatus] :: OtelResponse -> Int
+ Praxo.Otel: [otelTransport] :: OtelSink -> OtelTransport
+ Praxo.Otel: [otelURL] :: OtelRequest -> String
+ Praxo.Otel: [sendOtel] :: OtelTransport -> OtelRequest -> IO (Either AgentError OtelResponse)
+ Praxo.Otel: buildOtelRequest :: OtelSink -> Event -> IO (Either AgentError OtelRequest)
+ Praxo.Otel: data OtelRequest
+ Praxo.Otel: data OtelResponse
+ Praxo.Otel: data OtelSink
+ Praxo.Otel: emitOtelEvent :: OtelSink -> Event -> IO (Either AgentError ())
+ Praxo.Otel: instance GHC.Classes.Eq Praxo.Otel.OtelRequest
+ Praxo.Otel: instance GHC.Classes.Eq Praxo.Otel.OtelResponse
+ Praxo.Otel: instance GHC.Show.Show Praxo.Otel.OtelRequest
+ Praxo.Otel: instance GHC.Show.Show Praxo.Otel.OtelResponse
+ Praxo.Otel: newOtelSink :: String -> OtelTransport -> IO Integer -> OtelSink
+ Praxo.Otel: newtype OtelTransport
+ Praxo.Retriever: Document :: String -> String -> [(String, String)] -> Double -> Document
+ Praxo.Retriever: [documentContent] :: Document -> String
+ Praxo.Retriever: [documentId] :: Document -> String
+ Praxo.Retriever: [documentMetadata] :: Document -> [(String, String)]
+ Praxo.Retriever: [documentScore] :: Document -> Double
+ Praxo.Retriever: data Document
+ Praxo.Retriever: instance GHC.Classes.Eq Praxo.Retriever.Document
+ Praxo.Retriever: instance GHC.Show.Show Praxo.Retriever.Document
+ Praxo.Retriever: newRetriever :: (String -> Int -> IO (Either AgentError [Document])) -> Retriever
+ Praxo.Retriever: retrieveDocuments :: Retriever -> String -> Int -> IO (Either AgentError [Document])
+ Praxo.Retriever: type Retriever = String -> Int -> IO Either AgentError [Document]

Files

README.md view
@@ -7,7 +7,7 @@ middleware, JSON Schema validation, plugin manifest parsing, and runtime helpers.  - Implementation type: Core.-- Package version: `0.2.0`; supported `SPEC_VERSION`: `0.2.0`.+- Package version: `0.5.0`; supported `SPEC_VERSION`: `0.2.0`. - Local gate: all eleven shared vector groups, native regression tests, and   source API/parity checks. - Linux and Windows CI run the full suite. Hackage publication uses@@ -19,7 +19,7 @@ - Registry introduction: [REGISTRY.md](https://github.com/axisvela/praxo-core/blob/main/language/haskell/REGISTRY.md) - Machine-readable entry: [llms.txt](https://github.com/axisvela/praxo-core/blob/main/language/haskell/llms.txt) and [llms.md](https://github.com/axisvela/praxo-core/blob/main/language/haskell/llms.md) - Coding-agent contract: [docs/getting-started/coding-agents.md](https://github.com/axisvela/praxo-core/blob/main/docs/getting-started/coding-agents.md)-- Registry: [Hackage](https://hackage.haskell.org/package/praxo-core-0.3.0)+- Registry: [Hackage](https://hackage.haskell.org/package/praxo-core-0.4.0)  ## Build and Test 
REGISTRY.md view
@@ -1,7 +1,7 @@ # PRAXO-CORE Haskell Registry Introduction -**Status:** published. Source version `0.4.0`, source-tag `haskell/v0.4.0`. Published registry version: `0.3.0`. Registry+**Status:** published. Source version `0.5.0`, source+tag `haskell/v0.5.0`. Published registry version: `0.4.0`. Registry pages are release snapshots; verify the published version before relying on a feature. @@ -14,15 +14,28 @@ | Field | Value | | --- | --- | | Package | `praxo-core` |-| Registry | [Hackage](https://hackage.haskell.org/package/praxo-core-0.3.0) |-| Source version | `0.4.0` |-| Published registry version | `0.3.0` |+| Registry | [Hackage](https://hackage.haskell.org/package/praxo-core-0.4.0) |+| Source version | `0.5.0` |+| Published registry version | `0.4.0` | | Install (published) | `cabal install praxo-core` | | Install (source/release prepared) | `cabal install praxo-core` |-| Source tag | `haskell/v0.4.0` |+| Source tag | `haskell/v0.5.0` | | Tier | Core | | Contract | `SPEC_VERSION` `0.2.0` | | Documentation | https://axisvela.github.io/praxo-core/languages/haskell/ |++## Capabilities and Status++| Adapter | Status | Detail |+| --- | --- | --- |+| OpenAI-compatible model | native (transport seam) | Caller supplies `Transport`; no bundled HTTP client |+| Anthropic model | native (transport seam) | `Praxo.Anthropic`; caller supplies transport |+| Gemini model | native (transport seam) | `Praxo.Gemini`; caller supplies transport |+| MCP client | native (transport seam) | `Praxo.Mcp`; caller supplies `McpTransport` |+| Harmovela sink | native (transport seam) | `Praxo.Harmovela`; caller supplies transport |+| OpenTelemetry sink | native (transport seam) | `Praxo.Otel`; caller supplies `OtelTransport` |+| Subagent delegation | native (synchronous seam) | `Praxo.Delegation.subagentRun`; no asynchronous manager |+| Retriever | native | `Praxo.Retriever.Document` and `Retriever` |  ## Coding-agent entry 
api-snapshot.py view
@@ -21,7 +21,7 @@         exports = header[2].replace("(..)", "").split(",")         entries.append("## " + module)         for name in sorted(export.strip() for export in exports):-            pattern = r"^(?:(?:data|newtype) " + re.escape(name) + r"\b.*|" + re.escape(name) + r" :: .*)$"+            pattern = r"^(?:(?:data|newtype|type) " + re.escape(name) + r"\b.*|" + re.escape(name) + r" :: .*)$"             declaration = re.search(pattern, text, re.MULTILINE)             if not declaration:                 raise ValueError("missing declaration: " + module + "." + name)
api/0.1.0.txt view
@@ -1,4 +1,4 @@-# PRAXO-CORE Haskell Wire API 0.4.0+# PRAXO-CORE Haskell Wire API 0.5.0 # SPEC_VERSION 0.2.0  ## Praxo.Agent@@ -17,6 +17,13 @@ generateAnthropic :: AnthropicConfig -> [Message] -> IO (Either AgentError Response) parseAnthropicResponse :: String -> Either AgentError Response +## Praxo.Delegation+data SubagentRequest = SubagentRequest+data SubagentResult = SubagentResult+type SubagentRunner = SubagentRequest -> IO (Either AgentError SubagentResult)+data SubagentTask = SubagentTask+subagentRun :: SubagentRunner -> SubagentRequest -> IO (Either AgentError SubagentTask)+ ## Praxo.Gemini data GeminiConfig = GeminiConfig data GeminiTool = GeminiTool@@ -83,12 +90,27 @@ generate :: OpenAIConfig -> [Message] -> IO (Either AgentError Response) parseResponse :: String -> Either AgentError Response +## Praxo.Otel+data OtelRequest = OtelRequest+data OtelResponse = OtelResponse+data OtelSink = OtelSink+newtype OtelTransport = OtelTransport+buildOtelRequest :: OtelSink -> Event -> IO (Either AgentError OtelRequest)+emitOtelEvent :: OtelSink -> Event -> IO (Either AgentError ())+newOtelSink :: String -> OtelTransport -> IO Integer -> OtelSink+ ## Praxo.Pattern matchPattern :: String -> String -> Either String Bool  ## Praxo.Plugin parseManifest :: String -> Either String Json validateManifest :: Json -> Either String ()++## Praxo.Retriever+data Document = Document+type Retriever = String -> Int -> IO (Either AgentError [Document])+newRetriever :: (String -> Int -> IO (Either AgentError [Document])) -> Retriever+retrieveDocuments :: Retriever -> String -> Int -> IO (Either AgentError [Document])  ## Praxo.Runtime data ContextBudget = ContextBudget { budgetMaxTokens :: Int, budgetKeepRecent :: Int } deriving (Eq, Show)
llms.md view
@@ -7,15 +7,26 @@ ## Package  - package: `praxo-core`-- registry: [Hackage](https://hackage.haskell.org/package/praxo-core-0.3.0)-- source_version: `0.4.0`-- published_version: `0.3.0`+- registry: [Hackage](https://hackage.haskell.org/package/praxo-core-0.4.0)+- source_version: `0.5.0`+- published_version: `0.4.0` - install_published: `cabal install praxo-core` - install_source: `cabal install praxo-core`-- source_tag: `haskell/v0.4.0`+- source_tag: `haskell/v0.5.0` - tier: Core - contract: `SPEC_VERSION` `0.2.0` - documentation: https://axisvela.github.io/praxo-core/languages/haskell/++## Capabilities and Status++- OpenAI-compatible model: native (transport seam) (Caller supplies `Transport`; no bundled HTTP client)+- Anthropic model: native (transport seam) (`Praxo.Anthropic`; caller supplies transport)+- Gemini model: native (transport seam) (`Praxo.Gemini`; caller supplies transport)+- MCP client: native (transport seam) (`Praxo.Mcp`; caller supplies `McpTransport`)+- Harmovela sink: native (transport seam) (`Praxo.Harmovela`; caller supplies transport)+- OpenTelemetry sink: native (transport seam) (`Praxo.Otel`; caller supplies `OtelTransport`)+- Subagent delegation: native (synchronous seam) (`Praxo.Delegation.subagentRun`; no asynchronous manager)+- Retriever: native (`Praxo.Retriever.Document` and `Retriever`)  ## Start 
llms.txt view
@@ -7,15 +7,26 @@ # Package  - package: `praxo-core`-- registry: [Hackage](https://hackage.haskell.org/package/praxo-core-0.3.0)-- source_version: `0.4.0`-- published_version: `0.3.0`+- registry: [Hackage](https://hackage.haskell.org/package/praxo-core-0.4.0)+- source_version: `0.5.0`+- published_version: `0.4.0` - install_published: `cabal install praxo-core` - install_source: `cabal install praxo-core`-- source_tag: `haskell/v0.4.0`+- source_tag: `haskell/v0.5.0` - tier: Core - contract: `SPEC_VERSION` `0.2.0` - documentation: https://axisvela.github.io/praxo-core/languages/haskell/++# Capabilities and Status++- OpenAI-compatible model: native (transport seam) (Caller supplies `Transport`; no bundled HTTP client)+- Anthropic model: native (transport seam) (`Praxo.Anthropic`; caller supplies transport)+- Gemini model: native (transport seam) (`Praxo.Gemini`; caller supplies transport)+- MCP client: native (transport seam) (`Praxo.Mcp`; caller supplies `McpTransport`)+- Harmovela sink: native (transport seam) (`Praxo.Harmovela`; caller supplies transport)+- OpenTelemetry sink: native (transport seam) (`Praxo.Otel`; caller supplies `OtelTransport`)+- Subagent delegation: native (synchronous seam) (`Praxo.Delegation.subagentRun`; no asynchronous manager)+- Retriever: native (`Praxo.Retriever.Document` and `Retriever`)  # Start 
praxo-core.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.4 name: praxo-core-version: 0.4.0+version: 0.5.0 synopsis: Embeddable MIT-licensed agent framework for Haskell description: Native agent loop with tools, policy hooks, typed events, memory,              checkpoints, context budgets, middleware, JSON Schema validation,@@ -22,7 +22,7 @@  library   hs-source-dirs: src-  exposed-modules: Praxo.Agent, Praxo.Json, Praxo.ToolCall, Praxo.JsonSchema, Praxo.Pattern, Praxo.Plugin, Praxo.Runtime, Praxo.Middleware, Praxo.Wire, Praxo.OpenAICompatible, Praxo.Anthropic, Praxo.Gemini, Praxo.Harmovela, Praxo.Mcp+  exposed-modules: Praxo.Agent, Praxo.Json, Praxo.ToolCall, Praxo.JsonSchema, Praxo.Pattern, Praxo.Plugin, Praxo.Runtime, Praxo.Middleware, Praxo.Wire, Praxo.OpenAICompatible, Praxo.Anthropic, Praxo.Gemini, Praxo.Harmovela, Praxo.Mcp, Praxo.Otel, Praxo.Retriever, Praxo.Delegation   build-depends: base >=4.14 && <5, containers >=0.6 && <0.9   default-language: Haskell2010   ghc-options: -Wall
+ src/Praxo/Delegation.hs view
@@ -0,0 +1,66 @@+module Praxo.Delegation+    ( SubagentRequest(..)+    , SubagentResult(..)+    , SubagentTask(..)+    , SubagentRunner+    , subagentRun+    ) where++import Control.Exception (SomeException, try)+import Praxo.Agent (AgentError(..))++data SubagentRequest = SubagentRequest+    { subagentParentSessionId :: String+    , subagentAgentId :: String+    , subagentPrompt :: String+    , subagentMode :: String+    } deriving (Eq, Show)++data SubagentResult = SubagentResult+    { subagentTaskId :: String+    , subagentResultAgentId :: String+    , subagentOutput :: String+    } deriving (Eq, Show)++data SubagentTask = SubagentTask+    { subagentTaskKey :: String+    , subagentParentSession :: String+    , subagentAgent :: String+    , subagentTaskMode :: String+    , subagentStatus :: String+    , subagentTaskOutput :: String+    , subagentTaskError :: String+    } deriving (Eq, Show)++type SubagentRunner = SubagentRequest -> IO (Either AgentError SubagentResult)++subagentRun :: SubagentRunner -> SubagentRequest -> IO (Either AgentError SubagentTask)+subagentRun runner request+    | null (subagentParentSessionId request) || null (subagentAgentId request) =+        pure (Left (AgentError "SUBAGENT_INVALID"+            "agent: subagent parent session and agent id are required"))+    | otherwise = do+        outcome <- try (runner request)+            :: IO (Either SomeException (Either AgentError SubagentResult))+        pure $ case outcome of+            Left error -> Right (failedTask (show error))+            Right (Left err) -> Right (failedTask (errorCode err ++ ": " ++ errorMessage err))+            Right (Right result) -> Right (completedTask result)+  where+    base = SubagentTask+        { subagentTaskKey = "subagent"+        , subagentParentSession = subagentParentSessionId request+        , subagentAgent = subagentAgentId request+        , subagentTaskMode = if subagentMode request == "fork" then "fork" else "isolated"+        , subagentStatus = "running"+        , subagentTaskOutput = ""+        , subagentTaskError = ""+        }+    completedTask result = base+        { subagentStatus = "completed"+        , subagentTaskOutput = subagentOutput result+        }+    failedTask detail = base+        { subagentStatus = "failed"+        , subagentTaskError = detail+        }
+ src/Praxo/Otel.hs view
@@ -0,0 +1,146 @@+module Praxo.Otel+    ( OtelRequest(..)+    , OtelResponse(..)+    , OtelTransport(..)+    , OtelSink(..)+    , newOtelSink+    , buildOtelRequest+    , emitOtelEvent+    ) where++import Data.Bits (xor)+import Data.Char (ord)+import Data.Word (Word64)+import Numeric (showHex)+import Praxo.Agent (AgentError(..))+import Praxo.Harmovela (formatHarmovelaTimestamp)+import Praxo.Json+import Praxo.Runtime (Event(..))++data OtelRequest = OtelRequest+    { otelMethod :: String+    , otelURL :: String+    , otelHeaders :: [(String, String)]+    , otelBody :: String+    } deriving (Eq, Show)++data OtelResponse = OtelResponse+    { otelStatus :: Int+    , otelResponseBody :: String+    } deriving (Eq, Show)++newtype OtelTransport = OtelTransport+    { sendOtel :: OtelRequest -> IO (Either AgentError OtelResponse)+    }++data OtelSink = OtelSink+    { otelEndpoint :: String+    , otelServiceName :: String+    , otelAllowedDataKeys :: [String]+    , otelClock :: IO Integer+    , otelTransport :: OtelTransport+    }++newOtelSink :: String -> OtelTransport -> IO Integer -> OtelSink+newOtelSink endpoint transport clock = OtelSink+    { otelEndpoint = endpoint+    , otelServiceName = "praxo-core"+    , otelAllowedDataKeys = []+    , otelClock = clock+    , otelTransport = transport+    }++buildOtelRequest :: OtelSink -> Event -> IO (Either AgentError OtelRequest)+buildOtelRequest sink event = do+    nanos <- otelClock sink+    let dataText = either (const "") id (encodeJson (eventData event))+        key = eventSessionId event ++ "|" ++ maybe "" id (eventRunId event)+            ++ "|" ++ eventKind event ++ "|" ++ show nanos ++ "|" ++ dataText+        traceId = hex64 (fnv key 0xcbf29ce484222325)+            ++ hex64 (fnv key 0x84222325cbf29ce4)+        spanId = hex64 (fnv (key ++ "|span") 0x100000001b3)+        stamp = formatHarmovelaTimestamp nanos+        attributes = baseAttributes event stamp+            ++ dataAttributes sink event+        spanValue = JObject+            [ ("traceId", JString traceId)+            , ("spanId", JString spanId)+            , ("name", JString ("praxo." ++ eventKind event))+            , ("kind", JNumber 1)+            , ("startTimeUnixNano", JString (show nanos))+            , ("endTimeUnixNano", JString (show nanos))+            , ("attributes", JArray attributes)+            ]+        scopeSpan = JObject+            [ ("scope", JObject [("name", JString "praxo-core")])+            , ("spans", JArray [spanValue])+            ]+        resourceAttributes =+            JArray [ attribute "service.name" (otelServiceName sink) ]+        resourceSpan = JObject+            [ ("resource", JObject [("attributes", resourceAttributes)])+            , ("scopeSpans", JArray [scopeSpan])+            ]+        payload = JObject [ ("resourceSpans", JArray [resourceSpan]) ]+    pure $ case encodeJson payload of+        Left detail -> Left (AgentError "OTEL_JSON" detail)+        Right body -> Right OtelRequest+            { otelMethod = "POST"+            , otelURL = otelEndpoint sink+            , otelHeaders = [("content-type", "application/json")]+            , otelBody = body+            }++emitOtelEvent :: OtelSink -> Event -> IO (Either AgentError ())+emitOtelEvent sink event = do+    request <- buildOtelRequest sink event+    case request of+        Left err -> pure (Left err)+        Right httpRequest -> do+            response <- sendOtel (otelTransport sink) httpRequest+            pure $ case response of+                Left err -> Left err+                Right value+                    | otelStatus value >= 200 && otelStatus value < 300 -> Right ()+                    | otherwise -> Left (AgentError "OTEL_HTTP"+                        ("otel: HTTP " ++ show (otelStatus value) ++ ": "+                            ++ bounded (otelResponseBody value)))++baseAttributes :: Event -> String -> [Json]+baseAttributes event stamp =+    [ attribute "praxo.event.type" (eventKind event) ]+        ++ (if null (eventSessionId event)+                then []+                else [ attribute "praxo.session.id" (eventSessionId event) ])+        ++ maybe [] (\runId -> [ attribute "praxo.run.id" runId ]) (eventRunId event)+        ++ [ attribute "praxo.event.at" stamp ]++dataAttributes :: OtelSink -> Event -> [Json]+dataAttributes sink event =+    [ attribute ("praxo.event.data." ++ name) (textValue value)+    | name <- otelAllowedDataKeys sink+    , Just value <- [lookup name (asObject (eventData event))]+    ]++attribute :: String -> String -> Json+attribute key value = JObject+    [ ("key", JString key)+    , ("value", JObject [("stringValue", JString value)])+    ]++textValue :: Json -> String+textValue (JString value) = value+textValue value = either (const "") id (encodeJson value)++fnv :: String -> Word64 -> Word64+fnv text seed = foldl step seed (map (fromIntegral . ord) text)+  where+    step value byte = (value `xor` byte) * 0x100000001b3++hex64 :: Word64 -> String+hex64 value =+    let text = showHex value ""+    in replicate (16 - length text) '0' ++ text++bounded :: String -> String+bounded = take 512
+ src/Praxo/Retriever.hs view
@@ -0,0 +1,25 @@+module Praxo.Retriever+    ( Document(..)+    , Retriever+    , newRetriever+    , retrieveDocuments+    ) where++import Praxo.Agent (AgentError(..))++data Document = Document+    { documentId :: String+    , documentContent :: String+    , documentMetadata :: [(String, String)]+    , documentScore :: Double+    } deriving (Eq, Show)++type Retriever = String -> Int -> IO (Either AgentError [Document])++newRetriever :: (String -> Int -> IO (Either AgentError [Document])) -> Retriever+newRetriever = id++retrieveDocuments :: Retriever -> String -> Int -> IO (Either AgentError [Document])+retrieveDocuments retriever query limit+    | limit < 0 = pure (Left (AgentError "RETRIEVER_INVALID" "retriever: invalid limit"))+    | otherwise = retriever query limit
src/Praxo/Wire.hs view
@@ -4,4 +4,4 @@ specVersion = "0.2.0"  sdkVersion :: String-sdkVersion = "0.4.0"+sdkVersion = "0.5.0"
tests/Main.hs view
@@ -14,6 +14,9 @@ import qualified Praxo.Gemini as G import qualified Praxo.Harmovela as H import qualified Praxo.Mcp as M+import qualified Praxo.Otel as Ot+import qualified Praxo.Retriever as R+import qualified Praxo.Delegation as D import Praxo.Pattern import Praxo.Plugin import Praxo.Runtime@@ -29,7 +32,8 @@     providerTests     harmovelaTests     mcpTests-    assert "contract version" (specVersion == "0.2.0" && sdkVersion == "0.4.0")+    seamTests+    assert "contract version" (specVersion == "0.2.0" && sdkVersion == "0.5.0")     forM_ ["", "01", "1.", "+1", "NaN", "1e", "[1,]", "{\"a\":1,}", "{}{}", "\"\\q\"", "\"\\ud800\"", "\"\\udc00\"", "\"\\ud800\\u0041\"", "\"line\nbreak\"", "true garbage"] $ \raw ->         assert ("invalid JSON " ++ show raw) (isLeft (parseJson raw))     assert "unicode escape" (parseJson "\"\\u00e9\\ud83d\\ude00\"" == Right (JString "\x00e9\x1f600"))@@ -452,6 +456,76 @@             assert "mcp tool error" (case called of                 Left err -> A.errorCode err == "MCP_TOOL_ERROR"                 Right _ -> False)++seamTests :: IO ()+seamTests = do+    calls <- newIORef ([] :: [Ot.OtelRequest])+    let transport = Ot.OtelTransport (\request -> do+            modifyIORef' calls (++ [request])+            pure (Right (Ot.OtelResponse 200 "")))+        clock = pure 1794294000000000000+        sink = (Ot.newOtelSink "http://otel.test/v1/traces" transport clock)+            { Ot.otelAllowedDataKeys = ["name"] }+        event = Event "tool.call" "s1" (Just "run-123")+            (JObject [("name", JString "echo"), ("secret", JString "hidden")])+    emitted <- Ot.emitOtelEvent sink event+    assert "otel emit" (emitted == Right ())+    requests <- readIORef calls+    case requests of+        [request] -> do+            assert "otel url" (Ot.otelURL request == "http://otel.test/v1/traces")+            let payload = either error id (parseJson (Ot.otelBody request))+                resourceSpans = asArray (field "resourceSpans" payload)+            case resourceSpans of+                [resourceSpan] -> do+                    let scopeSpan = head (asArray (field "scopeSpans" resourceSpan))+                        spanValue = head (asArray (field "spans" scopeSpan))+                    assert "otel span name" (asString (field "name" spanValue) == "praxo.tool.call")+                    assert "otel trace id" (length (asString (field "traceId" spanValue)) == 32)+                    assert "otel span id" (length (asString (field "spanId" spanValue)) == 16)+                    let attributes = asArray (field "attributes" spanValue)+                        attributeValue key =+                            [ asString (field "stringValue" (field "value" item))+                            | item <- attributes+                            , asString (field "key" item) == key ]+                    assert "otel allowed data" (attributeValue "praxo.event.data.name" == ["echo"])+                    assert "otel data excluded" (null (attributeValue "praxo.event.data.secret"))+                _ -> fail "otel resource span count"+        _ -> fail "otel request count"+    let failing = (Ot.newOtelSink "http://otel.test/v1/traces"+            (Ot.OtelTransport (\_ -> pure (Right (Ot.OtelResponse 503 "down"))))+            clock)+    failed <- Ot.emitOtelEvent failing event+    assert "otel error" (case failed of+        Left err -> A.errorCode err == "OTEL_HTTP"+        Right () -> False)++    let retriever query limit = pure (Right+            [R.Document "doc-1" (query ++ ":" ++ show limit) [] 0.5])+    documents <- R.retrieveDocuments retriever "hello" 3+    assert "retriever" (case documents of+        Right [document] -> R.documentContent document == "hello:3"+            && R.documentScore document == 0.5+        _ -> False)++    let request = D.SubagentRequest "parent" "child" "do" "fork"+        runner _ = pure (Right (D.SubagentResult "" "" "done"))+    completed <- D.subagentRun runner request+    assert "delegation completed" (case completed of+        Right task -> D.subagentStatus task == "completed"+            && D.subagentTaskOutput task == "done"+            && D.subagentTaskMode task == "fork"+        Left _ -> False)+    let failingRunner _ = pure (Left (A.AgentError "TEST" "boom"))+    failedTask <- D.subagentRun failingRunner request+    assert "delegation failed" (case failedTask of+        Right task -> D.subagentStatus task == "failed"+            && "boom" `isInfixOf` D.subagentTaskError task+        Left _ -> False)+    invalid <- D.subagentRun runner (D.SubagentRequest "" "child" "do" "isolated")+    assert "delegation invalid" (case invalid of+        Left err -> A.errorCode err == "SUBAGENT_INVALID"+        Right _ -> False)  assert :: String -> Bool -> IO () assert label condition = unless condition (fail label)