Commit 4f0bc70
authored
Normalize completion responses at the provider boundary and erase the model type at agent construction (#2257)
* refactor(completion)!: normalize completion responses at the provider boundary
Remove the response and construction associated types from `CompletionModel`
so the trait describes only what a model *does*, and make the ordinary
completion and streaming types concrete.
`CompletionResponse` loses `raw_response` and gains the metadata callers
actually reached into it for: a normalized `finish_reason`, the stable
`provider` descriptor name, the provider-reported `model`, and `message_id`.
Provider-native payloads stay reachable through each model's inherent
`raw_completion`/`raw_stream`, which share one request, transport, parser,
telemetry path, and error path with their normalized counterparts.
Three details are load-bearing:
- The `Stop` -> `ToolCalls` reconciliation lives in exactly one place
(`FinishReason::reconcile_with_output`) and is applied by both the unary
setters and `normalize_stream`. Several OpenAI-compatible gateways report a
bare `stop` on a tool-calling turn, so a caller branching on `ToolCalls`
would otherwise miss the call on the unary path while the streaming path
caught it.
- Conversions over a wire type shared by many providers take the descriptor
name as an input rather than hardcoding one, so a shared shape cannot
mislabel the provider that used it.
- `StreamingCompletionResponse` records its provider when the stream is
opened, so a stream that errors or truncates before its terminal record
still reports one.
Construction moves to the required `CompletionClient::completion_model`;
`ProviderCapabilities` replaces `composes_native_output_with_tools` with plain
data a runtime can snapshot. Request construction is unchanged — no cassette
fixture differs.
* test: migrate the integration suite to the normalized completion boundary
Assertions that read normalized metadata now read the fields that replaced
`raw_response` (`model`, `finish_reason`, `provider`, `message_id`).
Raw-vs-normalized parity tests keep their intent by taking the provider's own
response from the model's inherent `raw_completion` and normalizing that same
value, rather than issuing a second request — each cassette holds exactly one
interaction and fails on both unmatched extra requests and unconsumed ones.
Tests that read genuinely provider-specific stream payloads (reasoning
metadata, local generation counters) move to `raw_stream`, which is what that
escape hatch is for.
No cassette fixture changed.
* docs(chatgpt): explain why the raw response can lack assistant output
/responses answers with an SSE body even for a non-streaming request, so the
value raw_completion returns is reassembled from the terminal event — and that
event sometimes carries an empty output, with the content living only in the
preceding events. Point callers who need full provider fidelity at raw_stream
rather than letting them discover this from an empty field.
* fix(completion): address independent review of the normalization boundary
An adversarial review of the full diff raised 38 findings; 24 survived
refutation. The load-bearing ones:
Normalization was stated as `TryFrom<(&str, Response)>`, which the orphan rule
makes unimplementable outside rig-core — a tuple is not a local type, so no
other crate could satisfy the bound. That silently blocked external provider
extensions, which are explicitly in scope. It is now a
`NormalizeCompletionResponse` trait, implementable on any provider's own
response type anywhere.
`normalize_stream` counted tool-call *deltas* toward the Stop -> ToolCalls
upgrade while the unary path counts only completed calls, so a stream whose
tool call never assembled reported `ToolCalls` over a choice containing none —
the exact divergence the single-implementation rule is supposed to prevent.
Both paths now count only completed calls.
`send_compatible_streaming_request` is public and stamped a hardcoded "openai"
onto every stream it produced, reintroducing in a public helper the same
placeholder hazard this change removes elsewhere. It takes the descriptor name
now.
Vertex AI built `FinishReason::Other` by `Debug`-formatting the SDK enum, which
turned MALFORMED_FUNCTION_CALL into MALFORMEDFUNCTIONCALL. It uses the SDK's
own `name()`.
Also reverted two behavior changes that had crept in and do not belong to a
normalization change: `message_id` was being fed response-scoped ids
(`chatcmpl-`, Gemini `responseId`) on five paths even though the agent replays
that field into assistant history, and Cohere's usage silently moved from
`billed_units` to `tokens`. Both are back to the pre-existing semantics.
Three confirmed findings are deliberately untouched because they are identical
at the merge base: anthropic's SSE parse-failure guard, bedrock's MaxTokens
early yield, and Z.AI's two descriptor names.
* fix(completion): correct two over-corrections from the review pass
Both were cases of acting on a finding's mechanism without first checking
whether it described a reachable harm. The test suite caught both.
`message_id` was stripped from five providers on the claim that a
response-scoped id would corrupt agent history, since the agent writes it into
`Message::Assistant { id }`. That data path is real, but every provider's
request conversion drops the id before it reaches the wire (see
providers/openai/completion/mod.rs, `id: None`), so nothing is replayed.
Stripping it discarded identifiers this change is meant to preserve. They are
restored, and the field now documents what it actually holds — providers differ
on whether it names the message or the response — and that it is not echoed
back.
Cohere's usage source had been changed to prefer `tokens`; reverting to
`billed_units` kept every existing number stable but left rig reporting zero
usage — the documented "no metrics" sentinel — for a response carrying `tokens`
and no `billed_units`. Neither was right. `billed_units` stays primary so no
caller's numbers move, with `tokens` as a fallback only where rig previously
reported nothing.
Gemini's Interactions API keeps its continuation handle reachable through
`raw_completion`, which is where the pre-refactor tests read it from
(`raw_response.id`); it is a `previous_interaction_id` handle, not an assistant
message, so the escape hatch is its proper home.
* fix(completion): split response-scoped IDs from replayable assistant-message IDs
CompletionResponse and StreamFinal gain a response_id field for identifiers
that name the response as a whole (OpenAI chat chatcmpl-*, Gemini responseId,
Cohere generation IDs, Gemini Interactions interaction IDs). message_id is now
reserved for identifiers the provider would recognize on a replayed assistant
message (Anthropic msg_*, OpenAI Responses output-message msg_*), which is what
agent history promotes into Message::Assistant's id. This stops chat response
IDs from being replayed to the Responses API as fabricated output-message IDs.
* fix(client): make the completion-model construction hook public
ConstructCompletionModel was pub(crate), but the blanket CompletionClient impl
over Client<Ext, H> bounds on it, and the orphan rule prevents a downstream
crate from implementing CompletionClient for the foreign Client<Ext, H> type.
Together that locked out-of-tree generic provider extensions out of
completion_model entirely. The hook is now public and documented as the
extension point, with a compile probe showing an external extension reaching
the blanket impl through public API only.
* fix(streaming): withhold terminal records from truncated streams
A synthetic FinalResponse was still emitted when a stream reached EOF without
the provider's genuine end-of-response event, and (on some paths) after a
yielded SSE parse error. Every provider stream now tracks whether the real
terminal signal arrived — Anthropic message_delta with stop_reason, OpenAI
[DONE]/finish_reason chunk, Responses response.completed, Cohere message-end,
Gemini finishReason chunk (REST, gRPC, Interactions), Copilot
response.completed — and emits the terminal record only then. Fully delivered
tool calls still flush on truncation; parse errors are surfaced without
aborting the stream, so a later genuine terminal can still complete it.
Regression tests cover EOF-truncation, parse-error-then-EOF, and
parse-error-then-terminal for Anthropic, Gemini REST, the shared OpenAI chat
path, and Cohere. The contract is documented on StreamFinal.
* fix(completion): align conversion inputs and Gemini mappers with the boundary contract
- send_compatible_streaming_request takes the provider name as impl
Into<String>, so runtime-named gateways can use the shared path.
- The Gemini REST finish-reason mapper now treats FINISH_REASON_UNSPECIFIED
as absent (None), matching the gRPC mapper's handling of the same wire
value instead of surfacing an Other("FINISH_REASON_UNSPECIFIED") that the
sibling transport would report as no reason.
- FinishReason documents the single policy for provider failure statuses that
arrive with parseable output.
* docs: cover the telemetry signature and terminal-emission changes in MIGRATING
* feat(agent): erase the model type at construction into ModelHandle
Erase the typed CompletionModel once, at AgentBuilder::new/ExtractorBuilder::new,
into an opaque, cloneable ModelHandle that is itself a CompletionModel with its
ProviderCapabilities captured by value at erasure. Agent, AgentBuilder (after
new()), AgentRunner, prompt/stream requests, and Extractor carry no model type
parameter. Port the ModelSelection hook event and ModelSelectionAction onto the
AgentHook stack (registration-order chaining, last selection wins, stop is
terminal), per-run using_model defaults, run-local extractor overrides, the
runtime_model_routing example, and the runtime_model_swapping suite.
* fix(agent): resolve completion-call hooks before model selection
Reorder the model-call boundary: completion-call hooks resolve first; only if
they proceed does ModelSelection fire, carrying the merged per-turn patch on a
new public request_patch field; request preparation then runs against the
selected model's captured ProviderCapabilities before the attempt is issued.
previous_model now advances immediately before invoking the selected model's
unary or streaming operation, so an issued attempt counts even when the
provider errors, while a completion-call stop, selection stop, or preparation
failure leaves it untouched. Document the ordering contract on both hook
events and add blocking/streaming parity tests covering the merged patch,
patch-driven routing, stop/preparation-failure suppression, and issued-attempt
tracking.
* style: apply rustfmt
* fix(completion): address independent review of the combined boundary
- The shared OpenAI chat-compatible stream no longer aborts on a chunk parse
error: the error is surfaced and consumption continues, matching the
Anthropic/Gemini parse-error policy, with the missing terminal signal still
suppressing the terminal record on truncation.
- The Responses API streaming terminal now carries the resp_* response ID
(accumulator, Copilot inline path), matching the unary path.
- MIGRATING covers the agent-side model-type-parameter removal and runtime
model swapping, and no longer claims an Agent's model is fixed.
* fix: address code-review findings on the normalized model-swapping PR
Correctness:
- Reject provider-stream EOF without a terminal record as truncation in
the streaming agent, before usage fallback, history commit, or tool
dispatch; add text-only and tool-call truncation tests.
- Carry response_id on OpenRouter unary, the ChatGPT empty-output
fallback, gemini-grpc unary, and vertexai unary responses.
- Add #[serde(default)] to DeepSeek Usage and to all Option fields of
CompletionResponse/StreamFinal.
- Route the mock model's stream through normalize_stream so tests see
the same Stop -> ToolCalls reconciliation as real providers.
Pre-existing contract violations:
- ollama: skip malformed NDJSON lines instead of aborting; use the
function name as the streamed tool-call id.
- bedrock: MAX_TOKENS ends as a genuine Length terminal; parse failures
warn and skip; Metadata always emits the terminal record.
- copilot: use Usage::to_normalized (saturating, real completion and
reasoning tokens) on the unary path and in telemetry.
- vertexai: map cached_content_token_count instead of hardcoding zero.
Coverage and tooling:
- Nine new streaming tests: core mid-stream-error/truncation, gemini
REST transport error, cohere malformed frame, OpenAI Responses
truncated EOF, anthropic transport error, xai/gemini-interactions/
ollama truncation.
- rig-agent dev-dependencies enable rig-core test-utils so plain
`cargo test -p rig-agent` builds.
- Candle real-model test exercises the normalized completion()/stream()
surfaces.
- Raise malformed-SSE-frame logs to warn.
Docs: fix the stale gemini example narrative, add StreamFinal
response_id and the normalize-bridge pointer to MIGRATING.md, remove
dangling/duplicated doc blocks, re-section changelogs, add
ModelSelection::new for hook unit tests, document ModelHandle's
per-call clone, and make the previous_model comment precise.
* fix: address code-review findings on the normalized model-swapping PR
CI blockers:
- Rerecord three ollama cassettes (local qwen3:4b) whose requests still
expected the old empty streamed tool-call ids; tool results now carry
the function name as tool_name, matching the unary path.
- Convert bedrock's one-arm MessageStop match to an if, fixing
clippy::single_match under --all-features -D warnings; reflow the
kill-token example's doc list for doc_overindented_list_items.
Parity:
- Treat OpenAI Responses `response.incomplete` as a genuine terminal on
the SSE and WebSocket paths (partial output, usage, and a
map_finish_reason-mapped finish reason), matching the unary path;
`response.failed` remains an error. Pinned tests rewritten and a
WebSocket incomplete-terminal test added.
Hardening:
- ModelHandle retains the erased model in one shared Arc and clones only
the Arc per attempt, so interior-mutable model state persists across
attempts; handle docs state the retention guarantee.
- MIGRATING documents the agent-level truncation error for streams that
end without a terminal record.
- Remove a stray blank line in StreamingCompletionResponse.
* refactor: erase models through an object-safe trait and drop the Clone supertrait
Erasure (rig-agent):
- Replace ModelHandle's boxed closure pair with a private object-safe
ErasedModel trait and a blanket impl over CompletionModel + 'static —
the tower::BoxService shape: one vtable, borrowed futures delegating
to the RPITIT methods, no per-attempt model handling beyond Box::pin.
- The WasmCompat supertraits carry the wasm cfg fork (as ErasedTool
does), removing all four cfg-forked callback type aliases.
- Store capabilities snapshot, label, and the unsized erased model in a
single Arc<ModelDriver<dyn ErasedModel>> allocation.
- Pin the shared-instance invariant structurally: a clone-counting probe
model asserts zero model clones across erasure and repeated
completion/stream attempts.
Trait relaxation (rig-core, breaking for generic code):
- CompletionModel no longer requires Clone; the trait demands only
async service behavior, tower::Service-style. completion_request
gates on `where Self: Sized + Clone`.
- build/send/stream funnel through one private into_model_and_request
destructuring, removing the terminal methods' model clones while
keeping build()'s public behavior identical.
- tool_choice_modes in the conformance suite bounds + Clone explicitly,
matching its siblings; every other call site is a concrete Clone type
and compiles unchanged.
- MIGRATING and the rig-core changelog document the relaxation.
Verified: workspace check --all-features, wasm32-unknown-unknown check
for rig-core/rig-agent/rig, fmt, clippy --all-features -D warnings,
rig-agent suite (incl. runtime_model_swapping and conformance), and
rig-core --features test-utils all green. No cassettes touched.
* fix: address the full third-round review of the normalization PR
Correctness (P1):
- openai responses websocket: streamed item events now feed the same
RawChoiceAccumulator the SSE path uses, so a response.incomplete
terminal with an empty output array keeps the partial output that
streamed before it, instead of failing with "Response contained no
message or tool call".
- copilot responses streaming: response.incomplete is a genuine
terminal (partial content, Length finish reason), no longer a vague
stream error with no terminal record; only response.failed errors.
- bedrock streaming: in-flight tool calls are keyed by
content_block_index and flushed at ContentBlockStop, so parallel tool
calls all arrive (previously a single slot kept only the last), text
after a closed tool block is delivered, and malformed tool-call JSON
surfaces an Err item instead of silently emitting a ToolCalls
terminal with zero calls.
Streaming contract (P2):
- corrupt frames (invalid JSON) yield an Err item and the stream keeps
consuming — openai responses, copilot chat, cohere, ollama; a later
genuine terminal still completes the stream. Valid-JSON events with
unrecognized shapes are skipped for forward compatibility with new
provider event types (xAI's extra Responses events exercised this).
- errored streams flush fully-delivered tool calls before ending
(shared openai-compatible path and the copilot responses route),
matching the clean-EOF truncation path.
- a bare [DONE] after only unparseable frames no longer fabricates a
zero-usage terminal record.
- gemini REST/Interactions wire enums gained untagged Unknown(String)
catch-alls so unknown values are preserved verbatim as
FinishReason::Other, matching the gRPC mapper, instead of failing the
whole payload.
- cohere message-end without a delta still emits the terminal record.
- the StreamFinal emission contract now documents the three failure
shapes (transport error / recoverable parse error / truncation) and
that consumers must drain to None.
API (breaking, documented in MIGRATING):
- CompletionResponse::finish_reason is a private field with a getter,
so the Stop -> ToolCalls reconciliation cannot be bypassed by direct
assignment.
- CompletionModel is implemented for Arc<M> by forwarding, making the
"wrap it in an Arc" guidance real through the generic APIs.
- identifier/model setters on CompletionResponse and StreamFinal treat
empty strings as absent, centralizing a rule previously duplicated at
provider call sites.
Tests: parallel-tool-call and text-after-tool bedrock coverage, WS
incomplete-with-empty-output regression, copilot incomplete terminal,
malformed-frame surfacing per provider, unknown-wire-value round trips,
a NonCloneModel compile-time traits() probe, and the clone-probe
assertions split per path. The misleading "did not yield
response.completed" error now names both terminal events, and the stale
Clone doc line on CompletionClient is rewritten.
* fix: address the reverified fourth-round review findings
Correctness:
- streaming aggregation: a full reasoning block supersedes its
accumulated deltas (correlated by reasoning item id; distinct ids
still append), so the aggregated choice and agent history no longer
carry the same reasoning twice.
- openai chat default profile: unparseable frames surface as Err items
instead of Ok(None), so the saw_any_valid_frame gate can no longer
produce a completely silent empty stream on the plain OpenAI path.
- bedrock: the MessageStop straggler flush is gated on a ToolUse stop
reason — a tool block truncated by MaxTokens is dropped with a
warning rather than fabricated into a {}-args call or a spurious
error item.
- openai responses websocket/SSE replay: terminal-body message text is
merged per content kind when it never streamed as deltas (the
reasoning-deltas-plus-body-text sequence previously dropped the
answer); the untested incomplete-with-body-output quadrant is now
pinned.
Streaming contract:
- one flush ordering on every path (shared compat, openai responses
SSE, copilot responses): fully-delivered tool calls are yielded
before the terminal Err, nothing follows the error, no terminal
record — previously the three paths had three behaviors and the
flush was invisible to first-Err consumers. Documented on the
StreamFinal contract table.
- parse policy discriminates on the known event type at every site
(responses, copilot chat, cohere, openai chat default): a known
event with a schema defect surfaces an Err; unknown event types are
skipped for forward compatibility. The ChatGPT buffered SSE fallback
fails the completion on corrupt known frames instead of returning
silently partial content. xai's reasoning_summary_text.done (which
carries text, not delta) now decodes via a serde alias.
- CompletionResponse and StreamFinal deserialize through wire-shape
mirrors that funnel the validating setters, so finish-reason
reconciliation and empty-string filtering also hold for persisted
values; serialization is unchanged and round-trips are identity.
- gemini interactions InteractionStatus::is_terminal enumerates the
known in-flight states (semantic-kernel-style allowlist), so an
unknown status reads as terminal.
Cleanups: redundant per-provider empty-string filters removed
(deepseek, openai, copilot); ollama post-terminal behavior pinned by
test.
* fix: address the reverified fifth-round review findings
Two regressions from the previous round's known-type parse policy,
both the same root cause — the discriminator lists were broader than
the typed wire models:
- Delta-less streamed choices (Azure prepends a prompt_filter_results
chunk to every stream when content filtering is enabled, its
default) parsed as recognizable-but-broken and surfaced a spurious
error item on every Azure request. The choice's delta is now
serde-defaulted (openai-compatible and copilot chat chunk models),
so the prelude parses as the no-op frame the reference SDKs treat it
as (vercel nullish + skip, langchain return-None, semantic-kernel
documented skip).
- Unmodeled Responses content_part shapes — the refusal and
reasoning_text parts real refusal/reasoning-text turns emit — failed
the typed decode: spurious error items on SSE, and a hard failure of
the whole ChatGPT buffered completion (the part frame precedes the
deliverable refusal deltas). ContentPartChunkPart gained an untagged
Unknown(Value) catch-all (the Output::Unknown shape); the content
still flows via response.refusal.delta.
Reasoning aggregation: the full-block supersede is now genuinely
id-correlated. The identity table is strict (matching ids or both
absent replace; an id on only one side appends instead of clobbering
an unrelated item), and when other output cleared the active index
(reasoning -> tool call -> completed block) a by-id fallback scan
replaces instead of duplicating. The supersede contract is documented
on StreamedAssistantContent::Reasoning and in MIGRATING: a full block
replaces accumulated deltas with the same id.
Also: the websocket/SSE terminal-body merge gates on any streamed text
(is_empty, not trim) so all-whitespace turns don't duplicate; gemini
interactions RequiresAction is terminal for a poll loop — it never
advances without submitted tool results — and documented as a
distinct resumable outcome (semantic-kernel's taxonomy).
Tests: Azure filter-prelude no-op, refusal content-part frames on the
live and buffered paths, interleaved reasoning supersede, id-less
block append, RequiresAction terminality.1 parent 6cfae6d commit 4f0bc70
131 files changed
Lines changed: 17160 additions & 5873 deletions
File tree
- crates
- rig-agent
- examples
- src
- agent
- prompt_request
- run
- integrations
- test_utils
- tests
- rig-bedrock
- examples
- src
- types
- rig-candle
- src
- model
- tests
- rig-core
- src
- client
- completion
- providers
- anthropic
- chatgpt
- cohere
- copilot
- gemini
- interactions_api
- internal
- mistral
- openai
- completion
- responses_api
- openrouter
- xai
- telemetry
- test_utils
- rig-gemini-grpc/src
- rig-vertexai/src
- types
- examples
- agent_autonomous/src
- agent_prompt_chaining/src
- agent_routing/src
- agent_stream_chat/src
- agent_with_memory_streaming/src
- candle_local/src
- candle_wasm_chat/src
- chain/src
- debate/src
- enum_dispatch/src
- gemini_default_api_recovery/src
- gemini_stream_kill_token_count/src
- multi_agent/src
- reasoning_loop/src
- tests
- cassettes/ollama
- agentic
- reasoning_tool_roundtrip
- streaming_tools
- common
- core
- providers
- anthropic/cassette
- bedrock/cassette
- chatgpt/cassette
- copilot
- deepseek
- gemini/cassette
- groq
- llamacpp
- mistralrs/cassette
- mistral
- openai/cassette
- openrouter/cassette
- xai
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | | - | |
4 | | - | |
5 | | - | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
6 | 7 | | |
7 | 8 | | |
8 | 9 | | |
| |||
11 | 12 | | |
12 | 13 | | |
13 | 14 | | |
| 15 | + | |
14 | 16 | | |
15 | 17 | | |
16 | 18 | | |
| |||
274 | 276 | | |
275 | 277 | | |
276 | 278 | | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
| 342 | + | |
| 343 | + | |
| 344 | + | |
| 345 | + | |
| 346 | + | |
| 347 | + | |
| 348 | + | |
| 349 | + | |
| 350 | + | |
| 351 | + | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
| 355 | + | |
| 356 | + | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
| 370 | + | |
| 371 | + | |
| 372 | + | |
| 373 | + | |
| 374 | + | |
| 375 | + | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
| 385 | + | |
| 386 | + | |
| 387 | + | |
| 388 | + | |
| 389 | + | |
| 390 | + | |
| 391 | + | |
| 392 | + | |
| 393 | + | |
| 394 | + | |
| 395 | + | |
| 396 | + | |
| 397 | + | |
| 398 | + | |
| 399 | + | |
| 400 | + | |
| 401 | + | |
| 402 | + | |
| 403 | + | |
| 404 | + | |
| 405 | + | |
| 406 | + | |
| 407 | + | |
| 408 | + | |
| 409 | + | |
| 410 | + | |
| 411 | + | |
| 412 | + | |
| 413 | + | |
| 414 | + | |
| 415 | + | |
| 416 | + | |
| 417 | + | |
| 418 | + | |
| 419 | + | |
| 420 | + | |
| 421 | + | |
| 422 | + | |
| 423 | + | |
| 424 | + | |
| 425 | + | |
| 426 | + | |
| 427 | + | |
| 428 | + | |
| 429 | + | |
| 430 | + | |
| 431 | + | |
| 432 | + | |
| 433 | + | |
| 434 | + | |
| 435 | + | |
| 436 | + | |
| 437 | + | |
| 438 | + | |
| 439 | + | |
| 440 | + | |
| 441 | + | |
| 442 | + | |
| 443 | + | |
| 444 | + | |
| 445 | + | |
| 446 | + | |
| 447 | + | |
| 448 | + | |
| 449 | + | |
| 450 | + | |
| 451 | + | |
| 452 | + | |
| 453 | + | |
| 454 | + | |
| 455 | + | |
| 456 | + | |
| 457 | + | |
| 458 | + | |
| 459 | + | |
| 460 | + | |
| 461 | + | |
| 462 | + | |
| 463 | + | |
| 464 | + | |
| 465 | + | |
| 466 | + | |
| 467 | + | |
| 468 | + | |
| 469 | + | |
| 470 | + | |
| 471 | + | |
| 472 | + | |
| 473 | + | |
| 474 | + | |
| 475 | + | |
| 476 | + | |
| 477 | + | |
| 478 | + | |
| 479 | + | |
| 480 | + | |
| 481 | + | |
| 482 | + | |
| 483 | + | |
| 484 | + | |
| 485 | + | |
| 486 | + | |
| 487 | + | |
| 488 | + | |
| 489 | + | |
| 490 | + | |
| 491 | + | |
| 492 | + | |
| 493 | + | |
| 494 | + | |
| 495 | + | |
| 496 | + | |
| 497 | + | |
| 498 | + | |
| 499 | + | |
| 500 | + | |
| 501 | + | |
| 502 | + | |
| 503 | + | |
| 504 | + | |
| 505 | + | |
| 506 | + | |
| 507 | + | |
| 508 | + | |
| 509 | + | |
| 510 | + | |
| 511 | + | |
| 512 | + | |
| 513 | + | |
| 514 | + | |
| 515 | + | |
| 516 | + | |
| 517 | + | |
| 518 | + | |
| 519 | + | |
| 520 | + | |
| 521 | + | |
| 522 | + | |
| 523 | + | |
| 524 | + | |
| 525 | + | |
| 526 | + | |
| 527 | + | |
| 528 | + | |
| 529 | + | |
| 530 | + | |
| 531 | + | |
| 532 | + | |
| 533 | + | |
| 534 | + | |
| 535 | + | |
| 536 | + | |
| 537 | + | |
| 538 | + | |
| 539 | + | |
277 | 540 | | |
278 | 541 | | |
279 | 542 | | |
| |||
444 | 707 | | |
445 | 708 | | |
446 | 709 | | |
447 | | - | |
448 | | - | |
449 | | - | |
| 710 | + | |
| 711 | + | |
| 712 | + | |
| 713 | + | |
450 | 714 | | |
451 | 715 | | |
452 | 716 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
6 | 6 | | |
7 | 7 | | |
8 | 8 | | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
9 | 29 | | |
10 | 30 | | |
11 | 31 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
47 | 47 | | |
48 | 48 | | |
49 | 49 | | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
50 | 53 | | |
51 | 54 | | |
52 | 55 | | |
| |||
0 commit comments