@veryfront/ext-llm-openai 0.1.1185 → 0.1.1189

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 (39) hide show
  1. package/README.md +49 -2
  2. package/esm/_dnt.polyfills.d.ts +12 -0
  3. package/esm/_dnt.polyfills.d.ts.map +1 -0
  4. package/esm/_dnt.polyfills.js +15 -0
  5. package/esm/index.d.ts +1 -0
  6. package/esm/index.d.ts.map +1 -1
  7. package/esm/index.js +7 -1
  8. package/esm/openai-chat-request-builder.d.ts +5 -43
  9. package/esm/openai-chat-request-builder.d.ts.map +1 -1
  10. package/esm/openai-chat-request-builder.js +18 -4
  11. package/esm/openai-chat-stream.d.ts +8 -1
  12. package/esm/openai-chat-stream.d.ts.map +1 -1
  13. package/esm/openai-chat-stream.js +286 -104
  14. package/esm/openai-provider-options.d.ts +6 -0
  15. package/esm/openai-provider-options.d.ts.map +1 -0
  16. package/esm/openai-provider-options.js +14 -0
  17. package/esm/openai-provider.d.ts +6 -5
  18. package/esm/openai-provider.d.ts.map +1 -1
  19. package/esm/openai-provider.js +579 -129
  20. package/esm/openai-reasoning-models.d.ts +3 -6
  21. package/esm/openai-reasoning-models.d.ts.map +1 -1
  22. package/esm/openai-responses-request-builder.d.ts.map +1 -1
  23. package/esm/openai-responses-request-builder.js +240 -19
  24. package/esm/openai-responses-stream.d.ts +12 -1
  25. package/esm/openai-responses-stream.d.ts.map +1 -1
  26. package/esm/openai-responses-stream.js +1053 -113
  27. package/esm/openai-sse-buffer.d.ts +8 -0
  28. package/esm/openai-sse-buffer.d.ts.map +1 -0
  29. package/esm/openai-sse-buffer.js +47 -0
  30. package/esm/openai-stream-metadata.d.ts +6 -0
  31. package/esm/openai-stream-metadata.d.ts.map +1 -0
  32. package/esm/openai-stream-metadata.js +10 -0
  33. package/esm/openai-tool-input.d.ts +10 -0
  34. package/esm/openai-tool-input.d.ts.map +1 -0
  35. package/esm/openai-tool-input.js +30 -0
  36. package/esm/openai-web-search.d.ts +35 -0
  37. package/esm/openai-web-search.d.ts.map +1 -0
  38. package/esm/openai-web-search.js +369 -0
  39. package/package.json +3 -3
@@ -0,0 +1,8 @@
1
+ import { MAX_PROVIDER_SSE_BUFFER_CODE_UNITS, parseSseChunk } from "veryfront/provider/shared";
2
+ type InvalidStreamFactory = (issue: string) => Error;
3
+ export { MAX_PROVIDER_SSE_BUFFER_CODE_UNITS as MAX_OPENAI_SSE_BUFFER_CODE_UNITS };
4
+ export declare const MAX_OPENAI_SSE_RAW_CHUNK_BYTES: number;
5
+ export declare function appendOpenAISseChunk(decoder: TextDecoder, buffer: string, chunk: Uint8Array, invalid: InvalidStreamFactory): string;
6
+ export declare function finishOpenAISseDecoding(decoder: TextDecoder, buffer: string, invalid: InvalidStreamFactory): string;
7
+ export declare function parseOpenAISseBuffer(buffer: string, invalid: InvalidStreamFactory, trailing?: boolean): ReturnType<typeof parseSseChunk>;
8
+ //# sourceMappingURL=openai-sse-buffer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai-sse-buffer.d.ts","sourceRoot":"","sources":["../src/openai-sse-buffer.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kCAAkC,EAElC,aAAa,EACd,MAAM,2BAA2B,CAAC;AAEnC,KAAK,oBAAoB,GAAG,CAAC,KAAK,EAAE,MAAM,KAAK,KAAK,CAAC;AAErD,OAAO,EAAE,kCAAkC,IAAI,gCAAgC,EAAE,CAAC;AAClF,eAAO,MAAM,8BAA8B,QAA6C,CAAC;AAEzF,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,WAAW,EACpB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,UAAU,EACjB,OAAO,EAAE,oBAAoB,GAC5B,MAAM,CA2BR;AAED,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,WAAW,EACpB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,oBAAoB,GAC5B,MAAM,CAaR;AAED,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,oBAAoB,EAC7B,QAAQ,UAAQ,GACf,UAAU,CAAC,OAAO,aAAa,CAAC,CAQlC"}
@@ -0,0 +1,47 @@
1
+ import { MAX_PROVIDER_SSE_BUFFER_CODE_UNITS, parseFinalSseChunk, parseSseChunk, } from "veryfront/provider/shared";
2
+ export { MAX_PROVIDER_SSE_BUFFER_CODE_UNITS as MAX_OPENAI_SSE_BUFFER_CODE_UNITS };
3
+ export const MAX_OPENAI_SSE_RAW_CHUNK_BYTES = MAX_PROVIDER_SSE_BUFFER_CODE_UNITS * 3 + 3;
4
+ export function appendOpenAISseChunk(decoder, buffer, chunk, invalid) {
5
+ if (!(chunk instanceof Uint8Array)) {
6
+ throw invalid("stream chunk was not binary data");
7
+ }
8
+ const remainingCodeUnits = MAX_PROVIDER_SSE_BUFFER_CODE_UNITS - buffer.length;
9
+ // Valid UTF-8 needs at most three bytes per UTF-16 code unit. The extra
10
+ // three bytes cover an incomplete sequence retained by the decoder.
11
+ if (chunk.byteLength > MAX_OPENAI_SSE_RAW_CHUNK_BYTES ||
12
+ chunk.byteLength > remainingCodeUnits * 3 + 3) {
13
+ throw invalid(`SSE buffer exceeded ${MAX_PROVIDER_SSE_BUFFER_CODE_UNITS} code units`);
14
+ }
15
+ let decoded;
16
+ try {
17
+ decoded = decoder.decode(chunk, { stream: true });
18
+ }
19
+ catch {
20
+ throw invalid("stream contained invalid UTF-8");
21
+ }
22
+ if (decoded.length > MAX_PROVIDER_SSE_BUFFER_CODE_UNITS - buffer.length) {
23
+ throw invalid(`SSE buffer exceeded ${MAX_PROVIDER_SSE_BUFFER_CODE_UNITS} code units`);
24
+ }
25
+ return buffer + decoded;
26
+ }
27
+ export function finishOpenAISseDecoding(decoder, buffer, invalid) {
28
+ let decoded;
29
+ try {
30
+ decoded = decoder.decode();
31
+ }
32
+ catch {
33
+ throw invalid("stream contained invalid UTF-8");
34
+ }
35
+ if (decoded.length > MAX_PROVIDER_SSE_BUFFER_CODE_UNITS - buffer.length) {
36
+ throw invalid(`SSE buffer exceeded ${MAX_PROVIDER_SSE_BUFFER_CODE_UNITS} code units`);
37
+ }
38
+ return buffer + decoded;
39
+ }
40
+ export function parseOpenAISseBuffer(buffer, invalid, trailing = false) {
41
+ try {
42
+ return trailing ? parseFinalSseChunk(buffer) : parseSseChunk(buffer);
43
+ }
44
+ catch {
45
+ throw invalid(trailing ? "trailing SSE event was malformed" : "SSE event framing was malformed");
46
+ }
47
+ }
@@ -0,0 +1,6 @@
1
+ export declare const MAX_OPENAI_STREAM_IDENTIFIER_BYTES = 512;
2
+ export declare const MAX_OPENAI_STREAM_TOOL_NAME_BYTES = 256;
3
+ export declare const MAX_OPENAI_STREAM_ITEM_TYPE_BYTES = 128;
4
+ export declare const MAX_OPENAI_STREAM_CONTENT_PARTS = 4096;
5
+ export declare function isBoundedOpenAIStreamString(value: unknown, maxBytes: number): value is string;
6
+ //# sourceMappingURL=openai-stream-metadata.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai-stream-metadata.d.ts","sourceRoot":"","sources":["../src/openai-stream-metadata.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,kCAAkC,MAAM,CAAC;AACtD,eAAO,MAAM,iCAAiC,MAAM,CAAC;AACrD,eAAO,MAAM,iCAAiC,MAAM,CAAC;AACrD,eAAO,MAAM,+BAA+B,OAAQ,CAAC;AAIrD,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,OAAO,EACd,QAAQ,EAAE,MAAM,GACf,KAAK,IAAI,MAAM,CAIjB"}
@@ -0,0 +1,10 @@
1
+ export const MAX_OPENAI_STREAM_IDENTIFIER_BYTES = 512;
2
+ export const MAX_OPENAI_STREAM_TOOL_NAME_BYTES = 256;
3
+ export const MAX_OPENAI_STREAM_ITEM_TYPE_BYTES = 128;
4
+ export const MAX_OPENAI_STREAM_CONTENT_PARTS = 4_096;
5
+ const encoder = new TextEncoder();
6
+ export function isBoundedOpenAIStreamString(value, maxBytes) {
7
+ return typeof value === "string" &&
8
+ value.length > 0 &&
9
+ encoder.encode(value).byteLength <= maxBytes;
10
+ }
@@ -0,0 +1,10 @@
1
+ export declare const MAX_OPENAI_STREAM_TOOL_ARGUMENT_BYTES = 1048576;
2
+ export declare const MAX_OPENAI_STREAM_TOOL_ARGUMENT_FRAGMENTS = 4096;
3
+ export type OpenAIStreamToolArgumentBudget = {
4
+ bytes: number;
5
+ fragments: number;
6
+ };
7
+ export declare function appendOpenAIStreamToolArgument(budget: OpenAIStreamToolArgumentBudget, chunks: string[], fragment: string): "bytes" | "fragments" | undefined;
8
+ export declare function joinOpenAIStreamToolArguments(chunks: readonly string[]): string;
9
+ export declare function isJsonObjectText(value: string): boolean;
10
+ //# sourceMappingURL=openai-tool-input.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai-tool-input.d.ts","sourceRoot":"","sources":["../src/openai-tool-input.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,qCAAqC,UAAY,CAAC;AAC/D,eAAO,MAAM,yCAAyC,OAAQ,CAAC;AAE/D,MAAM,MAAM,8BAA8B,GAAG;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAIF,wBAAgB,8BAA8B,CAC5C,MAAM,EAAE,8BAA8B,EACtC,MAAM,EAAE,MAAM,EAAE,EAChB,QAAQ,EAAE,MAAM,GACf,OAAO,GAAG,WAAW,GAAG,SAAS,CAgBnC;AAED,wBAAgB,6BAA6B,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAE/E;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAOvD"}
@@ -0,0 +1,30 @@
1
+ export const MAX_OPENAI_STREAM_TOOL_ARGUMENT_BYTES = 1_048_576;
2
+ export const MAX_OPENAI_STREAM_TOOL_ARGUMENT_FRAGMENTS = 4_096;
3
+ const TOOL_ARGUMENT_ENCODER = new TextEncoder();
4
+ export function appendOpenAIStreamToolArgument(budget, chunks, fragment) {
5
+ if (budget.fragments >= MAX_OPENAI_STREAM_TOOL_ARGUMENT_FRAGMENTS) {
6
+ return "fragments";
7
+ }
8
+ const fragmentBytes = TOOL_ARGUMENT_ENCODER.encode(fragment).byteLength;
9
+ if (fragmentBytes > MAX_OPENAI_STREAM_TOOL_ARGUMENT_BYTES - budget.bytes) {
10
+ return "bytes";
11
+ }
12
+ budget.fragments++;
13
+ budget.bytes += fragmentBytes;
14
+ if (fragment.length > 0) {
15
+ chunks.push(fragment);
16
+ }
17
+ return undefined;
18
+ }
19
+ export function joinOpenAIStreamToolArguments(chunks) {
20
+ return chunks.join("");
21
+ }
22
+ export function isJsonObjectText(value) {
23
+ try {
24
+ const parsed = JSON.parse(value);
25
+ return parsed !== null && typeof parsed === "object" && !Array.isArray(parsed);
26
+ }
27
+ catch {
28
+ return false;
29
+ }
30
+ }
@@ -0,0 +1,35 @@
1
+ export declare const OPENAI_WEB_SEARCH_SOURCES_INCLUDE = "web_search_call.action.sources";
2
+ export declare const OPENAI_REASONING_ENCRYPTED_CONTENT_INCLUDE = "reasoning.encrypted_content";
3
+ export declare const MAX_OPENAI_RAW_RESPONSE_METADATA_BYTES: number;
4
+ export declare const MAX_OPENAI_RAW_RESPONSE_OUTPUT_ITEMS = 4096;
5
+ export declare const MAX_OPENAI_WEB_SEARCH_TEXT_BYTES: number;
6
+ export declare const MAX_OPENAI_WEB_SEARCH_URL_BYTES: number;
7
+ export declare const MAX_OPENAI_WEB_SEARCH_VALUES = 256;
8
+ type ProviderTool = {
9
+ type: "provider";
10
+ name: string;
11
+ id: `${string}.${string}`;
12
+ args: Record<string, unknown>;
13
+ };
14
+ export type OpenAIWebSearchDescriptor = {
15
+ name: string;
16
+ requestTool: Record<string, unknown>;
17
+ };
18
+ export type OpenAIWebSearchResult = {
19
+ id: string;
20
+ input: string;
21
+ result: Record<string, unknown>;
22
+ isError: boolean;
23
+ };
24
+ export declare function resolveOpenAIWebSearchDescriptor(tools: ReadonlyArray<ProviderTool | {
25
+ type: "function";
26
+ name: string;
27
+ description?: string;
28
+ inputSchema: unknown;
29
+ }> | undefined): OpenAIWebSearchDescriptor | undefined;
30
+ export declare function normalizeOpenAIWebSearchCall(item: Record<string, unknown>, invalid: (issue: string) => Error): OpenAIWebSearchResult;
31
+ export declare function validateOpenAIUrlCitation(value: unknown, invalid: (issue: string) => Error): Record<string, unknown>;
32
+ export declare function createOpenAIRawResponseMetadata(outputItems: Array<Record<string, unknown>>): Record<string, unknown>;
33
+ export declare function readOpenAIRawResponseOutputItems(providerMetadata: Record<string, unknown> | undefined): Array<Record<string, unknown>> | undefined;
34
+ export {};
35
+ //# sourceMappingURL=openai-web-search.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"openai-web-search.d.ts","sourceRoot":"","sources":["../src/openai-web-search.ts"],"names":[],"mappings":"AAeA,eAAO,MAAM,iCAAiC,mCAAmC,CAAC;AAClF,eAAO,MAAM,0CAA0C,gCAAgC,CAAC;AACxF,eAAO,MAAM,sCAAsC,QAAkB,CAAC;AACtE,eAAO,MAAM,oCAAoC,OAAQ,CAAC;AAC1D,eAAO,MAAM,gCAAgC,QAAY,CAAC;AAC1D,eAAO,MAAM,+BAA+B,QAAY,CAAC;AACzD,eAAO,MAAM,4BAA4B,MAAM,CAAC;AAIhD,KAAK,YAAY,GAAG;IAClB,IAAI,EAAE,UAAU,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,GAAG,MAAM,IAAI,MAAM,EAAE,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC;AA2CF,wBAAgB,gCAAgC,CAC9C,KAAK,EACD,aAAa,CACX,YAAY,GACZ;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,OAAO,CAAA;CAAE,CACjF,GACC,SAAS,GACZ,yBAAyB,GAAG,SAAS,CA+CvC;AAED,wBAAgB,4BAA4B,CAC1C,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,KAAK,GAChC,qBAAqB,CAuFvB;AAED,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,OAAO,EACd,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,KAAK,GAChC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAmBzB;AAED,wBAAgB,+BAA+B,CAC7C,WAAW,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAC1C,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAMzB;AAoED,wBAAgB,gCAAgC,CAC9C,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,GACpD,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,SAAS,CA6C5C"}
@@ -0,0 +1,369 @@
1
+ import { readRecord, snapshotJsonValue, stringifyJsonValue } from "veryfront/provider/shared";
2
+ import { isBoundedOpenAIStreamString, MAX_OPENAI_STREAM_IDENTIFIER_BYTES, MAX_OPENAI_STREAM_TOOL_NAME_BYTES, } from "./openai-stream-metadata.js";
3
+ import { isJsonObjectText, MAX_OPENAI_STREAM_TOOL_ARGUMENT_BYTES } from "./openai-tool-input.js";
4
+ const OPENAI_WEB_SEARCH_TYPES = new Set([
5
+ "web_search",
6
+ "web_search_2025_08_26",
7
+ "web_search_preview",
8
+ "web_search_preview_2025_03_11",
9
+ ]);
10
+ export const OPENAI_WEB_SEARCH_SOURCES_INCLUDE = "web_search_call.action.sources";
11
+ export const OPENAI_REASONING_ENCRYPTED_CONTENT_INCLUDE = "reasoning.encrypted_content";
12
+ export const MAX_OPENAI_RAW_RESPONSE_METADATA_BYTES = 8 * 1024 * 1024;
13
+ export const MAX_OPENAI_RAW_RESPONSE_OUTPUT_ITEMS = 4_096;
14
+ export const MAX_OPENAI_WEB_SEARCH_TEXT_BYTES = 64 * 1024;
15
+ export const MAX_OPENAI_WEB_SEARCH_URL_BYTES = 16 * 1024;
16
+ export const MAX_OPENAI_WEB_SEARCH_VALUES = 256;
17
+ const textEncoder = new TextEncoder();
18
+ function hasAsciiControlCharacter(value) {
19
+ for (let index = 0; index < value.length; index += 1) {
20
+ const codeUnit = value.charCodeAt(index);
21
+ if (codeUnit <= 0x1f || codeUnit === 0x7f) {
22
+ return true;
23
+ }
24
+ }
25
+ return false;
26
+ }
27
+ function isHttpUrl(value) {
28
+ if (typeof value !== "string" ||
29
+ value.length === 0 ||
30
+ value.trim() !== value ||
31
+ hasAsciiControlCharacter(value) ||
32
+ textEncoder.encode(value).byteLength > MAX_OPENAI_WEB_SEARCH_URL_BYTES) {
33
+ return false;
34
+ }
35
+ try {
36
+ const url = new URL(value);
37
+ return (url.protocol === "http:" || url.protocol === "https:") &&
38
+ url.hostname.length > 0 &&
39
+ url.username.length === 0 &&
40
+ url.password.length === 0;
41
+ }
42
+ catch {
43
+ return false;
44
+ }
45
+ }
46
+ function hasOnlyKeys(record, allowed) {
47
+ return Object.keys(record).every((key) => allowed.has(key));
48
+ }
49
+ function isBoundedText(value, allowEmpty = false) {
50
+ return typeof value === "string" &&
51
+ (allowEmpty || value.length > 0) &&
52
+ textEncoder.encode(value).byteLength <= MAX_OPENAI_WEB_SEARCH_TEXT_BYTES;
53
+ }
54
+ export function resolveOpenAIWebSearchDescriptor(tools) {
55
+ let resolved;
56
+ for (const tool of tools ?? []) {
57
+ if (tool.type !== "provider" ||
58
+ typeof tool.id !== "string" ||
59
+ !tool.id.startsWith("openai.")) {
60
+ continue;
61
+ }
62
+ const providerType = tool.id.slice("openai.".length);
63
+ if (!OPENAI_WEB_SEARCH_TYPES.has(providerType)) {
64
+ throw new TypeError(`Unsupported OpenAI provider tool: ${tool.id}`);
65
+ }
66
+ if (resolved) {
67
+ throw new TypeError("Only one OpenAI web-search provider tool is supported per request");
68
+ }
69
+ if (!isBoundedOpenAIStreamString(tool.name, MAX_OPENAI_STREAM_TOOL_NAME_BYTES)) {
70
+ throw new TypeError("OpenAI web-search tool name was malformed");
71
+ }
72
+ const args = readRecord(tool.args);
73
+ if (!args ||
74
+ !hasOnlyKeys(args, new Set(["searchContextSize"])) ||
75
+ (args.searchContextSize !== undefined &&
76
+ args.searchContextSize !== "low" &&
77
+ args.searchContextSize !== "medium" &&
78
+ args.searchContextSize !== "high")) {
79
+ throw new TypeError("OpenAI web-search tool arguments were unsupported");
80
+ }
81
+ resolved = {
82
+ name: tool.name,
83
+ requestTool: {
84
+ type: providerType,
85
+ ...(args.searchContextSize !== undefined
86
+ ? { search_context_size: args.searchContextSize }
87
+ : {}),
88
+ },
89
+ };
90
+ }
91
+ return resolved;
92
+ }
93
+ export function normalizeOpenAIWebSearchCall(item, invalid) {
94
+ if (!isBoundedOpenAIStreamString(item.id, MAX_OPENAI_STREAM_IDENTIFIER_BYTES) ||
95
+ item.type !== "web_search_call" ||
96
+ (item.status !== "completed" &&
97
+ item.status !== "failed" &&
98
+ item.status !== "incomplete")) {
99
+ throw invalid("web-search output item identity or status was malformed");
100
+ }
101
+ const action = readRecord(item.action);
102
+ if (!action || typeof action.type !== "string") {
103
+ throw invalid("web-search action was malformed");
104
+ }
105
+ let sources;
106
+ if (action.type === "search") {
107
+ if (!hasOnlyKeys(action, new Set(["type", "query", "queries", "sources"])) ||
108
+ (action.query !== undefined && !isBoundedText(action.query, true)) ||
109
+ (action.queries !== undefined &&
110
+ (!Array.isArray(action.queries) ||
111
+ action.queries.length > MAX_OPENAI_WEB_SEARCH_VALUES ||
112
+ action.queries.some((query) => !isBoundedText(query, true))))) {
113
+ throw invalid("web-search search action was malformed");
114
+ }
115
+ if (action.sources !== undefined) {
116
+ if (!Array.isArray(action.sources) ||
117
+ action.sources.length > MAX_OPENAI_WEB_SEARCH_VALUES) {
118
+ throw invalid("web-search sources were malformed");
119
+ }
120
+ sources = action.sources.map((source) => {
121
+ const record = readRecord(source);
122
+ if (!record ||
123
+ !hasOnlyKeys(record, new Set(["type", "url"])) ||
124
+ record.type !== "url" ||
125
+ !isHttpUrl(record.url)) {
126
+ throw invalid("web-search source was malformed");
127
+ }
128
+ return record;
129
+ });
130
+ }
131
+ }
132
+ else if (action.type === "open_page") {
133
+ if (!hasOnlyKeys(action, new Set(["type", "url"])) ||
134
+ (action.url !== undefined &&
135
+ action.url !== null &&
136
+ !isHttpUrl(action.url))) {
137
+ throw invalid("web-search open-page action was malformed");
138
+ }
139
+ }
140
+ else if (action.type === "find_in_page") {
141
+ if (!hasOnlyKeys(action, new Set(["type", "url", "pattern"])) ||
142
+ !isHttpUrl(action.url) ||
143
+ !isBoundedText(action.pattern)) {
144
+ throw invalid("web-search find-in-page action was malformed");
145
+ }
146
+ }
147
+ else {
148
+ throw invalid("web-search action type was unsupported");
149
+ }
150
+ const { sources: _sources, ...invocationAction } = action;
151
+ const input = stringifyJsonValue(invocationAction);
152
+ if (textEncoder.encode(input).byteLength >
153
+ MAX_OPENAI_STREAM_TOOL_ARGUMENT_BYTES) {
154
+ throw invalid("web-search action exceeded the tool-input limit");
155
+ }
156
+ return {
157
+ id: item.id,
158
+ input,
159
+ result: item.status === "completed"
160
+ ? {
161
+ status: "completed",
162
+ ...(sources !== undefined ? { sources } : {}),
163
+ }
164
+ : { status: item.status, action },
165
+ isError: item.status !== "completed",
166
+ };
167
+ }
168
+ export function validateOpenAIUrlCitation(value, invalid) {
169
+ const annotation = readRecord(value);
170
+ if (!annotation ||
171
+ !hasOnlyKeys(annotation, new Set(["type", "start_index", "end_index", "url", "title"])) ||
172
+ annotation.type !== "url_citation" ||
173
+ !Number.isSafeInteger(annotation.start_index) ||
174
+ annotation.start_index < 0 ||
175
+ !Number.isSafeInteger(annotation.end_index) ||
176
+ annotation.end_index <= annotation.start_index ||
177
+ !isHttpUrl(annotation.url) ||
178
+ !isBoundedText(annotation.title, true)) {
179
+ throw invalid("URL citation annotation was malformed");
180
+ }
181
+ return annotation;
182
+ }
183
+ export function createOpenAIRawResponseMetadata(outputItems) {
184
+ return {
185
+ openai: {
186
+ rawResponseOutputItems: snapshotOpenAIRawResponseOutputItems(outputItems),
187
+ },
188
+ };
189
+ }
190
+ function readOwnOpenAIMetadataValue(providerMetadata) {
191
+ let descriptor;
192
+ try {
193
+ descriptor = Object.getOwnPropertyDescriptor(providerMetadata, "openai");
194
+ }
195
+ catch {
196
+ throw new TypeError("OpenAI provider metadata could not be inspected");
197
+ }
198
+ if (descriptor === undefined)
199
+ return undefined;
200
+ if (!Object.hasOwn(descriptor, "value") || descriptor.enumerable !== true) {
201
+ throw new TypeError("OpenAI provider metadata was malformed");
202
+ }
203
+ return descriptor.value;
204
+ }
205
+ function readOwnRawOutputItemsValue(openaiMetadata) {
206
+ if (openaiMetadata === null ||
207
+ typeof openaiMetadata !== "object" ||
208
+ Array.isArray(openaiMetadata)) {
209
+ throw new TypeError("OpenAI raw response metadata was malformed");
210
+ }
211
+ let descriptor;
212
+ try {
213
+ descriptor = Object.getOwnPropertyDescriptor(openaiMetadata, "rawResponseOutputItems");
214
+ }
215
+ catch {
216
+ throw new TypeError("OpenAI raw response metadata could not be inspected");
217
+ }
218
+ if (descriptor === undefined)
219
+ return undefined;
220
+ if (!Object.hasOwn(descriptor, "value") || descriptor.enumerable !== true) {
221
+ throw new TypeError("OpenAI raw response metadata was malformed");
222
+ }
223
+ return descriptor.value;
224
+ }
225
+ function snapshotOpenAIRawResponseOutputItems(value) {
226
+ let snapshot;
227
+ try {
228
+ snapshot = snapshotJsonValue(value, {
229
+ maxBytes: MAX_OPENAI_RAW_RESPONSE_METADATA_BYTES,
230
+ });
231
+ }
232
+ catch (error) {
233
+ if (error instanceof TypeError &&
234
+ error.message ===
235
+ `Provider JSON snapshot exceeded ${MAX_OPENAI_RAW_RESPONSE_METADATA_BYTES} UTF-8 bytes`) {
236
+ throw new TypeError(`OpenAI raw response metadata exceeded ${MAX_OPENAI_RAW_RESPONSE_METADATA_BYTES} UTF-8 bytes`);
237
+ }
238
+ throw new TypeError("OpenAI raw response metadata was not valid bounded JSON");
239
+ }
240
+ if (!Array.isArray(snapshot)) {
241
+ throw new TypeError("OpenAI raw response metadata was malformed");
242
+ }
243
+ return snapshot;
244
+ }
245
+ export function readOpenAIRawResponseOutputItems(providerMetadata) {
246
+ if (!providerMetadata)
247
+ return undefined;
248
+ const openaiMetadata = readOwnOpenAIMetadataValue(providerMetadata);
249
+ if (openaiMetadata === undefined)
250
+ return undefined;
251
+ const rawOutputItems = readOwnRawOutputItemsValue(openaiMetadata);
252
+ if (rawOutputItems === undefined)
253
+ return undefined;
254
+ const snapshot = snapshotOpenAIRawResponseOutputItems(rawOutputItems);
255
+ if (snapshot.length === 0) {
256
+ throw new TypeError("OpenAI raw response metadata contained no output items");
257
+ }
258
+ if (snapshot.length > MAX_OPENAI_RAW_RESPONSE_OUTPUT_ITEMS) {
259
+ throw new TypeError("OpenAI raw response metadata contained too many output items");
260
+ }
261
+ const seenIds = new Set();
262
+ for (let index = 0; index < snapshot.length; index += 1) {
263
+ const value = snapshot[index];
264
+ const item = readRecord(value);
265
+ if (!item ||
266
+ !isBoundedOpenAIStreamString(item.id, MAX_OPENAI_STREAM_IDENTIFIER_BYTES) ||
267
+ (item.type !== "message" &&
268
+ item.type !== "reasoning" &&
269
+ item.type !== "function_call" &&
270
+ item.type !== "web_search_call") ||
271
+ seenIds.has(item.id)) {
272
+ throw new TypeError("OpenAI raw response metadata item was malformed");
273
+ }
274
+ const validStatus = item.status === undefined ||
275
+ item.status === "completed" ||
276
+ item.status === "incomplete" ||
277
+ (item.type === "web_search_call" && item.status === "failed");
278
+ if (!validStatus) {
279
+ throw new TypeError("OpenAI raw response metadata item was malformed");
280
+ }
281
+ try {
282
+ validateRawOutputItem(item);
283
+ }
284
+ catch {
285
+ throw new TypeError("OpenAI raw response metadata item was malformed");
286
+ }
287
+ seenIds.add(item.id);
288
+ }
289
+ return snapshot;
290
+ }
291
+ function validateRawOutputItem(item) {
292
+ if (item.type === "web_search_call") {
293
+ normalizeOpenAIWebSearchCall(item, (issue) => new TypeError(issue));
294
+ return;
295
+ }
296
+ if (item.type === "function_call") {
297
+ if (!isBoundedOpenAIStreamString(item.call_id, MAX_OPENAI_STREAM_IDENTIFIER_BYTES) ||
298
+ !isBoundedOpenAIStreamString(item.name, MAX_OPENAI_STREAM_TOOL_NAME_BYTES) ||
299
+ typeof item.arguments !== "string" ||
300
+ textEncoder.encode(item.arguments).byteLength >
301
+ MAX_OPENAI_STREAM_TOOL_ARGUMENT_BYTES ||
302
+ !isJsonObjectText(item.arguments)) {
303
+ throw new TypeError("function call was malformed");
304
+ }
305
+ return;
306
+ }
307
+ if (item.type === "message") {
308
+ if (item.role !== "assistant" ||
309
+ !Array.isArray(item.content) ||
310
+ item.content.length > MAX_OPENAI_RAW_RESPONSE_OUTPUT_ITEMS) {
311
+ throw new TypeError("message was malformed");
312
+ }
313
+ for (const value of item.content) {
314
+ const part = readRecord(value);
315
+ if (!part)
316
+ throw new TypeError("message part was malformed");
317
+ if (part.type === "output_text") {
318
+ const annotations = part.annotations ?? [];
319
+ if (typeof part.text !== "string" ||
320
+ !Array.isArray(annotations) ||
321
+ annotations.length > MAX_OPENAI_RAW_RESPONSE_OUTPUT_ITEMS) {
322
+ throw new TypeError("output text was malformed");
323
+ }
324
+ for (const annotation of annotations) {
325
+ const citation = validateOpenAIUrlCitation(annotation, (issue) => new TypeError(issue));
326
+ if (citation.end_index > part.text.length) {
327
+ throw new TypeError("citation exceeded output text");
328
+ }
329
+ }
330
+ }
331
+ else if (part.type === "refusal") {
332
+ if (typeof part.refusal !== "string" ||
333
+ part.annotations !== undefined) {
334
+ throw new TypeError("refusal was malformed");
335
+ }
336
+ }
337
+ else {
338
+ throw new TypeError("message part type was unsupported");
339
+ }
340
+ }
341
+ return;
342
+ }
343
+ if (item.type === "reasoning") {
344
+ validateRawReasoningParts(item.summary, "summary_text");
345
+ validateRawReasoningParts(item.content, "reasoning_text");
346
+ if (item.encrypted_content !== undefined &&
347
+ typeof item.encrypted_content !== "string") {
348
+ throw new TypeError("reasoning signature was malformed");
349
+ }
350
+ }
351
+ }
352
+ function validateRawReasoningParts(value, expectedType) {
353
+ if (value === undefined)
354
+ return;
355
+ if (!Array.isArray(value) ||
356
+ value.length > MAX_OPENAI_RAW_RESPONSE_OUTPUT_ITEMS) {
357
+ throw new TypeError("reasoning parts were malformed");
358
+ }
359
+ for (const rawPart of value) {
360
+ const part = readRecord(rawPart);
361
+ if (!part ||
362
+ part.type !== expectedType ||
363
+ typeof part.text !== "string" ||
364
+ (part.id !== undefined &&
365
+ !isBoundedOpenAIStreamString(part.id, MAX_OPENAI_STREAM_IDENTIFIER_BYTES))) {
366
+ throw new TypeError("reasoning part was malformed");
367
+ }
368
+ }
369
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@veryfront/ext-llm-openai",
3
- "version": "0.1.1185",
3
+ "version": "0.1.1189",
4
4
  "description": "Veryfront first-party extension package for ext-llm-openai",
5
5
  "keywords": [
6
6
  "veryfront",
@@ -27,7 +27,7 @@
27
27
  },
28
28
  "scripts": {},
29
29
  "engines": {
30
- "node": ">=18.0.0"
30
+ "node": ">=22.3.0"
31
31
  },
32
32
  "publishConfig": {
33
33
  "access": "public"
@@ -45,7 +45,7 @@
45
45
  "capabilities": []
46
46
  },
47
47
  "peerDependencies": {
48
- "veryfront": "^0.1.1185"
48
+ "veryfront": "^0.1.1189"
49
49
  },
50
50
  "type": "module",
51
51
  "types": "./esm/index.d.ts",