toolCallDetails.test.ts5.3 KBView on GitHub
/**
 * `describeToolCall` — what a tool-call row says, and what it reveals when expanded.
 *
 * These are the labels the transcript is READ from. Before them every call into an action
 * family rendered as `Executing linkedin-write`, so four invitations to four people were four
 * identical lines and the transcript recorded nothing about who was contacted or whether a
 * note went with it.
 */

import { describeToolCall, nameFromLinkedinSlug } from '@/modules/cedar-os/src/components/renderers/toolCallDetails';

describe('nameFromLinkedinSlug', () => {
  it('reads a name out of a hyphenated slug', () => {
    expect(nameFromLinkedinSlug('jason-kobs')).toBe('Jason Kobs');
  });

  it('drops the random discriminator LinkedIn appends to a taken handle', () => {
    expect(nameFromLinkedinSlug('leo-ohaus-90b3351b0')).toBe('Leo Ohaus');
  });

  it('leaves an unsplittable handle alone rather than guessing word boundaries', () => {
    expect(nameFromLinkedinSlug('jackbartlett13')).toBe('jackbartlett13');
  });
});

describe('describeToolCall — LinkedIn', () => {
  it('names the person a connection request is going to, and links their profile', () => {
    const described = describeToolCall('linkedin-write', {
      action: 'send-invitation',
      sendInvitation: { url: 'https://www.linkedin.com/in/jason-kobs' },
    });
    expect(described?.label).toBe('Sending a LinkedIn connection request to Jason Kobs');
    expect(described?.rows).toContainEqual({
      label: 'Profile',
      value: 'https://www.linkedin.com/in/jason-kobs',
      href: 'https://www.linkedin.com/in/jason-kobs',
    });
  });

  it('says out loud that an invitation carried no note', () => {
    const described = describeToolCall('linkedin-write', {
      action: 'send-invitation',
      sendInvitation: { url: 'https://www.linkedin.com/in/jason-kobs' },
    });
    expect(described?.rows).toContainEqual({ label: 'Note', value: 'No note — invitation only' });
  });

  it('shows the note when there was one', () => {
    const described = describeToolCall('linkedin-write', {
      action: 'send-invitation',
      sendInvitation: { url: 'https://www.linkedin.com/in/jack', note: 'Hey Jack — I work with Willem.' },
    });
    expect(described?.rows).toContainEqual({ label: 'Note', value: 'Hey Jack — I work with Willem.' });
  });

  it('does not invent a name for a member addressed only by provider id', () => {
    const described = describeToolCall('linkedin-write', {
      action: 'send-invitation',
      sendInvitation: { providerId: 'ACoAABVHhO8B2EWRp7HE4YiPw6vI3fYjaYlOA0w' },
    });
    expect(described?.label).toBe('Sending a LinkedIn connection request to a LinkedIn member');
    expect(described?.rows).toContainEqual({
      label: 'Member id',
      value: 'ACoAABVHhO8B2EWRp7HE4YiPw6vI3fYjaYlOA0w',
    });
  });

  it('distinguishes a profile view from a connection request', () => {
    expect(
      describeToolCall('linkedin-read', { action: 'view-profile', viewProfile: { identifier: 'odin-hoffmann' } })?.label,
    ).toBe("Viewing Odin Hoffmann's LinkedIn profile");
    expect(describeToolCall('linkedin-read', { action: 'list-connections' })?.label).toBe(
      'Reading your LinkedIn connections',
    );
  });
});

describe('describeToolCall — tables', () => {
  it('names the table being created and the columns it declares', () => {
    const described = describeToolCall('table', {
      action: 'create',
      path: 'conversation/5d14874e/listen-labs-aes',
      create: { schema: { columns: [{ key=[redacted], label: 'Name' }, { key=[redacted], label: 'Connection Status' }] } },
    });
    expect(described?.label).toBe('Creating the table listen-labs-aes');
    expect(described?.rows).toContainEqual({ label: 'Columns', value: 'Name, Connection Status' });
  });

  it('counts the cells a set writes, and previews them', () => {
    const described = describeToolCall('table', {
      action: 'set',
      path: 'conversation/5d14874e/listen-labs-aes',
      set: { assignments: 'jack.status = reached out\njason.status = reached out' },
    });
    expect(described?.label).toBe('Filling in 2 cells in listen-labs-aes');
    expect(described?.rows).toContainEqual({
      label: 'Changes',
      value: 'jack.status = reached out\njason.status = reached out',
    });
  });

  it("surfaces a select column's option ORDER, which is also its sort order", () => {
    const described = describeToolCall('table', {
      action: 'add_columns',
      path: '#kb/aes',
      add_columns: {
        columns: [
          { key=[redacted], label: 'Connection Status', type: 'select', options: ['not connected', 'reached out', 'connected'] },
        ],
      },
    });
    expect(described?.label).toBe('Adding the Connection Status column in aes');
    expect(described?.rows).toContainEqual({
      label: 'Connection Status',
      value: 'not connected → reached out → connected',
    });
  });
});

describe('describeToolCall — the generic tail', () => {
  it('names the action of a family it has no specific branch for', () => {
    expect(describeToolCall('conversation-write', { action: 'update-fields' })?.label).toBe(
      'Conversation write · Update fields',
    );
  });

  it('defers to the static mapping when the args say nothing', () => {
    expect(describeToolCall('find-emails', {})).toBeNull();
  });
});