@opengeni/api-router 2.10.0-canary.2 → 2.10.0-canary.4

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.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  createOpenGeniSlackBotInteractionClient,
8
8
  startSlackInteractionPump,
9
9
  startTemporalScheduleCleanupPump
10
- } from "./chunk-BVX5ASFY.js";
10
+ } from "./chunk-NWSVDE7O.js";
11
11
 
12
12
  // src/index.ts
13
13
  import {
@@ -82,8 +82,8 @@ export declare function scheduledTaskMcpSummary(task: ScheduledTask): {
82
82
  runMode: "existing_session" | "new_session_per_run" | "reusable_session";
83
83
  overlapPolicy: "allow_concurrent" | "buffer_one" | "skip";
84
84
  action: {
85
- readonly sourceId?: never;
86
85
  kind: "agent_turn";
86
+ sourceId?: never;
87
87
  sourceGeneration?: never;
88
88
  sourceLifecycleGeneration?: never;
89
89
  destination?: never;
@@ -205,8 +205,8 @@ export declare function boundScheduledTaskDetailMcp(task: ScheduledTask): {
205
205
  runMode: "existing_session" | "new_session_per_run" | "reusable_session";
206
206
  overlapPolicy: "allow_concurrent" | "buffer_one" | "skip";
207
207
  action: {
208
- readonly sourceId?: never;
209
208
  kind: "agent_turn";
209
+ sourceId?: never;
210
210
  sourceGeneration?: never;
211
211
  sourceLifecycleGeneration?: never;
212
212
  destination?: never;
@@ -389,8 +389,8 @@ export declare function boundScheduledTaskMcpPage(input: {
389
389
  runMode: "existing_session" | "new_session_per_run" | "reusable_session";
390
390
  overlapPolicy: "allow_concurrent" | "buffer_one" | "skip";
391
391
  action: {
392
- readonly sourceId?: never;
393
392
  kind: "agent_turn";
393
+ sourceId?: never;
394
394
  sourceGeneration?: never;
395
395
  sourceLifecycleGeneration?: never;
396
396
  destination?: never;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/api-router",
3
- "version": "2.10.0-canary.2",
3
+ "version": "2.10.0-canary.4",
4
4
  "description": "OpenGeni HTTP surface: the Hono adapter/router (createApp), routes, MCP HTTP transport, and HTTP access adapters over @opengeni/core. An engine-distribution surface — its runtime closure includes engine-internal packages.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -43,24 +43,24 @@
43
43
  "@hono/zod-validator": "^0.7.6",
44
44
  "@llamaindex/liteparse": "2.14.2",
45
45
  "@modelcontextprotocol/sdk": "^1.29.0",
46
- "@opengeni/agent-proto": "^0.5.1-canary.11",
47
- "@opengeni/artifact-tool": "^0.3.19-canary.2",
48
- "@opengeni/capabilities": "^0.3.2-canary.2",
49
- "@opengeni/codemode": "^0.4.27-canary.2",
50
- "@opengeni/codex": "^0.2.21-canary.2",
51
- "@opengeni/config": "^1.0.0-canary.2",
52
- "@opengeni/contracts": "^2.13.0-canary.2",
53
- "@opengeni/core": "^2.7.5-canary.2",
54
- "@opengeni/db": "^4.0.0-canary.2",
55
- "@opengeni/documents": "^0.8.19-canary.2",
56
- "@opengeni/events": "^0.4.17-canary.2",
57
- "@opengeni/github": "^0.7.3-canary.2",
58
- "@opengeni/network": "^0.3.0-canary.2",
59
- "@opengeni/observability": "^0.8.19-canary.2",
60
- "@opengeni/runtime": "^2.3.0-canary.2",
61
- "@opengeni/storage": "^0.2.120-canary.2",
62
- "@opengeni/tool-gateway": "^0.1.0-canary.2",
63
- "@opengeni/xai-subscription": "^0.1.3-canary.2",
46
+ "@opengeni/agent-proto": "^0.5.1-canary.13",
47
+ "@opengeni/artifact-tool": "^0.3.19-canary.4",
48
+ "@opengeni/capabilities": "^0.3.2-canary.4",
49
+ "@opengeni/codemode": "^0.4.27-canary.4",
50
+ "@opengeni/codex": "^0.2.21-canary.4",
51
+ "@opengeni/config": "^1.0.0-canary.4",
52
+ "@opengeni/contracts": "^2.13.0-canary.4",
53
+ "@opengeni/core": "^2.7.5-canary.4",
54
+ "@opengeni/db": "^4.0.0-canary.4",
55
+ "@opengeni/documents": "^0.8.19-canary.4",
56
+ "@opengeni/events": "^0.4.17-canary.4",
57
+ "@opengeni/github": "^0.7.3-canary.4",
58
+ "@opengeni/network": "^0.3.0-canary.4",
59
+ "@opengeni/observability": "^0.8.19-canary.4",
60
+ "@opengeni/runtime": "^2.3.0-canary.4",
61
+ "@opengeni/storage": "^0.2.120-canary.4",
62
+ "@opengeni/tool-gateway": "^0.1.0-canary.4",
63
+ "@opengeni/xai-subscription": "^0.1.3-canary.4",
64
64
  "@temporalio/client": "^1.17.0",
65
65
  "better-auth": "^1.6.14",
66
66
  "hono": "^4.12.18",
package/src/app.ts CHANGED
@@ -1974,6 +1974,10 @@ const routeLabelPatterns: Array<{
1974
1974
  pattern: /^\/v1\/workspaces\/[^/]+\/inference-control$/,
1975
1975
  label: "/v1/workspaces/:workspaceId/inference-control",
1976
1976
  },
1977
+ {
1978
+ pattern: /^\/v1\/workspaces\/[^/]+\/pause-timer$/,
1979
+ label: "/v1/workspaces/:workspaceId/pause-timer",
1980
+ },
1977
1981
  {
1978
1982
  pattern:
1979
1983
  /^\/v1\/workspaces\/[^/]+\/sessions\/[^/]+\/fs\/(list|list-batch|read|write|delete|move|mkdir)$/,
@@ -1214,7 +1214,7 @@ export function registerCodexRoutes(app: Hono, deps: ApiRouteDeps): void {
1214
1214
  accountId: grant.accountId,
1215
1215
  workspaceId,
1216
1216
  subjectId: grant.subjectId,
1217
- mode: sourceBeforeConnect.workspaceKind === "personal" ? "automatic" : "workspace",
1217
+ mode: "workspace",
1218
1218
  effectiveSourceBeforeMutation: sourceBeforeConnect.effectiveSource,
1219
1219
  });
1220
1220
  const rotation = await getCodexRotationSettings(tx, workspaceId);
@@ -14,6 +14,7 @@ import {
14
14
  } from "@opengeni/db";
15
15
  import {
16
16
  requireAccessGrant,
17
+ requireWorkspaceSettingsGrant,
17
18
  VIDEO_GENERATION_MODEL_CATALOG,
18
19
  videoGenerationModelSupportsFundingSource,
19
20
  videoGenerationCapabilitiesForPolicy,
@@ -58,7 +59,7 @@ export function registerVideoGenerationRoutes(app: Hono, deps: ApiRouteDeps): vo
58
59
 
59
60
  app.put("/v1/workspaces/:workspaceId/video-generation/policy", async (c) => {
60
61
  const workspaceId = c.req.param("workspaceId");
61
- const grant = await requireAccessGrant(c, deps, workspaceId, "workspace:admin");
62
+ const grant = await requireWorkspaceSettingsGrant(c, deps, workspaceId);
62
63
  const payload = UpdateVideoGenerationPolicyRequest.parse(await c.req.json());
63
64
  const [connection, supergrokConfigured] = await Promise.all([
64
65
  getWorkspaceVercelAiGatewayConnectionMetadata(deps.db, workspaceId),
@@ -181,7 +181,9 @@ export function registerWorkspaceArtifactRoutes(app: Hono, deps: ApiRouteDeps):
181
181
  const workspaceId = context.req.param("workspaceId");
182
182
  await requireAccessGrant(context, deps, workspaceId, "artifacts:read");
183
183
  if (!deps.objectStorage)
184
- throw new HTTPException(503, { message: "Object storage is not configured" });
184
+ throw new HTTPException(503, {
185
+ message: "Object storage is not configured",
186
+ });
185
187
  const ref = await getWorkspaceArtifactContentRef(
186
188
  deps.db,
187
189
  workspaceId,
@@ -193,13 +195,20 @@ export function registerWorkspaceArtifactRoutes(app: Hono, deps: ApiRouteDeps):
193
195
 
194
196
  app.get(base, async (context) => {
195
197
  const workspaceId = context.req.param("workspaceId");
196
- await requireAccessGrant(context, deps, workspaceId, "artifacts:read");
198
+ const grant = await requireAccessGrant(context, deps, workspaceId, "artifacts:read");
197
199
  const query = WorkspaceArtifactListQuery.safeParse({
198
200
  limit: context.req.query("limit"),
199
201
  cursor: context.req.query("cursor"),
200
202
  status: context.req.query("status"),
203
+ sourceSessionId: context.req.query("sourceSessionId"),
201
204
  });
202
205
  if (!query.success) throw new HTTPException(422, { message: "Invalid artifact list query" });
206
+ if (
207
+ query.data.sourceSessionId &&
208
+ !(await canReadProvenanceSession(deps, grant, query.data.sourceSessionId))
209
+ ) {
210
+ throw new HTTPException(404, { message: "Session not found" });
211
+ }
203
212
  try {
204
213
  return context.json(
205
214
  WorkspaceArtifactListResponse.parse(
@@ -208,6 +217,9 @@ export function registerWorkspaceArtifactRoutes(app: Hono, deps: ApiRouteDeps):
208
217
  limit: query.data.limit,
209
218
  ...(query.data.cursor ? { cursor: query.data.cursor } : {}),
210
219
  ...(query.data.status ? { status: query.data.status } : {}),
220
+ ...(query.data.sourceSessionId
221
+ ? { sourceSessionId: query.data.sourceSessionId }
222
+ : {}),
211
223
  }),
212
224
  ),
213
225
  ),
@@ -281,7 +293,9 @@ export function registerWorkspaceArtifactRoutes(app: Hono, deps: ApiRouteDeps):
281
293
  const workspaceId = context.req.param("workspaceId");
282
294
  await requireAccessGrant(context, deps, workspaceId, "artifacts:read");
283
295
  if (!deps.objectStorage)
284
- throw new HTTPException(503, { message: "Object storage is not configured" });
296
+ throw new HTTPException(503, {
297
+ message: "Object storage is not configured",
298
+ });
285
299
  const ref = await getWorkspaceArtifactContentRef(
286
300
  deps.db,
287
301
  workspaceId,
@@ -12,6 +12,7 @@ import {
12
12
  import {
13
13
  publishGovernedLearningEventToSlack,
14
14
  requireAccessGrant,
15
+ requireWorkspaceSettingsGrant,
15
16
  type ApiRouteDeps,
16
17
  } from "@opengeni/core";
17
18
  import {
@@ -122,7 +123,7 @@ export function registerWorkspaceLearningRoutes(app: Hono, deps: ApiRouteDeps):
122
123
 
123
124
  app.post(`${base}/revisions`, async (context) => {
124
125
  const workspaceId = context.req.param("workspaceId");
125
- const grant = await requireAccessGrant(context, deps, workspaceId, "workspace:admin");
126
+ const grant = await requireWorkspaceSettingsGrant(context, deps, workspaceId);
126
127
  const request = await parseBody(context, CreateWorkspaceLearningPolicyRevisionRequest);
127
128
  try {
128
129
  return context.json(
@@ -149,7 +150,7 @@ export function registerWorkspaceLearningRoutes(app: Hono, deps: ApiRouteDeps):
149
150
  const workspaceId = context.req.param("workspaceId");
150
151
  const revisionId = z.string().uuid().safeParse(context.req.param("revisionId"));
151
152
  if (!revisionId.success) throw new HTTPException(422, { message: "Invalid revision id" });
152
- const grant = await requireAccessGrant(context, deps, workspaceId, "workspace:admin");
153
+ const grant = await requireWorkspaceSettingsGrant(context, deps, workspaceId);
153
154
  const request = await parseBody(context, ActivateWorkspaceLearningPolicyRevisionRequest);
154
155
  try {
155
156
  return context.json(
@@ -174,7 +175,7 @@ export function registerWorkspaceLearningRoutes(app: Hono, deps: ApiRouteDeps):
174
175
 
175
176
  app.post(`${base}/rollback`, async (context) => {
176
177
  const workspaceId = context.req.param("workspaceId");
177
- const grant = await requireAccessGrant(context, deps, workspaceId, "workspace:admin");
178
+ const grant = await requireWorkspaceSettingsGrant(context, deps, workspaceId);
178
179
  const request = await parseBody(context, RollbackWorkspaceLearningPolicyRevisionRequest);
179
180
  try {
180
181
  return context.json(
@@ -206,7 +207,7 @@ export function registerWorkspaceLearningRoutes(app: Hono, deps: ApiRouteDeps):
206
207
  if (!activationReceiptId.success) {
207
208
  throw new HTTPException(422, { message: "Invalid activation receipt id" });
208
209
  }
209
- const grant = await requireAccessGrant(context, deps, workspaceId, "workspace:admin");
210
+ const grant = await requireWorkspaceSettingsGrant(context, deps, workspaceId);
210
211
  const request = await parseBody(context, UndoGovernedLearningActivationHttpRequest);
211
212
  try {
212
213
  const undo = await undoGovernedLearningActivation(deps.db, {
@@ -1,3 +1,5 @@
1
+ import { SessionControlConflictError, WorkspacePauseTimerInputError } from "@opengeni/db";
2
+ import { WorkspacePauseTimerRequest } from "@opengeni/contracts";
1
3
  import { createHash } from "node:crypto";
2
4
  import {
3
5
  AddWorkspaceMemberRequest,
@@ -86,6 +88,7 @@ import {
86
88
  hasPermission,
87
89
  requireAccessContext,
88
90
  requireAccessGrant,
91
+ requireWorkspaceSettingsGrant,
89
92
  requireFreshAccessGrant,
90
93
  resolveWorkspaceCatalogSettings,
91
94
  } from "@opengeni/core";
@@ -95,6 +98,7 @@ import {
95
98
  assertWorkspaceMemberRemovable,
96
99
  assertWorkspaceMemberUpdateAllowed,
97
100
  controlHumanWorkspace,
101
+ controlHumanWorkspaceTimer,
98
102
  } from "@opengeni/core";
99
103
  import { boundedLimit } from "../http/common";
100
104
  import { ApiHttpError } from "../http/api-error";
@@ -367,7 +371,7 @@ export function registerWorkspaceRoutes(app: Hono, deps: ApiRouteDeps): void {
367
371
  // deep-merges (top-level) a settings patch, preserving unknown/future keys.
368
372
  app.patch("/v1/workspaces/:workspaceId/settings", async (c) => {
369
373
  const workspaceId = c.req.param("workspaceId");
370
- await requireAccessGrant(c, deps, workspaceId, "workspace:admin");
374
+ await requireWorkspaceSettingsGrant(c, deps, workspaceId);
371
375
  const parsed = UpdateWorkspaceSettingsRequest.safeParse(await c.req.json());
372
376
  if (!parsed.success) {
373
377
  throw new HTTPException(400, {
@@ -476,7 +480,7 @@ export function registerWorkspaceRoutes(app: Hono, deps: ApiRouteDeps): void {
476
480
 
477
481
  app.post("/v1/workspaces/:workspaceId/gateway-custom-models", async (c) => {
478
482
  const workspaceId = c.req.param("workspaceId");
479
- const grant = await requireAccessGrant(c, deps, workspaceId, "workspace:admin");
483
+ const grant = await requireWorkspaceSettingsGrant(c, deps, workspaceId);
480
484
  const parsed = CreateWorkspaceGatewayCustomModelRequest.safeParse(
481
485
  await c.req.json().catch(() => null),
482
486
  );
@@ -552,7 +556,7 @@ export function registerWorkspaceRoutes(app: Hono, deps: ApiRouteDeps): void {
552
556
 
553
557
  app.delete("/v1/workspaces/:workspaceId/gateway-custom-models/:customModelId", async (c) => {
554
558
  const workspaceId = c.req.param("workspaceId");
555
- const grant = await requireAccessGrant(c, deps, workspaceId, "workspace:admin");
559
+ const grant = await requireWorkspaceSettingsGrant(c, deps, workspaceId);
556
560
  const customModelId = c.req.param("customModelId");
557
561
  if (
558
562
  !/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu.test(
@@ -613,7 +617,7 @@ export function registerWorkspaceRoutes(app: Hono, deps: ApiRouteDeps): void {
613
617
 
614
618
  app.post("/v1/workspaces/:workspaceId/openrouter-custom-models", async (c) => {
615
619
  const workspaceId = c.req.param("workspaceId");
616
- const grant = await requireAccessGrant(c, deps, workspaceId, "workspace:admin");
620
+ const grant = await requireWorkspaceSettingsGrant(c, deps, workspaceId);
617
621
  const parsed = CreateWorkspaceOpenRouterCustomModelRequest.safeParse(
618
622
  await c.req.json().catch(() => null),
619
623
  );
@@ -691,7 +695,7 @@ export function registerWorkspaceRoutes(app: Hono, deps: ApiRouteDeps): void {
691
695
 
692
696
  app.delete("/v1/workspaces/:workspaceId/openrouter-custom-models/:customModelId", async (c) => {
693
697
  const workspaceId = c.req.param("workspaceId");
694
- const grant = await requireAccessGrant(c, deps, workspaceId, "workspace:admin");
698
+ const grant = await requireWorkspaceSettingsGrant(c, deps, workspaceId);
695
699
  const customModelId = c.req.param("customModelId");
696
700
  if (
697
701
  !/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu.test(
@@ -804,12 +808,12 @@ export function registerWorkspaceRoutes(app: Hono, deps: ApiRouteDeps): void {
804
808
  });
805
809
 
806
810
  // Full replace (PUT, not merge): null/omitted = unrestricted for that
807
- // dimension; an empty array is a valid explicit total block. Admin access —
808
- // this decides whether turns can reach paid providers, so it is the same
809
- // trust level as billing-affecting workspace settings.
811
+ // dimension; an empty array is a valid explicit total block. Settings access
812
+ // admits workspace administrators and the verified Personal owner, without
813
+ // widening membership or API-key delegation authority.
810
814
  app.put("/v1/workspaces/:workspaceId/model-policy", async (c) => {
811
815
  const workspaceId = c.req.param("workspaceId");
812
- const grant = await requireAccessGrant(c, deps, workspaceId, "workspace:admin");
816
+ const grant = await requireWorkspaceSettingsGrant(c, deps, workspaceId);
813
817
  const payload = UpdateWorkspaceModelPolicyRequest.parse(await c.req.json());
814
818
  const catalog = await resolveWorkspaceCatalogSettings(deps.db, deps.settings, {
815
819
  accountId: grant.accountId,
@@ -824,9 +828,42 @@ export function registerWorkspaceRoutes(app: Hono, deps: ApiRouteDeps): void {
824
828
  return c.json(policy);
825
829
  });
826
830
 
831
+ app.post("/v1/workspaces/:workspaceId/pause-timer", async (c) => {
832
+ const workspaceId = c.req.param("workspaceId");
833
+ const grant = await requireWorkspaceSettingsGrant(c, deps, workspaceId);
834
+ if (workspaceControlUtf8Bytes(grant.subjectId) > WORKSPACE_CONTROL_ACTOR_MAX_BYTES) {
835
+ throw new HTTPException(400, { message: "workspace-control actor is too large" });
836
+ }
837
+ const parsed = WorkspacePauseTimerRequest.safeParse(await c.req.json().catch(() => null));
838
+ if (!parsed.success) throw new HTTPException(400, { message: "Invalid pause timer" });
839
+ try {
840
+ await controlHumanWorkspaceTimer(
841
+ {
842
+ db: deps.db,
843
+ bus: deps.bus,
844
+ workflowClient: deps.workflowClient,
845
+ ...(deps.schedulePromptPostCommit
846
+ ? { schedulePromptPostCommit: deps.schedulePromptPostCommit }
847
+ : {}),
848
+ },
849
+ { accountId: grant.accountId, workspaceId, subjectId: grant.subjectId },
850
+ parsed.data,
851
+ );
852
+ } catch (error) {
853
+ if (error instanceof SessionControlConflictError)
854
+ throw new HTTPException(409, {
855
+ message: "Workspace changed. Reopen the timer and try again.",
856
+ });
857
+ if (error instanceof WorkspacePauseTimerInputError)
858
+ throw new HTTPException(400, { message: error.message });
859
+ throw error;
860
+ }
861
+ return c.json({ ok: true });
862
+ });
863
+
827
864
  app.post("/v1/workspaces/:workspaceId/inference-control", async (c) => {
828
865
  const workspaceId = c.req.param("workspaceId");
829
- const grant = await requireAccessGrant(c, deps, workspaceId, "workspace:admin");
866
+ const grant = await requireWorkspaceSettingsGrant(c, deps, workspaceId);
830
867
  if (workspaceControlUtf8Bytes(grant.subjectId) > WORKSPACE_CONTROL_ACTOR_MAX_BYTES) {
831
868
  throw new HTTPException(400, {
832
869
  message: "workspace-control actor is too large",