@flowy-agent-store/client 0.1.0-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +5 -0
- package/dist/index.cjs +2750 -0
- package/dist/index.d.cts +1303 -0
- package/dist/index.d.mts +1303 -0
- package/dist/index.mjs +2725 -0
- package/package.json +41 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,1303 @@
|
|
|
1
|
+
import { AgentDetail, AgentRunInput, AgentSummary, AnswerDecisionInput, CancelRunInput, ClientCapabilities, ClientInfo, ConnectorCallResult, ConnectorCredential, ConnectorDetail, ConnectorProbeResult, ConnectorRegistration, ConnectorStatusView, ConnectorSummary, ContextUsage, ConversationCreateInput, ConversationEvent, ConversationMessage, ConversationMessagesPage, ConversationMessagesQuery, ConversationModelOptions, ConversationSendReceipt, ConversationUpdateInput, ConversationView, ExpertPack, ImportAssetList, ImportDetail, ImportRequest, ImportResult, ImportSummary, InitializeResult, InstallOutcome, InstallRequest, InstallResult, InstallStatus, MarketSettings, MarketSettingsPatch, MarketplaceAddRequest, MarketplaceDetail, MarketplaceRefreshResult, MarketplaceRemoveResult, MarketplaceSummary, MentionRef, ModelSummary, OAuthStartResult, OAuthStatusView, ProviderWithModel, RunEvent, RunEventsQuery, RunPlan, RunReceipt, RunResult, RunStatus, RunView, ServerNotification, SkillDetail, SkillFileContent, SkillFileList, SkillSummary, SteerRunInput, StoreInstallResult, StoreItem, StoreItemKind, StoreList, TeamDetail, TeamRunInput, TeamRunReceipt, TeamSummary, WorkspaceRevokeResult, WorkspaceView } from "@flowy-agent-store/protocol";
|
|
2
|
+
//#region src/transport.d.ts
|
|
3
|
+
export type NotificationListener = (notification: ServerNotification) => void;
|
|
4
|
+
/**
|
|
5
|
+
* True for loopback-only URLs (`127.0.0.0/8`, `::1`, `localhost`).
|
|
6
|
+
* SDK spawn contract (docs/agent-store/12-sdk-packaging.md P0-4): an SDK
|
|
7
|
+
* must only speak the local-trusted mode over loopback; anything else is
|
|
8
|
+
* rejected before connecting.
|
|
9
|
+
*/
|
|
10
|
+
export declare function isLoopbackUrl(raw: string): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Transport abstraction (docs/agent-store/07-typescript-sdk.md §2.3).
|
|
13
|
+
* A transport only frames JSON-RPC over some channel; the
|
|
14
|
+
* initialize → initialized → ready lifecycle lives in `AppServerClient`,
|
|
15
|
+
* so business code never knows whether it runs over WebSocket (browser),
|
|
16
|
+
* stdio (Node/CLI, future) or a one-shot HTTP binding.
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* Lifecycle of the underlying channel. `open` is emitted on every successful
|
|
20
|
+
* dial (the first one included), `closed` only when an established connection
|
|
21
|
+
* is lost — so `closed` → `open` is exactly a reconnect.
|
|
22
|
+
*/
|
|
23
|
+
export type TransportLifecycle = "open" | "closed";
|
|
24
|
+
export type TransportLifecycleListener = (state: TransportLifecycle) => void;
|
|
25
|
+
export interface Transport {
|
|
26
|
+
connect(): Promise<void>;
|
|
27
|
+
request<T>(method: string, params: unknown): Promise<T>;
|
|
28
|
+
notify(method: string, params: unknown): void;
|
|
29
|
+
onNotification(listener: NotificationListener): () => void;
|
|
30
|
+
close(): void;
|
|
31
|
+
/**
|
|
32
|
+
* Optional: observe the channel lifecycle so a host can show a disconnect
|
|
33
|
+
* banner and re-arm subscriptions on reconnect (docs/agent-store/16 T8/W8).
|
|
34
|
+
* Custom transports may omit it.
|
|
35
|
+
*/
|
|
36
|
+
onLifecycle?(listener: TransportLifecycleListener): () => void;
|
|
37
|
+
}
|
|
38
|
+
export interface TransportOptions {
|
|
39
|
+
requestTimeoutMs?: number;
|
|
40
|
+
/** Max time `connect()` may stay unopened before failing (default 10s). */
|
|
41
|
+
connectTimeoutMs?: number;
|
|
42
|
+
token?: string;
|
|
43
|
+
}
|
|
44
|
+
export declare class WebSocketTransport implements Transport {
|
|
45
|
+
private socket;
|
|
46
|
+
private nextId;
|
|
47
|
+
private pending;
|
|
48
|
+
private listeners;
|
|
49
|
+
private lifecycleListeners;
|
|
50
|
+
private lifecycle;
|
|
51
|
+
private closedByCaller;
|
|
52
|
+
/** Single in-flight connect shared by concurrent callers; null once settled. */
|
|
53
|
+
private connecting;
|
|
54
|
+
readonly requestTimeoutMs: number;
|
|
55
|
+
readonly connectTimeoutMs: number;
|
|
56
|
+
readonly url: string;
|
|
57
|
+
readonly token?: string;
|
|
58
|
+
constructor(url: string, options?: TransportOptions);
|
|
59
|
+
get connected(): boolean;
|
|
60
|
+
connect(): Promise<void>;
|
|
61
|
+
request<T>(method: string, params: unknown): Promise<T>;
|
|
62
|
+
notify(method: string, params: unknown): void;
|
|
63
|
+
onNotification(listener: NotificationListener): () => void;
|
|
64
|
+
/**
|
|
65
|
+
* Observe the channel lifecycle. `closed` is only reported when an
|
|
66
|
+
* established connection is lost (a failed first dial is not a disconnect),
|
|
67
|
+
* and `open` follows it on the next successful dial — that pair is what a
|
|
68
|
+
* host should treat as a reconnect. Unlike `onNotification`, these listeners
|
|
69
|
+
* survive `close()`, since they exist to drive the reconnection itself.
|
|
70
|
+
*/
|
|
71
|
+
onLifecycle(listener: TransportLifecycleListener): () => void;
|
|
72
|
+
close(): void;
|
|
73
|
+
private setLifecycle;
|
|
74
|
+
/** Detach the current socket (if any) and close it; its later events are ignored. */
|
|
75
|
+
private detachSocket;
|
|
76
|
+
/** Settle the in-flight `connect()` successfully, when there is one. */
|
|
77
|
+
private resolveConnect;
|
|
78
|
+
/** Fail the in-flight `connect()`, when there is one. */
|
|
79
|
+
private rejectConnect;
|
|
80
|
+
private requireSocket;
|
|
81
|
+
private handleMessage;
|
|
82
|
+
private failAllPending;
|
|
83
|
+
}
|
|
84
|
+
//#endregion
|
|
85
|
+
//#region src/http-transport.d.ts
|
|
86
|
+
type Verb = "GET" | "POST" | "DELETE";
|
|
87
|
+
interface HttpRoute {
|
|
88
|
+
verb: Verb;
|
|
89
|
+
/** `:name` segments are filled from the same-named param and dropped from the body/query. */
|
|
90
|
+
path: string;
|
|
91
|
+
/** Params carried in the query string (GET, and the query-configured POSTs). */
|
|
92
|
+
query?: readonly string[];
|
|
93
|
+
/** Params carried in the JSON body; every other param goes here for POST. */
|
|
94
|
+
body?: readonly string[];
|
|
95
|
+
/** Where this mapping was verified in the server source. */
|
|
96
|
+
source: string;
|
|
97
|
+
}
|
|
98
|
+
export interface HttpTransportOptions {
|
|
99
|
+
/**
|
|
100
|
+
* App Server base URL. Either the full `…/api/app-server` root or a
|
|
101
|
+
* `ws(s)://…/api/app-server/ws` endpoint (the `/ws` segment is stripped).
|
|
102
|
+
*/
|
|
103
|
+
baseUrl: string;
|
|
104
|
+
/** Bearer token for the handshake (optional; hosts may inject auth instead). */
|
|
105
|
+
token?: string;
|
|
106
|
+
/** Injected `fetch` (defaults to the global); tests pass a stub. */
|
|
107
|
+
fetch?: typeof fetch;
|
|
108
|
+
/** Per-call budget covering handshake + business call (default 30s). */
|
|
109
|
+
requestTimeoutMs?: number;
|
|
110
|
+
/** Client name/version announced on `initialize`. */
|
|
111
|
+
client?: {
|
|
112
|
+
name: string;
|
|
113
|
+
version: string;
|
|
114
|
+
};
|
|
115
|
+
/** Client capabilities block echoed on `initialize` (hosts pass their own). */
|
|
116
|
+
capabilities?: unknown;
|
|
117
|
+
}
|
|
118
|
+
export declare class HttpTransport implements Transport {
|
|
119
|
+
private readonly routes;
|
|
120
|
+
private readonly options;
|
|
121
|
+
private closed;
|
|
122
|
+
constructor(options: HttpTransportOptions, routes?: Record<string, HttpRoute>);
|
|
123
|
+
/**
|
|
124
|
+
* No-op: the HTTP binding handshakes per call (`05` §2.1.1), so there is no
|
|
125
|
+
* connection to open. Kept for `Transport` compatibility.
|
|
126
|
+
*/
|
|
127
|
+
connect(): Promise<void>;
|
|
128
|
+
/**
|
|
129
|
+
* Perform one business call over its own handshake.
|
|
130
|
+
*
|
|
131
|
+
* Throws `TransportError("send", …)` for a method with no HTTP binding —
|
|
132
|
+
* never a silent fallback to another binding.
|
|
133
|
+
*/
|
|
134
|
+
request<T>(method: string, params: unknown): Promise<T>;
|
|
135
|
+
/**
|
|
136
|
+
* Not supported: HTTP carries no server-push. Throws instead of pretending.
|
|
137
|
+
*/
|
|
138
|
+
notify(method: string, _params: unknown): void;
|
|
139
|
+
/**
|
|
140
|
+
* Returns a no-op unsubscribe: this binding receives **no** notifications.
|
|
141
|
+
*
|
|
142
|
+
* Not equivalent to the WebSocket binding — callers that need live events
|
|
143
|
+
* must use `WebSocketTransport` (or run both).
|
|
144
|
+
*/
|
|
145
|
+
onNotification(_listener: NotificationListener): () => void;
|
|
146
|
+
/** No-op: every call is independent, so nothing is held open. */
|
|
147
|
+
close(): void;
|
|
148
|
+
/**
|
|
149
|
+
* One-shot handshake: `POST /initialize` (connection id header) →
|
|
150
|
+
* `POST /initialized`. Returns the connection id.
|
|
151
|
+
*
|
|
152
|
+
* Public because host-side services that sit *outside* the protocol surface
|
|
153
|
+
* (`/api/fs/*`, the HTTP-only `POST /workspaces` register route) still need a
|
|
154
|
+
* ready connection id; they are not protocol methods, so they cannot go
|
|
155
|
+
* through `request()`.
|
|
156
|
+
*/
|
|
157
|
+
openConnection(): Promise<string>;
|
|
158
|
+
private splitParams;
|
|
159
|
+
private fetchWithTimeout;
|
|
160
|
+
/** 2xx → result body; otherwise the server's wire error becomes an `AppServerError`. */
|
|
161
|
+
private readResult;
|
|
162
|
+
private baseUrl;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Turn an HTTP failure body into the SDK error model.
|
|
166
|
+
*
|
|
167
|
+
* Shared by this transport and by host-side helpers (webui `lib/client.ts`) so
|
|
168
|
+
* a second spelling of the wire-error mapping cannot drift. Two wire contracts
|
|
169
|
+
* reach this function, both of them `AppError` on the server:
|
|
170
|
+
*
|
|
171
|
+
* - the App Server emits `{code, message, retryable, details, request_id}`
|
|
172
|
+
* (`into_wire_error`), which is exactly the `AppServerError` shape;
|
|
173
|
+
* - the standalone host file service (`/api/fs/*`) emits the `ErrorResponse`
|
|
174
|
+
* envelope `{success: false, error, code, details?}` — same error, another
|
|
175
|
+
* spelling: `error` carries the human message, and there is no retry hint, so
|
|
176
|
+
* a 5xx status stays the retry signal.
|
|
177
|
+
*
|
|
178
|
+
* Anything else becomes a retryable-on-5xx `TransportError`.
|
|
179
|
+
*/
|
|
180
|
+
export declare function appServerErrorFromWire(payload: unknown, status: number, context?: string): unknown;
|
|
181
|
+
/** Exposed for tests and docs (R5): the verified method → route table. */
|
|
182
|
+
export declare function httpRouteTable(): Readonly<Record<string, {
|
|
183
|
+
verb: string;
|
|
184
|
+
path: string;
|
|
185
|
+
source: string;
|
|
186
|
+
}>>;
|
|
187
|
+
//#endregion
|
|
188
|
+
//#region src/agents.d.ts
|
|
189
|
+
export declare class AgentClient {
|
|
190
|
+
private readonly transport;
|
|
191
|
+
constructor(transport: Transport);
|
|
192
|
+
/** List Agent Store AgentDefinitions (public summaries only). */
|
|
193
|
+
list(): Promise<AgentSummary[]>;
|
|
194
|
+
/** Fetch one AgentDefinition; raw prompts never cross the wire. */
|
|
195
|
+
get(agentId: string): Promise<AgentDetail>;
|
|
196
|
+
/**
|
|
197
|
+
* Export one AgentDefinition as a portable {@link ExpertPack}.
|
|
198
|
+
*
|
|
199
|
+
* Requires WebSocket: like the rest of this family, `agent/export` has no HTTP
|
|
200
|
+
* route. The expert must be installed and enabled, or the host answers
|
|
201
|
+
* `agent_not_installed` / `agent_disabled`; a host whose `[expert_export]`
|
|
202
|
+
* table refuses the id answers `policy_denied`. Check
|
|
203
|
+
* `capabilities.expert_export` for the *seam* — it does not promise this id is
|
|
204
|
+
* exportable.
|
|
205
|
+
*/
|
|
206
|
+
export(agentId: string): Promise<ExpertPack>;
|
|
207
|
+
}
|
|
208
|
+
//#endregion
|
|
209
|
+
//#region src/conversations.d.ts
|
|
210
|
+
/**
|
|
211
|
+
* Options for `send()`.
|
|
212
|
+
*
|
|
213
|
+
* Two scopes, deliberately different (doc `27` §4.1 and doc `29` §5.1):
|
|
214
|
+
*
|
|
215
|
+
* - **per turn** — `attachments` and `mentions` are this turn's payload, and
|
|
216
|
+
* nothing else changes;
|
|
217
|
+
* - **per conversation, taking effect this turn** — `model` and
|
|
218
|
+
* `reasoningEffort` are *sticky*: they are written to the conversation and
|
|
219
|
+
* every later turn keeps them, because the Nomi runtime is built from the
|
|
220
|
+
* persisted row. The host refuses them while a turn is running (`conflict`).
|
|
221
|
+
*
|
|
222
|
+
* Every field is **additive**: absent means the exact wire shape callers sent
|
|
223
|
+
* before that field existed, and a plain `send()` never writes the conversation.
|
|
224
|
+
*/
|
|
225
|
+
export interface ConversationSendOptions {
|
|
226
|
+
/** Absolute paths inside the conversation's workspace (images only). */
|
|
227
|
+
attachments?: string[];
|
|
228
|
+
/**
|
|
229
|
+
* Skills to mount for **this turn** (`kind: "skill"`).
|
|
230
|
+
*
|
|
231
|
+
* The ids are `skill/list`'s ids (the skill's own name, not an
|
|
232
|
+
* `install/status` component id). A turn is the only scope a Skill has here:
|
|
233
|
+
* the conversation's frozen snapshot is create-time and immutable.
|
|
234
|
+
*/
|
|
235
|
+
mentions?: MentionRef[];
|
|
236
|
+
/**
|
|
237
|
+
* Model to switch the conversation to **from this turn on** (doc `29` §5.1).
|
|
238
|
+
*
|
|
239
|
+
* Sticky, not per-turn: the Nomi runtime is built from the persisted row, so
|
|
240
|
+
* the value is written to the conversation and every later turn keeps it.
|
|
241
|
+
* Omit it and the conversation's current model stays untouched.
|
|
242
|
+
*/
|
|
243
|
+
model?: ProviderWithModel;
|
|
244
|
+
/**
|
|
245
|
+
* Reasoning level to switch the conversation to **from this turn on**
|
|
246
|
+
* (doc `29` §5.1). Same sticky scope as `model`.
|
|
247
|
+
*/
|
|
248
|
+
reasoningEffort?: string;
|
|
249
|
+
}
|
|
250
|
+
export declare class ConversationClient {
|
|
251
|
+
private readonly transport;
|
|
252
|
+
constructor(transport: Transport);
|
|
253
|
+
create(input: ConversationCreateInput): Promise<ConversationView>;
|
|
254
|
+
update(conversationId: string, input: ConversationUpdateInput): Promise<ConversationView>;
|
|
255
|
+
modelOptions(): Promise<ConversationModelOptions>;
|
|
256
|
+
list(limit?: number): Promise<ConversationView[]>;
|
|
257
|
+
get(conversationId: string): Promise<ConversationView>;
|
|
258
|
+
messages(query: ConversationMessagesQuery): Promise<ConversationMessagesPage>;
|
|
259
|
+
/**
|
|
260
|
+
* R15(W10):`attachments` 是**会话工作区内的绝对路径**(图片附件)。
|
|
261
|
+
* doc `27` §4.1:`mentions` 是**本轮挂载的技能**(`kind: "skill"`)——技能的正文与
|
|
262
|
+
* 不可变快照都随这一轮走;`agent` / `connector` 在 send 上没有载体,服务端会以
|
|
263
|
+
* `invalid_request` **明确拒绝**(不是静默不挂)。
|
|
264
|
+
*
|
|
265
|
+
* 两者缺省都不上 wire,保持老调用方的形状逐字不变;服务端只接受会话工作区内的真实
|
|
266
|
+
* 文件(越界 / 相对路径 / 不存在一律拒),准入见 `nomifun-app-server` 的
|
|
267
|
+
* `resolve_conversation_attachments` 与 `send_mention_skills`。
|
|
268
|
+
*
|
|
269
|
+
* 第 4 个参数同时接受旧的 `string[]`(附件)与选项对象——再堆一个位置参数会让调用点
|
|
270
|
+
* 变成一串没有名字的布尔/数组。
|
|
271
|
+
*/
|
|
272
|
+
send(conversationId: string, content: string, idempotencyKey: string, options?: string[] | ConversationSendOptions): Promise<ConversationSendReceipt>;
|
|
273
|
+
cancel(conversationId: string): Promise<ConversationView>;
|
|
274
|
+
delete(conversationId: string): Promise<{
|
|
275
|
+
conversation_id: string;
|
|
276
|
+
deleted: boolean;
|
|
277
|
+
}>;
|
|
278
|
+
/**
|
|
279
|
+
* Subscribe to one conversation's realtime stream (doc `16` R1).
|
|
280
|
+
*
|
|
281
|
+
* `options.fetchMessages` is what makes auto catch-up possible: a detected
|
|
282
|
+
* sequence gap or a `conversation/resync-required` triggers one transcript
|
|
283
|
+
* fetch whose result is handed to `onBackfill`. Conversations have no event
|
|
284
|
+
* replay API (`05` §「V1 不提供 after_cursor 事件追平」), so the transcript is
|
|
285
|
+
* the recovery medium — unlike Runs, where `run/events` replays the events
|
|
286
|
+
* themselves.
|
|
287
|
+
*/
|
|
288
|
+
follow(conversationId: string, options?: ConversationFollowOptions): Promise<ConversationSubscription>;
|
|
289
|
+
}
|
|
290
|
+
export type ConversationEventListener = (event: ConversationEvent) => void;
|
|
291
|
+
export type ConversationResyncListener = (reason: string) => void;
|
|
292
|
+
export type ConversationErrorListener = (error: unknown) => void;
|
|
293
|
+
/** Transcript snapshot handed back after a detected gap / resync (R1). */
|
|
294
|
+
export interface ConversationBackfill {
|
|
295
|
+
conversationId: string;
|
|
296
|
+
/** `gap` or the server's `conversation/resync-required` reason. */
|
|
297
|
+
reason: string;
|
|
298
|
+
/** Latest transcript page, newest last (`conversation/messages`). */
|
|
299
|
+
messages: ConversationMessage[];
|
|
300
|
+
/** Server-computed: an older page still exists. */
|
|
301
|
+
hasMore: boolean;
|
|
302
|
+
}
|
|
303
|
+
export type ConversationBackfillListener = (backfill: ConversationBackfill) => void;
|
|
304
|
+
/** Options for `follow()` — mirrors the Run subscription's `FollowOptions`. */
|
|
305
|
+
export interface ConversationFollowOptions {
|
|
306
|
+
/**
|
|
307
|
+
* Fetch the transcript automatically after a gap / resync (default `true`).
|
|
308
|
+
* Needs `fetchMessages`; when absent only `onResync` fires.
|
|
309
|
+
*/
|
|
310
|
+
autoResync?: boolean;
|
|
311
|
+
/** Events held before the first `onEvent` listener attaches (default 256). */
|
|
312
|
+
pendingLimit?: number;
|
|
313
|
+
/** Transcript fetcher; `follow()` wires the real one. */
|
|
314
|
+
fetchMessages?: (conversationId: string) => Promise<ConversationMessagesPage>;
|
|
315
|
+
}
|
|
316
|
+
export declare class ConversationSubscription {
|
|
317
|
+
private readonly transport;
|
|
318
|
+
readonly conversationId: string;
|
|
319
|
+
private readonly options;
|
|
320
|
+
private events;
|
|
321
|
+
private resyncs;
|
|
322
|
+
private errors;
|
|
323
|
+
private backfills;
|
|
324
|
+
private closed;
|
|
325
|
+
private lastSeenSequence;
|
|
326
|
+
private removeListener;
|
|
327
|
+
/**
|
|
328
|
+
* `follow()` resolves as soon as the server acknowledged the subscription,
|
|
329
|
+
* but the caller attaches listeners one tick later. Events landing in that
|
|
330
|
+
* window used to be dropped silently; they are held here (oldest first) and
|
|
331
|
+
* flushed to the first `onEvent` listener.
|
|
332
|
+
*/
|
|
333
|
+
private pending;
|
|
334
|
+
private pendingDropped;
|
|
335
|
+
private backfillInFlight;
|
|
336
|
+
constructor(transport: Transport, conversationId: string, options?: ConversationFollowOptions);
|
|
337
|
+
get lastSequence(): number;
|
|
338
|
+
/** Events dropped because nobody was listening and the buffer was full. */
|
|
339
|
+
get droppedPendingCount(): number;
|
|
340
|
+
onEvent(listener: ConversationEventListener): () => void;
|
|
341
|
+
onResync(listener: ConversationResyncListener): () => void;
|
|
342
|
+
/**
|
|
343
|
+
* Auto catch-up result (R1). Fires only when `autoResync` is on and the
|
|
344
|
+
* transcript fetch succeeded; the listener owns replacing the transcript.
|
|
345
|
+
*/
|
|
346
|
+
onBackfill(listener: ConversationBackfillListener): () => void;
|
|
347
|
+
/**
|
|
348
|
+
* Failures that used to be swallowed: the catch-up fetch, and a failed
|
|
349
|
+
* re-subscribe during `rearm()`. Listener isolation keeps one bad listener
|
|
350
|
+
* from hiding the error from the others.
|
|
351
|
+
*/
|
|
352
|
+
onError(listener: ConversationErrorListener): () => void;
|
|
353
|
+
close(): Promise<void>;
|
|
354
|
+
/**
|
|
355
|
+
* Re-arm after a reconnect (docs/agent-store/16 T8): re-register the
|
|
356
|
+
* notification listener, reset the cursor and re-issue
|
|
357
|
+
* `conversation/subscribe`. The server restarts a conversation's sequence at
|
|
358
|
+
* 1 for a fresh subscription, which is exactly why the cursor resets. On a
|
|
359
|
+
* failed re-subscribe the error reaches `onError` and the caller can still
|
|
360
|
+
* backfill the outage window via `conversation/messages`.
|
|
361
|
+
*/
|
|
362
|
+
rearm(): Promise<void>;
|
|
363
|
+
private dispatch;
|
|
364
|
+
private buffer;
|
|
365
|
+
private deliver;
|
|
366
|
+
private emitResync;
|
|
367
|
+
private emitError;
|
|
368
|
+
/**
|
|
369
|
+
* One in-flight catch-up at a time: a burst of resync signals collapses into
|
|
370
|
+
* a single transcript fetch (the fetch itself returns the newest page, so the
|
|
371
|
+
* result supersedes every signal that arrived before it).
|
|
372
|
+
*/
|
|
373
|
+
private requestBackfill;
|
|
374
|
+
}
|
|
375
|
+
//#endregion
|
|
376
|
+
//#region src/connectors.d.ts
|
|
377
|
+
export interface WaitForAuthOptions {
|
|
378
|
+
/** Overall budget in ms; defaults to `DEFAULT_AUTH_TIMEOUT_MS` (120s). */
|
|
379
|
+
timeoutMs?: number;
|
|
380
|
+
/** Poll interval in ms; defaults to 500. The read is a local DB projection. */
|
|
381
|
+
pollMs?: number;
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* How a completed wait ended.
|
|
385
|
+
*
|
|
386
|
+
* `error` carries the host's own sentence. It is the shape of every failure that
|
|
387
|
+
* happens *after* `authStart` acknowledged the browser step — the state stays
|
|
388
|
+
* `not_authenticated` (there is no "failed" state on the wire), so the reason is
|
|
389
|
+
* the only signal there is.
|
|
390
|
+
*/
|
|
391
|
+
export type WaitForAuthOutcome = {
|
|
392
|
+
state: "authenticated";
|
|
393
|
+
} | {
|
|
394
|
+
state: "error";
|
|
395
|
+
error: string;
|
|
396
|
+
} | {
|
|
397
|
+
state: "timeout";
|
|
398
|
+
};
|
|
399
|
+
export declare class ConnectorClient {
|
|
400
|
+
private readonly transport;
|
|
401
|
+
constructor(transport: Transport);
|
|
402
|
+
/** List the Agent Store Connector catalog (public summaries only). */
|
|
403
|
+
list(): Promise<ConnectorSummary[]>;
|
|
404
|
+
/** Fetch one Connector detail: namespaced tools + auth state. */
|
|
405
|
+
get(connectorId: string): Promise<ConnectorDetail>;
|
|
406
|
+
/** Combined auth × probe status. `connected` only when auth is ready AND the last probe succeeded. */
|
|
407
|
+
status(connectorId: string): Promise<ConnectorStatusView>;
|
|
408
|
+
/**
|
|
409
|
+
* Run a connection probe and report what the connector currently offers —
|
|
410
|
+
* including each tool's `input_schema`.
|
|
411
|
+
*
|
|
412
|
+
* It really connects: a stdio connector gets its child process spawned, an
|
|
413
|
+
* HTTP/SSE one gets `initialize` + `tools/list`. The result is **persisted**,
|
|
414
|
+
* so this is also what refreshes the tool list `get()` reads back.
|
|
415
|
+
*
|
|
416
|
+
* Named `test` and not `probe` on purpose: this repo's split is `test` for the
|
|
417
|
+
* action (wire `connector/test`, `test_connection`) and `probe` for its
|
|
418
|
+
* artifact (`ConnectorProbeResult`, `probe_status`), and every method here is
|
|
419
|
+
* the last segment of its wire method.
|
|
420
|
+
*/
|
|
421
|
+
test(connectorId: string): Promise<ConnectorProbeResult>;
|
|
422
|
+
/**
|
|
423
|
+
* Check the current OAuth state for a connector.
|
|
424
|
+
*
|
|
425
|
+
* Read `error` too: a flow that failed after the browser step is reported as
|
|
426
|
+
* `state: "not_authenticated"` **plus** `error`, because the wire has no
|
|
427
|
+
* failing state to flip to. {@link waitForAuth} is the loop that does this.
|
|
428
|
+
*/
|
|
429
|
+
authStatus(connectorId: string): Promise<OAuthStatusView>;
|
|
430
|
+
/**
|
|
431
|
+
* The credential form a connector needs filled in, as **this caller** sees it
|
|
432
|
+
* (doc `34` §6.1).
|
|
433
|
+
*
|
|
434
|
+
* `mode` says which kind of authentication applies (`none` / `oauth` / `token`),
|
|
435
|
+
* `status` whether anything is still missing, and `missing` names the keys.
|
|
436
|
+
* `fields` is everything needed to render the form — labels, placeholders,
|
|
437
|
+
* descriptions and the marketplace's "where do I get a key" link, in both
|
|
438
|
+
* languages. No secret ever comes back: only `plain` fields carry a `value`.
|
|
439
|
+
*/
|
|
440
|
+
credentials(connectorId: string): Promise<ConnectorCredential>;
|
|
441
|
+
/**
|
|
442
|
+
* Store what the user typed and get the new state back.
|
|
443
|
+
*
|
|
444
|
+
* Only keys the connector's own declaration names are accepted — the form is
|
|
445
|
+
* the whole write surface. `secret` fields go to the host's credential store
|
|
446
|
+
* under the caller's own namespace, `plain` fields into the connector's
|
|
447
|
+
* configuration, so a shared host never resolves one user's token for another.
|
|
448
|
+
*/
|
|
449
|
+
setCredentials(connectorId: string, values: Record<string, string>): Promise<ConnectorCredential>;
|
|
450
|
+
/**
|
|
451
|
+
* Forget stored credentials. `keys` omitted = every secret field of this
|
|
452
|
+
* connector. Idempotent: clearing what is not there succeeds.
|
|
453
|
+
*/
|
|
454
|
+
clearCredentials(connectorId: string, keys?: string[]): Promise<ConnectorCredential>;
|
|
455
|
+
/**
|
|
456
|
+
* Register a connector this host never imported — your own MCP server, handed
|
|
457
|
+
* over together with the template that says what it needs (doc `34` §6.5).
|
|
458
|
+
*
|
|
459
|
+
* The **template is the whole declaration**: whatever `${secret:NAME}` the URL,
|
|
460
|
+
* headers or env name becomes that connector's credential form, so there is no
|
|
461
|
+
* `token-schema.json` to ship and nothing to install from a marketplace. The
|
|
462
|
+
* connector's own non-secret settings travel in `values`.
|
|
463
|
+
*
|
|
464
|
+
* Secrets do **not** travel here: hand them over with {@link setCredentials},
|
|
465
|
+
* which keeps exactly one write surface for values. The returned detail already
|
|
466
|
+
* carries the credential block, so `missing` tells you which keys are still
|
|
467
|
+
* needed.
|
|
468
|
+
*
|
|
469
|
+
* Re-registering the same name updates it; a builtin server's name is refused.
|
|
470
|
+
* The row is created **disabled** — enabling it still requires a probe that
|
|
471
|
+
* passes — and this method is on the installation-owner-only surface, because
|
|
472
|
+
* choosing where the host reaches is the owner's call.
|
|
473
|
+
*/
|
|
474
|
+
register(registration: ConnectorRegistration): Promise<ConnectorDetail>;
|
|
475
|
+
/**
|
|
476
|
+
* Kick off the OAuth browser flow on the trusted host. Returns the start
|
|
477
|
+
* acknowledgement immediately; follow it with {@link waitForAuth}.
|
|
478
|
+
*
|
|
479
|
+
* `state: "error"` here is a failure that happened **before** the browser was
|
|
480
|
+
* opened (endpoint discovery, client identity, binding the callback, launching
|
|
481
|
+
* the browser): nothing was shown to the user, so `error` is the whole story
|
|
482
|
+
* and there is nothing to wait for.
|
|
483
|
+
*/
|
|
484
|
+
authStart(connectorId: string): Promise<OAuthStartResult>;
|
|
485
|
+
/**
|
|
486
|
+
* Wait for an authorization that was started (in the browser, or by a
|
|
487
|
+
* previous `authStart`) to finish — and **report why** when it does not.
|
|
488
|
+
*
|
|
489
|
+
* A hand-rolled `while (state !== "authenticated") poll()` loop ends in "it
|
|
490
|
+
* never became authenticated", with the reason sitting unread in the status it
|
|
491
|
+
* already fetched: a token exchange the authorization server refused (a
|
|
492
|
+
* throttling gateway answers `slow_down`), a callback timeout, a CSRF/path
|
|
493
|
+
* mismatch. Those land in `authStatus().error` *only*, and the state stays
|
|
494
|
+
* `not_authenticated` — there is no failing state on the wire.
|
|
495
|
+
*
|
|
496
|
+
* Resolves for every domain outcome (`authenticated` / `error` / `timeout`);
|
|
497
|
+
* rejects only when the status read itself fails (transport or protocol), the
|
|
498
|
+
* same rule as `connector/call`. The wait is bounded by `timeoutMs`; a caller
|
|
499
|
+
* that needs to stop earlier can race the returned promise.
|
|
500
|
+
*/
|
|
501
|
+
waitForAuth(connectorId: string, options?: WaitForAuthOptions): Promise<WaitForAuthOutcome>;
|
|
502
|
+
/** Revoke the OAuth token for a connector. */
|
|
503
|
+
logout(connectorId: string): Promise<void>;
|
|
504
|
+
/**
|
|
505
|
+
* Run one tool on a registered connector, through the host's own connection.
|
|
506
|
+
*
|
|
507
|
+
* The host holds the transport, its headers and its OAuth token; this sends a
|
|
508
|
+
* tool name and an argument object and nothing else — you cannot name a URL,
|
|
509
|
+
* a command or a header. Whether the pair is callable at all is the host's
|
|
510
|
+
* `[connector_proxy]` policy: once its operator turns the proxy on, the
|
|
511
|
+
* enabled connectors are callable, and `allow` / `deny` are that operator's
|
|
512
|
+
* optional narrowing and subtraction. So `policy_denied` means either that
|
|
513
|
+
* the host never turned the proxy on, or that this pair was narrowed out or
|
|
514
|
+
* explicitly denied — not that a tool name was misspelled in a list you were
|
|
515
|
+
* supposed to maintain.
|
|
516
|
+
*
|
|
517
|
+
* Arguments are passed through verbatim (there is no client-side schema
|
|
518
|
+
* check); `get()` / `test()` return each tool's `input_schema` so you can see
|
|
519
|
+
* what it takes, and a parameter the server rejects comes back as
|
|
520
|
+
* `is_error: true`, not as a rejected promise.
|
|
521
|
+
*
|
|
522
|
+
* Resolves even when the tool itself failed — check `is_error`. It rejects
|
|
523
|
+
* only when the call never reached the tool: `connector_call_timeout`,
|
|
524
|
+
* `connector_call_failed`, `response_too_large`, `connector_unavailable`,
|
|
525
|
+
* `policy_denied`, `not_found`.
|
|
526
|
+
*/
|
|
527
|
+
call(connectorId: string, tool: string, args?: unknown): Promise<ConnectorCallResult>;
|
|
528
|
+
}
|
|
529
|
+
//#endregion
|
|
530
|
+
//#region src/runs.d.ts
|
|
531
|
+
export declare class RunClient {
|
|
532
|
+
private readonly transport;
|
|
533
|
+
constructor(transport: Transport);
|
|
534
|
+
/** Start an Agent run and return its asynchronous receipt. */
|
|
535
|
+
agent(input: AgentRunInput): Promise<RunReceipt>;
|
|
536
|
+
/**
|
|
537
|
+
* Start a Team run (`16` §7 决策 3): the server creates the Leader
|
|
538
|
+
* Conversation, materializes/binds the Team's execution template and returns
|
|
539
|
+
* the public run id of the execution the Leader started.
|
|
540
|
+
*
|
|
541
|
+
* The receipt is `TeamRunReceipt`, not `RunReceipt` — a Team Run has no lead
|
|
542
|
+
* preset revision/digest to report. Member pool and concurrency limits are not
|
|
543
|
+
* inputs; sending `planning` is rejected as `invalid_request`.
|
|
544
|
+
*/
|
|
545
|
+
team(input: TeamRunInput): Promise<TeamRunReceipt>;
|
|
546
|
+
/** Read the authoritative run state. */
|
|
547
|
+
get(runId: string): Promise<RunView>;
|
|
548
|
+
/** Read the terminal result (fails until the run reaches a terminal state). */
|
|
549
|
+
result(runId: string): Promise<RunResult>;
|
|
550
|
+
/**
|
|
551
|
+
* Read the authoritative plan / step snapshot (W4 / W6, resolves D-W6-1):
|
|
552
|
+
* titles, statuses, retries with their reason, errors and timings — none of
|
|
553
|
+
* which the append-only event log carries.
|
|
554
|
+
*/
|
|
555
|
+
plan(runId: string): Promise<RunPlan>;
|
|
556
|
+
/** Replay persisted events after a cursor. */
|
|
557
|
+
events(query: RunEventsQuery): Promise<RunEvent[]>;
|
|
558
|
+
/** Request cancellation; the terminal state is confirmed by events/run/get. */
|
|
559
|
+
cancel(input: CancelRunInput): Promise<RunView>;
|
|
560
|
+
/**
|
|
561
|
+
* Inject steering text into the currently active step of a running Agent
|
|
562
|
+
* (runtime durable conversation effect; server resolves the opaque step).
|
|
563
|
+
*/
|
|
564
|
+
steer(input: SteerRunInput): Promise<RunView>;
|
|
565
|
+
/**
|
|
566
|
+
* Answer the pending decision of a `waiting_input` attempt.
|
|
567
|
+
*
|
|
568
|
+
* The three `expected*Version` tokens are mandatory CAS inputs, normally the
|
|
569
|
+
* ones projected on the pending `approval.requested` event. The server passes
|
|
570
|
+
* them straight to the engine's single answer gate, so a stale token is a
|
|
571
|
+
* `conflict`, never a silent overwrite. There is no `always_allow` here: the
|
|
572
|
+
* desktop confirmation route's approve-all flag is not part of this protocol.
|
|
573
|
+
*/
|
|
574
|
+
answerDecision(input: AnswerDecisionInput): Promise<RunView>;
|
|
575
|
+
/** Subscribe to best-effort realtime events for one run. */
|
|
576
|
+
follow(runId: string, options?: FollowOptions): Promise<EventSubscription>;
|
|
577
|
+
}
|
|
578
|
+
export interface FollowOptions {
|
|
579
|
+
/** Silent cursor catch-up after subscribe (default true). */
|
|
580
|
+
catchUp?: boolean;
|
|
581
|
+
/** Automatic `run/events`追平 on `run/resync-required` (default true). */
|
|
582
|
+
autoResync?: boolean;
|
|
583
|
+
}
|
|
584
|
+
export type RunEventListener = (event: RunEvent) => void;
|
|
585
|
+
export type RunResyncListener = (params: {
|
|
586
|
+
run_ids: string[];
|
|
587
|
+
reason: string;
|
|
588
|
+
}) => void;
|
|
589
|
+
export type RunErrorListener = (error: unknown) => void;
|
|
590
|
+
export declare class EventSubscription {
|
|
591
|
+
private readonly transport;
|
|
592
|
+
readonly runId: string;
|
|
593
|
+
private readonly fetchAfter?;
|
|
594
|
+
private eventListeners;
|
|
595
|
+
private resyncListeners;
|
|
596
|
+
private errorListeners;
|
|
597
|
+
private closed;
|
|
598
|
+
private unsubscribeNotification;
|
|
599
|
+
private lastSeenSequence;
|
|
600
|
+
private seenSequences;
|
|
601
|
+
private resyncInFlight;
|
|
602
|
+
private readonly autoResync;
|
|
603
|
+
constructor(transport: Transport, runId: string, fetchAfter?: ((afterSequence: number) => Promise<RunEvent[]>) | undefined, options?: FollowOptions);
|
|
604
|
+
onEvent(listener: RunEventListener): () => void;
|
|
605
|
+
onResync(listener: RunResyncListener): () => void;
|
|
606
|
+
onError(listener: RunErrorListener): () => void;
|
|
607
|
+
get lastSequence(): number;
|
|
608
|
+
close(): Promise<void>;
|
|
609
|
+
private dispatch;
|
|
610
|
+
/**
|
|
611
|
+
* Silent cursor baseline: mark already-persisted events seen WITHOUT
|
|
612
|
+
* dispatching, so the first resync never replays history as "new".
|
|
613
|
+
* For explicit history use `RunClient.events({ afterSequence: 0 })`.
|
|
614
|
+
*/
|
|
615
|
+
catchUp(): Promise<void>;
|
|
616
|
+
/**
|
|
617
|
+
*追平 missed events via `run/events` after the cursor. Returned (and
|
|
618
|
+
* dispatched) in sequence order, deduplicated against live delivery.
|
|
619
|
+
* Concurrent calls share one flight.
|
|
620
|
+
*/
|
|
621
|
+
resync(): Promise<RunEvent[]>;
|
|
622
|
+
/**
|
|
623
|
+
* Re-arm after the connection was re-established (docs/agent-store/16 T8).
|
|
624
|
+
* A new socket means the server dropped its subscriptions and the transport
|
|
625
|
+
* dropped its notification listeners, so this:
|
|
626
|
+
*
|
|
627
|
+
* 1. re-registers this subscription's notification listener,
|
|
628
|
+
* 2. resets the cursor to 0 and forgets the dedupe set, then
|
|
629
|
+
* 3. re-issues `run/subscribe` and replays **all** persisted events.
|
|
630
|
+
*
|
|
631
|
+
* The replay is deliberate (the plan's "重连后重置 lastSeenSequence"): a
|
|
632
|
+
* consumer must dedupe by `sequence`, since events seen before the outage
|
|
633
|
+
* are delivered again. Call it only once the session handshake has completed
|
|
634
|
+
* again — the transport's `open` event fires before `initialize`. A no-op
|
|
635
|
+
* once closed.
|
|
636
|
+
*/
|
|
637
|
+
rearm(): Promise<RunEvent[]>;
|
|
638
|
+
private markSeen;
|
|
639
|
+
private emitEvent;
|
|
640
|
+
private emitError;
|
|
641
|
+
/** Forward transport-level errors into subscription error listeners. */
|
|
642
|
+
deliverError(error: unknown): void;
|
|
643
|
+
}
|
|
644
|
+
//#endregion
|
|
645
|
+
//#region src/skills.d.ts
|
|
646
|
+
export declare class SkillClient {
|
|
647
|
+
private readonly transport;
|
|
648
|
+
constructor(transport: Transport);
|
|
649
|
+
/** List the Agent Store Skill catalog (public summaries only). */
|
|
650
|
+
list(): Promise<SkillSummary[]>;
|
|
651
|
+
/** Fetch one Skill detail; instructions are a bounded public summary. */
|
|
652
|
+
get(skillId: string): Promise<SkillDetail>;
|
|
653
|
+
/**
|
|
654
|
+
* Every readable file inside a Skill's directory, with a tree digest.
|
|
655
|
+
*
|
|
656
|
+
* This is the only way to learn what a Skill actually ships: `get()` returns
|
|
657
|
+
* a bounded summary of `SKILL.md` and says nothing about the `references/`,
|
|
658
|
+
* `scripts/`, `templates/` and `assets/` beside it. Check
|
|
659
|
+
* `capabilities.skill_files` first — a host may wire the catalog without
|
|
660
|
+
* this face, in which case it answers `unsupported_operation`.
|
|
661
|
+
*/
|
|
662
|
+
files(skillId: string): Promise<SkillFileList>;
|
|
663
|
+
/**
|
|
664
|
+
* One skill file's bytes, over the WebSocket binding.
|
|
665
|
+
*
|
|
666
|
+
* The wire carries base64 because JSON has no byte string, so this decodes
|
|
667
|
+
* back to `Uint8Array` for you. The HTTP route
|
|
668
|
+
* (`GET /api/app-server/skills/{id}/files/{path}`) returns the raw body
|
|
669
|
+
* instead, but that is deliberately not on the typed client's JSON
|
|
670
|
+
* transport — use this method, or `fetch` the route directly if you are on a
|
|
671
|
+
* host token.
|
|
672
|
+
*/
|
|
673
|
+
readFile(skillId: string, path: string): Promise<Uint8Array>;
|
|
674
|
+
/** Same as {@link readFile}, but also returns the server's content type. */
|
|
675
|
+
readFileWithType(skillId: string, path: string): Promise<SkillFileContent>;
|
|
676
|
+
}
|
|
677
|
+
/**
|
|
678
|
+
* Decode base64 to bytes (shared with the snapshot asset face, doc 38).
|
|
679
|
+
*
|
|
680
|
+
* `atob` is used when present (browsers, Bun, Node 16+) and a manual decode
|
|
681
|
+
* otherwise, so the package keeps working on a bare Node without a DOM lib.
|
|
682
|
+
*/
|
|
683
|
+
export declare function decodeBase64(content: string): Uint8Array;
|
|
684
|
+
//#endregion
|
|
685
|
+
//#region src/store.d.ts
|
|
686
|
+
/** Why a `waitForReady` check ended without every component being usable. */
|
|
687
|
+
export type StoreReadyIssue =
|
|
688
|
+
/** The host never reported the component ready inside the timeout. */
|
|
689
|
+
"ready_timeout" |
|
|
690
|
+
/** A connector needs the OAuth browser flow; the caller must drive it. */
|
|
691
|
+
"authorization_required";
|
|
692
|
+
/** Branchable client-side failures. Server-side failures arrive as outcomes. */
|
|
693
|
+
export type StoreErrorCode = "not_installed" | "item_blocked" | "aborted";
|
|
694
|
+
export declare class StoreError extends Error {
|
|
695
|
+
readonly code: StoreErrorCode;
|
|
696
|
+
constructor(code: StoreErrorCode, message: string);
|
|
697
|
+
}
|
|
698
|
+
export interface InstallOptions {
|
|
699
|
+
/**
|
|
700
|
+
* Wait until every component is actually usable before resolving.
|
|
701
|
+
*
|
|
702
|
+
* Defaults to `true` because a resolved `install()` that is not yet usable is
|
|
703
|
+
* the single most misleading thing this surface could do: a skill is usable
|
|
704
|
+
* as soon as it is copied, but a connector is not usable until the server has
|
|
705
|
+
* been switched on and probed.
|
|
706
|
+
*/
|
|
707
|
+
waitForReady?: boolean;
|
|
708
|
+
/** Readiness budget. Defaults to `StoreClientOptions.readyTimeoutMs`. */
|
|
709
|
+
timeoutMs?: number;
|
|
710
|
+
signal?: AbortSignal;
|
|
711
|
+
}
|
|
712
|
+
export interface UninstallOptions {
|
|
713
|
+
/** Narrow the release to these component ids (default: every installed one). */
|
|
714
|
+
componentIds?: string[];
|
|
715
|
+
}
|
|
716
|
+
export interface SetEnabledOptions {
|
|
717
|
+
/** Narrow the state change to these component ids (default: all installed). */
|
|
718
|
+
componentIds?: string[];
|
|
719
|
+
}
|
|
720
|
+
export interface StoreClientOptions {
|
|
721
|
+
/** Default readiness budget in ms. */
|
|
722
|
+
readyTimeoutMs?: number;
|
|
723
|
+
/** First readiness poll interval in ms; doubles up to `readyPollMaxMs`. */
|
|
724
|
+
readyPollMs?: number;
|
|
725
|
+
readyPollMaxMs?: number;
|
|
726
|
+
/**
|
|
727
|
+
* Minimum gap between two *real probes* of the same connector while waiting
|
|
728
|
+
* for readiness, in ms. Defaults to `DEFAULT_READY_PROBE_MS`.
|
|
729
|
+
*
|
|
730
|
+
* A connector's readiness cannot be read: the status is derived from the last
|
|
731
|
+
* probe, so someone has to run one — but a probe is a real connection
|
|
732
|
+
* (`initialize` + `tools/list`) that also resolves the stored token, which
|
|
733
|
+
* means a refresh request when that token is near expiry, plus one more on a
|
|
734
|
+
* 401. Probing once per poll (the poll starts at `readyPollMs` = 400ms) turned
|
|
735
|
+
* a 30s readiness budget into ~10 connections and up to ~20 token requests
|
|
736
|
+
* against the connector's authorization server — with nobody having asked for
|
|
737
|
+
* anything. Between probes the loop reads the (free, local) status instead.
|
|
738
|
+
*/
|
|
739
|
+
readyProbeMs?: number;
|
|
740
|
+
}
|
|
741
|
+
export interface StoreOperationOutcome {
|
|
742
|
+
/** The snapshot the operation acted on; `null` when nothing was imported. */
|
|
743
|
+
snapshotId: string | null;
|
|
744
|
+
/** The server reported there was nothing to do. */
|
|
745
|
+
reused: boolean;
|
|
746
|
+
/**
|
|
747
|
+
* The host's own count of components it installed, when the verb reports one.
|
|
748
|
+
*
|
|
749
|
+
* Absent means "this verb does not say", never "it installed nothing" — the
|
|
750
|
+
* same rule [`StoreOperationOutcome.warnings`] follows. `install()` /
|
|
751
|
+
* `update()` pass it through; the uninstall / enable / disable projections
|
|
752
|
+
* carry no such number.
|
|
753
|
+
*/
|
|
754
|
+
installedCount?: number;
|
|
755
|
+
/**
|
|
756
|
+
* The server's per-component detail, verbatim. Empty on a host that predates
|
|
757
|
+
* `outcomes` — treat that as "no detail available", never as "nothing ran".
|
|
758
|
+
*/
|
|
759
|
+
components: InstallOutcome[];
|
|
760
|
+
/** Every reported component reached the requested state. */
|
|
761
|
+
ok: boolean;
|
|
762
|
+
/**
|
|
763
|
+
* The server's own failure lines, verbatim.
|
|
764
|
+
*
|
|
765
|
+
* `ok: false` alone is not an answer: the host explains *why* (a digest
|
|
766
|
+
* conflict means the market changed content without raising the version; a
|
|
767
|
+
* `preset_update_failed` names the component). Dropping these is what made a
|
|
768
|
+
* refused import unexplainable in the UI (`18` §11 D10), so they are carried
|
|
769
|
+
* rather than folded into `ok`.
|
|
770
|
+
*/
|
|
771
|
+
errors: string[];
|
|
772
|
+
/** The server's non-fatal notes, verbatim (e.g. a component left disabled). */
|
|
773
|
+
warnings: string[];
|
|
774
|
+
/**
|
|
775
|
+
* `undefined` when readiness was not checked. `true` when every component is
|
|
776
|
+
* usable now; `false` when it is not (see `readyIssue`).
|
|
777
|
+
*/
|
|
778
|
+
ready?: boolean;
|
|
779
|
+
readyIssue?: StoreReadyIssue;
|
|
780
|
+
/** The component that needs attention, when `readyIssue` is set. */
|
|
781
|
+
readyComponentId?: string;
|
|
782
|
+
/** `update()` only: the version that was installed before this call. */
|
|
783
|
+
fromVersion?: string;
|
|
784
|
+
/** `update()` only: the version now installed. */
|
|
785
|
+
toVersion?: string;
|
|
786
|
+
/**
|
|
787
|
+
* `update()` only: how many components of the replaced snapshot were actually
|
|
788
|
+
* released. `0` means the previous installation was not touched — an update
|
|
789
|
+
* that failed, or a no-op.
|
|
790
|
+
*/
|
|
791
|
+
releasedCount?: number;
|
|
792
|
+
}
|
|
793
|
+
/**
|
|
794
|
+
* The slice of `AppServerClient` this layer orchestrates.
|
|
795
|
+
*
|
|
796
|
+
* Declared structurally rather than imported so this module does not depend on
|
|
797
|
+
* the client that owns it (the client imports this one). It also documents the
|
|
798
|
+
* exact surface the store layer is allowed to lean on.
|
|
799
|
+
*/
|
|
800
|
+
export interface StoreHost {
|
|
801
|
+
listStore(): Promise<StoreList>;
|
|
802
|
+
installStoreEntry(marketplaceId: string, entryName: string): Promise<StoreInstallResult>;
|
|
803
|
+
updateStoreEntry(marketplaceId: string, entryName: string): Promise<StoreInstallResult>;
|
|
804
|
+
getInstallStatus(snapshotId: string): Promise<InstallStatus>;
|
|
805
|
+
disableInstall(snapshotId: string, componentIds: string[]): Promise<InstallStatus>;
|
|
806
|
+
enableInstall(snapshotId: string, componentIds: string[]): Promise<InstallStatus>;
|
|
807
|
+
uninstallInstall(snapshotId: string, componentIds: string[]): Promise<InstallStatus>;
|
|
808
|
+
readonly skills: {
|
|
809
|
+
list(): Promise<SkillSummary[]>;
|
|
810
|
+
};
|
|
811
|
+
readonly agents: {
|
|
812
|
+
get(id: string): Promise<AgentDetail>;
|
|
813
|
+
};
|
|
814
|
+
readonly teams: {
|
|
815
|
+
get(id: string): Promise<TeamDetail>;
|
|
816
|
+
};
|
|
817
|
+
readonly connectors: {
|
|
818
|
+
status(id: string): Promise<ConnectorStatusView>;
|
|
819
|
+
test(id: string): Promise<ConnectorProbeResult>;
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
export declare class StoreClient {
|
|
823
|
+
private readonly host;
|
|
824
|
+
private readonly readyTimeoutMs;
|
|
825
|
+
private readonly readyPollMs;
|
|
826
|
+
private readonly readyPollMaxMs;
|
|
827
|
+
private readonly readyProbeMs;
|
|
828
|
+
constructor(host: StoreHost, options?: StoreClientOptions);
|
|
829
|
+
/** Every item across enabled marketplaces, with install state. */
|
|
830
|
+
list(): Promise<StoreItem[]>;
|
|
831
|
+
/** Case-insensitive substring search over the fields a user can see. */
|
|
832
|
+
search(query: string, filter?: {
|
|
833
|
+
kind?: StoreItemKind;
|
|
834
|
+
}): Promise<StoreItem[]>;
|
|
835
|
+
/** Items currently installed on this host. */
|
|
836
|
+
installed(): Promise<StoreItem[]>;
|
|
837
|
+
/**
|
|
838
|
+
* The installed item that owns a given connector, or `null` when none does.
|
|
839
|
+
*
|
|
840
|
+
* A store item carries no field for the connector it installed — its
|
|
841
|
+
* `entry_name` is the *market's* name (real example: the entry
|
|
842
|
+
* `fenbi-baokao-decision` installs a server called `fenbi_mcp`), so a caller
|
|
843
|
+
* holding an id from `connectors.list()` cannot reach the `StoreItem` that
|
|
844
|
+
* `setEnabled()` / `uninstall()` require. `search()` cannot bridge that
|
|
845
|
+
* either: it is a substring scan over catalogue text, and the runtime name is
|
|
846
|
+
* not in it. This is the link, walked through the snapshot each installed item
|
|
847
|
+
* already points at.
|
|
848
|
+
*
|
|
849
|
+
* Both identifiers a caller may hold are accepted: the connector's own `id`
|
|
850
|
+
* (`connector/get`) and its `name` (`fenbi_mcp` — the MCP row's name and the
|
|
851
|
+
* tool namespace's middle segment). Matching is exact; call `list()`/`search()`
|
|
852
|
+
* when the question is "which catalogue entries mention X".
|
|
853
|
+
*/
|
|
854
|
+
findByConnector(connectorId: string): Promise<StoreItem | null>;
|
|
855
|
+
/**
|
|
856
|
+
* Installed items whose marketplace offers a different version.
|
|
857
|
+
*
|
|
858
|
+
* Both conditions matter: `update_available` is computed from the imported
|
|
859
|
+
* snapshot even when nothing is installed, so filtering on it alone would
|
|
860
|
+
* report an "update" for an entry the user has never installed.
|
|
861
|
+
*/
|
|
862
|
+
checkUpdates(): Promise<StoreItem[]>;
|
|
863
|
+
/**
|
|
864
|
+
* What a caller can offer for an item's pending update.
|
|
865
|
+
*
|
|
866
|
+
* `"none"` means there is nothing to do; `"update"` means the entry is
|
|
867
|
+
* installed at a different version and `update()` is the action that lands it
|
|
868
|
+
* (`store/update-entry`, `36` D3); `"unknown"` is returned rather than
|
|
869
|
+
* guessing when the item carries no snapshot to replace.
|
|
870
|
+
*/
|
|
871
|
+
updateHint(item: StoreItem): "none" | "update" | "unknown";
|
|
872
|
+
/**
|
|
873
|
+
* Upgrade an installed item to the version its marketplace advertises.
|
|
874
|
+
*
|
|
875
|
+
* The host does the ordering: new version installed first, previous one
|
|
876
|
+
* released only when that succeeded. So a failure resolves `ok: false` with
|
|
877
|
+
* the previous installation still in place (`releasedCount === 0`) — the
|
|
878
|
+
* caller never has to reason about a half-upgraded item.
|
|
879
|
+
*
|
|
880
|
+
* Refuses before the round trip when there is nothing to replace: an item that
|
|
881
|
+
* is not installed (`not_installed`) or is already current is not a failure of
|
|
882
|
+
* *this* call, and `updateHint()` is the way to ask first.
|
|
883
|
+
*/
|
|
884
|
+
update(item: StoreItem, options?: InstallOptions): Promise<StoreOperationOutcome>;
|
|
885
|
+
/**
|
|
886
|
+
* Import (when needed) and install one store item, then wait until it is
|
|
887
|
+
* usable.
|
|
888
|
+
*/
|
|
889
|
+
install(item: StoreItem, options?: InstallOptions): Promise<StoreOperationOutcome>;
|
|
890
|
+
/**
|
|
891
|
+
* Release an item's runtime artifacts and clear its install record.
|
|
892
|
+
*
|
|
893
|
+
* The server only clears the record for components it actually released, so a
|
|
894
|
+
* partial failure leaves `ok: false` with the failing component named — and
|
|
895
|
+
* the item stays installed, which is what makes a retry meaningful.
|
|
896
|
+
*/
|
|
897
|
+
uninstall(item: StoreItem, options?: UninstallOptions): Promise<StoreOperationOutcome>;
|
|
898
|
+
/**
|
|
899
|
+
* Switch an item's components on or off.
|
|
900
|
+
*
|
|
901
|
+
* Idempotent: the server sets the state it is asked for instead of toggling.
|
|
902
|
+
* A skill reports `skill_disable_flag_only` because the skill corpus has no
|
|
903
|
+
* enable state — that code is passed through, not swallowed, so a caller can
|
|
904
|
+
* tell "switched off" from "marked in the catalogue".
|
|
905
|
+
*/
|
|
906
|
+
setEnabled(item: StoreItem, enabled: boolean, options?: SetEnabledOptions): Promise<StoreOperationOutcome>;
|
|
907
|
+
private assertUsable;
|
|
908
|
+
private requireSnapshot;
|
|
909
|
+
private installedComponentIds;
|
|
910
|
+
private outcomeFromStatus;
|
|
911
|
+
/**
|
|
912
|
+
* Poll until every component of the snapshot is usable.
|
|
913
|
+
*
|
|
914
|
+
* A timeout does **not** discard the install: the caller gets the successful
|
|
915
|
+
* install plus `ready: false` and `readyIssue: "ready_timeout"`, so a slow
|
|
916
|
+
* connector never turns into "the install failed".
|
|
917
|
+
*/
|
|
918
|
+
private awaitReady;
|
|
919
|
+
/**
|
|
920
|
+
* Is this one component usable yet?
|
|
921
|
+
*
|
|
922
|
+
* Each kind answers with the narrowest read the protocol already offers. An
|
|
923
|
+
* unknown kind is reported ready rather than blocking: this layer cannot judge
|
|
924
|
+
* a component it does not understand, and a false timeout on a future kind
|
|
925
|
+
* would be worse than passing it through.
|
|
926
|
+
*
|
|
927
|
+
* `allowProbe` gates the one kind whose readiness needs a real connection (a
|
|
928
|
+
* connector); the other kinds answer from local reads, so the cadence does not
|
|
929
|
+
* apply to them.
|
|
930
|
+
*/
|
|
931
|
+
private readiness;
|
|
932
|
+
}
|
|
933
|
+
//#endregion
|
|
934
|
+
//#region src/teams.d.ts
|
|
935
|
+
export declare class TeamClient {
|
|
936
|
+
private readonly transport;
|
|
937
|
+
constructor(transport: Transport);
|
|
938
|
+
/** List AgentTeamDefinitions (fixed-member rosters). */
|
|
939
|
+
list(): Promise<TeamSummary[]>;
|
|
940
|
+
/** Fetch one TeamDefinition; planning-context bodies are never returned. */
|
|
941
|
+
get(teamId: string): Promise<TeamDetail>;
|
|
942
|
+
/**
|
|
943
|
+
* Export one TeamDefinition as a portable {@link ExpertPack}, with every member
|
|
944
|
+
* expanded and the **leader first**.
|
|
945
|
+
*
|
|
946
|
+
* All-or-nothing: if any member is uninstalled, disabled, or refused by the
|
|
947
|
+
* host's `[expert_export]` table, the whole call fails rather than returning a
|
|
948
|
+
* partial roster — the same posture `team/run` takes before it creates
|
|
949
|
+
* anything. `teamVersion` is an optional pin; a mismatch is `version_mismatch`.
|
|
950
|
+
*/
|
|
951
|
+
export(teamId: string, teamVersion?: string): Promise<ExpertPack>;
|
|
952
|
+
}
|
|
953
|
+
//#endregion
|
|
954
|
+
//#region src/models.d.ts
|
|
955
|
+
export declare class ModelClient {
|
|
956
|
+
private readonly transport;
|
|
957
|
+
constructor(transport: Transport);
|
|
958
|
+
/** Enabled providers' models; `is_default` marks the `agent/run` fallback. */
|
|
959
|
+
list(): Promise<ModelSummary[]>;
|
|
960
|
+
}
|
|
961
|
+
//#endregion
|
|
962
|
+
//#region src/workspaces.d.ts
|
|
963
|
+
export declare class WorkspaceClient {
|
|
964
|
+
private readonly transport;
|
|
965
|
+
constructor(transport: Transport);
|
|
966
|
+
list(): Promise<WorkspaceView[]>;
|
|
967
|
+
/**
|
|
968
|
+
* Validate + register (idempotently per canonical root) an absolute local
|
|
969
|
+
* directory chosen by the owner. The server canonicalizes the path and
|
|
970
|
+
* rejects links/reparse points; the returned view only carries the
|
|
971
|
+
* canonical path back to this same authenticated connection.
|
|
972
|
+
*/
|
|
973
|
+
create(path: string): Promise<WorkspaceView>;
|
|
974
|
+
/**
|
|
975
|
+
* Revoke (soft-delete) an owner's active workspace. Its conversations are
|
|
976
|
+
* kept and their `workspace_id` reference is preserved, so re-registering
|
|
977
|
+
* the same path re-activates the same workspace and those conversations
|
|
978
|
+
* become visible again.
|
|
979
|
+
*/
|
|
980
|
+
revoke(workspaceId: string): Promise<WorkspaceRevokeResult>;
|
|
981
|
+
}
|
|
982
|
+
//#endregion
|
|
983
|
+
//#region src/client.d.ts
|
|
984
|
+
export interface AppServerClientOptions {
|
|
985
|
+
/** Ready-made transport (07 §2.3); the host app owns its lifecycle. */
|
|
986
|
+
transport: Transport;
|
|
987
|
+
client: ClientInfo;
|
|
988
|
+
capabilities?: ClientCapabilities;
|
|
989
|
+
/** Readiness tuning for the `store` sub-client (`store.install`). */
|
|
990
|
+
store?: StoreClientOptions;
|
|
991
|
+
}
|
|
992
|
+
export declare class AppServerClient {
|
|
993
|
+
readonly transport: Transport;
|
|
994
|
+
/** Legacy preset/execution workflow client. */
|
|
995
|
+
readonly runs: RunClient;
|
|
996
|
+
/** Persistent presetless Nomi chat client. */
|
|
997
|
+
readonly conversations: ConversationClient;
|
|
998
|
+
/** Owner-scoped workspace registry client (user-chosen paths). */
|
|
999
|
+
readonly workspaces: WorkspaceClient;
|
|
1000
|
+
/** Agent Store Skill catalog client (`skill/list`, `skill/get`). */
|
|
1001
|
+
readonly skills: SkillClient;
|
|
1002
|
+
/** Agent Store Connector catalog/status/probe/OAuth client. */
|
|
1003
|
+
readonly connectors: ConnectorClient;
|
|
1004
|
+
/** Agent Store AgentDefinition catalog client (`agent/list`, `agent/get`). */
|
|
1005
|
+
readonly agents: AgentClient;
|
|
1006
|
+
/** Agent Store Team catalog client (`team/list`, `team/get`). */
|
|
1007
|
+
readonly teams: TeamClient;
|
|
1008
|
+
/** Public model directory (`models/list`, REQ-PAR-05b). */
|
|
1009
|
+
readonly models: ModelClient;
|
|
1010
|
+
/**
|
|
1011
|
+
* Agent Store lifecycle as one state machine: search → install (wait until
|
|
1012
|
+
* usable) → enable/disable → uninstall. Composes the flat methods below; it
|
|
1013
|
+
* adds no wire surface.
|
|
1014
|
+
*/
|
|
1015
|
+
readonly store: StoreClient;
|
|
1016
|
+
readonly clientInfo: ClientInfo;
|
|
1017
|
+
readonly capabilities?: ClientCapabilities;
|
|
1018
|
+
private initializeResult;
|
|
1019
|
+
private notificationListeners;
|
|
1020
|
+
/** Transport → client notification bridge; re-armed after a transport close. */
|
|
1021
|
+
private unbridgeNotifications;
|
|
1022
|
+
constructor(options: AppServerClientOptions);
|
|
1023
|
+
/**
|
|
1024
|
+
* Subscribe the client-level fan-out on the transport exactly once.
|
|
1025
|
+
* `transport.close()` drops its listeners, so this is also called on
|
|
1026
|
+
* `connect()`: otherwise a reconnect would leave notifications silent.
|
|
1027
|
+
*/
|
|
1028
|
+
private bridgeNotifications;
|
|
1029
|
+
get ready(): boolean;
|
|
1030
|
+
get initializeInfo(): InitializeResult | null;
|
|
1031
|
+
/** Connect and perform the initialize/initialized handshake. */
|
|
1032
|
+
connect(): Promise<InitializeResult>;
|
|
1033
|
+
onNotification(listener: NotificationListener): () => void;
|
|
1034
|
+
/** Close the transport; the server revokes the connection immediately. */
|
|
1035
|
+
close(): void;
|
|
1036
|
+
/** Import a local CodeBuddy/WorkBuddy source directory (roadmap Phase 1). */
|
|
1037
|
+
runImport(input: ImportRequest): Promise<ImportResult>;
|
|
1038
|
+
listImports(): Promise<ImportSummary[]>;
|
|
1039
|
+
/** One immutable snapshot with its standardized components. */
|
|
1040
|
+
getImport(snapshotId: string): Promise<ImportDetail>;
|
|
1041
|
+
/**
|
|
1042
|
+
* The plugin-level asset inventory of one snapshot (`import/assets`, doc 38):
|
|
1043
|
+
* the `avatars/`, `bin/` and `scripts/` files beside the skills, sorted by
|
|
1044
|
+
* path with a sha256 each. This is the same address space `readImportAsset`
|
|
1045
|
+
* accepts and an `ExpertPack.assets` names — one provider, one whitelist.
|
|
1046
|
+
* Check `capabilities.snapshot_assets` first.
|
|
1047
|
+
*/
|
|
1048
|
+
listImportAssets(snapshotId: string): Promise<ImportAssetList>;
|
|
1049
|
+
/**
|
|
1050
|
+
* One plugin-level asset's bytes, over the WebSocket binding (`import/asset`,
|
|
1051
|
+
* doc 38). Base64 on the wire, decoded to `Uint8Array` here — the same
|
|
1052
|
+
* envelope `skills.readFile()` decodes. Persona text and skill bodies are
|
|
1053
|
+
* deliberately not reachable through this face.
|
|
1054
|
+
*/
|
|
1055
|
+
readImportAsset(snapshotId: string, path: string): Promise<Uint8Array>;
|
|
1056
|
+
/** Install a snapshot into the runtime (roadmap Phase 2). */
|
|
1057
|
+
runInstall(input: InstallRequest): Promise<InstallResult>;
|
|
1058
|
+
/** Per-component installation state of a snapshot. */
|
|
1059
|
+
getInstallStatus(snapshotId: string): Promise<InstallStatus>;
|
|
1060
|
+
/**
|
|
1061
|
+
* Disable installed components.
|
|
1062
|
+
*
|
|
1063
|
+
* The runtime state moves with the flag — a connector's MCP server is switched
|
|
1064
|
+
* off, an expert's Preset is disabled — while the artifacts stay on disk. A
|
|
1065
|
+
* skill is the documented exception: the corpus has no enable state, so it
|
|
1066
|
+
* reports `skill_disable_flag_only` and the flag stays a catalogue marker.
|
|
1067
|
+
*/
|
|
1068
|
+
disableInstall(snapshotId: string, componentIds: string[]): Promise<InstallStatus>;
|
|
1069
|
+
/** Re-enable previously disabled components. */
|
|
1070
|
+
enableInstall(snapshotId: string, componentIds: string[]): Promise<InstallStatus>;
|
|
1071
|
+
/** Uninstall: remove runtime artifacts and clear the state (snapshot kept). */
|
|
1072
|
+
uninstallInstall(snapshotId: string, componentIds: string[]): Promise<InstallStatus>;
|
|
1073
|
+
/** Register a marketplace source on the trusted host. */
|
|
1074
|
+
addMarketplace(input: MarketplaceAddRequest): Promise<MarketplaceSummary>;
|
|
1075
|
+
/** All active marketplaces. */
|
|
1076
|
+
listMarketplaces(): Promise<MarketplaceSummary[]>;
|
|
1077
|
+
/** One marketplace with its discovered entries. */
|
|
1078
|
+
getMarketplace(marketplaceId: string): Promise<MarketplaceDetail>;
|
|
1079
|
+
/** Remove a marketplace (`cascade` uninstalls snapshots installed from it). */
|
|
1080
|
+
removeMarketplace(marketplaceId: string, cascade: boolean): Promise<MarketplaceRemoveResult>;
|
|
1081
|
+
/** Toggle auto-update for a marketplace. */
|
|
1082
|
+
setMarketplaceAutoUpdate(marketplaceId: string, enabled: boolean): Promise<MarketplaceSummary>;
|
|
1083
|
+
/** Re-fetch a marketplace source and rebuild entries when the revision changed. */
|
|
1084
|
+
refreshMarketplace(marketplaceId: string): Promise<MarketplaceRefreshResult>;
|
|
1085
|
+
/** Import one discovered marketplace entry (provenance linked). */
|
|
1086
|
+
importMarketplaceEntry(marketplaceId: string, entryName: string): Promise<ImportResult>;
|
|
1087
|
+
/**
|
|
1088
|
+
* The host's market policy: sweep cadence, auto-upgrade kind whitelist and the
|
|
1089
|
+
* last sweep's reading (doc `37` §3.2).
|
|
1090
|
+
*
|
|
1091
|
+
* Host-level rather than per-market: it answers "when does the sweep run and
|
|
1092
|
+
* what may it touch", while {@link setMarketplaceAutoUpdate} answers "which
|
|
1093
|
+
* market participates".
|
|
1094
|
+
*/
|
|
1095
|
+
getMarketSettings(): Promise<MarketSettings>;
|
|
1096
|
+
/**
|
|
1097
|
+
* Update the host's market policy. An absent field is left alone; the host
|
|
1098
|
+
* writes the value into its `config.toml` and answers with the file re-read,
|
|
1099
|
+
* so a caller can never see an optimistic echo.
|
|
1100
|
+
*/
|
|
1101
|
+
setMarketSettings(patch: MarketSettingsPatch): Promise<MarketSettings>;
|
|
1102
|
+
/** All store items across enabled marketplaces (display + install state). */
|
|
1103
|
+
listStore(): Promise<StoreList>;
|
|
1104
|
+
/** One-click install: import (when missing) + register in one call. */
|
|
1105
|
+
installStoreEntry(marketplaceId: string, entryName: string): Promise<StoreInstallResult>;
|
|
1106
|
+
/**
|
|
1107
|
+
* Upgrade an installed entry to the version its marketplace advertises.
|
|
1108
|
+
*
|
|
1109
|
+
* The host installs the new version before releasing the old one, so a failure
|
|
1110
|
+
* leaves the previous installation usable (`released_count: 0` is the machine
|
|
1111
|
+
* readable form of that). Refused for an entry that is not installed — this is
|
|
1112
|
+
* never a second way to install something.
|
|
1113
|
+
*/
|
|
1114
|
+
updateStoreEntry(marketplaceId: string, entryName: string): Promise<StoreInstallResult>;
|
|
1115
|
+
}
|
|
1116
|
+
//#endregion
|
|
1117
|
+
//#region src/conversation-handle.d.ts
|
|
1118
|
+
/** One completed (or in-flight / timed-out) turn in a conversation. */
|
|
1119
|
+
export interface ConversationTurn {
|
|
1120
|
+
conversation_id: string;
|
|
1121
|
+
/** The user content that was submitted. */
|
|
1122
|
+
content: string;
|
|
1123
|
+
/** Durable user message id (`ConversationSendReceipt.message_id`). */
|
|
1124
|
+
message_id: string;
|
|
1125
|
+
/** Wire/billing turn id when the server assigned one. */
|
|
1126
|
+
turn_id?: string | null;
|
|
1127
|
+
accepted: boolean;
|
|
1128
|
+
completed: boolean;
|
|
1129
|
+
/** Events observed for this turn (sequence order). */
|
|
1130
|
+
events: ConversationEvent[];
|
|
1131
|
+
/** Terminal assistant text: `receipt.result_text` (authoritative backfill)
|
|
1132
|
+
* else aggregated from `message.delta` events. */
|
|
1133
|
+
assistant_text: string | null;
|
|
1134
|
+
isError: boolean;
|
|
1135
|
+
result_error?: string | null;
|
|
1136
|
+
result_error_code?: string | null;
|
|
1137
|
+
/** Context occupancy when the runtime reported it during the turn. */
|
|
1138
|
+
usage?: ContextUsage | null;
|
|
1139
|
+
}
|
|
1140
|
+
export interface ConversationTurnOptions {
|
|
1141
|
+
/** Idempotency key; defaults to `crypto.randomUUID()`. */
|
|
1142
|
+
idempotencyKey?: string;
|
|
1143
|
+
/** Per-turn model override (applied server-side when set). */
|
|
1144
|
+
model?: ConversationCreateInput["model"];
|
|
1145
|
+
reasoningEffort?: string;
|
|
1146
|
+
}
|
|
1147
|
+
export declare class ConversationHandle {
|
|
1148
|
+
readonly conversationId: string;
|
|
1149
|
+
private readonly conversations;
|
|
1150
|
+
private readonly subscription;
|
|
1151
|
+
private constructor();
|
|
1152
|
+
/** Create a new conversation and subscribe to its live event stream. */
|
|
1153
|
+
static open(conversations: ConversationClient, input?: ConversationCreateInput): Promise<ConversationHandle>;
|
|
1154
|
+
/** Attach to an existing conversation (by id) and subscribe. */
|
|
1155
|
+
static attach(conversations: ConversationClient, conversationId: string): Promise<ConversationHandle>;
|
|
1156
|
+
/** Register a listener for every conversation event (returns an unsubscribe). */
|
|
1157
|
+
onEvent(listener: (event: ConversationEvent) => void): () => void;
|
|
1158
|
+
/**
|
|
1159
|
+
* Send a message (a "turn") and await its terminal state. The event collector
|
|
1160
|
+
* is installed before the send so streaming deltas / usage are captured even
|
|
1161
|
+
* when they arrive before the send acknowledgment is parsed.
|
|
1162
|
+
*/
|
|
1163
|
+
send(content: string, options?: ConversationTurnOptions): Promise<ConversationTurn>;
|
|
1164
|
+
/** Cancel the in-flight turn (idempotent; erases the current transcript row). */
|
|
1165
|
+
cancel(): Promise<void>;
|
|
1166
|
+
/** Fetch the transcript (paginated). */
|
|
1167
|
+
messages(query?: Partial<ConversationMessagesQuery>): Promise<ConversationMessagesPage>;
|
|
1168
|
+
/** Unsubscribe and release the handle. */
|
|
1169
|
+
close(): Promise<void>;
|
|
1170
|
+
}
|
|
1171
|
+
/** Convenience: simple transcript snapshot (all roles, newest last). */
|
|
1172
|
+
export declare function conversationMessages(page: ConversationMessagesPage): ConversationMessage[];
|
|
1173
|
+
//#endregion
|
|
1174
|
+
//#region src/turn-result.d.ts
|
|
1175
|
+
/** Token/context usage for the turn (absent until the runtime projects it). */
|
|
1176
|
+
export interface TurnUsage {
|
|
1177
|
+
input_tokens?: number;
|
|
1178
|
+
output_tokens?: number;
|
|
1179
|
+
total_tokens?: number;
|
|
1180
|
+
}
|
|
1181
|
+
/** One event-derived item (plan revision, task, attempt, approval, ...). */
|
|
1182
|
+
export interface TurnItem {
|
|
1183
|
+
sequence: number;
|
|
1184
|
+
event_type: string;
|
|
1185
|
+
kind: "plan" | "task" | "attempt" | "status" | "approval" | "other";
|
|
1186
|
+
/** Attempt/step identity when the event payload carries one. */
|
|
1187
|
+
ref_id?: string;
|
|
1188
|
+
status?: string;
|
|
1189
|
+
reason?: string;
|
|
1190
|
+
}
|
|
1191
|
+
/** Aggregated terminal result returned by `AgentRunHandle.finished`. */
|
|
1192
|
+
export interface TurnResult {
|
|
1193
|
+
run_id: string;
|
|
1194
|
+
status: RunStatus;
|
|
1195
|
+
version: number;
|
|
1196
|
+
/** Terminal assistant text (`run/result` summary). */
|
|
1197
|
+
final_response: string | null;
|
|
1198
|
+
output_files: string[];
|
|
1199
|
+
preset_revision?: number | null;
|
|
1200
|
+
content_digest?: string | null;
|
|
1201
|
+
/** Persisted event stream in sequence order (authoritative backfill). */
|
|
1202
|
+
events: RunEvent[];
|
|
1203
|
+
/** Event-derived items in the same order. */
|
|
1204
|
+
items: TurnItem[];
|
|
1205
|
+
usage?: TurnUsage;
|
|
1206
|
+
}
|
|
1207
|
+
/** Build the terminal turn result from the authoritative view + event stream. */
|
|
1208
|
+
export declare function aggregateTurnResult(view: RunView, events: RunEvent[]): TurnResult;
|
|
1209
|
+
//#endregion
|
|
1210
|
+
//#region src/run-handle.d.ts
|
|
1211
|
+
/** Options for `RunClient.launch()` (Codex-parity handle). */
|
|
1212
|
+
export interface LaunchHandleOptions {
|
|
1213
|
+
/** Whether to attach a live `EventSubscription` (default true). */
|
|
1214
|
+
follow?: boolean;
|
|
1215
|
+
}
|
|
1216
|
+
/**
|
|
1217
|
+
* A launched Agent run. Async-iterable over live events; await `.finished`
|
|
1218
|
+
* for the blocking Codex `thread.run()` shape.
|
|
1219
|
+
*/
|
|
1220
|
+
export declare class AgentRunHandle {
|
|
1221
|
+
readonly runId: string;
|
|
1222
|
+
readonly receipt: RunReceipt;
|
|
1223
|
+
private readonly runClient;
|
|
1224
|
+
private readonly poll;
|
|
1225
|
+
private subscription;
|
|
1226
|
+
private terminalView;
|
|
1227
|
+
private terminalResult;
|
|
1228
|
+
private finishWaiters;
|
|
1229
|
+
private iteratorQueue;
|
|
1230
|
+
private iteratorWaiters;
|
|
1231
|
+
private iteratorClosed;
|
|
1232
|
+
private readonly bufferedEvents;
|
|
1233
|
+
private readonly bufferedSequences;
|
|
1234
|
+
constructor(runClient: RunClient, receipt: RunReceipt, subscription: EventSubscription | null, poll: (runId: string) => Promise<RunView>);
|
|
1235
|
+
/**
|
|
1236
|
+
* Resolves with the aggregated terminal turn result (≈ `thread.run()`):
|
|
1237
|
+
* authoritative status/version plus `final_response`, output files, the
|
|
1238
|
+
* persisted event stream and derived items.
|
|
1239
|
+
*/
|
|
1240
|
+
get finished(): Promise<TurnResult>;
|
|
1241
|
+
/** Latest live cursor (proxied from the subscription when attached). */
|
|
1242
|
+
get lastSequence(): number;
|
|
1243
|
+
/** Ask the server to cancel; terminal confirmation still arrives via events/get. */
|
|
1244
|
+
cancel(): Promise<void>;
|
|
1245
|
+
/**
|
|
1246
|
+
* Inject steering text into the currently active step (≈ Codex turn steer).
|
|
1247
|
+
* Throws `conflict` when the run already changed or has no active step —
|
|
1248
|
+
* refresh `receipt`-level state via `runClient.get()` and retry.
|
|
1249
|
+
*/
|
|
1250
|
+
steer(text: string): Promise<RunView>;
|
|
1251
|
+
/** Detach the live subscription (events may still be polled via run/events). */
|
|
1252
|
+
close(): Promise<void>;
|
|
1253
|
+
/** Async iterator over live events (≈ `thread.runStreamed()`). */
|
|
1254
|
+
[Symbol.asyncIterator](): AsyncIterator<RunEvent>;
|
|
1255
|
+
private pushEvent;
|
|
1256
|
+
/**
|
|
1257
|
+
* Authoritative backfill for `finished`: `run/result` (terminal summary)
|
|
1258
|
+
* plus the persisted `run/events` stream, falling back to the live buffer
|
|
1259
|
+
* when the query fails.
|
|
1260
|
+
*/
|
|
1261
|
+
private aggregate;
|
|
1262
|
+
/** Poll run/get until terminal, then resolve all `finished` waiters once. */
|
|
1263
|
+
private ensureTerminalWatch;
|
|
1264
|
+
}
|
|
1265
|
+
/** Codex-parity launch: start the run and return a live handle immediately. */
|
|
1266
|
+
export declare function launchRun(runClient: RunClient, input: AgentRunInput, options?: LaunchHandleOptions): Promise<AgentRunHandle>;
|
|
1267
|
+
//#endregion
|
|
1268
|
+
//#region src/retry.d.ts
|
|
1269
|
+
/**
|
|
1270
|
+
* Retry helper (REQ-PAR-05e, ≈ Codex `retry_on_overload`): exponential backoff
|
|
1271
|
+
* with jitter, driven by the protocol's stable `retryable` hint.
|
|
1272
|
+
*
|
|
1273
|
+
* Only errors the server/transport marks retryable are retried; protocol
|
|
1274
|
+
* errors that carry `retryable: false` (validation, not_found, ...) surface
|
|
1275
|
+
* immediately. `command_id` / `idempotency_key` callers stay safe because the
|
|
1276
|
+
* App Server deduplicates replayed mutations.
|
|
1277
|
+
*/
|
|
1278
|
+
export interface RetryInfo {
|
|
1279
|
+
/** 1-based attempt that just failed and triggered the next try. */
|
|
1280
|
+
attempt: number;
|
|
1281
|
+
/** Delay applied before the next attempt. */
|
|
1282
|
+
delayMs: number;
|
|
1283
|
+
error: unknown;
|
|
1284
|
+
}
|
|
1285
|
+
export interface RetryOptions {
|
|
1286
|
+
/** Total attempts including the first (default 3). */
|
|
1287
|
+
maxAttempts?: number;
|
|
1288
|
+
/** First backoff delay in ms (default 500). */
|
|
1289
|
+
baseDelayMs?: number;
|
|
1290
|
+
/** Upper bound for one delay in ms (default 8000). */
|
|
1291
|
+
maxDelayMs?: number;
|
|
1292
|
+
/** Jitter fraction in [0, 1] (default 0.25 → delay ∈ [0.75×, 1.0×]). */
|
|
1293
|
+
jitter?: number;
|
|
1294
|
+
/** Observer hook for logging/metrics. */
|
|
1295
|
+
onRetry?: (info: RetryInfo) => void;
|
|
1296
|
+
/** Custom predicate; defaults to the protocol `retryable` hint. */
|
|
1297
|
+
shouldRetry?: (error: unknown, attempt: number) => boolean;
|
|
1298
|
+
/** Deterministic delay override (tests). */
|
|
1299
|
+
sleep?: (ms: number) => Promise<void>;
|
|
1300
|
+
}
|
|
1301
|
+
/** Run `operation`, retrying retryable failures with exponential backoff. */
|
|
1302
|
+
export declare function withRetry<T>(operation: () => Promise<T>, options?: RetryOptions): Promise<T>;
|
|
1303
|
+
//#endregion
|