fix: preserve data_collection child keys from case conversion (#70)#78
Conversation
…labs#70) data_collection stores user-defined identifiers (e.g. need_callback, call_end_reason, human_reached) that the ElevenLabs platform expects to receive and return verbatim. Without preservation, toCamelCaseKeys silently rewrote them to camelCase on push, which the platform stores as-is and which breaks dashboard features that key on the original snake_case identifiers (e.g. the callback checkbox feature driven by need_callback). Added data_collection and dataCollection to PRESERVE_CHILD_KEYS, so its immediate children are passed through unchanged in both push and pull directions. Tests: three new cases in casing.test.ts cover create / update / get round-trips. Verified that the existing failing reproduction fails without the fix and passes with it. No regressions in the wider suite. Fixes elevenlabs#70
There was a problem hiding this comment.
Pull request overview
This PR updates the shared key-casing normalization utilities so data_collection / dataCollection is treated like other “user-defined identifier maps” (e.g., dynamic_variables, language_presets, model_usage), preventing case conversion from rewriting its immediate child keys during push/pull.
Changes:
- Add
data_collection/dataCollectiontoPRESERVE_CHILD_KEYSto prevent child-key case conversion. - Add unit tests covering push-path preservation for
createAgentApiandupdateAgentApi. - Add a unit test intended to cover pull-path preservation for
getAgentApi.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| src/shared/utils.ts | Extends preserved-child-key handling to data_collection / dataCollection to avoid mutating user-defined identifiers during casing conversion. |
| src/tests/casing.test.ts | Adds tests asserting data_collection child keys are preserved on create/update, plus an inbound (get) test intended to validate the pull path. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| it("getAgentApi preserves data_collection child keys on inbound snake_case conversion", async () => { | ||
| const getWithDataCollection = jest.fn().mockResolvedValue({ | ||
| agentId: "agent_123", | ||
| name: "Test", | ||
| conversationConfig: { | ||
| agent: { prompt: { prompt: "hi", temperature: 0 } }, | ||
| }, | ||
| platformSettings: { | ||
| dataCollection: { | ||
| need_callback: { type: "boolean", description: "Callback" }, | ||
| call_end_reason: { type: "string" }, | ||
| }, | ||
| }, | ||
| tags: [], | ||
| }); | ||
| const client = { | ||
| conversationalAi: { agents: { get: getWithDataCollection } }, | ||
| } as unknown as ElevenLabsClient; | ||
|
|
||
| const response = await getAgentApi(client, "agent_123") as Record<string, any>; | ||
|
|
||
| // Envelope snake_cases back for disk | ||
| expect(response.platform_settings).toHaveProperty("data_collection"); | ||
| // User-defined identifiers preserved as-is — no round-trip corruption | ||
| expect(response.platform_settings.data_collection).toHaveProperty("need_callback"); | ||
| expect(response.platform_settings.data_collection).toHaveProperty("call_end_reason"); | ||
| expect(response.platform_settings.data_collection.need_callback).toEqual({ |
There was a problem hiding this comment.
Addressed in 303dac1 — added a separate test ("getAgentApi does not snake_case camelCase data_collection child keys") that uses a camelCase mock key and asserts it isn't converted.
…preservation logic The inbound test was using snake_case child keys in the mock response, which would pass even without PRESERVE_CHILD_KEYS. Changed one child key to camelCase (callEndReason) and assert it stays camelCase — proving the preservation logic prevents unwanted conversion to call_end_reason. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…servation The existing getAgentApi test uses snake_case child keys in the mock, which pass trivially without PRESERVE_CHILD_KEYS. Add a separate test with a camelCase child key (callEndReason) that asserts it is not converted to call_end_reason on the inbound path. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
kraenhansen
left a comment
There was a problem hiding this comment.
LGTM — fix follows the established pattern. Added a test to cover the inbound camelCase case flagged by Copilot.
Summary
Adds
data_collection/dataCollectiontoPRESERVE_CHILD_KEYSso its immediate children (user-defined identifiers likeneed_callback,call_end_reason,human_reached) are not silently rewritten to camelCase bytoCamelCaseKeyson push or to snake_case bytoSnakeCaseKeyson pull.Closes #70.
Why
data_collectionis the same class of construct asdynamic_variables,language_presets,model_usage,nodes,edges— its immediate children are user-defined identifiers that the platform stores verbatim. Case conversion is corrupting. In particular, the dashboard's callback-checkbox feature keys on the literalneed_callbackidentifier; after a CLI push those show up on the platform asneedCallbackand the feature silently stops working.Fix follows the same one-line pattern as the prior
model_usagefix (#72) andlanguage_presetsfix (#65).Test plan
Three new cases in
src/__tests__/casing.test.ts:createAgentApi preserves data_collection child keys (user-defined identifiers)— verifiesplatformSettings.dataCollection.need_callback(and siblings) survive outbound conversion with their exact original keys.updateAgentApi preserves data_collection child keys (user-defined identifiers)— same assertion on the update path.getAgentApi preserves data_collection child keys on inbound snake_case conversion— verifies the round-trip back to disk keeps children intact.Reproduced the bug locally by running the new tests against
main(2 of 3 fail: push-direction tests rewrite children to camelCase). With the fix, all 11casing.test.tscases pass.No regressions: the pre-existing failures in
tools.test.ts,add-commands-filename.integration.test.ts, andtest-commands.integration.test.tsreproduce identically onmainwithout this patch, so they are unrelated to this change.