# Utilities

Common utility functions for working with ThreadState

## Overview

`ThreadState` provides utility methods for common thread operations. Tools, hooks, and thread endpoints call these methods on the `state` object they receive.

## queueTool

Queue a tool call for execution in the current thread. During an execution,
the call joins the tool queue. On an idle thread, it starts a new execution.
The method returns `void`.

```typescript
state.queueTool('tool_name', { arg: 'value' });
```

**Parameters:**

- `toolName`: The name of the tool to call
- `args`: Arguments to pass to the tool

**Example:**

```typescript
import { defineTool } from "@standardagents/spec";
import { z } from "zod";

export default defineTool({
  description: "Process user order and send confirmation",
  args: z.object({
    orderId: z.string(),
    userId: z.string(),
  }),
  execute: async (state, args) => {
    // Validate the order
    const order = await validateOrder(args.orderId);

    if (!order.valid) {
      return {
        status: "error",
        error: "Order validation failed",
      };
    }

    // Queue additional tools to execute after this one
    state.queueTool("charge_payment", {
      orderId: args.orderId,
      amount: order.total,
    });

    state.queueTool("send_confirmation_email", {
      userId: args.userId,
      orderId: args.orderId,
    });

    state.queueTool("update_inventory", {
      items: order.items,
    });

    return {
      status: "success",
      result: `Order ${args.orderId} queued for processing`,
    };
  },
});
```

---

## invokeTool

Invoke a tool directly and wait for the result.

```typescript
const result = await state.invokeTool('tool_name', { arg: 'value' });

if (result.status === 'success') {
  console.log(result.result);
}
```

**Parameters:**

- `toolName`: The name of the tool to invoke
- `args`: Arguments to pass to the tool

**Returns:** `Promise<ToolResult>`

---

## injectMessage

Inject a message into the thread conversation.

```typescript
const message = await state.injectMessage({
  role: 'user',
  content: 'Additional context...',
  silent: true,
  metadata: { source: 'tool' },
});
```

**Options:**

```typescript
interface InjectMessageInput {
  role: 'user' | 'assistant' | 'system';
  content: string;
  silent?: boolean;          // Hide from UI
  metadata?: Record<string, unknown>;
}
```

`injectMessage()` persists the record and runs `before_create_message` and
`after_create_message` in active and at-rest contexts. During execution, later
tools and hooks immediately see it in `state.execution.messageHistory`.
`silent` controls ordinary UI presentation; silent records still participate in
the request context. A context assembly uses one stable history snapshot,
so injection from `filter_messages` or `prefilter_llm_history` becomes eligible
for the following assembly.

**Examples:**

### System Context

```typescript
export default defineTool({
  description: "Load user context",
  args: z.object({ userId: z.string() }),
  execute: async (state, args) => {
    const user = await fetchUser(args.userId);

    await state.injectMessage({
      role: "system",
      content: `User context loaded: ${user.name} (${user.email})
        - Account tier: ${user.tier}
        - Preferences: ${JSON.stringify(user.preferences)}`,
    });

    return {
      status: "success",
      result: "User context injected",
    };
  },
});
```

### Silent Message

```typescript
export default defineTool({
  description: "Add internal note",
  args: z.object({ note: z.string() }),
  execute: async (state, args) => {
    await state.injectMessage({
      role: "system",
      content: args.note,
      silent: true,  // Hidden from ordinary UI lists
    });

    return {
      status: "success",
      result: "Internal note added",
    };
  },
});
```

---

## getMessages

Retrieve message history from the thread.

```typescript
const { messages, total, hasMore } = await state.getMessages({
  limit: 50,
  offset: 0,
  order: 'desc',
});
```

**Options:**

| Option          | Type              | Default  | Description             |
| --------------- | ----------------- | -------- | ----------------------- |
| `limit`         | `number`          | 100      | Max messages to return  |
| `offset`        | `number`          | 0        | Messages to skip        |
| `order`         | `'asc' \| 'desc'` | `'desc'` | Sort order              |
| `includeSilent` | `boolean`         | false    | Include silent messages |
| `maxDepth`      | `number`          | 0        | Max sub-prompt nesting  |

**Example:**

```typescript
export default defineTool({
  description: "Analyze recent conversation tone",
  execute: async (state) => {
    // Get last 10 messages
    const { messages } = await state.getMessages({ limit: 10 });

    // Filter user and assistant messages only
    const conversationMessages = messages.filter(
      (m) => m.role === "user" || m.role === "assistant"
    );

    const tone = analyzeTone(conversationMessages);

    return {
      status: "success",
      result: `Conversation tone: ${tone}`,
    };
  },
});
```

---

## emit (Custom Events)

Send custom events to WebSocket clients.

```typescript
state.emit('event_type', {
  key: 'value',
  progress: 50,
});
```

**Parameters:**

- `event`: Event type name
- `data`: Event payload (must be JSON-serializable). When it is `undefined`, clients receive `"data": null`.

**Event Structure (received by clients):**

```json
{
  "type": "event",
  "eventType": "<your-type>",
  "data": <your-data>,
  "timestamp": 1234567890
}
```

**Examples:**

### Progress Updates

```typescript
export default defineTool({
  description: "Process large dataset",
  args: z.object({ datasetId: z.string() }),
  execute: async (state, args) => {
    const dataset = await loadDataset(args.datasetId);
    const total = dataset.length;

    for (let i = 0; i < total; i++) {
      await processItem(dataset[i]);

      // Emit progress event every 10 items
      if (i % 10 === 0) {
        state.emit("progress", {
          current: i + 1,
          total,
          percentage: ((i + 1) / total) * 100,
          message: `Processing item ${i + 1} of ${total}`,
        });
      }
    }

    return {
      status: "success",
      result: `Processed ${total} items`,
    };
  },
});
```

### Status Updates

```typescript
export default defineTool({
  description: "Upload file",
  args: z.object({ fileUrl: z.string() }),
  execute: async (state, args) => {
    state.emit("upload_status", {
      status: "started",
      message: "Starting download...",
    });

    const file = await downloadFile(args.fileUrl);

    state.emit("upload_status", {
      status: "processing",
      message: "Processing file...",
    });

    const result = await processFile(file);

    state.emit("upload_status", {
      status: "complete",
      message: "Upload complete!",
    });

    return {
      status: "success",
      result: "File processed",
    };
  },
});
```

### Frontend Integration

Listen for events on the frontend using `@standardagents/react`:

```tsx
import {
  AgentBuilderProvider,
  ThreadProvider,
  onThreadEvent,
} from "@standardagents/react";

function App() {
  return (
    <AgentBuilderProvider config={{ endpoint: "https://api.example.com" }}>
      <ThreadProvider threadId="123e4567-e89b-12d3-a456-426614174000">
        <OrderTracker />
      </ThreadProvider>
    </AgentBuilderProvider>
  );
}

function OrderTracker() {
  onThreadEvent<{ percentage: number }>('progress', (data) => {
    console.log('Progress:', data.percentage);
  });

  return <div>...</div>;
}
```

---

## forceTurn (Execution Control)

Force the next execution turn to a specific side.

```typescript
// Only available during execution
if (state.execution) {
  state.execution.forceTurn('b');
}
```

**Parameters:**

- `side`: The side to force (`'a'` or `'b'`)

> **Note:**
>
> Agents with two prompt-backed sides use this to control turn order.

---

## stop (Execution Control)

Stop the current execution after the current operation completes.

```typescript
if (state.execution) {
  state.execution.stop();
}
```

---

## scheduleEffect

Schedule an effect for delayed or background execution.

```typescript
const effectId = await state.scheduleEffect(
  'send_reminder_email',
  { to: 'user@example.com', subject: 'Reminder' },
  30 * 60 * 1000  // 30 minutes
);
```

**Parameters:**

- `name`: Effect name (file in `agents/effects/`)
- `args`: Arguments passed to effect handler
- `delay`: Delay in milliseconds (default: 0)

**Returns:** Effect ID (UUID) for later cancellation

See [Effects](/docs/core-concepts/effects) for effect definitions, cancellation, and failure behavior.

---

## Complete Example

Here's a comprehensive example showing multiple utilities working together:

```typescript
import { defineTool } from "@standardagents/spec";
import { z } from "zod";

export default defineTool({
  description: "Complete order processing workflow",
  args: z.object({
    orderId: z.string(),
    userId: z.string(),
    notifyUser: z.boolean().default(true),
  }),
  execute: async (state, args) => {
    // Emit initial status event
    state.emit("order_status", {
      orderId: args.orderId,
      status: "started",
    });

    // Inject system message for context
    await state.injectMessage({
      role: "system",
      content: `Processing order ${args.orderId} for user ${args.userId}`,
    });

    // Queue validation tool
    state.queueTool("validate_order", {
      orderId: args.orderId,
    });

    // Queue payment processing
    state.queueTool("process_payment", {
      orderId: args.orderId,
      userId: args.userId,
    });

    // Queue inventory update
    state.queueTool("update_inventory", {
      orderId: args.orderId,
    });

    // Conditionally queue notification
    if (args.notifyUser) {
      state.queueTool("send_notification", {
        userId: args.userId,
        type: "order_confirmation",
        orderId: args.orderId,
      });
    }

    // Get conversation history for audit
    const { messages } = await state.getMessages({ limit: 50 });
    const orderMessages = messages.filter(
      (m) => m.content?.includes(args.orderId)
    );

    // Emit completion event
    state.emit("order_status", {
      orderId: args.orderId,
      status: "queued",
      steps: 4,
      relatedMessages: orderMessages.length,
    });

    return {
      status: "success",
      result: `Order ${args.orderId} workflow initiated`,
    };
  },
});
```

## Best Practices

### Error Handling

Always wrap async operations in try-catch blocks:

```typescript
try {
  await state.injectMessage({
    role: "system",
    content: "Context updated",
  });
} catch (error) {
  console.error("Failed to inject message:", error);
  return {
    status: "error",
    error: `Message injection failed: ${error instanceof Error ? error.message : String(error)}`,
  };
}
```

### Throttle Event Emissions

Avoid excessive event emissions that could overwhelm WebSocket clients:

```typescript
// Good: Throttle emissions
for (let i = 0; i < 10000; i++) {
  if (i % 100 === 0) {
    state.emit("progress", {
      current: i,
      total: 10000,
    });
  }
}

// Avoid: Emitting inside tight loop
for (let i = 0; i < 10000; i++) {
  state.emit("progress", { value: i });
}
```

### Message Pagination

When retrieving large message histories, use pagination:

```typescript
const pageSize = 50;
let offset = 0;
let hasMore = true;

while (hasMore) {
  const result = await state.getMessages({ limit: pageSize, offset });
  await processMessages(result.messages);
  hasMore = result.hasMore;
  offset += pageSize;
}
```

### Check Execution State

When using execution-specific features, check if execution is active:

```typescript
if (state.execution) {
  console.log(`Step: ${state.execution.stepCount}`);
  state.execution.forceTurn('b');
}
```

## Troubleshooting

### Events not received by frontend

**Solution:** Ensure you're using `ThreadProvider` and that you've subscribed to the correct event type name.

### Tool not executing after queueTool

**Solution:** Check the callable name and the current tool's `uses` list. Check
whether the execution stopped before the queued call ran.

### getMessages returns empty

**Solution:** Check that messages exist in the thread and that limit/offset parameters are correct.

## Next Steps

- [ThreadState](/docs/core-concepts/flowstate)

  Understand the ThreadState interface

* [Tools](/docs/core-concepts/tools)

  Create custom tools

- [Hooks](/docs/core-concepts/hooks)

  Use utilities in lifecycle hooks

* [React Integration](/docs/user-interfaces/react)

  Connect frontend to backend events
