Skip to content

Commit 3082107

Browse files
author
Hawra2020
committed
feat: Support AI Extract endpoints
1 parent 3b5fc30 commit 3082107

14 files changed

+989
-78
lines changed

.eslintrc.yml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -246,9 +246,6 @@ rules:
246246
- error
247247
- last
248248
space-before-blocks: error
249-
space-before-function-paren:
250-
- error
251-
- never
252249
space-infix-ops: error
253250
space-unary-ops:
254251
- error

.prettierrc

Lines changed: 0 additions & 1 deletion
This file was deleted.

.prettierrc.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module.exports = {
2+
semi: true,
3+
singleQuote: true,
4+
bracketSpacing: true,
5+
trailingComma: 'all',
6+
};

docs/ai.md

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,71 @@ EXAMPLES
7878
```
7979

8080
_See code: [src/commands/ai/text-gen.js](https://github.com/box/boxcli/blob/v4.0.1/src/commands/ai/text-gen.js)_
81+
82+
## `box ai:extract`
83+
84+
Sends an AI request to supported LLMs and returns metadata in form of key-value pairs.
85+
86+
```
87+
USAGE
88+
$ box ai:extract --prompt <value> --items <value>... --ai-agent <value> [-t <value>] [--as-user <value>] [--no-color] [--json |
89+
--csv] [-s | --save-to-file-path <value>] [--fields <value>] [--bulk-file-path <value>] [-h] [-v] [-y] [-q]
90+
91+
FLAGS
92+
-h, --help Show CLI help
93+
-q, --quiet Suppress any non-error output to stderr
94+
-s, --save Save report to default reports folder on disk
95+
-t, --token=<value> Provide a token to perform this call
96+
-v, --verbose Show verbose output, which can be helpful for debugging
97+
-y, --yes Automatically respond yes to all confirmation prompts
98+
--as-user=<value> Provide an ID for a user
99+
--bulk-file-path=<value> File path to bulk .csv or .json objects
100+
--csv Output formatted CSV
101+
--fields=<value> Comma separated list of fields to show
102+
--items=<value>... (required) The items for the AI request
103+
--json Output formatted JSON
104+
--no-color Turn off colors for logging
105+
--prompt=<value> (required) The prompt for the AI request
106+
--ai-agent=<value> The AI agent to used for extraction
107+
--save-to-file-path=<value> Override default file path to save report
108+
109+
DESCRIPTION
110+
Sends an AI request to supported LLMs and returns metadata in form of key-value pairs.
111+
112+
EXAMPLES
113+
$ box ai:extract --prompt "firstName, lastName, location, yearOfBirth, company" --items "id=12345,type=file" --ai-agent \'{"type":"ai_agent_extract","basicText":{"llmEndpointParams":{"type":"openai_params","frequencyPenalty": 1.5,"presencePenalty": 1.5,"stop": "<|im_end|>","temperature": 0,"topP": 1},"model": "azure__openai__gpt_4o_mini","numTokensForCompletion": 8400,"promptTemplate": "It is, consider these travel options and answer the.","systemMessage": "You are a helpful travel assistant specialized in budget travel"},"longText":{"embeddings":{ "model": "azure__openai__text_embedding_ada_002","strategy":{"id": "basic","numTokensPerChunk": 64}},"llmEndpointParams":{"type":"openai_params","frequencyPenalty": 1.5,"presencePenalty": 1.5,"stop": "<|im_end|>","temperature": 0,"topP": 1},"model":"azure__openai__gpt_4o_mini","numTokensForCompletion":8400,"promptTemplate":"It is , consider these travel options and answer the.","systemMessage":"You are a helpful travel assistant specialized in budget travel"}}\'
114+
```
115+
116+
## `box ai:extract-structured`
117+
118+
Sends an AI request to supported LLMs and returns metadata in form of key-value pairs.
119+
120+
```
121+
USAGE
122+
$ box ai:extract-structured --items <value> --fields <value>... --metadata-template <value> --ai-agent <value> [-t <value>] [--as-user <value>] [--no-color] [--json |
123+
--csv] [-s | --save-to-file-path <value>] [--fields <value>] [--bulk-file-path <value>] [-h] [-v] [-y] [-q]
124+
125+
FLAGS
126+
-h, --help Show CLI help
127+
-q, --quiet Suppress any non-error output to stderr
128+
-s, --save Save report to default reports folder on disk
129+
-t, --token=<value> Provide a token to perform this call
130+
-v, --verbose Show verbose output, which can be helpful for debugging
131+
-y, --yes Automatically respond yes to all confirmation prompts
132+
--as-user=<value> Provide an ID for a user
133+
--bulk-file-path=<value> File path to bulk .csv or .json objects
134+
--csv Output formatted CSV
135+
--fields=<value> Comma separated list of fields to show
136+
--items=<value>... (required) The items for the AI request
137+
--json Output formatted JSON
138+
--no-color Turn off colors for logging
139+
--prompt=<value> (required) The prompt for the AI request
140+
--ai-agent=<value> The AI agent to be used for structured extraction
141+
--save-to-file-path=<value> Override default file path to save report
142+
143+
DESCRIPTION
144+
Sends an AI request to supported LLMs and returns metadata in form of key-value pairs.
145+
146+
EXAMPLES
147+
$ box ai:extract-structured --items="id=12345,type=file" --fields "key=firstName,type=string,description=Person first name,prompt=What is the first name?,displayName=First name" --fields "key=lastName,type=string,description=Person last name,prompt=What is the last name?,displayName=Last name" --ai-agent \'{"type":"ai_agent_extract_structured","basicText":{"llmEndpointParams":{"type":"openai_params","frequencyPenalty": 1.5,"presencePenalty": 1.5,"stop": "<|im_end|>","temperature": 0,"topP": 1},"model": "azure__openai__gpt_4o_mini","numTokensForCompletion": 8400,"promptTemplate": "It is, consider these travel options and answer the.","systemMessage": "You are a helpful travel assistant specialized in budget travel"},"longText":{"embeddings":{ "model": "azure__openai__text_embedding_ada_002","strategy":{"id": "basic","numTokensPerChunk": 64}},"llmEndpointParams":{"type":"openai_params","frequencyPenalty": 1.5,"presencePenalty": 1.5,"stop": "<|im_end|>","temperature": 0,"topP": 1},"model":"azure__openai__gpt_4o_mini","numTokensForCompletion":8400,"promptTemplate":"It is , consider these travel options and answer the.","systemMessage":"You are a helpful travel assistant specialized in budget travel"}}\'
148+
```

package-lock.json

Lines changed: 4 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
"@oclif/plugin-version": "^2.2.16",
3131
"archiver": "^3.0.0",
3232
"box-node-sdk": "^3.7.0",
33-
"box-typescript-sdk-gen": "^1.11.0",
33+
"box-typescript-sdk-gen": "^1.15.1",
3434
"chalk": "^2.4.1",
3535
"cli-progress": "^2.1.0",
3636
"csv": "^6.3.3",
@@ -50,7 +50,6 @@
5050
"p-event": "^2.3.1"
5151
},
5252
"devDependencies": {
53-
"@oclif/prettier-config": "^0.2.1",
5453
"@oclif/test": "^3.2.15",
5554
"@typescript-eslint/eslint-plugin": "^8.17.0",
5655
"babel-eslint": "^10.1.0",

src/box-command.js

Lines changed: 73 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,32 @@ function getBooleanFlagValue(value) {
118118
);
119119
}
120120

121+
/**
122+
* Removes all the undefined values from the object
123+
*
124+
* @param {Object} obj The object to format for display
125+
* @returns {Object} The formatted object output
126+
*/
127+
function removeUndefinedValues(obj) {
128+
if (typeof obj !== 'object' || obj === null) {
129+
return obj;
130+
}
131+
132+
if (Array.isArray(obj)) {
133+
return obj.map((item) => removeUndefinedValues(item));
134+
}
135+
136+
Object.keys(obj).forEach((key) => {
137+
if (obj[key] === undefined) {
138+
delete obj[key];
139+
} else {
140+
obj[key] = removeUndefinedValues(obj[key]);
141+
}
142+
});
143+
144+
return obj;
145+
}
146+
121147
/**
122148
* Add or subtract a given offset from a date
123149
*
@@ -157,7 +183,7 @@ function offsetDate(date, timeLength, timeUnit) {
157183
function formatKey(key) {
158184
// Converting camel case to snake case and then to title case
159185
return key
160-
.replace(/[A-Z]/gu, letter => `_${letter.toLowerCase()}`)
186+
.replace(/[A-Z]/gu, (letter) => `_${letter.toLowerCase()}`)
161187
.split('_')
162188
.map((s) => KEY_MAPPINGS[s] || _.capitalize(s))
163189
.join(' ');
@@ -433,7 +459,9 @@ class BoxCommand extends Command {
433459
* @private
434460
*/
435461
_setFlagsForBulkInput(bulkData) {
436-
const bulkDataFlags = bulkData.filter((o) => o.type === 'flag' && !_.isNil(o.value)).map((o) => o.fieldKey);
462+
const bulkDataFlags = bulkData
463+
.filter((o) => o.type === 'flag' && !_.isNil(o.value))
464+
.map((o) => o.fieldKey);
437465
Object.keys(this.flags)
438466
.filter((flag) => flag !== 'bulk-file-path') // Remove the bulk file path flag so we don't recurse!
439467
.filter((flag) => !bulkDataFlags.includes(flag))
@@ -810,7 +838,6 @@ class BoxCommand extends Command {
810838
tokenCache
811839
);
812840
DEBUG.init('Initialized client from environment config');
813-
814841
} else {
815842
// No environments set up yet!
816843
throw new BoxCLIError(
@@ -824,8 +851,12 @@ class BoxCommand extends Command {
824851
// Using the as-user flag should have precedence over the environment setting
825852
if (this.flags['as-user']) {
826853
client.asUser(this.flags['as-user']);
827-
DEBUG.init('Impersonating user ID %s using the ID provided via the --as-user flag', this.flags['as-user']);
828-
} else if (!this.flags.token && environment.useDefaultAsUser) { // We don't want to use any environment settings if a token is passed in the command
854+
DEBUG.init(
855+
'Impersonating user ID %s using the ID provided via the --as-user flag',
856+
this.flags['as-user']
857+
);
858+
} else if (!this.flags.token && environment.useDefaultAsUser) {
859+
// We don't want to use any environment settings if a token is passed in the command
829860
client.asUser(environment.defaultAsUserId);
830861
DEBUG.init(
831862
'Impersonating default user ID %s using environment configuration',
@@ -879,21 +910,26 @@ class BoxCommand extends Command {
879910
}
880911

881912
const { enterpriseID } = configObj;
882-
const tokenCache = environment.cacheTokens === false
883-
? null
884-
: new CLITokenCache(environmentsObj.default);
885-
let ccgConfig = new BoxTSSDK.CcgConfig(ccgUser ? {
886-
clientId,
887-
clientSecret,
888-
userId: ccgUser,
889-
tokenStorage: tokenCache,
890-
} : {
891-
clientId,
892-
clientSecret,
893-
enterpriseId: enterpriseID,
894-
tokenStorage: tokenCache,
895-
});
896-
let ccgAuth = new BoxTSSDK.BoxCcgAuth({config: ccgConfig});
913+
const tokenCache =
914+
environment.cacheTokens === false
915+
? null
916+
: new CLITokenCache(environmentsObj.default);
917+
let ccgConfig = new BoxTSSDK.CcgConfig(
918+
ccgUser
919+
? {
920+
clientId,
921+
clientSecret,
922+
userId: ccgUser,
923+
tokenStorage: tokenCache,
924+
}
925+
: {
926+
clientId,
927+
clientSecret,
928+
enterpriseId: enterpriseID,
929+
tokenStorage: tokenCache,
930+
}
931+
);
932+
let ccgAuth = new BoxTSSDK.BoxCcgAuth({ config: ccgConfig });
897933
client = new BoxTSSDK.BoxClient({
898934
auth: ccgAuth,
899935
});
@@ -918,7 +954,7 @@ class BoxCommand extends Command {
918954
const oauthAuth = new BoxTSSDK.BoxOAuth({
919955
config: oauthConfig,
920956
});
921-
client = new BoxTSSDK.BoxClient({auth: oauthAuth});
957+
client = new BoxTSSDK.BoxClient({ auth: oauthAuth });
922958
client = this._configureTsSdk(client, SDK_CONFIG);
923959
} catch (err) {
924960
throw new BoxCLIError(
@@ -969,8 +1005,8 @@ class BoxCommand extends Command {
9691005
enterpriseId: environment.enterpriseId,
9701006
tokenStorage: tokenCache,
9711007
});
972-
let jwtAuth = new BoxTSSDK.BoxJwtAuth({config: jwtConfig});
973-
client = new BoxTSSDK.BoxClient({auth: jwtAuth});
1008+
let jwtAuth = new BoxTSSDK.BoxJwtAuth({ config: jwtConfig });
1009+
client = new BoxTSSDK.BoxClient({ auth: jwtAuth });
9741010

9751011
DEBUG.init('Initialized client from environment config');
9761012
if (environment.useDefaultAsUser) {
@@ -1083,7 +1119,9 @@ class BoxCommand extends Command {
10831119
this.settings.enableAnalyticsClient &&
10841120
this.settings.analyticsClient.name
10851121
) {
1086-
additionalHeaders['X-Box-UA'] = `${DEFAULT_ANALYTICS_CLIENT_NAME} ${this.settings.analyticsClient.name}`;
1122+
additionalHeaders[
1123+
'X-Box-UA'
1124+
] = `${DEFAULT_ANALYTICS_CLIENT_NAME} ${this.settings.analyticsClient.name}`;
10871125
} else {
10881126
additionalHeaders['X-Box-UA'] = DEFAULT_ANALYTICS_CLIENT_NAME;
10891127
}
@@ -1128,6 +1166,9 @@ class BoxCommand extends Command {
11281166
let logFunc;
11291167
let stringifiedOutput;
11301168

1169+
// remove all the undefined values from the object
1170+
formattedOutputData = removeUndefinedValues(formattedOutputData);
1171+
11311172
if (outputFormat === 'json') {
11321173
stringifiedOutput = stringifyStream(formattedOutputData, null, 4);
11331174

@@ -1141,21 +1182,21 @@ class BoxCommand extends Command {
11411182
},
11421183
});
11431184

1144-
writeFunc = async(savePath) => {
1185+
writeFunc = async (savePath) => {
11451186
await pipeline(
11461187
stringifiedOutput,
11471188
appendNewLineTransform,
11481189
fs.createWriteStream(savePath, { encoding: 'utf8' })
11491190
);
11501191
};
11511192

1152-
logFunc = async() => {
1193+
logFunc = async () => {
11531194
await this.logStream(stringifiedOutput);
11541195
};
11551196
} else {
11561197
stringifiedOutput = await this._stringifyOutput(formattedOutputData);
11571198

1158-
writeFunc = async(savePath) => {
1199+
writeFunc = async (savePath) => {
11591200
await utils.writeFileAsync(savePath, stringifiedOutput + os.EOL, {
11601201
encoding: 'utf8',
11611202
});
@@ -1471,7 +1512,11 @@ class BoxCommand extends Command {
14711512
* @returns {void}
14721513
*/
14731514
async catch(err) {
1474-
if (err instanceof BoxTsErrors.BoxApiError && err.responseInfo && err.responseInfo.body) {
1515+
if (
1516+
err instanceof BoxTsErrors.BoxApiError &&
1517+
err.responseInfo &&
1518+
err.responseInfo.body
1519+
) {
14751520
const responseInfo = err.responseInfo;
14761521
let errorMessage = `Unexpected API Response [${responseInfo.body.status} ${responseInfo.body.message} | ${responseInfo.body.request_id}] ${responseInfo.body.code} - ${responseInfo.body.message}`;
14771522
err = new BoxCLIError(errorMessage, err);

0 commit comments

Comments
 (0)