@clinebot/core 0.0.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.
Files changed (200) hide show
  1. package/README.md +88 -0
  2. package/dist/account/cline-account-service.d.ts +34 -0
  3. package/dist/account/index.d.ts +3 -0
  4. package/dist/account/rpc.d.ts +38 -0
  5. package/dist/account/types.d.ts +74 -0
  6. package/dist/agents/agent-config-loader.d.ts +18 -0
  7. package/dist/agents/agent-config-parser.d.ts +25 -0
  8. package/dist/agents/hooks-config-loader.d.ts +23 -0
  9. package/dist/agents/index.d.ts +11 -0
  10. package/dist/agents/plugin-config-loader.d.ts +22 -0
  11. package/dist/agents/plugin-loader.d.ts +9 -0
  12. package/dist/agents/plugin-sandbox.d.ts +12 -0
  13. package/dist/agents/unified-config-file-watcher.d.ts +77 -0
  14. package/dist/agents/user-instruction-config-loader.d.ts +63 -0
  15. package/dist/auth/client.d.ts +11 -0
  16. package/dist/auth/cline.d.ts +41 -0
  17. package/dist/auth/codex.d.ts +39 -0
  18. package/dist/auth/oca.d.ts +22 -0
  19. package/dist/auth/server.d.ts +22 -0
  20. package/dist/auth/types.d.ts +72 -0
  21. package/dist/auth/utils.d.ts +32 -0
  22. package/dist/chat/chat-schema.d.ts +145 -0
  23. package/dist/default-tools/constants.d.ts +23 -0
  24. package/dist/default-tools/definitions.d.ts +96 -0
  25. package/dist/default-tools/executors/apply-patch-parser.d.ts +68 -0
  26. package/dist/default-tools/executors/apply-patch.d.ts +26 -0
  27. package/dist/default-tools/executors/bash.d.ts +49 -0
  28. package/dist/default-tools/executors/editor.d.ts +31 -0
  29. package/dist/default-tools/executors/file-read.d.ts +40 -0
  30. package/dist/default-tools/executors/index.d.ts +44 -0
  31. package/dist/default-tools/executors/search.d.ts +50 -0
  32. package/dist/default-tools/executors/web-fetch.d.ts +58 -0
  33. package/dist/default-tools/index.d.ts +57 -0
  34. package/dist/default-tools/presets.d.ts +124 -0
  35. package/dist/default-tools/schemas.d.ts +121 -0
  36. package/dist/default-tools/types.d.ts +237 -0
  37. package/dist/index.d.ts +23 -0
  38. package/dist/index.js +220 -0
  39. package/dist/input/file-indexer.d.ts +5 -0
  40. package/dist/input/index.d.ts +4 -0
  41. package/dist/input/mention-enricher.d.ts +12 -0
  42. package/dist/mcp/config-loader.d.ts +15 -0
  43. package/dist/mcp/index.d.ts +4 -0
  44. package/dist/mcp/manager.d.ts +24 -0
  45. package/dist/mcp/types.d.ts +66 -0
  46. package/dist/runtime/hook-file-hooks.d.ts +18 -0
  47. package/dist/runtime/rules.d.ts +5 -0
  48. package/dist/runtime/runtime-builder.d.ts +5 -0
  49. package/dist/runtime/sandbox/subprocess-sandbox.d.ts +19 -0
  50. package/dist/runtime/session-runtime.d.ts +36 -0
  51. package/dist/runtime/tool-approval.d.ts +9 -0
  52. package/dist/runtime/workflows.d.ts +13 -0
  53. package/dist/server/index.d.ts +47 -0
  54. package/dist/server/index.js +641 -0
  55. package/dist/session/default-session-manager.d.ts +77 -0
  56. package/dist/session/rpc-session-service.d.ts +12 -0
  57. package/dist/session/runtime-oauth-token-manager.d.ts +28 -0
  58. package/dist/session/session-artifacts.d.ts +19 -0
  59. package/dist/session/session-graph.d.ts +15 -0
  60. package/dist/session/session-host.d.ts +21 -0
  61. package/dist/session/session-manager.d.ts +50 -0
  62. package/dist/session/session-manifest.d.ts +30 -0
  63. package/dist/session/session-service.d.ts +113 -0
  64. package/dist/session/sqlite-rpc-session-backend.d.ts +30 -0
  65. package/dist/session/unified-session-persistence-service.d.ts +93 -0
  66. package/dist/session/workspace-manager.d.ts +28 -0
  67. package/dist/session/workspace-manifest.d.ts +25 -0
  68. package/dist/storage/provider-settings-legacy-migration.d.ts +13 -0
  69. package/dist/storage/provider-settings-manager.d.ts +20 -0
  70. package/dist/storage/sqlite-session-store.d.ts +29 -0
  71. package/dist/storage/sqlite-team-store.d.ts +31 -0
  72. package/dist/storage/team-store.d.ts +2 -0
  73. package/dist/team/index.d.ts +1 -0
  74. package/dist/team/projections.d.ts +8 -0
  75. package/dist/types/common.d.ts +10 -0
  76. package/dist/types/config.d.ts +37 -0
  77. package/dist/types/events.d.ts +54 -0
  78. package/dist/types/provider-settings.d.ts +20 -0
  79. package/dist/types/sessions.d.ts +9 -0
  80. package/dist/types/storage.d.ts +37 -0
  81. package/dist/types/workspace.d.ts +7 -0
  82. package/dist/types.d.ts +26 -0
  83. package/package.json +63 -0
  84. package/src/account/cline-account-service.test.ts +101 -0
  85. package/src/account/cline-account-service.ts +267 -0
  86. package/src/account/index.ts +20 -0
  87. package/src/account/rpc.test.ts +62 -0
  88. package/src/account/rpc.ts +172 -0
  89. package/src/account/types.ts +80 -0
  90. package/src/agents/agent-config-loader.test.ts +234 -0
  91. package/src/agents/agent-config-loader.ts +107 -0
  92. package/src/agents/agent-config-parser.ts +191 -0
  93. package/src/agents/hooks-config-loader.ts +97 -0
  94. package/src/agents/index.ts +84 -0
  95. package/src/agents/plugin-config-loader.test.ts +91 -0
  96. package/src/agents/plugin-config-loader.ts +160 -0
  97. package/src/agents/plugin-loader.test.ts +102 -0
  98. package/src/agents/plugin-loader.ts +105 -0
  99. package/src/agents/plugin-sandbox.test.ts +120 -0
  100. package/src/agents/plugin-sandbox.ts +471 -0
  101. package/src/agents/unified-config-file-watcher.test.ts +196 -0
  102. package/src/agents/unified-config-file-watcher.ts +483 -0
  103. package/src/agents/user-instruction-config-loader.test.ts +158 -0
  104. package/src/agents/user-instruction-config-loader.ts +438 -0
  105. package/src/auth/client.test.ts +40 -0
  106. package/src/auth/client.ts +25 -0
  107. package/src/auth/cline.test.ts +130 -0
  108. package/src/auth/cline.ts +414 -0
  109. package/src/auth/codex.test.ts +170 -0
  110. package/src/auth/codex.ts +466 -0
  111. package/src/auth/oca.test.ts +215 -0
  112. package/src/auth/oca.ts +546 -0
  113. package/src/auth/server.ts +216 -0
  114. package/src/auth/types.ts +78 -0
  115. package/src/auth/utils.test.ts +128 -0
  116. package/src/auth/utils.ts +247 -0
  117. package/src/chat/chat-schema.ts +82 -0
  118. package/src/default-tools/constants.ts +35 -0
  119. package/src/default-tools/definitions.test.ts +233 -0
  120. package/src/default-tools/definitions.ts +632 -0
  121. package/src/default-tools/executors/apply-patch-parser.ts +520 -0
  122. package/src/default-tools/executors/apply-patch.ts +359 -0
  123. package/src/default-tools/executors/bash.ts +205 -0
  124. package/src/default-tools/executors/editor.ts +231 -0
  125. package/src/default-tools/executors/file-read.test.ts +25 -0
  126. package/src/default-tools/executors/file-read.ts +94 -0
  127. package/src/default-tools/executors/index.ts +75 -0
  128. package/src/default-tools/executors/search.ts +278 -0
  129. package/src/default-tools/executors/web-fetch.ts +259 -0
  130. package/src/default-tools/index.ts +161 -0
  131. package/src/default-tools/presets.test.ts +63 -0
  132. package/src/default-tools/presets.ts +168 -0
  133. package/src/default-tools/schemas.ts +228 -0
  134. package/src/default-tools/types.ts +324 -0
  135. package/src/index.ts +119 -0
  136. package/src/input/file-indexer.d.ts +11 -0
  137. package/src/input/file-indexer.test.ts +87 -0
  138. package/src/input/file-indexer.ts +280 -0
  139. package/src/input/index.ts +7 -0
  140. package/src/input/mention-enricher.test.ts +82 -0
  141. package/src/input/mention-enricher.ts +119 -0
  142. package/src/mcp/config-loader.test.ts +238 -0
  143. package/src/mcp/config-loader.ts +219 -0
  144. package/src/mcp/index.ts +26 -0
  145. package/src/mcp/manager.test.ts +106 -0
  146. package/src/mcp/manager.ts +262 -0
  147. package/src/mcp/types.ts +88 -0
  148. package/src/runtime/hook-file-hooks.test.ts +106 -0
  149. package/src/runtime/hook-file-hooks.ts +736 -0
  150. package/src/runtime/index.ts +27 -0
  151. package/src/runtime/rules.ts +34 -0
  152. package/src/runtime/runtime-builder.team-persistence.test.ts +203 -0
  153. package/src/runtime/runtime-builder.test.ts +215 -0
  154. package/src/runtime/runtime-builder.ts +515 -0
  155. package/src/runtime/runtime-parity.test.ts +132 -0
  156. package/src/runtime/sandbox/subprocess-sandbox.ts +207 -0
  157. package/src/runtime/session-runtime.ts +44 -0
  158. package/src/runtime/tool-approval.ts +104 -0
  159. package/src/runtime/workflows.test.ts +119 -0
  160. package/src/runtime/workflows.ts +54 -0
  161. package/src/server/index.ts +282 -0
  162. package/src/session/default-session-manager.e2e.test.ts +354 -0
  163. package/src/session/default-session-manager.test.ts +816 -0
  164. package/src/session/default-session-manager.ts +1286 -0
  165. package/src/session/index.ts +37 -0
  166. package/src/session/rpc-session-service.ts +189 -0
  167. package/src/session/runtime-oauth-token-manager.test.ts +137 -0
  168. package/src/session/runtime-oauth-token-manager.ts +265 -0
  169. package/src/session/session-artifacts.ts +106 -0
  170. package/src/session/session-graph.ts +90 -0
  171. package/src/session/session-host.ts +190 -0
  172. package/src/session/session-manager.ts +56 -0
  173. package/src/session/session-manifest.ts +29 -0
  174. package/src/session/session-service.team-persistence.test.ts +48 -0
  175. package/src/session/session-service.ts +610 -0
  176. package/src/session/sqlite-rpc-session-backend.ts +303 -0
  177. package/src/session/unified-session-persistence-service.ts +781 -0
  178. package/src/session/workspace-manager.ts +98 -0
  179. package/src/session/workspace-manifest.ts +100 -0
  180. package/src/storage/artifact-store.ts +1 -0
  181. package/src/storage/index.ts +11 -0
  182. package/src/storage/provider-settings-legacy-migration.test.ts +175 -0
  183. package/src/storage/provider-settings-legacy-migration.ts +637 -0
  184. package/src/storage/provider-settings-manager.test.ts +111 -0
  185. package/src/storage/provider-settings-manager.ts +129 -0
  186. package/src/storage/session-store.ts +1 -0
  187. package/src/storage/sqlite-session-store.ts +270 -0
  188. package/src/storage/sqlite-team-store.ts +443 -0
  189. package/src/storage/team-store.ts +5 -0
  190. package/src/team/index.ts +4 -0
  191. package/src/team/projections.ts +285 -0
  192. package/src/types/common.ts +14 -0
  193. package/src/types/config.ts +64 -0
  194. package/src/types/events.ts +46 -0
  195. package/src/types/index.ts +24 -0
  196. package/src/types/provider-settings.ts +43 -0
  197. package/src/types/sessions.ts +16 -0
  198. package/src/types/storage.ts +64 -0
  199. package/src/types/workspace.ts +7 -0
  200. package/src/types.ts +127 -0
@@ -0,0 +1,546 @@
1
+ import { nanoid } from "nanoid";
2
+ import { startLocalOAuthServer } from "./server.js";
3
+ import type {
4
+ OAuthCredentials,
5
+ OAuthLoginCallbacks,
6
+ OAuthProviderInterface,
7
+ OcaClientMetadata,
8
+ OcaMode,
9
+ OcaOAuthConfig,
10
+ OcaOAuthProviderOptions,
11
+ OcaTokenResolution,
12
+ } from "./types.js";
13
+ import {
14
+ decodeJwtPayload,
15
+ getProofKey,
16
+ isCredentialLikelyExpired,
17
+ normalizeBaseUrl,
18
+ resolveAuthorizationCodeInput,
19
+ } from "./utils.js";
20
+
21
+ export const DEFAULT_INTERNAL_IDCS_CLIENT_ID =
22
+ "a8331954c0cf48ba99b5dd223a14c6ea";
23
+ export const DEFAULT_INTERNAL_IDCS_URL =
24
+ "https://idcs-9dc693e80d9b469480d7afe00e743931.identity.oraclecloud.com";
25
+ export const DEFAULT_INTERNAL_IDCS_SCOPES = "openid offline_access";
26
+ export const DEFAULT_INTERNAL_OCA_BASE_URL =
27
+ "https://code-internal.aiservice.us-chicago-1.oci.oraclecloud.com/20250206/app/litellm";
28
+
29
+ export const DEFAULT_EXTERNAL_IDCS_CLIENT_ID =
30
+ "c1aba3deed5740659981a752714eba33";
31
+ export const DEFAULT_EXTERNAL_IDCS_URL =
32
+ "https://login-ext.identity.oraclecloud.com";
33
+ export const DEFAULT_EXTERNAL_IDCS_SCOPES = "openid offline_access";
34
+ export const DEFAULT_EXTERNAL_OCA_BASE_URL =
35
+ "https://code.aiservice.us-chicago-1.oci.oraclecloud.com/20250206/app/litellm";
36
+
37
+ export const OCI_HEADER_OPC_REQUEST_ID = "opc-request-id";
38
+
39
+ const DEFAULT_CALLBACK_PATH = "/auth/oca";
40
+ const DEFAULT_CALLBACK_PORTS = Array.from(
41
+ { length: 11 },
42
+ (_, index) => 48801 + index,
43
+ );
44
+ const DEFAULT_REFRESH_BUFFER_MS = 5 * 60 * 1000;
45
+ const DEFAULT_RETRYABLE_TOKEN_GRACE_MS = 30 * 1000;
46
+ const DEFAULT_HTTP_TIMEOUT_MS = 30 * 1000;
47
+ const PKCE_STATE_TTL_MS = 10 * 60 * 1000;
48
+
49
+ type OcaTokenResponse = {
50
+ access_token?: string;
51
+ refresh_token?: string;
52
+ expires_in?: number;
53
+ id_token?: string;
54
+ error?: string;
55
+ error_description?: string;
56
+ };
57
+
58
+ type OcaDiscoveryDocument = {
59
+ token_endpoint?: string;
60
+ };
61
+
62
+ type OcaAuthFlowState = {
63
+ verifier: string;
64
+ nonce: string;
65
+ mode: OcaMode;
66
+ redirectUri: string;
67
+ createdAt: number;
68
+ };
69
+
70
+ class OcaOAuthTokenError extends Error {
71
+ public readonly status?: number;
72
+ public readonly errorCode?: string;
73
+
74
+ constructor(message: string, opts?: { status?: number; errorCode?: string }) {
75
+ super(message);
76
+ this.name = "OcaOAuthTokenError";
77
+ this.status = opts?.status;
78
+ this.errorCode = opts?.errorCode;
79
+ }
80
+
81
+ public isLikelyInvalidGrant(): boolean {
82
+ if (
83
+ this.errorCode &&
84
+ /invalid_grant|invalid_token|unauthorized/i.test(this.errorCode)
85
+ ) {
86
+ return true;
87
+ }
88
+ return this.status === 400 || this.status === 401 || this.status === 403;
89
+ }
90
+ }
91
+
92
+ const OCA_CONFIG_DEFAULTS: OcaOAuthConfig = {
93
+ internal: {
94
+ clientId: DEFAULT_INTERNAL_IDCS_CLIENT_ID,
95
+ idcsUrl: DEFAULT_INTERNAL_IDCS_URL,
96
+ scopes: DEFAULT_INTERNAL_IDCS_SCOPES,
97
+ baseUrl: DEFAULT_INTERNAL_OCA_BASE_URL,
98
+ },
99
+ external: {
100
+ clientId: DEFAULT_EXTERNAL_IDCS_CLIENT_ID,
101
+ idcsUrl: DEFAULT_EXTERNAL_IDCS_URL,
102
+ scopes: DEFAULT_EXTERNAL_IDCS_SCOPES,
103
+ baseUrl: DEFAULT_EXTERNAL_OCA_BASE_URL,
104
+ },
105
+ };
106
+
107
+ const OCA_FLOW_STATE = new Map<string, OcaAuthFlowState>();
108
+ const DISCOVERY_CACHE = new Map<string, string>();
109
+
110
+ function resolveMode(mode?: OcaMode | (() => OcaMode)): OcaMode {
111
+ if (typeof mode === "function") {
112
+ return mode();
113
+ }
114
+ return mode ?? "internal";
115
+ }
116
+
117
+ function resolveConfig(config?: Partial<OcaOAuthConfig>): OcaOAuthConfig {
118
+ return {
119
+ internal: {
120
+ clientId:
121
+ config?.internal?.clientId ?? OCA_CONFIG_DEFAULTS.internal.clientId,
122
+ idcsUrl:
123
+ config?.internal?.idcsUrl ?? OCA_CONFIG_DEFAULTS.internal.idcsUrl,
124
+ scopes: config?.internal?.scopes ?? OCA_CONFIG_DEFAULTS.internal.scopes,
125
+ baseUrl:
126
+ config?.internal?.baseUrl ?? OCA_CONFIG_DEFAULTS.internal.baseUrl,
127
+ },
128
+ external: {
129
+ clientId:
130
+ config?.external?.clientId ?? OCA_CONFIG_DEFAULTS.external.clientId,
131
+ idcsUrl:
132
+ config?.external?.idcsUrl ?? OCA_CONFIG_DEFAULTS.external.idcsUrl,
133
+ scopes: config?.external?.scopes ?? OCA_CONFIG_DEFAULTS.external.scopes,
134
+ baseUrl:
135
+ config?.external?.baseUrl ?? OCA_CONFIG_DEFAULTS.external.baseUrl,
136
+ },
137
+ };
138
+ }
139
+
140
+ function cleanupFlowState(now = Date.now()): void {
141
+ const cutoff = now - PKCE_STATE_TTL_MS;
142
+ for (const [state, value] of OCA_FLOW_STATE.entries()) {
143
+ if (value.createdAt < cutoff) {
144
+ OCA_FLOW_STATE.delete(state);
145
+ }
146
+ }
147
+ }
148
+
149
+ function resolveExpiryEpochMs(
150
+ response: OcaTokenResponse,
151
+ accessToken?: string,
152
+ idToken?: string,
153
+ ): number {
154
+ if (typeof response.expires_in === "number" && response.expires_in > 0) {
155
+ return Date.now() + response.expires_in * 1000;
156
+ }
157
+ const accessPayload = decodeJwtPayload(accessToken);
158
+ const accessExp = accessPayload?.exp;
159
+ if (typeof accessExp === "number" && accessExp > 0) {
160
+ return accessExp * 1000;
161
+ }
162
+ const idPayload = decodeJwtPayload(idToken);
163
+ const idExp = idPayload?.exp;
164
+ if (typeof idExp === "number" && idExp > 0) {
165
+ return idExp * 1000;
166
+ }
167
+ return Date.now() + 60 * 60 * 1000;
168
+ }
169
+
170
+ function toOcaCredentials(
171
+ response: OcaTokenResponse,
172
+ mode: OcaMode,
173
+ fallback?: OAuthCredentials,
174
+ ): OAuthCredentials {
175
+ const accessToken = response.access_token;
176
+ if (!accessToken) {
177
+ throw new Error("Token response did not include an access token");
178
+ }
179
+ const refreshToken = response.refresh_token ?? fallback?.refresh;
180
+ if (!refreshToken) {
181
+ throw new Error("Token response did not include a refresh token");
182
+ }
183
+
184
+ const idPayload = decodeJwtPayload(response.id_token);
185
+ const accessPayload = decodeJwtPayload(accessToken);
186
+ const sub =
187
+ (idPayload?.sub as string | undefined) ??
188
+ (accessPayload?.sub as string | undefined);
189
+ const email =
190
+ (idPayload?.email as string | undefined) ??
191
+ (accessPayload?.email as string | undefined);
192
+
193
+ return {
194
+ access: accessToken,
195
+ refresh: refreshToken,
196
+ expires: resolveExpiryEpochMs(response, accessToken, response.id_token),
197
+ accountId: sub ?? fallback?.accountId,
198
+ email: email ?? fallback?.email,
199
+ metadata: {
200
+ ...(fallback?.metadata ?? {}),
201
+ provider: "oca",
202
+ mode,
203
+ subject: sub,
204
+ idToken: response.id_token,
205
+ },
206
+ };
207
+ }
208
+
209
+ async function discoverTokenEndpoint(
210
+ idcsUrl: string,
211
+ requestTimeoutMs: number,
212
+ ): Promise<string> {
213
+ const normalizedIdcsUrl = normalizeBaseUrl(idcsUrl);
214
+ const cached = DISCOVERY_CACHE.get(normalizedIdcsUrl);
215
+ if (cached) {
216
+ return cached;
217
+ }
218
+
219
+ const discoveryUrl = `${normalizedIdcsUrl}/.well-known/openid-configuration`;
220
+ const response = await fetch(discoveryUrl, {
221
+ method: "GET",
222
+ signal: AbortSignal.timeout(requestTimeoutMs),
223
+ });
224
+
225
+ if (!response.ok) {
226
+ const fallback = `${normalizedIdcsUrl}/oauth2/v1/token`;
227
+ DISCOVERY_CACHE.set(normalizedIdcsUrl, fallback);
228
+ return fallback;
229
+ }
230
+
231
+ const discovery = (await response.json()) as OcaDiscoveryDocument;
232
+ const endpoint =
233
+ discovery.token_endpoint || `${normalizedIdcsUrl}/oauth2/v1/token`;
234
+ DISCOVERY_CACHE.set(normalizedIdcsUrl, endpoint);
235
+ return endpoint;
236
+ }
237
+
238
+ function parseOAuthErrorPayload(payload: OcaTokenResponse): {
239
+ code?: string;
240
+ message?: string;
241
+ } {
242
+ return {
243
+ code: payload.error,
244
+ message: payload.error_description,
245
+ };
246
+ }
247
+
248
+ async function exchangeAuthorizationCode(input: {
249
+ code: string;
250
+ state: string;
251
+ mode: OcaMode;
252
+ config: OcaOAuthConfig;
253
+ requestTimeoutMs: number;
254
+ }): Promise<OAuthCredentials> {
255
+ const flowState = OCA_FLOW_STATE.get(input.state);
256
+ if (!flowState) {
257
+ throw new Error("No PKCE verifier found for this state");
258
+ }
259
+ OCA_FLOW_STATE.delete(input.state);
260
+
261
+ const envConfig =
262
+ input.mode === "external" ? input.config.external : input.config.internal;
263
+ const tokenEndpoint = await discoverTokenEndpoint(
264
+ envConfig.idcsUrl,
265
+ input.requestTimeoutMs,
266
+ );
267
+
268
+ const body = new URLSearchParams({
269
+ grant_type: "authorization_code",
270
+ code: input.code,
271
+ redirect_uri: flowState.redirectUri,
272
+ client_id: envConfig.clientId,
273
+ code_verifier: flowState.verifier,
274
+ });
275
+
276
+ const response = await fetch(tokenEndpoint, {
277
+ method: "POST",
278
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
279
+ body,
280
+ signal: AbortSignal.timeout(input.requestTimeoutMs),
281
+ });
282
+
283
+ const tokenPayload = (await response.json()) as OcaTokenResponse;
284
+ if (!response.ok) {
285
+ const details = parseOAuthErrorPayload(tokenPayload);
286
+ throw new OcaOAuthTokenError(
287
+ `Token exchange failed: ${response.status}${details.message ? ` - ${details.message}` : ""}`,
288
+ { status: response.status, errorCode: details.code },
289
+ );
290
+ }
291
+
292
+ const idPayload = decodeJwtPayload(tokenPayload.id_token);
293
+ if (!tokenPayload.id_token || !idPayload) {
294
+ throw new Error("No ID token received from OCA");
295
+ }
296
+ if (idPayload.nonce !== flowState.nonce) {
297
+ throw new Error("OIDC nonce verification failed");
298
+ }
299
+
300
+ return toOcaCredentials(tokenPayload, input.mode);
301
+ }
302
+
303
+ function buildAuthorizationUrl(input: {
304
+ callbackUrl: string;
305
+ mode: OcaMode;
306
+ state: string;
307
+ nonce: string;
308
+ challenge: string;
309
+ config: OcaOAuthConfig;
310
+ }): string {
311
+ const envConfig =
312
+ input.mode === "external" ? input.config.external : input.config.internal;
313
+ const authorizeUrl = new URL(
314
+ `${normalizeBaseUrl(envConfig.idcsUrl)}/oauth2/v1/authorize`,
315
+ );
316
+ authorizeUrl.searchParams.set("client_id", envConfig.clientId);
317
+ authorizeUrl.searchParams.set("response_type", "code");
318
+ authorizeUrl.searchParams.set("scope", envConfig.scopes);
319
+ authorizeUrl.searchParams.set("code_challenge", input.challenge);
320
+ authorizeUrl.searchParams.set("code_challenge_method", "S256");
321
+ authorizeUrl.searchParams.set("redirect_uri", input.callbackUrl);
322
+ authorizeUrl.searchParams.set("state", input.state);
323
+ authorizeUrl.searchParams.set("nonce", input.nonce);
324
+ return authorizeUrl.toString();
325
+ }
326
+
327
+ export async function loginOcaOAuth(
328
+ options: OcaOAuthProviderOptions & { callbacks: OAuthLoginCallbacks },
329
+ ): Promise<OAuthCredentials> {
330
+ const config = resolveConfig(options.config);
331
+ const mode = resolveMode(options.mode);
332
+ const callbackPorts = options.callbackPorts?.length
333
+ ? options.callbackPorts
334
+ : DEFAULT_CALLBACK_PORTS;
335
+ const callbackPath = options.callbackPath ?? DEFAULT_CALLBACK_PATH;
336
+ const requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_HTTP_TIMEOUT_MS;
337
+
338
+ const localServer = await startLocalOAuthServer({
339
+ ports: callbackPorts,
340
+ callbackPath,
341
+ });
342
+ const callbackUrl = localServer.callbackUrl;
343
+ if (!callbackUrl) {
344
+ throw new Error("Unable to bind local OAuth callback server");
345
+ }
346
+
347
+ const state = nanoid(16);
348
+ const nonce = nanoid(16);
349
+ const { verifier, challenge } = await getProofKey();
350
+ cleanupFlowState();
351
+ OCA_FLOW_STATE.set(state, {
352
+ verifier,
353
+ nonce,
354
+ mode,
355
+ redirectUri: callbackUrl,
356
+ createdAt: Date.now(),
357
+ });
358
+
359
+ const authUrl = buildAuthorizationUrl({
360
+ callbackUrl,
361
+ mode,
362
+ state,
363
+ nonce,
364
+ challenge,
365
+ config,
366
+ });
367
+ options.callbacks.onAuth({
368
+ url: authUrl,
369
+ instructions: "Continue the authentication process in your browser.",
370
+ });
371
+
372
+ try {
373
+ const authResult = await resolveAuthorizationCodeInput({
374
+ waitForCallback: localServer.waitForCallback,
375
+ cancelWait: localServer.cancelWait,
376
+ onManualCodeInput: options.callbacks.onManualCodeInput,
377
+ });
378
+ const code = authResult.code;
379
+ const returnedState = authResult.state;
380
+ if (authResult.error) {
381
+ throw new Error(`OAuth error: ${authResult.error}`);
382
+ }
383
+
384
+ if (!code) {
385
+ if (!options.callbacks.onManualCodeInput) {
386
+ throw new Error("Timed out waiting for OCA callback");
387
+ }
388
+ throw new Error("Missing authorization code");
389
+ }
390
+ if (!returnedState || returnedState !== state) {
391
+ throw new Error("State mismatch");
392
+ }
393
+
394
+ return await exchangeAuthorizationCode({
395
+ code,
396
+ state: returnedState,
397
+ mode,
398
+ config,
399
+ requestTimeoutMs,
400
+ });
401
+ } finally {
402
+ localServer.close();
403
+ }
404
+ }
405
+
406
+ export async function refreshOcaToken(
407
+ credentials: OAuthCredentials,
408
+ options: OcaOAuthProviderOptions = {},
409
+ ): Promise<OAuthCredentials> {
410
+ const config = resolveConfig(options.config);
411
+ const requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_HTTP_TIMEOUT_MS;
412
+ const metadataMode = credentials.metadata?.mode;
413
+ const mode: OcaMode =
414
+ metadataMode === "internal" || metadataMode === "external"
415
+ ? metadataMode
416
+ : resolveMode(options.mode);
417
+ const envConfig = mode === "external" ? config.external : config.internal;
418
+ const tokenEndpoint = await discoverTokenEndpoint(
419
+ envConfig.idcsUrl,
420
+ requestTimeoutMs,
421
+ );
422
+
423
+ const body = new URLSearchParams({
424
+ grant_type: "refresh_token",
425
+ refresh_token: credentials.refresh,
426
+ client_id: envConfig.clientId,
427
+ });
428
+
429
+ const response = await fetch(tokenEndpoint, {
430
+ method: "POST",
431
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
432
+ body,
433
+ signal: AbortSignal.timeout(requestTimeoutMs),
434
+ });
435
+
436
+ const tokenPayload = (await response.json()) as OcaTokenResponse;
437
+ if (!response.ok) {
438
+ const details = parseOAuthErrorPayload(tokenPayload);
439
+ throw new OcaOAuthTokenError(
440
+ `Token refresh failed: ${response.status}${details.message ? ` - ${details.message}` : ""}`,
441
+ { status: response.status, errorCode: details.code },
442
+ );
443
+ }
444
+
445
+ return toOcaCredentials(tokenPayload, mode, credentials);
446
+ }
447
+
448
+ export async function getValidOcaCredentials(
449
+ currentCredentials: OAuthCredentials | null,
450
+ options?: OcaTokenResolution,
451
+ providerOptions?: OcaOAuthProviderOptions,
452
+ ): Promise<OAuthCredentials | null> {
453
+ if (!currentCredentials) {
454
+ return null;
455
+ }
456
+
457
+ const refreshBufferMs =
458
+ options?.refreshBufferMs ??
459
+ providerOptions?.refreshBufferMs ??
460
+ DEFAULT_REFRESH_BUFFER_MS;
461
+ const retryableTokenGraceMs =
462
+ options?.retryableTokenGraceMs ??
463
+ providerOptions?.retryableTokenGraceMs ??
464
+ DEFAULT_RETRYABLE_TOKEN_GRACE_MS;
465
+ const forceRefresh = options?.forceRefresh === true;
466
+
467
+ if (
468
+ !forceRefresh &&
469
+ !isCredentialLikelyExpired(currentCredentials, refreshBufferMs)
470
+ ) {
471
+ return currentCredentials;
472
+ }
473
+
474
+ try {
475
+ return await refreshOcaToken(currentCredentials, providerOptions);
476
+ } catch (error) {
477
+ if (error instanceof OcaOAuthTokenError && error.isLikelyInvalidGrant()) {
478
+ return null;
479
+ }
480
+ if (currentCredentials.expires - Date.now() > retryableTokenGraceMs) {
481
+ return currentCredentials;
482
+ }
483
+ return null;
484
+ }
485
+ }
486
+
487
+ export function createOcaOAuthProvider(
488
+ options: OcaOAuthProviderOptions = {},
489
+ ): OAuthProviderInterface {
490
+ return {
491
+ id: "oca",
492
+ name: "Oracle Code Assist",
493
+ usesCallbackServer: true,
494
+ async login(callbacks) {
495
+ return loginOcaOAuth({ ...options, callbacks });
496
+ },
497
+ async refreshToken(credentials) {
498
+ return refreshOcaToken(credentials, options);
499
+ },
500
+ getApiKey(credentials) {
501
+ return credentials.access;
502
+ },
503
+ };
504
+ }
505
+
506
+ export async function generateOcaOpcRequestId(
507
+ taskId: string,
508
+ token: string,
509
+ ): Promise<string> {
510
+ const encoder = new TextEncoder();
511
+ const hash8 = async (value: string): Promise<string> => {
512
+ const digest = await crypto.subtle.digest("SHA-256", encoder.encode(value));
513
+ return Array.from(new Uint8Array(digest).slice(0, 4), (byte) =>
514
+ byte.toString(16).padStart(2, "0"),
515
+ ).join("");
516
+ };
517
+
518
+ const [tokenHex, taskHex] = await Promise.all([hash8(token), hash8(taskId)]);
519
+ const timestampHex = Math.floor(Date.now() / 1000)
520
+ .toString(16)
521
+ .padStart(8, "0");
522
+ const randomPart = new Uint32Array(1);
523
+ crypto.getRandomValues(randomPart);
524
+ const randomHex = (randomPart[0] ?? 0).toString(16).padStart(8, "0");
525
+ return tokenHex + taskHex + timestampHex + randomHex;
526
+ }
527
+
528
+ export async function createOcaRequestHeaders(input: {
529
+ accessToken: string;
530
+ taskId: string;
531
+ metadata?: OcaClientMetadata;
532
+ }): Promise<Record<string, string>> {
533
+ const opcRequestId = await generateOcaOpcRequestId(
534
+ input.taskId,
535
+ input.accessToken,
536
+ );
537
+ return {
538
+ Authorization: `Bearer ${input.accessToken}`,
539
+ "Content-Type": "application/json",
540
+ client: input.metadata?.client ?? "Cline",
541
+ "client-version": input.metadata?.clientVersion ?? "unknown",
542
+ "client-ide": input.metadata?.clientIde ?? "unknown",
543
+ "client-ide-version": input.metadata?.clientIdeVersion ?? "unknown",
544
+ [OCI_HEADER_OPC_REQUEST_ID]: opcRequestId,
545
+ };
546
+ }