@skein-js/agent-protocol 0.7.0 → 0.8.0

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/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
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';
1
+ import { GraphSchema, SkeinStore, RunQueue, RunEventBus, AuthEngine, AuthUser, Metadata, Thread, ThreadSearchQuery, ThreadState, Checkpoint, 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. */
@@ -184,6 +184,17 @@ interface PatchThreadInput {
184
184
  interface HistoryOptions {
185
185
  limit?: number;
186
186
  }
187
+ /** Body of `POST /threads/{id}/state` — a time-travel update that forks a new checkpoint. */
188
+ interface UpdateStateInput {
189
+ /** New state to write. `null`/`undefined` re-points `next` without changing values. */
190
+ values?: unknown;
191
+ /** Attribute the update as though this node produced `values` (sets up which node runs next). */
192
+ as_node?: string;
193
+ /** The checkpoint to fork from; omitted updates the thread tip. */
194
+ checkpoint_id?: string;
195
+ /** Full checkpoint pointer to fork from (alternative to `checkpoint_id`). */
196
+ checkpoint?: Record<string, unknown>;
197
+ }
187
198
  interface ThreadService {
188
199
  create(input?: CreateThreadInput): Promise<Thread>;
189
200
  get(threadId: string): Promise<Thread>;
@@ -197,6 +208,15 @@ interface ThreadService {
197
208
  history(threadId: string, options?: HistoryOptions): Promise<ThreadState[]>;
198
209
  /** The thread's current state snapshot — `GET /threads/{id}/state`, what `useStream` hydrates from. */
199
210
  getState(threadId: string): Promise<ThreadState>;
211
+ /** State at a specific checkpoint (time travel) — `GET /threads/{id}/state/{checkpoint_id}`. */
212
+ getStateAt(threadId: string, checkpointId: string): Promise<ThreadState>;
213
+ /**
214
+ * Update (fork) thread state at a checkpoint — `POST /threads/{id}/state`. Writes a new checkpoint
215
+ * via `graph.updateState`, mirrors it onto the thread row, and returns the new checkpoint pointer.
216
+ */
217
+ updateState(threadId: string, input: UpdateStateInput): Promise<{
218
+ checkpoint: Checkpoint;
219
+ }>;
200
220
  }
201
221
 
202
222
  /** `POST /assistants` — create input plus LangGraph's `if_exists` conflict policy. */
@@ -266,6 +286,8 @@ interface CreateRunInput {
266
286
  interrupt_after?: string[] | "*";
267
287
  /** Absolute `http(s)` URL POSTed with the settled run once it reaches a terminal status. */
268
288
  webhook?: string;
289
+ /** Time travel: fork this run from a prior checkpoint instead of the thread tip (server-validated). */
290
+ checkpoint_id?: string;
269
291
  }
270
292
  /** A started streaming run: its id, plus the live frame iterable to serialize as SSE. */
271
293
  interface StartedStream {
@@ -387,6 +409,8 @@ interface ProtocolHandlers {
387
409
  deleteThread: ProtocolHandler;
388
410
  getThreadHistory: ProtocolHandler;
389
411
  getThreadState: ProtocolHandler;
412
+ getThreadStateAtCheckpoint: ProtocolHandler;
413
+ updateThreadState: ProtocolHandler;
390
414
  createWaitRun: ProtocolHandler;
391
415
  createStreamRun: ProtocolHandler;
392
416
  createBackgroundRun: ProtocolHandler;
package/dist/index.js CHANGED
@@ -64,6 +64,11 @@ var streamModeSchema = z.union([z.string(), z.array(z.string())]);
64
64
  var configSchema = z.record(z.unknown());
65
65
  var multitaskStrategySchema = z.enum(["reject", "interrupt", "rollback", "enqueue"]);
66
66
  var interruptWhenSchema = z.union([z.array(z.string()), z.literal("*")]);
67
+ var checkpointSchema = z.object({
68
+ checkpoint_id: z.string().optional(),
69
+ checkpoint_ns: z.string().nullish(),
70
+ checkpoint_map: z.record(z.unknown()).nullish()
71
+ });
67
72
  var runCreateSchema = z.object({
68
73
  assistant_id: z.string().min(1),
69
74
  thread_id: z.string().min(1).optional(),
@@ -77,7 +82,21 @@ var runCreateSchema = z.object({
77
82
  interrupt_before: interruptWhenSchema.optional(),
78
83
  interrupt_after: interruptWhenSchema.optional(),
79
84
  /** Run-completion webhook: an absolute `http(s)` URL POSTed the settled run when it finishes. */
80
- webhook: z.string().url().optional()
85
+ webhook: z.string().url().optional(),
86
+ /** Time travel: fork this run from a prior checkpoint instead of the thread tip. */
87
+ checkpoint_id: z.string().optional(),
88
+ /** Time travel: full checkpoint pointer to fork from (its `checkpoint_id` is what matters). */
89
+ checkpoint: checkpointSchema.optional()
90
+ }).passthrough();
91
+ var threadStateUpdateSchema = z.object({
92
+ /** New state to write. `null` re-points `next` without changing values; an array is a bulk write. */
93
+ values: z.union([z.record(z.unknown()), z.array(z.record(z.unknown()))]).nullish(),
94
+ /** Attribute the update as though this node produced `values` (sets up which node runs next). */
95
+ as_node: z.string().optional(),
96
+ /** The checkpoint to fork from; omitted updates the thread tip. */
97
+ checkpoint_id: z.string().optional(),
98
+ /** Full checkpoint pointer to fork from (alternative to `checkpoint_id`). */
99
+ checkpoint: checkpointSchema.nullish()
81
100
  }).passthrough();
82
101
  var threadStreamSchema = z.object({
83
102
  assistant_id: z.string().min(1),
@@ -321,6 +340,23 @@ function createProtocolHandlers(service) {
321
340
  return json(await service.threads.history(requireParam(req.params, "thread_id"), options));
322
341
  },
323
342
  getThreadState: async (req) => json(await service.threads.getState(requireParam(req.params, "thread_id"))),
343
+ getThreadStateAtCheckpoint: async (req) => json(
344
+ await service.threads.getStateAt(
345
+ requireParam(req.params, "thread_id"),
346
+ requireParam(req.params, "checkpoint_id")
347
+ )
348
+ ),
349
+ updateThreadState: async (req) => {
350
+ const body = parse(threadStateUpdateSchema, req.body ?? {});
351
+ return json(
352
+ await service.threads.updateState(requireParam(req.params, "thread_id"), {
353
+ values: body.values,
354
+ as_node: body.as_node,
355
+ checkpoint_id: body.checkpoint_id,
356
+ checkpoint: body.checkpoint ?? void 0
357
+ })
358
+ );
359
+ },
324
360
  // --- runs ---------------------------------------------------------------------------------
325
361
  createWaitRun: async (req) => json(await service.runs.createWait(parse(runCreateSchema, req.body))),
326
362
  createStreamRun: async (req) => {
@@ -990,10 +1026,15 @@ function toFactoryConfigurable(kwargs) {
990
1026
  }
991
1027
  function toGraphCallOptions(kwargs, threadId, signal) {
992
1028
  const options = {
993
- // Drop server-owned keys from the client's configurable, force this thread's id, then stamp the
994
- // authenticated caller so the graph can authorize off `configurable.langgraph_auth_user`.
1029
+ // Drop server-owned keys from the client's configurable, force this thread's id, add the
1030
+ // server-owned time-travel fork target (if any) *after* sanitizing so the client can never spoof
1031
+ // it, then stamp the authenticated caller so the graph can authorize off `langgraph_auth_user`.
995
1032
  configurable: withAuthUser(
996
- { ...sanitizeConfigurable(kwargs.config?.configurable), thread_id: threadId },
1033
+ {
1034
+ ...sanitizeConfigurable(kwargs.config?.configurable),
1035
+ thread_id: threadId,
1036
+ ...kwargs.checkpoint_id !== void 0 ? { checkpoint_id: kwargs.checkpoint_id } : {}
1037
+ },
997
1038
  kwargs.auth_user,
998
1039
  kwargs.auth_scopes
999
1040
  ),
@@ -1334,6 +1375,7 @@ function toKwargs(input, authUser, authScopes) {
1334
1375
  if (input.interrupt_before !== void 0) kwargs.interrupt_before = input.interrupt_before;
1335
1376
  if (input.interrupt_after !== void 0) kwargs.interrupt_after = input.interrupt_after;
1336
1377
  if (input.webhook !== void 0) kwargs.webhook = input.webhook;
1378
+ if (input.checkpoint_id !== void 0) kwargs.checkpoint_id = input.checkpoint_id;
1337
1379
  if (authUser !== void 0) {
1338
1380
  kwargs.auth_user = authUser;
1339
1381
  if (authScopes !== void 0) kwargs.auth_scopes = authScopes;
@@ -1554,13 +1596,13 @@ function createThreadService(ctx) {
1554
1596
  if (!thread) throw SkeinHttpError7.notFound(`Thread "${threadId}" not found.`);
1555
1597
  return thread;
1556
1598
  };
1557
- const readHistory = async (threadId, options) => {
1599
+ const loadThreadGraph = async (threadId) => {
1558
1600
  await requireThread(threadId);
1559
1601
  const runs = await deps.store.runs.listByThread(threadId);
1560
1602
  const latest = [...runs].sort((a, b) => b.created_at.localeCompare(a.created_at))[0];
1561
- if (!latest) return [];
1603
+ if (!latest) return void 0;
1562
1604
  const assistant = await deps.store.assistants.get(latest.assistant_id);
1563
- if (!assistant) return [];
1605
+ if (!assistant) return void 0;
1564
1606
  const resolved = await deps.graphs.load(assistant.graph_id);
1565
1607
  let graph;
1566
1608
  if (typeof resolved === "function") {
@@ -1570,6 +1612,11 @@ function createThreadService(ctx) {
1570
1612
  graph = resolved;
1571
1613
  }
1572
1614
  graph.checkpointer = deps.checkpointer;
1615
+ return graph;
1616
+ };
1617
+ const readHistory = async (threadId, options) => {
1618
+ const graph = await loadThreadGraph(threadId);
1619
+ if (!graph) return [];
1573
1620
  const states = [];
1574
1621
  const limit = options?.limit;
1575
1622
  for await (const snapshot of graph.getStateHistory({
@@ -1626,6 +1673,45 @@ function createThreadService(ctx) {
1626
1673
  async getState(threadId) {
1627
1674
  const [current] = await readHistory(threadId, { limit: 1 });
1628
1675
  return current ?? emptyThreadState(threadId);
1676
+ },
1677
+ async getStateAt(threadId, checkpointId) {
1678
+ const graph = await loadThreadGraph(threadId);
1679
+ if (!graph) return emptyThreadState(threadId);
1680
+ const snapshot = await graph.getState({
1681
+ configurable: { thread_id: threadId, checkpoint_ns: "", checkpoint_id: checkpointId }
1682
+ });
1683
+ return snapshotToThreadState(snapshot);
1684
+ },
1685
+ async updateState(threadId, input) {
1686
+ const graph = await loadThreadGraph(threadId);
1687
+ if (!graph) {
1688
+ throw SkeinHttpError7.unprocessable(`Thread "${threadId}" has no graph to update state on.`);
1689
+ }
1690
+ if (await deps.store.runs.hasActiveRun(threadId)) {
1691
+ throw SkeinHttpError7.conflict(
1692
+ `Thread "${threadId}" is busy; wait for its active run to finish before updating state.`
1693
+ );
1694
+ }
1695
+ const configurable = {
1696
+ checkpoint_ns: "",
1697
+ ...input.checkpoint ?? {},
1698
+ ...input.checkpoint_id !== void 0 ? { checkpoint_id: input.checkpoint_id } : {},
1699
+ thread_id: threadId
1700
+ };
1701
+ const nextConfig = await graph.updateState({ configurable }, input.values, input.as_node);
1702
+ const snapshot = await graph.getState({ configurable: { thread_id: threadId } });
1703
+ await deps.store.threads.update(
1704
+ threadId,
1705
+ snapshotToThreadUpdate(snapshot, runStatusForSnapshot(snapshot))
1706
+ );
1707
+ const next = nextConfig.configurable ?? {};
1708
+ const checkpoint = {
1709
+ thread_id: threadId,
1710
+ checkpoint_ns: next["checkpoint_ns"] ?? "",
1711
+ checkpoint_id: next["checkpoint_id"],
1712
+ checkpoint_map: next["checkpoint_map"]
1713
+ };
1714
+ return { checkpoint };
1629
1715
  }
1630
1716
  };
1631
1717
  }
@@ -1828,9 +1914,12 @@ var ROUTE_AUTHZ = {
1828
1914
  copyThread: { resource: "threads", action: "create" },
1829
1915
  getThread: { resource: "threads", action: "read" },
1830
1916
  getThreadState: { resource: "threads", action: "read" },
1917
+ getThreadStateAtCheckpoint: { resource: "threads", action: "read" },
1831
1918
  getThreadHistory: { resource: "threads", action: "read" },
1832
1919
  listThreads: { resource: "threads", action: "search" },
1833
1920
  patchThread: { resource: "threads", action: "update" },
1921
+ // Time-travel state update forks a checkpoint — a write, so read-only principals can't fork.
1922
+ updateThreadState: { resource: "threads", action: "update" },
1834
1923
  deleteThread: { resource: "threads", action: "delete" },
1835
1924
  // runs (authorized through the owning thread)
1836
1925
  createWaitRun: { resource: "threads", action: "create_run" },
@@ -2018,6 +2107,12 @@ var skeinRoutes = [
2018
2107
  { method: "patch", path: "/threads/:thread_id", handler: "patchThread" },
2019
2108
  { method: "delete", path: "/threads/:thread_id", handler: "deleteThread" },
2020
2109
  { method: "get", path: "/threads/:thread_id/state", handler: "getThreadState" },
2110
+ { method: "post", path: "/threads/:thread_id/state", handler: "updateThreadState" },
2111
+ {
2112
+ method: "get",
2113
+ path: "/threads/:thread_id/state/:checkpoint_id",
2114
+ handler: "getThreadStateAtCheckpoint"
2115
+ },
2021
2116
  { method: "post", path: "/threads/:thread_id/history", handler: "getThreadHistory" },
2022
2117
  // runs — the stateless handlers are reused on the thread-scoped path with the id folded in
2023
2118
  { method: "post", path: "/runs/wait", handler: "createWaitRun" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skein-js/agent-protocol",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Framework-agnostic Agent Protocol engine for LangGraph.js — run engine, handlers, and SSE, driven entirely by injected dependencies.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Maina Wycliffe <wmmaina7@gmail.com>",
@@ -44,14 +44,14 @@
44
44
  },
45
45
  "dependencies": {
46
46
  "zod": "^3.25.76",
47
- "@skein-js/core": "0.7.0"
47
+ "@skein-js/core": "0.8.0"
48
48
  },
49
49
  "peerDependencies": {
50
50
  "@langchain/langgraph": "^1.4.0",
51
51
  "@langchain/langgraph-sdk": "^1.9.0"
52
52
  },
53
53
  "devDependencies": {
54
- "@skein-js/storage-memory": "0.7.0"
54
+ "@skein-js/storage-memory": "0.8.0"
55
55
  },
56
56
  "publishConfig": {
57
57
  "access": "public"