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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions samples/js/native-chat-completions/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,22 @@ const completion = await chatClient.completeChat([
console.log('\nChat completion result:');
console.log(completion.choices[0]?.message?.content);

// Example streaming completion
console.log('\nTesting streaming completion...');
// Example streaming completion (async iterable pattern - recommended)
console.log('\nTesting streaming completion (async iterable)...');
for await (const chunk of chatClient.completeStreamingChat(
[{ role: 'user', content: 'Write a short poem about programming.' }]
)) {
const content = chunk.choices?.[0]?.delta?.content;
if (content) {
process.stdout.write(content);
}
}
console.log('\n');

// Example streaming completion (callback pattern - backward-compatible)
console.log('\nTesting streaming completion (callback)...');
await chatClient.completeStreamingChat(
[{ role: 'user', content: 'Write a short poem about programming.' }],
[{ role: 'user', content: 'Write a short poem about nature.' }],
(chunk) => {
const content = chunk.choices?.[0]?.message?.content;
if (content) {
Expand Down
42 changes: 41 additions & 1 deletion sdk_v2/js/docs/classes/AudioClient.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,51 @@ Error - If audioFilePath is invalid or transcription fails.

### transcribeStreaming()

#### Overload 1: Async iterable pattern (recommended)

```ts
transcribeStreaming(audioFilePath): AsyncIterable<any>;
```

Transcribes audio into the input language using streaming, returning an async iterable:

```ts
for await (const chunk of audioClient.transcribeStreaming(audioFilePath)) {
process.stdout.write(chunk.text ?? '');
}
```

#### Parameters

| Parameter | Type | Description |
| ------ | ------ | ------ |
| `audioFilePath` | `string` | Path to the audio file to transcribe. |

#### Returns

`AsyncIterable`\<`any`\>

An async iterable that yields each chunk of the streaming response.

#### Throws

Error - If audioFilePath is invalid, or streaming fails.

***

#### Overload 2: Callback pattern

```ts
transcribeStreaming(audioFilePath, callback): Promise<void>;
```

Transcribes audio into the input language using streaming.
Transcribes audio into the input language using streaming with a callback:

```ts
await audioClient.transcribeStreaming(audioFilePath, (chunk) => {
process.stdout.write(chunk.text ?? '');
});
```

#### Parameters

Expand Down
93 changes: 92 additions & 1 deletion sdk_v2/js/docs/classes/ChatClient.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,76 @@ Error - If messages are invalid or completion fails.

### completeStreamingChat()

#### Overload 1: Async iterable pattern (recommended)

```ts
completeStreamingChat(messages): AsyncIterable<any>;
```

Performs a streaming chat completion, returning an async iterable. This follows the same pattern as the OpenAI SDK:

```ts
for await (const chunk of chatClient.completeStreamingChat(messages)) {
process.stdout.write(chunk.choices?.[0]?.delta?.content ?? '');
}
```

#### Parameters

| Parameter | Type | Description |
| ------ | ------ | ------ |
| `messages` | `any`[] | An array of message objects. |

#### Returns

`AsyncIterable`\<`any`\>

An async iterable that yields each chunk of the streaming response.

#### Throws

Error - If messages are invalid, or streaming fails.

***

#### Overload 2: Async iterable pattern with tools

```ts
completeStreamingChat(messages, tools): AsyncIterable<any>;
```

#### Parameters

| Parameter | Type | Description |
| ------ | ------ | ------ |
| `messages` | `any`[] | An array of message objects. |
| `tools` | `any`[] | An array of tool objects. |

#### Returns

`AsyncIterable`\<`any`\>

An async iterable that yields each chunk of the streaming response.

#### Throws

Error - If messages or tools are invalid, or streaming fails.

***

#### Overload 3: Callback pattern

```ts
completeStreamingChat(messages, callback): Promise<void>;
```

Performs a streaming chat completion.
Performs a streaming chat completion using a callback for each chunk.

```ts
await chatClient.completeStreamingChat(messages, (chunk) => {
process.stdout.write(chunk.choices?.[0]?.delta?.content ?? '');
});
```

#### Parameters

Expand All @@ -67,3 +132,29 @@ A promise that resolves when the stream is complete.
#### Throws

Error - If messages or callback are invalid, or streaming fails.

***

#### Overload 4: Callback pattern with tools

```ts
completeStreamingChat(messages, tools, callback): Promise<void>;
```

#### Parameters

| Parameter | Type | Description |
| ------ | ------ | ------ |
| `messages` | `any`[] | An array of message objects. |
| `tools` | `any`[] | An array of tool objects. |
| `callback` | (`chunk`) => `void` | A callback function that receives each chunk of the streaming response. |

#### Returns

`Promise`\<`void`\>

A promise that resolves when the stream is complete.

#### Throws

Error - If messages, tools, or callback are invalid, or streaming fails.
124 changes: 121 additions & 3 deletions sdk_v2/js/src/openai/audioClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,16 +90,134 @@ export class AudioClient {

/**
* Transcribes audio into the input language using streaming.
*
* Can be used with the async iterable pattern (no callback):
* ```ts
* for await (const chunk of audioClient.transcribeStreaming(audioFilePath)) {
* process.stdout.write(chunk.text ?? '');
* }
* ```
*
* Or with the callback pattern:
* ```ts
* await audioClient.transcribeStreaming(audioFilePath, (chunk) => {
* process.stdout.write(chunk.text ?? '');
* });
* ```
*
* @param audioFilePath - Path to the audio file to transcribe.
* @returns An async iterable that yields each chunk of the streaming response.
* @throws Error - If audioFilePath is invalid, or streaming fails.
*/
public transcribeStreaming(audioFilePath: string): AsyncIterable<any>;
/**
* Transcribes audio into the input language using streaming with a callback.
* @param audioFilePath - Path to the audio file to transcribe.
* @param callback - A callback function that receives each chunk of the streaming response.
* @returns A promise that resolves when the stream is complete.
* @throws Error - If audioFilePath or callback are invalid, or streaming fails.
*/
public async transcribeStreaming(audioFilePath: string, callback: (chunk: any) => void): Promise<void> {
public transcribeStreaming(audioFilePath: string, callback: (chunk: any) => void): Promise<void>;
public transcribeStreaming(audioFilePath: string, callback?: (chunk: any) => void): AsyncIterable<any> | Promise<void> {
this.validateAudioFilePath(audioFilePath);
if (!callback || typeof callback !== 'function') {
throw new Error('Callback must be a valid function.');

if (callback !== undefined) {
if (typeof callback !== 'function') {
throw new Error('Callback must be a valid function.');
}
return this._transcribeStreamingWithCallback(audioFilePath, callback);
}

return this._transcribeStream(audioFilePath);
}

/**
* Internal async generator that bridges the native callback-based streaming API
* to an async iterable interface.
* @internal
*/
private async *_transcribeStream(audioFilePath: string): AsyncIterableIterator<any> {
const request = {
Model: this.modelId,
FileName: audioFilePath,
...this.settings._serialize()
};

const chunks: any[] = [];
let streamDone = false;
let streamError: Error | null = null;
let notify: (() => void) | null = null;

const wakeConsumer = () => {
if (notify) { const n = notify; notify = null; n(); }
};

const streamPromise = this.coreInterop.executeCommandStreaming(
"audio_transcribe",
{ Params: { OpenAICreateRequest: JSON.stringify(request) } },
(chunkStr: string) => {
// Skip processing if we already encountered an error
if (streamError) return;

if (chunkStr) {
let chunk: any;
try {
chunk = JSON.parse(chunkStr);
} catch (e) {
// Don't throw from callback - store first error and stop processing
streamError = new Error(
`Failed to parse streaming chunk: ${e instanceof Error ? e.message : String(e)}`,
{ cause: e }
);
wakeConsumer();
return;
}
chunks.push(chunk);
wakeConsumer();
}
}
).then(() => {
streamDone = true;
wakeConsumer();
}).catch((err: unknown) => {
streamError = err instanceof Error ? err : new Error(String(err));
streamDone = true;
wakeConsumer();
});

try {
while (!streamDone && !streamError) {
while (chunks.length > 0) {
yield chunks.shift()!;
}
if (!streamDone && !streamError) {
await new Promise<void>(resolve => { notify = resolve; });
}
}
// Drain any remaining chunks that arrived before streamDone was observed
while (chunks.length > 0) {
yield chunks.shift()!;
}
Comment on lines +146 to +200
} finally {
await streamPromise;
}

// TypeScript's control-flow analysis doesn't track mutations through closures,
// so cast through unknown to widen the narrowed type before checking.
const maybeError = streamError as unknown;
if (maybeError instanceof Error) {
throw new Error(
`Streaming audio transcription failed for model '${this.modelId}': ${maybeError.message}`,
{ cause: maybeError }
);
}
}

/**
* Internal callback-based streaming implementation.
* @internal
*/
private async _transcribeStreamingWithCallback(audioFilePath: string, callback: (chunk: any) => void): Promise<void> {
const request = {
Model: this.modelId,
FileName: audioFilePath,
Expand Down
Loading
Loading