Upstream sync — litellm → openai_compat_provider, timezone, cron improvements#14
Merged
Conversation
Replace `get_cron_dir()` with `config.workspace_path / "cron"` so each workspace keeps its own `jobs.json`. This lets users run multiple nanobot instances with independent cron schedules without cross-talk. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When upgrading, if jobs.json exists at the old global path and not yet at the workspace path, move it automatically. Prevents silent loss of existing cron jobs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implement file upload and sending for QQ C2C messages Reference: https://github.com/tencent-connect/botpy/blob/master/examples/demo_c2c_reply_file.py --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: chengyongru <chengyongru.ai@gmail.com>
- Fix _read_media_bytes treating local paths as URLs: local file handling code was dead code placed after an early return inside the HTTP try/except block. Restructure to check for local paths (plain path or file:// URI) before URL validation, so files like /home/.../.nanobot/workspace/generated_image.svg can be read and sent correctly. - Add .svg to _IMAGE_EXTS so SVG files are uploaded as file_type=1 (image) instead of file_type=4 (file). - Add tests for local path, file:// URI, and missing file cases. Fixes: HKUDS#1667 (comment) Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
urlparse on Windows puts the path in netloc, not path. Use (parsed.path or parsed.netloc) to get the correct raw path.
Keep cron state workspace-scoped while only migrating legacy jobs into the default workspace. This preserves seamless upgrades for existing installs without polluting intentionally new workspaces.
…th safe default-only migration fix(cron): scope cron state to each workspace with safe default-only migration
Keep the channel enhancements aligned with the current codebase while preserving a simpler product surface. This keeps QQ, Feishu, Telegram, and WhatsApp improvements together, removes the extra Telegram-only tool hint toggle, and makes WhatsApp mention-only groups actually work.
… WhatsApp feat: telegram/qq/whatsapp/feishu enhancement
Keep the mypy-friendly optional execute signatures while returning clearer errors for missing arguments and locking that behavior with regression tests. Made-with: Cursor
- Remove litellm dependency entirely (supply chain risk mitigation) - Add AnthropicProvider (native SDK) and OpenAICompatProvider (unified) - Merge CustomProvider into OpenAICompatProvider, delete custom_provider.py - Add ProviderSpec.backend field for declarative provider routing - Remove _resolve_model, find_gateway, find_by_model (dead heuristics) - Pass resolved spec directly into provider — zero internal lookups - Stub out litellm-dependent model database (cli/models.py) - Add anthropic>=0.45.0 to dependencies, remove litellm - 593 tests passed, net -1034 lines
…ai 1.0.3 requirements
- Prevent repeated retries on expired sessions in the polling thread - Stop sending messages to invalid agent sessions to eliminate noise logs and unnecessary requests
…versations after restart
Clarify group policy behavior for bot responses in group channels.
Clarified instructions for group policy behavior in README.
Handle string and dict-shaped responses from OpenAI-compatible backends so non-standard providers no longer crash on missing choices fields. Add regression tests to keep SDK, dict, and plain-text parsing paths aligned.
…e thought signatures in extra_content
…t_signature round-trip Replace the flatten/unflatten approach (merging extra_content.google.* into provider_specific_fields then reconstructing) with direct pass-through: parse extra_content as-is, store on ToolCallRequest.extra_content, serialize back untouched. This is lossless, requires no hardcoded field names, and covers all three parsing branches (str, dict, SDK object) plus streaming.
Add agent-level timezone configuration with a UTC default, propagate it into runtime context and heartbeat prompts, and document valid IANA timezone usage in the README.
Make cron use the configured agent timezone when a cron expression omits tz or a one-shot ISO time has no offset. This keeps runtime context, heartbeat, and scheduling aligned around the same notion of time. Made-with: Cursor
Make cron list output render one-shot and run-state timestamps in the same timezone context used to interpret schedules. This keeps scheduling logic and user-facing time displays consistent. Made-with: Cursor
Read the default timezone from the agent context when wiring the cron tool so startup no longer depends on an out-of-scope local variable. Add a regression test to ensure AgentLoop passes the configured timezone through to cron. Made-with: Cursor
- Add send_max_retries config option (default: 3, range: 0-10) - Implement _send_with_retry in ChannelManager with 1s/2s/4s backoff - Propagate CancelledError for graceful shutdown - Fix telegram send_delta to raise exceptions for Manager retry - Add comprehensive tests for retry logic - Document channel settings in README
Make channel delivery failures raise consistently so retry policy lives in ChannelManager rather than being split across individual channels. Tighten Telegram stream finalization, clarify sendMaxRetries semantics, and align the docs with the behavior the system actually guarantees.
Made-with: Cursor
Key changes from upstream: - Replace litellm_provider.py with openai_compat_provider.py (native openai SDK) - Add backend-based provider routing (openai_compat / anthropic / azure_openai / codex) - Add timezone support to ContextBuilder and AgentLoop - Add send_max_retries to ChannelConfig - Update registry: remove litellm_prefix/skip_prefixes, add backend field Preserved custom patches: - suppress_tools_param + request_timeout on OpenAICompatProvider - _extract_text_tool_calls (Qwen3/Nemotron/XML/JSON text-embedded tool call parsing) - EOS token stripping (<|im_end|>, </s>, etc.) - HttpConfig field on ChannelConfig - memory_max_chars/tokens/compaction_enabled on ContextBuilder + AgentLoop - sysmon param on AgentLoop - _parse_at relative time parsing in CronTool Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Weekly upstream sync from HKUDS/nanobot. Resolves all 7 conflict files, migrates our custom patches from the deleted
litellm_provider.pyto the newopenai_compat_provider.py.Upstream changes pulled in
litellm_provider.pyremoved upstream, replaced withopenai_compat_provider.py+ native Anthropic/Azure/Codex providersmax_completion_tokens, Gemini thought signature round-trip, plain-text response handlingOur patches preserved
suppress_tools_param— skipstoolsparam for models that reject it (Qwen3.5-4B). Moved fromlitellm_provider.py→OpenAICompatProviderrequest_timeout— configurable per-provider timeout. Same migration._extract_text_tool_calls— parses tool calls embedded in text content (Qwen3<tool_call>, Nemotron[TOOL_CALLS], XML, Python code-block, raw JSON). Added to both dict and SDK object parse paths inOpenAICompatProvider.<|im_end|>,<|endoftext|>,</s>,<|eot_id|>from response content.sysmontool wiring incommands.pyandloop.py.Conflicts resolved
providers/registry.py— removedlitellm_prefix/skip_prefixes, acceptedbackend="openai_compat"config/schema.py— kepthttp: HttpConfig, added upstreamsend_max_retriesagent/context.py— mergedtimezoneparam alongside our memory compaction paramsagent/loop.py— mergedtimezone+sysmonparams, updatedContextBuilderinstantiationcli/commands.py— movedsuppress_tools_param/request_timeouttoOpenAICompatProviderbranch; keptsysmon+timezonewiringagent/tools/cron.py— kept our relative time parsing, merged upstream timezone-awareathandlingproviders/litellm_provider.py— accepted deletion viagit rm