strapi-plugin-ai-sdk 0.6.9 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +151 -2
- package/dist/_chunks/{App-C_BH5Ir4.js → App-BGIUzHMh.js} +298 -2
- package/dist/_chunks/{App-DKyCb0BY.mjs → App-v0CobEGM.mjs} +298 -2
- package/dist/_chunks/{index-DCEjJ0as.js → index-BNk29VRc.js} +1 -1
- package/dist/_chunks/{index-BV9DET_M.mjs → index-CFO5UshL.mjs} +1 -1
- package/dist/admin/index.js +1 -1
- package/dist/admin/index.mjs +1 -1
- package/dist/admin/src/components/TaskConfirmCard.d.ts +11 -0
- package/dist/server/index.js +526 -76
- package/dist/server/index.mjs +526 -76
- package/dist/server/src/content-types/index.d.ts +62 -0
- package/dist/server/src/content-types/task/index.d.ts +63 -0
- package/dist/server/src/controllers/index.d.ts +8 -0
- package/dist/server/src/controllers/task.d.ts +11 -0
- package/dist/server/src/index.d.ts +70 -3
- package/dist/server/src/lib/tool-registry.d.ts +2 -0
- package/dist/server/src/tool-logic/index.d.ts +2 -0
- package/dist/server/src/tool-logic/manage-task.d.ts +43 -0
- package/dist/server/src/tools/definitions/manage-task.d.ts +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -286,9 +286,12 @@ The AI assistant has access to these tools. Tools marked as **public** are also
|
|
|
286
286
|
|------|----------|-------------|
|
|
287
287
|
| `listContentTypes` | `list_content_types` | List all Strapi content types and components with their fields and relations |
|
|
288
288
|
| `searchContent` | `search_content` | Search and query any content type with filters, sorting, and pagination |
|
|
289
|
+
| `aggregateContent` | `aggregate_content` | Count, group, and analyze content (faster than searchContent for analytics) |
|
|
289
290
|
| `writeContent` | `write_content` | Create or update documents in any content type |
|
|
290
291
|
| `sendEmail` | `send_email` | Send emails via the configured email provider (e.g. Resend) |
|
|
291
292
|
|
|
293
|
+
Additionally, the AI SDK automatically discovers tools from other installed plugins (see [Extending the Plugin](#adding-tools-from-other-plugins-convention-based-discovery)). For example, with the mentions and embeddings plugins installed, the AI also has access to `searchMentions`, `semanticSearch`, `ragQuery`, and more.
|
|
294
|
+
|
|
292
295
|
### Tool Details
|
|
293
296
|
|
|
294
297
|
**searchContent** parameters: `contentType` (required), `query`, `filters`, `fields`, `sort`, `page`, `pageSize` (max 50)
|
|
@@ -465,7 +468,151 @@ curl -N -X POST http://localhost:1337/api/ai-sdk/ask-stream \
|
|
|
465
468
|
|
|
466
469
|
## Extending the Plugin
|
|
467
470
|
|
|
468
|
-
### Adding
|
|
471
|
+
### Adding Tools from Other Plugins (Convention-Based Discovery)
|
|
472
|
+
|
|
473
|
+
Any Strapi plugin can contribute tools to the AI SDK by exposing an `ai-tools` service with a `getTools()` method. The AI SDK discovers these automatically at boot time -- no configuration required.
|
|
474
|
+
|
|
475
|
+
```mermaid
|
|
476
|
+
flowchart LR
|
|
477
|
+
subgraph "AI SDK Bootstrap"
|
|
478
|
+
B[Scan all plugins]
|
|
479
|
+
end
|
|
480
|
+
|
|
481
|
+
subgraph "Plugin A"
|
|
482
|
+
A1[ai-tools service] --> A2["getTools()"]
|
|
483
|
+
end
|
|
484
|
+
|
|
485
|
+
subgraph "Plugin B"
|
|
486
|
+
B1[ai-tools service] --> B2["getTools()"]
|
|
487
|
+
end
|
|
488
|
+
|
|
489
|
+
B --> A1
|
|
490
|
+
B --> B1
|
|
491
|
+
A2 --> R[ToolRegistry]
|
|
492
|
+
B2 --> R
|
|
493
|
+
R --> Chat[Admin Chat]
|
|
494
|
+
R --> MCP[MCP Server]
|
|
495
|
+
R --> Public[Public Chat]
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
#### How It Works
|
|
499
|
+
|
|
500
|
+
1. On startup, the AI SDK scans every loaded plugin for an `ai-tools` service
|
|
501
|
+
2. If found, it calls `getTools()` which returns an array of `ToolDefinition` objects
|
|
502
|
+
3. Each tool is namespaced as `pluginName__toolName` (e.g., `octalens_mentions__searchMentions`) to prevent collisions
|
|
503
|
+
4. Discovered tools are registered in the shared `ToolRegistry` alongside built-in tools
|
|
504
|
+
5. All registered tools are available in admin chat, public chat (if `publicSafe: true`), and MCP
|
|
505
|
+
|
|
506
|
+
#### Creating an `ai-tools` Service in Your Plugin
|
|
507
|
+
|
|
508
|
+
**1. Define canonical tools** in `server/src/tools/`:
|
|
509
|
+
|
|
510
|
+
```typescript
|
|
511
|
+
// server/src/tools/my-tool.ts
|
|
512
|
+
import { z } from 'zod';
|
|
513
|
+
import type { Core } from '@strapi/strapi';
|
|
514
|
+
|
|
515
|
+
const schema = z.object({
|
|
516
|
+
query: z.string().describe('Search query'),
|
|
517
|
+
limit: z.number().min(1).max(50).optional().default(10).describe('Max results'),
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
export const mySearchTool = {
|
|
521
|
+
name: 'mySearch',
|
|
522
|
+
description: 'Search my plugin data with relevance ranking.',
|
|
523
|
+
schema,
|
|
524
|
+
execute: async (args: z.infer<typeof schema>, strapi: Core.Strapi) => {
|
|
525
|
+
const validated = schema.parse(args);
|
|
526
|
+
const results = await strapi.documents('plugin::my-plugin.item' as any).findMany({
|
|
527
|
+
filters: { title: { $containsi: validated.query } },
|
|
528
|
+
limit: validated.limit,
|
|
529
|
+
});
|
|
530
|
+
return { results, total: results.length };
|
|
531
|
+
},
|
|
532
|
+
publicSafe: true, // available in public chat (read-only operations)
|
|
533
|
+
};
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
**2. Create the `ai-tools` service:**
|
|
537
|
+
|
|
538
|
+
```typescript
|
|
539
|
+
// server/src/services/ai-tools.ts
|
|
540
|
+
import type { Core } from '@strapi/strapi';
|
|
541
|
+
import { tools } from '../tools';
|
|
542
|
+
|
|
543
|
+
export default ({ strapi }: { strapi: Core.Strapi }) => ({
|
|
544
|
+
getTools() {
|
|
545
|
+
return tools;
|
|
546
|
+
},
|
|
547
|
+
});
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
**3. Register the service:**
|
|
551
|
+
|
|
552
|
+
```typescript
|
|
553
|
+
// server/src/services/index.ts
|
|
554
|
+
import myService from './my-service';
|
|
555
|
+
import aiTools from './ai-tools';
|
|
556
|
+
|
|
557
|
+
export default {
|
|
558
|
+
'my-service': myService,
|
|
559
|
+
'ai-tools': aiTools,
|
|
560
|
+
};
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
That's it. The AI SDK will discover and register your tools on the next Strapi restart.
|
|
564
|
+
|
|
565
|
+
#### ToolDefinition Interface
|
|
566
|
+
|
|
567
|
+
```typescript
|
|
568
|
+
interface ToolDefinition {
|
|
569
|
+
name: string; // camelCase, unique within your plugin
|
|
570
|
+
description: string; // Clear description for the AI model
|
|
571
|
+
schema: z.ZodObject<any>; // Zod schema for parameter validation
|
|
572
|
+
execute: (args: any, strapi: Core.Strapi, context?: ToolContext) => Promise<unknown>;
|
|
573
|
+
internal?: boolean; // If true, hidden from MCP (AI chat only)
|
|
574
|
+
publicSafe?: boolean; // If true, available in public/widget chat
|
|
575
|
+
}
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
#### Canonical Architecture Pattern
|
|
579
|
+
|
|
580
|
+
The recommended pattern is to define tools once in `server/src/tools/` and consume them from both the AI SDK service and MCP handlers:
|
|
581
|
+
|
|
582
|
+
```mermaid
|
|
583
|
+
flowchart TB
|
|
584
|
+
subgraph "Your Plugin"
|
|
585
|
+
T["server/src/tools/<br/>Canonical tool definitions<br/>(Zod schema + business logic)"]
|
|
586
|
+
|
|
587
|
+
subgraph "AI SDK Path"
|
|
588
|
+
S["services/ai-tools.ts<br/>getTools() → tools array"]
|
|
589
|
+
end
|
|
590
|
+
|
|
591
|
+
subgraph "MCP Path"
|
|
592
|
+
M["mcp/tools/*.ts<br/>Thin wrappers → MCP envelope"]
|
|
593
|
+
MS["mcp/server.ts"]
|
|
594
|
+
end
|
|
595
|
+
|
|
596
|
+
T --> S
|
|
597
|
+
T --> M
|
|
598
|
+
M --> MS
|
|
599
|
+
end
|
|
600
|
+
|
|
601
|
+
S -->|"Discovery"| SDK["AI SDK ToolRegistry"]
|
|
602
|
+
MS -->|"JSON-RPC"| Clients["Claude Desktop / Cursor"]
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
This eliminates duplication -- business logic lives in one place, and each consumer (AI SDK, MCP) uses a thin adapter.
|
|
606
|
+
|
|
607
|
+
#### Real-World Examples
|
|
608
|
+
|
|
609
|
+
Two plugins already use this pattern:
|
|
610
|
+
|
|
611
|
+
**[strapi-octolens-mentions-plugin](../strapi-octolens-mentions-plugin/)** -- Contributes 4 tools: `searchMentions` (BM25 relevance search), `listMentions`, `getMention`, `updateMention`
|
|
612
|
+
|
|
613
|
+
**[strapi-content-embeddings](../strapi-content-embeddings/)** -- Contributes 5 tools: `semanticSearch` (vector similarity), `ragQuery` (RAG), `listEmbeddings`, `getEmbedding`, `createEmbedding`
|
|
614
|
+
|
|
615
|
+
### Adding a Custom Tool (Without a Plugin)
|
|
469
616
|
|
|
470
617
|
**Option A: Inside the plugin** -- create files in `tools/definitions/` and `tool-logic/`, add to the `builtInTools` array.
|
|
471
618
|
|
|
@@ -578,7 +725,7 @@ Error response format:
|
|
|
578
725
|
server/src/
|
|
579
726
|
index.ts # Server entry point
|
|
580
727
|
register.ts # Plugin register lifecycle
|
|
581
|
-
bootstrap.ts # Initialize providers, tools, MCP
|
|
728
|
+
bootstrap.ts # Initialize providers, tools, MCP, plugin tool discovery
|
|
582
729
|
destroy.ts # Graceful shutdown
|
|
583
730
|
config/index.ts # Plugin config defaults
|
|
584
731
|
guardrails/ # Input safety middleware
|
|
@@ -650,6 +797,8 @@ STRAPI_TOKEN=your-api-token npm run test:guardrails
|
|
|
650
797
|
## Documentation
|
|
651
798
|
|
|
652
799
|
- [Architecture](./docs/architecture.md) -- full system architecture, data flows, extension guides
|
|
800
|
+
- [Plugin Tool Discovery](./docs/plugin-tool-discovery.md) -- cross-plugin tool discovery architecture and implementation
|
|
801
|
+
- [Tool Standardization Spec](./docs/tool-standardization-spec.md) -- canonical tool format, Zod-first vs MCP-native comparison, portability
|
|
653
802
|
- [Guardrails](./docs/guardrails.md) -- guardrail system, pattern lists, `beforeProcess` hook API
|
|
654
803
|
- [Sending Emails with Resend](./docs/sending-emails-with-resend.md) -- Resend setup, email tool, domain verification
|
|
655
804
|
|
|
@@ -6,7 +6,7 @@ const reactRouterDom = require("react-router-dom");
|
|
|
6
6
|
const designSystem = require("@strapi/design-system");
|
|
7
7
|
const react = require("react");
|
|
8
8
|
const styled = require("styled-components");
|
|
9
|
-
const index = require("./index-
|
|
9
|
+
const index = require("./index-BNk29VRc.js");
|
|
10
10
|
const icons = require("@strapi/icons");
|
|
11
11
|
const Markdown = require("react-markdown");
|
|
12
12
|
const remarkGfm = require("remark-gfm");
|
|
@@ -618,6 +618,267 @@ function MemoryPanel({ memories, open, onDelete }) {
|
|
|
618
618
|
] })
|
|
619
619
|
] });
|
|
620
620
|
}
|
|
621
|
+
const SCORE_LABELS = {
|
|
622
|
+
1: "Negligible",
|
|
623
|
+
2: "Minor",
|
|
624
|
+
3: "Moderate",
|
|
625
|
+
4: "Significant",
|
|
626
|
+
5: "Critical"
|
|
627
|
+
};
|
|
628
|
+
const Card = styled__default.default.div`
|
|
629
|
+
margin-top: 8px;
|
|
630
|
+
border: 1px solid #dcdce4;
|
|
631
|
+
border-radius: 8px;
|
|
632
|
+
padding: 14px 16px;
|
|
633
|
+
background: #fff;
|
|
634
|
+
font-size: 13px;
|
|
635
|
+
`;
|
|
636
|
+
const Title = styled__default.default.div`
|
|
637
|
+
font-weight: 700;
|
|
638
|
+
font-size: 14px;
|
|
639
|
+
color: #32324d;
|
|
640
|
+
margin-bottom: 2px;
|
|
641
|
+
`;
|
|
642
|
+
const Description = styled__default.default.div`
|
|
643
|
+
color: #8e8ea9;
|
|
644
|
+
font-size: 12px;
|
|
645
|
+
margin-bottom: 10px;
|
|
646
|
+
`;
|
|
647
|
+
const Row = styled__default.default.div`
|
|
648
|
+
display: flex;
|
|
649
|
+
align-items: center;
|
|
650
|
+
gap: 10px;
|
|
651
|
+
margin-bottom: 8px;
|
|
652
|
+
flex-wrap: wrap;
|
|
653
|
+
`;
|
|
654
|
+
const Label = styled__default.default.label`
|
|
655
|
+
font-size: 12px;
|
|
656
|
+
font-weight: 600;
|
|
657
|
+
color: #666687;
|
|
658
|
+
min-width: 80px;
|
|
659
|
+
`;
|
|
660
|
+
const Select = styled__default.default.select`
|
|
661
|
+
padding: 4px 8px;
|
|
662
|
+
border: 1px solid #dcdce4;
|
|
663
|
+
border-radius: 4px;
|
|
664
|
+
font-size: 12px;
|
|
665
|
+
background: #fff;
|
|
666
|
+
color: #32324d;
|
|
667
|
+
`;
|
|
668
|
+
const DateInput = styled__default.default.input`
|
|
669
|
+
padding: 4px 8px;
|
|
670
|
+
border: 1px solid #dcdce4;
|
|
671
|
+
border-radius: 4px;
|
|
672
|
+
font-size: 12px;
|
|
673
|
+
color: #32324d;
|
|
674
|
+
`;
|
|
675
|
+
const AsapButton = styled__default.default.button`
|
|
676
|
+
padding: 4px 10px;
|
|
677
|
+
border: none;
|
|
678
|
+
border-radius: 4px;
|
|
679
|
+
background: #4945ff;
|
|
680
|
+
color: #fff;
|
|
681
|
+
font-size: 11px;
|
|
682
|
+
font-weight: 600;
|
|
683
|
+
cursor: pointer;
|
|
684
|
+
|
|
685
|
+
&:hover {
|
|
686
|
+
background: #3b38e0;
|
|
687
|
+
}
|
|
688
|
+
`;
|
|
689
|
+
const ScorePreview = styled__default.default.div`
|
|
690
|
+
font-size: 12px;
|
|
691
|
+
color: #666687;
|
|
692
|
+
margin-bottom: 10px;
|
|
693
|
+
font-weight: 500;
|
|
694
|
+
`;
|
|
695
|
+
const CreateButton = styled__default.default.button`
|
|
696
|
+
padding: 8px 18px;
|
|
697
|
+
border: none;
|
|
698
|
+
border-radius: 4px;
|
|
699
|
+
background: #4945ff;
|
|
700
|
+
color: #fff;
|
|
701
|
+
font-size: 13px;
|
|
702
|
+
font-weight: 600;
|
|
703
|
+
cursor: pointer;
|
|
704
|
+
|
|
705
|
+
&:disabled {
|
|
706
|
+
background: #a5a5ba;
|
|
707
|
+
cursor: not-allowed;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
&:not(:disabled):hover {
|
|
711
|
+
background: #3b38e0;
|
|
712
|
+
}
|
|
713
|
+
`;
|
|
714
|
+
const SuccessBanner = styled__default.default.div`
|
|
715
|
+
margin-top: 8px;
|
|
716
|
+
border: 1px solid #c6f0c2;
|
|
717
|
+
border-radius: 8px;
|
|
718
|
+
padding: 14px 16px;
|
|
719
|
+
background: #eafbe7;
|
|
720
|
+
font-size: 13px;
|
|
721
|
+
color: #2f6846;
|
|
722
|
+
`;
|
|
723
|
+
const SuccessTitle = styled__default.default.div`
|
|
724
|
+
font-weight: 700;
|
|
725
|
+
margin-bottom: 4px;
|
|
726
|
+
`;
|
|
727
|
+
const TaskLink = styled__default.default(reactRouterDom.Link)`
|
|
728
|
+
color: #4945ff;
|
|
729
|
+
font-weight: 600;
|
|
730
|
+
text-decoration: none;
|
|
731
|
+
font-size: 12px;
|
|
732
|
+
|
|
733
|
+
&:hover {
|
|
734
|
+
text-decoration: underline;
|
|
735
|
+
}
|
|
736
|
+
`;
|
|
737
|
+
const ErrorText = styled__default.default.div`
|
|
738
|
+
color: #d02b20;
|
|
739
|
+
font-size: 12px;
|
|
740
|
+
margin-top: 4px;
|
|
741
|
+
`;
|
|
742
|
+
function TaskConfirmCard({ proposed }) {
|
|
743
|
+
const [consequence, setConsequence] = react.useState(null);
|
|
744
|
+
const [impact, setImpact] = react.useState(null);
|
|
745
|
+
const [dueDate, setDueDate] = react.useState(proposed.dueDate ?? "");
|
|
746
|
+
const [submitting, setSubmitting] = react.useState(false);
|
|
747
|
+
const [created, setCreated] = react.useState(null);
|
|
748
|
+
const [error, setError] = react.useState(null);
|
|
749
|
+
const score = consequence != null && impact != null ? consequence * impact : null;
|
|
750
|
+
async function handleCreate() {
|
|
751
|
+
if (consequence == null || impact == null) return;
|
|
752
|
+
setSubmitting(true);
|
|
753
|
+
setError(null);
|
|
754
|
+
try {
|
|
755
|
+
const token = getToken();
|
|
756
|
+
const backend = getBackendURL();
|
|
757
|
+
const res = await fetch(`${backend}/ai-sdk/tasks`, {
|
|
758
|
+
method: "POST",
|
|
759
|
+
headers: {
|
|
760
|
+
"Content-Type": "application/json",
|
|
761
|
+
...token ? { Authorization: `Bearer ${token}` } : {}
|
|
762
|
+
},
|
|
763
|
+
body: JSON.stringify({
|
|
764
|
+
title: proposed.title,
|
|
765
|
+
description: proposed.description,
|
|
766
|
+
content: proposed.content,
|
|
767
|
+
priority: proposed.priority,
|
|
768
|
+
consequence,
|
|
769
|
+
impact,
|
|
770
|
+
dueDate: dueDate || void 0
|
|
771
|
+
})
|
|
772
|
+
});
|
|
773
|
+
if (!res.ok) {
|
|
774
|
+
const body = await res.json().catch(() => ({}));
|
|
775
|
+
throw new Error(body.error ?? `HTTP ${res.status}`);
|
|
776
|
+
}
|
|
777
|
+
const { data } = await res.json();
|
|
778
|
+
setCreated({
|
|
779
|
+
documentId: data.documentId,
|
|
780
|
+
title: data.title,
|
|
781
|
+
consequence: data.consequence,
|
|
782
|
+
impact: data.impact,
|
|
783
|
+
priority: data.priority
|
|
784
|
+
});
|
|
785
|
+
} catch (err) {
|
|
786
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
787
|
+
} finally {
|
|
788
|
+
setSubmitting(false);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
if (created) {
|
|
792
|
+
const s = created.consequence * created.impact;
|
|
793
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(SuccessBanner, { children: [
|
|
794
|
+
/* @__PURE__ */ jsxRuntime.jsxs(SuccessTitle, { children: [
|
|
795
|
+
"Task created: ",
|
|
796
|
+
created.title
|
|
797
|
+
] }),
|
|
798
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
799
|
+
"Score: ",
|
|
800
|
+
created.consequence,
|
|
801
|
+
" x ",
|
|
802
|
+
created.impact,
|
|
803
|
+
" = ",
|
|
804
|
+
s,
|
|
805
|
+
" · Priority: ",
|
|
806
|
+
created.priority
|
|
807
|
+
] }),
|
|
808
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { style: { marginTop: 6 }, children: /* @__PURE__ */ jsxRuntime.jsx(TaskLink, { to: `/content-manager/collection-types/plugin::ai-sdk.task/${created.documentId}`, children: "Open in Content Manager" }) })
|
|
809
|
+
] });
|
|
810
|
+
}
|
|
811
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
812
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(Card, { children: [
|
|
813
|
+
/* @__PURE__ */ jsxRuntime.jsx(Title, { children: proposed.title }),
|
|
814
|
+
proposed.description && /* @__PURE__ */ jsxRuntime.jsx(Description, { children: proposed.description }),
|
|
815
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Row, { children: [
|
|
816
|
+
/* @__PURE__ */ jsxRuntime.jsx(Label, { children: "Consequence" }),
|
|
817
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
818
|
+
Select,
|
|
819
|
+
{
|
|
820
|
+
value: consequence ?? "",
|
|
821
|
+
onChange: (e) => setConsequence(e.target.value ? Number(e.target.value) : null),
|
|
822
|
+
children: [
|
|
823
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "Select…" }),
|
|
824
|
+
[1, 2, 3, 4, 5].map((n) => /* @__PURE__ */ jsxRuntime.jsxs("option", { value: n, children: [
|
|
825
|
+
n,
|
|
826
|
+
" — ",
|
|
827
|
+
SCORE_LABELS[n]
|
|
828
|
+
] }, n))
|
|
829
|
+
]
|
|
830
|
+
}
|
|
831
|
+
)
|
|
832
|
+
] }),
|
|
833
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Row, { children: [
|
|
834
|
+
/* @__PURE__ */ jsxRuntime.jsx(Label, { children: "Impact" }),
|
|
835
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
836
|
+
Select,
|
|
837
|
+
{
|
|
838
|
+
value: impact ?? "",
|
|
839
|
+
onChange: (e) => setImpact(e.target.value ? Number(e.target.value) : null),
|
|
840
|
+
children: [
|
|
841
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "Select…" }),
|
|
842
|
+
[1, 2, 3, 4, 5].map((n) => /* @__PURE__ */ jsxRuntime.jsxs("option", { value: n, children: [
|
|
843
|
+
n,
|
|
844
|
+
" — ",
|
|
845
|
+
SCORE_LABELS[n]
|
|
846
|
+
] }, n))
|
|
847
|
+
]
|
|
848
|
+
}
|
|
849
|
+
)
|
|
850
|
+
] }),
|
|
851
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Row, { children: [
|
|
852
|
+
/* @__PURE__ */ jsxRuntime.jsx(Label, { children: "Due date" }),
|
|
853
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
854
|
+
DateInput,
|
|
855
|
+
{
|
|
856
|
+
type: "date",
|
|
857
|
+
value: dueDate,
|
|
858
|
+
onChange: (e) => setDueDate(e.target.value)
|
|
859
|
+
}
|
|
860
|
+
),
|
|
861
|
+
/* @__PURE__ */ jsxRuntime.jsx(AsapButton, { type: "button", onClick: () => setDueDate(today), children: "ASAP" })
|
|
862
|
+
] }),
|
|
863
|
+
score != null && /* @__PURE__ */ jsxRuntime.jsxs(ScorePreview, { children: [
|
|
864
|
+
"Score: ",
|
|
865
|
+
consequence,
|
|
866
|
+
" x ",
|
|
867
|
+
impact,
|
|
868
|
+
" = ",
|
|
869
|
+
score
|
|
870
|
+
] }),
|
|
871
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
872
|
+
CreateButton,
|
|
873
|
+
{
|
|
874
|
+
disabled: consequence == null || impact == null || submitting,
|
|
875
|
+
onClick: handleCreate,
|
|
876
|
+
children: submitting ? "Creating…" : "Create Task"
|
|
877
|
+
}
|
|
878
|
+
),
|
|
879
|
+
error && /* @__PURE__ */ jsxRuntime.jsx(ErrorText, { children: error })
|
|
880
|
+
] });
|
|
881
|
+
}
|
|
621
882
|
function buildContentManagerUrl(contentType, documentId) {
|
|
622
883
|
const base = `/content-manager/collection-types/${contentType}`;
|
|
623
884
|
return documentId ? `${base}/${documentId}` : base;
|
|
@@ -663,6 +924,31 @@ function extractContentLinks(toolCall) {
|
|
|
663
924
|
}
|
|
664
925
|
return [];
|
|
665
926
|
}
|
|
927
|
+
const TASK_CONTENT_TYPE = "plugin::ai-sdk.task";
|
|
928
|
+
function extractTaskLinks(toolCall) {
|
|
929
|
+
if (toolCall.toolName !== "manageTask" || toolCall.output == null) return [];
|
|
930
|
+
const output = toolCall.output;
|
|
931
|
+
if (!output.success) return [];
|
|
932
|
+
const data = output.data;
|
|
933
|
+
if (!data) return [];
|
|
934
|
+
if (!Array.isArray(data)) {
|
|
935
|
+
const docId = data.documentId;
|
|
936
|
+
const title = data.title || docId;
|
|
937
|
+
if (docId && title) {
|
|
938
|
+
return [{ label: title, to: buildContentManagerUrl(TASK_CONTENT_TYPE, docId) }];
|
|
939
|
+
}
|
|
940
|
+
return [];
|
|
941
|
+
}
|
|
942
|
+
const links = [];
|
|
943
|
+
for (const task of data.slice(0, 5)) {
|
|
944
|
+
const docId = task.documentId;
|
|
945
|
+
const title = task.title || docId;
|
|
946
|
+
if (docId && title) {
|
|
947
|
+
links.push({ label: title, to: buildContentManagerUrl(TASK_CONTENT_TYPE, docId) });
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
return links;
|
|
951
|
+
}
|
|
666
952
|
const ToolCallBox = styled__default.default.div`
|
|
667
953
|
margin-top: 8px;
|
|
668
954
|
border: 1px solid #dcdce4;
|
|
@@ -747,6 +1033,13 @@ const HIDDEN_TOOLS = /* @__PURE__ */ new Set();
|
|
|
747
1033
|
function ToolCallDisplay({ toolCall }) {
|
|
748
1034
|
const [expanded, setExpanded] = react.useState(false);
|
|
749
1035
|
const contentLinks = extractContentLinks(toolCall);
|
|
1036
|
+
const taskLinks = extractTaskLinks(toolCall);
|
|
1037
|
+
if (toolCall.toolName === "manageTask" && toolCall.output != null) {
|
|
1038
|
+
const output = toolCall.output;
|
|
1039
|
+
if (output.status === "pending_confirmation" && output.proposed) {
|
|
1040
|
+
return /* @__PURE__ */ jsxRuntime.jsx(TaskConfirmCard, { proposed: output.proposed });
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
750
1043
|
return /* @__PURE__ */ jsxRuntime.jsxs(ToolCallBox, { children: [
|
|
751
1044
|
/* @__PURE__ */ jsxRuntime.jsxs(ToolCallHeader, { onClick: () => setExpanded(!expanded), children: [
|
|
752
1045
|
/* @__PURE__ */ jsxRuntime.jsx("span", { children: expanded ? "▼" : "▶" }),
|
|
@@ -756,7 +1049,10 @@ function ToolCallDisplay({ toolCall }) {
|
|
|
756
1049
|
] }),
|
|
757
1050
|
toolCall.output === void 0 ? /* @__PURE__ */ jsxRuntime.jsx(Spinner, {}) : /* @__PURE__ */ jsxRuntime.jsx("span", { style: { marginLeft: "auto", fontWeight: 400, opacity: 0.6 }, children: "completed" })
|
|
758
1051
|
] }),
|
|
759
|
-
contentLinks.length > 0
|
|
1052
|
+
(contentLinks.length > 0 || taskLinks.length > 0) && /* @__PURE__ */ jsxRuntime.jsxs(ContentLinksRow, { children: [
|
|
1053
|
+
contentLinks.map((link) => /* @__PURE__ */ jsxRuntime.jsx(ContentLinkChip, { to: link.to, children: link.label }, link.to)),
|
|
1054
|
+
taskLinks.map((link) => /* @__PURE__ */ jsxRuntime.jsx(ContentLinkChip, { to: link.to, children: link.label }, link.to))
|
|
1055
|
+
] }),
|
|
760
1056
|
expanded && /* @__PURE__ */ jsxRuntime.jsx(ToolCallContent, { children: toolCall.output === void 0 ? "Waiting for result..." : JSON.stringify(toolCall.output, null, 2) })
|
|
761
1057
|
] });
|
|
762
1058
|
}
|