@loomcycle/client 0.8.19

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.
@@ -0,0 +1,479 @@
1
+ /**
2
+ * Wire-shape types for the loomcycle HTTP+SSE surface. Field names use
3
+ * snake_case to match the Go server's JSON output (no client-side
4
+ * conversion — what's on the wire is what you get).
5
+ *
6
+ * Public API method *parameters* use camelCase (JS norm); see
7
+ * `client.ts` for the input shapes (RunOptions, CreateSnapshotOptions,
8
+ * etc.) — those are translated to snake_case in the request body.
9
+ */
10
+ export type EventType = "started" | "text" | "tool_call" | "tool_result" | "usage" | "done" | "error" | "retry" | "host_widened" | "session" | "agent";
11
+ export interface ToolUse {
12
+ id: string;
13
+ name: string;
14
+ input: unknown;
15
+ }
16
+ export interface Usage {
17
+ input_tokens: number;
18
+ output_tokens: number;
19
+ cache_creation_input_tokens?: number;
20
+ cache_read_input_tokens?: number;
21
+ model?: string;
22
+ /** Forward-compat: a future wire bump may include the provider-billed
23
+ * USD cost alongside token counts. Today the sidecar never populates
24
+ * this; the field stays optional so consumers can plumb it without a
25
+ * wire change. */
26
+ cost_usd?: number;
27
+ }
28
+ /** RetryInfo accompanies an `event: retry` frame (EventRetry in the Go
29
+ * server). Surfaced live during the retry sleep — useful for "waiting on
30
+ * rate limit" UI. The agent loop is unaffected; the retry is invisible
31
+ * to it. Wire-stable; mirrors providers.RetryInfo. */
32
+ export interface RetryInfo {
33
+ provider: string;
34
+ attempt: number;
35
+ wait_ms: number;
36
+ /** One of providers.RetryReason* constants — "retry-after header" or
37
+ * "exponential backoff" today. Stable wire string; do not parse. */
38
+ reason: string;
39
+ }
40
+ /** HostWidening accompanies an `event: host_widened` frame (v0.8.17+).
41
+ * Emitted once per dispatched tool call whose Pre-hook allow_hosts grant
42
+ * fired. Operators audit confused-deputy patterns by comparing `url`'s
43
+ * host to `hosts_added`. Wire-stable; mirrors providers.HostWideningEventInfo. */
44
+ export interface HostWidening {
45
+ tool_call_id: string;
46
+ tool_name: string;
47
+ url: string;
48
+ hook_owner: string;
49
+ hook_name: string;
50
+ hosts_added: string[];
51
+ }
52
+ export interface AgentEvent {
53
+ type: EventType;
54
+ text?: string;
55
+ tool_use?: ToolUse;
56
+ usage?: Usage;
57
+ error?: string;
58
+ /** is_error flags a tool_result whose execution failed. Surviving the
59
+ * persist+replay round-trip matters because a continuation that lost
60
+ * the flag would re-feed the model a successful-looking result. */
61
+ is_error?: boolean;
62
+ stop_reason?: string;
63
+ /** Retry payload on `event: retry`. Nil on all other event types. */
64
+ retry?: RetryInfo;
65
+ /** Host-widening payload on `event: host_widened`. Nil on all other
66
+ * event types. */
67
+ host_widening?: HostWidening;
68
+ agent_id?: string;
69
+ run_id?: string;
70
+ session_id?: string;
71
+ parent_agent_id?: string | null;
72
+ }
73
+ export type PromptContent = {
74
+ type: "trusted-text";
75
+ text: string;
76
+ cacheable?: boolean;
77
+ } | {
78
+ type: "untrusted-block";
79
+ kind: string;
80
+ text: string;
81
+ };
82
+ export interface PromptSegment {
83
+ role: "system" | "user";
84
+ content: PromptContent[];
85
+ }
86
+ export interface RunOptions {
87
+ agent: string;
88
+ segments: PromptSegment[];
89
+ allowedTools?: string[];
90
+ /** Per-request URL allowlist (v0.3.3+). Three-state on the wire:
91
+ * - omitted / `undefined` — no narrowing (operator's static list applies).
92
+ * - `null` — same as omitted (pass-through; convenience for callers
93
+ * that thread a possibly-unset slice).
94
+ * - `[]` — deny all (every network call refuses).
95
+ * - `["host1.com", ...]` — intersection with the operator's list
96
+ * (caller can shrink, never widen). */
97
+ allowedHosts?: string[] | null;
98
+ /** Brave-side filtering when `allowedHosts` is set:
99
+ * - "drop" (default) — Brave results outside the intersected list
100
+ * are omitted; the model only sees URLs it can follow up with WebFetch.
101
+ * - "keep" — Brave's full result set passes through; caller filters
102
+ * downstream.
103
+ * Ignored when `allowedHosts` is unset. */
104
+ webSearchFilter?: "drop" | "keep";
105
+ /** Bind the run to an existing session (v0.x). When set, the new run is
106
+ * appended to that session (transcript is NOT replayed by /v1/runs —
107
+ * use continueSession for replay semantics). When empty, a fresh
108
+ * session is created and announced as the first SSE frame. */
109
+ sessionId?: string;
110
+ tenantId?: string;
111
+ /** Caller-supplied user binding (v0.4+). Records the run under this
112
+ * user_id for cancel/list endpoints; sub-agents inherit it. Charset:
113
+ * [A-Za-z0-9_-]{1,128}. */
114
+ userId?: string;
115
+ /** Caller-supplied tracking handle (v0.4+). When omitted, the server
116
+ * generates one and announces it in `event: agent`. Addresses the run
117
+ * for status/cancel via /v1/agents/{agent_id}. Charset:
118
+ * [A-Za-z0-9_-]{1,128}. */
119
+ agentId?: string;
120
+ /** Per-user tier name (v0.8.2+). Maps to `user_tiers.{name}` in the
121
+ * sidecar config (provider_priority + optional per-agent overlay).
122
+ * Server 400s on unknown tier. When omitted, falls through to
123
+ * `user_tiers.default`. */
124
+ userTier?: string;
125
+ /** Per-run MCP bearer token (v0.8.x+). Substituted into MCP HTTP header
126
+ * values containing `${run.user_bearer}` at outbound request-build time.
127
+ * Charset: [A-Za-z0-9._\-+/=]{16,512}. Empty is backwards-compatible
128
+ * (static-bearer setups unaffected). Sub-agents inherit identically.
129
+ * Never persisted; never logged in full. */
130
+ userBearer?: string;
131
+ signal?: AbortSignal;
132
+ }
133
+ export interface ContinueOptions {
134
+ /** Required — the session to continue. */
135
+ sessionId: string;
136
+ segments: PromptSegment[];
137
+ allowedTools?: string[];
138
+ /** Per-call URL allowlist. Same three-state semantics as
139
+ * RunOptions.allowedHosts — continuations re-supply the list each
140
+ * time rather than inheriting from the seed run. */
141
+ allowedHosts?: string[] | null;
142
+ /** Brave-side filtering when allowedHosts is set. See RunOptions. */
143
+ webSearchFilter?: "drop" | "keep";
144
+ /** Pin the continuation to a specific running agent_id.
145
+ * Optional — when set, the server validates that the agent is
146
+ * live for this session before accepting; rejects with
147
+ * AgentNotFoundError / SessionBusyError otherwise. Mirrors
148
+ * Python adapter's ContinueOptions.agent_id (the Python field
149
+ * is snake_case; the wire field server-side is `agent_id`). */
150
+ agentId?: string;
151
+ /** Per-call user tier (v0.8.2+). Unlike user_id (session-bound),
152
+ * user_tier is per-request so a user upgrading mid-session sees
153
+ * the new tier applied to the next continuation. */
154
+ userTier?: string;
155
+ /** Per-call MCP bearer (v0.8.x+). Per-request (not session-bound)
156
+ * so different continuations in the same session may carry
157
+ * different end-user tokens. */
158
+ userBearer?: string;
159
+ signal?: AbortSignal;
160
+ }
161
+ export interface ClientOptions {
162
+ /** Base URL of the sidecar, e.g. "http://127.0.0.1:8787". Defaults to
163
+ * http://127.0.0.1:8787; in production, callers pass the deployed
164
+ * URL (LOOMCYCLE_BASE_URL equivalent). */
165
+ baseUrl?: string;
166
+ /** Bearer token for the Authorization header. Optional in
167
+ * open-mode deployments (LOOMCYCLE_AUTH_TOKEN unset on the server).
168
+ * When present, attached as `Authorization: Bearer <token>` on
169
+ * every request. */
170
+ authToken?: string;
171
+ /** Custom fetch implementation; defaults to global `fetch`.
172
+ * Useful for testing (vi.fn()) or for runtimes that ship their
173
+ * own fetch (e.g. node-fetch on older Node). */
174
+ fetch?: typeof fetch;
175
+ }
176
+ export type AgentStatus = "running" | "completed" | "failed" | "cancelled";
177
+ export interface AgentUsage {
178
+ input_tokens?: number;
179
+ output_tokens?: number;
180
+ cache_creation_tokens?: number;
181
+ cache_read_tokens?: number;
182
+ model?: string;
183
+ }
184
+ export interface Agent {
185
+ agent_id: string;
186
+ run_id: string;
187
+ session_id: string;
188
+ agent: string;
189
+ parent_agent_id: string | null;
190
+ user_id: string;
191
+ status: AgentStatus;
192
+ started_at: string;
193
+ completed_at: string | null;
194
+ stop_reason: string | null;
195
+ error: string | null;
196
+ usage: AgentUsage;
197
+ last_heartbeat_at: string | null;
198
+ live: boolean;
199
+ }
200
+ export interface ListAgentsResponse {
201
+ agents: Agent[];
202
+ }
203
+ export interface CancelAgentResult {
204
+ /** Number of agents marked cancelled (root + descendants reached
205
+ * via parent_agent_id cascade). 0 when the agent had already
206
+ * terminated; the call still succeeds (idempotent contract). */
207
+ cancelledCount: number;
208
+ }
209
+ /** TranscriptEvent — one persisted store.Event from
210
+ * GET /v1/sessions/{id}/transcript. The server wraps each
211
+ * providers.Event in {seq, run_id, ts_ns, type, event:{...}}. */
212
+ export interface TranscriptEvent {
213
+ seq: number;
214
+ run_id: string;
215
+ ts_ns: number;
216
+ type: string;
217
+ event: unknown;
218
+ }
219
+ export interface TranscriptResponse {
220
+ session: {
221
+ id: string;
222
+ user_id: string;
223
+ agent: string;
224
+ created_at: string;
225
+ };
226
+ events: TranscriptEvent[];
227
+ }
228
+ export interface HealthResponse {
229
+ ok: boolean;
230
+ commit?: string;
231
+ built?: string;
232
+ uptime_seconds?: number;
233
+ version?: string;
234
+ }
235
+ export interface UserSummary {
236
+ user_id: string;
237
+ running_count: number;
238
+ total_count: number;
239
+ last_started_at: string;
240
+ }
241
+ export interface ListUsersResponse {
242
+ users: UserSummary[];
243
+ }
244
+ export type RuntimeStateStatus = "running" | "pausing" | "paused";
245
+ export interface PauseResult {
246
+ state: string;
247
+ duration_ms: number;
248
+ force_cancelled_count: number;
249
+ paused_runs_count: number;
250
+ warnings?: string[];
251
+ }
252
+ export interface ResumeResult {
253
+ state: string;
254
+ resumed_runs_count: number;
255
+ warnings?: string[];
256
+ }
257
+ export interface RuntimeStateResponse {
258
+ state: RuntimeStateStatus;
259
+ paused_runs_count: number;
260
+ }
261
+ export interface SnapshotDescriptor {
262
+ id: string;
263
+ created_at: string;
264
+ label?: string;
265
+ schema_version: number;
266
+ byte_size: number;
267
+ }
268
+ export interface SnapshotListResponse {
269
+ entries: SnapshotDescriptor[];
270
+ }
271
+ export interface SnapshotCreateResponse {
272
+ id: string;
273
+ created_at: string;
274
+ label?: string;
275
+ schema_version: number;
276
+ byte_size: number;
277
+ }
278
+ /** Full envelope returned by GET /v1/_snapshots/{id}. json_content
279
+ * carries the canonical envelope as a parsed JSON object — pipe
280
+ * it directly into restoreSnapshot's `json` field. */
281
+ export interface SnapshotEnvelope {
282
+ id: string;
283
+ created_at: string;
284
+ label?: string;
285
+ schema_version: number;
286
+ byte_size: number;
287
+ json_content: unknown;
288
+ }
289
+ export interface CreateSnapshotOptions {
290
+ /** Free-text marker stored on the snapshots.label column. */
291
+ label?: string;
292
+ /** Capture the optional interaction_history section (large; opt-in). */
293
+ includeHistory?: boolean;
294
+ /** RFC3339 timestamp; only honoured when `includeHistory` is true. */
295
+ includeHistorySince?: string;
296
+ /** Override the operator's LOOMCYCLE_SNAPSHOT_MAX_BYTES cap. 0 = default. */
297
+ maxBytes?: number;
298
+ }
299
+ export interface SnapshotRestoreResponse {
300
+ agent_defs_restored?: number;
301
+ agent_def_active_restored?: number;
302
+ memory_restored?: number;
303
+ channel_messages_restored?: number;
304
+ channel_cursors_restored?: number;
305
+ evaluations_restored?: number;
306
+ paused_runs_restored?: number;
307
+ synthesized_sessions?: number;
308
+ transcript_events_restored?: number;
309
+ interaction_history_restored?: number;
310
+ warnings?: string[];
311
+ }
312
+ export interface MemoryScopeKind {
313
+ name: string;
314
+ description: string;
315
+ }
316
+ export interface MemoryScopesResponse {
317
+ scopes: MemoryScopeKind[];
318
+ }
319
+ export interface MemoryScopeIDSummary {
320
+ scope_id: string;
321
+ key_count: number;
322
+ bytes: number;
323
+ updated_at: string;
324
+ }
325
+ export interface MemoryScopeIDsResponse {
326
+ scope: string;
327
+ scope_ids: MemoryScopeIDSummary[];
328
+ }
329
+ export interface MemoryEntry {
330
+ key: string;
331
+ value: unknown;
332
+ expires_at?: string;
333
+ created_at: string;
334
+ updated_at: string;
335
+ }
336
+ export interface MemoryEntriesResponse {
337
+ scope: string;
338
+ scope_id: string;
339
+ entries: MemoryEntry[];
340
+ truncated: boolean;
341
+ }
342
+ export interface MemoryEntryResponse {
343
+ scope: string;
344
+ scope_id: string;
345
+ entry: MemoryEntry;
346
+ }
347
+ export type InterruptStatus = "pending" | "answered" | "cancelled" | "expired";
348
+ export interface InterruptRow {
349
+ interrupt_id: string;
350
+ run_id: string;
351
+ kind: string;
352
+ status: InterruptStatus;
353
+ question?: string;
354
+ options?: string[];
355
+ context_data?: string;
356
+ priority: string;
357
+ answer?: string;
358
+ created_at: string;
359
+ expires_at?: string;
360
+ resolved_at?: string;
361
+ resolved_by?: string;
362
+ user_id?: string;
363
+ agent_id?: string;
364
+ agent_name?: string;
365
+ }
366
+ export interface InterruptListResponse {
367
+ interrupts: InterruptRow[];
368
+ total: number;
369
+ }
370
+ export interface ResolveInterruptOptions {
371
+ /** The human's answer. When the original ask declared options,
372
+ * MUST be one of them (server-side validated). */
373
+ answer: string;
374
+ /** Audit attribution for who resolved it (free-form). Defaults
375
+ * server-side to "client" when omitted. */
376
+ resolvedBy?: string;
377
+ /** Discriminator. v0.8.16 supports only "question"; reserved
378
+ * for v0.9.x future kinds. */
379
+ kind?: string;
380
+ }
381
+ export type HookPhase = "pre" | "post";
382
+ export type HookFailMode = "open" | "closed";
383
+ /** Hook is the full descriptor returned by listHooks. The id +
384
+ * registered_at are loomcycle-assigned; the rest mirrors what was
385
+ * POSTed to registerHook. Field names use snake_case to match the
386
+ * Go server's JSON output. */
387
+ export interface Hook {
388
+ id: string;
389
+ owner: string;
390
+ name: string;
391
+ phase: HookPhase;
392
+ agents: string[];
393
+ tools: string[];
394
+ callback_url: string;
395
+ fail_mode: HookFailMode;
396
+ timeout_ms: number;
397
+ registered_at: string;
398
+ }
399
+ /** RegisterHookOptions uses camelCase (JS norm for method parameters)
400
+ * and is translated to snake_case in the request body — same split as
401
+ * RunOptions / CreateSnapshotOptions. */
402
+ export interface RegisterHookOptions {
403
+ /** App UID; (owner, name) is the identity tuple. Re-registering the
404
+ * same pair replaces the prior entry with a fresh id. */
405
+ owner: string;
406
+ name: string;
407
+ phase: HookPhase;
408
+ /** Agent name globs (exact match or trailing-* prefix). Empty list
409
+ * matches every agent (equivalent to ["*"]). */
410
+ agents?: string[];
411
+ /** Tool name globs (same syntax). Empty matches every tool. */
412
+ tools?: string[];
413
+ /** http:// or https:// URL loomcycle POSTs PreHookCall /
414
+ * PostHookCall payloads to. */
415
+ callbackUrl: string;
416
+ /** "open" (default) — webhook errors pass through. "closed" — webhook
417
+ * errors fail the tool call with IsError=true. */
418
+ failMode?: HookFailMode;
419
+ /** Per-call timeout. 0 / omitted = registry default (5 s). */
420
+ timeoutMs?: number;
421
+ }
422
+ export interface RegisterHookResponse {
423
+ /** Loomcycle-assigned id. Use it on deleteHook. */
424
+ id: string;
425
+ }
426
+ export interface ListHooksResponse {
427
+ hooks: Hook[];
428
+ }
429
+ export interface HookToolCall {
430
+ id: string;
431
+ name: string;
432
+ /** Raw JSON the model produced; consumers parse as needed. */
433
+ input: unknown;
434
+ }
435
+ export interface HookToolResult {
436
+ text: string;
437
+ is_error?: boolean;
438
+ }
439
+ export interface PreHookCall {
440
+ phase: "pre";
441
+ owner: string;
442
+ hook_name: string;
443
+ agent: string;
444
+ user_id?: string;
445
+ agent_id?: string;
446
+ tool_call: HookToolCall;
447
+ }
448
+ /** Response a Pre webhook returns. All fields optional; empty body
449
+ * means "pass through unchanged". See the server-side docs for the
450
+ * allow_hosts confused-deputy hazard before populating it. */
451
+ export interface PreHookResult {
452
+ /** Rewrite the tool input. Tool runs with this instead of the
453
+ * model's original payload. */
454
+ input?: unknown;
455
+ /** Short-circuit the call: model sees this synthetic result. When
456
+ * set, allow_hosts is dropped (deny wins; we do not let a denied
457
+ * hook contribute hostnames to peers in the chain). */
458
+ deny?: HookToolResult;
459
+ /** Per-call host approvals. Only takes effect when the registering
460
+ * owner is in the operator yaml's hooks.permit_host_widen.owners.
461
+ * Read the SECURITY note in internal/hooks/types.go before using —
462
+ * this is a confused-deputy attack surface. */
463
+ allow_hosts?: string[];
464
+ }
465
+ export interface PostHookCall {
466
+ phase: "post";
467
+ owner: string;
468
+ hook_name: string;
469
+ agent: string;
470
+ user_id?: string;
471
+ agent_id?: string;
472
+ tool_call: HookToolCall;
473
+ tool_result: HookToolResult;
474
+ }
475
+ /** Response a Post webhook returns. When result is omitted the tool
476
+ * result passes through unchanged. */
477
+ export interface PostHookResult {
478
+ result?: HookToolResult;
479
+ }
package/dist/types.js ADDED
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Wire-shape types for the loomcycle HTTP+SSE surface. Field names use
3
+ * snake_case to match the Go server's JSON output (no client-side
4
+ * conversion — what's on the wire is what you get).
5
+ *
6
+ * Public API method *parameters* use camelCase (JS norm); see
7
+ * `client.ts` for the input shapes (RunOptions, CreateSnapshotOptions,
8
+ * etc.) — those are translated to snake_case in the request body.
9
+ */
10
+ export {};
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@loomcycle/client",
3
+ "version": "0.8.19",
4
+ "description": "TypeScript client for the loomcycle sidecar (HTTP+SSE). 27 methods covering run streaming, agent metadata, pause/resume/state, snapshot lifecycle, memory admin, interruption resolve, and hook management.",
5
+ "license": "Apache-2.0",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/index.js",
12
+ "types": "./dist/index.d.ts"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsc",
21
+ "typecheck": "tsc --noEmit",
22
+ "test": "vitest run",
23
+ "test:watch": "vitest"
24
+ },
25
+ "devDependencies": {
26
+ "typescript": "^5.6.0",
27
+ "vitest": "^3.0.0"
28
+ },
29
+ "engines": {
30
+ "node": ">=18"
31
+ }
32
+ }