Skip to content

[Next] Refactor: migrate any-llm-sdk to republic and extract bub core - #357

Merged
frostming merged 12 commits into
mainfrom
dev
Oct 20, 2026
Merged

frostming merged 12 commits into
mainfrom
dev

Conversation

@frostming

Copy link
Copy Markdown
Collaborator

No description provided.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
bub 5c0a5a9 Commit Preview URL

Branch Preview URL
Oct 19 2026, 03:41 AM

PsiACE and others added 9 commits October 18, 2026 16:55
…349)

Builtin model requests use Republic for provider authentication, message
encoding and streaming. Bub owns turn orchestration, hooks, tool
execution and tape storage, and converts persisted messages into Republic
messages at the request boundary.

- Tape messages use text/media blocks with kind, MIME type and URL, flat
  Republic tool-call fields and native token usage fields. Reasoning,
  opaque provider data and tool metadata survive tape reload and replay
  for the same provider/model. Tool calls recorded by older Bub versions
  in the Chat Completions form are converted on replay.
- The runtime consumes the Republic stream directly and reads the
  completed outcome from `Stream.response`. Fallback applies before a
  stream opens; incomplete streams raise before tool execution. Model and
  stream contexts close owned HTTP clients on completion, failure and
  cancellation, and leave caller-supplied clients open.
- ChatGPT plan access uses `codex:<model>` and `bub login codex`, with
  Codex CLI file credentials, `--device-auth` and an optional credential
  directory; `openai:<model>` uses API credentials. The bundled Codex
  provider is removed. Named endpoints keep independent credentials.
- Provider names and protocol defaults follow Republic. Provider and chat
  options pass directly to Republic, which deep-merges provider defaults
  with `extra_body`; Bub supplies only the tools, the token limit and the
  session's reasoning effort. Onboarding includes Magpie and saves
  `api_format: chat` for Chat Completions services.
- `build_prompt` returns a list of text and Republic media
  (`republic.image/audio/video`). Legacy string and content-block results
  are still accepted and converted (`bub.prompt`), and hooks that receive
  the prompt still get the legacy form.
- Model clients identify themselves with `User-Agent: bub/{version}`; a
  User-Agent in `client_args.headers` still wins.
- Requires the Republic 0.6 preview.

English and Chinese docs describe the current commands, settings and
message contracts.

Co-authored-by: Frost Ming <me@frostming.com>
- Add a stdlib `republic` module to run_code: media classes, messages,
  get_model() (chat and stream), get_embedding_model() and
  get_decision_model(). Model calls are proxied to Bub, which uses its
  provider settings and credentials; they never reach the code.
- Add code-only tools attach_image/attach_audio/attach_video that accept
  republic media or a path/URL/data URL and attach it to the run_code
  result, sent to the model as a multimodal tool message.
- Add content results (bub.tools.content_result) for tool results with
  media; spilling bounds only their text.
- Add Environment.read_bytes with a default that runs the environment's
  python.
- Document the republic module in the code mode stub and prompt.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Bub's agent loop was tangled with settings, plugin hooks, skills and
commands in `bub.builtin.Agent` / `ModelRunner`. Extract a reusable agent
kernel into `bub.core` and rebuild the builtin agent on top of it.

bub.core:
- `Agent`: the agent loop over tools, system prompt sections and a tape
  store. It holds no settings, model or session; each run gets the model
  to call and works on a fork of a named tape.
  `async with agent.run(prompt, model=..., tape=..., state=...)` closes
  the stream (merging the fork and ending the `invoke_agent` span) on
  exit; `agent.start(...)` returns a stream for another consumer to close.
- Models: a run accepts a `republic.ChatModel` directly; the
  `ModelClient` protocol covers callers with their own policy (fallbacks,
  credentials, timeouts).
- Events (`bub.core.events`): run, step, LLM call, tool call and chat
  events, plus user-defined events, registered with `agent.events.on()`.
  `Before*` events can be modified or short-circuited. Tape recording is a
  `TapeRecorder` handler.
- Commands: with `Agent(command_prefix=...)`, a prompt starting with the
  prefix calls a tool (or `bash`) directly; prefix rules live in
  `bub.core.commands`.
- System prompt: ordered sections registered with
  `agent.add_system_prompt(...)`, each receiving a `PromptContext`.
- Tape, store, sidecars, tools and tape context move into `bub.core`. The
  old `bub.tape`, `bub.store`, `bub.sidecars`, `bub.tools`,
  `bub.builtin.commands` and `bub.builtin.context` paths remain as module
  aliases.

Builtin:
- `BubAgent` (alias `bub.builtin.Agent` kept) subclasses the core agent,
  adding settings, plugin hooks, skills, code mode, deferred tools and
  steering.
- `BubModel` replaces the candidate/fallback logic of `ModelRunner`.
- `PluginAgentHooks` adapts the pluggy interception hooks to core events;
  hookspecs are unchanged.
- Tape archiving moves into `bub.builtin.archive`; `Tape` and `Agent` no
  longer take an archive path.

Behavior changes:
- A session's `reasoning_effort` is fixed at the start of a turn.
- Unknown `allowed_tools` raise before the session tape is forked.
- `agent.tape(name, state=...)` returns a conversation's tape for use
  outside runs; `session_tape_name()` computes a session's tape name.
- `Agent(store=...)` takes a store instance. Builtin channels no longer
  create the agent before `framework.running()`, so SDK users create
  `BubAgent` inside `framework.running()`.

BREAKING CHANGE: `bub.builtin.model_runner` and the deprecated `bub.types`
module are removed; `Tape` no longer takes an archive path.
Signed-off-by: Frost Ming <me@frostming.com>
`bub.core.Agent` accepts `republic.tools` built-in tools (`WebSearch`,
`WebFetch`, `CodeExecution`, `ImageGeneration`, `NativeTool`) alongside
`Tool` objects:

    agent = Agent(model=model, tools=[my_tool, republic.tools.WebSearch()])

- Function tools stay in `agent.tools` by name; built-in tools go to the
  new `agent.builtin_tools` list and are appended to `tools` on every
  model call. Republic maps them to each API format's native definition.
- Providers run them server-side, so they bypass `ToolExecutor`,
  `BeforeToolCall` and `prepare_tools`; their calls and results round-trip
  through `ProviderData` on the tape.
…kernel (#361)

Users who only embed the agent loop had to install all of Bub, and
importing the kernel loaded the framework. Make `bub.core` installable and
importable on its own, and tidy the kernel's API while it is in alpha.

Packaging:
- `pip install bub` installs only the kernel's dependencies (republic,
  pydantic, loguru, typing-extensions). The framework, CLI, channels,
  hooks, skills and builtin agent need the new `app` extra; `logfire`
  implies it.
- The `bub` script (`bub.__main__:main`) checks the `app` extra's
  requirements from bub's metadata and exits with an install hint instead
  of an ImportError. `bub.__main__` imports nothing from the extra at
  import time.
- The dev group depends on `bub[app]`, so `uv sync` keeps a full
  environment. The Dockerfile, install scripts and `bub install` request
  the extra. A new CI job checks that a plain wheel install imports
  `bub.core` without app dependencies and that `bub` asks for the extra.
- `bub` exports its top-level names lazily through one `_EXPORTS` table,
  and `.env` is loaded by `bub.configure`, where settings read the
  environment, instead of `bub.framework`.

Kernel layout:
- `bub.core` holds the agent concepts: agent, commands, context, events,
  model, recorder, system_prompt, tape, tools, store and sidecars.
  `bub.tape`, `bub.tools`, `bub.store` and `bub.sidecars` remain aliases
  of the `bub.core` modules and need only the kernel's dependencies.
- `errors`, `prompt`, `streaming`, `tracing` and `turn` stay top-level
  modules that need only the kernel's dependencies.
- The newest-first, deduplicated fuzzy tape search moves from
  `FileTapeStore` into `bub.builtin.store.FuzzyFileTapeStore`, which
  `provide_tape_store` returns. The kernel's `FileTapeStore` uses the plain
  substring query, so `bub.core` needs neither yaml nor rapidfuzz.

Agent events:
- New `RunFinished` (status `ok`, `error` or `cancelled`, steps, elapsed
  time, error). It follows every `RunStarted` when the run's stream
  closes, even if it is never iterated, and is recorded as `loop.finish`.
- Events are renamed to `*Started` / `*Finished`: `LLMCallStarted`,
  `LLMCallFinished`, `ToolCallStarted`, `ToolCallFinished` and
  `ChatFinished`. The payloads become `LLMCallRequest`, `LLMCallResult`
  and `LLMCallDecision`; `bub.hooks.interception` keeps the `LlmCall*`
  names as aliases. Hook names such as `before_llm_call` are unchanged.

Runs:
- `start` hands its `invoke_agent` span and close callback to `run_on`,
  which builds the run's only stream.
- `run_once` is an async generator that writes usage into the run's
  `StreamState`.

BREAKING CHANGE: install `bub[app]` (e.g. `uv tool install 'bub[app]'`) to
get the `bub` command and runtime; plugins should depend on `bub[app]`.
The core event classes are renamed, and `Agent.run_once` returns an async
generator.
    agent = Agent(model=model, tools=[...], max_tool_concurrency=2)

Limit how many tool calls from one model response run at once. `None`
(the default) runs them all concurrently; `1` runs them one at a time, in
order; values below 1 raise ValueError. `ToolExecutor` takes
`max_concurrency` and holds the limiter: an `asyncio.Semaphore`, or
`nullcontext()` when unlimited.
Code mode lived in `bub.builtin` and depended on settings, `bub.home` and
the `BubAgent` turn state, so kernel-only users could not use it. Move it
into `bub.core.codemode` as a reusable `CodeMode` component:

    code_mode = CodeMode([search, fetch], models="openai:gpt-5")
    agent = Agent(model=model, tools=[code_mode.run_code],
                  system_prompt=["...", code_mode.prompt])

`CodeMode` takes the tools to expose (a list, or a selector per turn
state), `environment=`, `models=` (a spec or a `ModelResolver` for the
`republic` bridge), `stub_dir=`, `media_tools=` and `name=`. Nested tool
calls still emit `ToolCallStarted`/`ToolCallFinished` with
`context.code_mode=True`.

`bub.builtin.codemode` is now thin wiring: the `CODE_MODE` instance, the
session setting, settings-based model resolution and stub files under bub
home.

BREAKING CHANGE: `bub.builtin.codemode.code_runner` is now
`bub.core.codemode.runner`, `bub.builtin.codemode.republic_bridge` is now
`bub.core.codemode.republic_bridge`, and `LocalEnvironment` and
`LocalProcess` move to `bub.environment`.
Split the docs into SDK (`bub.core`) and app (`bub[app]`) guides for
0.6.0, with new pages for agent events, app tools, reusing plugins and
supported versions, and refreshed English and Chinese guides, tutorials
and references.
frostming and others added 3 commits October 19, 2026 09:20
…371)

* feat!: reuse plugin agent-loop hooks with core Agent through events

Add `MessagesPending` and `StepEnding` events to `bub.core.Agent`, replacing
the `pending_messages`, `has_pending_messages` and `continue_prompt` override
methods. `MessagesPending` handlers add user messages before a step's prompt;
`StepEnding` handlers decide whether the run continues and with which prompt.

Add `bub.hooks.agent.AgentPlugins`, which connects a framework's agent-loop
hooks to any core Agent: interception, `continue_prompt` and the steering
inbox through `attach()`, `system_prompt` as a prompt section, store, tape
context and sidecars through `components()`, and `load_state` and
`provide_environment` through `build_state()`. `BubAgent` now uses it.

BREAKING CHANGE: subclasses overriding `Agent.pending_messages`,
`has_pending_messages` or `continue_prompt` must handle `MessagesPending` and
`StepEnding` instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor!: merge StepEnding into StepFinished

`StepFinished` now carries the step's `prompt`, the run's `stream` state and a
settable `next_prompt`, which gives the next step a user message when the
status is `continue`. Whether a run continues is decided before the event:
tool calls, or messages `MessagesPending` handlers supply when a step ends
without tool calls. These messages are sent in the next step, so the recorded
step status is final and the steering inbox needs no separate continuation
handler. The last allowed step leaves pending messages with their source.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor!: add the plugin system prompt section in AgentPlugins.attach

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat: let AgentPlugins create its framework from a plugin list and expose running()

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat!: provide tools through the provide_tools hook instead of @tool registration

@tool no longer adds tools to a global registry. Plugins provide tools with the
new provide_tools hook; the builtin plugin provides its tools this way, and
AgentPlugins.attach adds hook tools to an agent. For plugins that still rely on
@tool registering their tools, load_hooks collects the tools an entry-point
plugin without provide_tools creates while it loads, and warns.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Report command, channel work, and prompt state through the native EventBus. Preserve agent execution, normal text output, and exit codes, and keep redirected output free of status sequences.

Co-authored-by: Frost Ming <me@frostming.com>
Signed-off-by: Frost Ming <me@frostming.com>
@frostming
frostming merged commit d9531f3 into main Oct 20, 2026
7 checks passed
@frostming
frostming deleted the dev branch October 20, 2026 00:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

拆出 core 使 bub 更加 meta

2 participants