@langchain/google 0.1.6-dev-1773345797648 → 0.1.7-dev-1773698445534
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/CHANGELOG.md +10 -0
- package/dist/chat_models/base.cjs +10 -11
- package/dist/chat_models/base.cjs.map +1 -1
- package/dist/chat_models/base.js +3 -4
- package/dist/chat_models/base.js.map +1 -1
- package/dist/chat_models/index.cjs +3 -4
- package/dist/chat_models/index.cjs.map +1 -1
- package/dist/chat_models/index.js +1 -2
- package/dist/chat_models/index.js.map +1 -1
- package/dist/chat_models/node.cjs +5 -6
- package/dist/chat_models/node.cjs.map +1 -1
- package/dist/chat_models/node.js +1 -2
- package/dist/chat_models/node.js.map +1 -1
- package/dist/chat_models/profiles.cjs +1 -2
- package/dist/chat_models/profiles.cjs.map +1 -1
- package/dist/chat_models/profiles.js +1 -1
- package/dist/clients/index.cjs +5 -6
- package/dist/clients/index.cjs.map +1 -1
- package/dist/clients/index.js +1 -2
- package/dist/clients/index.js.map +1 -1
- package/dist/clients/node.cjs +6 -7
- package/dist/clients/node.cjs.map +1 -1
- package/dist/clients/node.js +1 -2
- package/dist/clients/node.js.map +1 -1
- package/dist/const.cjs +1 -2
- package/dist/const.cjs.map +1 -1
- package/dist/const.js +1 -1
- package/dist/converters/messages.cjs +24 -10
- package/dist/converters/messages.cjs.map +1 -1
- package/dist/converters/messages.js +22 -8
- package/dist/converters/messages.js.map +1 -1
- package/dist/converters/params.cjs +1 -2
- package/dist/converters/params.cjs.map +1 -1
- package/dist/converters/params.js +1 -1
- package/dist/converters/params.js.map +1 -1
- package/dist/converters/tools.cjs +2 -3
- package/dist/converters/tools.cjs.map +1 -1
- package/dist/converters/tools.js +1 -2
- package/dist/converters/tools.js.map +1 -1
- package/dist/index.cjs +4 -5
- package/dist/index.js +1 -2
- package/dist/node.cjs +4 -5
- package/dist/node.js +1 -2
- package/dist/utils/errors.cjs +19 -19
- package/dist/utils/errors.cjs.map +1 -1
- package/dist/utils/errors.js +18 -18
- package/dist/utils/errors.js.map +1 -1
- package/dist/utils/gcp-auth.cjs +3 -4
- package/dist/utils/gcp-auth.cjs.map +1 -1
- package/dist/utils/gcp-auth.js +1 -2
- package/dist/utils/gcp-auth.js.map +1 -1
- package/dist/utils/handler.cjs +1 -2
- package/dist/utils/handler.cjs.map +1 -1
- package/dist/utils/handler.js +1 -2
- package/dist/utils/handler.js.map +1 -1
- package/dist/utils/metadata.cjs +1 -2
- package/dist/utils/metadata.cjs.map +1 -1
- package/dist/utils/metadata.js +1 -1
- package/dist/utils/metadata.js.map +1 -1
- package/dist/utils/misc.cjs +1 -2
- package/dist/utils/misc.cjs.map +1 -1
- package/dist/utils/misc.js +1 -1
- package/dist/utils/stream.cjs +1 -2
- package/dist/utils/stream.cjs.map +1 -1
- package/dist/utils/stream.js +1 -1
- package/package.json +5 -5
package/dist/utils/errors.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","names":["ns","baseNs"],"sources":["../../src/utils/errors.ts"],"sourcesContent":["import { ns as baseNs, LangChainError } from \"@langchain/core/errors\";\nimport type { Gemini } from \"../chat_models/types.js\";\nimport { iife } from \"./misc.js\";\n\n// Internal namespace for all Google provider errors\nconst ns = baseNs.sub(\"google\");\n\n/**\n * Base error class for all Google provider errors.\n *\n * All Google-specific error classes extend this class. Use\n * `GoogleError.isInstance(obj)` to check if an object is any\n * Google provider error.\n *\n * @example\n * ```typescript\n * try {\n * await model.invoke(\"hello\");\n * } catch (error) {\n * if (GoogleError.isInstance(error)) {\n * console.log(\"Got a Google error:\", error.message);\n * }\n * }\n * ```\n */\nexport class GoogleError extends ns.brand(LangChainError) {\n readonly name: string = \"GoogleError\";\n}\n\n/**\n * Error thrown when there is a configuration issue with the Google provider.\n *\n * This error is thrown when the Google provider encounters invalid configuration\n * parameters or settings that prevent it from functioning correctly.\n *\n * @example\n * ```typescript\n * try {\n * const model = new ChatGoogle({ invalidOption: true });\n * } catch (error) {\n * if (ConfigurationError.isInstance(error)) {\n * console.log(`Configuration error: ${error.message}`);\n * }\n * }\n * ```\n */\nexport class ConfigurationError extends ns.brand(GoogleError, \"configuration\") {\n readonly name = \"ConfigurationError\";\n}\n\n/**\n * Parameters for constructing a PromptBlockedError\n */\ntype PromptBlockedErrorParams = {\n /**\n * The reason why the prompt was blocked (e.g., \"SAFETY\", \"OTHER\", \"UNKNOWN\")\n */\n blockReason: string;\n /**\n * Optional array of safety ratings that contributed to the block decision\n */\n safetyRatings?: Gemini.SafetyRating[];\n /**\n * Optional custom error message. If not provided, a default message will be generated\n */\n message?: string;\n};\n\n/**\n * Error thrown when a prompt is blocked by Google's safety filters.\n *\n * This error is thrown when the Gemini API blocks a prompt due to safety concerns.\n * It contains information about why the prompt was blocked and the safety ratings\n * that triggered the block.\n *\n * @example\n * ```typescript\n * try {\n * await model.invoke(\"some potentially unsafe prompt\");\n * } catch (error) {\n * if (PromptBlockedError.isInstance(error)) {\n * console.log(`Blocked due to: ${error.blockReason}`);\n * console.log(`Safety ratings:`, error.safetyRatings);\n * }\n * }\n * ```\n */\nexport class PromptBlockedError extends ns.brand(\n GoogleError,\n \"prompt-blocked\"\n) {\n readonly name = \"PromptBlockedError\";\n\n /**\n * The reason why the prompt was blocked.\n *\n * Common values include:\n * - \"SAFETY\" - Blocked due to safety concerns\n * - \"OTHER\" - Blocked for other reasons\n * - \"UNKNOWN\" - Reason is unknown\n *\n * @readonly\n */\n readonly blockReason: string;\n\n /**\n * Optional array of safety ratings that contributed to the block decision.\n *\n * Each rating contains information about different safety categories\n * (e.g., hate speech, sexually explicit content, dangerous content)\n * and their probability levels.\n *\n * @readonly\n */\n readonly safetyRatings?: Gemini.SafetyRating[];\n\n constructor(params: PromptBlockedErrorParams) {\n const message =\n params.message ??\n `Prompt was blocked: ${params.blockReason}.${\n params.safetyRatings && params.safetyRatings.length > 0\n ? ` Safety ratings: ${JSON.stringify(params.safetyRatings)}`\n : \"\"\n }`;\n\n super(message);\n this.blockReason = params.blockReason;\n this.safetyRatings = params.safetyRatings;\n }\n\n /**\n * Creates a PromptBlockedError from a Gemini API prompt feedback object.\n *\n * This is a convenience factory method for creating errors from the API response\n * when a prompt is blocked. It extracts the relevant information from the\n * promptFeedback object returned by the Gemini API.\n *\n * @param promptFeedback - The prompt feedback object from the Gemini API response\n * @returns A new PromptBlockedError instance with information from the feedback\n *\n * @example\n * ```typescript\n * const response = await geminiApi.generateContent(request);\n * if (response.promptFeedback?.blockReason) {\n * throw PromptBlockedError.fromPromptFeedback(response.promptFeedback);\n * }\n * ```\n */\n static fromPromptFeedback(\n promptFeedback: Gemini.PromptFeedback\n ): PromptBlockedError {\n return new PromptBlockedError({\n blockReason: promptFeedback.blockReason ?? \"UNKNOWN\",\n safetyRatings: promptFeedback.safetyRatings,\n });\n }\n}\n\n/**\n * Parameters for constructing an AuthError\n */\ntype AuthErrorParams = {\n /**\n * The error message describing what went wrong\n */\n message: string;\n\n /**\n * The HTTP status code of the failed response.\n * Common values include 400 (Bad Request), 401 (Unauthorized),\n * 403 (Forbidden), 404 (Not Found), 429 (Too Many Requests),\n * 500 (Internal Server Error), etc.\n */\n statusCode?: number;\n\n /**\n * The HTTP status text of the failed response.\n * This is the human-readable status message that accompanies the status code\n * (e.g., \"Bad Request\", \"Internal Server Error\").\n */\n statusText?: string;\n\n /**\n * The HTTP response headers from the failed request.\n * Stored as a key-value record for easy access to header information.\n */\n headers?: Record<string, string>;\n\n /**\n * The response body data from the failed request.\n * This may be a parsed JSON object, a text string, or null if the body\n * could not be read. Often contains additional error details from the API.\n */\n data?: unknown;\n};\n\n/**\n * Error class for authentication failures when communicating with Google APIs.\n *\n * This error is thrown when authentication with a Google API fails, typically\n * during the OAuth token exchange process or when using service account credentials.\n * It captures detailed information about the failed authentication attempt including\n * the status code, headers, and response body to aid in debugging and error handling.\n *\n * Common authentication failure scenarios include:\n * - Invalid or expired credentials\n * - Incorrect service account configuration\n * - Missing or invalid API keys\n * - Insufficient permissions or scopes\n * - Token exchange failures\n *\n * @example\n * ```typescript\n * try {\n * const token = await getAccessToken(credentials);\n * } catch (error) {\n * if (AuthError.isInstance(error)) {\n * console.error(`Auth failed with status ${error.statusCode}: ${error.message}`);\n * console.error('Response data:', error.data);\n * }\n * }\n * ```\n */\nexport class AuthError extends ns.brand(GoogleError, \"auth\") {\n readonly name = \"AuthError\" as const;\n\n /**\n * The HTTP status code of the failed response.\n * Common values include 400 (Bad Request), 401 (Unauthorized),\n * 403 (Forbidden), 404 (Not Found), 429 (Too Many Requests),\n * 500 (Internal Server Error), etc.\n */\n readonly statusCode?: number;\n\n /**\n * The HTTP status text of the failed response.\n * This is the human-readable status message that accompanies the status code\n * (e.g., \"Bad Request\", \"Internal Server Error\").\n */\n readonly statusText?: string;\n\n /**\n * The HTTP response headers from the failed request.\n * Stored as a key-value record for easy access to header information.\n */\n readonly headers?: Record<string, string>;\n\n /**\n * The response body data from the failed request.\n * This may be a parsed JSON object, a text string, or null if the body\n * could not be read. Often contains additional error details from the API.\n */\n readonly data?: unknown;\n\n constructor(params: AuthErrorParams) {\n super(params.message);\n\n this.statusCode = params.statusCode;\n this.statusText = params.statusText;\n this.headers = params.headers;\n this.data = params.data;\n }\n\n /**\n * Creates an AuthError from a failed HTTP response.\n *\n * This is a convenience factory method for creating authentication errors from\n * HTTP responses. It automatically extracts the error message, status code,\n * headers, and response body from the Response object.\n *\n * The method attempts to parse the response body as JSON to extract an\n * `error_description` field if available. If JSON parsing fails or the\n * content type is not JSON, it falls back to reading the response as text.\n * If both fail, the error body will be null.\n *\n * @param response - The failed HTTP Response object from the authentication request\n * @returns A Promise that resolves to a new AuthError instance with information from the response\n *\n * @example\n * ```typescript\n * const response = await fetch(tokenUrl, {\n * method: 'POST',\n * body: authRequestBody\n * });\n *\n * if (!response.ok) {\n * throw await AuthError.fromResponse(response);\n * }\n * ```\n */\n static async fromResponse(response: Response): Promise<AuthError> {\n const errorBody = await iife(async () => {\n try {\n const contentType = response.headers.get(\"content-type\") ?? \"\";\n if (contentType.includes(\"application/json\")) {\n return await response.json();\n }\n return await response.text();\n } catch {\n return null;\n }\n });\n\n const message =\n errorBody?.error_description ??\n `Authentication failed with status code ${response.status}`;\n\n const headers = iife(() => {\n const object: Record<string, string> = {};\n response.headers.forEach((value, name) => {\n object[name] = value;\n });\n return object;\n });\n\n return new AuthError({\n message,\n statusCode: response.status,\n statusText: response.statusText,\n headers,\n data: errorBody,\n });\n }\n}\n\nconst RETRYABLE_STATUS_CODES = [\n 408, // Request Timeout\n 429, // Too Many Requests\n 500, // Internal Server Error\n 502, // Bad Gateway\n 503, // Service Unavailable\n 504, // Gateway Timeout\n];\n\n/**\n * Parameters for constructing a RequestError\n */\ntype RequestErrorParams = {\n /**\n * The error message describing what went wrong\n */\n message: string;\n\n /**\n * The URL of the failed request.\n * This is the full URL that was being accessed when the error occurred.\n */\n url: string;\n\n /**\n * The HTTP status code of the failed response.\n * Common values include 400 (Bad Request), 401 (Unauthorized),\n * 403 (Forbidden), 404 (Not Found), 429 (Too Many Requests),\n * 500 (Internal Server Error), etc.\n */\n statusCode?: number;\n\n /**\n * The HTTP status text of the failed response.\n * This is the human-readable status message that accompanies the status code\n * (e.g., \"Bad Request\", \"Internal Server Error\").\n */\n statusText?: string;\n\n /**\n * The HTTP response headers from the failed request.\n * Stored as a key-value record for easy access to header information.\n */\n headers?: Record<string, string>;\n\n /**\n * The response body data from the failed request.\n * This may be a parsed JSON object, a text string, or null if the body\n * could not be read. Often contains additional error details from the API.\n */\n data?: unknown;\n};\n\n/**\n * Error class for HTTP request failures when communicating with Google APIs.\n *\n * This error is thrown when an HTTP request to a Google API endpoint fails.\n * It captures detailed information about the failed request including the URL,\n * status code, headers, and response body to aid in debugging and error handling.\n *\n * The error includes a `isRetryable()` method to determine if the request should\n * be retried based on the HTTP status code (e.g., 429 Too Many Requests, 503 Service Unavailable).\n *\n * @example\n * ```typescript\n * try {\n * const response = await apiClient.fetch(request);\n * if (!response.ok) {\n * throw await RequestError.fromResponse(response);\n * }\n * } catch (error) {\n * if (RequestError.isInstance(error) && error.isRetryable()) {\n * // Retry the request\n * }\n * }\n * ```\n */\nexport class RequestError extends ns.brand(GoogleError, \"request\") {\n readonly name = \"RequestError\" as const;\n\n /**\n * The URL of the failed request.\n * This is the full URL that was being accessed when the error occurred.\n */\n readonly url: string;\n\n /**\n * The HTTP status code of the failed response.\n * Common values include 400 (Bad Request), 401 (Unauthorized),\n * 403 (Forbidden), 404 (Not Found), 429 (Too Many Requests),\n * 500 (Internal Server Error), etc.\n */\n readonly statusCode?: number;\n\n /**\n * The HTTP status text of the failed response.\n * This is the human-readable status message that accompanies the status code\n * (e.g., \"Bad Request\", \"Internal Server Error\").\n */\n readonly statusText?: string;\n\n /**\n * The HTTP response headers from the failed request.\n * Stored as a key-value record for easy access to header information.\n */\n readonly headers?: Record<string, string>;\n\n /**\n * The response body data from the failed request.\n * This may be a parsed JSON object, a text string, or null if the body\n * could not be read. Often contains additional error details from the API.\n */\n readonly data?: unknown;\n\n constructor(params: RequestErrorParams) {\n super(params.message);\n\n this.url = params.url;\n this.statusCode = params.statusCode;\n this.statusText = params.statusText;\n this.headers = params.headers;\n this.data = params.data;\n }\n\n /**\n * Determines if this error represents a retryable request failure.\n *\n * A request is considered retryable if it failed with a status code that\n * indicates a temporary issue that might succeed on retry. This includes:\n * - 408 (Request Timeout)\n * - 429 (Too Many Requests / Rate Limiting)\n * - 500 (Internal Server Error)\n * - 502 (Bad Gateway)\n * - 503 (Service Unavailable)\n * - 504 (Gateway Timeout)\n *\n * @returns `true` if the request should be retried, `false` otherwise\n *\n * @example\n * ```typescript\n * if (RequestError.isInstance(error) && error.isRetryable()) {\n * await sleep(1000);\n * return retry(request);\n * }\n * ```\n */\n isRetryable(): boolean {\n if (!this.statusCode) return false;\n return RETRYABLE_STATUS_CODES.includes(this.statusCode);\n }\n\n /**\n * Creates a RequestError from a failed HTTP Response object.\n *\n * This is a factory method that extracts all relevant information from a\n * Response object and constructs a RequestError with appropriate details.\n * It attempts to parse the response body as JSON first, falling back to\n * text if that fails, and finally to null if the body cannot be read.\n *\n * The error message is extracted from the response body if available\n * (looking for `message` or `error` fields), otherwise a generic message\n * is constructed using the status code.\n *\n * @param response - The failed HTTP Response object to convert into an error\n * @returns A Promise that resolves to a new RequestError instance\n *\n * @example\n * ```typescript\n * const response = await fetch(url);\n * if (!response.ok) {\n * throw await RequestError.fromResponse(response);\n * }\n * ```\n */\n static async fromResponse(response: Response): Promise<RequestError> {\n const errorBody = await iife(async () => {\n try {\n const contentType = response.headers.get(\"content-type\") ?? \"\";\n if (contentType.includes(\"application/json\")) {\n return await response.json();\n }\n return await response.text();\n } catch {\n return null;\n }\n });\n\n const message =\n errorBody?.error?.message ??\n errorBody?.message ??\n errorBody?.error ??\n `Request failed with status code ${response.status}`;\n\n const headers = iife(() => {\n const object: Record<string, string> = {};\n response.headers.forEach((value, name) => {\n object[name] = value;\n });\n return object;\n });\n\n return new RequestError({\n message,\n url: response.url,\n statusCode: response.status,\n statusText: response.statusText,\n headers,\n data: errorBody,\n });\n }\n}\n\n/**\n * Error thrown when the Gemini API returns no candidates in the response.\n *\n * This error is thrown when the API successfully processes a request but returns\n * an empty candidates array. This can occur when:\n * - The prompt was blocked by safety filters (but no explicit block reason was provided)\n * - An internal error occurred during generation\n * - The model was unable to generate a valid response\n *\n * @example\n * ```typescript\n * try {\n * await model.invoke(\"some prompt\");\n * } catch (error) {\n * if (NoCandidatesError.isInstance(error)) {\n * console.log(\"No response candidates were generated\");\n * }\n * }\n * ```\n */\nexport class NoCandidatesError extends ns.brand(GoogleError, \"no-candidates\") {\n readonly name = \"NoCandidatesError\";\n\n constructor() {\n super(\n \"No candidates returned from API. This may indicate the prompt was blocked or an error occurred.\"\n );\n }\n}\n\n/**\n * Error thrown when an invalid tool is provided to the tool converter.\n *\n * This error is thrown when a tool cannot be converted to Gemini's function\n * declaration format. Tools must be one of:\n * - LangChain structured tools (with Zod schemas)\n * - OpenAI format tools (with JSON Schema)\n * - Gemini-native function declarations\n * - Gemini-native tools (codeExecution, googleSearchRetrieval)\n *\n * @example\n * ```typescript\n * try {\n * convertToolsToGeminiFunctionDeclarations([invalidTool]);\n * } catch (error) {\n * if (InvalidToolError.isInstance(error)) {\n * console.log(`Invalid tool: ${error.tool}`);\n * console.log(`Expected formats: LangChain tools, OpenAI tools, or Gemini function declarations`);\n * }\n * }\n * ```\n */\nexport class InvalidToolError extends ns.brand(GoogleError, \"invalid-tool\") {\n readonly name = \"InvalidToolError\";\n\n /**\n * The invalid tool that was provided.\n */\n readonly tool: unknown;\n\n constructor(tool: unknown, message?: string) {\n const defaultMessage = `Received invalid tool: ${JSON.stringify(\n tool\n )}. Tools must be LangChain tools, OpenAI tools, or Gemini function declarations.`;\n\n super(message ?? defaultMessage);\n this.tool = tool;\n }\n}\n\n/**\n * Error thrown when a ToolMessage references a tool call that cannot be found.\n *\n * This error is thrown when a ToolMessage is passed to ChatGoogle but there is no\n * corresponding AIMessage with a matching toolCallId in the conversation history.\n * This typically indicates that:\n * - The tool call ID in the ToolMessage doesn't match any previous tool calls\n * - The AIMessage containing the tool call was not included in the message history\n * - The tool call ID was incorrectly set or modified\n *\n * @example\n * ```typescript\n * try {\n * await model.invoke([\n * new ToolMessage({ content: \"result\", tool_call_id: \"nonexistent-id\" })\n * ]);\n * } catch (error) {\n * if (ToolCallNotFoundError.isInstance(error)) {\n * console.log(`Tool call not found: ${error.toolCallId}`);\n * }\n * }\n * ```\n */\nexport class ToolCallNotFoundError extends ns.brand(\n GoogleError,\n \"tool-call-not-found\"\n) {\n readonly name = \"ToolCallNotFoundError\";\n\n /**\n * The tool call ID that could not be found in the conversation history.\n */\n readonly toolCallId: string;\n\n constructor(toolCallId: string, message?: string) {\n const defaultMessage = `Tool call with ID \"${toolCallId}\" not found in conversation history. Ensure the corresponding AIMessage with this tool call ID is included in the input.`;\n\n super(message ?? defaultMessage);\n this.toolCallId = toolCallId;\n }\n}\n\n/**\n * Parameters for constructing a MalformedOutputError\n */\ntype MalformedOutputErrorParams = {\n /**\n * The error message describing what went wrong during parsing\n */\n message: string;\n /**\n * Optional cause of the parsing error (e.g., the original error that occurred)\n */\n cause?: unknown;\n};\n\n/**\n * Error thrown when output parsing fails in structured output methods.\n *\n * This error is thrown when the model's response cannot be parsed according to\n * the expected schema or format. Common causes include:\n * - Invalid message type (not an AIMessage or AIMessageChunk)\n * - Missing or empty content in the response\n * - Malformed JSON that cannot be parsed\n * - Schema validation failures\n *\n * @example\n * ```typescript\n * try {\n * const result = await model.withStructuredOutput(schema).invoke(\"extract data\");\n * } catch (error) {\n * if (MalformedOutputError.isInstance(error)) {\n * console.log(`Failed to parse output: ${error.message}`);\n * if (error.cause) {\n * console.log(`Cause:`, error.cause);\n * }\n * }\n * }\n * ```\n */\nexport class MalformedOutputError extends ns.brand(\n GoogleError,\n \"malformed-output\"\n) {\n readonly name = \"MalformedOutputError\";\n\n /**\n * Optional cause of the parsing error.\n * This may be the original error that occurred during parsing,\n * or additional context about what went wrong.\n */\n readonly cause?: unknown;\n\n constructor(params: MalformedOutputErrorParams) {\n super(params.message);\n this.cause = params.cause;\n }\n}\n\n/**\n * Error thrown when input provided to the Google provider is invalid.\n *\n * This error is thrown when the user provides input that cannot be\n * processed by Gemini, such as:\n * - Union types in tool/function schemas\n * - Null-only types in schemas\n * - Unsupported content block source types\n * - Missing required fields in content blocks\n *\n * @example\n * ```typescript\n * try {\n * const result = schemaToGeminiParameters(unionTypeSchema);\n * } catch (error) {\n * if (InvalidInputError.isInstance(error)) {\n * console.log(`Invalid input: ${error.message}`);\n * }\n * }\n * ```\n */\nexport class InvalidInputError extends ns.brand(GoogleError, \"invalid-input\") {\n readonly name = \"InvalidInputError\";\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"errors.js","names":["ns","baseNs"],"sources":["../../src/utils/errors.ts"],"sourcesContent":["import { ns as baseNs, LangChainError } from \"@langchain/core/errors\";\nimport type { Gemini } from \"../chat_models/types.js\";\nimport { iife } from \"./misc.js\";\n\n// Internal namespace for all Google provider errors\nconst ns = baseNs.sub(\"google\");\n\nasync function readErrorResponseBody(response: Response): Promise<unknown> {\n return iife(async () => {\n try {\n const contentType = response.headers.get(\"content-type\") ?? \"\";\n if (contentType.includes(\"application/json\")) {\n return await response.json();\n }\n\n const text = await response.text();\n try {\n return JSON.parse(text);\n } catch {\n return text;\n }\n } catch {\n return null;\n }\n });\n}\n\n/**\n * Base error class for all Google provider errors.\n *\n * All Google-specific error classes extend this class. Use\n * `GoogleError.isInstance(obj)` to check if an object is any\n * Google provider error.\n *\n * @example\n * ```typescript\n * try {\n * await model.invoke(\"hello\");\n * } catch (error) {\n * if (GoogleError.isInstance(error)) {\n * console.log(\"Got a Google error:\", error.message);\n * }\n * }\n * ```\n */\nexport class GoogleError extends ns.brand(LangChainError) {\n readonly name: string = \"GoogleError\";\n}\n\n/**\n * Error thrown when there is a configuration issue with the Google provider.\n *\n * This error is thrown when the Google provider encounters invalid configuration\n * parameters or settings that prevent it from functioning correctly.\n *\n * @example\n * ```typescript\n * try {\n * const model = new ChatGoogle({ invalidOption: true });\n * } catch (error) {\n * if (ConfigurationError.isInstance(error)) {\n * console.log(`Configuration error: ${error.message}`);\n * }\n * }\n * ```\n */\nexport class ConfigurationError extends ns.brand(GoogleError, \"configuration\") {\n readonly name = \"ConfigurationError\";\n}\n\n/**\n * Parameters for constructing a PromptBlockedError\n */\ntype PromptBlockedErrorParams = {\n /**\n * The reason why the prompt was blocked (e.g., \"SAFETY\", \"OTHER\", \"UNKNOWN\")\n */\n blockReason: string;\n /**\n * Optional array of safety ratings that contributed to the block decision\n */\n safetyRatings?: Gemini.SafetyRating[];\n /**\n * Optional custom error message. If not provided, a default message will be generated\n */\n message?: string;\n};\n\n/**\n * Error thrown when a prompt is blocked by Google's safety filters.\n *\n * This error is thrown when the Gemini API blocks a prompt due to safety concerns.\n * It contains information about why the prompt was blocked and the safety ratings\n * that triggered the block.\n *\n * @example\n * ```typescript\n * try {\n * await model.invoke(\"some potentially unsafe prompt\");\n * } catch (error) {\n * if (PromptBlockedError.isInstance(error)) {\n * console.log(`Blocked due to: ${error.blockReason}`);\n * console.log(`Safety ratings:`, error.safetyRatings);\n * }\n * }\n * ```\n */\nexport class PromptBlockedError extends ns.brand(\n GoogleError,\n \"prompt-blocked\"\n) {\n readonly name = \"PromptBlockedError\";\n\n /**\n * The reason why the prompt was blocked.\n *\n * Common values include:\n * - \"SAFETY\" - Blocked due to safety concerns\n * - \"OTHER\" - Blocked for other reasons\n * - \"UNKNOWN\" - Reason is unknown\n *\n * @readonly\n */\n readonly blockReason: string;\n\n /**\n * Optional array of safety ratings that contributed to the block decision.\n *\n * Each rating contains information about different safety categories\n * (e.g., hate speech, sexually explicit content, dangerous content)\n * and their probability levels.\n *\n * @readonly\n */\n readonly safetyRatings?: Gemini.SafetyRating[];\n\n constructor(params: PromptBlockedErrorParams) {\n const message =\n params.message ??\n `Prompt was blocked: ${params.blockReason}.${\n params.safetyRatings && params.safetyRatings.length > 0\n ? ` Safety ratings: ${JSON.stringify(params.safetyRatings)}`\n : \"\"\n }`;\n\n super(message);\n this.blockReason = params.blockReason;\n this.safetyRatings = params.safetyRatings;\n }\n\n /**\n * Creates a PromptBlockedError from a Gemini API prompt feedback object.\n *\n * This is a convenience factory method for creating errors from the API response\n * when a prompt is blocked. It extracts the relevant information from the\n * promptFeedback object returned by the Gemini API.\n *\n * @param promptFeedback - The prompt feedback object from the Gemini API response\n * @returns A new PromptBlockedError instance with information from the feedback\n *\n * @example\n * ```typescript\n * const response = await geminiApi.generateContent(request);\n * if (response.promptFeedback?.blockReason) {\n * throw PromptBlockedError.fromPromptFeedback(response.promptFeedback);\n * }\n * ```\n */\n static fromPromptFeedback(\n promptFeedback: Gemini.PromptFeedback\n ): PromptBlockedError {\n return new PromptBlockedError({\n blockReason: promptFeedback.blockReason ?? \"UNKNOWN\",\n safetyRatings: promptFeedback.safetyRatings,\n });\n }\n}\n\n/**\n * Parameters for constructing an AuthError\n */\ntype AuthErrorParams = {\n /**\n * The error message describing what went wrong\n */\n message: string;\n\n /**\n * The HTTP status code of the failed response.\n * Common values include 400 (Bad Request), 401 (Unauthorized),\n * 403 (Forbidden), 404 (Not Found), 429 (Too Many Requests),\n * 500 (Internal Server Error), etc.\n */\n statusCode?: number;\n\n /**\n * The HTTP status text of the failed response.\n * This is the human-readable status message that accompanies the status code\n * (e.g., \"Bad Request\", \"Internal Server Error\").\n */\n statusText?: string;\n\n /**\n * The HTTP response headers from the failed request.\n * Stored as a key-value record for easy access to header information.\n */\n headers?: Record<string, string>;\n\n /**\n * The response body data from the failed request.\n * This may be a parsed JSON object, a text string, or null if the body\n * could not be read. Often contains additional error details from the API.\n */\n data?: unknown;\n};\n\n/**\n * Error class for authentication failures when communicating with Google APIs.\n *\n * This error is thrown when authentication with a Google API fails, typically\n * during the OAuth token exchange process or when using service account credentials.\n * It captures detailed information about the failed authentication attempt including\n * the status code, headers, and response body to aid in debugging and error handling.\n *\n * Common authentication failure scenarios include:\n * - Invalid or expired credentials\n * - Incorrect service account configuration\n * - Missing or invalid API keys\n * - Insufficient permissions or scopes\n * - Token exchange failures\n *\n * @example\n * ```typescript\n * try {\n * const token = await getAccessToken(credentials);\n * } catch (error) {\n * if (AuthError.isInstance(error)) {\n * console.error(`Auth failed with status ${error.statusCode}: ${error.message}`);\n * console.error('Response data:', error.data);\n * }\n * }\n * ```\n */\nexport class AuthError extends ns.brand(GoogleError, \"auth\") {\n readonly name = \"AuthError\" as const;\n\n /**\n * The HTTP status code of the failed response.\n * Common values include 400 (Bad Request), 401 (Unauthorized),\n * 403 (Forbidden), 404 (Not Found), 429 (Too Many Requests),\n * 500 (Internal Server Error), etc.\n */\n readonly statusCode?: number;\n\n /**\n * The HTTP status text of the failed response.\n * This is the human-readable status message that accompanies the status code\n * (e.g., \"Bad Request\", \"Internal Server Error\").\n */\n readonly statusText?: string;\n\n /**\n * The HTTP response headers from the failed request.\n * Stored as a key-value record for easy access to header information.\n */\n readonly headers?: Record<string, string>;\n\n /**\n * The response body data from the failed request.\n * This may be a parsed JSON object, a text string, or null if the body\n * could not be read. Often contains additional error details from the API.\n */\n readonly data?: unknown;\n\n constructor(params: AuthErrorParams) {\n super(params.message);\n\n this.statusCode = params.statusCode;\n this.statusText = params.statusText;\n this.headers = params.headers;\n this.data = params.data;\n }\n\n /**\n * Creates an AuthError from a failed HTTP response.\n *\n * This is a convenience factory method for creating authentication errors from\n * HTTP responses. It automatically extracts the error message, status code,\n * headers, and response body from the Response object.\n *\n * The method attempts to parse the response body as JSON to extract an\n * `error_description` field if available. If JSON parsing fails or the\n * content type is not JSON, it falls back to reading the response as text.\n * If both fail, the error body will be null.\n *\n * @param response - The failed HTTP Response object from the authentication request\n * @returns A Promise that resolves to a new AuthError instance with information from the response\n *\n * @example\n * ```typescript\n * const response = await fetch(tokenUrl, {\n * method: 'POST',\n * body: authRequestBody\n * });\n *\n * if (!response.ok) {\n * throw await AuthError.fromResponse(response);\n * }\n * ```\n */\n static async fromResponse(response: Response): Promise<AuthError> {\n const errorBody = await readErrorResponseBody(response);\n\n const message =\n errorBody?.error_description ??\n `Authentication failed with status code ${response.status}`;\n\n const headers = iife(() => {\n const object: Record<string, string> = {};\n response.headers.forEach((value, name) => {\n object[name] = value;\n });\n return object;\n });\n\n return new AuthError({\n message,\n statusCode: response.status,\n statusText: response.statusText,\n headers,\n data: errorBody,\n });\n }\n}\n\nconst RETRYABLE_STATUS_CODES = [\n 408, // Request Timeout\n 429, // Too Many Requests\n 500, // Internal Server Error\n 502, // Bad Gateway\n 503, // Service Unavailable\n 504, // Gateway Timeout\n];\n\n/**\n * Parameters for constructing a RequestError\n */\ntype RequestErrorParams = {\n /**\n * The error message describing what went wrong\n */\n message: string;\n\n /**\n * The URL of the failed request.\n * This is the full URL that was being accessed when the error occurred.\n */\n url: string;\n\n /**\n * The HTTP status code of the failed response.\n * Common values include 400 (Bad Request), 401 (Unauthorized),\n * 403 (Forbidden), 404 (Not Found), 429 (Too Many Requests),\n * 500 (Internal Server Error), etc.\n */\n statusCode?: number;\n\n /**\n * The HTTP status text of the failed response.\n * This is the human-readable status message that accompanies the status code\n * (e.g., \"Bad Request\", \"Internal Server Error\").\n */\n statusText?: string;\n\n /**\n * The HTTP response headers from the failed request.\n * Stored as a key-value record for easy access to header information.\n */\n headers?: Record<string, string>;\n\n /**\n * The response body data from the failed request.\n * This may be a parsed JSON object, a text string, or null if the body\n * could not be read. Often contains additional error details from the API.\n */\n data?: unknown;\n};\n\n/**\n * Error class for HTTP request failures when communicating with Google APIs.\n *\n * This error is thrown when an HTTP request to a Google API endpoint fails.\n * It captures detailed information about the failed request including the URL,\n * status code, headers, and response body to aid in debugging and error handling.\n *\n * The error includes a `isRetryable()` method to determine if the request should\n * be retried based on the HTTP status code (e.g., 429 Too Many Requests, 503 Service Unavailable).\n *\n * @example\n * ```typescript\n * try {\n * const response = await apiClient.fetch(request);\n * if (!response.ok) {\n * throw await RequestError.fromResponse(response);\n * }\n * } catch (error) {\n * if (RequestError.isInstance(error) && error.isRetryable()) {\n * // Retry the request\n * }\n * }\n * ```\n */\nexport class RequestError extends ns.brand(GoogleError, \"request\") {\n readonly name = \"RequestError\" as const;\n\n /**\n * The URL of the failed request.\n * This is the full URL that was being accessed when the error occurred.\n */\n readonly url: string;\n\n /**\n * The HTTP status code of the failed response.\n * Common values include 400 (Bad Request), 401 (Unauthorized),\n * 403 (Forbidden), 404 (Not Found), 429 (Too Many Requests),\n * 500 (Internal Server Error), etc.\n */\n readonly statusCode?: number;\n\n /**\n * The HTTP status text of the failed response.\n * This is the human-readable status message that accompanies the status code\n * (e.g., \"Bad Request\", \"Internal Server Error\").\n */\n readonly statusText?: string;\n\n /**\n * The HTTP response headers from the failed request.\n * Stored as a key-value record for easy access to header information.\n */\n readonly headers?: Record<string, string>;\n\n /**\n * The response body data from the failed request.\n * This may be a parsed JSON object, a text string, or null if the body\n * could not be read. Often contains additional error details from the API.\n */\n readonly data?: unknown;\n\n constructor(params: RequestErrorParams) {\n super(params.message);\n\n this.url = params.url;\n this.statusCode = params.statusCode;\n this.statusText = params.statusText;\n this.headers = params.headers;\n this.data = params.data;\n }\n\n /**\n * Determines if this error represents a retryable request failure.\n *\n * A request is considered retryable if it failed with a status code that\n * indicates a temporary issue that might succeed on retry. This includes:\n * - 408 (Request Timeout)\n * - 429 (Too Many Requests / Rate Limiting)\n * - 500 (Internal Server Error)\n * - 502 (Bad Gateway)\n * - 503 (Service Unavailable)\n * - 504 (Gateway Timeout)\n *\n * @returns `true` if the request should be retried, `false` otherwise\n *\n * @example\n * ```typescript\n * if (RequestError.isInstance(error) && error.isRetryable()) {\n * await sleep(1000);\n * return retry(request);\n * }\n * ```\n */\n isRetryable(): boolean {\n if (!this.statusCode) return false;\n return RETRYABLE_STATUS_CODES.includes(this.statusCode);\n }\n\n /**\n * Creates a RequestError from a failed HTTP Response object.\n *\n * This is a factory method that extracts all relevant information from a\n * Response object and constructs a RequestError with appropriate details.\n * It attempts to parse the response body as JSON first, falling back to\n * text if that fails, and finally to null if the body cannot be read.\n *\n * The error message is extracted from the response body if available\n * (looking for `message` or `error` fields), otherwise a generic message\n * is constructed using the status code.\n *\n * @param response - The failed HTTP Response object to convert into an error\n * @returns A Promise that resolves to a new RequestError instance\n *\n * @example\n * ```typescript\n * const response = await fetch(url);\n * if (!response.ok) {\n * throw await RequestError.fromResponse(response);\n * }\n * ```\n */\n static async fromResponse(response: Response): Promise<RequestError> {\n const errorBody = await readErrorResponseBody(response);\n\n const message =\n errorBody?.error?.message ??\n errorBody?.message ??\n errorBody?.error ??\n `Request failed with status code ${response.status}`;\n\n const headers = iife(() => {\n const object: Record<string, string> = {};\n response.headers.forEach((value, name) => {\n object[name] = value;\n });\n return object;\n });\n\n return new RequestError({\n message,\n url: response.url,\n statusCode: response.status,\n statusText: response.statusText,\n headers,\n data: errorBody,\n });\n }\n}\n\n/**\n * Error thrown when the Gemini API returns no candidates in the response.\n *\n * This error is thrown when the API successfully processes a request but returns\n * an empty candidates array. This can occur when:\n * - The prompt was blocked by safety filters (but no explicit block reason was provided)\n * - An internal error occurred during generation\n * - The model was unable to generate a valid response\n *\n * @example\n * ```typescript\n * try {\n * await model.invoke(\"some prompt\");\n * } catch (error) {\n * if (NoCandidatesError.isInstance(error)) {\n * console.log(\"No response candidates were generated\");\n * }\n * }\n * ```\n */\nexport class NoCandidatesError extends ns.brand(GoogleError, \"no-candidates\") {\n readonly name = \"NoCandidatesError\";\n\n constructor() {\n super(\n \"No candidates returned from API. This may indicate the prompt was blocked or an error occurred.\"\n );\n }\n}\n\n/**\n * Error thrown when an invalid tool is provided to the tool converter.\n *\n * This error is thrown when a tool cannot be converted to Gemini's function\n * declaration format. Tools must be one of:\n * - LangChain structured tools (with Zod schemas)\n * - OpenAI format tools (with JSON Schema)\n * - Gemini-native function declarations\n * - Gemini-native tools (codeExecution, googleSearchRetrieval)\n *\n * @example\n * ```typescript\n * try {\n * convertToolsToGeminiFunctionDeclarations([invalidTool]);\n * } catch (error) {\n * if (InvalidToolError.isInstance(error)) {\n * console.log(`Invalid tool: ${error.tool}`);\n * console.log(`Expected formats: LangChain tools, OpenAI tools, or Gemini function declarations`);\n * }\n * }\n * ```\n */\nexport class InvalidToolError extends ns.brand(GoogleError, \"invalid-tool\") {\n readonly name = \"InvalidToolError\";\n\n /**\n * The invalid tool that was provided.\n */\n readonly tool: unknown;\n\n constructor(tool: unknown, message?: string) {\n const defaultMessage = `Received invalid tool: ${JSON.stringify(\n tool\n )}. Tools must be LangChain tools, OpenAI tools, or Gemini function declarations.`;\n\n super(message ?? defaultMessage);\n this.tool = tool;\n }\n}\n\n/**\n * Error thrown when a ToolMessage references a tool call that cannot be found.\n *\n * This error is thrown when a ToolMessage is passed to ChatGoogle but there is no\n * corresponding AIMessage with a matching toolCallId in the conversation history.\n * This typically indicates that:\n * - The tool call ID in the ToolMessage doesn't match any previous tool calls\n * - The AIMessage containing the tool call was not included in the message history\n * - The tool call ID was incorrectly set or modified\n *\n * @example\n * ```typescript\n * try {\n * await model.invoke([\n * new ToolMessage({ content: \"result\", tool_call_id: \"nonexistent-id\" })\n * ]);\n * } catch (error) {\n * if (ToolCallNotFoundError.isInstance(error)) {\n * console.log(`Tool call not found: ${error.toolCallId}`);\n * }\n * }\n * ```\n */\nexport class ToolCallNotFoundError extends ns.brand(\n GoogleError,\n \"tool-call-not-found\"\n) {\n readonly name = \"ToolCallNotFoundError\";\n\n /**\n * The tool call ID that could not be found in the conversation history.\n */\n readonly toolCallId: string;\n\n constructor(toolCallId: string, message?: string) {\n const defaultMessage = `Tool call with ID \"${toolCallId}\" not found in conversation history. Ensure the corresponding AIMessage with this tool call ID is included in the input.`;\n\n super(message ?? defaultMessage);\n this.toolCallId = toolCallId;\n }\n}\n\n/**\n * Parameters for constructing a MalformedOutputError\n */\ntype MalformedOutputErrorParams = {\n /**\n * The error message describing what went wrong during parsing\n */\n message: string;\n /**\n * Optional cause of the parsing error (e.g., the original error that occurred)\n */\n cause?: unknown;\n};\n\n/**\n * Error thrown when output parsing fails in structured output methods.\n *\n * This error is thrown when the model's response cannot be parsed according to\n * the expected schema or format. Common causes include:\n * - Invalid message type (not an AIMessage or AIMessageChunk)\n * - Missing or empty content in the response\n * - Malformed JSON that cannot be parsed\n * - Schema validation failures\n *\n * @example\n * ```typescript\n * try {\n * const result = await model.withStructuredOutput(schema).invoke(\"extract data\");\n * } catch (error) {\n * if (MalformedOutputError.isInstance(error)) {\n * console.log(`Failed to parse output: ${error.message}`);\n * if (error.cause) {\n * console.log(`Cause:`, error.cause);\n * }\n * }\n * }\n * ```\n */\nexport class MalformedOutputError extends ns.brand(\n GoogleError,\n \"malformed-output\"\n) {\n readonly name = \"MalformedOutputError\";\n\n /**\n * Optional cause of the parsing error.\n * This may be the original error that occurred during parsing,\n * or additional context about what went wrong.\n */\n readonly cause?: unknown;\n\n constructor(params: MalformedOutputErrorParams) {\n super(params.message);\n this.cause = params.cause;\n }\n}\n\n/**\n * Error thrown when input provided to the Google provider is invalid.\n *\n * This error is thrown when the user provides input that cannot be\n * processed by Gemini, such as:\n * - Union types in tool/function schemas\n * - Null-only types in schemas\n * - Unsupported content block source types\n * - Missing required fields in content blocks\n *\n * @example\n * ```typescript\n * try {\n * const result = schemaToGeminiParameters(unionTypeSchema);\n * } catch (error) {\n * if (InvalidInputError.isInstance(error)) {\n * console.log(`Invalid input: ${error.message}`);\n * }\n * }\n * ```\n */\nexport class InvalidInputError extends ns.brand(GoogleError, \"invalid-input\") {\n readonly name = \"InvalidInputError\";\n}\n"],"mappings":"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"}
|
package/dist/utils/gcp-auth.cjs
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
const require_misc = require(
|
|
2
|
-
const require_errors = require(
|
|
1
|
+
const require_misc = require("./misc.cjs");
|
|
2
|
+
const require_errors = require("./errors.cjs");
|
|
3
3
|
let jose = require("jose");
|
|
4
|
-
|
|
5
4
|
//#region src/utils/gcp-auth.ts
|
|
6
5
|
/**
|
|
7
6
|
* GCP authentication utilities.
|
|
@@ -185,8 +184,8 @@ async function getGCPCredentialsAccessToken(credentials) {
|
|
|
185
184
|
});
|
|
186
185
|
return await promise.then((data) => data.token);
|
|
187
186
|
}
|
|
188
|
-
|
|
189
187
|
//#endregion
|
|
190
188
|
exports.getGCPCredentialsAccessToken = getGCPCredentialsAccessToken;
|
|
191
189
|
exports.normalizeGCPCredentials = normalizeGCPCredentials;
|
|
190
|
+
|
|
192
191
|
//# sourceMappingURL=gcp-auth.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gcp-auth.cjs","names":["SignJWT","iife","AuthError"],"sources":["../../src/utils/gcp-auth.ts"],"sourcesContent":["/**\n * GCP authentication utilities.\n *\n * Adapted from: https://github.com/kriasoft/web-auth-library\n * Original source: web-auth-library by Kriasoft\n * License: MIT (https://github.com/kriasoft/web-auth-library/blob/main/LICENSE)\n */\n\nimport { decodeJwt, importPKCS8, SignJWT } from \"jose\";\nimport { AuthError } from \"../utils/errors.js\";\nimport { iife } from \"../utils/misc.js\";\n\n/**\n * Google Cloud Platform service account credentials interface.\n *\n * This interface represents the structure of a GCP service account key file,\n * which is used for authenticating with Google Cloud APIs. Service account\n * keys can be downloaded from the Google Cloud Console and contain all the\n * information needed to authenticate as a service account.\n *\n * @see https://cloud.google.com/iam/docs/creating-managing-service-account-keys\n */\nexport interface GCPCredentials {\n /** The type of credential, typically \"service_account\" */\n type: string;\n /** The GCP project ID associated with this service account */\n project_id: string;\n /** The unique identifier for the private key */\n private_key_id: string;\n /** The RSA private key in PEM format used for signing JWTs */\n private_key: string;\n /** The unique client ID for this service account */\n client_id: string;\n /** The email address of the service account */\n client_email: string;\n /** The OAuth2 authorization endpoint URI */\n auth_uri: string;\n /** The OAuth2 token endpoint URI where access tokens are requested */\n token_uri: string;\n /** The URL for the OAuth2 provider's x509 certificate */\n auth_provider_x509_cert_url: string;\n /** The URL for this service account's x509 certificate */\n client_x509_cert_url: string;\n}\n\ninterface CacheValue {\n /** Unix timestamp (in seconds) when this cache entry was created */\n created: number;\n /** Promise that resolves to the token and its expiration time */\n promise: Promise<{ token: string; expires: number }>;\n}\n\nconst cache = new Map<string, CacheValue>();\nconst CACHE_MAX_AGE = 60 * 60; // 1 hour\n\n/**\n * Normalizes GCP credentials to a frozen GCPCredentials object.\n *\n * This function accepts credentials in either string (JSON) or object format\n * and ensures the returned value is a frozen (immutable) GCPCredentials object.\n * Freezing the credentials prevents accidental modification and ensures\n * consistency throughout the application lifecycle.\n *\n * @param credentials - The GCP service account credentials, either as a JSON string\n * or as a GCPCredentials object\n * @returns A frozen GCPCredentials object that cannot be modified\n *\n * @example\n * ```typescript\n * // From JSON string\n * const creds = normalizeGCPCredentials('{\"type\":\"service_account\",...}');\n *\n * // From object\n * const creds = normalizeGCPCredentials({\n * type: \"service_account\",\n * project_id: \"my-project\",\n * // ... other fields\n * });\n * ```\n */\nexport function normalizeGCPCredentials(\n credentials: string | GCPCredentials\n): GCPCredentials {\n return typeof credentials === \"string\"\n ? Object.freeze(JSON.parse(credentials))\n : Object.isFrozen(credentials)\n ? credentials\n : Object.freeze(credentials);\n}\n\n/**\n * Imports and returns the private key from GCP credentials for JWT signing.\n *\n * This function extracts the RSA private key from the service account credentials\n * and imports it using the PKCS#8 format with the RS256 algorithm. The returned\n * key can be used to sign JWTs for authentication with Google Cloud services.\n *\n * @param credentials - The GCP service account credentials containing the private key\n * @returns A Promise that resolves to the imported private key suitable for JWT signing\n *\n * @throws {Error} If the private key format is invalid or cannot be imported\n *\n * @example\n * ```typescript\n * const credentials = normalizeGCPCredentials(credentialsJson);\n * const privateKey = await getGCPPrivateKey(credentials);\n * // Use privateKey for signing operations\n * ```\n */\nexport function getGCPPrivateKey(credentials: GCPCredentials) {\n return importPKCS8(credentials.private_key, \"RS256\");\n}\n\n/**\n * Generates a custom JWT token for authenticating with Google Cloud services.\n *\n * This function creates a signed JWT (JSON Web Token) using the service account's\n * private key. The token includes standard claims such as issuer, audience, subject,\n * issued-at time, and expiration time. This custom token can be exchanged for an\n * access token or ID token through Google's OAuth2 token endpoint.\n *\n * The generated JWT is valid for 1 hour and follows the OAuth 2.0 JWT Bearer\n * Token flow as specified by RFC 7523.\n *\n * @param credentials - The GCP service account credentials used to sign the JWT\n * @returns A Promise that resolves to the signed JWT string\n *\n * @throws {Error} If the private key cannot be imported or the JWT cannot be signed\n *\n * @see https://datatracker.ietf.org/doc/html/rfc7523\n * @see https://cloud.google.com/iam/docs/creating-short-lived-service-account-credentials\n *\n * @example\n * ```typescript\n * const credentials = normalizeGCPCredentials(credentialsJson);\n * const customToken = await getGCPCustomToken(credentials);\n * // Use customToken to request an access token\n * ```\n */\nexport async function getGCPCustomToken(credentials: GCPCredentials) {\n const privateKey = await getGCPPrivateKey(credentials);\n const customToken = await new SignJWT()\n .setIssuer(credentials.client_email)\n .setAudience(credentials.token_uri)\n .setSubject(credentials.client_email)\n .setProtectedHeader({ alg: \"RS256\" })\n .setIssuedAt()\n .setExpirationTime(\"1h\")\n .sign(privateKey);\n return customToken;\n}\n\n/**\n * Retrieves an access token for authenticating with Google Cloud APIs.\n *\n * - Tokens are cached based on the token URI and private key ID\n * - Cache entries are valid for up to 1 hour\n * - Expired tokens trigger automatic refresh\n * - Multiple concurrent requests for the same token are deduplicated\n *\n * The function handles both access tokens and ID tokens, automatically detecting\n * which type is returned by the token endpoint and extracting the appropriate\n * expiration time.\n *\n * @param credentials - The GCP service account credentials used to request the token\n * @returns A Promise that resolves to the access token string\n *\n * @throws {AuthError} If the token request fails (e.g., invalid credentials,\n * network error, or API error)\n *\n * @see https://cloud.google.com/iam/docs/creating-short-lived-service-account-credentials\n * @see https://datatracker.ietf.org/doc/html/rfc7523\n *\n * @example\n * ```typescript\n * const credentials = normalizeGCPCredentials(credentialsJson);\n * const accessToken = await getGCPCredentialsAccessToken(credentials);\n *\n * // Use the access token in API requests\n * const response = await fetch('https://api.google.com/...', {\n * headers: {\n * 'Authorization': `Bearer ${accessToken}`\n * }\n * });\n * ```\n *\n * @remarks\n * The caching mechanism uses a two-level validation:\n * 1. Cache entry age (must be less than CACHE_MAX_AGE)\n * 2. Token expiration time (must not be expired)\n *\n * This ensures that even if a cache entry is fresh, an expired token\n * will trigger a refresh request.\n */\nexport async function getGCPCredentialsAccessToken(\n credentials: GCPCredentials\n): Promise<string> {\n const tokenUrl = credentials.token_uri;\n\n const cacheKeyUrl = new URL(tokenUrl);\n cacheKeyUrl.searchParams.set(\"key\", credentials.private_key_id);\n const cacheKey = cacheKeyUrl.toString();\n\n // Attempt to retrieve the token from the cache\n const cacheValue = cache.get(cacheKey);\n let now = Math.floor(Date.now() / 1000);\n\n if (cacheValue) {\n if (cacheValue.created > now - CACHE_MAX_AGE) {\n let token = await cacheValue.promise;\n\n if (token.expires > now) {\n return token.token;\n } else {\n const nextValue = cache.get(cacheKey);\n\n if (nextValue && nextValue !== cacheValue) {\n token = await nextValue.promise;\n if (token.expires > now) {\n return token.token;\n } else {\n cache.delete(cacheKey);\n }\n }\n }\n } else {\n cache.delete(cacheKey);\n }\n }\n\n const promise = iife(async () => {\n now = Math.floor(Date.now() / 1000);\n\n // Request a new token from the Google Cloud API\n const jwt = await getGCPCustomToken(credentials);\n const body = new URLSearchParams();\n body.append(\"grant_type\", \"urn:ietf:params:oauth:grant-type:jwt-bearer\");\n body.append(\"assertion\", jwt);\n const res = await fetch(tokenUrl, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body,\n });\n\n if (!res.ok) {\n throw await AuthError.fromResponse(res);\n }\n\n const data = await res.json();\n\n if (\"id_token\" in data) {\n const claims = decodeJwt(data.id_token);\n return { token: data.id_token, expires: claims.exp as number };\n }\n\n const lastModified = res.headers.get(\"last-modified\");\n const expires = lastModified\n ? Math.floor(new Date(lastModified).valueOf() / 1000) + data.expires_in\n : now + data.expires_in;\n\n return { expires, token: data.access_token };\n });\n\n cache.set(cacheKey, { created: now, promise });\n return await promise.then((data) => data.token);\n}\n"],"mappings":";;;;;;;;;;;;AAoDA,MAAM,wBAAQ,IAAI,KAAyB;AAC3C,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BtB,SAAgB,wBACd,aACgB;AAChB,QAAO,OAAO,gBAAgB,WAC1B,OAAO,OAAO,KAAK,MAAM,YAAY,CAAC,GACtC,OAAO,SAAS,YAAY,GAC5B,cACA,OAAO,OAAO,YAAY;;;;;;;;;;;;;;;;;;;;;AAsBhC,SAAgB,iBAAiB,aAA6B;AAC5D,8BAAmB,YAAY,aAAa,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BtD,eAAsB,kBAAkB,aAA6B;CACnE,MAAM,aAAa,MAAM,iBAAiB,YAAY;AAStD,QARoB,MAAM,IAAIA,cAAS,CACpC,UAAU,YAAY,aAAa,CACnC,YAAY,YAAY,UAAU,CAClC,WAAW,YAAY,aAAa,CACpC,mBAAmB,EAAE,KAAK,SAAS,CAAC,CACpC,aAAa,CACb,kBAAkB,KAAK,CACvB,KAAK,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8CrB,eAAsB,6BACpB,aACiB;CACjB,MAAM,WAAW,YAAY;CAE7B,MAAM,cAAc,IAAI,IAAI,SAAS;AACrC,aAAY,aAAa,IAAI,OAAO,YAAY,eAAe;CAC/D,MAAM,WAAW,YAAY,UAAU;CAGvC,MAAM,aAAa,MAAM,IAAI,SAAS;CACtC,IAAI,MAAM,KAAK,MAAM,KAAK,KAAK,GAAG,IAAK;AAEvC,KAAI,WACF,KAAI,WAAW,UAAU,MAAM,eAAe;EAC5C,IAAI,QAAQ,MAAM,WAAW;AAE7B,MAAI,MAAM,UAAU,IAClB,QAAO,MAAM;OACR;GACL,MAAM,YAAY,MAAM,IAAI,SAAS;AAErC,OAAI,aAAa,cAAc,YAAY;AACzC,YAAQ,MAAM,UAAU;AACxB,QAAI,MAAM,UAAU,IAClB,QAAO,MAAM;QAEb,OAAM,OAAO,SAAS;;;OAK5B,OAAM,OAAO,SAAS;CAI1B,MAAM,UAAUC,kBAAK,YAAY;AAC/B,QAAM,KAAK,MAAM,KAAK,KAAK,GAAG,IAAK;EAGnC,MAAM,MAAM,MAAM,kBAAkB,YAAY;EAChD,MAAM,OAAO,IAAI,iBAAiB;AAClC,OAAK,OAAO,cAAc,8CAA8C;AACxE,OAAK,OAAO,aAAa,IAAI;EAC7B,MAAM,MAAM,MAAM,MAAM,UAAU;GAChC,QAAQ;GACR,SAAS,EAAE,gBAAgB,qCAAqC;GAChE;GACD,CAAC;AAEF,MAAI,CAAC,IAAI,GACP,OAAM,MAAMC,yBAAU,aAAa,IAAI;EAGzC,MAAM,OAAO,MAAM,IAAI,MAAM;AAE7B,MAAI,cAAc,MAAM;GACtB,MAAM,6BAAmB,KAAK,SAAS;AACvC,UAAO;IAAE,OAAO,KAAK;IAAU,SAAS,OAAO;IAAe;;EAGhE,MAAM,eAAe,IAAI,QAAQ,IAAI,gBAAgB;AAKrD,SAAO;GAAE,SAJO,eACZ,KAAK,MAAM,IAAI,KAAK,aAAa,CAAC,SAAS,GAAG,IAAK,GAAG,KAAK,aAC3D,MAAM,KAAK;GAEG,OAAO,KAAK;GAAc;GAC5C;AAEF,OAAM,IAAI,UAAU;EAAE,SAAS;EAAK;EAAS,CAAC;AAC9C,QAAO,MAAM,QAAQ,MAAM,SAAS,KAAK,MAAM"}
|
|
1
|
+
{"version":3,"file":"gcp-auth.cjs","names":["SignJWT","iife","AuthError"],"sources":["../../src/utils/gcp-auth.ts"],"sourcesContent":["/**\n * GCP authentication utilities.\n *\n * Adapted from: https://github.com/kriasoft/web-auth-library\n * Original source: web-auth-library by Kriasoft\n * License: MIT (https://github.com/kriasoft/web-auth-library/blob/main/LICENSE)\n */\n\nimport { decodeJwt, importPKCS8, SignJWT } from \"jose\";\nimport { AuthError } from \"../utils/errors.js\";\nimport { iife } from \"../utils/misc.js\";\n\n/**\n * Google Cloud Platform service account credentials interface.\n *\n * This interface represents the structure of a GCP service account key file,\n * which is used for authenticating with Google Cloud APIs. Service account\n * keys can be downloaded from the Google Cloud Console and contain all the\n * information needed to authenticate as a service account.\n *\n * @see https://cloud.google.com/iam/docs/creating-managing-service-account-keys\n */\nexport interface GCPCredentials {\n /** The type of credential, typically \"service_account\" */\n type: string;\n /** The GCP project ID associated with this service account */\n project_id: string;\n /** The unique identifier for the private key */\n private_key_id: string;\n /** The RSA private key in PEM format used for signing JWTs */\n private_key: string;\n /** The unique client ID for this service account */\n client_id: string;\n /** The email address of the service account */\n client_email: string;\n /** The OAuth2 authorization endpoint URI */\n auth_uri: string;\n /** The OAuth2 token endpoint URI where access tokens are requested */\n token_uri: string;\n /** The URL for the OAuth2 provider's x509 certificate */\n auth_provider_x509_cert_url: string;\n /** The URL for this service account's x509 certificate */\n client_x509_cert_url: string;\n}\n\ninterface CacheValue {\n /** Unix timestamp (in seconds) when this cache entry was created */\n created: number;\n /** Promise that resolves to the token and its expiration time */\n promise: Promise<{ token: string; expires: number }>;\n}\n\nconst cache = new Map<string, CacheValue>();\nconst CACHE_MAX_AGE = 60 * 60; // 1 hour\n\n/**\n * Normalizes GCP credentials to a frozen GCPCredentials object.\n *\n * This function accepts credentials in either string (JSON) or object format\n * and ensures the returned value is a frozen (immutable) GCPCredentials object.\n * Freezing the credentials prevents accidental modification and ensures\n * consistency throughout the application lifecycle.\n *\n * @param credentials - The GCP service account credentials, either as a JSON string\n * or as a GCPCredentials object\n * @returns A frozen GCPCredentials object that cannot be modified\n *\n * @example\n * ```typescript\n * // From JSON string\n * const creds = normalizeGCPCredentials('{\"type\":\"service_account\",...}');\n *\n * // From object\n * const creds = normalizeGCPCredentials({\n * type: \"service_account\",\n * project_id: \"my-project\",\n * // ... other fields\n * });\n * ```\n */\nexport function normalizeGCPCredentials(\n credentials: string | GCPCredentials\n): GCPCredentials {\n return typeof credentials === \"string\"\n ? Object.freeze(JSON.parse(credentials))\n : Object.isFrozen(credentials)\n ? credentials\n : Object.freeze(credentials);\n}\n\n/**\n * Imports and returns the private key from GCP credentials for JWT signing.\n *\n * This function extracts the RSA private key from the service account credentials\n * and imports it using the PKCS#8 format with the RS256 algorithm. The returned\n * key can be used to sign JWTs for authentication with Google Cloud services.\n *\n * @param credentials - The GCP service account credentials containing the private key\n * @returns A Promise that resolves to the imported private key suitable for JWT signing\n *\n * @throws {Error} If the private key format is invalid or cannot be imported\n *\n * @example\n * ```typescript\n * const credentials = normalizeGCPCredentials(credentialsJson);\n * const privateKey = await getGCPPrivateKey(credentials);\n * // Use privateKey for signing operations\n * ```\n */\nexport function getGCPPrivateKey(credentials: GCPCredentials) {\n return importPKCS8(credentials.private_key, \"RS256\");\n}\n\n/**\n * Generates a custom JWT token for authenticating with Google Cloud services.\n *\n * This function creates a signed JWT (JSON Web Token) using the service account's\n * private key. The token includes standard claims such as issuer, audience, subject,\n * issued-at time, and expiration time. This custom token can be exchanged for an\n * access token or ID token through Google's OAuth2 token endpoint.\n *\n * The generated JWT is valid for 1 hour and follows the OAuth 2.0 JWT Bearer\n * Token flow as specified by RFC 7523.\n *\n * @param credentials - The GCP service account credentials used to sign the JWT\n * @returns A Promise that resolves to the signed JWT string\n *\n * @throws {Error} If the private key cannot be imported or the JWT cannot be signed\n *\n * @see https://datatracker.ietf.org/doc/html/rfc7523\n * @see https://cloud.google.com/iam/docs/creating-short-lived-service-account-credentials\n *\n * @example\n * ```typescript\n * const credentials = normalizeGCPCredentials(credentialsJson);\n * const customToken = await getGCPCustomToken(credentials);\n * // Use customToken to request an access token\n * ```\n */\nexport async function getGCPCustomToken(credentials: GCPCredentials) {\n const privateKey = await getGCPPrivateKey(credentials);\n const customToken = await new SignJWT()\n .setIssuer(credentials.client_email)\n .setAudience(credentials.token_uri)\n .setSubject(credentials.client_email)\n .setProtectedHeader({ alg: \"RS256\" })\n .setIssuedAt()\n .setExpirationTime(\"1h\")\n .sign(privateKey);\n return customToken;\n}\n\n/**\n * Retrieves an access token for authenticating with Google Cloud APIs.\n *\n * - Tokens are cached based on the token URI and private key ID\n * - Cache entries are valid for up to 1 hour\n * - Expired tokens trigger automatic refresh\n * - Multiple concurrent requests for the same token are deduplicated\n *\n * The function handles both access tokens and ID tokens, automatically detecting\n * which type is returned by the token endpoint and extracting the appropriate\n * expiration time.\n *\n * @param credentials - The GCP service account credentials used to request the token\n * @returns A Promise that resolves to the access token string\n *\n * @throws {AuthError} If the token request fails (e.g., invalid credentials,\n * network error, or API error)\n *\n * @see https://cloud.google.com/iam/docs/creating-short-lived-service-account-credentials\n * @see https://datatracker.ietf.org/doc/html/rfc7523\n *\n * @example\n * ```typescript\n * const credentials = normalizeGCPCredentials(credentialsJson);\n * const accessToken = await getGCPCredentialsAccessToken(credentials);\n *\n * // Use the access token in API requests\n * const response = await fetch('https://api.google.com/...', {\n * headers: {\n * 'Authorization': `Bearer ${accessToken}`\n * }\n * });\n * ```\n *\n * @remarks\n * The caching mechanism uses a two-level validation:\n * 1. Cache entry age (must be less than CACHE_MAX_AGE)\n * 2. Token expiration time (must not be expired)\n *\n * This ensures that even if a cache entry is fresh, an expired token\n * will trigger a refresh request.\n */\nexport async function getGCPCredentialsAccessToken(\n credentials: GCPCredentials\n): Promise<string> {\n const tokenUrl = credentials.token_uri;\n\n const cacheKeyUrl = new URL(tokenUrl);\n cacheKeyUrl.searchParams.set(\"key\", credentials.private_key_id);\n const cacheKey = cacheKeyUrl.toString();\n\n // Attempt to retrieve the token from the cache\n const cacheValue = cache.get(cacheKey);\n let now = Math.floor(Date.now() / 1000);\n\n if (cacheValue) {\n if (cacheValue.created > now - CACHE_MAX_AGE) {\n let token = await cacheValue.promise;\n\n if (token.expires > now) {\n return token.token;\n } else {\n const nextValue = cache.get(cacheKey);\n\n if (nextValue && nextValue !== cacheValue) {\n token = await nextValue.promise;\n if (token.expires > now) {\n return token.token;\n } else {\n cache.delete(cacheKey);\n }\n }\n }\n } else {\n cache.delete(cacheKey);\n }\n }\n\n const promise = iife(async () => {\n now = Math.floor(Date.now() / 1000);\n\n // Request a new token from the Google Cloud API\n const jwt = await getGCPCustomToken(credentials);\n const body = new URLSearchParams();\n body.append(\"grant_type\", \"urn:ietf:params:oauth:grant-type:jwt-bearer\");\n body.append(\"assertion\", jwt);\n const res = await fetch(tokenUrl, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body,\n });\n\n if (!res.ok) {\n throw await AuthError.fromResponse(res);\n }\n\n const data = await res.json();\n\n if (\"id_token\" in data) {\n const claims = decodeJwt(data.id_token);\n return { token: data.id_token, expires: claims.exp as number };\n }\n\n const lastModified = res.headers.get(\"last-modified\");\n const expires = lastModified\n ? Math.floor(new Date(lastModified).valueOf() / 1000) + data.expires_in\n : now + data.expires_in;\n\n return { expires, token: data.access_token };\n });\n\n cache.set(cacheKey, { created: now, promise });\n return await promise.then((data) => data.token);\n}\n"],"mappings":";;;;;;;;;;;AAoDA,MAAM,wBAAQ,IAAI,KAAyB;AAC3C,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BtB,SAAgB,wBACd,aACgB;AAChB,QAAO,OAAO,gBAAgB,WAC1B,OAAO,OAAO,KAAK,MAAM,YAAY,CAAC,GACtC,OAAO,SAAS,YAAY,GAC5B,cACA,OAAO,OAAO,YAAY;;;;;;;;;;;;;;;;;;;;;AAsBhC,SAAgB,iBAAiB,aAA6B;AAC5D,SAAA,GAAA,KAAA,aAAmB,YAAY,aAAa,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BtD,eAAsB,kBAAkB,aAA6B;CACnE,MAAM,aAAa,MAAM,iBAAiB,YAAY;AAStD,QARoB,MAAM,IAAIA,KAAAA,SAAS,CACpC,UAAU,YAAY,aAAa,CACnC,YAAY,YAAY,UAAU,CAClC,WAAW,YAAY,aAAa,CACpC,mBAAmB,EAAE,KAAK,SAAS,CAAC,CACpC,aAAa,CACb,kBAAkB,KAAK,CACvB,KAAK,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8CrB,eAAsB,6BACpB,aACiB;CACjB,MAAM,WAAW,YAAY;CAE7B,MAAM,cAAc,IAAI,IAAI,SAAS;AACrC,aAAY,aAAa,IAAI,OAAO,YAAY,eAAe;CAC/D,MAAM,WAAW,YAAY,UAAU;CAGvC,MAAM,aAAa,MAAM,IAAI,SAAS;CACtC,IAAI,MAAM,KAAK,MAAM,KAAK,KAAK,GAAG,IAAK;AAEvC,KAAI,WACF,KAAI,WAAW,UAAU,MAAM,eAAe;EAC5C,IAAI,QAAQ,MAAM,WAAW;AAE7B,MAAI,MAAM,UAAU,IAClB,QAAO,MAAM;OACR;GACL,MAAM,YAAY,MAAM,IAAI,SAAS;AAErC,OAAI,aAAa,cAAc,YAAY;AACzC,YAAQ,MAAM,UAAU;AACxB,QAAI,MAAM,UAAU,IAClB,QAAO,MAAM;QAEb,OAAM,OAAO,SAAS;;;OAK5B,OAAM,OAAO,SAAS;CAI1B,MAAM,UAAUC,aAAAA,KAAK,YAAY;AAC/B,QAAM,KAAK,MAAM,KAAK,KAAK,GAAG,IAAK;EAGnC,MAAM,MAAM,MAAM,kBAAkB,YAAY;EAChD,MAAM,OAAO,IAAI,iBAAiB;AAClC,OAAK,OAAO,cAAc,8CAA8C;AACxE,OAAK,OAAO,aAAa,IAAI;EAC7B,MAAM,MAAM,MAAM,MAAM,UAAU;GAChC,QAAQ;GACR,SAAS,EAAE,gBAAgB,qCAAqC;GAChE;GACD,CAAC;AAEF,MAAI,CAAC,IAAI,GACP,OAAM,MAAMC,eAAAA,UAAU,aAAa,IAAI;EAGzC,MAAM,OAAO,MAAM,IAAI,MAAM;AAE7B,MAAI,cAAc,MAAM;GACtB,MAAM,UAAA,GAAA,KAAA,WAAmB,KAAK,SAAS;AACvC,UAAO;IAAE,OAAO,KAAK;IAAU,SAAS,OAAO;IAAe;;EAGhE,MAAM,eAAe,IAAI,QAAQ,IAAI,gBAAgB;AAKrD,SAAO;GAAE,SAJO,eACZ,KAAK,MAAM,IAAI,KAAK,aAAa,CAAC,SAAS,GAAG,IAAK,GAAG,KAAK,aAC3D,MAAM,KAAK;GAEG,OAAO,KAAK;GAAc;GAC5C;AAEF,OAAM,IAAI,UAAU;EAAE,SAAS;EAAK;EAAS,CAAC;AAC9C,QAAO,MAAM,QAAQ,MAAM,SAAS,KAAK,MAAM"}
|
package/dist/utils/gcp-auth.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { iife } from "./misc.js";
|
|
2
2
|
import { AuthError } from "./errors.js";
|
|
3
3
|
import { SignJWT, decodeJwt, importPKCS8 } from "jose";
|
|
4
|
-
|
|
5
4
|
//#region src/utils/gcp-auth.ts
|
|
6
5
|
/**
|
|
7
6
|
* GCP authentication utilities.
|
|
@@ -185,7 +184,7 @@ async function getGCPCredentialsAccessToken(credentials) {
|
|
|
185
184
|
});
|
|
186
185
|
return await promise.then((data) => data.token);
|
|
187
186
|
}
|
|
188
|
-
|
|
189
187
|
//#endregion
|
|
190
188
|
export { getGCPCredentialsAccessToken, normalizeGCPCredentials };
|
|
189
|
+
|
|
191
190
|
//# sourceMappingURL=gcp-auth.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gcp-auth.js","names":[],"sources":["../../src/utils/gcp-auth.ts"],"sourcesContent":["/**\n * GCP authentication utilities.\n *\n * Adapted from: https://github.com/kriasoft/web-auth-library\n * Original source: web-auth-library by Kriasoft\n * License: MIT (https://github.com/kriasoft/web-auth-library/blob/main/LICENSE)\n */\n\nimport { decodeJwt, importPKCS8, SignJWT } from \"jose\";\nimport { AuthError } from \"../utils/errors.js\";\nimport { iife } from \"../utils/misc.js\";\n\n/**\n * Google Cloud Platform service account credentials interface.\n *\n * This interface represents the structure of a GCP service account key file,\n * which is used for authenticating with Google Cloud APIs. Service account\n * keys can be downloaded from the Google Cloud Console and contain all the\n * information needed to authenticate as a service account.\n *\n * @see https://cloud.google.com/iam/docs/creating-managing-service-account-keys\n */\nexport interface GCPCredentials {\n /** The type of credential, typically \"service_account\" */\n type: string;\n /** The GCP project ID associated with this service account */\n project_id: string;\n /** The unique identifier for the private key */\n private_key_id: string;\n /** The RSA private key in PEM format used for signing JWTs */\n private_key: string;\n /** The unique client ID for this service account */\n client_id: string;\n /** The email address of the service account */\n client_email: string;\n /** The OAuth2 authorization endpoint URI */\n auth_uri: string;\n /** The OAuth2 token endpoint URI where access tokens are requested */\n token_uri: string;\n /** The URL for the OAuth2 provider's x509 certificate */\n auth_provider_x509_cert_url: string;\n /** The URL for this service account's x509 certificate */\n client_x509_cert_url: string;\n}\n\ninterface CacheValue {\n /** Unix timestamp (in seconds) when this cache entry was created */\n created: number;\n /** Promise that resolves to the token and its expiration time */\n promise: Promise<{ token: string; expires: number }>;\n}\n\nconst cache = new Map<string, CacheValue>();\nconst CACHE_MAX_AGE = 60 * 60; // 1 hour\n\n/**\n * Normalizes GCP credentials to a frozen GCPCredentials object.\n *\n * This function accepts credentials in either string (JSON) or object format\n * and ensures the returned value is a frozen (immutable) GCPCredentials object.\n * Freezing the credentials prevents accidental modification and ensures\n * consistency throughout the application lifecycle.\n *\n * @param credentials - The GCP service account credentials, either as a JSON string\n * or as a GCPCredentials object\n * @returns A frozen GCPCredentials object that cannot be modified\n *\n * @example\n * ```typescript\n * // From JSON string\n * const creds = normalizeGCPCredentials('{\"type\":\"service_account\",...}');\n *\n * // From object\n * const creds = normalizeGCPCredentials({\n * type: \"service_account\",\n * project_id: \"my-project\",\n * // ... other fields\n * });\n * ```\n */\nexport function normalizeGCPCredentials(\n credentials: string | GCPCredentials\n): GCPCredentials {\n return typeof credentials === \"string\"\n ? Object.freeze(JSON.parse(credentials))\n : Object.isFrozen(credentials)\n ? credentials\n : Object.freeze(credentials);\n}\n\n/**\n * Imports and returns the private key from GCP credentials for JWT signing.\n *\n * This function extracts the RSA private key from the service account credentials\n * and imports it using the PKCS#8 format with the RS256 algorithm. The returned\n * key can be used to sign JWTs for authentication with Google Cloud services.\n *\n * @param credentials - The GCP service account credentials containing the private key\n * @returns A Promise that resolves to the imported private key suitable for JWT signing\n *\n * @throws {Error} If the private key format is invalid or cannot be imported\n *\n * @example\n * ```typescript\n * const credentials = normalizeGCPCredentials(credentialsJson);\n * const privateKey = await getGCPPrivateKey(credentials);\n * // Use privateKey for signing operations\n * ```\n */\nexport function getGCPPrivateKey(credentials: GCPCredentials) {\n return importPKCS8(credentials.private_key, \"RS256\");\n}\n\n/**\n * Generates a custom JWT token for authenticating with Google Cloud services.\n *\n * This function creates a signed JWT (JSON Web Token) using the service account's\n * private key. The token includes standard claims such as issuer, audience, subject,\n * issued-at time, and expiration time. This custom token can be exchanged for an\n * access token or ID token through Google's OAuth2 token endpoint.\n *\n * The generated JWT is valid for 1 hour and follows the OAuth 2.0 JWT Bearer\n * Token flow as specified by RFC 7523.\n *\n * @param credentials - The GCP service account credentials used to sign the JWT\n * @returns A Promise that resolves to the signed JWT string\n *\n * @throws {Error} If the private key cannot be imported or the JWT cannot be signed\n *\n * @see https://datatracker.ietf.org/doc/html/rfc7523\n * @see https://cloud.google.com/iam/docs/creating-short-lived-service-account-credentials\n *\n * @example\n * ```typescript\n * const credentials = normalizeGCPCredentials(credentialsJson);\n * const customToken = await getGCPCustomToken(credentials);\n * // Use customToken to request an access token\n * ```\n */\nexport async function getGCPCustomToken(credentials: GCPCredentials) {\n const privateKey = await getGCPPrivateKey(credentials);\n const customToken = await new SignJWT()\n .setIssuer(credentials.client_email)\n .setAudience(credentials.token_uri)\n .setSubject(credentials.client_email)\n .setProtectedHeader({ alg: \"RS256\" })\n .setIssuedAt()\n .setExpirationTime(\"1h\")\n .sign(privateKey);\n return customToken;\n}\n\n/**\n * Retrieves an access token for authenticating with Google Cloud APIs.\n *\n * - Tokens are cached based on the token URI and private key ID\n * - Cache entries are valid for up to 1 hour\n * - Expired tokens trigger automatic refresh\n * - Multiple concurrent requests for the same token are deduplicated\n *\n * The function handles both access tokens and ID tokens, automatically detecting\n * which type is returned by the token endpoint and extracting the appropriate\n * expiration time.\n *\n * @param credentials - The GCP service account credentials used to request the token\n * @returns A Promise that resolves to the access token string\n *\n * @throws {AuthError} If the token request fails (e.g., invalid credentials,\n * network error, or API error)\n *\n * @see https://cloud.google.com/iam/docs/creating-short-lived-service-account-credentials\n * @see https://datatracker.ietf.org/doc/html/rfc7523\n *\n * @example\n * ```typescript\n * const credentials = normalizeGCPCredentials(credentialsJson);\n * const accessToken = await getGCPCredentialsAccessToken(credentials);\n *\n * // Use the access token in API requests\n * const response = await fetch('https://api.google.com/...', {\n * headers: {\n * 'Authorization': `Bearer ${accessToken}`\n * }\n * });\n * ```\n *\n * @remarks\n * The caching mechanism uses a two-level validation:\n * 1. Cache entry age (must be less than CACHE_MAX_AGE)\n * 2. Token expiration time (must not be expired)\n *\n * This ensures that even if a cache entry is fresh, an expired token\n * will trigger a refresh request.\n */\nexport async function getGCPCredentialsAccessToken(\n credentials: GCPCredentials\n): Promise<string> {\n const tokenUrl = credentials.token_uri;\n\n const cacheKeyUrl = new URL(tokenUrl);\n cacheKeyUrl.searchParams.set(\"key\", credentials.private_key_id);\n const cacheKey = cacheKeyUrl.toString();\n\n // Attempt to retrieve the token from the cache\n const cacheValue = cache.get(cacheKey);\n let now = Math.floor(Date.now() / 1000);\n\n if (cacheValue) {\n if (cacheValue.created > now - CACHE_MAX_AGE) {\n let token = await cacheValue.promise;\n\n if (token.expires > now) {\n return token.token;\n } else {\n const nextValue = cache.get(cacheKey);\n\n if (nextValue && nextValue !== cacheValue) {\n token = await nextValue.promise;\n if (token.expires > now) {\n return token.token;\n } else {\n cache.delete(cacheKey);\n }\n }\n }\n } else {\n cache.delete(cacheKey);\n }\n }\n\n const promise = iife(async () => {\n now = Math.floor(Date.now() / 1000);\n\n // Request a new token from the Google Cloud API\n const jwt = await getGCPCustomToken(credentials);\n const body = new URLSearchParams();\n body.append(\"grant_type\", \"urn:ietf:params:oauth:grant-type:jwt-bearer\");\n body.append(\"assertion\", jwt);\n const res = await fetch(tokenUrl, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body,\n });\n\n if (!res.ok) {\n throw await AuthError.fromResponse(res);\n }\n\n const data = await res.json();\n\n if (\"id_token\" in data) {\n const claims = decodeJwt(data.id_token);\n return { token: data.id_token, expires: claims.exp as number };\n }\n\n const lastModified = res.headers.get(\"last-modified\");\n const expires = lastModified\n ? Math.floor(new Date(lastModified).valueOf() / 1000) + data.expires_in\n : now + data.expires_in;\n\n return { expires, token: data.access_token };\n });\n\n cache.set(cacheKey, { created: now, promise });\n return await promise.then((data) => data.token);\n}\n"],"mappings":";;;;;;;;;;;;AAoDA,MAAM,wBAAQ,IAAI,KAAyB;AAC3C,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BtB,SAAgB,wBACd,aACgB;AAChB,QAAO,OAAO,gBAAgB,WAC1B,OAAO,OAAO,KAAK,MAAM,YAAY,CAAC,GACtC,OAAO,SAAS,YAAY,GAC5B,cACA,OAAO,OAAO,YAAY;;;;;;;;;;;;;;;;;;;;;AAsBhC,SAAgB,iBAAiB,aAA6B;AAC5D,QAAO,YAAY,YAAY,aAAa,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BtD,eAAsB,kBAAkB,aAA6B;CACnE,MAAM,aAAa,MAAM,iBAAiB,YAAY;AAStD,QARoB,MAAM,IAAI,SAAS,CACpC,UAAU,YAAY,aAAa,CACnC,YAAY,YAAY,UAAU,CAClC,WAAW,YAAY,aAAa,CACpC,mBAAmB,EAAE,KAAK,SAAS,CAAC,CACpC,aAAa,CACb,kBAAkB,KAAK,CACvB,KAAK,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8CrB,eAAsB,6BACpB,aACiB;CACjB,MAAM,WAAW,YAAY;CAE7B,MAAM,cAAc,IAAI,IAAI,SAAS;AACrC,aAAY,aAAa,IAAI,OAAO,YAAY,eAAe;CAC/D,MAAM,WAAW,YAAY,UAAU;CAGvC,MAAM,aAAa,MAAM,IAAI,SAAS;CACtC,IAAI,MAAM,KAAK,MAAM,KAAK,KAAK,GAAG,IAAK;AAEvC,KAAI,WACF,KAAI,WAAW,UAAU,MAAM,eAAe;EAC5C,IAAI,QAAQ,MAAM,WAAW;AAE7B,MAAI,MAAM,UAAU,IAClB,QAAO,MAAM;OACR;GACL,MAAM,YAAY,MAAM,IAAI,SAAS;AAErC,OAAI,aAAa,cAAc,YAAY;AACzC,YAAQ,MAAM,UAAU;AACxB,QAAI,MAAM,UAAU,IAClB,QAAO,MAAM;QAEb,OAAM,OAAO,SAAS;;;OAK5B,OAAM,OAAO,SAAS;CAI1B,MAAM,UAAU,KAAK,YAAY;AAC/B,QAAM,KAAK,MAAM,KAAK,KAAK,GAAG,IAAK;EAGnC,MAAM,MAAM,MAAM,kBAAkB,YAAY;EAChD,MAAM,OAAO,IAAI,iBAAiB;AAClC,OAAK,OAAO,cAAc,8CAA8C;AACxE,OAAK,OAAO,aAAa,IAAI;EAC7B,MAAM,MAAM,MAAM,MAAM,UAAU;GAChC,QAAQ;GACR,SAAS,EAAE,gBAAgB,qCAAqC;GAChE;GACD,CAAC;AAEF,MAAI,CAAC,IAAI,GACP,OAAM,MAAM,UAAU,aAAa,IAAI;EAGzC,MAAM,OAAO,MAAM,IAAI,MAAM;AAE7B,MAAI,cAAc,MAAM;GACtB,MAAM,SAAS,UAAU,KAAK,SAAS;AACvC,UAAO;IAAE,OAAO,KAAK;IAAU,SAAS,OAAO;IAAe;;EAGhE,MAAM,eAAe,IAAI,QAAQ,IAAI,gBAAgB;AAKrD,SAAO;GAAE,SAJO,eACZ,KAAK,MAAM,IAAI,KAAK,aAAa,CAAC,SAAS,GAAG,IAAK,GAAG,KAAK,aAC3D,MAAM,KAAK;GAEG,OAAO,KAAK;GAAc;GAC5C;AAEF,OAAM,IAAI,UAAU;EAAE,SAAS;EAAK;EAAS,CAAC;AAC9C,QAAO,MAAM,QAAQ,MAAM,SAAS,KAAK,MAAM"}
|
|
1
|
+
{"version":3,"file":"gcp-auth.js","names":[],"sources":["../../src/utils/gcp-auth.ts"],"sourcesContent":["/**\n * GCP authentication utilities.\n *\n * Adapted from: https://github.com/kriasoft/web-auth-library\n * Original source: web-auth-library by Kriasoft\n * License: MIT (https://github.com/kriasoft/web-auth-library/blob/main/LICENSE)\n */\n\nimport { decodeJwt, importPKCS8, SignJWT } from \"jose\";\nimport { AuthError } from \"../utils/errors.js\";\nimport { iife } from \"../utils/misc.js\";\n\n/**\n * Google Cloud Platform service account credentials interface.\n *\n * This interface represents the structure of a GCP service account key file,\n * which is used for authenticating with Google Cloud APIs. Service account\n * keys can be downloaded from the Google Cloud Console and contain all the\n * information needed to authenticate as a service account.\n *\n * @see https://cloud.google.com/iam/docs/creating-managing-service-account-keys\n */\nexport interface GCPCredentials {\n /** The type of credential, typically \"service_account\" */\n type: string;\n /** The GCP project ID associated with this service account */\n project_id: string;\n /** The unique identifier for the private key */\n private_key_id: string;\n /** The RSA private key in PEM format used for signing JWTs */\n private_key: string;\n /** The unique client ID for this service account */\n client_id: string;\n /** The email address of the service account */\n client_email: string;\n /** The OAuth2 authorization endpoint URI */\n auth_uri: string;\n /** The OAuth2 token endpoint URI where access tokens are requested */\n token_uri: string;\n /** The URL for the OAuth2 provider's x509 certificate */\n auth_provider_x509_cert_url: string;\n /** The URL for this service account's x509 certificate */\n client_x509_cert_url: string;\n}\n\ninterface CacheValue {\n /** Unix timestamp (in seconds) when this cache entry was created */\n created: number;\n /** Promise that resolves to the token and its expiration time */\n promise: Promise<{ token: string; expires: number }>;\n}\n\nconst cache = new Map<string, CacheValue>();\nconst CACHE_MAX_AGE = 60 * 60; // 1 hour\n\n/**\n * Normalizes GCP credentials to a frozen GCPCredentials object.\n *\n * This function accepts credentials in either string (JSON) or object format\n * and ensures the returned value is a frozen (immutable) GCPCredentials object.\n * Freezing the credentials prevents accidental modification and ensures\n * consistency throughout the application lifecycle.\n *\n * @param credentials - The GCP service account credentials, either as a JSON string\n * or as a GCPCredentials object\n * @returns A frozen GCPCredentials object that cannot be modified\n *\n * @example\n * ```typescript\n * // From JSON string\n * const creds = normalizeGCPCredentials('{\"type\":\"service_account\",...}');\n *\n * // From object\n * const creds = normalizeGCPCredentials({\n * type: \"service_account\",\n * project_id: \"my-project\",\n * // ... other fields\n * });\n * ```\n */\nexport function normalizeGCPCredentials(\n credentials: string | GCPCredentials\n): GCPCredentials {\n return typeof credentials === \"string\"\n ? Object.freeze(JSON.parse(credentials))\n : Object.isFrozen(credentials)\n ? credentials\n : Object.freeze(credentials);\n}\n\n/**\n * Imports and returns the private key from GCP credentials for JWT signing.\n *\n * This function extracts the RSA private key from the service account credentials\n * and imports it using the PKCS#8 format with the RS256 algorithm. The returned\n * key can be used to sign JWTs for authentication with Google Cloud services.\n *\n * @param credentials - The GCP service account credentials containing the private key\n * @returns A Promise that resolves to the imported private key suitable for JWT signing\n *\n * @throws {Error} If the private key format is invalid or cannot be imported\n *\n * @example\n * ```typescript\n * const credentials = normalizeGCPCredentials(credentialsJson);\n * const privateKey = await getGCPPrivateKey(credentials);\n * // Use privateKey for signing operations\n * ```\n */\nexport function getGCPPrivateKey(credentials: GCPCredentials) {\n return importPKCS8(credentials.private_key, \"RS256\");\n}\n\n/**\n * Generates a custom JWT token for authenticating with Google Cloud services.\n *\n * This function creates a signed JWT (JSON Web Token) using the service account's\n * private key. The token includes standard claims such as issuer, audience, subject,\n * issued-at time, and expiration time. This custom token can be exchanged for an\n * access token or ID token through Google's OAuth2 token endpoint.\n *\n * The generated JWT is valid for 1 hour and follows the OAuth 2.0 JWT Bearer\n * Token flow as specified by RFC 7523.\n *\n * @param credentials - The GCP service account credentials used to sign the JWT\n * @returns A Promise that resolves to the signed JWT string\n *\n * @throws {Error} If the private key cannot be imported or the JWT cannot be signed\n *\n * @see https://datatracker.ietf.org/doc/html/rfc7523\n * @see https://cloud.google.com/iam/docs/creating-short-lived-service-account-credentials\n *\n * @example\n * ```typescript\n * const credentials = normalizeGCPCredentials(credentialsJson);\n * const customToken = await getGCPCustomToken(credentials);\n * // Use customToken to request an access token\n * ```\n */\nexport async function getGCPCustomToken(credentials: GCPCredentials) {\n const privateKey = await getGCPPrivateKey(credentials);\n const customToken = await new SignJWT()\n .setIssuer(credentials.client_email)\n .setAudience(credentials.token_uri)\n .setSubject(credentials.client_email)\n .setProtectedHeader({ alg: \"RS256\" })\n .setIssuedAt()\n .setExpirationTime(\"1h\")\n .sign(privateKey);\n return customToken;\n}\n\n/**\n * Retrieves an access token for authenticating with Google Cloud APIs.\n *\n * - Tokens are cached based on the token URI and private key ID\n * - Cache entries are valid for up to 1 hour\n * - Expired tokens trigger automatic refresh\n * - Multiple concurrent requests for the same token are deduplicated\n *\n * The function handles both access tokens and ID tokens, automatically detecting\n * which type is returned by the token endpoint and extracting the appropriate\n * expiration time.\n *\n * @param credentials - The GCP service account credentials used to request the token\n * @returns A Promise that resolves to the access token string\n *\n * @throws {AuthError} If the token request fails (e.g., invalid credentials,\n * network error, or API error)\n *\n * @see https://cloud.google.com/iam/docs/creating-short-lived-service-account-credentials\n * @see https://datatracker.ietf.org/doc/html/rfc7523\n *\n * @example\n * ```typescript\n * const credentials = normalizeGCPCredentials(credentialsJson);\n * const accessToken = await getGCPCredentialsAccessToken(credentials);\n *\n * // Use the access token in API requests\n * const response = await fetch('https://api.google.com/...', {\n * headers: {\n * 'Authorization': `Bearer ${accessToken}`\n * }\n * });\n * ```\n *\n * @remarks\n * The caching mechanism uses a two-level validation:\n * 1. Cache entry age (must be less than CACHE_MAX_AGE)\n * 2. Token expiration time (must not be expired)\n *\n * This ensures that even if a cache entry is fresh, an expired token\n * will trigger a refresh request.\n */\nexport async function getGCPCredentialsAccessToken(\n credentials: GCPCredentials\n): Promise<string> {\n const tokenUrl = credentials.token_uri;\n\n const cacheKeyUrl = new URL(tokenUrl);\n cacheKeyUrl.searchParams.set(\"key\", credentials.private_key_id);\n const cacheKey = cacheKeyUrl.toString();\n\n // Attempt to retrieve the token from the cache\n const cacheValue = cache.get(cacheKey);\n let now = Math.floor(Date.now() / 1000);\n\n if (cacheValue) {\n if (cacheValue.created > now - CACHE_MAX_AGE) {\n let token = await cacheValue.promise;\n\n if (token.expires > now) {\n return token.token;\n } else {\n const nextValue = cache.get(cacheKey);\n\n if (nextValue && nextValue !== cacheValue) {\n token = await nextValue.promise;\n if (token.expires > now) {\n return token.token;\n } else {\n cache.delete(cacheKey);\n }\n }\n }\n } else {\n cache.delete(cacheKey);\n }\n }\n\n const promise = iife(async () => {\n now = Math.floor(Date.now() / 1000);\n\n // Request a new token from the Google Cloud API\n const jwt = await getGCPCustomToken(credentials);\n const body = new URLSearchParams();\n body.append(\"grant_type\", \"urn:ietf:params:oauth:grant-type:jwt-bearer\");\n body.append(\"assertion\", jwt);\n const res = await fetch(tokenUrl, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body,\n });\n\n if (!res.ok) {\n throw await AuthError.fromResponse(res);\n }\n\n const data = await res.json();\n\n if (\"id_token\" in data) {\n const claims = decodeJwt(data.id_token);\n return { token: data.id_token, expires: claims.exp as number };\n }\n\n const lastModified = res.headers.get(\"last-modified\");\n const expires = lastModified\n ? Math.floor(new Date(lastModified).valueOf() / 1000) + data.expires_in\n : now + data.expires_in;\n\n return { expires, token: data.access_token };\n });\n\n cache.set(cacheKey, { created: now, promise });\n return await promise.then((data) => data.token);\n}\n"],"mappings":";;;;;;;;;;;AAoDA,MAAM,wBAAQ,IAAI,KAAyB;AAC3C,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BtB,SAAgB,wBACd,aACgB;AAChB,QAAO,OAAO,gBAAgB,WAC1B,OAAO,OAAO,KAAK,MAAM,YAAY,CAAC,GACtC,OAAO,SAAS,YAAY,GAC5B,cACA,OAAO,OAAO,YAAY;;;;;;;;;;;;;;;;;;;;;AAsBhC,SAAgB,iBAAiB,aAA6B;AAC5D,QAAO,YAAY,YAAY,aAAa,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BtD,eAAsB,kBAAkB,aAA6B;CACnE,MAAM,aAAa,MAAM,iBAAiB,YAAY;AAStD,QARoB,MAAM,IAAI,SAAS,CACpC,UAAU,YAAY,aAAa,CACnC,YAAY,YAAY,UAAU,CAClC,WAAW,YAAY,aAAa,CACpC,mBAAmB,EAAE,KAAK,SAAS,CAAC,CACpC,aAAa,CACb,kBAAkB,KAAK,CACvB,KAAK,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8CrB,eAAsB,6BACpB,aACiB;CACjB,MAAM,WAAW,YAAY;CAE7B,MAAM,cAAc,IAAI,IAAI,SAAS;AACrC,aAAY,aAAa,IAAI,OAAO,YAAY,eAAe;CAC/D,MAAM,WAAW,YAAY,UAAU;CAGvC,MAAM,aAAa,MAAM,IAAI,SAAS;CACtC,IAAI,MAAM,KAAK,MAAM,KAAK,KAAK,GAAG,IAAK;AAEvC,KAAI,WACF,KAAI,WAAW,UAAU,MAAM,eAAe;EAC5C,IAAI,QAAQ,MAAM,WAAW;AAE7B,MAAI,MAAM,UAAU,IAClB,QAAO,MAAM;OACR;GACL,MAAM,YAAY,MAAM,IAAI,SAAS;AAErC,OAAI,aAAa,cAAc,YAAY;AACzC,YAAQ,MAAM,UAAU;AACxB,QAAI,MAAM,UAAU,IAClB,QAAO,MAAM;QAEb,OAAM,OAAO,SAAS;;;OAK5B,OAAM,OAAO,SAAS;CAI1B,MAAM,UAAU,KAAK,YAAY;AAC/B,QAAM,KAAK,MAAM,KAAK,KAAK,GAAG,IAAK;EAGnC,MAAM,MAAM,MAAM,kBAAkB,YAAY;EAChD,MAAM,OAAO,IAAI,iBAAiB;AAClC,OAAK,OAAO,cAAc,8CAA8C;AACxE,OAAK,OAAO,aAAa,IAAI;EAC7B,MAAM,MAAM,MAAM,MAAM,UAAU;GAChC,QAAQ;GACR,SAAS,EAAE,gBAAgB,qCAAqC;GAChE;GACD,CAAC;AAEF,MAAI,CAAC,IAAI,GACP,OAAM,MAAM,UAAU,aAAa,IAAI;EAGzC,MAAM,OAAO,MAAM,IAAI,MAAM;AAE7B,MAAI,cAAc,MAAM;GACtB,MAAM,SAAS,UAAU,KAAK,SAAS;AACvC,UAAO;IAAE,OAAO,KAAK;IAAU,SAAS,OAAO;IAAe;;EAGhE,MAAM,eAAe,IAAI,QAAQ,IAAI,gBAAgB;AAKrD,SAAO;GAAE,SAJO,eACZ,KAAK,MAAM,IAAI,KAAK,aAAa,CAAC,SAAS,GAAG,IAAK,GAAG,KAAK,aAC3D,MAAM,KAAK;GAEG,OAAO,KAAK;GAAc;GAC5C;AAEF,OAAM,IAAI,UAAU;EAAE,SAAS;EAAK;EAAS,CAAC;AAC9C,QAAO,MAAM,QAAQ,MAAM,SAAS,KAAK,MAAM"}
|
package/dist/utils/handler.cjs
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
let _langchain_core_callbacks_base = require("@langchain/core/callbacks/base");
|
|
2
|
-
|
|
3
2
|
//#region src/utils/handler.ts
|
|
4
3
|
var GoogleRequestCallbackHandler = class extends _langchain_core_callbacks_base.BaseCallbackHandler {
|
|
5
4
|
customEventInfo(eventName) {
|
|
@@ -58,9 +57,9 @@ var GoogleRequestRecorder = class extends GoogleRequestCallbackHandler {
|
|
|
58
57
|
this.chunk.push(data);
|
|
59
58
|
}
|
|
60
59
|
};
|
|
61
|
-
|
|
62
60
|
//#endregion
|
|
63
61
|
exports.GoogleRequestCallbackHandler = GoogleRequestCallbackHandler;
|
|
64
62
|
exports.GoogleRequestLogger = GoogleRequestLogger;
|
|
65
63
|
exports.GoogleRequestRecorder = GoogleRequestRecorder;
|
|
64
|
+
|
|
66
65
|
//# sourceMappingURL=handler.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handler.cjs","names":["BaseCallbackHandler"],"sources":["../../src/utils/handler.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { BaseCallbackHandler } from \"@langchain/core/callbacks/base\";\n\nexport interface GoogleCustomEventInfo {\n subEvent: string;\n module: string;\n}\n\nexport abstract class GoogleRequestCallbackHandler extends BaseCallbackHandler {\n customEventInfo(eventName: string): GoogleCustomEventInfo {\n const names = eventName.split(\"-\");\n return {\n subEvent: names[1],\n module: names[2],\n };\n }\n\n abstract handleCustomRequestEvent(\n eventName: string,\n eventInfo: GoogleCustomEventInfo,\n data: any,\n runId: string,\n tags?: string[],\n metadata?: Record<string, any>\n ): any;\n\n abstract handleCustomResponseEvent(\n eventName: string,\n eventInfo: GoogleCustomEventInfo,\n data: any,\n runId: string,\n tags?: string[],\n metadata?: Record<string, any>\n ): any;\n\n abstract handleCustomChunkEvent(\n eventName: string,\n eventInfo: GoogleCustomEventInfo,\n data: any,\n runId: string,\n tags?: string[],\n metadata?: Record<string, any>\n ): any;\n\n handleCustomEvent(\n eventName: string,\n data: any,\n runId: string,\n tags?: string[],\n metadata?: Record<string, any>\n ): any {\n if (!eventName) {\n return undefined;\n }\n const eventInfo = this.customEventInfo(eventName);\n switch (eventInfo.subEvent) {\n case \"request\":\n return this.handleCustomRequestEvent(\n eventName,\n eventInfo,\n data,\n runId,\n tags,\n metadata\n );\n case \"response\":\n return this.handleCustomResponseEvent(\n eventName,\n eventInfo,\n data,\n runId,\n tags,\n metadata\n );\n case \"chunk\":\n return this.handleCustomChunkEvent(\n eventName,\n eventInfo,\n data,\n runId,\n tags,\n metadata\n );\n default:\n console.error(\n `Unexpected eventInfo for ${eventName} ${JSON.stringify(\n eventInfo,\n null,\n 1\n )}`\n );\n }\n }\n}\n\nexport class GoogleRequestLogger extends GoogleRequestCallbackHandler {\n name: string = \"GoogleRequestLogger\";\n\n shortenStringLength = 40;\n\n log(eventName: string, data: any, tags?: string[]): undefined {\n const splitLen = this.shortenStringLength;\n const half = splitLen / 2;\n const replacer = (_key: string, value: any) => {\n if (\n typeof value === \"string\" &&\n splitLen > 0 &&\n value.length > splitLen\n ) {\n return `${value.substring(0, half)}...${value.substring(\n value.length - half\n )}`;\n }\n return value;\n };\n const tagStr = tags ? `[${tags}]` : \"[]\";\n console.log(`${eventName} ${tagStr} ${JSON.stringify(data, replacer, 1)}`);\n }\n\n handleCustomRequestEvent(\n eventName: string,\n _eventInfo: GoogleCustomEventInfo,\n data: any,\n _runId: string,\n tags?: string[],\n _metadata?: Record<string, any>\n ): any {\n this.log(eventName, data, tags);\n }\n\n handleCustomResponseEvent(\n eventName: string,\n _eventInfo: GoogleCustomEventInfo,\n data: any,\n _runId: string,\n tags?: string[],\n _metadata?: Record<string, any>\n ): any {\n this.log(eventName, data, tags);\n }\n\n handleCustomChunkEvent(\n eventName: string,\n _eventInfo: GoogleCustomEventInfo,\n data: any,\n _runId: string,\n tags?: string[],\n _metadata?: Record<string, any>\n ): any {\n this.log(eventName, data, tags);\n }\n}\n\nexport class GoogleRequestRecorder extends GoogleRequestCallbackHandler {\n name = \"GoogleRequestRecorder\";\n\n request: any = {};\n\n response: any = {};\n\n chunk: any[] = [];\n\n handleCustomRequestEvent(\n _eventName: string,\n _eventInfo: GoogleCustomEventInfo,\n data: any,\n _runId: string,\n _tags?: string[],\n _metadata?: Record<string, any>\n ): any {\n this.request = data;\n }\n\n handleCustomResponseEvent(\n _eventName: string,\n _eventInfo: GoogleCustomEventInfo,\n data: any,\n _runId: string,\n _tags?: string[],\n _metadata?: Record<string, any>\n ): any {\n this.response = data;\n }\n\n handleCustomChunkEvent(\n _eventName: string,\n _eventInfo: GoogleCustomEventInfo,\n data: any,\n _runId: string,\n _tags?: string[],\n _metadata?: Record<string, any>\n ): any {\n this.chunk.push(data);\n }\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"handler.cjs","names":["BaseCallbackHandler"],"sources":["../../src/utils/handler.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { BaseCallbackHandler } from \"@langchain/core/callbacks/base\";\n\nexport interface GoogleCustomEventInfo {\n subEvent: string;\n module: string;\n}\n\nexport abstract class GoogleRequestCallbackHandler extends BaseCallbackHandler {\n customEventInfo(eventName: string): GoogleCustomEventInfo {\n const names = eventName.split(\"-\");\n return {\n subEvent: names[1],\n module: names[2],\n };\n }\n\n abstract handleCustomRequestEvent(\n eventName: string,\n eventInfo: GoogleCustomEventInfo,\n data: any,\n runId: string,\n tags?: string[],\n metadata?: Record<string, any>\n ): any;\n\n abstract handleCustomResponseEvent(\n eventName: string,\n eventInfo: GoogleCustomEventInfo,\n data: any,\n runId: string,\n tags?: string[],\n metadata?: Record<string, any>\n ): any;\n\n abstract handleCustomChunkEvent(\n eventName: string,\n eventInfo: GoogleCustomEventInfo,\n data: any,\n runId: string,\n tags?: string[],\n metadata?: Record<string, any>\n ): any;\n\n handleCustomEvent(\n eventName: string,\n data: any,\n runId: string,\n tags?: string[],\n metadata?: Record<string, any>\n ): any {\n if (!eventName) {\n return undefined;\n }\n const eventInfo = this.customEventInfo(eventName);\n switch (eventInfo.subEvent) {\n case \"request\":\n return this.handleCustomRequestEvent(\n eventName,\n eventInfo,\n data,\n runId,\n tags,\n metadata\n );\n case \"response\":\n return this.handleCustomResponseEvent(\n eventName,\n eventInfo,\n data,\n runId,\n tags,\n metadata\n );\n case \"chunk\":\n return this.handleCustomChunkEvent(\n eventName,\n eventInfo,\n data,\n runId,\n tags,\n metadata\n );\n default:\n console.error(\n `Unexpected eventInfo for ${eventName} ${JSON.stringify(\n eventInfo,\n null,\n 1\n )}`\n );\n }\n }\n}\n\nexport class GoogleRequestLogger extends GoogleRequestCallbackHandler {\n name: string = \"GoogleRequestLogger\";\n\n shortenStringLength = 40;\n\n log(eventName: string, data: any, tags?: string[]): undefined {\n const splitLen = this.shortenStringLength;\n const half = splitLen / 2;\n const replacer = (_key: string, value: any) => {\n if (\n typeof value === \"string\" &&\n splitLen > 0 &&\n value.length > splitLen\n ) {\n return `${value.substring(0, half)}...${value.substring(\n value.length - half\n )}`;\n }\n return value;\n };\n const tagStr = tags ? `[${tags}]` : \"[]\";\n console.log(`${eventName} ${tagStr} ${JSON.stringify(data, replacer, 1)}`);\n }\n\n handleCustomRequestEvent(\n eventName: string,\n _eventInfo: GoogleCustomEventInfo,\n data: any,\n _runId: string,\n tags?: string[],\n _metadata?: Record<string, any>\n ): any {\n this.log(eventName, data, tags);\n }\n\n handleCustomResponseEvent(\n eventName: string,\n _eventInfo: GoogleCustomEventInfo,\n data: any,\n _runId: string,\n tags?: string[],\n _metadata?: Record<string, any>\n ): any {\n this.log(eventName, data, tags);\n }\n\n handleCustomChunkEvent(\n eventName: string,\n _eventInfo: GoogleCustomEventInfo,\n data: any,\n _runId: string,\n tags?: string[],\n _metadata?: Record<string, any>\n ): any {\n this.log(eventName, data, tags);\n }\n}\n\nexport class GoogleRequestRecorder extends GoogleRequestCallbackHandler {\n name = \"GoogleRequestRecorder\";\n\n request: any = {};\n\n response: any = {};\n\n chunk: any[] = [];\n\n handleCustomRequestEvent(\n _eventName: string,\n _eventInfo: GoogleCustomEventInfo,\n data: any,\n _runId: string,\n _tags?: string[],\n _metadata?: Record<string, any>\n ): any {\n this.request = data;\n }\n\n handleCustomResponseEvent(\n _eventName: string,\n _eventInfo: GoogleCustomEventInfo,\n data: any,\n _runId: string,\n _tags?: string[],\n _metadata?: Record<string, any>\n ): any {\n this.response = data;\n }\n\n handleCustomChunkEvent(\n _eventName: string,\n _eventInfo: GoogleCustomEventInfo,\n data: any,\n _runId: string,\n _tags?: string[],\n _metadata?: Record<string, any>\n ): any {\n this.chunk.push(data);\n }\n}\n"],"mappings":";;AAQA,IAAsB,+BAAtB,cAA2DA,+BAAAA,oBAAoB;CAC7E,gBAAgB,WAA0C;EACxD,MAAM,QAAQ,UAAU,MAAM,IAAI;AAClC,SAAO;GACL,UAAU,MAAM;GAChB,QAAQ,MAAM;GACf;;CA8BH,kBACE,WACA,MACA,OACA,MACA,UACK;AACL,MAAI,CAAC,UACH;EAEF,MAAM,YAAY,KAAK,gBAAgB,UAAU;AACjD,UAAQ,UAAU,UAAlB;GACE,KAAK,UACH,QAAO,KAAK,yBACV,WACA,WACA,MACA,OACA,MACA,SACD;GACH,KAAK,WACH,QAAO,KAAK,0BACV,WACA,WACA,MACA,OACA,MACA,SACD;GACH,KAAK,QACH,QAAO,KAAK,uBACV,WACA,WACA,MACA,OACA,MACA,SACD;GACH,QACE,SAAQ,MACN,4BAA4B,UAAU,GAAG,KAAK,UAC5C,WACA,MACA,EACD,GACF;;;;AAKT,IAAa,sBAAb,cAAyC,6BAA6B;CACpE,OAAe;CAEf,sBAAsB;CAEtB,IAAI,WAAmB,MAAW,MAA4B;EAC5D,MAAM,WAAW,KAAK;EACtB,MAAM,OAAO,WAAW;EACxB,MAAM,YAAY,MAAc,UAAe;AAC7C,OACE,OAAO,UAAU,YACjB,WAAW,KACX,MAAM,SAAS,SAEf,QAAO,GAAG,MAAM,UAAU,GAAG,KAAK,CAAC,KAAK,MAAM,UAC5C,MAAM,SAAS,KAChB;AAEH,UAAO;;EAET,MAAM,SAAS,OAAO,IAAI,KAAK,KAAK;AACpC,UAAQ,IAAI,GAAG,UAAU,GAAG,OAAO,GAAG,KAAK,UAAU,MAAM,UAAU,EAAE,GAAG;;CAG5E,yBACE,WACA,YACA,MACA,QACA,MACA,WACK;AACL,OAAK,IAAI,WAAW,MAAM,KAAK;;CAGjC,0BACE,WACA,YACA,MACA,QACA,MACA,WACK;AACL,OAAK,IAAI,WAAW,MAAM,KAAK;;CAGjC,uBACE,WACA,YACA,MACA,QACA,MACA,WACK;AACL,OAAK,IAAI,WAAW,MAAM,KAAK;;;AAInC,IAAa,wBAAb,cAA2C,6BAA6B;CACtE,OAAO;CAEP,UAAe,EAAE;CAEjB,WAAgB,EAAE;CAElB,QAAe,EAAE;CAEjB,yBACE,YACA,YACA,MACA,QACA,OACA,WACK;AACL,OAAK,UAAU;;CAGjB,0BACE,YACA,YACA,MACA,QACA,OACA,WACK;AACL,OAAK,WAAW;;CAGlB,uBACE,YACA,YACA,MACA,QACA,OACA,WACK;AACL,OAAK,MAAM,KAAK,KAAK"}
|
package/dist/utils/handler.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { BaseCallbackHandler } from "@langchain/core/callbacks/base";
|
|
2
|
-
|
|
3
2
|
//#region src/utils/handler.ts
|
|
4
3
|
var GoogleRequestCallbackHandler = class extends BaseCallbackHandler {
|
|
5
4
|
customEventInfo(eventName) {
|
|
@@ -58,7 +57,7 @@ var GoogleRequestRecorder = class extends GoogleRequestCallbackHandler {
|
|
|
58
57
|
this.chunk.push(data);
|
|
59
58
|
}
|
|
60
59
|
};
|
|
61
|
-
|
|
62
60
|
//#endregion
|
|
63
61
|
export { GoogleRequestCallbackHandler, GoogleRequestLogger, GoogleRequestRecorder };
|
|
62
|
+
|
|
64
63
|
//# sourceMappingURL=handler.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handler.js","names":[],"sources":["../../src/utils/handler.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { BaseCallbackHandler } from \"@langchain/core/callbacks/base\";\n\nexport interface GoogleCustomEventInfo {\n subEvent: string;\n module: string;\n}\n\nexport abstract class GoogleRequestCallbackHandler extends BaseCallbackHandler {\n customEventInfo(eventName: string): GoogleCustomEventInfo {\n const names = eventName.split(\"-\");\n return {\n subEvent: names[1],\n module: names[2],\n };\n }\n\n abstract handleCustomRequestEvent(\n eventName: string,\n eventInfo: GoogleCustomEventInfo,\n data: any,\n runId: string,\n tags?: string[],\n metadata?: Record<string, any>\n ): any;\n\n abstract handleCustomResponseEvent(\n eventName: string,\n eventInfo: GoogleCustomEventInfo,\n data: any,\n runId: string,\n tags?: string[],\n metadata?: Record<string, any>\n ): any;\n\n abstract handleCustomChunkEvent(\n eventName: string,\n eventInfo: GoogleCustomEventInfo,\n data: any,\n runId: string,\n tags?: string[],\n metadata?: Record<string, any>\n ): any;\n\n handleCustomEvent(\n eventName: string,\n data: any,\n runId: string,\n tags?: string[],\n metadata?: Record<string, any>\n ): any {\n if (!eventName) {\n return undefined;\n }\n const eventInfo = this.customEventInfo(eventName);\n switch (eventInfo.subEvent) {\n case \"request\":\n return this.handleCustomRequestEvent(\n eventName,\n eventInfo,\n data,\n runId,\n tags,\n metadata\n );\n case \"response\":\n return this.handleCustomResponseEvent(\n eventName,\n eventInfo,\n data,\n runId,\n tags,\n metadata\n );\n case \"chunk\":\n return this.handleCustomChunkEvent(\n eventName,\n eventInfo,\n data,\n runId,\n tags,\n metadata\n );\n default:\n console.error(\n `Unexpected eventInfo for ${eventName} ${JSON.stringify(\n eventInfo,\n null,\n 1\n )}`\n );\n }\n }\n}\n\nexport class GoogleRequestLogger extends GoogleRequestCallbackHandler {\n name: string = \"GoogleRequestLogger\";\n\n shortenStringLength = 40;\n\n log(eventName: string, data: any, tags?: string[]): undefined {\n const splitLen = this.shortenStringLength;\n const half = splitLen / 2;\n const replacer = (_key: string, value: any) => {\n if (\n typeof value === \"string\" &&\n splitLen > 0 &&\n value.length > splitLen\n ) {\n return `${value.substring(0, half)}...${value.substring(\n value.length - half\n )}`;\n }\n return value;\n };\n const tagStr = tags ? `[${tags}]` : \"[]\";\n console.log(`${eventName} ${tagStr} ${JSON.stringify(data, replacer, 1)}`);\n }\n\n handleCustomRequestEvent(\n eventName: string,\n _eventInfo: GoogleCustomEventInfo,\n data: any,\n _runId: string,\n tags?: string[],\n _metadata?: Record<string, any>\n ): any {\n this.log(eventName, data, tags);\n }\n\n handleCustomResponseEvent(\n eventName: string,\n _eventInfo: GoogleCustomEventInfo,\n data: any,\n _runId: string,\n tags?: string[],\n _metadata?: Record<string, any>\n ): any {\n this.log(eventName, data, tags);\n }\n\n handleCustomChunkEvent(\n eventName: string,\n _eventInfo: GoogleCustomEventInfo,\n data: any,\n _runId: string,\n tags?: string[],\n _metadata?: Record<string, any>\n ): any {\n this.log(eventName, data, tags);\n }\n}\n\nexport class GoogleRequestRecorder extends GoogleRequestCallbackHandler {\n name = \"GoogleRequestRecorder\";\n\n request: any = {};\n\n response: any = {};\n\n chunk: any[] = [];\n\n handleCustomRequestEvent(\n _eventName: string,\n _eventInfo: GoogleCustomEventInfo,\n data: any,\n _runId: string,\n _tags?: string[],\n _metadata?: Record<string, any>\n ): any {\n this.request = data;\n }\n\n handleCustomResponseEvent(\n _eventName: string,\n _eventInfo: GoogleCustomEventInfo,\n data: any,\n _runId: string,\n _tags?: string[],\n _metadata?: Record<string, any>\n ): any {\n this.response = data;\n }\n\n handleCustomChunkEvent(\n _eventName: string,\n _eventInfo: GoogleCustomEventInfo,\n data: any,\n _runId: string,\n _tags?: string[],\n _metadata?: Record<string, any>\n ): any {\n this.chunk.push(data);\n }\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"handler.js","names":[],"sources":["../../src/utils/handler.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { BaseCallbackHandler } from \"@langchain/core/callbacks/base\";\n\nexport interface GoogleCustomEventInfo {\n subEvent: string;\n module: string;\n}\n\nexport abstract class GoogleRequestCallbackHandler extends BaseCallbackHandler {\n customEventInfo(eventName: string): GoogleCustomEventInfo {\n const names = eventName.split(\"-\");\n return {\n subEvent: names[1],\n module: names[2],\n };\n }\n\n abstract handleCustomRequestEvent(\n eventName: string,\n eventInfo: GoogleCustomEventInfo,\n data: any,\n runId: string,\n tags?: string[],\n metadata?: Record<string, any>\n ): any;\n\n abstract handleCustomResponseEvent(\n eventName: string,\n eventInfo: GoogleCustomEventInfo,\n data: any,\n runId: string,\n tags?: string[],\n metadata?: Record<string, any>\n ): any;\n\n abstract handleCustomChunkEvent(\n eventName: string,\n eventInfo: GoogleCustomEventInfo,\n data: any,\n runId: string,\n tags?: string[],\n metadata?: Record<string, any>\n ): any;\n\n handleCustomEvent(\n eventName: string,\n data: any,\n runId: string,\n tags?: string[],\n metadata?: Record<string, any>\n ): any {\n if (!eventName) {\n return undefined;\n }\n const eventInfo = this.customEventInfo(eventName);\n switch (eventInfo.subEvent) {\n case \"request\":\n return this.handleCustomRequestEvent(\n eventName,\n eventInfo,\n data,\n runId,\n tags,\n metadata\n );\n case \"response\":\n return this.handleCustomResponseEvent(\n eventName,\n eventInfo,\n data,\n runId,\n tags,\n metadata\n );\n case \"chunk\":\n return this.handleCustomChunkEvent(\n eventName,\n eventInfo,\n data,\n runId,\n tags,\n metadata\n );\n default:\n console.error(\n `Unexpected eventInfo for ${eventName} ${JSON.stringify(\n eventInfo,\n null,\n 1\n )}`\n );\n }\n }\n}\n\nexport class GoogleRequestLogger extends GoogleRequestCallbackHandler {\n name: string = \"GoogleRequestLogger\";\n\n shortenStringLength = 40;\n\n log(eventName: string, data: any, tags?: string[]): undefined {\n const splitLen = this.shortenStringLength;\n const half = splitLen / 2;\n const replacer = (_key: string, value: any) => {\n if (\n typeof value === \"string\" &&\n splitLen > 0 &&\n value.length > splitLen\n ) {\n return `${value.substring(0, half)}...${value.substring(\n value.length - half\n )}`;\n }\n return value;\n };\n const tagStr = tags ? `[${tags}]` : \"[]\";\n console.log(`${eventName} ${tagStr} ${JSON.stringify(data, replacer, 1)}`);\n }\n\n handleCustomRequestEvent(\n eventName: string,\n _eventInfo: GoogleCustomEventInfo,\n data: any,\n _runId: string,\n tags?: string[],\n _metadata?: Record<string, any>\n ): any {\n this.log(eventName, data, tags);\n }\n\n handleCustomResponseEvent(\n eventName: string,\n _eventInfo: GoogleCustomEventInfo,\n data: any,\n _runId: string,\n tags?: string[],\n _metadata?: Record<string, any>\n ): any {\n this.log(eventName, data, tags);\n }\n\n handleCustomChunkEvent(\n eventName: string,\n _eventInfo: GoogleCustomEventInfo,\n data: any,\n _runId: string,\n tags?: string[],\n _metadata?: Record<string, any>\n ): any {\n this.log(eventName, data, tags);\n }\n}\n\nexport class GoogleRequestRecorder extends GoogleRequestCallbackHandler {\n name = \"GoogleRequestRecorder\";\n\n request: any = {};\n\n response: any = {};\n\n chunk: any[] = [];\n\n handleCustomRequestEvent(\n _eventName: string,\n _eventInfo: GoogleCustomEventInfo,\n data: any,\n _runId: string,\n _tags?: string[],\n _metadata?: Record<string, any>\n ): any {\n this.request = data;\n }\n\n handleCustomResponseEvent(\n _eventName: string,\n _eventInfo: GoogleCustomEventInfo,\n data: any,\n _runId: string,\n _tags?: string[],\n _metadata?: Record<string, any>\n ): any {\n this.response = data;\n }\n\n handleCustomChunkEvent(\n _eventName: string,\n _eventInfo: GoogleCustomEventInfo,\n data: any,\n _runId: string,\n _tags?: string[],\n _metadata?: Record<string, any>\n ): any {\n this.chunk.push(data);\n }\n}\n"],"mappings":";;AAQA,IAAsB,+BAAtB,cAA2D,oBAAoB;CAC7E,gBAAgB,WAA0C;EACxD,MAAM,QAAQ,UAAU,MAAM,IAAI;AAClC,SAAO;GACL,UAAU,MAAM;GAChB,QAAQ,MAAM;GACf;;CA8BH,kBACE,WACA,MACA,OACA,MACA,UACK;AACL,MAAI,CAAC,UACH;EAEF,MAAM,YAAY,KAAK,gBAAgB,UAAU;AACjD,UAAQ,UAAU,UAAlB;GACE,KAAK,UACH,QAAO,KAAK,yBACV,WACA,WACA,MACA,OACA,MACA,SACD;GACH,KAAK,WACH,QAAO,KAAK,0BACV,WACA,WACA,MACA,OACA,MACA,SACD;GACH,KAAK,QACH,QAAO,KAAK,uBACV,WACA,WACA,MACA,OACA,MACA,SACD;GACH,QACE,SAAQ,MACN,4BAA4B,UAAU,GAAG,KAAK,UAC5C,WACA,MACA,EACD,GACF;;;;AAKT,IAAa,sBAAb,cAAyC,6BAA6B;CACpE,OAAe;CAEf,sBAAsB;CAEtB,IAAI,WAAmB,MAAW,MAA4B;EAC5D,MAAM,WAAW,KAAK;EACtB,MAAM,OAAO,WAAW;EACxB,MAAM,YAAY,MAAc,UAAe;AAC7C,OACE,OAAO,UAAU,YACjB,WAAW,KACX,MAAM,SAAS,SAEf,QAAO,GAAG,MAAM,UAAU,GAAG,KAAK,CAAC,KAAK,MAAM,UAC5C,MAAM,SAAS,KAChB;AAEH,UAAO;;EAET,MAAM,SAAS,OAAO,IAAI,KAAK,KAAK;AACpC,UAAQ,IAAI,GAAG,UAAU,GAAG,OAAO,GAAG,KAAK,UAAU,MAAM,UAAU,EAAE,GAAG;;CAG5E,yBACE,WACA,YACA,MACA,QACA,MACA,WACK;AACL,OAAK,IAAI,WAAW,MAAM,KAAK;;CAGjC,0BACE,WACA,YACA,MACA,QACA,MACA,WACK;AACL,OAAK,IAAI,WAAW,MAAM,KAAK;;CAGjC,uBACE,WACA,YACA,MACA,QACA,MACA,WACK;AACL,OAAK,IAAI,WAAW,MAAM,KAAK;;;AAInC,IAAa,wBAAb,cAA2C,6BAA6B;CACtE,OAAO;CAEP,UAAe,EAAE;CAEjB,WAAgB,EAAE;CAElB,QAAe,EAAE;CAEjB,yBACE,YACA,YACA,MACA,QACA,OACA,WACK;AACL,OAAK,UAAU;;CAGjB,0BACE,YACA,YACA,MACA,QACA,OACA,WACK;AACL,OAAK,WAAW;;CAGlB,uBACE,YACA,YACA,MACA,QACA,OACA,WACK;AACL,OAAK,MAAM,KAAK,KAAK"}
|
package/dist/utils/metadata.cjs
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
//#region src/utils/metadata.ts
|
|
3
2
|
/**
|
|
4
3
|
* Computes the delta between two token detail objects by subtracting
|
|
@@ -51,7 +50,7 @@ function subtractUsageMetadata(current, previous) {
|
|
|
51
50
|
output_token_details: subtractTokenDetails(current.output_token_details, previous.output_token_details)
|
|
52
51
|
};
|
|
53
52
|
}
|
|
54
|
-
|
|
55
53
|
//#endregion
|
|
56
54
|
exports.subtractUsageMetadata = subtractUsageMetadata;
|
|
55
|
+
|
|
57
56
|
//# sourceMappingURL=metadata.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metadata.cjs","names":[],"sources":["../../src/utils/metadata.ts"],"sourcesContent":["import type {\n UsageMetadata,\n InputTokenDetails,\n OutputTokenDetails,\n} from \"@langchain/core/messages\";\n\n/**\n * Computes the delta between two token detail objects by subtracting\n * each field in `previous` from the corresponding field in `current`.\n *\n * This is used to convert cumulative token detail counts (as reported\n * by the Gemini streaming API) into per-chunk deltas that can be\n * correctly summed by {@link mergeUsageMetadata} during chunk\n * concatenation.\n *\n * @param current - The cumulative token details from the current chunk.\n * @param previous - The cumulative token details from the previous chunk.\n * @returns The per-chunk delta, or `undefined` if `current` is undefined.\n */\nfunction subtractTokenDetails<T extends InputTokenDetails | OutputTokenDetails>(\n current?: T,\n previous?: T\n): T | undefined {\n if (!current) return undefined;\n if (!previous) return current;\n const result = {} as Record<string, number>;\n for (const key of Object.keys(current) as (keyof T)[]) {\n const cur = current[key] as number | undefined;\n const prev = previous[key] as number | undefined;\n if (cur !== undefined) {\n result[key as string] = cur - (prev ?? 0);\n }\n }\n return result as T;\n}\n\n/**\n * Computes the delta between two {@link UsageMetadata} objects by\n * subtracting each field in `previous` from the corresponding field in\n * `current`.\n *\n * The Gemini streaming API reports **cumulative** token counts on every\n * SSE chunk, but `@langchain/core`'s `mergeUsageMetadata` combines\n * values via addition. Emitting raw cumulative values on each\n * `AIMessageChunk` would therefore inflate the totals when chunks are\n * concatenated. This function converts cumulative values into per-chunk\n * deltas so that additive merging produces the correct final totals.\n *\n * @param current - The cumulative usage metadata from the current chunk.\n * @param previous - The cumulative usage metadata from the previous\n * chunk, or `undefined` for the first chunk in the stream.\n * @returns The per-chunk delta usage metadata.\n */\nexport function subtractUsageMetadata(\n current: UsageMetadata,\n previous?: UsageMetadata\n): UsageMetadata {\n if (!previous) return current;\n return {\n input_tokens: current.input_tokens - previous.input_tokens,\n output_tokens: current.output_tokens - previous.output_tokens,\n total_tokens: current.total_tokens - previous.total_tokens,\n input_token_details: subtractTokenDetails(\n current.input_token_details,\n previous.input_token_details\n ),\n output_token_details: subtractTokenDetails(\n current.output_token_details,\n previous.output_token_details\n ),\n };\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"metadata.cjs","names":[],"sources":["../../src/utils/metadata.ts"],"sourcesContent":["import type {\n UsageMetadata,\n InputTokenDetails,\n OutputTokenDetails,\n} from \"@langchain/core/messages\";\n\n/**\n * Computes the delta between two token detail objects by subtracting\n * each field in `previous` from the corresponding field in `current`.\n *\n * This is used to convert cumulative token detail counts (as reported\n * by the Gemini streaming API) into per-chunk deltas that can be\n * correctly summed by {@link mergeUsageMetadata} during chunk\n * concatenation.\n *\n * @param current - The cumulative token details from the current chunk.\n * @param previous - The cumulative token details from the previous chunk.\n * @returns The per-chunk delta, or `undefined` if `current` is undefined.\n */\nfunction subtractTokenDetails<T extends InputTokenDetails | OutputTokenDetails>(\n current?: T,\n previous?: T\n): T | undefined {\n if (!current) return undefined;\n if (!previous) return current;\n const result = {} as Record<string, number>;\n for (const key of Object.keys(current) as (keyof T)[]) {\n const cur = current[key] as number | undefined;\n const prev = previous[key] as number | undefined;\n if (cur !== undefined) {\n result[key as string] = cur - (prev ?? 0);\n }\n }\n return result as T;\n}\n\n/**\n * Computes the delta between two {@link UsageMetadata} objects by\n * subtracting each field in `previous` from the corresponding field in\n * `current`.\n *\n * The Gemini streaming API reports **cumulative** token counts on every\n * SSE chunk, but `@langchain/core`'s `mergeUsageMetadata` combines\n * values via addition. Emitting raw cumulative values on each\n * `AIMessageChunk` would therefore inflate the totals when chunks are\n * concatenated. This function converts cumulative values into per-chunk\n * deltas so that additive merging produces the correct final totals.\n *\n * @param current - The cumulative usage metadata from the current chunk.\n * @param previous - The cumulative usage metadata from the previous\n * chunk, or `undefined` for the first chunk in the stream.\n * @returns The per-chunk delta usage metadata.\n */\nexport function subtractUsageMetadata(\n current: UsageMetadata,\n previous?: UsageMetadata\n): UsageMetadata {\n if (!previous) return current;\n return {\n input_tokens: current.input_tokens - previous.input_tokens,\n output_tokens: current.output_tokens - previous.output_tokens,\n total_tokens: current.total_tokens - previous.total_tokens,\n input_token_details: subtractTokenDetails(\n current.input_token_details,\n previous.input_token_details\n ),\n output_token_details: subtractTokenDetails(\n current.output_token_details,\n previous.output_token_details\n ),\n };\n}\n"],"mappings":";;;;;;;;;;;;;;AAmBA,SAAS,qBACP,SACA,UACe;AACf,KAAI,CAAC,QAAS,QAAO,KAAA;AACrB,KAAI,CAAC,SAAU,QAAO;CACtB,MAAM,SAAS,EAAE;AACjB,MAAK,MAAM,OAAO,OAAO,KAAK,QAAQ,EAAiB;EACrD,MAAM,MAAM,QAAQ;EACpB,MAAM,OAAO,SAAS;AACtB,MAAI,QAAQ,KAAA,EACV,QAAO,OAAiB,OAAO,QAAQ;;AAG3C,QAAO;;;;;;;;;;;;;;;;;;;AAoBT,SAAgB,sBACd,SACA,UACe;AACf,KAAI,CAAC,SAAU,QAAO;AACtB,QAAO;EACL,cAAc,QAAQ,eAAe,SAAS;EAC9C,eAAe,QAAQ,gBAAgB,SAAS;EAChD,cAAc,QAAQ,eAAe,SAAS;EAC9C,qBAAqB,qBACnB,QAAQ,qBACR,SAAS,oBACV;EACD,sBAAsB,qBACpB,QAAQ,sBACR,SAAS,qBACV;EACF"}
|
package/dist/utils/metadata.js
CHANGED
|
@@ -50,7 +50,7 @@ function subtractUsageMetadata(current, previous) {
|
|
|
50
50
|
output_token_details: subtractTokenDetails(current.output_token_details, previous.output_token_details)
|
|
51
51
|
};
|
|
52
52
|
}
|
|
53
|
-
|
|
54
53
|
//#endregion
|
|
55
54
|
export { subtractUsageMetadata };
|
|
55
|
+
|
|
56
56
|
//# sourceMappingURL=metadata.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metadata.js","names":[],"sources":["../../src/utils/metadata.ts"],"sourcesContent":["import type {\n UsageMetadata,\n InputTokenDetails,\n OutputTokenDetails,\n} from \"@langchain/core/messages\";\n\n/**\n * Computes the delta between two token detail objects by subtracting\n * each field in `previous` from the corresponding field in `current`.\n *\n * This is used to convert cumulative token detail counts (as reported\n * by the Gemini streaming API) into per-chunk deltas that can be\n * correctly summed by {@link mergeUsageMetadata} during chunk\n * concatenation.\n *\n * @param current - The cumulative token details from the current chunk.\n * @param previous - The cumulative token details from the previous chunk.\n * @returns The per-chunk delta, or `undefined` if `current` is undefined.\n */\nfunction subtractTokenDetails<T extends InputTokenDetails | OutputTokenDetails>(\n current?: T,\n previous?: T\n): T | undefined {\n if (!current) return undefined;\n if (!previous) return current;\n const result = {} as Record<string, number>;\n for (const key of Object.keys(current) as (keyof T)[]) {\n const cur = current[key] as number | undefined;\n const prev = previous[key] as number | undefined;\n if (cur !== undefined) {\n result[key as string] = cur - (prev ?? 0);\n }\n }\n return result as T;\n}\n\n/**\n * Computes the delta between two {@link UsageMetadata} objects by\n * subtracting each field in `previous` from the corresponding field in\n * `current`.\n *\n * The Gemini streaming API reports **cumulative** token counts on every\n * SSE chunk, but `@langchain/core`'s `mergeUsageMetadata` combines\n * values via addition. Emitting raw cumulative values on each\n * `AIMessageChunk` would therefore inflate the totals when chunks are\n * concatenated. This function converts cumulative values into per-chunk\n * deltas so that additive merging produces the correct final totals.\n *\n * @param current - The cumulative usage metadata from the current chunk.\n * @param previous - The cumulative usage metadata from the previous\n * chunk, or `undefined` for the first chunk in the stream.\n * @returns The per-chunk delta usage metadata.\n */\nexport function subtractUsageMetadata(\n current: UsageMetadata,\n previous?: UsageMetadata\n): UsageMetadata {\n if (!previous) return current;\n return {\n input_tokens: current.input_tokens - previous.input_tokens,\n output_tokens: current.output_tokens - previous.output_tokens,\n total_tokens: current.total_tokens - previous.total_tokens,\n input_token_details: subtractTokenDetails(\n current.input_token_details,\n previous.input_token_details\n ),\n output_token_details: subtractTokenDetails(\n current.output_token_details,\n previous.output_token_details\n ),\n };\n}\n"],"mappings":";;;;;;;;;;;;;;AAmBA,SAAS,qBACP,SACA,UACe;AACf,KAAI,CAAC,QAAS,QAAO;AACrB,KAAI,CAAC,SAAU,QAAO;CACtB,MAAM,SAAS,EAAE;AACjB,MAAK,MAAM,OAAO,OAAO,KAAK,QAAQ,EAAiB;EACrD,MAAM,MAAM,QAAQ;EACpB,MAAM,OAAO,SAAS;AACtB,MAAI,QAAQ,
|
|
1
|
+
{"version":3,"file":"metadata.js","names":[],"sources":["../../src/utils/metadata.ts"],"sourcesContent":["import type {\n UsageMetadata,\n InputTokenDetails,\n OutputTokenDetails,\n} from \"@langchain/core/messages\";\n\n/**\n * Computes the delta between two token detail objects by subtracting\n * each field in `previous` from the corresponding field in `current`.\n *\n * This is used to convert cumulative token detail counts (as reported\n * by the Gemini streaming API) into per-chunk deltas that can be\n * correctly summed by {@link mergeUsageMetadata} during chunk\n * concatenation.\n *\n * @param current - The cumulative token details from the current chunk.\n * @param previous - The cumulative token details from the previous chunk.\n * @returns The per-chunk delta, or `undefined` if `current` is undefined.\n */\nfunction subtractTokenDetails<T extends InputTokenDetails | OutputTokenDetails>(\n current?: T,\n previous?: T\n): T | undefined {\n if (!current) return undefined;\n if (!previous) return current;\n const result = {} as Record<string, number>;\n for (const key of Object.keys(current) as (keyof T)[]) {\n const cur = current[key] as number | undefined;\n const prev = previous[key] as number | undefined;\n if (cur !== undefined) {\n result[key as string] = cur - (prev ?? 0);\n }\n }\n return result as T;\n}\n\n/**\n * Computes the delta between two {@link UsageMetadata} objects by\n * subtracting each field in `previous` from the corresponding field in\n * `current`.\n *\n * The Gemini streaming API reports **cumulative** token counts on every\n * SSE chunk, but `@langchain/core`'s `mergeUsageMetadata` combines\n * values via addition. Emitting raw cumulative values on each\n * `AIMessageChunk` would therefore inflate the totals when chunks are\n * concatenated. This function converts cumulative values into per-chunk\n * deltas so that additive merging produces the correct final totals.\n *\n * @param current - The cumulative usage metadata from the current chunk.\n * @param previous - The cumulative usage metadata from the previous\n * chunk, or `undefined` for the first chunk in the stream.\n * @returns The per-chunk delta usage metadata.\n */\nexport function subtractUsageMetadata(\n current: UsageMetadata,\n previous?: UsageMetadata\n): UsageMetadata {\n if (!previous) return current;\n return {\n input_tokens: current.input_tokens - previous.input_tokens,\n output_tokens: current.output_tokens - previous.output_tokens,\n total_tokens: current.total_tokens - previous.total_tokens,\n input_token_details: subtractTokenDetails(\n current.input_token_details,\n previous.input_token_details\n ),\n output_token_details: subtractTokenDetails(\n current.output_token_details,\n previous.output_token_details\n ),\n };\n}\n"],"mappings":";;;;;;;;;;;;;;AAmBA,SAAS,qBACP,SACA,UACe;AACf,KAAI,CAAC,QAAS,QAAO,KAAA;AACrB,KAAI,CAAC,SAAU,QAAO;CACtB,MAAM,SAAS,EAAE;AACjB,MAAK,MAAM,OAAO,OAAO,KAAK,QAAQ,EAAiB;EACrD,MAAM,MAAM,QAAQ;EACpB,MAAM,OAAO,SAAS;AACtB,MAAI,QAAQ,KAAA,EACV,QAAO,OAAiB,OAAO,QAAQ;;AAG3C,QAAO;;;;;;;;;;;;;;;;;;;AAoBT,SAAgB,sBACd,SACA,UACe;AACf,KAAI,CAAC,SAAU,QAAO;AACtB,QAAO;EACL,cAAc,QAAQ,eAAe,SAAS;EAC9C,eAAe,QAAQ,gBAAgB,SAAS;EAChD,cAAc,QAAQ,eAAe,SAAS;EAC9C,qBAAqB,qBACnB,QAAQ,qBACR,SAAS,oBACV;EACD,sBAAsB,qBACpB,QAAQ,sBACR,SAAS,qBACV;EACF"}
|
package/dist/utils/misc.cjs
CHANGED
package/dist/utils/misc.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"misc.cjs","names":[],"sources":["../../src/utils/misc.ts"],"sourcesContent":["/**\n * Immediately invokes the provided function and returns its result.\n */\nexport const iife = <T>(fn: () => T): T => fn();\n\n/**\n * Type utility that recursively resolves object shape, removing type aliases,\n * intersections, and unions for easier type introspection and error output.\n *\n * Usage:\n * type Input = { a: string } & { b: number };\n * type Output = Prettify<Input>; // { a: string; b: number }\n */\nexport type Prettify<T> = T extends object\n ? { [K in keyof T]: Prettify<T[K]> }\n : T;\n\n/**\n * Distributes over a string union and applies Lowercase only to\n * string-literal members, filtering out wide `string` / `string & {}`.\n */\nexport type LowercaseLiteral<T> = T extends string\n ? string extends T\n ? never\n : Lowercase<T>\n : never;\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"misc.cjs","names":[],"sources":["../../src/utils/misc.ts"],"sourcesContent":["/**\n * Immediately invokes the provided function and returns its result.\n */\nexport const iife = <T>(fn: () => T): T => fn();\n\n/**\n * Type utility that recursively resolves object shape, removing type aliases,\n * intersections, and unions for easier type introspection and error output.\n *\n * Usage:\n * type Input = { a: string } & { b: number };\n * type Output = Prettify<Input>; // { a: string; b: number }\n */\nexport type Prettify<T> = T extends object\n ? { [K in keyof T]: Prettify<T[K]> }\n : T;\n\n/**\n * Distributes over a string union and applies Lowercase only to\n * string-literal members, filtering out wide `string` / `string & {}`.\n */\nexport type LowercaseLiteral<T> = T extends string\n ? string extends T\n ? never\n : Lowercase<T>\n : never;\n"],"mappings":";;;;AAGA,MAAa,QAAW,OAAmB,IAAI"}
|
package/dist/utils/misc.js
CHANGED
package/dist/utils/stream.cjs
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
//#region src/utils/stream.ts
|
|
3
2
|
/**
|
|
4
3
|
* A transform stream that safely parses JSON from EventSource messages.
|
|
@@ -20,7 +19,7 @@ var SafeJsonEventParserStream = class extends TransformStream {
|
|
|
20
19
|
} });
|
|
21
20
|
}
|
|
22
21
|
};
|
|
23
|
-
|
|
24
22
|
//#endregion
|
|
25
23
|
exports.SafeJsonEventParserStream = SafeJsonEventParserStream;
|
|
24
|
+
|
|
26
25
|
//# sourceMappingURL=stream.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stream.cjs","names":[],"sources":["../../src/utils/stream.ts"],"sourcesContent":["import { EventSourceMessage } from \"eventsource-parser/stream\";\n\n/**\n * A transform stream that safely parses JSON from EventSource messages.\n *\n * This stream takes EventSourceMessage objects and attempts to parse the `data`\n * field as JSON. If parsing fails, it passes through the original message.\n */\nexport class SafeJsonEventParserStream<T> extends TransformStream<\n EventSourceMessage,\n T | null\n> {\n constructor() {\n super({\n transform(chunk, controller) {\n try {\n if (chunk.data) {\n const parsed = JSON.parse(chunk.data);\n controller.enqueue(parsed);\n } else {\n controller.enqueue(null);\n }\n } catch {\n // If parsing fails, enqueue null to indicate invalid JSON\n controller.enqueue(null);\n }\n },\n });\n }\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"stream.cjs","names":[],"sources":["../../src/utils/stream.ts"],"sourcesContent":["import { EventSourceMessage } from \"eventsource-parser/stream\";\n\n/**\n * A transform stream that safely parses JSON from EventSource messages.\n *\n * This stream takes EventSourceMessage objects and attempts to parse the `data`\n * field as JSON. If parsing fails, it passes through the original message.\n */\nexport class SafeJsonEventParserStream<T> extends TransformStream<\n EventSourceMessage,\n T | null\n> {\n constructor() {\n super({\n transform(chunk, controller) {\n try {\n if (chunk.data) {\n const parsed = JSON.parse(chunk.data);\n controller.enqueue(parsed);\n } else {\n controller.enqueue(null);\n }\n } catch {\n // If parsing fails, enqueue null to indicate invalid JSON\n controller.enqueue(null);\n }\n },\n });\n }\n}\n"],"mappings":";;;;;;;AAQA,IAAa,4BAAb,cAAkD,gBAGhD;CACA,cAAc;AACZ,QAAM,EACJ,UAAU,OAAO,YAAY;AAC3B,OAAI;AACF,QAAI,MAAM,MAAM;KACd,MAAM,SAAS,KAAK,MAAM,MAAM,KAAK;AACrC,gBAAW,QAAQ,OAAO;UAE1B,YAAW,QAAQ,KAAK;WAEpB;AAEN,eAAW,QAAQ,KAAK;;KAG7B,CAAC"}
|
package/dist/utils/stream.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@langchain/google",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7-dev-1773698445534",
|
|
4
4
|
"description": "Google integrations for LangChain.js",
|
|
5
5
|
"author": "LangChain",
|
|
6
6
|
"license": "MIT",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"uuid": "^10.0.0"
|
|
21
21
|
},
|
|
22
22
|
"peerDependencies": {
|
|
23
|
-
"@langchain/core": "1.1.
|
|
23
|
+
"@langchain/core": "1.1.33-dev-1773698445534"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"@tsconfig/recommended": "^1.0.10",
|
|
@@ -32,9 +32,9 @@
|
|
|
32
32
|
"vitest": "^3.2.4",
|
|
33
33
|
"zod": "^3.25.76 || ^4",
|
|
34
34
|
"zod-to-json-schema": "^3.24.6",
|
|
35
|
-
"@langchain/core": "^1.1.
|
|
36
|
-
"@langchain/
|
|
37
|
-
"@langchain/
|
|
35
|
+
"@langchain/core": "^1.1.33-dev-1773698445534",
|
|
36
|
+
"@langchain/standard-tests": "0.0.23",
|
|
37
|
+
"@langchain/eslint": "0.1.1"
|
|
38
38
|
},
|
|
39
39
|
"publishConfig": {
|
|
40
40
|
"access": "public"
|