gsd-pi 2.46.0-dev.f45e6dc → 2.46.1-dev.44f59e2
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.
- package/README.md +47 -29
- package/dist/resources/extensions/claude-code-cli/index.js +25 -0
- package/dist/resources/extensions/claude-code-cli/models.js +40 -0
- package/dist/resources/extensions/claude-code-cli/package.json +11 -0
- package/dist/resources/extensions/claude-code-cli/partial-builder.js +223 -0
- package/dist/resources/extensions/claude-code-cli/readiness.js +26 -0
- package/dist/resources/extensions/claude-code-cli/sdk-types.js +8 -0
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +293 -0
- package/dist/resources/extensions/gsd/auto-start.js +9 -8
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-plan-milestone.md +2 -2
- package/dist/resources/extensions/gsd/prompts/plan-milestone.md +2 -2
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/research-milestone.md +2 -2
- package/dist/resources/extensions/gsd/prompts/run-uat.md +2 -2
- package/dist/resources/extensions/gsd/repo-identity.js +5 -2
- package/dist/resources/extensions/gsd/state.js +29 -2
- package/dist/resources/extensions/gsd/workflow-events.js +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +18 -18
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +18 -18
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +2 -2
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +3 -1
- package/packages/pi-coding-agent/dist/core/auth-storage.test.js +27 -2
- package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/auth-storage.test.ts +27 -2
- package/pkg/package.json +1 -1
- package/src/resources/extensions/claude-code-cli/index.ts +28 -0
- package/src/resources/extensions/claude-code-cli/models.ts +42 -0
- package/src/resources/extensions/claude-code-cli/package.json +11 -0
- package/src/resources/extensions/claude-code-cli/partial-builder.ts +258 -0
- package/src/resources/extensions/claude-code-cli/readiness.ts +30 -0
- package/src/resources/extensions/claude-code-cli/sdk-types.ts +149 -0
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +355 -0
- package/src/resources/extensions/gsd/auto-start.ts +8 -7
- package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-plan-milestone.md +2 -2
- package/src/resources/extensions/gsd/prompts/plan-milestone.md +2 -2
- package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/research-milestone.md +2 -2
- package/src/resources/extensions/gsd/prompts/run-uat.md +2 -2
- package/src/resources/extensions/gsd/repo-identity.ts +5 -2
- package/src/resources/extensions/gsd/state.ts +33 -1
- package/src/resources/extensions/gsd/tests/inherited-repo-home-dir.test.ts +70 -0
- package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +40 -0
- package/src/resources/extensions/gsd/tests/run-uat.test.ts +25 -0
- package/src/resources/extensions/gsd/workflow-events.ts +1 -1
- /package/dist/web/standalone/.next/static/{9CuEqE-DGAlva1uIjyfjF → sQCECerYL5daQCVxqF23f}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{9CuEqE-DGAlva1uIjyfjF → sQCECerYL5daQCVxqF23f}/_ssgManifest.js +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
<!DOCTYPE html><!--
|
|
1
|
+
<!DOCTYPE html><!--sQCECerYL5daQCVxqF23f--><html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"/><link rel="preload" href="/_next/static/media/4cf2300e9c8272f7-s.p.woff2" as="font" crossorigin="" type="font/woff2"/><link rel="preload" href="/_next/static/media/93f479601ee12b01-s.p.woff2" as="font" crossorigin="" type="font/woff2"/><link rel="stylesheet" href="/_next/static/css/de70bee13400563f.css" data-precedence="next"/><link rel="stylesheet" href="/_next/static/css/dd4ae3f58ac9b600.css" data-precedence="next"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/webpack-0a4cd455ec4197d2.js"/><script src="/_next/static/chunks/4bd1b696-e5d7c65570c947b7.js" async=""></script><script src="/_next/static/chunks/3794-337d1ca25ad99a89.js" async=""></script><script src="/_next/static/chunks/main-app-fdab67f7802d7832.js" async=""></script><script src="/_next/static/chunks/4986-c2fc8845ce785303.js" async=""></script><script src="/_next/static/chunks/app/layout-a16c7a7ecdf0c2cf.js" async=""></script><meta name="robots" content="noindex"/><meta name="next-size-adjust" content=""/><title>404: This page could not be found.</title><title>GSD</title><meta name="description" content="The evolution of Get Shit Done — now a real coding agent. One command. Walk away. Come back to a built project."/><meta name="application-name" content="GSD"/><link rel="icon" href="/icon-light-32x32.png" media="(prefers-color-scheme: light)"/><link rel="icon" href="/icon-dark-32x32.png" media="(prefers-color-scheme: dark)"/><link rel="icon" href="/icon.svg" type="image/svg+xml"/><script src="/_next/static/chunks/polyfills-42372ed130431b0a.js" noModule=""></script></head><body class="__variable_188709 __variable_9a8899 font-sans antialiased"><div hidden=""><!--$--><!--/$--></div><script>((a,b,c,d,e,f,g,h)=>{let i=document.documentElement,j=["light","dark"];function k(b){var c;(Array.isArray(a)?a:[a]).forEach(a=>{let c="class"===a,d=c&&f?e.map(a=>f[a]||a):e;c?(i.classList.remove(...d),i.classList.add(f&&f[b]?f[b]:b)):i.setAttribute(a,b)}),c=b,h&&j.includes(c)&&(i.style.colorScheme=c)}if(d)k(d);else try{let a=localStorage.getItem(b)||c,d=g&&"system"===a?window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light":a;k(d)}catch(a){}})("class","theme","dark",null,["light","dark"],null,true,true)</script><div style="font-family:system-ui,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji";height:100vh;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center"><div><style>body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}</style><h1 class="next-error-h1" style="display:inline-block;margin:0 20px 0 0;padding:0 23px 0 0;font-size:24px;font-weight:500;vertical-align:top;line-height:49px">404</h1><div style="display:inline-block"><h2 style="font-size:14px;font-weight:400;line-height:49px;margin:0">This page could not be found.</h2></div></div></div><!--$--><!--/$--><section aria-label="Notifications alt+T" tabindex="-1" aria-live="polite" aria-relevant="additions text" aria-atomic="false"></section><script src="/_next/static/chunks/webpack-0a4cd455ec4197d2.js" id="_R_" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0])</script><script>self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:I[21942,[\"4986\",\"static/chunks/4986-c2fc8845ce785303.js\",\"7177\",\"static/chunks/app/layout-a16c7a7ecdf0c2cf.js\"],\"ThemeProvider\"]\n3:I[57121,[],\"\"]\n4:I[74581,[],\"\"]\n5:I[61549,[\"4986\",\"static/chunks/4986-c2fc8845ce785303.js\",\"7177\",\"static/chunks/app/layout-a16c7a7ecdf0c2cf.js\"],\"Toaster\"]\n6:I[90484,[],\"OutletBoundary\"]\n7:\"$Sreact.suspense\"\n9:I[90484,[],\"ViewportBoundary\"]\nb:I[90484,[],\"MetadataBoundary\"]\nd:I[27123,[],\"\"]\n:HL[\"/_next/static/media/4cf2300e9c8272f7-s.p.woff2\",\"font\",{\"crossOrigin\":\"\",\"type\":\"font/woff2\"}]\n:HL[\"/_next/static/media/93f479601ee12b01-s.p.woff2\",\"font\",{\"crossOrigin\":\"\",\"type\":\"font/woff2\"}]\n:HL[\"/_next/static/css/de70bee13400563f.css\",\"style\"]\n:HL[\"/_next/static/css/dd4ae3f58ac9b600.css\",\"style\"]\n"])</script><script>self.__next_f.push([1,"0:{\"P\":null,\"b\":\"sQCECerYL5daQCVxqF23f\",\"c\":[\"\",\"_not-found\"],\"q\":\"\",\"i\":false,\"f\":[[[\"\",{\"children\":[\"_not-found\",{\"children\":[\"__PAGE__\",{}]}]},\"$undefined\",\"$undefined\",true],[[\"$\",\"$1\",\"c\",{\"children\":[[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/css/de70bee13400563f.css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\",\"nonce\":\"$undefined\"}],[\"$\",\"link\",\"1\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/css/dd4ae3f58ac9b600.css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\",\"nonce\":\"$undefined\"}]],[\"$\",\"html\",null,{\"lang\":\"en\",\"suppressHydrationWarning\":true,\"children\":[\"$\",\"body\",null,{\"className\":\"__variable_188709 __variable_9a8899 font-sans antialiased\",\"children\":[\"$\",\"$L2\",null,{\"attribute\":\"class\",\"defaultTheme\":\"dark\",\"children\":[[\"$\",\"$L3\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L4\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}],[\"$\",\"$L5\",null,{\"position\":\"bottom-right\"}]]}]}]}]]}],{\"children\":[[\"$\",\"$1\",\"c\",{\"children\":[null,[\"$\",\"$L3\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L4\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]]}],{\"children\":[[\"$\",\"$1\",\"c\",{\"children\":[[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"},\"children\":404}],[\"$\",\"div\",null,{\"style\":{\"display\":\"inline-block\"},\"children\":[\"$\",\"h2\",null,{\"style\":{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0},\"children\":\"This page could not be found.\"}]}]]}]}]],null,[\"$\",\"$L6\",null,{\"children\":[\"$\",\"$7\",null,{\"name\":\"Next.MetadataOutlet\",\"children\":\"$@8\"}]}]]}],{},null,false,false]},null,false,false]},null,false,false],[\"$\",\"$1\",\"h\",{\"children\":[[\"$\",\"meta\",null,{\"name\":\"robots\",\"content\":\"noindex\"}],[\"$\",\"$L9\",null,{\"children\":\"$La\"}],[\"$\",\"div\",null,{\"hidden\":true,\"children\":[\"$\",\"$Lb\",null,{\"children\":[\"$\",\"$7\",null,{\"name\":\"Next.Metadata\",\"children\":\"$Lc\"}]}]}],[\"$\",\"meta\",null,{\"name\":\"next-size-adjust\",\"content\":\"\"}]]}],false]],\"m\":\"$undefined\",\"G\":[\"$d\",[]],\"S\":true}\n"])</script><script>self.__next_f.push([1,"a:[[\"$\",\"meta\",\"0\",{\"charSet\":\"utf-8\"}],[\"$\",\"meta\",\"1\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no\"}]]\n"])</script><script>self.__next_f.push([1,"e:I[86869,[],\"IconMark\"]\n8:null\nc:[[\"$\",\"title\",\"0\",{\"children\":\"GSD\"}],[\"$\",\"meta\",\"1\",{\"name\":\"description\",\"content\":\"The evolution of Get Shit Done — now a real coding agent. One command. Walk away. Come back to a built project.\"}],[\"$\",\"meta\",\"2\",{\"name\":\"application-name\",\"content\":\"GSD\"}],[\"$\",\"link\",\"3\",{\"rel\":\"icon\",\"href\":\"/icon-light-32x32.png\",\"media\":\"(prefers-color-scheme: light)\"}],[\"$\",\"link\",\"4\",{\"rel\":\"icon\",\"href\":\"/icon-dark-32x32.png\",\"media\":\"(prefers-color-scheme: dark)\"}],[\"$\",\"link\",\"5\",{\"rel\":\"icon\",\"href\":\"/icon.svg\",\"type\":\"image/svg+xml\"}],[\"$\",\"$Le\",\"6\",{}]]\n"])</script></body></html>
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
<!DOCTYPE html><!--
|
|
2
|
-
@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}</style><h1 class="next-error-h1" style="display:inline-block;margin:0 20px 0 0;padding-right:23px;font-size:24px;font-weight:500;vertical-align:top">500</h1><div style="display:inline-block"><h2 style="font-size:14px;font-weight:400;line-height:28px">Internal Server Error.</h2></div></div></div><!--$--><!--/$--><script src="/_next/static/chunks/webpack-0a4cd455ec4197d2.js" id="_R_" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0])</script><script>self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:I[57121,[],\"\"]\n3:I[74581,[],\"\"]\n4:I[90484,[],\"OutletBoundary\"]\n5:\"$Sreact.suspense\"\n7:I[90484,[],\"ViewportBoundary\"]\n9:I[90484,[],\"MetadataBoundary\"]\nb:I[27123,[],\"\"]\n"])</script><script>self.__next_f.push([1,"0:{\"P\":null,\"b\":\"
|
|
1
|
+
<!DOCTYPE html><!--sQCECerYL5daQCVxqF23f--><html id="__next_error__"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/webpack-0a4cd455ec4197d2.js"/><script src="/_next/static/chunks/4bd1b696-e5d7c65570c947b7.js" async=""></script><script src="/_next/static/chunks/3794-337d1ca25ad99a89.js" async=""></script><script src="/_next/static/chunks/main-app-fdab67f7802d7832.js" async=""></script><meta name="next-size-adjust" content=""/><title>500: Internal Server Error.</title><script src="/_next/static/chunks/polyfills-42372ed130431b0a.js" noModule=""></script></head><body><div hidden=""><!--$--><!--/$--></div><div style="font-family:system-ui,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji";height:100vh;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center"><div style="line-height:48px"><style>body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}
|
|
2
|
+
@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}</style><h1 class="next-error-h1" style="display:inline-block;margin:0 20px 0 0;padding-right:23px;font-size:24px;font-weight:500;vertical-align:top">500</h1><div style="display:inline-block"><h2 style="font-size:14px;font-weight:400;line-height:28px">Internal Server Error.</h2></div></div></div><!--$--><!--/$--><script src="/_next/static/chunks/webpack-0a4cd455ec4197d2.js" id="_R_" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0])</script><script>self.__next_f.push([1,"1:\"$Sreact.fragment\"\n2:I[57121,[],\"\"]\n3:I[74581,[],\"\"]\n4:I[90484,[],\"OutletBoundary\"]\n5:\"$Sreact.suspense\"\n7:I[90484,[],\"ViewportBoundary\"]\n9:I[90484,[],\"MetadataBoundary\"]\nb:I[27123,[],\"\"]\n"])</script><script>self.__next_f.push([1,"0:{\"P\":null,\"b\":\"sQCECerYL5daQCVxqF23f\",\"c\":[\"\",\"_global-error\"],\"q\":\"\",\"i\":false,\"f\":[[[\"\",{\"children\":[\"_global-error\",{\"children\":[\"__PAGE__\",{}]}]}],[[\"$\",\"$1\",\"c\",{\"children\":[null,[\"$\",\"$L2\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L3\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]]}],{\"children\":[[\"$\",\"$1\",\"c\",{\"children\":[null,[\"$\",\"$L2\",null,{\"parallelRouterKey\":\"children\",\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L3\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"forbidden\":\"$undefined\",\"unauthorized\":\"$undefined\"}]]}],{\"children\":[[\"$\",\"$1\",\"c\",{\"children\":[[\"$\",\"html\",null,{\"id\":\"__next_error__\",\"children\":[[\"$\",\"head\",null,{\"children\":[\"$\",\"title\",null,{\"children\":\"500: Internal Server Error.\"}]}],[\"$\",\"body\",null,{\"children\":[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"style\":{\"lineHeight\":\"48px\"},\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}\\n@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"paddingRight\":23,\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\"},\"children\":\"500\"}],[\"$\",\"div\",null,{\"style\":{\"display\":\"inline-block\"},\"children\":[\"$\",\"h2\",null,{\"style\":{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"28px\"},\"children\":\"Internal Server Error.\"}]}]]}]}]}]]}],null,[\"$\",\"$L4\",null,{\"children\":[\"$\",\"$5\",null,{\"name\":\"Next.MetadataOutlet\",\"children\":\"$@6\"}]}]]}],{},null,false,false]},null,false,false]},null,false,false],[\"$\",\"$1\",\"h\",{\"children\":[null,[\"$\",\"$L7\",null,{\"children\":\"$L8\"}],[\"$\",\"div\",null,{\"hidden\":true,\"children\":[\"$\",\"$L9\",null,{\"children\":[\"$\",\"$5\",null,{\"name\":\"Next.Metadata\",\"children\":\"$La\"}]}]}],[\"$\",\"meta\",null,{\"name\":\"next-size-adjust\",\"content\":\"\"}]]}],false]],\"m\":\"$undefined\",\"G\":[\"$b\",[]],\"S\":true}\n"])</script><script>self.__next_f.push([1,"8:[[\"$\",\"meta\",\"0\",{\"charSet\":\"utf-8\"}],[\"$\",\"meta\",\"1\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}]]\n"])</script><script>self.__next_f.push([1,"6:null\na:[]\n"])</script></body></html>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"node":{},"edge":{},"encryptionKey":"
|
|
1
|
+
{"node":{},"edge":{},"encryptionKey":"uJcNJizA4uCzsuuL7Qlkm5UEMqcrcMW/nhKRsBH4qZk="}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gsd-pi",
|
|
3
|
-
"version": "2.46.
|
|
3
|
+
"version": "2.46.1-dev.44f59e2",
|
|
4
4
|
"description": "GSD — Get Shit Done coding agent",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -54,6 +54,7 @@
|
|
|
54
54
|
"copy-themes": "node scripts/copy-themes.cjs",
|
|
55
55
|
"copy-export-html": "node scripts/copy-export-html.cjs",
|
|
56
56
|
"test:unit": "node --import ./src/resources/extensions/gsd/tests/resolve-ts.mjs --experimental-strip-types --experimental-test-isolation=process --test src/resources/extensions/gsd/tests/*.test.ts src/resources/extensions/gsd/tests/*.test.mjs src/tests/*.test.ts",
|
|
57
|
+
"test:packages": "node --test packages/pi-coding-agent/dist/core/*.test.js",
|
|
57
58
|
"test:marketplace": "GSD_TEST_CLONE_MARKETPLACES=1 node --import ./src/resources/extensions/gsd/tests/resolve-ts.mjs --experimental-strip-types --test src/resources/extensions/gsd/tests/claude-import-tui.test.ts src/resources/extensions/gsd/tests/plugin-importer-live.test.ts src/tests/marketplace-discovery.test.ts",
|
|
58
59
|
"test:coverage": "c8 --reporter=text --reporter=lcov --exclude='src/resources/extensions/gsd/tests/**' --exclude='src/tests/**' --exclude='scripts/**' --exclude='native/**' --exclude='node_modules/**' --check-coverage --statements=50 --lines=50 --branches=20 --functions=20 node --import ./src/resources/extensions/gsd/tests/resolve-ts.mjs --experimental-strip-types --experimental-test-isolation=process --test src/resources/extensions/gsd/tests/*.test.ts src/resources/extensions/gsd/tests/*.test.mjs src/tests/*.test.ts",
|
|
59
60
|
"test:integration": "node --import ./src/resources/extensions/gsd/tests/resolve-ts.mjs --experimental-strip-types --experimental-test-isolation=process --test src/resources/extensions/gsd/tests/*integration*.test.ts src/tests/integration/*.test.ts",
|
|
@@ -139,6 +140,7 @@
|
|
|
139
140
|
"typescript": "^5.4.0"
|
|
140
141
|
},
|
|
141
142
|
"optionalDependencies": {
|
|
143
|
+
"@anthropic-ai/claude-agent-sdk": "^0.2.83",
|
|
142
144
|
"@gsd-build/engine-darwin-arm64": ">=2.10.2",
|
|
143
145
|
"@gsd-build/engine-darwin-x64": ">=2.10.2",
|
|
144
146
|
"@gsd-build/engine-linux-arm64-gnu": ">=2.10.2",
|
|
@@ -215,7 +215,7 @@ describe("AuthStorage — areAllCredentialsBackedOff", () => {
|
|
|
215
215
|
});
|
|
216
216
|
// ─── mismatched oauth credential for non-OAuth provider (#2083) ───────────────
|
|
217
217
|
describe("AuthStorage — oauth credential for non-OAuth provider (#2083)", () => {
|
|
218
|
-
it("returns undefined when openrouter has type:oauth (no registered OAuth provider)", async () => {
|
|
218
|
+
it("returns undefined when openrouter has type:oauth (no registered OAuth provider)", async (t) => {
|
|
219
219
|
// Simulates the bug: OpenRouter credential stored as type:"oauth"
|
|
220
220
|
// but OpenRouter is not a registered OAuth provider.
|
|
221
221
|
const storage = inMemory({
|
|
@@ -226,12 +226,25 @@ describe("AuthStorage — oauth credential for non-OAuth provider (#2083)", () =
|
|
|
226
226
|
expires: Date.now() + 3_600_000,
|
|
227
227
|
},
|
|
228
228
|
});
|
|
229
|
+
// Isolate from any real OPENROUTER_API_KEY in the environment so the
|
|
230
|
+
// fall-through to env / fallback finds nothing and returns undefined.
|
|
231
|
+
const origEnv = process.env.OPENROUTER_API_KEY;
|
|
232
|
+
delete process.env.OPENROUTER_API_KEY;
|
|
233
|
+
t.after(() => {
|
|
234
|
+
if (origEnv === undefined) {
|
|
235
|
+
delete process.env.OPENROUTER_API_KEY;
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
process.env.OPENROUTER_API_KEY = origEnv;
|
|
239
|
+
}
|
|
240
|
+
});
|
|
229
241
|
// Before the fix, getApiKey returns undefined because
|
|
230
242
|
// resolveCredentialApiKey calls getOAuthProvider("openrouter") → null → undefined.
|
|
231
243
|
// The key in the oauth credential is never extracted.
|
|
232
244
|
const key = await storage.getApiKey("openrouter");
|
|
233
245
|
// After the fix, the oauth credential with an unrecognised provider
|
|
234
246
|
// should be skipped, and getApiKey should fall through to env / fallback.
|
|
247
|
+
// With no env var and no fallback resolver configured, the result is undefined.
|
|
235
248
|
assert.equal(key, undefined);
|
|
236
249
|
});
|
|
237
250
|
it("falls through to env var when openrouter has type:oauth credential", async (t) => {
|
|
@@ -257,7 +270,7 @@ describe("AuthStorage — oauth credential for non-OAuth provider (#2083)", () =
|
|
|
257
270
|
const key = await storage.getApiKey("openrouter");
|
|
258
271
|
assert.equal(key, "sk-or-v1-env-key");
|
|
259
272
|
});
|
|
260
|
-
it("falls through to fallback resolver when openrouter has type:oauth credential", async () => {
|
|
273
|
+
it("falls through to fallback resolver when openrouter has type:oauth credential", async (t) => {
|
|
261
274
|
const storage = inMemory({
|
|
262
275
|
openrouter: {
|
|
263
276
|
type: "oauth",
|
|
@@ -266,6 +279,18 @@ describe("AuthStorage — oauth credential for non-OAuth provider (#2083)", () =
|
|
|
266
279
|
expires: Date.now() + 3_600_000,
|
|
267
280
|
},
|
|
268
281
|
});
|
|
282
|
+
// Isolate from any real OPENROUTER_API_KEY so env fallback is skipped
|
|
283
|
+
// and the fallback resolver is reached.
|
|
284
|
+
const origEnv = process.env.OPENROUTER_API_KEY;
|
|
285
|
+
delete process.env.OPENROUTER_API_KEY;
|
|
286
|
+
t.after(() => {
|
|
287
|
+
if (origEnv === undefined) {
|
|
288
|
+
delete process.env.OPENROUTER_API_KEY;
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
process.env.OPENROUTER_API_KEY = origEnv;
|
|
292
|
+
}
|
|
293
|
+
});
|
|
269
294
|
storage.setFallbackResolver((provider) => provider === "openrouter" ? "sk-or-v1-fallback" : undefined);
|
|
270
295
|
const key = await storage.getApiKey("openrouter");
|
|
271
296
|
assert.equal(key, "sk-or-v1-fallback");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-storage.test.js","sourceRoot":"","sources":["../../src/core/auth-storage.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, it } from \"node:test\";\nimport assert from \"node:assert/strict\";\nimport { AuthStorage } from \"./auth-storage.js\";\n\n// ─── helpers ──────────────────────────────────────────────────────────────────\n\nfunction makeKey(key: string) {\n\treturn { type: \"api_key\" as const, key };\n}\n\nfunction inMemory(data: Record<string, unknown> = {}) {\n\treturn AuthStorage.inMemory(data as any);\n}\n\n// ─── single credential (backward compat) ─────────────────────────────────────\n\ndescribe(\"AuthStorage — single credential (backward compat)\", () => {\n\tit(\"returns the api key for a provider with one key\", async () => {\n\t\tconst storage = inMemory({ anthropic: makeKey(\"sk-abc\") });\n\t\tconst key = await storage.getApiKey(\"anthropic\");\n\t\tassert.equal(key, \"sk-abc\");\n\t});\n\n\tit(\"returns undefined for unknown provider\", async () => {\n\t\tconst storage = inMemory({});\n\t\tconst key = await storage.getApiKey(\"unknown\");\n\t\tassert.equal(key, undefined);\n\t});\n\n\tit(\"runtime override takes precedence over stored key\", async () => {\n\t\tconst storage = inMemory({ anthropic: makeKey(\"sk-stored\") });\n\t\tstorage.setRuntimeApiKey(\"anthropic\", \"sk-runtime\");\n\t\tconst key = await storage.getApiKey(\"anthropic\");\n\t\tassert.equal(key, \"sk-runtime\");\n\t});\n});\n\n// ─── multiple credentials ─────────────────────────────────────────────────────\n\ndescribe(\"AuthStorage — multiple credentials\", () => {\n\tit(\"round-robins across multiple api keys without sessionId\", async () => {\n\t\tconst storage = inMemory({\n\t\t\tanthropic: [makeKey(\"sk-1\"), makeKey(\"sk-2\"), makeKey(\"sk-3\")],\n\t\t});\n\n\t\tconst keys = new Set<string>();\n\t\tfor (let i = 0; i < 6; i++) {\n\t\t\tconst k = await storage.getApiKey(\"anthropic\");\n\t\t\tassert.ok(k, `call ${i} should return a key`);\n\t\t\tkeys.add(k);\n\t\t}\n\t\t// All three keys should have been selected across 6 calls\n\t\tassert.deepEqual(keys, new Set([\"sk-1\", \"sk-2\", \"sk-3\"]));\n\t});\n\n\tit(\"session-sticky: same sessionId always picks the same key\", async () => {\n\t\tconst storage = inMemory({\n\t\t\tanthropic: [makeKey(\"sk-1\"), makeKey(\"sk-2\"), makeKey(\"sk-3\")],\n\t\t});\n\n\t\tconst sessionId = \"sess-abc\";\n\t\tconst first = await storage.getApiKey(\"anthropic\", sessionId);\n\t\tfor (let i = 0; i < 5; i++) {\n\t\t\tconst k = await storage.getApiKey(\"anthropic\", sessionId);\n\t\t\tassert.equal(k, first, `call ${i} should be sticky to first selection`);\n\t\t}\n\t});\n\n\tit(\"different sessionIds may select different keys\", async () => {\n\t\tconst storage = inMemory({\n\t\t\tanthropic: [makeKey(\"sk-1\"), makeKey(\"sk-2\"), makeKey(\"sk-3\")],\n\t\t});\n\n\t\tconst results = new Set<string>();\n\t\tfor (let i = 0; i < 20; i++) {\n\t\t\tconst k = await storage.getApiKey(\"anthropic\", `sess-${i}`);\n\t\t\tif (k) results.add(k);\n\t\t}\n\t\t// With 20 different sessions and 3 keys, we should see more than one key\n\t\tassert.ok(results.size > 1, \"multiple sessions should hash to different keys\");\n\t});\n});\n\n// ─── login accumulation ───────────────────────────────────────────────────────\n\ndescribe(\"AuthStorage — login accumulation\", () => {\n\tit(\"accumulates api keys on repeated set()\", () => {\n\t\tconst storage = inMemory({});\n\t\tstorage.set(\"anthropic\", makeKey(\"sk-1\"));\n\t\tstorage.set(\"anthropic\", makeKey(\"sk-2\"));\n\t\tconst creds = storage.getCredentialsForProvider(\"anthropic\");\n\t\tassert.equal(creds.length, 2);\n\t\tassert.deepEqual(\n\t\t\tcreds.map((c) => (c.type === \"api_key\" ? c.key : null)),\n\t\t\t[\"sk-1\", \"sk-2\"],\n\t\t);\n\t});\n\n\tit(\"deduplicates identical api keys\", () => {\n\t\tconst storage = inMemory({});\n\t\tstorage.set(\"anthropic\", makeKey(\"sk-1\"));\n\t\tstorage.set(\"anthropic\", makeKey(\"sk-1\"));\n\t\tconst creds = storage.getCredentialsForProvider(\"anthropic\");\n\t\tassert.equal(creds.length, 1);\n\t});\n});\n\n// ─── backoff / markUsageLimitReached ─────────────────────────────────────────\n\ndescribe(\"AuthStorage — rate-limit backoff\", () => {\n\tit(\"returns true when a backed-off credential has an alternate\", async () => {\n\t\tconst storage = inMemory({\n\t\t\tanthropic: [makeKey(\"sk-1\"), makeKey(\"sk-2\")],\n\t\t});\n\n\t\t// Use sk-1 via round-robin (first call, index 0)\n\t\tawait storage.getApiKey(\"anthropic\");\n\n\t\t// Mark it as rate-limited; sk-2 should still be available\n\t\tconst hasAlternate = storage.markUsageLimitReached(\"anthropic\");\n\t\tassert.equal(hasAlternate, true);\n\t});\n\n\tit(\"returns false when all credentials are backed off\", async () => {\n\t\tconst storage = inMemory({\n\t\t\tanthropic: [makeKey(\"sk-1\"), makeKey(\"sk-2\")],\n\t\t});\n\n\t\t// Back off both keys\n\t\tawait storage.getApiKey(\"anthropic\"); // uses index 0\n\t\tstorage.markUsageLimitReached(\"anthropic\"); // backs off index 0\n\t\tawait storage.getApiKey(\"anthropic\"); // uses index 1\n\t\tconst hasAlternate = storage.markUsageLimitReached(\"anthropic\"); // backs off index 1\n\t\tassert.equal(hasAlternate, false);\n\t});\n\n\tit(\"backed-off credential is skipped; next available key is returned\", async () => {\n\t\tconst storage = inMemory({\n\t\t\tanthropic: [makeKey(\"sk-1\"), makeKey(\"sk-2\")],\n\t\t});\n\n\t\t// First call → sk-1 (round-robin index 0)\n\t\tconst first = await storage.getApiKey(\"anthropic\");\n\t\tassert.equal(first, \"sk-1\");\n\n\t\t// Back off sk-1\n\t\tstorage.markUsageLimitReached(\"anthropic\");\n\n\t\t// Next call should skip backed-off sk-1 and return sk-2\n\t\tconst second = await storage.getApiKey(\"anthropic\");\n\t\tassert.equal(second, \"sk-2\");\n\t});\n\n\tit(\"single credential: markUsageLimitReached returns false\", async () => {\n\t\tconst storage = inMemory({ anthropic: makeKey(\"sk-only\") });\n\t\tawait storage.getApiKey(\"anthropic\");\n\t\tconst hasAlternate = storage.markUsageLimitReached(\"anthropic\");\n\t\tassert.equal(hasAlternate, false);\n\t});\n\n\tit(\"single credential: unknown error type skips backoff entirely\", async () => {\n\t\tconst storage = inMemory({ anthropic: makeKey(\"sk-only\") });\n\t\tawait storage.getApiKey(\"anthropic\");\n\n\t\t// Mark with unknown error type (transport failure)\n\t\tconst hasAlternate = storage.markUsageLimitReached(\"anthropic\", undefined, {\n\t\t\terrorType: \"unknown\",\n\t\t});\n\t\tassert.equal(hasAlternate, false);\n\n\t\t// Key should still be available — backoff was not applied\n\t\tconst key = await storage.getApiKey(\"anthropic\");\n\t\tassert.equal(key, \"sk-only\");\n\t});\n\n\tit(\"multiple credentials: unknown error type still backs off the used credential\", async () => {\n\t\tconst storage = inMemory({\n\t\t\tanthropic: [makeKey(\"sk-1\"), makeKey(\"sk-2\")],\n\t\t});\n\t\tawait storage.getApiKey(\"anthropic\"); // uses sk-1\n\n\t\t// Mark with unknown error type — should still back off when alternates exist\n\t\tconst hasAlternate = storage.markUsageLimitReached(\"anthropic\", undefined, {\n\t\t\terrorType: \"unknown\",\n\t\t});\n\t\tassert.equal(hasAlternate, true);\n\n\t\t// Next call should return sk-2\n\t\tconst key = await storage.getApiKey(\"anthropic\");\n\t\tassert.equal(key, \"sk-2\");\n\t});\n\n\tit(\"single credential: rate_limit error type still backs off\", async () => {\n\t\tconst storage = inMemory({ anthropic: makeKey(\"sk-only\") });\n\t\tawait storage.getApiKey(\"anthropic\");\n\n\t\t// rate_limit should still back off even single credentials\n\t\tconst hasAlternate = storage.markUsageLimitReached(\"anthropic\", undefined, {\n\t\t\terrorType: \"rate_limit\",\n\t\t});\n\t\tassert.equal(hasAlternate, false);\n\n\t\t// Key should be backed off\n\t\tconst key = await storage.getApiKey(\"anthropic\");\n\t\tassert.equal(key, undefined);\n\t});\n\n\tit(\"session-sticky: marks the correct credential as backed off\", async () => {\n\t\tconst storage = inMemory({\n\t\t\tanthropic: [makeKey(\"sk-1\"), makeKey(\"sk-2\")],\n\t\t});\n\n\t\tconst sessionId = \"sess-xyz\";\n\t\tconst chosen = await storage.getApiKey(\"anthropic\", sessionId);\n\t\tassert.ok(chosen);\n\n\t\t// Back off the chosen credential for this session\n\t\tconst hasAlternate = storage.markUsageLimitReached(\"anthropic\", sessionId);\n\t\tassert.equal(hasAlternate, true);\n\n\t\t// Next call with same session should return the other key\n\t\tconst next = await storage.getApiKey(\"anthropic\", sessionId);\n\t\tassert.ok(next);\n\t\tassert.notEqual(next, chosen);\n\t});\n});\n\n// ─── areAllCredentialsBackedOff ───────────────────────────────────────────────\n\ndescribe(\"AuthStorage — areAllCredentialsBackedOff\", () => {\n\tit(\"returns false when no credentials are configured\", () => {\n\t\tconst storage = inMemory({});\n\t\tassert.equal(storage.areAllCredentialsBackedOff(\"anthropic\"), false);\n\t});\n\n\tit(\"returns false when credentials exist and none are backed off\", async () => {\n\t\tconst storage = inMemory({ anthropic: makeKey(\"sk-abc\") });\n\t\tassert.equal(storage.areAllCredentialsBackedOff(\"anthropic\"), false);\n\t});\n\n\tit(\"returns true when the single credential is backed off\", async () => {\n\t\tconst storage = inMemory({ anthropic: makeKey(\"sk-only\") });\n\t\tawait storage.getApiKey(\"anthropic\");\n\t\tstorage.markUsageLimitReached(\"anthropic\");\n\t\tassert.equal(storage.areAllCredentialsBackedOff(\"anthropic\"), true);\n\t});\n\n\tit(\"returns false when at least one credential is still available\", async () => {\n\t\tconst storage = inMemory({ anthropic: [makeKey(\"sk-1\"), makeKey(\"sk-2\")] });\n\t\tawait storage.getApiKey(\"anthropic\"); // uses index 0\n\t\tstorage.markUsageLimitReached(\"anthropic\"); // backs off index 0\n\t\t// index 1 is still available\n\t\tassert.equal(storage.areAllCredentialsBackedOff(\"anthropic\"), false);\n\t});\n\n\tit(\"returns true when all credentials are backed off\", async () => {\n\t\tconst storage = inMemory({ anthropic: [makeKey(\"sk-1\"), makeKey(\"sk-2\")] });\n\t\tawait storage.getApiKey(\"anthropic\"); // uses index 0\n\t\tstorage.markUsageLimitReached(\"anthropic\"); // backs off index 0\n\t\tawait storage.getApiKey(\"anthropic\"); // uses index 1\n\t\tstorage.markUsageLimitReached(\"anthropic\"); // backs off index 1\n\t\tassert.equal(storage.areAllCredentialsBackedOff(\"anthropic\"), true);\n\t});\n});\n\n// ─── mismatched oauth credential for non-OAuth provider (#2083) ───────────────\n\ndescribe(\"AuthStorage — oauth credential for non-OAuth provider (#2083)\", () => {\n\tit(\"returns undefined when openrouter has type:oauth (no registered OAuth provider)\", async () => {\n\t\t// Simulates the bug: OpenRouter credential stored as type:\"oauth\"\n\t\t// but OpenRouter is not a registered OAuth provider.\n\t\tconst storage = inMemory({\n\t\t\topenrouter: {\n\t\t\t\ttype: \"oauth\",\n\t\t\t\taccess_token: \"sk-or-v1-fake\",\n\t\t\t\trefresh_token: \"rt-fake\",\n\t\t\t\texpires: Date.now() + 3_600_000,\n\t\t\t},\n\t\t});\n\n\t\t// Before the fix, getApiKey returns undefined because\n\t\t// resolveCredentialApiKey calls getOAuthProvider(\"openrouter\") → null → undefined.\n\t\t// The key in the oauth credential is never extracted.\n\t\tconst key = await storage.getApiKey(\"openrouter\");\n\t\t// After the fix, the oauth credential with an unrecognised provider\n\t\t// should be skipped, and getApiKey should fall through to env / fallback.\n\t\tassert.equal(key, undefined);\n\t});\n\n\tit(\"falls through to env var when openrouter has type:oauth credential\", async (t) => {\n\t\tconst storage = inMemory({\n\t\t\topenrouter: {\n\t\t\t\ttype: \"oauth\",\n\t\t\t\taccess_token: \"sk-or-v1-fake\",\n\t\t\t\trefresh_token: \"rt-fake\",\n\t\t\t\texpires: Date.now() + 3_600_000,\n\t\t\t},\n\t\t});\n\n\t\t// Simulate OPENROUTER_API_KEY being set via env\n\t\tconst origEnv = process.env.OPENROUTER_API_KEY;\n\t\tt.after(() => {\n\t\t\tif (origEnv === undefined) {\n\t\t\t\tdelete process.env.OPENROUTER_API_KEY;\n\t\t\t} else {\n\t\t\t\tprocess.env.OPENROUTER_API_KEY = origEnv;\n\t\t\t}\n\t\t});\n\n\t\tprocess.env.OPENROUTER_API_KEY = \"sk-or-v1-env-key\";\n\t\tconst key = await storage.getApiKey(\"openrouter\");\n\t\tassert.equal(key, \"sk-or-v1-env-key\");\n\t});\n\n\tit(\"falls through to fallback resolver when openrouter has type:oauth credential\", async () => {\n\t\tconst storage = inMemory({\n\t\t\topenrouter: {\n\t\t\t\ttype: \"oauth\",\n\t\t\t\taccess_token: \"sk-or-v1-fake\",\n\t\t\t\trefresh_token: \"rt-fake\",\n\t\t\t\texpires: Date.now() + 3_600_000,\n\t\t\t},\n\t\t});\n\n\t\tstorage.setFallbackResolver((provider) =>\n\t\t\tprovider === \"openrouter\" ? \"sk-or-v1-fallback\" : undefined,\n\t\t);\n\n\t\tconst key = await storage.getApiKey(\"openrouter\");\n\t\tassert.equal(key, \"sk-or-v1-fallback\");\n\t});\n});\n\n// ─── getAll truncation ────────────────────────────────────────────────────────\n\ndescribe(\"AuthStorage — getAll()\", () => {\n\tit(\"returns first credential only for providers with multiple keys\", () => {\n\t\tconst storage = inMemory({\n\t\t\tanthropic: [makeKey(\"sk-1\"), makeKey(\"sk-2\")],\n\t\t\topenai: makeKey(\"sk-openai\"),\n\t\t});\n\t\tconst all = storage.getAll();\n\t\tassert.ok(all[\"anthropic\"]?.type === \"api_key\");\n\t\tassert.equal((all[\"anthropic\"] as any).key, \"sk-1\");\n\t\tassert.equal((all[\"openai\"] as any).key, \"sk-openai\");\n\t});\n});\n"]}
|
|
1
|
+
{"version":3,"file":"auth-storage.test.js","sourceRoot":"","sources":["../../src/core/auth-storage.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { describe, it } from \"node:test\";\nimport assert from \"node:assert/strict\";\nimport { AuthStorage } from \"./auth-storage.js\";\n\n// ─── helpers ──────────────────────────────────────────────────────────────────\n\nfunction makeKey(key: string) {\n\treturn { type: \"api_key\" as const, key };\n}\n\nfunction inMemory(data: Record<string, unknown> = {}) {\n\treturn AuthStorage.inMemory(data as any);\n}\n\n// ─── single credential (backward compat) ─────────────────────────────────────\n\ndescribe(\"AuthStorage — single credential (backward compat)\", () => {\n\tit(\"returns the api key for a provider with one key\", async () => {\n\t\tconst storage = inMemory({ anthropic: makeKey(\"sk-abc\") });\n\t\tconst key = await storage.getApiKey(\"anthropic\");\n\t\tassert.equal(key, \"sk-abc\");\n\t});\n\n\tit(\"returns undefined for unknown provider\", async () => {\n\t\tconst storage = inMemory({});\n\t\tconst key = await storage.getApiKey(\"unknown\");\n\t\tassert.equal(key, undefined);\n\t});\n\n\tit(\"runtime override takes precedence over stored key\", async () => {\n\t\tconst storage = inMemory({ anthropic: makeKey(\"sk-stored\") });\n\t\tstorage.setRuntimeApiKey(\"anthropic\", \"sk-runtime\");\n\t\tconst key = await storage.getApiKey(\"anthropic\");\n\t\tassert.equal(key, \"sk-runtime\");\n\t});\n});\n\n// ─── multiple credentials ─────────────────────────────────────────────────────\n\ndescribe(\"AuthStorage — multiple credentials\", () => {\n\tit(\"round-robins across multiple api keys without sessionId\", async () => {\n\t\tconst storage = inMemory({\n\t\t\tanthropic: [makeKey(\"sk-1\"), makeKey(\"sk-2\"), makeKey(\"sk-3\")],\n\t\t});\n\n\t\tconst keys = new Set<string>();\n\t\tfor (let i = 0; i < 6; i++) {\n\t\t\tconst k = await storage.getApiKey(\"anthropic\");\n\t\t\tassert.ok(k, `call ${i} should return a key`);\n\t\t\tkeys.add(k);\n\t\t}\n\t\t// All three keys should have been selected across 6 calls\n\t\tassert.deepEqual(keys, new Set([\"sk-1\", \"sk-2\", \"sk-3\"]));\n\t});\n\n\tit(\"session-sticky: same sessionId always picks the same key\", async () => {\n\t\tconst storage = inMemory({\n\t\t\tanthropic: [makeKey(\"sk-1\"), makeKey(\"sk-2\"), makeKey(\"sk-3\")],\n\t\t});\n\n\t\tconst sessionId = \"sess-abc\";\n\t\tconst first = await storage.getApiKey(\"anthropic\", sessionId);\n\t\tfor (let i = 0; i < 5; i++) {\n\t\t\tconst k = await storage.getApiKey(\"anthropic\", sessionId);\n\t\t\tassert.equal(k, first, `call ${i} should be sticky to first selection`);\n\t\t}\n\t});\n\n\tit(\"different sessionIds may select different keys\", async () => {\n\t\tconst storage = inMemory({\n\t\t\tanthropic: [makeKey(\"sk-1\"), makeKey(\"sk-2\"), makeKey(\"sk-3\")],\n\t\t});\n\n\t\tconst results = new Set<string>();\n\t\tfor (let i = 0; i < 20; i++) {\n\t\t\tconst k = await storage.getApiKey(\"anthropic\", `sess-${i}`);\n\t\t\tif (k) results.add(k);\n\t\t}\n\t\t// With 20 different sessions and 3 keys, we should see more than one key\n\t\tassert.ok(results.size > 1, \"multiple sessions should hash to different keys\");\n\t});\n});\n\n// ─── login accumulation ───────────────────────────────────────────────────────\n\ndescribe(\"AuthStorage — login accumulation\", () => {\n\tit(\"accumulates api keys on repeated set()\", () => {\n\t\tconst storage = inMemory({});\n\t\tstorage.set(\"anthropic\", makeKey(\"sk-1\"));\n\t\tstorage.set(\"anthropic\", makeKey(\"sk-2\"));\n\t\tconst creds = storage.getCredentialsForProvider(\"anthropic\");\n\t\tassert.equal(creds.length, 2);\n\t\tassert.deepEqual(\n\t\t\tcreds.map((c) => (c.type === \"api_key\" ? c.key : null)),\n\t\t\t[\"sk-1\", \"sk-2\"],\n\t\t);\n\t});\n\n\tit(\"deduplicates identical api keys\", () => {\n\t\tconst storage = inMemory({});\n\t\tstorage.set(\"anthropic\", makeKey(\"sk-1\"));\n\t\tstorage.set(\"anthropic\", makeKey(\"sk-1\"));\n\t\tconst creds = storage.getCredentialsForProvider(\"anthropic\");\n\t\tassert.equal(creds.length, 1);\n\t});\n});\n\n// ─── backoff / markUsageLimitReached ─────────────────────────────────────────\n\ndescribe(\"AuthStorage — rate-limit backoff\", () => {\n\tit(\"returns true when a backed-off credential has an alternate\", async () => {\n\t\tconst storage = inMemory({\n\t\t\tanthropic: [makeKey(\"sk-1\"), makeKey(\"sk-2\")],\n\t\t});\n\n\t\t// Use sk-1 via round-robin (first call, index 0)\n\t\tawait storage.getApiKey(\"anthropic\");\n\n\t\t// Mark it as rate-limited; sk-2 should still be available\n\t\tconst hasAlternate = storage.markUsageLimitReached(\"anthropic\");\n\t\tassert.equal(hasAlternate, true);\n\t});\n\n\tit(\"returns false when all credentials are backed off\", async () => {\n\t\tconst storage = inMemory({\n\t\t\tanthropic: [makeKey(\"sk-1\"), makeKey(\"sk-2\")],\n\t\t});\n\n\t\t// Back off both keys\n\t\tawait storage.getApiKey(\"anthropic\"); // uses index 0\n\t\tstorage.markUsageLimitReached(\"anthropic\"); // backs off index 0\n\t\tawait storage.getApiKey(\"anthropic\"); // uses index 1\n\t\tconst hasAlternate = storage.markUsageLimitReached(\"anthropic\"); // backs off index 1\n\t\tassert.equal(hasAlternate, false);\n\t});\n\n\tit(\"backed-off credential is skipped; next available key is returned\", async () => {\n\t\tconst storage = inMemory({\n\t\t\tanthropic: [makeKey(\"sk-1\"), makeKey(\"sk-2\")],\n\t\t});\n\n\t\t// First call → sk-1 (round-robin index 0)\n\t\tconst first = await storage.getApiKey(\"anthropic\");\n\t\tassert.equal(first, \"sk-1\");\n\n\t\t// Back off sk-1\n\t\tstorage.markUsageLimitReached(\"anthropic\");\n\n\t\t// Next call should skip backed-off sk-1 and return sk-2\n\t\tconst second = await storage.getApiKey(\"anthropic\");\n\t\tassert.equal(second, \"sk-2\");\n\t});\n\n\tit(\"single credential: markUsageLimitReached returns false\", async () => {\n\t\tconst storage = inMemory({ anthropic: makeKey(\"sk-only\") });\n\t\tawait storage.getApiKey(\"anthropic\");\n\t\tconst hasAlternate = storage.markUsageLimitReached(\"anthropic\");\n\t\tassert.equal(hasAlternate, false);\n\t});\n\n\tit(\"single credential: unknown error type skips backoff entirely\", async () => {\n\t\tconst storage = inMemory({ anthropic: makeKey(\"sk-only\") });\n\t\tawait storage.getApiKey(\"anthropic\");\n\n\t\t// Mark with unknown error type (transport failure)\n\t\tconst hasAlternate = storage.markUsageLimitReached(\"anthropic\", undefined, {\n\t\t\terrorType: \"unknown\",\n\t\t});\n\t\tassert.equal(hasAlternate, false);\n\n\t\t// Key should still be available — backoff was not applied\n\t\tconst key = await storage.getApiKey(\"anthropic\");\n\t\tassert.equal(key, \"sk-only\");\n\t});\n\n\tit(\"multiple credentials: unknown error type still backs off the used credential\", async () => {\n\t\tconst storage = inMemory({\n\t\t\tanthropic: [makeKey(\"sk-1\"), makeKey(\"sk-2\")],\n\t\t});\n\t\tawait storage.getApiKey(\"anthropic\"); // uses sk-1\n\n\t\t// Mark with unknown error type — should still back off when alternates exist\n\t\tconst hasAlternate = storage.markUsageLimitReached(\"anthropic\", undefined, {\n\t\t\terrorType: \"unknown\",\n\t\t});\n\t\tassert.equal(hasAlternate, true);\n\n\t\t// Next call should return sk-2\n\t\tconst key = await storage.getApiKey(\"anthropic\");\n\t\tassert.equal(key, \"sk-2\");\n\t});\n\n\tit(\"single credential: rate_limit error type still backs off\", async () => {\n\t\tconst storage = inMemory({ anthropic: makeKey(\"sk-only\") });\n\t\tawait storage.getApiKey(\"anthropic\");\n\n\t\t// rate_limit should still back off even single credentials\n\t\tconst hasAlternate = storage.markUsageLimitReached(\"anthropic\", undefined, {\n\t\t\terrorType: \"rate_limit\",\n\t\t});\n\t\tassert.equal(hasAlternate, false);\n\n\t\t// Key should be backed off\n\t\tconst key = await storage.getApiKey(\"anthropic\");\n\t\tassert.equal(key, undefined);\n\t});\n\n\tit(\"session-sticky: marks the correct credential as backed off\", async () => {\n\t\tconst storage = inMemory({\n\t\t\tanthropic: [makeKey(\"sk-1\"), makeKey(\"sk-2\")],\n\t\t});\n\n\t\tconst sessionId = \"sess-xyz\";\n\t\tconst chosen = await storage.getApiKey(\"anthropic\", sessionId);\n\t\tassert.ok(chosen);\n\n\t\t// Back off the chosen credential for this session\n\t\tconst hasAlternate = storage.markUsageLimitReached(\"anthropic\", sessionId);\n\t\tassert.equal(hasAlternate, true);\n\n\t\t// Next call with same session should return the other key\n\t\tconst next = await storage.getApiKey(\"anthropic\", sessionId);\n\t\tassert.ok(next);\n\t\tassert.notEqual(next, chosen);\n\t});\n});\n\n// ─── areAllCredentialsBackedOff ───────────────────────────────────────────────\n\ndescribe(\"AuthStorage — areAllCredentialsBackedOff\", () => {\n\tit(\"returns false when no credentials are configured\", () => {\n\t\tconst storage = inMemory({});\n\t\tassert.equal(storage.areAllCredentialsBackedOff(\"anthropic\"), false);\n\t});\n\n\tit(\"returns false when credentials exist and none are backed off\", async () => {\n\t\tconst storage = inMemory({ anthropic: makeKey(\"sk-abc\") });\n\t\tassert.equal(storage.areAllCredentialsBackedOff(\"anthropic\"), false);\n\t});\n\n\tit(\"returns true when the single credential is backed off\", async () => {\n\t\tconst storage = inMemory({ anthropic: makeKey(\"sk-only\") });\n\t\tawait storage.getApiKey(\"anthropic\");\n\t\tstorage.markUsageLimitReached(\"anthropic\");\n\t\tassert.equal(storage.areAllCredentialsBackedOff(\"anthropic\"), true);\n\t});\n\n\tit(\"returns false when at least one credential is still available\", async () => {\n\t\tconst storage = inMemory({ anthropic: [makeKey(\"sk-1\"), makeKey(\"sk-2\")] });\n\t\tawait storage.getApiKey(\"anthropic\"); // uses index 0\n\t\tstorage.markUsageLimitReached(\"anthropic\"); // backs off index 0\n\t\t// index 1 is still available\n\t\tassert.equal(storage.areAllCredentialsBackedOff(\"anthropic\"), false);\n\t});\n\n\tit(\"returns true when all credentials are backed off\", async () => {\n\t\tconst storage = inMemory({ anthropic: [makeKey(\"sk-1\"), makeKey(\"sk-2\")] });\n\t\tawait storage.getApiKey(\"anthropic\"); // uses index 0\n\t\tstorage.markUsageLimitReached(\"anthropic\"); // backs off index 0\n\t\tawait storage.getApiKey(\"anthropic\"); // uses index 1\n\t\tstorage.markUsageLimitReached(\"anthropic\"); // backs off index 1\n\t\tassert.equal(storage.areAllCredentialsBackedOff(\"anthropic\"), true);\n\t});\n});\n\n// ─── mismatched oauth credential for non-OAuth provider (#2083) ───────────────\n\ndescribe(\"AuthStorage — oauth credential for non-OAuth provider (#2083)\", () => {\n\tit(\"returns undefined when openrouter has type:oauth (no registered OAuth provider)\", async (t) => {\n\t\t// Simulates the bug: OpenRouter credential stored as type:\"oauth\"\n\t\t// but OpenRouter is not a registered OAuth provider.\n\t\tconst storage = inMemory({\n\t\t\topenrouter: {\n\t\t\t\ttype: \"oauth\",\n\t\t\t\taccess_token: \"sk-or-v1-fake\",\n\t\t\t\trefresh_token: \"rt-fake\",\n\t\t\t\texpires: Date.now() + 3_600_000,\n\t\t\t},\n\t\t});\n\n\t\t// Isolate from any real OPENROUTER_API_KEY in the environment so the\n\t\t// fall-through to env / fallback finds nothing and returns undefined.\n\t\tconst origEnv = process.env.OPENROUTER_API_KEY;\n\t\tdelete process.env.OPENROUTER_API_KEY;\n\t\tt.after(() => {\n\t\t\tif (origEnv === undefined) {\n\t\t\t\tdelete process.env.OPENROUTER_API_KEY;\n\t\t\t} else {\n\t\t\t\tprocess.env.OPENROUTER_API_KEY = origEnv;\n\t\t\t}\n\t\t});\n\n\t\t// Before the fix, getApiKey returns undefined because\n\t\t// resolveCredentialApiKey calls getOAuthProvider(\"openrouter\") → null → undefined.\n\t\t// The key in the oauth credential is never extracted.\n\t\tconst key = await storage.getApiKey(\"openrouter\");\n\t\t// After the fix, the oauth credential with an unrecognised provider\n\t\t// should be skipped, and getApiKey should fall through to env / fallback.\n\t\t// With no env var and no fallback resolver configured, the result is undefined.\n\t\tassert.equal(key, undefined);\n\t});\n\n\tit(\"falls through to env var when openrouter has type:oauth credential\", async (t) => {\n\t\tconst storage = inMemory({\n\t\t\topenrouter: {\n\t\t\t\ttype: \"oauth\",\n\t\t\t\taccess_token: \"sk-or-v1-fake\",\n\t\t\t\trefresh_token: \"rt-fake\",\n\t\t\t\texpires: Date.now() + 3_600_000,\n\t\t\t},\n\t\t});\n\n\t\t// Simulate OPENROUTER_API_KEY being set via env\n\t\tconst origEnv = process.env.OPENROUTER_API_KEY;\n\t\tt.after(() => {\n\t\t\tif (origEnv === undefined) {\n\t\t\t\tdelete process.env.OPENROUTER_API_KEY;\n\t\t\t} else {\n\t\t\t\tprocess.env.OPENROUTER_API_KEY = origEnv;\n\t\t\t}\n\t\t});\n\n\t\tprocess.env.OPENROUTER_API_KEY = \"sk-or-v1-env-key\";\n\t\tconst key = await storage.getApiKey(\"openrouter\");\n\t\tassert.equal(key, \"sk-or-v1-env-key\");\n\t});\n\n\tit(\"falls through to fallback resolver when openrouter has type:oauth credential\", async (t) => {\n\t\tconst storage = inMemory({\n\t\t\topenrouter: {\n\t\t\t\ttype: \"oauth\",\n\t\t\t\taccess_token: \"sk-or-v1-fake\",\n\t\t\t\trefresh_token: \"rt-fake\",\n\t\t\t\texpires: Date.now() + 3_600_000,\n\t\t\t},\n\t\t});\n\n\t\t// Isolate from any real OPENROUTER_API_KEY so env fallback is skipped\n\t\t// and the fallback resolver is reached.\n\t\tconst origEnv = process.env.OPENROUTER_API_KEY;\n\t\tdelete process.env.OPENROUTER_API_KEY;\n\t\tt.after(() => {\n\t\t\tif (origEnv === undefined) {\n\t\t\t\tdelete process.env.OPENROUTER_API_KEY;\n\t\t\t} else {\n\t\t\t\tprocess.env.OPENROUTER_API_KEY = origEnv;\n\t\t\t}\n\t\t});\n\n\t\tstorage.setFallbackResolver((provider) =>\n\t\t\tprovider === \"openrouter\" ? \"sk-or-v1-fallback\" : undefined,\n\t\t);\n\n\t\tconst key = await storage.getApiKey(\"openrouter\");\n\t\tassert.equal(key, \"sk-or-v1-fallback\");\n\t});\n});\n\n// ─── getAll truncation ────────────────────────────────────────────────────────\n\ndescribe(\"AuthStorage — getAll()\", () => {\n\tit(\"returns first credential only for providers with multiple keys\", () => {\n\t\tconst storage = inMemory({\n\t\t\tanthropic: [makeKey(\"sk-1\"), makeKey(\"sk-2\")],\n\t\t\topenai: makeKey(\"sk-openai\"),\n\t\t});\n\t\tconst all = storage.getAll();\n\t\tassert.ok(all[\"anthropic\"]?.type === \"api_key\");\n\t\tassert.equal((all[\"anthropic\"] as any).key, \"sk-1\");\n\t\tassert.equal((all[\"openai\"] as any).key, \"sk-openai\");\n\t});\n});\n"]}
|
|
@@ -266,7 +266,7 @@ describe("AuthStorage — areAllCredentialsBackedOff", () => {
|
|
|
266
266
|
// ─── mismatched oauth credential for non-OAuth provider (#2083) ───────────────
|
|
267
267
|
|
|
268
268
|
describe("AuthStorage — oauth credential for non-OAuth provider (#2083)", () => {
|
|
269
|
-
it("returns undefined when openrouter has type:oauth (no registered OAuth provider)", async () => {
|
|
269
|
+
it("returns undefined when openrouter has type:oauth (no registered OAuth provider)", async (t) => {
|
|
270
270
|
// Simulates the bug: OpenRouter credential stored as type:"oauth"
|
|
271
271
|
// but OpenRouter is not a registered OAuth provider.
|
|
272
272
|
const storage = inMemory({
|
|
@@ -278,12 +278,25 @@ describe("AuthStorage — oauth credential for non-OAuth provider (#2083)", () =
|
|
|
278
278
|
},
|
|
279
279
|
});
|
|
280
280
|
|
|
281
|
+
// Isolate from any real OPENROUTER_API_KEY in the environment so the
|
|
282
|
+
// fall-through to env / fallback finds nothing and returns undefined.
|
|
283
|
+
const origEnv = process.env.OPENROUTER_API_KEY;
|
|
284
|
+
delete process.env.OPENROUTER_API_KEY;
|
|
285
|
+
t.after(() => {
|
|
286
|
+
if (origEnv === undefined) {
|
|
287
|
+
delete process.env.OPENROUTER_API_KEY;
|
|
288
|
+
} else {
|
|
289
|
+
process.env.OPENROUTER_API_KEY = origEnv;
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
|
|
281
293
|
// Before the fix, getApiKey returns undefined because
|
|
282
294
|
// resolveCredentialApiKey calls getOAuthProvider("openrouter") → null → undefined.
|
|
283
295
|
// The key in the oauth credential is never extracted.
|
|
284
296
|
const key = await storage.getApiKey("openrouter");
|
|
285
297
|
// After the fix, the oauth credential with an unrecognised provider
|
|
286
298
|
// should be skipped, and getApiKey should fall through to env / fallback.
|
|
299
|
+
// With no env var and no fallback resolver configured, the result is undefined.
|
|
287
300
|
assert.equal(key, undefined);
|
|
288
301
|
});
|
|
289
302
|
|
|
@@ -312,7 +325,7 @@ describe("AuthStorage — oauth credential for non-OAuth provider (#2083)", () =
|
|
|
312
325
|
assert.equal(key, "sk-or-v1-env-key");
|
|
313
326
|
});
|
|
314
327
|
|
|
315
|
-
it("falls through to fallback resolver when openrouter has type:oauth credential", async () => {
|
|
328
|
+
it("falls through to fallback resolver when openrouter has type:oauth credential", async (t) => {
|
|
316
329
|
const storage = inMemory({
|
|
317
330
|
openrouter: {
|
|
318
331
|
type: "oauth",
|
|
@@ -322,6 +335,18 @@ describe("AuthStorage — oauth credential for non-OAuth provider (#2083)", () =
|
|
|
322
335
|
},
|
|
323
336
|
});
|
|
324
337
|
|
|
338
|
+
// Isolate from any real OPENROUTER_API_KEY so env fallback is skipped
|
|
339
|
+
// and the fallback resolver is reached.
|
|
340
|
+
const origEnv = process.env.OPENROUTER_API_KEY;
|
|
341
|
+
delete process.env.OPENROUTER_API_KEY;
|
|
342
|
+
t.after(() => {
|
|
343
|
+
if (origEnv === undefined) {
|
|
344
|
+
delete process.env.OPENROUTER_API_KEY;
|
|
345
|
+
} else {
|
|
346
|
+
process.env.OPENROUTER_API_KEY = origEnv;
|
|
347
|
+
}
|
|
348
|
+
});
|
|
349
|
+
|
|
325
350
|
storage.setFallbackResolver((provider) =>
|
|
326
351
|
provider === "openrouter" ? "sk-or-v1-fallback" : undefined,
|
|
327
352
|
);
|
package/pkg/package.json
CHANGED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code CLI Provider Extension
|
|
3
|
+
*
|
|
4
|
+
* Registers a model provider that delegates inference to the user's
|
|
5
|
+
* locally-installed Claude Code CLI via the official Agent SDK.
|
|
6
|
+
*
|
|
7
|
+
* Users with a Claude Code subscription (Pro/Max/Team) get access to
|
|
8
|
+
* subsidized inference through GSD's UI — no API key required.
|
|
9
|
+
*
|
|
10
|
+
* TOS-compliant: uses Anthropic's official `@anthropic-ai/claude-agent-sdk`,
|
|
11
|
+
* never touches credentials, never offers a login flow.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { ExtensionAPI } from "@gsd/pi-coding-agent";
|
|
15
|
+
import { CLAUDE_CODE_MODELS } from "./models.js";
|
|
16
|
+
import { isClaudeCodeReady } from "./readiness.js";
|
|
17
|
+
import { streamViaClaudeCode } from "./stream-adapter.js";
|
|
18
|
+
|
|
19
|
+
export default function claudeCodeCli(pi: ExtensionAPI) {
|
|
20
|
+
pi.registerProvider("claude-code", {
|
|
21
|
+
authMode: "externalCli",
|
|
22
|
+
api: "anthropic-messages",
|
|
23
|
+
baseUrl: "local://claude-code",
|
|
24
|
+
isReady: isClaudeCodeReady,
|
|
25
|
+
streamSimple: streamViaClaudeCode,
|
|
26
|
+
models: CLAUDE_CODE_MODELS,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model definitions for the Claude Code CLI provider.
|
|
3
|
+
*
|
|
4
|
+
* Costs are zero because inference is covered by the user's Claude Code
|
|
5
|
+
* subscription. The SDK's `result` message still provides token counts
|
|
6
|
+
* for display in the TUI.
|
|
7
|
+
*
|
|
8
|
+
* Context windows and max tokens match the Anthropic API definitions
|
|
9
|
+
* in models.generated.ts.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const ZERO_COST = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
13
|
+
|
|
14
|
+
export const CLAUDE_CODE_MODELS = [
|
|
15
|
+
{
|
|
16
|
+
id: "claude-opus-4-6",
|
|
17
|
+
name: "Claude Opus 4.6 (via Claude Code)",
|
|
18
|
+
reasoning: true,
|
|
19
|
+
input: ["text", "image"] as ("text" | "image")[],
|
|
20
|
+
cost: ZERO_COST,
|
|
21
|
+
contextWindow: 1_000_000,
|
|
22
|
+
maxTokens: 128_000,
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
id: "claude-sonnet-4-6",
|
|
26
|
+
name: "Claude Sonnet 4.6 (via Claude Code)",
|
|
27
|
+
reasoning: true,
|
|
28
|
+
input: ["text", "image"] as ("text" | "image")[],
|
|
29
|
+
cost: ZERO_COST,
|
|
30
|
+
contextWindow: 1_000_000,
|
|
31
|
+
maxTokens: 64_000,
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: "claude-haiku-4-5",
|
|
35
|
+
name: "Claude Haiku 4.5 (via Claude Code)",
|
|
36
|
+
reasoning: true,
|
|
37
|
+
input: ["text", "image"] as ("text" | "image")[],
|
|
38
|
+
cost: ZERO_COST,
|
|
39
|
+
contextWindow: 200_000,
|
|
40
|
+
maxTokens: 64_000,
|
|
41
|
+
},
|
|
42
|
+
];
|