// src/stream/turn-identity.ts
function normalizeClientTurnId(value) {
  if (value === void 0 || value === null) return void 0;
  if (typeof value !== "string") throw new Error("turnId must be a string");
  const trimmed = value.trim();
  if (!trimmed) throw new Error("turnId must not be blank");
  if (trimmed.length > 160) throw new Error("turnId is too long");
  if (!/^[A-Za-z0-9:_-]+$/.test(trimmed)) {
    throw new Error("turnId contains unsupported characters");
  }
  return trimmed;
}
function buildUserTextParts(text, turnId) {
  const part = { type: "text", text };
  if (turnId) part.turnId = turnId;
  return [part];
}
function messageHasTurnId(message, turnId) {
  for (const part of message.parts ?? []) {
    if (part && typeof part === "object" && String(part.turnId ?? "") === turnId) {
      return true;
    }
  }
  return false;
}
function resolveChatTurn(input) {
  const { existingMessages, userContent, turnId } = input;
  const reusableIndex = findReusableUserMessageIndex(existingMessages, userContent, turnId);
  if (reusableIndex >= 0) {
    return {
      turnIndex: countUserMessages(existingMessages.slice(0, reusableIndex)),
      shouldInsertUserMessage: false,
      priorMessages: existingMessages.slice(0, reusableIndex),
      userParts: buildUserTextParts(userContent, turnId)
    };
  }
  return {
    turnIndex: countUserMessages(existingMessages),
    shouldInsertUserMessage: true,
    priorMessages: existingMessages,
    userParts: buildUserTextParts(userContent, turnId)
  };
}
function findReusableUserMessageIndex(messages, userContent, turnId) {
  if (turnId) {
    for (let index = messages.length - 1; index >= 0; index -= 1) {
      const message = messages[index];
      if (message?.role === "user" && messageHasTurnId(message, turnId)) return index;
    }
  }
  const latest = messages.at(-1);
  if (latest?.role === "user" && latest.content === userContent) {
    return messages.length - 1;
  }
  return -1;
}
function countUserMessages(messages) {
  return messages.filter((message) => message.role === "user").length;
}

// src/stream/turn-buffer.ts
function deltaTypeOf(ev) {
  const e = ev;
  if (!e || typeof e !== "object") return null;
  const inner = e.kind === "event" ? e.event : e;
  if (!inner || typeof inner !== "object") return null;
  if ((inner.type === "text" || inner.type === "reasoning") && typeof inner.text === "string") {
    return inner.type;
  }
  return null;
}
function coalesceDeltas(events) {
  const out = [];
  for (const ev of events) {
    const type = deltaTypeOf(ev);
    const prev = out[out.length - 1];
    if (type && prev && deltaTypeOf(prev) === type) {
      const read = (x) => x.kind === "event" ? x.event : x;
      const merged = JSON.parse(JSON.stringify(prev));
      read(merged).text = String(read(prev).text) + String(read(ev).text);
      out[out.length - 1] = merged;
      continue;
    }
    out.push(ev);
  }
  return out;
}
function asPartUpdate(ev) {
  const e = ev;
  if (!e || typeof e !== "object" || e.type !== "message.part.updated") return null;
  const data = e.data;
  if (!data || typeof data !== "object") return null;
  const part = data.part;
  const partId = part?.id ?? data.partId ?? part?.partId ?? null;
  return { partId, delta: data.delta };
}
function coalesceChatStreamEvents(events) {
  const out = [];
  for (const ev of events) {
    const cur = asPartUpdate(ev);
    const prevEv = out[out.length - 1];
    const prev = prevEv ? asPartUpdate(prevEv) : null;
    if (cur && prev && cur.partId != null && cur.partId === prev.partId) {
      const merged = JSON.parse(JSON.stringify(ev));
      merged.data.delta = String(prev.delta ?? "") + String(cur.delta ?? "");
      out[out.length - 1] = merged;
      continue;
    }
    out.push(ev);
  }
  return out;
}
function createBufferedTurnTap(opts) {
  const flushIntervalMs = opts.flushIntervalMs ?? 400;
  const coalesce = opts.coalesce ?? coalesceDeltas;
  const startedAt = Date.now();
  let seq = 0;
  let clientGone = false;
  let pending = [];
  let lastFlush = Date.now();
  let started = false;
  async function flush() {
    if (pending.length === 0) return;
    const batch = coalesce(pending);
    pending = [];
    const rows = batch.map((ev) => ({ seq: ++seq, event: JSON.stringify(ev) }));
    await opts.store.append(opts.turnId, rows);
    lastFlush = Date.now();
  }
  async function ensureStarted() {
    if (started) return;
    started = true;
    await opts.store.setStatus(opts.turnId, "running", opts.scopeId);
  }
  return {
    async onEvent(raw) {
      await ensureStarted();
      const ev = raw && typeof raw === "object" ? { ...raw, _t: Date.now() - startedAt } : raw;
      pending.push(ev);
      if (!clientGone && opts.write) {
        try {
          await opts.write(JSON.stringify(ev));
        } catch {
          clientGone = true;
        }
      }
      if (Date.now() - lastFlush >= flushIntervalMs) await flush();
    },
    async done(status = "complete") {
      await ensureStarted();
      if (status === "error") {
        await flush().catch(() => {
        });
        await opts.store.setStatus(opts.turnId, "error", opts.scopeId).catch(() => {
        });
        return;
      }
      await flush();
      await opts.store.setStatus(opts.turnId, "complete", opts.scopeId);
    }
  };
}
async function pumpBufferedTurn(opts) {
  const tap = createBufferedTurnTap(opts);
  try {
    for await (const raw of opts.source) await tap.onEvent(raw);
    await tap.done("complete");
  } catch (err) {
    await tap.done("error");
    throw err;
  }
}
async function* replayTurnEvents(opts) {
  const pollMs = opts.pollMs ?? 500;
  const timeoutMs = opts.timeoutMs ?? 12e4;
  let cursor = opts.fromSeq ?? 0;
  const deadline = Date.now() + timeoutMs;
  for (; ; ) {
    const batch = await opts.store.read(opts.turnId, cursor);
    for (const row of batch) {
      cursor = Math.max(cursor, row.seq);
      yield row;
    }
    const status = await opts.store.getStatus(opts.turnId);
    if (status !== "running") {
      yield { seq: -1, event: JSON.stringify({ type: "turn_status", status: status ?? "unknown" }) };
      return;
    }
    if (Date.now() >= deadline) {
      yield { seq: -1, event: JSON.stringify({ type: "turn_status", status: "timeout" }) };
      return;
    }
    await new Promise((r) => setTimeout(r, pollMs));
  }
}
var TURN_EVENTS_MIGRATION_SQL = `
CREATE TABLE IF NOT EXISTS turn_events (
  turnId TEXT NOT NULL,
  seq INTEGER NOT NULL,
  event TEXT NOT NULL,
  PRIMARY KEY (turnId, seq)
);
CREATE TABLE IF NOT EXISTS turn_status (
  turnId TEXT PRIMARY KEY,
  status TEXT NOT NULL,
  scopeId TEXT,
  updatedAt TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_turn_status_scope ON turn_status (scopeId, status);
`;
var TURN_STATUS_SCOPE_MIGRATION_SQL = `ALTER TABLE turn_status ADD COLUMN scopeId TEXT;`;
function createD1TurnEventStore(db) {
  return {
    async append(turnId, events) {
      if (!events.length) return;
      const placeholders = events.map(() => "(?, ?, ?)").join(", ");
      const values = events.flatMap((e) => [turnId, e.seq, e.event]);
      await db.prepare(`INSERT OR IGNORE INTO turn_events (turnId, seq, event) VALUES ${placeholders}`).bind(...values).run();
    },
    async read(turnId, fromSeq) {
      const { results } = await db.prepare("SELECT seq, event FROM turn_events WHERE turnId = ? AND seq > ? ORDER BY seq ASC").bind(turnId, fromSeq).all();
      return results;
    },
    async setStatus(turnId, status, scopeId) {
      await db.prepare(
        "INSERT INTO turn_status (turnId, status, scopeId, updatedAt) VALUES (?, ?, ?, ?) ON CONFLICT(turnId) DO UPDATE SET status = excluded.status, scopeId = COALESCE(excluded.scopeId, turn_status.scopeId), updatedAt = excluded.updatedAt"
      ).bind(turnId, status, scopeId ?? null, (/* @__PURE__ */ new Date()).toISOString()).run();
    },
    async getStatus(turnId) {
      const row = await db.prepare("SELECT status FROM turn_status WHERE turnId = ?").bind(turnId).first();
      return row?.status ?? null;
    },
    async listRunning(scopeId) {
      const { results } = await db.prepare("SELECT turnId FROM turn_status WHERE scopeId = ? AND status = 'running' ORDER BY updatedAt DESC").bind(scopeId).all();
      return results.map((r) => r.turnId);
    }
  };
}
function createMemoryTurnEventStore() {
  const events = /* @__PURE__ */ new Map();
  const status = /* @__PURE__ */ new Map();
  const scopes = /* @__PURE__ */ new Map();
  const order = [];
  return {
    async append(turnId, rows) {
      const list = events.get(turnId) ?? [];
      list.push(...rows);
      events.set(turnId, list);
    },
    async read(turnId, fromSeq) {
      return (events.get(turnId) ?? []).filter((e) => e.seq > fromSeq);
    },
    async setStatus(turnId, s, scopeId) {
      status.set(turnId, s);
      if (scopeId) scopes.set(turnId, scopeId);
      if (!order.includes(turnId)) order.push(turnId);
    },
    async getStatus(turnId) {
      return status.get(turnId) ?? null;
    },
    async listRunning(scopeId) {
      return [...order].reverse().filter((t) => status.get(t) === "running" && scopes.get(t) === scopeId);
    }
  };
}

export {
  normalizeClientTurnId,
  buildUserTextParts,
  messageHasTurnId,
  resolveChatTurn,
  coalesceDeltas,
  coalesceChatStreamEvents,
  createBufferedTurnTap,
  pumpBufferedTurn,
  replayTurnEvents,
  TURN_EVENTS_MIGRATION_SQL,
  TURN_STATUS_SCOPE_MIGRATION_SQL,
  createD1TurnEventStore,
  createMemoryTurnEventStore
};
//# sourceMappingURL=chunk-23LUHHHN.js.map