crowdplaysdk 0.3.4 → 0.4.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/LICENSE.md ADDED
@@ -0,0 +1,14 @@
1
+ # CrowdPlay SDK License
2
+
3
+ Copyright © 2026 Symbia Co. All rights reserved.
4
+
5
+ The CrowdPlay SDK is distributed as a compiled binary for integration into
6
+ applications that deliver recorded sessions to CrowdPlay. You may download,
7
+ link, and ship the binary as part of such an application. You may not
8
+ reverse engineer, decompile, redistribute the binary standalone, or use it
9
+ to deliver data anywhere other than CrowdPlay. No source license is granted.
10
+
11
+ This package depends on open-source software fetched from its own
12
+ repositories under its own licenses: LiveKit Swift SDK and WebRTC
13
+ (Apache License 2.0), swift-atomics (Apache License 2.0 with Runtime
14
+ Library Exception). Their license terms apply to those components.
@@ -278,8 +278,20 @@ public final class CrowdPlayRNModule: RCTEventEmitter {
278
278
 
279
279
  @objc public func join(_ displayName: NSString, roomCode: NSString,
280
280
  consentGrantedAtMs: NSNumber,
281
+ agent: NSDictionary?,
281
282
  resolver: @escaping RCTPromiseResolveBlock,
282
283
  rejecter: @escaping RCTPromiseRejectBlock) {
284
+ // Per-join voice-AI overrides (D-095): the JS layer already clamped
285
+ // and dropped blanks; nil means "dashboard defaults".
286
+ var agentOptions: CrowdPlayAgentOptions?
287
+ if let agent {
288
+ let voice = agent["voice"] as? String
289
+ let prompt = agent["systemPrompt"] as? String
290
+ let context = agent["context"] as? String
291
+ if voice != nil || prompt != nil || context != nil {
292
+ agentOptions = CrowdPlayAgentOptions(voice: voice, systemPrompt: prompt, context: context)
293
+ }
294
+ }
283
295
  Task { @MainActor in
284
296
  let core = CrowdPlayRNCore.shared
285
297
  core.displayName = displayName as String
@@ -292,7 +304,8 @@ public final class CrowdPlayRNModule: RCTEventEmitter {
292
304
  )
293
305
  await core.engine.join(displayName: displayName as String,
294
306
  roomCode: roomCode as String,
295
- consent: consent)
307
+ consent: consent,
308
+ agent: agentOptions)
296
309
  if case let .failed(message) = core.engine.phase {
297
310
  rejecter("join_failed", message, nil)
298
311
  } else {
@@ -13,6 +13,7 @@ RCT_EXTERN_METHOD(consentText : (RCTPromiseResolveBlock)resolve
13
13
  RCT_EXTERN_METHOD(join : (NSString *)displayName
14
14
  roomCode : (NSString *)roomCode
15
15
  consentGrantedAtMs : (nonnull NSNumber *)consentGrantedAtMs
16
+ agent : (NSDictionary *)agent
16
17
  resolver : (RCTPromiseResolveBlock)resolve
17
18
  rejecter : (RCTPromiseRejectBlock)reject)
18
19
  RCT_EXTERN_METHOD(leave : (RCTPromiseResolveBlock)resolve
package/ios/wire.rb CHANGED
@@ -25,7 +25,7 @@ SDK_URL = 'https://github.com/symbiateam/crowdplaysdk'
25
25
  # The ENGINE release this bridge was tested against. The npm package
26
26
  # version may be AHEAD of this (bridge/docs fixes ship without a new
27
27
  # engine binary); that is deliberate, not drift.
28
- SDK_VERSION = '0.3.4'
28
+ SDK_VERSION = '0.4.0'
29
29
  BRIDGE_FILES = ['CrowdPlayRNModule.swift', 'CrowdPlayRNVideoView.swift', 'CrowdPlayReactNative.m'].freeze
30
30
 
31
31
  project_name = ARGV[0] or abort 'usage: ruby wire.rb <YourProjectName>'
package/lib/index.d.ts CHANGED
@@ -58,6 +58,28 @@ export interface ConsentGrant {
58
58
  /** Date.now() at the moment the participant agreed. */
59
59
  grantedAtMs: number;
60
60
  }
61
+ /**
62
+ * Per-join settings for the voice AI (D-095), for apps with the Voice AI
63
+ * switch on. The dashboard config stays the default; anything passed here
64
+ * overrides it for THIS session only. Fixed once the session starts (leave
65
+ * and join again to change). The options of the join that creates the AI
66
+ * apply to the whole room. The app can never change the provider, the
67
+ * model, or the API key from here.
68
+ */
69
+ export interface AgentOptions {
70
+ /** A voice of the app's configured provider. Gemini: Charon, Aoede,
71
+ * Fenrir, Kore, Puck. OpenAI: marin, cedar, alloy, ash, ballad, coral,
72
+ * echo, sage, shimmer, verse. Grok: Ara, Rex, Sal, Eve, Leo. Unknown
73
+ * names fall back to the dashboard voice. */
74
+ voice?: string;
75
+ /** Replaces the dashboard persona for this session. Max 8192 chars. */
76
+ systemPrompt?: string;
77
+ /** Short text appended to the persona: what the app knows about this
78
+ * user, where they left off. This is how an app gives the AI memory:
79
+ * keep a few sentences per user in your own storage and pass them
80
+ * here. Max 4000 chars. */
81
+ context?: string;
82
+ }
61
83
  export type Phase = 'idle' | 'connecting' | 'connected' | 'failed';
62
84
  export interface RecordingSnapshot {
63
85
  /** Seconds of audio safely written so far. */
@@ -161,6 +183,7 @@ declare const CrowdPlay: {
161
183
  displayName: string;
162
184
  roomCode: string;
163
185
  consent: ConsentGrant;
186
+ agent?: AgentOptions;
164
187
  }): Promise<void>;
165
188
  /** Leave the room. Recording stops; uploads proceed automatically (they
166
189
  * survive backgrounding; force-quit pauses them until next launch). */
package/lib/index.js CHANGED
@@ -81,7 +81,7 @@ const CrowdPlay = {
81
81
  if (!configured) {
82
82
  throw new Error('crowdplaysdk: call CrowdPlay.configure() before join()');
83
83
  }
84
- const { displayName, roomCode, consent } = options ?? {};
84
+ const { displayName, roomCode, consent, agent } = options ?? {};
85
85
  if (!displayName?.trim())
86
86
  throw new Error('crowdplaysdk: displayName is required');
87
87
  if (!roomCode?.trim())
@@ -91,7 +91,19 @@ const CrowdPlay = {
91
91
  '(or your own UI displaying CrowdPlay.consentText()) and pass its result. ' +
92
92
  'Recording without consent is not supported.');
93
93
  }
94
- return native().join(displayName.trim(), roomCode, consent.grantedAtMs);
94
+ // Flat strings, clamped to the server's caps; blanks are not sent.
95
+ let agentFields = null;
96
+ if (agent && typeof agent === 'object') {
97
+ const put = (key, value, max) => {
98
+ if (typeof value === 'string' && value.trim()) {
99
+ agentFields = { ...(agentFields ?? {}), [key]: value.slice(0, max) };
100
+ }
101
+ };
102
+ put('voice', agent.voice, 60);
103
+ put('systemPrompt', agent.systemPrompt, 8192);
104
+ put('context', agent.context, 4000);
105
+ }
106
+ return native().join(displayName.trim(), roomCode, consent.grantedAtMs, agentFields);
95
107
  },
96
108
  /** Leave the room. Recording stops; uploads proceed automatically (they
97
109
  * survive backgrounding; force-quit pauses them until next launch). */
package/llms.txt CHANGED
@@ -132,7 +132,9 @@ Default export `CrowdPlay`:
132
132
  consentText(): Promise<string> // the REQUIRED consent wording
133
133
  warmUp(): void // pre-warm backend from the join screen
134
134
  join(opts: { displayName: string; roomCode: string;
135
- consent: ConsentGrant }): Promise<void> // THROWS without consent
135
+ consent: ConsentGrant;
136
+ agent?: AgentOptions }): Promise<void> // THROWS without consent
137
+ AgentOptions { voice?: string; systemPrompt?: string; context?: string } // 0.4.0+, voice-AI apps
136
138
  leave(): Promise<void>
137
139
  setMicMuted(muted: boolean): Promise<void>
138
140
  setCameraEnabled(enabled: boolean): Promise<void>
@@ -159,8 +161,21 @@ call, CrowdPlay's servers put a live AI participant into the room within
159
161
  the user, and holds a real spoken conversation (speech-to-speech: it
160
162
  hears the user directly, answers in natural voice, can be interrupted).
161
163
  The app writes ZERO AI code — no model API calls, no AI keys, no chat
162
- backend, no STT/TTS. Persona (system prompt), voice, and model are set by
163
- the BUILDER on the CrowdPlay dashboard and apply from the next call.
164
+ backend, no STT/TTS. Provider, model and API key are set by the BUILDER on
165
+ the CrowdPlay dashboard, and so are the DEFAULT persona and voice.
166
+ Per-join overrides (0.4.0+): the app MAY pass `agent` to join() to change
167
+ the voice and the persona and give a short context text for one session:
168
+ await CrowdPlay.join({ displayName, roomCode, consent: grant,
169
+ agent: { voice: 'Aoede', context: "The user's name is Sam. Last time you practised past tense." } });
170
+ voice must be one of the configured provider's voices (Gemini: Charon,
171
+ Aoede, Fenrir, Kore, Puck; OpenAI: marin, cedar, alloy, ash, ballad,
172
+ coral, echo, sage, shimmer, verse; Grok: Ara, Rex, Sal, Eve, Leo), unknown
173
+ falls back to the dashboard voice. Caps: systemPrompt 8192 chars, context
174
+ 4000. The options of the join that CREATES the AI apply to the whole room;
175
+ fixed for the session (leave and join again to change); provider, model
176
+ and key cannot be changed from the app. MEMORY is built with `context`:
177
+ keep a few sentences per user in the app's own storage and pass them at
178
+ every join.
164
179
  Conversations are recorded and transcribed by the platform automatically.
165
180
  RECOMMENDED: voice-AI apps should be AUDIO-ONLY (audioOnly: true — no
166
181
  camera permission, no video anywhere); a camera adds nothing to talking
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "crowdplaysdk",
3
- "version": "0.3.4",
3
+ "version": "0.4.1",
4
4
  "description": "CrowdPlay lossless conversation capture for React Native (iOS). Studio-grade per-participant recording during live video calls, delivered to CrowdPlay automatically.",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -10,7 +10,8 @@
10
10
  "ios",
11
11
  "README.md",
12
12
  "llms.txt",
13
- "Examples"
13
+ "Examples",
14
+ "LICENSE.md"
14
15
  ],
15
16
  "scripts": {
16
17
  "build": "tsc",
package/src/index.ts CHANGED
@@ -68,6 +68,29 @@ export interface ConsentGrant {
68
68
  grantedAtMs: number;
69
69
  }
70
70
 
71
+ /**
72
+ * Per-join settings for the voice AI (D-095), for apps with the Voice AI
73
+ * switch on. The dashboard config stays the default; anything passed here
74
+ * overrides it for THIS session only. Fixed once the session starts (leave
75
+ * and join again to change). The options of the join that creates the AI
76
+ * apply to the whole room. The app can never change the provider, the
77
+ * model, or the API key from here.
78
+ */
79
+ export interface AgentOptions {
80
+ /** A voice of the app's configured provider. Gemini: Charon, Aoede,
81
+ * Fenrir, Kore, Puck. OpenAI: marin, cedar, alloy, ash, ballad, coral,
82
+ * echo, sage, shimmer, verse. Grok: Ara, Rex, Sal, Eve, Leo. Unknown
83
+ * names fall back to the dashboard voice. */
84
+ voice?: string;
85
+ /** Replaces the dashboard persona for this session. Max 8192 chars. */
86
+ systemPrompt?: string;
87
+ /** Short text appended to the persona: what the app knows about this
88
+ * user, where they left off. This is how an app gives the AI memory:
89
+ * keep a few sentences per user in your own storage and pass them
90
+ * here. Max 4000 chars. */
91
+ context?: string;
92
+ }
93
+
71
94
  export type Phase = 'idle' | 'connecting' | 'connected' | 'failed';
72
95
 
73
96
  export interface RecordingSnapshot {
@@ -144,7 +167,8 @@ export type CrowdPlayEventName = keyof CrowdPlayEvents;
144
167
  interface NativeCrowdPlay {
145
168
  configure(config: Record<string, unknown>): void;
146
169
  consentText(): Promise<string>;
147
- join(displayName: string, roomCode: string, consentGrantedAtMs: number): Promise<void>;
170
+ join(displayName: string, roomCode: string, consentGrantedAtMs: number,
171
+ agent: Record<string, string> | null): Promise<void>;
148
172
  leave(): Promise<void>;
149
173
  setMicMuted(muted: boolean): Promise<void>;
150
174
  setCameraEnabled(enabled: boolean): Promise<void>;
@@ -220,11 +244,13 @@ const CrowdPlay = {
220
244
  * your own UI at the moment of agreement. There is no way to record
221
245
  * without it, here or natively.
222
246
  */
223
- async join(options: { displayName: string; roomCode: string; consent: ConsentGrant }): Promise<void> {
247
+ async join(options: {
248
+ displayName: string; roomCode: string; consent: ConsentGrant; agent?: AgentOptions;
249
+ }): Promise<void> {
224
250
  if (!configured) {
225
251
  throw new Error('crowdplaysdk: call CrowdPlay.configure() before join()');
226
252
  }
227
- const { displayName, roomCode, consent } = options ?? ({} as never);
253
+ const { displayName, roomCode, consent, agent } = options ?? ({} as never);
228
254
  if (!displayName?.trim()) throw new Error('crowdplaysdk: displayName is required');
229
255
  if (!roomCode?.trim()) throw new Error('crowdplaysdk: roomCode is required');
230
256
  if (!consent || typeof consent.grantedAtMs !== 'number' || consent.grantedAtMs <= 0) {
@@ -234,7 +260,19 @@ const CrowdPlay = {
234
260
  'Recording without consent is not supported.'
235
261
  );
236
262
  }
237
- return native().join(displayName.trim(), roomCode, consent.grantedAtMs);
263
+ // Flat strings, clamped to the server's caps; blanks are not sent.
264
+ let agentFields: Record<string, string> | null = null;
265
+ if (agent && typeof agent === 'object') {
266
+ const put = (key: string, value: unknown, max: number) => {
267
+ if (typeof value === 'string' && value.trim()) {
268
+ agentFields = { ...(agentFields ?? {}), [key]: value.slice(0, max) };
269
+ }
270
+ };
271
+ put('voice', agent.voice, 60);
272
+ put('systemPrompt', agent.systemPrompt, 8192);
273
+ put('context', agent.context, 4000);
274
+ }
275
+ return native().join(displayName.trim(), roomCode, consent.grantedAtMs, agentFields);
238
276
  },
239
277
 
240
278
  /** Leave the room. Recording stops; uploads proceed automatically (they