@webex/internal-plugin-call-ai-summary 0.0.0-next.1

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/.eslintrc.js ADDED
@@ -0,0 +1,6 @@
1
+ const config = {
2
+ root: true,
3
+ extends: ['@webex/eslint-config-legacy'],
4
+ };
5
+
6
+ module.exports = config;
package/README.md ADDED
@@ -0,0 +1,257 @@
1
+ # @webex/internal-plugin-call-ai-summary
2
+
3
+ Internal Webex JS SDK plugin for retrieving AI-generated call summaries, notes, action items, and transcripts from completed calls.
4
+
5
+ ## Overview
6
+
7
+ This plugin resolves AI summary containers via the **Pragya** service and fetches encrypted summary content from URLs returned by Pragya. All AI-generated content is decrypted using KMS keys provided in the container response.
8
+
9
+ **Discovery flow:**
10
+
11
+ 1. **Janus** (call history) returns `extensionPayload.callingContainerIds` per call session
12
+ 2. **Pragya** resolves a container ID into metadata including content URLs and encryption key
13
+ 3. **Plugin** fetches content from those URLs and decrypts using `@webex/internal-plugin-encryption`
14
+
15
+ ## Install
16
+
17
+ This plugin is part of the Webex JS SDK monorepo. It self-registers when imported — no changes to `packages/webex` are needed.
18
+
19
+ ```bash
20
+ # From the SDK monorepo root
21
+ yarn
22
+ ```
23
+
24
+ To use in a consuming application:
25
+
26
+ ```javascript
27
+ // Importing the plugin auto-registers it on webex.internal.aisummary
28
+ import '@webex/internal-plugin-call-ai-summary';
29
+ ```
30
+
31
+ ## Prerequisites
32
+
33
+ - An authenticated Webex SDK instance with a registered device
34
+ - `@webex/internal-plugin-encryption` (pulled in automatically as a dependency)
35
+ - A valid Pragya container ID (obtained from Janus call history `extensionPayload.callingContainerIds`)
36
+
37
+ ## API
38
+
39
+ All methods are accessible via `webex.internal.aisummary`.
40
+
41
+ ### `getContainer({ containerId })`
42
+
43
+ Resolves a Pragya container by ID. Returns container metadata with summary content URLs and the KMS encryption key.
44
+
45
+ The raw Pragya response nests URLs under `summaryData.data` — this method flattens it so you can access `summaryData.summaryUrl` directly.
46
+
47
+ ```typescript
48
+ const container = await webex.internal.aisummary.getContainer({
49
+ containerId: '34125120-13b5-11f1-9b36-adb685725098',
50
+ });
51
+
52
+ // container.summaryData.summaryUrl — full summary URL
53
+ // container.summaryData.transcriptUrl — transcript URL
54
+ // container.summaryData.status — "Active" when ready
55
+ // container.encryptionKeyUrl — KMS key for decryption
56
+ ```
57
+
58
+ **Returns:** `Promise<PragyaContainerResponse>`
59
+
60
+ ### `getSummary({ containerInfo })`
61
+
62
+ Fetches and decrypts all summary content (note, short note, and action items) in a single request via `summaryUrl?fields=note,shortnote,actionitems`.
63
+
64
+ This is the **recommended** method for retrieving summary content.
65
+
66
+ ```typescript
67
+ const summary = await webex.internal.aisummary.getSummary({
68
+ containerInfo: container,
69
+ });
70
+
71
+ console.log(summary.note); // Decrypted full note
72
+ console.log(summary.shortNote); // Decrypted short note
73
+ summary.actionItems.forEach((item) => {
74
+ console.log(item.aiGeneratedContent); // Decrypted action item
75
+ });
76
+ ```
77
+
78
+ **Returns:** `Promise<SummaryContent>` — `{ id, note, shortNote, actionItems, feedbackUrl? }`
79
+
80
+ ### `getNotes({ containerInfo })`
81
+
82
+ Fetches and decrypts notes from the standalone `notesUrl` endpoint. Only available if `notesUrl` is present in the Pragya response.
83
+
84
+ ```typescript
85
+ const notes = await webex.internal.aisummary.getNotes({
86
+ containerInfo: container,
87
+ });
88
+
89
+ console.log(notes.content); // Decrypted notes
90
+ ```
91
+
92
+ **Returns:** `Promise<SummaryNotes>` — `{ id, content, feedbackUrl? }`
93
+
94
+ ### `getActionItems({ containerInfo })`
95
+
96
+ Fetches and decrypts action items from the standalone `actionItemsUrl` endpoint. Only available if `actionItemsUrl` is present in the Pragya response.
97
+
98
+ ```typescript
99
+ const actionItems = await webex.internal.aisummary.getActionItems({
100
+ containerInfo: container,
101
+ });
102
+
103
+ actionItems.snippets.forEach((snippet) => {
104
+ console.log(snippet.aiGeneratedContent); // Decrypted
105
+ console.log(snippet.editedContent); // User-edited version (if any)
106
+ });
107
+ ```
108
+
109
+ **Returns:** `Promise<SummaryActionItems>` — `{ id?, snippets[], feedbackUrl? }`
110
+
111
+ ### `getTranscriptUrl({ containerInfo })`
112
+
113
+ Returns the transcript URL string without fetching or decrypting. Use this when you need the URL for downstream processing.
114
+
115
+ ```typescript
116
+ const url = webex.internal.aisummary.getTranscriptUrl({
117
+ containerInfo: container,
118
+ });
119
+ ```
120
+
121
+ **Returns:** `string`
122
+
123
+ ### `getTranscript({ containerInfo })`
124
+
125
+ Fetches and decrypts the full call transcript.
126
+
127
+ ```typescript
128
+ const transcript = await webex.internal.aisummary.getTranscript({
129
+ containerInfo: container,
130
+ });
131
+
132
+ transcript.snippets.forEach((snippet) => {
133
+ console.log(`[${snippet.startTime}] ${snippet.speaker?.speakerName}: ${snippet.content}`);
134
+ });
135
+ ```
136
+
137
+ **Returns:** `Promise<TranscriptContent>` — `{ id, totalCount, snippets[] }`
138
+
139
+ ## Full Usage Example
140
+
141
+ ```typescript
142
+ import '@webex/internal-plugin-call-ai-summary';
143
+
144
+ // 1. Get call history (existing SDK API)
145
+ const callHistory = await callHistoryInstance.getCallHistoryData(10, 50);
146
+ const sessions = callHistory.data.userSessions;
147
+
148
+ // 2. Find a session with AI summary
149
+ const session = sessions.find(
150
+ (s) => s.extensionPayload?.callingContainerIds?.length > 0
151
+ );
152
+ if (!session) return;
153
+
154
+ // 3. Resolve the container
155
+ const containerId = session.extensionPayload.callingContainerIds[0];
156
+ const container = await webex.internal.aisummary.getContainer({ containerId });
157
+
158
+ if (container.summaryData.status !== 'Active') {
159
+ console.log('Summary not ready yet');
160
+ return;
161
+ }
162
+
163
+ // 4. Fetch all summary content in one call
164
+ const summary = await webex.internal.aisummary.getSummary({ containerInfo: container });
165
+ console.log('Note:', summary.note);
166
+ console.log('Short Note:', summary.shortNote);
167
+ summary.actionItems.forEach((item, i) => {
168
+ console.log(`Action ${i + 1}: ${item.aiGeneratedContent}`);
169
+ });
170
+
171
+ // 5. Fetch transcript
172
+ const transcript = await webex.internal.aisummary.getTranscript({ containerInfo: container });
173
+ transcript.snippets.forEach((s) => {
174
+ console.log(`[${s.startTime}] ${s.speaker?.speakerName}: ${s.content}`);
175
+ });
176
+ ```
177
+
178
+ ## Manual Testing
179
+
180
+ A manual integration test is provided for verifying against live APIs:
181
+
182
+ ```bash
183
+ cd packages/@webex/internal-plugin-call-ai-summary
184
+
185
+ # Provide a fresh token and container ID
186
+ WEBEX_TOKEN='<token>' CONTAINER_ID='<id>' node src/manual-integration-test.js
187
+ ```
188
+
189
+ This script registers a device (WDM), resolves the Pragya service via the SDK service catalog, fetches the container, decrypts summary content via KMS, and prints the results.
190
+
191
+ ## Error Handling
192
+
193
+ | Error | Cause | Recovery |
194
+ |-------|-------|----------|
195
+ | `containerId is required and must be a non-empty string` | Empty or missing containerId | Validate input before calling |
196
+ | `containerInfo with valid summaryData and encryptionKeyUrl is required` | Missing container info or URL field | Call `getContainer()` first |
197
+ | `Container not found` | 404 from Pragya | Verify containerId from Janus |
198
+ | `Summary content not available or expired` | 404 from content endpoint | Content may have been deleted |
199
+ | `Access denied: User not authorized to view this summary` | 403 | Check user permissions / org AI settings |
200
+ | `Authentication failed: Invalid or expired token` | 401 | Re-authenticate the user |
201
+
202
+ ## Encryption
203
+
204
+ All AI-generated content (`aiGeneratedContent` fields) is encrypted with KMS. The plugin decrypts automatically using:
205
+
206
+ - **Key source:** `encryptionKeyUrl` from the Pragya container response, with fallback to `keyUrl` from the content response body
207
+ - **Decryption method:** `webex.internal.encryption.decryptText(keyUrl, ciphertext)`
208
+
209
+ This is the same pattern used by `@webex/internal-plugin-ai-assistant` and `@webex/internal-plugin-task`.
210
+
211
+ ## Development
212
+
213
+ ```bash
214
+ cd packages/@webex/internal-plugin-call-ai-summary
215
+
216
+ # Build
217
+ yarn build
218
+
219
+ # Lint
220
+ yarn test:style
221
+
222
+ # Unit tests
223
+ yarn test:unit
224
+
225
+ # All checks
226
+ yarn test
227
+ ```
228
+
229
+ ## Package Structure
230
+
231
+ ```
232
+ src/
233
+ index.ts # Self-registration via registerInternalPlugin('aisummary', ...)
234
+ ai-summary.ts # Plugin implementation (WebexPlugin.extend)
235
+ config.ts # Plugin config
236
+ constants.ts # Service name, error messages
237
+ types.ts # TypeScript interfaces
238
+ test/
239
+ unit/
240
+ spec/
241
+ ai-summary.ts # Unit tests (26 tests)
242
+ data/
243
+ responses.ts # Mock API response fixtures
244
+ ai-docs/
245
+ ARCHITECTURE.md # Detailed architecture document
246
+ ```
247
+
248
+ ## Dependencies
249
+
250
+ | Package | Purpose |
251
+ |---------|---------|
252
+ | `@webex/webex-core` | Plugin infrastructure (`WebexPlugin`, `registerInternalPlugin`) |
253
+ | `@webex/internal-plugin-encryption` | KMS content decryption via `decryptText()` |
254
+
255
+ ## Architecture
256
+
257
+ See [ai-docs/ARCHITECTURE.md](ai-docs/ARCHITECTURE.md) for the full architecture document covering data flows, API request/response details, DTOs, security considerations, and testing strategy.
@@ -0,0 +1,300 @@
1
+ # @webex/internal-plugin-call-ai-summary
2
+
3
+ This is an internal Cisco Webex plugin. As such, it does not strictly adhere to semantic versioning. Use at your own risk. If you're not working on one of our first party clients, please look at our developer api and stick to our public plugins.
4
+ Internal Webex JS SDK plugin for retrieving AI-generated call summaries, notes, action items, and transcript URLs from the Pragya and AI Bridge services.
5
+
6
+ ## Overview
7
+
8
+ This plugin provides methods to:
9
+
10
+ 1. Resolve a **Pragya container** by ID (returns metadata, summary URLs, and encryption key)
11
+ 2. Fetch and decrypt **AI-generated summaries** (note, short note, action items) in a single call
12
+ 3. Fetch and decrypt **AI-generated notes** via a dedicated notes endpoint
13
+ 4. Fetch and decrypt **AI-generated action items** via a dedicated action items endpoint
14
+ 5. Retrieve the **transcript URL** for a call
15
+
16
+ All AI-generated content is **JWE-encrypted** and decrypted via the KMS (Key Management Service) using `@webex/internal-plugin-encryption`.
17
+
18
+ ## Architecture
19
+
20
+ ```
21
+ Pragya Service AI Bridge Service
22
+ (container metadata) (summary content)
23
+ | |
24
+ getContainer() getSummary() / getNotes() / getActionItems()
25
+ | |
26
+ v v
27
+ PragyaContainerResponse Encrypted JWE content
28
+ (summaryData, encryptionKeyUrl) |
29
+ v
30
+ KMS Decryption
31
+ (internal-plugin-encryption)
32
+ |
33
+ v
34
+ Decrypted plaintext (HTML)
35
+ ```
36
+
37
+ **Note:** The Pragya API returns summary URLs nested under `summaryData.data`. The `getContainer()` method normalizes this automatically, flattening `summaryData.data` into `summaryData` so consumers can access `summaryData.summaryUrl` directly.
38
+
39
+ ## Registration
40
+
41
+ The plugin registers itself as `aisummary` on the internal namespace:
42
+
43
+ ```typescript
44
+ import '@webex/internal-plugin-call-ai-summary';
45
+
46
+ // Accessed via:
47
+ webex.internal.aisummary.getContainer({ containerId: '...' });
48
+ ```
49
+
50
+ ## Source Files
51
+
52
+ | File | Description |
53
+ |------|-------------|
54
+ | `src/index.ts` | Entry point. Registers the plugin via `registerInternalPlugin('aisummary', ...)`. |
55
+ | `src/ai-summary.ts` | Main plugin class extending `WebexPlugin`. Contains all public and private methods. |
56
+ | `src/types.ts` | TypeScript interfaces for request/response DTOs. |
57
+ | `src/constants.ts` | Service name, resource path, and error message constants. |
58
+ | `src/config.ts` | Plugin configuration (currently empty). |
59
+
60
+ ## API Reference
61
+
62
+ ### `getContainer(options: GetContainerOptions): Promise<PragyaContainerResponse>`
63
+
64
+ Resolves a Pragya container by ID. Returns container metadata including summary URLs and the KMS encryption key URL. Normalizes the response by flattening `summaryData.data` into `summaryData`.
65
+
66
+ ```typescript
67
+ const container = await webex.internal.aisummary.getContainer({
68
+ containerId: '34125120-13b5-11f1-9b36-adb685725098',
69
+ });
70
+
71
+ // After normalization, URLs are directly on summaryData:
72
+ console.log(container.summaryData.summaryUrl); // https://aibridge-.../summaries/...
73
+ console.log(container.summaryData.transcriptUrl); // https://aibridge-.../transcripts/...
74
+ ```
75
+
76
+ **Request**: `GET {pragya-service}/containers/{containerId}`
77
+
78
+ **Response fields**:
79
+ - `summaryData` — Contains summary URLs (`summaryUrl`, `transcriptUrl`, `status`, `summarizeAfterCall`)
80
+ - `encryptionKeyUrl` — KMS key URL for decrypting content (e.g., `kms://kms-aore.wbx2.com/keys/...`)
81
+ - `kmsResourceObjectUrl`, `aclUrl`, `forkSessionId`, `callSessionId`, `ownerUserId`, `orgId`, `start`, `end`
82
+
83
+ ### `getSummary(options: GetSummaryContentOptions): Promise<SummaryContent>`
84
+
85
+ Fetches all AI-generated summary content (note, short note, and action items) from a single request to the summary URL, and decrypts each field via KMS. This is the primary method for retrieving summary content.
86
+
87
+ ```typescript
88
+ const summary = await webex.internal.aisummary.getSummary({
89
+ containerInfo: container,
90
+ });
91
+
92
+ console.log(summary.note); // Decrypted full note (HTML)
93
+ console.log(summary.shortNote); // Decrypted short note (HTML)
94
+ console.log(summary.actionItems); // Array of decrypted action item snippets
95
+ console.log(summary.feedbackUrl); // Feedback URL from links (if available)
96
+ ```
97
+
98
+ **Request**: `GET {summaryUrl}?fields=note,shortnote,actionitems`
99
+
100
+ **Response structure** (from AI Bridge, before decryption):
101
+ ```json
102
+ {
103
+ "id": "...",
104
+ "keyUrl": "kms://...",
105
+ "note": { "aiGeneratedContent": "<JWE>" },
106
+ "shortnote": { "aiGeneratedContent": "<JWE>" },
107
+ "actionitems": {
108
+ "snippets": [
109
+ { "id": "...", "aiGeneratedContent": "<JWE>" }
110
+ ]
111
+ },
112
+ "links": [
113
+ { "rel": "feedback", "href": "https://..." }
114
+ ]
115
+ }
116
+ ```
117
+
118
+ **Return type** (`SummaryContent`):
119
+ - `id` — Summary identifier
120
+ - `note` — Decrypted full note (HTML string)
121
+ - `shortNote` — Decrypted short note (HTML string)
122
+ - `actionItems` — Array of `ActionItemSnippet` objects
123
+ - `feedbackUrl` — Extracted from `links` array (`rel: "feedback"`), if available
124
+
125
+ ### `getNotes(options: GetSummaryContentOptions): Promise<SummaryNotes>`
126
+
127
+ Fetches AI-generated notes from the dedicated notes endpoint and decrypts via KMS. Requires `notesUrl` to be present in the container's `summaryData`.
128
+
129
+ ```typescript
130
+ const notes = await webex.internal.aisummary.getNotes({
131
+ containerInfo: container,
132
+ });
133
+
134
+ console.log(notes.content); // Decrypted notes content
135
+ ```
136
+
137
+ **Request**: `GET {notesUrl}`
138
+
139
+ > **Note:** The `notesUrl` may not be present in all API versions. Prefer `getSummary()` which returns notes, short notes, and action items in a single call.
140
+
141
+ ### `getActionItems(options: GetSummaryContentOptions): Promise<SummaryActionItems>`
142
+
143
+ Fetches AI-generated action items from the dedicated action items endpoint and decrypts each snippet via KMS. Requires `actionItemsUrl` to be present in the container's `summaryData`.
144
+
145
+ ```typescript
146
+ const actionItems = await webex.internal.aisummary.getActionItems({
147
+ containerInfo: container,
148
+ });
149
+
150
+ actionItems.snippets.forEach((item) => {
151
+ console.log(item.aiGeneratedContent); // Decrypted action item
152
+ });
153
+ ```
154
+
155
+ **Request**: `GET {actionItemsUrl}`
156
+
157
+ > **Note:** The `actionItemsUrl` may not be present in all API versions. Prefer `getSummary()` which returns notes, short notes, and action items in a single call.
158
+
159
+ ### `getTranscriptUrl(options: GetSummaryContentOptions): string`
160
+
161
+ Returns the transcript URL from the container info. Does not fetch or decrypt content.
162
+
163
+ ```typescript
164
+ const transcriptUrl = webex.internal.aisummary.getTranscriptUrl({
165
+ containerInfo: container,
166
+ });
167
+ ```
168
+
169
+ ## Types
170
+
171
+ ### Request Types
172
+
173
+ ```typescript
174
+ interface GetContainerOptions {
175
+ containerId: string; // Pragya container ID
176
+ }
177
+
178
+ interface GetSummaryContentOptions {
179
+ containerInfo: PragyaContainerResponse; // Resolved container from getContainer()
180
+ }
181
+ ```
182
+
183
+ ### Response Types
184
+
185
+ ```typescript
186
+ interface PragyaContainerResponse {
187
+ summaryData: PragyaSummaryData;
188
+ encryptionKeyUrl: string;
189
+ kmsResourceObjectUrl: string;
190
+ aclUrl: string;
191
+ forkSessionId: string;
192
+ callSessionId: string;
193
+ ownerUserId: string;
194
+ orgId: string;
195
+ start: string;
196
+ end: string;
197
+ }
198
+
199
+ interface PragyaSummaryData {
200
+ status: string;
201
+ summaryUrl: string;
202
+ transcriptUrl: string;
203
+ summarizeAfterCall: boolean;
204
+ notesUrl?: string; // May not be present in all API versions
205
+ actionItemsUrl?: string; // May not be present in all API versions
206
+ }
207
+
208
+ interface SummaryContent {
209
+ id: string;
210
+ note: string; // Decrypted full note (HTML)
211
+ shortNote: string; // Decrypted short note (HTML)
212
+ actionItems: ActionItemSnippet[];
213
+ feedbackUrl?: string; // From links array (rel="feedback")
214
+ }
215
+
216
+ interface SummaryNotes {
217
+ id: string;
218
+ content: string; // Decrypted notes content
219
+ feedbackUrl?: string;
220
+ }
221
+
222
+ interface SummaryActionItems {
223
+ id: string;
224
+ snippets: ActionItemSnippet[];
225
+ feedbackUrl?: string;
226
+ }
227
+
228
+ interface ActionItemSnippet {
229
+ id: string;
230
+ editedContent?: string; // User-edited version (if available)
231
+ aiGeneratedContent: string; // Decrypted AI-generated content
232
+ }
233
+ ```
234
+
235
+ ## Error Handling
236
+
237
+ The plugin normalizes HTTP errors into descriptive messages:
238
+
239
+ | Status Code | Error Message |
240
+ |-------------|---------------|
241
+ | 401 | `Authentication failed: Invalid or expired token` |
242
+ | 403 | `Access denied: User not authorized to view this summary` |
243
+ | 404 | `Container not found` |
244
+ | Other | `{methodName} failed: {error.message}` |
245
+
246
+ Validation errors are thrown synchronously:
247
+ - Missing or empty `containerId` throws `containerId is required and must be a non-empty string`
248
+ - Missing `containerInfo`, `summaryData` URL, or `encryptionKeyUrl` throws `containerInfo with valid summaryData and encryptionKeyUrl is required`
249
+
250
+ ## Encryption / Decryption
251
+
252
+ All AI-generated content from the AI Bridge service is JWE-encrypted. Decryption uses:
253
+
254
+ ```
255
+ webex.internal.encryption.decryptText(encryptionKeyUrl, encryptedContent)
256
+ ```
257
+
258
+ This requires:
259
+ 1. A registered device (`webex.internal.device.register()`)
260
+ 2. Mercury WebSocket connection (initiated automatically during KMS key fetch)
261
+ 3. ECDHE key exchange with KMS
262
+ 4. Key retrieval from KMS using the `encryptionKeyUrl`
263
+
264
+ The SDK handles steps 1-4 automatically when `decryptText` is called.
265
+
266
+ ## Dependencies
267
+
268
+ - `@webex/webex-core` — Base plugin class, request handling, auth interceptor
269
+ - `@webex/internal-plugin-encryption` — KMS decryption
270
+
271
+ ## Token Requirements
272
+
273
+ The Pragya and AI Bridge APIs require a valid Webex access token. The SDK's auth interceptor automatically attaches the token for URLs in the service catalog or on allowed domains (e.g., `wbx2.com`, `webex.com`).
274
+
275
+ ## Manual Testing
276
+
277
+ Two manual test scripts are provided in `src/`:
278
+
279
+ ### `manual-pragya-api-test.js`
280
+ Validates the Pragya container response structure (34 checks).
281
+
282
+ ```bash
283
+ cd packages/@webex/internal-plugin-call-ai-summary
284
+ WEBEX_TOKEN='<token>' node src/manual-pragya-api-test.js
285
+ ```
286
+
287
+ ### `manual-integration-test.js`
288
+ Tests the full end-to-end flow using the SDK service catalog:
289
+ 1. Device registration (WDM) to populate the service catalog
290
+ 2. `getContainer` via plugin (resolves `service: 'pragya'` from the catalog)
291
+ 3. `getSummary` via plugin (fetches + decrypts note, short note, and action items via KMS)
292
+ 4. `getTranscriptUrl` via plugin
293
+ 5. Transcript content fetch
294
+
295
+ ```bash
296
+ cd packages/@webex/internal-plugin-call-ai-summary
297
+ WEBEX_TOKEN='<token>' CONTAINER_ID='<id>' node src/manual-integration-test.js
298
+ ```
299
+
300
+ Both scripts require a valid Webex access token. Set `WEBEX_TOKEN` and optionally `CONTAINER_ID` as environment variables, or update the placeholder values in the scripts.