Skip to content

Releases: latere-ai/pkg

v0.94.0

Choose a tag to compare

@github-actions github-actions released this 06 Oct 21:35

Changed

  • authkit/oidc: HandleCallback requires a verified ID token. A token
    response without one now goes to /?auth_error=invalid_id_token and sets
    no session; before, the session was built from the access token's claims
    alone, which nothing verified. OpenID Connect requires an ID token on
    every authorization-code exchange that asks for openid, so a relying
    party signing in against Latere's auth service, or any conforming issuer,
    sees no difference. One configured without openid in its scopes cannot
    sign in until it adds it.
  • authkit/oidc: a session cookie over the 4096 bytes a browser stores for
    one cookie is refused instead of written. A browser drops a longer cookie
    without an error, which left the person signed out with nothing logged.
    At sign-in, HandleCallback goes to /?auth_error=session_too_large;
    SetSession returns an error, and a refresh that would outgrow the limit
    keeps the cookie the browser already holds and logs why.

v0.93.0

Choose a tag to compare

@github-actions github-actions released this 06 Oct 16:51

Added

  • authkit/oidc: Client.ReadSession reads the session cookie without
    refreshing it and without writing. It answers ErrRefreshRequired when
    the access token is within the refresh leeway of expiring and the session
    holds a refresh token, the same moment SessionFromRequest would
    refresh. A relying party that serves one browser from several replicas
    reads its API routes with it and refreshes on one route the page calls
    one request at a time, so two requests never spend one refresh token.
  • authkit/oidc: silent sign-in. /login?prompt=none asks the issuer to
    sign the person in without a page when they already hold a session
    there. When the issuer answers login_required (or any other error) to
    such a login, HandleCallback goes back to return_to with no
    auth_error, as if nothing was tried. HandleLogin forwards prompt
    only with a value OpenID Connect defines (none, login, consent,
    select_account), and FlowState.Silent records a silent login.

Fixed

  • authkit/oidc: a page that loads several routes at once no longer signs
    the person out. Each request found the access token about to expire and
    refreshed the same rotating refresh token; the issuer took the second
    use for replay and revoked the session. SessionFromRequest, BuildMe
    and UserFromRequest now spend a refresh token at most once per
    process: concurrent requests share one refresh, and a request that still
    carries the spent token within 30 seconds is answered with the refresh
    already made. Requests that reach different replicas can still race;
    see ReadSession.
  • authkit/oidc: a refresh that fails without the issuer refusing it (no
    connection, a timeout, a 5xx) wraps the new ErrIssuerUnavailable and
    no longer signs the person out: BuildMe and UserFromRequest keep the
    cookie, so the next request tries again. A refresh the issuer refuses
    wraps ErrSessionExpired, so errors.Is tells the two apart on the
    error SessionFromRequest returns.

v0.92.0

Choose a tag to compare

@github-actions github-actions released this 05 Oct 10:29

Added

  • vendors/linkup: a client for Linkup's web search API. Search sends
    one Request, the query with one of four depths (flash, fast,
    standard, deep) and an output type (ranked results, a sourced answer,
    or an object that follows a JSON Schema), optionally narrowed to domains,
    a date range and a result count, and returns a Response holding what
    that output type answers. A status other than 200 is an *Error with the
    status, Linkup's error code and message, the refused fields and the
    Retry-After wait, and it matches exactly one of ErrBadRequest,
    ErrNoResult, ErrAuth, ErrInsufficientCredit, ErrRateLimited and
    ErrUpstream, so a rate limit that passes is told apart from exhausted
    credit that does not. The API key never appears in an error, also where
    Linkup's error body echoes it. Requests go through otel.HTTPClient
    unless WithHTTPClient replaces it, and nothing is retried, since every
    search is billed.

Changed

  • typesafeai moves to vendors/typesafe, the directory that holds one
    client per third-party API, with its API unchanged. Import
    latere.ai/x/pkg/vendors/typesafe; the package name stays typesafe, so
    only the import path changes. The old path is removed, with no forwarding
    package.

v0.91.0

Choose a tag to compare

@github-actions github-actions released this 02 Oct 13:27

Added

  • verdict: the vocabulary every decision point shares when it decides
    whether an automated action runs. Allow, Flag, Ask and Block
    in their order; Least, under which a verdict outside the four counts
    as Block, so composition only narrows; OnFailure, which is never
    more permissive than Ask; and Decide, which applies a ceiling and
    random review sampling to a suggested verdict and returns the verdict
    with the probability, fixed before the action runs, that a person sees
    it. Recording that probability with each decision is what lets any
    decision source's error rate be estimated without bias. A harness that
    ranks verdicts itself should move to Least: ranking an unknown
    verdict by its index in a list makes it the most permissive.

v0.90.2

Choose a tag to compare

@github-actions github-actions released this 01 Oct 21:53
  • OpenTelemetry Go v1.46.0, with its log modules v0.22.0, the slog bridge v0.20.1 and otelhttp v0.71.0, past GO-2026-6615 and GO-2026-6505. otel names its resource with semantic conventions v1.43.0, the SDK's own schema since v1.46.0; with v1.41.0 the two schemas conflict and telemetry export is disabled at start.

v0.90.1

Choose a tag to compare

@github-actions github-actions released this 01 Oct 08:42

Fixed

  • llmdialect/openaichat reads native_finish_reason, the provider's
    own finish reason an aggregator such as OpenRouter passes beside the
    normalized one. A native reason naming the output limit (length,
    max_tokens, max_output_tokens, MAX_TOKENS) is max_tokens
    whatever finish_reason says. OpenRouter reports a response cut at the
    limit inside a tool call's arguments as tool_calls, so the call
    reached the caller as complete with arguments that are not JSON.
  • The llmdialect/openaichat stream decoder keeps the IR grammar for
    parallel tool calls whose arguments interleave: a call announced while
    the open call's arguments are not yet one JSON value waits, buffered,
    until they are or the stream ends, and its block then starts with what
    it received. It closed a call's block when the next call began, so
    arguments that arrived after for the earlier call were args deltas for
    a block already stopped, which a consumer that checks the grammar
    refuses. Calls that arrive one after another stream as before.

v0.90.0

Choose a tag to compare

@github-actions github-actions released this 27 Sep 18:15

Fixed

  • hostsandbox.Driver.Preflight looks for bwrap on Linux, the program
    the bubblewrap package installs. It looked for an executable named
    bubblewrap, which no package provides, so a Linux machine with
    Bubblewrap installed was refused. The refusal names the component
    bwrap, and DefaultRemedies keys its row bwrap, with install lines
    that still name the bubblewrap package. A consumer whose Config.Look
    mapped bubblewrap to bwrap can drop the mapping; one that adds or
    replaces that row through Config.Remedies, or calls
    Remedies.NotReady with bubblewrap, names bwrap instead.

Changed

  • hostsandbox.Driver.Stop, and Discard with it, also ends the
    processes a stage left running in its process group after its main
    process exited. It returned at once for a stage with a recorded exit
    status, so a consumer that wanted those processes gone had to parse the
    pid:<pid>@<start>:<log> handle and signal the group itself; it calls
    Stop instead. The group is signaled only while it provably is the
    stage's: its leader is alive with the handle's start time, or the leader
    is gone and a member of the group started no later than the stage was
    last known to run, which is when Stop last saw the leader or the
    modification time of the exit status file. A group formed later under a
    reused pid has only members started after that, so it is never
    signaled. A group whose leader was killed before it recorded a status,
    outside Stop, is left alone for the same reason, and a process that
    started its own session has left the group and is not reached. Stop
    now waits for the group to empty rather than for the status file, so a
    stage that exits on SIGTERM no longer costs the whole grace period. The
    exit status recorded before Stop is kept.
  • hostsandbox.Sandbox states this for every driver: Stop ends every
    process the stage started that is still running, including one left
    after its main process exited, and Discard ends what Stop would.
    hostsandboxtest.Run holds every driver to it, which needs a new
    Subject.Orphan field: an argv whose main process exits zero at once,
    leaving a process that writes to stdout at least every 100 milliseconds
    for at least the given duration. A consumer's contract test sets it; the
    two new cases fail while it is nil.

v0.89.1

Choose a tag to compare

@github-actions github-actions released this 27 Sep 17:28

Fixed

  • egress.Gateway refuses a request inside a terminated tunnel whose
    Host, or :authority over HTTP/2, names another authority than the
    tunnel's, answering 421 Misdirected Request. The request was dialed to
    the tunnel's host with that host's credentials swapped in and carried the
    other name upstream, so a front that routes by Host could deliver the
    credentials to another service. Hosts compare without case, and a missing
    port is 443.

v0.89.0

Choose a tag to compare

@github-actions github-actions released this 27 Sep 14:33

Added

  • llmdialect/bridge.Model carries a model's figures, so a model list
    can tell a client its window and its prices: ContextWindow,
    MaxOutputTokens, InputModalities, and Pricing, a
    bridge.ModelPricing of decimal strings per 1,000,000 tokens
    (Currency, Input, Output, CachedInput, CacheWrite); a price
    quoted per another count is the caller's to convert. ModelList and
    ModelEntry write them after the members each wire's clients read:
    OpenAI's as context_window, max_output_tokens, input_modalities
    and pricing (cached_input, cache_write); Anthropic's as its own
    max_input_tokens and max_tokens beside the same input_modalities
    and pricing; Google's as its own inputTokenLimit and
    outputTokenLimit, with no modalities or prices; the lux wire's as
    the OpenAI entry, since the lux dialect's JSON is snake case. Every
    pricing member writes per: 1000000. A zero figure or an empty price
    is left out, so an entry without figures renders byte for byte as
    before.

Changed

  • llmdialect/openairesp: the Responses frontend keeps a reasoning
    model's reasoning across turns, so a Responses client of the gateway
    no longer loses it on the way to an OpenAI Responses upstream. include: ["reasoning.encrypted_content"] sets ir.Request.ReasoningReplay
    instead of recording include loss. An input item of type reasoning
    that carries encrypted_content becomes an opaque block of dialect
    openai-responses and kind reasoning in the assistant turn where it
    stands, its JSON in the form ir.Opaque documents, so the backend
    replays it as the client sent it and the other backends drop it with
    ir.LossOpaque (opaque) instead of reasoning. A reasoning item
    without encrypted_content is still dropped with
    ir.LossReasoningItems: only the upstream's store could resolve it,
    and this surface stores nothing. EncodeResponse writes such an opaque
    block back as the output item it was, byte for byte, where it stands,
    and a thinking block right before it no longer gets a reasoning item
    of its own, since the item carries its summary. The stream does the
    same: the thinking block's response.output_item.added and summary
    deltas open the item, its response.output_item.done carries the
    item as it came, and response.completed lists it; a reasoning item
    without a thinking block before it gets an output_item.added with its
    id and an empty summary. The output_item.done of a thinking block is
    written when the next event arrives, not at its block_stop. The
    added frame names the item by a synthetic id, since the upstream's
    id arrives only with the item; take the item from the done frame.
    Opaque blocks of other dialects or kinds are still left out.

v0.88.0

Choose a tag to compare

@github-actions github-actions released this 27 Sep 08:55

Added

  • llmdialect/ir.BlockOpaque, a content block that carries one
    provider item verbatim for replay to the dialect that produced it:
    Block.Opaque holds an ir.Opaque with the producing Dialect, the
    item's Kind within it, and its JSON as Raw. A backend of that
    dialect writes Raw unchanged where the block stands; the other
    backends drop the block and record ir.LossOpaque (opaque), never an
    error. The lux dialect carries it both ways as {"type":"opaque", "opaque":{"dialect","kind","raw"}}, with raw as the JSON value
    itself, so a harness that stores sessions as lux JSON keeps the item
    byte for byte; luxsdk.Opaque and luxsdk.BlockOpaque re-export the
    vocabulary. Raw is kept in the form encoding/json writes (compact,
    with <, > and & escaped), which every later marshal leaves as it
    is; the lux decoder brings a body written by another encoder into that
    form. In a stream the block is one block_start whose header
    carries the payload, then its block_stop. The Anthropic, Chat and
    Responses frontends leave it out of the responses and streams they
    write, and the Anthropic stream shifts later content indices down past
    it so they stay dense. ir.PrefixCacheKeys hashes the block's
    dialect, kind and raw JSON after the fields every block contributes;
    keys of requests without opaque blocks are unchanged.
  • llmdialect/ir.Request.ReasoningReplay asks for the model's reasoning
    in a form the next request of the conversation can carry back, which
    a reasoning model served over OpenAI Responses needs to keep its
    reasoning across turns. The openairesp backend then writes include: ["reasoning.encrypted_content"] (beside the logprobs member when that
    is asked too) and store: false, and keeps each reasoning item that
    comes back with encrypted_content as an opaque block of kind
    reasoning, from the response body and from the stream's
    response.output_item.done frame alike, after the summary's thinking
    block. Encoding a request replays such a block as its input item, and
    the thinking block right before it travels inside the item instead of
    being reported as thinking loss. A reasoning item without
    encrypted_content is not kept, so a caller that does not ask sees
    the same blocks and streams as before. The lux dialect carries the
    ask as reasoning_replay; the anthropic backend needs no ask, since
    its thinking blocks carry their signatures; the openaichat backend
    records ir.LossReasoningReplay (reasoning_replay).