mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
fix(microsoft-planner): align with live Graph API docs, add plan CRUD + categories (#5486)
* fix(microsoft-planner): align with live Graph API docs, add plan CRUD + categories
- fix update_task never returning updated task (missing Prefer: return=representation)
- fix wrong @odata.type on task assignments (missing leading #)
- fix update_plan/update_plan_details/update_bucket/update_task_details crashing
on 204 No Content responses to PATCH (Graph sometimes ignores Prefer header)
- add .trim() on path IDs across tools invoked outside the block
- add create_plan, update_plan, delete_plan, get_plan_details, update_plan_details
tools (If-Match/etag handled correctly on update/delete)
- add appliedCategories support on create_task/update_task
- all new tools verified against Graph API Permissions tables to require only
scopes we already request (Group.ReadWrite.All, Group.Read.All, Tasks.ReadWrite)
- regenerate integration docs
* fix(microsoft-planner): use Graph-specific error extractor consistently
7 tools were pinned to the generic 'nested-error-object' extractor
instead of MICROSOFT_GRAPH_ERRORS, losing Graph's inner-error detail
(e.g. ETag mismatch specifics) that sibling Microsoft integrations
(Excel, OneDrive, SharePoint) already surface correctly.
* fix(microsoft-planner): address Cursor/Greptile round-1 review findings
- fix empty priority/percentComplete string coercing to 0 (Number('')) at
the block layer, which Planner treats as urgent/0% instead of leaving unset
- support removing applied categories on update via a "-category3" prefix,
since Graph only clears a label when its key is explicitly set to false
- add missing MICROSOFT_GRAPH_ERRORS extractor to delete_plan/get_plan_details
* fix(microsoft-planner): don't return a stale etag on 204 update_task response
Graph's If-Match update changes the resource's etag even when it returns
204 No Content instead of the updated representation. Returning the
request's (now-stale) etag as if it were current would let a chained
update silently send a wrong If-Match and fail with 412. Return an empty
etag instead so update_task's own etag-required guard forces a re-fetch.
This commit is contained in:
@@ -64,10 +64,13 @@ Create a new task in Microsoft Planner
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `planId` | string | Yes | The ID of the plan where the task will be created \(e.g., "xqQg5FS2LkCe54tAMV_v2ZgADW2J"\) |
|
||||
| `title` | string | Yes | The title of the task \(e.g., "Review quarterly report"\) |
|
||||
| `description` | string | No | The description of the task |
|
||||
| `dueDateTime` | string | No | The due date and time for the task in ISO 8601 format \(e.g., "2025-03-15T17:00:00Z"\) |
|
||||
| `startDateTime` | string | No | The start date and time for the task in ISO 8601 format \(e.g., "2025-03-10T09:00:00Z"\) |
|
||||
| `priority` | number | No | The priority of the task \(0-10, where 0 is urgent and 10 is low\) |
|
||||
| `percentComplete` | number | No | The percentage of task completion \(0-100\) |
|
||||
| `assigneeUserId` | string | No | The user ID to assign the task to \(e.g., "e82f74c3-4d8a-4b5c-9f1e-2a6b8c9d0e3f"\) |
|
||||
| `bucketId` | string | No | The bucket ID to place the task in \(e.g., "hsOf2dhOJkC6Fey9VjDg1JgAC9Rq"\) |
|
||||
| `appliedCategories` | string | No | Comma-separated category labels to apply to the task, e.g. "category1,category3" \(up to category1-category25, plan-defined color labels\) |
|
||||
|
||||
#### Output
|
||||
|
||||
@@ -97,6 +100,7 @@ Update a task in Microsoft Planner
|
||||
| `percentComplete` | number | No | The percentage of task completion \(0-100\) |
|
||||
| `priority` | number | No | The priority of the task \(0-10\) |
|
||||
| `assigneeUserId` | string | No | The user ID to assign the task to \(e.g., "e82f74c3-4d8a-4b5c-9f1e-2a6b8c9d0e3f"\) |
|
||||
| `appliedCategories` | string | No | Comma-separated category labels to apply to the task, e.g. "category1,category3" \(up to category1-category25, plan-defined color labels\) |
|
||||
|
||||
#### Output
|
||||
|
||||
@@ -170,6 +174,109 @@ Get details of a specific Microsoft Planner plan
|
||||
| ↳ `planId` | string | Plan ID |
|
||||
| ↳ `planUrl` | string | Microsoft Graph API URL for the plan |
|
||||
|
||||
### `microsoft_planner_create_plan`
|
||||
|
||||
Create a new Microsoft Planner plan owned by a Microsoft 365 group
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `groupId` | string | Yes | The ID of the Microsoft 365 group that will own the plan \(e.g., "ebf3b108-5234-4e22-b93d-656d7dae5874"\). The current user must be a member of this group. |
|
||||
| `title` | string | Yes | The title of the plan |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Whether the plan was created successfully |
|
||||
| `plan` | object | The created plan object with all properties |
|
||||
| `metadata` | object | Metadata including planId and groupId |
|
||||
| ↳ `planId` | string | Created plan ID |
|
||||
| ↳ `groupId` | string | Owning Microsoft 365 group ID |
|
||||
|
||||
### `microsoft_planner_update_plan`
|
||||
|
||||
Rename a Microsoft Planner plan
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `planId` | string | Yes | The ID of the plan to update \(e.g., "xqQg5FS2LkCe54tAMV_v2ZgADW2J"\) |
|
||||
| `etag` | string | Yes | The ETag value from the plan to update \(If-Match header\) |
|
||||
| `title` | string | Yes | The new title of the plan |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Whether the plan was updated successfully |
|
||||
| `plan` | object | The updated plan object with all properties |
|
||||
| `metadata` | object | Metadata including planId |
|
||||
| ↳ `planId` | string | Updated plan ID |
|
||||
|
||||
### `microsoft_planner_get_plan_details`
|
||||
|
||||
Get detailed information about a plan including category descriptions and sharing
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `planId` | string | Yes | The ID of the plan \(e.g., "xqQg5FS2LkCe54tAMV_v2ZgADW2J"\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Whether the plan details were retrieved successfully |
|
||||
| `planDetails` | object | The plan details including categoryDescriptions and sharedWith |
|
||||
| `etag` | string | The ETag value for this plan details resource |
|
||||
| `metadata` | object | Metadata including planId |
|
||||
| ↳ `planId` | string | Plan ID |
|
||||
|
||||
### `microsoft_planner_update_plan_details`
|
||||
|
||||
Update a plan's category (color label) descriptions and shared-with user list in Microsoft Planner
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `planId` | string | Yes | The ID of the plan \(e.g., "xqQg5FS2LkCe54tAMV_v2ZgADW2J"\) |
|
||||
| `etag` | string | Yes | The ETag value from the plan details to update \(If-Match header\) |
|
||||
| `categoryDescriptions` | object | No | Category label names as a JSON object, e.g. \{"category1": "Blocked", "category2": "At Risk"\}. Set a value to null to clear a label. |
|
||||
| `sharedWith` | object | No | User IDs to share the plan with as a JSON object, e.g. \{"<user-id>": true\}. Set a value to false to unshare. |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Whether the plan details were updated successfully |
|
||||
| `planDetails` | object | The updated plan details object with categoryDescriptions and sharedWith |
|
||||
| `metadata` | object | Metadata including planId |
|
||||
| ↳ `planId` | string | Plan ID |
|
||||
|
||||
### `microsoft_planner_delete_plan`
|
||||
|
||||
Delete a Microsoft Planner plan
|
||||
|
||||
#### Input
|
||||
|
||||
| Parameter | Type | Required | Description |
|
||||
| --------- | ---- | -------- | ----------- |
|
||||
| `planId` | string | Yes | The ID of the plan to delete \(e.g., "xqQg5FS2LkCe54tAMV_v2ZgADW2J"\) |
|
||||
| `etag` | string | Yes | The ETag value from the plan to delete \(If-Match header\) |
|
||||
|
||||
#### Output
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ---- | ----------- |
|
||||
| `success` | boolean | Whether the plan was deleted successfully |
|
||||
| `deleted` | boolean | Confirmation of deletion |
|
||||
| `metadata` | object | Additional metadata |
|
||||
|
||||
### `microsoft_planner_list_buckets`
|
||||
|
||||
List all buckets in a Microsoft Planner plan
|
||||
|
||||
@@ -23,6 +23,9 @@ interface MicrosoftPlannerBlockParams {
|
||||
checklist?: string
|
||||
references?: string
|
||||
previewType?: string
|
||||
appliedCategories?: string
|
||||
categoryDescriptions?: string
|
||||
sharedWith?: string
|
||||
[key: string]: string | number | boolean | undefined
|
||||
}
|
||||
|
||||
@@ -50,6 +53,11 @@ export const MicrosoftPlannerBlock: BlockConfig<MicrosoftPlannerResponse> = {
|
||||
{ label: 'Delete Task', id: 'delete_task' },
|
||||
{ label: 'List Plans', id: 'list_plans' },
|
||||
{ label: 'Read Plan', id: 'read_plan' },
|
||||
{ label: 'Create Plan', id: 'create_plan' },
|
||||
{ label: 'Update Plan', id: 'update_plan' },
|
||||
{ label: 'Get Plan Details', id: 'get_plan_details' },
|
||||
{ label: 'Update Plan Details', id: 'update_plan_details' },
|
||||
{ label: 'Delete Plan', id: 'delete_plan' },
|
||||
{ label: 'List Buckets', id: 'list_buckets' },
|
||||
{ label: 'Read Bucket', id: 'read_bucket' },
|
||||
{ label: 'Create Bucket', id: 'create_bucket' },
|
||||
@@ -91,11 +99,30 @@ export const MicrosoftPlannerBlock: BlockConfig<MicrosoftPlannerResponse> = {
|
||||
mode: 'basic',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['create_task', 'read_task', 'read_plan', 'list_buckets', 'create_bucket'],
|
||||
value: [
|
||||
'create_task',
|
||||
'read_task',
|
||||
'read_plan',
|
||||
'list_buckets',
|
||||
'create_bucket',
|
||||
'update_plan',
|
||||
'delete_plan',
|
||||
'get_plan_details',
|
||||
'update_plan_details',
|
||||
],
|
||||
},
|
||||
required: {
|
||||
field: 'operation',
|
||||
value: ['read_plan', 'list_buckets', 'create_bucket', 'create_task'],
|
||||
value: [
|
||||
'read_plan',
|
||||
'list_buckets',
|
||||
'create_bucket',
|
||||
'create_task',
|
||||
'update_plan',
|
||||
'delete_plan',
|
||||
'get_plan_details',
|
||||
'update_plan_details',
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -109,11 +136,30 @@ export const MicrosoftPlannerBlock: BlockConfig<MicrosoftPlannerResponse> = {
|
||||
mode: 'advanced',
|
||||
condition: {
|
||||
field: 'operation',
|
||||
value: ['create_task', 'read_task', 'read_plan', 'list_buckets', 'create_bucket'],
|
||||
value: [
|
||||
'create_task',
|
||||
'read_task',
|
||||
'read_plan',
|
||||
'list_buckets',
|
||||
'create_bucket',
|
||||
'update_plan',
|
||||
'delete_plan',
|
||||
'get_plan_details',
|
||||
'update_plan_details',
|
||||
],
|
||||
},
|
||||
required: {
|
||||
field: 'operation',
|
||||
value: ['read_plan', 'list_buckets', 'create_bucket', 'create_task'],
|
||||
value: [
|
||||
'read_plan',
|
||||
'list_buckets',
|
||||
'create_bucket',
|
||||
'create_task',
|
||||
'update_plan',
|
||||
'delete_plan',
|
||||
'get_plan_details',
|
||||
'update_plan_details',
|
||||
],
|
||||
},
|
||||
dependsOn: ['credential'],
|
||||
},
|
||||
@@ -184,6 +230,9 @@ export const MicrosoftPlannerBlock: BlockConfig<MicrosoftPlannerResponse> = {
|
||||
'update_bucket',
|
||||
'delete_bucket',
|
||||
'update_task_details',
|
||||
'update_plan',
|
||||
'update_plan_details',
|
||||
'delete_plan',
|
||||
],
|
||||
},
|
||||
dependsOn: ['credential'],
|
||||
@@ -209,13 +258,14 @@ export const MicrosoftPlannerBlock: BlockConfig<MicrosoftPlannerResponse> = {
|
||||
required: { field: 'operation', value: 'create_bucket' },
|
||||
},
|
||||
|
||||
// Description for task details
|
||||
// Description for task details (Microsoft Planner tasks store description on the
|
||||
// task details resource, not the task itself, so this only applies to update_task_details)
|
||||
{
|
||||
id: 'description',
|
||||
title: 'Description',
|
||||
type: 'long-input',
|
||||
placeholder: 'Enter task description',
|
||||
condition: { field: 'operation', value: ['create_task', 'update_task_details'] },
|
||||
condition: { field: 'operation', value: ['update_task_details'] },
|
||||
},
|
||||
|
||||
// Due Date
|
||||
@@ -247,7 +297,8 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
|
||||
title: 'Start Date',
|
||||
type: 'short-input',
|
||||
placeholder: 'Enter start date in ISO 8601 format (optional)',
|
||||
condition: { field: 'operation', value: ['update_task'] },
|
||||
mode: 'advanced',
|
||||
condition: { field: 'operation', value: ['create_task', 'update_task'] },
|
||||
wandConfig: {
|
||||
enabled: true,
|
||||
prompt: `Generate an ISO 8601 timestamp based on the user's description for Microsoft Planner task start date.
|
||||
@@ -288,7 +339,8 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
|
||||
title: 'Priority',
|
||||
type: 'short-input',
|
||||
placeholder: 'Enter priority (0-10, optional)',
|
||||
condition: { field: 'operation', value: ['update_task'] },
|
||||
mode: 'advanced',
|
||||
condition: { field: 'operation', value: ['create_task', 'update_task'] },
|
||||
},
|
||||
|
||||
// Percent Complete
|
||||
@@ -297,7 +349,8 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
|
||||
title: 'Percent Complete',
|
||||
type: 'short-input',
|
||||
placeholder: 'Enter completion percentage (0-100, optional)',
|
||||
condition: { field: 'operation', value: ['update_task'] },
|
||||
mode: 'advanced',
|
||||
condition: { field: 'operation', value: ['create_task', 'update_task'] },
|
||||
},
|
||||
|
||||
// Checklist for task details
|
||||
@@ -305,7 +358,9 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
|
||||
id: 'checklist',
|
||||
title: 'Checklist (JSON)',
|
||||
type: 'long-input',
|
||||
placeholder: 'Enter checklist as JSON object (optional)',
|
||||
placeholder:
|
||||
'e.g. {"<generated-guid>": {"@odata.type": "microsoft.graph.plannerChecklistItem", "title": "Step 1", "isChecked": false}}',
|
||||
mode: 'advanced',
|
||||
condition: { field: 'operation', value: ['update_task_details'] },
|
||||
},
|
||||
|
||||
@@ -314,7 +369,9 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
|
||||
id: 'references',
|
||||
title: 'References (JSON)',
|
||||
type: 'long-input',
|
||||
placeholder: 'Enter references as JSON object (optional)',
|
||||
placeholder:
|
||||
'e.g. {"https%3A//example%2Ecom": {"@odata.type": "microsoft.graph.plannerExternalReference", "alias": "Docs", "type": "Other"}}',
|
||||
mode: 'advanced',
|
||||
condition: { field: 'operation', value: ['update_task_details'] },
|
||||
},
|
||||
|
||||
@@ -324,8 +381,74 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
|
||||
title: 'Preview Type',
|
||||
type: 'short-input',
|
||||
placeholder: 'Enter preview type (automatic, noPreview, checklist, description, reference)',
|
||||
mode: 'advanced',
|
||||
condition: { field: 'operation', value: ['update_task_details'] },
|
||||
},
|
||||
|
||||
// Group ID for create plan
|
||||
{
|
||||
id: 'groupId',
|
||||
title: 'Microsoft 365 Group ID',
|
||||
type: 'short-input',
|
||||
placeholder: 'Enter the Microsoft 365 group ID that will own the plan',
|
||||
required: { field: 'operation', value: 'create_plan' },
|
||||
condition: { field: 'operation', value: ['create_plan'] },
|
||||
dependsOn: ['credential'],
|
||||
},
|
||||
|
||||
// Plan title for create/update plan
|
||||
{
|
||||
id: 'planTitle',
|
||||
title: 'Plan Title',
|
||||
type: 'short-input',
|
||||
placeholder: 'Enter the plan title',
|
||||
required: { field: 'operation', value: ['create_plan', 'update_plan'] },
|
||||
condition: { field: 'operation', value: ['create_plan', 'update_plan'] },
|
||||
dependsOn: ['credential'],
|
||||
},
|
||||
|
||||
// Applied categories for task create/update (color labels)
|
||||
{
|
||||
id: 'appliedCategories',
|
||||
title: 'Categories',
|
||||
type: 'short-input',
|
||||
placeholder: 'e.g. category1,category3,-category5',
|
||||
mode: 'advanced',
|
||||
condition: { field: 'operation', value: ['create_task', 'update_task'] },
|
||||
wandConfig: {
|
||||
enabled: true,
|
||||
prompt: `Generate a comma-separated list of Microsoft Planner category keys based on the user's description.
|
||||
Valid keys are category1 through category25. Prefix a key with "-" to remove/clear that label (only meaningful on update).
|
||||
Examples:
|
||||
- "flag it blocked" -> category1
|
||||
- "mark as urgent and needs review" -> category1,category2
|
||||
- "remove the blocked label" -> -category1
|
||||
|
||||
Return ONLY the comma-separated category keys - no explanations, no extra text.`,
|
||||
placeholder:
|
||||
'Describe which category labels to apply or remove (e.g., "mark as blocked", "remove urgent label")...',
|
||||
},
|
||||
},
|
||||
|
||||
// Category descriptions for plan details (color label names)
|
||||
{
|
||||
id: 'categoryDescriptions',
|
||||
title: 'Category Descriptions (JSON)',
|
||||
type: 'long-input',
|
||||
placeholder: 'e.g. {"category1": "Blocked", "category2": "At Risk"}',
|
||||
mode: 'advanced',
|
||||
condition: { field: 'operation', value: ['update_plan_details'] },
|
||||
},
|
||||
|
||||
// Shared with for plan details
|
||||
{
|
||||
id: 'sharedWith',
|
||||
title: 'Shared With (JSON)',
|
||||
type: 'long-input',
|
||||
placeholder: 'e.g. {"<user-id>": true}',
|
||||
mode: 'advanced',
|
||||
condition: { field: 'operation', value: ['update_plan_details'] },
|
||||
},
|
||||
],
|
||||
tools: {
|
||||
access: [
|
||||
@@ -335,6 +458,11 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
|
||||
'microsoft_planner_delete_task',
|
||||
'microsoft_planner_list_plans',
|
||||
'microsoft_planner_read_plan',
|
||||
'microsoft_planner_create_plan',
|
||||
'microsoft_planner_update_plan',
|
||||
'microsoft_planner_get_plan_details',
|
||||
'microsoft_planner_update_plan_details',
|
||||
'microsoft_planner_delete_plan',
|
||||
'microsoft_planner_list_buckets',
|
||||
'microsoft_planner_read_bucket',
|
||||
'microsoft_planner_create_bucket',
|
||||
@@ -358,6 +486,16 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
|
||||
return 'microsoft_planner_list_plans'
|
||||
case 'read_plan':
|
||||
return 'microsoft_planner_read_plan'
|
||||
case 'create_plan':
|
||||
return 'microsoft_planner_create_plan'
|
||||
case 'update_plan':
|
||||
return 'microsoft_planner_update_plan'
|
||||
case 'get_plan_details':
|
||||
return 'microsoft_planner_get_plan_details'
|
||||
case 'update_plan_details':
|
||||
return 'microsoft_planner_update_plan_details'
|
||||
case 'delete_plan':
|
||||
return 'microsoft_planner_delete_plan'
|
||||
case 'list_buckets':
|
||||
return 'microsoft_planner_list_buckets'
|
||||
case 'read_bucket':
|
||||
@@ -388,6 +526,7 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
|
||||
bucketIdForRead,
|
||||
title,
|
||||
name,
|
||||
planTitle,
|
||||
description,
|
||||
dueDateTime,
|
||||
startDateTime,
|
||||
@@ -398,6 +537,9 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
|
||||
checklist,
|
||||
references,
|
||||
previewType,
|
||||
appliedCategories,
|
||||
categoryDescriptions,
|
||||
sharedWith,
|
||||
...rest
|
||||
} = params
|
||||
|
||||
@@ -424,6 +566,58 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
|
||||
}
|
||||
}
|
||||
|
||||
// Create Plan
|
||||
if (operation === 'create_plan') {
|
||||
return {
|
||||
...baseParams,
|
||||
groupId: groupId?.trim(),
|
||||
title: planTitle?.trim(),
|
||||
}
|
||||
}
|
||||
|
||||
// Update Plan
|
||||
if (operation === 'update_plan') {
|
||||
return {
|
||||
...baseParams,
|
||||
planId: planId?.trim(),
|
||||
etag: etag?.trim(),
|
||||
title: planTitle?.trim(),
|
||||
}
|
||||
}
|
||||
|
||||
// Get Plan Details
|
||||
if (operation === 'get_plan_details') {
|
||||
return {
|
||||
...baseParams,
|
||||
planId: planId?.trim(),
|
||||
}
|
||||
}
|
||||
|
||||
// Update Plan Details
|
||||
if (operation === 'update_plan_details') {
|
||||
const updatePlanDetailsParams: MicrosoftPlannerBlockParams = {
|
||||
...baseParams,
|
||||
planId: planId?.trim(),
|
||||
etag: etag?.trim(),
|
||||
}
|
||||
if (categoryDescriptions?.trim()) {
|
||||
updatePlanDetailsParams.categoryDescriptions = categoryDescriptions.trim()
|
||||
}
|
||||
if (sharedWith?.trim()) {
|
||||
updatePlanDetailsParams.sharedWith = sharedWith.trim()
|
||||
}
|
||||
return updatePlanDetailsParams
|
||||
}
|
||||
|
||||
// Delete Plan
|
||||
if (operation === 'delete_plan') {
|
||||
return {
|
||||
...baseParams,
|
||||
planId: planId?.trim(),
|
||||
etag: etag?.trim(),
|
||||
}
|
||||
}
|
||||
|
||||
// List Buckets
|
||||
if (operation === 'list_buckets') {
|
||||
return {
|
||||
@@ -492,18 +686,27 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
|
||||
title: title?.trim(),
|
||||
}
|
||||
|
||||
if (description?.trim()) {
|
||||
createParams.description = description.trim()
|
||||
}
|
||||
if (dueDateTime?.trim()) {
|
||||
createParams.dueDateTime = dueDateTime.trim()
|
||||
}
|
||||
if (startDateTime?.trim()) {
|
||||
createParams.startDateTime = startDateTime.trim()
|
||||
}
|
||||
if (priority !== undefined && String(priority).trim() !== '') {
|
||||
createParams.priority = Number(priority)
|
||||
}
|
||||
if (percentComplete !== undefined && String(percentComplete).trim() !== '') {
|
||||
createParams.percentComplete = Number(percentComplete)
|
||||
}
|
||||
if (assigneeUserId?.trim()) {
|
||||
createParams.assigneeUserId = assigneeUserId.trim()
|
||||
}
|
||||
if (effectiveBucketId) {
|
||||
createParams.bucketId = effectiveBucketId
|
||||
}
|
||||
if (appliedCategories?.trim()) {
|
||||
createParams.appliedCategories = appliedCategories.trim()
|
||||
}
|
||||
|
||||
return createParams
|
||||
}
|
||||
@@ -531,12 +734,15 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
|
||||
if (assigneeUserId?.trim()) {
|
||||
updateParams.assigneeUserId = assigneeUserId.trim()
|
||||
}
|
||||
if (priority !== undefined) {
|
||||
if (priority !== undefined && String(priority).trim() !== '') {
|
||||
updateParams.priority = Number(priority)
|
||||
}
|
||||
if (percentComplete !== undefined) {
|
||||
if (percentComplete !== undefined && String(percentComplete).trim() !== '') {
|
||||
updateParams.percentComplete = Number(percentComplete)
|
||||
}
|
||||
if (appliedCategories?.trim()) {
|
||||
updateParams.appliedCategories = appliedCategories.trim()
|
||||
}
|
||||
|
||||
return updateParams
|
||||
}
|
||||
@@ -597,7 +803,8 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
|
||||
bucketIdForRead: { type: 'string', description: 'Bucket ID for read operations' },
|
||||
title: { type: 'string', description: 'Task title' },
|
||||
name: { type: 'string', description: 'Bucket name' },
|
||||
description: { type: 'string', description: 'Task or task details description' },
|
||||
planTitle: { type: 'string', description: 'Plan title for create/update plan' },
|
||||
description: { type: 'string', description: 'Task details description' },
|
||||
dueDateTime: { type: 'string', description: 'Due date' },
|
||||
startDateTime: { type: 'string', description: 'Start date' },
|
||||
assigneeUserId: { type: 'string', description: 'Assignee user ID' },
|
||||
@@ -607,6 +814,12 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
|
||||
checklist: { type: 'string', description: 'Checklist items as JSON' },
|
||||
references: { type: 'string', description: 'References as JSON' },
|
||||
previewType: { type: 'string', description: 'Preview type for task details' },
|
||||
appliedCategories: {
|
||||
type: 'string',
|
||||
description: 'Comma-separated category labels to apply to a task (e.g., category1,category3)',
|
||||
},
|
||||
categoryDescriptions: { type: 'string', description: 'Plan category label names as JSON' },
|
||||
sharedWith: { type: 'string', description: 'Plan shared-with user IDs as JSON' },
|
||||
},
|
||||
outputs: {
|
||||
message: {
|
||||
@@ -638,6 +851,11 @@ Return ONLY the timestamp string - no explanations, no quotes, no extra text.`,
|
||||
type: 'json',
|
||||
description: 'Array of Microsoft Planner plans',
|
||||
},
|
||||
planDetails: {
|
||||
type: 'json',
|
||||
description:
|
||||
'The Microsoft Planner plan details, including categoryDescriptions and sharedWith',
|
||||
},
|
||||
bucket: {
|
||||
type: 'json',
|
||||
description: 'The Microsoft Planner bucket object',
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type {
|
||||
MicrosoftPlannerCreatePlanResponse,
|
||||
MicrosoftPlannerToolParams,
|
||||
} from '@/tools/microsoft_planner/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('MicrosoftPlannerCreatePlan')
|
||||
|
||||
export const createPlanTool: ToolConfig<
|
||||
MicrosoftPlannerToolParams,
|
||||
MicrosoftPlannerCreatePlanResponse
|
||||
> = {
|
||||
id: 'microsoft_planner_create_plan',
|
||||
name: 'Create Microsoft Planner Plan',
|
||||
description: 'Create a new Microsoft Planner plan owned by a Microsoft 365 group',
|
||||
version: '1.0',
|
||||
errorExtractor: ErrorExtractorId.MICROSOFT_GRAPH_ERRORS,
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'microsoft-planner',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'The access token for the Microsoft Planner API',
|
||||
},
|
||||
groupId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'The ID of the Microsoft 365 group that will own the plan (e.g., "ebf3b108-5234-4e22-b93d-656d7dae5874"). The current user must be a member of this group.',
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The title of the plan',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: () => 'https://graph.microsoft.com/v1.0/planner/plans',
|
||||
method: 'POST',
|
||||
headers: (params) => {
|
||||
if (!params.accessToken) {
|
||||
throw new Error('Access token is required')
|
||||
}
|
||||
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
},
|
||||
body: (params) => {
|
||||
const groupId = params.groupId?.trim()
|
||||
if (!groupId) {
|
||||
throw new Error('Microsoft 365 group ID is required')
|
||||
}
|
||||
if (!params.title) {
|
||||
throw new Error('Plan title is required')
|
||||
}
|
||||
|
||||
const body = {
|
||||
container: {
|
||||
url: `https://graph.microsoft.com/v1.0/groups/${groupId}`,
|
||||
},
|
||||
title: params.title,
|
||||
}
|
||||
|
||||
logger.info('Creating plan with body:', body)
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const plan = await response.json()
|
||||
logger.info('Created plan:', plan)
|
||||
|
||||
const result: MicrosoftPlannerCreatePlanResponse = {
|
||||
success: true,
|
||||
output: {
|
||||
plan,
|
||||
metadata: {
|
||||
planId: plan.id,
|
||||
groupId: plan.container?.containerId,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return result
|
||||
},
|
||||
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Whether the plan was created successfully' },
|
||||
plan: { type: 'object', description: 'The created plan object with all properties' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Metadata including planId and groupId',
|
||||
properties: {
|
||||
planId: { type: 'string', description: 'Created plan ID' },
|
||||
groupId: { type: 'string', description: 'Owning Microsoft 365 group ID' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -42,12 +42,6 @@ export const createTaskTool: ToolConfig<
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The title of the task (e.g., "Review quarterly report")',
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'The description of the task',
|
||||
},
|
||||
dueDateTime: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
@@ -55,6 +49,25 @@ export const createTaskTool: ToolConfig<
|
||||
description:
|
||||
'The due date and time for the task in ISO 8601 format (e.g., "2025-03-15T17:00:00Z")',
|
||||
},
|
||||
startDateTime: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description:
|
||||
'The start date and time for the task in ISO 8601 format (e.g., "2025-03-10T09:00:00Z")',
|
||||
},
|
||||
priority: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'The priority of the task (0-10, where 0 is urgent and 10 is low)',
|
||||
},
|
||||
percentComplete: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description: 'The percentage of task completion (0-100)',
|
||||
},
|
||||
assigneeUserId: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
@@ -68,6 +81,13 @@ export const createTaskTool: ToolConfig<
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The bucket ID to place the task in (e.g., "hsOf2dhOJkC6Fey9VjDg1JgAC9Rq")',
|
||||
},
|
||||
appliedCategories: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Comma-separated category labels to apply to the task, e.g. "category1,category3" (up to category1-category25, plan-defined color labels)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
@@ -108,6 +128,22 @@ export const createTaskTool: ToolConfig<
|
||||
body.dueDateTime = params.dueDateTime
|
||||
}
|
||||
|
||||
if (
|
||||
params.startDateTime !== undefined &&
|
||||
params.startDateTime !== null &&
|
||||
params.startDateTime !== ''
|
||||
) {
|
||||
body.startDateTime = params.startDateTime
|
||||
}
|
||||
|
||||
if (params.priority !== undefined && params.priority !== null) {
|
||||
body.priority = Number(params.priority)
|
||||
}
|
||||
|
||||
if (params.percentComplete !== undefined && params.percentComplete !== null) {
|
||||
body.percentComplete = Number(params.percentComplete)
|
||||
}
|
||||
|
||||
if (
|
||||
params.assigneeUserId !== undefined &&
|
||||
params.assigneeUserId !== null &&
|
||||
@@ -115,12 +151,27 @@ export const createTaskTool: ToolConfig<
|
||||
) {
|
||||
body.assignments = {
|
||||
[params.assigneeUserId]: {
|
||||
'@odata.type': 'microsoft.graph.plannerAssignment',
|
||||
'@odata.type': '#microsoft.graph.plannerAssignment',
|
||||
orderHint: ' !',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if (params.appliedCategories?.trim()) {
|
||||
const categories = params.appliedCategories
|
||||
.split(',')
|
||||
.map((category) => category.trim())
|
||||
.filter(Boolean)
|
||||
|
||||
if (categories.length > 0) {
|
||||
body.appliedCategories = Object.fromEntries(
|
||||
categories.map((category) =>
|
||||
category.startsWith('-') ? [category.slice(1), false] : [category, true]
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
logger.info('Creating task with body:', body)
|
||||
return body
|
||||
},
|
||||
|
||||
@@ -44,10 +44,11 @@ export const deleteBucketTool: ToolConfig<
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
if (!params.bucketId) {
|
||||
const bucketId = params.bucketId?.trim()
|
||||
if (!bucketId) {
|
||||
throw new Error('Bucket ID is required')
|
||||
}
|
||||
return `https://graph.microsoft.com/v1.0/planner/buckets/${params.bucketId}`
|
||||
return `https://graph.microsoft.com/v1.0/planner/buckets/${bucketId}`
|
||||
},
|
||||
method: 'DELETE',
|
||||
headers: (params) => {
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type {
|
||||
MicrosoftPlannerDeletePlanResponse,
|
||||
MicrosoftPlannerToolParams,
|
||||
} from '@/tools/microsoft_planner/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('MicrosoftPlannerDeletePlan')
|
||||
|
||||
export const deletePlanTool: ToolConfig<
|
||||
MicrosoftPlannerToolParams,
|
||||
MicrosoftPlannerDeletePlanResponse
|
||||
> = {
|
||||
id: 'microsoft_planner_delete_plan',
|
||||
name: 'Delete Microsoft Planner Plan',
|
||||
description: 'Delete a Microsoft Planner plan',
|
||||
version: '1.0',
|
||||
errorExtractor: ErrorExtractorId.MICROSOFT_GRAPH_ERRORS,
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'microsoft-planner',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'The access token for the Microsoft Planner API',
|
||||
},
|
||||
planId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The ID of the plan to delete (e.g., "xqQg5FS2LkCe54tAMV_v2ZgADW2J")',
|
||||
},
|
||||
etag: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The ETag value from the plan to delete (If-Match header)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const planId = params.planId?.trim()
|
||||
if (!planId) {
|
||||
throw new Error('Plan ID is required')
|
||||
}
|
||||
return `https://graph.microsoft.com/v1.0/planner/plans/${planId}`
|
||||
},
|
||||
method: 'DELETE',
|
||||
headers: (params) => {
|
||||
if (!params.accessToken) {
|
||||
throw new Error('Access token is required')
|
||||
}
|
||||
if (!params.etag) {
|
||||
throw new Error('ETag is required for delete operations')
|
||||
}
|
||||
|
||||
let cleanedEtag = params.etag.trim()
|
||||
|
||||
while (cleanedEtag.startsWith('"') && cleanedEtag.endsWith('"')) {
|
||||
cleanedEtag = cleanedEtag.slice(1, -1)
|
||||
logger.info('Removed surrounding quotes:', cleanedEtag)
|
||||
}
|
||||
|
||||
if (cleanedEtag.includes('\\"')) {
|
||||
cleanedEtag = cleanedEtag.replace(/\\"/g, '"')
|
||||
logger.info('Cleaned escaped quotes from etag')
|
||||
}
|
||||
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'If-Match': cleanedEtag,
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
logger.info('Plan deleted successfully')
|
||||
|
||||
const result: MicrosoftPlannerDeletePlanResponse = {
|
||||
success: true,
|
||||
output: {
|
||||
deleted: true,
|
||||
metadata: {},
|
||||
},
|
||||
}
|
||||
|
||||
return result
|
||||
},
|
||||
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Whether the plan was deleted successfully' },
|
||||
deleted: { type: 'boolean', description: 'Confirmation of deletion' },
|
||||
metadata: { type: 'object', description: 'Additional metadata' },
|
||||
},
|
||||
}
|
||||
@@ -44,10 +44,11 @@ export const deleteTaskTool: ToolConfig<
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
if (!params.taskId) {
|
||||
const taskId = params.taskId?.trim()
|
||||
if (!taskId) {
|
||||
throw new Error('Task ID is required')
|
||||
}
|
||||
return `https://graph.microsoft.com/v1.0/planner/tasks/${params.taskId}`
|
||||
return `https://graph.microsoft.com/v1.0/planner/tasks/${taskId}`
|
||||
},
|
||||
method: 'DELETE',
|
||||
headers: (params) => {
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type {
|
||||
MicrosoftPlannerGetPlanDetailsResponse,
|
||||
MicrosoftPlannerToolParams,
|
||||
} from '@/tools/microsoft_planner/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('MicrosoftPlannerGetPlanDetails')
|
||||
|
||||
export const getPlanDetailsTool: ToolConfig<
|
||||
MicrosoftPlannerToolParams,
|
||||
MicrosoftPlannerGetPlanDetailsResponse
|
||||
> = {
|
||||
id: 'microsoft_planner_get_plan_details',
|
||||
name: 'Get Microsoft Planner Plan Details',
|
||||
description: 'Get detailed information about a plan including category descriptions and sharing',
|
||||
version: '1.0',
|
||||
errorExtractor: ErrorExtractorId.MICROSOFT_GRAPH_ERRORS,
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'microsoft-planner',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'The access token for the Microsoft Planner API',
|
||||
},
|
||||
planId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The ID of the plan (e.g., "xqQg5FS2LkCe54tAMV_v2ZgADW2J")',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const planId = params.planId?.trim()
|
||||
if (!planId) {
|
||||
throw new Error('Plan ID is required')
|
||||
}
|
||||
return `https://graph.microsoft.com/v1.0/planner/plans/${planId}/details`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
if (!params.accessToken) {
|
||||
throw new Error('Access token is required')
|
||||
}
|
||||
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const planDetails = await response.json()
|
||||
logger.info('Plan details retrieved:', planDetails)
|
||||
|
||||
const etag = planDetails['@odata.etag'] || ''
|
||||
|
||||
const result: MicrosoftPlannerGetPlanDetailsResponse = {
|
||||
success: true,
|
||||
output: {
|
||||
planDetails,
|
||||
etag,
|
||||
metadata: {
|
||||
planId: planDetails.id,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return result
|
||||
},
|
||||
|
||||
outputs: {
|
||||
success: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the plan details were retrieved successfully',
|
||||
},
|
||||
planDetails: {
|
||||
type: 'object',
|
||||
description: 'The plan details including categoryDescriptions and sharedWith',
|
||||
},
|
||||
etag: {
|
||||
type: 'string',
|
||||
description: 'The ETag value for this plan details resource',
|
||||
},
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Metadata including planId',
|
||||
properties: {
|
||||
planId: { type: 'string', description: 'Plan ID' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -38,10 +38,11 @@ export const getTaskDetailsTool: ToolConfig<
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
if (!params.taskId) {
|
||||
const taskId = params.taskId?.trim()
|
||||
if (!taskId) {
|
||||
throw new Error('Task ID is required')
|
||||
}
|
||||
return `https://graph.microsoft.com/v1.0/planner/tasks/${params.taskId}/details`
|
||||
return `https://graph.microsoft.com/v1.0/planner/tasks/${taskId}/details`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { createBucketTool } from '@/tools/microsoft_planner/create_bucket'
|
||||
import { createPlanTool } from '@/tools/microsoft_planner/create_plan'
|
||||
import { createTaskTool } from '@/tools/microsoft_planner/create_task'
|
||||
import { deleteBucketTool } from '@/tools/microsoft_planner/delete_bucket'
|
||||
import { deletePlanTool } from '@/tools/microsoft_planner/delete_plan'
|
||||
import { deleteTaskTool } from '@/tools/microsoft_planner/delete_task'
|
||||
import { getPlanDetailsTool } from '@/tools/microsoft_planner/get_plan_details'
|
||||
import { getTaskDetailsTool } from '@/tools/microsoft_planner/get_task_details'
|
||||
import { listBucketsTool } from '@/tools/microsoft_planner/list_buckets'
|
||||
import { listPlansTool } from '@/tools/microsoft_planner/list_plans'
|
||||
@@ -9,6 +12,8 @@ import { readBucketTool } from '@/tools/microsoft_planner/read_bucket'
|
||||
import { readPlanTool } from '@/tools/microsoft_planner/read_plan'
|
||||
import { readTaskTool } from '@/tools/microsoft_planner/read_task'
|
||||
import { updateBucketTool } from '@/tools/microsoft_planner/update_bucket'
|
||||
import { updatePlanTool } from '@/tools/microsoft_planner/update_plan'
|
||||
import { updatePlanDetailsTool } from '@/tools/microsoft_planner/update_plan_details'
|
||||
import { updateTaskTool } from '@/tools/microsoft_planner/update_task'
|
||||
import { updateTaskDetailsTool } from '@/tools/microsoft_planner/update_task_details'
|
||||
|
||||
@@ -18,6 +23,11 @@ export const microsoftPlannerUpdateTaskTool = updateTaskTool
|
||||
export const microsoftPlannerDeleteTaskTool = deleteTaskTool
|
||||
export const microsoftPlannerListPlansTool = listPlansTool
|
||||
export const microsoftPlannerReadPlanTool = readPlanTool
|
||||
export const microsoftPlannerCreatePlanTool = createPlanTool
|
||||
export const microsoftPlannerUpdatePlanTool = updatePlanTool
|
||||
export const microsoftPlannerGetPlanDetailsTool = getPlanDetailsTool
|
||||
export const microsoftPlannerUpdatePlanDetailsTool = updatePlanDetailsTool
|
||||
export const microsoftPlannerDeletePlanTool = deletePlanTool
|
||||
export const microsoftPlannerListBucketsTool = listBucketsTool
|
||||
export const microsoftPlannerReadBucketTool = readBucketTool
|
||||
export const microsoftPlannerCreateBucketTool = createBucketTool
|
||||
|
||||
@@ -38,10 +38,11 @@ export const listBucketsTool: ToolConfig<
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
if (!params.planId) {
|
||||
const planId = params.planId?.trim()
|
||||
if (!planId) {
|
||||
throw new Error('Plan ID is required')
|
||||
}
|
||||
return `https://graph.microsoft.com/v1.0/planner/plans/${params.planId}/buckets`
|
||||
return `https://graph.microsoft.com/v1.0/planner/plans/${planId}/buckets`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
|
||||
@@ -38,10 +38,11 @@ export const readBucketTool: ToolConfig<
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
if (!params.bucketId) {
|
||||
const bucketId = params.bucketId?.trim()
|
||||
if (!bucketId) {
|
||||
throw new Error('Bucket ID is required')
|
||||
}
|
||||
return `https://graph.microsoft.com/v1.0/planner/buckets/${params.bucketId}`
|
||||
return `https://graph.microsoft.com/v1.0/planner/buckets/${bucketId}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
|
||||
@@ -38,10 +38,11 @@ export const readPlanTool: ToolConfig<
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
if (!params.planId) {
|
||||
const planId = params.planId?.trim()
|
||||
if (!planId) {
|
||||
throw new Error('Plan ID is required')
|
||||
}
|
||||
return `https://graph.microsoft.com/v1.0/planner/plans/${params.planId}`
|
||||
return `https://graph.microsoft.com/v1.0/planner/plans/${planId}`
|
||||
},
|
||||
method: 'GET',
|
||||
headers: (params) => {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type {
|
||||
MicrosoftPlannerReadResponse,
|
||||
MicrosoftPlannerToolParams,
|
||||
@@ -13,7 +14,7 @@ export const readTaskTool: ToolConfig<MicrosoftPlannerToolParams, MicrosoftPlann
|
||||
description:
|
||||
'Read tasks from Microsoft Planner - get all user tasks or all tasks from a specific plan',
|
||||
version: '1.0',
|
||||
errorExtractor: 'nested-error-object',
|
||||
errorExtractor: ErrorExtractorId.MICROSOFT_GRAPH_ERRORS,
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
|
||||
@@ -41,6 +41,10 @@ interface PlannerContainer {
|
||||
url?: string
|
||||
}
|
||||
|
||||
interface PlannerAppliedCategories {
|
||||
[category: string]: boolean
|
||||
}
|
||||
|
||||
export interface PlannerTask {
|
||||
id?: string
|
||||
planId: string
|
||||
@@ -61,6 +65,7 @@ export interface PlannerTask {
|
||||
conversationThreadId?: string
|
||||
priority?: number
|
||||
assignments?: Record<string, PlannerAssignment>
|
||||
appliedCategories?: PlannerAppliedCategories
|
||||
bucketId?: string
|
||||
details?: {
|
||||
description?: string
|
||||
@@ -69,7 +74,7 @@ export interface PlannerTask {
|
||||
}
|
||||
}
|
||||
|
||||
interface PlannerBucket {
|
||||
export interface PlannerBucket {
|
||||
id: string
|
||||
name: string
|
||||
planId: string
|
||||
@@ -77,7 +82,7 @@ interface PlannerBucket {
|
||||
'@odata.etag'?: string
|
||||
}
|
||||
|
||||
interface PlannerPlan {
|
||||
export interface PlannerPlan {
|
||||
id: string
|
||||
title: string
|
||||
owner?: string
|
||||
@@ -86,7 +91,14 @@ interface PlannerPlan {
|
||||
'@odata.etag'?: string
|
||||
}
|
||||
|
||||
interface PlannerTaskDetails {
|
||||
export interface PlannerPlanDetails {
|
||||
id: string
|
||||
categoryDescriptions?: Record<string, string | null>
|
||||
sharedWith?: Record<string, boolean>
|
||||
'@odata.etag'?: string
|
||||
}
|
||||
|
||||
export interface PlannerTaskDetails {
|
||||
id: string
|
||||
description?: string
|
||||
previewType?: string
|
||||
@@ -153,6 +165,28 @@ export interface MicrosoftPlannerReadPlanResponse extends ToolResponse {
|
||||
}
|
||||
}
|
||||
|
||||
export interface MicrosoftPlannerCreatePlanResponse extends ToolResponse {
|
||||
output: {
|
||||
plan: PlannerPlan
|
||||
metadata: MicrosoftPlannerMetadata
|
||||
}
|
||||
}
|
||||
|
||||
export interface MicrosoftPlannerDeletePlanResponse extends ToolResponse {
|
||||
output: {
|
||||
deleted: boolean
|
||||
metadata: MicrosoftPlannerMetadata
|
||||
}
|
||||
}
|
||||
|
||||
export interface MicrosoftPlannerGetPlanDetailsResponse extends ToolResponse {
|
||||
output: {
|
||||
planDetails: PlannerPlanDetails
|
||||
etag: string
|
||||
metadata: MicrosoftPlannerMetadata
|
||||
}
|
||||
}
|
||||
|
||||
export interface MicrosoftPlannerListBucketsResponse extends ToolResponse {
|
||||
output: {
|
||||
buckets: PlannerBucket[]
|
||||
@@ -203,6 +237,20 @@ export interface MicrosoftPlannerUpdateTaskDetailsResponse extends ToolResponse
|
||||
}
|
||||
}
|
||||
|
||||
export interface MicrosoftPlannerUpdatePlanResponse extends ToolResponse {
|
||||
output: {
|
||||
plan: PlannerPlan
|
||||
metadata: MicrosoftPlannerMetadata
|
||||
}
|
||||
}
|
||||
|
||||
export interface MicrosoftPlannerUpdatePlanDetailsResponse extends ToolResponse {
|
||||
output: {
|
||||
planDetails: PlannerPlanDetails
|
||||
metadata: MicrosoftPlannerMetadata
|
||||
}
|
||||
}
|
||||
|
||||
export interface MicrosoftPlannerToolParams {
|
||||
accessToken: string
|
||||
planId?: string
|
||||
@@ -221,6 +269,9 @@ export interface MicrosoftPlannerToolParams {
|
||||
checklist?: Record<string, any>
|
||||
references?: Record<string, any>
|
||||
previewType?: string
|
||||
appliedCategories?: string
|
||||
categoryDescriptions?: Record<string, any>
|
||||
sharedWith?: Record<string, any>
|
||||
}
|
||||
|
||||
export type MicrosoftPlannerResponse =
|
||||
@@ -230,6 +281,9 @@ export type MicrosoftPlannerResponse =
|
||||
| MicrosoftPlannerDeleteTaskResponse
|
||||
| MicrosoftPlannerListPlansResponse
|
||||
| MicrosoftPlannerReadPlanResponse
|
||||
| MicrosoftPlannerCreatePlanResponse
|
||||
| MicrosoftPlannerDeletePlanResponse
|
||||
| MicrosoftPlannerGetPlanDetailsResponse
|
||||
| MicrosoftPlannerListBucketsResponse
|
||||
| MicrosoftPlannerReadBucketResponse
|
||||
| MicrosoftPlannerCreateBucketResponse
|
||||
@@ -237,3 +291,5 @@ export type MicrosoftPlannerResponse =
|
||||
| MicrosoftPlannerDeleteBucketResponse
|
||||
| MicrosoftPlannerGetTaskDetailsResponse
|
||||
| MicrosoftPlannerUpdateTaskDetailsResponse
|
||||
| MicrosoftPlannerUpdatePlanResponse
|
||||
| MicrosoftPlannerUpdatePlanDetailsResponse
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type {
|
||||
MicrosoftPlannerToolParams,
|
||||
MicrosoftPlannerUpdateBucketResponse,
|
||||
PlannerBucket,
|
||||
} from '@/tools/microsoft_planner/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
@@ -15,7 +17,7 @@ export const updateBucketTool: ToolConfig<
|
||||
name: 'Update Microsoft Planner Bucket',
|
||||
description: 'Update a bucket in Microsoft Planner',
|
||||
version: '1.0',
|
||||
errorExtractor: 'nested-error-object',
|
||||
errorExtractor: ErrorExtractorId.MICROSOFT_GRAPH_ERRORS,
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
@@ -51,10 +53,11 @@ export const updateBucketTool: ToolConfig<
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
if (!params.bucketId) {
|
||||
const bucketId = params.bucketId?.trim()
|
||||
if (!bucketId) {
|
||||
throw new Error('Bucket ID is required')
|
||||
}
|
||||
return `https://graph.microsoft.com/v1.0/planner/buckets/${params.bucketId}`
|
||||
return `https://graph.microsoft.com/v1.0/planner/buckets/${bucketId}`
|
||||
},
|
||||
method: 'PATCH',
|
||||
headers: (params) => {
|
||||
@@ -80,6 +83,7 @@ export const updateBucketTool: ToolConfig<
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
Prefer: 'return=representation',
|
||||
'If-Match': cleanedEtag,
|
||||
}
|
||||
},
|
||||
@@ -99,8 +103,24 @@ export const updateBucketTool: ToolConfig<
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const bucket = await response.json()
|
||||
transformResponse: async (response: Response, params?: MicrosoftPlannerToolParams) => {
|
||||
// Prefer: return=representation requests a body, but the service may still return
|
||||
// 204 No Content for some tenants/requests
|
||||
const text = await response.text()
|
||||
if (!text || text.trim() === '') {
|
||||
logger.info('Update successful but no response body returned (204 No Content)')
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
bucket: {} as PlannerBucket,
|
||||
metadata: {
|
||||
bucketId: params?.bucketId?.trim(),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const bucket = JSON.parse(text)
|
||||
logger.info('Updated bucket:', bucket)
|
||||
|
||||
const result: MicrosoftPlannerUpdateBucketResponse = {
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type {
|
||||
MicrosoftPlannerToolParams,
|
||||
MicrosoftPlannerUpdatePlanResponse,
|
||||
PlannerPlan,
|
||||
} from '@/tools/microsoft_planner/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('MicrosoftPlannerUpdatePlan')
|
||||
|
||||
export const updatePlanTool: ToolConfig<
|
||||
MicrosoftPlannerToolParams,
|
||||
MicrosoftPlannerUpdatePlanResponse
|
||||
> = {
|
||||
id: 'microsoft_planner_update_plan',
|
||||
name: 'Update Microsoft Planner Plan',
|
||||
description: 'Rename a Microsoft Planner plan',
|
||||
version: '1.0',
|
||||
errorExtractor: ErrorExtractorId.MICROSOFT_GRAPH_ERRORS,
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'microsoft-planner',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'The access token for the Microsoft Planner API',
|
||||
},
|
||||
planId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The ID of the plan to update (e.g., "xqQg5FS2LkCe54tAMV_v2ZgADW2J")',
|
||||
},
|
||||
etag: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The ETag value from the plan to update (If-Match header)',
|
||||
},
|
||||
title: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The new title of the plan',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const planId = params.planId?.trim()
|
||||
if (!planId) {
|
||||
throw new Error('Plan ID is required')
|
||||
}
|
||||
return `https://graph.microsoft.com/v1.0/planner/plans/${planId}`
|
||||
},
|
||||
method: 'PATCH',
|
||||
headers: (params) => {
|
||||
if (!params.accessToken) {
|
||||
throw new Error('Access token is required')
|
||||
}
|
||||
if (!params.etag) {
|
||||
throw new Error('ETag is required for update operations')
|
||||
}
|
||||
|
||||
let cleanedEtag = params.etag.trim()
|
||||
|
||||
while (cleanedEtag.startsWith('"') && cleanedEtag.endsWith('"')) {
|
||||
cleanedEtag = cleanedEtag.slice(1, -1)
|
||||
}
|
||||
|
||||
if (cleanedEtag.includes('\\"')) {
|
||||
cleanedEtag = cleanedEtag.replace(/\\"/g, '"')
|
||||
}
|
||||
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
Prefer: 'return=representation',
|
||||
'If-Match': cleanedEtag,
|
||||
}
|
||||
},
|
||||
body: (params) => {
|
||||
if (!params.title?.trim()) {
|
||||
throw new Error('Plan title is required')
|
||||
}
|
||||
|
||||
const body = { title: params.title.trim() }
|
||||
|
||||
logger.info('Updating plan with body:', body)
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, params?: MicrosoftPlannerToolParams) => {
|
||||
// Prefer: return=representation requests a body, but the service may still return
|
||||
// 204 No Content for some tenants/requests
|
||||
const text = await response.text()
|
||||
if (!text || text.trim() === '') {
|
||||
logger.info('Update successful but no response body returned (204 No Content)')
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
plan: {} as PlannerPlan,
|
||||
metadata: {
|
||||
planId: params?.planId?.trim(),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const plan = JSON.parse(text)
|
||||
logger.info('Updated plan:', plan)
|
||||
|
||||
const result: MicrosoftPlannerUpdatePlanResponse = {
|
||||
success: true,
|
||||
output: {
|
||||
plan,
|
||||
metadata: {
|
||||
planId: plan.id,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return result
|
||||
},
|
||||
|
||||
outputs: {
|
||||
success: { type: 'boolean', description: 'Whether the plan was updated successfully' },
|
||||
plan: { type: 'object', description: 'The updated plan object with all properties' },
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Metadata including planId',
|
||||
properties: {
|
||||
planId: { type: 'string', description: 'Updated plan ID' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type {
|
||||
MicrosoftPlannerToolParams,
|
||||
MicrosoftPlannerUpdatePlanDetailsResponse,
|
||||
PlannerPlanDetails,
|
||||
} from '@/tools/microsoft_planner/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
const logger = createLogger('MicrosoftPlannerUpdatePlanDetails')
|
||||
|
||||
export const updatePlanDetailsTool: ToolConfig<
|
||||
MicrosoftPlannerToolParams,
|
||||
MicrosoftPlannerUpdatePlanDetailsResponse
|
||||
> = {
|
||||
id: 'microsoft_planner_update_plan_details',
|
||||
name: 'Update Microsoft Planner Plan Details',
|
||||
description:
|
||||
"Update a plan's category (color label) descriptions and shared-with user list in Microsoft Planner",
|
||||
version: '1.0',
|
||||
errorExtractor: ErrorExtractorId.MICROSOFT_GRAPH_ERRORS,
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
provider: 'microsoft-planner',
|
||||
},
|
||||
|
||||
params: {
|
||||
accessToken: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'hidden',
|
||||
description: 'The access token for the Microsoft Planner API',
|
||||
},
|
||||
planId: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-or-llm',
|
||||
description: 'The ID of the plan (e.g., "xqQg5FS2LkCe54tAMV_v2ZgADW2J")',
|
||||
},
|
||||
etag: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
visibility: 'user-only',
|
||||
description: 'The ETag value from the plan details to update (If-Match header)',
|
||||
},
|
||||
categoryDescriptions: {
|
||||
type: 'object',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description:
|
||||
'Category label names as a JSON object, e.g. {"category1": "Blocked", "category2": "At Risk"}. Set a value to null to clear a label.',
|
||||
},
|
||||
sharedWith: {
|
||||
type: 'object',
|
||||
required: false,
|
||||
visibility: 'user-only',
|
||||
description:
|
||||
'User IDs to share the plan with as a JSON object, e.g. {"<user-id>": true}. Set a value to false to unshare.',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
const planId = params.planId?.trim()
|
||||
if (!planId) {
|
||||
throw new Error('Plan ID is required')
|
||||
}
|
||||
return `https://graph.microsoft.com/v1.0/planner/plans/${planId}/details`
|
||||
},
|
||||
method: 'PATCH',
|
||||
headers: (params) => {
|
||||
if (!params.accessToken) {
|
||||
throw new Error('Access token is required')
|
||||
}
|
||||
if (!params.etag) {
|
||||
throw new Error('ETag is required for update operations')
|
||||
}
|
||||
|
||||
let cleanedEtag = params.etag.trim()
|
||||
|
||||
while (cleanedEtag.startsWith('"') && cleanedEtag.endsWith('"')) {
|
||||
cleanedEtag = cleanedEtag.slice(1, -1)
|
||||
}
|
||||
|
||||
if (cleanedEtag.includes('\\"')) {
|
||||
cleanedEtag = cleanedEtag.replace(/\\"/g, '"')
|
||||
}
|
||||
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
Prefer: 'return=representation',
|
||||
'If-Match': cleanedEtag,
|
||||
}
|
||||
},
|
||||
body: (params) => {
|
||||
const body: Record<string, any> = {}
|
||||
|
||||
if (params.categoryDescriptions) {
|
||||
try {
|
||||
body.categoryDescriptions =
|
||||
typeof params.categoryDescriptions === 'string'
|
||||
? JSON.parse(params.categoryDescriptions)
|
||||
: params.categoryDescriptions
|
||||
} catch {
|
||||
throw new Error('categoryDescriptions must be valid JSON')
|
||||
}
|
||||
}
|
||||
|
||||
if (params.sharedWith) {
|
||||
try {
|
||||
body.sharedWith =
|
||||
typeof params.sharedWith === 'string'
|
||||
? JSON.parse(params.sharedWith)
|
||||
: params.sharedWith
|
||||
} catch {
|
||||
throw new Error('sharedWith must be valid JSON')
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(body).length === 0) {
|
||||
throw new Error('At least one field must be provided to update')
|
||||
}
|
||||
|
||||
logger.info('Updating plan details with body:', body)
|
||||
return body
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, params?: MicrosoftPlannerToolParams) => {
|
||||
// Prefer: return=representation requests a body, but the service may still return
|
||||
// 204 No Content for some tenants/requests
|
||||
const text = await response.text()
|
||||
if (!text || text.trim() === '') {
|
||||
logger.info('Update successful but no response body returned (204 No Content)')
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
planDetails: {} as PlannerPlanDetails,
|
||||
metadata: {
|
||||
planId: params?.planId?.trim(),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const planDetails = JSON.parse(text)
|
||||
logger.info('Updated plan details:', planDetails)
|
||||
|
||||
const result: MicrosoftPlannerUpdatePlanDetailsResponse = {
|
||||
success: true,
|
||||
output: {
|
||||
planDetails,
|
||||
metadata: {
|
||||
planId: planDetails.id,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return result
|
||||
},
|
||||
|
||||
outputs: {
|
||||
success: {
|
||||
type: 'boolean',
|
||||
description: 'Whether the plan details were updated successfully',
|
||||
},
|
||||
planDetails: {
|
||||
type: 'object',
|
||||
description: 'The updated plan details object with categoryDescriptions and sharedWith',
|
||||
},
|
||||
metadata: {
|
||||
type: 'object',
|
||||
description: 'Metadata including planId',
|
||||
properties: {
|
||||
planId: { type: 'string', description: 'Plan ID' },
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type {
|
||||
MicrosoftPlannerToolParams,
|
||||
MicrosoftPlannerUpdateTaskResponse,
|
||||
@@ -16,7 +17,7 @@ export const updateTaskTool: ToolConfig<
|
||||
name: 'Update Microsoft Planner Task',
|
||||
description: 'Update a task in Microsoft Planner',
|
||||
version: '1.0',
|
||||
errorExtractor: 'nested-error-object',
|
||||
errorExtractor: ErrorExtractorId.MICROSOFT_GRAPH_ERRORS,
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
@@ -86,14 +87,22 @@ export const updateTaskTool: ToolConfig<
|
||||
description:
|
||||
'The user ID to assign the task to (e.g., "e82f74c3-4d8a-4b5c-9f1e-2a6b8c9d0e3f")',
|
||||
},
|
||||
appliedCategories: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
visibility: 'user-or-llm',
|
||||
description:
|
||||
'Comma-separated category labels to apply to the task, e.g. "category1,category3" (up to category1-category25, plan-defined color labels)',
|
||||
},
|
||||
},
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
if (!params.taskId) {
|
||||
const taskId = params.taskId?.trim()
|
||||
if (!taskId) {
|
||||
throw new Error('Task ID is required')
|
||||
}
|
||||
return `https://graph.microsoft.com/v1.0/planner/tasks/${params.taskId}`
|
||||
return `https://graph.microsoft.com/v1.0/planner/tasks/${taskId}`
|
||||
},
|
||||
method: 'PATCH',
|
||||
headers: (params) => {
|
||||
@@ -123,6 +132,7 @@ export const updateTaskTool: ToolConfig<
|
||||
return {
|
||||
Authorization: `Bearer ${params.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
Prefer: 'return=representation',
|
||||
'If-Match': cleanedEtag,
|
||||
}
|
||||
},
|
||||
@@ -157,7 +167,7 @@ export const updateTaskTool: ToolConfig<
|
||||
body.percentComplete = params.percentComplete
|
||||
}
|
||||
|
||||
if (params.priority !== undefined) {
|
||||
if (params.priority !== undefined && params.priority !== null) {
|
||||
body.priority = Number(params.priority)
|
||||
}
|
||||
|
||||
@@ -168,12 +178,27 @@ export const updateTaskTool: ToolConfig<
|
||||
) {
|
||||
body.assignments = {
|
||||
[params.assigneeUserId]: {
|
||||
'@odata.type': 'microsoft.graph.plannerAssignment',
|
||||
'@odata.type': '#microsoft.graph.plannerAssignment',
|
||||
orderHint: ' !',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if (params.appliedCategories?.trim()) {
|
||||
const categories = params.appliedCategories
|
||||
.split(',')
|
||||
.map((category) => category.trim())
|
||||
.filter(Boolean)
|
||||
|
||||
if (categories.length > 0) {
|
||||
body.appliedCategories = Object.fromEntries(
|
||||
categories.map((category) =>
|
||||
category.startsWith('-') ? [category.slice(1), false] : [category, true]
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(body).length === 0) {
|
||||
throw new Error('At least one field must be provided to update')
|
||||
}
|
||||
@@ -184,19 +209,24 @@ export const updateTaskTool: ToolConfig<
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response, params?: MicrosoftPlannerToolParams) => {
|
||||
// Check if response has content before parsing
|
||||
// Check if response has content before parsing (Prefer: return=representation requests a
|
||||
// body, but the service may still return 204 No Content for some tenants/requests)
|
||||
const text = await response.text()
|
||||
if (!text || text.trim() === '') {
|
||||
logger.info('Update successful but no response body returned (204 No Content)')
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
message: 'Task updated successfully',
|
||||
// Graph returned no body, so the etag sent in this request is now stale (the
|
||||
// update changed it) and the actual new value is unknown. Returning it here would
|
||||
// let a chained update silently reuse a stale If-Match and fail with 412 — leave
|
||||
// it empty so callers re-fetch the task before their next update.
|
||||
message: 'Task updated successfully (re-fetch the task to get its current etag)',
|
||||
task: {} as PlannerTask,
|
||||
taskId: params?.taskId || '',
|
||||
etag: params?.etag || '',
|
||||
taskId: params?.taskId?.trim() || '',
|
||||
etag: '',
|
||||
metadata: {
|
||||
taskId: params?.taskId,
|
||||
taskId: params?.taskId?.trim(),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { ErrorExtractorId } from '@/tools/error-extractors'
|
||||
import type {
|
||||
MicrosoftPlannerToolParams,
|
||||
MicrosoftPlannerUpdateTaskDetailsResponse,
|
||||
PlannerTaskDetails,
|
||||
} from '@/tools/microsoft_planner/types'
|
||||
import type { ToolConfig } from '@/tools/types'
|
||||
|
||||
@@ -16,7 +18,7 @@ export const updateTaskDetailsTool: ToolConfig<
|
||||
description:
|
||||
'Update task details including description, checklist items, and references in Microsoft Planner',
|
||||
version: '1.0',
|
||||
errorExtractor: 'nested-error-object',
|
||||
errorExtractor: ErrorExtractorId.MICROSOFT_GRAPH_ERRORS,
|
||||
|
||||
oauth: {
|
||||
required: true,
|
||||
@@ -70,10 +72,11 @@ export const updateTaskDetailsTool: ToolConfig<
|
||||
|
||||
request: {
|
||||
url: (params) => {
|
||||
if (!params.taskId) {
|
||||
const taskId = params.taskId?.trim()
|
||||
if (!taskId) {
|
||||
throw new Error('Task ID is required')
|
||||
}
|
||||
return `https://graph.microsoft.com/v1.0/planner/tasks/${params.taskId}/details`
|
||||
return `https://graph.microsoft.com/v1.0/planner/tasks/${taskId}/details`
|
||||
},
|
||||
method: 'PATCH',
|
||||
headers: (params) => {
|
||||
@@ -127,11 +130,23 @@ export const updateTaskDetailsTool: ToolConfig<
|
||||
}
|
||||
|
||||
if (params.checklist) {
|
||||
body.checklist = params.checklist
|
||||
try {
|
||||
body.checklist =
|
||||
typeof params.checklist === 'string' ? JSON.parse(params.checklist) : params.checklist
|
||||
} catch {
|
||||
throw new Error('Checklist must be valid JSON')
|
||||
}
|
||||
}
|
||||
|
||||
if (params.references) {
|
||||
body.references = params.references
|
||||
try {
|
||||
body.references =
|
||||
typeof params.references === 'string'
|
||||
? JSON.parse(params.references)
|
||||
: params.references
|
||||
} catch {
|
||||
throw new Error('References must be valid JSON')
|
||||
}
|
||||
}
|
||||
|
||||
if (params.previewType) {
|
||||
@@ -147,8 +162,24 @@ export const updateTaskDetailsTool: ToolConfig<
|
||||
},
|
||||
},
|
||||
|
||||
transformResponse: async (response: Response) => {
|
||||
const taskDetails = await response.json()
|
||||
transformResponse: async (response: Response, params?: MicrosoftPlannerToolParams) => {
|
||||
// Prefer: return=representation requests a body, but the service may still return
|
||||
// 204 No Content for some tenants/requests
|
||||
const text = await response.text()
|
||||
if (!text || text.trim() === '') {
|
||||
logger.info('Update successful but no response body returned (204 No Content)')
|
||||
return {
|
||||
success: true,
|
||||
output: {
|
||||
taskDetails: {} as PlannerTaskDetails,
|
||||
metadata: {
|
||||
taskId: params?.taskId?.trim(),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const taskDetails = JSON.parse(text)
|
||||
logger.info('Updated task details:', taskDetails)
|
||||
|
||||
const result: MicrosoftPlannerUpdateTaskDetailsResponse = {
|
||||
|
||||
@@ -2334,9 +2334,12 @@ import {
|
||||
} from '@/tools/microsoft_excel'
|
||||
import {
|
||||
microsoftPlannerCreateBucketTool,
|
||||
microsoftPlannerCreatePlanTool,
|
||||
microsoftPlannerCreateTaskTool,
|
||||
microsoftPlannerDeleteBucketTool,
|
||||
microsoftPlannerDeletePlanTool,
|
||||
microsoftPlannerDeleteTaskTool,
|
||||
microsoftPlannerGetPlanDetailsTool,
|
||||
microsoftPlannerGetTaskDetailsTool,
|
||||
microsoftPlannerListBucketsTool,
|
||||
microsoftPlannerListPlansTool,
|
||||
@@ -2344,6 +2347,8 @@ import {
|
||||
microsoftPlannerReadPlanTool,
|
||||
microsoftPlannerReadTaskTool,
|
||||
microsoftPlannerUpdateBucketTool,
|
||||
microsoftPlannerUpdatePlanDetailsTool,
|
||||
microsoftPlannerUpdatePlanTool,
|
||||
microsoftPlannerUpdateTaskDetailsTool,
|
||||
microsoftPlannerUpdateTaskTool,
|
||||
} from '@/tools/microsoft_planner'
|
||||
@@ -7623,6 +7628,11 @@ export const tools: Record<string, ToolConfig> = {
|
||||
microsoft_planner_delete_task: microsoftPlannerDeleteTaskTool,
|
||||
microsoft_planner_list_plans: microsoftPlannerListPlansTool,
|
||||
microsoft_planner_read_plan: microsoftPlannerReadPlanTool,
|
||||
microsoft_planner_create_plan: microsoftPlannerCreatePlanTool,
|
||||
microsoft_planner_update_plan: microsoftPlannerUpdatePlanTool,
|
||||
microsoft_planner_get_plan_details: microsoftPlannerGetPlanDetailsTool,
|
||||
microsoft_planner_update_plan_details: microsoftPlannerUpdatePlanDetailsTool,
|
||||
microsoft_planner_delete_plan: microsoftPlannerDeletePlanTool,
|
||||
microsoft_planner_list_buckets: microsoftPlannerListBucketsTool,
|
||||
microsoft_planner_read_bucket: microsoftPlannerReadBucketTool,
|
||||
microsoft_planner_create_bucket: microsoftPlannerCreateBucketTool,
|
||||
|
||||
Reference in New Issue
Block a user