@asm-agent/ai 0.8.2

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 (210) hide show
  1. package/README.md +1333 -0
  2. package/dist/api-registry.d.ts +20 -0
  3. package/dist/api-registry.d.ts.map +1 -0
  4. package/dist/api-registry.js +44 -0
  5. package/dist/api-registry.js.map +1 -0
  6. package/dist/bedrock-provider.d.ts +5 -0
  7. package/dist/bedrock-provider.d.ts.map +1 -0
  8. package/dist/bedrock-provider.js +6 -0
  9. package/dist/bedrock-provider.js.map +1 -0
  10. package/dist/cache-pricing.d.ts +13 -0
  11. package/dist/cache-pricing.d.ts.map +1 -0
  12. package/dist/cache-pricing.js +38 -0
  13. package/dist/cache-pricing.js.map +1 -0
  14. package/dist/cli.d.ts +3 -0
  15. package/dist/cli.d.ts.map +1 -0
  16. package/dist/cli.js +116 -0
  17. package/dist/cli.js.map +1 -0
  18. package/dist/env-api-keys.d.ts +19 -0
  19. package/dist/env-api-keys.d.ts.map +1 -0
  20. package/dist/env-api-keys.js +180 -0
  21. package/dist/env-api-keys.js.map +1 -0
  22. package/dist/index.d.ts +30 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +18 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/log.d.ts +27 -0
  27. package/dist/log.d.ts.map +1 -0
  28. package/dist/log.js +69 -0
  29. package/dist/log.js.map +1 -0
  30. package/dist/mcp/catalog.d.ts +19 -0
  31. package/dist/mcp/catalog.d.ts.map +1 -0
  32. package/dist/mcp/catalog.js +42 -0
  33. package/dist/mcp/catalog.js.map +1 -0
  34. package/dist/mcp/index.d.ts +5 -0
  35. package/dist/mcp/index.d.ts.map +1 -0
  36. package/dist/mcp/index.js +3 -0
  37. package/dist/mcp/index.js.map +1 -0
  38. package/dist/mcp/oauth.d.ts +15 -0
  39. package/dist/mcp/oauth.d.ts.map +1 -0
  40. package/dist/mcp/oauth.js +489 -0
  41. package/dist/mcp/oauth.js.map +1 -0
  42. package/dist/mcp.d.ts +2 -0
  43. package/dist/mcp.d.ts.map +1 -0
  44. package/dist/mcp.js +2 -0
  45. package/dist/mcp.js.map +1 -0
  46. package/dist/models.d.ts +18 -0
  47. package/dist/models.d.ts.map +1 -0
  48. package/dist/models.generated.d.ts +26696 -0
  49. package/dist/models.generated.d.ts.map +1 -0
  50. package/dist/models.generated.js +22192 -0
  51. package/dist/models.generated.js.map +1 -0
  52. package/dist/models.js +72 -0
  53. package/dist/models.js.map +1 -0
  54. package/dist/oauth.d.ts +2 -0
  55. package/dist/oauth.d.ts.map +1 -0
  56. package/dist/oauth.js +2 -0
  57. package/dist/oauth.js.map +1 -0
  58. package/dist/openrouter-reasoning.d.ts +11 -0
  59. package/dist/openrouter-reasoning.d.ts.map +1 -0
  60. package/dist/openrouter-reasoning.js +52 -0
  61. package/dist/openrouter-reasoning.js.map +1 -0
  62. package/dist/providers/amazon-bedrock.d.ts +38 -0
  63. package/dist/providers/amazon-bedrock.d.ts.map +1 -0
  64. package/dist/providers/amazon-bedrock.js +767 -0
  65. package/dist/providers/amazon-bedrock.js.map +1 -0
  66. package/dist/providers/anthropic.d.ts +54 -0
  67. package/dist/providers/anthropic.d.ts.map +1 -0
  68. package/dist/providers/anthropic.js +999 -0
  69. package/dist/providers/anthropic.js.map +1 -0
  70. package/dist/providers/azure-openai-responses.d.ts +12 -0
  71. package/dist/providers/azure-openai-responses.d.ts.map +1 -0
  72. package/dist/providers/azure-openai-responses.js +206 -0
  73. package/dist/providers/azure-openai-responses.js.map +1 -0
  74. package/dist/providers/cloudflare.d.ts +10 -0
  75. package/dist/providers/cloudflare.d.ts.map +1 -0
  76. package/dist/providers/cloudflare.js +23 -0
  77. package/dist/providers/cloudflare.js.map +1 -0
  78. package/dist/providers/faux.d.ts +56 -0
  79. package/dist/providers/faux.d.ts.map +1 -0
  80. package/dist/providers/faux.js +368 -0
  81. package/dist/providers/faux.js.map +1 -0
  82. package/dist/providers/github-copilot-headers.d.ts +8 -0
  83. package/dist/providers/github-copilot-headers.d.ts.map +1 -0
  84. package/dist/providers/github-copilot-headers.js +26 -0
  85. package/dist/providers/github-copilot-headers.js.map +1 -0
  86. package/dist/providers/google-shared.d.ts +55 -0
  87. package/dist/providers/google-shared.d.ts.map +1 -0
  88. package/dist/providers/google-shared.js +312 -0
  89. package/dist/providers/google-shared.js.map +1 -0
  90. package/dist/providers/google-vertex.d.ts +15 -0
  91. package/dist/providers/google-vertex.d.ts.map +1 -0
  92. package/dist/providers/google-vertex.js +421 -0
  93. package/dist/providers/google-vertex.js.map +1 -0
  94. package/dist/providers/google.d.ts +13 -0
  95. package/dist/providers/google.d.ts.map +1 -0
  96. package/dist/providers/google.js +370 -0
  97. package/dist/providers/google.js.map +1 -0
  98. package/dist/providers/mistral.d.ts +20 -0
  99. package/dist/providers/mistral.d.ts.map +1 -0
  100. package/dist/providers/mistral.js +536 -0
  101. package/dist/providers/mistral.js.map +1 -0
  102. package/dist/providers/openai-codex-responses.d.ts +30 -0
  103. package/dist/providers/openai-codex-responses.d.ts.map +1 -0
  104. package/dist/providers/openai-codex-responses.js +1013 -0
  105. package/dist/providers/openai-codex-responses.js.map +1 -0
  106. package/dist/providers/openai-completions.d.ts +21 -0
  107. package/dist/providers/openai-completions.d.ts.map +1 -0
  108. package/dist/providers/openai-completions.js +1019 -0
  109. package/dist/providers/openai-completions.js.map +1 -0
  110. package/dist/providers/openai-responses-shared.d.ts +18 -0
  111. package/dist/providers/openai-responses-shared.d.ts.map +1 -0
  112. package/dist/providers/openai-responses-shared.js +488 -0
  113. package/dist/providers/openai-responses-shared.js.map +1 -0
  114. package/dist/providers/openai-responses.d.ts +10 -0
  115. package/dist/providers/openai-responses.d.ts.map +1 -0
  116. package/dist/providers/openai-responses.js +218 -0
  117. package/dist/providers/openai-responses.js.map +1 -0
  118. package/dist/providers/register-builtins.d.ts +35 -0
  119. package/dist/providers/register-builtins.d.ts.map +1 -0
  120. package/dist/providers/register-builtins.js +243 -0
  121. package/dist/providers/register-builtins.js.map +1 -0
  122. package/dist/providers/simple-options.d.ts +8 -0
  123. package/dist/providers/simple-options.d.ts.map +1 -0
  124. package/dist/providers/simple-options.js +40 -0
  125. package/dist/providers/simple-options.js.map +1 -0
  126. package/dist/providers/transform-messages.d.ts +8 -0
  127. package/dist/providers/transform-messages.d.ts.map +1 -0
  128. package/dist/providers/transform-messages.js +174 -0
  129. package/dist/providers/transform-messages.js.map +1 -0
  130. package/dist/session-resources.d.ts +4 -0
  131. package/dist/session-resources.d.ts.map +1 -0
  132. package/dist/session-resources.js +22 -0
  133. package/dist/session-resources.js.map +1 -0
  134. package/dist/stream.d.ts +8 -0
  135. package/dist/stream.d.ts.map +1 -0
  136. package/dist/stream.js +27 -0
  137. package/dist/stream.js.map +1 -0
  138. package/dist/types.d.ts +411 -0
  139. package/dist/types.d.ts.map +1 -0
  140. package/dist/types.js +2 -0
  141. package/dist/types.js.map +1 -0
  142. package/dist/utils/diagnostics.d.ts +19 -0
  143. package/dist/utils/diagnostics.d.ts.map +1 -0
  144. package/dist/utils/diagnostics.js +25 -0
  145. package/dist/utils/diagnostics.js.map +1 -0
  146. package/dist/utils/event-stream.d.ts +21 -0
  147. package/dist/utils/event-stream.d.ts.map +1 -0
  148. package/dist/utils/event-stream.js +78 -0
  149. package/dist/utils/event-stream.js.map +1 -0
  150. package/dist/utils/hash.d.ts +2 -0
  151. package/dist/utils/hash.d.ts.map +1 -0
  152. package/dist/utils/hash.js +13 -0
  153. package/dist/utils/hash.js.map +1 -0
  154. package/dist/utils/headers.d.ts +2 -0
  155. package/dist/utils/headers.d.ts.map +1 -0
  156. package/dist/utils/headers.js +8 -0
  157. package/dist/utils/headers.js.map +1 -0
  158. package/dist/utils/json-parse.d.ts +16 -0
  159. package/dist/utils/json-parse.d.ts.map +1 -0
  160. package/dist/utils/json-parse.js +113 -0
  161. package/dist/utils/json-parse.js.map +1 -0
  162. package/dist/utils/oauth/anthropic.d.ts +25 -0
  163. package/dist/utils/oauth/anthropic.d.ts.map +1 -0
  164. package/dist/utils/oauth/anthropic.js +335 -0
  165. package/dist/utils/oauth/anthropic.js.map +1 -0
  166. package/dist/utils/oauth/github-copilot.d.ts +24 -0
  167. package/dist/utils/oauth/github-copilot.d.ts.map +1 -0
  168. package/dist/utils/oauth/github-copilot.js +279 -0
  169. package/dist/utils/oauth/github-copilot.js.map +1 -0
  170. package/dist/utils/oauth/index.d.ts +45 -0
  171. package/dist/utils/oauth/index.d.ts.map +1 -0
  172. package/dist/utils/oauth/index.js +99 -0
  173. package/dist/utils/oauth/index.js.map +1 -0
  174. package/dist/utils/oauth/oauth-page.d.ts +3 -0
  175. package/dist/utils/oauth/oauth-page.d.ts.map +1 -0
  176. package/dist/utils/oauth/oauth-page.js +134 -0
  177. package/dist/utils/oauth/oauth-page.js.map +1 -0
  178. package/dist/utils/oauth/openai-codex.d.ts +34 -0
  179. package/dist/utils/oauth/openai-codex.d.ts.map +1 -0
  180. package/dist/utils/oauth/openai-codex.js +385 -0
  181. package/dist/utils/oauth/openai-codex.js.map +1 -0
  182. package/dist/utils/oauth/pkce.d.ts +13 -0
  183. package/dist/utils/oauth/pkce.d.ts.map +1 -0
  184. package/dist/utils/oauth/pkce.js +26 -0
  185. package/dist/utils/oauth/pkce.js.map +1 -0
  186. package/dist/utils/oauth/types.d.ts +57 -0
  187. package/dist/utils/oauth/types.d.ts.map +1 -0
  188. package/dist/utils/oauth/types.js +2 -0
  189. package/dist/utils/oauth/types.js.map +1 -0
  190. package/dist/utils/overflow.d.ts +55 -0
  191. package/dist/utils/overflow.d.ts.map +1 -0
  192. package/dist/utils/overflow.js +144 -0
  193. package/dist/utils/overflow.js.map +1 -0
  194. package/dist/utils/sanitize-unicode.d.ts +22 -0
  195. package/dist/utils/sanitize-unicode.d.ts.map +1 -0
  196. package/dist/utils/sanitize-unicode.js +26 -0
  197. package/dist/utils/sanitize-unicode.js.map +1 -0
  198. package/dist/utils/stream-failure.d.ts +55 -0
  199. package/dist/utils/stream-failure.d.ts.map +1 -0
  200. package/dist/utils/stream-failure.js +179 -0
  201. package/dist/utils/stream-failure.js.map +1 -0
  202. package/dist/utils/typebox-helpers.d.ts +17 -0
  203. package/dist/utils/typebox-helpers.d.ts.map +1 -0
  204. package/dist/utils/typebox-helpers.js +21 -0
  205. package/dist/utils/typebox-helpers.js.map +1 -0
  206. package/dist/utils/validation.d.ts +18 -0
  207. package/dist/utils/validation.d.ts.map +1 -0
  208. package/dist/utils/validation.js +281 -0
  209. package/dist/utils/validation.js.map +1 -0
  210. package/package.json +113 -0
@@ -0,0 +1,30 @@
1
+ export type { Static, TSchema } from "typebox";
2
+ export { Type } from "typebox";
3
+ export * from "./api-registry.js";
4
+ export * from "./env-api-keys.js";
5
+ export * from "./log.js";
6
+ export * from "./models.js";
7
+ export type { BedrockOptions, BedrockThinkingDisplay } from "./providers/amazon-bedrock.js";
8
+ export type { AnthropicEffort, AnthropicOptions, AnthropicThinkingDisplay } from "./providers/anthropic.js";
9
+ export type { AzureOpenAIResponsesOptions } from "./providers/azure-openai-responses.js";
10
+ export * from "./providers/faux.js";
11
+ export type { GoogleOptions } from "./providers/google.js";
12
+ export type { GoogleThinkingLevel } from "./providers/google-shared.js";
13
+ export type { GoogleVertexOptions } from "./providers/google-vertex.js";
14
+ export type { MistralOptions } from "./providers/mistral.js";
15
+ export type { OpenAICodexResponsesOptions, OpenAICodexWebSocketDebugStats, } from "./providers/openai-codex-responses.js";
16
+ export type { OpenAICompletionsOptions } from "./providers/openai-completions.js";
17
+ export type { OpenAIResponsesOptions } from "./providers/openai-responses.js";
18
+ export * from "./providers/register-builtins.js";
19
+ export * from "./session-resources.js";
20
+ export * from "./stream.js";
21
+ export * from "./types.js";
22
+ export * from "./utils/diagnostics.js";
23
+ export * from "./utils/event-stream.js";
24
+ export * from "./utils/json-parse.js";
25
+ export type { OAuthAuthInfo, OAuthCredentials, OAuthLoginCallbacks, OAuthPrompt, OAuthProvider, OAuthProviderId, OAuthProviderInfo, OAuthProviderInterface, OAuthSelectOption, OAuthSelectPrompt, } from "./utils/oauth/types.js";
26
+ export * from "./utils/overflow.js";
27
+ export * from "./utils/stream-failure.js";
28
+ export * from "./utils/typebox-helpers.js";
29
+ export * from "./utils/validation.js";
30
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAE/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,YAAY,EAAE,cAAc,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AAC5F,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AAC5G,YAAY,EAAE,2BAA2B,EAAE,MAAM,uCAAuC,CAAC;AACzF,cAAc,qBAAqB,CAAC;AACpC,YAAY,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,YAAY,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AACxE,YAAY,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AACxE,YAAY,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC7D,YAAY,EACX,2BAA2B,EAC3B,8BAA8B,GAC9B,MAAM,uCAAuC,CAAC;AAC/C,YAAY,EAAE,wBAAwB,EAAE,MAAM,mCAAmC,CAAC;AAClF,YAAY,EAAE,sBAAsB,EAAE,MAAM,iCAAiC,CAAC;AAC9E,cAAc,kCAAkC,CAAC;AACjD,cAAc,wBAAwB,CAAC;AACvC,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,uBAAuB,CAAC;AACtC,YAAY,EACX,aAAa,EACb,gBAAgB,EAChB,mBAAmB,EACnB,WAAW,EACX,aAAa,EACb,eAAe,EACf,iBAAiB,EACjB,sBAAsB,EACtB,iBAAiB,EACjB,iBAAiB,GACjB,MAAM,wBAAwB,CAAC;AAChC,cAAc,qBAAqB,CAAC;AACpC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,uBAAuB,CAAC","sourcesContent":["export type { Static, TSchema } from \"typebox\";\nexport { Type } from \"typebox\";\n\nexport * from \"./api-registry.js\";\nexport * from \"./env-api-keys.js\";\nexport * from \"./log.js\";\nexport * from \"./models.js\";\nexport type { BedrockOptions, BedrockThinkingDisplay } from \"./providers/amazon-bedrock.js\";\nexport type { AnthropicEffort, AnthropicOptions, AnthropicThinkingDisplay } from \"./providers/anthropic.js\";\nexport type { AzureOpenAIResponsesOptions } from \"./providers/azure-openai-responses.js\";\nexport * from \"./providers/faux.js\";\nexport type { GoogleOptions } from \"./providers/google.js\";\nexport type { GoogleThinkingLevel } from \"./providers/google-shared.js\";\nexport type { GoogleVertexOptions } from \"./providers/google-vertex.js\";\nexport type { MistralOptions } from \"./providers/mistral.js\";\nexport type {\n\tOpenAICodexResponsesOptions,\n\tOpenAICodexWebSocketDebugStats,\n} from \"./providers/openai-codex-responses.js\";\nexport type { OpenAICompletionsOptions } from \"./providers/openai-completions.js\";\nexport type { OpenAIResponsesOptions } from \"./providers/openai-responses.js\";\nexport * from \"./providers/register-builtins.js\";\nexport * from \"./session-resources.js\";\nexport * from \"./stream.js\";\nexport * from \"./types.js\";\nexport * from \"./utils/diagnostics.js\";\nexport * from \"./utils/event-stream.js\";\nexport * from \"./utils/json-parse.js\";\nexport type {\n\tOAuthAuthInfo,\n\tOAuthCredentials,\n\tOAuthLoginCallbacks,\n\tOAuthPrompt,\n\tOAuthProvider,\n\tOAuthProviderId,\n\tOAuthProviderInfo,\n\tOAuthProviderInterface,\n\tOAuthSelectOption,\n\tOAuthSelectPrompt,\n} from \"./utils/oauth/types.js\";\nexport * from \"./utils/overflow.js\";\nexport * from \"./utils/stream-failure.js\";\nexport * from \"./utils/typebox-helpers.js\";\nexport * from \"./utils/validation.js\";\n"]}
package/dist/index.js ADDED
@@ -0,0 +1,18 @@
1
+ export { Type } from "typebox";
2
+ export * from "./api-registry.js";
3
+ export * from "./env-api-keys.js";
4
+ export * from "./log.js";
5
+ export * from "./models.js";
6
+ export * from "./providers/faux.js";
7
+ export * from "./providers/register-builtins.js";
8
+ export * from "./session-resources.js";
9
+ export * from "./stream.js";
10
+ export * from "./types.js";
11
+ export * from "./utils/diagnostics.js";
12
+ export * from "./utils/event-stream.js";
13
+ export * from "./utils/json-parse.js";
14
+ export * from "./utils/overflow.js";
15
+ export * from "./utils/stream-failure.js";
16
+ export * from "./utils/typebox-helpers.js";
17
+ export * from "./utils/validation.js";
18
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAE/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAI5B,cAAc,qBAAqB,CAAC;AAWpC,cAAc,kCAAkC,CAAC;AACjD,cAAc,wBAAwB,CAAC;AACvC,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,uBAAuB,CAAC;AAatC,cAAc,qBAAqB,CAAC;AACpC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,4BAA4B,CAAC;AAC3C,cAAc,uBAAuB,CAAC","sourcesContent":["export type { Static, TSchema } from \"typebox\";\nexport { Type } from \"typebox\";\n\nexport * from \"./api-registry.js\";\nexport * from \"./env-api-keys.js\";\nexport * from \"./log.js\";\nexport * from \"./models.js\";\nexport type { BedrockOptions, BedrockThinkingDisplay } from \"./providers/amazon-bedrock.js\";\nexport type { AnthropicEffort, AnthropicOptions, AnthropicThinkingDisplay } from \"./providers/anthropic.js\";\nexport type { AzureOpenAIResponsesOptions } from \"./providers/azure-openai-responses.js\";\nexport * from \"./providers/faux.js\";\nexport type { GoogleOptions } from \"./providers/google.js\";\nexport type { GoogleThinkingLevel } from \"./providers/google-shared.js\";\nexport type { GoogleVertexOptions } from \"./providers/google-vertex.js\";\nexport type { MistralOptions } from \"./providers/mistral.js\";\nexport type {\n\tOpenAICodexResponsesOptions,\n\tOpenAICodexWebSocketDebugStats,\n} from \"./providers/openai-codex-responses.js\";\nexport type { OpenAICompletionsOptions } from \"./providers/openai-completions.js\";\nexport type { OpenAIResponsesOptions } from \"./providers/openai-responses.js\";\nexport * from \"./providers/register-builtins.js\";\nexport * from \"./session-resources.js\";\nexport * from \"./stream.js\";\nexport * from \"./types.js\";\nexport * from \"./utils/diagnostics.js\";\nexport * from \"./utils/event-stream.js\";\nexport * from \"./utils/json-parse.js\";\nexport type {\n\tOAuthAuthInfo,\n\tOAuthCredentials,\n\tOAuthLoginCallbacks,\n\tOAuthPrompt,\n\tOAuthProvider,\n\tOAuthProviderId,\n\tOAuthProviderInfo,\n\tOAuthProviderInterface,\n\tOAuthSelectOption,\n\tOAuthSelectPrompt,\n} from \"./utils/oauth/types.js\";\nexport * from \"./utils/overflow.js\";\nexport * from \"./utils/stream-failure.js\";\nexport * from \"./utils/typebox-helpers.js\";\nexport * from \"./utils/validation.js\";\n"]}
package/dist/log.d.ts ADDED
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Minimal structured logger shared by pi-ai and its consumers. The library
3
+ * never writes files: entries go to an injectable sink (setLogSink). Without
4
+ * a sink, warn/error fall back to console.error and debug/info are dropped.
5
+ * Logging must never throw into the caller.
6
+ */
7
+ export type LogLevel = "debug" | "info" | "warn" | "error";
8
+ export interface LogEntry {
9
+ ts: string;
10
+ level: LogLevel;
11
+ component: string;
12
+ msg: string;
13
+ [field: string]: unknown;
14
+ }
15
+ export type LogSink = (entry: LogEntry) => void;
16
+ export interface Logger {
17
+ debug(msg: string, fields?: Record<string, unknown>): void;
18
+ info(msg: string, fields?: Record<string, unknown>): void;
19
+ warn(msg: string, fields?: Record<string, unknown>): void;
20
+ error(msg: string, fields?: Record<string, unknown>): void;
21
+ }
22
+ /** Install the process-wide log sink. Pass undefined to restore the default. */
23
+ export declare function setLogSink(next: LogSink | undefined): void;
24
+ /** JSON.stringify that never throws: circular refs and BigInts degrade instead of dropping the entry. */
25
+ export declare function stringifyLogEntry(entry: LogEntry): string;
26
+ export declare function getLogger(component: string): Logger;
27
+ //# sourceMappingURL=log.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log.d.ts","sourceRoot":"","sources":["../src/log.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAE3D,MAAM,WAAW,QAAQ;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,QAAQ,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;CACzB;AAED,MAAM,MAAM,OAAO,GAAG,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAC;AAEhD,MAAM,WAAW,MAAM;IACtB,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC3D,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC1D,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC1D,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAC3D;AAID,gFAAgF;AAChF,wBAAgB,UAAU,CAAC,IAAI,EAAE,OAAO,GAAG,SAAS,GAAG,IAAI,CAE1D;AAcD,yGAAyG;AACzG,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,QAAQ,GAAG,MAAM,CAYzD;AAsBD,wBAAgB,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAOnD","sourcesContent":["/**\n * Minimal structured logger shared by pi-ai and its consumers. The library\n * never writes files: entries go to an injectable sink (setLogSink). Without\n * a sink, warn/error fall back to console.error and debug/info are dropped.\n * Logging must never throw into the caller.\n */\n\nexport type LogLevel = \"debug\" | \"info\" | \"warn\" | \"error\";\n\nexport interface LogEntry {\n\tts: string;\n\tlevel: LogLevel;\n\tcomponent: string;\n\tmsg: string;\n\t[field: string]: unknown;\n}\n\nexport type LogSink = (entry: LogEntry) => void;\n\nexport interface Logger {\n\tdebug(msg: string, fields?: Record<string, unknown>): void;\n\tinfo(msg: string, fields?: Record<string, unknown>): void;\n\twarn(msg: string, fields?: Record<string, unknown>): void;\n\terror(msg: string, fields?: Record<string, unknown>): void;\n}\n\nlet sink: LogSink | undefined;\n\n/** Install the process-wide log sink. Pass undefined to restore the default. */\nexport function setLogSink(next: LogSink | undefined): void {\n\tsink = next;\n}\n\nfunction jsonReplacer(): (key: string, value: unknown) => unknown {\n\tconst seen = new WeakSet<object>();\n\treturn (_key, value) => {\n\t\tif (typeof value === \"bigint\") return value.toString();\n\t\tif (typeof value === \"object\" && value !== null) {\n\t\t\tif (seen.has(value)) return \"[Circular]\";\n\t\t\tseen.add(value);\n\t\t}\n\t\treturn value;\n\t};\n}\n\n/** JSON.stringify that never throws: circular refs and BigInts degrade instead of dropping the entry. */\nexport function stringifyLogEntry(entry: LogEntry): string {\n\ttry {\n\t\treturn JSON.stringify(entry, jsonReplacer());\n\t} catch {\n\t\treturn JSON.stringify({\n\t\t\tts: entry.ts,\n\t\t\tlevel: entry.level,\n\t\t\tcomponent: entry.component,\n\t\t\tmsg: String(entry.msg),\n\t\t\tfieldsError: \"unserializable fields dropped\",\n\t\t});\n\t}\n}\n\nfunction emit(level: LogLevel, component: string, msg: string, fields?: Record<string, unknown>): void {\n\ttry {\n\t\t// Reserved keys win over caller fields so entries can't be misclassified.\n\t\tconst entry: LogEntry = { ...fields, ts: new Date().toISOString(), level, component, msg };\n\t\ttry {\n\t\t\tif (sink) {\n\t\t\t\tsink(entry);\n\t\t\t\treturn;\n\t\t\t}\n\t\t} catch {\n\t\t\t// Broken sink: fall through to the console fallback.\n\t\t}\n\t\tif (level === \"warn\" || level === \"error\") {\n\t\t\tconsole.error(stringifyLogEntry(entry));\n\t\t}\n\t} catch {\n\t\t// Diagnostics must never break the operation being logged.\n\t}\n}\n\nexport function getLogger(component: string): Logger {\n\treturn {\n\t\tdebug: (msg, fields) => emit(\"debug\", component, msg, fields),\n\t\tinfo: (msg, fields) => emit(\"info\", component, msg, fields),\n\t\twarn: (msg, fields) => emit(\"warn\", component, msg, fields),\n\t\terror: (msg, fields) => emit(\"error\", component, msg, fields),\n\t};\n}\n"]}
package/dist/log.js ADDED
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Minimal structured logger shared by pi-ai and its consumers. The library
3
+ * never writes files: entries go to an injectable sink (setLogSink). Without
4
+ * a sink, warn/error fall back to console.error and debug/info are dropped.
5
+ * Logging must never throw into the caller.
6
+ */
7
+ let sink;
8
+ /** Install the process-wide log sink. Pass undefined to restore the default. */
9
+ export function setLogSink(next) {
10
+ sink = next;
11
+ }
12
+ function jsonReplacer() {
13
+ const seen = new WeakSet();
14
+ return (_key, value) => {
15
+ if (typeof value === "bigint")
16
+ return value.toString();
17
+ if (typeof value === "object" && value !== null) {
18
+ if (seen.has(value))
19
+ return "[Circular]";
20
+ seen.add(value);
21
+ }
22
+ return value;
23
+ };
24
+ }
25
+ /** JSON.stringify that never throws: circular refs and BigInts degrade instead of dropping the entry. */
26
+ export function stringifyLogEntry(entry) {
27
+ try {
28
+ return JSON.stringify(entry, jsonReplacer());
29
+ }
30
+ catch {
31
+ return JSON.stringify({
32
+ ts: entry.ts,
33
+ level: entry.level,
34
+ component: entry.component,
35
+ msg: String(entry.msg),
36
+ fieldsError: "unserializable fields dropped",
37
+ });
38
+ }
39
+ }
40
+ function emit(level, component, msg, fields) {
41
+ try {
42
+ // Reserved keys win over caller fields so entries can't be misclassified.
43
+ const entry = { ...fields, ts: new Date().toISOString(), level, component, msg };
44
+ try {
45
+ if (sink) {
46
+ sink(entry);
47
+ return;
48
+ }
49
+ }
50
+ catch {
51
+ // Broken sink: fall through to the console fallback.
52
+ }
53
+ if (level === "warn" || level === "error") {
54
+ console.error(stringifyLogEntry(entry));
55
+ }
56
+ }
57
+ catch {
58
+ // Diagnostics must never break the operation being logged.
59
+ }
60
+ }
61
+ export function getLogger(component) {
62
+ return {
63
+ debug: (msg, fields) => emit("debug", component, msg, fields),
64
+ info: (msg, fields) => emit("info", component, msg, fields),
65
+ warn: (msg, fields) => emit("warn", component, msg, fields),
66
+ error: (msg, fields) => emit("error", component, msg, fields),
67
+ };
68
+ }
69
+ //# sourceMappingURL=log.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log.js","sourceRoot":"","sources":["../src/log.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAqBH,IAAI,IAAyB,CAAC;AAE9B,gFAAgF;AAChF,MAAM,UAAU,UAAU,CAAC,IAAyB,EAAQ;IAC3D,IAAI,GAAG,IAAI,CAAC;AAAA,CACZ;AAED,SAAS,YAAY,GAA6C;IACjE,MAAM,IAAI,GAAG,IAAI,OAAO,EAAU,CAAC;IACnC,OAAO,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QACvB,IAAI,OAAO,KAAK,KAAK,QAAQ;YAAE,OAAO,KAAK,CAAC,QAAQ,EAAE,CAAC;QACvD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;YACjD,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC;gBAAE,OAAO,YAAY,CAAC;YACzC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACjB,CAAC;QACD,OAAO,KAAK,CAAC;IAAA,CACb,CAAC;AAAA,CACF;AAED,yGAAyG;AACzG,MAAM,UAAU,iBAAiB,CAAC,KAAe,EAAU;IAC1D,IAAI,CAAC;QACJ,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,YAAY,EAAE,CAAC,CAAC;IAC9C,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,IAAI,CAAC,SAAS,CAAC;YACrB,EAAE,EAAE,KAAK,CAAC,EAAE;YACZ,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC;YACtB,WAAW,EAAE,+BAA+B;SAC5C,CAAC,CAAC;IACJ,CAAC;AAAA,CACD;AAED,SAAS,IAAI,CAAC,KAAe,EAAE,SAAiB,EAAE,GAAW,EAAE,MAAgC,EAAQ;IACtG,IAAI,CAAC;QACJ,0EAA0E;QAC1E,MAAM,KAAK,GAAa,EAAE,GAAG,MAAM,EAAE,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC;QAC3F,IAAI,CAAC;YACJ,IAAI,IAAI,EAAE,CAAC;gBACV,IAAI,CAAC,KAAK,CAAC,CAAC;gBACZ,OAAO;YACR,CAAC;QACF,CAAC;QAAC,MAAM,CAAC;YACR,qDAAqD;QACtD,CAAC;QACD,IAAI,KAAK,KAAK,MAAM,IAAI,KAAK,KAAK,OAAO,EAAE,CAAC;YAC3C,OAAO,CAAC,KAAK,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC,CAAC;QACzC,CAAC;IACF,CAAC;IAAC,MAAM,CAAC;QACR,2DAA2D;IAC5D,CAAC;AAAA,CACD;AAED,MAAM,UAAU,SAAS,CAAC,SAAiB,EAAU;IACpD,OAAO;QACN,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,CAAC;QAC7D,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,CAAC;QAC3D,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,CAAC;QAC3D,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,CAAC;KAC7D,CAAC;AAAA,CACF","sourcesContent":["/**\n * Minimal structured logger shared by pi-ai and its consumers. The library\n * never writes files: entries go to an injectable sink (setLogSink). Without\n * a sink, warn/error fall back to console.error and debug/info are dropped.\n * Logging must never throw into the caller.\n */\n\nexport type LogLevel = \"debug\" | \"info\" | \"warn\" | \"error\";\n\nexport interface LogEntry {\n\tts: string;\n\tlevel: LogLevel;\n\tcomponent: string;\n\tmsg: string;\n\t[field: string]: unknown;\n}\n\nexport type LogSink = (entry: LogEntry) => void;\n\nexport interface Logger {\n\tdebug(msg: string, fields?: Record<string, unknown>): void;\n\tinfo(msg: string, fields?: Record<string, unknown>): void;\n\twarn(msg: string, fields?: Record<string, unknown>): void;\n\terror(msg: string, fields?: Record<string, unknown>): void;\n}\n\nlet sink: LogSink | undefined;\n\n/** Install the process-wide log sink. Pass undefined to restore the default. */\nexport function setLogSink(next: LogSink | undefined): void {\n\tsink = next;\n}\n\nfunction jsonReplacer(): (key: string, value: unknown) => unknown {\n\tconst seen = new WeakSet<object>();\n\treturn (_key, value) => {\n\t\tif (typeof value === \"bigint\") return value.toString();\n\t\tif (typeof value === \"object\" && value !== null) {\n\t\t\tif (seen.has(value)) return \"[Circular]\";\n\t\t\tseen.add(value);\n\t\t}\n\t\treturn value;\n\t};\n}\n\n/** JSON.stringify that never throws: circular refs and BigInts degrade instead of dropping the entry. */\nexport function stringifyLogEntry(entry: LogEntry): string {\n\ttry {\n\t\treturn JSON.stringify(entry, jsonReplacer());\n\t} catch {\n\t\treturn JSON.stringify({\n\t\t\tts: entry.ts,\n\t\t\tlevel: entry.level,\n\t\t\tcomponent: entry.component,\n\t\t\tmsg: String(entry.msg),\n\t\t\tfieldsError: \"unserializable fields dropped\",\n\t\t});\n\t}\n}\n\nfunction emit(level: LogLevel, component: string, msg: string, fields?: Record<string, unknown>): void {\n\ttry {\n\t\t// Reserved keys win over caller fields so entries can't be misclassified.\n\t\tconst entry: LogEntry = { ...fields, ts: new Date().toISOString(), level, component, msg };\n\t\ttry {\n\t\t\tif (sink) {\n\t\t\t\tsink(entry);\n\t\t\t\treturn;\n\t\t\t}\n\t\t} catch {\n\t\t\t// Broken sink: fall through to the console fallback.\n\t\t}\n\t\tif (level === \"warn\" || level === \"error\") {\n\t\t\tconsole.error(stringifyLogEntry(entry));\n\t\t}\n\t} catch {\n\t\t// Diagnostics must never break the operation being logged.\n\t}\n}\n\nexport function getLogger(component: string): Logger {\n\treturn {\n\t\tdebug: (msg, fields) => emit(\"debug\", component, msg, fields),\n\t\tinfo: (msg, fields) => emit(\"info\", component, msg, fields),\n\t\twarn: (msg, fields) => emit(\"warn\", component, msg, fields),\n\t\terror: (msg, fields) => emit(\"error\", component, msg, fields),\n\t};\n}\n"]}
@@ -0,0 +1,19 @@
1
+ import { type McpOAuthConfig } from "./oauth.js";
2
+ export interface McpCatalogEntry {
3
+ /** Matches the skill package import name and the auth.json key `mcp:<server>`. */
4
+ server: string;
5
+ label: string;
6
+ url: string;
7
+ oauth?: Omit<McpOAuthConfig, "server" | "url"> & {
8
+ kind: "oauth";
9
+ };
10
+ }
11
+ export declare const BUILTIN_MCP_CATALOG: readonly McpCatalogEntry[];
12
+ export declare function getCatalogEntry(server: string): McpCatalogEntry | undefined;
13
+ /**
14
+ * Register the built-in catalog's OAuth providers. Idempotent. Must be called
15
+ * after any resetOAuthProviders() (e.g. ModelRegistry.refresh) since reset drops
16
+ * everything but the model-provider built-ins.
17
+ */
18
+ export declare function registerBuiltinMcpOAuthProviders(): void;
19
+ //# sourceMappingURL=catalog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../../src/mcp/catalog.ts"],"names":[],"mappings":"AAGA,OAAO,EAA0B,KAAK,cAAc,EAAE,MAAM,YAAY,CAAC;AAEzE,MAAM,WAAW,eAAe;IAC/B,kFAAkF;IAClF,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,IAAI,CAAC,cAAc,EAAE,QAAQ,GAAG,KAAK,CAAC,GAAG;QAAE,IAAI,EAAE,OAAO,CAAA;KAAE,CAAC;CACnE;AAED,eAAO,MAAM,mBAAmB,EAAE,SAAS,eAAe,EAazD,CAAC;AAEF,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS,CAE3E;AAED;;;;GAIG;AACH,wBAAgB,gCAAgC,IAAI,IAAI,CAevD","sourcesContent":["// Built-in MCP integrations we ship a skill package for. User servers go in the `mcpServers` setting instead.\n\nimport { getOAuthProvider, registerOAuthProvider } from \"../utils/oauth/index.js\";\nimport { createMcpOAuthProvider, type McpOAuthConfig } from \"./oauth.js\";\n\nexport interface McpCatalogEntry {\n\t/** Matches the skill package import name and the auth.json key `mcp:<server>`. */\n\tserver: string;\n\tlabel: string;\n\turl: string;\n\toauth?: Omit<McpOAuthConfig, \"server\" | \"url\"> & { kind: \"oauth\" };\n}\n\nexport const BUILTIN_MCP_CATALOG: readonly McpCatalogEntry[] = [\n\t{\n\t\tserver: \"linear\",\n\t\tlabel: \"Linear\",\n\t\turl: \"https://mcp.linear.app/mcp\",\n\t\toauth: { kind: \"oauth\", label: \"Linear\" },\n\t},\n\t{\n\t\tserver: \"notion\",\n\t\tlabel: \"Notion\",\n\t\turl: \"https://mcp.notion.com/mcp\",\n\t\toauth: { kind: \"oauth\", label: \"Notion\" },\n\t},\n];\n\nexport function getCatalogEntry(server: string): McpCatalogEntry | undefined {\n\treturn BUILTIN_MCP_CATALOG.find((entry) => entry.server === server);\n}\n\n/**\n * Register the built-in catalog's OAuth providers. Idempotent. Must be called\n * after any resetOAuthProviders() (e.g. ModelRegistry.refresh) since reset drops\n * everything but the model-provider built-ins.\n */\nexport function registerBuiltinMcpOAuthProviders(): void {\n\tfor (const entry of BUILTIN_MCP_CATALOG) {\n\t\tif (entry.oauth?.kind !== \"oauth\") continue;\n\t\tconst id = `mcp:${entry.server}`;\n\t\tif (getOAuthProvider(id)) continue;\n\t\tregisterOAuthProvider(\n\t\t\tcreateMcpOAuthProvider({\n\t\t\t\tserver: entry.server,\n\t\t\t\tlabel: entry.label,\n\t\t\t\turl: entry.url,\n\t\t\t\tscopes: entry.oauth.scopes,\n\t\t\t\tclientId: entry.oauth.clientId,\n\t\t\t}),\n\t\t);\n\t}\n}\n"]}
@@ -0,0 +1,42 @@
1
+ // Built-in MCP integrations we ship a skill package for. User servers go in the `mcpServers` setting instead.
2
+ import { getOAuthProvider, registerOAuthProvider } from "../utils/oauth/index.js";
3
+ import { createMcpOAuthProvider } from "./oauth.js";
4
+ export const BUILTIN_MCP_CATALOG = [
5
+ {
6
+ server: "linear",
7
+ label: "Linear",
8
+ url: "https://mcp.linear.app/mcp",
9
+ oauth: { kind: "oauth", label: "Linear" },
10
+ },
11
+ {
12
+ server: "notion",
13
+ label: "Notion",
14
+ url: "https://mcp.notion.com/mcp",
15
+ oauth: { kind: "oauth", label: "Notion" },
16
+ },
17
+ ];
18
+ export function getCatalogEntry(server) {
19
+ return BUILTIN_MCP_CATALOG.find((entry) => entry.server === server);
20
+ }
21
+ /**
22
+ * Register the built-in catalog's OAuth providers. Idempotent. Must be called
23
+ * after any resetOAuthProviders() (e.g. ModelRegistry.refresh) since reset drops
24
+ * everything but the model-provider built-ins.
25
+ */
26
+ export function registerBuiltinMcpOAuthProviders() {
27
+ for (const entry of BUILTIN_MCP_CATALOG) {
28
+ if (entry.oauth?.kind !== "oauth")
29
+ continue;
30
+ const id = `mcp:${entry.server}`;
31
+ if (getOAuthProvider(id))
32
+ continue;
33
+ registerOAuthProvider(createMcpOAuthProvider({
34
+ server: entry.server,
35
+ label: entry.label,
36
+ url: entry.url,
37
+ scopes: entry.oauth.scopes,
38
+ clientId: entry.oauth.clientId,
39
+ }));
40
+ }
41
+ }
42
+ //# sourceMappingURL=catalog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog.js","sourceRoot":"","sources":["../../src/mcp/catalog.ts"],"names":[],"mappings":"AAAA,8GAA8G;AAE9G,OAAO,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAClF,OAAO,EAAE,sBAAsB,EAAuB,MAAM,YAAY,CAAC;AAUzE,MAAM,CAAC,MAAM,mBAAmB,GAA+B;IAC9D;QACC,MAAM,EAAE,QAAQ;QAChB,KAAK,EAAE,QAAQ;QACf,GAAG,EAAE,4BAA4B;QACjC,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE;KACzC;IACD;QACC,MAAM,EAAE,QAAQ;QAChB,KAAK,EAAE,QAAQ;QACf,GAAG,EAAE,4BAA4B;QACjC,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE;KACzC;CACD,CAAC;AAEF,MAAM,UAAU,eAAe,CAAC,MAAc,EAA+B;IAC5E,OAAO,mBAAmB,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC;AAAA,CACpE;AAED;;;;GAIG;AACH,MAAM,UAAU,gCAAgC,GAAS;IACxD,KAAK,MAAM,KAAK,IAAI,mBAAmB,EAAE,CAAC;QACzC,IAAI,KAAK,CAAC,KAAK,EAAE,IAAI,KAAK,OAAO;YAAE,SAAS;QAC5C,MAAM,EAAE,GAAG,OAAO,KAAK,CAAC,MAAM,EAAE,CAAC;QACjC,IAAI,gBAAgB,CAAC,EAAE,CAAC;YAAE,SAAS;QACnC,qBAAqB,CACpB,sBAAsB,CAAC;YACtB,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,GAAG,EAAE,KAAK,CAAC,GAAG;YACd,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC,MAAM;YAC1B,QAAQ,EAAE,KAAK,CAAC,KAAK,CAAC,QAAQ;SAC9B,CAAC,CACF,CAAC;IACH,CAAC;AAAA,CACD","sourcesContent":["// Built-in MCP integrations we ship a skill package for. User servers go in the `mcpServers` setting instead.\n\nimport { getOAuthProvider, registerOAuthProvider } from \"../utils/oauth/index.js\";\nimport { createMcpOAuthProvider, type McpOAuthConfig } from \"./oauth.js\";\n\nexport interface McpCatalogEntry {\n\t/** Matches the skill package import name and the auth.json key `mcp:<server>`. */\n\tserver: string;\n\tlabel: string;\n\turl: string;\n\toauth?: Omit<McpOAuthConfig, \"server\" | \"url\"> & { kind: \"oauth\" };\n}\n\nexport const BUILTIN_MCP_CATALOG: readonly McpCatalogEntry[] = [\n\t{\n\t\tserver: \"linear\",\n\t\tlabel: \"Linear\",\n\t\turl: \"https://mcp.linear.app/mcp\",\n\t\toauth: { kind: \"oauth\", label: \"Linear\" },\n\t},\n\t{\n\t\tserver: \"notion\",\n\t\tlabel: \"Notion\",\n\t\turl: \"https://mcp.notion.com/mcp\",\n\t\toauth: { kind: \"oauth\", label: \"Notion\" },\n\t},\n];\n\nexport function getCatalogEntry(server: string): McpCatalogEntry | undefined {\n\treturn BUILTIN_MCP_CATALOG.find((entry) => entry.server === server);\n}\n\n/**\n * Register the built-in catalog's OAuth providers. Idempotent. Must be called\n * after any resetOAuthProviders() (e.g. ModelRegistry.refresh) since reset drops\n * everything but the model-provider built-ins.\n */\nexport function registerBuiltinMcpOAuthProviders(): void {\n\tfor (const entry of BUILTIN_MCP_CATALOG) {\n\t\tif (entry.oauth?.kind !== \"oauth\") continue;\n\t\tconst id = `mcp:${entry.server}`;\n\t\tif (getOAuthProvider(id)) continue;\n\t\tregisterOAuthProvider(\n\t\t\tcreateMcpOAuthProvider({\n\t\t\t\tserver: entry.server,\n\t\t\t\tlabel: entry.label,\n\t\t\t\turl: entry.url,\n\t\t\t\tscopes: entry.oauth.scopes,\n\t\t\t\tclientId: entry.oauth.clientId,\n\t\t\t}),\n\t\t);\n\t}\n}\n"]}
@@ -0,0 +1,5 @@
1
+ export type { McpCatalogEntry } from "./catalog.js";
2
+ export { BUILTIN_MCP_CATALOG, getCatalogEntry, registerBuiltinMcpOAuthProviders } from "./catalog.js";
3
+ export type { McpOAuthConfig } from "./oauth.js";
4
+ export { createMcpOAuthProvider } from "./oauth.js";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/mcp/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,gCAAgC,EAAE,MAAM,cAAc,CAAC;AACtG,YAAY,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC","sourcesContent":["export type { McpCatalogEntry } from \"./catalog.js\";\nexport { BUILTIN_MCP_CATALOG, getCatalogEntry, registerBuiltinMcpOAuthProviders } from \"./catalog.js\";\nexport type { McpOAuthConfig } from \"./oauth.js\";\nexport { createMcpOAuthProvider } from \"./oauth.js\";\n"]}
@@ -0,0 +1,3 @@
1
+ export { BUILTIN_MCP_CATALOG, getCatalogEntry, registerBuiltinMcpOAuthProviders } from "./catalog.js";
2
+ export { createMcpOAuthProvider } from "./oauth.js";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/mcp/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,gCAAgC,EAAE,MAAM,cAAc,CAAC;AAEtG,OAAO,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC","sourcesContent":["export type { McpCatalogEntry } from \"./catalog.js\";\nexport { BUILTIN_MCP_CATALOG, getCatalogEntry, registerBuiltinMcpOAuthProviders } from \"./catalog.js\";\nexport type { McpOAuthConfig } from \"./oauth.js\";\nexport { createMcpOAuthProvider } from \"./oauth.js\";\n"]}
@@ -0,0 +1,15 @@
1
+ import type { OAuthProviderInterface } from "../utils/oauth/types.js";
2
+ export interface McpOAuthConfig {
3
+ /** MCP server name; provider id becomes `mcp:<server>`. */
4
+ server: string;
5
+ /** Human-readable label shown in OAuth UI; defaults to `server`. */
6
+ label?: string;
7
+ /** MCP resource URL used for protected-resource and authorization-server discovery. */
8
+ url: string;
9
+ /** Pre-registered client id (servers without DCR, e.g. Slack). */
10
+ clientId?: string;
11
+ /** Requested OAuth scopes; defaults to the server's advertised scopes. */
12
+ scopes?: string;
13
+ }
14
+ export declare function createMcpOAuthProvider(config: McpOAuthConfig): OAuthProviderInterface;
15
+ //# sourceMappingURL=oauth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth.d.ts","sourceRoot":"","sources":["../../src/mcp/oauth.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAyC,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AAgC7G,MAAM,WAAW,cAAc;IAC9B,2DAA2D;IAC3D,MAAM,EAAE,MAAM,CAAC;IACf,oEAAoE;IACpE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uFAAuF;IACvF,GAAG,EAAE,MAAM,CAAC;IACZ,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0EAA0E;IAC1E,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAwWD,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,cAAc,GAAG,sBAAsB,CAgLrF","sourcesContent":["import type { Server } from \"node:http\";\nimport { oauthErrorHtml, oauthSuccessHtml } from \"../utils/oauth/oauth-page.js\";\nimport { generatePKCE } from \"../utils/oauth/pkce.js\";\nimport type { OAuthCredentials, OAuthLoginCallbacks, OAuthProviderInterface } from \"../utils/oauth/types.js\";\n\nconst CALLBACK_HOST = process.env.PI_OAUTH_CALLBACK_HOST || \"127.0.0.1\";\n// A range (not one port) so a leaked/concurrent login can't wedge all logins with EADDRINUSE.\n// Distinct from the Anthropic callback port (53692). All candidates are registered as redirect URIs.\nconst CALLBACK_PORT_BASE = Number(process.env.PI_MCP_OAUTH_CALLBACK_PORT || 53700);\nconst CALLBACK_PORT_COUNT = 10;\nconst CALLBACK_PATH = \"/callback\";\nconst CALLBACK_PORTS = Array.from({ length: CALLBACK_PORT_COUNT }, (_, i) => CALLBACK_PORT_BASE + i);\nconst redirectUriFor = (port: number) => `http://localhost:${port}${CALLBACK_PATH}`;\nconst ALL_REDIRECT_URIS = CALLBACK_PORTS.map(redirectUriFor);\nconst TOKEN_EXPIRY_BUFFER_MS = 5 * 60 * 1000;\n\ninterface AuthServerMetadata {\n\tissuer: string;\n\tauthorization_endpoint: string;\n\ttoken_endpoint: string;\n\tregistration_endpoint?: string;\n\tscopes_supported?: string[];\n}\n\ninterface ProtectedResourceMetadata {\n\tresource: string;\n\tauthorization_servers: string[];\n}\n\ninterface Discovery {\n\tmetadata: AuthServerMetadata;\n\tresource?: string;\n\tissuer?: string;\n}\n\nexport interface McpOAuthConfig {\n\t/** MCP server name; provider id becomes `mcp:<server>`. */\n\tserver: string;\n\t/** Human-readable label shown in OAuth UI; defaults to `server`. */\n\tlabel?: string;\n\t/** MCP resource URL used for protected-resource and authorization-server discovery. */\n\turl: string;\n\t/** Pre-registered client id (servers without DCR, e.g. Slack). */\n\tclientId?: string;\n\t/** Requested OAuth scopes; defaults to the server's advertised scopes. */\n\tscopes?: string;\n}\n\ninterface McpCredentials extends OAuthCredentials {\n\ttokenEndpoint?: string;\n\tclientId?: string;\n\t/** MCP endpoint the token was issued for; consumers refuse to send it elsewhere. */\n\tendpoint?: string;\n\t/** RFC 9728 resource indicator. Its presence marks a PRM-based login. */\n\tresource?: string;\n\t/** RFC 8414/OIDC issuer selected by the protected-resource metadata. */\n\tissuer?: string;\n}\n\nfunction validatedHttpsUrl(value: string, name: string): URL {\n\tlet url: URL;\n\ttry {\n\t\turl = new URL(value);\n\t} catch {\n\t\tthrow new Error(`${name} must be an absolute HTTPS URL`);\n\t}\n\tif (url.protocol !== \"https:\" || url.username || url.password || url.hash) {\n\t\tthrow new Error(`${name} must be an absolute HTTPS URL without credentials or a fragment`);\n\t}\n\treturn url;\n}\n\nfunction canonicalResource(url: URL): string {\n\tif (url.pathname === \"/\" && !url.search) return url.origin;\n\treturn `${url.origin}${url.pathname}${url.search}`;\n}\n\nfunction authorizationServerMetadataUrls(issuer: string): string[] {\n\tconst url = validatedHttpsUrl(issuer, \"Authorization server issuer\");\n\tif (url.search) throw new Error(\"Authorization server issuer must not contain a query string\");\n\tconst path = url.pathname === \"/\" ? \"\" : url.pathname.replace(/\\/$/, \"\");\n\treturn [\n\t\tnew URL(`/.well-known/oauth-authorization-server${path}`, url.origin).toString(),\n\t\tnew URL(`${path}/.well-known/openid-configuration`, url.origin).toString(),\n\t];\n}\n\nasync function fetchResponse(url: string, init?: RequestInit): Promise<Response> {\n\treturn fetch(url, { ...init, redirect: \"error\" });\n}\n\nasync function fetchJson(url: string, init?: RequestInit): Promise<unknown> {\n\tconst res = await fetchResponse(url, init);\n\tif (!res.ok) {\n\t\tthrow new Error(`${init?.method ?? \"GET\"} ${url} failed: ${res.status}`);\n\t}\n\treturn res.json();\n}\n\nfunction authorizationServerMetadata(value: unknown, issuer: string, requireExactIssuer: boolean): AuthServerMetadata {\n\tif (!value || typeof value !== \"object\") throw new Error(`Authorization server metadata for ${issuer} is invalid`);\n\tconst metadata = value as Partial<AuthServerMetadata>;\n\tif (typeof metadata.issuer !== \"string\") {\n\t\tthrow new Error(`Authorization server metadata for ${issuer} is missing its issuer`);\n\t}\n\tif (requireExactIssuer) {\n\t\tif (metadata.issuer !== issuer) {\n\t\t\tthrow new Error(`Authorization server metadata issuer does not exactly match ${issuer}`);\n\t\t}\n\t} else {\n\t\tconst advertisedIssuer = validatedHttpsUrl(metadata.issuer, \"Authorization server metadata issuer\");\n\t\tif (advertisedIssuer.origin !== new URL(issuer).origin || advertisedIssuer.search) {\n\t\t\tthrow new Error(`Origin authorization server metadata issuer must stay on ${new URL(issuer).origin}`);\n\t\t}\n\t}\n\tif (typeof metadata.authorization_endpoint !== \"string\" || typeof metadata.token_endpoint !== \"string\") {\n\t\tthrow new Error(`Authorization server metadata for ${issuer} is missing required endpoints`);\n\t}\n\tvalidatedHttpsUrl(metadata.authorization_endpoint, \"Authorization endpoint\");\n\tvalidatedHttpsUrl(metadata.token_endpoint, \"Token endpoint\");\n\tif (metadata.registration_endpoint) validatedHttpsUrl(metadata.registration_endpoint, \"Registration endpoint\");\n\treturn metadata as AuthServerMetadata;\n}\n\nasync function jsonMetadata(response: Response, url: string): Promise<unknown> {\n\tif (response.status !== 200) throw new Error(`GET ${url} failed: ${response.status}`);\n\tconst contentType = response.headers.get(\"content-type\")?.split(\";\", 1)[0].trim().toLowerCase();\n\tif (contentType !== \"application/json\") throw new Error(`GET ${url} did not return application/json`);\n\treturn response.json();\n}\n\nasync function discoverAuthorizationServer(issuer: string, requireExactIssuer: boolean): Promise<AuthServerMetadata> {\n\tconst candidates = authorizationServerMetadataUrls(issuer);\n\tlet lastError: unknown;\n\tfor (const candidate of candidates) {\n\t\ttry {\n\t\t\tconst response = await fetchResponse(candidate);\n\t\t\tif (response.status === 404) continue;\n\t\t\treturn authorizationServerMetadata(await jsonMetadata(response, candidate), issuer, requireExactIssuer);\n\t\t} catch (error) {\n\t\t\tlastError = error;\n\t\t}\n\t}\n\tthrow new Error(\n\t\t`Could not discover OAuth metadata for ${issuer}. Tried ${candidates.join(\", \")}. Last error: ${String(lastError)}`,\n\t);\n}\n\n/** Random, URL-safe CSRF `state` value, independent of the PKCE verifier. */\nfunction randomState(): string {\n\tconst bytes = new Uint8Array(32);\n\tcrypto.getRandomValues(bytes);\n\treturn btoa(String.fromCharCode(...bytes))\n\t\t.replace(/\\+/g, \"-\")\n\t\t.replace(/\\//g, \"_\")\n\t\t.replace(/=/g, \"\");\n}\n\nfunction resourceMetadata(value: unknown, resource: string): ProtectedResourceMetadata {\n\tif (!value || typeof value !== \"object\") throw new Error(\"Protected-resource metadata is invalid\");\n\tconst metadata = value as Partial<ProtectedResourceMetadata>;\n\tif (metadata.resource !== resource)\n\t\tthrow new Error(`Protected-resource metadata resource does not exactly match ${resource}`);\n\tif (!Array.isArray(metadata.authorization_servers) || metadata.authorization_servers.length === 0) {\n\t\tthrow new Error(\"Protected-resource metadata has no authorization_servers\");\n\t}\n\tfor (const issuer of metadata.authorization_servers) {\n\t\tif (typeof issuer !== \"string\")\n\t\t\tthrow new Error(\"Protected-resource metadata has an invalid authorization server\");\n\t\tvalidatedHttpsUrl(issuer, \"Authorization server issuer\");\n\t}\n\treturn metadata as ProtectedResourceMetadata;\n}\n\nfunction resourceMetadataUrl(resource: URL): string {\n\tconst path = resource.pathname === \"/\" ? \"\" : resource.pathname;\n\treturn `${resource.origin}/.well-known/oauth-protected-resource${path}${resource.search}`;\n}\n\nfunction headerResourceMetadata(value: string | null): string | undefined {\n\tconst match = value?.match(/(?:^|[,\\s])resource_metadata\\s*=\\s*\"((?:[^\"\\\\]|\\\\.)*)\"/i);\n\tif (!match) return undefined;\n\treturn match[1].replace(/\\\\(.)/g, \"$1\");\n}\n\nasync function tryProtectedResourceMetadata(url: string): Promise<ProtectedResourceMetadata | undefined> {\n\tconst resource = validatedHttpsUrl(url, \"MCP endpoint\");\n\tlet headerUrl: string | undefined;\n\ttry {\n\t\t// This probe deliberately has no Authorization header. It must not leak an existing token.\n\t\tconst response = await fetchResponse(resource.toString());\n\t\theaderUrl = headerResourceMetadata(response.headers.get(\"www-authenticate\"));\n\t\tawait response.body?.cancel();\n\t} catch {\n\t\t// The server need not support a GET probe; use the RFC well-known locations below.\n\t}\n\n\tconst candidate = headerUrl\n\t\t? validatedHttpsUrl(headerUrl, \"resource_metadata\").toString()\n\t\t: resourceMetadataUrl(resource);\n\tconst response = await fetchResponse(candidate);\n\tif (response.status === 404 && !headerUrl) return undefined;\n\treturn resourceMetadata(await jsonMetadata(response, candidate), canonicalResource(resource));\n}\n\n/** Discover RFC 9728 protected-resource metadata before the origin-level authorization server fallback. */\nasync function discover(url: string): Promise<Discovery> {\n\tconst protectedResource = await tryProtectedResourceMetadata(url);\n\tif (protectedResource) {\n\t\tconst issuer = protectedResource.authorization_servers[0];\n\t\treturn {\n\t\t\tmetadata: await discoverAuthorizationServer(issuer, true),\n\t\t\tresource: protectedResource.resource,\n\t\t\tissuer,\n\t\t};\n\t}\n\tconst issuer = validatedHttpsUrl(url, \"MCP endpoint\").origin;\n\treturn { metadata: await discoverAuthorizationServer(issuer, false) };\n}\n\nasync function registerClient(registrationEndpoint: string, label: string): Promise<string> {\n\tvalidatedHttpsUrl(registrationEndpoint, \"Registration endpoint\");\n\tconst body = {\n\t\tclient_name: label,\n\t\tredirect_uris: ALL_REDIRECT_URIS,\n\t\tgrant_types: [\"authorization_code\", \"refresh_token\"],\n\t\tresponse_types: [\"code\"],\n\t\ttoken_endpoint_auth_method: \"none\",\n\t};\n\tconst data = (await fetchJson(registrationEndpoint, {\n\t\tmethod: \"POST\",\n\t\theaders: { \"Content-Type\": \"application/json\" },\n\t\tbody: JSON.stringify(body),\n\t})) as { client_id?: unknown };\n\tif (typeof data.client_id !== \"string\" || !data.client_id) {\n\t\tthrow new Error(`Dynamic client registration at ${registrationEndpoint} returned no client_id`);\n\t}\n\treturn data.client_id;\n}\n\ntype CallbackResult = { code: string; state: string } | null;\n\nasync function startCallbackServer(label: string): Promise<{\n\tserver: Server;\n\tredirectUri: string;\n\tcancel: () => void;\n\twaitForCode: () => Promise<CallbackResult>;\n}> {\n\tconst { createServer } = await import(\"node:http\");\n\tlet settle: ((value: CallbackResult) => void) | undefined;\n\tconst waitPromise = new Promise<CallbackResult>((resolve) => {\n\t\tlet settled = false;\n\t\tsettle = (value) => {\n\t\t\tif (!settled) {\n\t\t\t\tsettled = true;\n\t\t\t\tresolve(value);\n\t\t\t}\n\t\t};\n\t});\n\n\tconst handler = (req: import(\"node:http\").IncomingMessage, res: import(\"node:http\").ServerResponse) => {\n\t\tconst url = new URL(req.url || \"\", \"http://localhost\");\n\t\tif (url.pathname !== CALLBACK_PATH) {\n\t\t\tres.writeHead(404, { \"Content-Type\": \"text/html; charset=utf-8\" });\n\t\t\tres.end(oauthErrorHtml(\"Callback route not found.\"));\n\t\t\treturn;\n\t\t}\n\t\tconst error = url.searchParams.get(\"error\");\n\t\tconst code = url.searchParams.get(\"code\");\n\t\tconst state = url.searchParams.get(\"state\");\n\t\tres.writeHead(error || !code ? 400 : 200, { \"Content-Type\": \"text/html; charset=utf-8\" });\n\t\tif (error) {\n\t\t\tres.end(oauthErrorHtml(`${label} authentication failed.`, `Error: ${error}`));\n\t\t\tsettle?.(null);\n\t\t\treturn;\n\t\t}\n\t\tif (!code || !state) {\n\t\t\tres.end(oauthErrorHtml(\"Missing code or state parameter.\"));\n\t\t\tsettle?.(null);\n\t\t\treturn;\n\t\t}\n\t\tres.end(oauthSuccessHtml(`${label} authentication completed. You can close this window.`));\n\t\tsettle?.({ code, state });\n\t};\n\n\t// Try each candidate port with a FRESH server (a server that failed to listen\n\t// can't be reused), so a leaked/concurrent login can't block us with EADDRINUSE.\n\tlet lastError: unknown;\n\tfor (const port of CALLBACK_PORTS) {\n\t\tconst server = createServer(handler);\n\t\t// Persistent handler so a post-bind 'error' is never an unhandled crash.\n\t\tlet bindErr: ((err: unknown) => void) | undefined;\n\t\tserver.on(\"error\", (err) => bindErr?.(err));\n\t\ttry {\n\t\t\tconst bound = await new Promise<boolean>((resolve) => {\n\t\t\t\tbindErr = () => resolve(false);\n\t\t\t\tserver.listen(port, CALLBACK_HOST, () => {\n\t\t\t\t\tbindErr = undefined;\n\t\t\t\t\tresolve(true);\n\t\t\t\t});\n\t\t\t});\n\t\t\tif (bound) {\n\t\t\t\treturn {\n\t\t\t\t\tserver,\n\t\t\t\t\tredirectUri: redirectUriFor(port),\n\t\t\t\t\tcancel: () => settle?.(null),\n\t\t\t\t\twaitForCode: () => waitPromise,\n\t\t\t\t};\n\t\t\t}\n\t\t\tlastError = `port ${port} in use`;\n\t\t\tserver.close();\n\t\t} catch (err) {\n\t\t\tlastError = err;\n\t\t\tserver.close();\n\t\t}\n\t}\n\tthrow new Error(\n\t\t`Could not start the OAuth callback server: ports ${CALLBACK_PORT_BASE}-${\n\t\t\tCALLBACK_PORT_BASE + CALLBACK_PORT_COUNT - 1\n\t\t} are all in use. Close other login attempts and retry. (${String(lastError)})`,\n\t);\n}\n\nfunction parseRedirectInput(input: string, expectedState: string): { code: string; state: string } {\n\tconst value = input.trim();\n\tlet code: string | undefined;\n\tlet state: string | undefined;\n\ttry {\n\t\tconst url = new URL(value);\n\t\tcode = url.searchParams.get(\"code\") ?? undefined;\n\t\tstate = url.searchParams.get(\"state\") ?? undefined;\n\t} catch {\n\t\tconst params = new URLSearchParams(value);\n\t\tcode = params.get(\"code\") ?? value;\n\t\tstate = params.get(\"state\") ?? undefined;\n\t}\n\tif (state && state !== expectedState) {\n\t\tthrow new Error(\"OAuth state mismatch\");\n\t}\n\tif (!code) {\n\t\tthrow new Error(\"Missing authorization code\");\n\t}\n\treturn { code, state: state ?? expectedState };\n}\n\nasync function exchangeToken(\n\ttokenEndpoint: string,\n\tparams: Record<string, string>,\n): Promise<{ access_token: string; refresh_token?: string; expires_in?: number }> {\n\tvalidatedHttpsUrl(tokenEndpoint, \"Token endpoint\");\n\tconst res = await fetchResponse(tokenEndpoint, {\n\t\tmethod: \"POST\",\n\t\theaders: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n\t\tbody: new URLSearchParams(params).toString(),\n\t});\n\tconst text = await res.text();\n\tif (!res.ok) {\n\t\tthrow new Error(`Token request to ${tokenEndpoint} failed: ${res.status}`);\n\t}\n\tlet token: unknown;\n\ttry {\n\t\ttoken = JSON.parse(text);\n\t} catch {\n\t\tthrow new Error(`Token request to ${tokenEndpoint} returned invalid JSON`);\n\t}\n\tif (!token || typeof token !== \"object\" || typeof (token as { access_token?: unknown }).access_token !== \"string\") {\n\t\tthrow new Error(`Token request to ${tokenEndpoint} returned no access_token`);\n\t}\n\tconst result = token as { access_token: string; refresh_token?: unknown; expires_in?: unknown };\n\tif (!result.access_token) throw new Error(`Token request to ${tokenEndpoint} returned no access_token`);\n\tif (result.refresh_token !== undefined && typeof result.refresh_token !== \"string\") {\n\t\tthrow new Error(`Token request to ${tokenEndpoint} returned an invalid refresh_token`);\n\t}\n\tif (\n\t\tresult.expires_in !== undefined &&\n\t\t(typeof result.expires_in !== \"number\" || !Number.isFinite(result.expires_in))\n\t) {\n\t\tthrow new Error(`Token request to ${tokenEndpoint} returned an invalid expires_in`);\n\t}\n\treturn result as { access_token: string; refresh_token?: string; expires_in?: number };\n}\n\nfunction toCredentials(\n\ttoken: { access_token: string; refresh_token?: string; expires_in?: number },\n\ttokenEndpoint: string,\n\tclientId: string,\n\tendpoint: string | undefined,\n\tresource: string | undefined,\n\tissuer: string | undefined,\n\tpreviousRefresh?: string,\n): McpCredentials {\n\treturn {\n\t\taccess: token.access_token,\n\t\t// Some servers omit refresh_token on refresh; keep the prior one.\n\t\trefresh: token.refresh_token ?? previousRefresh ?? \"\",\n\t\texpires: token.expires_in\n\t\t\t? Date.now() + token.expires_in * 1000 - TOKEN_EXPIRY_BUFFER_MS\n\t\t\t: Date.now() + 3600 * 1000 - TOKEN_EXPIRY_BUFFER_MS,\n\t\ttokenEndpoint,\n\t\tclientId,\n\t\tendpoint,\n\t\tresource,\n\t\tissuer,\n\t};\n}\n\nexport function createMcpOAuthProvider(config: McpOAuthConfig): OAuthProviderInterface {\n\tconst label = config.label ?? config.server;\n\n\tasync function login(callbacks: OAuthLoginCallbacks): Promise<OAuthCredentials> {\n\t\tconst discovery = await discover(config.url);\n\t\tconst { metadata: meta } = discovery;\n\t\tcallbacks.onProgress?.(`Discovered ${discovery.issuer ?? meta.issuer}`);\n\n\t\tlet clientId = config.clientId;\n\t\tif (!clientId) {\n\t\t\tif (!meta.registration_endpoint) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`${label} does not support dynamic client registration and no clientId was configured. ` +\n\t\t\t\t\t\t`Set a pre-registered client id for this server.`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tcallbacks.onProgress?.(\"Registering OAuth client…\");\n\t\t\tclientId = await registerClient(meta.registration_endpoint, `ASM Agent (${label})`);\n\t\t}\n\n\t\tconst { verifier, challenge } = await generatePKCE();\n\t\t// `state` must be independent of the PKCE verifier — the verifier is the\n\t\t// secret used at token exchange, while `state` is echoed on the redirect URL.\n\t\tconst state = randomState();\n\t\tconst scope = config.scopes ?? meta.scopes_supported?.join(\" \");\n\t\tconst cb = await startCallbackServer(label);\n\t\ttry {\n\t\t\tconst authParams = new URLSearchParams({\n\t\t\t\tclient_id: clientId,\n\t\t\t\tresponse_type: \"code\",\n\t\t\t\tredirect_uri: cb.redirectUri,\n\t\t\t\tcode_challenge: challenge,\n\t\t\t\tcode_challenge_method: \"S256\",\n\t\t\t\tstate,\n\t\t\t});\n\t\t\tif (scope) authParams.set(\"scope\", scope);\n\t\t\tif (discovery.resource) authParams.set(\"resource\", discovery.resource);\n\n\t\t\tconst authorizationUrl = new URL(meta.authorization_endpoint);\n\t\t\tfor (const [name, value] of authParams) authorizationUrl.searchParams.set(name, value);\n\t\t\tcallbacks.onAuth({\n\t\t\t\turl: authorizationUrl.toString(),\n\t\t\t\tinstructions:\n\t\t\t\t\t\"Complete login in your browser. If the browser is on another machine, paste the final redirect URL here.\",\n\t\t\t});\n\n\t\t\t// Race the local callback server against a manual paste (browser on\n\t\t\t// another machine). The login dialog supplies onManualCodeInput; when\n\t\t\t// absent we fall back to a blocking prompt after the callback resolves.\n\t\t\tlet result: { code: string; state: string } | null;\n\t\t\tlet manualCancelled = false;\n\t\t\tlet manualError: Error | undefined;\n\t\t\tif (callbacks.onManualCodeInput) {\n\t\t\t\t// Manual paste races the browser callback. A real paste cancels the\n\t\t\t\t// callback waiter (we're done). On manual cancellation we still settle\n\t\t\t\t// the waiter to avoid hanging when no redirect arrives — but only after\n\t\t\t\t// a short grace period so an in-flight browser redirect can win first.\n\t\t\t\tconst manual = callbacks\n\t\t\t\t\t.onManualCodeInput()\n\t\t\t\t\t.then((input) => {\n\t\t\t\t\t\tconst parsed = parseRedirectInput(input, state); // may throw a validation error\n\t\t\t\t\t\tcb.cancel();\n\t\t\t\t\t\treturn parsed;\n\t\t\t\t\t})\n\t\t\t\t\t.catch(async (err) => {\n\t\t\t\t\t\t// A validation error on a real paste (bad state / no code) is a genuine\n\t\t\t\t\t\t// failure to surface; a UI cancellation is not. .catch also prevents an\n\t\t\t\t\t\t// unhandled rejection when the callback wins the race.\n\t\t\t\t\t\tif (err instanceof Error && /state mismatch|authorization code/i.test(err.message)) {\n\t\t\t\t\t\t\tmanualError = err;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tmanualCancelled = true;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tawait new Promise((r) => setTimeout(r, 500));\n\t\t\t\t\t\tcb.cancel();\n\t\t\t\t\t\treturn null;\n\t\t\t\t\t});\n\t\t\t\tconst fromCallback = await cb.waitForCode();\n\t\t\t\tresult = fromCallback ?? (await manual);\n\t\t\t\tif (!result && manualError) throw manualError;\n\t\t\t} else {\n\t\t\t\tresult = await cb.waitForCode();\n\t\t\t\tif (!result) {\n\t\t\t\t\tconst input = await callbacks.onPrompt({\n\t\t\t\t\t\tmessage: \"Paste the authorization code or full redirect URL:\",\n\t\t\t\t\t\tplaceholder: cb.redirectUri,\n\t\t\t\t\t});\n\t\t\t\t\tresult = parseRedirectInput(input, state);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (!result) {\n\t\t\t\tthrow new Error(manualCancelled ? \"Login cancelled\" : \"Missing authorization code\");\n\t\t\t}\n\t\t\tif (result.state !== state) {\n\t\t\t\tthrow new Error(\"OAuth state mismatch\");\n\t\t\t}\n\n\t\t\tcallbacks.onProgress?.(\"Exchanging authorization code for tokens…\");\n\t\t\tconst token = await exchangeToken(meta.token_endpoint, {\n\t\t\t\tgrant_type: \"authorization_code\",\n\t\t\t\tcode: result.code,\n\t\t\t\tredirect_uri: cb.redirectUri,\n\t\t\t\tclient_id: clientId,\n\t\t\t\tcode_verifier: verifier,\n\t\t\t\t...(discovery.resource ? { resource: discovery.resource } : {}),\n\t\t\t});\n\t\t\treturn toCredentials(token, meta.token_endpoint, clientId, config.url, discovery.resource, discovery.issuer);\n\t\t} finally {\n\t\t\tcb.server.close();\n\t\t}\n\t}\n\n\tasync function refreshToken(credentials: OAuthCredentials): Promise<OAuthCredentials> {\n\t\tconst creds = credentials as McpCredentials;\n\t\tif (creds.endpoint !== config.url) {\n\t\t\tthrow new Error(`Stored OAuth credentials are not bound to ${config.url}; re-run /mcp login ${config.server}`);\n\t\t}\n\t\tconst configuredResource = canonicalResource(validatedHttpsUrl(config.url, \"MCP endpoint\"));\n\t\tif (creds.resource !== undefined && creds.resource !== configuredResource) {\n\t\t\tthrow new Error(\n\t\t\t\t`Stored OAuth credentials are not bound to ${configuredResource}; re-run /mcp login ${config.server}`,\n\t\t\t);\n\t\t}\n\t\tif ((creds.resource === undefined) !== (creds.issuer === undefined)) {\n\t\t\tthrow new Error(\n\t\t\t\t`Stored OAuth credentials for ${label} have incomplete resource binding; re-run /mcp login ${config.server}`,\n\t\t\t);\n\t\t}\n\t\tif (creds.issuer !== undefined) validatedHttpsUrl(creds.issuer, \"Stored authorization server issuer\");\n\t\tif (!creds.refresh) {\n\t\t\tthrow new Error(`No refresh token stored for ${label}; re-run /mcp login ${config.server}`);\n\t\t}\n\t\tconst discovery = await discover(config.url);\n\t\tif ((creds.resource === undefined) !== (discovery.resource === undefined)) {\n\t\t\tthrow new Error(`OAuth discovery mode changed for ${config.url}; re-run /mcp login ${config.server}`);\n\t\t}\n\t\tif (creds.resource) {\n\t\t\tif (discovery.resource !== creds.resource || discovery.issuer !== creds.issuer) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Stored OAuth credentials do not match current protected-resource metadata for ${config.url}`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t\tconst tokenEndpoint = creds.tokenEndpoint ?? discovery.metadata.token_endpoint;\n\t\tif (creds.tokenEndpoint && discovery.metadata.token_endpoint !== creds.tokenEndpoint) {\n\t\t\tthrow new Error(\n\t\t\t\t`Stored OAuth token endpoint does not match current authorization-server metadata for ${config.url}`,\n\t\t\t);\n\t\t}\n\t\tconst clientId = creds.clientId ?? config.clientId;\n\t\tif (!tokenEndpoint) throw new Error(`No token endpoint stored for ${label}; re-run /mcp login ${config.server}`);\n\t\tconst token = await exchangeToken(tokenEndpoint, {\n\t\t\tgrant_type: \"refresh_token\",\n\t\t\trefresh_token: creds.refresh,\n\t\t\t...(clientId ? { client_id: clientId } : {}),\n\t\t\t...(creds.resource ? { resource: creds.resource } : {}),\n\t\t});\n\t\treturn toCredentials(\n\t\t\ttoken,\n\t\t\ttokenEndpoint,\n\t\t\tclientId ?? \"\",\n\t\t\tcreds.endpoint,\n\t\t\tcreds.resource,\n\t\t\tcreds.issuer,\n\t\t\tcreds.refresh,\n\t\t);\n\t}\n\n\treturn {\n\t\tid: `mcp:${config.server}`,\n\t\tname: label,\n\t\tusesCallbackServer: true,\n\t\tlogin,\n\t\trefreshToken,\n\t\tgetApiKey: (credentials) => credentials.access,\n\t};\n}\n"]}