@ai-ecoverse/pi-audio 1.20.1 → 1.22.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 (39) hide show
  1. package/README.md +5 -1
  2. package/dist/cloudflare/catalog.js +165 -0
  3. package/dist/cloudflare/catalog.js.map +7 -0
  4. package/dist/cloudflare/index.js +5 -0
  5. package/dist/cloudflare/index.js.map +7 -0
  6. package/dist/cloudflare/listen.js +133 -0
  7. package/dist/cloudflare/listen.js.map +7 -0
  8. package/dist/cloudflare/speak.js +224 -0
  9. package/dist/cloudflare/speak.js.map +7 -0
  10. package/dist/cloudflare/transcribe.js +155 -0
  11. package/dist/cloudflare/transcribe.js.map +7 -0
  12. package/dist/cloudflare/wire.js +166 -0
  13. package/dist/cloudflare/wire.js.map +7 -0
  14. package/dist/fish-agents/events.js +94 -0
  15. package/dist/fish-agents/events.js.map +7 -0
  16. package/dist/fish-agents/index.js +57 -0
  17. package/dist/fish-agents/index.js.map +7 -0
  18. package/dist/fish-agents/provision.js +178 -0
  19. package/dist/fish-agents/provision.js.map +7 -0
  20. package/dist/fish-agents/room.js +80 -0
  21. package/dist/fish-agents/room.js.map +7 -0
  22. package/dist/fish-agents/session.js +304 -0
  23. package/dist/fish-agents/session.js.map +7 -0
  24. package/dist/realtime.js.map +2 -2
  25. package/dist/tokens.js +11 -1
  26. package/dist/tokens.js.map +2 -2
  27. package/dist/types/cloudflare/catalog.d.ts +1 -0
  28. package/dist/types/cloudflare/index.d.ts +1 -0
  29. package/dist/types/cloudflare/listen.d.ts +4 -0
  30. package/dist/types/cloudflare/speak.d.ts +4 -0
  31. package/dist/types/cloudflare/transcribe.d.ts +4 -0
  32. package/dist/types/cloudflare/wire.d.ts +27 -0
  33. package/dist/types/fish-agents/events.d.ts +21 -0
  34. package/dist/types/fish-agents/index.d.ts +11 -0
  35. package/dist/types/fish-agents/provision.d.ts +11 -0
  36. package/dist/types/fish-agents/room.d.ts +46 -0
  37. package/dist/types/fish-agents/session.d.ts +19 -0
  38. package/dist/types/realtime.d.ts +1 -0
  39. package/package.json +9 -1
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/realtime.ts"],
4
- "sourcesContent": ["import type {\n Message,\n ProviderRequestOptions,\n Tool,\n ToolCall,\n ToolResultMessage,\n Usage,\n} from '@earendil-works/pi-ai';\nimport {\n type AudioChunk,\n alignmentSeconds,\n type CharAlignment,\n chunkSeconds,\n type TextSpan,\n} from './audio.ts';\nimport { VoiceRequestError } from './errors.ts';\nimport type { Delivery, DeliverySupport, RealtimeModel } from './models.ts';\nimport { priceAudio, type RealtimeAudioCost } from './usage.ts';\n\nexport interface RealtimeContext {\n instructions: string;\n tools: Tool[];\n voice: string;\n history?: Message[];\n}\n\nexport interface RealtimeToken {\n value: string;\n expiresAt: number;\n model: string;\n}\n\nexport interface RealtimeOptions extends ProviderRequestOptions {\n token?: RealtimeToken;\n transport?: 'websocket' | 'webrtc';\n gateway?: { url: string; token: string };\n resumeHandle?: string;\n}\n\nexport type RealtimeEvent =\n | { type: 'session_start'; sessionId: string }\n | { type: 'input_speech_start' }\n | { type: 'input_transcript'; itemId: string; text: string; final: boolean }\n | {\n type: 'output_audio';\n itemId: string;\n chunk: AudioChunk;\n span?: TextSpan;\n alignment?: CharAlignment;\n }\n | { type: 'output_transcript'; itemId: string; text: string; final: boolean }\n | { type: 'tool_call'; call: ToolCall; blocking: boolean }\n | { type: 'tool_call_cancelled'; callId: string }\n | { type: 'response_done'; itemId: string; interrupted: boolean; usage: Usage }\n | { type: 'go_away'; timeLeftMs: number }\n | { type: 'resumption'; handle: string }\n | { type: 'error'; error: string; fatal: boolean };\n\nexport interface RealtimeSession extends AsyncIterable<RealtimeEvent> {\n readonly model: RealtimeModel<string>;\n appendAudio(chunk: AudioChunk): void;\n appendVideoFrame?(jpeg: Uint8Array): void;\n sendText(text: string): void;\n inject(text: string, delivery: Delivery): void;\n respond(callId: string, result: ToolResultMessage, delivery?: Delivery): void;\n interrupt(heard?: { itemId: string; heardMs: number }): void;\n update(patch: Partial<Pick<RealtimeContext, 'instructions' | 'tools' | 'voice'>>): void;\n close(): Promise<{ usage: Usage }>;\n}\n\nexport interface RealtimeApiProvider<TApi extends string = string> {\n api: TApi;\n connect: (\n model: RealtimeModel<TApi>,\n context: RealtimeContext,\n options?: RealtimeOptions\n ) => Promise<RealtimeSession>;\n}\n\nexport interface DeliveryPlan {\n delivery: Delivery;\n support: DeliverySupport;\n waitForIdle: boolean;\n}\n\nexport interface TruncationPlan {\n item?: { itemId: string; audioEndMs: number };\n}\n\nexport interface RealtimeSocket {\n url: string;\n protocols: string[];\n}\n\nexport function planDelivery(\n delivery: Record<Delivery, DeliverySupport>,\n requested: Delivery\n): DeliveryPlan {\n const support = delivery[requested];\n if (support !== 'native' && support !== 'approximate') {\n throw new VoiceRequestError('error', `unsupported delivery: ${requested}`);\n }\n return {\n delivery: requested,\n support,\n waitForIdle: requested === 'when_idle' && support === 'approximate',\n };\n}\n\nexport function planTruncation(\n truncation: RealtimeModel<string>['truncation'],\n heard?: { itemId: string; heardMs: number }\n): TruncationPlan {\n if (truncation === 'client' && heard !== undefined) {\n return { item: { itemId: heard.itemId, audioEndMs: heard.heardMs } };\n }\n return {};\n}\n\nexport function realtimeSocket(\n url: string,\n options?: Pick<RealtimeOptions, 'gateway'>,\n protocols: readonly string[] = []\n): RealtimeSocket {\n const gateway = options?.gateway;\n if (gateway === undefined) return { url, protocols: [...protocols] };\n return {\n url: gateway.url,\n protocols: [`cf-aig-authorization.${gateway.token}`, ...protocols],\n };\n}\n\nexport class RealtimeLedger {\n #inputSeconds = 0;\n #outputSeconds = 0;\n #textMessages = 0;\n #busy = false;\n #held: Array<() => void> = [];\n\n get inputSeconds(): number {\n return this.#inputSeconds;\n }\n\n get outputSeconds(): number {\n return this.#outputSeconds;\n }\n\n get textMessages(): number {\n return this.#textMessages;\n }\n\n get busy(): boolean {\n return this.#busy;\n }\n\n addInput(chunk: AudioChunk): void {\n this.#inputSeconds += chunkSeconds(chunk);\n }\n\n addOutput(chunk: AudioChunk, alignment?: CharAlignment): void {\n const seconds = chunkSeconds(chunk);\n this.#outputSeconds += seconds > 0 ? seconds : alignmentSeconds(alignment);\n }\n\n addText(): void {\n this.#textMessages += 1;\n }\n\n observe(event: RealtimeEvent): void {\n if (event.type === 'output_audio') this.addOutput(event.chunk, event.alignment);\n if (event.type === 'output_audio' || event.type === 'output_transcript') this.#busy = true;\n if (event.type === 'response_done') this.#release();\n }\n\n submit(\n delivery: Record<Delivery, DeliverySupport>,\n requested: Delivery,\n run: () => void\n ): DeliveryPlan {\n const plan = planDelivery(delivery, requested);\n if (plan.waitForIdle && this.#busy) this.#held.push(run);\n else run();\n return plan;\n }\n\n settle(): void {\n this.#release();\n }\n\n usage(rates: RealtimeAudioCost): Usage {\n return priceAudio(rates, {\n inputSeconds: this.#inputSeconds,\n outputSeconds: this.#outputSeconds,\n textMessages: this.#textMessages,\n });\n }\n\n #release(): void {\n this.#busy = false;\n const held = this.#held;\n this.#held = [];\n for (const run of held) run();\n }\n}\n"],
5
- "mappings": "AAQA;AAAA,EAEE;AAAA,EAEA;AAAA,OAEK;AACP,SAAS,yBAAyB;AAElC,SAAS,kBAA0C;AA6E5C,SAAS,aACd,UACA,WACc;AACd,QAAM,UAAU,SAAS,SAAS;AAClC,MAAI,YAAY,YAAY,YAAY,eAAe;AACrD,UAAM,IAAI,kBAAkB,SAAS,yBAAyB,SAAS,EAAE;AAAA,EAC3E;AACA,SAAO;AAAA,IACL,UAAU;AAAA,IACV;AAAA,IACA,aAAa,cAAc,eAAe,YAAY;AAAA,EACxD;AACF;AAEO,SAAS,eACd,YACA,OACgB;AAChB,MAAI,eAAe,YAAY,UAAU,QAAW;AAClD,WAAO,EAAE,MAAM,EAAE,QAAQ,MAAM,QAAQ,YAAY,MAAM,QAAQ,EAAE;AAAA,EACrE;AACA,SAAO,CAAC;AACV;AAEO,SAAS,eACd,KACA,SACA,YAA+B,CAAC,GAChB;AAChB,QAAM,UAAU,SAAS;AACzB,MAAI,YAAY,OAAW,QAAO,EAAE,KAAK,WAAW,CAAC,GAAG,SAAS,EAAE;AACnE,SAAO;AAAA,IACL,KAAK,QAAQ;AAAA,IACb,WAAW,CAAC,wBAAwB,QAAQ,KAAK,IAAI,GAAG,SAAS;AAAA,EACnE;AACF;AAEO,MAAM,eAAe;AAAA,EAC1B,gBAAgB;AAAA,EAChB,iBAAiB;AAAA,EACjB,gBAAgB;AAAA,EAChB,QAAQ;AAAA,EACR,QAA2B,CAAC;AAAA,EAE5B,IAAI,eAAuB;AACzB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,gBAAwB;AAC1B,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,eAAuB;AACzB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,OAAgB;AAClB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,SAAS,OAAyB;AAChC,SAAK,iBAAiB,aAAa,KAAK;AAAA,EAC1C;AAAA,EAEA,UAAU,OAAmB,WAAiC;AAC5D,UAAM,UAAU,aAAa,KAAK;AAClC,SAAK,kBAAkB,UAAU,IAAI,UAAU,iBAAiB,SAAS;AAAA,EAC3E;AAAA,EAEA,UAAgB;AACd,SAAK,iBAAiB;AAAA,EACxB;AAAA,EAEA,QAAQ,OAA4B;AAClC,QAAI,MAAM,SAAS,eAAgB,MAAK,UAAU,MAAM,OAAO,MAAM,SAAS;AAC9E,QAAI,MAAM,SAAS,kBAAkB,MAAM,SAAS,oBAAqB,MAAK,QAAQ;AACtF,QAAI,MAAM,SAAS,gBAAiB,MAAK,SAAS;AAAA,EACpD;AAAA,EAEA,OACE,UACA,WACA,KACc;AACd,UAAM,OAAO,aAAa,UAAU,SAAS;AAC7C,QAAI,KAAK,eAAe,KAAK,MAAO,MAAK,MAAM,KAAK,GAAG;AAAA,QAClD,KAAI;AACT,WAAO;AAAA,EACT;AAAA,EAEA,SAAe;AACb,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,MAAM,OAAiC;AACrC,WAAO,WAAW,OAAO;AAAA,MACvB,cAAc,KAAK;AAAA,MACnB,eAAe,KAAK;AAAA,MACpB,cAAc,KAAK;AAAA,IACrB,CAAC;AAAA,EACH;AAAA,EAEA,WAAiB;AACf,SAAK,QAAQ;AACb,UAAM,OAAO,KAAK;AAClB,SAAK,QAAQ,CAAC;AACd,eAAW,OAAO,KAAM,KAAI;AAAA,EAC9B;AACF;",
4
+ "sourcesContent": ["import type {\n Message,\n ProviderRequestOptions,\n Tool,\n ToolCall,\n ToolResultMessage,\n Usage,\n} from '@earendil-works/pi-ai';\nimport {\n type AudioChunk,\n alignmentSeconds,\n type CharAlignment,\n chunkSeconds,\n type TextSpan,\n} from './audio.ts';\nimport { VoiceRequestError } from './errors.ts';\nimport type { Delivery, DeliverySupport, RealtimeModel } from './models.ts';\nimport { priceAudio, type RealtimeAudioCost } from './usage.ts';\n\nexport interface RealtimeContext {\n instructions: string;\n tools: Tool[];\n voice: string;\n history?: Message[];\n}\n\nexport interface RealtimeToken {\n value: string;\n expiresAt: number;\n model: string;\n url?: string;\n}\n\nexport interface RealtimeOptions extends ProviderRequestOptions {\n token?: RealtimeToken;\n transport?: 'websocket' | 'webrtc';\n gateway?: { url: string; token: string };\n resumeHandle?: string;\n}\n\nexport type RealtimeEvent =\n | { type: 'session_start'; sessionId: string }\n | { type: 'input_speech_start' }\n | { type: 'input_transcript'; itemId: string; text: string; final: boolean }\n | {\n type: 'output_audio';\n itemId: string;\n chunk: AudioChunk;\n span?: TextSpan;\n alignment?: CharAlignment;\n }\n | { type: 'output_transcript'; itemId: string; text: string; final: boolean }\n | { type: 'tool_call'; call: ToolCall; blocking: boolean }\n | { type: 'tool_call_cancelled'; callId: string }\n | { type: 'response_done'; itemId: string; interrupted: boolean; usage: Usage }\n | { type: 'go_away'; timeLeftMs: number }\n | { type: 'resumption'; handle: string }\n | { type: 'error'; error: string; fatal: boolean };\n\nexport interface RealtimeSession extends AsyncIterable<RealtimeEvent> {\n readonly model: RealtimeModel<string>;\n appendAudio(chunk: AudioChunk): void;\n appendVideoFrame?(jpeg: Uint8Array): void;\n sendText(text: string): void;\n inject(text: string, delivery: Delivery): void;\n respond(callId: string, result: ToolResultMessage, delivery?: Delivery): void;\n interrupt(heard?: { itemId: string; heardMs: number }): void;\n update(patch: Partial<Pick<RealtimeContext, 'instructions' | 'tools' | 'voice'>>): void;\n close(): Promise<{ usage: Usage }>;\n}\n\nexport interface RealtimeApiProvider<TApi extends string = string> {\n api: TApi;\n connect: (\n model: RealtimeModel<TApi>,\n context: RealtimeContext,\n options?: RealtimeOptions\n ) => Promise<RealtimeSession>;\n}\n\nexport interface DeliveryPlan {\n delivery: Delivery;\n support: DeliverySupport;\n waitForIdle: boolean;\n}\n\nexport interface TruncationPlan {\n item?: { itemId: string; audioEndMs: number };\n}\n\nexport interface RealtimeSocket {\n url: string;\n protocols: string[];\n}\n\nexport function planDelivery(\n delivery: Record<Delivery, DeliverySupport>,\n requested: Delivery\n): DeliveryPlan {\n const support = delivery[requested];\n if (support !== 'native' && support !== 'approximate') {\n throw new VoiceRequestError('error', `unsupported delivery: ${requested}`);\n }\n return {\n delivery: requested,\n support,\n waitForIdle: requested === 'when_idle' && support === 'approximate',\n };\n}\n\nexport function planTruncation(\n truncation: RealtimeModel<string>['truncation'],\n heard?: { itemId: string; heardMs: number }\n): TruncationPlan {\n if (truncation === 'client' && heard !== undefined) {\n return { item: { itemId: heard.itemId, audioEndMs: heard.heardMs } };\n }\n return {};\n}\n\nexport function realtimeSocket(\n url: string,\n options?: Pick<RealtimeOptions, 'gateway'>,\n protocols: readonly string[] = []\n): RealtimeSocket {\n const gateway = options?.gateway;\n if (gateway === undefined) return { url, protocols: [...protocols] };\n return {\n url: gateway.url,\n protocols: [`cf-aig-authorization.${gateway.token}`, ...protocols],\n };\n}\n\nexport class RealtimeLedger {\n #inputSeconds = 0;\n #outputSeconds = 0;\n #textMessages = 0;\n #busy = false;\n #held: Array<() => void> = [];\n\n get inputSeconds(): number {\n return this.#inputSeconds;\n }\n\n get outputSeconds(): number {\n return this.#outputSeconds;\n }\n\n get textMessages(): number {\n return this.#textMessages;\n }\n\n get busy(): boolean {\n return this.#busy;\n }\n\n addInput(chunk: AudioChunk): void {\n this.#inputSeconds += chunkSeconds(chunk);\n }\n\n addOutput(chunk: AudioChunk, alignment?: CharAlignment): void {\n const seconds = chunkSeconds(chunk);\n this.#outputSeconds += seconds > 0 ? seconds : alignmentSeconds(alignment);\n }\n\n addText(): void {\n this.#textMessages += 1;\n }\n\n observe(event: RealtimeEvent): void {\n if (event.type === 'output_audio') this.addOutput(event.chunk, event.alignment);\n if (event.type === 'output_audio' || event.type === 'output_transcript') this.#busy = true;\n if (event.type === 'response_done') this.#release();\n }\n\n submit(\n delivery: Record<Delivery, DeliverySupport>,\n requested: Delivery,\n run: () => void\n ): DeliveryPlan {\n const plan = planDelivery(delivery, requested);\n if (plan.waitForIdle && this.#busy) this.#held.push(run);\n else run();\n return plan;\n }\n\n settle(): void {\n this.#release();\n }\n\n usage(rates: RealtimeAudioCost): Usage {\n return priceAudio(rates, {\n inputSeconds: this.#inputSeconds,\n outputSeconds: this.#outputSeconds,\n textMessages: this.#textMessages,\n });\n }\n\n #release(): void {\n this.#busy = false;\n const held = this.#held;\n this.#held = [];\n for (const run of held) run();\n }\n}\n"],
5
+ "mappings": "AAQA;AAAA,EAEE;AAAA,EAEA;AAAA,OAEK;AACP,SAAS,yBAAyB;AAElC,SAAS,kBAA0C;AA8E5C,SAAS,aACd,UACA,WACc;AACd,QAAM,UAAU,SAAS,SAAS;AAClC,MAAI,YAAY,YAAY,YAAY,eAAe;AACrD,UAAM,IAAI,kBAAkB,SAAS,yBAAyB,SAAS,EAAE;AAAA,EAC3E;AACA,SAAO;AAAA,IACL,UAAU;AAAA,IACV;AAAA,IACA,aAAa,cAAc,eAAe,YAAY;AAAA,EACxD;AACF;AAEO,SAAS,eACd,YACA,OACgB;AAChB,MAAI,eAAe,YAAY,UAAU,QAAW;AAClD,WAAO,EAAE,MAAM,EAAE,QAAQ,MAAM,QAAQ,YAAY,MAAM,QAAQ,EAAE;AAAA,EACrE;AACA,SAAO,CAAC;AACV;AAEO,SAAS,eACd,KACA,SACA,YAA+B,CAAC,GAChB;AAChB,QAAM,UAAU,SAAS;AACzB,MAAI,YAAY,OAAW,QAAO,EAAE,KAAK,WAAW,CAAC,GAAG,SAAS,EAAE;AACnE,SAAO;AAAA,IACL,KAAK,QAAQ;AAAA,IACb,WAAW,CAAC,wBAAwB,QAAQ,KAAK,IAAI,GAAG,SAAS;AAAA,EACnE;AACF;AAEO,MAAM,eAAe;AAAA,EAC1B,gBAAgB;AAAA,EAChB,iBAAiB;AAAA,EACjB,gBAAgB;AAAA,EAChB,QAAQ;AAAA,EACR,QAA2B,CAAC;AAAA,EAE5B,IAAI,eAAuB;AACzB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,gBAAwB;AAC1B,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,eAAuB;AACzB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,IAAI,OAAgB;AAClB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,SAAS,OAAyB;AAChC,SAAK,iBAAiB,aAAa,KAAK;AAAA,EAC1C;AAAA,EAEA,UAAU,OAAmB,WAAiC;AAC5D,UAAM,UAAU,aAAa,KAAK;AAClC,SAAK,kBAAkB,UAAU,IAAI,UAAU,iBAAiB,SAAS;AAAA,EAC3E;AAAA,EAEA,UAAgB;AACd,SAAK,iBAAiB;AAAA,EACxB;AAAA,EAEA,QAAQ,OAA4B;AAClC,QAAI,MAAM,SAAS,eAAgB,MAAK,UAAU,MAAM,OAAO,MAAM,SAAS;AAC9E,QAAI,MAAM,SAAS,kBAAkB,MAAM,SAAS,oBAAqB,MAAK,QAAQ;AACtF,QAAI,MAAM,SAAS,gBAAiB,MAAK,SAAS;AAAA,EACpD;AAAA,EAEA,OACE,UACA,WACA,KACc;AACd,UAAM,OAAO,aAAa,UAAU,SAAS;AAC7C,QAAI,KAAK,eAAe,KAAK,MAAO,MAAK,MAAM,KAAK,GAAG;AAAA,QAClD,KAAI;AACT,WAAO;AAAA,EACT;AAAA,EAEA,SAAe;AACb,SAAK,SAAS;AAAA,EAChB;AAAA,EAEA,MAAM,OAAiC;AACrC,WAAO,WAAW,OAAO;AAAA,MACvB,cAAc,KAAK;AAAA,MACnB,eAAe,KAAK;AAAA,MACpB,cAAc,KAAK;AAAA,IACrB,CAAC;AAAA,EACH;AAAA,EAEA,WAAiB;AACf,SAAK,QAAQ;AACb,UAAM,OAAO,KAAK;AAClB,SAAK,QAAQ,CAAC;AACd,eAAW,OAAO,KAAM,KAAI;AAAA,EAC9B;AACF;",
6
6
  "names": []
7
7
  }
package/dist/tokens.js CHANGED
@@ -159,6 +159,7 @@ function mintCall(model, constraints, options, key, now) {
159
159
  headers: mergedHeaders(options, { ...jsonHeaders, Authorization: `Bearer ${key}` }),
160
160
  body: JSON.stringify(fishBody(constraints)),
161
161
  field: "token",
162
+ urlField: "livekit_url",
162
163
  expires: fromField("expires_at"),
163
164
  fallbackExpires: now
164
165
  };
@@ -174,6 +175,12 @@ function readValue(json, field) {
174
175
  if (typeof value === "string" && value.length > 0) return value;
175
176
  throw new VoiceRequestError("error", "mint failed (empty token)");
176
177
  }
178
+ function readUrl(json, field) {
179
+ if (field === void 0) return void 0;
180
+ const value = json[field];
181
+ if (typeof value === "string" && value.length > 0) return value;
182
+ throw new VoiceRequestError("error", "mint failed (missing livekit url)");
183
+ }
177
184
  async function mintRealtimeToken(model, constraints, options) {
178
185
  const settings = options ?? {};
179
186
  const key = apiKey(settings);
@@ -188,11 +195,14 @@ async function mintRealtimeToken(model, constraints, options) {
188
195
  });
189
196
  if (!response.ok) throw new VoiceRequestError("error", `mint failed (${response.status})`);
190
197
  const json = await response.json();
191
- return {
198
+ const token = {
192
199
  value: readValue(json, call.field),
193
200
  expiresAt: call.expires(json, call.fallbackExpires),
194
201
  model: model.id
195
202
  };
203
+ const url = readUrl(json, call.urlField);
204
+ if (url !== void 0) token.url = url;
205
+ return token;
196
206
  }
197
207
  export {
198
208
  mintRealtimeToken
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/tokens.ts"],
4
- "sourcesContent": ["import type { ProviderRequestOptions, Tool } from '@earendil-works/pi-ai';\nimport { VoiceRequestError } from './errors.ts';\nimport type { RealtimeModel } from './models.ts';\nimport type { RealtimeToken } from './realtime.ts';\n\nexport interface RealtimeConstraints {\n instructions?: string;\n tools?: Tool[];\n voice?: string;\n uses?: number;\n agentId?: string;\n}\n\nconst openaiSecrets = 'https://api.openai.com/v1/realtime/client_secrets';\nconst xaiSecrets = 'https://api.x.ai/v1/realtime/client_secrets';\nconst geminiTokens = 'https://generativelanguage.googleapis.com/v1beta/auth_tokens';\nconst elevenSigned = 'https://api.elevenlabs.io/v1/convai/conversation/get-signed-url';\nconst elevenWebRtc = 'https://api.elevenlabs.io/v1/convai/conversation/token';\nconst fishSessions = 'https://api.fish.audio/v1/agent/sessions';\nconst fifteenMinutes = 15 * 60 * 1000;\nconst thirtyMinutes = 30 * 60 * 1000;\nconst oneMinute = 60 * 1000;\nconst xaiLifetimeSeconds = 300;\n\ntype Json = Record<string, unknown>;\n\ninterface MintCall {\n url: string;\n method: 'GET' | 'POST';\n headers: Record<string, string>;\n body?: string;\n field: string;\n expires: (json: Json, fallback: number) => number;\n fallbackExpires: number;\n}\n\nfunction apiKey(options: ProviderRequestOptions): string {\n const key = options.apiKey;\n if (typeof key === 'string' && key.length > 0) return key;\n throw new VoiceRequestError('error', 'missing realtime credential');\n}\n\nfunction mergedHeaders(\n options: ProviderRequestOptions,\n defaults: Record<string, string>\n): Record<string, string> {\n const headers = { ...defaults };\n const extra = options.headers;\n if (extra === undefined) return headers;\n for (const [key, value] of Object.entries(extra)) {\n if (value === null) delete headers[key];\n else headers[key] = value;\n }\n return headers;\n}\n\nfunction omitUndefined(source: Json): Json {\n const out: Json = {};\n for (const [key, value] of Object.entries(source)) {\n if (value !== undefined) out[key] = value;\n }\n return out;\n}\n\nfunction functionTool(tool: Tool): Json {\n return {\n type: 'function',\n name: tool.name,\n description: tool.description,\n parameters: tool.parameters as unknown,\n };\n}\n\nfunction declaration(tool: Tool): Json {\n return {\n name: tool.name,\n description: tool.description,\n parameters: tool.parameters as unknown,\n };\n}\n\nfunction requiredAgent(constraints: RealtimeConstraints): string {\n const id = constraints.agentId;\n if (typeof id === 'string' && id.length > 0) return id;\n throw new VoiceRequestError('error', 'missing agent id');\n}\n\nfunction expiresAt(value: unknown, fallback: number): number {\n if (typeof value === 'number' && Number.isFinite(value)) {\n return value < 1e12 ? value * 1000 : value;\n }\n if (typeof value === 'string') {\n const parsed = Date.parse(value);\n if (Number.isFinite(parsed)) return parsed;\n }\n return fallback;\n}\n\nfunction fromField(field: string): (json: Json, fallback: number) => number {\n return (json, fallback) => expiresAt(json[field], fallback);\n}\n\nfunction openaiBody(model: RealtimeModel<string>, constraints: RealtimeConstraints): Json {\n const session = omitUndefined({\n type: 'realtime',\n model: model.id,\n instructions: constraints.instructions,\n tools: constraints.tools?.map(functionTool),\n });\n if (constraints.voice !== undefined) session.audio = { output: { voice: constraints.voice } };\n return { session };\n}\n\nfunction geminiBody(\n model: RealtimeModel<string>,\n constraints: RealtimeConstraints,\n now: number\n): Json {\n const instructions = constraints.instructions;\n const voice = constraints.voice;\n const tools = constraints.tools;\n const config = omitUndefined({\n responseModalities: ['AUDIO'],\n sessionResumption: {},\n systemInstruction: instructions === undefined ? undefined : { parts: [{ text: instructions }] },\n speechConfig:\n voice === undefined\n ? undefined\n : { voiceConfig: { prebuiltVoiceConfig: { voiceName: voice } } },\n tools: tools === undefined ? undefined : [{ functionDeclarations: tools.map(declaration) }],\n });\n return {\n uses: constraints.uses ?? 1,\n expireTime: new Date(now + thirtyMinutes).toISOString(),\n newSessionExpireTime: new Date(now + oneMinute).toISOString(),\n liveConnectConstraints: { model: `models/${model.id}`, config },\n };\n}\n\nfunction fishBody(constraints: RealtimeConstraints): Json {\n const overrides = omitUndefined({\n system_prompt: constraints.instructions,\n voice_id: constraints.voice,\n });\n return omitUndefined({\n agent_id: requiredAgent(constraints),\n overrides: Object.keys(overrides).length > 0 ? overrides : undefined,\n });\n}\n\nfunction webrtcOnly(model: RealtimeModel<string>): boolean {\n return model.transports.length === 1 && model.transports[0] === 'webrtc';\n}\n\nfunction elevenUrl(model: RealtimeModel<string>, constraints: RealtimeConstraints): string {\n const url = new URL(webrtcOnly(model) ? elevenWebRtc : elevenSigned);\n url.searchParams.set('agent_id', requiredAgent(constraints));\n return url.toString();\n}\n\nfunction mintCall(\n model: RealtimeModel<string>,\n constraints: RealtimeConstraints,\n options: ProviderRequestOptions,\n key: string,\n now: number\n): MintCall {\n const jsonHeaders = { 'Content-Type': 'application/json' };\n switch (model.api) {\n case 'openai-realtime':\n return {\n url: openaiSecrets,\n method: 'POST',\n headers: mergedHeaders(options, { ...jsonHeaders, Authorization: `Bearer ${key}` }),\n body: JSON.stringify(openaiBody(model, constraints)),\n field: 'value',\n expires: fromField('expires_at'),\n fallbackExpires: now,\n };\n case 'xai-realtime':\n return {\n url: xaiSecrets,\n method: 'POST',\n headers: mergedHeaders(options, { ...jsonHeaders, Authorization: `Bearer ${key}` }),\n body: JSON.stringify({ expires_after: { seconds: xaiLifetimeSeconds } }),\n field: 'value',\n expires: fromField('expires_at'),\n fallbackExpires: now + xaiLifetimeSeconds * 1000,\n };\n case 'gemini-live':\n return {\n url: geminiTokens,\n method: 'POST',\n headers: mergedHeaders(options, { ...jsonHeaders, 'x-goog-api-key': key }),\n body: JSON.stringify(geminiBody(model, constraints, now)),\n field: 'name',\n expires: fromField('expireTime'),\n fallbackExpires: now + thirtyMinutes,\n };\n case 'elevenlabs-agents':\n return {\n url: elevenUrl(model, constraints),\n method: 'GET',\n headers: mergedHeaders(options, { 'xi-api-key': key }),\n field: webrtcOnly(model) ? 'token' : 'signed_url',\n expires: fromField('expires_at'),\n fallbackExpires: now + fifteenMinutes,\n };\n case 'fish-agents':\n return {\n url: fishSessions,\n method: 'POST',\n headers: mergedHeaders(options, { ...jsonHeaders, Authorization: `Bearer ${key}` }),\n body: JSON.stringify(fishBody(constraints)),\n field: 'token',\n expires: fromField('expires_at'),\n fallbackExpires: now,\n };\n default:\n throw new VoiceRequestError('error', 'unsupported realtime api');\n }\n}\n\nfunction readValue(json: unknown, field: string): string {\n if (typeof json !== 'object' || json === null) {\n throw new VoiceRequestError('error', 'mint failed (empty token)');\n }\n const value = (json as Json)[field];\n if (typeof value === 'string' && value.length > 0) return value;\n throw new VoiceRequestError('error', 'mint failed (empty token)');\n}\n\nexport async function mintRealtimeToken(\n model: RealtimeModel<string>,\n constraints: RealtimeConstraints,\n options?: ProviderRequestOptions\n): Promise<RealtimeToken> {\n const settings = options ?? {};\n const key = apiKey(settings);\n const now = Date.now();\n const call = mintCall(model, constraints, settings, key, now);\n const fetch = settings.fetch ?? globalThis.fetch;\n const response = await fetch(call.url, {\n method: call.method,\n headers: call.headers,\n body: call.body,\n signal: settings.signal,\n });\n if (!response.ok) throw new VoiceRequestError('error', `mint failed (${response.status})`);\n const json: unknown = await response.json();\n return {\n value: readValue(json, call.field),\n expiresAt: call.expires(json as Json, call.fallbackExpires),\n model: model.id,\n };\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import type { ProviderRequestOptions, Tool } from '@earendil-works/pi-ai';\nimport { VoiceRequestError } from './errors.ts';\nimport type { RealtimeModel } from './models.ts';\nimport type { RealtimeToken } from './realtime.ts';\n\nexport interface RealtimeConstraints {\n instructions?: string;\n tools?: Tool[];\n voice?: string;\n uses?: number;\n agentId?: string;\n}\n\nconst openaiSecrets = 'https://api.openai.com/v1/realtime/client_secrets';\nconst xaiSecrets = 'https://api.x.ai/v1/realtime/client_secrets';\nconst geminiTokens = 'https://generativelanguage.googleapis.com/v1beta/auth_tokens';\nconst elevenSigned = 'https://api.elevenlabs.io/v1/convai/conversation/get-signed-url';\nconst elevenWebRtc = 'https://api.elevenlabs.io/v1/convai/conversation/token';\nconst fishSessions = 'https://api.fish.audio/v1/agent/sessions';\nconst fifteenMinutes = 15 * 60 * 1000;\nconst thirtyMinutes = 30 * 60 * 1000;\nconst oneMinute = 60 * 1000;\nconst xaiLifetimeSeconds = 300;\n\ntype Json = Record<string, unknown>;\n\ninterface MintCall {\n url: string;\n method: 'GET' | 'POST';\n headers: Record<string, string>;\n body?: string;\n field: string;\n urlField?: string;\n expires: (json: Json, fallback: number) => number;\n fallbackExpires: number;\n}\n\nfunction apiKey(options: ProviderRequestOptions): string {\n const key = options.apiKey;\n if (typeof key === 'string' && key.length > 0) return key;\n throw new VoiceRequestError('error', 'missing realtime credential');\n}\n\nfunction mergedHeaders(\n options: ProviderRequestOptions,\n defaults: Record<string, string>\n): Record<string, string> {\n const headers = { ...defaults };\n const extra = options.headers;\n if (extra === undefined) return headers;\n for (const [key, value] of Object.entries(extra)) {\n if (value === null) delete headers[key];\n else headers[key] = value;\n }\n return headers;\n}\n\nfunction omitUndefined(source: Json): Json {\n const out: Json = {};\n for (const [key, value] of Object.entries(source)) {\n if (value !== undefined) out[key] = value;\n }\n return out;\n}\n\nfunction functionTool(tool: Tool): Json {\n return {\n type: 'function',\n name: tool.name,\n description: tool.description,\n parameters: tool.parameters as unknown,\n };\n}\n\nfunction declaration(tool: Tool): Json {\n return {\n name: tool.name,\n description: tool.description,\n parameters: tool.parameters as unknown,\n };\n}\n\nfunction requiredAgent(constraints: RealtimeConstraints): string {\n const id = constraints.agentId;\n if (typeof id === 'string' && id.length > 0) return id;\n throw new VoiceRequestError('error', 'missing agent id');\n}\n\nfunction expiresAt(value: unknown, fallback: number): number {\n if (typeof value === 'number' && Number.isFinite(value)) {\n return value < 1e12 ? value * 1000 : value;\n }\n if (typeof value === 'string') {\n const parsed = Date.parse(value);\n if (Number.isFinite(parsed)) return parsed;\n }\n return fallback;\n}\n\nfunction fromField(field: string): (json: Json, fallback: number) => number {\n return (json, fallback) => expiresAt(json[field], fallback);\n}\n\nfunction openaiBody(model: RealtimeModel<string>, constraints: RealtimeConstraints): Json {\n const session = omitUndefined({\n type: 'realtime',\n model: model.id,\n instructions: constraints.instructions,\n tools: constraints.tools?.map(functionTool),\n });\n if (constraints.voice !== undefined) session.audio = { output: { voice: constraints.voice } };\n return { session };\n}\n\nfunction geminiBody(\n model: RealtimeModel<string>,\n constraints: RealtimeConstraints,\n now: number\n): Json {\n const instructions = constraints.instructions;\n const voice = constraints.voice;\n const tools = constraints.tools;\n const config = omitUndefined({\n responseModalities: ['AUDIO'],\n sessionResumption: {},\n systemInstruction: instructions === undefined ? undefined : { parts: [{ text: instructions }] },\n speechConfig:\n voice === undefined\n ? undefined\n : { voiceConfig: { prebuiltVoiceConfig: { voiceName: voice } } },\n tools: tools === undefined ? undefined : [{ functionDeclarations: tools.map(declaration) }],\n });\n return {\n uses: constraints.uses ?? 1,\n expireTime: new Date(now + thirtyMinutes).toISOString(),\n newSessionExpireTime: new Date(now + oneMinute).toISOString(),\n liveConnectConstraints: { model: `models/${model.id}`, config },\n };\n}\n\nfunction fishBody(constraints: RealtimeConstraints): Json {\n const overrides = omitUndefined({\n system_prompt: constraints.instructions,\n voice_id: constraints.voice,\n });\n return omitUndefined({\n agent_id: requiredAgent(constraints),\n overrides: Object.keys(overrides).length > 0 ? overrides : undefined,\n });\n}\n\nfunction webrtcOnly(model: RealtimeModel<string>): boolean {\n return model.transports.length === 1 && model.transports[0] === 'webrtc';\n}\n\nfunction elevenUrl(model: RealtimeModel<string>, constraints: RealtimeConstraints): string {\n const url = new URL(webrtcOnly(model) ? elevenWebRtc : elevenSigned);\n url.searchParams.set('agent_id', requiredAgent(constraints));\n return url.toString();\n}\n\nfunction mintCall(\n model: RealtimeModel<string>,\n constraints: RealtimeConstraints,\n options: ProviderRequestOptions,\n key: string,\n now: number\n): MintCall {\n const jsonHeaders = { 'Content-Type': 'application/json' };\n switch (model.api) {\n case 'openai-realtime':\n return {\n url: openaiSecrets,\n method: 'POST',\n headers: mergedHeaders(options, { ...jsonHeaders, Authorization: `Bearer ${key}` }),\n body: JSON.stringify(openaiBody(model, constraints)),\n field: 'value',\n expires: fromField('expires_at'),\n fallbackExpires: now,\n };\n case 'xai-realtime':\n return {\n url: xaiSecrets,\n method: 'POST',\n headers: mergedHeaders(options, { ...jsonHeaders, Authorization: `Bearer ${key}` }),\n body: JSON.stringify({ expires_after: { seconds: xaiLifetimeSeconds } }),\n field: 'value',\n expires: fromField('expires_at'),\n fallbackExpires: now + xaiLifetimeSeconds * 1000,\n };\n case 'gemini-live':\n return {\n url: geminiTokens,\n method: 'POST',\n headers: mergedHeaders(options, { ...jsonHeaders, 'x-goog-api-key': key }),\n body: JSON.stringify(geminiBody(model, constraints, now)),\n field: 'name',\n expires: fromField('expireTime'),\n fallbackExpires: now + thirtyMinutes,\n };\n case 'elevenlabs-agents':\n return {\n url: elevenUrl(model, constraints),\n method: 'GET',\n headers: mergedHeaders(options, { 'xi-api-key': key }),\n field: webrtcOnly(model) ? 'token' : 'signed_url',\n expires: fromField('expires_at'),\n fallbackExpires: now + fifteenMinutes,\n };\n case 'fish-agents':\n return {\n url: fishSessions,\n method: 'POST',\n headers: mergedHeaders(options, { ...jsonHeaders, Authorization: `Bearer ${key}` }),\n body: JSON.stringify(fishBody(constraints)),\n field: 'token',\n urlField: 'livekit_url',\n expires: fromField('expires_at'),\n fallbackExpires: now,\n };\n default:\n throw new VoiceRequestError('error', 'unsupported realtime api');\n }\n}\n\nfunction readValue(json: unknown, field: string): string {\n if (typeof json !== 'object' || json === null) {\n throw new VoiceRequestError('error', 'mint failed (empty token)');\n }\n const value = (json as Json)[field];\n if (typeof value === 'string' && value.length > 0) return value;\n throw new VoiceRequestError('error', 'mint failed (empty token)');\n}\n\nfunction readUrl(json: unknown, field: string | undefined): string | undefined {\n if (field === undefined) return undefined;\n const value = (json as Json)[field];\n if (typeof value === 'string' && value.length > 0) return value;\n throw new VoiceRequestError('error', 'mint failed (missing livekit url)');\n}\n\nexport async function mintRealtimeToken(\n model: RealtimeModel<string>,\n constraints: RealtimeConstraints,\n options?: ProviderRequestOptions\n): Promise<RealtimeToken> {\n const settings = options ?? {};\n const key = apiKey(settings);\n const now = Date.now();\n const call = mintCall(model, constraints, settings, key, now);\n const fetch = settings.fetch ?? globalThis.fetch;\n const response = await fetch(call.url, {\n method: call.method,\n headers: call.headers,\n body: call.body,\n signal: settings.signal,\n });\n if (!response.ok) throw new VoiceRequestError('error', `mint failed (${response.status})`);\n const json: unknown = await response.json();\n const token: RealtimeToken = {\n value: readValue(json, call.field),\n expiresAt: call.expires(json as Json, call.fallbackExpires),\n model: model.id,\n };\n const url = readUrl(json, call.urlField);\n if (url !== undefined) token.url = url;\n return token;\n}\n"],
5
+ "mappings": "AACA,SAAS,yBAAyB;AAYlC,MAAM,gBAAgB;AACtB,MAAM,aAAa;AACnB,MAAM,eAAe;AACrB,MAAM,eAAe;AACrB,MAAM,eAAe;AACrB,MAAM,eAAe;AACrB,MAAM,iBAAiB,KAAK,KAAK;AACjC,MAAM,gBAAgB,KAAK,KAAK;AAChC,MAAM,YAAY,KAAK;AACvB,MAAM,qBAAqB;AAe3B,SAAS,OAAO,SAAyC;AACvD,QAAM,MAAM,QAAQ;AACpB,MAAI,OAAO,QAAQ,YAAY,IAAI,SAAS,EAAG,QAAO;AACtD,QAAM,IAAI,kBAAkB,SAAS,6BAA6B;AACpE;AAEA,SAAS,cACP,SACA,UACwB;AACxB,QAAM,UAAU,EAAE,GAAG,SAAS;AAC9B,QAAM,QAAQ,QAAQ;AACtB,MAAI,UAAU,OAAW,QAAO;AAChC,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,KAAK,GAAG;AAChD,QAAI,UAAU,KAAM,QAAO,QAAQ,GAAG;AAAA,QACjC,SAAQ,GAAG,IAAI;AAAA,EACtB;AACA,SAAO;AACT;AAEA,SAAS,cAAc,QAAoB;AACzC,QAAM,MAAY,CAAC;AACnB,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,MAAM,GAAG;AACjD,QAAI,UAAU,OAAW,KAAI,GAAG,IAAI;AAAA,EACtC;AACA,SAAO;AACT;AAEA,SAAS,aAAa,MAAkB;AACtC,SAAO;AAAA,IACL,MAAM;AAAA,IACN,MAAM,KAAK;AAAA,IACX,aAAa,KAAK;AAAA,IAClB,YAAY,KAAK;AAAA,EACnB;AACF;AAEA,SAAS,YAAY,MAAkB;AACrC,SAAO;AAAA,IACL,MAAM,KAAK;AAAA,IACX,aAAa,KAAK;AAAA,IAClB,YAAY,KAAK;AAAA,EACnB;AACF;AAEA,SAAS,cAAc,aAA0C;AAC/D,QAAM,KAAK,YAAY;AACvB,MAAI,OAAO,OAAO,YAAY,GAAG,SAAS,EAAG,QAAO;AACpD,QAAM,IAAI,kBAAkB,SAAS,kBAAkB;AACzD;AAEA,SAAS,UAAU,OAAgB,UAA0B;AAC3D,MAAI,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,GAAG;AACvD,WAAO,QAAQ,OAAO,QAAQ,MAAO;AAAA,EACvC;AACA,MAAI,OAAO,UAAU,UAAU;AAC7B,UAAM,SAAS,KAAK,MAAM,KAAK;AAC/B,QAAI,OAAO,SAAS,MAAM,EAAG,QAAO;AAAA,EACtC;AACA,SAAO;AACT;AAEA,SAAS,UAAU,OAAyD;AAC1E,SAAO,CAAC,MAAM,aAAa,UAAU,KAAK,KAAK,GAAG,QAAQ;AAC5D;AAEA,SAAS,WAAW,OAA8B,aAAwC;AACxF,QAAM,UAAU,cAAc;AAAA,IAC5B,MAAM;AAAA,IACN,OAAO,MAAM;AAAA,IACb,cAAc,YAAY;AAAA,IAC1B,OAAO,YAAY,OAAO,IAAI,YAAY;AAAA,EAC5C,CAAC;AACD,MAAI,YAAY,UAAU,OAAW,SAAQ,QAAQ,EAAE,QAAQ,EAAE,OAAO,YAAY,MAAM,EAAE;AAC5F,SAAO,EAAE,QAAQ;AACnB;AAEA,SAAS,WACP,OACA,aACA,KACM;AACN,QAAM,eAAe,YAAY;AACjC,QAAM,QAAQ,YAAY;AAC1B,QAAM,QAAQ,YAAY;AAC1B,QAAM,SAAS,cAAc;AAAA,IAC3B,oBAAoB,CAAC,OAAO;AAAA,IAC5B,mBAAmB,CAAC;AAAA,IACpB,mBAAmB,iBAAiB,SAAY,SAAY,EAAE,OAAO,CAAC,EAAE,MAAM,aAAa,CAAC,EAAE;AAAA,IAC9F,cACE,UAAU,SACN,SACA,EAAE,aAAa,EAAE,qBAAqB,EAAE,WAAW,MAAM,EAAE,EAAE;AAAA,IACnE,OAAO,UAAU,SAAY,SAAY,CAAC,EAAE,sBAAsB,MAAM,IAAI,WAAW,EAAE,CAAC;AAAA,EAC5F,CAAC;AACD,SAAO;AAAA,IACL,MAAM,YAAY,QAAQ;AAAA,IAC1B,YAAY,IAAI,KAAK,MAAM,aAAa,EAAE,YAAY;AAAA,IACtD,sBAAsB,IAAI,KAAK,MAAM,SAAS,EAAE,YAAY;AAAA,IAC5D,wBAAwB,EAAE,OAAO,UAAU,MAAM,EAAE,IAAI,OAAO;AAAA,EAChE;AACF;AAEA,SAAS,SAAS,aAAwC;AACxD,QAAM,YAAY,cAAc;AAAA,IAC9B,eAAe,YAAY;AAAA,IAC3B,UAAU,YAAY;AAAA,EACxB,CAAC;AACD,SAAO,cAAc;AAAA,IACnB,UAAU,cAAc,WAAW;AAAA,IACnC,WAAW,OAAO,KAAK,SAAS,EAAE,SAAS,IAAI,YAAY;AAAA,EAC7D,CAAC;AACH;AAEA,SAAS,WAAW,OAAuC;AACzD,SAAO,MAAM,WAAW,WAAW,KAAK,MAAM,WAAW,CAAC,MAAM;AAClE;AAEA,SAAS,UAAU,OAA8B,aAA0C;AACzF,QAAM,MAAM,IAAI,IAAI,WAAW,KAAK,IAAI,eAAe,YAAY;AACnE,MAAI,aAAa,IAAI,YAAY,cAAc,WAAW,CAAC;AAC3D,SAAO,IAAI,SAAS;AACtB;AAEA,SAAS,SACP,OACA,aACA,SACA,KACA,KACU;AACV,QAAM,cAAc,EAAE,gBAAgB,mBAAmB;AACzD,UAAQ,MAAM,KAAK;AAAA,IACjB,KAAK;AACH,aAAO;AAAA,QACL,KAAK;AAAA,QACL,QAAQ;AAAA,QACR,SAAS,cAAc,SAAS,EAAE,GAAG,aAAa,eAAe,UAAU,GAAG,GAAG,CAAC;AAAA,QAClF,MAAM,KAAK,UAAU,WAAW,OAAO,WAAW,CAAC;AAAA,QACnD,OAAO;AAAA,QACP,SAAS,UAAU,YAAY;AAAA,QAC/B,iBAAiB;AAAA,MACnB;AAAA,IACF,KAAK;AACH,aAAO;AAAA,QACL,KAAK;AAAA,QACL,QAAQ;AAAA,QACR,SAAS,cAAc,SAAS,EAAE,GAAG,aAAa,eAAe,UAAU,GAAG,GAAG,CAAC;AAAA,QAClF,MAAM,KAAK,UAAU,EAAE,eAAe,EAAE,SAAS,mBAAmB,EAAE,CAAC;AAAA,QACvE,OAAO;AAAA,QACP,SAAS,UAAU,YAAY;AAAA,QAC/B,iBAAiB,MAAM,qBAAqB;AAAA,MAC9C;AAAA,IACF,KAAK;AACH,aAAO;AAAA,QACL,KAAK;AAAA,QACL,QAAQ;AAAA,QACR,SAAS,cAAc,SAAS,EAAE,GAAG,aAAa,kBAAkB,IAAI,CAAC;AAAA,QACzE,MAAM,KAAK,UAAU,WAAW,OAAO,aAAa,GAAG,CAAC;AAAA,QACxD,OAAO;AAAA,QACP,SAAS,UAAU,YAAY;AAAA,QAC/B,iBAAiB,MAAM;AAAA,MACzB;AAAA,IACF,KAAK;AACH,aAAO;AAAA,QACL,KAAK,UAAU,OAAO,WAAW;AAAA,QACjC,QAAQ;AAAA,QACR,SAAS,cAAc,SAAS,EAAE,cAAc,IAAI,CAAC;AAAA,QACrD,OAAO,WAAW,KAAK,IAAI,UAAU;AAAA,QACrC,SAAS,UAAU,YAAY;AAAA,QAC/B,iBAAiB,MAAM;AAAA,MACzB;AAAA,IACF,KAAK;AACH,aAAO;AAAA,QACL,KAAK;AAAA,QACL,QAAQ;AAAA,QACR,SAAS,cAAc,SAAS,EAAE,GAAG,aAAa,eAAe,UAAU,GAAG,GAAG,CAAC;AAAA,QAClF,MAAM,KAAK,UAAU,SAAS,WAAW,CAAC;AAAA,QAC1C,OAAO;AAAA,QACP,UAAU;AAAA,QACV,SAAS,UAAU,YAAY;AAAA,QAC/B,iBAAiB;AAAA,MACnB;AAAA,IACF;AACE,YAAM,IAAI,kBAAkB,SAAS,0BAA0B;AAAA,EACnE;AACF;AAEA,SAAS,UAAU,MAAe,OAAuB;AACvD,MAAI,OAAO,SAAS,YAAY,SAAS,MAAM;AAC7C,UAAM,IAAI,kBAAkB,SAAS,2BAA2B;AAAA,EAClE;AACA,QAAM,QAAS,KAAc,KAAK;AAClC,MAAI,OAAO,UAAU,YAAY,MAAM,SAAS,EAAG,QAAO;AAC1D,QAAM,IAAI,kBAAkB,SAAS,2BAA2B;AAClE;AAEA,SAAS,QAAQ,MAAe,OAA+C;AAC7E,MAAI,UAAU,OAAW,QAAO;AAChC,QAAM,QAAS,KAAc,KAAK;AAClC,MAAI,OAAO,UAAU,YAAY,MAAM,SAAS,EAAG,QAAO;AAC1D,QAAM,IAAI,kBAAkB,SAAS,mCAAmC;AAC1E;AAEA,eAAsB,kBACpB,OACA,aACA,SACwB;AACxB,QAAM,WAAW,WAAW,CAAC;AAC7B,QAAM,MAAM,OAAO,QAAQ;AAC3B,QAAM,MAAM,KAAK,IAAI;AACrB,QAAM,OAAO,SAAS,OAAO,aAAa,UAAU,KAAK,GAAG;AAC5D,QAAM,QAAQ,SAAS,SAAS,WAAW;AAC3C,QAAM,WAAW,MAAM,MAAM,KAAK,KAAK;AAAA,IACrC,QAAQ,KAAK;AAAA,IACb,SAAS,KAAK;AAAA,IACd,MAAM,KAAK;AAAA,IACX,QAAQ,SAAS;AAAA,EACnB,CAAC;AACD,MAAI,CAAC,SAAS,GAAI,OAAM,IAAI,kBAAkB,SAAS,gBAAgB,SAAS,MAAM,GAAG;AACzF,QAAM,OAAgB,MAAM,SAAS,KAAK;AAC1C,QAAM,QAAuB;AAAA,IAC3B,OAAO,UAAU,MAAM,KAAK,KAAK;AAAA,IACjC,WAAW,KAAK,QAAQ,MAAc,KAAK,eAAe;AAAA,IAC1D,OAAO,MAAM;AAAA,EACf;AACA,QAAM,MAAM,QAAQ,MAAM,KAAK,QAAQ;AACvC,MAAI,QAAQ,OAAW,OAAM,MAAM;AACnC,SAAO;AACT;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1 @@
1
+ export declare function registerCloudflare(): () => void;
@@ -0,0 +1 @@
1
+ export { registerCloudflare } from './catalog.ts';
@@ -0,0 +1,4 @@
1
+ import type { AudioSource } from '../audio.ts';
2
+ import type { TranscriptionModel } from '../models.ts';
3
+ import { type ListenOptions, TranscriptionEventStream } from '../transcription.ts';
4
+ export declare function listenCloudflare(model: TranscriptionModel<string>, audio: AudioSource, options?: ListenOptions): TranscriptionEventStream;
@@ -0,0 +1,4 @@
1
+ import type { ProviderRequestOptions } from '@earendil-works/pi-ai';
2
+ import type { SpeechModel } from '../models.ts';
3
+ import { SpeechEventStream, type SpeechInput } from '../speech.ts';
4
+ export declare function speakCloudflare(model: SpeechModel<string>, input: SpeechInput, options?: ProviderRequestOptions): SpeechEventStream;
@@ -0,0 +1,4 @@
1
+ import type { ProviderRequestOptions } from '@earendil-works/pi-ai';
2
+ import type { TranscriptionModel } from '../models.ts';
3
+ import type { AudioInput, Transcript } from '../transcription.ts';
4
+ export declare function transcribeCloudflare(model: TranscriptionModel<string>, input: AudioInput, options?: ProviderRequestOptions): Promise<Transcript>;
@@ -0,0 +1,27 @@
1
+ import type { ProviderRequestOptions } from '@earendil-works/pi-ai';
2
+ import type { AudioFormat } from '../audio.ts';
3
+ interface RouteModel {
4
+ provider: string;
5
+ baseUrl: string;
6
+ headers?: Record<string, string>;
7
+ }
8
+ export interface CloudflareRoute {
9
+ headers: Record<string, string>;
10
+ account: string;
11
+ gateway: string;
12
+ }
13
+ export declare function gatewayId(headers: Record<string, string>): string;
14
+ export declare function session(model: RouteModel, options: ProviderRequestOptions | undefined): CloudflareRoute;
15
+ export declare function runUrl(baseUrl: string, modelId: string, account: string): string;
16
+ export declare function workersAiSocket(account: string, gateway: string, modelId: string, query: string): string;
17
+ export declare function auraEncoding(format: AudioFormat): string;
18
+ export declare function listenQuery(modelId: string, format: AudioFormat): string;
19
+ export declare function sameFormat(left: AudioFormat, right: AudioFormat): boolean;
20
+ export declare function connectSocket(url: string, headers: Record<string, string>): WebSocket;
21
+ export declare function awaitOpen(socket: WebSocket, label: string, fail: (message: string) => void): Promise<void>;
22
+ export declare function audioFrame(data: Uint8Array): ArrayBuffer;
23
+ export declare function messageBytes(data: unknown): Promise<Uint8Array | undefined>;
24
+ export declare function failureMessage(body: Record<string, unknown>, fallback: string): string;
25
+ export declare function resultOf(body: unknown): unknown;
26
+ export declare function errorText(text: string, status: number): string;
27
+ export {};
@@ -0,0 +1,21 @@
1
+ import type { JsonObject } from '@earendil-works/pi-ai';
2
+ import type { AudioChunk } from '../audio.ts';
3
+ import type { RealtimeEvent } from '../realtime.ts';
4
+ export interface FishTurn {
5
+ dropAudio: boolean;
6
+ discardFinals: number;
7
+ speaking: boolean;
8
+ turnOpen: boolean;
9
+ }
10
+ export interface FishText {
11
+ identity: string;
12
+ text: string;
13
+ attributes: Record<string, string>;
14
+ }
15
+ export declare function ingestFishData(message: Record<string, unknown>): RealtimeEvent[];
16
+ export declare function ingestFishText(turn: FishTurn, packet: FishText, localIdentity: string): RealtimeEvent[];
17
+ export declare function ingestFishAudio(turn: FishTurn, chunk: AudioChunk): RealtimeEvent[];
18
+ export declare function ingestFishState(turn: FishTurn, value: string): RealtimeEvent[];
19
+ export declare function ingestSpeakers(turn: FishTurn, identities: string[]): RealtimeEvent[];
20
+ export declare function userMessage(value: string, speak: boolean): JsonObject;
21
+ export declare function toolResult(callId: string, result: string, isError: boolean): JsonObject;
@@ -0,0 +1,11 @@
1
+ import type { RealtimeModel } from '../models.ts';
2
+ import type { FishOptions } from './session.ts';
3
+ export declare const fishAgent: RealtimeModel<'fish-agents'>;
4
+ export declare function registerFishAgents(options?: FishOptions): () => void;
5
+ export type { CustomLlm, HostedAgent, HostedAgentSpec, HostedCache } from '../hosted-agent.ts';
6
+ export { hashHostedSpec, memoryHostedCache } from '../hosted-agent.ts';
7
+ export type { ProvisionOptions } from './provision.ts';
8
+ export { agentConfig, httpsOrigin, provisionAgent } from './provision.ts';
9
+ export type { FishRoom, FishRoomFactory, FishSdk } from './room.ts';
10
+ export { importFishSdk, openFishRoom } from './room.ts';
11
+ export type { FishOptions } from './session.ts';
@@ -0,0 +1,11 @@
1
+ import type { JsonObject, ProviderRequestOptions } from '@earendil-works/pi-ai';
2
+ import { type HostedAgent, type HostedAgentSpec, type HostedCache } from '../hosted-agent.ts';
3
+ import type { RealtimeModel } from '../models.ts';
4
+ export interface ProvisionOptions extends ProviderRequestOptions {
5
+ agentId?: string;
6
+ now?: () => number;
7
+ cache?: HostedCache;
8
+ }
9
+ export declare function httpsOrigin(baseUrl: string): string;
10
+ export declare function agentConfig(spec: HostedAgentSpec, toolIds: string[]): JsonObject;
11
+ export declare function provisionAgent(model: RealtimeModel<string>, spec: HostedAgentSpec, options?: ProvisionOptions): Promise<HostedAgent>;
@@ -0,0 +1,46 @@
1
+ import type { AudioChunk, AudioSource } from '../audio.ts';
2
+ import type { MicOpen } from '../echo.ts';
3
+ export interface FishSdkRoom {
4
+ connect(url: string, token: string): Promise<void>;
5
+ disconnect(): void;
6
+ localParticipant: {
7
+ identity?: string;
8
+ publishTrack(track: unknown, options?: unknown): Promise<unknown>;
9
+ publishData(data: Uint8Array, options: {
10
+ reliable: boolean;
11
+ topic: string;
12
+ }): Promise<void>;
13
+ };
14
+ on(event: string, listener: (...args: unknown[]) => void): void;
15
+ registerTextStreamHandler(topic: string, handler: (reader: {
16
+ readAll(): Promise<string>;
17
+ info: {
18
+ attributes?: Record<string, string>;
19
+ };
20
+ }, participant: {
21
+ identity: string;
22
+ }) => void): void;
23
+ }
24
+ export interface FishSdk {
25
+ Room: new () => FishSdkRoom;
26
+ }
27
+ export interface FishHandlers {
28
+ data(topic: string, json: unknown, identity: string | undefined): void;
29
+ text(identity: string, value: string, attributes: Record<string, string>): void;
30
+ attribute(key: string, value: string): void;
31
+ speakers(identities: string[]): void;
32
+ audio(chunk: AudioChunk): void;
33
+ fail(error: unknown): void;
34
+ }
35
+ export interface FishRoom {
36
+ url?: string;
37
+ token?: string;
38
+ identity: string;
39
+ connect(url: string, token: string): Promise<void>;
40
+ publishMic(source: AudioSource, open: MicOpen): Promise<void>;
41
+ publishData(payload: string): Promise<void>;
42
+ close(): void;
43
+ }
44
+ export type FishRoomFactory = (handlers: FishHandlers) => FishRoom;
45
+ export declare function importFishSdk(load?: (specifier: string) => Promise<FishSdk>): Promise<FishSdk>;
46
+ export declare function openFishRoom(sdk: FishSdk, handlers: FishHandlers): FishRoom;
@@ -0,0 +1,19 @@
1
+ import type { AudioSource } from '../audio.ts';
2
+ import { type EchoControl } from '../echo.ts';
3
+ import type { HostedAgentSpec, HostedCache } from '../hosted-agent.ts';
4
+ import type { RealtimeModel } from '../models.ts';
5
+ import type { RealtimeContext, RealtimeOptions, RealtimeSession } from '../realtime.ts';
6
+ import { type FishRoomFactory, type FishSdk } from './room.ts';
7
+ export interface FishOptions {
8
+ room?: FishRoomFactory;
9
+ sdk?: FishSdk;
10
+ cache?: HostedCache;
11
+ agentId?: string;
12
+ now?: () => number;
13
+ firstMessage?: string;
14
+ microphone?: AudioSource;
15
+ echo?: EchoControl;
16
+ loadSdk?: (specifier: string) => Promise<FishSdk>;
17
+ }
18
+ export declare function specFromContext(model: RealtimeModel<string>, context: RealtimeContext): HostedAgentSpec;
19
+ export declare function connectFish(model: RealtimeModel<string>, context: RealtimeContext, options: RealtimeOptions | undefined, settings?: FishOptions): Promise<RealtimeSession>;
@@ -12,6 +12,7 @@ export interface RealtimeToken {
12
12
  value: string;
13
13
  expiresAt: number;
14
14
  model: string;
15
+ url?: string;
15
16
  }
16
17
  export interface RealtimeOptions extends ProviderRequestOptions {
17
18
  token?: RealtimeToken;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-ecoverse/pi-audio",
3
- "version": "1.20.1",
3
+ "version": "1.22.0",
4
4
  "description": "Voice bindings for pi: speech-to-text, text-to-speech and realtime speech for pi-coding-agent plugins and slicc-agent",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -62,6 +62,10 @@
62
62
  "types": "./dist/types/google/index.d.ts",
63
63
  "default": "./dist/google/index.js"
64
64
  },
65
+ "./cloudflare": {
66
+ "types": "./dist/types/cloudflare/index.d.ts",
67
+ "default": "./dist/cloudflare/index.js"
68
+ },
65
69
  "./durable": {
66
70
  "types": "./dist/types/durable/index.d.ts",
67
71
  "default": "./dist/durable/index.js"
@@ -73,6 +77,10 @@
73
77
  "./elevenlabs-agents": {
74
78
  "types": "./dist/types/elevenlabs-agents/index.d.ts",
75
79
  "default": "./dist/elevenlabs-agents/index.js"
80
+ },
81
+ "./fish-agents": {
82
+ "types": "./dist/types/fish-agents/index.d.ts",
83
+ "default": "./dist/fish-agents/index.js"
76
84
  }
77
85
  },
78
86
  "files": [