pipeline-moe 0.1.3 → 0.1.5
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/package.json
CHANGED
package/src/server.ts
CHANGED
|
@@ -717,6 +717,15 @@ async function main(): Promise<void> {
|
|
|
717
717
|
}
|
|
718
718
|
})
|
|
719
719
|
|
|
720
|
+
// Pending manual inputs for in-flight OAuth flows (pasted redirect URL or
|
|
721
|
+
// authorization code), keyed by provider. Resolved by the /login/input route,
|
|
722
|
+
// rejected by DELETE /login (cancel) — rejecting is what makes pi's flow
|
|
723
|
+
// abort and release the localhost callback port instead of waiting forever.
|
|
724
|
+
const pendingOAuthInputs = new Map<
|
|
725
|
+
string,
|
|
726
|
+
{ resolve: (value: string) => void; reject: (err: Error) => void }
|
|
727
|
+
>()
|
|
728
|
+
|
|
720
729
|
// Start OAuth login flow for a provider (device-code or auth URL).
|
|
721
730
|
app.post("/api/providers/:name/login", async (req, res) => {
|
|
722
731
|
const name = req.params.name
|
|
@@ -731,9 +740,22 @@ async function main(): Promise<void> {
|
|
|
731
740
|
return
|
|
732
741
|
}
|
|
733
742
|
|
|
743
|
+
// Retrying while a previous flow for this provider is still waiting would
|
|
744
|
+
// collide on pi's fixed callback port (EADDRINUSE) — abort the old flow
|
|
745
|
+
// first so a retry always gets a fresh start.
|
|
746
|
+
const stale = pendingOAuthInputs.get(name)
|
|
747
|
+
if (stale) {
|
|
748
|
+
pendingOAuthInputs.delete(name)
|
|
749
|
+
stale.reject(new Error("Login restarted"))
|
|
750
|
+
await new Promise((r) => setTimeout(r, 100)) // let the old flow release the port
|
|
751
|
+
}
|
|
752
|
+
|
|
734
753
|
// Start the login flow in the background — communicate progress via SSE.
|
|
735
754
|
// Return 202 immediately so the client can wait for SSE events.
|
|
736
755
|
const providerName = name // capture for closure
|
|
756
|
+
// Identity of THIS attempt's pending entry: the finally below must only
|
|
757
|
+
// clear the map when the entry is still ours, never a newer attempt's.
|
|
758
|
+
let myEntry: { resolve: (value: string) => void; reject: (err: Error) => void } | undefined
|
|
737
759
|
setImmediate(async () => {
|
|
738
760
|
try {
|
|
739
761
|
await resolved.authStorage.login(name, {
|
|
@@ -760,14 +782,28 @@ async function main(): Promise<void> {
|
|
|
760
782
|
message,
|
|
761
783
|
})
|
|
762
784
|
},
|
|
785
|
+
// Manual completion path: providers like Anthropic race a localhost
|
|
786
|
+
// callback server against a pasted redirect URL. Without this
|
|
787
|
+
// callback the flow waits on the callback forever when the browser
|
|
788
|
+
// is on another machine. The promise resolves when a client POSTs
|
|
789
|
+
// /login/input; cleanup happens in the outer finally.
|
|
790
|
+
onManualCodeInput: () =>
|
|
791
|
+
new Promise<string>((resolve, reject) => {
|
|
792
|
+
myEntry = { resolve, reject }
|
|
793
|
+
pendingOAuthInputs.set(providerName, myEntry)
|
|
794
|
+
}),
|
|
763
795
|
onPrompt: async (prompt) => {
|
|
764
|
-
//
|
|
796
|
+
// Ask the clients for input over SSE and wait for /login/input.
|
|
765
797
|
hub.broadcast("oauth_progress", {
|
|
766
798
|
provider: providerName,
|
|
767
|
-
type: "
|
|
768
|
-
message:
|
|
799
|
+
type: "prompt",
|
|
800
|
+
message: prompt.message,
|
|
801
|
+
placeholder: (prompt as { placeholder?: string }).placeholder,
|
|
802
|
+
})
|
|
803
|
+
return new Promise<string>((resolve, reject) => {
|
|
804
|
+
myEntry = { resolve, reject }
|
|
805
|
+
pendingOAuthInputs.set(providerName, myEntry)
|
|
769
806
|
})
|
|
770
|
-
throw new Error("interactive prompt not supported in headless mode")
|
|
771
807
|
},
|
|
772
808
|
onSelect: async () => {
|
|
773
809
|
hub.broadcast("oauth_progress", {
|
|
@@ -797,12 +833,49 @@ async function main(): Promise<void> {
|
|
|
797
833
|
type: "error",
|
|
798
834
|
message: msg,
|
|
799
835
|
})
|
|
836
|
+
} finally {
|
|
837
|
+
if (myEntry && pendingOAuthInputs.get(providerName) === myEntry) {
|
|
838
|
+
pendingOAuthInputs.delete(providerName)
|
|
839
|
+
}
|
|
800
840
|
}
|
|
801
841
|
})
|
|
802
842
|
|
|
803
843
|
res.status(202).json({ accepted: true, provider: name })
|
|
804
844
|
})
|
|
805
845
|
|
|
846
|
+
// Feed manual input (pasted redirect URL / authorization code) into an
|
|
847
|
+
// in-flight OAuth login for this provider.
|
|
848
|
+
app.post("/api/providers/:name/login/input", (req, res) => {
|
|
849
|
+
const name = req.params.name
|
|
850
|
+
const resolveInput = pendingOAuthInputs.get(name)
|
|
851
|
+
if (!resolveInput) {
|
|
852
|
+
res.status(404).json({ error: `no OAuth flow waiting for input for "${name}"` })
|
|
853
|
+
return
|
|
854
|
+
}
|
|
855
|
+
const value = String((req.body as { value?: unknown } | undefined)?.value ?? "").trim()
|
|
856
|
+
if (!value) {
|
|
857
|
+
res.status(400).json({ error: "value is required" })
|
|
858
|
+
return
|
|
859
|
+
}
|
|
860
|
+
pendingOAuthInputs.delete(name)
|
|
861
|
+
resolveInput.resolve(value)
|
|
862
|
+
res.json({ ok: true })
|
|
863
|
+
})
|
|
864
|
+
|
|
865
|
+
// Cancel an in-flight OAuth login: rejecting the pending input makes pi's
|
|
866
|
+
// flow abort and release its localhost callback port.
|
|
867
|
+
app.delete("/api/providers/:name/login", (req, res) => {
|
|
868
|
+
const name = req.params.name
|
|
869
|
+
const pending = pendingOAuthInputs.get(name)
|
|
870
|
+
if (!pending) {
|
|
871
|
+
res.status(404).json({ error: `no OAuth flow in flight for "${name}"` })
|
|
872
|
+
return
|
|
873
|
+
}
|
|
874
|
+
pendingOAuthInputs.delete(name)
|
|
875
|
+
pending.reject(new Error("Login cancelled"))
|
|
876
|
+
res.json({ ok: true })
|
|
877
|
+
})
|
|
878
|
+
|
|
806
879
|
// Remove credentials for a provider.
|
|
807
880
|
app.delete("/api/providers/:name", async (req, res) => {
|
|
808
881
|
const name = req.params.name
|