mirror of
https://github.com/stablyai/orca.git
synced 2026-10-07 08:02:21 +00:00
* fix(native-chat): a new structured chat's model list comes from the host that runs it agentSession.modelCatalog builds the structured-session host like agentSession.options, so a host with no saved chats since it started answers instead of refusing. When the host answers unknown because its first listing runs in the background, the picker re-reads on a bounded schedule until that listing lands. Local terminal-backed chat skips the structured catalog when a custom launch command is configured. * fix(native-chat): wait on the host's first model listing instead of re-reading on a timer A new structured chat's picker read the host catalog once; on an account the host had never listed, the answer was "unknown" while a background listing ran, and the client re-read on a 1-30 s schedule. Replace the schedule with the host's own completion signal: - The host answers a cold read with `listingInProgress: true` (new optional field) once it has started or joined that listing. A read that passes the new optional `waitForListing` param awaits the same joined listing and answers with it, or a plain "unknown" if it failed. The at-rest options read never waits. A host that predates the field never sends it, so the client never sends the param to a host that would refuse it. - The picker reads once per open and attach; after the host's report it sends one waiting read, with a 90 s client timeout above the slowest listing. While that read is out, the model pill keeps its label but cannot open or be set (typed /model included). An answer, failure, timeout, hide, attach or the provider's own list releases it. - `agentSession.modelCatalog` builds the structured-session host only for a read that names a session (a structured chat). Terminal-backed chat's session-less read keeps the non-building gate, so a desktop that never runs structured chat never opens the session journal. * fix(native-chat): one waiting model-list read per chat, and no late menu open The waiting catalog read was owned by one run of the picker's effect. Attach (a new fence), hide/show or a send re-ran the effect: the cleanup released the model picker onto the built-in list for a round trip, and the new run sent a second waiting read while the first, which cannot be withdrawn, kept a remote call slot until the listing ended. The waiting read now belongs to the chat (runtime target + agent + session): a small registry keeps one in flight per chat, every re-run or remount joins it, and the entry is deleted when the read settles. The picker hold is derived from that entry being in flight, so it lasts across attach and hide/show and ends when the read settles, the provider reports its own list, or the pane switches to another session. Answers still pass the stale and record checks. A bare /model typed while the list loads no longer opens the model menu by itself when the list lands: the menu stays keyed on the request, and only its initial open is suppressed while pending, so the request is spent shut and the end of the pending period never remounts it. * fix(native-chat): release the model picker in the same commit as the host list When the waiting catalog read settled in the chat that started it, the registry dropped its entry and told subscribers first, and the host list was applied a few microtasks later. React committed once with the picker enabled on the built-in list, then again with the host's list. Joiners now hand the registry their apply callback, and the registry runs every joiner (with the answer, or nothing when the read failed or timed out) before it deletes the entry and notifies. The release and the list land in one commit. An effect cleanup leaves the wait instead of flagging itself stale. * fix(runtime): queue model catalog reads in the long-wait lane A model catalog read that waits on a host's first listing replies only when that listing ends, yet it took one of the 8 foreground call slots for its server. Enough chats opened during one cold listing would stall that server's sends and interrupts until a wait settled. agentSession.modelCatalog now joins worktree.rm in the long-wait lane: same concurrency, counted apart from the foreground calls. The queue classifies by method only, and a warm catalog read answers at once, so the whole method moves.
514 lines
23 KiB
TypeScript
514 lines
23 KiB
TypeScript
import type {
|
|
AgentSessionBackgroundTask,
|
|
AgentSessionBackgroundTaskState
|
|
} from './agent-session-background-task-wire'
|
|
import type { AgentSessionRewindReason, AgentSessionRewindSupport } from './agent-session-rewind'
|
|
import type { AgentSessionWireRefusal } from './agent-session-wire-refusals'
|
|
import type { AgentChildWorkView } from './agent-status-child-work-view'
|
|
import type {
|
|
AgentSessionQueuedMessage,
|
|
AgentSessionQueuePause
|
|
} from './agent-session-queued-message-wire'
|
|
|
|
export * from './agent-session-wire-refusals'
|
|
export * from './agent-session-queued-message-wire'
|
|
import type { AgentSessionConversationCommand } from './agent-session-conversation-command'
|
|
import type { AgentSessionContextUsage } from './agent-session-context-usage'
|
|
// ─── Structured agent-session wire contract ─────────────────────────────────
|
|
// The shapes `agentSession.*` accepts and publishes. Phase 2 builds provider
|
|
// adapters and clients against exactly these types, so everything here must be
|
|
// plain JSON. The whole surface is gated by agent-session.structured.v1, which
|
|
// no released baseline advertises; after that capability ships, every new field
|
|
// must remain optional to old readers (docs/reference/remote-wire-compatibility.md).
|
|
|
|
import type {
|
|
AgentJournalCursor,
|
|
AgentJournalRenderItem,
|
|
AgentJournalResetReason,
|
|
AgentJournalResolution,
|
|
AgentJournalSubmission,
|
|
AgentJournalThreadGoal,
|
|
AgentJournalTurnOutcome
|
|
} from './agent-session-journal-types'
|
|
import type { AgentTurnOutcome } from './agent-turn-outcome'
|
|
import {
|
|
agentSessionScopeKey,
|
|
type AgentSessionExecutionLocation,
|
|
type AgentSessionHandoffStage,
|
|
type AgentSessionRecord
|
|
} from './agent-session-record'
|
|
import type { AgentProviderSessionMetadata } from './agent-session-resume'
|
|
import type { NativeChatSubagentEntry } from './native-chat-types'
|
|
import type { StructuredAgentSessionProjectedStatus } from './structured-agent-session-projection'
|
|
|
|
/** `agentSession.handoffStatus`. Named for the removed terminal handoff; released desktop clients
|
|
* still read `owner`. Clients parse the reply as unknown, since older hosts sent more fields. */
|
|
export type AgentSessionHandoffStatus = {
|
|
owner: 'native' | 'none'
|
|
direction: 'to-native' | null
|
|
phase: 'idle' | 'switching' | 'failed'
|
|
stage: AgentSessionHandoffStage | null
|
|
operationId: string | null
|
|
error?: { message: string; recoverableOwner: 'none' }
|
|
}
|
|
|
|
export type {
|
|
AgentSessionBackgroundTask,
|
|
AgentSessionBackgroundTaskRunState,
|
|
AgentSessionBackgroundTaskState
|
|
} from './agent-session-background-task-wire'
|
|
export { agentSessionBackgroundTasksEqual } from './agent-session-background-task-wire'
|
|
|
|
export type AgentSessionTurnActivity = {
|
|
turnId: string
|
|
text: string
|
|
}
|
|
|
|
export const AGENT_SESSION_ID_MAX_LENGTH = 512
|
|
|
|
/** Backward paging is the client's normal read; 40 matches the page size the
|
|
* mobile list renders without a visible fill-in. */
|
|
export const AGENT_SESSION_HISTORY_DEFAULT_LIMIT = 40
|
|
export const AGENT_SESSION_HISTORY_MAX_LIMIT = 200
|
|
|
|
export const AGENT_SESSION_HISTORY_DIRECTIONS = ['tail', 'before', 'after'] as const
|
|
/** `tail` is the newest page, `before` pages backward, `after` catches a live
|
|
* reader up. Only `after` needs replayable rows; the other two read the
|
|
* reduced timeline and so survive compaction. */
|
|
export type AgentSessionHistoryDirection = (typeof AGENT_SESSION_HISTORY_DIRECTIONS)[number]
|
|
|
|
export type AgentSessionHistoryRequest = {
|
|
sessionId: string
|
|
direction: AgentSessionHistoryDirection
|
|
/** Required for `before` and `after`; ignored for `tail`. */
|
|
cursor?: AgentJournalCursor
|
|
limit?: number
|
|
}
|
|
|
|
/** A subagent named by a roster row the page does not carry, while its own rows are on it. */
|
|
export type AgentSessionSubagentRosterEntry = {
|
|
/** The roster row naming it: the first that does. */
|
|
itemId: string
|
|
sequence: number
|
|
sequenceIndex?: number
|
|
revision: number
|
|
entry: NativeChatSubagentEntry
|
|
}
|
|
|
|
export type AgentSessionHistoryPage = {
|
|
sessionId: string
|
|
epoch: string
|
|
/** Optional for mixed-version readers; write-capable clients use the
|
|
* checkpoint without forcing a second attach or a redundant snapshot. */
|
|
fence?: number
|
|
direction: AgentSessionHistoryDirection
|
|
items: AgentJournalRenderItem[]
|
|
/** Populated by `after` reads so a disconnected client can apply tombstones. */
|
|
removedItemIds: string[]
|
|
/** Submissions overlapping this page, so an unconfirmed bubble renders with
|
|
* its dispatch state instead of as a plain message. */
|
|
submissions: AgentJournalSubmission[]
|
|
/** Page edges. `nextCursor` is what the client sends back for the same
|
|
* direction; it equals the request cursor when the page is empty. */
|
|
window: {
|
|
oldest: AgentJournalCursor | null
|
|
newest: AgentJournalCursor | null
|
|
nextCursor: AgentJournalCursor
|
|
}
|
|
/** Current journal head for switching from a bounded page to live subscribe. */
|
|
liveCursor?: AgentJournalCursor
|
|
hasOlder: boolean
|
|
hasNewer: boolean
|
|
/** Present on hosts that expose provider-owned background task lifecycle. */
|
|
backgroundTasks?: AgentSessionBackgroundTaskState | null
|
|
/** The host's queued drafts. Absent = no claim (older host); `[]`/null = empty.
|
|
* Live subscription state stays authoritative over a stale history answer. */
|
|
queuedMessages?: AgentSessionQueuedMessage[] | null
|
|
/** The queue's pause, published with the list: present whenever `queuedMessages` is, null
|
|
* when the queue sends on its own. */
|
|
queuePause?: AgentSessionQueuePause | null
|
|
/** Host wall clock (ms epoch) when the page was read, so a client attaching mid-turn
|
|
* can anchor a live counter on the real start. Absent from older hosts. */
|
|
hostNow?: number
|
|
/** Names the subagents with rows on the page whose roster row is older than it; bounded.
|
|
* Absent from older hosts, and when every such roster row is on the page. */
|
|
subagentRoster?: AgentSessionSubagentRosterEntry[]
|
|
}
|
|
|
|
export type AgentSessionHistoryResult =
|
|
| { ok: true; page: AgentSessionHistoryPage; providerSession?: AgentProviderSessionMetadata }
|
|
/** Every reset carries a byte-bounded tail page so recovery cannot exceed
|
|
* remote outbound admission or require another call before resubscribing. */
|
|
| {
|
|
ok: false
|
|
reset: AgentJournalResetReason
|
|
page: AgentSessionHistoryPage
|
|
fence?: number
|
|
providerSession?: AgentProviderSessionMetadata
|
|
}
|
|
|
|
/** Cursor-qualified incremental publication. Items and submissions carry their
|
|
* CURRENT reduced state rather than a delta, so applying a batch twice
|
|
* converges instead of double-appending. */
|
|
export type AgentSessionJournalBatch = {
|
|
cursor: AgentJournalCursor
|
|
items: AgentJournalRenderItem[]
|
|
removedItemIds: string[]
|
|
submissions: AgentJournalSubmission[]
|
|
}
|
|
|
|
/** Host wall clock (ms epoch) stamped once per published frame; see `AgentSessionHistoryPage`. */
|
|
type AgentSessionHostClockField = { hostNow?: number }
|
|
|
|
export type AgentSessionSubscribeEvent =
|
|
| ({
|
|
type: 'snapshot'
|
|
sessionId: string
|
|
page: AgentSessionHistoryPage
|
|
fence: number
|
|
backgroundTasks?: AgentSessionBackgroundTaskState | null
|
|
/** Whole-list draft publication; omitted when unchanged since the last frame sent. */
|
|
queuedMessages?: AgentSessionQueuedMessage[] | null
|
|
/** Rides with `queuedMessages`; null when the queue sends on its own. */
|
|
queuePause?: AgentSessionQueuePause | null
|
|
/** Omitted when unchanged; null clears a previous provider catalog. */
|
|
commands?: AgentSessionSlashCommand[] | null
|
|
/** Latest provider-authored turn activity; optional for mixed-version hosts. */
|
|
activity?: AgentSessionTurnActivity | null
|
|
} & AgentSessionHostClockField)
|
|
| ({
|
|
type: 'batch'
|
|
sessionId: string
|
|
batch: AgentSessionJournalBatch
|
|
/** Optional so mixed-version cursors retain the ownership fence. */
|
|
fence?: number
|
|
backgroundTasks?: AgentSessionBackgroundTaskState | null
|
|
/** Whole-list draft publication. On a multi-page catch-up it rides only the
|
|
* final page, so a consumed card never vanishes before its bubble arrives. */
|
|
queuedMessages?: AgentSessionQueuedMessage[] | null
|
|
/** Rides with `queuedMessages`; null when the queue sends on its own. */
|
|
queuePause?: AgentSessionQueuePause | null
|
|
/** Omitted when unchanged; null clears a previous provider catalog. */
|
|
commands?: AgentSessionSlashCommand[] | null
|
|
/** Additive ephemeral state; it never creates or advances journal rows. */
|
|
activity?: AgentSessionTurnActivity | null
|
|
} & AgentSessionHostClockField)
|
|
| ({
|
|
type: 'reset'
|
|
sessionId: string
|
|
reset: AgentJournalResetReason
|
|
page: AgentSessionHistoryPage
|
|
fence: number
|
|
backgroundTasks?: AgentSessionBackgroundTaskState | null
|
|
/** Whole-list draft publication; a reset re-hydrates it with the page. */
|
|
queuedMessages?: AgentSessionQueuedMessage[] | null
|
|
/** Rides with `queuedMessages`; null when the queue sends on its own. */
|
|
queuePause?: AgentSessionQueuePause | null
|
|
/** Omitted when unchanged; null clears a previous provider catalog. */
|
|
commands?: AgentSessionSlashCommand[] | null
|
|
activity?: AgentSessionTurnActivity | null
|
|
} & AgentSessionHostClockField)
|
|
| { type: 'end' }
|
|
|
|
// ─── Status feed ────────────────────────────────────────────────────────────
|
|
|
|
/** What a session list needs to know about one session. The host projects it
|
|
* from the journal so no client has to replay a transcript to learn whether a
|
|
* turn is running. Additive surface: an older host has no such method. */
|
|
export type AgentSessionStatusSummary = {
|
|
rewindBlockedReason?: AgentSessionRewindReason
|
|
sessionId: string
|
|
workspaceId: string
|
|
agent: AgentSessionRecord['provider']
|
|
/** Null until the journal holds a persisted user or assistant message. */
|
|
status: StructuredAgentSessionProjectedStatus | null
|
|
/** Present only while this host has the provider child executing the session. */
|
|
hostExecutionOwned?: true
|
|
/** With `hostExecutionOwned`: whether that child has proven its start. `starting` is a
|
|
* published session whose provider has not yet answered startup; absent on older hosts. */
|
|
hostExecutionPhase?: 'starting' | 'ready'
|
|
latestPrompt: string
|
|
/** Provider model in force for the next turn; absent until the host has read the options. */
|
|
model?: string
|
|
/** The tool the running turn is inside, else the last one it used. Absent unless `status`
|
|
* is 'working'. */
|
|
toolName?: string
|
|
toolInput?: string
|
|
/** Preview of the newest assistant prose, so a settled row says what the agent said. */
|
|
lastAssistantMessage?: string
|
|
/** The verdict on the latest request: the provider's, or, when it gave none, what the host
|
|
* observed of the turn's end. Present only while `status` is `idle`: a running or
|
|
* attention-blocked turn has no verdict yet, and a stale one must not ride along. Absent means
|
|
* UNKNOWN, never success. Optional for mixed-version hosts; an older client reads an arm it
|
|
* does not know as no verdict. The agent-status row publishes it as `mainAgent.outcome`. */
|
|
turnOutcome?: AgentTurnOutcome
|
|
/** Live provider-owned background tasks, so session lists can render
|
|
* subagent children without holding a journal reader open. Optional for
|
|
* mixed-version hosts. Derived from `children` on hosts that publish it. */
|
|
backgroundTasks?: AgentSessionBackgroundTask[]
|
|
/** The host's running child records for this session, as views: live ones, and a finished one
|
|
* whose own work still runs (it reads monitoring); finished children ride the background-task
|
|
* channel only. Absent from older hosts; decode with `decodeAgentChildWorkViews`. Usage is
|
|
* omitted, and an evidence clock that only ticked does not republish: per-tick freshness rides
|
|
* the background-task channel. */
|
|
children?: AgentChildWorkView[]
|
|
providerSession?: AgentProviderSessionMetadata
|
|
updatedAt: number
|
|
/** When the session's own agent entered `status`, dated by its own lifecycle edges and never by
|
|
* row activity: `updatedAt` also moves for a subagent's rows. Absent from older hosts, and when
|
|
* the journal records no such edge; readers then keep dating the state themselves. */
|
|
statusStartedAt?: number
|
|
}
|
|
|
|
/** A summary outlives its provider child: an evicted idle session is still idle, so the host
|
|
* keeps the last projection and never retracts one. Tabs, not this feed, decide what is listed. */
|
|
export type AgentSessionStatusEvent =
|
|
| { type: 'snapshot'; sessions: AgentSessionStatusSummary[] }
|
|
| { type: 'status'; session: AgentSessionStatusSummary }
|
|
| { type: 'end' }
|
|
|
|
// ─── Turn completion feed ───────────────────────────────────────────────────
|
|
|
|
/**
|
|
* The session's latest request reaching a terminal outcome — a root turn, or a send the agent or
|
|
* its start refused — derived by the EXECUTION HOST at journal commit.
|
|
*
|
|
* This is the EDGE, with turn identity; `AgentSessionStatusSummary.turnOutcome` is the STATE.
|
|
* The summary carries the verdict only while the session is idle, as a fact about the main agent's
|
|
* last turn that a status reader may act on (attention alerts, the `mainAgent.outcome` row field),
|
|
* and never a turn id: a reader that needs to know WHICH turn finished, or to react exactly once
|
|
* per finish, subscribes here. Re-broadcasting the summary on every status change therefore
|
|
* repeats a state, not a completion.
|
|
*
|
|
* `outcome` is the journal's recorded verdict (the provider's, a stop, or the host's supersede) and
|
|
* is never inferred — a turn the host only observed ending carries no outcome and produces no event
|
|
* at all, because absent means UNKNOWN, not success.
|
|
*/
|
|
export type AgentSessionTurnCompletion = {
|
|
/** Host-and-workspace scope; a bare provider turn id is not globally unique. */
|
|
scope: AgentSessionExecutionLocation
|
|
sessionId: string
|
|
/** The request's identity: the root turn's id, or for a send refused before any turn, that
|
|
* send's journal item key. Neither is minted here. */
|
|
turnId: string
|
|
outcome: AgentJournalTurnOutcome
|
|
/** Execution host's clock at journal commit. */
|
|
completedAt: number
|
|
/** The request settled while a prompt waits on the user. Absent otherwise, and from older hosts. */
|
|
awaitingUser?: true
|
|
}
|
|
|
|
/**
|
|
* LIVE-ONLY: there is no snapshot arm and no replay arm, by decision. A subscriber is told what
|
|
* completes while it is subscribed and nothing else; completions that land while it is away are
|
|
* dropped rather than queued, so nothing durable can strand. On reconnect the client baselines.
|
|
*/
|
|
export type AgentSessionTurnCompletionEvent =
|
|
| { type: 'completion'; completion: AgentSessionTurnCompletion }
|
|
| { type: 'end' }
|
|
|
|
/** Delivery dedupe address. Unread is idempotent and does not need it; mobile fanout does. */
|
|
export function agentSessionTurnCompletionKey(completion: AgentSessionTurnCompletion): string {
|
|
return [agentSessionScopeKey(completion.scope), completion.sessionId, completion.turnId].join(
|
|
'\u0000'
|
|
)
|
|
}
|
|
|
|
// ─── Mutation envelope ──────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* The four fields every mutating call carries. Same operation id and same
|
|
* fingerprint replays the recorded outcome; a different fingerprint under one
|
|
* operation id is a conflict, never a second effect.
|
|
*/
|
|
export type AgentSessionMutationEnvelope = {
|
|
sessionId: string
|
|
clientOperationId: string
|
|
/** Null only on a create for a session that does not exist yet. */
|
|
expectedRuntimeFence: number | null
|
|
/** Client-declared; the host recomputes it and compares. */
|
|
payloadFingerprint: string
|
|
}
|
|
|
|
export type AgentSessionMutationResult<TValue> =
|
|
| {
|
|
ok: true
|
|
/** True when the recorded outcome was returned instead of a new effect. */
|
|
replayed: boolean
|
|
fence: number
|
|
cursor: AgentJournalCursor
|
|
value: TValue
|
|
}
|
|
| { ok: false; refusal: AgentSessionWireRefusal }
|
|
|
|
// ─── Per-method payloads ────────────────────────────────────────────────────
|
|
|
|
export type AgentSessionAttachResult = {
|
|
sessionId: string
|
|
fence: number
|
|
page: AgentSessionHistoryPage
|
|
/** Submissions a crash boundary left `unknown` that provider history could not decide. */
|
|
unconfirmedClientMessageIds: string[]
|
|
/** The host-owned id of the tab showing this chat, when it has one. Absent from older hosts. */
|
|
tabId?: string
|
|
}
|
|
|
|
/** The host queued the send as a draft instead of submitting it. Only clients
|
|
* that sent `delivery: 'queue-if-active'` — gated on
|
|
* `agent-session.queued-messages.v1` — ever receive this arm; `state` other
|
|
* than `waiting` appears only on replays of an already-settled draft. */
|
|
export type AgentSessionQueuedSendReceipt = {
|
|
messageId: string
|
|
position: number
|
|
state: 'waiting' | 'dispatched' | 'returned' | 'withdrawn'
|
|
}
|
|
|
|
export type AgentSessionSendResult =
|
|
| {
|
|
clientMessageId: string
|
|
submission: AgentJournalSubmission
|
|
}
|
|
| { clientMessageId: string; queued: AgentSessionQueuedSendReceipt }
|
|
|
|
/** The submission arm's payload; undefined for a queued answer. For callers that
|
|
* never send `delivery` the queued arm cannot arrive, and `undefined` reads as
|
|
* delivery-unknown rather than as an error. */
|
|
export function agentSessionSendSubmission(
|
|
result: AgentSessionSendResult | undefined
|
|
): AgentJournalSubmission | undefined {
|
|
return result !== undefined && 'submission' in result ? result.submission : undefined
|
|
}
|
|
|
|
export type AgentSessionCancelResult = {
|
|
/** The turn the client named, echoed so a late reply can be matched; absent when it named none. */
|
|
turnId?: string
|
|
cancelled: boolean
|
|
}
|
|
|
|
export type AgentSessionPromptResult = {
|
|
itemId: string
|
|
revision: number
|
|
resolution: AgentJournalResolution
|
|
}
|
|
|
|
export type AgentSessionOptionResult = {
|
|
key: string
|
|
value: string
|
|
/** Full effective next-turn values when the provider reconciled related options. */
|
|
options?: Record<string, string>
|
|
}
|
|
|
|
export type AgentSessionOptionChoice = {
|
|
value: string
|
|
label: string
|
|
description?: string
|
|
}
|
|
|
|
export type AgentSessionModelOption = {
|
|
id: string
|
|
label: string
|
|
description?: string
|
|
isDefault: boolean
|
|
defaultEffort?: string
|
|
efforts: AgentSessionOptionChoice[]
|
|
/** Provider catalog fact. Absent means the host could not determine support. */
|
|
supportsFastMode?: boolean
|
|
}
|
|
|
|
export type AgentSessionFastModeState = 'off' | 'cooldown' | 'on'
|
|
|
|
export type AgentSessionFastModeSupport = {
|
|
supported: boolean
|
|
/** Provider-authored or host-normalized reason code; presentation may ignore unknown values. */
|
|
reason?: string
|
|
}
|
|
|
|
/**
|
|
* The host's model catalog for an agent, answered from its own store and
|
|
* never through a session's queue. `unknown` means this host has no listing
|
|
* for the key yet — the client keeps its static seed. Additive read-only
|
|
* surface: an older host simply lacks the method.
|
|
*/
|
|
export type AgentSessionModelCatalogResult =
|
|
| {
|
|
origin: 'unknown'
|
|
/** The host is running its first listing for this account; a `waitForListing` read answers
|
|
* when it lands. Absent from a host that predates it. */
|
|
listingInProgress?: true
|
|
}
|
|
| {
|
|
/** What produced the listing; any age is served, `fetchedAt` carries it. */
|
|
origin: 'live-session' | 'probe'
|
|
models: AgentSessionModelOption[]
|
|
fastModeSupport?: AgentSessionFastModeSupport
|
|
fetchedAt: number
|
|
}
|
|
|
|
/** One entry of the `/` menu the running provider reports for itself. `skill`
|
|
* marks a name the session loaded as a skill rather than a built-in command;
|
|
* commands the provider reserves for a terminal UI are already removed. */
|
|
export type AgentSessionSlashCommand = {
|
|
name: string
|
|
kind: 'command' | 'skill'
|
|
/** Membership is authoritative, but this provider report did not classify the name. */
|
|
kindUnspecified?: true
|
|
/** Provider-authored row text; absent when the report carried names only. */
|
|
description?: string
|
|
/** Provider-authored argument sketch, e.g. `<issue-url>`. */
|
|
argumentHint?: string
|
|
}
|
|
|
|
/** The provider's own command surface, read per session. Additive read-only
|
|
* surface: a host that predates it answers `method_not_found`, and the client
|
|
* keeps rendering its curated catalog. */
|
|
export type AgentSessionCommandsResult = {
|
|
commands?: AgentSessionSlashCommand[]
|
|
}
|
|
|
|
/** Longest objective a client may send; matches the provider's own limit. */
|
|
export const AGENT_SESSION_THREAD_GOAL_OBJECTIVE_MAX_LENGTH = 4000
|
|
|
|
/** A client's change to the thread goal. `set` replaces the objective and makes
|
|
* it active, which the provider pursues without a separate turn. */
|
|
export type AgentSessionThreadGoalChange =
|
|
| { kind: 'set'; objective: string }
|
|
| { kind: 'status'; status: 'active' | 'paused' }
|
|
| { kind: 'clear' }
|
|
|
|
export type AgentSessionThreadGoalResult = {
|
|
change: AgentSessionThreadGoalChange['kind']
|
|
}
|
|
|
|
/** Provider-reported choices and effective next-turn values. Additive read-only
|
|
* surface so older hosts can reject it without changing structured v1 writes. */
|
|
export type AgentSessionOptionsResult = {
|
|
rewind?: AgentSessionRewindSupport
|
|
conversationCommands?: readonly AgentSessionConversationCommand[]
|
|
/** Present only where this session can change its goal, so a host without
|
|
* `agentSession.threadGoal` never offers the controls. `current` is the
|
|
* latest goal the whole journal records, for a client whose loaded page
|
|
* starts after it. */
|
|
threadGoal?: { current: AgentJournalThreadGoal | null }
|
|
/** Present only where this session writes context facts to its turn rows.
|
|
* `current` is the newest of each part the whole journal records, for a
|
|
* client whose loaded page starts after the row that carries it. */
|
|
contextUsage?: { current: AgentSessionContextUsage }
|
|
models: AgentSessionModelOption[]
|
|
/** Session/account/transport support. Absent means unknown, never unsupported. */
|
|
fastModeSupport?: AgentSessionFastModeSupport
|
|
current: {
|
|
model: string
|
|
effort?: string
|
|
/** Canonical preference for the next turn. Explicit false is meaningful. */
|
|
fastMode?: boolean
|
|
/** Provider-reported effective routing, distinct from the next-turn preference. */
|
|
fastModeState?: AgentSessionFastModeState
|
|
/**
|
|
* Option ids whose value the provider reported back, not merely accepted.
|
|
* Optional: a host that predates it sends nothing and the client keeps
|
|
* treating the value as unconfirmed, which is what it was before.
|
|
*/
|
|
confirmed?: readonly string[]
|
|
}
|
|
}
|