Skip to content

Commit 9ffc9b6

Browse files
jsonbaileyclaude
andcommitted
refactor: extract _parseToolsMap helper, add per-entry validation to root tools path, fall back to key name when name field absent
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent b6a9ee4 commit 9ffc9b6

2 files changed

Lines changed: 33 additions & 23 deletions

File tree

packages/sdk/server-ai/__tests__/LDAIClientImpl.test.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -980,7 +980,7 @@ describe('tools map support', () => {
980980
expect(result.tools).toBeUndefined();
981981
});
982982

983-
it('returns undefined when a model.parameters.tools entry is missing the name field', async () => {
983+
it('falls back to key name for model.parameters.tools entries missing the name field', async () => {
984984
const client = new LDAIClientImpl(mockLdClient);
985985
const key = 'test-flag';
986986
const defaultValue: LDAICompletionConfigDefault = { enabled: false };
@@ -990,7 +990,7 @@ describe('tools map support', () => {
990990
parameters: {
991991
tools: {
992992
'valid-tool': { name: 'valid-tool', type: 'function' },
993-
'invalid-tool': { type: 'function' },
993+
'no-name-tool': { type: 'function' },
994994
},
995995
},
996996
},
@@ -1000,6 +1000,9 @@ describe('tools map support', () => {
10001000

10011001
const result = await client.completionConfig(key, testContext, defaultValue);
10021002

1003-
expect(result.tools).toBeUndefined();
1003+
expect(result.tools).toBeDefined();
1004+
expect(result.tools!['valid-tool'].name).toBe('valid-tool');
1005+
expect(result.tools!['no-name-tool']).toBeDefined();
1006+
expect(result.tools!['no-name-tool'].name).toBe('no-name-tool');
10041007
});
10051008
});

packages/sdk/server-ai/src/api/config/LDAIConfigUtils.ts

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,29 @@ export class LDAIConfigUtils {
146146
}
147147
}
148148

149+
private static _parseToolsMap(
150+
toolsMap: { [key: string]: unknown },
151+
logger: LDLogger | undefined,
152+
): { [toolName: string]: LDTool } {
153+
const result: { [toolName: string]: LDTool } = {};
154+
for (const [toolName, toolValue] of Object.entries(toolsMap)) {
155+
if (toolValue === null || typeof toolValue !== 'object' || Array.isArray(toolValue)) {
156+
logger?.warn(`LaunchDarkly AI: Skipping tool "${toolName}": expected an object`);
157+
continue;
158+
}
159+
const toolObj = toolValue as { [key: string]: unknown };
160+
result[toolName] = {
161+
name: typeof toolObj['name'] === 'string' ? toolObj['name'] : toolName,
162+
description:
163+
typeof toolObj['description'] === 'string' ? toolObj['description'] : undefined,
164+
type: typeof toolObj['type'] === 'string' ? toolObj['type'] : undefined,
165+
parameters: toolObj['parameters'] as LDTool['parameters'],
166+
customParameters: toolObj['customParameters'] as LDTool['customParameters'],
167+
};
168+
}
169+
return result;
170+
}
171+
149172
private static _resolveTools(
150173
flagValue: LDAIConfigFlagValue,
151174
logger?: LDLogger,
@@ -157,7 +180,8 @@ export class LDAIConfigUtils {
157180
);
158181
return undefined;
159182
}
160-
return flagValue.tools;
183+
const parsed = this._parseToolsMap(flagValue.tools as { [key: string]: unknown }, logger);
184+
return Object.keys(parsed).length > 0 ? parsed : undefined;
161185
}
162186

163187
const rawTools = flagValue.model?.parameters?.['tools'];
@@ -173,25 +197,8 @@ export class LDAIConfigUtils {
173197
return undefined;
174198
}
175199

176-
const toolsMap = rawTools as { [key: string]: unknown };
177-
const result: { [toolName: string]: LDTool } = {};
178-
179-
for (const [toolName, toolValue] of Object.entries(toolsMap)) {
180-
if (
181-
toolValue === null ||
182-
typeof toolValue !== 'object' ||
183-
Array.isArray(toolValue) ||
184-
typeof (toolValue as { name?: unknown }).name !== 'string'
185-
) {
186-
logger?.warn(
187-
`LaunchDarkly AI: Skipping tool "${toolName}" in model.parameters.tools: expected an object with a name string`,
188-
);
189-
continue;
190-
}
191-
result[toolName] = toolValue as LDTool;
192-
}
193-
194-
return Object.keys(result).length > 0 ? result : undefined;
200+
const parsed = this._parseToolsMap(rawTools as { [key: string]: unknown }, logger);
201+
return Object.keys(parsed).length > 0 ? parsed : undefined;
195202
}
196203

197204
/**

0 commit comments

Comments
 (0)