Skip to content

Commit 076e719

Browse files
box-catzhangahorowitz123mergify[bot]
authored
feat(metadata-editor): expose custom agent in extractStructured payload (#4135)
* feat(metadataeditor): enable ai folder extraction through API (#4102) * feat(metadata-editor): enable ai folder extraction through API * feat(metadata-editor): enable ai folder extraction through API * feat(metadata-editor): enable ai folder extraction through API * feat(metadata-editor): enable ai folder extraction through API * feat(metadata-editor): enable ai folder extraction through API * feat(metadata-editor): enable ai folder extraction through API * feat(metadata-editor): enable ai folder extraction through API * feat(metadata-editor): enable ai folder extraction through API * feat(metadata-editor): enable ai folder extraction through API * feat(metadata-editor): enable ai folder extraction through API * feat(metadata-editor): enable ai folder extraction through API * feat(metadata-editor): enable ai folder extraction through API * feat(metadata-editor): enable ai folder extraction through API * feat(metadata-editor): enable ai folder extraction through API * feat(metadata-editor): enable ai folder extraction through API --------- Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> * feat(metadata-editor): expose custom agent in extractStructured payload * chore: address coderrabit comments --------- Co-authored-by: adamh <[email protected]> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
1 parent 0dff276 commit 076e719

File tree

4 files changed

+91
-8
lines changed

4 files changed

+91
-8
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/**
2+
* @flow
3+
* @author Box
4+
*/
5+
6+
export type AiAgentTypeField = 'ai_agent_id';
7+
8+
export interface AiAgentReference {
9+
/**
10+
* AI Agent Reference to pass custom AI Agent ID to requests.
11+
* See https://developer.box.com/reference/resources/ai-agent-reference/
12+
*/
13+
+type: AiAgentTypeField;
14+
+id: string;
15+
}

src/api/schemas/AiExtractStructured.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
*/
55

66
import { AiAgentExtractStructured } from './AiAgentExtractStructured';
7+
import { AiAgentReference } from './AiAgentReference';
78
import { AiItemBase } from './AiItemBase';
89

910
export type AiExtractStructuredMetadataTemplateTypeField = 'metadata_template';
@@ -80,5 +81,10 @@ export interface AiExtractStructured {
8081
* The JSON blob that contains overrides for the agent config.
8182
*/
8283
+agent_config?: string;
83-
+ai_agent?: AiAgentExtractStructured;
84+
/**
85+
* * AI agent definition to use for extraction.
86+
* – `AiAgentExtractStructured`: customise Basic-Text / Long-Text agents
87+
* – `AiAgentReference` : reference a custom AI-Agent by ID
88+
*/
89+
+ai_agent?: AiAgentExtractStructured | AiAgentReference;
8490
}

src/elements/content-sidebar/__tests__/useSidebarMetadataFetcher.test.tsx

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ const mockPreconditionFailedError = {
3636
const mockFile = {
3737
id: '123',
3838
permissions: { [FIELD_PERMISSIONS_CAN_UPLOAD]: true },
39+
type: 'file',
3940
};
4041

4142
const mockTemplates = [
@@ -423,5 +424,62 @@ describe('useSidebarMetadataFetcher', () => {
423424
}),
424425
);
425426
});
427+
428+
test('should call extractStructured with custom AI agent ID', async () => {
429+
const { result } = setupHook();
430+
const agentId = 'custom-agent-123';
431+
432+
await result.current.extractSuggestions('templateKey', 'global', agentId);
433+
434+
expect(mockAPI.extractStructured).toHaveBeenCalledWith({
435+
items: [{ id: mockFile.id, type: mockFile.type }],
436+
metadata_template: { template_key: 'templateKey', scope: 'global', type: 'metadata_template' },
437+
ai_agent: { type: 'ai_agent_id', id: agentId },
438+
});
439+
});
440+
441+
test('should not call extractStructured with custom AI agent ID', async () => {
442+
const { result } = setupHook();
443+
444+
await result.current.extractSuggestions('templateKey', 'global');
445+
446+
// Assert that ai_agent is NOT present
447+
expect(mockAPI.extractStructured).toHaveBeenCalledWith(
448+
expect.not.objectContaining({
449+
ai_agent: expect.anything(),
450+
}),
451+
);
452+
// Also verify what IS present
453+
expect(mockAPI.extractStructured).toHaveBeenCalledWith({
454+
items: [{ id: mockFile.id, type: mockFile.type }],
455+
metadata_template: { template_key: 'templateKey', scope: 'global', type: 'metadata_template' },
456+
});
457+
});
458+
459+
test('should handle undefined agentIDs', async () => {
460+
const { result } = setupHook();
461+
462+
await result.current.extractSuggestions('templateKey', 'global', undefined);
463+
464+
// Assert that ai_agent is NOT present
465+
expect(mockAPI.extractStructured).toHaveBeenCalledWith(
466+
expect.not.objectContaining({
467+
ai_agent: expect.anything(),
468+
}),
469+
);
470+
// Also verify what IS present
471+
expect(mockAPI.extractStructured).toHaveBeenCalledWith({
472+
items: [{ id: mockFile.id, type: mockFile.type }],
473+
metadata_template: { template_key: 'templateKey', scope: 'global', type: 'metadata_template' },
474+
});
475+
mockAPI.extractStructured.mockClear();
476+
477+
await result.current.extractSuggestions('templateKey', 'global', '');
478+
expect(mockAPI.extractStructured).toHaveBeenCalledWith(
479+
expect.not.objectContaining({
480+
ai_agent: expect.anything(),
481+
}),
482+
);
483+
});
426484
});
427485
});

src/elements/content-sidebar/hooks/useSidebarMetadataFetcher.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import messages from '../../common/messages';
2929

3030
import { type BoxItem } from '../../../common/types/core';
3131
import { type ErrorContextProps, type ExternalProps, type SuccessContextProps } from '../MetadataSidebarRedesign';
32+
import { type AiExtractStructured } from '../../../api/schemas/AiExtractStructured';
3233

3334
export enum STATUS {
3435
IDLE = 'idle',
@@ -45,7 +46,7 @@ interface DataFetcher {
4546
| ERROR_CODE_METADATA_PRECONDITION_FAILED
4647
| ERROR_CODE_UNKNOWN
4748
| null;
48-
extractSuggestions: (templateKey: string, scope: string) => Promise<MetadataTemplateField[]>;
49+
extractSuggestions: (templateKey: string, scope: string, agentId?: string) => Promise<MetadataTemplateField[]>;
4950
file: BoxItem | null;
5051
handleCreateMetadataInstance: (
5152
templateInstance: MetadataTemplateInstance,
@@ -214,16 +215,19 @@ function useSidebarMetadataFetcher(
214215

215216
const [, setError] = React.useState();
216217
const extractSuggestions = React.useCallback(
217-
async (templateKey: string, scope: string): Promise<MetadataTemplateField[]> => {
218+
async (templateKey: string, scope: string, agentId?: string): Promise<MetadataTemplateField[]> => {
218219
const aiAPI = api.getIntelligenceAPI();
219220
setExtractErrorCode(null);
220-
221221
let answer = null;
222+
const customAiAgent = agentId ? { ai_agent: { type: 'ai_agent_id', id: agentId } } : {};
223+
const requestBody: AiExtractStructured = {
224+
items: [{ id: file.id, type: file.type }],
225+
metadata_template: { template_key: templateKey, scope, type: 'metadata_template' },
226+
...customAiAgent,
227+
};
228+
222229
try {
223-
answer = (await aiAPI.extractStructured({
224-
items: [{ id: file.id, type: file.type }],
225-
metadata_template: { template_key: templateKey, scope, type: 'metadata_template' },
226-
})) as Record<string, MetadataFieldValue>;
230+
answer = (await aiAPI.extractStructured(requestBody)) as Record<string, MetadataFieldValue>;
227231
} catch (error) {
228232
// Axios makes the status code nested under the response object
229233
if (error.response?.status === 408) {

0 commit comments

Comments
 (0)