Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/calling/ai-docs/RULES.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
| Classes | PascalCase | `CallingClient`, `CallHistory`, `Registration`, `CallManager` |
| Interfaces | `I` prefix + PascalCase | `ICall`, `ILine`, `ICallingClient`, `IRegistration`, `ICallManager`, `ICallerId` |
| Type aliases | PascalCase | `CallId`, `CorrelationId`, `MobiusDeviceId`, `DisplayInformation`, `WebexRequestPayload` |
| Enums | PascalCase name, SCREAMING_SNAKE_CASE values | `CALL_EVENT_KEYS.ALERTING`, `ERROR_TYPE.CALL_ERROR`, `METRIC_EVENT.CALL` |
| Enums | PascalCase name, SCREAMING_SNAKE_CASE values | `CALL_EVENT_KEYS.PROGRESS`, `ERROR_TYPE.CALL_ERROR`, `METRIC_EVENT.CALL` |
| Constants | SCREAMING_SNAKE_CASE | `DISCOVERY_URL`, `DEFAULT_KEEPALIVE_INTERVAL`, `NETWORK_FLAP_TIMEOUT` |
| Methods | camelCase | `getLines()`, `makeCall()`, `doHoldResume()`, `triggerRegistration()` |
| Private fields | `private` keyword | `private webex: WebexSDK`, `private metricManager: IMetricManager` |
Expand Down Expand Up @@ -274,7 +274,7 @@ class Call extends Eventing<CallEventTypes> implements ICall {
```typescript
// Each event key maps to a typed callback signature
type CallEventTypes = {
[CALL_EVENT_KEYS.ALERTING]: (callId: CallId) => void;
[CALL_EVENT_KEYS.PROGRESS]: (callId: CallId) => void;
[CALL_EVENT_KEYS.CALL_ERROR]: (error: CallError) => void;
[CALL_EVENT_KEYS.CONNECT]: (callId: CallId) => void;
// ...
Expand Down
2 changes: 1 addition & 1 deletion packages/calling/ai-docs/patterns/event-patterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ Each emitter class has a corresponding type map that constrains event keys and c

```typescript
export type CallEventTypes = {
[CALL_EVENT_KEYS.ALERTING]: (callId: CallId) => void;

[CALL_EVENT_KEYS.CALL_ERROR]: (error: CallError) => void;
[CALL_EVENT_KEYS.CALLER_ID]: (display: CallerIdDisplay) => void;
[CALL_EVENT_KEYS.CONNECT]: (callId: CallId) => void;
Expand Down
1 change: 0 additions & 1 deletion packages/calling/ai-docs/patterns/typescript-patterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,6 @@ export interface ICall extends Eventing<CallEventTypes> {

```typescript
export type CallEventTypes = {
[CALL_EVENT_KEYS.ALERTING]: (callId: CallId) => void;
[CALL_EVENT_KEYS.CALL_ERROR]: (error: CallError) => void;
[CALL_EVENT_KEYS.CALLER_ID]: (display: CallerIdDisplay) => void;
[CALL_EVENT_KEYS.CONNECT]: (callId: CallId) => void;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
# CallerId Sub-Module - Agent Specification
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to create separate AGENTS.md for CallerId since it's pretty small ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's why I have not added ARCHITECTURE.md for this. But method details and how this component is designed has to be added somewhere so keeping AGENTS.md file for that reason


## Overview

The `CallerId` sub-module resolves caller identity for a `Call` using SIP-style headers delivered by Mobius signaling events. It provides immediate, best-effort display information from headers and then performs asynchronous enrichment through SCIM lookup when BroadWorks metadata includes an `externalId`.

This module is intentionally small and stateful:
- `fetchCallerDetails()` is the public entrypoint.
- It resets and repopulates internal `callerInfo` for each resolution request.
- It emits updates via a callback when new identity data becomes available.

---

## Key Capabilities

### 1. Deterministic Header Priority Resolution
- Parses `p-asserted-identity` first (highest preference).
- Uses `from` header as fallback for missing fields.
- Maintains predictable precedence for name/number population.

### 2. SIP URI Parsing for Name/Number
- Extracts display name from quoted/header prefix.
- Extracts number from SIP URI local part.
- Validates parsed phone tokens using `VALID_PHONE_REGEX`.

### 3. Async SCIM Enrichment from BroadWorks Data
- Parses `x-broadworks-remote-party-info`.
- Detects `externalId` and issues SCIM-driven resolution through `resolveCallerIdDisplay()`.
- Upgrades interim caller details with richer profile fields (`name`, `num`, `avatarSrc`, `id`) when available.

### 4. Incremental Event-Style Updates
- Emits initial caller info early when header parsing yields usable values.
- Emits again only when async resolution actually changes fields.
- Avoids noisy duplicate emissions by checking diffs before callback.

### 5. Logging and Failure Tolerance
- Logs parsing/resolution steps with `{file, method}` context.
- Continues gracefully when external ID is missing or SCIM enrichment fails.
- Preserves best-known caller info instead of failing hard.

---

## Files

| File | Primary Symbol(s) | Description |
|------|--------------------|-------------|
| `index.ts` | `CallerId`, `createCallerId` | Main implementation and factory for caller ID resolution |
| `types.ts` | `ICallerId`, helper types | Public contract for caller detail resolution |
| `index.test.ts` | Jest tests | Priority, fallback, and async enrichment behavior validation |

---

## Public API

## Factory Function

```typescript
export const createCallerId = (webex: WebexSDK, emitterCb: CallEmitterCallBack): ICallerId =>
new CallerId(webex, emitterCb);
```

## ICallerId Interface

`ICallerId` is the contract consumed by `Call`. It defines a single entrypoint that returns immediate display info while potentially triggering async updates through the emitter callback.

```typescript
export interface ICallerId {
fetchCallerDetails: (callerId: CallerIdInfo) => DisplayInformation;
}
```

## CallerId Class

### Constructor

```typescript
constructor(webex: WebexSDK, emitter: CallEmitterCallBack)
```

Responsibilities:
- Ensures `SDKConnector` has a valid Webex instance.
- Initializes internal mutable `callerInfo`.
- Stores emitter callback for incremental caller ID updates.

### Core Methods

| Method | Visibility | Purpose |
|--------|------------|---------|
| `fetchCallerDetails(callerId)` | Public | Main entrypoint: resets fields, applies header priority parsing, emits initial data, triggers async BroadWorks enrichment |
| `parseSipUri(paid)` | Private | Parses name and number from SIP-like header string |
| `parseRemotePartyInfo(data)` | Private | Extracts BroadWorks `externalId` and starts SCIM lookup |
| `resolveCallerId(filter)` | Private async | Performs SCIM enrichment and emits only when resolved fields differ |

---

## Resolution Rules (Source of Truth)

1. Reset `callerInfo` (`id`, `avatarSrc`, `name`, `num`) before processing a new event.
2. If `p-asserted-identity` exists, parse and set `name`/`num` directly (highest priority).
3. If `from` exists, parse and fill only fields still unset by step 2.
4. Emit immediate caller update if `name` or `num` is available.
5. If `x-broadworks-remote-party-info` exists, parse `externalId` and run async SCIM enrichment.
6. During enrichment, update only changed fields and emit callback only when at least one field changed.

---

## Control Flow

```mermaid
flowchart TD
A[fetchCallerDetails CallerIdInfo] --> B[Reset callerInfo fields]
B --> C{Has p-asserted-identity?}
C -- Yes --> D[parseSipUri PAI and set name/num]
C -- No --> E
D --> E{Has from header?}
E -- Yes --> F[parseSipUri from and fill missing fields only]
E -- No --> G
F --> G{Has name or num?}
G -- Yes --> H[emit interim callerInfo]
G -- No --> I
H --> I{Has x-broadworks-remote-party-info?}
I -- Yes --> J[parseRemotePartyInfo]
I -- No --> M[Return current callerInfo]
J --> K{externalId found?}
K -- Yes --> L[resolveCallerId SCIM query]
K -- No --> M
L --> N{Any field changed?}
N -- Yes --> O[emit enriched callerInfo]
N -- No --> M
O --> M
```

---

## Testing Expectations

Tests for this module should cover:
- Header precedence: `p-asserted-identity` over `from`.
- Fallback behavior when one/both SIP fields are partially missing.
- Async overwrite by BroadWorks+SCIM enrichment when `externalId` is present.
- No overwrite when `externalId` is absent.
- SCIM failure path preserves already-resolved interim details.
- Emission behavior:
- Interim emit when `name`/`num` exists.
- Follow-up emit only when enrichment changes fields.

---

## Agent Rules for Code Generation

When implementing or modifying `CallerId`:
- Keep `fetchCallerDetails()` as the only public resolution entrypoint.
- Preserve the strict precedence order: `p-asserted-identity` -> `from` -> BroadWorks enrichment.
- Keep enrichment non-blocking for initial caller identity delivery.
- Do not introduce direct event emitter dependencies; continue using `CallEmitterCallBack`.
- Use existing logger conventions with `{file, method}` metadata.
- Reuse `DisplayInformation` and `CallerIdInfo` types (no duplicate local types).
- Keep parsing and enrichment side effects minimal and explicit.

### Do Not
- Do not replace the callback-based update mechanism with direct `Call` mutations.
- Do not block return of interim caller details while waiting for SCIM lookup.
- Do not emit duplicate callbacks when resolved data is unchanged.
- Do not weaken validation around parsed number fields.

---

## Quick Validation Checklist

- [ ] `createCallerId()` still returns `ICallerId`.
- [ ] `fetchCallerDetails()` resets stale state before processing input.
- [ ] Header parsing order and fallback semantics remain unchanged.
- [ ] BroadWorks `externalId` parsing still triggers async SCIM lookup.
- [ ] Callback emissions occur for interim and changed enriched data only.
- [ ] Existing `index.test.ts` scenarios remain valid or are updated with behavior-preserving intent.
Loading
Loading