MCPcopy Create free account
hub / github.com/codehamr/codehamr / handleStreamClosed

Method handleStreamClosed

internal/tui/model.go:911–972  ·  view source on GitHub ↗

handleStreamClosed drives what happens after one round's stream finishes: dispatch the next pending tool call, or, if none, finalize the turn and hand control back. A turn ends precisely when the assistant emits no tool calls; there is no loop tool to land on.

()

Source from the content-addressed store, hash-verified

909 // history (measured ~1.6x on a real run), so the true prompt can reach the
910 // server's window while Pack still believes it has headroom. Ollama then
911 // front-truncates silently (200 OK, system prompt lost). A server count at
912 // or past 95% of the window is that band; log it so a forensic pass sees
913 // the overflow instead of inferring it. Log-only; the headroom constant
914 // stays put until a run actually trips this.
915 if ctxSize := m.activeContextSize(); e.PromptTokens > 0 && e.PromptTokens >= ctxSize-ctxSize/20 {
916 dbgWritef("ctx_pressure", "prompt_tokens=%d at >=95%% of ctx=%d; real prompt has outgrown the packer's estimate, next request risks silent server-side truncation", e.PromptTokens, ctxSize)
917 // Say it out loud once. Past this band the server front-truncates
918 // silently - dropping the system prompt first - and every later round
919 // re-prefills a window it then discards. That reads to the user as "the
920 // agent got slow and stupid", with the cause visible only in a debug log
921 // they had to have enabled in advance.
922 if !m.ctxPressureWarned {
923 m.ctxPressureWarned = true
924 m.appendLine(styleError.Render(fmt.Sprintf(
925 "⚠ prompt is at %d of %d context tokens. Your server may be silently truncating it (the system prompt goes first). Lower context_size in .codehamr/config.yaml to match what the server really serves, or /clear.",
926 e.PromptTokens, ctxSize)))
927 }
928 }
929 // The inverse tripwire: the server counted far FEWER prompt tokens than the
930 // packer sent. char/4 only ever UNDERcounts (measured ~1.6x on code-heavy
931 // history) and chat-template overhead inflates the server's count further,
932 // so a report under HALF the estimate can only mean the server dropped
933 // content before the model saw it - stock Ollama's small default num_ctx
934 // front-truncating the system prompt away while the configured context_size
935 // says there is room. The >=95% check above can never fire in that state
936 // (prompt_tokens plateaus far below the configured window), which is
937 // exactly why this one exists. The 12k floor keeps small early prompts,
938 // where fixed overhead dominates, from tripping it.
939 if e.PromptTokens > 0 && m.lastPromptEstimate > 12000 && e.PromptTokens < m.lastPromptEstimate/2 && !m.ctxTruncationWarned {
940 m.ctxTruncationWarned = true
941 dbgWritef("ctx_truncation", "server prompt_tokens=%d vs packed estimate=%d; server-side truncation", e.PromptTokens, m.lastPromptEstimate)
942 m.appendLine(styleError.Render(fmt.Sprintf(
943 "⚠ the server processed only %d tokens of a ~%d-token prompt - it is silently truncating context (the system prompt goes first). Raise the server's window (e.g. Ollama num_ctx) or lower context_size in .codehamr/config.yaml to what it really serves.",
944 e.PromptTokens, m.lastPromptEstimate)))
945 }
946 m.flushStreaming()
947}
948
949// maxStreamReplays bounds transparent mid-stream retries per turn. Two is
950// enough to ride out a flaky proxy without letting a server that drops every
951// round spin forever; past it the error surfaces as it always did.
952const maxStreamReplays = 2
953
954// applyError unwinds the turn on a stream error: preserve content streamed
955// before the error (so the user keeps failure context), emit the one-line hint,
956// drop the pending queue, reset turn state.
957//
958// Unless the drop is replayable. llm sets MidStream for a socket that died
959// after delivering at least one frame and that is not the server's own refusal.
960// Only applyDone writes an assistant message to history, so a round that never
961// reached EventDone left history untouched: re-issuing the identical request
962// duplicates nothing. Discard this round's partial buffers first - they are
963// display and queue state, not context - and go again. Without this, one
964// transient drop three hours into an unattended run ends it and waits for a
965// human.
966func (m *Model) applyError(e llm.Event) tea.Cmd {
967 dbgWritef("error", "%v", e.Err)
968 if e.MidStream && m.phase.active() && m.streamReplays < maxStreamReplays {

Calls 11

dispatchNextToolMethod · 0.95
startChatMethod · 0.95
appendLineMethod · 0.95
maybeVerifyNudgeMethod · 0.95
finalizeTurnMethod · 0.95
endTurnMethod · 0.95
fireQueuedMethod · 0.95
newestAssistantEmptyFunction · 0.85
dbgWritefFunction · 0.85
toolCallLeakWarningFunction · 0.85
activeMethod · 0.80