@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 +25 -1
- package/dist/index.js +102 -7
- package/package.json +3 -3
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,
|
|
994
|
-
//
|
|
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
|
-
{
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
54
|
+
"@skein-js/storage-memory": "0.8.0"
|
|
55
55
|
},
|
|
56
56
|
"publishConfig": {
|
|
57
57
|
"access": "public"
|