mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
fix(agiloft): correct response parsing, add EWGetChoiceLineId tool (#4477)
* fix(agiloft): correct response parsing, add EWGetChoiceLineId tool * fix(agiloft): address PR review feedback
This commit is contained in:
@@ -7,7 +7,7 @@ import { BlockInfoCard } from "@/components/ui/block-info-card"
|
||||
|
||||
<BlockInfoCard
|
||||
type="agiloft"
|
||||
color="#FFFFFF"
|
||||
color="#001028"
|
||||
/>
|
||||
|
||||
{/* MANUAL-CONTENT-START:intro */}
|
||||
@@ -137,6 +137,28 @@ Delete a record from an Agiloft table.
|
||||
| `id` | string | ID of the deleted record |
|
||||
| `deleted` | boolean | Whether the record was successfully deleted |
|
||||
|
||||
### `agiloft_get_choice_line_id`
|
||||
|
||||
Resolve the internal numeric ID of a choice-list value, for use in EWSelect WHERE clauses against choice fields.
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `instanceUrl` | string | Yes | Agiloft instance URL \(e.g., https://mycompany.agiloft.com\) |
|
||||
| `knowledgeBase` | string | Yes | Knowledge base name |
|
||||
| `login` | string | Yes | Agiloft username |
|
||||
| `password` | string | Yes | Agiloft password |
|
||||
| `table` | string | Yes | Table name \(e.g., "case", "contracts"\) |
|
||||
| `fieldName` | string | Yes | Choice field name \(e.g., "priority", "status"\) |
|
||||
| `value` | string | Yes | Choice display value to resolve \(e.g., "High", "Active"\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `choiceLineId` | number | Internal numeric line ID of the choice value |
|
||||
|
||||
### `agiloft_lock_record`
|
||||
|
||||
Lock, unlock, or check the lock status of an Agiloft record.
|
||||
@@ -254,7 +276,7 @@ List saved searches defined for an Agiloft table.
|
||||
| `searches` | array | List of saved searches for the table |
|
||||
| ↳ `name` | string | Saved search name |
|
||||
| ↳ `label` | string | Saved search display label |
|
||||
| ↳ `id` | string | Saved search database identifier |
|
||||
| ↳ `id` | number | Saved search database identifier |
|
||||
| ↳ `description` | string | Saved search description |
|
||||
|
||||
### `agiloft_search_records`
|
||||
|
||||
@@ -87,7 +87,7 @@ List persons (users) in PostHog. Returns user profiles with their properties and
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `personalApiKey` | string | Yes | PostHog Personal API Key \(for authenticated API access\) |
|
||||
| `apiKey` | string | Yes | PostHog Personal API Key \(for authenticated API access\) |
|
||||
| `region` | string | No | PostHog region: us \(default\) or eu |
|
||||
| `projectId` | string | Yes | PostHog Project ID \(e.g., "12345" or project UUID\) |
|
||||
| `limit` | number | No | Number of persons to return \(default: 100, max: 100\) |
|
||||
@@ -115,7 +115,7 @@ Get detailed information about a specific person in PostHog by their ID or UUID.
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `personalApiKey` | string | Yes | PostHog Personal API Key \(for authenticated API access\) |
|
||||
| `apiKey` | string | Yes | PostHog Personal API Key \(for authenticated API access\) |
|
||||
| `region` | string | No | PostHog region: us \(default\) or eu |
|
||||
| `projectId` | string | Yes | PostHog Project ID \(e.g., "12345" or project UUID\) |
|
||||
| `personId` | string | Yes | Person ID or UUID to retrieve \(e.g., "01234567-89ab-cdef-0123-456789abcdef"\) |
|
||||
@@ -139,7 +139,7 @@ Delete a person from PostHog. This will remove all associated events and data. U
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `personalApiKey` | string | Yes | PostHog Personal API Key \(for authenticated API access\) |
|
||||
| `apiKey` | string | Yes | PostHog Personal API Key \(for authenticated API access\) |
|
||||
| `region` | string | No | PostHog region: us \(default\) or eu |
|
||||
| `projectId` | string | Yes | PostHog Project ID \(e.g., "12345" or project UUID\) |
|
||||
| `personId` | string | Yes | Person ID or UUID to delete \(e.g., "01234567-89ab-cdef-0123-456789abcdef"\) |
|
||||
@@ -158,7 +158,7 @@ Execute a HogQL query in PostHog. HogQL is PostHog
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `personalApiKey` | string | Yes | PostHog Personal API Key \(for authenticated API access\) |
|
||||
| `apiKey` | string | Yes | PostHog Personal API Key \(for authenticated API access\) |
|
||||
| `region` | string | No | PostHog region: us \(default\) or eu |
|
||||
| `projectId` | string | Yes | PostHog Project ID \(e.g., "12345" or project UUID\) |
|
||||
| `query` | string | Yes | HogQL query to execute. Example: \{"kind": "HogQLQuery", "query": "SELECT event, count\(\) FROM events WHERE timestamp > now\(\) - INTERVAL 1 DAY GROUP BY event"\} |
|
||||
|
||||
@@ -321,7 +321,7 @@
|
||||
"name": "Agiloft",
|
||||
"description": "Manage records in Agiloft CLM",
|
||||
"longDescription": "Integrate with Agiloft contract lifecycle management to create, read, update, delete, and search records. Supports file attachments, SQL-based selection, saved searches, and record locking across any table in your knowledge base.",
|
||||
"bgColor": "#FFFFFF",
|
||||
"bgColor": "#001028",
|
||||
"iconName": "AgiloftIcon",
|
||||
"docsUrl": "https://docs.sim.ai/tools/agiloft",
|
||||
"operations": [
|
||||
@@ -372,9 +372,13 @@
|
||||
{
|
||||
"name": "Lock Record",
|
||||
"description": "Lock, unlock, or check the lock status of an Agiloft record."
|
||||
},
|
||||
{
|
||||
"name": "Get Choice Line ID",
|
||||
"description": "Resolve the internal numeric ID of a choice-list value, for use in EWSelect WHERE clauses against choice fields."
|
||||
}
|
||||
],
|
||||
"operationCount": 12,
|
||||
"operationCount": 13,
|
||||
"triggers": [],
|
||||
"triggerCount": 0,
|
||||
"authType": "none",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { toError } from '@sim/utils/errors'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { agiloftAttachContract } from '@/lib/api/contracts/tools/agiloft'
|
||||
import { getValidationErrorMessage, parseRequest } from '@/lib/api/server'
|
||||
@@ -90,7 +91,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
|
||||
const agiloftResponse = await fetch(url, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': userFile.type || 'application/octet-stream',
|
||||
'Content-Type': 'application/octet-stream',
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
body: new Uint8Array(fileBuffer),
|
||||
@@ -136,9 +137,6 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
|
||||
} catch (error) {
|
||||
logger.error(`[${requestId}] Error attaching file to Agiloft:`, error)
|
||||
|
||||
return NextResponse.json(
|
||||
{ success: false, error: error instanceof Error ? error.message : 'Internal server error' },
|
||||
{ status: 500 }
|
||||
)
|
||||
return NextResponse.json({ success: false, error: toError(error).message }, { status: 500 })
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { toError } from '@sim/utils/errors'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { agiloftRetrieveContract } from '@/lib/api/contracts/tools/agiloft'
|
||||
import { getValidationErrorMessage, parseRequest } from '@/lib/api/server'
|
||||
@@ -127,9 +128,6 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
|
||||
} catch (error) {
|
||||
logger.error(`[${requestId}] Error retrieving Agiloft attachment:`, error)
|
||||
|
||||
return NextResponse.json(
|
||||
{ success: false, error: error instanceof Error ? error.message : 'Internal server error' },
|
||||
{ status: 500 }
|
||||
)
|
||||
return NextResponse.json({ success: false, error: toError(error).message }, { status: 500 })
|
||||
}
|
||||
})
|
||||
|
||||
@@ -13,7 +13,7 @@ export const AgiloftBlock: BlockConfig = {
|
||||
category: 'tools',
|
||||
integrationType: IntegrationType.Productivity,
|
||||
tags: ['automation'],
|
||||
bgColor: '#FFFFFF',
|
||||
bgColor: '#001028',
|
||||
icon: AgiloftIcon,
|
||||
authMode: AuthMode.ApiKey,
|
||||
|
||||
@@ -35,6 +35,7 @@ export const AgiloftBlock: BlockConfig = {
|
||||
{ label: 'Remove Attachment', id: 'remove_attachment' },
|
||||
{ label: 'Attachment Info', id: 'attachment_info' },
|
||||
{ label: 'Lock Record', id: 'lock_record' },
|
||||
{ label: 'Get Choice Line ID', id: 'get_choice_line_id' },
|
||||
],
|
||||
value: () => 'search_records',
|
||||
},
|
||||
@@ -44,7 +45,6 @@ export const AgiloftBlock: BlockConfig = {
|
||||
type: 'short-input',
|
||||
placeholder: 'https://mycompany.agiloft.com',
|
||||
required: true,
|
||||
password: false,
|
||||
},
|
||||
{
|
||||
id: 'knowledgeBase',
|
||||
@@ -151,16 +151,36 @@ export const AgiloftBlock: BlockConfig = {
|
||||
id: 'fieldName',
|
||||
title: 'Field Name',
|
||||
type: 'short-input',
|
||||
placeholder: 'e.g., attached_docs',
|
||||
placeholder: 'e.g., attached_docs, priority',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['attach_file', 'retrieve_attachment', 'remove_attachment', 'attachment_info'],
|
||||
value: [
|
||||
'attach_file',
|
||||
'retrieve_attachment',
|
||||
'remove_attachment',
|
||||
'attachment_info',
|
||||
'get_choice_line_id',
|
||||
],
|
||||
},
|
||||
required: {
|
||||
field: 'operation',
|
||||
value: ['attach_file', 'retrieve_attachment', 'remove_attachment', 'attachment_info'],
|
||||
value: [
|
||||
'attach_file',
|
||||
'retrieve_attachment',
|
||||
'remove_attachment',
|
||||
'attachment_info',
|
||||
'get_choice_line_id',
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'value',
|
||||
title: 'Choice Value',
|
||||
type: 'short-input',
|
||||
placeholder: 'e.g., High, Active',
|
||||
condition: { field: 'operation', value: 'get_choice_line_id' },
|
||||
required: { field: 'operation', value: 'get_choice_line_id' },
|
||||
},
|
||||
{
|
||||
id: 'uploadFile',
|
||||
title: 'File',
|
||||
@@ -254,6 +274,7 @@ export const AgiloftBlock: BlockConfig = {
|
||||
'agiloft_attachment_info',
|
||||
'agiloft_create_record',
|
||||
'agiloft_delete_record',
|
||||
'agiloft_get_choice_line_id',
|
||||
'agiloft_lock_record',
|
||||
'agiloft_read_record',
|
||||
'agiloft_remove_attachment',
|
||||
@@ -288,7 +309,8 @@ export const AgiloftBlock: BlockConfig = {
|
||||
data: { type: 'string', description: 'Record data as JSON' },
|
||||
query: { type: 'string', description: 'Search query' },
|
||||
where: { type: 'string', description: 'SQL WHERE clause for select' },
|
||||
fieldName: { type: 'string', description: 'Attachment field name' },
|
||||
fieldName: { type: 'string', description: 'Attachment field name or choice field name' },
|
||||
value: { type: 'string', description: 'Choice value to resolve to its line ID' },
|
||||
attachFile: { type: 'file', description: 'File to attach' },
|
||||
fileName: { type: 'string', description: 'Name for the attached file' },
|
||||
position: { type: 'string', description: 'Attachment position index' },
|
||||
@@ -403,5 +425,10 @@ export const AgiloftBlock: BlockConfig = {
|
||||
description: 'Minutes until the lock expires',
|
||||
condition: { field: 'operation', value: 'lock_record' },
|
||||
},
|
||||
choiceLineId: {
|
||||
type: 'number',
|
||||
description: 'Internal numeric ID of the resolved choice value',
|
||||
condition: { field: 'operation', value: 'get_choice_line_id' },
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -50,8 +50,8 @@ export const agiloftAttachBodySchema = z.object({
|
||||
table: z.string().min(1, 'Table is required'),
|
||||
recordId: z.string().min(1, 'Record ID is required'),
|
||||
fieldName: z.string().min(1, 'Field name is required'),
|
||||
file: FileInputSchema.optional().nullable(),
|
||||
fileName: z.string().optional().nullable(),
|
||||
file: FileInputSchema.optional(),
|
||||
fileName: z.string().optional(),
|
||||
})
|
||||
|
||||
export const agiloftRetrieveContract = defineRouteContract({
|
||||
|
||||
@@ -91,9 +91,13 @@ export const agiloftAttachmentInfoTool: ToolConfig<
|
||||
for (let i = 0; i < result.length; i++) {
|
||||
const item = result[i] as Record<string, unknown>
|
||||
attachments.push({
|
||||
position: (item.position as number) ?? i,
|
||||
name: (item.name as string) ?? (item.filename as string) ?? '',
|
||||
size: (item.size as number) ?? 0,
|
||||
position: (item.filePosition as number) ?? (item.position as number) ?? i,
|
||||
name:
|
||||
(item.fileName as string) ??
|
||||
(item.name as string) ??
|
||||
(item.filename as string) ??
|
||||
'',
|
||||
size: (item.size as number) ?? (item.fileSize as number) ?? 0,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ export const agiloftCreateRecordTool: ToolConfig<AgiloftCreateRecordParams, Agil
|
||||
(base) => ({
|
||||
url: buildCreateRecordUrl(base, params),
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
|
||||
body,
|
||||
}),
|
||||
async (response) => {
|
||||
|
||||
@@ -60,6 +60,7 @@ export const agiloftDeleteRecordTool: ToolConfig<AgiloftDeleteRecordParams, Agil
|
||||
(base) => ({
|
||||
url: buildDeleteRecordUrl(base, params),
|
||||
method: 'DELETE',
|
||||
headers: { Accept: 'application/json' },
|
||||
}),
|
||||
async (response) => {
|
||||
if (!response.ok) {
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
import type {
|
||||
AgiloftGetChoiceLineIdParams,
|
||||
AgiloftGetChoiceLineIdResponse,
|
||||
} from '@/tools/agiloft/types'
|
||||
import { buildGetChoiceLineIdUrl, executeAgiloftRequest } from '@/tools/agiloft/utils'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
export const agiloftGetChoiceLineIdTool: ToolConfig<
|
||||
AgiloftGetChoiceLineIdParams,
|
||||
AgiloftGetChoiceLineIdResponse
|
||||
> = {
|
||||
id: 'agiloft_get_choice_line_id',
|
||||
name: 'Agiloft Get Choice Line ID',
|
||||
description:
|
||||
'Resolve the internal numeric ID of a choice-list value, for use in EWSelect WHERE clauses against choice fields.',
|
||||
version: '1.0.0',
|
||||
|
||||
params: {
|
||||
instanceUrl: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Agiloft instance URL (e.g., https://mycompany.agiloft.com)',
|
||||
},
|
||||
knowledgeBase: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Knowledge base name',
|
||||
},
|
||||
login: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Agiloft username',
|
||||
},
|
||||
password: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'Agiloft password',
|
||||
},
|
||||
table: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Table name (e.g., "case", "contracts")',
|
||||
},
|
||||
fieldName: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Choice field name (e.g., "priority", "status")',
|
||||
},
|
||||
value: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'Choice display value to resolve (e.g., "High", "Active")',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: 'https://placeholder.agiloft.com',
|
||||
method: 'GET',
|
||||
headers: () => ({}),
|
||||
},
|
||||
|
||||
directExecution: async (params) => {
|
||||
return executeAgiloftRequest<AgiloftGetChoiceLineIdResponse>(
|
||||
params,
|
||||
(base) => ({
|
||||
url: buildGetChoiceLineIdUrl(base, params),
|
||||
method: 'GET',
|
||||
headers: { Accept: 'application/json' },
|
||||
}),
|
||||
async (response) => {
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
return {
|
||||
success: false,
|
||||
output: { choiceLineId: null },
|
||||
error: `Agiloft error: ${response.status} - ${errorText}`,
|
||||
}
|
||||
}
|
||||
|
||||
const data = (await response.json()) as Record<string, unknown>
|
||||
const result = data.result ?? data
|
||||
let choiceLineId: number | null = null
|
||||
|
||||
if (typeof result === 'number') {
|
||||
choiceLineId = result
|
||||
} else if (typeof result === 'string') {
|
||||
const parsed = Number(result)
|
||||
choiceLineId = Number.isFinite(parsed) ? parsed : null
|
||||
} else if (typeof result === 'object' && result !== null) {
|
||||
const obj = result as Record<string, unknown>
|
||||
const idVal = obj.id ?? obj.choiceLineId ?? obj.lineId
|
||||
if (typeof idVal === 'number') {
|
||||
choiceLineId = idVal
|
||||
} else if (typeof idVal === 'string') {
|
||||
const parsed = Number(idVal)
|
||||
choiceLineId = Number.isFinite(parsed) ? parsed : null
|
||||
}
|
||||
}
|
||||
|
||||
if (choiceLineId === null) {
|
||||
return {
|
||||
success: false,
|
||||
output: { choiceLineId: null },
|
||||
error: `No choice line ID found for value "${params.value}" in field "${params.fieldName}"`,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: data.success !== false,
|
||||
output: { choiceLineId },
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
outputs: {
|
||||
choiceLineId: {
|
||||
type: 'number',
|
||||
description: 'Internal numeric line ID of the choice value',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -2,6 +2,7 @@ export { agiloftAttachFileTool } from '@/tools/agiloft/attach_file'
|
||||
export { agiloftAttachmentInfoTool } from '@/tools/agiloft/attachment_info'
|
||||
export { agiloftCreateRecordTool } from '@/tools/agiloft/create_record'
|
||||
export { agiloftDeleteRecordTool } from '@/tools/agiloft/delete_record'
|
||||
export { agiloftGetChoiceLineIdTool } from '@/tools/agiloft/get_choice_line_id'
|
||||
export { agiloftLockRecordTool } from '@/tools/agiloft/lock_record'
|
||||
export { agiloftReadRecordTool } from '@/tools/agiloft/read_record'
|
||||
export { agiloftRemoveAttachmentTool } from '@/tools/agiloft/remove_attachment'
|
||||
|
||||
@@ -65,6 +65,7 @@ export const agiloftReadRecordTool: ToolConfig<AgiloftReadRecordParams, AgiloftR
|
||||
(base) => ({
|
||||
url: buildReadRecordUrl(base, params),
|
||||
method: 'GET',
|
||||
headers: { Accept: 'application/json' },
|
||||
}),
|
||||
async (response) => {
|
||||
if (!response.ok) {
|
||||
|
||||
@@ -67,7 +67,7 @@ export const agiloftRemoveAttachmentTool: ToolConfig<
|
||||
|
||||
request: {
|
||||
url: 'https://placeholder.agiloft.com',
|
||||
method: 'GET',
|
||||
method: 'DELETE',
|
||||
headers: () => ({}),
|
||||
},
|
||||
|
||||
@@ -76,7 +76,7 @@ export const agiloftRemoveAttachmentTool: ToolConfig<
|
||||
params,
|
||||
(base) => ({
|
||||
url: buildRemoveAttachmentUrl(base, params),
|
||||
method: 'GET',
|
||||
method: 'DELETE',
|
||||
}),
|
||||
async (response) => {
|
||||
const text = await response.text()
|
||||
|
||||
@@ -107,8 +107,12 @@ export const agiloftSavedSearchTool: ToolConfig<
|
||||
properties: {
|
||||
name: { type: 'string', description: 'Saved search name' },
|
||||
label: { type: 'string', description: 'Saved search display label' },
|
||||
id: { type: 'string', description: 'Saved search database identifier' },
|
||||
description: { type: 'string', description: 'Saved search description' },
|
||||
id: { type: 'number', description: 'Saved search database identifier' },
|
||||
description: {
|
||||
type: 'string',
|
||||
description: 'Saved search description',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -94,45 +94,44 @@ export const agiloftSearchRecordsTool: ToolConfig<
|
||||
|
||||
const data = (await response.json()) as Record<string, unknown>
|
||||
const records: Record<string, unknown>[] = []
|
||||
const result = (data.result ?? data) as Record<string, unknown>
|
||||
|
||||
if (data.result && Array.isArray(data.result)) {
|
||||
for (const item of data.result as Record<string, unknown>[]) {
|
||||
if (Array.isArray(result)) {
|
||||
for (const item of result as Record<string, unknown>[]) {
|
||||
records.push(item)
|
||||
}
|
||||
} else if (Array.isArray(data)) {
|
||||
for (const item of data as Record<string, unknown>[]) {
|
||||
records.push(item)
|
||||
}
|
||||
} else if (data.results && Array.isArray(data.results)) {
|
||||
for (const item of data.results as Record<string, unknown>[]) {
|
||||
records.push(item)
|
||||
}
|
||||
} else if (data.records && Array.isArray(data.records)) {
|
||||
for (const item of data.records as Record<string, unknown>[]) {
|
||||
records.push(item)
|
||||
}
|
||||
} else if (typeof data.EWREST_length === 'number') {
|
||||
const count = data.EWREST_length as number
|
||||
for (let i = 0; i < count; i++) {
|
||||
const record: Record<string, unknown> = {}
|
||||
for (const key of Object.keys(data)) {
|
||||
const match = key.match(/^EWREST_(.+)_(\d+)$/)
|
||||
if (match && Number(match[2]) === i) {
|
||||
record[match[1]] = data[key]
|
||||
} else {
|
||||
const lengthRaw = result.EWREST_length ?? data.EWREST_length
|
||||
const count = typeof lengthRaw === 'string' ? Number(lengthRaw) : (lengthRaw as number)
|
||||
if (typeof count === 'number' && Number.isFinite(count)) {
|
||||
const source = (result.EWREST_length != null ? result : data) as Record<string, unknown>
|
||||
for (let i = 0; i < count; i++) {
|
||||
const record: Record<string, unknown> = {}
|
||||
for (const key of Object.keys(source)) {
|
||||
const match = key.match(/^EWREST_(.+)_(\d+)$/)
|
||||
if (match && Number(match[2]) === i) {
|
||||
record[match[1]] = source[key]
|
||||
}
|
||||
}
|
||||
if (Object.keys(record).length > 0) {
|
||||
records.push(record)
|
||||
}
|
||||
}
|
||||
if (Object.keys(record).length > 0) {
|
||||
records.push(record)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const totalCount =
|
||||
(data.totalCount as number) ??
|
||||
(data.total as number) ??
|
||||
(data.count as number) ??
|
||||
(data.EWREST_length as number) ??
|
||||
const totalCountRaw =
|
||||
result.totalCount ??
|
||||
result.total ??
|
||||
result.count ??
|
||||
result.EWREST_length ??
|
||||
data.totalCount ??
|
||||
data.total ??
|
||||
data.count ??
|
||||
data.EWREST_length ??
|
||||
records.length
|
||||
const totalCount =
|
||||
typeof totalCountRaw === 'string' ? Number(totalCountRaw) : (totalCountRaw as number)
|
||||
const page = params.page ? Number(params.page) : 0
|
||||
const limit = params.limit ? Number(params.limit) : 25
|
||||
|
||||
|
||||
@@ -95,14 +95,22 @@ export const agiloftSelectRecordsTool: ToolConfig<
|
||||
}
|
||||
}
|
||||
|
||||
const totalCount =
|
||||
data.EWREST_id_length ?? data.totalCount ?? data.total ?? data.count ?? recordIds.length
|
||||
const totalCountRaw =
|
||||
result.EWREST_id_length ??
|
||||
result.totalCount ??
|
||||
result.total ??
|
||||
result.count ??
|
||||
data.EWREST_id_length ??
|
||||
data.totalCount ??
|
||||
data.total ??
|
||||
data.count ??
|
||||
recordIds.length
|
||||
|
||||
return {
|
||||
success: data.success !== false,
|
||||
output: {
|
||||
recordIds,
|
||||
totalCount: Number(totalCount),
|
||||
totalCount: Number(totalCountRaw),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,3 +160,14 @@ export interface AgiloftRemoveAttachmentResponse extends ToolResponse {
|
||||
remainingAttachments: number
|
||||
}
|
||||
}
|
||||
|
||||
export interface AgiloftGetChoiceLineIdParams extends AgiloftBaseParams {
|
||||
fieldName: string
|
||||
value: string
|
||||
}
|
||||
|
||||
export interface AgiloftGetChoiceLineIdResponse extends ToolResponse {
|
||||
output: {
|
||||
choiceLineId: number | null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ export const agiloftUpdateRecordTool: ToolConfig<AgiloftUpdateRecordParams, Agil
|
||||
(base) => ({
|
||||
url: buildUpdateRecordUrl(base, params),
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
|
||||
body,
|
||||
}),
|
||||
async (response) => {
|
||||
|
||||
@@ -4,6 +4,7 @@ import type {
|
||||
AgiloftAttachmentInfoParams,
|
||||
AgiloftBaseParams,
|
||||
AgiloftDeleteRecordParams,
|
||||
AgiloftGetChoiceLineIdParams,
|
||||
AgiloftLockRecordParams,
|
||||
AgiloftReadRecordParams,
|
||||
AgiloftRemoveAttachmentParams,
|
||||
@@ -243,6 +244,15 @@ export function buildAttachFileUrl(
|
||||
return `${base}/ewws/EWAttach?$KB=${kb}&$table=${table}&$lang=en&id=${recordId}&field=${fieldName}&fileName=${encodedFileName}`
|
||||
}
|
||||
|
||||
export function buildGetChoiceLineIdUrl(
|
||||
base: string,
|
||||
params: AgiloftGetChoiceLineIdParams
|
||||
): string {
|
||||
const field = encodeURIComponent(params.fieldName.trim())
|
||||
const value = encodeURIComponent(params.value.trim())
|
||||
return `${base}/ewws/EWGetChoiceLineId/.json?${buildEwBaseQuery(params)}&field=${field}&value=${value}`
|
||||
}
|
||||
|
||||
export function getLockHttpMethod(lockAction: string): HttpMethod {
|
||||
switch (lockAction) {
|
||||
case 'lock':
|
||||
|
||||
@@ -60,6 +60,7 @@ import {
|
||||
agiloftAttachmentInfoTool,
|
||||
agiloftCreateRecordTool,
|
||||
agiloftDeleteRecordTool,
|
||||
agiloftGetChoiceLineIdTool,
|
||||
agiloftLockRecordTool,
|
||||
agiloftReadRecordTool,
|
||||
agiloftRemoveAttachmentTool,
|
||||
@@ -3003,6 +3004,7 @@ export const tools: Record<string, ToolConfig> = {
|
||||
agiloft_attachment_info: agiloftAttachmentInfoTool,
|
||||
agiloft_create_record: agiloftCreateRecordTool,
|
||||
agiloft_delete_record: agiloftDeleteRecordTool,
|
||||
agiloft_get_choice_line_id: agiloftGetChoiceLineIdTool,
|
||||
agiloft_lock_record: agiloftLockRecordTool,
|
||||
agiloft_read_record: agiloftReadRecordTool,
|
||||
agiloft_remove_attachment: agiloftRemoveAttachmentTool,
|
||||
|
||||
Reference in New Issue
Block a user