Member Junction
    Preparing search index...

    MemberJunction provider for Betty, the MJ-native organization-scoped assistant.

    @memberjunction/ai-betty-bot targets a different service with a different wire protocol: it exchanges its key for a JWT at POST /settings, then posts { input } to POST /response. Betty takes the API key directly as a bearer and posts { message, conversationId } to POST /messages. Auth, endpoint, request and response all differ, so BETTY_BOT_BASE_URL cannot bridge them.

    Making this a separate DriverClass rather than a mode of the existing one is deliberate:

    • GetAIAPIKey() is keyed by DRIVER CLASS, so a single class serving both protocols would read one AI_VENDOR_API_KEY__BETTYBOTLLM for two unrelated services — a misconfiguration would silently send a customer's key to the wrong host. A distinct class gets a distinct variable.
    • @memberjunction/ai-betty-bot stays byte-identical, so no existing deployment changes and nobody has to be contacted or given a deadline.
    • Both can run in one instance. Migrating is repointing an AIModelVendor.DriverClass; rolling back is repointing it back.

    Betty threads conversations server-side via conversationId, but ChatParams carries no correlation id and a fresh provider instance is constructed for every execution, so there is nothing to thread it through. MJ therefore owns the history: the latest user message is the turn, and everything before it is passed as advisory context.text.

    This is the one thing NOT to copy from the legacy provider, which does params.messages.find(m => m.role === user) — .find returns the FIRST match, so on a three-turn exchange it sends the opening question and silently discards the follow-up.

    Hierarchy (View Summary)

    Index

    Constructors

    • Parameters

      • apiKey: string

        Betty API key, sent as the bearer on every request.

      • OptionalendUserId: string

        Optional caller identifier, forwarded as the endUserId request-body field.

        Betty records it against the conversation so a caller gets its own attribution row and its own rate-limit bucket rather than sharing an anonymous per-IP one — the same problem BettyBotLLM's userId argument solves for the legacy service, and named here for the field Betty's own API documents.

        ATTRIBUTION ONLY, and unverified by design: retrieval scope is pinned server-side from the credential, so this never widens what content the request can reach. Do not use it for authorization.

        Omitted, the request body is byte-identical to one built without this parameter.

        Note this is reachable only by constructing the provider directly — AIPromptRunner instantiates through ClassFactory.CreateInstance(BaseLLM, driverClass, apiKey) and passes the key alone, and ChatParams carries no user identity to fall back on. A caller that wants attribution does new BettyLLM(key, 'izzy'), exactly as the legacy provider is used from betty.action.ts.

      Returns BettyLLM

    Properties

    _additionalSettings: Record<string, any>

    Protected property to store additional provider-specific settings

    thinkingStreamState: ThinkingStreamState

    State tracking for streaming thinking extraction Providers should initialize this if they support thinking models

    Accessors

    • get AdditionalSettings(): Record<string, any>

      Get the current additional settings

      Returns Record<string, any>

    • get apiKey(): string

      Only sub-classes can access the API key

      Returns string

    • get SupportsPrefill(): boolean

      Whether this LLM provider supports assistant prefill (pre-seeding the start of the model's response). Providers that support prefill should override this to return true. This is used as a code-level default when database metadata (AIModelType/AIModel/AIModelVendor.SupportsPrefill) is null. Database values of true/false override this getter.

      Returns boolean

    • get SupportsStreaming(): boolean

      The API supports SSE when the request sets Accept: text/event-stream, but this provider does not use it yet — wiring it means implementing the three streaming members below against Betty's event shape. Declared false so BaseLLM never routes a streaming request here.

      Returns boolean

    • get SupportsTools(): boolean

      Whether this driver implements native tool/function calling — i.e. whether it maps ChatParams.tools onto its SDK and normalizes tool calls back into ChatCompletionMessage.toolCalls.

      This is a CODE-level capability ("has the mapping been written for this driver?"), distinct from the METADATA-level capability ModelConfiguration.LLM.SupportsNativeToolCalling ("does this model, on this vendor, support tools at all?"). Both must hold for native mode.

      A driver returning false ignores any tools passed to it and records the fact in the result's modelSpecificResponseDetails — the prompt runner's gate should keep that from happening, but the layer is safe standalone.

      Returns boolean

    Methods

    • The result of a cancelled request, the same for every driver: a failed result with no choices, statusText 'cancelled', and an errorInfo that says Cancelled (Fatal, never failed over) — or, when the signal was aborted by a timeout, a retriable NetworkError.

      Parameters

      • startTime: Date
      • Optionaloptions: { error?: unknown; message?: string; signal?: AbortSignal }
        • Optionalerror?: unknown

          The error the abort raised, kept on exception.

        • Optionalmessage?: string

          The message to report; defaults to one naming the signal's reason.

        • Optionalsignal?: AbortSignal

          The request's abort signal, read for why it was aborted.

      Returns ChatResult

    • Process a chat completion request. If streaming is enabled and supported, this will route to the streaming implementation.

      Parameters

      Returns Promise<ChatResult>

    • Process multiple chat completion requests in parallel. This is useful for:

      • Generating multiple variations with different parameters (temperature, etc.)
      • Getting multiple responses to compare or select from
      • Improving reliability by sending the same request multiple times

      Parameters

      Returns Promise<ChatResult[]>

      Promise resolving to an array of ChatResults in the same order as the input params

    • Clear all additional settings This is useful for resetting the state of the provider or when switching between different configurations.

      Returns void

    • Extract thinking content from non-streaming content This method handles case-insensitive extraction of thinking blocks

      Parameters

      • content: string

      Returns { content: string; thinking?: string }

    • Create the final response object from streaming results

      Parameters

      • _accumulatedContent: string

        The complete content accumulated from all chunks

      • _lastChunk: unknown

        The last chunk received from the stream

      • _usage: unknown

        The usage information (tokens, etc.)

      Returns ChatResult

      A complete ChatResult object

    • Returns (and clears) any user-visible content the thinking-tag stripper is still holding back at the end of a stream. Mid-stream, processStreamChunkWithThinking holds back a trailing fragment that could be the start of a <think>/</think> tag so a split tag never leaks as partial text; once the stream ends, such a fragment is real content and must be emitted (bug A5).

      Only flushes when NOT inside a thinking block: an unterminated <think> block's buffered text is reasoning, not answer, and is left held back (never surfaced as visible content). Returns '' when thinking extraction isn't active (no state) or there's nothing to flush.

      Returns string

    • Returns the native file input capabilities of this LLM driver, or null if the driver does not support file attachments. Subclasses that accept files (PDFs, images, etc.) should override this method.

      Returns FileCapabilities

    • Get the thinking tag format for this provider Providers can override this to customize the thinking tag format

      Returns { close: string; open: string }

    • Template method for handling streaming chat completion This implements the common pattern across providers while delegating provider-specific logic to abstract methods.

      Parameters

      Returns Promise<ChatResult>

    • Initialize thinking stream state for streaming extraction

      Returns void

    • Whether a request ended because it was aborted: its signal is aborted, the error is an abort by name (AbortError, APIUserAbortError, RequestAbortedError), or IsProviderAbortError recognizes it.

      Parameters

      • error: unknown
      • Optionalsignal: AbortSignal

      Returns boolean

    • Whether a message is framework-authored volatile state for this request only: the loop agent's trailing runtime-state fragment (date/time, scratchpad, payload, a relocated specialization), which is rebuilt every iteration and must never sit inside a provider's cached prefix.

      The base test is the VolatileStateMessageMetadata.volatileState flag the agent layer sets. A driver may override to recognise the message by other means (Anthropic also accepts the fragment's tag literal for callers that pass plain text), but should call super first.

      Parameters

      Returns boolean

    • Process streaming chunk with thinking extraction This method handles case-insensitive extraction across chunk boundaries

      Parameters

      • rawContent: string

      Returns string

    • Process a streaming chunk from the provider

      Parameters

      • _chunk: unknown

        The raw chunk from the provider

      Returns { content: string; finishReason?: string; usage?: null }

      Processed content and metadata

    • Hook invoked at the start AND end (in finally) of every streaming chat completion to reset per-request streaming state. Default is a no-op; providers that maintain instance-level streaming state (e.g., Anthropic / OpenAI thinking-block accumulators) MUST override this. See audit R2-C5 for context — without this, state from a prior request bleeds into the next one and accumulated buffers grow unbounded across the singleton's lifetime.

      Returns void

    • Reset thinking stream state

      Returns void

    • Set additional provider-specific settings Subclasses should override this method to validate required settings

      Parameters

      • settings: Record<string, any>

        Provider-specific settings

      Returns void

    • Splits an outgoing request around its trailing volatile-state message, so a driver can place its cache boundary on the last message of head and send tail uncached. Returns null when the request has no trailing volatile state, in which case the driver formats the whole list as usual. Neither array is a copy of the input's messages; only the list is new.

      Parameters

      Returns TrailingVolatileStateSplit

    • Check if the provider supports thinking models Providers should override this to return true if they support thinking extraction

      Returns boolean

    • Index of the trailing volatile-state message in an outgoing request, or -1 when there is none. The fragment is the LAST message, or the one before it when an assistant prefill has been appended after it. -1 is also returned when nothing precedes the fragment, because then there is no stable history for a provider to cache ahead of it.

      Parameters

      Returns number