File size: 3,216 Bytes
4e23b01
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
/**
 * `oauthService` + `authSummaryService` — app-scope OAuth flow and auth
 * summary. Mirrors `agent-core-v2/app/auth/auth.ts`; wire shapes mirror
 * `protocol/src/rest/oauth.ts` (snake_case fields). `resolveTokenProvider`
 * and `getCachedAccessToken` are excluded (non-serializable).
 */

import { z } from 'zod';

import { maybe, noResult } from '../helpers.js';
import type { ServiceContract } from '../types.js';

export const oAuthFlowStatusSchema = z.enum([
  'pending',
  'authenticated',
  'denied',
  'expired',
  'cancelled',
]);

export const oAuthFlowStartSchema = z.discriminatedUnion('status', [
  z.object({
    flow_id: z.string(),
    provider: z.string(),
    status: z.literal('pending'),
    verification_uri: z.string(),
    verification_uri_complete: z.string(),
    user_code: z.string(),
    expires_in: z.number(),
    interval: z.number(),
    expires_at: z.string(),
  }),
  z.object({
    flow_id: z.string(),
    provider: z.string(),
    status: z.literal('authenticated'),
  }),
]);

export const oAuthFlowSnapshotSchema = z.object({
  flow_id: z.string(),
  provider: z.string(),
  status: oAuthFlowStatusSchema,
  verification_uri: z.string(),
  verification_uri_complete: z.string(),
  user_code: z.string(),
  expires_in: z.number(),
  expires_at: z.string(),
  interval: z.number(),
  resolved_at: z.string().optional(),
  error_message: z.string().optional(),
});

export const oAuthLoginCancelResponseSchema = z.object({
  cancelled: z.boolean(),
  status: oAuthFlowStatusSchema,
});

export const oAuthLogoutResponseSchema = z.object({
  logged_out: z.literal(true),
  provider: z.string(),
});

export const authStatusSchema = z.object({
  loggedIn: z.boolean(),
  provider: z.string().optional(),
});

/** Same shape as `refreshProviderModelsResponseSchema` in `./catalog.js` — keep in sync. */
export const refreshOAuthProviderModelsResponseSchema = z.object({
  changed: z.array(
    z.object({
      provider_id: z.string(),
      provider_name: z.string(),
      added: z.number(),
      removed: z.number(),
    }),
  ),
  unchanged: z.array(z.string()),
  failed: z.array(z.object({ provider: z.string(), reason: z.string() })),
});

export const oAuthLoginOptionsSchema = z.object({
  region: z.enum(['mainland-cn', 'global']).optional(),
});

export const authContract = {
  startLogin: {
    input: z.tuple([z.string().optional(), oAuthLoginOptionsSchema.optional()]),
    output: oAuthFlowStartSchema,
  },
  getFlow: {
    input: z.tuple([z.string().optional()]),
    output: maybe(oAuthFlowSnapshotSchema),
  },
  cancelLogin: {
    input: z.tuple([z.string().optional()]),
    output: oAuthLoginCancelResponseSchema,
  },
  logout: { input: z.tuple([z.string().optional()]), output: oAuthLogoutResponseSchema },
  status: { input: z.tuple([z.string().optional()]), output: authStatusSchema },
  refreshOAuthProviderModels: {
    input: z.tuple([]),
    output: refreshOAuthProviderModelsResponseSchema,
  },
} satisfies ServiceContract;

export const authSummaryContract = {
  summarize: { input: z.tuple([]), output: z.array(authStatusSchema) },
  ensureReady: { input: z.tuple([z.string().optional()]), output: noResult },
} satisfies ServiceContract;