@braze/sanity-plugin-ai-translation 0.3.0 → 0.3.2
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/README.md +24 -3
- package/dist/index.d.ts +88 -19
- package/dist/index.js +70 -65
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +39 -1
- package/dist/server.js +12 -1
- package/dist/server.js.map +1 -1
- package/package.json +35 -35
package/dist/server.d.ts
CHANGED
|
@@ -19,6 +19,34 @@ declare interface GPT5Response {
|
|
|
19
19
|
};
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Whether a client-supplied value is one of the supported {@link OPENAI_MODELS}.
|
|
24
|
+
* Use it in the translation function to allowlist the model before calling OpenAI.
|
|
25
|
+
*
|
|
26
|
+
* @public
|
|
27
|
+
*/
|
|
28
|
+
export declare const isOpenAIModel: (value: unknown) => value is OpenAIModel;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* OpenAI models the plugin supports.
|
|
32
|
+
*
|
|
33
|
+
* This is the single source of truth: Studio's `PluginConfig['openai']['model']` is derived from
|
|
34
|
+
* it, and the translation function allowlists against it before calling `requestOpenAICompletion`,
|
|
35
|
+
* so the two can never drift apart.
|
|
36
|
+
*
|
|
37
|
+
* @public
|
|
38
|
+
*/
|
|
39
|
+
export declare const OPENAI_MODELS: readonly [
|
|
40
|
+
"gpt-5.1",
|
|
41
|
+
"gpt-5",
|
|
42
|
+
"gpt-5-mini",
|
|
43
|
+
"gpt-5-nano",
|
|
44
|
+
"gpt-5-pro",
|
|
45
|
+
"gpt-4o",
|
|
46
|
+
"gpt-4-turbo",
|
|
47
|
+
"gpt-4",
|
|
48
|
+
];
|
|
49
|
+
|
|
22
50
|
/**
|
|
23
51
|
* Server-side configuration for `requestOpenAICompletion`
|
|
24
52
|
*
|
|
@@ -36,7 +64,10 @@ export declare interface OpenAICompletionOptions {
|
|
|
36
64
|
* @public
|
|
37
65
|
*/
|
|
38
66
|
export declare interface OpenAICompletionPayload {
|
|
39
|
-
/**
|
|
67
|
+
/**
|
|
68
|
+
* OpenAI model id, e.g. `gpt-5` or `gpt-4o`. Typed as `string` because it is client-supplied;
|
|
69
|
+
* the caller should check it with `isOpenAIModel` before invoking.
|
|
70
|
+
*/
|
|
40
71
|
model: string;
|
|
41
72
|
/** Messages assembled by Studio (serialized document + prompts) */
|
|
42
73
|
messages: OpenAIMessage[];
|
|
@@ -58,6 +89,13 @@ export declare type OpenAICompletionResult = GPT5Response;
|
|
|
58
89
|
*/
|
|
59
90
|
export declare type OpenAIMessage = GPT5Request["messages"][number];
|
|
60
91
|
|
|
92
|
+
/**
|
|
93
|
+
* An OpenAI model id from {@link OPENAI_MODELS}
|
|
94
|
+
*
|
|
95
|
+
* @public
|
|
96
|
+
*/
|
|
97
|
+
export declare type OpenAIModel = (typeof OPENAI_MODELS)[number];
|
|
98
|
+
|
|
61
99
|
/**
|
|
62
100
|
* Reasoning effort for GPT-5 models
|
|
63
101
|
*
|
package/dist/server.js
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import { devLog, retryWithBackoff, isNetworkError } from "./_chunks-es/retry.js";
|
|
2
|
-
const
|
|
2
|
+
const OPENAI_MODELS = [
|
|
3
|
+
"gpt-5.1",
|
|
4
|
+
"gpt-5",
|
|
5
|
+
"gpt-5-mini",
|
|
6
|
+
"gpt-5-nano",
|
|
7
|
+
"gpt-5-pro",
|
|
8
|
+
"gpt-4o",
|
|
9
|
+
"gpt-4-turbo",
|
|
10
|
+
"gpt-4"
|
|
11
|
+
], OPENAI_MODEL_SET = new Set(OPENAI_MODELS), isOpenAIModel = (value) => typeof value == "string" && OPENAI_MODEL_SET.has(value), describeMessages = (messages) => messages.map(({ role, content }) => ({ role, contentLength: content.length })), callGPT5 = async (apiKey, request) => (request.model || "gpt-5").startsWith("gpt-5") ? await callGPT5ResponsesAPI(apiKey, request) : await callChatCompletionsAPI(apiKey, request), callGPT5ResponsesAPI = async (apiKey, request) => {
|
|
3
12
|
const model = request.model || "gpt-5", requestBody = {
|
|
4
13
|
model: model === "gpt-5" ? "gpt-5.1" : model,
|
|
5
14
|
// Map gpt-5 to gpt-5.1, use others as-is
|
|
@@ -139,6 +148,8 @@ const describeMessages = (messages) => messages.map(({ role, content }) => ({ ro
|
|
|
139
148
|
return callGPT5(options.apiKey, { model, messages, reasoningEffort, temperature });
|
|
140
149
|
};
|
|
141
150
|
export {
|
|
151
|
+
OPENAI_MODELS,
|
|
152
|
+
isOpenAIModel,
|
|
142
153
|
requestOpenAICompletion
|
|
143
154
|
};
|
|
144
155
|
//# sourceMappingURL=server.js.map
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sources":["../src/utils/openai/callGPT5.ts","../src/server/openai/requestOpenAICompletion.ts"],"sourcesContent":["import { devLog } from '../common/devLog'\nimport { isNetworkError, retryWithBackoff } from '../common/retry'\n\nexport interface GPT5Request {\n messages: Array<{ role: 'developer' | 'user' | 'assistant'; content: string }>\n model?: string\n temperature?: number\n reasoningEffort?: 'low' | 'medium' | 'high'\n}\n\nexport interface GPT5Response {\n content: string\n tokensUsed: {\n prompt: number\n completion: number\n reasoning: number\n total: number\n cached: number\n }\n}\n\n// This module also runs inside Sanity functions, so logs must never include prompt or document text.\nconst describeMessages = (messages: GPT5Request['messages']) =>\n messages.map(({ role, content }) => ({ role, contentLength: content.length }))\n\n/**\n * Calls the OpenAI API with the given request parameters.\n * All GPT-5 variants (gpt-5, gpt-5.1, gpt-5-mini, gpt-5-nano, gpt-5-pro) use the new /v1/responses endpoint.\n * GPT-4 models use the standard /v1/chat/completions endpoint.\n */\nexport const callGPT5 = async (apiKey: string, request: GPT5Request): Promise<GPT5Response> => {\n const model = request.model || 'gpt-5'\n const isGPT5Family = model.startsWith('gpt-5')\n\n // All GPT-5 variants require /v1/responses endpoint; GPT-4 uses /v1/chat/completions\n if (isGPT5Family) {\n return await callGPT5ResponsesAPI(apiKey, request)\n }\n\n // GPT-4 and other models use the standard chat completions endpoint\n return await callChatCompletionsAPI(apiKey, request)\n}\n\n/**\n * Calls all GPT-5 variants using the new /v1/responses endpoint.\n * This endpoint supports reasoning configuration for applicable models.\n */\nconst callGPT5ResponsesAPI = async (apiKey: string, request: GPT5Request): Promise<GPT5Response> => {\n const model = request.model || 'gpt-5'\n\n // Format request for OpenAI Responses API (different from chat completions API)\n const requestBody: Record<string, unknown> = {\n model: model === 'gpt-5' ? 'gpt-5.1' : model, // Map gpt-5 to gpt-5.1, use others as-is\n input: request.messages, // Uses 'input' instead of 'messages'\n }\n\n // Include reasoning effort setting if provided by caller\n if (request.reasoningEffort) {\n requestBody.reasoning = {\n effort: request.reasoningEffort,\n summary: 'auto', // Enable automatic reasoning summaries\n }\n }\n\n devLog.debug('OpenAI GPT-5 Request', {\n model: requestBody.model,\n reasoningEffort: request.reasoningEffort,\n messages: describeMessages(request.messages),\n })\n\n const response = await retryWithBackoff(\n async () => {\n const fetchResponse = await fetch('https://api.openai.com/v1/responses', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${apiKey}`,\n },\n body: JSON.stringify(requestBody),\n })\n\n if (!fetchResponse.ok) {\n const error = await fetchResponse.json()\n const errorMessage = `GPT-5 API error: ${error.error?.message || 'Unknown error'}`\n const errorObj = new Error(errorMessage)\n // Attach response status for error classification\n ;(errorObj as { response?: { status: number } }).response = { status: fetchResponse.status }\n throw errorObj\n }\n\n return fetchResponse\n },\n {\n maxAttempts: 2,\n initialDelayMs: 0,\n retryableErrors: isNetworkError,\n context: { model: requestBody.model, endpoint: 'responses' },\n },\n )\n\n if (!response.ok) {\n const error = await response.json()\n throw new Error(`GPT-5 API error: ${error.error?.message || 'Unknown error'}`)\n }\n\n const result = await response.json()\n\n // Parse token usage from Responses API response structure\n const tokensUsed = {\n prompt: result.usage?.input_tokens || 0,\n completion: result.usage?.output_tokens || 0,\n reasoning: result.usage?.output_tokens_details?.reasoning_tokens || 0,\n total: result.usage?.total_tokens || 0,\n cached: result.usage?.input_tokens_details?.cached_tokens || 0,\n }\n\n // Responses API wraps output in array with reasoning and message objects\n let content = ''\n\n if (result.output && Array.isArray(result.output)) {\n // Find the message item in the output array\n const messageItem = result.output.find((item: { type: string }) => item.type === 'message') as\n | { content?: Array<{ type: string; text?: string }> }\n | undefined\n\n if (messageItem?.content && Array.isArray(messageItem.content)) {\n // Find the output_text item in the content array\n const outputTextItem = messageItem.content.find((item) => item.type === 'output_text')\n content = outputTextItem?.text || ''\n }\n }\n\n // Fallback to other possible field names if output array approach didn't work\n if (!content) {\n content = result.output_text || result.text || ''\n }\n\n return {\n content,\n tokensUsed,\n }\n}\n\n/**\n * Calls GPT-4 and other models using the standard /v1/chat/completions endpoint.\n */\nconst callChatCompletionsAPI = async (apiKey: string, request: GPT5Request): Promise<GPT5Response> => {\n const model = request.model || 'gpt-4o'\n\n const requestBody: Record<string, unknown> = {\n model,\n messages: request.messages,\n temperature: request.temperature ?? 0.3,\n }\n\n devLog.debug('OpenAI Chat Completions Request', {\n model,\n temperature: request.temperature ?? 0.3,\n messages: describeMessages(request.messages),\n })\n\n const response = await retryWithBackoff(\n async () => {\n const fetchResponse = await fetch('https://api.openai.com/v1/chat/completions', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${apiKey}`,\n },\n body: JSON.stringify(requestBody),\n })\n\n if (!fetchResponse.ok) {\n const error = await fetchResponse.json()\n const errorMessage = `OpenAI API error: ${error.error?.message || 'Unknown error'}`\n const errorObj = new Error(errorMessage)\n // Attach response status for error classification\n ;(errorObj as { response?: { status: number } }).response = { status: fetchResponse.status }\n throw errorObj\n }\n\n return fetchResponse\n },\n {\n maxAttempts: 2,\n initialDelayMs: 0,\n retryableErrors: isNetworkError,\n context: { model, endpoint: 'chat/completions' },\n },\n )\n\n if (!response.ok) {\n const error = await response.json()\n throw new Error(`OpenAI API error: ${error.error?.message || 'Unknown error'}`)\n }\n\n const result = await response.json()\n\n const usage = result.usage || {}\n const tokensUsed = {\n prompt: usage.prompt_tokens || 0,\n completion: usage.completion_tokens || 0,\n reasoning: usage.reasoning_tokens || 0,\n total: usage.total_tokens || 0,\n cached: usage.prompt_tokens_details?.cached_tokens || 0,\n }\n\n const message = result.choices[0].message\n const messageContent = message.content || ''\n\n return {\n content: messageContent,\n tokensUsed,\n }\n}\n","import { callGPT5 } from '../../utils/openai/callGPT5'\nimport type {\n OpenAICompletionOptions,\n OpenAICompletionPayload,\n OpenAICompletionResult,\n OpenAIMessage,\n OpenAIReasoningEffort,\n} from './types'\n\nconst MESSAGE_ROLES: ReadonlySet<string> = new Set<OpenAIMessage['role']>(['developer', 'user', 'assistant'])\nconst REASONING_EFFORTS: ReadonlySet<string> = new Set<OpenAIReasoningEffort>(['low', 'medium', 'high'])\n\n/** Studio sends a developer message and a user message; anything far beyond that is not a translation request. */\nexport const MAX_MESSAGES = 20\n/** Roughly the GPT-5 input limit (about 272k tokens); larger payloads would be rejected by OpenAI anyway. */\nexport const MAX_TOTAL_CONTENT_LENGTH = 1_000_000\n\nconst assertValidPayload = (payload: OpenAICompletionPayload): void => {\n if (!payload || typeof payload !== 'object') {\n throw new Error('requestOpenAICompletion: payload must be an object')\n }\n if (typeof payload.model !== 'string' || payload.model.trim() === '') {\n throw new Error('requestOpenAICompletion: model is required')\n }\n if (!Array.isArray(payload.messages) || payload.messages.length === 0) {\n throw new Error('requestOpenAICompletion: messages must be a non-empty array')\n }\n if (payload.messages.length > MAX_MESSAGES) {\n throw new Error(`requestOpenAICompletion: messages must contain at most ${MAX_MESSAGES} items`)\n }\n let totalContentLength = 0\n payload.messages.forEach((message, index) => {\n if (!message || !MESSAGE_ROLES.has(message.role) || typeof message.content !== 'string') {\n throw new Error(\n `requestOpenAICompletion: messages[${index}] must have a role of developer, user, or assistant and string content`,\n )\n }\n totalContentLength += message.content.length\n })\n if (totalContentLength > MAX_TOTAL_CONTENT_LENGTH) {\n throw new Error(\n `requestOpenAICompletion: message content must total at most ${MAX_TOTAL_CONTENT_LENGTH} characters (got ${totalContentLength})`,\n )\n }\n if (payload.reasoningEffort !== undefined && !REASONING_EFFORTS.has(payload.reasoningEffort)) {\n throw new Error('requestOpenAICompletion: reasoningEffort must be low, medium, or high')\n }\n if (payload.temperature !== undefined && !Number.isFinite(payload.temperature)) {\n throw new Error('requestOpenAICompletion: temperature must be a finite number')\n }\n}\n\n/**\n * Sends assembled translation messages to OpenAI and returns the translated text and token usage.\n *\n * Serialization, prompt assembly, structure validation, and the Content Release write stay in\n * Studio; this only performs the completion call. Model allowlisting is the caller's responsibility.\n *\n * Payloads with more than {@link MAX_MESSAGES} messages or more than {@link MAX_TOTAL_CONTENT_LENGTH}\n * characters of content are rejected before any request is sent. Only `role` and `content` of each\n * message are forwarded to OpenAI.\n *\n * @public\n */\nexport const requestOpenAICompletion = async (\n payload: OpenAICompletionPayload,\n options: OpenAICompletionOptions,\n): Promise<OpenAICompletionResult> => {\n if (typeof options?.apiKey !== 'string' || options.apiKey.trim() === '') {\n throw new Error('requestOpenAICompletion: apiKey is required')\n }\n assertValidPayload(payload)\n\n const { model, reasoningEffort, temperature } = payload\n const messages = payload.messages.map(({ role, content }) => ({ role, content }))\n return callGPT5(options.apiKey, { model, messages, reasoningEffort, temperature })\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"server.js","sources":["../src/server/openai/models.ts","../src/utils/openai/callGPT5.ts","../src/server/openai/requestOpenAICompletion.ts"],"sourcesContent":["/**\n * OpenAI models the plugin supports.\n *\n * This is the single source of truth: Studio's `PluginConfig['openai']['model']` is derived from\n * it, and the translation function allowlists against it before calling `requestOpenAICompletion`,\n * so the two can never drift apart.\n *\n * @public\n */\nexport const OPENAI_MODELS = [\n 'gpt-5.1',\n 'gpt-5',\n 'gpt-5-mini',\n 'gpt-5-nano',\n 'gpt-5-pro',\n 'gpt-4o',\n 'gpt-4-turbo',\n 'gpt-4',\n] as const\n\n/**\n * An OpenAI model id from {@link OPENAI_MODELS}\n *\n * @public\n */\nexport type OpenAIModel = (typeof OPENAI_MODELS)[number]\n\nconst OPENAI_MODEL_SET: ReadonlySet<string> = new Set(OPENAI_MODELS)\n\n/**\n * Whether a client-supplied value is one of the supported {@link OPENAI_MODELS}.\n * Use it in the translation function to allowlist the model before calling OpenAI.\n *\n * @public\n */\nexport const isOpenAIModel = (value: unknown): value is OpenAIModel =>\n typeof value === 'string' && OPENAI_MODEL_SET.has(value)\n","import { devLog } from '../common/devLog'\nimport { isNetworkError, retryWithBackoff } from '../common/retry'\n\nexport interface GPT5Request {\n messages: Array<{ role: 'developer' | 'user' | 'assistant'; content: string }>\n model?: string\n temperature?: number\n reasoningEffort?: 'low' | 'medium' | 'high'\n}\n\nexport interface GPT5Response {\n content: string\n tokensUsed: {\n prompt: number\n completion: number\n reasoning: number\n total: number\n cached: number\n }\n}\n\n// This module also runs inside Sanity functions, so logs must never include prompt or document text.\nconst describeMessages = (messages: GPT5Request['messages']) =>\n messages.map(({ role, content }) => ({ role, contentLength: content.length }))\n\n/**\n * Calls the OpenAI API with the given request parameters.\n * All GPT-5 variants (gpt-5, gpt-5.1, gpt-5-mini, gpt-5-nano, gpt-5-pro) use the new /v1/responses endpoint.\n * GPT-4 models use the standard /v1/chat/completions endpoint.\n */\nexport const callGPT5 = async (apiKey: string, request: GPT5Request): Promise<GPT5Response> => {\n const model = request.model || 'gpt-5'\n const isGPT5Family = model.startsWith('gpt-5')\n\n // All GPT-5 variants require /v1/responses endpoint; GPT-4 uses /v1/chat/completions\n if (isGPT5Family) {\n return await callGPT5ResponsesAPI(apiKey, request)\n }\n\n // GPT-4 and other models use the standard chat completions endpoint\n return await callChatCompletionsAPI(apiKey, request)\n}\n\n/**\n * Calls all GPT-5 variants using the new /v1/responses endpoint.\n * This endpoint supports reasoning configuration for applicable models.\n */\nconst callGPT5ResponsesAPI = async (apiKey: string, request: GPT5Request): Promise<GPT5Response> => {\n const model = request.model || 'gpt-5'\n\n // Format request for OpenAI Responses API (different from chat completions API)\n const requestBody: Record<string, unknown> = {\n model: model === 'gpt-5' ? 'gpt-5.1' : model, // Map gpt-5 to gpt-5.1, use others as-is\n input: request.messages, // Uses 'input' instead of 'messages'\n }\n\n // Include reasoning effort setting if provided by caller\n if (request.reasoningEffort) {\n requestBody.reasoning = {\n effort: request.reasoningEffort,\n summary: 'auto', // Enable automatic reasoning summaries\n }\n }\n\n devLog.debug('OpenAI GPT-5 Request', {\n model: requestBody.model,\n reasoningEffort: request.reasoningEffort,\n messages: describeMessages(request.messages),\n })\n\n const response = await retryWithBackoff(\n async () => {\n const fetchResponse = await fetch('https://api.openai.com/v1/responses', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${apiKey}`,\n },\n body: JSON.stringify(requestBody),\n })\n\n if (!fetchResponse.ok) {\n const error = await fetchResponse.json()\n const errorMessage = `GPT-5 API error: ${error.error?.message || 'Unknown error'}`\n const errorObj = new Error(errorMessage)\n // Attach response status for error classification\n ;(errorObj as { response?: { status: number } }).response = { status: fetchResponse.status }\n throw errorObj\n }\n\n return fetchResponse\n },\n {\n maxAttempts: 2,\n initialDelayMs: 0,\n retryableErrors: isNetworkError,\n context: { model: requestBody.model, endpoint: 'responses' },\n },\n )\n\n if (!response.ok) {\n const error = await response.json()\n throw new Error(`GPT-5 API error: ${error.error?.message || 'Unknown error'}`)\n }\n\n const result = await response.json()\n\n // Parse token usage from Responses API response structure\n const tokensUsed = {\n prompt: result.usage?.input_tokens || 0,\n completion: result.usage?.output_tokens || 0,\n reasoning: result.usage?.output_tokens_details?.reasoning_tokens || 0,\n total: result.usage?.total_tokens || 0,\n cached: result.usage?.input_tokens_details?.cached_tokens || 0,\n }\n\n // Responses API wraps output in array with reasoning and message objects\n let content = ''\n\n if (result.output && Array.isArray(result.output)) {\n // Find the message item in the output array\n const messageItem = result.output.find((item: { type: string }) => item.type === 'message') as\n | { content?: Array<{ type: string; text?: string }> }\n | undefined\n\n if (messageItem?.content && Array.isArray(messageItem.content)) {\n // Find the output_text item in the content array\n const outputTextItem = messageItem.content.find((item) => item.type === 'output_text')\n content = outputTextItem?.text || ''\n }\n }\n\n // Fallback to other possible field names if output array approach didn't work\n if (!content) {\n content = result.output_text || result.text || ''\n }\n\n return {\n content,\n tokensUsed,\n }\n}\n\n/**\n * Calls GPT-4 and other models using the standard /v1/chat/completions endpoint.\n */\nconst callChatCompletionsAPI = async (apiKey: string, request: GPT5Request): Promise<GPT5Response> => {\n const model = request.model || 'gpt-4o'\n\n const requestBody: Record<string, unknown> = {\n model,\n messages: request.messages,\n temperature: request.temperature ?? 0.3,\n }\n\n devLog.debug('OpenAI Chat Completions Request', {\n model,\n temperature: request.temperature ?? 0.3,\n messages: describeMessages(request.messages),\n })\n\n const response = await retryWithBackoff(\n async () => {\n const fetchResponse = await fetch('https://api.openai.com/v1/chat/completions', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${apiKey}`,\n },\n body: JSON.stringify(requestBody),\n })\n\n if (!fetchResponse.ok) {\n const error = await fetchResponse.json()\n const errorMessage = `OpenAI API error: ${error.error?.message || 'Unknown error'}`\n const errorObj = new Error(errorMessage)\n // Attach response status for error classification\n ;(errorObj as { response?: { status: number } }).response = { status: fetchResponse.status }\n throw errorObj\n }\n\n return fetchResponse\n },\n {\n maxAttempts: 2,\n initialDelayMs: 0,\n retryableErrors: isNetworkError,\n context: { model, endpoint: 'chat/completions' },\n },\n )\n\n if (!response.ok) {\n const error = await response.json()\n throw new Error(`OpenAI API error: ${error.error?.message || 'Unknown error'}`)\n }\n\n const result = await response.json()\n\n const usage = result.usage || {}\n const tokensUsed = {\n prompt: usage.prompt_tokens || 0,\n completion: usage.completion_tokens || 0,\n reasoning: usage.reasoning_tokens || 0,\n total: usage.total_tokens || 0,\n cached: usage.prompt_tokens_details?.cached_tokens || 0,\n }\n\n const message = result.choices[0].message\n const messageContent = message.content || ''\n\n return {\n content: messageContent,\n tokensUsed,\n }\n}\n","import { callGPT5 } from '../../utils/openai/callGPT5'\nimport type {\n OpenAICompletionOptions,\n OpenAICompletionPayload,\n OpenAICompletionResult,\n OpenAIMessage,\n OpenAIReasoningEffort,\n} from './types'\n\nconst MESSAGE_ROLES: ReadonlySet<string> = new Set<OpenAIMessage['role']>(['developer', 'user', 'assistant'])\nconst REASONING_EFFORTS: ReadonlySet<string> = new Set<OpenAIReasoningEffort>(['low', 'medium', 'high'])\n\n/** Studio sends a developer message and a user message; anything far beyond that is not a translation request. */\nexport const MAX_MESSAGES = 20\n/** Roughly the GPT-5 input limit (about 272k tokens); larger payloads would be rejected by OpenAI anyway. */\nexport const MAX_TOTAL_CONTENT_LENGTH = 1_000_000\n\nconst assertValidPayload = (payload: OpenAICompletionPayload): void => {\n if (!payload || typeof payload !== 'object') {\n throw new Error('requestOpenAICompletion: payload must be an object')\n }\n if (typeof payload.model !== 'string' || payload.model.trim() === '') {\n throw new Error('requestOpenAICompletion: model is required')\n }\n if (!Array.isArray(payload.messages) || payload.messages.length === 0) {\n throw new Error('requestOpenAICompletion: messages must be a non-empty array')\n }\n if (payload.messages.length > MAX_MESSAGES) {\n throw new Error(`requestOpenAICompletion: messages must contain at most ${MAX_MESSAGES} items`)\n }\n let totalContentLength = 0\n payload.messages.forEach((message, index) => {\n if (!message || !MESSAGE_ROLES.has(message.role) || typeof message.content !== 'string') {\n throw new Error(\n `requestOpenAICompletion: messages[${index}] must have a role of developer, user, or assistant and string content`,\n )\n }\n totalContentLength += message.content.length\n })\n if (totalContentLength > MAX_TOTAL_CONTENT_LENGTH) {\n throw new Error(\n `requestOpenAICompletion: message content must total at most ${MAX_TOTAL_CONTENT_LENGTH} characters (got ${totalContentLength})`,\n )\n }\n if (payload.reasoningEffort !== undefined && !REASONING_EFFORTS.has(payload.reasoningEffort)) {\n throw new Error('requestOpenAICompletion: reasoningEffort must be low, medium, or high')\n }\n if (payload.temperature !== undefined && !Number.isFinite(payload.temperature)) {\n throw new Error('requestOpenAICompletion: temperature must be a finite number')\n }\n}\n\n/**\n * Sends assembled translation messages to OpenAI and returns the translated text and token usage.\n *\n * Serialization, prompt assembly, structure validation, and the Content Release write stay in\n * Studio; this only performs the completion call. Model allowlisting is the caller's responsibility.\n *\n * Payloads with more than {@link MAX_MESSAGES} messages or more than {@link MAX_TOTAL_CONTENT_LENGTH}\n * characters of content are rejected before any request is sent. Only `role` and `content` of each\n * message are forwarded to OpenAI.\n *\n * @public\n */\nexport const requestOpenAICompletion = async (\n payload: OpenAICompletionPayload,\n options: OpenAICompletionOptions,\n): Promise<OpenAICompletionResult> => {\n if (typeof options?.apiKey !== 'string' || options.apiKey.trim() === '') {\n throw new Error('requestOpenAICompletion: apiKey is required')\n }\n assertValidPayload(payload)\n\n const { model, reasoningEffort, temperature } = payload\n const messages = payload.messages.map(({ role, content }) => ({ role, content }))\n return callGPT5(options.apiKey, { model, messages, reasoningEffort, temperature })\n}\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@braze/sanity-plugin-ai-translation",
|
|
3
|
-
"version": "0.3.
|
|
4
|
-
"description": "A Sanity Studio
|
|
3
|
+
"version": "0.3.2",
|
|
4
|
+
"description": "A Sanity Studio plugin that provides AI translation for internationalized documents.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"keywords": [
|
|
7
7
|
"sanity",
|
|
@@ -45,78 +45,78 @@
|
|
|
45
45
|
"v2-incompatible.js"
|
|
46
46
|
],
|
|
47
47
|
"browserslist": "extends @sanity/browserslist-config",
|
|
48
|
-
"scripts": {
|
|
49
|
-
"build": "bash copy-readme.sh && plugin-kit verify-package --silent && pkg-utils build --strict --check --clean",
|
|
50
|
-
"check:openai-server": "pnpm build && node scripts/check-openai-server.mjs",
|
|
51
|
-
"dev": "plugin-kit link-watch",
|
|
52
|
-
"demo:serialization": "tsx scripts/demo-serialization.ts",
|
|
53
|
-
"prepublishOnly": "pnpm run build",
|
|
54
|
-
"test:pricing": "tsx scripts/test-pricing.ts",
|
|
55
|
-
"test": "vitest run",
|
|
56
|
-
"test:watch": "vitest",
|
|
57
|
-
"watch": "pkg-utils watch --strict",
|
|
58
|
-
"link-watch": "plugin-kit link-watch",
|
|
59
|
-
"lint": "biome check .",
|
|
60
|
-
"typecheck": "tsc -p tsconfig.settings.json --noEmit"
|
|
61
|
-
},
|
|
62
48
|
"dependencies": {
|
|
63
49
|
"@portabletext/react": "^4.0.3",
|
|
64
50
|
"@sanity/diff-patch": "^6.0.0",
|
|
65
51
|
"@sanity/icons": "^3.7.4",
|
|
66
52
|
"@sanity/incompatible-plugin": "^1.0.5",
|
|
67
|
-
"@sanity/ui": "^3.1.4",
|
|
68
53
|
"@sanity/uuid": "^3.0.2",
|
|
69
|
-
"@types/react": "^18.3.0",
|
|
70
|
-
"@types/react-dom": "^18.3.0",
|
|
71
54
|
"lodash.clonedeep": "^4.5.0",
|
|
72
55
|
"lodash.defaults": "4.2.0",
|
|
73
56
|
"lodash.get": "^4.4.2",
|
|
74
57
|
"lodash.set": "^4.3.2",
|
|
75
58
|
"react-fast-compare": "^3.2.2",
|
|
76
59
|
"rxjs": "^7.8.2",
|
|
77
|
-
"sanity-plugin-utils": "^
|
|
60
|
+
"sanity-plugin-utils": "^2.0.22"
|
|
78
61
|
},
|
|
79
62
|
"devDependencies": {
|
|
80
63
|
"@biomejs/biome": "2.0.6",
|
|
81
64
|
"@sanity/pkg-utils": "^7.9.15",
|
|
82
65
|
"@sanity/plugin-kit": "^4.0.19",
|
|
83
|
-
"@sanity/types": "^
|
|
66
|
+
"@sanity/types": "^6.20.0",
|
|
67
|
+
"@sanity/ui": "^4.4.10",
|
|
84
68
|
"@types/lodash.clonedeep": "^4.5.9",
|
|
85
69
|
"@types/lodash.defaults": "^4.2.9",
|
|
86
70
|
"@types/lodash.get": "^4.4.9",
|
|
87
71
|
"@types/lodash.set": "^4.3.9",
|
|
72
|
+
"@types/react": "^19.2.0",
|
|
73
|
+
"@types/react-dom": "^19.2.0",
|
|
88
74
|
"postcss": "^8.5.6",
|
|
89
|
-
"react": "^
|
|
90
|
-
"
|
|
91
|
-
"sanity
|
|
92
|
-
|
|
75
|
+
"react": "^19.2.0",
|
|
76
|
+
"react-dom": "^19.2.0",
|
|
77
|
+
"sanity": "^6.20.0",
|
|
78
|
+
"sanity-plugin-internationalized-array": "^5.3.7",
|
|
79
|
+
"styled-components": "^6.1.19",
|
|
93
80
|
"typescript": "^5.8.3",
|
|
94
81
|
"vitest": "^3.2.4"
|
|
95
82
|
},
|
|
96
83
|
"peerDependencies": {
|
|
97
|
-
"@sanity/ui": "^
|
|
98
|
-
"react": "^
|
|
99
|
-
"react-dom": "^
|
|
100
|
-
"sanity": "^
|
|
101
|
-
"sanity-plugin-internationalized-array": "^3.1.5",
|
|
102
|
-
"styled-components": "^
|
|
84
|
+
"@sanity/ui": "^4",
|
|
85
|
+
"react": "^19.2",
|
|
86
|
+
"react-dom": "^19.2",
|
|
87
|
+
"sanity": "^6.12.0",
|
|
88
|
+
"sanity-plugin-internationalized-array": "^3.1.5 || ^4 || ^5",
|
|
89
|
+
"styled-components": "^6.1",
|
|
103
90
|
"typescript": "^5"
|
|
104
91
|
},
|
|
105
92
|
"engines": {
|
|
106
|
-
"node": ">=
|
|
93
|
+
"node": ">=22.12"
|
|
107
94
|
},
|
|
108
95
|
"publishConfig": {
|
|
109
96
|
"registry": "https://registry.npmjs.org/",
|
|
110
97
|
"access": "restricted"
|
|
111
98
|
},
|
|
112
|
-
"packageManager": "pnpm@10.12.4",
|
|
113
99
|
"sanityPlugin": {
|
|
114
100
|
"verifyPackage": {
|
|
115
101
|
"eslintImports": false,
|
|
116
102
|
"dependencies": false,
|
|
117
103
|
"scripts": false,
|
|
118
104
|
"srcIndex": false,
|
|
119
|
-
"tsconfig": false
|
|
105
|
+
"tsconfig": false,
|
|
106
|
+
"nodeEngine": false
|
|
120
107
|
}
|
|
108
|
+
},
|
|
109
|
+
"scripts": {
|
|
110
|
+
"build": "bash copy-readme.sh && plugin-kit verify-package --silent && pkg-utils build --strict --check --clean",
|
|
111
|
+
"check:openai-server": "pnpm build && node scripts/check-openai-server.mjs",
|
|
112
|
+
"dev": "plugin-kit link-watch",
|
|
113
|
+
"demo:serialization": "tsx scripts/demo-serialization.ts",
|
|
114
|
+
"test:pricing": "tsx scripts/test-pricing.ts",
|
|
115
|
+
"test": "vitest run",
|
|
116
|
+
"test:watch": "vitest",
|
|
117
|
+
"watch": "pkg-utils watch --strict",
|
|
118
|
+
"link-watch": "plugin-kit link-watch",
|
|
119
|
+
"lint": "biome check .",
|
|
120
|
+
"typecheck": "tsc -p tsconfig.settings.json --noEmit"
|
|
121
121
|
}
|
|
122
|
-
}
|
|
122
|
+
}
|