تخطَّ إلى المحتوى

Streaming

هذا المحتوى غير متوفر بلغتك بعد.

Enabling Streaming

Set stream: true in your chat completion request:

Terminal window
curl https://ghostmind.optdmsa.com/v1/chat/completions \
-H "Authorization: Bearer gmk_live_..." \
-H "Content-Type: application/json" \
-d '{
"model": "auto",
"stream": true,
"messages": [{"role": "user", "content": "Hello!"}]
}'

SSE Format

The response is a stream of Server-Sent Events:

data: {"id":"chatcmpl-abc","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"role":"assistant"},"finish_reason":null}]}
data: {"id":"chatcmpl-abc","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"Hello"},"finish_reason":null}]}
data: {"id":"chatcmpl-abc","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"!"},"finish_reason":null}]}
data: {"id":"chatcmpl-abc","object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}
data: [DONE]

Event Flow

  1. First chunk — Contains delta.role: "assistant"
  2. Content chunks — Contain delta.content with text fragments
  3. Final chunk — Contains finish_reason: "stop"
  4. [DONE] — Stream terminator

Error During Stream

If a provider or session error occurs during streaming, GhostMind emits a structured response.failed lifecycle event — not assistant content text.

Never interpret error text as assistant content. The error is delivered as a chunk with finish_reason: "error" and a lifecycle_event field.

Error event format

data: {"id":"chatcmpl-abc","object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":"error"}],"lifecycle_event":{"type":"response.failed","error":{"code":"ACCOUNT_REAUTH_REQUIRED","message":"ChatGPT session expired. Reconnect the account in the User App.","type":"account_error","http_status":403,"retryable":false}}}

Machine-readable error codes

error.codeerror.http_statusMeaningretryable
ACCOUNT_REAUTH_REQUIRED403ChatGPT session expired — reconnect in User Appfalse
PROVIDER_RATE_LIMITED429Upstream provider rate-limited the requesttrue
PROVIDER_UNAVAILABLE502/503Upstream provider returned an error or is downtrue
PROVIDER_TIMEOUT504Upstream provider did not respond in timetrue
INVALID_REQUEST400Request rejected by upstream (bad parameter)false
IMAGE_GENERATION_UNAVAILABLE503Image generation failed (plan/quota/feature) — the session is healthy, only the image feature failedfalse

Image generation failures

ChatGPT image generation is implicit — triggered by the user’s natural-language prompt, not a request flag. When the account’s plan/quota cannot generate an image, ChatGPT returns ordinary assistant text (e.g., “image creation is temporarily unavailable”) instead of a structured image asset. GhostMind detects this pattern and emits a IMAGE_GENERATION_UNAVAILABLE lifecycle event instead of passing the fallback text through as a successful response.

Important: This error does not mean the session is dead. The account can still serve text/chat requests. Do not reconnect the account or mark it expired. Do not retry blindly — retrying re-triggers the same fallback and burns quota.

Client handling rules

  1. Stop reading the stream when you receive lifecycle_event.type == "response.failed".
  2. Check error.retryable — if true, retry with exponential backoff. If false, surface the error to the user.
  3. Do not append delta.content to your assistant text buffer after a response.failed event.
  4. The stream will terminate with data: [DONE] after the error event.
  5. Save the X-Request-Id header for troubleshooting.

Client Example (JavaScript)

const response = await fetch("https://ghostmind.optdmsa.com/v1/chat/completions", {
method: "POST",
headers: {
"Authorization": "Bearer gmk_live_...",
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "auto",
stream: true,
messages: [{ role: "user", content: "Hello!" }],
}),
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\n");
buffer = lines.pop();
for (const line of lines) {
if (line.startsWith("data: ")) {
const data = line.slice(6);
if (data === "[DONE]") return;
const chunk = JSON.parse(data);
// Check for structured error event
const lifecycle = chunk.lifecycle_event;
if (lifecycle && lifecycle.type === "response.failed") {
const err = lifecycle.error;
console.error(`Stream error: ${err.code} — ${err.message}`);
if (err.retryable) {
// Retry with backoff
}
return; // Stop processing
}
const content = chunk.choices?.[0]?.delta?.content;
if (content) process.stdout.write(content);
}
}
}

Next Steps