# Citations
Citations link spans of an assistant response back to the sources that support them. Turn on `enableCitations` when you create or resume a session, then read the `citations` payload on `assistant.message` events to render footnotes, source lists, or inline links.
> [!WARNING]
> Citations are experimental. The option name, event payload, and provider coverage can change in a future release.
## How citations work
Citations are produced by the model provider, not by the SDK. The flow has three parts:
1. Your application supplies citable material, such as a document attachment or a tool result that carries source content.
1. The runtime marks that material as citable on the wire when `enableCitations` is on. For Anthropic models, file attachments are sent as `document` blocks with citations enabled.
1. The model returns citation metadata, and the runtime normalizes it into a provider-agnostic `citations` object on the final `assistant.message` event.
Provider support is limited. The `provider` field on each source records where the citation came from:
| Provider value | Meaning |
|---|---|
| `anthropic` | Citation produced by an Anthropic (Claude) model response |
| `openai` | Citation produced by an OpenAI model response |
| `client` | Citation synthesized by the runtime from tool output |
> [!NOTE]
> Turning on `enableCitations` does not guarantee that a response contains citations. Models emit them only when the response is grounded in citable source material. Always treat the `citations` field as optional.
## Enable citations on a session
Set the option on session create, and set it again on resume if you want citations after a restart.
TypeScript
```typescript
const session = await client.createSession({
onPermissionRequest: approveAll,
enableCitations: true,
});
const resumed = await client.resumeSession(session.sessionId, {
onPermissionRequest: approveAll,
enableCitations: true,
});
```
Python
```python
session = await client.create_session(
on_permission_request=PermissionHandler.approve_all,
enable_citations=True,
)
resumed = await client.resume_session(
session.session_id,
on_permission_request=PermissionHandler.approve_all,
enable_citations=True,
)
```
Go
```go
session, err := client.CreateSession(ctx, &copilot.SessionConfig{
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
EnableCitations: copilot.Bool(true),
})
resumed, err := client.ResumeSession(ctx, session.SessionID, &copilot.ResumeSessionConfig{
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
EnableCitations: copilot.Bool(true),
})
```
.NET
```csharp
var session = await client.CreateSessionAsync(new SessionConfig
{
OnPermissionRequest = PermissionHandler.ApproveAll,
EnableCitations = true,
});
var resumed = await client.ResumeSessionAsync(session.SessionId, new ResumeSessionConfig
{
OnPermissionRequest = PermissionHandler.ApproveAll,
EnableCitations = true,
});
```
Java
```java
CopilotSession session = client
.createSession(new SessionConfig()
.setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
.setEnableCitations(true))
.get();
CopilotSession resumed = client
.resumeSession(session.getSessionId(), new ResumeSessionConfig()
.setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
.setEnableCitations(true))
.get();
```
Rust
```rust
let session = client
.create_session(
SessionConfig::new()
.approve_all_permissions()
.with_enable_citations(true),
)
.await?;
let resumed = client
.resume_session(
ResumeSessionConfig::new(session.id().clone())
.approve_all_permissions()
.with_enable_citations(true),
)
.await?;
```
## Read citations from assistant messages
Citations arrive on the final `assistant.message` event, not on `assistant.message_delta` events. Wait for the final message before you render source markers.
TypeScript
```typescript
session.on((event) => {
if (event.type !== "assistant.message" || !event.data.citations) {
return;
}
const { sources, spans } = event.data.citations;
const sourceById = new Map(sources.map((source) => [source.id, source]));
for (const span of spans) {
const quoted = event.data.content.slice(span.startIndex, span.endIndex);
for (const reference of span.references) {
const source = sourceById.get(reference.sourceId);
const label = source?.title ?? source?.url ?? source?.path ?? source?.id;
console.log(`"${quoted}" — ${label}`);
}
}
});
```
Python
```python
from copilot.session_events import SessionEventType
def utf16_slice(text: str, start: int, end: int) -> str:
"""Slice by UTF-16 code units, which is how span offsets are measured."""
units = text.encode("utf-16-le")
return units[start * 2 : end * 2].decode("utf-16-le")
def handle(event):
if event.type != SessionEventType.ASSISTANT_MESSAGE or not event.data.citations:
return
sources = {source.id: source for source in event.data.citations.sources}
for span in event.data.citations.spans:
quoted = utf16_slice(event.data.content, span.start_index, span.end_index)
for reference in span.references:
source = sources[reference.source_id]
label = source.title or source.url or source.path or source.id
print(f'"{quoted}" — {label}')
session.on(handle)
```
Go
```go
// import "unicode/utf16"
session.On(func(event copilot.SessionEvent) {
d, ok := event.Data.(*copilot.AssistantMessageData)
if !ok || d.Citations == nil {
return
}
sources := map[string]copilot.CitationSource{}
for _, source := range d.Citations.Sources {
sources[source.ID] = source
}
// Span offsets are UTF-16 code units, so index the UTF-16 view of the content.
units := utf16.Encode([]rune(d.Content))
for _, span := range d.Citations.Spans {
quoted := string(utf16.Decode(units[span.StartIndex:span.EndIndex]))
for _, reference := range span.References {
source := sources[reference.SourceID]
label := source.ID
switch {
case source.Title != nil:
label = *source.Title
case source.URL != nil:
label = *source.URL
case source.Path != nil:
label = *source.Path
}
fmt.Printf("%q — %s\n", quoted, label)
}
}
})
```
.NET
```csharp
session.On(evt =>
{
if (evt is not AssistantMessageEvent message || message.Data.Citations is null)
{
return;
}
var sources = message.Data.Citations.Sources.ToDictionary(source => source.Id);
foreach (var span in message.Data.Citations.Spans)
{
var quoted = message.Data.Content[(int)span.StartIndex..(int)span.EndIndex];
foreach (var reference in span.References)
{
var source = sources[reference.SourceId];
var label = source.Title ?? source.Url ?? source.Path ?? source.Id;
Console.WriteLine($"\"{quoted}\" — {label}");
}
}
});
```
Java
```java
session.on(AssistantMessageEvent.class, event -> {
Citations citations = event.getData().citations();
if (citations == null) {
return;
}
Map sources = citations.sources().stream()
.collect(Collectors.toMap(CitationSource::id, source -> source));
for (CitationSpan span : citations.spans()) {
String quoted = event.getData().content()
.substring(span.startIndex().intValue(), span.endIndex().intValue());
for (CitationReference reference : span.references()) {
CitationSource source = sources.get(reference.sourceId());
String label = source.title() != null ? source.title()
: source.url() != null ? source.url()
: source.path() != null ? source.path()
: source.id();
System.out.printf("\"%s\" — %s%n", quoted, label);
}
}
});
```
Rust
```rust
use github_copilot_sdk::session_events::AssistantMessageData;
use std::collections::HashMap;
let mut events = session.subscribe();
while let Ok(event) = events.recv().await {
if event.event_type != "assistant.message" {
continue;
}
let Some(data) = event.typed_data::() else {
continue;
};
let Some(citations) = data.citations.as_ref() else {
continue;
};
let sources: HashMap<&str, _> = citations
.sources
.iter()
.map(|source| (source.id.as_str(), source))
.collect();
// Span offsets are UTF-16 code units, so index the UTF-16 view of the content.
let units: Vec = data.content.encode_utf16().collect();
for span in &citations.spans {
let quoted = String::from_utf16_lossy(
&units[span.start_index as usize..span.end_index as usize],
);
for reference in &span.references {
let Some(source) = sources.get(reference.source_id.as_str()) else {
continue;
};
let label = source
.title
.as_deref()
.or(source.url.as_deref())
.or(source.path.as_deref())
.unwrap_or(source.id.as_str());
println!("\"{quoted}\" — {label}");
}
}
}
```
## Citation payload reference
The `citations` object separates deduplicated sources from the spans that reference them, so a source cited five times appears once in `sources`.
| Type | Field | Description |
|---|---|---|
| `Citations` | `sources` | Deduplicated set of sources referenced by the citation spans |
| `Citations` | `spans` | Spans of generated text annotated with their supporting sources |
| `CitationSource` | `id` | Stable, turn-scoped identifier referenced by `CitationReference.sourceId` |
| `CitationSource` | `provider` | System that produced the citation: `anthropic`, `openai`, or `client` |
| `CitationSource` | `title?` | Human-readable title of the source |
| `CitationSource` | `url?` | URL of the source, when it is a web resource |
| `CitationSource` | `path?` | File path relative to the agent workspace root, when the source is a file |
| `CitationSpan` | `startIndex` | Start offset in the final message content (UTF-16 code units, zero-based, inclusive) |
| `CitationSpan` | `endIndex` | End offset in the final message content (UTF-16 code units, zero-based, exclusive) |
| `CitationSpan` | `references` | The sources that support this span |
| `CitationReference` | `sourceId` | Identifier of the `CitationSource` this reference points to |
| `CitationReference` | `citedText?` | Exact text from the source that supports the span, when the model provides it |
| `CitationReference` | `location?` | Location within the source that supports the span |
| `CitationReference` | `providerMetadata?` | Provider-native correlation data, passed through opaquely |
> [!TIP]
> Span offsets are measured in UTF-16 code units against the final `content` string. TypeScript, Java, and .NET strings are already UTF-16, so you can slice them directly. Python strings are indexed by Unicode code point and Go and Rust strings are UTF-8, so convert the content to UTF-16 code units before slicing, as the examples above do.
### Citation locations
`CitationReference.location` is a discriminated union keyed on `type`:
| Location type | Fields | Use |
|---|---|---|
| `char` | `startIndex`, `endIndex` | Character range within the source text |
| `page` | `startPage`, `endPage` | Page range within a paginated document |
| `block` | `startBlock`, `endBlock` | Content-block range within a structured document |
## Provide citable sources
Citations need source material the model can attribute. There are two ways to supply it.
### Attach documents to a message
When citations are enabled and the session uses an Anthropic provider, file attachments are sent as `document` blocks with citations turned on, so the model can cite passages from them.
```typescript
await session.sendAndWait({
prompt: "Summarize the attached PDF and cite the passages you used.",
attachments: [
{
type: "blob",
data: pdfBase64,
displayName: "quarterly-report.pdf",
mimeType: "application/pdf",
},
],
});
```
See [Image input](./image-input.md) for the attachment API and the `file` and `blob` attachment shapes.
### Return citable sources from a tool
Tool results carry an experimental `citableSources` array. Each entry supplies `content` that the model can cite, along with an `id` and optional `title`, `url`, and `path`. These sources are persisted with the tool result, so they survive session resume, and citations built from them are tagged with the `client` provider.
## Limitations
* Citations are experimental in every SDK and are not covered by compatibility guarantees.
* Coverage depends on the model provider. A session configured for a provider without citation support emits no `citations` payload.
* Citations are only present on the final `assistant.message` event, so streaming consumers cannot render them mid-response.
* Public code and IP-duplication citations are not part of this surface.
## Further reading
* [Streaming events](./streaming-events.md): subscribe to session events and narrow event types
* [Image input](./image-input.md): attach files and in-memory blobs to a message
* [Session persistence](./session-persistence.md): resume sessions and re-apply session options
* [Compatibility](../troubleshooting/compatibility.md): SDK and CLI feature matrix