mirror of
https://github.com/danielmiessler/Fabric.git
synced 2026-01-17 02:07:56 -05:00
Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e3c2723988 | ||
|
|
198b5af12c | ||
|
|
c66aad556b | ||
|
|
9f8a2531ca | ||
|
|
a2370a0e3b | ||
|
|
1af6418486 | ||
|
|
f50a7568d1 | ||
|
|
83d9d0b336 | ||
|
|
52db4f1961 | ||
|
|
36a22aa432 | ||
|
|
487199394b | ||
|
|
3a1d7757fb | ||
|
|
d98ad5290c | ||
|
|
a6fc9a0ef0 | ||
|
|
fd5530d38b | ||
|
|
8ec09be550 | ||
|
|
6bac79703e | ||
|
|
24afe127f1 | ||
|
|
c26a56a368 | ||
|
|
84470eac3f | ||
|
|
a2058ae26e | ||
|
|
74250bbcbd | ||
|
|
a593e83d9f | ||
|
|
62e6812c7f | ||
|
|
7e7ab9e5f2 | ||
|
|
6a18913a23 |
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@@ -117,6 +117,7 @@
|
||||
"listvendors",
|
||||
"lmstudio",
|
||||
"Makefiles",
|
||||
"Mammouth",
|
||||
"markmap",
|
||||
"matplotlib",
|
||||
"mattn",
|
||||
@@ -157,6 +158,7 @@
|
||||
"pyperclip",
|
||||
"qwen",
|
||||
"readystream",
|
||||
"reflexion",
|
||||
"restapi",
|
||||
"rmextension",
|
||||
"Sadachbia",
|
||||
|
||||
41
CHANGELOG.md
41
CHANGELOG.md
@@ -1,5 +1,46 @@
|
||||
# Changelog
|
||||
|
||||
## v1.4.380 (2026-01-16)
|
||||
|
||||
### PR [#1936](https://github.com/danielmiessler/Fabric/pull/1936) by [ksylvan](https://github.com/ksylvan): New Vendor: Microsoft Copilot
|
||||
|
||||
- Add Microsoft 365 Copilot integration as a new AI vendor with OAuth2 authentication for delegated user permissions
|
||||
- Enable querying of Microsoft 365 data including emails, documents, and chats with both synchronous and streaming response support
|
||||
- Provide comprehensive setup instructions for Azure AD app registration and detail licensing, technical, and permission requirements
|
||||
- Add troubleshooting steps for common authentication and API errors with current API limitations documentation
|
||||
- Fix SendStream interface to use domain.StreamUpdate instead of chan string to match current Vendor interface requirements
|
||||
|
||||
## v1.4.379 (2026-01-15)
|
||||
|
||||
### PR [#1935](https://github.com/danielmiessler/Fabric/pull/1935) by [dependabot](https://github.com/apps/dependabot): chore(deps): bump the npm_and_yarn group across 1 directory with 2 updates
|
||||
|
||||
- Updated @sveltejs/kit from version 2.21.1 to 2.49.5
|
||||
- Updated devalue dependency from version 5.3.2 to 5.6.2
|
||||
|
||||
## v1.4.378 (2026-01-14)
|
||||
|
||||
### PR [#1933](https://github.com/danielmiessler/Fabric/pull/1933) by [ksylvan](https://github.com/ksylvan): Add DigitalOcean Gradient AI support
|
||||
|
||||
- Feat: add DigitalOcean Gradient AI Agents as a new vendor
|
||||
- Add DigitalOcean as a new AI provider in plugin registry
|
||||
- Implement DigitalOcean client with OpenAI-compatible inference endpoint
|
||||
- Support model access key authentication for inference requests
|
||||
- Add optional control plane token for model discovery
|
||||
|
||||
### Direct commits
|
||||
|
||||
- Chore: Update README with a links to other docs
|
||||
|
||||
## v1.4.377 (2026-01-12)
|
||||
|
||||
### PR [#1929](https://github.com/danielmiessler/Fabric/pull/1929) by [ksylvan](https://github.com/ksylvan): Add Mammouth as new OpenAI-compatible AI provider
|
||||
|
||||
- Feat: add Mammouth as new OpenAI-compatible AI provider
|
||||
- Add Mammouth provider configuration with API base URL
|
||||
- Configure Mammouth to use standard OpenAI-compatible interface
|
||||
- Disable Responses API implementation for Mammouth provider
|
||||
- Add "Mammouth" to VSCode spell check dictionary
|
||||
|
||||
## v1.4.376 (2026-01-12)
|
||||
|
||||
### PR [#1928](https://github.com/danielmiessler/Fabric/pull/1928) by [ksylvan](https://github.com/ksylvan): Eliminate repetitive boilerplate across eight vendor implementations
|
||||
|
||||
@@ -63,6 +63,9 @@ Fabric organizes prompts by real-world task, allowing people to create, collect,
|
||||
|
||||
## Updates
|
||||
|
||||
For a deep dive into Fabric and its internals, read the documentation in the [docs folder](https://github.com/danielmiessler/Fabric/tree/main/docs). There is
|
||||
also the extremely useful and regularly updated [DeepWiki](https://deepwiki.com/danielmiessler/Fabric) for Fabric.
|
||||
|
||||
<details>
|
||||
<summary>Click to view recent updates</summary>
|
||||
|
||||
@@ -74,6 +77,8 @@ Below are the **new features and capabilities** we've added (newest first):
|
||||
|
||||
### Recent Major Features
|
||||
|
||||
- [v1.4.380](https://github.com/danielmiessler/fabric/releases/tag/v1.4.380) (Jan 15, 2026) — **Microsoft 365 Copilot Integration**: Added support for corporate Microsoft 365 Copilot, enabling enterprise users to leverage AI grounded in their organization's Microsoft 365 data (emails, documents, meetings.
|
||||
- [v1.4.378](https://github.com/danielmiessler/fabric/releases/tag/v1.4.378) (Jan 14, 2026) — **Digital Ocean GenAI Support**: Added support for Digital Ocean GenAI, along with a [guide for how to use it](./docs/DigitalOcean-Agents-Setup.md).
|
||||
- [v1.4.356](https://github.com/danielmiessler/fabric/releases/tag/v1.4.356) (Dec 22, 2025) — **Complete Internationalization**: Full i18n support for setup prompts across all 10 languages with intelligent environment variable handling—making Fabric truly accessible worldwide while maintaining configuration consistency.
|
||||
- [v1.4.350](https://github.com/danielmiessler/fabric/releases/tag/v1.4.350) (Dec 18, 2025) — **Interactive API Documentation**: Adds Swagger/OpenAPI UI at `/swagger/index.html` with comprehensive REST API documentation, enhanced developer guides, and improved endpoint discoverability for easier integration.
|
||||
- [v1.4.338](https://github.com/danielmiessler/fabric/releases/tag/v1.4.338) (Dec 4, 2025) — Add Abacus vendor support for Chat-LLM
|
||||
@@ -196,6 +201,7 @@ Keep in mind that many of these were recorded when Fabric was Python-based, so r
|
||||
- [Meta](#meta)
|
||||
- [Primary contributors](#primary-contributors)
|
||||
- [Contributors](#contributors)
|
||||
- [💜 Support This Project](#-support-this-project)
|
||||
|
||||
<br />
|
||||
|
||||
@@ -376,6 +382,7 @@ Fabric supports a wide range of AI providers:
|
||||
- AIML
|
||||
- Cerebras
|
||||
- DeepSeek
|
||||
- DigitalOcean
|
||||
- GitHub Models
|
||||
- GrokAI
|
||||
- Groq
|
||||
@@ -1098,6 +1105,6 @@ Made with [contrib.rocks](https://contrib.rocks).
|
||||
|
||||
<img src="https://img.shields.io/badge/Sponsor-❤️-EA4AAA?style=for-the-badge&logo=github-sponsors&logoColor=white" alt="Sponsor">
|
||||
|
||||
**I spend hundreds of hours a year on open source. If you'd like to help support this project, you can sponsor me [here](https://github.com/sponsors/danielmiessler). 🙏🏼**
|
||||
**I spend hundreds of hours a year on open source. If you'd like to help support this project, you can [sponsor me here](https://github.com/sponsors/danielmiessler). 🙏🏼**
|
||||
|
||||
</div>
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
package main
|
||||
|
||||
var version = "v1.4.376"
|
||||
var version = "v1.4.380"
|
||||
|
||||
Binary file not shown.
55
docs/DigitalOcean-Agents-Setup.md
Normal file
55
docs/DigitalOcean-Agents-Setup.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# DigitalOcean Gradient AI Agents
|
||||
|
||||
Fabric can talk to DigitalOcean Gradient™ AI Agents by using DigitalOcean's OpenAI-compatible
|
||||
inference endpoint. You provide a **model access key** for inference plus an optional **DigitalOcean
|
||||
API token** for model discovery.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. Create or locate a Gradient AI Agent in the DigitalOcean control panel.
|
||||
2. Create a **model access key** for inference (this is not the same as your DigitalOcean API token).
|
||||
3. (Optional) Keep a DigitalOcean API token handy if you want `fabric --listmodels` to query the
|
||||
control plane for available models.
|
||||
|
||||
The official walkthrough for creating and using agents is here:
|
||||
<https://docs.digitalocean.com/products/gradient-ai-platform/how-to/use-agents/>
|
||||
|
||||
## Environment variables
|
||||
|
||||
Set the following environment variables before running `fabric --setup`:
|
||||
|
||||
```bash
|
||||
# Required: model access key for inference
|
||||
export DIGITALOCEAN_INFERENCE_KEY="your-model-access-key"
|
||||
|
||||
# Optional: control-plane token for model listing
|
||||
export DIGITALOCEAN_TOKEN="your-digitalocean-api-token"
|
||||
|
||||
# Optional: override the default inference base URL
|
||||
export DIGITALOCEAN_INFERENCE_BASE_URL="https://inference.do-ai.run/v1"
|
||||
```
|
||||
|
||||
If you need a region-specific inference URL, you can retrieve it from the GenAI regions API:
|
||||
|
||||
```bash
|
||||
curl -H "Authorization: Bearer $DIGITALOCEAN_TOKEN" \
|
||||
"https://api.digitalocean.com/v2/gen-ai/regions"
|
||||
```
|
||||
|
||||
## Fabric setup
|
||||
|
||||
Run setup and select the DigitalOcean vendor:
|
||||
|
||||
```bash
|
||||
fabric --setup
|
||||
```
|
||||
|
||||
Then list models (requires `DIGITALOCEAN_TOKEN`) and pick the inference name:
|
||||
|
||||
```bash
|
||||
fabric --listmodels
|
||||
fabric --vendor DigitalOcean --model <inference_name> --pattern summarize
|
||||
```
|
||||
|
||||
If you skip `DIGITALOCEAN_TOKEN`, you can still use Fabric by supplying the model name directly
|
||||
based on the agent or model you created in DigitalOcean.
|
||||
449
docs/Microsoft-365-Copilot-Setup.md
Normal file
449
docs/Microsoft-365-Copilot-Setup.md
Normal file
@@ -0,0 +1,449 @@
|
||||
# Microsoft 365 Copilot Setup Guide for Fabric
|
||||
|
||||
This guide walks you through setting up and using Microsoft 365 Copilot with Fabric CLI. Microsoft 365 Copilot provides AI capabilities grounded in your organization's Microsoft 365 data, including emails, documents, meetings, and more.
|
||||
|
||||
> NOTE: As per the conversation in [discussion 1853](https://github.com/danielmiessler/Fabric/discussions/1853) - enterprise users with restrictive consent policies will probably need their IT admin to either create an app registration with the required permissions, or grant admin consent for an existing app like Graph Explorer.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [What is Microsoft 365 Copilot?](#what-is-microsoft-365-copilot)
|
||||
- [Requirements](#requirements)
|
||||
- [Azure AD App Registration](#azure-ad-app-registration)
|
||||
- [Obtaining Access Tokens](#obtaining-access-tokens)
|
||||
- [Configuring Fabric for Copilot](#configuring-fabric-for-copilot)
|
||||
- [Testing Your Setup](#testing-your-setup)
|
||||
- [Usage Examples](#usage-examples)
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
- [API Limitations](#api-limitations)
|
||||
|
||||
---
|
||||
|
||||
## What is Microsoft 365 Copilot?
|
||||
|
||||
**Microsoft 365 Copilot** is an AI-powered assistant that works across Microsoft 365 applications. When integrated with Fabric, it allows you to:
|
||||
|
||||
- **Query your organization's data**: Ask questions about emails, documents, calendars, and Teams chats
|
||||
- **Grounded responses**: Get AI responses that are based on your actual Microsoft 365 content
|
||||
- **Enterprise compliance**: All interactions respect your organization's security policies, permissions, and sensitivity labels
|
||||
|
||||
### Why Use Microsoft 365 Copilot with Fabric?
|
||||
|
||||
- **Enterprise-ready**: Built for organizations with compliance requirements
|
||||
- **Data grounding**: Responses are based on your actual organizational data
|
||||
- **Unified access**: Single integration for all Microsoft 365 content
|
||||
- **Security**: Respects existing permissions and access controls
|
||||
|
||||
---
|
||||
|
||||
## Requirements
|
||||
|
||||
Before you begin, ensure you have:
|
||||
|
||||
### Licensing Requirements
|
||||
|
||||
1. **Microsoft 365 Copilot License**: Required for each user accessing the API
|
||||
2. **Microsoft 365 E3 or E5 Subscription** (or equivalent): Foundation for Copilot services
|
||||
|
||||
### Technical Requirements
|
||||
|
||||
1. **Azure AD Tenant**: Your organization's Azure Active Directory
|
||||
2. **Azure AD App Registration**: To authenticate with Microsoft Graph
|
||||
3. **Delegated Permissions**: The Chat API only supports delegated (user) permissions, not application permissions
|
||||
|
||||
### Permissions Required
|
||||
|
||||
The following Microsoft Graph permissions are needed:
|
||||
|
||||
| Permission | Type | Description |
|
||||
|------------|------|-------------|
|
||||
| `Sites.Read.All` | Delegated | Read SharePoint sites |
|
||||
| `Mail.Read` | Delegated | Read user's email |
|
||||
| `People.Read.All` | Delegated | Read organization's people directory |
|
||||
| `OnlineMeetingTranscript.Read.All` | Delegated | Read meeting transcripts |
|
||||
| `Chat.Read` | Delegated | Read Teams chat messages |
|
||||
| `ChannelMessage.Read.All` | Delegated | Read Teams channel messages |
|
||||
| `ExternalItem.Read.All` | Delegated | Read external content connectors |
|
||||
|
||||
---
|
||||
|
||||
## Azure AD App Registration
|
||||
|
||||
### Step 1: Create the App Registration
|
||||
|
||||
1. Go to the [Azure Portal](https://portal.azure.com)
|
||||
2. Navigate to **Azure Active Directory** > **App registrations**
|
||||
3. Click **New registration**
|
||||
4. Configure the application:
|
||||
- **Name**: `Fabric CLI - Copilot`
|
||||
- **Supported account types**: Select "Accounts in this organizational directory only"
|
||||
- **Redirect URI**: Select "Public client/native (mobile & desktop)" and enter `http://localhost:8400/callback`
|
||||
5. Click **Register**
|
||||
|
||||
### Step 2: Note Your Application IDs
|
||||
|
||||
After registration, note these values from the **Overview** page:
|
||||
|
||||
- **Application (client) ID**: e.g., `12345678-1234-1234-1234-123456789abc`
|
||||
- **Directory (tenant) ID**: e.g., `abcdef12-3456-7890-abcd-ef1234567890`
|
||||
|
||||
### Step 3: Configure API Permissions
|
||||
|
||||
1. Go to **API permissions** in your app registration
|
||||
2. Click **Add a permission**
|
||||
3. Select **Microsoft Graph**
|
||||
4. Select **Delegated permissions**
|
||||
5. Add the following permissions:
|
||||
- `Sites.Read.All`
|
||||
- `Mail.Read`
|
||||
- `People.Read.All`
|
||||
- `OnlineMeetingTranscript.Read.All`
|
||||
- `Chat.Read`
|
||||
- `ChannelMessage.Read.All`
|
||||
- `ExternalItem.Read.All`
|
||||
- `offline_access` (for refresh tokens)
|
||||
6. Click **Add permissions**
|
||||
7. **Important**: Click **Grant admin consent for [Your Organization]** (requires admin privileges)
|
||||
|
||||
### Step 4: Configure Authentication (Optional - For Confidential Clients)
|
||||
|
||||
If you want to use client credentials for token refresh:
|
||||
|
||||
1. Go to **Certificates & secrets**
|
||||
2. Click **New client secret**
|
||||
3. Add a description and select an expiration
|
||||
4. Click **Add**
|
||||
5. **Important**: Copy the secret value immediately (it won't be shown again)
|
||||
|
||||
---
|
||||
|
||||
## Obtaining Access Tokens
|
||||
|
||||
The Microsoft 365 Copilot Chat API requires **delegated permissions**, meaning you need to authenticate as a user. There are several ways to obtain tokens:
|
||||
|
||||
### Option 1: Using Azure CLI (Recommended for Development)
|
||||
|
||||
```bash
|
||||
# Install Azure CLI if not already installed
|
||||
# https://docs.microsoft.com/en-us/cli/azure/install-azure-cli
|
||||
|
||||
# Login with your work account
|
||||
az login --tenant YOUR_TENANT_ID
|
||||
|
||||
# Get an access token for Microsoft Graph
|
||||
az account get-access-token --resource https://graph.microsoft.com --query accessToken -o tsv
|
||||
```
|
||||
|
||||
### Option 2: Using Device Code Flow
|
||||
|
||||
For headless environments or when browser authentication isn't possible:
|
||||
|
||||
```bash
|
||||
# Request device code
|
||||
curl -X POST "https://login.microsoftonline.com/YOUR_TENANT_ID/oauth2/v2.0/devicecode" \
|
||||
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||
-d "client_id=YOUR_CLIENT_ID&scope=Sites.Read.All Mail.Read People.Read.All OnlineMeetingTranscript.Read.All Chat.Read ChannelMessage.Read.All ExternalItem.Read.All offline_access"
|
||||
|
||||
# Follow the instructions to authenticate in a browser
|
||||
# Then poll for the token using the device_code from the response
|
||||
```
|
||||
|
||||
### Option 3: Using Microsoft Graph Explorer (For Testing)
|
||||
|
||||
1. Go to [Microsoft Graph Explorer](https://developer.microsoft.com/en-us/graph/graph-explorer)
|
||||
2. Sign in with your work account
|
||||
3. Click the gear icon > "Select permissions"
|
||||
4. Enable the required permissions
|
||||
5. Use the access token from the "Access token" tab
|
||||
|
||||
### Option 4: Using MSAL Libraries
|
||||
|
||||
For production applications, use Microsoft Authentication Library (MSAL):
|
||||
|
||||
```go
|
||||
// Example using Azure Identity SDK for Go
|
||||
import "github.com/Azure/azure-sdk-for-go/sdk/azidentity"
|
||||
|
||||
cred, err := azidentity.NewInteractiveBrowserCredential(&azidentity.InteractiveBrowserCredentialOptions{
|
||||
TenantID: "YOUR_TENANT_ID",
|
||||
ClientID: "YOUR_CLIENT_ID",
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuring Fabric for Copilot
|
||||
|
||||
### Method 1: Using Fabric Setup (Recommended)
|
||||
|
||||
1. **Run Fabric Setup:**
|
||||
|
||||
```bash
|
||||
fabric --setup
|
||||
```
|
||||
|
||||
2. **Select Copilot from the menu:**
|
||||
- Find `Copilot` in the numbered list
|
||||
- Enter the number and press Enter
|
||||
|
||||
3. **Enter Configuration Values:**
|
||||
|
||||
```
|
||||
[Copilot] Enter your Azure AD Tenant ID:
|
||||
> contoso.onmicrosoft.com
|
||||
|
||||
[Copilot] Enter your Azure AD Application (Client) ID:
|
||||
> 12345678-1234-1234-1234-123456789abc
|
||||
|
||||
[Copilot] Enter your Azure AD Client Secret (optional):
|
||||
> (press Enter to skip, or enter secret for token refresh)
|
||||
|
||||
[Copilot] Enter a pre-obtained OAuth2 Access Token:
|
||||
> eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIs...
|
||||
|
||||
[Copilot] Enter a pre-obtained OAuth2 Refresh Token (optional):
|
||||
> (press Enter to skip, or enter refresh token)
|
||||
|
||||
[Copilot] Enter your timezone:
|
||||
> America/New_York
|
||||
```
|
||||
|
||||
### Method 2: Manual Configuration
|
||||
|
||||
Edit `~/.config/fabric/.env`:
|
||||
|
||||
```bash
|
||||
# Microsoft 365 Copilot Configuration
|
||||
COPILOT_TENANT_ID=contoso.onmicrosoft.com
|
||||
COPILOT_CLIENT_ID=12345678-1234-1234-1234-123456789abc
|
||||
COPILOT_CLIENT_SECRET=your-client-secret-if-applicable
|
||||
COPILOT_ACCESS_TOKEN=eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIs...
|
||||
COPILOT_REFRESH_TOKEN=your-refresh-token-if-available
|
||||
COPILOT_API_BASE_URL=https://graph.microsoft.com/beta/copilot
|
||||
COPILOT_TIME_ZONE=America/New_York
|
||||
```
|
||||
|
||||
### Verify Configuration
|
||||
|
||||
```bash
|
||||
fabric --listmodels | grep -i copilot
|
||||
```
|
||||
|
||||
Expected output:
|
||||
|
||||
```
|
||||
[X] Copilot|microsoft-365-copilot
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing Your Setup
|
||||
|
||||
### Basic Test
|
||||
|
||||
```bash
|
||||
# Simple query
|
||||
echo "What meetings do I have tomorrow?" | fabric --vendor Copilot
|
||||
|
||||
# With explicit model (though there's only one)
|
||||
echo "Summarize my recent emails" | fabric --vendor Copilot --model microsoft-365-copilot
|
||||
```
|
||||
|
||||
### Test with Streaming
|
||||
|
||||
```bash
|
||||
echo "What are the key points from my last team meeting?" | \
|
||||
fabric --vendor Copilot --stream
|
||||
```
|
||||
|
||||
### Test with Patterns
|
||||
|
||||
```bash
|
||||
# Use a pattern with Copilot
|
||||
echo "Find action items from my recent emails" | \
|
||||
fabric --pattern extract_wisdom --vendor Copilot
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Query Calendar
|
||||
|
||||
```bash
|
||||
echo "What meetings do I have scheduled for next week?" | fabric --vendor Copilot
|
||||
```
|
||||
|
||||
### Summarize Emails
|
||||
|
||||
```bash
|
||||
echo "Summarize the emails I received yesterday from my manager" | fabric --vendor Copilot
|
||||
```
|
||||
|
||||
### Search Documents
|
||||
|
||||
```bash
|
||||
echo "Find documents about the Q4 budget proposal" | fabric --vendor Copilot
|
||||
```
|
||||
|
||||
### Team Collaboration
|
||||
|
||||
```bash
|
||||
echo "What were the main discussion points in the engineering standup channel this week?" | fabric --vendor Copilot
|
||||
```
|
||||
|
||||
### Meeting Insights
|
||||
|
||||
```bash
|
||||
echo "What action items came out of the project review meeting on Monday?" | fabric --vendor Copilot
|
||||
```
|
||||
|
||||
### Using with Fabric Patterns
|
||||
|
||||
```bash
|
||||
# Extract wisdom from organizational content
|
||||
echo "What are the key decisions from last month's leadership updates?" | \
|
||||
fabric --pattern extract_wisdom --vendor Copilot
|
||||
|
||||
# Summarize with a specific pattern
|
||||
echo "Summarize the HR policy document about remote work" | \
|
||||
fabric --pattern summarize --vendor Copilot
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Error: "Authentication failed" or "401 Unauthorized"
|
||||
|
||||
**Cause**: Invalid or expired access token
|
||||
|
||||
**Solutions**:
|
||||
|
||||
1. Obtain a fresh access token:
|
||||
|
||||
```bash
|
||||
az account get-access-token --resource https://graph.microsoft.com --query accessToken -o tsv
|
||||
```
|
||||
|
||||
2. Update your configuration:
|
||||
|
||||
```bash
|
||||
fabric --setup
|
||||
# Select Copilot and enter the new token
|
||||
```
|
||||
|
||||
3. Check token hasn't expired (tokens typically expire after 1 hour)
|
||||
|
||||
### Error: "403 Forbidden"
|
||||
|
||||
**Cause**: Missing permissions or admin consent not granted
|
||||
|
||||
**Solutions**:
|
||||
|
||||
1. Verify all required permissions are added to your app registration
|
||||
2. Ensure admin consent has been granted
|
||||
3. Check that your user has a Microsoft 365 Copilot license
|
||||
|
||||
### Error: "Failed to create conversation"
|
||||
|
||||
**Cause**: API access issues or service unavailable
|
||||
|
||||
**Solutions**:
|
||||
|
||||
1. Verify the API base URL is correct: `https://graph.microsoft.com/beta/copilot`
|
||||
2. Check Microsoft 365 service status
|
||||
3. Ensure your organization has Copilot enabled
|
||||
|
||||
### Error: "Rate limit exceeded"
|
||||
|
||||
**Cause**: Too many requests
|
||||
|
||||
**Solutions**:
|
||||
|
||||
1. Wait a few minutes before retrying
|
||||
2. Reduce request frequency
|
||||
3. Consider batching queries
|
||||
|
||||
### Token Refresh Not Working
|
||||
|
||||
**Cause**: Missing client secret or refresh token
|
||||
|
||||
**Solutions**:
|
||||
|
||||
1. Ensure you have both a refresh token and client secret configured
|
||||
2. Re-authenticate to get new tokens
|
||||
3. Check that your app registration supports refresh tokens (public client)
|
||||
|
||||
---
|
||||
|
||||
## API Limitations
|
||||
|
||||
### Current Limitations
|
||||
|
||||
1. **Preview API**: The Chat API is currently in preview (`/beta` endpoint) and subject to change
|
||||
2. **Delegated Only**: Only delegated (user) permissions are supported, not application permissions
|
||||
3. **Single Model**: Copilot exposes a single unified model, unlike other vendors with multiple model options
|
||||
4. **Enterprise Only**: Requires Microsoft 365 work or school accounts
|
||||
5. **Licensing**: Requires Microsoft 365 Copilot license per user
|
||||
|
||||
### Rate Limits
|
||||
|
||||
The Microsoft Graph API has rate limits that apply:
|
||||
|
||||
- Per-app limits
|
||||
- Per-user limits
|
||||
- Tenant-wide limits
|
||||
|
||||
Consult [Microsoft Graph throttling guidance](https://docs.microsoft.com/en-us/graph/throttling) for details.
|
||||
|
||||
### Data Freshness
|
||||
|
||||
Copilot indexes data from Microsoft 365 services. There may be a delay between when content is created and when it becomes available in Copilot responses.
|
||||
|
||||
---
|
||||
|
||||
## Additional Resources
|
||||
|
||||
### Microsoft Documentation
|
||||
|
||||
- [Microsoft 365 Copilot APIs Overview](https://learn.microsoft.com/en-us/microsoft-365-copilot/extensibility/copilot-apis-overview)
|
||||
- [Chat API Documentation](https://learn.microsoft.com/en-us/microsoft-365-copilot/extensibility/api/ai-services/chat/overview)
|
||||
- [Microsoft Graph Authentication](https://learn.microsoft.com/en-us/graph/auth/)
|
||||
- [Azure AD App Registration](https://learn.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app)
|
||||
|
||||
### Fabric Documentation
|
||||
|
||||
- [Fabric README](../README.md)
|
||||
- [Contexts and Sessions Tutorial](./contexts-and-sessions-tutorial.md)
|
||||
- [Other Vendor Setup Guides](./GitHub-Models-Setup.md)
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
Microsoft 365 Copilot integration with Fabric provides enterprise-ready AI capabilities grounded in your organization's data. Key points:
|
||||
|
||||
- **Enterprise compliance**: Works within your organization's security and compliance policies
|
||||
- **Data grounding**: Responses are based on your actual Microsoft 365 content
|
||||
- **Single model**: Exposes one unified AI model (`microsoft-365-copilot`)
|
||||
- **Delegated auth**: Requires user authentication (OAuth2 with delegated permissions)
|
||||
- **Preview API**: Currently in beta; expect changes
|
||||
|
||||
### Quick Start Commands
|
||||
|
||||
```bash
|
||||
# 1. Set up Azure AD app registration (see guide above)
|
||||
|
||||
# 2. Get access token
|
||||
az login --tenant YOUR_TENANT_ID
|
||||
ACCESS_TOKEN=$(az account get-access-token --resource https://graph.microsoft.com --query accessToken -o tsv)
|
||||
|
||||
# 3. Configure Fabric
|
||||
fabric --setup
|
||||
# Select Copilot, enter tenant ID, client ID, and access token
|
||||
|
||||
# 4. Test it
|
||||
echo "What meetings do I have this week?" | fabric --vendor Copilot
|
||||
```
|
||||
|
||||
Happy prompting with Microsoft 365 Copilot!
|
||||
@@ -15,6 +15,8 @@ import (
|
||||
"github.com/danielmiessler/fabric/internal/plugins/ai/anthropic"
|
||||
"github.com/danielmiessler/fabric/internal/plugins/ai/azure"
|
||||
"github.com/danielmiessler/fabric/internal/plugins/ai/bedrock"
|
||||
"github.com/danielmiessler/fabric/internal/plugins/ai/copilot"
|
||||
"github.com/danielmiessler/fabric/internal/plugins/ai/digitalocean"
|
||||
"github.com/danielmiessler/fabric/internal/plugins/ai/dryrun"
|
||||
"github.com/danielmiessler/fabric/internal/plugins/ai/exolab"
|
||||
"github.com/danielmiessler/fabric/internal/plugins/ai/gemini"
|
||||
@@ -98,6 +100,7 @@ func NewPluginRegistry(db *fsdb.Db) (ret *PluginRegistry, err error) {
|
||||
// Add non-OpenAI compatible clients
|
||||
vendors = append(vendors,
|
||||
openai.NewClient(),
|
||||
digitalocean.NewClient(),
|
||||
ollama.NewClient(),
|
||||
azure.NewClient(),
|
||||
gemini.NewClient(),
|
||||
@@ -105,7 +108,8 @@ func NewPluginRegistry(db *fsdb.Db) (ret *PluginRegistry, err error) {
|
||||
vertexai.NewClient(),
|
||||
lmstudio.NewClient(),
|
||||
exolab.NewClient(),
|
||||
perplexity.NewClient(), // Added Perplexity client
|
||||
perplexity.NewClient(),
|
||||
copilot.NewClient(), // Microsoft 365 Copilot
|
||||
)
|
||||
|
||||
if hasAWSCredentials() {
|
||||
|
||||
485
internal/plugins/ai/copilot/copilot.go
Normal file
485
internal/plugins/ai/copilot/copilot.go
Normal file
@@ -0,0 +1,485 @@
|
||||
// Package copilot provides integration with Microsoft 365 Copilot Chat API.
|
||||
// This vendor allows Fabric to interact with Microsoft 365 Copilot, which provides
|
||||
// AI capabilities grounded in your organization's Microsoft 365 data.
|
||||
//
|
||||
// Requirements:
|
||||
// - Microsoft 365 Copilot license for each user
|
||||
// - Microsoft 365 E3 or E5 subscription (or equivalent)
|
||||
// - Azure AD app registration with appropriate permissions
|
||||
//
|
||||
// The Chat API is currently in preview and requires delegated (work or school account)
|
||||
// permissions. Application permissions are not supported.
|
||||
package copilot
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/danielmiessler/fabric/internal/chat"
|
||||
"github.com/danielmiessler/fabric/internal/domain"
|
||||
debuglog "github.com/danielmiessler/fabric/internal/log"
|
||||
"github.com/danielmiessler/fabric/internal/plugins"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
const (
|
||||
vendorName = "Copilot"
|
||||
|
||||
// Microsoft Graph API endpoints
|
||||
defaultBaseURL = "https://graph.microsoft.com/beta/copilot"
|
||||
conversationsPath = "/conversations"
|
||||
|
||||
// OAuth2 endpoints for Microsoft identity platform
|
||||
microsoftAuthURL = "https://login.microsoftonline.com/%s/oauth2/v2.0/authorize"
|
||||
microsoftTokenURL = "https://login.microsoftonline.com/%s/oauth2/v2.0/token"
|
||||
|
||||
// Default scopes required for Copilot Chat API
|
||||
// These are the minimum required permissions
|
||||
defaultScopes = "Sites.Read.All Mail.Read People.Read.All OnlineMeetingTranscript.Read.All Chat.Read ChannelMessage.Read.All ExternalItem.Read.All offline_access"
|
||||
|
||||
// Model name exposed by Copilot (single model)
|
||||
copilotModelName = "microsoft-365-copilot"
|
||||
)
|
||||
|
||||
// NewClient creates a new Microsoft 365 Copilot client.
|
||||
func NewClient() *Client {
|
||||
c := &Client{}
|
||||
|
||||
c.PluginBase = &plugins.PluginBase{
|
||||
Name: vendorName,
|
||||
EnvNamePrefix: plugins.BuildEnvVariablePrefix(vendorName),
|
||||
ConfigureCustom: c.configure,
|
||||
}
|
||||
|
||||
// Setup questions for configuration
|
||||
c.TenantID = c.AddSetupQuestion("Tenant ID", true)
|
||||
c.TenantID.Question = "Enter your Azure AD Tenant ID (e.g., contoso.onmicrosoft.com or GUID)"
|
||||
|
||||
c.ClientID = c.AddSetupQuestion("Client ID", true)
|
||||
c.ClientID.Question = "Enter your Azure AD Application (Client) ID"
|
||||
|
||||
c.ClientSecret = c.AddSetupQuestion("Client Secret", false)
|
||||
c.ClientSecret.Question = "Enter your Azure AD Client Secret (optional, for confidential clients)"
|
||||
|
||||
c.AccessToken = c.AddSetupQuestion("Access Token", false)
|
||||
c.AccessToken.Question = "Enter a pre-obtained OAuth2 Access Token (optional, for testing)"
|
||||
|
||||
c.RefreshToken = c.AddSetupQuestion("Refresh Token", false)
|
||||
c.RefreshToken.Question = "Enter a pre-obtained OAuth2 Refresh Token (optional)"
|
||||
|
||||
c.ApiBaseURL = c.AddSetupQuestion("API Base URL", false)
|
||||
c.ApiBaseURL.Value = defaultBaseURL
|
||||
|
||||
c.TimeZone = c.AddSetupQuestion("Time Zone", false)
|
||||
c.TimeZone.Value = "America/New_York"
|
||||
c.TimeZone.Question = "Enter your timezone (e.g., America/New_York, Europe/London)"
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
// Client represents a Microsoft 365 Copilot API client.
|
||||
type Client struct {
|
||||
*plugins.PluginBase
|
||||
|
||||
// Configuration
|
||||
TenantID *plugins.SetupQuestion
|
||||
ClientID *plugins.SetupQuestion
|
||||
ClientSecret *plugins.SetupQuestion
|
||||
AccessToken *plugins.SetupQuestion
|
||||
RefreshToken *plugins.SetupQuestion
|
||||
ApiBaseURL *plugins.SetupQuestion
|
||||
TimeZone *plugins.SetupQuestion
|
||||
|
||||
// Runtime state
|
||||
httpClient *http.Client
|
||||
oauth2Config *oauth2.Config
|
||||
token *oauth2.Token
|
||||
}
|
||||
|
||||
// configure initializes the client with OAuth2 configuration.
|
||||
func (c *Client) configure() error {
|
||||
if c.TenantID.Value == "" || c.ClientID.Value == "" {
|
||||
return fmt.Errorf("tenant ID and client ID are required")
|
||||
}
|
||||
|
||||
// Build OAuth2 configuration
|
||||
c.oauth2Config = &oauth2.Config{
|
||||
ClientID: c.ClientID.Value,
|
||||
ClientSecret: c.ClientSecret.Value,
|
||||
Endpoint: oauth2.Endpoint{
|
||||
AuthURL: fmt.Sprintf(microsoftAuthURL, c.TenantID.Value),
|
||||
TokenURL: fmt.Sprintf(microsoftTokenURL, c.TenantID.Value),
|
||||
},
|
||||
Scopes: strings.Split(defaultScopes, " "),
|
||||
}
|
||||
|
||||
// If we have pre-configured tokens, use them
|
||||
if c.AccessToken.Value != "" {
|
||||
c.token = &oauth2.Token{
|
||||
AccessToken: c.AccessToken.Value,
|
||||
RefreshToken: c.RefreshToken.Value,
|
||||
TokenType: "Bearer",
|
||||
}
|
||||
// If we have a refresh token, set expiry in the past to trigger refresh
|
||||
if c.RefreshToken.Value != "" && c.ClientSecret.Value != "" {
|
||||
c.token.Expiry = time.Now().Add(-time.Hour)
|
||||
}
|
||||
}
|
||||
|
||||
// Create HTTP client with OAuth2 token source
|
||||
if c.token != nil {
|
||||
tokenSource := c.oauth2Config.TokenSource(context.Background(), c.token)
|
||||
c.httpClient = oauth2.NewClient(context.Background(), tokenSource)
|
||||
} else {
|
||||
// No tokens available - will need device code flow or manual token
|
||||
c.httpClient = &http.Client{Timeout: 120 * time.Second}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsConfigured returns true if the client has valid configuration.
|
||||
func (c *Client) IsConfigured() bool {
|
||||
// Minimum requirement: tenant ID and client ID
|
||||
if c.TenantID.Value == "" || c.ClientID.Value == "" {
|
||||
return false
|
||||
}
|
||||
// Must have either an access token or ability to get one
|
||||
return c.AccessToken.Value != "" || (c.RefreshToken.Value != "" && c.ClientSecret.Value != "")
|
||||
}
|
||||
|
||||
// ListModels returns the available models.
|
||||
// Microsoft 365 Copilot exposes a single model - the Copilot service itself.
|
||||
func (c *Client) ListModels() ([]string, error) {
|
||||
// Copilot doesn't expose multiple models - it's a unified service
|
||||
// We expose it as a single "model" for consistency with Fabric's architecture
|
||||
return []string{copilotModelName}, nil
|
||||
}
|
||||
|
||||
// Send sends a message to Copilot and returns the response.
|
||||
func (c *Client) Send(ctx context.Context, msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions) (string, error) {
|
||||
// Create a conversation
|
||||
conversationID, err := c.createConversation(ctx)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to create conversation: %w", err)
|
||||
}
|
||||
|
||||
// Build the message content from chat messages
|
||||
messageText := c.buildMessageText(msgs)
|
||||
|
||||
// Send the chat message
|
||||
response, err := c.sendChatMessage(ctx, conversationID, messageText)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to send message: %w", err)
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// SendStream sends a message to Copilot and streams the response.
|
||||
func (c *Client) SendStream(msgs []*chat.ChatCompletionMessage, opts *domain.ChatOptions, channel chan domain.StreamUpdate) error {
|
||||
defer close(channel)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// Create a conversation
|
||||
conversationID, err := c.createConversation(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create conversation: %w", err)
|
||||
}
|
||||
|
||||
// Build the message content from chat messages
|
||||
messageText := c.buildMessageText(msgs)
|
||||
|
||||
// Send the streaming chat message
|
||||
if err := c.sendChatMessageStream(ctx, conversationID, messageText, channel); err != nil {
|
||||
return fmt.Errorf("failed to stream message: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NeedsRawMode returns whether the model needs raw mode.
|
||||
func (c *Client) NeedsRawMode(modelName string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// buildMessageText combines chat messages into a single prompt for Copilot.
|
||||
func (c *Client) buildMessageText(msgs []*chat.ChatCompletionMessage) string {
|
||||
var parts []string
|
||||
|
||||
for _, msg := range msgs {
|
||||
content := strings.TrimSpace(msg.Content)
|
||||
if content == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
switch msg.Role {
|
||||
case chat.ChatMessageRoleSystem:
|
||||
// Prepend system messages as context
|
||||
parts = append([]string{content}, parts...)
|
||||
case chat.ChatMessageRoleUser, chat.ChatMessageRoleAssistant:
|
||||
parts = append(parts, content)
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(parts, "\n\n")
|
||||
}
|
||||
|
||||
// createConversation creates a new Copilot conversation.
|
||||
func (c *Client) createConversation(ctx context.Context) (string, error) {
|
||||
url := c.ApiBaseURL.Value + conversationsPath
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBufferString("{}"))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
c.addAuthHeader(req)
|
||||
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
return "", fmt.Errorf("failed to create conversation: %s - %s", resp.Status, string(body))
|
||||
}
|
||||
|
||||
var result conversationResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
debuglog.Debug(debuglog.Detailed, "Created Copilot conversation: %s\n", result.ID)
|
||||
return result.ID, nil
|
||||
}
|
||||
|
||||
// sendChatMessage sends a message to an existing conversation (synchronous).
|
||||
func (c *Client) sendChatMessage(ctx context.Context, conversationID, messageText string) (string, error) {
|
||||
url := fmt.Sprintf("%s%s/%s/chat", c.ApiBaseURL.Value, conversationsPath, conversationID)
|
||||
|
||||
reqBody := chatRequest{
|
||||
Message: messageParam{
|
||||
Text: messageText,
|
||||
},
|
||||
LocationHint: locationHint{
|
||||
TimeZone: c.TimeZone.Value,
|
||||
},
|
||||
}
|
||||
|
||||
jsonBody, err := json.Marshal(reqBody)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonBody))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
c.addAuthHeader(req)
|
||||
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
return "", fmt.Errorf("chat request failed: %s - %s", resp.Status, string(body))
|
||||
}
|
||||
|
||||
var result conversationResponse
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Extract the assistant's response from messages
|
||||
return c.extractResponseText(result.Messages), nil
|
||||
}
|
||||
|
||||
// sendChatMessageStream sends a message and streams the response via SSE.
|
||||
func (c *Client) sendChatMessageStream(ctx context.Context, conversationID, messageText string, channel chan domain.StreamUpdate) error {
|
||||
url := fmt.Sprintf("%s%s/%s/chatOverStream", c.ApiBaseURL.Value, conversationsPath, conversationID)
|
||||
|
||||
reqBody := chatRequest{
|
||||
Message: messageParam{
|
||||
Text: messageText,
|
||||
},
|
||||
LocationHint: locationHint{
|
||||
TimeZone: c.TimeZone.Value,
|
||||
},
|
||||
}
|
||||
|
||||
jsonBody, err := json.Marshal(reqBody)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonBody))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Accept", "text/event-stream")
|
||||
c.addAuthHeader(req)
|
||||
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
return fmt.Errorf("stream request failed: %s - %s", resp.Status, string(body))
|
||||
}
|
||||
|
||||
// Parse SSE stream
|
||||
return c.parseSSEStream(resp.Body, channel)
|
||||
}
|
||||
|
||||
// parseSSEStream parses the Server-Sent Events stream from Copilot.
|
||||
func (c *Client) parseSSEStream(reader io.Reader, channel chan domain.StreamUpdate) error {
|
||||
scanner := bufio.NewScanner(reader)
|
||||
var lastMessageText string
|
||||
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
|
||||
// SSE format: "data: {...json...}"
|
||||
if !strings.HasPrefix(line, "data: ") {
|
||||
continue
|
||||
}
|
||||
|
||||
jsonData := strings.TrimPrefix(line, "data: ")
|
||||
if jsonData == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
var event conversationResponse
|
||||
if err := json.Unmarshal([]byte(jsonData), &event); err != nil {
|
||||
debuglog.Debug(debuglog.Detailed, "Failed to parse SSE event: %v\n", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Extract new text from the response
|
||||
newText := c.extractResponseText(event.Messages)
|
||||
if newText != "" && newText != lastMessageText {
|
||||
// Send only the delta (new content)
|
||||
if delta, ok := strings.CutPrefix(newText, lastMessageText); ok {
|
||||
if delta != "" {
|
||||
channel <- domain.StreamUpdate{Type: domain.StreamTypeContent, Content: delta}
|
||||
}
|
||||
} else {
|
||||
// Complete message replacement
|
||||
channel <- domain.StreamUpdate{Type: domain.StreamTypeContent, Content: newText}
|
||||
}
|
||||
lastMessageText = newText
|
||||
}
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
return fmt.Errorf("error reading stream: %w", err)
|
||||
}
|
||||
|
||||
channel <- domain.StreamUpdate{Type: domain.StreamTypeContent, Content: "\n"}
|
||||
return nil
|
||||
}
|
||||
|
||||
// extractResponseText extracts the assistant's response from messages.
|
||||
func (c *Client) extractResponseText(messages []responseMessage) string {
|
||||
// Find the last assistant message (Copilot's response)
|
||||
for i := len(messages) - 1; i >= 0; i-- {
|
||||
msg := messages[i]
|
||||
// Response messages from Copilot have the copilotConversationResponseMessage type
|
||||
if msg.ODataType == "#microsoft.graph.copilotConversationResponseMessage" {
|
||||
if msg.Text != "" {
|
||||
return msg.Text
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// addAuthHeader adds the authorization header to a request.
|
||||
func (c *Client) addAuthHeader(req *http.Request) {
|
||||
if c.token != nil && c.token.AccessToken != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+c.token.AccessToken)
|
||||
} else if c.AccessToken.Value != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+c.AccessToken.Value)
|
||||
}
|
||||
}
|
||||
|
||||
// API request/response types
|
||||
|
||||
type chatRequest struct {
|
||||
Message messageParam `json:"message"`
|
||||
LocationHint locationHint `json:"locationHint"`
|
||||
AdditionalContext []contextMessage `json:"additionalContext,omitempty"`
|
||||
ContextualResources *contextualResources `json:"contextualResources,omitempty"`
|
||||
}
|
||||
|
||||
type messageParam struct {
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
type locationHint struct {
|
||||
TimeZone string `json:"timeZone"`
|
||||
}
|
||||
|
||||
type contextMessage struct {
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
type contextualResources struct {
|
||||
Files []fileResource `json:"files,omitempty"`
|
||||
WebContext *webContext `json:"webContext,omitempty"`
|
||||
}
|
||||
|
||||
type fileResource struct {
|
||||
URI string `json:"uri"`
|
||||
}
|
||||
|
||||
type webContext struct {
|
||||
IsWebEnabled bool `json:"isWebEnabled"`
|
||||
}
|
||||
|
||||
type conversationResponse struct {
|
||||
ID string `json:"id"`
|
||||
CreatedDateTime string `json:"createdDateTime"`
|
||||
DisplayName string `json:"displayName"`
|
||||
State string `json:"state"`
|
||||
TurnCount int `json:"turnCount"`
|
||||
Messages []responseMessage `json:"messages,omitempty"`
|
||||
}
|
||||
|
||||
type responseMessage struct {
|
||||
ODataType string `json:"@odata.type"`
|
||||
ID string `json:"id"`
|
||||
Text string `json:"text"`
|
||||
CreatedDateTime string `json:"createdDateTime"`
|
||||
AdaptiveCards []any `json:"adaptiveCards,omitempty"`
|
||||
Attributions []attribution `json:"attributions,omitempty"`
|
||||
}
|
||||
|
||||
type attribution struct {
|
||||
AttributionType string `json:"attributionType"`
|
||||
ProviderDisplayName string `json:"providerDisplayName"`
|
||||
AttributionSource string `json:"attributionSource"`
|
||||
SeeMoreWebURL string `json:"seeMoreWebUrl"`
|
||||
}
|
||||
151
internal/plugins/ai/digitalocean/digitalocean.go
Normal file
151
internal/plugins/ai/digitalocean/digitalocean.go
Normal file
@@ -0,0 +1,151 @@
|
||||
package digitalocean
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/danielmiessler/fabric/internal/i18n"
|
||||
"github.com/danielmiessler/fabric/internal/plugins"
|
||||
"github.com/danielmiessler/fabric/internal/plugins/ai/openai"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultInferenceBaseURL = "https://inference.do-ai.run/v1"
|
||||
controlPlaneModelsURL = "https://api.digitalocean.com/v2/gen-ai/models"
|
||||
errorResponseLimit = 1024
|
||||
maxResponseSize = 10 * 1024 * 1024
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
*openai.Client
|
||||
ControlPlaneToken *plugins.SetupQuestion
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
type modelsResponse struct {
|
||||
Models []modelDetails `json:"models"`
|
||||
}
|
||||
|
||||
type modelDetails struct {
|
||||
InferenceName string `json:"inference_name"`
|
||||
Name string `json:"name"`
|
||||
UUID string `json:"uuid"`
|
||||
}
|
||||
|
||||
func NewClient() *Client {
|
||||
base := openai.NewClientCompatibleNoSetupQuestions("DigitalOcean", nil)
|
||||
base.ApiKey = base.AddSetupQuestion("Inference Key", true)
|
||||
base.ApiBaseURL = base.AddSetupQuestion("Inference Base URL", false)
|
||||
base.ApiBaseURL.Value = defaultInferenceBaseURL
|
||||
base.ImplementsResponses = false
|
||||
|
||||
client := &Client{
|
||||
Client: base,
|
||||
}
|
||||
client.ControlPlaneToken = client.AddSetupQuestion("Token", false)
|
||||
return client
|
||||
}
|
||||
|
||||
func (c *Client) ListModels() ([]string, error) {
|
||||
if c.ControlPlaneToken.Value == "" {
|
||||
models, err := c.Client.ListModels()
|
||||
if err == nil && len(models) > 0 {
|
||||
return models, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"DigitalOcean model list unavailable: %w. Set DIGITALOCEAN_TOKEN to fetch models from the control plane",
|
||||
err,
|
||||
)
|
||||
}
|
||||
return nil, fmt.Errorf("DigitalOcean model list unavailable. Set DIGITALOCEAN_TOKEN to fetch models from the control plane")
|
||||
}
|
||||
return c.fetchModelsFromControlPlane(context.Background())
|
||||
}
|
||||
|
||||
func (c *Client) fetchModelsFromControlPlane(ctx context.Context) ([]string, error) {
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
|
||||
fullURL, err := url.Parse(controlPlaneModelsURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse DigitalOcean control plane URL: %w", err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, fullURL.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.ControlPlaneToken.Value))
|
||||
req.Header.Set("Accept", "application/json")
|
||||
|
||||
client := c.httpClient
|
||||
if client == nil {
|
||||
client = &http.Client{Timeout: 10 * time.Second}
|
||||
}
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
bodyBytes, readErr := io.ReadAll(io.LimitReader(resp.Body, errorResponseLimit))
|
||||
if readErr != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"DigitalOcean models request failed with status %d: %w",
|
||||
resp.StatusCode,
|
||||
readErr,
|
||||
)
|
||||
}
|
||||
return nil, fmt.Errorf(
|
||||
"DigitalOcean models request failed with status %d: %s",
|
||||
resp.StatusCode,
|
||||
string(bodyBytes),
|
||||
)
|
||||
}
|
||||
|
||||
bodyBytes, err := io.ReadAll(io.LimitReader(resp.Body, maxResponseSize+1))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(bodyBytes) > maxResponseSize {
|
||||
return nil, fmt.Errorf(i18n.T("openai_models_response_too_large"), c.GetName(), maxResponseSize)
|
||||
}
|
||||
|
||||
var payload modelsResponse
|
||||
if err := json.Unmarshal(bodyBytes, &payload); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
models := make([]string, 0, len(payload.Models))
|
||||
seen := make(map[string]struct{}, len(payload.Models))
|
||||
for _, model := range payload.Models {
|
||||
var value string
|
||||
switch {
|
||||
case model.InferenceName != "":
|
||||
value = model.InferenceName
|
||||
case model.Name != "":
|
||||
value = model.Name
|
||||
case model.UUID != "":
|
||||
value = model.UUID
|
||||
}
|
||||
if value == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[value]; ok {
|
||||
continue
|
||||
}
|
||||
seen[value] = struct{}{}
|
||||
models = append(models, value)
|
||||
}
|
||||
return models, nil
|
||||
}
|
||||
@@ -206,6 +206,11 @@ var ProviderMap = map[string]ProviderConfig{
|
||||
ModelsURL: "static:abacus", // Special marker for static model list
|
||||
ImplementsResponses: false,
|
||||
},
|
||||
"Mammouth": {
|
||||
Name: "Mammouth",
|
||||
BaseURL: "https://api.mammouth.ai/v1",
|
||||
ImplementsResponses: false,
|
||||
},
|
||||
}
|
||||
|
||||
// GetProviderByName returns the provider configuration for a given name with O(1) lookup
|
||||
|
||||
@@ -1 +1 @@
|
||||
"1.4.376"
|
||||
"1.4.380"
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
"@skeletonlabs/skeleton": "^2.11.0",
|
||||
"@skeletonlabs/tw-plugin": "^0.3.1",
|
||||
"@sveltejs/adapter-auto": "^3.3.1",
|
||||
"@sveltejs/kit": "^2.21.1",
|
||||
"@sveltejs/kit": "^2.49.5",
|
||||
"@sveltejs/vite-plugin-svelte": "^3.1.2",
|
||||
"@tailwindcss/forms": "^0.5.10",
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
|
||||
127
web/pnpm-lock.yaml
generated
127
web/pnpm-lock.yaml
generated
@@ -77,10 +77,10 @@ importers:
|
||||
version: 0.3.1(tailwindcss@3.4.17)
|
||||
'@sveltejs/adapter-auto':
|
||||
specifier: ^3.3.1
|
||||
version: 3.3.1(@sveltejs/kit@2.21.1(@sveltejs/vite-plugin-svelte@3.1.2(svelte@4.2.20)(vite@5.4.21(@types/node@20.17.50)))(svelte@4.2.20)(vite@5.4.21(@types/node@20.17.50)))
|
||||
version: 3.3.1(@sveltejs/kit@2.49.5(@sveltejs/vite-plugin-svelte@3.1.2(svelte@4.2.20)(vite@5.4.21(@types/node@20.17.50)))(svelte@4.2.20)(typescript@5.8.3)(vite@5.4.21(@types/node@20.17.50)))
|
||||
'@sveltejs/kit':
|
||||
specifier: ^2.21.1
|
||||
version: 2.21.1(@sveltejs/vite-plugin-svelte@3.1.2(svelte@4.2.20)(vite@5.4.21(@types/node@20.17.50)))(svelte@4.2.20)(vite@5.4.21(@types/node@20.17.50))
|
||||
specifier: ^2.49.5
|
||||
version: 2.49.5(@sveltejs/vite-plugin-svelte@3.1.2(svelte@4.2.20)(vite@5.4.21(@types/node@20.17.50)))(svelte@4.2.20)(typescript@5.8.3)(vite@5.4.21(@types/node@20.17.50))
|
||||
'@sveltejs/vite-plugin-svelte':
|
||||
specifier: ^3.1.2
|
||||
version: 3.1.2(svelte@4.2.20)(vite@5.4.21(@types/node@20.17.50))
|
||||
@@ -317,8 +317,8 @@ packages:
|
||||
peerDependencies:
|
||||
eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
|
||||
|
||||
'@eslint-community/eslint-utils@4.9.0':
|
||||
resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==}
|
||||
'@eslint-community/eslint-utils@4.9.1':
|
||||
resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==}
|
||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
peerDependencies:
|
||||
eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
|
||||
@@ -403,6 +403,9 @@ packages:
|
||||
'@jridgewell/sourcemap-codec@1.5.0':
|
||||
resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==}
|
||||
|
||||
'@jridgewell/sourcemap-codec@1.5.5':
|
||||
resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
|
||||
|
||||
'@jridgewell/trace-mapping@0.3.25':
|
||||
resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
|
||||
|
||||
@@ -630,8 +633,11 @@ packages:
|
||||
peerDependencies:
|
||||
tailwindcss: '>=3.0.0'
|
||||
|
||||
'@sveltejs/acorn-typescript@1.0.5':
|
||||
resolution: {integrity: sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==}
|
||||
'@standard-schema/spec@1.1.0':
|
||||
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
|
||||
|
||||
'@sveltejs/acorn-typescript@1.0.8':
|
||||
resolution: {integrity: sha512-esgN+54+q0NjB0Y/4BomT9samII7jGwNy/2a3wNZbT2A2RpmXsXwUt24LvLhx6jUq2gVk4cWEvcRO6MFQbOfNA==}
|
||||
peerDependencies:
|
||||
acorn: ^8.9.0
|
||||
|
||||
@@ -640,14 +646,21 @@ packages:
|
||||
peerDependencies:
|
||||
'@sveltejs/kit': ^2.0.0
|
||||
|
||||
'@sveltejs/kit@2.21.1':
|
||||
resolution: {integrity: sha512-vLbtVwtDcK8LhJKnFkFYwM0uCdFmzioQnif0bjEYH1I24Arz22JPr/hLUiXGVYAwhu8INKx5qrdvr4tHgPwX6w==}
|
||||
'@sveltejs/kit@2.49.5':
|
||||
resolution: {integrity: sha512-dCYqelr2RVnWUuxc+Dk/dB/SjV/8JBndp1UovCyCZdIQezd8TRwFLNZctYkzgHxRJtaNvseCSRsuuHPeUgIN/A==}
|
||||
engines: {node: '>=18.13'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@sveltejs/vite-plugin-svelte': ^3.0.0 || ^4.0.0-next.1 || ^5.0.0
|
||||
'@opentelemetry/api': ^1.0.0
|
||||
'@sveltejs/vite-plugin-svelte': ^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0
|
||||
svelte: ^4.0.0 || ^5.0.0-next.0
|
||||
vite: ^5.0.3 || ^6.0.0
|
||||
typescript: ^5.3.3
|
||||
vite: ^5.0.3 || ^6.0.0 || ^7.0.0-beta.0
|
||||
peerDependenciesMeta:
|
||||
'@opentelemetry/api':
|
||||
optional: true
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
'@sveltejs/vite-plugin-svelte-inspector@2.1.0':
|
||||
resolution: {integrity: sha512-9QX28IymvBlSCqsCll5t0kQVxipsfhFFL+L2t3nTWfXnddYwxBuAEtTtlaVQpRz9c37BhJjltSeY4AJSC03SSg==}
|
||||
@@ -909,8 +922,8 @@ packages:
|
||||
concat-map@0.0.1:
|
||||
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
|
||||
|
||||
cookie@1.0.2:
|
||||
resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==}
|
||||
cookie@1.1.1:
|
||||
resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
core-util-is@1.0.2:
|
||||
@@ -977,8 +990,8 @@ packages:
|
||||
resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
devalue@5.3.2:
|
||||
resolution: {integrity: sha512-UDsjUbpQn9kvm68slnrs+mfxwFkIflOhkanmyabZ8zOYk8SMEIbJ3TK+88g70hSIeytu4y18f0z/hYHMTrXIWw==}
|
||||
devalue@5.6.2:
|
||||
resolution: {integrity: sha512-nPRkjWzzDQlsejL1WVifk5rvcFi/y1onBRxjaFMjZeR9mFpqu2gmAZ9xUB9/IEanEP/vBtGeGganC/GO1fmufg==}
|
||||
|
||||
devlop@1.1.0:
|
||||
resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==}
|
||||
@@ -1099,8 +1112,8 @@ packages:
|
||||
resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==}
|
||||
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||
|
||||
esquery@1.6.0:
|
||||
resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==}
|
||||
esquery@1.7.0:
|
||||
resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==}
|
||||
engines: {node: '>=0.10'}
|
||||
|
||||
esrecurse@4.3.0:
|
||||
@@ -1477,6 +1490,9 @@ packages:
|
||||
magic-string@0.30.17:
|
||||
resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==}
|
||||
|
||||
magic-string@0.30.21:
|
||||
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
|
||||
|
||||
marked@15.0.12:
|
||||
resolution: {integrity: sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==}
|
||||
engines: {node: '>= 18'}
|
||||
@@ -1899,8 +1915,8 @@ packages:
|
||||
engines: {node: '>=10'}
|
||||
hasBin: true
|
||||
|
||||
set-cookie-parser@2.7.1:
|
||||
resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==}
|
||||
set-cookie-parser@2.7.2:
|
||||
resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==}
|
||||
|
||||
set-function-length@1.2.2:
|
||||
resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
|
||||
@@ -1924,8 +1940,8 @@ packages:
|
||||
simple-statistics@7.8.8:
|
||||
resolution: {integrity: sha512-CUtP0+uZbcbsFpqEyvNDYjJCl+612fNgjT8GaVuvMG7tBuJg8gXGpsP5M7X658zy0IcepWOZ6nPBu1Qb9ezA1w==}
|
||||
|
||||
sirv@3.0.1:
|
||||
resolution: {integrity: sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==}
|
||||
sirv@3.0.2:
|
||||
resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
slash@2.0.0:
|
||||
@@ -2377,7 +2393,7 @@ snapshots:
|
||||
eslint: 9.17.0(jiti@1.21.7)
|
||||
eslint-visitor-keys: 3.4.3
|
||||
|
||||
'@eslint-community/eslint-utils@4.9.0(eslint@9.17.0(jiti@1.21.7))':
|
||||
'@eslint-community/eslint-utils@4.9.1(eslint@9.17.0(jiti@1.21.7))':
|
||||
dependencies:
|
||||
eslint: 9.17.0(jiti@1.21.7)
|
||||
eslint-visitor-keys: 3.4.3
|
||||
@@ -2459,7 +2475,7 @@ snapshots:
|
||||
'@jridgewell/gen-mapping@0.3.8':
|
||||
dependencies:
|
||||
'@jridgewell/set-array': 1.2.1
|
||||
'@jridgewell/sourcemap-codec': 1.5.0
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
'@jridgewell/trace-mapping': 0.3.25
|
||||
|
||||
'@jridgewell/resolve-uri@3.1.2': {}
|
||||
@@ -2468,6 +2484,8 @@ snapshots:
|
||||
|
||||
'@jridgewell/sourcemap-codec@1.5.0': {}
|
||||
|
||||
'@jridgewell/sourcemap-codec@1.5.5': {}
|
||||
|
||||
'@jridgewell/trace-mapping@0.3.25':
|
||||
dependencies:
|
||||
'@jridgewell/resolve-uri': 3.1.2
|
||||
@@ -2644,32 +2662,37 @@ snapshots:
|
||||
dependencies:
|
||||
tailwindcss: 3.4.17
|
||||
|
||||
'@sveltejs/acorn-typescript@1.0.5(acorn@8.14.1)':
|
||||
dependencies:
|
||||
acorn: 8.14.1
|
||||
'@standard-schema/spec@1.1.0': {}
|
||||
|
||||
'@sveltejs/adapter-auto@3.3.1(@sveltejs/kit@2.21.1(@sveltejs/vite-plugin-svelte@3.1.2(svelte@4.2.20)(vite@5.4.21(@types/node@20.17.50)))(svelte@4.2.20)(vite@5.4.21(@types/node@20.17.50)))':
|
||||
'@sveltejs/acorn-typescript@1.0.8(acorn@8.15.0)':
|
||||
dependencies:
|
||||
'@sveltejs/kit': 2.21.1(@sveltejs/vite-plugin-svelte@3.1.2(svelte@4.2.20)(vite@5.4.21(@types/node@20.17.50)))(svelte@4.2.20)(vite@5.4.21(@types/node@20.17.50))
|
||||
acorn: 8.15.0
|
||||
|
||||
'@sveltejs/adapter-auto@3.3.1(@sveltejs/kit@2.49.5(@sveltejs/vite-plugin-svelte@3.1.2(svelte@4.2.20)(vite@5.4.21(@types/node@20.17.50)))(svelte@4.2.20)(typescript@5.8.3)(vite@5.4.21(@types/node@20.17.50)))':
|
||||
dependencies:
|
||||
'@sveltejs/kit': 2.49.5(@sveltejs/vite-plugin-svelte@3.1.2(svelte@4.2.20)(vite@5.4.21(@types/node@20.17.50)))(svelte@4.2.20)(typescript@5.8.3)(vite@5.4.21(@types/node@20.17.50))
|
||||
import-meta-resolve: 4.1.0
|
||||
|
||||
'@sveltejs/kit@2.21.1(@sveltejs/vite-plugin-svelte@3.1.2(svelte@4.2.20)(vite@5.4.21(@types/node@20.17.50)))(svelte@4.2.20)(vite@5.4.21(@types/node@20.17.50))':
|
||||
'@sveltejs/kit@2.49.5(@sveltejs/vite-plugin-svelte@3.1.2(svelte@4.2.20)(vite@5.4.21(@types/node@20.17.50)))(svelte@4.2.20)(typescript@5.8.3)(vite@5.4.21(@types/node@20.17.50))':
|
||||
dependencies:
|
||||
'@sveltejs/acorn-typescript': 1.0.5(acorn@8.14.1)
|
||||
'@standard-schema/spec': 1.1.0
|
||||
'@sveltejs/acorn-typescript': 1.0.8(acorn@8.15.0)
|
||||
'@sveltejs/vite-plugin-svelte': 3.1.2(svelte@4.2.20)(vite@5.4.21(@types/node@20.17.50))
|
||||
'@types/cookie': 0.6.0
|
||||
acorn: 8.14.1
|
||||
cookie: 1.0.2
|
||||
devalue: 5.3.2
|
||||
acorn: 8.15.0
|
||||
cookie: 1.1.1
|
||||
devalue: 5.6.2
|
||||
esm-env: 1.2.2
|
||||
kleur: 4.1.5
|
||||
magic-string: 0.30.17
|
||||
magic-string: 0.30.21
|
||||
mrmime: 2.0.1
|
||||
sade: 1.8.1
|
||||
set-cookie-parser: 2.7.1
|
||||
sirv: 3.0.1
|
||||
set-cookie-parser: 2.7.2
|
||||
sirv: 3.0.2
|
||||
svelte: 4.2.20
|
||||
vite: 5.4.21(@types/node@20.17.50)
|
||||
optionalDependencies:
|
||||
typescript: 5.8.3
|
||||
|
||||
'@sveltejs/vite-plugin-svelte-inspector@2.1.0(@sveltejs/vite-plugin-svelte@3.1.2(svelte@4.2.20)(vite@5.4.21(@types/node@20.17.50)))(svelte@4.2.20)(vite@5.4.21(@types/node@20.17.50))':
|
||||
dependencies:
|
||||
@@ -2741,10 +2764,6 @@ snapshots:
|
||||
|
||||
'@yarnpkg/lockfile@1.1.0': {}
|
||||
|
||||
acorn-jsx@5.3.2(acorn@8.14.1):
|
||||
dependencies:
|
||||
acorn: 8.14.1
|
||||
|
||||
acorn-jsx@5.3.2(acorn@8.15.0):
|
||||
dependencies:
|
||||
acorn: 8.15.0
|
||||
@@ -2900,7 +2919,7 @@ snapshots:
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.5.0
|
||||
'@types/estree': 1.0.7
|
||||
acorn: 8.14.1
|
||||
acorn: 8.15.0
|
||||
estree-walker: 3.0.3
|
||||
periscopic: 3.1.0
|
||||
|
||||
@@ -2922,7 +2941,7 @@ snapshots:
|
||||
|
||||
concat-map@0.0.1: {}
|
||||
|
||||
cookie@1.0.2: {}
|
||||
cookie@1.1.1: {}
|
||||
|
||||
core-util-is@1.0.2: {}
|
||||
|
||||
@@ -2969,7 +2988,7 @@ snapshots:
|
||||
|
||||
detect-indent@6.1.0: {}
|
||||
|
||||
devalue@5.3.2: {}
|
||||
devalue@5.6.2: {}
|
||||
|
||||
devlop@1.1.0:
|
||||
dependencies:
|
||||
@@ -3082,7 +3101,7 @@ snapshots:
|
||||
|
||||
eslint@9.17.0(jiti@1.21.7):
|
||||
dependencies:
|
||||
'@eslint-community/eslint-utils': 4.9.0(eslint@9.17.0(jiti@1.21.7))
|
||||
'@eslint-community/eslint-utils': 4.9.1(eslint@9.17.0(jiti@1.21.7))
|
||||
'@eslint-community/regexpp': 4.12.2
|
||||
'@eslint/config-array': 0.19.2
|
||||
'@eslint/core': 0.9.1
|
||||
@@ -3102,7 +3121,7 @@ snapshots:
|
||||
eslint-scope: 8.4.0
|
||||
eslint-visitor-keys: 4.2.1
|
||||
espree: 10.4.0
|
||||
esquery: 1.6.0
|
||||
esquery: 1.7.0
|
||||
esutils: 2.0.3
|
||||
fast-deep-equal: 3.1.3
|
||||
file-entry-cache: 8.0.0
|
||||
@@ -3133,11 +3152,11 @@ snapshots:
|
||||
|
||||
espree@9.6.1:
|
||||
dependencies:
|
||||
acorn: 8.14.1
|
||||
acorn-jsx: 5.3.2(acorn@8.14.1)
|
||||
acorn: 8.15.0
|
||||
acorn-jsx: 5.3.2(acorn@8.15.0)
|
||||
eslint-visitor-keys: 3.4.3
|
||||
|
||||
esquery@1.6.0:
|
||||
esquery@1.7.0:
|
||||
dependencies:
|
||||
estraverse: 5.3.0
|
||||
|
||||
@@ -3533,6 +3552,10 @@ snapshots:
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.5.0
|
||||
|
||||
magic-string@0.30.21:
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
|
||||
marked@15.0.12: {}
|
||||
|
||||
marked@5.1.2: {}
|
||||
@@ -3985,7 +4008,7 @@ snapshots:
|
||||
|
||||
semver@7.7.2: {}
|
||||
|
||||
set-cookie-parser@2.7.1: {}
|
||||
set-cookie-parser@2.7.2: {}
|
||||
|
||||
set-function-length@1.2.2:
|
||||
dependencies:
|
||||
@@ -4017,7 +4040,7 @@ snapshots:
|
||||
|
||||
simple-statistics@7.8.8: {}
|
||||
|
||||
sirv@3.0.1:
|
||||
sirv@3.0.2:
|
||||
dependencies:
|
||||
'@polka/url': 1.0.0-next.29
|
||||
mrmime: 2.0.1
|
||||
@@ -4027,7 +4050,7 @@ snapshots:
|
||||
|
||||
sorcery@0.11.1:
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.5.0
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
buffer-crc32: 1.0.0
|
||||
minimist: 1.2.8
|
||||
sander: 0.5.1
|
||||
@@ -4147,7 +4170,7 @@ snapshots:
|
||||
dependencies:
|
||||
'@types/pug': 2.0.10
|
||||
detect-indent: 6.1.0
|
||||
magic-string: 0.30.17
|
||||
magic-string: 0.30.21
|
||||
sorcery: 0.11.1
|
||||
strip-indent: 3.0.0
|
||||
svelte: 4.2.20
|
||||
|
||||
Reference in New Issue
Block a user