@microsoft/agents-hosting-storage-blob 0.1.49
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 +13 -0
- package/dist/index.js +20 -0
- package/dist/src/blobsStorage.d.ts +22 -0
- package/dist/src/blobsStorage.js +128 -0
- package/dist/src/blobsStorage.js.map +1 -0
- package/dist/src/blobsTranscriptStore.d.ts +29 -0
- package/dist/src/blobsTranscriptStore.js +242 -0
- package/dist/src/blobsTranscriptStore.js.map +1 -0
- package/dist/src/ignoreError.d.ts +8 -0
- package/dist/src/ignoreError.js +25 -0
- package/dist/src/ignoreError.js.map +1 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +21 -0
- package/dist/src/index.js.map +1 -0
- package/package.json +39 -0
- package/src/blobsStorage.ts +139 -0
- package/src/blobsTranscriptStore.ts +298 -0
- package/src/ignoreError.ts +35 -0
- package/src/index.ts +5 -0
package/README.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# @microsoft/agents-hosting-storage-blob
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
This package allows to configure Azure Blob Storage as the backend for Agents conversation State
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
const blobStorage = new AzureBlobStorage(process.env.BLOB_STORAGE_CONNECTION_STRING!, process.env.BLOB_CONTAINER_ID!)
|
|
11
|
+
const conversationState = new ConversationState(blobStorage)
|
|
12
|
+
const userState = new UserState(blobStorage)
|
|
13
|
+
```
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
|
+
// Licensed under the MIT License.
|
|
4
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
5
|
+
if (k2 === undefined) k2 = k;
|
|
6
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
7
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
8
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
9
|
+
}
|
|
10
|
+
Object.defineProperty(o, k2, desc);
|
|
11
|
+
}) : (function(o, m, k, k2) {
|
|
12
|
+
if (k2 === undefined) k2 = k;
|
|
13
|
+
o[k2] = m[k];
|
|
14
|
+
}));
|
|
15
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
16
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
17
|
+
};
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
__exportStar(require("./src"), exports);
|
|
20
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { AnonymousCredential, StoragePipelineOptions, StorageSharedKeyCredential } from '@azure/storage-blob';
|
|
2
|
+
import { Storage, StoreItems } from '@microsoft/agents-hosting';
|
|
3
|
+
/**
|
|
4
|
+
* Options for configuring the BlobsStorage.
|
|
5
|
+
*/
|
|
6
|
+
export interface BlobsStorageOptions {
|
|
7
|
+
storagePipelineOptions?: StoragePipelineOptions;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* A class that implements the Storage interface using Azure Blob Storage.
|
|
11
|
+
*/
|
|
12
|
+
export declare class BlobsStorage implements Storage {
|
|
13
|
+
private readonly _containerClient;
|
|
14
|
+
private readonly _concurrency;
|
|
15
|
+
private _initializePromise?;
|
|
16
|
+
constructor(connectionString: string, containerName: string, options?: BlobsStorageOptions, url?: string, credential?: StorageSharedKeyCredential | AnonymousCredential);
|
|
17
|
+
private toJSON;
|
|
18
|
+
private _initialize;
|
|
19
|
+
read(keys: string[]): Promise<StoreItems>;
|
|
20
|
+
write(changes: StoreItems): Promise<void>;
|
|
21
|
+
delete(keys: string[]): Promise<void>;
|
|
22
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.BlobsStorage = void 0;
|
|
40
|
+
const z = __importStar(require("zod"));
|
|
41
|
+
const consumers_1 = __importDefault(require("stream/consumers"));
|
|
42
|
+
const storage_blob_1 = require("@azure/storage-blob");
|
|
43
|
+
const blobsTranscriptStore_1 = require("./blobsTranscriptStore");
|
|
44
|
+
const ignoreError_1 = require("./ignoreError");
|
|
45
|
+
/**
|
|
46
|
+
* A class that implements the Storage interface using Azure Blob Storage.
|
|
47
|
+
*/
|
|
48
|
+
class BlobsStorage {
|
|
49
|
+
constructor(connectionString, containerName, options, url = '', credential) {
|
|
50
|
+
this._concurrency = Infinity;
|
|
51
|
+
if (url !== '' && credential != null) {
|
|
52
|
+
z.object({ url: z.string() }).parse({
|
|
53
|
+
url,
|
|
54
|
+
});
|
|
55
|
+
this._containerClient = new storage_blob_1.ContainerClient(url, credential, options === null || options === void 0 ? void 0 : options.storagePipelineOptions);
|
|
56
|
+
if (url.trim() === 'UseDevelopmentStorage=true;') {
|
|
57
|
+
this._concurrency = 1;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
z.object({ connectionString: z.string(), containerName: z.string() }).parse({
|
|
62
|
+
connectionString,
|
|
63
|
+
containerName,
|
|
64
|
+
});
|
|
65
|
+
this._containerClient = new storage_blob_1.ContainerClient(connectionString, containerName, options === null || options === void 0 ? void 0 : options.storagePipelineOptions);
|
|
66
|
+
if (connectionString.trim() === 'UseDevelopmentStorage=true;') {
|
|
67
|
+
this._concurrency = 1;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
toJSON() {
|
|
72
|
+
return { name: 'BlobsStorage' };
|
|
73
|
+
}
|
|
74
|
+
_initialize() {
|
|
75
|
+
if (!this._initializePromise) {
|
|
76
|
+
this._initializePromise = this._containerClient.createIfNotExists();
|
|
77
|
+
}
|
|
78
|
+
return this._initializePromise;
|
|
79
|
+
}
|
|
80
|
+
async read(keys) {
|
|
81
|
+
z.object({ keys: z.array(z.string()) }).parse({ keys });
|
|
82
|
+
await this._initialize();
|
|
83
|
+
const results = await Promise.all(keys.map(async (key) => {
|
|
84
|
+
const result = { key, value: undefined };
|
|
85
|
+
const blob = await (0, ignoreError_1.ignoreError)(this._containerClient.getBlobClient((0, blobsTranscriptStore_1.sanitizeBlobKey)(key)).download(), (0, ignoreError_1.isStatusCodeError)(404));
|
|
86
|
+
if (!blob) {
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
89
|
+
const { etag: eTag, readableStreamBody } = blob;
|
|
90
|
+
if (!readableStreamBody) {
|
|
91
|
+
return result;
|
|
92
|
+
}
|
|
93
|
+
const parsed = (await consumers_1.default.json(readableStreamBody));
|
|
94
|
+
result.value = { ...parsed, eTag };
|
|
95
|
+
return result;
|
|
96
|
+
}));
|
|
97
|
+
return results.reduce((acc, { key, value }) => (value ? { ...acc, [key]: value } : acc), {});
|
|
98
|
+
}
|
|
99
|
+
async write(changes) {
|
|
100
|
+
z.record(z.unknown()).parse(changes);
|
|
101
|
+
await this._initialize();
|
|
102
|
+
await Promise.all(Object.entries(changes).map(async ([key, { eTag = '', ...change }]) => {
|
|
103
|
+
try {
|
|
104
|
+
const blob = this._containerClient.getBlockBlobClient((0, blobsTranscriptStore_1.sanitizeBlobKey)(key));
|
|
105
|
+
const serialized = JSON.stringify(change);
|
|
106
|
+
return await blob.upload(serialized, serialized.length, {
|
|
107
|
+
conditions: typeof eTag === 'string' && eTag !== '*' ? { ifMatch: eTag } : {},
|
|
108
|
+
blobHTTPHeaders: { blobContentType: 'application/json' },
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
catch (err) {
|
|
112
|
+
if (err.statusCode === 412) {
|
|
113
|
+
throw new Error(`Storage: error writing "${key}" due to eTag conflict.`);
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
throw err;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}));
|
|
120
|
+
}
|
|
121
|
+
async delete(keys) {
|
|
122
|
+
z.object({ keys: z.array(z.string()) }).parse({ keys });
|
|
123
|
+
await this._initialize();
|
|
124
|
+
await Promise.all(keys.map((key) => (0, ignoreError_1.ignoreError)(this._containerClient.deleteBlob((0, blobsTranscriptStore_1.sanitizeBlobKey)(key)), (0, ignoreError_1.isStatusCodeError)(404))));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
exports.BlobsStorage = BlobsStorage;
|
|
128
|
+
//# sourceMappingURL=blobsStorage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blobsStorage.js","sourceRoot":"","sources":["../../src/blobsStorage.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Activity } from '@microsoft/agents-activity';
|
|
2
|
+
import { AnonymousCredential, StoragePipelineOptions, StorageSharedKeyCredential } from '@azure/storage-blob';
|
|
3
|
+
import { TranscriptStore, PagedResult, TranscriptInfo } from '@microsoft/agents-hosting';
|
|
4
|
+
export declare function sanitizeBlobKey(key: string, options?: BlobsTranscriptStoreOptions): string;
|
|
5
|
+
export declare function maybeCast<T>(value: unknown, ctor?: {
|
|
6
|
+
new (...args: any[]): T;
|
|
7
|
+
}): T;
|
|
8
|
+
/**
|
|
9
|
+
* Options for configuring the BlobsTranscriptStore.
|
|
10
|
+
*/
|
|
11
|
+
export interface BlobsTranscriptStoreOptions {
|
|
12
|
+
storagePipelineOptions?: StoragePipelineOptions;
|
|
13
|
+
decodeTranscriptKey?: boolean;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* A class that implements the TranscriptStore interface using Azure Blob Storage.
|
|
17
|
+
*/
|
|
18
|
+
export declare class BlobsTranscriptStore implements TranscriptStore {
|
|
19
|
+
private readonly _containerClient;
|
|
20
|
+
private readonly _concurrency;
|
|
21
|
+
private _initializePromise?;
|
|
22
|
+
private _isDecodeTranscriptKey?;
|
|
23
|
+
constructor(connectionString: string, containerName: string, options?: BlobsTranscriptStoreOptions, blobServiceUri?: string, tokenCredential?: StorageSharedKeyCredential | AnonymousCredential);
|
|
24
|
+
private _initialize;
|
|
25
|
+
getTranscriptActivities(channelId: string, conversationId: string, continuationToken?: string, startDate?: Date): Promise<PagedResult<Activity>>;
|
|
26
|
+
listTranscripts(channelId: string, continuationToken?: string): Promise<PagedResult<TranscriptInfo>>;
|
|
27
|
+
deleteTranscript(channelId: string, conversationId: string): Promise<void>;
|
|
28
|
+
logActivity(activity: Activity, options?: BlobsTranscriptStoreOptions): Promise<void>;
|
|
29
|
+
}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.BlobsTranscriptStore = void 0;
|
|
40
|
+
exports.sanitizeBlobKey = sanitizeBlobKey;
|
|
41
|
+
exports.maybeCast = maybeCast;
|
|
42
|
+
const z = __importStar(require("zod"));
|
|
43
|
+
const consumers_1 = __importDefault(require("stream/consumers"));
|
|
44
|
+
const storage_blob_1 = require("@azure/storage-blob");
|
|
45
|
+
function formatTicks(timestamp) {
|
|
46
|
+
const epochTicks = 621355968000000000;
|
|
47
|
+
const ticksPerMillisecond = 10000;
|
|
48
|
+
const ticks = epochTicks + timestamp.getTime() * ticksPerMillisecond;
|
|
49
|
+
return ticks.toString(16);
|
|
50
|
+
}
|
|
51
|
+
function getChannelPrefix(channelId) {
|
|
52
|
+
return sanitizeBlobKey(`${channelId}/`);
|
|
53
|
+
}
|
|
54
|
+
function getConversationPrefix(channelId, conversationId) {
|
|
55
|
+
return sanitizeBlobKey(`${channelId}/${conversationId}`);
|
|
56
|
+
}
|
|
57
|
+
function getBlobKey(activity, options) {
|
|
58
|
+
var _a;
|
|
59
|
+
if (!(activity.timestamp instanceof Date)) {
|
|
60
|
+
throw new Error('Invalid timestamp: must be an instance of Date');
|
|
61
|
+
}
|
|
62
|
+
const { timestamp } = z
|
|
63
|
+
.object({ timestamp: z.instanceof(Date) })
|
|
64
|
+
.passthrough()
|
|
65
|
+
.parse(activity);
|
|
66
|
+
return sanitizeBlobKey([activity.channelId, (_a = activity.conversation) === null || _a === void 0 ? void 0 : _a.id, `${formatTicks(timestamp)}-${activity.id}.json`].join('/'), options);
|
|
67
|
+
}
|
|
68
|
+
function sanitizeBlobKey(key, options) {
|
|
69
|
+
if (!key || key.length === 0) {
|
|
70
|
+
throw new Error('Please provide a non-empty key');
|
|
71
|
+
}
|
|
72
|
+
const sanitized = key.split('/').reduce((acc, part, idx) => {
|
|
73
|
+
return part ? `${acc}/${part}` : acc;
|
|
74
|
+
}, '').substr(0, 1024);
|
|
75
|
+
const encodedKey = encodeURIComponent(sanitized).substr(0, 1024);
|
|
76
|
+
if (options === null || options === void 0 ? void 0 : options.decodeTranscriptKey) {
|
|
77
|
+
return decodeURIComponent(encodedKey).substr(0, 1024);
|
|
78
|
+
}
|
|
79
|
+
return encodedKey;
|
|
80
|
+
}
|
|
81
|
+
function maybeCast(value, ctor) {
|
|
82
|
+
if (ctor != null && value instanceof ctor) {
|
|
83
|
+
return value;
|
|
84
|
+
}
|
|
85
|
+
return value;
|
|
86
|
+
}
|
|
87
|
+
const MAX_PAGE_SIZE = 20;
|
|
88
|
+
/**
|
|
89
|
+
* A class that implements the TranscriptStore interface using Azure Blob Storage.
|
|
90
|
+
*/
|
|
91
|
+
class BlobsTranscriptStore {
|
|
92
|
+
constructor(connectionString, containerName, options, blobServiceUri = '', tokenCredential) {
|
|
93
|
+
this._concurrency = Infinity;
|
|
94
|
+
this._isDecodeTranscriptKey = false;
|
|
95
|
+
if (blobServiceUri !== '' && tokenCredential !== null) {
|
|
96
|
+
z.object({ blobServiceUri: z.string() }).parse({
|
|
97
|
+
blobServiceUri,
|
|
98
|
+
});
|
|
99
|
+
this._containerClient = new storage_blob_1.ContainerClient(blobServiceUri, tokenCredential, options === null || options === void 0 ? void 0 : options.storagePipelineOptions);
|
|
100
|
+
if (blobServiceUri.trim() === 'UseDevelopmentStorage=true;') {
|
|
101
|
+
this._concurrency = 1;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
z.object({ connectionString: z.string(), containerName: z.string() }).parse({
|
|
106
|
+
connectionString,
|
|
107
|
+
containerName,
|
|
108
|
+
});
|
|
109
|
+
this._containerClient = new storage_blob_1.ContainerClient(connectionString, containerName, options === null || options === void 0 ? void 0 : options.storagePipelineOptions);
|
|
110
|
+
if (connectionString.trim() === 'UseDevelopmentStorage=true;') {
|
|
111
|
+
this._concurrency = 1;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
this._isDecodeTranscriptKey = options === null || options === void 0 ? void 0 : options.decodeTranscriptKey;
|
|
115
|
+
}
|
|
116
|
+
_initialize() {
|
|
117
|
+
if (!this._initializePromise) {
|
|
118
|
+
this._initializePromise = this._containerClient.createIfNotExists();
|
|
119
|
+
}
|
|
120
|
+
return this._initializePromise;
|
|
121
|
+
}
|
|
122
|
+
async getTranscriptActivities(channelId, conversationId, continuationToken, startDate) {
|
|
123
|
+
var _a, _b, _c, _d;
|
|
124
|
+
z.object({ channelId: z.string(), conversationId: z.string() }).parse({ channelId, conversationId });
|
|
125
|
+
await this._initialize();
|
|
126
|
+
const prefix = getConversationPrefix(channelId, conversationId);
|
|
127
|
+
console.log(`Using prefix: ${prefix}`);
|
|
128
|
+
const iter = this._containerClient
|
|
129
|
+
.listBlobsByHierarchy('/', {
|
|
130
|
+
prefix,
|
|
131
|
+
})
|
|
132
|
+
.byPage({ continuationToken, maxPageSize: MAX_PAGE_SIZE });
|
|
133
|
+
let page = await iter.next();
|
|
134
|
+
const result = [];
|
|
135
|
+
let response;
|
|
136
|
+
while (!page.done) {
|
|
137
|
+
response = maybeCast((_a = page === null || page === void 0 ? void 0 : page.value) !== null && _a !== void 0 ? _a : {});
|
|
138
|
+
const blobItems = (_c = (_b = response === null || response === void 0 ? void 0 : response.segment) === null || _b === void 0 ? void 0 : _b.blobItems) !== null && _c !== void 0 ? _c : [];
|
|
139
|
+
console.log(`Fetched ${blobItems.length} blob items.`);
|
|
140
|
+
const fromIdx = startDate != null
|
|
141
|
+
? blobItems.findIndex((blobItem) => { var _a, _b; return ((_a = blobItem === null || blobItem === void 0 ? void 0 : blobItem.properties) === null || _a === void 0 ? void 0 : _a.createdOn) && ((_b = blobItem === null || blobItem === void 0 ? void 0 : blobItem.properties) === null || _b === void 0 ? void 0 : _b.createdOn) >= startDate; })
|
|
142
|
+
: 0;
|
|
143
|
+
console.log(`fromIdx: ${fromIdx}`);
|
|
144
|
+
if (fromIdx !== -1) {
|
|
145
|
+
const activities = await Promise.all(blobItems.slice(fromIdx).map(async (blobItem) => {
|
|
146
|
+
const blob = await this._containerClient.getBlobClient(blobItem.name).download();
|
|
147
|
+
const { readableStreamBody } = blob;
|
|
148
|
+
if (!readableStreamBody) {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
const activity = (await consumers_1.default.json(readableStreamBody));
|
|
152
|
+
return { ...activity, timestamp: new Date(activity.timestamp) };
|
|
153
|
+
}));
|
|
154
|
+
activities.forEach((activity) => {
|
|
155
|
+
if (activity)
|
|
156
|
+
result.push(activity);
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
page = await iter.next();
|
|
160
|
+
}
|
|
161
|
+
console.log(`Total activities fetched: ${result.length}`);
|
|
162
|
+
return {
|
|
163
|
+
continuationToken: (_d = response === null || response === void 0 ? void 0 : response.continuationToken) !== null && _d !== void 0 ? _d : '',
|
|
164
|
+
items: result.reduce((acc, activity) => (activity ? acc.concat(activity) : acc), []),
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
async listTranscripts(channelId, continuationToken) {
|
|
168
|
+
var _a, _b, _c, _d;
|
|
169
|
+
z.object({ channelId: z.string() }).parse({ channelId });
|
|
170
|
+
await this._initialize();
|
|
171
|
+
const iter = this._containerClient
|
|
172
|
+
.listBlobsByHierarchy('/', {
|
|
173
|
+
prefix: getChannelPrefix(channelId),
|
|
174
|
+
})
|
|
175
|
+
.byPage({ continuationToken, maxPageSize: MAX_PAGE_SIZE });
|
|
176
|
+
let page = await iter.next();
|
|
177
|
+
const result = [];
|
|
178
|
+
let response;
|
|
179
|
+
while (!page.done) {
|
|
180
|
+
const response = maybeCast((_a = page === null || page === void 0 ? void 0 : page.value) !== null && _a !== void 0 ? _a : {});
|
|
181
|
+
const blobItems = (_c = (_b = response === null || response === void 0 ? void 0 : response.segment) === null || _b === void 0 ? void 0 : _b.blobItems) !== null && _c !== void 0 ? _c : [];
|
|
182
|
+
const items = blobItems.map((blobItem) => {
|
|
183
|
+
var _a;
|
|
184
|
+
const [, id] = decodeURIComponent(blobItem.name).split('/');
|
|
185
|
+
const created = ((_a = blobItem.metadata) === null || _a === void 0 ? void 0 : _a.timestamp) ? new Date(blobItem.metadata.timestamp) : new Date();
|
|
186
|
+
return { channelId, created, id };
|
|
187
|
+
});
|
|
188
|
+
items.forEach((transcript) => {
|
|
189
|
+
if (transcript)
|
|
190
|
+
result.push(transcript);
|
|
191
|
+
});
|
|
192
|
+
page = await iter.next();
|
|
193
|
+
}
|
|
194
|
+
return {
|
|
195
|
+
continuationToken: (_d = response === null || response === void 0 ? void 0 : response.continuationToken) !== null && _d !== void 0 ? _d : '',
|
|
196
|
+
items: result !== null && result !== void 0 ? result : [],
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
async deleteTranscript(channelId, conversationId) {
|
|
200
|
+
var _a, _b, _c;
|
|
201
|
+
z.object({ channelId: z.string(), conversationId: z.string() }).parse({ channelId, conversationId });
|
|
202
|
+
await this._initialize();
|
|
203
|
+
const iter = this._containerClient
|
|
204
|
+
.listBlobsByHierarchy('/', {
|
|
205
|
+
prefix: getConversationPrefix(channelId, conversationId),
|
|
206
|
+
})
|
|
207
|
+
.byPage({
|
|
208
|
+
maxPageSize: MAX_PAGE_SIZE,
|
|
209
|
+
});
|
|
210
|
+
let page = await iter.next();
|
|
211
|
+
while (!page.done) {
|
|
212
|
+
const response = maybeCast((_a = page === null || page === void 0 ? void 0 : page.value) !== null && _a !== void 0 ? _a : {});
|
|
213
|
+
const blobItems = (_c = (_b = response === null || response === void 0 ? void 0 : response.segment) === null || _b === void 0 ? void 0 : _b.blobItems) !== null && _c !== void 0 ? _c : [];
|
|
214
|
+
const deletionPromises = blobItems.map(blobItem => this._containerClient.deleteBlob(blobItem.name));
|
|
215
|
+
await Promise.all(deletionPromises);
|
|
216
|
+
page = await iter.next();
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
async logActivity(activity, options) {
|
|
220
|
+
var _a, _b, _c, _d;
|
|
221
|
+
z.object({ activity: z.record(z.unknown()) }).parse({ activity });
|
|
222
|
+
if (!(activity.timestamp instanceof Date)) {
|
|
223
|
+
activity.timestamp = activity.timestamp ? new Date(activity.timestamp) : new Date();
|
|
224
|
+
}
|
|
225
|
+
await this._initialize();
|
|
226
|
+
const blob = this._containerClient.getBlockBlobClient(getBlobKey(activity, options));
|
|
227
|
+
const serialized = JSON.stringify(activity);
|
|
228
|
+
const metadata = {
|
|
229
|
+
FromId: (_b = (_a = activity.from) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : '',
|
|
230
|
+
RecipientId: (_d = (_c = activity.recipient) === null || _c === void 0 ? void 0 : _c.id) !== null && _d !== void 0 ? _d : '',
|
|
231
|
+
};
|
|
232
|
+
if (activity.id) {
|
|
233
|
+
metadata.Id = activity.id;
|
|
234
|
+
}
|
|
235
|
+
if (activity.timestamp) {
|
|
236
|
+
metadata.Timestamp = new Date(activity.timestamp).toJSON();
|
|
237
|
+
}
|
|
238
|
+
await blob.upload(serialized, serialized.length, { metadata });
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
exports.BlobsTranscriptStore = BlobsTranscriptStore;
|
|
242
|
+
//# sourceMappingURL=blobsTranscriptStore.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blobsTranscriptStore.js","sourceRoot":"","sources":["../../src/blobsTranscriptStore.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A function type that determines whether an error should be ignored.
|
|
3
|
+
* @param err - The error to check.
|
|
4
|
+
* @returns A boolean indicating whether the error should be ignored.
|
|
5
|
+
*/
|
|
6
|
+
export type IgnoreError = (err: Error) => boolean;
|
|
7
|
+
export declare function ignoreError<T>(promise: Promise<T>, ignore: IgnoreError): Promise<T | null>;
|
|
8
|
+
export declare function isStatusCodeError(...codes: number[]): IgnoreError;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ignoreError = ignoreError;
|
|
4
|
+
exports.isStatusCodeError = isStatusCodeError;
|
|
5
|
+
async function ignoreError(promise, ignore) {
|
|
6
|
+
try {
|
|
7
|
+
return await promise;
|
|
8
|
+
}
|
|
9
|
+
catch (err) {
|
|
10
|
+
if (!ignore(err)) {
|
|
11
|
+
throw err;
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function isStatusCodeError(...codes) {
|
|
19
|
+
const ignoredCodes = new Set(codes);
|
|
20
|
+
return function (err) {
|
|
21
|
+
return (typeof err.statusCode === 'number' &&
|
|
22
|
+
ignoredCodes.has(err.statusCode));
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=ignoreError.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ignoreError.js","sourceRoot":"","sources":["../../src/ignoreError.ts"],"names":[],"mappings":";;AAOA,kCAUC;AASD,8CAQC;AA3BM,KAAK,UAAU,WAAW,CAAK,OAAmB,EAAE,MAAmB;IAC5E,IAAI,CAAC;QACH,OAAO,MAAM,OAAO,CAAA;IACtB,CAAC;IAAC,OAAO,GAAQ,EAAE,CAAC;QAClB,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;YACjB,MAAM,GAAG,CAAA;QACX,CAAC;aAAM,CAAC;YACN,OAAO,IAAI,CAAA;QACb,CAAC;IACH,CAAC;AACH,CAAC;AASD,SAAgB,iBAAiB,CAAE,GAAG,KAAe;IACnD,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAA;IACnC,OAAO,UAAU,GAAG;QAClB,OAAO,CACL,OAAQ,GAA2B,CAAC,UAAU,KAAK,QAAQ;YACrD,YAAY,CAAC,GAAG,CAAE,GAA2B,CAAC,UAAW,CAAC,CACjE,CAAA;IACH,CAAC,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
|
+
// Licensed under the MIT License.
|
|
4
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
5
|
+
if (k2 === undefined) k2 = k;
|
|
6
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
7
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
8
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
9
|
+
}
|
|
10
|
+
Object.defineProperty(o, k2, desc);
|
|
11
|
+
}) : (function(o, m, k, k2) {
|
|
12
|
+
if (k2 === undefined) k2 = k;
|
|
13
|
+
o[k2] = m[k];
|
|
14
|
+
}));
|
|
15
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
16
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
17
|
+
};
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
__exportStar(require("./blobsStorage"), exports);
|
|
20
|
+
__exportStar(require("./blobsTranscriptStore"), exports);
|
|
21
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AAAA,4DAA4D;AAC5D,kCAAkC;;;;;;;;;;;;;;;;AAElC,iDAA8B;AAC9B,yDAAsC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/package.json",
|
|
3
|
+
"name": "@microsoft/agents-hosting-storage-blob",
|
|
4
|
+
"version": "0.1.49",
|
|
5
|
+
"homepage": "https://github.com/microsoft/Agents-for-js",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/microsoft/Agents-for-js.git"
|
|
9
|
+
},
|
|
10
|
+
"author": {
|
|
11
|
+
"name": "Microsoft",
|
|
12
|
+
"email": "agentssdk@microsoft.com",
|
|
13
|
+
"url": "https://aka.ms/Agents"
|
|
14
|
+
},
|
|
15
|
+
"description": "Microsoft 365 Agents SDK State Blob Storage support.",
|
|
16
|
+
"main": "dist/index.js",
|
|
17
|
+
"types": "dist/src/index.d.ts",
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@azure/storage-blob": "^12.27.0",
|
|
20
|
+
"@microsoft/agents-hosting": "0.1.49"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {},
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"files": [
|
|
25
|
+
"README.md",
|
|
26
|
+
"dist/src",
|
|
27
|
+
"src"
|
|
28
|
+
],
|
|
29
|
+
"exports": {
|
|
30
|
+
".": {
|
|
31
|
+
"import": "./dist/src/index.js",
|
|
32
|
+
"require": "./dist/src/index.js"
|
|
33
|
+
},
|
|
34
|
+
"./package.json": "./package.json"
|
|
35
|
+
},
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=18.0.0"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import * as z from 'zod'
|
|
2
|
+
import StreamConsumers from 'stream/consumers'
|
|
3
|
+
import {
|
|
4
|
+
AnonymousCredential,
|
|
5
|
+
ContainerClient,
|
|
6
|
+
StoragePipelineOptions,
|
|
7
|
+
StorageSharedKeyCredential,
|
|
8
|
+
} from '@azure/storage-blob'
|
|
9
|
+
import { Storage, StoreItems } from '@microsoft/agents-hosting'
|
|
10
|
+
import { sanitizeBlobKey } from './blobsTranscriptStore'
|
|
11
|
+
import { ignoreError, isStatusCodeError } from './ignoreError'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Options for configuring the BlobsStorage.
|
|
15
|
+
*/
|
|
16
|
+
export interface BlobsStorageOptions {
|
|
17
|
+
storagePipelineOptions?: StoragePipelineOptions;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* A class that implements the Storage interface using Azure Blob Storage.
|
|
22
|
+
*/
|
|
23
|
+
export class BlobsStorage implements Storage {
|
|
24
|
+
private readonly _containerClient: ContainerClient
|
|
25
|
+
private readonly _concurrency = Infinity
|
|
26
|
+
private _initializePromise?: Promise<unknown>
|
|
27
|
+
|
|
28
|
+
constructor (
|
|
29
|
+
connectionString: string,
|
|
30
|
+
containerName: string,
|
|
31
|
+
options?: BlobsStorageOptions,
|
|
32
|
+
url = '',
|
|
33
|
+
credential?: StorageSharedKeyCredential | AnonymousCredential
|
|
34
|
+
) {
|
|
35
|
+
if (url !== '' && credential != null) {
|
|
36
|
+
z.object({ url: z.string() }).parse({
|
|
37
|
+
url,
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
this._containerClient = new ContainerClient(url, credential, options?.storagePipelineOptions)
|
|
41
|
+
|
|
42
|
+
if (url.trim() === 'UseDevelopmentStorage=true;') {
|
|
43
|
+
this._concurrency = 1
|
|
44
|
+
}
|
|
45
|
+
} else {
|
|
46
|
+
z.object({ connectionString: z.string(), containerName: z.string() }).parse({
|
|
47
|
+
connectionString,
|
|
48
|
+
containerName,
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
this._containerClient = new ContainerClient(
|
|
52
|
+
connectionString,
|
|
53
|
+
containerName,
|
|
54
|
+
options?.storagePipelineOptions
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
if (connectionString.trim() === 'UseDevelopmentStorage=true;') {
|
|
58
|
+
this._concurrency = 1
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private toJSON (): unknown {
|
|
64
|
+
return { name: 'BlobsStorage' }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private _initialize (): Promise<unknown> {
|
|
68
|
+
if (!this._initializePromise) {
|
|
69
|
+
this._initializePromise = this._containerClient.createIfNotExists()
|
|
70
|
+
}
|
|
71
|
+
return this._initializePromise
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async read (keys: string[]): Promise<StoreItems> {
|
|
75
|
+
z.object({ keys: z.array(z.string()) }).parse({ keys })
|
|
76
|
+
|
|
77
|
+
await this._initialize()
|
|
78
|
+
|
|
79
|
+
const results = await Promise.all(keys.map(async (key) => {
|
|
80
|
+
const result = { key, value: undefined }
|
|
81
|
+
|
|
82
|
+
const blob = await ignoreError(
|
|
83
|
+
this._containerClient.getBlobClient(sanitizeBlobKey(key)).download(),
|
|
84
|
+
isStatusCodeError(404)
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
if (!blob) {
|
|
88
|
+
return result
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const { etag: eTag, readableStreamBody } = blob
|
|
92
|
+
if (!readableStreamBody) {
|
|
93
|
+
return result
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const parsed = (await StreamConsumers.json(readableStreamBody)) as any
|
|
97
|
+
result.value = { ...parsed, eTag }
|
|
98
|
+
|
|
99
|
+
return result
|
|
100
|
+
}))
|
|
101
|
+
|
|
102
|
+
return results.reduce((acc, { key, value }) => (value ? { ...acc, [key]: value } : acc), {})
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async write (changes: StoreItems): Promise<void> {
|
|
106
|
+
z.record(z.unknown()).parse(changes)
|
|
107
|
+
|
|
108
|
+
await this._initialize()
|
|
109
|
+
|
|
110
|
+
await Promise.all(
|
|
111
|
+
Object.entries(changes).map(async ([key, { eTag = '', ...change }]) => {
|
|
112
|
+
try {
|
|
113
|
+
const blob = this._containerClient.getBlockBlobClient(sanitizeBlobKey(key))
|
|
114
|
+
const serialized = JSON.stringify(change)
|
|
115
|
+
return await blob.upload(serialized, serialized.length, {
|
|
116
|
+
conditions: typeof eTag === 'string' && eTag !== '*' ? { ifMatch: eTag } : {},
|
|
117
|
+
blobHTTPHeaders: { blobContentType: 'application/json' },
|
|
118
|
+
})
|
|
119
|
+
} catch (err: any) {
|
|
120
|
+
if (err.statusCode === 412) {
|
|
121
|
+
throw new Error(`Storage: error writing "${key}" due to eTag conflict.`)
|
|
122
|
+
} else {
|
|
123
|
+
throw err
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
})
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async delete (keys: string[]): Promise<void> {
|
|
131
|
+
z.object({ keys: z.array(z.string()) }).parse({ keys })
|
|
132
|
+
|
|
133
|
+
await this._initialize()
|
|
134
|
+
|
|
135
|
+
await Promise.all(
|
|
136
|
+
keys.map((key) => ignoreError(this._containerClient.deleteBlob(sanitizeBlobKey(key)), isStatusCodeError(404)))
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import * as z from 'zod'
|
|
2
|
+
import StreamConsumers from 'stream/consumers'
|
|
3
|
+
import { Activity } from '@microsoft/agents-activity'
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
AnonymousCredential,
|
|
7
|
+
BlobItem,
|
|
8
|
+
ContainerClient,
|
|
9
|
+
ContainerListBlobHierarchySegmentResponse,
|
|
10
|
+
StoragePipelineOptions,
|
|
11
|
+
StorageSharedKeyCredential,
|
|
12
|
+
} from '@azure/storage-blob'
|
|
13
|
+
import { TranscriptStore, PagedResult, TranscriptInfo } from '@microsoft/agents-hosting'
|
|
14
|
+
|
|
15
|
+
function formatTicks (timestamp: Date): string {
|
|
16
|
+
const epochTicks = 621355968000000000
|
|
17
|
+
const ticksPerMillisecond = 10000
|
|
18
|
+
const ticks = epochTicks + timestamp.getTime() * ticksPerMillisecond
|
|
19
|
+
return ticks.toString(16)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function getChannelPrefix (channelId: string): string {
|
|
23
|
+
return sanitizeBlobKey(`${channelId}/`)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function getConversationPrefix (channelId: string, conversationId: string): string {
|
|
27
|
+
return sanitizeBlobKey(`${channelId}/${conversationId}`)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function getBlobKey (activity: Activity, options?: BlobsTranscriptStoreOptions): string {
|
|
31
|
+
if (!(activity.timestamp instanceof Date)) {
|
|
32
|
+
throw new Error('Invalid timestamp: must be an instance of Date')
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const { timestamp } = z
|
|
36
|
+
.object({ timestamp: z.instanceof(Date) })
|
|
37
|
+
.passthrough()
|
|
38
|
+
.parse(activity)
|
|
39
|
+
return sanitizeBlobKey(
|
|
40
|
+
[activity.channelId, activity.conversation?.id, `${formatTicks(timestamp)}-${activity.id}.json`].join('/'),
|
|
41
|
+
options
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function sanitizeBlobKey (key: string, options?: BlobsTranscriptStoreOptions): string {
|
|
46
|
+
if (!key || key.length === 0) {
|
|
47
|
+
throw new Error('Please provide a non-empty key')
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const sanitized = key.split('/').reduce((acc, part, idx) => {
|
|
51
|
+
return part ? `${acc}/${part}` : acc
|
|
52
|
+
}, '').substr(0, 1024)
|
|
53
|
+
|
|
54
|
+
const encodedKey = encodeURIComponent(sanitized).substr(0, 1024)
|
|
55
|
+
|
|
56
|
+
if (options?.decodeTranscriptKey) {
|
|
57
|
+
return decodeURIComponent(encodedKey).substr(0, 1024)
|
|
58
|
+
}
|
|
59
|
+
return encodedKey
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function maybeCast<T> (value: unknown, ctor?: { new (...args: any[]): T }): T {
|
|
63
|
+
if (ctor != null && value instanceof ctor) {
|
|
64
|
+
return value
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return value as T
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const MAX_PAGE_SIZE = 20
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Options for configuring the BlobsTranscriptStore.
|
|
74
|
+
*/
|
|
75
|
+
export interface BlobsTranscriptStoreOptions {
|
|
76
|
+
storagePipelineOptions?: StoragePipelineOptions;
|
|
77
|
+
decodeTranscriptKey?: boolean;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* A class that implements the TranscriptStore interface using Azure Blob Storage.
|
|
82
|
+
*/
|
|
83
|
+
export class BlobsTranscriptStore implements TranscriptStore {
|
|
84
|
+
private readonly _containerClient: ContainerClient
|
|
85
|
+
private readonly _concurrency = Infinity
|
|
86
|
+
private _initializePromise?: Promise<unknown>
|
|
87
|
+
private _isDecodeTranscriptKey?: boolean = false
|
|
88
|
+
|
|
89
|
+
constructor (
|
|
90
|
+
connectionString: string,
|
|
91
|
+
containerName: string,
|
|
92
|
+
options?: BlobsTranscriptStoreOptions,
|
|
93
|
+
blobServiceUri = '',
|
|
94
|
+
tokenCredential?: StorageSharedKeyCredential | AnonymousCredential
|
|
95
|
+
) {
|
|
96
|
+
if (blobServiceUri !== '' && tokenCredential !== null) {
|
|
97
|
+
z.object({ blobServiceUri: z.string() }).parse({
|
|
98
|
+
blobServiceUri,
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
this._containerClient = new ContainerClient(
|
|
102
|
+
blobServiceUri,
|
|
103
|
+
tokenCredential,
|
|
104
|
+
options?.storagePipelineOptions
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
if (blobServiceUri.trim() === 'UseDevelopmentStorage=true;') {
|
|
108
|
+
this._concurrency = 1
|
|
109
|
+
}
|
|
110
|
+
} else {
|
|
111
|
+
z.object({ connectionString: z.string(), containerName: z.string() }).parse({
|
|
112
|
+
connectionString,
|
|
113
|
+
containerName,
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
this._containerClient = new ContainerClient(
|
|
117
|
+
connectionString,
|
|
118
|
+
containerName,
|
|
119
|
+
options?.storagePipelineOptions
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
if (connectionString.trim() === 'UseDevelopmentStorage=true;') {
|
|
123
|
+
this._concurrency = 1
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
this._isDecodeTranscriptKey = options?.decodeTranscriptKey
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
private _initialize (): Promise<unknown> {
|
|
131
|
+
if (!this._initializePromise) {
|
|
132
|
+
this._initializePromise = this._containerClient.createIfNotExists()
|
|
133
|
+
}
|
|
134
|
+
return this._initializePromise
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async getTranscriptActivities (
|
|
138
|
+
channelId: string,
|
|
139
|
+
conversationId: string,
|
|
140
|
+
continuationToken?: string,
|
|
141
|
+
startDate?: Date
|
|
142
|
+
): Promise<PagedResult<Activity>> {
|
|
143
|
+
z.object({ channelId: z.string(), conversationId: z.string() }).parse({ channelId, conversationId })
|
|
144
|
+
|
|
145
|
+
await this._initialize()
|
|
146
|
+
|
|
147
|
+
const prefix = getConversationPrefix(channelId, conversationId)
|
|
148
|
+
console.log(`Using prefix: ${prefix}`)
|
|
149
|
+
|
|
150
|
+
const iter = this._containerClient
|
|
151
|
+
.listBlobsByHierarchy('/', {
|
|
152
|
+
prefix,
|
|
153
|
+
})
|
|
154
|
+
.byPage({ continuationToken, maxPageSize: MAX_PAGE_SIZE })
|
|
155
|
+
|
|
156
|
+
let page = await iter.next()
|
|
157
|
+
const result: Activity[] = []
|
|
158
|
+
let response: ContainerListBlobHierarchySegmentResponse | undefined
|
|
159
|
+
while (!page.done) {
|
|
160
|
+
response = maybeCast<ContainerListBlobHierarchySegmentResponse>(page?.value ?? {})
|
|
161
|
+
const blobItems = response?.segment?.blobItems ?? []
|
|
162
|
+
|
|
163
|
+
console.log(`Fetched ${blobItems.length} blob items.`)
|
|
164
|
+
|
|
165
|
+
const fromIdx =
|
|
166
|
+
startDate != null
|
|
167
|
+
? blobItems.findIndex(
|
|
168
|
+
(blobItem: BlobItem) => blobItem?.properties?.createdOn && blobItem?.properties?.createdOn >= startDate
|
|
169
|
+
)
|
|
170
|
+
: 0
|
|
171
|
+
|
|
172
|
+
console.log(`fromIdx: ${fromIdx}`)
|
|
173
|
+
|
|
174
|
+
if (fromIdx !== -1) {
|
|
175
|
+
const activities = await Promise.all(
|
|
176
|
+
blobItems.slice(fromIdx).map(async (blobItem: BlobItem) => {
|
|
177
|
+
const blob = await this._containerClient.getBlobClient(blobItem.name).download()
|
|
178
|
+
|
|
179
|
+
const { readableStreamBody } = blob
|
|
180
|
+
if (!readableStreamBody) {
|
|
181
|
+
return null
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const activity = (await StreamConsumers.json(readableStreamBody)) as any
|
|
185
|
+
return { ...activity, timestamp: new Date(activity.timestamp) } as Activity
|
|
186
|
+
})
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
activities.forEach((activity: Activity | null) => {
|
|
190
|
+
if (activity) result.push(activity)
|
|
191
|
+
})
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
page = await iter.next()
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
console.log(`Total activities fetched: ${result.length}`)
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
continuationToken: response?.continuationToken ?? '',
|
|
201
|
+
items: result.reduce<Activity[]>((acc, activity) => (activity ? acc.concat(activity) : acc), []),
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async listTranscripts (channelId: string, continuationToken?: string): Promise<PagedResult<TranscriptInfo>> {
|
|
206
|
+
z.object({ channelId: z.string() }).parse({ channelId })
|
|
207
|
+
|
|
208
|
+
await this._initialize()
|
|
209
|
+
|
|
210
|
+
const iter = this._containerClient
|
|
211
|
+
.listBlobsByHierarchy('/', {
|
|
212
|
+
prefix: getChannelPrefix(channelId),
|
|
213
|
+
})
|
|
214
|
+
.byPage({ continuationToken, maxPageSize: MAX_PAGE_SIZE })
|
|
215
|
+
|
|
216
|
+
let page = await iter.next()
|
|
217
|
+
const result: any[] = []
|
|
218
|
+
let response: ContainerListBlobHierarchySegmentResponse | undefined
|
|
219
|
+
|
|
220
|
+
while (!page.done) {
|
|
221
|
+
const response = maybeCast<ContainerListBlobHierarchySegmentResponse>(page?.value ?? {})
|
|
222
|
+
const blobItems = response?.segment?.blobItems ?? []
|
|
223
|
+
|
|
224
|
+
const items = blobItems.map((blobItem) => {
|
|
225
|
+
const [, id] = decodeURIComponent(blobItem.name).split('/')
|
|
226
|
+
|
|
227
|
+
const created = blobItem.metadata?.timestamp ? new Date(blobItem.metadata.timestamp) : new Date()
|
|
228
|
+
|
|
229
|
+
return { channelId, created, id }
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
items.forEach((transcript) => {
|
|
233
|
+
if (transcript) result.push(transcript)
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
page = await iter.next()
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return {
|
|
240
|
+
continuationToken: response?.continuationToken ?? '',
|
|
241
|
+
items: result ?? [],
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async deleteTranscript (channelId: string, conversationId: string): Promise<void> {
|
|
246
|
+
z.object({ channelId: z.string(), conversationId: z.string() }).parse({ channelId, conversationId })
|
|
247
|
+
|
|
248
|
+
await this._initialize()
|
|
249
|
+
|
|
250
|
+
const iter = this._containerClient
|
|
251
|
+
.listBlobsByHierarchy('/', {
|
|
252
|
+
prefix: getConversationPrefix(channelId, conversationId),
|
|
253
|
+
})
|
|
254
|
+
.byPage({
|
|
255
|
+
maxPageSize: MAX_PAGE_SIZE,
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
let page = await iter.next()
|
|
259
|
+
while (!page.done) {
|
|
260
|
+
const response = maybeCast<ContainerListBlobHierarchySegmentResponse>(page?.value ?? {})
|
|
261
|
+
const blobItems = response?.segment?.blobItems ?? []
|
|
262
|
+
|
|
263
|
+
const deletionPromises = blobItems.map(blobItem =>
|
|
264
|
+
this._containerClient.deleteBlob(blobItem.name)
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
await Promise.all(deletionPromises)
|
|
268
|
+
|
|
269
|
+
page = await iter.next()
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
async logActivity (activity: Activity, options?: BlobsTranscriptStoreOptions): Promise<void> {
|
|
274
|
+
z.object({ activity: z.record(z.unknown()) }).parse({ activity })
|
|
275
|
+
|
|
276
|
+
if (!(activity.timestamp instanceof Date)) {
|
|
277
|
+
activity.timestamp = activity.timestamp ? new Date(activity.timestamp) : new Date()
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
await this._initialize()
|
|
281
|
+
|
|
282
|
+
const blob = this._containerClient.getBlockBlobClient(getBlobKey(activity, options))
|
|
283
|
+
const serialized = JSON.stringify(activity)
|
|
284
|
+
const metadata: Record<string, string> = {
|
|
285
|
+
FromId: activity.from?.id ?? '',
|
|
286
|
+
RecipientId: activity.recipient?.id ?? '',
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (activity.id) {
|
|
290
|
+
metadata.Id = activity.id
|
|
291
|
+
}
|
|
292
|
+
if (activity.timestamp) {
|
|
293
|
+
metadata.Timestamp = new Date(activity.timestamp).toJSON()
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
await blob.upload(serialized, serialized.length, { metadata })
|
|
297
|
+
}
|
|
298
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A function type that determines whether an error should be ignored.
|
|
3
|
+
* @param err - The error to check.
|
|
4
|
+
* @returns A boolean indicating whether the error should be ignored.
|
|
5
|
+
*/
|
|
6
|
+
export type IgnoreError = (err: Error) => boolean
|
|
7
|
+
|
|
8
|
+
export async function ignoreError<T> (promise: Promise<T>, ignore: IgnoreError): Promise<T | null> {
|
|
9
|
+
try {
|
|
10
|
+
return await promise
|
|
11
|
+
} catch (err: any) {
|
|
12
|
+
if (!ignore(err)) {
|
|
13
|
+
throw err
|
|
14
|
+
} else {
|
|
15
|
+
return null
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* An interface representing an error with an optional status code.
|
|
22
|
+
*/
|
|
23
|
+
interface ErrorWithStatusCode {
|
|
24
|
+
statusCode?: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function isStatusCodeError (...codes: number[]): IgnoreError {
|
|
28
|
+
const ignoredCodes = new Set(codes)
|
|
29
|
+
return function (err) {
|
|
30
|
+
return (
|
|
31
|
+
typeof (err as ErrorWithStatusCode).statusCode === 'number' &&
|
|
32
|
+
ignoredCodes.has((err as ErrorWithStatusCode).statusCode!)
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
}
|