@ai-ecoverse/pi-audio 1.18.0 → 1.19.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.
package/dist/talker.js CHANGED
@@ -88,6 +88,16 @@ function capResult(result, maxChars) {
88
88
  });
89
89
  return { ...result, content };
90
90
  }
91
+ function requestContext(call) {
92
+ const value = call.arguments.context;
93
+ return typeof value === "string" ? value : void 0;
94
+ }
95
+ function track(pending, promise) {
96
+ pending.add(promise);
97
+ void promise.finally(() => {
98
+ pending.delete(promise);
99
+ });
100
+ }
91
101
  function bindThinker(session, thinker, direct, options) {
92
102
  const captions = new PushStream();
93
103
  const phases = new PushStream();
@@ -102,42 +112,31 @@ function bindThinker(session, thinker, direct, options) {
102
112
  async function run(session, thinker, direct, options, captions, phases) {
103
113
  const heard = [];
104
114
  const running = /* @__PURE__ */ new Map();
115
+ const pending = /* @__PURE__ */ new Set();
105
116
  for await (const event of session) {
106
117
  if (event.type === "input_transcript" && event.final) heard.push(`user: ${event.text}`);
107
118
  if (event.type === "output_transcript" && event.final) heard.push(`talker: ${event.text}`);
108
119
  if (event.type === "tool_call_cancelled") {
109
- const job2 = running.get(event.callId);
110
- if (!job2) continue;
111
- job2.abort.abort();
112
- if (!job2.answered) {
113
- job2.answered = true;
114
- send(() => session.respond(event.callId, errorResult(job2.call, new Error("cancelled"))));
115
- }
120
+ cancelJob(session, running, event.callId);
116
121
  continue;
117
122
  }
118
123
  if (event.type !== "tool_call") continue;
119
124
  const call = event.call;
120
125
  if (call.name !== askAgent.name) {
121
- phases.push({
122
- phase: "tool",
123
- toolName: call.name,
124
- toolCallId: call.id,
125
- args: call.arguments
126
- });
127
- void direct(call, AbortSignal.timeout(options.directTimeoutMs)).then((result) => {
128
- send(() => session.respond(call.id, capResult(result, options.directMaxChars)));
129
- phases.push({ phase: "idle" });
130
- }).catch((error) => {
131
- send(() => session.respond(call.id, errorResult(call, error)));
132
- phases.push({ phase: "idle" });
133
- });
126
+ track(pending, runDirect(session, direct, options, phases, running, call));
134
127
  continue;
135
128
  }
136
- const job = { abort: new AbortController(), answered: event.blocking, call };
129
+ const job = {
130
+ abort: new AbortController(),
131
+ answered: false,
132
+ dropped: false,
133
+ call,
134
+ kind: "ask"
135
+ };
137
136
  running.set(call.id, job);
138
137
  phases.push({ phase: "thinking" });
139
138
  if (event.blocking) send(() => session.respond(call.id, acknowledged(call, options.ack)));
140
- void followAsk(
139
+ const ask = followAsk(
141
140
  session,
142
141
  thinker,
143
142
  options,
@@ -149,14 +148,52 @@ async function run(session, thinker, direct, options, captions, phases) {
149
148
  event.blocking,
150
149
  job
151
150
  );
151
+ track(pending, ask);
152
152
  }
153
+ for (const job of running.values()) {
154
+ job.dropped = true;
155
+ job.abort.abort();
156
+ }
157
+ await Promise.allSettled([...pending]);
158
+ }
159
+ function cancelJob(session, running, callId) {
160
+ const job = running.get(callId);
161
+ if (!job) return;
162
+ job.abort.abort();
163
+ job.dropped = true;
164
+ if (job.kind !== "ask" || job.answered) return;
165
+ job.answered = true;
166
+ send(() => session.respond(callId, errorResult(job.call, new Error("cancelled"))));
167
+ }
168
+ function runDirect(session, direct, options, phases, running, call) {
169
+ const abort = new AbortController();
170
+ const signal = AbortSignal.any([abort.signal, AbortSignal.timeout(options.directTimeoutMs)]);
171
+ const job = { abort, answered: false, dropped: false, call, kind: "direct" };
172
+ running.set(call.id, job);
173
+ phases.push({
174
+ phase: "tool",
175
+ toolName: call.name,
176
+ toolCallId: call.id,
177
+ args: call.arguments
178
+ });
179
+ return direct(call, signal).then((result) => {
180
+ if (job.dropped) return;
181
+ send(() => session.respond(call.id, capResult(result, options.directMaxChars)));
182
+ }).catch((error) => {
183
+ if (job.dropped) return;
184
+ send(() => session.respond(call.id, errorResult(call, error)));
185
+ }).finally(() => {
186
+ running.delete(call.id);
187
+ phases.push({ phase: "idle" });
188
+ });
153
189
  }
154
190
  async function followAsk(session, thinker, options, captions, phases, running, heard, call, blocking, job) {
155
191
  try {
156
192
  for await (const update of thinker.ask(
157
193
  String(call.arguments.request),
158
194
  heard.slice(-heardLimit),
159
- job.abort.signal
195
+ job.abort.signal,
196
+ requestContext(call)
160
197
  )) {
161
198
  if (job.abort.signal.aborted) break;
162
199
  if (update.type === "progress") {
@@ -175,10 +212,17 @@ async function followAsk(session, thinker, options, captions, phases, running, h
175
212
  else send(() => session.respond(call.id, update.message, options.result));
176
213
  }
177
214
  } catch (error) {
178
- if (!job.abort.signal.aborted && !job.answered) {
179
- job.answered = true;
180
- send(() => session.respond(call.id, errorResult(call, error)));
181
- }
215
+ if (job.abort.signal.aborted || job.answered) return;
216
+ job.answered = true;
217
+ const message = errorResult(call, error);
218
+ captions.push({
219
+ type: "thinker_result",
220
+ callId: call.id,
221
+ summary: textOf(message),
222
+ isError: true
223
+ });
224
+ if (blocking) send(() => session.inject(textOf(message), options.result));
225
+ else send(() => session.respond(call.id, message));
182
226
  } finally {
183
227
  running.delete(call.id);
184
228
  phases.push({ phase: "idle" });
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/talker.ts"],
4
- "sourcesContent": ["import {\n type JsonObject,\n type Tool,\n type ToolCall,\n type ToolResultMessage,\n Type,\n} from '@earendil-works/pi-ai';\nimport { VoiceRequestError } from './errors.ts';\nimport type { Delivery } from './models.ts';\nimport type { RealtimeSession } from './realtime.ts';\n\nconst heardLimit = 20;\n\nexport const talkerTools = ['ask_agent', 'read'] as const;\n\nexport const askAgent: Tool = {\n name: 'ask_agent',\n description:\n 'Hand a request to the coding agent. Use for anything that needs files, the browser, tools or more than a few seconds of thought.',\n parameters: Type.Object({\n request: Type.String(),\n context: Type.Optional(Type.String()),\n }),\n};\n\nexport type ThinkerUpdate =\n | { type: 'progress'; text: string }\n | { type: 'result'; message: ToolResultMessage };\n\nexport interface Thinker {\n ask(request: string, heard: string[], signal: AbortSignal): AsyncIterable<ThinkerUpdate>;\n}\n\nexport interface BindThinkerOptions {\n progress: Delivery;\n result: Delivery;\n ack: string;\n directTimeoutMs: number;\n directMaxChars: number;\n}\n\nexport type ThinkerCaption =\n | { type: 'thinker_progress'; callId: string; text: string }\n | { type: 'thinker_result'; callId: string; summary: string; isError: boolean };\n\nexport type ThinkerPhase =\n | { phase: 'thinking' }\n | { phase: 'tool'; toolName: string; toolCallId: string; args: JsonObject }\n | { phase: 'idle' };\n\nexport interface BoundThinker {\n captions: AsyncIterable<ThinkerCaption>;\n phases: AsyncIterable<ThinkerPhase>;\n done: Promise<void>;\n}\n\nclass PushStream<T> implements AsyncIterable<T> {\n readonly #queue: T[] = [];\n readonly #waiters: Array<(result: { value?: T; done: boolean }) => void> = [];\n #closed = false;\n\n push(value: T): void {\n if (this.#closed) return;\n const waiter = this.#waiters.shift();\n if (waiter) waiter({ value, done: false });\n else this.#queue.push(value);\n }\n\n close(): void {\n if (this.#closed) return;\n this.#closed = true;\n while (this.#waiters.length > 0) this.#waiters.shift()?.({ done: true });\n }\n\n async *[Symbol.asyncIterator](): AsyncIterator<T> {\n while (true) {\n const queued = this.#queue.shift();\n if (queued !== undefined) {\n yield queued;\n continue;\n }\n if (this.#closed) return;\n const next = await new Promise<{ value?: T; done: boolean }>((resolve) =>\n this.#waiters.push(resolve)\n );\n if (next.done || next.value === undefined) return;\n yield next.value;\n }\n }\n}\n\nfunction send(action: () => void): void {\n try {\n action();\n } catch (error) {\n if (!(error instanceof VoiceRequestError)) throw error;\n }\n}\n\nfunction textOf(message: ToolResultMessage): string {\n return message.content\n .filter((part) => part.type === 'text')\n .map((part) => part.text)\n .join('\\n');\n}\n\nfunction acknowledged(call: ToolCall, ack: string): ToolResultMessage {\n return {\n role: 'toolResult',\n toolCallId: call.id,\n toolName: call.name,\n content: [{ type: 'text', text: ack }],\n isError: false,\n timestamp: Date.now(),\n };\n}\n\nfunction errorResult(call: ToolCall, error: unknown): ToolResultMessage {\n return {\n role: 'toolResult',\n toolCallId: call.id,\n toolName: call.name,\n content: [{ type: 'text', text: error instanceof Error ? error.message : String(error) }],\n isError: true,\n timestamp: Date.now(),\n };\n}\n\nfunction capResult(result: ToolResultMessage, maxChars: number): ToolResultMessage {\n let left = maxChars;\n const content = result.content.map((part) => {\n if (part.type !== 'text') return part;\n if (left <= 0) return { ...part, text: '' };\n if (part.text.length <= left) {\n left -= part.text.length;\n return part;\n }\n const text = part.text.slice(0, left);\n left = 0;\n return { ...part, text };\n });\n return { ...result, content };\n}\n\ninterface AskJob {\n abort: AbortController;\n answered: boolean;\n call: ToolCall;\n}\n\nexport function bindThinker(\n session: RealtimeSession,\n thinker: Thinker,\n direct: (call: ToolCall, signal: AbortSignal) => Promise<ToolResultMessage>,\n options: BindThinkerOptions\n): BoundThinker {\n const captions = new PushStream<ThinkerCaption>();\n const phases = new PushStream<ThinkerPhase>();\n const done = run(session, thinker, direct, options, captions, phases).finally(() => {\n captions.close();\n phases.close();\n captions.close();\n phases.close();\n });\n return { captions, phases, done };\n}\n\nasync function run(\n session: RealtimeSession,\n thinker: Thinker,\n direct: (call: ToolCall, signal: AbortSignal) => Promise<ToolResultMessage>,\n options: BindThinkerOptions,\n captions: PushStream<ThinkerCaption>,\n phases: PushStream<ThinkerPhase>\n): Promise<void> {\n const heard: string[] = [];\n const running = new Map<string, AskJob>();\n for await (const event of session) {\n if (event.type === 'input_transcript' && event.final) heard.push(`user: ${event.text}`);\n if (event.type === 'output_transcript' && event.final) heard.push(`talker: ${event.text}`);\n if (event.type === 'tool_call_cancelled') {\n const job = running.get(event.callId);\n if (!job) continue;\n job.abort.abort();\n if (!job.answered) {\n job.answered = true;\n send(() => session.respond(event.callId, errorResult(job.call, new Error('cancelled'))));\n }\n continue;\n }\n if (event.type !== 'tool_call') continue;\n const call = event.call;\n if (call.name !== askAgent.name) {\n phases.push({\n phase: 'tool',\n toolName: call.name,\n toolCallId: call.id,\n args: call.arguments,\n });\n void direct(call, AbortSignal.timeout(options.directTimeoutMs))\n .then((result) => {\n send(() => session.respond(call.id, capResult(result, options.directMaxChars)));\n phases.push({ phase: 'idle' });\n })\n .catch((error: unknown) => {\n send(() => session.respond(call.id, errorResult(call, error)));\n phases.push({ phase: 'idle' });\n });\n continue;\n }\n const job: AskJob = { abort: new AbortController(), answered: event.blocking, call };\n running.set(call.id, job);\n phases.push({ phase: 'thinking' });\n if (event.blocking) send(() => session.respond(call.id, acknowledged(call, options.ack)));\n void followAsk(\n session,\n thinker,\n options,\n captions,\n phases,\n running,\n heard,\n call,\n event.blocking,\n job\n );\n }\n}\n\nasync function followAsk(\n session: RealtimeSession,\n thinker: Thinker,\n options: BindThinkerOptions,\n captions: PushStream<ThinkerCaption>,\n phases: PushStream<ThinkerPhase>,\n running: Map<string, AskJob>,\n heard: string[],\n call: ToolCall,\n blocking: boolean,\n job: AskJob\n): Promise<void> {\n try {\n for await (const update of thinker.ask(\n String(call.arguments.request),\n heard.slice(-heardLimit),\n job.abort.signal\n )) {\n if (job.abort.signal.aborted) break;\n if (update.type === 'progress') {\n captions.push({ type: 'thinker_progress', callId: call.id, text: update.text });\n send(() => session.inject(update.text, options.progress));\n continue;\n }\n job.answered = true;\n captions.push({\n type: 'thinker_result',\n callId: call.id,\n summary: textOf(update.message),\n isError: update.message.isError,\n });\n if (blocking) send(() => session.inject(textOf(update.message), options.result));\n else send(() => session.respond(call.id, update.message, options.result));\n }\n } catch (error) {\n if (!job.abort.signal.aborted && !job.answered) {\n job.answered = true;\n send(() => session.respond(call.id, errorResult(call, error)));\n }\n } finally {\n running.delete(call.id);\n phases.push({ phase: 'idle' });\n }\n}\n"],
5
- "mappings": "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",
6
- "names": ["job"]
4
+ "sourcesContent": ["import {\n type JsonObject,\n type Tool,\n type ToolCall,\n type ToolResultMessage,\n Type,\n} from '@earendil-works/pi-ai';\nimport { VoiceRequestError } from './errors.ts';\nimport type { Delivery } from './models.ts';\nimport type { RealtimeSession } from './realtime.ts';\n\nconst heardLimit = 20;\n\nexport const talkerTools = ['ask_agent', 'read'] as const;\n\nexport const askAgent: Tool = {\n name: 'ask_agent',\n description:\n 'Hand a request to the coding agent. Use for anything that needs files, the browser, tools or more than a few seconds of thought.',\n parameters: Type.Object({\n request: Type.String(),\n context: Type.Optional(Type.String()),\n }),\n};\n\nexport type ThinkerUpdate =\n | { type: 'progress'; text: string }\n | { type: 'result'; message: ToolResultMessage };\n\nexport interface Thinker {\n ask(\n request: string,\n heard: string[],\n signal: AbortSignal,\n context?: string\n ): AsyncIterable<ThinkerUpdate>;\n}\n\nexport interface BindThinkerOptions {\n progress: Delivery;\n result: Delivery;\n ack: string;\n directTimeoutMs: number;\n directMaxChars: number;\n}\n\nexport type ThinkerCaption =\n | { type: 'thinker_progress'; callId: string; text: string }\n | { type: 'thinker_result'; callId: string; summary: string; isError: boolean };\n\nexport type ThinkerPhase =\n | { phase: 'thinking' }\n | { phase: 'tool'; toolName: string; toolCallId: string; args: JsonObject }\n | { phase: 'idle' };\n\nexport interface BoundThinker {\n captions: AsyncIterable<ThinkerCaption>;\n phases: AsyncIterable<ThinkerPhase>;\n done: Promise<void>;\n}\n\nclass PushStream<T> implements AsyncIterable<T> {\n readonly #queue: T[] = [];\n readonly #waiters: Array<(result: { value?: T; done: boolean }) => void> = [];\n #closed = false;\n\n push(value: T): void {\n if (this.#closed) return;\n const waiter = this.#waiters.shift();\n if (waiter) waiter({ value, done: false });\n else this.#queue.push(value);\n }\n\n close(): void {\n if (this.#closed) return;\n this.#closed = true;\n while (this.#waiters.length > 0) this.#waiters.shift()?.({ done: true });\n }\n\n async *[Symbol.asyncIterator](): AsyncIterator<T> {\n while (true) {\n const queued = this.#queue.shift();\n if (queued !== undefined) {\n yield queued;\n continue;\n }\n if (this.#closed) return;\n const next = await new Promise<{ value?: T; done: boolean }>((resolve) =>\n this.#waiters.push(resolve)\n );\n if (next.done || next.value === undefined) return;\n yield next.value;\n }\n }\n}\n\nfunction send(action: () => void): void {\n try {\n action();\n } catch (error) {\n if (!(error instanceof VoiceRequestError)) throw error;\n }\n}\n\nfunction textOf(message: ToolResultMessage): string {\n return message.content\n .filter((part) => part.type === 'text')\n .map((part) => part.text)\n .join('\\n');\n}\n\nfunction acknowledged(call: ToolCall, ack: string): ToolResultMessage {\n return {\n role: 'toolResult',\n toolCallId: call.id,\n toolName: call.name,\n content: [{ type: 'text', text: ack }],\n isError: false,\n timestamp: Date.now(),\n };\n}\n\nfunction errorResult(call: ToolCall, error: unknown): ToolResultMessage {\n return {\n role: 'toolResult',\n toolCallId: call.id,\n toolName: call.name,\n content: [{ type: 'text', text: error instanceof Error ? error.message : String(error) }],\n isError: true,\n timestamp: Date.now(),\n };\n}\n\nfunction capResult(result: ToolResultMessage, maxChars: number): ToolResultMessage {\n let left = maxChars;\n const content = result.content.map((part) => {\n if (part.type !== 'text') return part;\n if (left <= 0) return { ...part, text: '' };\n if (part.text.length <= left) {\n left -= part.text.length;\n return part;\n }\n const text = part.text.slice(0, left);\n left = 0;\n return { ...part, text };\n });\n return { ...result, content };\n}\n\ninterface AskJob {\n abort: AbortController;\n answered: boolean;\n dropped: boolean;\n call: ToolCall;\n kind: 'ask' | 'direct';\n}\n\nfunction requestContext(call: ToolCall): string | undefined {\n const value = call.arguments.context;\n return typeof value === 'string' ? value : undefined;\n}\n\nfunction track(pending: Set<Promise<void>>, promise: Promise<void>): void {\n pending.add(promise);\n void promise.finally(() => {\n pending.delete(promise);\n });\n}\n\nexport function bindThinker(\n session: RealtimeSession,\n thinker: Thinker,\n direct: (call: ToolCall, signal: AbortSignal) => Promise<ToolResultMessage>,\n options: BindThinkerOptions\n): BoundThinker {\n const captions = new PushStream<ThinkerCaption>();\n const phases = new PushStream<ThinkerPhase>();\n const done = run(session, thinker, direct, options, captions, phases).finally(() => {\n captions.close();\n phases.close();\n captions.close();\n phases.close();\n });\n return { captions, phases, done };\n}\n\nasync function run(\n session: RealtimeSession,\n thinker: Thinker,\n direct: (call: ToolCall, signal: AbortSignal) => Promise<ToolResultMessage>,\n options: BindThinkerOptions,\n captions: PushStream<ThinkerCaption>,\n phases: PushStream<ThinkerPhase>\n): Promise<void> {\n const heard: string[] = [];\n const running = new Map<string, AskJob>();\n const pending = new Set<Promise<void>>();\n for await (const event of session) {\n if (event.type === 'input_transcript' && event.final) heard.push(`user: ${event.text}`);\n if (event.type === 'output_transcript' && event.final) heard.push(`talker: ${event.text}`);\n if (event.type === 'tool_call_cancelled') {\n cancelJob(session, running, event.callId);\n continue;\n }\n if (event.type !== 'tool_call') continue;\n const call = event.call;\n if (call.name !== askAgent.name) {\n track(pending, runDirect(session, direct, options, phases, running, call));\n continue;\n }\n const job: AskJob = {\n abort: new AbortController(),\n answered: false,\n dropped: false,\n call,\n kind: 'ask',\n };\n running.set(call.id, job);\n phases.push({ phase: 'thinking' });\n if (event.blocking) send(() => session.respond(call.id, acknowledged(call, options.ack)));\n const ask = followAsk(\n session,\n thinker,\n options,\n captions,\n phases,\n running,\n heard,\n call,\n event.blocking,\n job\n );\n track(pending, ask);\n }\n for (const job of running.values()) {\n job.dropped = true;\n job.abort.abort();\n }\n await Promise.allSettled([...pending]);\n}\n\nfunction cancelJob(session: RealtimeSession, running: Map<string, AskJob>, callId: string): void {\n const job = running.get(callId);\n if (!job) return;\n job.abort.abort();\n job.dropped = true;\n if (job.kind !== 'ask' || job.answered) return;\n job.answered = true;\n send(() => session.respond(callId, errorResult(job.call, new Error('cancelled'))));\n}\n\nfunction runDirect(\n session: RealtimeSession,\n direct: (call: ToolCall, signal: AbortSignal) => Promise<ToolResultMessage>,\n options: BindThinkerOptions,\n phases: PushStream<ThinkerPhase>,\n running: Map<string, AskJob>,\n call: ToolCall\n): Promise<void> {\n const abort = new AbortController();\n const signal = AbortSignal.any([abort.signal, AbortSignal.timeout(options.directTimeoutMs)]);\n const job: AskJob = { abort, answered: false, dropped: false, call, kind: 'direct' };\n running.set(call.id, job);\n phases.push({\n phase: 'tool',\n toolName: call.name,\n toolCallId: call.id,\n args: call.arguments,\n });\n return direct(call, signal)\n .then((result) => {\n if (job.dropped) return;\n send(() => session.respond(call.id, capResult(result, options.directMaxChars)));\n })\n .catch((error: unknown) => {\n if (job.dropped) return;\n send(() => session.respond(call.id, errorResult(call, error)));\n })\n .finally(() => {\n running.delete(call.id);\n phases.push({ phase: 'idle' });\n });\n}\n\nasync function followAsk(\n session: RealtimeSession,\n thinker: Thinker,\n options: BindThinkerOptions,\n captions: PushStream<ThinkerCaption>,\n phases: PushStream<ThinkerPhase>,\n running: Map<string, AskJob>,\n heard: string[],\n call: ToolCall,\n blocking: boolean,\n job: AskJob\n): Promise<void> {\n try {\n for await (const update of thinker.ask(\n String(call.arguments.request),\n heard.slice(-heardLimit),\n job.abort.signal,\n requestContext(call)\n )) {\n if (job.abort.signal.aborted) break;\n if (update.type === 'progress') {\n captions.push({ type: 'thinker_progress', callId: call.id, text: update.text });\n send(() => session.inject(update.text, options.progress));\n continue;\n }\n job.answered = true;\n captions.push({\n type: 'thinker_result',\n callId: call.id,\n summary: textOf(update.message),\n isError: update.message.isError,\n });\n if (blocking) send(() => session.inject(textOf(update.message), options.result));\n else send(() => session.respond(call.id, update.message, options.result));\n }\n } catch (error) {\n if (job.abort.signal.aborted || job.answered) return;\n job.answered = true;\n const message = errorResult(call, error);\n captions.push({\n type: 'thinker_result',\n callId: call.id,\n summary: textOf(message),\n isError: true,\n });\n if (blocking) send(() => session.inject(textOf(message), options.result));\n else send(() => session.respond(call.id, message));\n } finally {\n running.delete(call.id);\n phases.push({ phase: 'idle' });\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
7
  }
@@ -0,0 +1 @@
1
+ export declare function registerFish(): () => void;
@@ -0,0 +1 @@
1
+ export { registerFish } from './catalog.ts';
@@ -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 speakFish(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 transcribeFish(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
+ export type PackValue = null | boolean | number | string | Uint8Array | PackValue[] | {
4
+ [key: string]: PackValue;
5
+ };
6
+ export interface FishFile {
7
+ data: Uint8Array;
8
+ mimeType: string;
9
+ filename: string;
10
+ }
11
+ export declare function pack(value: PackValue): Uint8Array;
12
+ export declare function unpack(data: Uint8Array): PackValue;
13
+ export declare function mediaType(mimeType: string): string;
14
+ export declare function audioFilename(mimeType: string): string;
15
+ export declare function encodeForm(file: FishFile, fields: Record<string, string>): ArrayBuffer;
16
+ export declare function formType(): string;
17
+ export declare function fishHeaders(model: {
18
+ id: string;
19
+ provider: string;
20
+ headers?: Record<string, string>;
21
+ }, options: ProviderRequestOptions | undefined): Record<string, string>;
22
+ export declare function socketUrl(baseUrl: string, path: string): URL;
23
+ export declare function speechFormatName(format: AudioFormat): string;
24
+ export declare function connectSocket(url: string, headers: Record<string, string>): WebSocket;
25
+ export declare function awaitOpen(socket: WebSocket, label: string, fail: (message: string) => void): Promise<void>;
26
+ export declare function messageBytes(data: unknown): Promise<Uint8Array>;
27
+ export declare function sameFormat(left: AudioFormat, right: AudioFormat): boolean;
@@ -11,6 +11,7 @@ export interface VoiceWire {
11
11
  export interface VoiceSocket {
12
12
  send(data: string): void;
13
13
  close(): void;
14
+ addEventListener(type: 'open', listener: () => void): void;
14
15
  addEventListener(type: 'message', listener: (event: {
15
16
  data: unknown;
16
17
  }) => void): void;
@@ -11,7 +11,7 @@ export type ThinkerUpdate = {
11
11
  message: ToolResultMessage;
12
12
  };
13
13
  export interface Thinker {
14
- ask(request: string, heard: string[], signal: AbortSignal): AsyncIterable<ThinkerUpdate>;
14
+ ask(request: string, heard: string[], signal: AbortSignal, context?: string): AsyncIterable<ThinkerUpdate>;
15
15
  }
16
16
  export interface BindThinkerOptions {
17
17
  progress: Delivery;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-ecoverse/pi-audio",
3
- "version": "1.18.0",
3
+ "version": "1.19.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",
@@ -54,6 +54,10 @@
54
54
  "types": "./dist/types/mistral/index.d.ts",
55
55
  "default": "./dist/mistral/index.js"
56
56
  },
57
+ "./fish": {
58
+ "types": "./dist/types/fish/index.d.ts",
59
+ "default": "./dist/fish/index.js"
60
+ },
57
61
  "./durable": {
58
62
  "types": "./dist/types/durable/index.d.ts",
59
63
  "default": "./dist/durable/index.js"