@pingroom/sdk 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (179) hide show
  1. package/README.md +153 -41
  2. package/dist/client.d.ts +14 -348
  3. package/dist/client.d.ts.map +1 -1
  4. package/dist/client.js +15 -1300
  5. package/dist/client.js.map +1 -1
  6. package/dist/errors.d.ts +32 -3
  7. package/dist/errors.d.ts.map +1 -1
  8. package/dist/errors.js +47 -1
  9. package/dist/errors.js.map +1 -1
  10. package/dist/http.d.ts.map +1 -1
  11. package/dist/http.js +7 -1
  12. package/dist/http.js.map +1 -1
  13. package/dist/index.d.ts +3 -5
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +1 -2
  16. package/dist/index.js.map +1 -1
  17. package/dist/internal/guards.d.ts +7 -0
  18. package/dist/internal/guards.d.ts.map +1 -1
  19. package/dist/internal/guards.js +1 -1
  20. package/dist/internal/guards.js.map +1 -1
  21. package/dist/internal/request.d.ts +41 -0
  22. package/dist/internal/request.d.ts.map +1 -0
  23. package/dist/internal/request.js +78 -0
  24. package/dist/internal/request.js.map +1 -0
  25. package/dist/internal/retry.d.ts +9 -2
  26. package/dist/internal/retry.d.ts.map +1 -1
  27. package/dist/internal/retry.js +24 -6
  28. package/dist/internal/retry.js.map +1 -1
  29. package/dist/internal/validate.d.ts +13 -0
  30. package/dist/internal/validate.d.ts.map +1 -0
  31. package/dist/internal/validate.js +36 -0
  32. package/dist/internal/validate.js.map +1 -0
  33. package/dist/liveStatus.d.ts +106 -11
  34. package/dist/liveStatus.d.ts.map +1 -1
  35. package/dist/liveStatus.js +20 -3
  36. package/dist/liveStatus.js.map +1 -1
  37. package/dist/mcp.d.ts.map +1 -1
  38. package/dist/mcp.js +30 -8
  39. package/dist/mcp.js.map +1 -1
  40. package/dist/namespaces/actions.d.ts +50 -0
  41. package/dist/namespaces/actions.d.ts.map +1 -0
  42. package/dist/namespaces/actions.js +219 -0
  43. package/dist/namespaces/actions.js.map +1 -0
  44. package/dist/namespaces/approvals.d.ts +23 -0
  45. package/dist/namespaces/approvals.d.ts.map +1 -0
  46. package/dist/namespaces/approvals.js +78 -0
  47. package/dist/namespaces/approvals.js.map +1 -0
  48. package/dist/namespaces/attachments.d.ts +37 -0
  49. package/dist/namespaces/attachments.d.ts.map +1 -0
  50. package/dist/namespaces/attachments.js +91 -0
  51. package/dist/namespaces/attachments.js.map +1 -0
  52. package/dist/namespaces/auth.d.ts +58 -0
  53. package/dist/namespaces/auth.d.ts.map +1 -0
  54. package/dist/namespaces/auth.js +242 -0
  55. package/dist/namespaces/auth.js.map +1 -0
  56. package/dist/namespaces/directory.d.ts +10 -0
  57. package/dist/namespaces/directory.d.ts.map +1 -0
  58. package/dist/namespaces/directory.js +19 -0
  59. package/dist/namespaces/directory.js.map +1 -0
  60. package/dist/namespaces/handoffs.d.ts +47 -0
  61. package/dist/namespaces/handoffs.d.ts.map +1 -0
  62. package/dist/namespaces/handoffs.js +126 -0
  63. package/dist/namespaces/handoffs.js.map +1 -0
  64. package/dist/namespaces/inbox.d.ts +17 -0
  65. package/dist/namespaces/inbox.d.ts.map +1 -0
  66. package/dist/namespaces/inbox.js +192 -0
  67. package/dist/namespaces/inbox.js.map +1 -0
  68. package/dist/namespaces/live.d.ts +57 -0
  69. package/dist/namespaces/live.d.ts.map +1 -0
  70. package/dist/namespaces/live.js +116 -0
  71. package/dist/namespaces/live.js.map +1 -0
  72. package/dist/namespaces/notifications.d.ts +25 -0
  73. package/dist/namespaces/notifications.d.ts.map +1 -0
  74. package/dist/namespaces/notifications.js +128 -0
  75. package/dist/namespaces/notifications.js.map +1 -0
  76. package/dist/namespaces/profile.d.ts +19 -0
  77. package/dist/namespaces/profile.d.ts.map +1 -0
  78. package/dist/namespaces/profile.js +22 -0
  79. package/dist/namespaces/profile.js.map +1 -0
  80. package/dist/namespaces/questions.d.ts +41 -0
  81. package/dist/namespaces/questions.d.ts.map +1 -0
  82. package/dist/namespaces/questions.js +107 -0
  83. package/dist/namespaces/questions.js.map +1 -0
  84. package/dist/namespaces/rooms.d.ts +13 -0
  85. package/dist/namespaces/rooms.d.ts.map +1 -0
  86. package/dist/namespaces/rooms.js +41 -0
  87. package/dist/namespaces/rooms.js.map +1 -0
  88. package/dist/namespaces/webhooks.d.ts +11 -0
  89. package/dist/namespaces/webhooks.d.ts.map +1 -0
  90. package/dist/namespaces/webhooks.js +32 -0
  91. package/dist/namespaces/webhooks.js.map +1 -0
  92. package/dist/types/approvals.d.ts +62 -0
  93. package/dist/types/approvals.d.ts.map +1 -0
  94. package/dist/types/approvals.js +2 -0
  95. package/dist/types/approvals.js.map +1 -0
  96. package/dist/types/auth.d.ts +185 -0
  97. package/dist/types/auth.d.ts.map +1 -0
  98. package/dist/types/auth.js +2 -0
  99. package/dist/types/auth.js.map +1 -0
  100. package/dist/types/common.d.ts +43 -0
  101. package/dist/types/common.d.ts.map +1 -0
  102. package/dist/types/common.js +2 -0
  103. package/dist/types/common.js.map +1 -0
  104. package/dist/types/handoffs.d.ts +121 -0
  105. package/dist/types/handoffs.d.ts.map +1 -0
  106. package/dist/types/handoffs.js +2 -0
  107. package/dist/types/handoffs.js.map +1 -0
  108. package/dist/types/inbox.d.ts +51 -0
  109. package/dist/types/inbox.d.ts.map +1 -0
  110. package/dist/types/inbox.js +2 -0
  111. package/dist/types/inbox.js.map +1 -0
  112. package/dist/types/notifications.d.ts +376 -0
  113. package/dist/types/notifications.d.ts.map +1 -0
  114. package/dist/types/notifications.js +2 -0
  115. package/dist/types/notifications.js.map +1 -0
  116. package/dist/types/profile.d.ts +30 -0
  117. package/dist/types/profile.d.ts.map +1 -0
  118. package/dist/types/profile.js +3 -0
  119. package/dist/types/profile.js.map +1 -0
  120. package/dist/types/questions.d.ts +210 -0
  121. package/dist/types/questions.d.ts.map +1 -0
  122. package/dist/types/questions.js +2 -0
  123. package/dist/types/questions.js.map +1 -0
  124. package/dist/types/rooms.d.ts +163 -0
  125. package/dist/types/rooms.d.ts.map +1 -0
  126. package/dist/types/rooms.js +3 -0
  127. package/dist/types/rooms.js.map +1 -0
  128. package/dist/types.d.ts +9 -1199
  129. package/dist/types.d.ts.map +1 -1
  130. package/dist/version.d.ts +1 -1
  131. package/dist/version.js +1 -1
  132. package/dist/webhooks.d.ts +14 -4
  133. package/dist/webhooks.d.ts.map +1 -1
  134. package/dist/webhooks.js +30 -5
  135. package/dist/webhooks.js.map +1 -1
  136. package/package.json +4 -2
  137. package/src/client.ts +115 -0
  138. package/src/errors.ts +291 -0
  139. package/src/http.ts +225 -0
  140. package/src/index.ts +97 -0
  141. package/src/internal/async.ts +54 -0
  142. package/src/internal/guards.ts +129 -0
  143. package/src/internal/request.ts +97 -0
  144. package/src/internal/retry.ts +157 -0
  145. package/src/internal/url.ts +78 -0
  146. package/src/internal/validate.ts +42 -0
  147. package/src/linkPing.ts +56 -0
  148. package/src/liveStatus.ts +337 -0
  149. package/src/locationPing.ts +101 -0
  150. package/src/mcp.ts +161 -0
  151. package/src/namespaces/actions.ts +234 -0
  152. package/src/namespaces/approvals.ts +104 -0
  153. package/src/namespaces/attachments.ts +106 -0
  154. package/src/namespaces/auth.ts +284 -0
  155. package/src/namespaces/directory.ts +21 -0
  156. package/src/namespaces/handoffs.ts +160 -0
  157. package/src/namespaces/inbox.ts +253 -0
  158. package/src/namespaces/live.ts +134 -0
  159. package/src/namespaces/notifications.ts +152 -0
  160. package/src/namespaces/profile.ts +24 -0
  161. package/src/namespaces/questions.ts +133 -0
  162. package/src/namespaces/rooms.ts +53 -0
  163. package/src/namespaces/webhooks.ts +38 -0
  164. package/src/types/approvals.ts +67 -0
  165. package/src/types/auth.ts +202 -0
  166. package/src/types/common.ts +74 -0
  167. package/src/types/handoffs.ts +148 -0
  168. package/src/types/inbox.ts +64 -0
  169. package/src/types/notifications.ts +393 -0
  170. package/src/types/profile.ts +37 -0
  171. package/src/types/questions.ts +226 -0
  172. package/src/types/rooms.ts +169 -0
  173. package/src/types.ts +17 -0
  174. package/src/version.ts +2 -0
  175. package/src/webhooks.ts +373 -0
  176. package/dist/liveActivity.d.ts +0 -101
  177. package/dist/liveActivity.d.ts.map +0 -1
  178. package/dist/liveActivity.js +0 -83
  179. package/dist/liveActivity.js.map +0 -1
@@ -0,0 +1,133 @@
1
+ // `pr.questions`: Questions and polls in a room.
2
+
3
+ import { PingRoomError } from '../errors.js';
4
+ import type { HttpClient } from '../http.js';
5
+ import { assertStructuredData, requireNonEmpty } from '../internal/guards.js';
6
+ import { pollUntilSettled } from '../internal/retry.js';
7
+ import type {
8
+ ListQuestionsInput,
9
+ Question,
10
+ QuestionInput,
11
+ WaitForOutcomeOptions,
12
+ WaitQuestionInput,
13
+ } from '../types.js';
14
+ import {
15
+ MIN_WAIT_POLL_INTERVAL_MS,
16
+ enc,
17
+ splitIdempotencyKey,
18
+ assertOnRetry,
19
+ dropUndefined,
20
+ waitTimeoutMs,
21
+ } from '../internal/request.js';
22
+ import { isRecord, sameId, invalidWaitResponse } from '../internal/validate.js';
23
+
24
+ const QUESTION_STATES: ReadonlySet<string> = new Set(['pending', 'answered', 'expired', 'cancelled']);
25
+
26
+ function validateQuestionWait(value: unknown, questionId: string): Question {
27
+ const state = isRecord(value) ? value['state'] : undefined;
28
+ if (!isRecord(value) || !sameId(value['id'], questionId) || typeof state !== 'string' || !QUESTION_STATES.has(state)) {
29
+ return invalidWaitResponse('question', questionId, value, 'state');
30
+ }
31
+ return value as unknown as Question;
32
+ }
33
+
34
+ /**
35
+ * The general human-in-the-loop primitive: ask a human a question with 2–4
36
+ * options (or a short typed answer) and block until they tap an answer. An
37
+ * {@link ApprovalsApi | approval} is the two-option case; reach for questions
38
+ * when you need more choices, a typed reply, or room-scope answering.
39
+ */
40
+ export class QuestionsApi {
41
+ constructor(private readonly http: HttpClient) {}
42
+
43
+ /**
44
+ * Ask a Question in a room; returns the pending Question (id + computed
45
+ * expiry). Pass `resolution_policy: 'all'` to poll the room: everyone asked
46
+ * votes once, and `results` carries the tally on every read.
47
+ */
48
+ ask(inviteCode: string, input: QuestionInput): Promise<Question> {
49
+ requireNonEmpty(input.prompt, 'prompt');
50
+ assertStructuredData(input.data);
51
+ assertPollShape(input);
52
+ const { body, headers } = splitIdempotencyKey(input);
53
+ return this.http.request('POST', `/api/agent/rooms/${enc(inviteCode)}/questions`, {
54
+ body: dropUndefined(body),
55
+ ...(headers ? { headers } : {}),
56
+ });
57
+ }
58
+
59
+ /** Fetch a Question's current state. */
60
+ get(questionId: string): Promise<Question> {
61
+ return this.http.request('GET', `/api/agent/questions/${enc(questionId)}`);
62
+ }
63
+
64
+ /** One long-poll for the answer; returns the (possibly still-pending) Question. */
65
+ wait(questionId: string, input: WaitQuestionInput = {}): Promise<Question> {
66
+ return this.http.request('GET', `/api/agent/questions/${enc(questionId)}/wait`, {
67
+ query: dropUndefined({ timeout: input.timeout }),
68
+ timeoutMs: waitTimeoutMs(input.timeout),
69
+ ...(input.signal ? { signal: input.signal } : {}),
70
+ });
71
+ }
72
+
73
+ /**
74
+ * Block until the human answers (or the question expires/cancels), looping
75
+ * wait() under the hood. Returns the resolved Question — inspect `state` and
76
+ * `answer.value` to branch. A poll returns once it closes; its `answer` is the
77
+ * leading option, or `null` on a tie, and `results` holds the tally.
78
+ *
79
+ * Polls start at least 2.1 s apart. A response that is not this Question in a
80
+ * known state rejects with `invalid_response`. Network failures, 429 and 5xx
81
+ * are retried with backoff (honouring `retryAfter`), and reported to
82
+ * `onRetry`, until `signal` aborts or the outage outlasts the Question's
83
+ * `expires_at` by 5 minutes (15 minutes of failures while no `expires_at` has
84
+ * been read); that rejects with `unavailable`. See {@link WaitForOutcomeOptions}.
85
+ */
86
+ async waitForAnswer(questionId: string, input: WaitForOutcomeOptions = {}): Promise<Question> {
87
+ requireNonEmpty(questionId, 'questionId');
88
+ assertOnRetry(input.onRetry);
89
+ return pollUntilSettled({
90
+ poll: () => this.wait(questionId, input.signal ? { signal: input.signal } : {}),
91
+ validate: (value) => validateQuestionWait(value, questionId),
92
+ settled: (question) => question.state !== 'pending',
93
+ expiresAt: (question) => question.expires_at,
94
+ what: `question ${questionId}`,
95
+ minIntervalMs: MIN_WAIT_POLL_INTERVAL_MS,
96
+ ...(input.signal ? { signal: input.signal } : {}),
97
+ ...(input.onRetry ? { onRetry: input.onRetry } : {}),
98
+ });
99
+ }
100
+
101
+ /** List the asker's Questions, optionally filtered by state. */
102
+ async list(input: ListQuestionsInput = {}): Promise<Question[]> {
103
+ const res = await this.http.request<{ questions: Question[] }>('GET', '/api/agent/questions', {
104
+ query: dropUndefined({ state: input.state }),
105
+ });
106
+ return res.questions ?? [];
107
+ }
108
+
109
+ /** Withdraw a still-pending Question (409 if it already resolved). */
110
+ cancel(questionId: string): Promise<Question> {
111
+ return this.http.request('POST', `/api/agent/questions/${enc(questionId)}/cancel`, { body: {} });
112
+ }
113
+ }
114
+
115
+ /**
116
+ * A poll asks the whole room and counts option votes, so the server refuses it
117
+ * (422 on `resolution_policy`) alongside a direct scope, a named target, or a
118
+ * typed answer. Fail before the request instead.
119
+ */
120
+ function assertPollShape(input: QuestionInput): void {
121
+ if (input.resolution_policy !== 'all') return;
122
+ const target = input.target_user_id as unknown;
123
+ if (
124
+ input.responder_scope === 'direct'
125
+ || (target !== undefined && target !== null && target !== '')
126
+ || (input.text_input !== undefined && input.text_input !== null)
127
+ ) {
128
+ throw new PingRoomError(
129
+ "A poll (`resolution_policy: 'all'`) asks the whole room with options only; it cannot use `responder_scope: 'direct'`, `target_user_id`, or `text_input`.",
130
+ { code: 'invalid_request' },
131
+ );
132
+ }
133
+ }
@@ -0,0 +1,53 @@
1
+ // `pr.rooms`: list, read, create and join rooms.
2
+
3
+ import { PingRoomError } from '../errors.js';
4
+ import type { HttpClient } from '../http.js';
5
+ import { requireNonEmpty } from '../internal/guards.js';
6
+ import type {
7
+ CreatePublicRoomInput,
8
+ CreateRoomInput,
9
+ JoinRoomInput,
10
+ Room,
11
+ RoomIconCatalog,
12
+ } from '../types.js';
13
+ import { enc, dropUndefined } from '../internal/request.js';
14
+
15
+ export class RoomsApi {
16
+ constructor(private readonly http: HttpClient) {}
17
+
18
+ icons(): Promise<RoomIconCatalog> {
19
+ return this.http.request('GET', '/api/agent/room-icons');
20
+ }
21
+
22
+ list(): Promise<Room[]> {
23
+ return this.http.request('GET', '/api/agent/rooms');
24
+ }
25
+
26
+ get(inviteCode: string): Promise<Room> {
27
+ return this.http.request('GET', `/api/agent/rooms/${enc(inviteCode)}`);
28
+ }
29
+
30
+ create(input: CreateRoomInput): Promise<Room> {
31
+ requireNonEmpty(input.name, 'name');
32
+ return this.http.request('POST', '/api/agent/rooms', { body: dropUndefined({ ...input }) });
33
+ }
34
+
35
+ createPublic(input: CreatePublicRoomInput): Promise<Room> {
36
+ requireNonEmpty(input.name, 'name');
37
+ requireNonEmpty(input.handle, 'handle');
38
+ // The server takes the location trio all-or-nothing; a partial set is a
39
+ // 422 on a field the caller may not have meant to send at all.
40
+ const present = [input.location_name, input.location_latitude, input.location_longitude].filter((v) => v !== undefined).length;
41
+ if (present !== 0 && present !== 3) {
42
+ throw new PingRoomError('Send `location_name`, `location_latitude` and `location_longitude` together, or none of them.', {
43
+ code: 'invalid_request',
44
+ });
45
+ }
46
+ return this.http.request('POST', '/api/agent/rooms/public', { body: dropUndefined({ ...input }) });
47
+ }
48
+
49
+ join(input: JoinRoomInput): Promise<Room> {
50
+ requireNonEmpty(input.invite_code, 'invite_code');
51
+ return this.http.request('POST', '/api/agent/rooms/join', { body: dropUndefined({ ...input }) });
52
+ }
53
+ }
@@ -0,0 +1,38 @@
1
+ // `pr.webhooks`: a room's incoming webhooks.
2
+
3
+ import type { HttpClient } from '../http.js';
4
+ import { assertActionNumber, requireNonEmpty } from '../internal/guards.js';
5
+ import type { CreateWebhookInput, UpdateWebhookInput, Webhook } from '../types.js';
6
+ import { enc, dropUndefined } from '../internal/request.js';
7
+
8
+ export class WebhooksApi {
9
+ constructor(private readonly http: HttpClient) {}
10
+
11
+ async list(inviteCode: string): Promise<Webhook[]> {
12
+ const result = await this.http.request<{ webhooks: Webhook[] }>(
13
+ 'GET', `/api/agent/rooms/${enc(inviteCode)}/webhooks`,
14
+ );
15
+ return result.webhooks;
16
+ }
17
+
18
+ create(inviteCode: string, input: CreateWebhookInput): Promise<Webhook> {
19
+ requireNonEmpty(input.name, 'name');
20
+ assertActionNumber(input.action_number);
21
+ return this.http.request('POST', `/api/agent/rooms/${enc(inviteCode)}/webhooks`, {
22
+ body: dropUndefined({ ...input }),
23
+ });
24
+ }
25
+
26
+ update(inviteCode: string, webhookId: string, input: UpdateWebhookInput): Promise<Webhook> {
27
+ requireNonEmpty(webhookId, 'webhookId');
28
+ assertActionNumber(input.action_number);
29
+ return this.http.request('PUT', `/api/agent/rooms/${enc(inviteCode)}/webhooks/${enc(webhookId)}`, {
30
+ body: dropUndefined({ ...input }),
31
+ });
32
+ }
33
+
34
+ async delete(inviteCode: string, webhookId: string): Promise<void> {
35
+ requireNonEmpty(webhookId, 'webhookId');
36
+ await this.http.request('DELETE', `/api/agent/rooms/${enc(inviteCode)}/webhooks/${enc(webhookId)}`);
37
+ }
38
+ }
@@ -0,0 +1,67 @@
1
+ import type { PingRoomError } from '../errors.js';
2
+ import type { JsonObject } from './common.js';
3
+
4
+ // --- approvals ------------------------------------------------------------
5
+
6
+ export interface ApprovalInput {
7
+ question: string;
8
+ title?: string;
9
+ options?: string[];
10
+ correlation_id?: string;
11
+ data?: JsonObject;
12
+ ttl?: number;
13
+ /**
14
+ * Sent as the `Idempotency-Key` header (1–255 printable ASCII characters,
15
+ * spaces allowed), never in the body.
16
+ * Reuse the same key when retrying after a timeout or network error: the
17
+ * server replays the first result instead of asking the human a second time.
18
+ */
19
+ idempotencyKey?: string;
20
+ }
21
+
22
+ export interface Approval {
23
+ id: string;
24
+ /**
25
+ * A human answer has status `decided`; inspect `decision` for the chosen
26
+ * option. `approved`/`denied` are never sent.
27
+ */
28
+ status: 'pending' | 'decided' | 'expired' | 'cancelled' | (string & {});
29
+ question: string;
30
+ title: string | null;
31
+ options: string[];
32
+ decision: string | null;
33
+ decided_at: string | null;
34
+ expires_at: string | null;
35
+ correlation_id: string | null;
36
+ data: JsonObject | null;
37
+ /** Null when the approval's room no longer exists. */
38
+ room_code: string | null;
39
+ [key: string]: unknown;
40
+ }
41
+
42
+ export interface WaitApprovalInput {
43
+ /** Seconds the server holds the connection (0–30, default 20). */
44
+ timeout?: number;
45
+ signal?: AbortSignal;
46
+ }
47
+
48
+ /**
49
+ * Options for the blocking waits: `approvals.waitForDecision()`,
50
+ * `questions.waitForAnswer()` and `handoffs.waitForResult()`.
51
+ *
52
+ * Network errors, request timeouts, 429 and 5xx are retried with backoff. The
53
+ * retries stop with `PingRoomError` code `unavailable` (the last failure as
54
+ * its `cause`) once only failures have come back since the last valid
55
+ * response and the last-seen `expires_at` is more than 5 minutes past, or,
56
+ * before any `expires_at` was seen, after 15 minutes of unbroken failures.
57
+ */
58
+ export interface WaitForOutcomeOptions {
59
+ /** Abort to stop waiting; the wait rejects with the signal's reason. */
60
+ signal?: AbortSignal;
61
+ /**
62
+ * Called before each retry of a transient failure, with that failure and
63
+ * the number of consecutive failures so far (1 on the first). Throwing from
64
+ * it ends the wait with the thrown error.
65
+ */
66
+ onRetry?: (error: PingRoomError, attempt: number) => void;
67
+ }
@@ -0,0 +1,202 @@
1
+ import type { ScopeInput } from './common.js';
2
+
3
+ // --- auth -----------------------------------------------------------------
4
+
5
+ export type AgentRegisterType = 'identity_assertion' | 'anonymous';
6
+ export type AgentAssertionType = 'urn:ietf:params:oauth:token-type:id-jag' | 'verified_email';
7
+
8
+ export interface RegisterParams {
9
+ type: AgentRegisterType;
10
+ assertion_type?: AgentAssertionType;
11
+ assertion?: string;
12
+ scopes?: ScopeInput[];
13
+ agent_label?: string;
14
+ }
15
+
16
+ /**
17
+ * How wide the human made this agent's room grant.
18
+ *
19
+ * `'all'` — every room the account is in; the grant lists none, while `room`
20
+ * may identify one eligible private delivery destination. `'selected'` —
21
+ * exactly the rooms in `rooms`.
22
+ */
23
+ export type AgentRoomAccess = 'all' | 'selected';
24
+
25
+ /** One room inside an agent's grant. */
26
+ export interface AgentAccessRoom {
27
+ id: string;
28
+ invite_code: string;
29
+ name: string;
30
+ }
31
+
32
+ /** The delivery room: where handoffs and questions land. */
33
+ export interface AgentDeliveryRoom {
34
+ invite_code: string;
35
+ name?: string | null;
36
+ }
37
+
38
+ /** Stable, non-credential URLs returned with an approved agent connection. */
39
+ export interface AgentCredentialLinks {
40
+ /** REST endpoint for the agent's latest visible pings. Authenticate normally. */
41
+ latest_pings: string;
42
+ /** Token-free smart link for installing PingRoom on iOS or Android. */
43
+ install_app?: string;
44
+ }
45
+
46
+ /** Public robot identity provisioned before a person approves pairing. */
47
+ export interface AgentProfile {
48
+ display_name: string | null;
49
+ handle: string | null;
50
+ avatar_id: string | null;
51
+ avatar_url: string | null;
52
+ }
53
+
54
+ /** Agent wrapper used by pairing v2. Older servers omit it entirely. */
55
+ export interface PairingAgent {
56
+ id?: string;
57
+ label?: string | null;
58
+ handle?: string | null;
59
+ profile?: AgentProfile;
60
+ }
61
+
62
+ /** Human who claimed the robot. Present only after approval. */
63
+ export interface PairingOwner {
64
+ name: string | null;
65
+ }
66
+
67
+ /** Visible home-room membership, distinct from the wider room-access grant. */
68
+ export interface AgentRoomMembership {
69
+ status: 'active' | 'removed';
70
+ joined_at: string | null;
71
+ removed_at: string | null;
72
+ /** True for the robot's home room. Sent by `auth.connection()`. */
73
+ is_home?: boolean;
74
+ room: AgentDeliveryRoom | null;
75
+ }
76
+
77
+ /** Additive pairing-v2 identity metadata; every field is optional for old servers. */
78
+ export interface PairingIdentityMetadata {
79
+ flow_version?: 2;
80
+ claim_mode?: 'agent_identity';
81
+ agent?: PairingAgent;
82
+ owner?: PairingOwner;
83
+ /** Alias of legacy `room`: the robot's Question/Handoff delivery room. */
84
+ home_room?: AgentDeliveryRoom | null;
85
+ room_membership?: AgentRoomMembership | null;
86
+ }
87
+
88
+ export interface Credential extends PairingIdentityMetadata {
89
+ credential: string;
90
+ credential_type: 'pre_claim' | 'active';
91
+ expires_in: number | null;
92
+ scopes: string[];
93
+ handle?: string;
94
+ claim?: { start_uri: string; complete_uri: string };
95
+ account?: { name?: string | null } | null;
96
+ /** The delivery room, or null when the human pinned none. */
97
+ room?: AgentDeliveryRoom | null;
98
+ room_access?: AgentRoomAccess | null;
99
+ /** The full grant. Empty under `room_access: 'all'`. */
100
+ rooms?: AgentAccessRoom[];
101
+ /** Server-authoritative endpoints associated with this connection. */
102
+ links?: AgentCredentialLinks;
103
+ }
104
+
105
+ /** The human a connection belongs to, as `auth.connection()` and `auth.disconnect()` report it. */
106
+ export interface AgentConnectionOwner {
107
+ /** Public user id. Match it to the account you approved before sending. */
108
+ id: string;
109
+ name: string;
110
+ }
111
+
112
+ /** `auth.connection()`: what this credential is connected to. */
113
+ export interface AgentConnectionInfo {
114
+ flow_version: 2;
115
+ claim_mode: 'agent_identity';
116
+ status: 'active';
117
+ handle: string | null;
118
+ agent: PairingAgent;
119
+ owner: AgentConnectionOwner;
120
+ /** The robot's Question/Handoff delivery room, or null when none is designated. */
121
+ home_room: AgentAccessRoom | null;
122
+ /** The robot's membership of its home room. */
123
+ room_membership: AgentRoomMembership | null;
124
+ /** Every active room membership, home room first. */
125
+ room_memberships: AgentRoomMembership[];
126
+ room_access: AgentRoomAccess | null;
127
+ links: AgentCredentialLinks;
128
+ [key: string]: unknown;
129
+ }
130
+
131
+ /** `auth.disconnect()`: the calling credential and its refresh credentials are revoked. */
132
+ export interface AgentDisconnectResult {
133
+ status: 'revoked';
134
+ handle: string | null;
135
+ owner: AgentConnectionOwner;
136
+ /** Reconnect guidance for the human. */
137
+ message: string;
138
+ [key: string]: unknown;
139
+ }
140
+
141
+ export interface ClaimStartParams {
142
+ email: string;
143
+ }
144
+
145
+ export interface ClaimCompleteParams {
146
+ email: string;
147
+ otp: string;
148
+ scopes?: ScopeInput[];
149
+ }
150
+
151
+ /** Start an app-approved pairing without copying an API token or room code. */
152
+ export interface StartPairingParams {
153
+ /**
154
+ * @deprecated Pairing grants are server-authoritative. This value is ignored
155
+ * and retained only so existing TypeScript callers continue to compile.
156
+ */
157
+ scopes?: ScopeInput[];
158
+ agent_label?: string;
159
+ }
160
+
161
+ /** Short-lived browser and native-app links returned when pairing starts. */
162
+ export interface PairingStart extends PairingIdentityMetadata {
163
+ pair_token: string;
164
+ /** Legacy approval link, kept QR-compatible for existing clients. */
165
+ pair_url: string;
166
+ /** Browser approval page. Fall back to `pair_url` on older servers. */
167
+ pair_browser_url?: string;
168
+ /** Native app universal link for QR renderers. Falls back to `pair_url` on older servers. */
169
+ pair_qr_url?: string;
170
+ /** Token-free smart install link. Never append `pair_token` to it. */
171
+ app_install_url?: string;
172
+ expires_in: number;
173
+ poll_interval_ms: number;
174
+ }
175
+
176
+ export type PairedCredential = Omit<Credential, 'credential_type' | 'scopes'> & {
177
+ credential_type: 'active';
178
+ /**
179
+ * The selected/deterministic private delivery room. Null remains valid when
180
+ * the account has no eligible private room (and for older servers).
181
+ */
182
+ room: AgentDeliveryRoom | null;
183
+ room_access: AgentRoomAccess;
184
+ /** The full grant. Empty under `room_access: 'all'`. */
185
+ rooms: AgentAccessRoom[];
186
+ };
187
+
188
+ export type PairingStatus =
189
+ | (PairingIdentityMetadata & { status: 'pending' })
190
+ | (PairingIdentityMetadata & { status: 'expired' })
191
+ | (PairedCredential & {
192
+ status: 'active';
193
+ /** Legacy server metadata accepted on input and omitted from the result. */
194
+ scopes?: string[];
195
+ });
196
+
197
+ export interface WaitForPairingOptions {
198
+ /** Stop waiting without consuming the pairing session. */
199
+ signal?: AbortSignal;
200
+ /** Override the server-suggested polling interval (clamped to 1–10 seconds). */
201
+ pollIntervalMs?: number;
202
+ }
@@ -0,0 +1,74 @@
1
+ /** A plain JSON object — the shape the API accepts for structured `data`. */
2
+ export type JsonObject = Record<string, unknown>;
3
+
4
+ /** Minimal fetch signature the SDK depends on (Node >= 18, browsers, workers, Deno, Bun). */
5
+ export type FetchLike = (input: string, init?: RequestInit) => Promise<Response>;
6
+
7
+ /** The scopes an agent credential can hold. */
8
+ export type AgentScope =
9
+ | 'pingroom:full'
10
+ | 'pingroom:rooms:read'
11
+ | 'pingroom:rooms:write'
12
+ | 'pingroom:rooms:publish'
13
+ | 'pingroom:rooms:join'
14
+ | 'pingroom:actions:write'
15
+ | 'pingroom:actions:trigger'
16
+ | 'pingroom:broadcast:send'
17
+ | 'pingroom:attachments:write'
18
+ | 'pingroom:notifications:read'
19
+ | 'pingroom:webhooks:read'
20
+ | 'pingroom:webhooks:write'
21
+ | 'pingroom:webhooks:delete'
22
+ | 'pingroom:agents:ping'
23
+ | 'pingroom:approvals:request'
24
+ | 'pingroom:questions:ask'
25
+ | 'pingroom:handoffs:create'
26
+ | 'pingroom:live:write'
27
+ | 'pingroom:profile:write'
28
+ | 'pingroom:codes:redeem';
29
+
30
+ /** A known scope, or any forward-compatible string (keeps autocomplete on the knowns). */
31
+ export type ScopeInput = AgentScope | (string & {});
32
+
33
+ // --- client options -------------------------------------------------------
34
+
35
+ export interface PingRoomOptions {
36
+ /** Agent credential (Bearer JWT). Optional — public reads (directory) work without it. */
37
+ token?: string | null;
38
+ /** API base URL. Defaults to env PINGROOM_API_URL/PINGROOM_BASE_URL, else https://api.pingroom.io. */
39
+ baseUrl?: string;
40
+ /** Default per-request timeout in ms (long-poll calls extend this automatically). Default 30000. */
41
+ timeoutMs?: number;
42
+ /** Custom fetch implementation (for tests or non-global-fetch runtimes). */
43
+ fetch?: FetchLike;
44
+ /** Override the User-Agent header. */
45
+ userAgent?: string;
46
+ /** Allow plain-http base URLs to non-loopback hosts. Off by default — credentials require https. */
47
+ allowInsecure?: boolean;
48
+ }
49
+
50
+ // --- gift and promotional codes --------------------------------------------
51
+
52
+ /** Pro entitlement applied to the human who authorized this agent. */
53
+ export interface RedeemCodeResult {
54
+ message: string;
55
+ kind: 'gift' | 'redeem';
56
+ reward_days: number | null;
57
+ package: string | null;
58
+ lifetime: boolean;
59
+ plan: 'pro';
60
+ plan_expires_at: string | null;
61
+ }
62
+
63
+ // --- pagination -----------------------------------------------------------
64
+
65
+ export interface Paginated<T> {
66
+ current_page: number;
67
+ data: T[];
68
+ per_page: number;
69
+ total: number;
70
+ last_page: number;
71
+ next_page_url: string | null;
72
+ prev_page_url: string | null;
73
+ [key: string]: unknown;
74
+ }