@focus-reactive/payload-plugin-translator 0.10.4 → 0.11.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 (48) hide show
  1. package/README.md +152 -10
  2. package/dist/index.d.ts +8 -3
  3. package/dist/index.js +4 -3
  4. package/dist/translation-providers/index.d.ts +1 -0
  5. package/dist/translation-providers/index.js +1 -7
  6. package/dist/translation-providers/openai/OpenAI.shapes.d.ts +52 -0
  7. package/dist/translation-providers/openai/OpenAI.shapes.js +13 -0
  8. package/dist/translation-providers/openai/OpenAITranslation.provider.d.ts +51 -112
  9. package/dist/translation-providers/openai/OpenAITranslation.provider.js +49 -137
  10. package/dist/translation-providers/openai/OpenAITranslationLegacy.provider.d.ts +11 -0
  11. package/dist/translation-providers/openai/OpenAITranslationLegacy.provider.js +15 -0
  12. package/dist/translation-providers/openai/index.d.ts +6 -1
  13. package/dist/translation-providers/openai/index.js +3 -3
  14. package/dist/translation-providers/openai/loadOpenAIClient.d.ts +24 -0
  15. package/dist/translation-providers/openai/loadOpenAIClient.js +79 -0
  16. package/dist/translation-providers/openai/openAIComplete.d.ts +39 -0
  17. package/dist/translation-providers/openai/openAIComplete.js +84 -0
  18. package/dist/translation-providers/shared/CompletionProvider.provider.d.ts +65 -0
  19. package/dist/translation-providers/shared/CompletionProvider.provider.js +93 -0
  20. package/dist/translation-providers/shared/buildResponseSchema.d.ts +13 -0
  21. package/dist/translation-providers/shared/buildResponseSchema.js +22 -0
  22. package/dist/translation-providers/shared/buildSystemPrompt.d.ts +28 -0
  23. package/dist/translation-providers/shared/buildSystemPrompt.js +25 -0
  24. package/dist/translation-providers/shared/errors/KeySetMismatchError.d.ts +15 -0
  25. package/dist/translation-providers/shared/errors/KeySetMismatchError.js +26 -0
  26. package/dist/translation-providers/shared/errors/NoContentError.d.ts +9 -0
  27. package/dist/translation-providers/shared/errors/NoContentError.js +10 -0
  28. package/dist/translation-providers/shared/errors/ProviderConfigurationError.d.ts +10 -0
  29. package/dist/translation-providers/shared/errors/ProviderConfigurationError.js +11 -0
  30. package/dist/translation-providers/shared/errors/TranslationProviderError.d.ts +18 -0
  31. package/dist/translation-providers/shared/errors/TranslationProviderError.js +21 -0
  32. package/dist/translation-providers/shared/errors/TransportError.d.ts +10 -0
  33. package/dist/translation-providers/shared/errors/TransportError.js +11 -0
  34. package/dist/translation-providers/shared/errors/UnparseableReplyError.d.ts +9 -0
  35. package/dist/translation-providers/shared/errors/UnparseableReplyError.js +10 -0
  36. package/dist/translation-providers/shared/errors/errorMessageLower.d.ts +1 -0
  37. package/dist/translation-providers/shared/errors/errorMessageLower.js +8 -0
  38. package/dist/translation-providers/shared/errors/index.d.ts +9 -0
  39. package/dist/translation-providers/shared/errors/index.js +10 -0
  40. package/dist/translation-providers/shared/errors/wrapTransportError.d.ts +7 -0
  41. package/dist/translation-providers/shared/errors/wrapTransportError.js +14 -0
  42. package/dist/translation-providers/shared/index.d.ts +8 -0
  43. package/dist/translation-providers/shared/index.js +5 -0
  44. package/dist/translation-providers/shared/parseAndValidateReply.d.ts +13 -0
  45. package/dist/translation-providers/shared/parseAndValidateReply.js +46 -0
  46. package/dist/translation-providers/shared/runDryRun.d.ts +20 -0
  47. package/dist/translation-providers/shared/runDryRun.js +17 -0
  48. package/package.json +1 -1
package/README.md CHANGED
@@ -288,21 +288,86 @@ translatorPlugin({
288
288
 
289
289
  #### OpenAI (built in) — `createOpenAIProvider(config)`
290
290
 
291
- | Property | Type | Required | Default | Description |
292
- | -------------- | ------------------------- | -------- | -------------------- | --------------------------------------------------------------------------------------------------------------- |
293
- | `apiKey` | `string` | Yes | | OpenAI API key. |
294
- | `model` | `string \| ChatModel` | No | `'gpt-4o'` | Model used for translation. |
295
- | `systemPrompt` | `SystemPromptBuilder` | No | Built-in prompt | Custom system-prompt builder. |
296
- | `dryRun` | `boolean \| DryRunConfig` | No | `false` | Simulate translations without API calls. |
297
- | `timeout` | `number` | No | SDK default (10 min) | Per-request timeout (ms). A job blocks on this call, so the 10-min default is usually too long. _Since v0.6.0._ |
298
- | `maxRetries` | `number` | No | SDK default (2) | Max automatic retries on transient errors (429/5xx/network). `0` disables. _Since v0.6.0._ |
291
+ Pass **either** an `apiKey` **or** a ready-made `client` — never both; the types enforce it.
292
+
293
+ | Property | Type | Required | Default | Description |
294
+ | -------------- | ------------------------- | ------------------ | -------------------- | --------------------------------------------------------------------------------------------------------------- |
295
+ | `apiKey` | `string` | Unless `client` | | OpenAI API key. The `openai` package is loaded on first translation, not at config load. _Since v0.11.0: optional when `client` is given._ |
296
+ | `client` | `OpenAIClientShape` | Unless `apiKey` | | Your own client — Azure OpenAI, a corporate proxy, OpenRouter, anything with a matching `chat.completions.create`. On this path the `openai` package is never loaded. _Since v0.11.0._ |
297
+ | `model` | `string` | No | `'gpt-4o'` | Model used for translation. **The default may change in a minor release** pin it if you need reproducible output and cost. |
298
+ | `systemPrompt` | `SystemPromptBuilder` | No | Built-in prompt | Custom system-prompt builder. |
299
+ | `dryRun` | `boolean \| DryRunConfig` | No | `false` | **Deprecated** — see the note below. Simulates translations without API calls, but still writes, publishes and records provenance. |
300
+ | `sampling` | `OpenAISamplingParams` | No | not sent | `temperature`, `top_p`, `frequency_penalty`, `presence_penalty`. Omitted entirely unless set — several models reject them. **Before v0.11.0 this package always sent `temperature: 0, top_p: 1, frequency_penalty: 0, presence_penalty: 0`**, so translations were deterministic; they now follow the model's defaults. Set `{ temperature: 0 }` to restore that. _Since v0.11.0._ |
301
+ | `structuredOutput` | `"json_schema" \| "json_object"` | No | `"json_schema"` | Which structured-output envelope to send. See the note below — the two carry different risks. _Since v0.11.0._ |
302
+ | `timeout` | `number` | No | `60000` | Per-request timeout (ms) for the client this package builds. Ignored when you pass your own `client`. _Since v0.6.0; the default dropped from the SDK's 10 minutes to 60 s in v0.11.0._ |
303
+ | `maxRetries` | `number` | No | SDK default (2) | Max automatic retries on transient errors (429/5xx/network) for the client this package builds. `0` disables. Ignored when you pass your own `client`. _Since v0.6.0._ |
304
+
305
+ > **`createOpenAIProvider` is deprecated and goes away in the next major.** What it adds over
306
+ > `openAIComplete` is building the SDK client for you — and carrying `openai` as an optional
307
+ > dependency of ours to do it, which is where the cost is: a lazy import shaped around deployment
308
+ > file-tracers, and a classifier telling "not installed" from "installed but broken" across four
309
+ > runtimes. Construct the client yourself instead and keep everything else:
310
+ >
311
+ > ```typescript
312
+ > import OpenAI from "openai";
313
+ > import {
314
+ > createTranslationProvider,
315
+ > openAIComplete,
316
+ > } from "@focus-reactive/payload-plugin-translator";
317
+ >
318
+ > // The SDK's own default timeout is ten minutes — far too long for a live edit.
319
+ > const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY, timeout: 60_000 });
320
+ >
321
+ > const translationProvider = createTranslationProvider({
322
+ > complete: openAIComplete({ client, model: "gpt-4o" }),
323
+ > });
324
+ > ```
325
+ >
326
+ > `openAIComplete` stays: the request body, the `structuredOutput` choice below, and the message
327
+ > naming that option when a gateway rejects a strict schema are all still ours. What becomes yours
328
+ > is the SDK version and the client's own settings, the timeout most of all.
329
+ > See [docs/DEPRECATIONS.md](docs/DEPRECATIONS.md#openai-client-construction).
330
+
331
+ #### Choosing a structured-output envelope
332
+
333
+ `json_schema` (the default) sends the request with a schema the reply must satisfy, so a compliant
334
+ model **cannot** drop a requested field. That is what closed the silent half-translation defect. It
335
+ costs two things:
336
+
337
+ - Older models, and some gateways (OpenRouter with certain upstream models, older Azure
338
+ deployments, self-hosted proxies), reject it with a 400. You do not need to know which: the error
339
+ names this option as the fix.
340
+ - The schema has a size limit, so **one request carries a limited number of pieces of text**, and a
341
+ document past that ceiling fails as a whole. The schema names one property per translatable piece,
342
+ and rich text is split one piece per text node — a sentence with two emphasised spans is already
343
+ four pieces — so the count grows faster than "one per field" suggests, and a document is never
344
+ split across requests. The ceiling differs by model and moves over time; measure it against your
345
+ largest documents rather than assuming headroom.
346
+
347
+ `json_object` asks only for valid JSON. There is no schema, so no ceiling — but key preservation
348
+ falls back to this package's key-set check, which **detects** a dropped key instead of preventing
349
+ it. A reply missing one key out of two hundred still writes the other 199, the gap is reported to
350
+ the server log, and only a reply matching nothing at all fails. Your editors never see that log, so
351
+ a dropped field looks translated in the admin UI.
352
+
353
+ Pick by which risk you would rather carry: a hard failure on very large documents, or a quiet gap on
354
+ any document.
299
355
 
300
356
  ```typescript
357
+ // Quick start
301
358
  createOpenAIProvider({
302
359
  apiKey: process.env.OPENAI_API_KEY,
303
360
  model: "gpt-4o-mini",
304
361
  systemPrompt: ({ sourceLang, targetLang, defaultPrompt }) => `${defaultPrompt}\nUse formal language. Keep brand names unchanged.`,
305
362
  });
363
+
364
+ // Your own client — Azure, a proxy, OpenRouter. Since v0.11.0.
365
+ import OpenAI from "openai";
366
+
367
+ createOpenAIProvider({
368
+ client: new OpenAI({ apiKey: process.env.AZURE_KEY, baseURL: process.env.AZURE_ENDPOINT }),
369
+ model: "gpt-4o",
370
+ });
306
371
  ```
307
372
 
308
373
  `systemPrompt` receives `{ sourceLang, targetLang, defaultPrompt }` and returns the prompt string. When `dryRun` is an object it can transform text locally with an optional delay:
@@ -314,6 +379,81 @@ type DryRunConfig = {
314
379
  };
315
380
  ```
316
381
 
382
+ > **`dryRun` is deprecated and will be removed in the next major.** It skips the network call and
383
+ > nothing else: the transformed strings are still written to the target locale, still published when
384
+ > `publishOnTranslation` is set, and still recorded as provenance — after which the locale reads as
385
+ > up to date and no re-translation is prompted. Use your own fake instead, which is explicit about
386
+ > being one:
387
+ >
388
+ > ```typescript
389
+ > import { createTranslationProvider } from "@focus-reactive/payload-plugin-translator";
390
+ >
391
+ > const fakeProvider = createTranslationProvider({
392
+ > complete: async ({ userContent }) => {
393
+ > const input = JSON.parse(userContent) as Record<string, string>;
394
+ > const reversed: Record<string, string> = {};
395
+ > for (const [key, value] of Object.entries(input)) {
396
+ > reversed[key] = value.trim() ? [...value].reverse().join("") : value;
397
+ > }
398
+ > return JSON.stringify(reversed);
399
+ > },
400
+ > });
401
+ > ```
402
+ >
403
+ > `complete` returns the reply as raw text, exactly as a service would; parsing and validation stay
404
+ > on our side. See [docs/DEPRECATIONS.md](docs/DEPRECATIONS.md#provider-dry-run).
405
+
406
+ #### Another service — `createTranslationProvider(config)`
407
+
408
+ _Since v0.11.0._
409
+
410
+ Need a different model provider, or full control of the request body? Supply one function — "send this text, give me the reply" — and keep everything else. The prompt, the response schema, reply parsing, key-set validation, dry-run simulation and the failure taxonomy all stay on our side, so you cannot accidentally skip them.
411
+
412
+ For OpenAI specifically you do not have to write that function: `openAIComplete({ client, model })` is one, built from a client you constructed. _Since v0.11.0._
413
+
414
+ ```typescript
415
+ import { createTranslationProvider } from "@focus-reactive/payload-plugin-translator";
416
+
417
+ const provider = createTranslationProvider({
418
+ complete: async ({ systemPrompt, userContent, responseSchema }) => {
419
+ const reply = await myService.chat({
420
+ system: systemPrompt,
421
+ user: userContent,
422
+ schema: responseSchema, // hand this to whatever structured-output mechanism your service offers
423
+ });
424
+ return reply.text; // raw text, not a parsed object — we parse it
425
+ },
426
+ });
427
+ ```
428
+
429
+ > `signal` is reserved and currently always `undefined` — the port does not carry cancellation yet, so wiring it into your client is harmless but has no effect today. It is in the request shape so that adding cancellation later is a pure addition rather than a breaking change.
430
+
431
+ | Property | Type | Required | Default | Description |
432
+ | -------------- | ------------------------- | -------- | --------------- | -------------------------------------------------- |
433
+ | `complete` | `CompletionFn` | Yes | — | Sends one request, returns the reply as raw text. |
434
+ | `systemPrompt` | `SystemPromptBuilder` | No | Built-in prompt | Custom system-prompt builder. |
435
+ | `dryRun` | `boolean \| DryRunConfig` | No | `false` | **Deprecated** — supply your own fake `complete`. |
436
+
437
+ Your `complete` owns the timeout, the retry policy and the credentials — this package adds no retry of its own and imposes no timeout on your call.
438
+
439
+ #### Failure causes
440
+
441
+ _Since v0.11.0._
442
+
443
+ Every built-in provider throws a typed error naming what went wrong, instead of returning `null`:
444
+
445
+ | Error | `code` | Means |
446
+ | --------------------------- | -------------------- | ---------------------------------------------------------------------- |
447
+ | `NoContentError` | `no-content` | The reply was empty, or the service filtered it. |
448
+ | `UnparseableReplyError` | `unparseable-reply` | The reply was not JSON, or not an object. |
449
+ | `KeySetMismatchError` | `key-set-mismatch` | The reply answered none of the requested fields. |
450
+ | `TransportError` | `transport` | The call failed — network, auth, rate limit, timeout. |
451
+ | `ProviderConfigurationError`| `config` | The provider cannot work as configured (usually a missing optional SDK). |
452
+
453
+ All extend `TranslationProviderError`, so one `catch` covers them. The original failure is on the standard `cause` property — never copied into `message`, because that text can reach an HTTP response body and a vendor error may carry your API key.
454
+
455
+ A **partial** reply is not an error: the fields that came back are applied, and the ones that did not are named in a warning. That is deliberate — dropping good translations because one field is missing helps nobody — but it does mean a partial translation still completes.
456
+
317
457
  #### Custom provider
318
458
 
319
459
  Implement the `TranslationProvider` interface — a single `translate` method:
@@ -341,8 +481,10 @@ class DeepLProvider implements TranslationProvider {
341
481
  result[key] = data.translations[i].text;
342
482
  });
343
483
  return result;
344
- } catch {
345
- return null; // null aborts the translation for this chunk
484
+ } catch (cause) {
485
+ // Throwing is preferred the cause reaches the log and the editor sees why it failed.
486
+ // Returning `null` still works and still aborts the whole run, but it says nothing about why.
487
+ throw new Error("DeepL translation failed", { cause });
346
488
  }
347
489
  }
348
490
  }
package/dist/index.d.ts CHANGED
@@ -5,8 +5,12 @@ export type { TranslationTask, TranslationLifecycleCallbacks } from "./server/mo
5
5
  export type { TranslationProvenanceRecord } from "./core";
6
6
  export type { AccessGuard, AccessGuardRequest } from "./types/AccessGuard";
7
7
  export type { TranslationProvider, TranslationInput, TranslationOutput } from "./core";
8
- export { createOpenAIProvider } from "./translation-providers";
9
- export type { OpenAIProviderConfig, DryRunConfig } from "./translation-providers";
8
+ export { openAIComplete } from "./translation-providers";
9
+ export type { OpenAIClientShape, OpenAISamplingParams, OpenAIStructuredOutput, DryRunConfig, } from "./translation-providers";
10
+ export { createTranslationProvider } from "./translation-providers";
11
+ export type { CompletionFn, CompletionRequest, TranslationProviderConfig, JsonSchemaObject, SystemPromptBuilder, SystemPromptContext, } from "./translation-providers";
12
+ export { TranslationProviderError, NoContentError, UnparseableReplyError, KeySetMismatchError, TransportError, ProviderConfigurationError, } from "./translation-providers";
13
+ export type { TranslationFailureCode } from "./translation-providers";
10
14
  export { createPayloadJobsRunner, createSyncRunner } from "./server/modules/task-runner";
11
15
  export type { TaskRunnerProvider, PayloadJobsRunnerOptions } from "./server/modules/task-runner";
12
16
  export { documentLevel, collectionLevel, fieldLevel } from "./composition/levels";
@@ -17,6 +21,7 @@ export { withAutoTranslate } from "./auto-translate-config";
17
21
  export type { AutoTranslateConfig, AutoTranslateStrategy } from "./auto-translate-config";
18
22
  export { createTranslatePlugin, TranslateCollectionPlugin } from "./plugin";
19
23
  export type { TranslateCollectionPluginConfig } from "./plugin";
20
- export { OpenAITranslationProvider } from "./translation-providers";
24
+ export { createOpenAIProvider, OpenAITranslationProvider } from "./translation-providers";
25
+ export type { OpenAIProviderConfig } from "./translation-providers";
21
26
  export { translateKitField } from "./field-config";
22
27
  export type { TranslateKitFieldConfig } from "./field-config";
package/dist/index.js CHANGED
@@ -1,7 +1,8 @@
1
1
  // Main plugin
2
2
  export { translatorPlugin } from "./plugin";
3
- // Built-in OpenAI provider (opt-in; pulls `openai`) — lives in the plugin's src/providers, outside core
4
- export { createOpenAIProvider } from "./translation-providers";
3
+ export { openAIComplete } from "./translation-providers";
4
+ export { createTranslationProvider } from "./translation-providers";
5
+ export { TranslationProviderError, NoContentError, UnparseableReplyError, KeySetMismatchError, TransportError, ProviderConfigurationError } from "./translation-providers";
5
6
  // Task runners
6
7
  export { createPayloadJobsRunner, createSyncRunner } from "./server/modules/task-runner";
7
8
  // Translation levels
@@ -14,7 +15,7 @@ export { withFieldTranslation } from "./field-config";
14
15
  export { withAutoTranslate } from "./auto-translate-config";
15
16
  // Deprecated exports (for backwards compatibility)
16
17
  export { createTranslatePlugin, TranslateCollectionPlugin } from "./plugin";
17
- export { OpenAITranslationProvider } from "./translation-providers";
18
+ export { createOpenAIProvider, OpenAITranslationProvider } from "./translation-providers";
18
19
  export { translateKitField } from "./field-config";
19
20
 
20
21
  //# sourceMappingURL=index.js.map
@@ -1,2 +1,3 @@
1
1
  export type { TranslationProvider, TranslationInput, TranslationOutput, } from "../core/domain/translation-providers";
2
+ export * from "./shared";
2
3
  export * from "./openai";
@@ -1,10 +1,4 @@
1
- // Translation-provider implementations (adapters), one directory per vendor. Payload-free and
2
- // opt-in — each vendor pulls its own deps (e.g. `openai`). Kept OUT of the framework-agnostic core
3
- // so the core stays dependency-free; the PORT they implement lives in the core and is re-exported
4
- // here for convenience. Naming is explicit ("translation-providers") to leave room for other kinds
5
- // of providers later without a collision.
6
- // The port (contract) — source of truth is the core.
7
- // Vendor implementations.
1
+ export * from "./shared";
8
2
  export * from "./openai";
9
3
 
10
4
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,52 @@
1
+ export type OpenAIChatMessage = {
2
+ role: "system" | "user";
3
+ content: string;
4
+ };
5
+ /**
6
+ * `schema` is `Record<string, unknown>`, not `unknown`: a widened field is not assignable to the
7
+ * SDK's closed union and OpenAI.shapes.test.ts stops compiling.
8
+ */
9
+ export type OpenAIResponseFormat = {
10
+ type: "json_object";
11
+ } | {
12
+ type: "json_schema";
13
+ json_schema: {
14
+ name: string;
15
+ strict?: boolean;
16
+ schema: Record<string, unknown>;
17
+ };
18
+ };
19
+ export type OpenAIChatParams = {
20
+ model: string;
21
+ messages: OpenAIChatMessage[];
22
+ response_format?: OpenAIResponseFormat;
23
+ temperature?: number;
24
+ top_p?: number;
25
+ frequency_penalty?: number;
26
+ presence_penalty?: number;
27
+ };
28
+ export type OpenAIChatResult = {
29
+ choices: Array<{
30
+ message?: {
31
+ content?: string | null;
32
+ };
33
+ }>;
34
+ };
35
+ export type OpenAIRequestOptions = {
36
+ signal?: AbortSignal;
37
+ };
38
+ /**
39
+ * Any client with a `chat.completions.create` of this shape.
40
+ *
41
+ * Method syntax, not a property with a function type: property syntax is checked contravariantly
42
+ * under strictFunctionTypes and the real SDK's overloaded `create` stops being assignable.
43
+ *
44
+ * @since 0.11.0
45
+ */
46
+ export type OpenAIClientShape = {
47
+ chat: {
48
+ completions: {
49
+ create(params: OpenAIChatParams, options?: OpenAIRequestOptions): Promise<OpenAIChatResult>;
50
+ };
51
+ };
52
+ };
@@ -0,0 +1,13 @@
1
+ // Never import from `openai` here, in any position: an SDK type would enter this package's emitted
2
+ // declarations and force every consumer to install the SDK to type-check. Chat Completions rather
3
+ // than the Responses API — that is what Azure, OpenRouter and LiteLLM gateways implement.
4
+ /**
5
+ * Any client with a `chat.completions.create` of this shape.
6
+ *
7
+ * Method syntax, not a property with a function type: property syntax is checked contravariantly
8
+ * under strictFunctionTypes and the real SDK's overloaded `create` stops being assignable.
9
+ *
10
+ * @since 0.11.0
11
+ */ export { };
12
+
13
+ //# sourceMappingURL=OpenAI.shapes.js.map
@@ -1,145 +1,84 @@
1
- import type { TranslationProvider, TranslationInput, TranslationOutput } from "../../core/domain/translation-providers/TranslationProvider.interface";
2
- import type { ChatModel } from "openai/resources/index.mjs";
3
- /**
4
- * Function to transform text in dry run mode.
5
- * Receives the original text and returns the transformed text.
6
- */
7
- type DryRunTransformer = (text: string) => string | Promise<string>;
8
- /**
9
- * Configuration for dry run mode with custom transformer.
10
- */
11
- export type DryRunConfig = {
12
- /** Custom transformer function for text */
13
- transform: DryRunTransformer;
14
- /** Delay in milliseconds before returning mock translation (simulates API latency) */
15
- timeout?: number;
16
- };
17
- /**
18
- * Context passed to the system prompt builder function.
19
- */
20
- export type SystemPromptContext = {
21
- /** Source language code (e.g., 'en', 'de') */
22
- sourceLang: string;
23
- /** Target language code (e.g., 'fr', 'es') */
24
- targetLang: string;
25
- /** Default system prompt that can be extended or replaced */
26
- defaultPrompt: string;
27
- };
28
- /**
29
- * Function to build a custom system prompt for translation.
30
- */
31
- type SystemPromptBuilder = (context: SystemPromptContext) => string;
32
- export type OpenAIProviderConfig = {
33
- /** OpenAI API key (required). Read it from an env var — never hard-code it. */
34
- apiKey: string;
1
+ import type { TranslationProvider } from "../../core/domain/translation-providers";
2
+ import type { DryRunConfig, SystemPromptBuilder } from "../shared";
3
+ import type { OpenAIClientShape } from "./OpenAI.shapes";
4
+ import type { OpenAISamplingParams, OpenAIStructuredOutput } from "./openAIComplete";
5
+ type OpenAIProviderBase = {
35
6
  /**
36
- * OpenAI model to use for translation.
7
+ * Model used for translation.
37
8
  *
38
- * @default 'gpt-4o'
39
- *
40
- * @example
41
- * model: 'gpt-4o-mini'
9
+ * @default 'gpt-4o' — may move in a minor release; pin it if you need reproducibility.
42
10
  */
43
- model?: (string & {}) | ChatModel;
11
+ model?: string;
44
12
  /**
45
- * Custom system prompt builder for translation.
46
- * Receives context with source/target languages and the default prompt.
47
- *
48
- * @example
49
- * // Add custom instructions
50
- * systemPrompt: ({ sourceLang, targetLang, defaultPrompt }) =>
51
- * `${defaultPrompt}\nUse formal language. Keep brand names unchanged.`
52
- *
53
- * @example
54
- * // Completely custom prompt
55
- * systemPrompt: ({ sourceLang, targetLang }) =>
56
- * `Translate JSON values from ${sourceLang} to ${targetLang}. Be concise.`
13
+ * Custom system-prompt builder. Receives the source and target languages plus the prompt this
14
+ * package would otherwise send, so you can extend it rather than rewrite it.
57
15
  */
58
16
  systemPrompt?: SystemPromptBuilder;
59
17
  /**
60
- * When enabled, simulates translations without making actual API calls to OpenAI.
61
- *
62
- * - `true` — uses default transformer that reverses the text
63
- * - `{ transform, timeout? }` — custom transformer with optional delay
64
- *
65
- * @example
66
- * // Default behavior (reverse text, no delay)
67
- * dryRun: true
68
- *
69
- * @example
70
- * // Custom transformer with delay
71
- * dryRun: {
72
- * transform: (text) => `[TRANSLATED] ${text}`,
73
- * timeout: 1000, // 1 second delay
74
- * }
18
+ * Simulate translations without calling OpenAI see {@link DryRunConfig}.
75
19
  *
76
20
  * @default false
21
+ * @deprecated Pass your own `client`, or build a provider with `createTranslationProvider({
22
+ * complete })`. Remove in next major. See docs/DEPRECATIONS.md#provider-dry-run
77
23
  */
78
24
  dryRun?: boolean | DryRunConfig;
79
25
  /**
80
- * Per-request timeout in milliseconds for the OpenAI client. A translation job blocks on this
81
- * call, so the OpenAI SDK default (10 minutes) is usually too long. Omit to keep the SDK default.
26
+ * Per-request timeout in milliseconds for the client this package builds.
82
27
  *
83
- * @example
84
- * timeout: 60_000 // 60s
28
+ * Ignored when you pass your own `client` — then the timeout is whatever you configured on it.
85
29
  *
30
+ * @default 60000
86
31
  * @since 0.6.0
87
32
  */
88
33
  timeout?: number;
89
34
  /**
90
- * Maximum automatic retries the OpenAI client performs on transient errors (429, 5xx, network).
91
- * Omit to keep the SDK default (2). Set `0` to disable retries.
35
+ * Maximum automatic retries on transient errors (429, 5xx, network) for the client this package
36
+ * builds. Ignored when you pass your own `client`.
92
37
  *
38
+ * @default the SDK's own default (2)
93
39
  * @since 0.6.0
94
40
  */
95
41
  maxRetries?: number;
96
- };
97
- /** @deprecated Use `createOpenAIProvider` function instead */
98
- export declare class OpenAITranslationProvider implements TranslationProvider {
99
- private openAiClient;
100
- private readonly config;
101
- constructor(config: OpenAIProviderConfig);
102
- translate(content: TranslationInput, souceLng: string, targetLng: string): Promise<TranslationOutput | null>;
103
42
  /**
104
- * Builds the system prompt for translation.
105
- */
106
- private buildSystemPrompt;
107
- /**
108
- * Returns the transformer function for dry run mode.
109
- * If dryRun is an object with transform, returns it.
110
- * If dryRun is true, returns the default transformer that reverses text.
43
+ * Sampling parameters see {@link OpenAISamplingParams}, which survives this option.
44
+ *
45
+ * @since 0.11.0
111
46
  */
112
- private getDryRunTransformer;
47
+ sampling?: OpenAISamplingParams;
113
48
  /**
114
- * Returns the timeout for dry run mode.
115
- * If dryRun is an object with timeout, returns it.
116
- * Otherwise returns 0 (no delay).
49
+ * Which structured-output envelope to send see {@link OpenAIStructuredOutput}, which documents
50
+ * the trade-off and survives this option.
51
+ *
52
+ * @since 0.11.0
117
53
  */
118
- private getDryRunTimeout;
119
- private createMockTranslation;
54
+ structuredOutput?: OpenAIStructuredOutput;
55
+ };
56
+ /**
57
+ * Configuration for {@link createOpenAIProvider}: an API key **or** a ready-made client, never both.
58
+ *
59
+ * @deprecated Construct the client yourself and pass it to `openAIComplete`, which stays.
60
+ * Remove in next major. See docs/DEPRECATIONS.md#openai-client-construction
61
+ */
62
+ export type OpenAIProviderConfig = OpenAIProviderBase & ({
63
+ apiKey: string;
64
+ client?: never;
65
+ } | {
120
66
  /**
121
- * Recursively transforms the values of an object or array using the provided transformer function.
122
- * @param obj The object or value to transform.
123
- * @param transformer The function to apply to each value.
124
- * @returns The transformed object, array, or value.
67
+ * A ready-made client — the OpenAI SDK client, Azure, OpenRouter, a proxy. On this path the
68
+ * `openai` package is never loaded and `timeout` / `maxRetries` are yours, not ours.
69
+ *
70
+ * @since 0.11.0
125
71
  */
126
- private transformObjectValues;
127
- }
72
+ client: OpenAIClientShape;
73
+ apiKey?: never;
74
+ });
128
75
  /**
129
76
  * Creates an OpenAI translation provider.
130
77
  *
131
- * @example
132
- * ```ts
133
- * // Basic usage
134
- * createOpenAIProvider({ apiKey: process.env.OPENAI_API_KEY })
135
- *
136
- * // With options
137
- * createOpenAIProvider({
138
- * apiKey: process.env.OPENAI_API_KEY,
139
- * model: 'gpt-4o-mini',
140
- * systemPrompt: ({ defaultPrompt }) => `${defaultPrompt}\nUse formal language.`,
141
- * })
142
- * ```
78
+ * @deprecated What this adds over `openAIComplete` is building the SDK client for you, and
79
+ * carrying `openai` as an optional dependency to do it. Construct the client yourself instead:
80
+ * `createTranslationProvider({ complete: openAIComplete({ client, model }) })`. Remove in next
81
+ * major. See the recipe in the README and docs/DEPRECATIONS.md#openai-client-construction
143
82
  */
144
- export declare function createOpenAIProvider(config: OpenAIProviderConfig): OpenAITranslationProvider;
83
+ export declare function createOpenAIProvider(config: OpenAIProviderConfig): TranslationProvider;
145
84
  export {};