96 lines
2.8 KiB
TypeScript
96 lines
2.8 KiB
TypeScript
// @generated — DO NOT EDIT. Source: packages/ai/base-session.ts
|
|
/**
|
|
* Shared session base class — extracts the common lifecycle, abort, and
|
|
* ID-resolution logic that every AIProvider session needs.
|
|
*
|
|
* Concrete providers extend this and implement query().
|
|
*/
|
|
|
|
import type { AIMessage, AISession } from "./types.ts";
|
|
|
|
export abstract class BaseSession implements AISession {
|
|
readonly parentSessionId: string | null;
|
|
onIdResolved?: (oldId: string, newId: string) => void;
|
|
|
|
protected _placeholderId: string;
|
|
protected _resolvedId: string | null = null;
|
|
protected _isActive = false;
|
|
protected _currentAbort: AbortController | null = null;
|
|
protected _queryGen = 0;
|
|
protected _firstQuerySent = false;
|
|
|
|
constructor(opts: { parentSessionId: string | null; initialId?: string }) {
|
|
this.parentSessionId = opts.parentSessionId;
|
|
this._placeholderId = opts.initialId ?? crypto.randomUUID();
|
|
}
|
|
|
|
get id(): string {
|
|
return this._resolvedId ?? this._placeholderId;
|
|
}
|
|
|
|
get isActive(): boolean {
|
|
return this._isActive;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Query lifecycle helpers — call from concrete query() implementations
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/** Error message returned when a query is already active. */
|
|
static readonly BUSY_ERROR: AIMessage = {
|
|
type: "error",
|
|
error:
|
|
"A query is already in progress. Abort the current query before sending a new one.",
|
|
code: "session_busy",
|
|
};
|
|
|
|
/**
|
|
* Call at the start of query(). Returns the generation number and abort
|
|
* signal, or null if the session is busy.
|
|
*/
|
|
protected startQuery(): { gen: number; signal: AbortSignal } | null {
|
|
if (this._isActive) return null;
|
|
|
|
const gen = ++this._queryGen;
|
|
this._isActive = true;
|
|
this._currentAbort = new AbortController();
|
|
return { gen, signal: this._currentAbort.signal };
|
|
}
|
|
|
|
/**
|
|
* Call in the finally block of query(). Only clears state if the
|
|
* generation matches (prevents a stale finally from clobbering a newer query).
|
|
*/
|
|
protected endQuery(gen: number): void {
|
|
if (this._queryGen === gen) {
|
|
this._isActive = false;
|
|
this._currentAbort = null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Call when the provider resolves the real session ID from the backend.
|
|
* Fires the onIdResolved callback so the SessionManager can remap its key.
|
|
*/
|
|
protected resolveId(newId: string): void {
|
|
if (this._resolvedId) return; // Already resolved
|
|
const oldId = this._placeholderId;
|
|
this._resolvedId = newId;
|
|
this.onIdResolved?.(oldId, newId);
|
|
}
|
|
|
|
/**
|
|
* Abort the current in-flight query. Subclasses should call super.abort()
|
|
* after any provider-specific cleanup.
|
|
*/
|
|
abort(): void {
|
|
if (this._currentAbort) {
|
|
this._currentAbort.abort();
|
|
this._isActive = false;
|
|
this._currentAbort = null;
|
|
}
|
|
}
|
|
|
|
abstract query(prompt: string): AsyncIterable<AIMessage>;
|
|
}
|