@opengeni/contracts 0.15.0 → 0.19.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/src/index.ts CHANGED
@@ -22,6 +22,30 @@ export {
22
22
  type SessionEventPayloadTruncation,
23
23
  } from "./event-preview";
24
24
 
25
+ export {
26
+ RETAINED_OUTPUT_DEFAULT_PAGE_BYTES,
27
+ RETAINED_OUTPUT_MAX_PAGE_BYTES,
28
+ RETAINED_OUTPUT_RECEIPT_MAX_BYTES,
29
+ RetainedArtifactMetadataSchema,
30
+ RetainedArtifactReferenceSchema,
31
+ RetainedArtifactUnavailableSchema,
32
+ RetainedOutputEvidenceSchema,
33
+ RetainedOutputKind,
34
+ RetainedOutputUnavailableReason,
35
+ retainedArtifactReferenceFromFile,
36
+ retainedOutputUnavailable,
37
+ resolveRetainedOutputRange,
38
+ validateRetainedOutputEvidence,
39
+ type RetainedArtifactFileInput,
40
+ type RetainedArtifactMetadata,
41
+ type RetainedArtifactReference,
42
+ type RetainedArtifactUnavailable,
43
+ type RetainedOutputAvailableEvidence,
44
+ type RetainedOutputEvidence,
45
+ type RetainedOutputRangeResolution,
46
+ type RetainedOutputResolvedRange,
47
+ } from "./retained-output";
48
+
25
49
  export const SessionStatus = z.enum([
26
50
  "queued",
27
51
  "running",
@@ -444,6 +468,8 @@ export const ErrorCode = z.enum([
444
468
  "conflict",
445
469
  "idempotency_conflict",
446
470
  "limit_exceeded",
471
+ "nested_agent_depth_exceeded",
472
+ "nested_agent_depth_override_forbidden",
447
473
  "provider_verification_failed",
448
474
  "upstream_unavailable",
449
475
  "internal_error",
@@ -460,6 +486,45 @@ export const ErrorEnvelope = z.object({
460
486
  });
461
487
  export type ErrorEnvelope = z.infer<typeof ErrorEnvelope>;
462
488
 
489
+ /** Physical ceiling of the PostgreSQL integer columns that persist depth policy. */
490
+ export const MAX_NESTED_AGENT_DEPTH = 2_147_483_647;
491
+ export const NestedAgentDepthValue = z.number().int().nonnegative().max(MAX_NESTED_AGENT_DEPTH);
492
+ export type NestedAgentDepthValue = z.infer<typeof NestedAgentDepthValue>;
493
+ /** A denied child can be one greater than the persisted PostgreSQL int ceiling. */
494
+ export const NestedAgentDepthAttemptValue = z
495
+ .number()
496
+ .int()
497
+ .nonnegative()
498
+ .max(MAX_NESTED_AGENT_DEPTH + 1);
499
+
500
+ export const NestedAgentDepthPolicySource = z.enum([
501
+ "session",
502
+ "workspace",
503
+ "deployment",
504
+ "default",
505
+ ]);
506
+ export type NestedAgentDepthPolicySource = z.infer<typeof NestedAgentDepthPolicySource>;
507
+
508
+ /** Durable evidence for a session-create denial at the database admission boundary. */
509
+ export const SessionSpawnDenial = z.object({
510
+ id: z.string().uuid(),
511
+ accountId: z.string().uuid(),
512
+ workspaceId: z.string().uuid(),
513
+ parentSessionId: z.string().uuid().nullable(),
514
+ rootSessionId: z.string().uuid().nullable(),
515
+ currentDepth: NestedAgentDepthValue,
516
+ attemptedDepth: NestedAgentDepthAttemptValue,
517
+ effectiveMaxNestedAgentDepth: NestedAgentDepthValue,
518
+ requestedMaxNestedAgentDepthOverride: NestedAgentDepthValue.nullable(),
519
+ policySource: NestedAgentDepthPolicySource,
520
+ policySessionId: z.string().uuid().nullable(),
521
+ subjectId: z.string().nullable(),
522
+ code: z.enum(["nested_agent_depth_exceeded", "nested_agent_depth_override_forbidden"]),
523
+ idempotencyKey: z.string().nullable(),
524
+ createdAt: z.string(),
525
+ });
526
+ export type SessionSpawnDenial = z.infer<typeof SessionSpawnDenial>;
527
+
463
528
  export const Permission = z.enum([
464
529
  "account:read",
465
530
  "account:admin",
@@ -535,6 +600,30 @@ export const Permission = z.enum([
535
600
  ]);
536
601
  export type Permission = z.infer<typeof Permission>;
537
602
 
603
+ /**
604
+ * Capability-first permissions signed into a session's first-party OpenGeni
605
+ * MCP token when a top-level creator does not explicitly narrow them.
606
+ *
607
+ * Keep this contract shared by admission and runtime signing: a worker-signed
608
+ * child whose parent was narrowed must inherit the parent's effective subset,
609
+ * never fall back to a different runtime-local default.
610
+ */
611
+ export const DEFAULT_FIRST_PARTY_MCP_PERMISSIONS = [
612
+ "workspace:read",
613
+ "files:read",
614
+ "documents:search",
615
+ "scheduled_tasks:manage",
616
+ "scheduled_tasks:run",
617
+ "goals:manage",
618
+ "sessions:read",
619
+ "sessions:create",
620
+ "sessions:control",
621
+ "variable-sets:use",
622
+ "variable-sets:manage",
623
+ "rigs:use",
624
+ "github:use",
625
+ ] as const satisfies readonly Permission[];
626
+
538
627
  export function prefixedMcpToolName(registryId: string, toolName: string): string {
539
628
  return `${registryId}__${toolName}`;
540
629
  }
@@ -886,6 +975,9 @@ export const WorkspaceSettingsSchema = z
886
975
  .object({
887
976
  memoryEnabled: z.boolean().optional(),
888
977
  transcription: WorkspaceTranscriptionPolicy.optional(),
978
+ // null clears the workspace override and falls back to the persisted
979
+ // deployment policy. The database boundary validates the same range.
980
+ maxNestedAgentDepth: NestedAgentDepthValue.nullable().optional(),
889
981
  })
890
982
  .passthrough();
891
983
  export type WorkspaceSettings = z.infer<typeof WorkspaceSettingsSchema>;
@@ -903,6 +995,7 @@ export const UpdateWorkspaceSettingsRequest = z
903
995
  .object({
904
996
  memoryEnabled: z.boolean().optional(),
905
997
  transcription: WorkspaceTranscriptionPolicy.optional(),
998
+ maxNestedAgentDepth: NestedAgentDepthValue.nullable().optional(),
906
999
  })
907
1000
  .passthrough();
908
1001
  export type UpdateWorkspaceSettingsRequest = z.infer<typeof UpdateWorkspaceSettingsRequest>;
@@ -1737,11 +1830,39 @@ export type GitCredentialsRequest = {
1737
1830
  repositoryIds: number[];
1738
1831
  };
1739
1832
 
1833
+ /**
1834
+ * One exact repository route exposed by a host-owned HTTPS smart-Git broker.
1835
+ *
1836
+ * `repositoryUri` must echo one URI from the request's `repositoryRefs`.
1837
+ * `brokerUri` is a stable, credential-free HTTPS remote. The rotating bearer
1838
+ * remains separate in `GitCredentials.token`, so it cannot leak through Git
1839
+ * configuration, provider-CLI arguments, manifests, or repository metadata.
1840
+ */
1841
+ export type GitHttpBrokerRepositoryRoute = {
1842
+ repositoryUri: string;
1843
+ brokerUri: string;
1844
+ };
1845
+
1846
+ /**
1847
+ * Optional transport override for credentials that cannot be safely narrowed
1848
+ * into a provider token. Omission retains the provider-token behavior.
1849
+ */
1850
+ export type GitCredentialTransport = {
1851
+ kind: "http_broker";
1852
+ repositories: GitHttpBrokerRepositoryRoute[];
1853
+ };
1854
+
1740
1855
  export type GitCredentials = {
1741
- // The minted provider token. Required for purpose="token"; optional for
1742
- // purpose="identity" so hosts can return only stable git identity before lazy
1743
- // sandbox provision. The value never enters the manifest.
1856
+ // The minted secret. For the default transport this is a provider token; for
1857
+ // `http_broker` it is the broker bearer. Required for purpose="token";
1858
+ // optional for purpose="identity" so hosts can return only stable git identity
1859
+ // before lazy sandbox provision. The value never enters the manifest.
1744
1860
  token?: string;
1861
+ // A host-owned exact smart-Git transport for providers whose available token
1862
+ // cannot be constrained to the selected repositories. OpenGeni rewrites only
1863
+ // the echoed repository remotes and never exposes this bearer to provider
1864
+ // CLIs. Omitted means the token is a direct provider credential.
1865
+ transport?: GitCredentialTransport;
1745
1866
  // workspace-scope cross-check echo: the workspace the provider scoped this token to. The activity
1746
1867
  // asserts `workspaceId === request.workspaceId` before injecting.
1747
1868
  workspaceId: string;
@@ -1964,6 +2085,8 @@ export type McpCredentialsRequest = {
1964
2085
  /** Immediate technical caller, retained only as non-authoritative audit context. */
1965
2086
  callerSubjectId?: string;
1966
2087
  surface: "model" | "toolspace";
2088
+ /** Canonical MCP destination that will receive the resolved headers. */
2089
+ destinationUrl: string;
1967
2090
  serverId: string;
1968
2091
  toolName?: string;
1969
2092
  connectionRef: McpServerConnectionRef;
@@ -2604,6 +2727,63 @@ export const ToolRef = z.object({
2604
2727
  export type ToolRef = z.infer<typeof ToolRef>;
2605
2728
 
2606
2729
  const registryId = /^[A-Za-z0-9_-]+$/;
2730
+ export const SessionMcpServerId = z.string().min(1).regex(registryId);
2731
+ export type SessionMcpServerId = z.infer<typeof SessionMcpServerId>;
2732
+
2733
+ // How a session's persisted `tools` snapshot was selected. `legacy` is
2734
+ // reserved for rows written before this descriptor existed; those rows must
2735
+ // keep their materialized historical allow-list rather than being guessed to
2736
+ // mean either omitted or explicitly empty.
2737
+ export const SessionToolPolicy = z.object({
2738
+ mode: z.enum(["workspace_default", "explicit", "inherited", "legacy"]),
2739
+ inheritedFromSessionId: z.string().uuid().nullable(),
2740
+ });
2741
+ export type SessionToolPolicy = z.infer<typeof SessionToolPolicy>;
2742
+
2743
+ export const SESSION_EFFECTIVE_TOOL_POLICY_ID_LIMIT = 64;
2744
+ export const SESSION_EFFECTIVE_TOOL_POLICY_ID_MAX_LENGTH = 200;
2745
+ const SessionEffectiveToolPolicyId = z
2746
+ .string()
2747
+ .min(1)
2748
+ .max(SESSION_EFFECTIVE_TOOL_POLICY_ID_MAX_LENGTH)
2749
+ .regex(registryId);
2750
+ const SessionEffectiveToolPolicyIds = z
2751
+ .array(SessionEffectiveToolPolicyId)
2752
+ .max(SESSION_EFFECTIVE_TOOL_POLICY_ID_LIMIT);
2753
+
2754
+ // Secret-safe, read-time policy truth. This projection contains only bounded
2755
+ // MCP registry ids and exact counts: never URLs, names, headers, credentials,
2756
+ // connector configuration, or tool schemas. IDs are samples when capped;
2757
+ // counts remain exact and idsTruncated makes that explicit to clients.
2758
+ export const SessionEffectiveToolPolicy = z
2759
+ .object({
2760
+ mode: z.enum(["workspace_default", "explicit", "inherited", "legacy"]),
2761
+ inheritedFromSessionId: z.string().uuid().nullable(),
2762
+ selectedIds: SessionEffectiveToolPolicyIds,
2763
+ effectiveIds: SessionEffectiveToolPolicyIds,
2764
+ mandatoryIds: SessionEffectiveToolPolicyIds,
2765
+ lazyRouter: z
2766
+ .object({
2767
+ state: z.enum(["required", "disabled"]),
2768
+ deferredIds: SessionEffectiveToolPolicyIds,
2769
+ })
2770
+ .strict(),
2771
+ configuredIds: SessionEffectiveToolPolicyIds,
2772
+ droppedIds: SessionEffectiveToolPolicyIds,
2773
+ counts: z
2774
+ .object({
2775
+ selected: z.number().int().nonnegative(),
2776
+ effective: z.number().int().nonnegative(),
2777
+ mandatory: z.number().int().nonnegative(),
2778
+ deferred: z.number().int().nonnegative(),
2779
+ configured: z.number().int().nonnegative(),
2780
+ dropped: z.number().int().nonnegative(),
2781
+ })
2782
+ .strict(),
2783
+ idsTruncated: z.boolean(),
2784
+ })
2785
+ .strict();
2786
+ export type SessionEffectiveToolPolicy = z.infer<typeof SessionEffectiveToolPolicy>;
2607
2787
  const httpsUrl = z
2608
2788
  .string()
2609
2789
  .url()
@@ -2618,19 +2798,54 @@ const httpsUrl = z
2618
2798
  { message: "URL must use https" },
2619
2799
  );
2620
2800
 
2801
+ /**
2802
+ * Human-approval policy for one MCP server. `true` gates every tool, `false`
2803
+ * gates none, and a list gates only those unprefixed names.
2804
+ */
2805
+ export const SESSION_MCP_APPROVAL_POLICY_MAX_TOOL_NAMES = 2_048;
2806
+ export const SESSION_MCP_APPROVAL_POLICY_MAX_BYTES = 256 * 1024;
2807
+ export const SESSION_MCP_APPROVAL_TOOL_NAME_MAX_BYTES = 1_024;
2808
+ export const SESSION_MCP_SERVERS_MAX = 64;
2809
+
2810
+ const sessionMcpApprovalToolName = z
2811
+ .string()
2812
+ .min(1)
2813
+ .superRefine((name, ctx) => {
2814
+ if (new TextEncoder().encode(name).byteLength > SESSION_MCP_APPROVAL_TOOL_NAME_MAX_BYTES) {
2815
+ ctx.addIssue({
2816
+ code: z.ZodIssueCode.custom,
2817
+ message: `MCP approval tool names must be at most ${SESSION_MCP_APPROVAL_TOOL_NAME_MAX_BYTES} UTF-8 bytes`,
2818
+ });
2819
+ }
2820
+ });
2821
+ const selectiveSessionMcpApprovalPolicy = z
2822
+ .array(sessionMcpApprovalToolName)
2823
+ .max(SESSION_MCP_APPROVAL_POLICY_MAX_TOOL_NAMES)
2824
+ .superRefine((names, ctx) => {
2825
+ const bytes = names.reduce(
2826
+ (total, name) => total + new TextEncoder().encode(name).byteLength,
2827
+ 0,
2828
+ );
2829
+ if (bytes > SESSION_MCP_APPROVAL_POLICY_MAX_BYTES) {
2830
+ ctx.addIssue({
2831
+ code: z.ZodIssueCode.custom,
2832
+ message: `MCP approval policies must be at most ${SESSION_MCP_APPROVAL_POLICY_MAX_BYTES} UTF-8 bytes`,
2833
+ });
2834
+ }
2835
+ })
2836
+ .transform((names) => [...new Set(names)].sort());
2837
+ export const SessionMcpApprovalPolicy = z.union([z.boolean(), selectiveSessionMcpApprovalPolicy]);
2838
+ export type SessionMcpApprovalPolicy = z.infer<typeof SessionMcpApprovalPolicy>;
2839
+
2621
2840
  export const SessionMcpServerInput = z.object({
2622
- id: z.string().min(1).regex(registryId),
2841
+ id: SessionMcpServerId,
2623
2842
  name: z.string().min(1).optional(),
2624
2843
  url: httpsUrl,
2625
2844
  allowedTools: z.array(z.string().min(1)).optional(),
2626
2845
  timeoutMs: z.number().int().positive().optional(),
2627
2846
  cacheToolsList: z.boolean().optional(),
2628
- // Human-approval policy for this server's tools. `true` = every tool of this
2629
- // server requires approval before it runs (a `session.requiresAction` pause
2630
- // the caller resolves with `user.approvalDecision`); a string[] = ONLY the
2631
- // listed UNPREFIXED tool names require approval (e.g. reads auto-run, writes
2632
- // ask); absent / `false` = auto-run everything (the historical default).
2633
- requireApproval: z.union([z.boolean(), z.array(z.string().min(1))]).optional(),
2847
+ // The caller resolves an approval pause with `user.approvalDecision`.
2848
+ requireApproval: SessionMcpApprovalPolicy.optional(),
2634
2849
  // Write-only credential headers. Values are encrypted at rest and never
2635
2850
  // returned in session responses or events; response metadata exposes names.
2636
2851
  headers: z.record(z.string(), z.string()).optional(),
@@ -2641,23 +2856,43 @@ export const SessionMcpServerInput = z.object({
2641
2856
  export type SessionMcpServerInput = z.infer<typeof SessionMcpServerInput>;
2642
2857
 
2643
2858
  export const SessionMcpCredentialUpdateInput = z.object({
2644
- id: z.string().min(1).regex(registryId),
2859
+ id: SessionMcpServerId,
2645
2860
  headers: z.record(z.string(), z.string()),
2646
2861
  });
2647
2862
  export type SessionMcpCredentialUpdateInput = z.infer<typeof SessionMcpCredentialUpdateInput>;
2648
2863
 
2649
2864
  export const SessionMcpServerMetadata = z
2650
2865
  .object({
2651
- id: z.string().min(1).regex(registryId),
2866
+ id: SessionMcpServerId,
2652
2867
  name: z.string().min(1).nullable(),
2653
2868
  url: httpsUrl,
2654
2869
  headerNames: z.array(z.string()).default([]),
2655
2870
  credentialVersion: z.number().int().positive(),
2871
+ requireApproval: SessionMcpApprovalPolicy.default(false),
2656
2872
  connectionRef: McpServerConnectionRef.nullable().default(null),
2657
2873
  })
2658
2874
  .strict();
2659
2875
  export type SessionMcpServerMetadata = z.infer<typeof SessionMcpServerMetadata>;
2660
2876
 
2877
+ export const UpdateSessionMcpApprovalPolicyRequest = z
2878
+ .object({
2879
+ requireApproval: SessionMcpApprovalPolicy,
2880
+ })
2881
+ .strict();
2882
+ export type UpdateSessionMcpApprovalPolicyRequest = z.infer<
2883
+ typeof UpdateSessionMcpApprovalPolicyRequest
2884
+ >;
2885
+
2886
+ export const UpdateSessionMcpApprovalPolicyResponse = z
2887
+ .object({
2888
+ server: SessionMcpServerMetadata,
2889
+ effectiveFrom: z.literal("next_attempt"),
2890
+ })
2891
+ .strict();
2892
+ export type UpdateSessionMcpApprovalPolicyResponse = z.infer<
2893
+ typeof UpdateSessionMcpApprovalPolicyResponse
2894
+ >;
2895
+
2661
2896
  export class ResourceRefConflictError extends Error {
2662
2897
  constructor(message: string) {
2663
2898
  super(message);
@@ -2821,6 +3056,41 @@ export const SessionGoalPausedReason = z.enum([
2821
3056
  ]);
2822
3057
  export type SessionGoalPausedReason = z.infer<typeof SessionGoalPausedReason>;
2823
3058
 
3059
+ export const SessionGoalContinuationState = z.enum([
3060
+ "inactive",
3061
+ "scheduled",
3062
+ "running",
3063
+ "blocked",
3064
+ "invariant_broken",
3065
+ ]);
3066
+ export type SessionGoalContinuationState = z.infer<typeof SessionGoalContinuationState>;
3067
+
3068
+ export const SessionGoalContinuationReason = z.enum([
3069
+ "goal_inactive",
3070
+ "wake_pending",
3071
+ "continuation_pending",
3072
+ "human_work_pending",
3073
+ "goal_turn_running",
3074
+ "human_turn_running",
3075
+ "workstream_paused",
3076
+ "approval_required",
3077
+ "provider_backpressure",
3078
+ "session_cancelled",
3079
+ "system_work_pending",
3080
+ "missing_obligation",
3081
+ ]);
3082
+ export type SessionGoalContinuationReason = z.infer<typeof SessionGoalContinuationReason>;
3083
+
3084
+ export const SessionGoalContinuation = z.object({
3085
+ state: SessionGoalContinuationState,
3086
+ reason: SessionGoalContinuationReason,
3087
+ wakeRevision: z.number().int().nonnegative(),
3088
+ observedRevision: z.number().int().nonnegative(),
3089
+ nextAttemptAt: z.string().datetime({ offset: true }).nullable(),
3090
+ lastError: z.string().nullable(),
3091
+ });
3092
+ export type SessionGoalContinuation = z.infer<typeof SessionGoalContinuation>;
3093
+
2824
3094
  export const SessionGoal = z.object({
2825
3095
  id: z.string().uuid(),
2826
3096
  accountId: z.string().uuid(),
@@ -2838,6 +3108,9 @@ export const SessionGoal = z.object({
2838
3108
  noProgressStreak: z.number().int().nonnegative(),
2839
3109
  maxAutoContinuations: z.number().int().positive().nullable(),
2840
3110
  metadata: z.record(z.string(), z.unknown()),
3111
+ // Optional for source compatibility with older clients; the API always
3112
+ // supplies this authoritative continuation projection.
3113
+ continuation: SessionGoalContinuation.optional(),
2841
3114
  createdAt: z.string(),
2842
3115
  updatedAt: z.string(),
2843
3116
  });
@@ -3001,6 +3274,7 @@ export const SessionAuthorizationOperation = z.enum([
3001
3274
  "session.human_input.read",
3002
3275
  "session.human_input.write",
3003
3276
  "session.title.write",
3277
+ "session.mcp.approval_policy.write",
3004
3278
  "session.goal.read",
3005
3279
  "session.goal.write",
3006
3280
  "session.child.create",
@@ -3115,6 +3389,10 @@ export const SessionTurn = z.object({
3115
3389
  prompt: z.string().min(1),
3116
3390
  resources: z.array(ResourceRef),
3117
3391
  tools: z.array(ToolRef),
3392
+ // Omitted/default discovery and explicit `tools: []` are distinct. False
3393
+ // inherits the durable session policy; true replaces it for this turn after
3394
+ // admission proves the selection is a subset.
3395
+ toolsProvided: z.boolean().optional(),
3118
3396
  model: z.string().min(1),
3119
3397
  reasoningEffort: ReasoningEffort,
3120
3398
  sandboxBackend: SandboxBackend,
@@ -3204,6 +3482,9 @@ export const ComposerDraft = z.object({
3204
3482
  text: z.string(),
3205
3483
  resources: z.array(ResourceRef),
3206
3484
  tools: z.array(ToolRef),
3485
+ // False means the draft inherits the session policy. True preserves an
3486
+ // explicit array, including [], across autosave/reload and queue checkout.
3487
+ toolsProvided: z.boolean().default(false),
3207
3488
  model: z.string().min(1),
3208
3489
  reasoningEffort: ReasoningEffort,
3209
3490
  sourceTurnId: z.string().uuid().nullable(),
@@ -3260,11 +3541,51 @@ export const SaveComposerDraftRequest = ComposerDraft.pick({
3260
3541
  text: true,
3261
3542
  resources: true,
3262
3543
  tools: true,
3544
+ toolsProvided: true,
3263
3545
  model: true,
3264
3546
  reasoningEffort: true,
3265
3547
  }).extend({ expectedRevision: z.number().int().nonnegative() });
3266
3548
  export type SaveComposerDraftRequest = z.infer<typeof SaveComposerDraftRequest>;
3267
3549
 
3550
+ /**
3551
+ * Create-only options saved with an actor's private pre-session draft. This is
3552
+ * deliberately narrower than CreateSessionRequest: idempotency/event keys and
3553
+ * credential-bearing MCP server inputs are per-attempt data, never draft state.
3554
+ */
3555
+ export const NewSessionDraftOptions = z.object({
3556
+ sandboxBackend: SandboxBackend.optional(),
3557
+ targetSandboxId: z.string().uuid().optional(),
3558
+ workingDir: z.string().min(1).optional(),
3559
+ variableSetId: z.string().uuid().optional(),
3560
+ rigId: z.string().uuid().optional(),
3561
+ goal: GoalSpec.optional(),
3562
+ firstPartyMcpPermissions: z.array(Permission).optional(),
3563
+ });
3564
+ export type NewSessionDraftOptions = z.infer<typeof NewSessionDraftOptions>;
3565
+
3566
+ /** Actor-private, server-authoritative composer state before a session exists. */
3567
+ export const NewSessionDraft = z.object({
3568
+ revision: z.number().int().nonnegative(),
3569
+ text: z.string(),
3570
+ resources: z.array(ResourceRef),
3571
+ tools: z.array(ToolRef),
3572
+ model: z.string().min(1),
3573
+ reasoningEffort: ReasoningEffort,
3574
+ options: NewSessionDraftOptions,
3575
+ updatedAt: z.string().nullable(),
3576
+ });
3577
+ export type NewSessionDraft = z.infer<typeof NewSessionDraft>;
3578
+
3579
+ export const SaveNewSessionDraftRequest = NewSessionDraft.pick({
3580
+ text: true,
3581
+ resources: true,
3582
+ tools: true,
3583
+ model: true,
3584
+ reasoningEffort: true,
3585
+ options: true,
3586
+ }).extend({ expectedRevision: z.number().int().nonnegative() });
3587
+ export type SaveNewSessionDraftRequest = z.infer<typeof SaveNewSessionDraftRequest>;
3588
+
3268
3589
  export const WORKSPACE_CONTROL_REASON_MAX_BYTES = 8 * 1024;
3269
3590
  export const WORKSPACE_CONTROL_ACTOR_MAX_BYTES = 1024;
3270
3591
  export const WORKSPACE_CONTROL_EVENT_MAX_BYTES = 16 * 1024;
@@ -3838,6 +4159,9 @@ export const ScheduledTaskAgentConfig = z.object({
3838
4159
  reasoningEffort: ReasoningEffort.optional(),
3839
4160
  sandboxBackend: SandboxBackend.optional(),
3840
4161
  goal: GoalSpec.optional(),
4162
+ // Durable task override. Scheduled dispatch is trusted to preserve this
4163
+ // snapshot even if the workspace/deployment policy narrows later.
4164
+ maxNestedAgentDepth: NestedAgentDepthValue.optional(),
3841
4165
  });
3842
4166
  export type ScheduledTaskAgentConfig = z.infer<typeof ScheduledTaskAgentConfig>;
3843
4167
 
@@ -4355,6 +4679,19 @@ export const CapabilityRuntime = z.object({
4355
4679
  mcpServerId: z.string().min(1).optional(),
4356
4680
  transport: z.string().min(1).optional(),
4357
4681
  notes: z.string().nullable().default(null),
4682
+ // Registry exposure provenance is server-derived and contains no endpoint or
4683
+ // credential material.
4684
+ catalogTrust: z
4685
+ .object({
4686
+ state: z.enum(["trusted", "legacy_active", "unverified"]),
4687
+ reason: z.enum([
4688
+ "trusted_source",
4689
+ "verified_probe",
4690
+ "active_installation_compatibility",
4691
+ "missing_verification",
4692
+ ]),
4693
+ })
4694
+ .optional(),
4358
4695
  });
4359
4696
  export type CapabilityRuntime = z.infer<typeof CapabilityRuntime>;
4360
4697
 
@@ -4406,6 +4743,34 @@ export const CapabilityCatalogItem = z.object({
4406
4743
  });
4407
4744
  export type CapabilityCatalogItem = z.infer<typeof CapabilityCatalogItem>;
4408
4745
 
4746
+ /**
4747
+ * Shared trust gate for catalog visibility and runtime selection. Registry rows
4748
+ * remain durable for provenance and audit, but only a reviewed real-MCP probe
4749
+ * with known authentication is exposable. API-key rows additionally need a
4750
+ * machine-actionable header contract; prose credential instructions are not a
4751
+ * runtime contract and must fail closed.
4752
+ */
4753
+ export function capabilityCatalogItemIsTrustedForExposure(
4754
+ item: Pick<CapabilityCatalogItem, "source" | "stale" | "authKind" | "metadata">,
4755
+ ): boolean {
4756
+ if (item.stale) return false;
4757
+ if (item.source !== "registry") return true;
4758
+ const probe = item.metadata.mcpProbe;
4759
+ if (!probe || typeof probe !== "object" || Array.isArray(probe)) return false;
4760
+ if ((probe as Record<string, unknown>).status !== "real") return false;
4761
+ if (item.authKind === null || item.authKind === "unknown") return false;
4762
+ if (item.authKind !== "api_key") return true;
4763
+ const contract = item.metadata.authContract;
4764
+ if (!contract || typeof contract !== "object" || Array.isArray(contract)) return false;
4765
+ const record = contract as Record<string, unknown>;
4766
+ return (
4767
+ typeof record.headerName === "string" &&
4768
+ /^[A-Za-z0-9!#$%&'*+.^_`|~-]+$/.test(record.headerName) &&
4769
+ typeof record.scheme === "string" &&
4770
+ record.scheme.trim().length > 0
4771
+ );
4772
+ }
4773
+
4409
4774
  export const CapabilityInstallation = z.object({
4410
4775
  id: z.string().uuid(),
4411
4776
  accountId: z.string().uuid(),
@@ -4485,6 +4850,13 @@ export const Session = z.object({
4485
4850
  instructions: z.string().nullable(),
4486
4851
  resources: z.array(ResourceRef),
4487
4852
  tools: z.array(ToolRef),
4853
+ // Origin of the persisted tool allow-list. Optional for rolling client
4854
+ // compatibility; current servers emit it and legacy rows map to `legacy`.
4855
+ toolPolicy: SessionToolPolicy.optional(),
4856
+ // Secret-safe current resolution, computed at an API/read or execution
4857
+ // boundary from IDs only. Optional because internal DB readers need not load
4858
+ // the workspace runtime registry.
4859
+ effectiveToolPolicy: SessionEffectiveToolPolicy.optional(),
4488
4860
  metadata: z.record(z.string(), z.unknown()),
4489
4861
  /** Frozen creator fact used only for creation attribution/idempotent repair. */
4490
4862
  createdBy: TurnInitiator,
@@ -4525,6 +4897,14 @@ export const Session = z.object({
4525
4897
  // direct API creates and scheduled-task runs. When set, this session's
4526
4898
  // terminal-for-now transitions wake the parent.
4527
4899
  parentSessionId: z.string().uuid().nullable(),
4900
+ // Server-authored nested-agent lineage/policy. Root sessions are depth 0;
4901
+ // snapshots are immutable and govern only future descendant creation.
4902
+ rootSessionId: z.string().uuid(),
4903
+ nestedAgentDepth: NestedAgentDepthValue,
4904
+ maxNestedAgentDepthOverride: NestedAgentDepthValue.nullable(),
4905
+ effectiveMaxNestedAgentDepth: NestedAgentDepthValue,
4906
+ nestedAgentDepthPolicySource: NestedAgentDepthPolicySource,
4907
+ nestedAgentDepthPolicySessionId: z.string().uuid().nullable(),
4528
4908
  // Workspace-scoped CREATE idempotency key the session was created under (the
4529
4909
  // dedup target collapsing double-submit/retry races to one session); null
4530
4910
  // when the create carried no key.
@@ -4656,6 +5036,9 @@ export const SessionEventType = z.enum([
4656
5036
  "agent.reasoning.delta",
4657
5037
  "agent.toolCall.created",
4658
5038
  "agent.toolCall.output",
5039
+ // Attempt-fenced Codex Responses lifecycle metadata (request identity,
5040
+ // deadlines, first-byte/terminal phase, provider request id). Never body/auth.
5041
+ "agent.model.request",
4659
5042
  "agent.model.usage",
4660
5043
  "tool.auth_needed",
4661
5044
  "credential.auth_needed",
@@ -4719,6 +5102,7 @@ export const SessionEventType = z.enum([
4719
5102
  "terminal.pty.output.delta", // PTY stdout/stderr bytes (separate from command.output)
4720
5103
  "terminal.pty.exited", // PTY session ended (exitCode/reason)
4721
5104
  "session.title_set",
5105
+ "session.mcp.approval_policy.updated",
4722
5106
  // Multi-account Codex (P1): the account a session's turn runs on changed
4723
5107
  // (manual switch in P1; failover/rotation in P3 reuse the same event). Drives
4724
5108
  // the in-session "Running on:" indicator's live flip.
@@ -4726,6 +5110,10 @@ export const SessionEventType = z.enum([
4726
5110
  // credential allocator per-turn selection audit. Payload is metadata only: credential row
4727
5111
  // id, bounded strategy/reason, and pool counts — never token material.
4728
5112
  "codex.credential.selected",
5113
+ // Adaptive fleet shadow decision record. Contains only bounded opaque candidate aliases,
5114
+ // normalized pressure/cache/confidence features, deterministic fingerprints,
5115
+ // the actual-vs-shadow comparison, and no credential/account identity.
5116
+ "codex.fleet.decision",
4729
5117
  // credential allocator durable zero-capacity wait lifecycle. Runtime/system events only;
4730
5118
  // no synthetic user message is created when capacity returns.
4731
5119
  "codex.capacity.waiting",
@@ -4813,9 +5201,35 @@ export const SessionEventSemanticClass = z.enum([
4813
5201
  ]);
4814
5202
  export type SessionEventSemanticClass = z.infer<typeof SessionEventSemanticClass>;
4815
5203
 
5204
+ /**
5205
+ * The semantic classes accepted by an exclusive latest lookup. `receipt` is
5206
+ * the concise public spelling for the historical `tool_receipt` class; the
5207
+ * latter remains accepted everywhere for backwards compatibility.
5208
+ */
5209
+ export const SessionEventLatestClass = z.enum([
5210
+ "control",
5211
+ "terminal",
5212
+ "failure",
5213
+ "checkpoint",
5214
+ "tool_receipt",
5215
+ "provider_account",
5216
+ "receipt",
5217
+ ]);
5218
+ export type SessionEventLatestClass = z.infer<typeof SessionEventLatestClass>;
5219
+
5220
+ export function sessionEventLatestClassToSemanticClass(
5221
+ value: SessionEventLatestClass,
5222
+ ): SessionEventSemanticClass {
5223
+ return value === "receipt" ? "tool_receipt" : value;
5224
+ }
5225
+
4816
5226
  export const SessionEventPayloadMode = z.enum(["none", "summary", "full"]);
4817
5227
  export type SessionEventPayloadMode = z.infer<typeof SessionEventPayloadMode>;
4818
5228
 
5229
+ /** Select the compact semantic-result projection instead of an event array. */
5230
+ export const SessionEventResultMode = z.enum(["events", "compact"]);
5231
+ export type SessionEventResultMode = z.infer<typeof SessionEventResultMode>;
5232
+
4819
5233
  export const SessionEventReadMode = z.enum(["monitoring", "forensic"]);
4820
5234
  export type SessionEventReadMode = z.infer<typeof SessionEventReadMode>;
4821
5235
 
@@ -4853,9 +5267,11 @@ export const SESSION_EVENT_SEMANTIC_CLASS_TYPES = {
4853
5267
  "workspace.inference.resumed",
4854
5268
  "session.queue.changed",
4855
5269
  "session.queue.prompt.cancelled",
5270
+ "session.mcp.approval_policy.updated",
4856
5271
  ],
4857
5272
  terminal: [
4858
5273
  "turn.completed",
5274
+ "agent.message.completed",
4859
5275
  "turn.failed",
4860
5276
  "turn.cancelled",
4861
5277
  "turn.superseded",
@@ -5167,7 +5583,7 @@ export type TerminalPtyOutputDeltaPayload = z.infer<typeof TerminalPtyOutputDelt
5167
5583
  export const TerminalPtyExitedPayload = z.object({
5168
5584
  ptyId: z.string().uuid(),
5169
5585
  exitCode: z.number().int().nullable(),
5170
- reason: z.enum(["exit", "killed", "owner_gone", "timeout"]),
5586
+ reason: z.enum(["exit", "killed", "owner_gone", "timeout", "lost"]),
5171
5587
  });
5172
5588
  export type TerminalPtyExitedPayload = z.infer<typeof TerminalPtyExitedPayload>;
5173
5589
 
@@ -5626,7 +6042,8 @@ export type GitShowResponse = z.infer<typeof GitShowResponse>;
5626
6042
  export const TerminalExecRequest = z.object({
5627
6043
  command: z.string().min(1),
5628
6044
  cwd: z.string().default(""), // workspace-relative
5629
- // Soft per-call wall-clock bound (the box yields output back when reached).
6045
+ // Hard wall-clock bound. A timeout response is returned only after the exact
6046
+ // provider process is physically absent and any retained admission settles.
5630
6047
  timeoutMs: z.number().int().positive().max(120_000).default(30_000),
5631
6048
  // Stream the deltas onto A1 as the agent firehose (so other viewers see it),
5632
6049
  // in addition to returning the buffered result inline.
@@ -5636,10 +6053,10 @@ export type TerminalExecRequest = z.infer<typeof TerminalExecRequest>;
5636
6053
  export const TerminalExecResponse = z.object({
5637
6054
  stdout: z.string(),
5638
6055
  stderr: z.string(),
5639
- exitCode: z.number().int().nullable(),
5640
- // True when the process was still running when the call yielded (a long
5641
- // command); the remaining output drains onto A1 if emitStream was set.
5642
- running: z.boolean(),
6056
+ exitCode: z.number().int(),
6057
+ // Retained for wire compatibility; synchronous exec never exposes a live
6058
+ // provider process. Interactive work uses the PTY API.
6059
+ running: z.literal(false),
5643
6060
  wallTimeSeconds: z.number().nonnegative(),
5644
6061
  });
5645
6062
  export type TerminalExecResponse = z.infer<typeof TerminalExecResponse>;
@@ -5709,6 +6126,354 @@ export const SessionEvent = z.object({
5709
6126
  });
5710
6127
  export type SessionEvent = z.infer<typeof SessionEvent>;
5711
6128
 
6129
+ export type SessionEventCompactResult = {
6130
+ version: 1;
6131
+ semanticClass: SessionEventSemanticClass;
6132
+ source: {
6133
+ id: string;
6134
+ type: SessionEventType;
6135
+ sequence: number;
6136
+ occurredAt: string;
6137
+ turnId: string | null;
6138
+ turnGeneration: number | null;
6139
+ turnAttemptId: string | null;
6140
+ turnAssociation: SessionEvent["turnAssociation"];
6141
+ };
6142
+ // These identity fields are repeated at the top level intentionally: an
6143
+ // MCP caller can act on the result without unpacking the source envelope.
6144
+ id: string;
6145
+ type: SessionEventType;
6146
+ sequence: number;
6147
+ occurredAt: string;
6148
+ turnId: string | null;
6149
+ turnGeneration: number | null;
6150
+ turnAttemptId: string | null;
6151
+ turnAssociation: SessionEvent["turnAssociation"];
6152
+ coveredSequence: { first: number; last: number };
6153
+ status:
6154
+ | "completed"
6155
+ | "failed"
6156
+ | "cancelled"
6157
+ | "superseded"
6158
+ | "checkpoint"
6159
+ | "receipt"
6160
+ | "unknown";
6161
+ text: string | null;
6162
+ output: unknown;
6163
+ result: unknown;
6164
+ failure: {
6165
+ error: string | null;
6166
+ code: string | null;
6167
+ retryable: boolean | null;
6168
+ recovery: string | null;
6169
+ } | null;
6170
+ checkpoint: unknown;
6171
+ receipt: unknown;
6172
+ truncation: {
6173
+ truncated: boolean;
6174
+ fields: string[];
6175
+ originalBytes: number | null;
6176
+ deliveredBytes: number;
6177
+ };
6178
+ };
6179
+
6180
+ const SESSION_EVENT_COMPACT_RESULT_TEXT_MAX_BYTES = 12 * 1024;
6181
+ // Five independently bounded slots plus identity/metadata must fit below the
6182
+ // 64 KiB MCP envelope even when a pathological producer supplies every slot.
6183
+ const SESSION_EVENT_COMPACT_RESULT_VALUE_MAX_BYTES = 8 * 1024;
6184
+
6185
+ type CompactValue = {
6186
+ value: unknown;
6187
+ truncated: boolean;
6188
+ originalBytes: number | null;
6189
+ };
6190
+
6191
+ type JsonRecord = Record<string, unknown>;
6192
+
6193
+ /**
6194
+ * Build the bounded semantic result used by `latest + result=compact`.
6195
+ *
6196
+ * This is intentionally a pure projection over one already-authoritative
6197
+ * event. It never reads history, invokes a model, follows a URL, or stores an
6198
+ * artifact. The DB/API/MCP layers decide which event is authoritative; this
6199
+ * helper only extracts the small result facts that can cross a client boundary.
6200
+ */
6201
+ export function compactSessionEventResult(
6202
+ event: SessionEvent,
6203
+ semanticClass: SessionEventSemanticClass,
6204
+ coveredSequence: { first: number; last: number } = {
6205
+ first: event.sequence,
6206
+ last: event.sequence,
6207
+ },
6208
+ ): SessionEventCompactResult {
6209
+ const payload = isSessionEventJsonRecord(event.payload) ? event.payload : {};
6210
+ const fields: string[] = [];
6211
+ let originalBytes = 0;
6212
+
6213
+ const textCandidate = typeof payload.text === "string" ? payload.text : null;
6214
+ const outputCandidate = Object.prototype.hasOwnProperty.call(payload, "output")
6215
+ ? payload.output
6216
+ : null;
6217
+ const resultCandidate = Object.prototype.hasOwnProperty.call(payload, "result")
6218
+ ? payload.result
6219
+ : undefined;
6220
+ const textValue = textCandidate ?? (typeof outputCandidate === "string" ? outputCandidate : null);
6221
+ const text = textValue === null ? null : compactResultText(textValue);
6222
+ if (text && text.truncated) {
6223
+ fields.push("text");
6224
+ originalBytes += text.originalBytes ?? 0;
6225
+ }
6226
+
6227
+ const output = compactResultValue(outputCandidate);
6228
+ if (outputCandidate !== null && output.truncated) {
6229
+ fields.push("output");
6230
+ originalBytes += output.originalBytes ?? 0;
6231
+ }
6232
+
6233
+ const result = compactResultValue(
6234
+ resultCandidate === undefined ? (textValue ?? outputCandidate) : resultCandidate,
6235
+ );
6236
+ if (resultCandidate !== undefined && result.truncated) {
6237
+ fields.push("result");
6238
+ originalBytes += result.originalBytes ?? 0;
6239
+ }
6240
+
6241
+ const checkpointField = firstOwnPayloadValue(payload, ["checkpoint", "summary", "snapshot"]);
6242
+ const checkpointCandidate =
6243
+ checkpointField !== undefined
6244
+ ? checkpointField
6245
+ : semanticClass === "checkpoint"
6246
+ ? payload
6247
+ : null;
6248
+ const checkpoint = compactResultValue(checkpointCandidate);
6249
+ if (checkpointCandidate !== null && checkpoint.truncated) {
6250
+ fields.push("checkpoint");
6251
+ originalBytes += checkpoint.originalBytes ?? 0;
6252
+ }
6253
+
6254
+ const receiptCandidate = firstOwnPayloadValue(payload, ["receipt", "receiptData"]);
6255
+ const receipt = compactResultValue(
6256
+ receiptCandidate !== undefined
6257
+ ? receiptCandidate
6258
+ : semanticClass === "tool_receipt"
6259
+ ? payload
6260
+ : null,
6261
+ );
6262
+ if (receipt.truncated) {
6263
+ fields.push("receipt");
6264
+ originalBytes += receipt.originalBytes ?? 0;
6265
+ }
6266
+
6267
+ const failure = compactFailure(payload, event.type);
6268
+ if (failure.truncated) {
6269
+ fields.push("failure");
6270
+ originalBytes += failure.originalBytes ?? 0;
6271
+ }
6272
+
6273
+ if (isSessionEventJsonRecord(payload.truncation) && payload.truncation.truncated === true) {
6274
+ fields.push("payload");
6275
+ }
6276
+
6277
+ const source = {
6278
+ id: event.id,
6279
+ type: event.type,
6280
+ sequence: event.sequence,
6281
+ occurredAt: event.occurredAt,
6282
+ turnId: event.turnId ?? null,
6283
+ turnGeneration: event.turnGeneration ?? null,
6284
+ turnAttemptId: event.turnAttemptId ?? null,
6285
+ turnAssociation: event.turnAssociation ?? null,
6286
+ };
6287
+ const status = compactResultStatus(event.type, semanticClass, payload);
6288
+ const outputValue = outputCandidate === null ? null : output.value;
6289
+ const resultValue = result.value;
6290
+ const checkpointValue = checkpointCandidate === null ? null : checkpoint.value;
6291
+ const receiptValue =
6292
+ receiptCandidate === null && semanticClass !== "tool_receipt" ? null : receipt.value;
6293
+ const compact: SessionEventCompactResult = {
6294
+ version: 1,
6295
+ semanticClass,
6296
+ source,
6297
+ id: source.id,
6298
+ type: source.type,
6299
+ sequence: source.sequence,
6300
+ occurredAt: source.occurredAt,
6301
+ turnId: source.turnId,
6302
+ turnGeneration: source.turnGeneration,
6303
+ turnAttemptId: source.turnAttemptId,
6304
+ turnAssociation: source.turnAssociation,
6305
+ coveredSequence,
6306
+ status,
6307
+ text: text?.value ?? null,
6308
+ output: outputValue,
6309
+ result: resultValue,
6310
+ failure: failure.value,
6311
+ checkpoint: checkpointValue,
6312
+ receipt: receiptValue,
6313
+ truncation: {
6314
+ truncated: fields.length > 0,
6315
+ fields: [...new Set(fields)],
6316
+ originalBytes: fields.length > 0 ? originalBytes || null : null,
6317
+ deliveredBytes: sessionEventJsonBytes({
6318
+ text: text?.value ?? null,
6319
+ output: outputValue,
6320
+ result: resultValue,
6321
+ failure: failure.value,
6322
+ checkpoint: checkpointValue,
6323
+ receipt: receiptValue,
6324
+ }),
6325
+ },
6326
+ };
6327
+ return compact;
6328
+ }
6329
+
6330
+ function isSessionEventJsonRecord(value: unknown): value is JsonRecord {
6331
+ return value !== null && typeof value === "object" && !Array.isArray(value);
6332
+ }
6333
+
6334
+ function firstOwnPayloadValue(payload: JsonRecord, keys: readonly string[]): unknown | undefined {
6335
+ for (const key of keys) {
6336
+ if (Object.prototype.hasOwnProperty.call(payload, key)) return payload[key];
6337
+ }
6338
+ return undefined;
6339
+ }
6340
+
6341
+ function compactResultText(value: string): CompactValue & { value: string } {
6342
+ const originalBytes = new TextEncoder().encode(value).byteLength;
6343
+ if (originalBytes <= SESSION_EVENT_COMPACT_RESULT_TEXT_MAX_BYTES) {
6344
+ return { value, truncated: false, originalBytes };
6345
+ }
6346
+ let omittedBytes = originalBytes - SESSION_EVENT_COMPACT_RESULT_TEXT_MAX_BYTES;
6347
+ let projected = value;
6348
+ for (let attempt = 0; attempt < 4; attempt += 1) {
6349
+ const marker = `…[${omittedBytes} UTF-8 bytes omitted from compact result]…`;
6350
+ const budget = Math.max(0, SESSION_EVENT_COMPACT_RESULT_TEXT_MAX_BYTES - utf8Bytes(marker));
6351
+ const head = utf8PrefixForResult(value, Math.floor(budget * 0.7));
6352
+ const tail = utf8SuffixForResult(value, budget - utf8Bytes(head));
6353
+ projected = `${head}${marker}${tail}`;
6354
+ const nextOmitted = Math.max(0, originalBytes - utf8Bytes(head) - utf8Bytes(tail));
6355
+ if (nextOmitted === omittedBytes) break;
6356
+ omittedBytes = nextOmitted;
6357
+ }
6358
+ return { value: projected, truncated: true, originalBytes };
6359
+ }
6360
+
6361
+ function compactResultValue(value: unknown): CompactValue {
6362
+ if (value === null || value === undefined) {
6363
+ return { value: null, truncated: false, originalBytes: null };
6364
+ }
6365
+ const measurement = measureSessionEventJson(value);
6366
+ const bounded = boundSessionEventPayload(value, {
6367
+ surface: "http_projection",
6368
+ maxBytes: SESSION_EVENT_COMPACT_RESULT_VALUE_MAX_BYTES,
6369
+ });
6370
+ const deliveredBytes = measureSessionEventJson(bounded).bytes;
6371
+ return {
6372
+ value: bounded,
6373
+ truncated:
6374
+ measurement.bytes === null || deliveredBytes === null || measurement.bytes !== deliveredBytes,
6375
+ originalBytes: measurement.bytes,
6376
+ };
6377
+ }
6378
+
6379
+ function compactFailure(
6380
+ payload: JsonRecord,
6381
+ eventType: SessionEventType,
6382
+ ): CompactValue & {
6383
+ value: SessionEventCompactResult["failure"];
6384
+ } {
6385
+ const isFailure =
6386
+ eventType === "turn.failed" ||
6387
+ eventType === "turn.cancelled" ||
6388
+ eventType === "turn.superseded";
6389
+ const hasFailureField = ["error", "code", "retryable", "recovery"].some((key) =>
6390
+ Object.prototype.hasOwnProperty.call(payload, key),
6391
+ );
6392
+ if (!isFailure && !hasFailureField) {
6393
+ return { value: null, truncated: false, originalBytes: null };
6394
+ }
6395
+ const error = compactResultStringField(payload.error);
6396
+ const code = compactResultStringField(payload.code);
6397
+ const recovery = compactResultStringField(payload.recovery);
6398
+ const retryable = typeof payload.retryable === "boolean" ? payload.retryable : null;
6399
+ const value = { error: error.value, code: code.value, retryable, recovery: recovery.value };
6400
+ const originalBytes = [error, code, recovery]
6401
+ .map((field) => field.originalBytes ?? 0)
6402
+ .reduce((sum, bytes) => sum + bytes, 0);
6403
+ return {
6404
+ value,
6405
+ truncated: error.truncated || code.truncated || recovery.truncated,
6406
+ originalBytes: originalBytes || null,
6407
+ };
6408
+ }
6409
+
6410
+ function compactResultStringField(value: unknown): CompactValue & { value: string | null } {
6411
+ if (typeof value !== "string") {
6412
+ return { value: null, truncated: false, originalBytes: null };
6413
+ }
6414
+ return compactResultText(value);
6415
+ }
6416
+
6417
+ function compactResultStatus(
6418
+ eventType: SessionEventType,
6419
+ semanticClass: SessionEventSemanticClass,
6420
+ payload: JsonRecord,
6421
+ ): SessionEventCompactResult["status"] {
6422
+ if (eventType === "turn.failed") return "failed";
6423
+ if (eventType === "turn.cancelled") return "cancelled";
6424
+ if (eventType === "turn.superseded") return "superseded";
6425
+ if (eventType === "turn.completed" || eventType === "agent.message.completed") {
6426
+ return "completed";
6427
+ }
6428
+ if (semanticClass === "checkpoint") return "checkpoint";
6429
+ if (
6430
+ semanticClass === "tool_receipt" ||
6431
+ eventType === "artifact.created" ||
6432
+ eventType === "recording.available"
6433
+ ) {
6434
+ return "receipt";
6435
+ }
6436
+ if (payload.status === "failed") return "failed";
6437
+ if (payload.status === "completed") return "completed";
6438
+ return "unknown";
6439
+ }
6440
+
6441
+ function utf8Bytes(value: string): number {
6442
+ return new TextEncoder().encode(value).byteLength;
6443
+ }
6444
+
6445
+ function utf8PrefixForResult(value: string, maxBytes: number): string {
6446
+ let bytes = 0;
6447
+ let index = 0;
6448
+ while (index < value.length) {
6449
+ const codePoint = value.codePointAt(index);
6450
+ if (codePoint === undefined) break;
6451
+ const character = String.fromCodePoint(codePoint);
6452
+ const next = utf8Bytes(character);
6453
+ if (bytes + next > maxBytes) break;
6454
+ bytes += next;
6455
+ index += character.length;
6456
+ }
6457
+ return value.slice(0, index);
6458
+ }
6459
+
6460
+ function utf8SuffixForResult(value: string, maxBytes: number): string {
6461
+ let bytes = 0;
6462
+ let index = value.length;
6463
+ while (index > 0) {
6464
+ const width =
6465
+ index > 1 && value.charCodeAt(index - 1) >= 0xdc00 && value.charCodeAt(index - 1) <= 0xdfff
6466
+ ? 2
6467
+ : 1;
6468
+ const character = value.slice(index - width, index);
6469
+ const next = utf8Bytes(character);
6470
+ if (bytes + next > maxBytes) break;
6471
+ bytes += next;
6472
+ index -= width;
6473
+ }
6474
+ return value.slice(index);
6475
+ }
6476
+
5712
6477
  // --- Durable host export ------------------------------------------------------
5713
6478
 
5714
6479
  /** Wire revision for the durable host event/usage export stream. */
@@ -6388,6 +7153,14 @@ export const CreateSessionRequest = withVariableSetIdAlias({
6388
7153
  // creation of a brand-new session. Absent means no create-dedup (each call
6389
7154
  // is an independent create).
6390
7155
  idempotencyKey: z.string().min(1).max(200).optional(),
7156
+ // The exact actor-private pre-session draft revision represented by this
7157
+ // create. The durable initializer consumes only this revision. A newer draft
7158
+ // written by a sibling tab survives, while every failed pre-initialization
7159
+ // create leaves the submitted draft intact.
7160
+ expectedNewSessionDraftRevision: z.number().int().nonnegative().optional(),
7161
+ // A child may lower its inherited limit freely; an increase requires
7162
+ // workspace:admin and is checked again at the DB transaction boundary.
7163
+ maxNestedAgentDepth: NestedAgentDepthValue.optional(),
6391
7164
  // Permissions the session's first-party MCP token should carry. A top-level
6392
7165
  // omission uses the deployment's worker default; a child omission inherits
6393
7166
  // the creating session's effective grant. An explicit set is capped at
@@ -6401,7 +7174,7 @@ export const CreateSessionRequest = withVariableSetIdAlias({
6401
7174
  // including [], are authoritative; non-empty explicit arrays require attach
6402
7175
  // permission. Credential headers are write-only: create responses and events
6403
7176
  // expose only SessionMcpServerMetadata.
6404
- mcpServers: z.array(SessionMcpServerInput).default([]),
7177
+ mcpServers: z.array(SessionMcpServerInput).max(SESSION_MCP_SERVERS_MAX).default([]),
6405
7178
  // Shared-sandbox placement (addendum 05 §D.1). Three-way union; OMITTED ⇒
6406
7179
  // today's behavior (a context-dependent default resolved server-side: from
6407
7180
  // inside a session → "shared" with the creator's box, top-level → "new").
@@ -6770,6 +7543,9 @@ export const SessionCapabilities = z.object({
6770
7543
  liveness: z.enum(["cold", "warming", "warm", "draining"]),
6771
7544
  // Echoed on viewer heartbeats (the split-brain fence).
6772
7545
  leaseEpoch: z.number().int().nonnegative(),
7546
+ workspaceGeneration: z.number().int().nonnegative().nullable().default(null),
7547
+ archiveGeneration: z.number().int().nonnegative().nullable().default(null),
7548
+ archiveComplete: z.boolean().default(false),
6773
7549
  viewerHeartbeatIntervalMs: z.number().int().positive().default(30_000),
6774
7550
  FileSystem: z.object({
6775
7551
  available: z.boolean(),
@@ -6874,6 +7650,9 @@ export const ViewerHolder = z.object({
6874
7650
  liveness: z.enum(["cold", "warming", "warm", "draining"]),
6875
7651
  // The epoch the viewer is fenced on; echoed back on heartbeats.
6876
7652
  leaseEpoch: z.number().int().nonnegative(),
7653
+ workspaceGeneration: z.number().int().nonnegative().nullable(),
7654
+ archiveGeneration: z.number().int().nonnegative().nullable(),
7655
+ archiveComplete: z.boolean(),
6877
7656
  viewerHeartbeatIntervalMs: z.number().int().positive(),
6878
7657
  // The desktop pixel tunnel URL the viewer connects to directly; null until
6879
7658
  // a viewer grant is minted (gated until then).
@@ -7237,6 +8016,9 @@ export const MachineView = z.object({
7237
8016
  state: MachineState,
7238
8017
  active: z.boolean(),
7239
8018
  isSessionGroup: z.boolean(),
8019
+ workspaceGeneration: z.number().int().nonnegative().nullable(),
8020
+ archiveGeneration: z.number().int().nonnegative().nullable(),
8021
+ archiveComplete: z.boolean(),
7240
8022
  os: z.string(),
7241
8023
  arch: z.string(),
7242
8024
  hasDisplay: z.boolean(),
@@ -7296,6 +8078,9 @@ export const SwapActiveSandboxResponse = z.object({
7296
8078
  "unsupported_backend_context",
7297
8079
  "transient_establishment",
7298
8080
  "concurrent_swap",
8081
+ "recovery_in_progress",
8082
+ "recovery_degraded",
8083
+ "recovery_unrecoverable",
7299
8084
  ])
7300
8085
  .optional(),
7301
8086
  });
@@ -7310,6 +8095,239 @@ export const MachineMetricsSeriesResponse = z.object({
7310
8095
  });
7311
8096
  export type MachineMetricsSeriesResponse = z.infer<typeof MachineMetricsSeriesResponse>;
7312
8097
 
8098
+ /**
8099
+ * Keep this server-facing schema graph eager when imported while allowing
8100
+ * browser bundlers to discard it when contracts is used only for unrelated
8101
+ * helpers. Keep each call site annotated as pure; the factory argument itself
8102
+ * is side-effect-free until invoked.
8103
+ */
8104
+ function defineModelContractSchema<Schema>(factory: () => Schema): Schema {
8105
+ return factory();
8106
+ }
8107
+
8108
+ export const ModelCapabilitySupportV1 = /* @__PURE__ */ defineModelContractSchema(() =>
8109
+ z.enum(["supported", "unsupported", "unknown"]),
8110
+ );
8111
+ export type ModelCapabilitySupportV1 = z.infer<typeof ModelCapabilitySupportV1>;
8112
+
8113
+ export const ModelCapabilityStateV1 = /* @__PURE__ */ defineModelContractSchema(() =>
8114
+ z.object({
8115
+ upstream: ModelCapabilitySupportV1,
8116
+ runnable: z.boolean(),
8117
+ }),
8118
+ );
8119
+ export type ModelCapabilityStateV1 = z.infer<typeof ModelCapabilityStateV1>;
8120
+
8121
+ export const ModelCapabilitiesV1 = /* @__PURE__ */ defineModelContractSchema(() =>
8122
+ z.object({
8123
+ reasoning: ModelCapabilityStateV1.extend({
8124
+ efforts: z.array(ReasoningEffort),
8125
+ defaultEffort: ReasoningEffort.nullable(),
8126
+ required: z.boolean(),
8127
+ }),
8128
+ functionCalling: ModelCapabilityStateV1,
8129
+ structuredOutput: ModelCapabilityStateV1,
8130
+ hostedTools: z.object({
8131
+ webSearch: ModelCapabilityStateV1,
8132
+ xSearch: ModelCapabilityStateV1,
8133
+ codeExecution: ModelCapabilityStateV1,
8134
+ }),
8135
+ inputModalities: z.array(z.enum(["text", "image", "audio"])),
8136
+ outputModalities: z.array(z.enum(["text", "image", "audio"])),
8137
+ transports: z.object({
8138
+ sse: ModelCapabilityStateV1,
8139
+ responsesWebSocket: ModelCapabilityStateV1,
8140
+ realtimeAudio: ModelCapabilityStateV1,
8141
+ }),
8142
+ latencyModes: z.array(
8143
+ z.object({
8144
+ id: z.enum(["standard", "priority", "fast"]),
8145
+ upstream: ModelCapabilitySupportV1,
8146
+ runnable: z.boolean(),
8147
+ billingMultiplierBps: z.number().int().positive().optional(),
8148
+ }),
8149
+ ),
8150
+ }),
8151
+ );
8152
+ export type ModelCapabilitiesV1 = z.infer<typeof ModelCapabilitiesV1>;
8153
+
8154
+ export const ModelCredentialSourceV1 = /* @__PURE__ */ defineModelContractSchema(() =>
8155
+ z.union([
8156
+ z
8157
+ .object({ kind: z.literal("deployment"), mechanism: z.enum(["api_key", "azure_ad_bearer"]) })
8158
+ .strict(),
8159
+ z.object({ kind: z.literal("connected_subscription"), provider: z.literal("codex") }).strict(),
8160
+ z.object({ kind: z.literal("workspace_connection"), mechanism: z.literal("api_key") }).strict(),
8161
+ ]),
8162
+ );
8163
+ export type ModelCredentialSourceV1 = z.infer<typeof ModelCredentialSourceV1>;
8164
+
8165
+ export const ModelBillingAttributionV1 = /* @__PURE__ */ defineModelContractSchema(() =>
8166
+ z
8167
+ .object({
8168
+ upstreamPayer: z.enum(["deployment", "workspace", "connected_subscription"]),
8169
+ metering: z.enum(["opengeni_credits", "external"]),
8170
+ })
8171
+ .strict(),
8172
+ );
8173
+ export type ModelBillingAttributionV1 = z.infer<typeof ModelBillingAttributionV1>;
8174
+
8175
+ export const TURN_EXECUTION_POLICY_METADATA_KEY = "turnExecutionPolicyV1" as const;
8176
+
8177
+ export const TurnExecutionModelSourceV1 = /* @__PURE__ */ defineModelContractSchema(() =>
8178
+ z.enum(["explicit", "session", "deployment", "continuation"]),
8179
+ );
8180
+ export type TurnExecutionModelSourceV1 = z.infer<typeof TurnExecutionModelSourceV1>;
8181
+
8182
+ export const TurnExecutionReasoningSourceV1 = /* @__PURE__ */ defineModelContractSchema(() =>
8183
+ z.enum(["explicit", "session", "deployment", "continuation"]),
8184
+ );
8185
+ export type TurnExecutionReasoningSourceV1 = z.infer<typeof TurnExecutionReasoningSourceV1>;
8186
+
8187
+ /**
8188
+ * Secret-safe execution identity frozen onto one accepted logical turn.
8189
+ *
8190
+ * This is deliberately a strict, normalized reference to the deployment
8191
+ * definition rather than a serialized provider client. It must never contain
8192
+ * a key/token, concrete connected credential id, account label, authorization
8193
+ * header, or credential-bearing URL/query value.
8194
+ */
8195
+ export const TurnExecutionPolicyV1 = /* @__PURE__ */ defineModelContractSchema(() =>
8196
+ z
8197
+ .object({
8198
+ schemaVersion: z.literal(1),
8199
+ productModelId: z.string().min(1),
8200
+ requestedModelId: z.string().min(1).nullable(),
8201
+ modelSource: TurnExecutionModelSourceV1,
8202
+ reasoningEffort: ReasoningEffort,
8203
+ reasoningSource: TurnExecutionReasoningSourceV1,
8204
+ providerId: z.string().min(1),
8205
+ upstreamModelId: z.string().min(1),
8206
+ wireApi: z.enum(["responses", "chat"]),
8207
+ credentialSource: ModelCredentialSourceV1,
8208
+ billing: ModelBillingAttributionV1,
8209
+ definitionVersion: z.string().regex(/^sha256:[a-f0-9]{64}$/u),
8210
+ })
8211
+ .strict()
8212
+ .superRefine((policy, context) => {
8213
+ if (policy.modelSource === "explicit" && policy.requestedModelId === null) {
8214
+ context.addIssue({
8215
+ code: "custom",
8216
+ path: ["requestedModelId"],
8217
+ message: "an explicit model source requires a requested model id",
8218
+ });
8219
+ }
8220
+ if (policy.modelSource !== "explicit" && policy.requestedModelId !== null) {
8221
+ context.addIssue({
8222
+ code: "custom",
8223
+ path: ["requestedModelId"],
8224
+ message: "only an explicit model source may retain a requested model id",
8225
+ });
8226
+ }
8227
+ }),
8228
+ );
8229
+ export type TurnExecutionPolicyV1 = z.infer<typeof TurnExecutionPolicyV1>;
8230
+
8231
+ export type TurnExecutionPolicyReadV1 =
8232
+ | { kind: "absent" }
8233
+ | { kind: "valid"; policy: TurnExecutionPolicyV1 };
8234
+
8235
+ /**
8236
+ * Read the policy from turn metadata. Only a literally absent key is legacy;
8237
+ * null, undefined, an unknown schema version, extra fields, and every other
8238
+ * malformed present value fail closed. Error text reports paths only and never
8239
+ * reflects the untrusted value into logs or events.
8240
+ */
8241
+ export function readTurnExecutionPolicyV1(metadata: unknown): TurnExecutionPolicyReadV1 {
8242
+ if (metadata === null || metadata === undefined) {
8243
+ return { kind: "absent" };
8244
+ }
8245
+ if (typeof metadata !== "object" || Array.isArray(metadata)) {
8246
+ throw new Error("Malformed turn execution policy metadata: turn metadata is not an object");
8247
+ }
8248
+ const record = metadata as Record<string, unknown>;
8249
+ if (!Object.prototype.hasOwnProperty.call(record, TURN_EXECUTION_POLICY_METADATA_KEY)) {
8250
+ return { kind: "absent" };
8251
+ }
8252
+ const parsed = TurnExecutionPolicyV1.safeParse(record[TURN_EXECUTION_POLICY_METADATA_KEY]);
8253
+ if (!parsed.success) {
8254
+ const paths = [
8255
+ ...new Set(
8256
+ parsed.error.issues.map((issue) =>
8257
+ issue.path.length === 0 ? "policy" : `policy.${issue.path.join(".")}`,
8258
+ ),
8259
+ ),
8260
+ ].join(", ");
8261
+ throw new Error(`Malformed turn execution policy metadata at ${paths || "policy"}`);
8262
+ }
8263
+ return { kind: "valid", policy: parsed.data };
8264
+ }
8265
+
8266
+ /** Merge a trusted policy into metadata without disturbing dispatch/recovery state. */
8267
+ export function metadataWithTurnExecutionPolicyV1(
8268
+ metadata: Readonly<Record<string, unknown>> | null | undefined,
8269
+ policy: TurnExecutionPolicyV1,
8270
+ ): Record<string, unknown> {
8271
+ return {
8272
+ ...(metadata ?? {}),
8273
+ [TURN_EXECUTION_POLICY_METADATA_KEY]: TurnExecutionPolicyV1.parse(policy),
8274
+ };
8275
+ }
8276
+
8277
+ /**
8278
+ * Minimal, stable evidence projection for command receipts and audit events.
8279
+ * It intentionally excludes aliases, URLs, request metadata, and all concrete
8280
+ * credential-selection identity.
8281
+ */
8282
+ export function turnExecutionPolicyAuditMetadata(
8283
+ policy: TurnExecutionPolicyV1,
8284
+ turnId: string,
8285
+ ): Record<string, unknown> {
8286
+ const parsed = TurnExecutionPolicyV1.parse(policy);
8287
+ return {
8288
+ turnId,
8289
+ requestedModelId: parsed.requestedModelId,
8290
+ effectiveModelId: parsed.productModelId,
8291
+ modelSource: parsed.modelSource,
8292
+ effectiveReasoningEffort: parsed.reasoningEffort,
8293
+ reasoningSource: parsed.reasoningSource,
8294
+ providerId: parsed.providerId,
8295
+ credentialSourceKind: parsed.credentialSource.kind,
8296
+ credentialSourceMechanism:
8297
+ parsed.credentialSource.kind === "connected_subscription"
8298
+ ? parsed.credentialSource.provider
8299
+ : parsed.credentialSource.mechanism,
8300
+ billingOwner: parsed.billing.upstreamPayer,
8301
+ billingMetering: parsed.billing.metering,
8302
+ definitionVersion: parsed.definitionVersion,
8303
+ };
8304
+ }
8305
+
8306
+ export const ModelPricingV1 = /* @__PURE__ */ defineModelContractSchema(() =>
8307
+ z.object({
8308
+ inputMicrosPerMillionTokens: z.number().int().nonnegative(),
8309
+ cachedInputMicrosPerMillionTokens: z.number().int().nonnegative().optional(),
8310
+ outputMicrosPerMillionTokens: z.number().int().nonnegative(),
8311
+ marginBps: z.number().int().min(0).max(100_000).optional(),
8312
+ }),
8313
+ );
8314
+ export type ModelPricingV1 = z.infer<typeof ModelPricingV1>;
8315
+
8316
+ export const ModelPricingScheduleV1 = /* @__PURE__ */ defineModelContractSchema(() =>
8317
+ z.object({
8318
+ default: ModelPricingV1,
8319
+ inputTokenTiers: z
8320
+ .array(
8321
+ z.object({
8322
+ minimumInputTokens: z.number().int().nonnegative(),
8323
+ pricing: ModelPricingV1,
8324
+ }),
8325
+ )
8326
+ .optional(),
8327
+ }),
8328
+ );
8329
+ export type ModelPricingScheduleV1 = z.infer<typeof ModelPricingScheduleV1>;
8330
+
7313
8331
  /**
7314
8332
  * A single host-exposed model + the provider that serves it, as surfaced to
7315
8333
  * clients (SDK + React composer) by GET /v1/config/client. The wire `api`
@@ -7317,16 +8335,134 @@ export type MachineMetricsSeriesResponse = z.infer<typeof MachineMetricsSeriesRe
7317
8335
  * provider id/label drive the picker's grouping. This mirrors the runtime's
7318
8336
  * ConfiguredModel (packages/config) projected to the client-safe fields.
7319
8337
  */
7320
- export const ClientModel = z.object({
7321
- id: z.string(),
7322
- label: z.string(),
7323
- provider: z.string(), // provider id
7324
- providerLabel: z.string(),
7325
- api: z.enum(["responses", "chat"]),
7326
- contextWindowTokens: z.number().int().positive().optional(),
7327
- });
8338
+ export const ClientModel = /* @__PURE__ */ defineModelContractSchema(() =>
8339
+ z.object({
8340
+ id: z.string(),
8341
+ label: z.string(),
8342
+ provider: z.string(), // provider id
8343
+ providerLabel: z.string(),
8344
+ api: z.enum(["responses", "chat"]),
8345
+ contextWindowTokens: z.number().int().positive().optional(),
8346
+ // Additive normalized definition metadata. Optional so older server payloads
8347
+ // remain parseable; current servers project the complete V1 set.
8348
+ schemaVersion: z.literal(1).optional(),
8349
+ aliases: z.array(z.string()).optional(),
8350
+ deployment: z
8351
+ .object({
8352
+ upstreamModelId: z.string().min(1),
8353
+ wireApi: z.enum(["responses", "chat"]),
8354
+ })
8355
+ .optional(),
8356
+ executionLimits: z
8357
+ .object({
8358
+ contextWindowTokens: z.number().int().positive().nullable(),
8359
+ effectiveContextWindowTokens: z.number().int().positive().nullable(),
8360
+ autoCompactTokenLimit: z.number().int().positive().nullable(),
8361
+ toolOutputTruncationTokens: z.number().int().positive().nullable(),
8362
+ })
8363
+ .optional(),
8364
+ credentialSource: ModelCredentialSourceV1.optional(),
8365
+ billing: ModelBillingAttributionV1.optional(),
8366
+ capabilities: ModelCapabilitiesV1.optional(),
8367
+ pricing: ModelPricingScheduleV1.optional(),
8368
+ definitionVersion: z
8369
+ .string()
8370
+ .regex(/^sha256:[a-f0-9]{64}$/u)
8371
+ .optional(),
8372
+ }),
8373
+ );
7328
8374
  export type ClientModel = z.infer<typeof ClientModel>;
7329
8375
 
8376
+ export const ModelCredentialReadinessV1 = /* @__PURE__ */ defineModelContractSchema(() =>
8377
+ z
8378
+ .object({
8379
+ status: z.enum(["ready", "not_ready", "error"]),
8380
+ reason: z
8381
+ .enum([
8382
+ "missing_credential",
8383
+ "needs_reauth",
8384
+ "prerequisites_missing",
8385
+ "resolver_error",
8386
+ "observation_stale",
8387
+ ])
8388
+ .nullable(),
8389
+ basis: z.enum(["configuration", "connection", "resolver"]),
8390
+ checkedAt: z.string().datetime().nullable(),
8391
+ })
8392
+ .strict()
8393
+ .superRefine((readiness, context) => {
8394
+ if ((readiness.status === "ready") !== (readiness.reason === null)) {
8395
+ context.addIssue({
8396
+ code: "custom",
8397
+ path: ["reason"],
8398
+ message: "ready credential state requires no reason; non-ready state requires a reason",
8399
+ });
8400
+ }
8401
+ if ((readiness.status === "error") !== (readiness.reason === "resolver_error")) {
8402
+ context.addIssue({
8403
+ code: "custom",
8404
+ path: ["reason"],
8405
+ message:
8406
+ "credential errors require resolver_error and resolver_error requires error status",
8407
+ });
8408
+ }
8409
+ if (
8410
+ readiness.basis === "resolver" &&
8411
+ readiness.status === "ready" &&
8412
+ readiness.checkedAt === null
8413
+ ) {
8414
+ context.addIssue({
8415
+ code: "custom",
8416
+ path: ["checkedAt"],
8417
+ message: "resolver readiness requires an observation timestamp",
8418
+ });
8419
+ }
8420
+ if (readiness.reason === "observation_stale" && readiness.checkedAt === null) {
8421
+ context.addIssue({
8422
+ code: "custom",
8423
+ path: ["checkedAt"],
8424
+ message: "a stale observation requires its observation timestamp",
8425
+ });
8426
+ }
8427
+ }),
8428
+ );
8429
+ export type ModelCredentialReadinessV1 = z.infer<typeof ModelCredentialReadinessV1>;
8430
+
8431
+ export const ModelAvailabilityV1 = /* @__PURE__ */ defineModelContractSchema(() =>
8432
+ z.object({
8433
+ status: z.enum(["available", "unavailable", "degraded", "unknown"]),
8434
+ selectable: z.boolean(),
8435
+ reason: z
8436
+ .enum([
8437
+ "missing_credential",
8438
+ "needs_reauth",
8439
+ "credential_not_ready",
8440
+ "not_entitled",
8441
+ "provider_unhealthy",
8442
+ "policy_blocked",
8443
+ "unsupported",
8444
+ ])
8445
+ .nullable(),
8446
+ checkedAt: z.string().datetime().nullable(),
8447
+ }),
8448
+ );
8449
+ export type ModelAvailabilityV1 = z.infer<typeof ModelAvailabilityV1>;
8450
+
8451
+ export const WorkspaceModelCatalogModel = /* @__PURE__ */ defineModelContractSchema(() =>
8452
+ ClientModel.extend({
8453
+ credentialReadiness: ModelCredentialReadinessV1,
8454
+ availability: ModelAvailabilityV1,
8455
+ }),
8456
+ );
8457
+ export type WorkspaceModelCatalogModel = z.infer<typeof WorkspaceModelCatalogModel>;
8458
+
8459
+ export const WorkspaceModelCatalogResponse = /* @__PURE__ */ defineModelContractSchema(() =>
8460
+ z.object({
8461
+ models: z.array(WorkspaceModelCatalogModel),
8462
+ }),
8463
+ );
8464
+ export type WorkspaceModelCatalogResponse = z.infer<typeof WorkspaceModelCatalogResponse>;
8465
+
7330
8466
  /**
7331
8467
  * Exact public HTTP protocol revision spoken by this release train.
7332
8468
  *
@@ -7338,46 +8474,48 @@ export type ClientModel = z.infer<typeof ClientModel>;
7338
8474
  export const OPENGENI_API_CONTRACT_REVISION = "2026-07-turn-instructions-v1" as const;
7339
8475
  export const OPENGENI_API_CONTRACT_HEADER = "x-opengeni-api-contract" as const;
7340
8476
 
7341
- export const ClientConfig = z.object({
7342
- deploymentRevision: z.string(),
7343
- apiContractRevision: z.literal(OPENGENI_API_CONTRACT_REVISION),
7344
- // Release-train version of the server (absent on dev/source builds). The
7345
- // compatibility policy lives in docs/architecture.md clients within the
7346
- // same major are supported; evolution is additive within a major.
7347
- serverVersion: z.string().optional(),
7348
- defaultModel: z.string(),
7349
- allowedModels: z.array(z.string()).min(1),
7350
- // Richer model list (provider-grouped) for the picker. Defaults to [] for
7351
- // back-compat: callers that only read allowedModels are unaffected.
7352
- models: z.array(ClientModel).default([]),
7353
- defaultReasoningEffort: ReasoningEffort,
7354
- allowedReasoningEfforts: z.array(ReasoningEffort).min(1),
7355
- mcpServers: z
7356
- .array(
7357
- z.object({
7358
- id: z.string(),
7359
- name: z.string(),
7360
- }),
7361
- )
7362
- .default([]),
7363
- fileUploads: z.object({
7364
- enabled: z.boolean(),
7365
- maxSizeBytes: z.number().int().positive(),
8477
+ export const ClientConfig = /* @__PURE__ */ defineModelContractSchema(() =>
8478
+ z.object({
8479
+ deploymentRevision: z.string(),
8480
+ apiContractRevision: z.literal(OPENGENI_API_CONTRACT_REVISION),
8481
+ // Release-train version of the server (absent on dev/source builds). The
8482
+ // compatibility policy lives in docs/architecture.md clients within the
8483
+ // same major are supported; evolution is additive within a major.
8484
+ serverVersion: z.string().optional(),
8485
+ defaultModel: z.string(),
8486
+ allowedModels: z.array(z.string()).min(1),
8487
+ // Richer model list (provider-grouped) for the picker. Defaults to [] for
8488
+ // back-compat: callers that only read allowedModels are unaffected.
8489
+ models: z.array(ClientModel).default([]),
8490
+ defaultReasoningEffort: ReasoningEffort,
8491
+ allowedReasoningEfforts: z.array(ReasoningEffort).min(1),
8492
+ mcpServers: z
8493
+ .array(
8494
+ z.object({
8495
+ id: z.string(),
8496
+ name: z.string(),
8497
+ }),
8498
+ )
8499
+ .default([]),
8500
+ fileUploads: z.object({
8501
+ enabled: z.boolean(),
8502
+ maxSizeBytes: z.number().int().positive(),
8503
+ }),
8504
+ productAccessMode: ProductAccessMode,
8505
+ auth: ClientAuthConfig.default({ mode: "none" }),
8506
+ // Server-wide hint: does this deployment support Channel-A structured services
8507
+ // at all (P4.4). Per-session availability is negotiated on /stream-capabilities
8508
+ // (it depends on the session's pinned backend); this is the coarse on/off the
8509
+ // client uses to decide whether to even attempt the fs/git/terminal panels.
8510
+ structuredServices: z
8511
+ .object({
8512
+ fileSystem: z.boolean(),
8513
+ git: z.boolean(),
8514
+ terminalEvents: z.boolean(),
8515
+ })
8516
+ .default({ fileSystem: false, git: false, terminalEvents: false }),
7366
8517
  }),
7367
- productAccessMode: ProductAccessMode,
7368
- auth: ClientAuthConfig.default({ mode: "none" }),
7369
- // Server-wide hint: does this deployment support Channel-A structured services
7370
- // at all (P4.4). Per-session availability is negotiated on /stream-capabilities
7371
- // (it depends on the session's pinned backend); this is the coarse on/off the
7372
- // client uses to decide whether to even attempt the fs/git/terminal panels.
7373
- structuredServices: z
7374
- .object({
7375
- fileSystem: z.boolean(),
7376
- git: z.boolean(),
7377
- terminalEvents: z.boolean(),
7378
- })
7379
- .default({ fileSystem: false, git: false, terminalEvents: false }),
7380
- });
8518
+ );
7381
8519
  export type ClientConfig = z.infer<typeof ClientConfig>;
7382
8520
 
7383
8521
  function base64UrlEncode(value: string): string {
@@ -7454,3 +8592,5 @@ export function evaluateWorkspaceModelPolicy(
7454
8592
  }
7455
8593
  return { allowed: true };
7456
8594
  }
8595
+
8596
+ export * from "./codex-fleet-policy";