Skip to main content
Using a Skills-compatible agent like Claude Code? The Greenflash agent skill handles the entire integration automatically — install, configure, and wire up logging with a single command. Run /greenflash-onboard instead of copying this prompt.

Greenflash TypeScript SDK Integration Plan

Your task is to integrate the Greenflash TypeScript SDK into this codebase and ship the minimal, correct implementation to start sending conversation data. Follow the steps below carefully and exactly, and make sure to use the checklist at the end to ensure you’ve followed all the steps. Important: Before implementing, analyze the codebase to understand:
  1. Where does user input get received vs. where LLM responses are generated?
  2. Are they in the same function/location or separate?
This analysis will determine your implementation approach (see Section 4A).

1) Installation

Install the Greenflash package:
npm install greenflash
# or
yarn add greenflash
# or
pnpm add greenflash

2) Configuration

  1. Ensure the API key is available at runtime:
    • Add to your environment (or .env file):
      GREENFLASH_API_KEY=YOUR_GREENFLASH_API_KEY
      
    • Do not hard-code the key.
    • The SDK will automatically read GREENFLASH_API_KEY from environment variables if not passed explicitly.
  2. Ensure you have your Product ID(s):
    • Find your Product ID in Greenflash for the product you want to track.
    • You will pass this directly to each API call (not as an env var), since a single codebase may serve multiple products.

3) Create a single reusable Greenflash client

Best practice: create one module that exports a ready-to-use client. Name it greenflash-client.ts (or .js). Keep all env lookups centralized here.
// greenflash-client.ts
import Greenflash from 'greenflash';

// The SDK will automatically read GREENFLASH_API_KEY from environment variables.
// Explicitly passing it here allows for better error messages if it's missing.
export const client = new Greenflash({
  apiKey: process.env.GREENFLASH_API_KEY,
});
Place this file in a shared module location (e.g., src/lib/greenflash-client.ts or app/services/greenflash-client.ts). Import client wherever you log messages or identify users/orgs.

4) Log messages with client.messages.create (required fields only)

Required fields:
  • productId
  • externalUserId
  • externalConversationId
  • messages → array of { role: "user" | "assistant" | "system", content: string }
Recommended optional fields:
  • model — the AI model used (e.g., "gpt-4o", "claude-sonnet-4-20250514"). Enables model performance comparison in Greenflash.
  • externalOrganizationId — links conversations to an organization/account for segment analysis.
  • properties — object of custom metadata to attach to the conversation.

Fire-and-forget logging to avoid blocking

Use promises without await to avoid blocking your LLM response:
// example-logging.ts
import { client } from './greenflash-client';

interface Message {
  role: 'user' | 'assistant' | 'system';
  content: string;
}

function logChat(productId: string, userId: string, convoId: string, messages: Message[]): void {
  /**
   * Helper function for logging messages to Greenflash.
   *
   * messages can be:
   * - A single message: [{ role: "user", content: "Hello" }]
   * - A turn: [{ role: "user", content: "Hello" }, { role: "assistant", content: "Hi!" }]
   * - Multiple turns/conversation
   *
   * See section 4A for deciding which pattern to use.
   */
  const params = {
    productId,
    externalUserId: userId,
    externalConversationId: convoId,
    messages: messages,
  };

  // Fire-and-forget so LLM response isn't delayed
  // Don't await this promise - let it resolve in the background
  client.messages.create(params).catch(err => {
    console.error('Failed to log to Greenflash:', err);
  });
}
Note: The .catch() is optional but recommended to prevent unhandled promise rejections from crashing your app.

4A) Where to call it in your flow — choosing the right pattern

The Greenflash messages API is flexible: You can send 1 to many messages per call. Subsequent calls are automatically ordered by timestamp, so you can safely send:
  • Each message individually (user message, then assistant message separately)
  • Messages grouped as turns (user + assistant together in one call)
  • An entire conversation at once (less common for real-time logging)
How to decide which pattern to use:
  1. Analyze your codebase structure first:
    • Look for where user input is received and where the assistant/LLM response is generated
    • Check if these happen in the same function or in different parts of the code
  2. Pattern A - Send as turns:
    • Use if: User input and assistant output are available together in the same function/location
    • Example: Your LLM handler receives user input, generates a response, and returns both
    • Benefit: One API call per turn, simpler tracking
    // After generating the assistant response
    const messages = [
      { role: 'user' as const, content: userInput },
      { role: 'assistant' as const, content: assistantResponse }
    ];
    
    client.messages.create({
      productId: 'YOUR_PRODUCT_ID',
      externalUserId: userId,
      externalConversationId: convoId,
      messages: messages
    }).catch(err => console.error('Greenflash error:', err));
    
  3. Pattern B - Send individually:
    • Use if: User input and assistant output are handled in separate functions/locations
    • Example: User input handler is separate from the LLM response generation
    • Benefit: Log as events happen, no need to refactor code structure
    // When user message is received
    client.messages.create({
      productId: 'YOUR_PRODUCT_ID',
      externalUserId: userId,
      externalConversationId: convoId,
      messages: [{ role: 'user', content: userInput }]
    }).catch(err => console.error('Greenflash error:', err));
    
    // Later, when assistant responds
    client.messages.create({
      productId: 'YOUR_PRODUCT_ID',
      externalUserId: userId,
      externalConversationId: convoId,
      messages: [{ role: 'assistant', content: assistantResponse }]
    }).catch(err => console.error('Greenflash error:', err));
    
Key points:
  • Greenflash automatically orders messages by timestamp across calls, so both patterns work correctly
  • Choose the pattern that requires minimal changes to the existing codebase
  • You can mix patterns if needed (e.g., some conversations logged as turns, others as individual messages)

5) Minimal examples for message payload

const messages = [
  { role: 'user', content: 'What is the weather like today?' },
  { role: 'assistant', content: 'I can help you check the weather. What city are you in?' }
];

6) (Optional) Agentic Workflows

If the product is an AI agent with tool calls, reasoning steps, or multi-step workflows, you can use structured messageType values instead of simple role to give Greenflash richer visibility into agent behavior. Key concepts:
  • Use messageType instead of role to describe what the agent is doing
  • Use content only for user-facing input/output
  • Use output for internal agent data (thoughts, tool results)
  • Use input for tool call parameters
  • Link related messages with externalMessageId and parentExternalMessageId
Example: Agent with tool call
client.messages.create({
  productId: 'YOUR_PRODUCT_ID',
  externalUserId: userId,
  externalConversationId: convoId,
  messages: [
    // User query (user-facing)
    { role: 'user', content: "What's my account balance?" },
    // Agent reasoning (internal)
    {
      externalMessageId: 'thought-1',
      messageType: 'thought',
      output: 'User wants account balance. Need to call account_lookup tool.',
    },
    // Tool invocation
    {
      externalMessageId: 'tool-1',
      messageType: 'tool_call',
      toolName: 'account_lookup',
      input: { userId: '12345' },
    },
    // Tool result (internal)
    {
      externalMessageId: 'obs-1',
      messageType: 'observation',
      parentExternalMessageId: 'tool-1',
      output: { balance: 1250.00, currency: 'USD' },
    },
    // Final response (user-facing)
    { role: 'assistant', content: 'Your current account balance is $1,250.00.' },
  ],
}).catch(err => console.error('Greenflash error:', err));
For more details: See the Agentic Messages prompt for comprehensive guidance on message types, content vs output, and modeling complex agent execution.

7) (Optional) Identify users — only the required field

Call once when a user logs in or as soon as a stable ID is available. Required: externalUserId. Optional fields you can include: externalOrganizationId, name, email, phone, properties (object for custom data).
// identify-user.ts
import { client } from './greenflash-client';

function identifyUser(userId: string, orgId?: string): void {
  const params: any = { externalUserId: userId };
  if (orgId) {
    params.externalOrganizationId = orgId;
  }

  // Fire-and-forget
  client.users.create(params).catch(err => {
    console.error('Failed to identify user:', err);
  });
}

8) (Optional) Identify organizations — only the required field

Use when you have a clear organization/account ID. Required: externalOrganizationId. Optional fields you can include: name, properties (object for custom data).
// identify-org.ts
import { client } from './greenflash-client';

function identifyOrg(orgId: string): void {
  client.organizations.create({
    externalOrganizationId: orgId
  }).catch(err => {
    console.error('Failed to identify organization:', err);
  });
}

While fire-and-forget logging is appropriate for most cases, you may want to handle errors for monitoring purposes:
import Greenflash from 'greenflash';

try {
  await client.messages.create({
    productId: 'YOUR_PRODUCT_ID',
    externalUserId: 'USER_ID',
    externalConversationId: 'CONVO_ID',
    messages: [{ role: 'user', content: 'Hello' }],
  });
} catch (err) {
  if (err instanceof Greenflash.APIConnectionError) {
    console.error('Connection error:', err);
  } else if (err instanceof Greenflash.RateLimitError) {
    console.error('Rate limit exceeded:', err);
  } else if (err instanceof Greenflash.APIError) {
    console.error(`API error ${err.status}:`, err.name);
  } else {
    throw err;
  }
}
Available error classes: APIConnectionError, RateLimitError, APIError, AuthenticationError, PermissionDeniedError, NotFoundError, ValidationError.

10) File/PR checklist

  • Installed package: npm install greenflash (or yarn/pnpm equivalent)
  • Added GREENFLASH_API_KEY to runtime env (not hard-coded).
  • Have your Product ID ready (from Greenflash) to pass directly in each API call.
  • Created greenflash-client.ts with the client.
  • Analyzed codebase to determine message logging pattern:
    • User input + assistant output in same location → use Pattern A (send as turns)
    • User input + assistant output in separate locations → use Pattern B (send individually)
  • Logging messages with only required fields: productId, externalUserId, externalConversationId, messages.
  • Passing productId directly in each client.messages.create(...) call (not from an env var).
  • Using fire-and-forget pattern (no await) to avoid blocking LLM responses.
  • Added .catch() handlers to prevent unhandled promise rejections.
  • (Optional) Added client.users.create(...) where a stable user ID exists.
  • (Optional) Added client.organizations.create(...) where an org ID exists.
Deliverable: a single PR containing greenflash-client.ts, minimal logging wired into the main chat flow (using the appropriate pattern based on codebase structure), and optional user/org identification hooks. Include README notes describing env vars and where the logging occurs.