wire

package
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 7, 2026 License: MIT Imports: 6 Imported by: 0

Documentation

Overview

anthropic_messages.go covers Anthropic's native Messages API (POST /v1/messages, SSE streaming). Distinct shape vs OpenAI/Chat: system is a top-level field (not a message), user/assistant alternate strictly, tool calls live as tool_use content blocks in assistant turns, and tool results live as tool_result blocks in user turns.

Auth is API-key only (x-api-key). The OAuth subscription path and its first-party-client identity headers and tool-name impersonation are deliberately not implemented. bee uses the public Anthropic API on its own terms. Ephemeral prompt-cache breakpoints are emitted on the system block and the last tool entry to reduce token cost on repeat prefixes.

Package wire — gemini.go covers Google's generativelanguage.googleapis.com v1beta schema for streamGenerateContent. The shape is meaningfully different from OpenAI's: messages are `contents[]`, each with typed `parts[]`, and system text goes in a top-level systemInstruction field.

Package wire translates internal agent types to/from provider wire formats.

openai.go covers the OpenAI Chat Completions schema, which OpenRouter, DeepSeek, Groq, Ollama, LM Studio, Together, and Fireworks all speak.

responses.go covers the Responses API schema used by the chatgpt.com subscription backend.

The shape is different from /chat/completions: messages are a flat "input" list of items (each role+content array of typed parts, plus function_call / function_call_output items), tools are bare {type,name,description,parameters} (no nested function envelope), and streaming events are typed (response.output_text.delta etc) rather than incremental message deltas.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func FormatToolArgs

func FormatToolArgs(input map[string]any) (string, error)

FormatToolArgs renders tool input back to a JSON string for assistant replay.

func SanitizeToolName

func SanitizeToolName(raw string) string

SanitizeToolName extracts a clean identifier from a possibly-noisy tool name. Some models inject markup or extra fields into function.name, e.g. `"read path=\"/x\"</|DSML|parameter"`. Take the leading identifier run after trimming quotes/markup. Returns "" if nothing identifier-like is found (caller should surface a parse error).

func StripMarkupBytes

func StripMarkupBytes(b []byte) []byte

StripMarkupBytes removes DSML / stray closing tags from a raw byte slice before JSON parsing. preserves length-on-success guarantees: nil in → nil out, empty in → empty out.

func StripMarkupInValues

func StripMarkupInValues(m map[string]any)

StripMarkupInValues walks string values in a parsed args map and strips model markup tokens. Handles nested maps and slices. Mutates in place.

Types

type AnthropicCacheCtl

type AnthropicCacheCtl struct {
	Type string `json:"type"`
}

AnthropicCacheCtl marks a block as a prompt-cache breakpoint.

type AnthropicContentBlockStart

type AnthropicContentBlockStart struct {
	Type  string         `json:"type"`
	ID    string         `json:"id,omitempty"`
	Name  string         `json:"name,omitempty"`
	Input map[string]any `json:"input,omitempty"`
}

AnthropicContentBlockStart is the body of `content_block_start` events. For tool_use blocks we get name + id up front (input is empty, filled by input_json_delta).

type AnthropicContentPart

type AnthropicContentPart struct {
	Type string `json:"type"`
	// text
	Text string `json:"text,omitempty"`
	// image
	Source *AnthropicImageSource `json:"source,omitempty"`
	// tool_use
	ID    string         `json:"id,omitempty"`
	Name  string         `json:"name,omitempty"`
	Input map[string]any `json:"input,omitempty"`
	// tool_result
	ToolUseID string                 `json:"tool_use_id,omitempty"`
	IsError   bool                   `json:"is_error,omitempty"`
	Content   []AnthropicContentPart `json:"content,omitempty"`
	// CacheControl marks this block as a prompt-cache breakpoint. Placed on the
	// last block of recent turns so the conversation prefix caches across turns.
	CacheControl *AnthropicCacheCtl `json:"cache_control,omitempty"`
}

AnthropicContentPart is one block inside a message: text | image | tool_use | tool_result.

type AnthropicDeltaPayload

type AnthropicDeltaPayload struct {
	Type        string `json:"type,omitempty"`
	Text        string `json:"text,omitempty"`
	PartialJSON string `json:"partial_json,omitempty"`
	Thinking    string `json:"thinking,omitempty"`
	Signature   string `json:"signature,omitempty"`
	StopReason  string `json:"stop_reason,omitempty"`
}

AnthropicDeltaPayload covers both content_block_delta (text_delta / input_json_delta / thinking_delta / signature_delta) and message_delta (stop_reason).

type AnthropicImageSource

type AnthropicImageSource struct {
	Type      string `json:"type"`
	MediaType string `json:"media_type"`
	Data      string `json:"data"`
}

AnthropicImageSource is the base64-payload image envelope.

type AnthropicMessage

type AnthropicMessage struct {
	Role    string                 `json:"role"`
	Content []AnthropicContentPart `json:"content"`
}

AnthropicMessage is one user/assistant turn. Content is always the typed array form (string shorthand is omitted to keep tool_use round-trips clean).

type AnthropicMessagesRequest

type AnthropicMessagesRequest struct {
	Model     string             `json:"model"`
	MaxTokens int                `json:"max_tokens"`
	Messages  []AnthropicMessage `json:"messages"`
	// System accepts either a string or an array of {type:text,text}. We use
	// the array form so future caching/multi-block work doesn't require a
	// schema change.
	System      []AnthropicSysBlock `json:"system,omitempty"`
	Tools       []AnthropicTool     `json:"tools,omitempty"`
	Temperature *float64            `json:"temperature,omitempty"`
	Stream      bool                `json:"stream,omitempty"`
	Thinking    *AnthropicThinking  `json:"thinking,omitempty"`
}

AnthropicMessagesRequest is the request body for POST /v1/messages.

func BuildAnthropicMessagesRequest

func BuildAnthropicMessagesRequest(model, system string, messages []types.Message, tools []ToolAdvert, maxTokens int, temperature float64, stream bool, thinkingBudget int) AnthropicMessagesRequest

BuildAnthropicMessagesRequest converts the internal Request shape into the Messages-API body. API-key only — no identity header injection, no tool renaming, no ephemeral caching.

type AnthropicStreamEvent

type AnthropicStreamEvent struct {
	Type string `json:"type"`

	Index int `json:"index,omitempty"`

	Delta *AnthropicDeltaPayload `json:"delta,omitempty"`

	ContentBlock *AnthropicContentBlockStart `json:"content_block,omitempty"`

	Message *AnthropicStreamMessage `json:"message,omitempty"`

	Usage *AnthropicUsage `json:"usage,omitempty"`
}

AnthropicStreamEvent is the parsed SSE event envelope. Field set depends on Type: text_delta carries Delta; input_json_delta carries PartialJSON; message_delta carries StopReason + Usage; content_block_start announces a new block (text or tool_use); message_start carries initial usage.

func ParseAnthropicEvent

func ParseAnthropicEvent(data []byte) (*AnthropicStreamEvent, error)

ParseAnthropicEvent parses one SSE `data:` line payload. Returns nil for events with no useful fields (ping / unknown types) so callers can skip.

type AnthropicStreamMessage

type AnthropicStreamMessage struct {
	ID           string          `json:"id,omitempty"`
	Model        string          `json:"model,omitempty"`
	Role         string          `json:"role,omitempty"`
	StopReason   string          `json:"stop_reason,omitempty"`
	StopSequence string          `json:"stop_sequence,omitempty"`
	Usage        *AnthropicUsage `json:"usage,omitempty"`
}

AnthropicStreamMessage is the body of `message_start` (carries initial usage with cache stats) and `message_stop` (sometimes empty).

type AnthropicSysBlock

type AnthropicSysBlock struct {
	Type         string             `json:"type"`
	Text         string             `json:"text"`
	CacheControl *AnthropicCacheCtl `json:"cache_control,omitempty"`
}

AnthropicSysBlock is one entry of the top-level system array.

type AnthropicThinking

type AnthropicThinking struct {
	Type         string `json:"type"`
	BudgetTokens int    `json:"budget_tokens,omitempty"`
}

AnthropicThinking is the extended-thinking knob. type=enabled with budget_tokens for older thinking models; type=adaptive (no budget) for Opus 4.6+ / Sonnet 4.6+ where the model picks.

type AnthropicTool

type AnthropicTool struct {
	Name         string             `json:"name"`
	Description  string             `json:"description,omitempty"`
	InputSchema  map[string]any     `json:"input_schema"`
	CacheControl *AnthropicCacheCtl `json:"cache_control,omitempty"`
}

AnthropicTool advertises a function tool.

type AnthropicUsage

type AnthropicUsage struct {
	InputTokens              int `json:"input_tokens"`
	OutputTokens             int `json:"output_tokens"`
	CacheCreationInputTokens int `json:"cache_creation_input_tokens,omitempty"`
	CacheReadInputTokens     int `json:"cache_read_input_tokens,omitempty"`
}

AnthropicUsage is the token-accounting block.

type ChatFunction

type ChatFunction struct {
	Name        string         `json:"name"`
	Description string         `json:"description"`
	Parameters  map[string]any `json:"parameters"`
}

ChatFunction is the function-shaped half of a tool spec.

type ChatMessage

type ChatMessage struct {
	Role       string     `json:"role"`
	Content    any        `json:"content,omitempty"`
	ToolCalls  []ToolCall `json:"tool_calls,omitempty"`
	ToolCallID string     `json:"tool_call_id,omitempty"`
	Name       string     `json:"name,omitempty"`
}

ChatMessage is one role-tagged message in the OpenAI schema.

Content is `any` so we can emit either a plain string (text-only) or an array of typed parts ({"type":"text",...},{"type":"image_url",...}) for vision-capable user messages.

type ChatRequest

type ChatRequest struct {
	Model       string        `json:"model"`
	Messages    []ChatMessage `json:"messages"`
	Tools       []ChatTool    `json:"tools,omitempty"`
	MaxTokens   int           `json:"max_tokens,omitempty"`
	Temperature *float64      `json:"temperature,omitempty"`
	TopP        *float64      `json:"top_p,omitempty"`
	Stop        []string      `json:"stop,omitempty"`
	Stream      bool          `json:"stream,omitempty"`
	// StreamOptions opts in to per-stream extras. Required (include_usage=true)
	// for OpenAI/OpenRouter/DeepSeek to emit token counts on the final SSE
	// chunk; otherwise the stream finishes without a Usage event.
	StreamOptions *StreamOptions `json:"stream_options,omitempty"`
	// ReasoningEffort is OpenAI's o-series extended-reasoning knob. Values:
	// "low" | "medium" | "high". Omitted when empty.
	ReasoningEffort string `json:"reasoning_effort,omitempty"`
	// ChatTemplateKwargs is an MLX/vllm extension to OpenAI's chat completions
	// body — kwargs forwarded into the model's chat template at apply time.
	// Used to flip Qwen3 / Hermes template switches like `enable_thinking=false`
	// or `tools=true` that flip the model out of prose-summary mode into the
	// `<tool_call>` envelope shape bee expects. Omitted when empty.
	ChatTemplateKwargs map[string]any `json:"chat_template_kwargs,omitempty"`
	// Usage opts in to provider-reported actual spend on the final usage block.
	// Only routed aggregators understand it; strict OpenAI-style endpoints
	// reject unknown body fields, so it is gated (set only when the provider
	// advertises the capability) rather than always emitted.
	Usage *UsageRequest `json:"usage,omitempty"`
}

ChatRequest is the OpenAI chat completions body.

func BuildRequest

func BuildRequest(model string, system string, messages []types.Message, tools []ToolAdvert, maxTokens int, temperature, topP float64, stop []string, stream bool) ChatRequest

BuildRequest converts internal Request shape to OpenAI wire form.

type ChatTool

type ChatTool struct {
	Type     string       `json:"type"`
	Function ChatFunction `json:"function"`
}

ChatTool advertises a tool to the model.

type FinalizedCall

type FinalizedCall struct {
	ID         string
	Name       string
	Input      map[string]any
	RawArgs    string
	ParseError string
}

FinalizedCall is the assembled tool call after stream end. RawArgs holds the un-decoded argument string when Input is empty due to a parse failure caller can surface to the model instead of executing with empty args. ParseError is the json error message (non-empty only on failure).

type FunctionCall

type FunctionCall struct {
	Name      string `json:"name"`
	Arguments string `json:"arguments"`
}

FunctionCall holds the function name + JSON-string args.

type GeminiCandidate

type GeminiCandidate struct {
	Content      GeminiContent `json:"content"`
	FinishReason string        `json:"finishReason,omitempty"`
}

GeminiCandidate is one alternative response within a chunk.

type GeminiContent

type GeminiContent struct {
	Role  string       `json:"role,omitempty"`
	Parts []GeminiPart `json:"parts"`
}

GeminiContent is one entry in the contents[] array. Role is "user" or "model"; Gemini does not have a separate "tool" role — tool results are user-role messages carrying functionResponse parts.

type GeminiFunctionCall

type GeminiFunctionCall struct {
	Name string         `json:"name"`
	Args map[string]any `json:"args,omitempty"`
}

GeminiFunctionCall is the model's request to invoke a function.

type GeminiFunctionDecl

type GeminiFunctionDecl struct {
	Name        string         `json:"name"`
	Description string         `json:"description"`
	Parameters  map[string]any `json:"parameters,omitempty"`
}

GeminiFunctionDecl advertises one tool with a JSON-schema-shaped Parameters.

type GeminiFunctionResponse

type GeminiFunctionResponse struct {
	Name     string         `json:"name"`
	Response map[string]any `json:"response"`
}

GeminiFunctionResponse is the caller's reply carrying the function output.

type GeminiInlineData

type GeminiInlineData struct {
	MimeType string `json:"mime_type"`
	Data     string `json:"data"`
}

GeminiInlineData carries a base64-encoded blob (image, audio, etc.).

type GeminiPart

type GeminiPart struct {
	Text             string                  `json:"text,omitempty"`
	InlineData       *GeminiInlineData       `json:"inline_data,omitempty"`
	FunctionCall     *GeminiFunctionCall     `json:"functionCall,omitempty"`
	FunctionResponse *GeminiFunctionResponse `json:"functionResponse,omitempty"`
}

GeminiPart is one element of a content's parts[]. Exactly one of Text / InlineData / FunctionCall / FunctionResponse is set per part.

type GeminiRequest

type GeminiRequest struct {
	Contents          []GeminiContent `json:"contents"`
	SystemInstruction *GeminiContent  `json:"systemInstruction,omitempty"`
	Tools             []GeminiTool    `json:"tools,omitempty"`
	GenerationConfig  map[string]any  `json:"generationConfig,omitempty"`
}

GeminiRequest is the POST body for :streamGenerateContent.

type GeminiStreamChunk

type GeminiStreamChunk struct {
	Candidates    []GeminiCandidate `json:"candidates"`
	UsageMetadata *GeminiUsage      `json:"usageMetadata,omitempty"`
}

GeminiStreamChunk is one SSE-delimited JSON object in the stream response.

type GeminiTool

type GeminiTool struct {
	FunctionDeclarations []GeminiFunctionDecl `json:"function_declarations"`
}

GeminiTool wraps a set of function declarations exposed to the model.

type GeminiUsage

type GeminiUsage struct {
	PromptTokenCount     int `json:"promptTokenCount"`
	CandidatesTokenCount int `json:"candidatesTokenCount"`
}

GeminiUsage captures Gemini's token accounting.

type ResponsesContent

type ResponsesContent struct {
	Type     string `json:"type"`
	Text     string `json:"text,omitempty"`
	ImageURL string `json:"image_url,omitempty"`
}

ResponsesContent is one typed content part. Types: "input_text", "output_text", "input_image".

type ResponsesErrorPayload

type ResponsesErrorPayload struct {
	Code    string `json:"code"`
	Message string `json:"message"`
}

ResponsesErrorPayload is the error envelope on failed responses.

type ResponsesEvent

type ResponsesEvent struct {
	Type        string               `json:"type"`
	Response    *ResponsesEventBody  `json:"response,omitempty"`
	Item        *ResponsesOutputItem `json:"item,omitempty"`
	Delta       string               `json:"delta,omitempty"`
	OutputIndex *int                 `json:"output_index,omitempty"`
	ItemID      string               `json:"item_id,omitempty"`
	CallID      string               `json:"call_id,omitempty"`
	// Arguments-done event includes the full arguments string.
	Arguments string `json:"arguments,omitempty"`
}

ResponsesEvent is one parsed SSE event from a /responses stream. Type names follow the Responses API spec: response.created, response.output_text.delta, response.function_call_arguments.delta, response.output_item.added, response.output_item.done, response.completed, response.failed.

func ParseResponsesEvent

func ParseResponsesEvent(data []byte) (*ResponsesEvent, error)

ParseResponsesEvent decodes one SSE `data:` payload.

type ResponsesEventBody

type ResponsesEventBody struct {
	ID     string                 `json:"id"`
	Status string                 `json:"status"`
	Usage  *ResponsesUsage        `json:"usage"`
	Output []ResponsesOutputItem  `json:"output"`
	Error  *ResponsesErrorPayload `json:"error"`
}

ResponsesEventBody carries the response-level payload on created/completed/failed.

type ResponsesItem

type ResponsesItem struct {
	Type      string             `json:"type,omitempty"`
	Role      string             `json:"role,omitempty"`
	Content   []ResponsesContent `json:"content,omitempty"`
	CallID    string             `json:"call_id,omitempty"`
	Name      string             `json:"name,omitempty"`
	Arguments string             `json:"arguments,omitempty"`
	Output    string             `json:"output,omitempty"`
	Status    string             `json:"status,omitempty"`
}

ResponsesItem is one entry in the flat input list. Type is one of: "message" (role+content), "function_call", "function_call_output". For "message" items, Content is an array of typed parts. For function items, CallID/Name/Arguments/Output carry the payload directly.

type ResponsesOutputItem

type ResponsesOutputItem struct {
	ID        string             `json:"id"`
	Type      string             `json:"type"`
	Status    string             `json:"status"`
	Role      string             `json:"role,omitempty"`
	Content   []ResponsesContent `json:"content,omitempty"`
	CallID    string             `json:"call_id,omitempty"`
	Name      string             `json:"name,omitempty"`
	Arguments string             `json:"arguments,omitempty"`
}

ResponsesOutputItem is one entry in the final output list. Type is "message" or "function_call". For function_call items the call id, name, and arguments string are flat fields.

type ResponsesReason

type ResponsesReason struct {
	Effort string `json:"effort,omitempty"`
}

ResponsesReason carries the reasoning-effort hint for reasoning models.

type ResponsesRequest

type ResponsesRequest struct {
	Model        string           `json:"model"`
	Instructions string           `json:"instructions,omitempty"`
	Input        []ResponsesItem  `json:"input"`
	Tools        []ResponsesTool  `json:"tools,omitempty"`
	Stream       bool             `json:"stream,omitempty"`
	MaxOutput    int              `json:"max_output_tokens,omitempty"`
	Temperature  *float64         `json:"temperature,omitempty"`
	Reasoning    *ResponsesReason `json:"reasoning,omitempty"`
	// Store=false keeps the call ephemeral on the server side. The
	// backend appears to default to false but we set it explicitly.
	Store *bool `json:"store,omitempty"`
}

ResponsesRequest is the request body for POST /responses.

func BuildResponsesRequest

func BuildResponsesRequest(model, system string, messages []types.Message, tools []ToolAdvert, maxTokens int, temperature float64, stream bool, reasoningEffort string) ResponsesRequest

BuildResponsesRequest converts internal Request shape to the Responses API body. System prompt maps to instructions (not a message). Tool calls and tool results become flat function_call / function_call_output items.

type ResponsesTool

type ResponsesTool struct {
	Type        string         `json:"type"`
	Name        string         `json:"name"`
	Description string         `json:"description,omitempty"`
	Parameters  map[string]any `json:"parameters,omitempty"`
}

ResponsesTool advertises a function tool. Flat shape (no nested function envelope like chat/completions uses).

type ResponsesUsage

type ResponsesUsage struct {
	InputTokens  int `json:"input_tokens"`
	OutputTokens int `json:"output_tokens"`
	TotalTokens  int `json:"total_tokens"`
}

ResponsesUsage matches the usage block on the final event.

type StreamChoice

type StreamChoice struct {
	Index        int         `json:"index"`
	Delta        StreamDelta `json:"delta"`
	FinishReason *string     `json:"finish_reason"`
}

StreamChoice is one alternative completion within a chunk. OpenAI commonly streams a single choice.

type StreamChunk

type StreamChunk struct {
	ID      string         `json:"id"`
	Choices []StreamChoice `json:"choices"`
	Usage   *StreamUsage   `json:"usage"`
}

StreamChunk is one decoded SSE data: payload from an OpenAI chat stream.

func ParseChunk

func ParseChunk(data []byte) (*StreamChunk, bool, error)

ParseChunk decodes one SSE `data:` JSON line. The literal "[DONE]" marker returns (nil, true, nil) — caller should treat that as the terminator.

type StreamDelta

type StreamDelta struct {
	Role             string           `json:"role,omitempty"`
	Content          string           `json:"content,omitempty"`
	ReasoningContent string           `json:"reasoning_content,omitempty"`
	Reasoning        string           `json:"reasoning,omitempty"`
	ToolCalls        []StreamToolCall `json:"tool_calls,omitempty"`
}

StreamDelta carries the incremental fields for this chunk.

ReasoningContent is DeepSeek-reasoner's chain-of-thought field. Reasoning is OpenAI o-series' equivalent (some compat servers expose it on chat- completions deltas too). Both are agent-facing — never echoed back as assistant content; rendered separately in a dimmed style.

type StreamFunctionDelta

type StreamFunctionDelta struct {
	Name      string `json:"name,omitempty"`
	Arguments string `json:"arguments,omitempty"`
}

StreamFunctionDelta is the incremental function payload.

type StreamOptions

type StreamOptions struct {
	IncludeUsage bool `json:"include_usage,omitempty"`
}

StreamOptions matches OpenAI's stream_options envelope. Only include_usage is used today.

type StreamToolCall

type StreamToolCall struct {
	Index    int                 `json:"index"`
	ID       string              `json:"id,omitempty"`
	Type     string              `json:"type,omitempty"`
	Function StreamFunctionDelta `json:"function"`
}

StreamToolCall is the streamed shape of a tool invocation. Function name + args come in pieces over multiple chunks; the index tells us which slot.

type StreamUsage

type StreamUsage struct {
	PromptTokens        int     `json:"prompt_tokens"`
	CompletionTokens    int     `json:"completion_tokens"`
	TotalTokens         int     `json:"total_tokens"`
	Cost                float64 `json:"cost"`
	PromptTokensDetails *struct {
		CachedTokens int `json:"cached_tokens"`
	} `json:"prompt_tokens_details,omitempty"`
}

StreamUsage is the optional usage block, often only on the final chunk.

Cost is the actual spend in account credits, returned by routed aggregators when the request opts in (see ChatRequest.Usage). Absent on strict endpoints, so it stays zero there. PromptTokensDetails carries a cached-prompt count when reported.

type ToolAdvert

type ToolAdvert struct {
	Name        string
	Description string
	Schema      map[string]any
}

ToolAdvert mirrors llm.ToolSpec without the import cycle.

type ToolCall

type ToolCall struct {
	ID       string       `json:"id"`
	Type     string       `json:"type"`
	Function FunctionCall `json:"function"`
}

ToolCall is OpenAI's tool_use envelope.

type ToolCallAccumulator

type ToolCallAccumulator struct {
	// contains filtered or unexported fields
}

ToolCallAccumulator threads partial tool-call deltas across chunks. Each streamed tool call has a stable Index; ID/Name arrive first, Arguments accumulate as a JSON string fragment.

func NewToolCallAccumulator

func NewToolCallAccumulator() *ToolCallAccumulator

NewToolCallAccumulator builds a fresh accumulator.

func (*ToolCallAccumulator) Apply

func (a *ToolCallAccumulator) Apply(deltas []StreamToolCall)

Apply merges a chunk's tool-call deltas into the accumulator.

func (*ToolCallAccumulator) Finalize

func (a *ToolCallAccumulator) Finalize() ([]FinalizedCall, error)

Finalize returns the assembled tool calls in index order. Empty Args are treated as `{}`. Malformed JSON is repaired before failing so a stray trailing brace or unbalanced delta from a noisy model doesn't kill the whole turn — see repairToolArgs. When repair also fails, the call is returned with empty Input + RawArgs/ParseError populated so the caller can surface a structured error to the model rather than silently mis- executing.

type UsageRequest added in v0.1.1

type UsageRequest struct {
	Include bool `json:"include"`
}

UsageRequest asks the service to include real cost in the usage block.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL