Last reviewed: 2026-08-01

Direct answer

The safe rule for CometAPI streaming tool calls is simple: streamed bytes may update a candidate action, but only a complete and validated output item may authorize that action. A coding-agent runner should never invoke a command, write a file, open a pull request, or call another service directly from a delta handler.

Build the parser in two layers. The first layer decodes the server-sent event stream into complete SSE messages. The second layer normalizes those messages into provider-specific events and accumulates each tool call separately. Only after the relevant terminal event arrives should the runner parse the accumulated arguments, validate them against the registered tool schema, apply policy and approval checks, and hand the call to the executor.

CometAPI documents an ordered Responses stream that includes response.output_item.added, response.output_item.done, and the final response.completed event. That gives a Responses-based adapter explicit lifecycle boundaries. A provider-native stream can use different names. Claude streaming, for example, sends tool inputs as partial JSON strings and says to accumulate them until content_block_stop before parsing. The adapter must therefore define its terminal event rather than relying on a generic string such as done.

If the connection ends, an error event arrives, or the expected terminal marker never appears, classify the candidate call as incomplete. Quarantine its buffer and do not execute it. A retry starts a new attempt with a new accumulator; it must not append new bytes to the abandoned buffer.

Who this is for

This guide is for developers who maintain coding-agent runners, model gateways, tool dispatchers, or CI services that consume streamed model output. It is especially relevant when a model-selected tool can change repository files, run shell commands, update an issue, trigger a deployment, or perform another action that cannot be treated as display-only text.

It also applies to teams using a compatibility library in front of CometAPI. A wrapper can simplify synchronous and asynchronous iteration, but the application still owns the boundary between receiving a chunk and authorizing a tool. If your system only streams text to a read-only interface and never dispatches tools, the execution gate is less urgent, although correct SSE framing and error handling still matter.

Key takeaways

  • Parse complete SSE messages, not arbitrary network chunks.
  • Keep an independent accumulator for every response item or tool-call identifier.
  • Treat argument deltas as opaque text until the provider’s terminal event arrives.
  • Parse the entire completed buffer once and reject trailing non-whitespace data.
  • Validate the resulting object against the exact registered tool schema.
  • Keep policy checks, human approval, and execution outside the stream callback.
  • Quarantine interrupted calls and never splice a retry onto an old buffer.
  • Log opaque identifiers and validation outcomes, not raw prompts, file contents, or tool arguments.
  • Test the same parser contract against every model route your agent can use.

Sources checked

  • The CometAPI Responses documentation describes custom function calls, warns that provider fields can vary, and lists the ordered SSE lifecycle through response.output_item.done and response.completed.
  • The Claude streaming messages documentation explains its event flow, in-stream errors, unknown event handling, and partial JSON tool-input deltas that are accumulated until content_block_stop.
  • MDN’s guide to server-sent events defines UTF-8 event framing, blank-line message boundaries, named events, multi-line data fields, comments, and reconnection behavior.
  • The LiteLLM CometAPI provider guide shows both synchronous and asynchronous streaming with stream=True, which provides a practical surface for integration fixtures.

Contract details to verify

Before implementing the dispatcher, verify the Responses endpoint contract for the exact route and model you plan to use. CometAPI’s documentation explicitly cautions that providers do not all accept or return the same fields. Record the observed contract as a versioned adapter fixture rather than assuming one model’s event shape applies to every route.

Separate transport completion from tool completion

An SSE message ends when the decoder reaches the blank-line delimiter. That boundary only means one transport message is available. It does not mean a model response, output item, content block, or tool call has finished. The decoder should concatenate consecutive data lines according to SSE rules, preserve the named event when present, ignore comment lines as protocol comments, and pass one complete message to the event normalizer.

The normalizer should map provider events into a small internal vocabulary such as response_started, item_started, argument_delta, item_finished, response_finished, and stream_error. Keep the original event type for diagnostics. Unknown events may be retained and counted, but they must never be interpreted as permission to execute.

Use an explicit execution state machine

A useful state machine makes it impossible for a delta callback to reach the executor:

RECEIVING -> ITEM_OPEN -> ITEM_TERMINAL
ITEM_TERMINAL -> PARSED -> SCHEMA_VALID -> POLICY_APPROVED -> EXECUTED
ANY_NONTERMINAL_ERROR -> QUARANTINED

Key each item by a composite identity that includes the run, response, and provider item or call identifier. Do not rely on the visible tool name: two parallel calls can select the same tool with different arguments. The CometAPI schema exposes parallel tool calls, so an implementation must either maintain separate buffers or deliberately serialize tool selection for workflows with dependencies.

At ITEM_TERMINAL, parse the assembled argument string as exactly one JSON value. Reject invalid JSON, non-whitespace trailing data, unexpected top-level types, unknown properties when the schema forbids them, missing required properties, values outside declared bounds, and tool names that are not registered for the run. Schema validity is necessary but not sufficient: repository scope, command policy, file-path restrictions, and any required human approval still apply.

A parser may determine that the bytes received so far contain one valid JSON value before the terminal event. That observation is not authorization. The provider has not yet declared the item complete, the stream may still report an error, and any later argument bytes must be treated as trailing data rather than inserted into the already closed JSON value. Keep the execution gate closed until both the protocol terminal event and full-buffer validation succeed.

Happy-path operator workflow

  1. Select the model route and load its versioned stream adapter, tool schemas, execution policy, and approval rules.
  2. Open the stream with execution disabled. Assign a run identifier and create an empty response record.
  3. Decode complete SSE messages. Normalize each named event while retaining the provider event type and item index.
  4. When an output item begins, create its accumulator. Append argument deltas only to the matching item; continue rendering text separately.
  5. When the adapter sees response.output_item.done, or the provider-specific equivalent such as content_block_stop, mark that item terminal. Do not use a text-completion event as a tool-completion event.
  6. Parse the entire completed argument buffer as one value, reject trailing non-whitespace bytes, and validate the value against the registered schema. Run path, permission, and approval checks after schema validation.
  7. Persist an execution decision before invoking the tool. Execute once, capture the structured result, and attach the result to the same item record.
  8. Send the tool result through the expected follow-up flow. Close the response record only when the overall terminal event arrives.
  9. Confirm that the log contains the terminal marker, validation outcomes, decision, and result status without containing raw arguments.

Error-path operator workflow

  1. On a timeout, disconnect, malformed SSE message, explicit stream error, missing terminal marker, or trailing argument data, stop advancing every affected item toward execution.
  2. Mark affected accumulators QUARANTINED. Record their byte counts and existing opaque run, response, and item identifiers, but do not parse or run them as complete calls.
  3. Preserve the last valid event type, response identifier, item identifier, and disconnect reason. Discard raw argument buffers according to the run’s data-handling policy.
  4. Start any retry as a separate attempt. Never concatenate a new stream with the previous attempt, even if the new bytes appear to continue the same JSON text.
  5. If the executor may have started before a local crash, reconcile the target system before retrying. The absence of a local success record does not prove that no side effect occurred.
  6. Escalate repeated contract mismatches as an adapter or model-route problem. Do not relax schema checks merely to make a failing stream pass.

Use explicit timeout rules for coding-agent calls so operators can distinguish an intentionally cancelled run from a stalled connection.

Keep logs useful and sanitized

A compact structured record is enough to reconstruct the parser decision without storing source code or arguments:

{
  "observed_at": "2026-08-01T00:00:00Z",
  "run_id": "run-42",
  "response_id": "resp-84",
  "item_id": "item-3",
  "model_route": "route-a",
  "stream_event": "response.output_item.done",
  "tool_name": "write_file",
  "buffer_bytes": 284,
  "terminal_seen": true,
  "json_status": "valid",
  "trailing_data": false,
  "schema_status": "valid",
  "policy_status": "approved",
  "execution_state": "completed",
  "error_class": null
}

Use opaque, content-independent run, response, and item identifiers for correlation. Do not derive those identifiers from the tool arguments. If a narrowly justified cross-system workflow requires content-based correlation, use a full-length keyed HMAC with restricted access and an explicit retention policy; a shortened display form must never be used for matching or deduplication.

Log the event type, item index or identifier, byte count, terminal status, parse result, trailing-data result, schema result, policy decision, execution status, duration, and a short error class. Do not log raw argument buffers, prompts, request bodies, repository content, environment values, or model reasoning. If a tool name itself can reveal sensitive workflow details, map it to an internal category before logging.

Failure modes

Parsing each network chunk as JSON. TCP and HTTP chunk boundaries are not SSE message boundaries. One JSON object may span chunks, and one chunk may contain several SSE messages. Feed bytes into an SSE decoder first.

Executing before the protocol declares the item complete. A strict parser can accept one complete JSON value in the bytes received so far, but that does not establish provider-level completion. The stream may subsequently report an error, and any later argument bytes would be trailing data that the final parse must reject. Wait for the configured item or content-block terminal event, then parse the entire buffer as exactly one value before authorizing anything.

Using a text terminal event for every output type. response.output_text.done completes a text part. It does not, by itself, prove that a separate function-call item is complete. Track output items by type and lifecycle.

Mixing parallel calls. Appending all argument deltas to one global string can create syntactically valid but semantically unrelated input. Maintain one accumulator per item or disable parallel tool calls when the workflow cannot safely separate them.

Treating a clean socket close as success. A connection can end before response.completed or before an open item reaches its terminal event. Success is a semantic state established by the event contract, not by the absence of another byte.

Blindly accepting reconnects. MDN notes that browser EventSource reconnects by default. Whether an SDK or wrapper does the same varies, so make retry and resume behavior explicit. A reconnected transport must not silently reuse an incomplete tool buffer.

Crashing on every unknown event. Provider protocols can add event types. Preserve unknown events for observability and ignore them for execution unless they invalidate a required invariant. The terminal gate must remain closed until a recognized completion event arrives.

Using an argument digest as a safe log identifier. Low-entropy arguments can be vulnerable to offline guessing when an unkeyed digest is stored, and truncated digests create avoidable collisions. Prefer opaque identifiers that are independent of content. If content-based correlation is unavoidable, use a full-length keyed HMAC with controlled retention rather than a shortened unkeyed digest.

Logging the evidence you meant to protect. Raw tool arguments may contain source text, paths, issue content, or user data. Store opaque identifiers, sizes, classifications, and validation outcomes instead.

Assuming a wrapper removes protocol responsibility. A library can expose convenient sync or async chunks, but your application still needs to know whether it is receiving text deltas, normalized output events, or provider-native events. Verify the wrapper’s behavior with fixtures before enabling mutating tools.

FAQ

Can I parse a tool call as soon as its arguments form valid JSON?

You may perform a side-effect-free speculative parse for diagnostics, but you must not execute the tool from that result. JSON validity only proves that the current bytes contain a parseable value; it does not prove that the provider has declared the item complete or that the stream will not end with an error. Wait for the provider-specific terminal event, parse the full buffer as exactly one value, reject trailing non-whitespace data, and then apply schema and policy checks.

Should I wait for response.completed before every tool call?

Use the strongest boundary required by your workflow. A Responses adapter can validate a tool item after its recognized item-terminal event, while response.completed closes the overall response and carries final response data such as usage. If calls have dependencies or your adapter cannot prove item completion independently, serialize them and wait for the overall terminal state.

How should parallel tool calls be handled?

Give every call its own state, buffer, terminal marker, validation record, and execution decision. Never key only by tool name or array position across retries. If the tools modify the same files or depend on ordering, turn parallel execution off or introduce an explicit dependency scheduler.

What should happen when an unknown event arrives?

Record its type and continue only if the adapter’s required invariants remain intact. Do not treat it as a delta, completion marker, approval, or error unless the adapter version defines that meaning. A missing recognized terminal event still leaves the item incomplete.

What fixtures should the parser test?

Split SSE input at every practical byte boundary, include multi-line data fields and comments, interleave text with two tool calls, inject an error before the terminal marker, omit the final event, send malformed JSON, send one valid JSON value followed by trailing data, fail schema validation, add an unknown event, and simulate a disconnect followed by a fresh attempt. Then use the site’s tool-call contract testing guide to repeat the suite across approved model routes.

Is sanitized logging enough to authorize execution?

No. Logging explains what the runner decided; it does not replace schema validation, permission checks, approval, or reconciliation. Keep authorization in the execution policy and use logs as evidence after the decision.

Reader next step

Start with one read-only tool and capture a sanitized successful stream fixture plus fixtures for interruption, trailing data, malformed arguments, and an unknown event. Implement the state machine so only the POLICY_APPROVED state can call the executor. Confirm that every error fixture ends in QUARANTINED, then add a mutating tool in a disposable repository and repeat the tests.

Once the parser, adapter, and fixtures are in place, Start with CometAPI and verify the exact event contract for each model route before enabling real tool side effects.