@skein-js/agent-protocol 0.3.0 → 0.5.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, Assistant, Config, StreamMode, Metadata, MultitaskStrategy, RunFrame, DefaultValues, Run, RunStatus, Item, StoreSearchQuery, SearchItem, Thread, ThreadState, StoreRepo } from '@skein-js/core';
1
+ import { GraphSchema, SkeinStore, RunQueue, RunEventBus, AuthEngine, Assistant, AuthUser, Config, StreamMode, Metadata, MultitaskStrategy, RunFrame, DefaultValues, Run, RunStatus, StorePutOptions, Item, StoreSearchQuery, SearchItem, Thread, ThreadSearchQuery, ThreadState, 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. */
@@ -119,6 +119,14 @@ interface ProtocolContext {
119
119
  deps: ResolvedDeps;
120
120
  control: RunControlRegistry;
121
121
  locks: ThreadLocks;
122
+ /**
123
+ * The authenticated caller for the current request, populated by the authorizing handler wrapper
124
+ * so the run service can stamp it onto a run's kwargs. Undefined when no auth engine is
125
+ * configured (the default), matching `langgraph dev` — no principal is injected into the graph.
126
+ */
127
+ authUser?: AuthUser;
128
+ /** The current request's authenticated permission scopes (`AuthContext.scopes`), stamped with {@link authUser}. */
129
+ authScopes?: string[];
122
130
  }
123
131
  declare function createContext(deps: ProtocolDeps): ProtocolContext;
124
132
 
@@ -159,7 +167,7 @@ interface RunService {
159
167
  }
160
168
 
161
169
  interface StoreService {
162
- put(namespace: string[], key: string, value: Record<string, unknown>): Promise<Item>;
170
+ put(namespace: string[], key: string, value: Record<string, unknown>, options?: StorePutOptions): Promise<Item>;
163
171
  get(namespace: string[], key: string): Promise<Item>;
164
172
  delete(namespace: string[], key: string): Promise<void>;
165
173
  search(query: StoreSearchQuery): Promise<SearchItem[]>;
@@ -180,7 +188,11 @@ interface ThreadService {
180
188
  create(input?: CreateThreadInput): Promise<Thread>;
181
189
  get(threadId: string): Promise<Thread>;
182
190
  list(): Promise<Thread[]>;
191
+ /** Filtered + paginated listing — `POST /threads/search`. */
192
+ search(query: ThreadSearchQuery): Promise<Thread[]>;
183
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>;
184
196
  delete(threadId: string): Promise<void>;
185
197
  history(threadId: string, options?: HistoryOptions): Promise<ThreadState[]>;
186
198
  /** The thread's current state snapshot — `GET /threads/{id}/state`, what `useStream` hydrates from. */
@@ -262,6 +274,7 @@ interface ProtocolHandlers {
262
274
  createThread: ProtocolHandler;
263
275
  getThread: ProtocolHandler;
264
276
  listThreads: ProtocolHandler;
277
+ copyThread: ProtocolHandler;
265
278
  patchThread: ProtocolHandler;
266
279
  deleteThread: ProtocolHandler;
267
280
  getThreadHistory: ProtocolHandler;
package/dist/index.js CHANGED
@@ -101,6 +101,16 @@ var threadCreateSchema = z.object({
101
101
  var threadPatchSchema = z.object({
102
102
  metadata: z.record(z.unknown()).optional()
103
103
  }).passthrough();
104
+ var threadSearchSchema = z.object({
105
+ metadata: z.record(z.unknown()).optional(),
106
+ values: z.record(z.unknown()).optional(),
107
+ status: z.enum(["idle", "busy", "interrupted", "error"]).optional(),
108
+ ids: z.array(z.string()).optional(),
109
+ limit: z.number().int().positive().optional(),
110
+ offset: z.number().int().nonnegative().optional(),
111
+ sort_by: z.enum(["thread_id", "status", "created_at", "updated_at"]).optional(),
112
+ sort_order: z.enum(["asc", "desc"]).optional()
113
+ }).passthrough();
104
114
  var assistantSearchSchema = z.object({
105
115
  graph_id: z.string().optional(),
106
116
  limit: z.number().int().positive().optional(),
@@ -109,7 +119,9 @@ var assistantSearchSchema = z.object({
109
119
  var storePutSchema = z.object({
110
120
  namespace: z.array(z.string()).min(1),
111
121
  key: z.string().min(1),
112
- value: z.record(z.unknown())
122
+ value: z.record(z.unknown()),
123
+ /** Optional item lifetime in minutes; overrides the configured `store.ttl.default_ttl`. */
124
+ ttl: z.number().positive().optional()
113
125
  }).passthrough();
114
126
  var storeSearchSchema = z.object({
115
127
  namespace_prefix: z.array(z.string()).optional(),
@@ -153,7 +165,22 @@ function createProtocolHandlers(service) {
153
165
  // --- threads ------------------------------------------------------------------------------
154
166
  createThread: async (req) => json(await service.threads.create(parse(threadCreateSchema, req.body ?? {}))),
155
167
  getThread: async (req) => json(await service.threads.get(requireParam(req.params, "thread_id"))),
156
- listThreads: async () => json(await service.threads.list()),
168
+ listThreads: async (req) => {
169
+ const body = parse(threadSearchSchema, req.body ?? {});
170
+ return json(
171
+ await service.threads.search({
172
+ metadata: body.metadata,
173
+ values: body.values,
174
+ status: body.status,
175
+ ids: body.ids,
176
+ limit: body.limit,
177
+ offset: body.offset,
178
+ sortBy: body.sort_by,
179
+ sortOrder: body.sort_order
180
+ })
181
+ );
182
+ },
183
+ copyThread: async (req) => json(await service.threads.copy(requireParam(req.params, "thread_id"))),
157
184
  patchThread: async (req) => json(
158
185
  await service.threads.patch(
159
186
  requireParam(req.params, "thread_id"),
@@ -221,7 +248,7 @@ function createProtocolHandlers(service) {
221
248
  // --- store --------------------------------------------------------------------------------
222
249
  putStoreItem: async (req) => {
223
250
  const body = parse(storePutSchema, req.body);
224
- return json(await service.store.put(body.namespace, body.key, body.value));
251
+ return json(await service.store.put(body.namespace, body.key, body.value, { ttl: body.ttl }));
225
252
  },
226
253
  getStoreItem: async (req) => {
227
254
  const namespace = namespaceFromQuery(req.query["namespace"]);
@@ -496,9 +523,11 @@ function threadStatusForRun(status) {
496
523
  case "error":
497
524
  case "timeout":
498
525
  return "error";
499
- // pending (not yet started) and success both leave the thread idle.
526
+ // pending (not yet started), success, and cancelled all leave the thread idle. A cancellation
527
+ // is a clean stop, not a failure, so the thread returns to idle rather than error.
500
528
  case "pending":
501
529
  case "success":
530
+ case "cancelled":
502
531
  return "idle";
503
532
  default:
504
533
  return "idle";
@@ -593,7 +622,10 @@ var RESERVED_CONFIGURABLE_KEYS = /* @__PURE__ */ new Set([
593
622
  "checkpoint_id",
594
623
  "checkpoint_ns",
595
624
  "checkpoint_map",
596
- "checkpoint"
625
+ "checkpoint",
626
+ "langgraph_auth_user",
627
+ "langgraph_auth_user_id",
628
+ "langgraph_auth_permissions"
597
629
  ]);
598
630
  function sanitizeConfigurable(configurable) {
599
631
  if (!configurable) return {};
@@ -604,10 +636,32 @@ function sanitizeConfigurable(configurable) {
604
636
  }
605
637
  return clean;
606
638
  }
639
+ function withAuthUser(configurable, authUser, scopes) {
640
+ if (!authUser) return configurable;
641
+ return {
642
+ ...configurable,
643
+ langgraph_auth_user: authUser,
644
+ langgraph_auth_user_id: authUser.identity,
645
+ langgraph_auth_permissions: scopes ?? authUser.permissions
646
+ };
647
+ }
648
+ function toFactoryConfigurable(kwargs) {
649
+ const configurable = withAuthUser(
650
+ sanitizeConfigurable(kwargs.config?.configurable),
651
+ kwargs.auth_user,
652
+ kwargs.auth_scopes
653
+ );
654
+ return Object.keys(configurable).length > 0 ? configurable : void 0;
655
+ }
607
656
  function toGraphCallOptions(kwargs, threadId, signal) {
608
657
  const options = {
609
- // Drop server-owned keys from the client's configurable, then force this thread's id.
610
- configurable: { ...sanitizeConfigurable(kwargs.config?.configurable), thread_id: threadId },
658
+ // Drop server-owned keys from the client's configurable, force this thread's id, then stamp the
659
+ // authenticated caller so the graph can authorize off `configurable.langgraph_auth_user`.
660
+ configurable: withAuthUser(
661
+ { ...sanitizeConfigurable(kwargs.config?.configurable), thread_id: threadId },
662
+ kwargs.auth_user,
663
+ kwargs.auth_scopes
664
+ ),
611
665
  streamMode: normalizeModes(kwargs.stream_mode),
612
666
  signal
613
667
  };
@@ -688,7 +742,12 @@ function describeInterrupts(snapshot) {
688
742
  // src/runs/run-engine.ts
689
743
  async function resolveGraph(deps, graphId, kwargs) {
690
744
  const resolved = await deps.graphs.load(graphId);
691
- const graph = typeof resolved === "function" ? await resolved({ configurable: kwargs.config?.configurable }) : resolved;
745
+ const graph = typeof resolved === "function" ? (
746
+ // Expose the authenticated caller to a graph *factory* too, so a factory that branches on the
747
+ // principal sees the same `langgraph_auth_user` a node reads — sanitized identically, so a
748
+ // client can't spoof it via its own configurable.
749
+ await resolved({ configurable: toFactoryConfigurable(kwargs) })
750
+ ) : resolved;
692
751
  graph.checkpointer = deps.checkpointer;
693
752
  graph.store = new SkeinBaseStore(deps.store.store);
694
753
  return graph;
@@ -775,9 +834,9 @@ async function executeRun(deps, exec) {
775
834
  }
776
835
  if (control.signal.aborted) {
777
836
  const timedOut = control.reason.current === "timeout";
778
- const finalStatus2 = await finalizeRun(deps, runId, timedOut ? "timeout" : "error");
837
+ const finalStatus2 = await finalizeRun(deps, runId, timedOut ? "timeout" : "cancelled");
779
838
  if (finalStatus2 === "timeout") await mirrorThreadError(deps, threadId, "Run timed out.");
780
- else if (finalStatus2 === "error") await mirrorThreadStatus(deps, threadId, "idle");
839
+ else if (finalStatus2 === "cancelled") await mirrorThreadStatus(deps, threadId, "idle");
781
840
  logFinished(finalStatus2);
782
841
  return { status: finalStatus2, values: {} };
783
842
  }
@@ -806,8 +865,8 @@ async function executeRun(deps, exec) {
806
865
  return { status: finalStatus2, values: {} };
807
866
  }
808
867
  if (reason === "cancel" || control.signal.aborted) {
809
- const finalStatus2 = await finalizeRun(deps, runId, "error");
810
- if (finalStatus2 === "error") await mirrorThreadStatus(deps, threadId, "idle");
868
+ const finalStatus2 = await finalizeRun(deps, runId, "cancelled");
869
+ if (finalStatus2 === "cancelled") await mirrorThreadStatus(deps, threadId, "idle");
811
870
  logFinished(finalStatus2);
812
871
  return { status: finalStatus2, values: {} };
813
872
  }
@@ -826,7 +885,7 @@ async function executeRun(deps, exec) {
826
885
  }
827
886
 
828
887
  // src/runs/run-service.ts
829
- function toKwargs(input) {
888
+ function toKwargs(input, authUser, authScopes) {
830
889
  const kwargs = {};
831
890
  if (input.input !== void 0) kwargs.input = input.input;
832
891
  if (input.command !== void 0) kwargs.command = input.command;
@@ -835,24 +894,44 @@ function toKwargs(input) {
835
894
  if (input.stream_mode !== void 0) kwargs.stream_mode = input.stream_mode;
836
895
  if (input.interrupt_before !== void 0) kwargs.interrupt_before = input.interrupt_before;
837
896
  if (input.interrupt_after !== void 0) kwargs.interrupt_after = input.interrupt_after;
897
+ if (authUser !== void 0) {
898
+ kwargs.auth_user = authUser;
899
+ if (authScopes !== void 0) kwargs.auth_scopes = authScopes;
900
+ }
838
901
  return kwargs;
839
902
  }
840
903
  function createRunService(ctx) {
841
- const { deps, control, locks } = ctx;
904
+ const { deps, control, locks, authUser, authScopes } = ctx;
842
905
  const requireThread = async (threadId) => {
843
- if (!await deps.store.threads.get(threadId)) {
844
- throw SkeinHttpError5.notFound(`Thread "${threadId}" not found.`);
845
- }
906
+ const thread = await deps.store.threads.get(threadId);
907
+ if (!thread) throw SkeinHttpError5.notFound(`Thread "${threadId}" not found.`);
908
+ return thread;
846
909
  };
847
910
  const requireAssistant = async (assistantId) => {
848
911
  const assistant = await deps.store.assistants.get(assistantId);
849
912
  if (!assistant) throw SkeinHttpError5.notFound(`Assistant "${assistantId}" not found.`);
850
- return assistant.assistant_id;
913
+ return assistant;
914
+ };
915
+ const stampGraphOnThread = async (thread, assistant) => {
916
+ if (thread.metadata?.["graph_id"] === assistant.graph_id && thread.metadata?.["assistant_id"] === assistant.assistant_id) {
917
+ return;
918
+ }
919
+ try {
920
+ await deps.store.threads.update(thread.thread_id, {
921
+ metadata: {
922
+ ...thread.metadata,
923
+ graph_id: assistant.graph_id,
924
+ assistant_id: assistant.assistant_id
925
+ }
926
+ });
927
+ } catch (error) {
928
+ deps.logger.warn("failed to stamp graph_id/assistant_id onto thread", error);
929
+ }
851
930
  };
852
931
  const ensureThread = async (threadId) => {
853
- if (threadId === void 0) return (await deps.store.threads.create()).thread_id;
932
+ if (threadId === void 0) return deps.store.threads.create();
854
933
  const existing = await deps.store.threads.get(threadId);
855
- return (existing ?? await deps.store.threads.create({ thread_id: threadId })).thread_id;
934
+ return existing ?? await deps.store.threads.create({ thread_id: threadId });
856
935
  };
857
936
  const createPendingRun = async (input, threadId, assistantId, kwargs) => {
858
937
  return locks.run(threadId, async () => {
@@ -884,33 +963,36 @@ function createRunService(ctx) {
884
963
  };
885
964
  return {
886
965
  async createWait(input) {
887
- const assistantId = await requireAssistant(input.assistant_id);
888
- const threadId = await ensureThread(input.thread_id);
889
- const kwargs = toKwargs(input);
890
- const run = await createPendingRun(input, threadId, assistantId, kwargs);
966
+ const assistant = await requireAssistant(input.assistant_id);
967
+ const thread = await ensureThread(input.thread_id);
968
+ const kwargs = toKwargs(input, authUser, authScopes);
969
+ const run = await createPendingRun(input, thread.thread_id, assistant.assistant_id, kwargs);
970
+ await stampGraphOnThread(thread, assistant);
891
971
  const outcome = await runInline(run, kwargs);
892
972
  return outcome.values;
893
973
  },
894
974
  async createStream(input) {
895
- const assistantId = await requireAssistant(input.assistant_id);
896
- const threadId = await ensureThread(input.thread_id);
897
- const kwargs = toKwargs(input);
898
- const run = await createPendingRun(input, threadId, assistantId, kwargs);
975
+ const assistant = await requireAssistant(input.assistant_id);
976
+ const thread = await ensureThread(input.thread_id);
977
+ const kwargs = toKwargs(input, authUser, authScopes);
978
+ const run = await createPendingRun(input, thread.thread_id, assistant.assistant_id, kwargs);
979
+ await stampGraphOnThread(thread, assistant);
899
980
  void runInline(run, kwargs).catch(
900
981
  (error) => deps.logger.error("stream run failed", error)
901
982
  );
902
983
  return { runId: run.run_id, frames: deps.bus.subscribe(run.run_id, 0) };
903
984
  },
904
985
  async createBackground(threadId, input) {
905
- const assistantId = await requireAssistant(input.assistant_id);
906
- await requireThread(threadId);
907
- const kwargs = toKwargs(input);
986
+ const assistant = await requireAssistant(input.assistant_id);
987
+ const thread = await requireThread(threadId);
988
+ const kwargs = toKwargs(input, authUser, authScopes);
908
989
  const run = await createPendingRun(
909
990
  { ...input, thread_id: threadId },
910
991
  threadId,
911
- assistantId,
992
+ assistant.assistant_id,
912
993
  kwargs
913
994
  );
995
+ await stampGraphOnThread(thread, assistant);
914
996
  await deps.queue.enqueue({ run_id: run.run_id, thread_id: threadId });
915
997
  return run;
916
998
  },
@@ -928,12 +1010,12 @@ function createRunService(ctx) {
928
1010
  if (!run) throw SkeinHttpError5.notFound(`Run "${runId}" not found.`);
929
1011
  if (isTerminalRunStatus3(run.status)) return run;
930
1012
  if (run.status === "pending") {
931
- await deps.store.runs.setStatus(runId, "error");
1013
+ await deps.store.runs.setStatus(runId, "cancelled");
932
1014
  await deps.store.threads.update(run.thread_id, { status: "idle" });
933
1015
  await deps.bus.close(runId);
934
1016
  control.abort(runId, "cancel");
935
1017
  } else {
936
- await deps.store.runs.setStatus(runId, "error");
1018
+ await deps.store.runs.setStatus(runId, "cancelled");
937
1019
  await deps.store.threads.update(run.thread_id, { status: "idle" });
938
1020
  control.abort(runId, "cancel");
939
1021
  }
@@ -960,10 +1042,12 @@ function createRunService(ctx) {
960
1042
  }
961
1043
 
962
1044
  // src/store/store-service.ts
963
- import { SkeinHttpError as SkeinHttpError6 } from "@skein-js/core";
1045
+ import {
1046
+ SkeinHttpError as SkeinHttpError6
1047
+ } from "@skein-js/core";
964
1048
  function createStoreService(deps) {
965
1049
  return {
966
- put: (namespace, key, value) => deps.store.store.put(namespace, key, value),
1050
+ put: (namespace, key, value, options) => deps.store.store.put(namespace, key, value, options),
967
1051
  async get(namespace, key) {
968
1052
  const item = await deps.store.store.get(namespace, key);
969
1053
  if (!item) {
@@ -980,10 +1064,50 @@ function createStoreService(deps) {
980
1064
  }
981
1065
 
982
1066
  // src/threads/thread-service.ts
1067
+ import {
1068
+ copyCheckpoint
1069
+ } from "@langchain/langgraph";
983
1070
  import {
984
1071
  isTerminalRunStatus as isTerminalRunStatus4,
985
1072
  SkeinHttpError as SkeinHttpError7
986
1073
  } from "@skein-js/core";
1074
+ async function copyCheckpointHistory(checkpointer, sourceId, targetId) {
1075
+ const tuples = [];
1076
+ for await (const tuple of checkpointer.list({ configurable: { thread_id: sourceId } })) {
1077
+ tuples.push(tuple);
1078
+ }
1079
+ for (const tuple of tuples.reverse()) {
1080
+ const ns = tuple.config.configurable?.checkpoint_ns ?? "";
1081
+ const parentId = tuple.parentConfig?.configurable?.checkpoint_id;
1082
+ const putConfig = {
1083
+ configurable: { thread_id: targetId, checkpoint_ns: ns, checkpoint_id: parentId }
1084
+ };
1085
+ await checkpointer.put(
1086
+ putConfig,
1087
+ copyCheckpoint(tuple.checkpoint),
1088
+ tuple.metadata ?? {},
1089
+ tuple.checkpoint.channel_versions
1090
+ );
1091
+ if (tuple.pendingWrites && tuple.pendingWrites.length > 0) {
1092
+ const writeConfig = {
1093
+ configurable: {
1094
+ thread_id: targetId,
1095
+ checkpoint_ns: ns,
1096
+ checkpoint_id: tuple.checkpoint.id
1097
+ }
1098
+ };
1099
+ const byTask = /* @__PURE__ */ new Map();
1100
+ for (const [taskId, channel, value] of tuple.pendingWrites) {
1101
+ const writes = byTask.get(taskId) ?? [];
1102
+ writes.push([channel, value]);
1103
+ byTask.set(taskId, writes);
1104
+ }
1105
+ for (const [taskId, writes] of byTask) {
1106
+ await checkpointer.putWrites(writeConfig, writes, taskId);
1107
+ }
1108
+ }
1109
+ }
1110
+ }
987
1111
  function emptyThreadState(threadId) {
988
1112
  return {
989
1113
  values: {},
@@ -1037,10 +1161,33 @@ function createThreadService(ctx) {
1037
1161
  create: (input) => deps.store.threads.create(input),
1038
1162
  get: requireThread,
1039
1163
  list: () => deps.store.threads.list(),
1164
+ search: (query) => deps.store.threads.search(query),
1040
1165
  async patch(threadId, patch) {
1041
1166
  await requireThread(threadId);
1042
1167
  return deps.store.threads.update(threadId, { metadata: patch.metadata });
1043
1168
  },
1169
+ async copy(threadId) {
1170
+ await requireThread(threadId);
1171
+ const copy = await deps.store.threads.copy(threadId);
1172
+ await copyCheckpointHistory(deps.checkpointer, threadId, copy.thread_id);
1173
+ const sourceRuns = await deps.store.runs.listByThread(threadId);
1174
+ for (const run of [...sourceRuns].sort((a, b) => a.created_at.localeCompare(b.created_at))) {
1175
+ if (!isTerminalRunStatus4(run.status)) continue;
1176
+ const kwargs = await deps.store.runs.getKwargs(run.run_id);
1177
+ await deps.store.runs.create({
1178
+ thread_id: copy.thread_id,
1179
+ assistant_id: run.assistant_id,
1180
+ status: run.status,
1181
+ metadata: run.metadata,
1182
+ multitask_strategy: run.multitask_strategy,
1183
+ ...kwargs ? { kwargs } : {}
1184
+ });
1185
+ }
1186
+ if (copy.status === "busy") {
1187
+ return deps.store.threads.update(copy.thread_id, { status: "idle" });
1188
+ }
1189
+ return copy;
1190
+ },
1044
1191
  async delete(threadId) {
1045
1192
  await requireThread(threadId);
1046
1193
  const runs = await deps.store.runs.listByThread(threadId);
@@ -1162,6 +1309,14 @@ function createAuthScopedStore(inner, engine, filters, resource) {
1162
1309
  if (resource === "threads") {
1163
1310
  const threads = {
1164
1311
  list: async () => (await inner.threads.list()).filter((thread) => matches(thread.metadata)),
1312
+ search: async (query) => {
1313
+ const { limit, offset, ...filters2 } = query;
1314
+ const owned = (await inner.threads.search(filters2)).filter(
1315
+ (thread) => matches(thread.metadata)
1316
+ );
1317
+ const start = offset ?? 0;
1318
+ return owned.slice(start, limit === void 0 ? void 0 : start + limit);
1319
+ },
1165
1320
  get: async (threadId) => {
1166
1321
  const thread = await inner.threads.get(threadId);
1167
1322
  return thread && matches(thread.metadata) ? thread : null;
@@ -1183,6 +1338,13 @@ function createAuthScopedStore(inner, engine, filters, resource) {
1183
1338
  const next = patch.metadata !== void 0 ? { ...patch, metadata: stamp(patch.metadata) } : patch;
1184
1339
  return inner.threads.update(threadId, next);
1185
1340
  },
1341
+ copy: async (threadId) => {
1342
+ const thread = await inner.threads.get(threadId);
1343
+ if (!thread || !matches(thread.metadata)) {
1344
+ throw SkeinHttpError9.notFound(`Thread "${threadId}" not found.`);
1345
+ }
1346
+ return inner.threads.copy(threadId);
1347
+ },
1186
1348
  delete: async (threadId) => {
1187
1349
  const thread = await inner.threads.get(threadId);
1188
1350
  if (!thread || !matches(thread.metadata)) {
@@ -1227,6 +1389,7 @@ var ROUTE_AUTHZ = {
1227
1389
  searchAssistants: { resource: "assistants", action: "search" },
1228
1390
  // threads
1229
1391
  createThread: { resource: "threads", action: "create" },
1392
+ copyThread: { resource: "threads", action: "create" },
1230
1393
  getThread: { resource: "threads", action: "read" },
1231
1394
  getThreadState: { resource: "threads", action: "read" },
1232
1395
  getThreadHistory: { resource: "threads", action: "read" },
@@ -1281,7 +1444,7 @@ var STUDIO_USER = {
1281
1444
  };
1282
1445
  function createAuthorizingHandlers(context, engine) {
1283
1446
  const baseHandlers = createProtocolHandlers(buildProtocolService(context));
1284
- const names = Object.keys(baseHandlers);
1447
+ const names = Object.keys(ROUTE_AUTHZ);
1285
1448
  const resolveAuthContext = async (req) => {
1286
1449
  if (!engine.studioAuthDisabled && req.headers["x-auth-scheme"] === "langsmith") {
1287
1450
  return { user: STUDIO_USER, scopes: [] };
@@ -1299,13 +1462,17 @@ function createAuthorizingHandlers(context, engine) {
1299
1462
  value: authValue(req),
1300
1463
  context: authContext
1301
1464
  });
1302
- if (!filters) return baseHandlers[name](req);
1303
- const scopedStore = createAuthScopedStore(context.deps.store, engine, filters, route.resource);
1304
- const scopedContext = {
1465
+ if (!filters && !authContext) return baseHandlers[name](req);
1466
+ const requestContext = {
1305
1467
  ...context,
1306
- deps: { ...context.deps, store: scopedStore }
1468
+ authUser: authContext?.user,
1469
+ authScopes: authContext?.scopes,
1470
+ deps: filters ? {
1471
+ ...context.deps,
1472
+ store: createAuthScopedStore(context.deps.store, engine, filters, route.resource)
1473
+ } : context.deps
1307
1474
  };
1308
- return createProtocolHandlers(buildProtocolService(scopedContext))[name](req);
1475
+ return createProtocolHandlers(buildProtocolService(requestContext))[name](req);
1309
1476
  };
1310
1477
  }
1311
1478
  return wrapped;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skein-js/agent-protocol",
3
- "version": "0.3.0",
3
+ "version": "0.5.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.3.0"
47
+ "@skein-js/core": "0.5.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.3.0"
54
+ "@skein-js/storage-memory": "0.5.0"
55
55
  },
56
56
  "publishConfig": {
57
57
  "access": "public"