feat(core): add connector authentication (#31837)

This commit is contained in:
Dax
2026-06-11 06:31:17 +00:00
committed by GitHub
parent bf05e8a122
commit dac0dd5309
47 changed files with 5433 additions and 862 deletions
+25 -1
View File
@@ -2,6 +2,7 @@ import { useEvent } from "./event"
import type {
AgentV2Info,
CommandV2Info,
ConnectorInfo,
Event,
LocationRef,
ModelV2Info,
@@ -26,6 +27,7 @@ import { createSignal, onMount } from "solid-js"
type LocationData = {
agent?: AgentV2Info[]
command?: CommandV2Info[]
connector?: ConnectorInfo[]
model?: ModelV2Info[]
provider?: ProviderV2Info[]
reference?: ReferenceInfo[]
@@ -119,7 +121,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
},
}
event.subscribe((event) => {
event.subscribe((event, metadata) => {
switch (event.type) {
case "session.next.agent.switched":
message.update(event.properties.sessionID, (draft) => {
@@ -421,6 +423,17 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
case "reference.updated":
void result.location.reference.refresh()
break
case "credential.switched": {
const location = { directory: metadata.directory, workspaceID: metadata.workspace }
void Promise.allSettled([
result.location.model.refresh(location),
result.location.provider.refresh(location),
])
break
}
case "connector.updated":
void result.location.connector.refresh({ directory: metadata.directory, workspaceID: metadata.workspace })
break
}
})
@@ -503,6 +516,16 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
setStore("location", key, "command", result.data.data)
},
},
connector: {
list(location?: LocationRef) {
return store.location[locationKey(location ?? defaultLocation())]?.connector
},
async refresh(ref?: LocationRef) {
const result = await sdk.client.v2.connector.list({ location: locationQuery(ref) }, { throwOnError: true })
const key = locationKey(result.data.location)
setStore("location", key, "connector", result.data.data)
},
},
model: {
list(location?: LocationRef) {
return store.location[locationKey(location ?? defaultLocation())]?.model
@@ -550,6 +573,7 @@ export const { use: useData, provider: DataProvider } = createSimpleContext({
void Promise.allSettled([
result.location.refresh(),
result.location.agent.refresh(),
result.location.connector.refresh(),
result.location.model.refresh(),
result.location.provider.refresh(),
result.location.reference.refresh(),
+2 -1
View File
@@ -2,6 +2,7 @@ import type { Event } from "@opencode-ai/sdk/v2"
import { useSDK } from "./sdk"
type EventMetadata = {
directory: string
workspace: string | undefined
}
@@ -14,7 +15,7 @@ export function useEvent() {
return
}
handler(event.payload, { workspace: event.workspace })
handler(event.payload, { directory: event.directory, workspace: event.workspace })
})
}
+57
View File
@@ -92,6 +92,63 @@ test("refreshes resources into reactive getters", async () => {
}
})
test("refreshes connectors after connector updates", async () => {
const events = createEventSource()
let requests = 0
const calls = createFetch((url) => {
if (url.pathname !== "/api/connector") return
requests++
return json({
location: { directory, project: { id: "proj_test", directory } },
data:
requests === 1
? []
: [
{
id: "openai",
name: "OpenAI",
methods: [{ id: "api-key", type: "key", label: "API Key" }],
},
],
})
})
let data!: ReturnType<typeof useData>
let ready!: () => void
const mounted = new Promise<void>((resolve) => {
ready = resolve
})
function Probe() {
data = useData()
onMount(ready)
return <box />
}
const app = await testRender(() => (
<TestTuiContexts>
<SDKProvider url="http://test" directory={directory} events={events.source} fetch={calls.fetch}>
<ProjectProvider>
<DataProvider>
<Probe />
</DataProvider>
</ProjectProvider>
</SDKProvider>
</TestTuiContexts>
))
try {
await mounted
await wait(() => data.location.connector.list() !== undefined)
expect(data.location.connector.list()).toEqual([])
emitEvent(events, { id: "evt_connector", type: "connector.updated", properties: {} })
await wait(() => data.location.connector.list()?.length === 1)
expect(data.location.connector.list()?.[0]).toMatchObject({ id: "openai", name: "OpenAI" })
} finally {
app.renderer.destroy()
}
})
test("refreshes references after updates", async () => {
const events = createEventSource()
let requests = 0
+1 -1
View File
@@ -60,7 +60,7 @@ export function createFetch(override?: FetchHandler) {
if (url.pathname === "/experimental/console") return json({ consoleManagedProviders: [], switchableOrgCount: 0 })
if (url.pathname === "/path") return json({ home: "", state: "", config: "", worktree, directory })
if (url.pathname === "/api/location") return json({ directory, project: { id: "proj_test", directory: worktree } })
if (["/api/agent", "/api/model", "/api/provider", "/api/command", "/api/skill"].includes(url.pathname))
if (["/api/agent", "/api/model", "/api/provider", "/api/connector", "/api/command", "/api/skill"].includes(url.pathname))
return json({
location: { directory, project: { id: "proj_test", directory: worktree } },
data: [],