# @cuylabs/agent-a365-tooling

Microsoft Agent 365 tooling adapter for `@cuylabs/agent-core`.

This package exposes a capability provider factory and an optional Agent 365
chat-history middleware. On each chat turn, the provider uses
Microsoft's Agent 365 tooling SDK to discover MCP servers for the active
Microsoft 365 turn, connects those servers through agent-core's MCP manager,
and returns MCP tools for that turn only. The middleware can submit recent
agent-core chat history to Microsoft's Agent 365 real-time threat protection
registration endpoint.

Use this when your agent is hosted behind Microsoft 365 / Agent 365 and should
use the MCP tool servers configured for the current tenant, user, and agent
identity. Do not use it for static MCP servers known at startup; use
`agent-core` MCP config for those.

## What It Gives You

- Agent 365 MCP server discovery per Microsoft 365 turn.
- Microsoft SDK token exchange and per-server bearer headers.
- Agent 365 platform headers for real Microsoft-hosted MCP servers.
- Capability-provided MCP tools that are cleaned up after the turn.
- Opt-in Agent 365 chat-history submission for Microsoft's real-time threat
  protection registration API.
- A Microsoft-specific adapter without importing Microsoft SDK types into
  `agent-core`.

## Install

```bash
pnpm add @cuylabs/agent-a365-tooling @microsoft/agents-a365-tooling
```

If you use the default ambient TurnContext lookup, also use
`@cuylabs/channel-m365-agent-core` and configure `@microsoft/agents-hosting` in the
host app. The dynamically loaded channel integration is not installed
transitively; custom hosts can instead provide `getTurnContext` directly.

## Usage

```ts
import { createAgent } from "@cuylabs/agent-core";
import { createA365ToolingCapabilityProvider } from "@cuylabs/agent-a365-tooling";

const agent = createAgent({
  model,
  capabilityProviders: [
    createA365ToolingCapabilityProvider({
      authorization,
      authHandlerName: "agentic",
      toolOptions: {
        orchestratorName: "dory",
      },
    }),
  ],
});
```

By default the provider reads the active `TurnContext` from
`currentM365TurnContext()` in `@cuylabs/channel-m365-agent-core`. For tests or custom
hosts, pass `getTurnContext`.

```ts
createA365ToolingCapabilityProvider({
  authorization,
  authHandlerName: "agentic",
  getTurnContext: () => myTurnContext,
});
```

## Chat History Submission

Microsoft's RTP API is developer-invoked; this package does not automatically
intercept every conversation. Register the middleware only when the host should
submit chat history for Microsoft Agent 365 analysis.

```ts
import { createAgent } from "@cuylabs/agent-core";
import {
  createA365ChatHistoryMiddleware,
  createA365ToolingCapabilityProvider,
} from "@cuylabs/agent-a365-tooling";

const agent = createAgent({
  model,
  capabilityProviders: [
    createA365ToolingCapabilityProvider({
      authorization,
      authHandlerName: "agentic",
    }),
  ],
  middleware: [
    createA365ChatHistoryMiddleware({
      toolOptions: {
        orchestratorName: "dory",
      },
    }),
  ],
});
```

The middleware defaults to `timing: "after-turn"`, `limit: 20`, and
`maxContentChars: 32000`. It reads history lazily from agent-core's lifecycle
context, converts text content to Microsoft's
`{ id, role, content, timestamp }` shape, keeps the most recent converted
messages within the content budget, and calls `sendChatHistory` even when the
converted history is `[]`. Empty arrays are intentional: Microsoft uses the
current `TurnContext.activity` as the message being registered, with
`chatHistory` as context.
On `onChatEnd`, agent-core resolves this lazy history from the completed turn
before any automatic context compaction rewrites the visible session history.

Tool messages are included by default to match Microsoft's standard-message
expectation. Set `includeTools: false` only when the host has a privacy or
compliance reason to filter tool outputs; doing so can reduce RTP context.

For non-M365 hosts, missing TurnContext is skipped by default:

```ts
createA365ChatHistoryMiddleware({
  onMissingTurnContext: "skip", // "skip" | "warn"
  getTurnContext: () => myTurnContext,
});
```

If another `onChatEnd` hook must run after the submission attempt, place it
later in the middleware array. The captured history leaf is fixed before
automatic compaction and before `onChatEnd` hooks run. The Microsoft SDK returns
transport success/failure only; threat verdicts are not returned inline through
`sendChatHistory`.
Submission failures are non-blocking by design; use `failureMode: "warn"` to
log them or `failureMode: "ignore"` to silence them.

## Concept Docs

The package docs are split by concern:

| Doc                                                                | What it explains                                                                                               |
| ------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------- |
| [docs/architecture.md](./docs/architecture.md)                     | The four-layer split between agent-core, M365 channel ingress, Microsoft's A365 tooling SDK, and this adapter. |
| [docs/agent-core-mcp.md](./docs/agent-core-mcp.md)                 | How this package uses agent-core `capabilityProviders` and how that differs from static agent-core MCP config. |
| [docs/microsoft-a365-tooling.md](./docs/microsoft-a365-tooling.md) | What Microsoft's Agent 365 tooling SDK owns: discovery, dev manifest mode, gateway calls, and auth headers.    |
| [docs/lifecycle-and-limits.md](./docs/lifecycle-and-limits.md)     | Per-turn lifecycle, cleanup, latency, filtering, connector replacement, and v0 limits.                         |

Start with [docs/README.md](./docs/README.md) if you want the reading order.

## Required Host Inputs

| Option            | Meaning                                                             | Typical source                                                         |
| ----------------- | ------------------------------------------------------------------- | ---------------------------------------------------------------------- |
| `authorization`   | Microsoft Agents SDK `Authorization` object used for token exchange | Your M365 host application / `@microsoft/agents-hosting` setup         |
| `authHandlerName` | Name of the auth handler configured for Agent 365 token exchange    | Your Microsoft Agents SDK app configuration                            |
| `getTurnContext`  | Optional resolver for the active Microsoft `TurnContext`            | Defaults to `@cuylabs/channel-m365-agent-core` ambient context         |
| `authToken`       | Optional pre-resolved gateway token                                 | Local development or specialized hosts; normally omitted in production |
| `toolOptions`     | Optional metadata such as `orchestratorName`                        | Host/application configuration                                         |

In production, prefer omitting `authToken` so Microsoft's SDK performs token
exchange for the active turn. In local dev-manifest mode, examples can pass a
fake decoded token because the SDK reads `ToolingManifest.json` instead of
calling the cloud gateway.

## Open Microsoft Questions

These do not block the v0 adapter, but they matter for production policy:

- Does `/agents/real-time-threat-protection/chat-message` require an
  `Authorization` header in production? Microsoft's current SDK call path passes
  `undefined` as the auth token.
- What is the server-side HTTP body size cap for `chatHistory`, and does
  Microsoft recommend a client-side history limit?
- Are retries with the same `activity.id` / `messageId` guaranteed idempotent?
- Should post-turn `chatHistory` include the current user/assistant/tool
  messages, or only messages before the current activity?

## What This Does Not Do

This package is not a channel, not an LLM provider, and not a replacement for
agent-core's MCP engine. It does not implement cross-turn MCP pooling,
mid-turn token refresh, or inline threat blocking. `sendChatHistory` is a
submit/register call; hosts should not treat it as a synchronous threat verdict.

## Examples

Runnable examples live in the repository under
[`packages/agent-a365-tooling/examples`](https://github.com/cuylabs-ai/agents-ts/tree/main/packages/agent-a365-tooling/examples).
They are intentionally not included in the npm tarball because the example
`tsconfig.json` and `ToolingManifest.json` are monorepo-local development
harness files.

Start with `examples/01-dev-manifest.ts`; it uses Microsoft's local
`ToolingManifest.json` path and an in-process MCP fixture, so it verifies the
provider without Azure.
