Skip to content

Commit c3d728e

Browse files
committed
feat: enhance date handling in API tools and update SKILL.md for date format guidance
1 parent 55678b9 commit c3d728e

2 files changed

Lines changed: 96 additions & 5 deletions

File tree

apiBasedTools.ts

Lines changed: 91 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ type GetResourceDataToolResponse = {
9494
options?: Record<string, unknown>;
9595
};
9696

97+
type DateTimeColumnType = AdminForthDataTypes.DATETIME | AdminForthDataTypes.TIME;
98+
9799
const DEFAULT_USER_TIME_ZONE = 'UTC';
98100

99101
const TOOL_OVERRIDES: Record<string, ToolOverride> = {
@@ -373,6 +375,7 @@ async function applyToolOverride(params: {
373375
adminUser,
374376
inputs: nestedInputs,
375377
httpExtra: nestedHttpExtra,
378+
userTimeZone: nestedUserTimeZone,
376379
});
377380

378381
return applyToolOverride({
@@ -488,20 +491,104 @@ function createRawExpressResponse(response: ToolHttpResponse) {
488491
return rawResponse;
489492
}
490493

494+
function normalizeDateTimeInputsToUtc(
495+
body: Record<string, unknown>,
496+
adminforth: IAdminForth,
497+
userTimeZone?: string,
498+
): Record<string, unknown> {
499+
if (!userTimeZone || typeof body.resourceId !== 'string') {
500+
return body;
501+
}
502+
503+
const resource = adminforth.config.resources.find((res) => res.resourceId === body.resourceId);
504+
505+
if (!resource) {
506+
return body;
507+
}
508+
509+
const columnsByName = new Map(resource.dataSourceColumns.map((column) => [column.name, column]));
510+
511+
const normalizeColumnValue = (
512+
value: unknown,
513+
columnType: DateTimeColumnType,
514+
): unknown => {
515+
if (Array.isArray(value)) {
516+
return value.map((item) => normalizeColumnValue(item, columnType));
517+
}
518+
519+
if (typeof value !== 'string' || value === '') {
520+
return value;
521+
}
522+
523+
if (columnType === AdminForthDataTypes.DATETIME) {
524+
return dayjs.tz(value, userTimeZone).utc().toISOString();
525+
}
526+
527+
if (columnType === AdminForthDataTypes.TIME) {
528+
const userDate = dayjs().tz(userTimeZone).format('YYYY-MM-DD');
529+
return dayjs.tz(`${userDate}T${value}`, userTimeZone).utc().format('HH:mm:ss');
530+
}
531+
};
532+
533+
const normalizeValue = (value: unknown, key?: string): unknown => {
534+
const column = key ? columnsByName.get(key) : undefined;
535+
536+
if (column?.type === AdminForthDataTypes.DATETIME || column?.type === AdminForthDataTypes.TIME) {
537+
return normalizeColumnValue(value, column.type);
538+
}
539+
540+
if (Array.isArray(value)) {
541+
return value.map((item) => normalizeValue(item));
542+
}
543+
544+
if (!value || typeof value !== 'object') {
545+
return value;
546+
}
547+
548+
const record = value as Record<string, unknown>;
549+
const filterColumn = typeof record.field === 'string' ? columnsByName.get(record.field) : undefined;
550+
551+
if (
552+
'value' in record &&
553+
(filterColumn?.type === AdminForthDataTypes.DATETIME || filterColumn?.type === AdminForthDataTypes.TIME)
554+
) {
555+
return {
556+
...record,
557+
value: normalizeColumnValue(record.value, filterColumn.type),
558+
};
559+
}
560+
561+
return Object.fromEntries(
562+
Object.entries(record).map(([nestedKey, nestedValue]) => [
563+
nestedKey,
564+
normalizeValue(nestedValue, nestedKey),
565+
]),
566+
);
567+
};
568+
569+
return normalizeValue(body) as Record<string, unknown>;
570+
}
571+
491572
async function callCapturedEndpoint(params: {
492573
adminforth: IAdminForth;
493574
adminUser?: AdminUser;
494575
endpoint: CapturedEndpoint;
495576
httpExtra?: Partial<HttpExtra>;
496577
inputs?: Record<string, unknown>;
578+
userTimeZone?: string;
497579
}) {
498-
const { adminforth, adminUser, endpoint, httpExtra, inputs } = params;
580+
const { adminforth, adminUser, endpoint, httpExtra, inputs, userTimeZone } = params;
499581
const response = createToolResponse(httpExtra?.response);
500582
const headers = {
501583
'content-type': 'application/json',
584+
'X-TimeZone': userTimeZone,
502585
...(httpExtra?.headers ?? {}),
503586
};
504-
const body = (inputs ?? httpExtra?.body ?? {}) as Record<string, unknown>;
587+
const body = normalizeDateTimeInputsToUtc(
588+
(inputs ?? httpExtra?.body ?? {}) as Record<string, unknown>,
589+
adminforth,
590+
headers['X-TimeZone'],
591+
);
505592
const query = httpExtra?.query ?? {};
506593
const cookies = normalizeCookies(httpExtra?.cookies);
507594
const requestUrl = httpExtra?.requestUrl ?? `${adminforth.config.baseUrl}/adminapi/v1${endpoint.path}`;
@@ -596,6 +683,7 @@ export function prepareApiBasedTools(adminforth: IAdminForth): Record<string, Ap
596683
adminUser: adminUser ?? adminuser,
597684
inputs,
598685
httpExtra,
686+
userTimeZone,
599687
});
600688

601689
const processedOutput = await applyToolOverride({
@@ -629,4 +717,4 @@ export function serializeApiBasedTool(tool: ApiBasedTool | undefined) {
629717
output_schema: tool.output_schema,
630718
call: '[Function]',
631719
};
632-
}
720+
}

custom/skills/mutate_data/SKILL.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,7 @@ Are you sure?
7373
## Updating
7474

7575
You can use tool `update_record` tool it updates fields of record. To update `allowedActions.edit` should be set to true and
76-
`updated` column `showIn.edit` should be true at the same time. If one of this condition is not met, explain to user that is
77-
not allowed to edit
76+
`updated` column `showIn.edit` should be true at the same time. If one of this condition is not met, explain to user that is not allowed to edit
7877

7978
In addition to instructions above show user the table of edits (old value/new value)
8079

@@ -126,6 +125,10 @@ After creation of new record also show user a link to this record. If several re
126125

127126
Omit any pictures or file paths, you are not capable of doing it. If they are not required all is good, if they are required, explain to user that you are not able to create record because of this reason.
128127

128+
### Working with dates
129+
130+
When you create or update date or datetime fields, please use ISO format for this. For example, "2024-01-01" for date and "2024-01-01T12:00:00Z" for datetime. If user provides date in different format, try to parse it and convert to ISO format.
131+
129132
### Example
130133

131134

0 commit comments

Comments
 (0)