shikumi 0.1.0.0 → 0.1.0.1
raw patch · 7 files changed
+54/−42 lines, 7 filesdep ~baikaidep ~baikai-claudedep ~baikai-effectfulPVP ok
version bump matches the API change (PVP)
Dependency ranges changed: baikai, baikai-claude, baikai-effectful, baikai-openai, shikumi
API changes (from Hackage documentation)
Files
- CHANGELOG.md +10/−0
- shikumi.cabal +10/−10
- src/Shikumi/Error.hs +14/−13
- src/Shikumi/Stream.hs +2/−2
- test/ErrorSpec.hs +10/−10
- test/StreamSpec.hs +3/−2
- test/StubProvider.hs +5/−5
CHANGELOG.md view
@@ -2,6 +2,16 @@ ## Unreleased +## 0.1.0.1 - 2026-06-21++### Fixed++- Adapted `Shikumi.Error.fromBaikaiError` and streaming response reassembly to the baikai 0.2 API.++### Changed++- Constrained baikai package dependencies to the 0.2 series for Hackage builds.+ ## 0.1.0.0 - 2026-06-13 ### Added
shikumi.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.4 name: shikumi-version: 0.1.0.0+version: 0.1.0.1 synopsis: Typed, structured, evaluable LM programs over baikai category: AI description:@@ -52,10 +52,10 @@ build-depends: , aeson- , baikai- , baikai-claude- , baikai-effectful- , baikai-openai+ , baikai >=0.2 && <0.3+ , baikai-claude >=0.2 && <0.3+ , baikai-effectful >=0.2 && <0.3+ , baikai-openai >=0.2 && <0.3 , base >=4.20 && <5 , base64-bytestring , bytestring@@ -108,10 +108,10 @@ build-depends: , aeson- , baikai- , baikai-claude- , baikai-effectful- , baikai-openai+ , baikai >=0.2 && <0.3+ , baikai-claude >=0.2 && <0.3+ , baikai-effectful >=0.2 && <0.3+ , baikai-openai >=0.2 && <0.3 , base , base64-bytestring , bytestring@@ -121,7 +121,7 @@ , generic-lens , lens , QuickCheck- , shikumi ^>=0.1.0.0+ , shikumi ^>=0.1.0.1 , stm , streamly-core , tasty
src/Shikumi/Error.hs view
@@ -11,7 +11,7 @@ ) where -import Baikai.Error (BaikaiError (..))+import Baikai.Error (BaikaiError (..), ErrorCategory (..)) import Data.Text (Text) import Data.Text qualified as T @@ -37,19 +37,20 @@ deriving stock (Eq, Show) -- | Total mapping from baikai's transport-level errors into shikumi's--- vocabulary. baikai's constructors are 'ProviderError', 'RequestInvalid',--- 'DecodeError', and 'ProcessError'.------ 'RequestInvalid' maps to 'SchemaMismatch' because in baikai a malformed request--- is almost always bad schema/parameters; 'DecodeError' maps to 'InvalidJSON'--- because baikai's decode failures are JSON parse failures of the provider--- response. These two judgments are the only non-mechanical choices.+-- vocabulary. Invalid requests map to 'SchemaMismatch' because in baikai a+-- malformed request is almost always bad schema/parameters; decode failures+-- map to 'InvalidJSON' because baikai's decode failures are JSON parse+-- failures of the provider response. fromBaikaiError :: BaikaiError -> ShikumiError-fromBaikaiError = \case- ProviderError t -> ProviderFailure t- RequestInvalid t -> SchemaMismatch ("invalid request: " <> t)- DecodeError t -> InvalidJSON t- ProcessError n t -> ProviderFailure ("process exited " <> T.pack (show n) <> ": " <> t)+fromBaikaiError e = case category e of+ DecodeFailure -> InvalidJSON (message e)+ InvalidRequest -> SchemaMismatch ("invalid request: " <> message e)+ ProcessFailure ->+ ProviderFailure $+ case exitCode e of+ Just n -> "process exited " <> T.pack (show n) <> ": " <> message e+ Nothing -> message e+ _ -> ProviderFailure (message e) -- | Which errors are worth retrying. Provider/transport failures and timeouts are -- transient; decode, schema, validation, and budget errors are deterministic and
src/Shikumi/Stream.hs view
@@ -183,8 +183,8 @@ [] -> synthResponse (fromMaybe "" (firstTextEnd evs)) where terminalPayloads =- [p | EventDone (TerminalPayload _ (AssistantMessage p)) <- evs]- ++ [p | EventError (TerminalPayload _ (AssistantMessage p)) <- evs]+ [p | EventDone TerminalPayload {message = AssistantMessage p} <- evs]+ ++ [p | EventError TerminalPayload {message = AssistantMessage p} <- evs] -- | A response carrying @t@ as its single assistant text block. synthResponse :: Text -> Response
test/ErrorSpec.hs view
@@ -1,9 +1,9 @@ -- | Pins the 'BaikaiError' -> 'ShikumiError' mapping and the 'isTransient'--- classification. Deliberately breaking the mapping (e.g. mapping 'DecodeError'+-- classification. Deliberately breaking the mapping (e.g. mapping 'decodeError' -- to 'ProviderFailure') makes this group fail. module ErrorSpec (tests) where -import Baikai.Error (BaikaiError (..))+import Baikai.Error (decodeError, invalidRequest, processError, providerError) import Shikumi.Error ( ShikumiError (..), fromBaikaiError,@@ -16,14 +16,14 @@ tests = testGroup "ErrorSpec"- [ testCase "maps ProviderError -> ProviderFailure" $- fromBaikaiError (ProviderError "x") @?= ProviderFailure "x",- testCase "maps DecodeError -> InvalidJSON" $- fromBaikaiError (DecodeError "y") @?= InvalidJSON "y",- testCase "maps RequestInvalid -> SchemaMismatch" $- fromBaikaiError (RequestInvalid "z") @?= SchemaMismatch "invalid request: z",- testCase "maps ProcessError -> ProviderFailure" $- fromBaikaiError (ProcessError 2 "boom") @?= ProviderFailure "process exited 2: boom",+ [ testCase "maps providerError -> ProviderFailure" $+ fromBaikaiError (providerError "x") @?= ProviderFailure "x",+ testCase "maps decodeError -> InvalidJSON" $+ fromBaikaiError (decodeError "y") @?= InvalidJSON "y",+ testCase "maps invalidRequest -> SchemaMismatch" $+ fromBaikaiError (invalidRequest "z") @?= SchemaMismatch "invalid request: z",+ testCase "maps processError -> ProviderFailure" $+ fromBaikaiError (processError 2 "boom") @?= ProviderFailure "process exited 2: boom", testCase "isTransient classification" $ do isTransient (ProviderFailure "") @?= True isTransient (Timeout "") @?= True
test/StreamSpec.hs view
@@ -19,6 +19,7 @@ StartPayload (..), StopReason (..), TerminalPayload (..),+ doneTerminal, _Context, _Model, _Options,@@ -106,7 +107,7 @@ -- | The 'Response' assembled from a stream's terminal event. terminalResponse :: [AssistantMessageEvent] -> Response terminalResponse evs =- case [p | EventDone (TerminalPayload _ (AssistantMessage p)) <- evs] of+ case [p | EventDone TerminalPayload {message = AssistantMessage p} <- evs] of (p : _) -> _Response & #message .~ p [] -> mkResponse "" @@ -121,7 +122,7 @@ ] ++ [TextDelta (DeltaPayload 0 d) | d <- deltas] ++ [ TextEnd (BlockEndPayload 0 (T.concat deltas)),- EventDone (TerminalPayload Stop (AssistantMessage (payloadWith terminalText)))+ EventDone (doneTerminal Stop (AssistantMessage (payloadWith terminalText))) ] where payloadWith t = (_Response ^. #message) & #content .~ V.singleton (AssistantText (_TextContent & #text .~ t))
test/StubProvider.hs view
@@ -95,7 +95,7 @@ TextStart IndexPayload {contentIndex = 0}, TextDelta DeltaPayload {contentIndex = 0, delta = t}, TextEnd BlockEndPayload {contentIndex = 0, content = t},- EventDone TerminalPayload {reason = Stop, message = AssistantMessage (stubPayloadWith t)}+ EventDone (doneTerminal Stop (AssistantMessage (stubPayloadWith t))) ] -- | Build an isolated registry from a @complete@ implementation, reusing the@@ -122,7 +122,7 @@ costStubRegistry c t = mkRegistry t (\_ _ _ -> pure (stubResponse t & #message . #usage . #cost . #usd .~ c)) --- | A registry whose @complete@ throws 'ProviderError' (a transient failure) the+-- | A registry whose @complete@ throws 'providerError' (a transient failure) the -- first @failTimes@ calls, then returns the given text. The 'IORef' records the -- total number of attempts (used by the retry tests). failingStubRegistry :: IORef Int -> Int -> Text -> IO ProviderRegistry@@ -130,17 +130,17 @@ mkRegistry t $ \_ _ _ -> do n <- atomicModifyIORef' ref (\k -> (k + 1, k + 1)) if n <= failTimes- then throwIO (ProviderError ("stub transient failure #" <> T.pack (show n)))+ then throwIO (providerError ("stub transient failure #" <> T.pack (show n))) else pure (stubResponse t) --- | A registry whose @complete@ always throws 'RequestInvalid' (a non-transient+-- | A registry whose @complete@ always throws 'invalidRequest' (a non-transient -- failure that maps to 'Shikumi.Error.SchemaMismatch'). The 'IORef' records the -- number of attempts (a retry must not increase it). invalidStubRegistry :: IORef Int -> IO ProviderRegistry invalidStubRegistry ref = mkRegistry "" $ \_ _ _ -> do _ <- atomicModifyIORef' ref (\k -> (k + 1, k + 1))- throwIO (RequestInvalid "stub bad request")+ throwIO (invalidRequest "stub bad request") -- | A registry whose @complete@ records observed concurrency: it bumps @cur@ on -- entry (updating the running maximum in @mx@), sleeps briefly, and decrements on