@skein-js/agent-protocol 0.4.0 → 0.6.3

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.
Files changed (3) hide show
  1. package/dist/index.d.ts +190 -44
  2. package/dist/index.js +686 -113
  3. package/package.json +6 -6
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { GraphSchema, SkeinStore, RunQueue, RunEventBus, AuthEngine, Assistant, Config, StreamMode, Metadata, MultitaskStrategy, RunFrame, DefaultValues, Run, RunStatus, StorePutOptions, Item, StoreSearchQuery, SearchItem, Thread, ThreadSearchQuery, ThreadState, StoreRepo } from '@skein-js/core';
1
+ import { GraphSchema, SkeinStore, RunQueue, RunEventBus, AuthEngine, AuthUser, Metadata, Thread, ThreadSearchQuery, ThreadState, Assistant, AssistantSearchQuery, AssistantCreate, AssistantUpdate, AssistantVersionsQuery, AssistantVersion, AssistantGraph, Config, StreamMode, MultitaskStrategy, RunFrame, DefaultValues, Run, RunStatus, StorePutOptions, Item, StoreSearchQuery, SearchItem, StoreRepo } from '@skein-js/core';
2
2
  import { CompiledGraph, BaseCheckpointSaver, BaseStore, Item as Item$1, Operation, OperationResults } from '@langchain/langgraph';
3
3
 
4
4
  /** A factory export: called (optionally with per-run config) to produce a compiled graph. */
@@ -23,6 +23,18 @@ interface GraphResolver {
23
23
  }
24
24
  /** A source of the current time; injected so tests are deterministic. */
25
25
  type Clock = () => Date;
26
+ /**
27
+ * Delivers a run-completion webhook: POST `payload` (as JSON) to `url`. Injected so the transport is
28
+ * swappable and tests can capture deliveries without a network. Should resolve once sent and reject
29
+ * on failure — the engine calls it best-effort (a rejection is logged, never fails the run).
30
+ *
31
+ * Security: `url` is client-supplied, so the dispatcher makes a *server-side* request to a target the
32
+ * caller chose (an SSRF surface) and sends it the run's final `values`. The default dispatcher
33
+ * restricts the scheme to `http(s)` but, since skein is self-hosted and internal webhook targets are a
34
+ * legitimate use, does **not** block private/loopback hosts. Deployments that accept untrusted
35
+ * `webhook` URLs should inject a dispatcher that validates the resolved host against an allowlist.
36
+ */
37
+ type WebhookDispatcher = (url: string, payload: unknown) => Promise<void>;
26
38
  /** A minimal structured logger. Defaults to a no-op so nothing is required. */
27
39
  interface Logger {
28
40
  debug(message: string, meta?: unknown): void;
@@ -58,6 +70,11 @@ interface ProtocolDeps {
58
70
  logRunActivity?: boolean;
59
71
  /** Optional per-run wall-clock timeout in ms. When set, a run exceeding it becomes `"timeout"`. */
60
72
  runTimeoutMs?: number;
73
+ /**
74
+ * Delivers run-completion webhooks (the run's `webhook` field). Defaults to a `globalThis.fetch`
75
+ * POST with a JSON body; inject to customize transport/retries or to capture deliveries in tests.
76
+ */
77
+ webhookDispatcher?: WebhookDispatcher;
61
78
  /**
62
79
  * Optional auth engine. When set, every request is authenticated (401 on failure) and authorized
63
80
  * per resource + action (403 on deny), with ownership filters scoping reads and stamping writes.
@@ -65,28 +82,23 @@ interface ProtocolDeps {
65
82
  */
66
83
  auth?: AuthEngine;
67
84
  }
68
- /** Fill in the optional deps (`clock`, `logger`) so the rest of the code can rely on them. */
85
+ /** Fill in the optional deps (`clock`, `logger`, `webhookDispatcher`) so the rest can rely on them. */
69
86
  interface ResolvedDeps extends ProtocolDeps {
70
87
  clock: Clock;
71
88
  logger: Logger;
89
+ webhookDispatcher: WebhookDispatcher;
72
90
  }
73
91
 
74
- interface AssistantSearch {
75
- graph_id?: string;
76
- limit?: number;
77
- offset?: number;
78
- }
79
- interface AssistantService {
80
- /** Register one assistant per declared graph (idempotent safe to call on every boot). */
81
- registerGraphAssistants(): Promise<Assistant[]>;
82
- get(assistantId: string): Promise<Assistant>;
83
- list(): Promise<Assistant[]>;
84
- search(query: AssistantSearch): Promise<Assistant[]>;
85
- schemas(assistantId: string): Promise<GraphSchemas>;
86
- }
87
-
88
- /** Why a run was aborted. Absent (`null`) means the run failed on its own. */
89
- type AbortReason = "cancel" | "timeout";
92
+ /**
93
+ * Why a run was aborted. Absent (`null`) means the run failed on its own.
94
+ * - `cancel` — explicit `POST /runs/{id}/cancel`; the run settles `cancelled`.
95
+ * - `timeout` — the run exceeded `runTimeoutMs`; it settles `timeout`.
96
+ * - `interrupt` — displaced by a `multitask_strategy: "interrupt"` run; it settles `interrupted`,
97
+ * keeping its checkpoint writes.
98
+ * - `rollback` displaced by a `multitask_strategy: "rollback"` run; it settles terminally and the
99
+ * displacing run then discards its checkpoint writes (see the run service).
100
+ */
101
+ type AbortReason = "cancel" | "timeout" | "interrupt" | "rollback";
90
102
  /** Handle handed to the engine for a run: the signal to thread into the graph, and the reason box. */
91
103
  interface RunControl {
92
104
  signal: AbortSignal;
@@ -115,13 +127,126 @@ declare class ThreadLocks {
115
127
  run<T>(key: string, task: () => Promise<T>): Promise<T>;
116
128
  }
117
129
 
130
+ /**
131
+ * What a `multitask_strategy: "rollback"` run must do to the thread *before* it executes: drop the
132
+ * displaced run(s)' checkpoint writes and delete their run rows. Registered at create time and
133
+ * applied once the displaced run has settled (see `startRunExecution`). In-process, like
134
+ * {@link RunControlRegistry} — a single-process seam.
135
+ *
136
+ * `revertToCheckpoint` is `false` when no displaced run wrote checkpoints (all were still pending),
137
+ * so only the rows are deleted; otherwise it carries the base tip to revert to (`baseCheckpointId
138
+ * === undefined` means the displaced run started on a fresh thread, so the revert is a clean wipe).
139
+ */
140
+ interface RollbackPlan {
141
+ revertToCheckpoint: {
142
+ baseCheckpointId: string | undefined;
143
+ } | false;
144
+ displacedRunIds: string[];
145
+ }
118
146
  interface ProtocolContext {
119
147
  deps: ResolvedDeps;
120
148
  control: RunControlRegistry;
149
+ /** Per-thread lock closing the create-guard's check-then-insert race. */
121
150
  locks: ThreadLocks;
151
+ /**
152
+ * Per-thread lock serializing run *execution* (distinct from {@link locks}, which only guards
153
+ * creation). Every run executes inside it, so a second run on a busy thread waits its turn — this
154
+ * is what makes `multitask_strategy: "enqueue"` run behind the active run, and what makes an
155
+ * `interrupt`/`rollback` run start only after the displaced run has fully settled.
156
+ */
157
+ executionLocks: ThreadLocks;
158
+ /**
159
+ * runId → the thread's checkpoint tip when that run started executing (`undefined` if the thread
160
+ * had no checkpoints yet). Captured by the engine at `pending -> running`, cleared when the run
161
+ * settles; read to compute a {@link RollbackPlan}. In-process, per {@link control}'s convention.
162
+ */
163
+ runBaseCheckpoints: Map<string, string | undefined>;
164
+ /** New runId → the {@link RollbackPlan} its execution must apply before running. In-process. */
165
+ rollbackPlans: Map<string, RollbackPlan>;
166
+ /**
167
+ * The authenticated caller for the current request, populated by the authorizing handler wrapper
168
+ * so the run service can stamp it onto a run's kwargs. Undefined when no auth engine is
169
+ * configured (the default), matching `langgraph dev` — no principal is injected into the graph.
170
+ */
171
+ authUser?: AuthUser;
172
+ /** The current request's authenticated permission scopes (`AuthContext.scopes`), stamped with {@link authUser}. */
173
+ authScopes?: string[];
122
174
  }
123
175
  declare function createContext(deps: ProtocolDeps): ProtocolContext;
124
176
 
177
+ interface CreateThreadInput {
178
+ thread_id?: string;
179
+ metadata?: Metadata;
180
+ }
181
+ interface PatchThreadInput {
182
+ metadata?: Metadata;
183
+ }
184
+ interface HistoryOptions {
185
+ limit?: number;
186
+ }
187
+ interface ThreadService {
188
+ create(input?: CreateThreadInput): Promise<Thread>;
189
+ get(threadId: string): Promise<Thread>;
190
+ list(): Promise<Thread[]>;
191
+ /** Filtered + paginated listing — `POST /threads/search`. */
192
+ search(query: ThreadSearchQuery): Promise<Thread[]>;
193
+ patch(threadId: string, patch: PatchThreadInput): Promise<Thread>;
194
+ /** Duplicate a thread (new id) together with its full checkpoint history — `POST /threads/{id}/copy`. */
195
+ copy(threadId: string): Promise<Thread>;
196
+ delete(threadId: string): Promise<void>;
197
+ history(threadId: string, options?: HistoryOptions): Promise<ThreadState[]>;
198
+ /** The thread's current state snapshot — `GET /threads/{id}/state`, what `useStream` hydrates from. */
199
+ getState(threadId: string): Promise<ThreadState>;
200
+ }
201
+
202
+ /** `POST /assistants` — create input plus LangGraph's `if_exists` conflict policy. */
203
+ interface CreateAssistantInput extends AssistantCreate {
204
+ /** When the `assistant_id` already exists: `"raise"` (default, 409) or `"do_nothing"` (return it). */
205
+ ifExists?: "raise" | "do_nothing";
206
+ }
207
+ /** `DELETE /assistants/{id}` options. */
208
+ interface DeleteAssistantOptions {
209
+ /** Also delete threads whose `metadata.assistant_id` is this assistant (aborting their runs). */
210
+ deleteThreads?: boolean;
211
+ }
212
+ /** `GET /assistants/{id}/graph` options. */
213
+ interface DrawGraphOptions {
214
+ /** Include subgraphs in the drawn graph; an integer bounds the depth (mirrors LangGraph). */
215
+ xray?: number | boolean;
216
+ }
217
+ /** `GET /assistants/{id}/subgraphs[/{namespace}]` options. */
218
+ interface SubgraphsOptions {
219
+ /** Restrict to this subgraph namespace. */
220
+ namespace?: string;
221
+ /** Recurse into nested subgraphs. */
222
+ recurse?: boolean;
223
+ }
224
+ interface AssistantService {
225
+ /** Register one assistant per declared graph (idempotent — safe to call on every boot). */
226
+ registerGraphAssistants(): Promise<Assistant[]>;
227
+ get(assistantId: string): Promise<Assistant>;
228
+ list(): Promise<Assistant[]>;
229
+ /** Filtered + paginated listing — `POST /assistants/search`. */
230
+ search(query: AssistantSearchQuery): Promise<Assistant[]>;
231
+ /** Count matching assistants (ignores limit/offset) — `POST /assistants/count`. */
232
+ count(query: AssistantSearchQuery): Promise<number>;
233
+ schemas(assistantId: string): Promise<GraphSchemas>;
234
+ /** Create an assistant, honoring `if_exists` — `POST /assistants`. */
235
+ create(input: CreateAssistantInput): Promise<Assistant>;
236
+ /** Patch an assistant, minting a new version — `PATCH /assistants/{id}`. */
237
+ update(assistantId: string, patch: AssistantUpdate): Promise<Assistant>;
238
+ /** Delete an assistant (and, optionally, its threads) — `DELETE /assistants/{id}`. */
239
+ delete(assistantId: string, options?: DeleteAssistantOptions): Promise<void>;
240
+ /** Version history, newest-first — `POST /assistants/{id}/versions`. */
241
+ listVersions(assistantId: string, query?: AssistantVersionsQuery): Promise<AssistantVersion[]>;
242
+ /** Roll back to an existing version — `POST /assistants/{id}/latest`. */
243
+ setLatest(assistantId: string, version: number): Promise<Assistant>;
244
+ /** The drawable graph JSON — `GET /assistants/{id}/graph`. */
245
+ drawGraph(assistantId: string, options?: DrawGraphOptions): Promise<AssistantGraph>;
246
+ /** Each subgraph's schema, keyed by namespace — `GET /assistants/{id}/subgraphs`. */
247
+ subgraphs(assistantId: string, options?: SubgraphsOptions): Promise<GraphSchemas>;
248
+ }
249
+
125
250
  /** A run request as it arrives from any of the run endpoints (already validated). */
126
251
  interface CreateRunInput {
127
252
  thread_id?: string;
@@ -139,6 +264,8 @@ interface CreateRunInput {
139
264
  multitask_strategy?: MultitaskStrategy;
140
265
  interrupt_before?: string[] | "*";
141
266
  interrupt_after?: string[] | "*";
267
+ /** Absolute `http(s)` URL POSTed with the settled run once it reaches a terminal status. */
268
+ webhook?: string;
142
269
  }
143
270
  /** A started streaming run: its id, plus the live frame iterable to serialize as SSE. */
144
271
  interface StartedStream {
@@ -166,31 +293,6 @@ interface StoreService {
166
293
  listNamespaces(prefix?: string[]): Promise<string[][]>;
167
294
  }
168
295
 
169
- interface CreateThreadInput {
170
- thread_id?: string;
171
- metadata?: Metadata;
172
- }
173
- interface PatchThreadInput {
174
- metadata?: Metadata;
175
- }
176
- interface HistoryOptions {
177
- limit?: number;
178
- }
179
- interface ThreadService {
180
- create(input?: CreateThreadInput): Promise<Thread>;
181
- get(threadId: string): Promise<Thread>;
182
- list(): Promise<Thread[]>;
183
- /** Filtered + paginated listing — `POST /threads/search`. */
184
- search(query: ThreadSearchQuery): Promise<Thread[]>;
185
- patch(threadId: string, patch: PatchThreadInput): Promise<Thread>;
186
- /** Duplicate a thread (new id) together with its full checkpoint history — `POST /threads/{id}/copy`. */
187
- copy(threadId: string): Promise<Thread>;
188
- delete(threadId: string): Promise<void>;
189
- history(threadId: string, options?: HistoryOptions): Promise<ThreadState[]>;
190
- /** The thread's current state snapshot — `GET /threads/{id}/state`, what `useStream` hydrates from. */
191
- getState(threadId: string): Promise<ThreadState>;
192
- }
193
-
194
296
  /** Body for `POST /threads/{id}/stream` — a run request without the thread id (it's in the path). */
195
297
  interface ThreadStreamInput {
196
298
  assistant_id: string;
@@ -262,7 +364,15 @@ type ProtocolHandler = (req: ProtocolRequest) => Promise<ProtocolResponse>;
262
364
  interface ProtocolHandlers {
263
365
  getAssistant: ProtocolHandler;
264
366
  searchAssistants: ProtocolHandler;
367
+ countAssistants: ProtocolHandler;
265
368
  getAssistantSchemas: ProtocolHandler;
369
+ createAssistant: ProtocolHandler;
370
+ updateAssistant: ProtocolHandler;
371
+ deleteAssistant: ProtocolHandler;
372
+ listAssistantVersions: ProtocolHandler;
373
+ setAssistantLatestVersion: ProtocolHandler;
374
+ getAssistantGraph: ProtocolHandler;
375
+ getAssistantSubgraphs: ProtocolHandler;
266
376
  createThread: ProtocolHandler;
267
377
  getThread: ProtocolHandler;
268
378
  listThreads: ProtocolHandler;
@@ -319,6 +429,42 @@ interface ProtocolRuntime {
319
429
  */
320
430
  declare function createProtocolRuntime(deps: ProtocolDeps, options?: ProtocolRuntimeOptions): ProtocolRuntime;
321
431
 
432
+ type HttpMethod = "get" | "post" | "put" | "patch" | "delete";
433
+ interface RouteBinding {
434
+ method: HttpMethod;
435
+ /** Path with `:param` placeholders, e.g. `/threads/:thread_id/runs/stream`. */
436
+ path: string;
437
+ handler: keyof ProtocolHandlers;
438
+ /**
439
+ * Fold the path `thread_id` into the request body before dispatch. The SDK addresses a
440
+ * thread-scoped run by its path (`POST /threads/{id}/runs/stream`) while carrying only
441
+ * `assistant_id` in the body, but the stateless run handlers read `thread_id` from the body — so a
442
+ * thread-scoped mount must copy it across, or the run would target a stray new thread.
443
+ */
444
+ foldThreadIdIntoBody?: boolean;
445
+ }
446
+ /** The route table, ordered most-specific-first within each method so literals win over params. */
447
+ declare const skeinRoutes: readonly RouteBinding[];
448
+ /**
449
+ * Copy the path `thread_id` into an object body so a stateless run handler runs on the right thread.
450
+ * A no-op when there is no `thread_id` param. Used by every adapter for the `foldThreadIdIntoBody`
451
+ * routes, so the folding rule lives in one place.
452
+ */
453
+ declare function foldThreadId(request: ProtocolRequest): ProtocolRequest;
454
+ /** A resolved route: the matched binding plus the path params extracted from the URL. */
455
+ interface RouteMatch {
456
+ binding: RouteBinding;
457
+ params: Record<string, string>;
458
+ }
459
+ /**
460
+ * Match a `method` + `pathname` (no query string) against the route table, returning the binding and
461
+ * extracted path params, or `undefined` when nothing matches. Iterates in table order, which is
462
+ * most-specific-first, so a literal segment wins over a `:param`. This is what adapters that dispatch
463
+ * from a catch-all route (NestJS middleware, Next.js route handlers) use in place of a framework
464
+ * router; Express/Fastify bind `skeinRoutes` to their native router instead.
465
+ */
466
+ declare function matchSkeinRoute(method: string, pathname: string): RouteMatch | undefined;
467
+
322
468
  type LangGraphSearchItem = Item$1 & {
323
469
  score?: number;
324
470
  };
@@ -372,4 +518,4 @@ declare function toSseEvents(frames: AsyncIterable<RunFrame>, finalStatus: () =>
372
518
  */
373
519
  declare function parseAfterSeq(lastEventId: string | undefined): number;
374
520
 
375
- export { type AssistantSearch, type AssistantService, type Clock, type CommandInput, type CompiledGraphFactory, type CreateRunInput, type CreateThreadInput, type GraphResolver, type GraphSchemas, type HistoryOptions, type Logger, type PatchThreadInput, type ProtocolContext, type ProtocolDeps, type ProtocolHandler, type ProtocolHandlers, type ProtocolRequest, type ProtocolResponse, type ProtocolRuntime, type ProtocolRuntimeOptions, type ProtocolService, type ResolvedGraph, type RunService, type RunWorker, type RunWorkerOptions, SSE_HEADERS, SkeinBaseStore, type StartedStream, type StoreService, type ThreadService, type ThreadStreamInput, type ThreadStreamService, buildProtocolService, createContext, createProtocolHandlers, createProtocolRuntime, createProtocolService, createRunWorker, encodeFrame, encodeTerminal, parseAfterSeq, toSseEvents };
521
+ export { type AssistantService, type Clock, type CommandInput, type CompiledGraphFactory, type CreateAssistantInput, type CreateRunInput, type CreateThreadInput, type DeleteAssistantOptions, type DrawGraphOptions, type GraphResolver, type GraphSchemas, type HistoryOptions, type HttpMethod, type Logger, type PatchThreadInput, type ProtocolContext, type ProtocolDeps, type ProtocolHandler, type ProtocolHandlers, type ProtocolRequest, type ProtocolResponse, type ProtocolRuntime, type ProtocolRuntimeOptions, type ProtocolService, type ResolvedGraph, type RouteBinding, type RouteMatch, type RunService, type RunWorker, type RunWorkerOptions, SSE_HEADERS, SkeinBaseStore, type StartedStream, type StoreService, type SubgraphsOptions, type ThreadService, type ThreadStreamInput, type ThreadStreamService, buildProtocolService, createContext, createProtocolHandlers, createProtocolRuntime, createProtocolService, createRunWorker, encodeFrame, encodeTerminal, foldThreadId, matchSkeinRoute, parseAfterSeq, skeinRoutes, toSseEvents };