weopqrst/mooncassette/core does not have a README file

    CassetteError

    pub(all) suberror CassetteError {
    Malformed(String)
    SchemaViolation(String)
    UnsupportedVersion(Int)
    FingerprintCollision(String)
    IntegrityViolation(String)
    NoMatch(String)
    Exhausted(String)
    MissingTransport(String)
    TransportFailure(String)
    }

    mooncassette 统一错误类型。

    所有错误都携带足以定位问题的上下文:出错路径、指纹或记录序号。

    Cassette

    pub(all) struct Cassette {
    version : Int
    meta : CassetteMeta
    interactions : Array[Interaction]
    } derive(Eq)

    一个 cassette:一组按录制顺序排列的交互。

    Cassette::length

    fn Cassette::length(self : Cassette) -> Int

    记录条数。

    Cassette::new

    fn Cassette::new(name : String, recorded_at? : String, interactions? : Array[Interaction]) -> Cassette

    构造一个空 cassette,version 自动填为当前格式版本。

    Cassette::push

    fn Cassette::push(self : Cassette, interaction : Interaction) -> Unit

    追加一条记录(录制时使用)。

    Cassette::total_input_tokens

    fn Cassette::total_input_tokens(self : Cassette) -> Int

    输入 token 合计(忽略未上报用量的记录)。

    Cassette::total_output_tokens

    fn Cassette::total_output_tokens(self : Cassette) -> Int

    输出 token 合计(忽略未上报用量的记录)。

    CassetteMeta

    pub(all) struct CassetteMeta {
    name : String
    generator : String
    recorded_at : String?
    } derive(Eq)

    cassette 元信息。

    Interaction 相反,本类型允许包含非确定性字段。 重新录制时这些字段出现 diff 属预期行为,不影响回放。

    CassetteMeta::new

    fn CassetteMeta::new(name : String, recorded_at? : String) -> CassetteMeta

    构造元信息,generator 自动填为当前 generator_id

    Interaction

    pub(all) struct Interaction {
    request : Request
    response : Response
    } derive(Eq)

    一条被录制的交互。

    不变量:本类型必须完全确定。任何不确定字段一经混入, 「确定性回放」这一核心承诺即被破坏,因此这里刻意不提供时间戳字段。

    Interaction::new

    fn Interaction::new(request : Request, response : Response) -> Interaction

    构造一条交互记录。

    Interaction::to_json

    fn Interaction::to_json(self : Interaction) -> Json

    交互记录的 JSON 视图(请求 + 响应)。

    Request

    pub(all) struct Request {
    provider : String
    model : String
    body : Json
    } derive(Eq)

    一次 LLM 请求。

    不变量:
    • body对象键顺序不参与语义,由 @canon 的规范文本统一归一;
    • 生成指纹前必须先调用 Request::normalize 剔除易变字段, 否则同一语义的请求每次都会得到不同指纹,回放将永远无法命中。

    Request::new

    fn Request::new(provider : String, model : String, body : Json) -> Request

    构造一个请求。

    Request::normalize

    fn Request::normalize(self : Request, drop_keys? : ArrayView[String]) -> Request

    规范化一个请求:剔除易变字段,并把等价的载荷写法收敛到规范形式。

    未显式传入 drop_keys 时使用 default_drop_keys

    第二步(@payload.normalize_json)只改写疑似 base64 载荷的字符串叶子: 同一张图片用标准 / URL-safe / 折行三种写法编码,指纹必须只有一个。 不含这类字符串的请求(即全部历史内容)逐字节不变,指纹也因此不变。

    Request::to_json

    fn Request::to_json(self : Request) -> Json

    请求的 JSON 视图。

    Response

    pub(all) struct Response {
    status : Int
    body : Json
    usage : Usage?
    stream : Array[StreamFrame]?
    } derive(Eq)

    一次 LLM 响应。

    status 沿用 HTTP 语义;非 HTTP 传输(例如本地 mock)使用 0。

    Response::new

    fn Response::new(status : Int, body : Json, usage? : Usage, stream? : Array[StreamFrame]) -> Response

    构造响应。

    Response::ok

    fn Response::ok(body : Json, usage? : Usage, stream? : Array[StreamFrame]) -> Response

    构造一个 200 响应。

    直接构造结构体而不转发给 Response::new:可选参数在函数体内已经是 Usage? / Array[StreamFrame]?,直接落入字段可以避免一次无意义的拆装。

    Response::to_json

    fn Response::to_json(self : Response) -> Json

    响应的 JSON 视图。

    未上报用量时省略 usage 字段;非流式响应省略 stream 字段。

    ScriptedReplies

    pub struct ScriptedReplies[T] {
    // private fields
    }

    一个请求对应的应答序列。

    取值规则:按登记顺序依次给出;序列用完之后重复最后一个

    为什么是「重复最后一个」而不是「用完报错」:

    • 同一个请求被反复调用是常态(服务端重试、循环里重复提问、多轮对话里 相同的前缀),报错会把正常用法变成障碍;
    • 「调用次数超出预期」应当由 call_count 断言来管。让测试替身去决定它, 等于把一条业务断言藏进了替身内部,失败信息反而更难懂。

    与之配套的是「重试后成功」这类序列(例如先 429 再 200):它让重试路径 本身也能被离线回归,而不只是被测试到「最终成功」。 字段是 priv 的:next 若可被外部写成负数,take() 就会用负下标取数组, 那是越界崩溃而不是「行为不同」。取值状态是本类型的全部内容,因此由它自己管。

    ScriptedReplies::consumed

    fn[T] ScriptedReplies::consumed(self : ScriptedReplies[T]) -> Int

    已经被取走的应答个数。

    ScriptedReplies::is_spent

    fn[T] ScriptedReplies::is_spent(self : ScriptedReplies[T]) -> Bool

    是否一个应答都没剩。

    ScriptedReplies::new

    fn[T] ScriptedReplies::new(replies : Array[T]) -> ScriptedReplies[T]

    构造一个应答序列。

    ScriptedReplies::push

    fn[T] ScriptedReplies::push(self : ScriptedReplies[T], reply : T) -> Unit

    追加一个应答。

    ScriptedReplies::remaining

    fn[T] ScriptedReplies::remaining(self : ScriptedReplies[T]) -> Int

    尚未被取走的应答个数。

    ScriptedReplies::take

    fn[T] ScriptedReplies::take(self : ScriptedReplies[T]) -> T?

    取下一个应答;序列为空时返回 None

    StreamFrame

    pub(all) struct StreamFrame {
    event : String?
    data : String
    } derive(Eq)

    流式响应中的一帧。

    只保留 SSE 的 eventdata 两个字段:

    • id / retry 属于「断线重连」的传输层元数据,不是模型输出,而且往往 带随机性;写进 cassette 只会让漂移检测误报;
    • : 开头的注释行按规范本就应当忽略。

    StreamFrame::new

    fn StreamFrame::new(data : String, event? : String) -> StreamFrame

    构造一帧。

    StreamFrame::to_json

    fn StreamFrame::to_json(self : StreamFrame) -> Json

    一帧的 JSON 视图。event 缺省时省略该字段。

    Usage

    pub(all) struct Usage {
    input_tokens : Int
    output_tokens : Int
    } derive(Eq)

    供应方返回的 token 用量。

    Usage::new

    fn Usage::new(input_tokens : Int, output_tokens : Int) -> Usage

    构造用量信息。

    Usage::to_json

    fn Usage::to_json(self : Usage) -> Json

    用量的 JSON 视图。

    Usage::total

    fn Usage::total(self : Usage) -> Int

    输入与输出 token 数之和。

    cassette_format_version

    let cassette_format_version : Int

    当前写入的 cassette 格式版本。

    历史:
    • 1:0.3.0 及更早写出的格式;
    • 2:引入流式记录(Response.stream)。

    default_drop_keys

    let default_drop_keys : Array[String]

    默认剔除的易变字段名。

    这些字段在真实 API 请求中经常变化,却不影响语义; 若它们参与指纹,回放将永远无法命中。

    generator_id

    let generator_id : String

    生成器标识。写入每个 cassette,便于追溯产出来源与排查兼容性问题。

    必须与 moon.mod 里的 version 保持一致:CI 会比对这两处,不一致 直接失败。之所以要靠校验而不是共享常量,是因为 MoonBit 无法在编译期 读取模块元信息,而这个标识一旦漂移,cassette 就会谎报产出版本。

    is_supported_version

    fn is_supported_version(version : Int) -> Bool

    读取端是否支持某个格式版本。

    读取端接受所有已发布过的版本,因此旧 cassette 不需要迁移就能继续用; 写入端则始终写当前版本。这样旧读取端遇到带流式记录的 cassette 会明确报 UnsupportedVersion,而不是把不认识的 stream 字段悄悄丢掉——后者会让 完整性校验以「摘要对不上」的形式失败,用户根本看不出真正的原因。

    prune_json

    fn prune_json(value : Json, drop_keys : ArrayView[String]) -> Json

    递归剔除对象中的指定键。

    数组元素会被逐项处理;标量原样返回。

    request_json

    fn request_json(provider : String, model : String, body : Json) -> Json

    由三要素拼出请求的 JSON 形状。

    独立暴露该函数是为了让「子集匹配」也能基于同一形状投影, 而不是各自拼一遍、日后产生分歧。

    stream_format_version

    let stream_format_version : Int

    引入流式记录(Response.stream)的格式版本。

    单独写出来,是因为写入端需要判断「这份内容最低要用哪个版本才能表达」: 从旧文件读进来、又追加了流式记录的 cassette 声明的仍是旧版本,此时必须 升到本值,否则文件会谎报自己是旧格式。