Skip to content
Merged
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
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -594,7 +594,18 @@ choco install ffmpeg # Windows

`timeoutMs` raises the SDK's timeout, but with `openai` 5 or newer a request still fails after about 5 minutes
without response headers: those versions use Node's built-in `fetch`, whose own 300-second limit applies first.
`openai` 4 honours `timeoutMs` beyond that.
`openai` 4 honours `timeoutMs` beyond that. To wait longer on `openai` 5+, pass an undici dispatcher through
`fetchOptions` (`npm i undici`):

```js
import { Agent } from 'undici';

engines.openai({
baseUrl: 'http://localhost:8881/v1',
timeoutMs: 1_800_000,
fetchOptions: { dispatcher: new Agent({ headersTimeout: 0, bodyTimeout: 0 }) },
})
```

2. **Record** — Playwright runs the demo script in a real browser. The `narration` fixture records timestamps for each `mark()` call. Video is captured at native resolution.

Expand Down
2 changes: 1 addition & 1 deletion skills/argo-guide/references/tts-engines.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ engines.openai({
```

- With `baseUrl` set, `OPENAI_API_KEY` is never read — an OpenAI key is not leaked to another server. Pass `apiKey` if the server needs one.
- With `openai` >= 5, requests still fail after ~300 s without response headers (Node `fetch`'s `headersTimeout`), regardless of `timeoutMs`. `openai` 4 honours longer timeouts.
- With `openai` >= 5, requests still fail after ~300 s without response headers (Node `fetch`'s `headersTimeout`), regardless of `timeoutMs`. `openai` 4 honours longer timeouts. On 5+, lift the limit with `fetchOptions: { dispatcher: new Agent({ headersTimeout: 0, bodyTimeout: 0 }) }` (`import { Agent } from 'undici'`).
- `baseUrl` is recorded in `<demo>.meta.json` but is not part of the clip cache key — clear `.argo/<demo>/clips` after switching servers.

---
Expand Down
12 changes: 11 additions & 1 deletion src/tts/engines/openai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,16 @@ export interface OpenAIEngineOptions {
baseUrl?: string;
/**
* Request timeout in milliseconds, for slow local servers. Default: the OpenAI SDK's (10 minutes).
* With `openai` >= 5, Node's built-in `fetch` still gives up after ~300 s without response headers.
* With `openai` >= 5, Node's built-in `fetch` still gives up after ~300 s without response headers;
* lift that with `fetchOptions`.
*/
timeoutMs?: number;
/**
* Extra options merged into every `fetch` call the SDK makes (`openai` >= 5). To wait longer than ~300 s,
* pass an undici dispatcher with a larger `headersTimeout`:
* `fetchOptions: { dispatcher: new Agent({ headersTimeout: 0, bodyTimeout: 0 }) }` (`import { Agent } from 'undici'`).
*/
fetchOptions?: Record<string, unknown>;
}

export class OpenAIEngine implements TTSEngine {
Expand All @@ -26,13 +33,15 @@ export class OpenAIEngine implements TTSEngine {
private instructions?: string;
private baseUrl?: string;
private timeoutMs?: number;
private fetchOptions?: Record<string, unknown>;

constructor(options?: OpenAIEngineOptions) {
this.apiKey = options?.apiKey ?? '';
this.model = options?.model ?? 'tts-1';
this.instructions = options?.instructions;
this.baseUrl = options?.baseUrl;
this.timeoutMs = options?.timeoutMs;
this.fetchOptions = options?.fetchOptions;
}

describe(): TTSEngineMetadata {
Expand Down Expand Up @@ -67,6 +76,7 @@ export class OpenAIEngine implements TTSEngine {
apiKey: this.resolveApiKey(),
...(this.baseUrl ? { baseURL: this.baseUrl } : {}),
...(this.timeoutMs != null ? { timeout: this.timeoutMs } : {}),
...(this.fetchOptions ? { fetchOptions: this.fetchOptions } : {}),
});

// Request raw PCM so we can build an exact WAV without ffmpeg pipe artifacts
Expand Down
6 changes: 6 additions & 0 deletions tests/tts/openai.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ describe('OpenAIEngine', () => {
});
});

it('passes fetchOptions through to the client', async () => {
const dispatcher = { tag: 'agent' };
await new OpenAIEngine({ baseUrl: 'http://localhost:8881/v1', fetchOptions: { dispatcher } }).generate('Hello', {});
expect(clientOptions.mock.calls[0][0].fetchOptions).toEqual({ dispatcher });
});

it('never sends OPENAI_API_KEY to another server', async () => {
process.env.OPENAI_API_KEY = 'sk-env';
await new OpenAIEngine({ baseUrl: 'http://localhost:8881/v1' }).generate('Hello', {});
Expand Down
Loading