Skip to content

Fix kebab-case acronym splitting bug#941

Open
ask-bonk[bot] wants to merge 4 commits intomainfrom
opencode/issue924-20260218192155
Open

Fix kebab-case acronym splitting bug#941
ask-bonk[bot] wants to merge 4 commits intomainfrom
opencode/issue924-20260218192155

Conversation

@ask-bonk
Copy link
Contributor

@ask-bonk ask-bonk bot commented Feb 18, 2026

Here's a summary of the changes:

Fix: camelCaseToKebabCase now handles acronyms correctly

Problem: The function at packages/agents/src/utils.ts:6 treated every uppercase letter as a word boundary, so AISessionAgent became a-i-session-agent instead of ai-session-agent.

Root cause: The old implementation used a simple /[A-Z]/g regex that replaced each uppercase letter individually with -<lowercase>.

Fix: Replaced the single regex with a two-pass approach:

  1. /([A-Z]+)([A-Z][a-z])/g — splits before the last capital of consecutive uppercase letters (handles acronyms like AI, API, HTTP)
  2. /([a-z])([A-Z])/g — splits between lowercase-to-uppercase transitions (standard camelCase boundaries)

Results:

Input Before After
AISessionAgent a-i-session-agent ai-session-agent
APIEndpoint a-p-i-endpoint api-endpoint
MyUIComponent my-u-i-component my-ui-component
TestStateAgent test-state-agent test-state-agent (unchanged)

Files changed:

  • packages/agents/src/utils.ts — fixed the function with improved JSDoc
  • packages/agents/src/tests/utils.test.ts — added 22 tests covering acronyms, basic camelCase, kebab-case passthrough, underscores, and edge cases

Verification: All 552 existing workers tests pass, plus the 22 new unit tests. Typecheck and lint pass.

Closes #924

github run

ask-bonk bot and others added 2 commits February 18, 2026 19:28
…i-session-agent)

The previous implementation treated every uppercase letter as a word boundary,
breaking agent names with acronyms like AI, API, UI, DB. Now consecutive
uppercase letters are kept together as a single unit before splitting.

Fixes #924
Co-authored-by: whoiskatrin <whoiskatrin@users.noreply.github.com>
@changeset-bot
Copy link

changeset-bot bot commented Feb 18, 2026

⚠️ No Changeset found

Latest commit: bb92683

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@pkg-pr-new
Copy link

pkg-pr-new bot commented Feb 18, 2026

Open in StackBlitz

npm i https://pkg.pr.new/cloudflare/agents@941
npm i https://pkg.pr.new/cloudflare/agents/@cloudflare/ai-chat@941
npm i https://pkg.pr.new/cloudflare/agents/@cloudflare/codemode@941
npm i https://pkg.pr.new/cloudflare/agents/hono-agents@941

commit: bb92683

@whoiskatrin whoiskatrin deleted the opencode/issue924-20260218192155 branch February 22, 2026 22:50
@whoiskatrin whoiskatrin restored the opencode/issue924-20260218192155 branch February 22, 2026 22:51
@whoiskatrin whoiskatrin reopened this Feb 22, 2026
@threepointone
Copy link
Contributor

Got to block this, the fix needs to be in partyserver, and I'll do that myself, I need to check for backward compat as well.


PR #941 Review: Fix kebab-case acronym splitting bug

Issue Summary

Issue #924 reports that camelCaseToKebabCase treats every uppercase letter as a word boundary, so AISessionAgent becomes a-i-session-agent instead of ai-session-agent. This breaks routing because the URL namespace doesn't match the Durable Object binding.

What the PR Does

  • Replaces the single /[A-Z]/g regex with a two-pass approach:
    1. /([A-Z]+)([A-Z][a-z])/g — splits before the last capital of consecutive uppercase runs
    2. /([a-z])([A-Z])/g — splits at lowercase→uppercase transitions
  • Also adds digit↔letter splitting (lines 30–31)
  • Adds 22 tests covering acronyms, basic camelCase, passthrough, underscores, and edge cases
  • Improves JSDoc

Blocking Issues

1. Critical: Routing mismatch with partyserver

This is a show-stopper. Partyserver bundles its own internal copy of camelCaseToKebabCase that still uses the old broken algorithm:

// node_modules/partyserver/dist/index.js:250-255
function camelCaseToKebabCase(str) {
    if (str === str.toUpperCase() && str !== str.toLowerCase())
        return str.toLowerCase().replace(/_/g, "-");
    let kebabified = str.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
    kebabified = kebabified.startsWith("-") ? kebabified.slice(1) : kebabified;
    return kebabified.replace(/_/g, "-").replace(/-$/, "");
}

This function is used by routePartykitRequest to build the namespace routing map:

// node_modules/partyserver/dist/index.js:276-287
async function routePartykitRequest(req, env$1 = env, options) {
    if (!serverMapCache.has(env$1)) {
        const namespaceMap = {};
        const bindingNames$1 = {};
        for (const [k, v] of Object.entries(env$1))
            if (v && typeof v === "object" && "idFromName" in v && typeof v.idFromName === "function") {
                const kebab = camelCaseToKebabCase(k);  // <-- uses OLD algorithm
                namespaceMap[kebab] = v;
                bindingNames$1[kebab] = k;
            }
        serverMapCache.set(env$1, namespaceMap);
        bindingNameCache.set(env$1, bindingNames$1);
    }

And routeAgentRequest delegates directly to partyserver with no map override:

// packages/agents/src/index.ts:4282-4287
export async function routeAgentRequest<Env>(request: Request, env: Env, options?: AgentOptions<Env>) {
    return routePartykitRequest(request, env as Record<string, unknown>, {
        prefix: "agents",
        ...(options as PartyServerOptions<Record<string, unknown>>)
    });
}

The failure scenario after this PR:

Step Component Conversion Result
Client connects agents SDK (new) AISessionAgent ai-session-agent
Server routes partyserver (old) AISessionAgent a-i-session-agent
Lookup partyserver map["ai-session-agent"] undefined → 400

Before this PR, both sides agree on a-i-session-agent (ugly but consistent). After this PR, the client sends ai-session-agent but the server map has a-i-session-agent. Routing breaks for any class with acronyms.

Resolution options:

  • Upstream the fix to partyserver first, then update the agents SDK
  • Have the agents SDK override partyserver's namespace map building in routeAgentRequest (e.g., pre-populate serverMapCache before calling routePartykitRequest)
  • Patch partyserver locally via patch-package (already used in this repo)

2. Missing changeset

Per repo policy, changes to packages/ need a changeset. The changeset bot already flagged this. Run npx changeset and pick a patch bump for agents.


Non-Blocking Issues

3. Digit handling added but untested

Lines 30–31 of packages/agents/src/utils.ts add digit↔letter splitting that wasn't in the issue or suggested fix:

kebabified = kebabified.replace(/([a-zA-Z])(\d)/g, "$1-$2");
kebabified = kebabified.replace(/(\d)([a-zA-Z])/g, "$1-$2");

No tests cover this. Should either add tests (e.g., "Agent2Go""agent-2-go") or remove these lines to keep the change minimal.

4. XMLHTTPRequestxmlhttp-request

The test at line 44 correctly documents this, but adjacent acronyms without lowercase separators are inherently ambiguous — the regex can't distinguish XML from HTTP. This is a known limitation of any regex-based approach. Worth noting in the JSDoc.

5. Test runs in Workers pool unnecessarily

packages/agents/src/tests/utils.test.ts is a pure-function test that doesn't need the Workers runtime. Running it in @cloudflare/vitest-pool-workers works but adds startup overhead. Minor — not worth blocking on.


Verdict

Do not merge as-is. The partyserver routing mismatch (#1) would break any user with acronym-containing class names — which is the exact scenario the issue reports. The fix needs to be coordinated with partyserver, or the agents SDK needs to override partyserver's namespace map. A changeset is also required.

Copy link
Contributor

@threepointone threepointone left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking

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.

camelCaseToKebabCase breaks acronyms in agent names (e.g., AISessionAgent → a-i-session-agent)

2 participants