@slates/provider 1.0.0-rc.2 → 1.0.0-rc.20

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.
Files changed (124) hide show
  1. package/dist/index.cjs +2847 -2
  2. package/dist/index.d.cts +1019 -0
  3. package/dist/index.d.ts +1019 -9
  4. package/dist/index.module.js +2737 -2
  5. package/package.json +9 -6
  6. package/src/action/action.ts +92 -4
  7. package/src/action/attachment.ts +87 -0
  8. package/src/action/builder.test.ts +136 -0
  9. package/src/action/builder.ts +113 -15
  10. package/src/action/directUpload.ts +59 -0
  11. package/src/action/getFileUrlTool.ts +41 -0
  12. package/src/action/index.ts +4 -2
  13. package/src/action/publicTool.ts +36 -0
  14. package/src/action/scopes.test.ts +76 -0
  15. package/src/action/scopes.ts +103 -0
  16. package/src/action/tool.ts +22 -6
  17. package/src/action/trigger.ts +19 -11
  18. package/src/adapter/adapter.ts +38 -0
  19. package/src/adapter/index.test.ts +322 -0
  20. package/src/adapter/index.ts +2 -0
  21. package/src/adapter/spec.ts +154 -0
  22. package/src/auth/auth.ts +3 -3
  23. package/src/auth/index.ts +2 -0
  24. package/src/auth/oauth.test.ts +83 -0
  25. package/src/auth/oauth.ts +150 -0
  26. package/src/auth/redact.test.ts +103 -0
  27. package/src/auth/redact.ts +111 -0
  28. package/src/auth/types.ts +72 -13
  29. package/src/axios/http.test.ts +83 -0
  30. package/src/axios/index.ts +116 -10
  31. package/src/axios/trace.test.ts +377 -0
  32. package/src/axios/trace.ts +803 -0
  33. package/src/config/config.ts +14 -3
  34. package/src/context/context.ts +264 -17
  35. package/src/context/hook.test.ts +46 -0
  36. package/src/context/hook.ts +15 -6
  37. package/src/context/index.ts +1 -0
  38. package/src/context/pQueue.ts +4 -0
  39. package/src/crypto/hmac.test.ts +40 -0
  40. package/src/crypto/hmac.ts +32 -0
  41. package/src/crypto/index.ts +1 -0
  42. package/src/docs.ts +22 -0
  43. package/src/error/api.test.ts +114 -0
  44. package/src/error/api.ts +236 -0
  45. package/src/error/axios.ts +220 -0
  46. package/src/error/base.test.ts +178 -0
  47. package/src/error/base.ts +108 -2
  48. package/src/error/declaration.ts +7 -2
  49. package/src/error/defaults.ts +50 -0
  50. package/src/error/index.ts +4 -0
  51. package/src/error/service.ts +170 -0
  52. package/src/error/types.ts +75 -0
  53. package/src/error/utils.ts +157 -0
  54. package/src/file/index.test.ts +36 -0
  55. package/src/file/index.ts +27 -0
  56. package/src/index.ts +8 -0
  57. package/src/logger/logger.test.ts +57 -0
  58. package/src/logger/logger.ts +72 -11
  59. package/src/module/resolveDefaultExport.test.ts +32 -0
  60. package/src/module/resolveDefaultExport.ts +14 -0
  61. package/src/object/index.test.ts +38 -0
  62. package/src/object/index.ts +12 -0
  63. package/src/specification/slate.ts +40 -4
  64. package/src/specification/specification.ts +10 -6
  65. package/src/webhook/graph.test.ts +37 -0
  66. package/src/webhook/graph.ts +22 -0
  67. package/src/webhook/index.ts +2 -0
  68. package/src/webhook/meta.test.ts +52 -0
  69. package/src/webhook/meta.ts +43 -0
  70. package/dist/action/action.d.ts +0 -98
  71. package/dist/action/action.d.ts.map +0 -1
  72. package/dist/action/builder.d.ts +0 -27
  73. package/dist/action/builder.d.ts.map +0 -1
  74. package/dist/action/index.d.ts +0 -5
  75. package/dist/action/index.d.ts.map +0 -1
  76. package/dist/action/tool.d.ts +0 -14
  77. package/dist/action/tool.d.ts.map +0 -1
  78. package/dist/action/trigger.d.ts +0 -32
  79. package/dist/action/trigger.d.ts.map +0 -1
  80. package/dist/auth/auth.d.ts +0 -30
  81. package/dist/auth/auth.d.ts.map +0 -1
  82. package/dist/auth/index.d.ts +0 -3
  83. package/dist/auth/index.d.ts.map +0 -1
  84. package/dist/auth/types.d.ts +0 -148
  85. package/dist/auth/types.d.ts.map +0 -1
  86. package/dist/axios/index.d.ts +0 -4
  87. package/dist/axios/index.d.ts.map +0 -1
  88. package/dist/config/config.d.ts +0 -26
  89. package/dist/config/config.d.ts.map +0 -1
  90. package/dist/config/index.d.ts +0 -2
  91. package/dist/config/index.d.ts.map +0 -1
  92. package/dist/context/context.d.ts +0 -20
  93. package/dist/context/context.d.ts.map +0 -1
  94. package/dist/context/hook.d.ts +0 -4
  95. package/dist/context/hook.d.ts.map +0 -1
  96. package/dist/context/index.d.ts +0 -2
  97. package/dist/context/index.d.ts.map +0 -1
  98. package/dist/error/base.d.ts +0 -5
  99. package/dist/error/base.d.ts.map +0 -1
  100. package/dist/error/declaration.d.ts +0 -6
  101. package/dist/error/declaration.d.ts.map +0 -1
  102. package/dist/error/index.d.ts +0 -3
  103. package/dist/error/index.d.ts.map +0 -1
  104. package/dist/index.cjs.map +0 -1
  105. package/dist/index.d.ts.map +0 -1
  106. package/dist/index.modern.js +0 -2
  107. package/dist/index.modern.js.map +0 -1
  108. package/dist/index.module.js.map +0 -1
  109. package/dist/index.umd.js +0 -2
  110. package/dist/index.umd.js.map +0 -1
  111. package/dist/logger/index.d.ts +0 -2
  112. package/dist/logger/index.d.ts.map +0 -1
  113. package/dist/logger/logger.d.ts +0 -20
  114. package/dist/logger/logger.d.ts.map +0 -1
  115. package/dist/specification/index.d.ts +0 -3
  116. package/dist/specification/index.d.ts.map +0 -1
  117. package/dist/specification/slate.d.ts +0 -15
  118. package/dist/specification/slate.d.ts.map +0 -1
  119. package/dist/specification/specification.d.ts +0 -30
  120. package/dist/specification/specification.d.ts.map +0 -1
  121. package/dist/specification/zero.d.ts +0 -13
  122. package/dist/specification/zero.d.ts.map +0 -1
  123. package/dist/tokens.d.ts +0 -34
  124. package/dist/tokens.d.ts.map +0 -1
@@ -0,0 +1,150 @@
1
+ import { createApiServiceError } from '../error/api';
2
+
3
+ export type OAuthTokenResponse = {
4
+ access_token?: unknown;
5
+ refresh_token?: unknown;
6
+ expires_in?: unknown;
7
+ [key: string]: unknown;
8
+ };
9
+
10
+ export type OAuthRefreshTokenFallbackMode = 'nullish' | 'falsy';
11
+
12
+ export type OAuthExpiresAtOptions = {
13
+ nowMs?: number;
14
+ providerLabel?: string;
15
+ operation?: string;
16
+ required?: boolean;
17
+ expiresInType?: 'number' | 'numberLike';
18
+ message?: string;
19
+ };
20
+
21
+ export type NormalizeOAuthTokenResponseOptions = OAuthExpiresAtOptions & {
22
+ previousRefreshToken?: string;
23
+ refreshTokenFallbackMode?: OAuthRefreshTokenFallbackMode;
24
+ accessTokenMessage?: string;
25
+ expiresInMessage?: string;
26
+ };
27
+
28
+ let isOAuthTokenResponse = (value: unknown): value is OAuthTokenResponse =>
29
+ typeof value === 'object' && value !== null && !Array.isArray(value);
30
+
31
+ let formatOAuthTokenResponseMessage = (
32
+ fieldLabel: string,
33
+ providerLabel = 'OAuth provider',
34
+ operation = 'token response'
35
+ ) => `${providerLabel} OAuth ${operation} did not return ${fieldLabel}.`;
36
+
37
+ export let getOAuthExpiresAtFromExpiresIn = (
38
+ expiresIn: unknown,
39
+ options: OAuthExpiresAtOptions = {}
40
+ ) => {
41
+ let {
42
+ nowMs = Date.now(),
43
+ providerLabel,
44
+ operation,
45
+ required = false,
46
+ expiresInType = 'numberLike',
47
+ message
48
+ } = options;
49
+
50
+ if (expiresIn === undefined || expiresIn === null || expiresIn === '') {
51
+ if (required) {
52
+ throw createApiServiceError(
53
+ message ??
54
+ formatOAuthTokenResponseMessage(
55
+ 'a valid expires_in value',
56
+ providerLabel,
57
+ operation
58
+ ),
59
+ { reason: 'oauth_token_response' }
60
+ );
61
+ }
62
+
63
+ return undefined;
64
+ }
65
+
66
+ let seconds =
67
+ typeof expiresIn === 'number'
68
+ ? expiresIn
69
+ : expiresInType === 'numberLike' && typeof expiresIn === 'string'
70
+ ? Number(expiresIn)
71
+ : Number.NaN;
72
+
73
+ if (!Number.isFinite(seconds)) {
74
+ throw createApiServiceError(
75
+ message ??
76
+ formatOAuthTokenResponseMessage('a valid expires_in value', providerLabel, operation),
77
+ { reason: 'oauth_token_response' }
78
+ );
79
+ }
80
+
81
+ return new Date(nowMs + seconds * 1000).toISOString();
82
+ };
83
+
84
+ let getRefreshToken = (
85
+ refreshToken: unknown,
86
+ previousRefreshToken: string | undefined,
87
+ fallbackMode: OAuthRefreshTokenFallbackMode
88
+ ) => {
89
+ if (typeof refreshToken === 'string') {
90
+ if (fallbackMode === 'falsy' && !refreshToken) {
91
+ return previousRefreshToken;
92
+ }
93
+
94
+ return refreshToken;
95
+ }
96
+
97
+ if (fallbackMode === 'falsy' && !refreshToken) {
98
+ return previousRefreshToken;
99
+ }
100
+
101
+ return refreshToken === undefined || refreshToken === null
102
+ ? previousRefreshToken
103
+ : undefined;
104
+ };
105
+
106
+ export let normalizeOAuthTokenResponse = (
107
+ data: unknown,
108
+ options: NormalizeOAuthTokenResponseOptions = {}
109
+ ) => {
110
+ let {
111
+ providerLabel,
112
+ operation,
113
+ previousRefreshToken,
114
+ refreshTokenFallbackMode = 'nullish',
115
+ accessTokenMessage,
116
+ expiresInMessage,
117
+ ...expiresAtOptions
118
+ } = options;
119
+
120
+ if (!isOAuthTokenResponse(data)) {
121
+ throw createApiServiceError(
122
+ accessTokenMessage ??
123
+ formatOAuthTokenResponseMessage('a token response object', providerLabel, operation),
124
+ { reason: 'oauth_token_response' }
125
+ );
126
+ }
127
+
128
+ if (typeof data.access_token !== 'string' || !data.access_token) {
129
+ throw createApiServiceError(
130
+ accessTokenMessage ??
131
+ formatOAuthTokenResponseMessage('an access token', providerLabel, operation),
132
+ { reason: 'oauth_token_response' }
133
+ );
134
+ }
135
+
136
+ return {
137
+ token: data.access_token,
138
+ refreshToken: getRefreshToken(
139
+ data.refresh_token,
140
+ previousRefreshToken,
141
+ refreshTokenFallbackMode
142
+ ),
143
+ expiresAt: getOAuthExpiresAtFromExpiresIn(data.expires_in, {
144
+ ...expiresAtOptions,
145
+ providerLabel,
146
+ operation,
147
+ message: expiresInMessage
148
+ })
149
+ };
150
+ };
@@ -0,0 +1,103 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import type { SlateAttachment } from '../action/attachment';
3
+ import { AuthConfigSecretRedactor, redactUrlAttachmentSecrets } from './redact';
4
+
5
+ describe('AuthConfigSecretRedactor', () => {
6
+ let authConfig = { token: '123SECRET', nested: { token2: 'also_secret' } };
7
+
8
+ it('redacts values that match a secret to placeholders', () => {
9
+ let redactor = new AuthConfigSecretRedactor(authConfig);
10
+
11
+ expect(
12
+ redactor.redact({
13
+ headers: { Authorization: 'Bearer 123SECRET' },
14
+ query: { key: 'also_secret' }
15
+ })
16
+ ).toEqual({
17
+ headers: { Authorization: 'Bearer 123SECRET' }, // whole-value match only, not substring
18
+ query: { key: '$$MT$secret$authConfig$nested.token2' }
19
+ });
20
+ });
21
+
22
+ it('redacts a bare string value', () => {
23
+ let redactor = new AuthConfigSecretRedactor(authConfig);
24
+ expect(redactor.redact('123SECRET')).toBe('$$MT$secret$authConfig$token');
25
+ });
26
+
27
+ it('leaves non-secret strings untouched', () => {
28
+ let redactor = new AuthConfigSecretRedactor(authConfig);
29
+ expect(redactor.redact('https://example.com/file.pdf')).toBe(
30
+ 'https://example.com/file.pdf'
31
+ );
32
+ });
33
+
34
+ it('recurses through arrays and nested objects', () => {
35
+ let redactor = new AuthConfigSecretRedactor(authConfig);
36
+ expect(redactor.redact({ list: ['also_secret', 'plain'] })).toEqual({
37
+ list: ['$$MT$secret$authConfig$nested.token2', 'plain']
38
+ });
39
+ });
40
+
41
+ it('never registers an empty-string secret', () => {
42
+ let redactor = new AuthConfigSecretRedactor({ token: '' });
43
+ expect(redactor.redact({ a: '', b: '' })).toEqual({ a: '', b: '' });
44
+ });
45
+
46
+ it('is a no-op when there is no auth config', () => {
47
+ let redactor = new AuthConfigSecretRedactor(undefined);
48
+ expect(redactor.redact({ a: 'b' })).toEqual({ a: 'b' });
49
+ });
50
+ });
51
+
52
+ describe('redactUrlAttachmentSecrets', () => {
53
+ let authConfig = { token: '123SECRET', nested: { token2: 'also_secret' } };
54
+
55
+ let urlAttachment = (
56
+ overrides: Partial<Extract<SlateAttachment['content'], { type: 'url' }>> = {}
57
+ ): SlateAttachment => ({
58
+ mimeType: 'application/pdf',
59
+ content: { type: 'url', url: 'https://example.com/file.pdf', ...overrides }
60
+ });
61
+
62
+ it('redacts headers and query on url attachments', () => {
63
+ let attachments: SlateAttachment[] = [
64
+ urlAttachment({
65
+ headers: { Authorization: 'Bearer 123SECRET' },
66
+ query: { key: 'also_secret' }
67
+ })
68
+ ];
69
+
70
+ expect(redactUrlAttachmentSecrets(attachments, authConfig)).toEqual([
71
+ urlAttachment({
72
+ headers: { Authorization: 'Bearer 123SECRET' },
73
+ query: { key: '$$MT$secret$authConfig$nested.token2' }
74
+ })
75
+ ]);
76
+ });
77
+
78
+ it('leaves url attachments with no headers/query untouched', () => {
79
+ let attachments = [urlAttachment()];
80
+ expect(redactUrlAttachmentSecrets(attachments, authConfig)).toEqual(attachments);
81
+ });
82
+
83
+ it('leaves content and upload_reference attachments untouched', () => {
84
+ let attachments: SlateAttachment[] = [
85
+ {
86
+ mimeType: 'text/plain',
87
+ content: { type: 'content', encoding: 'utf-8', content: 'hi' }
88
+ },
89
+ { content: { type: 'upload_reference', referenceId: 'ref_1' } }
90
+ ];
91
+
92
+ expect(redactUrlAttachmentSecrets(attachments, authConfig)).toEqual(attachments);
93
+ });
94
+
95
+ it('is a no-op when there is no auth config', () => {
96
+ let attachments = [urlAttachment({ headers: { Authorization: 'Bearer 123SECRET' } })];
97
+ expect(redactUrlAttachmentSecrets(attachments, undefined)).toBe(attachments);
98
+ });
99
+
100
+ it('passes an undefined attachment list through unchanged', () => {
101
+ expect(redactUrlAttachmentSecrets(undefined, authConfig)).toBeUndefined();
102
+ });
103
+ });
@@ -0,0 +1,111 @@
1
+ import type { SlateAttachment } from '../action/attachment';
2
+
3
+ /**
4
+ * Placeholder prefix used to mark a redacted auth-config secret. Must match the hub's
5
+ * AuthConfigSecretSerializer (oss/src/slates/apps/hub/src/lib/secretSerializer.ts) exactly --
6
+ * the hub resolves these placeholders back to live secret values when it proxies an attachment.
7
+ */
8
+ export let AUTH_CONFIG_SECRET_PLACEHOLDER_PREFIX = '$$MT$secret$authConfig$';
9
+
10
+ /**
11
+ * Walks an auth config object and replaces any string value that exactly matches one of its
12
+ * leaf values with a `$$MT$secret$authConfig$<path>` placeholder. Literal, whole-value
13
+ * substitution only -- not a substring scan, and not a heuristic key-name/value-shape guess
14
+ * like the http trace redactor in '../axios/trace'.
15
+ */
16
+ export class AuthConfigSecretRedactor {
17
+ private readonly secretToPlaceholder = new Map<string, string>();
18
+
19
+ public constructor(authConfig: Record<string, unknown> | undefined) {
20
+ if (authConfig) this.buildSecretMap(authConfig);
21
+ }
22
+
23
+ public redact<T>(value: T): T {
24
+ return this.transform(value, stringValue => {
25
+ return this.secretToPlaceholder.get(stringValue) ?? stringValue;
26
+ });
27
+ }
28
+
29
+ private buildSecretMap(value: unknown, path: string[] = []): void {
30
+ if (typeof value === 'string') {
31
+ // Never register "" -- it would replace every empty header/query value with a placeholder.
32
+ if (value.length === 0) return;
33
+
34
+ this.secretToPlaceholder.set(
35
+ value,
36
+ `${AUTH_CONFIG_SECRET_PLACEHOLDER_PREFIX}${path.join('.')}`
37
+ );
38
+ return;
39
+ }
40
+
41
+ if (Array.isArray(value)) {
42
+ value.forEach((item, index) => {
43
+ this.buildSecretMap(item, [...path, String(index)]);
44
+ });
45
+
46
+ return;
47
+ }
48
+
49
+ if (this.isPlainObject(value)) {
50
+ Object.entries(value).forEach(([key, nestedValue]) => {
51
+ this.buildSecretMap(nestedValue, [...path, key]);
52
+ });
53
+ }
54
+ }
55
+
56
+ private transform<T>(value: T, transformString: (value: string) => string): T {
57
+ if (typeof value === 'string') {
58
+ return transformString(value) as T;
59
+ }
60
+
61
+ if (Array.isArray(value)) {
62
+ return value.map(item => this.transform(item, transformString)) as T;
63
+ }
64
+
65
+ if (this.isPlainObject(value)) {
66
+ let result: Record<string, unknown> = {};
67
+
68
+ Object.entries(value).forEach(([key, nestedValue]) => {
69
+ result[key] = this.transform(nestedValue, transformString);
70
+ });
71
+
72
+ return result as T;
73
+ }
74
+
75
+ return value;
76
+ }
77
+
78
+ private isPlainObject(value: unknown): value is Record<string, unknown> {
79
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Applied to every finalized attachment list right before it's sent over
85
+ * `slates/action.tool.invoke` (see @slates/provider-handler). Only `type: 'url'` attachments
86
+ * carry headers/query -- content/upload_reference attachments pass through untouched.
87
+ */
88
+ export let redactUrlAttachmentSecrets = (
89
+ attachments: SlateAttachment[] | undefined,
90
+ authConfig: Record<string, unknown> | undefined
91
+ ): SlateAttachment[] | undefined => {
92
+ if (!authConfig || !attachments) return attachments;
93
+
94
+ let redactor = new AuthConfigSecretRedactor(authConfig);
95
+
96
+ return attachments.map(attachment => {
97
+ if (attachment.content.type !== 'url') return attachment;
98
+ if (!attachment.content.headers && !attachment.content.query) return attachment;
99
+
100
+ return {
101
+ ...attachment,
102
+ content: {
103
+ ...attachment.content,
104
+ headers: attachment.content.headers
105
+ ? redactor.redact(attachment.content.headers)
106
+ : undefined,
107
+ query: attachment.content.query ? redactor.redact(attachment.content.query) : undefined
108
+ }
109
+ };
110
+ });
111
+ };
package/src/auth/types.ts CHANGED
@@ -1,4 +1,7 @@
1
- import z from 'zod';
1
+ import type z from 'zod';
2
+ import type { SlateAuthDocsReference } from '../docs';
3
+
4
+ type SlateAuthCallbackConfig = Record<string, any>;
2
5
 
3
6
  export type SlateAuthWithOauth<
4
7
  InputType extends {},
@@ -19,12 +22,14 @@ export type SlateAuthWithOauth<
19
22
  scope: string;
20
23
  }[];
21
24
 
25
+ docs?: SlateAuthDocsReference[];
26
+
22
27
  inputSchema?: z.ZodType<InputType>;
23
28
 
24
29
  onInputChanged?: (params: {
25
30
  previousInput: InputType | null;
26
31
  newInput: InputType;
27
- }) => Promise<{ input?: InputType } | void>;
32
+ }) => Promise<{ input?: InputType } | undefined>;
28
33
 
29
34
  getDefaultInput?: () => Promise<InputType>;
30
35
 
@@ -35,9 +40,11 @@ export type SlateAuthWithOauth<
35
40
  clientId: string;
36
41
  clientSecret: string;
37
42
  scopes: string[];
43
+ config?: SlateAuthCallbackConfig;
38
44
  }) => Promise<{
39
45
  url: string;
40
46
  input?: InputType;
47
+ callbackState?: Record<string, any>;
41
48
  }>;
42
49
 
43
50
  handleCallback: (ctx: {
@@ -48,9 +55,13 @@ export type SlateAuthWithOauth<
48
55
  clientId: string;
49
56
  clientSecret: string;
50
57
  scopes: string[];
58
+ callbackParams?: Record<string, string>;
59
+ callbackState: Record<string, any>;
60
+ config?: SlateAuthCallbackConfig;
51
61
  }) => Promise<{
52
62
  output: OutputType;
53
63
  input?: InputType;
64
+ scopes?: string[];
54
65
  }>;
55
66
 
56
67
  handleTokenRefresh?: (ctx: {
@@ -59,12 +70,18 @@ export type SlateAuthWithOauth<
59
70
  clientId: string;
60
71
  clientSecret: string;
61
72
  scopes: string[];
73
+ config?: SlateAuthCallbackConfig;
62
74
  }) => Promise<{
63
75
  output: OutputType;
64
76
  input?: InputType;
65
77
  }>;
66
78
 
67
- getProfile?: (ctx: { output: OutputType; input: InputType; scopes: string[] }) => Promise<{
79
+ getProfile?: (ctx: {
80
+ output: OutputType;
81
+ input: InputType;
82
+ scopes: string[];
83
+ config?: SlateAuthCallbackConfig;
84
+ }) => Promise<{
68
85
  profile: Record<string, any>;
69
86
  }>;
70
87
  };
@@ -77,16 +94,26 @@ export type SlateAuthWithToken<InputType extends {}, OutputType extends { token?
77
94
 
78
95
  inputSchema?: z.ZodType<InputType>;
79
96
 
80
- getOutput: (ctx: { input: InputType }) => Promise<{ output: OutputType }>;
97
+ docs?: SlateAuthDocsReference[];
98
+
99
+ getOutput: (ctx: { input: InputType; config?: SlateAuthCallbackConfig }) => Promise<{
100
+ output: OutputType;
101
+ scopes?: string[];
102
+ }>;
81
103
 
82
104
  onInputChanged?: (params: {
83
105
  previousInput: InputType;
84
106
  newInput: InputType;
85
- }) => Promise<{ input?: InputType } | void>;
107
+ }) => Promise<{ input?: InputType } | undefined>;
86
108
 
87
109
  getDefaultInput?: () => Promise<InputType>;
88
110
 
89
- getProfile?: (ctx: { output: OutputType; input: InputType }) => Promise<{
111
+ getProfile?: (ctx: {
112
+ output: OutputType;
113
+ input: InputType;
114
+ scopes: string[];
115
+ config?: SlateAuthCallbackConfig;
116
+ }) => Promise<{
90
117
  profile: Record<string, any>;
91
118
  }>;
92
119
  };
@@ -99,16 +126,26 @@ export type SlateAuthWithServiceAccount<InputType extends {}, OutputType extends
99
126
 
100
127
  inputSchema?: z.ZodType<InputType>;
101
128
 
102
- getOutput: (ctx: { input: InputType }) => Promise<{ output: OutputType }>;
129
+ docs?: SlateAuthDocsReference[];
130
+
131
+ getOutput: (ctx: { input: InputType; config?: SlateAuthCallbackConfig }) => Promise<{
132
+ output: OutputType;
133
+ scopes?: string[];
134
+ }>;
103
135
 
104
136
  onInputChanged?: (params: {
105
137
  previousInput: InputType;
106
138
  newInput: InputType;
107
- }) => Promise<{ input?: InputType } | void>;
139
+ }) => Promise<{ input?: InputType } | undefined>;
108
140
 
109
141
  getDefaultInput?: () => Promise<InputType>;
110
142
 
111
- getProfile?: (ctx: { output: OutputType; input: InputType }) => Promise<{
143
+ getProfile?: (ctx: {
144
+ output: OutputType;
145
+ input: InputType;
146
+ scopes: string[];
147
+ config?: SlateAuthCallbackConfig;
148
+ }) => Promise<{
112
149
  profile: Record<string, any>;
113
150
  }>;
114
151
  };
@@ -121,21 +158,43 @@ export type SlateAuthWithCustomData<InputType extends {}, OutputType extends {}>
121
158
 
122
159
  inputSchema?: z.ZodType<InputType>;
123
160
 
124
- getOutput: (ctx: { input: InputType }) => Promise<{ output: OutputType }>;
161
+ docs?: SlateAuthDocsReference[];
162
+
163
+ getOutput: (ctx: { input: InputType; config?: SlateAuthCallbackConfig }) => Promise<{
164
+ output: OutputType;
165
+ scopes?: string[];
166
+ }>;
167
+
168
+ handleTokenRefresh?: (ctx: {
169
+ output: OutputType;
170
+ input: InputType;
171
+ clientId: string;
172
+ clientSecret: string;
173
+ scopes: string[];
174
+ config?: SlateAuthCallbackConfig;
175
+ }) => Promise<{
176
+ output: OutputType;
177
+ input?: InputType;
178
+ }>;
125
179
 
126
180
  onInputChanged?: (params: {
127
181
  previousInput: InputType;
128
182
  newInput: InputType;
129
- }) => Promise<{ input?: InputType } | void>;
183
+ }) => Promise<{ input?: InputType } | undefined>;
130
184
 
131
185
  getDefaultInput?: () => Promise<InputType>;
132
186
 
133
- getProfile?: (ctx: { output: OutputType; input: InputType }) => Promise<{
187
+ getProfile?: (ctx: {
188
+ output: OutputType;
189
+ input: InputType;
190
+ scopes: string[];
191
+ config?: SlateAuthCallbackConfig;
192
+ }) => Promise<{
134
193
  profile: Record<string, any>;
135
194
  }>;
136
195
  };
137
196
 
138
- export type SlateAuthWithNone<InputType extends {}, OutputType extends {}> = {
197
+ export type SlateAuthWithNone<_InputType extends {}, _OutputType extends {}> = {
139
198
  type: 'auth.none';
140
199
 
141
200
  name: string;
@@ -0,0 +1,83 @@
1
+ import type { AxiosResponse } from 'axios';
2
+ import { describe, expect, it } from 'vitest';
3
+ import { createAuthenticatedAxios, requestAxios, requestAxiosData } from './index';
4
+
5
+ describe('createAuthenticatedAxios', () => {
6
+ it('sets auth and JSON headers while preserving caller headers', () => {
7
+ let client = createAuthenticatedAxios({
8
+ baseURL: 'https://example.test',
9
+ authHeader: {
10
+ value: 'Bearer token'
11
+ },
12
+ headers: {
13
+ Accept: 'application/json'
14
+ }
15
+ });
16
+
17
+ expect(client.defaults.baseURL).toBe('https://example.test');
18
+ expect(client.defaults.headers.Authorization).toBe('Bearer token');
19
+ expect(client.defaults.headers['Content-Type']).toBe('application/json');
20
+ expect(client.defaults.headers.Accept).toBe('application/json');
21
+ });
22
+
23
+ it('allows custom auth header names and disabling content type defaults', () => {
24
+ let client = createAuthenticatedAxios({
25
+ authHeader: {
26
+ name: 'X-API-Key',
27
+ value: 'secret'
28
+ },
29
+ contentType: false
30
+ });
31
+
32
+ expect(client.defaults.headers['X-API-Key']).toBe('secret');
33
+ expect(client.defaults.headers['Content-Type']).toBeUndefined();
34
+ });
35
+ });
36
+
37
+ describe('requestAxios helpers', () => {
38
+ it('returns full responses or response data', async () => {
39
+ let response = {
40
+ config: {} as AxiosResponse<{ ok: boolean }>['config'],
41
+ data: { ok: true },
42
+ headers: {},
43
+ status: 200,
44
+ statusText: 'OK'
45
+ };
46
+
47
+ await expect(
48
+ requestAxios(
49
+ 'get thing',
50
+ async () => response,
51
+ error => error
52
+ )
53
+ ).resolves.toBe(response);
54
+ await expect(
55
+ requestAxiosData(
56
+ 'get thing',
57
+ async () => response,
58
+ error => error
59
+ )
60
+ ).resolves.toEqual({ ok: true });
61
+ });
62
+
63
+ it('maps failures with the operation name', async () => {
64
+ let upstream = new Error('upstream failed');
65
+ let mapped = new Error('mapped failure');
66
+ let seenOperation: string | undefined;
67
+
68
+ await expect(
69
+ requestAxiosData(
70
+ 'create thing',
71
+ async () => {
72
+ throw upstream;
73
+ },
74
+ (_error, operation) => {
75
+ seenOperation = operation;
76
+ return mapped;
77
+ }
78
+ )
79
+ ).rejects.toBe(mapped);
80
+
81
+ expect(seenOperation).toBe('create thing');
82
+ });
83
+ });