@draht/ai 2026.10.20 → 2026.10.23

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 (269) hide show
  1. package/dist/api/anthropic-messages.d.ts.map +1 -1
  2. package/dist/api/anthropic-messages.js +155 -111
  3. package/dist/api/anthropic-messages.js.map +1 -1
  4. package/dist/api/azure-openai-config.d.ts +15 -0
  5. package/dist/api/azure-openai-config.d.ts.map +1 -0
  6. package/dist/api/azure-openai-config.js +76 -0
  7. package/dist/api/azure-openai-config.js.map +1 -0
  8. package/dist/api/azure-openai-responses.d.ts +3 -6
  9. package/dist/api/azure-openai-responses.d.ts.map +1 -1
  10. package/dist/api/azure-openai-responses.js +19 -83
  11. package/dist/api/azure-openai-responses.js.map +1 -1
  12. package/dist/api/bedrock-converse-stream.d.ts.map +1 -1
  13. package/dist/api/bedrock-converse-stream.js +86 -22
  14. package/dist/api/bedrock-converse-stream.js.map +1 -1
  15. package/dist/api/classifier-shared.d.ts +21 -0
  16. package/dist/api/classifier-shared.d.ts.map +1 -0
  17. package/dist/api/classifier-shared.js +99 -0
  18. package/dist/api/classifier-shared.js.map +1 -0
  19. package/dist/api/cloudflare-workers-ai-system-one.d.ts.map +1 -1
  20. package/dist/api/cloudflare-workers-ai-system-one.js +15 -9
  21. package/dist/api/cloudflare-workers-ai-system-one.js.map +1 -1
  22. package/dist/api/google-generative-ai.js +4 -4
  23. package/dist/api/google-generative-ai.js.map +1 -1
  24. package/dist/api/google-shared.d.ts.map +1 -1
  25. package/dist/api/google-shared.js +3 -1
  26. package/dist/api/google-shared.js.map +1 -1
  27. package/dist/api/google-vertex.d.ts.map +1 -1
  28. package/dist/api/google-vertex.js +9 -9
  29. package/dist/api/google-vertex.js.map +1 -1
  30. package/dist/api/lazy.d.ts +7 -2
  31. package/dist/api/lazy.d.ts.map +1 -1
  32. package/dist/api/lazy.js +28 -8
  33. package/dist/api/lazy.js.map +1 -1
  34. package/dist/api/llama-cpp-classify.d.ts.map +1 -1
  35. package/dist/api/llama-cpp-classify.js +4 -1
  36. package/dist/api/llama-cpp-classify.js.map +1 -1
  37. package/dist/api/mistral-conversations.d.ts.map +1 -1
  38. package/dist/api/mistral-conversations.js +38 -18
  39. package/dist/api/mistral-conversations.js.map +1 -1
  40. package/dist/api/openai-codex-responses.d.ts.map +1 -1
  41. package/dist/api/openai-codex-responses.js +21 -13
  42. package/dist/api/openai-codex-responses.js.map +1 -1
  43. package/dist/api/openai-completions.d.ts.map +1 -1
  44. package/dist/api/openai-completions.js +49 -38
  45. package/dist/api/openai-completions.js.map +1 -1
  46. package/dist/api/openai-decisions.d.ts +4 -0
  47. package/dist/api/openai-decisions.d.ts.map +1 -0
  48. package/dist/api/openai-decisions.js +162 -0
  49. package/dist/api/openai-decisions.js.map +1 -0
  50. package/dist/api/openai-decisions.lazy.d.ts +3 -0
  51. package/dist/api/openai-decisions.lazy.d.ts.map +1 -0
  52. package/dist/api/openai-decisions.lazy.js +4 -0
  53. package/dist/api/openai-decisions.lazy.js.map +1 -0
  54. package/dist/api/openai-responses-shared.d.ts +26 -0
  55. package/dist/api/openai-responses-shared.d.ts.map +1 -1
  56. package/dist/api/openai-responses-shared.js +82 -16
  57. package/dist/api/openai-responses-shared.js.map +1 -1
  58. package/dist/api/openai-responses.d.ts.map +1 -1
  59. package/dist/api/openai-responses.js +47 -18
  60. package/dist/api/openai-responses.js.map +1 -1
  61. package/dist/api/openrouter-images.d.ts.map +1 -1
  62. package/dist/api/openrouter-images.js +4 -3
  63. package/dist/api/openrouter-images.js.map +1 -1
  64. package/dist/api/pi-messages.d.ts.map +1 -1
  65. package/dist/api/pi-messages.js +3 -3
  66. package/dist/api/pi-messages.js.map +1 -1
  67. package/dist/api/simple-options.d.ts +2 -1
  68. package/dist/api/simple-options.d.ts.map +1 -1
  69. package/dist/api/simple-options.js +12 -2
  70. package/dist/api/simple-options.js.map +1 -1
  71. package/dist/api/system-one-shared.d.ts +0 -1
  72. package/dist/api/system-one-shared.d.ts.map +1 -1
  73. package/dist/api/system-one-shared.js +5 -89
  74. package/dist/api/system-one-shared.js.map +1 -1
  75. package/dist/api/transform-messages.d.ts +16 -1
  76. package/dist/api/transform-messages.d.ts.map +1 -1
  77. package/dist/api/transform-messages.js +28 -15
  78. package/dist/api/transform-messages.js.map +1 -1
  79. package/dist/api/typesafe-system-one.d.ts.map +1 -1
  80. package/dist/api/typesafe-system-one.js +2 -1
  81. package/dist/api/typesafe-system-one.js.map +1 -1
  82. package/dist/auth/oauth/anthropic.d.ts.map +1 -1
  83. package/dist/auth/oauth/anthropic.js +65 -7
  84. package/dist/auth/oauth/anthropic.js.map +1 -1
  85. package/dist/auth/oauth/load.d.ts +2 -0
  86. package/dist/auth/oauth/load.d.ts.map +1 -1
  87. package/dist/auth/oauth/load.js +5 -0
  88. package/dist/auth/oauth/load.js.map +1 -1
  89. package/dist/auth/oauth/openai-chatgpt.d.ts.map +1 -1
  90. package/dist/auth/oauth/openai-chatgpt.js +11 -14
  91. package/dist/auth/oauth/openai-chatgpt.js.map +1 -1
  92. package/dist/auth/oauth/openai-codex.d.ts.map +1 -1
  93. package/dist/auth/oauth/openai-codex.js +7 -5
  94. package/dist/auth/oauth/openai-codex.js.map +1 -1
  95. package/dist/auth/oauth/opencode-go.d.ts +3 -12
  96. package/dist/auth/oauth/opencode-go.d.ts.map +1 -1
  97. package/dist/auth/oauth/opencode-go.js +229 -34
  98. package/dist/auth/oauth/opencode-go.js.map +1 -1
  99. package/dist/auth/resolve.d.ts +16 -1
  100. package/dist/auth/resolve.d.ts.map +1 -1
  101. package/dist/auth/resolve.js +51 -27
  102. package/dist/auth/resolve.js.map +1 -1
  103. package/dist/auth/types.d.ts +5 -0
  104. package/dist/auth/types.d.ts.map +1 -1
  105. package/dist/auth/types.js.map +1 -1
  106. package/dist/bun-oauth.d.ts.map +1 -1
  107. package/dist/bun-oauth.js +2 -1
  108. package/dist/bun-oauth.js.map +1 -1
  109. package/dist/env-api-keys.js +1 -1
  110. package/dist/env-api-keys.js.map +1 -1
  111. package/dist/index.d.ts +2 -1
  112. package/dist/index.d.ts.map +1 -1
  113. package/dist/index.js +2 -1
  114. package/dist/index.js.map +1 -1
  115. package/dist/models.d.ts +9 -0
  116. package/dist/models.d.ts.map +1 -1
  117. package/dist/models.generated.d.ts +4 -4
  118. package/dist/models.generated.d.ts.map +1 -1
  119. package/dist/models.generated.js +4 -4
  120. package/dist/models.generated.js.map +1 -1
  121. package/dist/models.js +26 -14
  122. package/dist/models.js.map +1 -1
  123. package/dist/providers/all.js +2 -2
  124. package/dist/providers/all.js.map +1 -1
  125. package/dist/providers/azure.d.ts +3 -0
  126. package/dist/providers/azure.d.ts.map +1 -0
  127. package/dist/providers/azure.js +46 -0
  128. package/dist/providers/azure.js.map +1 -0
  129. package/dist/providers/azure.models.d.ts +6 -0
  130. package/dist/providers/azure.models.d.ts.map +1 -0
  131. package/dist/providers/azure.models.js +8 -0
  132. package/dist/providers/azure.models.js.map +1 -0
  133. package/dist/providers/compat-schema.d.ts +491 -0
  134. package/dist/providers/compat-schema.d.ts.map +1 -0
  135. package/dist/providers/compat-schema.js +386 -0
  136. package/dist/providers/compat-schema.js.map +1 -0
  137. package/dist/providers/data/.manifest.json +1 -1
  138. package/dist/providers/data/amazon-bedrock.json +1 -1
  139. package/dist/providers/data/anthropic.json +1 -1
  140. package/dist/providers/data/azure.json +1 -0
  141. package/dist/providers/data/baseten.json +1 -1
  142. package/dist/providers/data/cerebras.json +1 -1
  143. package/dist/providers/data/cloudflare-ai-gateway.json +1 -1
  144. package/dist/providers/data/cloudflare-workers-ai.json +1 -1
  145. package/dist/providers/data/fireworks.json +1 -1
  146. package/dist/providers/data/github-copilot.json +1 -1
  147. package/dist/providers/data/google-vertex.json +1 -1
  148. package/dist/providers/data/google.json +1 -1
  149. package/dist/providers/data/groq.json +1 -1
  150. package/dist/providers/data/huggingface.json +1 -1
  151. package/dist/providers/data/kimi-coding.json +1 -1
  152. package/dist/providers/data/meta.json +1 -1
  153. package/dist/providers/data/minimax-cn.json +1 -1
  154. package/dist/providers/data/minimax.json +1 -1
  155. package/dist/providers/data/mistral.json +1 -1
  156. package/dist/providers/data/moonshotai-cn.json +1 -1
  157. package/dist/providers/data/nvidia.json +1 -1
  158. package/dist/providers/data/openai-codex.json +1 -1
  159. package/dist/providers/data/openai.json +1 -1
  160. package/dist/providers/data/opencode-go.json +1 -1
  161. package/dist/providers/data/opencode.json +1 -1
  162. package/dist/providers/data/openrouter.json +1 -1
  163. package/dist/providers/data/qwen-token-plan-cn.json +1 -1
  164. package/dist/providers/data/qwen-token-plan.json +1 -1
  165. package/dist/providers/data/radius.json +1 -1
  166. package/dist/providers/data/together.json +1 -1
  167. package/dist/providers/data/vercel-ai-gateway.json +1 -1
  168. package/dist/providers/data/xai.json +1 -1
  169. package/dist/providers/data/xiaomi-token-plan-ams.json +1 -1
  170. package/dist/providers/data/xiaomi-token-plan-cn.json +1 -1
  171. package/dist/providers/data/xiaomi-token-plan-sgp.json +1 -1
  172. package/dist/providers/data/xiaomi.json +1 -1
  173. package/dist/providers/data/zai-coding-cn.json +1 -1
  174. package/dist/providers/faux.d.ts.map +1 -1
  175. package/dist/providers/faux.js +25 -8
  176. package/dist/providers/faux.js.map +1 -1
  177. package/dist/providers/kimi-coding.d.ts +1 -1
  178. package/dist/providers/kimi-coding.js +3 -3
  179. package/dist/providers/kimi-coding.js.map +1 -1
  180. package/dist/providers/model-schema.d.ts +91 -0
  181. package/dist/providers/model-schema.d.ts.map +1 -0
  182. package/dist/providers/model-schema.js +57 -0
  183. package/dist/providers/model-schema.js.map +1 -0
  184. package/dist/providers/openai.d.ts.map +1 -1
  185. package/dist/providers/openai.js +12 -3
  186. package/dist/providers/openai.js.map +1 -1
  187. package/dist/providers/opencode-go.d.ts +3 -1
  188. package/dist/providers/opencode-go.d.ts.map +1 -1
  189. package/dist/providers/opencode-go.js +4 -3
  190. package/dist/providers/opencode-go.js.map +1 -1
  191. package/dist/providers/opencode.d.ts.map +1 -1
  192. package/dist/providers/opencode.js +10 -2
  193. package/dist/providers/opencode.js.map +1 -1
  194. package/dist/providers/openrouter-api.d.ts +40 -0
  195. package/dist/providers/openrouter-api.d.ts.map +1 -0
  196. package/dist/providers/openrouter-api.js +41 -0
  197. package/dist/providers/openrouter-api.js.map +1 -0
  198. package/dist/providers/openrouter.d.ts +11 -1
  199. package/dist/providers/openrouter.d.ts.map +1 -1
  200. package/dist/providers/openrouter.js +52 -1
  201. package/dist/providers/openrouter.js.map +1 -1
  202. package/dist/providers/radius.d.ts.map +1 -1
  203. package/dist/providers/radius.js +4 -12
  204. package/dist/providers/radius.js.map +1 -1
  205. package/dist/telemetry.d.ts +288 -0
  206. package/dist/telemetry.d.ts.map +1 -0
  207. package/dist/telemetry.js +191 -0
  208. package/dist/telemetry.js.map +1 -0
  209. package/dist/types.d.ts +42 -310
  210. package/dist/types.d.ts.map +1 -1
  211. package/dist/types.js.map +1 -1
  212. package/dist/utils/estimate.js +1 -1
  213. package/dist/utils/estimate.js.map +1 -1
  214. package/dist/utils/event-stream.d.ts +10 -1
  215. package/dist/utils/event-stream.d.ts.map +1 -1
  216. package/dist/utils/event-stream.js +25 -0
  217. package/dist/utils/event-stream.js.map +1 -1
  218. package/dist/utils/identity.d.ts +65 -0
  219. package/dist/utils/identity.d.ts.map +1 -0
  220. package/dist/utils/identity.js +145 -0
  221. package/dist/utils/identity.js.map +1 -0
  222. package/dist/utils/json-parse.d.ts +10 -0
  223. package/dist/utils/json-parse.d.ts.map +1 -1
  224. package/dist/utils/json-parse.js +26 -1
  225. package/dist/utils/json-parse.js.map +1 -1
  226. package/dist/utils/model-operations.d.ts +3 -1
  227. package/dist/utils/model-operations.d.ts.map +1 -1
  228. package/dist/utils/model-operations.js +6 -0
  229. package/dist/utils/model-operations.js.map +1 -1
  230. package/dist/utils/overflow.d.ts +1 -0
  231. package/dist/utils/overflow.d.ts.map +1 -1
  232. package/dist/utils/overflow.js +6 -1
  233. package/dist/utils/overflow.js.map +1 -1
  234. package/dist/utils/provider-retry.d.ts +2 -0
  235. package/dist/utils/provider-retry.d.ts.map +1 -1
  236. package/dist/utils/provider-retry.js +2 -0
  237. package/dist/utils/provider-retry.js.map +1 -1
  238. package/dist/utils/retry.d.ts.map +1 -1
  239. package/dist/utils/retry.js +30 -10
  240. package/dist/utils/retry.js.map +1 -1
  241. package/dist/utils/transcript.d.ts +27 -10
  242. package/dist/utils/transcript.d.ts.map +1 -1
  243. package/dist/utils/transcript.js +34 -20
  244. package/dist/utils/transcript.js.map +1 -1
  245. package/dist/utils/validation.d.ts.map +1 -1
  246. package/dist/utils/validation.js +3 -0
  247. package/dist/utils/validation.js.map +1 -1
  248. package/dist/utils/version.d.ts +2 -0
  249. package/dist/utils/version.d.ts.map +1 -0
  250. package/dist/utils/version.js +5 -0
  251. package/dist/utils/version.js.map +1 -0
  252. package/package.json +5 -5
  253. package/dist/providers/azure-openai-responses.d.ts +0 -3
  254. package/dist/providers/azure-openai-responses.d.ts.map +0 -1
  255. package/dist/providers/azure-openai-responses.js +0 -14
  256. package/dist/providers/azure-openai-responses.js.map +0 -1
  257. package/dist/providers/azure-openai-responses.models.d.ts +0 -6
  258. package/dist/providers/azure-openai-responses.models.d.ts.map +0 -1
  259. package/dist/providers/azure-openai-responses.models.js +0 -8
  260. package/dist/providers/azure-openai-responses.models.js.map +0 -1
  261. package/dist/providers/data/azure-openai-responses.json +0 -1
  262. package/dist/utils/assistant-message-frame.d.ts +0 -77
  263. package/dist/utils/assistant-message-frame.d.ts.map +0 -1
  264. package/dist/utils/assistant-message-frame.js +0 -425
  265. package/dist/utils/assistant-message-frame.js.map +0 -1
  266. package/dist/utils/draht-user-agent.d.ts +0 -2
  267. package/dist/utils/draht-user-agent.d.ts.map +0 -1
  268. package/dist/utils/draht-user-agent.js +0 -12
  269. package/dist/utils/draht-user-agent.js.map +0 -1
@@ -1,41 +1,236 @@
1
- /**
2
- * OpenCode Go login flow.
3
- *
4
- * OpenCode Go (https://opencode.ai/zen/go) does not use OAuth — the user signs
5
- * in to the OpenCode Zen dashboard, copies an API key, and pastes it back.
6
- * We model it as an OAuth flow so it shows up in the same /login UI as the
7
- * real OAuth providers; credentials are stored with `access` = API key and a
8
- * far-future `expires` so the refresh path is never taken.
9
- */
10
- const DASHBOARD_URL = "https://opencode.ai/zen";
11
- const NEVER_EXPIRES = Number.MAX_SAFE_INTEGER;
12
- export async function loginOpenCodeGo(interaction) {
13
- interaction.notify({
14
- type: "auth_url",
15
- url: DASHBOARD_URL,
16
- instructions: "Sign in to OpenCode Zen, subscribe to Go, then copy your API key.",
1
+ /** OpenCode Console device authorization, shared by Zen and Go. */
2
+ import { pollOAuthDeviceCodeFlow } from "./device-code.js";
3
+ const CONSOLE_URL = "https://opencode.ai/console";
4
+ const CLIENT_ID = "opencode-cli";
5
+ const REQUEST_TIMEOUT_MS = 30_000;
6
+ const SIGN_IN_MESSAGE = "Sign in with OpenCode Console again to continue.";
7
+ function record(value) {
8
+ return typeof value === "object" && value !== null && !Array.isArray(value)
9
+ ? value
10
+ : undefined;
11
+ }
12
+ function nonempty(value) {
13
+ return typeof value === "string" && value.trim().length > 0;
14
+ }
15
+ function accessToken(value) {
16
+ return typeof value === "string" && /^[\x21-\x7e]+$/.test(value);
17
+ }
18
+ function timerSeconds(value) {
19
+ return typeof value === "number" && Number.isFinite(value) && value > 0 && value * 1000 <= 2_147_483_647;
20
+ }
21
+ function organizationId(value) {
22
+ return nonempty(value) && !/[\r\n]/.test(value);
23
+ }
24
+ async function request(path, init, signal) {
25
+ signal.throwIfAborted();
26
+ return fetch(`${CONSOLE_URL}${path}`, {
27
+ ...init,
28
+ headers: { Accept: "application/json", ...init.headers },
29
+ redirect: "error",
30
+ signal: AbortSignal.any([signal, AbortSignal.timeout(REQUEST_TIMEOUT_MS)]),
17
31
  });
18
- const key = (await interaction.prompt({ type: "secret", message: "Paste your OpenCode API key:" })).trim();
19
- if (!key) {
20
- throw new Error("Missing OpenCode API key");
32
+ }
33
+ function post(path, body, signal) {
34
+ return request(path, {
35
+ method: "POST",
36
+ headers: { "Content-Type": "application/json" },
37
+ body: JSON.stringify(body),
38
+ }, signal);
39
+ }
40
+ async function responseJson(response, operation) {
41
+ try {
42
+ return await response.json();
43
+ }
44
+ catch {
45
+ throw new Error(`Invalid OpenCode ${operation} response (HTTP ${response.status})`);
46
+ }
47
+ }
48
+ async function successfulJson(response, operation) {
49
+ if (!response.ok) {
50
+ await response.body?.cancel();
51
+ throw new Error(`OpenCode ${operation} failed (HTTP ${response.status})`);
52
+ }
53
+ return responseJson(response, operation);
54
+ }
55
+ function parseDevice(raw) {
56
+ const data = record(raw);
57
+ if (!data ||
58
+ !nonempty(data.device_code) ||
59
+ !nonempty(data.user_code) ||
60
+ !nonempty(data.verification_uri_complete) ||
61
+ !timerSeconds(data.expires_in) ||
62
+ !timerSeconds(data.interval)) {
63
+ throw new Error("Invalid OpenCode device authorization response");
64
+ }
65
+ let url;
66
+ try {
67
+ url = new URL(data.verification_uri_complete, `${CONSOLE_URL}/`);
68
+ if (url.protocol !== "https:" || url.username || url.password)
69
+ throw new Error("Invalid URL");
70
+ }
71
+ catch {
72
+ throw new Error("Invalid OpenCode device verification URL");
73
+ }
74
+ return {
75
+ deviceCode: data.device_code,
76
+ userCode: data.user_code,
77
+ verificationUri: url.href,
78
+ expiresIn: data.expires_in,
79
+ interval: data.interval,
80
+ };
81
+ }
82
+ function parseToken(raw) {
83
+ const data = record(raw);
84
+ if (!data ||
85
+ !accessToken(data.access_token) ||
86
+ !nonempty(data.refresh_token) ||
87
+ typeof data.expires_in !== "number" ||
88
+ !Number.isFinite(data.expires_in) ||
89
+ data.expires_in <= 0 ||
90
+ (data.org_id !== undefined && data.org_id !== null && !organizationId(data.org_id))) {
91
+ throw new Error("Invalid OpenCode token response");
92
+ }
93
+ const expires = Date.now() + data.expires_in * 1000;
94
+ // Token validity is a stored timestamp, not a JavaScript timer duration.
95
+ if (!Number.isFinite(expires) || expires > Number.MAX_SAFE_INTEGER) {
96
+ throw new Error("Invalid OpenCode token response");
21
97
  }
98
+ return {
99
+ access: data.access_token,
100
+ refresh: data.refresh_token,
101
+ expires,
102
+ orgId: typeof data.org_id === "string" ? data.org_id : undefined,
103
+ };
104
+ }
105
+ async function accountCredential(token, signal) {
106
+ const init = { headers: { Authorization: `Bearer ${token.access}` } };
107
+ const [rawUser, rawOrgs] = await Promise.all([
108
+ request("/api/user", init, signal).then((response) => successfulJson(response, "account lookup")),
109
+ request("/api/orgs", init, signal).then((response) => successfulJson(response, "organization lookup")),
110
+ ]);
111
+ signal.throwIfAborted();
112
+ const user = record(rawUser);
113
+ if (!user || !nonempty(user.id) || !nonempty(user.email))
114
+ throw new Error("Invalid OpenCode account response");
115
+ if (!Array.isArray(rawOrgs))
116
+ throw new Error("Invalid OpenCode organization response");
117
+ const orgs = rawOrgs.map((raw) => {
118
+ const org = record(raw);
119
+ if (!org || !organizationId(org.id) || !nonempty(org.name))
120
+ throw new Error("Invalid OpenCode organization response");
121
+ return { id: org.id, name: org.name };
122
+ });
123
+ const org = token.orgId === undefined
124
+ ? orgs.sort((a, b) => a.name.localeCompare(b.name) || a.id.localeCompare(b.id))[0]
125
+ : orgs.find((item) => item.id === token.orgId);
126
+ if (token.orgId !== undefined && !org)
127
+ throw new Error("OpenCode organization not found for the authorized account");
22
128
  return {
23
129
  type: "oauth",
24
- access: key,
25
- refresh: key,
26
- expires: NEVER_EXPIRES,
130
+ access: token.access,
131
+ refresh: token.refresh,
132
+ expires: token.expires,
133
+ opencodeConsole: true,
134
+ accountId: user.id,
135
+ email: user.email,
136
+ orgId: org?.id,
137
+ orgName: org?.name,
138
+ };
139
+ }
140
+ async function loginOpenCode(interaction) {
141
+ const { signal } = interaction;
142
+ const response = await post("/auth/device/code", { client_id: CLIENT_ID, supports_org_scope: true }, signal);
143
+ const device = parseDevice(await successfulJson(response, "device authorization"));
144
+ signal.throwIfAborted();
145
+ interaction.notify({
146
+ type: "auth_url",
147
+ url: device.verificationUri,
148
+ instructions: `Enter code: ${device.userCode}`,
149
+ });
150
+ const deadline = Date.now() + device.expiresIn * 1000;
151
+ const token = await pollOAuthDeviceCodeFlow({
152
+ intervalSeconds: device.interval,
153
+ expiresInSeconds: device.expiresIn,
154
+ waitBeforeFirstPoll: true,
155
+ signal,
156
+ poll: async () => {
157
+ const remaining = deadline - Date.now();
158
+ if (remaining <= 0)
159
+ return { status: "failed", message: "OpenCode device authorization expired" };
160
+ const pollSignal = AbortSignal.any([signal, AbortSignal.timeout(Math.ceil(remaining))]);
161
+ const result = await post("/auth/device/token", {
162
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code",
163
+ device_code: device.deviceCode,
164
+ client_id: CLIENT_ID,
165
+ }, pollSignal);
166
+ const raw = await responseJson(result, "device token");
167
+ const error = record(raw)?.error;
168
+ if (result.status === 400 || result.ok) {
169
+ if (error === "authorization_pending")
170
+ return { status: "pending" };
171
+ if (error === "slow_down")
172
+ return { status: "slow_down" };
173
+ if (error === "access_denied")
174
+ return { status: "failed", message: "OpenCode authorization denied" };
175
+ if (error === "expired_token")
176
+ return { status: "failed", message: "OpenCode device authorization expired" };
177
+ }
178
+ if (!result.ok || error !== undefined)
179
+ return { status: "failed", message: `OpenCode device authorization failed (HTTP ${result.status})` };
180
+ return { status: "complete", value: parseToken(raw) };
181
+ },
182
+ });
183
+ return accountCredential(token, signal);
184
+ }
185
+ async function refreshOpenCode(credential, signal) {
186
+ signal.throwIfAborted();
187
+ if (credential.opencodeConsole !== true || !nonempty(credential.refresh))
188
+ throw new Error(SIGN_IN_MESSAGE);
189
+ const response = await post("/auth/device/token", {
190
+ grant_type: "refresh_token",
191
+ refresh_token: credential.refresh,
192
+ client_id: CLIENT_ID,
193
+ }, signal);
194
+ const raw = await responseJson(response, "refresh token");
195
+ if (response.status === 400 && record(raw)?.error === "invalid_grant")
196
+ throw new Error(SIGN_IN_MESSAGE);
197
+ if (!response.ok)
198
+ throw new Error(`OpenCode token refresh failed (HTTP ${response.status})`);
199
+ const token = parseToken(raw);
200
+ signal.throwIfAborted();
201
+ return {
202
+ ...credential,
203
+ access: token.access,
204
+ refresh: token.refresh,
205
+ expires: token.expires,
206
+ ...(token.orgId === undefined
207
+ ? {}
208
+ : {
209
+ orgId: token.orgId,
210
+ orgName: token.orgId === credential.orgId ? credential.orgName : token.orgId,
211
+ }),
212
+ };
213
+ }
214
+ function consoleOAuth(name, isSubscription) {
215
+ return {
216
+ name,
217
+ isSubscription,
218
+ loginLabel: "Sign in with OpenCode Console",
219
+ login: loginOpenCode,
220
+ refresh: refreshOpenCode,
221
+ async toAuth(credential) {
222
+ if (!accessToken(credential.access))
223
+ throw new Error("Invalid stored OpenCode access token");
224
+ const orgId = credential.orgId;
225
+ if (orgId !== undefined && !organizationId(orgId))
226
+ throw new Error("Invalid stored OpenCode organization");
227
+ return {
228
+ apiKey: credential.access,
229
+ ...(orgId === undefined ? {} : { headers: { "x-opencode-org-id": orgId } }),
230
+ };
231
+ },
27
232
  };
28
233
  }
29
- export async function refreshOpenCodeGoToken(credential) {
30
- return { ...credential, expires: NEVER_EXPIRES };
31
- }
32
- export const opencodeGoOAuth = {
33
- name: "OpenCode Go",
34
- loginLabel: "Paste an OpenCode Zen API key",
35
- login: loginOpenCodeGo,
36
- refresh: (credential) => refreshOpenCodeGoToken(credential),
37
- async toAuth(credential) {
38
- return { apiKey: credential.access };
39
- },
40
- };
234
+ export const opencodeGoOAuth = consoleOAuth("OpenCode Go", true);
235
+ export const opencodeOAuth = consoleOAuth("OpenCode Zen");
41
236
  //# sourceMappingURL=opencode-go.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"opencode-go.js","sourceRoot":"","sources":["../../../src/auth/oauth/opencode-go.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,MAAM,aAAa,GAAG,yBAAyB,CAAC;AAChD,MAAM,aAAa,GAAG,MAAM,CAAC,gBAAgB,CAAC;AAE9C,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,WAA4B;IACjE,WAAW,CAAC,MAAM,CAAC;QAClB,IAAI,EAAE,UAAU;QAChB,GAAG,EAAE,aAAa;QAClB,YAAY,EAAE,mEAAmE;KACjF,CAAC,CAAC;IAEH,MAAM,GAAG,GAAG,CAAC,MAAM,WAAW,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,8BAA8B,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAC3G,IAAI,CAAC,GAAG,EAAE,CAAC;QACV,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC;IAC7C,CAAC;IAED,OAAO;QACN,IAAI,EAAE,OAAO;QACb,MAAM,EAAE,GAAG;QACX,OAAO,EAAE,GAAG;QACZ,OAAO,EAAE,aAAa;KACtB,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAAC,UAA2B;IACvE,OAAO,EAAE,GAAG,UAAU,EAAE,OAAO,EAAE,aAAa,EAAE,CAAC;AAClD,CAAC;AAED,MAAM,CAAC,MAAM,eAAe,GAAc;IACzC,IAAI,EAAE,aAAa;IACnB,UAAU,EAAE,+BAA+B;IAC3C,KAAK,EAAE,eAAe;IACtB,OAAO,EAAE,CAAC,UAAU,EAAE,EAAE,CAAC,sBAAsB,CAAC,UAAU,CAAC;IAE3D,KAAK,CAAC,MAAM,CAAC,UAAU;QACtB,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,CAAC;IACtC,CAAC;CACD,CAAC","sourcesContent":["/**\n * OpenCode Go login flow.\n *\n * OpenCode Go (https://opencode.ai/zen/go) does not use OAuth — the user signs\n * in to the OpenCode Zen dashboard, copies an API key, and pastes it back.\n * We model it as an OAuth flow so it shows up in the same /login UI as the\n * real OAuth providers; credentials are stored with `access` = API key and a\n * far-future `expires` so the refresh path is never taken.\n */\n\nimport type { AuthInteraction, OAuthAuth, OAuthCredential } from \"../types.ts\";\n\nconst DASHBOARD_URL = \"https://opencode.ai/zen\";\nconst NEVER_EXPIRES = Number.MAX_SAFE_INTEGER;\n\nexport async function loginOpenCodeGo(interaction: AuthInteraction): Promise<OAuthCredential> {\n\tinteraction.notify({\n\t\ttype: \"auth_url\",\n\t\turl: DASHBOARD_URL,\n\t\tinstructions: \"Sign in to OpenCode Zen, subscribe to Go, then copy your API key.\",\n\t});\n\n\tconst key = (await interaction.prompt({ type: \"secret\", message: \"Paste your OpenCode API key:\" })).trim();\n\tif (!key) {\n\t\tthrow new Error(\"Missing OpenCode API key\");\n\t}\n\n\treturn {\n\t\ttype: \"oauth\",\n\t\taccess: key,\n\t\trefresh: key,\n\t\texpires: NEVER_EXPIRES,\n\t};\n}\n\nexport async function refreshOpenCodeGoToken(credential: OAuthCredential): Promise<OAuthCredential> {\n\treturn { ...credential, expires: NEVER_EXPIRES };\n}\n\nexport const opencodeGoOAuth: OAuthAuth = {\n\tname: \"OpenCode Go\",\n\tloginLabel: \"Paste an OpenCode Zen API key\",\n\tlogin: loginOpenCodeGo,\n\trefresh: (credential) => refreshOpenCodeGoToken(credential),\n\n\tasync toAuth(credential) {\n\t\treturn { apiKey: credential.access };\n\t},\n};\n"]}
1
+ {"version":3,"file":"opencode-go.js","sourceRoot":"","sources":["../../../src/auth/oauth/opencode-go.ts"],"names":[],"mappings":"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","sourcesContent":["/** OpenCode Console device authorization, shared by Zen and Go. */\n\nimport type { OAuthAuth, OAuthCredential, ProviderAuthInteraction } from \"../types.ts\";\nimport { pollOAuthDeviceCodeFlow } from \"./device-code.ts\";\n\nconst CONSOLE_URL = \"https://opencode.ai/console\";\nconst CLIENT_ID = \"opencode-cli\";\nconst REQUEST_TIMEOUT_MS = 30_000;\nconst SIGN_IN_MESSAGE = \"Sign in with OpenCode Console again to continue.\";\n\ntype Device = {\n\tdeviceCode: string;\n\tuserCode: string;\n\tverificationUri: string;\n\texpiresIn: number;\n\tinterval: number;\n};\ntype Token = { access: string; refresh: string; expires: number; orgId?: string };\ntype Organization = { id: string; name: string };\n\nfunction record(value: unknown): Record<string, unknown> | undefined {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value)\n\t\t? (value as Record<string, unknown>)\n\t\t: undefined;\n}\n\nfunction nonempty(value: unknown): value is string {\n\treturn typeof value === \"string\" && value.trim().length > 0;\n}\n\nfunction accessToken(value: unknown): value is string {\n\treturn typeof value === \"string\" && /^[\\x21-\\x7e]+$/.test(value);\n}\n\nfunction timerSeconds(value: unknown): value is number {\n\treturn typeof value === \"number\" && Number.isFinite(value) && value > 0 && value * 1000 <= 2_147_483_647;\n}\n\nfunction organizationId(value: unknown): value is string {\n\treturn nonempty(value) && !/[\\r\\n]/.test(value);\n}\n\nasync function request(path: string, init: RequestInit, signal: AbortSignal): Promise<Response> {\n\tsignal.throwIfAborted();\n\treturn fetch(`${CONSOLE_URL}${path}`, {\n\t\t...init,\n\t\theaders: { Accept: \"application/json\", ...init.headers },\n\t\tredirect: \"error\",\n\t\tsignal: AbortSignal.any([signal, AbortSignal.timeout(REQUEST_TIMEOUT_MS)]),\n\t});\n}\n\nfunction post(path: string, body: Record<string, string | boolean>, signal: AbortSignal): Promise<Response> {\n\treturn request(\n\t\tpath,\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\t\tbody: JSON.stringify(body),\n\t\t},\n\t\tsignal,\n\t);\n}\n\nasync function responseJson(response: Response, operation: string): Promise<unknown> {\n\ttry {\n\t\treturn await response.json();\n\t} catch {\n\t\tthrow new Error(`Invalid OpenCode ${operation} response (HTTP ${response.status})`);\n\t}\n}\n\nasync function successfulJson(response: Response, operation: string): Promise<unknown> {\n\tif (!response.ok) {\n\t\tawait response.body?.cancel();\n\t\tthrow new Error(`OpenCode ${operation} failed (HTTP ${response.status})`);\n\t}\n\treturn responseJson(response, operation);\n}\n\nfunction parseDevice(raw: unknown): Device {\n\tconst data = record(raw);\n\tif (\n\t\t!data ||\n\t\t!nonempty(data.device_code) ||\n\t\t!nonempty(data.user_code) ||\n\t\t!nonempty(data.verification_uri_complete) ||\n\t\t!timerSeconds(data.expires_in) ||\n\t\t!timerSeconds(data.interval)\n\t) {\n\t\tthrow new Error(\"Invalid OpenCode device authorization response\");\n\t}\n\tlet url: URL;\n\ttry {\n\t\turl = new URL(data.verification_uri_complete, `${CONSOLE_URL}/`);\n\t\tif (url.protocol !== \"https:\" || url.username || url.password) throw new Error(\"Invalid URL\");\n\t} catch {\n\t\tthrow new Error(\"Invalid OpenCode device verification URL\");\n\t}\n\treturn {\n\t\tdeviceCode: data.device_code,\n\t\tuserCode: data.user_code,\n\t\tverificationUri: url.href,\n\t\texpiresIn: data.expires_in,\n\t\tinterval: data.interval,\n\t};\n}\n\nfunction parseToken(raw: unknown): Token {\n\tconst data = record(raw);\n\tif (\n\t\t!data ||\n\t\t!accessToken(data.access_token) ||\n\t\t!nonempty(data.refresh_token) ||\n\t\ttypeof data.expires_in !== \"number\" ||\n\t\t!Number.isFinite(data.expires_in) ||\n\t\tdata.expires_in <= 0 ||\n\t\t(data.org_id !== undefined && data.org_id !== null && !organizationId(data.org_id))\n\t) {\n\t\tthrow new Error(\"Invalid OpenCode token response\");\n\t}\n\tconst expires = Date.now() + data.expires_in * 1000;\n\t// Token validity is a stored timestamp, not a JavaScript timer duration.\n\tif (!Number.isFinite(expires) || expires > Number.MAX_SAFE_INTEGER) {\n\t\tthrow new Error(\"Invalid OpenCode token response\");\n\t}\n\treturn {\n\t\taccess: data.access_token,\n\t\trefresh: data.refresh_token,\n\t\texpires,\n\t\torgId: typeof data.org_id === \"string\" ? data.org_id : undefined,\n\t};\n}\n\nasync function accountCredential(token: Token, signal: AbortSignal): Promise<OAuthCredential> {\n\tconst init = { headers: { Authorization: `Bearer ${token.access}` } };\n\tconst [rawUser, rawOrgs] = await Promise.all([\n\t\trequest(\"/api/user\", init, signal).then((response) => successfulJson(response, \"account lookup\")),\n\t\trequest(\"/api/orgs\", init, signal).then((response) => successfulJson(response, \"organization lookup\")),\n\t]);\n\tsignal.throwIfAborted();\n\tconst user = record(rawUser);\n\tif (!user || !nonempty(user.id) || !nonempty(user.email)) throw new Error(\"Invalid OpenCode account response\");\n\tif (!Array.isArray(rawOrgs)) throw new Error(\"Invalid OpenCode organization response\");\n\tconst orgs: Organization[] = rawOrgs.map((raw: unknown) => {\n\t\tconst org = record(raw);\n\t\tif (!org || !organizationId(org.id) || !nonempty(org.name))\n\t\t\tthrow new Error(\"Invalid OpenCode organization response\");\n\t\treturn { id: org.id, name: org.name };\n\t});\n\tconst org =\n\t\ttoken.orgId === undefined\n\t\t\t? orgs.sort((a, b) => a.name.localeCompare(b.name) || a.id.localeCompare(b.id))[0]\n\t\t\t: orgs.find((item) => item.id === token.orgId);\n\tif (token.orgId !== undefined && !org) throw new Error(\"OpenCode organization not found for the authorized account\");\n\treturn {\n\t\ttype: \"oauth\",\n\t\taccess: token.access,\n\t\trefresh: token.refresh,\n\t\texpires: token.expires,\n\t\topencodeConsole: true,\n\t\taccountId: user.id,\n\t\temail: user.email,\n\t\torgId: org?.id,\n\t\torgName: org?.name,\n\t};\n}\n\nasync function loginOpenCode(interaction: ProviderAuthInteraction): Promise<OAuthCredential> {\n\tconst { signal } = interaction;\n\tconst response = await post(\"/auth/device/code\", { client_id: CLIENT_ID, supports_org_scope: true }, signal);\n\tconst device = parseDevice(await successfulJson(response, \"device authorization\"));\n\tsignal.throwIfAborted();\n\tinteraction.notify({\n\t\ttype: \"auth_url\",\n\t\turl: device.verificationUri,\n\t\tinstructions: `Enter code: ${device.userCode}`,\n\t});\n\tconst deadline = Date.now() + device.expiresIn * 1000;\n\tconst token = await pollOAuthDeviceCodeFlow<Token>({\n\t\tintervalSeconds: device.interval,\n\t\texpiresInSeconds: device.expiresIn,\n\t\twaitBeforeFirstPoll: true,\n\t\tsignal,\n\t\tpoll: async () => {\n\t\t\tconst remaining = deadline - Date.now();\n\t\t\tif (remaining <= 0) return { status: \"failed\", message: \"OpenCode device authorization expired\" };\n\t\t\tconst pollSignal = AbortSignal.any([signal, AbortSignal.timeout(Math.ceil(remaining))]);\n\t\t\tconst result = await post(\n\t\t\t\t\"/auth/device/token\",\n\t\t\t\t{\n\t\t\t\t\tgrant_type: \"urn:ietf:params:oauth:grant-type:device_code\",\n\t\t\t\t\tdevice_code: device.deviceCode,\n\t\t\t\t\tclient_id: CLIENT_ID,\n\t\t\t\t},\n\t\t\t\tpollSignal,\n\t\t\t);\n\t\t\tconst raw = await responseJson(result, \"device token\");\n\t\t\tconst error = record(raw)?.error;\n\t\t\tif (result.status === 400 || result.ok) {\n\t\t\t\tif (error === \"authorization_pending\") return { status: \"pending\" };\n\t\t\t\tif (error === \"slow_down\") return { status: \"slow_down\" };\n\t\t\t\tif (error === \"access_denied\") return { status: \"failed\", message: \"OpenCode authorization denied\" };\n\t\t\t\tif (error === \"expired_token\")\n\t\t\t\t\treturn { status: \"failed\", message: \"OpenCode device authorization expired\" };\n\t\t\t}\n\t\t\tif (!result.ok || error !== undefined)\n\t\t\t\treturn { status: \"failed\", message: `OpenCode device authorization failed (HTTP ${result.status})` };\n\t\t\treturn { status: \"complete\", value: parseToken(raw) };\n\t\t},\n\t});\n\treturn accountCredential(token, signal);\n}\n\nasync function refreshOpenCode(credential: OAuthCredential, signal: AbortSignal): Promise<OAuthCredential> {\n\tsignal.throwIfAborted();\n\tif (credential.opencodeConsole !== true || !nonempty(credential.refresh)) throw new Error(SIGN_IN_MESSAGE);\n\tconst response = await post(\n\t\t\"/auth/device/token\",\n\t\t{\n\t\t\tgrant_type: \"refresh_token\",\n\t\t\trefresh_token: credential.refresh,\n\t\t\tclient_id: CLIENT_ID,\n\t\t},\n\t\tsignal,\n\t);\n\tconst raw = await responseJson(response, \"refresh token\");\n\tif (response.status === 400 && record(raw)?.error === \"invalid_grant\") throw new Error(SIGN_IN_MESSAGE);\n\tif (!response.ok) throw new Error(`OpenCode token refresh failed (HTTP ${response.status})`);\n\tconst token = parseToken(raw);\n\tsignal.throwIfAborted();\n\treturn {\n\t\t...credential,\n\t\taccess: token.access,\n\t\trefresh: token.refresh,\n\t\texpires: token.expires,\n\t\t...(token.orgId === undefined\n\t\t\t? {}\n\t\t\t: {\n\t\t\t\t\torgId: token.orgId,\n\t\t\t\t\torgName: token.orgId === credential.orgId ? credential.orgName : token.orgId,\n\t\t\t\t}),\n\t};\n}\n\nfunction consoleOAuth(name: string, isSubscription?: boolean): OAuthAuth {\n\treturn {\n\t\tname,\n\t\tisSubscription,\n\t\tloginLabel: \"Sign in with OpenCode Console\",\n\t\tlogin: loginOpenCode,\n\t\trefresh: refreshOpenCode,\n\t\tasync toAuth(credential) {\n\t\t\tif (!accessToken(credential.access)) throw new Error(\"Invalid stored OpenCode access token\");\n\t\t\tconst orgId = credential.orgId;\n\t\t\tif (orgId !== undefined && !organizationId(orgId)) throw new Error(\"Invalid stored OpenCode organization\");\n\t\t\treturn {\n\t\t\t\tapiKey: credential.access,\n\t\t\t\t...(orgId === undefined ? {} : { headers: { \"x-opencode-org-id\": orgId } }),\n\t\t\t};\n\t\t},\n\t};\n}\n\nexport const opencodeGoOAuth = consoleOAuth(\"OpenCode Go\", true);\nexport const opencodeOAuth = consoleOAuth(\"OpenCode Zen\");\n"]}
@@ -1,6 +1,6 @@
1
1
  import type { ProviderEnv } from "../types.ts";
2
2
  export { ModelsError, type ModelsErrorCode } from "../utils/models-error.ts";
3
- import type { AuthContext, AuthResult, CredentialStore, ProviderAuth } from "./types.ts";
3
+ import type { AuthContext, AuthResult, CredentialStore, OAuthAuth, OAuthCredential, ProviderAuth } from "./types.ts";
4
4
  export interface AuthResolutionOverrides {
5
5
  apiKey?: string;
6
6
  env?: ProviderEnv;
@@ -18,4 +18,19 @@ export declare function resolveProviderAuth(provider: {
18
18
  id: string;
19
19
  auth: ProviderAuth;
20
20
  }, credentials: CredentialStore, authContext: AuthContext, overrides?: AuthResolutionOverrides): Promise<AuthResult | undefined>;
21
+ /**
22
+ * Refresh a stored OAuth credential under the credential-store lock and persist
23
+ * the result before the lock is released. `needsRefresh` is re-checked under
24
+ * the lock, so concurrent callers and processes refresh only once.
25
+ *
26
+ * `signal` cancels only the wait for the lock. Once a refresh starts, the
27
+ * provider may already have rotated the refresh token, so the refresh and its
28
+ * persistence ignore `signal` and are bounded only by a timeout. Otherwise a
29
+ * cancelled caller could discard the only valid refresh token. Callers that
30
+ * must return promptly on cancellation race this promise with their signal.
31
+ *
32
+ * Resolves with the stored OAuth credential after the operation, or undefined
33
+ * when the provider no longer has an OAuth credential.
34
+ */
35
+ export declare function refreshStoredOAuthCredential(credentials: CredentialStore, providerId: string, oauth: OAuthAuth, needsRefresh: (credential: OAuthCredential) => boolean, signal: AbortSignal): Promise<OAuthCredential | undefined>;
21
36
  //# sourceMappingURL=resolve.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../src/auth/resolve.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAI/C,OAAO,EAAE,WAAW,EAAE,KAAK,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAE7E,OAAO,KAAK,EAGX,WAAW,EACX,UAAU,EAEV,eAAe,EAGf,YAAY,EACZ,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,uBAAuB;IACvC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,WAAW,CAAC;IAClB,kFAAkF;IAClF,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,MAAM,CAAC,EAAE,WAAW,CAAC;CACrB;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAClC,QAAQ,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,YAAY,CAAA;CAAE,EAC5C,WAAW,EAAE,eAAe,EAC5B,WAAW,EAAE,WAAW,EACxB,SAAS,CAAC,EAAE,uBAAuB,GACjC,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAMjC"}
1
+ {"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../src/auth/resolve.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAI/C,OAAO,EAAE,WAAW,EAAE,KAAK,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAE7E,OAAO,KAAK,EAGX,WAAW,EACX,UAAU,EAEV,eAAe,EACf,SAAS,EACT,eAAe,EACf,YAAY,EACZ,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,uBAAuB;IACvC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,WAAW,CAAC;IAClB,kFAAkF;IAClF,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,MAAM,CAAC,EAAE,WAAW,CAAC;CACrB;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAClC,QAAQ,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,YAAY,CAAA;CAAE,EAC5C,WAAW,EAAE,eAAe,EAC5B,WAAW,EAAE,WAAW,EACxB,SAAS,CAAC,EAAE,uBAAuB,GACjC,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAMjC;AA6DD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,4BAA4B,CACjD,WAAW,EAAE,eAAe,EAC5B,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,SAAS,EAChB,YAAY,EAAE,CAAC,UAAU,EAAE,eAAe,KAAK,OAAO,EACtD,MAAM,EAAE,WAAW,GACjB,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC,CA8BtC"}
@@ -45,10 +45,57 @@ function overlayEnvAuthContext(base, env) {
45
45
  }
46
46
  const DEFAULT_OAUTH_MINIMUM_VALIDITY_MS = 5 * 60 * 1000;
47
47
  const DEFAULT_OAUTH_REFRESH_TIMEOUT_MS = 15_000;
48
+ /**
49
+ * Refresh a stored OAuth credential under the credential-store lock and persist
50
+ * the result before the lock is released. `needsRefresh` is re-checked under
51
+ * the lock, so concurrent callers and processes refresh only once.
52
+ *
53
+ * `signal` cancels only the wait for the lock. Once a refresh starts, the
54
+ * provider may already have rotated the refresh token, so the refresh and its
55
+ * persistence ignore `signal` and are bounded only by a timeout. Otherwise a
56
+ * cancelled caller could discard the only valid refresh token. Callers that
57
+ * must return promptly on cancellation race this promise with their signal.
58
+ *
59
+ * Resolves with the stored OAuth credential after the operation, or undefined
60
+ * when the provider no longer has an OAuth credential.
61
+ */
62
+ export async function refreshStoredOAuthCredential(credentials, providerId, oauth, needsRefresh, signal) {
63
+ let post;
64
+ const lockWait = new AbortController();
65
+ const cancelLockWait = () => lockWait.abort(signal.reason);
66
+ signal.addEventListener("abort", cancelLockWait, { once: true });
67
+ if (signal.aborted)
68
+ cancelLockWait();
69
+ try {
70
+ post = await credentials.modify(providerId, async (current) => {
71
+ signal.removeEventListener("abort", cancelLockWait);
72
+ signal.throwIfAborted();
73
+ if (current?.type !== "oauth")
74
+ return undefined; // logged out meanwhile
75
+ if (!needsRefresh(current))
76
+ return undefined; // another process/request refreshed
77
+ try {
78
+ return await oauth.refresh(current, AbortSignal.timeout(DEFAULT_OAUTH_REFRESH_TIMEOUT_MS));
79
+ }
80
+ catch (error) {
81
+ throw new ModelsError("oauth", `OAuth refresh failed for ${providerId}`, { cause: error });
82
+ }
83
+ }, { signal: lockWait.signal });
84
+ }
85
+ catch (error) {
86
+ if (error instanceof ModelsError)
87
+ throw error;
88
+ signal.throwIfAborted();
89
+ throw new ModelsError("auth", `Credential store modify failed for ${providerId}`, { cause: error });
90
+ }
91
+ finally {
92
+ signal.removeEventListener("abort", cancelLockWait);
93
+ }
94
+ return post?.type === "oauth" ? post : undefined;
95
+ }
48
96
  /**
49
97
  * OAuth resolution with double-checked locking: tokens with less than five
50
- * minutes remaining lock, re-check expiry under the lock, refresh once
51
- * globally, and persist the rotated credential before release.
98
+ * minutes remaining are refreshed through `refreshStoredOAuthCredential`.
52
99
  */
53
100
  async function resolveStoredOAuth(credentials, providerId, oauth, stored, signal, minOAuthValidityMs) {
54
101
  const minimumValidityMs = Math.max(DEFAULT_OAUTH_MINIMUM_VALIDITY_MS, minOAuthValidityMs ?? 0);
@@ -56,31 +103,8 @@ async function resolveStoredOAuth(credentials, providerId, oauth, stored, signal
56
103
  let credential = stored;
57
104
  if (expiresSoon(credential)) {
58
105
  // Optimistic check said expired; the authoritative check runs under the lock.
59
- let post;
60
- try {
61
- post = await credentials.modify(providerId, async (current) => {
62
- if (current?.type !== "oauth")
63
- return undefined; // logged out meanwhile
64
- if (!expiresSoon(current))
65
- return undefined; // another process/request refreshed
66
- try {
67
- const refreshSignal = AbortSignal.any([
68
- signal,
69
- AbortSignal.timeout(DEFAULT_OAUTH_REFRESH_TIMEOUT_MS),
70
- ]);
71
- return await oauth.refresh(current, refreshSignal);
72
- }
73
- catch (error) {
74
- throw new ModelsError("oauth", `OAuth refresh failed for ${providerId}`, { cause: error });
75
- }
76
- }, { signal });
77
- }
78
- catch (error) {
79
- if (error instanceof ModelsError)
80
- throw error;
81
- throw new ModelsError("auth", `Credential store modify failed for ${providerId}`, { cause: error });
82
- }
83
- if (post?.type !== "oauth")
106
+ const post = await refreshStoredOAuthCredential(credentials, providerId, oauth, expiresSoon, signal);
107
+ if (!post)
84
108
  return undefined; // logged out meanwhile
85
109
  credential = post;
86
110
  // The normal five-minute window triggers a refresh but does not impose a
@@ -1 +1 @@
1
- {"version":3,"file":"resolve.js","sourceRoot":"","sources":["../../src/auth/resolve.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ProviderEnv } from \"../types.ts\";\nimport { operationSignal, raceWithAbortSignal } from \"../utils/abort.ts\";\nimport { ModelsError } from \"../utils/models-error.ts\";\n\nexport { ModelsError, type ModelsErrorCode } from \"../utils/models-error.ts\";\n\nimport type {\n\tApiKeyAuth,\n\tApiKeyCredential,\n\tAuthContext,\n\tAuthResult,\n\tCredential,\n\tCredentialStore,\n\tOAuthAuth,\n\tOAuthCredential,\n\tProviderAuth,\n} from \"./types.ts\";\n\nexport interface AuthResolutionOverrides {\n\tapiKey?: string;\n\tenv?: ProviderEnv;\n\t/** Require this much remaining OAuth-token validity; defaults to five minutes. */\n\tminOAuthValidityMs?: number;\n\tsignal?: AbortSignal;\n}\n\n/**\n * Auth resolution shared by all operations in a `Models` collection.\n * A stored credential owns the provider: ambient/env is consulted only when\n * nothing is stored. No silent env fallback after a failed refresh or for a\n * credential type without a matching handler.\n */\nexport function resolveProviderAuth(\n\tprovider: { id: string; auth: ProviderAuth },\n\tcredentials: CredentialStore,\n\tauthContext: AuthContext,\n\toverrides?: AuthResolutionOverrides,\n): Promise<AuthResult | undefined> {\n\tconst signal = operationSignal(overrides?.signal);\n\treturn raceWithAbortSignal(\n\t\tresolveProviderAuthWithSignal(provider, credentials, authContext, overrides, signal),\n\t\tsignal,\n\t);\n}\n\nasync function resolveProviderAuthWithSignal(\n\tprovider: { id: string; auth: ProviderAuth },\n\tcredentials: CredentialStore,\n\tauthContext: AuthContext,\n\toverrides: AuthResolutionOverrides | undefined,\n\tsignal: AbortSignal,\n): Promise<AuthResult | undefined> {\n\tsignal.throwIfAborted();\n\tconst requestAuthContext = overrides?.env ? overlayEnvAuthContext(authContext, overrides.env) : authContext;\n\n\tif (overrides?.apiKey !== undefined && provider.auth.apiKey) {\n\t\treturn resolveApiKey(\n\t\t\trequestAuthContext,\n\t\t\tprovider.auth.apiKey,\n\t\t\tprovider.id,\n\t\t\t{\n\t\t\t\ttype: \"api_key\",\n\t\t\t\tkey: overrides.apiKey,\n\t\t\t\tenv: overrides.env,\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t}\n\n\tconst stored = await readCredential(credentials, provider.id, signal);\n\tif (stored) {\n\t\tif (stored.type === \"oauth\" && provider.auth.oauth) {\n\t\t\treturn resolveStoredOAuth(\n\t\t\t\tcredentials,\n\t\t\t\tprovider.id,\n\t\t\t\tprovider.auth.oauth,\n\t\t\t\tstored,\n\t\t\t\tsignal,\n\t\t\t\toverrides?.minOAuthValidityMs,\n\t\t\t);\n\t\t}\n\t\tif (stored.type === \"api_key\" && provider.auth.apiKey) {\n\t\t\tconst credential = overrides?.env ? { ...stored, env: { ...stored.env, ...overrides.env } } : stored;\n\t\t\treturn resolveApiKey(requestAuthContext, provider.auth.apiKey, provider.id, credential, signal);\n\t\t}\n\t\treturn undefined;\n\t}\n\n\t// Ambient (env vars, AWS profiles, ADC files).\n\treturn provider.auth.apiKey\n\t\t? resolveApiKey(requestAuthContext, provider.auth.apiKey, provider.id, undefined, signal)\n\t\t: undefined;\n}\n\nfunction overlayEnvAuthContext(base: AuthContext, env: ProviderEnv): AuthContext {\n\treturn {\n\t\tenv: async (name) => env[name] || (await base.env(name)),\n\t\tfileExists: (path) => base.fileExists(path),\n\t};\n}\n\nconst DEFAULT_OAUTH_MINIMUM_VALIDITY_MS = 5 * 60 * 1000;\nconst DEFAULT_OAUTH_REFRESH_TIMEOUT_MS = 15_000;\n\n/**\n * OAuth resolution with double-checked locking: tokens with less than five\n * minutes remaining lock, re-check expiry under the lock, refresh once\n * globally, and persist the rotated credential before release.\n */\nasync function resolveStoredOAuth(\n\tcredentials: CredentialStore,\n\tproviderId: string,\n\toauth: OAuthAuth,\n\tstored: OAuthCredential,\n\tsignal: AbortSignal,\n\tminOAuthValidityMs?: number,\n): Promise<AuthResult | undefined> {\n\tconst minimumValidityMs = Math.max(DEFAULT_OAUTH_MINIMUM_VALIDITY_MS, minOAuthValidityMs ?? 0);\n\tconst expiresSoon = (credential: OAuthCredential) => Date.now() + minimumValidityMs >= credential.expires;\n\tlet credential = stored;\n\n\tif (expiresSoon(credential)) {\n\t\t// Optimistic check said expired; the authoritative check runs under the lock.\n\t\tlet post: Credential | undefined;\n\t\ttry {\n\t\t\tpost = await credentials.modify(\n\t\t\t\tproviderId,\n\t\t\t\tasync (current) => {\n\t\t\t\t\tif (current?.type !== \"oauth\") return undefined; // logged out meanwhile\n\t\t\t\t\tif (!expiresSoon(current)) return undefined; // another process/request refreshed\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst refreshSignal = AbortSignal.any([\n\t\t\t\t\t\t\tsignal,\n\t\t\t\t\t\t\tAbortSignal.timeout(DEFAULT_OAUTH_REFRESH_TIMEOUT_MS),\n\t\t\t\t\t\t]);\n\t\t\t\t\t\treturn await oauth.refresh(current, refreshSignal);\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tthrow new ModelsError(\"oauth\", `OAuth refresh failed for ${providerId}`, { cause: error });\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{ signal },\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tif (error instanceof ModelsError) throw error;\n\t\t\tthrow new ModelsError(\"auth\", `Credential store modify failed for ${providerId}`, { cause: error });\n\t\t}\n\t\tif (post?.type !== \"oauth\") return undefined; // logged out meanwhile\n\t\tcredential = post;\n\t\t// The normal five-minute window triggers a refresh but does not impose a\n\t\t// provider contract. Explicit callers (such as bearer-token export) do\n\t\t// require the requested minimum after the refresh.\n\t\tif (minOAuthValidityMs !== undefined && expiresSoon(credential)) {\n\t\t\tthrow new ModelsError(\"oauth\", `OAuth refresh returned a token that expires too soon for ${providerId}`);\n\t\t}\n\t}\n\n\ttry {\n\t\treturn { auth: await oauth.toAuth(credential), source: \"OAuth\" };\n\t} catch (error) {\n\t\tthrow new ModelsError(\"oauth\", `OAuth auth derivation failed for ${providerId}`, { cause: error });\n\t}\n}\n\nasync function resolveApiKey(\n\tauthContext: AuthContext,\n\tapiKey: ApiKeyAuth,\n\tproviderId: string,\n\tcredential: ApiKeyCredential | undefined,\n\tsignal: AbortSignal,\n): Promise<AuthResult | undefined> {\n\ttry {\n\t\treturn await apiKey.resolve({ ctx: authContext, credential, signal });\n\t} catch (error) {\n\t\tthrow new ModelsError(\"auth\", `API key auth failed for provider ${providerId}`, { cause: error });\n\t}\n}\n\nasync function readCredential(\n\tcredentials: CredentialStore,\n\tproviderId: string,\n\tsignal: AbortSignal,\n): Promise<Credential | undefined> {\n\ttry {\n\t\treturn await credentials.read(providerId, { signal });\n\t} catch (error) {\n\t\tthrow new ModelsError(\"auth\", `Credential store read failed for ${providerId}`, { cause: error });\n\t}\n}\n"]}
1
+ {"version":3,"file":"resolve.js","sourceRoot":"","sources":["../../src/auth/resolve.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ProviderEnv } from \"../types.ts\";\nimport { operationSignal, raceWithAbortSignal } from \"../utils/abort.ts\";\nimport { ModelsError } from \"../utils/models-error.ts\";\n\nexport { ModelsError, type ModelsErrorCode } from \"../utils/models-error.ts\";\n\nimport type {\n\tApiKeyAuth,\n\tApiKeyCredential,\n\tAuthContext,\n\tAuthResult,\n\tCredential,\n\tCredentialStore,\n\tOAuthAuth,\n\tOAuthCredential,\n\tProviderAuth,\n} from \"./types.ts\";\n\nexport interface AuthResolutionOverrides {\n\tapiKey?: string;\n\tenv?: ProviderEnv;\n\t/** Require this much remaining OAuth-token validity; defaults to five minutes. */\n\tminOAuthValidityMs?: number;\n\tsignal?: AbortSignal;\n}\n\n/**\n * Auth resolution shared by all operations in a `Models` collection.\n * A stored credential owns the provider: ambient/env is consulted only when\n * nothing is stored. No silent env fallback after a failed refresh or for a\n * credential type without a matching handler.\n */\nexport function resolveProviderAuth(\n\tprovider: { id: string; auth: ProviderAuth },\n\tcredentials: CredentialStore,\n\tauthContext: AuthContext,\n\toverrides?: AuthResolutionOverrides,\n): Promise<AuthResult | undefined> {\n\tconst signal = operationSignal(overrides?.signal);\n\treturn raceWithAbortSignal(\n\t\tresolveProviderAuthWithSignal(provider, credentials, authContext, overrides, signal),\n\t\tsignal,\n\t);\n}\n\nasync function resolveProviderAuthWithSignal(\n\tprovider: { id: string; auth: ProviderAuth },\n\tcredentials: CredentialStore,\n\tauthContext: AuthContext,\n\toverrides: AuthResolutionOverrides | undefined,\n\tsignal: AbortSignal,\n): Promise<AuthResult | undefined> {\n\tsignal.throwIfAborted();\n\tconst requestAuthContext = overrides?.env ? overlayEnvAuthContext(authContext, overrides.env) : authContext;\n\n\tif (overrides?.apiKey !== undefined && provider.auth.apiKey) {\n\t\treturn resolveApiKey(\n\t\t\trequestAuthContext,\n\t\t\tprovider.auth.apiKey,\n\t\t\tprovider.id,\n\t\t\t{\n\t\t\t\ttype: \"api_key\",\n\t\t\t\tkey: overrides.apiKey,\n\t\t\t\tenv: overrides.env,\n\t\t\t},\n\t\t\tsignal,\n\t\t);\n\t}\n\n\tconst stored = await readCredential(credentials, provider.id, signal);\n\tif (stored) {\n\t\tif (stored.type === \"oauth\" && provider.auth.oauth) {\n\t\t\treturn resolveStoredOAuth(\n\t\t\t\tcredentials,\n\t\t\t\tprovider.id,\n\t\t\t\tprovider.auth.oauth,\n\t\t\t\tstored,\n\t\t\t\tsignal,\n\t\t\t\toverrides?.minOAuthValidityMs,\n\t\t\t);\n\t\t}\n\t\tif (stored.type === \"api_key\" && provider.auth.apiKey) {\n\t\t\tconst credential = overrides?.env ? { ...stored, env: { ...stored.env, ...overrides.env } } : stored;\n\t\t\treturn resolveApiKey(requestAuthContext, provider.auth.apiKey, provider.id, credential, signal);\n\t\t}\n\t\treturn undefined;\n\t}\n\n\t// Ambient (env vars, AWS profiles, ADC files).\n\treturn provider.auth.apiKey\n\t\t? resolveApiKey(requestAuthContext, provider.auth.apiKey, provider.id, undefined, signal)\n\t\t: undefined;\n}\n\nfunction overlayEnvAuthContext(base: AuthContext, env: ProviderEnv): AuthContext {\n\treturn {\n\t\tenv: async (name) => env[name] || (await base.env(name)),\n\t\tfileExists: (path) => base.fileExists(path),\n\t};\n}\n\nconst DEFAULT_OAUTH_MINIMUM_VALIDITY_MS = 5 * 60 * 1000;\nconst DEFAULT_OAUTH_REFRESH_TIMEOUT_MS = 15_000;\n\n/**\n * Refresh a stored OAuth credential under the credential-store lock and persist\n * the result before the lock is released. `needsRefresh` is re-checked under\n * the lock, so concurrent callers and processes refresh only once.\n *\n * `signal` cancels only the wait for the lock. Once a refresh starts, the\n * provider may already have rotated the refresh token, so the refresh and its\n * persistence ignore `signal` and are bounded only by a timeout. Otherwise a\n * cancelled caller could discard the only valid refresh token. Callers that\n * must return promptly on cancellation race this promise with their signal.\n *\n * Resolves with the stored OAuth credential after the operation, or undefined\n * when the provider no longer has an OAuth credential.\n */\nexport async function refreshStoredOAuthCredential(\n\tcredentials: CredentialStore,\n\tproviderId: string,\n\toauth: OAuthAuth,\n\tneedsRefresh: (credential: OAuthCredential) => boolean,\n\tsignal: AbortSignal,\n): Promise<OAuthCredential | undefined> {\n\tlet post: Credential | undefined;\n\tconst lockWait = new AbortController();\n\tconst cancelLockWait = () => lockWait.abort(signal.reason);\n\tsignal.addEventListener(\"abort\", cancelLockWait, { once: true });\n\tif (signal.aborted) cancelLockWait();\n\ttry {\n\t\tpost = await credentials.modify(\n\t\t\tproviderId,\n\t\t\tasync (current) => {\n\t\t\t\tsignal.removeEventListener(\"abort\", cancelLockWait);\n\t\t\t\tsignal.throwIfAborted();\n\t\t\t\tif (current?.type !== \"oauth\") return undefined; // logged out meanwhile\n\t\t\t\tif (!needsRefresh(current)) return undefined; // another process/request refreshed\n\t\t\t\ttry {\n\t\t\t\t\treturn await oauth.refresh(current, AbortSignal.timeout(DEFAULT_OAUTH_REFRESH_TIMEOUT_MS));\n\t\t\t\t} catch (error) {\n\t\t\t\t\tthrow new ModelsError(\"oauth\", `OAuth refresh failed for ${providerId}`, { cause: error });\n\t\t\t\t}\n\t\t\t},\n\t\t\t{ signal: lockWait.signal },\n\t\t);\n\t} catch (error) {\n\t\tif (error instanceof ModelsError) throw error;\n\t\tsignal.throwIfAborted();\n\t\tthrow new ModelsError(\"auth\", `Credential store modify failed for ${providerId}`, { cause: error });\n\t} finally {\n\t\tsignal.removeEventListener(\"abort\", cancelLockWait);\n\t}\n\treturn post?.type === \"oauth\" ? post : undefined;\n}\n\n/**\n * OAuth resolution with double-checked locking: tokens with less than five\n * minutes remaining are refreshed through `refreshStoredOAuthCredential`.\n */\nasync function resolveStoredOAuth(\n\tcredentials: CredentialStore,\n\tproviderId: string,\n\toauth: OAuthAuth,\n\tstored: OAuthCredential,\n\tsignal: AbortSignal,\n\tminOAuthValidityMs?: number,\n): Promise<AuthResult | undefined> {\n\tconst minimumValidityMs = Math.max(DEFAULT_OAUTH_MINIMUM_VALIDITY_MS, minOAuthValidityMs ?? 0);\n\tconst expiresSoon = (credential: OAuthCredential) => Date.now() + minimumValidityMs >= credential.expires;\n\tlet credential = stored;\n\n\tif (expiresSoon(credential)) {\n\t\t// Optimistic check said expired; the authoritative check runs under the lock.\n\t\tconst post = await refreshStoredOAuthCredential(credentials, providerId, oauth, expiresSoon, signal);\n\t\tif (!post) return undefined; // logged out meanwhile\n\t\tcredential = post;\n\t\t// The normal five-minute window triggers a refresh but does not impose a\n\t\t// provider contract. Explicit callers (such as bearer-token export) do\n\t\t// require the requested minimum after the refresh.\n\t\tif (minOAuthValidityMs !== undefined && expiresSoon(credential)) {\n\t\t\tthrow new ModelsError(\"oauth\", `OAuth refresh returned a token that expires too soon for ${providerId}`);\n\t\t}\n\t}\n\n\ttry {\n\t\treturn { auth: await oauth.toAuth(credential), source: \"OAuth\" };\n\t} catch (error) {\n\t\tthrow new ModelsError(\"oauth\", `OAuth auth derivation failed for ${providerId}`, { cause: error });\n\t}\n}\n\nasync function resolveApiKey(\n\tauthContext: AuthContext,\n\tapiKey: ApiKeyAuth,\n\tproviderId: string,\n\tcredential: ApiKeyCredential | undefined,\n\tsignal: AbortSignal,\n): Promise<AuthResult | undefined> {\n\ttry {\n\t\treturn await apiKey.resolve({ ctx: authContext, credential, signal });\n\t} catch (error) {\n\t\tthrow new ModelsError(\"auth\", `API key auth failed for provider ${providerId}`, { cause: error });\n\t}\n}\n\nasync function readCredential(\n\tcredentials: CredentialStore,\n\tproviderId: string,\n\tsignal: AbortSignal,\n): Promise<Credential | undefined> {\n\ttry {\n\t\treturn await credentials.read(providerId, { signal });\n\t} catch (error) {\n\t\tthrow new ModelsError(\"auth\", `Credential store read failed for ${providerId}`, { cause: error });\n\t}\n}\n"]}
@@ -202,6 +202,11 @@ export interface LoginOptions {
202
202
  * the ID on first use and must return the same ID on every later call.
203
203
  */
204
204
  getDeviceId?: () => string;
205
+ /**
206
+ * Name this app introduces itself with during login, e.g. OpenAI's agent name hint and
207
+ * Codex browser-login originator. Defaults to "Draht" and "codex_cli_rs" respectively.
208
+ */
209
+ agentName?: string;
205
210
  }
206
211
  /**
207
212
  * OAuth auth. The `refresh`/`toAuth` split lets `Models` own the locked
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/auth/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/auth/types.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/auth/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { ProviderEnv, ProviderHeaders } from \"../types.ts\";\n\n/**\n * Request auth for a single model request. If a value cannot be expressed as\n * `apiKey`, `headers`, or `baseUrl`, it is provider config, not auth.\n */\nexport interface ModelAuth {\n\tapiKey?: string;\n\theaders?: ProviderHeaders;\n\tbaseUrl?: string;\n}\n\n/**\n * Stored api-key credential. `env` holds provider-scoped environment/config\n * values such as Cloudflare account/gateway ids.\n */\nexport interface ApiKeyCredential {\n\ttype: \"api_key\";\n\tkey?: string;\n\tenv?: ProviderEnv;\n}\n\n/** OAuth token data returned by extension compatibility flows. */\nexport interface OAuthCredentials {\n\trefresh: string;\n\taccess: string;\n\texpires: number;\n\t[key: string]: unknown;\n}\n\n/** Stored canonical OAuth credential. */\nexport interface OAuthCredential extends OAuthCredentials {\n\ttype: \"oauth\";\n}\n\n/** One type-tagged credential per provider — the shape of today's auth.json. */\nexport type Credential = ApiKeyCredential | OAuthCredential;\n\n/** Non-secret credential metadata for account/status enumeration. */\nexport interface CredentialInfo {\n\tproviderId: string;\n\ttype: Credential[\"type\"];\n}\n\n/** Optional cancellation for public auth and credential operations. */\nexport interface AuthOperationOptions {\n\tsignal?: AbortSignal;\n}\n\n/**\n * App-owned credential storage, keyed by `Provider.id`, one credential per\n * provider. `modify` is the only write path, so every mutation is a\n * serialized read-modify-write; `Models.getAuth()` runs OAuth refresh inside\n * `modify` so concurrent requests cannot double-refresh a rotated token. The\n * app persists a credential after login via\n * `modify(provider.id, async () => credential)`. Login/logout orchestration\n * is app-owned.\n *\n * Error semantics: `read` resolves `undefined` for missing entries. Methods\n * reject only on storage failure; `Models` wraps such rejections in\n * `ModelsError` with code \"auth\". Best-effort stores that serve an in-memory\n * view and record persistence errors internally (like coding-agent's\n * AuthStorage) are valid implementations.\n */\nexport interface CredentialStore {\n\t/**\n\t * Read the stored credential, possibly expired. Display/status use;\n\t * resolved request auth comes from `Models.getAuth()`.\n\t */\n\tread(providerId: string, options?: AuthOperationOptions): Promise<Credential | undefined>;\n\n\t/**\n\t * List stored credential metadata without resolving or exposing secrets.\n\t * Implementations must not execute configured API-key commands while listing.\n\t */\n\tlist(options?: AuthOperationOptions): Promise<readonly CredentialInfo[]>;\n\n\t/**\n\t * Serialized write — the only write path. `fn` sees the current credential\n\t * because correct writes (refresh, login-during-refresh) depend on it;\n\t * return the new credential, or undefined to leave the entry unchanged.\n\t * Mutual exclusion per provider id, cross-process too where the backing\n\t * store supports it (e.g. a file lock). Resolves with the post-write\n\t * credential. Rejections from `fn` propagate.\n\t */\n\tmodify(\n\t\tproviderId: string,\n\t\tfn: (current: Credential | undefined) => Promise<Credential | undefined>,\n\t\toptions?: AuthOperationOptions,\n\t): Promise<Credential | undefined>;\n\n\t/** Remove a credential (logout). Implementations serialize this against `modify`. */\n\tdelete(providerId: string, options?: AuthOperationOptions): Promise<void>;\n}\n\n/** Environment access for auth resolution. Injectable for tests and browsers. */\nexport interface AuthContext {\n\tenv(name: string): Promise<string | undefined>;\n\t/** Check whether a file exists. Supports a leading `~`. Always false in browsers. */\n\tfileExists(path: string): Promise<boolean>;\n}\n\n/** Result of resolving auth for a model. */\nexport interface AuthResult {\n\tauth: ModelAuth;\n\t/** Provider-scoped environment/config values resolved from credentials and ambient context. */\n\tenv?: ProviderEnv;\n\t/** Human-readable label for status UI: \"ANTHROPIC_API_KEY\", \"OAuth\", \"~/.aws/credentials\". */\n\tsource?: string;\n}\n\nexport interface AuthCheck {\n\tsource?: string;\n\ttype: \"api_key\" | \"oauth\";\n}\n\nexport type AuthType = \"api_key\" | \"oauth\";\n\n/**\n * Prompt shown to the user during login. `signal` lets the flow cancel a\n * pending prompt when an out-of-band event resolves the step, e.g. a\n * `manual_code` prompt raced against a callback server, aborted when the\n * callback wins.\n */\nexport type AuthPrompt = { signal?: AbortSignal } & (\n\t| { type: \"text\"; message: string; placeholder?: string }\n\t| { type: \"secret\"; message: string; placeholder?: string }\n\t| { type: \"select\"; message: string; options: readonly { id: string; label: string; description?: string }[] }\n\t| { type: \"manual_code\"; message: string; placeholder?: string }\n);\n\nexport interface AuthInfoLink {\n\turl: string;\n\tlabel?: string;\n}\n\nexport type AuthEvent =\n\t| { type: \"info\"; message: string; links?: readonly AuthInfoLink[] }\n\t| { type: \"auth_url\"; url: string; instructions?: string }\n\t| {\n\t\t\ttype: \"device_code\";\n\t\t\tuserCode: string;\n\t\t\tverificationUri: string;\n\t\t\tintervalSeconds?: number;\n\t\t\texpiresInSeconds?: number;\n\t }\n\t| { type: \"progress\"; message: string };\n\n/**\n * Login interaction callbacks serving both api-key and OAuth flows.\n *\n * `prompt()` returns the entered/selected string (`select` returns the option\n * id). Rejects on cancel/abort. `signal` aborts the whole login flow;\n * per-prompt cancellation uses `AuthPrompt.signal`.\n */\nexport interface AuthInteraction {\n\tsignal?: AbortSignal;\n\n\tprompt(prompt: AuthPrompt): Promise<string>;\n\tnotify(event: AuthEvent): void;\n}\n\n/** Normalized interaction passed to provider login implementations. */\nexport type ProviderAuthInteraction = AuthInteraction & { signal: AbortSignal };\n\n/**\n * Api-key auth: stored key/provider env plus ambient sources (env vars, AWS\n * profiles, ADC files). Ambient-only providers omit `login`.\n */\nexport interface ApiKeyAuth {\n\t/** Display name, e.g. \"Anthropic API key\". */\n\tname: string;\n\n\t/** Interactive setup (prompt for key/provider env). Absent = ambient-only. */\n\tlogin?(interaction: ProviderAuthInteraction): Promise<ApiKeyCredential>;\n\n\t/**\n\t * Optional side-effect-free availability check. Use this when `resolve()` may\n\t * execute commands or perform other request-time work. Missing means Models\n\t * checks availability by resolving auth.\n\t */\n\tcheck?(input: {\n\t\tctx: AuthContext;\n\t\tcredential?: ApiKeyCredential;\n\t\tsignal: AbortSignal;\n\t}): Promise<AuthCheck | undefined>;\n\n\t/**\n\t * Resolve auth from the stored credential and/or ambient sources, merging\n\t * per field (`credential.key ?? env(\"...\")`, `credential.env?.NAME ?? env(\"...\")`).\n\t * undefined = not configured. Resolution is provider-scoped; model-specific\n\t * endpoint preparation happens after auth has been resolved.\n\t */\n\tresolve(input: {\n\t\tctx: AuthContext;\n\t\tcredential?: ApiKeyCredential;\n\t\tsignal: AbortSignal;\n\t}): Promise<AuthResult | undefined>;\n}\n\n/** App-supplied context for `Models.login`. */\nexport interface LoginOptions {\n\t/**\n\t * Returns the stable ID of this app installation, e.g. sent to OpenAI as its\n\t * agent host ID. Called only by login flows that need it, so apps can create\n\t * the ID on first use and must return the same ID on every later call.\n\t */\n\tgetDeviceId?: () => string;\n}\n\n/**\n * OAuth auth. The `refresh`/`toAuth` split lets `Models` own the locked\n * refresh pattern: `refresh` produces a credential, `toAuth` derives request\n * auth from whatever credential ends up stored.\n */\nexport interface OAuthAuth {\n\t/** Display name, e.g. \"Anthropic (Claude Pro/Max)\". */\n\tname: string;\n\n\t/** Whether access through this auth method is backed by a provider subscription. */\n\tisSubscription?: boolean;\n\n\t/** Selector label for the OAuth login option, e.g. \"Sign in with SuperGrok or X Premium\". */\n\tloginLabel?: string;\n\n\tlogin(interaction: ProviderAuthInteraction, options?: LoginOptions): Promise<OAuthCredential>;\n\n\t/**\n\t * Exchange the refresh token. Network call; throws on failure\n\t * (invalid_grant etc.). `Models` runs this under the store lock.\n\t */\n\trefresh(credential: OAuthCredential, signal: AbortSignal): Promise<OAuthCredential>;\n\n\t/**\n\t * Side-effect-free derivation of request auth from a valid credential.\n\t * Covers per-credential baseUrl (GitHub Copilot). Async so lazy wrappers\n\t * can load the implementation on first use.\n\t */\n\ttoAuth(credential: OAuthCredential): Promise<ModelAuth>;\n}\n\n/**\n * Provider auth. At least one of `apiKey`/`oauth` must be present: even\n * ambient-credential providers and keyless local servers provide `apiKey`\n * auth whose `resolve()` reports whether the provider is configured.\n */\nexport interface ProviderAuth {\n\tapiKey?: ApiKeyAuth;\n\toauth?: OAuthAuth;\n}\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/auth/types.ts"],"names":[],"mappings":"","sourcesContent":["import type { ProviderEnv, ProviderHeaders } from \"../types.ts\";\n\n/**\n * Request auth for a single model request. If a value cannot be expressed as\n * `apiKey`, `headers`, or `baseUrl`, it is provider config, not auth.\n */\nexport interface ModelAuth {\n\tapiKey?: string;\n\theaders?: ProviderHeaders;\n\tbaseUrl?: string;\n}\n\n/**\n * Stored api-key credential. `env` holds provider-scoped environment/config\n * values such as Cloudflare account/gateway ids.\n */\nexport interface ApiKeyCredential {\n\ttype: \"api_key\";\n\tkey?: string;\n\tenv?: ProviderEnv;\n}\n\n/** OAuth token data returned by extension compatibility flows. */\nexport interface OAuthCredentials {\n\trefresh: string;\n\taccess: string;\n\texpires: number;\n\t[key: string]: unknown;\n}\n\n/** Stored canonical OAuth credential. */\nexport interface OAuthCredential extends OAuthCredentials {\n\ttype: \"oauth\";\n}\n\n/** One type-tagged credential per provider — the shape of today's auth.json. */\nexport type Credential = ApiKeyCredential | OAuthCredential;\n\n/** Non-secret credential metadata for account/status enumeration. */\nexport interface CredentialInfo {\n\tproviderId: string;\n\ttype: Credential[\"type\"];\n}\n\n/** Optional cancellation for public auth and credential operations. */\nexport interface AuthOperationOptions {\n\tsignal?: AbortSignal;\n}\n\n/**\n * App-owned credential storage, keyed by `Provider.id`, one credential per\n * provider. `modify` is the only write path, so every mutation is a\n * serialized read-modify-write; `Models.getAuth()` runs OAuth refresh inside\n * `modify` so concurrent requests cannot double-refresh a rotated token. The\n * app persists a credential after login via\n * `modify(provider.id, async () => credential)`. Login/logout orchestration\n * is app-owned.\n *\n * Error semantics: `read` resolves `undefined` for missing entries. Methods\n * reject only on storage failure; `Models` wraps such rejections in\n * `ModelsError` with code \"auth\". Best-effort stores that serve an in-memory\n * view and record persistence errors internally (like coding-agent's\n * AuthStorage) are valid implementations.\n */\nexport interface CredentialStore {\n\t/**\n\t * Read the stored credential, possibly expired. Display/status use;\n\t * resolved request auth comes from `Models.getAuth()`.\n\t */\n\tread(providerId: string, options?: AuthOperationOptions): Promise<Credential | undefined>;\n\n\t/**\n\t * List stored credential metadata without resolving or exposing secrets.\n\t * Implementations must not execute configured API-key commands while listing.\n\t */\n\tlist(options?: AuthOperationOptions): Promise<readonly CredentialInfo[]>;\n\n\t/**\n\t * Serialized write — the only write path. `fn` sees the current credential\n\t * because correct writes (refresh, login-during-refresh) depend on it;\n\t * return the new credential, or undefined to leave the entry unchanged.\n\t * Mutual exclusion per provider id, cross-process too where the backing\n\t * store supports it (e.g. a file lock). Resolves with the post-write\n\t * credential. Rejections from `fn` propagate.\n\t */\n\tmodify(\n\t\tproviderId: string,\n\t\tfn: (current: Credential | undefined) => Promise<Credential | undefined>,\n\t\toptions?: AuthOperationOptions,\n\t): Promise<Credential | undefined>;\n\n\t/** Remove a credential (logout). Implementations serialize this against `modify`. */\n\tdelete(providerId: string, options?: AuthOperationOptions): Promise<void>;\n}\n\n/** Environment access for auth resolution. Injectable for tests and browsers. */\nexport interface AuthContext {\n\tenv(name: string): Promise<string | undefined>;\n\t/** Check whether a file exists. Supports a leading `~`. Always false in browsers. */\n\tfileExists(path: string): Promise<boolean>;\n}\n\n/** Result of resolving auth for a model. */\nexport interface AuthResult {\n\tauth: ModelAuth;\n\t/** Provider-scoped environment/config values resolved from credentials and ambient context. */\n\tenv?: ProviderEnv;\n\t/** Human-readable label for status UI: \"ANTHROPIC_API_KEY\", \"OAuth\", \"~/.aws/credentials\". */\n\tsource?: string;\n}\n\nexport interface AuthCheck {\n\tsource?: string;\n\ttype: \"api_key\" | \"oauth\";\n}\n\nexport type AuthType = \"api_key\" | \"oauth\";\n\n/**\n * Prompt shown to the user during login. `signal` lets the flow cancel a\n * pending prompt when an out-of-band event resolves the step, e.g. a\n * `manual_code` prompt raced against a callback server, aborted when the\n * callback wins.\n */\nexport type AuthPrompt = { signal?: AbortSignal } & (\n\t| { type: \"text\"; message: string; placeholder?: string }\n\t| { type: \"secret\"; message: string; placeholder?: string }\n\t| { type: \"select\"; message: string; options: readonly { id: string; label: string; description?: string }[] }\n\t| { type: \"manual_code\"; message: string; placeholder?: string }\n);\n\nexport interface AuthInfoLink {\n\turl: string;\n\tlabel?: string;\n}\n\nexport type AuthEvent =\n\t| { type: \"info\"; message: string; links?: readonly AuthInfoLink[] }\n\t| { type: \"auth_url\"; url: string; instructions?: string }\n\t| {\n\t\t\ttype: \"device_code\";\n\t\t\tuserCode: string;\n\t\t\tverificationUri: string;\n\t\t\tintervalSeconds?: number;\n\t\t\texpiresInSeconds?: number;\n\t }\n\t| { type: \"progress\"; message: string };\n\n/**\n * Login interaction callbacks serving both api-key and OAuth flows.\n *\n * `prompt()` returns the entered/selected string (`select` returns the option\n * id). Rejects on cancel/abort. `signal` aborts the whole login flow;\n * per-prompt cancellation uses `AuthPrompt.signal`.\n */\nexport interface AuthInteraction {\n\tsignal?: AbortSignal;\n\n\tprompt(prompt: AuthPrompt): Promise<string>;\n\tnotify(event: AuthEvent): void;\n}\n\n/** Normalized interaction passed to provider login implementations. */\nexport type ProviderAuthInteraction = AuthInteraction & { signal: AbortSignal };\n\n/**\n * Api-key auth: stored key/provider env plus ambient sources (env vars, AWS\n * profiles, ADC files). Ambient-only providers omit `login`.\n */\nexport interface ApiKeyAuth {\n\t/** Display name, e.g. \"Anthropic API key\". */\n\tname: string;\n\n\t/** Interactive setup (prompt for key/provider env). Absent = ambient-only. */\n\tlogin?(interaction: ProviderAuthInteraction): Promise<ApiKeyCredential>;\n\n\t/**\n\t * Optional side-effect-free availability check. Use this when `resolve()` may\n\t * execute commands or perform other request-time work. Missing means Models\n\t * checks availability by resolving auth.\n\t */\n\tcheck?(input: {\n\t\tctx: AuthContext;\n\t\tcredential?: ApiKeyCredential;\n\t\tsignal: AbortSignal;\n\t}): Promise<AuthCheck | undefined>;\n\n\t/**\n\t * Resolve auth from the stored credential and/or ambient sources, merging\n\t * per field (`credential.key ?? env(\"...\")`, `credential.env?.NAME ?? env(\"...\")`).\n\t * undefined = not configured. Resolution is provider-scoped; model-specific\n\t * endpoint preparation happens after auth has been resolved.\n\t */\n\tresolve(input: {\n\t\tctx: AuthContext;\n\t\tcredential?: ApiKeyCredential;\n\t\tsignal: AbortSignal;\n\t}): Promise<AuthResult | undefined>;\n}\n\n/** App-supplied context for `Models.login`. */\nexport interface LoginOptions {\n\t/**\n\t * Returns the stable ID of this app installation, e.g. sent to OpenAI as its\n\t * agent host ID. Called only by login flows that need it, so apps can create\n\t * the ID on first use and must return the same ID on every later call.\n\t */\n\tgetDeviceId?: () => string;\n\t/**\n\t * Name this app introduces itself with during login, e.g. OpenAI's agent name hint and\n\t * Codex browser-login originator. Defaults to \"Draht\" and \"codex_cli_rs\" respectively.\n\t */\n\tagentName?: string;\n}\n\n/**\n * OAuth auth. The `refresh`/`toAuth` split lets `Models` own the locked\n * refresh pattern: `refresh` produces a credential, `toAuth` derives request\n * auth from whatever credential ends up stored.\n */\nexport interface OAuthAuth {\n\t/** Display name, e.g. \"Anthropic (Claude Pro/Max)\". */\n\tname: string;\n\n\t/** Whether access through this auth method is backed by a provider subscription. */\n\tisSubscription?: boolean;\n\n\t/** Selector label for the OAuth login option, e.g. \"Sign in with SuperGrok or X Premium\". */\n\tloginLabel?: string;\n\n\tlogin(interaction: ProviderAuthInteraction, options?: LoginOptions): Promise<OAuthCredential>;\n\n\t/**\n\t * Exchange the refresh token. Network call; throws on failure\n\t * (invalid_grant etc.). `Models` runs this under the store lock.\n\t */\n\trefresh(credential: OAuthCredential, signal: AbortSignal): Promise<OAuthCredential>;\n\n\t/**\n\t * Side-effect-free derivation of request auth from a valid credential.\n\t * Covers per-credential baseUrl (GitHub Copilot). Async so lazy wrappers\n\t * can load the implementation on first use.\n\t */\n\ttoAuth(credential: OAuthCredential): Promise<ModelAuth>;\n}\n\n/**\n * Provider auth. At least one of `apiKey`/`oauth` must be present: even\n * ambient-credential providers and keyless local servers provide `apiKey`\n * auth whose `resolve()` reports whether the provider is configured.\n */\nexport interface ProviderAuth {\n\tapiKey?: ApiKeyAuth;\n\toauth?: OAuthAuth;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"bun-oauth.d.ts","sourceRoot":"","sources":["../src/bun-oauth.ts"],"names":[],"mappings":"AAcA,6EAA6E;AAC7E,wBAAgB,qBAAqB,IAAI,IAAI,CAe5C"}
1
+ {"version":3,"file":"bun-oauth.d.ts","sourceRoot":"","sources":["../src/bun-oauth.ts"],"names":[],"mappings":"AAcA,6EAA6E;AAC7E,wBAAgB,qBAAqB,IAAI,IAAI,CAgB5C"}
package/dist/bun-oauth.js CHANGED
@@ -7,7 +7,7 @@ import { registerBundledOAuthFlowLoaders } from "./auth/oauth/load.js";
7
7
  import { metaOAuth } from "./auth/oauth/meta.js";
8
8
  import { openaiChatGPTOAuth } from "./auth/oauth/openai-chatgpt.js";
9
9
  import { openaiCodexOAuth } from "./auth/oauth/openai-codex.js";
10
- import { opencodeGoOAuth } from "./auth/oauth/opencode-go.js";
10
+ import { opencodeGoOAuth, opencodeOAuth } from "./auth/oauth/opencode-go.js";
11
11
  import { openRouterOAuth } from "./auth/oauth/openrouter.js";
12
12
  import { createRadiusOAuth } from "./auth/oauth/radius.js";
13
13
  import { xaiOAuth } from "./auth/oauth/xai.js";
@@ -24,6 +24,7 @@ export function registerBunOAuthFlows() {
24
24
  xai: () => xaiOAuth,
25
25
  radius: createRadiusOAuth,
26
26
  opencodeGo: () => opencodeGoOAuth,
27
+ opencode: () => opencodeOAuth,
27
28
  googleAntigravity: () => antigravityOAuth,
28
29
  googleGeminiCli: () => geminiCliOAuth,
29
30
  });