Pārlūkot izejas kodu

add jsDocs (#88)

- refactored return statements in some functions
- added jsDocs
Rohit Khanduri 1 gadu atpakaļ
vecāks
revīzija
305eb0be06
4 mainītis faili ar 199 papildinājumiem un 56 dzēšanām
  1. 6 0
      .gitignore
  2. 93 26
      src/browser.ts
  3. 21 4
      src/index.ts
  4. 79 26
      src/utils.ts

+ 6 - 0
.gitignore

@@ -128,3 +128,9 @@ dist
 .yarn/build-state.yml
 .yarn/install-state.gz
 .pnp.*
+
+# IDEs
+.idea
+
+# MacOS
+.DS_Store

+ 93 - 26
src/browser.ts

@@ -2,25 +2,25 @@ import * as utils from './utils.js'
 import 'whatwg-fetch'
 
 import type {
-  Fetch,
+  ChatRequest,
+  ChatResponse,
   Config,
-  GenerateRequest,
-  PullRequest,
-  PushRequest,
+  CopyRequest,
+  CreateRequest,
+  DeleteRequest,
   EmbeddingsRequest,
-  GenerateResponse,
   EmbeddingsResponse,
+  ErrorResponse,
+  Fetch,
+  GenerateRequest,
+  GenerateResponse,
   ListResponse,
   ProgressResponse,
-  ErrorResponse,
-  StatusResponse,
-  DeleteRequest,
-  CopyRequest,
-  ShowResponse,
+  PullRequest,
+  PushRequest,
   ShowRequest,
-  ChatRequest,
-  ChatResponse,
-  CreateRequest,
+  ShowResponse,
+  StatusResponse,
 } from './interfaces.js'
 
 export class Ollama {
@@ -50,6 +50,17 @@ export class Ollama {
     this.abortController = new AbortController()
   }
 
+  /**
+   * Processes a request to the Ollama server. If the request is streamable, it will return an
+   * AsyncGenerator that yields the response messages. Otherwise, it will return the response
+   * object.
+   * @param endpoint {string} - The endpoint to send the request to.
+   * @param request {object} - The request object to send to the endpoint.
+   * @protected {T | AsyncGenerator<T>} - The response object or an AsyncGenerator that yields
+   * response messages.
+   * @throws {Error} - If the response body is missing or if the response is an error.
+   * @returns {Promise<T | AsyncGenerator<T>>} - The response object or an AsyncGenerator that yields the streamed response.
+   */
   protected async processStreamableRequest<T extends object>(
     endpoint: string,
     request: { stream?: boolean } & Record<string, any>,
@@ -94,13 +105,17 @@ export class Ollama {
     }
   }
 
+  /**
+   * Encodes an image to base64 if it is a Uint8Array.
+   * @param image {Uint8Array | string} - The image to encode.
+   * @returns {Promise<string>} - The base64 encoded image.
+   */
   async encodeImage(image: Uint8Array | string): Promise<string> {
     if (typeof image !== 'string') {
       // image is Uint8Array convert it to base64
       const uint8Array = new Uint8Array(image)
       const numberArray = Array.from(uint8Array)
-      const base64String = btoa(String.fromCharCode.apply(null, numberArray))
-      return base64String
+      return btoa(String.fromCharCode.apply(null, numberArray))
     }
     // the string may be base64 encoded
     return image
@@ -110,7 +125,12 @@ export class Ollama {
     request: GenerateRequest & { stream: true },
   ): Promise<AsyncGenerator<GenerateResponse>>
   generate(request: GenerateRequest & { stream?: false }): Promise<GenerateResponse>
-
+  /**
+   * Generates a response from a text prompt.
+   * @param request {GenerateRequest} - The request object.
+   * @returns {Promise<GenerateResponse | AsyncGenerator<GenerateResponse>>} - The response object or
+   * an AsyncGenerator that yields response messages.
+   */
   async generate(
     request: GenerateRequest,
   ): Promise<GenerateResponse | AsyncGenerator<GenerateResponse>> {
@@ -122,7 +142,14 @@ export class Ollama {
 
   chat(request: ChatRequest & { stream: true }): Promise<AsyncGenerator<ChatResponse>>
   chat(request: ChatRequest & { stream?: false }): Promise<ChatResponse>
-
+  /**
+   * Chats with the model. The request object can contain messages with images that are either
+   * Uint8Arrays or base64 encoded strings. The images will be base64 encoded before sending the
+   * request.
+   * @param request {ChatRequest} - The request object.
+   * @returns {Promise<ChatResponse | AsyncGenerator<ChatResponse>>} - The response object or an
+   * AsyncGenerator that yields response messages.
+   */
   async chat(request: ChatRequest): Promise<ChatResponse | AsyncGenerator<ChatResponse>> {
     if (request.messages) {
       for (const message of request.messages) {
@@ -140,7 +167,11 @@ export class Ollama {
     request: CreateRequest & { stream: true },
   ): Promise<AsyncGenerator<ProgressResponse>>
   create(request: CreateRequest & { stream?: false }): Promise<ProgressResponse>
-
+  /**
+   * Creates a new model from a stream of data.
+   * @param request {CreateRequest} - The request object.
+   * @returns {Promise<ProgressResponse | AsyncGenerator<ProgressResponse>>} - The response object or a stream of progress responses.
+   */
   async create(
     request: CreateRequest,
   ): Promise<ProgressResponse | AsyncGenerator<ProgressResponse>> {
@@ -154,7 +185,13 @@ export class Ollama {
 
   pull(request: PullRequest & { stream: true }): Promise<AsyncGenerator<ProgressResponse>>
   pull(request: PullRequest & { stream?: false }): Promise<ProgressResponse>
-
+  /**
+   * Pulls a model from the Ollama registry. The request object can contain a stream flag to indicate if the
+   * response should be streamed.
+   * @param request {PullRequest} - The request object.
+   * @returns {Promise<ProgressResponse | AsyncGenerator<ProgressResponse>>} - The response object or
+   * an AsyncGenerator that yields response messages.
+   */
   async pull(
     request: PullRequest,
   ): Promise<ProgressResponse | AsyncGenerator<ProgressResponse>> {
@@ -167,7 +204,13 @@ export class Ollama {
 
   push(request: PushRequest & { stream: true }): Promise<AsyncGenerator<ProgressResponse>>
   push(request: PushRequest & { stream?: false }): Promise<ProgressResponse>
-
+  /**
+   * Pushes a model to the Ollama registry. The request object can contain a stream flag to indicate if the
+   * response should be streamed.
+   * @param request {PushRequest} - The request object.
+   * @returns {Promise<ProgressResponse | AsyncGenerator<ProgressResponse>>} - The response object or
+   * an AsyncGenerator that yields response messages.
+   */
   async push(
     request: PushRequest,
   ): Promise<ProgressResponse | AsyncGenerator<ProgressResponse>> {
@@ -178,6 +221,12 @@ export class Ollama {
     })
   }
 
+  /**
+   * Deletes a model from the server. The request object should contain the name of the model to
+   * delete.
+   * @param request {DeleteRequest} - The request object.
+   * @returns {Promise<StatusResponse>} - The response object.
+   */
   async delete(request: DeleteRequest): Promise<StatusResponse> {
     await utils.del(this.fetch, `${this.config.host}/api/delete`, {
       name: request.model,
@@ -185,31 +234,49 @@ export class Ollama {
     return { status: 'success' }
   }
 
+  /**
+   * Copies a model from one name to another. The request object should contain the name of the
+   * model to copy and the new name.
+   * @param request {CopyRequest} - The request object.
+   * @returns {Promise<StatusResponse>} - The response object.
+   */
   async copy(request: CopyRequest): Promise<StatusResponse> {
     await utils.post(this.fetch, `${this.config.host}/api/copy`, { ...request })
     return { status: 'success' }
   }
 
+  /**
+   * Lists the models on the server.
+   * @returns {Promise<ListResponse>} - The response object.
+   * @throws {Error} - If the response body is missing.
+   */
   async list(): Promise<ListResponse> {
     const response = await utils.get(this.fetch, `${this.config.host}/api/tags`)
-    const listResponse = (await response.json()) as ListResponse
-    return listResponse
+    return (await response.json()) as ListResponse
   }
 
+  /**
+   * Shows the metadata of a model. The request object should contain the name of the model.
+   * @param request {ShowRequest} - The request object.
+   * @returns {Promise<ShowResponse>} - The response object.
+   */
   async show(request: ShowRequest): Promise<ShowResponse> {
     const response = await utils.post(this.fetch, `${this.config.host}/api/show`, {
       ...request,
     })
-    const showResponse = (await response.json()) as ShowResponse
-    return showResponse
+    return (await response.json()) as ShowResponse
   }
 
+  /**
+   * Embeds a text prompt into a vector.
+   * @param request {EmbeddingsRequest} - The request object.
+   * @returns {Promise<EmbeddingsResponse>} - The response object.
+   */
   async embeddings(request: EmbeddingsRequest): Promise<EmbeddingsResponse> {
     const response = await utils.post(this.fetch, `${this.config.host}/api/embeddings`, {
       ...request,
     })
-    const embeddingsResponse = (await response.json()) as EmbeddingsResponse
-    return embeddingsResponse
+    return (await response.json()) as EmbeddingsResponse
   }
 }
 

+ 21 - 4
src/index.ts

@@ -1,6 +1,6 @@
 import * as utils from './utils.js'
-import fs, { promises, createReadStream } from 'fs'
-import { join, resolve, dirname } from 'path'
+import fs, { createReadStream, promises } from 'fs'
+import { dirname, join, resolve } from 'path'
 import { createHash } from 'crypto'
 import { homedir } from 'os'
 import { Ollama as OllamaBrowser } from './browser.js'
@@ -11,8 +11,7 @@ export class Ollama extends OllamaBrowser {
   async encodeImage(image: Uint8Array | Buffer | string): Promise<string> {
     if (typeof image !== 'string') {
       // image is Uint8Array or Buffer, convert it to base64
-      const result = Buffer.from(image).toString('base64')
-      return result
+      return Buffer.from(image).toString('base64')
     }
     try {
       if (fs.existsSync(image)) {
@@ -27,6 +26,12 @@ export class Ollama extends OllamaBrowser {
     return image
   }
 
+  /**
+   * Parse the modelfile and replace the FROM and ADAPTER commands with the corresponding blob hashes.
+   * @param modelfile {string} - The modelfile content
+   * @param mfDir {string} - The directory of the modelfile
+   * @private @internal
+   */
   private async parseModelfile(
     modelfile: string,
     mfDir: string = process.cwd(),
@@ -49,6 +54,12 @@ export class Ollama extends OllamaBrowser {
     return out.join('\n')
   }
 
+  /**
+   * Resolve the path to an absolute path.
+   * @param inputPath {string} - The input path
+   * @param mfDir {string} - The directory of the modelfile
+   * @private @internal
+   */
   private resolvePath(inputPath, mfDir) {
     if (inputPath.startsWith('~')) {
       return join(homedir(), inputPath.slice(1))
@@ -56,6 +67,12 @@ export class Ollama extends OllamaBrowser {
     return resolve(mfDir, inputPath)
   }
 
+  /**
+   * checks if a file exists
+   * @param path {string} - The path to the file
+   * @private @internal
+   * @returns {Promise<boolean>} - Whether the file exists or not
+   */
   private async fileExists(path: string): Promise<boolean> {
     try {
       await promises.access(path)

+ 79 - 26
src/utils.ts

@@ -1,6 +1,10 @@
 import { version } from './version.js'
 import type { Fetch, ErrorResponse } from './interfaces.js'
 
+/**
+ * An error class for response errors.
+ * @extends Error
+ */
 class ResponseError extends Error {
   constructor(
     public error: string,
@@ -15,33 +19,43 @@ class ResponseError extends Error {
   }
 }
 
+/**
+ * Checks if the response is ok, if not throws an error.
+ * If the response is not ok, it will try to parse the response as JSON and use the error field as the error message.
+ * @param response {Response} - The response object to check
+ */
 const checkOk = async (response: Response): Promise<void> => {
-  if (!response.ok) {
-    let message = `Error ${response.status}: ${response.statusText}`
-    let errorData: ErrorResponse | null = null
+  if (response.ok) {
+    return
+  }
+  let message = `Error ${response.status}: ${response.statusText}`
+  let errorData: ErrorResponse | null = null
 
-    if (response.headers.get('content-type')?.includes('application/json')) {
-      try {
-        errorData = (await response.json()) as ErrorResponse
-        message = errorData.error || message
-      } catch (error) {
-        console.log('Failed to parse error response as JSON')
-      }
-    } else {
-      try {
-        console.log('Getting text from response')
-        const textResponse = await response.text()
-        message = textResponse || message
-      } catch (error) {
-        console.log('Failed to get text from error response')
-      }
+  if (response.headers.get('content-type')?.includes('application/json')) {
+    try {
+      errorData = (await response.json()) as ErrorResponse
+      message = errorData.error || message
+    } catch (error) {
+      console.log('Failed to parse error response as JSON')
+    }
+  } else {
+    try {
+      console.log('Getting text from response')
+      const textResponse = await response.text()
+      message = textResponse || message
+    } catch (error) {
+      console.log('Failed to get text from error response')
     }
-
-    throw new ResponseError(message, response.status)
   }
+
+  throw new ResponseError(message, response.status)
 }
 
-function getPlatform() {
+/**
+ * Returns the platform string based on the environment.
+ * @returns {string} - The platform string
+ */
+function getPlatform(): string {
   if (typeof window !== 'undefined' && window.navigator) {
     return `${window.navigator.platform.toLowerCase()} Browser/${navigator.userAgent};`
   } else if (typeof process !== 'undefined') {
@@ -50,6 +64,13 @@ function getPlatform() {
   return '' // unknown
 }
 
+/**
+ * A wrapper around fetch that adds default headers.
+ * @param fetch {Fetch} - The fetch function to use
+ * @param url {string} - The URL to fetch
+ * @param options {RequestInit} - The fetch options
+ * @returns {Promise<Response>} - The fetch response
+ */
 const fetchWithHeaders = async (
   fetch: Fetch,
   url: string,
@@ -73,6 +94,12 @@ const fetchWithHeaders = async (
   return fetch(url, options)
 }
 
+/**
+ * A wrapper around the get method that adds default headers.
+ * @param fetch {Fetch} - The fetch function to use
+ * @param host {string} - The host to fetch
+ * @returns {Promise<Response>} - The fetch response
+ */
 export const get = async (fetch: Fetch, host: string): Promise<Response> => {
   const response = await fetchWithHeaders(fetch, host)
 
@@ -80,7 +107,12 @@ export const get = async (fetch: Fetch, host: string): Promise<Response> => {
 
   return response
 }
-
+/**
+ * A wrapper around the head method that adds default headers.
+ * @param fetch {Fetch} - The fetch function to use
+ * @param host {string} - The host to fetch
+ * @returns {Promise<Response>} - The fetch response
+ */
 export const head = async (fetch: Fetch, host: string): Promise<Response> => {
   const response = await fetchWithHeaders(fetch, host, {
     method: 'HEAD',
@@ -90,7 +122,14 @@ export const head = async (fetch: Fetch, host: string): Promise<Response> => {
 
   return response
 }
-
+/**
+ * A wrapper around the post method that adds default headers.
+ * @param fetch {Fetch} - The fetch function to use
+ * @param host {string} - The host to fetch
+ * @param data {Record<string, unknown> | BodyInit} - The data to send
+ * @param options {{ signal: AbortSignal }} - The fetch options
+ * @returns {Promise<Response>} - The fetch response
+ */
 export const post = async (
   fetch: Fetch,
   host: string,
@@ -113,7 +152,13 @@ export const post = async (
 
   return response
 }
-
+/**
+ * A wrapper around the delete method that adds default headers.
+ * @param fetch {Fetch} - The fetch function to use
+ * @param host {string} - The host to fetch
+ * @param data {Record<string, unknown>} - The data to send
+ * @returns {Promise<Response>} - The fetch response
+ */
 export const del = async (
   fetch: Fetch,
   host: string,
@@ -128,7 +173,11 @@ export const del = async (
 
   return response
 }
-
+/**
+ * Parses a ReadableStream of Uint8Array into JSON objects.
+ * @param itr {ReadableStream<Uint8Array>} - The stream to parse
+ * @returns {AsyncGenerator<T>} - The parsed JSON objects
+ */
 export const parseJSON = async function* <T = unknown>(
   itr: ReadableStream<Uint8Array>,
 ): AsyncGenerator<T> {
@@ -167,7 +216,11 @@ export const parseJSON = async function* <T = unknown>(
     }
   }
 }
-
+/**
+ * Formats the host string to include the protocol and port.
+ * @param host {string} - The host string to format
+ * @returns {string} - The formatted host string
+ */
 export const formatHost = (host: string): string => {
   if (!host) {
     return 'http://127.0.0.1:11434'