diff --git a/.github/workflows/functional-tests.yml b/.github/workflows/functional-tests.yml index 8ed85f0..e17f1c7 100644 --- a/.github/workflows/functional-tests.yml +++ b/.github/workflows/functional-tests.yml @@ -29,12 +29,18 @@ jobs: - name: Checkout code uses: actions/checkout@v5 + - name: Submodules + run: git submodule update --init --recursive + - name: Setup Node.js uses: volta-cli/action@v4 - name: Install dependencies run: npm ci --no-audit + - name: Generate code + run: npm run codegen + - name: Build project run: npm run build --if-present diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index a9a2833..95b515b 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -13,12 +13,18 @@ jobs: - name: Checkout code uses: actions/checkout@v5 + - name: Submodules + run: git submodule update --init --recursive + - name: Setup Node.js uses: volta-cli/action@v4 - name: Install dependencies run: npm ci --no-audit + - name: Generate code + run: npm run codegen + - name: Run linting run: npm run lint --if-present diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1be7cea..60c742d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,4 +1,6 @@ -on: workflow_dispatch +on: + release: + types: [published] jobs: publish: @@ -10,11 +12,30 @@ jobs: - uses: actions/checkout@v5 - uses: actions/setup-node@v5 with: - node-version: "22" + node-version: '22' + + - name: Submodules + run: git submodule update --init --recursive + + - name: Set version from tag + run: | + if [[ "$GITHUB_REF" == refs/tags/* ]]; then + TAG_VERSION=${GITHUB_REF#refs/tags/} + # Remove 'v' prefix if present + VERSION=${TAG_VERSION#v} + echo "Setting version to: $VERSION" + npm version "$VERSION" + else + echo "Not building from a tag, using default version" + fi + + - run: npm run codegen + - run: npm ci + - run: npm run build:release - uses: JS-DevTools/npm-publish@v3 with: token: ${{ secrets.GITHUB_TOKEN }} - registry: "https://npm.pkg.github.com" + registry: 'https://npm.pkg.github.com' access: restricted diff --git a/.gitignore b/.gitignore index 711351d..ab0c42a 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,5 @@ dist/ .env .venv + +src/generated diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..d3db8f9 --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +src/generated/ diff --git a/.prettierrc b/.prettierrc index 6858002..04f1f68 100644 --- a/.prettierrc +++ b/.prettierrc @@ -2,5 +2,5 @@ "parser": "typescript", "semi": true, "singleQuote": true, - "trailingComma": "all" + "trailingComma": "all", } diff --git a/__tests__/functional/main.functional.test.ts b/__tests__/functional/main.functional.test.ts index 0a46ede..106e0b1 100644 --- a/__tests__/functional/main.functional.test.ts +++ b/__tests__/functional/main.functional.test.ts @@ -1,11 +1,18 @@ -import { describe, it, } from 'vitest'; -import { ClassificationOutput, ClassifierSdk, type ClassifyImageInput, ImageFormat } from '../../src'; +import { describe, it } from 'vitest'; +import { + ClassificationOutput, + ClassifierSdk, + type ClassifyImageInput, + ImageFormat, +} from '../../src'; import fs from 'fs'; import { randomUUID } from 'crypto'; describe('ClassifierSdk Functional Tests', () => { describe('listDeployments', () => { - it('should listDeployments and return responses (smoke test)', async ({ expect }) => { + it('should listDeployments and return responses (smoke test)', async ({ + expect, + }) => { // This is a smoke test. You must have a running gRPC server at localhost:50051 for this to pass. // You may want to mock the gRPC client for true unit testing. const sdk = new ClassifierSdk({ @@ -15,8 +22,8 @@ describe('ClassifierSdk Functional Tests', () => { issuerUrl: process.env.VITE_OAUTH_ISSUER, clientId: process.env.VITE_ATHENA_CLIENT_ID, clientSecret: process.env.VITE_ATHENA_CLIENT_SECRET, - scope: 'manage:classify' - } + scope: 'manage:classify', + }, }); let error: any = null; @@ -28,11 +35,41 @@ describe('ClassifierSdk Functional Tests', () => { } // Assert error is unset expect(error).toBeNull(); - }, 10000) + }, 10000); + }); + + describe('classifySingle', () => { + it('should classify a single image and return the response', async ({ + expect, + }) => { + const imagePath = __dirname + '/448x448.jpg'; + const sdk = new ClassifierSdk({ + deploymentId: process.env.VITE_ATHENA_DEPLOYMENT_ID, + affiliate: process.env.VITE_ATHENA_AFFILIATE, + authentication: { + issuerUrl: process.env.VITE_OAUTH_ISSUER, + clientId: process.env.VITE_ATHENA_CLIENT_ID, + clientSecret: process.env.VITE_ATHENA_CLIENT_SECRET, + scope: 'manage:classify', + }, + }); + + const input: ClassifyImageInput = { + data: fs.createReadStream(imagePath), + format: ImageFormat.PNG, + }; + + const response = await sdk.classifySingle(input); + expect(response.classifications).toBe(true); + expect(response.error).toBeNull(); + }, 10000); }); describe('classifyImage', () => { - it('should classify 10 images in a single request and return responses (integration smoke test)', async ({ expect, annotate }) => { + it('should classify 10 images in a single request and return responses (integration smoke test)', async ({ + expect, + annotate, + }) => { // This is a smoke test. You must have a running gRPC server at localhost:50051 for this to pass. // You may want to mock the gRPC client for true unit testing. const imagePath = __dirname + '/448x448.jpg'; @@ -43,23 +80,27 @@ describe('ClassifierSdk Functional Tests', () => { issuerUrl: process.env.VITE_OAUTH_ISSUER, clientId: process.env.VITE_ATHENA_CLIENT_ID, clientSecret: process.env.VITE_ATHENA_CLIENT_SECRET, - scope: 'manage:classify' - } + scope: 'manage:classify', + }, }); // Generate 10 unique correlationIds - const correlationIds = Array.from({ length: 10 }, () => randomUUID().toString()); + const correlationIds = Array.from({ length: 10 }, () => + randomUUID().toString(), + ); correlationIds.sort((a, b) => a.localeCompare(b)); annotate(`Correlation IDs: ${correlationIds.join(', ')}`); // Create 10 input objects, each with a new stream and unique correlationId - const inputs: ClassifyImageInput[] = correlationIds.map((correlationId) => ({ - data: fs.createReadStream(imagePath), - format: ImageFormat.PNG, - correlationId - })); + const inputs: ClassifyImageInput[] = correlationIds.map( + (correlationId) => ({ + data: fs.createReadStream(imagePath), + format: ImageFormat.PNG, + correlationId, + }), + ); // Create a promise to wrap the event emitter event 'data' const promise = new Promise((resolve, reject) => { @@ -107,23 +148,26 @@ describe('ClassifierSdk Functional Tests', () => { // Check that all correlationIds are present in the outputs expect(outputs.length).toBe(correlationIds.length); - const expectedOutputs = correlationIds.map(id => ( - { - correlationId: id, - classifications: expect.toBeOneOf([expect.arrayContaining([ + const expectedOutputs = correlationIds.map((id) => ({ + correlationId: id, + classifications: expect.toBeOneOf([ + expect.arrayContaining([ { label: expect.any(String), - weight: expect.any(Number) - } - ]), []]) - } - )); + weight: expect.any(Number), + }, + ]), + [], + ]), + })); expect(outputs).toMatchObject(expectedOutputs); }, 120000); - it('should classify with raw uint8 resize return responses (integration smoke test)', async ({ expect, annotate }) => { - + it('should classify with raw uint8 resize return responses (integration smoke test)', async ({ + expect, + annotate, + }) => { const imagePath = __dirname + '/448x448.jpg'; const sdk = new ClassifierSdk({ deploymentId: process.env.VITE_ATHENA_DEPLOYMENT_ID, @@ -132,8 +176,8 @@ describe('ClassifierSdk Functional Tests', () => { issuerUrl: process.env.VITE_OAUTH_ISSUER, clientId: process.env.VITE_ATHENA_CLIENT_ID, clientSecret: process.env.VITE_ATHENA_CLIENT_SECRET, - scope: 'manage:classify' - } + scope: 'manage:classify', + }, }); const correlationId = randomUUID(); @@ -148,7 +192,9 @@ describe('ClassifierSdk Functional Tests', () => { }, 30000); sdk.on('data', (data) => { - const byCorrelationId = data.outputs.filter(o => o.correlationId === correlationId); + const byCorrelationId = data.outputs.filter( + (o) => o.correlationId === correlationId, + ); if (byCorrelationId.length > 0) { clearTimeout(timeout); resolve(byCorrelationId); @@ -185,20 +231,26 @@ describe('ClassifierSdk Functional Tests', () => { expect(first).toMatchObject([ { correlationId, - classifications: expect.toBeOneOf([expect.arrayContaining([ - { - label: expect.any(String), - weight: expect.any(Number) - } - ]), []]) - } as ClassificationOutput + classifications: expect.toBeOneOf([ + expect.arrayContaining([ + { + label: expect.any(String), + weight: expect.any(Number), + }, + ]), + [], + ]), + } as ClassificationOutput, ]); // Accept either a successful call or a connection error (for CI/dev convenience) expect(error).toBeUndefined(); }, 120000); - it('should classify return responses (integration smoke test)', async ({ expect, annotate }) => { + it('should classify return responses (integration smoke test)', async ({ + expect, + annotate, + }) => { // This is a smoke test. You must have a running gRPC server at localhost:50051 for this to pass. // You may want to mock the gRPC client for true unit testing. const imagePath = __dirname + '/448x448.jpg'; @@ -209,8 +261,8 @@ describe('ClassifierSdk Functional Tests', () => { issuerUrl: process.env.VITE_OAUTH_ISSUER, clientId: process.env.VITE_ATHENA_CLIENT_ID, clientSecret: process.env.VITE_ATHENA_CLIENT_SECRET, - scope: 'manage:classify' - } + scope: 'manage:classify', + }, }); const correlationId = randomUUID(); @@ -225,7 +277,9 @@ describe('ClassifierSdk Functional Tests', () => { }, 30000); sdk.on('data', (data) => { - const byCorrelationId = data.outputs.filter(o => o.correlationId === correlationId); + const byCorrelationId = data.outputs.filter( + (o) => o.correlationId === correlationId, + ); if (byCorrelationId.length > 0) { clearTimeout(timeout); resolve(byCorrelationId); @@ -262,13 +316,16 @@ describe('ClassifierSdk Functional Tests', () => { expect(first).toMatchObject([ { correlationId, - classifications: expect.toBeOneOf([expect.arrayContaining([ - { - label: expect.any(String), - weight: expect.any(Number) - } - ]), []]) - } as ClassificationOutput + classifications: expect.toBeOneOf([ + expect.arrayContaining([ + { + label: expect.any(String), + weight: expect.any(Number), + }, + ]), + [], + ]), + } as ClassificationOutput, ]); // Accept either a successful call or a connection error (for CI/dev convenience) diff --git a/__tests__/unit/main.test.ts b/__tests__/unit/main.test.ts index ba4dc16..b065d1f 100644 --- a/__tests__/unit/main.test.ts +++ b/__tests__/unit/main.test.ts @@ -122,6 +122,10 @@ describe('ClassifierSdk', () => { expect(typeof sdk.sendClassifyRequest).toBe('function'); }); + it('should have classifySingle method', () => { + expect(typeof sdk.classifySingle).toBe('function'); + }); + it('should throw error when sendClassifyRequest called without open', async () => { const input = { data: Buffer.from('test'), diff --git a/athena-protobufs b/athena-protobufs index ce69291..7f69cdf 160000 --- a/athena-protobufs +++ b/athena-protobufs @@ -1 +1 @@ -Subproject commit ce69291a44e8fb2551ab1cc49d805448e57061cd +Subproject commit 7f69cdfb55bc761e18a5e244da034ec361f265dd diff --git a/eslint.config.mjs b/eslint.config.mjs index 8293cbd..c2250b3 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -19,6 +19,7 @@ export default tseslint.config( 'src/athena/**', 'athena-protobufs/**', 'docs/**', + '**/generated/**', ], }, eslint.configs.recommended, diff --git a/package.json b/package.json index 155c4fa..5ab65b3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@crispthinking/athena-classifier-sdk", - "version": "0.2.0", + "version": "0.0.0", "main": "./dist/src/index.js", "description": "A Node.js SDK for the Athena classifier that uses gRPC transport.", "type": "module", @@ -50,7 +50,7 @@ "build": "tsc -p tsconfig.json", "build:watch": "tsc -w -p tsconfig.json", "build:release": "npm run clean && tsc -p tsconfig.release.json", - "codegen": "npx protoc --plugin=protoc-gen-ts=./node_modules/.bin/protoc-gen-ts --ts_out=import_style=module,client_grpc1:./src/athena --proto_path=./athena-protobufs/athena ./athena-protobufs/athena/athena.proto", + "codegen": "mkdir -p src/generated && npx protoc --plugin=protoc-gen-ts=./node_modules/.bin/protoc-gen-ts --ts_out=client_grpc1:./src/generated --proto_path=./athena-protobufs ./athena-protobufs/athena/**.proto", "lint": "eslint .", "lint:all": "npm run lint && npm run prettier:check && npx tsc --noEmit", "precommit:setup": "echo 'To install pre-commit hooks, run: uv tool install pre-commit && uvx pre-commit install'", diff --git a/src/athena/athena.grpc-client.ts b/src/athena/athena.grpc-client.ts deleted file mode 100644 index dc0d819..0000000 --- a/src/athena/athena.grpc-client.ts +++ /dev/null @@ -1,159 +0,0 @@ -// @generated by protobuf-ts 2.11.1 with parameter client_grpc1 -// @generated from protobuf file "athena.proto" (package "athena", syntax proto3) -// tslint:disable -import { ClassifierService } from './athena'; -import type { BinaryWriteOptions } from '@protobuf-ts/runtime'; -import type { BinaryReadOptions } from '@protobuf-ts/runtime'; -import type { ListDeploymentsResponse } from './athena'; -import type { Empty } from './google/protobuf/empty'; -import type { ClassifyResponse } from './athena'; -import type { ClassifyRequest } from './athena'; -import * as grpc from '@grpc/grpc-js'; -/** - * The classifier service definition. - * Provides image classification capabilities with session-based streaming - * and client management functionality. - * - * @generated from protobuf service athena.ClassifierService - */ -export interface IClassifierServiceClient { - /** - * Classify images in a deployment-based streaming context - * Multiple affiliates can join the same deployment to share responses - * Supports bidirectional streaming for real-time classification - * - * @generated from protobuf rpc: Classify - */ - classify( - metadata: grpc.Metadata, - options?: grpc.CallOptions, - ): grpc.ClientDuplexStream; - classify( - options?: grpc.CallOptions, - ): grpc.ClientDuplexStream; - /** - * Retrieves a list of all active deployment IDs - * Returns the active deployment_id values that can be used in Classify requests - * Useful for monitoring and debugging active connections - * - * @generated from protobuf rpc: ListDeployments - */ - listDeployments( - input: Empty, - metadata: grpc.Metadata, - options: grpc.CallOptions, - callback: ( - err: grpc.ServiceError | null, - value?: ListDeploymentsResponse, - ) => void, - ): grpc.ClientUnaryCall; - listDeployments( - input: Empty, - metadata: grpc.Metadata, - callback: ( - err: grpc.ServiceError | null, - value?: ListDeploymentsResponse, - ) => void, - ): grpc.ClientUnaryCall; - listDeployments( - input: Empty, - options: grpc.CallOptions, - callback: ( - err: grpc.ServiceError | null, - value?: ListDeploymentsResponse, - ) => void, - ): grpc.ClientUnaryCall; - listDeployments( - input: Empty, - callback: ( - err: grpc.ServiceError | null, - value?: ListDeploymentsResponse, - ) => void, - ): grpc.ClientUnaryCall; -} -/** - * The classifier service definition. - * Provides image classification capabilities with session-based streaming - * and client management functionality. - * - * @generated from protobuf service athena.ClassifierService - */ -export class ClassifierServiceClient - extends grpc.Client - implements IClassifierServiceClient -{ - private readonly _binaryOptions: Partial< - BinaryReadOptions & BinaryWriteOptions - >; - constructor( - address: string, - credentials: grpc.ChannelCredentials, - options: grpc.ClientOptions = {}, - binaryOptions: Partial = {}, - ) { - super(address, credentials, options); - this._binaryOptions = binaryOptions; - } - /** - * Classify images in a deployment-based streaming context - * Multiple affiliates can join the same deployment to share responses - * Supports bidirectional streaming for real-time classification - * - * @generated from protobuf rpc: Classify - */ - classify( - metadata?: grpc.Metadata | grpc.CallOptions, - options?: grpc.CallOptions, - ): grpc.ClientDuplexStream { - const method = ClassifierService.methods[0]; - return this.makeBidiStreamRequest( - `/${ClassifierService.typeName}/${method.name}`, - (value: ClassifyRequest): Buffer => - Buffer.from(method.I.toBinary(value, this._binaryOptions)), - (value: Buffer): ClassifyResponse => - method.O.fromBinary(value, this._binaryOptions), - metadata as any, - options, - ); - } - /** - * Retrieves a list of all active deployment IDs - * Returns the active deployment_id values that can be used in Classify requests - * Useful for monitoring and debugging active connections - * - * @generated from protobuf rpc: ListDeployments - */ - listDeployments( - input: Empty, - metadata: - | grpc.Metadata - | grpc.CallOptions - | (( - err: grpc.ServiceError | null, - value?: ListDeploymentsResponse, - ) => void), - options?: - | grpc.CallOptions - | (( - err: grpc.ServiceError | null, - value?: ListDeploymentsResponse, - ) => void), - callback?: ( - err: grpc.ServiceError | null, - value?: ListDeploymentsResponse, - ) => void, - ): grpc.ClientUnaryCall { - const method = ClassifierService.methods[1]; - return this.makeUnaryRequest( - `/${ClassifierService.typeName}/${method.name}`, - (value: Empty): Buffer => - Buffer.from(method.I.toBinary(value, this._binaryOptions)), - (value: Buffer): ListDeploymentsResponse => - method.O.fromBinary(value, this._binaryOptions), - input, - metadata as any, - options as any, - callback as any, - ); - } -} diff --git a/src/athena/athena.ts b/src/athena/athena.ts deleted file mode 100644 index 4ae5440..0000000 --- a/src/athena/athena.ts +++ /dev/null @@ -1,1302 +0,0 @@ -// @generated by protobuf-ts 2.11.1 with parameter client_grpc1 -// @generated from protobuf file "athena.proto" (package "athena", syntax proto3) -// tslint:disable -import { Empty } from './google/protobuf/empty'; -import { ServiceType } from '@protobuf-ts/runtime-rpc'; -import type { BinaryWriteOptions } from '@protobuf-ts/runtime'; -import type { IBinaryWriter } from '@protobuf-ts/runtime'; -import { WireType } from '@protobuf-ts/runtime'; -import type { BinaryReadOptions } from '@protobuf-ts/runtime'; -import type { IBinaryReader } from '@protobuf-ts/runtime'; -import { UnknownFieldHandler } from '@protobuf-ts/runtime'; -import type { PartialMessage } from '@protobuf-ts/runtime'; -import { reflectionMergePartial } from '@protobuf-ts/runtime'; -import { MessageType } from '@protobuf-ts/runtime'; -/** - * Response message for ListDeployments RPC - * Contains the list of active deployments and their details. - * - * @generated from protobuf message athena.ListDeploymentsResponse - */ -export interface ListDeploymentsResponse { - /** - * List of active deployments with their backlog information. - * - * @generated from protobuf field: repeated athena.Deployment deployments = 1 - */ - deployments: Deployment[]; -} -/** - * A single active deployment part of a `ListDeployments` response - * - * @generated from protobuf message athena.Deployment - */ -export interface Deployment { - /** - * active deployment identifier - * - * @generated from protobuf field: string deployment_id = 1 - */ - deploymentId: string; - /** - * Backlog of classification responses in this deployment - * - * @generated from protobuf field: int32 backlog = 2 - */ - backlog: number; -} -/** - * The request message containing the image data to classify. - * Each request represents a batch of images that should be processed within - * the same deployment context. - * - * @generated from protobuf message athena.ClassifyRequest - */ -export interface ClassifyRequest { - /** - * Client's unique identifier for this deployment. Responses returned will be - * sent to a client with a matching deployment_id. - * - * @generated from protobuf field: string deployment_id = 1 - */ - deploymentId: string; - /** - * Array of images to be classified in this request batch - * Allows sending multiple images in a single request for efficiency. - * - * @generated from protobuf field: repeated athena.ClassificationInput inputs = 2 - */ - inputs: ClassificationInput[]; -} -/** - * A single image within a classification request batch. - * Contains all necessary metadata and data for classifying one image. - * - * @generated from protobuf message athena.ClassificationInput - */ -export interface ClassificationInput { - /** - * The affiliate or source system that provided this image - * Used for tracking, analytics, and routing purposes. - * - * @generated from protobuf field: string affiliate = 1 - */ - affiliate: string; - /** - * Unique identifier for correlating this input with its response - * Must be unique within the deployment to properly match responses - * - * @generated from protobuf field: string correlation_id = 2 - */ - correlationId: string; - /** - * Specifies the encoding/compression format of the image data - * Allows the server to properly decode the image before classification - * - * @generated from protobuf field: athena.RequestEncoding encoding = 3 - */ - encoding: RequestEncoding; - /** - * The raw image data bytes in the format specified by encoding - * Can be compressed or uncompressed based on the encoding field - * - * @generated from protobuf field: bytes data = 4 - */ - data: Uint8Array; - /** - * The image file format of the data bytes - * - * @generated from protobuf field: athena.ImageFormat format = 5 - */ - format: ImageFormat; - /** - * Hashes of the image data, can be multiple depending on image processing - * (e.g. of raw bytes, jpeg image, etc.) but must be for a single image. - * - * @generated from protobuf field: repeated athena.ImageHash hashes = 6 - */ - hashes: ImageHash[]; -} -/** - * The response message containing the classification results. - * Sent back to clients for each processed batch, containing either - * a global error or individual results for each image in the batch. - * - * @generated from protobuf message athena.ClassifyResponse - */ -export interface ClassifyResponse { - /** - * Global error affecting the entire batch/request - * If present, indicates that the entire request failed and no individual - * image results will be provided - * - * @generated from protobuf field: athena.ClassificationError global_error = 1 - */ - globalError?: ClassificationError; - /** - * Array of classification results, one for each input image - * Will be empty if global_error is present - * - * @generated from protobuf field: repeated athena.ClassificationOutput outputs = 2 - */ - outputs: ClassificationOutput[]; -} -/** - * Individual classification result for a single image. - * Contains the correlation ID and classification results for one image. - * - * @generated from protobuf message athena.ClassificationOutput - */ -export interface ClassificationOutput { - /** - * Matches the correlationId from the corresponding ClassificationInput - * Allows clients to match responses with their original requests - * - * @generated from protobuf field: string correlation_id = 1 - */ - correlationId: string; - /** - * Array of all classifications detected for this image - * Multiple classifications may be returned with different confidence levels - * - * @generated from protobuf field: repeated athena.Classification classifications = 2 - */ - classifications: Classification[]; - /** - * Error information if this specific image classification failed - * If present, indicates that this particular image could not be processed - * - * @generated from protobuf field: athena.ClassificationError error = 3 - */ - error?: ClassificationError; -} -/** - * A single classification result for an image. - * Represents one detected category with its confidence score. - * - * @generated from protobuf message athena.Classification - */ -export interface Classification { - /** - * Human-readable label describing what was classified - * Examples: "CatA", "CatB", "Indicitive", "Distraction", etc. - * - * @generated from protobuf field: string label = 1 - */ - label: string; - /** - * Confidence score between 0.0 and 1.0 indicating certainty - * Higher values indicate greater confidence in the classification - * - * @generated from protobuf field: float weight = 2 - */ - weight: number; -} -/** - * Error information for failed classification attempts. - * Provides details about why a classification could not be completed. - * - * @generated from protobuf message athena.ClassificationError - */ -export interface ClassificationError { - /** - * Error code indicating the type of failure - * - * @generated from protobuf field: athena.ErrorCode code = 1 - */ - code: ErrorCode; - /** - * Human-readable error message providing details about the failure - * - * @generated from protobuf field: string message = 2 - */ - message: string; - /** - * Additional context or details about the error (optional) - * - * @generated from protobuf field: string details = 3 - */ - details: string; -} -/** - * @generated from protobuf message athena.ImageHash - */ -export interface ImageHash { - /** - * The hash value of the image data - * - * @generated from protobuf field: string value = 1 - */ - value: string; - /** - * The type of hash algorithm used to generate the hash value - * - * @generated from protobuf field: athena.HashType type = 2 - */ - type: HashType; -} -/** - * Enumeration of possible classification error codes. - * - * @generated from protobuf enum athena.ErrorCode - */ -export enum ErrorCode { - /** - * Unknown or unspecified error - * - * @generated from protobuf enum value: ERROR_CODE_UNSPECIFIED = 0; - */ - UNSPECIFIED = 0, - /** - * Image is too large to process - * - * @generated from protobuf enum value: ERROR_CODE_IMAGE_TOO_LARGE = 2; - */ - IMAGE_TOO_LARGE = 2, - /** - * Opaque error from the classifier - * - * @generated from protobuf enum value: ERROR_CODE_MODEL_ERROR = 3; - */ - MODEL_ERROR = 3, - /** - * Attempt to send data for an affiliate which is not granted to the current - * client. - * - * @generated from protobuf enum value: ERROR_CODE_AFFILIATE_NOT_PERMITTED = 4; - */ - AFFILIATE_NOT_PERMITTED = 4, - /** - * The format of the deployment ID is not valid. Deployment IDs must be - * non-empty strings of up to 64 characters. - * - * @generated from protobuf enum value: ERROR_CODE_DEPLOYMENT_ID_INVALID = 5; - */ - DEPLOYMENT_ID_INVALID = 5, - /** - * The format of the correlation ID is not valid. Correlation IDs must be - * non-empty strings of up to 64 characters. - * - * @generated from protobuf enum value: ERROR_CODE_CORRELATION_ID_INVALID = 6; - */ - CORRELATION_ID_INVALID = 6, -} -/** - * Enumeration of supported image data encoding formats. - * Determines how the server should interpret the image data bytes. - * - * @generated from protobuf enum athena.RequestEncoding - */ -export enum RequestEncoding { - /** - * Unspecified encoding. Assumed to be uncompressed. - * - * @generated from protobuf enum value: REQUEST_ENCODING_UNSPECIFIED = 0; - */ - UNSPECIFIED = 0, - /** - * Uncompressed raw image data (e.g., raw RGB pixels, BMP, etc.) - * - * @generated from protobuf enum value: REQUEST_ENCODING_UNCOMPRESSED = 1; - */ - UNCOMPRESSED = 1, - /** - * Brotli-compressed image data for reduced bandwidth usage - * Server will decompress using Brotli algorithm before processing - * - * @generated from protobuf enum value: REQUEST_ENCODING_BROTLI = 2; - */ - BROTLI = 2, -} -/** - * Enumeration of supported image file formats. - * Specifies the image file format structure for proper parsing. - * - * @generated from protobuf enum athena.ImageFormat - */ -export enum ImageFormat { - /** - * @generated from protobuf enum value: IMAGE_FORMAT_UNSPECIFIED = 0; - */ - UNSPECIFIED = 0, - /** - * @generated from protobuf enum value: IMAGE_FORMAT_GIF = 1; - */ - GIF = 1, - /** - * Covers .jpeg, .jpg, .jpe extensions - * - * @generated from protobuf enum value: IMAGE_FORMAT_JPEG = 2; - */ - JPEG = 2, - /** - * @generated from protobuf enum value: IMAGE_FORMAT_BMP = 3; - */ - BMP = 3, - /** - * @generated from protobuf enum value: IMAGE_FORMAT_DIB = 4; - */ - DIB = 4, - /** - * @generated from protobuf enum value: IMAGE_FORMAT_PNG = 5; - */ - PNG = 5, - /** - * @generated from protobuf enum value: IMAGE_FORMAT_WEBP = 6; - */ - WEBP = 6, - /** - * @generated from protobuf enum value: IMAGE_FORMAT_PBM = 7; - */ - PBM = 7, - /** - * @generated from protobuf enum value: IMAGE_FORMAT_PGM = 8; - */ - PGM = 8, - /** - * @generated from protobuf enum value: IMAGE_FORMAT_PPM = 9; - */ - PPM = 9, - /** - * @generated from protobuf enum value: IMAGE_FORMAT_PXM = 10; - */ - PXM = 10, - /** - * @generated from protobuf enum value: IMAGE_FORMAT_PNM = 11; - */ - PNM = 11, - /** - * @generated from protobuf enum value: IMAGE_FORMAT_PFM = 12; - */ - PFM = 12, - /** - * @generated from protobuf enum value: IMAGE_FORMAT_SR = 13; - */ - SR = 13, - /** - * @generated from protobuf enum value: IMAGE_FORMAT_RAS = 14; - */ - RAS = 14, - /** - * Covers .tiff, .tif extensions - * - * @generated from protobuf enum value: IMAGE_FORMAT_TIFF = 15; - */ - TIFF = 15, - /** - * @generated from protobuf enum value: IMAGE_FORMAT_HDR = 16; - */ - HDR = 16, - /** - * @generated from protobuf enum value: IMAGE_FORMAT_PIC = 17; - */ - PIC = 17, - /** - * Raw unsigned 8-bit image data (RGB, C order array) - * - * @generated from protobuf enum value: IMAGE_FORMAT_RAW_UINT8 = 18; - */ - RAW_UINT8 = 18, -} -/** - * @generated from protobuf enum athena.HashType - */ -export enum HashType { - /** - * @generated from protobuf enum value: HASH_TYPE_UNKNOWN = 0; - */ - UNKNOWN = 0, - /** - * @generated from protobuf enum value: HASH_TYPE_MD5 = 1; - */ - MD5 = 1, - /** - * @generated from protobuf enum value: HASH_TYPE_SHA1 = 2; - */ - SHA1 = 2, -} -// @generated message type with reflection information, may provide speed optimized methods -class ListDeploymentsResponse$Type extends MessageType { - constructor() { - super('athena.ListDeploymentsResponse', [ - { - no: 1, - name: 'deployments', - kind: 'message', - repeat: 2 /*RepeatType.UNPACKED*/, - T: () => Deployment, - }, - ]); - } - create( - value?: PartialMessage, - ): ListDeploymentsResponse { - const message = globalThis.Object.create(this.messagePrototype!); - message.deployments = []; - if (value !== undefined) - reflectionMergePartial(this, message, value); - return message; - } - internalBinaryRead( - reader: IBinaryReader, - length: number, - options: BinaryReadOptions, - target?: ListDeploymentsResponse, - ): ListDeploymentsResponse { - let message = target ?? this.create(), - end = reader.pos + length; - while (reader.pos < end) { - let [fieldNo, wireType] = reader.tag(); - switch (fieldNo) { - case /* repeated athena.Deployment deployments */ 1: - message.deployments.push( - Deployment.internalBinaryRead(reader, reader.uint32(), options), - ); - break; - default: - let u = options.readUnknownField; - if (u === 'throw') - throw new globalThis.Error( - `Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`, - ); - let d = reader.skip(wireType); - if (u !== false) - (u === true ? UnknownFieldHandler.onRead : u)( - this.typeName, - message, - fieldNo, - wireType, - d, - ); - } - } - return message; - } - internalBinaryWrite( - message: ListDeploymentsResponse, - writer: IBinaryWriter, - options: BinaryWriteOptions, - ): IBinaryWriter { - /* repeated athena.Deployment deployments = 1; */ - for (let i = 0; i < message.deployments.length; i++) - Deployment.internalBinaryWrite( - message.deployments[i], - writer.tag(1, WireType.LengthDelimited).fork(), - options, - ).join(); - let u = options.writeUnknownFields; - if (u !== false) - (u == true ? UnknownFieldHandler.onWrite : u)( - this.typeName, - message, - writer, - ); - return writer; - } -} -/** - * @generated MessageType for protobuf message athena.ListDeploymentsResponse - */ -export const ListDeploymentsResponse = new ListDeploymentsResponse$Type(); -// @generated message type with reflection information, may provide speed optimized methods -class Deployment$Type extends MessageType { - constructor() { - super('athena.Deployment', [ - { - no: 1, - name: 'deployment_id', - kind: 'scalar', - T: 9 /*ScalarType.STRING*/, - }, - { no: 2, name: 'backlog', kind: 'scalar', T: 5 /*ScalarType.INT32*/ }, - ]); - } - create(value?: PartialMessage): Deployment { - const message = globalThis.Object.create(this.messagePrototype!); - message.deploymentId = ''; - message.backlog = 0; - if (value !== undefined) - reflectionMergePartial(this, message, value); - return message; - } - internalBinaryRead( - reader: IBinaryReader, - length: number, - options: BinaryReadOptions, - target?: Deployment, - ): Deployment { - let message = target ?? this.create(), - end = reader.pos + length; - while (reader.pos < end) { - let [fieldNo, wireType] = reader.tag(); - switch (fieldNo) { - case /* string deployment_id */ 1: - message.deploymentId = reader.string(); - break; - case /* int32 backlog */ 2: - message.backlog = reader.int32(); - break; - default: - let u = options.readUnknownField; - if (u === 'throw') - throw new globalThis.Error( - `Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`, - ); - let d = reader.skip(wireType); - if (u !== false) - (u === true ? UnknownFieldHandler.onRead : u)( - this.typeName, - message, - fieldNo, - wireType, - d, - ); - } - } - return message; - } - internalBinaryWrite( - message: Deployment, - writer: IBinaryWriter, - options: BinaryWriteOptions, - ): IBinaryWriter { - /* string deployment_id = 1; */ - if (message.deploymentId !== '') - writer.tag(1, WireType.LengthDelimited).string(message.deploymentId); - /* int32 backlog = 2; */ - if (message.backlog !== 0) - writer.tag(2, WireType.Varint).int32(message.backlog); - let u = options.writeUnknownFields; - if (u !== false) - (u == true ? UnknownFieldHandler.onWrite : u)( - this.typeName, - message, - writer, - ); - return writer; - } -} -/** - * @generated MessageType for protobuf message athena.Deployment - */ -export const Deployment = new Deployment$Type(); -// @generated message type with reflection information, may provide speed optimized methods -class ClassifyRequest$Type extends MessageType { - constructor() { - super('athena.ClassifyRequest', [ - { - no: 1, - name: 'deployment_id', - kind: 'scalar', - T: 9 /*ScalarType.STRING*/, - }, - { - no: 2, - name: 'inputs', - kind: 'message', - repeat: 2 /*RepeatType.UNPACKED*/, - T: () => ClassificationInput, - }, - ]); - } - create(value?: PartialMessage): ClassifyRequest { - const message = globalThis.Object.create(this.messagePrototype!); - message.deploymentId = ''; - message.inputs = []; - if (value !== undefined) - reflectionMergePartial(this, message, value); - return message; - } - internalBinaryRead( - reader: IBinaryReader, - length: number, - options: BinaryReadOptions, - target?: ClassifyRequest, - ): ClassifyRequest { - let message = target ?? this.create(), - end = reader.pos + length; - while (reader.pos < end) { - let [fieldNo, wireType] = reader.tag(); - switch (fieldNo) { - case /* string deployment_id */ 1: - message.deploymentId = reader.string(); - break; - case /* repeated athena.ClassificationInput inputs */ 2: - message.inputs.push( - ClassificationInput.internalBinaryRead( - reader, - reader.uint32(), - options, - ), - ); - break; - default: - let u = options.readUnknownField; - if (u === 'throw') - throw new globalThis.Error( - `Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`, - ); - let d = reader.skip(wireType); - if (u !== false) - (u === true ? UnknownFieldHandler.onRead : u)( - this.typeName, - message, - fieldNo, - wireType, - d, - ); - } - } - return message; - } - internalBinaryWrite( - message: ClassifyRequest, - writer: IBinaryWriter, - options: BinaryWriteOptions, - ): IBinaryWriter { - /* string deployment_id = 1; */ - if (message.deploymentId !== '') - writer.tag(1, WireType.LengthDelimited).string(message.deploymentId); - /* repeated athena.ClassificationInput inputs = 2; */ - for (let i = 0; i < message.inputs.length; i++) - ClassificationInput.internalBinaryWrite( - message.inputs[i], - writer.tag(2, WireType.LengthDelimited).fork(), - options, - ).join(); - let u = options.writeUnknownFields; - if (u !== false) - (u == true ? UnknownFieldHandler.onWrite : u)( - this.typeName, - message, - writer, - ); - return writer; - } -} -/** - * @generated MessageType for protobuf message athena.ClassifyRequest - */ -export const ClassifyRequest = new ClassifyRequest$Type(); -// @generated message type with reflection information, may provide speed optimized methods -class ClassificationInput$Type extends MessageType { - constructor() { - super('athena.ClassificationInput', [ - { no: 1, name: 'affiliate', kind: 'scalar', T: 9 /*ScalarType.STRING*/ }, - { - no: 2, - name: 'correlation_id', - kind: 'scalar', - T: 9 /*ScalarType.STRING*/, - }, - { - no: 3, - name: 'encoding', - kind: 'enum', - T: () => [ - 'athena.RequestEncoding', - RequestEncoding, - 'REQUEST_ENCODING_', - ], - }, - { no: 4, name: 'data', kind: 'scalar', T: 12 /*ScalarType.BYTES*/ }, - { - no: 5, - name: 'format', - kind: 'enum', - T: () => ['athena.ImageFormat', ImageFormat, 'IMAGE_FORMAT_'], - }, - { - no: 6, - name: 'hashes', - kind: 'message', - repeat: 2 /*RepeatType.UNPACKED*/, - T: () => ImageHash, - }, - ]); - } - create(value?: PartialMessage): ClassificationInput { - const message = globalThis.Object.create(this.messagePrototype!); - message.affiliate = ''; - message.correlationId = ''; - message.encoding = 0; - message.data = new Uint8Array(0); - message.format = 0; - message.hashes = []; - if (value !== undefined) - reflectionMergePartial(this, message, value); - return message; - } - internalBinaryRead( - reader: IBinaryReader, - length: number, - options: BinaryReadOptions, - target?: ClassificationInput, - ): ClassificationInput { - let message = target ?? this.create(), - end = reader.pos + length; - while (reader.pos < end) { - let [fieldNo, wireType] = reader.tag(); - switch (fieldNo) { - case /* string affiliate */ 1: - message.affiliate = reader.string(); - break; - case /* string correlation_id */ 2: - message.correlationId = reader.string(); - break; - case /* athena.RequestEncoding encoding */ 3: - message.encoding = reader.int32(); - break; - case /* bytes data */ 4: - message.data = reader.bytes(); - break; - case /* athena.ImageFormat format */ 5: - message.format = reader.int32(); - break; - case /* repeated athena.ImageHash hashes */ 6: - message.hashes.push( - ImageHash.internalBinaryRead(reader, reader.uint32(), options), - ); - break; - default: - let u = options.readUnknownField; - if (u === 'throw') - throw new globalThis.Error( - `Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`, - ); - let d = reader.skip(wireType); - if (u !== false) - (u === true ? UnknownFieldHandler.onRead : u)( - this.typeName, - message, - fieldNo, - wireType, - d, - ); - } - } - return message; - } - internalBinaryWrite( - message: ClassificationInput, - writer: IBinaryWriter, - options: BinaryWriteOptions, - ): IBinaryWriter { - /* string affiliate = 1; */ - if (message.affiliate !== '') - writer.tag(1, WireType.LengthDelimited).string(message.affiliate); - /* string correlation_id = 2; */ - if (message.correlationId !== '') - writer.tag(2, WireType.LengthDelimited).string(message.correlationId); - /* athena.RequestEncoding encoding = 3; */ - if (message.encoding !== 0) - writer.tag(3, WireType.Varint).int32(message.encoding); - /* bytes data = 4; */ - if (message.data.length) - writer.tag(4, WireType.LengthDelimited).bytes(message.data); - /* athena.ImageFormat format = 5; */ - if (message.format !== 0) - writer.tag(5, WireType.Varint).int32(message.format); - /* repeated athena.ImageHash hashes = 6; */ - for (let i = 0; i < message.hashes.length; i++) - ImageHash.internalBinaryWrite( - message.hashes[i], - writer.tag(6, WireType.LengthDelimited).fork(), - options, - ).join(); - let u = options.writeUnknownFields; - if (u !== false) - (u == true ? UnknownFieldHandler.onWrite : u)( - this.typeName, - message, - writer, - ); - return writer; - } -} -/** - * @generated MessageType for protobuf message athena.ClassificationInput - */ -export const ClassificationInput = new ClassificationInput$Type(); -// @generated message type with reflection information, may provide speed optimized methods -class ClassifyResponse$Type extends MessageType { - constructor() { - super('athena.ClassifyResponse', [ - { - no: 1, - name: 'global_error', - kind: 'message', - T: () => ClassificationError, - }, - { - no: 2, - name: 'outputs', - kind: 'message', - repeat: 2 /*RepeatType.UNPACKED*/, - T: () => ClassificationOutput, - }, - ]); - } - create(value?: PartialMessage): ClassifyResponse { - const message = globalThis.Object.create(this.messagePrototype!); - message.outputs = []; - if (value !== undefined) - reflectionMergePartial(this, message, value); - return message; - } - internalBinaryRead( - reader: IBinaryReader, - length: number, - options: BinaryReadOptions, - target?: ClassifyResponse, - ): ClassifyResponse { - let message = target ?? this.create(), - end = reader.pos + length; - while (reader.pos < end) { - let [fieldNo, wireType] = reader.tag(); - switch (fieldNo) { - case /* athena.ClassificationError global_error */ 1: - message.globalError = ClassificationError.internalBinaryRead( - reader, - reader.uint32(), - options, - message.globalError, - ); - break; - case /* repeated athena.ClassificationOutput outputs */ 2: - message.outputs.push( - ClassificationOutput.internalBinaryRead( - reader, - reader.uint32(), - options, - ), - ); - break; - default: - let u = options.readUnknownField; - if (u === 'throw') - throw new globalThis.Error( - `Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`, - ); - let d = reader.skip(wireType); - if (u !== false) - (u === true ? UnknownFieldHandler.onRead : u)( - this.typeName, - message, - fieldNo, - wireType, - d, - ); - } - } - return message; - } - internalBinaryWrite( - message: ClassifyResponse, - writer: IBinaryWriter, - options: BinaryWriteOptions, - ): IBinaryWriter { - /* athena.ClassificationError global_error = 1; */ - if (message.globalError) - ClassificationError.internalBinaryWrite( - message.globalError, - writer.tag(1, WireType.LengthDelimited).fork(), - options, - ).join(); - /* repeated athena.ClassificationOutput outputs = 2; */ - for (let i = 0; i < message.outputs.length; i++) - ClassificationOutput.internalBinaryWrite( - message.outputs[i], - writer.tag(2, WireType.LengthDelimited).fork(), - options, - ).join(); - let u = options.writeUnknownFields; - if (u !== false) - (u == true ? UnknownFieldHandler.onWrite : u)( - this.typeName, - message, - writer, - ); - return writer; - } -} -/** - * @generated MessageType for protobuf message athena.ClassifyResponse - */ -export const ClassifyResponse = new ClassifyResponse$Type(); -// @generated message type with reflection information, may provide speed optimized methods -class ClassificationOutput$Type extends MessageType { - constructor() { - super('athena.ClassificationOutput', [ - { - no: 1, - name: 'correlation_id', - kind: 'scalar', - T: 9 /*ScalarType.STRING*/, - }, - { - no: 2, - name: 'classifications', - kind: 'message', - repeat: 2 /*RepeatType.UNPACKED*/, - T: () => Classification, - }, - { no: 3, name: 'error', kind: 'message', T: () => ClassificationError }, - ]); - } - create(value?: PartialMessage): ClassificationOutput { - const message = globalThis.Object.create(this.messagePrototype!); - message.correlationId = ''; - message.classifications = []; - if (value !== undefined) - reflectionMergePartial(this, message, value); - return message; - } - internalBinaryRead( - reader: IBinaryReader, - length: number, - options: BinaryReadOptions, - target?: ClassificationOutput, - ): ClassificationOutput { - let message = target ?? this.create(), - end = reader.pos + length; - while (reader.pos < end) { - let [fieldNo, wireType] = reader.tag(); - switch (fieldNo) { - case /* string correlation_id */ 1: - message.correlationId = reader.string(); - break; - case /* repeated athena.Classification classifications */ 2: - message.classifications.push( - Classification.internalBinaryRead(reader, reader.uint32(), options), - ); - break; - case /* athena.ClassificationError error */ 3: - message.error = ClassificationError.internalBinaryRead( - reader, - reader.uint32(), - options, - message.error, - ); - break; - default: - let u = options.readUnknownField; - if (u === 'throw') - throw new globalThis.Error( - `Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`, - ); - let d = reader.skip(wireType); - if (u !== false) - (u === true ? UnknownFieldHandler.onRead : u)( - this.typeName, - message, - fieldNo, - wireType, - d, - ); - } - } - return message; - } - internalBinaryWrite( - message: ClassificationOutput, - writer: IBinaryWriter, - options: BinaryWriteOptions, - ): IBinaryWriter { - /* string correlation_id = 1; */ - if (message.correlationId !== '') - writer.tag(1, WireType.LengthDelimited).string(message.correlationId); - /* repeated athena.Classification classifications = 2; */ - for (let i = 0; i < message.classifications.length; i++) - Classification.internalBinaryWrite( - message.classifications[i], - writer.tag(2, WireType.LengthDelimited).fork(), - options, - ).join(); - /* athena.ClassificationError error = 3; */ - if (message.error) - ClassificationError.internalBinaryWrite( - message.error, - writer.tag(3, WireType.LengthDelimited).fork(), - options, - ).join(); - let u = options.writeUnknownFields; - if (u !== false) - (u == true ? UnknownFieldHandler.onWrite : u)( - this.typeName, - message, - writer, - ); - return writer; - } -} -/** - * @generated MessageType for protobuf message athena.ClassificationOutput - */ -export const ClassificationOutput = new ClassificationOutput$Type(); -// @generated message type with reflection information, may provide speed optimized methods -class Classification$Type extends MessageType { - constructor() { - super('athena.Classification', [ - { no: 1, name: 'label', kind: 'scalar', T: 9 /*ScalarType.STRING*/ }, - { no: 2, name: 'weight', kind: 'scalar', T: 2 /*ScalarType.FLOAT*/ }, - ]); - } - create(value?: PartialMessage): Classification { - const message = globalThis.Object.create(this.messagePrototype!); - message.label = ''; - message.weight = 0; - if (value !== undefined) - reflectionMergePartial(this, message, value); - return message; - } - internalBinaryRead( - reader: IBinaryReader, - length: number, - options: BinaryReadOptions, - target?: Classification, - ): Classification { - let message = target ?? this.create(), - end = reader.pos + length; - while (reader.pos < end) { - let [fieldNo, wireType] = reader.tag(); - switch (fieldNo) { - case /* string label */ 1: - message.label = reader.string(); - break; - case /* float weight */ 2: - message.weight = reader.float(); - break; - default: - let u = options.readUnknownField; - if (u === 'throw') - throw new globalThis.Error( - `Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`, - ); - let d = reader.skip(wireType); - if (u !== false) - (u === true ? UnknownFieldHandler.onRead : u)( - this.typeName, - message, - fieldNo, - wireType, - d, - ); - } - } - return message; - } - internalBinaryWrite( - message: Classification, - writer: IBinaryWriter, - options: BinaryWriteOptions, - ): IBinaryWriter { - /* string label = 1; */ - if (message.label !== '') - writer.tag(1, WireType.LengthDelimited).string(message.label); - /* float weight = 2; */ - if (message.weight !== 0) - writer.tag(2, WireType.Bit32).float(message.weight); - let u = options.writeUnknownFields; - if (u !== false) - (u == true ? UnknownFieldHandler.onWrite : u)( - this.typeName, - message, - writer, - ); - return writer; - } -} -/** - * @generated MessageType for protobuf message athena.Classification - */ -export const Classification = new Classification$Type(); -// @generated message type with reflection information, may provide speed optimized methods -class ClassificationError$Type extends MessageType { - constructor() { - super('athena.ClassificationError', [ - { - no: 1, - name: 'code', - kind: 'enum', - T: () => ['athena.ErrorCode', ErrorCode, 'ERROR_CODE_'], - }, - { no: 2, name: 'message', kind: 'scalar', T: 9 /*ScalarType.STRING*/ }, - { no: 3, name: 'details', kind: 'scalar', T: 9 /*ScalarType.STRING*/ }, - ]); - } - create(value?: PartialMessage): ClassificationError { - const message = globalThis.Object.create(this.messagePrototype!); - message.code = 0; - message.message = ''; - message.details = ''; - if (value !== undefined) - reflectionMergePartial(this, message, value); - return message; - } - internalBinaryRead( - reader: IBinaryReader, - length: number, - options: BinaryReadOptions, - target?: ClassificationError, - ): ClassificationError { - let message = target ?? this.create(), - end = reader.pos + length; - while (reader.pos < end) { - let [fieldNo, wireType] = reader.tag(); - switch (fieldNo) { - case /* athena.ErrorCode code */ 1: - message.code = reader.int32(); - break; - case /* string message */ 2: - message.message = reader.string(); - break; - case /* string details */ 3: - message.details = reader.string(); - break; - default: - let u = options.readUnknownField; - if (u === 'throw') - throw new globalThis.Error( - `Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`, - ); - let d = reader.skip(wireType); - if (u !== false) - (u === true ? UnknownFieldHandler.onRead : u)( - this.typeName, - message, - fieldNo, - wireType, - d, - ); - } - } - return message; - } - internalBinaryWrite( - message: ClassificationError, - writer: IBinaryWriter, - options: BinaryWriteOptions, - ): IBinaryWriter { - /* athena.ErrorCode code = 1; */ - if (message.code !== 0) writer.tag(1, WireType.Varint).int32(message.code); - /* string message = 2; */ - if (message.message !== '') - writer.tag(2, WireType.LengthDelimited).string(message.message); - /* string details = 3; */ - if (message.details !== '') - writer.tag(3, WireType.LengthDelimited).string(message.details); - let u = options.writeUnknownFields; - if (u !== false) - (u == true ? UnknownFieldHandler.onWrite : u)( - this.typeName, - message, - writer, - ); - return writer; - } -} -/** - * @generated MessageType for protobuf message athena.ClassificationError - */ -export const ClassificationError = new ClassificationError$Type(); -// @generated message type with reflection information, may provide speed optimized methods -class ImageHash$Type extends MessageType { - constructor() { - super('athena.ImageHash', [ - { no: 1, name: 'value', kind: 'scalar', T: 9 /*ScalarType.STRING*/ }, - { - no: 2, - name: 'type', - kind: 'enum', - T: () => ['athena.HashType', HashType, 'HASH_TYPE_'], - }, - ]); - } - create(value?: PartialMessage): ImageHash { - const message = globalThis.Object.create(this.messagePrototype!); - message.value = ''; - message.type = 0; - if (value !== undefined) - reflectionMergePartial(this, message, value); - return message; - } - internalBinaryRead( - reader: IBinaryReader, - length: number, - options: BinaryReadOptions, - target?: ImageHash, - ): ImageHash { - let message = target ?? this.create(), - end = reader.pos + length; - while (reader.pos < end) { - let [fieldNo, wireType] = reader.tag(); - switch (fieldNo) { - case /* string value */ 1: - message.value = reader.string(); - break; - case /* athena.HashType type */ 2: - message.type = reader.int32(); - break; - default: - let u = options.readUnknownField; - if (u === 'throw') - throw new globalThis.Error( - `Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`, - ); - let d = reader.skip(wireType); - if (u !== false) - (u === true ? UnknownFieldHandler.onRead : u)( - this.typeName, - message, - fieldNo, - wireType, - d, - ); - } - } - return message; - } - internalBinaryWrite( - message: ImageHash, - writer: IBinaryWriter, - options: BinaryWriteOptions, - ): IBinaryWriter { - /* string value = 1; */ - if (message.value !== '') - writer.tag(1, WireType.LengthDelimited).string(message.value); - /* athena.HashType type = 2; */ - if (message.type !== 0) writer.tag(2, WireType.Varint).int32(message.type); - let u = options.writeUnknownFields; - if (u !== false) - (u == true ? UnknownFieldHandler.onWrite : u)( - this.typeName, - message, - writer, - ); - return writer; - } -} -/** - * @generated MessageType for protobuf message athena.ImageHash - */ -export const ImageHash = new ImageHash$Type(); -/** - * @generated ServiceType for protobuf service athena.ClassifierService - */ -export const ClassifierService = new ServiceType('athena.ClassifierService', [ - { - name: 'Classify', - serverStreaming: true, - clientStreaming: true, - options: {}, - I: ClassifyRequest, - O: ClassifyResponse, - }, - { - name: 'ListDeployments', - options: {}, - I: Empty, - O: ListDeploymentsResponse, - }, -]); diff --git a/src/athena/google/protobuf/empty.ts b/src/athena/google/protobuf/empty.ts deleted file mode 100644 index 3a9e890..0000000 --- a/src/athena/google/protobuf/empty.ts +++ /dev/null @@ -1,115 +0,0 @@ -// @generated by protobuf-ts 2.11.1 with parameter client_grpc1 -// @generated from protobuf file "google/protobuf/empty.proto" (package "google.protobuf", syntax proto3) -// tslint:disable -// -// Protocol Buffers - Google's data interchange format -// Copyright 2008 Google Inc. All rights reserved. -// https://developers.google.com/protocol-buffers/ -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -// -import type { BinaryWriteOptions } from '@protobuf-ts/runtime'; -import type { IBinaryWriter } from '@protobuf-ts/runtime'; -import type { BinaryReadOptions } from '@protobuf-ts/runtime'; -import type { IBinaryReader } from '@protobuf-ts/runtime'; -import { UnknownFieldHandler } from '@protobuf-ts/runtime'; -import type { PartialMessage } from '@protobuf-ts/runtime'; -import { reflectionMergePartial } from '@protobuf-ts/runtime'; -import { MessageType } from '@protobuf-ts/runtime'; -/** - * A generic empty message that you can re-use to avoid defining duplicated - * empty messages in your APIs. A typical example is to use it as the request - * or the response type of an API method. For instance: - * - * service Foo { - * rpc Bar(google.protobuf.Empty) returns (google.protobuf.Empty); - * } - * - * - * @generated from protobuf message google.protobuf.Empty - */ -export interface Empty {} -// @generated message type with reflection information, may provide speed optimized methods -class Empty$Type extends MessageType { - constructor() { - super('google.protobuf.Empty', []); - } - create(value?: PartialMessage): Empty { - const message = globalThis.Object.create(this.messagePrototype!); - if (value !== undefined) - reflectionMergePartial(this, message, value); - return message; - } - internalBinaryRead( - reader: IBinaryReader, - length: number, - options: BinaryReadOptions, - target?: Empty, - ): Empty { - let message = target ?? this.create(), - end = reader.pos + length; - while (reader.pos < end) { - let [fieldNo, wireType] = reader.tag(); - switch (fieldNo) { - default: - let u = options.readUnknownField; - if (u === 'throw') - throw new globalThis.Error( - `Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`, - ); - let d = reader.skip(wireType); - if (u !== false) - (u === true ? UnknownFieldHandler.onRead : u)( - this.typeName, - message, - fieldNo, - wireType, - d, - ); - } - } - return message; - } - internalBinaryWrite( - message: Empty, - writer: IBinaryWriter, - options: BinaryWriteOptions, - ): IBinaryWriter { - let u = options.writeUnknownFields; - if (u !== false) - (u == true ? UnknownFieldHandler.onWrite : u)( - this.typeName, - message, - writer, - ); - return writer; - } -} -/** - * @generated MessageType for protobuf message google.protobuf.Empty - */ -export const Empty = new Empty$Type(); diff --git a/src/hashing.ts b/src/hashing.ts index 53a9dc3..77e6964 100644 --- a/src/hashing.ts +++ b/src/hashing.ts @@ -1,7 +1,11 @@ import { Readable } from 'stream'; import crypto from 'crypto'; import sharp from 'sharp'; -import { HashType, ImageFormat, RequestEncoding } from '.'; +import { + HashType, + ImageFormat, + RequestEncoding, +} from './generated/athena/models'; import brotli from 'brotli'; import { buffer } from 'stream/consumers'; @@ -64,7 +68,7 @@ export async function computeHashesFromStream( } if (encoding === RequestEncoding.BROTLI) { - data = Buffer.from(await brotli.compress(data)); + data = Buffer.from(brotli.compress(data)); } return { diff --git a/src/index.ts b/src/index.ts index 80425f3..428db2b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,14 +9,15 @@ import { ClassifyResponse, Deployment, ImageFormat, -} from './athena/athena'; + ClassificationOutput, +} from './generated/athena/models'; import * as grpc from '@grpc/grpc-js'; import { type IClassifierServiceClient, ClassifierServiceClient, -} from './athena/athena.grpc-client'; +} from './generated/athena/athena.grpc-client'; import { EventEmitter } from 'events'; -import { Empty } from './athena/google/protobuf/empty'; +import { Empty } from './generated/google/protobuf/empty'; import { type AuthenticationOptions, AuthenticationManager, @@ -308,6 +309,60 @@ export class ClassifierSdk extends (EventEmitter as new () => TypedEventEmitter< }); } + public async classifySingle( + request: ClassifyImageInput, + ): Promise { + const options = { + affiliate: this.options.affiliate, + correlationId: randomUUID().toString(), + includeHashes: [HashType.MD5, HashType.SHA1], + encoding: request.encoding, + }; + + let inputFormat: ImageFormat = ImageFormat.UNSPECIFIED; + + if ('resize' in request === false) { + inputFormat = request.format; + } + + const { md5, sha1, data, format } = await computeHashesFromStream( + request.data, + options.encoding, + inputFormat, + 'resize' in request, + options.includeHashes, + ); + + const hashes: ImageHash[] = []; + + if (md5 && md5.trim() != '') { + hashes.push({ value: md5, type: HashType.MD5 }); + } + + if (sha1 && sha1.trim() != '') { + hashes.push({ value: sha1, type: HashType.SHA1 }); + } + + const input: ClassificationInput = { + affiliate: this.options.affiliate, + correlationId: options.correlationId, + data: data, + format: format, + encoding: options.encoding, + hashes: hashes, + }; + + return new Promise((resolve, reject) => { + this.client.classifySingle(input, (err, response) => { + if (err) { + reject(err); + } else { + resolve(response); + } + }); + }); + } + /** * Closes the gRPC stream and cleans up resources. * Emits 'close' event. @@ -345,6 +400,7 @@ export class ClassifierSdk extends (EventEmitter as new () => TypedEventEmitter< } } -export * from './athena/athena'; -export * from './athena/athena.grpc-client'; +export * from './generated/athena/models'; +export * from './generated/athena/athena'; +export * from './generated/athena/athena.grpc-client'; export * from './hashing';