import type { Result } from '../domain/result.js'; import type { AuthManager } from '../infra/auth.js'; type GraphError = { type: 'api_error'; status: number; message: string; code?: string; } | { type: 'auth_failed'; message: string; code?: string; } | { type: 'network_error'; message: string; code?: string; } | { type: 'validation_error'; message: string; code?: string; }; type GraphClient = { /** * `extraHeaders` lets a caller add request headers Graph requires on * specific endpoints — currently the only documented use is * `Prefer: odata.maxpagesize=N` on the calendar/mail delta endpoints, * which reject `$top` as a query parameter. Auth + content-type are * always set internally. */ get: (path: string, extraHeaders?: Record) => Promise>; /** * Same JSON-GET shape as `get`, but signs the request with the * elevated Graph token (M365ChatClient). Used by commands the Teams * web client token cannot reach — currently `list-chats` and * `get-chat`, which need `Chat.ReadBasic` (only present on the * elevated token). */ getElevated: (path: string) => Promise>; /** * JSON-GET against the Teams chat substrate (post-2026-05: * `teams.microsoft.com/api/csa//api/v{N}/...` — see * `gotcha_chatsvcagg_substrate_moved` in memory for the migration * away from `chatsvcagg.teams.microsoft.com`). Signs the request * with the chatsvcagg-audience bearer captured at login (same Teams * web client identity as `get`, different audience), and injects the * cached substrate region between the host and `path`. Used by * commands that need to read chat message BODIES, which the basic * Graph token cannot reach (`Chat.Read*` scopes are missing). * * `path` MUST start with `/api/v{N}/...` — the host + `/api/csa/` * prefix are added by this client. */ teamsChat: (path: string) => Promise>; /** * JSON-GET against the Teams IC3 chat-message substrate at * `teams.microsoft.com/api/chatsvc//v1/...`. Same host as * `teamsChat` but a DIFFERENT path prefix AND a different bearer * audience (`https://ic3.teams.office.com` instead of * `https://chatsvcagg.teams.microsoft.com`). The path supports * `syncState` + `startTime` pagination — unlocking arbitrary-depth * chat-history reads beyond the chatsvcagg 200-message cap (see * `gotcha_chatsvcagg_substrate_moved` in memory). Used by * `list-teams-chat-history`. * * `path` MUST start with `/v1/...` (e.g. `/v1/users/ME/conversations/{id}/messages?startTime=...`) * — the host + `/api/chatsvc/` prefix are added here. */ teamsChatIc3: (path: string) => Promise>; post: (path: string, body: unknown) => Promise>; getBinary: (path: string) => Promise>; /** * Same shape as `getBinary` but signs the request with an "elevated" * Graph token (issued for an app on Microsoft's ODSP * `logicalPermissions` allow-list — e.g., M365ChatClient). Used by * the historical-version commands which the Teams web client token * cannot fetch (403 logicalPermissionAccessDenied). */ getBinaryElevated: (path: string) => Promise>; /** * Auth-less fetch of an arbitrary URL whose host MUST be on the * Microsoft allow-list. Used to follow `@microsoft.graph.downloadUrl` * 302 redirects (CDN-signed URLs) that the format-conversion * commands sometimes get back from Graph instead of inline bytes. */ fetchUrl: (url: string) => Promise>; /** * Upload bytes to a drive item. `basePath` is the bare driveItem * path (e.g. `/me/drive/root:/.ask-marcel-temp/abc.rtf`) — `put()` * appends `:/content` for the simple ≤4 MiB sync path or * `:/createUploadSession` for the chunked-session path internally * based on `body.byteLength`. No upper file-size limit beyond the * user's OneDrive quota. */ put: (basePath: string, body: Uint8Array, contentType?: string) => Promise>; delete: (path: string) => Promise>; /** * Decode the cached basic Teams token's JWT and return its scopes / * audience / expiry. Used by the `scopes-check` self-test command so the * LLM can predict `accessDenied` instead of discovering it on the next * Graph call. No network IO — operates on the cached token only. */ getCachedTokenInfo: () => Promise>; }; type TokenInfo = { readonly scopes: ReadonlyArray; readonly audience: string | undefined; readonly expiresAt: string | undefined; /** * Seconds remaining until the cached token's `exp` claim — derived from * `expiresAt - now`. Negative when the token has already expired. Absent * when the JWT did not carry an `exp` claim. Audit Hervé-session §4: lets * an LLM decide pre-emptively to run `ask-marcel login` (re-auth typically * worth doing under ~5 minutes) without parsing the ISO string itself. */ readonly expiresInSeconds: number | undefined; }; type FetchFn = (url: string, init?: RequestInit) => Promise; declare const createGraphClient: (auth: AuthManager, fetchFn?: FetchFn) => GraphClient; export { createGraphClient }; export type { FetchFn, GraphClient, GraphError, TokenInfo };